sidekick-shared 0.26.0 → 0.26.2
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 +23 -0
- package/README.md +28 -6
- package/dist/doctor.d.ts +5 -0
- package/dist/doctor.js +69 -18
- package/dist/index.d.ts +1 -1
- package/dist/modelContext.js +8 -3
- package/dist/modelInfo.js +45 -27
- package/dist/providers/claudeCode.js +11 -2
- package/dist/providers/codex.js +17 -7
- package/dist/quotaPresentation.js +17 -5
- package/dist/quotaResolver.d.ts +6 -4
- package/dist/quotaResolver.js +36 -15
- package/dist/search/sessionSearch.d.ts +9 -5
- package/dist/search/sessionSearch.js +61 -90
- package/dist/watchers/factory.js +2 -8
- package/dist/watchers/jsonlTail.js +26 -2
- package/dist/watchers/jsonlWatcher.d.ts +5 -11
- package/dist/watchers/jsonlWatcher.js +21 -150
- package/dist/watchers/providerReaderWatcher.js +5 -3
- package/dist/watchers/sqliteWatcher.js +1 -0
- package/dist/watchers/types.d.ts +2 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,29 @@ 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.2] - 2026-09-06
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `DoctorOptions.provider` selects the session provider to diagnose; `HealthReport.sessionProvider` records it.
|
|
13
|
+
- Optional watcher `onBatchComplete` callback for persisting state after all events in a read have been delivered.
|
|
14
|
+
- `SessionSearchOptions.projectPath` and `signal` for provider-aware workspace filtering and cancellation; legacy `projectSlug` remains supported.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- JSONL watcher and provider reader checkpoints end at complete lines, retaining partial UTF-8 characters across reads and restarts. Codex restores parser context when seeking.
|
|
19
|
+
- Watcher failures retain polling fallback; OpenCode watchers share the canonical data-directory resolver.
|
|
20
|
+
- Session search enumerates through providers, includes database-only sessions, and deduplicates paths.
|
|
21
|
+
- Doctor checks the selected session provider, treats unrelated integrations as informational, and reads account status without changing account selection.
|
|
22
|
+
|
|
23
|
+
## [0.26.1] - 2026-09-05
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- Static model metadata verified on 2026-09-05 adds exact Fable 5.1 pricing/context entries in dashed and dotted forms, with its $0.25/M cache-read rate, and GPT-6 Astra pricing with a 1.05M context window. GPT-5.6 Sol/Terra/Luna rates and the Sol alias are current; GPT-5.4 mini cache reads cost $0.075/M and mini/nano context windows are 400K. Sonnet 5's permanent $2/$10 rate is documented. Catalog overrides, observed limits, and historical models are preserved.
|
|
28
|
+
- `resolveQuota({ preferFresh: false })` tries the API before local samples when API access is allowed. Failed live queries select the newer session or cached sample by capture time, prefer the cache on ties, and retain the API failure. Only a selected session sample is persisted.
|
|
29
|
+
- Codex quota failures name Codex instead of Claude or Anthropic. Default cache preferences and API-disabled resolution are unchanged.
|
|
30
|
+
|
|
8
31
|
## [0.26.0] - 2026-09-05
|
|
9
32
|
|
|
10
33
|
### Added
|
package/README.md
CHANGED
|
@@ -9,7 +9,8 @@ Types, parsers, providers, readers, formatters, aggregation, search, reporting,
|
|
|
9
9
|
|
|
10
10
|
## Recent additions
|
|
11
11
|
|
|
12
|
-
- **
|
|
12
|
+
- **0.26.2: reliable monitoring and search** — complete-line JSONL cursors preserve Unicode and Codex parser context; watcher `onBatchComplete` signals a delivered batch. `searchSessions()` adds workspace-path filtering and cancellation, and Doctor accepts an explicit session provider.
|
|
13
|
+
- **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()` shares quota resolution across CLI, MCP, and dashboard startup, with default local-sample preferences and an API-first option used by one-shot Codex queries; `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
14
|
- **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.
|
|
14
15
|
- **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).
|
|
15
16
|
- **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.
|
|
@@ -104,7 +105,7 @@ returns the platform default that both overrides bypass.
|
|
|
104
105
|
| **Credentials** | Claude Max OAuth credential reading from `~/.claude/.credentials.json` |
|
|
105
106
|
| **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
107
|
| **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()`
|
|
108
|
+
| **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 |
|
|
108
109
|
| **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
110
|
| **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
111
|
| **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 |
|
|
@@ -475,24 +476,25 @@ if (!peak.unavailable && peak.isPeak) {
|
|
|
475
476
|
}
|
|
476
477
|
```
|
|
477
478
|
|
|
478
|
-
### Resolve quota with
|
|
479
|
+
### Resolve quota with shared source policies
|
|
479
480
|
|
|
480
481
|
```typescript
|
|
481
482
|
import { resolveQuota } from 'sidekick-shared';
|
|
482
483
|
|
|
483
|
-
//
|
|
484
|
+
// Default: fresh persisted sample (status line, session, or API; under five minutes) →
|
|
484
485
|
// session logs (Codex rollouts) → provider API → older sample labelled by age.
|
|
485
486
|
const quota = await resolveQuota({ providerId: 'claude-code' });
|
|
486
487
|
console.log(quota.resolution); // 'snapshot-fresh' | 'session' | 'api' | 'snapshot-aging' | 'snapshot-stale' | 'unavailable'
|
|
487
488
|
console.log(quota.source, quota.capturedSource, quota.freshness, quota.ageMs);
|
|
488
489
|
|
|
489
|
-
//
|
|
490
|
+
// Ask the API before any local sample: used by every one-shot Codex query
|
|
491
|
+
// and by --refresh for other providers. API failures retain local fallbacks.
|
|
490
492
|
await resolveQuota({ providerId: 'codex', workspacePath: process.cwd(), preferFresh: false });
|
|
491
493
|
// Paint from local samples only (a poller will fetch live data next).
|
|
492
494
|
await resolveQuota({ providerId: 'claude-code', allowApi: false });
|
|
493
495
|
```
|
|
494
496
|
|
|
495
|
-
`resolveQuota()` is
|
|
497
|
+
`resolveQuota()` is shared by `sidekick quota`, `quota --all`, the MCP `get_quota_status` tool, and dashboard startup. CLI and MCP Codex queries pass `preferFresh: false` to fetch live utilization and reset credits every time; dashboard schedules and default cache preferences are unchanged. After a failed live query, the newer session or persisted sample wins by `capturedAt`, with ties going to the persisted sample and missing timestamps treated as oldest. Only a selected session sample is persisted. Fallback results keep their source and age plus the API `failure` descriptor. `allowApi: false` always prevents network access, including when `preferFresh` is false. Every side effect is injectable (`readSnapshot`, `writeSnapshot`, `fetchImpl`, `getClaudeAccessToken`, the account resolvers) for tests.
|
|
496
498
|
|
|
497
499
|
### Fetch subscription quota
|
|
498
500
|
|
|
@@ -669,6 +671,26 @@ const tail = createJsonlTail({
|
|
|
669
671
|
tail.start();
|
|
670
672
|
```
|
|
671
673
|
|
|
674
|
+
`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.
|
|
675
|
+
|
|
676
|
+
### Search a workspace and diagnose its provider
|
|
677
|
+
|
|
678
|
+
```typescript
|
|
679
|
+
import { CodexProvider, searchSessions, runDoctor } from 'sidekick-shared';
|
|
680
|
+
|
|
681
|
+
const provider = new CodexProvider();
|
|
682
|
+
const controller = new AbortController();
|
|
683
|
+
const matches = await searchSessions(provider, 'authentication', {
|
|
684
|
+
projectPath: '/path/to/project',
|
|
685
|
+
signal: controller.signal,
|
|
686
|
+
maxResults: 20,
|
|
687
|
+
});
|
|
688
|
+
const report = await runDoctor({ cwd: '/path/to/project', provider: 'codex' });
|
|
689
|
+
console.log(matches, report.sessionProvider, report.checks);
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
`projectPath` resolves the workspace canonically and takes precedence over the legacy `projectSlug`. Cancellation is checked between session reads; it does not interrupt an in-progress synchronous provider read. `HealthReport.sessionProvider` is optional for compatibility. `getSessionDiagnostics()` remains a Claude Code path diagnostic; use `runDoctor()` for provider-aware checks.
|
|
693
|
+
|
|
672
694
|
### Poll quota with backoff
|
|
673
695
|
|
|
674
696
|
```typescript
|
package/dist/doctor.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type ActiveAccountStatus } from './accountStatus';
|
|
2
2
|
import { type ProjectIdentity } from './paths';
|
|
3
3
|
import { type ProviderStatusState } from './providerStatus';
|
|
4
|
+
import type { ProviderId } from './providers/types';
|
|
4
5
|
import { type OpenCodeDbRuntimeStatus } from './providers/openCodeDatabase';
|
|
5
6
|
export type HealthCheckStatus = 'ok' | 'warning' | 'error' | 'info';
|
|
6
7
|
export interface HealthCheck {
|
|
@@ -38,10 +39,14 @@ export interface HealthReport {
|
|
|
38
39
|
claude: ProviderStatusState;
|
|
39
40
|
openai: ProviderStatusState;
|
|
40
41
|
};
|
|
42
|
+
/** Legacy Claude Code path diagnostics, retained for compatibility. */
|
|
41
43
|
sessions: SessionDiagnostics;
|
|
44
|
+
sessionProvider?: ProviderId;
|
|
42
45
|
}
|
|
43
46
|
export interface DoctorOptions {
|
|
44
47
|
cwd?: string;
|
|
48
|
+
/** Session provider to diagnose; defaults to auto-detection. */
|
|
49
|
+
provider?: ProviderId | 'auto';
|
|
45
50
|
deprecatedSettings?: DeprecatedSetting[];
|
|
46
51
|
openCodeStatus?: OpenCodeDbRuntimeStatus;
|
|
47
52
|
fetchStatuses?: () => Promise<{
|
package/dist/doctor.js
CHANGED
|
@@ -44,6 +44,8 @@ const paths_1 = require("./paths");
|
|
|
44
44
|
const sessionPathResolver_1 = require("./parsers/sessionPathResolver");
|
|
45
45
|
const providerStatus_1 = require("./providerStatus");
|
|
46
46
|
const openCode_1 = require("./providers/openCode");
|
|
47
|
+
const factory_1 = require("./providers/factory");
|
|
48
|
+
const detect_1 = require("./providers/detect");
|
|
47
49
|
const openCodeDatabase_1 = require("./providers/openCodeDatabase");
|
|
48
50
|
/** Collects the Claude Code path-resolution detail used by both host surfaces. */
|
|
49
51
|
function getSessionDiagnostics(workspacePath) {
|
|
@@ -100,15 +102,66 @@ function statusUnavailable() {
|
|
|
100
102
|
updatedAt: new Date().toISOString(),
|
|
101
103
|
};
|
|
102
104
|
}
|
|
105
|
+
async function checkSessionProvider(providerId, workspacePath) {
|
|
106
|
+
const { providers } = (0, factory_1.createSessionProviders)({ providerIds: [providerId] });
|
|
107
|
+
const provider = providers[0];
|
|
108
|
+
const title = `Session discovery (${provider?.displayName ?? providerId})`;
|
|
109
|
+
try {
|
|
110
|
+
if (!provider)
|
|
111
|
+
throw new Error(`${providerId} could not be initialized.`);
|
|
112
|
+
const sessions = provider.listSessionFilesAsync
|
|
113
|
+
? await provider.listSessionFilesAsync(workspacePath)
|
|
114
|
+
: provider.findAllSessions(workspacePath);
|
|
115
|
+
if (sessions.length > 0) {
|
|
116
|
+
return {
|
|
117
|
+
id: 'session_discovery',
|
|
118
|
+
status: 'ok',
|
|
119
|
+
title,
|
|
120
|
+
message: `Found ${sessions.length} ${provider.displayName} session${sessions.length === 1 ? '' : 's'} for ${workspacePath}.`,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
const operation = provider.getLastOperationStatus?.();
|
|
124
|
+
const runtime = provider.getRuntimeStatus?.();
|
|
125
|
+
const issue = operation && !operation.usable
|
|
126
|
+
? operation.runtimeStatus
|
|
127
|
+
: runtime && !runtime.available
|
|
128
|
+
? runtime
|
|
129
|
+
: undefined;
|
|
130
|
+
return {
|
|
131
|
+
id: 'session_discovery',
|
|
132
|
+
status: 'warning',
|
|
133
|
+
title,
|
|
134
|
+
message: issue?.message ?? `No ${provider.displayName} sessions found for ${workspacePath}.`,
|
|
135
|
+
repair: issue?.kind === 'sqlite_missing'
|
|
136
|
+
? 'Install sqlite3 and ensure it is on PATH, then rerun sidekick doctor.'
|
|
137
|
+
: `Start ${provider.displayName} in this project, or inspect ${provider.getSessionDirectory(workspacePath)}.`,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
return {
|
|
142
|
+
id: 'session_discovery',
|
|
143
|
+
status: 'warning',
|
|
144
|
+
title,
|
|
145
|
+
message: error instanceof Error ? error.message : String(error),
|
|
146
|
+
repair: `Check ${providerId} session storage permissions and rerun sidekick doctor.`,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
finally {
|
|
150
|
+
for (const candidate of providers)
|
|
151
|
+
candidate.dispose();
|
|
152
|
+
}
|
|
153
|
+
}
|
|
103
154
|
async function runDoctor(options = {}) {
|
|
104
155
|
const project = (0, paths_1.resolveProjectIdentity)(options.cwd);
|
|
105
156
|
const sessions = getSessionDiagnostics(project.cwd);
|
|
106
|
-
const accounts = (0, accountStatus_1.getActiveAccountStatus)();
|
|
157
|
+
const accounts = (0, accountStatus_1.getActiveAccountStatus)(undefined, { selfHeal: false });
|
|
158
|
+
const sessionProvider = options.provider && options.provider !== 'auto' ? options.provider : (0, detect_1.detectProvider)();
|
|
107
159
|
let openCode = options.openCodeStatus;
|
|
108
160
|
if (!openCode) {
|
|
109
161
|
const database = new openCodeDatabase_1.OpenCodeDatabase((0, openCode_1.getOpenCodeDataDir)());
|
|
110
162
|
database.open();
|
|
111
163
|
openCode = database.getRuntimeStatus();
|
|
164
|
+
database.close();
|
|
112
165
|
}
|
|
113
166
|
const providerStatus = await (options.fetchStatuses ??
|
|
114
167
|
(async () => {
|
|
@@ -130,20 +183,7 @@ async function runDoctor(options = {}) {
|
|
|
130
183
|
title: 'Project identity',
|
|
131
184
|
message: `Canonical slug: ${project.canonicalSlug}`,
|
|
132
185
|
});
|
|
133
|
-
checks.push(
|
|
134
|
-
? {
|
|
135
|
-
id: 'session_discovery',
|
|
136
|
-
status: 'ok',
|
|
137
|
-
title: 'Session discovery',
|
|
138
|
-
message: `Found Claude Code sessions at ${sessions.discoveredSessionDir}.`,
|
|
139
|
-
}
|
|
140
|
-
: {
|
|
141
|
-
id: 'session_discovery',
|
|
142
|
-
status: 'warning',
|
|
143
|
-
title: 'Session discovery',
|
|
144
|
-
message: `No Claude Code session directory found for ${project.cwd}.`,
|
|
145
|
-
repair: `Start Claude Code in this project, or inspect ${sessions.expectedSessionDir}.`,
|
|
146
|
-
});
|
|
186
|
+
checks.push(await checkSessionProvider(sessionProvider, project.resolvedCwd));
|
|
147
187
|
const openCodeCheck = openCode.available
|
|
148
188
|
? {
|
|
149
189
|
id: 'opencode_sqlite',
|
|
@@ -167,16 +207,22 @@ async function runDoctor(options = {}) {
|
|
|
167
207
|
? 'Install sqlite3 and ensure the sqlite3 executable is on PATH, then rerun sidekick doctor.'
|
|
168
208
|
: 'Make sqlite3 executable and verify the OpenCode database is readable.',
|
|
169
209
|
};
|
|
210
|
+
if (sessionProvider !== 'opencode' && openCodeCheck.status === 'warning') {
|
|
211
|
+
openCodeCheck.status = 'info';
|
|
212
|
+
openCodeCheck.message += ' This does not affect the selected session provider.';
|
|
213
|
+
}
|
|
170
214
|
checks.push(openCodeCheck);
|
|
171
215
|
checks.push({
|
|
172
216
|
id: 'accounts',
|
|
173
|
-
status: accounts.ok ? 'ok' : '
|
|
217
|
+
status: accounts.ok ? 'ok' : 'info',
|
|
174
218
|
title: 'Accounts and credentials',
|
|
175
219
|
message: accounts.ok
|
|
176
220
|
? `Detected ${[accounts.claude.present && 'Claude', accounts.codex.present && 'Codex'].filter(Boolean).join(' and ')} credentials.`
|
|
177
221
|
: (accounts.error ?? 'No Claude or Codex credentials were detected.'),
|
|
178
222
|
...(!accounts.ok
|
|
179
|
-
? {
|
|
223
|
+
? {
|
|
224
|
+
repair: 'Sign in with claude or codex to refresh account quota. Existing sessions can be monitored without saved credentials.',
|
|
225
|
+
}
|
|
180
226
|
: {}),
|
|
181
227
|
});
|
|
182
228
|
const unavailableProviders = Object.entries(providerStatus)
|
|
@@ -187,7 +233,11 @@ async function runDoctor(options = {}) {
|
|
|
187
233
|
.map(([name]) => name);
|
|
188
234
|
checks.push({
|
|
189
235
|
id: 'provider_api',
|
|
190
|
-
status: degradedProviders.
|
|
236
|
+
status: degradedProviders.includes(sessionProvider === 'codex' ? 'openai' : sessionProvider === 'claude-code' ? 'claude' : '')
|
|
237
|
+
? 'warning'
|
|
238
|
+
: degradedProviders.length > 0 || unavailableProviders.length > 0
|
|
239
|
+
? 'info'
|
|
240
|
+
: 'ok',
|
|
191
241
|
title: 'Provider API status',
|
|
192
242
|
message: degradedProviders.length > 0
|
|
193
243
|
? `Provider incidents reported for: ${degradedProviders.join(', ')}.`
|
|
@@ -226,6 +276,7 @@ async function runDoctor(options = {}) {
|
|
|
226
276
|
openCode,
|
|
227
277
|
providerStatus,
|
|
228
278
|
sessions,
|
|
279
|
+
sessionProvider,
|
|
229
280
|
};
|
|
230
281
|
}
|
|
231
282
|
function formatHealthReport(report) {
|
package/dist/index.d.ts
CHANGED
|
@@ -97,7 +97,7 @@ export type { DebugLogEntry, DebugLogFile, DebugLogLevel } from './parsers/debug
|
|
|
97
97
|
export { OpenCodeDatabase } from './providers/openCodeDatabase';
|
|
98
98
|
export { CodexDatabase } from './providers/codexDatabase';
|
|
99
99
|
export { searchSessions } from './search/sessionSearch';
|
|
100
|
-
export type { SearchResult } from './search/sessionSearch';
|
|
100
|
+
export type { SearchResult, SessionSearchOptions } from './search/sessionSearch';
|
|
101
101
|
export { FilterEngine } from './search/advancedFilter';
|
|
102
102
|
export type { FilterMode, FilterState, HighlightFormat as FilterHighlightFormat, } from './search/advancedFilter';
|
|
103
103
|
export { composeContext } from './context/composer';
|
package/dist/modelContext.js
CHANGED
|
@@ -42,9 +42,11 @@ exports._getModelContextIds = _getModelContextIds;
|
|
|
42
42
|
exports.resolveModelContextWindow = resolveModelContextWindow;
|
|
43
43
|
exports.getModelContextWindowSize = getModelContextWindowSize;
|
|
44
44
|
const modelAliases_1 = require("./modelAliases");
|
|
45
|
-
/** Known model context window sizes (in tokens). */
|
|
45
|
+
/** Known model context window sizes (in tokens); current models verified 2026-09-05. */
|
|
46
46
|
const MODEL_CONTEXT_SIZES = {
|
|
47
|
-
// Claude — native 1M context (Fable 5
|
|
47
|
+
// Claude — native 1M context (Fable 5+, Opus 4.6+, Sonnet 4.6+)
|
|
48
|
+
'claude-fable-5-1': 1_000_000,
|
|
49
|
+
'claude-fable-5.1': 1_000_000,
|
|
48
50
|
'claude-fable-5': 1_000_000,
|
|
49
51
|
'claude-opus-5': 1_000_000,
|
|
50
52
|
'claude-opus-4-8': 1_000_000,
|
|
@@ -67,16 +69,19 @@ const MODEL_CONTEXT_SIZES = {
|
|
|
67
69
|
'claude-3-haiku': 200_000,
|
|
68
70
|
// OpenAI GPT-4.1 series (1M context)
|
|
69
71
|
'gpt-4.1': 1_048_576,
|
|
70
|
-
// OpenAI GPT-5 series (keys sorted longest-first below; explicit entries
|
|
72
|
+
// OpenAI GPT-5/6 series (keys sorted longest-first below; explicit entries
|
|
71
73
|
// for every variant so prefix matching can't misclassify a new one).
|
|
72
74
|
// These are published model maxima. Codex reports a smaller *effective*
|
|
73
75
|
// window per account tier via token_count; that value wins through the
|
|
74
76
|
// observed-override layer.
|
|
77
|
+
'gpt-6-astra': 1_050_000,
|
|
75
78
|
'gpt-5.6-sol': 1_050_000,
|
|
76
79
|
'gpt-5.6-terra': 1_050_000,
|
|
77
80
|
'gpt-5.6-luna': 1_050_000,
|
|
78
81
|
'gpt-5.6': 1_050_000,
|
|
79
82
|
'gpt-5.5': 1_050_000,
|
|
83
|
+
'gpt-5.4-mini': 400_000,
|
|
84
|
+
'gpt-5.4-nano': 400_000,
|
|
80
85
|
'gpt-5.4': 1_050_000,
|
|
81
86
|
'gpt-5.3-codex-spark': 128_000,
|
|
82
87
|
'gpt-5.3-codex': 400_000,
|
package/dist/modelInfo.js
CHANGED
|
@@ -50,9 +50,10 @@ Object.defineProperty(exports, "MODEL_PROVIDER_IDS", { enumerable: true, get: fu
|
|
|
50
50
|
* against `claude-sonnet-4.5`.
|
|
51
51
|
*
|
|
52
52
|
* Sources:
|
|
53
|
-
* - Anthropic: https://
|
|
54
|
-
* - OpenAI: https://openai.com/api/
|
|
55
|
-
*
|
|
53
|
+
* - Anthropic: https://platform.claude.com/docs/en/about-claude/pricing
|
|
54
|
+
* - OpenAI: https://developers.openai.com/api/docs/models
|
|
55
|
+
* Current Anthropic and GPT-5.6/6 rates verified: 2026-09-05.
|
|
56
|
+
* Runtime LiteLLM hydration refreshes this offline baseline.
|
|
56
57
|
*
|
|
57
58
|
* Anthropic keys appear in both dashed (`claude-opus-4-8`, the real model-ID
|
|
58
59
|
* form) and dotted (`claude-opus-4.8`, the LiteLLM catalog form) spellings —
|
|
@@ -60,6 +61,19 @@ Object.defineProperty(exports, "MODEL_PROVIDER_IDS", { enumerable: true, get: fu
|
|
|
60
61
|
*/
|
|
61
62
|
const PRICING_TABLE = {
|
|
62
63
|
// ── Anthropic: Claude ──
|
|
64
|
+
// Fable 5.1 cache reads cost 0.025x input; Fable 5 retains its 0.1x rate.
|
|
65
|
+
'claude-fable-5-1': {
|
|
66
|
+
inputCostPerMillion: 10.0,
|
|
67
|
+
outputCostPerMillion: 50.0,
|
|
68
|
+
cacheWriteCostPerMillion: 12.5,
|
|
69
|
+
cacheReadCostPerMillion: 0.25,
|
|
70
|
+
},
|
|
71
|
+
'claude-fable-5.1': {
|
|
72
|
+
inputCostPerMillion: 10.0,
|
|
73
|
+
outputCostPerMillion: 50.0,
|
|
74
|
+
cacheWriteCostPerMillion: 12.5,
|
|
75
|
+
cacheReadCostPerMillion: 0.25,
|
|
76
|
+
},
|
|
63
77
|
'claude-fable-5': {
|
|
64
78
|
inputCostPerMillion: 10.0,
|
|
65
79
|
outputCostPerMillion: 50.0,
|
|
@@ -84,11 +98,8 @@ const PRICING_TABLE = {
|
|
|
84
98
|
cacheWriteCostPerMillion: 1.0,
|
|
85
99
|
cacheReadCostPerMillion: 0.08,
|
|
86
100
|
},
|
|
87
|
-
// Sonnet 5
|
|
88
|
-
//
|
|
89
|
-
// Sonnet 5 is $2/$10, not the $3/$15 every earlier Sonnet charged, and it
|
|
90
|
-
// carries no >200K surcharge the way Sonnet 4.5 does. The catalog agrees
|
|
91
|
-
// across all eleven of its Sonnet 5 entries.
|
|
101
|
+
// Sonnet 5's $2/$10 introductory rate is now permanent. Standard pricing
|
|
102
|
+
// applies across its full 1M context window.
|
|
92
103
|
'claude-sonnet-5': {
|
|
93
104
|
inputCostPerMillion: 2.0,
|
|
94
105
|
outputCostPerMillion: 10.0,
|
|
@@ -234,37 +245,44 @@ const PRICING_TABLE = {
|
|
|
234
245
|
cacheWriteCostPerMillion: 0,
|
|
235
246
|
cacheReadCostPerMillion: 0,
|
|
236
247
|
},
|
|
237
|
-
// ── OpenAI: GPT-5
|
|
238
|
-
// Published rates,
|
|
248
|
+
// ── OpenAI: GPT-5/6 families ──
|
|
249
|
+
// Published rates, cross-checked with the LiteLLM catalog. Runtime hydration
|
|
239
250
|
// still supersedes these; they are the offline baseline.
|
|
240
251
|
//
|
|
241
252
|
// Every tier that ships at a rate of its own needs an entry of its own. Keys
|
|
242
253
|
// are matched longest-first, so a `-mini`/`-nano`/`-pro` variant with no entry
|
|
243
254
|
// silently inherits its base model's rate and reports a wrong number rather
|
|
244
255
|
// than none — `gpt-5-nano` read 25x its true cost this way.
|
|
256
|
+
'gpt-6-astra': {
|
|
257
|
+
inputCostPerMillion: 10.0,
|
|
258
|
+
outputCostPerMillion: 50.0,
|
|
259
|
+
cacheWriteCostPerMillion: 12.5,
|
|
260
|
+
cacheReadCostPerMillion: 1.0,
|
|
261
|
+
},
|
|
245
262
|
'gpt-5.6-sol': {
|
|
246
|
-
inputCostPerMillion:
|
|
247
|
-
outputCostPerMillion:
|
|
248
|
-
cacheWriteCostPerMillion:
|
|
249
|
-
cacheReadCostPerMillion: 0.
|
|
263
|
+
inputCostPerMillion: 4.0,
|
|
264
|
+
outputCostPerMillion: 20.0,
|
|
265
|
+
cacheWriteCostPerMillion: 5.0,
|
|
266
|
+
cacheReadCostPerMillion: 0.4,
|
|
250
267
|
},
|
|
251
268
|
'gpt-5.6-terra': {
|
|
252
|
-
inputCostPerMillion: 2.
|
|
253
|
-
outputCostPerMillion:
|
|
254
|
-
cacheWriteCostPerMillion:
|
|
255
|
-
cacheReadCostPerMillion: 0.
|
|
269
|
+
inputCostPerMillion: 2.0,
|
|
270
|
+
outputCostPerMillion: 12.0,
|
|
271
|
+
cacheWriteCostPerMillion: 2.5,
|
|
272
|
+
cacheReadCostPerMillion: 0.2,
|
|
256
273
|
},
|
|
257
274
|
'gpt-5.6-luna': {
|
|
258
|
-
inputCostPerMillion:
|
|
259
|
-
outputCostPerMillion:
|
|
260
|
-
cacheWriteCostPerMillion:
|
|
261
|
-
cacheReadCostPerMillion: 0.
|
|
275
|
+
inputCostPerMillion: 0.2,
|
|
276
|
+
outputCostPerMillion: 1.2,
|
|
277
|
+
cacheWriteCostPerMillion: 0.25,
|
|
278
|
+
cacheReadCostPerMillion: 0.02,
|
|
262
279
|
},
|
|
280
|
+
// The GPT-5.6 alias routes to Sol.
|
|
263
281
|
'gpt-5.6': {
|
|
264
|
-
inputCostPerMillion:
|
|
265
|
-
outputCostPerMillion:
|
|
266
|
-
cacheWriteCostPerMillion:
|
|
267
|
-
cacheReadCostPerMillion: 0.
|
|
282
|
+
inputCostPerMillion: 4.0,
|
|
283
|
+
outputCostPerMillion: 20.0,
|
|
284
|
+
cacheWriteCostPerMillion: 5.0,
|
|
285
|
+
cacheReadCostPerMillion: 0.4,
|
|
268
286
|
},
|
|
269
287
|
'gpt-5.5-pro': {
|
|
270
288
|
inputCostPerMillion: 30.0,
|
|
@@ -282,7 +300,7 @@ const PRICING_TABLE = {
|
|
|
282
300
|
inputCostPerMillion: 0.75,
|
|
283
301
|
outputCostPerMillion: 4.5,
|
|
284
302
|
cacheWriteCostPerMillion: 0,
|
|
285
|
-
cacheReadCostPerMillion: 0.
|
|
303
|
+
cacheReadCostPerMillion: 0.075,
|
|
286
304
|
},
|
|
287
305
|
'gpt-5.4-nano': {
|
|
288
306
|
inputCostPerMillion: 0.2,
|
|
@@ -136,6 +136,8 @@ class ClaudeCodeReader {
|
|
|
136
136
|
sessionPath;
|
|
137
137
|
parser;
|
|
138
138
|
filePosition = 0;
|
|
139
|
+
/** Cursor after the last complete line, safe to persist across restarts. */
|
|
140
|
+
committedPosition = 0;
|
|
139
141
|
events = [];
|
|
140
142
|
_wasTruncated = false;
|
|
141
143
|
decoder = new string_decoder_1.StringDecoder('utf8');
|
|
@@ -165,6 +167,7 @@ class ClaudeCodeReader {
|
|
|
165
167
|
if (currentSize < this.filePosition) {
|
|
166
168
|
this._wasTruncated = true;
|
|
167
169
|
this.filePosition = 0;
|
|
170
|
+
this.committedPosition = 0;
|
|
168
171
|
this.parser.reset();
|
|
169
172
|
this.decoder = new string_decoder_1.StringDecoder('utf8');
|
|
170
173
|
}
|
|
@@ -183,7 +186,11 @@ class ClaudeCodeReader {
|
|
|
183
186
|
finally {
|
|
184
187
|
fs.closeSync(fd);
|
|
185
188
|
}
|
|
186
|
-
const
|
|
189
|
+
const bytes = buffer.subarray(0, bytesRead);
|
|
190
|
+
const newline = bytes.lastIndexOf(0x0a);
|
|
191
|
+
if (newline >= 0)
|
|
192
|
+
this.committedPosition = this.filePosition + newline + 1;
|
|
193
|
+
const chunk = this.decoder.write(bytes);
|
|
187
194
|
this.parser.processChunk(chunk);
|
|
188
195
|
this.filePosition += bytesRead;
|
|
189
196
|
}
|
|
@@ -198,6 +205,7 @@ class ClaudeCodeReader {
|
|
|
198
205
|
}
|
|
199
206
|
reset() {
|
|
200
207
|
this.filePosition = 0;
|
|
208
|
+
this.committedPosition = 0;
|
|
201
209
|
this.parser.reset();
|
|
202
210
|
this.decoder = new string_decoder_1.StringDecoder('utf8');
|
|
203
211
|
this._wasTruncated = false;
|
|
@@ -213,10 +221,11 @@ class ClaudeCodeReader {
|
|
|
213
221
|
this.decoder = new string_decoder_1.StringDecoder('utf8');
|
|
214
222
|
}
|
|
215
223
|
getPosition() {
|
|
216
|
-
return this.
|
|
224
|
+
return this.committedPosition;
|
|
217
225
|
}
|
|
218
226
|
seekTo(position) {
|
|
219
227
|
this.filePosition = position;
|
|
228
|
+
this.committedPosition = position;
|
|
220
229
|
this.parser.reset();
|
|
221
230
|
this.decoder = new string_decoder_1.StringDecoder('utf8');
|
|
222
231
|
}
|
package/dist/providers/codex.js
CHANGED
|
@@ -473,6 +473,8 @@ class CodexReader {
|
|
|
473
473
|
onTokenUsage;
|
|
474
474
|
parser;
|
|
475
475
|
filePosition = 0;
|
|
476
|
+
/** Cursor after the last complete line, safe to persist across restarts. */
|
|
477
|
+
committedPosition = 0;
|
|
476
478
|
lineBuffer = '';
|
|
477
479
|
_wasTruncated = false;
|
|
478
480
|
decoder = new string_decoder_1.StringDecoder('utf8');
|
|
@@ -487,18 +489,19 @@ class CodexReader {
|
|
|
487
489
|
getSessionMeta() {
|
|
488
490
|
return this.parser.getSessionMeta();
|
|
489
491
|
}
|
|
490
|
-
readNew() {
|
|
492
|
+
readNew(endPosition) {
|
|
491
493
|
this._wasTruncated = false;
|
|
492
494
|
const events = [];
|
|
493
495
|
try {
|
|
494
496
|
if (!fs.existsSync(this.rolloutPath))
|
|
495
497
|
return [];
|
|
496
498
|
const stats = fs.statSync(this.rolloutPath);
|
|
497
|
-
const currentSize = stats.size;
|
|
499
|
+
const currentSize = Math.min(stats.size, endPosition ?? stats.size);
|
|
498
500
|
// Handle truncation
|
|
499
501
|
if (currentSize < this.filePosition) {
|
|
500
502
|
this._wasTruncated = true;
|
|
501
503
|
this.filePosition = 0;
|
|
504
|
+
this.committedPosition = 0;
|
|
502
505
|
this.lineBuffer = '';
|
|
503
506
|
this.parser.reset();
|
|
504
507
|
this.decoder = new string_decoder_1.StringDecoder('utf8');
|
|
@@ -517,7 +520,11 @@ class CodexReader {
|
|
|
517
520
|
finally {
|
|
518
521
|
fs.closeSync(fd);
|
|
519
522
|
}
|
|
520
|
-
const
|
|
523
|
+
const bytes = buffer.subarray(0, bytesRead);
|
|
524
|
+
const newline = bytes.lastIndexOf(0x0a);
|
|
525
|
+
if (newline >= 0)
|
|
526
|
+
this.committedPosition = this.filePosition + newline + 1;
|
|
527
|
+
const chunk = this.decoder.write(bytes);
|
|
521
528
|
this.filePosition += bytesRead;
|
|
522
529
|
// Process lines
|
|
523
530
|
const text = this.lineBuffer + chunk;
|
|
@@ -583,6 +590,7 @@ class CodexReader {
|
|
|
583
590
|
}
|
|
584
591
|
reset() {
|
|
585
592
|
this.filePosition = 0;
|
|
593
|
+
this.committedPosition = 0;
|
|
586
594
|
this.lineBuffer = '';
|
|
587
595
|
this.parser.reset();
|
|
588
596
|
this.decoder = new string_decoder_1.StringDecoder('utf8');
|
|
@@ -606,12 +614,14 @@ class CodexReader {
|
|
|
606
614
|
this.decoder = new string_decoder_1.StringDecoder('utf8');
|
|
607
615
|
}
|
|
608
616
|
getPosition() {
|
|
609
|
-
return this.
|
|
617
|
+
return this.committedPosition;
|
|
610
618
|
}
|
|
611
619
|
seekTo(position) {
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
this.
|
|
620
|
+
// Rebuild model, turn, and cumulative-token context before resuming.
|
|
621
|
+
// The prefix is parsed only; its events are already in the consumer snapshot.
|
|
622
|
+
this.reset();
|
|
623
|
+
if (position > 0)
|
|
624
|
+
this.readNew(position);
|
|
615
625
|
}
|
|
616
626
|
wasTruncated() {
|
|
617
627
|
return this._wasTruncated;
|
|
@@ -23,8 +23,20 @@ function formatRetryAfter(ms) {
|
|
|
23
23
|
function describeQuotaFailure(quota) {
|
|
24
24
|
if (!quota || quota.available || !quota.failureKind)
|
|
25
25
|
return null;
|
|
26
|
+
const isCodex = quota.providerId === 'codex';
|
|
27
|
+
const apiName = isCodex ? 'Codex' : 'Anthropic';
|
|
26
28
|
switch (quota.failureKind) {
|
|
27
29
|
case 'auth':
|
|
30
|
+
if (isCodex) {
|
|
31
|
+
return {
|
|
32
|
+
severity: 'error',
|
|
33
|
+
title: quota.httpStatus ? 'Codex sign-in expired' : 'Codex sign-in required',
|
|
34
|
+
message: quota.error ?? 'The current Codex login could not be used.',
|
|
35
|
+
detail: 'Run `codex login` to sign in, then retry quota refresh.',
|
|
36
|
+
alertKey: `auth:${quota.httpStatus ?? 'no-credentials'}`,
|
|
37
|
+
isRetryable: false,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
28
40
|
if (quota.error === 'No OAuth token available') {
|
|
29
41
|
return {
|
|
30
42
|
severity: 'error',
|
|
@@ -47,7 +59,7 @@ function describeQuotaFailure(quota) {
|
|
|
47
59
|
return {
|
|
48
60
|
severity: 'warning',
|
|
49
61
|
title: 'Quota API unreachable',
|
|
50
|
-
message:
|
|
62
|
+
message: `Could not reach ${apiName} from the current environment.`,
|
|
51
63
|
detail: 'Check connectivity, proxy, or firewall settings, then retry.',
|
|
52
64
|
alertKey: 'network',
|
|
53
65
|
isRetryable: true,
|
|
@@ -59,7 +71,7 @@ function describeQuotaFailure(quota) {
|
|
|
59
71
|
message: quota.retryAfterMs != null
|
|
60
72
|
? `Retry in ${formatRetryAfter(quota.retryAfterMs)}.`
|
|
61
73
|
: 'Retry shortly.',
|
|
62
|
-
detail: quota.httpStatus != null ?
|
|
74
|
+
detail: quota.httpStatus != null ? `${apiName} returned HTTP ${quota.httpStatus}.` : undefined,
|
|
63
75
|
alertKey: `rate_limit:${quota.httpStatus ?? 429}`,
|
|
64
76
|
isRetryable: true,
|
|
65
77
|
};
|
|
@@ -68,8 +80,8 @@ function describeQuotaFailure(quota) {
|
|
|
68
80
|
severity: 'warning',
|
|
69
81
|
title: 'Quota API unavailable',
|
|
70
82
|
message: quota.httpStatus != null
|
|
71
|
-
?
|
|
72
|
-
:
|
|
83
|
+
? `${apiName} returned HTTP ${quota.httpStatus}. Try again shortly.`
|
|
84
|
+
: `${apiName} quota data is temporarily unavailable.`,
|
|
73
85
|
alertKey: `server:${quota.httpStatus ?? 'unknown'}`,
|
|
74
86
|
isRetryable: true,
|
|
75
87
|
};
|
|
@@ -78,7 +90,7 @@ function describeQuotaFailure(quota) {
|
|
|
78
90
|
severity: 'error',
|
|
79
91
|
title: 'Unexpected quota response',
|
|
80
92
|
message: quota.httpStatus != null
|
|
81
|
-
?
|
|
93
|
+
? `${apiName} returned HTTP ${quota.httpStatus}.`
|
|
82
94
|
: (quota.error ?? 'Quota data could not be retrieved.'),
|
|
83
95
|
detail: 'This failure is not classified as retryable.',
|
|
84
96
|
alertKey: `unknown:${quota.httpStatus ?? 'none'}`,
|