Repository navigation
fix(send): allow_busy opt-in for send_to / send_to_agent #86
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| node_modules/ | ||
| dist/ | ||
| bun.lock | ||
| docs.local/ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -335,6 +335,124 @@ describe("agent lifecycle tool handlers", () => { | |
| expect(result.content[0].text).toMatch(/not in an interactive state/); | ||
| }); | ||
|
|
||
| it("send_to with allow_busy=true delivers to agents in working state", async () => { | ||
| const server = createLifecycleServer(mockExec); | ||
| const spawn = (server as any)._registeredTools["spawn_agent"]; | ||
| const sendTo = (server as any)._registeredTools["send_to"]; | ||
|
|
||
| const spawnResult = await spawn.handler( | ||
| { | ||
| repo: "brainlayer", | ||
| model: "sonnet", | ||
| cli: "claude", | ||
| prompt: "test", | ||
| }, | ||
| {} as any, | ||
| ); | ||
| const agentId = ( | ||
| spawnResult.structuredContent ?? JSON.parse(spawnResult.content[0].text) | ||
| ).agent_id; | ||
|
|
||
| const engine = (server as any)._registeredTools["interact"]._engine; | ||
| const registry = engine.getRegistry(); | ||
| const agent = registry.get(agentId); | ||
| registry.set(agentId, { ...agent, state: "working" }); | ||
| mockExec.mockClear(); | ||
|
|
||
| const result = await sendTo.handler( | ||
| { | ||
| agent_id: agentId, | ||
| text: "interject while working", | ||
| press_enter: true, | ||
| allow_busy: true, | ||
| }, | ||
| {} as any, | ||
| ); | ||
| const parsed = | ||
| result.structuredContent ?? JSON.parse(result.content[0].text); | ||
| const sendCalls = mockExec.mock.calls.filter( | ||
| ([, args]) => Array.isArray(args) && args.includes("send"), | ||
| ); | ||
| const deliveredText = sendCalls.map(([, args]) => args.at(-1)).join(""); | ||
|
|
||
| expect(result.isError).toBeFalsy(); | ||
| expect(parsed.ok).toBe(true); | ||
| expect(parsed.agent_id).toBe(agentId); | ||
| expect(deliveredText).toBe("interject while working"); | ||
| }); | ||
|
|
||
| it("send_to without allow_busy still rejects working agents (backwards compat)", async () => { | ||
| const server = createLifecycleServer(mockExec); | ||
| const spawn = (server as any)._registeredTools["spawn_agent"]; | ||
| const sendTo = (server as any)._registeredTools["send_to"]; | ||
|
|
||
| const spawnResult = await spawn.handler( | ||
| { | ||
| repo: "brainlayer", | ||
| model: "sonnet", | ||
| cli: "claude", | ||
| prompt: "test", | ||
| }, | ||
| {} as any, | ||
| ); | ||
| const agentId = ( | ||
| spawnResult.structuredContent ?? JSON.parse(spawnResult.content[0].text) | ||
| ).agent_id; | ||
|
|
||
| const engine = (server as any)._registeredTools["interact"]._engine; | ||
| const registry = engine.getRegistry(); | ||
| const agent = registry.get(agentId); | ||
| registry.set(agentId, { ...agent, state: "working" }); | ||
|
|
||
| const result = await sendTo.handler( | ||
| { agent_id: agentId, text: "hello", press_enter: true }, | ||
| {} as any, | ||
| ); | ||
| expect(result.isError).toBe(true); | ||
| expect(result.content[0].text).toMatch(/not in an interactive state/); | ||
|
Comment on lines
+384
to
+412
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Assert the new This test protects backwards compatibility, but it does not lock the PR’s updated error guidance that tells callers how to bypass the gate. 🧪 Proposed assertion expect(result.isError).toBe(true);
expect(result.content[0].text).toMatch(/not in an interactive state/);
+ expect(result.content[0].text).toMatch(/allow_busy: true/);🤖 Prompt for AI Agents |
||
| }); | ||
|
|
||
| it("send_to_agent with allow_busy=true delivers to agents in working state", async () => { | ||
| const server = createLifecycleServer(mockExec); | ||
| const spawn = (server as any)._registeredTools["spawn_agent"]; | ||
| const sendTo = (server as any)._registeredTools["send_to_agent"]; | ||
|
|
||
| const spawnResult = await spawn.handler( | ||
| { | ||
| repo: "brainlayer", | ||
| model: "sonnet", | ||
| cli: "claude", | ||
| prompt: "test", | ||
| }, | ||
| {} as any, | ||
| ); | ||
| const agentId = ( | ||
| spawnResult.structuredContent ?? JSON.parse(spawnResult.content[0].text) | ||
| ).agent_id; | ||
|
|
||
| const engine = (server as any)._registeredTools["interact"]._engine; | ||
| const registry = engine.getRegistry(); | ||
| const agent = registry.get(agentId); | ||
| registry.set(agentId, { ...agent, state: "working" }); | ||
| mockExec.mockClear(); | ||
|
|
||
| const result = await sendTo.handler( | ||
| { | ||
| agent_id: agentId, | ||
| text: "force deliver", | ||
| press_enter: true, | ||
| allow_busy: true, | ||
| }, | ||
| {} as any, | ||
| ); | ||
| const parsed = | ||
| result.structuredContent ?? JSON.parse(result.content[0].text); | ||
|
|
||
| expect(result.isError).toBeFalsy(); | ||
| expect(parsed.ok).toBe(true); | ||
| expect(parsed.agent_id).toBe(agentId); | ||
| }); | ||
|
Comment on lines
+415
to
+454
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Verify The test currently proves the response is successful, but not that the deprecated tool forwards 🧪 Proposed delivery assertion const parsed =
result.structuredContent ?? JSON.parse(result.content[0].text);
+ const sendCalls = mockExec.mock.calls.filter(
+ ([, args]) => Array.isArray(args) && args.includes("send"),
+ );
+ const deliveredText = sendCalls.map(([, args]) => args.at(-1)).join("");
expect(result.isError).toBeFalsy();
expect(parsed.ok).toBe(true);
expect(parsed.agent_id).toBe(agentId);
+ expect(deliveredText).toBe("force deliver");🤖 Prompt for AI Agents |
||
|
|
||
| it("send_to sanitizes and chunks delivery through the agent surface", async () => { | ||
| const server = createLifecycleServer(mockExec); | ||
| const spawn = (server as any)._registeredTools["spawn_agent"]; | ||
|
|
@@ -359,8 +477,7 @@ describe("agent lifecycle tool handlers", () => { | |
| registry.set(agentId, { ...agent, state: "ready" }); | ||
| mockExec.mockClear(); | ||
|
|
||
| const rawText = | ||
| `${"a".repeat(510)}\x1b[31mHELLO\x1b[0m\x07${"b".repeat(10)}`; | ||
| const rawText = `${"a".repeat(510)}\x1b[31mHELLO\x1b[0m\x07${"b".repeat(10)}`; | ||
| const sanitizedText = `${"a".repeat(510)}HELLO${"b".repeat(10)}`; | ||
|
|
||
| const result = await sendTo.handler( | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Place new server coverage in the mirrored server test file, or document the exception.
These new
src/server.tstests are being added totests/server-agent-tools.test.ts, which does not follow the source/test mirroring rule. If this suite is intentionally exempt, please codify that exception. As per coding guidelines, "**/tests/**/*.test.ts: Test files must mirror source structure:src/foo.ts->tests/foo.test.ts."🤖 Prompt for AI Agents