sidekick-shared 0.28.3 → 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.
- package/CHANGELOG.md +86 -0
- package/README.md +188 -27
- package/dist/aggregation/EventAggregator.d.ts +19 -0
- package/dist/aggregation/EventAggregator.js +174 -35
- package/dist/aggregation/snapshot.d.ts +37 -8
- package/dist/aggregation/snapshot.js +31 -9
- package/dist/aggregation/types.d.ts +13 -0
- package/dist/authStatus.d.ts +54 -0
- package/dist/authStatus.js +91 -0
- package/dist/browser.d.ts +16 -6
- package/dist/browser.js +24 -2
- package/dist/codex.d.ts +23 -0
- package/dist/codex.js +44 -0
- package/dist/codexProfiles.d.ts +1 -0
- package/dist/codexProfiles.js +8 -11
- package/dist/context/sessionContext.js +4 -0
- package/dist/esm/aggregation/EventAggregator.js +174 -35
- package/dist/esm/aggregation/snapshot.js +31 -9
- package/dist/esm/authStatus.js +87 -0
- package/dist/esm/browser.js +5 -2
- package/dist/esm/codex.js +18 -0
- package/dist/esm/codexProfiles.js +8 -11
- package/dist/esm/context/sessionContext.js +4 -0
- package/dist/esm/formatters/toolSummary.js +4 -1
- package/dist/esm/formatting.js +10 -1
- package/dist/esm/index.js +11 -6
- package/dist/esm/modelCatalog.js +7 -5
- package/dist/esm/modelContext.js +4 -3
- package/dist/esm/modelInfo.js +425 -47
- package/dist/esm/node.js +9 -3
- package/dist/esm/observedContextWindows.js +2 -1
- package/dist/esm/ownKey.js +26 -0
- package/dist/esm/parsers/codexParser.js +351 -54
- package/dist/esm/parsers/codexRolloutOrigin.js +107 -0
- package/dist/esm/parsers/openCodeParser.js +85 -22
- package/dist/esm/parsers/subagentScanner.js +33 -12
- package/dist/esm/parsers/subagentTraceParser.js +97 -37
- package/dist/esm/pricing.js +18 -0
- package/dist/esm/pricingCatalog.js +17 -9
- package/dist/esm/promptHistory.js +66 -4
- package/dist/esm/providerFailure.js +124 -3
- package/dist/esm/providers/claudeCode.js +10 -2
- package/dist/esm/providers/codex.js +84 -62
- package/dist/esm/providers/codexDatabase.js +3 -19
- package/dist/esm/providers/codexRollouts.js +24 -1
- package/dist/esm/providers/openCode.js +9 -17
- package/dist/esm/report/htmlReportGenerator.js +42 -5
- package/dist/esm/report/transcriptParser.js +13 -11
- package/dist/esm/schemas/providerFailure.js +3 -1
- package/dist/esm/schemas/sessionEvent.js +35 -14
- package/dist/esm/schemas/transcript.js +4 -0
- package/dist/esm/sessionPromptHistory.js +2 -2
- package/dist/esm/statusline/payload.js +9 -0
- package/dist/esm/subagentSpawn.js +163 -0
- package/dist/esm/testing/credentialGuard.js +87 -5
- package/dist/esm/toolCallOrigins.js +310 -0
- package/dist/esm/transcript.js +46 -3
- package/dist/esm/turns/assistantTurn.js +3 -2
- package/dist/esm/usage/claudeUsageDedupe.js +68 -0
- package/dist/esm/usage/usageEvents.js +146 -17
- package/dist/esm/usage/usageReports.js +39 -6
- package/dist/esm/watchers/eventBridge.js +15 -0
- package/dist/esm/watchers/factory.js +5 -3
- package/dist/esm/watchers/jsonlTail.js +442 -58
- package/dist/esm/watchers/jsonlWatcher.js +51 -4
- package/dist/esm/watchers/providerReaderWatcher.js +13 -0
- package/dist/esm/zaiQuota.js +2 -1
- package/dist/formatters/toolSummary.js +4 -1
- package/dist/formatting.d.ts +26 -1
- package/dist/formatting.js +11 -1
- package/dist/index.d.ts +22 -11
- package/dist/index.js +44 -12
- package/dist/modelCatalog.js +7 -5
- package/dist/modelContext.js +4 -3
- package/dist/modelInfo.d.ts +185 -16
- package/dist/modelInfo.js +434 -49
- package/dist/node.d.ts +16 -5
- package/dist/node.js +33 -2
- package/dist/observedContextWindows.js +2 -1
- package/dist/ownKey.d.ts +16 -0
- package/dist/ownKey.js +30 -0
- package/dist/parsers/codexParser.d.ts +118 -0
- package/dist/parsers/codexParser.js +352 -54
- package/dist/parsers/codexRolloutOrigin.d.ts +49 -0
- package/dist/parsers/codexRolloutOrigin.js +111 -0
- package/dist/parsers/openCodeParser.d.ts +38 -0
- package/dist/parsers/openCodeParser.js +88 -22
- package/dist/parsers/subagentScanner.d.ts +19 -6
- package/dist/parsers/subagentScanner.js +33 -12
- package/dist/parsers/subagentTraceParser.d.ts +25 -5
- package/dist/parsers/subagentTraceParser.js +97 -37
- package/dist/pricing.d.ts +22 -0
- package/dist/pricing.js +50 -0
- package/dist/pricingCatalog.d.ts +10 -0
- package/dist/pricingCatalog.js +16 -8
- package/dist/promptHistory.d.ts +65 -4
- package/dist/promptHistory.js +68 -5
- package/dist/providerFailure.d.ts +44 -0
- package/dist/providerFailure.js +126 -4
- package/dist/providers/claudeCode.js +9 -1
- package/dist/providers/codex.d.ts +3 -22
- package/dist/providers/codex.js +85 -64
- package/dist/providers/codexDatabase.d.ts +3 -2
- package/dist/providers/codexDatabase.js +4 -22
- package/dist/providers/codexRollouts.d.ts +22 -0
- package/dist/providers/codexRollouts.js +24 -0
- package/dist/providers/openCode.js +8 -16
- package/dist/providers/types.d.ts +14 -0
- package/dist/report/htmlReportGenerator.js +42 -5
- package/dist/report/transcriptParser.d.ts +10 -1
- package/dist/report/transcriptParser.js +13 -11
- package/dist/schemas/index.d.ts +1 -0
- package/dist/schemas/providerFailure.d.ts +39 -0
- package/dist/schemas/providerFailure.js +2 -0
- package/dist/schemas/sessionEvent.d.ts +27 -10
- package/dist/schemas/sessionEvent.js +35 -14
- package/dist/schemas/transcript.d.ts +2 -0
- package/dist/schemas/transcript.js +4 -0
- package/dist/sessionPromptHistory.d.ts +7 -1
- package/dist/sessionPromptHistory.js +1 -1
- package/dist/statusline/payload.d.ts +10 -0
- package/dist/statusline/payload.js +14 -0
- package/dist/subagentSpawn.d.ts +80 -0
- package/dist/subagentSpawn.js +174 -0
- package/dist/testing/credentialGuard.d.ts +14 -0
- package/dist/testing/credentialGuard.js +87 -5
- package/dist/toolCallOrigins.d.ts +70 -0
- package/dist/toolCallOrigins.js +346 -0
- package/dist/transcript.d.ts +12 -0
- package/dist/transcript.js +46 -3
- package/dist/turns/assistantTurn.d.ts +1 -0
- package/dist/turns/assistantTurn.js +3 -2
- package/dist/types/opencode.d.ts +2 -0
- package/dist/usage/claudeUsageDedupe.d.ts +31 -0
- package/dist/usage/claudeUsageDedupe.js +68 -0
- package/dist/usage/usageEvents.d.ts +54 -6
- package/dist/usage/usageEvents.js +146 -17
- package/dist/usage/usageReports.d.ts +35 -4
- package/dist/usage/usageReports.js +39 -6
- package/dist/watchers/eventBridge.js +15 -0
- package/dist/watchers/factory.d.ts +6 -0
- package/dist/watchers/factory.js +5 -3
- package/dist/watchers/jsonlTail.d.ts +124 -4
- package/dist/watchers/jsonlTail.js +446 -59
- package/dist/watchers/jsonlWatcher.d.ts +18 -1
- package/dist/watchers/jsonlWatcher.js +50 -3
- package/dist/watchers/providerReaderWatcher.d.ts +7 -0
- package/dist/watchers/providerReaderWatcher.js +13 -0
- package/dist/watchers/types.d.ts +12 -0
- package/dist/zaiQuota.js +2 -1
- package/package.json +41 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,92 @@ 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
|
+
|
|
30
|
+
## [0.29.0] - 2026-11-01
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
|
|
34
|
+
- **OpenCode usage on the OpenAI provider is no longer under-counted.** OpenCode stores every token bucket separately for every provider: input without cached tokens since 2025-11, output without reasoning since 2026-04. The parser still treated `openai` messages as cache-inclusive with reasoning inside output, so it subtracted cache reads from input a second time and never billed reasoning. It now reads the layout from the message's stored `total` (older layouts are still recognized) and defaults to the current one.
|
|
35
|
+
- **Cache tokens from OpenCode's older file storage are now counted.** Messages in that storage keep cache counts under `tokens.cache`, and the parser had ignored them.
|
|
36
|
+
|
|
37
|
+
### Added
|
|
38
|
+
|
|
39
|
+
- **Lean subpaths.** Every new subpath has types, a CommonJS build and a tree-shakable ES-module build, and a contract test pins each one's module set to the root's bindings.
|
|
40
|
+
- `sidekick-shared/pricing`: pure, 8 modules, no Zod. Model pricing and identity, usage normalization and cost, cost provenance, cost/token/duration formatting, and text token estimates.
|
|
41
|
+
- `sidekick-shared/codex`: Node only, 13 modules, no Zod. Codex homes and child-process env, `auth.json` helpers, credential-store mode, `findCodexRolloutFile()`, `codexRolloutOrigin()`, auth-status parsers and atomic JSON writers.
|
|
42
|
+
- `sidekick-shared/providerFailure`: pure, 1 module.
|
|
43
|
+
- `sidekick-shared/statusline/payload`: pure, 2 modules. The status-line stdin parser without the account, credential and snapshot code the full `statusline` entry loads.
|
|
44
|
+
- **Usage events.** `collectUsageEvents()` events gain:
|
|
45
|
+
- `tokens.reasoningTokens`
|
|
46
|
+
- `promptOrdinal`: the prompt the call answered, using the same ordinals as `collectPromptHistory()` with default bounds
|
|
47
|
+
- `requestId`: the provider message id; Codex has none
|
|
48
|
+
- `subagent: { agentId, parentSessionId }`: Claude Code subagent transcripts, and Codex forks and spawned subagents
|
|
49
|
+
|
|
50
|
+
Priced cost is unchanged.
|
|
51
|
+
|
|
52
|
+
- **`bucketUsage()`:**
|
|
53
|
+
- a `session` group dimension
|
|
54
|
+
- `pricedCostUsd`, null when no call in the row was priced
|
|
55
|
+
- `reasoningTokens`
|
|
56
|
+
- `excludeSynthetic`
|
|
57
|
+
- `modelKey: 'canonical'`, which merges model spellings into one row
|
|
58
|
+
- **`findToolCallOrigins({ ids, workspacePaths, since })`** (Node) maps Claude Code `tool_use` ids and Codex `call_id`s to their session, file, time, tool name and the prompt ordinal they answered. Subagents and forks count against the parent's prompts.
|
|
59
|
+
- Workspace scoping and session keys match prompt history.
|
|
60
|
+
- Only lines containing a wanted id are parsed, and the walk stops once every id is found.
|
|
61
|
+
- **Model identity and pricing:**
|
|
62
|
+
- `canonicalModelId()` strips provider/region prefixes, Bedrock and Vertex forms, date suffixes, `-latest` and `[1m]`.
|
|
63
|
+
- `isMovingModelAlias()`.
|
|
64
|
+
- `parseModelId().variant` for GPT tier names, and `compareModelIds()` ranks astra > sol > terra > luna within a version.
|
|
65
|
+
- `registerModelPricing(entries, { onlyIfMissing })` / `clearRegisteredModelPricing()` add a local price layer (provenance `source: 'registered'`). It survives LiteLLM hydration and imported catalogs and is never exported in catalog snapshots.
|
|
66
|
+
- `getPricingCatalogInfo()` reports the active catalog source (`static`, `litellm`, `stale-cache`, `imported`), when it was fetched, and a stable `digest`. `HydrateResult` gains `digest`.
|
|
67
|
+
- **Cost provenance:** `costSourceFromPricingProvenance()`, and `mergeCostComponents()`, which ignores components not seen yet and zero-token unpriced parts. `formatCost(value, { source })` renders null or unpriced as "—" and estimates with " (est.)"; numeric output is unchanged.
|
|
68
|
+
- **Provider failures:** `diagnoseProviderFailure()` decodes Codex `codexErrorInfo` in string or object form (`decodeCodexErrorInfo()`, `CODEX_ERROR_INFO_KINDS`). Every diagnosis gains `retryable`, optional in the type and schema. Evidence for inputs without `codexErrorInfo` is unchanged.
|
|
69
|
+
- **Auth status parsers:** pure `parseClaudeAuthStatus()` for `claude auth status --json` and `parseCodexLoginStatus()` for `codex login status`. The Codex profile code now uses the latter.
|
|
70
|
+
- **Prompt history:**
|
|
71
|
+
- `collectPromptHistory({ includeText: false })` returns metadata only. Ordinals, cursors and exclusions are identical in both modes, and cursors resume across modes.
|
|
72
|
+
- `PROMPT_HISTORY_ORDINAL_MAX_RECORD_BYTES` documents that ordinals are only comparable with the default record bound.
|
|
73
|
+
- **`codexRolloutOrigin()` / `codexSpawnParentFromSource()`:** pure fork and subagent detection from a rollout's `session_meta`.
|
|
74
|
+
- **`createJsonlTail()`:**
|
|
75
|
+
- `onRewrite: 'restart' | 'end'`
|
|
76
|
+
- `noFollow` (`JsonlTailSymlinkError`)
|
|
77
|
+
- `checkpoint()` / `resumeFrom()`, which survive a restart
|
|
78
|
+
- an async `read()`
|
|
79
|
+
- **`CodexRolloutParser`:** `maxPendingBytes`, `metadataOnly`, and `getPendingStats()`.
|
|
80
|
+
- **`installCredentialGuard({ preserveEnv })`** keeps listed variables when they point at safe locations, and throws when one points at a real home.
|
|
81
|
+
- **More exports:** `ProviderId` on `sidekick-shared/browser`. On `sidekick-shared/node`: `codexRefreshFingerprint()`, `compareCodexAuthFreshness()`, `getCodexCredentialStoreMode()`, `forceFileCredentialStore()` and the atomic JSON writers.
|
|
82
|
+
|
|
83
|
+
### Changed
|
|
84
|
+
|
|
85
|
+
- **Usage cache v3.** `USAGE_CACHE_VERSION` is 3, and files are named `*.v3.json`, so a 0.28 reader sharing `usage-cache/` and this version never overwrite each other. The first collection after upgrading re-reads each session once, and old files age out through the existing LRU pruning.
|
|
86
|
+
- **Transcript projections.**
|
|
87
|
+
- Tool items gain `rawToolName` / `mcpServerName`, and projected `input` no longer carries internal `_sidekick*` keys. Raw `SessionEvent`s are unchanged.
|
|
88
|
+
- HTML reports show the raw tool and MCP server instead of those keys.
|
|
89
|
+
- The transcript schemas accept the new fields.
|
|
90
|
+
- **Lighter bundles.** `formatCost()` moved to the `formatting` module (still exported from `modelInfo`), and table-derived pricing lookups are lazy. A tree-shaken bundle that imports only `formatCost()`, `shortModelName()` and `parseModelId()` drops from 12.7 KB to 2.1 KB minified.
|
|
91
|
+
- `shortModelName()` / `getModelDisplayInfo()` use `canonicalModelId()`, so date-suffixed and provider-prefixed ids show their family label.
|
|
92
|
+
- `findCodexRolloutFile()` lives in the light rollout-helpers module; it is still exported from the provider.
|
|
93
|
+
|
|
8
94
|
## [0.28.3] - 2026-10-31
|
|
9
95
|
|
|
10
96
|
### Fixed
|
package/README.md
CHANGED
|
@@ -9,6 +9,34 @@ 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.
|
|
18
|
+
- **0.29.0: lean entries, usage attribution, and honest pricing**
|
|
19
|
+
- **New subpaths:**
|
|
20
|
+
- `sidekick-shared/pricing`: pure, 8 modules. Pricing, model identity, usage cost, cost provenance, formatting and token estimates.
|
|
21
|
+
- `sidekick-shared/codex`: Node, no Zod, 13 modules. Codex homes and env, `auth.json` helpers, rollout lookup and origin, auth-status parsers.
|
|
22
|
+
- `sidekick-shared/providerFailure`: pure.
|
|
23
|
+
- `sidekick-shared/statusline/payload`: pure. The status-line stdin parser without account or quota code.
|
|
24
|
+
- **Usage events:** `collectUsageEvents()` events carry reasoning tokens, the `promptOrdinal` they answered (the same ordinals as prompt history), the provider message id, and `subagent` attribution. The cache is v3, written as `*.v3.json` so 0.28 readers sharing the folder are unaffected.
|
|
25
|
+
- **`bucketUsage()`** gains a `session` dimension, `pricedCostUsd` (null when nothing was priced), `reasoningTokens`, `excludeSynthetic`, and `modelKey: 'canonical'`.
|
|
26
|
+
- **`findToolCallOrigins()`** maps tool-call ids to their session, time and prompt ordinal.
|
|
27
|
+
- **Pricing:**
|
|
28
|
+
- `canonicalModelId()` merges spellings of one model id.
|
|
29
|
+
- `registerModelPricing()` adds local prices that survive catalog hydration.
|
|
30
|
+
- `getPricingCatalogInfo()` reports which catalog priced a cost, with a stable digest.
|
|
31
|
+
- `formatCost(value, { source })` renders null and unpriced costs as "—" and estimates with " (est.)".
|
|
32
|
+
- **Provider failures:** `diagnoseProviderFailure()` decodes Codex `codexErrorInfo` and sets `retryable`.
|
|
33
|
+
- **Also new:**
|
|
34
|
+
- `parseClaudeAuthStatus()` and `parseCodexLoginStatus()`
|
|
35
|
+
- `collectPromptHistory({ includeText: false })`
|
|
36
|
+
- `createJsonlTail()` checkpoints, `onRewrite: 'end'`, `noFollow` and async `read()`
|
|
37
|
+
- Codex parser `maxPendingBytes` and `metadataOnly`
|
|
38
|
+
- transcript projections expose `rawToolName` and `mcpServerName` without leaking internal keys
|
|
39
|
+
- `installCredentialGuard({ preserveEnv })`
|
|
12
40
|
- **0.28.3: plans without a 5-hour window** — a null `five_hour` from the Claude usage endpoint (Ultra, since 2026-10-30) becomes `fiveHour.absent` instead of 0% used. The endpoint's server-driven `limits[]` meters, `extra_usage`, and the credential plan land on `QuotaState.limits` / `extraUsage` / `planLabel` (`quotaStateFromClaudeUsagePayload()`, `fetchQuota(token, { plan })`). Pure helpers `quotaWindowApplies()` / `quotaWindowHasData()` / `quotaWindowLabel()` / `quotaWindowShortLabel()` / `extraQuotaRows()` / `formatExtraUsage()` / `claudePlanLabel()` are on the root and browser entries, and `quotaHistoryWindow()` is on the root and statusline entries. `writeQuotaSnapshot()` no longer freezes once a window disappears or resets, and `resolveQuota()` reports a fully expired cache as unavailable.
|
|
13
41
|
- **0.28.1: credential guard keeps `promisify(execFile)`** — after `installCredentialGuard()`, `promisify(execFile)` and `promisify(exec)` resolve to `{ stdout, stderr }` again (0.28.0 dropped Node's `util.promisify.custom`, so they returned the bare stdout string), and the promisified calls are guarded too.
|
|
14
42
|
- **0.28.0: ES modules, narrow entries, and new readers** — a `module` export condition gives bundlers a tree-shakable ES-module build (Node keeps CommonJS); browser-safe parsers and helpers move onto `/browser` and the provider-home helpers onto `/node`; `sidekick-shared/testing` publishes the credential guard; `providerFailureDiagnosisSchema` joins `/schemas`; prompt history reports `changed` / `progressed` / `roots` and takes `sessionRefs`; signals carry `toolCategory` and `mcpServer`; `collectUsageEvents()` takes `workspacePaths`; `createJsonlTail()` is bounded and reports resets; and Claude sessions follow `CLAUDE_CONFIG_DIR` while Codex lookups include `archived_sessions/`.
|
|
@@ -105,21 +133,21 @@ returns the platform default that both overrides bypass.
|
|
|
105
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 |
|
|
106
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 |
|
|
107
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`) |
|
|
108
|
-
| **Parsers** | JSONL event parsing, OpenCode/Codex format normalization, subagent scanning, session path resolution, debug log parsing
|
|
109
|
-
| **Watchers** | Live session file watching with event bridging
|
|
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 |
|
|
110
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 |
|
|
111
139
|
| **Search** | Cross-session full-text search, advanced filtering (substring, fuzzy, regex, date) |
|
|
112
|
-
| **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()`) |
|
|
113
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()`) |
|
|
114
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 |
|
|
115
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) |
|
|
116
|
-
| **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 |
|
|
117
145
|
| **Report** | Self-contained HTML session report generation; `readSessionReportInputs()` returns metrics, transcript, and `subagents` for the session total |
|
|
118
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()` |
|
|
119
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 |
|
|
120
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()`) |
|
|
121
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 |
|
|
122
|
-
| **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 |
|
|
123
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 |
|
|
124
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 |
|
|
125
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 |
|
|
@@ -134,20 +162,24 @@ returns the platform default that both overrides bypass.
|
|
|
134
162
|
|
|
135
163
|
## Supported import paths
|
|
136
164
|
|
|
137
|
-
`sidekick-shared` ships three
|
|
138
|
-
|
|
139
|
-
| Path
|
|
140
|
-
|
|
|
141
|
-
| `sidekick-shared`
|
|
142
|
-
| `sidekick-shared/browser`
|
|
143
|
-
| `sidekick-shared/node`
|
|
144
|
-
| `sidekick-shared/phrases`
|
|
145
|
-
| `sidekick-shared/modelContext`
|
|
146
|
-
| `sidekick-shared/modelInfo`
|
|
147
|
-
| `sidekick-shared/formatting`
|
|
148
|
-
| `sidekick-shared/schemas`
|
|
149
|
-
| `sidekick-shared/statusline`
|
|
150
|
-
| `sidekick-shared/testing`
|
|
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.
|
|
166
|
+
|
|
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. |
|
|
151
183
|
|
|
152
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.
|
|
153
185
|
|
|
@@ -165,7 +197,7 @@ const guard = installCredentialGuard();
|
|
|
165
197
|
afterEach(() => guard.assertNoViolations());
|
|
166
198
|
```
|
|
167
199
|
|
|
168
|
-
The guard points `HOME` (and `USERPROFILE`, `APPDATA`, `LOCALAPPDATA`) at a temp directory it removes on exit, unsets `CLAUDE_CONFIG_DIR`, `CLAUDE_SECURESTORAGE_CONFIG_DIR`, `CODEX_HOME`, and `SIDEKICK_CONFIG_DIR
|
|
200
|
+
The guard points `HOME` (and `USERPROFILE`, `APPDATA`, `LOCALAPPDATA`) at a temp directory it removes on exit, unsets `CLAUDE_CONFIG_DIR`, `CLAUDE_SECURESTORAGE_CONFIG_DIR`, `CODEX_HOME`, and `SIDEKICK_CONFIG_DIR` (pass `preserveEnv: ['SIDEKICK_CONFIG_DIR', …]` to keep any of these four when they point at a fixture; one pointing at a real home throws), and patches the real `child_process` and `fs` modules. Spawning `security`, `claude`, or `codex` throws (a promisified `execFile` or `exec` rejects instead, and otherwise keeps Node's `{ stdout, stderr }` result), writing under the real `~/.claude`, `~/.claude.json`, `~/.codex`, or `~/.config/sidekick` throws, and reads there are recorded. Production code often swallows errors, so `assertNoViolations()` reports what was recorded even when nothing threw. A `vi.mock('child_process')` factory replaces the module and bypasses the guard for that file.
|
|
169
201
|
|
|
170
202
|
### Browser / webview runtimes
|
|
171
203
|
|
|
@@ -383,11 +415,32 @@ for (const exclusion of result.exclusions) console.warn('record left out', exclu
|
|
|
383
415
|
- **Model and usage** come from the first model call that answered the prompt (Claude: the first non-synthetic assistant message, with split records of that call replacing rather than adding usage; Codex: the first `token_count` with the model from `turn_context`). Later tool-loop calls never replace it. `inputTokens` is uncached input, and missing usage stays `undefined`, never zero.
|
|
384
416
|
- **Late answers enrich the same prompt.** A prompt can be collected before its answer is written. `metadataStatus` is `pending` (no answering call yet), `provisional` (Claude call still being written, `stop_reason: null`), or `final`. While a file's last prompt is not final, the cursor keeps the byte offset and length of its line, its timestamp, and its answer state so far (first-answer message id, model, and usage, never prompt or answer text). The next call continues from where the file stopped, however far the answer is, and returns the prompt again with the same provider, sessionId, ordinal, timestamp, and text only when its model, usage, or status changed; the text comes from re-reading that one line. A prompt closed by the next prompt without an answer comes back once as `final` with no usage. Consumers should upsert by identity.
|
|
385
417
|
- **Large files are streamed and resumable.** Files are read in 1 MiB chunks and every pass stops after a complete line, so memory stays near one chunk plus one record whatever the file size. `maxFileBytes` is the most one file may read in one call: a file with more left keeps what it returned, records `backlog` in its cursor, is counted in `stats.filesOverSizeLimit`, and continues on the next call even if its size and mtime have not changed. `maxSessions`, `maxTotalBytes`, `maxEntries`, the deadline, and an abort stop the whole call the same way, and `cursor.next` makes the next call start with the file that was stopped, so a large or busy session cannot starve the others. The first file of a call always finishes its first record, so a record larger than the budget still moves forward. Bytes appended during a call and a half-written final line wait for the next call. `boundsHit` lists these resumable stops and `hasMore` says whether calling again would read more. No bound throws.
|
|
386
|
-
- **Permanent exclusions are reported.** A record over `maxRecordBytes` is skipped as it streams past without being buffered, even when it is still being written, and a complete line that is not JSON is skipped. Each is counted (`stats.recordsOverSizeLimit`, `stats.recordsMalformed`) and listed in `exclusions` with its byte offset. A prompt inside such a record is lost and does not consume an ordinal. A file that shrank or whose first 4 KiB changed is treated as rewritten: it is read again from the start with fresh ordinals and counted in `stats.sessionsRewritten`.
|
|
418
|
+
- **Permanent exclusions are reported.** A record over `maxRecordBytes` is skipped as it streams past without being buffered, even when it is still being written, and a complete line that is not JSON is skipped. Each is counted (`stats.recordsOverSizeLimit`, `stats.recordsMalformed`) and listed in `exclusions` with its byte offset. A prompt inside such a record is lost and does not consume an ordinal. Ordinals are therefore only comparable across runs and consumers that use the default bound, exported as `PROMPT_HISTORY_ORDINAL_MAX_RECORD_BYTES` (16 MiB). A file that shrank or whose first 4 KiB changed is treated as rewritten: it is read again from the start with fresh ordinals and counted in `stats.sessionsRewritten`.
|
|
387
419
|
- **Change reporting.** `changed` lists the sessions (provider, sessionId, `sessionKey`) whose cursor entry this call added, advanced, moved, or dropped after a rewrite, so a consumer never has to diff cursor internals. `progressed` is false when nothing new was read, which tells a stalled `hasMore` loop apart from one that is moving. `sessionKey` is opaque and equal to the cursor key; it tells apart two files that carry the same session id. `isPromptHistorySessionSettled(cursor.sessions[key])` says whether a cursor entry is fully read with no prompt waiting for its answer, and `parsePromptHistoryCursor(stored)` validates a stored cursor (versions 1 to 3) and returns it as `version: 3` with malformed entries dropped, or `undefined`.
|
|
388
420
|
- **Moved files.** A session whose file is listed again under another path (Codex moves an archived thread's rollout to `archived_sessions/`) keeps its cursor entry and ordinals instead of being read again as a new session.
|
|
389
421
|
- **Cursor versions.** Output is `version: 3`. Cursors from 0.27.0 (`version: 1`) and 0.27.1 (`version: 2`) are accepted: each file is re-verified once, and a 0.27.1 pending prompt is replayed from its line in resumable passes, so it now settles even when its answer is far away. Files 0.27.1 skipped for size were never in its cursor and are read in full on the first call. Metadata that 0.27.1 already gave up on (a pending replay that exceeded `maxFileBytes`) was not saved and cannot be recovered, and a 0.27.0 cursor still cannot recover prompts it returned without metadata.
|
|
390
422
|
|
|
423
|
+
- **Metadata only.** `includeText: false` returns the same entries, ordinals, cursors and exclusions with `text: ''` and `textOmitted: true`, for callers that only need ordinals and timestamps. A cursor written in either mode resumes in the other.
|
|
424
|
+
|
|
425
|
+
### Find where a tool call came from
|
|
426
|
+
|
|
427
|
+
`findToolCallOrigins()` (Node-only) maps Claude Code `tool_use` ids and Codex `call_id`s to the record that issued them. It uses the scope, listing and session keys of `collectPromptHistory()`, so results join with prompt entries:
|
|
428
|
+
|
|
429
|
+
```typescript
|
|
430
|
+
import { findToolCallOrigins } from 'sidekick-shared/node';
|
|
431
|
+
|
|
432
|
+
const origins = await findToolCallOrigins({
|
|
433
|
+
ids: ['toolu_01…', 'call_4df5…'],
|
|
434
|
+
workspacePaths: ['/Users/me/code/app'],
|
|
435
|
+
since: new Date('2026-10-01'), // prunes by file modification time
|
|
436
|
+
});
|
|
437
|
+
for (const [id, origin] of origins) {
|
|
438
|
+
// { provider, sessionId, sessionKey, filePath, timestamp, afterOrdinal, toolName?, subagent? }
|
|
439
|
+
}
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
Only lines containing a wanted id are parsed, Claude Code subagent transcripts are searched too, and the walk stops once every id is found. `afterOrdinal` counts in the prompt history of `sessionId`. For a Codex subagent or fork, that is the parent session; the call's own rollout is in `filePath` and `subagent.agentId`. Ids that are not found are absent from the map.
|
|
443
|
+
|
|
391
444
|
### Collect whole sessions of prompts
|
|
392
445
|
|
|
393
446
|
`collectSessionPromptHistory()` (Node-only) returns every human prompt of each matching session, grouped by session, for consumers that classify a session as a whole. It reads each session from the start with the same scanners as `collectPromptHistory()`, so text, scope, model and usage, and ordinals are identical. The replies and interaction signals that the prompt filter leaves out are opt-in.
|
|
@@ -587,6 +640,38 @@ is read and counted once. The result's `roots` names what was listed, and
|
|
|
587
640
|
every event carries the `filePath` of its session. When no root resolves,
|
|
588
641
|
no session is read.
|
|
589
642
|
|
|
643
|
+
#### Attribute usage to prompts and subagents
|
|
644
|
+
|
|
645
|
+
Since 0.29.0 each event says which prompt it answered and who made the call:
|
|
646
|
+
|
|
647
|
+
- **`promptOrdinal`** is the ordinal of the last human prompt at or before the call. These are the same ordinals `collectPromptHistory()` returns with default bounds, so usage joins with prompt entries on `(provider, sessionId, ordinal)`. It is `null` before the first prompt, and for a Codex fork or subagent, which has no prompts of its own. It is absent for OpenCode, which has no prompt history.
|
|
648
|
+
- **`tokens.reasoningTokens`** is set when the provider reported reasoning. It is informational: cost is unchanged because it is already priced from the billable output. Codex counts reasoning inside `outputTokens`; OpenCode reports it separately.
|
|
649
|
+
- **`requestId`** is the provider's message id (Claude Code, OpenCode). Codex records none.
|
|
650
|
+
- **`subagent: { agentId, parentSessionId }`** marks calls made by Claude Code subagent transcripts and by Codex spawned subagents or forks.
|
|
651
|
+
|
|
652
|
+
```typescript
|
|
653
|
+
import { bucketUsage } from 'sidekick-shared/browser';
|
|
654
|
+
|
|
655
|
+
// Per session per day, model spellings merged, placeholders dropped.
|
|
656
|
+
const rows = bucketUsage(events, {
|
|
657
|
+
granularity: 'day',
|
|
658
|
+
groupBy: ['session', 'model'],
|
|
659
|
+
modelKey: 'canonical', // claude-opus-5-5-20261001 and claude-opus-5-5 share a row
|
|
660
|
+
excludeSynthetic: true,
|
|
661
|
+
});
|
|
662
|
+
// row.pricedCostUsd is null when no call in the row was priced; row.costUsd keeps 0 for compatibility.
|
|
663
|
+
|
|
664
|
+
// Cost per prompt:
|
|
665
|
+
const perPrompt = new Map<string, number>();
|
|
666
|
+
for (const event of events) {
|
|
667
|
+
if (event.promptOrdinal == null || event.costUsd == null) continue;
|
|
668
|
+
const key = `${event.provider}:${event.sessionId}:${event.promptOrdinal}`;
|
|
669
|
+
perPrompt.set(key, (perPrompt.get(key) ?? 0) + event.costUsd);
|
|
670
|
+
}
|
|
671
|
+
```
|
|
672
|
+
|
|
673
|
+
The cache is versioned (`USAGE_CACHE_VERSION` 3, files named `*.v3.json`), so the first collection after upgrading re-reads each session once.
|
|
674
|
+
|
|
590
675
|
### Detect the active session provider
|
|
591
676
|
|
|
592
677
|
For a single-provider surface, auto-detect and construct just one:
|
|
@@ -626,9 +711,9 @@ console.log(`Found ${tasks.length} tasks`);
|
|
|
626
711
|
|
|
627
712
|
### Diagnose terminal provider failures
|
|
628
713
|
|
|
629
|
-
`diagnoseProviderFailure()` is a pure, synchronous helper exported from `sidekick-shared` and `sidekick-shared/
|
|
714
|
+
`diagnoseProviderFailure()` is a pure, synchronous helper exported from `sidekick-shared`, `sidekick-shared/browser`, and `sidekick-shared/providerFailure`. The caller decides whether an SDK event is terminal: a Codex `ErrorItem`, including a reconnect notice, must not become terminal merely because its message can be classified.
|
|
630
715
|
|
|
631
|
-
The identifier lists behind the result types are exported alongside it as readonly arrays (`PROVIDER_FAILURE_CODES`, `PROVIDER_RECOVERY_ACTIONS`, `PROVIDER_CREDENTIAL_KINDS`, `PROVIDER_AUTHENTICATION_STATES`, `PROVIDER_AUTHENTICATION_SOURCES`, `PROVIDER_FAILURE_EVIDENCE_SOURCES`). To validate a diagnosis that crossed a process boundary, use `providerFailureDiagnosisSchema` from `sidekick-shared/schemas`; it is built from the same arrays, so it accepts every value a given release can return.
|
|
716
|
+
The identifier lists behind the result types are exported alongside it as readonly arrays (`PROVIDER_FAILURE_CODES`, `PROVIDER_RECOVERY_ACTIONS`, `PROVIDER_CREDENTIAL_KINDS`, `PROVIDER_AUTHENTICATION_STATES`, `PROVIDER_AUTHENTICATION_SOURCES`, `PROVIDER_FAILURE_EVIDENCE_SOURCES`, and since 0.29.0 `CODEX_ERROR_INFO_KINDS`). To validate a diagnosis that crossed a process boundary, use `providerFailureDiagnosisSchema` from `sidekick-shared/schemas`; it is built from the same arrays, so it accepts every value a given release can return.
|
|
632
717
|
|
|
633
718
|
```typescript
|
|
634
719
|
import { diagnoseProviderFailure } from 'sidekick-shared/browser';
|
|
@@ -668,6 +753,8 @@ The result contains `provider`, caller-supplied `credentialKind`, `diagnosis`, `
|
|
|
668
753
|
|
|
669
754
|
Recognized structured request codes take precedence over HTTP status, followed by narrow message patterns. A bare 401 establishes credential rejection, with recovery chosen from the credential kind; a bare 403 does not distinguish authentication from execution policy and remains unknown, while a message containing `denied by policy` is `execution_policy_denied` regardless of status. An explicit OAuth or refresh-token rejection (`refresh token is invalid`) outranks overlapping thread/conversation-expiry wording; `login has expired` and `please re-authenticate` resolve according to the credential kind. Ambiguous phrases such as `Session expired` or `Permission denied` on their own remain unknown. Explicit credential rejection from the request overrides a successful local OAuth check. OAuth observations never validate API keys. Thread/conversation failures, 5xx responses, DNS failures, and connection resets do not establish rejected account credentials. The reported `Reconnecting... 2/5 (unexpected status 503 Service Unavailable: upstream connect error or disconnect/reset before headers. reset reason: connection termination)` is classified as `service_unavailable`, with message provenance and HTTP 503, if the caller supplies it as a terminal error.
|
|
670
755
|
|
|
756
|
+
Since 0.29.0 every diagnosis also carries `retryable`. It is true when sending the same request again, after any `retryAfter`, can reasonably succeed: a rate limit that lifts, overload, 5xx, timeouts and failed connections, but not a Codex `usageLimitExceeded` with no known reset. The field is optional in the type and the schema, so diagnoses from older producers stay valid. Codex's structured `codexErrorInfo` is decoded on the error or any nested `error`, `cause`, `response`, `data`, `body` or `errors` entry, in string or object form (`usageLimitExceeded`, `serverOverloaded`, `httpConnectionFailed: { httpStatusCode }`, `unauthorized`, `contextWindowExceeded`, …). The evidence entry records it as `codexErrorInfo`, and `decodeCodexErrorInfo()` exposes the decoder. `parseClaudeAuthStatus()` and `parseCodexLoginStatus()` are pure parsers for `claude auth status --json` and `codex login status` output.
|
|
757
|
+
|
|
671
758
|
The projection contains no raw errors, stacks, credentials, or request URLs. Product wording belongs to consumers. The helper performs no I/O, credential reads, SDK calls, stream control, retries, or UI actions. Existing transcript/tool `ErrorCategory` and quota-specific guidance contracts remain unchanged.
|
|
672
759
|
|
|
673
760
|
### Check public provider service status
|
|
@@ -806,6 +893,48 @@ console.log(formatCost(costUsd)); // "$0.01"
|
|
|
806
893
|
cache/reasoning normalization above and misprices OpenAI-style usage, where
|
|
807
894
|
cached tokens are already included in the input count.
|
|
808
895
|
|
|
896
|
+
#### Model identity, local prices, and the pricing basis
|
|
897
|
+
|
|
898
|
+
```typescript
|
|
899
|
+
import {
|
|
900
|
+
canonicalModelId,
|
|
901
|
+
registerModelPricing,
|
|
902
|
+
getPricingCatalogInfo,
|
|
903
|
+
formatCost,
|
|
904
|
+
mergeCostComponents,
|
|
905
|
+
costSourceFromPricingProvenance,
|
|
906
|
+
} from 'sidekick-shared/pricing';
|
|
907
|
+
|
|
908
|
+
canonicalModelId('us.anthropic.claude-opus-5-5-20261001-v1:0'); // 'claude-opus-5-5'
|
|
909
|
+
canonicalModelId('azure/us/gpt-6.1-terra'); // 'gpt-6.1-terra'
|
|
910
|
+
|
|
911
|
+
// Fill a gap the catalogs do not price yet (never overrides an exact price)...
|
|
912
|
+
registerModelPricing(
|
|
913
|
+
{
|
|
914
|
+
'gpt-7-sol': {
|
|
915
|
+
inputCostPerMillion: 3,
|
|
916
|
+
outputCostPerMillion: 15,
|
|
917
|
+
cacheWriteCostPerMillion: 3.75,
|
|
918
|
+
cacheReadCostPerMillion: 0.3,
|
|
919
|
+
},
|
|
920
|
+
},
|
|
921
|
+
{ onlyIfMissing: true },
|
|
922
|
+
);
|
|
923
|
+
// ...or, without onlyIfMissing, set prices that win over every catalog (e.g. negotiated rates).
|
|
924
|
+
|
|
925
|
+
getPricingCatalogInfo();
|
|
926
|
+
// { source: 'litellm' | 'stale-cache' | 'imported' | 'static', fetchedAt?, digest: 'fnv1a64:…', catalogEntries, registeredEntries }
|
|
927
|
+
|
|
928
|
+
formatCost(null); // '—'
|
|
929
|
+
formatCost(1.5, { source: 'estimated' }); // '$1.50 (est.)'
|
|
930
|
+
mergeCostComponents([
|
|
931
|
+
{ source: 'reported', tokens: 1200 },
|
|
932
|
+
{ source: 'unpriced', tokens: 0 }, // zero-token unpriced parts do not taint the total
|
|
933
|
+
]); // 'reported'
|
|
934
|
+
```
|
|
935
|
+
|
|
936
|
+
Registered prices survive `hydratePricingCatalog()` and are reported with provenance `source: 'registered'`. They are local configuration, so `exportResolvedModelCatalog()` never includes them. Store `getPricingCatalogInfo().digest` next to a synced cost so a later re-price can be told apart from a usage change. `isMovingModelAlias()` names agent-CLI aliases (`default`, `auto`, `sonnet`, `opus`, …) whose concrete model changes over time; don't apply dollar caps to them. `parseModelId()` reports a `variant` for GPT tier names, and `compareModelIds()` ranks astra > sol > terra > luna within a version.
|
|
937
|
+
|
|
809
938
|
### Extract token usage and tool calls from events
|
|
810
939
|
|
|
811
940
|
```typescript
|
|
@@ -876,9 +1005,28 @@ const projection = segmentAssistantTurn(assistantTurnEventsFromSessionEvents(ses
|
|
|
876
1005
|
console.log(projection.answer);
|
|
877
1006
|
console.log(projection.timeline);
|
|
878
1007
|
console.log(projection.process.steps);
|
|
879
|
-
console.log(projection.subagents); // Claude Task spawns, prompt text omitted
|
|
1008
|
+
console.log(projection.subagents); // Claude Code Agent / Task spawns, prompt text omitted
|
|
1009
|
+
```
|
|
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 }
|
|
880
1024
|
```
|
|
881
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
|
+
|
|
882
1030
|
### Format shared dashboard values
|
|
883
1031
|
|
|
884
1032
|
```typescript
|
|
@@ -901,7 +1049,7 @@ const parser = new JsonlParser(
|
|
|
901
1049
|
parser.processChunk(rawData);
|
|
902
1050
|
```
|
|
903
1051
|
|
|
904
|
-
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.
|
|
905
1053
|
|
|
906
1054
|
### Tail raw JSONL events incrementally
|
|
907
1055
|
|
|
@@ -922,9 +1070,22 @@ const tail = createJsonlTail({
|
|
|
922
1070
|
tail.start();
|
|
923
1071
|
```
|
|
924
1072
|
|
|
925
|
-
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 and calls `onReset({ reason, previousOffset })` before the events from the start arrive. `createJsonlTail()` is also exported from
|
|
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.
|
|
1074
|
+
|
|
1075
|
+
Since 0.29.0:
|
|
1076
|
+
|
|
1077
|
+
- `onRewrite: 'end'` continues from the rewritten file's last complete line instead of replaying it, and `onReset` then also receives the `offset` reading continues from. The default is `'restart'`.
|
|
1078
|
+
- `noFollow: true` refuses a symlinked path (`JsonlTailSymlinkError`, code `JSONL_TAIL_SYMLINK`) instead of following it.
|
|
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.
|
|
1080
|
+
- `read()` is an async pull that returns the new complete lines.
|
|
1081
|
+
|
|
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.
|
|
926
1087
|
|
|
927
|
-
`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
|
|
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.
|
|
928
1089
|
|
|
929
1090
|
### Search a workspace and diagnose its provider
|
|
930
1091
|
|