Let the coordinator create bots and put them in workspaces

The coordinator had no tool to add a worker, so a request to create one
fell back to update_workspace and wrote a bot that did not exist into the
workspace context. create_bot now adds a worker with its name, role and
instructions and joins it to a workspace, and add_bot_to_workspace puts an
existing worker in one. Both are coordinator-only and chat-replies-only,
and createBot still enforces the crew-manager check.

The coordinator's prompts now say how a bot gets created, and that one
which does not exist is never written into workspace context.

pick-free-hue moves to utils so the tool can pick a hue without importing
from layouts.
This commit is contained in:
2026-10-19 12:11:04 +01:00
parent e45d54d30b
commit 981a180d19
9 changed files with 190 additions and 9 deletions
@@ -0,0 +1,80 @@
import BunSQLite from "@moduletrace/bun-sqlite";
import { z } from "zod";
import type { BUN_SQLITE_COBBLE_BOTS, BUN_SQLITE_COBBLE_WORKSPACES } from "@/db/types/db";
import addBotToWorkspace from "@/src/functions/backend/db/workspaces/add-bot-to-workspace";
import type { HarnessTool } from "../../types";
type Params = {
user_id: number | string;
/**
* The user's worker bots.
*/
workers: BUN_SQLITE_COBBLE_BOTS[];
/**
* The user's live workspaces.
*/
workspaces: BUN_SQLITE_COBBLE_WORKSPACES[];
};
type Args = {
bot_id: number;
workspace_id: number;
};
/**
* Lets the coordinator put a crew member in one of the user's workspaces,
* so the user can talk to it there and it gets that workspace's context,
* computers and models. Offered in the coordinator's own chat only, where
* the whole crew and every live workspace are known.
*/
export default function addBotToWorkspaceTool({
user_id,
workers,
workspaces,
}: Params): HarnessTool<Args> {
const crew = workers.map((bot) => `${bot.id}: ${bot.name}`).join("; ");
const list = workspaces.map((w) => `${w.id}: ${w.name}`).join("; ");
return {
name: "add_bot_to_workspace",
description: `Put a crew member in one of the user's workspaces. There they work with the workspace's context, its computers and its models, and the user chats with them in it. Use it when the user asks for a bot to work in a workspace, or after create_bot made one without a place. Crew: ${crew || "none"}. Workspaces: ${list || "none"}.`,
schema: z.object({
bot_id: z.number().int(),
workspace_id: z.number().int(),
}),
run: async ({ args }) => {
const worker = workers.find((bot) => bot.id == args.bot_id);
const workspace = workspaces.find((w) => w.id == args.workspace_id);
if (!worker?.id) {
throw new Error(`No crew member has id ${args.bot_id}. Crew: ${crew || "none"}.`);
}
if (!workspace?.id) {
throw new Error(
`No workspace has id ${args.workspace_id}. Workspaces: ${list || "none"}.`,
);
}
const already =
(
await BunSQLite.sql<{ present: number }>({
sql: `SELECT 1 AS present FROM workspace_bots WHERE workspace_id = ? AND bot_id = ? LIMIT 1`,
values: [workspace.id, worker.id],
})
).payload?.[0] || null;
if (already) {
return `${worker.name} is already in ${workspace.name}.`;
}
await addBotToWorkspace({
user_id,
workspace_id: workspace.id,
bot_id: worker.id,
});
return `${worker.name} joined ${workspace.name}.`;
},
};
}