mixdog 0.9.65 → 0.9.67

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.
Files changed (54) hide show
  1. package/package.json +1 -1
  2. package/src/app.mjs +2 -2
  3. package/src/cli.mjs +1 -1
  4. package/src/repl.mjs +72 -6
  5. package/src/runtime/agent/orchestrator/session/context-utils.mjs +76 -21
  6. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +45 -4
  7. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +11 -1
  8. package/src/runtime/agent/orchestrator/session/store/summary-cache.mjs +36 -13
  9. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +83 -18
  10. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +14 -0
  11. package/src/runtime/agent/orchestrator/session/store.mjs +37 -2
  12. package/src/runtime/channels/lib/config.mjs +7 -0
  13. package/src/runtime/channels/lib/status-snapshot.mjs +6 -30
  14. package/src/runtime/channels/lib/webhook/relay-tunnel.mjs +203 -0
  15. package/src/runtime/channels/lib/webhook.mjs +23 -201
  16. package/src/runtime/shared/config.mjs +0 -9
  17. package/src/runtime/shared/time-format.mjs +56 -0
  18. package/src/runtime/shared/tool-card-model.mjs +740 -0
  19. package/src/session-runtime/channel-config-api.mjs +5 -0
  20. package/src/session-runtime/context-status.mjs +2 -1
  21. package/src/session-runtime/lifecycle-api.mjs +5 -0
  22. package/src/session-runtime/prewarm.mjs +13 -7
  23. package/src/session-runtime/provider-request-tools.mjs +6 -0
  24. package/src/session-runtime/runtime-core.mjs +28 -9
  25. package/src/session-runtime/session-text.mjs +6 -0
  26. package/src/session-runtime/settings-api.mjs +0 -12
  27. package/src/session-runtime/tool-catalog-schema.mjs +5 -1
  28. package/src/standalone/channel-admin.mjs +35 -21
  29. package/src/tui/App.jsx +0 -15
  30. package/src/tui/app/channel-pickers.mjs +18 -42
  31. package/src/tui/app/doctor.mjs +0 -1
  32. package/src/tui/app/transcript-window.mjs +16 -0
  33. package/src/tui/app/use-transcript-window.mjs +36 -1
  34. package/src/tui/components/Markdown.jsx +15 -1
  35. package/src/tui/components/Message.jsx +1 -1
  36. package/src/tui/components/PromptInput.jsx +7 -0
  37. package/src/tui/components/ToolExecution.jsx +47 -196
  38. package/src/tui/components/tool-execution/surface-detail.mjs +33 -394
  39. package/src/tui/components/tool-execution/text-format.mjs +27 -104
  40. package/src/tui/dist/index.mjs +613 -264
  41. package/src/tui/engine/frame-batched-store.mjs +5 -3
  42. package/src/tui/engine/live-share.mjs +9 -0
  43. package/src/tui/engine/render-timing.mjs +28 -0
  44. package/src/tui/engine/session-api-ext.mjs +7 -10
  45. package/src/tui/engine/tui-steering-persist.mjs +25 -4
  46. package/src/tui/engine.mjs +9 -1
  47. package/src/tui/index.jsx +11 -3
  48. package/src/tui/markdown/measure-rendered-rows.mjs +28 -16
  49. package/src/tui/markdown/render-ansi.mjs +34 -1
  50. package/src/tui/markdown/stream-fence.mjs +44 -7
  51. package/src/tui/markdown/streaming-markdown.mjs +95 -27
  52. package/src/tui/time-format.mjs +4 -51
  53. package/src/ui/stream-finalize.mjs +45 -0
  54. package/src/runtime/channels/lib/webhook/ngrok.mjs +0 -181
@@ -126,6 +126,7 @@ export function PromptInput({
126
126
  // that node's REAL laid-out position + our caret col/row — no external
127
127
  // absolute-coordinate guessing, so it never drifts).
128
128
  const boxRef = useRef(null);
129
+ const inkRootRef = useRef(null);
129
130
  const cursorEnabledRef = useRef(false); // latest enabled state, read by the anchor fn at render time
130
131
  const contentWidthRef = useRef(80);
131
132
  const preferredColumnRef = useRef(null);
@@ -154,9 +155,15 @@ export function PromptInput({
154
155
  // (slower) rendering, never a crash.
155
156
  const flushThrottleRef = useRef({ lastAt: 0, timer: null });
156
157
  const flushImmediate = () => {
158
+ const cachedRoot = inkRootRef.current;
159
+ if (typeof cachedRoot?.onImmediateRender === 'function') {
160
+ cachedRoot.onImmediateRender();
161
+ return;
162
+ }
157
163
  let node = boxRef.current;
158
164
  for (let i = 0; node && i < 64; i++) {
159
165
  if (node.nodeName === 'ink-root') {
166
+ inkRootRef.current = node;
160
167
  if (typeof node.onImmediateRender === 'function') node.onImmediateRender();
161
168
  return;
162
169
  }
@@ -18,13 +18,9 @@ import { BULLET_OPERATOR } from '../figures.mjs';
18
18
  import {
19
19
  displayToolName as surfaceDisplayToolName,
20
20
  formatToolSurface,
21
- summarizeToolResult as surfaceSummarizeToolResult,
22
21
  formatAggregateHeader,
23
- formatToolActionHeader,
24
- summarizeAgentSurfaceBrief,
25
- AGENT_SURFACE_BRIEF_MAX,
26
22
  } from '../../runtime/shared/tool-surface.mjs';
27
- import { isBackgroundErrorOnlyBody, backgroundTaskFailureStatusLabel } from '../../runtime/shared/err-text.mjs';
23
+ import { deriveToolCardModel } from '../../runtime/shared/tool-card-model.mjs';
28
24
  import {
29
25
  MIN_RESULT_LINE_CHARS,
30
26
  RESULT_LINE_HARD_MAX,
@@ -33,36 +29,13 @@ import {
33
29
  safeInlineText,
34
30
  normalizeCountMap,
35
31
  truncateToWidth,
36
- shellResultElapsed,
37
- normalizeTerminalStatus,
38
32
  resultTerminalStatus,
39
33
  stripLeadingStatusMarkerLines,
40
34
  stripLeadingStatusMarkerFromText,
41
35
  } from './tool-execution/text-format.mjs';
42
36
  import {
43
37
  SKILL_SURFACE_NAMES,
44
- isShellTool,
45
- shellDisplayStatus,
46
- shellHeader,
47
38
  isAgentTool,
48
- isBackgroundTaskTool,
49
- agentResponseTitle,
50
- agentActionTitle,
51
- agentActionSummary,
52
- hasAgentResponseResult,
53
- resolveBackgroundTaskMeta,
54
- backgroundTaskElapsed,
55
- prefixElapsed,
56
- mergeTerminalDetail,
57
- shouldPrefixSyncElapsed,
58
- backgroundTaskResultTitle,
59
- backgroundTaskActionTitle,
60
- backgroundTaskFailureDetail,
61
- backgroundTaskDetail,
62
- isBackgroundTaskResponseArgs,
63
- genericCompletedDetail,
64
- toolSearchLoadedSummary,
65
- agentTerminalDetail,
66
39
  clampFailureCount,
67
40
  toolStatusColor,
68
41
  } from './tool-execution/surface-detail.mjs';
@@ -77,15 +50,6 @@ const TOOL_PENDING_SHOW_DELAY_MS = 1000;
77
50
  // One shared-tick cadence covers both the 500ms blink and per-second elapsed;
78
51
  // finer than either boundary so both stay crisp off a single timer.
79
52
  const TOOL_ANIM_TICK_MS = TOOL_BLINK_MS;
80
- function statusCopy(name, label, count, doneCount, pending, isError, args = {}) {
81
- // No stableVerbWidth padding: it padded the done verb to the active ("-ing")
82
- // width, which Ink trims at the line END (vendor output trimEnd) so it never
83
- // stabilized the pending→done flip — it only left an UGLY mid-header gap
84
- // ("Searched 1 pattern", "Read 1 file"). The header is wrap="truncate"
85
- // behind a fixed gutter and the fullscreen full-clear repaints the row, so
86
- // dropping the pad just normalizes the spacing.
87
- return formatToolActionHeader(name, args, { pending, count });
88
- }
89
53
  export function ToolExecution({ name, args, result, rawResult, isError, errorCount, callErrorCount, exitErrorCount, expanded, columns = 80, attached = false, count = 1, completedCount = 0, startedAt = 0, completedAt = 0, aggregate = false, categories = {}, doneCategories = null, headerFinalized = true, deferredDisplayReady = false, agentResponseAggregate = false }) {
90
54
  const rowWidth = Math.max(1, Number(columns || 80));
91
55
  const groupCount = Math.max(1, Number(count || 1));
@@ -284,104 +248,52 @@ export function ToolExecution({ name, args, result, rawResult, isError, errorCou
284
248
  }
285
249
 
286
250
  // ── Normal (non-aggregate) tool card ────────────────────────────
287
- const { label, summary, normalizedName, args: parsedArgs } = formatToolSurface(name, args);
288
- const isShellSurface = isShellTool(normalizedName, label);
289
- const isSkillSurface = SKILL_SURFACE_NAMES.has(String(normalizedName || '').toLowerCase());
290
- const backgroundMeta = !pending && isBackgroundTaskTool(normalizedName)
291
- ? resolveBackgroundTaskMeta(parsedArgs, rt || '')
292
- : null;
293
- const backgroundError = backgroundMeta?.error || parsedArgs?.error || '';
294
- const errorOnlyResult = Boolean(rt) && isBackgroundErrorOnlyBody(rt, backgroundError);
295
- const backgroundResultText = backgroundMeta?.hasResponse ? backgroundMeta.body : '';
296
- const displayedResultText = backgroundResultText || (errorOnlyResult ? '' : (rt || ''));
297
- const hasDisplayResult = Boolean(String(displayedResultText || '').trim());
298
- const displayedResultBodyText = stripLeadingStatusMarkerFromText(displayedResultText);
299
- const hasDisplayBody = Boolean(String(displayedResultBodyText || '').trim());
300
- const lines = displayedResultBodyText ? displayedResultBodyText.split('\n') : [];
301
- const totalLines = lines.length;
302
- // Semantic one-line summary derived purely from name/args/result text.
303
- // Shown in the collapsed, non-error view in place of the raw result block.
304
- // Grouped cards ("Searched N files" / "Read N files") get the same treatment
305
- // as single calls: a one-line semantic summary stands in for the raw block.
306
- const resultSummary = !pending && hasDisplayBody
307
- ? surfaceSummarizeToolResult(name, args, displayedResultBodyText, isError)
308
- : null;
309
- // Same fit budget fitResultLine() uses, to detect a line that will be clipped.
251
+ // Single source: the shared collapsed-card derivation (labels, casing,
252
+ // status merging, detail row) consumed by BOTH the TUI and the desktop
253
+ // renderer (apps/desktop TranscriptView ToolCard). Width fitting, theme
254
+ // colors, blink, and expansion handling stay TUI-side below.
310
255
  const maxResultChars = Math.min(RESULT_LINE_HARD_MAX, Math.max(MIN_RESULT_LINE_CHARS, Number(columns || 80) - 7));
256
+ const model = deriveToolCardModel({
257
+ name,
258
+ args,
259
+ result,
260
+ rawResult,
261
+ isError,
262
+ errorCount,
263
+ callErrorCount,
264
+ exitErrorCount,
265
+ count: displayGroupCount,
266
+ completedCount: doneCount,
267
+ startedAt,
268
+ completedAt,
269
+ headerFinalized,
270
+ nowMs,
271
+ }, { truncate: truncateToWidth, maxResultChars });
272
+ const {
273
+ labelText,
274
+ summaryText,
275
+ headerFailureText: headerFailureStatus,
276
+ detailLine: collapsedDetailLine,
277
+ detailIsPlaceholder,
278
+ terminalStatus,
279
+ normalizedName,
280
+ isShellSurface,
281
+ isAgentSurfaceCard,
282
+ isAgentResponse,
283
+ isBackgroundMetadataResult,
284
+ hasDisplayResult,
285
+ hasDisplayBody,
286
+ displayedResultBodyText,
287
+ firstResultLine,
288
+ totalLines,
289
+ resultSummary,
290
+ shellCollapsedSummary,
291
+ toolArgPath,
292
+ } = model;
293
+ const lines = displayedResultBodyText ? displayedResultBodyText.split('\n') : [];
311
294
  const resultColor = theme.text;
312
- const firstResultLine = hasDisplayResult ? String(lines[0] ?? '') : '';
313
295
  const firstResultLineClipped = hasDisplayBody && stringWidth(firstResultLine) > maxResultChars;
314
296
  const hasHiddenDetail = !pending && hasDisplayBody && (totalLines > 1 || firstResultLineClipped || Boolean(resultSummary));
315
- const shellStatus = isShellSurface ? shellDisplayStatus({ pending, failedCount, exitFailedCount, isError, result: displayedResultText }) : '';
316
- const shellElapsed = isShellSurface ? (shellResultElapsed(displayedResultText) || elapsed) : '';
317
- const backgroundElapsed = backgroundMeta
318
- ? backgroundTaskElapsed(backgroundMeta, elapsed)
319
- : (isBackgroundTaskTool(normalizedName) ? backgroundTaskElapsed(parsedArgs, elapsed) : '');
320
-
321
- const toolArgPath = parsedArgs?.path ?? parsedArgs?.file_path ?? parsedArgs?.file ?? '';
322
- // Audit HIGH: on a FAILED view_image the path detail used to win over the
323
- // error cause (nonShellDetail order puts imageDetail before genericDetail),
324
- // so the card showed the filename instead of why it failed. Suppress the
325
- // path detail on error; the error-cause summary/first line takes the row.
326
- const imageDetail = normalizedName === 'view_image' && toolArgPath && !isError ? String(toolArgPath) : '';
327
- const isBackgroundResult = !pending && isBackgroundTaskTool(normalizedName) && Boolean(backgroundMeta);
328
- const isBackgroundResponse = isBackgroundResult && (backgroundMeta?.hasResponse || isBackgroundTaskResponseArgs(normalizedName, parsedArgs));
329
- const isBackgroundMetadataResult = isBackgroundResult && !isBackgroundResponse && Boolean(backgroundMeta);
330
- const backgroundMetadataFailureLabel = isBackgroundMetadataResult
331
- ? backgroundTaskFailureDetail(backgroundMeta, parsedArgs)
332
- : '';
333
- const backgroundMetadataHeaderFailure = Boolean(backgroundMetadataFailureLabel) && !hasDisplayResult
334
- ? backgroundMetadataFailureLabel
335
- : '';
336
- const agentHeaderFailure = !pending && isAgentTool(normalizedName) && isError && parsedArgs?.error && !hasDisplayResult
337
- ? backgroundTaskFailureStatusLabel(parsedArgs?.status, parsedArgs?.error, { surface: 'agent' })
338
- : '';
339
- const headerFailureStatus = backgroundMetadataHeaderFailure || agentHeaderFailure || '';
340
- const agentCompletionDetail = !pending && isAgentTool(normalizedName) && !agentHeaderFailure
341
- ? agentTerminalDetail(parsedArgs?.status, isError, elapsed, parsedArgs?.error)
342
- : '';
343
- const agentDetail = !pending && isAgentTool(normalizedName) && !hasDisplayResult
344
- ? agentCompletionDetail
345
- : '';
346
- const genericDetail = !pending && !isShellSurface && !agentDetail && !imageDetail && !resultSummary
347
- ? genericCompletedDetail({ normalizedName, label, hasResult, firstResultLine, isError })
348
- : '';
349
- const terminalStatus = pending
350
- ? 'running'
351
- : (shellStatus || normalizeTerminalStatus(backgroundMeta?.status) || normalizeTerminalStatus(parsedArgs?.status) || resultTerminalStatus(displayedResultText) || (isError || failedCount > 0 ? 'failed' : 'completed'));
352
- const backgroundMetadataDetail = isBackgroundMetadataResult && !backgroundMetadataHeaderFailure
353
- ? backgroundTaskDetail(backgroundMeta, backgroundElapsed, parsedArgs)
354
- : '';
355
- const backgroundResponseDetail = isBackgroundResponse && resultSummary
356
- ? prefixElapsed(resultSummary, backgroundElapsed)
357
- : resultSummary;
358
- const syncElapsedDetail = !isBackgroundResponse && shouldPrefixSyncElapsed(normalizedName, label)
359
- ? prefixElapsed(backgroundResponseDetail, elapsed)
360
- : backgroundResponseDetail;
361
- const nonShellDetail = backgroundMetadataDetail || (/^(Cancelled|Failed|Finished)$/i.test(resultSummary || '') && agentCompletionDetail
362
- ? agentCompletionDetail
363
- : syncElapsedDetail) || agentDetail || imageDetail || genericDetail;
364
- // A pending non-aggregate tool used to drop its detail row entirely
365
- // (collapsedDetail = ''), so the card rendered as a single header row. But
366
- // estimateTranscriptItemRows() in App.jsx reserves 2 rows for a collapsed
367
- // non-aggregate tool (1 only for skill surfaces). That left a 1-row gap that
368
- // closed the instant the result landed — the surviving "line-jump". Reserve the same
369
- // dim placeholder detail row the aggregate card uses (`Running`) for the whole
370
- // pending lifecycle so the height stays fixed at header + one detail row and
371
- // the final summary just fills in place. Skill surfaces collapse to a single
372
- // row in BOTH the estimate and the render (visibleDetailLines drops the row
373
- // for isSkillSurface below), so they get no placeholder.
374
- const pendingDetailPlaceholder = pending && !isSkillSurface
375
- ? (elapsed ? `Running · ${elapsed}` : 'Running')
376
- : '';
377
- const shellCollapsedSummary = isShellSurface && !pending && hasDisplayResult
378
- ? (resultSummary || truncateToWidth(firstResultLine, Math.min(120, maxResultChars)))
379
- : resultSummary;
380
- const collapsedDetail = pending
381
- ? pendingDetailPlaceholder
382
- : isShellSurface
383
- ? prefixElapsed(mergeTerminalDetail(shellStatus, shellCollapsedSummary), shellElapsed)
384
- : mergeTerminalDetail(terminalStatus, nonShellDetail);
385
297
  const backgroundMetadataExpandable = isBackgroundMetadataResult && hasRawResult && !pending;
386
298
  const showRawResult = expanded && (hasDisplayBody || hasRawResult)
387
299
  && (!isBackgroundMetadataResult || hasRawResult);
@@ -389,47 +301,11 @@ export function ToolExecution({ name, args, result, rawResult, isError, errorCou
389
301
  ? (agentResponseAggregate && hasRawResult
390
302
  ? stripLeadingStatusMarkerLines(rawRt.split('\n'))
391
303
  : (hasDisplayBody ? lines : (rawRt ? stripLeadingStatusMarkerLines(rawRt.split('\n')) : [])))
392
- : (collapsedDetail ? [collapsedDetail] : []);
393
- const isPendingPlaceholderDetail = !showRawResult && Boolean(pendingDetailPlaceholder);
304
+ : (collapsedDetailLine ? [collapsedDetailLine] : []);
305
+ const isPendingPlaceholderDetail = !showRawResult && detailIsPlaceholder;
394
306
  const detailColor = isPendingPlaceholderDetail ? theme.subtle : theme.text;
395
-
396
- const isAgentResult = !isBackgroundResult && !pending && isAgentTool(normalizedName) && hasDisplayResult;
397
- const isAgentResponse = isAgentResult && hasAgentResponseResult(rt);
398
- const isAgentSurfaceCard = isAgentTool(normalizedName);
399
- const agentSurfaceBriefRaw = isAgentSurfaceCard && !showRawResult
400
- ? summarizeAgentSurfaceBrief(name, parsedArgs, displayedResultText || '', { isError, isResponse: isAgentResponse })
401
- : '';
402
- const agentSurfaceBrief = agentSurfaceBriefRaw
403
- ? truncateToWidth(agentSurfaceBriefRaw, Math.min(AGENT_SURFACE_BRIEF_MAX, maxResultChars))
404
- : '';
405
- // Skill loads carry the skill name in the header already
406
- // ("Loaded 1 skill (name)"); the collapsed detail row just repeats it, so
407
- // drop it and keep the card a single line. Expanding (ctrl+o) still shows the
408
- // full skill body via the raw-result path.
409
- // Agent spawn/send/response cards show a tight brief under the ⎿ gutter when
410
- // collapsed; ctrl+o expand still surfaces the full body.
411
- let visibleDetailLines = detailLines;
412
- if (isSkillSurface && !showRawResult) {
413
- // Audit HIGH: a FAILED skill load used to drop its detail row with the
414
- // success path, hiding the one-line cause entirely. Keep the detail row
415
- // when the call errored; only the redundant success repeat is dropped.
416
- visibleDetailLines = isError && collapsedDetail ? [collapsedDetail] : [];
417
- } else if (isBackgroundMetadataResult && backgroundMetadataHeaderFailure && !showRawResult) {
418
- visibleDetailLines = [];
419
- } else if (isAgentSurfaceCard && !showRawResult) {
420
- // Agent cards collapse to a SINGLE header row like skill surfaces. Keep the
421
- // ⎿ detail row ONLY when it carries failure info: the call errored, or the
422
- // brief/summary reads as a failure/cancel. A header-failure-only card (no
423
- // brief) shows the cause in the header, so it stays a single row too.
424
- // Expanded (ctrl+o raw) still flows through the showRawResult path above.
425
- const agentDetailFallback = collapsedDetail
426
- || (pending ? (pendingDetailPlaceholder || 'Running') : 'Finished');
427
- const agentDetailLine = agentSurfaceBrief
428
- || truncateToWidth(String(agentDetailFallback), Math.min(AGENT_SURFACE_BRIEF_MAX, maxResultChars));
429
- const agentFailureText = /\b(Cancelled|Canceled|Failed)\b/i.test(agentSurfaceBrief || collapsedDetail || '');
430
- const keepAgentDetail = (isError || agentFailureText) && !(agentHeaderFailure && !agentSurfaceBrief);
431
- visibleDetailLines = keepAgentDetail ? [agentDetailLine] : [];
432
- }
307
+ // Skill/agent collapsed gating lives in the shared model (detailLine).
308
+ const visibleDetailLines = detailLines;
433
309
  const finalStatusColor = toolStatusColor({ pending, groupCount, callFailedCount, exitFailedCount, terminalStatus });
434
310
  const dotColor = finalStatusColor;
435
311
  // Agent surface cards use directional markers: `←` for requests going OUT
@@ -449,31 +325,6 @@ export function ToolExecution({ name, args, result, rawResult, isError, errorCou
449
325
  const isDirectionalMarker = isAgentResponse || isAgentSurfaceCard;
450
326
  const markerText = isDirectionalMarker ? `${markerGlyph} ` : markerGlyph;
451
327
  const dotText = pending && !blinkOn ? ' ' : markerText;
452
- let labelText;
453
- if (isAgentResponse) labelText = agentResponseTitle(parsedArgs, displayGroupCount);
454
- else if (isBackgroundResponse) labelText = backgroundTaskResultTitle(normalizedName, backgroundMeta || parsedArgs);
455
- else if (isBackgroundMetadataResult) labelText = backgroundTaskActionTitle(normalizedName, backgroundMeta);
456
- else if (isShellSurface) labelText = shellHeader(shellStatus, displayGroupCount);
457
- else labelText = (isAgentTool(normalizedName) ? agentActionTitle(parsedArgs) : '') || statusCopy(name, label, displayGroupCount, doneCount, headerPending, isError, parsedArgs);
458
- labelText = safeInlineText(labelText);
459
- // Show the parenthesized arg summary for grouped cards too, matching single
460
- // calls so the header carries the same context.
461
- const toolSearchSummary = !pending && normalizedName === 'load_tool' && hasResult
462
- ? toolSearchLoadedSummary(displayedResultText)
463
- : '';
464
- const rawSummaryText = safeInlineText(isAgentResponse || isBackgroundResponse
465
- ? ''
466
- : toolSearchSummary || (isAgentTool(normalizedName) ? agentActionSummary(parsedArgs, summary) : summary));
467
- // Drop the parenthesized arg summary when it is a bare "<n> <unit>" count
468
- // that the header verb already spells out (e.g. header "Searching 6 patterns"
469
- // + summary "6 patterns"). Multi-arg array calls hit this; single calls keep
470
- // their descriptive summary ("pattern: \"foo\"") since it never matches the
471
- // header tail. Channel surfaces are unaffected — they build the summary from
472
- // summarizeToolArgs directly and never render this header verb.
473
- const summaryIsHeaderCount = rawSummaryText
474
- && /^\d+\s+\S+$/.test(rawSummaryText)
475
- && labelText.endsWith(rawSummaryText);
476
- const summaryText = summaryIsHeaderCount ? '' : rawSummaryText;
477
328
  // Agent cards hide their collapsed body but still expose ctrl+o expand only
478
329
  // when expanding would actually reveal something: an agent response body, or a
479
330
  // multiline / clipped raw result (e.g. the "agents: N …" worker list). A