@pi-archimedes/subagent 0.3.2 → 0.4.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-archimedes/subagent",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
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": "0.3.2"
14
+ "@pi-archimedes/core": "0.4.0"
15
15
  },
16
16
  "peerDependencies": {
17
17
  "@earendil-works/pi-coding-agent": ">=0.1.0",
package/src/compact.ts CHANGED
@@ -9,12 +9,12 @@ type RenderContext = { state: Record<string, unknown>; invalidate: () => void };
9
9
 
10
10
  export function buildActivityLine(
11
11
  progress: {
12
- currentTool?: string;
13
- currentToolArgs?: string;
14
- currentToolStartedAt?: number;
15
- finalOutput?: string;
16
- status?: "running" | "completed" | "failed";
17
- error?: string;
12
+ currentTool: string | undefined;
13
+ currentToolArgs: string | undefined;
14
+ currentToolStartedAt: number | undefined;
15
+ finalOutput: string | undefined;
16
+ status: "running" | "completed" | "failed" | undefined;
17
+ error: string | undefined;
18
18
  },
19
19
  theme: Theme,
20
20
  ): string {
@@ -26,7 +26,7 @@ export function buildActivityLine(
26
26
 
27
27
  if (progress.currentTool) {
28
28
  const argsPreview = progress.currentToolArgs
29
- ? truncLine(progress.currentToolArgs, 60)
29
+ ? truncLine(progress.currentToolArgs ?? "", 60)
30
30
  : "";
31
31
  const durationPart = progress.currentToolStartedAt
32
32
  ? " | " + formatDuration(Date.now() - progress.currentToolStartedAt)
@@ -51,6 +51,8 @@ export function buildActivityLine(
51
51
 
52
52
  // ── Spinner helper ──────────────────────────────────────────────────────────
53
53
 
54
+ const SPINNER_INTERVAL_MS = 80;
55
+
54
56
  function getSpinnerGlyph(agentName: string, isRunning: boolean, status: string, context: RenderContext): string {
55
57
  if (isRunning) {
56
58
  const timerKey = "_subagentTimer_" + agentName;
@@ -60,24 +62,33 @@ function getSpinnerGlyph(agentName: string, isRunning: boolean, status: string,
60
62
  const timer = setInterval(() => {
61
63
  context.state[frameKey] = ((context.state[frameKey] as number) + 1) % SPINNER_FRAMES.length;
62
64
  context.invalidate();
63
- }, 80);
65
+ }, SPINNER_INTERVAL_MS);
64
66
  context.state[timerKey] = timer;
65
67
  }
66
- return SPINNER_FRAMES[context.state[frameKey] as number];
68
+ return SPINNER_FRAMES[context.state[frameKey] as number]!;
67
69
  }
68
70
  return status === "completed" ? "✓" : "✗";
69
71
  }
70
72
 
71
- function cleanupTimer(agentName: string, context: RenderContext) {
73
+ function cleanupTimer(agentName: string, context: RenderContext): void {
72
74
  const timerKey = "_subagentTimer_" + agentName;
73
75
  const frameKey = "_subagentFrame_" + agentName;
74
- if (context.state[timerKey]) {
75
- clearInterval(context.state[timerKey] as ReturnType<typeof setInterval>);
76
+ const timer = context.state[timerKey] as ReturnType<typeof setInterval> | undefined;
77
+ if (timer) {
78
+ clearInterval(timer);
76
79
  delete context.state[timerKey];
77
80
  }
78
81
  delete context.state[frameKey];
79
82
  }
80
83
 
84
+ /** Cleanup ALL timers for a given agent — idempotent. */
85
+ function cleanupAllTimers(agentName: string, context: RenderContext): void {
86
+ cleanupTimer(agentName, context);
87
+ // Also clean up start time tracker
88
+ const timeKey = "_subagentStartTime_" + agentName;
89
+ delete context.state[timeKey];
90
+ }
91
+
81
92
  // ── Compact single agent ────────────────────────────────────────────────────
82
93
 
83
94
  export function renderCompactSingle(
@@ -92,23 +103,20 @@ export function renderCompactSingle(
92
103
  const isRunning = progress?.status === "running";
93
104
  const status = isRunning ? "running" : (result.exitCode === 0 ? "completed" : "failed");
94
105
 
95
- // Manage timer for spinner on line 3
106
+ // Manage timer for spinner
96
107
  if (isRunning) {
97
108
  getSpinnerGlyph(agentName, true, "running", context);
98
109
  } else {
99
- cleanupTimer(agentName, context);
110
+ cleanupAllTimers(agentName, context);
100
111
  }
101
112
 
102
113
  // Track start time for live duration
103
114
  const timeKey = "_subagentStartTime_" + agentName;
104
- if (isRunning && !context.state[timeKey]) {
115
+ if (isRunning && context.state[timeKey] === undefined) {
105
116
  // Estimate start time from current duration
106
117
  const currentDuration = summary.durationMs || (progress?.durationMs ?? 0);
107
118
  context.state[timeKey] = Date.now() - currentDuration;
108
119
  }
109
- if (!isRunning) {
110
- delete context.state[timeKey];
111
- }
112
120
  const liveDuration = isRunning && context.state[timeKey]
113
121
  ? Date.now() - (context.state[timeKey] as number)
114
122
  : summary.durationMs;
@@ -128,10 +136,10 @@ export function renderCompactSingle(
128
136
  let activityLine: string;
129
137
  if (isRunning) {
130
138
  const spinner = SPINNER_FRAMES[(context.state["_subagentFrame_" + agentName] as number) ?? 0];
131
- const spinnerColored = theme.fg("muted", spinner);
139
+ const spinnerColored = theme.fg("muted", spinner ?? "");
132
140
  if (progress?.currentTool) {
133
141
  const argsPreview = progress.currentToolArgs
134
- ? truncLine(progress.currentToolArgs, 60)
142
+ ? truncLine(progress.currentToolArgs ?? "", 60)
135
143
  : "";
136
144
  const durationPart = progress.currentToolStartedAt
137
145
  ? " | " + formatDuration(Date.now() - progress.currentToolStartedAt)
@@ -146,7 +154,7 @@ export function renderCompactSingle(
146
154
  activityLine = spinnerColored + " " + line;
147
155
  } else if (progress?.toolCalls && progress.toolCalls.length > 0) {
148
156
  const lastCall = progress.toolCalls[progress.toolCalls.length - 1];
149
- activityLine = theme.fg("dim", " ⎿ ") + theme.fg("muted", lastCall);
157
+ activityLine = theme.fg("dim", " ⎿ ") + theme.fg("muted", lastCall ?? "");
150
158
  } else {
151
159
  activityLine = theme.fg("muted", " ⎿ Working...");
152
160
  }
@@ -182,9 +190,17 @@ export function renderCompactParallel(
182
190
  const progress = details.progress?.[i];
183
191
  const agentName = result.agent ?? "agent-" + i;
184
192
  const summary = result.progressSummary ?? { toolCount: 0, tokens: 0, durationMs: 0 };
193
+ const isRunning = progress?.status === "running";
185
194
  const status = progress?.status ?? (result.exitCode === 0 ? "completed" : "failed");
186
195
 
187
- let glyph = status === "completed" ? "✓" : status === "failed" ? "✗" : "⠋";
196
+ // Use animated spinner for running agents
197
+ let glyph: string;
198
+ if (isRunning) {
199
+ glyph = getSpinnerGlyph(agentName, true, "running", context);
200
+ } else {
201
+ cleanupTimer(agentName, context);
202
+ glyph = status === "completed" ? "✓" : "✗";
203
+ }
188
204
  const glyphColored = status === "completed"
189
205
  ? theme.fg("success", glyph)
190
206
  : status === "failed"
@@ -243,13 +259,13 @@ export function renderCompactProgress(
243
259
 
244
260
  // Track start time for live duration
245
261
  const timeKey = "_subagentStartTime_" + agentName;
246
- if (isRunning && !context.state[timeKey]) {
262
+ if (isRunning && context.state[timeKey] === undefined) {
247
263
  context.state[timeKey] = Date.now() - (progress.durationMs ?? 0);
248
264
  }
249
265
  if (!isRunning) {
250
- delete context.state[timeKey];
266
+ cleanupAllTimers(agentName, context);
251
267
  }
252
- const liveDuration = isRunning && context.state[timeKey]
268
+ const liveDuration = isRunning && context.state[timeKey] !== undefined
253
269
  ? Date.now() - (context.state[timeKey] as number)
254
270
  : progress.durationMs;
255
271
 
@@ -266,10 +282,10 @@ export function renderCompactProgress(
266
282
  let activityLine: string;
267
283
  if (isRunning) {
268
284
  const spinner = SPINNER_FRAMES[(context.state["_subagentFrame_" + agentName] as number) ?? 0];
269
- const spinnerColored = theme.fg("muted", spinner);
285
+ const spinnerColored = theme.fg("muted", spinner ?? "");
270
286
  if (progress.currentTool) {
271
287
  const argsPreview = progress.currentToolArgs
272
- ? truncLine(progress.currentToolArgs, 60)
288
+ ? truncLine(progress.currentToolArgs ?? "", 60)
273
289
  : "";
274
290
  const durationPart = progress.currentToolStartedAt
275
291
  ? " | " + formatDuration(Date.now() - progress.currentToolStartedAt)
@@ -284,7 +300,7 @@ export function renderCompactProgress(
284
300
  activityLine = spinnerColored + " " + line;
285
301
  } else if (progress.toolCalls && progress.toolCalls.length > 0) {
286
302
  const lastCall = progress.toolCalls[progress.toolCalls.length - 1];
287
- activityLine = spinnerColored + " " + theme.fg("muted", lastCall);
303
+ activityLine = spinnerColored + " " + theme.fg("muted", lastCall ?? "");
288
304
  } else {
289
305
  activityLine = spinnerColored + " " + theme.fg("muted", "Working...");
290
306
  }
package/src/execute.ts CHANGED
@@ -4,12 +4,12 @@ import { emitCostUpdate } from "./cost.js";
4
4
  import type { SubagentProgress, SubagentResult, SubagentUsage } from "./types.js";
5
5
 
6
6
  export interface ExecuteOptions {
7
- agent?: string;
7
+ agent: string | undefined;
8
8
  task: string;
9
- model?: string;
10
- cwd?: string;
11
- signal?: AbortSignal;
12
- onUpdate?: (progress: SubagentProgress) => void;
9
+ model: string | undefined;
10
+ cwd: string | undefined;
11
+ signal: AbortSignal | undefined;
12
+ onUpdate: ((progress: SubagentProgress) => void) | undefined;
13
13
  }
14
14
 
15
15
  /**
@@ -80,7 +80,10 @@ export async function executeSubagent(options: ExecuteOptions): Promise<Subagent
80
80
  cost: 0,
81
81
  turns: 0,
82
82
  } as SubagentUsage,
83
+ model: undefined,
84
+ finalOutput: undefined,
83
85
  error: err instanceof Error ? err.message : String(err),
86
+ progress: undefined,
84
87
  progressSummary: { toolCount: 0, tokens: 0, durationMs: Date.now() - startTime },
85
88
  };
86
89
  }
@@ -90,19 +93,23 @@ export async function executeSubagent(options: ExecuteOptions): Promise<Subagent
90
93
  * Execute multiple subagents in parallel.
91
94
  */
92
95
  export async function executeParallel(options: {
93
- tasks: Array<{ agent?: string; task: string; count?: number; model?: string; cwd?: string }>;
94
- signal?: AbortSignal;
95
- onUpdate?: (progress: SubagentProgress[]) => void;
96
+ tasks: Array<{ agent: string | undefined; task: string; model: string | undefined; cwd: string | undefined }>;
97
+ signal: AbortSignal | undefined;
98
+ onUpdate: ((progress: SubagentProgress[]) => void) | undefined;
96
99
  }): Promise<SubagentResult[]> {
100
+ // Collect latest progress per agent for aggregated reporting
101
+ const latestProgress = new Map<string, SubagentProgress>();
102
+
97
103
  const results = await Promise.all(
98
104
  options.tasks.map((taskDef) =>
99
105
  executeSubagent({
100
106
  ...taskDef,
101
107
  signal: options.signal,
102
108
  onUpdate: (progress: SubagentProgress) => {
103
- // Collect all parallel progress updates
104
- // This is a simplified approach — each task fires independently
105
- options.onUpdate?.([progress]);
109
+ // Store latest progress keyed by agent name
110
+ latestProgress.set(progress.agent, progress);
111
+ // Emit aggregated progress across ALL agents
112
+ options.onUpdate?.([...latestProgress.values()]);
106
113
  },
107
114
  }),
108
115
  ),
package/src/expanded.ts CHANGED
@@ -88,6 +88,7 @@ export function renderProgressExpanded(
88
88
  // Stats line (same as compact view)
89
89
  const modelLabel = progress.model ? theme.fg("accent", progress.model) : "";
90
90
  const statsLine = buildStatsLine({
91
+ turns: undefined,
91
92
  toolCount: progress.toolCount,
92
93
  tokens: progress.tokens,
93
94
  durationMs: progress.durationMs,
@@ -160,6 +161,7 @@ export function buildProgressExpandedText(
160
161
  // Stats line (same as compact view)
161
162
  const modelLabel = progress.model ? theme.fg("accent", progress.model) : "";
162
163
  const statsLine = buildStatsLine({
164
+ turns: undefined,
163
165
  toolCount: progress.toolCount,
164
166
  tokens: progress.tokens,
165
167
  durationMs: progress.durationMs,
package/src/format.ts CHANGED
@@ -9,7 +9,8 @@ export function formatTokens(n: number): string {
9
9
  }
10
10
 
11
11
  export function formatDuration(ms: number): string {
12
- if (ms < 1000) return "0s";
12
+ if (ms < 100) return "<0.1s";
13
+ if (ms < 1000) return (ms / 1000).toFixed(1) + "s";
13
14
  const seconds = Math.floor(ms / 1000);
14
15
  if (seconds < 60) return seconds + "s";
15
16
  const minutes = Math.floor(seconds / 60);
@@ -28,11 +29,11 @@ export function truncLine(text: string, width: number): string {
28
29
  }
29
30
 
30
31
  export interface StatsData {
31
- turns?: number;
32
- toolCount?: number;
33
- tokens?: number;
34
- durationMs?: number;
35
- cost?: number;
32
+ turns: number | undefined;
33
+ toolCount: number | undefined;
34
+ tokens: number | undefined;
35
+ durationMs: number | undefined;
36
+ cost: number | undefined;
36
37
  }
37
38
 
38
39
  export interface StatsTheme {
package/src/handlers.ts CHANGED
@@ -1,5 +1,10 @@
1
1
  import type { StreamState } from "./types.js";
2
2
 
3
+ // Truncation limits for previews
4
+ const ARGS_PREVIEW_MAX = 120;
5
+ const TOOL_CALLS_MAX = 50;
6
+ const RECENT_OUTPUT_MAX = 50;
7
+
3
8
  export interface JsonEvent {
4
9
  type: string;
5
10
  [key: string]: unknown;
@@ -10,14 +15,14 @@ export interface JsonEvent {
10
15
  * Generic: no hardcoded tool names.
11
16
  */
12
17
  export function extractArgsPreview(args: unknown): string {
13
- if (typeof args === "string") return args.slice(0, 120);
18
+ if (typeof args === "string") return args.slice(0, ARGS_PREVIEW_MAX);
14
19
  if (args && typeof args === "object" && !Array.isArray(args)) {
15
20
  const obj = args as Record<string, unknown>;
16
21
  const keys = Object.keys(obj);
17
22
  // Single-key object: just show the value
18
23
  if (keys.length === 1) {
19
- const v = obj[keys[0]];
20
- if (typeof v === "string") return v.slice(0, 120);
24
+ const v = obj[keys[0]!];
25
+ if (typeof v === "string") return v.slice(0, ARGS_PREVIEW_MAX);
21
26
  if (typeof v === "number" || typeof v === "boolean") return String(v);
22
27
  }
23
28
  // Multi-key: find the longest string value (likely the main payload)
@@ -27,9 +32,10 @@ export function extractArgsPreview(args: unknown): string {
27
32
  best = v;
28
33
  }
29
34
  }
30
- if (best) return best.slice(0, 120);
35
+ if (best) return best.slice(0, ARGS_PREVIEW_MAX);
31
36
  }
32
- return JSON.stringify(args)?.slice(0, 120) ?? "";
37
+ const serialized = JSON.stringify(args);
38
+ return serialized?.slice(0, ARGS_PREVIEW_MAX) ?? "";
33
39
  }
34
40
 
35
41
  /**
@@ -43,8 +49,8 @@ export function handleToolStart(state: StreamState, event: JsonEvent): void {
43
49
  // Record tool call with args preview
44
50
  const argsPreview = extractArgsPreview(event.args);
45
51
  state.toolCalls.push(`${state.currentTool}: ${argsPreview}`);
46
- if (state.toolCalls.length > 50) {
47
- state.toolCalls.splice(0, state.toolCalls.length - 50);
52
+ if (state.toolCalls.length > TOOL_CALLS_MAX) {
53
+ state.toolCalls.splice(0, state.toolCalls.length - TOOL_CALLS_MAX);
48
54
  }
49
55
  }
50
56
 
@@ -69,20 +75,20 @@ export function handleToolResult(state: StreamState, event: JsonEvent): void {
69
75
 
70
76
  if (typeof toolContent === "string" && toolContent.trim()) {
71
77
  const lines = toolContent.split("\n").filter((l) => l.trim());
72
- state.recentOutput.push(`[${toolName}] ${lines[0]?.slice(0, 120)}`);
78
+ state.recentOutput.push(`[${toolName}] ${lines[0]?.slice(0, ARGS_PREVIEW_MAX)}`);
73
79
  } else if (Array.isArray(toolContent)) {
74
80
  for (const part of toolContent) {
75
81
  if (part.type === "text" && (part.text as string)?.trim()) {
76
82
  const text = part.text as string;
77
83
  const lines = text.split("\n").filter((l) => l.trim());
78
- state.recentOutput.push(`[${toolName}] ${lines[0]?.slice(0, 120)}`);
84
+ state.recentOutput.push(`[${toolName}] ${lines[0]?.slice(0, ARGS_PREVIEW_MAX)}`);
79
85
  break;
80
86
  }
81
87
  }
82
88
  }
83
89
 
84
- if (state.recentOutput.length > 50) {
85
- state.recentOutput.splice(0, state.recentOutput.length - 50);
90
+ if (state.recentOutput.length > RECENT_OUTPUT_MAX) {
91
+ state.recentOutput.splice(0, state.recentOutput.length - RECENT_OUTPUT_MAX);
86
92
  }
87
93
  }
88
94
 
@@ -115,9 +121,9 @@ export function handleMessageEnd(state: StreamState, event: JsonEvent): void {
115
121
  }
116
122
  }
117
123
 
118
- // Cap recentOutput at 50 lines
119
- if (state.recentOutput.length > 50) {
120
- state.recentOutput.splice(0, state.recentOutput.length - 50);
124
+ // Cap recentOutput
125
+ if (state.recentOutput.length > RECENT_OUTPUT_MAX) {
126
+ state.recentOutput.splice(0, state.recentOutput.length - RECENT_OUTPUT_MAX);
121
127
  }
122
128
 
123
129
  // Extract usage (turnCount tracked via turn_start in stream.ts)
package/src/index.ts CHANGED
@@ -15,7 +15,6 @@ import type {
15
15
  const TaskItem = Type.Object({
16
16
  agent: Type.Optional(Type.String()),
17
17
  task: Type.String(),
18
- count: Type.Optional(Type.Number()),
19
18
  model: Type.Optional(Type.String()),
20
19
  cwd: Type.Optional(Type.String()),
21
20
  });
@@ -75,8 +74,13 @@ export function registerSubagent(pi: ExtensionAPI): void {
75
74
  // Parallel mode
76
75
  if (params.tasks && params.tasks.length > 0) {
77
76
  const results: SubagentResult[] = await executeParallel({
78
- tasks: params.tasks,
79
- signal,
77
+ tasks: params.tasks.map((t) => ({
78
+ agent: t.agent ?? undefined,
79
+ task: t.task,
80
+ model: t.model ?? undefined,
81
+ cwd: t.cwd ?? undefined,
82
+ })),
83
+ signal: signal ?? undefined,
80
84
  onUpdate: (progress: SubagentProgress[]) => {
81
85
  onUpdate?.({
82
86
  content: [],
@@ -102,11 +106,11 @@ export function registerSubagent(pi: ExtensionAPI): void {
102
106
  // Single mode
103
107
  if (params.task) {
104
108
  const result: SubagentResult = await executeSubagent({
105
- agent: params.agent,
109
+ agent: params.agent ?? undefined,
106
110
  task: params.task,
107
- model: params.model,
108
- cwd: params.cwd,
109
- signal,
111
+ model: params.model ?? undefined,
112
+ cwd: params.cwd ?? undefined,
113
+ signal: signal ?? undefined,
110
114
  onUpdate: (progress: SubagentProgress) => {
111
115
  onUpdate?.({
112
116
  content: [],
@@ -135,6 +139,7 @@ export function registerSubagent(pi: ExtensionAPI): void {
135
139
  details: {
136
140
  mode: "single",
137
141
  results: [],
142
+ progress: undefined,
138
143
  },
139
144
  isError: true,
140
145
  };
package/src/render.ts CHANGED
@@ -40,7 +40,7 @@ export function renderSubagentResult(
40
40
  // ── Streaming progress (results empty but progress has data) ──────────
41
41
  if (details.results.length === 0 && details.progress && details.progress.length > 0) {
42
42
  if (details.progress.length === 1) {
43
- return renderProgressUpdate(text, details.progress[0], expanded, theme, context);
43
+ return renderProgressUpdate(text, details.progress[0]!, expanded, theme, context);
44
44
  }
45
45
  return renderProgressUpdatesParallel(text, details, expanded, theme, context);
46
46
  }
@@ -52,7 +52,7 @@ export function renderSubagentResult(
52
52
 
53
53
  // ── Single agent ────────────────────────────────────────────────────────
54
54
  if (details.mode === "single" || details.results.length === 1) {
55
- return renderSingleAgent(text, details.results[0], details.progress?.[0], expanded, theme, context);
55
+ return renderSingleAgent(text, details.results[0]!, details.progress?.[0], expanded, theme, context);
56
56
  }
57
57
 
58
58
  // ── Parallel agents ─────────────────────────────────────────────────────
package/src/spawn.ts CHANGED
@@ -4,9 +4,9 @@ import { existsSync } from "node:fs";
4
4
 
5
5
  export interface SpawnOptions {
6
6
  task: string;
7
- model?: string;
8
- cwd?: string;
9
- signal?: AbortSignal;
7
+ model: string | undefined;
8
+ cwd: string | undefined;
9
+ signal: AbortSignal | undefined;
10
10
  }
11
11
 
12
12
  /**
@@ -69,5 +69,13 @@ export function spawnSubagent(options: SpawnOptions): ChildProcess {
69
69
  options.signal.addEventListener("abort", cleanup, { once: true });
70
70
  }
71
71
 
72
+ // Clean up on normal exit to prevent forceKill firing after process ends
73
+ const exitCleanup = (): void => {
74
+ child.removeListener("exit", exitCleanup);
75
+ child.removeListener("error", exitCleanup);
76
+ };
77
+ child.on("exit", exitCleanup);
78
+ child.on("error", exitCleanup);
79
+
72
80
  return child;
73
81
  }
package/src/types.ts CHANGED
@@ -11,24 +11,24 @@ export interface SubagentProgress {
11
11
  agent: string;
12
12
  status: "running" | "completed" | "failed";
13
13
  task: string;
14
- currentTool?: string;
15
- currentToolArgs?: string;
16
- currentToolStartedAt?: number;
14
+ currentTool: string | undefined;
15
+ currentToolArgs: string | undefined;
16
+ currentToolStartedAt: number | undefined;
17
17
  toolCount: number;
18
18
  inputTokens: number;
19
19
  outputTokens: number;
20
20
  tokens: number;
21
21
  cost: number;
22
22
  durationMs: number;
23
- error?: string;
23
+ error: string | undefined;
24
24
  /** Model used by the subagent */
25
- model?: string;
25
+ model: string | undefined;
26
26
  /** Accumulated assistant text output during streaming */
27
- output?: string;
27
+ output: string | undefined;
28
28
  /** Last N lines of assistant text for live display */
29
- recentOutput?: string[];
29
+ recentOutput: string[] | undefined;
30
30
  /** History of tool calls: "toolName: args_preview" */
31
- toolCalls?: string[];
31
+ toolCalls: string[] | undefined;
32
32
  }
33
33
 
34
34
  export interface SubagentResult {
@@ -36,11 +36,11 @@ export interface SubagentResult {
36
36
  task: string;
37
37
  exitCode: number;
38
38
  usage: SubagentUsage;
39
- model?: string;
40
- finalOutput?: string;
41
- error?: string;
42
- progress?: SubagentProgress;
43
- progressSummary?: { toolCount: number; tokens: number; durationMs: number };
39
+ model: string | undefined;
40
+ finalOutput: string | undefined;
41
+ error: string | undefined;
42
+ progress: SubagentProgress | undefined;
43
+ progressSummary: { toolCount: number; tokens: number; durationMs: number } | undefined;
44
44
  }
45
45
 
46
46
  export interface SubagentToolResult {
@@ -52,7 +52,7 @@ export interface SubagentToolResult {
52
52
  export interface SubagentDetails {
53
53
  mode: "single" | "parallel";
54
54
  results: SubagentResult[];
55
- progress?: SubagentProgress[];
55
+ progress: SubagentProgress[] | undefined;
56
56
  }
57
57
 
58
58
  /** Mutable state during streaming — shared between stream.ts and handlers.ts */