Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/desktop-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,9 @@ jobs:
- name: Verify release version metadata
run: node scripts/verify-release-version-sync.mjs --version "${{ needs.prepare.outputs.version }}"

- name: Download and verify target Flashgrep binary
run: node scripts/prepare-flashgrep-resource.mjs --target "${{ matrix.platform.target }}"

- name: Build desktop app
run: ${{ matrix.platform.build_command }}

Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,7 @@ src/apps/mobile/android/.kotlin/
/src/apps/mobile/ios/build/
.openbitfun/
.bitfun/

# Downloaded Flashgrep release assets (metadata stays tracked).
/resources/flashgrep/flashgrep-*
/resources/flashgrep/*.download-*
2 changes: 2 additions & 0 deletions OpenBitFun-Installer/scripts/build-installer.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const REQUIRED_PAYLOAD_FILES = [
"mobile-web/dist/index.html",
"resources/ext-host/extension-host.js",
"resources/worker_host.js",
"flashgrep/flashgrep-x86_64-pc-windows-msvc.exe",
];

function getMode(args) {
Expand Down Expand Up @@ -288,6 +289,7 @@ if (appExePath) {
"swiftshader",
"mobile-web",
"frontend",
"flashgrep",
];
for (const dirName of runtimeDirs) {
const srcDir = path.join(releaseDir, dirName);
Expand Down
3 changes: 2 additions & 1 deletion OpenBitFun-Installer/src-tauri/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ use std::path::{Path, PathBuf};
use zip::write::FileOptions;
use zip::{CompressionMethod, ZipWriter};

const REQUIRED_PAYLOAD_FILES: [&str; 6] = [
const REQUIRED_PAYLOAD_FILES: [&str; 7] = [
"openbitfun-desktop.exe",
"openbitfun-data-migrator.exe",
"frontend/dist/index.html",
"mobile-web/dist/index.html",
"resources/ext-host/extension-host.js",
"resources/worker_host.js",
"flashgrep/flashgrep-x86_64-pc-windows-msvc.exe",
];

fn main() {
Expand Down
3 changes: 2 additions & 1 deletion OpenBitFun-Installer/src-tauri/src/installer/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,14 @@ struct WindowsInstallState {

const MIN_WINDOWS_APP_EXE_BYTES: u64 = 5 * 1024 * 1024;
const PAYLOAD_MANIFEST_FILE: &str = "payload-manifest.json";
const REQUIRED_PAYLOAD_FILES: [&str; 6] = [
const REQUIRED_PAYLOAD_FILES: [&str; 7] = [
MAIN_APP_EXE,
DATA_MIGRATOR_EXE,
"frontend/dist/index.html",
"mobile-web/dist/index.html",
"resources/ext-host/extension-host.js",
"resources/worker_host.js",
"flashgrep/flashgrep-x86_64-pc-windows-msvc.exe",
];
const INSTALLER_STATE_FILE: &str = "installer-state.json";
#[cfg(target_os = "windows")]
Expand Down
5 changes: 5 additions & 0 deletions docs/features/remote-workspaces.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Remote SSH, container, and WSL workspaces

Flashgrep accelerated search is currently unavailable for remote workspaces.
Index controls are hidden; content-search operations that depend on Flashgrep
return an explicit unsupported error. Remote file-name search remains available.
OpenBitFun never falls back to searching the controller filesystem for a remote path.

OpenBitFun remote workspaces use one saved target for the file explorer, terminal,
Agent commands, and workspace tools. The target can be:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8401,7 +8401,7 @@
"rustPath": "search_build_index",
"sourceFile": "src/apps/desktop/src/api/search_api.rs",
"signature": "fn search_build_index( state: State<'_, AppState>, request: SearchRepoIndexRequest, ) -> Result<serde_json::Value, String>",
"remoteWorkspacePolicy": "RemoteRouted"
"remoteWorkspacePolicy": "RemoteUnsupported"
},
{
"id": "search_file_contents",
Expand Down Expand Up @@ -8465,7 +8465,7 @@
"rustPath": "search_get_repo_status",
"sourceFile": "src/apps/desktop/src/api/search_api.rs",
"signature": "fn search_get_repo_status( state: State<'_, AppState>, request: SearchRepoIndexRequest, ) -> Result<serde_json::Value, String>",
"remoteWorkspacePolicy": "RemoteRouted"
"remoteWorkspacePolicy": "RemoteUnsupported"
},
{
"id": "search_rebuild_index",
Expand All @@ -8483,7 +8483,7 @@
"rustPath": "search_rebuild_index",
"sourceFile": "src/apps/desktop/src/api/search_api.rs",
"signature": "fn search_rebuild_index( state: State<'_, AppState>, request: SearchRepoIndexRequest, ) -> Result<serde_json::Value, String>",
"remoteWorkspacePolicy": "RemoteRouted"
"remoteWorkspacePolicy": "RemoteUnsupported"
},
{
"id": "search_referenceable_sessions",
Expand Down
47 changes: 31 additions & 16 deletions resources/flashgrep/README.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,37 @@
Flashgrep distribution is temporarily suspended. The platform binaries have been removed,
and desktop development, packaging, and the Windows installer no longer require them.
The Web UI hides accelerated-search settings and index controls, including on remote
workspaces and peer devices. Saved preferences and backend implementations are retained.
# Flashgrep binary distribution

To restore the feature, restore the binaries and desktop preparation/bundling steps,
then enable `WORKSPACE_SEARCH_AVAILABLE` in the Web UI.
Desktop development and packaging download the pinned release from
https://github.com/wgqqqqq/flashgrep-binaries/releases. No binaries belong in Git.
`VERSION.json` pins the repository, tag, filename, size, and SHA-256 for every target.

Pinned release:
Run `node scripts/prepare-flashgrep-resource.mjs` for the current host, or pass
`--target x86_64-pc-windows-msvc` (including `--target=...` syntax). Desktop builds
use the explicit Tauri target, falling back to the Rust host triple. Linux GNU
application targets use the matching musl Flashgrep binary. Unsupported targets
fail explicitly. The preparation step requires curl (curl.exe on Windows).

- `v0.2.16` from `wgqqqqq/flashgrep`
Downloads go to ignored files in this directory. Cached files are verified before
reuse; failed or corrupted downloads never become package inputs. Only the
selected binary is added to Tauri resources, even if the cache holds other targets.
macOS release builds sign a copy with APPLE_SIGNING_IDENTITY, preserving the
original download and its checksum. The Windows custom installer currently targets
x86_64 and requires its matching Flashgrep payload.

Expected filenames (restoration reference):
Remote workspaces do not support Flashgrep. Index controls are hidden and remote
index/content/glob service calls return an explicit unsupported error before SSH
or local filesystem access. Remote file-name search and agent search paths that
already use remote shell tools remain available. Saved preferences are retained.

- macOS x86_64: `flashgrep-x86_64-apple-darwin`
- macOS arm64: `flashgrep-aarch64-apple-darwin`
- Linux x86_64: `flashgrep-x86_64-unknown-linux-musl`
- Linux arm64: `flashgrep-aarch64-unknown-linux-musl`
- Windows x86_64: `flashgrep-x86_64-pc-windows-msvc.exe`
- Windows arm64: `flashgrep-aarch64-pc-windows-msvc.exe`
## Updating the pinned release

macOS binaries are ad-hoc signed after download so local development can execute them directly.
Publish the six standalone binaries and SHA256SUMS to the public binary repository
as Release assets, without publishing private source or MCP bundles. Verify the
release, then update VERSION.json with its tag and asset checksums/sizes. Do not use
`latest` or replace assets of an already consumed release; publish a new version.
OpenBitFun builds only need public download access, with no private-repository token.

## Focused verification

```sh
node --test scripts/prepare-flashgrep-resource.test.mjs scripts/desktop-tauri-build.test.mjs OpenBitFun-Installer/scripts/build-installer.test.cjs
```
29 changes: 27 additions & 2 deletions resources/flashgrep/VERSION.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,30 @@
{
"repo": "wgqqqqq/flashgrep",
"repo": "wgqqqqq/flashgrep-binaries",
"tag": "v0.2.16",
"published_at": "2026-09-01T09:14:49Z"
"assets": {
"flashgrep-aarch64-apple-darwin": {
"sha256": "f35051821426b51409afdf13f927ce3e3270ef9eb938c5f60d6bbafa09cbc783",
"size": 8835776
},
"flashgrep-aarch64-pc-windows-msvc.exe": {
"sha256": "a6dc3912f7fd65428a68a5a061179c7dd951531aa6db176dadfb651519e36cc6",
"size": 7029760
},
"flashgrep-aarch64-unknown-linux-musl": {
"sha256": "2702a4f3e34c45e9e322c6ac5b0a842a789a617d41b15241117f5097889f907c",
"size": 9953280
},
"flashgrep-x86_64-apple-darwin": {
"sha256": "f81bf953eb8680ce896b66b997cff8ade25681b0f9b6c290b8130c57f035a14f",
"size": 9423968
},
"flashgrep-x86_64-pc-windows-msvc.exe": {
"sha256": "96684c3a946d90a42d3cfca3a7c511a41422eb7e0cb702801179ba363d9d6ed0",
"size": 8240128
},
"flashgrep-x86_64-unknown-linux-musl": {
"sha256": "e09eb8c187512c3a29eea89126ba814e411d53417af5c0cfd68a28f253a2bcc4",
"size": 10638320
}
}
}
32 changes: 9 additions & 23 deletions scripts/desktop-tauri-build.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
statSync,
writeFileSync,
} from 'fs';
import { ensureFlashgrepBinary } from './prepare-flashgrep-resource.mjs';
import { extractProductConfigArg } from './product-customization/cli.mjs';
import { productBuildEnvironment } from './product-customization/projections.mjs';
import { resolveProductDefinition } from './product-customization/resolver.mjs';
Expand All @@ -24,12 +25,6 @@ import {

const __dirname = dirname(fileURLToPath(import.meta.url));
const ROOT = join(__dirname, '..');
const LINUX_FLASHGREP_BINARIES = [
'flashgrep-x86_64-unknown-linux-musl',
'flashgrep-x86_64-unknown-linux-gnu',
'flashgrep-aarch64-unknown-linux-musl',
'flashgrep-aarch64-unknown-linux-gnu',
];

function tauriBuildArgsFromArgv() {
const args = process.argv.slice(2);
Expand All @@ -53,8 +48,11 @@ async function main() {

const desktopDir = join(ROOT, 'src', 'apps', 'desktop');
preparePluginHost();
// Flashgrep distribution is temporarily suspended.
const flashgrepBinary = null;
const flashgrepBinary = prepareMacOSFlashgrepForSigning(
ensureFlashgrepBinary({ target: optionValue(forward, '--target') || rustHostTargetTriple() }),
desktopDir,
);
process.env.FLASHGREP_DAEMON_BIN = flashgrepBinary;
// Tauri CLI reads CI and rejects numeric "1" (common in CI providers).
process.env.CI = 'true';
if (process.platform === 'darwin' && requestsDmgBundle(forward)) {
Expand Down Expand Up @@ -112,7 +110,7 @@ async function main() {

if (r.status === 0 && forward.includes('--no-bundle')) {
console.warn(
'[tauri-build] No bundle was produced. The raw desktop executable depends on its adjacent frontend, mobile-web, and resources directories and must not be distributed by itself.'
'[tauri-build] No bundle was produced. The raw desktop executable depends on its adjacent frontend, flashgrep, mobile-web, and resources directories and must not be distributed by itself.'
);
}

Expand Down Expand Up @@ -280,7 +278,7 @@ export function prepareMacOSFlashgrepForSigning(
'--timestamp',
signedBinary,
],
{ encoding: 'utf8', shell: false },
{ encoding: 'utf8', shell: false, windowsHide: true },
);
if (result.error || result.status !== 0) {
const detail = result.error?.message || result.stderr || `exit status ${result.status}`;
Expand Down Expand Up @@ -409,19 +407,7 @@ function injectTargetFlashgrepResource(config, desktopDir, flashgrepBinary) {
}

function bundledFlashgrepResources(primaryBinary) {
if (!primaryBinary) return [];
const binaries = [primaryBinary];

if (process.platform === 'win32') {
for (const binaryName of LINUX_FLASHGREP_BINARIES) {
const binaryPath = join(ROOT, 'resources', 'flashgrep', binaryName);
if (existsSync(binaryPath)) {
binaries.push(binaryPath);
}
}
}

return [...new Set(binaries)];
return primaryBinary ? [primaryBinary] : [];
}

function toTauriPath(value) {
Expand Down
13 changes: 9 additions & 4 deletions scripts/desktop-tauri-build.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -325,18 +325,23 @@ test('Desktop Tauri projection consumes only the resolved member identity', () =
}
});

test('Desktop packaging works without the suspended Flashgrep resource', () => {
test('Desktop packaging includes only the selected Flashgrep target', () => {
const fixture = join(tmpdir(), `openbitfun-without-flashgrep-${process.pid}-${Date.now()}`);
mkdirSync(fixture, { recursive: true });
const baseConfig = join(fixture, 'tauri.conf.json');
writeFileSync(baseConfig, JSON.stringify({
bundle: { resources: { '../../../resources/flashgrep': 'flashgrep' } },
}));
try {
const generated = prepareTauriConfig(baseConfig, { desktopDir: fixture });
const generated = prepareTauriConfig(baseConfig, {
desktopDir: fixture, flashgrepBinary: join(fixture, 'flashgrep-aarch64-apple-darwin'),
});
const config = JSON.parse(readFileSync(generated, 'utf8'));
assert.ok(Object.entries(config.bundle.resources).every(([source, target]) =>
!source.includes('flashgrep') && !target.includes('flashgrep')));
assert.deepEqual(Object.entries(config.bundle.resources).filter(([, target]) =>
target.startsWith('flashgrep/')), [
['flashgrep-aarch64-apple-darwin', 'flashgrep/flashgrep-aarch64-apple-darwin'],
]);
assert.equal(config.bundle.resources['../../../resources/flashgrep'], undefined);
assert.equal(config.bundle.resources['../../../dist'], 'frontend/dist');
} finally {
rmSync(fixture, { force: true, recursive: true });
Expand Down
9 changes: 9 additions & 0 deletions scripts/dev-server-ports.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export function resolveDevServerPorts(env = process.env) {
const port = Number(env.OPENBITFUN_DEV_PORT || 1422);
const hmrPort = Number(env.OPENBITFUN_DEV_HMR_PORT || port - 1);
if (![port, hmrPort].every(value => Number.isInteger(value) && value >= 1024 && value <= 65535)
|| port === hmrPort) {
throw new Error('Development HTTP and HMR ports must be distinct integers between 1024 and 65535');
}
return { port, hmrPort };
}
77 changes: 77 additions & 0 deletions scripts/dev-startup.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import assert from 'node:assert/strict';
import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import test from 'node:test';
import { resolveDevServerPorts } from './dev-server-ports.mjs';
import { prepareSherpaDev } from './prepare-sherpa-dev.mjs';

test('HTTP and HMR ports share one contract and reject conflicting overrides', () => {
assert.deepEqual(resolveDevServerPorts({}), { port: 1422, hmrPort: 1421 });
assert.deepEqual(resolveDevServerPorts({ OPENBITFUN_DEV_PORT: '1432' }), { port: 1432, hmrPort: 1431 });
assert.deepEqual(resolveDevServerPorts({ OPENBITFUN_DEV_PORT: '1432', OPENBITFUN_DEV_HMR_PORT: '1440' }), { port: 1432, hmrPort: 1440 });
for (const port of ['abc', '0', '65536', '1.5']) {
assert.throws(() => resolveDevServerPorts({ OPENBITFUN_DEV_PORT: port }), /ports/);
}
assert.throws(() => resolveDevServerPorts({ OPENBITFUN_DEV_PORT: '1432', OPENBITFUN_DEV_HMR_PORT: '1432' }), /distinct/);
});

function fixture(t) {
const root = mkdtempSync(join(tmpdir(), 'sherpa-dev-'));
t.after(() => rmSync(root, { recursive: true, force: true }));
writeFileSync(join(root, 'Cargo.lock'), '[[package]]\nname = "sherpa-onnx-sys"\nversion = "1.13.4"\n');
return root;
}
const target = 'aarch64-apple-darwin';
const archive = 'sherpa-onnx-v1.13.4-osx-arm64-static-lib.tar.bz2';

test('explicit library and archive paths are preserved', () => {
for (const key of ['SHERPA_ONNX_LIB_DIR', 'SHERPA_ONNX_ARCHIVE_DIR']) {
const env = { [key]: '/explicit' };
prepareSherpaDev('/unused', env, { run: () => assert.fail('must not run commands') });
assert.deepEqual(env, { [key]: '/explicit' });
}
});

test('a worktree reuses the matching main checkout library without downloading', t => {
const root = fixture(t);
const main = join(root, 'main');
const library = join(main, 'target/sherpa-onnx-prebuilt', archive.replace('.tar.bz2', ''), 'lib');
mkdirSync(library, { recursive: true });
writeFileSync(join(library, 'libsherpa-onnx-c-api.a'), 'fixture');
const env = {};
prepareSherpaDev(root, env, { target, run(command) {
assert.equal(command, 'git');
return { status: 0, stdout: join(main, '.git') };
} });
assert.equal(env.SHERPA_ONNX_LIB_DIR, library);
});

test('downloads the locked archive through curl and reuses it on the next run', t => {
const root = fixture(t);
let downloads = 0;
const run = (command, args, options) => {
if (command === 'git') return { status: 1 };
assert.match(command, /^curl(?:\.exe)?$/);
assert.equal(options.windowsHide, true);
assert.equal(args.at(-1), `https://github.com/k2-fsa/sherpa-onnx/releases/download/v1.13.4/${archive}`);
writeFileSync(args[args.indexOf('--output') + 1], 'archive fixture');
downloads++;
return { status: 0 };
};
for (let i = 0; i < 2; i++) {
const env = {};
prepareSherpaDev(root, env, { target, run });
assert.equal(readFileSync(join(env.SHERPA_ONNX_ARCHIVE_DIR, archive), 'utf8'), 'archive fixture');
}
assert.equal(downloads, 1);
});

test('failed downloads are not published as reusable archives', t => {
const root = fixture(t);
const env = {};
assert.throws(() => prepareSherpaDev(root, env, { target, run(command) {
return command === 'git' ? { status: 1 } : { status: 22, stderr: 'download failed' };
} }), /download failed/);
assert.equal(env.SHERPA_ONNX_ARCHIVE_DIR, undefined);
});
Loading
Loading