From f26f8917f4bd4d621d43abb512cde81177d7a05a Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Thu, 15 Oct 2026 20:07:07 -0700 Subject: [PATCH 1/2] fix(dispatch): advertise approve_worker_call beside send_input The dispatch and orchestrator allow lists name the grant tool next to send_input so a parent can advertise it. send_input stays text-only. --- agents/dispatch/src/toolset.ts | 1 + src/agent/directors/tool-sets.test.ts | 9 +++++++++ src/agent/directors/tool-sets.ts | 1 + src/agent/prompt-sizes.test.ts | 6 +++++- src/agent/prompt-sizes.ts | 1 + 5 files changed, 17 insertions(+), 1 deletion(-) diff --git a/agents/dispatch/src/toolset.ts b/agents/dispatch/src/toolset.ts index f273a330e..7ec80a64a 100644 --- a/agents/dispatch/src/toolset.ts +++ b/agents/dispatch/src/toolset.ts @@ -21,6 +21,7 @@ export const tools = [ "resume_agent", "interrupt_agent", "send_input", + "approve_worker_call", "read_agent_trace", "search_agents", ] as const; diff --git a/src/agent/directors/tool-sets.test.ts b/src/agent/directors/tool-sets.test.ts index 156704451..f8010b542 100644 --- a/src/agent/directors/tool-sets.test.ts +++ b/src/agent/directors/tool-sets.test.ts @@ -92,6 +92,15 @@ describe("DISPATCH_TOOLS / ORCHESTRATOR_TOOLS", () => { ); }); + test("approve_worker_call sits beside send_input on both dispatch surfaces", () => { + for (const surface of [DISPATCH_TOOLS, ORCHESTRATOR_TOOLS] as const) { + const names = surface as readonly string[]; + const send = names.indexOf("send_input"); + expect(send).toBeGreaterThanOrEqual(0); + expect(names[send + 1]).toBe("approve_worker_call"); + } + }); + test("skill_search + use_skill mount on every worker surface, never ask_operator", () => { expect([...SKILL_TOOLS]).toEqual(["skill_search", "use_skill"]); for (const surface of [ diff --git a/src/agent/directors/tool-sets.ts b/src/agent/directors/tool-sets.ts index abe9fc31d..0189c8457 100644 --- a/src/agent/directors/tool-sets.ts +++ b/src/agent/directors/tool-sets.ts @@ -65,6 +65,7 @@ export const ORCHESTRATOR_TOOLS = [ "resume_agent", "interrupt_agent", "send_input", + "approve_worker_call", "read_agent_trace", ] as const; diff --git a/src/agent/prompt-sizes.test.ts b/src/agent/prompt-sizes.test.ts index 75c82386f..97d92c114 100644 --- a/src/agent/prompt-sizes.test.ts +++ b/src/agent/prompt-sizes.test.ts @@ -233,7 +233,11 @@ describe("director prompt size budget", () => { } } if (DIRECTOR_REGISTRY[directorId].spawn.maySpawn) { - for (const verb of ["spawn_agent", "send_input"]) { + for (const verb of [ + "spawn_agent", + "send_input", + "approve_worker_call", + ]) { expect( names.filter((n) => n === verb).length, `${directorId} [${family}] mounts ${verb} once`, diff --git a/src/agent/prompt-sizes.ts b/src/agent/prompt-sizes.ts index fe505b83a..a762d1849 100644 --- a/src/agent/prompt-sizes.ts +++ b/src/agent/prompt-sizes.ts @@ -126,6 +126,7 @@ export function canonicalToolNamesForDirector( "resume_agent", "interrupt_agent", "send_input", + "approve_worker_call", ); } const dupe = names.find((name, index) => names.indexOf(name) !== index); From 0acd31510917e4badd38145bf27ab5c90f42c120 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Thu, 15 Oct 2026 20:17:06 -0700 Subject: [PATCH 2/2] fix(dispatch): advertise approve_worker_call on the parent wire CORE_TOOL_NAMES stopped at send_input, so the orchestrator prefix and the dispatch call gate never listed the grant tool. send_input stays text-only. --- src/agent/tool-search.ts | 2 ++ src/exec/runner.test.ts | 62 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/src/agent/tool-search.ts b/src/agent/tool-search.ts index c9486db94..8b0d19295 100644 --- a/src/agent/tool-search.ts +++ b/src/agent/tool-search.ts @@ -51,6 +51,7 @@ export const CORE_TOOL_NAMES: readonly string[] = [ "resume_agent", "interrupt_agent", "send_input", + "approve_worker_call", ]; const ORCHESTRATOR_ONLY_TOOL_NAMES: readonly string[] = [ @@ -62,6 +63,7 @@ const ORCHESTRATOR_ONLY_TOOL_NAMES: readonly string[] = [ "resume_agent", "interrupt_agent", "send_input", + "approve_worker_call", ]; // Session-start facts gating a core tool's advertisement, fixed for the diff --git a/src/exec/runner.test.ts b/src/exec/runner.test.ts index de85c49d1..23701272a 100644 --- a/src/exec/runner.test.ts +++ b/src/exec/runner.test.ts @@ -750,6 +750,68 @@ describe("exec advertised tools vs TUI", () => { }); expect(isAdvertised("ask_operator")).toBe(true); }); + + test("parent prefix and dispatch call gate list approve_worker_call after send_input", () => { + const stub = (name: string) => ({ + name, + description: name, + inputSchema: { type: "object" as const, properties: {} }, + }); + const expectBesideSendInput = (names: readonly string[]) => { + const send = names.indexOf("send_input"); + expect(send).toBeGreaterThanOrEqual(0); + expect(names[send + 1]).toBe("approve_worker_call"); + }; + const registry = [ + stub("read_file"), + stub("approve_worker_call"), + stub("send_input"), + ]; + for (const toolAvailability of [ + { + languageServerAvailable: false, + operatorAvailable: true, + waitAgentsMounted: false, + }, + { + languageServerAvailable: false, + operatorAvailable: false, + waitAgentsMounted: true, + }, + ] as const) { + expectBesideSendInput( + advertisedToolNamesForSessionMode(sessionMode, toolAvailability), + ); + const tui = createAdvertisedToolset({ + sessionMode, + toolAvailability, + getProvider: () => ({ providerName: "test", model: "test" }), + }); + expectBesideSendInput( + tui.computeAdvertised(registry).map((def) => def.name), + ); + expect( + createExecToolCallGate(tui.isAdvertised)("approve_worker_call"), + ).toBe(true); + } + const overlay = resolveExecDirectorOverlay("dispatch"); + const exec = createAdvertisedToolset({ + sessionMode, + toolAvailability: { + languageServerAvailable: false, + operatorAvailable: false, + waitAgentsMounted: true, + }, + getProvider: () => ({ providerName: "test", model: "test" }), + builtInPrefix: overlay.advertisedAllow, + }); + expectBesideSendInput( + exec.computeAdvertised(registry).map((def) => def.name), + ); + expect( + createExecToolCallGate(exec.isAdvertised)("approve_worker_call"), + ).toBe(true); + }); }); describe("exec tool call gate and promoter", () => {