graph-service.js
4.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
/**
* 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');
}
/**
* P2(27-p2-org-graph.md §3.2)起改为**部分**唯一索引:只约束 status='active' 的行,
* 这样某个角色被 fire(status='removed')之后,同一个 role_name 能被重新 hire 进来。
* 索引本身 graph-db.js 的 migrate() 已经建过了,这里保留是为了让本模块不依赖调用顺序;
* 名字/条件必须与 graph-db.js 完全一致,否则会互相把对方的索引建回来。
*/
function ensureOrgGraphNodeIndex(db) {
if (indexEnsured) return;
db.exec(`
CREATE UNIQUE INDEX IF NOT EXISTS idx_org_graph_nodes_company_role_active
ON org_graph_nodes(company_id, role_name) WHERE status = 'active'
`);
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) WHERE status='active' 部分唯一索引
* + INSERT ... ON CONFLICT(...) WHERE status='active' DO NOTHING,重复调用不产生重复行。
* (SQLite 的 conflict target 要匹配部分索引,必须原样带上索引的 WHERE 条件。)
*
* @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, status, created_at)
VALUES (@company_id, @system_agent_id, @role_name, 'active', @created_at)
ON CONFLICT(company_id, role_name) WHERE status = 'active' DO NOTHING
`).run({
company_id: companyId,
system_agent_id: systemAgentId,
role_name: roleName,
created_at: now,
});
});
tx();
}
module.exports = {
lookupSharedAgentMd,
upsertOrgGraphNode,
};