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.
| Each agent has an Abilities tab: g1t's built-ins, always on within the asker's access; its computer, coming; each connected integration's actions one row each, read, import, comment or resolve, with a level for each, alone, alone when the person asked for it, ask first or never, and whose connection it runs on; and MCP servers an owner adds. Reading is alone, writing inside g1t follows today's choices, anything that leaves g1t asks first, and production deploys can't go above ask. The agents service enforces every level: ask first posts a card to allow or deny and parks a session until it's answered, a refusal names its rule in the transcript and the audit log, and a missing ability posts a request to owners. The agent abilities guide says how. | 1 | import assert from "node:assert/strict"; |
| 2 | import { test } from "node:test"; | |
| 3 | ||
| 4 | import { | |
| 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"; | |
| 26 | import { CONNECTORS } from "./connectors.ts"; | |
| 27 | ||
| 28 | const AUTONOMY = { open_pull_requests: "alone", merge: "approval", deploy_production: "approval", edit_docs: "suggest" } as const; | |
| 29 | const resolve = (over: Partial<Parameters<typeof resolveAbilities>[0]> = {}) => resolveAbilities({ connectors: CONNECTORS, abilities: null, autonomy: AUTONOMY, connected: [], ...over }); | |
| 30 | ||
| 31 | test("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 | ||
| 43 | test("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 | ||
| 62 | test("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 | ||
| 84 | test("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 | ||
| 109 | test("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 | ||
| 123 | test("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 | ||
| 130 | test("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 | ||
| 156 | test("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 | ||
| 171 | test("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 | ||
| 178 | test("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 | }); |