Skip to content
269 linesCodeBlameRaw
1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import type { FolioRef, User } from "@g1t/contracts";
5
6import { AGENT_TOOL_NAMES, RESERVED_SKILL_NAMES, SKILLS_PER_AGENT_MAX } from "../../../packages/contracts/src/skill-format.ts";
7import { FOUNDATIONAL_SKILLS, FOUNDATIONAL_SKILL_IDS, skillTools, skillsOn } from "../../../packages/contracts/src/skills.ts";
8import { Audience, type AudienceInfo } from "./audience.ts";
9import { applyChanges } from "./definition.ts";
10import { systemPrompt } from "./prompt.ts";
11import { type AttachedRow, shelfFrom, skillsSection, teamSlugs } from "./skills.ts";
12import { TEMPLATE_IDS } from "./templates.ts";
13import { type FoliosPorts, type ToolPorts, TOOL_NAMES, ToolBox, docBody } from "./tools.ts";
14
15test("the foundational skills name only tools agents have, and say what's coming", () => {
16 assert.deepEqual(FOUNDATIONAL_SKILL_IDS, ["documents", "research", "data", "code", "communication", "files"]);
17 for (const skill of FOUNDATIONAL_SKILLS) {
18 assert.equal(skill.source, "foundational");
19 assert.ok(skill.instructions.length > 100, `${skill.id} has a playbook`);
20 for (const ability of skill.abilities) {
21 for (const tool of ability.tools) assert.ok(TOOL_NAMES.has(tool), `${skill.id}/${ability.id} names ${tool}, which agents have`);
22 if (ability.status === "coming") assert.deepEqual(ability.tools, [], `${skill.id}/${ability.id} is coming, so it uses no tool yet`);
23 assert.ok(ability.note, `${skill.id}/${ability.id} says how or what's missing`);
24 }
25 // Every tool a playbook tells the agent to call is one it can have.
26 for (const [, tool] of skill.instructions.matchAll(/\b([a-z]+_[a-z_]+)\b/g)) assert.ok(TOOL_NAMES.has(tool!), `${skill.id}'s playbook names ${tool}`);
27 }
28 assert.ok(skillTools(FOUNDATIONAL_SKILLS[0]!).includes("make_file"));
29 const coming = FOUNDATIONAL_SKILLS.flatMap((s) => s.abilities.filter((a) => a.status === "coming").map((a) => a.id));
30 for (const id of ["slides", "search", "browse", "sql", "run", "schedule", "ocr", "images"]) assert.ok(coming.includes(id), `${id} is marked coming`);
31});
32
33test("the tools a skill may name are exactly the tools agents have", () => {
34 assert.deepEqual([...AGENT_TOOL_NAMES].sort(), [...TOOL_NAMES].sort());
35 assert.deepEqual(RESERVED_SKILL_NAMES, FOUNDATIONAL_SKILL_IDS);
36});
37
38const library = (over: Partial<AttachedRow> = {}): AttachedRow => ({
39 skill_id: "skl_01k7a0b1c2d3e4f5g6h7j8k9mn",
40 name: "release-notes",
41 description: "Use when someone asks for release notes.",
42 version: 3,
43 tools: '["recent_activity","create_artifact","teleport_tool"]',
44 requires_computer: 0,
45 scope: "workspace",
46 attached_at: "2026-10-01T00:00:00Z",
47 ...over,
48});
49
50test("the prompt names each skill and when to use it; the playbooks stay out until read", () => {
51 const all = [...TOOL_NAMES];
52 const { skills } = shelfFrom([], [library()]);
53 const section = skillsSection(skills, all)!;
54 for (const skill of FOUNDATIONAL_SKILLS) assert.match(section, new RegExp(`^- ${skill.id}: ${skill.when.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`, "m"));
55 assert.match(section, /^- release-notes: Use when someone asks for release notes\.$/m);
56 assert.match(section, /call use_skill with its name before you start/);
57 assert.match(section, /never add one/);
58 for (const skill of FOUNDATIONAL_SKILLS) assert.ok(!section.includes(skill.instructions), `${skill.id}'s playbook isn't in the prompt`);
59 // Without use_skill (no tools at all), nothing is listed: no skill could be followed.
60 assert.equal(skillsSection(skills, all.filter((t) => t !== "use_skill")), null);
61 // Off: gone from the list, the rest stays; all off and none attached, no section.
62 const someOff = skillsSection(shelfFrom(["communication", "files", "skl_01k7a0b1c2d3e4f5g6h7j8k9mn"], [library()]).skills, all)!;
63 assert.doesNotMatch(someOff, /^- communication:/m);
64 assert.doesNotMatch(someOff, /^- files:/m);
65 assert.doesNotMatch(someOff, /release-notes/);
66 assert.match(someOff, /^- documents:/m);
67 assert.equal(skillsSection(shelfFrom(FOUNDATIONAL_SKILL_IDS, []).skills, all), null);
68 assert.equal(skillsOn(["data"]).length, 5);
69 // A skill that needs a computer is marked: as usable when the session has one (run_command offered), as out of reach otherwise.
70 assert.match(skillsSection(shelfFrom([], [library({ requires_computer: 1 })]).skills, all)!, /release-notes: .* Uses your computer\./);
71 assert.match(skillsSection(shelfFrom([], [library({ requires_computer: 1 })]).skills, all.filter((t) => t !== "run_command"))!, /release-notes: .* Needs your computer, which you don't have in this turn\./);
72 const prompt = systemPrompt({
73 agent: { id: "agt_1", handle: "ship", display_name: "Ship", role: "Release manager", instructions: "Ship.", personality_preset: "crisp", personality: "" },
74 workspace: "acme",
75 channel: { kind: "dm", name: null },
76 asker: { name: "dana", display_name: null, access: null },
77 today: new Date("2026-10-10T00:00:00Z"),
78 skills: section,
79 });
80 assert.ok(prompt.indexOf("## Your skills") > prompt.indexOf("## How to answer"), "skills come after the rules");
81});
82
83test("owners turn skills off by id; unknown ids are refused, and the list reads in the skills' order", () => {
84 const made = applyChanges(null, { handle: "ship", display_name: "Ship", role: "r", instructions: "i" }, TEMPLATE_IDS);
85 assert.ok(made.ok);
86 if (!made.ok) return;
87 assert.deepEqual(made.value.skills_off, []);
88 const off = applyChanges(made.value, { skills_off: ["files", " data ", "files"] }, TEMPLATE_IDS);
89 assert.ok(off.ok);
90 assert.deepEqual(off.ok && off.value.skills_off, ["data", "files"]);
91 // Library skills by their ids, after the foundational ones.
92 const lib = applyChanges(made.value, { skills_off: ["skl_01k7a0b1c2d3e4f5g6h7j8k9mn", "files"] }, TEMPLATE_IDS);
93 assert.deepEqual(lib.ok && lib.value.skills_off, ["files", "skl_01k7a0b1c2d3e4f5g6h7j8k9mn"]);
94 const bad = applyChanges(made.value, { skills_off: ["teleport"] }, TEMPLATE_IDS);
95 assert.equal(bad.ok, false);
96 assert.match(!bad.ok ? bad.message : "", /no skill called teleport/);
97 assert.equal(applyChanges(made.value, { skills_off: ["skl_short"] }, TEMPLATE_IDS).ok, false);
98 assert.equal(applyChanges(made.value, { skills_off: "data" as unknown as string[] }, TEMPLATE_IDS).ok, false);
99});
100
101test("a library skill reaches an agent once, at the version where it is attached closest, and at most 100 do", () => {
102 const id = (n: number) => `skl_${String(n).padStart(26, "0")}`;
103 const { skills } = shelfFrom(
104 [],
105 [
106 library({ scope: "workspace", version: 1 }),
107 library({ scope: "agent", version: 3, attached_at: "2026-10-05T00:00:00Z" }),
108 library({ scope: "team", version: 2 }),
109 ],
110 );
111 const lib = skills.filter((s) => s.kind === "library");
112 assert.equal(lib.length, 1);
113 assert.equal(lib[0]!.kind === "library" && lib[0]!.version, 3, "attached to the agent itself wins");
114 assert.equal(lib[0]!.kind === "library" && lib[0]!.via, "agent");
115 const many = Array.from({ length: SKILLS_PER_AGENT_MAX + 5 }, (_, n) => library({ skill_id: id(n), name: `s-${String(n).padStart(3, "0")}`, attached_at: `2026-10-01T00:00:${String(n % 60).padStart(2, "0")}Z` }));
116 const capped = shelfFrom([], many);
117 assert.equal(capped.skills.filter((s) => s.kind === "library").length, SKILLS_PER_AGENT_MAX);
118 assert.equal(capped.over, 5);
119 assert.deepEqual(teamSlugs({ teams: [{ slug: "qa" }, { slug: "web" }, { slug: "qa" }] as never, agents: [], presence: [] }), ["qa", "web"]);
120 assert.deepEqual(teamSlugs(null), [], "none when its teams couldn't be read");
121});
122
123test("use_skill reads a skill the agent has: the playbook with what isn't here, files, and scripts never run", async () => {
124 const { folios } = fakeFolios();
125 const tools = await box({ kind: "dm", member_user_ids: ["asker"], member_count: 1 }, folios);
126 assert.ok(!tools.definitions().some((t) => t.name === "use_skill"), "no skills, no use_skill");
127 const stored = {
128 skill_md: "---\nname: release-notes\ndescription: Use when someone asks for release notes.\ntools: [recent_activity, create_artifact]\n---\n\n# Release notes\n\nGroup changes by area.\n",
129 files: [
130 { path: "resources/template.md", content: "## Added\n\n## Fixed\n", encoding: "utf8" as const },
131 { path: "scripts/collect.py", content: "print('hi')\n", encoding: "utf8" as const },
132 { path: "resources/logo.png", content: "iVBORw0KGgo=", encoding: "base64" as const },
133 ],
134 };
135 const reads: string[] = [];
136 const shelf = shelfFrom(["files"], [library({ tools: '["recent_activity","create_artifact"]', requires_computer: 1 })]).skills;
137 tools.useShelf(shelf, async (skillId, version) => {
138 reads.push(`${skillId}@${version}`);
139 return stored;
140 });
141 assert.ok(tools.definitions().some((t) => t.name === "use_skill"));
142 const documents = await tools.run("use_skill", { name: "documents" });
143 assert.equal(documents.outcome, "allowed");
144 assert.match(documents.text, /When someone asks for a document, give them the document/);
145 assert.match(documents.text, /Not yet in g1t: slide decks\./);
146 assert.doesNotMatch(documents.text, /Not available in this conversation/, "every tool it uses is offered here");
147 const off = await tools.run("use_skill", { name: "files" });
148 assert.equal(off.outcome, "refused", "a skill that is off isn't the agent's");
149 const notes = await tools.run("use_skill", { name: "release-notes" });
150 assert.equal(notes.outcome, "allowed");
151 assert.deepEqual(reads, ["skl_01k7a0b1c2d3e4f5g6h7j8k9mn@3"], "the pinned version is read");
152 assert.match(notes.text, /^# release-notes \(your workspace's skill, version 3\)\n\n# Release notes\n\nGroup changes by area\./);
153 assert.doesNotMatch(notes.text, /^name:/m, "front-matter isn't repeated");
154 assert.match(notes.text, /needs a computer of its own, which you don't have in this turn: its scripts can't run/, "a reply has no computer");
155 assert.match(notes.text, /Its files, which you can read with use_skill and file: resources\/template\.md \(19 B\), scripts\/collect\.py \(12 B\), resources\/logo\.png \(8 B\)\./);
156 const template = await tools.run("use_skill", { name: "release-notes", file: "resources/template.md" });
157 assert.match(template.text, /## Added/);
158 const script = await tools.run("use_skill", { name: "release-notes", file: "scripts/collect.py" });
159 assert.match(script.text, /This is a script: it needs a computer of its own/);
160 const image = await tools.run("use_skill", { name: "release-notes", file: "resources/logo.png" });
161 assert.match(image.text, /isn't text/);
162 const missing = await tools.run("use_skill", { name: "teleport" });
163 assert.equal(missing.outcome, "refused");
164 assert.match(missing.text, /no skill called teleport\. Your skills: documents, research, data, code, communication, release-notes\./);
165});
166
167// ── make_file, end to end through the tool box ──────────────────────────
168
169const person = (id: string): User => ({ id, username: id, workspaces: [{ slug: "acme", role: "member" }] }) as User;
170
171const basePorts: ToolPorts = {
172 readFile: async () => null,
173 searchCode: async () => [],
174 listIssues: async () => [],
175 getIssue: async () => null,
176 getPull: async () => null,
177 recentPulls: async () => [],
178 searchMessages: async () => [],
179 readThread: async () => null,
180 roster: async () => "",
181 consult: async () => ({ ok: false, message: "no" }),
182};
183
184function fakeFolios(opts: { audienceCanRead?: boolean; canEdit?: boolean } = {}) {
185 const log: { created: { title: string; markdown: string | null }[]; attached: { folio: string; name: string; type: string; bytes: number }[]; edits: string[]; links: string[] } = { created: [], attached: [], edits: [], links: [] };
186 const ref = (id: string, title: string): FolioRef => ({ id, kind: "doc", title, path: `/acme/-/artifacts/${id}`, icon: null }) as unknown as FolioRef;
187 const folios: FoliosPorts = {
188 spaces: async () => [],
189 recall: async () => [],
190 search: async () => "",
191 read: async (_v, _a, id) => ({
192 ok: true,
193 value: { folio: { ...ref(id, "Existing"), edited_at: "2026-10-10" }, space: null, content: "", can: { read: true, suggest: true, edit: opts.canEdit ?? true }, audience_can_read: opts.audienceCanRead ?? true } as never,
194 }),
195 stale: async () => "",
196 create: async (_v, input) => {
197 log.created.push({ title: input.title, markdown: input.markdown });
198 return { ok: true, value: ref("fol_new", input.title) };
199 },
200 edit: async (_v, _id, edit) => {
201 log.edits.push(edit.kind === "doc" ? edit.markdown : "");
202 return { ok: true, value: { mode: "applied", version_id: null, folio: ref("fol_new", "x"), summary: "" } };
203 },
204 share: async () => ({ ok: true, value: null }),
205 attach: async (_v, folio, file) => {
206 log.attached.push({ folio, name: file.name, type: file.content_type, bytes: file.bytes.length });
207 return { ok: true, value: { url: "https://g1tusercontent.com/docs-files/abc", name: file.name, bytes: file.bytes.length } };
208 },
209 sendLink: async (_a, link) => {
210 log.links.push(link.path);
211 return true;
212 },
213 };
214 return { folios, log };
215}
216
217const actions = { remember: async () => ({ ok: true, message: "" }), forget: async () => ({ ok: true, message: "" }), draftIssue: async () => ({ ok: true, message: "" }) };
218const context = { agentId: "agt_me", notConsult: [], hops: 0, maxHops: 4 };
219
220async function box(info: AudienceInfo, folios: FoliosPorts) {
221 const people = [person("asker"), person("bea")];
222 const audience = await Audience.build("acme", "asker", {
223 info: async () => info,
224 users: async (ids) => people.filter((u) => ids.includes(u.id)),
225 workspaceRepos: async () => [],
226 readable: async () => [],
227 });
228 return new ToolBox(audience, { ...basePorts, folios }, context, [], actions);
229}
230
231test("make_file makes the PDF, keeps it with a new doc holding its text, links it, and hands back the link", async () => {
232 const { folios, log } = fakeFolios();
233 const tools = await box({ kind: "dm", member_user_ids: ["asker"], member_count: 1 }, folios);
234 assert.ok(tools.definitions().some((t) => t.name === "make_file"));
235 const result = await tools.run("make_file", { format: "pdf", title: "Q3 report", content: "# Q3 report\n\nRevenue grew." });
236 assert.equal(result.outcome, "allowed");
237 assert.match(result.text, /Made Q3 report\.pdf \(1 page, \d+ KB\)/);
238 assert.match(result.text, /https:\/\/g1tusercontent\.com\/docs-files\/abc/);
239 assert.deepEqual(log.created, [{ title: "Q3 report", markdown: "Revenue grew." }], "the doc has its own title, so the heading isn't repeated");
240 assert.equal(log.attached[0]!.type, "application/pdf");
241 assert.equal(log.attached[0]!.folio, "fol_new");
242 assert.match(log.edits[0]!, /^\*\*File:\*\* \[Q3 report\.pdf\]\(https:\/\/g1tusercontent\.com\/docs-files\/abc\)/);
243});
244
245test("make_file attaches to a doc only where it may edit, and keeps a doc others here can't read out of the conversation", async () => {
246 const readOnly = fakeFolios({ canEdit: false });
247 const tools = await box({ kind: "dm", member_user_ids: ["asker"], member_count: 1 }, readOnly.folios);
248 const refused = await tools.run("make_file", { format: "csv", title: "Rows", sheets: [{ rows: [["a"], [1]] }], artifact: "fol_01k7a0b1c2d3e4f5g6h7j8k9mn" });
249 assert.equal(refused.outcome, "refused");
250 assert.match(refused.text, /can.t edit that doc/);
251 assert.equal(readOnly.log.attached.length, 0);
252
253 const hidden = fakeFolios({ audienceCanRead: false });
254 const group = await box({ kind: "private", member_user_ids: ["asker", "bea"], member_count: 2 }, hidden.folios);
255 const sent = await group.run("make_file", { format: "xlsx", title: "Salaries", sheets: [{ rows: [["name", "pay"], ["bea", 1]] }], artifact: "fol_01k7a0b1c2d3e4f5g6h7j8k9mn" });
256 assert.equal(hidden.log.attached.length, 1, `kept with the doc: ${sent.text}`);
257 assert.doesNotMatch(sent.text, /docs-files/, "the link isn't given here");
258 assert.deepEqual(hidden.log.links, ["/acme/-/artifacts/fol_01k7a0b1c2d3e4f5g6h7j8k9mn"], "the person who asked gets it directly");
259});
260
261test("a spreadsheet's doc shows its rows; a format it can't write is refused with what to do instead", async () => {
262 assert.match(docBody("xlsx", "Sales", { sheets: [{ name: "S", rows: [["a", "b"], [1, 2]] }] }), /^\| a \| b \|/);
263 assert.equal(docBody("pdf", "T", { content: "# T" }), "The file is attached below.");
264 const { folios } = fakeFolios();
265 const tools = await box({ kind: "dm", member_user_ids: ["asker"], member_count: 1 }, folios);
266 const deck = await tools.run("make_file", { format: "pptx", title: "Deck" });
267 assert.equal(deck.outcome, "refused");
268 assert.match(deck.text, /offer a PDF or a doc instead/);
269});