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.
| Chat and workspace agents: channels, DMs and named agents you talk to | 1 | import { test } from "node:test"; |
| 2 | import assert from "node:assert/strict"; | |
| 3 | ||
| In a group message with several agents, a message that names no agent goes to the one you are talking with: the agents that answered you last, else the ones you last mentioned, else, before anyone has been addressed, all of them; a message that mentions agents still goes only to those, and a conversation with one agent is as it was. Each agent is also told, when several are here, who you are talking with, so one that is woken alongside answers only what is clearly for it and does not redo a colleague's work. And an agent shows as typing for as long as it works, not for eight seconds: its desk says so every five seconds until the answer is posted, and after ten seconds the line reads that it is still working on it. The chat and agents guides say so. | 4 | import { MAX_HOPS, type RecentMessage, addressedAgents, chainFor, deliveries, delivery, handOffPlace, handOffRefusal, personalAgentRefusal } from "./delivery.ts"; |
| Chat and workspace agents: channels, DMs and named agents you talk to | 5 | |
| 6 | const ship = { id: "a1", handle: "ship" }; | |
| 7 | const docs = { id: "a2", handle: "docs" }; | |
| 8 | ||
| In a group message with several agents, a message that names no agent goes to the one you are talking with: the agents that answered you last, else the ones you last mentioned, else, before anyone has been addressed, all of them; a message that mentions agents still goes only to those, and a conversation with one agent is as it was. Each agent is also told, when several are here, who you are talking with, so one that is woken alongside answers only what is clearly for it and does not redo a colleague's work. And an agent shows as typing for as long as it works, not for eight seconds: its desk says so every five seconds until the answer is posted, and after ten seconds the line reads that it is still working on it. The chat and agents guides say so. | 9 | test("a person's direct message with nothing said yet wakes every agent in it, at hop 0", () => { |
| Chat and workspace agents: channels, DMs and named agents you talk to | 10 | assert.deepEqual(deliveries({ author: "user:u", hops: 3, channelKind: "dm", agents: [ship, docs], mentioned: [] }), [ |
| 11 | { agent_id: "a1", hops: 0 }, | |
| 12 | { agent_id: "a2", hops: 0 }, | |
| 13 | ]); | |
| In a group message with several agents, a message that names no agent goes to the one you are talking with: the agents that answered you last, else the ones you last mentioned, else, before anyone has been addressed, all of them; a message that mentions agents still goes only to those, and a conversation with one agent is as it was. Each agent is also told, when several are here, who you are talking with, so one that is woken alongside answers only what is clearly for it and does not redo a colleague's work. And an agent shows as typing for as long as it works, not for eight seconds: its desk says so every five seconds until the answer is posted, and after ten seconds the line reads that it is still working on it. The chat and agents guides say so. | 14 | assert.deepEqual(deliveries({ author: "user:u", hops: 0, channelKind: "dm", agents: [ship, docs], mentioned: [], recent: [] }), [ |
| 15 | { agent_id: "a1", hops: 0 }, | |
| 16 | { agent_id: "a2", hops: 0 }, | |
| 17 | ]); | |
| 18 | // With one agent there is nobody else it could be for. | |
| 19 | assert.deepEqual(deliveries({ author: "user:u", hops: 0, channelKind: "dm", agents: [ship], mentioned: [], recent: [{ author: "user:u", mentioned: ["docs"] }] }), [{ agent_id: "a1", hops: 0 }]); | |
| 20 | }); | |
| 21 | ||
| 22 | const mike = { id: "a_mike", handle: "mike" }; | |
| 23 | const g1t = { id: "a_g1t", handle: "g1t" }; | |
| 24 | const says = (author: string, ...mentioned: string[]): RecentMessage => ({ author, mentioned }); | |
| 25 | ||
| 26 | test("in a group DM, an unaddressed message goes to the agent the person is talking with", () => { | |
| 27 | // The conversation as it happened: Chase, Mike the recruiter and @g1t in one group message. | |
| 28 | const transcript = [ | |
| 29 | says("user:chase"), // "Are you able to generate PDFs now?" | |
| 30 | says("agent:a_mike"), | |
| 31 | says("agent:a_g1t"), | |
| 32 | says("user:chase", "mike"), // "@Mike Make a PDF of that example resume for me" | |
| 33 | says("agent:a_mike"), | |
| 34 | ]; | |
| 35 | // "Let's take that template and make a fake resume for some fake person": Mike was helping. | |
| 36 | assert.deepEqual(deliveries({ author: "user:chase", hops: 0, channelKind: "dm", agents: [mike, g1t], mentioned: [], recent: transcript }), [{ agent_id: "a_mike", hops: 0 }]); | |
| 37 | // The first question, with nothing before it, went to both; so does a follow-up to both their answers. | |
| 38 | assert.deepEqual(addressedAgents([mike, g1t], "user:chase", []), [mike, g1t]); | |
| 39 | assert.deepEqual(addressedAgents([mike, g1t], "user:chase", transcript.slice(0, 3)), [mike, g1t]); | |
| 40 | // A mention still goes only to those named, whoever was talking. | |
| 41 | assert.deepEqual(deliveries({ author: "user:chase", hops: 0, channelKind: "dm", agents: [mike, g1t], mentioned: ["g1t"], recent: transcript }), [{ agent_id: "a_g1t", hops: 0 }]); | |
| 42 | }); | |
| 43 | ||
| 44 | test("who is addressed: the last exchange, else the person's last mention, else everyone", () => { | |
| 45 | // Mentioned but not yet answered: the mention stands, through the person's own follow-ups. | |
| 46 | assert.deepEqual(addressedAgents([mike, g1t], "user:chase", [says("user:chase", "Mike"), says("user:chase")]), [mike]); | |
| 47 | // Answered by a colleague after the mention (a hand-off here, say): the colleague has the conversation now. | |
| 48 | assert.deepEqual(addressedAgents([mike, g1t], "user:chase", [says("user:chase", "mike"), says("agent:a_mike", "g1t"), says("user:chase"), says("agent:a_g1t")]), [g1t]); | |
| 49 | // Both answered the last message: both are in the exchange, in the conversation's order. | |
| 50 | assert.deepEqual(addressedAgents([mike, g1t], "user:chase", [says("user:chase", "g1t", "mike"), says("agent:a_g1t"), says("agent:a_mike")]), [mike, g1t]); | |
| 51 | // A thank-you in between changes nothing: the person's last mention still says who. | |
| 52 | assert.deepEqual(addressedAgents([mike, g1t], "user:chase", [says("user:chase", "mike"), says("agent:a_mike"), says("user:chase"), says("user:chase")]), [mike]); | |
| 53 | // Another person's message ends the exchange without deciding it; only this person's mentions do. | |
| 54 | assert.deepEqual(addressedAgents([mike, g1t], "user:chase", [says("user:chase", "mike"), says("agent:a_mike"), says("user:bo", "g1t")]), [mike]); | |
| 55 | assert.deepEqual(addressedAgents([mike, g1t], "user:chase", [says("user:bo", "g1t"), says("agent:a_g1t")]), [g1t], "whoever answered last, whoever they answered"); | |
| 56 | // A second message before anyone answers the first goes where the first did. | |
| 57 | assert.deepEqual(addressedAgents([mike, g1t], "user:chase", [says("user:bo", "g1t"), says("agent:a_g1t"), says("user:chase")]), [g1t]); | |
| 58 | assert.deepEqual(addressedAgents([mike, g1t], "user:chase", [says("user:chase"), says("agent:a_mike"), says("user:chase")]), [mike], "only Mike answered the first"); | |
| 59 | // Mentions of agents that aren't here, and messages of agents that left, decide nothing. | |
| 60 | assert.deepEqual(addressedAgents([mike, g1t], "user:chase", [says("user:chase", "sam"), says("agent:a_gone")]), [mike, g1t]); | |
| 61 | assert.deepEqual(addressedAgents([mike], "user:chase", [says("user:chase", "g1t")]), [mike], "one agent: nothing to decide"); | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 62 | }); |
| 63 | ||
| Merge branch 'worktree-agent-a1398e81ad1a64c5f' | 64 | test("in a group direct message, a person who mentions some of its agents wakes only those", () => { |
| 65 | assert.deepEqual(deliveries({ author: "user:u", hops: 0, channelKind: "dm", agents: [ship, docs], mentioned: ["docs"] }), [{ agent_id: "a2", hops: 0 }]); | |
| 66 | // Mentioning someone who isn't in it is no mention of its agents: all of them answer. | |
| 67 | assert.deepEqual(deliveries({ author: "user:u", hops: 0, channelKind: "dm", agents: [ship, docs], mentioned: ["mike"] }), [ | |
| 68 | { agent_id: "a1", hops: 0 }, | |
| 69 | { agent_id: "a2", hops: 0 }, | |
| 70 | ]); | |
| 71 | }); | |
| 72 | ||
| Chat and workspace agents: channels, DMs and named agents you talk to | 73 | test("in a channel, a person wakes only the agent members they mention", () => { |
| 74 | assert.deepEqual(deliveries({ author: "user:u", hops: 0, channelKind: "channel", agents: [ship, docs], mentioned: [] }), []); | |
| 75 | assert.deepEqual( | |
| 76 | deliveries({ author: "user:u", hops: 0, channelKind: "channel", agents: [ship, docs], mentioned: ["Ship", "nobody"] }), | |
| 77 | [{ agent_id: "a1", hops: 0 }], | |
| 78 | ); | |
| 79 | }); | |
| 80 | ||
| Merge branch 'worktree-agent-a1398e81ad1a64c5f' | 81 | test("an agent's message wakes nobody, even when it @mentions an agent member", () => { |
| Chat and workspace agents: channels, DMs and named agents you talk to | 82 | assert.deepEqual(deliveries({ author: "agent:a1", hops: 0, channelKind: "dm", agents: [ship, docs], mentioned: [] }), []); |
| Merge branch 'worktree-agent-a1398e81ad1a64c5f' | 83 | assert.deepEqual(deliveries({ author: "agent:a1", hops: 0, channelKind: "dm", agents: [ship, docs], mentioned: ["docs"] }), []); |
| 84 | assert.deepEqual(deliveries({ author: "agent:a1", hops: 2, channelKind: "channel", agents: [ship, docs], mentioned: ["docs", "ship"] }), []); | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 85 | }); |
| 86 | ||
| 87 | test("a delivery carries the chain's asker on g1t's own chat, and the wake's hops", () => { | |
| 88 | const asker = { username: "ada", role: "member", can_write: false }; | |
| 89 | const place = { workspace: "acme", workspace_id: "w1", channel_id: "c1", channel_kind: "channel", channel_name: "ops" }; | |
| 90 | assert.deepEqual( | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 91 | delivery(place, { agent_id: "a2", hops: 3 }, { id: "m1", thread_root: "m0" }, { hops: 2, asked_by: "u1", asker, chain: ["a1"] }), |
| 92 | { ...place, agent_id: "a2", message_id: "m1", thread_root: "m0", asked_by: "u1", asker, chain: ["a1"], hops: 3, surface: "g1t" }, | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 93 | ); |
| 94 | // An agent that passed no asker back hands on none. | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 95 | assert.equal(delivery(place, { agent_id: "a2", hops: 1 }, { id: "m1", thread_root: null }, { hops: 0, asked_by: "u1", asker: null, chain: [] }).asker, null); |
| 96 | }); | |
| 97 | ||
| 98 | test("mentioning @g1t in a channel brings it in once; never into a DM", async () => { | |
| 99 | const { addsOrchestrator } = await import("./delivery.ts"); | |
| 100 | assert.equal(addsOrchestrator({ channelKind: "channel", mentioned: ["G1T"], orchestratorIsMember: false }), true); | |
| 101 | assert.equal(addsOrchestrator({ channelKind: "channel", mentioned: ["g1t"], orchestratorIsMember: true }), false, "already in"); | |
| 102 | assert.equal(addsOrchestrator({ channelKind: "channel", mentioned: ["ship"], orchestratorIsMember: false }), false); | |
| 103 | assert.equal(addsOrchestrator({ channelKind: "dm", mentioned: ["g1t"], orchestratorIsMember: false }), false); | |
| 104 | // Once in, it is woken like any mentioned agent member. | |
| 105 | const g1t = { id: "a9", handle: "g1t" }; | |
| 106 | assert.deepEqual(deliveries({ author: "user:u", hops: 0, channelKind: "channel", agents: [ship, g1t], mentioned: ["g1t"] }), [{ agent_id: "a9", hops: 0 }]); | |
| 107 | }); | |
| 108 | ||
| Merge branch 'worktree-agent-a1398e81ad1a64c5f' | 109 | test("a chain is the agents that handled a request, with the poster added", () => { |
| 110 | assert.deepEqual(chainFor(["a9"], "a1"), ["a9", "a1"]); | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 111 | assert.deepEqual(chainFor("nonsense", "a1"), ["a1"]); |
| 112 | }); | |
| 113 | ||
| Merge branch 'worktree-agent-a1398e81ad1a64c5f' | 114 | test("a hand-off goes here when the colleague is in this channel or group DM, else to a group DM", () => { |
| 115 | assert.equal(handOffPlace({ channelKind: "channel", members: 40, colleagueHere: true }), "here"); | |
| 116 | assert.equal(handOffPlace({ channelKind: "dm", members: 3, colleagueHere: true }), "here"); | |
| 117 | assert.equal(handOffPlace({ channelKind: "channel", members: 40, colleagueHere: false }), "group_dm"); | |
| 118 | // The 1:1 DM the user saw: @g1t and the person, Mike not in it. | |
| 119 | assert.equal(handOffPlace({ channelKind: "dm", members: 2, colleagueHere: false }), "group_dm"); | |
| 120 | }); | |
| 121 | ||
| 122 | test("a hand-off is refused to itself, to @g1t, back along the chain, and past the hop limit", () => { | |
| 123 | const ok = { agent: "a9", colleague: { id: "a1" }, chain: [], hops: 0 }; | |
| 124 | assert.equal(handOffRefusal(ok), null); | |
| 125 | assert.match(handOffRefusal({ ...ok, colleague: { id: "a9" } })!, /itself/); | |
| 126 | assert.match(handOffRefusal({ ...ok, agent: "a1", colleague: { id: "a9", builtin: true } })!, /@g1t/); | |
| 127 | assert.match(handOffRefusal({ ...ok, agent: "a2", chain: ["a1", "a2"] })!, /already handled/); | |
| 128 | assert.equal(handOffRefusal({ ...ok, hops: MAX_HOPS - 1 }), null); | |
| 129 | assert.match(handOffRefusal({ ...ok, hops: MAX_HOPS })!, /too many times/); | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 130 | }); |
| Anyone makes an agent by describing it: one box drafts the whole agent (name, job, responsibilities, voice, skills to keep, integrations it needs, model limits and budget) as a card to change and try in a chat beside it before creating it, and an agent changes later by saying what to change, shown as a diff before it's saved. Members can make personal agents, which only they talk to and which spend from their own budget, unless owners turn that off; owners promote one to a workspace agent. The agents guide says how. | 131 | |
| 132 | test("a personal agent is only in the direct message of it and the person it belongs to", () => { | |
| 133 | const mine = { id: "a9", handle: "pip", scope: "personal", personal_owner_id: "u1" }; | |
| 134 | assert.equal(personalAgentRefusal(mine, { kind: "dm", members: ["user:u1", "agent:a9"] }), null); | |
| 135 | assert.match(personalAgentRefusal(mine, { kind: "dm", members: ["user:u2", "agent:a9"] }) ?? "", /personal agent/); | |
| 136 | assert.match(personalAgentRefusal(mine, { kind: "dm", members: ["user:u1", "user:u2", "agent:a9"] }) ?? "", /personal agent/, "not a group DM"); | |
| 137 | assert.match(personalAgentRefusal(mine, { kind: "channel", members: null }) ?? "", /personal agent/, "never invited to a channel"); | |
| 138 | assert.match(personalAgentRefusal(mine, { kind: "hand_off", members: null }) ?? "", /personal agent/, "never handed work"); | |
| 139 | assert.equal(personalAgentRefusal({ id: "a1", handle: "ship", scope: "workspace" }, { kind: "channel", members: null }), null); | |
| 140 | assert.equal(personalAgentRefusal({ id: "a1", handle: "ship" }, { kind: "hand_off", members: null }), null, "agents from before personal ones are the workspace's"); | |
| 141 | }); |
This file's history is long; its oldest lines are credited to the oldest commit read.