From 8fc9325f69858915ce7b0b53b46f69fdbaba0806 Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Sat, 25 Jul 2026 00:45:53 +0000 Subject: [PATCH 1/2] feat(core): enable fff in node runtimes --- bun.lock | 27 +++++++ packages/core/package.json | 1 + packages/core/src/filesystem/fff.node.ts | 98 ++++++++++++------------ 3 files changed, 76 insertions(+), 50 deletions(-) diff --git a/bun.lock b/bun.lock index fb36652e940b..0ac2088a605a 100644 --- a/bun.lock +++ b/bun.lock @@ -364,6 +364,7 @@ "@effect/platform-node": "catalog:", "@effect/sql-sqlite-bun": "catalog:", "@ff-labs/fff-bun": "0.10.1", + "@ff-labs/fff-node": "0.10.1", "@lydell/node-pty": "catalog:", "@modelcontextprotocol/sdk": "1.29.0", "@opencode-ai/ai": "workspace:*", @@ -1666,6 +1667,8 @@ "@ff-labs/fff-bun": ["@ff-labs/fff-bun@0.10.1", "", { "optionalDependencies": { "@ff-labs/fff-bin-android-arm64": "0.10.1", "@ff-labs/fff-bin-darwin-arm64": "0.10.1", "@ff-labs/fff-bin-darwin-x64": "0.10.1", "@ff-labs/fff-bin-linux-arm64-gnu": "0.10.1", "@ff-labs/fff-bin-linux-arm64-musl": "0.10.1", "@ff-labs/fff-bin-linux-x64-gnu": "0.10.1", "@ff-labs/fff-bin-linux-x64-musl": "0.10.1", "@ff-labs/fff-bin-win32-arm64": "0.10.1", "@ff-labs/fff-bin-win32-x64": "0.10.1" }, "os": [ "!aix", "!sunos", "!freebsd", "!openbsd", ], "cpu": [ "x64", "arm64", ] }, "sha512-9oUCxypGbf2q3vNfKZ31wdzt5KqjhA9S6TwQaFol/j1lkSHVGvtIa3RvdGHPs1UUuvR/MO7b6pHj054BR9bXPQ=="], + "@ff-labs/fff-node": ["@ff-labs/fff-node@0.10.1", "", { "dependencies": { "ffi-rs": "^1.0.0" }, "optionalDependencies": { "@ff-labs/fff-bin-android-arm64": "0.10.1", "@ff-labs/fff-bin-darwin-arm64": "0.10.1", "@ff-labs/fff-bin-darwin-x64": "0.10.1", "@ff-labs/fff-bin-linux-arm64-gnu": "0.10.1", "@ff-labs/fff-bin-linux-arm64-musl": "0.10.1", "@ff-labs/fff-bin-linux-x64-gnu": "0.10.1", "@ff-labs/fff-bin-linux-x64-musl": "0.10.1", "@ff-labs/fff-bin-win32-arm64": "0.10.1", "@ff-labs/fff-bin-win32-x64": "0.10.1" }, "os": [ "!aix", "!sunos", "!freebsd", "!openbsd", ], "cpu": [ "x64", "arm64", ] }, "sha512-I2TIWHkey4wpLCncUQymBz/yWrjp/TE91yjrPvSLaPMUMRf3wXwEmelGNnZ5ynxy3cUauYg5b5hsUwhOK4skfA=="], + "@floating-ui/core": ["@floating-ui/core@1.8.0", "", { "dependencies": { "@floating-ui/utils": "^0.2.12" } }, "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ=="], "@floating-ui/dom": ["@floating-ui/dom@1.8.0", "", { "dependencies": { "@floating-ui/core": "^1.8.0", "@floating-ui/utils": "^0.2.12" } }, "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg=="], @@ -3300,6 +3303,28 @@ "@xmldom/xmldom": ["@xmldom/xmldom@0.8.13", "", {}, "sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw=="], + "@yuuang/ffi-rs-android-arm64": ["@yuuang/ffi-rs-android-arm64@1.3.2", "", { "os": "android", "cpu": "arm64" }, "sha512-eDYLT0kVBkp7e2BwdRDmt6N1rkeDPUHDefk3ZX0/nok+GLsqfy1WBoSL3Yg7HVXN1EyW8OBVc2uK8Zq8HbmaSA=="], + + "@yuuang/ffi-rs-darwin-arm64": ["@yuuang/ffi-rs-darwin-arm64@1.3.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-kRdgPaOM6TfuC5wHUwstlatk4HNie2lwSLJWQL2LiAUIJ7+96CoiWUNVhwBcFrhdfxhnWenYS6F668CV0vit8Q=="], + + "@yuuang/ffi-rs-darwin-x64": ["@yuuang/ffi-rs-darwin-x64@1.3.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-O3AlVgre8FQcZRJe44Xs7A6iDLumoPXqbw40+eJCa2gyXaXyLPdHoWrS1W9rBCa1QZRRnG7zRulPVFw8C5uo8g=="], + + "@yuuang/ffi-rs-linux-arm-gnueabihf": ["@yuuang/ffi-rs-linux-arm-gnueabihf@1.3.2", "", { "os": "linux", "cpu": "arm" }, "sha512-IXiNdTbIcTCPny5eeElijFWYeKSJjQWSjt9ZyJNdLHYiB1Np+XD6K7wNZS6EOMgMelhW1kQE62T654skGkVDIA=="], + + "@yuuang/ffi-rs-linux-arm64-gnu": ["@yuuang/ffi-rs-linux-arm64-gnu@1.3.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-gWFO6xufUK9lPYUqDvKa6IR243dPqdetgl9Q7HrZWaDu7wLo06QQrosw8QTzndafQnOcBKm6LoLujmGCfTgJOA=="], + + "@yuuang/ffi-rs-linux-arm64-musl": ["@yuuang/ffi-rs-linux-arm64-musl@1.3.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-lejvOSqypPziQH5rzfkDlJ6e92qhWbDutE9ttOO6z5I2k83zoh9iZhZWhaXSU5VqgQpcshRkrbtXb9gy1ft5dA=="], + + "@yuuang/ffi-rs-linux-x64-gnu": ["@yuuang/ffi-rs-linux-x64-gnu@1.3.2", "", { "os": "linux", "cpu": "x64" }, "sha512-s8VCFazaJKmgY2hgMTpWk4TtBY/zy5ovbaGgwyY0FvBD0YvyhcET4IrMsDJpHhFVTPCYfKZ1dN45clD/YiFp6g=="], + + "@yuuang/ffi-rs-linux-x64-musl": ["@yuuang/ffi-rs-linux-x64-musl@1.3.2", "", { "os": "linux", "cpu": "x64" }, "sha512-Ahr5chfKZKWUik20bEZRug+be57LZ2yYrtolyjSRoo7A4ZniBUHBZUNWm6TD6i0CJayqyxWeVk/XiaABD8bY0w=="], + + "@yuuang/ffi-rs-win32-arm64-msvc": ["@yuuang/ffi-rs-win32-arm64-msvc@1.3.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-yhpLcj0qel5VNlpzxPZfNmi7+rEX8444QHjUP6WWLxdRfqPllROu/Cp3OpkBpw3BLdxfcDhWkjWMD5QsJN0Pvg=="], + + "@yuuang/ffi-rs-win32-ia32-msvc": ["@yuuang/ffi-rs-win32-ia32-msvc@1.3.2", "", { "os": "win32", "cpu": [ "x64", "ia32", ] }, "sha512-BFVSbdtg/7mJBw5kQFOPKFiA+SF7z3240HpzHN81Umm4Bp4dWkyx0msYn8+Q7/BBJiLQ4F6bi3Nftk58YA9r9w=="], + + "@yuuang/ffi-rs-win32-x64-msvc": ["@yuuang/ffi-rs-win32-x64-msvc@1.3.2", "", { "os": "win32", "cpu": "x64" }, "sha512-ZL5MJ76n2rjwGo26kCWW7wK6QT/cee00Rx8pfW79pz6vM6jqfhoE7zTnwFiw4aOQUes9+HUc5DeeJ3z+Vb9oLg=="], + "@zip.js/zip.js": ["@zip.js/zip.js@2.7.62", "", {}, "sha512-OaLvZ8j4gCkLn048ypkZu29KX30r8/OfFF2w4Jo5WXFr+J04J+lzJ5TKZBVgFXhlvSkqNFQdfnY1Q8TMTCyBVA=="], "abbrev": ["abbrev@4.0.0", "", {}, "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA=="], @@ -4084,6 +4109,8 @@ "fetchdts": ["fetchdts@0.1.7", "", {}, "sha512-YoZjBdafyLIop9lSxXVI33oLD5kN31q4Td+CasofLLYeLXRFeOsuOw0Uo+XNRi9PZlbfdlN2GmRtm4tCEQ9/KA=="], + "ffi-rs": ["ffi-rs@1.3.2", "", { "optionalDependencies": { "@yuuang/ffi-rs-android-arm64": "1.3.2", "@yuuang/ffi-rs-darwin-arm64": "1.3.2", "@yuuang/ffi-rs-darwin-x64": "1.3.2", "@yuuang/ffi-rs-linux-arm-gnueabihf": "1.3.2", "@yuuang/ffi-rs-linux-arm64-gnu": "1.3.2", "@yuuang/ffi-rs-linux-arm64-musl": "1.3.2", "@yuuang/ffi-rs-linux-x64-gnu": "1.3.2", "@yuuang/ffi-rs-linux-x64-musl": "1.3.2", "@yuuang/ffi-rs-win32-arm64-msvc": "1.3.2", "@yuuang/ffi-rs-win32-ia32-msvc": "1.3.2", "@yuuang/ffi-rs-win32-x64-msvc": "1.3.2" } }, "sha512-4s8dX9VbBw/jd5NOuE3EJRqXaIVdjMyiumeeDzrOhtjQRwp6Bz2za7iksWXTnvTQKV/tTdm1s1w7mObe92zPjQ=="], + "filelist": ["filelist@1.0.6", "", { "dependencies": { "minimatch": "^5.0.1" } }, "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA=="], "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], diff --git a/packages/core/package.json b/packages/core/package.json index 86f97f7d5099..7272a1499b77 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -92,6 +92,7 @@ "@lydell/node-pty": "catalog:", "@modelcontextprotocol/sdk": "1.29.0", "@ff-labs/fff-bun": "0.10.1", + "@ff-labs/fff-node": "0.10.1", "@opencode-ai/codemode": "workspace:*", "@opencode-ai/effect-drizzle-sqlite": "workspace:*", "@opencode-ai/effect-sqlite-node": "workspace:*", diff --git a/packages/core/src/filesystem/fff.node.ts b/packages/core/src/filesystem/fff.node.ts index 464c2853d7ea..c1c9553b1c7e 100644 --- a/packages/core/src/filesystem/fff.node.ts +++ b/packages/core/src/filesystem/fff.node.ts @@ -1,71 +1,51 @@ -export type Result = { ok: true; value: T } | { ok: false; error: string } - -export interface Init { - basePath: string - frecencyDbPath?: string - historyDbPath?: string - useUnsafeNoLock?: boolean - disableMmapCache?: boolean - disableContentIndexing?: boolean - disableWatch?: boolean - aiMode?: boolean - logFilePath?: string - logLevel?: "trace" | "debug" | "info" | "warn" | "error" - enableFsRootScanning?: boolean - enableHomeDirScanning?: boolean -} - -export interface File { - relativePath: string - fileName: string - modified: number -} +import { + FileFinder, + type DirItem, + type DirSearchResult, + type FileItem, + type GrepCursor, + type GrepMatch, + type GrepResult, + type InitOptions, + type MixedItem, + type MixedSearchResult, + type SearchResult, +} from "@ff-labs/fff-node" -export interface Directory { - relativePath: string - dirName: string - maxAccessFrecency: number -} +export type Result = { ok: true; value: T } | { ok: false; error: string } -export type Mixed = { type: "file"; item: File } | { type: "directory"; item: Directory } +export type Init = InitOptions export interface Search { - items: File[] - scores: Array<{ total: number }> + items: FileItem[] + scores: SearchResult["scores"] totalMatched: number totalFiles: number } export interface DirSearch { - items: Directory[] - scores: Array<{ total: number }> + items: DirItem[] + scores: DirSearchResult["scores"] totalMatched: number totalDirs: number } export interface MixedSearch { - items: Mixed[] - scores: Array<{ total: number }> + items: MixedItem[] + scores: MixedSearchResult["scores"] totalMatched: number totalFiles: number totalDirs: number } -export type Cursor = null - -export interface Hit { - relativePath: string - fileName: string - lineNumber: number - byteOffset: number - lineContent: string - matchRanges: [number, number][] - contextBefore?: string[] - contextAfter?: string[] -} +export type File = FileItem +export type Directory = DirItem +export type Mixed = MixedItem +export type Cursor = GrepCursor | null +export type Hit = GrepMatch export interface Grep { - items: Hit[] + items: GrepResult["items"] totalMatched: number totalFilesSearched: number totalFiles: number @@ -128,11 +108,29 @@ export interface Picker { } export function available() { - return false + return FileFinder.isAvailable() } -export function create(_opts: Init): Result { - return { ok: false, error: "fff unavailable on node runtime" } +export function create(opts: Init): Result { + const made = FileFinder.create(opts) + if (!made.ok) return made + const pick = made.value + return { + ok: true, + value: { + destroy: () => pick.destroy(), + isScanning: () => pick.isScanning(), + waitForScan: (timeoutMs) => pick.waitForScan(timeoutMs), + refreshGitStatus: () => pick.refreshGitStatus(), + fileSearch: (query, next) => pick.fileSearch(query, next), + glob: (pattern, next) => pick.glob(pattern, next), + directorySearch: (query, next) => pick.directorySearch(query, next), + mixedSearch: (query, next) => pick.mixedSearch(query, next), + grep: (query, next) => pick.grep(query, next), + trackQuery: (query, file) => pick.trackQuery(query, file), + getHistoricalQuery: (offset) => pick.getHistoricalQuery(offset), + }, + } } export * as Fff from "./fff.node" From 129694bf8ae9c3db0a7fb7f05f6abc0eece00f47 Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Sat, 25 Jul 2026 02:31:26 +0000 Subject: [PATCH 2/2] fix(cli): package fff node assets --- bun.lock | 10 ++++++++++ packages/cli/package.json | 10 ++++++++++ packages/cli/script/node-assets.ts | 2 ++ packages/cli/src/node/target.ts | 6 ++++++ packages/cli/vite.node.config.ts | 28 ++++++++++++++++++++++++++++ 5 files changed, 56 insertions(+) diff --git a/bun.lock b/bun.lock index 0ac2088a605a..b498427f1f63 100644 --- a/bun.lock +++ b/bun.lock @@ -147,6 +147,11 @@ "ws": "8.21.0", }, "devDependencies": { + "@ff-labs/fff-bin-darwin-arm64": "0.10.1", + "@ff-labs/fff-bin-linux-arm64-gnu": "0.10.1", + "@ff-labs/fff-bin-linux-x64-gnu": "0.10.1", + "@ff-labs/fff-bin-win32-arm64": "0.10.1", + "@ff-labs/fff-bin-win32-x64": "0.10.1", "@lydell/node-pty-darwin-arm64": "1.2.0-beta.12", "@lydell/node-pty-darwin-x64": "1.2.0-beta.12", "@lydell/node-pty-linux-arm64": "1.2.0-beta.12", @@ -164,6 +169,11 @@ "@types/bun": "catalog:", "@types/semver": "catalog:", "@typescript/native-preview": "catalog:", + "@yuuang/ffi-rs-darwin-arm64": "1.3.2", + "@yuuang/ffi-rs-linux-arm64-gnu": "1.3.2", + "@yuuang/ffi-rs-linux-x64-gnu": "1.3.2", + "@yuuang/ffi-rs-win32-arm64-msvc": "1.3.2", + "@yuuang/ffi-rs-win32-x64-msvc": "1.3.2", "vite": "catalog:", "vite-plugin-solid": "catalog:", }, diff --git a/packages/cli/package.json b/packages/cli/package.json index f848b562798b..94a4b20aee6d 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -56,6 +56,16 @@ "@lydell/node-pty-linux-x64": "1.2.0-beta.12", "@lydell/node-pty-win32-arm64": "1.2.0-beta.12", "@lydell/node-pty-win32-x64": "1.2.0-beta.12", + "@ff-labs/fff-bin-darwin-arm64": "0.10.1", + "@ff-labs/fff-bin-linux-arm64-gnu": "0.10.1", + "@ff-labs/fff-bin-linux-x64-gnu": "0.10.1", + "@ff-labs/fff-bin-win32-arm64": "0.10.1", + "@ff-labs/fff-bin-win32-x64": "0.10.1", + "@yuuang/ffi-rs-darwin-arm64": "1.3.2", + "@yuuang/ffi-rs-linux-arm64-gnu": "1.3.2", + "@yuuang/ffi-rs-linux-x64-gnu": "1.3.2", + "@yuuang/ffi-rs-win32-arm64-msvc": "1.3.2", + "@yuuang/ffi-rs-win32-x64-msvc": "1.3.2", "@parcel/watcher-darwin-arm64": "2.5.1", "@parcel/watcher-linux-arm64-glibc": "2.5.1", "@parcel/watcher-linux-x64-glibc": "2.5.1", diff --git a/packages/cli/script/node-assets.ts b/packages/cli/script/node-assets.ts index cde31db13f6e..3f4823d82310 100644 --- a/packages/cli/script/node-assets.ts +++ b/packages/cli/script/node-assets.ts @@ -37,6 +37,8 @@ export async function collectNodeAssets(target: NodeTarget) { ...(target.platform === "linux" ? { libc: "glibc" as const } : {}), }), { key: target.parcelWatcherAsset, source: fileURLToPath(import.meta.resolve(target.parcelWatcherPackage)) }, + { key: target.fffAsset, source: fileURLToPath(import.meta.resolve(target.fffPackage)) }, + { key: target.fffFfiAsset, source: fileURLToPath(import.meta.resolve(target.fffFfiPackage)) }, { key: photonWasmAsset, source: fileURLToPath(import.meta.resolve(photonWasmAsset)), diff --git a/packages/cli/src/node/target.ts b/packages/cli/src/node/target.ts index 097d74974f96..f39db8f820ce 100644 --- a/packages/cli/src/node/target.ts +++ b/packages/cli/src/node/target.ts @@ -11,6 +11,8 @@ export function nodeTarget(platform: string, arch: string) { const targetArch = arch as "arm64" | "x64" const nodePtyPackage = `@lydell/node-pty-${targetPlatform}-${targetArch}` const parcelWatcherPackage = `@parcel/watcher-${targetPlatform}-${targetArch}${targetPlatform === "linux" ? "-glibc" : ""}` + const fffPackage = `@ff-labs/fff-bin-${targetPlatform}-${targetArch}${targetPlatform === "linux" ? "-gnu" : ""}` + const fffFfiPackage = `@yuuang/ffi-rs-${targetPlatform}-${targetArch}${targetPlatform === "linux" ? "-gnu" : targetPlatform === "win32" ? "-msvc" : ""}` return { platform: targetPlatform, @@ -19,6 +21,10 @@ export function nodeTarget(platform: string, arch: string) { nodePtyEntryAsset: `${nodePtyPackage}/lib/index.js`, parcelWatcherPackage, parcelWatcherAsset: `${parcelWatcherPackage}/watcher.node`, + fffPackage, + fffAsset: `${fffPackage}/${targetPlatform === "darwin" ? "libfff_c.dylib" : targetPlatform === "win32" ? "fff_c.dll" : "libfff_c.so"}`, + fffFfiPackage, + fffFfiAsset: `${fffFfiPackage}/ffi-rs.${targetPlatform}-${targetArch}${targetPlatform === "linux" ? "-gnu" : targetPlatform === "win32" ? "-msvc" : ""}.node`, } } diff --git a/packages/cli/vite.node.config.ts b/packages/cli/vite.node.config.ts index 15bcc8490a3f..da0c05410631 100644 --- a/packages/cli/vite.node.config.ts +++ b/packages/cli/vite.node.config.ts @@ -30,6 +30,30 @@ function runtimeRequirePlugin(): Plugin { } } +function fffNodePlugin(): Plugin { + return { + name: "opencode:fff-node", + enforce: "pre", + transform(code, id) { + const normalized = id.replaceAll("\\", "/") + if (normalized.endsWith("/ffi-rs/index.js")) { + const start = code.indexOf("if (!nativeBinding) {") + if (start === -1) this.error("Failed to rewrite ffi-rs native binding loader") + return `const nativeBinding = globalThis.__OPENCODE_FFF_FFI +const loadError = undefined +${code.slice(start)}` + } + if (!normalized.endsWith("/fff-node/dist/src/binary.js")) return + const transformed = code.replace( + "export function findBinary() {", + "export function findBinary() { if (process.env.FFF_BINARY_PATH) return process.env.FFF_BINARY_PATH;", + ) + if (transformed === code) this.error("Failed to rewrite FFF binary loader") + return transformed + }, + } +} + const resolve = { alias: [ { find: /^solid-js\/store$/, replacement: "solid-js/store/dist/store.js" }, @@ -165,6 +189,9 @@ process.env.OTUI_ASSET_ROOT = __ocAssetRoot process.env.OPENCODE_NODE_PTY_PATH = __ocPath.join(__ocAssetRoot, ${JSON.stringify(input.target.nodePtyEntryAsset)}) process.env.OPENCODE_PARCEL_WATCHER_PATH = __ocPath.join(__ocAssetRoot, ${JSON.stringify(input.target.parcelWatcherAsset)}) process.env.OPENCODE_PHOTON_WASM_PATH = __ocPath.join(__ocAssetRoot, ${JSON.stringify(photonWasmAsset)}) +process.env.FFF_BINARY_PATH = __ocPath.join(__ocAssetRoot, ${JSON.stringify(input.target.fffAsset)}) +process.env.OPENCODE_FFF_FFI_PATH = __ocPath.join(__ocAssetRoot, ${JSON.stringify(input.target.fffFfiAsset)}) +globalThis.__OPENCODE_FFF_FFI = require(process.env.OPENCODE_FFF_FFI_PATH) globalThis.__OPENCODE_PHOTON_WASM_PATH = process.env.OPENCODE_PHOTON_WASM_PATH if (process.platform === "linux") process.env.OPENTUI_LIBC = "glibc"` } @@ -183,6 +210,7 @@ export function mainConfig(input: NodeBuildInput): UserConfig { plugins: [ rawTextPlugin(), runtimeRequirePlugin(), + fffNodePlugin(), solid({ solid: { generate: "universal",