@bolloon/bolloon-agent 0.2.12 → 0.2.13
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/README.md +495 -467
- package/bin/bolloon-cli.cjs +183 -183
- package/bin/bolloon-daemon.sh +207 -0
- package/bin/bolloon.cjs +0 -0
- package/bin/ipfs +0 -0
- package/dist/agents/agent-manifest-protocol.js +52 -0
- package/dist/agents/chat-segmenter.js +298 -0
- package/dist/agents/constraint-layer.js +19 -19
- package/dist/agents/judgment-protocol.js +479 -0
- package/dist/agents/parse-tool-call.js +304 -0
- package/dist/agents/peer-manifest-loader.js +210 -0
- package/dist/agents/pi-sdk-session-factory.js +110 -0
- package/dist/agents/pi-sdk-session-manager.js +297 -0
- package/dist/agents/pi-sdk-tools.js +1288 -0
- package/dist/agents/pi-sdk-types.js +13 -0
- package/dist/agents/pi-sdk.js +553 -1306
- package/dist/agents/react-loop.js +120 -0
- package/dist/agents/session-store.js +171 -0
- package/dist/agents/shell-guard.js +2 -2
- package/dist/agents/tool-registry.js +136 -0
- package/dist/agents/workflow-pivot-loop.js +200 -22
- package/dist/bollharness/src/scripts/context_router.js +7 -0
- package/dist/bollharness/src/scripts/deploy-guard.js +8 -8
- package/dist/bollharness-integration/context-router-judgment.js +4 -4
- package/dist/bollharness-integration/context-router.js +292 -292
- package/dist/bollharness-integration/gate-state-machine.js +26 -13
- package/dist/bollharness-integration/integration.js +71 -0
- package/dist/bollharness-integration/llm-judgment-engine.js +134 -134
- package/dist/bollharness-integration/skill-adapter.js +57 -127
- package/dist/bootstrap/chat-archiver.js +276 -0
- package/dist/bootstrap/context-collector.js +6 -3
- package/dist/bootstrap/event-log.js +160 -0
- package/dist/bootstrap/lifecycle-hooks.js +15 -1
- package/dist/bootstrap/memory-compressor.js +170 -0
- package/dist/bootstrap/persona-loader.js +94 -0
- package/dist/bootstrap/project-state.js +159 -0
- package/dist/bootstrap/remote-mirror.js +97 -0
- package/dist/bootstrap/session-window.js +121 -0
- package/dist/bootstrap/vector-index.js +248 -0
- package/dist/cli/loading-tui.js +40 -0
- package/dist/cli-entry.js +41 -37
- package/dist/context-compaction/auto-compact.js +7 -7
- package/dist/electron/config.js +21 -0
- package/dist/electron/config.js.map +1 -0
- package/dist/electron/dialogs.js +108 -0
- package/dist/electron/dialogs.js.map +1 -0
- package/dist/electron/first-run.js +170 -0
- package/dist/electron/first-run.js.map +1 -0
- package/dist/electron/ipc.js +20 -0
- package/dist/electron/ipc.js.map +1 -0
- package/dist/electron/logger.js +114 -0
- package/dist/electron/logger.js.map +1 -0
- package/dist/electron/main.js +63 -0
- package/dist/electron/main.js.map +1 -0
- package/dist/electron/menu.js +145 -0
- package/dist/electron/menu.js.map +1 -0
- package/dist/electron/paths.js +75 -0
- package/dist/electron/paths.js.map +1 -0
- package/dist/electron/server.js +119 -0
- package/dist/electron/server.js.map +1 -0
- package/dist/electron/tray.js +111 -0
- package/dist/electron/tray.js.map +1 -0
- package/dist/electron/window.js +111 -0
- package/dist/electron/window.js.map +1 -0
- package/dist/electron-preload.js +26 -6
- package/dist/electron-preload.js.map +1 -1
- package/dist/electron.js +6 -168
- package/dist/electron.js.map +1 -1
- package/dist/index.js +323 -266
- package/dist/llm/config-store.js +24 -8
- package/dist/llm/llm-judgment-client.js +102 -102
- package/dist/llm/pi-ai.js +137 -85
- package/dist/llm/system-prompt/layers/channel/local.md +14 -14
- package/dist/llm/system-prompt/layers/channel/p2p-agent.md +18 -18
- package/dist/llm/system-prompt/layers/channel/p2p-visitor.md +19 -19
- package/dist/llm/system-prompt/layers/core/artifacts_storage.md +89 -89
- package/dist/llm/system-prompt/layers/core/evenhandedness.md +21 -21
- package/dist/llm/system-prompt/layers/core/hibs_reminders.md +15 -15
- package/dist/llm/system-prompt/layers/core/identity.md +37 -37
- package/dist/llm/system-prompt/layers/core/knowledge.md +17 -17
- package/dist/llm/system-prompt/layers/core/memory_system.md +12 -12
- package/dist/llm/system-prompt/layers/core/network_filesystem.md +28 -28
- package/dist/llm/system-prompt/layers/core/refusal.md +37 -37
- package/dist/llm/system-prompt/layers/core/tone.md +31 -31
- package/dist/llm/system-prompt/layers/core/tools.thin.md +13 -13
- package/dist/llm/system-prompt/layers/core/wellbeing.md +41 -41
- package/dist/llm/system-prompt/layers/role/architect.md +20 -20
- package/dist/llm/system-prompt/layers/role/expert.md +19 -19
- package/dist/llm/system-prompt/layers/role/implementer.md +15 -15
- package/dist/llm/system-prompt/layers/role/security.md +15 -15
- package/dist/llm/system-prompt/layers/tool/artifacts.md +72 -72
- package/dist/llm/system-prompt/layers/tool/bash.md +25 -25
- package/dist/llm/system-prompt/layers/tool/hibs_api.md +171 -171
- package/dist/llm/system-prompt/layers/tool/image_search.md +70 -70
- package/dist/llm/system-prompt/layers/tool/manifest.md +89 -89
- package/dist/llm/system-prompt/layers/tool/mcp_apps.md +53 -53
- package/dist/llm/system-prompt/layers/tool/web_search.md +83 -83
- package/dist/llm/tool-manifest/ask_user_input.js +1 -1
- package/dist/llm/tool-manifest/bash.js +3 -3
- package/dist/llm/tool-manifest/create_file.js +4 -4
- package/dist/llm/tool-manifest/fetch_sports_data.js +3 -3
- package/dist/llm/tool-manifest/image_search.js +2 -2
- package/dist/llm/tool-manifest/mcp.js +2 -2
- package/dist/llm/tool-manifest/message_compose.js +3 -3
- package/dist/llm/tool-manifest/places.js +2 -2
- package/dist/llm/tool-manifest/present_files.js +1 -1
- package/dist/llm/tool-manifest/recipe.js +2 -2
- package/dist/llm/tool-manifest/str_replace.js +5 -5
- package/dist/llm/tool-manifest/view.js +3 -3
- package/dist/llm/tool-manifest/weather.js +4 -4
- package/dist/llm/tool-manifest/web.js +4 -4
- package/dist/network/local-inbox-bus.js +73 -0
- package/dist/network/p2p-outbox.js +161 -0
- package/dist/network/peer-fs.js +420 -0
- package/dist/network/peer-resource-bridge.js +216 -0
- package/dist/pi-ecosystem-judgment/causal-judge.js +51 -51
- package/dist/pi-ecosystem-judgment/cleanup.js +126 -0
- package/dist/pi-ecosystem-judgment/distill-prompt.js +42 -42
- package/dist/pi-ecosystem-judgment/distillation.js +14 -14
- package/dist/pi-ecosystem-judgment/human-value-store.js +45 -11
- package/dist/pi-ecosystem-judgment/monitor-gate.js +28 -28
- package/dist/pi-ecosystem-judgment/value-injection.js +102 -102
- package/dist/security/context-router-tool.js +15 -15
- package/dist/security/tool-gate.js +11 -0
- package/dist/web/api-config.html +520 -520
- package/dist/web/client-loop-status.js +220 -0
- package/dist/web/client.js +22 -5
- package/dist/web/client.js.map +7 -0
- package/dist/web/components/wallet-viem.mjs +118 -118
- package/dist/web/index.html +375 -375
- package/dist/web/input-validator.js +103 -0
- package/dist/web/manifest.json +20 -20
- package/dist/web/routes-judgments.js +738 -0
- package/dist/web/routes-llm-config.js +279 -0
- package/dist/web/routes-tasks.js +222 -0
- package/dist/web/server-sse.js +121 -0
- package/dist/web/server-storage.js +154 -0
- package/dist/web/server-types.js +15 -0
- package/dist/web/server-v3-p2p.js +224 -0
- package/dist/web/server.js +1070 -1307
- package/dist/web/style.css +4742 -4742
- package/dist/web/util/safe-name.js +32 -0
- package/package.json +162 -160
- package/scripts/build-cli.js +215 -215
- package/scripts/build-web.ts +130 -130
- package/scripts/postinstall.js +152 -152
- package/bin/bolloon.js +0 -157
- package/dist/constraint-runtime/src/_archive_helper.js +0 -9
- package/dist/constraint-runtime/src/agent/coordinator.js +0 -48
- package/dist/constraint-runtime/src/agent/index.js +0 -1
- package/dist/constraint-runtime/src/assistant/index.js +0 -12
- package/dist/constraint-runtime/src/bootstrap/index.js +0 -12
- package/dist/constraint-runtime/src/bootstrap_graph.js +0 -13
- package/dist/constraint-runtime/src/bridge/index.js +0 -12
- package/dist/constraint-runtime/src/buddy/index.js +0 -12
- package/dist/constraint-runtime/src/cli/index.js +0 -12
- package/dist/constraint-runtime/src/command_graph.js +0 -10
- package/dist/constraint-runtime/src/commands.js +0 -60
- package/dist/constraint-runtime/src/components/index.js +0 -12
- package/dist/constraint-runtime/src/constants/index.js +0 -12
- package/dist/constraint-runtime/src/constraint/budget.js +0 -22
- package/dist/constraint-runtime/src/constraint/index.js +0 -2
- package/dist/constraint-runtime/src/constraint/permission.js +0 -20
- package/dist/constraint-runtime/src/context.js +0 -30
- package/dist/constraint-runtime/src/coordinator/index.js +0 -12
- package/dist/constraint-runtime/src/cost_hook.js +0 -4
- package/dist/constraint-runtime/src/cost_tracker.js +0 -8
- package/dist/constraint-runtime/src/deferred_init.js +0 -10
- package/dist/constraint-runtime/src/direct_modes.js +0 -6
- package/dist/constraint-runtime/src/dynamic-tool-loader.js +0 -85
- package/dist/constraint-runtime/src/entrypoints/index.js +0 -12
- package/dist/constraint-runtime/src/execution_registry.js +0 -44
- package/dist/constraint-runtime/src/history.js +0 -9
- package/dist/constraint-runtime/src/hooks/index.js +0 -12
- package/dist/constraint-runtime/src/index.js +0 -26
- package/dist/constraint-runtime/src/ink.js +0 -4
- package/dist/constraint-runtime/src/keybindings/index.js +0 -12
- package/dist/constraint-runtime/src/memdir/index.js +0 -12
- package/dist/constraint-runtime/src/migrations/index.js +0 -12
- package/dist/constraint-runtime/src/models.js +0 -1
- package/dist/constraint-runtime/src/moreright/index.js +0 -12
- package/dist/constraint-runtime/src/native_ts/index.js +0 -12
- package/dist/constraint-runtime/src/output_styles/index.js +0 -12
- package/dist/constraint-runtime/src/parity_audit.js +0 -12
- package/dist/constraint-runtime/src/plugins/index.js +0 -12
- package/dist/constraint-runtime/src/port_manifest.js +0 -11
- package/dist/constraint-runtime/src/prefetch.js +0 -9
- package/dist/constraint-runtime/src/query.js +0 -1
- package/dist/constraint-runtime/src/remote/index.js +0 -12
- package/dist/constraint-runtime/src/remote_runtime.js +0 -9
- package/dist/constraint-runtime/src/runtime/index.js +0 -1
- package/dist/constraint-runtime/src/runtime/session.js +0 -35
- package/dist/constraint-runtime/src/schemas/index.js +0 -12
- package/dist/constraint-runtime/src/screens/index.js +0 -12
- package/dist/constraint-runtime/src/server/index.js +0 -12
- package/dist/constraint-runtime/src/services/index.js +0 -12
- package/dist/constraint-runtime/src/session_store.js +0 -22
- package/dist/constraint-runtime/src/setup.js +0 -30
- package/dist/constraint-runtime/src/skills/index.js +0 -1
- package/dist/constraint-runtime/src/skills/skill-registry.js +0 -28
- package/dist/constraint-runtime/src/state/index.js +0 -12
- package/dist/constraint-runtime/src/system_init.js +0 -20
- package/dist/constraint-runtime/src/thinking/engine.js +0 -42
- package/dist/constraint-runtime/src/thinking/index.js +0 -1
- package/dist/constraint-runtime/src/tool_pool.js +0 -8
- package/dist/constraint-runtime/src/tools/OpenCLI/execAdapter.js +0 -7
- package/dist/constraint-runtime/src/tools/OpenCLI/listAdapters.js +0 -7
- package/dist/constraint-runtime/src/tools/OpenCLI/runCommand.js +0 -7
- package/dist/constraint-runtime/src/tools/SafeSDK/confirmTransaction.js +0 -6
- package/dist/constraint-runtime/src/tools/SafeSDK/createTransaction.js +0 -8
- package/dist/constraint-runtime/src/tools/SafeSDK/executeTransaction.js +0 -6
- package/dist/constraint-runtime/src/tools/SafeSDK/getBalance.js +0 -6
- package/dist/constraint-runtime/src/tools/SafeSDK/getPendingTransactions.js +0 -6
- package/dist/constraint-runtime/src/tools/SafeSDK/proposeTransaction.js +0 -6
- package/dist/constraint-runtime/src/tools.js +0 -80
- package/dist/constraint-runtime/src/transcript.js +0 -19
- package/dist/constraint-runtime/src/types/index.js +0 -12
- package/dist/constraint-runtime/src/upstream_proxy/index.js +0 -12
- package/dist/constraint-runtime/src/utils/index.js +0 -12
- package/dist/constraint-runtime/src/vim/index.js +0 -12
- package/dist/constraint-runtime/src/voice/index.js +0 -12
- package/dist/constraint-runtime/tests/agent.test.js +0 -16
- package/dist/constraint-runtime/tests/constraint.test.js +0 -41
- package/dist/constraint-runtime/tests/skill.test.js +0 -19
- package/dist/constraint-runtime/tests/thinking.test.js +0 -22
- package/dist/pi-ecosystem-colony/index.js +0 -365
- package/dist/social/ant-colony/AdaptiveHeartbeat.js +0 -101
- package/dist/social/ant-colony/PheromoneEngine.js +0 -227
- package/dist/social/ant-colony/index.js +0 -6
- package/dist/social/ant-colony/types.js +0 -24
- package/dist/test/ai-judgment-test.js +0 -80
- package/dist/test/bollharness-integration.test.js +0 -318
- package/dist/test/channel-agent-multi-dialogue.js +0 -205
- package/dist/test/channel-heartbeat-agent-test.js +0 -201
- package/dist/test/constraint-layer.test.js +0 -164
- package/dist/test/diap-identity-test.js +0 -172
- package/dist/test/diap-quick-test.js +0 -62
- package/dist/test/global-shared-context.test.js +0 -315
- package/dist/test/harness-judgment-injection.test.js +0 -246
- package/dist/test/harness-workflow-integrator-test.js +0 -228
- package/dist/test/human-value-store.test.js +0 -243
- package/dist/test/hybrid-integration-test.js +0 -118
- package/dist/test/hybrid-messenger-verify.js +0 -55
- package/dist/test/iroh-bistream-debug.js +0 -38
- package/dist/test/iroh-communication.test.js +0 -66
- package/dist/test/iroh-debug-test.js +0 -57
- package/dist/test/iroh-diap-test.js +0 -71
- package/dist/test/iroh-direct-connect.js +0 -55
- package/dist/test/iroh-e2e-fixed.js +0 -89
- package/dist/test/iroh-e2e-same-process.js +0 -63
- package/dist/test/iroh-e2e.js +0 -66
- package/dist/test/iroh-final-e2e.js +0 -72
- package/dist/test/iroh-relay-test.js +0 -37
- package/dist/test/iroh-simple-test.js +0 -41
- package/dist/test/iroh-transport-verify.js +0 -54
- package/dist/test/iroh-transport.test.js +0 -37
- package/dist/test/iroh-two-nodes.js +0 -70
- package/dist/test/iroh-verify.js +0 -44
- package/dist/test/judgment-decision.test.js +0 -219
- package/dist/test/llm-judgment-integration.test.js +0 -220
- package/dist/test/p2p-agent-complex-dialogue.js +0 -385
- package/dist/test/p2p-agent-dialogue.js +0 -341
- package/dist/test/p2p-agent-full-bidirectional.js +0 -510
- package/dist/test/p2p-agent-harness-flow.js +0 -437
- package/dist/test/p2p-agent-harness-single.js +0 -143
- package/dist/test/p2p-ai-dialogue-test.js +0 -318
- package/dist/test/p2p-cid-connect-test.js +0 -195
- package/dist/test/p2p-connect-receiver.js +0 -69
- package/dist/test/p2p-doc-transfer.js +0 -110
- package/dist/test/p2p-identity-page-test.js +0 -77
- package/dist/test/p2p-iroh-test.js +0 -171
- package/dist/test/p2p-minimal-test.js +0 -241
- package/dist/test/p2p-node-1.js +0 -148
- package/dist/test/p2p-node-2.js +0 -148
- package/dist/test/p2p-server.js +0 -281
- package/dist/test/p2p-two-nodes-test.js +0 -438
- package/dist/test/pi-sdk.test.js +0 -44
- package/dist/test/set-persona.js +0 -40
- package/dist/test/simple.test.js +0 -9
- package/dist/test/storage-integration.test.js +0 -150
- package/dist/test/subagent-manager.test.js +0 -276
- package/dist/test/test-gate-flow.test.js +0 -81
- package/dist/test/workflow-engine.test.js +0 -87
- package/dist/test/workflow-pivot-loop.test.js +0 -246
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ReAct Loop 决策层 (decideNext + runStep)
|
|
3
|
+
*
|
|
4
|
+
* 2026-06-30 抽出:
|
|
5
|
+
* - 原 PiAgentSession.runReActLoop 是 1100+ 行的 private 巨石函数, 既管 LLM IO 又管状态机.
|
|
6
|
+
* - 现在抽出"决策下一步"为纯函数, claude code / 外部 harness 可独立消融验证.
|
|
7
|
+
*
|
|
8
|
+
* 决策表 (apply 到 LLM 回复 + 当前 state):
|
|
9
|
+
* 1. reply 是 AI failure sentinel → continue (push error 进 history, 让 LLM 反思)
|
|
10
|
+
* 2. reply 可 parse 出 tool call → execute-tool (调 tool, push result 进 history)
|
|
11
|
+
* 3. reply 含 <final gen> 且无 tool_call → final (LLM 显式终止)
|
|
12
|
+
* 4. reply 无 <final gen>, 无 tool_call → continue (LLM 可能再想想, 让下一轮重新生成)
|
|
13
|
+
*
|
|
14
|
+
* 这个模块不调 LLM, 只接受 reply/state 返回下一步决策. LLM 调用 + 真正状态变更
|
|
15
|
+
* 仍由 PiAgentSession 协调. 这样 claude code 可以独立 unit-test 决策边界.
|
|
16
|
+
*/
|
|
17
|
+
import { parseToolCall, isFinalResponse } from './parse-tool-call.js';
|
|
18
|
+
/** 把 LLM 回复归到一种 next action — 纯函数. */
|
|
19
|
+
export function decideNext(ctx) {
|
|
20
|
+
if (ctx.signalAborted) {
|
|
21
|
+
return { kind: 'continue', reason: 'signal-aborted-upstream' };
|
|
22
|
+
}
|
|
23
|
+
if (ctx.atMaxIterations) {
|
|
24
|
+
return { kind: 'final', answer: '(max iterations reached, fail-safe)', reason: 'max-iterations' };
|
|
25
|
+
}
|
|
26
|
+
// AI failure sentinel 优先 (matches pi-sdk.ts 现有顺序 L2558)
|
|
27
|
+
// LLM 偶尔在 retry 中夹杂 sentinel + 工具块, sentinel 是必须先看的"停止信号"
|
|
28
|
+
if (isAiFailureSentinel(ctx.reply)) {
|
|
29
|
+
return { kind: 'continue', reason: 'ai-failure-sentinel' };
|
|
30
|
+
}
|
|
31
|
+
// 工具调用优先 (per 2026-06-19 fix) — 在 sentinel 之后
|
|
32
|
+
const tc = parseToolCall(ctx.reply, { tools: ctx.knownToolNames });
|
|
33
|
+
if (tc) {
|
|
34
|
+
if (!ctx.knownToolNames.has(tc.name)) {
|
|
35
|
+
// 名字解析出来但不在已知集合 — LLM 幻觉高频场景
|
|
36
|
+
return { kind: 'parse-error', reason: `unknown-tool:${tc.name}` };
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
kind: 'execute-tool',
|
|
40
|
+
name: tc.name,
|
|
41
|
+
args: tc.args,
|
|
42
|
+
reason: 'parse-tool-call',
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
// final gen 终止
|
|
46
|
+
if (isFinalResponse(ctx.reply)) {
|
|
47
|
+
return { kind: 'final', answer: ctx.reply, reason: 'final-gen-marker' };
|
|
48
|
+
}
|
|
49
|
+
// 默认 — 普通 text, 让 LLM 继续想
|
|
50
|
+
return { kind: 'continue', reason: 'no-tool-no-final' };
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* isAiFailureSentinel — 检测 LLM 上游失败哨兵.
|
|
54
|
+
* bolloon 用 [AI 服务调用失败] 前缀作为哨兵 — runReActLoop 收到时
|
|
55
|
+
* 把错误当 tool result push 进 history, 让 LLM 下轮能反思.
|
|
56
|
+
*/
|
|
57
|
+
export function isAiFailureSentinel(reply) {
|
|
58
|
+
if (!reply)
|
|
59
|
+
return false;
|
|
60
|
+
const trimmed = reply.trim();
|
|
61
|
+
return trimmed.startsWith('[AI 服务调用失败]') ||
|
|
62
|
+
trimmed.startsWith('[AI 调用失败]') ||
|
|
63
|
+
trimmed.startsWith('[错误:');
|
|
64
|
+
}
|
|
65
|
+
/** extractFinalText — 抽 LLM 输出里能呈现给用户的最终文字 (已是 Message 状态). */
|
|
66
|
+
export function extractFinalText(reply, marker = '<final gen>') {
|
|
67
|
+
const idx = reply.indexOf(marker);
|
|
68
|
+
if (idx !== -1) {
|
|
69
|
+
const after = reply.substring(idx + marker.length).trim();
|
|
70
|
+
return after || reply.substring(0, idx).trim();
|
|
71
|
+
}
|
|
72
|
+
return reply;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* 错误累计判定 — 当 totalErrors 达阈值时, 给 final 答案 + 汇总.
|
|
76
|
+
* 这是独立的小函数, 因为 runReActLoop 用它决定是否继续.
|
|
77
|
+
*/
|
|
78
|
+
export function shouldForceExit(totalErrors, maxTotalErrors) {
|
|
79
|
+
return totalErrors >= maxTotalErrors;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* 2026-07-01 (v0.2.4 子任务 1): 迭代上限判定 — 纯函数.
|
|
83
|
+
* 之前 runReActLoop 在循环顶部硬编码 `if (iteration >= MAX_REACT_ITERATIONS)`,
|
|
84
|
+
* 抽出后 claude code 可独立单测 + 替代.
|
|
85
|
+
*/
|
|
86
|
+
export function decideMaxIterations(iteration, maxIterations) {
|
|
87
|
+
return {
|
|
88
|
+
shouldExit: iteration >= maxIterations,
|
|
89
|
+
finalAnswer: iteration >= maxIterations
|
|
90
|
+
? '(本轮 ReAct 循环达到最大步数, 强制结束)'
|
|
91
|
+
: '',
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* 2026-07-01 (v0.2.4 子任务 1): context overflow 判定 — 纯函数.
|
|
96
|
+
* 之前 runReActLoop 紧接 decideMaxIterations 之后硬编码 token 阈值检查.
|
|
97
|
+
* 抽出后行为一致 + 可测.
|
|
98
|
+
*/
|
|
99
|
+
export function decideContextOverflow(estimatedTokens, threshold) {
|
|
100
|
+
return {
|
|
101
|
+
shouldExit: estimatedTokens > threshold,
|
|
102
|
+
finalAnswer: estimatedTokens > threshold
|
|
103
|
+
? `(本轮 ReAct 循环因上下文溢出终止)`
|
|
104
|
+
: '',
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* 2026-07-01 (v0.2.4 子任务 1): compact 触发判定 — 纯函数.
|
|
109
|
+
* 之前 runReActLoop 入口处: estimatedTokens > compactThreshold → 跑 maybeAutoCompact.
|
|
110
|
+
* 抽出纯函数后, claude code 能 dry-run "token X 时该 compact 吗".
|
|
111
|
+
*/
|
|
112
|
+
export function shouldCompactBeforeIteration(estimatedTokens, compactThreshold) {
|
|
113
|
+
return estimatedTokens > compactThreshold;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* 同一工具连续失败 N 次, 提示 LLM 不要再用同一个工具 (force final).
|
|
117
|
+
*/
|
|
118
|
+
export function shouldHintToStopSameTool(consecutiveFails, threshold) {
|
|
119
|
+
return consecutiveFails >= threshold;
|
|
120
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* session-store — 持久化 Message[] (LLM 历史) 到 ~/.bolloon/sessions/cache/<key>.json
|
|
3
|
+
*
|
|
4
|
+
* 2026-06-30 抽出:
|
|
5
|
+
* - 之前 PiAgentSession 用 constraint-runtime 的 saveSession (存 string[]) + 独立的 hydrateMessageHistory
|
|
6
|
+
* 读路径, 写路径不存在 — claude code / 外部 harness 想"接续 prompt"完全无入口.
|
|
7
|
+
* - 现在抽出 SessionStore 模块, 暴露纯 IO 接口, 完全可消融测试:
|
|
8
|
+
* saveMessages(key, msgs) — 写
|
|
9
|
+
* loadMessages(key) — 读
|
|
10
|
+
* listKeys() — 列全部 key
|
|
11
|
+
* deleteKey(key) — 清
|
|
12
|
+
* - 任何目录位置都可注入 (默认 ~/.bolloon/sessions/cache/), 测试里指向临时 dir.
|
|
13
|
+
*
|
|
14
|
+
* 使用:
|
|
15
|
+
* import { sessionStore } from './session-store';
|
|
16
|
+
* sessionStore.saveMessages('cli:session-A', messageHistory);
|
|
17
|
+
* const restored = sessionStore.loadMessages('cli:session-A');
|
|
18
|
+
*/
|
|
19
|
+
import * as fs from 'fs/promises';
|
|
20
|
+
import * as path from 'path';
|
|
21
|
+
import * as os from 'os';
|
|
22
|
+
export class SessionStore {
|
|
23
|
+
cacheDir;
|
|
24
|
+
constructor(config = {}) {
|
|
25
|
+
this.cacheDir = config.cacheDir ?? path.join(os.homedir(), '.bolloon', 'sessions', 'cache');
|
|
26
|
+
}
|
|
27
|
+
/** 当前缓存目录 (只读). */
|
|
28
|
+
get dir() {
|
|
29
|
+
return this.cacheDir;
|
|
30
|
+
}
|
|
31
|
+
/** 单文件路径 — 暴露出来方便测试和外部读取.
|
|
32
|
+
*
|
|
33
|
+
* 2026-07-04 fix: Windows 文件名禁止 `:` (NTFS). web server 用 `channelId:currentSessionId`
|
|
34
|
+
* 拼 sessionKey (server.ts:1759 之类), 会含 `:`. 在 Linux/macOS 上能用, Windows 上
|
|
35
|
+
* fs.writeFile 抛 EINVAL. 修法: filename 层 escape `:` → `__`, key 保持不变
|
|
36
|
+
* (load/save/listKeys/deleteKey 全部透明).
|
|
37
|
+
*/
|
|
38
|
+
pathFor(key) {
|
|
39
|
+
if (!key || key.includes('/') || key.includes('..')) {
|
|
40
|
+
throw new Error(`SessionStore: invalid key ${JSON.stringify(key)}`);
|
|
41
|
+
}
|
|
42
|
+
return path.join(this.cacheDir, `${SessionStore.filenameEscape(key)}.json`);
|
|
43
|
+
}
|
|
44
|
+
/** 把 session key 转换成跨平台安全的 filename (escape Windows 非法字符). */
|
|
45
|
+
static filenameEscape(key) {
|
|
46
|
+
return key.replace(/:/g, '__');
|
|
47
|
+
}
|
|
48
|
+
/** listKeys 反向: 把 filename 还原成 session key. */
|
|
49
|
+
static filenameUnescape(filenameKey) {
|
|
50
|
+
return filenameKey.replace(/__/g, ':');
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* 写 history to disk.
|
|
54
|
+
* - 自动 ensure 缓存目录
|
|
55
|
+
* - 失败抛错 (写失败不应静默 — 用户可能依赖持久化作为审计)
|
|
56
|
+
*/
|
|
57
|
+
async saveMessages(key, messages) {
|
|
58
|
+
if (!key || key.includes('/') || key.includes('..')) {
|
|
59
|
+
throw new Error(`SessionStore: invalid key ${JSON.stringify(key)}`);
|
|
60
|
+
}
|
|
61
|
+
await fs.mkdir(this.cacheDir, { recursive: true });
|
|
62
|
+
const payload = {
|
|
63
|
+
key,
|
|
64
|
+
messages,
|
|
65
|
+
metadata: {
|
|
66
|
+
savedAt: Date.now(),
|
|
67
|
+
cwd: process.cwd(),
|
|
68
|
+
totalCount: messages.length,
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
const filePath = this.pathFor(key);
|
|
72
|
+
// 写临时文件 + rename — 防止半写损坏
|
|
73
|
+
const tmpPath = `${filePath}.tmp`;
|
|
74
|
+
await fs.writeFile(tmpPath, JSON.stringify(payload, null, 2), 'utf-8');
|
|
75
|
+
await fs.rename(tmpPath, filePath);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* 同步版 (PiAgentSession 里多条路径用同步 — 避免 async 链)
|
|
79
|
+
* 失败抛错
|
|
80
|
+
*/
|
|
81
|
+
saveMessagesSync(key, messages) {
|
|
82
|
+
if (!key || key.includes('/') || key.includes('..')) {
|
|
83
|
+
throw new Error(`SessionStore: invalid key ${JSON.stringify(key)}`);
|
|
84
|
+
}
|
|
85
|
+
fsSyncMkdir(this.cacheDir);
|
|
86
|
+
const payload = {
|
|
87
|
+
key,
|
|
88
|
+
messages,
|
|
89
|
+
metadata: {
|
|
90
|
+
savedAt: Date.now(),
|
|
91
|
+
cwd: process.cwd(),
|
|
92
|
+
totalCount: messages.length,
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
const filePath = this.pathFor(key);
|
|
96
|
+
const tmpPath = `${filePath}.tmp`;
|
|
97
|
+
fsSyncWrite(tmpPath, JSON.stringify(payload, null, 2));
|
|
98
|
+
fsSyncRename(tmpPath, filePath);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* 读 history from disk.
|
|
102
|
+
* - key 不存在 → 返回 null (不抛错)
|
|
103
|
+
* - 文件存在但 JSON 损坏 → 抛错 (claude code 应该感知坏数据)
|
|
104
|
+
* - 文件 schema 不匹配 (新版格式) → 抛错含 details
|
|
105
|
+
*/
|
|
106
|
+
async loadMessages(key) {
|
|
107
|
+
if (!key || key.includes('/') || key.includes('..')) {
|
|
108
|
+
throw new Error(`SessionStore: invalid key ${JSON.stringify(key)}`);
|
|
109
|
+
}
|
|
110
|
+
const filePath = this.pathFor(key);
|
|
111
|
+
let raw;
|
|
112
|
+
try {
|
|
113
|
+
raw = await fs.readFile(filePath, 'utf-8');
|
|
114
|
+
}
|
|
115
|
+
catch (e) {
|
|
116
|
+
if (e?.code === 'ENOENT')
|
|
117
|
+
return null;
|
|
118
|
+
throw e;
|
|
119
|
+
}
|
|
120
|
+
const parsed = JSON.parse(raw);
|
|
121
|
+
if (!parsed || !Array.isArray(parsed.messages)) {
|
|
122
|
+
throw new Error(`SessionStore: ${key} schema invalid (missing messages[])`);
|
|
123
|
+
}
|
|
124
|
+
return parsed.messages;
|
|
125
|
+
}
|
|
126
|
+
/** 列所有 keys — 用于调试 / claude code "看有哪些 session 可接续". */
|
|
127
|
+
async listKeys() {
|
|
128
|
+
try {
|
|
129
|
+
await fs.mkdir(this.cacheDir, { recursive: true });
|
|
130
|
+
const files = await fs.readdir(this.cacheDir);
|
|
131
|
+
return files
|
|
132
|
+
.filter(f => f.endsWith('.json') && !f.endsWith('.tmp'))
|
|
133
|
+
.map(f => SessionStore.filenameUnescape(f.slice(0, -'.json'.length)))
|
|
134
|
+
.sort();
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return [];
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/** 删一条 session — 失败抛错. */
|
|
141
|
+
async deleteKey(key) {
|
|
142
|
+
if (!key || key.includes('/') || key.includes('..')) {
|
|
143
|
+
throw new Error(`SessionStore: invalid key ${JSON.stringify(key)}`);
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
await fs.unlink(this.pathFor(key));
|
|
147
|
+
}
|
|
148
|
+
catch (e) {
|
|
149
|
+
if (e?.code !== 'ENOENT')
|
|
150
|
+
throw e;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// --- sync fs helpers (避免 import 'fs' 二次) ---
|
|
155
|
+
function fsSyncMkdir(dir) {
|
|
156
|
+
try {
|
|
157
|
+
require('fs').mkdirSync(dir, { recursive: true });
|
|
158
|
+
}
|
|
159
|
+
catch (e) {
|
|
160
|
+
if (e?.code !== 'EEXIST')
|
|
161
|
+
throw e;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function fsSyncWrite(filePath, content) {
|
|
165
|
+
require('fs').writeFileSync(filePath, content, 'utf-8');
|
|
166
|
+
}
|
|
167
|
+
function fsSyncRename(from, to) {
|
|
168
|
+
require('fs').renameSync(from, to);
|
|
169
|
+
}
|
|
170
|
+
/** 默认 store 实例 (用 ~/.bolloon/sessions/cache/). 业务代码用这个. */
|
|
171
|
+
export const sessionStore = new SessionStore();
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* "commandAllowlist": ["git", "npm", "tsc", "vitest", "cat", "ls", "..."],
|
|
14
14
|
* "commandDenylist": ["rm", "mv", "chmod", "sudo", "su", "curl", "wget"],
|
|
15
15
|
* "pathAllowlist": [
|
|
16
|
-
* "src/web/client.
|
|
16
|
+
* "src/web/client.ts",
|
|
17
17
|
* "src/agents/workflow-engine.ts",
|
|
18
18
|
* "*.md",
|
|
19
19
|
* "docs/**"
|
|
@@ -58,7 +58,7 @@ const FALLBACK_COMMAND_ALLOWLIST = new Set([
|
|
|
58
58
|
]);
|
|
59
59
|
const FALLBACK_PATH_ALLOWLIST = [
|
|
60
60
|
// 自由区: AI 可以改
|
|
61
|
-
'src/web/client.
|
|
61
|
+
'src/web/client.ts',
|
|
62
62
|
'src/web/style.css',
|
|
63
63
|
'src/agents/workflow-engine.ts',
|
|
64
64
|
'src/agents/workflow-pivot-loop.ts',
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ToolRegistry — 工具注册表 + 别名解析器
|
|
3
|
+
*
|
|
4
|
+
* 2026-06-30 抽出:
|
|
5
|
+
* - 原 PiAgentSession.tools Map + resolveToolName 是 private 方法, 测试无法直接验证 alias 映射
|
|
6
|
+
* - 现在抽到独立模块, 完全可消融测试.
|
|
7
|
+
*
|
|
8
|
+
* 设计目标:
|
|
9
|
+
* 1. claude code / 外部 harness 直接 import, 用同一份 alias 表
|
|
10
|
+
* 2. 注册/反注册/查表都是纯方法, 无副作用
|
|
11
|
+
* 3. 默认 alias 表跟 parse-tool-call.ts 保持一致 (read→read_file, bash→shell_exec 等)
|
|
12
|
+
* 4. 自定义 alias 可注入 (业务可扩)
|
|
13
|
+
*
|
|
14
|
+
* 用法:
|
|
15
|
+
* import { ToolRegistry, DEFAULT_ALIASES } from './tool-registry';
|
|
16
|
+
* const reg = new ToolRegistry();
|
|
17
|
+
* reg.register({ name: 'shell_exec', description: '...', parameters: {}, execute });
|
|
18
|
+
* reg.has('bash'); // true (alias 命中)
|
|
19
|
+
* reg.resolve('bash'); // 'shell_exec'
|
|
20
|
+
* reg.get('shell_exec')?.execute({ command: 'ls' });
|
|
21
|
+
*/
|
|
22
|
+
/** 默认 alias 表 — Claude Code 风格 → bolloon 工具名 */
|
|
23
|
+
export const DEFAULT_ALIASES = Object.freeze({
|
|
24
|
+
// file ops
|
|
25
|
+
read: 'read_file',
|
|
26
|
+
edit: 'edit_file',
|
|
27
|
+
write: 'write_file',
|
|
28
|
+
rm: 'delete_file',
|
|
29
|
+
mv: 'move_file',
|
|
30
|
+
cat: 'read_file',
|
|
31
|
+
// shell
|
|
32
|
+
bash: 'shell_exec',
|
|
33
|
+
shell: 'shell_exec',
|
|
34
|
+
sh: 'shell_exec',
|
|
35
|
+
// test/build
|
|
36
|
+
test: 'vitest_run',
|
|
37
|
+
vitest: 'vitest_run',
|
|
38
|
+
typecheck: 'tsc_check',
|
|
39
|
+
tsc: 'tsc_check',
|
|
40
|
+
// git
|
|
41
|
+
log: 'git_log',
|
|
42
|
+
show: 'git_show',
|
|
43
|
+
diff: 'git_diff',
|
|
44
|
+
commit: 'git_commit',
|
|
45
|
+
push: 'git_push',
|
|
46
|
+
branch: 'git_branch',
|
|
47
|
+
checkout: 'git_branch',
|
|
48
|
+
stash: 'git_stash',
|
|
49
|
+
// task
|
|
50
|
+
todo_write: 'create_task',
|
|
51
|
+
todowrite: 'create_task',
|
|
52
|
+
task: 'create_task',
|
|
53
|
+
});
|
|
54
|
+
export class ToolRegistry {
|
|
55
|
+
tools = new Map();
|
|
56
|
+
aliases;
|
|
57
|
+
constructor(aliases = DEFAULT_ALIASES) {
|
|
58
|
+
this.aliases = new Map();
|
|
59
|
+
for (const [k, v] of Object.entries(aliases)) {
|
|
60
|
+
this.aliases.set(k.toLowerCase(), v);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/** 注册一个 tool (alias 字典不动) */
|
|
64
|
+
register(tool) {
|
|
65
|
+
if (!tool.name)
|
|
66
|
+
throw new Error('ToolRegistry.register: tool.name 必填');
|
|
67
|
+
this.tools.set(tool.name, tool);
|
|
68
|
+
}
|
|
69
|
+
/** 批量注册 */
|
|
70
|
+
registerAll(tools) {
|
|
71
|
+
for (const t of tools)
|
|
72
|
+
this.register(t);
|
|
73
|
+
}
|
|
74
|
+
/** 反注册 — 名字 + 对应 alias (如果有指向它) 全部清掉. */
|
|
75
|
+
unregister(name) {
|
|
76
|
+
const existed = this.tools.delete(name);
|
|
77
|
+
// 反向清 alias
|
|
78
|
+
for (const [alias, target] of this.aliases.entries()) {
|
|
79
|
+
if (target === name)
|
|
80
|
+
this.aliases.delete(alias);
|
|
81
|
+
}
|
|
82
|
+
return existed;
|
|
83
|
+
}
|
|
84
|
+
/** 查 tool (只查主名, alias 走 resolve). */
|
|
85
|
+
get(name) {
|
|
86
|
+
return this.tools.get(name);
|
|
87
|
+
}
|
|
88
|
+
/** 是否注册 (主名 + alias 命中都算). */
|
|
89
|
+
has(name) {
|
|
90
|
+
if (this.tools.has(name))
|
|
91
|
+
return true;
|
|
92
|
+
const aliased = this.aliases.get(name.toLowerCase());
|
|
93
|
+
return aliased !== undefined && this.tools.has(aliased);
|
|
94
|
+
}
|
|
95
|
+
/** 把 LLM 输出的名字 (可能是 alias / 大小写不一致) 解析为标准主名. */
|
|
96
|
+
resolve(name) {
|
|
97
|
+
if (this.tools.has(name))
|
|
98
|
+
return name;
|
|
99
|
+
const lower = name.toLowerCase();
|
|
100
|
+
const aliased = this.aliases.get(lower);
|
|
101
|
+
if (aliased && this.tools.has(aliased))
|
|
102
|
+
return aliased;
|
|
103
|
+
if (this.tools.has(lower))
|
|
104
|
+
return lower;
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
/** 列出所有主名. 顺序按注册时间. */
|
|
108
|
+
list() {
|
|
109
|
+
return Array.from(this.tools.keys());
|
|
110
|
+
}
|
|
111
|
+
/** 当前 alias 表快照 (调试用). */
|
|
112
|
+
get aliasMap() {
|
|
113
|
+
const out = {};
|
|
114
|
+
for (const [k, v] of this.aliases.entries())
|
|
115
|
+
out[k] = v;
|
|
116
|
+
return out;
|
|
117
|
+
}
|
|
118
|
+
/** 个数 */
|
|
119
|
+
get size() {
|
|
120
|
+
return this.tools.size;
|
|
121
|
+
}
|
|
122
|
+
/** 清空 */
|
|
123
|
+
clear() {
|
|
124
|
+
this.tools.clear();
|
|
125
|
+
// alias 表保留 (除非调用方显式 clear, alias 表是稳定的)
|
|
126
|
+
}
|
|
127
|
+
/** 调一个 tool, 名字可能是 alias. throw if 未知. */
|
|
128
|
+
async invoke(name, args) {
|
|
129
|
+
const canonical = this.resolve(name);
|
|
130
|
+
if (!canonical) {
|
|
131
|
+
throw new Error(`ToolRegistry.invoke: 未知工具名 ${JSON.stringify(name)}`);
|
|
132
|
+
}
|
|
133
|
+
const tool = this.tools.get(canonical);
|
|
134
|
+
return tool.execute(args);
|
|
135
|
+
}
|
|
136
|
+
}
|