sidekick-shared 0.29.0 → 0.29.1

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 (84) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +56 -25
  3. package/dist/aggregation/EventAggregator.d.ts +19 -0
  4. package/dist/aggregation/EventAggregator.js +174 -35
  5. package/dist/aggregation/snapshot.d.ts +37 -8
  6. package/dist/aggregation/snapshot.js +31 -9
  7. package/dist/aggregation/types.d.ts +13 -0
  8. package/dist/browser.d.ts +5 -1
  9. package/dist/browser.js +9 -3
  10. package/dist/context/sessionContext.js +4 -0
  11. package/dist/esm/aggregation/EventAggregator.js +174 -35
  12. package/dist/esm/aggregation/snapshot.js +31 -9
  13. package/dist/esm/browser.js +1 -0
  14. package/dist/esm/context/sessionContext.js +4 -0
  15. package/dist/esm/formatters/toolSummary.js +4 -1
  16. package/dist/esm/index.js +3 -1
  17. package/dist/esm/modelCatalog.js +5 -4
  18. package/dist/esm/modelContext.js +4 -3
  19. package/dist/esm/modelInfo.js +4 -3
  20. package/dist/esm/node.js +1 -1
  21. package/dist/esm/observedContextWindows.js +2 -1
  22. package/dist/esm/ownKey.js +26 -0
  23. package/dist/esm/parsers/codexParser.js +180 -4
  24. package/dist/esm/parsers/openCodeParser.js +2 -1
  25. package/dist/esm/parsers/subagentScanner.js +33 -12
  26. package/dist/esm/parsers/subagentTraceParser.js +97 -37
  27. package/dist/esm/pricingCatalog.js +3 -2
  28. package/dist/esm/providers/claudeCode.js +10 -2
  29. package/dist/esm/providers/codex.js +82 -38
  30. package/dist/esm/schemas/sessionEvent.js +35 -14
  31. package/dist/esm/subagentSpawn.js +163 -0
  32. package/dist/esm/transcript.js +4 -0
  33. package/dist/esm/turns/assistantTurn.js +3 -2
  34. package/dist/esm/usage/claudeUsageDedupe.js +68 -0
  35. package/dist/esm/watchers/eventBridge.js +15 -0
  36. package/dist/esm/watchers/factory.js +5 -3
  37. package/dist/esm/watchers/jsonlTail.js +73 -8
  38. package/dist/esm/watchers/jsonlWatcher.js +51 -4
  39. package/dist/esm/watchers/providerReaderWatcher.js +13 -0
  40. package/dist/esm/zaiQuota.js +2 -1
  41. package/dist/formatters/toolSummary.js +4 -1
  42. package/dist/index.d.ts +8 -3
  43. package/dist/index.js +18 -9
  44. package/dist/modelCatalog.js +5 -4
  45. package/dist/modelContext.js +4 -3
  46. package/dist/modelInfo.js +4 -3
  47. package/dist/node.d.ts +1 -1
  48. package/dist/node.js +3 -1
  49. package/dist/observedContextWindows.js +2 -1
  50. package/dist/ownKey.d.ts +16 -0
  51. package/dist/ownKey.js +30 -0
  52. package/dist/parsers/codexParser.d.ts +63 -0
  53. package/dist/parsers/codexParser.js +181 -4
  54. package/dist/parsers/openCodeParser.js +2 -1
  55. package/dist/parsers/subagentScanner.d.ts +19 -6
  56. package/dist/parsers/subagentScanner.js +33 -12
  57. package/dist/parsers/subagentTraceParser.d.ts +25 -5
  58. package/dist/parsers/subagentTraceParser.js +97 -37
  59. package/dist/pricingCatalog.js +3 -2
  60. package/dist/providers/claudeCode.js +9 -1
  61. package/dist/providers/codex.js +82 -38
  62. package/dist/providers/types.d.ts +14 -0
  63. package/dist/schemas/index.d.ts +1 -0
  64. package/dist/schemas/sessionEvent.d.ts +27 -10
  65. package/dist/schemas/sessionEvent.js +35 -14
  66. package/dist/subagentSpawn.d.ts +80 -0
  67. package/dist/subagentSpawn.js +174 -0
  68. package/dist/transcript.js +4 -0
  69. package/dist/turns/assistantTurn.d.ts +1 -0
  70. package/dist/turns/assistantTurn.js +3 -2
  71. package/dist/usage/claudeUsageDedupe.d.ts +31 -0
  72. package/dist/usage/claudeUsageDedupe.js +68 -0
  73. package/dist/watchers/eventBridge.js +15 -0
  74. package/dist/watchers/factory.d.ts +6 -0
  75. package/dist/watchers/factory.js +5 -3
  76. package/dist/watchers/jsonlTail.d.ts +27 -4
  77. package/dist/watchers/jsonlTail.js +75 -8
  78. package/dist/watchers/jsonlWatcher.d.ts +18 -1
  79. package/dist/watchers/jsonlWatcher.js +50 -3
  80. package/dist/watchers/providerReaderWatcher.d.ts +7 -0
  81. package/dist/watchers/providerReaderWatcher.js +13 -0
  82. package/dist/watchers/types.d.ts +12 -0
  83. package/dist/zaiQuota.js +2 -1
  84. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,28 @@ All notable changes to sidekick-shared will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.29.1] - 2026-11-01
9
+
10
+ ### Fixed
11
+
12
+ - **Claude Code `Agent` spawns are tracked.** Claude Code renamed its subagent tool from `Task` to `Agent`. `EventAggregator`, the subagent trace parser and scanner, the `Agent` tool summary and `isAssistantTurnSubagentTool()` recognize both names (OpenCode's `task` still maps to `Task`).
13
+ - **Background agents complete when they finish, not at launch.** An `Async agent launched` result keeps the lifecycle running and records the agent id; a `<task-notification>` completes it with `outcome: 'completed' | 'failed' | 'killed' | 'stopped'`. An interim notification keeps or puts the agent back to running. A foreground spawn still running at a snapshot completes after `restore()`.
14
+ - **Unknown permission modes no longer drop events.** `extractSessionEvents` rejected every line whose `permissionMode` it didn't know, such as Claude Code's `auto`, losing prompts, tool results and notifications. The unknown mode is now dropped and the line kept; `PermissionMode` is unchanged.
15
+ - **Lookups keyed by ids that match `Object.prototype` members.** `getModelPricing`, `resolveModelPricing` (which returned no `provenance` for `constructor` and `__proto__`), `getModelContextWindowSize` / `resolveModelContextWindow`, `getModelInfo`, `normalizeToolName`, `formatToolSummary` and `parseZaiQuotaError` read only own keys, and catalog, observed-window and LiteLLM tables store a `__proto__` id as data.
16
+ - **Snapshot resume can verify the source file.** `isSnapshotValid(snapshot, size, sourcePath)` also checks the file's identity, the digest of its first 4 KiB and the newline before the offset, and rejects a snapshot without `sourceCheckpoint`; without `sourcePath` it behaves as before. Restoring `readerState` keeps the Claude usage deduper and the Codex parser state, so a response split across the snapshot point is counted once.
17
+
18
+ ### Added
19
+
20
+ - `isSubagentSpawnTool()`, `SUBAGENT_SPAWN_TOOL_NAMES`, `parseTaskNotification()`, `taskNotificationFromEvent()`, `isTaskNotificationSystemEvent()` and the `TaskNotification` / `SubagentOutcome` types (root and `browser`).
21
+ - `extractSessionEvents(raw, { includeTaskNotifications })` (`ExtractSessionEventsOptions`, also from `sidekick-shared/schemas`) and `CreateWatcherOptions.includeTaskNotifications`. When enabled, Claude Code's mid-turn `queued_command` task-notification attachments become one `system` event with `origin: { kind: 'task-notification' }`. The Claude Code provider's reader enables it; the default is off.
22
+ - `SubagentLifecycle.agentId` / `background` / `outcome`; `SubagentTrace.filePath` / `spawnToolUseId` / `parentAgentId` / `background` / `finished` / `stoppedByUser`. `readAgentMeta()` (type `AgentMeta`) also returns `toolUseId`, `parentAgentId`, `requestShape`, `finished` and `stoppedByUser`. `scanSubagentTraces` links traces exactly by meta `parentAgentId` and spawn id before falling back to heuristics.
23
+ - `captureJsonlCheckpoint(filePath, offset)` and `verifyJsonlCheckpoint(filePath, checkpoint)` (root and `node`); `SessionSnapshot.sourceCheckpoint` and `readerState`.
24
+ - Optional `getResumeState()` / `restoreResumeState(state)` on `SessionReader` and `SessionWatcher`, implemented by the Claude Code and Codex readers and the JSONL and reader-backed watchers. `ClaudeUsageDeduper.exportState()` / `importState()` (`ClaudeUsageDedupeState`) and `CodexRolloutParser.exportResumeState()` / `importResumeState()` (`CodexRolloutResumeState`). `CodexReader.seekTo()` defers its re-parse to the first read, which a successful `restoreResumeState()` makes unnecessary.
25
+
26
+ ### Changed
27
+
28
+ - `SubagentTrace.parentToolUseId` holds the spawning tool-use id from Claude Code's meta file when present, as its documentation says. Heuristic links keep storing the parent's agent id there and also set `parentAgentId`; top-level traces are those without a parent agent.
29
+
8
30
  ## [0.29.0] - 2026-11-01
9
31
 
10
32
  ### Fixed
package/README.md CHANGED
@@ -9,6 +9,12 @@ Types, parsers, providers, readers, formatters, aggregation, search, reporting,
9
9
 
10
10
  ## Recent additions
11
11
 
12
+ - **0.29.1: background agents, `Agent` spawns, and verified resume**
13
+ - **Subagents:** Claude Code's `Agent` tool (the new name of `Task`) is tracked wherever `Task` was; `isSubagentSpawnTool()` / `SUBAGENT_SPAWN_TOOL_NAMES` name both. A background agent stays running until its `<task-notification>`, and `SubagentLifecycle` records `agentId`, `background`, and `outcome` (`completed`, `failed`, `killed`, `stopped`). `parseTaskNotification()`, `taskNotificationFromEvent()`, and `isTaskNotificationSystemEvent()` read the notices.
14
+ - **Traces:** `SubagentTrace` gains `filePath`, `spawnToolUseId`, `parentAgentId`, `background`, `finished`, and `stoppedByUser` from Claude Code's meta file (`readAgentMeta()`). Traces are linked by that metadata before the heuristics run, and `parentToolUseId` now holds the spawning tool-use id when the meta file records one.
15
+ - **Events:** `extractSessionEvents(raw, { includeTaskNotifications: true })` and `createWatcher({ includeTaskNotifications: true })` turn mid-turn task-notification attachments into `system` events. A `permissionMode` this release doesn't know (Claude Code's `auto`) no longer drops the line.
16
+ - **Resume:** `captureJsonlCheckpoint()` / `verifyJsonlCheckpoint()`, `SessionSnapshot.sourceCheckpoint` / `readerState`, `isSnapshotValid(snapshot, size, sourcePath)`, and optional `getResumeState()` / `restoreResumeState()` on readers and watchers. A resumed session counts a split Claude response once and keeps its Codex parser context.
17
+ - **Lookups:** pricing, context-window, model-info, tool-name, and z.ai error tables read only their own keys, so an id such as `constructor` or `__proto__` is treated as unknown instead of matching an `Object.prototype` member.
12
18
  - **0.29.0: lean entries, usage attribution, and honest pricing**
13
19
  - **New subpaths:**
14
20
  - `sidekick-shared/pricing`: pure, 8 modules. Pricing, model identity, usage cost, cost provenance, formatting and token estimates.
@@ -127,21 +133,21 @@ returns the platform default that both overrides bypass.
127
133
  | **Writers** | Atomic persistence writers — `atomicWriteJson()` / `updateJsonStoreAtomic()` and their synchronous twins `atomicWriteJsonSync()` / `atomicWriteFileSync()` / `updateJsonStoreAtomicSync()` / `withFileLockSync()` (same cross-process lock file, progress-based timeouts, abandoned-lock reclaim), plus lock-coordinated capture helpers `addTask()`, `completeTask()`, `addDecision()`, `addNote()` that merge into the shared stores without clobbering concurrent writers |
128
134
  | **Providers** | Session provider abstraction with Claude Code, OpenCode, and Codex implementations; auto-detection via filesystem. As of 0.25.0 constructors perform no environment I/O — build them all with `createSessionProviders({ onDiagnostic })`, which reports unavailable providers as structured diagnostics instead of failing; `findSessionById()` resolves one session through provider-native indexes; stable provider-id const arrays (`SESSION_PROVIDER_IDS`, `ACCOUNT_PROVIDER_IDS`, `QUOTA_PROVIDER_IDS`, `MODEL_PROVIDER_IDS`) accompany the derived unions. As of 0.26.5 Claude Code and Codex enumerate through a per-instance `DirectoryListingCache` (one stat per unchanged directory) and implement `statWatchedSessionFile()` so a watch event resolves with one stat; `splitWatchedPath()` and the `ListSessionFilesOptions` / `WatchedSessionFile` / `SessionEnumerationCounters` types are exported alongside |
129
135
  | **Observed Sessions (V1)** | Versioned provider-neutral observation contracts — `createProviderSessionAdapterV1()` / `derivePendingUserRequestV1()` with `ObservedAgentSessionV1`, `ProviderCapabilitiesV1`, `PendingUserRequestV1`, `SessionEvidenceRefV1`, and provenance/confidence wrapper types (Zod schemas in `sidekick-shared/schemas`) |
130
- | **Parsers** | JSONL event parsing, OpenCode/Codex format normalization, subagent scanning, session path resolution, debug log parsing |
131
- | **Watchers** | Live session file watching with event bridging, plus `createJsonlTail()` for raw incremental JSONL consumers |
136
+ | **Parsers** | JSONL event parsing, OpenCode/Codex format normalization, subagent scanning (`scanSubagentDir()`, `scanSubagentTraces()`, `readAgentMeta()`), session path resolution, debug log parsing |
137
+ | **Watchers** | Live session file watching with event bridging (`createWatcher()`, with `includeTaskNotifications` for Claude Code mid-turn task notifications) and optional `getResumeState()` / `restoreResumeState()`, plus `createJsonlTail()` for raw incremental JSONL consumers and `captureJsonlCheckpoint()` / `verifyJsonlCheckpoint()` for readers that keep their own offset |
132
138
  | **Formatters** | Display helpers (`formatTokenCount()`, `formatDurationMs()`), the shared token vocabulary (`summarizeTokens()`, `formatTokenBreakdown()`, `TOKEN_*_LABEL`s) and session totals incl. subagents (`collectSessionTokenTotals()`), tool summary, noise classification, session dump (text/markdown/JSON), event highlighting |
133
139
  | **Search** | Cross-session full-text search, advanced filtering (substring, fuzzy, regex, date) |
134
- | **Aggregation** | Event aggregation, frequency tracking, activity heatmaps, pattern extraction |
140
+ | **Aggregation** | Event aggregation (including subagent lifecycles that follow background agents to their task notification), frequency tracking, activity heatmaps, pattern extraction, and session snapshots (`saveSnapshot()`, `loadSnapshot()`, `isSnapshotValid()`) |
135
141
  | **Analytics** | Session-quality scoring (beta) with factor contributions and weekly trends (`scoreSessionQuality()`, `calculateQualityTrend()`), code-impact metrics including cost per changed line and per-model churn/cost (`calculateCodeImpact()`), and reported-vs-heuristic compaction accounting (`calculateCompactionLedger()`, `formatCompactionLedger()`) |
136
142
  | **Error History** | Shared error taxonomy (`categorizeError()`, `extractErrorMessage()`) plus append-only per-project failure history (`appendErrorHistory()`, `readErrorHistory()`, `getTopFailingTools()`) behind per-tool/hour/model error rollups |
137
143
  | **Session Context** | Provider-neutral context evidence snapshots (`buildSessionContextSnapshot()`, `calculateSessionContextPressure()`, `createSessionContextProjector()`, `readSessionContextSnapshot()`): layered evidence sources, low/medium/high context pressure, and observed capabilities (tools, MCP servers, permission mode, rate limits) |
138
- | **Assistant Turns** | Browser-safe timeline/process/answer projection for provider-normalized assistant turns (`segmentAssistantTurn()`, `assistantTurnEventsFromSessionEvents()`), including interleaved reasoning, compact tool groups, and Claude `Task` subagent refs without prompt leakage |
144
+ | **Assistant Turns** | Browser-safe timeline/process/answer projection for provider-normalized assistant turns (`segmentAssistantTurn()`, `assistantTurnEventsFromSessionEvents()`), including interleaved reasoning, compact tool groups, and Claude Code `Agent` / `Task` subagent refs without prompt leakage |
139
145
  | **Report** | Self-contained HTML session report generation; `readSessionReportInputs()` returns metrics, transcript, and `subagents` for the session total |
140
146
  | **Credentials** | Claude subscription (Pro / Max / Ultra) OAuth credential reading (macOS Keychain with a `~/.claude/.credentials.json` fallback; the file on Linux and Windows). `readClaudeCredentialStore()` returns `ok` / `absent` / `corrupt` / `error`; `writeActiveCredentials()` requires a config directory and reads every write back (`ClaudeCredentialWriteError`). `decideLiveClaudeWrite()`, `claudeRefreshFingerprint()`, `claudeLoginOnly()`, and `mergeClaudeLoginIntoLive()` expose the live-write policy. As of 0.27.6, `inspectClaudeCredentialStores()` / `repairClaudeCredentialStores()` and `inspectCodexCredentialStores()` / `repairCodexCredentialStores()` find and repair unused, duplicate, and unreadable stores Sidekick provably owns (repairs are dry runs unless `dryRun: false`), with Claude ownership recorded by `readClaudeCredentialLedger()` |
141
147
  | **Quota** | Claude subscription quota fetching (5-hour and 7-day windows; `fiveHour.absent` on plans without a 5-hour limit such as Ultra; server meters in `limits`, `extraUsage`, and `planLabel`), Codex rate-limit extraction from event streams (plus Codex reset credits via `fetchCodexResetCreditsFromApi()`, attached to quota state by `fetchCodexQuotaFromApi()`), and authoritative z.ai Coding Plan quota fetching via `resolveZaiQuota()` / `fetchZaiQuotaFromApi()` (reads z.ai's `api/monitor/usage/quota/limit` endpoint with cached-snapshot fallback). The observed-traffic z.ai estimator was removed from the package root exports in 0.23.0 and every remaining export of it is `@deprecated` — `resolveZaiQuota()` is the single supported z.ai quota API |
142
148
  | **Provider Status** | `fetchProviderServiceStatus()` returns explicit observed/unavailable public status with components and unresolved incidents from status.claude.com and status.openai.com; `fetchProviderStatus()` / `fetchOpenAIStatus()` remain as lossy legacy projections (`toLegacyProviderStatus()`) |
143
149
  | **Doctor** | Typed cross-provider health diagnostics — `runDoctor()` checks project identity, sessions, accounts, providers, and dependencies into a `HealthReport`; `formatHealthReport()` renders it and `getSessionDiagnostics()` retains the legacy Claude Code path probe; `runDoctor({ provider })` diagnoses the selected session provider |
144
- | **Schemas** | Zod schemas for runtime validation of data crossing process/IPC boundaries — JSONL session events (`sessionEventSchema`, `messageUsageSchema`, `sessionMessageSchema`), assistant turns, observed-session V1 records, quota, account status, account management, quota history, and the public `state.json` contract (`sidekickStateFileSchema`, exported only from `sidekick-shared/schemas`) — plus `extractSessionEvents()` to unwrap `progress`-wrapped events. Account and quota APIs guarantee results valid against the same-release schemas. Also published fs-free via the [`sidekick-shared/schemas`](#supported-import-paths) subpath |
150
+ | **Schemas** | Zod schemas for runtime validation of data crossing process/IPC boundaries — JSONL session events (`sessionEventSchema`, `messageUsageSchema`, `sessionMessageSchema`), assistant turns, observed-session V1 records, quota, account status, account management, quota history, and the public `state.json` contract (`sidekickStateFileSchema`, exported only from `sidekick-shared/schemas`) — plus `extractSessionEvents()` to unwrap `progress`-wrapped events (and, with `{ includeTaskNotifications: true }`, mid-turn task-notification attachments). Account and quota APIs guarantee results valid against the same-release schemas. Also published fs-free via the [`sidekick-shared/schemas`](#supported-import-paths) subpath |
145
151
  | **Extractors** | Pure functions for single-event processing: `extractTokenUsage()`, `extractToolCall()` (top-level `tool_use`), `extractToolCalls()` (assistant content blocks), plus Node-only session asset extraction (`gatherAssetsForCwd()`, `readClaudeAssets()`, `readCodexAssets()`) for URLs, file paths, commands, and plans |
146
152
  | **Model Info & Pricing** | Model family parsing (Anthropic / OpenAI / Google, including legacy `claude-3-opus-…` and `claude-3-5-sonnet-…` IDs), context-window lookup (including Fable 5.1 / Opus 5.5 / Opus 5 / Opus 4.8 / Opus 4.7 1M and GPT-5.x / GPT-6 variants), pricing tables with optional LiteLLM hydration, normalized non-double-counting cost (`calculateNormalizedUsageCost()`, fed by `normalizeProviderUsage()` / `extractNormalizedUsage()`), legacy cost helpers retained but deprecated (`calculateCost()`, `calculateCostWithProvenance()`), `mergeCostSources()`, display helpers (`shortModelName()`, `getModelDisplayInfo()`, `compareModelIds()`, `sortModelIds()`, `formatCost()`), and cross-realm catalog transfer — `exportResolvedModelCatalog()` / `importResolvedModelCatalog()` move resolved context windows and pricing (with provenance, prefix inheritance explicit) between Node and browser realms, and `registerModelAlias()` resolves short or host-specific names to canonical ids |
147
153
  | **Quota Polling** | `QuotaPoller` class with exponential backoff, active/idle intervals, and cached fallback. Stays dormant while no account exists and wakes through the account-change signal |
@@ -158,22 +164,22 @@ returns the platform default that both overrides bypass.
158
164
 
159
165
  `sidekick-shared` ships three main entry points (the root, `/browser`, `/node`) plus narrower single-purpose subpaths. Pick the one that matches your runtime. As of 0.25.0 every documented subpath declares explicit `types` / `import` / `require` / `default` export conditions and resolves under Vite and TypeScript's legacy `moduleResolution: node` without consumer-side aliases.
160
166
 
161
- | Path | Runtime | What it exposes |
162
- | ------------------------------------ | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
163
- | `sidekick-shared` | Node (CLI, extension host) | Full public API (readers, providers, parsers, pricing, …). |
164
- | `sidekick-shared/browser` | **Browser / webview** | Pure helpers: usage normalization/pricing, token estimation, transcript and assistant-turn projection, model/context math, `parseMcpToolName()`, the `CodexRolloutParser` with the Codex rollout and session-event types, human-prompt detection (`isHumanPrompt()` / `humanPromptText()`), `renderHandoffUrlTemplate()`, and the quota window helpers (`quotaWindowApplies()`, `quotaWindowHasData()`, `quotaWindowLabel()`, `quotaWindowShortLabel()`, `extraQuotaRows()`, `formatExtraUsage()`, `claudePlanLabel()`). |
165
- | `sidekick-shared/node` | Node only | LiteLLM pricing and context-window hydration, public provider service status (`fetchProviderServiceStatus()`), observed context-window persistence, recent-session/transcript reads, human prompt history (`collectPromptHistory()`, and whole sessions with `collectSessionPromptHistory()`), tool-call origins (`findToolCallOrigins()`), usage events from session logs (`collectUsageEvents()` with `createSessionProviders()` and `SYNTHETIC_MODEL`), session previews (`listSessionPreviews()` / `readSessionPreview()` plus the bounded-concurrency `listSessionPreviewsAsync()` / `readSessionPreviewAsync()`), Codex history/rollout lookup (`readCodexHistory()`, `findCodexRolloutFile()`), resilient observed-session collection (`fs` + providers), the provider homes the CLIs read (`getLiveClaudeHome()`, `getClaudeProjectsDir()`, `getSystemCodexHome()`, `getCodexMonitoringHomes()`, `getCodexExecutionEnv()`), Codex `auth.json` and credential-store helpers (`codexRefreshFingerprint()`, `compareCodexAuthFreshness()`, `getCodexCredentialStoreMode()`, `forceFileCredentialStore()`), the atomic JSON writers, Claude session-directory encoding (`encodeClaudeWorkspacePath()`, `getClaudeSessionDirectory()`), and the config root (`getConfigDir()`, `CONFIG_DIR_ENV_VAR`). |
166
- | `sidekick-shared/phrases` | Any runtime | Phrase arrays + `getRandomPhrase()`. |
167
- | `sidekick-shared/modelContext` | Any runtime | Direct access to the context-window module. |
168
- | `sidekick-shared/modelInfo` | Any runtime | Direct access to model parsing and cost math. |
169
- | `sidekick-shared/formatting` | Any runtime | Direct access to pure cost, token, and duration display helpers (`formatCost()`, `formatTokenCount()`, `formatDurationMs()`). |
170
- | `sidekick-shared/schemas` | Any runtime | Pure Zod boundary schemas (session events, assistant turns, observed-session V1, quota, account status, account management, quota history, the `state.json` contract) — fs-free, no Node builtins. |
171
- | `sidekick-shared/statusline` | Node only | Cache-only statusline surface: account status + quota snapshot readers and writers, `formatStatusline()`, burn-rate estimates, quota freshness and window helpers (`quotaWindowApplies()`, `quotaWindowHasData()`, `quotaWindowShortLabel()`, `quotaHistoryWindow()`), `parseClaudeStatuslinePayload()` / `quotaFromStatuslinePayload()`, and `readStateFile()` / `writeStateFile()`. |
172
- | `sidekick-shared/testing` | Node test runners | `installCredentialGuard()` keeps a test process away from the real `security` / `claude` / `codex` binaries and the real Claude, Codex, and Sidekick homes, and the Claude Code / Codex session-log builders (`createSessionLogFixtures()`, `writeJsonl()`, …). Framework-free. |
173
- | `sidekick-shared/pricing` | Any runtime | Pure, 8 modules, no Zod: model pricing and identity (`canonicalModelId()`, `parseModelId()`, `compareModelIds()`, `resolveModelPricing()`, `registerModelPricing()`, `getPricingCatalogInfo()`), usage normalization and cost (`normalizeProviderUsage()`, `calculateNormalizedUsageCost()`), cost provenance (`costSourceFromPricingProvenance()`, `mergeCostComponents()`, `mergeCostSources()`), `formatCost()` / `formatTokenCount()` / `formatDurationMs()`, and `estimateTextTokens()`. |
174
- | `sidekick-shared/codex` | Node only | 13 modules, no Zod: Codex homes and the child-process env (`getSystemCodexHome()`, `resolveSidekickCodexHome()`, `getCodexExecutionEnv()`), `auth.json` helpers (`parseCodexAuthJson()`, `codexRefreshFingerprint()`, `compareCodexAuthFreshness()`), `getCodexCredentialStoreMode()` / `forceFileCredentialStore()`, `findCodexRolloutFile()`, `codexRolloutOrigin()`, `parseCodexLoginStatus()` / `parseClaudeAuthStatus()`, and `atomicWriteJson()`. |
175
- | `sidekick-shared/providerFailure` | Any runtime | Pure, 1 module: `diagnoseProviderFailure()`, `decodeCodexErrorInfo()`, and the identifier lists. |
176
- | `sidekick-shared/statusline/payload` | Any runtime | Pure, 2 modules: `parseClaudeStatuslinePayload()` / `quotaFromStatuslinePayload()` without the account, credential, or snapshot code the full `statusline` entry loads. |
167
+ | Path | Runtime | What it exposes |
168
+ | ------------------------------------ | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
169
+ | `sidekick-shared` | Node (CLI, extension host) | Full public API (readers, providers, parsers, pricing, …). |
170
+ | `sidekick-shared/browser` | **Browser / webview** | Pure helpers: usage normalization/pricing, token estimation, transcript and assistant-turn projection, model/context math, `parseMcpToolName()`, the `CodexRolloutParser` with the Codex rollout and session-event types, human-prompt detection (`isHumanPrompt()` / `humanPromptText()`), `renderHandoffUrlTemplate()`, the subagent-spawn and task-notification helpers (`isSubagentSpawnTool()`, `parseTaskNotification()`, `taskNotificationFromEvent()`, `isTaskNotificationSystemEvent()`), `ClaudeUsageDeduper`, and the quota window helpers (`quotaWindowApplies()`, `quotaWindowHasData()`, `quotaWindowLabel()`, `quotaWindowShortLabel()`, `extraQuotaRows()`, `formatExtraUsage()`, `claudePlanLabel()`). |
171
+ | `sidekick-shared/node` | Node only | LiteLLM pricing and context-window hydration, public provider service status (`fetchProviderServiceStatus()`), observed context-window persistence, recent-session/transcript reads, human prompt history (`collectPromptHistory()`, and whole sessions with `collectSessionPromptHistory()`), tool-call origins (`findToolCallOrigins()`), usage events from session logs (`collectUsageEvents()` with `createSessionProviders()` and `SYNTHETIC_MODEL`), session previews (`listSessionPreviews()` / `readSessionPreview()` plus the bounded-concurrency `listSessionPreviewsAsync()` / `readSessionPreviewAsync()`), Codex history/rollout lookup (`readCodexHistory()`, `findCodexRolloutFile()`), resilient observed-session collection (`fs` + providers), the provider homes the CLIs read (`getLiveClaudeHome()`, `getClaudeProjectsDir()`, `getSystemCodexHome()`, `getCodexMonitoringHomes()`, `getCodexExecutionEnv()`), Codex `auth.json` and credential-store helpers (`codexRefreshFingerprint()`, `compareCodexAuthFreshness()`, `getCodexCredentialStoreMode()`, `forceFileCredentialStore()`), the bounded JSONL tail and its checkpoints (`createJsonlTail()`, `captureJsonlCheckpoint()`, `verifyJsonlCheckpoint()`), the atomic JSON writers, Claude session-directory encoding (`encodeClaudeWorkspacePath()`, `getClaudeSessionDirectory()`), and the config root (`getConfigDir()`, `CONFIG_DIR_ENV_VAR`). |
172
+ | `sidekick-shared/phrases` | Any runtime | Phrase arrays + `getRandomPhrase()`. |
173
+ | `sidekick-shared/modelContext` | Any runtime | Direct access to the context-window module. |
174
+ | `sidekick-shared/modelInfo` | Any runtime | Direct access to model parsing and cost math. |
175
+ | `sidekick-shared/formatting` | Any runtime | Direct access to pure cost, token, and duration display helpers (`formatCost()`, `formatTokenCount()`, `formatDurationMs()`). |
176
+ | `sidekick-shared/schemas` | Any runtime | Pure Zod boundary schemas (session events, assistant turns, observed-session V1, quota, account status, account management, quota history, the `state.json` contract) — fs-free, no Node builtins. |
177
+ | `sidekick-shared/statusline` | Node only | Cache-only statusline surface: account status + quota snapshot readers and writers, `formatStatusline()`, burn-rate estimates, quota freshness and window helpers (`quotaWindowApplies()`, `quotaWindowHasData()`, `quotaWindowShortLabel()`, `quotaHistoryWindow()`), `parseClaudeStatuslinePayload()` / `quotaFromStatuslinePayload()`, and `readStateFile()` / `writeStateFile()`. |
178
+ | `sidekick-shared/testing` | Node test runners | `installCredentialGuard()` keeps a test process away from the real `security` / `claude` / `codex` binaries and the real Claude, Codex, and Sidekick homes, and the Claude Code / Codex session-log builders (`createSessionLogFixtures()`, `writeJsonl()`, …). Framework-free. |
179
+ | `sidekick-shared/pricing` | Any runtime | Pure, 9 modules, no Zod: model pricing and identity (`canonicalModelId()`, `parseModelId()`, `compareModelIds()`, `resolveModelPricing()`, `registerModelPricing()`, `getPricingCatalogInfo()`), usage normalization and cost (`normalizeProviderUsage()`, `calculateNormalizedUsageCost()`), cost provenance (`costSourceFromPricingProvenance()`, `mergeCostComponents()`, `mergeCostSources()`), `formatCost()` / `formatTokenCount()` / `formatDurationMs()`, and `estimateTextTokens()`. |
180
+ | `sidekick-shared/codex` | Node only | 13 modules, no Zod: Codex homes and the child-process env (`getSystemCodexHome()`, `resolveSidekickCodexHome()`, `getCodexExecutionEnv()`), `auth.json` helpers (`parseCodexAuthJson()`, `codexRefreshFingerprint()`, `compareCodexAuthFreshness()`), `getCodexCredentialStoreMode()` / `forceFileCredentialStore()`, `findCodexRolloutFile()`, `codexRolloutOrigin()`, `parseCodexLoginStatus()` / `parseClaudeAuthStatus()`, and `atomicWriteJson()`. |
181
+ | `sidekick-shared/providerFailure` | Any runtime | Pure, 1 module: `diagnoseProviderFailure()`, `decodeCodexErrorInfo()`, and the identifier lists. |
182
+ | `sidekick-shared/statusline/payload` | Any runtime | Pure, 2 modules: `parseClaudeStatuslinePayload()` / `quotaFromStatuslinePayload()` without the account, credential, or snapshot code the full `statusline` entry loads. |
177
183
 
178
184
  Since 0.28.0 every subpath also has a `module` condition pointing at an ES-module build in `dist/esm/`. Bundlers that read it (esbuild, Vite, Rollup, webpack) tree-shake unused exports, so importing one helper from `sidekick-shared/node` bundles a few hundred bytes instead of the whole entry. Node itself never reads `module` and keeps loading the CommonJS build for both `require` and `import`, so a process never holds two copies of module state.
179
185
 
@@ -999,9 +1005,28 @@ const projection = segmentAssistantTurn(assistantTurnEventsFromSessionEvents(ses
999
1005
  console.log(projection.answer);
1000
1006
  console.log(projection.timeline);
1001
1007
  console.log(projection.process.steps);
1002
- console.log(projection.subagents); // Claude Task spawns, prompt text omitted
1008
+ console.log(projection.subagents); // Claude Code Agent / Task spawns, prompt text omitted
1003
1009
  ```
1004
1010
 
1011
+ ### Follow subagents and background agents
1012
+
1013
+ Claude Code spawns subagents with its `Agent` tool (`Task` is the earlier name, and OpenCode's `task` normalizes to it). `isSubagentSpawnTool(name)` and `SUBAGENT_SPAWN_TOOL_NAMES` name both, and `EventAggregator`, the trace scanner, `formatToolSummary()`, and `isAssistantTurnSubagentTool()` use them.
1014
+
1015
+ ```typescript
1016
+ import { scanSubagentTraces } from 'sidekick-shared';
1017
+
1018
+ for (const agent of aggregator.getMetrics().subagents) {
1019
+ // { id, description, subagentType, spawnTime, status, completionTime?, durationMs?, agentId?, background?, outcome? }
1020
+ }
1021
+ const traces = scanSubagentTraces(sessionDir, sessionId); // top-level traces; children nested
1022
+ // { agentId, agentType?, description?, filePath?, spawnToolUseId?, parentToolUseId?, parentAgentId?,
1023
+ // background?, finished?, stoppedByUser?, events, children, stats }
1024
+ ```
1025
+
1026
+ - **Background agents.** Since 0.29.1 a spawn whose result is `Async agent launched successfully.` stays `running` with `background: true` and the reported `agentId`. Its `<task-notification>` completes it with an `outcome` of `completed`, `failed`, `killed`, or `stopped`. A foreground spawn ends on its result, with `failed` when that result is an error. An interim notification, sent when the agent is waiting on background work of its own, keeps it `running` or puts it back.
1027
+ - **Task notifications.** Most arrive as their own user line. One that lands mid-turn is recorded only as a `queued_command` attachment. `extractSessionEvents(raw, { includeTaskNotifications: true })` and `createWatcher({ …, includeTaskNotifications: true })` turn that attachment into a `system` event with `origin: { kind: 'task-notification' }`. The option is off by default, and the Claude Code provider's reader always turns it on. Transcripts, context snapshots, and the aggregator's counters skip that event. `parseTaskNotification(text)` returns `{ taskId, toolUseId?, status?, summary?, interim }`, `taskNotificationFromEvent(event)` finds a notification in a user or system event, and `isTaskNotificationSystemEvent(event)` recognizes the synthetic event. These helpers and the `TaskNotification` / `SubagentOutcome` types are on the root and `/browser`.
1028
+ - **Traces.** `readAgentMeta()` reads Claude Code's `agent-<id>.meta.json` and returns an `AgentMeta` with `agentType`, `description`, `toolUseId`, `parentAgentId`, `requestShape`, `finished`, and `stoppedByUser`. `scanSubagentTraces()` places a trace under the agent its meta file names, then under the trace that holds its spawn call, and falls back to heuristics only after that. `parentToolUseId` is the spawning tool-use id when the meta file records one. A heuristic link stores the parent's agent id there, as before, and also sets `parentAgentId`.
1029
+
1005
1030
  ### Format shared dashboard values
1006
1031
 
1007
1032
  ```typescript
@@ -1024,7 +1049,7 @@ const parser = new JsonlParser(
1024
1049
  parser.processChunk(rawData);
1025
1050
  ```
1026
1051
 
1027
- The boundary schemas — `sessionEventSchema` plus the quota, account-status, account-management, and quota-history schemas — are also importable fs-free from `sidekick-shared/schemas`, which keeps Zod out of bundles that only need the pure math/formatting helpers. Account and quota APIs are guaranteed to return values valid against the schemas exported in the same release, so consumers do not need to parse those library results again. Additive account or quota fields are added to the matching exported schema in the same release. `extractSessionEvents()` from the same subpath unwraps Claude Code `progress`-wrapped events into canonical `SessionEvent[]`.
1052
+ The boundary schemas — `sessionEventSchema` plus the quota, account-status, account-management, and quota-history schemas — are also importable fs-free from `sidekick-shared/schemas`, which keeps Zod out of bundles that only need the pure math/formatting helpers. Account and quota APIs are guaranteed to return values valid against the schemas exported in the same release, so consumers do not need to parse those library results again. Additive account or quota fields are added to the matching exported schema in the same release. `extractSessionEvents()` from the same subpath unwraps Claude Code `progress`-wrapped events into canonical `SessionEvent[]`; its second argument is still the starting depth or, since 0.29.1, `ExtractSessionEventsOptions` (`{ includeTaskNotifications }`). Since 0.29.1 `sessionEventSchema` drops a `permissionMode` it does not know (Claude Code's `auto`) instead of rejecting the line; `permissionModeSchema` and `PermissionMode` are unchanged.
1028
1053
 
1029
1054
  ### Tail raw JSONL events incrementally
1030
1055
 
@@ -1045,7 +1070,7 @@ const tail = createJsonlTail({
1045
1070
  tail.start();
1046
1071
  ```
1047
1072
 
1048
- A poll reads from the last position to the end of the file in chunks of `maxReadBytes` (default 1 MiB), so a cold start on a large file never allocates more than one chunk plus one line. `onEvent` receives each line's `{ offset, length }` as its second argument. A line longer than `maxLineBytes` (default 16 MiB) is skipped as it streams past, even while it is still being written, and reported to `onError`. The tail also checks that it is still reading the same content. When the path now names another file (`replaced`), the file shrank (`truncated`), or its first 4 KiB or the byte before the resume point changed (`rewritten`), it starts over (from offset 0 by default; see `onRewrite`) and calls `onReset({ reason, previousOffset })` before the events from the start arrive. `createJsonlTail()` is also exported from `sidekick-shared/node`.
1073
+ A poll reads from the last position to the end of the file in chunks of `maxReadBytes` (default 1 MiB), so a cold start on a large file never allocates more than one chunk plus one line. `onEvent` receives each line's `{ offset, length }` as its second argument. A line longer than `maxLineBytes` (default 16 MiB) is skipped as it streams past, even while it is still being written, and reported to `onError`. The tail also checks that it is still reading the same content. When the path now names another file (`replaced`), the file shrank (`truncated`), or its first 4 KiB or the byte before the resume point changed (`rewritten`), it starts over (from offset 0 by default; see `onRewrite`) and calls `onReset({ reason, previousOffset })` before the events from the start arrive. `createJsonlTail()` is also exported from the package root.
1049
1074
 
1050
1075
  Since 0.29.0:
1051
1076
 
@@ -1054,7 +1079,13 @@ Since 0.29.0:
1054
1079
  - `checkpoint()` returns a JSON-serializable `{ version: 1, offset, identity, prefixDigest }`. A new tail's `resumeFrom(checkpoint)` continues from it after a restart, and returns false (applying the rewrite policy) when the file is no longer the one checkpointed.
1055
1080
  - `read()` is an async pull that returns the new complete lines.
1056
1081
 
1057
- `SessionWatcherCallbacks.onBatchComplete` is also available through `createWatcher()`. Save aggregation state only after a complete event batch and only when the state includes the full history (a completed replay or a compatible restored checkpoint). Live-only state is incomplete and must not replace a full-history checkpoint. JSONL positions end at complete lines; Codex readers reconstruct parser context when seeking. OpenCode's SQLite timestamp cursor does not persist row-version deduplication, so the dashboards replay OpenCode history instead of restoring a snapshot.
1082
+ #### Resume readers and snapshots
1083
+
1084
+ Since 0.29.1, readers that keep their own byte offset can use the same check without a tail: `captureJsonlCheckpoint(path, offset)` returns a checkpoint for a resume-safe offset (or `null`), and `verifyJsonlCheckpoint(path, checkpoint)` reports whether it still matches the file. Store one as `SessionSnapshot.sourceCheckpoint` when you save a snapshot (`saveSnapshot()` writes what it is given); `isSnapshotValid(snapshot, size, sourcePath)` then rejects a snapshot whose file was replaced or rewritten, or that has no checkpoint for its `readerPosition`; without `sourcePath` it keeps the size-only check.
1085
+
1086
+ Also since 0.29.1, a position alone does not resume every reader correctly: Claude Code writes one response as several lines that repeat its usage, so a reader resumed between them would count it again. `SessionReader` and `SessionWatcher` gain optional `getResumeState()` / `restoreResumeState(state)`: persist the state with the position (snapshots carry it as `readerState`), call `restoreResumeState()` right after `seekTo()` (for a watcher, before `start()`), and replay from the start when it returns false. The Claude Code reader and watcher carry their `ClaudeUsageDeduper` state (`exportState()` / `importState()`, a `ClaudeUsageDedupeState`); the Codex reader, and the reader-backed watcher `createWatcher()` builds for Codex, carry `CodexRolloutParser.exportResumeState()` / `importResumeState()` (a `CodexRolloutResumeState`). The Codex reader's `seekTo()` now waits until the first read to re-parse the rollout prefix, and a successful `restoreResumeState()` skips that re-parse.
1087
+
1088
+ `SessionWatcherCallbacks.onBatchComplete` is also available through `createWatcher()`. Save aggregation state only after a complete event batch and only when the state includes the full history (a completed replay or a compatible restored checkpoint). Live-only state is incomplete and must not replace a full-history checkpoint. JSONL positions end at complete lines; a Codex reader rebuilds parser context from the rollout prefix on its first read after seeking, unless `restoreResumeState()` supplied it. OpenCode's SQLite timestamp cursor does not persist row-version deduplication, so the dashboards replay OpenCode history instead of restoring a snapshot.
1058
1089
 
1059
1090
  ### Search a workspace and diagnose its provider
1060
1091
 
@@ -226,7 +226,26 @@ export declare class EventAggregator {
226
226
  private isGoalGateTask;
227
227
  private extractSubagentFromEvent;
228
228
  private extractSubagentFromFollowEvent;
229
+ private trackSubagentSpawn;
230
+ /**
231
+ * The spawning tool call's result. A foreground agent ends here; a
232
+ * background launch only reports the agent id, and the agent keeps running
233
+ * until its task notification arrives.
234
+ */
235
+ private resolveSubagentResult;
236
+ private markBackgroundLaunch;
229
237
  private completeSubagent;
238
+ private finishSubagent;
239
+ /**
240
+ * Applies a background task's notification to the subagent it reports on,
241
+ * matched by the spawning tool_use id (or by agent id when the notification
242
+ * has none). The latest notification wins: an interim one ("waiting on its
243
+ * own background work") keeps or puts the agent back to running, a final one
244
+ * completes it. Notifications for background shell tasks match nothing.
245
+ */
246
+ private applyTaskNotification;
247
+ private findNotifiedSubagent;
248
+ private addTaskNotificationTimeline;
230
249
  private extractPlanFromSessionEvent;
231
250
  private processPlanFollowEvent;
232
251
  private convertExtractedPlanToPlanState;