From a9dd264a724354c392118e7f15178a60623fa175 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Mon, 24 Aug 2026 17:23:27 +0000 Subject: [PATCH] a commander's children stay out of the chat list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /chat already hid commanders. it did not hid their spawned agents, and a commander that fires once an hour spawns one per run — each with its own transcript, titled after whatever its first message happened to be. one commander had thirteen sessions under its directory, twelve of them reading "Read /home/.../procedures/classify-t…". a loop's bookkeeping, filling the list you keep conversations in. listCommandHqOwnedSessionIds is the union: commanders plus their children. kept separate from listCommandHqSessionIds rather than replacing it, because a CHILD is not a commander — isCommander has to stay false for one or the composer would offer it the handoff watcher. /chat/live is deliberately untouched. it is the "what is running right now" panel, and a spawned agent that is still going belongs there — hiding it would mean an agent running with nowhere showing it. Co-Authored-By: Claude Opus 5 Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043 --- .../officer_db/src/command-hq/index.ts | 1 + .../officer_db/src/command-hq/queries.ts | 24 +++++++++++++++++++ src/servers/api/chat/chat.ts | 3 ++- 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/databases/officer_db/src/command-hq/index.ts b/src/databases/officer_db/src/command-hq/index.ts index 32838350..ec2ad5c8 100644 --- a/src/databases/officer_db/src/command-hq/index.ts +++ b/src/databases/officer_db/src/command-hq/index.ts @@ -8,6 +8,7 @@ export { getCommandHqSessionById, listCommandHqSessions, listCommandHqSessionIds, + listCommandHqOwnedSessionIds, getCommandHqSession, setCommandHqSessionCategory, deleteCommandHqSession, diff --git a/src/databases/officer_db/src/command-hq/queries.ts b/src/databases/officer_db/src/command-hq/queries.ts index d97f23f8..160d19f8 100644 --- a/src/databases/officer_db/src/command-hq/queries.ts +++ b/src/databases/officer_db/src/command-hq/queries.ts @@ -186,6 +186,30 @@ export async function listCommandHqSessionIds(userId: number): Promise r.sessionId!)); } +/** + * Every transcript that belongs to a commander: the commanders themselves AND the agents they spawned. + * + * What `/chat`'s list hides. The set above is not enough — it holds the commanders, and a commander that + * fires once an hour spawns a child per run, each with its own transcript titled after whatever its first + * message happened to be. Thirteen of them under one directory was what made this obvious: twelve reading + * `Read /home/.../procedures/classify-t…`, filling the conversation list with a loop's bookkeeping. + * + * Separate from `listCommandHqSessionIds` rather than replacing it, because a CHILD is not a commander — + * `isCommander` on a session must stay false for one, or the composer would offer it the handoff watcher. + */ +export async function listCommandHqOwnedSessionIds(userId: number): Promise> { + const [commanders, children] = await Promise.all([ + listCommandHqSessionIds(userId), + db + .select({ id: commandHqSpawned.spawnedSessionId }) + .from(commandHqSpawned) + .innerJoin(commandHqSessions, eq(commandHqSessions.id, commandHqSpawned.sessionRowId)) + .where(eq(commandHqSessions.userId, userId)), + ]); + for (const row of children) commanders.add(row.id); + return commanders; +} + // drizzle's `eq` cannot express `IS NOT NULL` either. const isNotNullSessionId = () => sql`${commandHqSessions.sessionId} is not null`; diff --git a/src/servers/api/chat/chat.ts b/src/servers/api/chat/chat.ts index f04a262d..e28f428f 100644 --- a/src/servers/api/chat/chat.ts +++ b/src/servers/api/chat/chat.ts @@ -8,6 +8,7 @@ import { getChatSessionModel, getChatSessionModels, listCommandHqSessionIds, + listCommandHqOwnedSessionIds, getChatNotifyPreference, getChatNotifyPreferences, setChatNotifyPreference, @@ -156,7 +157,7 @@ chatRouter.get('/sessions', async (ctx) => { that excludes commanders has nothing to say about which of its rows is one. `/chat/live` is where the flag is real, because that route keeps both kinds. */ - const filed = await listCommandHqSessionIds(user.id).catch(() => new Set()); + const filed = await listCommandHqOwnedSessionIds(user.id).catch(() => new Set()); const sessions = merged.filter((s) => !filed.has(s.id)); /*