Nothing anywhere recorded what a model call cost: the harness used a
provider's usage numbers only to decide whether a turn had finished, then
dropped them. So a reply could not say what it took to make, one model
could not be compared with another on price, and there was no way to ask
what a day, a week or a month of the crew's work came to.
- `token_usages` records one row per call: user, bot, workspace, session,
job and message, the harness, the provider and model that actually
answered, the prompt and completion counts, and the cost when the
provider reports one.
- `harness-usage-scope.ts` carries those ids in an AsyncLocalStorage
scope that `run-bot-job.ts` enters once, so a run's turns, its session
summary, its transcriptions, images and embeddings are all attributed
without threading ids through every harness function. Nested runs merge
rather than replace, and a null genuinely clears a field (a bot's own
chat has no workspace).
- Work with no run behind it is charged to its owner instead:
`run-with-fallback-scope.ts` reaches the workspace's creator else the
bot's, and deliberately does not override a real run, so an ingest a
bot asked for is not misattributed to the bot's owner.
- All six harnesses record through `withCandidates`'s `on_success`.
Recording never throws: a model that answered must not lose its answer
to a failed insert.
- A job's calls land before its message exists, so `complete-bot-job.ts`
back-fills `message_id` once the message is inserted.
- The message's actions row shows what it cost, beside the timestamp and
the play button, with the breakdown in its title.
- `/admin/usage` totals the install's spending for 7, 30 or 90 days: the
total against the previous period, a day-by-day chart, a split per kind
of call, and a table per model with the price per thousand tokens
worked out from cost and tokens, which is what makes two models
comparable. Crew managers only, since it is the whole install's work
rather than one user's conversation. It sits with Routines and Activity
in the user popover rather than under Settings, because it is a record
of what happened, not something to configure.
Days are grouped in local time (`date(created_at / 1000, 'unixepoch',
'localtime')`), matching the local midnights the window builds and the
`YYYY-MM-DD` buckets it looks up. Grouping by the bare UTC day put a late
evening's calls under a name no bucket carried, so they vanished and the
chart's total silently disagreed with the totals card; a test pins it.
`canSeeRailItem` now decides who sees a popover or rail entry. The two
each filtered with their own inline rule and only one understood
`can_view`, so a gated page could be offered in the popover to everyone.
A bot's deliverables and the raw material behind them only existed as
paths in a message, so they couldn't be opened from the app at all. This
browses them read-only at /admin/files, one volume at a time: a bot's own
folder, a workspace's shared one, a computer's home (where a bot's ~/...
actually is), the media library, an MCP server's install folder and the
database.
Access is the whole gate: resolveStorageSpec checks the viewer against
the volume first (a bot through grabUserBot, a workspace through
grabUserWorkspace, a computer through grabUserComputer, media only their
own unless they manage the crew) and no path is built before it passes.
resolveStoragePath then applies the resolveBotPath rules to any root.
The picker lists folders through reachableBotsSql, so it never offers one
the resolver would refuse.
Reading is listStorageEntries (folders first, paginated, symlinks
skipped, catalog rows joined so a bot file still opens through its media
row) and grabStorageFile for bytes under a cap, both behind an ETag.
Bots create, list, update and delete schedules (cron_jobs, Bun.cron in UTC);
a per-minute cron queues due runs as tasks. Routines, Activity (super admins)
and Help get their own rail aside. Help covers familiar problems, starting
with signing in to Google through Cookie-Editor over the new Desktop (noVNC)
view.
Co-Authored-By: Claude Opus 5.5 <[email protected]>