@mystilleef/pi-subagent 0.6.0 → 0.8.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.
@@ -1,3 +1,4 @@
1
+ import type { AgentToolUpdateCallback } from "@earendil-works/pi-agent-core";
1
2
  import { type Message, StringEnum } from "@earendil-works/pi-ai";
2
3
  import type {
3
4
  ExtensionAPI,
@@ -10,7 +11,7 @@ import type {
10
11
  AgentScope,
11
12
  ThinkingLevel,
12
13
  } from "../agent/agents.js";
13
- import { runSingleAgent } from "../child/process.js";
14
+ import { runSingleAgent, SubagentAbortError } from "../child/process.js";
14
15
  import { formatSubagentResultForParent } from "../output/summary.js";
15
16
  import {
16
17
  cancelProgressState,
@@ -22,6 +23,7 @@ import {
22
23
  import {
23
24
  createSubagentError,
24
25
  getFeedbackSummaryText,
26
+ getLatestResult,
25
27
  getResultDisplayText,
26
28
  hasSubagentFailed,
27
29
  patchProgressFromDetails,
@@ -34,6 +36,7 @@ import type {
34
36
  SubagentDetails,
35
37
  SubagentToolResult,
36
38
  } from "../shared/types.js";
39
+ import { getSubagentDepth } from "../shared/utils.js";
37
40
  import {
38
41
  listRunJobs,
39
42
  type RunJob,
@@ -74,6 +77,22 @@ type DetailsBuilder = (
74
77
  options?: DetailsOptions,
75
78
  ) => SubagentDetails;
76
79
 
80
+ interface LifecycleContext {
81
+ pi: ExtensionAPI;
82
+ ctx: ExtensionContext;
83
+ requestId: string;
84
+ job: RunJob;
85
+ debug: boolean;
86
+ makeDetails: DetailsBuilder;
87
+ mergedSignal: AbortSignal;
88
+ agents: AgentConfig[];
89
+ agentName: string;
90
+ task: string;
91
+ parentModel: { provider: string; id: string } | undefined;
92
+ parentThinking: ThinkingLevel;
93
+ hostOnUpdate?: AgentToolUpdateCallback<SubagentDetails>;
94
+ }
95
+
77
96
  function createDetailsBuilder(
78
97
  agentScope: AgentScope,
79
98
  projectAgentsDir: string | null,
@@ -108,14 +127,22 @@ function sanitizeResultDetails(
108
127
  contextWindowTokens;
109
128
  }
110
129
  if (progress !== undefined) {
111
- const { activityText, lastToolPreview, ...progBase } = progress;
130
+ const {
131
+ activityText,
132
+ activeToolActivity,
133
+ lastToolPreview,
134
+ toolResultCompleted,
135
+ ...progBase
136
+ } = progress;
112
137
  sanitized.progress = {
113
138
  toolCalls: progBase.toolCalls.map((tc) => ({
114
139
  id: tc.id,
115
140
  preview: tc.preview,
116
141
  })),
117
142
  ...(activityText !== undefined && { activityText }),
143
+ ...(activeToolActivity !== undefined && { activeToolActivity }),
118
144
  ...(lastToolPreview !== undefined && { lastToolPreview }),
145
+ ...(toolResultCompleted !== undefined && { toolResultCompleted }),
119
146
  };
120
147
  }
121
148
  if (includeMessages) {
@@ -177,93 +204,136 @@ export function emitCompletionAlert(
177
204
  process.stdout.write("\x07");
178
205
  }
179
206
 
180
- async function runSubagentWorker(
181
- pi: ExtensionAPI,
182
- ctx: ExtensionContext,
183
- agents: AgentConfig[],
184
- agentName: string,
185
- task: string,
186
- debug: boolean,
187
- parentModel: { provider: string; id: string } | undefined,
188
- parentThinking: ThinkingLevel,
189
- makeDetails: DetailsBuilder,
190
- requestId: string,
191
- job: RunJob,
192
- mergedSignal: AbortSignal,
193
- ): Promise<void> {
207
+ function createCompletedToolResult(
208
+ content: string,
209
+ details: SubagentDetails,
210
+ ): SubagentToolResult {
211
+ return {
212
+ content: [{ type: "text", text: content }],
213
+ details: { ...details, renderedByMessage: true },
214
+ };
215
+ }
216
+
217
+ function finishLifecycleFailure(
218
+ lc: LifecycleContext,
219
+ errorMessage: string,
220
+ details: SubagentDetails,
221
+ ): SubagentToolResult {
222
+ const displayDetails = sanitizeDetailsForDisplay(details, lc.debug);
223
+ if (lc.mergedSignal.aborted) {
224
+ cancelProgressState(lc.requestId, lc.job.cancelReason ?? errorMessage);
225
+ if (getSubagentDepth() > 0) {
226
+ sendSubagentResultMessage(lc.pi, "Canceled", displayDetails);
227
+ }
228
+ return createCompletedToolResult("Canceled", displayDetails);
229
+ }
230
+ failProgressState(lc.requestId, errorMessage);
231
+ lc.ctx.ui?.notify(errorMessage, "error");
232
+ const latestResult = getLatestResult(details);
233
+ const content = latestResult
234
+ ? formatSubagentResultForParent(latestResult) || "(failed)"
235
+ : errorMessage;
236
+ sendSubagentResultMessage(lc.pi, content, displayDetails);
237
+ return createCompletedToolResult(content, displayDetails);
238
+ }
239
+
240
+ function finishLifecycleResult(
241
+ lc: LifecycleContext,
242
+ result: SingleResult,
243
+ ): SubagentToolResult {
244
+ const details = lc.makeDetails([result]);
245
+ if (hasSubagentFailed(result)) {
246
+ return finishLifecycleFailure(
247
+ lc,
248
+ lc.mergedSignal.aborted ? "Aborted" : createSubagentError(result).message,
249
+ details,
250
+ );
251
+ }
252
+ const displayDetails = sanitizeDetailsForDisplay(details, lc.debug);
253
+ const content = formatSubagentResultForParent(result) || "(no output)";
254
+ const toolResult = createCompletedToolResult(content, displayDetails);
255
+ finalizeProgressState(lc.requestId, getFeedbackSummaryText(toolResult));
256
+ sendSubagentResultMessage(
257
+ lc.pi,
258
+ getResultDisplayText(toolResult),
259
+ displayDetails,
260
+ );
261
+ return toolResult;
262
+ }
263
+
264
+ function createPayloadFingerprint(payload: {
265
+ content: { type: string; text?: string }[];
266
+ details: SubagentDetails;
267
+ }): string {
268
+ const contentText = payload.content[0]?.text ?? "";
269
+ const latestResult = payload.details.results[0];
270
+ const activityText = latestResult?.progress?.activityText ?? "";
271
+ const toolCallIds =
272
+ [...new Set(latestResult?.progress?.toolCalls?.map((tc) => tc.id))]
273
+ .sort()
274
+ .join(",") ?? "";
275
+ const exitCode = latestResult?.exitCode ?? 0;
276
+ const stopReason = latestResult?.stopReason ?? "";
277
+ return `${contentText}|${activityText}|${toolCallIds}|${exitCode}|${stopReason}`;
278
+ }
279
+
280
+ async function runSubagentLifecycle(
281
+ lc: LifecycleContext,
282
+ ): Promise<SubagentToolResult> {
194
283
  const seenToolCallIds = new Set<string>();
195
- const requestProgressRender = createProgressRenderRequester(ctx, requestId);
284
+ const requestProgressRender = createProgressRenderRequester(
285
+ lc.ctx,
286
+ lc.requestId,
287
+ );
288
+ let lastDeliveredFingerprint: string | undefined;
196
289
  const onUpdate: OnUpdateCallback = (result) => {
197
- patchProgressFromDetails(requestId, result.details, seenToolCallIds);
290
+ patchProgressFromDetails(lc.requestId, result.details, seenToolCallIds);
198
291
  requestProgressRender();
199
- };
200
- function handleWorkerFailure(
201
- errorMessage: string,
202
- isAborted: boolean,
203
- details: SubagentDetails,
204
- ) {
205
- if (isAborted) {
206
- cancelProgressState(requestId, job.cancelReason ?? errorMessage);
207
- } else {
208
- failProgressState(requestId, errorMessage);
209
- ctx.ui?.notify(errorMessage, "error");
210
- const content = details.results[0]
211
- ? formatSubagentResultForParent(details.results[0] as SingleResult) ||
212
- "(failed)"
213
- : errorMessage;
214
- sendSubagentResultMessage(
215
- pi,
216
- content,
217
- sanitizeDetailsForDisplay(details, debug),
292
+ if (lc.hostOnUpdate) {
293
+ const sanitizedDetails = sanitizeDetailsForDisplay(
294
+ result.details,
295
+ lc.debug,
218
296
  );
297
+ const { renderedByMessage, ...partialDetails } = sanitizedDetails;
298
+ const payload = {
299
+ content: result.content,
300
+ details: partialDetails,
301
+ };
302
+ const fingerprint = createPayloadFingerprint(payload);
303
+ if (fingerprint !== lastDeliveredFingerprint) {
304
+ lastDeliveredFingerprint = fingerprint;
305
+ lc.hostOnUpdate(payload);
306
+ }
219
307
  }
220
- }
308
+ };
309
+ const timerTick = setInterval(requestProgressRender, 500);
221
310
  try {
222
311
  const result = await runSingleAgent(
223
- ctx.cwd,
224
- agents,
225
- agentName,
226
- task,
227
- mergedSignal,
312
+ lc.ctx.cwd,
313
+ lc.agents,
314
+ lc.agentName,
315
+ lc.task,
316
+ lc.mergedSignal,
228
317
  onUpdate,
229
- makeDetails,
230
- parentModel,
231
- parentThinking,
318
+ lc.makeDetails,
319
+ lc.parentModel,
320
+ lc.parentThinking,
232
321
  );
233
- if (hasSubagentFailed(result)) {
234
- handleWorkerFailure(
235
- mergedSignal.aborted ? "Aborted" : createSubagentError(result).message,
236
- mergedSignal.aborted,
237
- makeDetails([result]),
238
- );
239
- } else {
240
- const toolResult: SubagentToolResult = {
241
- content: [
242
- {
243
- type: "text" as const,
244
- text: formatSubagentResultForParent(result) || "(no output)",
245
- },
246
- ],
247
- details: makeDetails([result]),
248
- };
249
- finalizeProgressState(requestId, getFeedbackSummaryText(toolResult));
250
- sendSubagentResultMessage(
251
- pi,
252
- getResultDisplayText(toolResult),
253
- sanitizeDetailsForDisplay(toolResult.details, debug),
254
- );
255
- }
322
+ return finishLifecycleResult(lc, result);
256
323
  } catch (error) {
257
- handleWorkerFailure(
324
+ const abortResult =
325
+ error instanceof SubagentAbortError ? error.result : undefined;
326
+ return finishLifecycleFailure(
327
+ lc,
258
328
  error instanceof Error ? error.message : String(error),
259
- mergedSignal.aborted,
260
- makeDetails([]),
329
+ abortResult ? lc.makeDetails([abortResult]) : lc.makeDetails([]),
261
330
  );
262
331
  } finally {
332
+ clearInterval(timerTick);
263
333
  requestProgressRender();
264
- removeRunJob(requestId);
334
+ removeRunJob(lc.requestId);
265
335
  if (listRunJobs().length === 0) {
266
- const state = getProgressState(requestId);
336
+ const state = getProgressState(lc.requestId);
267
337
  if (state) {
268
338
  emitCompletionAlert(state);
269
339
  }
@@ -271,23 +341,43 @@ async function runSubagentWorker(
271
341
  }
272
342
  }
273
343
 
274
- export type StartJobResult =
344
+ type StartJobResult =
275
345
  | {
276
346
  kind: "started";
277
347
  requestId: string;
278
348
  instanceName: string;
279
349
  makeDetails: DetailsBuilder;
280
350
  }
351
+ | { kind: "completed"; result: SubagentToolResult }
281
352
  | { kind: "cancelled"; makeDetails: DetailsBuilder }
282
353
  | { kind: "not_found"; makeDetails: DetailsBuilder };
283
354
 
284
- export function formatStartJobStatus(
355
+ type PrepareSubagentJobResult =
356
+ | {
357
+ kind: "ready";
358
+ lc: LifecycleContext;
359
+ instanceName: string;
360
+ requestProgressRender: () => void;
361
+ hostOnUpdate?: AgentToolUpdateCallback<SubagentDetails>;
362
+ }
363
+ | { kind: "not_found"; makeDetails: DetailsBuilder }
364
+ | { kind: "cancelled"; makeDetails: DetailsBuilder }
365
+ | { kind: "aborted"; makeDetails: DetailsBuilder };
366
+
367
+ export function formatSubagentToolResult(
285
368
  agentName: string,
286
369
  result: StartJobResult,
287
- ): string {
288
- if (result.kind === "not_found") return `Unknown agent: "${agentName}"`;
289
- if (result.kind === "cancelled") return "Canceled";
290
- return `Subagent ${agentName} ${result.instanceName} started (job: ${result.requestId})`;
370
+ ): SubagentToolResult {
371
+ if (result.kind === "completed") return result.result;
372
+ let text: string;
373
+ if (result.kind === "not_found") text = `Unknown agent: "${agentName}"`;
374
+ else if (result.kind === "cancelled") text = "Canceled";
375
+ else
376
+ text = `Subagent ${agentName} ${result.instanceName} started (job: ${result.requestId})`;
377
+ return {
378
+ content: [{ type: "text", text }],
379
+ details: result.makeDetails([]),
380
+ };
291
381
  }
292
382
 
293
383
  function needsProjectAgentConfirmation(
@@ -309,12 +399,13 @@ function confirmProjectAgentRun(
309
399
  );
310
400
  }
311
401
 
312
- export async function startSubagentJob(
402
+ async function prepareSubagentJob(
313
403
  pi: ExtensionAPI,
314
404
  ctx: ExtensionContext,
315
405
  params: Static<typeof SubagentParams>,
316
406
  hostSignal: AbortSignal | undefined,
317
- ): Promise<StartJobResult> {
407
+ hostOnUpdate?: AgentToolUpdateCallback<SubagentDetails>,
408
+ ): Promise<PrepareSubagentJobResult> {
318
409
  const agentScope: AgentScope = params.agentScope ?? "both";
319
410
  const discovery = await getCachedAgentDiscovery(ctx.cwd, agentScope);
320
411
  const agents = discovery.agents;
@@ -380,32 +471,70 @@ export async function startSubagentJob(
380
471
  details: { agent: params.agent, instanceName, requestId },
381
472
  });
382
473
  const requestProgressRender = createProgressRenderRequester(ctx, requestId);
383
- setImmediate(() => {
384
- if (mergedSignal.aborted) {
385
- cancelStartedJob(job, job.cancelReason ?? "Aborted");
386
- requestProgressRender();
387
- return;
388
- }
389
- void runSubagentWorker(
474
+ if (mergedSignal.aborted) {
475
+ cancelStartedJob(job, job.cancelReason ?? "Aborted");
476
+ requestProgressRender();
477
+ return { kind: "aborted", makeDetails: makeStartedDetails };
478
+ }
479
+ return {
480
+ kind: "ready",
481
+ lc: {
390
482
  pi,
391
483
  ctx,
484
+ requestId,
485
+ job,
486
+ debug,
487
+ makeDetails: makeStartedDetails,
488
+ mergedSignal,
392
489
  agents,
393
- params.agent,
490
+ agentName: params.agent,
394
491
  task,
395
- debug,
396
492
  parentModel,
397
493
  parentThinking,
398
- makeStartedDetails,
399
- requestId,
400
- job,
401
- mergedSignal,
402
- );
494
+ hostOnUpdate,
495
+ },
496
+ instanceName,
497
+ requestProgressRender,
498
+ hostOnUpdate,
499
+ };
500
+ }
501
+
502
+ export async function startSubagentJob(
503
+ pi: ExtensionAPI,
504
+ ctx: ExtensionContext,
505
+ params: Static<typeof SubagentParams>,
506
+ hostSignal: AbortSignal | undefined,
507
+ hostOnUpdate?: AgentToolUpdateCallback<SubagentDetails>,
508
+ ): Promise<StartJobResult> {
509
+ const prepared = await prepareSubagentJob(
510
+ pi,
511
+ ctx,
512
+ params,
513
+ hostSignal,
514
+ hostOnUpdate,
515
+ );
516
+ if (prepared.kind !== "ready") {
517
+ if (prepared.kind === "aborted")
518
+ return { kind: "cancelled", makeDetails: prepared.makeDetails };
519
+ return prepared;
520
+ }
521
+ const { lc, instanceName, requestProgressRender } = prepared;
522
+ if (getSubagentDepth() > 0) {
523
+ const result = await runSubagentLifecycle(lc);
524
+ return { kind: "completed", result };
525
+ }
526
+ setImmediate(() => {
527
+ if (lc.mergedSignal.aborted) {
528
+ cancelStartedJob(lc.job, lc.job.cancelReason ?? "Aborted");
529
+ requestProgressRender();
530
+ return;
531
+ }
532
+ runSubagentLifecycle(lc);
403
533
  });
404
- if (mergedSignal.aborted) return { kind: "cancelled", makeDetails };
405
534
  return {
406
535
  kind: "started",
407
- requestId,
536
+ requestId: lc.requestId,
408
537
  instanceName,
409
- makeDetails: makeStartedDetails,
538
+ makeDetails: lc.makeDetails,
410
539
  };
411
540
  }
@@ -7,35 +7,37 @@ export function normalizeSummaryValue(value: string): string {
7
7
  return normalized;
8
8
  }
9
9
 
10
+ const SECRET_KEY_RE = /secret|token|password|passwd|credential|auth/i;
11
+ const JWT_RE = /^eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/;
12
+
10
13
  export function extractSemanticToolTarget(
11
- toolName: string,
12
14
  args: Record<string, unknown>,
13
15
  forceJson = false,
14
16
  ): string {
15
17
  if (forceJson) return JSON.stringify(args);
16
- if (toolName === "bash" && typeof args.command === "string")
17
- return args.command;
18
- if (
19
- ["read", "write", "edit", "file_search"].includes(toolName) &&
20
- typeof args.path === "string"
21
- )
22
- return args.path;
23
- if (toolName === "subagent") {
24
- const parts = [];
25
- if (typeof args.agent === "string") parts.push(args.agent);
26
- if (typeof args.task === "string")
27
- parts.push(normalizeSummaryValue(args.task));
28
- if (typeof args.agentScope === "string") parts.push(`[${args.agentScope}]`);
29
- if (parts.length) return parts.join(" ");
30
- return JSON.stringify(args);
18
+ const semanticKeys = [
19
+ "command",
20
+ "path",
21
+ "agent",
22
+ "query",
23
+ "url",
24
+ "action",
25
+ "name",
26
+ ];
27
+ for (const key of semanticKeys) {
28
+ const value = args[key];
29
+ if (typeof value === "string" && value.trim()) return value;
30
+ }
31
+ for (const key of Object.keys(args)) {
32
+ const value = args[key];
33
+ if (typeof value !== "string" || !value.trim()) continue;
34
+ if (SECRET_KEY_RE.test(key)) continue;
35
+ if (value.length > 60 || JWT_RE.test(value)) continue;
36
+ return value;
31
37
  }
32
38
  return "";
33
39
  }
34
40
 
35
- export function stripTerminalStatusPrefixes(value: string): string {
36
- return value.replace(/^(?:(?:success|failure):\s*)+/i, "");
37
- }
38
-
39
41
  export function truncateText(text: string, limit: number): string {
40
42
  if (text.length <= limit) return text;
41
43
  return `${text.slice(0, limit - 1)}…`;
@@ -51,7 +53,10 @@ export function normalizeTerminalSentence(
51
53
  .replace(/^\s*`{1,3}([^`]+)`{1,3}\s*$/, "$1")
52
54
  .replace(/^\s*\*\*([^*]+)\*\*\s*$/, "$1")
53
55
  .replace(/^\s*__([^_]+)__\s*$/, "$1");
54
- const withoutStatusPrefix = stripTerminalStatusPrefixes(unwrapped);
56
+ const withoutStatusPrefix = unwrapped.replace(
57
+ /^(?:(?:success|failure):\s*)+/i,
58
+ "",
59
+ );
55
60
  const withoutLabel = withoutStatusPrefix.replace(
56
61
  /^\s*(?:status|summary|result|output|message|error|check|outcome|project summary):\s+/i,
57
62
  "",
@@ -64,19 +69,21 @@ export function normalizeTerminalSentence(
64
69
 
65
70
  export const TOOL_PREVIEW_MAX_CHARS = 120;
66
71
 
72
+ export function normalizeAndTruncate(
73
+ text: string,
74
+ limit = TOOL_PREVIEW_MAX_CHARS,
75
+ ): string {
76
+ return truncateText(normalizeSummaryValue(text), limit);
77
+ }
78
+
67
79
  export function makeToolPreview(
68
80
  toolName: string,
69
81
  args: Record<string, unknown> | undefined,
70
82
  ): string {
71
83
  if (!args || Object.keys(args).length === 0) return toolName;
72
- const target = normalizeSummaryValue(
73
- extractSemanticToolTarget(toolName, args),
74
- );
84
+ const target = normalizeSummaryValue(extractSemanticToolTarget(args));
75
85
  if (!target) return toolName;
76
- return truncateText(
77
- normalizeSummaryValue(`${toolName}: ${target}`),
78
- TOOL_PREVIEW_MAX_CHARS,
79
- );
86
+ return normalizeAndTruncate(`${toolName}: ${target}`);
80
87
  }
81
88
 
82
89
  export function isStatusOnlySuccess(value: string): boolean {
package/src/output/ui.ts CHANGED
@@ -25,8 +25,6 @@ import {
25
25
  normalizeSummaryValue,
26
26
  } from "./normalize.js";
27
27
 
28
- export type { ThemeBg };
29
-
30
28
  /**
31
29
  * Abstraction for theme-aware text formatting.
32
30
  */
@@ -127,7 +125,7 @@ export function formatToolCall(
127
125
  forceJson = false,
128
126
  ): string {
129
127
  const target = normalizeSummaryValue(
130
- extractSemanticToolTarget(toolName, args, forceJson),
128
+ extractSemanticToolTarget(args, forceJson),
131
129
  );
132
130
  if (!target) return themeFg("accent", toolName);
133
131
  return themeFg("accent", toolName) + themeFg("dim", ` ${target}`);
@@ -183,7 +181,8 @@ export function renderSubagentCall(
183
181
  ): Text {
184
182
  const scope: AgentScope = args.agentScope ?? "both";
185
183
  const agentName = args.agent || "...";
186
- const target = extractSemanticToolTarget("subagent", args);
184
+ // Parser-owned preview suppresses task text: show agent + scope only
185
+ const target = args.agent ? `[${scope}]` : JSON.stringify(args);
187
186
  let text =
188
187
  theme.fg("toolTitle", theme.bold("subagent ")) +
189
188
  theme.fg("accent", agentName) +
@@ -192,15 +191,17 @@ export function renderSubagentCall(
192
191
  return new Text(text, 0, 0, (line) => theme.bg("toolPendingBg", line));
193
192
  }
194
193
 
195
- /**
196
- * Renders the subagent result box.
197
- *
198
- * Invariants:
199
- * - Red background indicates failure (exit code, error reason, or message error).
200
- * - Green background indicates success.
201
- * - Trims redundant "Outcome:" lines from the body.
202
- * - Displays usage stats and duration in the footer.
203
- */
194
+ export function renderSubagentToolResult(
195
+ result: { content: { type: string; text?: string }[]; details?: unknown },
196
+ theme: SubagentTheme,
197
+ display?: { isPartial?: boolean },
198
+ ): Component {
199
+ const details = result.details as SubagentDetails | undefined;
200
+ if (details?.renderedByMessage) return new Text("", 0, 0);
201
+ return renderSubagentResult(result, theme, display);
202
+ }
203
+
204
+ // Invariants: Red background = failure, green = success. Trims redundant "Outcome:" lines. Shows usage stats + duration in footer.
204
205
  export function renderSubagentResult(
205
206
  result: { content: { type: string; text?: string }[]; details?: unknown },
206
207
  theme: SubagentTheme,
@@ -226,7 +227,15 @@ export function renderSubagentResult(
226
227
  : "success";
227
228
  const finalOutput = r.finalOutput ?? getFinalOutput(r.messages ?? []);
228
229
  const title = formatSubagentTitle(r.agent, r.instanceName, theme);
229
- const bodyText = stripOutcomeLineForResultUi(bodyOverride ?? finalOutput);
230
+ let effectiveBody = bodyOverride ?? finalOutput;
231
+ if (_display?.isPartial && !finalOutput?.trim() && !bodyOverride) {
232
+ effectiveBody =
233
+ result.content[0]?.text ||
234
+ r.progress?.activityText ||
235
+ r.progress?.lastToolPreview ||
236
+ "(running...)";
237
+ }
238
+ const bodyText = stripOutcomeLineForResultUi(effectiveBody);
230
239
  const toolCount = r.progress?.toolCalls?.length ?? 0;
231
240
  const toolLabel = `${toolCount} ${toolCount === 1 ? "tool" : "tools"}`;
232
241
  const ctxPercent = formatContextPercent({
@@ -339,7 +348,6 @@ function renderJobCard(
339
348
  );
340
349
  }
341
350
 
342
- /** Sort states by startTime descending (newest first). */
343
351
  function sortByStartTimeDesc(
344
352
  a: SubagentProgressState,
345
353
  b: SubagentProgressState,
@@ -355,11 +363,8 @@ const BOARD_SECTIONS: [string, ProgressStatus][] = [
355
363
  ["SUCCEEDED", "success"],
356
364
  ];
357
365
 
358
- /**
359
- * Renders a unified job board for the `/jobs` command.
360
- * Jobs render in status-specific sections, each sorted by `startTime` descending.
361
- * Status icons preserve the existing /jobs contract for running and cancelled jobs.
362
- */
366
+ // Jobs render in status-specific sections, each sorted by `startTime` descending.
367
+ // Status icons preserve the existing /jobs contract for running and cancelled jobs.
363
368
  export function renderRunsBoard(
364
369
  states: SubagentProgressState[],
365
370
  theme: SubagentTheme,