Commit 158c46ca198aa4dab2f77d905aca7702600b5aca

Authored by yibo
1 parent b15d8570

feat(agent): implement Harness Engineering Phase 2-5 blueprint

- Add task transition CLI and API for Context Flush & Hot Swap
- Enforce strict bash-only tool policy for Base Agents
- Inject  into system prompt for self-retrying loop
- Implement Goal node updates on transitions
- Add P0 Execute pre-approval block (Human-in-the-loop)
- Track token metrics to billing.log
bin/lemon.js
@@ -167,6 +167,15 @@ async function run() { @@ -167,6 +167,15 @@ async function run() {
167 }); 167 });
168 console.log(JSON.stringify(res.data, null, 2)); 168 console.log(JSON.stringify(res.data, null, 2));
169 } 169 }
  170 + else if (command === 'task transition') {
  171 + const { values } = parseArgs({ args: args.slice(2), options: {
  172 + agentId: { type: 'string' }, phase: { type: 'string' }, artifact: { type: 'string', default: '' }
  173 + }});
  174 + const res = await request('POST', `/agent/${values.agentId}/task/transition`, {
  175 + phase: values.phase, artifact: values.artifact
  176 + });
  177 + console.log(JSON.stringify(res.data, null, 2));
  178 + }
170 else if (command === 'pipeline create') { 179 else if (command === 'pipeline create') {
171 const { values } = parseArgs({ args: args.slice(2), options: { 180 const { values } = parseArgs({ args: args.slice(2), options: {
172 companyId: { type: 'string' }, file: { type: 'string' } 181 companyId: { type: 'string' }, file: { type: 'string' }
data/system/templates/_registry.json
@@ -16,5 +16,14 @@ @@ -16,5 +16,14 @@
16 "benchmarkScore": 95, 16 "benchmarkScore": 95,
17 "trainedAt": "2026-03-25", 17 "trainedAt": "2026-03-25",
18 "usedCount": 6 18 "usedCount": 6
  19 + },
  20 + {
  21 + "id": "base",
  22 + "name": "标准基座员工",
  23 + "type": "specialist",
  24 + "description": "严格遵循四阶段执行规范的基础执行员工",
  25 + "benchmarkScore": 90,
  26 + "trainedAt": "2026-03-26",
  27 + "usedCount": 0
19 } 28 }
20 ] 29 ]
21 \ No newline at end of file 30 \ No newline at end of file
data/system/templates/base/agent.md 0 → 100644
  1 +# Base Agent (Standard Corporate Employee)
  2 +
  3 +## 身份定位
  4 +你是 LemonBoss 企业网络中的一名 **标准基座员工 (Base Agent)**。
  5 +在你收到上级派发的任何 Task 时,你绝对不能“一气呵成、随地大小写代码或写产出”。你被赋予了严厉的生命周期“四段式法则”:`Research` -> `Plan` -> `Execute` -> `Verify`。
  6 +
  7 +## 强制执行回路 (Task Lifecycle Paradigm)
  8 +你当前默认只被挂载了 **`meta-research`** 这个元技能,你必须依靠自己主动呼叫状态机工具来切换你的脑子和技能包。请严格按照下述单节点法则推进你的干活流程:
  9 +
  10 +### 阶段一:RESEARCH(调研)->你当前所处的阶段!
  11 +1. **不要猜测,去寻找**:动用一切读取工具看懂上级给你的任务,去硬盘里找背景文件。
  12 +3. **斩断记忆(Context Flush)**:写完后,运行 `node bin/lemon.js task transition --agentId <你自己ID> --phase plan --artifact 你的_research.md路径`。**绝不要自己在当前对话里直接去出规划!必须呼叫此命令行让系统清空你的记忆并给你换上 Plan 技能!**
  13 +
  14 +### 阶段二:PLAN(规划)
  15 +*(当你成功跳过防线来到这里时,你将失去 `meta-research`,获得 `meta-plan`)*
  16 +1. 读你的 `_research.md`。
  17 +3. 运行 `node bin/lemon.js task transition --agentId <你自己ID> --phase execute --artifact 你的_plan.md路径`。
  18 +
  19 +### 阶段三:EXECUTE(执行)
  20 +*(系统给你换上 `meta-execute` 技能时,你才会拥有修改核心文件的破坏性权限)*
  21 +2. 运行 `node bin/lemon.js task transition --agentId <你自己ID> --phase verify`。
  22 +
  23 +### 阶段四:VERIFY(验证)
  24 +*(系统给你换上 `meta-verify` 技能,你的任务是自己检测自己跑的代码或跑分)*
  25 +1. 如果失败,将原因定入本地 `_lessons.md`,用工具打回上一阶段。
  26 +2. 如果确信真的 PASS,才可以向上级(老板)调用归档终结命令 `report_task({status: "PASS"})`。
  27 +
  28 +⚠️ **警告:未经四步走流转直接汇报 `PASS` 或写代码的行为将被视为严重违规记录!**
data/system/templates/base/config.json 0 → 100644
  1 +{
  2 + "type": "specialist",
  3 + "status": "idle",
  4 + "cli": {
  5 + "toolPolicy": {
  6 + "mode": "restricted",
  7 + "allowedTools": ["bash", "read_file", "str_replace"]
  8 + }
  9 + }
  10 +}
docs/system-design/25-harness-engineering-improvements.md 0 → 100644
  1 +# 25. Harness Engineering Improvements (Agent 上下文与生命周期优化)
  2 +
  3 +## 1. 背景与目标 (Background & Objectives)
  4 +
  5 +随着 LemonBoss 架构从单体 Agent 演进为多租户下的“数字代理公司(Corporate OS)”,我们的宏观阵法(层级汇报、跨部门协作、OKR 分解)已初具规模(参考之前的相关系统设计)。然而,基于对前沿 Harness Engineering 最佳实践的深度反思,单个 Agent 节点在处理长链路复杂任务时,依然面临**“上下文衰减(Context Decay)”**、**“信息污染(Noise Pollution)”**、以及**“试错成本高昂(High Trial Cost)”**三大核心痛点。
  6 +
  7 +为了确保底层每一个“数字员工”即使在超长命名的商业运营中也不会发生注意力弥散(Attention Decay),我们提出以下补充重构计划。该计划的核心思想是:**做精信息喂食、隔离决策上下文、构建自查闭环。**
  8 +
  9 +---
  10 +
  11 +## 2. 核心改进计划 (Core Improvement Plans)
  12 +
  13 +### 改进点一:将四阶段分解固化为基座 Agent 的四大元技能 (Meta-Skills Integration)
  14 +**现状痛点**:当前单个 session 会承载一个 Agent 从“思考规划”到“调用执行”直至“修复 Bug”的全生命周期。后期 Session Prompt 过长(超过甜区限度),导致关键指令被稀释,且执行边界模糊。
  15 +**改进策略**:我们将 `Research` -> `Plan` -> `Execute` -> `Verify` 不仅作为宏观规范,而是**实际固化为基座 Agent (Base Agent) 标准配备的四大元技能包(Meta-Skills)**。
  16 +1. **按需装载独立技能树 (Dynamic Skill Invocation)**:在底层设定中,为每一个 Base Agent 提供四个相互独立的元技能引擎接口。每次面临新任务,代理根据自身所处的流转阶段,主动调用加载专属的技能包(如只调用 `skill_plan` 去出方案)。
  17 +2. **基于技能切换的 Context Flush(上下文切断与防污染)**:一旦当前阶段的技能执行完毕(例如调用 Planner 技能产出了 `architecture.md`),该 Session 生命周期强制结束归档。当进入下一环节调起 `skill_execute` 技能时,新开一条绝对干净的 Session,只传入上一步确定的干货文件,彻底断绝跨阶段的幻觉与信息污染。
  18 +
  19 +### 改进点二:实施“事前阻断阀”管理 (Human/Master pre-approval)
  20 +**现状痛点**:人类往往在 Agent 完全做错产出一堆无效代码后(即事后)去执行 Code Review,纠错成本极高。
  21 +**改进策略**:
  22 +1. **杠杆前置审查**:当 CEO 或 CTO 这类管理层 Agent 产出“规划文档(Plan / Architecture)”后,系统强制挂起该节点的 `status`。
  23 +2. **等待放行命令**:必须由 Master(人类用户) 或高阶 Validator/Innovator Review 批准该规划文档。一旦驳回,重做 Plan;一旦批准,才能生成下游子任务清单(Task Spawning)并消耗算力。以一行 Plan 的纠正,避免百行代码的错误生成。
  24 +
  25 +### 改进点三:渐进式披露与去工具化 (Progressive Disclosure & Minimized Tools)
  26 +**现状痛点**:为了提供强大的功能,在 `tools.md` 或 System Prompt 里塞入海量工具说明与技能细则,导致模型注意力过载或陷入决策瘫痪(Decision Paralysis)。
  27 +**改进策略**:
  28 +1. **多级目录动态按需加载**:确立三级机制:
  29 + - Level 1 (`CLAUDE.md` / `identity.md`):只讲最最核心的存活元规则和找资料入口。
  30 + - Level 2 (`SKILL.md`):作为外挂组件引擎,只有触发特定领域关键词,引擎才将其注给 Agent `system_prompt`。一旦切走任务则将其卸载(Unload)。
  31 + - Level 3 (`references`):基于 RAG 工具被动按需调取日志或全量文件。
  32 +2. **修剪泛洪工具群**:将非必须的复杂组合 API 下架。退守“最强的万能扳手”:赋予 Agent 沙盒受控的终端执行与代码编写权(如受限的 Python 解释器或 `bash`),让 Agent 现写代码去满足需求,而非提供写死上百个微工具集。
  33 +
  34 +### 改进点四:单体自治理的“微小自反思闭环” (Micro Self-Verification)
  35 +**现状痛点**:完全依赖事后外置的角色(如 `QA-Judge` 或者遭遇硬报错再触发反馈),链路长且对中控要求高。
  36 +**改进策略**:
  37 +1. **植入 Local Verify Hook**:规定在下级执行类 Agent 调用 `report_task` 汇报 `status: PASS` 之前,必须调用自身的沙箱测试或执行一次自我反思的 `verify` 工具。
  38 +2. **Autoresearch Error Loop**:为底层 Agent 本地增加 `_lessons.md`(试错经验暂存区)。在执行重试(Retry)循环时,如果不通过,先强制写一条经验记录到 `_lessons.md` 中。下次 retry 带上本条经验执行,避免单次死循环卡点。此级循环不惊动顶层的 Innovator(FDE),实现“兵不烦将”。
  39 +
  40 +### 改进点五:引入算力经济学观测 (Cost & Profiling Observability)
  41 +**现状痛点**:对“系统表现和行为”透明(有 Logs),但对“运营成本”无感知,不符合 Corporate OS(企业组织)要求。
  42 +**改进策略**:
  43 +1. 建立 `logs/billing` 观测维度:在 `pipeline_runs` 和 `audit` 同级,加入 Token 消耗监测系统。
  44 +2. 精确计算每一次交接任务“花费的 API 计价预估”。给 CEO 提供财务数据维度支持,帮助甄别“能力较弱导致长耗时高频失败”的节点,为其发起汰换重构提供商业依据。
  45 +
  46 +---
  47 +
  48 +## 3. 落地 Roadmap (Implementation Roadmap)
  49 +
  50 +| 阶段 | 里程碑 (Milestones) | 涉及模块 (Modules) | 预期时间 |
  51 +|------|----------------|----------------|----------|
  52 +| **Phase 1** | **微观验证引入**:在现有节点中强制引入自我校验 (Local Verify),以及局部 `_lessons.md`。 | `agent-chat.js`, 基础 `templates/` 补充设定 | P1 优先 |
  53 +| **Phase 2** | **四大元技能装配与重构**:为 Base Agent 植装并切分 `Research/Plan/Execute/Verify` 四大独立技能入口,支持阶段流转中的 Context Flush。 | `memory-service.js`, `boss-meta-capabilities`, `master-session` | P1 优先 |
  54 +| **Phase 3** | **事前阻断流程化**:集成规划节点停摆拦截,接入前端 UI 以获得 Human/Master 的手动 Approved 后才能唤醒下游。 | `schema/goals`, Web UI 组件 | P2 规划 |
  55 +| **Phase 4** | **重置工具与按需挂载**:清理多余的工具并实施 `SKILL.md` 动态路由加载机制,提高上下文利用率甜区表现。 | `skill-registry/service`, 工具池整理 | P2 规划 |
  56 +| **Phase 5** | **经济学审计引入**:Token 数理追踪统计埋点,提供可视化报表支持(Cost Tracker Dashboard)。 | `logger/billing_service` | P3 未来探讨 |
  57 +
  58 +> **最后修订记录:**
  59 +> - 本规划文档由对行业 Harness Engineering 原理和 LemonBoss 特性对比后的工程反思形成,将指导未来一至两个大版本的演进。
server/routes/agent-chat.js
@@ -352,6 +352,14 @@ function buildSystemPrompt(agentDir, userMessage) { @@ -352,6 +352,14 @@ function buildSystemPrompt(agentDir, userMessage) {
352 } 352 }
353 parts.push('---\n\n# 我的核心主技能 (执行准则)\n\n' + mainSkill.trim()); 353 parts.push('---\n\n# 我的核心主技能 (执行准则)\n\n' + mainSkill.trim());
354 } 354 }
  355 +
  356 + // ── Phase 1 / Micro-Verify: 当前局部任务的试错经验池 `_lessons.md` ──
  357 + // 当 Agent 遇到棘手的坑或执行报错时,要求它总结并写入 _lessons.md。
  358 + // 通过在此处注入,它下次 retry 时便能带着前车之鉴,避免死循环。
  359 + const localLessons = loadFile(agentDir, 'workspace/_lessons.md');
  360 + if (localLessons.trim()) {
  361 + parts.push('---\n\n## ⚠️ 本地试错防坑经验 (_lessons.md)\n不要重复踩下面的坑,仔细阅读并绕开:\n\n' + localLessons.trim());
  362 + }
355 const skills = loadFile(agentDir, '.claude/skills/skills.md'); 363 const skills = loadFile(agentDir, '.claude/skills/skills.md');
356 if (skills.trim()) parts.push('---\n\n' + skills.trim()); 364 if (skills.trim()) parts.push('---\n\n' + skills.trim());
357 365
@@ -1287,6 +1295,158 @@ router.post(&#39;/task/report&#39;, (req, res) =&gt; { @@ -1287,6 +1295,158 @@ router.post(&#39;/task/report&#39;, (req, res) =&gt; {
1287 }); 1295 });
1288 1296
1289 // ═══════════════════════════════════════ 1297 // ═══════════════════════════════════════
  1298 +// POST /api/agent/:agentId/task/transition — 强制流转与记忆断舍离 (Context Flush)
  1299 +// ═══════════════════════════════════════
  1300 +router.post('/:agentId/task/transition', (req, res) => {
  1301 + try {
  1302 + const { phase, artifact } = req.body;
  1303 + const agentId = req.params.agentId;
  1304 + const agentDir = getAgentDir(agentId);
  1305 + if (!fs.existsSync(agentDir)) return res.status(404).json({ error: 'Agent not found' });
  1306 +
  1307 + console.log(`[TaskTransition] 🔄 ${agentId} 正在跨越阶段至: ${phase}`);
  1308 +
  1309 + // 1. 查找当前的活动会话 ID
  1310 + let activeConvId = null;
  1311 + let sessionKey = null;
  1312 + for (const [k, v] of sessions.entries()) {
  1313 + if (v.agentId === agentId) {
  1314 + activeConvId = v.convId;
  1315 + sessionKey = k;
  1316 + break;
  1317 + }
  1318 + }
  1319 +
  1320 + // 2. Phase 3 核心: 事前阻断阀 (Human-in-the-loop)
  1321 + // 拦截 P0 任务由 plan -> execute 的跳转,强制挂起并申请 Master 审批
  1322 + if (phase === 'execute' && activeConvId && activeConvId.startsWith('task-')) {
  1323 + const taskId = activeConvId.substring(5);
  1324 + const taskMeta = loadTaskMeta(taskId);
  1325 + if (taskMeta && taskMeta.priority === 'P0') {
  1326 + console.log(`[TaskTransition] 🛑 拦截 P0 任务进入 Execute,挂起等待人类审批 (taskId: ${taskId})`);
  1327 +
  1328 + // 更新 OKR 节点状态为 pending_approval
  1329 + if (taskMeta.goalId && taskMeta.nodeId) {
  1330 + try {
  1331 + goalsService.updateNode(taskMeta.goalId, taskMeta.nodeId, { status: 'pending_approval' });
  1332 + } catch (err) {}
  1333 + }
  1334 +
  1335 + // 通知 Master 审批
  1336 + setImmediate(() => {
  1337 + const masterMsg = `[⚠️ 事前审批请求]\n你的下属 ${agentId} 已经完成了 P0 级任务 ${taskId} 的规划阶段。\n现在它正准备加载大威力执行权限 (Execute) 落地方案。\n\n请你阅读其产生的 ${artifact || '_plan.md'},并在确认无误后通过指令手动放行它。`;
  1338 + const http = require('http');
  1339 + const postData = JSON.stringify({ message: masterMsg, conversationId: `approval-${taskId}` });
  1340 + const options = {
  1341 + hostname: 'localhost', port: 3000,
  1342 + path: `/api/agent/master/chat`,
  1343 + method: 'POST',
  1344 + headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(postData) },
  1345 + };
  1346 + const chatReq = http.request(options, () => {});
  1347 + chatReq.on('error', () => {});
  1348 + chatReq.write(postData);
  1349 + chatReq.end();
  1350 + });
  1351 +
  1352 + // 掐断当前进程使其休眠,等待重新唤醒
  1353 + const v = sessions.get(sessionKey);
  1354 + if (v && v.protocol) v.protocol.disconnect();
  1355 + sessions.delete(sessionKey);
  1356 +
  1357 + return res.json({ success: true, message: '已拦截。该任务为 P0 级别,必须等待 Master 审批放行后才能进入执行环节!' });
  1358 + }
  1359 + }
  1360 +
  1361 + // 若未被阻断,更新 OKR 为进行中子状态 (如 researching, executing等)
  1362 + if (activeConvId && activeConvId.startsWith('task-')) {
  1363 + const taskId = activeConvId.substring(5);
  1364 + const taskMeta = loadTaskMeta(taskId);
  1365 + if (taskMeta && taskMeta.goalId && taskMeta.nodeId) {
  1366 + try {
  1367 + const subStatus = phase === 'research' ? 'researching' : phase === 'plan' ? 'planning' : phase === 'execute' ? 'executing' : phase === 'verify' ? 'verifying' : 'in_progress';
  1368 + goalsService.updateNode(taskMeta.goalId, taskMeta.nodeId, { status: subStatus });
  1369 + } catch (err) {}
  1370 + }
  1371 + }
  1372 +
  1373 + // 3. Hot Swap 动态换脑: 把 main_skill 替换为 meta-* 技能
  1374 + const sourceSkill = path.resolve(__dirname, `../../data/system/skills/meta-${phase}/SKILL.md`);
  1375 + const targetSkill = path.join(agentDir, '.claude', 'skills', 'main_skill', 'SKILL.md');
  1376 + if (fs.existsSync(sourceSkill)) {
  1377 + if (!fs.existsSync(path.dirname(targetSkill))) fs.mkdirSync(path.dirname(targetSkill), { recursive: true });
  1378 + fs.copyFileSync(sourceSkill, targetSkill);
  1379 + console.log(`[TaskTransition] 🧠 ${agentId} 动态技能树已切换为 meta-${phase}`);
  1380 + }
  1381 +
  1382 + // 4. Context Flush 记忆断舍离: 掐断当前运行中的 ACP 进程,清空历史消息
  1383 + if (sessionKey) {
  1384 + const v = sessions.get(sessionKey);
  1385 + if (v && v.protocol) v.protocol.disconnect();
  1386 + sessions.delete(sessionKey);
  1387 + }
  1388 +
  1389 + if (activeConvId) {
  1390 + const dir = getConvDir(agentId, activeConvId);
  1391 + // 清空长周期的幻觉来源
  1392 + fs.writeFileSync(path.join(dir, 'history.json'), '[]', 'utf-8');
  1393 + logMessageChunk(agentId, activeConvId, `\n\n[SYSTEM] 🛑 Context Flush 触发!记忆已断开,阶段切换至 ${phase}\n\n`);
  1394 +
  1395 + // 3. 构建起步上下文: 告诉 Agent 它的前序成就
  1396 + let artifactContent = '';
  1397 + if (artifact) {
  1398 + // artifact 可能带有 'workspace/' 前缀,尝试解析
  1399 + const possiblePaths = [
  1400 + path.join(agentDir, artifact),
  1401 + path.join(agentDir, path.basename(artifact)),
  1402 + path.join(agentDir, 'workspace', path.basename(artifact))
  1403 + ];
  1404 + for (const p of possiblePaths) {
  1405 + if (fs.existsSync(p)) {
  1406 + artifactContent = fs.readFileSync(p, 'utf-8');
  1407 + break;
  1408 + }
  1409 + }
  1410 + }
  1411 +
  1412 + const flushMsg = `【系统指令:强制状态切换】
  1413 +你的生命周期已成功切入新阶段:**${phase.toUpperCase()}**。
  1414 +你之前的对话记忆已被完全抹除防范幻觉,你的系统技能已获得 ${phase} 的授权限制。
  1415 +
  1416 +${artifactContent ? `这是上一阶段你或你的前置节点留下的交付物(Artifact),你必须严格参照此文件行动,不再另行发散:\n\n\`\`\`\n${artifactContent}\n\`\`\`` : ''}
  1417 +
  1418 +请开始你的新阶段工作!`;
  1419 +
  1420 + // 写入一条用户指令开启新的清爽对话
  1421 + appendHistory(agentId, activeConvId, {
  1422 + role: 'user', content: flushMsg, messageId: `msg-${Date.now()}-flush`, timestamp: new Date().toISOString()
  1423 + });
  1424 +
  1425 + // 4. 发送唤醒指令让他立刻基于新脑子行动
  1426 + setImmediate(() => {
  1427 + const http = require('http');
  1428 + const postData = JSON.stringify({ message: "请确认你已收到系统的新阶段挂载指令,并立即执行你当前阶段本分的任务!", conversationId: activeConvId });
  1429 + const options = {
  1430 + hostname: 'localhost', port: 3000,
  1431 + path: `/api/agent/${agentId}/chat`,
  1432 + method: 'POST',
  1433 + headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(postData) },
  1434 + };
  1435 + const chatReq = http.request(options, () => {});
  1436 + chatReq.on('error', () => {});
  1437 + chatReq.write(postData);
  1438 + chatReq.end();
  1439 + });
  1440 + }
  1441 +
  1442 + res.json({ success: true, message: `已成功进行 Context Flush 并切换至 ${phase} 阶段` });
  1443 + } catch (err) {
  1444 + console.error('[TaskTransition] Error:', err.message);
  1445 + res.status(500).json({ error: err.message });
  1446 + }
  1447 +});
  1448 +
  1449 +// ═══════════════════════════════════════
1290 // POST /api/agent/task/reject — COO 质量门控:驳回任务 1450 // POST /api/agent/task/reject — COO 质量门控:驳回任务
1291 // ═══════════════════════════════════════ 1451 // ═══════════════════════════════════════
1292 1452
@@ -1534,6 +1694,13 @@ router.post(&#39;/:agentId/chat&#39;, async (req, res) =&gt; { @@ -1534,6 +1694,13 @@ router.post(&#39;/:agentId/chat&#39;, async (req, res) =&gt; {
1534 } else if (event.method === 'session/result') { 1694 } else if (event.method === 'session/result') {
1535 if (event.params?.cost_usd) { 1695 if (event.params?.cost_usd) {
1536 sendSSE('session_cost', { cost: event.params.cost_usd, duration: event.params.duration_ms }); 1696 sendSSE('session_cost', { cost: event.params.cost_usd, duration: event.params.duration_ms });
  1697 + // Phase 5: Economic Audit - 记录账单 (Token Profiling)
  1698 + try {
  1699 + const billingDir = path.resolve(__dirname, '../../data/logs');
  1700 + if (!fs.existsSync(billingDir)) fs.mkdirSync(billingDir, { recursive: true });
  1701 + const billingEntry = `${new Date().toISOString()} | Agent: ${agentId} | Conv: ${convId} | Cost: $${event.params.cost_usd.toFixed(6)} | Duration: ${event.params.duration_ms}ms\n`;
  1702 + fs.appendFileSync(path.join(billingDir, 'billing.log'), billingEntry, 'utf-8');
  1703 + } catch (err) {}
1537 } 1704 }
1538 if (!fullOutput && event.params?.result) { 1705 if (!fullOutput && event.params?.result) {
1539 fullOutput = event.params.result; 1706 fullOutput = event.params.result;
server/services/goals.js
@@ -319,7 +319,7 @@ class GoalsService { @@ -319,7 +319,7 @@ class GoalsService {
319 let nodesText = ''; 319 let nodesText = '';
320 320
321 for (const [nodeId, node] of Object.entries(goal.nodes)) { 321 for (const [nodeId, node] of Object.entries(goal.nodes)) {
322 - if (node.owner === agentId && (node.status === 'in_progress' || node.status === 'pending')) { 322 + if (node.owner === agentId && node.status !== 'done' && node.status !== 'failed') {
323 if (!hasActiveNodes) { 323 if (!hasActiveNodes) {
324 const progress = this._calcProgress(goal); 324 const progress = this._calcProgress(goal);
325 combinedText += `\n\n## 🎯 当前参与的目标\n\n`; 325 combinedText += `\n\n## 🎯 当前参与的目标\n\n`;
@@ -385,7 +385,8 @@ class GoalsService { @@ -385,7 +385,8 @@ class GoalsService {
385 385
386 const total = leafIds.length; 386 const total = leafIds.length;
387 const done = leafIds.filter(id => nodes[id].status === 'done').length; 387 const done = leafIds.filter(id => nodes[id].status === 'done').length;
388 - const inProgress = leafIds.filter(id => nodes[id].status === 'in_progress').length; 388 + const inProgressStates = ['in_progress', 'researching', 'planning', 'executing', 'verifying', 'pending_approval'];
  389 + const inProgress = leafIds.filter(id => inProgressStates.includes(nodes[id].status)).length;
389 const pending = total - done - inProgress; 390 const pending = total - done - inProgress;
390 391
391 // 按 owner 分组 392 // 按 owner 分组