Skip to content
197 linesCodeBlameRaw
1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import {
5 ABILITY_LEVELS,
6 COMPUTER_ABILITIES,
7 G1T_ABILITIES,
8 abilitiesSummary,
9 allAbilities,
10 askedFor,
11 autonomyOfLevel,
12 checkMcpName,
13 checkMcpUrl,
14 connectorsWithAbilities,
15 defaultLevel,
16 findAbility,
17 integrationAbilities,
18 levelOfAutonomy,
19 maxLevel,
20 mcpAbility,
21 mcpToolName,
22 resolveAbilities,
23 withSetting,
24 withinLevel,
25} from "./abilities.ts";
26import { CONNECTORS } from "./connectors.ts";
27
28const AUTONOMY = { open_pull_requests: "alone", merge: "approval", deploy_production: "approval", edit_docs: "suggest" } as const;
29const resolve = (over: Partial<Parameters<typeof resolveAbilities>[0]> = {}) => resolveAbilities({ connectors: CONNECTORS, abilities: null, autonomy: AUTONOMY, connected: [], ...over });
30
31test("defaults by kind: reads alone, writes inside g1t when asked for, sending outside asks first, restricted never and no freer than asking", () => {
32 assert.equal(defaultLevel("read"), "alone");
33 assert.equal(defaultLevel("write"), "asked");
34 assert.equal(defaultLevel("send"), "ask");
35 assert.equal(defaultLevel("restricted"), "never");
36 assert.equal(maxLevel("restricted"), "ask");
37 assert.equal(maxLevel("send"), "alone");
38 assert.ok(withinLevel("never", "ask"));
39 assert.ok(!withinLevel("alone", "ask"));
40 assert.deepEqual(ABILITY_LEVELS, ["alone", "asked", "ask", "never"]);
41});
42
43test("every integration ability belongs to an available workspace connector with a provider, and its kind fits the connector's capabilities", () => {
44 const withAbilities = connectorsWithAbilities(CONNECTORS);
45 assert.ok(withAbilities.length >= 3, "Linear, Jira and Sentry at least");
46 for (const connector of withAbilities) {
47 assert.equal(connector.status, "available", `${connector.id} is connectable today`);
48 assert.ok(connector.scopes.includes("workspace"), `${connector.id} connects for a workspace`);
49 assert.ok(connector.provider, `${connector.id} has a provider the integrations service knows`);
50 const defs = integrationAbilities(connector.id);
51 const caps = connector.capabilities ?? [];
52 if (defs.some((d) => d.kind === "read")) assert.ok(caps.includes("Agents can read"), `${connector.id} says agents can read`);
53 if (defs.some((d) => d.kind === "send")) assert.ok(caps.includes("Writes back"), `${connector.id} says it writes back`);
54 for (const def of defs) {
55 assert.equal(def.id, `integration:${connector.id}:${def.id.split(":")[2]}`);
56 assert.equal(def.tools.length, 1, "one tool per integration ability");
57 }
58 }
59 assert.deepEqual(integrationAbilities("datadog"), [], "Datadog opens issues by itself: nothing for an agent to call");
60});
61
62test("g1t's own are always on and the four autonomy ones keep their choices as levels", () => {
63 const sections = resolve();
64 const g1t = sections.find((s) => s.group === "g1t")!;
65 const artifacts = g1t.sources[0]!.abilities.find((a) => a.id === "g1t:artifacts")!;
66 assert.equal(artifacts.level, "alone");
67 assert.equal(artifacts.can_change, false);
68 const merge = g1t.sources[0]!.abilities.find((a) => a.id === "g1t:merge")!;
69 assert.equal(merge.level, "ask", "approval reads as Ask first");
70 assert.deepEqual(merge.choices, ["alone", "ask", "never"]);
71 const deploy = g1t.sources[0]!.abilities.find((a) => a.id === "g1t:deploy")!;
72 assert.equal(deploy.kind, "restricted");
73 assert.ok(!deploy.choices.includes("alone"), "production deploys never go alone");
74 assert.equal(levelOfAutonomy("suggest"), "ask");
75 assert.equal(autonomyOfLevel("edit_docs", "ask"), "suggest");
76 assert.equal(autonomyOfLevel("merge", "ask"), "approval");
77 assert.equal(autonomyOfLevel("merge", "never"), "never");
78 assert.equal(G1T_ABILITIES.filter((d) => d.autonomy).length, 4);
79 const computer = sections.find((s) => s.group === "computer")!;
80 const shell = computer.sources[0]!.abilities.find((a) => a.id === "computer:shell")!;
81 assert.equal(shell.status, "ready");
82 assert.equal(shell.level, "asked", "commands run when asked for, until the person says otherwise");
83 assert.deepEqual(shell.choices, ["alone", "asked", "ask", "never"]);
84 const files = computer.sources[0]!.abilities.find((a) => a.id === "computer:files")!;
85 assert.equal(files.level, "alone");
86 assert.ok(files.can_change);
87 const coming = computer.sources[0]!.abilities.filter((a) => a.id === "computer:browser" || a.id === "computer:web");
88 assert.equal(coming.length, 2);
89 assert.ok(coming.every((a) => a.status === "coming" && a.choices.length === 0 && !a.can_change), "the browser and web search are still coming");
90 assert.equal(COMPUTER_ABILITIES.length, 4);
91});
92
93test("a connected integration lists its rows at their defaults; one that isn't is listed, but nothing there can be changed", () => {
94 const sections = resolve({ connected: ["linear"] });
95 const integrations = sections.find((s) => s.group === "integration")!;
96 const linear = integrations.sources.find((s) => s.id === "linear")!;
97 assert.equal(linear.connected, true);
98 assert.deepEqual(
99 linear.abilities.map((a) => [a.id, a.level, a.credentials, a.can_change]),
100 [
101 ["integration:linear:read", "alone", "workspace", true],
102 ["integration:linear:import", "asked", "workspace", true],
103 ["integration:linear:comment", "ask", "workspace", true],
104 ],
105 );
106 assert.equal(linear.abilities[0]!.personal_available, false, "Linear's personal side is still coming");
107 const jira = integrations.sources.find((s) => s.id === "jira")!;
108 assert.equal(jira.connected, false);
109 assert.ok(jira.abilities.every((a) => !a.can_change));
110 assert.equal(integrations.sources[0]!.id, "linear", "connected first");
111 assert.ok(!integrations.sources.some((s) => s.id === "webhooks" || s.id === "anthropic"), "nothing to call, not connected: not listed");
112 const withDatadog = resolve({ connected: ["datadog"] }).find((s) => s.group === "integration")!;
113 const datadog = withDatadog.sources.find((s) => s.id === "datadog")!;
114 assert.equal(datadog.abilities.length, 0);
115 assert.match(datadog.note ?? "", /Opens issues by itself/);
116});
117
118test("a setting moves a level within its kind's limit, and is dropped when it is the default again", () => {
119 let abilities = withSetting(null, "integration:linear:comment", { level: "alone" });
120 assert.deepEqual(abilities.settings, { "integration:linear:comment": { level: "alone" } });
121 const sections = resolve({ connected: ["linear"], abilities });
122 assert.equal(findAbility(sections, "integration:linear:comment")!.ability.level, "alone");
123 abilities = withSetting(abilities, "integration:linear:comment", { credentials: "asker" });
124 assert.deepEqual(abilities.settings["integration:linear:comment"], { level: "alone", credentials: "asker" });
125 abilities = withSetting(abilities, "integration:linear:comment", { level: null, credentials: null });
126 assert.deepEqual(abilities.settings, {}, "nothing kept once everything is the default");
127 // A restricted ability set freer than Ask is held at Ask.
128 const held = resolveAbilities({ connectors: CONNECTORS, abilities: null, autonomy: { ...AUTONOMY, deploy_production: "approval" }, connected: [] });
129 assert.equal(findAbility(held, "g1t:deploy")!.ability.max, "ask");
130});
131
132test("a personal agent runs on the asker's connections unless an owner chose the workspace's", () => {
133 const sections = resolve({ connected: ["linear"], personal: true });
134 assert.equal(findAbility(sections, "integration:linear:read")!.ability.credentials, "asker");
135 const chosen = resolve({ connected: ["linear"], personal: true, abilities: withSetting(null, "integration:linear:read", { credentials: "workspace" }) });
136 assert.equal(findAbility(chosen, "integration:linear:read")!.ability.credentials, "workspace");
137});
138
139test("MCP servers: each listed tool is a row, a write unless the server says it reads, offered under <server>__<tool>", () => {
140 const server = {
141 id: "mcp_1",
142 name: "weather",
143 url: "https://mcp.example.com/",
144 tools: [
145 { name: "get_forecast", description: "Today's forecast", kind: "read" as const, input_schema: { type: "object", properties: {} } },
146 { name: "set_alert", description: "", kind: "write" as const, input_schema: { type: "object", properties: {} } },
147 ],
148 added_by: "ana",
149 added_at: "2026-10-10T00:00:00.000Z",
150 checked_at: null,
151 problem: null,
152 };
153 const sections = resolve({ abilities: { settings: {}, mcp_servers: [server] } });
154 const mcp = sections.find((s) => s.group === "mcp")!;
155 assert.equal(mcp.sources.length, 1);
156 const [read, write] = mcp.sources[0]!.abilities;
157 assert.equal(read!.level, "alone");
158 assert.equal(write!.level, "ask", "a write outside g1t asks first");
159 assert.equal(write!.kind, "send");
160 assert.deepEqual(read!.tools, ["weather__get_forecast"]);
161 assert.equal(mcpToolName("My Server", "do.thing"), "my_server__do_thing");
162 assert.equal(mcpAbility(server, server.tools[1]!).id, "mcp:mcp_1:set_alert");
163});
164
165test("an MCP server's address is HTTPS on a public host, never local, an address or g1t's own", () => {
166 assert.equal(checkMcpUrl("https://mcp.example.com/sse").ok, true);
167 assert.equal(checkMcpUrl("http://mcp.example.com/").ok, false);
168 assert.equal(checkMcpUrl("https://localhost:3000/").ok, false);
169 assert.equal(checkMcpUrl("https://10.0.0.5/").ok, false);
170 assert.equal(checkMcpUrl("https://[::1]/").ok, false);
171 assert.equal(checkMcpUrl("https://mcp.internal/").ok, false);
172 assert.equal(checkMcpUrl("https://api.g1t.sh/mcp").ok, false);
173 assert.equal(checkMcpUrl("https://user:pw@mcp.example.com/").ok, false);
174 assert.equal(checkMcpUrl("not a url").ok, false);
175 assert.equal(checkMcpName("Weather").ok, true);
176 assert.equal(checkMcpName("a").ok, false);
177 assert.equal(checkMcpName("bad name").ok, false);
178});
179
180test("alone when asked for it: the item's key or the ability's name in what the person said", () => {
181 assert.ok(askedFor("Can you comment on ENG-42 with the test plan?", ["ENG-42", "comment"]));
182 assert.ok(askedFor("what's eng-42 about", ["ENG-42"]), "any case");
183 assert.ok(!askedFor("Summarise the thread", ["ENG-42", "import"]));
184 assert.ok(!askedFor("", ["ENG-42"]));
185});
186
187test("the summary says what it does alone, when asked, after asking, and never", () => {
188 const abilities = withSetting(withSetting(null, "integration:linear:comment", { level: "never" }), "integration:sentry:resolve", { level: "alone" });
189 const sections = resolve({ connected: ["linear", "sentry"], abilities });
190 const summary = abilitiesSummary(sections);
191 assert.equal(
192 summary,
193 "Can open pull requests, change files on its computer, read issues in Linear, read issues in Sentry and resolve issues in Sentry on its own; runs commands on its computer, imports issues in Linear and imports issues in Sentry when asked for it; asks before merging, deploying to production, editing docs and commenting in Sentry; never comments in Linear.",
194 );
195 assert.equal(allAbilities(sections).filter((a) => a.group === "integration" && a.can_change).length, 7);
196 assert.match(abilitiesSummary(resolve()), /open pull requests and change files on its computer on its own/);
197});