sidekick-shared 0.29.1 → 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 (109) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +171 -60
  3. package/dist/aggregation/EventAggregator.d.ts +79 -4
  4. package/dist/aggregation/EventAggregator.js +391 -37
  5. package/dist/aggregation/types.d.ts +27 -3
  6. package/dist/browser.d.ts +7 -3
  7. package/dist/browser.js +23 -3
  8. package/dist/context/sessionContext.js +5 -1
  9. package/dist/esm/aggregation/EventAggregator.js +392 -38
  10. package/dist/esm/browser.js +3 -1
  11. package/dist/esm/context/sessionContext.js +6 -2
  12. package/dist/esm/fileContinuity.js +149 -0
  13. package/dist/esm/index.js +5 -2
  14. package/dist/esm/node.js +2 -0
  15. package/dist/esm/parsers/codexParser.js +192 -3
  16. package/dist/esm/parsers/subagentEndState.js +61 -0
  17. package/dist/esm/parsers/subagentScanner.js +4 -0
  18. package/dist/esm/parsers/subagentTraceParser.js +6 -0
  19. package/dist/esm/promptHistory.js +22 -11
  20. package/dist/esm/providers/claudeCode.js +50 -27
  21. package/dist/esm/providers/codex.js +153 -163
  22. package/dist/esm/providers/codexCompressedReader.js +184 -0
  23. package/dist/esm/providers/codexDatabase.js +47 -2
  24. package/dist/esm/providers/codexRollouts.js +20 -2
  25. package/dist/esm/providers/codexSubagents.js +456 -0
  26. package/dist/esm/providers/compressedRollouts.js +330 -0
  27. package/dist/esm/providers/factory.js +4 -1
  28. package/dist/esm/providers/rolloutWalker.js +32 -4
  29. package/dist/esm/schemas/sessionEvent.js +32 -4
  30. package/dist/esm/sessionMonitor.js +7 -1
  31. package/dist/esm/sessionPreviews.js +9 -0
  32. package/dist/esm/sessionPromptHistory.js +5 -3
  33. package/dist/esm/subagentActivity.js +125 -0
  34. package/dist/esm/subagentDisplayState.js +81 -0
  35. package/dist/esm/subagentSpawn.js +206 -6
  36. package/dist/esm/testing/codexSubagentFixtures.js +336 -0
  37. package/dist/esm/toolCallOrigins.js +17 -10
  38. package/dist/esm/toolCategory.js +6 -2
  39. package/dist/esm/transcript.js +6 -2
  40. package/dist/esm/turns/assistantTurn.js +6 -3
  41. package/dist/esm/usage/usageEvents.js +4 -1
  42. package/dist/esm/watchers/eventBridge.js +31 -1
  43. package/dist/esm/watchers/jsonlTail.js +11 -42
  44. package/dist/esm/watchers/jsonlWatcher.js +19 -9
  45. package/dist/esm/watchers/providerReaderWatcher.js +5 -4
  46. package/dist/fileContinuity.d.ts +95 -0
  47. package/dist/fileContinuity.js +187 -0
  48. package/dist/index.d.ts +14 -7
  49. package/dist/index.js +39 -10
  50. package/dist/node.d.ts +4 -0
  51. package/dist/node.js +16 -2
  52. package/dist/parsers/codexParser.d.ts +44 -0
  53. package/dist/parsers/codexParser.js +192 -3
  54. package/dist/parsers/subagentEndState.d.ts +20 -0
  55. package/dist/parsers/subagentEndState.js +65 -0
  56. package/dist/parsers/subagentScanner.js +4 -0
  57. package/dist/parsers/subagentTraceParser.d.ts +8 -0
  58. package/dist/parsers/subagentTraceParser.js +6 -0
  59. package/dist/promptHistory.d.ts +13 -3
  60. package/dist/promptHistory.js +22 -11
  61. package/dist/providers/claudeCode.js +50 -27
  62. package/dist/providers/codex.d.ts +35 -5
  63. package/dist/providers/codex.js +150 -160
  64. package/dist/providers/codexCompressedReader.d.ts +58 -0
  65. package/dist/providers/codexCompressedReader.js +221 -0
  66. package/dist/providers/codexDatabase.d.ts +25 -0
  67. package/dist/providers/codexDatabase.js +48 -2
  68. package/dist/providers/codexRollouts.d.ts +16 -2
  69. package/dist/providers/codexRollouts.js +20 -2
  70. package/dist/providers/codexSubagents.d.ts +66 -0
  71. package/dist/providers/codexSubagents.js +495 -0
  72. package/dist/providers/compressedRollouts.d.ts +114 -0
  73. package/dist/providers/compressedRollouts.js +380 -0
  74. package/dist/providers/factory.d.ts +5 -0
  75. package/dist/providers/factory.js +4 -1
  76. package/dist/providers/rolloutWalker.d.ts +15 -2
  77. package/dist/providers/rolloutWalker.js +33 -4
  78. package/dist/providers/types.d.ts +22 -1
  79. package/dist/schemas/sessionEvent.d.ts +26 -2
  80. package/dist/schemas/sessionEvent.js +31 -3
  81. package/dist/sessionMonitor.d.ts +6 -0
  82. package/dist/sessionMonitor.js +7 -1
  83. package/dist/sessionPreviews.js +9 -0
  84. package/dist/sessionPromptHistory.d.ts +5 -0
  85. package/dist/sessionPromptHistory.js +5 -36
  86. package/dist/subagentActivity.d.ts +91 -0
  87. package/dist/subagentActivity.js +132 -0
  88. package/dist/subagentDisplayState.d.ts +81 -0
  89. package/dist/subagentDisplayState.js +86 -0
  90. package/dist/subagentSpawn.d.ts +100 -5
  91. package/dist/subagentSpawn.js +218 -7
  92. package/dist/testing/codexSubagentFixtures.d.ts +48 -0
  93. package/dist/testing/codexSubagentFixtures.js +344 -0
  94. package/dist/toolCallOrigins.d.ts +5 -0
  95. package/dist/toolCallOrigins.js +17 -10
  96. package/dist/toolCategory.js +6 -2
  97. package/dist/transcript.js +5 -1
  98. package/dist/turns/assistantTurn.d.ts +4 -1
  99. package/dist/turns/assistantTurn.js +5 -2
  100. package/dist/types/codex.d.ts +71 -2
  101. package/dist/types/sessionEvent.d.ts +38 -3
  102. package/dist/usage/usageEvents.d.ts +4 -1
  103. package/dist/usage/usageEvents.js +4 -1
  104. package/dist/watchers/eventBridge.js +30 -0
  105. package/dist/watchers/jsonlTail.js +11 -42
  106. package/dist/watchers/jsonlWatcher.d.ts +5 -1
  107. package/dist/watchers/jsonlWatcher.js +18 -8
  108. package/dist/watchers/providerReaderWatcher.js +5 -4
  109. package/package.json +1 -1
package/dist/node.js CHANGED
@@ -8,8 +8,8 @@
8
8
  */
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.removeSessionSummary = exports.markFileImported = exports.isFileImported = exports.applySessionSummary = exports.subagentTokenTotals = exports.scanSessionSubagents = exports.readMainThreadTokenTotals = exports.combineSessionTokenTotals = exports.collectSessionTokenTotals = exports.pruneUsageCache = exports.getUsageCacheDir = exports.collectUsageEvents = exports.USAGE_CACHE_VERSION = exports.MAX_USAGE_CACHE_FILES = exports.findActiveBillingBlock = exports.computeBillingBlocks = exports.BILLING_BLOCK_DURATION_MS = exports.writeStateFile = exports.readStateFile = exports.quotaToStateFile = exports.getStateFilePath = exports.billingBlockToStateFile = exports.STATE_FILE_SCHEMA_VERSION = exports.STATE_FILE_NAME = exports.sessionFingerprintParts = exports.fingerprintString = exports.readSessionReportInputs = exports.readSessionFileStats = exports.providerContextSizeFn = exports.firstUserPrompt = exports.computeSessionFileStats = exports.isHumanPrompt = exports.humanPromptText = exports.HUMAN_CODEX_SOURCES = exports.HUMAN_CLAUDE_ENTRYPOINTS = exports.collectSessionPromptHistory = exports.findToolCallOrigins = exports.PROMPT_HISTORY_ORDINAL_MAX_RECORD_BYTES = exports.parsePromptHistoryCursor = exports.isPromptHistorySessionSettled = exports.collectPromptHistory = exports.readSessionTranscript = exports.listRecentSessions = exports.getObservedContextWindowPath = exports.recordObservedContextWindow = exports.loadObservedContextWindows = exports.LITELLM_CATALOG_URL = exports.normalizeLiteLlmContextWindows = exports.normalizeLiteLlmCatalog = exports.hydratePricingCatalog = void 0;
11
- exports.atomicWriteJson = exports.compareCodexAuthFreshness = exports.codexRefreshFingerprint = exports.codexIdentitiesMatch = exports.readCodexAccessTokenExpiry = exports.readCodexLastRefresh = exports.readCodexAuthIdentity = exports.parseCodexAuthJson = exports.CODEX_ARCHIVED_SESSIONS_SUBDIR = exports.CODEX_SESSIONS_SUBDIR = exports.forceFileCredentialStore = exports.getCodexCredentialStoreMode = exports.getCodexSessionsDirs = exports.getCodexExecutionEnv = exports.resolveSidekickCodexHome = exports.getCodexMonitoringHomes = exports.getSystemCodexHome = exports.getClaudeProjectsDir = exports.isDefaultClaudeHome = exports.getDefaultClaudeHome = exports.getLiveClaudeHome = exports.getClaudeSessionDirectory = exports.encodeClaudeWorkspacePath = exports.getDefaultConfigDir = exports.getConfigDirOverride = exports.getConfigDir = exports.CONFIG_DIR_ENV_VAR = exports.SYNTHETIC_MODEL = exports.createSessionProviders = exports.splitWatchedPath = exports.DirectoryListingCache = exports.fileFingerprintParts = exports.fileFingerprint = exports.observedSessionSourceFromProvider = exports.ObservedSessionCollector = exports.readSessionPreviewAsync = exports.readSessionPreview = exports.listSessionPreviewsAsync = exports.listSessionPreviews = exports.readCodexHistory = exports.walkRolloutFilesAsync = exports.walkRolloutFiles = exports.findCodexRolloutFile = exports.weekKey = exports.usageBucketKey = exports.summarizeUsageRows = exports.bucketUsage = exports.importSessionHistory = exports.ACTIVE_SESSION_MTIME_THRESHOLD_MS = exports.sessionSummaryFromStats = void 0;
12
- exports.verifyJsonlCheckpoint = exports.JsonlTailSymlinkError = exports.JSONL_TAIL_CHECKPOINT_VERSION = exports.DEFAULT_JSONL_TAIL_READ_BYTES = exports.DEFAULT_JSONL_TAIL_MAX_LINE_BYTES = exports.createJsonlTail = exports.captureJsonlCheckpoint = exports.resolveWorktreeMainRepo = exports.discoverWorktreeSiblings = exports.resolveWorkspaceRoots = exports.TOOL_CATEGORIES = exports.classifyTool = exports.fetchProviderServiceStatus = exports.clearRegisteredModelPricing = exports.registerModelPricing = exports.getPricingCatalogInfo = exports.codexSpawnParentFromSource = exports.codexRolloutOrigin = exports.parseCodexLoginStatus = exports.parseClaudeAuthStatus = exports.atomicWriteJsonSync = void 0;
11
+ exports.readCodexAuthIdentity = exports.parseCodexAuthJson = exports.CODEX_ARCHIVED_SESSIONS_SUBDIR = exports.CODEX_SESSIONS_SUBDIR = exports.forceFileCredentialStore = exports.getCodexCredentialStoreMode = exports.getCodexSessionsDirs = exports.getCodexExecutionEnv = exports.resolveSidekickCodexHome = exports.getCodexMonitoringHomes = exports.getSystemCodexHome = exports.getClaudeProjectsDir = exports.isDefaultClaudeHome = exports.getDefaultClaudeHome = exports.getLiveClaudeHome = exports.getClaudeSessionDirectory = exports.encodeClaudeWorkspacePath = exports.getDefaultConfigDir = exports.getConfigDirOverride = exports.getConfigDir = exports.CONFIG_DIR_ENV_VAR = exports.SYNTHETIC_MODEL = exports.createSessionProviders = exports.splitWatchedPath = exports.DirectoryListingCache = exports.fileFingerprintParts = exports.fileFingerprint = exports.observedSessionSourceFromProvider = exports.ObservedSessionCollector = exports.readSessionPreviewAsync = exports.readSessionPreview = exports.listSessionPreviewsAsync = exports.listSessionPreviews = exports.readCodexHistory = exports.resolveRolloutPath = exports.readRolloutText = exports.readRolloutBuffer = exports.isZstdDecompressionAvailable = exports.isCompressedRolloutPath = exports.COMPRESSED_ROLLOUT_SUFFIX = exports.walkRolloutFilesAsync = exports.walkRolloutFiles = exports.findCodexRolloutFile = exports.weekKey = exports.usageBucketKey = exports.summarizeUsageRows = exports.bucketUsage = exports.importSessionHistory = exports.ACTIVE_SESSION_MTIME_THRESHOLD_MS = exports.sessionSummaryFromStats = void 0;
12
+ exports.verifyJsonlCheckpoint = exports.JsonlTailSymlinkError = exports.JSONL_TAIL_CHECKPOINT_VERSION = exports.DEFAULT_JSONL_TAIL_READ_BYTES = exports.DEFAULT_JSONL_TAIL_MAX_LINE_BYTES = exports.createJsonlTail = exports.captureJsonlCheckpoint = exports.resolveWorktreeMainRepo = exports.discoverWorktreeSiblings = exports.resolveWorkspaceRoots = exports.TOOL_CATEGORIES = exports.classifyTool = exports.fetchProviderServiceStatus = exports.clearRegisteredModelPricing = exports.registerModelPricing = exports.getPricingCatalogInfo = exports.subagentActivityFromEvent = exports.parseCodexAgentMessageText = exports.isSubagentActivitySystemEvent = exports.describeSubagentActivity = exports.SUBAGENT_ACTIVITY_ORIGIN_KIND = exports.SUBAGENT_ACTIVITY_BLOCK_TYPE = exports.codexSpawnParentFromSource = exports.codexRolloutOrigin = exports.parseCodexLoginStatus = exports.parseClaudeAuthStatus = exports.atomicWriteJsonSync = exports.atomicWriteJson = exports.compareCodexAuthFreshness = exports.codexRefreshFingerprint = exports.codexIdentitiesMatch = exports.readCodexAccessTokenExpiry = exports.readCodexLastRefresh = void 0;
13
13
  var pricingCatalog_1 = require("./pricingCatalog");
14
14
  Object.defineProperty(exports, "hydratePricingCatalog", { enumerable: true, get: function () { return pricingCatalog_1.hydratePricingCatalog; } });
15
15
  Object.defineProperty(exports, "normalizeLiteLlmCatalog", { enumerable: true, get: function () { return pricingCatalog_1.normalizeLiteLlmCatalog; } });
@@ -89,6 +89,13 @@ Object.defineProperty(exports, "findCodexRolloutFile", { enumerable: true, get:
89
89
  var rolloutWalker_1 = require("./providers/rolloutWalker");
90
90
  Object.defineProperty(exports, "walkRolloutFiles", { enumerable: true, get: function () { return rolloutWalker_1.walkRolloutFiles; } });
91
91
  Object.defineProperty(exports, "walkRolloutFilesAsync", { enumerable: true, get: function () { return rolloutWalker_1.walkRolloutFilesAsync; } });
92
+ var compressedRollouts_1 = require("./providers/compressedRollouts");
93
+ Object.defineProperty(exports, "COMPRESSED_ROLLOUT_SUFFIX", { enumerable: true, get: function () { return compressedRollouts_1.COMPRESSED_ROLLOUT_SUFFIX; } });
94
+ Object.defineProperty(exports, "isCompressedRolloutPath", { enumerable: true, get: function () { return compressedRollouts_1.isCompressedRolloutPath; } });
95
+ Object.defineProperty(exports, "isZstdDecompressionAvailable", { enumerable: true, get: function () { return compressedRollouts_1.isZstdDecompressionAvailable; } });
96
+ Object.defineProperty(exports, "readRolloutBuffer", { enumerable: true, get: function () { return compressedRollouts_1.readRolloutBuffer; } });
97
+ Object.defineProperty(exports, "readRolloutText", { enumerable: true, get: function () { return compressedRollouts_1.readRolloutText; } });
98
+ Object.defineProperty(exports, "resolveRolloutPath", { enumerable: true, get: function () { return compressedRollouts_1.resolveRolloutPath; } });
92
99
  var codexHistory_1 = require("./parsers/codexHistory");
93
100
  Object.defineProperty(exports, "readCodexHistory", { enumerable: true, get: function () { return codexHistory_1.readCodexHistory; } });
94
101
  var sessionPreviews_1 = require("./sessionPreviews");
@@ -151,6 +158,13 @@ Object.defineProperty(exports, "parseCodexLoginStatus", { enumerable: true, get:
151
158
  var codexRolloutOrigin_1 = require("./parsers/codexRolloutOrigin");
152
159
  Object.defineProperty(exports, "codexRolloutOrigin", { enumerable: true, get: function () { return codexRolloutOrigin_1.codexRolloutOrigin; } });
153
160
  Object.defineProperty(exports, "codexSpawnParentFromSource", { enumerable: true, get: function () { return codexRolloutOrigin_1.codexSpawnParentFromSource; } });
161
+ var subagentActivity_1 = require("./subagentActivity");
162
+ Object.defineProperty(exports, "SUBAGENT_ACTIVITY_BLOCK_TYPE", { enumerable: true, get: function () { return subagentActivity_1.SUBAGENT_ACTIVITY_BLOCK_TYPE; } });
163
+ Object.defineProperty(exports, "SUBAGENT_ACTIVITY_ORIGIN_KIND", { enumerable: true, get: function () { return subagentActivity_1.SUBAGENT_ACTIVITY_ORIGIN_KIND; } });
164
+ Object.defineProperty(exports, "describeSubagentActivity", { enumerable: true, get: function () { return subagentActivity_1.describeSubagentActivity; } });
165
+ Object.defineProperty(exports, "isSubagentActivitySystemEvent", { enumerable: true, get: function () { return subagentActivity_1.isSubagentActivitySystemEvent; } });
166
+ Object.defineProperty(exports, "parseCodexAgentMessageText", { enumerable: true, get: function () { return subagentActivity_1.parseCodexAgentMessageText; } });
167
+ Object.defineProperty(exports, "subagentActivityFromEvent", { enumerable: true, get: function () { return subagentActivity_1.subagentActivityFromEvent; } });
154
168
  var modelInfo_1 = require("./modelInfo");
155
169
  Object.defineProperty(exports, "getPricingCatalogInfo", { enumerable: true, get: function () { return modelInfo_1.getPricingCatalogInfo; } });
156
170
  Object.defineProperty(exports, "registerModelPricing", { enumerable: true, get: function () { return modelInfo_1.registerModelPricing; } });
@@ -88,6 +88,15 @@ export interface CodexRolloutParserOptions {
88
88
  * unchanged. For cheap passes over large rollouts; default false.
89
89
  */
90
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;
91
100
  }
92
101
  /** Pending tool-call state counters from {@link CodexRolloutParser.getPendingStats}. */
93
102
  export interface CodexPendingStats {
@@ -126,6 +135,18 @@ export interface CodexRolloutResumeState {
126
135
  pendingExecCommands: PendingExecCommand[];
127
136
  /** `mcp_tool_call_begin` events awaiting their end. */
128
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;
129
150
  }
130
151
  /** A valid `CodexTokenUsage` (copied), `null` for `null`, or `undefined` when invalid. Internal. */
131
152
  export declare function codexTokenUsageFrom(value: unknown): CodexTokenUsage | null | undefined;
@@ -156,6 +177,15 @@ export declare class CodexRolloutParser {
156
177
  /** Byte budget for pending calls; `null` = unlimited (no accounting). */
157
178
  private readonly maxPendingBytes;
158
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;
159
189
  /** Pending calls in insertion order, for the byte budget. */
160
190
  private pendingLedger;
161
191
  private pendingBytes;
@@ -174,6 +204,14 @@ export declare class CodexRolloutParser {
174
204
  getModelContextWindow(): number | null;
175
205
  /** Get the last observed rate limits from token_count events. */
176
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;
177
215
  /**
178
216
  * The state later lines depend on — session metadata, current model,
179
217
  * previous cumulative token total, context window, rate limits, plan mode,
@@ -214,6 +252,12 @@ export declare class CodexRolloutParser {
214
252
  private toolOutput;
215
253
  private handleSessionMeta;
216
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;
217
261
  private handleMessage;
218
262
  private handleReasoning;
219
263
  private handleFunctionCall;
@@ -22,6 +22,7 @@ exports.codexTokenUsageFrom = codexTokenUsageFrom;
22
22
  const codex_1 = require("../types/codex");
23
23
  const openCodeParser_1 = require("./openCodeParser");
24
24
  const usageNormalization_1 = require("../usageNormalization");
25
+ const subagentActivity_1 = require("../subagentActivity");
25
26
  function reportedCompaction(tokensBefore, tokensAfter) {
26
27
  if (typeof tokensBefore !== 'number' ||
27
28
  typeof tokensAfter !== 'number' ||
@@ -181,6 +182,29 @@ function extractPatchFilePaths(input) {
181
182
  exports.DEFAULT_MAX_TRACKED_TOOL_CALLS = 4096;
182
183
  /** Entries per table {@link CodexRolloutParser.exportResumeState} keeps by default. */
183
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
+ }
184
208
  function isPlainObject(value) {
185
209
  return !!value && typeof value === 'object' && !Array.isArray(value);
186
210
  }
@@ -298,6 +322,15 @@ class CodexRolloutParser {
298
322
  /** Byte budget for pending calls; `null` = unlimited (no accounting). */
299
323
  maxPendingBytes;
300
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();
301
334
  /** Pending calls in insertion order, for the byte budget. */
302
335
  pendingLedger = new Map();
303
336
  pendingBytes = 0;
@@ -313,6 +346,7 @@ class CodexRolloutParser {
313
346
  ? Math.floor(maxBytes)
314
347
  : null;
315
348
  this.metadataOnly = options.metadataOnly === true;
349
+ this.includeSubagentActivity = options.includeSubagentActivity === true;
316
350
  }
317
351
  /** Counters for the pending tool-call state and what the caps evicted. */
318
352
  getPendingStats() {
@@ -345,6 +379,16 @@ class CodexRolloutParser {
345
379
  getLastRateLimits() {
346
380
  return this.lastRateLimits;
347
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
+ }
348
392
  /**
349
393
  * The state later lines depend on — session metadata, current model,
350
394
  * previous cumulative token total, context window, rate limits, plan mode,
@@ -366,6 +410,14 @@ class CodexRolloutParser {
366
410
  patchExpandedToolUseIds: newest(this.patchExpandedToolUseIds, this.patchExpandedToolUseIds.size, limit),
367
411
  pendingExecCommands: newest(this.pendingExecCommands.values(), this.pendingExecCommands.size, limit),
368
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
+ : {}),
369
421
  });
370
422
  }
371
423
  /**
@@ -426,6 +478,21 @@ class CodexRolloutParser {
426
478
  typeof entry.timestamp === 'string')) {
427
479
  return false;
428
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
+ }
429
496
  const copy = cloneJson({
430
497
  sessionMeta: s.sessionMeta,
431
498
  lastRateLimits: s.lastRateLimits,
@@ -435,6 +502,7 @@ class CodexRolloutParser {
435
502
  });
436
503
  this.reset();
437
504
  this.sessionMeta = copy.sessionMeta;
505
+ this.historyStartOrdinal = typeof inheritedEnd === 'number' ? inheritedEnd : null;
438
506
  this.currentModel = s.currentModel;
439
507
  this.prevTotalUsage = prevTotalUsage;
440
508
  this.lastTokenUsage = lastTokenUsage;
@@ -455,15 +523,28 @@ class CodexRolloutParser {
455
523
  for (const pending of copy.mcps) {
456
524
  this.putPendingMcp(this.metadataOnly ? { ...pending, arguments: undefined } : pending);
457
525
  }
526
+ if (this.includeSubagentActivity && Array.isArray(collaborationCalls)) {
527
+ for (const [callId, name] of collaborationCalls) {
528
+ this.rememberCollaborationCall(callId, name);
529
+ }
530
+ }
458
531
  return true;
459
532
  }
460
533
  /**
461
534
  * Converts a single rollout line to zero or more session events.
462
535
  */
463
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
+ }
464
547
  switch (line.type) {
465
- case 'session_meta':
466
- return this.handleSessionMeta(line.timestamp, line.payload);
467
548
  case 'response_item':
468
549
  return this.handleResponseItem(line.timestamp, line.payload);
469
550
  case 'compacted':
@@ -479,6 +560,8 @@ class CodexRolloutParser {
479
560
  /** Reset all parser state. */
480
561
  reset() {
481
562
  this.sessionMeta = null;
563
+ this.historyStartOrdinal = null;
564
+ this.collaborationCalls.clear();
482
565
  this.currentModel = null;
483
566
  this.pendingExecCommands.clear();
484
567
  this.pendingMcpToolCalls.clear();
@@ -568,7 +651,25 @@ class CodexRolloutParser {
568
651
  return this.metadataOnly ? '' : content;
569
652
  }
570
653
  // --- Handlers ---
571
- 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 [];
572
673
  this.sessionMeta = payload;
573
674
  const text = payload.base_instructions?.text?.trim();
574
675
  return [
@@ -608,10 +709,91 @@ class CodexRolloutParser {
608
709
  return this.handleCustomToolCall(timestamp, payload);
609
710
  case 'custom_tool_call_output':
610
711
  return this.handleCustomToolCallOutput(timestamp, payload);
712
+ case 'agent_message':
713
+ return this.handleAgentMessage(timestamp, payload);
611
714
  default:
612
715
  return [];
613
716
  }
614
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
+ }
615
797
  handleMessage(timestamp, item) {
616
798
  const text = extractTextFromContent(item.content);
617
799
  if (!text)
@@ -693,6 +875,11 @@ class CodexRolloutParser {
693
875
  const canonicalName = normalizeCodexToolName(item.name);
694
876
  const normalizedInput = normalizeCodexToolInput(canonicalName, item.name, parsedArgs);
695
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
+ }
696
883
  return [
697
884
  {
698
885
  type: 'assistant',
@@ -1149,6 +1336,8 @@ class CodexRolloutParser {
1149
1336
  timestamp,
1150
1337
  },
1151
1338
  ];
1339
+ case 'item_completed':
1340
+ return this.handleItemCompleted(timestamp, event);
1152
1341
  case 'turn_aborted':
1153
1342
  case 'agent_reasoning':
1154
1343
  case 'agent_message':
@@ -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
+ }
@@ -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;
@@ -52,6 +52,7 @@ const claudeUsageDedupe_1 = require("../usage/claudeUsageDedupe");
52
52
  const subagentUsage_1 = require("../usage/subagentUsage");
53
53
  const subagentStatsCache_1 = require("./subagentStatsCache");
54
54
  const subagentSpawn_1 = require("../subagentSpawn");
55
+ const subagentEndState_1 = require("./subagentEndState");
55
56
  /**
56
57
  * Pattern for matching subagent JSONL files.
57
58
  * Files are named like: agent-<hash>.jsonl
@@ -199,6 +200,7 @@ function parseAgentFile(filePath, agentId, log) {
199
200
  let description;
200
201
  const tokens = { agentId, toolCalls: [], inputTokens: 0, outputTokens: 0 };
201
202
  const usageDeduper = new claudeUsageDedupe_1.ClaudeUsageDeduper();
203
+ const endTracker = new subagentEndState_1.SubagentEndTracker();
202
204
  let startTime;
203
205
  let endTime;
204
206
  // Task tracking state
@@ -215,6 +217,7 @@ function parseAgentFile(filePath, agentId, log) {
215
217
  if (!startTime)
216
218
  startTime = eventTimestamp;
217
219
  endTime = eventTimestamp;
220
+ endTracker.observe(event);
218
221
  // Extract token usage from assistant messages. Split lines of one
219
222
  // response repeat its usage, so count each response once.
220
223
  const usage = (0, claudeUsageDedupe_1.dedupedRawClaudeUsage)(event, usageDeduper);
@@ -337,6 +340,7 @@ function parseAgentFile(filePath, agentId, log) {
337
340
  startTime,
338
341
  endTime,
339
342
  durationMs,
343
+ endedCleanly: endTracker.endedCleanly(),
340
344
  };
341
345
  }
342
346
  return null;
@@ -63,6 +63,14 @@ export interface SubagentTrace {
63
63
  finished?: boolean;
64
64
  /** The user stopped the agent (meta `stoppedByUser`). */
65
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;
66
74
  }
67
75
  /**
68
76
  * Scans and parses subagent traces with full conversation events.
@@ -58,6 +58,7 @@ const noiseClassifier_1 = require("../formatters/noiseClassifier");
58
58
  const claudeUsageDedupe_1 = require("../usage/claudeUsageDedupe");
59
59
  const subagentUsage_1 = require("../usage/subagentUsage");
60
60
  const subagentScanner_1 = require("./subagentScanner");
61
+ const subagentEndState_1 = require("./subagentEndState");
61
62
  const subagentSpawn_1 = require("../subagentSpawn");
62
63
  const ownKey_1 = require("../ownKey");
63
64
  // ── Constants ──
@@ -109,6 +110,7 @@ function parseAgentTrace(filePath, agentId) {
109
110
  let description;
110
111
  const tokens = { agentId, toolCalls: [], inputTokens: 0, outputTokens: 0 };
111
112
  const usageDeduper = new claudeUsageDedupe_1.ClaudeUsageDeduper();
113
+ const endTracker = new subagentEndState_1.SubagentEndTracker();
112
114
  let startTime;
113
115
  let endTime;
114
116
  for (const line of lines) {
@@ -118,6 +120,7 @@ function parseAgentTrace(filePath, agentId) {
118
120
  if (!startTime)
119
121
  startTime = timestamp;
120
122
  endTime = timestamp;
123
+ endTracker.observe(raw);
121
124
  // Build SessionEvent from raw JSONL
122
125
  const sessionEvent = rawToSessionEvent(raw);
123
126
  if (!sessionEvent)
@@ -218,6 +221,8 @@ function parseAgentTrace(filePath, agentId) {
218
221
  ...(meta.requestShape ? { background: meta.requestShape === 'background' } : {}),
219
222
  ...(meta.finished !== undefined ? { finished: meta.finished } : {}),
220
223
  ...(meta.stoppedByUser !== undefined ? { stoppedByUser: meta.stoppedByUser } : {}),
224
+ ...(endTime ? { lastActivityAt: endTime } : {}),
225
+ endedCleanly: endTracker.endedCleanly(),
221
226
  events,
222
227
  children: [],
223
228
  stats: {
@@ -234,6 +239,7 @@ function parseAgentTrace(filePath, agentId) {
234
239
  startTime,
235
240
  endTime,
236
241
  durationMs,
242
+ endedCleanly: endTracker.endedCleanly(),
237
243
  },
238
244
  };
239
245
  }
@@ -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): Promise<Candidate[]>;
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, stat: fs.Stats, prior: PromptHistoryCursorSession | undefined, budget: FileBudget, context: ScanContext): Promise<FileOutcome>;
390
+ export declare function scanFile(candidate: Candidate, fileStat: RolloutContentStat, prior: PromptHistoryCursorSession | undefined, budget: FileBudget, context: ScanContext): Promise<FileOutcome>;