Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 129694bf8a | |||
| 8fc9325f69 |
@@ -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:",
|
||||
},
|
||||
@@ -364,6 +374,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 +1677,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 +3313,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 +4119,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=="],
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)),
|
||||
|
||||
@@ -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`,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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:*",
|
||||
|
||||
@@ -63,6 +63,7 @@ const layer = Layer.effect(
|
||||
if (rule?.resource === "*" && rule.effect === "deny") continue
|
||||
registrations.set(name, registration)
|
||||
}
|
||||
if (registrations.size === 0) return {}
|
||||
const executeRule = rules.findLast((rule) => Wildcard.match("execute", rule.action))
|
||||
if (executeRule?.resource === "*" && executeRule.effect === "deny") return {}
|
||||
return {
|
||||
|
||||
@@ -19,8 +19,7 @@ Use \`search\` to discover exact paths and signatures for additional tools:
|
||||
## Available tools`
|
||||
|
||||
export function render(catalog: CodeModeCatalog.Summary) {
|
||||
if (catalog.total === 0)
|
||||
return "No Code Mode tools are currently available. Do not call `execute` until a later system update announces available tools."
|
||||
if (catalog.total === 0) return "No tools are currently available."
|
||||
|
||||
const tools = catalog.namespaces.flatMap((namespace) => {
|
||||
const count = namespace.count === 1 ? "1 tool" : `${namespace.count} tools`
|
||||
@@ -39,13 +38,12 @@ ${tools.join("\n")}`
|
||||
}
|
||||
|
||||
export function update(previous: CodeModeCatalog.Summary, current: CodeModeCatalog.Summary) {
|
||||
const replacement = `The Code Mode tool catalog has changed. This catalog supersedes the previous Code Mode tool catalog.
|
||||
const full = `The Code Mode tool catalog has changed. This catalog supersedes the previous Code Mode tool catalog.
|
||||
|
||||
${render(current)}`
|
||||
if (current.total === 0) return replacement
|
||||
const previousComplete = previous.shown === previous.total
|
||||
const currentComplete = current.shown === current.total
|
||||
if (previousComplete !== currentComplete) return replacement
|
||||
if (previousComplete !== currentComplete) return full
|
||||
|
||||
const diff = Instructions.diffByKey(
|
||||
previous.namespaces.flatMap((namespace) => namespace.entries),
|
||||
@@ -56,7 +54,7 @@ ${render(current)}`
|
||||
const entriesChanged = diff.added.length > 0 || diff.removed.length > 0 || diff.changed.length > 0
|
||||
|
||||
if (!currentComplete) {
|
||||
if (entriesChanged) return replacement
|
||||
if (entriesChanged) return full
|
||||
const namespaces = Instructions.diffByKey(
|
||||
previous.namespaces,
|
||||
current.namespaces,
|
||||
@@ -64,7 +62,7 @@ ${render(current)}`
|
||||
(before, after) => before.count !== after.count,
|
||||
)
|
||||
const changed = namespaces.added.length > 0 || namespaces.removed.length > 0 || namespaces.changed.length > 0
|
||||
if (!changed) return replacement
|
||||
if (!changed) return full
|
||||
|
||||
const parts = ["The Code Mode tool catalog has changed."]
|
||||
if (namespaces.added.length > 0) {
|
||||
@@ -89,11 +87,11 @@ ${render(current)}`
|
||||
)
|
||||
}
|
||||
const delta = parts.join("\n\n")
|
||||
if (delta.length < replacement.length) return delta
|
||||
return replacement
|
||||
if (delta.length < full.length) return delta
|
||||
return full
|
||||
}
|
||||
|
||||
if (!entriesChanged) return replacement
|
||||
if (!entriesChanged) return full
|
||||
const parts = ["The Code Mode tool catalog has changed."]
|
||||
if (diff.added.length > 0) {
|
||||
parts.push(
|
||||
@@ -119,19 +117,19 @@ ${render(current)}`
|
||||
)
|
||||
}
|
||||
const delta = parts.join("\n\n")
|
||||
if (delta.length < replacement.length) return delta
|
||||
return replacement
|
||||
if (delta.length < full.length) return delta
|
||||
return full
|
||||
}
|
||||
|
||||
const key = Instructions.Key.make("core/codemode")
|
||||
const codec = Schema.toCodecJson(CodeModeCatalog.Summary)
|
||||
|
||||
export const make = (entries?: ReadonlyArray<CodeModeCatalog.Entry>): Instructions.Instructions => {
|
||||
const catalog = entries === undefined ? Instructions.removed : CodeModeCatalog.summarize(entries)
|
||||
const catalog = CodeModeCatalog.summarize(entries ?? [])
|
||||
return Instructions.make({
|
||||
key,
|
||||
codec,
|
||||
read: Effect.succeed(catalog),
|
||||
read: Effect.succeed(catalog.total === 0 ? Instructions.removed : catalog),
|
||||
render: {
|
||||
initial: render,
|
||||
changed: update,
|
||||
|
||||
@@ -1,71 +1,51 @@
|
||||
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 type Result<T> = { 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
|
||||
}
|
||||
|
||||
export interface Directory {
|
||||
relativePath: string
|
||||
dirName: string
|
||||
maxAccessFrecency: number
|
||||
}
|
||||
|
||||
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<Picker> {
|
||||
return { ok: false, error: "fff unavailable on node runtime" }
|
||||
export function create(opts: Init): Result<Picker> {
|
||||
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"
|
||||
|
||||
@@ -117,9 +117,7 @@ describe("CodeModeInstructions.render", () => {
|
||||
})
|
||||
|
||||
test("renders only the no-tools notice for an empty catalog", () => {
|
||||
expect(render([])).toBe(
|
||||
"No Code Mode tools are currently available. Do not call `execute` until a later system update announces available tools.",
|
||||
)
|
||||
expect(render([])).toBe("No tools are currently available.")
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -21,25 +21,6 @@ const lookup: CodeModeCatalog.Entry = {
|
||||
}
|
||||
|
||||
describe("CodeModeInstructions", () => {
|
||||
it.effect("instructs the model not to call execute while the catalog is empty", () =>
|
||||
Effect.gen(function* () {
|
||||
const initialized = yield* readInitial(CodeModeInstructions.make([]))
|
||||
expect(initialized.text).toBe(
|
||||
"No Code Mode tools are currently available. Do not call `execute` until a later system update announces available tools.",
|
||||
)
|
||||
|
||||
const added = yield* readUpdate(CodeModeInstructions.make([echo]), initialized)
|
||||
expect(added.text).toContain("New tools are available in addition to those previously listed:")
|
||||
expect(added.text).toContain(echo.signature)
|
||||
|
||||
expect(yield* readUpdate(CodeModeInstructions.make([]), { values: added.values })).toMatchObject({
|
||||
text:
|
||||
"The Code Mode tool catalog has changed. This catalog supersedes the previous Code Mode tool catalog.\n\n" +
|
||||
"No Code Mode tools are currently available. Do not call `execute` until a later system update announces available tools.",
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("renders the initial catalog, semantic deltas, and removal", () =>
|
||||
Effect.gen(function* () {
|
||||
const initialized = yield* readInitial(CodeModeInstructions.make([echo]))
|
||||
|
||||
@@ -32,22 +32,6 @@ export function waitForTool(
|
||||
})
|
||||
}
|
||||
|
||||
export function waitForCodeModeTool(
|
||||
registry: ToolRegistry.Interface,
|
||||
path: string,
|
||||
remaining = 1000,
|
||||
): Effect.Effect<ToolRegistry.ToolSet, Error> {
|
||||
return Effect.gen(function* () {
|
||||
const toolSet = yield* registry.snapshot()
|
||||
if (toolSet.codeModeCatalog?.some((tool) => tool.path === path)) return toolSet
|
||||
if (remaining === 0) {
|
||||
return yield* Effect.fail(new Error(`Timed out waiting for Code Mode tool: ${path}`))
|
||||
}
|
||||
yield* Effect.promise(() => Bun.sleep(1))
|
||||
return yield* waitForCodeModeTool(registry, path, remaining - 1)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a core tool plugin's tools against the real registry without booting the
|
||||
* full plugin host. Only the tool domain is live; focused tool tests exercise
|
||||
|
||||
@@ -33,7 +33,7 @@ import { Image } from "@opencode-ai/core/image"
|
||||
import { testEffect } from "./lib/effect"
|
||||
import { imagePassthrough } from "./lib/image"
|
||||
import { location } from "./fixture/location"
|
||||
import { executeTool, toolDefinitions, toolIdentity, waitForCodeModeTool, waitForTool } from "./lib/tool"
|
||||
import { executeTool, toolDefinitions, toolIdentity, waitForTool } from "./lib/tool"
|
||||
|
||||
let assertion: Deferred.Deferred<PermissionV2.AssertInput> | undefined
|
||||
let decision: Effect.Effect<void, PermissionV2.Error> = Effect.void
|
||||
@@ -802,16 +802,10 @@ test("serializes concurrent MCP lifecycle operations", async () => {
|
||||
it.effect("advertises MCP output schemas to Code Mode", () =>
|
||||
Effect.gen(function* () {
|
||||
const registry = yield* ToolRegistry.Service
|
||||
const toolSet = yield* waitForCodeModeTool(registry, "demo.search")
|
||||
const execute = toolSet.definitions.find((tool) => tool.name === "execute")
|
||||
yield* waitForTool(registry, "execute")
|
||||
const definitions = yield* toolDefinitions(registry)
|
||||
const execute = definitions.find((tool) => tool.name === "execute")
|
||||
|
||||
expect(toolSet.definitions.map((tool) => tool.name)).toEqual([
|
||||
"direct_fail",
|
||||
"direct_lookup",
|
||||
"direct_media",
|
||||
"execute",
|
||||
])
|
||||
expect(toolSet.codeModeCatalog?.find((tool) => tool.path === "demo.search")?.signature).toContain("ok: boolean")
|
||||
expect(execute?.description).not.toContain("tools.demo.search")
|
||||
}),
|
||||
)
|
||||
@@ -879,9 +873,9 @@ it.effect("waits for permission before calling an MCP tool", () =>
|
||||
const permission = yield* Deferred.make<void>()
|
||||
decision = Deferred.await(permission)
|
||||
const registry = yield* ToolRegistry.Service
|
||||
const toolSet = yield* waitForCodeModeTool(registry, "demo.search")
|
||||
yield* waitForTool(registry, "execute")
|
||||
|
||||
const fiber = yield* toolSet.execute({
|
||||
const fiber = yield* executeTool(registry, {
|
||||
sessionID: SessionV2.ID.make("ses_mcp_permission"),
|
||||
...toolIdentity,
|
||||
call: {
|
||||
@@ -918,9 +912,9 @@ it.effect("does not call MCP when permission is blocked", () =>
|
||||
assertion = yield* Deferred.make<PermissionV2.AssertInput>()
|
||||
decision = Effect.fail(new PermissionV2.BlockedError({ rules: [], permission: "demo_search", resources: ["*"] }))
|
||||
const registry = yield* ToolRegistry.Service
|
||||
const toolSet = yield* waitForCodeModeTool(registry, "demo.search")
|
||||
yield* waitForTool(registry, "execute")
|
||||
|
||||
const execution = yield* toolSet.execute({
|
||||
const execution = yield* executeTool(registry, {
|
||||
sessionID: SessionV2.ID.make("ses_mcp_blocked"),
|
||||
...toolIdentity,
|
||||
call: {
|
||||
|
||||
@@ -186,7 +186,6 @@ describe("SessionRunnerLLM recorded", () => {
|
||||
yield* agents.transform((draft) =>
|
||||
draft.update(AgentV2.ID.make("build"), (agent) => {
|
||||
agent.mode = "primary"
|
||||
agent.permissions.push({ action: "execute", resource: "*", effect: "deny" })
|
||||
}),
|
||||
)
|
||||
const pluginHost = host({
|
||||
|
||||
@@ -88,7 +88,7 @@ describe("ToolRegistry", () => {
|
||||
|
||||
expect(error).toBeInstanceOf(Tool.RegistrationError)
|
||||
expect(error.message).toBe('Invalid tool namespace: "slack..admin"')
|
||||
expect((yield* service.snapshot()).definitions.map((tool) => tool.name)).toEqual(["execute"])
|
||||
expect((yield* service.snapshot()).definitions).toEqual([])
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -102,7 +102,7 @@ describe("ToolRegistry", () => {
|
||||
.register({ "echo.tool": make(), echo_tool: make() }, { codemode: false })
|
||||
.pipe(Effect.flip)
|
||||
expect(collision.message).toBe("Duplicate normalized tool name: echo_tool")
|
||||
expect((yield* service.snapshot()).definitions.map((tool) => tool.name)).toEqual(["execute"])
|
||||
expect((yield* service.snapshot()).definitions).toEqual([])
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -117,7 +117,7 @@ describe("ToolRegistry", () => {
|
||||
.pipe(Effect.flip)
|
||||
|
||||
expect(error).toBeInstanceOf(Tool.RegistrationError)
|
||||
expect((yield* service.snapshot()).definitions.map((tool) => tool.name)).toEqual(["execute"])
|
||||
expect((yield* service.snapshot()).definitions).toEqual([])
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -148,20 +148,6 @@ describe("ToolRegistry", () => {
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("keeps execute available without Code Mode tools unless explicitly denied", () =>
|
||||
Effect.gen(function* () {
|
||||
const service = yield* ToolRegistry.Service
|
||||
|
||||
const available = yield* service.snapshot()
|
||||
expect(available.definitions.map((tool) => tool.name)).toEqual(["execute"])
|
||||
expect(available.codeModeCatalog).toEqual([])
|
||||
|
||||
const denied = yield* service.snapshot([{ action: "execute", resource: "*", effect: "deny" }])
|
||||
expect(denied.definitions).toEqual([])
|
||||
expect(denied.codeModeCatalog).toBeUndefined()
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("filters disabled tools with edit aliases and ordered wildcard precedence", () =>
|
||||
Effect.gen(function* () {
|
||||
const service = yield* ToolRegistry.Service
|
||||
@@ -170,12 +156,7 @@ describe("ToolRegistry", () => {
|
||||
const names = (permissions: PermissionV2.Ruleset) =>
|
||||
toolDefinitions(service, permissions).pipe(Effect.map((definitions) => definitions.map((tool) => tool.name)))
|
||||
|
||||
expect(yield* names([{ action: "question", resource: "*", effect: "deny" }])).toEqual([
|
||||
"bash",
|
||||
"edit",
|
||||
"write",
|
||||
"execute",
|
||||
])
|
||||
expect(yield* names([{ action: "question", resource: "*", effect: "deny" }])).toEqual(["bash", "edit", "write"])
|
||||
expect(
|
||||
yield* names([
|
||||
{ action: "*", resource: "*", effect: "deny" },
|
||||
@@ -188,11 +169,7 @@ describe("ToolRegistry", () => {
|
||||
{ action: "*", resource: "*", effect: "deny" },
|
||||
]),
|
||||
).toEqual([])
|
||||
expect(yield* names([{ action: "edit", resource: "*", effect: "deny" }])).toEqual([
|
||||
"bash",
|
||||
"question",
|
||||
"execute",
|
||||
])
|
||||
expect(yield* names([{ action: "edit", resource: "*", effect: "deny" }])).toEqual(["bash", "question"])
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -205,7 +182,7 @@ describe("ToolRegistry", () => {
|
||||
|
||||
expect(
|
||||
(yield* toolDefinitions(service, [{ action: "edit", resource: "*", effect: "deny" }])).map((tool) => tool.name),
|
||||
).toEqual(["first", "execute"])
|
||||
).toEqual(["first"])
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -214,9 +191,9 @@ describe("ToolRegistry", () => {
|
||||
const service = yield* ToolRegistry.Service
|
||||
const scope = yield* Scope.make()
|
||||
yield* service.register({ echo: make() }, { codemode: false }).pipe(Scope.provide(scope))
|
||||
expect((yield* toolDefinitions(service)).map((tool) => tool.name)).toEqual(["echo", "execute"])
|
||||
expect((yield* toolDefinitions(service)).map((tool) => tool.name)).toEqual(["echo"])
|
||||
yield* Scope.close(scope, Exit.void)
|
||||
expect((yield* toolDefinitions(service)).map((tool) => tool.name)).toEqual(["execute"])
|
||||
expect(yield* toolDefinitions(service)).toEqual([])
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -236,9 +213,9 @@ describe("ToolRegistry", () => {
|
||||
yield* Deferred.await(registered)
|
||||
yield* Fiber.interrupt(fiber)
|
||||
|
||||
expect((yield* toolDefinitions(service)).map((tool) => tool.name)).toEqual(["echo", "execute"])
|
||||
expect((yield* toolDefinitions(service)).map((tool) => tool.name)).toEqual(["echo"])
|
||||
yield* Scope.close(scope, Exit.void)
|
||||
expect((yield* toolDefinitions(service)).map((tool) => tool.name)).toEqual(["execute"])
|
||||
expect(yield* toolDefinitions(service)).toEqual([])
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
@@ -880,46 +880,6 @@ describe("SessionRunnerLLM", () => {
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("advertises execute and durable guidance for an empty Code Mode catalog", () =>
|
||||
Effect.gen(function* () {
|
||||
const session = yield* setup
|
||||
const empty = {
|
||||
catalog: [],
|
||||
tool: Tool.make({
|
||||
description: "Execute Code Mode",
|
||||
input: Schema.Struct({ code: Schema.String }),
|
||||
output: Schema.String,
|
||||
execute: () => Effect.succeed({ output: "unused" }),
|
||||
}),
|
||||
}
|
||||
codeModeMaterializations = [empty, empty, {}]
|
||||
yield* admit(session, "Continue without Code Mode tools")
|
||||
response = reply.stop()
|
||||
|
||||
yield* session.resume(sessionID)
|
||||
yield* admit(session, "Still no Code Mode tools")
|
||||
yield* session.resume(sessionID)
|
||||
yield* admit(session, "Code Mode denied")
|
||||
yield* session.resume(sessionID)
|
||||
|
||||
expect(requests).toHaveLength(3)
|
||||
expect(requests[0]?.tools.map((tool) => tool.name)).toEqual(["defect", "echo", "storefail", "execute"])
|
||||
expect(requests[0]?.system.some((part) => part.text.includes("Do not call `execute`"))).toBe(true)
|
||||
expect(requests[1]?.tools.map((tool) => tool.name)).toEqual(["defect", "echo", "storefail", "execute"])
|
||||
expect(requests[1]?.messages.filter((message) => message.role === "system")).toEqual([])
|
||||
expect(requests[2]?.tools.map((tool) => tool.name)).toEqual(["defect", "echo", "storefail"])
|
||||
expect(
|
||||
requests[2]?.messages.some(
|
||||
(message) =>
|
||||
message.role === "system" &&
|
||||
message.content.some(
|
||||
(part) => part.type === "text" && part.text.includes("Code Mode tools are no longer available"),
|
||||
),
|
||||
),
|
||||
).toBe(true)
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("applies session context hooks without exposing unavailable tools", () =>
|
||||
Effect.gen(function* () {
|
||||
const session = yield* setup
|
||||
|
||||
@@ -137,12 +137,10 @@ describe("EditTool", () => {
|
||||
Effect.andThen(
|
||||
withTool(tmp.path, (registry) =>
|
||||
Effect.gen(function* () {
|
||||
expect((yield* toolDefinitions(registry)).map((tool) => tool.name)).toEqual(["edit", "execute"])
|
||||
expect(
|
||||
(yield* toolDefinitions(registry, [{ action: "edit", resource: "*", effect: "deny" }])).map(
|
||||
(tool) => tool.name,
|
||||
),
|
||||
).toEqual(["execute"])
|
||||
expect((yield* toolDefinitions(registry)).map((tool) => tool.name)).toEqual(["edit"])
|
||||
expect(yield* toolDefinitions(registry, [{ action: "edit", resource: "*", effect: "deny" }])).toEqual(
|
||||
[],
|
||||
)
|
||||
const settled = yield* executeTool(
|
||||
registry,
|
||||
call({ path: "hello.txt", oldString: "before", newString: "after" }),
|
||||
|
||||
@@ -181,7 +181,7 @@ describe("PatchTool", () => {
|
||||
Effect.andThen(
|
||||
withTool(tmp.path, (registry) =>
|
||||
Effect.gen(function* () {
|
||||
expect((yield* toolDefinitions(registry)).map((tool) => tool.name)).toEqual(["patch", "execute"])
|
||||
expect((yield* toolDefinitions(registry)).map((tool) => tool.name)).toEqual(["patch"])
|
||||
const settled = yield* executeTool(
|
||||
registry,
|
||||
call(
|
||||
|
||||
@@ -97,11 +97,7 @@ describe("QuestionTool", () => {
|
||||
deny = true
|
||||
const registry = yield* ToolRegistry.Service
|
||||
|
||||
expect(
|
||||
(yield* toolDefinitions(registry, [{ action: "question", resource: "*", effect: "deny" }])).map(
|
||||
(tool) => tool.name,
|
||||
),
|
||||
).toEqual(["execute"])
|
||||
expect(yield* toolDefinitions(registry, [{ action: "question", resource: "*", effect: "deny" }])).toEqual([])
|
||||
expect(
|
||||
yield* executeTool(registry, {
|
||||
sessionID,
|
||||
@@ -146,7 +142,7 @@ describe("QuestionTool", () => {
|
||||
},
|
||||
]
|
||||
|
||||
expect((yield* toolDefinitions(registry)).map((definition) => definition.name)).toEqual(["question", "execute"])
|
||||
expect((yield* toolDefinitions(registry)).map((definition) => definition.name)).toEqual(["question"])
|
||||
expect(
|
||||
yield* executeTool(registry, {
|
||||
sessionID,
|
||||
|
||||
@@ -197,12 +197,8 @@ describe("ReadTool", () => {
|
||||
Effect.gen(function* () {
|
||||
const registry = yield* ToolRegistry.Service
|
||||
|
||||
expect((yield* toolDefinitions(registry)).map((tool) => tool.name)).toEqual(["read", "execute"])
|
||||
expect(
|
||||
(yield* toolDefinitions(registry, [{ action: "read", resource: "*", effect: "deny" }])).map(
|
||||
(tool) => tool.name,
|
||||
),
|
||||
).toEqual(["execute"])
|
||||
expect(yield* toolDefinitions(registry)).toMatchObject([{ name: "read" }])
|
||||
expect(yield* toolDefinitions(registry, [{ action: "read", resource: "*", effect: "deny" }])).toEqual([])
|
||||
const execution = yield* executeTool(registry, {
|
||||
sessionID,
|
||||
...toolIdentity,
|
||||
|
||||
@@ -92,7 +92,7 @@ describe("WebFetchTool registration", () => {
|
||||
const registry = yield* ToolRegistry.Service
|
||||
const url = "http://example.com/public"
|
||||
|
||||
expect((yield* toolDefinitions(registry)).map((tool) => tool.name)).toEqual(["webfetch", "execute"])
|
||||
expect((yield* toolDefinitions(registry)).map((tool) => tool.name)).toEqual(["webfetch"])
|
||||
expect(yield* executeTool(registry, call({ url, format: "text", timeout: 4 }))).toEqual({
|
||||
status: "completed",
|
||||
output: { url, contentType: "text/plain", format: "text", output: "hello" },
|
||||
|
||||
@@ -154,7 +154,7 @@ describe("WebSearchTool registration", () => {
|
||||
config = { provider: "exa", enableExa: false, enableParallel: false }
|
||||
const registry = yield* ToolRegistry.Service
|
||||
|
||||
expect((yield* toolDefinitions(registry)).map((tool) => tool.name)).toEqual(["websearch", "execute"])
|
||||
expect((yield* toolDefinitions(registry)).map((tool) => tool.name)).toEqual(["websearch"])
|
||||
expect(
|
||||
yield* executeTool(registry, {
|
||||
sessionID,
|
||||
|
||||
@@ -118,7 +118,7 @@ describe("WriteTool", () => {
|
||||
reset()
|
||||
return withTool(tmp.path, (registry) =>
|
||||
Effect.gen(function* () {
|
||||
expect((yield* toolDefinitions(registry)).map((tool) => tool.name)).toEqual(["write", "execute"])
|
||||
expect((yield* toolDefinitions(registry)).map((tool) => tool.name)).toEqual(["write"])
|
||||
const settled = yield* executeTool(registry, call({ path: "src/new.txt", content: "created" }))
|
||||
expect(settled).toEqual({
|
||||
status: "completed",
|
||||
|
||||
Reference in New Issue
Block a user