diff --git a/src/bots-queue/process-bot-job.ts b/src/bots-queue/process-bot-job.ts index 9086e7d..1a3a621 100644 --- a/src/bots-queue/process-bot-job.ts +++ b/src/bots-queue/process-bot-job.ts @@ -16,12 +16,13 @@ import scheduledRunRetryAt from "@/src/utils/scheduled-run-retry-at"; import { BotComputerBusyStatusLine, BotReplyJobTitle, - BotTaskStatusLine, BotWaitingStatusLine, } from "@/src/dict/bot-queue-dict"; import BotJobFatalError from "./bot-job-fatal-error"; import describeJobStep from "./describe-job-step"; import pushToSessionUser from "./push-to-session-user"; +import taskStatusLine from "@/src/utils/task-status-line"; +import grabWorkspaceName from "@/src/functions/backend/db/workspaces/grab-workspace-name"; import pushBotNotice from "./push-bot-notice"; import grabJobUserId from "@/src/functions/backend/db/bots-queue/grab-job-user-id"; import runBotJob from "./run-bot-job"; @@ -167,7 +168,10 @@ export default async function processBotJob({ const run = async () => { await setFace({ face: "working", - status_line: job.kind == "reply" ? BotReplyJobTitle : BotTaskStatusLine, + status_line: + job.kind == "reply" + ? BotReplyJobTitle + : taskStatusLine({ workspace_name: await grabWorkspaceName({ workspace_id: job.workspace_id }) }), }); /** diff --git a/src/functions/backend/db/bots-queue/grab-running-bot-job-face.ts b/src/functions/backend/db/bots-queue/grab-running-bot-job-face.ts index 8c2a7cc..955ecb0 100644 --- a/src/functions/backend/db/bots-queue/grab-running-bot-job-face.ts +++ b/src/functions/backend/db/bots-queue/grab-running-bot-job-face.ts @@ -1,6 +1,7 @@ import BunSQLite from "@moduletrace/bun-sqlite"; import type { BotFace } from "@/src/dict/bot-dict"; -import { BotReplyJobTitle, BotTaskStatusLine } from "@/src/dict/bot-queue-dict"; +import { BotReplyJobTitle } from "@/src/dict/bot-queue-dict"; +import taskStatusLine from "@/src/utils/task-status-line"; type Params = { bot_id: number | string; @@ -13,22 +14,29 @@ type Params = { /** * 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. + * needs the row), else working on a task (in its workspace, if it runs in + * one). 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 = ?)`, + const jobs = ( + await BunSQLite.sql<{ kind: string; workspace_name: string | null }>({ + sql: `SELECT q.kind, w.name AS workspace_name FROM bots_queue q + LEFT JOIN workspaces w ON w.id = q.workspace_id + WHERE q.bot_id = ? AND q.status = 'processing' + AND q.session_id IN (SELECT id FROM bot_sessions WHERE user_id = ?) + ORDER BY q.id`, values: [bot_id, user_id], }) - ).payload?.map((row) => row.kind) || []; + ).payload || []; - if (kinds.includes("reply")) return { face: "working", status_line: BotReplyJobTitle }; - if (kinds.includes("task")) return { face: "working", status_line: BotTaskStatusLine }; + if (jobs.some((job) => job.kind == "reply")) return { face: "working", status_line: BotReplyJobTitle }; + + const task = jobs.find((job) => job.kind == "task"); + + if (task) return { face: "working", status_line: taskStatusLine({ workspace_name: task.workspace_name }) }; return null; } diff --git a/src/functions/backend/db/workspaces/grab-workspace-name.ts b/src/functions/backend/db/workspaces/grab-workspace-name.ts new file mode 100644 index 0000000..b50fbed --- /dev/null +++ b/src/functions/backend/db/workspaces/grab-workspace-name.ts @@ -0,0 +1,20 @@ +import BunSQLite from "@moduletrace/bun-sqlite"; + +type Params = { + workspace_id?: number | string | null; +}; + +/** + * A workspace's name as it is, without an access check, for copy the + * caller already decided to show. Null when there's none. + */ +export default async function grabWorkspaceName({ workspace_id }: Params): Promise { + if (!workspace_id) return null; + + const res = await BunSQLite.sql<{ name: string | null }>({ + sql: `SELECT name FROM workspaces WHERE id = ? LIMIT 1`, + values: [Number(workspace_id)], + }); + + return res.payload?.[0]?.name || null; +} diff --git a/src/utils/task-status-line.test.ts b/src/utils/task-status-line.test.ts new file mode 100644 index 0000000..dbcc1da --- /dev/null +++ b/src/utils/task-status-line.test.ts @@ -0,0 +1,13 @@ +import { describe, expect, test } from "bun:test"; +import taskStatusLine from "./task-status-line"; + +describe("taskStatusLine", () => { + test("names the workspace a task runs in", () => { + expect(taskStatusLine({ workspace_name: "Launch" })).toBe("Working on a task in Launch workspace"); + }); + + test("says just working on a task outside a workspace", () => { + expect(taskStatusLine({})).toBe("Working on a task"); + expect(taskStatusLine({ workspace_name: " " })).toBe("Working on a task"); + }); +}); diff --git a/src/utils/task-status-line.ts b/src/utils/task-status-line.ts new file mode 100644 index 0000000..0c41502 --- /dev/null +++ b/src/utils/task-status-line.ts @@ -0,0 +1,18 @@ +import { BotTaskStatusLine } from "@/src/dict/bot-queue-dict"; + +type Params = { + /** + * The workspace the task runs in, if any. + */ + workspace_name?: string | null; +}; + +/** + * The status line while a task runs, naming its workspace when it runs in + * one, so the bot's own chat says where the work is. Never the task. + */ +export default function taskStatusLine({ workspace_name }: Params) { + const name = workspace_name?.trim(); + + return name ? `${BotTaskStatusLine} in ${name} workspace` : BotTaskStatusLine; +}