sidekick-shared 0.29.1 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/README.md +171 -60
- package/dist/aggregation/EventAggregator.d.ts +79 -4
- package/dist/aggregation/EventAggregator.js +391 -37
- package/dist/aggregation/types.d.ts +27 -3
- package/dist/browser.d.ts +7 -3
- package/dist/browser.js +23 -3
- package/dist/context/sessionContext.js +5 -1
- package/dist/esm/aggregation/EventAggregator.js +392 -38
- package/dist/esm/browser.js +3 -1
- package/dist/esm/context/sessionContext.js +6 -2
- package/dist/esm/fileContinuity.js +149 -0
- package/dist/esm/index.js +5 -2
- package/dist/esm/node.js +2 -0
- package/dist/esm/parsers/codexParser.js +192 -3
- package/dist/esm/parsers/subagentEndState.js +61 -0
- package/dist/esm/parsers/subagentScanner.js +4 -0
- package/dist/esm/parsers/subagentTraceParser.js +6 -0
- package/dist/esm/promptHistory.js +22 -11
- package/dist/esm/providers/claudeCode.js +50 -27
- package/dist/esm/providers/codex.js +153 -163
- package/dist/esm/providers/codexCompressedReader.js +184 -0
- package/dist/esm/providers/codexDatabase.js +47 -2
- package/dist/esm/providers/codexRollouts.js +20 -2
- package/dist/esm/providers/codexSubagents.js +456 -0
- package/dist/esm/providers/compressedRollouts.js +330 -0
- package/dist/esm/providers/factory.js +4 -1
- package/dist/esm/providers/rolloutWalker.js +32 -4
- package/dist/esm/schemas/sessionEvent.js +32 -4
- package/dist/esm/sessionMonitor.js +7 -1
- package/dist/esm/sessionPreviews.js +9 -0
- package/dist/esm/sessionPromptHistory.js +5 -3
- package/dist/esm/subagentActivity.js +125 -0
- package/dist/esm/subagentDisplayState.js +81 -0
- package/dist/esm/subagentSpawn.js +206 -6
- package/dist/esm/testing/codexSubagentFixtures.js +336 -0
- package/dist/esm/toolCallOrigins.js +17 -10
- package/dist/esm/toolCategory.js +6 -2
- package/dist/esm/transcript.js +6 -2
- package/dist/esm/turns/assistantTurn.js +6 -3
- package/dist/esm/usage/usageEvents.js +4 -1
- package/dist/esm/watchers/eventBridge.js +31 -1
- package/dist/esm/watchers/jsonlTail.js +11 -42
- package/dist/esm/watchers/jsonlWatcher.js +19 -9
- package/dist/esm/watchers/providerReaderWatcher.js +5 -4
- package/dist/fileContinuity.d.ts +95 -0
- package/dist/fileContinuity.js +187 -0
- package/dist/index.d.ts +14 -7
- package/dist/index.js +39 -10
- package/dist/node.d.ts +4 -0
- package/dist/node.js +16 -2
- package/dist/parsers/codexParser.d.ts +44 -0
- package/dist/parsers/codexParser.js +192 -3
- package/dist/parsers/subagentEndState.d.ts +20 -0
- package/dist/parsers/subagentEndState.js +65 -0
- package/dist/parsers/subagentScanner.js +4 -0
- package/dist/parsers/subagentTraceParser.d.ts +8 -0
- package/dist/parsers/subagentTraceParser.js +6 -0
- package/dist/promptHistory.d.ts +13 -3
- package/dist/promptHistory.js +22 -11
- package/dist/providers/claudeCode.js +50 -27
- package/dist/providers/codex.d.ts +35 -5
- package/dist/providers/codex.js +150 -160
- package/dist/providers/codexCompressedReader.d.ts +58 -0
- package/dist/providers/codexCompressedReader.js +221 -0
- package/dist/providers/codexDatabase.d.ts +25 -0
- package/dist/providers/codexDatabase.js +48 -2
- package/dist/providers/codexRollouts.d.ts +16 -2
- package/dist/providers/codexRollouts.js +20 -2
- package/dist/providers/codexSubagents.d.ts +66 -0
- package/dist/providers/codexSubagents.js +495 -0
- package/dist/providers/compressedRollouts.d.ts +114 -0
- package/dist/providers/compressedRollouts.js +380 -0
- package/dist/providers/factory.d.ts +5 -0
- package/dist/providers/factory.js +4 -1
- package/dist/providers/rolloutWalker.d.ts +15 -2
- package/dist/providers/rolloutWalker.js +33 -4
- package/dist/providers/types.d.ts +22 -1
- package/dist/schemas/sessionEvent.d.ts +26 -2
- package/dist/schemas/sessionEvent.js +31 -3
- package/dist/sessionMonitor.d.ts +6 -0
- package/dist/sessionMonitor.js +7 -1
- package/dist/sessionPreviews.js +9 -0
- package/dist/sessionPromptHistory.d.ts +5 -0
- package/dist/sessionPromptHistory.js +5 -36
- package/dist/subagentActivity.d.ts +91 -0
- package/dist/subagentActivity.js +132 -0
- package/dist/subagentDisplayState.d.ts +81 -0
- package/dist/subagentDisplayState.js +86 -0
- package/dist/subagentSpawn.d.ts +100 -5
- package/dist/subagentSpawn.js +218 -7
- package/dist/testing/codexSubagentFixtures.d.ts +48 -0
- package/dist/testing/codexSubagentFixtures.js +344 -0
- package/dist/toolCallOrigins.d.ts +5 -0
- package/dist/toolCallOrigins.js +17 -10
- package/dist/toolCategory.js +6 -2
- package/dist/transcript.js +5 -1
- package/dist/turns/assistantTurn.d.ts +4 -1
- package/dist/turns/assistantTurn.js +5 -2
- package/dist/types/codex.d.ts +71 -2
- package/dist/types/sessionEvent.d.ts +38 -3
- package/dist/usage/usageEvents.d.ts +4 -1
- package/dist/usage/usageEvents.js +4 -1
- package/dist/watchers/eventBridge.js +30 -0
- package/dist/watchers/jsonlTail.js +11 -42
- package/dist/watchers/jsonlWatcher.d.ts +5 -1
- package/dist/watchers/jsonlWatcher.js +18 -8
- package/dist/watchers/providerReaderWatcher.js +5 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,6 +9,13 @@ Types, parsers, providers, readers, formatters, aggregation, search, reporting,
|
|
|
9
9
|
|
|
10
10
|
## Recent additions
|
|
11
11
|
|
|
12
|
+
- **0.30.0: Codex subagents, agents that never report, and compressed rollouts**
|
|
13
|
+
- **Codex subagents:** `EventAggregator` follows a `spawn_agent` child from its `{"task_name"}` launch to its `completed` record or `FINAL_ANSWER` report; `followup_task` reopens it, `interrupt_agent` stops it, and a failed spawn is `failed`. `CodexRolloutParserOptions.includeSubagentActivity` (on in the Codex reader) turns Codex's `SubAgentActivity` records and `FINAL_ANSWER` messages into `system` events that `subagentActivityFromEvent()` reads. `isSubagentSpawnTool()` now also matches `spawn_agent`; `isCodexSubagentSpawnTool()` and `parseCodexSpawnLaunch()` are new, and `SUBAGENT_SPAWN_TOOL_NAMES` / `isAssistantTurnSubagentTool()` stay Claude Code-only.
|
|
14
|
+
- **Discovery:** Codex `scanSubagents()` returns every spawned thread below a session, and `SubagentStats` gains `filePath`, `spawnToolUseId`, `agentNickname`, `agentPath`, `parentAgentId`, `depth`, and `endedCleanly`. A forked child's stats no longer count the parent history its rollout embeds.
|
|
15
|
+
- **Settling:** `applySubagentEvidence()` applies Claude Code meta flags, and `agents_killed` lines, stop notices, and post-compaction `task_status` attachments settle running agents; `SubagentLifecycle.settledBy` records which. `resolveSubagentDisplayState()` reads a still-running agent as `finished-unreported` or `ended` without changing its lifecycle.
|
|
16
|
+
- **Compressed rollouts:** `includeCompressedRollouts` reads Codex `.jsonl.zst` rollouts where `zlib.zstdDecompressSync` exists (Node 22.15+) and skips them with a diagnostic elsewhere. `readRolloutText()` and `resolveRolloutPath()` are on the root and `/node`.
|
|
17
|
+
- **Readers:** the Claude Code and Codex readers restart from offset 0 when the file was rewritten or replaced, not only when it shrank. `wasTruncated()` reports any restart and optional `lastReset()` says why. `SessionMonitor.poll()` and the Codex watcher now read first and reset before delivering the replay, so a truncated file's events are no longer counted twice.
|
|
18
|
+
- **Also:** `PermissionMode` adds `auto` and `dontAsk`; `includeTaskNotifications` also emits `task_status` events; `classifyTool()` maps `wait` to `other` and `interrupt_agent` to `subagent`; saved aggregator snapshots (schema 7) replay once.
|
|
12
19
|
- **0.29.1: background agents, `Agent` spawns, and verified resume**
|
|
13
20
|
- **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
21
|
- **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.
|
|
@@ -125,61 +132,61 @@ returns the platform default that both overrides bypass.
|
|
|
125
132
|
|
|
126
133
|
## API Overview
|
|
127
134
|
|
|
128
|
-
| Module | Description
|
|
129
|
-
| --------------------------------- |
|
|
130
|
-
| **Types** | Session events, OpenCode/Codex format types, persistence schemas (tasks, decisions, notes, plans, historical data)
|
|
131
|
-
| **Paths** | Config directory resolution, project data paths, workspace encoding
|
|
132
|
-
| **Readers** | Read tasks, decisions, notes, history, handoff, and plans from `~/.config/sidekick/`
|
|
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
|
|
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
|
|
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`)
|
|
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
|
|
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
|
|
139
|
-
| **Search** | Cross-session full-text search, advanced filtering (substring, fuzzy, regex, date)
|
|
140
|
-
| **Aggregation** | Event aggregation (including subagent lifecycles that follow background agents
|
|
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()`)
|
|
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
|
|
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)
|
|
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
|
|
145
|
-
| **Report** | Self-contained HTML session report generation; `readSessionReportInputs()` returns metrics, transcript, and `subagents` for the session total
|
|
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()`
|
|
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
|
|
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()`)
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
154
|
-
| **Multi-Provider Quota** | `MultiProviderQuotaService` orchestrates Claude polling + peak-hours + account labels + Codex/z.ai quota updates behind one typed event stream. `CodexQuotaWatcher` watches the active Codex rollout for live rate limits with snapshot fallback; z.ai quota uses the API resolver with snapshot fallback. All three stay dormant without a matching account and wake on account changes
|
|
155
|
-
| **Accounts** | Multi-provider account registry (v2) with per-provider active account, save/switch/remove, v1 migration, `ensureDefaultAccounts()` for startup bootstrap (migrations, abandoned-login cleanup, then `syncLiveAccountState()`), `syncLiveAccountState()` to register live logins the registry has never seen under their email, fold rotated tokens into a private backup (the live account keeps no profile-home copy; `ProviderSyncReport` reports `demoted`, `repaired`, and switch-`journal` recovery), and merge duplicate Codex profiles, `getAccountHealth()` / `listAccountsWithHealth()` for secret-free `fresh`/`expiring`/`expired`/`unknown`/`missing` health with access/refresh expiry, `getActiveAccountStatus()` for a single-pass active-account read across providers, `onAccountsChanged()` for push-based login/logout/switch notification (process-local mutations, filesystem watches, and a catch-up poll), and live-first `resolveActiveClaudeAccount()` /`resolveActiveCodexAccount()` that report the currently logged-in account (self-healing the saved pointer) for display
|
|
156
|
-
| **Account Management 2.0** | Provider-neutral acquisition + switching: `beginAccountLogin()` / `getAccountLoginStatus()` / `finalizeAccountLogin()` / `spawnAccountLogin()` drive a TTY-less, profile-isolated login that doesn't disturb the active account until finalization; `listAllAccounts()` and `switchAccount()` expose a shared switcher across Claude and Codex; as of 0.26.6 switches are two-phase and verified, return a `SwitchAccountResult` with `runningConsumers`, `needsLogin`, and an `undoToken` (`undoLastSwitch()`), refuse expired or missing credentials unless forced, and `beginAccountLogin(provider, label, { existingAccountId })` re-authenticates a saved profile in place. As of 0.27.6 a switch always captures the outgoing login first (even with `force`), and `discardAccountLogin()` releases an abandoned login home. Every status/finalize/switch entry point also has an `-Async` variant (`getAccountLoginStatusAsync()`, `finalizeAccountLoginAsync()`, `switchAccountAsync()`, …) that keeps `codex` CLI probes off the event loop — prefer those from hosts with a UI loop. Claude accounts get canonical profile homes with account-scoped macOS keychain suffixes (the live account's home holds no credential) and startup migration from legacy flat backups
|
|
157
|
-
| **Isolated launch & auto-switch** | `getAccountLaunchEnv()` builds an isolated `CLAUDE_CONFIG_DIR` / `CODEX_HOME` environment (plus `envUnset`) for running a CLI as a saved account without switching — the live account gets the live home, and an inactive profile is restored from its backup first unless `restoreBackup: false` — `writeAccountLauncher()` / `removeLauncher()` write POSIX launcher scripts that resolve the account's home when they run (`writeLauncher()` is deprecated), `detectRunningAccountConsumers()` / `findRunningProcesses()` find CLIs and desktop apps that hold a login (`ps` / `tasklist`), `refreshInactiveAccounts()` is an opt-in keep-alive through the official CLIs, and a default-off `AutoSwitchController` (`decideAutoSwitch()`) moves to a healthier saved account when quota crosses a configured threshold; its `switchAccount` callback may return a Promise and defaults to `switchAccountAsync()`
|
|
158
|
-
| **Codex Profiles** | Codex account lifecycle — prepare, finalize, switch, remove — switching atomically swaps the profile's backed-up credentials into the system `~/.codex/auth.json`, with rotated-token staleness protection, one-time dual-home migration, and legacy multi-home session monitoring. As of 0.27.6 every account keeps a private byte-preserved backup, the live account keeps no profile copy, switches are journaled and refuse while Codex consumers run or keep-alive holds the account (`force` bypasses neither), file-mode credential storage is required, and `readStoredCodexAccount()` / `syncCodexLiveStateAsync()` are public
|
|
159
|
-
| **Quota Snapshots** | Persistent quota caching per provider/account for offline fallback
|
|
160
|
-
| **Statusline** | Cache-only one-line account/quota/burn footer — `formatStatusline()`, `selectStatuslineAccount()`, `BurnRateCalculator`, and `estimateTimeToQuota()`, also published via the [`sidekick-shared/statusline`](#supported-import-paths) subpath for fast per-prompt rendering
|
|
161
|
-
| **Phrases** | Curated humorous phrases for loading/idle states, available as a flat `ALL_PHRASES` array or grouped via `PHRASE_CATEGORIES` for category-aware UI
|
|
135
|
+
| Module | Description |
|
|
136
|
+
| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
137
|
+
| **Types** | Session events, OpenCode/Codex format types, persistence schemas (tasks, decisions, notes, plans, historical data) |
|
|
138
|
+
| **Paths** | Config directory resolution, project data paths, workspace encoding |
|
|
139
|
+
| **Readers** | Read tasks, decisions, notes, history, handoff, and plans from `~/.config/sidekick/` |
|
|
140
|
+
| **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 |
|
|
141
|
+
| **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. As of 0.30.0 `includeCompressedRollouts` (on `createSessionProviders()` and `CodexProviderOptions`) reads Codex `.jsonl.zst` rollouts, Codex `scanSubagents()` returns every spawned thread below a session (`isCodexSubagentThread()`, `CodexDatabase.getSpawnedDescendantThreads()`), and the Claude Code and Codex readers restart on a rewritten or replaced file (`wasTruncated()`, optional `lastReset()`) |
|
|
142
|
+
| **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`) |
|
|
143
|
+
| **Parsers** | JSONL event parsing, OpenCode/Codex format normalization, subagent scanning (`scanSubagentDir()`, `scanSubagentTraces()`, `readAgentMeta()`), Codex subagent activity events (`includeSubagentActivity`, `subagentActivityFromEvent()`), session path resolution, debug log parsing |
|
|
144
|
+
| **Watchers** | Live session file watching with event bridging (`createWatcher()`, with `includeTaskNotifications` for Claude Code mid-turn task notifications, stop notices, final `task_status` attachments, and `agents_killed` lines) and optional `getResumeState()` / `restoreResumeState()`, plus `createJsonlTail()` for raw incremental JSONL consumers and `captureJsonlCheckpoint()` / `verifyJsonlCheckpoint()` for readers that keep their own offset |
|
|
145
|
+
| **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 |
|
|
146
|
+
| **Search** | Cross-session full-text search, advanced filtering (substring, fuzzy, regex, date) |
|
|
147
|
+
| **Aggregation** | Event aggregation (including subagent lifecycles that follow Claude Code background agents and Codex `spawn_agent` children until a notification, stop marker, task status, or host evidence (`applySubagentEvidence()`) settles them, recorded in `settledBy`, and `resolveSubagentDisplayState()` for display), frequency tracking, activity heatmaps, pattern extraction, and session snapshots (`saveSnapshot()`, `loadSnapshot()`, `isSnapshotValid()`) |
|
|
148
|
+
| **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()`) |
|
|
149
|
+
| **Error History** | Shared error taxonomy (`categorizeError()`, `extractErrorMessage()`) plus append-only per-project failure history (`appendErrorHistory()`, `readErrorHistory()`, `getTopFailingTools()`) behind per-tool/hour/model error rollups |
|
|
150
|
+
| **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) |
|
|
151
|
+
| **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 |
|
|
152
|
+
| **Report** | Self-contained HTML session report generation; `readSessionReportInputs()` returns metrics, transcript, and `subagents` for the session total |
|
|
153
|
+
| **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()` |
|
|
154
|
+
| **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 |
|
|
155
|
+
| **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()`) |
|
|
156
|
+
| **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 |
|
|
157
|
+
| **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 and background agents' final `task_status` 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 |
|
|
158
|
+
| **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 |
|
|
159
|
+
| **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 |
|
|
160
|
+
| **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 |
|
|
161
|
+
| **Multi-Provider Quota** | `MultiProviderQuotaService` orchestrates Claude polling + peak-hours + account labels + Codex/z.ai quota updates behind one typed event stream. `CodexQuotaWatcher` watches the active Codex rollout for live rate limits with snapshot fallback; z.ai quota uses the API resolver with snapshot fallback. All three stay dormant without a matching account and wake on account changes |
|
|
162
|
+
| **Accounts** | Multi-provider account registry (v2) with per-provider active account, save/switch/remove, v1 migration, `ensureDefaultAccounts()` for startup bootstrap (migrations, abandoned-login cleanup, then `syncLiveAccountState()`), `syncLiveAccountState()` to register live logins the registry has never seen under their email, fold rotated tokens into a private backup (the live account keeps no profile-home copy; `ProviderSyncReport` reports `demoted`, `repaired`, and switch-`journal` recovery), and merge duplicate Codex profiles, `getAccountHealth()` / `listAccountsWithHealth()` for secret-free `fresh`/`expiring`/`expired`/`unknown`/`missing` health with access/refresh expiry, `getActiveAccountStatus()` for a single-pass active-account read across providers, `onAccountsChanged()` for push-based login/logout/switch notification (process-local mutations, filesystem watches, and a catch-up poll), and live-first `resolveActiveClaudeAccount()` /`resolveActiveCodexAccount()` that report the currently logged-in account (self-healing the saved pointer) for display |
|
|
163
|
+
| **Account Management 2.0** | Provider-neutral acquisition + switching: `beginAccountLogin()` / `getAccountLoginStatus()` / `finalizeAccountLogin()` / `spawnAccountLogin()` drive a TTY-less, profile-isolated login that doesn't disturb the active account until finalization; `listAllAccounts()` and `switchAccount()` expose a shared switcher across Claude and Codex; as of 0.26.6 switches are two-phase and verified, return a `SwitchAccountResult` with `runningConsumers`, `needsLogin`, and an `undoToken` (`undoLastSwitch()`), refuse expired or missing credentials unless forced, and `beginAccountLogin(provider, label, { existingAccountId })` re-authenticates a saved profile in place. As of 0.27.6 a switch always captures the outgoing login first (even with `force`), and `discardAccountLogin()` releases an abandoned login home. Every status/finalize/switch entry point also has an `-Async` variant (`getAccountLoginStatusAsync()`, `finalizeAccountLoginAsync()`, `switchAccountAsync()`, …) that keeps `codex` CLI probes off the event loop — prefer those from hosts with a UI loop. Claude accounts get canonical profile homes with account-scoped macOS keychain suffixes (the live account's home holds no credential) and startup migration from legacy flat backups |
|
|
164
|
+
| **Isolated launch & auto-switch** | `getAccountLaunchEnv()` builds an isolated `CLAUDE_CONFIG_DIR` / `CODEX_HOME` environment (plus `envUnset`) for running a CLI as a saved account without switching — the live account gets the live home, and an inactive profile is restored from its backup first unless `restoreBackup: false` — `writeAccountLauncher()` / `removeLauncher()` write POSIX launcher scripts that resolve the account's home when they run (`writeLauncher()` is deprecated), `detectRunningAccountConsumers()` / `findRunningProcesses()` find CLIs and desktop apps that hold a login (`ps` / `tasklist`), `refreshInactiveAccounts()` is an opt-in keep-alive through the official CLIs, and a default-off `AutoSwitchController` (`decideAutoSwitch()`) moves to a healthier saved account when quota crosses a configured threshold; its `switchAccount` callback may return a Promise and defaults to `switchAccountAsync()` |
|
|
165
|
+
| **Codex Profiles** | Codex account lifecycle — prepare, finalize, switch, remove — switching atomically swaps the profile's backed-up credentials into the system `~/.codex/auth.json`, with rotated-token staleness protection, one-time dual-home migration, and legacy multi-home session monitoring. As of 0.27.6 every account keeps a private byte-preserved backup, the live account keeps no profile copy, switches are journaled and refuse while Codex consumers run or keep-alive holds the account (`force` bypasses neither), file-mode credential storage is required, and `readStoredCodexAccount()` / `syncCodexLiveStateAsync()` are public |
|
|
166
|
+
| **Quota Snapshots** | Persistent quota caching per provider/account for offline fallback |
|
|
167
|
+
| **Statusline** | Cache-only one-line account/quota/burn footer — `formatStatusline()`, `selectStatuslineAccount()`, `BurnRateCalculator`, and `estimateTimeToQuota()`, also published via the [`sidekick-shared/statusline`](#supported-import-paths) subpath for fast per-prompt rendering |
|
|
168
|
+
| **Phrases** | Curated humorous phrases for loading/idle states, available as a flat `ALL_PHRASES` array or grouped via `PHRASE_CATEGORIES` for category-aware UI |
|
|
162
169
|
|
|
163
170
|
## Supported import paths
|
|
164
171
|
|
|
165
172
|
`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
173
|
|
|
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
|
|
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 |
|
|
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.
|
|
174
|
+
| Path | Runtime | What it exposes |
|
|
175
|
+
| ------------------------------------ | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
176
|
+
| `sidekick-shared` | Node (CLI, extension host) | Full public API (readers, providers, parsers, pricing, …). |
|
|
177
|
+
| `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 helpers: spawns (`isSubagentSpawnTool()`, `isCodexSubagentSpawnTool()`, `parseCodexSpawnLaunch()`), task notifications (`parseTaskNotification()`, `taskNotificationFromEvent()`, `isTaskNotificationSystemEvent()`), settling records (`parseAgentsStoppedNotice()`, `agentsStoppedNoticeFromEvent()`, `isAgentsKilledSystemEvent()`, `taskStatusFromAttachment()`, `taskStatusFromEvent()`, `isTaskStatusSystemEvent()`), Codex subagent activity (`subagentActivityFromEvent()`, `isSubagentActivitySystemEvent()`, `describeSubagentActivity()`, `parseCodexAgentMessageText()`), and display state (`resolveSubagentDisplayState()`, `subagentDisplayLabel()`), `ClaudeUsageDeduper`, and the quota window helpers (`quotaWindowApplies()`, `quotaWindowHasData()`, `quotaWindowLabel()`, `quotaWindowShortLabel()`, `extraQuotaRows()`, `formatExtraUsage()`, `claudePlanLabel()`). |
|
|
178
|
+
| `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()`, `walkRolloutFiles()`), compressed Codex rollouts (`readRolloutText()`, `readRolloutBuffer()`, `resolveRolloutPath()`, `isCompressedRolloutPath()`, `isZstdDecompressionAvailable()`, `COMPRESSED_ROLLOUT_SUFFIX`), the Codex subagent-activity readers (`subagentActivityFromEvent()`, `isSubagentActivitySystemEvent()`, `describeSubagentActivity()`, `parseCodexAgentMessageText()`), 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`). |
|
|
179
|
+
| `sidekick-shared/phrases` | Any runtime | Phrase arrays + `getRandomPhrase()`. |
|
|
180
|
+
| `sidekick-shared/modelContext` | Any runtime | Direct access to the context-window module. |
|
|
181
|
+
| `sidekick-shared/modelInfo` | Any runtime | Direct access to model parsing and cost math. |
|
|
182
|
+
| `sidekick-shared/formatting` | Any runtime | Direct access to pure cost, token, and duration display helpers (`formatCost()`, `formatTokenCount()`, `formatDurationMs()`). |
|
|
183
|
+
| `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. |
|
|
184
|
+
| `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()`. |
|
|
185
|
+
| `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. |
|
|
186
|
+
| `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()`. |
|
|
187
|
+
| `sidekick-shared/codex` | Node only | 14 modules, no Zod: Codex homes and the child-process env (`getSystemCodexHome()`, `resolveSidekickCodexHome()`, `getCodexExecutionEnv()`), `auth.json` helpers (`parseCodexAuthJson()`, `codexRefreshFingerprint()`, `compareCodexAuthFreshness()`), `getCodexCredentialStoreMode()` / `forceFileCredentialStore()`, `findCodexRolloutFile()` (with `includeCompressed`), `codexRolloutOrigin()`, `parseCodexLoginStatus()` / `parseClaudeAuthStatus()`, and `atomicWriteJson()`. |
|
|
188
|
+
| `sidekick-shared/providerFailure` | Any runtime | Pure, 1 module: `diagnoseProviderFailure()`, `decodeCodexErrorInfo()`, and the identifier lists. |
|
|
189
|
+
| `sidekick-shared/statusline/payload` | Any runtime | Pure, 2 modules: `parseClaudeStatuslinePayload()` / `quotaFromStatuslinePayload()` without the account, credential, or snapshot code the full `statusline` entry loads. |
|
|
183
190
|
|
|
184
191
|
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.
|
|
185
192
|
|
|
@@ -362,7 +369,7 @@ const transcript = readSessionTranscript(provider, recent.sessionPath, {
|
|
|
362
369
|
});
|
|
363
370
|
```
|
|
364
371
|
|
|
365
|
-
The same API works with `ClaudeCodeProvider` and `OpenCodeProvider`. It covers provider sessions discoverable by those readers. Remote Claude SDK-only history and Codex history entries whose rollout file has disappeared are not synthesized.
|
|
372
|
+
The same API works with `ClaudeCodeProvider` and `OpenCodeProvider`. It covers provider sessions discoverable by those readers. Remote Claude SDK-only history and Codex history entries whose rollout file has disappeared are not synthesized. A rollout Codex compressed to `.jsonl.zst` is read only with `includeCompressedRollouts` (see [Read compressed Codex rollouts](#read-compressed-codex-rollouts)).
|
|
366
373
|
|
|
367
374
|
Canonical transcript messages retain provider-origin facts on `message.source`, including the original role, entrypoint, meta/sidechain flags, and source identifiers. Transcript-level `cwd` and `gitBranch` use the latest non-empty value reported by the provider. Claude Code messages also carry `isCompactSummary`, `originKind` (`origin.kind`), and `promptSource`. Consumers can therefore pick out the prompts a person typed without reparsing JSONL, using the same rule `collectPromptHistory()` applies:
|
|
368
375
|
|
|
@@ -484,7 +491,7 @@ if (result.unread.length > 0) {
|
|
|
484
491
|
- `interrupt`: Claude `[Request interrupted by user…]`, or Codex `turn_aborted`.
|
|
485
492
|
- `toolRejected`: Claude only. Carries the rejection reason the person typed, in full. A rejection without a reason and the interrupt Claude Code writes after it count as one signal.
|
|
486
493
|
- `toolError`: a failed tool call, with the tool name and the error text cut to 1024 characters (`textTruncated`).
|
|
487
|
-
- Signals that name a tool also carry `toolCategory` (`shell`, `file_read`, `file_edit`, `search`, `web`, `subagent`, `planning`, `mcp`, or `other`, from `classifyTool()`) and, for MCP calls, `mcpServer`. Claude Code names MCP tools `mcp__<server>__<tool>`; Codex names the bare tool and records the server separately, so `mcpServer` is the only way to tell a Codex MCP tool from a built-in one. `classifyTool(name, { mcpServer })` is exported from the root, `/browser`, and `/node`.
|
|
494
|
+
- Signals that name a tool also carry `toolCategory` (`shell`, `file_read`, `file_edit`, `search`, `web`, `subagent`, `planning`, `mcp`, or `other`, from `classifyTool()`) and, for MCP calls, `mcpServer`. Claude Code names MCP tools `mcp__<server>__<tool>`; Codex names the bare tool and records the server separately, so `mcpServer` is the only way to tell a Codex MCP tool from a built-in one. `classifyTool(name, { mcpServer })` is exported from the root, `/browser`, and `/node`. Since 0.30.0 Codex's bare `wait` (a code-mode cell wait; the subagent wait is `wait_agent`) is `other`, and `interrupt_agent` is `subagent`.
|
|
488
495
|
- `compaction`, `apiError` (including Codex usage-limit errors), and `rollback` (Codex `thread_rolled_back`).
|
|
489
496
|
- **Scope fails closed.** Sessions with no prompt inside the roots are omitted. Replies and signals that follow an out-of-scope prompt are dropped.
|
|
490
497
|
|
|
@@ -540,6 +547,35 @@ To resolve one session id to its file without scanning, every bundled provider i
|
|
|
540
547
|
|
|
541
548
|
MCP tool identifiers (`mcp__<server>__<tool>`) split with `parseMcpToolName(name)` — first-double-underscore rule, `null` for malformed names, safe in browser bundles.
|
|
542
549
|
|
|
550
|
+
### Read compressed Codex rollouts
|
|
551
|
+
|
|
552
|
+
Codex compresses older rollouts in place to `rollout-<ts>-<id>.jsonl.zst`, and its state database keeps the `.jsonl` path. Since 0.30.0 they are read when you opt in:
|
|
553
|
+
|
|
554
|
+
```typescript
|
|
555
|
+
import {
|
|
556
|
+
createSessionProviders,
|
|
557
|
+
isZstdDecompressionAvailable,
|
|
558
|
+
readRolloutText,
|
|
559
|
+
resolveRolloutPath,
|
|
560
|
+
} from 'sidekick-shared/node';
|
|
561
|
+
|
|
562
|
+
// Or new CodexProvider({ includeCompressedRollouts: true }) from the package root.
|
|
563
|
+
const { providers } = createSessionProviders({
|
|
564
|
+
includeCompressedRollouts: true,
|
|
565
|
+
onDiagnostic: (diagnostic) => log(diagnostic), // reports .jsonl.zst rollouts it had to skip
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
isZstdDecompressionAvailable(); // zlib.zstdDecompressSync exists (Node 22.15+)
|
|
569
|
+
// A path as the Codex state database stores it: still `.jsonl` after compression.
|
|
570
|
+
const current = resolveRolloutPath(storedPath, { includeCompressed: true }); // .jsonl, else .jsonl.zst, else null
|
|
571
|
+
const text = current ? readRolloutText(current) : null; // either form, whole; null when unreadable
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
- **Coverage.** A Codex provider with the option includes compressed rollouts in listings, session lookups, labels, search, `scanSubagents()` stats, and `createReader()`. That reader reads a `.jsonl.zst` whole: Codex compresses only rollouts it no longer writes, so live tailing stays `.jsonl`-only. `collectUsageEvents()` reads them through such a provider. `collectPromptHistory()`, `collectSessionPromptHistory()`, and `findToolCallOrigins()` take their own `includeCompressedRollouts`; cursor offsets and sizes are then those of the decompressed content.
|
|
575
|
+
- **Older runtimes.** Without `zlib.zstdDecompressSync` (Node 20, older VS Code Electron builds) compressed rollouts are skipped and reported once per process as an info-severity `read_failed` diagnostic. A corrupt frame is a warning-severity `read_failed`. The option defaults to off, so session and history counts do not change.
|
|
576
|
+
- **No duplicates.** A `.jsonl` is preferred over its `.zst` twin. `walkRolloutFiles(dirs, { includeCompressed })` skips a `.jsonl.zst` listed beside its `.jsonl`, and `findCodexRolloutFile(id, { includeCompressed })` matches a compressed rollout only when no `.jsonl` for the id exists. Do not share one walk `cache` between calls that differ in `includeCompressed`.
|
|
577
|
+
- **Helpers.** `readRolloutBuffer()` is the `Buffer` form of `readRolloutText()`, and both take `{ onDiagnostic }`. `isCompressedRolloutPath(path)` tests for `COMPRESSED_ROLLOUT_SUFFIX` (`.jsonl.zst`). All are on the root and `/node`.
|
|
578
|
+
|
|
543
579
|
### Collect observed sessions with isolation and retry
|
|
544
580
|
|
|
545
581
|
```typescript
|
|
@@ -1010,22 +1046,70 @@ console.log(projection.subagents); // Claude Code Agent / Task spawns, prompt te
|
|
|
1010
1046
|
|
|
1011
1047
|
### Follow subagents and background agents
|
|
1012
1048
|
|
|
1013
|
-
Claude Code spawns subagents with its `Agent` tool (`Task` is the earlier name, and OpenCode's `task` normalizes to it). `
|
|
1049
|
+
Claude Code spawns subagents with its `Agent` tool (`Task` is the earlier name, and OpenCode's `task` normalizes to it), and Codex with `spawn_agent` (parsed as `Spawn_agent`). `SUBAGENT_SPAWN_TOOL_NAMES` lists the Claude Code names. `isCodexSubagentSpawnTool(name, input?)` recognizes Codex's spawn by name or by the raw tool name the Codex parser keeps in the input, and since 0.30.0 `isSubagentSpawnTool(name, input?)` matches both kinds. `EventAggregator` and the trace scanner use `isSubagentSpawnTool()`. `formatToolSummary()` and `isAssistantTurnSubagentTool()` know only `Agent` and `Task`, so Codex assistant turns project as before.
|
|
1014
1050
|
|
|
1015
1051
|
```typescript
|
|
1016
|
-
import {
|
|
1052
|
+
import {
|
|
1053
|
+
resolveSubagentDisplayState,
|
|
1054
|
+
scanSubagentTraces,
|
|
1055
|
+
subagentDisplayLabel,
|
|
1056
|
+
} from 'sidekick-shared';
|
|
1017
1057
|
|
|
1018
|
-
for (const agent of aggregator.getMetrics().subagents) {
|
|
1019
|
-
// { id, description, subagentType, spawnTime, status, completionTime?, durationMs?, agentId?, background?, outcome? }
|
|
1020
|
-
}
|
|
1021
1058
|
const traces = scanSubagentTraces(sessionDir, sessionId); // top-level traces; children nested
|
|
1022
1059
|
// { agentId, agentType?, description?, filePath?, spawnToolUseId?, parentToolUseId?, parentAgentId?,
|
|
1023
|
-
// background?, finished?, stoppedByUser?, events, children, stats }
|
|
1060
|
+
// background?, finished?, stoppedByUser?, lastActivityAt?, endedCleanly?, events, children, stats }
|
|
1061
|
+
for (const agent of aggregator.getMetrics().subagents) {
|
|
1062
|
+
// { id, description, subagentType, spawnTime, status, completionTime?, durationMs?, agentId?,
|
|
1063
|
+
// agentPath?, background?, outcome?, settledBy? }
|
|
1064
|
+
const trace = traces.find((t) => t.agentId === agent.agentId);
|
|
1065
|
+
const display = resolveSubagentDisplayState(agent, trace, {
|
|
1066
|
+
now: Date.now(),
|
|
1067
|
+
parentLastActivity: aggregator.getLastEventTime(),
|
|
1068
|
+
});
|
|
1069
|
+
console.log(agent.description, subagentDisplayLabel(display.state)); // e.g. 'finished (no report)'
|
|
1070
|
+
}
|
|
1024
1071
|
```
|
|
1025
1072
|
|
|
1026
1073
|
- **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
|
|
1074
|
+
- **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' }`. Since 0.30.0 the option also turns a background agent's final `task_status` attachment (written after a compaction) into a `system` event with `origin: { kind: 'task-status' }`. A plain-text stop notice arrives as a task-notification event, which `agentsStoppedNoticeFromEvent()` reads. The Claude Code watcher now also emits stop notices, final `task_status` attachments, and `agents_killed` lines. 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 the task-notification and task-status events. `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
1075
|
- **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`.
|
|
1076
|
+
- **Codex subagents.** Since 0.30.0 `EventAggregator` tracks `spawn_agent` calls. The description comes from the input's `description` or `task_name`, and the type from `agent_type`, else `default`. A `{"task_name": "/root/x"}` result is a background launch (`parseCodexSpawnLaunch()` returns `{ agentPath }`), and an error result such as `collab spawn failed: …` ends the agent as `failed`. The child's `started` record sets `agentId` (the child thread id, which is its rollout's session id) and `agentPath`. A `completed` record (matched by thread id, else agent path) or a `FINAL_ANSWER` report (matched by agent path) completes it, `interrupt_agent` settles it as `stopped`, and `followup_task` puts a settled child back to `running` (`send_message` does not). These records reach the aggregator only as subagent-activity events, so feed it events from the Codex reader or from a parser built with `includeSubagentActivity`.
|
|
1077
|
+
- **Codex activity events.** `new CodexRolloutParser({ includeSubagentActivity: true })` turns `SubAgentActivity` items and `FINAL_ANSWER` agent messages into `system` events with `origin: { kind: 'subagent-activity' }` (`SUBAGENT_ACTIVITY_ORIGIN_KIND`), each carrying one `subagent_activity` content block. `subagentActivityFromEvent(event)` returns `{ kind, callId?, tool?, turnId?, agentThreadId?, agentPath?, recipient?, model?, reasoningEffort?, parentThreadId?, parentTurnId?, text? }` or `null`. `kind` is one of:
|
|
1078
|
+
- `started`: a spawn started the child.
|
|
1079
|
+
- `completed`: one child turn finished (Codex 0.150+).
|
|
1080
|
+
- `interacted`: `followup_task` or `send_message`, named in `tool`.
|
|
1081
|
+
- `interrupted`: `interrupt_agent`.
|
|
1082
|
+
- `final-answer`: the child's report, in `text` (empty with `metadataOnly`).
|
|
1083
|
+
|
|
1084
|
+
`isSubagentActivitySystemEvent()` recognizes the event, `describeSubagentActivity()` returns a content-free line such as `Subagent /root/review started`, and `parseCodexAgentMessageText(text)` splits an inter-agent message into `{ messageType, taskName?, sender?, payload }`. The option is off by default, and the Codex provider's readers turn it on. Transcripts, context sources, and the aggregator's counters skip these events. All of these are on the root, `/browser`, and `/node`.
|
|
1085
|
+
|
|
1086
|
+
- **Codex discovery.** `CodexProvider.scanSubagents(sessionDir, sessionId)` returns every spawned thread below the session: the state database's spawn tree (up to 8 levels) plus older `forked_from_id` forks, or, without the database, the 50 newest rollouts linked by their `session_meta`. Each `SubagentStats` carries:
|
|
1087
|
+
- `filePath`: the child's rollout.
|
|
1088
|
+
- `spawnToolUseId`: the parent's `spawn_agent` call id, from its `started` record or, on older Codex, from the spawn whose output named the child's agent path.
|
|
1089
|
+
- `agentNickname` and `agentPath`.
|
|
1090
|
+
- `parentAgentId`: the session id for its own children.
|
|
1091
|
+
- `depth`: 1 for the session's own children.
|
|
1092
|
+
- `endedCleanly`: its last own turn ended with `task_complete`.
|
|
1093
|
+
|
|
1094
|
+
Tool calls, tokens, and times come from the child's own records. A forked child's rollout embeds the parent's history before `subagent_history_start_ordinal`, and that prefix is no longer counted. `collectSessionTokenTotals()` therefore covers every depth for Codex. Claude Code subagent stats and traces carry `endedCleanly` too: the last assistant turn stopped with `end_turn` or `stop_sequence` and left no tool call unanswered, and a later prompt clears it. `CodexDatabase.getSpawnedDescendantThreads(rootId, maxDepth?)` and `isCodexSubagentThread(thread)` are on the root, and Codex active-session detection now skips subagent threads.
|
|
1095
|
+
|
|
1096
|
+
- **Agents that never report.** Since 0.30.0 these records settle a lifecycle that is still `running`; an agent already settled keeps its outcome. `settledBy` records what settled it:
|
|
1097
|
+
- `notification`: a final task notification, or a Codex `completed` record or `FINAL_ANSWER`.
|
|
1098
|
+
- `meta`: host evidence (see the next paragraph).
|
|
1099
|
+
- `stop-marker`: an `agents_killed` line (every running background agent), a stop notice (the running agents with the quoted descriptions), or Codex `interrupt_agent`.
|
|
1100
|
+
- `task-status`: a final `task_status` attachment, matched by agent id.
|
|
1101
|
+
|
|
1102
|
+
`settledBy` is absent while the agent runs and when its spawn's own result settled it. The parsers behind these are exported: `parseAgentsStoppedNotice(text)` returns `{ descriptions }` for `2 background agents were stopped by the user: "A", "B".` or `Background agent "A" was stopped by the user.`; `agentsStoppedNoticeFromEvent(event)` reads one from a task-notification event; `isAgentsKilledSystemEvent(eventOrRawLine)`; `taskStatusFromAttachment(rawLine)` and `taskStatusFromEvent(event)` return `{ taskId, taskType, status, description? }` and ignore background shell tasks; and `isTaskStatusSystemEvent(event)`.
|
|
1103
|
+
|
|
1104
|
+
`aggregator.applySubagentEvidence({ agentId?, toolUseId?, finished?, stoppedByUser? }, timestamp)` applies what a host read from Claude Code's `agent-<id>.meta.json` (`readAgentMeta()`, or a trace's `finished` / `stoppedByUser`). `stoppedByUser` settles a running agent as `stopped`, `finished` settles it as `completed`, and `finished: false` reopens an agent that only meta settled. It matches by `toolUseId`, then by the newest agent with that `agentId`, records the agent id, uses `timestamp` as the completion time, and returns whether a lifecycle changed. `getLastEventTime()` returns `getMetrics().lastEventTime` without building the metrics.
|
|
1105
|
+
|
|
1106
|
+
- **Display state.** `resolveSubagentDisplayState(lifecycle, trace, { now, parentLastActivity })` decides how to show an agent without changing its lifecycle. A settled lifecycle shows its outcome (`reason: 'settled'`). A running one shows:
|
|
1107
|
+
- `stopped` or `completed` when the trace's meta flags say so (`meta`).
|
|
1108
|
+
- `finished-unreported` when the transcript ended cleanly and has been quiet for `SUBAGENT_UNREPORTED_QUIET_MS` (2 minutes; `clean-end`).
|
|
1109
|
+
- `ended` when neither the parent nor the transcript has been active for `SUBAGENT_PARENT_IDLE_ENDED_MS` (6 hours; `parent-idle`; needs `parentLastActivity`).
|
|
1110
|
+
- `running` otherwise.
|
|
1111
|
+
|
|
1112
|
+
Silence alone never gives `finished-unreported`, and the state is recomputed on every call, so an agent that resumes reads as `running` again. For Codex, pass a `scanSubagents()` entry as `{ endedCleanly: stats.endedCleanly, lastActivityAt: stats.endTime }`, matched to the lifecycle by `spawnToolUseId` (the lifecycle's `id`) or `agentId`. `subagentDisplayLabel(state)` returns `running`, `finished (no report)`, `ended`, `completed`, `failed`, `stopped`, or `killed`. The two helpers and the two constants are on the root and `/browser`.
|
|
1029
1113
|
|
|
1030
1114
|
### Format shared dashboard values
|
|
1031
1115
|
|
|
@@ -1049,7 +1133,7 @@ const parser = new JsonlParser(
|
|
|
1049
1133
|
parser.processChunk(rawData);
|
|
1050
1134
|
```
|
|
1051
1135
|
|
|
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`
|
|
1136
|
+
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 instead of rejecting the line (in 0.29.1 that included Claude Code's `auto`, while `permissionModeSchema` and `PermissionMode` were unchanged). Since 0.30.0 `PermissionMode` and `permissionModeSchema` are `default`, `acceptEdits`, `bypassPermissions`, `plan`, `auto`, and `dontAsk`. Modes added later are still dropped, and `EventAggregator` now tracks the mode from followed events as well. System lines keep the provider's `subtype` (`agents_killed`, `turn_duration`, …) on `SystemSessionEvent.subtype`.
|
|
1053
1137
|
|
|
1054
1138
|
### Tail raw JSONL events incrementally
|
|
1055
1139
|
|
|
@@ -1087,6 +1171,33 @@ Also since 0.29.1, a position alone does not resume every reader correctly: Clau
|
|
|
1087
1171
|
|
|
1088
1172
|
`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.
|
|
1089
1173
|
|
|
1174
|
+
#### Reader restarts
|
|
1175
|
+
|
|
1176
|
+
Since 0.30.0 the Claude Code and Codex readers check on every `readNew()` that the open file is still the one their offsets refer to, using the rules `createJsonlTail()` applies, and start over from offset 0 when it is not:
|
|
1177
|
+
|
|
1178
|
+
- `replaced`: another file has the path (device and inode, read from the open descriptor).
|
|
1179
|
+
- `truncated`: the file is shorter than the read position.
|
|
1180
|
+
- `rewritten`: the mtime changed, and either the first 4 KiB differ from what was read or the byte before the resume point is no longer a newline.
|
|
1181
|
+
|
|
1182
|
+
`wasTruncated()` is now true after any of the three; before 0.30.0 the built-in readers reported only a file that shrank. The optional `lastReset()` returns a `SessionReaderReset`, `{ reason, previousPosition }` (the committed position before the restart), or `null`. The events that `readNew()` returned are then the session from the beginning, so check after reading and discard what you built before processing them:
|
|
1183
|
+
|
|
1184
|
+
```typescript
|
|
1185
|
+
const events = reader.readNew();
|
|
1186
|
+
if (reader.wasTruncated()) {
|
|
1187
|
+
console.warn('session restarted', reader.lastReset?.()); // { reason: 'rewritten', previousPosition }
|
|
1188
|
+
aggregator.reset();
|
|
1189
|
+
}
|
|
1190
|
+
for (const event of events) aggregator.processEvent(event);
|
|
1191
|
+
```
|
|
1192
|
+
|
|
1193
|
+
`SessionMonitor.poll()` and the reader-backed watcher that `createWatcher()` builds for Codex now do exactly this; the watcher calls `onReset()` before delivering the replay. Before 0.30.0 both checked before reading and counted a truncated file's events twice. `lastReset()` is optional: the Claude Code and Codex readers implement it, and OpenCode's does not.
|
|
1194
|
+
|
|
1195
|
+
Known limits:
|
|
1196
|
+
|
|
1197
|
+
- A rewrite that keeps the mtime (coarse mtime resolution, or an mtime restored on purpose), or that changes only bytes past the first 4 KiB while the resume point still follows a newline, goes unnoticed.
|
|
1198
|
+
- On filesystems without stable inode numbers (some SMB mounts, Windows) an unchanged file can read as `replaced`, which costs a replay.
|
|
1199
|
+
- After `seekTo()` a reader adopts whatever file it finds on its next read, so only a file now shorter than the position is caught. Check a stored position with `verifyJsonlCheckpoint()` first.
|
|
1200
|
+
|
|
1090
1201
|
### Search a workspace and diagnose its provider
|
|
1091
1202
|
|
|
1092
1203
|
```typescript
|