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
+1 -1
View File
@@ -11,7 +11,7 @@ import { BotHues, type BotHue } from "@/src/dict/bot-dict";
import setProfileImage from "@/src/functions/frontend/set-profile-image";
import postAdminApi from "../(functions)/post-admin-api";
import usePickedImage from "../(hooks)/use-picked-image";
import pickFreeHue from "../(utils)/pick-free-hue";
import pickFreeHue from "@/src/utils/pick-free-hue";
import AddFormActions from "./add-form-actions";
import BotFormPreview from "./bot-form-preview";
@@ -1,14 +0,0 @@
import { describe, expect, test } from "bun:test";
import pickFreeHue from "./pick-free-hue";
describe("pickFreeHue", () => {
test("takes the first hue nobody has", () => {
expect(pickFreeHue({ taken: [] })).toBe("ember");
expect(pickFreeHue({ taken: ["ember", null, "amber"] })).toBe("moss");
});
test("falls back to the least used hue", () => {
const all = ["ember", "amber", "moss", "teal", "cobalt", "violet", "rose"];
expect(pickFreeHue({ taken: [...all, ...all.filter((h) => h != "teal")] })).toBe("teal");
});
});
-22
View File
@@ -1,22 +0,0 @@
import { BotHues, type BotHue } from "@/src/dict/bot-dict";
type Params = {
/**
* Hues the crew already has.
*/
taken: (string | null | undefined)[];
};
/**
* The first hue nobody on the crew has yet, else the least used one, so a
* new bot is told apart at a glance.
*/
export default function pickFreeHue({ taken }: Params): BotHue {
const counts = new Map<string, number>(BotHues.map((hue) => [hue.value, 0]));
for (const hue of taken) {
if (hue && counts.has(hue)) counts.set(hue, counts.get(hue)! + 1);
}
return [...BotHues].sort((a, b) => counts.get(a.value)! - counts.get(b.value)!)[0]!.value;
}