graph-service.js 3.67 KB
/**
 * Graph Service — P1 虚拟引用寻址支撑函数(新文件,P1 专属)
 *
 * 不修改 P0 已有的 graph-db.js / graph-seed-service.js(那是并行工作线),
 * 只通过 graph-db.js 导出的 getDb() 读写同一个 SQLite 连接。
 *
 * 详细需求与设计见 docs/todo/system-design/27-p1-virtual-reference.md。
 */

const path = require('path');
const { getDb } = require('./graph-db');

const ROOT = path.resolve(__dirname, '../..');

let indexEnsured = false;

/**
 * 查询某个 system_agent id 对应的共享 agent.md 绝对路径。
 *
 * 只匹配 source_kind = 'template'(不匹配 'global_agent')——这是本次设计修复的一个
 * 真实 bug:system/agents/ 和 system/templates/ 下有 6 个同名 id
 * (agent-creator/digester/evolver/evolver-youtube-crawler/hunter/innovator)
 * 内容可能不同步,hire() 只从 system/templates/ 拷贝过文件,如果查表命中了
 * global_agent 那一行会读错文件(详见 27-graph-agent-architecture.md §7、
 * 27-p1-virtual-reference.md §4.6)。
 *
 * @param {string} systemAgentId
 * @returns {string|null} 共享 agent.md 的绝对路径;查不到对应 template 行时返回 null
 */
function lookupSharedAgentMd(systemAgentId) {
  const db = getDb();
  const row = db
    .prepare(`SELECT legacy_path FROM system_agents WHERE id = ? AND source_kind = 'template'`)
    .get(systemAgentId);

  if (!row) return null;

  return path.resolve(ROOT, row.legacy_path, 'agent.md');
}

function ensureOrgGraphNodeIndex(db) {
  if (indexEnsured) return;
  db.exec(`
    CREATE UNIQUE INDEX IF NOT EXISTS idx_org_graph_nodes_company_role
      ON org_graph_nodes(company_id, role_name)
  `);
  indexEnsured = true;
}

/**
 * 为 (companyId, roleName) 建立/复用一条 org_graph_nodes 引用记录。
 *
 * hire() 每次成功后都会调用(不论走引用模式还是 legacy 全量拷贝模式),因此不能假设
 * system_agents 里已经有 systemAgentId 对应的行——没有时按 seedCompanyAgents()
 * (graph-seed-service.js 约 213-295 行)对同样场景的字段约定 fallback 插入一行
 * source_kind='company_hired' 的 system_agents 记录。
 *
 * 幂等:org_graph_nodes(company_id, role_name) 唯一索引 + INSERT ... ON CONFLICT DO NOTHING,
 * 重复调用不产生重复行。
 *
 * @param {string} companyId
 * @param {string} systemAgentId
 * @param {string} roleName
 * @returns {void}
 */
function upsertOrgGraphNode(companyId, systemAgentId, roleName) {
  const db = getDb();
  ensureOrgGraphNodeIndex(db);

  const now = new Date().toISOString();

  const tx = db.transaction(() => {
    const existingAgent = db.prepare(`SELECT id FROM system_agents WHERE id = ?`).get(systemAgentId);

    if (!existingAgent) {
      db.prepare(`
        INSERT INTO system_agents (id, name, scope, owner_company_id, source_kind, legacy_path, status, benchmark_score, created_at, updated_at)
        VALUES (@id, @name, 'company', @owner_company_id, 'company_hired', @legacy_path, 'active', NULL, @created_at, @updated_at)
      `).run({
        id: systemAgentId,
        name: systemAgentId,
        owner_company_id: companyId,
        legacy_path: `data/companies/${companyId}/agents/${roleName}`,
        created_at: now,
        updated_at: now,
      });
    }

    db.prepare(`
      INSERT INTO org_graph_nodes (company_id, system_agent_id, role_name, created_at)
      VALUES (@company_id, @system_agent_id, @role_name, @created_at)
      ON CONFLICT(company_id, role_name) DO NOTHING
    `).run({
      company_id: companyId,
      system_agent_id: systemAgentId,
      role_name: roleName,
      created_at: now,
    });
  });

  tx();
}

module.exports = {
  lookupSharedAgentMd,
  upsertOrgGraphNode,
};