@bolloon/bolloon-agent 0.2.12 → 0.2.14
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 +29 -8
- package/dist/llm/llm-judgment-client.js +102 -102
- package/dist/llm/pi-ai.js +139 -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,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* parseToolCall — 从 LLM 输出 (minimax / Hermes / Qwen / GLM / Anthropic 等)
|
|
3
|
+
* 中提取 tool_call { name, args }.
|
|
4
|
+
*
|
|
5
|
+
* 2026-06-30 抽出: 原先作为 PiAgentSession 的 private 方法藏在 4522 行类里,
|
|
6
|
+
* 测试只能复刻逻辑不能 import 真源 = 测试通过 ≠ 实际工作. 现在抽出为纯函数,
|
|
7
|
+
* 接受 ctx.tools (可用工具名集合) + 可选 ctx.resolveAlias (别名 → 真实工具名).
|
|
8
|
+
*
|
|
9
|
+
* 用法:
|
|
10
|
+
* import { parseToolCall } from './parse-tool-call';
|
|
11
|
+
* const r = parseToolCall(content, { tools: new Set(['shell_exec', 'read_file']) });
|
|
12
|
+
* if (r) tool.execute(r.args);
|
|
13
|
+
*/
|
|
14
|
+
const SHELL_KEYWORDS = ['git', 'npx', 'npm', 'tsx', 'tsc', 'vitest', 'node', 'mkdir', 'touch', 'ls', 'echo', 'cat', 'head', 'tail', 'wc', 'pwd', 'date'];
|
|
15
|
+
/**
|
|
16
|
+
* 默认的 alias resolver — Claude Code 风格 → bolloon 工具名
|
|
17
|
+
* 当 ctx.resolveAlias 缺失时使用.
|
|
18
|
+
*/
|
|
19
|
+
function defaultResolveAlias(name, tools) {
|
|
20
|
+
if (tools.has(name))
|
|
21
|
+
return name;
|
|
22
|
+
const lower = name.toLowerCase();
|
|
23
|
+
const aliasMap = {
|
|
24
|
+
read: 'read_file',
|
|
25
|
+
edit: 'edit_file',
|
|
26
|
+
write: 'write_file',
|
|
27
|
+
rm: 'delete_file',
|
|
28
|
+
mv: 'move_file',
|
|
29
|
+
bash: 'shell_exec',
|
|
30
|
+
shell: 'shell_exec',
|
|
31
|
+
sh: 'shell_exec',
|
|
32
|
+
cat: 'read_file',
|
|
33
|
+
test: 'vitest_run',
|
|
34
|
+
vitest: 'vitest_run',
|
|
35
|
+
typecheck: 'tsc_check',
|
|
36
|
+
tsc: 'tsc_check',
|
|
37
|
+
log: 'git_log',
|
|
38
|
+
show: 'git_show',
|
|
39
|
+
diff: 'git_diff',
|
|
40
|
+
commit: 'git_commit',
|
|
41
|
+
push: 'git_push',
|
|
42
|
+
branch: 'git_branch',
|
|
43
|
+
checkout: 'git_branch',
|
|
44
|
+
stash: 'git_stash',
|
|
45
|
+
todo_write: 'create_task',
|
|
46
|
+
todowrite: 'create_task',
|
|
47
|
+
task: 'create_task',
|
|
48
|
+
};
|
|
49
|
+
const aliased = aliasMap[lower];
|
|
50
|
+
if (aliased && tools.has(aliased))
|
|
51
|
+
return aliased;
|
|
52
|
+
if (tools.has(lower))
|
|
53
|
+
return lower;
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
/** 拆分 args.command: "git status" → { command: "git", args: "status" } */
|
|
57
|
+
function autoSplitCommand(args) {
|
|
58
|
+
if (typeof args.command === 'string' && args.command.includes(' ') && !args.args) {
|
|
59
|
+
const parts = args.command.split(/\s+/);
|
|
60
|
+
args.command = parts[0];
|
|
61
|
+
args.args = parts.slice(1).join(' ');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** 用 resolver 解析工具名 — 接受任意 resolver (默认或自定义). */
|
|
65
|
+
function resolve(ctx, name) {
|
|
66
|
+
if (ctx.resolveAlias)
|
|
67
|
+
return ctx.resolveAlias(name);
|
|
68
|
+
return defaultResolveAlias(name, ctx.tools);
|
|
69
|
+
}
|
|
70
|
+
export function parseToolCall(content, ctx) {
|
|
71
|
+
if (!content)
|
|
72
|
+
return null;
|
|
73
|
+
// === 0. 剥离 思考块 — 必须先做, 否则旧 regex `<(\w+)>...</\1>` 会先匹配 想想标签 ===
|
|
74
|
+
const strippedContent = content.replace(/<think[\s\S]*?<\/think/g, '');
|
|
75
|
+
// === 1. JSON function-call (OpenAI / Anthropic / Minimax-style) ===
|
|
76
|
+
const jsonPatterns = [
|
|
77
|
+
// markdown json code block + OpenAI 块, 同时匹配 arguments/input 字段
|
|
78
|
+
/(?:```(?:json|json5)?\s*\n?)?\{[\s\S]*?"name"\s*:\s*["'](\w+)["']\s*,\s*["']?(?:arguments|input)["']?\s*:\s*(\{[\s\S]*?\})\s*\}/,
|
|
79
|
+
];
|
|
80
|
+
for (const p of jsonPatterns) {
|
|
81
|
+
const m = content.match(p);
|
|
82
|
+
if (m) {
|
|
83
|
+
const name = m[1];
|
|
84
|
+
let args = {};
|
|
85
|
+
try {
|
|
86
|
+
const parsed = JSON.parse(m[2]);
|
|
87
|
+
if (parsed && typeof parsed === 'object') {
|
|
88
|
+
args = Object.fromEntries(Object.entries(parsed).map(([k, v]) => [k, String(v)]));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
/* 解析失败保持 args={} */
|
|
93
|
+
}
|
|
94
|
+
autoSplitCommand(args);
|
|
95
|
+
const resolved = resolve(ctx, name);
|
|
96
|
+
if (resolved) {
|
|
97
|
+
return { name: resolved, args };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// === 2. <tools:call name="X">...</tools:call> 嵌套格式 ===
|
|
102
|
+
const toolsCallMatch = content.match(/<tools:call\s+name=["'](\w+)["']>([\s\S]*?)<\/tools:call>/);
|
|
103
|
+
if (toolsCallMatch) {
|
|
104
|
+
const name = toolsCallMatch[1];
|
|
105
|
+
const inner = toolsCallMatch[2];
|
|
106
|
+
const args = {};
|
|
107
|
+
const argTags = inner.matchAll(/<tools:call\s+name=["'](\w+)["']>([\s\S]*?)<\/tools:call>/g);
|
|
108
|
+
for (const m of argTags) {
|
|
109
|
+
args[m[1]] = m[2].trim();
|
|
110
|
+
}
|
|
111
|
+
const resolved = resolve(ctx, name);
|
|
112
|
+
if (resolved) {
|
|
113
|
+
return { name: resolved, args };
|
|
114
|
+
}
|
|
115
|
+
// 兜底: 外层 tool name 不在 tools, 但内层有 command, 推断 shell_exec
|
|
116
|
+
if (args.command) {
|
|
117
|
+
const cmdFirst = args.command.split(/\s+/)[0];
|
|
118
|
+
if (SHELL_KEYWORDS.includes(cmdFirst)) {
|
|
119
|
+
return { name: 'shell_exec', args };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// === 3. XML 格式 (陆续检测多个 pattern) ===
|
|
124
|
+
const patterns = [
|
|
125
|
+
// minimax/Hermes 自闭合 XML 格式 <invoke name="X">...</invoke>
|
|
126
|
+
new RegExp('<invoke\\s+name=["\']([\\w]+)["\']>([\\s\\S]*?)</invoke>'),
|
|
127
|
+
// <function_calls> 包裹
|
|
128
|
+
new RegExp('<function_calls>[\\s\\S]*?<invoke\\s+name=["\']([\\w]+)["\']>([\\s\\S]*?)</invoke>[\\s\\S]*?</function_calls>'),
|
|
129
|
+
// <function_calls><tool name="X"><param name="Y">value</param></tool></function_calls>
|
|
130
|
+
new RegExp('<function_calls>[\\s\\S]*?<tool\\s+name=["\']([\\w]+)["\']>([\\s\\S]*?)</tool>[\\s\\S]*?</function_calls>'),
|
|
131
|
+
/调用工具[::]\s*(\w+)\s*\(([^)]*)\)/,
|
|
132
|
+
/使用工具[::]\s*(\w+)\s*\(([^)]*)\)/,
|
|
133
|
+
/tool[_\w]*[::]\s*(\w+)\s*\(([^)]*)\)/i,
|
|
134
|
+
/(\w+)\s*\(\s*([^)]*)\s*\)/,
|
|
135
|
+
// 对象字面量格式 {tool => "get_identity", args => {...}}
|
|
136
|
+
/\{\s*tool\s*=>\s*["'](\w+)["']\s*(?:,\s*args\s*=>\s*(\{[\s\S]*?\}))?\s*\}/,
|
|
137
|
+
// tool => "get_identity" (无 args 包裹)
|
|
138
|
+
/\btool\s*=>\s*["'](\w+)["']/,
|
|
139
|
+
// [TOOL_CALL] 块内 JSON 形式 {"name": "x", "args": {...}}
|
|
140
|
+
/\[TOOL_CALL\][\s\S]*?\{\s*"name"\s*:\s*"(\w+)"\s*,\s*"args"\s*:\s*(\{[\s\S]*?\})/i,
|
|
141
|
+
// "tool_name {json_args}" 形式 (单行, 无 name 字段)
|
|
142
|
+
/(?:^|\n)(\w+)\s+(\{[\s\S]*?\})(?=\n|$)/,
|
|
143
|
+
// XML 格式 <tool_name>...<arg>value</arg>...</tool_name>
|
|
144
|
+
/<(\w+)>([\s\S]*?)<\/\1>/,
|
|
145
|
+
];
|
|
146
|
+
for (const pattern of patterns) {
|
|
147
|
+
const match = strippedContent.match(pattern);
|
|
148
|
+
if (!match)
|
|
149
|
+
continue;
|
|
150
|
+
const name = match[1];
|
|
151
|
+
let args = {};
|
|
152
|
+
const rawArgs = match[2] || '';
|
|
153
|
+
if (rawArgs && rawArgs.trim().startsWith('{')) {
|
|
154
|
+
// JSON 形式, 尝试解析
|
|
155
|
+
try {
|
|
156
|
+
const parsed = JSON.parse(rawArgs);
|
|
157
|
+
if (parsed && typeof parsed === 'object') {
|
|
158
|
+
args = Object.fromEntries(Object.entries(parsed).map(([k, v]) => [k, String(v)]));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
// 解析失败就当字符串处理
|
|
163
|
+
const argPairs = rawArgs.split(',').map(s => s.trim()).filter(Boolean);
|
|
164
|
+
for (const pair of argPairs) {
|
|
165
|
+
const [key, ...valueParts] = pair.split(':').map(s => s.trim().replace(/['"]/g, ''));
|
|
166
|
+
if (key)
|
|
167
|
+
args[key] = valueParts.join(':') || '';
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
else if (rawArgs && /<[\w]/.test(rawArgs) && /<\/\w+>/.test(rawArgs)) {
|
|
172
|
+
// 2026-06-30 修: 检测 XML 子标签 (含带属性的 <parameter name="X"> 形式)
|
|
173
|
+
// 原文用 /<\w+>[\s\S]*<\/\w+>/ 检测, 但是 <parameter name="command"> 形式
|
|
174
|
+
// 首标签是 <parameter, 不是简单的 <command 所以原检测会 false,
|
|
175
|
+
// 路由错误走 key:value 路径. 这里放宽到任何 <...>...content...</...>
|
|
176
|
+
const paramRe = /<parameter\s+name=["'](\w+)["']>([\s\S]*?)<\/parameter>/g;
|
|
177
|
+
const paramReShort = /<param\s+name=["'](\w+)["']>([\s\S]*?)<\/param>/g;
|
|
178
|
+
let pMatch;
|
|
179
|
+
while ((pMatch = paramRe.exec(rawArgs)) !== null) {
|
|
180
|
+
const argName = pMatch[1];
|
|
181
|
+
const argValue = pMatch[2].trim();
|
|
182
|
+
if (argName && argValue) {
|
|
183
|
+
args[argName] = argValue;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (Object.keys(args).length === 0) {
|
|
187
|
+
let sMatch;
|
|
188
|
+
paramReShort.lastIndex = 0;
|
|
189
|
+
while ((sMatch = paramReShort.exec(rawArgs)) !== null) {
|
|
190
|
+
const argName = sMatch[1];
|
|
191
|
+
const argValue = sMatch[2].trim().replace(/^["']|["']$/g, '');
|
|
192
|
+
if (argName && argValue) {
|
|
193
|
+
args[argName] = argValue;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (Object.keys(args).length === 0) {
|
|
198
|
+
const xmlArgPattern = /<(\w+)>([\s\S]*?)<\/\1>/g;
|
|
199
|
+
let xmlMatch;
|
|
200
|
+
while ((xmlMatch = xmlArgPattern.exec(rawArgs)) !== null) {
|
|
201
|
+
const argName = xmlMatch[1];
|
|
202
|
+
const argValue = xmlMatch[2].trim();
|
|
203
|
+
if (argName && argValue) {
|
|
204
|
+
args[argName] = argValue;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
else if (rawArgs) {
|
|
210
|
+
// 形参串 key: value
|
|
211
|
+
const argPairs = rawArgs.split(',').map(s => s.trim()).filter(Boolean);
|
|
212
|
+
for (const pair of argPairs) {
|
|
213
|
+
const [key, ...valueParts] = pair.split(':').map(s => s.trim().replace(/['"]/g, ''));
|
|
214
|
+
if (key)
|
|
215
|
+
args[key] = valueParts.join(':') || '';
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
// 名字 resolve: 优先 this.tools.has, 然后 alias resolver
|
|
219
|
+
const resolved = ctx.tools.has(name) ? name : resolve(ctx, name);
|
|
220
|
+
if (resolved) {
|
|
221
|
+
autoSplitCommand(args);
|
|
222
|
+
return { name: resolved, args };
|
|
223
|
+
}
|
|
224
|
+
// 名字未识别但 rawArgs 含 <command> 子标签 → 跳出, 走 fallback 按 command 推断 shell_exec
|
|
225
|
+
if (rawArgs && /<\w+>[\s\S]*<\/\w+>/.test(strippedContent)) {
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
// === 4. Fallback: 任意 <tag><command>...</command></tag> 按 cmdFirst 推断 shell_exec ===
|
|
230
|
+
const xmlTagMatch = strippedContent.match(/<(\w+)>([\s\S]*?)<\/\1>/);
|
|
231
|
+
if (xmlTagMatch) {
|
|
232
|
+
const outerTag = xmlTagMatch[1];
|
|
233
|
+
const inner = xmlTagMatch[2];
|
|
234
|
+
if (!resolve(ctx, outerTag)) {
|
|
235
|
+
const cmdMatch = inner.match(/<command>([\s\S]*?)<\/command>/);
|
|
236
|
+
if (cmdMatch) {
|
|
237
|
+
const cmd = cmdMatch[1].trim();
|
|
238
|
+
const cmdFirst = cmd.split(/\s+/)[0];
|
|
239
|
+
if (SHELL_KEYWORDS.includes(cmdFirst)) {
|
|
240
|
+
const args = {};
|
|
241
|
+
const remaining = cmd.slice(cmdFirst.length).trim();
|
|
242
|
+
if (remaining) {
|
|
243
|
+
args.command = cmdFirst;
|
|
244
|
+
args.args = remaining;
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
args.command = cmd;
|
|
248
|
+
}
|
|
249
|
+
const argsM = inner.match(/<args>([\s\S]*?)<\/args>/);
|
|
250
|
+
if (argsM && argsM[1].trim()) {
|
|
251
|
+
args.args = argsM[1].trim();
|
|
252
|
+
}
|
|
253
|
+
return { name: 'shell_exec', args };
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* isFinalResponse — 临时判断 LLM 是否已完结 (含 <final gen>)
|
|
262
|
+
* 2026-06-19/06-30 修: 先剥离 think, 避免思考块中提及 <final gen> 误杀
|
|
263
|
+
* 含可解析 tool_call 则优先执行工具, 不算 final
|
|
264
|
+
*/
|
|
265
|
+
export function isFinalResponse(content, ctx) {
|
|
266
|
+
const stripped = content.replace(/<think[\s\S]*?<\/think/g, '');
|
|
267
|
+
if (!stripped.includes('<final gen>'))
|
|
268
|
+
return false;
|
|
269
|
+
// 用默认 ctx (无需 tool 名解析, 只看能否 parse 出来) - 但 isFinalResponse 不需要 tools 列表上下文.
|
|
270
|
+
// 如果没有传 ctx, 使用 empty set (只解析不 resolve).
|
|
271
|
+
if (ctx && parseToolCall(stripped, ctx))
|
|
272
|
+
return false;
|
|
273
|
+
if (!ctx) {
|
|
274
|
+
// 试探性 parse (不 resolve name) — 如果至少能提取 name+args 模式, 就不是 final.
|
|
275
|
+
// 这里粗略检测: stripped 中含 json "name": 或 <invoke name= 或 <function_calls> 任一
|
|
276
|
+
if (/\{[\s\S]*?"name"\s*:/.test(stripped))
|
|
277
|
+
return false;
|
|
278
|
+
if (/<invoke\s+name=/.test(stripped))
|
|
279
|
+
return false;
|
|
280
|
+
if (/<function_calls>/.test(stripped))
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
return true;
|
|
284
|
+
}
|
|
285
|
+
/** extractFinalAnswer: 去除 <final gen> 标记 + tool call 噪声. */
|
|
286
|
+
export function extractFinalAnswer(content) {
|
|
287
|
+
const marker = '<final gen>';
|
|
288
|
+
const markerIndex = content.indexOf(marker);
|
|
289
|
+
if (markerIndex !== -1) {
|
|
290
|
+
const after = content.substring(markerIndex + marker.length).trim();
|
|
291
|
+
if (after) {
|
|
292
|
+
content = after;
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
content = content.substring(0, markerIndex).trim();
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
let cleaned = content
|
|
299
|
+
.replace(/调用工具[::]\s*\w+\s*\([^)]*\)/g, '')
|
|
300
|
+
.replace(/使用工具[::]\s*\w+\s*\([^)]*\)/g, '')
|
|
301
|
+
.replace(/tool[_\w]*[::]\s*\w+\s*\([^)]*\)/gi, '')
|
|
302
|
+
.trim();
|
|
303
|
+
return cleaned;
|
|
304
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* peer-manifest-loader.ts — 智能体相互了解的懒加载触发器
|
|
3
|
+
*
|
|
4
|
+
* 设计目的 (2026-07-05):
|
|
5
|
+
* 远程 P2P 节点的 manifest 不默认进 LLM prompt, 只在需要时拉取.
|
|
6
|
+
* 3 个触发点:
|
|
7
|
+
* ① @-mention 远端 channel → 立即拉对方 manifest + 对应 agent 详细描述
|
|
8
|
+
* ② 本地 agent 连续失败 → 兜底拉对方 manifest (对方可能有解)
|
|
9
|
+
* ③ 关键词触发 → 用户说 "持续协助" / "cooperate" 时加载
|
|
10
|
+
*
|
|
11
|
+
* 加载结果拼成 prompt 附加块 (≤2000 字符), 拼到 system prompt 尾部.
|
|
12
|
+
* 不入持久化 prompt, 不污染主对话.
|
|
13
|
+
*/
|
|
14
|
+
import * as fs from 'fs/promises';
|
|
15
|
+
import * as peerFs from '../network/peer-fs.js';
|
|
16
|
+
// ============== 主入口 ==============
|
|
17
|
+
/**
|
|
18
|
+
* 加载对方的 manifest + (可选) 详细 agent 描述, 拼成 prompt 块.
|
|
19
|
+
* 行为:
|
|
20
|
+
* 1) 先看本地 ~/.bolloon/peers/<pk>/capability-index.md 缓存 — 命中且新 (≤1h) → 直接用
|
|
21
|
+
* 2) 否则调 RPC 拉 manifest → 落盘 → 读 capability-index.md
|
|
22
|
+
* 3) 如果 triggerValue 是 agent id 或 capability 名, 再发 RPC 拉详细描述
|
|
23
|
+
*/
|
|
24
|
+
export async function loadPeerManifest(ctx, opts) {
|
|
25
|
+
const t0 = Date.now();
|
|
26
|
+
const pk = ctx.remotePublicKey;
|
|
27
|
+
if (!pk || pk === opts.p2p.getPublicKey())
|
|
28
|
+
return null; // 自己
|
|
29
|
+
let rpcTriggered = false;
|
|
30
|
+
let idx = await peerFs.readPeerIndex(pk);
|
|
31
|
+
let capabilityIndex = await peerFs.readCapabilityIndex(pk);
|
|
32
|
+
// 缓存判断: capability-index 存在 + 索引在 TTL 内 → 跳过 RPC
|
|
33
|
+
const ttl = opts.cacheTtlMs ?? 60 * 60 * 1000;
|
|
34
|
+
if (!idx || !capabilityIndex || (idx.updatedAt && (Date.now() - new Date(idx.updatedAt).getTime()) > ttl)) {
|
|
35
|
+
// 缓存过期或不完整, 发 RPC 拉新
|
|
36
|
+
const since = idx?.manifestTs || 0;
|
|
37
|
+
const req = JSON.stringify({
|
|
38
|
+
v: 3, op: 'agent.manifest.exchange',
|
|
39
|
+
payload: { since, fromPublicKey: opts.p2p.getPublicKey() }
|
|
40
|
+
});
|
|
41
|
+
const r = await opts.p2p.sendToWithWait(pk, req, 3000);
|
|
42
|
+
if (r === 'SENT') {
|
|
43
|
+
rpcTriggered = true;
|
|
44
|
+
// 等异步处理落盘 — 这里 sleep 短时间, 不阻塞主线程太久
|
|
45
|
+
await new Promise(r => setTimeout(r, 500));
|
|
46
|
+
idx = await peerFs.readPeerIndex(pk);
|
|
47
|
+
capabilityIndex = await peerFs.readCapabilityIndex(pk);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (!idx) {
|
|
51
|
+
return {
|
|
52
|
+
publicKey: pk,
|
|
53
|
+
capabilityIndex: '',
|
|
54
|
+
promptBlock: `[远端 peer ${pk.slice(0, 12)}…] 暂无 manifest 缓存 (对方未响应或本地首次连接).`,
|
|
55
|
+
agentDescriptions: [],
|
|
56
|
+
rpcTriggered,
|
|
57
|
+
durationMs: Date.now() - t0,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
// 按 triggerValue 决定要不要拉详细 agent 描述
|
|
61
|
+
const agentDescriptions = [];
|
|
62
|
+
const lowerVal = ctx.triggerValue.toLowerCase();
|
|
63
|
+
// 1) 如果 triggerValue 看起来是 agent id (有特殊前缀/格式) → 直接拉
|
|
64
|
+
// 2) 如果是 capability 名 (编程/翻译/...) → 在 idx 里找匹配 capability 的 agent → 拉
|
|
65
|
+
const candidates = [];
|
|
66
|
+
if (ctx.triggerValue.match(/^(agent_|agt_|bot_|assist_|task_)/)) {
|
|
67
|
+
candidates.push(ctx.triggerValue);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
for (const a of idx.agents) {
|
|
71
|
+
if (a.capabilities.some(c => c.toLowerCase().includes(lowerVal))) {
|
|
72
|
+
candidates.push(a.id);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// 拉详细描述 (限 3 个, 避免 prompt 爆炸)
|
|
77
|
+
for (const agentId of candidates.slice(0, 3)) {
|
|
78
|
+
const localFile = peerFs.getPeerAgentMdPath(pk, agentId);
|
|
79
|
+
let body = '';
|
|
80
|
+
try {
|
|
81
|
+
body = await fs.readFile(localFile, 'utf-8');
|
|
82
|
+
}
|
|
83
|
+
catch { }
|
|
84
|
+
if (!body) {
|
|
85
|
+
// 本地没缓存, 拉 RPC
|
|
86
|
+
const req = JSON.stringify({
|
|
87
|
+
v: 3, op: 'agent.resource.get',
|
|
88
|
+
payload: { agentId, fromPublicKey: opts.p2p.getPublicKey() }
|
|
89
|
+
});
|
|
90
|
+
const r = await opts.p2p.sendToWithWait(pk, req, 3000);
|
|
91
|
+
if (r === 'SENT') {
|
|
92
|
+
rpcTriggered = true;
|
|
93
|
+
// 异步处理, 等 300ms
|
|
94
|
+
await new Promise(r => setTimeout(r, 300));
|
|
95
|
+
try {
|
|
96
|
+
body = await fs.readFile(localFile, 'utf-8');
|
|
97
|
+
}
|
|
98
|
+
catch { }
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (body)
|
|
102
|
+
agentDescriptions.push({ agentId, body: body.slice(0, 1500) });
|
|
103
|
+
}
|
|
104
|
+
// 拼 prompt 附加块
|
|
105
|
+
const block = buildPromptBlock(ctx, capabilityIndex || '(无索引)', agentDescriptions, idx);
|
|
106
|
+
return {
|
|
107
|
+
publicKey: pk,
|
|
108
|
+
capabilityIndex: capabilityIndex || '',
|
|
109
|
+
promptBlock: block,
|
|
110
|
+
agentDescriptions,
|
|
111
|
+
rpcTriggered,
|
|
112
|
+
durationMs: Date.now() - t0,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* 把加载结果拼成可注入 system prompt 的文本块 (≤2000 字)
|
|
117
|
+
*/
|
|
118
|
+
function buildPromptBlock(ctx, capabilityIndex, agentDescriptions, idx) {
|
|
119
|
+
const lines = [];
|
|
120
|
+
lines.push(`[远端 peer 临时上下文] ${ctx.reason} → ${ctx.triggerValue}`);
|
|
121
|
+
lines.push(`远端节点: ${idx.ownerName || idx.publicKey.slice(0, 12)}… (${idx.agents.length} agents)`);
|
|
122
|
+
lines.push('');
|
|
123
|
+
lines.push('## 对方能力索引');
|
|
124
|
+
lines.push(capabilityIndex);
|
|
125
|
+
lines.push('');
|
|
126
|
+
if (agentDescriptions.length > 0) {
|
|
127
|
+
lines.push('## 触发相关的 agent 详细描述');
|
|
128
|
+
for (const a of agentDescriptions) {
|
|
129
|
+
lines.push(`### ${a.agentId}`);
|
|
130
|
+
lines.push(a.body);
|
|
131
|
+
lines.push('');
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
const text = lines.join('\n').trim();
|
|
135
|
+
return text.length > 2000 ? text.slice(0, 1997) + '…' : text;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* 检测 /message 处理后是否需要加载远端 peer manifest.
|
|
139
|
+
* 输入:
|
|
140
|
+
* - text (用户原始输入)
|
|
141
|
+
* - channelId (当前 channel)
|
|
142
|
+
* - remoteChannels (远端 channel 列表 [{ id, name, _ownerPublicKey }])
|
|
143
|
+
* - consecutiveFailures (连续 LLM 失败次数, 可选)
|
|
144
|
+
*/
|
|
145
|
+
export function detectLoadTrigger(opts) {
|
|
146
|
+
// 触发 ①: @-mention 远端 channel
|
|
147
|
+
const mentionRe = /@([一-龥A-Za-z0-9_\-]{1,30})/g;
|
|
148
|
+
for (const m of opts.text.matchAll(mentionRe)) {
|
|
149
|
+
const name = m[1];
|
|
150
|
+
const rc = opts.remoteChannels.find(c => c.name === name);
|
|
151
|
+
if (rc && rc._ownerPublicKey) {
|
|
152
|
+
return {
|
|
153
|
+
shouldLoad: true,
|
|
154
|
+
reason: 'mention-remote',
|
|
155
|
+
remotePublicKey: rc._ownerPublicKey,
|
|
156
|
+
triggerValue: name,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// 触发 ③: 关键词
|
|
161
|
+
const text = opts.text.toLowerCase();
|
|
162
|
+
const keywords = ['持续协助', '一起合作', 'cooperate', '协作', '对方能干', 'remote help', 'find peer'];
|
|
163
|
+
for (const k of keywords) {
|
|
164
|
+
if (text.includes(k.toLowerCase())) {
|
|
165
|
+
// 关键词触发 — 用第一个 known remote channel 的 owner
|
|
166
|
+
if (opts.remoteChannels.length > 0 && opts.remoteChannels[0]._ownerPublicKey) {
|
|
167
|
+
return {
|
|
168
|
+
shouldLoad: true,
|
|
169
|
+
reason: 'cooperate-keyword',
|
|
170
|
+
remotePublicKey: opts.remoteChannels[0]._ownerPublicKey,
|
|
171
|
+
triggerValue: k,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// 触发 ②: 连续失败 (3+ 次)
|
|
177
|
+
const fails = opts.consecutiveFailures || 0;
|
|
178
|
+
if (fails >= 3) {
|
|
179
|
+
// 用最近一次 @ 的远端 channel; 没有则取 remoteChannels[0]
|
|
180
|
+
const lastMention = [...opts.text.matchAll(mentionRe)].pop();
|
|
181
|
+
let rc = opts.remoteChannels[0];
|
|
182
|
+
if (lastMention) {
|
|
183
|
+
const found = opts.remoteChannels.find(c => c.name === lastMention[1]);
|
|
184
|
+
if (found)
|
|
185
|
+
rc = found;
|
|
186
|
+
}
|
|
187
|
+
if (rc && rc._ownerPublicKey) {
|
|
188
|
+
return {
|
|
189
|
+
shouldLoad: true,
|
|
190
|
+
reason: 'consecutive-failure',
|
|
191
|
+
remotePublicKey: rc._ownerPublicKey,
|
|
192
|
+
triggerValue: rc.name,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return { shouldLoad: false };
|
|
197
|
+
}
|
|
198
|
+
// ============== 失败计数器 (内存) ==============
|
|
199
|
+
const failureCounters = new Map();
|
|
200
|
+
export function recordFailure(channelId) {
|
|
201
|
+
const cur = (failureCounters.get(channelId) || 0) + 1;
|
|
202
|
+
failureCounters.set(channelId, cur);
|
|
203
|
+
return cur;
|
|
204
|
+
}
|
|
205
|
+
export function clearFailure(channelId) {
|
|
206
|
+
failureCounters.delete(channelId);
|
|
207
|
+
}
|
|
208
|
+
export function getFailures(channelId) {
|
|
209
|
+
return failureCounters.get(channelId) || 0;
|
|
210
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { shellExec } from './shell-tool.js';
|
|
2
|
+
import { getBranchPrefix, getCooldownMs } from './shell-guard.js';
|
|
3
|
+
/**
|
|
4
|
+
* Session factory + runSelfImproveLoop.
|
|
5
|
+
*
|
|
6
|
+
* 从 pi-sdk.ts 抽出 (2026-07-06):
|
|
7
|
+
* - createAgentSession(config, forceNew?)
|
|
8
|
+
* - getAgentSession()
|
|
9
|
+
* - resetAgentSession()
|
|
10
|
+
* - runSelfImproveLoop(goal)
|
|
11
|
+
*
|
|
12
|
+
* 三个状态变量 (module-level):
|
|
13
|
+
* - sessionInstance: 单例 cache
|
|
14
|
+
* - lastIdentityDid: DID 变化检测
|
|
15
|
+
* - independentSessions: 多 session 缓存 (peerId 含 : 的场景)
|
|
16
|
+
* - lastSelfImproveAt: 自改冷却期
|
|
17
|
+
*/
|
|
18
|
+
import { PiAgentSession } from './pi-sdk.js';
|
|
19
|
+
let sessionInstance = null;
|
|
20
|
+
let lastIdentityDid = null;
|
|
21
|
+
const independentSessions = new Map();
|
|
22
|
+
export async function createAgentSession(config, forceNew) {
|
|
23
|
+
const incomingDid = config.identityDoc?.did;
|
|
24
|
+
if (config.peerId && config.peerId.includes(':')) {
|
|
25
|
+
const key = config.peerId;
|
|
26
|
+
if (!forceNew && independentSessions.has(key)) {
|
|
27
|
+
console.log(`[createAgentSession] 找到现有独立 session, key=${key}`);
|
|
28
|
+
const existing = independentSessions.get(key);
|
|
29
|
+
await existing.whenReady();
|
|
30
|
+
return existing;
|
|
31
|
+
}
|
|
32
|
+
const session = new PiAgentSession(config);
|
|
33
|
+
independentSessions.set(key, session);
|
|
34
|
+
console.log(`[createAgentSession] 创建独立 session, key=${key}, DID=${incomingDid}`);
|
|
35
|
+
await session.whenReady();
|
|
36
|
+
return session;
|
|
37
|
+
}
|
|
38
|
+
if (forceNew) {
|
|
39
|
+
const key = `force:${Date.now()}`;
|
|
40
|
+
const session = new PiAgentSession(config);
|
|
41
|
+
independentSessions.set(key, session);
|
|
42
|
+
console.log(`[createAgentSession] 创建强制新 session, key=${key}`);
|
|
43
|
+
await session.whenReady();
|
|
44
|
+
return session;
|
|
45
|
+
}
|
|
46
|
+
if (sessionInstance && lastIdentityDid && incomingDid && lastIdentityDid !== incomingDid) {
|
|
47
|
+
console.log(`[createAgentSession] DID 变化 ${lastIdentityDid} -> ${incomingDid},重建 session`);
|
|
48
|
+
sessionInstance = null;
|
|
49
|
+
}
|
|
50
|
+
if (sessionInstance) {
|
|
51
|
+
const currentDid = sessionInstance.getIdentity().did;
|
|
52
|
+
if (incomingDid && currentDid !== incomingDid) {
|
|
53
|
+
console.log(`[createAgentSession] 更新 identity: ${currentDid} -> ${incomingDid}`);
|
|
54
|
+
sessionInstance.updateIdentity({
|
|
55
|
+
did: incomingDid,
|
|
56
|
+
name: config.identityDoc?.name || sessionInstance.getIdentity().name,
|
|
57
|
+
publicKey: config.identityDoc?.publicKey || '',
|
|
58
|
+
createdAt: Date.now()
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
await sessionInstance.whenReady();
|
|
62
|
+
return sessionInstance;
|
|
63
|
+
}
|
|
64
|
+
const newSession = new PiAgentSession(config);
|
|
65
|
+
sessionInstance = newSession;
|
|
66
|
+
lastIdentityDid = config.identityDoc?.did || null;
|
|
67
|
+
console.log(`[createAgentSession] 新建 session, DID=${lastIdentityDid}`);
|
|
68
|
+
await newSession.whenReady();
|
|
69
|
+
return newSession;
|
|
70
|
+
}
|
|
71
|
+
export function getAgentSession() {
|
|
72
|
+
return sessionInstance;
|
|
73
|
+
}
|
|
74
|
+
export function resetAgentSession() {
|
|
75
|
+
sessionInstance = null;
|
|
76
|
+
lastIdentityDid = null;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* 自我改进循环: 在沙箱分支上工作, 输出结果给用户审.
|
|
80
|
+
*
|
|
81
|
+
* 不在 PiAgent 实例上的原因: 心跳回调可能没有 agent 实例, 单独函数更易复用.
|
|
82
|
+
*
|
|
83
|
+
* **关键不变量**:
|
|
84
|
+
* 1. AI 不能 push 到 master (shell-guard 黑名单 + git 受保护分支)
|
|
85
|
+
* 2. 改动必须走沙箱分支 (SELF_IMPROVE_BRANCH_PREFIX)
|
|
86
|
+
* 3. 6 小时冷却期 (SELF_IMPROVE_COOLDOWN_MS)
|
|
87
|
+
* 4. 写文件必须经过 shell_exec + 护栏检查
|
|
88
|
+
*/
|
|
89
|
+
let lastSelfImproveAt = null;
|
|
90
|
+
export async function runSelfImproveLoop(goal) {
|
|
91
|
+
const cooldownMs = getCooldownMs();
|
|
92
|
+
if (lastSelfImproveAt && Date.now() - lastSelfImproveAt < cooldownMs) {
|
|
93
|
+
const waitHrs = Math.ceil((cooldownMs - (Date.now() - lastSelfImproveAt)) / 3600000);
|
|
94
|
+
return { success: false, error: `自改冷却中, 还需要约 ${waitHrs} 小时` };
|
|
95
|
+
}
|
|
96
|
+
const sourceBranch = 'master';
|
|
97
|
+
const newBranch = `${getBranchPrefix()}${Date.now()}`;
|
|
98
|
+
console.log(`[self-improve] 启动自改循环, 目标: ${goal}, 新分支: ${newBranch}`);
|
|
99
|
+
const r1 = await shellExec('git', ['checkout', sourceBranch]);
|
|
100
|
+
if (!r1.success)
|
|
101
|
+
return { success: false, error: `切换到 ${sourceBranch} 失败: ${r1.error}` };
|
|
102
|
+
const r2 = await shellExec('git', ['checkout', '-b', newBranch]);
|
|
103
|
+
if (!r2.success)
|
|
104
|
+
return { success: false, error: `创建分支失败: ${r2.error}` };
|
|
105
|
+
lastSelfImproveAt = Date.now();
|
|
106
|
+
return {
|
|
107
|
+
success: true,
|
|
108
|
+
output: `✅ 自改分支已创建: ${newBranch}\n目标: ${goal}\n\n**护栏已激活**:\n - 仅允许白名单命令\n - 6 小时冷却期\n\nAI 接下来会用 shell_exec 工具改源码. 完成后你会在对话里看到 diff 摘要, 手动 git diff master..${newBranch} 审, 满意再 merge.`
|
|
109
|
+
};
|
|
110
|
+
}
|