Skip to content

Commit 893dc84

Browse files
committed
feat(agent): streamline prompts, merge skill search, and replace shell_collect
- Rephrase primary chat prompt as a structured card (~140 words covering Role, Route, Rules, and Spawn) and compact worker contract to ~150 words. - Rewrite neckbeard and bruckheimer director bodies to under 600 words; drop verbose harness guidelines, duplicates, and redundant recovery text. - Remove shell_collect tool in favor of bash stop=<shell_id> cancellation and later-turn completion delivery. - Merge skill search into tool_search as a dedicated Skills block, removing the separate skill_search tool from primary sessions.
1 parent b8ce115 commit 893dc84

29 files changed

Lines changed: 338 additions & 1259 deletions

‎src/agent/agent-search.ts‎

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -112,19 +112,17 @@ export function formatAgentSearchResults(
112112
export const searchAgentsDefinition: ToolDefinition = {
113113
name: "search_agents",
114114
description:
115-
"Find spawnable agent profiles by capability, role, or team name (e.g. 'review', 'review team', 'architect', 'security'). Returns profile ids, descriptions, and spawn metadata (orchestrator flag, source). Pass include_body=true to include the loaded system prompt / body for each match (truncated). Use the id in spawn_agent(agent=...). Call this when the user asks to spin up specialists or a team without naming exact ids.",
115+
"Find spawnable agent profiles by role or capability. Returns ids for spawn_agent(agent=...).",
116116
inputSchema: {
117117
type: "object",
118118
properties: {
119119
query: {
120120
type: "string",
121-
description:
122-
"What kind of agent or team you need — keywords from the user's request (e.g. 'review team', 'code quality', 'explore codebase').",
121+
description: "Role or capability.",
123122
},
124123
include_body: {
125124
type: "boolean",
126-
description:
127-
"When true, include each match's loaded system prompt / body (truncated). Default false: id, description, and spawn metadata only.",
125+
description: "Include profile body.",
128126
},
129127
},
130128
required: ["query"],

‎src/agent/background-shell-tool.test.ts‎

Lines changed: 12 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -51,37 +51,10 @@ describe("background shell through the agent toolset", () => {
5151
const parsed = JSON.parse(String(started.content)) as {
5252
shell_id: string;
5353
};
54-
const snapshotNow = await toolset.dynamicRunner.run(
55-
{
56-
id: "bg-collect",
57-
name: "shell_collect",
58-
arguments: { shell_id: parsed.shell_id, action: "collect" },
59-
},
60-
new AbortController().signal,
61-
);
62-
expect(JSON.parse(String(snapshotNow.content))).toMatchObject({
63-
status: "running",
64-
});
65-
const final = await toolset.dynamicRunner.run(
66-
{
67-
id: "bg-collect2",
68-
name: "shell_collect",
69-
arguments: {
70-
shell_id: parsed.shell_id,
71-
action: "collect",
72-
wait_ms: 5_000,
73-
},
74-
},
75-
new AbortController().signal,
76-
);
77-
const result = JSON.parse(String(final.content)) as {
78-
status: string;
79-
exit_code: number;
80-
output: string;
81-
};
82-
expect(result).toMatchObject({ status: "completed", exit_code: 0 });
83-
expect(result.output).toContain("bg-done");
84-
await new Promise((r) => setTimeout(r, 50));
54+
const deadline = Date.now() + 5_000;
55+
while (exits.length === 0 && Date.now() < deadline) {
56+
await new Promise((r) => setTimeout(r, 25));
57+
}
8558
expect(exits).toHaveLength(1);
8659
expect(defined(exits[0]).id).toBe(parsed.shell_id);
8760
const message = buildShellBackgroundMessage(defined(exits[0]));
@@ -97,7 +70,7 @@ describe("background shell through the agent toolset", () => {
9770
}
9871
});
9972

100-
test("shell_collect cancel kills the session's own child process group", async () => {
73+
test("run_shell stop kills the session's own child process group", async () => {
10174
const token = `ic_toolset_cancel_${randomUUID()}`;
10275
const toolset = await createAgentToolset({
10376
cwd: process.cwd(),
@@ -119,15 +92,11 @@ describe("background shell through the agent toolset", () => {
11992
const { shell_id } = JSON.parse(String(started.content)) as {
12093
shell_id: string;
12194
};
122-
const cancelled = await toolset.dynamicRunner.run(
123-
{
124-
id: "c-cancel",
125-
name: "shell_collect",
126-
arguments: { shell_id, action: "cancel" },
127-
},
95+
const stopped = await toolset.dynamicRunner.run(
96+
{ id: "c-stop", name: "run_shell", arguments: { stop: shell_id } },
12897
new AbortController().signal,
12998
);
130-
expect(JSON.parse(String(cancelled.content))).toMatchObject({
99+
expect(JSON.parse(String(stopped.content))).toMatchObject({
131100
status: "cancelling",
132101
});
133102
await waitUntilGone(token);
@@ -136,76 +105,18 @@ describe("background shell through the agent toolset", () => {
136105
}
137106
});
138107

139-
test("shell_collect with an aborted signal releases as running without killing the child", async () => {
108+
test("run_shell stop on an unknown id is an error", async () => {
140109
const toolset = await createAgentToolset({
141110
cwd: process.cwd(),
142111
permissionGate: gate(process.cwd()),
112+
onOperatorGate: async () => ({ kind: "cancel" as const }),
143113
});
144114
try {
145-
const started = await toolset.dynamicRunner.run(
146-
{
147-
id: "a-start",
148-
name: "run_shell",
149-
arguments: {
150-
command: "sleep 60",
151-
background: true,
152-
},
153-
},
154-
new AbortController().signal,
155-
);
156-
const { shell_id } = JSON.parse(String(started.content)) as {
157-
shell_id: string;
158-
};
159-
const aborted = new AbortController();
160-
aborted.abort(new Error("interrupted by interrupt_agent"));
161115
const out = await toolset.dynamicRunner.run(
162-
{
163-
id: "a-collect",
164-
name: "shell_collect",
165-
arguments: { shell_id, action: "collect", wait_ms: 60_000 },
166-
},
167-
aborted.signal,
168-
);
169-
expect(JSON.parse(String(out.content))).toMatchObject({
170-
status: "running",
171-
});
172-
// A live-signal collect still sees the child running: the abort above
173-
// released the waiter instead of killing the process. Had the abort
174-
// killed it, this would already report completed.
175-
const stillThere = await toolset.dynamicRunner.run(
176-
{
177-
id: "a-collect2",
178-
name: "shell_collect",
179-
arguments: { shell_id, action: "collect" },
180-
},
181-
new AbortController().signal,
182-
);
183-
expect(JSON.parse(String(stillThere.content))).toMatchObject({
184-
status: "running",
185-
});
186-
// And the child is still killable: cancel settles it as completed.
187-
const cancelled = await toolset.dynamicRunner.run(
188-
{
189-
id: "a-cancel",
190-
name: "shell_collect",
191-
arguments: { shell_id, action: "cancel" },
192-
},
116+
{ id: "u-stop", name: "run_shell", arguments: { stop: "nope" } },
193117
new AbortController().signal,
194118
);
195-
expect(JSON.parse(String(cancelled.content))).toMatchObject({
196-
status: "cancelling",
197-
});
198-
const final = await toolset.dynamicRunner.run(
199-
{
200-
id: "a-collect3",
201-
name: "shell_collect",
202-
arguments: { shell_id, action: "collect", wait_ms: 5_000 },
203-
},
204-
new AbortController().signal,
205-
);
206-
expect(JSON.parse(String(final.content))).toMatchObject({
207-
status: "completed",
208-
});
119+
expect(out.isError).toBe(true);
209120
} finally {
210121
await toolset.dispose();
211122
}

‎src/agent/background-shell-tool.ts‎

Lines changed: 1 addition & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,6 @@
1-
import { type } from "arktype";
2-
import type { ToolDefinition } from "@intx/types/runtime";
3-
import {
4-
createBackgroundShellRegistry,
5-
MAX_SHELL_COLLECT_WAIT_MS,
6-
type BackgroundShellExit,
7-
type BackgroundShellRegistry,
8-
} from "../shell/background-shell.js";
1+
import { type BackgroundShellExit } from "../shell/background-shell.js";
92
import type { SpillBlobWriter } from "../plugins/result-truncation-plugin.js";
103

11-
const ShellCollectArgs = type({
12-
shell_id: "string>0",
13-
action: "'collect' | 'cancel'",
14-
"wait_ms?": "number",
15-
});
16-
type ShellCollectArgs = typeof ShellCollectArgs.infer;
17-
18-
export const shellCollectDefinition: ToolDefinition = {
19-
name: "shell_collect",
20-
description:
21-
"Collect or cancel a background run_shell (started with background: true). " +
22-
'action="collect" returns the result once finished (or status running); ' +
23-
'action="cancel" kills the process group. Completion also arrives as a ' +
24-
"system message on a later turn — collect is for polling or retrieving " +
25-
'output again after eviction risk. A "running" result is liveness, not a ' +
26-
"stall or a crash: repeated identical collects of a still-running shell " +
27-
"are exempt from the run's doom-loop guard, so keep polling rather than " +
28-
"treating it as a failure.",
29-
inputSchema: {
30-
type: "object",
31-
properties: {
32-
shell_id: {
33-
type: "string",
34-
description: "shell_id from the background run_shell start.",
35-
},
36-
action: {
37-
type: "string",
38-
enum: ["collect", "cancel"],
39-
description:
40-
'"collect" retrieves status/output; "cancel" kills the process group.',
41-
},
42-
wait_ms: {
43-
type: "number",
44-
description:
45-
'For action="collect": milliseconds to wait for completion before returning "running" (default 0, non-blocking, capped at ' +
46-
`${MAX_SHELL_COLLECT_WAIT_MS}).`,
47-
},
48-
},
49-
required: ["shell_id", "action"],
50-
},
51-
};
52-
534
export function createSpillingBackgroundShellExitNotifier(args: {
545
getBlobWriter?: () => SpillBlobWriter | undefined;
556
notify: (exit: BackgroundShellExit) => void;
@@ -75,51 +26,3 @@ export function createSpillingBackgroundShellExitNotifier(args: {
7526
})();
7627
};
7728
}
78-
79-
export function createShellCollectTool(
80-
registry: BackgroundShellRegistry = createBackgroundShellRegistry(),
81-
) {
82-
return {
83-
definition: shellCollectDefinition,
84-
handler: async (
85-
rawArgs: Record<string, unknown>,
86-
signal?: AbortSignal,
87-
): Promise<string> => {
88-
const parsed = ShellCollectArgs(rawArgs);
89-
if (parsed instanceof type.errors) {
90-
return "Error: shell_collect requires shell_id (string) and action ('collect' | 'cancel').";
91-
}
92-
const { shell_id, action } = parsed;
93-
if (action === "cancel") {
94-
if (!registry.cancel(shell_id)) {
95-
return `No running background shell with id ${shell_id}; it may have already finished or been collected.`;
96-
}
97-
return JSON.stringify({ shell_id, status: "cancelling" });
98-
}
99-
const snapshot = await registry.collect(
100-
shell_id,
101-
parsed.wait_ms ?? 0,
102-
signal,
103-
);
104-
if (snapshot.state === "running") {
105-
return JSON.stringify({ shell_id, status: "running" });
106-
}
107-
if (snapshot.state === "not-found") {
108-
return (
109-
`No background shell with id ${shell_id}. It may have been evicted from the ` +
110-
"completed ring; if its output was truncated, the completion message carried " +
111-
"a tool-output:/// URI for the full output."
112-
);
113-
}
114-
const { exit } = snapshot;
115-
return JSON.stringify({
116-
shell_id,
117-
status: "completed",
118-
exit_code: exit.exitCode,
119-
timed_out: exit.timedOut,
120-
...(exit.spillUri !== undefined ? { output_uri: exit.spillUri } : {}),
121-
output: exit.output,
122-
});
123-
},
124-
};
125-
}

‎src/agent/codex-tool-mount.test.ts‎

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,19 @@
22
* Mount coverage for Codex: no advertised apply_patch/shell/update_plan,
33
* engines stay posix-named, hidden aliases dispatch without dual-publish.
44
*/
5-
import { mkdtempSync } from "node:fs";
5+
import { mkdtempSync, readFileSync } from "node:fs";
66
import { tmpdir } from "node:os";
77
import { join } from "node:path";
88
import { afterEach, describe, expect, test, spyOn } from "bun:test";
99
import * as posixModule from "@intx/tools-posix";
1010

1111
import { BUILD_TOOLS, DOCS_TOOLS } from "./directors/tool-sets.js";
12-
import { advertisedTools, CORE_TOOL_NAMES } from "./tool-search.js";
12+
import { foldFileToolNames } from "./tool-aliases.js";
13+
import {
14+
ADVERTISED_TOOL_NAMES,
15+
advertisedTools,
16+
CORE_TOOL_NAMES,
17+
} from "./tool-search.js";
1318

1419
afterEach(() => {
1520
spyOn(posixModule, "createPosixTools").mockRestore();
@@ -42,7 +47,7 @@ describe("Codex tool proxy mount", () => {
4247
await toolset.dispose();
4348
});
4449

45-
test("Codex createAgentToolset does not advertise apply_patch/shell/update_plan", async () => {
50+
test("Codex createAgentToolset keeps engines posix-named and projects wire names by profile", async () => {
4651
// Unstubbed createPosixTools: write_file / edit_file / delete_file come from
4752
// the real posix + delete-file plugin mount. An empty stub would hide them
4853
// and make the DIY-remains assertion meaningless.
@@ -74,6 +79,17 @@ describe("Codex tool proxy mount", () => {
7479
expect(advertised).not.toContain("shell");
7580
expect(advertised).not.toContain("apply_patch");
7681
expect(advertised).not.toContain("update_plan");
82+
const gptAdvertised = advertisedTools(
83+
toolset.dynamicRunner.currentDefinitions(),
84+
[],
85+
foldFileToolNames(ADVERTISED_TOOL_NAMES, "gpt"),
86+
"gpt",
87+
).map((d) => d.name);
88+
expect(gptAdvertised).toContain("shell");
89+
expect(gptAdvertised).toContain("apply_patch");
90+
expect(gptAdvertised).toContain("update_plan");
91+
expect(gptAdvertised).not.toContain("write");
92+
expect(gptAdvertised).not.toContain("bash");
7793
await toolset.dispose();
7894
});
7995

@@ -111,6 +127,36 @@ describe("Codex tool proxy mount", () => {
111127
await toolset.dispose();
112128
});
113129

130+
test("wire names from every profile dispatch onto the mounted engines", async () => {
131+
const cwd = mkdtempSync(join(tmpdir(), "corbits-codex-mount-"));
132+
const { createAgentToolset } = await import("./tools.js");
133+
const toolset = await createAgentToolset({
134+
cwd,
135+
permissionGate: {
136+
check: async () => ({ allowed: true }),
137+
getSkipPermissions: () => false,
138+
} as never,
139+
onOperatorGate: async () => ({ kind: "option", index: 0 }),
140+
isCodex: true,
141+
});
142+
const call = (name: string, args: Record<string, unknown>) =>
143+
toolset.dynamicRunner.run(
144+
{ id: name, name, arguments: args },
145+
new AbortController().signal,
146+
);
147+
const patched = await call("apply_patch", {
148+
input: "*** Begin Patch\n*** Add File: made.txt\n+hi\n*** End Patch",
149+
});
150+
expect(patched.isError).toBeFalsy();
151+
expect(readFileSync(join(cwd, "made.txt"), "utf8")).toBe("hi\n");
152+
const todo = await call("todowrite", {
153+
action: "create",
154+
tasks: [{ id: "t1", title: "x", status: "todo" }],
155+
});
156+
expect(todo.isError).toBeFalsy();
157+
await toolset.dispose();
158+
});
159+
114160
test("BUILD_TOOLS and DOCS_TOOLS omit apply_patch; CORE_TOOL_NAMES does not list it", () => {
115161
expect(BUILD_TOOLS).not.toContain("apply_patch");
116162
expect(DOCS_TOOLS).not.toContain("apply_patch");

0 commit comments

Comments
 (0)