@pi-archimedes/subagent 1.2.0 → 1.2.2
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/package.json +2 -2
- package/src/compact.ts +22 -6
- package/src/execute.ts +51 -5
- package/src/index.ts +9 -7
- package/src/spawn.ts +12 -10
- package/src/stream.ts +17 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-archimedes/subagent",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package"
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
],
|
|
12
12
|
"main": "./src/index.ts",
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@pi-archimedes/core": "1.2.
|
|
14
|
+
"@pi-archimedes/core": "1.2.2"
|
|
15
15
|
},
|
|
16
16
|
"peerDependencies": {
|
|
17
17
|
"@earendil-works/pi-coding-agent": ">=0.1.0",
|
package/src/compact.ts
CHANGED
|
@@ -25,6 +25,17 @@ export function buildActivityLine(
|
|
|
25
25
|
return theme.fg("error", "✗ " + truncLine(data.error, 80));
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
// Finished subagents always show their final status — never a stale
|
|
29
|
+
// tool call from history. This keeps compact view consistent with
|
|
30
|
+
// expanded view ("✓ Done" / "✗ Failed" at the bottom).
|
|
31
|
+
if (data.status === "completed") {
|
|
32
|
+
return theme.fg("success", "✓ Done");
|
|
33
|
+
}
|
|
34
|
+
if (data.status === "failed") {
|
|
35
|
+
return theme.fg("error", "✗ Failed");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Running: show the current tool with live duration
|
|
28
39
|
if (data.currentTool) {
|
|
29
40
|
const arrow = theme.fg("muted", "↳ ");
|
|
30
41
|
const argsPreview = data.currentToolArgs
|
|
@@ -43,25 +54,24 @@ export function buildActivityLine(
|
|
|
43
54
|
return arrow + line;
|
|
44
55
|
}
|
|
45
56
|
|
|
46
|
-
//
|
|
57
|
+
// Running, no active tool: show the most recently completed tool call
|
|
47
58
|
if (data.toolCalls && data.toolCalls.length > 0) {
|
|
48
59
|
const lastCall = data.toolCalls[data.toolCalls.length - 1];
|
|
49
60
|
if (lastCall) return theme.fg("muted", "↳ " + lastCall);
|
|
50
61
|
}
|
|
51
62
|
|
|
63
|
+
// Running, no tool history: show first line of streamed output if any
|
|
52
64
|
if (data.finalOutput) {
|
|
53
65
|
const firstLine = data.finalOutput.split("\n")[0] ?? "";
|
|
54
66
|
return theme.fg("muted", "↳ " + truncLine(firstLine, 80));
|
|
55
67
|
}
|
|
56
68
|
|
|
57
|
-
// Running
|
|
69
|
+
// Running, no info yet
|
|
58
70
|
if (data.status === "running") {
|
|
59
71
|
return theme.fg("muted", "↳ Starting...");
|
|
60
72
|
}
|
|
61
73
|
|
|
62
|
-
return
|
|
63
|
-
? theme.fg("success", "✓ Done")
|
|
64
|
-
: theme.fg("error", "✗ Failed");
|
|
74
|
+
return "";
|
|
65
75
|
}
|
|
66
76
|
|
|
67
77
|
// ── Status glyph ────────────────────────────────────────────────────────────
|
|
@@ -143,7 +153,13 @@ export function renderCompactParallel(
|
|
|
143
153
|
const agentName = result.agent ?? "agent-" + i;
|
|
144
154
|
const summary = result.progressSummary ?? { toolCount: 0, tokens: 0, durationMs: 0 };
|
|
145
155
|
const isRunning = progress?.status === "running";
|
|
146
|
-
|
|
156
|
+
// Prefer result.exitCode as the source of truth for completion status
|
|
157
|
+
// (matches expanded view). Only fall back to progress.status when the
|
|
158
|
+
// subagent is still actively running. This prevents stale or misaligned
|
|
159
|
+
// progress from showing the wrong status for a finished subagent.
|
|
160
|
+
const status: "running" | "completed" | "failed" = isRunning
|
|
161
|
+
? "running"
|
|
162
|
+
: result.exitCode === 0 ? "completed" : "failed";
|
|
147
163
|
|
|
148
164
|
const glyph = statusGlyph(isRunning, status);
|
|
149
165
|
const glyphColored = status === "completed"
|
package/src/execute.ts
CHANGED
|
@@ -9,6 +9,7 @@ export interface ExecuteOptions {
|
|
|
9
9
|
agentConfig: AgentConfig | undefined;
|
|
10
10
|
task: string;
|
|
11
11
|
model: string | undefined;
|
|
12
|
+
activeModel: string | undefined;
|
|
12
13
|
cwd: string | undefined;
|
|
13
14
|
signal: AbortSignal | undefined;
|
|
14
15
|
onUpdate: ((progress: SubagentProgress) => void) | undefined;
|
|
@@ -30,6 +31,7 @@ export async function executeSubagent(options: ExecuteOptions): Promise<Subagent
|
|
|
30
31
|
const child = spawnSubagent({
|
|
31
32
|
task: options.task,
|
|
32
33
|
model: options.model,
|
|
34
|
+
activeModel: options.activeModel,
|
|
33
35
|
cwd: options.cwd,
|
|
34
36
|
signal: options.signal,
|
|
35
37
|
agent: options.agentConfig,
|
|
@@ -65,12 +67,34 @@ export async function executeSubagent(options: ExecuteOptions): Promise<Subagent
|
|
|
65
67
|
task: options.task,
|
|
66
68
|
progress: result.progress
|
|
67
69
|
? { ...result.progress, agent: agentName, durationMs }
|
|
68
|
-
:
|
|
70
|
+
: // Defensive: streamEvents should always return a progress, but if not,
|
|
71
|
+
// synthesize one so the parallel renderer stays aligned with results.
|
|
72
|
+
{
|
|
73
|
+
agent: agentName,
|
|
74
|
+
status: result.exitCode === 0 ? "completed" : "failed",
|
|
75
|
+
task: options.task,
|
|
76
|
+
currentTool: undefined,
|
|
77
|
+
currentToolArgs: undefined,
|
|
78
|
+
currentToolStartedAt: undefined,
|
|
79
|
+
toolCount: 0,
|
|
80
|
+
inputTokens: 0,
|
|
81
|
+
outputTokens: 0,
|
|
82
|
+
tokens: 0,
|
|
83
|
+
cost: 0,
|
|
84
|
+
durationMs,
|
|
85
|
+
error: undefined,
|
|
86
|
+
output: undefined,
|
|
87
|
+
recentOutput: undefined,
|
|
88
|
+
toolCalls: undefined,
|
|
89
|
+
model: result.model,
|
|
90
|
+
},
|
|
69
91
|
progressSummary: result.progressSummary
|
|
70
92
|
? { ...result.progressSummary, durationMs }
|
|
71
93
|
: { toolCount: 0, tokens: 0, durationMs },
|
|
72
94
|
};
|
|
73
95
|
} catch (err) {
|
|
96
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
97
|
+
const durationMs = Date.now() - startTime;
|
|
74
98
|
return {
|
|
75
99
|
agent: agentName,
|
|
76
100
|
task: options.task,
|
|
@@ -85,9 +109,31 @@ export async function executeSubagent(options: ExecuteOptions): Promise<Subagent
|
|
|
85
109
|
} as SubagentUsage,
|
|
86
110
|
model: undefined,
|
|
87
111
|
finalOutput: undefined,
|
|
88
|
-
error:
|
|
89
|
-
progress
|
|
90
|
-
|
|
112
|
+
error: errorMessage,
|
|
113
|
+
// Always return a valid progress object so the parallel renderer's
|
|
114
|
+
// `details.progress[i]` stays aligned with `details.results[i]`.
|
|
115
|
+
// Returning undefined here would be filtered out and cause index
|
|
116
|
+
// misalignment between results and progress in the parallel view.
|
|
117
|
+
progress: {
|
|
118
|
+
agent: agentName,
|
|
119
|
+
status: "failed",
|
|
120
|
+
task: options.task,
|
|
121
|
+
currentTool: undefined,
|
|
122
|
+
currentToolArgs: undefined,
|
|
123
|
+
currentToolStartedAt: undefined,
|
|
124
|
+
toolCount: 0,
|
|
125
|
+
inputTokens: 0,
|
|
126
|
+
outputTokens: 0,
|
|
127
|
+
tokens: 0,
|
|
128
|
+
cost: 0,
|
|
129
|
+
durationMs,
|
|
130
|
+
error: errorMessage,
|
|
131
|
+
output: undefined,
|
|
132
|
+
recentOutput: undefined,
|
|
133
|
+
toolCalls: undefined,
|
|
134
|
+
model: undefined,
|
|
135
|
+
},
|
|
136
|
+
progressSummary: { toolCount: 0, tokens: 0, durationMs },
|
|
91
137
|
};
|
|
92
138
|
}
|
|
93
139
|
}
|
|
@@ -96,7 +142,7 @@ export async function executeSubagent(options: ExecuteOptions): Promise<Subagent
|
|
|
96
142
|
* Execute multiple subagents in parallel.
|
|
97
143
|
*/
|
|
98
144
|
export async function executeParallel(options: {
|
|
99
|
-
tasks: Array<{ agent: string | undefined; agentConfig: AgentConfig | undefined; task: string; model: string | undefined; cwd: string | undefined }>;
|
|
145
|
+
tasks: Array<{ agent: string | undefined; agentConfig: AgentConfig | undefined; task: string; model: string | undefined; activeModel: string | undefined; cwd: string | undefined }>;
|
|
100
146
|
signal: AbortSignal | undefined;
|
|
101
147
|
onUpdate: ((progress: SubagentProgress[]) => void) | undefined;
|
|
102
148
|
}): Promise<SubagentResult[]> {
|
package/src/index.ts
CHANGED
|
@@ -97,9 +97,8 @@ export function registerSubagent(pi: ExtensionAPI): void {
|
|
|
97
97
|
agent: t.agent ?? undefined,
|
|
98
98
|
agentConfig: t.agent ? findAgent(agents, t.agent) : undefined,
|
|
99
99
|
task: t.task,
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
model: t.model ?? ctx.model?.id,
|
|
100
|
+
model: t.model,
|
|
101
|
+
activeModel: ctx.model?.id,
|
|
103
102
|
cwd: t.cwd ?? undefined,
|
|
104
103
|
})),
|
|
105
104
|
signal: signal ?? undefined,
|
|
@@ -120,7 +119,11 @@ export function registerSubagent(pi: ExtensionAPI): void {
|
|
|
120
119
|
details: {
|
|
121
120
|
mode: "parallel",
|
|
122
121
|
results,
|
|
123
|
-
progress
|
|
122
|
+
// progress is always defined for each result (executeSubagent synthesizes
|
|
123
|
+
// a failed-progress object in its catch block) so we keep alignment with
|
|
124
|
+
// results by index. Do NOT filter(Boolean) here — that would misalign
|
|
125
|
+
// details.progress[i] with details.results[i] in the renderer.
|
|
126
|
+
progress: results.map(r => r.progress) as SubagentProgress[],
|
|
124
127
|
},
|
|
125
128
|
};
|
|
126
129
|
}
|
|
@@ -144,9 +147,8 @@ export function registerSubagent(pi: ExtensionAPI): void {
|
|
|
144
147
|
agent: params.agent ?? undefined,
|
|
145
148
|
agentConfig,
|
|
146
149
|
task: params.task,
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
model: params.model ?? ctx.model?.id,
|
|
150
|
+
model: params.model,
|
|
151
|
+
activeModel: ctx.model?.id,
|
|
150
152
|
cwd: params.cwd ?? undefined,
|
|
151
153
|
signal: signal ?? undefined,
|
|
152
154
|
onUpdate: (progress: SubagentProgress) => {
|
package/src/spawn.ts
CHANGED
|
@@ -19,6 +19,7 @@ process.on("exit", () => {
|
|
|
19
19
|
export interface SpawnOptions {
|
|
20
20
|
task: string;
|
|
21
21
|
model: string | undefined;
|
|
22
|
+
activeModel: string | undefined;
|
|
22
23
|
cwd: string | undefined;
|
|
23
24
|
signal: AbortSignal | undefined;
|
|
24
25
|
agent: AgentConfig | undefined;
|
|
@@ -86,24 +87,25 @@ export function spawnSubagent(options: SpawnOptions): ChildProcess {
|
|
|
86
87
|
"--no-session",
|
|
87
88
|
];
|
|
88
89
|
|
|
89
|
-
//
|
|
90
|
+
// Resolve model with correct priority:
|
|
91
|
+
// 1. frontmatter model (agent.model)
|
|
92
|
+
// 2. explicit tool-call model (options.model)
|
|
93
|
+
// 3. currently active model (options.activeModel)
|
|
94
|
+
// 4. no --model flag → pi default
|
|
95
|
+
const modelToUse = options.agent?.model ?? options.model ?? options.activeModel;
|
|
96
|
+
if (modelToUse) {
|
|
97
|
+
args.push("--model", modelToUse);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Apply other agent config options
|
|
90
101
|
if (options.agent) {
|
|
91
102
|
const agent = options.agent;
|
|
92
|
-
// Agent's model takes precedence; tool-call model as fallback
|
|
93
|
-
const modelToUse = agent.model ?? options.model;
|
|
94
|
-
if (modelToUse) {
|
|
95
|
-
args.push("--model", modelToUse);
|
|
96
|
-
}
|
|
97
103
|
if (agent.thinking) {
|
|
98
104
|
args.push("--thinking", agent.thinking);
|
|
99
105
|
}
|
|
100
106
|
if (agent.tools && agent.tools.length > 0) {
|
|
101
107
|
args.push("--tools", agent.tools.join(","));
|
|
102
108
|
}
|
|
103
|
-
} else if (options.model) {
|
|
104
|
-
// Fallback: tool-call model (which itself falls back to the parent's
|
|
105
|
-
// currently-selected model via ctx.model?.id at the call site).
|
|
106
|
-
args.push("--model", options.model);
|
|
107
109
|
}
|
|
108
110
|
|
|
109
111
|
// Temp files for system prompt
|
package/src/stream.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createInterface } from "node:readline";
|
|
2
2
|
import type { ChildProcess } from "node:child_process";
|
|
3
3
|
import type { StreamState, SubagentProgress, SubagentResult } from "./types.js";
|
|
4
|
+
import { getBus, Events } from "@pi-archimedes/core/bus";
|
|
4
5
|
import {
|
|
5
6
|
type JsonEvent,
|
|
6
7
|
handleToolStart,
|
|
@@ -124,6 +125,17 @@ export function streamEvents(
|
|
|
124
125
|
case "tool_execution_start": {
|
|
125
126
|
handleToolStart(state, event);
|
|
126
127
|
emitProgress();
|
|
128
|
+
// Forward manage_todo_list writes to the bus for parent widget
|
|
129
|
+
if (event.toolName === "manage_todo_list") {
|
|
130
|
+
const args = event.args as Record<string, unknown> | undefined;
|
|
131
|
+
const todoList = args?.todoList as Array<unknown> | undefined;
|
|
132
|
+
if (Array.isArray(todoList)) {
|
|
133
|
+
getBus().emit(Events.TODOS_UPDATE, {
|
|
134
|
+
source: `subagent:${callbacks.agent ?? "general"}`,
|
|
135
|
+
todos: todoList,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
127
139
|
break;
|
|
128
140
|
}
|
|
129
141
|
case "tool_execution_end": {
|
|
@@ -193,6 +205,11 @@ export function streamEvents(
|
|
|
193
205
|
// Final progress update
|
|
194
206
|
callbacks.onProgress?.(result.progress!);
|
|
195
207
|
|
|
208
|
+
// Clear subagent todos from the bus on process exit
|
|
209
|
+
getBus().emit(Events.TODOS_CLEAR, {
|
|
210
|
+
source: `subagent:${callbacks.agent ?? "general"}`,
|
|
211
|
+
});
|
|
212
|
+
|
|
196
213
|
resolve(result);
|
|
197
214
|
});
|
|
198
215
|
|