server/agents-bridge-switch.test.ts
Rama main · réplica de lectura del repositorio original.
// The harness side of the agents tool bridge (drivers/agents-bridge.ts): the
// switch, the one decision for a turn (which bots, which engines), what the
// prompt builder says on the strength of it, and the name a bridged call is
// given. What the proxy then lists and runs is drivers/agents-bridge.test.ts
// and drivers/agents-proxy.test.ts; its bytes are
// drivers/agents-bridge-wire.test.ts; the mounts that start it unbridged are
// agents-bridge-gates.test.ts; a real turn is agents-bridge.e2e.test.ts.
import { execFileSync, spawn } from "node:child_process";
import { mkdirSync, mkdtempSync, symlinkSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { build } from "esbuild";
import { afterAll, describe, expect, it, vi } from "vitest";
import {
AGENTS_PROXY_ENV,
BRIDGED_THREADS_MAX,
TOOL_BRIDGE_ENV,
agentsProxyLaunch,
agentsToolBridgeEnabled,
agentsToolBridgeEnv as decide,
agentsToolBridgeRefusal as refusal,
applyAgentsBridge,
bridgedAgents,
bridgedToolTitle,
claudeManagedPolicyFiles,
engineBridgeRefusal,
turnEngine,
type BridgeEngine,
type EngineHost,
} from "./agents-bridge-switch.ts";
import { removeTempDir } from "./testing/cleanup.ts";
import { instanceConfigs, parseConfigPatch, parseStoredConfig } from "./config.ts";
import { BRIDGE_HOT_TOOL_NAMES, FIND_TOOL, USE_TOOL, agentsToolBridged } from "./drivers/agents-bridge.ts";
import { SPAWNED_PROXIES } from "./proxy-paths.ts";
import { availableTools } from "./drivers/agents-catalog.ts";
import { WATCHER_OPTIONS_CARD_BOT_ID } from "../shared/options-card.ts";
import { AGENTS_TOOL_BRIDGE_PROMPT, buildSystemPrompt, type PromptPart } from "./system-prompt.ts";
const ON = { features: { agentsToolBridge: true } };
// Every folder a decision below reads is one of these: never the machine's
// own home, project or policy files.
const folders: string[] = [];
const folder = () => { const made = mkdtempSync(join(tmpdir(), "omb-bridge-switch-")); folders.push(made); return made; };
afterAll(async () => { for (const made of folders) await removeTempDir(made); });
/** A machine with no Claude settings anywhere. */
const HOST: EngineHost = { home: folder(), managedPolicyFiles: [join(folder(), "managed-settings.json")] };
/** An engine the bridge is applied on: a current Claude CLI in a folder with no settings of its own. */
const CLAUDE: BridgeEngine = { driverKind: "claudeAgent", version: "2.1.232 (Claude Code)", cwd: folder() };
const agentsToolBridgeEnv = (...args: Parameters<typeof decide> extends [infer A, infer B, infer C, ...unknown[]] ? [A, B, C, NodeJS.ProcessEnv?, EngineHost?] : never) =>
decide(args[0], args[1], args[2], args[3] ?? {}, args[4] ?? HOST);
const agentsToolBridgeRefusal = (...args: Parameters<typeof refusal> extends [infer A, infer B, infer C, ...unknown[]] ? [A, B, C, NodeJS.ProcessEnv?, EngineHost?] : never) =>
refusal(args[0], args[1], args[2], args[3] ?? {}, args[4] ?? HOST);
/** Writes one Claude settings file, making its folder. */
const settings = (path: string, content: unknown) => { mkdirSync(join(path, ".."), { recursive: true }); writeFileSync(path, typeof content === "string" ? content : JSON.stringify(content)); };
// as much of a bot record as the decision reads: one with no tool selection
const bot: { id: string; toolScope?: unknown } = { id: "bot-1" };
const on = { [TOOL_BRIDGE_ENV]: "1" };
const off = { [TOOL_BRIDGE_ENV]: "0" };
describe("the agents tool bridge switch", () => {
it("is off unless switched on", () => {
expect(agentsToolBridgeEnabled({}, {})).toBe(false);
expect(agentsToolBridgeEnabled({ features: {} }, {})).toBe(false);
expect(agentsToolBridgeEnabled({ features: { agentsToolBridge: false } }, {})).toBe(false);
expect(agentsToolBridgeEnabled(parseStoredConfig({ features: { skillAuthoring: true, sharedComputers: true } }), {})).toBe(false);
});
it("is switched on by the config key or by OMB_AGENTS_TOOL_BRIDGE=1", () => {
expect(agentsToolBridgeEnabled(ON, {})).toBe(true);
expect(agentsToolBridgeEnabled({}, { OMB_AGENTS_TOOL_BRIDGE: "1" })).toBe(true);
expect(TOOL_BRIDGE_ENV).toBe("OMB_AGENTS_TOOL_BRIDGE");
});
it("lets the environment override the saved setting either way, and ignores any other value", () => {
expect(agentsToolBridgeEnabled(ON, { OMB_AGENTS_TOOL_BRIDGE: "0" })).toBe(false);
expect(agentsToolBridgeEnabled({ features: { agentsToolBridge: false } }, { OMB_AGENTS_TOOL_BRIDGE: "1" })).toBe(true);
for (const value of ["", "true", "yes", "on", "2"]) {
expect(agentsToolBridgeEnabled({}, { OMB_AGENTS_TOOL_BRIDGE: value }), value).toBe(false);
expect(agentsToolBridgeEnabled(ON, { OMB_AGENTS_TOOL_BRIDGE: value }), value).toBe(true);
}
});
it("is a saved setting like the other feature flags: kept by a patch and by the stored file, a boolean only", () => {
expect(parseConfigPatch({ features: { agentsToolBridge: true } })).toEqual({ features: { agentsToolBridge: true } });
expect(parseStoredConfig({ features: { agentsToolBridge: true, browser: true } })).toEqual({ features: { agentsToolBridge: true, browser: true } });
expect(agentsToolBridgeEnabled(parseStoredConfig({ features: { agentsToolBridge: true } }), {})).toBe(true);
expect(() => parseConfigPatch({ features: { agentsToolBridge: "yes" } })).toThrow("features.agentsToolBridge");
// the other flags are still read beside it, and an unknown one still drops
expect(parseConfigPatch({ features: { agentsToolBridge: false, showToolCalls: true, skillRecorder: true } })).toEqual({ features: { agentsToolBridge: false, showToolCalls: true } });
});
});
describe("which engines a turn is bridged on", () => {
// The default is no bridge: an engine is bridged only where the harness
// pre-approves the whole agents server, passes no pattern that names it,
// and everything else the engine reads about a tool's name can be read too.
it.each([
// the chat-completions runtime, by every driver that runs on it
["openai-compat"], ["cerebras"], ["greenference"], ["modelgo"], ["grok"], ["groq"], ["meta"], ["minimax"], ["mistral"],
])("bridges %s", (driverKind) => {
expect(engineBridgeRefusal(ON, { driverKind }, {}, HOST)).toBeNull();
expect(agentsToolBridgeEnv(ON, bot, { driverKind }, {})).toEqual(on);
});
it("bridges Claude Code, on a current CLI in a folder whose settings name none of these tools", () => {
expect(engineBridgeRefusal(ON, CLAUDE, {}, HOST)).toBeNull();
expect(agentsToolBridgeEnv(ON, bot, CLAUDE, {})).toEqual(on);
});
it("does not bridge Codex: it reads a layered configuration and hooks of its own that are not examined", () => {
// ~/.codex/config.toml or CODEX_HOME, a Company engine's own home under
// this server's data, a trusted project's .codex/config.toml,
// /etc/codex/config.toml and managed_config.toml, and its PreToolUse and
// PermissionRequest hooks: each can take a rule about a tool by name.
const reason = "Codex reads its own layered configuration and hooks, which this version does not examine";
expect(engineBridgeRefusal(ON, { driverKind: "codex", instanceId: "codex" }, {}, HOST)).toBe(reason);
expect(engineBridgeRefusal(ON, { driverKind: "codex", instanceId: "chatgpt", config: { authMode: "chatgpt-plan" } }, {}, HOST)).toBe(reason);
// whatever its environment says about where that configuration is
expect(agentsToolBridgeEnv(ON, bot, { driverKind: "codex", environment: { CODEX_HOME: join(folder(), "gone") } }, { HOME: folder() })).toEqual(off);
expect(agentsToolBridgeEnv({}, bot, { driverKind: "codex" }, { OMB_AGENTS_TOOL_BRIDGE: "1" })).toEqual(off);
});
it("does not bridge Pi: it loads the person's own extensions, which can block a tool by its name", () => {
// pi is launched with -e for this server's extension and nothing that
// keeps its own out (drivers/pi.ts PI_ARGS), in the person's home. An
// extension's tool_call handler sees the name in the call, as this
// server's own does (drivers/pi-mcp-extension.ts).
const reason = "Pi loads the person's own extensions, which can block a tool by its name, and this version does not examine them";
expect(engineBridgeRefusal(ON, { driverKind: "piAgent", instanceId: "pi" }, {}, HOST)).toBe(reason);
expect(engineBridgeRefusal(ON, { driverKind: "piAgent", cwd: folder() }, {}, HOST)).toBe(reason);
expect(agentsToolBridgeEnv(ON, bot, { driverKind: "piAgent" }, {})).toEqual(off);
expect(agentsToolBridgeEnv({}, bot, { driverKind: "piAgent" }, { OMB_AGENTS_TOOL_BRIDGE: "1" })).toEqual(off);
});
it.each([
// ACP agents keep their own permissions, Grok among them
["grokAgent"], ["geminiAgent"], ["qwenAgent"], ["kimiAgent"], ["droidAgent"], ["cursorAgent"], ["clineAgent"], ["copilotAgent"], ["hermesAgent"],
["opencodeGo"], ["antigravityAgent"], ["customAcp"],
// not shown to qualify, and anything nobody has heard of
["museCode"], ["someFutureDriver"], [""],
])("does not bridge %s", (driverKind) => {
expect(engineBridgeRefusal(ON, { driverKind, version: "2.1.232" }, {}, HOST)).toEqual(expect.any(String));
expect(agentsToolBridgeEnv(ON, bot, { driverKind }, {})).toEqual(off);
expect(agentsToolBridgeEnv({}, bot, { driverKind }, { OMB_AGENTS_TOOL_BRIDGE: "1" })).toEqual(off);
});
it("does not bridge a turn with no engine at all", () => {
expect(agentsToolBridgeEnv(ON, bot, undefined, {})).toEqual(off);
});
it("sorts the default fleet the same way: its Claude and API-model engines, and none of Codex, Pi or its ACP agents", () => {
const fleet = instanceConfigs({});
const bridged = new Set(Object.entries(fleet).filter(([, instance]) => engineBridgeRefusal(ON, turnEngine({ instanceId: "any", driverKind: instance.driver }, { any: instance }, { version: "2.1.232 (Claude Code)" }, CLAUDE.cwd), {}, HOST) === null).map(([id]) => id));
for (const id of ["claude", "openaiCompat", "mistral", "cerebras", "groq", "greenference", "modelgo", "meta"]) expect(bridged.has(id), id).toBe(true);
// the default `grok` is the ACP agent, not the API-key driver
for (const id of ["codex", "chatgpt", "pi", "grok", "kimi", "droid", "cursor", "antigravity", "opencodeGo", "museCode", "qwen", "hermes", "cline"]) expect(bridged.has(id), id).toBe(false);
for (const id of bridged) expect(["claudeAgent", "openai-compat", "cerebras", "greenference", "modelgo", "grok", "groq", "meta", "minimax", "mistral"], id).toContain(fleet[id]!.driver);
});
describe("Claude Code's exceptions", () => {
it("is not bridged when the bot inherits this machine's own Claude Code setup", () => {
// settings the harness never sees may deny an agents tool by name
expect(agentsToolBridgeEnv(ON, bot, CLAUDE, { OMB_CLAUDE_INHERIT_USER_CONFIG: "1" })).toEqual(off);
expect(agentsToolBridgeEnv(ON, bot, { ...CLAUDE, environment: { OMB_CLAUDE_INHERIT_USER_CONFIG: "1" } }, {})).toEqual(off);
// the instance's environment is laid over the server's, as the driver lays it
expect(agentsToolBridgeEnv(ON, bot, { ...CLAUDE, environment: { OMB_CLAUDE_INHERIT_USER_CONFIG: "0" } }, { OMB_CLAUDE_INHERIT_USER_CONFIG: "1" })).toEqual(on);
// and the person's own Claude Code MCP servers are part of that setup
expect(agentsToolBridgeEnv({ features: { agentsToolBridge: true, claudeUserMcp: true } }, bot, CLAUDE, {})).toEqual(off);
expect(agentsToolBridgeEnv({ features: { agentsToolBridge: true, claudeUserMcp: false } }, bot, CLAUDE, {})).toEqual(on);
// neither is a reason on another engine
expect(agentsToolBridgeEnv({ features: { agentsToolBridge: true, claudeUserMcp: true } }, bot, { driverKind: "openai-compat" }, { OMB_CLAUDE_INHERIT_USER_CONFIG: "1" })).toEqual(on);
});
const PATTERNS = "the engine's tool patterns name the agents server or match tools by pattern";
it.each([
[{ disallowedTools: ["mcp__agents__vm_exec"] }],
[{ disallowedTools: ["Bash", "mcp__agents"] }],
[{ disallowedTools: ["MCP__Agents__start_thread"] }],
// a wildcard in the server position may reach the agents server
[{ disallowedTools: ["mcp__*"] }],
[{ disallowedTools: ["MCP__*"] }],
[{ disallowedTools: ["mcp__*__vm_exec"] }],
[{ disallowedTools: ["mcp__?__vm_exec"] }],
[{ tools: ["Read", "mcp__agents__list_bots"] }],
// The driver takes any text as an entry and joins the entries with a
// comma for the CLI, so one entry can hold several patterns.
[{ disallowedTools: ["Bash,mcp__agents__vm_exec"] }],
[{ disallowedTools: ["Bash, mcp__agents__vm_exec"] }],
[{ disallowedTools: ["Bash(rm *),mcp__agents__vm_exec"] }],
[{ disallowedTools: ["Read", "Edit mcp__agents__start_thread"] }],
[{ disallowedTools: ["Bash,mcp__*"] }],
// "contains", as for a settings file: a server whose name merely starts the same way counts
[{ disallowedTools: ["mcp__agentsmith__run"] }],
// The CLI matches a deny rule's whole tool name as a glob, so a
// wildcard anywhere in a tool's name can reach an agents tool, whatever
// else the rule says. "In a tool's name" is outside parentheses, with
// one flag set at "(" and cleared at ")", as the CLI splits the list.
[{ disallowedTools: ["*"] }],
[{ disallowedTools: ["mcp__a*__vm_exec"] }],
[{ disallowedTools: ["*exec*"] }],
[{ disallowedTools: ["*__vm_exec"] }],
[{ disallowedTools: ["mcp_*"] }],
[{ disallowedTools: ["mcp__composio__*"] }],
[{ disallowedTools: ["Bash", "WebFetch", "mcp__composio__GMAIL_SEND_EMAIL", "mcp__composio__*"] }],
[{ disallowedTools: ["Read?"] }],
[{ tools: ["Read", "Edi*"] }],
[{ disallowedTools: ["Bash(echo ( x),mcp__a*__vm_exec"] }],
[{ disallowedTools: ["Bash(\\(a),*exec*"] }],
[{ disallowedTools: ["Bash(a),mcp__a*__vm_exec,Read(b)"] }],
[{ disallowedTools: ["Bash(a)*"] }],
// a list the driver itself would refuse is never a reason to bridge
[{ disallowedTools: "mcp__agents__vm_exec" }],
[{ disallowedTools: [7] }],
])("is not bridged when a pattern the CLI is handed names the agents server or matches tools by pattern: %j", (config) => {
expect(agentsToolBridgeRefusal(ON, bot, { ...CLAUDE, config }, {})).toBe(PATTERNS);
expect(agentsToolBridgeEnv(ON, bot, { ...CLAUDE, config }, {})).toEqual(off);
});
it.each([
[undefined], [{}], [{ disallowedTools: [] }], [{ disallowedTools: ["Bash", "WebFetch", "mcp__composio__GMAIL_SEND_EMAIL", "Bash(git *),mcp__notes__read"] }],
[{ tools: ["Read", "Grep"], disallowedTools: ["Bash(rm *)"] }], [{ cli: "claude", permissionMode: "auto" }],
// a wildcard inside the parentheses is in the rule's specifier, not in a tool's name
[{ disallowedTools: ["Bash(git *)"] }], [{ disallowedTools: ["Read(./src/**)"] }], [{ disallowedTools: ["Bash(npm run test:*)"] }],
[{ disallowedTools: ["Bash(git *),Read(./src/**)", "WebFetch(domain:*.example.com)"] }],
])("is bridged when none of them does: %j", (config) => {
expect(agentsToolBridgeEnv(ON, bot, { ...CLAUDE, config }, {})).toEqual(on);
});
it("decides an entry of many wildcards at once: no pattern is ever built from configuration text", () => {
// Built into a regular expression, this entry took about a minute of
// the server's one thread to test against the catalog's names. It is a
// wildcard in a tool's name, so it refuses, and the test's own timeout
// is the guard: a regression takes minutes.
const stars = "**********#";
expect(agentsToolBridgeRefusal(ON, bot, { ...CLAUDE, config: { disallowedTools: [stars] } }, {})).toBe(PATTERNS);
expect(agentsToolBridgeRefusal(ON, bot, { ...CLAUDE, config: { tools: [stars, `${stars}${stars}`] } }, {})).toBe(PATTERNS);
expect(agentsToolBridgeRefusal(ON, bot, inFolder("settings.json", { permissions: { deny: [stars] } }))).toBe(WILDCARD);
// an allow rule does not glob
expect(agentsToolBridgeEnv(ON, bot, inFolder("settings.json", { permissions: { allow: [stars] } }))).toEqual(on);
});
it("is not bridged when a settings file's deny or ask rules match tools by pattern, whatever server they are about", () => {
// The CLI matches a deny or ask rule's whole tool name as a glob, so
// such a rule can reach a MausBot tool by pattern even when it was
// written for another server; allow rules do not glob.
expect(agentsToolBridgeRefusal(ON, bot, inFolder("settings.json", { permissions: { deny: ["mcp__github__*"] } }))).toBe(WILDCARD);
expect(agentsToolBridgeRefusal(ON, bot, inFolder("settings.json", { permissions: { ask: ["*exec*"] } }))).toBe(WILDCARD);
expect(agentsToolBridgeRefusal(ON, bot, inFolder("settings.local.json", { permissions: { deny: ["Bash(a)", "mcp__a*__vm_exec"] } }))).toBe(WILDCARD);
expect(agentsToolBridgeRefusal(ON, bot, inFolder("settings.json", { permissions: { deny: ["Read?"] } }))).toBe(WILDCARD);
// the keys in any case, as for hooks
expect(agentsToolBridgeRefusal(ON, bot, inFolder("settings.json", { Permissions: { DENY: ["*"] } }))).toBe(WILDCARD);
expect(agentsToolBridgeRefusal(ON, bot, inFolder("settings.json", { permissions: { Ask: ["mcp__github__*"] } }))).toBe(WILDCARD);
const policy = join(folder(), "managed-settings.json");
settings(policy, { permissions: { deny: ["mcp__github__*"] } });
expect(agentsToolBridgeRefusal(ON, bot, CLAUDE, {}, { ...HOST, managedPolicyFiles: [policy] })).toBe(WILDCARD);
// naming the agents server is still the first thing seen
expect(agentsToolBridgeRefusal(ON, bot, inFolder("settings.json", { permissions: { deny: ["mcp__github__*", "mcp__agents__vm_exec"] } }))).toBe(NAMES);
// not given the wildcard test: allow rules, additional directories, anything outside permissions
expect(agentsToolBridgeEnv(ON, bot, inFolder("settings.json", { permissions: { allow: ["mcp__github__*", "*"] } }))).toEqual(on);
expect(agentsToolBridgeEnv(ON, bot, inFolder("settings.json", { permissions: { additionalDirectories: ["/tmp/*"], deny: ["Bash(git *)", "Read(./src/**)", "Bash(npm run test:*)"] } }))).toEqual(on);
expect(agentsToolBridgeEnv(ON, bot, inFolder("settings.json", { env: { GLOB: "*" }, deny: ["*"], hooks: { PostToolUse: [{ matcher: "*", hooks: [] }] } }))).toEqual(on);
// a rule that is not a string is walked like anything else
expect(agentsToolBridgeEnv(ON, bot, inFolder("settings.json", { permissions: { deny: [{ tool: "Bash" }, 7] } }))).toEqual(on);
});
it("is not bridged when a managed policy allows only its own permission rules, which may void the pre-approval", () => {
const only = (content: unknown) => { const policy = join(folder(), "managed-settings.json"); settings(policy, content); return { ...HOST, managedPolicyFiles: [policy] }; };
expect(agentsToolBridgeRefusal(ON, bot, CLAUDE, {}, only({ allowManagedPermissionRulesOnly: true }))).toBe(MANAGED_ONLY);
// any value but false or null, the key in any case, in a drop-in too
expect(agentsToolBridgeRefusal(ON, bot, CLAUDE, {}, only({ allowmanagedpermissionrulesonly: 1 }))).toBe(MANAGED_ONLY);
expect(agentsToolBridgeRefusal(ON, bot, CLAUDE, {}, only({ AllowManagedPermissionRulesOnly: "yes" }))).toBe(MANAGED_ONLY);
const base = folder();
settings(join(base, "managed-settings.d", "10-rules.json"), { allowManagedPermissionRulesOnly: {} });
expect(agentsToolBridgeRefusal(ON, bot, CLAUDE, {}, { ...HOST, managedPolicyFiles: [join(base, "managed-settings.json")] })).toBe(MANAGED_ONLY);
expect(agentsToolBridgeEnv(ON, bot, CLAUDE, {}, only({ allowManagedPermissionRulesOnly: false }))).toEqual(on);
expect(agentsToolBridgeEnv(ON, bot, CLAUDE, {}, only({ allowManagedPermissionRulesOnly: null }))).toEqual(on);
expect(agentsToolBridgeEnv(ON, bot, CLAUDE, {}, only({ permissions: { allow: ["Read"] } }))).toEqual(on);
// the key means nothing in a project's or folder's own settings
expect(agentsToolBridgeEnv(ON, bot, inFolder("settings.json", { allowManagedPermissionRulesOnly: true }))).toEqual(on);
expect(agentsToolBridgeEnv(ON, bot, inFolder("settings.local.json", { allowManagedPermissionRulesOnly: true }))).toEqual(on);
});
// An isolated turn is launched with --setting-sources project, so the
// CLI still reads the settings of the folder it is launched in, and the
// machine's managed policy always applies.
const NAMES = "a Claude settings file names the agents server";
const HOOKS = "a Claude settings file sets a hook that judges tool calls";
const WILDCARD = "a Claude settings file denies or asks for tools by pattern";
const MANAGED_ONLY = "a managed Claude policy allows only its own permission rules";
const UNREADABLE = "a Claude settings file cannot be read";
const inFolder = (file: string, content: unknown) => { const cwd = folder(); settings(join(cwd, ".claude", file), content); return { ...CLAUDE, cwd }; };
it("is not bridged when the project's settings name the agents server anywhere", () => {
expect(agentsToolBridgeRefusal(ON, bot, inFolder("settings.json", { permissions: { deny: ["Bash(rm *)", "mcp__agents__vm_exec"] } }))).toBe(NAMES);
expect(agentsToolBridgeEnv(ON, bot, inFolder("settings.json", { permissions: { ask: ["mcp__agents"] } }))).toEqual(off);
// anywhere in the file, however it is nested or cased: a rule, a hook's matcher, a note
expect(agentsToolBridgeEnv(ON, bot, inFolder("settings.json", { permissions: { rules: [{ tool: "MCP__Agents__start_thread", effect: "deny" }] } }))).toEqual(off);
expect(agentsToolBridgeRefusal(ON, bot, inFolder("settings.json", { hooks: { PostToolUse: [{ matcher: "mcp__agents__vm_exec", hooks: [{ type: "command", command: "audit.sh" }] }] } }))).toBe(NAMES);
expect(agentsToolBridgeRefusal(ON, bot, inFolder("settings.json", { env: { NOTE: "mcp__agents__vm_exec" }, permissions: { allow: ["Read"] } }))).toBe(NAMES);
expect(agentsToolBridgeEnv(ON, bot, inFolder("settings.json", { permissions: { allow: ["mcp__agents__list_bots"] } }))).toEqual(off);
// the test is "contains", so a server whose name merely starts the same way counts too: the cautious side
expect(agentsToolBridgeEnv(ON, bot, inFolder("settings.json", { permissions: { deny: ["mcp__agentsmith__run"] } }))).toEqual(off);
// a wildcard in the server position, the same test as for the engine's own pattern lists
expect(agentsToolBridgeRefusal(ON, bot, inFolder("settings.json", { permissions: { deny: ["mcp__*__vm_exec"] } }))).toBe(NAMES);
expect(agentsToolBridgeRefusal(ON, bot, inFolder("settings.local.json", { permissions: { allow: ["Read", "MCP__?"] } }))).toBe(NAMES);
});
it("is not bridged when the name is spelled with JSON escapes, which the raw text does not show", () => {
// "mcp__agents" is "mcp__agents" once parsed: every string value and every key is walked
expect(agentsToolBridgeRefusal(ON, bot, inFolder("settings.json", '{"permissions":{"deny":["mcp\\u005f\\u005fagents\\u005f\\u005fvm_exec"]}}'))).toBe(NAMES);
expect(agentsToolBridgeRefusal(ON, bot, inFolder("settings.json", '{"permissions":{"deny":["\\u006dcp__*"]}}'))).toBe(NAMES);
expect(agentsToolBridgeRefusal(ON, bot, inFolder("settings.local.json", '{"hooks":{"PostToolUse":[{"matcher":"MCP\\u005f\\u005fAGENTS","hooks":[]}]}}'))).toBe(NAMES);
expect(agentsToolBridgeRefusal(ON, bot, inFolder("settings.json", '{"env":{"mcp\\u005f\\u005fagents":"1"}}'))).toBe(NAMES);
// deep in a list of lists, as a key
expect(agentsToolBridgeRefusal(ON, bot, inFolder("settings.json", '{"a":[[{"MCP\\u005f\\u005fAgents\\u005f\\u005fvm_exec":true}]]}'))).toBe(NAMES);
// the escaped spelling of something else is read for what it is
expect(agentsToolBridgeEnv(ON, bot, inFolder("settings.json", '{"permissions":{"deny":["mcp\\u005f\\u005fcomposio"]}}'))).toEqual(on);
});
it("is not bridged when the folder's local settings do", () => {
expect(agentsToolBridgeRefusal(ON, bot, inFolder("settings.local.json", { permissions: { deny: ["mcp__agents__shared_computer"] } }))).toBe(NAMES);
});
it("is not bridged when the machine's managed policy does", () => {
const policy = join(folder(), "managed-settings.json");
settings(policy, { permissions: { deny: ["mcp__agents__vm_exec"] } });
expect(agentsToolBridgeRefusal(ON, bot, CLAUDE, {}, { ...HOST, managedPolicyFiles: [policy] })).toBe(NAMES);
// every documented location is read: the rule may be in the second
expect(agentsToolBridgeEnv(ON, bot, CLAUDE, {}, { ...HOST, managedPolicyFiles: [join(folder(), "absent.json"), policy] })).toEqual(off);
});
it("reads the folder the turn is launched in: the person's home when the turn has none", () => {
// drivers/claude.ts launches in turn.cwd ?? homedir()
const home = folder();
settings(join(home, ".claude", "settings.json"), { permissions: { deny: ["mcp__agents__vm_exec"] } });
expect(agentsToolBridgeEnv(ON, bot, { ...CLAUDE, cwd: undefined }, {}, { ...HOST, home })).toEqual(off);
expect(agentsToolBridgeEnv(ON, bot, { ...CLAUDE, cwd: undefined }, {})).toEqual(on);
// and never the home when the turn has a folder of its own
expect(agentsToolBridgeEnv(ON, bot, CLAUDE, {}, { ...HOST, home })).toEqual(on);
});
it.each([
["{ not json"], [""], ["null"], ["[]"], ['"mcp__composio"'],
])("is not bridged when a settings file is there and cannot be read as settings: %j", (content) => {
expect(agentsToolBridgeRefusal(ON, bot, inFolder("settings.json", content))).toBe(UNREADABLE);
expect(agentsToolBridgeRefusal(ON, bot, inFolder("settings.local.json", content))).toBe(UNREADABLE);
});
it("is not bridged when something that is not a file sits where a settings file would", () => {
const cwd = folder();
mkdirSync(join(cwd, ".claude", "settings.json"), { recursive: true });
expect(agentsToolBridgeRefusal(ON, bot, { ...CLAUDE, cwd })).toBe(UNREADABLE);
});
it("is bridged when the files are absent, or neither name the agents server nor judge tool calls", () => {
expect(agentsToolBridgeEnv(ON, bot, { ...CLAUDE, cwd: folder() })).toEqual(on);
// a folder that is not there at all has no settings either
expect(agentsToolBridgeEnv(ON, bot, { ...CLAUDE, cwd: join(folder(), "gone") })).toEqual(on);
expect(agentsToolBridgeEnv(ON, bot, inFolder("settings.json", {}))).toEqual(on);
expect(agentsToolBridgeEnv(ON, bot, inFolder("settings.json", { permissions: { deny: ["Bash(rm *)", "mcp__composio__GMAIL_SEND_EMAIL", "mcp__notes"], allow: ["Read"] } }))).toEqual(on);
// hooks that only watch what happened are no judge of a call
const watching = [{ matcher: "", hooks: [{ type: "command", command: "log.sh" }] }];
expect(agentsToolBridgeEnv(ON, bot, inFolder("settings.json", { hooks: { PostToolUse: watching, Stop: watching, SessionStart: watching, PreCompact: watching } }))).toEqual(on);
// a key that holds null, or is not there, sets none
expect(agentsToolBridgeEnv(ON, bot, inFolder("settings.json", { hooks: { PreToolUse: null, PermissionRequest: null } }))).toEqual(on);
expect(agentsToolBridgeEnv(ON, bot, inFolder("settings.json", { hooks: {} }))).toEqual(on);
// "hooks" somewhere other than as a key is a word, not a block
expect(agentsToolBridgeEnv(ON, bot, inFolder("settings.json", { permissions: { deny: ["hooks", "PreToolUse"] } }))).toEqual(on);
});
it.each([
// whatever it matches: a script can test the name itself, and an empty or wildcard matcher sees every call
[{ PreToolUse: [{ matcher: "Bash", hooks: [{ type: "command", command: "check.sh" }] }] }],
[{ PreToolUse: [{ matcher: "", hooks: [{ type: "command", command: "check.sh" }] }] }],
[{ PreToolUse: [{ matcher: "*", hooks: [{ type: "command", command: "check.sh" }] }] }],
[{ PermissionRequest: [{ hooks: [{ type: "command", command: "decide.sh" }] }] }],
// any value but null: a list of none, a string, an object, a number
[{ PreToolUse: [] }],
[{ PermissionRequest: [] }],
[{ PreToolUse: "check.sh" }],
[{ PermissionRequest: { matcher: "" } }],
[{ PreToolUse: 0 }],
[{ PreToolUse: false }],
// in any case: the key is compared case-insensitively
[{ pretooluse: [{ matcher: "Bash", hooks: [{ type: "command", command: "check.sh" }] }] }],
[{ preToolUse: [{ matcher: "Bash", hooks: [{ type: "command", command: "check.sh" }] }] }],
[{ PRETOOLUSE: [] }],
[{ permissionrequest: [{ hooks: [{ type: "command", command: "decide.sh" }] }] }],
[{ PermissionREQUEST: {} }],
])("is not bridged when a settings file sets a hook that judges tool calls: %j", (hooks) => {
expect(agentsToolBridgeRefusal(ON, bot, inFolder("settings.json", { hooks }))).toBe(HOOKS);
expect(agentsToolBridgeRefusal(ON, bot, inFolder("settings.local.json", { hooks }))).toBe(HOOKS);
const policy = join(folder(), "managed-settings.json");
settings(policy, { hooks });
expect(agentsToolBridgeRefusal(ON, bot, CLAUDE, {}, { ...HOST, managedPolicyFiles: [policy] })).toBe(HOOKS);
// and so is the outer key, wherever the block sits
expect(agentsToolBridgeRefusal(ON, bot, inFolder("settings.json", { Hooks: hooks }))).toBe(HOOKS);
expect(agentsToolBridgeRefusal(ON, bot, inFolder("settings.json", { HOOKS: hooks }))).toBe(HOOKS);
expect(agentsToolBridgeRefusal(ON, bot, inFolder("settings.json", { profiles: [{ hooks }] }))).toBe(HOOKS);
});
it("is not bridged when a file's hooks cannot be read as hooks", () => {
expect(agentsToolBridgeRefusal(ON, bot, inFolder("settings.json", { hooks: "PreToolUse" }))).toBe(UNREADABLE);
expect(agentsToolBridgeRefusal(ON, bot, inFolder("settings.json", { hooks: [] }))).toBe(UNREADABLE);
expect(agentsToolBridgeRefusal(ON, bot, inFolder("settings.json", { Hooks: 1 }))).toBe(UNREADABLE);
});
it("is not bridged when a settings file is larger than it reads, and does not read it", () => {
// valid, harmless settings, one byte past the cap
const padded = (bytes: number) => `{${" ".repeat(bytes - 2)}}`;
const cwd = folder();
settings(join(cwd, ".claude", "settings.json"), padded(1024 * 1024 + 1));
expect(agentsToolBridgeRefusal(ON, bot, { ...CLAUDE, cwd })).toBe(UNREADABLE);
// at the cap it is read
const fits = folder();
settings(join(fits, ".claude", "settings.json"), padded(1024 * 1024));
expect(agentsToolBridgeEnv(ON, bot, { ...CLAUDE, cwd: fits })).toEqual(on);
});
it.skipIf(process.platform === "win32")("is not bridged when a FIFO sits where a settings file would, and does not open it", () => {
// A writer stands ready with harmless settings: were the FIFO opened
// and read, the turn would be bridged. It is refused on sight, and the
// writer never gets to write.
const cwd = folder();
mkdirSync(join(cwd, ".claude"));
const fifo = join(cwd, ".claude", "settings.json");
execFileSync("mkfifo", [fifo]);
const writer = spawn("sh", ["-c", 'echo "{}" > "$0"', fifo], { stdio: "ignore" });
try {
expect(agentsToolBridgeRefusal(ON, bot, { ...CLAUDE, cwd })).toBe(UNREADABLE);
} finally {
writer.kill("SIGKILL");
}
});
it.skipIf(process.platform === "win32")("is not bridged when a device sits where a settings file would", () => {
// reached through a link, as the CLI would reach it; its text, "", would read as unreadable anyway, so the reason is the same
const cwd = folder();
mkdirSync(join(cwd, ".claude"));
symlinkSync("/dev/null", join(cwd, ".claude", "settings.json"));
expect(agentsToolBridgeRefusal(ON, bot, { ...CLAUDE, cwd })).toBe(UNREADABLE);
});
it("reads every settings file in the managed policy's drop-in folder", () => {
const base = folder();
const host = { ...HOST, managedPolicyFiles: [join(base, "managed-settings.json")] };
// no such folder: nothing to read
expect(agentsToolBridgeEnv(ON, bot, CLAUDE, {}, host)).toEqual(on);
settings(join(base, "managed-settings.d", "10-defaults.json"), { permissions: { deny: ["WebFetch"] } });
settings(join(base, "managed-settings.d", "notes.txt"), "mcp__agents__vm_exec is mentioned here, in a file the CLI does not read");
expect(agentsToolBridgeEnv(ON, bot, CLAUDE, {}, host)).toEqual(on);
settings(join(base, "managed-settings.d", "20-tools.json"), { permissions: { deny: ["mcp__agents__vm_exec"] } });
expect(agentsToolBridgeRefusal(ON, bot, CLAUDE, {}, host)).toBe(NAMES);
// one that is there and cannot be read
const broken = folder();
settings(join(broken, "managed-settings.d", "10-defaults.json"), "{ not json");
expect(agentsToolBridgeRefusal(ON, bot, CLAUDE, {}, { ...HOST, managedPolicyFiles: [join(broken, "managed-settings.json")] })).toBe(UNREADABLE);
// and something that is not a folder where the folder would be
const odd = folder();
settings(join(odd, "managed-settings.d"), "not a folder");
expect(agentsToolBridgeRefusal(ON, bot, CLAUDE, {}, { ...HOST, managedPolicyFiles: [join(odd, "managed-settings.json")] })).toBe(UNREADABLE);
});
it.each([
["1.0.121 (Claude Code)"], ["1.0.0"], ["0.2.9 (Claude Code)"],
])("is not bridged on a CLI older than the one that keeps the person's settings out: %s", (version) => {
expect(agentsToolBridgeRefusal(ON, bot, { ...CLAUDE, version })).toBe("the Claude CLI is too old to leave the person's own settings out");
});
it.each([[undefined], [null], [""], ["Claude Code"], ["wrapper v2"]])("is not bridged when the harness does not know the CLI's version: %j", (version) => {
expect(agentsToolBridgeRefusal(ON, bot, { ...CLAUDE, version })).toBe("the Claude CLI's version is not known");
});
it("is bridged from the first CLI that takes --setting-sources", () => {
for (const version of ["1.0.122 (Claude Code)", "1.0.122", "2.1.232 (Claude Code)", "3.0.0"]) expect(agentsToolBridgeEnv(ON, bot, { ...CLAUDE, version }), version).toEqual(on);
});
it("reads none of this for another engine", () => {
const cwd = inFolder("settings.json", { permissions: { deny: ["mcp__agents__vm_exec"] } }).cwd;
for (const driverKind of ["openai-compat", "mistral"]) expect(agentsToolBridgeEnv(ON, bot, { driverKind, cwd }), driverKind).toEqual(on);
});
});
it("knows where a machine's managed Claude policy lives, on each platform", () => {
expect(claudeManagedPolicyFiles("darwin")).toEqual(["/Library/Application Support/ClaudeCode/managed-settings.json"]);
expect(claudeManagedPolicyFiles("linux")).toEqual(["/etc/claude-code/managed-settings.json"]);
expect(claudeManagedPolicyFiles("win32")).toEqual(["C:\\Program Files\\ClaudeCode\\managed-settings.json", "C:\\ProgramData\\ClaudeCode\\managed-settings.json"]);
expect(claudeManagedPolicyFiles("freebsd")).toEqual(claudeManagedPolicyFiles("linux"));
});
it("describes a turn's engine from what the harness holds: the instance, its config, its last snapshot and the turn's folder", () => {
const configs = { claude: { driver: "claudeAgent", config: { disallowedTools: ["Bash"] }, environment: { OMB_CLAUDE_INHERIT_USER_CONFIG: "0" } } };
expect(turnEngine({ instanceId: "claude", driverKind: "claudeAgent" }, configs, { version: "2.1.232 (Claude Code)" }, "/work/site")).toEqual({
driverKind: "claudeAgent", instanceId: "claude", config: { disallowedTools: ["Bash"] }, environment: { OMB_CLAUDE_INHERIT_USER_CONFIG: "0" }, version: "2.1.232 (Claude Code)", cwd: "/work/site",
});
// nothing configured, never described, no folder of its own
expect(turnEngine({ instanceId: "pi", driverKind: "piAgent" }, {}, undefined, undefined)).toEqual({ driverKind: "piAgent", instanceId: "pi" });
});
});
describe("a bot on a local model gets the bridge without the switch", () => {
const onOllama = { modelSelection: { model: "ollama::qwen3.5:4b" } };
const onServer = (url: string) => ({ driverKind: "openai-compat", config: { url } });
it.each(["http://127.0.0.1:1234/v1", "http://192.168.1.20:8080/v1", "http://mac-studio.local:11434/v1"])("on a server at %s", (url) => {
expect(agentsToolBridgeRefusal({}, {}, onServer(url), {})).toBeNull();
});
it("picked from a local server, on the chat engine", () => {
expect(agentsToolBridgeRefusal({}, onOllama, onServer("https://openrouter.ai/api/v1"), {})).toBeNull();
});
it("not on a hosted model", () => {
expect(agentsToolBridgeRefusal({}, { modelSelection: { model: "gpt-5" } }, onServer("https://openrouter.ai/api/v1"), {})).toBe("the switch is off");
expect(agentsToolBridgeRefusal({}, {}, { driverKind: "grok", config: { url: "http://127.0.0.1:1/v1" } }, {})).toBe("the switch is off");
});
it("not when the operator's variable says no", () => {
expect(agentsToolBridgeRefusal({}, onOllama, onServer("http://127.0.0.1:1234/v1"), { OMB_AGENTS_TOOL_BRIDGE: "0" })).toBe("the switch is off");
});
it("not past the rules everyone else keeps: a tool selection, or an engine that judges tools by name", () => {
expect(agentsToolBridgeRefusal({}, { ...onOllama, toolScope: { allow: ["native:read"] } }, onServer("http://127.0.0.1:1234/v1"), {})).toBe("the bot has a tool selection");
expect(agentsToolBridgeRefusal({}, onOllama, { driverKind: "codex" }, {})).toContain("Codex");
expect(agentsToolBridgeRefusal({}, onOllama, { driverKind: "piAgent" }, {})).toContain("Pi");
});
});
describe("which bots a turn is bridged for", () => {
it("writes the switch on every turn, so a value inherited from the server's own environment cannot stand in", () => {
expect(agentsToolBridgeEnv({}, bot, CLAUDE, {})).toEqual(off);
expect(agentsToolBridgeEnv(ON, bot, CLAUDE, {})).toEqual(on);
expect(agentsToolBridgeEnv({}, bot, CLAUDE, { OMB_AGENTS_TOOL_BRIDGE: "1" })).toEqual(on);
expect(agentsToolBridgeEnv(ON, bot, CLAUDE, { OMB_AGENTS_TOOL_BRIDGE: "0" })).toEqual(off);
expect(Object.keys(agentsToolBridgeEnv(ON, bot, CLAUDE, {}))).toEqual([TOOL_BRIDGE_ENV]);
expect(agentsToolBridgeRefusal({}, bot, CLAUDE, {})).toBe("the switch is off");
});
it("starts every agents proxy unbridged until a turn's decision says otherwise", () => {
// what server/index.ts spreads into each agents integration it builds
expect(agentsProxyLaunch()).toEqual({ command: process.execPath, args: [SPAWNED_PROXIES.agents] });
expect(AGENTS_PROXY_ENV).toEqual({ ELECTRON_RUN_AS_NODE: "1", OMB_AGENTS_TOOL_BRIDGE: "0" });
});
it("gives back the integration a turn mounts with the decision in it, over whatever it carried, and leaves the rest alone", () => {
const agents = { command: "node", args: ["agents-proxy.js"], env: { OMB_BOT_ID: "bot-1", OMB_AGENTS_TOOL_BRIDGE: "0", OMB_COMMS_TOKEN: "t" } };
const mounted = bridgedAgents("thread-mount", agents, ON, bot, CLAUDE, {}, HOST);
expect(mounted).toEqual({ command: "node", args: ["agents-proxy.js"], env: { OMB_BOT_ID: "bot-1", OMB_AGENTS_TOOL_BRIDGE: "1", OMB_COMMS_TOKEN: "t" } });
// a new value for the turn to assign: what was handed in is not rewritten
expect(agents.env.OMB_AGENTS_TOOL_BRIDGE).toBe("0");
const asked = { ...agents, env: { ...agents.env, OMB_AGENTS_TOOL_BRIDGE: "1" } };
expect(bridgedAgents("thread-mount-acp", asked, ON, bot, { driverKind: "grokAgent" }, {}, HOST)!.env.OMB_AGENTS_TOOL_BRIDGE).toBe("0");
// a turn that mounts no agents tools has nothing to decide
expect(bridgedAgents("thread-mount-none", undefined, ON, bot, CLAUDE, {}, HOST)).toBeUndefined();
expect(agentsToolBridged(mounted)).toBe(true);
expect(agentsToolBridged(agents)).toBe(false);
// no agents tools mounted, or an integration that says nothing: no bridge
for (const none of [undefined, {}, { env: {} }, { env: { OMB_AGENTS_TOOL_BRIDGE: "true" } }]) expect(agentsToolBridged(none)).toBe(false);
});
it("writes the decision into the integrations a turn mounts, and gives a turn with no agents tools no agents key at all", () => {
// what server/index.ts does once per turn, before the prompt is built
const integrations: { agents?: { command: string; args: string[]; env: Record<string, string> }; composio?: { env: Record<string, string> } } = { composio: { env: { KEY: "k" } } };
applyAgentsBridge("thread-apply-none", integrations, ON, bot, CLAUDE, {}, HOST);
expect(Object.hasOwn(integrations, "agents")).toBe(false);
expect(integrations).toEqual({ composio: { env: { KEY: "k" } } });
const mounted = { agents: { command: "node", args: ["agents-proxy.js"], env: { OMB_BOT_ID: "bot-1", OMB_AGENTS_TOOL_BRIDGE: "0" } } };
applyAgentsBridge("thread-apply", mounted, ON, bot, CLAUDE, {}, HOST);
expect(mounted.agents).toEqual({ command: "node", args: ["agents-proxy.js"], env: { OMB_BOT_ID: "bot-1", OMB_AGENTS_TOOL_BRIDGE: "1" } });
expect(agentsToolBridged(mounted.agents)).toBe(true);
expect(bridgedToolTitle("thread-apply", "use_tool", { name: "list_triggers" })).toBe("list_triggers");
// the next turn of that thread mounts no agents tools: its mark goes
applyAgentsBridge("thread-apply", {}, ON, bot, CLAUDE, {}, HOST);
expect(bridgedToolTitle("thread-apply", "use_tool", { name: "list_triggers" })).toBe("use_tool");
// a bot or an engine that is turned down is written in as "0"
const turnedDown = { agents: { command: "node", args: [] as string[], env: { OMB_AGENTS_TOOL_BRIDGE: "1" } } };
applyAgentsBridge("thread-apply-acp", turnedDown, ON, bot, { driverKind: "grokAgent" }, {}, HOST);
expect(turnedDown.agents.env).toEqual({ OMB_AGENTS_TOOL_BRIDGE: "0" });
});
it("stays off for a bot with a tool selection, whose gates read the tool's own name", () => {
// mcp-gate.ts, the chat runtime and the Pi extension all refuse an
// excluded tool by the name in the call. Behind use_tool that name is
// use_tool, so a denied vm_exec would run. Any selection at all mounts
// those gates, even one that permits everything.
for (const toolScope of [
{ deny: ["mcp:agents:vm_exec"] },
{ allow: ["native:*", "mcp:agents:list_bots"] },
{ allow: ["mcp:agents:*"] },
{ allow: [] },
{},
]) {
expect(agentsToolBridgeRefusal(ON, { ...bot, toolScope }, CLAUDE, {}), JSON.stringify(toolScope)).toBe("the bot has a tool selection");
expect(agentsToolBridgeEnv(ON, { ...bot, toolScope }, CLAUDE, {}), JSON.stringify(toolScope)).toEqual(off);
expect(agentsToolBridgeEnv({}, { ...bot, toolScope }, CLAUDE, { OMB_AGENTS_TOOL_BRIDGE: "1" }), JSON.stringify(toolScope)).toEqual(off);
}
// a stored selection that cannot be read refuses the turn elsewhere; never on here
for (const toolScope of [false, [], "all", { allow: "all" }, { unrecognized: true }, null]) {
expect(agentsToolBridgeEnv(ON, { ...bot, toolScope }, CLAUDE, {}), JSON.stringify(toolScope)).toEqual(off);
}
expect(agentsToolBridgeEnv(ON, { ...bot, toolScope: undefined }, CLAUDE, {})).toEqual(on);
});
it("stays off when there is no such bot", () => {
// null is what the store answers for a bot that is gone
expect(agentsToolBridgeEnv(ON, null, CLAUDE, {})).toEqual(off);
expect(agentsToolBridgeEnv(ON, undefined, CLAUDE, {})).toEqual(off);
});
});
describe("the decision cannot fail a turn", () => {
const FAILED = "the decision failed";
const agents = () => ({ command: "node", args: [] as string[], env: { OMB_BOT_ID: "bot-1" } as Record<string, string> });
/** A machine whose home cannot be asked for without an error that carries a path. */
const throwingHost = (message: string): EngineHost => ({ get home(): string { throw new Error(message); }, managedPolicyFiles: [] });
const quiet = () => vi.spyOn(console, "warn").mockImplementation(() => {});
it("answers no bridge when something it reads throws: the integration exactly as the switch off gives it, and one line that says so", () => {
const warn = quiet();
try {
const host = throwingHost("EACCES: permission denied, open '/Users/someone/.claude/settings.json'");
const engine = { ...CLAUDE, cwd: undefined };
expect(agentsToolBridgeRefusal(ON, bot, engine, {}, host)).toBe(FAILED);
expect(agentsToolBridgeEnv(ON, bot, engine, {}, host)).toEqual(off);
const asked = { ...agents(), env: { ...agents().env, OMB_AGENTS_TOOL_BRIDGE: "1" } };
expect(bridgedAgents("thread-throws", asked, ON, bot, engine, {}, host)).toEqual(bridgedAgents("thread-off-for-comparison", asked, {}, bot, CLAUDE, {}, HOST));
expect(bridgedToolTitle("thread-throws", "mcp__agents__use_tool", { name: "list_triggers" })).toBe("mcp__agents__use_tool");
const integrations = { agents: asked };
applyAgentsBridge("thread-throws-apply", integrations, ON, bot, engine, {}, host);
expect(integrations.agents.env.OMB_AGENTS_TOOL_BRIDGE).toBe("0");
// one line each time, naming the class of what was thrown and nothing it carried
expect(warn).toHaveBeenCalledTimes(4);
for (const [line] of warn.mock.calls) {
expect(line).toMatch(/^\[agents\] .*tool bridge.*\(Error\)/);
expect(line).not.toContain("/Users");
expect(line).not.toContain("permission denied");
expect(line).not.toContain("settings.json");
}
// and the next turn, with what it reads in order, is bridged again
expect(agentsToolBridgeEnv(ON, bot, CLAUDE, {})).toEqual(on);
expect(warn).toHaveBeenCalledTimes(4);
} finally {
warn.mockRestore();
}
});
it("names the class of whatever was thrown, from wherever it was thrown", () => {
const warn = quiet();
try {
const last = () => String(warn.mock.calls.at(-1)?.[0]);
// the bot's record
class VaultLocked extends Error { override name = "VaultLocked"; }
const lockedBot = { get toolScope(): unknown { throw new VaultLocked("token=abc123"); } };
expect(agentsToolBridgeEnv(ON, lockedBot, CLAUDE, {})).toEqual(off);
expect(last()).toContain("(VaultLocked)");
expect(last()).not.toContain("abc123");
// the engine's config
const oddEngine = { ...CLAUDE, get config(): unknown { throw new TypeError("config has {\"disallowedTools\": [\"mcp__agents__vm_exec\"]}"); } };
expect(agentsToolBridgeEnv(ON, bot, oddEngine, {})).toEqual(off);
expect(last()).toContain("(TypeError)");
expect(last()).not.toContain("vm_exec");
// a file system error keeps its code, which names no path
const denied = Object.assign(new Error("EACCES: permission denied, open '/etc/claude-code/managed-settings.json'"), { code: "EACCES" });
const deniedHost: EngineHost = { home: folder(), get managedPolicyFiles(): readonly string[] { throw denied; } };
expect(agentsToolBridgeRefusal(ON, bot, CLAUDE, {}, deniedHost)).toBe(FAILED);
expect(last()).toContain("(Error EACCES)");
expect(last()).not.toContain("/etc");
// something thrown that is not an error at all
const rude = { ...CLAUDE, get environment(): Record<string, string> | undefined { throw 42; } };
expect(agentsToolBridgeEnv(ON, bot, rude, {})).toEqual(off);
expect(last()).toContain("(number)");
// a name or code that is not a short plain word is not printed as given
const loud = Object.assign(new Error("x"), { name: "Boom: /Users/someone/.claude\nsettings", code: { path: "/etc/claude-code" } });
expect(agentsToolBridgeEnv(ON, bot, { ...CLAUDE, get config(): unknown { throw loud; } }, {})).toEqual(off);
expect(last()).toContain("(unknown unknown)");
expect(last()).not.toContain("/Users");
expect(last()).not.toContain("/etc");
expect(last()).not.toMatch(/\n/);
const long = Object.assign(new Error("x"), { name: "E".repeat(65), code: "ENOENT" });
expect(agentsToolBridgeEnv(ON, bot, { ...CLAUDE, get config(): unknown { throw long; } }, {})).toEqual(off);
expect(last()).toContain("(unknown ENOENT)");
// and reading them cannot throw out of the handler
const hostile = new Proxy(new Error("x"), { get(target, key) { if (key === "name" || key === "code") throw new Error("/Users/someone"); return Reflect.get(target, key); } });
expect(agentsToolBridgeEnv(ON, bot, { ...CLAUDE, get config(): unknown { throw hostile; } }, {})).toEqual(off);
expect(last()).toContain("(unknown unknown)");
expect(last()).not.toContain("/Users");
// nor can asking what kind of value it is (a revoked proxy throws on instanceof)
const { proxy: revoked, revoke } = Proxy.revocable(new Error("x"), {});
revoke();
expect(agentsToolBridgeEnv(ON, bot, { ...CLAUDE, get config(): unknown { throw revoked; } }, {})).toEqual(off);
expect(last()).toContain("(unknown unknown)");
} finally {
warn.mockRestore();
}
});
});
describe("the name a bridged call is given", () => {
const agents = () => ({ command: "node", args: [] as string[], env: { OMB_BOT_ID: "bot-1" } as Record<string, string> });
it("is the tool it ran on a turn that was bridged, for any tool of today's catalog, and its own for anything else", () => {
bridgedAgents("thread-bridged", agents(), ON, bot, CLAUDE, {}, HOST);
for (const tool of ["list_triggers", "vm_exec", "shared_computer", "create_bot", "create_options_card", "memory_log", "post_to_room"]) {
expect(bridgedToolTitle("thread-bridged", "mcp__agents__use_tool", JSON.stringify({ name: tool, arguments: {} }, null, 2)), tool).toBe(`mcp__agents__${tool}`);
expect(bridgedToolTitle("thread-bridged", "agents_use_tool", { name: tool }), tool).toBe(`agents_${tool}`);
expect(bridgedToolTitle("thread-bridged", "use_tool", { name: tool }), tool).toBe(tool);
}
expect(bridgedToolTitle("thread-bridged", "mcp__agents__use_tool", { name: "made_up" })).toBe("mcp__agents__use_tool");
expect(bridgedToolTitle("thread-bridged", "mcp__agents__use_tool", { name: FIND_TOOL })).toBe("mcp__agents__use_tool");
expect(bridgedToolTitle("thread-bridged", "mcp__agents__use_tool", undefined)).toBe("mcp__agents__use_tool");
expect(bridgedToolTitle("thread-bridged", "mcp__agents__list_triggers", {})).toBe("mcp__agents__list_triggers");
expect(bridgedToolTitle("thread-bridged", "Bash", { name: "vm_exec" })).toBe("Bash");
});
it("leaves a use_tool row alone on a turn that was not bridged: it is some other server's tool of that name", () => {
// Grok has a native use_tool, and Codex reports any server's tool by its
// bare name. Neither turn started a bridged agents server.
const call = { name: "list_triggers", arguments: {} };
// never decided, switched off, an engine that is not bridged, a bot with a selection
bridgedAgents("thread-off", agents(), {}, bot, CLAUDE, {}, HOST);
bridgedAgents("thread-acp", agents(), ON, bot, { driverKind: "grokAgent" }, {}, HOST);
bridgedAgents("thread-selected", agents(), ON, { toolScope: { deny: ["mcp:agents:vm_exec"] } }, CLAUDE, {}, HOST);
for (const threadId of ["thread-never", "thread-off", "thread-acp", "thread-selected"]) {
for (const title of ["use_tool", "mcp__agents__use_tool", "agents_use_tool"]) expect(bridgedToolTitle(threadId, title, call), `${threadId} ${title}`).toBe(title);
}
});
it("follows the thread's latest turn: bridged, then not, then bridged again", () => {
const call = { name: "list_triggers" };
bridgedAgents("thread-moves", agents(), ON, bot, CLAUDE, {}, HOST);
expect(bridgedToolTitle("thread-moves", "use_tool", call)).toBe("list_triggers");
// its engine was switched to an ACP agent
bridgedAgents("thread-moves", agents(), ON, bot, { driverKind: "qwenAgent" }, {}, HOST);
expect(bridgedToolTitle("thread-moves", "use_tool", call)).toBe("use_tool");
// a turn with no agents tools at all
bridgedAgents("thread-moves", agents(), ON, bot, CLAUDE, {}, HOST);
bridgedAgents("thread-moves", undefined, ON, bot, CLAUDE, {}, HOST);
expect(bridgedToolTitle("thread-moves", "use_tool", call)).toBe("use_tool");
bridgedAgents("thread-moves", agents(), ON, bot, CLAUDE, {}, HOST);
expect(bridgedToolTitle("thread-moves", "use_tool", call)).toBe("list_triggers");
});
});
describe("the record of bridged turns", () => {
const agents = () => ({ command: "node", args: [] as string[], env: {} as Record<string, string> });
const call = { name: "list_triggers" };
it("is bounded: the oldest mark goes first, and all it costs is a row shown as use_tool", () => {
bridgedAgents("thread-oldest", agents(), ON, bot, CLAUDE, {}, HOST);
bridgedAgents("thread-kept-fresh", agents(), ON, bot, CLAUDE, {}, HOST);
for (let n = 0; n < BRIDGED_THREADS_MAX - 2; n++) bridgedAgents(`thread-filler-${n}`, agents(), ON, bot, CLAUDE, {}, HOST);
expect(bridgedToolTitle("thread-oldest", "use_tool", call)).toBe("list_triggers");
// a thread whose turn is decided again is the newest again
bridgedAgents("thread-kept-fresh", agents(), ON, bot, CLAUDE, {}, HOST);
bridgedAgents("thread-one-more", agents(), ON, bot, CLAUDE, {}, HOST);
expect(bridgedToolTitle("thread-oldest", "use_tool", call)).toBe("use_tool");
expect(bridgedToolTitle("thread-kept-fresh", "use_tool", call)).toBe("list_triggers");
expect(bridgedToolTitle("thread-one-more", "use_tool", call)).toBe("list_triggers");
expect(bridgedToolTitle("thread-filler-0", "use_tool", call)).toBe("list_triggers");
});
});
describe("what the prompt builder and the Activity log load", () => {
it("is no engine driver: the rule's text and the question of whether a turn is bridged need none", async () => {
const here = dirname(fileURLToPath(import.meta.url));
const loaded = async (entry: string) => {
const result = await build({ entryPoints: [join(here, entry)], bundle: true, platform: "node", format: "esm", write: false, metafile: true, logLevel: "silent", packages: "external" });
return Object.keys(result.metafile.inputs);
};
for (const entry of ["system-prompt.ts", "activity.ts", "agents-bridge-names.ts", "mcp-gate.ts", "drivers/agents-proxy.ts"]) {
const inputs = await loaded(entry);
const drivers = inputs.filter((input) => /server\/drivers\/(claude|codex|pi|openai-chat|muse-code)\.ts$|server\/drivers\/acp\/|agents-bridge-switch\.ts$/.test(input));
expect(drivers, entry).toEqual([]);
}
// and the switch, which reads the drivers to decide, is where they are loaded from
expect((await loaded("agents-bridge-switch.ts")).some((input) => input.endsWith("server/drivers/claude.ts"))).toBe(true);
}, 60_000);
});
describe("the prompt of a bridged turn", () => {
const parts: PromptPart[] = [
{ id: "routine", label: "Routines", text: " If the user explicitly asks to schedule routines, use list_triggers and create_trigger." },
{ id: "memory", label: "Memory", text: "\nMEMORY: the deploy script lives in scripts/." },
{ id: "credential", label: "Credentials", text: " Use request_credential for a missing key." },
];
const bridged = { env: { OMB_AGENTS_TOOL_BRIDGE: "1", OMB_BOT_ID: "bot-1" } };
it("is unchanged, to the byte, without the bridge", () => {
const plain = buildSystemPrompt("You are Kiwi.", "Be brief.", parts);
expect(buildSystemPrompt("You are Kiwi.", "Be brief.", parts, undefined)).toEqual(plain);
expect(buildSystemPrompt("You are Kiwi.", "Be brief.", parts, { env: { OMB_AGENTS_TOOL_BRIDGE: "0" } })).toEqual(plain);
expect(buildSystemPrompt("You are Kiwi.", "Be brief.", parts, { env: {} })).toEqual(plain);
expect(buildSystemPrompt("You are Kiwi.", "Be brief.", parts, {})).toEqual(plain);
expect(plain.text).not.toContain(FIND_TOOL);
expect(plain.sections.map((section) => section.id)).toEqual(["persona", "soul", "routine", "memory", "credential", "reply-habits"]);
});
it("has the section only when this turn was asked to bridge, once, right after the soul", () => {
const built = buildSystemPrompt("You are Kiwi.", "Be brief.", parts, bridged);
expect(built.sections.map((section) => section.id)).toEqual(["persona", "soul", "tool-bridge", "routine", "memory", "credential", "reply-habits"]);
const section = built.sections.find((entry) => entry.id === "tool-bridge")!;
expect(section.text).toBe(AGENTS_TOOL_BRIDGE_PROMPT);
expect(section.bytes).toBe(Buffer.byteLength(AGENTS_TOOL_BRIDGE_PROMPT, "utf8"));
expect(built.text.split(AGENTS_TOOL_BRIDGE_PROMPT)).toHaveLength(2);
// every other section is exactly what it was
const plain = buildSystemPrompt("You are Kiwi.", "Be brief.", parts);
expect(built.sections.filter((entry) => entry.id !== "tool-bridge")).toEqual(plain.sections);
expect(built.text.replace(AGENTS_TOOL_BRIDGE_PROMPT, "")).toBe(plain.text);
// the decision's own output is what switches it: off for an engine or a bot that is turned down
for (const [agents, has] of [
[{ env: agentsToolBridgeEnv(ON, bot, CLAUDE, {}) }, true],
[{ env: agentsToolBridgeEnv(ON, bot, { driverKind: "grokAgent" }, {}) }, false],
[{ env: agentsToolBridgeEnv(ON, { toolScope: { deny: ["mcp:agents:vm_exec"] } }, CLAUDE, {}) }, false],
[{ env: agentsToolBridgeEnv({}, bot, CLAUDE, {}) }, false],
] as const) {
expect(buildSystemPrompt("You are Kiwi.", "", parts, agents).sections.some((entry) => entry.id === "tool-bridge")).toBe(has);
}
});
it("never names a tool the bridge may have hidden, so it cannot send a bot to call one directly", () => {
const everyTool = availableTools({ skillAuthoring: true, sharedComputers: true, voiceNotes: true, cloudHome: false, chief: true, botId: WATCHER_OPTIONS_CARD_BOT_ID }).map((tool) => tool.name);
expect(everyTool.length).toBeGreaterThan(BRIDGE_HOT_TOOL_NAMES.size);
for (const tool of everyTool) expect(AGENTS_TOOL_BRIDGE_PROMPT, tool).not.toContain(tool);
});
it("reads true when the bridge tools are not in the turn's list after all", () => {
// A tool selection saved after the prompt was built gives the turn an
// unbridged proxy at its mount. So nothing is said about find_tool or
// use_tool except on the condition that they are listed, and what is
// said without that condition is true of every turn.
const sentences = AGENTS_TOOL_BRIDGE_PROMPT.trim().split(/(?<=\.)\s+/);
const aboutTheBridge = sentences.filter((sentence) => sentence.includes(FIND_TOOL) || sentence.includes(USE_TOOL));
expect(aboutTheBridge).toHaveLength(1);
expect(aboutTheBridge[0]!.startsWith(`If ${FIND_TOOL} and ${USE_TOOL} are in your tool list, `)).toBe(true);
expect(sentences.filter((sentence) => !aboutTheBridge.includes(sentence))).toEqual(["A tool that is in your tool list is always called directly."]);
// one sentence-run, joined to its neighbours like every other section
expect(AGENTS_TOOL_BRIDGE_PROMPT.startsWith(" ")).toBe(true);
expect(AGENTS_TOOL_BRIDGE_PROMPT).not.toMatch(/\n/);
});
it("sits in the stable half, so a warm engine is not relaunched by it turn after turn", () => {
const first = buildSystemPrompt("You are Kiwi.", "Be brief.", parts, bridged);
const later = buildSystemPrompt("You are Kiwi.", "Be brief.", parts.map((part) => part.id === "memory" ? { ...part, text: "\nMEMORY: the deploy script moved to tools/." } : part), bridged);
expect(first.stable).toContain(AGENTS_TOOL_BRIDGE_PROMPT);
expect(first.volatile).not.toContain(FIND_TOOL);
expect(later.stable).toBe(first.stable);
expect(later.volatile).not.toBe(first.volatile);
// and it is the same text for every bot and every turn that has it
expect(buildSystemPrompt("You are Pip.", "", [], { env: { OMB_AGENTS_TOOL_BRIDGE: "1", OMB_CHIEF_OF_STAFF: "1" } }).sections.find((entry) => entry.id === "tool-bridge")!.text).toBe(AGENTS_TOOL_BRIDGE_PROMPT);
});
});