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.
Files changed (142) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/README.md +198 -56
  3. package/dist/aggregation/EventAggregator.d.ts +96 -2
  4. package/dist/aggregation/EventAggregator.js +535 -42
  5. package/dist/aggregation/snapshot.d.ts +37 -8
  6. package/dist/aggregation/snapshot.js +31 -9
  7. package/dist/aggregation/types.d.ts +38 -1
  8. package/dist/browser.d.ts +9 -1
  9. package/dist/browser.js +29 -3
  10. package/dist/context/sessionContext.js +8 -0
  11. package/dist/esm/aggregation/EventAggregator.js +535 -42
  12. package/dist/esm/aggregation/snapshot.js +31 -9
  13. package/dist/esm/browser.js +3 -0
  14. package/dist/esm/context/sessionContext.js +8 -0
  15. package/dist/esm/fileContinuity.js +149 -0
  16. package/dist/esm/formatters/toolSummary.js +4 -1
  17. package/dist/esm/index.js +7 -2
  18. package/dist/esm/modelCatalog.js +5 -4
  19. package/dist/esm/modelContext.js +4 -3
  20. package/dist/esm/modelInfo.js +4 -3
  21. package/dist/esm/node.js +3 -1
  22. package/dist/esm/observedContextWindows.js +2 -1
  23. package/dist/esm/ownKey.js +26 -0
  24. package/dist/esm/parsers/codexParser.js +372 -7
  25. package/dist/esm/parsers/openCodeParser.js +2 -1
  26. package/dist/esm/parsers/subagentEndState.js +61 -0
  27. package/dist/esm/parsers/subagentScanner.js +37 -12
  28. package/dist/esm/parsers/subagentTraceParser.js +103 -37
  29. package/dist/esm/pricingCatalog.js +3 -2
  30. package/dist/esm/promptHistory.js +22 -11
  31. package/dist/esm/providers/claudeCode.js +60 -29
  32. package/dist/esm/providers/codex.js +235 -201
  33. package/dist/esm/providers/codexCompressedReader.js +184 -0
  34. package/dist/esm/providers/codexDatabase.js +47 -2
  35. package/dist/esm/providers/codexRollouts.js +20 -2
  36. package/dist/esm/providers/codexSubagents.js +456 -0
  37. package/dist/esm/providers/compressedRollouts.js +330 -0
  38. package/dist/esm/providers/factory.js +4 -1
  39. package/dist/esm/providers/rolloutWalker.js +32 -4
  40. package/dist/esm/schemas/sessionEvent.js +63 -14
  41. package/dist/esm/sessionMonitor.js +7 -1
  42. package/dist/esm/sessionPreviews.js +9 -0
  43. package/dist/esm/sessionPromptHistory.js +5 -3
  44. package/dist/esm/subagentActivity.js +125 -0
  45. package/dist/esm/subagentDisplayState.js +81 -0
  46. package/dist/esm/subagentSpawn.js +363 -0
  47. package/dist/esm/testing/codexSubagentFixtures.js +336 -0
  48. package/dist/esm/toolCallOrigins.js +17 -10
  49. package/dist/esm/toolCategory.js +6 -2
  50. package/dist/esm/transcript.js +8 -0
  51. package/dist/esm/turns/assistantTurn.js +6 -2
  52. package/dist/esm/usage/claudeUsageDedupe.js +68 -0
  53. package/dist/esm/usage/usageEvents.js +4 -1
  54. package/dist/esm/watchers/eventBridge.js +45 -0
  55. package/dist/esm/watchers/factory.js +5 -3
  56. package/dist/esm/watchers/jsonlTail.js +82 -48
  57. package/dist/esm/watchers/jsonlWatcher.js +61 -4
  58. package/dist/esm/watchers/providerReaderWatcher.js +18 -4
  59. package/dist/esm/zaiQuota.js +2 -1
  60. package/dist/fileContinuity.d.ts +95 -0
  61. package/dist/fileContinuity.js +187 -0
  62. package/dist/formatters/toolSummary.js +4 -1
  63. package/dist/index.d.ts +19 -7
  64. package/dist/index.js +48 -10
  65. package/dist/modelCatalog.js +5 -4
  66. package/dist/modelContext.js +4 -3
  67. package/dist/modelInfo.js +4 -3
  68. package/dist/node.d.ts +5 -1
  69. package/dist/node.js +18 -2
  70. package/dist/observedContextWindows.js +2 -1
  71. package/dist/ownKey.d.ts +16 -0
  72. package/dist/ownKey.js +30 -0
  73. package/dist/parsers/codexParser.d.ts +107 -0
  74. package/dist/parsers/codexParser.js +373 -7
  75. package/dist/parsers/openCodeParser.js +2 -1
  76. package/dist/parsers/subagentEndState.d.ts +20 -0
  77. package/dist/parsers/subagentEndState.js +65 -0
  78. package/dist/parsers/subagentScanner.d.ts +19 -6
  79. package/dist/parsers/subagentScanner.js +37 -12
  80. package/dist/parsers/subagentTraceParser.d.ts +33 -5
  81. package/dist/parsers/subagentTraceParser.js +103 -37
  82. package/dist/pricingCatalog.js +3 -2
  83. package/dist/promptHistory.d.ts +13 -3
  84. package/dist/promptHistory.js +22 -11
  85. package/dist/providers/claudeCode.js +59 -28
  86. package/dist/providers/codex.d.ts +35 -5
  87. package/dist/providers/codex.js +232 -198
  88. package/dist/providers/codexCompressedReader.d.ts +58 -0
  89. package/dist/providers/codexCompressedReader.js +221 -0
  90. package/dist/providers/codexDatabase.d.ts +25 -0
  91. package/dist/providers/codexDatabase.js +48 -2
  92. package/dist/providers/codexRollouts.d.ts +16 -2
  93. package/dist/providers/codexRollouts.js +20 -2
  94. package/dist/providers/codexSubagents.d.ts +66 -0
  95. package/dist/providers/codexSubagents.js +495 -0
  96. package/dist/providers/compressedRollouts.d.ts +114 -0
  97. package/dist/providers/compressedRollouts.js +380 -0
  98. package/dist/providers/factory.d.ts +5 -0
  99. package/dist/providers/factory.js +4 -1
  100. package/dist/providers/rolloutWalker.d.ts +15 -2
  101. package/dist/providers/rolloutWalker.js +33 -4
  102. package/dist/providers/types.d.ts +36 -1
  103. package/dist/schemas/index.d.ts +1 -0
  104. package/dist/schemas/sessionEvent.d.ts +51 -10
  105. package/dist/schemas/sessionEvent.js +63 -14
  106. package/dist/sessionMonitor.d.ts +6 -0
  107. package/dist/sessionMonitor.js +7 -1
  108. package/dist/sessionPreviews.js +9 -0
  109. package/dist/sessionPromptHistory.d.ts +5 -0
  110. package/dist/sessionPromptHistory.js +5 -36
  111. package/dist/subagentActivity.d.ts +91 -0
  112. package/dist/subagentActivity.js +132 -0
  113. package/dist/subagentDisplayState.d.ts +81 -0
  114. package/dist/subagentDisplayState.js +86 -0
  115. package/dist/subagentSpawn.d.ts +175 -0
  116. package/dist/subagentSpawn.js +385 -0
  117. package/dist/testing/codexSubagentFixtures.d.ts +48 -0
  118. package/dist/testing/codexSubagentFixtures.js +344 -0
  119. package/dist/toolCallOrigins.d.ts +5 -0
  120. package/dist/toolCallOrigins.js +17 -10
  121. package/dist/toolCategory.js +6 -2
  122. package/dist/transcript.js +8 -0
  123. package/dist/turns/assistantTurn.d.ts +4 -0
  124. package/dist/turns/assistantTurn.js +6 -2
  125. package/dist/types/codex.d.ts +71 -2
  126. package/dist/types/sessionEvent.d.ts +38 -3
  127. package/dist/usage/claudeUsageDedupe.d.ts +31 -0
  128. package/dist/usage/claudeUsageDedupe.js +68 -0
  129. package/dist/usage/usageEvents.d.ts +4 -1
  130. package/dist/usage/usageEvents.js +4 -1
  131. package/dist/watchers/eventBridge.js +45 -0
  132. package/dist/watchers/factory.d.ts +6 -0
  133. package/dist/watchers/factory.js +5 -3
  134. package/dist/watchers/jsonlTail.d.ts +27 -4
  135. package/dist/watchers/jsonlTail.js +84 -48
  136. package/dist/watchers/jsonlWatcher.d.ts +22 -1
  137. package/dist/watchers/jsonlWatcher.js +60 -3
  138. package/dist/watchers/providerReaderWatcher.d.ts +7 -0
  139. package/dist/watchers/providerReaderWatcher.js +18 -4
  140. package/dist/watchers/types.d.ts +12 -0
  141. package/dist/zaiQuota.js +2 -1
  142. package/package.json +1 -1
@@ -13,6 +13,21 @@
13
13
  */
14
14
  import type { SessionEvent } from '../types/sessionEvent';
15
15
  import type { CodexRolloutLine, CodexSessionMeta, CodexTokenUsage, CodexRateLimits } from '../types/codex';
16
+ /** Pending exec command awaiting its end event. */
17
+ interface PendingExecCommand {
18
+ call_id: string;
19
+ command: string[];
20
+ workdir?: string;
21
+ timestamp: string;
22
+ }
23
+ /** Pending MCP tool call awaiting its end event. */
24
+ interface PendingMcpToolCall {
25
+ call_id: string;
26
+ server_name?: string;
27
+ tool_name: string;
28
+ arguments?: Record<string, unknown>;
29
+ timestamp: string;
30
+ }
16
31
  /**
17
32
  * Resolve the usage of the single call a Codex `token_count` event reports.
18
33
  *
@@ -73,6 +88,15 @@ export interface CodexRolloutParserOptions {
73
88
  * unchanged. For cheap passes over large rollouts; default false.
74
89
  */
75
90
  metadataOnly?: boolean;
91
+ /**
92
+ * Emit the multi-agent lifecycle as `system` events with `origin: { kind:
93
+ * 'subagent-activity' }` (read them with `subagentActivityFromEvent()`):
94
+ * `SubAgentActivity` items (`started`, `completed`, `interacted`,
95
+ * `interrupted`) and each `FINAL_ANSWER` agent message. Transcripts,
96
+ * context attribution, and the aggregator's message counts skip them.
97
+ * Default false: those records produce no events.
98
+ */
99
+ includeSubagentActivity?: boolean;
76
100
  }
77
101
  /** Pending tool-call state counters from {@link CodexRolloutParser.getPendingStats}. */
78
102
  export interface CodexPendingStats {
@@ -85,6 +109,47 @@ export interface CodexPendingStats {
85
109
  /** Pending calls forgotten because `maxPendingBytes` was exceeded. */
86
110
  evictedByBytes: number;
87
111
  }
112
+ /**
113
+ * JSON-serializable continuation state of a {@link CodexRolloutParser}, from
114
+ * `exportResumeState()`, for `importResumeState()` in a parser that resumes
115
+ * at the same rollout position (for example from a persisted session
116
+ * snapshot). Every table lists its entries oldest first.
117
+ */
118
+ export interface CodexRolloutResumeState {
119
+ version: 1;
120
+ sessionMeta: CodexSessionMeta | null;
121
+ /** Model from the latest `turn_context`, attributed to later events. */
122
+ currentModel: string | null;
123
+ /** Cumulative total of the previous token_count event, to skip its duplicates. */
124
+ prevTotalUsage: CodexTokenUsage | null;
125
+ lastTokenUsage: CodexTokenUsage | null;
126
+ modelContextWindow: number | null;
127
+ lastRateLimits: CodexRateLimits | null;
128
+ hasAggregateRateLimits: boolean;
129
+ inPlanMode: boolean;
130
+ /** Call ids whose tool use was already emitted, so their end event does not emit another. */
131
+ emittedToolUseIds: string[];
132
+ /** `apply_patch` calls awaiting output: call id and its per-file tool-use ids. */
133
+ patchExpandedToolUseIds: Array<[string, string[]]>;
134
+ /** `exec_command_begin` events awaiting their end. */
135
+ pendingExecCommands: PendingExecCommand[];
136
+ /** `mcp_tool_call_begin` events awaiting their end. */
137
+ pendingMcpToolCalls: PendingMcpToolCall[];
138
+ /**
139
+ * Multi-agent tool calls by call id with their raw tool names, so a later
140
+ * `SubAgentActivity` record names its tool. Optional: absent in states
141
+ * from earlier versions (and when `includeSubagentActivity` is off).
142
+ */
143
+ collaborationCalls?: Array<[string, string]>;
144
+ /**
145
+ * Forked subagent rollouts: the ordinal below which records are the
146
+ * embedded parent history (see `getInheritedHistoryEnd()`). Optional:
147
+ * absent (nothing skipped) in states from earlier versions.
148
+ */
149
+ inheritedHistoryEnd?: number | null;
150
+ }
151
+ /** A valid `CodexTokenUsage` (copied), `null` for `null`, or `undefined` when invalid. Internal. */
152
+ export declare function codexTokenUsageFrom(value: unknown): CodexTokenUsage | null | undefined;
88
153
  /**
89
154
  * Stateful parser for Codex JSONL rollout lines.
90
155
  *
@@ -112,6 +177,15 @@ export declare class CodexRolloutParser {
112
177
  /** Byte budget for pending calls; `null` = unlimited (no accounting). */
113
178
  private readonly maxPendingBytes;
114
179
  private readonly metadataOnly;
180
+ private readonly includeSubagentActivity;
181
+ /**
182
+ * Set once a forked child's rollout embeds an ancestor's session_meta: its
183
+ * own `subagent_history_start_ordinal`, below which records are the
184
+ * parent's history and are skipped. `null` while nothing is skipped.
185
+ */
186
+ private historyStartOrdinal;
187
+ /** Multi-agent tool calls (call id → raw name), only with `includeSubagentActivity`. */
188
+ private collaborationCalls;
115
189
  /** Pending calls in insertion order, for the byte budget. */
116
190
  private pendingLedger;
117
191
  private pendingBytes;
@@ -130,6 +204,29 @@ export declare class CodexRolloutParser {
130
204
  getModelContextWindow(): number | null;
131
205
  /** Get the last observed rate limits from token_count events. */
132
206
  getLastRateLimits(): CodexRateLimits | null;
207
+ /**
208
+ * In a forked subagent's rollout, the ordinal of its first own record:
209
+ * records with a lower `ordinal` are the parent history the fork embeds
210
+ * and produce no events. Known once the rollout's second line (an embedded
211
+ * ancestor `session_meta`) was parsed; `null` otherwise, and for every
212
+ * rollout that is not such a fork.
213
+ */
214
+ getInheritedHistoryEnd(): number | null;
215
+ /**
216
+ * The state later lines depend on — session metadata, current model,
217
+ * previous cumulative token total, context window, rate limits, plan mode,
218
+ * and the tool-call correlation tables (newest `limit` entries each) — as
219
+ * JSON-serializable data. Eviction counters are not included.
220
+ */
221
+ exportResumeState(limit?: number): CodexRolloutResumeState;
222
+ /**
223
+ * Replaces this parser's state with one from {@link exportResumeState}, as
224
+ * if it had parsed the rollout up to where that state was taken. Returns
225
+ * false, changing nothing, when `state` is not a valid version-1 state.
226
+ * This parser's own `maxTrackedToolCalls`, `maxPendingBytes`, and
227
+ * `metadataOnly` options apply to the imported tables.
228
+ */
229
+ importResumeState(state: unknown): boolean;
133
230
  /**
134
231
  * Converts a single rollout line to zero or more session events.
135
232
  */
@@ -142,6 +239,9 @@ export declare class CodexRolloutParser {
142
239
  * first.
143
240
  */
144
241
  private putPending;
242
+ private putPendingExec;
243
+ private putPendingMcp;
244
+ private putPendingPatch;
145
245
  /** Removes and returns a pending call. */
146
246
  private takePending;
147
247
  private forgetPendingBytes;
@@ -152,6 +252,12 @@ export declare class CodexRolloutParser {
152
252
  private toolOutput;
153
253
  private handleSessionMeta;
154
254
  private handleResponseItem;
255
+ private rememberCollaborationCall;
256
+ private subagentActivityEvent;
257
+ /** A child's `FINAL_ANSWER` message to its parent (opt-in). */
258
+ private handleAgentMessage;
259
+ /** A `SubAgentActivity` turn item (opt-in). */
260
+ private handleItemCompleted;
155
261
  private handleMessage;
156
262
  private handleReasoning;
157
263
  private handleFunctionCall;
@@ -164,3 +270,4 @@ export declare class CodexRolloutParser {
164
270
  private handleEventMsg;
165
271
  private handleTokenCount;
166
272
  }
273
+ export {};
@@ -18,9 +18,11 @@ exports.resolveCodexCallUsage = resolveCodexCallUsage;
18
18
  exports.normalizeCodexToolName = normalizeCodexToolName;
19
19
  exports.normalizeCodexToolInput = normalizeCodexToolInput;
20
20
  exports.extractPatchFilePaths = extractPatchFilePaths;
21
+ exports.codexTokenUsageFrom = codexTokenUsageFrom;
21
22
  const codex_1 = require("../types/codex");
22
23
  const openCodeParser_1 = require("./openCodeParser");
23
24
  const usageNormalization_1 = require("../usageNormalization");
25
+ const subagentActivity_1 = require("../subagentActivity");
24
26
  function reportedCompaction(tokensBefore, tokensAfter) {
25
27
  if (typeof tokensBefore !== 'number' ||
26
28
  typeof tokensAfter !== 'number' ||
@@ -178,6 +180,84 @@ function extractPatchFilePaths(input) {
178
180
  }
179
181
  /** Default cap on each piece of per-call correlation state the parser keeps. */
180
182
  exports.DEFAULT_MAX_TRACKED_TOOL_CALLS = 4096;
183
+ /** Entries per table {@link CodexRolloutParser.exportResumeState} keeps by default. */
184
+ const DEFAULT_RESUME_STATE_LIMIT = 256;
185
+ /** Raw names of Codex's multi-agent tools (namespace `collaboration`). */
186
+ const COLLABORATION_TOOL_NAMES = new Set([
187
+ 'spawn_agent',
188
+ 'followup_task',
189
+ 'send_message',
190
+ 'interrupt_agent',
191
+ 'wait_agent',
192
+ 'list_agents',
193
+ ]);
194
+ const SUBAGENT_ACTIVITY_KINDS = new Set([
195
+ 'started',
196
+ 'completed',
197
+ 'interacted',
198
+ 'interrupted',
199
+ ]);
200
+ function nonEmptyString(value) {
201
+ return typeof value === 'string' && value.length > 0 ? value : undefined;
202
+ }
203
+ /** `subagent_history_start_ordinal` of a session_meta payload, when valid. */
204
+ function historyStartOrdinalOf(meta) {
205
+ const value = meta?.subagent_history_start_ordinal;
206
+ return typeof value === 'number' && Number.isInteger(value) && value > 0 ? value : null;
207
+ }
208
+ function isPlainObject(value) {
209
+ return !!value && typeof value === 'object' && !Array.isArray(value);
210
+ }
211
+ function cloneJson(value) {
212
+ return JSON.parse(JSON.stringify(value));
213
+ }
214
+ function isOptionalString(value) {
215
+ return value === undefined || typeof value === 'string';
216
+ }
217
+ function isStringArray(value) {
218
+ return Array.isArray(value) && value.every((item) => typeof item === 'string');
219
+ }
220
+ function isTokenCount(value) {
221
+ return typeof value === 'number' && Number.isFinite(value) && value >= 0;
222
+ }
223
+ /** A valid `CodexTokenUsage` (copied), `null` for `null`, or `undefined` when invalid. Internal. */
224
+ function codexTokenUsageFrom(value) {
225
+ if (value === null)
226
+ return null;
227
+ if (!isPlainObject(value))
228
+ return undefined;
229
+ if (!isTokenCount(value.input_tokens) || !isTokenCount(value.output_tokens))
230
+ return undefined;
231
+ const usage = {
232
+ input_tokens: value.input_tokens,
233
+ output_tokens: value.output_tokens,
234
+ };
235
+ for (const field of ['cached_input_tokens', 'reasoning_output_tokens', 'total_tokens']) {
236
+ const count = value[field];
237
+ if (count === undefined)
238
+ continue;
239
+ if (!isTokenCount(count))
240
+ return undefined;
241
+ usage[field] = count;
242
+ }
243
+ return usage;
244
+ }
245
+ /** Last `limit` items of an insertion-ordered collection, oldest first. */
246
+ function newest(items, size, limit) {
247
+ const count = limit >= 0 ? Math.min(size, Math.floor(limit)) : 0;
248
+ const kept = [];
249
+ let skip = size - count;
250
+ for (const item of items) {
251
+ if (kept.length >= count)
252
+ break;
253
+ if (skip > 0) {
254
+ skip -= 1;
255
+ continue;
256
+ }
257
+ kept.push(item);
258
+ }
259
+ return kept;
260
+ }
181
261
  /** Fixed per-entry cost added to each pending call's byte estimate. */
182
262
  const PENDING_ENTRY_OVERHEAD_BYTES = 64;
183
263
  /** Prefix of the bookkeeping keys the parser stashes inside tool inputs. */
@@ -242,6 +322,15 @@ class CodexRolloutParser {
242
322
  /** Byte budget for pending calls; `null` = unlimited (no accounting). */
243
323
  maxPendingBytes;
244
324
  metadataOnly;
325
+ includeSubagentActivity;
326
+ /**
327
+ * Set once a forked child's rollout embeds an ancestor's session_meta: its
328
+ * own `subagent_history_start_ordinal`, below which records are the
329
+ * parent's history and are skipped. `null` while nothing is skipped.
330
+ */
331
+ historyStartOrdinal = null;
332
+ /** Multi-agent tool calls (call id → raw name), only with `includeSubagentActivity`. */
333
+ collaborationCalls = new Map();
245
334
  /** Pending calls in insertion order, for the byte budget. */
246
335
  pendingLedger = new Map();
247
336
  pendingBytes = 0;
@@ -257,6 +346,7 @@ class CodexRolloutParser {
257
346
  ? Math.floor(maxBytes)
258
347
  : null;
259
348
  this.metadataOnly = options.metadataOnly === true;
349
+ this.includeSubagentActivity = options.includeSubagentActivity === true;
260
350
  }
261
351
  /** Counters for the pending tool-call state and what the caps evicted. */
262
352
  getPendingStats() {
@@ -289,13 +379,172 @@ class CodexRolloutParser {
289
379
  getLastRateLimits() {
290
380
  return this.lastRateLimits;
291
381
  }
382
+ /**
383
+ * In a forked subagent's rollout, the ordinal of its first own record:
384
+ * records with a lower `ordinal` are the parent history the fork embeds
385
+ * and produce no events. Known once the rollout's second line (an embedded
386
+ * ancestor `session_meta`) was parsed; `null` otherwise, and for every
387
+ * rollout that is not such a fork.
388
+ */
389
+ getInheritedHistoryEnd() {
390
+ return this.historyStartOrdinal;
391
+ }
392
+ /**
393
+ * The state later lines depend on — session metadata, current model,
394
+ * previous cumulative token total, context window, rate limits, plan mode,
395
+ * and the tool-call correlation tables (newest `limit` entries each) — as
396
+ * JSON-serializable data. Eviction counters are not included.
397
+ */
398
+ exportResumeState(limit = DEFAULT_RESUME_STATE_LIMIT) {
399
+ return cloneJson({
400
+ version: 1,
401
+ sessionMeta: this.sessionMeta,
402
+ currentModel: this.currentModel,
403
+ prevTotalUsage: this.prevTotalUsage,
404
+ lastTokenUsage: this.lastTokenUsage,
405
+ modelContextWindow: this.modelContextWindow,
406
+ lastRateLimits: this.lastRateLimits,
407
+ hasAggregateRateLimits: this.hasAggregateRateLimits,
408
+ inPlanMode: this.inPlanMode,
409
+ emittedToolUseIds: newest(this.emittedToolUseIds, this.emittedToolUseIds.size, limit),
410
+ patchExpandedToolUseIds: newest(this.patchExpandedToolUseIds, this.patchExpandedToolUseIds.size, limit),
411
+ pendingExecCommands: newest(this.pendingExecCommands.values(), this.pendingExecCommands.size, limit),
412
+ pendingMcpToolCalls: newest(this.pendingMcpToolCalls.values(), this.pendingMcpToolCalls.size, limit),
413
+ ...(this.historyStartOrdinal !== null
414
+ ? { inheritedHistoryEnd: this.historyStartOrdinal }
415
+ : {}),
416
+ ...(this.collaborationCalls.size > 0
417
+ ? {
418
+ collaborationCalls: newest(this.collaborationCalls, this.collaborationCalls.size, limit),
419
+ }
420
+ : {}),
421
+ });
422
+ }
423
+ /**
424
+ * Replaces this parser's state with one from {@link exportResumeState}, as
425
+ * if it had parsed the rollout up to where that state was taken. Returns
426
+ * false, changing nothing, when `state` is not a valid version-1 state.
427
+ * This parser's own `maxTrackedToolCalls`, `maxPendingBytes`, and
428
+ * `metadataOnly` options apply to the imported tables.
429
+ */
430
+ importResumeState(state) {
431
+ if (!isPlainObject(state) || state.version !== 1)
432
+ return false;
433
+ const s = state;
434
+ const prevTotalUsage = codexTokenUsageFrom(s.prevTotalUsage);
435
+ const lastTokenUsage = codexTokenUsageFrom(s.lastTokenUsage);
436
+ if (prevTotalUsage === undefined || lastTokenUsage === undefined)
437
+ return false;
438
+ if (s.sessionMeta !== null && !isPlainObject(s.sessionMeta))
439
+ return false;
440
+ if (s.lastRateLimits !== null && !isPlainObject(s.lastRateLimits))
441
+ return false;
442
+ if (s.currentModel !== null && typeof s.currentModel !== 'string')
443
+ return false;
444
+ const contextWindow = s.modelContextWindow;
445
+ if (contextWindow !== null &&
446
+ !(typeof contextWindow === 'number' && Number.isFinite(contextWindow) && contextWindow > 0)) {
447
+ return false;
448
+ }
449
+ if (typeof s.hasAggregateRateLimits !== 'boolean' || typeof s.inPlanMode !== 'boolean') {
450
+ return false;
451
+ }
452
+ if (!isStringArray(s.emittedToolUseIds))
453
+ return false;
454
+ const patches = s.patchExpandedToolUseIds;
455
+ if (!Array.isArray(patches) ||
456
+ !patches.every((entry) => Array.isArray(entry) &&
457
+ entry.length === 2 &&
458
+ typeof entry[0] === 'string' &&
459
+ isStringArray(entry[1]))) {
460
+ return false;
461
+ }
462
+ const execs = s.pendingExecCommands;
463
+ if (!Array.isArray(execs) ||
464
+ !execs.every((entry) => isPlainObject(entry) &&
465
+ typeof entry.call_id === 'string' &&
466
+ isStringArray(entry.command) &&
467
+ isOptionalString(entry.workdir) &&
468
+ typeof entry.timestamp === 'string')) {
469
+ return false;
470
+ }
471
+ const mcps = s.pendingMcpToolCalls;
472
+ if (!Array.isArray(mcps) ||
473
+ !mcps.every((entry) => isPlainObject(entry) &&
474
+ typeof entry.call_id === 'string' &&
475
+ typeof entry.tool_name === 'string' &&
476
+ isOptionalString(entry.server_name) &&
477
+ (entry.arguments === undefined || isPlainObject(entry.arguments)) &&
478
+ typeof entry.timestamp === 'string')) {
479
+ return false;
480
+ }
481
+ const inheritedEnd = s.inheritedHistoryEnd;
482
+ if (inheritedEnd !== undefined &&
483
+ inheritedEnd !== null &&
484
+ !(typeof inheritedEnd === 'number' && Number.isInteger(inheritedEnd) && inheritedEnd > 0)) {
485
+ return false;
486
+ }
487
+ const collaborationCalls = s.collaborationCalls;
488
+ if (collaborationCalls !== undefined &&
489
+ !(Array.isArray(collaborationCalls) &&
490
+ collaborationCalls.every((entry) => Array.isArray(entry) &&
491
+ entry.length === 2 &&
492
+ typeof entry[0] === 'string' &&
493
+ typeof entry[1] === 'string'))) {
494
+ return false;
495
+ }
496
+ const copy = cloneJson({
497
+ sessionMeta: s.sessionMeta,
498
+ lastRateLimits: s.lastRateLimits,
499
+ patches: patches,
500
+ execs: execs,
501
+ mcps: mcps,
502
+ });
503
+ this.reset();
504
+ this.sessionMeta = copy.sessionMeta;
505
+ this.historyStartOrdinal = typeof inheritedEnd === 'number' ? inheritedEnd : null;
506
+ this.currentModel = s.currentModel;
507
+ this.prevTotalUsage = prevTotalUsage;
508
+ this.lastTokenUsage = lastTokenUsage;
509
+ this.modelContextWindow = contextWindow;
510
+ this.lastRateLimits = copy.lastRateLimits;
511
+ this.hasAggregateRateLimits = s.hasAggregateRateLimits;
512
+ this.inPlanMode = s.inPlanMode;
513
+ for (const id of s.emittedToolUseIds) {
514
+ rememberBounded(this.emittedToolUseIds, id, this.maxTrackedToolCalls);
515
+ }
516
+ for (const [callId, toolUseIds] of copy.patches)
517
+ this.putPendingPatch(callId, toolUseIds);
518
+ for (const pending of copy.execs) {
519
+ this.putPendingExec(this.metadataOnly
520
+ ? { call_id: pending.call_id, command: [], timestamp: pending.timestamp }
521
+ : pending);
522
+ }
523
+ for (const pending of copy.mcps) {
524
+ this.putPendingMcp(this.metadataOnly ? { ...pending, arguments: undefined } : pending);
525
+ }
526
+ if (this.includeSubagentActivity && Array.isArray(collaborationCalls)) {
527
+ for (const [callId, name] of collaborationCalls) {
528
+ this.rememberCollaborationCall(callId, name);
529
+ }
530
+ }
531
+ return true;
532
+ }
292
533
  /**
293
534
  * Converts a single rollout line to zero or more session events.
294
535
  */
295
536
  convertLine(line) {
537
+ if (line.type === 'session_meta') {
538
+ return this.handleSessionMeta(line.timestamp, line.payload, line.ordinal);
539
+ }
540
+ // A forked subagent rollout embeds the parent's history before its own
541
+ // records; that history is the parent's, not the child's.
542
+ if (this.historyStartOrdinal !== null &&
543
+ typeof line.ordinal === 'number' &&
544
+ line.ordinal < this.historyStartOrdinal) {
545
+ return [];
546
+ }
296
547
  switch (line.type) {
297
- case 'session_meta':
298
- return this.handleSessionMeta(line.timestamp, line.payload);
299
548
  case 'response_item':
300
549
  return this.handleResponseItem(line.timestamp, line.payload);
301
550
  case 'compacted':
@@ -311,6 +560,8 @@ class CodexRolloutParser {
311
560
  /** Reset all parser state. */
312
561
  reset() {
313
562
  this.sessionMeta = null;
563
+ this.historyStartOrdinal = null;
564
+ this.collaborationCalls.clear();
314
565
  this.currentModel = null;
315
566
  this.pendingExecCommands.clear();
316
567
  this.pendingMcpToolCalls.clear();
@@ -360,6 +611,16 @@ class CodexRolloutParser {
360
611
  this.evictedByBytes += 1;
361
612
  }
362
613
  }
614
+ putPendingExec(pending) {
615
+ this.putPending(this.pendingExecCommands, pending.call_id, pending, () => stringBytes(pending.call_id, pending.workdir, pending.timestamp, ...(Array.isArray(pending.command) ? pending.command : [])));
616
+ }
617
+ putPendingMcp(pending) {
618
+ this.putPending(this.pendingMcpToolCalls, pending.call_id, pending, () => stringBytes(pending.call_id, pending.server_name, pending.tool_name, pending.timestamp) +
619
+ approxJsonBytes(pending.arguments));
620
+ }
621
+ putPendingPatch(callId, toolUseIds) {
622
+ this.putPending(this.patchExpandedToolUseIds, callId, toolUseIds, () => stringBytes(callId, ...toolUseIds));
623
+ }
363
624
  /** Removes and returns a pending call. */
364
625
  takePending(table, callId) {
365
626
  const value = table.get(callId);
@@ -390,7 +651,25 @@ class CodexRolloutParser {
390
651
  return this.metadataOnly ? '' : content;
391
652
  }
392
653
  // --- Handlers ---
393
- handleSessionMeta(timestamp, payload) {
654
+ handleSessionMeta(timestamp, payload, ordinal) {
655
+ if (this.sessionMeta) {
656
+ // Only the rollout's own (first) session_meta counts. A forked child
657
+ // embeds its parent's right after its own, followed by the parent's
658
+ // history up to the child's `subagent_history_start_ordinal`; from
659
+ // here on, records below that ordinal are skipped. (Children whose
660
+ // rollout has no embedded meta keep every record: in some, Codex
661
+ // recorded a start ordinal past their own records.)
662
+ const start = historyStartOrdinalOf(this.sessionMeta);
663
+ if (this.historyStartOrdinal === null &&
664
+ start !== null &&
665
+ typeof ordinal === 'number' &&
666
+ ordinal < start) {
667
+ this.historyStartOrdinal = start;
668
+ }
669
+ return [];
670
+ }
671
+ if (!payload || typeof payload !== 'object')
672
+ return [];
394
673
  this.sessionMeta = payload;
395
674
  const text = payload.base_instructions?.text?.trim();
396
675
  return [
@@ -430,10 +709,91 @@ class CodexRolloutParser {
430
709
  return this.handleCustomToolCall(timestamp, payload);
431
710
  case 'custom_tool_call_output':
432
711
  return this.handleCustomToolCallOutput(timestamp, payload);
712
+ case 'agent_message':
713
+ return this.handleAgentMessage(timestamp, payload);
433
714
  default:
434
715
  return [];
435
716
  }
436
717
  }
718
+ // --- Subagent activity ---
719
+ rememberCollaborationCall(callId, name) {
720
+ this.collaborationCalls.delete(callId);
721
+ this.collaborationCalls.set(callId, name);
722
+ while (this.collaborationCalls.size > this.maxTrackedToolCalls) {
723
+ this.collaborationCalls.delete(this.collaborationCalls.keys().next().value);
724
+ }
725
+ }
726
+ subagentActivityEvent(timestamp, id, activity) {
727
+ return {
728
+ type: 'system',
729
+ timestamp,
730
+ origin: { kind: subagentActivity_1.SUBAGENT_ACTIVITY_ORIGIN_KIND },
731
+ message: {
732
+ role: 'system',
733
+ id: `subagent-activity:${id}`,
734
+ sourceLabel: 'subagent activity',
735
+ content: [{ type: subagentActivity_1.SUBAGENT_ACTIVITY_BLOCK_TYPE, ...activity }],
736
+ },
737
+ providerMetadata: { providerId: 'codex', source: 'codex-rollout' },
738
+ };
739
+ }
740
+ /** A child's `FINAL_ANSWER` message to its parent (opt-in). */
741
+ handleAgentMessage(timestamp, item) {
742
+ if (!this.includeSubagentActivity || !Array.isArray(item.content))
743
+ return [];
744
+ const first = item.content.find((part) => !!part && typeof part === 'object' && typeof part.text === 'string');
745
+ const parsed = (0, subagentActivity_1.parseCodexAgentMessageText)(first?.text);
746
+ if (!parsed || parsed.messageType !== 'FINAL_ANSWER')
747
+ return [];
748
+ const agentPath = nonEmptyString(item.author) ?? parsed.sender;
749
+ const recipient = nonEmptyString(item.recipient) ?? parsed.taskName;
750
+ const text = this.metadataOnly ? undefined : nonEmptyString(parsed.payload.trim());
751
+ const activity = {
752
+ kind: 'final-answer',
753
+ ...(agentPath ? { agentPath } : {}),
754
+ ...(recipient ? { recipient } : {}),
755
+ ...(text ? { text } : {}),
756
+ };
757
+ return [
758
+ this.subagentActivityEvent(timestamp, nonEmptyString(item.id) ?? `final-answer-${timestamp}-${agentPath ?? ''}`, activity),
759
+ ];
760
+ }
761
+ /** A `SubAgentActivity` turn item (opt-in). */
762
+ handleItemCompleted(timestamp, event) {
763
+ if (!this.includeSubagentActivity)
764
+ return [];
765
+ const item = event.item;
766
+ if (!item || item.type !== 'SubAgentActivity')
767
+ return [];
768
+ const kind = typeof item.kind === 'string' ? item.kind : '';
769
+ if (!SUBAGENT_ACTIVITY_KINDS.has(kind))
770
+ return [];
771
+ const itemId = nonEmptyString(item.id);
772
+ const completedTurn = kind === 'completed' && itemId?.startsWith('subagent-completed-')
773
+ ? nonEmptyString(itemId.slice('subagent-completed-'.length))
774
+ : undefined;
775
+ const callId = kind === 'completed' ? undefined : itemId;
776
+ const tool = callId ? this.collaborationCalls.get(callId) : undefined;
777
+ const fields = {
778
+ callId,
779
+ tool,
780
+ turnId: completedTurn,
781
+ agentThreadId: nonEmptyString(item.agent_thread_id),
782
+ agentPath: nonEmptyString(item.agent_path),
783
+ model: nonEmptyString(item.model),
784
+ reasoningEffort: nonEmptyString(item.reasoning_effort),
785
+ parentThreadId: nonEmptyString(event.thread_id),
786
+ parentTurnId: nonEmptyString(event.turn_id),
787
+ };
788
+ const activity = { kind: kind };
789
+ for (const [key, value] of Object.entries(fields)) {
790
+ if (value !== undefined)
791
+ activity[key] = value;
792
+ }
793
+ return [
794
+ this.subagentActivityEvent(timestamp, itemId ?? `${kind}-${timestamp}-${activity.agentThreadId ?? ''}`, activity),
795
+ ];
796
+ }
437
797
  handleMessage(timestamp, item) {
438
798
  const text = extractTextFromContent(item.content);
439
799
  if (!text)
@@ -515,6 +875,11 @@ class CodexRolloutParser {
515
875
  const canonicalName = normalizeCodexToolName(item.name);
516
876
  const normalizedInput = normalizeCodexToolInput(canonicalName, item.name, parsedArgs);
517
877
  rememberBounded(this.emittedToolUseIds, item.call_id, this.maxTrackedToolCalls);
878
+ if (this.includeSubagentActivity &&
879
+ typeof item.call_id === 'string' &&
880
+ (item.namespace === 'collaboration' || COLLABORATION_TOOL_NAMES.has(item.name))) {
881
+ this.rememberCollaborationCall(item.call_id, item.name);
882
+ }
518
883
  return [
519
884
  {
520
885
  type: 'assistant',
@@ -589,7 +954,7 @@ class CodexRolloutParser {
589
954
  if (filePaths.length === 0)
590
955
  return [];
591
956
  const toolUseIds = filePaths.map((fp) => `${item.call_id}-${fp}`);
592
- this.putPending(this.patchExpandedToolUseIds, item.call_id, toolUseIds, () => stringBytes(item.call_id, ...toolUseIds));
957
+ this.putPendingPatch(item.call_id, toolUseIds);
593
958
  for (const id of toolUseIds) {
594
959
  rememberBounded(this.emittedToolUseIds, id, this.maxTrackedToolCalls);
595
960
  }
@@ -739,7 +1104,7 @@ class CodexRolloutParser {
739
1104
  const pending = this.metadataOnly
740
1105
  ? { call_id: e.call_id, command: [], timestamp }
741
1106
  : { call_id: e.call_id, command: e.command, workdir: e.workdir, timestamp };
742
- this.putPending(this.pendingExecCommands, e.call_id, pending, () => stringBytes(pending.call_id, pending.workdir, pending.timestamp, ...(Array.isArray(pending.command) ? pending.command : [])));
1107
+ this.putPendingExec(pending);
743
1108
  return [];
744
1109
  }
745
1110
  case 'exec_command_end': {
@@ -802,8 +1167,7 @@ class CodexRolloutParser {
802
1167
  arguments: this.metadataOnly ? undefined : e.arguments,
803
1168
  timestamp,
804
1169
  };
805
- this.putPending(this.pendingMcpToolCalls, e.call_id, pending, () => stringBytes(pending.call_id, pending.server_name, pending.tool_name, timestamp) +
806
- approxJsonBytes(pending.arguments));
1170
+ this.putPendingMcp(pending);
807
1171
  return [];
808
1172
  }
809
1173
  case 'mcp_tool_call_end': {
@@ -972,6 +1336,8 @@ class CodexRolloutParser {
972
1336
  timestamp,
973
1337
  },
974
1338
  ];
1339
+ case 'item_completed':
1340
+ return this.handleItemCompleted(timestamp, event);
975
1341
  case 'turn_aborted':
976
1342
  case 'agent_reasoning':
977
1343
  case 'agent_message':
@@ -22,6 +22,7 @@ exports.convertOpenCodeMessage = convertOpenCodeMessage;
22
22
  exports.parseDbMessageData = parseDbMessageData;
23
23
  exports.parseDbPartData = parseDbPartData;
24
24
  const usageNormalization_1 = require("../usageNormalization");
25
+ const ownKey_1 = require("../ownKey");
25
26
  /**
26
27
  * OpenCode → Claude Code tool name mapping.
27
28
  * OpenCode uses lowercase names; downstream code expects PascalCase.
@@ -57,7 +58,7 @@ function normalizeToolName(name) {
57
58
  if (!name)
58
59
  return name;
59
60
  // Check mapping (case-insensitive lookup via lowercase key)
60
- const mapped = TOOL_NAME_MAP[name.toLowerCase()];
61
+ const mapped = (0, ownKey_1.ownValue)(TOOL_NAME_MAP, name.toLowerCase());
61
62
  if (mapped)
62
63
  return mapped;
63
64
  // Already PascalCase (starts with uppercase)? Pass through.
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Whether a Claude Code subagent transcript ended cleanly, read line by line.
3
+ *
4
+ * Clean means: the last user or assistant line is an assistant line that
5
+ * stopped with `end_turn` or `stop_sequence`, and every `tool_use` in the
6
+ * transcript has its `tool_result`. A user line after that (a resumed agent's
7
+ * new prompt) makes it unfinished again.
8
+ *
9
+ * Pure. Internal to the subagent parsers.
10
+ *
11
+ * @module parsers/subagentEndState
12
+ */
13
+ export declare class SubagentEndTracker {
14
+ private readonly unanswered;
15
+ private lastWasCleanAssistant;
16
+ /** Feeds one parsed JSONL line, in file order. */
17
+ observe(raw: unknown): void;
18
+ /** Whether the lines so far end cleanly. */
19
+ endedCleanly(): boolean;
20
+ }