sidekick-shared 0.27.5 → 0.28.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 +58 -0
- package/README.md +93 -52
- package/dist/accountHealth.d.ts +14 -0
- package/dist/accountHealth.js +61 -14
- package/dist/accountLaunch.d.ts +6 -0
- package/dist/accountLaunch.js +61 -4
- package/dist/accountLease.d.ts +5 -0
- package/dist/accountLease.js +98 -0
- package/dist/accountManager.d.ts +7 -0
- package/dist/accountManager.js +226 -89
- package/dist/accountSync.d.ts +1 -5
- package/dist/accountSync.js +22 -6
- package/dist/accountSyncTypes.d.ts +9 -0
- package/dist/accounts.d.ts +75 -18
- package/dist/accounts.js +712 -189
- package/dist/browser.d.ts +13 -2
- package/dist/browser.js +30 -2
- package/dist/claudeCredentialDoctor.d.ts +50 -0
- package/dist/claudeCredentialDoctor.js +277 -0
- package/dist/claudeCredentialLedger.d.ts +15 -0
- package/dist/claudeCredentialLedger.js +130 -0
- package/dist/claudeCredentials.d.ts +20 -3
- package/dist/claudeCredentials.js +50 -3
- package/dist/claudeLiveGuard.d.ts +33 -0
- package/dist/claudeLiveGuard.js +46 -0
- package/dist/claudePaths.d.ts +11 -0
- package/dist/claudePaths.js +61 -0
- package/dist/claudeProfiles.d.ts +15 -8
- package/dist/claudeProfiles.js +30 -23
- package/dist/claudeRefreshLock.d.ts +15 -0
- package/dist/claudeRefreshLock.js +145 -0
- package/dist/codexAuth.d.ts +4 -0
- package/dist/codexAuth.js +18 -0
- package/dist/codexCredentialDoctor.d.ts +33 -0
- package/dist/codexCredentialDoctor.js +302 -0
- package/dist/codexCredentialStore.d.ts +29 -0
- package/dist/codexCredentialStore.js +213 -0
- package/dist/codexKeepAlive.d.ts +2 -0
- package/dist/codexKeepAlive.js +116 -0
- package/dist/codexPaths.d.ts +30 -2
- package/dist/codexPaths.js +118 -7
- package/dist/codexProfiles.d.ts +38 -35
- package/dist/codexProfiles.js +848 -715
- package/dist/credentialIO.d.ts +67 -7
- package/dist/credentialIO.js +211 -47
- package/dist/doctor.js +2 -2
- package/dist/ensureDefaultAccounts.js +1 -8
- package/dist/esm/accountChangeSignal.js +15 -0
- package/dist/esm/accountChanges.js +152 -0
- package/dist/esm/accountHealth.js +288 -0
- package/dist/esm/accountLaunch.js +145 -0
- package/dist/esm/accountLease.js +60 -0
- package/dist/esm/accountManager.js +606 -0
- package/dist/esm/accountRegistry.js +214 -0
- package/dist/esm/accountStatus.js +51 -0
- package/dist/esm/accountSwitch.js +74 -0
- package/dist/esm/accountSync.js +156 -0
- package/dist/esm/accountSyncTypes.js +4 -0
- package/dist/esm/accounts.js +1291 -0
- package/dist/esm/aggregation/EventAggregator.js +2034 -0
- package/dist/esm/aggregation/FrequencyTracker.js +69 -0
- package/dist/esm/aggregation/HeatmapTracker.js +89 -0
- package/dist/esm/aggregation/PatternExtractor.js +168 -0
- package/dist/esm/aggregation/costProvenance.js +31 -0
- package/dist/esm/aggregation/snapshot.js +161 -0
- package/dist/esm/aggregation/types.js +5 -0
- package/dist/esm/analytics/codeImpact.js +18 -0
- package/dist/esm/analytics/compactionLedger.js +37 -0
- package/dist/esm/analytics/qualityScore.js +112 -0
- package/dist/esm/autoSwitch.js +185 -0
- package/dist/esm/browser.js +34 -0
- package/dist/esm/claudeCredentialDoctor.js +239 -0
- package/dist/esm/claudeCredentialLedger.js +91 -0
- package/dist/esm/claudeCredentials.js +111 -0
- package/dist/esm/claudeLiveGuard.js +43 -0
- package/dist/esm/claudePaths.js +22 -0
- package/dist/esm/claudeProfiles.js +142 -0
- package/dist/esm/claudeRefreshLock.js +106 -0
- package/dist/esm/codexAuth.js +101 -0
- package/dist/esm/codexCredentialDoctor.js +265 -0
- package/dist/esm/codexCredentialStore.js +164 -0
- package/dist/esm/codexKeepAlive.js +112 -0
- package/dist/esm/codexPaths.js +172 -0
- package/dist/esm/codexProfiles.js +1251 -0
- package/dist/esm/codexQuota.js +564 -0
- package/dist/esm/codexQuotaWatcher.js +363 -0
- package/dist/esm/context/composer.js +71 -0
- package/dist/esm/context/sessionContext.js +546 -0
- package/dist/esm/credentialIO.js +283 -0
- package/dist/esm/credentials.js +49 -0
- package/dist/esm/doctor.js +305 -0
- package/dist/esm/ensureDefaultAccounts.js +62 -0
- package/dist/esm/errorHistory.js +56 -0
- package/dist/esm/extractors/errorTaxonomy.js +43 -0
- package/dist/esm/extractors/gatherAssets.js +32 -0
- package/dist/esm/extractors/sessionAssets.js +176 -0
- package/dist/esm/extractors/sources/claudeAssets.js +158 -0
- package/dist/esm/extractors/sources/codexAssets.js +234 -0
- package/dist/esm/extractors/tokenUsage.js +41 -0
- package/dist/esm/extractors/toolCall.js +142 -0
- package/dist/esm/failingTools.js +65 -0
- package/dist/esm/fileHead.js +27 -0
- package/dist/esm/formatters/eventHighlighter.js +332 -0
- package/dist/esm/formatters/noiseClassifier.js +209 -0
- package/dist/esm/formatters/sessionDump.js +328 -0
- package/dist/esm/formatters/toolSummary.js +226 -0
- package/dist/esm/formatting.js +82 -0
- package/dist/esm/handoffUrl.js +23 -0
- package/dist/esm/historicalStore.js +305 -0
- package/dist/esm/humanPrompt.js +88 -0
- package/dist/esm/index.js +213 -0
- package/dist/esm/keepAlive.js +189 -0
- package/dist/esm/liveCredentialGuard.js +33 -0
- package/dist/esm/modelAliases.js +51 -0
- package/dist/esm/modelCatalog.js +139 -0
- package/dist/esm/modelContext.js +280 -0
- package/dist/esm/modelInfo.js +832 -0
- package/dist/esm/multiProviderQuotaService.js +450 -0
- package/dist/esm/node.js +46 -0
- package/dist/esm/observedContextWindows.js +165 -0
- package/dist/esm/observedSessionCollector.js +939 -0
- package/dist/esm/parsers/changelogParser.js +71 -0
- package/dist/esm/parsers/codexHistory.js +89 -0
- package/dist/esm/parsers/codexParser.js +906 -0
- package/dist/esm/parsers/debugLogParser.js +126 -0
- package/dist/esm/parsers/jsonl.js +66 -0
- package/dist/esm/parsers/mcpToolName.js +28 -0
- package/dist/esm/parsers/openCodeParser.js +625 -0
- package/dist/esm/parsers/planExtractor.js +335 -0
- package/dist/esm/parsers/sessionActivityDetector.js +246 -0
- package/dist/esm/parsers/sessionPathResolver.js +713 -0
- package/dist/esm/parsers/subagentScanner.js +360 -0
- package/dist/esm/parsers/subagentStatsCache.js +45 -0
- package/dist/esm/parsers/subagentTraceParser.js +325 -0
- package/dist/esm/paths.js +134 -0
- package/dist/esm/peakHours.js +194 -0
- package/dist/esm/phrases.js +1330 -0
- package/dist/esm/pricingCatalog.js +247 -0
- package/dist/esm/processDetection.js +200 -0
- package/dist/esm/projectMigration.js +59 -0
- package/dist/esm/promptHistory.js +1221 -0
- package/dist/esm/providerFailure.js +362 -0
- package/dist/esm/providerIds.js +6 -0
- package/dist/esm/providerQuota.js +1 -0
- package/dist/esm/providerServiceStatus.js +145 -0
- package/dist/esm/providerServiceStatusTypes.js +1 -0
- package/dist/esm/providerStatus.js +117 -0
- package/dist/esm/providers/claudeCode.js +575 -0
- package/dist/esm/providers/claudeListing.js +121 -0
- package/dist/esm/providers/codex.js +1034 -0
- package/dist/esm/providers/codexDatabase.js +297 -0
- package/dist/esm/providers/codexRollouts.js +215 -0
- package/dist/esm/providers/detect.js +185 -0
- package/dist/esm/providers/diagnostics.js +83 -0
- package/dist/esm/providers/directoryListingCache.js +206 -0
- package/dist/esm/providers/factory.js +54 -0
- package/dist/esm/providers/openCode.js +2008 -0
- package/dist/esm/providers/openCodeDatabase.js +419 -0
- package/dist/esm/providers/rolloutWalker.js +252 -0
- package/dist/esm/providers/types.js +12 -0
- package/dist/esm/quota.js +182 -0
- package/dist/esm/quotaHistory.js +364 -0
- package/dist/esm/quotaPoller.js +230 -0
- package/dist/esm/quotaPresentation.js +97 -0
- package/dist/esm/quotaResolver.js +231 -0
- package/dist/esm/quotaSnapshots.js +130 -0
- package/dist/esm/quotaThresholds.js +85 -0
- package/dist/esm/readers/decisions.js +24 -0
- package/dist/esm/readers/handoff.js +20 -0
- package/dist/esm/readers/helpers.js +27 -0
- package/dist/esm/readers/history.js +9 -0
- package/dist/esm/readers/notes.js +48 -0
- package/dist/esm/readers/plans.js +225 -0
- package/dist/esm/readers/tasks.js +19 -0
- package/dist/esm/report/htmlHelpers.js +280 -0
- package/dist/esm/report/htmlReportGenerator.js +704 -0
- package/dist/esm/report/index.js +7 -0
- package/dist/esm/report/logo.js +2 -0
- package/dist/esm/report/openBrowser.js +19 -0
- package/dist/esm/report/sessionReportInputs.js +33 -0
- package/dist/esm/report/transcriptParser.js +83 -0
- package/dist/esm/report/types.js +4 -0
- package/dist/esm/schemas/accountManager.js +204 -0
- package/dist/esm/schemas/accountStatus.js +25 -0
- package/dist/esm/schemas/assistantTurn.js +74 -0
- package/dist/esm/schemas/index.js +25 -0
- package/dist/esm/schemas/observedSessionV1.js +77 -0
- package/dist/esm/schemas/providerFailure.js +37 -0
- package/dist/esm/schemas/quota.js +132 -0
- package/dist/esm/schemas/quotaHistory.js +40 -0
- package/dist/esm/schemas/sessionEvent.js +216 -0
- package/dist/esm/schemas/stateFile.js +75 -0
- package/dist/esm/schemas/tokenEstimation.js +7 -0
- package/dist/esm/schemas/transcript.js +86 -0
- package/dist/esm/schemas/usageNormalization.js +38 -0
- package/dist/esm/search/advancedFilter.js +197 -0
- package/dist/esm/search/sessionSearch.js +66 -0
- package/dist/esm/sessionFingerprint.js +40 -0
- package/dist/esm/sessionMonitor.js +198 -0
- package/dist/esm/sessionPreviews.js +522 -0
- package/dist/esm/sessionPromptHistory.js +254 -0
- package/dist/esm/sessionPromptSignals.js +364 -0
- package/dist/esm/sessionStats.js +160 -0
- package/dist/esm/sessionTokenTotals.js +98 -0
- package/dist/esm/sessionTranscripts.js +49 -0
- package/dist/esm/stateFile.js +119 -0
- package/dist/esm/statusline/BurnRateCalculator.js +40 -0
- package/dist/esm/statusline/claudeStatuslinePayload.js +148 -0
- package/dist/esm/statusline/formatter.js +122 -0
- package/dist/esm/statusline/index.js +8 -0
- package/dist/esm/terminalSync.js +95 -0
- package/dist/esm/testing/credentialGuard.js +197 -0
- package/dist/esm/testing/index.js +12 -0
- package/dist/esm/testing/sessionLogFixtures.js +128 -0
- package/dist/esm/tokenEstimation.js +67 -0
- package/dist/esm/tokenSummary.js +113 -0
- package/dist/esm/toolCategory.js +82 -0
- package/dist/esm/transcript.js +257 -0
- package/dist/esm/turns/assistantTurn.js +373 -0
- package/dist/esm/types/codex.js +23 -0
- package/dist/esm/types/decisionLog.js +5 -0
- package/dist/esm/types/historicalData.js +47 -0
- package/dist/esm/types/knowledgeNote.js +15 -0
- package/dist/esm/types/observedSessionV1.js +257 -0
- package/dist/esm/types/opencode.js +12 -0
- package/dist/esm/types/plan.js +7 -0
- package/dist/esm/types/sessionEvent.js +10 -0
- package/dist/esm/types/taskPersistence.js +12 -0
- package/dist/esm/usage/billingBlocks.js +148 -0
- package/dist/esm/usage/claudeUsageDedupe.js +189 -0
- package/dist/esm/usage/importSessionHistory.js +84 -0
- package/dist/esm/usage/subagentUsage.js +18 -0
- package/dist/esm/usage/usageEvents.js +402 -0
- package/dist/esm/usage/usageReports.js +202 -0
- package/dist/esm/usageNormalization.js +95 -0
- package/dist/esm/watchers/eventBridge.js +219 -0
- package/dist/esm/watchers/factory.js +63 -0
- package/dist/esm/watchers/jsonlTail.js +289 -0
- package/dist/esm/watchers/jsonlWatcher.js +435 -0
- package/dist/esm/watchers/providerReaderWatcher.js +118 -0
- package/dist/esm/watchers/sqliteWatcher.js +285 -0
- package/dist/esm/watchers/types.js +4 -0
- package/dist/esm/workspaceRoots.js +56 -0
- package/dist/esm/writers/atomic.js +375 -0
- package/dist/esm/writers/decisions.js +51 -0
- package/dist/esm/writers/notes.js +46 -0
- package/dist/esm/writers/tasks.js +63 -0
- package/dist/esm/zaiQuota.js +323 -0
- package/dist/esm/zaiQuotaApi.js +271 -0
- package/dist/esm/zaiQuotaWatcher.js +265 -0
- package/dist/fileHead.d.ts +22 -0
- package/dist/fileHead.js +33 -0
- package/dist/index.d.ts +30 -11
- package/dist/index.js +72 -15
- package/dist/keepAlive.d.ts +2 -1
- package/dist/keepAlive.js +116 -29
- package/dist/liveCredentialGuard.d.ts +28 -0
- package/dist/liveCredentialGuard.js +36 -0
- package/dist/node.d.ts +13 -1
- package/dist/node.js +43 -2
- package/dist/parsers/codexHistory.js +2 -2
- package/dist/parsers/codexParser.d.ts +12 -0
- package/dist/parsers/codexParser.js +34 -12
- package/dist/parsers/debugLogParser.js +2 -2
- package/dist/parsers/sessionPathResolver.js +5 -4
- package/dist/processDetection.d.ts +10 -3
- package/dist/processDetection.js +44 -11
- package/dist/promptHistory.d.ts +32 -0
- package/dist/promptHistory.js +115 -59
- package/dist/providerFailure.d.ts +12 -6
- package/dist/providerFailure.js +45 -0
- package/dist/providers/claudeCode.js +7 -99
- package/dist/providers/claudeListing.d.ts +22 -0
- package/dist/providers/claudeListing.js +158 -0
- package/dist/providers/codex.d.ts +3 -1
- package/dist/providers/codex.js +26 -227
- package/dist/providers/codexRollouts.d.ts +44 -0
- package/dist/providers/codexRollouts.js +261 -0
- package/dist/providers/detect.js +4 -4
- package/dist/readers/plans.js +3 -3
- package/dist/schemas/accountManager.d.ts +22 -0
- package/dist/schemas/accountManager.js +4 -0
- package/dist/schemas/assistantTurn.d.ts +2 -2
- package/dist/schemas/index.d.ts +2 -0
- package/dist/schemas/index.js +7 -2
- package/dist/schemas/providerFailure.d.ts +154 -0
- package/dist/schemas/providerFailure.js +40 -0
- package/dist/sessionPromptHistory.d.ts +12 -7
- package/dist/sessionPromptHistory.js +15 -3
- package/dist/sessionPromptSignals.d.ts +9 -0
- package/dist/sessionPromptSignals.js +13 -0
- package/dist/terminalSync.d.ts +12 -2
- package/dist/terminalSync.js +27 -2
- package/dist/testing/credentialGuard.d.ts +23 -0
- package/dist/testing/credentialGuard.js +233 -0
- package/dist/testing/index.d.ts +14 -0
- package/dist/testing/index.js +22 -0
- package/dist/testing/sessionLogFixtures.d.ts +28 -0
- package/dist/testing/sessionLogFixtures.js +169 -0
- package/dist/tokenEstimation.d.ts +2 -0
- package/dist/tokenEstimation.js +21 -11
- package/dist/toolCategory.d.ts +18 -0
- package/dist/toolCategory.js +86 -0
- package/dist/types/codex.d.ts +4 -0
- package/dist/usage/usageEvents.d.ts +16 -0
- package/dist/usage/usageEvents.js +53 -9
- package/dist/watchers/jsonlTail.d.ts +26 -1
- package/dist/watchers/jsonlTail.js +136 -28
- package/dist/watchers/jsonlWatcher.js +7 -0
- package/dist/watchers/providerReaderWatcher.js +1 -0
- package/dist/watchers/types.d.ts +6 -0
- package/dist/workspaceRoots.d.ts +23 -0
- package/dist/workspaceRoots.js +93 -0
- package/package.json +22 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,64 @@ 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.28.0] - 2026-10-18
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Tree-shakable ES modules for bundlers.** Every exports subpath gains a `module` condition pointing at an ES-module build in `dist/esm/` (esbuild, Vite, Rollup, and webpack read it ahead of `import`/`require`). Node never reads `module` and keeps loading CommonJS, so a process cannot hold two copies of module state; `./dist/esm/*` is blocked from the `dist/*` wildcard. One helper from `/node` now bundles in hundreds of bytes instead of about 366 KB.
|
|
13
|
+
- **Narrow-entry exports.** `sidekick-shared/browser`: `CodexRolloutParser` (with `DEFAULT_MAX_TRACKED_TOOL_CALLS` and `CodexRolloutParserOptions`), the Codex tool-name helpers, the session-event and Codex rollout types (including `CompactionEvent` and `ContextAttribution`), `isAggregateCodexLimit()`, `isHumanPrompt()` / `humanPromptText()` and the human entrypoint lists, `renderHandoffUrlTemplate()`, the provider-failure identifier arrays, `ASCII_CHARS_PER_TOKEN`, and `classifyTool()`. `sidekick-shared/node`: `getLiveClaudeHome()`, `getDefaultClaudeHome()`, `isDefaultClaudeHome()`, `getClaudeProjectsDir()`, `getSystemCodexHome()`, `getCodexMonitoringHomes()`, `resolveSidekickCodexHome()`, `getCodexExecutionEnv()`, `getCodexSessionsDirs()`, `getConfigDir()` / `CONFIG_DIR_ENV_VAR`, `encodeClaudeWorkspacePath()` / `getClaudeSessionDirectory()`, `resolveWorkspaceRoots()`, `resolveWorktreeMainRepo()` / `discoverWorktreeSiblings()`, and `createJsonlTail()`.
|
|
14
|
+
- **`sidekick-shared/testing`.** `installCredentialGuard()` keeps a test process away from the real `security` / `claude` / `codex` binaries and the real Claude, Codex, and Sidekick homes (framework-free; Node 20.16+, or CommonJS on older Node; temp home removed on exit; `SIDEKICK_CONFIG_DIR` cleared), plus the Claude Code and Codex session-log builders. The internal vitest setup is now a thin shim over it.
|
|
15
|
+
- **Provider-failure schema.** `PROVIDER_FAILURE_CODES`, `PROVIDER_RECOVERY_ACTIONS`, `PROVIDER_CREDENTIAL_KINDS`, `PROVIDER_AUTHENTICATION_STATES`, `PROVIDER_AUTHENTICATION_SOURCES`, and `PROVIDER_FAILURE_EVIDENCE_SOURCES` define the existing union types, and `providerFailureDiagnosisSchema` (plus the evidence, authentication, and retry-after schemas) on `sidekick-shared/schemas` is built from them.
|
|
16
|
+
- **Prompt-history change reporting.** `collectPromptHistory()` results add `changed`, `progressed`, and `roots`; entries, whole sessions, `unread`, and `changed` carry an opaque `sessionKey`; `collectSessionPromptHistory()` accepts `sessionRefs` matched on (provider, sessionId) or one file; `isPromptHistorySessionSettled()` and `parsePromptHistoryCursor()` are exported.
|
|
17
|
+
- **Tool categories on signals.** `classifyTool(name, { mcpServer })` and `TOOL_CATEGORIES`; prompt-history signals that name a tool carry `toolCategory` and, for MCP calls, `mcpServer` (Codex's `invocation.server` / `server` was dropped before).
|
|
18
|
+
- **Multi-root usage.** `collectUsageEvents({ workspacePaths, includeWorktrees })` lists several roots in one call and counts each session file once; results add `roots`, and events add `filePath`.
|
|
19
|
+
- **Bounded JSONL tail.** `createJsonlTail()` gains `maxReadBytes` (1 MiB chunks), `maxLineBytes` (16 MiB, longer lines skipped and reported), each line's `{ offset, length }` as `onEvent`'s second argument, and `onReset({ reason, previousOffset })` for `truncated`, `rewritten` (first 4 KiB or the byte before the resume point changed), and `replaced` (another inode) files.
|
|
20
|
+
- `SessionWatcherCallbacks.onReset`: the JSONL and provider-reader watchers call it when the session file is truncated, rewritten, or replaced and its events are delivered again from the start.
|
|
21
|
+
- `getCodexSessionsDirs()`, `CODEX_SESSIONS_SUBDIR`, `CODEX_ARCHIVED_SESSIONS_SUBDIR`, and `CodexDbThread.archived` / `archived_at`.
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- **`CLAUDE_CONFIG_DIR`.** Claude session discovery, path resolution, provider detection, doctor, plans, and debug logs hard-coded `~/.claude`; they now use the live Claude home, as every credential and identity read already did.
|
|
26
|
+
- **Archived Codex rollouts.** Rollout lookup, listings, and watch roots include each home's `archived_sessions/`, and a prompt-history cursor entry follows a session whose file moved there (when the new file starts with the recorded head) instead of re-reading it as new. An `archived_sessions/` directory created after a watcher starts is picked up by its catch-up poll.
|
|
27
|
+
- `getCodexExecutionEnv(baseEnv)` keeps a non-blank `CODEX_HOME` from `baseEnv`.
|
|
28
|
+
- `CodexRolloutParser` bounds its emitted-tool, expanded-patch, and pending exec/MCP tables (`maxTrackedToolCalls`, default 4096).
|
|
29
|
+
- The session watcher resets its Claude usage de-duplication and Codex state when its tail starts over.
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
|
|
33
|
+
- `sidekick-shared/node` no longer reaches the account-switching modules: Codex home resolution moved to `codexPaths` (re-exported from `codexProfiles`), and prompt history lists files through standalone `listClaudeSessionFilesAsync()` / `listCodexSessionFilesAsync()` instead of constructing providers (`collectPromptHistory` bundles in 30 KB instead of 170 KB).
|
|
34
|
+
- `estimateTextTokens()` counts a leading ASCII run without regex tests (about 15× faster, identical results).
|
|
35
|
+
|
|
36
|
+
## [0.27.6] - 2026-09-26
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
|
|
40
|
+
- **One usable copy per Claude refresh-token chain.** Claude Code rotates the refresh token on every refresh, so a second usable copy of a login invalidates the first ("Not logged in · Please run /login").
|
|
41
|
+
- **Keychain writes** use Claude Code's method: `-X <hex>` through `security -i` up to 4032 bytes, argv beyond. The old `-w '<json>'` line was silently truncated at 4095 bytes once `mcpOAuth` grew. Every write is read back; a mismatch or an unverifiable read-back throws `ClaudeCredentialWriteError`. Reads and deletes pass the account name like Claude Code, and profile items carry a `sidekick-agent-hub:<dir>` comment.
|
|
42
|
+
- **The live account keeps no profile-home credential.** `storeClaudeProfileCredentials()` is the chokepoint: for the live account it writes only the private backup and the sidecar. `syncLiveAccountState()` removes a leftover copy (`report.claude.demoted`) and installs it first when it is strictly newer than live (`repaired`). It also refuses to fold when the live credential's fingerprint belongs to another profile.
|
|
43
|
+
- **Guarded live writes.** Only `installIntoLiveClaudeStore` writes the live store, after `decideLiveClaudeWrite()`: a switch requires the outgoing login captured (mandatory even with `force`), a re-login requires a strictly fresher credential of the same account, and a rollback never clobbers a store that changed. The installed blob keeps live's `mcpOAuth` and swaps `claudeAiOauth`; profile copies hold `claudeAiOauth` only.
|
|
44
|
+
- **Switch.** Holds Claude Code's refresh locks (`<home>/.oauth_refresh.lock` plus the legacy lock), re-checks the live fingerprint before installing, and writes `accounts/claude/switch-journal.json`. The next sync finishes or rolls back an interrupted switch from the live fingerprint (`report.claude.journal`). Sidecars record `refreshFp`, and read-only probes keep the recorded `source`.
|
|
45
|
+
- **`readStoredClaudeAccount()`** is read-only and returns the fresher of the profile store and the backup. `reconcileClaudeAuthState()` skips the live account.
|
|
46
|
+
- **Keep-alive** skips the live account, a copy with the live refresh token, `live-sync` provenance, a profile without its own copy, and a copy older than its backup. It takes a lease under the swap lock that makes a switch to that account refuse until it is done.
|
|
47
|
+
- **Login homes** are recorded in the ledger before the CLI runs, and `finalizeAccountLogin`, `spawnAccountLogin`, and `cleanupAbandonedClaudeLogins` (authenticated logins after 24 h) delete their Keychain item on every exit.
|
|
48
|
+
- **`getAccountLaunchEnv()`** points the live Claude account at the live home and restores an inactive profile from its backup (skip with `restoreBackup: false`).
|
|
49
|
+
- **Leftover Keychain items.** Running against a throwaway `SIDEKICK_CONFIG_DIR` with the real `HOME` no longer creates a Keychain item for the live login.
|
|
50
|
+
- **Profile-home shells.** When `CLAUDE_CONFIG_DIR` points at a Sidekick profile home (a shell from `sidekick accounts env` or `shell`), `syncLiveAccountState()` reports `claude.skipped: 'managed-home'` as it does for Codex, `applyClaudeProfileToLiveHome()` refuses, and `inspectClaudeCredentialStores()` reports no live duplicate, so that profile's only usable copy is never demoted.
|
|
51
|
+
- **Codex OAuth credential safety:** private byte-preserved backups, shared live-write guard policy, verified outgoing capture, journal recovery, and non-bypassable consumer/lease checks replace duplicate profile credentials and timestamped stashes. Live launches use the live home. Codex keep-alive uses the official app-server refresh request and requires a verified credential advance. Sign-in homes are separate and cleaned up after use. Added `inspectCodexCredentialStores`, `repairCodexCredentialStores`, `readStoredCodexAccount`, `syncCodexLiveStateAsync`, and non-persisting health inspection (`persist: false`). File storage is required, and unrelated MCP data is preserved.
|
|
52
|
+
|
|
53
|
+
### Added
|
|
54
|
+
|
|
55
|
+
- `readClaudeCredentialStore()` (`ok` / `absent` / `corrupt` / `error`), `decideLiveClaudeWrite()`, `claudeRefreshFingerprint()`, `claudeLoginOnly()`, `mergeClaudeLoginIntoLive()`, and `ClaudeCredentialWriteError`.
|
|
56
|
+
- A ledger of credential homes (`accounts/claude/credential-homes.json`, `readClaudeCredentialLedger()`), plus `inspectClaudeCredentialStores()` and `repairClaudeCredentialStores({ dryRun })`: `orphan`, `live-duplicate`, `corrupt`, and `stale-entry` findings. Ownership is proven by the ledger, the comment tag, or the profile directory, never by name.
|
|
57
|
+
- `discardAccountLogin(provider, loginId)` and `writeAccountLauncher(name, provider, accountId)`.
|
|
58
|
+
- `ProviderSyncReport` gains `demoted`, `repaired`, and `journal`, and the sidecar gains `refreshFp`; the schemas are updated.
|
|
59
|
+
|
|
60
|
+
### Changed
|
|
61
|
+
|
|
62
|
+
- `writeActiveCredentials()` throws without a config directory. `deleteStoredCredentials()` returns whether the store is gone.
|
|
63
|
+
- Deprecated: `writeLauncher()`, `applyActiveClaudeToLiveHome()` (now a guarded re-login that refuses another account), and `resolveActiveClaudeHome()` (the live home for the live account).
|
|
64
|
+
- A global test guard (`src/testing/credentialGuard.setup.ts`, also used by the CLI and extension suites) fails any test that reaches the real `security`, `claude`, or `codex` binaries or real homes.
|
|
65
|
+
|
|
8
66
|
## [0.27.5] - 2026-09-23
|
|
9
67
|
|
|
10
68
|
### Added
|
package/README.md
CHANGED
|
@@ -9,6 +9,8 @@ Types, parsers, providers, readers, formatters, aggregation, search, reporting,
|
|
|
9
9
|
|
|
10
10
|
## Recent additions
|
|
11
11
|
|
|
12
|
+
- **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/`.
|
|
13
|
+
- **0.27.6: one usable copy per login** — Claude Code and Codex rotate refresh tokens, so a second usable copy of a login invalidates the first ("Not logged in · Please run /login"). The live account now keeps no profile-home credential, only a private backup; `syncLiveAccountState()` demotes a leftover copy (installing it first when it is newer) and reports `demoted`, `repaired`, and `journal`. Live writes pass `decideLiveClaudeWrite()` or the Codex equivalent: a switch captures the outgoing login first even with `force`, holds Claude Code's refresh lock, is journaled so the next sync finishes or rolls it back, and keeps live MCP tokens. Claude Keychain writes use Claude Code's hex method and are read back (`ClaudeCredentialWriteError`), so blobs over about 4 KB are no longer truncated. New root APIs: `readClaudeCredentialStore()`, `inspectClaudeCredentialStores()` / `repairClaudeCredentialStores()`, `inspectCodexCredentialStores()` / `repairCodexCredentialStores()`, `readClaudeCredentialLedger()`, `readStoredCodexAccount()`, `syncCodexLiveStateAsync()`, `discardAccountLogin()`, `writeAccountLauncher()`, and non-persisting health inspection (`persist: false`). `writeActiveCredentials()` now requires a config directory; `writeLauncher()`, `applyActiveClaudeToLiveHome()`, and `resolveActiveClaudeHome()` are deprecated.
|
|
12
14
|
- **0.27.5: whole-session prompt history** — `collectSessionPromptHistory()` (root and Node) returns every human prompt of each matching Claude Code or Codex session, grouped by session and read from the start with the `collectPromptHistory()` scanners, so text, scope, model and usage, and ordinals are identical. Select with `sessionIds`, `since` (file mtime), or `limit`; each session carries `startedAt` / `lastActivityAt`, `cwds`, `gitBranches`, `models`, and `complete` / `truncated` / `droppedPrompts` / `excludedRecords`. `include.replies` adds the agent's last text for each prompt, and `include.signals` adds `interrupt`, `toolRejected`, `toolError`, `compaction`, `apiError`, and `rollback` signals. Sessions that `maxSessions`, `maxTotalBytes`, the deadline, or an abort cut off are listed in `unread` instead of being returned partially.
|
|
13
15
|
- **0.27.4: usage events from the Node entry** — `sidekick-shared/node` now exports `createSessionProviders()`, `SYNTHETIC_MODEL`, and the `ProviderId` / `SessionProviderBase` / `SessionProviderDiagnostic` / `CreateSessionProviders*` types, so `collectUsageEvents()` callers never import the package root (a CommonJS barrel that bundlers cannot tree-shake). `collectUsageEvents()` accepts an optional `signal` that stops the collection before listing and between sessions.
|
|
14
16
|
- **0.27.3: honest session totals** — Claude Code split lines are counted once: `ClaudeUsageDeduper` keeps a `message.id`'s first usage, strips identical repeats, and turns streamed growth into a `usageKind: 'correction'` delta that `EventAggregator` adds to tokens and cost without counting another call. `resolveCodexCallUsage()` drops duplicate Codex `token_count` events. `collectSessionTokenTotals()` (root and Node) returns `mainThread`, per-`subagents`, `subagentTotal`, and `combined` in the `summarizeTokens()` vocabulary, and `formatTokenBreakdown()` renders one line whose parts add up to the headline. `TokenSummary` gains `reasoning` / `billedOutsideBuckets`, and `SubagentStats` from every provider now carries cache, reasoning, and total tokens. The session dumps and `HtmlReportOptions` accept `subagents`, `readSessionReportInputs()` returns them, `collectUsageEvents()` reads Claude Code subagent transcripts, and `USAGE_CACHE_VERSION` 2 re-reads caches built with the old counting.
|
|
@@ -93,56 +95,75 @@ returns the platform default that both overrides bypass.
|
|
|
93
95
|
|
|
94
96
|
## API Overview
|
|
95
97
|
|
|
96
|
-
| Module | Description
|
|
97
|
-
| --------------------------------- |
|
|
98
|
-
| **Types** | Session events, OpenCode/Codex format types, persistence schemas (tasks, decisions, notes, plans, historical data)
|
|
99
|
-
| **Paths** | Config directory resolution, project data paths, workspace encoding
|
|
100
|
-
| **Readers** | Read tasks, decisions, notes, history, handoff, and plans from `~/.config/sidekick/`
|
|
101
|
-
| **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
|
|
102
|
-
| **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
|
|
103
|
-
| **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`)
|
|
104
|
-
| **Parsers** | JSONL event parsing, OpenCode/Codex format normalization, subagent scanning, session path resolution, debug log parsing
|
|
105
|
-
| **Watchers** | Live session file watching with event bridging, plus `createJsonlTail()` for raw incremental JSONL consumers
|
|
106
|
-
| **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
|
|
107
|
-
| **Search** | Cross-session full-text search, advanced filtering (substring, fuzzy, regex, date)
|
|
108
|
-
| **Aggregation** | Event aggregation, frequency tracking, activity heatmaps, pattern extraction
|
|
109
|
-
| **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()`)
|
|
110
|
-
| **Error History** | Shared error taxonomy (`categorizeError()`, `extractErrorMessage()`) plus append-only per-project failure history (`appendErrorHistory()`, `readErrorHistory()`, `getTopFailingTools()`) behind per-tool/hour/model error rollups
|
|
111
|
-
| **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)
|
|
112
|
-
| **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
|
|
113
|
-
| **Report** | Self-contained HTML session report generation; `readSessionReportInputs()` returns metrics, transcript, and `subagents` for the session total
|
|
114
|
-
| **Credentials** | Claude Max OAuth credential reading (macOS Keychain with a `~/.claude/.credentials.json` fallback; the file on Linux and Windows)
|
|
115
|
-
| **Quota** | Claude Max subscription quota fetching (5-hour and 7-day windows), 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
|
|
116
|
-
| **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()`)
|
|
117
|
-
| **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
|
|
118
|
-
| **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
|
|
119
|
-
| **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
|
|
120
|
-
| **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
|
|
121
|
-
| **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
|
|
122
|
-
| **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
|
|
123
|
-
| **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
|
|
124
|
-
| **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({ existingAccountId })` re-authenticates a saved profile in place. 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 and startup migration from legacy flat backups |
|
|
125
|
-
| **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, `
|
|
126
|
-
| **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
|
|
127
|
-
| **Quota Snapshots** | Persistent quota caching per provider/account for offline fallback
|
|
128
|
-
| **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
|
|
129
|
-
| **Phrases** | Curated humorous phrases for loading/idle states, available as a flat `ALL_PHRASES` array or grouped via `PHRASE_CATEGORIES` for category-aware UI
|
|
98
|
+
| Module | Description |
|
|
99
|
+
| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
100
|
+
| **Types** | Session events, OpenCode/Codex format types, persistence schemas (tasks, decisions, notes, plans, historical data) |
|
|
101
|
+
| **Paths** | Config directory resolution, project data paths, workspace encoding |
|
|
102
|
+
| **Readers** | Read tasks, decisions, notes, history, handoff, and plans from `~/.config/sidekick/` |
|
|
103
|
+
| **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 |
|
|
104
|
+
| **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 |
|
|
105
|
+
| **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`) |
|
|
106
|
+
| **Parsers** | JSONL event parsing, OpenCode/Codex format normalization, subagent scanning, session path resolution, debug log parsing |
|
|
107
|
+
| **Watchers** | Live session file watching with event bridging, plus `createJsonlTail()` for raw incremental JSONL consumers |
|
|
108
|
+
| **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 |
|
|
109
|
+
| **Search** | Cross-session full-text search, advanced filtering (substring, fuzzy, regex, date) |
|
|
110
|
+
| **Aggregation** | Event aggregation, frequency tracking, activity heatmaps, pattern extraction |
|
|
111
|
+
| **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()`) |
|
|
112
|
+
| **Error History** | Shared error taxonomy (`categorizeError()`, `extractErrorMessage()`) plus append-only per-project failure history (`appendErrorHistory()`, `readErrorHistory()`, `getTopFailingTools()`) behind per-tool/hour/model error rollups |
|
|
113
|
+
| **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) |
|
|
114
|
+
| **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 |
|
|
115
|
+
| **Report** | Self-contained HTML session report generation; `readSessionReportInputs()` returns metrics, transcript, and `subagents` for the session total |
|
|
116
|
+
| **Credentials** | Claude Max 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()` |
|
|
117
|
+
| **Quota** | Claude Max subscription quota fetching (5-hour and 7-day windows), 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 |
|
|
118
|
+
| **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()`) |
|
|
119
|
+
| **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 |
|
|
120
|
+
| **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 |
|
|
121
|
+
| **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 |
|
|
122
|
+
| **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 |
|
|
123
|
+
| **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 |
|
|
124
|
+
| **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 |
|
|
125
|
+
| **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 |
|
|
126
|
+
| **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 |
|
|
127
|
+
| **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()` |
|
|
128
|
+
| **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 |
|
|
129
|
+
| **Quota Snapshots** | Persistent quota caching per provider/account for offline fallback |
|
|
130
|
+
| **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 |
|
|
131
|
+
| **Phrases** | Curated humorous phrases for loading/idle states, available as a flat `ALL_PHRASES` array or grouped via `PHRASE_CATEGORIES` for category-aware UI |
|
|
130
132
|
|
|
131
133
|
## Supported import paths
|
|
132
134
|
|
|
133
135
|
`sidekick-shared` ships three public entry points plus a few convenience 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.
|
|
134
136
|
|
|
135
|
-
| Path | Runtime | What it exposes
|
|
136
|
-
| ------------------------------ | -------------------------- |
|
|
137
|
-
| `sidekick-shared` | Node (CLI, extension host) | Full public API (readers, providers, parsers, pricing, …).
|
|
138
|
-
| `sidekick-shared/browser` | **Browser / webview** | Pure helpers: usage normalization/pricing, token estimation, transcript and assistant-turn projection, model/context math, `parseMcpToolName()`.
|
|
139
|
-
| `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()`), 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()`),
|
|
140
|
-
| `sidekick-shared/phrases` | Any runtime | Phrase arrays + `getRandomPhrase()`.
|
|
141
|
-
| `sidekick-shared/modelContext` | Any runtime | Direct access to the context-window module.
|
|
142
|
-
| `sidekick-shared/modelInfo` | Any runtime | Direct access to model parsing and cost math.
|
|
143
|
-
| `sidekick-shared/formatting` | Any runtime | Direct access to pure token and duration display helpers.
|
|
144
|
-
| `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.
|
|
145
|
-
| `sidekick-shared/statusline` | Node only | Cache-only statusline surface: account status + quota snapshot readers and writers, `formatStatusline()`, burn-rate estimates, quota freshness helpers, `parseClaudeStatuslinePayload()` / `quotaFromStatuslinePayload()`, and `readStateFile()` / `writeStateFile()`.
|
|
137
|
+
| Path | Runtime | What it exposes |
|
|
138
|
+
| ------------------------------ | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
139
|
+
| `sidekick-shared` | Node (CLI, extension host) | Full public API (readers, providers, parsers, pricing, …). |
|
|
140
|
+
| `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()`), and `renderHandoffUrlTemplate()`. |
|
|
141
|
+
| `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()`), 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()`), Claude session-directory encoding (`encodeClaudeWorkspacePath()`, `getClaudeSessionDirectory()`), and the config root (`getConfigDir()`, `CONFIG_DIR_ENV_VAR`). |
|
|
142
|
+
| `sidekick-shared/phrases` | Any runtime | Phrase arrays + `getRandomPhrase()`. |
|
|
143
|
+
| `sidekick-shared/modelContext` | Any runtime | Direct access to the context-window module. |
|
|
144
|
+
| `sidekick-shared/modelInfo` | Any runtime | Direct access to model parsing and cost math. |
|
|
145
|
+
| `sidekick-shared/formatting` | Any runtime | Direct access to pure token and duration display helpers. |
|
|
146
|
+
| `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. |
|
|
147
|
+
| `sidekick-shared/statusline` | Node only | Cache-only statusline surface: account status + quota snapshot readers and writers, `formatStatusline()`, burn-rate estimates, quota freshness helpers, `parseClaudeStatuslinePayload()` / `quotaFromStatuslinePayload()`, and `readStateFile()` / `writeStateFile()`. |
|
|
148
|
+
| `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. |
|
|
149
|
+
|
|
150
|
+
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.
|
|
151
|
+
|
|
152
|
+
Prefer these paths over `sidekick-shared/dist/*` deep imports. The `dist/*` wildcard still resolves for compatibility, but module files behind it can move between releases without notice.
|
|
153
|
+
|
|
154
|
+
### Test suites
|
|
155
|
+
|
|
156
|
+
`sidekick-shared/testing` is for test suites that run sidekick-shared code against session data. Install the guard once per test process, before anything reads a home or spawns a CLI, and check it after each test:
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
// vitest setupFiles entry (with `globals: true`)
|
|
160
|
+
import { installCredentialGuard } from 'sidekick-shared/testing';
|
|
161
|
+
|
|
162
|
+
const guard = installCredentialGuard();
|
|
163
|
+
afterEach(() => guard.assertNoViolations());
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
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`, and patches the real `child_process` and `fs` modules. Spawning `security`, `claude`, or `codex` throws, 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.
|
|
146
167
|
|
|
147
168
|
### Browser / webview runtimes
|
|
148
169
|
|
|
@@ -344,11 +365,13 @@ const result = await collectPromptHistory({
|
|
|
344
365
|
});
|
|
345
366
|
|
|
346
367
|
for (const entry of result.entries) {
|
|
347
|
-
// { provider, sessionId, ordinal, timestamp, text, cwd, gitBranch?, model?, usage?, metadataStatus }
|
|
368
|
+
// { provider, sessionId, ordinal, timestamp, text, cwd, gitBranch?, model?, usage?, metadataStatus, sessionKey }
|
|
348
369
|
// Upsert by (provider, sessionId, ordinal): a prompt comes back when its answer metadata changes.
|
|
349
370
|
}
|
|
350
371
|
saveCursor(result.cursor); // sensitive local state; fully read unchanged files are skipped next time
|
|
351
372
|
if (result.hasMore) scheduleAnotherCall(); // a bound stopped the pass; the cursor resumes it
|
|
373
|
+
// result.changed: sessions whose cursor entry moved this call; result.progressed: anything new was read;
|
|
374
|
+
// result.roots: the realpaths scanned after worktree expansion
|
|
352
375
|
for (const exclusion of result.exclusions) console.warn('record left out', exclusion); // permanent
|
|
353
376
|
```
|
|
354
377
|
|
|
@@ -359,6 +382,8 @@ for (const exclusion of result.exclusions) console.warn('record left out', exclu
|
|
|
359
382
|
- **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.
|
|
360
383
|
- **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.
|
|
361
384
|
- **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`.
|
|
385
|
+
- **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`.
|
|
386
|
+
- **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.
|
|
362
387
|
- **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.
|
|
363
388
|
|
|
364
389
|
### Collect whole sessions of prompts
|
|
@@ -372,7 +397,8 @@ const result = await collectSessionPromptHistory({
|
|
|
372
397
|
workspacePaths: ['/Users/me/code/app'], // plus git worktree siblings
|
|
373
398
|
providers: ['claude-code', 'codex'], // default: both
|
|
374
399
|
since: new Date('2026-09-01'), // sessions whose file changed since then, returned whole
|
|
375
|
-
// sessionIds: ['…'], // or exact session ids
|
|
400
|
+
// sessionIds: ['…'], // or exact session ids (any provider)
|
|
401
|
+
// sessionRefs: result.changed, // or (provider, sessionId), pinned to one file by sessionKey
|
|
376
402
|
// limit: 20, // most recent sessions with prompts
|
|
377
403
|
include: { signals: true, replies: true }, // default: prompts only
|
|
378
404
|
bounds: {
|
|
@@ -384,18 +410,18 @@ const result = await collectSessionPromptHistory({
|
|
|
384
410
|
});
|
|
385
411
|
|
|
386
412
|
for (const session of result.sessions) {
|
|
387
|
-
// { provider, sessionId, startedAt, lastActivityAt, cwds, gitBranches, models,
|
|
413
|
+
// { provider, sessionId, sessionKey, startedAt, lastActivityAt, cwds, gitBranches, models,
|
|
388
414
|
// prompts: [{ ordinal, timestamp, text, cwd, gitBranch?, model?, usage?, metadataStatus, reply? }],
|
|
389
|
-
// signals?: [{ kind, timestamp, afterOrdinal, tool?, text?, textTruncated? }],
|
|
415
|
+
// signals?: [{ kind, timestamp, afterOrdinal, tool?, toolCategory?, mcpServer?, text?, textTruncated? }],
|
|
390
416
|
// complete, truncated, droppedPrompts, excludedRecords }
|
|
391
417
|
}
|
|
392
418
|
if (result.unread.length > 0) {
|
|
393
419
|
// A bound stopped the call, or a session changed while it was read:
|
|
394
|
-
// pass these back as `
|
|
420
|
+
// pass these back as `sessionRefs` to continue.
|
|
395
421
|
}
|
|
396
422
|
```
|
|
397
423
|
|
|
398
|
-
- **Whole sessions, no cursor.** Every call reads each session from its first line and never resumes one mid-file. `since` selects files by modification time and never drops older prompts from a returned session. A long-lived consumer can
|
|
424
|
+
- **Whole sessions, no cursor.** Every call reads each session from its first line and never resumes one mid-file. `since` selects files by modification time and never drops older prompts from a returned session. A long-lived consumer can keep a `collectPromptHistory()` cursor, then fetch the sessions in its result's `changed` whole with `sessionRefs: result.changed`. `sessionRefs` match on provider and id, or on one file when `sessionKey` is set; `sessionIds` match on id alone across providers.
|
|
399
425
|
- **Bounds.** `maxSessions`, `maxTotalBytes`, the deadline, and an abort stop the call. Any session they interrupt is listed in `unread` rather than returned partially, and `boundsHit` names the bound. A session that changed while it was being read is also listed in `unread`, with no `boundsHit` entry. A session longer than `maxSessionBytes` is returned with `truncated: true`, and `boundsHit` includes `maxSessionBytes`; keep `maxSessionBytes` at or below `maxTotalBytes`, or a session larger than `maxTotalBytes` is never read. A record over `maxRecordBytes` (default 16 MiB) is skipped and counted in `excludedRecords`.
|
|
400
426
|
- **Completeness.** `complete` is false when the session was truncated, when `droppedPrompts` > 0 (prompts out of scope or without a timestamp, visible as gaps in `ordinal`), or when `excludedRecords` > 0 (oversized or malformed records).
|
|
401
427
|
- **Replies** (`include.replies`): the agent's last text for the prompt's turn. For Claude Code this is the last assistant message with text before the next prompt, with split records joined. For Codex it is `task_complete.last_agent_message`, falling back to the last agent message.
|
|
@@ -403,6 +429,7 @@ if (result.unread.length > 0) {
|
|
|
403
429
|
- `interrupt`: Claude `[Request interrupted by user…]`, or Codex `turn_aborted`.
|
|
404
430
|
- `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.
|
|
405
431
|
- `toolError`: a failed tool call, with the tool name and the error text cut to 1024 characters (`textTruncated`).
|
|
432
|
+
- 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`.
|
|
406
433
|
- `compaction`, `apiError` (including Codex usage-limit errors), and `rollback` (Codex `thread_rolled_back`).
|
|
407
434
|
- **Scope fails closed.** Sessions with no prompt inside the roots are omitted. Replies and signals that follow an out-of-scope prompt are dropped.
|
|
408
435
|
|
|
@@ -549,6 +576,15 @@ const priced = events.filter((event) => event.model !== SYNTHETIC_MODEL);
|
|
|
549
576
|
On abort the promise rejects with the signal's reason (an `AbortError` by
|
|
550
577
|
default); sessions read before the abort keep their cache entries.
|
|
551
578
|
|
|
579
|
+
To cover several workspaces in one call, pass `workspacePaths` instead of
|
|
580
|
+
`workspacePath`. Each root is realpath-resolved and, unless
|
|
581
|
+
`includeWorktrees: false`, expanded with its main checkout and git
|
|
582
|
+
worktrees (`resolveWorkspaceRoots()`, also on `sidekick-shared/node`). Each
|
|
583
|
+
root is listed, and a session file found under several overlapping roots
|
|
584
|
+
is read and counted once. The result's `roots` names what was listed, and
|
|
585
|
+
every event carries the `filePath` of its session. When no root resolves,
|
|
586
|
+
no session is read.
|
|
587
|
+
|
|
552
588
|
### Detect the active session provider
|
|
553
589
|
|
|
554
590
|
For a single-provider surface, auto-detect and construct just one:
|
|
@@ -590,6 +626,8 @@ console.log(`Found ${tasks.length} tasks`);
|
|
|
590
626
|
|
|
591
627
|
`diagnoseProviderFailure()` is a pure, synchronous helper exported from `sidekick-shared` and `sidekick-shared/browser`. 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.
|
|
592
628
|
|
|
629
|
+
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.
|
|
630
|
+
|
|
593
631
|
```typescript
|
|
594
632
|
import { diagnoseProviderFailure } from 'sidekick-shared/browser';
|
|
595
633
|
|
|
@@ -860,7 +898,8 @@ The boundary schemas — `sessionEventSchema` plus the quota, account-status, ac
|
|
|
860
898
|
Use `createJsonlTail()` when a consumer needs raw parsed events and owns its own aggregation lifecycle. `onBatchComplete` fires once after each drained byte chunk, which lets callers defer expensive UI or metrics updates until parsing for that chunk is complete.
|
|
861
899
|
|
|
862
900
|
```typescript
|
|
863
|
-
import { createJsonlTail
|
|
901
|
+
import { createJsonlTail } from 'sidekick-shared/node';
|
|
902
|
+
import { sessionEventSchema } from 'sidekick-shared/schemas';
|
|
864
903
|
|
|
865
904
|
const tail = createJsonlTail({
|
|
866
905
|
path: '/path/to/session.jsonl',
|
|
@@ -873,6 +912,8 @@ const tail = createJsonlTail({
|
|
|
873
912
|
tail.start();
|
|
874
913
|
```
|
|
875
914
|
|
|
915
|
+
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 `sidekick-shared/node`.
|
|
916
|
+
|
|
876
917
|
`SessionWatcherCallbacks.onBatchComplete` is also available through `createWatcher()`. Save aggregation state only after a complete event batch and only when the state includes the full history (a completed replay or a compatible restored checkpoint). Live-only state is incomplete and must not replace a full-history checkpoint. JSONL positions end at complete lines; Codex readers reconstruct parser context when seeking. OpenCode's SQLite timestamp cursor does not persist row-version deduplication, so the dashboards replay OpenCode history instead of restoring a snapshot.
|
|
877
918
|
|
|
878
919
|
### Search a workspace and diagnose its provider
|
package/dist/accountHealth.d.ts
CHANGED
|
@@ -12,6 +12,12 @@ export interface AccountHealthSidecar {
|
|
|
12
12
|
lastRefreshAt?: number;
|
|
13
13
|
hasRefreshToken?: boolean;
|
|
14
14
|
authMode?: 'chatgpt' | 'api-key' | 'oauth';
|
|
15
|
+
/**
|
|
16
|
+
* sha256(refreshToken)[:16] of the stored provider credential: lets sync tell
|
|
17
|
+
* whose chain the live store holds without keeping another token copy.
|
|
18
|
+
*/
|
|
19
|
+
refreshFp?: string;
|
|
20
|
+
/** Provenance of the stored credential; read-only probes preserve it. */
|
|
15
21
|
source: AccountHealthSource;
|
|
16
22
|
}
|
|
17
23
|
export interface AccountHealth {
|
|
@@ -48,6 +54,8 @@ export interface AccountHealthOptions {
|
|
|
48
54
|
* is paid once per profile and never on a hot path that opts into `cache`.
|
|
49
55
|
*/
|
|
50
56
|
probe?: 'cache' | 'store' | 'auto';
|
|
57
|
+
/** Inspection-only callers must not persist health metadata. Default true. */
|
|
58
|
+
persist?: boolean;
|
|
51
59
|
now?: number;
|
|
52
60
|
}
|
|
53
61
|
export declare function getHealthSidecarPath(provider: AccountProviderId, accountId: string): string;
|
|
@@ -60,6 +68,12 @@ export declare function writeClaudeHealthSidecar(accountId: string, blob: unknow
|
|
|
60
68
|
export declare function codexHealthSidecarFromAuth(authRaw: string | null, source: AccountHealthSource, now?: number, fallbackPath?: string): AccountHealthSidecar | null;
|
|
61
69
|
/** Record the health of a Codex profile from the auth file just stored. */
|
|
62
70
|
export declare function writeCodexHealthSidecar(accountId: string, authRaw: string | null, source: AccountHealthSource, now?: number): AccountHealthSidecar | null;
|
|
71
|
+
/**
|
|
72
|
+
* The credential a Claude profile would use right now: the live store for the
|
|
73
|
+
* live account (its profile home is empty by design), otherwise the fresher of
|
|
74
|
+
* the profile-home store and Sidekick's backup.
|
|
75
|
+
*/
|
|
76
|
+
export declare function readEffectiveClaudeCredential(accountId: string): unknown;
|
|
63
77
|
/**
|
|
64
78
|
* Health of one saved account. `probe: 'cache'` never spawns anything; a
|
|
65
79
|
* missing sidecar on a profile that has credentials is reported as `unknown`
|