live sessions default to agents only, and you ask by name for a commander

`?kind=` was opt-in, so a client that said nothing got everything — which is the
wrong default for the one caller that cannot cope. The phone swipes between live
chats one at a time, has no room for tabs and no way to skip a row, so a commander
in that list is a commander you are stuck on. Defaulting to `agent` means
forgetting the parameter can only ever under-report.

The two callers that want both now say `kind=all` explicitly, and that is a fair
price: they are few, they are on screens built to show both kinds, and they are
the ones positioned to notice when the answer is wrong. `counts` still describes
every live session whatever the filter, so a filtered caller renders both tab
numbers from one request rather than two.

Also corrects useLiveSessions' comment, which said `isCommander` was derived from
the cwd. It stopped being true when a commander gained the right to choose its own
working directory — it comes from the `command_hq_sessions` row, and the directory
answers nothing.

Unrelated but caught by docs:check on the way past: the registry gained a
nineteenth entry (minesweeper, execution) and CLAUDE.md still said eighteen, which
also left the generated platform skill two lines stale. Both corrected; the check
is green again. The minesweeper feature itself is half-landed and not mine —
`rfb.ts` fails typecheck and its registry test fails on a websocket that is not
served.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-22 22:11:12 +00:00
co-authored by Claude Opus 5
parent 749c80c877
commit 3837af657d
4 changed files with 58 additions and 18 deletions
+18 -3
View File
@@ -27,7 +27,8 @@ export type LiveSession = {
title: string | null;
cwd: string | null;
/**
* A commander rather than an ordinary chat, derived server-side from the cwd.
* A commander rather than an ordinary chat, derived server-side from its `command_hq_sessions` row.
* NOT from the cwd — a commander chooses its own working directory, so the directory answers nothing.
*
* Every consumer of this list needs it, and one of them cannot work around its absence. The sessions
* panel splits Live into two tabs; the iPhone app swipes between live chats, has no tabs, and so can only
@@ -36,6 +37,12 @@ export type LiveSession = {
isCommander: boolean;
};
/**
* Counts describe every live session, not just the ones returned — so a filtered request still renders
* `Agents (n) / Commanders (n)` without a second round trip.
*/
type LivePayload = { sessions: LiveSession[]; counts: { agents: number; commanders: number } };
const LIVE_KEY = 'CHAT_LIVE_SESSIONS';
/**
@@ -50,9 +57,17 @@ export function useLiveSessions() {
const client = useClient();
const { isAuthenticated } = useAuth();
const { data, isLoading, error, refetch } = useQuery<{ sessions: LiveSession[] }>({
/*
`kind=all` is required, not a default. `/chat/live` returns ONLY agents unless asked otherwise, so that
a caller which forgets can never be handed a commander — a phone swiping between live chats one at a
time has no way to skip one. This list is the other case: it renders both kinds as tabs, so it asks for
both, explicitly, and the explicitness is what makes it reviewable.
`counts` describes the whole set either way, which is why both tab numbers are right from one request.
*/
const { data, isLoading, error, refetch } = useQuery<LivePayload>({
queryKey: [LIVE_KEY],
queryFn: () => client.get<{ sessions: LiveSession[] }>('/chat/live'),
queryFn: () => client.get<LivePayload>('/chat/live?kind=all'),
enabled: isAuthenticated,
refetchInterval: 10_000,
staleTime: 5_000,