Stats: GET /stats endpoint — per-agent + per-repo cost/turns/success aggregates #123
Labels
No labels
area:agents
area:dashboard
area:database
area:design
area:design-review
area:flows
area:infra
area:meta
area:security
area:sessions
area:webhook
area:workdir
security
type:bug
type:chore
type:meta
type:user-story
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
charles/claude-hooks#123
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
User story
As the operator, I want a
GET /statsHTTP endpoint that returns cost / turn / success-rate / throughput aggregates grouped by agent, by repo, and by day so that the dashboard (current and the #70 rework) can show spend trends without every page recomputing from raw/historyon the client.Context
/historycurrently returns up to 50 most-recentTaskRecords withcost_usd,turns,status,started_at,finished_at. Clients derive everything from that raw list.Two problems at scale:
This ticket adds a server-side aggregation that reads from SQLite (task history beyond the in-memory 50 cap once we persist it) and returns a compact summary. The aggregates are defined so any future dashboard surface can consume them without re-deriving.
Acceptance criteria
Endpoint shape
GET /stats— default window = last 30 days — returns:?window=7d/30d/90d/all— controls the time range. Default30d.?agent=<name>— filter to one instance.?repo=<owner/name>— filter to one repo.TaskRecords wherestatusis one ofsuccess,failure,cancelled.running/queuedare excluded (they have nofinished_atand no final cost).success_rate=successful / (successful + failed)— excludes cancelled (operator chose to abort, not an agent failure). Document this in the endpoint's response docstring.Data source
src/storage.tsto persist each finalizedTaskRecordononFinish. The in-memory 50-task list remains for fast SSE-driven UI refreshes; SQLite becomes the source of truth for aggregates.id TEXT PRIMARY KEY,repo TEXT,issue_number INT,user TEXT,model TEXT,status TEXT,cost_usd REAL,turns INT,started_at INT,finished_at INT.Performance
finished_atand a filter byfinished_at BETWEEN ?handles this.Tests
src/stats.test.ts— seed SQLite with a fixture of ~20 tasks across 3 agents and 2 repos, call/statswith various filters, assert the aggregates.NaNforsuccess_rate), single task window, all-cancelled window, filter to a non-existent agent returns emptyby_agentwithout 404.Docs
storage.tsadditions (if applicable).README.mdon the/statsendpoint (URL + query params + response shape).Out of scope
/stats— that's the monitor rework ticket (#70's follow-up). This ticket just ships the endpoint./history's in-memory list./stats— inherits whatever the dashboard has today (none).References
src/main.ts::handleHistoryreadstaskHistory(in-memory array ofTaskRecord).TaskRecordshape:src/main.tslines ~60-80.src/db.ts(was added in #48).src/storage.ts.Dependencies
main.claude-hooks-dev-defaultcontainer disappears silently #132claude-hooks-dev-defaultcontainer disappears silently #132