| 1 | import assert from "node:assert/strict"; |
| 2 | import { test } from "node:test"; |
| 3 | |
| 4 | import { changeRows, integrationHint, lineDiff, readDefinition } from "./agent-builder.ts"; |
| 5 | |
| 6 | test("a draft comes back from the page checked for shape", () => { |
| 7 | const read = readDefinition( |
| 8 | JSON.stringify({ |
| 9 | handle: "juniper", |
| 10 | display_name: " Juniper ", |
| 11 | title: "Release Manager", |
| 12 | instructions: "Cut releases.", |
| 13 | responsibilities: ["One", 2, "Two"], |
| 14 | personality_preset: "loud", |
| 15 | routing: { floor: "huge", ceiling: "small", providers: ["x"], pinned: "a/b" }, |
| 16 | budget: { monthly_micros: 20_000_000, task_micros: -1 }, |
| 17 | skills_off: ["data"], |
| 18 | team: "ops", |
| 19 | scope: "personal", |
| 20 | }), |
| 21 | ); |
| 22 | assert.ok(read); |
| 23 | assert.equal(read.display_name, "Juniper"); |
| 24 | assert.deepEqual(read.responsibilities, ["One", "Two"]); |
| 25 | assert.equal(read.personality_preset, "crisp"); |
| 26 | assert.deepEqual(read.routing, { floor: null, ceiling: "small", providers: [], pinned: null }, "nothing but limits"); |
| 27 | assert.deepEqual(read.budget, { monthly_micros: 20_000_000, daily_micros: null, task_micros: null }); |
| 28 | assert.ok(!("team" in read) && !("department" in read), "a draft names no team: teams are added as memberships"); |
| 29 | assert.equal(read.scope, "personal"); |
| 30 | assert.equal(readDefinition("not json"), null); |
| 31 | assert.equal(readDefinition("[1]"), null); |
| 32 | }); |
| 33 | |
| 34 | test("an integration it needs: owners connect it, anyone else asks, and soon is soon", () => { |
| 35 | const owner = integrationHint("acme", { id: "linear", why: "Reads issues." }, true); |
| 36 | assert.equal(owner?.action, "connect"); |
| 37 | assert.equal(owner?.href, "/acme/-/integrations/trackers?add=linear#add"); |
| 38 | const member = integrationHint("acme", { id: "linear", why: "Reads issues." }, false); |
| 39 | assert.deepEqual(member && { action: member.action, href: member.href }, { action: "ask", href: "/acme/-/marketplace/integrations/linear" }); |
| 40 | assert.equal(integrationHint("acme", { id: "box", why: "" }, true)?.action, "soon"); |
| 41 | assert.equal(integrationHint("acme", { id: "nope", why: "" }, true), null); |
| 42 | }); |
| 43 | |
| 44 | test("a job's change reads as lines kept, removed and added", () => { |
| 45 | assert.deepEqual(lineDiff("a\nb\nc", "a\nB\nc\nd"), [ |
| 46 | { kind: "same", text: "a" }, |
| 47 | { kind: "removed", text: "b" }, |
| 48 | { kind: "added", text: "B" }, |
| 49 | { kind: "same", text: "c" }, |
| 50 | { kind: "added", text: "d" }, |
| 51 | ]); |
| 52 | assert.deepEqual(lineDiff("same", "same"), [{ kind: "same", text: "same" }]); |
| 53 | }); |
| 54 | |
| 55 | test("a drafted change lists each field it touches, before and after", () => { |
| 56 | const agent = { |
| 57 | handle: "juniper", |
| 58 | display_name: "Juniper", |
| 59 | title: "Release Manager", |
| 60 | instructions: "Cut releases.", |
| 61 | responsibilities: ["One", "Two"], |
| 62 | personality_preset: "crisp" as const, |
| 63 | personality: "", |
| 64 | skills_off: [], |
| 65 | routing: { floor: null, ceiling: null, providers: [], pinned: null }, |
| 66 | budget: { monthly_micros: 20_000_000, daily_micros: null, task_micros: 2_000_000 }, |
| 67 | }; |
| 68 | const rows = changeRows(agent as never, { |
| 69 | display_name: "Wren", |
| 70 | handle: "wren", |
| 71 | instructions: "Cut releases.\nAnswer in Spanish.", |
| 72 | skills_off: ["data"], |
| 73 | routing: { ceiling: "small" }, |
| 74 | budget: { monthly_micros: 50_000_000 }, |
| 75 | }); |
| 76 | assert.deepEqual( |
| 77 | rows.map((r) => [r.label, r.before, r.after]), |
| 78 | [ |
| 79 | ["Name", "Juniper", "Wren"], |
| 80 | ["Handle", "@juniper", "@wren"], |
| 81 | ["Job", "Cut releases.", "Cut releases.\nAnswer in Spanish."], |
| 82 | ["Skills", "All on", "Turned off: data"], |
| 83 | ["Model ceiling", "None", "Fast"], |
| 84 | ["Monthly budget", "$20", "$50"], |
| 85 | ], |
| 86 | ); |
| 87 | assert.equal(rows[2]!.lines?.filter((l) => l.kind === "added").length, 1); |
| 88 | }); |