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.
- Cache the model catalog once per user and each conversation's setting
separately; clear both on logout and after model saves
- Compress hashed assets asynchronously with a byte budget and set Vary
- Memoize message parsing and throttle selection reads
- Debounce draft writes to localStorage
- Keep the composer editor on the page and focused, as before
- Add a bundle budget check and run it in CI
- Show a loading block while sessions load and a workspace avatar in the
mobile conversation header
- Compress responses: page scripts and styles once (brotli or gzip, in
memory) and cached immutable; pages and API JSON gzipped as they stream
- Service worker serves content-named assets cache-first
- Load the models modal's data when it opens (cached in localStorage)
instead of shipping the model catalog in every conversation page
- Lazy-load the Tiptap composer and memoize thread messages
- Import lucide icons directly; twui 0.1.4 drops by-name icons from Button
- bun run start builds the pages minified before serving
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The composer now writes markdown with bold, italic, strike, code,
links, quotes and lists, from shortcuts, markdown typing or a bubble
over the selection. @ mentions a bot and # a workspace, shown as chips
and saved as [bot:ID] / [workspace:ID]. Text selected in a message can
be quoted into the composer. User messages render as markdown, and
replies tell the bot who and what the user mentioned.
Adds an ESM stand-in for use-sync-external-store, since bunext keeps
React external in page bundles and the CommonJS original requires it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Every message has a row under it to reply or react. Replying quotes the
message above the composer, and the sent message stores
reply_to_message_id (a plain id, not a foreign key, so deleting the
quoted message only drops the quote). send-message accepts a quote from
any session of the same conversation, so a rollover or a thread that
moved on still sends; the conversation drops a picked quote when its
session changes. The bot reads the quote as "[Replying to ...]" before
the user's turn, file-only replies included, and relayed messages carry
it too.
Reactions are a fixed set (BotMessageReactions) in
bot_message_reactions, one per message, emoji and user or bot. Users
toggle their own through api/admin/bots/react-message, and every bot
gets react_to_message for the session it answers (the user's latest
message by default). Both push the fresh list as server:bot-reaction,
with mine worked out for the session's user.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The timestamp no longer takes its own line under a bot group: it sits
before the read-aloud button on the last message, after the needs-you
chip when there is one.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- A mic in the composer records, transcribes with the bot's
transcription model (api/admin/bots/transcribe) and writes the text
into the input at the cursor to edit before sending. Escape cancels;
recordings aren't stored.
- Every bot message has a read-aloud button that plays
GET api/admin/bots/speak-message, streamed from the provider as the
audio is made, so it starts in a couple of seconds. One message plays
at a time; only the user's own sessions can be read.
- Both buttons stay visible but disabled while the bot has no model for
them (bot_voice page prop).
- Transcription and speech no longer pick free models automatically:
they're not available until a model is picked, and Settings says so.
Transcription lists speech-to-text models only, so the chat-model
transcript path and its missing-audio check are gone.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Bots have a current computer (bots.computer_id), switched from the chat
header beside the model button or by clicking a computer the bot names
([computer:ID]); tasks run on it by default
- search_computers runs one read-only command on all of a bot's computers;
chat replies know their computers (compose-computers-note)
- The coordinator can use every computer and grants or revokes crew access
with assign_computer
- Delegated jobs record requested_by_bot_id; the worker's result or failure
is reported back in the coordinator's conversation
- bots_queue_updates records each model turn of a job, pushed live and shown
in the working-on panel
- Live relay: every process pushes new messages, job steps and bot changes
from the database to its own sockets, so work done elsewhere shows live
- Bot messages render as markdown (react-markdown + remark-gfm, no raw HTML)
with twui components; rail previews are plain text
- Fix header button overflow in the model and computer selects
- Bump @moduletrace/twui to 0.1.2
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Remove the preset document suite (letters, emails, invoices, quotes,
PDF generation, line items) with its routes, API handlers and tables
- Rebrand to Cobble: site data, Lato, handoff design tokens (light and
dark) in main.css, brand assets in public/brand, ember stone logo and
regenerated favicons
- Add bots, bot_messages, bot_tasks and bot_task_steps tables, regenerate
DB types (BUN_SQLITE_COBBLE_*) and add a sample crew seed (seed:bots)
- Add the chat shell at /admin/bots: rail with pinned coordinator and
user popover, bottom-anchored conversation with composer, working-on
panel, and a send-message API scoped to the session user
- Add BotStone and Wordmark components, crew status and relative time
utils with tests, and update AGENTS.md
Co-Authored-By: Claude Opus 5.5 <[email protected]>