sidekick-shared 0.29.0 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +48 -0
- package/README.md +198 -56
- package/dist/aggregation/EventAggregator.d.ts +96 -2
- package/dist/aggregation/EventAggregator.js +535 -42
- package/dist/aggregation/snapshot.d.ts +37 -8
- package/dist/aggregation/snapshot.js +31 -9
- package/dist/aggregation/types.d.ts +38 -1
- package/dist/browser.d.ts +9 -1
- package/dist/browser.js +29 -3
- package/dist/context/sessionContext.js +8 -0
- package/dist/esm/aggregation/EventAggregator.js +535 -42
- package/dist/esm/aggregation/snapshot.js +31 -9
- package/dist/esm/browser.js +3 -0
- package/dist/esm/context/sessionContext.js +8 -0
- package/dist/esm/fileContinuity.js +149 -0
- package/dist/esm/formatters/toolSummary.js +4 -1
- package/dist/esm/index.js +7 -2
- package/dist/esm/modelCatalog.js +5 -4
- package/dist/esm/modelContext.js +4 -3
- package/dist/esm/modelInfo.js +4 -3
- package/dist/esm/node.js +3 -1
- package/dist/esm/observedContextWindows.js +2 -1
- package/dist/esm/ownKey.js +26 -0
- package/dist/esm/parsers/codexParser.js +372 -7
- package/dist/esm/parsers/openCodeParser.js +2 -1
- package/dist/esm/parsers/subagentEndState.js +61 -0
- package/dist/esm/parsers/subagentScanner.js +37 -12
- package/dist/esm/parsers/subagentTraceParser.js +103 -37
- package/dist/esm/pricingCatalog.js +3 -2
- package/dist/esm/promptHistory.js +22 -11
- package/dist/esm/providers/claudeCode.js +60 -29
- package/dist/esm/providers/codex.js +235 -201
- package/dist/esm/providers/codexCompressedReader.js +184 -0
- package/dist/esm/providers/codexDatabase.js +47 -2
- package/dist/esm/providers/codexRollouts.js +20 -2
- package/dist/esm/providers/codexSubagents.js +456 -0
- package/dist/esm/providers/compressedRollouts.js +330 -0
- package/dist/esm/providers/factory.js +4 -1
- package/dist/esm/providers/rolloutWalker.js +32 -4
- package/dist/esm/schemas/sessionEvent.js +63 -14
- package/dist/esm/sessionMonitor.js +7 -1
- package/dist/esm/sessionPreviews.js +9 -0
- package/dist/esm/sessionPromptHistory.js +5 -3
- package/dist/esm/subagentActivity.js +125 -0
- package/dist/esm/subagentDisplayState.js +81 -0
- package/dist/esm/subagentSpawn.js +363 -0
- package/dist/esm/testing/codexSubagentFixtures.js +336 -0
- package/dist/esm/toolCallOrigins.js +17 -10
- package/dist/esm/toolCategory.js +6 -2
- package/dist/esm/transcript.js +8 -0
- package/dist/esm/turns/assistantTurn.js +6 -2
- package/dist/esm/usage/claudeUsageDedupe.js +68 -0
- package/dist/esm/usage/usageEvents.js +4 -1
- package/dist/esm/watchers/eventBridge.js +45 -0
- package/dist/esm/watchers/factory.js +5 -3
- package/dist/esm/watchers/jsonlTail.js +82 -48
- package/dist/esm/watchers/jsonlWatcher.js +61 -4
- package/dist/esm/watchers/providerReaderWatcher.js +18 -4
- package/dist/esm/zaiQuota.js +2 -1
- package/dist/fileContinuity.d.ts +95 -0
- package/dist/fileContinuity.js +187 -0
- package/dist/formatters/toolSummary.js +4 -1
- package/dist/index.d.ts +19 -7
- package/dist/index.js +48 -10
- package/dist/modelCatalog.js +5 -4
- package/dist/modelContext.js +4 -3
- package/dist/modelInfo.js +4 -3
- package/dist/node.d.ts +5 -1
- package/dist/node.js +18 -2
- package/dist/observedContextWindows.js +2 -1
- package/dist/ownKey.d.ts +16 -0
- package/dist/ownKey.js +30 -0
- package/dist/parsers/codexParser.d.ts +107 -0
- package/dist/parsers/codexParser.js +373 -7
- package/dist/parsers/openCodeParser.js +2 -1
- package/dist/parsers/subagentEndState.d.ts +20 -0
- package/dist/parsers/subagentEndState.js +65 -0
- package/dist/parsers/subagentScanner.d.ts +19 -6
- package/dist/parsers/subagentScanner.js +37 -12
- package/dist/parsers/subagentTraceParser.d.ts +33 -5
- package/dist/parsers/subagentTraceParser.js +103 -37
- package/dist/pricingCatalog.js +3 -2
- package/dist/promptHistory.d.ts +13 -3
- package/dist/promptHistory.js +22 -11
- package/dist/providers/claudeCode.js +59 -28
- package/dist/providers/codex.d.ts +35 -5
- package/dist/providers/codex.js +232 -198
- package/dist/providers/codexCompressedReader.d.ts +58 -0
- package/dist/providers/codexCompressedReader.js +221 -0
- package/dist/providers/codexDatabase.d.ts +25 -0
- package/dist/providers/codexDatabase.js +48 -2
- package/dist/providers/codexRollouts.d.ts +16 -2
- package/dist/providers/codexRollouts.js +20 -2
- package/dist/providers/codexSubagents.d.ts +66 -0
- package/dist/providers/codexSubagents.js +495 -0
- package/dist/providers/compressedRollouts.d.ts +114 -0
- package/dist/providers/compressedRollouts.js +380 -0
- package/dist/providers/factory.d.ts +5 -0
- package/dist/providers/factory.js +4 -1
- package/dist/providers/rolloutWalker.d.ts +15 -2
- package/dist/providers/rolloutWalker.js +33 -4
- package/dist/providers/types.d.ts +36 -1
- package/dist/schemas/index.d.ts +1 -0
- package/dist/schemas/sessionEvent.d.ts +51 -10
- package/dist/schemas/sessionEvent.js +63 -14
- package/dist/sessionMonitor.d.ts +6 -0
- package/dist/sessionMonitor.js +7 -1
- package/dist/sessionPreviews.js +9 -0
- package/dist/sessionPromptHistory.d.ts +5 -0
- package/dist/sessionPromptHistory.js +5 -36
- package/dist/subagentActivity.d.ts +91 -0
- package/dist/subagentActivity.js +132 -0
- package/dist/subagentDisplayState.d.ts +81 -0
- package/dist/subagentDisplayState.js +86 -0
- package/dist/subagentSpawn.d.ts +175 -0
- package/dist/subagentSpawn.js +385 -0
- package/dist/testing/codexSubagentFixtures.d.ts +48 -0
- package/dist/testing/codexSubagentFixtures.js +344 -0
- package/dist/toolCallOrigins.d.ts +5 -0
- package/dist/toolCallOrigins.js +17 -10
- package/dist/toolCategory.js +6 -2
- package/dist/transcript.js +8 -0
- package/dist/turns/assistantTurn.d.ts +4 -0
- package/dist/turns/assistantTurn.js +6 -2
- package/dist/types/codex.d.ts +71 -2
- package/dist/types/sessionEvent.d.ts +38 -3
- package/dist/usage/claudeUsageDedupe.d.ts +31 -0
- package/dist/usage/claudeUsageDedupe.js +68 -0
- package/dist/usage/usageEvents.d.ts +4 -1
- package/dist/usage/usageEvents.js +4 -1
- package/dist/watchers/eventBridge.js +45 -0
- package/dist/watchers/factory.d.ts +6 -0
- package/dist/watchers/factory.js +5 -3
- package/dist/watchers/jsonlTail.d.ts +27 -4
- package/dist/watchers/jsonlTail.js +84 -48
- package/dist/watchers/jsonlWatcher.d.ts +22 -1
- package/dist/watchers/jsonlWatcher.js +60 -3
- package/dist/watchers/providerReaderWatcher.d.ts +7 -0
- package/dist/watchers/providerReaderWatcher.js +18 -4
- package/dist/watchers/types.d.ts +12 -0
- package/dist/zaiQuota.js +2 -1
- package/package.json +1 -1
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Whether a Claude Code subagent transcript ended cleanly, read line by line.
|
|
4
|
+
*
|
|
5
|
+
* Clean means: the last user or assistant line is an assistant line that
|
|
6
|
+
* stopped with `end_turn` or `stop_sequence`, and every `tool_use` in the
|
|
7
|
+
* transcript has its `tool_result`. A user line after that (a resumed agent's
|
|
8
|
+
* new prompt) makes it unfinished again.
|
|
9
|
+
*
|
|
10
|
+
* Pure. Internal to the subagent parsers.
|
|
11
|
+
*
|
|
12
|
+
* @module parsers/subagentEndState
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.SubagentEndTracker = void 0;
|
|
16
|
+
const ENDED_STOP_REASONS = new Set(['end_turn', 'stop_sequence']);
|
|
17
|
+
/** Bounds the unanswered-call set for pathological transcripts. */
|
|
18
|
+
const MAX_PENDING_TOOL_USES = 4096;
|
|
19
|
+
class SubagentEndTracker {
|
|
20
|
+
unanswered = new Set();
|
|
21
|
+
lastWasCleanAssistant = false;
|
|
22
|
+
/** Feeds one parsed JSONL line, in file order. */
|
|
23
|
+
observe(raw) {
|
|
24
|
+
if (!raw || typeof raw !== 'object')
|
|
25
|
+
return;
|
|
26
|
+
const line = raw;
|
|
27
|
+
if (line.type !== 'assistant' && line.type !== 'user')
|
|
28
|
+
return;
|
|
29
|
+
const message = line.message && typeof line.message === 'object'
|
|
30
|
+
? line.message
|
|
31
|
+
: undefined;
|
|
32
|
+
const content = Array.isArray(message?.content) ? message.content : [];
|
|
33
|
+
if (line.type === 'user') {
|
|
34
|
+
for (const block of content) {
|
|
35
|
+
if (!block || typeof block !== 'object')
|
|
36
|
+
continue;
|
|
37
|
+
const typed = block;
|
|
38
|
+
if (typed.type === 'tool_result' && typeof typed.tool_use_id === 'string') {
|
|
39
|
+
this.unanswered.delete(typed.tool_use_id);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
this.lastWasCleanAssistant = false;
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
let callsTool = false;
|
|
46
|
+
for (const block of content) {
|
|
47
|
+
if (!block || typeof block !== 'object')
|
|
48
|
+
continue;
|
|
49
|
+
const typed = block;
|
|
50
|
+
if (typed.type !== 'tool_use')
|
|
51
|
+
continue;
|
|
52
|
+
callsTool = true;
|
|
53
|
+
if (typeof typed.id === 'string' && this.unanswered.size < MAX_PENDING_TOOL_USES) {
|
|
54
|
+
this.unanswered.add(typed.id);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const stop = typeof message?.stop_reason === 'string' ? message.stop_reason : '';
|
|
58
|
+
this.lastWasCleanAssistant = !callsTool && ENDED_STOP_REASONS.has(stop);
|
|
59
|
+
}
|
|
60
|
+
/** Whether the lines so far end cleanly. */
|
|
61
|
+
endedCleanly() {
|
|
62
|
+
return this.lastWasCleanAssistant && this.unanswered.size === 0;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
exports.SubagentEndTracker = SubagentEndTracker;
|
|
@@ -8,15 +8,28 @@
|
|
|
8
8
|
* @module parsers/subagentScanner
|
|
9
9
|
*/
|
|
10
10
|
import type { SubagentStats, TaskState } from '../types/sessionEvent';
|
|
11
|
+
/** Fields of a Claude Code `agent-<id>.meta.json`; each is present only when well-typed. */
|
|
12
|
+
export interface AgentMeta {
|
|
13
|
+
agentType?: string;
|
|
14
|
+
description?: string;
|
|
15
|
+
/** The tool_use id of the `Agent` / `Task` call that spawned this agent. */
|
|
16
|
+
toolUseId?: string;
|
|
17
|
+
/** The agent that spawned this one, for spawns nested inside a subagent. */
|
|
18
|
+
parentAgentId?: string;
|
|
19
|
+
/** `'background'` for an agent launched in the background. */
|
|
20
|
+
requestShape?: string;
|
|
21
|
+
/** Written as `true` once a background agent is done (newer Claude Code). */
|
|
22
|
+
finished?: boolean;
|
|
23
|
+
/** The user stopped the agent. */
|
|
24
|
+
stoppedByUser?: boolean;
|
|
25
|
+
}
|
|
11
26
|
/**
|
|
12
27
|
* Reads `agent-<id>.meta.json` (written by Claude Code next to the transcript)
|
|
13
|
-
* for the spawn's agent type
|
|
14
|
-
* yield an empty object
|
|
28
|
+
* for the spawn's agent type, description, spawning tool_use id, parent agent,
|
|
29
|
+
* and completion flags. Missing or malformed files yield an empty object;
|
|
30
|
+
* wrongly typed fields are left out.
|
|
15
31
|
*/
|
|
16
|
-
export declare function readAgentMeta(agentFilePath: string):
|
|
17
|
-
agentType?: string;
|
|
18
|
-
description?: string;
|
|
19
|
-
};
|
|
32
|
+
export declare function readAgentMeta(agentFilePath: string): AgentMeta;
|
|
20
33
|
/**
|
|
21
34
|
* Scans subagent directory for agent JSONL files and extracts tool calls.
|
|
22
35
|
*
|
|
@@ -51,28 +51,50 @@ const path = __importStar(require("path"));
|
|
|
51
51
|
const claudeUsageDedupe_1 = require("../usage/claudeUsageDedupe");
|
|
52
52
|
const subagentUsage_1 = require("../usage/subagentUsage");
|
|
53
53
|
const subagentStatsCache_1 = require("./subagentStatsCache");
|
|
54
|
+
const subagentSpawn_1 = require("../subagentSpawn");
|
|
55
|
+
const subagentEndState_1 = require("./subagentEndState");
|
|
54
56
|
/**
|
|
55
57
|
* Pattern for matching subagent JSONL files.
|
|
56
58
|
* Files are named like: agent-<hash>.jsonl
|
|
57
59
|
*/
|
|
58
60
|
const AGENT_FILE_PATTERN = /^agent-(.+)\.jsonl$/;
|
|
61
|
+
function metaString(value) {
|
|
62
|
+
return typeof value === 'string' && value ? value : undefined;
|
|
63
|
+
}
|
|
59
64
|
/**
|
|
60
65
|
* Reads `agent-<id>.meta.json` (written by Claude Code next to the transcript)
|
|
61
|
-
* for the spawn's agent type
|
|
62
|
-
* yield an empty object
|
|
66
|
+
* for the spawn's agent type, description, spawning tool_use id, parent agent,
|
|
67
|
+
* and completion flags. Missing or malformed files yield an empty object;
|
|
68
|
+
* wrongly typed fields are left out.
|
|
63
69
|
*/
|
|
64
70
|
function readAgentMeta(agentFilePath) {
|
|
65
71
|
try {
|
|
66
72
|
const metaPath = agentFilePath.replace(/\.jsonl$/, '.meta.json');
|
|
67
73
|
const parsed = JSON.parse(fs.readFileSync(metaPath, 'utf-8'));
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
75
|
+
return {};
|
|
76
|
+
const record = parsed;
|
|
77
|
+
const meta = {};
|
|
78
|
+
const agentType = metaString(record.agentType);
|
|
79
|
+
if (agentType)
|
|
80
|
+
meta.agentType = agentType;
|
|
81
|
+
const description = metaString(record.description);
|
|
82
|
+
if (description)
|
|
83
|
+
meta.description = description;
|
|
84
|
+
const toolUseId = metaString(record.toolUseId);
|
|
85
|
+
if (toolUseId)
|
|
86
|
+
meta.toolUseId = toolUseId;
|
|
87
|
+
const parentAgentId = metaString(record.parentAgentId);
|
|
88
|
+
if (parentAgentId)
|
|
89
|
+
meta.parentAgentId = parentAgentId;
|
|
90
|
+
const requestShape = metaString(record.requestShape);
|
|
91
|
+
if (requestShape)
|
|
92
|
+
meta.requestShape = requestShape;
|
|
93
|
+
if (typeof record.finished === 'boolean')
|
|
94
|
+
meta.finished = record.finished;
|
|
95
|
+
if (typeof record.stoppedByUser === 'boolean')
|
|
96
|
+
meta.stoppedByUser = record.stoppedByUser;
|
|
97
|
+
return meta;
|
|
76
98
|
}
|
|
77
99
|
catch {
|
|
78
100
|
return {};
|
|
@@ -178,6 +200,7 @@ function parseAgentFile(filePath, agentId, log) {
|
|
|
178
200
|
let description;
|
|
179
201
|
const tokens = { agentId, toolCalls: [], inputTokens: 0, outputTokens: 0 };
|
|
180
202
|
const usageDeduper = new claudeUsageDedupe_1.ClaudeUsageDeduper();
|
|
203
|
+
const endTracker = new subagentEndState_1.SubagentEndTracker();
|
|
181
204
|
let startTime;
|
|
182
205
|
let endTime;
|
|
183
206
|
// Task tracking state
|
|
@@ -194,6 +217,7 @@ function parseAgentFile(filePath, agentId, log) {
|
|
|
194
217
|
if (!startTime)
|
|
195
218
|
startTime = eventTimestamp;
|
|
196
219
|
endTime = eventTimestamp;
|
|
220
|
+
endTracker.observe(event);
|
|
197
221
|
// Extract token usage from assistant messages. Split lines of one
|
|
198
222
|
// response repeat its usage, so count each response once.
|
|
199
223
|
const usage = (0, claudeUsageDedupe_1.dedupedRawClaudeUsage)(event, usageDeduper);
|
|
@@ -247,8 +271,8 @@ function parseAgentFile(filePath, agentId, log) {
|
|
|
247
271
|
}
|
|
248
272
|
}
|
|
249
273
|
toolCalls.push(toolCall);
|
|
250
|
-
// If this is
|
|
251
|
-
if (toolUse.name
|
|
274
|
+
// If this is an Agent/Task call (spawning another subagent), extract info
|
|
275
|
+
if ((0, subagentSpawn_1.isSubagentSpawnTool)(toolUse.name) && toolUse.input) {
|
|
252
276
|
if (toolUse.input.subagent_type && !agentType) {
|
|
253
277
|
agentType = String(toolUse.input.subagent_type);
|
|
254
278
|
}
|
|
@@ -316,6 +340,7 @@ function parseAgentFile(filePath, agentId, log) {
|
|
|
316
340
|
startTime,
|
|
317
341
|
endTime,
|
|
318
342
|
durationMs,
|
|
343
|
+
endedCleanly: endTracker.endedCleanly(),
|
|
319
344
|
};
|
|
320
345
|
}
|
|
321
346
|
return null;
|
|
@@ -5,10 +5,13 @@
|
|
|
5
5
|
* (not just stats) for drill-down trace visualization. Applies noise classification
|
|
6
6
|
* and tool summary formatting from the formatters module.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
* 1.
|
|
10
|
-
* 2.
|
|
11
|
-
*
|
|
8
|
+
* Parent-child linking, exact links first:
|
|
9
|
+
* 1. Meta `parentAgentId` (Claude Code writes it for nested spawns)
|
|
10
|
+
* 2. The child's spawning tool_use id (meta `toolUseId`) found in another
|
|
11
|
+
* trace's `Agent` / `Task` calls
|
|
12
|
+
* Only traces without that spawn metadata (older Claude Code) fall back to:
|
|
13
|
+
* 3. Team summary: parse <teammate-message> XML blocks
|
|
14
|
+
* 4. Positional fallback: timestamp ordering + proximity
|
|
12
15
|
*
|
|
13
16
|
* @module parsers/subagentTraceParser
|
|
14
17
|
*/
|
|
@@ -41,8 +44,33 @@ export interface SubagentTrace {
|
|
|
41
44
|
children: SubagentTrace[];
|
|
42
45
|
/** Basic stats (tokens, duration, etc.) */
|
|
43
46
|
stats: SubagentStats;
|
|
44
|
-
/**
|
|
47
|
+
/**
|
|
48
|
+
* Parent tool_use_id that spawned this agent: the spawning `Agent` / `Task`
|
|
49
|
+
* call's id when Claude Code recorded it (also for top-level traces, whose
|
|
50
|
+
* spawn is in the main session). For a heuristic link without that
|
|
51
|
+
* metadata it holds the parent's agent id, as before; see `parentAgentId`.
|
|
52
|
+
*/
|
|
45
53
|
parentToolUseId?: string;
|
|
54
|
+
/** Path of this agent's `agent-<id>.jsonl` transcript. */
|
|
55
|
+
filePath?: string;
|
|
56
|
+
/** The tool_use id of the call that spawned this agent (meta `toolUseId`). */
|
|
57
|
+
spawnToolUseId?: string;
|
|
58
|
+
/** Agent id of the parent trace, for agents spawned by another subagent. */
|
|
59
|
+
parentAgentId?: string;
|
|
60
|
+
/** Launched in the background (meta `requestShape: 'background'`). */
|
|
61
|
+
background?: boolean;
|
|
62
|
+
/** Claude Code marked the background agent done (meta `finished`). */
|
|
63
|
+
finished?: boolean;
|
|
64
|
+
/** The user stopped the agent (meta `stoppedByUser`). */
|
|
65
|
+
stoppedByUser?: boolean;
|
|
66
|
+
/** Time of the transcript's last record (the same as `stats.endTime`). */
|
|
67
|
+
lastActivityAt?: Date;
|
|
68
|
+
/**
|
|
69
|
+
* The transcript ended cleanly: its last assistant turn stopped with
|
|
70
|
+
* `end_turn` / `stop_sequence` and no tool call is unanswered. A resumed
|
|
71
|
+
* agent's next prompt clears it.
|
|
72
|
+
*/
|
|
73
|
+
endedCleanly?: boolean;
|
|
46
74
|
}
|
|
47
75
|
/**
|
|
48
76
|
* Scans and parses subagent traces with full conversation events.
|
|
@@ -6,10 +6,13 @@
|
|
|
6
6
|
* (not just stats) for drill-down trace visualization. Applies noise classification
|
|
7
7
|
* and tool summary formatting from the formatters module.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
* 1.
|
|
11
|
-
* 2.
|
|
12
|
-
*
|
|
9
|
+
* Parent-child linking, exact links first:
|
|
10
|
+
* 1. Meta `parentAgentId` (Claude Code writes it for nested spawns)
|
|
11
|
+
* 2. The child's spawning tool_use id (meta `toolUseId`) found in another
|
|
12
|
+
* trace's `Agent` / `Task` calls
|
|
13
|
+
* Only traces without that spawn metadata (older Claude Code) fall back to:
|
|
14
|
+
* 3. Team summary: parse <teammate-message> XML blocks
|
|
15
|
+
* 4. Positional fallback: timestamp ordering + proximity
|
|
13
16
|
*
|
|
14
17
|
* @module parsers/subagentTraceParser
|
|
15
18
|
*/
|
|
@@ -55,6 +58,9 @@ const noiseClassifier_1 = require("../formatters/noiseClassifier");
|
|
|
55
58
|
const claudeUsageDedupe_1 = require("../usage/claudeUsageDedupe");
|
|
56
59
|
const subagentUsage_1 = require("../usage/subagentUsage");
|
|
57
60
|
const subagentScanner_1 = require("./subagentScanner");
|
|
61
|
+
const subagentEndState_1 = require("./subagentEndState");
|
|
62
|
+
const subagentSpawn_1 = require("../subagentSpawn");
|
|
63
|
+
const ownKey_1 = require("../ownKey");
|
|
58
64
|
// ── Constants ──
|
|
59
65
|
const AGENT_FILE_PATTERN = /^agent-(.+)\.jsonl$/;
|
|
60
66
|
// ── Public API ──
|
|
@@ -86,10 +92,8 @@ function scanSubagentTraces(sessionDir, sessionId) {
|
|
|
86
92
|
if (trace)
|
|
87
93
|
traces.push(trace);
|
|
88
94
|
}
|
|
89
|
-
// Link parent-child relationships
|
|
90
|
-
linkTraces(traces);
|
|
91
|
-
// Return only top-level traces (those without parents)
|
|
92
|
-
return traces.filter((t) => !t.parentToolUseId);
|
|
95
|
+
// Link parent-child relationships; returns the traces with no parent agent.
|
|
96
|
+
return linkTraces(traces);
|
|
93
97
|
}
|
|
94
98
|
catch {
|
|
95
99
|
return [];
|
|
@@ -106,6 +110,7 @@ function parseAgentTrace(filePath, agentId) {
|
|
|
106
110
|
let description;
|
|
107
111
|
const tokens = { agentId, toolCalls: [], inputTokens: 0, outputTokens: 0 };
|
|
108
112
|
const usageDeduper = new claudeUsageDedupe_1.ClaudeUsageDeduper();
|
|
113
|
+
const endTracker = new subagentEndState_1.SubagentEndTracker();
|
|
109
114
|
let startTime;
|
|
110
115
|
let endTime;
|
|
111
116
|
for (const line of lines) {
|
|
@@ -115,6 +120,7 @@ function parseAgentTrace(filePath, agentId) {
|
|
|
115
120
|
if (!startTime)
|
|
116
121
|
startTime = timestamp;
|
|
117
122
|
endTime = timestamp;
|
|
123
|
+
endTracker.observe(raw);
|
|
118
124
|
// Build SessionEvent from raw JSONL
|
|
119
125
|
const sessionEvent = rawToSessionEvent(raw);
|
|
120
126
|
if (!sessionEvent)
|
|
@@ -169,8 +175,8 @@ function parseAgentTrace(filePath, agentId) {
|
|
|
169
175
|
input: block.input || {},
|
|
170
176
|
timestamp,
|
|
171
177
|
});
|
|
172
|
-
// Extract agent info from Task
|
|
173
|
-
if (block.name
|
|
178
|
+
// Extract agent info from an Agent/Task spawn
|
|
179
|
+
if ((0, subagentSpawn_1.isSubagentSpawnTool)(block.name) && block.input) {
|
|
174
180
|
if (block.input.subagent_type && !agentType) {
|
|
175
181
|
agentType = String(block.input.subagent_type);
|
|
176
182
|
}
|
|
@@ -207,6 +213,16 @@ function parseAgentTrace(filePath, agentId) {
|
|
|
207
213
|
agentId,
|
|
208
214
|
agentType,
|
|
209
215
|
description,
|
|
216
|
+
filePath,
|
|
217
|
+
...(meta.toolUseId
|
|
218
|
+
? { spawnToolUseId: meta.toolUseId, parentToolUseId: meta.toolUseId }
|
|
219
|
+
: {}),
|
|
220
|
+
...(meta.parentAgentId ? { parentAgentId: meta.parentAgentId } : {}),
|
|
221
|
+
...(meta.requestShape ? { background: meta.requestShape === 'background' } : {}),
|
|
222
|
+
...(meta.finished !== undefined ? { finished: meta.finished } : {}),
|
|
223
|
+
...(meta.stoppedByUser !== undefined ? { stoppedByUser: meta.stoppedByUser } : {}),
|
|
224
|
+
...(endTime ? { lastActivityAt: endTime } : {}),
|
|
225
|
+
endedCleanly: endTracker.endedCleanly(),
|
|
210
226
|
events,
|
|
211
227
|
children: [],
|
|
212
228
|
stats: {
|
|
@@ -223,6 +239,7 @@ function parseAgentTrace(filePath, agentId) {
|
|
|
223
239
|
startTime,
|
|
224
240
|
endTime,
|
|
225
241
|
durationMs,
|
|
242
|
+
endedCleanly: endTracker.endedCleanly(),
|
|
226
243
|
},
|
|
227
244
|
};
|
|
228
245
|
}
|
|
@@ -231,23 +248,61 @@ function parseAgentTrace(filePath, agentId) {
|
|
|
231
248
|
}
|
|
232
249
|
}
|
|
233
250
|
// ── Parent-Child Linking ──
|
|
251
|
+
/**
|
|
252
|
+
* Nests child traces under their parents and returns the traces with no
|
|
253
|
+
* parent agent. Exact metadata links come first; the heuristics only place
|
|
254
|
+
* traces Claude Code recorded no spawn metadata for.
|
|
255
|
+
*/
|
|
234
256
|
function linkTraces(traces) {
|
|
235
257
|
const traceById = new Map();
|
|
236
258
|
for (const trace of traces) {
|
|
237
259
|
traceById.set(trace.agentId, trace);
|
|
238
260
|
}
|
|
239
|
-
|
|
240
|
-
|
|
261
|
+
const parentOf = new Map();
|
|
262
|
+
const attach = (parent, child) => {
|
|
263
|
+
if (parent === child || parentOf.has(child))
|
|
264
|
+
return false;
|
|
265
|
+
// Never nest an agent under its own descendant (malformed metadata).
|
|
266
|
+
for (let up = parent; up; up = parentOf.get(up)) {
|
|
267
|
+
if (up === child)
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
parentOf.set(child, parent);
|
|
271
|
+
parent.children.push(child);
|
|
272
|
+
return true;
|
|
273
|
+
};
|
|
274
|
+
// Phase 1: the meta file names the parent agent
|
|
275
|
+
for (const child of traces) {
|
|
276
|
+
const parent = child.parentAgentId ? traceById.get(child.parentAgentId) : undefined;
|
|
277
|
+
if (parent)
|
|
278
|
+
attach(parent, child);
|
|
279
|
+
}
|
|
280
|
+
// Phase 2: the child's spawning call is in another trace
|
|
281
|
+
const spawnOwner = new Map();
|
|
241
282
|
for (const trace of traces) {
|
|
242
|
-
for (const
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
// The tool_use_id from this call should appear as a result in the parent
|
|
246
|
-
// but we can link via timestamp proximity to child traces
|
|
247
|
-
}
|
|
283
|
+
for (const id of spawnToolUseIdsOf(trace)) {
|
|
284
|
+
if (!spawnOwner.has(id))
|
|
285
|
+
spawnOwner.set(id, trace);
|
|
248
286
|
}
|
|
249
287
|
}
|
|
250
|
-
|
|
288
|
+
for (const child of traces) {
|
|
289
|
+
if (parentOf.has(child) || !child.spawnToolUseId)
|
|
290
|
+
continue;
|
|
291
|
+
const parent = spawnOwner.get(child.spawnToolUseId);
|
|
292
|
+
if (parent && attach(parent, child))
|
|
293
|
+
child.parentAgentId ??= parent.agentId;
|
|
294
|
+
}
|
|
295
|
+
// Heuristic links set parentAgentId and, as before, parentToolUseId to the
|
|
296
|
+
// parent's agent id.
|
|
297
|
+
const unresolved = (trace) => !parentOf.has(trace) && !trace.spawnToolUseId && !trace.parentAgentId;
|
|
298
|
+
const linkHeuristically = (parent, child) => {
|
|
299
|
+
if (!attach(parent, child))
|
|
300
|
+
return false;
|
|
301
|
+
child.parentAgentId = parent.agentId;
|
|
302
|
+
child.parentToolUseId = parent.agentId;
|
|
303
|
+
return true;
|
|
304
|
+
};
|
|
305
|
+
// Phase 3: Team summary linking
|
|
251
306
|
for (const trace of traces) {
|
|
252
307
|
for (const traceEvent of trace.events) {
|
|
253
308
|
const content = traceEvent.event.message?.content;
|
|
@@ -255,45 +310,56 @@ function linkTraces(traces) {
|
|
|
255
310
|
// Parse teammate agent IDs from XML blocks
|
|
256
311
|
const matches = content.matchAll(/agent[_-]id['":\s]+([a-f0-9]+)/gi);
|
|
257
312
|
for (const match of matches) {
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
child.parentToolUseId = trace.agentId;
|
|
262
|
-
trace.children.push(child);
|
|
263
|
-
}
|
|
313
|
+
const child = traceById.get(match[1]);
|
|
314
|
+
if (child && unresolved(child))
|
|
315
|
+
linkHeuristically(trace, child);
|
|
264
316
|
}
|
|
265
317
|
}
|
|
266
318
|
}
|
|
267
319
|
}
|
|
268
|
-
// Phase
|
|
320
|
+
// Phase 4: Positional fallback — timestamp proximity
|
|
269
321
|
// Traces without parents that fall within another trace's time window
|
|
270
|
-
const
|
|
271
|
-
for (const child of parentless) {
|
|
322
|
+
for (const child of traces.filter(unresolved)) {
|
|
272
323
|
if (!child.stats.startTime)
|
|
273
324
|
continue;
|
|
274
325
|
const childStart = child.stats.startTime.getTime();
|
|
275
|
-
// Find a trace that
|
|
326
|
+
// Find a top-level trace that spawned an agent right before this child started
|
|
276
327
|
for (const potential of traces) {
|
|
277
|
-
if (potential === child)
|
|
328
|
+
if (potential === child || parentOf.has(potential))
|
|
278
329
|
continue;
|
|
279
|
-
if (potential.parentToolUseId)
|
|
280
|
-
continue; // Already linked
|
|
281
330
|
for (const traceEvent of potential.events) {
|
|
282
331
|
const evt = traceEvent.event;
|
|
283
|
-
if (evt.type === 'assistant' && evt.tool?.name
|
|
332
|
+
if (evt.type === 'assistant' && (0, subagentSpawn_1.isSubagentSpawnTool)(evt.tool?.name)) {
|
|
284
333
|
const taskTime = new Date(evt.timestamp).getTime();
|
|
285
334
|
// Within 2 seconds before child start
|
|
286
335
|
if (taskTime <= childStart && childStart - taskTime < 2000) {
|
|
287
|
-
child
|
|
288
|
-
potential.children.push(child);
|
|
336
|
+
linkHeuristically(potential, child);
|
|
289
337
|
break;
|
|
290
338
|
}
|
|
291
339
|
}
|
|
292
340
|
}
|
|
293
|
-
if (child
|
|
341
|
+
if (parentOf.has(child))
|
|
294
342
|
break;
|
|
295
343
|
}
|
|
296
344
|
}
|
|
345
|
+
return traces.filter((trace) => !parentOf.has(trace));
|
|
346
|
+
}
|
|
347
|
+
/** Ids of the `Agent` / `Task` calls a trace made. */
|
|
348
|
+
function spawnToolUseIdsOf(trace) {
|
|
349
|
+
const ids = [];
|
|
350
|
+
for (const traceEvent of trace.events) {
|
|
351
|
+
const content = traceEvent.event.message?.content;
|
|
352
|
+
if (!Array.isArray(content))
|
|
353
|
+
continue;
|
|
354
|
+
for (const block of content) {
|
|
355
|
+
if (block?.type === 'tool_use' &&
|
|
356
|
+
typeof block.id === 'string' &&
|
|
357
|
+
(0, subagentSpawn_1.isSubagentSpawnTool)(block.name)) {
|
|
358
|
+
ids.push(block.id);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return ids;
|
|
297
363
|
}
|
|
298
364
|
// ── Helpers ──
|
|
299
365
|
function rawToSessionEvent(raw) {
|
|
@@ -308,7 +374,7 @@ function rawToSessionEvent(raw) {
|
|
|
308
374
|
tool_result: 'tool_result',
|
|
309
375
|
summary: 'summary',
|
|
310
376
|
};
|
|
311
|
-
const mappedType = typeMap
|
|
377
|
+
const mappedType = (0, ownKey_1.ownValue)(typeMap, type);
|
|
312
378
|
if (!mappedType)
|
|
313
379
|
return null;
|
|
314
380
|
const message = raw.message;
|
package/dist/pricingCatalog.js
CHANGED
|
@@ -59,6 +59,7 @@ const node_fs_1 = require("node:fs");
|
|
|
59
59
|
const path = __importStar(require("node:path"));
|
|
60
60
|
const modelContext_1 = require("./modelContext");
|
|
61
61
|
const modelInfo_1 = require("./modelInfo");
|
|
62
|
+
const ownKey_1 = require("./ownKey");
|
|
62
63
|
const paths_1 = require("./paths");
|
|
63
64
|
const atomic_1 = require("./writers/atomic");
|
|
64
65
|
// ── Constants ──
|
|
@@ -156,7 +157,7 @@ function normalizeLiteLlmCatalog(raw) {
|
|
|
156
157
|
continue;
|
|
157
158
|
// Catalog keys use both `provider/model` and bare `model` forms. Record both
|
|
158
159
|
// the raw key and the bare-model form so lookups work regardless of caller.
|
|
159
|
-
out
|
|
160
|
+
(0, ownKey_1.setOwnValue)(out, key, pricing);
|
|
160
161
|
const bare = stripProviderPrefix(key);
|
|
161
162
|
if (bare && bare !== key && !out[bare]) {
|
|
162
163
|
out[bare] = pricing;
|
|
@@ -193,7 +194,7 @@ function normalizeLiteLlmContextWindows(raw) {
|
|
|
193
194
|
const window = entry.max_input_tokens;
|
|
194
195
|
if (typeof window !== 'number' || !Number.isFinite(window) || window <= 0)
|
|
195
196
|
continue;
|
|
196
|
-
out
|
|
197
|
+
(0, ownKey_1.setOwnValue)(out, key, window);
|
|
197
198
|
const bare = stripProviderPrefix(key);
|
|
198
199
|
if (bare && bare !== key) {
|
|
199
200
|
const seen = aliasWindows.get(bare) ?? new Set();
|
package/dist/promptHistory.d.ts
CHANGED
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
* the prompt is returned again under the same identity once its first
|
|
24
24
|
* answering call brings model or usage (`metadataStatus` says how settled).
|
|
25
25
|
*/
|
|
26
|
-
import * as fs from 'fs';
|
|
27
26
|
import { type HumanPromptProvider } from './humanPrompt';
|
|
27
|
+
import { type RolloutContentStat } from './providers/compressedRollouts';
|
|
28
28
|
export type PromptHistoryProvider = HumanPromptProvider;
|
|
29
29
|
export type PromptHistoryBound = 'maxSessions' | 'maxFileBytes' | 'maxTotalBytes' | 'maxEntries' | 'deadline' | 'aborted';
|
|
30
30
|
export interface PromptHistoryBounds {
|
|
@@ -235,6 +235,14 @@ export interface CollectPromptHistoryOptions {
|
|
|
235
235
|
* both modes, so a cursor written in one mode resumes in the other.
|
|
236
236
|
*/
|
|
237
237
|
includeText?: boolean;
|
|
238
|
+
/**
|
|
239
|
+
* Codex: also scan compressed rollouts (`….jsonl.zst`, decompressed when
|
|
240
|
+
* the runtime has `zlib.zstdDecompressSync`; skipped otherwise). Offsets
|
|
241
|
+
* and sizes in the cursor are then those of the decompressed content, so
|
|
242
|
+
* a session whose rollout Codex compressed continues where it stopped.
|
|
243
|
+
* Default false.
|
|
244
|
+
*/
|
|
245
|
+
includeCompressedRollouts?: boolean;
|
|
238
246
|
}
|
|
239
247
|
export interface PromptHistoryResult {
|
|
240
248
|
/** Ordered by provider, session, then ordinal. */
|
|
@@ -375,6 +383,8 @@ export declare function resolveRoots(workspacePaths: string[], includeWorktrees:
|
|
|
375
383
|
listingRoots: string[];
|
|
376
384
|
};
|
|
377
385
|
/** @internal */
|
|
378
|
-
export declare function listCandidates(providers: PromptHistoryProvider[], listingRoots: string[], sinceMs: number | null
|
|
386
|
+
export declare function listCandidates(providers: PromptHistoryProvider[], listingRoots: string[], sinceMs: number | null, options?: {
|
|
387
|
+
includeCompressedRollouts?: boolean;
|
|
388
|
+
}): Promise<Candidate[]>;
|
|
379
389
|
/** @internal */
|
|
380
|
-
export declare function scanFile(candidate: Candidate,
|
|
390
|
+
export declare function scanFile(candidate: Candidate, fileStat: RolloutContentStat, prior: PromptHistoryCursorSession | undefined, budget: FileBudget, context: ScanContext): Promise<FileOutcome>;
|
package/dist/promptHistory.js
CHANGED
|
@@ -69,11 +69,11 @@ exports.resolveRoots = resolveRoots;
|
|
|
69
69
|
exports.listCandidates = listCandidates;
|
|
70
70
|
exports.scanFile = scanFile;
|
|
71
71
|
const crypto_1 = require("crypto");
|
|
72
|
-
const fs = __importStar(require("fs"));
|
|
73
72
|
const path = __importStar(require("path"));
|
|
74
73
|
const humanPrompt_1 = require("./humanPrompt");
|
|
75
74
|
const claudeListing_1 = require("./providers/claudeListing");
|
|
76
75
|
const codexRollouts_1 = require("./providers/codexRollouts");
|
|
76
|
+
const compressedRollouts_1 = require("./providers/compressedRollouts");
|
|
77
77
|
const directoryListingCache_1 = require("./providers/directoryListingCache");
|
|
78
78
|
const workspaceRoots_1 = require("./workspaceRoots");
|
|
79
79
|
const fileHead_1 = require("./fileHead");
|
|
@@ -150,7 +150,9 @@ async function collectPromptHistory(options) {
|
|
|
150
150
|
return finish();
|
|
151
151
|
const sinceMs = options.since ? options.since.getTime() : null;
|
|
152
152
|
const providers = options.providers ?? ALL_PROVIDERS;
|
|
153
|
-
const candidates = rotate(await listCandidates(providers, listingRoots, sinceMs
|
|
153
|
+
const candidates = rotate(await listCandidates(providers, listingRoots, sinceMs, {
|
|
154
|
+
includeCompressedRollouts: options.includeCompressedRollouts,
|
|
155
|
+
}), previousNext);
|
|
154
156
|
const moved = movedSessionKeys(previous, candidates);
|
|
155
157
|
const context = {
|
|
156
158
|
roots,
|
|
@@ -186,7 +188,8 @@ async function collectPromptHistory(options) {
|
|
|
186
188
|
}
|
|
187
189
|
let stat;
|
|
188
190
|
try {
|
|
189
|
-
|
|
191
|
+
// A compressed rollout's size is its decompressed content's.
|
|
192
|
+
stat = await (0, compressedRollouts_1.statRolloutContent)(candidate.filePath);
|
|
190
193
|
}
|
|
191
194
|
catch {
|
|
192
195
|
continue;
|
|
@@ -291,7 +294,7 @@ async function collectPromptHistory(options) {
|
|
|
291
294
|
*/
|
|
292
295
|
async function readPromptOrdinalMarks(provider, sessionId, filePath) {
|
|
293
296
|
const marks = [];
|
|
294
|
-
const stat = await
|
|
297
|
+
const stat = await (0, compressedRollouts_1.statRolloutContent)(filePath);
|
|
295
298
|
const unbounded = Number.MAX_SAFE_INTEGER;
|
|
296
299
|
const context = {
|
|
297
300
|
roots: [],
|
|
@@ -480,19 +483,24 @@ function scopedCwd(cwd, roots, realpaths) {
|
|
|
480
483
|
return null;
|
|
481
484
|
}
|
|
482
485
|
/** @internal */
|
|
483
|
-
async function listCandidates(providers, listingRoots, sinceMs) {
|
|
486
|
+
async function listCandidates(providers, listingRoots, sinceMs, options = {}) {
|
|
484
487
|
const byPath = new Map();
|
|
485
488
|
const listOptions = sinceMs !== null ? { since: sinceMs } : {};
|
|
486
489
|
for (const provider of [...new Set(providers)]) {
|
|
487
490
|
if (!ALL_PROVIDERS.includes(provider))
|
|
488
491
|
continue;
|
|
489
492
|
// Listings are only candidate supersets; scope is re-checked per prompt.
|
|
490
|
-
const list = provider === 'claude-code' ? claudeListing_1.listClaudeSessionFilesAsync : codexRollouts_1.listCodexSessionFilesAsync;
|
|
491
493
|
const cache = new directoryListingCache_1.DirectoryListingCache();
|
|
492
494
|
for (const root of listingRoots) {
|
|
493
495
|
let files;
|
|
494
496
|
try {
|
|
495
|
-
files =
|
|
497
|
+
files =
|
|
498
|
+
provider === 'claude-code'
|
|
499
|
+
? await (0, claudeListing_1.listClaudeSessionFilesAsync)(root, listOptions, cache)
|
|
500
|
+
: await (0, codexRollouts_1.listCodexSessionFilesAsync)(root, {
|
|
501
|
+
...listOptions,
|
|
502
|
+
includeCompressed: options.includeCompressedRollouts === true && (0, compressedRollouts_1.canReadCompressedRollouts)(),
|
|
503
|
+
}, cache);
|
|
496
504
|
}
|
|
497
505
|
catch {
|
|
498
506
|
files = [];
|
|
@@ -514,11 +522,11 @@ async function listCandidates(providers, listingRoots, sinceMs) {
|
|
|
514
522
|
compareStrings(left.sessionId, right.sessionId) ||
|
|
515
523
|
compareStrings(left.key, right.key));
|
|
516
524
|
}
|
|
517
|
-
/** True when the file's first bytes still match a recorded head. */
|
|
525
|
+
/** True when the file's first bytes (a compressed rollout's content) still match a recorded head. */
|
|
518
526
|
async function fileStartsWith(filePath, head) {
|
|
519
527
|
let handle;
|
|
520
528
|
try {
|
|
521
|
-
handle = await
|
|
529
|
+
({ handle } = await (0, compressedRollouts_1.openRolloutReadHandle)(filePath));
|
|
522
530
|
return (0, fileHead_1.headMatches)(head, await readAt(handle, 0, Math.min(fileHead_1.HEAD_BYTES, head.bytes)));
|
|
523
531
|
}
|
|
524
532
|
catch {
|
|
@@ -591,8 +599,11 @@ async function verifyFile(handle, size, session) {
|
|
|
591
599
|
return { head: (0, fileHead_1.fileHeadOf)(head), bytesRead };
|
|
592
600
|
}
|
|
593
601
|
/** @internal */
|
|
594
|
-
async function scanFile(candidate,
|
|
595
|
-
|
|
602
|
+
async function scanFile(candidate, fileStat, prior, budget, context) {
|
|
603
|
+
// A compressed rollout is scanned over its decompressed content.
|
|
604
|
+
const opened = await (0, compressedRollouts_1.openRolloutReadHandle)(candidate.filePath);
|
|
605
|
+
const handle = opened.handle;
|
|
606
|
+
const stat = opened.size === null ? fileStat : { size: opened.size, mtimeMs: fileStat.mtimeMs };
|
|
596
607
|
try {
|
|
597
608
|
let bytesRead = 0;
|
|
598
609
|
const verified = await verifyFile(handle, stat.size, prior);
|