Skip to content
188 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 assert.ok(computer.sources[0]!.abilities.every((a) => a.status === "coming" && a.choices.length === 0 && !a.can_change));
81 assert.equal(COMPUTER_ABILITIES.length, 4);
82});
83
84test("a connected integration lists its rows at their defaults; one that isn't is listed, but nothing there can be changed", () => {
85 const sections = resolve({ connected: ["linear"] });
86 const integrations = sections.find((s) => s.group === "integration")!;
87 const linear = integrations.sources.find((s) => s.id === "linear")!;
88 assert.equal(linear.connected, true);
89 assert.deepEqual(
90 linear.abilities.map((a) => [a.id, a.level, a.credentials, a.can_change]),
91 [
92 ["integration:linear:read", "alone", "workspace", true],
93 ["integration:linear:import", "asked", "workspace", true],
94 ["integration:linear:comment", "ask", "workspace", true],
95 ],
96 );
97 assert.equal(linear.abilities[0]!.personal_available, false, "Linear's personal side is still coming");
98 const jira = integrations.sources.find((s) => s.id === "jira")!;
99 assert.equal(jira.connected, false);
100 assert.ok(jira.abilities.every((a) => !a.can_change));
101 assert.equal(integrations.sources[0]!.id, "linear", "connected first");
102 assert.ok(!integrations.sources.some((s) => s.id === "webhooks" || s.id === "anthropic"), "nothing to call, not connected: not listed");
103 const withDatadog = resolve({ connected: ["datadog"] }).find((s) => s.group === "integration")!;
104 const datadog = withDatadog.sources.find((s) => s.id === "datadog")!;
105 assert.equal(datadog.abilities.length, 0);
106 assert.match(datadog.note ?? "", /Opens issues by itself/);
107});
108
109test("a setting moves a level within its kind's limit, and is dropped when it is the default again", () => {
110 let abilities = withSetting(null, "integration:linear:comment", { level: "alone" });
111 assert.deepEqual(abilities.settings, { "integration:linear:comment": { level: "alone" } });
112 const sections = resolve({ connected: ["linear"], abilities });
113 assert.equal(findAbility(sections, "integration:linear:comment")!.ability.level, "alone");
114 abilities = withSetting(abilities, "integration:linear:comment", { credentials: "asker" });
115 assert.deepEqual(abilities.settings["integration:linear:comment"], { level: "alone", credentials: "asker" });
116 abilities = withSetting(abilities, "integration:linear:comment", { level: null, credentials: null });
117 assert.deepEqual(abilities.settings, {}, "nothing kept once everything is the default");
118 // A restricted ability set freer than Ask is held at Ask.
119 const held = resolveAbilities({ connectors: CONNECTORS, abilities: null, autonomy: { ...AUTONOMY, deploy_production: "approval" }, connected: [] });
120 assert.equal(findAbility(held, "g1t:deploy")!.ability.max, "ask");
121});
122
123test("a personal agent runs on the asker's connections unless an owner chose the workspace's", () => {
124 const sections = resolve({ connected: ["linear"], personal: true });
125 assert.equal(findAbility(sections, "integration:linear:read")!.ability.credentials, "asker");
126 const chosen = resolve({ connected: ["linear"], personal: true, abilities: withSetting(null, "integration:linear:read", { credentials: "workspace" }) });
127 assert.equal(findAbility(chosen, "integration:linear:read")!.ability.credentials, "workspace");
128});
129
130test("MCP servers: each listed tool is a row, a write unless the server says it reads, offered under <server>__<tool>", () => {
131 const server = {
132 id: "mcp_1",
133 name: "weather",
134 url: "https://mcp.example.com/",
135 tools: [
136 { name: "get_forecast", description: "Today's forecast", kind: "read" as const, input_schema: { type: "object", properties: {} } },
137 { name: "set_alert", description: "", kind: "write" as const, input_schema: { type: "object", properties: {} } },
138 ],
139 added_by: "ana",
140 added_at: "2026-10-10T00:00:00.000Z",
141 checked_at: null,
142 problem: null,
143 };
144 const sections = resolve({ abilities: { settings: {}, mcp_servers: [server] } });
145 const mcp = sections.find((s) => s.group === "mcp")!;
146 assert.equal(mcp.sources.length, 1);
147 const [read, write] = mcp.sources[0]!.abilities;
148 assert.equal(read!.level, "alone");
149 assert.equal(write!.level, "ask", "a write outside g1t asks first");
150 assert.equal(write!.kind, "send");
151 assert.deepEqual(read!.tools, ["weather__get_forecast"]);
152 assert.equal(mcpToolName("My Server", "do.thing"), "my_server__do_thing");
153 assert.equal(mcpAbility(server, server.tools[1]!).id, "mcp:mcp_1:set_alert");
154});
155
156test("an MCP server's address is HTTPS on a public host, never local, an address or g1t's own", () => {
157 assert.equal(checkMcpUrl("https://mcp.example.com/sse").ok, true);
158 assert.equal(checkMcpUrl("http://mcp.example.com/").ok, false);
159 assert.equal(checkMcpUrl("https://localhost:3000/").ok, false);
160 assert.equal(checkMcpUrl("https://10.0.0.5/").ok, false);
161 assert.equal(checkMcpUrl("https://[::1]/").ok, false);
162 assert.equal(checkMcpUrl("https://mcp.internal/").ok, false);
163 assert.equal(checkMcpUrl("https://api.g1t.sh/mcp").ok, false);
164 assert.equal(checkMcpUrl("https://user:pw@mcp.example.com/").ok, false);
165 assert.equal(checkMcpUrl("not a url").ok, false);
166 assert.equal(checkMcpName("Weather").ok, true);
167 assert.equal(checkMcpName("a").ok, false);
168 assert.equal(checkMcpName("bad name").ok, false);
169});
170
171test("alone when asked for it: the item's key or the ability's name in what the person said", () => {
172 assert.ok(askedFor("Can you comment on ENG-42 with the test plan?", ["ENG-42", "comment"]));
173 assert.ok(askedFor("what's eng-42 about", ["ENG-42"]), "any case");
174 assert.ok(!askedFor("Summarise the thread", ["ENG-42", "import"]));
175 assert.ok(!askedFor("", ["ENG-42"]));
176});
177
178test("the summary says what it does alone, when asked, after asking, and never", () => {
179 const abilities = withSetting(withSetting(null, "integration:linear:comment", { level: "never" }), "integration:sentry:resolve", { level: "alone" });
180 const sections = resolve({ connected: ["linear", "sentry"], abilities });
181 const summary = abilitiesSummary(sections);
182 assert.equal(
183 summary,
184 "Can open pull requests, read issues in Linear, read issues in Sentry and resolve issues in Sentry on its own; 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.",
185 );
186 assert.equal(allAbilities(sections).filter((a) => a.group === "integration" && a.can_change).length, 7);
187 assert.match(abilitiesSummary(resolve()), /open pull requests on its own/);
188});