From 9c89ec9db22d0a7226c36b62640addc00918029b Mon Sep 17 00:00:00 2001 From: Adam Malczewski Date: Tue, 2 Jun 2026 15:30:42 +0900 Subject: fix(tabs): advertise send_to_tab/read_tab in the agent system prompt Granted tab-messaging tools were registered in the API tool payload but buildSystemPrompt built its 'You have access to the following tools' list by filtering toolNames through TOOL_DESCRIPTIONS, which had no entries for send_to_tab/read_tab. The model was therefore told it lacked those tools and refused to use them even when explicitly granted. Add the two missing TOOL_DESCRIPTIONS entries so the capability list matches the granted toolset. Add regression tests that capture the constructed Agent's systemPrompt and assert the tab-messaging tools are listed when granted (and omitted when not), locking the prompt list to the schema list so they can't drift again. --- packages/api/src/agent-manager.ts | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'packages/api/src') diff --git a/packages/api/src/agent-manager.ts b/packages/api/src/agent-manager.ts index 85dd160..36a26f8 100644 --- a/packages/api/src/agent-manager.ts +++ b/packages/api/src/agent-manager.ts @@ -83,6 +83,10 @@ const TOOL_DESCRIPTIONS: Record = { web_search: "Search the web and optionally scrape full page content from results.", youtube_transcribe: "Fetch the transcript/subtitles for a YouTube video. Set background=true to start in the background and get a job_id for later retrieval.", + send_to_tab: + "Send a message to another tab (agent) by its short ID, as shown in the tab bar. Fire-and-forget: it queues/wakes the target and returns immediately without waiting for a reply. Use read_tab later to read the target's response.", + read_tab: + "Read another tab (agent)'s most recent completed response by its short ID. Returns a non-blocking snapshot; if the target is still running you get its previous completed turn. Use after send_to_tab to collect a reply.", }; /** -- cgit v1.2.3 From e475e527cd768dc05368a0881a07a84ea140e13e Mon Sep 17 00:00:00 2001 From: Adam Malczewski Date: Tue, 2 Jun 2026 15:42:00 +0900 Subject: fix(tabs): clearer send_to_tab context to stop busy-wait + wrong-recipient replies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two behavioral problems observed once the tools were usable: 1. The SENDER busy-waited for a reply (ran 'sleep 20' / polled) instead of ending its turn. Tool description, the delivery result text, and the system-prompt one-liner now say plainly: do not sleep/poll/run commands to wait; a reply arrives on its own in a later turn (or via read_tab in a future turn); keep working if there's other work, else end your turn. 2. The RECIPIENT replied to its OWN user in plain text instead of routing the answer back through send_to_tab. The provenance wrapper now states the message is from another AGENT (not your user), and that to reply you must use send_to_tab addressed to the sender's handle — and only if asked, since it may just be context. A plain text answer reaches only your own user. Tests updated for the new wording. --- packages/api/src/agent-manager.ts | 2 +- packages/core/src/tools/send-to-tab.ts | 32 ++++++++++++++++++++++----- packages/core/tests/tools/send-to-tab.test.ts | 16 ++++++++++++-- 3 files changed, 42 insertions(+), 8 deletions(-) (limited to 'packages/api/src') diff --git a/packages/api/src/agent-manager.ts b/packages/api/src/agent-manager.ts index 36a26f8..4264884 100644 --- a/packages/api/src/agent-manager.ts +++ b/packages/api/src/agent-manager.ts @@ -84,7 +84,7 @@ const TOOL_DESCRIPTIONS: Record = { youtube_transcribe: "Fetch the transcript/subtitles for a YouTube video. Set background=true to start in the background and get a job_id for later retrieval.", send_to_tab: - "Send a message to another tab (agent) by its short ID, as shown in the tab bar. Fire-and-forget: it queues/wakes the target and returns immediately without waiting for a reply. Use read_tab later to read the target's response.", + "Send a message to another tab (agent) by its short ID, as shown in the tab bar. Fire-and-forget: it queues/wakes the target and returns immediately without waiting for a reply. Do NOT sleep, poll, or run commands to wait — a reply arrives on its own in a later turn (or use read_tab in a future turn); if you are only waiting, end your turn.", read_tab: "Read another tab (agent)'s most recent completed response by its short ID. Returns a non-blocking snapshot; if the target is still running you get its previous completed turn. Use after send_to_tab to collect a reply.", }; diff --git a/packages/core/src/tools/send-to-tab.ts b/packages/core/src/tools/send-to-tab.ts index eb86b7e..84e5f25 100644 --- a/packages/core/src/tools/send-to-tab.ts +++ b/packages/core/src/tools/send-to-tab.ts @@ -64,9 +64,14 @@ export function createSendToTabTool(callbacks: SendToTabCallbacks): ToolDefiniti " - If the target tab is idle, your message WAKES it and starts a new turn.", "", "This is fire-and-forget: it returns immediately and does NOT wait for a reply.", - "Use the 'read_tab' tool with the same ID later to read the target's latest response.", + "Do NOT sleep, poll, or run shell commands to wait for a reply — that wastes turns and", + "money. If the target replies it arrives on its own as a new message in a later turn; you", + "can also call 'read_tab' with the same ID in a FUTURE turn to check. If you have other", + "work to do, keep going; if you are ONLY waiting for the reply, end your turn now.", "", - "Your tab ID is auto-added to the top of the message so the recipient can reply to you.", + "Your tab ID is auto-added to the top of the message so the recipient knows who to reply", + "to. The recipient must use this same 'send_to_tab' tool (addressed to your ID) to answer;", + "a plain text response reaches only their own user, not you.", "IDs are git-style prefixes: pass any length that uniquely identifies the target (min 4 chars).", "If the ID is ambiguous you'll be asked to add a character.", ].join("\n"), @@ -117,8 +122,18 @@ export function createSendToTabTool(callbacks: SendToTabCallbacks): ToolDefiniti } // Stamp provenance so the recipient (and the watching user) can see - // which tab the message came from and reply back via its handle. - const delivered = `[message from tab ${callbacks.self.handle}]\n\n${message}`; + // which tab the message came from and how to reply. The header makes + // clear this is a PEER AGENT, not the recipient's own user, and the + // footer states the reply contract: a reply (only if warranted) must + // go back through `send_to_tab`, since a plain text answer reaches + // only the recipient's own user — not this sender. + const delivered = [ + `[message from tab ${callbacks.self.handle} — this is another agent, NOT your user]`, + "", + message, + "", + `[To reply to tab ${callbacks.self.handle}, use the send_to_tab tool with tab_id "${callbacks.self.handle}". Only reply if this message asks you to, or your user tells you to — it may just be context or instructions. A plain text response goes to your own user, not to this agent.]`, + ].join("\n"); try { const result = await callbacks.deliver(target.id, delivered); @@ -138,7 +153,14 @@ export function createSendToTabTool(callbacks: SendToTabCallbacks): ToolDefiniti result.status === "queued" ? "queued (target is busy; it will be picked up next turn)" : "delivered (target was idle; a new turn has started)"; - return `Message ${verb}. Target tab: ${target.handle} (${target.title}). Use read_tab with "${target.handle}" to read its reply later.`; + return [ + `Message ${verb}. Target tab: ${target.handle} (${target.title}).`, + "", + "Do NOT sleep, poll, or run commands to wait for a reply. If the target replies it", + `arrives on its own as a new message later; you can also call read_tab with "${target.handle}"`, + "in a FUTURE turn to check. Keep working if you have other tasks; if you are ONLY", + "waiting for this reply, end your turn now.", + ].join("\n"); } catch (err) { return `Error delivering message: ${err instanceof Error ? err.message : String(err)}`; } diff --git a/packages/core/tests/tools/send-to-tab.test.ts b/packages/core/tests/tools/send-to-tab.test.ts index 4450fc5..68f8fa0 100644 --- a/packages/core/tests/tools/send-to-tab.test.ts +++ b/packages/core/tests/tools/send-to-tab.test.ts @@ -24,6 +24,9 @@ describe("createSendToTabTool — schema & description", () => { expect(tool.name).toBe("send_to_tab"); expect(tool.description).toContain("fire-and-forget"); expect(tool.description.toLowerCase()).toContain("queued"); + // Description must steer the model away from busy-waiting for a reply. + expect(tool.description.toLowerCase()).toContain("do not sleep"); + expect(tool.description.toLowerCase()).toContain("end your turn"); }); }); @@ -35,11 +38,20 @@ describe("createSendToTabTool — execute()", () => { expect(deliver).toHaveBeenCalledTimes(1); const [targetId, delivered] = deliver.mock.calls[0] ?? []; expect(targetId).toBe("target-id"); - // Provenance prefix names the sending tab's handle. - expect(delivered).toContain("[message from tab self]"); + // Provenance header names the sending tab's handle and marks it as a + // peer agent (not the recipient's own user). + expect(delivered).toContain("[message from tab self"); + expect(delivered).toContain("another agent"); expect(delivered).toContain("hello there"); + // Reply contract: the recipient must answer via send_to_tab back to the + // sender's handle, not as a plain text reply to its own user. + expect(delivered).toContain('send_to_tab tool with tab_id "self"'); + expect(delivered.toLowerCase()).toContain("only reply if"); expect(out).toContain("idle"); expect(out).toContain("targ"); + // Sender is steered away from busy-waiting and told to end its turn. + expect(out.toLowerCase()).toContain("do not sleep"); + expect(out.toLowerCase()).toContain("end your turn"); }); it("reports the queued status when the target is busy", async () => { -- cgit v1.2.3 From aa295e82197ebc77d9466eee28380bc5bcc0863d Mon Sep 17 00:00:00 2001 From: Adam Malczewski Date: Tue, 2 Jun 2026 15:53:15 +0900 Subject: fix(tabs): only mention read_tab when the sender actually has it; CAPS on ONLY MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The send_to_tab guidance previously told the agent it could call read_tab to check for a reply, but the tab-messaging permissions are split — a tab can hold send_to_tab WITHOUT read_tab (the exact case in testing). Advertising a tool the agent wasn't granted is wrong. Thread a canReadTab flag from AgentManager.buildTabCommToolEntries into createSendToTabTool (true iff this tab is also granted read_tab). The tool description and the delivery-result text now only reference read_tab when canReadTab is true; otherwise they say a reply arrives on its own and to end the turn. Drop the read_tab phrasing from the static TOOL_DESCRIPTIONS one-liner (can't be conditional per-tab there). Also uppercase ONLY in the recipient reply-contract footer for emphasis. Tests: cover both canReadTab branches for description + result text; assert ONLY is uppercased. --- packages/api/src/agent-manager.ts | 13 ++++++-- packages/core/src/tools/send-to-tab.ts | 45 ++++++++++++++++++++++----- packages/core/tests/tools/send-to-tab.test.ts | 33 +++++++++++++++++++- 3 files changed, 79 insertions(+), 12 deletions(-) (limited to 'packages/api/src') diff --git a/packages/api/src/agent-manager.ts b/packages/api/src/agent-manager.ts index 4264884..3d233fc 100644 --- a/packages/api/src/agent-manager.ts +++ b/packages/api/src/agent-manager.ts @@ -84,7 +84,7 @@ const TOOL_DESCRIPTIONS: Record = { youtube_transcribe: "Fetch the transcript/subtitles for a YouTube video. Set background=true to start in the background and get a job_id for later retrieval.", send_to_tab: - "Send a message to another tab (agent) by its short ID, as shown in the tab bar. Fire-and-forget: it queues/wakes the target and returns immediately without waiting for a reply. Do NOT sleep, poll, or run commands to wait — a reply arrives on its own in a later turn (or use read_tab in a future turn); if you are only waiting, end your turn.", + "Send a message to another tab (agent) by its short ID, as shown in the tab bar. Fire-and-forget: it queues/wakes the target and returns immediately without waiting for a reply. Do NOT sleep, poll, or run commands to wait — a reply arrives on its own in a later turn; if you are only waiting, end your turn.", read_tab: "Read another tab (agent)'s most recent completed response by its short ID. Returns a non-blocking snapshot; if the target is still running you get its previous completed turn. Use after send_to_tab to collect a reply.", }; @@ -546,7 +546,7 @@ export class AgentManager { } // Tab-to-tab communication — gated on the child whitelist. if (allowed.has("send_to_tab") || allowed.has("read_tab")) { - for (const entry of this.buildTabCommToolEntries(tabId)) { + for (const entry of this.buildTabCommToolEntries(tabId, allowed.has("read_tab"))) { if (allowed.has(entry.name)) toolEntries.push(entry); } } @@ -631,7 +631,7 @@ export class AgentManager { const tabCommAllowed = new Set(); if (permSendToTab) tabCommAllowed.add("send_to_tab"); if (permReadTab) tabCommAllowed.add("read_tab"); - for (const entry of this.buildTabCommToolEntries(tabId)) { + for (const entry of this.buildTabCommToolEntries(tabId, permReadTab)) { if (tabCommAllowed.has(entry.name)) toolEntries.push(entry); } } @@ -1241,9 +1241,15 @@ export class AgentManager { * both tool-construction paths (child whitelist + permission-gated parent). * `selfHandle` is computed once so the calling tab can stamp provenance and * reject self-sends. + * + * `canReadTab` reflects whether THIS tab will also be granted `read_tab` + * (the permissions are split). It is forwarded into `send_to_tab` so the + * tool only points the agent at `read_tab` when it actually has it — never + * advertising a tool the agent wasn't granted. */ private buildTabCommToolEntries( tabId: string, + canReadTab: boolean, ): Array<{ name: string; tool: ReturnType }> { const selfHandle = shortestUniquePrefix(tabId); return [ @@ -1257,6 +1263,7 @@ export class AgentManager { this.deliverMessage(targetId, message, { origin: "agent" }), listOpenHandles: () => this.listOpenHandles(tabId), self: { id: tabId, handle: selfHandle }, + canReadTab, }), }, { diff --git a/packages/core/src/tools/send-to-tab.ts b/packages/core/src/tools/send-to-tab.ts index 84e5f25..50023a7 100644 --- a/packages/core/src/tools/send-to-tab.ts +++ b/packages/core/src/tools/send-to-tab.ts @@ -44,6 +44,13 @@ export interface SendToTabCallbacks { /** The calling tab's own id + handle — used to block self-sends and to * stamp provenance onto the delivered message. */ self: { id: string; handle: string }; + /** + * Whether THIS calling tab also has the `read_tab` tool granted. The + * tab-messaging permissions are split, so a tab can hold `send_to_tab` + * without `read_tab`. When false, the tool must NOT tell the agent to use + * `read_tab` (it doesn't have it) — replies only arrive on their own. + */ + canReadTab: boolean; } /** Render the "available tabs" hint shared by the none/ambiguous branches. */ @@ -54,6 +61,19 @@ function renderOpenHandles(handles: Array<{ handle: string; title: string }>): s } export function createSendToTabTool(callbacks: SendToTabCallbacks): ToolDefinition { + // The `read_tab` follow-up hint is only truthful when this tab actually + // holds the `read_tab` tool (the permissions are split). When it doesn't, + // the only honest guidance is that a reply arrives on its own — never tell + // the agent to call a tool it wasn't granted. + const waitLine = callbacks.canReadTab + ? "money. If the target replies it arrives on its own as a new message in a later turn; you" + : "money. If the target replies it arrives on its own as a new message in a later turn."; + const readTabLine = callbacks.canReadTab + ? ["can also call 'read_tab' with the same ID in a FUTURE turn to check. If you have other"] + : []; + const keepGoingLine = callbacks.canReadTab + ? "work to do, keep going; if you are ONLY waiting for the reply, end your turn now." + : "If you have other work to do, keep going; if you are ONLY waiting for the reply, end your turn now."; return { name: "send_to_tab", description: [ @@ -65,9 +85,9 @@ export function createSendToTabTool(callbacks: SendToTabCallbacks): ToolDefiniti "", "This is fire-and-forget: it returns immediately and does NOT wait for a reply.", "Do NOT sleep, poll, or run shell commands to wait for a reply — that wastes turns and", - "money. If the target replies it arrives on its own as a new message in a later turn; you", - "can also call 'read_tab' with the same ID in a FUTURE turn to check. If you have other", - "work to do, keep going; if you are ONLY waiting for the reply, end your turn now.", + waitLine, + ...readTabLine, + keepGoingLine, "", "Your tab ID is auto-added to the top of the message so the recipient knows who to reply", "to. The recipient must use this same 'send_to_tab' tool (addressed to your ID) to answer;", @@ -132,7 +152,7 @@ export function createSendToTabTool(callbacks: SendToTabCallbacks): ToolDefiniti "", message, "", - `[To reply to tab ${callbacks.self.handle}, use the send_to_tab tool with tab_id "${callbacks.self.handle}". Only reply if this message asks you to, or your user tells you to — it may just be context or instructions. A plain text response goes to your own user, not to this agent.]`, + `[To reply to tab ${callbacks.self.handle}, use the send_to_tab tool with tab_id "${callbacks.self.handle}". ONLY reply if this message asks you to, or your user tells you to — it may just be context or instructions. A plain text response goes to your own user, not to this agent.]`, ].join("\n"); try { @@ -153,13 +173,22 @@ export function createSendToTabTool(callbacks: SendToTabCallbacks): ToolDefiniti result.status === "queued" ? "queued (target is busy; it will be picked up next turn)" : "delivered (target was idle; a new turn has started)"; + const tail = callbacks.canReadTab + ? [ + "Do NOT sleep, poll, or run commands to wait for a reply. If the target replies it", + `arrives on its own as a new message later; you can also call read_tab with "${target.handle}"`, + "in a FUTURE turn to check. Keep working if you have other tasks; if you are ONLY", + "waiting for this reply, end your turn now.", + ] + : [ + "Do NOT sleep, poll, or run commands to wait for a reply. If the target replies it", + "arrives on its own as a new message later. Keep working if you have other tasks; if", + "you are ONLY waiting for this reply, end your turn now.", + ]; return [ `Message ${verb}. Target tab: ${target.handle} (${target.title}).`, "", - "Do NOT sleep, poll, or run commands to wait for a reply. If the target replies it", - `arrives on its own as a new message later; you can also call read_tab with "${target.handle}"`, - "in a FUTURE turn to check. Keep working if you have other tasks; if you are ONLY", - "waiting for this reply, end your turn now.", + ...tail, ].join("\n"); } catch (err) { return `Error delivering message: ${err instanceof Error ? err.message : String(err)}`; diff --git a/packages/core/tests/tools/send-to-tab.test.ts b/packages/core/tests/tools/send-to-tab.test.ts index 68f8fa0..48ff460 100644 --- a/packages/core/tests/tools/send-to-tab.test.ts +++ b/packages/core/tests/tools/send-to-tab.test.ts @@ -14,6 +14,7 @@ function makeCallbacks(overrides: Partial = {}): SendToTabCa deliver: () => ({ status: "started" }), listOpenHandles: () => [{ handle: "targ", title: "Target" }], self: { id: "self-id", handle: "self" }, + canReadTab: true, ...overrides, }; } @@ -28,6 +29,19 @@ describe("createSendToTabTool — schema & description", () => { expect(tool.description.toLowerCase()).toContain("do not sleep"); expect(tool.description.toLowerCase()).toContain("end your turn"); }); + + it("mentions read_tab in the description only when canReadTab is true", () => { + const tool = createSendToTabTool(makeCallbacks({ canReadTab: true })); + expect(tool.description).toContain("read_tab"); + }); + + it("never mentions read_tab in the description when canReadTab is false", () => { + const tool = createSendToTabTool(makeCallbacks({ canReadTab: false })); + expect(tool.description).not.toContain("read_tab"); + // Still tells the agent a reply arrives on its own + to end its turn. + expect(tool.description.toLowerCase()).toContain("arrives on its own"); + expect(tool.description.toLowerCase()).toContain("end your turn"); + }); }); describe("createSendToTabTool — execute()", () => { @@ -46,7 +60,7 @@ describe("createSendToTabTool — execute()", () => { // Reply contract: the recipient must answer via send_to_tab back to the // sender's handle, not as a plain text reply to its own user. expect(delivered).toContain('send_to_tab tool with tab_id "self"'); - expect(delivered.toLowerCase()).toContain("only reply if"); + expect(delivered).toContain("ONLY reply if"); expect(out).toContain("idle"); expect(out).toContain("targ"); // Sender is steered away from busy-waiting and told to end its turn. @@ -54,6 +68,23 @@ describe("createSendToTabTool — execute()", () => { expect(out.toLowerCase()).toContain("end your turn"); }); + it("points the sender at read_tab in the result only when canReadTab is true", async () => { + const deliver = vi.fn(() => ({ status: "started" as const })); + const tool = createSendToTabTool(makeCallbacks({ deliver, canReadTab: true })); + const out = await tool.execute({ tab_id: "targ", message: "hi" }); + expect(out).toContain("read_tab"); + }); + + it("omits read_tab from the result when canReadTab is false", async () => { + const deliver = vi.fn(() => ({ status: "started" as const })); + const tool = createSendToTabTool(makeCallbacks({ deliver, canReadTab: false })); + const out = await tool.execute({ tab_id: "targ", message: "hi" }); + expect(out).not.toContain("read_tab"); + // Still steers away from busy-waiting and toward ending the turn. + expect(out.toLowerCase()).toContain("do not sleep"); + expect(out.toLowerCase()).toContain("end your turn"); + }); + it("reports the queued status when the target is busy", async () => { const deliver = vi.fn(() => ({ status: "queued" as const })); const tool = createSendToTabTool(makeCallbacks({ deliver })); -- cgit v1.2.3 From e4379da8d1e8c7a8a89c63bdaaef99a74bf56cf2 Mon Sep 17 00:00:00 2001 From: Adam Malczewski Date: Tue, 2 Jun 2026 15:55:12 +0900 Subject: fix(tabs): say a reply will WAKE you with a new message (clearer than 'arrives on its own') Matches actual behavior: a peer's reply wakes this tab with a new message in a later turn. Updated the send_to_tab description (both canReadTab branches), the delivery-result text (both branches), and the system-prompt one-liner; updated the test assertion accordingly. --- packages/api/src/agent-manager.ts | 2 +- packages/core/src/tools/send-to-tab.ts | 10 +++++----- packages/core/tests/tools/send-to-tab.test.ts | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) (limited to 'packages/api/src') diff --git a/packages/api/src/agent-manager.ts b/packages/api/src/agent-manager.ts index 3d233fc..684f8ec 100644 --- a/packages/api/src/agent-manager.ts +++ b/packages/api/src/agent-manager.ts @@ -84,7 +84,7 @@ const TOOL_DESCRIPTIONS: Record = { youtube_transcribe: "Fetch the transcript/subtitles for a YouTube video. Set background=true to start in the background and get a job_id for later retrieval.", send_to_tab: - "Send a message to another tab (agent) by its short ID, as shown in the tab bar. Fire-and-forget: it queues/wakes the target and returns immediately without waiting for a reply. Do NOT sleep, poll, or run commands to wait — a reply arrives on its own in a later turn; if you are only waiting, end your turn.", + "Send a message to another tab (agent) by its short ID, as shown in the tab bar. Fire-and-forget: it queues/wakes the target and returns immediately without waiting for a reply. Do NOT sleep, poll, or run commands to wait — if the target replies it will wake you with a new message in a later turn; if you are only waiting, end your turn.", read_tab: "Read another tab (agent)'s most recent completed response by its short ID. Returns a non-blocking snapshot; if the target is still running you get its previous completed turn. Use after send_to_tab to collect a reply.", }; diff --git a/packages/core/src/tools/send-to-tab.ts b/packages/core/src/tools/send-to-tab.ts index 50023a7..eae6bfa 100644 --- a/packages/core/src/tools/send-to-tab.ts +++ b/packages/core/src/tools/send-to-tab.ts @@ -63,11 +63,11 @@ function renderOpenHandles(handles: Array<{ handle: string; title: string }>): s export function createSendToTabTool(callbacks: SendToTabCallbacks): ToolDefinition { // The `read_tab` follow-up hint is only truthful when this tab actually // holds the `read_tab` tool (the permissions are split). When it doesn't, - // the only honest guidance is that a reply arrives on its own — never tell + // the only honest guidance is that a reply will wake it as a new message — never tell // the agent to call a tool it wasn't granted. const waitLine = callbacks.canReadTab - ? "money. If the target replies it arrives on its own as a new message in a later turn; you" - : "money. If the target replies it arrives on its own as a new message in a later turn."; + ? "money. If the target replies it will WAKE you with a new message in a later turn; you" + : "money. If the target replies it will WAKE you with a new message in a later turn."; const readTabLine = callbacks.canReadTab ? ["can also call 'read_tab' with the same ID in a FUTURE turn to check. If you have other"] : []; @@ -176,13 +176,13 @@ export function createSendToTabTool(callbacks: SendToTabCallbacks): ToolDefiniti const tail = callbacks.canReadTab ? [ "Do NOT sleep, poll, or run commands to wait for a reply. If the target replies it", - `arrives on its own as a new message later; you can also call read_tab with "${target.handle}"`, + `will WAKE you with a new message later; you can also call read_tab with "${target.handle}"`, "in a FUTURE turn to check. Keep working if you have other tasks; if you are ONLY", "waiting for this reply, end your turn now.", ] : [ "Do NOT sleep, poll, or run commands to wait for a reply. If the target replies it", - "arrives on its own as a new message later. Keep working if you have other tasks; if", + "will WAKE you with a new message later. Keep working if you have other tasks; if", "you are ONLY waiting for this reply, end your turn now.", ]; return [ diff --git a/packages/core/tests/tools/send-to-tab.test.ts b/packages/core/tests/tools/send-to-tab.test.ts index 48ff460..21d8032 100644 --- a/packages/core/tests/tools/send-to-tab.test.ts +++ b/packages/core/tests/tools/send-to-tab.test.ts @@ -38,8 +38,8 @@ describe("createSendToTabTool — schema & description", () => { it("never mentions read_tab in the description when canReadTab is false", () => { const tool = createSendToTabTool(makeCallbacks({ canReadTab: false })); expect(tool.description).not.toContain("read_tab"); - // Still tells the agent a reply arrives on its own + to end its turn. - expect(tool.description.toLowerCase()).toContain("arrives on its own"); + // Still tells the agent a reply will wake it + to end its turn. + expect(tool.description.toLowerCase()).toContain("wake you with a new message"); expect(tool.description.toLowerCase()).toContain("end your turn"); }); }); -- cgit v1.2.3