sidekick-shared 0.24.5 → 0.26.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 +92 -0
- package/README.md +205 -52
- package/dist/accountChangeSignal.d.ts +3 -0
- package/dist/accountChangeSignal.js +19 -0
- package/dist/accountChanges.d.ts +21 -0
- package/dist/accountChanges.js +170 -0
- package/dist/accountRegistry.d.ts +25 -2
- package/dist/accountRegistry.js +41 -13
- package/dist/accountStatus.d.ts +9 -1
- package/dist/accountStatus.js +3 -3
- package/dist/accounts.d.ts +3 -1
- package/dist/accounts.js +84 -64
- package/dist/aggregation/EventAggregator.d.ts +15 -2
- package/dist/aggregation/EventAggregator.js +54 -9
- package/dist/aggregation/costProvenance.d.ts +14 -0
- package/dist/aggregation/costProvenance.js +35 -0
- package/dist/aggregation/snapshot.d.ts +13 -2
- package/dist/aggregation/snapshot.js +57 -6
- package/dist/aggregation/types.d.ts +30 -1
- package/dist/analytics/compactionLedger.js +15 -5
- package/dist/analytics/qualityScore.js +2 -1
- package/dist/browser.d.ts +19 -4
- package/dist/browser.js +43 -1
- package/dist/codexProfiles.d.ts +3 -1
- package/dist/codexProfiles.js +4 -2
- package/dist/codexQuota.d.ts +2 -0
- package/dist/codexQuota.js +11 -66
- package/dist/codexQuotaWatcher.d.ts +6 -0
- package/dist/codexQuotaWatcher.js +101 -47
- package/dist/failingTools.d.ts +37 -0
- package/dist/failingTools.js +70 -0
- package/dist/formatters/sessionDump.js +13 -7
- package/dist/formatting.d.ts +13 -0
- package/dist/formatting.js +35 -0
- package/dist/historicalStore.d.ts +47 -0
- package/dist/historicalStore.js +312 -0
- package/dist/index.d.ts +67 -23
- package/dist/index.js +138 -26
- package/dist/modelAliases.d.ts +13 -0
- package/dist/modelAliases.js +58 -0
- package/dist/modelCatalog.d.ts +32 -0
- package/dist/modelCatalog.js +145 -0
- package/dist/modelContext.d.ts +36 -3
- package/dist/modelContext.js +109 -34
- package/dist/modelInfo.d.ts +30 -1
- package/dist/modelInfo.js +116 -26
- package/dist/multiProviderQuotaService.d.ts +8 -0
- package/dist/multiProviderQuotaService.js +64 -3
- package/dist/node.d.ts +23 -4
- package/dist/node.js +50 -1
- package/dist/observedContextWindows.d.ts +7 -0
- package/dist/observedContextWindows.js +17 -2
- package/dist/observedSessionCollector.d.ts +72 -6
- package/dist/observedSessionCollector.js +440 -47
- package/dist/parsers/codexHistory.d.ts +4 -2
- package/dist/parsers/codexHistory.js +9 -2
- package/dist/parsers/sessionActivityDetector.d.ts +28 -0
- package/dist/parsers/sessionActivityDetector.js +110 -15
- package/dist/parsers/sessionPathResolver.d.ts +11 -0
- package/dist/parsers/sessionPathResolver.js +13 -10
- package/dist/peakHours.d.ts +34 -3
- package/dist/peakHours.js +92 -7
- package/dist/pricingCatalog.d.ts +7 -0
- package/dist/pricingCatalog.js +8 -5
- package/dist/providerIds.d.ts +11 -0
- package/dist/providerIds.js +9 -0
- package/dist/providerQuota.d.ts +3 -1
- package/dist/providerQuota.js +3 -0
- package/dist/providers/claudeCode.d.ts +10 -1
- package/dist/providers/claudeCode.js +211 -131
- package/dist/providers/codex.d.ts +20 -1
- package/dist/providers/codex.js +308 -244
- package/dist/providers/codexDatabase.d.ts +9 -0
- package/dist/providers/codexDatabase.js +107 -2
- package/dist/providers/detect.d.ts +2 -0
- package/dist/providers/detect.js +29 -24
- package/dist/providers/diagnostics.d.ts +13 -0
- package/dist/providers/diagnostics.js +88 -0
- package/dist/providers/factory.d.ts +20 -0
- package/dist/providers/factory.js +57 -0
- package/dist/providers/openCode.d.ts +22 -1
- package/dist/providers/openCode.js +460 -126
- package/dist/providers/openCodeDatabase.d.ts +10 -0
- package/dist/providers/openCodeDatabase.js +69 -0
- package/dist/providers/rolloutWalker.d.ts +53 -0
- package/dist/providers/rolloutWalker.js +209 -0
- package/dist/providers/types.d.ts +82 -2
- package/dist/providers/types.js +4 -0
- package/dist/quota.d.ts +34 -4
- package/dist/quota.js +37 -9
- package/dist/quotaHistory.d.ts +3 -2
- package/dist/quotaHistory.js +36 -35
- package/dist/quotaPoller.d.ts +13 -1
- package/dist/quotaPoller.js +65 -3
- package/dist/quotaResolver.d.ts +81 -0
- package/dist/quotaResolver.js +213 -0
- package/dist/quotaSnapshots.d.ts +15 -2
- package/dist/quotaSnapshots.js +25 -3
- package/dist/quotaThresholds.d.ts +60 -0
- package/dist/quotaThresholds.js +90 -0
- package/dist/report/htmlReportGenerator.js +10 -4
- package/dist/report/sessionReportInputs.d.ts +23 -0
- package/dist/report/sessionReportInputs.js +34 -0
- package/dist/schemas/accountManager.d.ts +2 -2
- package/dist/schemas/accountManager.js +2 -4
- package/dist/schemas/accountStatus.d.ts +3 -0
- package/dist/schemas/accountStatus.js +1 -0
- package/dist/schemas/index.d.ts +3 -0
- package/dist/schemas/index.js +16 -2
- package/dist/schemas/observedSessionV1.d.ts +27 -26
- package/dist/schemas/observedSessionV1.js +6 -4
- package/dist/schemas/quota.d.ts +141 -2
- package/dist/schemas/quota.js +20 -8
- package/dist/schemas/quotaHistory.d.ts +3 -2
- package/dist/schemas/quotaHistory.js +1 -1
- package/dist/schemas/sessionEvent.d.ts +6 -0
- package/dist/schemas/sessionEvent.js +1 -0
- package/dist/schemas/stateFile.d.ts +198 -0
- package/dist/schemas/stateFile.js +77 -0
- package/dist/schemas/tokenEstimation.d.ts +1 -1
- package/dist/schemas/transcript.js +2 -0
- package/dist/sessionFingerprint.d.ts +25 -0
- package/dist/sessionFingerprint.js +46 -0
- package/dist/sessionMonitor.d.ts +15 -0
- package/dist/sessionMonitor.js +142 -0
- package/dist/sessionPreviews.d.ts +33 -2
- package/dist/sessionPreviews.js +333 -6
- package/dist/sessionStats.d.ts +51 -0
- package/dist/sessionStats.js +166 -0
- package/dist/stateFile.d.ts +113 -0
- package/dist/stateFile.js +160 -0
- package/dist/statusline/claudeStatuslinePayload.d.ts +89 -0
- package/dist/statusline/claudeStatuslinePayload.js +152 -0
- package/dist/statusline/formatter.d.ts +13 -0
- package/dist/statusline/formatter.js +48 -6
- package/dist/statusline/index.d.ts +10 -2
- package/dist/statusline/index.js +19 -1
- package/dist/tokenSummary.d.ts +62 -0
- package/dist/tokenSummary.js +72 -0
- package/dist/transcript.d.ts +5 -0
- package/dist/transcript.js +2 -0
- package/dist/types/observedSessionV1.d.ts +8 -2
- package/dist/types/observedSessionV1.js +88 -25
- package/dist/types/sessionEvent.d.ts +2 -0
- package/dist/usage/billingBlocks.d.ts +85 -0
- package/dist/usage/billingBlocks.js +153 -0
- package/dist/usage/importSessionHistory.d.ts +53 -0
- package/dist/usage/importSessionHistory.js +88 -0
- package/dist/usage/usageEvents.d.ts +92 -0
- package/dist/usage/usageEvents.js +322 -0
- package/dist/usage/usageReports.d.ts +76 -0
- package/dist/usage/usageReports.js +208 -0
- package/dist/watchers/factory.d.ts +6 -0
- package/dist/watchers/factory.js +30 -25
- package/dist/zaiQuotaApi.d.ts +2 -0
- package/dist/zaiQuotaApi.js +2 -0
- package/package.json +22 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,98 @@ 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.26.0] - 2026-09-05
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `summarizeTokens()`, `sumTokenTotals()`, `TOKEN_TOTAL_LABEL`, and `TOKEN_CONTEXT_LABEL` define the one token vocabulary every Sidekick surface uses: `total` is input + output + cache writes + cache reads, `context` is input + cache; browser-safe
|
|
13
|
+
- `AggregatedTokens` gains `costUsd`, `costProvenance` (`reported` | `estimated` | `mixed` | `unpriced` | `none`), `reportedCostUsd`, `estimatedCostUsd`, and `unpricedCalls`; `describeCostProvenance()` and `classifyCostProvenance()` are exported; `CanonicalSessionTranscript.usage` adds `pricedCostUsd` and `unpricedEvents` (schema updated)
|
|
14
|
+
- `parseClaudeStatuslinePayload()` and `quotaFromStatuslinePayload()` (also on the `sidekick-shared/statusline` subpath, which now exports `writeQuotaSnapshot`, `appendQuotaHistorySample`, and `getWorkspaceIdFromPath`) read the JSON Claude Code pipes to a status-line command; `QuotaState.source` and quota-history samples accept `statusline`
|
|
15
|
+
- `formatStatusline({ live })` renders context %, session cost, prompt-cache hit rate, the seven-day window, and the snapshot age when it is older than five minutes
|
|
16
|
+
- `readQuotaSnapshot()` reports `ageMs` and `freshness`; `classifyQuotaFreshness()`, `formatQuotaAge()`, `QUOTA_FRESH_MAX_AGE_MS`, and `QUOTA_AGING_MAX_AGE_MS` are exported and the quota schema accepts the new fields
|
|
17
|
+
- `getScheduledPeakHoursState()`, `PEAK_HOURS_DESCRIPTION`, `PEAK_HOURS_CACHE_MS`, and `PeakHoursState.source` (`promoclock` | `schedule`); `fetchPeakHoursStatus({ force })` bypasses the ten-minute cache
|
|
18
|
+
- `evaluateQuotaThresholds()`, `describeQuotaThresholdAlert()`, and `DEFAULT_QUOTA_THRESHOLDS` for once-per-reset-window threshold alerts
|
|
19
|
+
- `formatLocalDateKey()`, `parseLocalDateKey()`, and `addLocalDays()` (root and browser entry points); `hydratePricingCatalog({ offline })`; `pruneSnapshots()` and `MAX_SNAPSHOT_FILES`; `getActiveAccountStatus(error, { selfHeal })`, mirrored on `resolveActiveClaudeAccount()` and `resolveActiveCodexAccount()`; `_resetProviderDetectionCache()` and `_resetPeakHoursCache()` for tests
|
|
20
|
+
- `resolveQuota({ providerId, workspacePath, preferFresh, allowApi, … })` resolves Claude, Codex, or z.ai quota with one precedence — a persisted sample younger than `QUOTA_FRESH_MAX_AGE_MS` (any origin), then session logs, then the provider API (persisted on success), then an aging or stale sample with the API failure attached — returning `ResolvedQuota` with `resolution`, `source`, `capturedSource`, `freshness`, and `ageMs`; every side effect is injectable
|
|
21
|
+
- `QuotaState.capturedSource` (`api` | `session` | `statusline`) records where a cached sample came from; `readQuotaSnapshot()` fills it and `writeQuotaSnapshot()` preserves it instead of storing `cache`; the quota schema accepts it; `QuotaSnapshotRecord`, `enrichCodexQuota()`, and `enrichZaiQuota()` are exported; `fetchQuota(token, { fetchImpl })` accepts an injected fetch
|
|
22
|
+
- `computeSessionFileStats(events, options)` and `readSessionFileStats(provider, sessionPath, { resolveLabel })` compute `SessionFileStats` for every provider over the shared `EventAggregator`; `firstUserPrompt(events)` derives a session label from events already in memory. `SessionFileStats` gains `availability` (`full` | `partial` | `unavailable`), `unavailableReason`, `costUsd`, `costProvenance`, `unpricedCalls`, and `toolFailures`; `reportedCost` is deprecated (same value as `costUsd`)
|
|
23
|
+
- `collectUsageEvents({ providers, since, until, workspacePath })` reads every session touched in a window once through its provider reader and returns priced usage events, caching raw token counts per session under `<configDir>/usage-cache/` keyed by size and mtime (priced at load, so a catalog refresh never leaves stale costs); `pruneUsageCache()`, `getUsageCacheDir()`, `USAGE_CACHE_VERSION`, and `MAX_USAGE_CACHE_FILES` are exported
|
|
24
|
+
- `computeBillingBlocks(events, { now })` and `findActiveBillingBlock()` (root and browser entry points) group usage events into ccusage-style five-hour blocks aligned to the UTC hour, with cache-inclusive totals, cost provenance, per-model usage, burn rate, remaining time, and end-of-block projections; `BILLING_BLOCK_DURATION_MS` is exported
|
|
25
|
+
- `bucketUsage(events, { granularity, utc, weekStartsOn, groupBy })`, `summarizeUsageRows()`, `usageBucketKey()`, and `weekKey()` (root and browser entry points) bucket usage events by event time into day, week, month, or session rows with provider, project, and model dimensions, cache-inclusive totals, and cost provenance
|
|
26
|
+
- `applySessionSummary(store, summary)`, `removeSessionSummary()`, `markFileImported()`, `isFileImported()`, and `sessionSummaryFromStats()` (root and browser entry points) are the pure `historical-data.json` mutations ported from the VS Code extension, so the CLI and the extension credit sessions identically; `importSessionHistory({ providers, since, isImported, applySummary, markImported })` reads every finished session once through `readSessionFileStats()` and hands summaries to the caller's store, skipping files modified within `ACTIVE_SESSION_MTIME_THRESHOLD_MS`
|
|
27
|
+
- `SessionFileStats.modelUsage` entries gain `costUsd` and `priced`
|
|
28
|
+
- `classifySessionActivity({ events, mtimeMs })` classifies a session from events already in memory with the same states, reasons, grace period, and staleness rule as `detectSessionActivity()`; `refreshSessionActivityState()` accepts the previous reason so a session that was active only by grace period ends once the grace period lapses; `getObservedActivityReason()` exposes the reason behind an observed session's activity; `SESSION_ACTIVITY_GRACE_PERIOD_MS` and `SESSION_ACTIVITY_STALENESS_MS` are exported
|
|
29
|
+
- `readSessionReportInputs(provider, sessionPath)` reads a session once and returns its events, aggregator metrics, and report transcript; `resolveSessionPath(provider, workspacePath, sessionId?)` is the session lookup `createWatcher()` uses (exact id, unique prefix, or most recent); `sessionFingerprintParts()` and `fingerprintString()` are exported alongside `fileFingerprintParts()`; `SessionMessage.stop_reason` is preserved by the event schema
|
|
30
|
+
- `walkRolloutFiles(roots, { maxDepth, maxFiles, limit, sessionId })` and `walkRolloutFilesAsync()` are the one capped walker for Codex rollout files: newest-dated directories first, each file stat'ed once, results newest first with size and session id, an early exit for `limit`, and defaults of six levels and 20 000 files; `findSessionFilesWithStats()` returns a Claude Code session directory's files with the stat the listing already paid for
|
|
31
|
+
- `writeStateFile()`, `readStateFile()`, `getStateFilePath()`, `quotaToStateFile()`, `billingBlockToStateFile()`, `STATE_FILE_SCHEMA_VERSION`, and the `SidekickStateFile` types (root, node, and `statusline` entry points) maintain the public, versioned `<configDir>/state.json` for external tools — account, quota windows with freshness, context usage, session cost, active billing block — writing atomically only when the content changed; `sidekickStateFileSchema` and its sub-schemas are exported from `sidekick-shared/schemas`
|
|
32
|
+
- `SessionPreviewListResult.hasMore` reports whether `listSessionPreviewsAsync()` left candidates beyond `limit`, so callers can offer a larger limit only when one would show more
|
|
33
|
+
- `mergeFailingToolWindows()`, `failingToolTrend()`, and `FAILING_TOOL_TREND_ARROWS` (root and `browser` entry points) merge the 7-day and 30-day `getTopFailingTools()` windows into per-tool rows with a trend, shared by `sidekick stats` and the VS Code Health tab
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
|
|
37
|
+
- The HTML report and session dump label cost by provenance and render unpriced sessions as `—` with the unpriced call count; their "Total" row is cache-inclusive and labelled "Total (incl. cache)"
|
|
38
|
+
- `calculateCompactionLedger()` prices re-establishment through `calculateNormalizedUsageCost()` instead of the deprecated additive-reasoning path
|
|
39
|
+
- Codex `readSessionStats()` computes context as uncached + cached input, matching `CodexProvider.computeContextSize()`
|
|
40
|
+
- OpenCode `workspaceMatches` only matches sessions started in the workspace or a subdirectory of it
|
|
41
|
+
- `readQuotaHistoryDailyBuckets()` keys buckets by local calendar day
|
|
42
|
+
- `detectProvider()` and `getAllDetectedProviders()` share one implementation memoised for five seconds
|
|
43
|
+
- `loadObservedContextWindows()` ignores entries older than `OBSERVED_CONTEXT_WINDOW_TTL_MS` (30 days)
|
|
44
|
+
- `saveSnapshot()` writes through the fsynced atomic writer and prunes the directory to the newest 200 files every 25 saves; quota-history append and prune run under a per-file cross-process lock with fsync; the pricing catalog cache is written atomically
|
|
45
|
+
- `DEFAULT_PEAK_HOURS_TIMEOUT_MS` is 4 000 ms; `fetchPeakHoursStatus()` falls back to the schedule rather than returning `unavailable: true`
|
|
46
|
+
- `listSessionPreviews({ since })` throws `RangeError` on an unparseable cutoff
|
|
47
|
+
- `AggregatedTokens.reportedCost` is deprecated (same value as `costUsd`); `SNAPSHOT_SCHEMA_VERSION` is 5
|
|
48
|
+
- Claude Code, Codex, and OpenCode `readSessionStats()` share one implementation: per-model `tokens` are cache-inclusive (`summarizeTokens().total`) for every provider, Claude sessions report catalog-estimated cost and a tool success/failure split instead of `reportedCost: 0`, OpenCode compaction and truncation counts come from the aggregator instead of hardcoded zeros, a missing or unreadable source reports `availability: 'unavailable'` with a reason instead of silent zeros, and the label is taken from the events already read (Codex and OpenCode still prefer their database title) so the file is never opened twice
|
|
49
|
+
- OpenCode's file-backed reader implements `seekTo()` by message count (its `getPosition()` unit), so a reader restored from a snapshot emits only newer messages instead of replaying the whole session; the database-backed reader's `exists()` checks the session row (memoised for 30 s, treating a transient query failure as present) instead of always returning true
|
|
50
|
+
- `createProviderSessionAdapterV1().read()` reads a session once: stats, transcript, activity, and the pending request all come from one flushed reader pass instead of three separate file reads, and activity is classified from the events, which also fixes Codex sessions (whose raw lines never matched the Claude-only tail patterns) and database-backed OpenCode sessions (which always read as ended). `watch()` checks the size/mtime fingerprint before re-reading and refreshes only the activity in place while the content is unchanged, so a 2 s poll on an idle session costs a `stat`
|
|
51
|
+
- `ObservedSessionCollector` cache refreshes demote a session that was active only by grace period to ended after five seconds, matching a fresh classification
|
|
52
|
+
- The Codex provider's six recursive walkers (session discovery, listing, id lookup, project folders, subagent scan) and the quota resolver's separate copy all use the capped walker; `getAllProjectFolders()` reads `session_meta` only for rollouts newer than the SQLite index's mtime when the index answered, and every discovery path uses the per-path cwd cache (now bounded to 5 000 entries) instead of re-reading the first line of every rollout; `scanSubagents()` stops after the 50 newest-dated rollouts instead of enumerating the whole history; the local Codex quota scan walks at most `maxSessionFiles` rollouts and no longer stats every path a second time to sort it; `ClaudeCodeProvider.listAllSessionFiles()` no longer stats every session twice
|
|
53
|
+
|
|
54
|
+
### Fixed
|
|
55
|
+
|
|
56
|
+
- `onAccountsChanged()` applies a later subscriber's faster `pollIntervalMs` and resets the interval after the last unsubscribe
|
|
57
|
+
- `OpenCodeProvider.dispose()` resets `dbStatus`; `CodexProvider.listSessionFilesAsync()` records its filesystem fallback for `getLastOperationStatus()`
|
|
58
|
+
- Observed-session cache hits could report a session as active for up to five minutes when a fresh parse would have said ended (the 0.25.0 grace-period drift note)
|
|
59
|
+
- `addCurrentAccount()` and `removeAccount()` hold the registry lock across their whole read-modify-write (new `withSavedAccountRegistryLock()` and `replaceSavedAccountProfilesUnlocked()`), so a concurrent add from another process is no longer dropped when the Claude profile set is replaced
|
|
60
|
+
- `resolveActiveClaudeAccount()`'s self-heal of the active pointer no longer fires an accounts-changed notification from a read path (`setActiveSavedAccount(..., { silent: true })`)
|
|
61
|
+
- `readSessionPreview()` / `readSessionPreviewAsync()` honour `maxPrefixBytes: 0` as "skip the prefix scan" (`firstTimestamp` and `workspacePath` come back null, the file is not opened) instead of flooring the budget to one byte
|
|
62
|
+
- `createSessionProviders()` documents that `diagnostics` is live (updated in place as providers run) and should be copied for a snapshot
|
|
63
|
+
- The packaging contract tests create their scratch projects under the OS temp directory instead of inside the package, so an interrupted run cannot leave a recursive `node_modules/sidekick-shared` link behind
|
|
64
|
+
|
|
65
|
+
## [0.25.0] - 2026-08-18
|
|
66
|
+
|
|
67
|
+
### Added
|
|
68
|
+
|
|
69
|
+
- `listSessionPreviewsAsync()` and `readSessionPreviewAsync()` provide bounded-concurrency, cooperative session preview reads while preserving the synchronous APIs and their signatures
|
|
70
|
+
- Async preview labeling batches Codex and OpenCode database work into at most one `sqlite3` subprocess per provider per few hundred sessions listed (ids are chunked clear of argv limits); when `sqlite3` is absent, the same bounded file-scan fallback remains available
|
|
71
|
+
- `OpenCodeProvider.listAllSessionFiles()` completes native preview enumeration across Claude Code, Codex, and OpenCode
|
|
72
|
+
- `ObservedSessionCollector.subscribe()` and `SessionMonitor.subscribe()` emit debounced, coalesced session changes with the previous and current fingerprints, backed by filesystem watching plus a documented catch-up poll for unreliable filesystems
|
|
73
|
+
- `createSessionProviders({ onDiagnostic })` constructs every usable provider independently and reports unavailable providers without aborting the host
|
|
74
|
+
- `SessionProviderBase.findSessionById()` has provider-native implementations: filename lookup for Claude Code and Codex, and a database lookup with a bounded file-scan fallback for OpenCode
|
|
75
|
+
- `exportResolvedModelCatalog()` and `importResolvedModelCatalog()` transfer serializable context-window and pricing resolutions between Node and browser realms without private hooks
|
|
76
|
+
- `registerModelAlias()` lets hosts resolve short model names to canonical ids, and catalog provenance marks prefix-inherited context and pricing matches so callers can reject them
|
|
77
|
+
- Stable provider-id arrays are exported as runtime values alongside their derived TypeScript unions
|
|
78
|
+
- `onAccountsChanged()` reports process-local and filesystem-observed login, logout, and account-switch changes without account-state polling in each host
|
|
79
|
+
|
|
80
|
+
### Changed
|
|
81
|
+
|
|
82
|
+
- Provider constructors are environment-independent and perform no filesystem, configuration, database, or binary probing; environmental failures are deferred to first use and returned as structured diagnostics
|
|
83
|
+
- Repeated identical provider degradations are coalesced in the factory diagnostic result, keeping first-use reporting bounded in long-lived hosts
|
|
84
|
+
- `ObservedSessionCollector.collect()` caches parsed sessions by fingerprint, so a second collection over unchanged files performs no content reads
|
|
85
|
+
- Observed sessions expose `fingerprintParts: { sizeBytes, mtimeMs }` alongside the compatible opaque fingerprint string
|
|
86
|
+
- Collector reads accept bounded-concurrency and cooperative-yield hooks; cached observations refresh `observedAt` and activity state while retaining `contentObservedAt` to make the age of cached usage/model data explicit
|
|
87
|
+
- `ObservedSessionDiagnostic` now includes `severity` and `phase` while retaining every existing `kind` value
|
|
88
|
+
- Account and quota entry points guarantee results that validate against the same-release exported schemas, including additive schema fields, and their contract tests enforce the boundary
|
|
89
|
+
- Every documented package subpath now declares explicit `types`, `import`, `require`, and `default` export conditions and resolves in both Vite and TypeScript's legacy `moduleResolution: node` mode without aliases
|
|
90
|
+
- `readCodexHistory()` retains raw epoch-second `ts`, adds normalized `tsMs`, and sizes its tail read from `limit` unless an explicit byte bound is supplied
|
|
91
|
+
- Observed-session projection accepts `observationOnly: true`, which reports transcripts as read-only observations with `capabilities.resume` disabled
|
|
92
|
+
- `QuotaPoller`, `MultiProviderQuotaService`, and `CodexQuotaWatcher` stay dormant when their account is absent and wake on account changes instead of issuing empty polls
|
|
93
|
+
|
|
94
|
+
### Fixed
|
|
95
|
+
|
|
96
|
+
- Codex and OpenCode database-backed operations attach an availability diagnostic when the external `sqlite3` binary is missing, so an unavailable runtime is distinguishable from an empty workspace
|
|
97
|
+
- A wrong session id resolves to `null` through every provider instead of surfacing discovery or environment errors
|
|
98
|
+
- Exact model pricing/context entries are considered before prefix inheritance across all sources, and prefix-only matches are explicit in provenance rather than silently trusted as exact
|
|
99
|
+
|
|
8
100
|
## [0.24.5] - 2026-08-18
|
|
9
101
|
|
|
10
102
|
### 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
|
+
- **One token vocabulary and cost provenance (0.26.0)** — `summarizeTokens()` gives every surface the same `total` (input + output + cache writes + cache reads) and `context` (input + cache) figures; `AggregatedTokens.costUsd` carries `costProvenance` so reports can say whether a cost was provider-reported or estimated; `parseClaudeStatuslinePayload()` / `quotaFromStatuslinePayload()` turn Claude Code's status-line JSON into an official `statusline` quota source; `readQuotaSnapshot()` reports `ageMs` / `freshness` and keeps the sample's origin as `capturedSource`; `resolveQuota()` is the one quota resolution path (fresh persisted sample → session logs → provider API → older sample) shared by the CLI, the MCP server, and both dashboards; `readSessionFileStats()` / `computeSessionFileStats()` give every provider one `readSessionStats()` over the shared aggregator, with `availability`, `costProvenance`, and `toolFailures` on `SessionFileStats`; `collectUsageEvents()` reads priced usage events from session logs with a per-session cache, `computeBillingBlocks()` groups them into five-hour billing blocks, and `bucketUsage()` turns them into day, week, month, or session rows; `applySessionSummary()` and `importSessionHistory()` let any host fold finished sessions into `historical-data.json` the way the extension does; observed sessions are read once per discovery, re-read only when their fingerprint changes, and classified from events by `classifySessionActivity()`; `readSessionReportInputs()` derives report metrics and transcript from one read; `writeStateFile()` / `readStateFile()` (also on the `statusline` subpath) maintain the public, versioned `state.json` for external tools, validated by `sidekickStateFileSchema`; `getScheduledPeakHoursState()` and `evaluateQuotaThresholds()` are shared by the CLI and extension dashboards.
|
|
13
|
+
- **Host-safe APIs (0.25.0)** — `listSessionPreviewsAsync()` / `readSessionPreviewAsync()` read previews with bounded concurrency and cooperative yielding; `ObservedSessionCollector.subscribe()` and `SessionMonitor.subscribe()` replace host polling loops with debounced change batches; `createSessionProviders({ onDiagnostic })` constructs every usable provider with structured diagnostics instead of throwing; `findSessionById()` resolves one session without scanning; `exportResolvedModelCatalog()` / `importResolvedModelCatalog()` and `registerModelAlias()` transfer context/pricing resolutions across realms; `onAccountsChanged()` reports login/logout/switch events; provider constructors perform no environment I/O, and quota pollers stay dormant without an account.
|
|
12
14
|
- **Native session-file helpers** — `encodeClaudeWorkspacePath()` / `getClaudeSessionDirectory()` locate Claude Code's `~/.claude/projects/` directories with the real on-disk encoding, `findCodexRolloutFile()` resolves a Codex session id to its rollout path, `readCodexHistory()` tail-reads `~/.codex/history.jsonl`, `listSessionPreviews()` / `readSessionPreview()` build a cheap stat-first recent-sessions index across providers, and `parseMcpToolName()` splits `mcp__<server>__<tool>` identifiers (browser-safe).
|
|
13
15
|
- **Non-blocking account APIs & locked sync writers** — every login/switch entry point gains an async variant (`switchAccountAsync()`, `getAccountLoginStatusAsync()`, `finalizeAccountLoginAsync()`, `prepareCodexAccountAsync()`, `finalizeCodexAccountAsync()`, `switchToCodexAccountAsync()`) that keeps `codex` CLI probes off the event loop, and synchronous callers get the same cross-process store lock via `atomicWriteFileSync()` / `updateJsonStoreAtomicSync()` / `withFileLockSync()`, with progress-based timeouts and abandoned-lock reclaim.
|
|
14
16
|
- **Canonical usage and pricing** — provider-specific counters normalize into disjoint input/cache/output/reasoning categories with `normalizeProviderUsage()`; `calculateNormalizedUsageCost()` prevents cache/reasoning double counting and reports pricing provenance.
|
|
@@ -41,16 +43,19 @@ shelling out to an executable **`sqlite3` on `PATH`**. There is no bundled
|
|
|
41
43
|
driver. `CodexProvider` shells out to the same binary, but only for an optional
|
|
42
44
|
index.
|
|
43
45
|
|
|
44
|
-
Without it
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
Without it, database-backed reads still return results rather than throwing —
|
|
47
|
+
but as of 0.25.0 the missing binary is no longer invisible: Codex and OpenCode
|
|
48
|
+
attach a `sqlite_missing` diagnostic to affected operations, so an unavailable
|
|
49
|
+
runtime is distinguishable from an empty workspace. Construct providers through
|
|
50
|
+
`createSessionProviders({ onDiagnostic })` to receive those diagnostics, or ask
|
|
51
|
+
any provider directly — `getRuntimeStatus()` is implemented by all three:
|
|
47
52
|
|
|
48
53
|
```typescript
|
|
49
54
|
const status = provider.getRuntimeStatus?.();
|
|
50
55
|
// status.kind: 'available' | 'db_missing' | 'sqlite_missing'
|
|
51
|
-
// | 'sqlite_blocked' | 'query_failed'
|
|
56
|
+
// | 'sqlite_blocked' | 'query_failed' | ...
|
|
52
57
|
if (status && status.kind !== 'available') {
|
|
53
|
-
console.warn(`Session data
|
|
58
|
+
console.warn(`Session data degraded: ${status.kind}`);
|
|
54
59
|
}
|
|
55
60
|
```
|
|
56
61
|
|
|
@@ -78,56 +83,56 @@ returns the platform default that both overrides bypass.
|
|
|
78
83
|
|
|
79
84
|
## API Overview
|
|
80
85
|
|
|
81
|
-
| Module | Description
|
|
82
|
-
| ------------------------------- |
|
|
83
|
-
| **Types** | Session events, OpenCode/Codex format types, persistence schemas (tasks, decisions, notes, plans, historical data)
|
|
84
|
-
| **Paths** | Config directory resolution, project data paths, workspace encoding
|
|
85
|
-
| **Readers** | Read tasks, decisions, notes, history, handoff, and plans from `~/.config/sidekick/`
|
|
86
|
-
| **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
|
|
87
|
-
| **Providers** | Session provider abstraction with Claude Code, OpenCode, and Codex implementations; auto-detection via filesystem
|
|
88
|
-
| **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`)
|
|
89
|
-
| **Parsers** | JSONL event parsing, OpenCode/Codex format normalization, subagent scanning, session path resolution, debug log parsing
|
|
90
|
-
| **Watchers** | Live session file watching with event bridging, plus `createJsonlTail()` for raw incremental JSONL consumers
|
|
91
|
-
| **Formatters** | Display helpers (`formatTokenCount()`, `formatDurationMs()`), tool summary, noise classification, session dump (text/markdown/JSON), event highlighting
|
|
92
|
-
| **Search** | Cross-session full-text search, advanced filtering (substring, fuzzy, regex, date)
|
|
93
|
-
| **Aggregation** | Event aggregation, frequency tracking, activity heatmaps, pattern extraction
|
|
94
|
-
| **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()`)
|
|
95
|
-
| **Error History** | Shared error taxonomy (`categorizeError()`, `extractErrorMessage()`) plus append-only per-project failure history (`appendErrorHistory()`, `readErrorHistory()`, `getTopFailingTools()`) behind per-tool/hour/model error rollups
|
|
96
|
-
| **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)
|
|
97
|
-
| **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
|
|
98
|
-
| **Report** | Self-contained HTML session report generation
|
|
99
|
-
| **Credentials** | Claude Max OAuth credential reading from `~/.claude/.credentials.json`
|
|
100
|
-
| **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
|
|
101
|
-
| **Provider Status** | API health checking via status.claude.com and status.openai.com (indicator, components, incidents)
|
|
102
|
-
| **Doctor** | Typed cross-provider health diagnostics — `runDoctor()` checks project identity, sessions, accounts, providers, and dependencies into a `HealthReport`; `formatHealthReport()` renders it and `getSessionDiagnostics()` exposes the per-provider session probe
|
|
103
|
-
| **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,
|
|
104
|
-
| **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
|
|
105
|
-
| **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 / Opus 4.8 / Opus 4.7 / Sonnet 4.7 1M and GPT-5.x 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()`,
|
|
106
|
-
| **Quota Polling** | `QuotaPoller` class with exponential backoff, active/idle intervals, and cached fallback
|
|
107
|
-
| **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
|
|
108
|
-
| **Accounts** | Multi-provider account registry (v2) with per-provider active account, save/switch/remove, v1 migration, `ensureDefaultAccounts()` for first-run bootstrap of the active system Claude/Codex credentials as a "Default" saved account, `getActiveAccountStatus()` for a single-pass active-account read across providers, and live-first `resolveActiveClaudeAccount()` / `resolveActiveCodexAccount()` that report the currently logged-in account (self-healing the saved pointer) for display
|
|
109
|
-
| **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. 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
|
|
110
|
-
| **Terminal sync & auto-switch** | Opt-in terminal profile pointers, shell-hook/launcher helpers, and a default-off `AutoSwitchController` (`decideAutoSwitch()`) that moves to a healthier saved account when quota crosses a configured threshold; its `switchAccount` callback may return a Promise and defaults to `switchAccountAsync()`
|
|
111
|
-
| **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
|
|
112
|
-
| **Quota Snapshots** | Persistent quota caching per provider/account for offline fallback
|
|
113
|
-
| **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
|
|
114
|
-
| **Phrases** | Curated humorous phrases for loading/idle states, available as a flat `ALL_PHRASES` array or grouped via `PHRASE_CATEGORIES` for category-aware UI
|
|
86
|
+
| Module | Description |
|
|
87
|
+
| ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
88
|
+
| **Types** | Session events, OpenCode/Codex format types, persistence schemas (tasks, decisions, notes, plans, historical data) |
|
|
89
|
+
| **Paths** | Config directory resolution, project data paths, workspace encoding |
|
|
90
|
+
| **Readers** | Read tasks, decisions, notes, history, handoff, and plans from `~/.config/sidekick/` |
|
|
91
|
+
| **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 |
|
|
92
|
+
| **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 |
|
|
93
|
+
| **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`) |
|
|
94
|
+
| **Parsers** | JSONL event parsing, OpenCode/Codex format normalization, subagent scanning, session path resolution, debug log parsing |
|
|
95
|
+
| **Watchers** | Live session file watching with event bridging, plus `createJsonlTail()` for raw incremental JSONL consumers |
|
|
96
|
+
| **Formatters** | Display helpers (`formatTokenCount()`, `formatDurationMs()`), tool summary, noise classification, session dump (text/markdown/JSON), event highlighting |
|
|
97
|
+
| **Search** | Cross-session full-text search, advanced filtering (substring, fuzzy, regex, date) |
|
|
98
|
+
| **Aggregation** | Event aggregation, frequency tracking, activity heatmaps, pattern extraction |
|
|
99
|
+
| **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()`) |
|
|
100
|
+
| **Error History** | Shared error taxonomy (`categorizeError()`, `extractErrorMessage()`) plus append-only per-project failure history (`appendErrorHistory()`, `readErrorHistory()`, `getTopFailingTools()`) behind per-tool/hour/model error rollups |
|
|
101
|
+
| **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) |
|
|
102
|
+
| **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 |
|
|
103
|
+
| **Report** | Self-contained HTML session report generation |
|
|
104
|
+
| **Credentials** | Claude Max OAuth credential reading from `~/.claude/.credentials.json` |
|
|
105
|
+
| **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 |
|
|
106
|
+
| **Provider Status** | API health checking via status.claude.com and status.openai.com (indicator, components, incidents) |
|
|
107
|
+
| **Doctor** | Typed cross-provider health diagnostics — `runDoctor()` checks project identity, sessions, accounts, providers, and dependencies into a `HealthReport`; `formatHealthReport()` renders it and `getSessionDiagnostics()` exposes the per-provider session probe |
|
|
108
|
+
| **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`) — 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 |
|
|
109
|
+
| **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 |
|
|
110
|
+
| **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 / Opus 4.8 / Opus 4.7 / Sonnet 4.7 1M and GPT-5.x 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 |
|
|
111
|
+
| **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 |
|
|
112
|
+
| **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 |
|
|
113
|
+
| **Accounts** | Multi-provider account registry (v2) with per-provider active account, save/switch/remove, v1 migration, `ensureDefaultAccounts()` for first-run bootstrap of the active system Claude/Codex credentials as a "Default" saved account, `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 |
|
|
114
|
+
| **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. 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 |
|
|
115
|
+
| **Terminal sync & auto-switch** | Opt-in terminal profile pointers, shell-hook/launcher helpers, and a default-off `AutoSwitchController` (`decideAutoSwitch()`) that moves to a healthier saved account when quota crosses a configured threshold; its `switchAccount` callback may return a Promise and defaults to `switchAccountAsync()` |
|
|
116
|
+
| **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 |
|
|
117
|
+
| **Quota Snapshots** | Persistent quota caching per provider/account for offline fallback |
|
|
118
|
+
| **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 |
|
|
119
|
+
| **Phrases** | Curated humorous phrases for loading/idle states, available as a flat `ALL_PHRASES` array or grouped via `PHRASE_CATEGORIES` for category-aware UI |
|
|
115
120
|
|
|
116
121
|
## Supported import paths
|
|
117
122
|
|
|
118
|
-
`sidekick-shared` ships three public entry points plus a few convenience subpaths. Pick the one that matches your runtime.
|
|
123
|
+
`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.
|
|
119
124
|
|
|
120
|
-
| Path | Runtime | What it exposes
|
|
121
|
-
| ------------------------------ | -------------------------- |
|
|
122
|
-
| `sidekick-shared` | Node (CLI, extension host) | Full public API (readers, providers, parsers, pricing, …).
|
|
123
|
-
| `sidekick-shared/browser` | **Browser / webview** | Pure helpers: usage normalization/pricing, token estimation, transcript and assistant-turn projection, model/context math, `parseMcpToolName()`.
|
|
124
|
-
| `sidekick-shared/node` | Node only | LiteLLM pricing and context-window hydration, observed context-window persistence, recent-session/transcript reads, session previews (`listSessionPreviews()` / `readSessionPreview()`), Codex history/rollout lookup (`readCodexHistory()`, `findCodexRolloutFile()`), and resilient observed-session collection (`fs` + providers). |
|
|
125
|
-
| `sidekick-shared/phrases` | Any runtime | Phrase arrays + `getRandomPhrase()`.
|
|
126
|
-
| `sidekick-shared/modelContext` | Any runtime | Direct access to the context-window module.
|
|
127
|
-
| `sidekick-shared/modelInfo` | Any runtime | Direct access to model parsing and cost math.
|
|
128
|
-
| `sidekick-shared/formatting` | Any runtime | Direct access to pure token and duration display helpers.
|
|
129
|
-
| `sidekick-shared/schemas` | Any runtime | Pure Zod boundary schemas (session events, assistant turns, observed-session V1, quota, account status, quota history) — fs-free, no Node builtins.
|
|
130
|
-
| `sidekick-shared/statusline` | Node only | Cache-only statusline surface: account status + quota snapshot readers, `formatStatusline()`, and burn-rate estimates.
|
|
125
|
+
| Path | Runtime | What it exposes |
|
|
126
|
+
| ------------------------------ | -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
127
|
+
| `sidekick-shared` | Node (CLI, extension host) | Full public API (readers, providers, parsers, pricing, …). |
|
|
128
|
+
| `sidekick-shared/browser` | **Browser / webview** | Pure helpers: usage normalization/pricing, token estimation, transcript and assistant-turn projection, model/context math, `parseMcpToolName()`. |
|
|
129
|
+
| `sidekick-shared/node` | Node only | LiteLLM pricing and context-window hydration, observed context-window persistence, recent-session/transcript reads, session previews (`listSessionPreviews()` / `readSessionPreview()` plus the bounded-concurrency `listSessionPreviewsAsync()` / `readSessionPreviewAsync()`), Codex history/rollout lookup (`readCodexHistory()`, `findCodexRolloutFile()`), and resilient observed-session collection (`fs` + providers). |
|
|
130
|
+
| `sidekick-shared/phrases` | Any runtime | Phrase arrays + `getRandomPhrase()`. |
|
|
131
|
+
| `sidekick-shared/modelContext` | Any runtime | Direct access to the context-window module. |
|
|
132
|
+
| `sidekick-shared/modelInfo` | Any runtime | Direct access to model parsing and cost math. |
|
|
133
|
+
| `sidekick-shared/formatting` | Any runtime | Direct access to pure token and duration display helpers. |
|
|
134
|
+
| `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. |
|
|
135
|
+
| `sidekick-shared/statusline` | Node only | Cache-only statusline surface: account status + quota snapshot readers, `formatStatusline()`, and burn-rate estimates. |
|
|
131
136
|
|
|
132
137
|
### Browser / webview runtimes
|
|
133
138
|
|
|
@@ -169,6 +174,31 @@ await loadObservedContextWindows();
|
|
|
169
174
|
|
|
170
175
|
Both calls always resolve; a network failure, missing cache, or malformed store leaves lookups on the static tables. To record a window a provider reports at runtime, call `recordObservedContextWindow(modelId, window)` — it persists only when the value changes.
|
|
171
176
|
|
|
177
|
+
### Transfer resolved model data across realms
|
|
178
|
+
|
|
179
|
+
A realm that can hydrate (extension host, CLI) can hand its resolved context
|
|
180
|
+
windows and pricing to one that cannot (webview, browser bundle) as one
|
|
181
|
+
serializable snapshot — no private hooks, no re-hydration:
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
// Node side (after hydratePricingCatalog / loadObservedContextWindows):
|
|
185
|
+
import { exportResolvedModelCatalog } from 'sidekick-shared';
|
|
186
|
+
const snapshot = exportResolvedModelCatalog(); // or pass specific model ids
|
|
187
|
+
|
|
188
|
+
// Browser side:
|
|
189
|
+
import { importResolvedModelCatalog, registerModelAlias } from 'sidekick-shared/browser';
|
|
190
|
+
const { imported, diagnostics } = importResolvedModelCatalog(snapshot);
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Entries carry provenance (`source`, `match`, `matchedModelId`, and
|
|
194
|
+
`inheritedByPrefix` for pricing), so consumers can reject prefix-inherited
|
|
195
|
+
guesses. Unresolved entries (`default` window / `none` pricing) are skipped on
|
|
196
|
+
import rather than pinned, successive partial imports merge, and imported data
|
|
197
|
+
ranks below anything the importing realm has observed first-hand.
|
|
198
|
+
`registerModelAlias('sonnet', 'claude-sonnet-5')` resolves short or
|
|
199
|
+
host-specific names everywhere lookups happen; data recorded under the alias id
|
|
200
|
+
itself keeps winning over the canonical fallback.
|
|
201
|
+
|
|
172
202
|
### Normalize and price provider usage
|
|
173
203
|
|
|
174
204
|
```typescript
|
|
@@ -209,6 +239,32 @@ const estimate = estimateTextTokens(sourceCode, {
|
|
|
209
239
|
|
|
210
240
|
`sidekick-fallback-v1` counts Latin/source-code text at 3.5 characters per token, CJK code points at one token, emoji at two, and other non-ASCII code points at one. Empty input is zero. An injected finite, nonnegative exact result takes precedence.
|
|
211
241
|
|
|
242
|
+
### Read session stats for any provider
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
import { createSessionProviders, readSessionFileStats } from 'sidekick-shared';
|
|
246
|
+
|
|
247
|
+
const { providers } = createSessionProviders();
|
|
248
|
+
for (const provider of providers) {
|
|
249
|
+
for (const sessionPath of provider.findAllSessions(process.cwd())) {
|
|
250
|
+
const stats = provider.readSessionStats(sessionPath); // same as readSessionFileStats(provider, sessionPath)
|
|
251
|
+
if (stats.availability === 'unavailable') {
|
|
252
|
+
console.warn(`${provider.id}: ${stats.unavailableReason}`);
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
console.log(
|
|
256
|
+
stats.label,
|
|
257
|
+
stats.modelUsage,
|
|
258
|
+
stats.costUsd,
|
|
259
|
+
stats.costProvenance,
|
|
260
|
+
stats.toolFailures,
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
Every provider computes `SessionFileStats` the same way — one reader pass through the shared `EventAggregator` — so per-model `tokens` are cache-inclusive, `costUsd` carries `costProvenance`, `toolFailures` splits out failed calls, and compaction and truncation counts are real for Claude Code, Codex, and OpenCode alike. `computeSessionFileStats(events, options)` does the same for events you already hold, and `firstUserPrompt(events)` derives the label.
|
|
267
|
+
|
|
212
268
|
### Read canonical session history
|
|
213
269
|
|
|
214
270
|
```typescript
|
|
@@ -274,6 +330,17 @@ const previews = listSessionPreviews([new ClaudeCodeProvider(), new CodexProvide
|
|
|
274
330
|
|
|
275
331
|
`readSessionPreview(provider, filePath)` reads the same bounded preview for one known session file — `null` when the file is missing or unreadable, `null` fields when its content is malformed.
|
|
276
332
|
|
|
333
|
+
Hosts with a UI loop should prefer the async variants from `sidekick-shared/node`. They read with bounded concurrency, yield between files, batch Codex/OpenCode label lookups into non-blocking `sqlite3` subprocesses, and — unlike their sync twins — return degradation status alongside the data:
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
import { listSessionPreviewsAsync, readSessionPreviewAsync } from 'sidekick-shared/node';
|
|
337
|
+
|
|
338
|
+
const { previews, diagnostics, hasMore } = await listSessionPreviewsAsync(providers, { limit: 10 });
|
|
339
|
+
const { preview } = await readSessionPreviewAsync(provider, filePath);
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
To resolve one session id to its file without scanning, every bundled provider implements `findSessionById(workspacePath, sessionId)` — a filename join for Claude Code and Codex, a database lookup for OpenCode — returning `null` for misses instead of reading transcripts. (It is optional on the `SessionProviderBase` interface so pre-0.25 external implementations keep compiling; fall back to `findAllSessions()` when absent.)
|
|
343
|
+
|
|
277
344
|
MCP tool identifiers (`mcp__<server>__<tool>`) split with `parseMcpToolName(name)` — first-double-underscore rule, `null` for malformed names, safe in browser bundles.
|
|
278
345
|
|
|
279
346
|
### Collect observed sessions with isolation and retry
|
|
@@ -299,10 +366,55 @@ applyReadOnlyProjection(source.capabilities);
|
|
|
299
366
|
|
|
300
367
|
Discovery and reads are isolated, retries use bounded 30-second-to-5-minute exponential backoff, file changes bypass delay, duplicate identical failures are suppressed, and recovery emits a content-free diagnostic. Clock and fingerprint callbacks are injectable for deterministic hosts/tests.
|
|
301
368
|
|
|
369
|
+
As of 0.25.0, parses are cached by fingerprint — a second `collect()` over unchanged files performs no content reads (cache hits refresh `observedAt` and activity state; `contentObservedAt` reports how old the cached usage/model data actually is, and `cacheHit` marks them). Observations expose structured `fingerprintParts: { sizeBytes, mtimeMs }` alongside the opaque fingerprint string, and diagnostics carry `severity` and `phase` in addition to their existing `kind`.
|
|
370
|
+
|
|
371
|
+
Instead of a host polling loop, subscribe to debounced change batches. Each change carries the previous and current fingerprints; filesystem watching is combined with a catch-up poll for filesystems that drop events:
|
|
372
|
+
|
|
373
|
+
```typescript
|
|
374
|
+
const subscription = collector.subscribe(
|
|
375
|
+
(batch) => {
|
|
376
|
+
for (const change of batch.changes) {
|
|
377
|
+
// change.type: 'added' | 'changed' | 'removed'
|
|
378
|
+
console.log(batch.providerId, change.type, change.reference.sessionId);
|
|
379
|
+
console.log(change.previousFingerprint, '→', change.fingerprint);
|
|
380
|
+
}
|
|
381
|
+
void collector.collect(); // consume changed sessions through the parse cache
|
|
382
|
+
},
|
|
383
|
+
{ debounceMs: 250, pollIntervalMs: 30_000 },
|
|
384
|
+
);
|
|
385
|
+
// later: subscription.dispose();
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
`SessionMonitor.subscribe()` offers the same push model at the session-event level.
|
|
389
|
+
|
|
302
390
|
## Usage Examples
|
|
303
391
|
|
|
392
|
+
### Construct session providers for a long-lived host
|
|
393
|
+
|
|
394
|
+
`createSessionProviders()` builds every requested provider without probing the
|
|
395
|
+
filesystem or binaries — constructors are I/O-free as of 0.25.0, so one
|
|
396
|
+
unavailable provider cannot abort host startup. Environmental failures surface
|
|
397
|
+
on first use as structured diagnostics instead:
|
|
398
|
+
|
|
399
|
+
```typescript
|
|
400
|
+
import { createSessionProviders } from 'sidekick-shared';
|
|
401
|
+
|
|
402
|
+
const { providers, diagnostics } = createSessionProviders({
|
|
403
|
+
onDiagnostic: (diagnostic) => {
|
|
404
|
+
// e.g. { providerId: 'opencode', kind: 'sqlite_missing',
|
|
405
|
+
// severity: 'warning', phase: 'query', message: '...' }
|
|
406
|
+
log(diagnostic);
|
|
407
|
+
},
|
|
408
|
+
});
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
Repeated identical degradations are coalesced, so the diagnostic stream stays
|
|
412
|
+
bounded across long-lived polling and subscription cycles.
|
|
413
|
+
|
|
304
414
|
### Detect the active session provider
|
|
305
415
|
|
|
416
|
+
For a single-provider surface, auto-detect and construct just one:
|
|
417
|
+
|
|
306
418
|
```typescript
|
|
307
419
|
import {
|
|
308
420
|
detectProvider,
|
|
@@ -363,6 +475,25 @@ if (!peak.unavailable && peak.isPeak) {
|
|
|
363
475
|
}
|
|
364
476
|
```
|
|
365
477
|
|
|
478
|
+
### Resolve quota with one precedence
|
|
479
|
+
|
|
480
|
+
```typescript
|
|
481
|
+
import { resolveQuota } from 'sidekick-shared';
|
|
482
|
+
|
|
483
|
+
// Fresh persisted sample (status line, session, or API; under five minutes) →
|
|
484
|
+
// session logs (Codex rollouts) → provider API → older sample labelled by age.
|
|
485
|
+
const quota = await resolveQuota({ providerId: 'claude-code' });
|
|
486
|
+
console.log(quota.resolution); // 'snapshot-fresh' | 'session' | 'api' | 'snapshot-aging' | 'snapshot-stale' | 'unavailable'
|
|
487
|
+
console.log(quota.source, quota.capturedSource, quota.freshness, quota.ageMs);
|
|
488
|
+
|
|
489
|
+
// `--refresh` semantics: ask the API first.
|
|
490
|
+
await resolveQuota({ providerId: 'codex', workspacePath: process.cwd(), preferFresh: false });
|
|
491
|
+
// Paint from local samples only (a poller will fetch live data next).
|
|
492
|
+
await resolveQuota({ providerId: 'claude-code', allowApi: false });
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
`resolveQuota()` is what `sidekick quota`, `quota --all`, the MCP `get_quota_status` tool, and both dashboards call, so every surface reports the same numbers and the same `resolution`. Every side effect is injectable (`readSnapshot`, `writeSnapshot`, `fetchImpl`, `getClaudeAccessToken`, the account resolvers) for tests.
|
|
496
|
+
|
|
366
497
|
### Fetch subscription quota
|
|
367
498
|
|
|
368
499
|
```typescript
|
|
@@ -518,7 +649,7 @@ const parser = new JsonlParser(
|
|
|
518
649
|
parser.processChunk(rawData);
|
|
519
650
|
```
|
|
520
651
|
|
|
521
|
-
The boundary schemas — `sessionEventSchema` plus the quota, account-status, 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. `extractSessionEvents()` from the same subpath unwraps Claude Code `progress`-wrapped events into canonical `SessionEvent[]`.
|
|
652
|
+
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[]`.
|
|
522
653
|
|
|
523
654
|
### Tail raw JSONL events incrementally
|
|
524
655
|
|
|
@@ -594,6 +725,28 @@ console.log(status.claude.present, status.claude.email);
|
|
|
594
725
|
console.log(status.codex.present, status.codex.label);
|
|
595
726
|
```
|
|
596
727
|
|
|
728
|
+
To react to logins, logouts, and switches without polling that status yourself,
|
|
729
|
+
subscribe to `onAccountsChanged()`. It combines process-local mutation signals,
|
|
730
|
+
filesystem watches on the account stores, and a low-frequency catch-up poll —
|
|
731
|
+
and only emits when the status actually changed:
|
|
732
|
+
|
|
733
|
+
```typescript
|
|
734
|
+
import { onAccountsChanged } from 'sidekick-shared';
|
|
735
|
+
|
|
736
|
+
const subscription = onAccountsChanged(
|
|
737
|
+
({ reason, status }) => {
|
|
738
|
+
// reason: 'local' | 'filesystem' | 'poll'
|
|
739
|
+
render(status.claude, status.codex);
|
|
740
|
+
},
|
|
741
|
+
{ emitCurrent: true },
|
|
742
|
+
);
|
|
743
|
+
// later: subscription.dispose();
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
`QuotaPoller`, `MultiProviderQuotaService`, and `CodexQuotaWatcher` use the same
|
|
747
|
+
signal internally: they stay dormant while no matching account exists and wake
|
|
748
|
+
when one appears.
|
|
749
|
+
|
|
597
750
|
For display surfaces that must reflect the **currently logged-in** account — even after a native `claude /login` or `codex login` outside Sidekick — use the live-first resolvers. They prefer the live provider auth (`~/.claude/.claude.json` oauthAccount; the `~/.codex/auth.json` id_token JWT) over the saved `activeByProvider` pointer, fall back to the registry, and self-heal the saved pointer on an unambiguous match (best-effort, never creating or deleting profiles):
|
|
598
751
|
|
|
599
752
|
```typescript
|