The owner asked for individual turn token/dollar costs, a running total, cost
over time, and cost per trigger — derived, not stored, following the same
rule `running` already uses. Two new tables carry it:
- command_hq_watcher_fires: one row per trigger (sessionRowId, taskId,
firedAt). Replaces the single watcherLastFiredAt column, which lost the
previous fire's timestamp every time a new one landed — fine for "when did
it last fire", useless for "how many times, and what did each one cost".
- command_hq_turns: one row per completed turn (sessionRowId, fireId
nullable, inputTokens, outputTokens, totalUsd). Nothing pre-summed —
running total, cost-over-time and cost-per-trigger are all queries over
this table. Turn count needed no column either: COUNT(*) already answers
it, confirmed explicitly with the owner rather than assumed.
command_hq_sessions gains watcherArmedAt/watcherStoppedAt in place of any
"last fired" cache — set only on the false<->true transitions, so duration
(current run, or the last completed one) is computable without a full
transition history table.
Two new read routes, GET /commanders/:sessionId/turns and .../fires, expose
the raw series behind the cost summary and watcherLastFiredAt (now computed
via MAX(fired_at), not read off a column — the field survives in the JSON
response under the same name).
Bug found and fixed while wiring this up, recorded in docs/command-hq.md:
recordCommandHqWatcherFired's signature changed from (userId, sessionId) to
(sessionRowId, taskId) and one call site kept the old argument order —
tsgo stayed clean because number/string still matched structurally. Caught
by re-reading every call site by hand, not by any tool.
Needs bun db:push before any route answers with real data.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>