identity-context.js
5.32 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
/**
* 本体身份上下文(soul/identity/tools/heartbeat/OKR/hippocampus/skills)的共用读法。
*
* ── 修的是哪个坑 ──────────────────────────────────────────────────────────
* graph-engine 驱动的 work 节点(`agent_call`)此前只有 `agent.md` 进提示词——
* `PromptBuilder.build()` 走的是「protocol + agent.md + companyContextPath +
* user.md/memory.md/近期记忆」这一条链路,`soul.md`/`identity.md`/`tools.md`/
* `heartbeat.md`/OKR 目标/hippocampus 检索/skills 全部缺失。而交互式的
* `agent-chat.js`(`buildSystemPrompt()`)一直都在读这些文件,两条链路从未同步过。
* 静默到什么程度:agent 照样跑、照样成功,只是少了人格/工具约束/长期记忆检索,
* 表现为「work 节点里的 agent 比 chat 里蠢一截」,没有任何报错或日志能看出原因。
*
* ── 这里只补缺的,不重复已有的 ────────────────────────────────────────────
* `agent.md` 由 `PromptBuilder.getAgentPrompt()` 已经读过;`user.md`/`memory.md`/
* 近期记忆由 `PromptBuilder.buildCompanyStateParts()` 已经读过——这个函数**不重复**
* 那五样,只读 agent-chat 有、PromptBuilder 没有的那几样。
*
* ── ponytail:没有让 agent-chat.js 反过来调用这份实现 ─────────────────────
* 两处现在是「同构但独立」的两份代码,不是真正共享一份——`buildSystemPrompt()`
* 是长期稳定的高频路径,为了这次修复去动它,风险和收益不成比例。真要收敛成
* 一份实现,得先给 `buildSystemPrompt()` 补一圈契约测试再动。升级路径:有余力
* 时把 agent-chat.js 里对应的读文件片段也换成调这份实现,届时可以直接删掉
* agent-chat.js 里那几段、只保留它自己特有的东西(company 环境上下文 / 运行时坐标
* 声明 / userMessage 相关的 hippocampus 搜索文本拼接)。
*/
const fs = require('fs');
const path = require('path');
const { OkrService } = require('../../services/okr-service');
const { retrieveRelevantPlans } = require('../../services/memory-service');
// 与 agent-chat.js 同款用法:全局 OKR 索引(不按公司分库),module 级单例。
const okrService = new OkrService();
function readTrim(p) {
try { return fs.readFileSync(p, 'utf-8').trim(); } catch { return ''; }
}
/**
* @param {object} a
* @param {string} a.bodyDir 本体目录(agent.md/soul.md/.claude/ 所在,只读)
* @param {string} [a.agentId] OKR/hippocampus 检索用的身份键;没有则跳过这两段
* @param {string} [a.searchText=''] hippocampus 检索用的当前任务文本(agent-chat 用的是
* userMessage + todo.md,这里用调用方传入的任务描述)
* @returns {string[]} 按顺序排好的提示词分段,调用方自己 push 进 parts 数组
*/
function buildIdentityContextParts({ bodyDir, agentId, searchText = '' }) {
const parts = [];
if (!bodyDir) return parts;
// soul.md / identity.md / tools.md —— agent.md 已经由调用方读过,这里不重复
for (const f of ['soul.md', 'identity.md', 'tools.md']) {
const c = readTrim(path.join(bodyDir, f));
if (c) parts.push(c);
}
const heartbeat = readTrim(path.join(bodyDir, 'heartbeat.md'));
if (heartbeat) parts.push(heartbeat);
if (agentId) {
try {
const goalContext = okrService.getActiveContextForAgent(agentId);
if (goalContext && goalContext.trim()) parts.push(goalContext.trim());
} catch (err) {
// eslint-disable-next-line no-console
console.error(`[IdentityContext] ⚠️ 读取目标上下文失败: ${err.message}`);
}
// user.md / memory.md / 近期记忆已经由 PromptBuilder.buildCompanyStateParts 读过,
// 不在这里重复;hippocampus 检索原实现里跟那三样同一批读,但 PromptBuilder 没有
// 对应覆盖,仍然缺,补上。
const hippocampusPlans = retrieveRelevantPlans(agentId, searchText);
if (hippocampusPlans) parts.push(hippocampusPlans);
}
const mainSkill = readTrim(path.join(bodyDir, '.claude/skills/main_skill/SKILL.md'));
if (mainSkill) parts.push('---\n\n# 我的核心主技能 (执行准则)\n\n' + mainSkill);
const localLessons = readTrim(path.join(bodyDir, 'workspace/_lessons.md'));
if (localLessons) {
parts.push('---\n\n## ⚠️ 本地试错防坑经验 (_lessons.md)\n不要重复踩下面的坑,仔细阅读并绕开:\n\n' + localLessons);
}
const skillsMd = readTrim(path.join(bodyDir, '.claude/skills/skills.md'));
if (skillsMd) parts.push(skillsMd);
// 扫描除 main_skill 外的所有 skill 子目录
const skillsDir = path.join(bodyDir, '.claude', 'skills');
if (fs.existsSync(skillsDir)) {
for (const entry of fs.readdirSync(skillsDir, { withFileTypes: true })) {
if (!entry.isDirectory() || entry.name === 'main_skill') continue;
const c = readTrim(path.join(skillsDir, entry.name, 'SKILL.md'));
if (c) parts.push(`---\n\n## 依赖技能: ${entry.name}\n\n${c}`);
}
}
return parts;
}
module.exports = { buildIdentityContextParts };