Files
cobble/src/harness/tools/bot/delegate-task-tool.test.ts
T
tbenandClaude Opus 5.5 93896646c7 Let the coordinator and workspace admins hand over tasks and routines
delegate_task takes a task_id to hand over a task already on a list: it
moves to the worker (a workspace's task stays on its list), goes to
doing, and the job is linked back so its end is noted on the task.
hand_over_schedule moves a bot's own routine to another bot, keeping its
workspace. Users get Hand over in the Tasks UI and both routine lists.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-25 11:23:26 +01:00

112 lines
4.2 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import type { BUN_SQLITE_COBBLE_BOTS, BUN_SQLITE_COBBLE_WORKSPACES } from "@/db/types/db";
import delegateTaskTool from "./delegate-task-tool";
const worker = { id: 5, name: "Researcher", role: "research", kind: "worker" } as BUN_SQLITE_COBBLE_BOTS;
const workspace = { id: 3, name: "Launch", archived: 0 } as BUN_SQLITE_COBBLE_WORKSPACES;
const signal = new AbortController().signal;
function coordinatorTool({
workspace_id,
workers = [worker],
workspaces = [workspace],
}: { workspace_id?: number; workers?: BUN_SQLITE_COBBLE_BOTS[]; workspaces?: BUN_SQLITE_COBBLE_WORKSPACES[] } = {}) {
return delegateTaskTool({
user_id: 1,
requester_id: 1,
session_id: 10,
workspace_id: workspace_id ?? null,
requester_name: "Coordinator",
workers,
workspaces,
});
}
/**
* The tool's own confines, kept to what needs no database: the argument it
* accepts and the refusal that keeps a workspace hand-over in its
* workspace. Every run that gets past those reaches the DB.
*/
describe("delegateTaskTool", () => {
test("takes an optional workspace the task runs in", () => {
const tool = coordinatorTool();
expect(tool.name).toBe("delegate_task");
expect(tool.schema?.safeParse({ bot_id: 5, title: "Dig", instructions: "Read the docs." }).success).toBe(true);
expect(
tool.schema?.safeParse({ bot_id: 5, title: "Dig", instructions: "Read the docs.", workspace_id: 3 }).success,
).toBe(true);
});
test("names the workspaces it can hand work into", () => {
expect(coordinatorTool().description).toContain("3: Launch");
});
test("in a workspace, says the task runs there and offers no other", () => {
const tool = coordinatorTool({ workspace_id: 3 });
expect(tool.description).toContain("runs there");
expect(tool.schema?.safeParse({ bot_id: 5, title: "Dig", instructions: "x", workspace_id: 9 }).success).toBe(
true,
);
});
test("refuses to move a hand-over out of the workspace it came from", async () => {
await expect(
coordinatorTool({ workspace_id: 3 }).run({
args: { bot_id: 5, title: "Dig", instructions: "x", workspace_id: 9 },
signal,
}),
).rejects.toThrow(/runs in that workspace/);
});
test("accepts the workspace it came from", async () => {
/** Reaches the DB and fails there for a test id, never on the guard. */
await expect(
coordinatorTool({ workspace_id: 3 }).run({
args: { bot_id: 5, title: "Dig", instructions: "x", workspace_id: 3 },
signal,
}),
).rejects.not.toThrow(/runs in that workspace/);
});
test("a workspace admin can only hand work to the bots it may direct", async () => {
const tool = delegateTaskTool({
user_id: 1,
requester_id: 7,
session_id: 10,
workspace_id: 3,
requester_name: "Lead",
workers: [worker],
grabTargets: async () => [worker],
});
await expect(
tool.run({ args: { bot_id: 8, title: "Dig", instructions: "x" }, signal }),
).rejects.toThrow(/No crew member has id 8 in workspace 3. Crew: 5: Researcher/);
});
test("takes the id of a task already on a list in place of a brief", () => {
const tool = coordinatorTool();
expect(tool.schema?.safeParse({ bot_id: 5, task_id: 12 }).success).toBe(true);
expect(tool.schema?.safeParse({ bot_id: 5, task_id: 12, instructions: "Start with the intro." }).success).toBe(
true,
);
expect(tool.description).toContain("task_id");
});
test("without a task_id, still needs a title and instructions", async () => {
const tool = delegateTaskTool({
user_id: 1,
requester_id: 1,
session_id: 10,
requester_name: "Coordinator",
workers: [worker],
grabTargets: async () => [worker],
});
await expect(tool.run({ args: { bot_id: 5 }, signal })).rejects.toThrow(/Give a title and instructions/);
});
});