command hq: displayText, caught by the mobile side before either of us ran this
mobile-2.md found it: a commander created to become a background job sends buildBackgroundJobPrompt's full mechanics as `prompt` — a page of preamble, the owner's actual sentence only at the end — and command_hq_sessions has no name column, so the title comes straight from `(displayText ?? prompt).slice(0,100)`. without it every such commander titles itself with the mechanics text, and the grid becomes a column of identical unreadable cards. same precedence the websocket path already uses on both clients (sendWithHiddenPrefix on web, useSessionView.ts on mobile) — now the same on this endpoint too. fixed before either side had run this against a real server, so there's nothing to clean up on a live grid. also confirmed rather than assumed, per mobile's other note: cwd cannot be talked into a different one on this path. resolveChatCwd's command-hq branch ignores msg.cwd unconditionally and this endpoint never sets it. replied in COMMS/command-hq/platform.md. also read watcher-loop-proposal.md (exit/act/drain/re-arm as harness infrastructure, "Andre's call" to build it before first tests) — not starting it off a COMMS note alone, taking it to the owner directly since it's a real scope addition beyond what tonight's command hq build was scoped to. bunx tsgo clean, suite at baseline. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+28
-18
@@ -20,23 +20,23 @@ Base path `/command-hq`, `core` permission (every signed-in account, no grant to
|
||||
to the caller's own `user_id` regardless. Router: `src/servers/api/command-hq/router.ts` — read it directly
|
||||
for exact request/response shapes; the table below is the map, not the full contract.
|
||||
|
||||
| Method | Path | Body | Notes |
|
||||
| ------ | ------------------------------------ | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| GET | `/categories` | — | `{ categories: [{id,name,createdAt}] }` |
|
||||
| POST | `/categories` | `{name}` | 409 on a duplicate name for this user |
|
||||
| PATCH | `/categories/:id` | `{name}` | rename |
|
||||
| DELETE | `/categories/:id` | — | uncategorises its commanders, does not delete them |
|
||||
| GET | `/tags` | — | schema exists; no UI planned for v1 |
|
||||
| POST | `/tags` | `{name}` | |
|
||||
| DELETE | `/tags/:id` | — | |
|
||||
| GET | `/commanders[?category=<id>\|none]` | — | omitted = all, `none` = uncategorised bucket. See below |
|
||||
| POST | `/commanders/new` | `{prompt, categoryId?}` | creates a commander; `prompt` required. `-> {sessionId, cwd}` — `sessionId` is Claude's real transcript id, not a placeholder |
|
||||
| GET | `/commanders/:sessionId` | — | Command HQ's own metadata only — NOT the transcript, see below |
|
||||
| POST | `/commanders` | `{sessionId, categoryId?}` | files an EXISTING session; idempotent; 404 if the session isn't in the commanders directory. Not the creation path — see `/commanders/new` |
|
||||
| PATCH | `/commanders/:sessionId` | `{categoryId?, name?}` | `name` proxies to `renameClaudeSession` — the same rename `/chat` uses |
|
||||
| POST | `/purge` | — | sweeps rows whose transcript no longer resolves; returns `{removed}` |
|
||||
| POST | `/commanders/:sessionId/tags` | `{tagId}` | |
|
||||
| DELETE | `/commanders/:sessionId/tags/:tagId` | — | |
|
||||
| Method | Path | Body | Notes |
|
||||
| ------ | ------------------------------------ | ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| GET | `/categories` | — | `{ categories: [{id,name,createdAt}] }` |
|
||||
| POST | `/categories` | `{name}` | 409 on a duplicate name for this user |
|
||||
| PATCH | `/categories/:id` | `{name}` | rename |
|
||||
| DELETE | `/categories/:id` | — | uncategorises its commanders, does not delete them |
|
||||
| GET | `/tags` | — | schema exists; no UI planned for v1 |
|
||||
| POST | `/tags` | `{name}` | |
|
||||
| DELETE | `/tags/:id` | — | |
|
||||
| GET | `/commanders[?category=<id>\|none]` | — | omitted = all, `none` = uncategorised bucket. See below |
|
||||
| POST | `/commanders/new` | `{prompt, displayText?, categoryId?}` | creates a commander; `prompt` required. `displayText ?? prompt` titles it, same precedence as the websocket. `-> {sessionId, cwd}` — `sessionId` is Claude's real transcript id, not a placeholder |
|
||||
| GET | `/commanders/:sessionId` | — | Command HQ's own metadata only — NOT the transcript, see below |
|
||||
| POST | `/commanders` | `{sessionId, categoryId?}` | files an EXISTING session; idempotent; 404 if the session isn't in the commanders directory. Not the creation path — see `/commanders/new` |
|
||||
| PATCH | `/commanders/:sessionId` | `{categoryId?, name?}` | `name` proxies to `renameClaudeSession` — the same rename `/chat` uses |
|
||||
| POST | `/purge` | — | sweeps rows whose transcript no longer resolves; returns `{removed}` |
|
||||
| POST | `/commanders/:sessionId/tags` | `{tagId}` | |
|
||||
| DELETE | `/commanders/:sessionId/tags/:tagId` | — | |
|
||||
|
||||
**A commander's conversation is `GET /chat/sessions/:id`, unchanged.** This router deliberately does not
|
||||
duplicate it — `GET /commanders/:sessionId` returns only `{sessionId, categoryId, tagIds, running,
|
||||
@@ -121,7 +121,7 @@ conversation in the wrong directory rather than failing loudly. Decided with the
|
||||
|
||||
```
|
||||
POST /command-hq/commanders/new
|
||||
body: { prompt: string, categoryId?: number | null }
|
||||
body: { prompt: string, displayText?: string, categoryId?: number | null }
|
||||
-> { sessionId, cwd }
|
||||
```
|
||||
|
||||
@@ -131,6 +131,16 @@ long before a background job is ever armed via the existing dialog — sometimes
|
||||
just "we've already discussed this, do it." Either way there is always a real first message, the same as
|
||||
any conversation; there is no "empty commander" case to design around.
|
||||
|
||||
**`displayText` was missing from the first version of this route, and the mobile side caught it** before
|
||||
it was exercised against a real server. A commander created to become a background job sends the FULL
|
||||
`buildBackgroundJobPrompt` mechanics as `prompt` — a page of preamble, the owner's actual sentence only at
|
||||
the end — and `command_hq_sessions` has no name column, so the title comes from `session.meta.title =
|
||||
(displayText ?? prompt).slice(0, 100)`, exactly the precedence the websocket path already uses (both
|
||||
clients already send `displayText` there — `sendWithHiddenPrefix` on web, `useSessionView.ts` on mobile).
|
||||
Without it, every commander created through this endpoint would have titled itself with the mechanics
|
||||
text, and the grid would have been a column of identical, unreadable cards. Fixed same session, before
|
||||
anyone had run it for real.
|
||||
|
||||
**How it actually gets the id.** Claude assigns a transcript id only once a turn has run far enough to
|
||||
report `system.init` — there is no way to reserve one in advance. So the endpoint genuinely starts the
|
||||
turn: `startClaudeTurn` (`websocket.ts`), the turn-starting half of `handleClaudeCodeChat` extracted so both
|
||||
|
||||
Reference in New Issue
Block a user