@sumicom/quicksave 0.8.9 → 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 (112) hide show
  1. package/dist/ai/cardBuilder.d.ts +62 -2
  2. package/dist/ai/cardBuilder.d.ts.map +1 -1
  3. package/dist/ai/cardBuilder.js +331 -25
  4. package/dist/ai/cardBuilder.js.map +1 -1
  5. package/dist/ai/claudeCliProvider.d.ts +14 -0
  6. package/dist/ai/claudeCliProvider.d.ts.map +1 -1
  7. package/dist/ai/claudeCliProvider.js +83 -2
  8. package/dist/ai/claudeCliProvider.js.map +1 -1
  9. package/dist/ai/claudeCodeProvider.d.ts.map +1 -1
  10. package/dist/ai/claudeCodeProvider.js +2 -0
  11. package/dist/ai/claudeCodeProvider.js.map +1 -1
  12. package/dist/ai/claudeSdkProvider.d.ts.map +1 -1
  13. package/dist/ai/claudeSdkProvider.js +13 -2
  14. package/dist/ai/claudeSdkProvider.js.map +1 -1
  15. package/dist/ai/claudeTerminal/cardSynth.d.ts +63 -0
  16. package/dist/ai/claudeTerminal/cardSynth.d.ts.map +1 -0
  17. package/dist/ai/claudeTerminal/cardSynth.js +92 -0
  18. package/dist/ai/claudeTerminal/cardSynth.js.map +1 -0
  19. package/dist/ai/claudeTerminal/hookBridge.d.ts +56 -0
  20. package/dist/ai/claudeTerminal/hookBridge.d.ts.map +1 -0
  21. package/dist/ai/claudeTerminal/hookBridge.js +131 -0
  22. package/dist/ai/claudeTerminal/hookBridge.js.map +1 -0
  23. package/dist/ai/claudeTerminal/hookHandler.d.ts +18 -0
  24. package/dist/ai/claudeTerminal/hookHandler.d.ts.map +1 -0
  25. package/dist/ai/claudeTerminal/hookHandler.js +124 -0
  26. package/dist/ai/claudeTerminal/hookHandler.js.map +1 -0
  27. package/dist/ai/claudeTerminal/index.d.ts +5 -0
  28. package/dist/ai/claudeTerminal/index.d.ts.map +1 -0
  29. package/dist/ai/claudeTerminal/index.js +7 -0
  30. package/dist/ai/claudeTerminal/index.js.map +1 -0
  31. package/dist/ai/claudeTerminal/jsonlTail.d.ts +40 -0
  32. package/dist/ai/claudeTerminal/jsonlTail.d.ts.map +1 -0
  33. package/dist/ai/claudeTerminal/jsonlTail.js +129 -0
  34. package/dist/ai/claudeTerminal/jsonlTail.js.map +1 -0
  35. package/dist/ai/claudeTerminal/provider.d.ts +106 -0
  36. package/dist/ai/claudeTerminal/provider.d.ts.map +1 -0
  37. package/dist/ai/claudeTerminal/provider.js +579 -0
  38. package/dist/ai/claudeTerminal/provider.js.map +1 -0
  39. package/dist/ai/claudeTerminal/settingsBuilder.d.ts +44 -0
  40. package/dist/ai/claudeTerminal/settingsBuilder.d.ts.map +1 -0
  41. package/dist/ai/claudeTerminal/settingsBuilder.js +40 -0
  42. package/dist/ai/claudeTerminal/settingsBuilder.js.map +1 -0
  43. package/dist/ai/codexAppServer/cardAdapter.d.ts.map +1 -1
  44. package/dist/ai/codexAppServer/cardAdapter.js +13 -5
  45. package/dist/ai/codexAppServer/cardAdapter.js.map +1 -1
  46. package/dist/ai/codexAppServer/provider.d.ts +9 -2
  47. package/dist/ai/codexAppServer/provider.d.ts.map +1 -1
  48. package/dist/ai/codexAppServer/provider.js +157 -22
  49. package/dist/ai/codexAppServer/provider.js.map +1 -1
  50. package/dist/ai/profiles/project-sandbox.sb +26 -0
  51. package/dist/ai/provider.d.ts +29 -1
  52. package/dist/ai/provider.d.ts.map +1 -1
  53. package/dist/ai/provider.js +1 -0
  54. package/dist/ai/provider.js.map +1 -1
  55. package/dist/ai/queuedUserPrompts.d.ts +8 -0
  56. package/dist/ai/queuedUserPrompts.d.ts.map +1 -0
  57. package/dist/ai/queuedUserPrompts.js +19 -0
  58. package/dist/ai/queuedUserPrompts.js.map +1 -0
  59. package/dist/ai/realtimeTranscription.d.ts +39 -0
  60. package/dist/ai/realtimeTranscription.d.ts.map +1 -0
  61. package/dist/ai/realtimeTranscription.js +154 -0
  62. package/dist/ai/realtimeTranscription.js.map +1 -0
  63. package/dist/ai/sandboxMcp.d.ts +7 -0
  64. package/dist/ai/sandboxMcp.d.ts.map +1 -1
  65. package/dist/ai/sandboxMcp.js +4 -0
  66. package/dist/ai/sandboxMcp.js.map +1 -1
  67. package/dist/ai/sandboxMcpStdio.d.ts +1 -1
  68. package/dist/ai/sandboxMcpStdio.js +120 -47
  69. package/dist/ai/sandboxMcpStdio.js.map +1 -1
  70. package/dist/ai/sessionManager.d.ts +14 -0
  71. package/dist/ai/sessionManager.d.ts.map +1 -1
  72. package/dist/ai/sessionManager.js +144 -3
  73. package/dist/ai/sessionManager.js.map +1 -1
  74. package/dist/ai/sessionRegistry.d.ts +6 -0
  75. package/dist/ai/sessionRegistry.d.ts.map +1 -1
  76. package/dist/ai/sessionRegistry.js +13 -0
  77. package/dist/ai/sessionRegistry.js.map +1 -1
  78. package/dist/ai/sessionRegistryLocator.d.ts +14 -0
  79. package/dist/ai/sessionRegistryLocator.d.ts.map +1 -0
  80. package/dist/ai/sessionRegistryLocator.js +44 -0
  81. package/dist/ai/sessionRegistryLocator.js.map +1 -0
  82. package/dist/ai/systemPrompt.d.ts.map +1 -1
  83. package/dist/ai/systemPrompt.js +1 -0
  84. package/dist/ai/systemPrompt.js.map +1 -1
  85. package/dist/ai/voiceStream.d.ts +74 -0
  86. package/dist/ai/voiceStream.d.ts.map +1 -0
  87. package/dist/ai/voiceStream.js +179 -0
  88. package/dist/ai/voiceStream.js.map +1 -0
  89. package/dist/ai/voiceTranscription.d.ts +33 -0
  90. package/dist/ai/voiceTranscription.d.ts.map +1 -0
  91. package/dist/ai/voiceTranscription.js +122 -0
  92. package/dist/ai/voiceTranscription.js.map +1 -0
  93. package/dist/files/fileBrowser.d.ts.map +1 -1
  94. package/dist/files/fileBrowser.js +3 -2
  95. package/dist/files/fileBrowser.js.map +1 -1
  96. package/dist/handlers/legacyBusAdapter.d.ts +1 -1
  97. package/dist/handlers/legacyBusAdapter.d.ts.map +1 -1
  98. package/dist/handlers/legacyBusAdapter.js +6 -1
  99. package/dist/handlers/legacyBusAdapter.js.map +1 -1
  100. package/dist/handlers/messageHandler.d.ts +5 -0
  101. package/dist/handlers/messageHandler.d.ts.map +1 -1
  102. package/dist/handlers/messageHandler.js +100 -5
  103. package/dist/handlers/messageHandler.js.map +1 -1
  104. package/dist/index.js +2 -2
  105. package/dist/index.js.map +1 -1
  106. package/dist/service/ipcClient.js +1 -1
  107. package/dist/service/ipcClient.js.map +1 -1
  108. package/dist/service/run.d.ts.map +1 -1
  109. package/dist/service/run.js +74 -3
  110. package/dist/service/run.js.map +1 -1
  111. package/dist/service/types.js +1 -1
  112. package/package.json +6 -3
@@ -1,9 +1,31 @@
1
- import type { Attachment, Card, CardId, CardEvent, CardHistoryResponse, PendingInputAttachment } from '@sumicom/quicksave-shared';
1
+ import type { Attachment, Card, CardId, CardEvent, CardHistoryResponse, PendingInputAttachment, GeneratedImageCard, SystemCard } from '@sumicom/quicksave-shared';
2
2
  /**
3
3
  * Load persisted card history for a memory-mode session.
4
4
  * Returns cards in insertion order, or empty array if none exist.
5
5
  */
6
6
  export declare function loadPersistedCards(sessionId: string): Promise<Card[]>;
7
+ export declare function encodeCwdPath(cwd: string): string;
8
+ export declare function claudeProjectDir(cwd: string): string;
9
+ export declare function jsonlPath(sessionId: string, cwd: string): string;
10
+ /** Compact "1m 25s" / "45s" duration for the `text` fallback. The PWA renders
11
+ * its own badge from `meta.durationMs`; this keeps debug logs and non-PWA
12
+ * consumers readable. */
13
+ export declare function formatDurationMs(ms: number): string;
14
+ /** Build a structured `turn_duration` system card from a Claude JSONL entry. */
15
+ export declare function turnDurationCard(msg: {
16
+ durationMs?: unknown;
17
+ messageCount?: unknown;
18
+ }, id: CardId): SystemCard;
19
+ /** Build a structured `stop_hook_summary` system card from a Claude JSONL entry.
20
+ * Flags as `warning` when any hook errored or blocked continuation — that's the
21
+ * channel that surfaces hook misconfigs (e.g. a bad interpreter path). */
22
+ export declare function stopHookSummaryCard(msg: {
23
+ hookInfos?: unknown;
24
+ hookErrors?: unknown;
25
+ level?: unknown;
26
+ preventedContinuation?: unknown;
27
+ stopReason?: unknown;
28
+ }, id: CardId): SystemCard;
7
29
  export declare class StreamCardBuilder {
8
30
  private sessionId;
9
31
  private cwd;
@@ -15,6 +37,14 @@ export declare class StreamCardBuilder {
15
37
  private agentIdToCardId;
16
38
  /** Cards created for subagent permissions — removed after resolution. */
17
39
  private ephemeralCards;
40
+ /** Agent tool_use input stashed from assistant message, keyed by tool_use block.id.
41
+ * Consumed when task_started arrives with the matching tool_use_id. */
42
+ private agentToolUseStash;
43
+ /** Card ID of the currently running subagent; tool calls are nested here instead of
44
+ * being emitted as top-level ToolCallCards. Cleared on task_notification. */
45
+ private activeSubagentCardId;
46
+ /** Nested tool_use_id → subagentCardId — lets toolResult() route to the right card. */
47
+ private nestedToolUseToSubagentCard;
18
48
  /** Current streaming assistant_text card (for append_text events) */
19
49
  private currentTextCardId;
20
50
  /** JSONL file byte offset at the start of the current turn — history reads stop here. */
@@ -34,6 +64,11 @@ export declare class StreamCardBuilder {
34
64
  * Call before clearCards() at the end of each turn.
35
65
  */
36
66
  persistCards(): Promise<void>;
67
+ /** Persist a single card immediately. Used when a memory-mode provider
68
+ * injects a user prompt into an already-running turn; the card must survive
69
+ * refresh before the turn's normal end-of-turn persist runs. */
70
+ persistCard(card: Card): Promise<void>;
71
+ private persistCardBatch;
37
72
  /** Snapshot current JSONL byte size so history reads stop before the active turn. */
38
73
  snapshotCutoff(): Promise<void>;
39
74
  /**
@@ -103,10 +138,35 @@ export declare class StreamCardBuilder {
103
138
  toolResult(toolUseId: string, content: string, isError: boolean): CardEvent | null;
104
139
  /** Attach AskUserQuestion answers to the matching ToolCallCard. */
105
140
  setToolAnswers(toolUseId: string, answers: Record<string, string>): CardEvent | null;
106
- subagentStart(description: string, agentId: string, toolUseId?: string): CardEvent;
141
+ /** Stash Agent tool_use input fields for later use when task_started arrives. */
142
+ stashAgentToolUseInput(toolUseId: string, input: {
143
+ prompt?: string;
144
+ subagentType?: string;
145
+ requestedModel?: string;
146
+ }): void;
147
+ subagentStart(description: string, agentId: string, toolUseId?: string, extras?: {
148
+ prompt?: string;
149
+ subagentType?: string;
150
+ requestedModel?: string;
151
+ }): CardEvent;
107
152
  subagentProgress(agentId: string, toolUseId: string | undefined, toolUseCount?: number, lastToolName?: string): CardEvent | null;
108
153
  subagentEnd(agentId: string, toolUseId: string | undefined, status: 'completed' | 'failed' | 'stopped', summary?: string): CardEvent | null;
109
154
  systemMessage(text: string, subtype?: 'compacted' | 'cost' | 'error' | 'info' | 'warning'): CardEvent;
155
+ /** Live structured `turn_duration` card — mirrors the history builder so a
156
+ * reload renders identically. */
157
+ turnDuration(raw: {
158
+ durationMs?: unknown;
159
+ messageCount?: unknown;
160
+ }): CardEvent;
161
+ /** Live structured `stop_hook_summary` card — see history builder counterpart. */
162
+ stopHookSummary(raw: {
163
+ hookInfos?: unknown;
164
+ hookErrors?: unknown;
165
+ level?: unknown;
166
+ preventedContinuation?: unknown;
167
+ stopReason?: unknown;
168
+ }): CardEvent;
169
+ generatedImage(prompt: string, status: GeneratedImageCard['status'], savedPath?: string): CardEvent;
110
170
  recoverySuggested(reason: string, action: 'compact', label: string): CardEvent;
111
171
  errorMessage(text: string): CardEvent;
112
172
  }
@@ -1 +1 @@
1
- {"version":3,"file":"cardBuilder.d.ts","sourceRoot":"","sources":["../../src/ai/cardBuilder.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cardBuilder.d.ts","sourceRoot":"","sources":["../../src/ai/cardBuilder.ts"],"names":[],"mappings":"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"}
@@ -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) {
@@ -551,6 +645,17 @@ export class StreamCardBuilder {
551
645
  return Array.from(this.cards.values());
552
646
  }
553
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
+ }
554
659
  const cardId = this.toolUseIdToCardId.get(toolUseId);
555
660
  if (!cardId)
556
661
  return null;
@@ -569,9 +674,19 @@ export class StreamCardBuilder {
569
674
  return null;
570
675
  return this.updateEvent(cardId, { answers });
571
676
  }
572
- 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) {
573
682
  this.currentTextCardId = null;
574
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;
575
690
  const card = {
576
691
  type: 'subagent', id, timestamp: Date.now(),
577
692
  description,
@@ -579,9 +694,12 @@ export class StreamCardBuilder {
579
694
  agentId,
580
695
  status: 'running',
581
696
  toolUseCount: 0,
697
+ ...(subagentType ? { subagentType } : {}),
698
+ ...(requestedModel ? { requestedModel } : {}),
699
+ ...(prompt ? { prompt } : {}),
582
700
  };
583
701
  this.agentIdToCardId.set(agentId, id);
584
- // Position after the parent ToolCallCard if we have the toolUseId
702
+ this.activeSubagentCardId = id;
585
703
  const afterCardId = toolUseId ? this.toolUseIdToCardId.get(toolUseId) : undefined;
586
704
  return this.addEvent(card, afterCardId);
587
705
  }
@@ -602,12 +720,34 @@ export class StreamCardBuilder {
602
720
  ?? (toolUseId ? this.agentIdToCardId.get(toolUseId) : undefined);
603
721
  if (!cardId)
604
722
  return null;
723
+ if (cardId === this.activeSubagentCardId)
724
+ this.activeSubagentCardId = null;
605
725
  return this.updateEvent(cardId, { status, summary });
606
726
  }
607
727
  systemMessage(text, subtype) {
608
728
  const card = { type: 'system', id: this.nextId(), timestamp: Date.now(), text, subtype };
609
729
  return this.addEvent(card);
610
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
+ }
611
751
  recoverySuggested(reason, action, label) {
612
752
  const card = {
613
753
  type: 'recovery_suggested',
@@ -623,6 +763,54 @@ export class StreamCardBuilder {
623
763
  return this.systemMessage(`Error: ${text}`, 'error');
624
764
  }
625
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
+ }
626
814
  // ============================================================================
627
815
  // buildCardsFromHistory — convert JSONL history into Card[]
628
816
  // ============================================================================
@@ -682,7 +870,52 @@ export async function buildCardsFromHistory(sessionId, cwd, offset = 0, limit =
682
870
  // The Claude CLI stores this as `toolUseResult` at the message envelope
683
871
  // level, separate from the human-readable tool_result.content string.
684
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
+ };
685
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
+ }
686
919
  const rawMsg = msg.message;
687
920
  const toolUseResult = msg.toolUseResult;
688
921
  if (!Array.isArray(rawMsg?.content))
@@ -707,24 +940,32 @@ export async function buildCardsFromHistory(sessionId, cwd, offset = 0, limit =
707
940
  // System messages (task_started) are NOT reliably present in the JSONL.
708
941
  // Instead, read subagent meta.json files and match by description to the
709
942
  // Agent tool_use input.description.
710
- // Build description → toolUseId map from Agent tool_use blocks
943
+ // Build description → toolUseId map and capture Agent tool_use input data
711
944
  const descToToolUseId = new Map();
945
+ const agentInputByToolUseId = new Map();
712
946
  for (const msg of allMessages) {
713
947
  const rawMsg = msg.message;
714
948
  if (!Array.isArray(rawMsg?.content))
715
949
  continue;
716
950
  for (const block of rawMsg.content) {
717
951
  if (block.type === 'tool_use' && block.name === 'Agent' && block.id) {
718
- const desc = block.input?.description ?? '';
952
+ const input = block.input;
953
+ const desc = input?.description ?? '';
719
954
  if (desc)
720
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
+ });
721
961
  }
722
962
  }
723
963
  }
964
+ const subagentsDir = join(claudeProjectDir(cwd), sessionId, 'subagents');
724
965
  const toolUseIdToAgentId = new Map();
966
+ const subagentToolCallsByToolUseId = new Map();
725
967
  try {
726
968
  const agentIds = await listSubagentIdsFromDisk(sessionId, cwd);
727
- const subagentsDir = join(claudeProjectDir(cwd), sessionId, 'subagents');
728
969
  for (const agentId of agentIds) {
729
970
  try {
730
971
  const meta = JSON.parse(await readFile(join(subagentsDir, `${agentId}.meta.json`), 'utf-8'));
@@ -740,6 +981,12 @@ export async function buildCardsFromHistory(sessionId, cwd, offset = 0, limit =
740
981
  toolUseIdToAgentId.set(toolUseId, toolUseId);
741
982
  }
742
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
+ }
743
990
  }
744
991
  catch {
745
992
  // No subagents or SDK error — fallback: agentId = toolUseId
@@ -765,12 +1012,54 @@ export async function buildCardsFromHistory(sessionId, cwd, offset = 0, limit =
765
1012
  let seq = Math.max(0, total - offset - limit);
766
1013
  const nextId = () => `${sessionId}:h:${++seq}`;
767
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
+ };
768
1047
  for (const msg of sliced) {
769
1048
  // ── System messages ──
770
1049
  if (msg.type === 'system') {
771
1050
  const subtype = msg.subtype;
772
- // Skip subagent lifecycle events (handled via subagentCards below)
773
- 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')
774
1063
  continue;
775
1064
  // Skip init and status messages (not useful in history)
776
1065
  if (subtype === 'init' || subtype === 'status' || subtype === 'session_state_changed')
@@ -778,6 +1067,12 @@ export async function buildCardsFromHistory(sessionId, cwd, offset = 0, limit =
778
1067
  if (subtype === 'compact_boundary') {
779
1068
  cards.push({ type: 'system', id: nextId(), timestamp: Date.now(), text: 'Context compacted', subtype: 'compacted' });
780
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
+ }
781
1076
  else {
782
1077
  // Unknown system subtype — show as info
783
1078
  cards.push({ type: 'system', id: nextId(), timestamp: Date.now(), text: subtype ?? 'System event', subtype: 'info' });
@@ -901,12 +1196,17 @@ export async function buildCardsFromHistory(sessionId, cwd, offset = 0, limit =
901
1196
  break;
902
1197
  case 'tool_use': {
903
1198
  if (agentToolUseIds.has(block.id)) {
904
- // Agent tool_use → create a ToolCallCard as anchor, then SubagentCard
1199
+ // Agent tool_use → SubagentCard
905
1200
  const agentId = toolUseIdToAgentId.get(block.id) ?? block.id;
906
1201
  const result = toolResults.get(block.id);
907
1202
  const description = block.input?.description ?? '';
908
1203
  const summary = result ? result.content.slice(0, 200) : undefined;
909
- // 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);
910
1210
  const subagentCard = {
911
1211
  type: 'subagent',
912
1212
  id: nextId(),
@@ -914,11 +1214,17 @@ export async function buildCardsFromHistory(sessionId, cwd, offset = 0, limit =
914
1214
  description,
915
1215
  toolUseId: block.id,
916
1216
  agentId,
917
- status: result ? 'completed' : 'running',
918
- summary,
919
- 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 } : {}),
920
1225
  };
921
1226
  cards.push(subagentCard);
1227
+ emittedSubagentKeys.add(block.id);
922
1228
  }
923
1229
  else {
924
1230
  // Normal tool call