P0 阶段开发 79 工具时,不可能每次都真调 Claude(烧 token + 等响应慢)。需要一个 MOCK_CLAUDE=1 环境变量让 ClaudeCliProtocol 返回 stub 回复,工具流照常跑通。
// packages/acp-core/protocol/ClaudeCliProtocol.js · 增量改动 ~40 行 +const MOCK = process.env.MOCK_CLAUDE === '1'; +const mockResponsesDir = path.resolve(__dirname, '../../../system/mock-responses'); + +class ClaudeCliProtocol { + async sendPrompt(input) { + if (MOCK) return this._sendPromptMock(input); + return this._sendPromptReal(input); + } + + async _sendPromptMock(input) { + // 1. 解析 agent name 从 system prompt 或 input.agent + const agent = this.agentName; // 启动时存的 + const mode = input.mode || 'default'; + + // 2. 读 mock 响应文件 + const p = path.join(mockResponsesDir, agent, `${mode}.json`); + if (!fs.existsSync(p)) { + return this._fallbackMock(agent, input); + } + + const mock = JSON.parse(fs.readFileSync(p, 'utf8')); + + // 3. 按 mock 描述的 tool_use 序列,逐个回调工具 + // 模拟 Claude 自己调工具的行为 + for (const step of mock.steps) { + if (step.tool_use) { + const result = await this._dispatchTool(step.tool_use.name, step.tool_use.input); + // 工具回执理论上影响下一步,简化版直接忽略 result + // (高级版可让 mock 描述含 tool_result 处理逻辑) + } + if (step.delay_ms) await new Promise(r => setTimeout(r, step.delay_ms)); + if (step.text) this._emitChunk(step.text); + } + + return mock.final_result; + } + + _fallbackMock(agent, input) { + // 没有专门 mock 文件时,返回最小合规结果 + return { + ok: true, + duration_ms: 100, + tokens_used: 0, + content: '[mock fallback response]', + }; + } +}+ +
+system/mock-responses/ # 由 P0.A2 创建 +├── pm/ +│ ├── init.json # PM init mode 的 stub +│ ├── incremental.json +│ └── resume.json +├── architect/ +│ ├── generate.json +│ ├── patch.json +│ └── resume.json +├── coder/ +│ ├── implement.json +│ └── retry.json +├── tester/ +│ ├── test.json +│ └── retry.json +├── integrator/ +│ ├── run.json +│ └── retry.json +└── bug-triager/ + └── triage.json ++ +
{ + "description": "PM init mock: 假装解析需求并写出 prd/ 11 章节", + "steps": [ + { "text": "读取上下文...\n" }, + { "tool_use": { + "name": "write_todolist", + "input": { "items": [ + { "id": "step-001", "description": "解析需求" }, + { "id": "step-002", "description": "bootstrap_prd_site" }, + { "id": "step-003", "description": "写 index.html" } + ] } + } }, + { "tool_use": { "name": "bootstrap_prd_site", "input": { "pid": "$pid" } } }, + { "tool_use": { "name": "write_chapter", + "input": { "pid": "$pid", "section": "index", "page": "index", + "html": "<!doctype html>...mock content...</html>" } + } }, + { "tool_use": { "name": "echo_diff", + "input": { "summary": "创建 mock PRD 站点" } } } + ], + "final_result": { + "ok": true, + "mode": "init", + "prd_rev": 1, + "files_written": ["prd/index.html"], + "change_summary": "[mock] PM init", + "duration_ms": 100, + "tokens_used": 0 + } +}+ +
| 场景 | 用什么 | 原因 |
|---|---|---|
| 开发新工具,验证落盘 / SSE 正确 | mock | 不烧 token;快速迭代 |
| 调试 LoopController 主循环 | mock | 同上 |
| 跑 phase0-verify CI | mock | CI 不能依赖真 Claude |
| 验证 Agent system prompt 质量 | 真 | mock 不能反映 prompt 修改 |
| P1 验收(30s 内 prd/ 落盘) | 真 | 真 token 消耗 + 真 prompt 检验 |
| P3 BugTriager 归因准确率测试 | 真 | 核心检验项 |
mock JSON 里写 "$pid" 时,dispatch 前 protocol 替换为实际运行时值。简单但够用:
function interpolate(input, ctx) { + const json = JSON.stringify(input); + const resolved = json.replace(/"\$(\w+)"/g, (_, key) => + JSON.stringify(ctx[key] || '__missing_' + key) + ); + return JSON.parse(resolved); +}+ +
MOCK_CLAUDE=0 跑一遍真 PM 验证。
+ | 项 | 关注点 | 数字 / 缓解 |
|---|