Concurrent omp --session restores after an unclean shutdown crashed in SqliteAuthCredentialStore.#initializeSchema() with SQLITE_BUSY_RECOVERY because the multi-statement schema run installed PRAGMA busy_timeout=5000 AFTER PRAGMA journal_mode=WAL, the first lock-taking statement during WAL recovery. Bun's default busy_timeout is 0, so the lock conflict surfaces immediately. - packages/ai/src/auth-storage.ts: hoisted PRAGMA busy_timeout to a standalone first statement, dropped it from the multi-statement schema run, wrapped SqliteAuthCredentialStore.open() in a 4-attempt exponential-backoff retry loop on the SQLITE_BUSY family, and the exhausted-retry error now includes the DB path. Exported isSqliteBusyError(err) (matches code prefix 'SQLITE_BUSY'). - packages/coding-agent/src/session/agent-storage.ts: same hoist and the existing retry loop now uses isSqliteBusyError so SQLITE_BUSY_RECOVERY / _SNAPSHOT / _TIMEOUT also trigger backoff. - Hoisted busy_timeout before journal_mode=WAL in every other shared SQLite open path: history-storage, autoresearch/storage, memories/storage, github-cache, report-tool-issue (auto-QA), catalog/model-cache; stats/db.ts now sets busy_timeout at all. - packages/ai/test/auth-storage-sqlite-busy.test.ts pins the contract: isSqliteBusyError matches every BUSY extended code (rejects SQLITE_LOCKED, non-errors, strings); open() leaves the connection in WAL mode (proves busy_timeout ran before journal_mode); open() retries through synthetic SQLITE_BUSY_RECOVERY; non-BUSY errors (SQLITE_CORRUPT) short-circuit; exhausted retries throw an error mentioning the DB path with exactly 3 sleeps for a 4-attempt budget. Fixes #2421
@oh-my-pi/omp-stats
Local observability dashboard for AI usage statistics.
Features
- Session log parsing: Reads JSONL session logs from
~/.omp/agent/sessions/ - SQLite aggregation: Efficient stats storage and querying using
bun:sqlite - Web dashboard: Real-time metrics visualization with Chart.js
- Incremental sync: Only processes new/modified log entries
Metrics Tracked
| Metric | Calculation |
|---|---|
| Tokens/s | output_tokens / (duration / 1000) |
| Cache Rate | cache_read / (input + cache_read) * 100 |
| Error Rate | count(stopReason=error) / total_calls * 100 |
| Total Cost | Sum of usage.cost.total |
| Avg Latency | Mean of duration |
| TTFT | Mean of ttft (time to first token) |
Usage
Via CLI
# Start dashboard server (default: http://localhost:3847)
omp stats
# Custom port
omp stats --port 8080
# Print summary to console
omp stats --summary
# Output as JSON (for scripting)
omp stats --json
Programmatic
import { getDashboardStats, syncAllSessions } from "@oh-my-pi/omp-stats";
// Sync session logs to database
const { processed, files } = await syncAllSessions();
// Get aggregated stats
const stats = await getDashboardStats();
console.log(stats.overall.totalCost);
console.log(stats.byModel[0].avgTokensPerSecond);
API Endpoints
| Endpoint | Description |
|---|---|
GET /api/stats |
Overall stats with all breakdowns |
GET /api/stats/models |
Per-model statistics |
GET /api/stats/folders |
Per-folder/project statistics |
GET /api/stats/timeseries |
Hourly time series data |
GET /api/sync |
Trigger sync and return counts |
Data Storage
- Session logs:
~/.omp/agent/sessions/(JSONL files) - Stats database:
~/.omp/stats.db(SQLite)
Dashboard
The web dashboard provides:
- Overall metrics cards (requests, cost, cache rate, error rate, duration, tokens/s)
- Time series chart showing requests and errors over time
- Per-model breakdown table
- Per-folder breakdown table
- Auto-refresh every 30 seconds
License
MIT