Commit 158c46ca198aa4dab2f77d905aca7702600b5aca
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
Showing
7 changed files
with
285 additions
and
2 deletions
bin/lemon.js
| ... | ... | @@ -167,6 +167,15 @@ async function run() { |
| 167 | 167 | }); |
| 168 | 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 | 179 | else if (command === 'pipeline create') { |
| 171 | 180 | const { values } = parseArgs({ args: args.slice(2), options: { |
| 172 | 181 | companyId: { type: 'string' }, file: { type: 'string' } | ... | ... |
data/system/templates/_registry.json
| ... | ... | @@ -16,5 +16,14 @@ |
| 16 | 16 | "benchmarkScore": 95, |
| 17 | 17 | "trainedAt": "2026-03-25", |
| 18 | 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 | 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
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 | 352 | } |
| 353 | 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 | 363 | const skills = loadFile(agentDir, '.claude/skills/skills.md'); |
| 356 | 364 | if (skills.trim()) parts.push('---\n\n' + skills.trim()); |
| 357 | 365 | |
| ... | ... | @@ -1287,6 +1295,158 @@ router.post('/task/report', (req, res) => { |
| 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 | 1450 | // POST /api/agent/task/reject — COO 质量门控:驳回任务 |
| 1291 | 1451 | // ═══════════════════════════════════════ |
| 1292 | 1452 | |
| ... | ... | @@ -1534,6 +1694,13 @@ router.post('/:agentId/chat', async (req, res) => { |
| 1534 | 1694 | } else if (event.method === 'session/result') { |
| 1535 | 1695 | if (event.params?.cost_usd) { |
| 1536 | 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 | 1705 | if (!fullOutput && event.params?.result) { |
| 1539 | 1706 | fullOutput = event.params.result; | ... | ... |
server/services/goals.js
| ... | ... | @@ -319,7 +319,7 @@ class GoalsService { |
| 319 | 319 | let nodesText = ''; |
| 320 | 320 | |
| 321 | 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 | 323 | if (!hasActiveNodes) { |
| 324 | 324 | const progress = this._calcProgress(goal); |
| 325 | 325 | combinedText += `\n\n## 🎯 当前参与的目标\n\n`; |
| ... | ... | @@ -385,7 +385,8 @@ class GoalsService { |
| 385 | 385 | |
| 386 | 386 | const total = leafIds.length; |
| 387 | 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 | 390 | const pending = total - done - inProgress; |
| 390 | 391 | |
| 391 | 392 | // 按 owner 分组 | ... | ... |