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
@@ -85,7 +85,7 @@ export interface SessionEventBase {
85
85
  /** Provider-reported git branch for this event/session. */
86
86
  gitBranch?: string;
87
87
  /** Permission mode active when this event occurred */
88
- permissionMode?: 'default' | 'acceptEdits' | 'bypassPermissions' | 'plan';
88
+ permissionMode?: PermissionMode;
89
89
  /** Subscription rate limits, when provided by the session source. */
90
90
  rateLimits?: {
91
91
  primary?: {
@@ -166,6 +166,11 @@ export interface SystemSessionEvent extends SessionEventBase {
166
166
  type: 'system';
167
167
  /** Bookkeeping-only system rows legitimately have no message. */
168
168
  message?: SessionMessage;
169
+ /**
170
+ * The provider's system-line subtype, when it wrote one (Claude Code:
171
+ * `turn_duration`, `compact_boundary`, `agents_killed`, …).
172
+ */
173
+ subtype?: string;
169
174
  }
170
175
  export interface ToolUseSessionEvent extends SessionEventBase {
171
176
  type: 'tool_use';
@@ -187,9 +192,11 @@ export interface ToolResultSessionEvent extends SessionEventBase {
187
192
  /** Canonical provider-neutral event contract. */
188
193
  export type SessionEvent = UserSessionEvent | AssistantSessionEvent | SummarySessionEvent | SystemSessionEvent | ToolUseSessionEvent | ToolResultSessionEvent;
189
194
  /**
190
- * Permission mode values from Claude Code JSONL.
195
+ * Permission mode values from Claude Code JSONL. `auto` approves or denies
196
+ * permission prompts with a model classifier; `dontAsk` denies anything not
197
+ * pre-approved. Unknown future modes are dropped by the schema, not rejected.
191
198
  */
192
- export type PermissionMode = 'default' | 'acceptEdits' | 'bypassPermissions' | 'plan';
199
+ export type PermissionMode = 'default' | 'acceptEdits' | 'bypassPermissions' | 'plan' | 'auto' | 'dontAsk';
193
200
  /**
194
201
  * A permission mode change event in the session timeline.
195
202
  */
@@ -423,6 +430,34 @@ export interface SubagentStats {
423
430
  endTime?: Date;
424
431
  /** Duration in milliseconds */
425
432
  durationMs?: number;
433
+ /**
434
+ * Codex: the subagent's rollout file — the `.jsonl`, or the `.jsonl.zst`
435
+ * when only the compressed one exists (with `includeCompressedRollouts`).
436
+ */
437
+ filePath?: string;
438
+ /**
439
+ * Codex: the parent's spawn call (`spawn_agent` call id) that started this
440
+ * subagent — the tool-use id of that call in the parent's events.
441
+ */
442
+ spawnToolUseId?: string;
443
+ /** Codex: the display nickname Codex gave the subagent (e.g. `Banach`). */
444
+ agentNickname?: string;
445
+ /** Codex: the subagent's agent path (e.g. `/root/review_builder`). */
446
+ agentPath?: string;
447
+ /**
448
+ * Codex: the thread that spawned this subagent — the session's id for its
449
+ * own children, another subagent's `agentId` below that.
450
+ */
451
+ parentAgentId?: string;
452
+ /** Codex: spawn depth — 1 for the session's own children, 2 for theirs, … */
453
+ depth?: number;
454
+ /**
455
+ * The subagent's transcript ended cleanly: Claude Code, its last assistant
456
+ * turn stopped with `end_turn` / `stop_sequence` and left no tool call
457
+ * unanswered; Codex, its last own turn ended with `task_complete`. A later
458
+ * prompt (a resumed agent) clears it. Absent when unknown.
459
+ */
460
+ endedCleanly?: boolean;
426
461
  }
427
462
  /**
428
463
  * Pending user request awaiting assistant response.
@@ -15,7 +15,10 @@
15
15
  * tagged with the subagent, and their sizes and mtimes join the fingerprint.
16
16
  * (Codex spawned subagents and forks are top-level rollouts, already listed
17
17
  * as sessions of their own; their events are tagged with the parent read
18
- * from the rollout's first line.)
18
+ * from the rollout's first line; the parent history a forked child embeds
19
+ * is not counted (it carries no token usage). Compressed rollouts
20
+ * (`….jsonl.zst`) are read when a Codex provider was created with
21
+ * `includeCompressedRollouts`.)
19
22
  *
20
23
  * Each event also carries the ordinal of the human prompt it answered, from
21
24
  * the same scanner as `collectPromptHistory()`, so usage joins prompt history
@@ -16,7 +16,10 @@
16
16
  * tagged with the subagent, and their sizes and mtimes join the fingerprint.
17
17
  * (Codex spawned subagents and forks are top-level rollouts, already listed
18
18
  * as sessions of their own; their events are tagged with the parent read
19
- * from the rollout's first line.)
19
+ * from the rollout's first line; the parent history a forked child embeds
20
+ * is not counted (it carries no token usage). Compressed rollouts
21
+ * (`….jsonl.zst`) are read when a Codex provider was created with
22
+ * `includeCompressedRollouts`.)
20
23
  *
21
24
  * Each event also carries the ordinal of the human prompt it answered, from
22
25
  * the same scanner as `collectPromptHistory()`, so usage joins prompt history
@@ -12,6 +12,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.toFollowEvents = toFollowEvents;
13
13
  const toolSummary_1 = require("../formatters/toolSummary");
14
14
  const subagentSpawn_1 = require("../subagentSpawn");
15
+ const subagentActivity_1 = require("../subagentActivity");
15
16
  /**
16
17
  * Converts a single SessionEvent to one or more FollowEvents.
17
18
  *
@@ -166,6 +167,35 @@ function toFollowEvents(event, providerId) {
166
167
  });
167
168
  break;
168
169
  }
170
+ // The user stopping background agents, or a background agent's final status.
171
+ const stopped = (0, subagentSpawn_1.agentsStoppedNoticeFromEvent)(event);
172
+ const taskStatus = stopped ? null : (0, subagentSpawn_1.taskStatusFromEvent)(event);
173
+ if (stopped || taskStatus || (0, subagentSpawn_1.isAgentsKilledSystemEvent)(event)) {
174
+ events.push({
175
+ providerId,
176
+ type: 'system',
177
+ timestamp: ts,
178
+ summary: stopped
179
+ ? (0, subagentSpawn_1.describeAgentsStopped)(stopped)
180
+ : taskStatus
181
+ ? (0, subagentSpawn_1.describeTaskStatus)(taskStatus)
182
+ : (0, subagentSpawn_1.describeAgentsStopped)(null),
183
+ raw: event,
184
+ });
185
+ break;
186
+ }
187
+ // A subagent lifecycle record (opt-in Codex parser event): a content-free summary.
188
+ const activity = (0, subagentActivity_1.subagentActivityFromEvent)(event);
189
+ if (activity) {
190
+ events.push({
191
+ providerId,
192
+ type: 'system',
193
+ timestamp: ts,
194
+ summary: (0, subagentActivity_1.describeSubagentActivity)(activity),
195
+ raw: event,
196
+ });
197
+ break;
198
+ }
169
199
  const text = extractTextContent(event.message?.content);
170
200
  const label = event.message?.sourceLabel || event.message?.role || 'system';
171
201
  const summary = text ||
@@ -43,6 +43,7 @@ exports.captureJsonlCheckpoint = captureJsonlCheckpoint;
43
43
  exports.verifyJsonlCheckpoint = verifyJsonlCheckpoint;
44
44
  const fs = __importStar(require("fs"));
45
45
  const fileHead_1 = require("../fileHead");
46
+ const fileContinuity_1 = require("../fileContinuity");
46
47
  const jsonl_1 = require("../parsers/jsonl");
47
48
  const DEFAULT_DEBOUNCE_MS = 100;
48
49
  const DEFAULT_CATCHUP_INTERVAL_MS = 30_000;
@@ -168,10 +169,8 @@ class JsonlTailReader {
168
169
  /** Inside a line over `maxLineBytes`: drop bytes until its newline. */
169
170
  skippingLine = false;
170
171
  currentLine = { offset: 0, length: 0 };
171
- /** Identity and prefix of the file the offsets refer to, once read. */
172
- identity = null;
173
- head = null;
174
- lastMtimeMs = -1;
172
+ /** Identity, mtime, and prefix of the file the offsets refer to, once read. */
173
+ continuity = new fileContinuity_1.FileContinuity();
175
174
  maxReadBytes;
176
175
  maxLineBytes;
177
176
  rewritePolicy;
@@ -379,21 +378,10 @@ class JsonlTailReader {
379
378
  }
380
379
  /** Whether the continuity check needs bytes from the file. */
381
380
  probeNeeded(stat) {
382
- return (this.identity === null ||
383
- stat.dev !== this.identity.dev ||
384
- stat.ino !== this.identity.ino ||
385
- stat.size < this.readOffset ||
386
- stat.mtimeMs !== this.lastMtimeMs ||
387
- this.head === null ||
388
- this.head.bytes < Math.min(fileHead_1.HEAD_BYTES, stat.size));
381
+ return this.continuity.needsProbe(stat, this.readOffset);
389
382
  }
390
383
  probeSync(fd, stat) {
391
- return {
392
- prefix: readPrefix(fd, stat),
393
- before: this.committedOffset > 0 && this.committedOffset <= stat.size
394
- ? readAt(fd, this.committedOffset - 1, 1)
395
- : null,
396
- };
384
+ return (0, fileContinuity_1.probeFileContinuity)(fd, stat, this.committedOffset);
397
385
  }
398
386
  async probeAsync(handle, stat) {
399
387
  const committedOffset = this.committedOffset;
@@ -410,31 +398,16 @@ class JsonlTailReader {
410
398
  * newline. `null` when it can continue.
411
399
  */
412
400
  resetReason(stat, probe) {
413
- if (this.identity && (stat.dev !== this.identity.dev || stat.ino !== this.identity.ino)) {
414
- return 'replaced';
415
- }
416
- if (stat.size < this.readOffset)
417
- return 'truncated';
418
- if (this.identity && stat.mtimeMs !== this.lastMtimeMs && probe) {
419
- if (this.head && !(0, fileHead_1.headMatches)(this.head, probe.prefix))
420
- return 'rewritten';
421
- if (probe.before && probe.before[0] !== 0x0a)
422
- return 'rewritten';
423
- }
424
- return null;
401
+ return this.continuity.reasonFor(stat, this.readOffset, probe);
425
402
  }
426
403
  /** Records the identity and prefix of the file the offsets now refer to. */
427
404
  adoptFile(stat, probe) {
428
- this.identity = { dev: stat.dev, ino: stat.ino };
429
- this.lastMtimeMs = stat.mtimeMs;
430
- if (probe && (!this.head || this.head.bytes < Math.min(fileHead_1.HEAD_BYTES, stat.size))) {
431
- this.head = (0, fileHead_1.fileHeadOf)(probe.prefix);
432
- }
405
+ this.continuity.record(stat, probe);
433
406
  }
434
407
  reset(reason, offset) {
435
408
  const previousOffset = this.committedOffset;
436
409
  this.position(offset);
437
- this.head = null;
410
+ this.continuity.discardHead();
438
411
  if (reason === 'replaced' && this.active) {
439
412
  // fs.watch follows the old inode; watch the file now at the path.
440
413
  this.fsWatcher?.close();
@@ -509,7 +482,7 @@ class JsonlTailReader {
509
482
  this.position(Math.max(0, offset));
510
483
  }
511
484
  checkpoint() {
512
- return checkpointOf(this.committedOffset, this.identity, this.head);
485
+ return checkpointOf(this.committedOffset, this.continuity.knownIdentity, this.continuity.knownHead);
513
486
  }
514
487
  resumeFrom(checkpoint) {
515
488
  this.generation += 1;
@@ -527,16 +500,12 @@ class JsonlTailReader {
527
500
  ? lastCompleteLineOffset(fd, stat.size)
528
501
  : 0;
529
502
  this.position(offset);
530
- this.identity = { dev: stat.dev, ino: stat.ino };
531
- this.lastMtimeMs = stat.mtimeMs;
532
- this.head = (0, fileHead_1.fileHeadOf)(prefix);
503
+ this.continuity.adoptPrefix(stat, prefix);
533
504
  return matches;
534
505
  }
535
506
  catch (error) {
536
507
  this.position(0);
537
- this.identity = null;
538
- this.head = null;
539
- this.lastMtimeMs = -1;
508
+ this.continuity.forget();
540
509
  this.options.onError?.(toError(error));
541
510
  return false;
542
511
  }
@@ -8,7 +8,11 @@ import type { SessionWatcher, SessionWatcherCallbacks } from './types';
8
8
  export interface JsonlSessionWatcherOptions {
9
9
  /**
10
10
  * Emit a `system` event (summary from the notification, `raw` the line) for
11
- * each Claude Code mid-turn task-notification attachment. Default `false`.
11
+ * each Claude Code mid-turn task-notification attachment, and for the other
12
+ * lines that settle background agents: the plain-text "background agents
13
+ * were stopped by the user" notice, a background agent's final
14
+ * `task_status` attachment, and an `agents_killed` system line. Default
15
+ * `false`.
12
16
  */
13
17
  includeTaskNotifications?: boolean;
14
18
  }
@@ -139,15 +139,25 @@ function normalizeClaudeCodeEvent(raw, deduper, includeTaskNotifications) {
139
139
  }
140
140
  else if (includeTaskNotifications) {
141
141
  // A task notification that landed mid-turn is only an attachment line.
142
- const notification = (0, subagentSpawn_1.parseTaskNotification)((0, subagentSpawn_1.taskNotificationPromptFromAttachment)(raw));
142
+ const prompt = (0, subagentSpawn_1.taskNotificationPromptFromAttachment)(raw);
143
+ const notification = (0, subagentSpawn_1.parseTaskNotification)(prompt);
144
+ const stopped = notification ? null : (0, subagentSpawn_1.parseAgentsStoppedNotice)(prompt);
145
+ const taskStatus = (0, subagentSpawn_1.taskStatusFromAttachment)(raw);
146
+ let summary = null;
143
147
  if (notification) {
144
- events.push({
145
- providerId: 'claude-code',
146
- type: 'system',
147
- timestamp: ts,
148
- summary: notification.summary ?? `Background task ${notification.status ?? 'finished'}`,
149
- raw,
150
- });
148
+ summary = notification.summary ?? `Background task ${notification.status ?? 'finished'}`;
149
+ }
150
+ else if (stopped) {
151
+ summary = (0, subagentSpawn_1.describeAgentsStopped)(stopped);
152
+ }
153
+ else if (taskStatus && (0, subagentSpawn_1.isFinalTaskStatus)(taskStatus.status)) {
154
+ summary = (0, subagentSpawn_1.describeTaskStatus)(taskStatus);
155
+ }
156
+ else if ((0, subagentSpawn_1.isAgentsKilledSystemEvent)(raw)) {
157
+ summary = (0, subagentSpawn_1.describeAgentsStopped)(null);
158
+ }
159
+ if (summary !== null) {
160
+ events.push({ providerId: 'claude-code', type: 'system', timestamp: ts, summary, raw });
151
161
  }
152
162
  }
153
163
  }
@@ -148,11 +148,12 @@ class ProviderReaderSessionWatcher {
148
148
  if (!this._isActive)
149
149
  return;
150
150
  try {
151
- if (this.reader.wasTruncated()) {
152
- this.reader.reset();
153
- this.callbacks.onReset?.();
154
- }
155
151
  const events = this.reader.readNew();
152
+ // The reader started over (truncated, rewritten, or replaced file):
153
+ // these events are the session from the beginning, so consumers must
154
+ // drop what they built before receiving them.
155
+ if (this.reader.wasTruncated())
156
+ this.callbacks.onReset?.();
156
157
  for (const event of events) {
157
158
  for (const followEvent of (0, eventBridge_1.toFollowEvents)(event, this.provider.id)) {
158
159
  this.callbacks.onEvent(followEvent);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sidekick-shared",
3
- "version": "0.29.1",
3
+ "version": "0.30.0",
4
4
  "description": "Shared data access layer for Sidekick — readers, types, providers, credentials, quota",
5
5
  "keywords": [
6
6
  "claude-code",