Skip to content

Commit 6682910

Browse files
Merge pull request #871 from corbitsdev/cl-7622-pretty-print-fleet-json-so-the-pager-can-scroll-it
Pretty-print fleet JSON so the pager can scroll it
2 parents b495023 + 4965848 commit 6682910

4 files changed

Lines changed: 56 additions & 19 deletions

File tree

src/subagent/agent-fleet.test.ts

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,14 +135,21 @@ async function callToolRaw(
135135
};
136136
}
137137

138+
function parseFleetJson(content: string): Record<string, unknown> {
139+
expect(content).toContain("\n");
140+
const parsed = JSON.parse(content) as Record<string, unknown>;
141+
expect(JSON.stringify(parsed, null, 2)).toBe(content);
142+
return parsed;
143+
}
144+
138145
async function callTool(
139146
tool:
140147
| ReturnType<typeof createSpawnAgentTool>
141148
| ReturnType<typeof createWaitAgentsTool>,
142149
args: Record<string, unknown>,
143150
): Promise<Record<string, unknown>> {
144151
const { content } = await callToolRaw(tool, args);
145-
return JSON.parse(content);
152+
return parseFleetJson(content);
146153
}
147154

148155
describe("spawn_agent", () => {
@@ -196,6 +203,7 @@ describe("spawn_agent", () => {
196203
});
197204

198205
expect(result.isError).toBe(true);
206+
expect(result.content.startsWith("Error:")).toBe(true);
199207
expect(result.content).toContain("profile orchestrators are not supported");
200208
expect(runCalled).toBe(false);
201209
expect(deps.sessions.list()).toEqual([]);
@@ -291,6 +299,17 @@ describe("spawn_agent + wait_agents", () => {
291299
defined(gates[2]).resolve({ report: "third" });
292300
});
293301

302+
test("wait_agents with no uncollected agents returns empty pretty-printed results", async () => {
303+
const deps = makeDeps(async () => ({ report: "unused" }));
304+
const wait = createWaitAgentsTool({
305+
sessions: deps.sessions,
306+
fleetRecords: deps.fleetRecords,
307+
});
308+
const { content } = await callToolRaw(wait, { timeout_ms: 50 });
309+
const parsed = parseFleetJson(content);
310+
expect(parsed).toEqual({ results: [], timed_out: false });
311+
});
312+
294313
test("wait_agents times out on a still-running agent without cancelling it, and can be called again", async () => {
295314
const gate = deferred<RunSubAgentResult>();
296315
const deps = makeDeps(async () => gate.promise);
@@ -1975,7 +1994,7 @@ describe("list_agents", () => {
19751994
typeof raw.content === "string"
19761995
? raw.content
19771996
: JSON.stringify(raw.content);
1978-
const parsed = JSON.parse(content) as {
1997+
const parsed = parseFleetJson(content) as {
19791998
agents: {
19801999
agent_id: string;
19812000
status: string;
@@ -2893,7 +2912,15 @@ describe("admission queue", () => {
28932912
},
28942913
new AbortController().signal,
28952914
);
2896-
expect(raw.content).toContain('"status":"interrupted"');
2915+
const interrupted = parseFleetJson(
2916+
typeof raw.content === "string"
2917+
? raw.content
2918+
: JSON.stringify(raw.content),
2919+
);
2920+
expect(interrupted).toEqual({
2921+
agent_id: result.agent_id,
2922+
status: "interrupted",
2923+
});
28972924
expect(deps.sessions.get(result.agent_id as string)?.lifecycleStatus).toBe(
28982925
"interrupted",
28992926
);

src/subagent/agent-fleet.ts

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -619,6 +619,10 @@ function fleetResult(callId: string, content: string): ToolResult {
619619
return { callId, content, ...(isError ? { isError: true } : {}) };
620620
}
621621

622+
function fleetJson(value: unknown): string {
623+
return JSON.stringify(value, null, 2);
624+
}
625+
622626
/** Resolve agent=/intent= to a closed director. */
623627
export function resolveDirectorDispatch(
624628
agentId: string | undefined,
@@ -1450,10 +1454,7 @@ export function createSpawnAgentTool(deps: AgentFleetDeps): AgentTool {
14501454
start,
14511455
});
14521456
if (status === "queued") deps.fleetRecords.markQueued(session.id);
1453-
return fleetResult(
1454-
call.id,
1455-
JSON.stringify({ agent_id: session.id, status }),
1456-
);
1457+
return fleetResult(call.id, fleetJson({ agent_id: session.id, status }));
14571458
},
14581459
});
14591460
}
@@ -1551,7 +1552,7 @@ export function createWaitAgentsTool(deps: WaitAgentsDeps): AgentTool {
15511552
if (targets.length === 0) {
15521553
return fleetResult(
15531554
call.id,
1554-
JSON.stringify({ results: [], timed_out: false }),
1555+
fleetJson({ results: [], timed_out: false }),
15551556
);
15561557
}
15571558

@@ -1627,10 +1628,7 @@ export function createWaitAgentsTool(deps: WaitAgentsDeps): AgentTool {
16271628
};
16281629
});
16291630

1630-
return fleetResult(
1631-
call.id,
1632-
JSON.stringify({ results, timed_out: timedOut }),
1633-
);
1631+
return fleetResult(call.id, fleetJson({ results, timed_out: timedOut }));
16341632
},
16351633
});
16361634
}
@@ -1678,7 +1676,7 @@ export function createListAgentsTool(deps: WaitAgentsDeps): AgentTool {
16781676
: {}),
16791677
};
16801678
});
1681-
return fleetResult(call.id, JSON.stringify({ agents }));
1679+
return fleetResult(call.id, fleetJson({ agents }));
16821680
},
16831681
});
16841682
}

src/subagent/lifecycle-tools.test.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@ import {
1515
import { createAdmissionQueue } from "./admission.js";
1616
import { defined } from "../../tests/helpers/defined.js";
1717

18+
function parseFleetJson(content: string): Record<string, unknown> {
19+
expect(content).toContain("\n");
20+
const parsed = JSON.parse(content) as Record<string, unknown>;
21+
expect(JSON.stringify(parsed, null, 2)).toBe(content);
22+
return parsed;
23+
}
24+
1825
async function callTool(
1926
tool:
2027
| ReturnType<typeof createCloseAgentTool>
@@ -38,7 +45,7 @@ async function callTool(
3845
typeof result.content === "string"
3946
? result.content
4047
: JSON.stringify(result.content);
41-
return JSON.parse(content);
48+
return parseFleetJson(content);
4249
}
4350

4451
describe("close_agent", () => {
@@ -659,6 +666,7 @@ describe("resume_agent", () => {
659666
new AbortController().signal,
660667
);
661668
expect(empty.isError).toBe(true);
669+
expect(String(empty.content).startsWith("Error:")).toBe(true);
662670
expect(String(empty.content)).toContain("non-empty message");
663671

664672
const oversize = await resumeAgent.handler(

src/subagent/lifecycle-tools.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ function lifecycleResult(callId: string, content: string): ToolResult {
3232
return { callId, content, ...(isError ? { isError: true } : {}) };
3333
}
3434

35+
function fleetJson(value: unknown): string {
36+
return JSON.stringify(value, null, 2);
37+
}
38+
3539
const CloseAgentArgs = type({
3640
target: "string",
3741
});
@@ -185,7 +189,7 @@ export function createCloseAgentTool(deps: CloseAgentToolDeps): AgentTool {
185189
if (deps.sessions.get(target) === undefined) {
186190
return lifecycleResult(
187191
call.id,
188-
JSON.stringify({
192+
fleetJson({
189193
agent_id: target,
190194
status: "not_found" satisfies AgentLifecycleStatus,
191195
}),
@@ -228,7 +232,7 @@ export function createCloseAgentTool(deps: CloseAgentToolDeps): AgentTool {
228232
const own = closed.find((c) => c.agent_id === target);
229233
return lifecycleResult(
230234
call.id,
231-
JSON.stringify({
235+
fleetJson({
232236
agent_id: target,
233237
status: own?.status ?? "shutdown",
234238
closed,
@@ -299,7 +303,7 @@ export function createResumeAgentTool(deps: ResumeAgentToolDeps): AgentTool {
299303
}
300304
return lifecycleResult(
301305
call.id,
302-
JSON.stringify({ agent_id: target, status: outcome.status }),
306+
fleetJson({ agent_id: target, status: outcome.status }),
303307
);
304308
},
305309
});
@@ -361,7 +365,7 @@ export function createInterruptAgentTool(
361365
deps.fleetRecords.interrupt(target);
362366
return lifecycleResult(
363367
call.id,
364-
JSON.stringify({
368+
fleetJson({
365369
agent_id: target,
366370
status: "interrupted" satisfies AgentLifecycleStatus,
367371
}),
@@ -474,7 +478,7 @@ export function createSendInputTool(deps: LifecycleToolDeps): AgentTool {
474478
}
475479
return lifecycleResult(
476480
call.id,
477-
JSON.stringify({ agent_id: target, status: outcome.status }),
481+
fleetJson({ agent_id: target, status: outcome.status }),
478482
);
479483
},
480484
});

0 commit comments

Comments
 (0)