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
+4 -2
View File
@@ -1,11 +1,12 @@
import userAuth from "@/src/functions/backend/auth/user-auth";
import ensureCoordinatorBot from "@/src/functions/backend/db/bots/ensure-coordinator-bot";
import ensureMainComputer from "@/src/functions/backend/db/computers/ensure-main-computer";
import type { PagePropsType } from "@/src/types";
import type { BunextPageServerFn } from "@moduletrace/bunext/types";
/**
* Opens the coordinator's conversation, creating the coordinator first if
* this workspace doesn't have one yet.
* Opens the coordinator's conversation, creating the coordinator and the
* main computer first if this account doesn't have them yet.
*/
export const server: BunextPageServerFn<PagePropsType> = async ({ req }) => {
const { user } = await userAuth({ req });
@@ -19,6 +20,7 @@ export const server: BunextPageServerFn<PagePropsType> = async ({ req }) => {
}
const coordinator_id = await ensureCoordinatorBot({ user_id: user.id });
await ensureMainComputer({ user_id: user.id, coordinator_id });
return {
redirect: {
@@ -0,0 +1,72 @@
import type { BUN_SQLITE_COBBLE_BOT_COMPUTERS } from "@/db/types/db";
import type { AdminCrudAPIParams } from "@/src/types";
import isLockedComputerAccess from "@/src/utils/is-locked-computer-access";
import BunSQLite from "@moduletrace/bun-sqlite";
import type { APIResponseObject } from "@moduletrace/bunext/types";
import _ from "lodash";
type AccessRow = BUN_SQLITE_COBBLE_BOT_COMPUTERS & {
bot_kind?: string;
computer_kind?: string;
};
/**
* Deletes the matching access rows, skipping the coordinator's access to
* the main computer.
*/
export default async function (
params: AdminCrudAPIParams,
): Promise<APIResponseObject> {
const { table, body, id, query } = params;
const records_to_delete =
await BunSQLite.select<BUN_SQLITE_COBBLE_BOT_COMPUTERS>({
table,
query: _.merge(query?.sql_query, body?.sql_query) || {},
targetId: id,
});
if (!records_to_delete.payload?.[0]) {
return {
success: false,
msg: `No Records to Delete`,
};
}
let skipped = 0;
for (let i = 0; i < records_to_delete.payload.length; i++) {
const record = records_to_delete.payload[i];
if (!record?.id) continue;
const access = (
await BunSQLite.sql<AccessRow>({
sql: `SELECT bc.id, b.kind AS bot_kind, c.kind AS computer_kind FROM bot_computers bc JOIN bots b ON b.id = bc.bot_id JOIN computers c ON c.id = bc.computer_id WHERE bc.id = ? LIMIT 1`,
values: [record.id],
})
).payload?.[0];
if (
isLockedComputerAccess({
bot_kind: access?.bot_kind,
computer_kind: access?.computer_kind,
})
) {
skipped++;
continue;
}
await BunSQLite.delete({
table,
targetId: record.id,
});
}
return {
success: true,
msg: skipped
? `The coordinator always has access to the main computer.`
: undefined,
};
}
@@ -0,0 +1,42 @@
import type { AdminCrudAPIParams } from "@/src/types";
import type { APIResponseObject } from "@moduletrace/bunext/types";
import checkUserAccess from "@/src/functions/backend/auth/check-user-access";
import get from "../../(functions)/get";
import post from "../../(functions)/post";
import put from "./put";
import del from "./del";
/**
* Generic CRUD (super admin only), except that the coordinator's access to
* the main computer can't be changed or removed.
*/
export default async function (
params: AdminCrudAPIParams,
): Promise<APIResponseObject> {
const { req, user_types } = params;
const is_super_admin = checkUserAccess({ user_types });
if (!is_super_admin.success) {
return {
success: false,
msg: `Unauthorized`,
};
}
switch (req.method) {
case "GET":
return await get(params);
case "POST":
return await post(params);
case "PUT":
return await put(params);
case "DELETE":
return await del(params);
default:
return {
success: false,
};
}
}
@@ -0,0 +1,11 @@
import type { AdminCrudAPIParams } from "@/src/types";
import type { APIResponseObject } from "@moduletrace/bunext/types";
export default async function (
params: AdminCrudAPIParams,
): Promise<APIResponseObject> {
return {
success: false,
msg: `Can't update computer access. Can only delete and insert.`,
};
}
+3
View File
@@ -2,6 +2,7 @@ import _ from "lodash";
import users from "./(tables)/users";
import userTypes from "./(tables)/user_types";
import media from "./(tables)/media";
import botComputers from "./(tables)/bot_computers";
import get from "./(functions)/get";
import put from "./(functions)/put";
import del from "./(functions)/delete";
@@ -83,6 +84,8 @@ export const handler: BunextAPIRouteHandler<APIResponseObject> = async (
return await userTypes(crud_params);
case "media":
return await media(crud_params);
case "bot_computers":
return await botComputers(crud_params);
default:
break;
+14 -9
View File
@@ -3,6 +3,7 @@ import setUserPassword from "@/src/functions/backend/auth/set-user-password";
import grabHasUsers from "@/src/functions/backend/auth/grab-has-users";
import loginUser from "@/src/functions/backend/auth/login-user";
import ensureCoordinatorBot from "@/src/functions/backend/db/bots/ensure-coordinator-bot";
import ensureMainComputer from "@/src/functions/backend/db/computers/ensure-main-computer";
import validatePassword from "@/src/utils/validate-password";
import logEvent, { grabRequestIP, logError } from "@/src/functions/backend/logger";
import { AppData } from "@/src/data/app-data";
@@ -74,16 +75,20 @@ export const handler: BunextAPIRouteHandler<APIResponseObject> = async ({
/**
* The account exists now, so a failure here mustn't fail signup
* (which is one-shot). `/admin/bots` creates it on the next visit.
* (which is one-shot). `/admin/bots` creates both on the next visit.
*/
await ensureCoordinatorBot({ user_id: new_user_id }).catch((error) =>
logError({
title: "Coordinator bot not created at signup",
error,
user_id: new_user_id,
ip_address: grabRequestIP(req),
}).catch(() => {}),
);
await ensureCoordinatorBot({ user_id: new_user_id })
.then((coordinator_id) =>
ensureMainComputer({ user_id: new_user_id, coordinator_id }),
)
.catch((error) =>
logError({
title: "Coordinator or main computer not created at signup",
error,
user_id: new_user_id,
ip_address: grabRequestIP(req),
}).catch(() => {}),
);
await logEvent({
title: "Super admin created",