@mystilleef/pi-subagent 0.6.0 → 0.7.0

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.
@@ -16,13 +16,17 @@ import {
16
16
  } from "@earendil-works/pi-ai";
17
17
  import type { AgentConfig, ThinkingLevel } from "../agent/agents.js";
18
18
  import { getFinalOutput } from "../output/ui.js";
19
- import { makeToolPreview } from "../progress/progress.js";
20
- import { isToolCallPart } from "../progress/progress-state.js";
19
+ import { makeToolPreview, renderToolActivity } from "../progress/progress.js";
20
+ import {
21
+ isToolCallPart,
22
+ SENSITIVE_PATTERN,
23
+ } from "../progress/progress-state.js";
21
24
  import type {
22
25
  OnUpdateCallback,
23
26
  SingleResult,
24
27
  StreamingProgress,
25
28
  SubagentDetails,
29
+ ToolActivity,
26
30
  } from "../shared/types.js";
27
31
  import {
28
32
  detectMessageError,
@@ -33,7 +37,11 @@ import {
33
37
  truncateOutput,
34
38
  writePromptToTempFile,
35
39
  } from "../shared/utils.js";
36
- import { parseChildEventLine } from "./child-events.js";
40
+ import {
41
+ type ChildKnownEvent,
42
+ parseChildEventLine,
43
+ TOOL_EXECUTION_UPDATE_EVENT,
44
+ } from "./child-events.js";
37
45
  import { appendSubagentResultContract } from "./prompt-contract.js";
38
46
  import {
39
47
  getProcessTreeSpawnOptions,
@@ -42,16 +50,6 @@ import {
42
50
 
43
51
  const MAX_STDERR_BYTES = 10_000;
44
52
  const AGENT_END_GRACE_MS = 250;
45
-
46
- function thinkingWarningFor(
47
- requested: ThinkingLevel,
48
- effective: ThinkingLevel,
49
- provider: string,
50
- modelId: string,
51
- ): string {
52
- return `Thinking level "${requested}" not supported by model "${provider}/${modelId}"; using "${effective}" instead`;
53
- }
54
-
55
53
  export function resolveThinkingLevel(
56
54
  requested: ThinkingLevel,
57
55
  provider: string,
@@ -59,11 +57,10 @@ export function resolveThinkingLevel(
59
57
  ): { level: ThinkingLevel; warning?: string } {
60
58
  const model = getModel(provider as never, modelId as never);
61
59
  if (!model) return { level: requested };
60
+ const mkWarning = (effective: ThinkingLevel) =>
61
+ `Thinking level "${requested}" not supported by model "${provider}/${modelId}"; using "${effective}" instead`;
62
62
  if (model.reasoning === false) {
63
- return {
64
- level: "off",
65
- warning: thinkingWarningFor(requested, "off", provider, modelId),
66
- };
63
+ return { level: "off", warning: mkWarning("off") };
67
64
  }
68
65
  if (!model.thinkingLevelMap) return { level: requested };
69
66
  const supported = getSupportedThinkingLevels(model);
@@ -73,17 +70,21 @@ export function resolveThinkingLevel(
73
70
  requested as ModelThinkingLevel,
74
71
  ) as ThinkingLevel;
75
72
  if (clamped === requested) return { level: requested };
76
- return {
77
- level: clamped,
78
- warning: thinkingWarningFor(requested, clamped, provider, modelId),
79
- };
73
+ return { level: clamped, warning: mkWarning(clamped) };
80
74
  }
81
75
 
82
- const MAX_SUBAGENT_DEPTH = 1;
76
+ const MAX_SUBAGENT_DEPTH = 2;
83
77
  export const TOOL_RESULT_FAILED_MESSAGE = "Subagent tool result failed.";
84
78
 
85
79
  type RuntimeResult = SingleResult & { messages: Message[] };
86
80
 
81
+ export class SubagentAbortError extends Error {
82
+ constructor(public readonly result: SingleResult) {
83
+ super("Subagent was aborted");
84
+ this.name = "SubagentAbortError";
85
+ }
86
+ }
87
+
87
88
  type TempPrompt = { dir: string; filePath: string };
88
89
 
89
90
  type PromptSetupResult = { tmpPrompt: TempPrompt | null } | { error: unknown };
@@ -96,10 +97,6 @@ interface SubagentState {
96
97
  terminationPromise?: Promise<unknown>;
97
98
  }
98
99
 
99
- /**
100
- * Appends data to a string while ensuring the result does not exceed a maximum byte limit.
101
- * Used to prevent memory exhaustion when capturing child process stderr.
102
- */
103
100
  function appendWithByteLimit(
104
101
  current: string,
105
102
  data: string,
@@ -130,9 +127,6 @@ function resolveContextWindowTokens(msg: Message): number | undefined {
130
127
  }
131
128
  }
132
129
 
133
- /**
134
- * Normalizes AbortSignal reasons into human-readable strings.
135
- */
136
130
  function getAbortReason(signal: AbortSignal): string {
137
131
  const { reason } = signal;
138
132
  if (reason instanceof Error && reason.message) return reason.message;
@@ -178,6 +172,25 @@ function getAgentEndTimeoutExitCode(
178
172
  * Safety: Implements a dual-timer strategy (idle and hard) to ensure streams
179
173
  * are destroyed and promises settled even if the process or its pipes hang.
180
174
  */
175
+ function createProcessCleanup(proc: ChildProcess, idleMs: number) {
176
+ let idleTimer: NodeJS.Timeout | undefined;
177
+ const destroyStreams = () => {
178
+ proc.stdout?.destroy();
179
+ proc.stderr?.destroy();
180
+ };
181
+ return {
182
+ armIdleTimer: () => {
183
+ if (idleTimer) clearTimeout(idleTimer);
184
+ idleTimer = setTimeout(destroyStreams, idleMs);
185
+ idleTimer.unref?.();
186
+ },
187
+ clearIdleTimer: () => {
188
+ if (idleTimer) clearTimeout(idleTimer);
189
+ },
190
+ destroyStreams,
191
+ };
192
+ }
193
+
181
194
  async function waitForSubagentProcess(
182
195
  proc: ChildProcess,
183
196
  idleMs = 100,
@@ -187,23 +200,13 @@ async function waitForSubagentProcess(
187
200
  let exitCode: number | null = null;
188
201
  let exited = false;
189
202
  let settled = false;
190
- let idleTimer: NodeJS.Timeout | undefined;
203
+ const cleanup = createProcessCleanup(proc, idleMs);
191
204
  const done = () => {
192
205
  if (settled) return;
193
206
  settled = true;
194
- if (idleTimer) clearTimeout(idleTimer);
207
+ cleanup.clearIdleTimer();
195
208
  resolve(exitCode);
196
209
  };
197
- const destroyStreams = () => {
198
- proc.stdout?.destroy();
199
- proc.stderr?.destroy();
200
- };
201
- const armIdleTimer = () => {
202
- if (!exited) return;
203
- if (idleTimer) clearTimeout(idleTimer);
204
- idleTimer = setTimeout(destroyStreams, idleMs);
205
- idleTimer.unref?.();
206
- };
207
210
  proc.on("close", done);
208
211
  proc.on("error", () => {
209
212
  exitCode = 1;
@@ -213,12 +216,15 @@ async function waitForSubagentProcess(
213
216
  proc.on("exit", (code) => {
214
217
  exitCode = code;
215
218
  exited = true;
216
- armIdleTimer();
217
- const hardTimer = setTimeout(destroyStreams, hardMs);
219
+ cleanup.armIdleTimer();
220
+ const hardTimer = setTimeout(cleanup.destroyStreams, hardMs);
218
221
  hardTimer.unref?.();
219
222
  });
220
- proc.stdout?.on("data", armIdleTimer);
221
- proc.stderr?.on("data", armIdleTimer);
223
+ const onStreamData = () => {
224
+ if (exited) cleanup.armIdleTimer();
225
+ };
226
+ proc.stdout?.on("data", onStreamData);
227
+ proc.stderr?.on("data", onStreamData);
222
228
  });
223
229
  }
224
230
 
@@ -232,6 +238,16 @@ function buildModelDisplay(
232
238
  return thinking ? `thinking:${thinking}` : undefined;
233
239
  }
234
240
 
241
+ const EMPTY_USAGE = {
242
+ input: 0,
243
+ output: 0,
244
+ cacheRead: 0,
245
+ cacheWrite: 0,
246
+ cost: 0,
247
+ contextTokens: 0,
248
+ turns: 0,
249
+ };
250
+
235
251
  function initRuntimeResult(
236
252
  agentName: string,
237
253
  source: "user" | "project" | "unknown",
@@ -246,19 +262,26 @@ function initRuntimeResult(
246
262
  finalOutput: "",
247
263
  messages: [],
248
264
  stderr: "",
249
- usage: {
250
- input: 0,
251
- output: 0,
252
- cacheRead: 0,
253
- cacheWrite: 0,
254
- cost: 0,
255
- contextTokens: 0,
256
- turns: 0,
257
- },
265
+ usage: { ...EMPTY_USAGE },
258
266
  model: modelDisplay,
259
267
  };
260
268
  }
261
269
 
270
+ function accumulateUsage(result: RuntimeResult, msg: Message): void {
271
+ if (msg.role !== "assistant") return;
272
+ result.usage.turns++;
273
+ const { usage } = msg;
274
+ if (!usage) return;
275
+ result.usage.input += usage.input || 0;
276
+ result.usage.output += usage.output || 0;
277
+ result.usage.cacheRead += usage.cacheRead || 0;
278
+ result.usage.cacheWrite += usage.cacheWrite || 0;
279
+ result.usage.cost += usage.cost?.total || 0;
280
+ result.usage.contextTokens = usage.totalTokens || 0;
281
+ result.usage.contextWindowTokens =
282
+ resolveContextWindowTokens(msg) ?? result.usage.contextWindowTokens;
283
+ }
284
+
262
285
  function addMessageToResult(result: RuntimeResult, msg: Message): void {
263
286
  result.messages.push(msg);
264
287
  result.finalOutput = truncateOutput(getFinalOutput(result.messages));
@@ -267,27 +290,14 @@ function addMessageToResult(result: RuntimeResult, msg: Message): void {
267
290
  } else if (result.errorMessage === TOOL_RESULT_FAILED_MESSAGE) {
268
291
  result.errorMessage = undefined;
269
292
  }
270
- if (msg.role !== "assistant") return;
271
- result.usage.turns++;
272
- const { usage } = msg;
273
- if (usage) {
274
- result.usage.input += usage.input || 0;
275
- result.usage.output += usage.output || 0;
276
- result.usage.cacheRead += usage.cacheRead || 0;
277
- result.usage.cacheWrite += usage.cacheWrite || 0;
278
- result.usage.cost += usage.cost?.total || 0;
279
- result.usage.contextTokens = usage.totalTokens || 0;
280
- result.usage.contextWindowTokens =
281
- resolveContextWindowTokens(msg) ?? result.usage.contextWindowTokens;
293
+ if (msg.role === "assistant") {
294
+ accumulateUsage(result, msg);
295
+ if (!result.model && msg.model) result.model = msg.model;
296
+ if (msg.stopReason) result.stopReason = msg.stopReason;
297
+ if (msg.errorMessage) result.errorMessage = msg.errorMessage;
282
298
  }
283
- if (!result.model && msg.model) result.model = msg.model;
284
- if (msg.stopReason) result.stopReason = msg.stopReason;
285
- if (msg.errorMessage) result.errorMessage = msg.errorMessage;
286
299
  }
287
300
 
288
- /**
289
- * Standardized error result generator.
290
- */
291
301
  function createErrorResult(
292
302
  agent: string,
293
303
  source: "user" | "project" | "unknown",
@@ -302,15 +312,7 @@ function createErrorResult(
302
312
  exitCode: 1,
303
313
  finalOutput: "",
304
314
  stderr: error,
305
- usage: {
306
- input: 0,
307
- output: 0,
308
- cacheRead: 0,
309
- cacheWrite: 0,
310
- cost: 0,
311
- contextTokens: 0,
312
- turns: 0,
313
- },
315
+ usage: { ...EMPTY_USAGE },
314
316
  model,
315
317
  };
316
318
  }
@@ -388,10 +390,13 @@ function findRecentMessagesAnchor(messages: Message[]): number {
388
390
  /**
389
391
  * Derives current execution progress from accumulated messages.
390
392
  * Maps tool calls to UI-safe previews for real-time feedback.
393
+ * Builds activeToolActivity from the most recent tool call, providing
394
+ * a compact parent summary for subagent tools before nested child data arrives.
391
395
  */
392
396
  function deriveStreamingProgress(messages: Message[]): StreamingProgress {
393
397
  const toolCalls: { id: string; preview: string }[] = [];
394
398
  let lastToolPreview: string | undefined;
399
+ let activeToolActivity: ToolActivity | undefined;
395
400
  for (const msg of messages) {
396
401
  if (msg.role !== "assistant" || !Array.isArray(msg.content)) continue;
397
402
  for (const part of msg.content) {
@@ -402,9 +407,15 @@ function deriveStreamingProgress(messages: Message[]): StreamingProgress {
402
407
  );
403
408
  toolCalls.push({ id: part.id, preview });
404
409
  lastToolPreview = preview;
410
+ activeToolActivity = { toolName: part.name, inputSummary: preview };
405
411
  }
406
412
  }
407
- return { activityText: lastToolPreview, toolCalls, lastToolPreview };
413
+ return {
414
+ activeToolActivity,
415
+ activityText: renderToolActivity(activeToolActivity),
416
+ toolCalls,
417
+ lastToolPreview,
418
+ };
408
419
  }
409
420
 
410
421
  /**
@@ -412,23 +423,62 @@ function deriveStreamingProgress(messages: Message[]): StreamingProgress {
412
423
  * Redacts values if the preview contains sensitive keywords.
413
424
  */
414
425
  function sanitizeProgressPreview(preview: string, toolName: string): string {
415
- return /secret|token|password/i.test(preview) ? toolName : preview;
426
+ return SENSITIVE_PATTERN.test(preview) ? toolName : preview;
416
427
  }
417
428
 
418
- function makeEmitUpdate(
429
+ export function makeEmitUpdate(
419
430
  result: RuntimeResult,
420
431
  onUpdate: OnUpdateCallback | undefined,
421
432
  makeDetails: (
422
433
  results: RuntimeResult[],
423
434
  options?: { includeMessages?: boolean; recentMessages?: Message[] },
424
435
  ) => SubagentDetails,
425
- ): () => void {
426
- return () => {
436
+ ): (options?: {
437
+ toolActivity?: ToolActivity;
438
+ toolResultCompleted?: boolean;
439
+ }) => void {
440
+ return (options) => {
427
441
  const msgs = result.messages;
428
442
  const anchorIdx = findRecentMessagesAnchor(msgs);
429
443
  const recentMessages =
430
444
  anchorIdx >= 0 ? msgs.slice(anchorIdx) : msgs.slice(-5);
431
445
  const progress = deriveStreamingProgress(msgs);
446
+ // Preserve stored activity tree for tool-result completion signals
447
+ // so the parent retains nested context until newer activity arrives
448
+ if (options?.toolResultCompleted && result.progress?.activeToolActivity) {
449
+ progress.activeToolActivity = result.progress.activeToolActivity;
450
+ progress.activityText = renderToolActivity(progress.activeToolActivity);
451
+ }
452
+ // Handle parsed tool activity from child events
453
+ // Merge with parent activity if this is a nested update
454
+ if (options?.toolActivity) {
455
+ if (
456
+ progress.activeToolActivity &&
457
+ progress.activeToolActivity.toolName === options.toolActivity.toolName
458
+ ) {
459
+ // Merge: prefer parser inputSummary when non-empty and richer than bare toolName fallback
460
+ const incomingSummary = options.toolActivity.inputSummary;
461
+ const preferIncoming =
462
+ incomingSummary && incomingSummary !== options.toolActivity.toolName;
463
+ progress.activeToolActivity = {
464
+ ...progress.activeToolActivity,
465
+ inputSummary: preferIncoming
466
+ ? incomingSummary
467
+ : progress.activeToolActivity.inputSummary,
468
+ instanceName:
469
+ options.toolActivity.instanceName ??
470
+ progress.activeToolActivity.instanceName,
471
+ child:
472
+ options.toolActivity.child ?? progress.activeToolActivity.child,
473
+ };
474
+ } else {
475
+ progress.activeToolActivity = options.toolActivity;
476
+ }
477
+ progress.activityText = renderToolActivity(progress.activeToolActivity);
478
+ }
479
+ if (options?.toolResultCompleted) {
480
+ progress.toolResultCompleted = true;
481
+ }
432
482
  result.progress = progress;
433
483
  onUpdate?.({
434
484
  content: [
@@ -468,22 +518,42 @@ function clearGraceTimer(state: SubagentState): void {
468
518
  state.agentEndGraceTimer = undefined;
469
519
  }
470
520
 
471
- function processEventLine(
472
- line: string,
521
+ function handleMessageEvent(
522
+ event: ChildKnownEvent,
473
523
  state: SubagentState,
474
- emitUpdate: () => void,
475
- requestTermination: (reason: string) => Promise<unknown>,
524
+ emitUpdate: (options?: {
525
+ toolActivity?: ToolActivity;
526
+ toolResultCompleted?: boolean;
527
+ }) => void,
476
528
  ): void {
477
- const parseResult = parseChildEventLine(line);
478
- if (parseResult.kind !== "known") return;
479
- const { event } = parseResult;
480
- if (
481
- (event.type === "message_end" || event.type === "tool_result_end") &&
482
- event.message
483
- ) {
529
+ if (event.type !== "message_end" && event.type !== "tool_result_end") return;
530
+ if (event.message) {
484
531
  addMessageToResult(state.result, event.message as Message);
485
- emitUpdate();
532
+ const toolResultCompleted = event.type === "tool_result_end";
533
+ emitUpdate({ toolResultCompleted });
486
534
  }
535
+ }
536
+
537
+ function handleToolExecutionUpdateEvent(
538
+ event: ChildKnownEvent,
539
+ emitUpdate: (options?: {
540
+ toolActivity?: ToolActivity;
541
+ toolResultCompleted?: boolean;
542
+ }) => void,
543
+ ): void {
544
+ if (event.type !== TOOL_EXECUTION_UPDATE_EVENT) return;
545
+ emitUpdate({ toolActivity: event.toolActivity });
546
+ }
547
+
548
+ function handleAgentEndEvent(
549
+ event: ChildKnownEvent,
550
+ state: SubagentState,
551
+ emitUpdate: (options?: {
552
+ toolActivity?: ToolActivity;
553
+ toolResultCompleted?: boolean;
554
+ }) => void,
555
+ requestTermination: (reason: string) => Promise<unknown>,
556
+ ): void {
487
557
  if (event.type !== "agent_end") return;
488
558
  if (state.result.messages.length === 0 && Array.isArray(event.messages)) {
489
559
  for (const msg of event.messages as Message[]) {
@@ -499,6 +569,23 @@ function processEventLine(
499
569
  state.agentEndGraceTimer.unref?.();
500
570
  }
501
571
 
572
+ function processEventLine(
573
+ line: string,
574
+ state: SubagentState,
575
+ emitUpdate: (options?: {
576
+ toolActivity?: ToolActivity;
577
+ toolResultCompleted?: boolean;
578
+ }) => void,
579
+ requestTermination: (reason: string) => Promise<unknown>,
580
+ ): void {
581
+ const parseResult = parseChildEventLine(line);
582
+ if (parseResult.kind !== "known") return;
583
+ const { event } = parseResult;
584
+ handleMessageEvent(event, state, emitUpdate);
585
+ handleToolExecutionUpdateEvent(event, emitUpdate);
586
+ handleAgentEndEvent(event, state, emitUpdate, requestTermination);
587
+ }
588
+
502
589
  function setupAbortHandler(
503
590
  signal: AbortSignal | undefined,
504
591
  state: SubagentState,
@@ -547,7 +634,10 @@ function buildPiArgs(
547
634
  function setupChildProcess(
548
635
  proc: ChildProcess,
549
636
  state: SubagentState,
550
- emitUpdate: () => void,
637
+ emitUpdate: (options?: {
638
+ toolActivity?: ToolActivity;
639
+ toolResultCompleted?: boolean;
640
+ }) => void,
551
641
  requestTermination: (reason: string) => Promise<unknown>,
552
642
  ): void {
553
643
  proc.once("error", (error) => {
@@ -594,7 +684,7 @@ async function finalizeResult(
594
684
  if (agentEndTimeoutExitCode !== undefined) {
595
685
  state.result.exitCode = agentEndTimeoutExitCode;
596
686
  }
597
- if (state.wasAborted) throw new Error("Subagent was aborted");
687
+ if (state.wasAborted) throw new SubagentAbortError(state.result);
598
688
  return state.result;
599
689
  }
600
690
 
@@ -606,7 +696,7 @@ async function finalizeResult(
606
696
  * the main conversation.
607
697
  *
608
698
  * Safety:
609
- * - Enforces a strict recursion limit (depth 1) via environment variables.
699
+ * - Enforces a strict recursion limit (depth 2) via environment variables.
610
700
  * - Uses temporary prompt files to pass large system prompts without shell limits.
611
701
  * - Streams JSON events from the child to provide real-time UI updates to the parent.
612
702
  * - Implements aggressive process tree termination to prevent orphan processes.
package/src/index.ts CHANGED
@@ -6,16 +6,17 @@ import {
6
6
  getCachedAgentCompletions,
7
7
  resetAgentDiscoveryCache,
8
8
  } from "./agent/agent-cache.js";
9
+ import { isDirectoryAsync } from "./agent/agents.js";
9
10
  import { cancelSubagentCommandHandler } from "./orchestration/cancel-command.js";
10
11
  import { jobsCommandHandler } from "./orchestration/jobs-command.js";
11
12
  import { renderSubagentResultMessage } from "./orchestration/run.js";
12
13
  import { runCommandHandler } from "./orchestration/run-command.js";
13
14
  import {
14
- formatStartJobStatus,
15
+ formatSubagentToolResult,
15
16
  SubagentParams,
16
17
  startSubagentJob,
17
18
  } from "./orchestration/subagent-orchestrator.js";
18
- import { renderSubagentCall, renderSubagentResult } from "./output/ui.js";
19
+ import { renderSubagentCall, renderSubagentToolResult } from "./output/ui.js";
19
20
  import { renderSubagentProgress } from "./progress/progress.js";
20
21
 
21
22
  export { SubagentParams };
@@ -24,15 +25,40 @@ export function resetAgentCache() {
24
25
  resetAgentDiscoveryCache();
25
26
  }
26
27
 
28
+ function normalizeWorkspaceRoot(cwd: string | undefined): string | undefined {
29
+ if (typeof cwd !== "string") return undefined;
30
+ const root = cwd.trim();
31
+ return root.length > 0 ? root : undefined;
32
+ }
33
+
27
34
  export default function registerSubagentExtension(pi: ExtensionAPI) {
35
+ let activeWorkspaceRoot: string | undefined;
36
+ const setActiveWorkspaceRoot = (
37
+ cwd: string | undefined,
38
+ fallback?: string,
39
+ ) => {
40
+ activeWorkspaceRoot = normalizeWorkspaceRoot(cwd ?? fallback);
41
+ };
42
+ const getRunArgumentCompletions = async (prefix: string) => {
43
+ if (!activeWorkspaceRoot) return [];
44
+ if (!(await isDirectoryAsync(activeWorkspaceRoot))) return [];
45
+ return getCachedAgentCompletions(prefix, activeWorkspaceRoot);
46
+ };
47
+ pi.on("resources_discover", (event, ctx) => {
48
+ setActiveWorkspaceRoot(ctx.cwd, event.cwd);
49
+ });
50
+ pi.on("session_start", (_event, ctx) => {
51
+ setActiveWorkspaceRoot(ctx.cwd);
52
+ });
28
53
  pi.registerMessageRenderer("subagent-progress", renderSubagentProgress);
29
54
  pi.registerMessageRenderer("subagent-result", renderSubagentResultMessage);
30
55
  pi.registerCommand("run", {
31
56
  description: "Run a subagent directly: /run <agent> [task]",
32
- getArgumentCompletions: (prefix: string) =>
33
- getCachedAgentCompletions(prefix),
34
- handler: async (args, ctx) =>
35
- runCommandHandler(pi, ctx as ExtensionContext, args),
57
+ getArgumentCompletions: getRunArgumentCompletions,
58
+ handler: async (args, ctx) => {
59
+ setActiveWorkspaceRoot(ctx.cwd);
60
+ return runCommandHandler(pi, ctx as ExtensionContext, args);
61
+ },
36
62
  });
37
63
  pi.registerCommand("cancel-subagent", {
38
64
  description:
@@ -48,28 +74,21 @@ export default function registerSubagentExtension(pi: ExtensionAPI) {
48
74
  label: "Subagent",
49
75
  description: "Delegate a task to a subagent with isolated context.",
50
76
  parameters: SubagentParams,
51
- async execute(_toolCallId, params, signal, _onUpdate, ctx) {
77
+ async execute(_toolCallId, params, signal, onUpdate, ctx) {
52
78
  const result = await startSubagentJob(
53
79
  pi,
54
80
  ctx,
55
81
  params,
56
82
  signal ?? undefined,
83
+ onUpdate ?? undefined,
57
84
  );
58
- return {
59
- content: [
60
- {
61
- type: "text" as const,
62
- text: formatStartJobStatus(params.agent, result),
63
- },
64
- ],
65
- details: result.makeDetails([]),
66
- };
85
+ return formatSubagentToolResult(params.agent, result);
67
86
  },
68
87
  renderCall(args, theme, _context) {
69
88
  return renderSubagentCall(args, theme);
70
89
  },
71
90
  renderResult(result, display, theme, _context) {
72
- return renderSubagentResult(result, theme, display);
91
+ return renderSubagentToolResult(result, theme, display);
73
92
  },
74
93
  });
75
94
  }