/** * Unit Tests — AgentManager.getAgentConfig() P1 虚拟引用寻址 * * 覆盖 docs/todo/system-design/27-p1-virtual-reference.md §4.2 / §6-1,2,4: * - config.json 带 systemAgentRef 且 system_agents 表命中 template 行 → prompt 指向共享 * agent.md,companyContextPath 指向私有 company-context.md * - config.json 没有 systemAgentRef(legacy agent) → prompt 仍指向私有 agent.md,行为不变 * * Uses GRAPH_DB_PATH env override (same pattern as graph-service.test.js) so the real * data/graph.sqlite is never touched. Also creates a temporary company directory under * data/companies/ that is removed in afterAll. */ import { describe, it, expect, beforeAll, afterAll } from 'vitest'; import fs from 'fs'; import path from 'path'; describe('AgentManager.getAgentConfig() — P1 virtual reference addressing', () => { const TEST_DB_PATH = '/tmp/test-agent-manager-p1-graph.sqlite'; const REPO_ROOT = path.resolve(__dirname, '../../..'); const TMP_COMPANY_ID = `tmp-p1-test-co-${Date.now()}`; const AGENTS_ROOT = path.join(REPO_ROOT, 'data/companies', TMP_COMPANY_ID, 'agents'); let graphDb; let AgentManager; beforeAll(() => { if (fs.existsSync(TEST_DB_PATH)) fs.unlinkSync(TEST_DB_PATH); process.env.GRAPH_DB_PATH = TEST_DB_PATH; delete require.cache[require.resolve('../../services/graph-db')]; delete require.cache[require.resolve('../../services/graph-service')]; delete require.cache[require.resolve('../../src/core/agent-manager')]; graphDb = require('../../services/graph-db'); AgentManager = require('../../src/core/agent-manager'); // Seed a template row for "qa-judge-p1-test" pointing at a fixture-free legacy_path // (the shared agent.md just needs to exist so lookupSharedAgentMd's result is a real file). const db = graphDb.getDb(); const now = new Date().toISOString(); db.prepare(` INSERT INTO system_agents (id, name, scope, owner_company_id, source_kind, legacy_path, status, benchmark_score, created_at, updated_at) VALUES (?, ?, 'public', NULL, 'template', ?, 'active', NULL, ?, ?) `).run('qa-judge', 'qa-judge', 'system/templates/qa-judge', now, now); fs.mkdirSync(AGENTS_ROOT, { recursive: true }); }); afterAll(() => { try { fs.rmSync(path.join(REPO_ROOT, 'data/companies', TMP_COMPANY_ID), { recursive: true, force: true }); } catch {} try { fs.unlinkSync(TEST_DB_PATH); } catch {} try { fs.unlinkSync(`${TEST_DB_PATH}-shm`); } catch {} try { fs.unlinkSync(`${TEST_DB_PATH}-wal`); } catch {} delete process.env.GRAPH_DB_PATH; }); function makeAgentDir(role, configJson) { const dir = path.join(AGENTS_ROOT, role); fs.mkdirSync(dir, { recursive: true }); if (configJson !== undefined) { fs.writeFileSync(path.join(dir, 'config.json'), JSON.stringify(configJson), 'utf-8'); } // Legacy agent.md always present in the private dir (P1 doesn't delete it if it exists // for the reference-mode case — company-service.hire() simply never writes it there). fs.writeFileSync(path.join(dir, 'agent.md'), '# legacy private agent.md\n', 'utf-8'); return dir; } it('reference mode: systemAgentRef present + template row found -> prompt points at shared agent.md, companyContextPath set', () => { const role = 'qa-judge-referenced'; const agentDir = makeAgentDir(role, { systemAgentRef: 'qa-judge' }); const manager = new AgentManager({}, REPO_ROOT); const { agent } = manager.getAgentConfig(`${TMP_COMPANY_ID}:${role}`); const expectedSharedPath = path.resolve(REPO_ROOT, 'system/templates/qa-judge', 'agent.md'); expect(agent.prompt).toBe(expectedSharedPath); expect(agent.companyContextPath).toBe(path.join(agentDir, 'company-context.md')); // Hard constraint: workDir must remain the private directory (CLI cwd for soul.md/identity.md/.claude/skills/). expect(agent.workDir).toBe(agentDir); }); it('legacy mode: no systemAgentRef -> prompt points at private agent.md, companyContextPath falsy', () => { const role = 'legacy-role'; const agentDir = makeAgentDir(role, { someOtherField: true }); const manager = new AgentManager({}, REPO_ROOT); const { agent } = manager.getAgentConfig(`${TMP_COMPANY_ID}:${role}`); expect(agent.prompt).toBe(path.join(agentDir, 'agent.md')); expect(agent.companyContextPath).toBeFalsy(); expect(agent.workDir).toBe(agentDir); }); it('legacy mode: no config.json at all -> prompt points at private agent.md, companyContextPath falsy', () => { const role = 'no-config-role'; const agentDir = makeAgentDir(role, undefined); const manager = new AgentManager({}, REPO_ROOT); const { agent } = manager.getAgentConfig(`${TMP_COMPANY_ID}:${role}`); expect(agent.prompt).toBe(path.join(agentDir, 'agent.md')); expect(agent.companyContextPath).toBeFalsy(); expect(agent.workDir).toBe(agentDir); }); it('defensive fallback: systemAgentRef present but no matching template row -> falls back to private agent.md', () => { const role = 'dangling-ref-role'; const agentDir = makeAgentDir(role, { systemAgentRef: 'does-not-exist-in-system-agents' }); const manager = new AgentManager({}, REPO_ROOT); const { agent } = manager.getAgentConfig(`${TMP_COMPANY_ID}:${role}`); expect(agent.prompt).toBe(path.join(agentDir, 'agent.md')); expect(agent.companyContextPath).toBeNull(); expect(agent.workDir).toBe(agentDir); }); });