Add workspaces, computers, bot memory and the bots queue

- Schema: workspaces (+ workspace_bots, workspace_computers), containers,
  computers, bot_computers, bots.memory, workspaces.system_prompt, and
  bots_queue as a bun-sqlite isQueue table
- Main computer (and its container record) ensured with the coordinator on
  signup, seed and /admin/bots; the coordinator's access to it is locked
- Effective computer access: direct grants plus workspace computers
- Full bot prompt: core instructions, workspace prompts, then memory;
  bots can rewrite their memory and system prompt
- Queue helpers on the native BunSQLite.queue API, plus a handler that
  heartbeats, serializes per computer, recovers after restarts, reports
  lost jobs, supports cancelling and pushes outcomes over the WebSocket
- Enable SQLite foreign keys at startup so schema cascades fire
- Prune finished jobs daily

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-18 18:33:54 +01:00
co-authored by Claude Opus 5.5
parent 694c83346f
commit d49f524fe8
72 changed files with 2961 additions and 31 deletions
+65
View File
@@ -0,0 +1,65 @@
import bunext from "@moduletrace/bunext";
import { AppData } from "@/src/data/app-data";
import claimBotJobs from "@/src/functions/backend/db/bots-queue/claim-bot-jobs";
import releaseWorkerBotJobs from "@/src/functions/backend/db/bots-queue/release-worker-bot-jobs";
import { logError } from "@/src/functions/backend/logger";
import processBotJob from "./process-bot-job";
import reportLostBotJobs from "./report-lost-bot-jobs";
const worker = AppData.BotQueueWorker;
let running = 0;
/**
* Starts the `bots_queue` handler: releases jobs a restart cut off, then
* polls every `AppData.BotQueuePollMs`, reporting lost jobs and claiming
* up to `AppData.BotQueueConcurrency` at once. Gated in production to the
* `MAIN_SERVER` container, like cron.
*/
export default async function botsQueue() {
if (process.env.NODE_ENV == "production" && !process.env.MAIN_SERVER) {
return;
}
bunext.bunextLog.watch(`Running Bots Queue ...`);
const released = await releaseWorkerBotJobs({ worker });
if (released) {
bunext.bunextLog.info(`Bots queue: released ${released} cut-off job(s)`);
}
poll();
}
async function poll() {
try {
await reportLostBotJobs();
const free = AppData.BotQueueConcurrency - running;
if (free > 0) {
const jobs = await claimBotJobs({ worker, limit: free });
for (const job of jobs) {
running++;
processBotJob({ job, worker })
.catch((error) =>
logError({
title: `Bot job #${job.id} crashed`,
error,
}).catch(() => {}),
)
.finally(() => {
running--;
});
}
}
} catch (error) {
await logError({ title: "Bots queue poll failed", error }).catch(
() => {},
);
} finally {
setTimeout(poll, AppData.BotQueuePollMs);
}
}