@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.
- package/dist/ai/cardBuilder.d.ts +65 -4
- package/dist/ai/cardBuilder.d.ts.map +1 -1
- package/dist/ai/cardBuilder.js +338 -28
- package/dist/ai/cardBuilder.js.map +1 -1
- package/dist/ai/claudeCliProvider.d.ts +15 -0
- package/dist/ai/claudeCliProvider.d.ts.map +1 -1
- package/dist/ai/claudeCliProvider.js +87 -3
- package/dist/ai/claudeCliProvider.js.map +1 -1
- package/dist/ai/claudeCodeProvider.d.ts +4 -0
- package/dist/ai/claudeCodeProvider.d.ts.map +1 -1
- package/dist/ai/claudeCodeProvider.js +41 -0
- package/dist/ai/claudeCodeProvider.js.map +1 -1
- package/dist/ai/claudeSdkProvider.d.ts +1 -0
- package/dist/ai/claudeSdkProvider.d.ts.map +1 -1
- package/dist/ai/claudeSdkProvider.js +14 -2
- package/dist/ai/claudeSdkProvider.js.map +1 -1
- package/dist/ai/claudeTerminal/cardSynth.d.ts +63 -0
- package/dist/ai/claudeTerminal/cardSynth.d.ts.map +1 -0
- package/dist/ai/claudeTerminal/cardSynth.js +92 -0
- package/dist/ai/claudeTerminal/cardSynth.js.map +1 -0
- package/dist/ai/claudeTerminal/hookBridge.d.ts +56 -0
- package/dist/ai/claudeTerminal/hookBridge.d.ts.map +1 -0
- package/dist/ai/claudeTerminal/hookBridge.js +131 -0
- package/dist/ai/claudeTerminal/hookBridge.js.map +1 -0
- package/dist/ai/claudeTerminal/hookHandler.d.ts +18 -0
- package/dist/ai/claudeTerminal/hookHandler.d.ts.map +1 -0
- package/dist/ai/claudeTerminal/hookHandler.js +124 -0
- package/dist/ai/claudeTerminal/hookHandler.js.map +1 -0
- package/dist/ai/claudeTerminal/index.d.ts +5 -0
- package/dist/ai/claudeTerminal/index.d.ts.map +1 -0
- package/dist/ai/claudeTerminal/index.js +7 -0
- package/dist/ai/claudeTerminal/index.js.map +1 -0
- package/dist/ai/claudeTerminal/jsonlTail.d.ts +40 -0
- package/dist/ai/claudeTerminal/jsonlTail.d.ts.map +1 -0
- package/dist/ai/claudeTerminal/jsonlTail.js +129 -0
- package/dist/ai/claudeTerminal/jsonlTail.js.map +1 -0
- package/dist/ai/claudeTerminal/provider.d.ts +106 -0
- package/dist/ai/claudeTerminal/provider.d.ts.map +1 -0
- package/dist/ai/claudeTerminal/provider.js +579 -0
- package/dist/ai/claudeTerminal/provider.js.map +1 -0
- package/dist/ai/claudeTerminal/settingsBuilder.d.ts +44 -0
- package/dist/ai/claudeTerminal/settingsBuilder.d.ts.map +1 -0
- package/dist/ai/claudeTerminal/settingsBuilder.js +40 -0
- package/dist/ai/claudeTerminal/settingsBuilder.js.map +1 -0
- package/dist/ai/codexAppServer/cardAdapter.d.ts.map +1 -1
- package/dist/ai/codexAppServer/cardAdapter.js +13 -5
- package/dist/ai/codexAppServer/cardAdapter.js.map +1 -1
- package/dist/ai/codexAppServer/provider.d.ts +10 -2
- package/dist/ai/codexAppServer/provider.d.ts.map +1 -1
- package/dist/ai/codexAppServer/provider.js +158 -22
- package/dist/ai/codexAppServer/provider.js.map +1 -1
- package/dist/ai/openCodeProvider.d.ts +106 -0
- package/dist/ai/openCodeProvider.d.ts.map +1 -0
- package/dist/ai/openCodeProvider.js +617 -0
- package/dist/ai/openCodeProvider.js.map +1 -0
- package/dist/ai/openCodeServer.d.ts +88 -0
- package/dist/ai/openCodeServer.d.ts.map +1 -0
- package/dist/ai/openCodeServer.js +350 -0
- package/dist/ai/openCodeServer.js.map +1 -0
- package/dist/ai/profiles/project-sandbox.sb +26 -0
- package/dist/ai/provider.d.ts +54 -1
- package/dist/ai/provider.d.ts.map +1 -1
- package/dist/ai/provider.js +10 -0
- package/dist/ai/provider.js.map +1 -1
- package/dist/ai/queuedUserPrompts.d.ts +8 -0
- package/dist/ai/queuedUserPrompts.d.ts.map +1 -0
- package/dist/ai/queuedUserPrompts.js +19 -0
- package/dist/ai/queuedUserPrompts.js.map +1 -0
- package/dist/ai/realtimeTranscription.d.ts +39 -0
- package/dist/ai/realtimeTranscription.d.ts.map +1 -0
- package/dist/ai/realtimeTranscription.js +154 -0
- package/dist/ai/realtimeTranscription.js.map +1 -0
- package/dist/ai/sandboxMcp.d.ts +7 -0
- package/dist/ai/sandboxMcp.d.ts.map +1 -1
- package/dist/ai/sandboxMcp.js +4 -0
- package/dist/ai/sandboxMcp.js.map +1 -1
- package/dist/ai/sandboxMcpStdio.d.ts +1 -1
- package/dist/ai/sandboxMcpStdio.js +120 -47
- package/dist/ai/sandboxMcpStdio.js.map +1 -1
- package/dist/ai/sessionManager.d.ts +20 -1
- package/dist/ai/sessionManager.d.ts.map +1 -1
- package/dist/ai/sessionManager.js +220 -10
- package/dist/ai/sessionManager.js.map +1 -1
- package/dist/ai/sessionRegistry.d.ts +6 -0
- package/dist/ai/sessionRegistry.d.ts.map +1 -1
- package/dist/ai/sessionRegistry.js +13 -0
- package/dist/ai/sessionRegistry.js.map +1 -1
- package/dist/ai/sessionRegistryLocator.d.ts +14 -0
- package/dist/ai/sessionRegistryLocator.d.ts.map +1 -0
- package/dist/ai/sessionRegistryLocator.js +44 -0
- package/dist/ai/sessionRegistryLocator.js.map +1 -0
- package/dist/ai/systemPrompt.d.ts.map +1 -1
- package/dist/ai/systemPrompt.js +2 -1
- package/dist/ai/systemPrompt.js.map +1 -1
- package/dist/ai/voiceStream.d.ts +74 -0
- package/dist/ai/voiceStream.d.ts.map +1 -0
- package/dist/ai/voiceStream.js +179 -0
- package/dist/ai/voiceStream.js.map +1 -0
- package/dist/ai/voiceTranscription.d.ts +33 -0
- package/dist/ai/voiceTranscription.d.ts.map +1 -0
- package/dist/ai/voiceTranscription.js +122 -0
- package/dist/ai/voiceTranscription.js.map +1 -0
- package/dist/files/fileBrowser.d.ts.map +1 -1
- package/dist/files/fileBrowser.js +15 -2
- package/dist/files/fileBrowser.js.map +1 -1
- package/dist/handlers/legacyBusAdapter.d.ts +1 -1
- package/dist/handlers/legacyBusAdapter.d.ts.map +1 -1
- package/dist/handlers/legacyBusAdapter.js +7 -1
- package/dist/handlers/legacyBusAdapter.js.map +1 -1
- package/dist/handlers/messageHandler.d.ts +6 -0
- package/dist/handlers/messageHandler.d.ts.map +1 -1
- package/dist/handlers/messageHandler.js +119 -8
- package/dist/handlers/messageHandler.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/service/ipcClient.js +1 -1
- package/dist/service/ipcClient.js.map +1 -1
- package/dist/service/run.d.ts.map +1 -1
- package/dist/service/run.js +81 -3
- package/dist/service/run.js.map +1 -1
- package/dist/service/types.js +1 -1
- package/package.json +6 -3
package/dist/ai/cardBuilder.js
CHANGED
|
@@ -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
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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 =
|
|
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
|
-
|
|
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, {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
769
|
-
if (subtype === 'task_started'
|
|
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 →
|
|
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
|
-
|
|
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
|