handoff: two chat fixes for the next session
This commit is contained in:
+113
@@ -0,0 +1,113 @@
|
||||
# Next session — two chat fixes
|
||||
|
||||
**Disposable.** Written 2026-08-18 ~05:00 as a handoff across a compaction. Delete it when both are done.
|
||||
|
||||
Both are in `/chat`. Do them in order — the first is small and well understood, the second is a design
|
||||
question wearing a UI job's clothes.
|
||||
|
||||
---
|
||||
|
||||
## 1. The model dropdown says `claude-code` instead of the model that is answering
|
||||
|
||||
### Symptom
|
||||
|
||||
In the composer, bottom right, the model select renders the literal string **`claude-code`**. It should
|
||||
name a model — the owner expects "Opus".
|
||||
|
||||
### Why, exactly
|
||||
|
||||
`DEFAULT_MODEL` in `src/servers/api/chat/websocket.ts` is the bare harness string `claude-code`, used when
|
||||
the client sends no model and the account has no default. It routes correctly — `isClaudeModel` is a prefix
|
||||
test and `model.split('/')[1]` is `undefined`, so no `--model` is passed and **the CLI picks for itself**.
|
||||
|
||||
It names no catalogue entry, so Radix's `SelectValue` matches no item and renders nothing. `773f0e1c` added
|
||||
a fallback in `ModelSelector.tsx` so a blank trigger became the raw string's last segment
|
||||
(`displayModel.split('/').pop()`), which for `claude-code` is `claude-code`. Honest, and useless.
|
||||
|
||||
### The fix, and the distinction it turns on
|
||||
|
||||
**The server does not know which model answered** — it deliberately did not choose one. The transcript does:
|
||||
every assistant entry carries `message.model` (`claude-opus-5`), and `officerModelForApiModel()` in
|
||||
`src/servers/api/chat/model-aliases.ts` already maps that back to `claude-code/opus`.
|
||||
|
||||
So: when the effective model is the bare harness, display **what answered**, not what was asked for. That is
|
||||
the same `effective`-vs-requested distinction `createMessageHandler` already makes for tier switches.
|
||||
|
||||
Options, cheapest first:
|
||||
|
||||
1. **Client-side.** The turn's `result`/assistant messages already reach `useChat`. If a message carries a
|
||||
model, prefer it over the bare harness for display only — do not write it back to `selectedModel`, or the
|
||||
next turn will request a tier the user never picked.
|
||||
2. **Server-side.** `GET /chat/sessions/:id` already returns `officerModel`, resolved from the stored row
|
||||
with a family-guess fallback. A live session could report the same thing on `session:init`.
|
||||
|
||||
Option 1 is smaller. Option 2 is more consistent with how a resumed session already works.
|
||||
|
||||
### Traps
|
||||
|
||||
- **Do not store the resolved value.** `record()` in `websocket.ts` refuses to persist a model without a `/`
|
||||
precisely because a bare harness overwrote a correctly backfilled `claude-code/opus` and made the picker
|
||||
render blank. Read that comment before touching either end.
|
||||
- **This picker has been broken once already today.** Verify: a session with no explicit model, a session on
|
||||
`opus[1m]`, and a resumed session all show something sensible.
|
||||
- `chat_session_models.model` holds Officer ids (`claude-code/opus[1m]`), not API ids. Do not mix them.
|
||||
|
||||
---
|
||||
|
||||
## 2. Usage popover on the "Claude Code" pill
|
||||
|
||||
### What is wanted
|
||||
|
||||
Clicking the harness pill — the left-hand label in the composer's control row, which reads **Claude Code**
|
||||
and is inert once a conversation has started — should open a panel showing what Claude Code's own usage
|
||||
display shows:
|
||||
|
||||
- **Limits**: Session %, Weekly %, Fable Weekly %, each with a reset countdown
|
||||
- **Tokens by day**, last seven days, as bars
|
||||
- **Tokens by model**: Opus 5, Opus 4.8, and any others, with totals
|
||||
|
||||
The owner supplied a screenshot of the target and a reference implementation:
|
||||
**https://github.com/basecamp/omarchy/tree/quattro/shell/plugins/agents**
|
||||
|
||||
### Read the reference FIRST — this is a data question, not a UI one
|
||||
|
||||
**Officer does not have any of these numbers.** Per-session cost is accumulated in memory in
|
||||
`websocket.ts` (search `session.meta.cost`), it dies with `pm2 restart officer`, and nothing aggregates it
|
||||
across sessions, days or models. There is no usage table.
|
||||
|
||||
So before writing any component, answer: **where does omarchy get this?** Two possibilities, and they are
|
||||
completely different jobs:
|
||||
|
||||
- **It reads local Claude Code state** — something under `~/.claude`. Then this is a display job: a sidecar
|
||||
route reads the file, the panel renders it, and the numbers are the same ones the CLI shows.
|
||||
- **It calls an Anthropic endpoint** — then it needs the OAuth credential, which lives with
|
||||
`officer-anthropic-proxy` and never leaves it, and the route belongs there rather than in the chat socket.
|
||||
|
||||
Do not build a schema for this until that is known. The wrong answer is inventing Officer's own token
|
||||
accounting to reproduce a number the CLI already computes.
|
||||
|
||||
### Where the pill is
|
||||
|
||||
`src/workspaces/officerdev/src/apps/Chat/components/ModelSelector.tsx` — the provider label rendered when
|
||||
`providerStripLocked` is true (a `<span>` with `bg-muted`, currently not a button). It sits left of the
|
||||
background-job button and the model select.
|
||||
|
||||
Note the owner's framing: _"in a running chat that is not responding to harness change anymore"_ — the pill
|
||||
is already inert in that state, which is exactly why it is free to become a button.
|
||||
|
||||
### Constraints
|
||||
|
||||
- **Owner-only data.** These are the owner's subscription limits. A member must not see them — gate it.
|
||||
- **No palette classes.** Semantic tokens only (`bg-muted`, `text-muted-foreground`, `bg-primary/15`); the
|
||||
ratchet is `bun run check:theme`.
|
||||
- **Do not block the composer.** If the source is unreachable the popover says so; it must not spin.
|
||||
|
||||
---
|
||||
|
||||
## Before you start
|
||||
|
||||
`bunx tsgo` is clean and `bun test` is at **7 pre-existing failures** (registry/totality drift and a cliamp
|
||||
socket — none yours). Compare against that baseline, not against zero.
|
||||
|
||||
Everything else from tonight is committed and pushed across four repositories: `platform`, `officer-oas`,
|
||||
`plugins/email`, `plugins/kiosk`.
|
||||
Reference in New Issue
Block a user