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]>
112 lines
4.2 KiB
TypeScript
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/);
|
|
});
|
|
});
|