@bolloon/bolloon-agent 0.5.4 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/agent-identity-store.js +41 -5
- package/dist/agents/background-notices.js +124 -0
- package/dist/agents/channel-identity.js +54 -0
- package/dist/agents/code-write-gate.js +47 -0
- package/dist/agents/deny-pipeline.js +11 -2
- package/dist/agents/egress-sanitize.js +69 -0
- package/dist/agents/error-classifier.js +67 -5
- package/dist/agents/experience-review.js +226 -0
- package/dist/agents/gateway-group.js +153 -18
- package/dist/agents/iteration-budget.js +64 -0
- package/dist/agents/lesson-to-skill.js +211 -0
- package/dist/agents/managed-services.js +64 -0
- package/dist/agents/parse-tool-call.js +43 -32
- package/dist/agents/pi-sdk-session-factory.js +36 -3
- package/dist/agents/pi-sdk-session-manager.js +72 -3
- package/dist/agents/pi-sdk-tools.js +201 -32
- package/dist/agents/pi-sdk.js +798 -206
- package/dist/agents/plan-store.js +36 -0
- package/dist/agents/process-runner.js +64 -2
- package/dist/agents/run-context.js +20 -0
- package/dist/agents/runner-resolver.js +2 -1
- package/dist/agents/shell-guard.js +109 -13
- package/dist/agents/skill-health.js +90 -0
- package/dist/agents/skill-ledger.js +129 -0
- package/dist/agents/tool-loop-guard.js +303 -0
- package/dist/agents/tool-path-args.js +53 -0
- package/dist/agents/tool-result-gate.js +50 -0
- package/dist/agents/tool-subset.js +109 -0
- package/dist/agents/tool-telemetry.js +97 -0
- package/dist/bootstrap/context-collector.js +37 -7
- package/dist/bootstrap/lifecycle-hooks.js +12 -3
- package/dist/bootstrap/persona-init.js +174 -0
- package/dist/bootstrap/persona-loader.js +5 -1
- package/dist/cli/ink-app.js +153 -6
- package/dist/cli/input-paste.js +218 -0
- package/dist/cli/loading-tui.js +3 -1
- package/dist/cli/log-gate.js +7 -0
- package/dist/cli/mention-data.js +30 -0
- package/dist/cli/reasoning-view.js +103 -0
- package/dist/cli/startup-notice.js +25 -0
- package/dist/cli/status-segments.js +87 -2
- package/dist/cli/trace-line.js +54 -0
- package/dist/context-compaction/auto-compact.js +37 -2
- package/dist/electron-build/agents/agent-identity-store.js +43 -5
- package/dist/electron-build/agents/agent-identity-store.js.map +1 -1
- package/dist/electron-build/agents/background-notices.js +138 -0
- package/dist/electron-build/agents/background-notices.js.map +1 -0
- package/dist/electron-build/agents/channel-identity.js +64 -0
- package/dist/electron-build/agents/channel-identity.js.map +1 -0
- package/dist/electron-build/agents/code-write-gate.js +55 -0
- package/dist/electron-build/agents/code-write-gate.js.map +1 -0
- package/dist/electron-build/agents/deny-pipeline.js +11 -2
- package/dist/electron-build/agents/deny-pipeline.js.map +1 -1
- package/dist/electron-build/agents/egress-sanitize.js +76 -0
- package/dist/electron-build/agents/egress-sanitize.js.map +1 -0
- package/dist/electron-build/agents/error-classifier.js +68 -5
- package/dist/electron-build/agents/error-classifier.js.map +1 -1
- package/dist/electron-build/agents/experience-review.js +243 -0
- package/dist/electron-build/agents/experience-review.js.map +1 -0
- package/dist/electron-build/agents/iteration-budget.js +73 -0
- package/dist/electron-build/agents/iteration-budget.js.map +1 -0
- package/dist/electron-build/agents/lesson-to-skill.js +228 -0
- package/dist/electron-build/agents/lesson-to-skill.js.map +1 -0
- package/dist/electron-build/agents/managed-services.js +69 -0
- package/dist/electron-build/agents/managed-services.js.map +1 -0
- package/dist/electron-build/agents/parse-tool-call.js +43 -32
- package/dist/electron-build/agents/parse-tool-call.js.map +1 -1
- package/dist/electron-build/agents/pi-sdk-session-factory.js +36 -3
- package/dist/electron-build/agents/pi-sdk-session-factory.js.map +1 -1
- package/dist/electron-build/agents/pi-sdk-session-manager.js +75 -3
- package/dist/electron-build/agents/pi-sdk-session-manager.js.map +1 -1
- package/dist/electron-build/agents/pi-sdk-tools.js +200 -31
- package/dist/electron-build/agents/pi-sdk-tools.js.map +1 -1
- package/dist/electron-build/agents/pi-sdk-types.js.map +1 -1
- package/dist/electron-build/agents/pi-sdk.js +799 -206
- package/dist/electron-build/agents/pi-sdk.js.map +1 -1
- package/dist/electron-build/agents/plan-store.js +37 -0
- package/dist/electron-build/agents/plan-store.js.map +1 -1
- package/dist/electron-build/agents/process-runner.js +71 -2
- package/dist/electron-build/agents/process-runner.js.map +1 -1
- package/dist/electron-build/agents/run-context.js +24 -0
- package/dist/electron-build/agents/run-context.js.map +1 -0
- package/dist/electron-build/agents/runner-resolver.js +2 -1
- package/dist/electron-build/agents/runner-resolver.js.map +1 -1
- package/dist/electron-build/agents/shell-guard.js +110 -14
- package/dist/electron-build/agents/shell-guard.js.map +1 -1
- package/dist/electron-build/agents/skill-health.js +103 -0
- package/dist/electron-build/agents/skill-health.js.map +1 -0
- package/dist/electron-build/agents/skill-ledger.js +146 -0
- package/dist/electron-build/agents/skill-ledger.js.map +1 -0
- package/dist/electron-build/agents/tool-loop-guard.js +319 -0
- package/dist/electron-build/agents/tool-loop-guard.js.map +1 -0
- package/dist/electron-build/agents/tool-path-args.js +63 -0
- package/dist/electron-build/agents/tool-path-args.js.map +1 -0
- package/dist/electron-build/agents/tool-result-gate.js +59 -0
- package/dist/electron-build/agents/tool-result-gate.js.map +1 -0
- package/dist/electron-build/agents/tool-subset.js +116 -0
- package/dist/electron-build/agents/tool-subset.js.map +1 -0
- package/dist/electron-build/agents/tool-telemetry.js +108 -0
- package/dist/electron-build/agents/tool-telemetry.js.map +1 -0
- package/dist/electron-build/bootstrap/context-collector.js +38 -7
- package/dist/electron-build/bootstrap/context-collector.js.map +1 -1
- package/dist/electron-build/bootstrap/lifecycle-hooks.js +12 -3
- package/dist/electron-build/bootstrap/lifecycle-hooks.js.map +1 -1
- package/dist/electron-build/bootstrap/persona-init.js +216 -0
- package/dist/electron-build/bootstrap/persona-init.js.map +1 -0
- package/dist/electron-build/bootstrap/persona-loader.js +5 -1
- package/dist/electron-build/bootstrap/persona-loader.js.map +1 -1
- package/dist/electron-build/context-compaction/auto-compact.js +38 -2
- package/dist/electron-build/context-compaction/auto-compact.js.map +1 -1
- package/dist/electron-build/external-engines/delegate.js +22 -0
- package/dist/electron-build/external-engines/delegate.js.map +1 -1
- package/dist/electron-build/kernel/channel-actor.js +314 -0
- package/dist/electron-build/kernel/channel-actor.js.map +1 -0
- package/dist/electron-build/orbitdb/cid-database.js +55 -0
- package/dist/electron-build/orbitdb/cid-database.js.map +1 -1
- package/dist/electron-build/orbitdb/ipfs-node.js +36 -11
- package/dist/electron-build/orbitdb/ipfs-node.js.map +1 -1
- package/dist/electron-build/web/server-storage.js +26 -5
- package/dist/electron-build/web/server-storage.js.map +1 -1
- package/dist/external-engines/delegate.js +21 -0
- package/dist/index.js +526 -95
- package/dist/kernel/channel-actor.js +297 -0
- package/dist/kernel/control.js +95 -0
- package/dist/kernel/gate-scan.js +1036 -0
- package/dist/kernel/model-runtime.js +472 -0
- package/dist/kernel/plan-channel-actor.js +298 -0
- package/dist/kernel/plan-constraint.js +165 -0
- package/dist/kernel/plan-deletion.js +32 -0
- package/dist/kernel/plan-modelruntime.js +60 -0
- package/dist/kernel/plan-runcontext.js +163 -0
- package/dist/kernel/plan.js +153 -0
- package/dist/kernel/roster.js +242 -0
- package/dist/orbitdb/cid-database.js +55 -0
- package/dist/orbitdb/event-index.js +370 -0
- package/dist/orbitdb/event-query.js +131 -0
- package/dist/orbitdb/event-shell.js +510 -0
- package/dist/orbitdb/group-access.js +381 -0
- package/dist/orbitdb/group-shards.js +140 -0
- package/dist/orbitdb/ipfs-node.js +36 -11
- package/dist/web/bolloon-web.json +3 -3
- package/dist/web/client.js +4 -2
- package/dist/web/mobile-core.js +7762 -6289
- package/dist/web/routes-mobile-tasks.js +2 -1
- package/dist/web/routes-tasks.js +5 -1
- package/dist/web/server-storage.js +26 -5
- package/dist/web/server.js +60 -18
- package/dist/web/sw.js +1 -1
- package/dist/web/ui/message-renderer.js +473 -29
- package/dist/web/ui/step-timeline.js +13 -12
- package/package.json +2 -1
- package/scripts/build-web.ts +74 -1
- package/dist/constraint-runtime/_archive_helper.d.ts +0 -1
- package/dist/constraint-runtime/_archive_helper.js +0 -9
- package/dist/constraint-runtime/assistant/index.d.ts +0 -11
- package/dist/constraint-runtime/assistant/index.js +0 -12
- package/dist/constraint-runtime/bootstrap/index.d.ts +0 -11
- package/dist/constraint-runtime/bootstrap/index.js +0 -12
- package/dist/constraint-runtime/bridge/index.d.ts +0 -11
- package/dist/constraint-runtime/bridge/index.js +0 -12
- package/dist/constraint-runtime/buddy/index.d.ts +0 -11
- package/dist/constraint-runtime/buddy/index.js +0 -12
- package/dist/constraint-runtime/cli/index.d.ts +0 -11
- package/dist/constraint-runtime/cli/index.js +0 -12
- package/dist/constraint-runtime/components/index.d.ts +0 -11
- package/dist/constraint-runtime/components/index.js +0 -12
- package/dist/constraint-runtime/constants/index.d.ts +0 -11
- package/dist/constraint-runtime/constants/index.js +0 -12
- package/dist/constraint-runtime/coordinator/index.d.ts +0 -11
- package/dist/constraint-runtime/coordinator/index.js +0 -12
- package/dist/constraint-runtime/cost_hook.d.ts +0 -2
- package/dist/constraint-runtime/cost_hook.js +0 -4
- package/dist/constraint-runtime/entrypoints/index.d.ts +0 -11
- package/dist/constraint-runtime/entrypoints/index.js +0 -12
- package/dist/constraint-runtime/execution_registry.d.ts +0 -20
- package/dist/constraint-runtime/execution_registry.js +0 -44
- package/dist/constraint-runtime/hooks/index.d.ts +0 -11
- package/dist/constraint-runtime/hooks/index.js +0 -12
- package/dist/constraint-runtime/ink.d.ts +0 -1
- package/dist/constraint-runtime/ink.js +0 -4
- package/dist/constraint-runtime/keybindings/index.d.ts +0 -11
- package/dist/constraint-runtime/keybindings/index.js +0 -12
- package/dist/constraint-runtime/memdir/index.d.ts +0 -11
- package/dist/constraint-runtime/memdir/index.js +0 -12
- package/dist/constraint-runtime/migrations/index.d.ts +0 -11
- package/dist/constraint-runtime/migrations/index.js +0 -12
- package/dist/constraint-runtime/moreright/index.d.ts +0 -11
- package/dist/constraint-runtime/moreright/index.js +0 -12
- package/dist/constraint-runtime/native_ts/index.d.ts +0 -11
- package/dist/constraint-runtime/native_ts/index.js +0 -12
- package/dist/constraint-runtime/output_styles/index.d.ts +0 -11
- package/dist/constraint-runtime/output_styles/index.js +0 -12
- package/dist/constraint-runtime/plugins/index.d.ts +0 -11
- package/dist/constraint-runtime/plugins/index.js +0 -12
- package/dist/constraint-runtime/port_manifest.d.ts +0 -7
- package/dist/constraint-runtime/port_manifest.js +0 -11
- package/dist/constraint-runtime/query.d.ts +0 -6
- package/dist/constraint-runtime/query.js +0 -1
- package/dist/constraint-runtime/remote/index.d.ts +0 -11
- package/dist/constraint-runtime/remote/index.js +0 -12
- package/dist/constraint-runtime/schemas/index.d.ts +0 -11
- package/dist/constraint-runtime/schemas/index.js +0 -12
- package/dist/constraint-runtime/screens/index.d.ts +0 -11
- package/dist/constraint-runtime/screens/index.js +0 -12
- package/dist/constraint-runtime/server/index.d.ts +0 -11
- package/dist/constraint-runtime/server/index.js +0 -12
- package/dist/constraint-runtime/services/index.d.ts +0 -11
- package/dist/constraint-runtime/services/index.js +0 -12
- package/dist/constraint-runtime/state/index.d.ts +0 -11
- package/dist/constraint-runtime/state/index.js +0 -12
- package/dist/constraint-runtime/system_init.d.ts +0 -1
- package/dist/constraint-runtime/system_init.js +0 -20
- package/dist/constraint-runtime/types/index.d.ts +0 -11
- package/dist/constraint-runtime/types/index.js +0 -12
- package/dist/constraint-runtime/upstream_proxy/index.d.ts +0 -11
- package/dist/constraint-runtime/upstream_proxy/index.js +0 -12
- package/dist/constraint-runtime/utils/index.d.ts +0 -11
- package/dist/constraint-runtime/utils/index.js +0 -12
- package/dist/constraint-runtime/vim/index.d.ts +0 -11
- package/dist/constraint-runtime/vim/index.js +0 -12
- package/dist/constraint-runtime/voice/index.d.ts +0 -11
- package/dist/constraint-runtime/voice/index.js +0 -12
|
@@ -44,12 +44,32 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
44
44
|
};
|
|
45
45
|
})();
|
|
46
46
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
47
|
-
exports.PiAgentSession = exports.runSelfImproveLoop = exports.resetAgentSession = exports.getAgentSession = exports.createAgentSession = exports.PiSessionManager = exports.TOOL_DEFINITIONS = void 0;
|
|
47
|
+
exports.PiAgentSession = exports.PROACTIVE_WORK_DISCIPLINE = exports.runSelfImproveLoop = exports.resetAgentSession = exports.getAgentSession = exports.createAgentSession = exports.PiSessionManager = exports.TOOL_DEFINITIONS = void 0;
|
|
48
48
|
exports.decideUnreported = decideUnreported;
|
|
49
49
|
exports.writeBackCurrentTurnInto = writeBackCurrentTurnInto;
|
|
50
|
+
exports.verifyWriteOutcome = verifyWriteOutcome;
|
|
51
|
+
const fs = __importStar(require("fs/promises"));
|
|
50
52
|
const fsSync = __importStar(require("fs"));
|
|
51
53
|
const os = __importStar(require("os"));
|
|
52
54
|
const path = __importStar(require("path"));
|
|
55
|
+
const run_context_js_1 = require("./run-context.js");
|
|
56
|
+
const channel_actor_js_1 = require("../kernel/channel-actor.js");
|
|
57
|
+
const tool_path_args_js_1 = require("./tool-path-args.js");
|
|
58
|
+
const background_notices_js_1 = require("./background-notices.js");
|
|
59
|
+
const skill_ledger_js_1 = require("./skill-ledger.js");
|
|
60
|
+
const node_crypto_1 = require("node:crypto");
|
|
61
|
+
const experience_review_js_1 = require("./experience-review.js");
|
|
62
|
+
const lesson_to_skill_js_1 = require("./lesson-to-skill.js");
|
|
63
|
+
const managed_services_js_1 = require("./managed-services.js");
|
|
64
|
+
const iteration_budget_js_1 = require("./iteration-budget.js");
|
|
65
|
+
const tool_telemetry_js_1 = require("./tool-telemetry.js");
|
|
66
|
+
const tool_result_gate_js_1 = require("./tool-result-gate.js");
|
|
67
|
+
const tool_subset_js_1 = require("./tool-subset.js");
|
|
68
|
+
const code_write_gate_js_1 = require("./code-write-gate.js");
|
|
69
|
+
// 2026-10-01: 身份解析必须**静态导入** —— 之前在函数体里用 require(), 而打包后是 ESM ⇒
|
|
70
|
+
// require 是 undefined ⇒ 抛错被 catch 静默吞掉 ⇒ "自愈"根本没跑, DID 一直是空的 ✗。
|
|
71
|
+
const agent_identity_js_1 = require("./agent-identity.js");
|
|
72
|
+
const channel_identity_js_1 = require("./channel-identity.js");
|
|
53
73
|
const context_manager_js_1 = require("../bootstrap/context-manager.js");
|
|
54
74
|
const module_1 = require("module");
|
|
55
75
|
const version_info_js_1 = require("../utils/version-info.js");
|
|
@@ -174,6 +194,62 @@ function writeBackCurrentTurnInto(history, wire) {
|
|
|
174
194
|
return 0; // 写回失败绝不影响对话本身
|
|
175
195
|
}
|
|
176
196
|
}
|
|
197
|
+
const tool_loop_guard_js_1 = require("./tool-loop-guard.js");
|
|
198
|
+
/**
|
|
199
|
+
* 写操作"读回自证" (2026-10-01 优化 #4): 写类工具成功后**自动**核一次, 把事实拼进结果。
|
|
200
|
+
* 为什么: 过程纪律写了"读回一次", 但**没人执行** ✗ ⇒ 现在由机制执行: "工具说成功 ≠ 任务成功"。
|
|
201
|
+
* 只做**便宜**的核对(存在性/大小); 失败静默(核对不该把工具搞失败)。
|
|
202
|
+
*/
|
|
203
|
+
function verifyWriteOutcome(toolName, args, cwd) {
|
|
204
|
+
try {
|
|
205
|
+
const fsMod = require('node:fs');
|
|
206
|
+
const pathMod = require('node:path');
|
|
207
|
+
const WRITE = new Set(['write_file', 'edit_file', 'mkdir', 'move_file', 'copy_file']);
|
|
208
|
+
if (!WRITE.has(String(toolName)))
|
|
209
|
+
return null;
|
|
210
|
+
const rel = String(args?.path ?? args?.to ?? args?.dir ?? '').trim();
|
|
211
|
+
if (!rel)
|
|
212
|
+
return null;
|
|
213
|
+
const abs = pathMod.isAbsolute(rel) ? rel : pathMod.resolve(cwd, rel);
|
|
214
|
+
const st = fsMod.statSync(abs);
|
|
215
|
+
if (st.isDirectory())
|
|
216
|
+
return `[已核对] 目录存在: ${rel}`;
|
|
217
|
+
return `[已核对] 文件已落盘: ${rel} (${st.size} 字节, ${new Date(st.mtimeMs).toISOString()})`;
|
|
218
|
+
}
|
|
219
|
+
catch (e) {
|
|
220
|
+
return `[未核对] 读回失败: ${String(e?.message || e).slice(0, 80)} —— 别急着说"已完成", 先确认路径/权限`;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* 过程纪律 (2026-10-01, 用户: 「智能体回复方式没有主动性 … 在过程里面更加主动考虑」)。
|
|
225
|
+
*
|
|
226
|
+
* 诊断: 系统提示里只有"理解→分析→调用→观察"这种**反应式**循环描述 ⇒ 模型容易"问一句答一句、
|
|
227
|
+
* 试一次就收尾、把方案当交付"。主动不是靠一句"请主动"能给的, 要落成**可判定的规矩**。
|
|
228
|
+
* 落地方式 (7 条, 都是"做/不做"能验收的): 先动手别先问 · 做到底别停在半成品 · 一个工具不够就换法继续 ·
|
|
229
|
+
* 工具说成功 ≠ 任务成功(读回验证) · 被阻塞如实说绝不编 · 每轮要么推进要么交付 · 顺手想影响面并给下一步建议。
|
|
230
|
+
*/
|
|
231
|
+
exports.PROACTIVE_WORK_DISCIPLINE = `
|
|
232
|
+
过程纪律 (比"答得漂亮"更重要, 逐条可判定):
|
|
233
|
+
0. **先动手, 别先问**: 请求有显而易见的默认解释时, 按它做, 不要为了确认而停下 —— 只有"不同解释会让我做不同的事"时才问。
|
|
234
|
+
0.5 **别把"主动"用在闲聊上**: 寒暄 / 感受 / 纯闲聊这类**没有任务**的消息 ⇒ 直接回答, **不要**为此调工具
|
|
235
|
+
(用户实测: 一句「你好」去跑了 git_status 花 4.1s ✗ —— 那是浪费)。"主动"= 接到任务后**想在你前面**,
|
|
236
|
+
不是"每句话都先把环境查一遍" ✗。
|
|
237
|
+
1. **做到底**: 交付的是**能跑的东西 + 真实的工具输出**, 不是"我打算怎么做"。只写方案、只搭一半、只报告打算做什么, 都算没做完。
|
|
238
|
+
2. **一个工具不够就换法继续**: 同一件事试到确实不通为止 —— 换参数/换命令/换路径/换工具接着试, 不要试一次就收尾。
|
|
239
|
+
3. **工具说成功 ≠ 任务成功**: 写入/生成/发布这类动作, **读回一次**再声称完成(内容/存在/大小/哈希都可)。
|
|
240
|
+
4. **被阻塞就如实说**: 讲清卡在哪一步、为什么、还缺什么; **绝不**用编造的结果顶替(编一个"看起来对"的输出比说"没做成"更糟)。
|
|
241
|
+
5. **每一轮要么用工具推进, 要么给出结论**: 不要把"下一步我打算…"当成回答。
|
|
242
|
+
6. **收尾时说三句**: 改了什么 · 验过什么(拿什么读到的) · 还剩什么。不重述过程。
|
|
243
|
+
7.4 **技能是"要用时先读再照做"**: 库里有一批已装载技能(名字+说明), 用 list_skills <关键词> 找,
|
|
244
|
+
用 read_skill <名字> 读全文 —— **相关时先读它再动手**, 别凭印象做(技能里往往写着踩过的坑与判据 ✓)。
|
|
245
|
+
7.5 **一轮里可以同时调多个独立工具** (并行执行已支持): 互不依赖的读取/查询**一次发出去**, 别一个一个来回 —
|
|
246
|
+
零碎调用既慢又费预算(批量工具还会退还额度 ✓)。
|
|
247
|
+
7.6 **收尾标记的语义**(别猜, 就按这条): 写完那个结束标记 = "**我已收尾, 且自己验过**(改了什么/拿什么验证的), 请复核"。
|
|
248
|
+
只在这两种情况下写: ① 任务真的做完了(不是"我打算做") · ② 纯对话/问答也**不用**写(直接答即可)。
|
|
249
|
+
没做完就**别写** —— 写了会被当成"已完成"进入复核, 反而浪费一轮 ✓。
|
|
250
|
+
7. **过程中主动考虑**: 动手前先想这个改动的**影响面**(同类调用点 · 相邻功能 · 已有数据/契约), 发现关联问题就说出来,
|
|
251
|
+
并给出你建议的下一步 —— 主动是指"想在你前面", 不是"多问几句"。
|
|
252
|
+
`;
|
|
177
253
|
class PiAgentSession {
|
|
178
254
|
cwd;
|
|
179
255
|
peerId;
|
|
@@ -186,7 +262,90 @@ class PiAgentSession {
|
|
|
186
262
|
sessionManager;
|
|
187
263
|
agentsManager;
|
|
188
264
|
socialHeartbeat = null;
|
|
189
|
-
|
|
265
|
+
/**
|
|
266
|
+
* **K5 第 4 步 (所有权转移)**: history 的**本体**。
|
|
267
|
+
* · 未绑定 actor ⇒ 本体在这里 (绑定前的暂存);
|
|
268
|
+
* · 绑定 actor 后 ⇒ 本体是 `actor.state.messageHistory`, 本地那份被**收养**并清空。
|
|
269
|
+
* 绑定由 `attachActor()` 做, 且**只在会话身份已知时**发生 (见 session factory) —— 没有身份就不归属。
|
|
270
|
+
*/
|
|
271
|
+
/**
|
|
272
|
+
* **K5 第 4 步 — history 写入的唯一漏斗 (push)**。
|
|
273
|
+
* 为什么必须同步: 调用点遍布 ReAct 循环 / 工具回灌 / 压缩, 而且**写完立刻读** (length / 索引 / slice),
|
|
274
|
+
* 改成 await 会改变同拍可见性。所以这里有意识地**不排队** —— 归属收敛 + 可数 (门能断言"零处直写"),
|
|
275
|
+
* 并发安全由入口投递进 mailbox 负责 (K5 第 5 步)。
|
|
276
|
+
*/
|
|
277
|
+
pushHistory(...msgs) {
|
|
278
|
+
for (const m of msgs) {
|
|
279
|
+
// 步骤⑧ 起**只有一条路径**: 写 actor (实例侧暂存字段已删除) —— 也彻底消除了"自递归"的可能
|
|
280
|
+
this.actor.appendMessageSync(m);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
/** **K5 第 4 步 — history 写入漏斗 (pop)** —— 同 pushHistory 的性质 */
|
|
284
|
+
popHistory() {
|
|
285
|
+
return this.actor.popMessageSync();
|
|
286
|
+
}
|
|
287
|
+
/** **K5 第 4 步 — history 写入漏斗 (整体替换)**: hydrate 回灌 / 压缩后的整块赋值都走这里 */
|
|
288
|
+
/**
|
|
289
|
+
* **整体替换**历史 —— 唯一漏斗之一, 步骤⑧ 起是**唯一**的"种历史"入口 (公开)。
|
|
290
|
+
* 公开的理由: 上层/测试要种一份历史时必须走这里; 直接给对象赋一个新数组会**换掉数组身份**
|
|
291
|
+
* (actor 里那份仍是空的) —— 那是静默丢数据 (实测: 删掉 setter 后 `session.messageHistory = [...]`
|
|
292
|
+
* 变成写在一个凭空出现的自有属性上, 测试于是看到 0 条)。
|
|
293
|
+
*/
|
|
294
|
+
replaceHistory(next) {
|
|
295
|
+
this.actor.replaceHistory(next);
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* **K5 步骤⑦ — 把一次性请求里的绑定落到位** (入口不再散着设字段: 只交一个请求)。
|
|
299
|
+
* 写入都走访问器 ⇒ 本体落进 `actor.state` (步骤③ 起就是这样); 没给的不覆盖 (与环境变量"只覆盖显式给出的"同秩)。
|
|
300
|
+
*/
|
|
301
|
+
applyExecutionRequest(req) {
|
|
302
|
+
if (req.channelId)
|
|
303
|
+
this.actor.state.channelId = req.channelId;
|
|
304
|
+
if (req.agentId)
|
|
305
|
+
this.actor.state.agentId = req.agentId;
|
|
306
|
+
if (req.goalId)
|
|
307
|
+
this.actor.state.goalBinding = req.goalId;
|
|
308
|
+
if (req.resumeRunId)
|
|
309
|
+
this.resumeRunId = req.resumeRunId;
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* **K5 步骤⑦ — Pi 的唯一执行入口**: 只接收一次性 `ExecutionRequest`。
|
|
313
|
+
* 有 `onStream` ⇒ 走 `promptStream` (流式); 否则走 `prompt`。绑定由 `applyExecutionRequest` 落位。
|
|
314
|
+
* 入口侧只需: `deliverThroughActor(session, () => session.runExecution(req))`。
|
|
315
|
+
*/
|
|
316
|
+
async runExecution(req) {
|
|
317
|
+
this.applyExecutionRequest(req);
|
|
318
|
+
if (req.onStream) {
|
|
319
|
+
return this.promptStream(req.input, req.onStream, req.signal, req.channelId);
|
|
320
|
+
}
|
|
321
|
+
return this.prompt(req.input, { signal: req.signal, channelId: req.channelId });
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* **K5 第 4 步**: 绑定 actor 并**收养**绑定前已存在的本地历史。
|
|
325
|
+
* 收养规则: actor 侧为空 且 本地非空 ⇒ 搬过去 (任何绑定时序都不丢历史); 收养后清空本地。
|
|
326
|
+
* 约束 (全量回归打出来的): 调用方必须用**会话身份**当注册键; 没有身份就**不要调**这个方法。
|
|
327
|
+
*/
|
|
328
|
+
attachActor(actor) {
|
|
329
|
+
const prev = this.actor;
|
|
330
|
+
this.actor = actor;
|
|
331
|
+
if (!prev || prev === actor)
|
|
332
|
+
return;
|
|
333
|
+
// **收养**: 把上一个 actor (通常是"出生时的私有 actor") 的状态搬进新家。
|
|
334
|
+
// 新家已有值时不覆盖 —— 同一会话身份以先到者为准 (禁止"后到的调用把先建的状态冲掉")。
|
|
335
|
+
const from = prev.state;
|
|
336
|
+
const to = actor.state;
|
|
337
|
+
if (from.messageHistory.length > 0 && to.messageHistory.length === 0) {
|
|
338
|
+
to.messageHistory.push(...from.messageHistory);
|
|
339
|
+
}
|
|
340
|
+
if (from.channelId && !to.channelId)
|
|
341
|
+
to.channelId = from.channelId;
|
|
342
|
+
if (from.agentId && !to.agentId)
|
|
343
|
+
to.agentId = from.agentId;
|
|
344
|
+
if (from.goalBinding && !to.goalBinding)
|
|
345
|
+
to.goalBinding = from.goalBinding;
|
|
346
|
+
if (from.activeRun && !to.activeRun)
|
|
347
|
+
to.activeRun = from.activeRun;
|
|
348
|
+
}
|
|
190
349
|
tools = new Map();
|
|
191
350
|
/** 2026-06-30: tool registry 模块 — 独立 alias resolve, 测试可消融. */
|
|
192
351
|
_toolRegistry = new tool_registry_js_1.ToolRegistry();
|
|
@@ -194,7 +353,51 @@ class PiAgentSession {
|
|
|
194
353
|
/** M2.4: 缓存 tool 列表, registerTools() 之后不变, runReActLoop 多次循环复用 */
|
|
195
354
|
cachedToolDefinitions = '';
|
|
196
355
|
/** M2.4: 缓存 persona section */
|
|
356
|
+
/**
|
|
357
|
+
* 当前活跃计划 (待办) 的**重注入段** (2026-10-01 落实④)。
|
|
358
|
+
* 每轮都带 —— 因为上下文压缩会把早先注入的计划文本丢掉, 一次性注入等于"压完就忘"。
|
|
359
|
+
* 有界 (条数/字符都封顶); 读不到就返回空串, 绝不影响主流程。
|
|
360
|
+
*/
|
|
361
|
+
async renderActivePlansSection() {
|
|
362
|
+
// 2026-10-01: 后台委派的结果**自动回灌** —— 完成后的第一轮在这里给一行提示(只提一次)
|
|
363
|
+
let bg = '';
|
|
364
|
+
try {
|
|
365
|
+
bg = (0, background_notices_js_1.renderDelegateNotices)();
|
|
366
|
+
}
|
|
367
|
+
catch { /* 提示失败不打断主流程 */ }
|
|
368
|
+
try {
|
|
369
|
+
const block = (0, background_notices_js_1.renderNoticeBlock)(); // 通用回流(复盘产出/后台任务) —— 空闲排空 ⇒ 新一轮 ✓
|
|
370
|
+
if (block)
|
|
371
|
+
bg = bg ? `${bg}\n${block}` : block;
|
|
372
|
+
}
|
|
373
|
+
catch { /* 回流失败不打断 */ }
|
|
374
|
+
let plans = '';
|
|
375
|
+
try {
|
|
376
|
+
const { listActivePlans, formatPlansForPrompt } = await Promise.resolve().then(() => __importStar(require('./plan-store.js')));
|
|
377
|
+
const list = await listActivePlans();
|
|
378
|
+
plans = formatPlansForPrompt(list, { maxChars: 1200, maxItems: 3 });
|
|
379
|
+
}
|
|
380
|
+
catch {
|
|
381
|
+
return bg;
|
|
382
|
+
}
|
|
383
|
+
return bg ? `${bg}\n${plans}` : plans;
|
|
384
|
+
}
|
|
197
385
|
cachedPersonaSection = '';
|
|
386
|
+
/** 本轮迭代预算 (每轮重置; 批处理工具会退还 —— 见 iteration-budget.ts) */
|
|
387
|
+
/** 上一个工具调用的参数指纹 (遥测判"是否重复调用") */
|
|
388
|
+
lastToolSig = null;
|
|
389
|
+
/** 最近一条用户消息 (按需子集档用来判 intent) */
|
|
390
|
+
lastUserIntent = '';
|
|
391
|
+
/** 本回合改过的 TS 文件 (回合收尾自动类型检查用) */
|
|
392
|
+
tsTouchedThisTurn = [];
|
|
393
|
+
/** 本回合是否已跑过类型检查 (一轮只跑一次) */
|
|
394
|
+
typecheckRanThisTurn = false;
|
|
395
|
+
iterBudget = null;
|
|
396
|
+
iterBudgetWarned = false;
|
|
397
|
+
/** 上一次已复盘的**任务签名** (签名变了 ⇒ 立刻复盘, 对应"每次任务都总结" ✓) */
|
|
398
|
+
lastReviewedTaskSig = undefined;
|
|
399
|
+
/** 上一次回合后自审的时间 (节流; 0 = 从未) */
|
|
400
|
+
lastExperienceReviewAt = 0;
|
|
198
401
|
/** 2026-06-30: 持久化层 — 默认走 ~/.bolloon/sessions/cache/, 测试可注入临时目录. */
|
|
199
402
|
_sessionStore;
|
|
200
403
|
/** 构造期间 fire-and-forget 任务的 promise — whenReady() 等它 */
|
|
@@ -305,11 +508,29 @@ class PiAgentSession {
|
|
|
305
508
|
// 拼到 systemPrompt 末尾, 别再混进 user message
|
|
306
509
|
contextHintAddition = '';
|
|
307
510
|
/**
|
|
308
|
-
*
|
|
309
|
-
*
|
|
511
|
+
* **K2 收尾 (leo 2026-10-02 口径)**: **唯一一处**把"已有的恢复身份"播种进 RunContext。
|
|
512
|
+
*
|
|
513
|
+
* · `currentRunId` 属 **run-boundary**, 不是循环内部的可变 RunContext 状态 ⇒ 只允许这一次显式播种读取;
|
|
514
|
+
* · 冻结 `CURRENT_RUN_ID_SEED_READS = 1` (见 src/kernel/plan-runcontext.ts), 由门强制;
|
|
515
|
+
* · 只发生在**进入一次 Run 时**; 不得在循环中新增读取点; 不得成为新的写入口;
|
|
516
|
+
* · K5 Channel Actor 完成后改为 `createRunContext({ runId: request.resumeRunId })`, 届时删除 `currentRunId` 字段本体。
|
|
517
|
+
*/
|
|
518
|
+
seedRunContext(extra = {}) {
|
|
519
|
+
return (0, run_context_js_1.createRunContext)({ runId: this.actor.state.activeRun, ...extra });
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* **K5 迁移中**: 本会话所属 channel 的 Actor (由 session factory 在创建时绑定)。
|
|
523
|
+
* 现在只做**归属** —— 会话状态 (messageHistory / channelId / agentId / goalId) **仍然**在实例字段上,
|
|
524
|
+
* 逐项迁入 Actor 见 `src/kernel/plan-channel-actor.ts` 的 8 步。
|
|
310
525
|
*/
|
|
311
|
-
|
|
312
|
-
|
|
526
|
+
actor;
|
|
527
|
+
/**
|
|
528
|
+
* **K2 迁移中**: 一次 Run 的显式状态载体 (见 src/agents/run-context.ts)。
|
|
529
|
+
* 入口 (prompt / promptStream / promptWithPivotLoop) 用 createRunContext() 快照一次, 用完即清。
|
|
530
|
+
* **已外置**: `eventSink` (原 currentOnStream) · `abortSignal` (原 currentSignal)。
|
|
531
|
+
* 其余 6 个字段仍以本类实例字段为准, 逐格迁移。
|
|
532
|
+
*/
|
|
533
|
+
runCtx = (0, run_context_js_1.createRunContext)();
|
|
313
534
|
/** Bootstrap SessionStart 拼的 system prompt 片段 (用完即清) */
|
|
314
535
|
bootstrapAddition = '';
|
|
315
536
|
/** 2026-08-12 (Task3 认知卸载): 工具选择与认知卸载指南 — 注入 system prompt, 降低"模型不触发 write/edit/read"率. */
|
|
@@ -323,12 +544,17 @@ class PiAgentSession {
|
|
|
323
544
|
- 每步只选一个最合适的工具, 先读后写, 写完验证 (跑 tsc/vitest 或读回文件).`;
|
|
324
545
|
/** 当前 prompt 开始时间 (供 Stop hook 计算 durationMs) */
|
|
325
546
|
promptStartTime = 0;
|
|
326
|
-
/**
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
547
|
+
/**
|
|
548
|
+
* **K5 步骤③ (会话绑定迁入 Actor)**: 三处绑定 (channelId / agentId / goalBinding) 的本体住进
|
|
549
|
+
* `actor.state`, 实例上只留**绑定前的暂存** (`_channelId` 等), 由 `attachActor()` 收养后清空。
|
|
550
|
+
* 用访问器而不是改调用点: 写入点 (prompt 入口 / Goal 绑定 / snapshot) 分散且语义各异, 逐个改风险大;
|
|
551
|
+
* 访问器让所有读写自动落到 actor (同一个值对象), 调用点零改动。
|
|
552
|
+
* 注意: 这三个字段的**访问数不变** (台账按 "self.currentX" 形态计数) —— 迁的是**所有权**, 不是删访问。
|
|
553
|
+
* 本注释刻意不写出那种带 `this.` 前缀的字面形态: 计数口径只剥 `//` 行注释, 块注释里的同形串会被算进去
|
|
554
|
+
* (曾因此把 currentChannelId 的计数虚增 1, 被门照出)。
|
|
555
|
+
*/
|
|
556
|
+
// **K5 步骤⑧ 批次4**: channelId 的访问器已删 —— 本体 `actor.state.channelId`, 直接影响本体。
|
|
557
|
+
// M2.2 intent 已外置到 runCtx.intent (K2); 拼 systemPrompt 时读 this.runCtx.intent
|
|
332
558
|
/** 2026-08-10: 本轮用户原始输入 (loop-review 任务动词兜底检测用) */
|
|
333
559
|
currentUserInput = '';
|
|
334
560
|
/**
|
|
@@ -336,7 +562,16 @@ class PiAgentSession {
|
|
|
336
562
|
* 之前只有"跑完才写"的轨迹 (trajectory-store, fire-and-forget, 崩了就没有), 没有跨重载可见的
|
|
337
563
|
* 运行事实, 也没有预算闸门/失速巡检; 这里补的就是那一层。
|
|
338
564
|
*/
|
|
339
|
-
|
|
565
|
+
/**
|
|
566
|
+
* **K5 步骤⑥ (Run 身份归属 Actor)**: 本体是 `actor.state.activeRun`。
|
|
567
|
+
* · 未绑定 actor ⇒ 住在这里 (`_runId`, 绑定前的暂存);
|
|
568
|
+
* · 绑定后 ⇒ 读写全落 actor (同一个值), 由 `attachActor()` 收养绑定前的值。
|
|
569
|
+
* 语义: "本会话当前活跃的 Run" —— 一个会话身份同时只有一个活跃 Run ✓ 与 Actor 的粒度一致。
|
|
570
|
+
* 注意: 访问数**不变** —— 迁的是**所有权**不是删访问 (与步骤③同理)。
|
|
571
|
+
* 本注释刻意不写出"带 self. 前缀 + 该字段名"的那种字面形态: K2 的计数只剥 `//` 行注释,
|
|
572
|
+
* 块注释里的同形串会被算进去 (同类踩过三次, 每次都被门照出)。
|
|
573
|
+
*/
|
|
574
|
+
// **K5 步骤⑧ 批次5 (收尾)**: currentRunId 访问器已删 —— Run 身份的本体是 `actor.state.activeRun`, 直接读写本体。
|
|
340
575
|
/** 上一次运行的 runId (收尾不清空; 见 getLastRunId) */
|
|
341
576
|
lastRunId = '';
|
|
342
577
|
runSurface = 'cli';
|
|
@@ -353,14 +588,14 @@ class PiAgentSession {
|
|
|
353
588
|
currentIntentHint = '';
|
|
354
589
|
/**
|
|
355
590
|
* 算 judgment 注入门: 失败静默, 不阻塞主对话
|
|
356
|
-
* 期间通过
|
|
591
|
+
* 期间通过 runCtx.eventSink 广播 phase 事件, 前端可显示 "正在检索判断力..." 状态
|
|
357
592
|
* 调用方负责用完即清 (judgmentGateAddition='')
|
|
358
593
|
*/
|
|
359
594
|
async computeJudgmentGate(input) {
|
|
360
595
|
const safePhase = (phase, extra = {}) => {
|
|
361
596
|
try {
|
|
362
|
-
if (this.
|
|
363
|
-
this.
|
|
597
|
+
if (this.runCtx.eventSink) {
|
|
598
|
+
this.runCtx.eventSink({ type: 'phase', phase, ...extra, content: '' });
|
|
364
599
|
}
|
|
365
600
|
}
|
|
366
601
|
catch { /* 静默 */ }
|
|
@@ -402,17 +637,51 @@ class PiAgentSession {
|
|
|
402
637
|
this.judgmentGateNegativeUsedIds = [];
|
|
403
638
|
}
|
|
404
639
|
constructor(config) {
|
|
640
|
+
// **K5 步骤⑧**: actor 从出生就在 —— 工厂通常在**构造前**就把身份 actor 注进来 (`config.actor`);
|
|
641
|
+
// 没有身份时用一份**私有 actor** (不注册 ⇒ 谁也拿不到, 隔离性优先)。
|
|
642
|
+
// ⇒ 实例侧不再需要"绑定前暂存"字段 (那些字段本步删除)。
|
|
643
|
+
this.actor = config.actor ?? (0, channel_actor_js_1.createPrivateActor)();
|
|
405
644
|
this.cwd = config.cwd;
|
|
406
645
|
this.peerId = config.peerId || 'local';
|
|
407
646
|
this.identity = config.identityDoc || this.createDefaultIdentity();
|
|
408
647
|
this.minimaxAvailable = this.checkMinimax();
|
|
409
648
|
// 2026-07-04: 透传 agentId (server.ts 通过 createAgentSession 选项注入)
|
|
410
|
-
this.
|
|
649
|
+
this.actor.state.agentId = config.agentId || '';
|
|
650
|
+
// 2026-10-01 **身份自愈** (用户实测: get_identity 返回 "DID: " 空 + 各 channel 名字串台):
|
|
651
|
+
// 工具上下文里的身份来自 this.identity, 而它可能是 config.identityDoc 与 session 实例身份的
|
|
652
|
+
// 合并结果 —— **空 did 会盖掉真 did** ✗; 名字也可能缺失或被别处覆盖。
|
|
653
|
+
// 规矩: 缺 did / 名字时, 按 **currentAgentId** 从真身份生成器补齐 (did:key + 落盘密钥),
|
|
654
|
+
// 名字优先取该 agent 自己的 persona.json。绝不回落到全局/用户身份名。
|
|
655
|
+
try {
|
|
656
|
+
if (this.actor.state.agentId) {
|
|
657
|
+
const cur = this.identity || {};
|
|
658
|
+
const curDid = String(cur.did || '');
|
|
659
|
+
const badDid = !curDid || /^did:(local|pi):/i.test(curDid);
|
|
660
|
+
const missingName = !String(cur.name || '').trim();
|
|
661
|
+
// 2026-10-01 加固: **did 与本人密钥不符** 也算坏 —— 用户实测 get_identity 返回一个
|
|
662
|
+
// 数据里根本不存在的 did (按 peerId 当 scope 新造的), 而它非空、非假值 ⇒ 上一版判据漏过 ✗。
|
|
663
|
+
// 规矩: 只要 currentAgentId 有值, 身份的 did 就必须等于该 agent 自己的密钥 did。
|
|
664
|
+
const mine = (0, agent_identity_js_1.loadOrCreateAgentIdentity)(this.actor.state.agentId);
|
|
665
|
+
const mismatch = !!mine?.did && !!curDid && curDid !== mine.did;
|
|
666
|
+
if (badDid || missingName || mismatch) {
|
|
667
|
+
if (badDid || mismatch) {
|
|
668
|
+
console.warn(`[identity] 身份纠正: ${this.actor.state.agentId} 的 did ${curDid ? curDid.slice(0, 26) : '(空)'} ⇒ ${mine.did.slice(0, 26)} (以该 agent 自己的密钥为准)`);
|
|
669
|
+
}
|
|
670
|
+
// 2026-10-01: 同样必须**原地改** —— 这段在 registerTools() **之后**跑, 工具上下文已经按引用
|
|
671
|
+
// 捕获了 this.identity; 换对象 ⇒ 工具看不到纠正结果 ✗ (同类 bug, 一并修)
|
|
672
|
+
Object.assign(this.identity, {
|
|
673
|
+
...(mine?.did ? { did: mine.did, publicKey: mine.publicKey || cur.publicKey } : {}),
|
|
674
|
+
...(missingName ? { name: (0, channel_identity_js_1.agentPersonaName)(this.actor.state.agentId) || this.actor.state.agentId } : {}),
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
catch { /* 自愈失败不致命 */ }
|
|
411
680
|
// 2026-06-30: 持久化层可注入 — 测试传 tmpDir, 业务用默认 ~/.bolloon/sessions/cache/
|
|
412
681
|
this._sessionStore = config.sessionStore ?? session_store_js_1.sessionStore;
|
|
413
682
|
this.constraintLayer = new constraint_layer_js_1.ConstraintLayer();
|
|
414
683
|
this.workflowEngine = new workflow_engine_js_1.WorkflowEngine(this.constraintLayer);
|
|
415
|
-
this.sessionManager = new pi_sdk_session_manager_js_2.PiSessionManager(this.identity.did, this.cwd);
|
|
684
|
+
this.sessionManager = new pi_sdk_session_manager_js_2.PiSessionManager(this.identity.did, this.cwd, this.actor.state.agentId);
|
|
416
685
|
this.agentsManager = new heartbeat_js_1.DiscoveredAgentsManager();
|
|
417
686
|
this.usePivotLoop = config.usePivotLoop ?? false;
|
|
418
687
|
this.pivotLoopConfig = config.pivotLoopConfig;
|
|
@@ -498,6 +767,18 @@ class PiAgentSession {
|
|
|
498
767
|
*/
|
|
499
768
|
async hydrateMessageHistory(sessionKey, maxMessages) {
|
|
500
769
|
try {
|
|
770
|
+
// **K5 第 4 步**: 实现体 (load → filter → 截断 → 替换) 已搬进 Actor —— 走邮箱 ⇒ 与 append/persist 串行。
|
|
771
|
+
// 业务侧只交两个**纯回调** (内核因此不必 import 业务模块); 未绑定 actor 的会话走下面原路径 (行为不变)。
|
|
772
|
+
if (this.actor) {
|
|
773
|
+
const n = await this.actor.hydrateHistory({
|
|
774
|
+
load: () => this._sessionStore.loadMessages(sessionKey),
|
|
775
|
+
filter: (loaded) => this._filterToMessage(loaded),
|
|
776
|
+
maxMessages,
|
|
777
|
+
});
|
|
778
|
+
if (n > 0)
|
|
779
|
+
console.log(`[PiAgent] 从 ${sessionKey} 回灌 ${n} 条历史 (经 Channel Actor)`);
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
501
782
|
const loaded = await this._sessionStore.loadMessages(sessionKey);
|
|
502
783
|
if (!loaded) {
|
|
503
784
|
console.log(`[PiAgent] hydrate: 没有 ${sessionKey} 的历史`);
|
|
@@ -505,7 +786,7 @@ class PiAgentSession {
|
|
|
505
786
|
}
|
|
506
787
|
const hydrated = this._filterToMessage(loaded).slice(-maxMessages);
|
|
507
788
|
if (hydrated.length > 0) {
|
|
508
|
-
this.
|
|
789
|
+
this.replaceHistory(hydrated);
|
|
509
790
|
console.log(`[PiAgent] 从 ${sessionKey} 回灌 ${hydrated.length} 条历史`);
|
|
510
791
|
}
|
|
511
792
|
}
|
|
@@ -525,7 +806,10 @@ class PiAgentSession {
|
|
|
525
806
|
* 即可获得"重启 / 跨进程接续"的语义.
|
|
526
807
|
*/
|
|
527
808
|
async saveCurrentSession(key) {
|
|
528
|
-
|
|
809
|
+
// **K5 第 4 步**: 取数拍走 Actor 的快照 (走邮箱 ⇒ 与 append 串行, 不会抓到"边写边读"的半截状态);
|
|
810
|
+
// 未绑定 actor 的会话走原路径 (行为不变)。
|
|
811
|
+
const source = this.actor ? await this.actor.historySnapshot() : this.actor.state.messageHistory;
|
|
812
|
+
const persisted = source.map((m) => ({
|
|
529
813
|
role: m.role,
|
|
530
814
|
content: m.content,
|
|
531
815
|
toolCall: m.toolCall,
|
|
@@ -542,9 +826,9 @@ class PiAgentSession {
|
|
|
542
826
|
* 与 loadSessionKey (构造时读) 不同: 这个是 session 已建好后再读.
|
|
543
827
|
*/
|
|
544
828
|
async resumeSession(key, maxMessages = 30) {
|
|
545
|
-
const before = this.messageHistory.length;
|
|
829
|
+
const before = this.actor.state.messageHistory.length;
|
|
546
830
|
await this.hydrateMessageHistory(key, maxMessages);
|
|
547
|
-
return this.messageHistory.length - before;
|
|
831
|
+
return this.actor.state.messageHistory.length - before;
|
|
548
832
|
}
|
|
549
833
|
/**
|
|
550
834
|
* 2026-06-30: 读历史不修改 messageHistory.
|
|
@@ -650,6 +934,59 @@ class PiAgentSession {
|
|
|
650
934
|
this._inboxMessages = [];
|
|
651
935
|
const toolCtx = {
|
|
652
936
|
tools: this.tools,
|
|
937
|
+
// 2026-10-01 (用户: 「process 也要可以管理群聊和去中心化交流进程」):
|
|
938
|
+
// 把**常驻的交流类服务**也挂进 process —— 能启停的给启停(社交心跳 ✓), 只能看的如实标"仅状态" ✓(不假装能停 ✗)
|
|
939
|
+
managedServices: (() => {
|
|
940
|
+
const ms = managed_services_js_1.managedServices;
|
|
941
|
+
ms.register({
|
|
942
|
+
name: 'social-heartbeat',
|
|
943
|
+
description: '社交心跳: 主动发现节点/发起对话/组织群聊的常驻循环(去中心化交流用)',
|
|
944
|
+
status: () => (this.socialHeartbeat ? '运行中' : '已停止'),
|
|
945
|
+
start: () => this.startSocialHeartbeat(),
|
|
946
|
+
stop: () => this.stopSocialHeartbeat(),
|
|
947
|
+
});
|
|
948
|
+
ms.register({
|
|
949
|
+
name: 'p2p-network',
|
|
950
|
+
description: 'P2P 网络: 与其他 bolloon 节点的连接(群聊/消息都走它)',
|
|
951
|
+
status: () => { try {
|
|
952
|
+
const peers = this.getPeers?.() ?? [];
|
|
953
|
+
return `已连接 ${peers.length} 个节点`;
|
|
954
|
+
}
|
|
955
|
+
catch {
|
|
956
|
+
return '状态不可用';
|
|
957
|
+
} },
|
|
958
|
+
});
|
|
959
|
+
ms.register({
|
|
960
|
+
name: 'orbitdb-groups',
|
|
961
|
+
description: 'OrbitDB 群聊存储: 本地群列表与消息持久化',
|
|
962
|
+
status: () => { try {
|
|
963
|
+
const g = this.listGroupsShallow?.();
|
|
964
|
+
return g ? `本地群 ${g}` : '已就绪(用 /group 看列表)';
|
|
965
|
+
}
|
|
966
|
+
catch {
|
|
967
|
+
return '状态不可用';
|
|
968
|
+
} },
|
|
969
|
+
});
|
|
970
|
+
ms.register({
|
|
971
|
+
name: 'document-receiver',
|
|
972
|
+
description: '文档接收器: 监听并接收其他节点发来的文档分片',
|
|
973
|
+
status: () => '监听中(随进程启动)',
|
|
974
|
+
});
|
|
975
|
+
return ms;
|
|
976
|
+
})(),
|
|
977
|
+
// 2026-10-01: 技能发现(list_skills / read_skill)的实现 —— 技能原先只注册不露面 ✗, 现在模型能自己找
|
|
978
|
+
listSkills: () => this.skillRegistry.list().map((sk) => ({ name: sk.name, description: String(sk.description || '') })),
|
|
979
|
+
getSkillBody: async (name) => {
|
|
980
|
+
const sk = this.skillRegistry.get(name);
|
|
981
|
+
if (!sk)
|
|
982
|
+
return null;
|
|
983
|
+
try {
|
|
984
|
+
return await sk.execute({});
|
|
985
|
+
}
|
|
986
|
+
catch {
|
|
987
|
+
return null;
|
|
988
|
+
}
|
|
989
|
+
},
|
|
653
990
|
cwd: this.cwd,
|
|
654
991
|
identity: this.identity,
|
|
655
992
|
persona: this.persona,
|
|
@@ -663,7 +1000,7 @@ class PiAgentSession {
|
|
|
663
1000
|
const { CHANNELS_PATH } = await Promise.resolve().then(() => __importStar(require('../web/server-types.js')));
|
|
664
1001
|
const { loadChannels } = await Promise.resolve().then(() => __importStar(require('../web/server-storage.js')));
|
|
665
1002
|
const channels = await loadChannels();
|
|
666
|
-
const ch = channels.find((c) => c.id === this.
|
|
1003
|
+
const ch = channels.find((c) => c.id === this.actor.state.channelId);
|
|
667
1004
|
if (ch && ch.encryptedPrivateKey && ch.encryptedPrivateKeyIv && ch.walletAddress) {
|
|
668
1005
|
return {
|
|
669
1006
|
encryptedPrivateKey: ch.encryptedPrivateKey,
|
|
@@ -712,16 +1049,18 @@ class PiAgentSession {
|
|
|
712
1049
|
// 2026-07-29: 拒绝列表变化时清空缓存, 重新生成
|
|
713
1050
|
if (this.cachedToolDefinitions)
|
|
714
1051
|
return this.cachedToolDefinitions;
|
|
715
|
-
const defs = ['可用工具 (name(params) - 简介):'];
|
|
716
1052
|
// 2026-07-29: 使用 allowedTools() 过滤掉拒绝列表中的工具
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
1053
|
+
const allowed = Array.from(this.allowedTools());
|
|
1054
|
+
const header = '可用工具 (按类分组; name(params) - 简介):';
|
|
1055
|
+
// 2026-06-19: 压缩 tool 定义 — 只显示参数名 (不显示描述, 减少 60% 长度)
|
|
1056
|
+
// 完整 description 在 history 第一轮注入 (getToolDefinitionsFull 调用), 后续轮只看简短
|
|
1057
|
+
// 避免 system prompt 太大导致 minimax 撞 max_tokens 输出空
|
|
1058
|
+
// 2026-10-01 (优化 #2 v2): 按类**分组**列 + 说明"未列出的也能按名字调" ⇒ 治选择过载, 且不丢能力。
|
|
1059
|
+
// BOLLOON_TOOL_SUBSET=on 时改走"按 intent 只展开相关桶"(仍带 list_tools + 能力提示 ✓)。
|
|
1060
|
+
const subsetOn = String(process.env.BOLLOON_TOOL_SUBSET || '').toLowerCase() === 'on';
|
|
1061
|
+
const intentText = subsetOn ? this.lastUserIntent : '';
|
|
1062
|
+
const rendered = (0, tool_subset_js_1.renderToolListWithParams)(allowed, intentText, { subset: subsetOn, perBucket: 14 });
|
|
1063
|
+
this.cachedToolDefinitions = `${header}\n${rendered.text}`;
|
|
725
1064
|
return this.cachedToolDefinitions;
|
|
726
1065
|
}
|
|
727
1066
|
async initSession() {
|
|
@@ -733,12 +1072,28 @@ class PiAgentSession {
|
|
|
733
1072
|
}
|
|
734
1073
|
}
|
|
735
1074
|
createDefaultIdentity() {
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
1075
|
+
// 2026-10-01: 原先自造 `did:pi:<peerId>` —— **不是有效 DID** (仓里 server.ts 自己都把 did:pi:
|
|
1076
|
+
// 当"待升级"占位; 用户实测 get_identity 报 did:pi:ch_1785668060213)。
|
|
1077
|
+
// 改用仓里既有的真身份生成器 (agent-identity.loadOrCreateAgentIdentity, 同步, 产 did:key + 落盘密钥)。
|
|
1078
|
+
const scope = this.actor.state.agentId || this.peerId || 'default';
|
|
1079
|
+
try {
|
|
1080
|
+
const real = (0, agent_identity_js_1.loadOrCreateAgentIdentity)(scope);
|
|
1081
|
+
return {
|
|
1082
|
+
did: real.did,
|
|
1083
|
+
name: `Agent-${this.peerId.substring(0, 8)}`,
|
|
1084
|
+
publicKey: real.publicKey || this.peerId,
|
|
1085
|
+
createdAt: Date.now(),
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
1088
|
+
catch {
|
|
1089
|
+
// 真身份生成失败时**不编假 DID**: 如实留空, 由 /did 与 get_identity 报"未绑定"
|
|
1090
|
+
return {
|
|
1091
|
+
did: '',
|
|
1092
|
+
name: `Agent-${this.peerId.substring(0, 8)}`,
|
|
1093
|
+
publicKey: this.peerId,
|
|
1094
|
+
createdAt: Date.now(),
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
742
1097
|
}
|
|
743
1098
|
checkMinimax() {
|
|
744
1099
|
try {
|
|
@@ -779,9 +1134,9 @@ class PiAgentSession {
|
|
|
779
1134
|
const did = await resolveUserDid();
|
|
780
1135
|
const model = this.llmConfig?.model || this.model || '';
|
|
781
1136
|
return new TrajectoryRecorder({
|
|
782
|
-
agentId: this.
|
|
1137
|
+
agentId: this.actor.state.agentId || 'default',
|
|
783
1138
|
input,
|
|
784
|
-
channelId: channelId || this.
|
|
1139
|
+
channelId: channelId || this.actor.state.channelId,
|
|
785
1140
|
did: did || undefined,
|
|
786
1141
|
model: typeof model === 'string' && model ? model : undefined,
|
|
787
1142
|
});
|
|
@@ -823,8 +1178,8 @@ class PiAgentSession {
|
|
|
823
1178
|
if (gate !== 'ready') {
|
|
824
1179
|
const why = `初始化未就绪 (${gate}, 当前阶段 ${state.stage})${state.lastError ? ` — ${state.lastError.message}` : ''}`;
|
|
825
1180
|
const hint = (state.actions && state.actions[0]) || '运行 `bolloon setup` 完成初始化';
|
|
826
|
-
this.
|
|
827
|
-
this.
|
|
1181
|
+
this.pushHistory({ role: 'user', content: input });
|
|
1182
|
+
this.pushHistory({ role: 'assistant', content: `[初始化未就绪] ${why}\n${hint}` });
|
|
828
1183
|
console.warn(`[PiAgent] 拒绝执行: ${why}`);
|
|
829
1184
|
return `[初始化未就绪] ${why}\n${hint}`;
|
|
830
1185
|
}
|
|
@@ -832,13 +1187,13 @@ class PiAgentSession {
|
|
|
832
1187
|
catch { /* 门禁自身异常 → 按"不能判定"处理会阻塞一切; 这里只在能读到状态时才拦 */ }
|
|
833
1188
|
}
|
|
834
1189
|
this.minimaxAvailable = this.checkMinimax();
|
|
835
|
-
this.
|
|
1190
|
+
this.actor.state.channelId = options?.channelId ?? this.actor.state.channelId;
|
|
836
1191
|
// 2026-08-08: 运行轨迹采集 (落盘 + OrbitDB, 失败静默) — 包裹 onStream 收集步骤事件
|
|
837
1192
|
const trajRec = await this.createTrajectoryRecorder(input, options?.channelId);
|
|
838
1193
|
if (trajRec && options?.onStream) {
|
|
839
1194
|
options = { ...options, onStream: this.wrapTrajectoryStream(options.onStream, trajRec) };
|
|
840
1195
|
}
|
|
841
|
-
this.
|
|
1196
|
+
this.pushHistory({
|
|
842
1197
|
role: 'user',
|
|
843
1198
|
content: input
|
|
844
1199
|
});
|
|
@@ -848,23 +1203,23 @@ class PiAgentSession {
|
|
|
848
1203
|
// 于是把"什么都没跑"当成一次正常执行 (这正是"agent 跑了但没记录"的老毛病在 fallback 路径的翻版)。
|
|
849
1204
|
const response = await this.handleFallback(input);
|
|
850
1205
|
try {
|
|
851
|
-
let boundGoalId = this.
|
|
1206
|
+
let boundGoalId = this.actor.state.goalBinding;
|
|
852
1207
|
if (!boundGoalId) {
|
|
853
1208
|
const g = await (0, goal_store_js_1.createGoal)({
|
|
854
1209
|
objective: this.currentUserInput || input.slice(0, 200),
|
|
855
|
-
channelId: this.
|
|
856
|
-
agentId: this.
|
|
1210
|
+
channelId: this.actor.state.channelId || undefined,
|
|
1211
|
+
agentId: this.actor.state.agentId || undefined,
|
|
857
1212
|
createdBy: this.runSurface,
|
|
858
1213
|
});
|
|
859
1214
|
boundGoalId = g.goalId;
|
|
860
1215
|
}
|
|
861
|
-
this.
|
|
1216
|
+
this.actor.state.goalBinding = boundGoalId;
|
|
862
1217
|
const rec = await (0, run_store_js_1.startRun)({
|
|
863
1218
|
surface: this.runSurface,
|
|
864
1219
|
goal: this.currentUserInput || input.slice(0, 200),
|
|
865
1220
|
goalId: boundGoalId,
|
|
866
|
-
channelId: this.
|
|
867
|
-
agentId: this.
|
|
1221
|
+
channelId: this.actor.state.channelId || undefined,
|
|
1222
|
+
agentId: this.actor.state.agentId || undefined,
|
|
868
1223
|
modelConfig: await this.runModelSnapshot(),
|
|
869
1224
|
});
|
|
870
1225
|
this.lastRunId = rec.runId;
|
|
@@ -903,25 +1258,27 @@ class PiAgentSession {
|
|
|
903
1258
|
catch (err) {
|
|
904
1259
|
await (0, run_store_js_1.recordDegradation)({ kind: 'core', op: 'pi-sdk.fallbackRun', runId: this.lastRunId || '', message: String(err?.message || err).slice(0, 160) }).catch(() => { });
|
|
905
1260
|
}
|
|
906
|
-
this.
|
|
1261
|
+
this.pushHistory({ role: 'assistant', content: response });
|
|
907
1262
|
this.reportUsageToContextManager();
|
|
908
1263
|
this.finishTrajectory(trajRec, response);
|
|
909
1264
|
return response;
|
|
910
1265
|
}
|
|
911
1266
|
// P0 注入门
|
|
912
|
-
|
|
913
|
-
|
|
1267
|
+
// K2: eventSink + abortSignal 已外置, 入口一次建好本轮 Context。
|
|
1268
|
+
// 其余字段**不在这里抄一份** —— 抄写会新增对旧实例字段的读, 违反方向判据
|
|
1269
|
+
// 「新层出现后旧写口调用点数只许不变或减少」。逐字段迁移时再各自搬进来。
|
|
1270
|
+
this.runCtx = this.seedRunContext({ eventSink: options?.onStream ?? null, abortSignal: options?.signal ?? null });
|
|
914
1271
|
await this.computeJudgmentGate(input);
|
|
915
1272
|
// M2.2 (2026-06-17): intent 分类 — prompt() 路径也跑 (跟 promptStream 对齐)
|
|
916
1273
|
try {
|
|
917
1274
|
const { classifyIntent, intentHint } = await Promise.resolve().then(() => __importStar(require('./intent-classifier.js')));
|
|
918
|
-
this.
|
|
919
|
-
this.currentIntentHint = intentHint(this.
|
|
1275
|
+
this.runCtx.intent = classifyIntent(input);
|
|
1276
|
+
this.currentIntentHint = intentHint(this.runCtx.intent);
|
|
920
1277
|
this.currentUserInput = input;
|
|
921
1278
|
}
|
|
922
1279
|
catch (err) {
|
|
923
1280
|
console.warn('[PiAgent] classifyIntent in prompt() failed:', err);
|
|
924
|
-
this.
|
|
1281
|
+
this.runCtx.intent = 'chitchat';
|
|
925
1282
|
this.currentIntentHint = '';
|
|
926
1283
|
}
|
|
927
1284
|
// P2: 解析当前 permission mode
|
|
@@ -946,14 +1303,113 @@ class PiAgentSession {
|
|
|
946
1303
|
(0, injection_gate_js_1.recordJudgmentUsage)(this.judgmentGateUsedIds, { userInput: input }).catch((err) => console.warn('[PiAgent] recordJudgmentUsage failed:', err));
|
|
947
1304
|
}
|
|
948
1305
|
this.clearJudgmentGate();
|
|
949
|
-
this.
|
|
950
|
-
this.currentOnStream = null;
|
|
1306
|
+
this.runCtx = (0, run_context_js_1.createRunContext)(); // K2: 用完即清 (换新 Context, 不继承残留);
|
|
951
1307
|
this.reportUsageToContextManager();
|
|
952
1308
|
}
|
|
953
1309
|
}
|
|
954
1310
|
try {
|
|
955
1311
|
// 2026-06-16: runReActLoop 现在返回 { reply, aiFailed, aiFailureReason } — 这里只需 reply 字符串
|
|
956
|
-
const loopResult = await this.runReActLoop(this.
|
|
1312
|
+
const loopResult = await this.runReActLoop(this.runCtx.eventSink ?? undefined, options?.signal);
|
|
1313
|
+
// 2026-10-01: **本回合改过 TS ⇒ 收尾自动类型检查** (一轮一次) —— 复用已有的 tsc_check 工具
|
|
1314
|
+
if ((0, code_write_gate_js_1.decideTypecheck)(this.tsTouchedThisTurn, this.typecheckRanThisTurn)) {
|
|
1315
|
+
this.typecheckRanThisTurn = true;
|
|
1316
|
+
try {
|
|
1317
|
+
const tscTool = this.tools.get('tsc_check');
|
|
1318
|
+
if (tscTool?.execute) {
|
|
1319
|
+
const r = await tscTool.execute({});
|
|
1320
|
+
const line = (0, code_write_gate_js_1.formatTypecheckResult)(r?.success !== false && !/error TS\d+/.test(String(r?.output || '')), String(r?.output || ''));
|
|
1321
|
+
this.runCtx.eventSink?.({ type: 'status', content: `🔎 ${line}`, tool: 'system' });
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
catch (e) {
|
|
1325
|
+
this.runCtx.eventSink?.({ type: 'status', content: `🔎 类型检查没能跑起来: ${String(e?.message || e).slice(0, 100)} (改动已落盘, 记得自己跑一次)`, tool: 'system' });
|
|
1326
|
+
}
|
|
1327
|
+
this.tsTouchedThisTurn = [];
|
|
1328
|
+
}
|
|
1329
|
+
// 2026-10-01: **回合后自审 (纯旁路)** —— 沉淀可复用经验, 对应"结束后沉淀"那一段。
|
|
1330
|
+
// 纪律: fire-and-forget (不阻塞主回合) · 只写 ~/.bolloon/experience/ (**不碰主对话/prompt 缓存**)
|
|
1331
|
+
// · 节流 (默认 10 分钟/agent) · 失败只记一行日志, 绝不影响本回合结果。
|
|
1332
|
+
try {
|
|
1333
|
+
const reviewNow = Date.now();
|
|
1334
|
+
const turnSummary = `用户: ${String(this.currentUserInput || '').slice(0, 2000)}\n助手: ${String(loopResult?.reply || '').slice(0, 4000)}`;
|
|
1335
|
+
// 任务签名: 用户这次要的东西 + 用了哪些工具 ⇒ 换任务立刻复盘(同一任务才节流) ✓
|
|
1336
|
+
const taskSig = (0, node_crypto_1.createHash)('sha256')
|
|
1337
|
+
.update(`${String(this.currentUserInput || '').slice(0, 300)}|${this.totalToolCallsThisTurn || ''}`)
|
|
1338
|
+
.digest('hex').slice(0, 12);
|
|
1339
|
+
if ((0, experience_review_js_1.shouldReviewTask)(reviewNow, this.lastExperienceReviewAt, this.lastReviewedTaskSig, taskSig)) {
|
|
1340
|
+
this.lastExperienceReviewAt = reviewNow; // 先记账, 免得多路并发各起一次
|
|
1341
|
+
this.lastReviewedTaskSig = taskSig;
|
|
1342
|
+
// 2026-10-01 (用户: 「我没有看到执行完命令后 bolloon 知道选中更新 skills,是不是没有触发」):
|
|
1343
|
+
// 实测**确实没触发** ✓ —— 两个原因: ① 被调方又自己判了一次节流 ⇒ 换任务也被 throttled 掉 ✗;
|
|
1344
|
+
// ② 反馈走 `console.warn`(TUI 里被吞 ✗) + `void … .catch(() => {})`(失败无声 ✗)
|
|
1345
|
+
// ⇒ 你什么都看不到 ✓。现在: 传 force 尊重调用方的决定 ✓ · 复盘过程/结果/失败**都进对话流** ✓ ·
|
|
1346
|
+
// 同时落一份 `~/.bolloon/logs/experience-review.log` 供事后核 ✓。
|
|
1347
|
+
const reviewLog = (m) => {
|
|
1348
|
+
const line = `[${new Date().toISOString()}] ${m}`;
|
|
1349
|
+
try {
|
|
1350
|
+
const p = path.join(os.homedir(), '.bolloon', 'logs', 'experience-review.log');
|
|
1351
|
+
void fs.mkdir(path.dirname(p), { recursive: true })
|
|
1352
|
+
.then(() => fs.appendFile(p, line + '\n'))
|
|
1353
|
+
.catch(() => { });
|
|
1354
|
+
}
|
|
1355
|
+
catch { /* 落日志失败不影响主流程 */ }
|
|
1356
|
+
try {
|
|
1357
|
+
this.runCtx.eventSink?.({ type: 'status', content: `📚 复盘: ${m}`, tool: 'system' });
|
|
1358
|
+
}
|
|
1359
|
+
catch { /* 上屏失败不打断 */ }
|
|
1360
|
+
};
|
|
1361
|
+
reviewLog('开始(换了任务 ⇒ 立刻复盘)');
|
|
1362
|
+
void (0, experience_review_js_1.runExperienceReview)({
|
|
1363
|
+
turnSummary,
|
|
1364
|
+
force: true,
|
|
1365
|
+
chat: async (prompt) => {
|
|
1366
|
+
const r = await (0, index_js_1.getMinimax)().chat(prompt);
|
|
1367
|
+
return typeof r === 'string' ? r : String(r?.content ?? r?.text ?? '');
|
|
1368
|
+
},
|
|
1369
|
+
log: reviewLog,
|
|
1370
|
+
// 2026-10-01: 写了经验之后再找"能沉淀进哪个已有技能"的候选 —— **只记候选, 不自动改技能** ✓
|
|
1371
|
+
// (实测: 拿真实教训撞真实 1300 个技能, 最高分只有 2 且 top 命中是瞎的 ✗ ⇒ 不替人决定 ✓)
|
|
1372
|
+
onLesson: (lesson) => {
|
|
1373
|
+
try {
|
|
1374
|
+
// ⓐ 管理: 找"能沉淀进哪个已有技能"的候选(只记, 不自动改 ✗)
|
|
1375
|
+
const dirs = (0, skill_loader_js_1.defaultSkillPaths)(os.homedir(), process.cwd());
|
|
1376
|
+
const decision = (0, lesson_to_skill_js_1.decideLessonSink)(lesson, (0, lesson_to_skill_js_1.skillsFromDirs)(dirs));
|
|
1377
|
+
(0, lesson_to_skill_js_1.logLessonSuggestions)(lesson, decision);
|
|
1378
|
+
// ⓐ' 用户要求「**都进去**」⇒ 每条教训都落进技能库: 强命中写那个技能 ✓, 否则写沉淀技能 `lessons-learned` ✓
|
|
1379
|
+
// 2026-10-01 **写来源隔离**: 复盘/自审产生的写, 打上 review 标记 ✓ ⇒
|
|
1380
|
+
// 它只许治理**自己造出来**的技能 ✓(用户在 skills 里点名要的归用户 ✓)
|
|
1381
|
+
(0, skill_ledger_js_1.runWithWriteOrigin)('review', () => (0, lesson_to_skill_js_1.routeLessonToSkill)(lesson, (0, lesson_to_skill_js_1.skillsFromDirs)(dirs)));
|
|
1382
|
+
// 2026-10-01 (用户: 「复盘任务能否触发 loop?」): 教训**回流成下一轮的任务源** ✓ ——
|
|
1383
|
+
// 只"记下来"等于闭环断在这 ✗; 投进共享队列 ⇒ 空闲时排空 ⇒ 以**新一轮**浮现 ✓(绝不中途插队 ✓)。
|
|
1384
|
+
try {
|
|
1385
|
+
(0, background_notices_js_1.pushNotice)('review', `${lesson.title} —— ${String(lesson.body || '').replace(/\s+/g, ' ').slice(0, 120)}`);
|
|
1386
|
+
}
|
|
1387
|
+
catch { /* 回流失败不打断 */ }
|
|
1388
|
+
// ⓑ 接入**判断力系统**: 同一条教训也进 HumanJudgment(带 source/confidence/revisable ⇒ 可被后续演化取代 ✓)
|
|
1389
|
+
// 这样经验库与判断力库**同一份来源**, 判断力注入(gate)时就能用上 ✓
|
|
1390
|
+
Promise.resolve().then(() => __importStar(require('../pi-ecosystem-judgment/human-value-store.js'))).then((m) => m.storeHumanJudgment({
|
|
1391
|
+
decision: lesson.title,
|
|
1392
|
+
decision_type: 'modify',
|
|
1393
|
+
reasons: [lesson.body],
|
|
1394
|
+
values_derived: [],
|
|
1395
|
+
context: { domain: lesson.klass || 'general', complexity: 'simple', stakes: 'medium', time_pressure: 'low' },
|
|
1396
|
+
outcome: { approved: true },
|
|
1397
|
+
metadata: { source: 'trajectory', confidence: 0.7, revisable: true },
|
|
1398
|
+
})).catch(() => { });
|
|
1399
|
+
}
|
|
1400
|
+
catch { /* 这一段整体是锦上添花, 绝不外泄错误 */ }
|
|
1401
|
+
},
|
|
1402
|
+
}).then((r) => {
|
|
1403
|
+
// 结果**如实上屏** ✓: 审没审 / 写没写 / 为什么没写 —— 不再无声 ✓
|
|
1404
|
+
reviewLog(r?.reviewed
|
|
1405
|
+
? (r?.applied ? `已沉淀经验 ✓${r?.file ? ` → ${path.basename(String(r.file))}` : ''}` : `审了但没写: ${r?.reason || '无'}`)
|
|
1406
|
+
: `没审: ${r?.reason || '无'}`);
|
|
1407
|
+
}).catch((e) => {
|
|
1408
|
+
reviewLog(`失败(已如实记录, 不外泄错误): ${String(e?.message || e).slice(0, 160)}`);
|
|
1409
|
+
});
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
catch { /* 挂点自身失败也不影响主流程 */ }
|
|
957
1413
|
this.finishTrajectory(trajRec, loopResult.reply, loopResult.aiFailed ? 'error' : 'ok');
|
|
958
1414
|
return loopResult.reply;
|
|
959
1415
|
}
|
|
@@ -965,8 +1421,7 @@ class PiAgentSession {
|
|
|
965
1421
|
(0, injection_gate_js_1.recordJudgmentUsage)(this.judgmentGateNegativeUsedIds, { userInput: input, polarity: 'negative' }).catch((err) => console.warn('[PiAgent] recordJudgmentUsage (negative) failed:', err));
|
|
966
1422
|
}
|
|
967
1423
|
this.clearJudgmentGate();
|
|
968
|
-
this.
|
|
969
|
-
this.currentOnStream = null;
|
|
1424
|
+
this.runCtx = (0, run_context_js_1.createRunContext)(); // K2: 用完即清 (换新 Context, 不继承残留);
|
|
970
1425
|
this.reportUsageToContextManager();
|
|
971
1426
|
}
|
|
972
1427
|
}
|
|
@@ -974,7 +1429,7 @@ class PiAgentSession {
|
|
|
974
1429
|
console.log(`[PiAgent.promptStream] ENTRY, channelId=${channelId}, input chars=${input.length}`);
|
|
975
1430
|
this.minimaxAvailable = this.checkMinimax();
|
|
976
1431
|
console.log(`[PiAgent.promptStream] minimaxAvailable=${this.minimaxAvailable}`);
|
|
977
|
-
this.
|
|
1432
|
+
this.actor.state.channelId = channelId ?? this.actor.state.channelId;
|
|
978
1433
|
// 2026-08-08: 运行轨迹采集 (落盘 + OrbitDB, 失败静默) — 包裹 onStream 收集步骤事件
|
|
979
1434
|
const trajRec = await this.createTrajectoryRecorder(input, channelId);
|
|
980
1435
|
if (trajRec)
|
|
@@ -987,7 +1442,7 @@ class PiAgentSession {
|
|
|
987
1442
|
const userText = markerMatch ? markerMatch[1].trim() : input;
|
|
988
1443
|
const contextHint = markerMatch ? input.replace(markerMatch[0], '').trim() : '';
|
|
989
1444
|
console.log(`[PiAgent.promptStream] marker matched=${!!markerMatch}, userText chars=${userText.length}, contextHint chars=${contextHint.length}`);
|
|
990
|
-
this.
|
|
1445
|
+
this.pushHistory({
|
|
991
1446
|
role: 'user',
|
|
992
1447
|
content: userText
|
|
993
1448
|
});
|
|
@@ -997,7 +1452,7 @@ class PiAgentSession {
|
|
|
997
1452
|
// 让 agent 能"回忆起"之前 session 的记忆 (自动获取之前 session), 而非只靠启动时批量压缩.
|
|
998
1453
|
try {
|
|
999
1454
|
const { recallMemory } = await Promise.resolve().then(() => __importStar(require('./memory-recall.js')));
|
|
1000
|
-
const recalled = await recallMemory({ query: userText, agentId: this.
|
|
1455
|
+
const recalled = await recallMemory({ query: userText, agentId: this.actor.state.agentId || this.peerId || '' });
|
|
1001
1456
|
if (recalled) {
|
|
1002
1457
|
this.contextHintAddition = [this.contextHintAddition, recalled].filter(Boolean).join('\n\n');
|
|
1003
1458
|
}
|
|
@@ -1006,29 +1461,29 @@ class PiAgentSession {
|
|
|
1006
1461
|
onStream({ type: 'thinking', content: '🤔 开始思考...' });
|
|
1007
1462
|
if (!this.minimaxAvailable) {
|
|
1008
1463
|
const response = await this.handleFallback(userText);
|
|
1009
|
-
this.
|
|
1464
|
+
this.pushHistory({ role: 'assistant', content: response });
|
|
1010
1465
|
onStream({ type: 'done', content: '' });
|
|
1011
1466
|
this.reportUsageToContextManager();
|
|
1012
1467
|
this.finishTrajectory(trajRec, response);
|
|
1013
1468
|
return response;
|
|
1014
1469
|
}
|
|
1015
|
-
// P0 注入门: 缓存 onStream + signal, computeJudgmentGate 用
|
|
1016
|
-
|
|
1017
|
-
this.
|
|
1470
|
+
// P0 注入门: 缓存 onStream + signal, computeJudgmentGate 用 runCtx.eventSink 广播 phase
|
|
1471
|
+
// K2: eventSink + abortSignal 一起建进本轮 Context (只搬已外置的字段, 不抄未迁移的)
|
|
1472
|
+
this.runCtx = this.seedRunContext({ eventSink: onStream, abortSignal: signal ?? null });
|
|
1018
1473
|
await this.computeJudgmentGate(userText);
|
|
1019
1474
|
// M2.2 (2026-06-17): intent 分类 — 0 LLM 成本, 5 行 keyword 匹配
|
|
1020
1475
|
try {
|
|
1021
1476
|
const { classifyIntent, intentHint } = await Promise.resolve().then(() => __importStar(require('./intent-classifier.js')));
|
|
1022
|
-
this.
|
|
1023
|
-
this.currentIntentHint = intentHint(this.
|
|
1477
|
+
this.runCtx.intent = classifyIntent(userText);
|
|
1478
|
+
this.currentIntentHint = intentHint(this.runCtx.intent);
|
|
1024
1479
|
this.currentUserInput = userText;
|
|
1025
|
-
if (this.
|
|
1026
|
-
onStream({ type: 'phase', phase: 'intent_classified', detail: this.
|
|
1480
|
+
if (this.runCtx.intent !== 'chitchat') {
|
|
1481
|
+
onStream({ type: 'phase', phase: 'intent_classified', detail: this.runCtx.intent, content: '' });
|
|
1027
1482
|
}
|
|
1028
1483
|
}
|
|
1029
1484
|
catch (err) {
|
|
1030
1485
|
console.warn('[PiAgent] classifyIntent failed (non-fatal):', err);
|
|
1031
|
-
this.
|
|
1486
|
+
this.runCtx.intent = 'chitchat';
|
|
1032
1487
|
this.currentIntentHint = '';
|
|
1033
1488
|
}
|
|
1034
1489
|
// P1.1: 异步跑 Auto-Compact (LLM 摘要, 仅在 budget 超限时触发, 失败静默)
|
|
@@ -1045,8 +1500,8 @@ class PiAgentSession {
|
|
|
1045
1500
|
let bootstrapAddition = '';
|
|
1046
1501
|
try {
|
|
1047
1502
|
const ss = await (0, human_value_pipeline_js_1.onSessionStart)({
|
|
1048
|
-
channelId: this.
|
|
1049
|
-
agentId: this.
|
|
1503
|
+
channelId: this.actor.state.channelId || undefined,
|
|
1504
|
+
agentId: this.actor.state.agentId || undefined,
|
|
1050
1505
|
});
|
|
1051
1506
|
bootstrapAddition = ss.systemAddition || '';
|
|
1052
1507
|
}
|
|
@@ -1055,10 +1510,10 @@ class PiAgentSession {
|
|
|
1055
1510
|
}
|
|
1056
1511
|
// 2026-07-07 P1-B: 注入最近 5 条项目事件日志 (L2) — 让 LLM 知道项目状态/feature 变化
|
|
1057
1512
|
// 失败静默, append 到 bootstrapAddition 末尾 (超 800 字截断)
|
|
1058
|
-
if (this.
|
|
1513
|
+
if (this.actor.state.channelId) {
|
|
1059
1514
|
try {
|
|
1060
1515
|
const { getRecentEvents } = await Promise.resolve().then(() => __importStar(require('../bootstrap/event-log.js')));
|
|
1061
|
-
const events = await getRecentEvents(this.
|
|
1516
|
+
const events = await getRecentEvents(this.actor.state.channelId, 5);
|
|
1062
1517
|
if (events.length > 0) {
|
|
1063
1518
|
const eventBlock = [
|
|
1064
1519
|
'## 最近项目事件 (最近 5 条, 倒序)',
|
|
@@ -1073,7 +1528,7 @@ class PiAgentSession {
|
|
|
1073
1528
|
// 2026-07-07 P2-C: 注入项目当前状态 (L3) — 目标/约束/待办/已完成
|
|
1074
1529
|
try {
|
|
1075
1530
|
const { readState, formatStateForPrompt } = await Promise.resolve().then(() => __importStar(require('../bootstrap/project-state.js')));
|
|
1076
|
-
const state = await readState({ channelId: this.
|
|
1531
|
+
const state = await readState({ channelId: this.actor.state.channelId });
|
|
1077
1532
|
const stateText = formatStateForPrompt(state);
|
|
1078
1533
|
if (stateText) {
|
|
1079
1534
|
bootstrapAddition = (bootstrapAddition + '\n\n' + stateText).slice(-2500);
|
|
@@ -1085,7 +1540,7 @@ class PiAgentSession {
|
|
|
1085
1540
|
// 2026-07-07 P2-C: 向量检索 top-3 (L4) — 按当前 channelId + userText 找历史相关片段
|
|
1086
1541
|
try {
|
|
1087
1542
|
const { searchIndex } = await Promise.resolve().then(() => __importStar(require('../bootstrap/vector-index.js')));
|
|
1088
|
-
const indexName = `channel-${this.
|
|
1543
|
+
const indexName = `channel-${this.actor.state.channelId}`;
|
|
1089
1544
|
const results = await searchIndex({
|
|
1090
1545
|
indexName,
|
|
1091
1546
|
query: userText,
|
|
@@ -1146,13 +1601,12 @@ class PiAgentSession {
|
|
|
1146
1601
|
(0, monitor_gate_js_1.monitorAfterReply)(userText, pivotResult);
|
|
1147
1602
|
const stopStartTime = this.promptStartTime || Date.now();
|
|
1148
1603
|
(0, human_value_pipeline_js_1.onStop)({
|
|
1149
|
-
channelId: this.
|
|
1604
|
+
channelId: this.actor.state.channelId || 'unknown',
|
|
1150
1605
|
durationMs: Date.now() - stopStartTime,
|
|
1151
1606
|
usedJudgmentIds: [...this.judgmentGateUsedIds],
|
|
1152
1607
|
}).catch((err) => console.warn('[PiAgent] onStop failed:', err));
|
|
1153
1608
|
this.clearJudgmentGate();
|
|
1154
|
-
this.
|
|
1155
|
-
this.currentSignal = null;
|
|
1609
|
+
this.runCtx = (0, run_context_js_1.createRunContext)(); // K2: 用完即清 (换新 Context, 不继承残留);
|
|
1156
1610
|
this.bootstrapAddition = '';
|
|
1157
1611
|
this.contextHintAddition = '';
|
|
1158
1612
|
this.promptStartTime = 0;
|
|
@@ -1182,8 +1636,7 @@ class PiAgentSession {
|
|
|
1182
1636
|
}
|
|
1183
1637
|
catch (err) {
|
|
1184
1638
|
// abort 失败: 视作"已中断", 抛错让上层用 partial 兜底
|
|
1185
|
-
this.
|
|
1186
|
-
this.currentSignal = null;
|
|
1639
|
+
this.runCtx = (0, run_context_js_1.createRunContext)(); // K2: 用完即清 (换新 Context, 不继承残留);
|
|
1187
1640
|
throw err;
|
|
1188
1641
|
}
|
|
1189
1642
|
attempt++;
|
|
@@ -1221,8 +1674,8 @@ class PiAgentSession {
|
|
|
1221
1674
|
// 因为 messageHistory.push({role:'user'}) 在 promptStream 顶部已经做过, 重跑 runReActLoop 不会重复 push,
|
|
1222
1675
|
// 但 assistant 失败那条也别留 (留了会污染下一轮 LLM context).
|
|
1223
1676
|
// 简化: 重试前 pop 一次 assistant (如果最后一条是 assistant)
|
|
1224
|
-
if (this.messageHistory.length > 0 && this.messageHistory[this.messageHistory.length - 1].role === 'assistant') {
|
|
1225
|
-
this.
|
|
1677
|
+
if (this.actor.state.messageHistory.length > 0 && this.actor.state.messageHistory[this.actor.state.messageHistory.length - 1].role === 'assistant') {
|
|
1678
|
+
this.popHistory();
|
|
1226
1679
|
}
|
|
1227
1680
|
}
|
|
1228
1681
|
onStream({ type: 'done', content: '' });
|
|
@@ -1240,15 +1693,14 @@ class PiAgentSession {
|
|
|
1240
1693
|
// Bootstrap Stop hook: fire-and-forget 写本次 session 摘要
|
|
1241
1694
|
const stopStartTime = this.promptStartTime || Date.now();
|
|
1242
1695
|
(0, human_value_pipeline_js_1.onStop)({
|
|
1243
|
-
channelId: this.
|
|
1696
|
+
channelId: this.actor.state.channelId || 'unknown',
|
|
1244
1697
|
durationMs: Date.now() - stopStartTime,
|
|
1245
1698
|
usedJudgmentIds: [...this.judgmentGateUsedIds],
|
|
1246
1699
|
}).catch((err) => console.warn('[PiAgent] onStop failed:', err));
|
|
1247
1700
|
// 用完即清, 避免污染下一轮
|
|
1248
1701
|
this.clearJudgmentGate();
|
|
1249
|
-
this.
|
|
1702
|
+
this.runCtx = (0, run_context_js_1.createRunContext)(); // K2: 用完即清 (换新 Context, 不继承残留);
|
|
1250
1703
|
this.reportUsageToContextManager();
|
|
1251
|
-
this.currentSignal = null;
|
|
1252
1704
|
this.bootstrapAddition = '';
|
|
1253
1705
|
this.promptStartTime = 0;
|
|
1254
1706
|
// 2026-08-08: 轨迹收尾 — 落盘 + OrbitDB (失败静默, 不阻塞回复)
|
|
@@ -1256,7 +1708,7 @@ class PiAgentSession {
|
|
|
1256
1708
|
return result;
|
|
1257
1709
|
}
|
|
1258
1710
|
async promptWithPivotLoop(input, config, channelId) {
|
|
1259
|
-
this.
|
|
1711
|
+
this.actor.state.channelId = channelId ?? this.actor.state.channelId;
|
|
1260
1712
|
if (!this.minimaxAvailable) {
|
|
1261
1713
|
const response = await this.handleFallback(input);
|
|
1262
1714
|
return {
|
|
@@ -1288,8 +1740,8 @@ class PiAgentSession {
|
|
|
1288
1740
|
// M2.2 (2026-06-17): intent 分类 — pivot loop 也要拿到 hint
|
|
1289
1741
|
try {
|
|
1290
1742
|
const { classifyIntent, intentHint } = await Promise.resolve().then(() => __importStar(require('./intent-classifier.js')));
|
|
1291
|
-
this.
|
|
1292
|
-
this.currentIntentHint = intentHint(this.
|
|
1743
|
+
this.runCtx.intent = classifyIntent(input);
|
|
1744
|
+
this.currentIntentHint = intentHint(this.runCtx.intent);
|
|
1293
1745
|
this.currentUserInput = input;
|
|
1294
1746
|
}
|
|
1295
1747
|
catch (err) {
|
|
@@ -1312,6 +1764,10 @@ ${this.getToolDefinitions()}
|
|
|
1312
1764
|
|
|
1313
1765
|
${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
1314
1766
|
|
|
1767
|
+
${exports.PROACTIVE_WORK_DISCIPLINE}
|
|
1768
|
+
|
|
1769
|
+
${await this.renderActivePlansSection()}
|
|
1770
|
+
|
|
1315
1771
|
工作模式:
|
|
1316
1772
|
1. 理解用户自然语言请求
|
|
1317
1773
|
2. 分析需要哪些工具来完成
|
|
@@ -1332,21 +1788,21 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
1332
1788
|
- 工具调用前可以简短思考 (1-2 句话), 但**不要写长篇 thinking** (会撞 max_tokens)
|
|
1333
1789
|
- 工具调用后必须等结果, 不要在同一个回复里继续输出
|
|
1334
1790
|
- <final gen> 只在**真完成所有任务**时输出, 不要在工具调用前/中输出${this.judgmentGateAddition}${this.contextHintAddition}`;
|
|
1335
|
-
// 2026-06-15: 把
|
|
1791
|
+
// 2026-06-15: 把 runCtx.eventSink 传给 loop, 让 step-timeline 在 pivot 循环里也能 emit step_start/done
|
|
1336
1792
|
// 之前 loop.execute() 不接 streamCallback, 导致 step-timeline 只能看到老 runReActLoop 路径
|
|
1337
1793
|
// promptWithPivotLoop 路径 0 step events — UI 显示 timeline 但永远是空
|
|
1338
1794
|
// 2026-06-17: 透传 signal 让 abort 工作 — loop.execute() 当前不接 signal 参数,
|
|
1339
1795
|
// 所以 abort 行为通过 this.currentSignal 共享给 loop 内部读 (后续 M3.2 接 task plan 时一起加)
|
|
1340
1796
|
// 2026-07-06: pivot 内 token 累计到 ~70% 时回调 (workflow-pivot-loop.ts line 270+).
|
|
1341
|
-
// pivot 的 messageHistory 是 process-local, 不和 pi-sdk 的
|
|
1797
|
+
// pivot 的 messageHistory 是 process-local, 不和 pi-sdk 的 actor 的 messageHistory 同步.
|
|
1342
1798
|
// 真正折叠需要把 pi-sdk 历史灌回 pivot 的 history 数组 — 侵入较大.
|
|
1343
1799
|
// 当前 priority: 临时传空实现, 让 budget 公式放够 (workflow-pivot-loop.ts line 220)
|
|
1344
1800
|
// 不再撞预算. 这条路径留作技术债.
|
|
1345
1801
|
// 2026-07-17 Bug 1 修: 注入 messageHistory (hydrateMessageHistory 从 session JSON 回灌的) 到 system prompt
|
|
1346
|
-
// pivot loop execute() 内部自己维护 messageHistory, 跟 pi-sdk 的
|
|
1802
|
+
// pivot loop execute() 内部自己维护 messageHistory, 跟 pi-sdk 的 actor 的 messageHistory 隔离,
|
|
1347
1803
|
// 不注入的话 LLM 看不到历史对话, 每次都是新对话.
|
|
1348
1804
|
const historyLines = [];
|
|
1349
|
-
const historyToInject = this.messageHistory.slice(-20, -1);
|
|
1805
|
+
const historyToInject = this.actor.state.messageHistory.slice(-20, -1);
|
|
1350
1806
|
for (const m of historyToInject) {
|
|
1351
1807
|
const roleLabel = m.role === 'user' ? '用户' : m.role === 'assistant' ? '你' : m.role === 'tool' ? '工具结果' : m.role;
|
|
1352
1808
|
const text = (m.content || '').slice(0, 2000);
|
|
@@ -1359,9 +1815,9 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
1359
1815
|
const onCompact = async () => {
|
|
1360
1816
|
// no-op (best-effort hook for future pi-sdk/pivot history sync)
|
|
1361
1817
|
};
|
|
1362
|
-
const result = await loop.execute(input, llm, systemPrompt + historyBlock, this.
|
|
1818
|
+
const result = await loop.execute(input, llm, systemPrompt + historyBlock, this.runCtx.eventSink ?? undefined, this.runCtx.abortSignal ?? undefined, onCompact);
|
|
1363
1819
|
if (result.response) {
|
|
1364
|
-
this.
|
|
1820
|
+
this.pushHistory({ role: 'assistant', content: result.response });
|
|
1365
1821
|
}
|
|
1366
1822
|
// 回溯 + 清场
|
|
1367
1823
|
if (this.judgmentGateUsedIds.length > 0) {
|
|
@@ -1377,7 +1833,9 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
1377
1833
|
*/
|
|
1378
1834
|
_harness = null;
|
|
1379
1835
|
/** M2 绑定 GoalStore 后填真值; 在此之前为空 (事件里 goalId 字段已就位) */
|
|
1380
|
-
|
|
1836
|
+
// **K5 步骤③/⑧**: 会话的 Goal 默认绑定本体在 `actor.state.goalBinding` (访问器已删, 直接读本体)。
|
|
1837
|
+
// leo 口径不变: 这只是默认值; 每次执行开始必须把最终绑定写进 RunContext/Run 记录,
|
|
1838
|
+
// 运行中重绑必须走显式 Goal Binding 操作, 不许靠裸字段隐式生效。
|
|
1381
1839
|
/** 2026-09-16 (M2): 本次执行是"从 checkpoint 恢复"的 runId (非空 = 恢复模式, 不再新建 run) */
|
|
1382
1840
|
resumeRunId = '';
|
|
1383
1841
|
resumePlan = null;
|
|
@@ -1400,7 +1858,7 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
1400
1858
|
}
|
|
1401
1859
|
/** CLI / Web 注入"当前目标" (有 goalId 就在该 Goal 下执行) */
|
|
1402
1860
|
setGoalId(goalId) {
|
|
1403
|
-
this.
|
|
1861
|
+
this.actor.state.goalBinding = String(goalId || '');
|
|
1404
1862
|
}
|
|
1405
1863
|
/** 核心状态迁移的兜底: 失败 → 记降级 + 交给循环顶部的持久化硬闸 (不吞错) */
|
|
1406
1864
|
async safeSetRunStatus(runId, to) {
|
|
@@ -1419,13 +1877,13 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
1419
1877
|
* 这是"错误恢复闭环"的运行时接线点 (此前 recordRecovery / repeatedFailureCount 只是数据结构)。
|
|
1420
1878
|
*/
|
|
1421
1879
|
async wireToolFailure(tool, errorText, args) {
|
|
1422
|
-
if (!this.
|
|
1880
|
+
if (!this.actor.state.activeRun)
|
|
1423
1881
|
return;
|
|
1424
1882
|
const cls = (0, run_store_js_1.classifyError)(errorText);
|
|
1425
1883
|
const digest = (0, run_store_js_1.argsDigestOf)(args);
|
|
1426
1884
|
let repeats = 1;
|
|
1427
1885
|
try {
|
|
1428
|
-
const rec = await (0, run_store_js_1.readRun)(this.
|
|
1886
|
+
const rec = await (0, run_store_js_1.readRun)(this.actor.state.activeRun);
|
|
1429
1887
|
// recordStep 已经把**这次**失败记进去了, 所以"连续失败次数"直接就是当前值 (不再 +1)
|
|
1430
1888
|
const seen = rec ? (0, run_store_js_1.repeatedFailureCount)(rec, tool, digest) : 0;
|
|
1431
1889
|
repeats = Math.max(seen, 1);
|
|
@@ -1436,7 +1894,7 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
1436
1894
|
: cls === 'external_no_reply' ? 'pause'
|
|
1437
1895
|
: 'retry';
|
|
1438
1896
|
try {
|
|
1439
|
-
await (0, run_store_js_1.recordRecovery)(this.
|
|
1897
|
+
await (0, run_store_js_1.recordRecovery)(this.actor.state.activeRun, {
|
|
1440
1898
|
errorClass: cls,
|
|
1441
1899
|
message: `${tool}: ${errorText}`.slice(0, 200),
|
|
1442
1900
|
action,
|
|
@@ -1445,20 +1903,20 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
1445
1903
|
});
|
|
1446
1904
|
}
|
|
1447
1905
|
catch (err) {
|
|
1448
|
-
await (0, run_store_js_1.recordDegradation)({ kind: 'core', op: 'pi-sdk.recordRecovery', runId: this.
|
|
1906
|
+
await (0, run_store_js_1.recordDegradation)({ kind: 'core', op: 'pi-sdk.recordRecovery', runId: this.actor.state.activeRun, message: String(err?.message || err) }).catch(() => { });
|
|
1449
1907
|
}
|
|
1450
1908
|
// 外部无响应 → awaiting_external (不算失败; 等回话后回到 running)
|
|
1451
1909
|
if (cls === 'external_no_reply') {
|
|
1452
1910
|
this.awaitingExternal = true;
|
|
1453
|
-
await this.safeSetRunStatus(this.
|
|
1911
|
+
await this.safeSetRunStatus(this.actor.state.activeRun, 'awaiting_external');
|
|
1454
1912
|
// 2026-09-16 (2-C.4): 把"在等什么"写成持久化事实 (来源/关联/过期), 否则真实回包到了也不知道该唤醒谁。
|
|
1455
1913
|
try {
|
|
1456
|
-
if (this.
|
|
1914
|
+
if (this.actor.state.goalBinding) {
|
|
1457
1915
|
const { bindExternalWait, newContinuationId, defaultWaitExpiry } = await Promise.resolve().then(() => __importStar(require('./external-events.js')));
|
|
1458
1916
|
const isDelegate = /delegate/i.test(String(tool || ''));
|
|
1459
|
-
await bindExternalWait(this.
|
|
1460
|
-
requestId: this.pendingExternalRequestId || `${this.
|
|
1461
|
-
continuationId: newContinuationId(this.
|
|
1917
|
+
await bindExternalWait(this.actor.state.goalBinding, {
|
|
1918
|
+
requestId: this.pendingExternalRequestId || `${this.actor.state.activeRun}:${Date.now().toString(36)}`,
|
|
1919
|
+
continuationId: newContinuationId(this.actor.state.goalBinding),
|
|
1462
1920
|
expectedSource: isDelegate ? 'delegate' : 'p2p',
|
|
1463
1921
|
expectedEvent: 'result',
|
|
1464
1922
|
createdAt: new Date().toISOString(),
|
|
@@ -1468,31 +1926,31 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
1468
1926
|
}
|
|
1469
1927
|
}
|
|
1470
1928
|
catch (e) {
|
|
1471
|
-
await (0, run_store_js_1.recordDegradation)({ kind: 'core', op: 'pi-sdk.bindExternalWait', runId: this.
|
|
1929
|
+
await (0, run_store_js_1.recordDegradation)({ kind: 'core', op: 'pi-sdk.bindExternalWait', runId: this.actor.state.activeRun, message: String(e?.message || e) }).catch(() => { });
|
|
1472
1930
|
}
|
|
1473
1931
|
return;
|
|
1474
1932
|
}
|
|
1475
1933
|
// 鉴权类: 不重试, 直接交人
|
|
1476
1934
|
if (cls === 'auth') {
|
|
1477
1935
|
this.breakerReason = `鉴权类错误不重试 (${tool}): ${errorText.slice(0, 120)}`;
|
|
1478
|
-
await this.safeSetRunStatus(this.
|
|
1936
|
+
await this.safeSetRunStatus(this.actor.state.activeRun, 'needs_human');
|
|
1479
1937
|
return;
|
|
1480
1938
|
}
|
|
1481
1939
|
// 重复失败熔断: 同一工具 + 同一组参数连续失败达 3 次
|
|
1482
1940
|
if (repeats >= MAX_SAME_TOOL_FAILURES) {
|
|
1483
1941
|
this.breakerReason = `同一工具 ${tool} 连续失败 ${repeats} 次 (${cls}) → 熔断, 不再重试`;
|
|
1484
|
-
await this.safeSetRunStatus(this.
|
|
1942
|
+
await this.safeSetRunStatus(this.actor.state.activeRun, 'needs_human');
|
|
1485
1943
|
}
|
|
1486
1944
|
}
|
|
1487
1945
|
getRunId() {
|
|
1488
|
-
return this.
|
|
1946
|
+
return this.actor.state.activeRun;
|
|
1489
1947
|
}
|
|
1490
1948
|
/**
|
|
1491
1949
|
* 2026-09-16 (M2-B): **上一次**运行的 runId —— prompt 收尾时 `currentRunId` 会被清空,
|
|
1492
1950
|
* 而 Supervisor / 控制面要在运行结束后才知道"刚才跑的是哪条 run" (否则会拿旧 run 做决策)。
|
|
1493
1951
|
*/
|
|
1494
1952
|
getLastRunId() {
|
|
1495
|
-
return this.lastRunId || this.
|
|
1953
|
+
return this.lastRunId || this.actor.state.activeRun;
|
|
1496
1954
|
}
|
|
1497
1955
|
/**
|
|
1498
1956
|
* 2026-09-16 (M2): 从 checkpoint 恢复一次运行 —— **不是**重发原 prompt。
|
|
@@ -1539,7 +1997,7 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
1539
1997
|
}
|
|
1540
1998
|
this.resumeRunId = runId;
|
|
1541
1999
|
this.resumePlan = prep.plan;
|
|
1542
|
-
this.
|
|
2000
|
+
this.actor.state.goalBinding = prep.plan.goalId || this.actor.state.goalBinding;
|
|
1543
2001
|
try {
|
|
1544
2002
|
const reply = await this.prompt((0, run_store_js_1.buildResumeInstruction)(prep.plan), {});
|
|
1545
2003
|
return { ok: true, reply, ...(modelDrift ? { modelDrift } : {}), ...(modelApplied ? { modelApplied } : {}) };
|
|
@@ -1558,8 +2016,8 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
1558
2016
|
// pre-tool-validator 4 步链 (modeGate/blacklist/shell-guard/schema), 经 human-value-pipeline 包装
|
|
1559
2017
|
preToolUse: async (o) => (0, human_value_pipeline_js_1.onPreToolUse)({ tool: o.tool, args: o.args, permissionMode: o.permissionMode }),
|
|
1560
2018
|
// 事件写 Run: 观测级 (记账失败不改变已做出的决策)
|
|
1561
|
-
events: (e) => { if (this.
|
|
1562
|
-
void (0, run_store_js_1.recordHarnessEvent)(this.
|
|
2019
|
+
events: (e) => { if (this.actor.state.activeRun)
|
|
2020
|
+
void (0, run_store_js_1.recordHarnessEvent)(this.actor.state.activeRun, e); },
|
|
1563
2021
|
});
|
|
1564
2022
|
}
|
|
1565
2023
|
return this._harness;
|
|
@@ -1567,10 +2025,10 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
1567
2025
|
/** 每个生命周期事件都带上的运行身份 (runId / goalId / agentId / channelId / surface) */
|
|
1568
2026
|
harnessCtx() {
|
|
1569
2027
|
return {
|
|
1570
|
-
runId: this.
|
|
1571
|
-
goalId: this.
|
|
1572
|
-
agentId: this.
|
|
1573
|
-
channelId: this.
|
|
2028
|
+
runId: this.actor.state.activeRun || undefined,
|
|
2029
|
+
goalId: this.actor.state.goalBinding || undefined,
|
|
2030
|
+
agentId: this.actor.state.agentId || undefined,
|
|
2031
|
+
channelId: this.actor.state.channelId || undefined,
|
|
1574
2032
|
surface: this.runSurface,
|
|
1575
2033
|
};
|
|
1576
2034
|
}
|
|
@@ -1596,6 +2054,9 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
1596
2054
|
const MAX_TOOL_CALLS_PER_LOOP = 25; // 单轮循环总工具调用上限 → 注入 hint
|
|
1597
2055
|
let totalToolCallsThisLoop = 0;
|
|
1598
2056
|
const lastNTools = []; // 最近 MAX_IDEMPOTENT_TOOL 次工具名, 检测重复
|
|
2057
|
+
// 2026-10-01: 工具停滞观测状态 (分类 + 温和引导 + 重复结果引用) —— 见 agents/tool-loop-guard.ts
|
|
2058
|
+
// 与上面的"同工具 5 次就提示"不同: 它按 (工具名 + 参数 + 结果) 签名判定, 并抓 A→B→A→B 的循环
|
|
2059
|
+
const stallState = new tool_loop_guard_js_1.LoopStallState();
|
|
1599
2060
|
// 2026-08-10: unreported 循环逃生门 — LLM 反复不把工具结果写进回复时, 3 次后强制 final (不死板)
|
|
1600
2061
|
const MAX_UNREPORTED_RETRIES = 3;
|
|
1601
2062
|
let unreportedRetries = 0;
|
|
@@ -1634,26 +2095,26 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
1634
2095
|
this.awaitingExternal = false;
|
|
1635
2096
|
if (this.resumeRunId) {
|
|
1636
2097
|
// ── 恢复模式: 复用原来的 runId, 不新建 run (历史保留) ──
|
|
1637
|
-
this.
|
|
2098
|
+
this.actor.state.activeRun = this.resumeRunId;
|
|
1638
2099
|
this.lastRunId = this.resumeRunId;
|
|
1639
2100
|
try {
|
|
1640
|
-
await (0, run_store_js_1.markRunRunning)(this.
|
|
2101
|
+
await (0, run_store_js_1.markRunRunning)(this.actor.state.activeRun);
|
|
1641
2102
|
}
|
|
1642
2103
|
catch (err) {
|
|
1643
2104
|
runPersistenceFailure = `恢复时状态迁移失败 (recovering → running): ${String(err?.message || err).slice(0, 180)}`;
|
|
1644
2105
|
}
|
|
1645
2106
|
const doneN = this.resumePlan?.completedSteps.length ?? 0;
|
|
1646
2107
|
const guards = this.resumePlan?.replayGuards.length ?? 0;
|
|
1647
|
-
onStream?.({ type: 'status', internal: true, content: `♻️ 从 checkpoint 恢复运行 ${this.
|
|
2108
|
+
onStream?.({ type: 'status', internal: true, content: `♻️ 从 checkpoint 恢复运行 ${this.actor.state.activeRun} (已完成 ${doneN} 步, 非幂等重放守卫 ${guards} 条)`, tool: 'harness' });
|
|
1648
2109
|
}
|
|
1649
2110
|
else {
|
|
1650
2111
|
// 2026-09-16 (M2): 目标绑定 —— 有 goalId 就在该 Goal 下执行; 没有就建 Goal 再建 Run。
|
|
1651
2112
|
// 延续规则 (确定性, 不靠猜): 该 channel/agent 上已有 open/active Goal, 且它的上一次执行**没收尾**
|
|
1652
2113
|
// (interrupted/stalled/needs_human/paused/awaiting_external/recovering) → 继续该 Goal; 否则新建。
|
|
1653
|
-
let boundGoalId = this.
|
|
2114
|
+
let boundGoalId = this.actor.state.goalBinding;
|
|
1654
2115
|
if (!boundGoalId) {
|
|
1655
2116
|
try {
|
|
1656
|
-
const active = await (0, goal_store_js_1.findActiveGoal)({ channelId: this.
|
|
2117
|
+
const active = await (0, goal_store_js_1.findActiveGoal)({ channelId: this.actor.state.channelId || undefined, agentId: this.actor.state.agentId || undefined });
|
|
1657
2118
|
if (active?.currentRunId) {
|
|
1658
2119
|
const prev = await (0, run_store_js_1.readRun)(active.currentRunId);
|
|
1659
2120
|
const unfinished = prev && ['interrupted', 'stalled', 'needs_human', 'paused', 'awaiting_external', 'recovering'].includes(prev.status);
|
|
@@ -1668,8 +2129,8 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
1668
2129
|
try {
|
|
1669
2130
|
const g = await (0, goal_store_js_1.createGoal)({
|
|
1670
2131
|
objective: this.currentUserInput || '(未记录目标)',
|
|
1671
|
-
channelId: this.
|
|
1672
|
-
agentId: this.
|
|
2132
|
+
channelId: this.actor.state.channelId || undefined,
|
|
2133
|
+
agentId: this.actor.state.agentId || undefined,
|
|
1673
2134
|
createdBy: this.runSurface,
|
|
1674
2135
|
});
|
|
1675
2136
|
boundGoalId = g.goalId;
|
|
@@ -1678,25 +2139,25 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
1678
2139
|
console.warn('[PiAgent] 创建 Goal 失败 (无目标也要有运行记录):', err?.message);
|
|
1679
2140
|
}
|
|
1680
2141
|
}
|
|
1681
|
-
this.
|
|
2142
|
+
this.actor.state.goalBinding = boundGoalId;
|
|
1682
2143
|
}
|
|
1683
2144
|
try {
|
|
1684
2145
|
const rec = await (0, run_store_js_1.startRun)({
|
|
1685
2146
|
surface: this.runSurface,
|
|
1686
2147
|
goal: this.currentUserInput || '(未记录目标)',
|
|
1687
2148
|
goalId: boundGoalId || undefined,
|
|
1688
|
-
channelId: this.
|
|
1689
|
-
agentId: this.
|
|
2149
|
+
channelId: this.actor.state.channelId || undefined,
|
|
2150
|
+
agentId: this.actor.state.agentId || undefined,
|
|
1690
2151
|
modelConfig: await this.runModelSnapshot(),
|
|
1691
2152
|
});
|
|
1692
|
-
this.
|
|
2153
|
+
this.actor.state.activeRun = rec.runId;
|
|
1693
2154
|
this.lastRunId = rec.runId;
|
|
1694
|
-
this.
|
|
1695
|
-
if (this.
|
|
2155
|
+
this.actor.state.goalBinding = rec.goalId || this.actor.state.goalBinding;
|
|
2156
|
+
if (this.actor.state.goalBinding) {
|
|
1696
2157
|
// Run → Goal 反查链: runId → goalId → objective / success criteria
|
|
1697
|
-
await (0, goal_store_js_1.attachRun)(this.
|
|
2158
|
+
await (0, goal_store_js_1.attachRun)(this.actor.state.goalBinding, rec.runId).catch((err) => console.warn('[PiAgent] attachRun 失败:', err?.message));
|
|
1698
2159
|
}
|
|
1699
|
-
onStream?.({ type: 'status', internal: true, content: `🧷 运行已登记 (run=${rec.runId}${this.
|
|
2160
|
+
onStream?.({ type: 'status', internal: true, content: `🧷 运行已登记 (run=${rec.runId}${this.actor.state.goalBinding ? `, goal=${this.actor.state.goalBinding}` : ''}, 预算 ${rec.budget.maxSteps} 步 / ${Math.round(rec.budget.deadlineMs / 60000)} 分钟)`, tool: 'harness' });
|
|
1700
2161
|
}
|
|
1701
2162
|
catch (err) {
|
|
1702
2163
|
// 核心写失败: 不再 warn 后继续 —— 没有运行记录就不执行 (strict 模式默认如此)
|
|
@@ -1726,18 +2187,18 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
1726
2187
|
break;
|
|
1727
2188
|
}
|
|
1728
2189
|
// 2026-09-16: 预算闸门 (持久化 harness 的约束面) —— 到点必须**如实**终止, 不许静默算完成
|
|
1729
|
-
if (this.
|
|
2190
|
+
if (this.actor.state.activeRun) {
|
|
1730
2191
|
try {
|
|
1731
|
-
const rec = await (0, run_store_js_1.readRun)(this.
|
|
2192
|
+
const rec = await (0, run_store_js_1.readRun)(this.actor.state.activeRun);
|
|
1732
2193
|
if (!rec)
|
|
1733
|
-
throw new Error(`运行记录读不到: ${this.
|
|
2194
|
+
throw new Error(`运行记录读不到: ${this.actor.state.activeRun}`);
|
|
1734
2195
|
// 2026-09-16 (M5): 外部控制面 (CLI /pause /abort, Web API) 改过状态 → 如实停在那儿。
|
|
1735
2196
|
// 不覆盖成 done/failed: 人按下暂停就是暂停, 人按下中止就是中止。
|
|
1736
2197
|
if (rec.status === 'paused' || rec.status === 'aborted') {
|
|
1737
2198
|
runExternallyPaused = rec.status === 'paused';
|
|
1738
2199
|
runExternallyAborted = rec.status === 'aborted';
|
|
1739
2200
|
runStopReason = `外部请求: ${rec.status}`;
|
|
1740
|
-
onStream?.({ type: 'error', content: `⏹️ 运行被外部${rec.status === 'paused' ? '暂停' : '中止'} (run=${this.
|
|
2201
|
+
onStream?.({ type: 'error', content: `⏹️ 运行被外部${rec.status === 'paused' ? '暂停' : '中止'} (run=${this.actor.state.activeRun})`, tool: 'harness' });
|
|
1741
2202
|
finalResponse = finalResponse || `(运行已${rec.status === 'paused' ? '暂停' : '中止'})`;
|
|
1742
2203
|
break;
|
|
1743
2204
|
}
|
|
@@ -1760,7 +2221,18 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
1760
2221
|
}
|
|
1761
2222
|
// 停止条件 1: max turns (fail-safe 10000, 正常任务永远跑不到)
|
|
1762
2223
|
// 2026-07-01 (v0.2.4 子任务 1): 委托给 react-loop.decideMaxIterations 纯函数
|
|
1763
|
-
|
|
2224
|
+
// 2026-10-01 (落实②: 可退还的迭代预算): 惩罚零碎调用, **奖励批处理** ——
|
|
2225
|
+
// 程序化工具(execute_code, 一次能顶多次)调用后归还一次迭代 ⇒ 有效寿命被延长。
|
|
2226
|
+
// 退出条件仍由 decideMaxIterations 决定(读**净**用量), 不做硬刹车。
|
|
2227
|
+
if (iteration === 0 || !this.iterBudget)
|
|
2228
|
+
this.iterBudget = new iteration_budget_js_1.IterationBudget(this.MAX_REACT_ITERATIONS);
|
|
2229
|
+
const iterBudget = this.iterBudget;
|
|
2230
|
+
iterBudget.consume();
|
|
2231
|
+
if (iterBudget.warn(0.8) && !this.iterBudgetWarned) {
|
|
2232
|
+
this.iterBudgetWarned = true;
|
|
2233
|
+
onStream?.({ type: 'status', internal: true, content: `⏳ 迭代预算已用 ${iterBudget.describe()} (批量工具会退还)` });
|
|
2234
|
+
}
|
|
2235
|
+
const maxIterDecision = (0, react_loop_js_1.decideMaxIterations)(iterBudget.used, iterBudget.maxTotal);
|
|
1764
2236
|
if (maxIterDecision.shouldExit) {
|
|
1765
2237
|
console.warn(`[PiAgent] 达到最大循环数 ${this.MAX_REACT_ITERATIONS}, 强制终止 (fail-safe)`);
|
|
1766
2238
|
onStream?.({ type: 'error', content: `⏹️ 达到最大循环数 (${this.MAX_REACT_ITERATIONS}, fail-safe)`, tool: 'loop' });
|
|
@@ -1778,14 +2250,14 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
1778
2250
|
if (totalToolCallsThisLoop >= MAX_TOOL_CALLS_PER_LOOP) {
|
|
1779
2251
|
console.warn(`[PiAgent] 单轮工具调用已达 ${MAX_TOOL_CALLS_PER_LOOP}, 注入 hint 让 LLM 总结`);
|
|
1780
2252
|
onStream?.({ type: 'error', content: `⏹️ 工具调用已达上限 (${MAX_TOOL_CALLS_PER_LOOP}), 请基于已有结果回答`, tool: 'loop' });
|
|
1781
|
-
this.
|
|
2253
|
+
this.pushHistory({ role: 'system', content: `[注意] 你已连续调用 ${MAX_TOOL_CALLS_PER_LOOP} 次工具。请基于已有结果直接回答用户, 不要再次调用任何工具。在回答末尾加 <final gen> 标记结束。` });
|
|
1782
2254
|
totalToolCallsThisLoop = 0; // 重置计数器, 只防连续死循环
|
|
1783
2255
|
}
|
|
1784
2256
|
if (lastNTools.length >= MAX_IDEMPOTENT_TOOL && new Set(lastNTools).size === 1) {
|
|
1785
2257
|
const repeatedTool = lastNTools[0];
|
|
1786
2258
|
console.warn(`[PiAgent] 同工具 ${repeatedTool} 连续成功调 ${MAX_IDEMPOTENT_TOOL} 次, 注入 hint 让 LLM 总结`);
|
|
1787
2259
|
onStream?.({ type: 'error', content: `⏹️ 工具 ${repeatedTool} 重复调用 ${MAX_IDEMPOTENT_TOOL} 次, 请基于已有结果回答`, tool: 'loop' });
|
|
1788
|
-
this.
|
|
2260
|
+
this.pushHistory({ role: 'system', content: `[注意] 你已连续 ${MAX_IDEMPOTENT_TOOL} 次调用 ${repeatedTool}。请基于已有结果直接回答用户, 不要再次调用任何工具。在回答末尾加 <final gen> 标记结束。` });
|
|
1789
2261
|
lastNTools.length = 0; // 重置计数器
|
|
1790
2262
|
// 不 break — 让 LLM 在下一轮用已有信息回答
|
|
1791
2263
|
}
|
|
@@ -1889,6 +2361,10 @@ ${toolDefs}
|
|
|
1889
2361
|
|
|
1890
2362
|
${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
1891
2363
|
|
|
2364
|
+
${exports.PROACTIVE_WORK_DISCIPLINE}
|
|
2365
|
+
|
|
2366
|
+
${await this.renderActivePlansSection()}
|
|
2367
|
+
|
|
1892
2368
|
工作模式:
|
|
1893
2369
|
1. 理解用户自然语言请求
|
|
1894
2370
|
2. 分析需要哪些工具来完成
|
|
@@ -1984,7 +2460,7 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
1984
2460
|
break;
|
|
1985
2461
|
}
|
|
1986
2462
|
// 把错误当成 tool 结果 push 进 history, 这样下一轮 LLM 看到错误能调整
|
|
1987
|
-
this.
|
|
2463
|
+
this.pushHistory({
|
|
1988
2464
|
role: 'system',
|
|
1989
2465
|
content: `[Loop 错误恢复 ${totalErrors}/${this.MAX_TOTAL_ERRORS}] ${aiFailureReason}\n\n请基于上轮工具结果继续完成任务, 不要重复调用同一失败操作. 如果工具已成功执行, 请基于 result.output 给用户总结; 如果工具失败, 请换其他方式或重试.`
|
|
1990
2466
|
});
|
|
@@ -2057,7 +2533,7 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
2057
2533
|
}
|
|
2058
2534
|
if (toolCalls.length > 0) {
|
|
2059
2535
|
// 把原始 LLM 回复 push 进 history (仅一次)
|
|
2060
|
-
this.
|
|
2536
|
+
this.pushHistory({
|
|
2061
2537
|
role: 'assistant',
|
|
2062
2538
|
content: reply,
|
|
2063
2539
|
toolCalls: toolCalls.length > 1 ? toolCalls : [toolCalls[0]],
|
|
@@ -2087,12 +2563,12 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
2087
2563
|
consecutiveErrors++;
|
|
2088
2564
|
totalErrors++;
|
|
2089
2565
|
const errorResult = { success: false, error: `未知工具: ${toolCall.name}` };
|
|
2090
|
-
this.
|
|
2566
|
+
this.pushHistory({ role: 'tool', content: JSON.stringify(errorResult), toolResult: errorResult });
|
|
2091
2567
|
this.logToHarness(toolCall.name, toolCall.args, errorResult);
|
|
2092
2568
|
// 2026-07-28: 注入 Reflection 帮助 LLM 理解错误
|
|
2093
2569
|
const obs = (0, error_classifier_js_1.buildObservation)(toolCall.name, toolCall.args, errorResult);
|
|
2094
2570
|
const ref = (0, error_classifier_js_1.buildReflection)(toolCall.name, errorResult.error, totalErrors, lastFailedToolCount);
|
|
2095
|
-
this.
|
|
2571
|
+
this.pushHistory({ role: 'system', content: (0, error_classifier_js_1.formatObservationWithReflection)(obs, ref) });
|
|
2096
2572
|
if (onStream)
|
|
2097
2573
|
onStream({ type: 'status', content: `💡 Reflection: ${obs.summary}`, tool: 'system' });
|
|
2098
2574
|
console.warn(`[PiAgent] 未知工具: ${toolCall.name} (累计 ${totalErrors}/${this.MAX_TOTAL_ERRORS}),跳过并继续`);
|
|
@@ -2126,7 +2602,7 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
2126
2602
|
consecutiveErrors++;
|
|
2127
2603
|
totalErrors++;
|
|
2128
2604
|
const denyResultMsg = { success: false, error: `拒绝: [${toolDecision.rejectedBy || 'deny-pipeline'}] ${toolDecision.reason}` };
|
|
2129
|
-
this.
|
|
2605
|
+
this.pushHistory({ role: 'tool', content: JSON.stringify(denyResultMsg), toolResult: denyResultMsg });
|
|
2130
2606
|
this.logToHarness(toolCall.name, toolCall.args, denyResultMsg);
|
|
2131
2607
|
return;
|
|
2132
2608
|
}
|
|
@@ -2138,7 +2614,7 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
2138
2614
|
? `Harness gate 拒绝 (${toolDecision.rejectedBy}): ${toolDecision.reason || '未通过安全校验'}`
|
|
2139
2615
|
: `PreToolUse 拒绝: ${toolDecision.reason || '未通过安全校验'}`,
|
|
2140
2616
|
};
|
|
2141
|
-
this.
|
|
2617
|
+
this.pushHistory({ role: 'tool', content: JSON.stringify(deniedResult), toolResult: deniedResult });
|
|
2142
2618
|
this.logToHarness(toolCall.name, toolCall.args, deniedResult);
|
|
2143
2619
|
if (onStream) {
|
|
2144
2620
|
const gateName = isGateDeny ? `Harness ${toolDecision.rejectedBy}` : (isHarnessError ? '核心约束层' : 'PreToolUse');
|
|
@@ -2167,13 +2643,13 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
2167
2643
|
? `[注意] 连续 ${consecutiveErrors} 次工具调用因约束层失效被阻止. 请换其他工具或直接回答用户, 末尾加 <final gen>.`
|
|
2168
2644
|
: `[注意] 连续 ${consecutiveErrors} 次工具调用被系统拒绝. 请换其他工具或直接回答用户, 末尾加 <final gen>.`;
|
|
2169
2645
|
if (lastFailedToolCount >= MAX_SAME_TOOL_FAILURES) {
|
|
2170
|
-
this.
|
|
2646
|
+
this.pushHistory({ role: 'system', content: systemMaxMsg });
|
|
2171
2647
|
lastFailedTool = '';
|
|
2172
2648
|
lastFailedToolCount = 0;
|
|
2173
2649
|
consecutiveErrors = 0;
|
|
2174
2650
|
}
|
|
2175
2651
|
else if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
|
|
2176
|
-
this.
|
|
2652
|
+
this.pushHistory({ role: 'system', content: systemConsecMsg });
|
|
2177
2653
|
consecutiveErrors = 0;
|
|
2178
2654
|
}
|
|
2179
2655
|
return;
|
|
@@ -2199,7 +2675,7 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
2199
2675
|
}
|
|
2200
2676
|
let result = replaySkip
|
|
2201
2677
|
? { success: true, output: `[恢复保护] ${toolCall.name} 在中断前已成功执行过, 本次不重复执行 (避免重复副作用)。当时结果: ${replaySkip}`, _replaySkipped: true }
|
|
2202
|
-
: await tool.execute(toolCall.args);
|
|
2678
|
+
: await (toolCall.__t0 = Date.now(), tool.execute((0, tool_path_args_js_1.expandHomeArgs)(toolCall.args)));
|
|
2203
2679
|
const toolDurationMs = Date.now() - toolStart;
|
|
2204
2680
|
if (replaySkip) {
|
|
2205
2681
|
console.log(`[PiAgent] 恢复重放守卫: 跳过已完成的非幂等工具 ${toolCall.name}`);
|
|
@@ -2207,18 +2683,18 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
2207
2683
|
}
|
|
2208
2684
|
console.log(`[PiAgent] 工具 ${toolCall.name} 执行完成: success=${result.success} (${toolDurationMs}ms)`);
|
|
2209
2685
|
// 2026-09-16 (M3): 失败接线 —— 分类 + recovery 留痕 + 熔断 / 外部等待
|
|
2210
|
-
if (!result.success && this.
|
|
2686
|
+
if (!result.success && this.actor.state.activeRun && !replaySkip) {
|
|
2211
2687
|
await this.wireToolFailure(toolCall.name, String(result.error || ''), toolCall.args);
|
|
2212
2688
|
}
|
|
2213
|
-
else if (result.success && this.
|
|
2689
|
+
else if (result.success && this.actor.state.activeRun && this.awaitingExternal) {
|
|
2214
2690
|
// 外部回话了: awaiting_external → running (不是"恢复完成", 只是等待结束)
|
|
2215
2691
|
this.awaitingExternal = false;
|
|
2216
|
-
await this.safeSetRunStatus(this.
|
|
2692
|
+
await this.safeSetRunStatus(this.actor.state.activeRun, 'running');
|
|
2217
2693
|
}
|
|
2218
2694
|
// 2026-09-16: 持久化 run harness — 每步工具调用立即落盘 (崩在这里也能看到做到哪步)
|
|
2219
|
-
if (this.
|
|
2695
|
+
if (this.actor.state.activeRun) {
|
|
2220
2696
|
try {
|
|
2221
|
-
await (0, run_store_js_1.recordStep)(this.
|
|
2697
|
+
await (0, run_store_js_1.recordStep)(this.actor.state.activeRun, {
|
|
2222
2698
|
tool: toolCall.name,
|
|
2223
2699
|
ok: !!result.success,
|
|
2224
2700
|
ms: toolDurationMs,
|
|
@@ -2249,7 +2725,7 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
2249
2725
|
ok: !!result.success,
|
|
2250
2726
|
});
|
|
2251
2727
|
if (after.routeHint?.systemAddition) {
|
|
2252
|
-
this.
|
|
2728
|
+
this.pushHistory({ role: 'system', content: `[Harness Router Hint: ${after.routeHint.reason}]\n${after.routeHint.systemAddition}` });
|
|
2253
2729
|
}
|
|
2254
2730
|
if (after.outputBlocked) {
|
|
2255
2731
|
if (onStream) {
|
|
@@ -2258,7 +2734,39 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
2258
2734
|
console.warn(`[PiAgent] Harness output denied ${toolCall.name}: ${after.outputBlocked.reason}`);
|
|
2259
2735
|
result = { ...result, output: `[harness output gate: 输出含敏感内容, 已屏蔽. 原因: ${after.outputBlocked.reason}]`, _harnessDenied: true };
|
|
2260
2736
|
}
|
|
2261
|
-
|
|
2737
|
+
// 2026-10-01: 落库前做一次停滞观测 —— 命中的是"引导/引用", 不是"拒绝执行":
|
|
2738
|
+
// 完全相同的返回从第 2 次起折叠成引用 (省上下文); 连续 3 次同参数同结果或检测到循环 ⇒
|
|
2739
|
+
// 在结果尾部追加一条系统提示 (保留模型的选择权, 不硬停 —— 硬停那条已被用户否决)。
|
|
2740
|
+
try {
|
|
2741
|
+
const obs = (0, tool_loop_guard_js_1.observeToolCall)(stallState, {
|
|
2742
|
+
toolName: toolCall.name,
|
|
2743
|
+
sameSignatureBefore: this.lastToolSig === (0, tool_telemetry_js_1.argsFingerprint)(toolCall.args),
|
|
2744
|
+
args: toolCall.args,
|
|
2745
|
+
resultText: String(result.output || (result.success ? '' : String(result.error || ''))),
|
|
2746
|
+
ok: !!result.success,
|
|
2747
|
+
seenResultBefore: stallState.hasSeenResult(String(result.output || '')),
|
|
2748
|
+
});
|
|
2749
|
+
if (obs.stub) {
|
|
2750
|
+
result = { ...result, output: obs.stub, _stubbed: true };
|
|
2751
|
+
}
|
|
2752
|
+
else if (obs.notice) {
|
|
2753
|
+
result = { ...result, output: `${String(result.output || '')}\n\n${obs.notice}` };
|
|
2754
|
+
}
|
|
2755
|
+
if (obs.action === 'warn') {
|
|
2756
|
+
console.warn(`[PiAgent] 工具停滞引导 (${obs.code}, 第 ${obs.count} 次): ${toolCall.name}`);
|
|
2757
|
+
onStream?.({ type: 'status', internal: true, content: `🩺 检测到重复调用 ${toolCall.name} (${obs.code}), 已提示模型换法`, tool: 'loop' });
|
|
2758
|
+
}
|
|
2759
|
+
}
|
|
2760
|
+
catch { /* 观测失败绝不影响主路径 */ }
|
|
2761
|
+
this.pushHistory({ role: 'tool', content: JSON.stringify(result), toolResult: result, toolCallId: toolCall.id || `call_${Date.now()}_${Math.random().toString(36).slice(2, 8)}` });
|
|
2762
|
+
// 2026-10-01 (用户: 「为什么这么慢」): 量到的事实 —— 每次 LLM 往返平均 3338ms,
|
|
2763
|
+
// 而"一轮发多个工具"(toolCalls>1) 只占 12/63 ✗ ⇒ 回合时长 ≈ 往返次数 × 3.3 秒 ✓。
|
|
2764
|
+
// 本回合**第一次**且**只发一个**工具时, 附一句带代价的提醒(不是口号 ✓), 只提一次免得刷屏。
|
|
2765
|
+
if (toolCalls.length === 1 && this.successfulToolResults.length <= 1) { // 本回合头一次(免得每轮刷屏)
|
|
2766
|
+
const __batchHint = (0, tool_loop_guard_js_1.batchHint)(toolCall.name, 1);
|
|
2767
|
+
if (__batchHint)
|
|
2768
|
+
this.pushHistory({ role: 'system', content: __batchHint.trim() });
|
|
2769
|
+
}
|
|
2262
2770
|
this.logToHarness(toolCall.name, toolCall.args, result);
|
|
2263
2771
|
// 2026-08-09: 记录到本轮行动日志 (循环进度 + final 前目标核查用)
|
|
2264
2772
|
// 去重: 同一工具同 args 连续成功只记一次 (防 LLM 重复 react 刷屏)
|
|
@@ -2287,10 +2795,48 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
2287
2795
|
onStream({ type: 'step_error', content: `${toolCall.name} 执行失败`, tool: toolCall.name, error: result.error });
|
|
2288
2796
|
}
|
|
2289
2797
|
}
|
|
2798
|
+
// 2026-10-01 (优化 #1/#3/#4): 工具结果的**进上下文闸** + **遥测** + **写操作读回自证**
|
|
2799
|
+
{
|
|
2800
|
+
const __ms = Date.now() - Number(toolCall.__t0 || Date.now());
|
|
2801
|
+
try {
|
|
2802
|
+
// #1 结果闸: 超上限 ⇒ 头尾 + 完整结果落文件(给路径) ⇒ 上下文不再被一条大输出长期占住
|
|
2803
|
+
const capped = (0, tool_result_gate_js_1.capToolResult)(String(result.output ?? ''), { tool: toolCall.name });
|
|
2804
|
+
if (capped.capped) {
|
|
2805
|
+
result.output = capped.text;
|
|
2806
|
+
console.warn(`[PiAgent] ${toolCall.name} 结果过长(${capped.originalChars})已截断` + (capped.spilledTo ? `, 完整内容: ${capped.spilledTo}` : ''));
|
|
2807
|
+
}
|
|
2808
|
+
// #4 写操作"读回自证": 写类工具成功后自动核一次(存在? 大小?) ⇒ "工具说成功≠任务成功"从规矩变成机制
|
|
2809
|
+
// 2026-10-01: 改了 TS 源码就记账 ⇒ 回合收尾自动跑类型检查 (把"靠自觉"变成机制)
|
|
2810
|
+
{
|
|
2811
|
+
const tsFile = (0, code_write_gate_js_1.codeWriteTarget)(toolCall.name, toolCall.args);
|
|
2812
|
+
if (tsFile && !this.tsTouchedThisTurn.includes(tsFile))
|
|
2813
|
+
this.tsTouchedThisTurn.push(tsFile);
|
|
2814
|
+
}
|
|
2815
|
+
const verified = verifyWriteOutcome(toolCall.name, toolCall.args, this.cwd);
|
|
2816
|
+
if (verified)
|
|
2817
|
+
result.output = `${String(result.output ?? '')}\n${verified}`;
|
|
2818
|
+
// #3 遥测: 记一行(工具/指纹/耗时/成败/结果大小/是否与上一次同签名) ⇒ 重复率可算
|
|
2819
|
+
(0, tool_telemetry_js_1.recordToolCall)({
|
|
2820
|
+
tool: toolCall.name,
|
|
2821
|
+
sig: (0, tool_telemetry_js_1.argsFingerprint)(toolCall.args),
|
|
2822
|
+
ms: __ms,
|
|
2823
|
+
ok: true,
|
|
2824
|
+
resultChars: String(result.output ?? '').length,
|
|
2825
|
+
prevSig: this.lastToolSig ?? null,
|
|
2826
|
+
});
|
|
2827
|
+
this.lastToolSig = (0, tool_telemetry_js_1.argsFingerprint)(toolCall.args);
|
|
2828
|
+
}
|
|
2829
|
+
catch { /* 任何优化项失败都不影响工具结果本身 */ }
|
|
2830
|
+
}
|
|
2290
2831
|
if (result.success) {
|
|
2291
2832
|
consecutiveErrors = 0;
|
|
2292
2833
|
// 2026-07-29: Hermes 风格硬限制计数
|
|
2293
2834
|
totalToolCallsThisLoop++;
|
|
2835
|
+
// 2026-10-01: 批处理工具(一次顶多次) ⇒ **退还**这次迭代 (奖励批处理, 压零碎调用)
|
|
2836
|
+
if (this.iterBudget && (0, iteration_budget_js_1.isRefundableTool)(toolCall.name)) {
|
|
2837
|
+
this.iterBudget.refund();
|
|
2838
|
+
console.warn(`[PiAgent] ${toolCall.name} 是批量工具 ⇒ 退还 1 次迭代 (现 ${this.iterBudget.describe()})`);
|
|
2839
|
+
}
|
|
2294
2840
|
lastNTools.push(toolCall.name);
|
|
2295
2841
|
if (lastNTools.length > MAX_IDEMPOTENT_TOOL)
|
|
2296
2842
|
lastNTools.shift();
|
|
@@ -2311,6 +2857,14 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
2311
2857
|
}
|
|
2312
2858
|
else {
|
|
2313
2859
|
consecutiveErrors++;
|
|
2860
|
+
// 2026-10-01 (优化 #3): 失败也记一行(带错误类别) ⇒ "错工具率"从这类错误里看得出来
|
|
2861
|
+
try {
|
|
2862
|
+
const __cls = (0, error_classifier_js_1.classifyError)(String(result.error || '')).label || '未分类';
|
|
2863
|
+
(0, tool_telemetry_js_1.recordToolCall)({ tool: toolCall.name, sig: (0, tool_telemetry_js_1.argsFingerprint)(toolCall.args), ms: 0,
|
|
2864
|
+
ok: false, resultChars: 0, prevSig: this.lastToolSig ?? null, errorClass: __cls });
|
|
2865
|
+
this.lastToolSig = (0, tool_telemetry_js_1.argsFingerprint)(toolCall.args);
|
|
2866
|
+
}
|
|
2867
|
+
catch { /* 遥测失败不影响 */ }
|
|
2314
2868
|
totalErrors++;
|
|
2315
2869
|
if (toolCall.name === lastFailedTool) {
|
|
2316
2870
|
lastFailedToolCount++;
|
|
@@ -2323,18 +2877,18 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
2323
2877
|
// 2026-07-28: 注入 Observation + Reflection 替代旧 hardcode 提示
|
|
2324
2878
|
const obs = (0, error_classifier_js_1.buildObservation)(toolCall.name, toolCall.args, { success: false, error: result.error });
|
|
2325
2879
|
const ref = (0, error_classifier_js_1.buildReflection)(toolCall.name, result.error, totalErrors, lastFailedToolCount);
|
|
2326
|
-
this.
|
|
2880
|
+
this.pushHistory({ role: 'system', content: (0, error_classifier_js_1.formatObservationWithReflection)(obs, ref) + SHELL_ESCAPE_HINT });
|
|
2327
2881
|
if (onStream)
|
|
2328
2882
|
onStream({ type: 'status', content: `💡 Reflection: ${obs.summary} → ${ref[0]?.action || '放弃'}`, tool: 'system' });
|
|
2329
2883
|
if (lastFailedToolCount >= MAX_SAME_TOOL_FAILURES) {
|
|
2330
|
-
this.
|
|
2884
|
+
this.pushHistory({ role: 'system', content: `[注意] 工具 ${toolCall.name} 在这个上下文中不可用 (连续 ${MAX_SAME_TOOL_FAILURES} 次失败: ${result.error}). 请不要再次调用它, 直接用你已知的信息回答用户, 并在回答开头标记 <final gen>.` });
|
|
2331
2885
|
lastFailedTool = '';
|
|
2332
2886
|
lastFailedToolCount = 0;
|
|
2333
2887
|
consecutiveErrors = 0;
|
|
2334
2888
|
return;
|
|
2335
2889
|
}
|
|
2336
2890
|
if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
|
|
2337
|
-
this.
|
|
2891
|
+
this.pushHistory({ role: 'system', content: `[注意] 前面的工具调用连续失败。请尝试其他工具或换一种方式完成用户请求, 或用 <final gen> 给出最终回答.` });
|
|
2338
2892
|
consecutiveErrors = 0;
|
|
2339
2893
|
}
|
|
2340
2894
|
}
|
|
@@ -2343,11 +2897,11 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
2343
2897
|
consecutiveErrors++;
|
|
2344
2898
|
totalErrors++;
|
|
2345
2899
|
const errorResult = { success: false, error: String(execError) };
|
|
2346
|
-
this.
|
|
2900
|
+
this.pushHistory({ role: 'tool', content: JSON.stringify(errorResult), toolResult: errorResult });
|
|
2347
2901
|
this.logToHarness(toolCall.name, toolCall.args, errorResult);
|
|
2348
2902
|
const obs = (0, error_classifier_js_1.buildObservation)(toolCall.name, toolCall.args, errorResult);
|
|
2349
2903
|
const ref = (0, error_classifier_js_1.buildReflection)(toolCall.name, errorResult.error, totalErrors, lastFailedToolCount);
|
|
2350
|
-
this.
|
|
2904
|
+
this.pushHistory({ role: 'system', content: (0, error_classifier_js_1.formatObservationWithReflection)(obs, ref) + SHELL_ESCAPE_HINT });
|
|
2351
2905
|
if (onStream)
|
|
2352
2906
|
onStream({ type: 'status', content: `💡 Reflection: ${obs.summary}`, tool: 'system' });
|
|
2353
2907
|
console.error(`[PiAgent] 工具执行异常 (累计 ${totalErrors}/${this.MAX_TOTAL_ERRORS}): ${execError}`);
|
|
@@ -2358,7 +2912,7 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
2358
2912
|
}
|
|
2359
2913
|
else {
|
|
2360
2914
|
// LLM 返回的不是 tool call 格式
|
|
2361
|
-
this.
|
|
2915
|
+
this.pushHistory({
|
|
2362
2916
|
role: 'assistant',
|
|
2363
2917
|
content: reply,
|
|
2364
2918
|
reasoningContent: response?.reasoningContent,
|
|
@@ -2387,7 +2941,7 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
2387
2941
|
unreportedRetries++;
|
|
2388
2942
|
const unreported = this.successfulToolResults.length;
|
|
2389
2943
|
console.log(`[PiAgent] LLM 想 final_gen 但还有 ${unreported} 个工具结果未汇报 (${unreportedRetries}/${MAX_UNREPORTED_RETRIES}), push hint 让其继续`);
|
|
2390
|
-
this.
|
|
2944
|
+
this.pushHistory({
|
|
2391
2945
|
role: 'system',
|
|
2392
2946
|
content: `[dive-into stop condition] 你之前已成功执行了 ${unreported} 个工具, 但当前回复里没把它们的结果告诉用户. 请基于已有的工具结果 (在 history 里) 写一个完整总结回复给用户, 用 <final gen> 结尾. 不要再调工具.`
|
|
2393
2947
|
});
|
|
@@ -2400,7 +2954,7 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
2400
2954
|
// 反复提示仍未汇报超过上限 → 清空积压强制 final, 不再死循环
|
|
2401
2955
|
console.log(`[PiAgent] unreported 循环超限 (${unreportedRetries} 次), 清空积压强制 final`);
|
|
2402
2956
|
this.successfulToolResults = [];
|
|
2403
|
-
this.
|
|
2957
|
+
this.pushHistory({
|
|
2404
2958
|
role: 'system',
|
|
2405
2959
|
content: `[dive-into stop condition] 已多次提示汇报工具结果仍未完成 (超过 ${MAX_UNREPORTED_RETRIES} 次). 现在直接基于你已知的信息写最终回复给用户, 用 <final gen> 结尾, 不要再调任何工具.`
|
|
2406
2960
|
});
|
|
@@ -2412,7 +2966,7 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
2412
2966
|
// 2026-07-29: 质量门 — 即使 LLM 声称完成, 质量太低也继续
|
|
2413
2967
|
if (lastQualityScore < this.QUALITY_THRESHOLD && refineAttempts < this.MAX_REFINE_ATTEMPTS) {
|
|
2414
2968
|
console.log(`[PiAgent] final gen 质量 ${lastQualityScore.toFixed(2)} < ${this.QUALITY_THRESHOLD}, 注入 refine hint`);
|
|
2415
|
-
this.
|
|
2969
|
+
this.pushHistory({ role: 'system', content: `[质量检查] 你的回答质量评分为 ${(lastQualityScore * 10).toFixed(1)}/10, 低于 ${(this.QUALITY_THRESHOLD * 10).toFixed(1)}/10 阈值。请提供更完整、详细的回答, 包含工具调用获取到的具体信息, 末尾加 <final gen>。` });
|
|
2416
2970
|
refineAttempts++;
|
|
2417
2971
|
continue;
|
|
2418
2972
|
}
|
|
@@ -2427,13 +2981,13 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
2427
2981
|
userIntent: this.currentUserInput,
|
|
2428
2982
|
completedTools: Array.from(loopReviewCompletedTools),
|
|
2429
2983
|
actionLog: loopActionLog,
|
|
2430
|
-
runId: this.
|
|
2431
|
-
goalId: this.
|
|
2984
|
+
runId: this.actor.state.activeRun || undefined,
|
|
2985
|
+
goalId: this.actor.state.goalBinding || undefined,
|
|
2432
2986
|
}, loop_review_js_1.DEFAULT_MAX_REVIEWS);
|
|
2433
2987
|
if (reviewDecision.kind === 'continue-review') {
|
|
2434
2988
|
loopReviewCount++;
|
|
2435
2989
|
console.log(`[PiAgent] review ${loopReviewCount}/${loop_review_js_1.DEFAULT_MAX_REVIEWS}: LLM 想 final 但先对齐需求深挖一次`);
|
|
2436
|
-
this.
|
|
2990
|
+
this.pushHistory({ role: 'system', content: reviewDecision.hint });
|
|
2437
2991
|
if (onStream) {
|
|
2438
2992
|
onStream({ type: 'status', internal: true, content: `🔄 目标对齐 review ${loopReviewCount}/${loop_review_js_1.DEFAULT_MAX_REVIEWS}: 深挖续跑`, tool: 'system' });
|
|
2439
2993
|
}
|
|
@@ -2486,16 +3040,16 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
2486
3040
|
if (onStream) {
|
|
2487
3041
|
onStream({ type: 'status', internal: true, content: `✅ 处理完成,共 ${iteration - 1} 次循环`, tool: 'system' });
|
|
2488
3042
|
}
|
|
2489
|
-
this.
|
|
3043
|
+
this.pushHistory({ role: 'assistant', content: finalResponse });
|
|
2490
3044
|
// React Harness: 循环结束
|
|
2491
3045
|
// 2026-09-16 (Milestone 1-B): 会话收尾走唯一门面
|
|
2492
3046
|
await this.piHarness().sessionEnd(this.harnessCtx());
|
|
2493
3047
|
// 2026-09-16: 收尾落盘 — done / failed / aborted / needs_human 如实写回 (不留幽灵 running)
|
|
2494
|
-
if (this.
|
|
3048
|
+
if (this.actor.state.activeRun && !runExternallyPaused && !runExternallyAborted) {
|
|
2495
3049
|
try {
|
|
2496
3050
|
// 2026-09-16 (M4): 完成门 —— "模型说完成"不等于"系统确认完成"。
|
|
2497
3051
|
// 证据 = 成功步骤的事实摘要; 末尾还有失败步骤没被后续成功覆盖 → 不许 done。
|
|
2498
|
-
const recBefore = await (0, run_store_js_1.readRun)(this.
|
|
3052
|
+
const recBefore = await (0, run_store_js_1.readRun)(this.actor.state.activeRun).catch(() => null);
|
|
2499
3053
|
const steps = recBefore?.steps || [];
|
|
2500
3054
|
const evidence = steps.filter((s) => s.ok).map((s) => `${s.tool}: ${(s.summary || '').slice(0, 120)}`).slice(-10);
|
|
2501
3055
|
const lastStep = steps[steps.length - 1];
|
|
@@ -2516,7 +3070,7 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
2516
3070
|
: (trailingFailure || noEvidence)
|
|
2517
3071
|
? 'failed'
|
|
2518
3072
|
: 'done';
|
|
2519
|
-
await (0, run_store_js_1.finishRun)(this.
|
|
3073
|
+
await (0, run_store_js_1.finishRun)(this.actor.state.activeRun, {
|
|
2520
3074
|
status,
|
|
2521
3075
|
summary: finalResponse ? String(finalResponse).slice(0, 400) : undefined,
|
|
2522
3076
|
error: errText
|
|
@@ -2529,7 +3083,7 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
2529
3083
|
// 终态也写不下去: 这是最坏情况 —— 除了留痕, 没有别的自愈手段, 所以必须显眼
|
|
2530
3084
|
const message = `收尾落盘失败 (状态无法写回, 记录会停在 running): ${String(err?.message || err).slice(0, 200)}`;
|
|
2531
3085
|
console.error('[PiAgent] run-store finishRun 失败 (核心持久化):', message);
|
|
2532
|
-
await (0, run_store_js_1.recordDegradation)({ kind: 'core', op: 'pi-sdk.finishRun', runId: this.
|
|
3086
|
+
await (0, run_store_js_1.recordDegradation)({ kind: 'core', op: 'pi-sdk.finishRun', runId: this.actor.state.activeRun, message }).catch(() => { });
|
|
2533
3087
|
onStream?.({ type: 'error', content: `⚠️ ${message}`, tool: 'harness' });
|
|
2534
3088
|
}
|
|
2535
3089
|
// ★ 2026-09-25 (M0 接线冻结, 规则 ③ + ④): Run 结束**必过收尾漏斗**。
|
|
@@ -2541,14 +3095,14 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
2541
3095
|
//
|
|
2542
3096
|
// 现在: `closeRunOnce` (幂等) → 9 步收尾 → 产物落盘; 再由 Goal reducer 把"下一步是什么"
|
|
2543
3097
|
// 落进 Goal (没有 Supervisor 宿主时 Runner 也要留下 continuation, 否则下一次还是从零开始)。
|
|
2544
|
-
if (this.
|
|
3098
|
+
if (this.actor.state.goalBinding) {
|
|
2545
3099
|
try {
|
|
2546
3100
|
const wiring = await Promise.resolve().then(() => __importStar(require('./goal-flywheel-wiring.js')));
|
|
2547
3101
|
const { reduceGoalState } = await Promise.resolve().then(() => __importStar(require('./goal-state-reducer.js')));
|
|
2548
3102
|
const nowIso = new Date().toISOString();
|
|
2549
3103
|
const closed = await wiring.closeRunOnce({
|
|
2550
|
-
goalId: this.
|
|
2551
|
-
runId: this.
|
|
3104
|
+
goalId: this.actor.state.goalBinding,
|
|
3105
|
+
runId: this.actor.state.activeRun,
|
|
2552
3106
|
caller: 'runner',
|
|
2553
3107
|
now: nowIso,
|
|
2554
3108
|
finalReview: finalResponse ? String(finalResponse).slice(0, 2000) : '',
|
|
@@ -2559,7 +3113,7 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
2559
3113
|
else if (closed.outcome) {
|
|
2560
3114
|
const view = closed.outcome;
|
|
2561
3115
|
const applied = await reduceGoalState({
|
|
2562
|
-
goalId: this.
|
|
3116
|
+
goalId: this.actor.state.goalBinding,
|
|
2563
3117
|
intent: 'closure_outcome',
|
|
2564
3118
|
now: nowIso,
|
|
2565
3119
|
by: 'runner',
|
|
@@ -2576,7 +3130,7 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
2576
3130
|
onStream?.({
|
|
2577
3131
|
type: 'status', internal: true,
|
|
2578
3132
|
content: `${line}${applied.gateRejected ? ` [完成门拒绝: ${applied.gateRejected}]` : ''} `
|
|
2579
|
-
+ `(收尾 ${view.steps} 步 · memory ${view.memories} · skill 候选 ${view.candidates} · goal=${this.
|
|
3133
|
+
+ `(收尾 ${view.steps} 步 · memory ${view.memories} · skill 候选 ${view.candidates} · goal=${this.actor.state.goalBinding})`,
|
|
2580
3134
|
tool: 'harness',
|
|
2581
3135
|
});
|
|
2582
3136
|
}
|
|
@@ -2586,15 +3140,15 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
2586
3140
|
}
|
|
2587
3141
|
}
|
|
2588
3142
|
catch (err) {
|
|
2589
|
-
await (0, run_store_js_1.recordDegradation)({ kind: 'observational', op: 'pi-sdk.runClosure', runId: this.
|
|
3143
|
+
await (0, run_store_js_1.recordDegradation)({ kind: 'observational', op: 'pi-sdk.runClosure', runId: this.actor.state.activeRun, message: String(err?.message || err).slice(0, 160) }).catch(() => { });
|
|
2590
3144
|
}
|
|
2591
3145
|
}
|
|
2592
|
-
this.
|
|
3146
|
+
this.actor.state.activeRun = '';
|
|
2593
3147
|
}
|
|
2594
|
-
else if (this.
|
|
3148
|
+
else if (this.actor.state.activeRun) {
|
|
2595
3149
|
// 外部暂停/中止: 状态是人定的, 不覆盖 (paused 等 /resume; aborted 是终态)
|
|
2596
3150
|
onStream?.({ type: 'status', content: `⏹️ 运行状态保持为 ${runExternallyPaused ? 'paused' : 'aborted'} (由外部控制面决定)`, tool: 'harness' });
|
|
2597
|
-
this.
|
|
3151
|
+
this.actor.state.activeRun = '';
|
|
2598
3152
|
}
|
|
2599
3153
|
// 2026-06-16: 暴露 aiFailed 标志 — promptStream 据此决定是否自动重试整个 loop
|
|
2600
3154
|
return { reply: finalResponse, aiFailed, aiFailureReason: aiFailureReason || undefined };
|
|
@@ -2648,7 +3202,7 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
2648
3202
|
// 失败静默: 任何 stage 抛错 → 走老 slice(-10) 逻辑
|
|
2649
3203
|
//
|
|
2650
3204
|
// P1.2: 如果 maybeAutoCompact 算过 Context Collapse 投影, 用 this.projectedHistory (读时投影, 非破坏)
|
|
2651
|
-
const source = this.projectedHistory ?? this.messageHistory;
|
|
3205
|
+
const source = this.projectedHistory ?? this.actor.state.messageHistory;
|
|
2652
3206
|
const recentMessages = this.compressHistorySync(source).slice(-10);
|
|
2653
3207
|
return recentMessages.map(m => {
|
|
2654
3208
|
if (m.role === 'user')
|
|
@@ -2675,7 +3229,7 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
2675
3229
|
// 2026-08-06: 来源优先用 projectedHistory (Context Collapse 投影, 非破坏) —
|
|
2676
3230
|
// 与 buildContext 一致; 之前只让字符串路径用投影, messages 数组路径被跳过,
|
|
2677
3231
|
// 导致 LLM 实际看到的还是未压缩的历史.
|
|
2678
|
-
const source = this.projectedHistory ?? this.messageHistory;
|
|
3232
|
+
const source = this.projectedHistory ?? this.actor.state.messageHistory;
|
|
2679
3233
|
const WINDOW = 15;
|
|
2680
3234
|
const out = [];
|
|
2681
3235
|
// 2026-09-28 (前缀 KV 可命中): 带 CURRENT TURN 注入的条目要**原样回带**.
|
|
@@ -2754,11 +3308,11 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
2754
3308
|
/**
|
|
2755
3309
|
* 2026-09-28 (前缀 KV 可命中): 把 chat() 回带的当前轮写回自己的 messageHistory.
|
|
2756
3310
|
* 逻辑在模块级 `writeBackCurrentTurnInto` (纯函数, 门可以直接驱它断言);
|
|
2757
|
-
* 这里只负责接到 `
|
|
3311
|
+
* 这里只负责接到 `actor 的 messageHistory` 上, 并保证任何异常都静默 (写回失败不影响对话).
|
|
2758
3312
|
*/
|
|
2759
3313
|
writeBackCurrentTurn(wire) {
|
|
2760
3314
|
try {
|
|
2761
|
-
return writeBackCurrentTurnInto(this.messageHistory, wire);
|
|
3315
|
+
return writeBackCurrentTurnInto(this.actor.state.messageHistory, wire);
|
|
2762
3316
|
}
|
|
2763
3317
|
catch (err) {
|
|
2764
3318
|
console.warn('[PiAgent] writeBackCurrentTurn failed (silent):', err);
|
|
@@ -2772,7 +3326,7 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
2772
3326
|
estimateHistoryTokens() {
|
|
2773
3327
|
try {
|
|
2774
3328
|
const { estimateTokens } = _piRequire('../context-compaction/index.js');
|
|
2775
|
-
return estimateTokens(this.messageHistory);
|
|
3329
|
+
return estimateTokens(this.actor.state.messageHistory);
|
|
2776
3330
|
}
|
|
2777
3331
|
catch {
|
|
2778
3332
|
return 0;
|
|
@@ -2795,8 +3349,10 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
2795
3349
|
console.warn(`[PiAgent] reactive compaction pre-check (${estimated} tokens > 80% threshold)`);
|
|
2796
3350
|
onStream?.({ type: 'status', internal: true, content: '⚠️ reactive compaction 预检触发', tool: 'recovery' });
|
|
2797
3351
|
try {
|
|
2798
|
-
|
|
2799
|
-
|
|
3352
|
+
// 同步压缩: 取快照与替换在**同一拍相邻两行** (中间没有 await) ⇒ 不存在"变换期间被追加"的窗口。
|
|
3353
|
+
// 若哪天这里插入 await, 必须改成 `actor.rebaseHistory(…, snapshotLen)` (见 channel-actor.ts)。
|
|
3354
|
+
const compacted = this.compressHistorySync(this.actor.state.messageHistory);
|
|
3355
|
+
this.replaceHistory(compacted);
|
|
2800
3356
|
if (this.estimateHistoryTokens() > this.maxContextTokens() * 0.8) {
|
|
2801
3357
|
await this.maybeAutoCompact(onStream, signal);
|
|
2802
3358
|
}
|
|
@@ -2847,7 +3403,14 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
2847
3403
|
// currentTurnContext 是调用方自己的当前轮易变段 (循环进度/改进提示), 走 CURRENT TURN 区.
|
|
2848
3404
|
// 2026-09-28: 回带最终 wire messages (含注入后的当前轮) → 上层据此写回 messageHistory.
|
|
2849
3405
|
const response = await llm.chat(contextOrMessages, systemPrompt, signal, tools, 'main-agent', 'react-loop', undefined, currentTurnContext);
|
|
2850
|
-
// 2026-
|
|
3406
|
+
// 2026-10-01 (用户: 「思考的记录可以也显示出来吗」): 思考模型的思维链原先只被"存起来回带",
|
|
3407
|
+
// **不显示** ✗。这里把它往流里送一份 —— 显示与否由 CLI 侧决定(BOLLOON_SHOW_THINKING, 默认显示) ✓。
|
|
3408
|
+
try {
|
|
3409
|
+
const rc = String(response?.reasoningContent || '').trim();
|
|
3410
|
+
if (rc)
|
|
3411
|
+
this.runCtx.eventSink?.({ type: 'reasoning', content: rc });
|
|
3412
|
+
}
|
|
3413
|
+
catch { /* 送不出去不影响主流程 */ } // 2026-06-30: 透传 toolCalls (OpenAI 协议 native) 给上层, 让 assistant message 能 emit 真 id
|
|
2851
3414
|
return { reply: response.reply || '', toolCalls: response.toolCalls, messages: response.messages };
|
|
2852
3415
|
}
|
|
2853
3416
|
catch (err) {
|
|
@@ -2885,8 +3448,8 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
2885
3448
|
if (Array.isArray(contextOrMessages)) {
|
|
2886
3449
|
contextOrMessages = this.buildContext();
|
|
2887
3450
|
// 也清除最近一轮的 toolCalls, 防止再触发
|
|
2888
|
-
if (this.messageHistory.length > 1) {
|
|
2889
|
-
const last = this.messageHistory[this.messageHistory.length - 1];
|
|
3451
|
+
if (this.actor.state.messageHistory.length > 1) {
|
|
3452
|
+
const last = this.actor.state.messageHistory[this.actor.state.messageHistory.length - 1];
|
|
2890
3453
|
if (last.role === 'assistant' && last.toolCalls) {
|
|
2891
3454
|
delete last.toolCalls;
|
|
2892
3455
|
}
|
|
@@ -2949,7 +3512,7 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
2949
3512
|
* onStream 广播: 跟 computeJudgmentGate 风格一致 (phase 事件供 UI timeline 显示)
|
|
2950
3513
|
*/
|
|
2951
3514
|
async maybeAutoCompact(onStream, signal) {
|
|
2952
|
-
if (this.messageHistory.length < 10)
|
|
3515
|
+
if (this.actor.state.messageHistory.length < 10)
|
|
2953
3516
|
return; // 历史太短, 不值得压
|
|
2954
3517
|
onStream?.({ type: 'status', internal: true, content: '🗜️ 评估是否需要压缩上下文...', tool: 'compactor' });
|
|
2955
3518
|
// 注入 LLM (用 getMinimax().chat, 与 judgment 注入门 / ReAct 循环同一来源)
|
|
@@ -2968,14 +3531,17 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
2968
3531
|
const maxTokens = Math.max(4000, Math.round(cfg.maxTokens * cfg.compressionThreshold));
|
|
2969
3532
|
const beforeTokens = this.estimateHistoryTokens();
|
|
2970
3533
|
const { compactPipeline, isContextCollapseEnabled } = await Promise.resolve().then(() => __importStar(require('../context-compaction/index.js')));
|
|
2971
|
-
|
|
3534
|
+
// **K5 第 4 步**: 记下取快照时的长度 —— 压缩是 async, 从这一拍到 "落地" 之间是 await 窗口;
|
|
3535
|
+
// 期间 append 进来的消息必须由 `actor.rebaseHistory(…, snapshotLen)` 接回去, 否则会被整块替换丢掉。
|
|
3536
|
+
const snapshotLen = this.actor.state.messageHistory.length;
|
|
3537
|
+
const result = await compactPipeline(this.actor.state.messageHistory, {
|
|
2972
3538
|
maxTokens,
|
|
2973
3539
|
llmChat,
|
|
2974
3540
|
collapseLlmChat: llmChat, // P1.2: Context Collapse 投影也用同一 LLM
|
|
2975
|
-
cacheScope: this.
|
|
3541
|
+
cacheScope: this.actor.state.channelId || 'default',
|
|
2976
3542
|
});
|
|
2977
|
-
if (result.compacted && result.history.length < this.messageHistory.length) {
|
|
2978
|
-
const saved = this.messageHistory.length - result.history.length;
|
|
3543
|
+
if (result.compacted && result.history.length < this.actor.state.messageHistory.length) {
|
|
3544
|
+
const saved = this.actor.state.messageHistory.length - result.history.length;
|
|
2979
3545
|
const stagesApplied = result.stages.filter((s) => s.applied).map((s) => s.stage).join(' → ');
|
|
2980
3546
|
const afterTokens = this.estimateHistoryTokens();
|
|
2981
3547
|
const savedTokens = Math.max(0, beforeTokens - afterTokens);
|
|
@@ -2993,7 +3559,17 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
2993
3559
|
// messageHistory 不变 (非破坏)
|
|
2994
3560
|
}
|
|
2995
3561
|
else {
|
|
2996
|
-
|
|
3562
|
+
// 真破坏性更新 —— **K5 第 4 步**: 绑定了 actor 就交它落地 (rebase: 保住变换期间的追加);
|
|
3563
|
+
// 未绑定的会话走原路径 (行为不变)。
|
|
3564
|
+
if (this.actor) {
|
|
3565
|
+
const { keptTail } = await this.actor.rebaseHistory(result.history, snapshotLen);
|
|
3566
|
+
if (keptTail > 0) {
|
|
3567
|
+
console.warn(`[PiAgent] 压缩落地: 保住变换期间新追加的 ${keptTail} 条消息 (快照后到达)`);
|
|
3568
|
+
}
|
|
3569
|
+
}
|
|
3570
|
+
else {
|
|
3571
|
+
this.replaceHistory(result.history);
|
|
3572
|
+
}
|
|
2997
3573
|
this.projectedHistory = null;
|
|
2998
3574
|
}
|
|
2999
3575
|
// 2026-08-06: snapshot 记录 before/after + 摘要 (供恢复/调试/UI), 事件广播
|
|
@@ -3004,10 +3580,10 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
|
3004
3580
|
afterTokens,
|
|
3005
3581
|
summary: `压缩管道: ${summaryLine}; 节省 ${savedTokens} tokens / ${saved} 条消息`,
|
|
3006
3582
|
preservedMemory: [
|
|
3007
|
-
...this.messageHistory.filter(m => m.role === 'user').slice(-3).map(m => (m.content || '').slice(0, 80)),
|
|
3583
|
+
...this.actor.state.messageHistory.filter(m => m.role === 'user').slice(-3).map(m => (m.content || '').slice(0, 80)),
|
|
3008
3584
|
],
|
|
3009
|
-
agentId: this.
|
|
3010
|
-
channelId: this.
|
|
3585
|
+
agentId: this.actor.state.agentId,
|
|
3586
|
+
channelId: this.actor.state.channelId,
|
|
3011
3587
|
});
|
|
3012
3588
|
cm.markCompressComplete(snap);
|
|
3013
3589
|
}
|
|
@@ -3393,10 +3969,16 @@ ${this.extractOperationsFromRef(operationsRef)}
|
|
|
3393
3969
|
return { ...this.identity };
|
|
3394
3970
|
}
|
|
3395
3971
|
updateIdentity(updates) {
|
|
3396
|
-
this.identity = { ...this.identity, ...updates }
|
|
3972
|
+
// 2026-10-01 **修切换频道后身份不换的根因**: 上一版是 `this.identity = { ...this.identity, ...updates }`
|
|
3973
|
+
// —— **换成新对象** ✗。而工具上下文是在 registerTools() 时按**对象引用**捕获的 (`identity: this.identity`),
|
|
3974
|
+
// 并且切频道时 session 是**复用**的(pi-sdk-session-factory 的模块级单例, 只调 updateIdentity) ⇒
|
|
3975
|
+
// 工具里的 ctx.identity 一直指向**旧对象** ⇒ `get_identity` 永远返回上一个频道的身份 ✗✗
|
|
3976
|
+
// (实测: xiaomi/智能体频道都答「233 的 DID + 小龙」, 因为 233 是进程启动时的频道)。
|
|
3977
|
+
// ⇒ 改成**原地改**: 所有持有者(工具上下文/会话/状态栏)都看到同一个对象的新值 ✓。
|
|
3978
|
+
Object.assign(this.identity, updates);
|
|
3397
3979
|
}
|
|
3398
3980
|
setCurrentChannelId(channelId) {
|
|
3399
|
-
this.
|
|
3981
|
+
this.actor.state.channelId = channelId;
|
|
3400
3982
|
}
|
|
3401
3983
|
getSessionState() {
|
|
3402
3984
|
return this.sessionManager.getState();
|
|
@@ -3409,6 +3991,17 @@ ${this.extractOperationsFromRef(operationsRef)}
|
|
|
3409
3991
|
}
|
|
3410
3992
|
async setPersona(persona) {
|
|
3411
3993
|
await this.sessionManager.savePersona(persona);
|
|
3994
|
+
// 2026-10-01: **写透** —— 有身份文档的 agent 不套 persona.json, 只写 JSON 等于没改
|
|
3995
|
+
// (用户报"每次让智能体改都是同一个") ⇒ 同步落进它自己的身份文档 (标记区内)
|
|
3996
|
+
if (this.actor.state.agentId) {
|
|
3997
|
+
try {
|
|
3998
|
+
const { applyPersonaToDocs } = await Promise.resolve().then(() => __importStar(require('../bootstrap/persona-init.js')));
|
|
3999
|
+
const wrote = await applyPersonaToDocs(this.actor.state.agentId, persona);
|
|
4000
|
+
if (wrote.length)
|
|
4001
|
+
console.warn(`[persona] 已把 ${this.actor.state.agentId} 的 persona 写进身份文档: ${wrote.join(' · ')}`);
|
|
4002
|
+
}
|
|
4003
|
+
catch { /* 非致命 */ }
|
|
4004
|
+
}
|
|
3412
4005
|
this.persona = persona;
|
|
3413
4006
|
if (persona.name) {
|
|
3414
4007
|
this.identity.name = persona.name;
|