{"version":3,"file":"operator-roster.test.d.ts","sourceRoot":"","sources":["../../src/core/operator-roster.test.ts"],"names":[],"mappings":"","sourcesContent":["import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { afterEach, describe, expect, it } from \"vitest\";\nimport {\n\tisLocalOperatorModel,\n\tloadOperatorRoster,\n\tOPERATOR_LOCAL_MODEL_REFERENCE,\n\tOPERATOR_ROSTER_NAMES,\n} from \"./operator-roster.js\";\n\nconst roots: string[] = [];\n\nfunction agentDirWith(files: Record<string, string>): string {\n\tconst base = mkdtempSync(join(tmpdir(), \"jensen-operator-roster-\"));\n\troots.push(base);\n\tmkdirSync(join(base, \"agents\"), { recursive: true });\n\tfor (const [name, content] of Object.entries(files)) writeFileSync(join(base, \"agents\", name), content);\n\treturn base;\n}\n\nafterEach(() => {\n\tfor (const value of roots.splice(0)) rmSync(value, { recursive: true, force: true });\n});\n\ndescribe(\"operator roster loading\", () => {\n\tit(\"loads file agents from getAgentDir()/agents/*.md frontmatter\", () => {\n\t\tconst agentDir = agentDirWith({\n\t\t\t\"custom-op.md\": [\n\t\t\t\t\"---\",\n\t\t\t\t\"name: custom-op\",\n\t\t\t\t\"description: A custom operator.\",\n\t\t\t\t\"tools: read, grep\",\n\t\t\t\t\"model: qwen3.8-27b\",\n\t\t\t\t\"---\",\n\t\t\t\t\"Body content is ignored by the loader.\",\n\t\t\t].join(\"\\n\"),\n\t\t});\n\t\tconst roster = loadOperatorRoster({ agentDir });\n\t\tconst custom = roster.agents.find((agent) => agent.name === \"custom-op\");\n\t\texpect(custom).toMatchObject({\n\t\t\tdescription: \"A custom operator.\",\n\t\t\ttools: [\"read\", \"grep\"],\n\t\t\tmodel: OPERATOR_LOCAL_MODEL_REFERENCE,\n\t\t\tsource: \"file\",\n\t\t\tfilePath: \"custom-op.md\",\n\t\t});\n\t\texpect(roster.diagnostics).toEqual([]);\n\t});\n\n\tit(\"normalizes the full provider/model local reference and accepts array tools\", () => {\n\t\tconst agentDir = agentDirWith({\n\t\t\t\"full-ref.md\": [\n\t\t\t\t\"---\",\n\t\t\t\t\"name: full-ref\",\n\t\t\t\t\"tools:\",\n\t\t\t\t\"  - read\",\n\t\t\t\t\"  - ls\",\n\t\t\t\t`  - ${OPERATOR_LOCAL_MODEL_REFERENCE}`,\n\t\t\t\t\"---\",\n\t\t\t].join(\"\\n\"),\n\t\t\t\"array-tools.md\": [\"---\", \"name: array-tools\", \"tools: [read, find]\", \"---\"].join(\"\\n\"),\n\t\t});\n\t\tconst roster = loadOperatorRoster({ agentDir });\n\t\tconst fullRef = roster.agents.find((agent) => agent.name === \"full-ref\");\n\t\texpect(fullRef?.model).toBe(OPERATOR_LOCAL_MODEL_REFERENCE);\n\t\texpect(fullRef?.tools).toEqual([\"read\", \"ls\", OPERATOR_LOCAL_MODEL_REFERENCE]);\n\t\texpect(roster.agents.find((agent) => agent.name === \"array-tools\")?.tools).toEqual([\"read\", \"find\"]);\n\t});\n\n\tit(\"defaults to the local model when frontmatter omits it\", () => {\n\t\tconst agentDir = agentDirWith({\n\t\t\t\"no-model.md\": [\"---\", \"name: no-model\", \"description: No model declared.\", \"---\"].join(\"\\n\"),\n\t\t});\n\t\tconst roster = loadOperatorRoster({ agentDir });\n\t\texpect(roster.agents.find((agent) => agent.name === \"no-model\")?.model).toBe(OPERATOR_LOCAL_MODEL_REFERENCE);\n\t\texpect(roster.diagnostics).toEqual([]);\n\t});\n\n\tit(\"skips files without a frontmatter name\", () => {\n\t\tconst agentDir = agentDirWith({\n\t\t\t\"anonymous.md\": [\"---\", \"description: Missing name.\", \"---\", \"body\"].join(\"\\n\"),\n\t\t\t\"not-frontmatter.md\": [\"plain body without frontmatter\"].join(\"\\n\"),\n\t\t});\n\t\tconst roster = loadOperatorRoster({ agentDir });\n\t\texpect(roster.agents.filter((agent) => agent.source === \"file\")).toEqual([]);\n\t\texpect(roster.diagnostics.map((d) => d.code)).toEqual([\n\t\t\t\"OPERATOR_ROSTER_NAME_MISSING\",\n\t\t\t\"OPERATOR_ROSTER_NAME_MISSING\",\n\t\t]);\n\t});\n\n\tit(\"emits a per-file diagnostic for malformed frontmatter and keeps loading the rest\", () => {\n\t\tconst agentDir = agentDirWith({\n\t\t\t\"broken.md\": [\"---\", \"name: [unclosed\", \"description: malformed yaml\", \"---\"].join(\"\\n\"),\n\t\t\t\"good.md\": [\"---\", \"name: good-op\", \"description: Fine.\", \"---\"].join(\"\\n\"),\n\t\t});\n\t\tconst roster = loadOperatorRoster({ agentDir });\n\t\texpect(roster.agents.find((agent) => agent.name === \"good-op\")?.source).toBe(\"file\");\n\t\tconst broken = roster.diagnostics.find((diagnostic) => diagnostic.code === \"OPERATOR_ROSTER_FRONTMATTER_INVALID\");\n\t\texpect(broken).toMatchObject({ path: \"broken.md\" });\n\t\texpect(broken?.message).toContain(\"broken.md\");\n\t\texpect(roster.agents.find((agent) => agent.name === \"broken\")).toBeUndefined();\n\t\t// Canonical roles are still guaranteed next to the diagnostic.\n\t\tfor (const name of OPERATOR_ROSTER_NAMES) expect(roster.agents.some((agent) => agent.name === name)).toBe(true);\n\t});\n\n\tit(\"keeps the first of duplicate names\", () => {\n\t\tconst agentDir = agentDirWith({\n\t\t\t\"a.md\": [\"---\", \"name: dup\", \"description: first\", \"---\"].join(\"\\n\"),\n\t\t\t\"b.md\": [\"---\", \"name: dup\", \"description: second\", \"---\"].join(\"\\n\"),\n\t\t});\n\t\tconst roster = loadOperatorRoster({ agentDir });\n\t\tconst duplicates = roster.agents.filter((agent) => agent.name === \"dup\");\n\t\texpect(duplicates).toHaveLength(1);\n\t\texpect(duplicates[0].description).toBe(\"first\");\n\t\texpect(roster.diagnostics.map((d) => d.code)).toEqual([\"OPERATOR_ROSTER_NAME_DUPLICATE\"]);\n\t});\n\n\tit(\"rejects non-local models in strict mode\", () => {\n\t\tconst agentDir = agentDirWith({\n\t\t\t\"cloud.md\": [\"---\", \"name: cloud-op\", \"model: openai/gpt-5.6-luna\", \"---\"].join(\"\\n\"),\n\t\t});\n\t\tconst roster = loadOperatorRoster({ agentDir });\n\t\texpect(roster.agents.find((agent) => agent.name === \"cloud-op\")).toBeUndefined();\n\t\texpect(roster.diagnostics).toEqual([\n\t\t\t{\n\t\t\t\tcode: \"OPERATOR_ROSTER_MODEL_NON_LOCAL\",\n\t\t\t\tmessage: \"Operator cloud-op declares non-local model openai/gpt-5.6-luna\",\n\t\t\t\tpath: \"cloud.md\",\n\t\t\t},\n\t\t]);\n\t\t// Synthesized roles are still present and strictly local.\n\t\tfor (const name of OPERATOR_ROSTER_NAMES)\n\t\t\texpect(roster.agents.find((agent) => agent.name === name)?.model).toBe(OPERATOR_LOCAL_MODEL_REFERENCE);\n\t});\n\n\tit(\"keeps non-local models with a diagnostic in non-strict mode\", () => {\n\t\tconst agentDir = agentDirWith({\n\t\t\t\"cloud.md\": [\"---\", \"name: cloud-op\", \"model: openai/gpt-5.6-luna\", \"---\"].join(\"\\n\"),\n\t\t});\n\t\tconst roster = loadOperatorRoster({ agentDir, strict: false });\n\t\texpect(roster.agents.find((agent) => agent.name === \"cloud-op\")?.model).toBe(\"openai/gpt-5.6-luna\");\n\t\texpect(roster.diagnostics.map((d) => d.code)).toEqual([\"OPERATOR_ROSTER_MODEL_NON_LOCAL\"]);\n\t});\n\n\tit(\"synthesizes only the missing canonical operator roles\", () => {\n\t\tconst agentDir = agentDirWith({\n\t\t\t\"investigator.md\": [\"---\", \"name: investigator\", \"description: File-provided investigator.\", \"---\"].join(\"\\n\"),\n\t\t});\n\t\tconst roster = loadOperatorRoster({ agentDir });\n\t\texpect(roster.agents.find((agent) => agent.name === \"investigator\")?.source).toBe(\"file\");\n\t\tfor (const name of [\"tester\", \"synthesizer\"]) {\n\t\t\tconst agent = roster.agents.find((candidate) => candidate.name === name);\n\t\t\texpect(agent?.source).toBe(\"synthesized\");\n\t\t\texpect(agent?.model).toBe(OPERATOR_LOCAL_MODEL_REFERENCE);\n\t\t\texpect(agent?.tools).toEqual([\"read\", \"grep\", \"find\", \"ls\"]);\n\t\t\texpect(agent?.description.length).toBeGreaterThan(0);\n\t\t}\n\t});\n\n\tit(\"returns exactly the three synthesized roles when the agents directory is absent\", () => {\n\t\tconst base = mkdtempSync(join(tmpdir(), \"jensen-operator-roster-empty-\"));\n\t\troots.push(base);\n\t\tconst roster = loadOperatorRoster({ agentDir: base });\n\t\texpect(roster.agents.map((agent) => agent.name)).toEqual([...OPERATOR_ROSTER_NAMES].sort());\n\t\texpect(roster.agents.every((agent) => agent.model === OPERATOR_LOCAL_MODEL_REFERENCE)).toBe(true);\n\t\texpect(roster.diagnostics).toEqual([]);\n\t});\n\n\tit(\"orders the roster deterministically by name\", () => {\n\t\tconst agentDir = agentDirWith({\n\t\t\t\"zeta.md\": [\"---\", \"name: zeta\", \"---\"].join(\"\\n\"),\n\t\t\t\"alpha.md\": [\"---\", \"name: alpha\", \"---\"].join(\"\\n\"),\n\t\t\t\"mid.md\": [\"---\", \"name: mid\", \"---\"].join(\"\\n\"),\n\t\t});\n\t\tconst roster = loadOperatorRoster({ agentDir });\n\t\texpect(roster.agents.map((agent) => agent.name)).toEqual([\n\t\t\t\"alpha\",\n\t\t\t\"investigator\",\n\t\t\t\"mid\",\n\t\t\t\"synthesizer\",\n\t\t\t\"tester\",\n\t\t\t\"zeta\",\n\t\t]);\n\t});\n\n\tit(\"recognizes the local model as bare id or full reference\", () => {\n\t\texpect(isLocalOperatorModel(\"qwen3.8-27b\")).toBe(true);\n\t\texpect(isLocalOperatorModel(OPERATOR_LOCAL_MODEL_REFERENCE)).toBe(true);\n\t\texpect(isLocalOperatorModel(\"openai/gpt-5.6-luna\")).toBe(false);\n\t\texpect(isLocalOperatorModel(\"\")).toBe(false);\n\t});\n});\n"]}