Let a bot answer its sessions side by side, and show a due reply in each

Replies were locked per bot, so a long reply in one session held up every
other session's reply with nothing shown. The lock is now per session. A
chat shows the thinking row whenever the user has the last word, rather
than from the bot's face (one per user, shared by every session), so it
shows even while the bot works elsewhere. A task waiting for its busy
computer says so, and a job finishing while another still runs puts back
the running one's face.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-19 16:09:21 +01:00
co-authored by Claude Opus 5.5
parent e157863cfb
commit 4ee81d6127
8 changed files with 122 additions and 19 deletions
@@ -0,0 +1,34 @@
import BunSQLite from "@moduletrace/bun-sqlite";
import type { BotFace } from "@/src/dict/bot-dict";
import { BotReplyJobTitle, BotTaskStatusLine } from "@/src/dict/bot-queue-dict";
type Params = {
bot_id: number | string;
/**
* Only jobs in this user's conversations.
*/
user_id: number | string;
};
/**
* The face a bot should show one user while other jobs for them are
* still running: thinking while a reply runs (it's short, and its chat
* needs the row), else working on a task. Null when none is running.
*/
export default async function grabRunningBotJobFace({
bot_id,
user_id,
}: Params): Promise<{ face: BotFace; status_line: string } | null> {
const kinds = (
await BunSQLite.sql<{ kind: string }>({
sql: `SELECT DISTINCT kind FROM bots_queue WHERE bot_id = ? AND status = 'processing'
AND session_id IN (SELECT id FROM bot_sessions WHERE user_id = ?)`,
values: [bot_id, user_id],
})
).payload?.map((row) => row.kind) || [];
if (kinds.includes("reply")) return { face: "working", status_line: BotReplyJobTitle };
if (kinds.includes("task")) return { face: "working", status_line: BotTaskStatusLine };
return null;
}