Skip to content
75 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Workspace agents are told where they are and who is in the conversation every turn, and hand work to a colleague with a hand_off tool instead of an @mention: the brief is posted where the colleague already is, or in a group message of the person who asked, the agent and the colleague with a card linking to it; an agent's messages no longer wake other agents, its mentions of anyone outside the conversation show as plain names, and the agents and chat guides and the workspace plan say how.1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import { type Colleague, type HandOffPorts, addressed, handOffPort } from "./handoff.ts";
5import type { HandedOff } from "./surface.ts";
6
7const mike: Colleague = { id: "agt_mike", handle: "mike", display_name: "Mike", builtin: false, status: "idle" };
8const g1t: Colleague = { id: "agt_g1t", handle: "g1t", display_name: "g1t", builtin: true, status: "idle" };
9const margo: Colleague = { id: "agt_margo", handle: "margo", display_name: "Margo", builtin: false, status: "paused" };
10const sam: Colleague = { id: "agt_sam", handle: "sam", display_name: "Sam", builtin: false, status: "out_of_budget" };
11
12/** A small workspace: these agents, and the person syntaqx. */
13function world(over: Partial<HandOffPorts> = {}, answer: HandedOff = { ok: true, where: "group_dm", opened: true }) {
14 const handed: { colleague: string; brief: string }[] = [];
15 const agents = [mike, g1t, margo, sam];
16 const ports: HandOffPorts = {
17 self: { id: "agt_g1t", handle: "g1t" },
18 chain: [],
19 asker: { username: "syntaqx", role: "owner", can_write: true },
20 agent: async (handle) => agents.find((a) => a.handle === handle) ?? null,
21 person: async (handle) => handle === "syntaqx",
22 handOff: async (colleague, brief) => {
23 handed.push({ colleague, brief });
24 return answer;
25 },
26 ...over,
27 };
28 return { port: handOffPort(ports), handed };
29}
30
31test("g1t hands Mike the hiring work: he is woken with the brief addressed to him, and g1t is told where it went", async () => {
32 const { port, handed } = world();
33 const done = await port("mike", "Help Chase hire an engineering manager: draft the role brief.");
34 assert.equal(done.ok, true);
35 assert.deepEqual(handed, [{ colleague: "agt_mike", brief: "@mike Help Chase hire an engineering manager: draft the role brief." }]);
36 assert.match(done.message, /Handed to @mike in a new group message with @syntaqx, you and them/);
37 assert.match(done.message, /a card here links to it/);
38});
39
40test("handed here, or into the group message they already share", async () => {
41 const here = await world({}, { ok: true, where: "here", opened: false }).port("mike", "x");
42 assert.match(here.message, /your brief is posted in this conversation/);
43 const again = await world({}, { ok: true, where: "group_dm", opened: false }).port("mike", "x");
44 assert.match(again.message, /the group message you three already have/);
45});
46
47test("refusals: unknown, a person, itself, @g1t, back along the chain, unavailable, an outside asker", async () => {
48 const refused = async (handle: string, over: Partial<HandOffPorts> = {}) => {
49 const { port, handed } = world(over);
50 const done = await port(handle, "Do it.");
51 assert.equal(done.ok, false, handle);
52 assert.equal(handed.length, 0, `nothing handed to ${handle}`);
53 return done.message;
54 };
55 assert.match(await refused("nobody"), /no agent called @nobody/);
56 assert.match(await refused("syntaqx"), /@syntaqx is a person, not an agent/);
57 assert.match(await refused("mike", { self: { id: "agt_mike", handle: "mike" } }), /That's you/);
58 assert.match(await refused("g1t", { self: { id: "agt_mike", handle: "mike" } }), /@g1t can't be handed work by an agent/);
59 assert.match(await refused("mike", { self: { id: "agt_sam", handle: "sam" }, chain: ["agt_g1t", "agt_mike"] }), /already handled this request/);
60 assert.match(await refused("margo"), /is paused/);
61 assert.match(await refused("sam"), /out of budget/);
62 assert.match(await refused("mike", { asker: { username: "eve", role: "outside", can_write: false } }), /isn't a member of this workspace/);
63});
64
65test("chat's own refusal comes back to the agent as the tool's answer", async () => {
66 const { port } = world({}, { ok: false, message: "This request has been passed along too many times." });
67 const done = await port("mike", "x");
68 assert.equal(done.ok, false);
69 assert.match(done.message, /couldn't be handed over: This request has been passed along too many times/);
70});
71
72test("a brief that already names the colleague isn't addressed twice", () => {
73 assert.equal(addressed("mike", "@Mike, please draft it."), "@Mike, please draft it.");
74 assert.equal(addressed("mike", "Please draft it, cc @mikey."), "@mike Please draft it, cc @mikey.");
75});