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:
2026-08-18 07:33:09 +00:00
co-authored by Claude Opus 5
parent cc121f3156
commit f810bdc48e
3 changed files with 65 additions and 22 deletions
+12 -4
View File
@@ -196,7 +196,7 @@ commandHqRouter.get('/commanders', async (ctx) => {
});
/**
* POST /command-hq/commanders/new { prompt: string, categoryId?: number | null }
* POST /command-hq/commanders/new { prompt: string, displayText?: string, categoryId?: number | null }
*
* Creates a commander for a client that cannot open a websocket with the right parameters — mobile,
* notably. The web composer never calls this: it sends an ordinary `/chat` turn with `context:
@@ -209,6 +209,14 @@ commandHqRouter.get('/commanders', async (ctx) => {
* has already happened elsewhere — but it always begins with something, the same as any `/chat`
* conversation does. There is no "empty commander" to create and later fill in.
*
* `displayText`, separately, is what TITLES the commander — found missing here by the mobile side after
* this route first shipped without it. A commander created to become a background job sends the full
* `buildBackgroundJobPrompt` mechanics as `prompt` (a page of preamble) with the owner's actual sentence
* only at the end, and `command_hq_sessions` has no name column — the title comes from
* `session.meta.title = (displayText ?? prompt).slice(0, 100)`, same precedence the websocket path
* already uses. Without this field every commander created this way titles itself with the mechanics
* text, and the grid becomes a column of identical, unreadable cards.
*
* The response's `sessionId` is Claude's own transcript id — not a placeholder, not Officer's — so the
* client has something it can immediately resume, attach to, or list among its commanders. Getting it
* costs actually starting the turn: Claude assigns no id until one has run at least far enough to report
@@ -219,8 +227,8 @@ commandHqRouter.get('/commanders', async (ctx) => {
commandHqRouter.post('/commanders/new', async (ctx) => {
const user = ctx.get('user');
const body = await ctx.req
.json<{ prompt?: string; categoryId?: number | null }>()
.catch(() => ({}) as { prompt?: string; categoryId?: number | null });
.json<{ prompt?: string; displayText?: string; categoryId?: number | null }>()
.catch(() => ({}) as { prompt?: string; displayText?: string; categoryId?: number | null });
const prompt = body.prompt?.trim();
if (!prompt) throw errors.BAD_REQUEST('prompt is required');
@@ -245,7 +253,7 @@ commandHqRouter.post('/commanders/new', async (ctx) => {
ws: null,
sessionId,
model,
msg: { prompt, context: 'command-hq' },
msg: { prompt, displayText: body.displayText?.trim() || undefined, context: 'command-hq' },
effectivePrompt: prompt,
onOutcome: settle,
});