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
@@ -60,9 +60,11 @@ export declare const sessionMessageSchema: z.ZodObject<{
60
60
  }, z.core.$strip>;
61
61
  export declare const permissionModeSchema: z.ZodEnum<{
62
62
  default: "default";
63
+ auto: "auto";
63
64
  acceptEdits: "acceptEdits";
64
65
  bypassPermissions: "bypassPermissions";
65
66
  plan: "plan";
67
+ dontAsk: "dontAsk";
66
68
  }>;
67
69
  export declare const userSessionEventSchema: z.ZodObject<{
68
70
  timestamp: z.ZodString;
@@ -78,9 +80,11 @@ export declare const userSessionEventSchema: z.ZodObject<{
78
80
  gitBranch: z.ZodOptional<z.ZodString>;
79
81
  permissionMode: z.ZodCatch<z.ZodOptional<z.ZodEnum<{
80
82
  default: "default";
83
+ auto: "auto";
81
84
  acceptEdits: "acceptEdits";
82
85
  bypassPermissions: "bypassPermissions";
83
86
  plan: "plan";
87
+ dontAsk: "dontAsk";
84
88
  }>>>;
85
89
  rateLimits: z.ZodOptional<z.ZodObject<{
86
90
  primary: z.ZodOptional<z.ZodObject<{
@@ -167,9 +171,11 @@ export declare const assistantSessionEventSchema: z.ZodObject<{
167
171
  gitBranch: z.ZodOptional<z.ZodString>;
168
172
  permissionMode: z.ZodCatch<z.ZodOptional<z.ZodEnum<{
169
173
  default: "default";
174
+ auto: "auto";
170
175
  acceptEdits: "acceptEdits";
171
176
  bypassPermissions: "bypassPermissions";
172
177
  plan: "plan";
178
+ dontAsk: "dontAsk";
173
179
  }>>>;
174
180
  rateLimits: z.ZodOptional<z.ZodObject<{
175
181
  primary: z.ZodOptional<z.ZodObject<{
@@ -244,6 +250,7 @@ export declare const assistantSessionEventSchema: z.ZodObject<{
244
250
  }, z.core.$strip>;
245
251
  export declare const summarySessionEventSchema: z.ZodType<SummarySessionEvent>;
246
252
  export declare const systemSessionEventSchema: z.ZodObject<{
253
+ subtype: z.ZodCatch<z.ZodOptional<z.ZodString>>;
247
254
  timestamp: z.ZodString;
248
255
  entrypoint: z.ZodOptional<z.ZodString>;
249
256
  isMeta: z.ZodOptional<z.ZodBoolean>;
@@ -257,9 +264,11 @@ export declare const systemSessionEventSchema: z.ZodObject<{
257
264
  gitBranch: z.ZodOptional<z.ZodString>;
258
265
  permissionMode: z.ZodCatch<z.ZodOptional<z.ZodEnum<{
259
266
  default: "default";
267
+ auto: "auto";
260
268
  acceptEdits: "acceptEdits";
261
269
  bypassPermissions: "bypassPermissions";
262
270
  plan: "plan";
271
+ dontAsk: "dontAsk";
263
272
  }>>>;
264
273
  rateLimits: z.ZodOptional<z.ZodObject<{
265
274
  primary: z.ZodOptional<z.ZodObject<{
@@ -350,9 +359,11 @@ export declare const toolUseSessionEventSchema: z.ZodObject<{
350
359
  gitBranch: z.ZodOptional<z.ZodString>;
351
360
  permissionMode: z.ZodCatch<z.ZodOptional<z.ZodEnum<{
352
361
  default: "default";
362
+ auto: "auto";
353
363
  acceptEdits: "acceptEdits";
354
364
  bypassPermissions: "bypassPermissions";
355
365
  plan: "plan";
366
+ dontAsk: "dontAsk";
356
367
  }>>>;
357
368
  rateLimits: z.ZodOptional<z.ZodObject<{
358
369
  primary: z.ZodOptional<z.ZodObject<{
@@ -444,9 +455,11 @@ export declare const toolResultSessionEventSchema: z.ZodObject<{
444
455
  gitBranch: z.ZodOptional<z.ZodString>;
445
456
  permissionMode: z.ZodCatch<z.ZodOptional<z.ZodEnum<{
446
457
  default: "default";
458
+ auto: "auto";
447
459
  acceptEdits: "acceptEdits";
448
460
  bypassPermissions: "bypassPermissions";
449
461
  plan: "plan";
462
+ dontAsk: "dontAsk";
450
463
  }>>>;
451
464
  rateLimits: z.ZodOptional<z.ZodObject<{
452
465
  primary: z.ZodOptional<z.ZodObject<{
@@ -528,8 +541,19 @@ export interface ExtractSessionEventsOptions {
528
541
  * commandMode: 'task-notification', prompt } }`) into one `system` event
529
542
  * with `origin: { kind: 'task-notification' }`, `message: { role: 'user',
530
543
  * sourceLabel: 'task notification', content: <prompt> }`, and the line's
531
- * timestamp. They report background agents finishing. Default `false`:
532
- * attachment lines yield no events.
544
+ * timestamp. They report background agents finishing (or, as plain text,
545
+ * the user stopping them: see `parseAgentsStoppedNotice`).
546
+ *
547
+ * Also turns a background agent's `task_status` attachment with a final
548
+ * status (`{ type: 'attachment', attachment: { type: 'task_status',
549
+ * taskType: 'local_agent', taskId, status: 'completed', … } }`) into a
550
+ * `system` event with `origin: { kind: 'task-status' }` and `message: {
551
+ * role: 'system', sourceLabel: 'task status', content: [{ type:
552
+ * 'task_status', taskId, taskType, status, description }] }` (read it with
553
+ * `taskStatusFromEvent`). Default `false`: attachment lines yield no events.
554
+ *
555
+ * System lines (`agents_killed` among them) parse either way and keep
556
+ * their `subtype`.
533
557
  */
534
558
  includeTaskNotifications?: boolean;
535
559
  }
@@ -45,6 +45,8 @@ exports.permissionModeSchema = zod_1.z.enum([
45
45
  'acceptEdits',
46
46
  'bypassPermissions',
47
47
  'plan',
48
+ 'auto',
49
+ 'dontAsk',
48
50
  ]);
49
51
  // ── SessionEvent ──
50
52
  const eventBaseShape = {
@@ -57,8 +59,8 @@ const eventBaseShape = {
57
59
  promptSource: zod_1.z.string().optional(),
58
60
  cwd: zod_1.z.string().optional(),
59
61
  gitBranch: zod_1.z.string().optional(),
60
- // A mode this release doesn't know (Claude Code added `auto`) is dropped
61
- // rather than rejecting the whole line with its prompt or tool results.
62
+ // A mode this release doesn't know is dropped rather than rejecting the
63
+ // whole line with its prompt or tool results.
62
64
  permissionMode: exports.permissionModeSchema.optional().catch(undefined),
63
65
  rateLimits: zod_1.z
64
66
  .object({
@@ -148,6 +150,9 @@ exports.systemSessionEventSchema = zod_1.z.object({
148
150
  type: zod_1.z.literal('system'),
149
151
  message: exports.sessionMessageSchema.optional(),
150
152
  ...eventBaseShape,
153
+ // Claude Code's system-line subtype (`agents_killed`, `turn_duration`, …);
154
+ // a malformed one is dropped rather than rejecting the line.
155
+ subtype: zod_1.z.string().optional().catch(undefined),
151
156
  });
152
157
  exports.toolUseSessionEventSchema = zod_1.z.object({
153
158
  type: zod_1.z.literal('tool_use'),
@@ -187,7 +192,7 @@ function extractSessionEventsWithProvenance(raw, depth, inherited, options) {
187
192
  if (direct.success)
188
193
  return [{ ...inherited, ...direct.data }];
189
194
  if (options.includeTaskNotifications) {
190
- const notification = taskNotificationAttachmentEvent(raw);
195
+ const notification = taskNotificationAttachmentEvent(raw) ?? taskStatusAttachmentEvent(raw);
191
196
  if (notification)
192
197
  return [{ ...inherited, ...notification }];
193
198
  }
@@ -224,6 +229,29 @@ function taskNotificationAttachmentEvent(raw) {
224
229
  const parsed = exports.systemSessionEventSchema.safeParse(candidate);
225
230
  return parsed.success ? parsed.data : null;
226
231
  }
232
+ /** The `system` event for a background agent's final `task_status` attachment, or `null`. */
233
+ function taskStatusAttachmentEvent(raw) {
234
+ const status = (0, subagentSpawn_1.taskStatusFromAttachment)(raw);
235
+ if (!status || !(0, subagentSpawn_1.isFinalTaskStatus)(status.status))
236
+ return null;
237
+ const line = raw;
238
+ const candidate = {
239
+ type: 'system',
240
+ timestamp: line.timestamp,
241
+ origin: { kind: subagentSpawn_1.TASK_STATUS_ORIGIN_KIND },
242
+ message: {
243
+ role: 'system',
244
+ sourceLabel: 'task status',
245
+ content: [{ type: subagentSpawn_1.TASK_STATUS_BLOCK_TYPE, ...status }],
246
+ },
247
+ };
248
+ for (const key of ['entrypoint', 'isSidechain', 'cwd', 'gitBranch']) {
249
+ if (line[key] !== undefined)
250
+ candidate[key] = line[key];
251
+ }
252
+ const parsed = exports.systemSessionEventSchema.safeParse(candidate);
253
+ return parsed.success ? parsed.data : null;
254
+ }
227
255
  function progressProvenance(raw) {
228
256
  const value = raw;
229
257
  const provenance = {};
@@ -25,6 +25,12 @@ export declare class SessionMonitor {
25
25
  constructor(provider: SessionProviderBase, options?: SessionMonitorOptions);
26
26
  attach(sessionPath: string, replay?: boolean): AggregatedMetrics;
27
27
  attachLatest(workspacePath: string, replay?: boolean): AggregatedMetrics | null;
28
+ /**
29
+ * Reads and aggregates what the session gained since the last call. When
30
+ * the reader had to start over (the file was truncated, rewritten, or
31
+ * replaced) the aggregator is reset first and the returned events are the
32
+ * session from the beginning.
33
+ */
28
34
  poll(): SessionEvent[];
29
35
  process(event: SessionEvent): void;
30
36
  onEvent(listener: (event: SessionEvent) => void): () => void;
@@ -67,12 +67,18 @@ class SessionMonitor {
67
67
  const sessionPath = this.provider.findActiveSession(workspacePath);
68
68
  return sessionPath ? this.attach(sessionPath, replay) : null;
69
69
  }
70
+ /**
71
+ * Reads and aggregates what the session gained since the last call. When
72
+ * the reader had to start over (the file was truncated, rewritten, or
73
+ * replaced) the aggregator is reset first and the returned events are the
74
+ * session from the beginning.
75
+ */
70
76
  poll() {
71
77
  if (!this.reader)
72
78
  return [];
79
+ const events = this.reader.readNew();
73
80
  if (this.reader.wasTruncated())
74
81
  this.aggregator.reset();
75
- const events = this.reader.readNew();
76
82
  this.consume(events);
77
83
  return events;
78
84
  }
@@ -57,6 +57,7 @@ exports.readSessionPreviewAsync = readSessionPreviewAsync;
57
57
  exports.listSessionPreviews = listSessionPreviews;
58
58
  exports.listSessionPreviewsAsync = listSessionPreviewsAsync;
59
59
  const fs = __importStar(require("fs"));
60
+ const compressedRollouts_1 = require("./providers/compressedRollouts");
60
61
  function emptyPrefixScan() {
61
62
  return { firstTimestamp: null, workspacePath: null };
62
63
  }
@@ -454,6 +455,12 @@ function scanPrefix(sessionPath, maxPrefixBytes) {
454
455
  const result = { firstTimestamp: null, workspacePath: null };
455
456
  let content;
456
457
  let sawWholeFile;
458
+ if ((0, compressedRollouts_1.isCompressedRolloutPath)(sessionPath)) {
459
+ // A compressed Codex rollout: scan its decompressed prefix.
460
+ const head = (0, compressedRollouts_1.readRolloutHead)(sessionPath, Math.max(1, maxPrefixBytes) + 1);
461
+ parsePrefixInto(result, head.subarray(0, Math.max(1, maxPrefixBytes)).toString('utf8'), head.length <= maxPrefixBytes);
462
+ return result;
463
+ }
457
464
  try {
458
465
  const descriptor = fs.openSync(sessionPath, 'r');
459
466
  try {
@@ -509,6 +516,8 @@ function scanPrefix(sessionPath, maxPrefixBytes) {
509
516
  }
510
517
  async function scanPrefixAsync(sessionPath, maxPrefixBytes) {
511
518
  const result = { firstTimestamp: null, workspacePath: null };
519
+ if ((0, compressedRollouts_1.isCompressedRolloutPath)(sessionPath))
520
+ return scanPrefix(sessionPath, maxPrefixBytes);
512
521
  let handle = null;
513
522
  try {
514
523
  handle = await fs.promises.open(sessionPath, 'r');
@@ -62,6 +62,11 @@ export interface CollectSessionPromptHistoryOptions {
62
62
  include?: SessionPromptHistoryInclude;
63
63
  bounds?: SessionPromptHistoryBounds;
64
64
  signal?: AbortSignal;
65
+ /**
66
+ * Codex: also read compressed rollouts (`….jsonl.zst`), as for
67
+ * `collectPromptHistory()`. Default false.
68
+ */
69
+ includeCompressedRollouts?: boolean;
65
70
  }
66
71
  export interface PromptHistorySessionPrompt extends Omit<PromptHistoryEntry, 'provider' | 'sessionId' | 'sessionKey'> {
67
72
  /** With `include.replies`: the agent's last text for this prompt's turn. */
@@ -14,42 +14,9 @@
14
14
  * `sessionIds` continues. Scope fails closed as in `collectPromptHistory()`,
15
15
  * and replies or signals that belong to an out-of-scope prompt are dropped.
16
16
  */
17
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
18
- if (k2 === undefined) k2 = k;
19
- var desc = Object.getOwnPropertyDescriptor(m, k);
20
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
21
- desc = { enumerable: true, get: function() { return m[k]; } };
22
- }
23
- Object.defineProperty(o, k2, desc);
24
- }) : (function(o, m, k, k2) {
25
- if (k2 === undefined) k2 = k;
26
- o[k2] = m[k];
27
- }));
28
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
29
- Object.defineProperty(o, "default", { enumerable: true, value: v });
30
- }) : function(o, v) {
31
- o["default"] = v;
32
- });
33
- var __importStar = (this && this.__importStar) || (function () {
34
- var ownKeys = function(o) {
35
- ownKeys = Object.getOwnPropertyNames || function (o) {
36
- var ar = [];
37
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
38
- return ar;
39
- };
40
- return ownKeys(o);
41
- };
42
- return function (mod) {
43
- if (mod && mod.__esModule) return mod;
44
- var result = {};
45
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
46
- __setModuleDefault(result, mod);
47
- return result;
48
- };
49
- })();
50
17
  Object.defineProperty(exports, "__esModule", { value: true });
51
18
  exports.collectSessionPromptHistory = collectSessionPromptHistory;
52
- const fs = __importStar(require("fs"));
19
+ const compressedRollouts_1 = require("./providers/compressedRollouts");
53
20
  const promptHistory_1 = require("./promptHistory");
54
21
  const sessionPromptSignals_1 = require("./sessionPromptSignals");
55
22
  const DEFAULT_SESSION_BOUNDS = {
@@ -224,7 +191,9 @@ function definedBounds(bounds) {
224
191
  async function selectCandidates(options, listingRoots) {
225
192
  const sinceMs = options.since ? options.since.getTime() : null;
226
193
  const providers = options.providers ?? ['claude-code', 'codex'];
227
- let candidates = await (0, promptHistory_1.listCandidates)(providers, listingRoots, sinceMs);
194
+ let candidates = await (0, promptHistory_1.listCandidates)(providers, listingRoots, sinceMs, {
195
+ includeCompressedRollouts: options.includeCompressedRollouts,
196
+ });
228
197
  if (options.sessionIds || options.sessionRefs) {
229
198
  const ids = new Set(options.sessionIds ?? []);
230
199
  const keys = new Set();
@@ -242,7 +211,7 @@ async function selectCandidates(options, listingRoots) {
242
211
  const selected = [];
243
212
  for (const candidate of candidates) {
244
213
  try {
245
- selected.push({ candidate, stat: await fs.promises.stat(candidate.filePath) });
214
+ selected.push({ candidate, stat: await (0, compressedRollouts_1.statRolloutContent)(candidate.filePath) });
246
215
  }
247
216
  catch {
248
217
  // Gone since it was listed.
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Subagent lifecycle records as `system` session events.
3
+ *
4
+ * Codex records its multi-agent lifecycle in the parent's rollout:
5
+ * `item_completed` → `SubAgentActivity` items link a `spawn_agent` call to the
6
+ * child thread it started (`started`), mark each finished child turn
7
+ * (`completed`, Codex 0.150+), and note `followup_task` / `send_message`
8
+ * calls (`interacted`) and `interrupt_agent` calls (`interrupted`); a child's
9
+ * report arrives as an `agent_message` whose text starts
10
+ * `Message Type: FINAL_ANSWER`. With
11
+ * `CodexRolloutParserOptions.includeSubagentActivity`, the parser turns each
12
+ * into one `system` event with `origin: { kind: 'subagent-activity' }` whose
13
+ * `message.content` is a single `{ type: 'subagent_activity', ... }` block.
14
+ *
15
+ * Those events are bookkeeping, not messages: transcripts, context
16
+ * attribution, and message counts skip them, as they do task notifications.
17
+ *
18
+ * Pure: no filesystem access. Browser-safe.
19
+ *
20
+ * @module subagentActivity
21
+ */
22
+ import type { SessionEvent } from './types/sessionEvent';
23
+ /** `origin.kind` of a subagent-activity `system` event. */
24
+ export declare const SUBAGENT_ACTIVITY_ORIGIN_KIND = "subagent-activity";
25
+ /** `type` of the content block that carries the activity. */
26
+ export declare const SUBAGENT_ACTIVITY_BLOCK_TYPE = "subagent_activity";
27
+ /**
28
+ * - `started`: a spawn call started the child thread (`callId` = the spawn
29
+ * call's id, `agentThreadId` = the child's rollout id).
30
+ * - `completed`: one child turn finished (`turnId` = that child turn).
31
+ * - `interacted`: the parent sent the child work or a message (`callId` =
32
+ * the `followup_task` or `send_message` call; `tool` says which, when the
33
+ * call was seen). After `followup_task` the child runs again.
34
+ * - `interrupted`: the parent interrupted the child (`interrupt_agent`).
35
+ * - `final-answer`: the child's final report reached the parent
36
+ * (`agentPath` = its author, `text` = the report).
37
+ */
38
+ export type SubagentActivityKind = 'started' | 'completed' | 'interacted' | 'interrupted' | 'final-answer';
39
+ export interface SubagentActivity {
40
+ kind: SubagentActivityKind;
41
+ /**
42
+ * The parent's tool call the record refers to: the spawn call for
43
+ * `started`, the `followup_task` / `send_message` call for `interacted`,
44
+ * the `interrupt_agent` call for `interrupted`.
45
+ */
46
+ callId?: string;
47
+ /**
48
+ * Raw name of the `callId` tool call (`spawn_agent`, `followup_task`,
49
+ * `send_message`, `interrupt_agent`) when the parser saw that call.
50
+ */
51
+ tool?: string;
52
+ /** `completed`: the child turn that finished. */
53
+ turnId?: string;
54
+ /** The child's thread id, which is its rollout's session id. */
55
+ agentThreadId?: string;
56
+ /** The child's agent path, e.g. `/root/review_builder` (`final-answer`: the author). */
57
+ agentPath?: string;
58
+ /** `final-answer`: the agent path the report went to, e.g. `/root`. */
59
+ recipient?: string;
60
+ /** `started`: the child's model and reasoning effort, when recorded. */
61
+ model?: string;
62
+ reasoningEffort?: string;
63
+ /** The thread and turn that recorded the activity (the parent's). */
64
+ parentThreadId?: string;
65
+ parentTurnId?: string;
66
+ /** `final-answer`: the report text (empty in metadata-only parsing). */
67
+ text?: string;
68
+ }
69
+ /** Whether an event is a subagent-activity `system` event (see the module docs). */
70
+ export declare function isSubagentActivitySystemEvent(event: SessionEvent): boolean;
71
+ /**
72
+ * The activity a subagent-activity `system` event carries, copied with only
73
+ * its known string fields; `null` for any other event or a malformed block.
74
+ */
75
+ export declare function subagentActivityFromEvent(event: SessionEvent): SubagentActivity | null;
76
+ /** A one-line, content-free description, e.g. `Subagent /root/review started`. */
77
+ export declare function describeSubagentActivity(activity: SubagentActivity): string;
78
+ /** Fields of a Codex inter-agent message text (`Message Type: … Payload:\n…`). */
79
+ export interface CodexAgentMessageText {
80
+ /** `MESSAGE`, `NEW_TASK`, `FINAL_ANSWER`, … */
81
+ messageType: string;
82
+ taskName?: string;
83
+ sender?: string;
84
+ /** Everything after the `Payload:` line. */
85
+ payload: string;
86
+ }
87
+ /**
88
+ * Parses a Codex inter-agent message text. Returns `null` unless it starts
89
+ * with a `Message Type:` line.
90
+ */
91
+ export declare function parseCodexAgentMessageText(text: unknown): CodexAgentMessageText | null;
@@ -0,0 +1,132 @@
1
+ "use strict";
2
+ /**
3
+ * Subagent lifecycle records as `system` session events.
4
+ *
5
+ * Codex records its multi-agent lifecycle in the parent's rollout:
6
+ * `item_completed` → `SubAgentActivity` items link a `spawn_agent` call to the
7
+ * child thread it started (`started`), mark each finished child turn
8
+ * (`completed`, Codex 0.150+), and note `followup_task` / `send_message`
9
+ * calls (`interacted`) and `interrupt_agent` calls (`interrupted`); a child's
10
+ * report arrives as an `agent_message` whose text starts
11
+ * `Message Type: FINAL_ANSWER`. With
12
+ * `CodexRolloutParserOptions.includeSubagentActivity`, the parser turns each
13
+ * into one `system` event with `origin: { kind: 'subagent-activity' }` whose
14
+ * `message.content` is a single `{ type: 'subagent_activity', ... }` block.
15
+ *
16
+ * Those events are bookkeeping, not messages: transcripts, context
17
+ * attribution, and message counts skip them, as they do task notifications.
18
+ *
19
+ * Pure: no filesystem access. Browser-safe.
20
+ *
21
+ * @module subagentActivity
22
+ */
23
+ Object.defineProperty(exports, "__esModule", { value: true });
24
+ exports.SUBAGENT_ACTIVITY_BLOCK_TYPE = exports.SUBAGENT_ACTIVITY_ORIGIN_KIND = void 0;
25
+ exports.isSubagentActivitySystemEvent = isSubagentActivitySystemEvent;
26
+ exports.subagentActivityFromEvent = subagentActivityFromEvent;
27
+ exports.describeSubagentActivity = describeSubagentActivity;
28
+ exports.parseCodexAgentMessageText = parseCodexAgentMessageText;
29
+ /** `origin.kind` of a subagent-activity `system` event. */
30
+ exports.SUBAGENT_ACTIVITY_ORIGIN_KIND = 'subagent-activity';
31
+ /** `type` of the content block that carries the activity. */
32
+ exports.SUBAGENT_ACTIVITY_BLOCK_TYPE = 'subagent_activity';
33
+ const KINDS = new Set([
34
+ 'started',
35
+ 'completed',
36
+ 'interacted',
37
+ 'interrupted',
38
+ 'final-answer',
39
+ ]);
40
+ const STRING_FIELDS = [
41
+ 'callId',
42
+ 'tool',
43
+ 'turnId',
44
+ 'agentThreadId',
45
+ 'agentPath',
46
+ 'recipient',
47
+ 'model',
48
+ 'reasoningEffort',
49
+ 'parentThreadId',
50
+ 'parentTurnId',
51
+ 'text',
52
+ ];
53
+ /** Whether an event is a subagent-activity `system` event (see the module docs). */
54
+ function isSubagentActivitySystemEvent(event) {
55
+ return event.type === 'system' && event.origin?.kind === exports.SUBAGENT_ACTIVITY_ORIGIN_KIND;
56
+ }
57
+ /**
58
+ * The activity a subagent-activity `system` event carries, copied with only
59
+ * its known string fields; `null` for any other event or a malformed block.
60
+ */
61
+ function subagentActivityFromEvent(event) {
62
+ if (!isSubagentActivitySystemEvent(event))
63
+ return null;
64
+ const content = event.message?.content;
65
+ if (!Array.isArray(content))
66
+ return null;
67
+ for (const block of content) {
68
+ if (!block || typeof block !== 'object')
69
+ continue;
70
+ const raw = block;
71
+ if (raw.type !== exports.SUBAGENT_ACTIVITY_BLOCK_TYPE)
72
+ continue;
73
+ if (typeof raw.kind !== 'string' || !KINDS.has(raw.kind))
74
+ return null;
75
+ const activity = { kind: raw.kind };
76
+ for (const field of STRING_FIELDS) {
77
+ const value = raw[field];
78
+ if (typeof value === 'string' && value.length > 0)
79
+ activity[field] = value;
80
+ }
81
+ return activity;
82
+ }
83
+ return null;
84
+ }
85
+ /** A one-line, content-free description, e.g. `Subagent /root/review started`. */
86
+ function describeSubagentActivity(activity) {
87
+ const who = activity.agentPath ?? activity.agentThreadId ?? 'subagent';
88
+ const name = `Subagent ${who}`;
89
+ switch (activity.kind) {
90
+ case 'started':
91
+ return `${name} started`;
92
+ case 'completed':
93
+ return `${name} finished a turn`;
94
+ case 'interacted':
95
+ return activity.tool === 'send_message'
96
+ ? `Message sent to subagent ${who}`
97
+ : `${name} given a follow-up task`;
98
+ case 'interrupted':
99
+ return `${name} interrupted`;
100
+ case 'final-answer':
101
+ return `${name} reported its final answer`;
102
+ }
103
+ }
104
+ const MESSAGE_HEADER = /^Message Type:[ \t]*([A-Z_]+)[ \t]*(?:\r?\n|$)/;
105
+ /**
106
+ * Parses a Codex inter-agent message text. Returns `null` unless it starts
107
+ * with a `Message Type:` line.
108
+ */
109
+ function parseCodexAgentMessageText(text) {
110
+ if (typeof text !== 'string')
111
+ return null;
112
+ const header = MESSAGE_HEADER.exec(text);
113
+ if (!header)
114
+ return null;
115
+ const rest = text.slice(header[0].length);
116
+ const payloadAt = /(?:^|\n)Payload:[ \t]*(?:\r?\n|$)/.exec(rest);
117
+ const head = payloadAt ? rest.slice(0, payloadAt.index) : rest;
118
+ const payload = payloadAt ? rest.slice(payloadAt.index + payloadAt[0].length) : '';
119
+ const field = (name) => {
120
+ const match = new RegExp(`(?:^|\\n)${name}:[ \\t]*([^\\n]*)`).exec(head);
121
+ const value = match?.[1]?.trim();
122
+ return value ? value : undefined;
123
+ };
124
+ const taskName = field('Task name');
125
+ const sender = field('Sender');
126
+ return {
127
+ messageType: header[1],
128
+ ...(taskName ? { taskName } : {}),
129
+ ...(sender ? { sender } : {}),
130
+ payload,
131
+ };
132
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * How to show a subagent: its lifecycle's outcome once something settled
3
+ * it, otherwise `running`, unless its transcript or its parent says it has
4
+ * stopped in all but name.
5
+ *
6
+ * Some background agents never get a final notification (they stopped after
7
+ * an interim "may resume" notice, the user stopped them from the UI, or a
8
+ * restart killed them). Their lifecycles stay `running`. This rule reads
9
+ * them as:
10
+ *
11
+ * - `finished-unreported`: the transcript ended cleanly (last assistant turn
12
+ * `end_turn` / `stop_sequence`, no tool call unanswered) and has been quiet
13
+ * for {@link SUBAGENT_UNREPORTED_QUIET_MS}.
14
+ * - `ended`: neither the parent session nor the transcript has been active for
15
+ * {@link SUBAGENT_PARENT_IDLE_ENDED_MS}.
16
+ *
17
+ * Neither is stored: the state is recomputed from the latest data on every
18
+ * call, so an agent that resumes (its transcript grows, or a later
19
+ * notification reopens its lifecycle) reads as `running` again. Silence alone
20
+ * is never enough for `finished-unreported`: live agents can be quiet for
21
+ * hours.
22
+ *
23
+ * Pure: no filesystem access. Browser-safe.
24
+ *
25
+ * @module subagentDisplayState
26
+ */
27
+ import type { SubagentLifecycle } from './aggregation/types';
28
+ /** Quiet time after a clean end before a running agent reads as finished without a report (2 min). */
29
+ export declare const SUBAGENT_UNREPORTED_QUIET_MS: number;
30
+ /** Parent and transcript idle time after which a running agent reads as ended (6 h). */
31
+ export declare const SUBAGENT_PARENT_IDLE_ENDED_MS: number;
32
+ /** How a subagent is shown. The last four are lifecycle outcomes. */
33
+ export type SubagentDisplayStateName = 'running' | 'finished-unreported' | 'ended' | 'completed' | 'failed' | 'stopped' | 'killed';
34
+ /** Why a subagent reads as it does. */
35
+ export type SubagentDisplayReason =
36
+ /** Its lifecycle is settled; the state is the outcome. */
37
+ 'settled'
38
+ /** Claude Code's meta flags (`finished`, `stoppedByUser`) on a running lifecycle. */
39
+ | 'meta'
40
+ /** The transcript ended cleanly and has been quiet. */
41
+ | 'clean-end'
42
+ /** The parent session (and the transcript) has been idle. */
43
+ | 'parent-idle';
44
+ export interface SubagentDisplayState {
45
+ state: SubagentDisplayStateName;
46
+ /** Absent for a plain `running`. */
47
+ reason?: SubagentDisplayReason;
48
+ }
49
+ /** The lifecycle fields the rule reads. */
50
+ export type SubagentDisplayLifecycle = Pick<SubagentLifecycle, 'status' | 'outcome'>;
51
+ /**
52
+ * Transcript facts the rule reads; a `SubagentTrace`, or a `SubagentStats`
53
+ * mapped with `lastActivityAt: stats.endTime`.
54
+ */
55
+ export interface SubagentDisplayTrace {
56
+ /** The transcript ended cleanly (see `SubagentTrace.endedCleanly`). */
57
+ endedCleanly?: boolean;
58
+ /** Time of the transcript's last record. */
59
+ lastActivityAt?: Date | string | number;
60
+ /** Claude Code meta `finished`. */
61
+ finished?: boolean;
62
+ /** Claude Code meta `stoppedByUser`. */
63
+ stoppedByUser?: boolean;
64
+ }
65
+ export interface SubagentDisplayOptions {
66
+ /** The current time (epoch ms or a Date). */
67
+ now: number | Date;
68
+ /** Time of the parent session's last event, when known. */
69
+ parentLastActivity?: Date | string | number | null;
70
+ }
71
+ /**
72
+ * Resolves how to show a subagent (see the module docs). Settled lifecycles
73
+ * map to their outcome (`completed` when none is recorded). A running one is
74
+ * `stopped` / `completed` when its meta flags say so, `finished-unreported`
75
+ * after a clean end and {@link SUBAGENT_UNREPORTED_QUIET_MS} of quiet,
76
+ * `ended` when the parent and transcript have both been idle for longer than
77
+ * {@link SUBAGENT_PARENT_IDLE_ENDED_MS}, and `running` otherwise.
78
+ */
79
+ export declare function resolveSubagentDisplayState(lifecycle: SubagentDisplayLifecycle, trace: SubagentDisplayTrace | null | undefined, options: SubagentDisplayOptions): SubagentDisplayState;
80
+ /** A short label: `running`, `finished (no report)`, `ended`, `completed`, `failed`, `stopped`, `killed`. */
81
+ export declare function subagentDisplayLabel(state: SubagentDisplayStateName): string;