import test from "node:test"; import assert from "node:assert/strict"; import { parseAgentManifest } from "../src/agents/manifest.js"; function validManifest(): unknown { return { schemaVersion: "1", topologies: ["hierarchical", "retry-unrolled", "sequential"], personas: [ { id: "product", displayName: "Product", systemPromptTemplate: "Product for {{repo}}", toolClearance: { allowlist: ["read_file"], banlist: ["delete_file"], }, }, { id: "coder", displayName: "Coder", systemPromptTemplate: "Coder for {{repo}}", toolClearance: { allowlist: ["read_file", "write_file"], banlist: [], }, }, ], relationships: [ { parentPersonaId: "product", childPersonaId: "coder", constraints: { maxDepth: 2, maxChildren: 3, }, }, ], topologyConstraints: { maxDepth: 5, maxRetries: 2, }, pipeline: { entryNodeId: "product-node", nodes: [ { id: "product-node", actorId: "product_actor", personaId: "product", }, { id: "coder-node", actorId: "coder_actor", personaId: "coder", constraints: { maxRetries: 1, }, }, ], edges: [ { from: "product-node", to: "coder-node", on: "success", when: [{ type: "always" }], }, ], }, }; } test("parses a valid AgentManifest", () => { const manifest = parseAgentManifest(validManifest()); assert.equal(manifest.schemaVersion, "1"); assert.equal(manifest.pipeline.nodes.length, 2); assert.equal(manifest.relationships.length, 1); }); test("parses optional persona modelConstraint", () => { const manifest = validManifest() as { personas: Array>; }; manifest.personas[1] = { ...manifest.personas[1], modelConstraint: "claude-3-haiku", }; const parsed = parseAgentManifest(manifest); const coder = parsed.personas.find((persona) => persona.id === "coder"); assert.ok(coder); assert.equal(coder.modelConstraint, "claude-3-haiku"); }); test("rejects pipeline cycles", () => { const manifest = validManifest() as { pipeline: { edges: Array<{ from: string; to: string; on: string }>; }; }; manifest.pipeline.edges.push({ from: "coder-node", to: "product-node", on: "success", }); assert.throws(() => parseAgentManifest(manifest), /strict DAG/); }); test("rejects relationship with unknown persona", () => { const manifest = validManifest() as { relationships: Array<{ parentPersonaId: string; childPersonaId: string }>; }; manifest.relationships.push({ parentPersonaId: "product", childPersonaId: "unknown", }); assert.throws(() => parseAgentManifest(manifest), /unknown child persona/); }); test("rejects relationship cycles", () => { const manifest = validManifest() as { relationships: Array<{ parentPersonaId: string; childPersonaId: string }>; }; manifest.relationships.push({ parentPersonaId: "coder", childPersonaId: "product", }); assert.throws(() => parseAgentManifest(manifest), /Relationship graph must be acyclic/); }); test("rejects legacy edge trigger aliases", () => { const manifest = validManifest() as { pipeline: { edges: Array<{ from: string; to: string; on: string }>; }; }; manifest.pipeline.edges[0] = { from: "product-node", to: "coder-node", on: "onValidationFail", }; assert.throws( () => parseAgentManifest(manifest), /Invalid option/, ); }); test("rejects empty persona modelConstraint", () => { const manifest = validManifest() as { personas: Array>; }; manifest.personas[0] = { ...manifest.personas[0], modelConstraint: " ", }; assert.throws( () => parseAgentManifest(manifest), /modelConstraint/, ); });