@pi-archimedes/subagent 2.7.2 → 2.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +3 -3
- package/src/format.test.ts +90 -1
- package/src/format.ts +23 -0
- package/src/index.ts +2 -12
package/README.md
CHANGED
|
@@ -44,7 +44,7 @@ The `agent` field is optional — omitting it is not a built-in default agent, a
|
|
|
44
44
|
}
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
**The dispatch waits.** A call — single or parallel — blocks until every task completes and returns the combined results, each optionally carrying the child's `childSessionId`. There is an `async` field in the schema; the implementation ignores it, so don't plan on fire-and-forget: batch the work into `tasks` and let the call block on all of it.
|
|
47
|
+
**The dispatch waits.** A call — single or parallel — blocks until every task completes and returns the combined results, each optionally carrying the child's `childSessionId`. There is an `async` field in the schema; the implementation ignores it, so don't plan on fire-and-forget: batch the work into `tasks` and let the call block on all of it. In parallel mode each result section carries the child's final output beneath its metrics line.
|
|
48
48
|
|
|
49
49
|
### Model and thinking resolution
|
|
50
50
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-archimedes/subagent",
|
|
3
|
-
"version": "2.7.
|
|
3
|
+
"version": "2.7.3",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/danielcherubini/pi-archimedes.git"
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
],
|
|
16
16
|
"main": "./src/index.ts",
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@pi-archimedes/core": "2.7.
|
|
19
|
-
"@pi-archimedes/todo": "2.7.
|
|
18
|
+
"@pi-archimedes/core": "2.7.3",
|
|
19
|
+
"@pi-archimedes/todo": "2.7.3"
|
|
20
20
|
},
|
|
21
21
|
"peerDependencies": {
|
|
22
22
|
"@earendil-works/pi-ai": ">=0.1.0",
|
package/src/format.test.ts
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { truncLine } from "./format.js";
|
|
2
|
+
import { truncLine, formatParallelResults } from "./format.js";
|
|
3
|
+
import type { SubagentResult } from "./types.js";
|
|
4
|
+
|
|
5
|
+
function makeResult(overrides: Partial<SubagentResult> = {}): SubagentResult {
|
|
6
|
+
return {
|
|
7
|
+
agent: "test-agent",
|
|
8
|
+
task: "do something",
|
|
9
|
+
exitCode: 0,
|
|
10
|
+
usage: { input: 100, output: 50, cacheRead: 0, cacheWrite: 0, cost: 0.01, turns: 3 },
|
|
11
|
+
model: "gpt-4",
|
|
12
|
+
finalOutput: undefined,
|
|
13
|
+
error: undefined,
|
|
14
|
+
progress: undefined,
|
|
15
|
+
progressSummary: { toolCount: 5, tokens: 150, durationMs: 3000 },
|
|
16
|
+
...overrides,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
3
19
|
|
|
4
20
|
// ── truncLine ───────────────────────────────────────────────────────────────
|
|
5
21
|
|
|
@@ -28,3 +44,76 @@ describe("truncLine", () => {
|
|
|
28
44
|
expect(truncLine("\nhello", 10)).toBe("...");
|
|
29
45
|
});
|
|
30
46
|
});
|
|
47
|
+
|
|
48
|
+
describe("formatParallelResults", () => {
|
|
49
|
+
it("renders header + body per task with exactly one blank line between sections", () => {
|
|
50
|
+
const r1 = makeResult({
|
|
51
|
+
agent: "researcher",
|
|
52
|
+
progressSummary: { toolCount: 31, tokens: 12345, durationMs: 153000 },
|
|
53
|
+
finalOutput: "Found the bug.\n", // trailing newline: pins trimEnd
|
|
54
|
+
});
|
|
55
|
+
const r2 = makeResult({
|
|
56
|
+
agent: "reviewer",
|
|
57
|
+
progressSummary: { toolCount: 4, tokens: 2000, durationMs: 12000 },
|
|
58
|
+
finalOutput: "Looks good.",
|
|
59
|
+
});
|
|
60
|
+
expect(formatParallelResults([r1, r2])).toBe(
|
|
61
|
+
"✓ researcher 31 tools · 12k tok · 153s\nFound the bug.\n\n✓ reviewer 4 tools · 2k tok · 12s\nLooks good.",
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("rounds tokens and duration with Math.round", () => {
|
|
66
|
+
const r = makeResult({
|
|
67
|
+
progressSummary: { toolCount: 1, tokens: 12500, durationMs: 154000 },
|
|
68
|
+
finalOutput: "x",
|
|
69
|
+
});
|
|
70
|
+
expect(formatParallelResults([r])).toBe("✓ test-agent 1 tools · 13k tok · 154s\nx");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("renders header only when progressSummary is absent (synthetic case)", () => {
|
|
74
|
+
const r = makeResult({ progressSummary: undefined });
|
|
75
|
+
expect(formatParallelResults([r])).toBe("✓ test-agent\ncompleted");
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("uses the ✗ glyph for non-zero exit codes", () => {
|
|
79
|
+
const r = makeResult({ exitCode: 1, finalOutput: "done" });
|
|
80
|
+
expect(formatParallelResults([r])).toBe("✗ test-agent 5 tools · 0k tok · 3s\ndone");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("falls back to 'completed' when there is no finalOutput and no error", () => {
|
|
84
|
+
const r = makeResult();
|
|
85
|
+
expect(formatParallelResults([r])).toBe("✓ test-agent 5 tools · 0k tok · 3s\ncompleted");
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("uses the error text as the body when there is no finalOutput", () => {
|
|
89
|
+
const r = makeResult({ exitCode: 1, error: "spawn failed" });
|
|
90
|
+
expect(formatParallelResults([r])).toBe("✗ test-agent 5 tools · 0k tok · 3s\nspawn failed");
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("prefers finalOutput over error when both are set", () => {
|
|
94
|
+
const r = makeResult({ finalOutput: "out", error: "boom" });
|
|
95
|
+
expect(formatParallelResults([r])).toBe("✓ test-agent 5 tools · 0k tok · 3s\nout");
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("keeps internal newlines in a multi-line body and trims only the tail", () => {
|
|
99
|
+
const r1 = makeResult({
|
|
100
|
+
agent: "researcher",
|
|
101
|
+
finalOutput: "line one\nline two\n",
|
|
102
|
+
});
|
|
103
|
+
const r2 = makeResult({
|
|
104
|
+
agent: "reviewer",
|
|
105
|
+
finalOutput: "second",
|
|
106
|
+
});
|
|
107
|
+
expect(formatParallelResults([r1, r2])).toBe(
|
|
108
|
+
"✓ researcher 5 tools · 0k tok · 3s\nline one\nline two\n\n✓ reviewer 5 tools · 0k tok · 3s\nsecond",
|
|
109
|
+
);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("keeps duplicate agent names distinguishable by position and body", () => {
|
|
113
|
+
const a = makeResult({ agent: "researcher", finalOutput: "first" });
|
|
114
|
+
const b = makeResult({ agent: "researcher", finalOutput: "second" });
|
|
115
|
+
expect(formatParallelResults([a, b])).toBe(
|
|
116
|
+
"✓ researcher 5 tools · 0k tok · 3s\nfirst\n\n✓ researcher 5 tools · 0k tok · 3s\nsecond",
|
|
117
|
+
);
|
|
118
|
+
});
|
|
119
|
+
});
|
package/src/format.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { SubagentResult } from "./types.js";
|
|
2
|
+
|
|
1
3
|
export function formatTokens(n: number): string {
|
|
2
4
|
if (n >= 1_000_000) return (n / 1_000_000).toFixed(1) + "M";
|
|
3
5
|
if (n >= 1_000) return Math.round(n / 1_000) + "k";
|
|
@@ -41,6 +43,27 @@ export function truncLine(text: string, maxLen: number): string {
|
|
|
41
43
|
return text.slice(0, maxLen - 3) + "...";
|
|
42
44
|
}
|
|
43
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Renders one section per parallel result. The header line is intentionally
|
|
48
|
+
* byte-identical to the pre-#55 metrics summary (a contract, not an
|
|
49
|
+
* accident); the body mirrors the single-mode fallback chain
|
|
50
|
+
* (finalOutput ?? error ?? "completed"), `trimEnd()`-ed so sections stay
|
|
51
|
+
* separated by exactly one blank line, and is deliberately untruncated by
|
|
52
|
+
* design (see docs/decisions/0020-subagent-parallel-full-output.md).
|
|
53
|
+
*/
|
|
54
|
+
export function formatParallelResults(results: SubagentResult[]): string {
|
|
55
|
+
const sections = results.map((r) => {
|
|
56
|
+
const status = r.exitCode === 0 ? "✓" : "✗";
|
|
57
|
+
const summary = r.progressSummary
|
|
58
|
+
? `${r.progressSummary.toolCount} tools · ${Math.round(r.progressSummary.tokens / 1000)}k tok · ${Math.round(r.progressSummary.durationMs / 1000)}s`
|
|
59
|
+
: "";
|
|
60
|
+
const header = `${status} ${r.agent}${summary ? " " + summary : ""}`;
|
|
61
|
+
const body = (r.finalOutput ?? r.error ?? "completed").trimEnd();
|
|
62
|
+
return `${header}\n${body}`;
|
|
63
|
+
});
|
|
64
|
+
return sections.join("\n\n");
|
|
65
|
+
}
|
|
66
|
+
|
|
44
67
|
export interface StatsData {
|
|
45
68
|
turns: number | undefined;
|
|
46
69
|
toolCount: number | undefined;
|
package/src/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { Text, type TUI } from "@earendil-works/pi-tui";
|
|
|
3
3
|
import { Type } from "typebox";
|
|
4
4
|
import { SUBAGENT_PARAMS_SCHEMA } from "./tool-schema.js";
|
|
5
5
|
import { executeSubagent, executeParallel } from "./execute.js";
|
|
6
|
+
import { formatParallelResults } from "./format.js";
|
|
6
7
|
// agent-manager.js lazy-loaded below to keep subagent tool registration fast
|
|
7
8
|
import { renderSubagentResult } from "./render.js";
|
|
8
9
|
import { OVERLAY_CHROME } from "@pi-archimedes/core/overlay";
|
|
@@ -110,7 +111,7 @@ export function registerSubagent(pi: ExtensionAPI): void {
|
|
|
110
111
|
});
|
|
111
112
|
|
|
112
113
|
return {
|
|
113
|
-
content: [{ type: "text", text:
|
|
114
|
+
content: [{ type: "text", text: formatParallelResults(results) }],
|
|
114
115
|
details: {
|
|
115
116
|
mode: "parallel",
|
|
116
117
|
results,
|
|
@@ -281,17 +282,6 @@ function formatProgressSummary(progress: SubagentProgress[]): string {
|
|
|
281
282
|
return lines.join("\n");
|
|
282
283
|
}
|
|
283
284
|
|
|
284
|
-
function formatResultsSummary(results: SubagentResult[]): string {
|
|
285
|
-
const lines = results.map((r) => {
|
|
286
|
-
const status = r.exitCode === 0 ? "✓" : "✗";
|
|
287
|
-
const summary = r.progressSummary
|
|
288
|
-
? `${r.progressSummary.toolCount} tools · ${Math.round(r.progressSummary.tokens / 1000)}k tok · ${Math.round(r.progressSummary.durationMs / 1000)}s`
|
|
289
|
-
: "";
|
|
290
|
-
return `${status} ${r.agent}${summary ? " " + summary : ""}`;
|
|
291
|
-
});
|
|
292
|
-
return lines.join("\n");
|
|
293
|
-
}
|
|
294
|
-
|
|
295
285
|
// ── Command registration ────────────────────────────────────────────────────
|
|
296
286
|
|
|
297
287
|
export function registerAgentsCommand(pi: ExtensionAPI): void {
|