@sumicom/quicksave 0.8.8 → 0.8.10

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 (122) hide show
  1. package/dist/ai/cardBuilder.d.ts +65 -4
  2. package/dist/ai/cardBuilder.d.ts.map +1 -1
  3. package/dist/ai/cardBuilder.js +338 -28
  4. package/dist/ai/cardBuilder.js.map +1 -1
  5. package/dist/ai/claudeCliProvider.d.ts +15 -0
  6. package/dist/ai/claudeCliProvider.d.ts.map +1 -1
  7. package/dist/ai/claudeCliProvider.js +87 -3
  8. package/dist/ai/claudeCliProvider.js.map +1 -1
  9. package/dist/ai/claudeCodeProvider.d.ts +4 -0
  10. package/dist/ai/claudeCodeProvider.d.ts.map +1 -1
  11. package/dist/ai/claudeCodeProvider.js +41 -0
  12. package/dist/ai/claudeCodeProvider.js.map +1 -1
  13. package/dist/ai/claudeSdkProvider.d.ts +1 -0
  14. package/dist/ai/claudeSdkProvider.d.ts.map +1 -1
  15. package/dist/ai/claudeSdkProvider.js +14 -2
  16. package/dist/ai/claudeSdkProvider.js.map +1 -1
  17. package/dist/ai/claudeTerminal/cardSynth.d.ts +63 -0
  18. package/dist/ai/claudeTerminal/cardSynth.d.ts.map +1 -0
  19. package/dist/ai/claudeTerminal/cardSynth.js +92 -0
  20. package/dist/ai/claudeTerminal/cardSynth.js.map +1 -0
  21. package/dist/ai/claudeTerminal/hookBridge.d.ts +56 -0
  22. package/dist/ai/claudeTerminal/hookBridge.d.ts.map +1 -0
  23. package/dist/ai/claudeTerminal/hookBridge.js +131 -0
  24. package/dist/ai/claudeTerminal/hookBridge.js.map +1 -0
  25. package/dist/ai/claudeTerminal/hookHandler.d.ts +18 -0
  26. package/dist/ai/claudeTerminal/hookHandler.d.ts.map +1 -0
  27. package/dist/ai/claudeTerminal/hookHandler.js +124 -0
  28. package/dist/ai/claudeTerminal/hookHandler.js.map +1 -0
  29. package/dist/ai/claudeTerminal/index.d.ts +5 -0
  30. package/dist/ai/claudeTerminal/index.d.ts.map +1 -0
  31. package/dist/ai/claudeTerminal/index.js +7 -0
  32. package/dist/ai/claudeTerminal/index.js.map +1 -0
  33. package/dist/ai/claudeTerminal/jsonlTail.d.ts +40 -0
  34. package/dist/ai/claudeTerminal/jsonlTail.d.ts.map +1 -0
  35. package/dist/ai/claudeTerminal/jsonlTail.js +129 -0
  36. package/dist/ai/claudeTerminal/jsonlTail.js.map +1 -0
  37. package/dist/ai/claudeTerminal/provider.d.ts +106 -0
  38. package/dist/ai/claudeTerminal/provider.d.ts.map +1 -0
  39. package/dist/ai/claudeTerminal/provider.js +579 -0
  40. package/dist/ai/claudeTerminal/provider.js.map +1 -0
  41. package/dist/ai/claudeTerminal/settingsBuilder.d.ts +44 -0
  42. package/dist/ai/claudeTerminal/settingsBuilder.d.ts.map +1 -0
  43. package/dist/ai/claudeTerminal/settingsBuilder.js +40 -0
  44. package/dist/ai/claudeTerminal/settingsBuilder.js.map +1 -0
  45. package/dist/ai/codexAppServer/cardAdapter.d.ts.map +1 -1
  46. package/dist/ai/codexAppServer/cardAdapter.js +13 -5
  47. package/dist/ai/codexAppServer/cardAdapter.js.map +1 -1
  48. package/dist/ai/codexAppServer/provider.d.ts +10 -2
  49. package/dist/ai/codexAppServer/provider.d.ts.map +1 -1
  50. package/dist/ai/codexAppServer/provider.js +158 -22
  51. package/dist/ai/codexAppServer/provider.js.map +1 -1
  52. package/dist/ai/openCodeProvider.d.ts +106 -0
  53. package/dist/ai/openCodeProvider.d.ts.map +1 -0
  54. package/dist/ai/openCodeProvider.js +617 -0
  55. package/dist/ai/openCodeProvider.js.map +1 -0
  56. package/dist/ai/openCodeServer.d.ts +88 -0
  57. package/dist/ai/openCodeServer.d.ts.map +1 -0
  58. package/dist/ai/openCodeServer.js +350 -0
  59. package/dist/ai/openCodeServer.js.map +1 -0
  60. package/dist/ai/profiles/project-sandbox.sb +26 -0
  61. package/dist/ai/provider.d.ts +54 -1
  62. package/dist/ai/provider.d.ts.map +1 -1
  63. package/dist/ai/provider.js +10 -0
  64. package/dist/ai/provider.js.map +1 -1
  65. package/dist/ai/queuedUserPrompts.d.ts +8 -0
  66. package/dist/ai/queuedUserPrompts.d.ts.map +1 -0
  67. package/dist/ai/queuedUserPrompts.js +19 -0
  68. package/dist/ai/queuedUserPrompts.js.map +1 -0
  69. package/dist/ai/realtimeTranscription.d.ts +39 -0
  70. package/dist/ai/realtimeTranscription.d.ts.map +1 -0
  71. package/dist/ai/realtimeTranscription.js +154 -0
  72. package/dist/ai/realtimeTranscription.js.map +1 -0
  73. package/dist/ai/sandboxMcp.d.ts +7 -0
  74. package/dist/ai/sandboxMcp.d.ts.map +1 -1
  75. package/dist/ai/sandboxMcp.js +4 -0
  76. package/dist/ai/sandboxMcp.js.map +1 -1
  77. package/dist/ai/sandboxMcpStdio.d.ts +1 -1
  78. package/dist/ai/sandboxMcpStdio.js +120 -47
  79. package/dist/ai/sandboxMcpStdio.js.map +1 -1
  80. package/dist/ai/sessionManager.d.ts +20 -1
  81. package/dist/ai/sessionManager.d.ts.map +1 -1
  82. package/dist/ai/sessionManager.js +220 -10
  83. package/dist/ai/sessionManager.js.map +1 -1
  84. package/dist/ai/sessionRegistry.d.ts +6 -0
  85. package/dist/ai/sessionRegistry.d.ts.map +1 -1
  86. package/dist/ai/sessionRegistry.js +13 -0
  87. package/dist/ai/sessionRegistry.js.map +1 -1
  88. package/dist/ai/sessionRegistryLocator.d.ts +14 -0
  89. package/dist/ai/sessionRegistryLocator.d.ts.map +1 -0
  90. package/dist/ai/sessionRegistryLocator.js +44 -0
  91. package/dist/ai/sessionRegistryLocator.js.map +1 -0
  92. package/dist/ai/systemPrompt.d.ts.map +1 -1
  93. package/dist/ai/systemPrompt.js +2 -1
  94. package/dist/ai/systemPrompt.js.map +1 -1
  95. package/dist/ai/voiceStream.d.ts +74 -0
  96. package/dist/ai/voiceStream.d.ts.map +1 -0
  97. package/dist/ai/voiceStream.js +179 -0
  98. package/dist/ai/voiceStream.js.map +1 -0
  99. package/dist/ai/voiceTranscription.d.ts +33 -0
  100. package/dist/ai/voiceTranscription.d.ts.map +1 -0
  101. package/dist/ai/voiceTranscription.js +122 -0
  102. package/dist/ai/voiceTranscription.js.map +1 -0
  103. package/dist/files/fileBrowser.d.ts.map +1 -1
  104. package/dist/files/fileBrowser.js +15 -2
  105. package/dist/files/fileBrowser.js.map +1 -1
  106. package/dist/handlers/legacyBusAdapter.d.ts +1 -1
  107. package/dist/handlers/legacyBusAdapter.d.ts.map +1 -1
  108. package/dist/handlers/legacyBusAdapter.js +7 -1
  109. package/dist/handlers/legacyBusAdapter.js.map +1 -1
  110. package/dist/handlers/messageHandler.d.ts +6 -0
  111. package/dist/handlers/messageHandler.d.ts.map +1 -1
  112. package/dist/handlers/messageHandler.js +119 -8
  113. package/dist/handlers/messageHandler.js.map +1 -1
  114. package/dist/index.js +2 -2
  115. package/dist/index.js.map +1 -1
  116. package/dist/service/ipcClient.js +1 -1
  117. package/dist/service/ipcClient.js.map +1 -1
  118. package/dist/service/run.d.ts.map +1 -1
  119. package/dist/service/run.js +81 -3
  120. package/dist/service/run.js.map +1 -1
  121. package/dist/service/types.js +1 -1
  122. package/package.json +6 -3
@@ -46,6 +46,22 @@ const TOOL_RESULT_TRUNCATE_LENGTH = 500;
46
46
  function cardHistoryPath(sessionId) {
47
47
  return join(getCardHistoryDir(), `${sessionId}.json`);
48
48
  }
49
+ function cleanPersistedCard(card) {
50
+ const { pendingInput, ...rest } = card;
51
+ void pendingInput;
52
+ if (rest.type === 'assistant_text') {
53
+ return { ...rest, streaming: false };
54
+ }
55
+ return rest;
56
+ }
57
+ function mergeCardsById(existing, incoming) {
58
+ const byId = new Map();
59
+ for (const card of existing)
60
+ byId.set(card.id, card);
61
+ for (const card of incoming)
62
+ byId.set(card.id, card);
63
+ return Array.from(byId.values());
64
+ }
49
65
  /**
50
66
  * Load persisted card history for a memory-mode session.
51
67
  * Returns cards in insertion order, or empty array if none exist.
@@ -64,13 +80,13 @@ export async function loadPersistedCards(sessionId) {
64
80
  }
65
81
  }
66
82
  // ── Direct JSONL file reading (replaces SDK getSessionMessages/listSubagents) ──
67
- function encodeCwdPath(cwd) {
83
+ export function encodeCwdPath(cwd) {
68
84
  return cwd.replace(/\//g, '-');
69
85
  }
70
- function claudeProjectDir(cwd) {
86
+ export function claudeProjectDir(cwd) {
71
87
  return join(homedir(), '.claude', 'projects', encodeCwdPath(cwd));
72
88
  }
73
- function jsonlPath(sessionId, cwd) {
89
+ export function jsonlPath(sessionId, cwd) {
74
90
  return join(claudeProjectDir(cwd), sessionId + '.jsonl');
75
91
  }
76
92
  function parseJSONLContent(content) {
@@ -220,6 +236,58 @@ function extractToolResultText(content) {
220
236
  }
221
237
  return JSON.stringify(content);
222
238
  }
239
+ /** Compact "1m 25s" / "45s" duration for the `text` fallback. The PWA renders
240
+ * its own badge from `meta.durationMs`; this keeps debug logs and non-PWA
241
+ * consumers readable. */
242
+ export function formatDurationMs(ms) {
243
+ if (!Number.isFinite(ms) || ms < 0)
244
+ return '0s';
245
+ const totalSec = Math.round(ms / 1000);
246
+ const m = Math.floor(totalSec / 60);
247
+ const s = totalSec % 60;
248
+ return m > 0 ? `${m}m ${s}s` : `${s}s`;
249
+ }
250
+ /** Build a structured `turn_duration` system card from a Claude JSONL entry. */
251
+ export function turnDurationCard(msg, id) {
252
+ const durationMs = typeof msg.durationMs === 'number' ? msg.durationMs : 0;
253
+ const messageCount = typeof msg.messageCount === 'number' ? msg.messageCount : 0;
254
+ return {
255
+ type: 'system',
256
+ id,
257
+ timestamp: Date.now(),
258
+ text: `Turn: ${formatDurationMs(durationMs)} · ${messageCount} messages`,
259
+ subtype: 'info',
260
+ meta: { kind: 'turn_duration', durationMs, messageCount },
261
+ };
262
+ }
263
+ /** Build a structured `stop_hook_summary` system card from a Claude JSONL entry.
264
+ * Flags as `warning` when any hook errored or blocked continuation — that's the
265
+ * channel that surfaces hook misconfigs (e.g. a bad interpreter path). */
266
+ export function stopHookSummaryCard(msg, id) {
267
+ const hooks = Array.isArray(msg.hookInfos)
268
+ ? msg.hookInfos.map((h) => ({
269
+ command: typeof h?.command === 'string' ? h.command : '',
270
+ durationMs: typeof h?.durationMs === 'number' ? h.durationMs : 0,
271
+ }))
272
+ : [];
273
+ const errors = Array.isArray(msg.hookErrors)
274
+ ? msg.hookErrors.filter((e) => typeof e === 'string')
275
+ : [];
276
+ const preventedContinuation = msg.preventedContinuation === true;
277
+ const level = typeof msg.level === 'string' ? msg.level : undefined;
278
+ const stopReason = typeof msg.stopReason === 'string' && msg.stopReason ? msg.stopReason : undefined;
279
+ const text = errors.length > 0
280
+ ? `Stop hook: ${hooks.length} ran, ${errors.length} error${errors.length > 1 ? 's' : ''}`
281
+ : `Stop hook: ${hooks.length} ran`;
282
+ return {
283
+ type: 'system',
284
+ id,
285
+ timestamp: Date.now(),
286
+ text,
287
+ subtype: errors.length > 0 || preventedContinuation ? 'warning' : 'info',
288
+ meta: { kind: 'stop_hook_summary', hooks, errors, level, preventedContinuation, stopReason },
289
+ };
290
+ }
223
291
  // ============================================================================
224
292
  // StreamCardBuilder — streaming session, emits CardEvents
225
293
  // ============================================================================
@@ -234,6 +302,14 @@ export class StreamCardBuilder {
234
302
  agentIdToCardId = new Map();
235
303
  /** Cards created for subagent permissions — removed after resolution. */
236
304
  ephemeralCards = new Set();
305
+ /** Agent tool_use input stashed from assistant message, keyed by tool_use block.id.
306
+ * Consumed when task_started arrives with the matching tool_use_id. */
307
+ agentToolUseStash = new Map();
308
+ /** Card ID of the currently running subagent; tool calls are nested here instead of
309
+ * being emitted as top-level ToolCallCards. Cleared on task_notification. */
310
+ activeSubagentCardId = null;
311
+ /** Nested tool_use_id → subagentCardId — lets toolResult() route to the right card. */
312
+ nestedToolUseToSubagentCard = new Map();
237
313
  /** Current streaming assistant_text card (for append_text events) */
238
314
  currentTextCardId = null;
239
315
  /** JSONL file byte offset at the start of the current turn — history reads stop here. */
@@ -258,6 +334,9 @@ export class StreamCardBuilder {
258
334
  this.toolUseIdToCardId.clear();
259
335
  this.agentIdToCardId.clear();
260
336
  this.ephemeralCards.clear();
337
+ this.agentToolUseStash.clear();
338
+ this.activeSubagentCardId = null;
339
+ this.nestedToolUseToSubagentCard.clear();
261
340
  this.currentTextCardId = null;
262
341
  }
263
342
  /**
@@ -269,17 +348,20 @@ export class StreamCardBuilder {
269
348
  const cards = this.getCards();
270
349
  if (cards.length === 0)
271
350
  return;
272
- // Strip transient fields before persisting
273
- const cleaned = cards.map(c => {
274
- const { pendingInput, ...rest } = c;
275
- if (rest.type === 'assistant_text') {
276
- return { ...rest, streaming: false };
277
- }
278
- return rest;
279
- });
351
+ await this.persistCardBatch(cards);
352
+ }
353
+ /** Persist a single card immediately. Used when a memory-mode provider
354
+ * injects a user prompt into an already-running turn; the card must survive
355
+ * refresh before the turn's normal end-of-turn persist runs. */
356
+ async persistCard(card) {
357
+ await this.persistCardBatch([card]);
358
+ }
359
+ async persistCardBatch(cards) {
360
+ if (cards.length === 0)
361
+ return;
362
+ const cleaned = cards.map(cleanPersistedCard);
280
363
  const dir = getCardHistoryDir();
281
364
  const p = cardHistoryPath(this.sessionId);
282
- // Append to existing history
283
365
  let existing = [];
284
366
  try {
285
367
  if (existsSync(p)) {
@@ -290,7 +372,7 @@ export class StreamCardBuilder {
290
372
  }
291
373
  }
292
374
  catch { /* start fresh */ }
293
- const merged = [...existing, ...cleaned];
375
+ const merged = mergeCardsById(existing, cleaned);
294
376
  await mkdir(dir, { recursive: true });
295
377
  await writeFile(p, JSON.stringify(merged) + '\n');
296
378
  }
@@ -364,6 +446,9 @@ export class StreamCardBuilder {
364
446
  this.toolUseIdToCardId.clear();
365
447
  this.agentIdToCardId.clear();
366
448
  this.ephemeralCards.clear();
449
+ this.agentToolUseStash.clear();
450
+ this.activeSubagentCardId = null;
451
+ this.nestedToolUseToSubagentCard.clear();
367
452
  this.currentTextCardId = null;
368
453
  }
369
454
  /** Cancel any pending deferred clear. Called on hot resume so the queued clear
@@ -468,6 +553,15 @@ export class StreamCardBuilder {
468
553
  */
469
554
  toolUse(toolName, toolInput, toolUseId) {
470
555
  this.currentTextCardId = null;
556
+ // Route to active subagent: nest this tool call inside the SubagentCard
557
+ if (this.activeSubagentCardId) {
558
+ const subagentCard = this.cards.get(this.activeSubagentCardId);
559
+ if (subagentCard) {
560
+ const toolCalls = [...(subagentCard.toolCalls ?? []), { id: toolUseId, toolName, toolInput }];
561
+ this.nestedToolUseToSubagentCard.set(toolUseId, this.activeSubagentCardId);
562
+ return this.updateEvent(this.activeSubagentCardId, { toolCalls });
563
+ }
564
+ }
471
565
  // Check if card was pre-created from canUseTool
472
566
  const existingCardId = this.toolUseIdToCardId.get(toolUseId);
473
567
  if (existingCardId) {
@@ -515,8 +609,9 @@ export class StreamCardBuilder {
515
609
  return null;
516
610
  return this.updateEvent(cardId, { pendingInput });
517
611
  }
518
- /** Clear pending input from a card. Ephemeral cards are removed entirely. */
519
- clearPendingInput(requestId) {
612
+ /** Clear pending input from a card. Ephemeral cards are removed entirely.
613
+ * Pass `answers` to store user-supplied data (e.g. rejection reason) on the card. */
614
+ clearPendingInput(requestId, answers) {
520
615
  for (const [, card] of this.cards) {
521
616
  if (card.pendingInput?.requestId === requestId) {
522
617
  if (this.ephemeralCards.has(card.id)) {
@@ -525,7 +620,10 @@ export class StreamCardBuilder {
525
620
  }
526
621
  // `null` signals "clear" — JSON drops `undefined` so it can't cross
527
622
  // the bus. See updateEvent() for the wire convention.
528
- return this.updateEvent(card.id, { pendingInput: null });
623
+ return this.updateEvent(card.id, {
624
+ pendingInput: null,
625
+ ...(answers ? { answers } : {}),
626
+ });
529
627
  }
530
628
  }
531
629
  return null;
@@ -547,6 +645,17 @@ export class StreamCardBuilder {
547
645
  return Array.from(this.cards.values());
548
646
  }
549
647
  toolResult(toolUseId, content, isError) {
648
+ // Route to active subagent's nested tool call
649
+ const subagentCardId = this.nestedToolUseToSubagentCard.get(toolUseId);
650
+ if (subagentCardId) {
651
+ const subagentCard = this.cards.get(subagentCardId);
652
+ if (!subagentCard?.toolCalls)
653
+ return null;
654
+ const truncated = content.length > TOOL_RESULT_TRUNCATE_LENGTH;
655
+ const resultContent = truncated ? content.slice(0, TOOL_RESULT_TRUNCATE_LENGTH) + ' [truncated]' : content;
656
+ const toolCalls = subagentCard.toolCalls.map(tc => tc.id === toolUseId ? { ...tc, result: { content: resultContent, isError } } : tc);
657
+ return this.updateEvent(subagentCardId, { toolCalls });
658
+ }
550
659
  const cardId = this.toolUseIdToCardId.get(toolUseId);
551
660
  if (!cardId)
552
661
  return null;
@@ -565,9 +674,19 @@ export class StreamCardBuilder {
565
674
  return null;
566
675
  return this.updateEvent(cardId, { answers });
567
676
  }
568
- subagentStart(description, agentId, toolUseId) {
677
+ /** Stash Agent tool_use input fields for later use when task_started arrives. */
678
+ stashAgentToolUseInput(toolUseId, input) {
679
+ this.agentToolUseStash.set(toolUseId, input);
680
+ }
681
+ subagentStart(description, agentId, toolUseId, extras) {
569
682
  this.currentTextCardId = null;
570
683
  const id = this.nextId();
684
+ const stashed = toolUseId ? this.agentToolUseStash.get(toolUseId) : undefined;
685
+ if (toolUseId)
686
+ this.agentToolUseStash.delete(toolUseId);
687
+ const subagentType = stashed?.subagentType ?? extras?.subagentType;
688
+ const requestedModel = stashed?.requestedModel ?? extras?.requestedModel;
689
+ const prompt = extras?.prompt ?? stashed?.prompt;
571
690
  const card = {
572
691
  type: 'subagent', id, timestamp: Date.now(),
573
692
  description,
@@ -575,9 +694,12 @@ export class StreamCardBuilder {
575
694
  agentId,
576
695
  status: 'running',
577
696
  toolUseCount: 0,
697
+ ...(subagentType ? { subagentType } : {}),
698
+ ...(requestedModel ? { requestedModel } : {}),
699
+ ...(prompt ? { prompt } : {}),
578
700
  };
579
701
  this.agentIdToCardId.set(agentId, id);
580
- // Position after the parent ToolCallCard if we have the toolUseId
702
+ this.activeSubagentCardId = id;
581
703
  const afterCardId = toolUseId ? this.toolUseIdToCardId.get(toolUseId) : undefined;
582
704
  return this.addEvent(card, afterCardId);
583
705
  }
@@ -598,12 +720,34 @@ export class StreamCardBuilder {
598
720
  ?? (toolUseId ? this.agentIdToCardId.get(toolUseId) : undefined);
599
721
  if (!cardId)
600
722
  return null;
723
+ if (cardId === this.activeSubagentCardId)
724
+ this.activeSubagentCardId = null;
601
725
  return this.updateEvent(cardId, { status, summary });
602
726
  }
603
727
  systemMessage(text, subtype) {
604
728
  const card = { type: 'system', id: this.nextId(), timestamp: Date.now(), text, subtype };
605
729
  return this.addEvent(card);
606
730
  }
731
+ /** Live structured `turn_duration` card — mirrors the history builder so a
732
+ * reload renders identically. */
733
+ turnDuration(raw) {
734
+ return this.addEvent(turnDurationCard(raw, this.nextId()));
735
+ }
736
+ /** Live structured `stop_hook_summary` card — see history builder counterpart. */
737
+ stopHookSummary(raw) {
738
+ return this.addEvent(stopHookSummaryCard(raw, this.nextId()));
739
+ }
740
+ generatedImage(prompt, status, savedPath) {
741
+ const card = {
742
+ type: 'generated_image',
743
+ id: this.nextId(),
744
+ timestamp: Date.now(),
745
+ prompt,
746
+ status,
747
+ ...(savedPath ? { savedPath } : {}),
748
+ };
749
+ return this.addEvent(card);
750
+ }
607
751
  recoverySuggested(reason, action, label) {
608
752
  const card = {
609
753
  type: 'recovery_suggested',
@@ -619,6 +763,54 @@ export class StreamCardBuilder {
619
763
  return this.systemMessage(`Error: ${text}`, 'error');
620
764
  }
621
765
  }
766
+ /** Read tool calls from a subagent's JSONL — used by buildCardsFromHistory to nest them. */
767
+ async function readSubagentToolCalls(jsonlPath) {
768
+ try {
769
+ const content = await readFile(jsonlPath, 'utf-8');
770
+ const messages = parseJSONLContent(content);
771
+ const resultMap = new Map();
772
+ const toolCalls = [];
773
+ for (const msg of messages) {
774
+ const rawMsg = msg.message;
775
+ if (!Array.isArray(rawMsg?.content))
776
+ continue;
777
+ if (msg.type === 'user') {
778
+ for (const block of rawMsg.content) {
779
+ if (block.type === 'tool_result' && block.tool_use_id) {
780
+ const text = extractToolResultText(block.content);
781
+ resultMap.set(block.tool_use_id, { content: text, isError: !!block.is_error });
782
+ }
783
+ }
784
+ }
785
+ else if (msg.type === 'assistant') {
786
+ for (const block of rawMsg.content) {
787
+ if (block.type === 'tool_use' && block.id && block.name !== 'Agent') {
788
+ toolCalls.push({
789
+ id: block.id,
790
+ toolName: block.name,
791
+ toolInput: typeof block.input === 'object' && block.input ? block.input : {},
792
+ });
793
+ }
794
+ }
795
+ }
796
+ }
797
+ // Attach results and apply truncation
798
+ for (const tc of toolCalls) {
799
+ const r = resultMap.get(tc.id);
800
+ if (r) {
801
+ const truncated = r.content.length > TOOL_RESULT_TRUNCATE_LENGTH;
802
+ tc.result = {
803
+ content: truncated ? r.content.slice(0, TOOL_RESULT_TRUNCATE_LENGTH) + ' [truncated]' : r.content,
804
+ isError: r.isError,
805
+ };
806
+ }
807
+ }
808
+ return toolCalls;
809
+ }
810
+ catch {
811
+ return [];
812
+ }
813
+ }
622
814
  // ============================================================================
623
815
  // buildCardsFromHistory — convert JSONL history into Card[]
624
816
  // ============================================================================
@@ -678,7 +870,52 @@ export async function buildCardsFromHistory(sessionId, cwd, offset = 0, limit =
678
870
  // The Claude CLI stores this as `toolUseResult` at the message envelope
679
871
  // level, separate from the human-readable tool_result.content string.
680
872
  const askUserResults = new Map();
873
+ const taskStarts = new Map();
874
+ const taskProgress = new Map();
875
+ const taskEnds = new Map();
876
+ const taskKey = (agentId, toolUseId) => {
877
+ if (typeof toolUseId === 'string' && toolUseId.length > 0)
878
+ return toolUseId;
879
+ if (typeof agentId === 'string' && agentId.length > 0)
880
+ return agentId;
881
+ return null;
882
+ };
681
883
  for (const msg of allMessages) {
884
+ if (msg.type === 'system') {
885
+ const subtype = msg.subtype;
886
+ const key = taskKey(msg.task_id, msg.tool_use_id);
887
+ if (key && subtype === 'task_started') {
888
+ taskStarts.set(key, {
889
+ description: typeof msg.description === 'string' ? msg.description : '',
890
+ agentId: typeof msg.task_id === 'string' ? msg.task_id : key,
891
+ toolUseId: typeof msg.tool_use_id === 'string' ? msg.tool_use_id : key,
892
+ prompt: typeof msg.prompt === 'string' ? msg.prompt : undefined,
893
+ subagentType: typeof msg.subagent_type === 'string'
894
+ ? msg.subagent_type
895
+ : typeof msg.subagentType === 'string'
896
+ ? msg.subagentType
897
+ : undefined,
898
+ requestedModel: typeof msg.model === 'string'
899
+ ? msg.model
900
+ : typeof msg.requested_model === 'string'
901
+ ? msg.requested_model
902
+ : undefined,
903
+ });
904
+ }
905
+ else if (key && subtype === 'task_progress') {
906
+ taskProgress.set(key, {
907
+ toolUseCount: typeof msg.usage?.tool_uses === 'number' ? msg.usage.tool_uses : undefined,
908
+ lastToolName: typeof msg.last_tool_name === 'string' ? msg.last_tool_name : undefined,
909
+ });
910
+ }
911
+ else if (key && subtype === 'task_notification') {
912
+ const status = msg.status;
913
+ taskEnds.set(key, {
914
+ status: status === 'failed' || status === 'stopped' ? status : 'completed',
915
+ summary: typeof msg.summary === 'string' ? msg.summary : undefined,
916
+ });
917
+ }
918
+ }
682
919
  const rawMsg = msg.message;
683
920
  const toolUseResult = msg.toolUseResult;
684
921
  if (!Array.isArray(rawMsg?.content))
@@ -703,24 +940,32 @@ export async function buildCardsFromHistory(sessionId, cwd, offset = 0, limit =
703
940
  // System messages (task_started) are NOT reliably present in the JSONL.
704
941
  // Instead, read subagent meta.json files and match by description to the
705
942
  // Agent tool_use input.description.
706
- // Build description → toolUseId map from Agent tool_use blocks
943
+ // Build description → toolUseId map and capture Agent tool_use input data
707
944
  const descToToolUseId = new Map();
945
+ const agentInputByToolUseId = new Map();
708
946
  for (const msg of allMessages) {
709
947
  const rawMsg = msg.message;
710
948
  if (!Array.isArray(rawMsg?.content))
711
949
  continue;
712
950
  for (const block of rawMsg.content) {
713
951
  if (block.type === 'tool_use' && block.name === 'Agent' && block.id) {
714
- const desc = block.input?.description ?? '';
952
+ const input = block.input;
953
+ const desc = input?.description ?? '';
715
954
  if (desc)
716
955
  descToToolUseId.set(desc, block.id);
956
+ agentInputByToolUseId.set(block.id, {
957
+ prompt: typeof input?.prompt === 'string' ? input.prompt : undefined,
958
+ subagentType: typeof input?.subagent_type === 'string' ? input.subagent_type : undefined,
959
+ requestedModel: typeof input?.model === 'string' ? input.model : undefined,
960
+ });
717
961
  }
718
962
  }
719
963
  }
964
+ const subagentsDir = join(claudeProjectDir(cwd), sessionId, 'subagents');
720
965
  const toolUseIdToAgentId = new Map();
966
+ const subagentToolCallsByToolUseId = new Map();
721
967
  try {
722
968
  const agentIds = await listSubagentIdsFromDisk(sessionId, cwd);
723
- const subagentsDir = join(claudeProjectDir(cwd), sessionId, 'subagents');
724
969
  for (const agentId of agentIds) {
725
970
  try {
726
971
  const meta = JSON.parse(await readFile(join(subagentsDir, `${agentId}.meta.json`), 'utf-8'));
@@ -736,6 +981,12 @@ export async function buildCardsFromHistory(sessionId, cwd, offset = 0, limit =
736
981
  toolUseIdToAgentId.set(toolUseId, toolUseId);
737
982
  }
738
983
  }
984
+ // Read each subagent's JSONL to extract nested tool calls
985
+ for (const [toolUseId, agentId] of toolUseIdToAgentId) {
986
+ const toolCalls = await readSubagentToolCalls(join(subagentsDir, `${agentId}.jsonl`));
987
+ if (toolCalls.length > 0)
988
+ subagentToolCallsByToolUseId.set(toolUseId, toolCalls);
989
+ }
739
990
  }
740
991
  catch {
741
992
  // No subagents or SDK error — fallback: agentId = toolUseId
@@ -761,12 +1012,54 @@ export async function buildCardsFromHistory(sessionId, cwd, offset = 0, limit =
761
1012
  let seq = Math.max(0, total - offset - limit);
762
1013
  const nextId = () => `${sessionId}:h:${++seq}`;
763
1014
  const cards = [];
1015
+ const emittedSubagentKeys = new Set();
1016
+ const slicedAgentToolUseIds = new Set();
1017
+ for (const msg of sliced) {
1018
+ const rawMsg = msg.message;
1019
+ if (!Array.isArray(rawMsg?.content))
1020
+ continue;
1021
+ for (const block of rawMsg.content) {
1022
+ if (block.type === 'tool_use' && block.name === 'Agent' && block.id) {
1023
+ slicedAgentToolUseIds.add(block.id);
1024
+ }
1025
+ }
1026
+ }
1027
+ const subagentCardFromTask = (task) => {
1028
+ const key = task.toolUseId || task.agentId;
1029
+ const end = taskEnds.get(key);
1030
+ const progress = taskProgress.get(key);
1031
+ return {
1032
+ type: 'subagent',
1033
+ id: nextId(),
1034
+ timestamp: Date.now(),
1035
+ description: task.description,
1036
+ toolUseId: task.toolUseId,
1037
+ agentId: task.agentId,
1038
+ status: end?.status ?? 'running',
1039
+ ...(end?.summary ? { summary: end.summary } : {}),
1040
+ toolUseCount: progress?.toolUseCount ?? 0,
1041
+ ...(progress?.lastToolName ? { lastToolName: progress.lastToolName } : {}),
1042
+ ...(task.prompt ? { prompt: task.prompt } : {}),
1043
+ ...(task.subagentType ? { subagentType: task.subagentType } : {}),
1044
+ ...(task.requestedModel ? { requestedModel: task.requestedModel } : {}),
1045
+ };
1046
+ };
764
1047
  for (const msg of sliced) {
765
1048
  // ── System messages ──
766
1049
  if (msg.type === 'system') {
767
1050
  const subtype = msg.subtype;
768
- // Skip subagent lifecycle events (handled via subagentCards below)
769
- if (subtype === 'task_started' || subtype === 'task_progress' || subtype === 'task_notification')
1051
+ const key = taskKey(msg.task_id, msg.tool_use_id);
1052
+ if (subtype === 'task_started' && key) {
1053
+ const task = taskStarts.get(key);
1054
+ if (task && !slicedAgentToolUseIds.has(task.toolUseId) && !emittedSubagentKeys.has(key)) {
1055
+ cards.push(subagentCardFromTask(task));
1056
+ emittedSubagentKeys.add(key);
1057
+ }
1058
+ continue;
1059
+ }
1060
+ // Progress/end task events update the task_started card; they do not
1061
+ // render as standalone history cards.
1062
+ if (subtype === 'task_progress' || subtype === 'task_notification')
770
1063
  continue;
771
1064
  // Skip init and status messages (not useful in history)
772
1065
  if (subtype === 'init' || subtype === 'status' || subtype === 'session_state_changed')
@@ -774,6 +1067,12 @@ export async function buildCardsFromHistory(sessionId, cwd, offset = 0, limit =
774
1067
  if (subtype === 'compact_boundary') {
775
1068
  cards.push({ type: 'system', id: nextId(), timestamp: Date.now(), text: 'Context compacted', subtype: 'compacted' });
776
1069
  }
1070
+ else if (subtype === 'turn_duration') {
1071
+ cards.push(turnDurationCard(msg, nextId()));
1072
+ }
1073
+ else if (subtype === 'stop_hook_summary') {
1074
+ cards.push(stopHookSummaryCard(msg, nextId()));
1075
+ }
777
1076
  else {
778
1077
  // Unknown system subtype — show as info
779
1078
  cards.push({ type: 'system', id: nextId(), timestamp: Date.now(), text: subtype ?? 'System event', subtype: 'info' });
@@ -897,12 +1196,17 @@ export async function buildCardsFromHistory(sessionId, cwd, offset = 0, limit =
897
1196
  break;
898
1197
  case 'tool_use': {
899
1198
  if (agentToolUseIds.has(block.id)) {
900
- // Agent tool_use → create a ToolCallCard as anchor, then SubagentCard
1199
+ // Agent tool_use → SubagentCard
901
1200
  const agentId = toolUseIdToAgentId.get(block.id) ?? block.id;
902
1201
  const result = toolResults.get(block.id);
903
1202
  const description = block.input?.description ?? '';
904
1203
  const summary = result ? result.content.slice(0, 200) : undefined;
905
- // Insert SubagentCard
1204
+ const agentInput = agentInputByToolUseId.get(block.id);
1205
+ const taskStart = taskStarts.get(block.id);
1206
+ const progress = taskProgress.get(block.id);
1207
+ const end = taskEnds.get(block.id);
1208
+ const prompt = agentInput?.prompt ?? taskStart?.prompt;
1209
+ const nestedToolCalls = subagentToolCallsByToolUseId.get(block.id);
906
1210
  const subagentCard = {
907
1211
  type: 'subagent',
908
1212
  id: nextId(),
@@ -910,11 +1214,17 @@ export async function buildCardsFromHistory(sessionId, cwd, offset = 0, limit =
910
1214
  description,
911
1215
  toolUseId: block.id,
912
1216
  agentId,
913
- status: result ? 'completed' : 'running',
914
- summary,
915
- toolUseCount: 0,
1217
+ status: end?.status ?? (result ? 'completed' : 'running'),
1218
+ summary: end?.summary ?? summary,
1219
+ toolUseCount: progress?.toolUseCount ?? nestedToolCalls?.length ?? 0,
1220
+ ...(progress?.lastToolName ? { lastToolName: progress.lastToolName } : {}),
1221
+ ...(agentInput?.subagentType ? { subagentType: agentInput.subagentType } : {}),
1222
+ ...(agentInput?.requestedModel ? { requestedModel: agentInput.requestedModel } : {}),
1223
+ ...(prompt ? { prompt } : {}),
1224
+ ...(nestedToolCalls?.length ? { toolCalls: nestedToolCalls } : {}),
916
1225
  };
917
1226
  cards.push(subagentCard);
1227
+ emittedSubagentKeys.add(block.id);
918
1228
  }
919
1229
  else {
920
1230
  // Normal tool call