/** * 本体身份上下文(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 };