Give the coordinator working crew management tools

- Resolve the crew and workspaces fresh inside each tool run, so a bot or
  workspace created earlier in the same reply is usable: create_bot,
  assign_computer, add_bot_to_workspace, delegate_task and
  set_bot_workspace_instructions.
- Add coordinator-only update_bot, find_bot, find_workspace, delete_bot,
  delete_workspace and remove_bot_from_workspace tools.
- Push server:workspace-removed when a workspace is deleted, and
  workspace_ids on server:bot-updated so a workspace's crew drops a bot
  that was taken out of it.
This commit is contained in:
2026-10-19 20:34:51 +01:00
parent b79cebf397
commit 139b3e443d
20 changed files with 648 additions and 39 deletions
+58
View File
@@ -0,0 +1,58 @@
import BunSQLite from "@moduletrace/bun-sqlite";
import { z } from "zod";
import type { BUN_SQLITE_COBBLE_BOTS } from "@/db/types/db";
import type { HarnessTool } from "../../types";
type Args = {
query?: string;
limit?: number;
};
/**
* Lets the coordinator look up crew members in the install: by id, name,
* role or core instructions, so it can find the bot behind a job before
* changing or deleting it. Read fresh every call, so a bot another reply
* just created is already there.
*/
export default function findBotTool(): HarnessTool<Args> {
return {
name: "find_bot",
description: `Find crew members: by id, name, the one-line role, or words in their core instructions. Returns each match's id, name, kind and role. With no query it lists the crew, up to limit. The coordinator is listed too but can't be deleted.`,
schema: z.object({
query: z.string().trim().max(200).optional(),
limit: z.number().int().min(1).max(50).optional(),
}),
run: async ({ args }) => {
const all =
(
await BunSQLite.sql<BUN_SQLITE_COBBLE_BOTS>({
sql: `SELECT * FROM bots ORDER BY id ASC`,
values: [],
})
).payload || [];
const query = (args.query || "").trim().toLowerCase();
const matches = (
query
? all.filter((bot) =>
[bot.id ? String(bot.id) : "", bot.name, bot.role, bot.system_prompt].some(
(field) => (field || "").toLowerCase().includes(query),
),
)
: all
).slice(0, args.limit ?? 20);
if (!matches.length) {
return `No crew member matches "${args.query || ""}".`;
}
return matches
.map(
(bot) =>
`#${bot.id} ${bot.name} (${bot.kind}${bot.role ? `: ${bot.role}` : ""})`,
)
.join("\n");
},
};
}