@bolloon/bolloon-agent 0.2.9 → 0.2.11
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 +467 -467
- package/bin/bolloon-cli.cjs +183 -183
- package/bin/bolloon.cjs +0 -0
- package/bin/bolloon.js +157 -0
- package/dist/agents/agent-manifest-protocol.js +0 -52
- package/dist/agents/constraint-layer.js +19 -19
- package/dist/agents/pi-sdk.js +287 -1213
- package/dist/agents/shell-guard.js +2 -2
- package/dist/agents/workflow-pivot-loop.js +2 -81
- 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 +13 -26
- package/dist/bollharness-integration/integration.js +0 -71
- package/dist/bollharness-integration/llm-judgment-engine.js +134 -134
- package/dist/bollharness-integration/skill-adapter.js +127 -52
- package/dist/bootstrap/context-collector.js +3 -6
- package/dist/bootstrap/lifecycle-hooks.js +1 -15
- package/dist/cli-entry.js +35 -39
- package/dist/constraint-runtime/src/_archive_helper.js +9 -0
- package/dist/constraint-runtime/src/agent/coordinator.js +48 -0
- package/dist/constraint-runtime/src/agent/index.js +1 -0
- package/dist/constraint-runtime/src/assistant/index.js +12 -0
- package/dist/constraint-runtime/src/bootstrap/index.js +12 -0
- package/dist/constraint-runtime/src/bootstrap_graph.js +13 -0
- package/dist/constraint-runtime/src/bridge/index.js +12 -0
- package/dist/constraint-runtime/src/buddy/index.js +12 -0
- package/dist/constraint-runtime/src/cli/index.js +12 -0
- package/dist/constraint-runtime/src/command_graph.js +10 -0
- package/dist/constraint-runtime/src/commands.js +60 -0
- package/dist/constraint-runtime/src/components/index.js +12 -0
- package/dist/constraint-runtime/src/constants/index.js +12 -0
- package/dist/constraint-runtime/src/constraint/budget.js +22 -0
- package/dist/constraint-runtime/src/constraint/index.js +2 -0
- package/dist/constraint-runtime/src/constraint/permission.js +20 -0
- package/dist/constraint-runtime/src/context.js +30 -0
- package/dist/constraint-runtime/src/coordinator/index.js +12 -0
- package/dist/constraint-runtime/src/cost_hook.js +4 -0
- package/dist/constraint-runtime/src/cost_tracker.js +8 -0
- package/dist/constraint-runtime/src/deferred_init.js +10 -0
- package/dist/constraint-runtime/src/direct_modes.js +6 -0
- package/dist/constraint-runtime/src/dynamic-tool-loader.js +85 -0
- package/dist/constraint-runtime/src/entrypoints/index.js +12 -0
- package/dist/constraint-runtime/src/execution_registry.js +44 -0
- package/dist/constraint-runtime/src/history.js +9 -0
- package/dist/constraint-runtime/src/hooks/index.js +12 -0
- package/dist/constraint-runtime/src/index.js +26 -0
- package/dist/constraint-runtime/src/ink.js +4 -0
- package/dist/constraint-runtime/src/keybindings/index.js +12 -0
- package/dist/constraint-runtime/src/memdir/index.js +12 -0
- package/dist/constraint-runtime/src/migrations/index.js +12 -0
- package/dist/constraint-runtime/src/models.js +1 -0
- package/dist/constraint-runtime/src/moreright/index.js +12 -0
- package/dist/constraint-runtime/src/native_ts/index.js +12 -0
- package/dist/constraint-runtime/src/output_styles/index.js +12 -0
- package/dist/constraint-runtime/src/parity_audit.js +12 -0
- package/dist/constraint-runtime/src/plugins/index.js +12 -0
- package/dist/constraint-runtime/src/port_manifest.js +11 -0
- package/dist/constraint-runtime/src/prefetch.js +9 -0
- package/dist/constraint-runtime/src/query.js +1 -0
- package/dist/constraint-runtime/src/remote/index.js +12 -0
- package/dist/constraint-runtime/src/remote_runtime.js +9 -0
- package/dist/constraint-runtime/src/runtime/index.js +1 -0
- package/dist/constraint-runtime/src/runtime/session.js +35 -0
- package/dist/constraint-runtime/src/schemas/index.js +12 -0
- package/dist/constraint-runtime/src/screens/index.js +12 -0
- package/dist/constraint-runtime/src/server/index.js +12 -0
- package/dist/constraint-runtime/src/services/index.js +12 -0
- package/dist/constraint-runtime/src/session_store.js +22 -0
- package/dist/constraint-runtime/src/setup.js +30 -0
- package/dist/constraint-runtime/src/skills/index.js +1 -0
- package/dist/constraint-runtime/src/skills/skill-registry.js +28 -0
- package/dist/constraint-runtime/src/state/index.js +12 -0
- package/dist/constraint-runtime/src/system_init.js +20 -0
- package/dist/constraint-runtime/src/thinking/engine.js +42 -0
- package/dist/constraint-runtime/src/thinking/index.js +1 -0
- package/dist/constraint-runtime/src/tool_pool.js +8 -0
- package/dist/constraint-runtime/src/tools/OpenCLI/execAdapter.js +7 -0
- package/dist/constraint-runtime/src/tools/OpenCLI/listAdapters.js +7 -0
- package/dist/constraint-runtime/src/tools/OpenCLI/runCommand.js +7 -0
- package/dist/constraint-runtime/src/tools/SafeSDK/confirmTransaction.js +6 -0
- package/dist/constraint-runtime/src/tools/SafeSDK/createTransaction.js +8 -0
- package/dist/constraint-runtime/src/tools/SafeSDK/executeTransaction.js +6 -0
- package/dist/constraint-runtime/src/tools/SafeSDK/getBalance.js +6 -0
- package/dist/constraint-runtime/src/tools/SafeSDK/getPendingTransactions.js +6 -0
- package/dist/constraint-runtime/src/tools/SafeSDK/proposeTransaction.js +6 -0
- package/dist/constraint-runtime/src/tools.js +80 -0
- package/dist/constraint-runtime/src/transcript.js +19 -0
- package/dist/constraint-runtime/src/types/index.js +12 -0
- package/dist/constraint-runtime/src/upstream_proxy/index.js +12 -0
- package/dist/constraint-runtime/src/utils/index.js +12 -0
- package/dist/constraint-runtime/src/vim/index.js +12 -0
- package/dist/constraint-runtime/src/voice/index.js +12 -0
- package/dist/constraint-runtime/tests/agent.test.js +16 -0
- package/dist/constraint-runtime/tests/constraint.test.js +41 -0
- package/dist/constraint-runtime/tests/skill.test.js +19 -0
- package/dist/constraint-runtime/tests/thinking.test.js +22 -0
- package/dist/context-compaction/auto-compact.js +7 -7
- package/dist/electron-preload.js +1 -18
- package/dist/electron-preload.js.map +1 -1
- package/dist/electron.js +168 -3
- package/dist/electron.js.map +1 -1
- package/dist/index.js +116 -136
- package/dist/llm/config-store.js +8 -24
- package/dist/llm/llm-judgment-client.js +102 -102
- package/dist/llm/pi-ai.js +85 -126
- 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/pi-ecosystem-colony/index.js +365 -0
- package/dist/pi-ecosystem-judgment/causal-judge.js +51 -51
- package/dist/pi-ecosystem-judgment/distill-prompt.js +42 -42
- package/dist/pi-ecosystem-judgment/distillation.js +14 -14
- 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 +0 -11
- package/dist/social/ant-colony/AdaptiveHeartbeat.js +101 -0
- package/dist/social/ant-colony/PheromoneEngine.js +227 -0
- package/dist/social/ant-colony/index.js +6 -0
- package/dist/social/ant-colony/types.js +24 -0
- package/dist/test/ai-judgment-test.js +80 -0
- package/dist/test/bollharness-integration.test.js +318 -0
- package/dist/test/channel-agent-multi-dialogue.js +205 -0
- package/dist/test/channel-heartbeat-agent-test.js +201 -0
- package/dist/test/constraint-layer.test.js +164 -0
- package/dist/test/diap-identity-test.js +172 -0
- package/dist/test/diap-quick-test.js +62 -0
- package/dist/test/global-shared-context.test.js +315 -0
- package/dist/test/harness-judgment-injection.test.js +246 -0
- package/dist/test/harness-workflow-integrator-test.js +228 -0
- package/dist/test/human-value-store.test.js +243 -0
- package/dist/test/hybrid-integration-test.js +118 -0
- package/dist/test/hybrid-messenger-verify.js +55 -0
- package/dist/test/iroh-bistream-debug.js +38 -0
- package/dist/test/iroh-communication.test.js +66 -0
- package/dist/test/iroh-debug-test.js +57 -0
- package/dist/test/iroh-diap-test.js +71 -0
- package/dist/test/iroh-direct-connect.js +55 -0
- package/dist/test/iroh-e2e-fixed.js +89 -0
- package/dist/test/iroh-e2e-same-process.js +63 -0
- package/dist/test/iroh-e2e.js +66 -0
- package/dist/test/iroh-final-e2e.js +72 -0
- package/dist/test/iroh-relay-test.js +37 -0
- package/dist/test/iroh-simple-test.js +41 -0
- package/dist/test/iroh-transport-verify.js +54 -0
- package/dist/test/iroh-transport.test.js +37 -0
- package/dist/test/iroh-two-nodes.js +70 -0
- package/dist/test/iroh-verify.js +44 -0
- package/dist/test/judgment-decision.test.js +219 -0
- package/dist/test/llm-judgment-integration.test.js +220 -0
- package/dist/test/p2p-agent-complex-dialogue.js +385 -0
- package/dist/test/p2p-agent-dialogue.js +341 -0
- package/dist/test/p2p-agent-full-bidirectional.js +510 -0
- package/dist/test/p2p-agent-harness-flow.js +437 -0
- package/dist/test/p2p-agent-harness-single.js +143 -0
- package/dist/test/p2p-ai-dialogue-test.js +318 -0
- package/dist/test/p2p-cid-connect-test.js +195 -0
- package/dist/test/p2p-connect-receiver.js +69 -0
- package/dist/test/p2p-doc-transfer.js +110 -0
- package/dist/test/p2p-identity-page-test.js +77 -0
- package/dist/test/p2p-iroh-test.js +171 -0
- package/dist/test/p2p-minimal-test.js +241 -0
- package/dist/test/p2p-node-1.js +148 -0
- package/dist/test/p2p-node-2.js +148 -0
- package/dist/test/p2p-server.js +281 -0
- package/dist/test/p2p-two-nodes-test.js +438 -0
- package/dist/test/pi-sdk.test.js +44 -0
- package/dist/test/set-persona.js +40 -0
- package/dist/test/simple.test.js +9 -0
- package/dist/test/storage-integration.test.js +150 -0
- package/dist/test/subagent-manager.test.js +276 -0
- package/dist/test/test-gate-flow.test.js +81 -0
- package/dist/test/workflow-engine.test.js +87 -0
- package/dist/test/workflow-pivot-loop.test.js +246 -0
- package/dist/web/api-config.html +520 -520
- package/dist/web/client.js +2917 -3791
- package/dist/web/components/p2p/index.js +234 -276
- package/dist/web/components/wallet-viem.mjs +118 -118
- package/dist/web/index.html +374 -373
- package/dist/web/manifest.json +20 -20
- package/dist/web/server.js +23 -775
- package/dist/web/style.css +4737 -4737
- package/dist/web/ui/message-renderer.js +373 -451
- package/dist/web/ui/step-timeline.js +255 -351
- package/package.json +160 -160
- package/scripts/build-cli.js +215 -215
- package/scripts/build-web.ts +125 -125
- package/scripts/postinstall.js +152 -152
- package/bin/bolloon-daemon.sh +0 -207
- package/bin/ipfs +0 -0
- package/dist/agents/chat-segmenter.js +0 -298
- package/dist/agents/judgment-protocol.js +0 -479
- package/dist/agents/parse-tool-call.js +0 -304
- package/dist/agents/peer-manifest-loader.js +0 -210
- package/dist/agents/react-loop.js +0 -120
- package/dist/agents/session-store.js +0 -171
- package/dist/agents/tool-registry.js +0 -136
- package/dist/bootstrap/chat-archiver.js +0 -276
- package/dist/bootstrap/memory-compressor.js +0 -170
- package/dist/bootstrap/persona-loader.js +0 -94
- package/dist/electron/config.js +0 -16
- package/dist/electron/config.js.map +0 -1
- package/dist/electron/dialogs.js +0 -71
- package/dist/electron/dialogs.js.map +0 -1
- package/dist/electron/first-run.js +0 -129
- package/dist/electron/first-run.js.map +0 -1
- package/dist/electron/ipc.js +0 -16
- package/dist/electron/ipc.js.map +0 -1
- package/dist/electron/logger.js +0 -77
- package/dist/electron/logger.js.map +0 -1
- package/dist/electron/main.js +0 -60
- package/dist/electron/main.js.map +0 -1
- package/dist/electron/menu.js +0 -140
- package/dist/electron/menu.js.map +0 -1
- package/dist/electron/paths.js +0 -33
- package/dist/electron/paths.js.map +0 -1
- package/dist/electron/server.js +0 -74
- package/dist/electron/server.js.map +0 -1
- package/dist/electron/tray.js +0 -73
- package/dist/electron/tray.js.map +0 -1
- package/dist/electron/window.js +0 -69
- package/dist/electron/window.js.map +0 -1
- package/dist/network/local-inbox-bus.js +0 -73
- package/dist/network/p2p-outbox.js +0 -161
- package/dist/network/peer-fs.js +0 -420
- package/dist/network/peer-resource-bridge.js +0 -216
- package/dist/web/components/p2p/P2PModal.js +0 -188
- package/dist/web/components/p2p/p2p-modal.js +0 -664
- package/dist/web/components/p2p/p2p-tools.js +0 -248
- package/dist/web/input-validator.js +0 -103
- package/dist/web/util/safe-name.js +0 -32
package/dist/agents/pi-sdk.js
CHANGED
|
@@ -19,7 +19,7 @@ import { getBranchPrefix, getCooldownMs, checkWritePath } from './shell-guard.js
|
|
|
19
19
|
import { DiscoveredAgentsManager, createSocialHeartbeat } from '../social/heartbeat.js';
|
|
20
20
|
import { getGlobalSharedContext } from '../social/global-shared-context.js';
|
|
21
21
|
import { Session, SkillRegistry, saveSession, loadSession } from '@bolloon/constraint-runtime';
|
|
22
|
-
import { loadSkillsFromPaths, defaultSkillPaths } from './skill-loader.js';
|
|
22
|
+
import { loadSkillsFromPaths, defaultSkillPaths, describeSkill } from './skill-loader.js';
|
|
23
23
|
// Judgment 注入门 (P0): 在主对话 LLM 调起前自动拼入 Top 3 判断力
|
|
24
24
|
// 失败静默, 不阻塞主对话
|
|
25
25
|
import { injectJudgmentGate, recordJudgmentUsage } from '../pi-ecosystem-judgment/injection-gate.js';
|
|
@@ -31,10 +31,6 @@ import { onPostToolUse } from '../bootstrap/lifecycle-hooks.js';
|
|
|
31
31
|
import { budgetReduce, snip, microcompact } from '../context-compaction/index.js';
|
|
32
32
|
// React Harness: 8-gate + 4-guard (防越权 / 防 prompt 注入)
|
|
33
33
|
import { ReactHarness } from '../security/react-harness.js';
|
|
34
|
-
import { parseToolCall as parseToolCallImpl, isFinalResponse as isFinalResponseImpl, extractFinalAnswer as extractFinalAnswerImpl } from './parse-tool-call.js';
|
|
35
|
-
import { sessionStore as defaultSessionStore } from './session-store.js';
|
|
36
|
-
import { ToolRegistry } from './tool-registry.js';
|
|
37
|
-
import { decideMaxIterations, decideContextOverflow, shouldCompactBeforeIteration } from './react-loop.js';
|
|
38
34
|
const SHARED_SESSION_PATH = path.join(process.env.HOME || '/tmp', '.bolloon', 'sessions');
|
|
39
35
|
const PERSONA_PATH = path.join(process.env.HOME || '/tmp', '.bolloon', 'persona.json');
|
|
40
36
|
export class PiSessionManager {
|
|
@@ -321,18 +317,18 @@ export class PiSessionManager {
|
|
|
321
317
|
await this.sharedContext.updateAgentStatus(this.agentId, status);
|
|
322
318
|
}
|
|
323
319
|
}
|
|
324
|
-
const TOOL_DEFINITIONS = `
|
|
325
|
-
可用工具:
|
|
326
|
-
1. read_document(path) - 读取文档内容,支持 .txt, .md, .pdf, .docx
|
|
327
|
-
2. summarize_document(path, context?) - 总结文档内容,可选提供上下文
|
|
328
|
-
3. improve_document(path, requirements) - 改进文档,需提供文件路径和改进要求
|
|
329
|
-
4. list_peers() - 列出已连接的对等节点
|
|
330
|
-
5. send_message(peer_id, message) - 向指定对等节点发送消息
|
|
331
|
-
6. broadcast_message(message) - 向所有对等节点广播消息
|
|
332
|
-
7. get_identity() - 获取当前智能体身份信息
|
|
333
|
-
8. set_persona(persona_json) - 更新智能体 persona,包含 name、description、personality、greeting 等
|
|
334
|
-
9. run_workflow(steps) - 执行预定义工作流
|
|
335
|
-
10. get_operation_logs() - 获取操作日志
|
|
320
|
+
const TOOL_DEFINITIONS = `
|
|
321
|
+
可用工具:
|
|
322
|
+
1. read_document(path) - 读取文档内容,支持 .txt, .md, .pdf, .docx
|
|
323
|
+
2. summarize_document(path, context?) - 总结文档内容,可选提供上下文
|
|
324
|
+
3. improve_document(path, requirements) - 改进文档,需提供文件路径和改进要求
|
|
325
|
+
4. list_peers() - 列出已连接的对等节点
|
|
326
|
+
5. send_message(peer_id, message) - 向指定对等节点发送消息
|
|
327
|
+
6. broadcast_message(message) - 向所有对等节点广播消息
|
|
328
|
+
7. get_identity() - 获取当前智能体身份信息
|
|
329
|
+
8. set_persona(persona_json) - 更新智能体 persona,包含 name、description、personality、greeting 等
|
|
330
|
+
9. run_workflow(steps) - 执行预定义工作流
|
|
331
|
+
10. get_operation_logs() - 获取操作日志
|
|
336
332
|
`;
|
|
337
333
|
class PiAgentSession {
|
|
338
334
|
cwd;
|
|
@@ -348,17 +344,11 @@ class PiAgentSession {
|
|
|
348
344
|
socialHeartbeat = null;
|
|
349
345
|
messageHistory = [];
|
|
350
346
|
tools = new Map();
|
|
351
|
-
/** 2026-06-30: tool registry 模块 — 独立 alias resolve, 测试可消融. */
|
|
352
|
-
_toolRegistry = new ToolRegistry();
|
|
353
347
|
skillRegistry = new SkillRegistry();
|
|
354
348
|
/** M2.4: 缓存 tool 列表, registerTools() 之后不变, runReActLoop 多次循环复用 */
|
|
355
349
|
cachedToolDefinitions = '';
|
|
356
350
|
/** M2.4: 缓存 persona section */
|
|
357
351
|
cachedPersonaSection = '';
|
|
358
|
-
/** 2026-06-30: 持久化层 — 默认走 ~/.bolloon/sessions/cache/, 测试可注入临时目录. */
|
|
359
|
-
_sessionStore;
|
|
360
|
-
/** 构造期间 fire-and-forget 任务的 promise — whenReady() 等它 */
|
|
361
|
-
_readyPromise = null;
|
|
362
352
|
// 2026-06-16 修: 父要求把 ReAct loop 上限放大到 "几乎无限", 靠自动压缩上下文 + fail-safe 兜底
|
|
363
353
|
// 默认 10000 — 正常任务永远跑不到, 但作为防 LLM 死循环 / 防 OOM 的最后一道闸
|
|
364
354
|
// 旧默认 100 写死导致中等复杂度任务 (10-50 个 tool call + 多步反思) 会被误杀
|
|
@@ -370,10 +360,6 @@ class PiAgentSession {
|
|
|
370
360
|
/** 2026-06-16 新增: 累计错误总数兜底 (不管是否同工具, 累计 N 次就强制退出)
|
|
371
361
|
* 防 LLM 轮换工具名绕开 MAX_SAME_TOOL_FAILURES 的死循环攻击 */
|
|
372
362
|
MAX_TOTAL_ERRORS = 20;
|
|
373
|
-
/** 2026-06-19: 记录 loop 内成功执行的工具结果, 失败退出时汇总给用户 */
|
|
374
|
-
successfulToolResults = [];
|
|
375
|
-
/** 2026-06-19: Agent Mesh 通信 — 本地 + 远端 inbox 缓存, 给 check_inbox 工具读 */
|
|
376
|
-
_inboxMessages = [];
|
|
377
363
|
/** 2026-06-16 新增: loop 内自动压缩触发阈值 (相对 60K 阈值的比例) */
|
|
378
364
|
LOOP_COMPACT_RATIO = 0.8;
|
|
379
365
|
/** P1: max output token 升级重试 (LLM 截断时重试, 最多 3 次) */
|
|
@@ -396,9 +382,6 @@ class PiAgentSession {
|
|
|
396
382
|
*/
|
|
397
383
|
judgmentGateAddition = '';
|
|
398
384
|
judgmentGateUsedIds = [];
|
|
399
|
-
// 2026-06-18: 来自 web server markedPrompt 外的 contextHint (channel/judgment/distill/remote channels),
|
|
400
|
-
// 拼到 systemPrompt 末尾, 别再混进 user message
|
|
401
|
-
contextHintAddition = '';
|
|
402
385
|
/**
|
|
403
386
|
* 当前 onStream 引用 + abort signal (computeJudgmentGate 需要 onStream 广播 phase)
|
|
404
387
|
* 每次 prompt / promptStream / promptWithPivotLoop 入口设置, 用完即清
|
|
@@ -411,8 +394,6 @@ class PiAgentSession {
|
|
|
411
394
|
promptStartTime = 0;
|
|
412
395
|
/** 当前 channel id (由 getAgentForChannel / prompt 4 参注入, 供 hook / log 使用) */
|
|
413
396
|
currentChannelId = '';
|
|
414
|
-
/** 2026-07-04: 当前 agentId (server.ts 通过 createAgentSession 选项注入), 供 onSessionStart 加载 persona docs */
|
|
415
|
-
currentAgentId = '';
|
|
416
397
|
/** M2.2 (2026-06-17): 当前轮的用户请求 intent, runReActLoop 拼 systemPrompt 时会读这个 */
|
|
417
398
|
currentIntent = 'chitchat';
|
|
418
399
|
currentIntentHint = '';
|
|
@@ -456,10 +437,6 @@ class PiAgentSession {
|
|
|
456
437
|
this.peerId = config.peerId || 'local';
|
|
457
438
|
this.identity = config.identityDoc || this.createDefaultIdentity();
|
|
458
439
|
this.minimaxAvailable = this.checkMinimax();
|
|
459
|
-
// 2026-07-04: 透传 agentId (server.ts 通过 createAgentSession 选项注入)
|
|
460
|
-
this.currentAgentId = config.agentId || '';
|
|
461
|
-
// 2026-06-30: 持久化层可注入 — 测试传 tmpDir, 业务用默认 ~/.bolloon/sessions/cache/
|
|
462
|
-
this._sessionStore = config.sessionStore ?? defaultSessionStore;
|
|
463
440
|
this.constraintLayer = new ConstraintLayer();
|
|
464
441
|
this.workflowEngine = new WorkflowEngine(this.constraintLayer);
|
|
465
442
|
this.sessionManager = new PiSessionManager(this.identity.did, this.cwd);
|
|
@@ -475,37 +452,38 @@ class PiAgentSession {
|
|
|
475
452
|
// 之前 messageHistory 是空的, 服务重启后 LLM 看到的是新对话
|
|
476
453
|
// 现在 loadSessionKey 形如 "channel-xxx:default" 走 ~/.bolloon/sessions/cache/<key>.json
|
|
477
454
|
if (config.loadSessionKey) {
|
|
478
|
-
this.
|
|
479
|
-
// 失败静默, 但不让 whenReady 永久 hang
|
|
480
|
-
console.warn(`[PiAgent] hydrateMessageHistory failed: ${err.message?.slice(0, 100)}`);
|
|
481
|
-
});
|
|
455
|
+
this.hydrateMessageHistory(config.loadSessionKey, config.loadSessionMaxMessages ?? 30);
|
|
482
456
|
}
|
|
483
457
|
}
|
|
484
458
|
/**
|
|
485
|
-
*
|
|
486
|
-
* 解决 fire-and-forget 让 messageHistory 不可预测的问题.
|
|
487
|
-
* 不传 loadSessionKey 时立即返回.
|
|
488
|
-
*/
|
|
489
|
-
whenReady() {
|
|
490
|
-
return this._readyPromise ?? Promise.resolve();
|
|
491
|
-
}
|
|
492
|
-
/**
|
|
493
|
-
* M2.3 (2026-06-30 重构): 从 SessionStore 加载历史, 转成 messageHistory 格式
|
|
459
|
+
* M2.3: 从 session JSON 加载历史, 转成 messageHistory 格式
|
|
494
460
|
* - 失败静默 (历史加载失败不应该阻塞 agent 启动)
|
|
495
461
|
* - 限制 max 条数, 防止 context 爆
|
|
496
|
-
* -
|
|
497
|
-
* -
|
|
498
|
-
*
|
|
499
|
-
* 历史格式兼容旧 schema ({type, content}) 和新 schema (PersistedMessage[])
|
|
462
|
+
* - user 消息 role=user, ai 消息 role=assistant
|
|
463
|
+
* - 跳过 metadata 中含 error 的 (错误消息会污染 LLM)
|
|
500
464
|
*/
|
|
501
465
|
async hydrateMessageHistory(sessionKey, maxMessages) {
|
|
502
466
|
try {
|
|
503
|
-
const
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
467
|
+
const sessionPath = path.join(os.homedir(), '.bolloon', 'sessions', 'cache', `${sessionKey}.json`);
|
|
468
|
+
const content = await fs.readFile(sessionPath, 'utf-8');
|
|
469
|
+
const session = JSON.parse(content);
|
|
470
|
+
const messages = Array.isArray(session?.messages) ? session.messages : [];
|
|
471
|
+
// 保留最后 N 条, 转换 role 字段
|
|
472
|
+
const tail = messages.slice(-maxMessages);
|
|
473
|
+
const hydrated = [];
|
|
474
|
+
for (const m of tail) {
|
|
475
|
+
// 跳过错误的 AI 消息 (M1.2 之后, AI 错误时 reply 是 [AI 服务调用失败] 字符串, 不该进 history)
|
|
476
|
+
if (m?.type === 'ai' && typeof m.content === 'string' && m.content.startsWith('[AI 服务调用失败]'))
|
|
477
|
+
continue;
|
|
478
|
+
if (m?.type === 'ai' && typeof m.content === 'string' && m.content.startsWith('[错误:'))
|
|
479
|
+
continue;
|
|
480
|
+
if (!m?.content)
|
|
481
|
+
continue;
|
|
482
|
+
const role = m.type === 'user' ? 'user' : m.type === 'ai' ? 'assistant' : null;
|
|
483
|
+
if (!role)
|
|
484
|
+
continue;
|
|
485
|
+
hydrated.push({ role, content: String(m.content) });
|
|
507
486
|
}
|
|
508
|
-
const hydrated = this._filterToMessage(loaded).slice(-maxMessages);
|
|
509
487
|
if (hydrated.length > 0) {
|
|
510
488
|
this.messageHistory = hydrated;
|
|
511
489
|
console.log(`[PiAgent] 从 ${sessionKey} 回灌 ${hydrated.length} 条历史`);
|
|
@@ -515,84 +493,6 @@ class PiAgentSession {
|
|
|
515
493
|
console.warn(`[PiAgent] hydrateMessageHistory 失败 (non-fatal): ${err.message?.slice(0, 100)}`);
|
|
516
494
|
}
|
|
517
495
|
}
|
|
518
|
-
/**
|
|
519
|
-
* 2026-06-30: 把当前 messageHistory 持久化到 SessionStore.
|
|
520
|
-
* 公开方法 — claude code / 外部 harness 在每次 prompt 完成后调一下,
|
|
521
|
-
* 即可获得"重启 / 跨进程接续"的语义.
|
|
522
|
-
*/
|
|
523
|
-
async saveCurrentSession(key) {
|
|
524
|
-
const persisted = this.messageHistory.map((m) => ({
|
|
525
|
-
role: m.role,
|
|
526
|
-
content: m.content,
|
|
527
|
-
toolCall: m.toolCall,
|
|
528
|
-
toolResult: m.toolResult,
|
|
529
|
-
toolCallId: m.toolCallId,
|
|
530
|
-
timestamp: Date.now(),
|
|
531
|
-
source: 'pi-session',
|
|
532
|
-
}));
|
|
533
|
-
await this._sessionStore.saveMessages(key, persisted);
|
|
534
|
-
}
|
|
535
|
-
/**
|
|
536
|
-
* 2026-06-30: 从 disk 拉历史覆盖当前 messageHistory.
|
|
537
|
-
* 返回加载条数 — 失败或空则返回 0.
|
|
538
|
-
* 与 loadSessionKey (构造时读) 不同: 这个是 session 已建好后再读.
|
|
539
|
-
*/
|
|
540
|
-
async resumeSession(key, maxMessages = 30) {
|
|
541
|
-
const before = this.messageHistory.length;
|
|
542
|
-
await this.hydrateMessageHistory(key, maxMessages);
|
|
543
|
-
return this.messageHistory.length - before;
|
|
544
|
-
}
|
|
545
|
-
/**
|
|
546
|
-
* 2026-06-30: 读历史不修改 messageHistory.
|
|
547
|
-
* 给 claude code / 测试做"先看一下历史"用 — 不破坏当前会话.
|
|
548
|
-
* 返回 Message[] 数组 (空数组表示无历史).
|
|
549
|
-
*/
|
|
550
|
-
async peekSessionHistory(key, maxMessages = 30) {
|
|
551
|
-
try {
|
|
552
|
-
const loaded = await this._sessionStore.loadMessages(key);
|
|
553
|
-
if (!loaded)
|
|
554
|
-
return [];
|
|
555
|
-
return this._filterToMessage(loaded).slice(-maxMessages);
|
|
556
|
-
}
|
|
557
|
-
catch {
|
|
558
|
-
return [];
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
/** hydrateMessageHistory 用的过滤逻辑 — 提到外面复用 */
|
|
562
|
-
_filterToMessage(loaded) {
|
|
563
|
-
const hydrated = [];
|
|
564
|
-
const VALID_ROLES = new Set(['user', 'assistant', 'tool', 'system']);
|
|
565
|
-
for (const m of loaded) {
|
|
566
|
-
// role 必须合法 (拒绝旧 schema {type:'user'} 没 role 字段的)
|
|
567
|
-
if (!VALID_ROLES.has(m.role))
|
|
568
|
-
continue;
|
|
569
|
-
// 跳过污染消息
|
|
570
|
-
if (typeof m.content === 'string' && m.content.startsWith('[AI 服务调用失败]'))
|
|
571
|
-
continue;
|
|
572
|
-
if (typeof m.content === 'string' && m.content.startsWith('[错误:'))
|
|
573
|
-
continue;
|
|
574
|
-
// 注意: '!m.content' 会跳过 content='' 的 tool call 消息 (assistant role + toolCall 字段),
|
|
575
|
-
// 这种是合法的 (LLM 输出只有 tool call, 没有正文) — 必须保留.
|
|
576
|
-
// 这里只跳过"无内容 + 也没 tool call/tool result"的废消息.
|
|
577
|
-
if (!m.content && !m.toolCall && !m.toolResult)
|
|
578
|
-
continue;
|
|
579
|
-
// 跳过空 tool role (tool result 占位但没有任何内容)
|
|
580
|
-
if (m.role === 'tool' && !m.toolResult)
|
|
581
|
-
continue;
|
|
582
|
-
hydrated.push({
|
|
583
|
-
role: m.role,
|
|
584
|
-
content: m.content ?? '',
|
|
585
|
-
toolCall: m.toolCall,
|
|
586
|
-
toolResult: m.toolResult,
|
|
587
|
-
toolCallId: m.toolCallId,
|
|
588
|
-
});
|
|
589
|
-
}
|
|
590
|
-
return hydrated;
|
|
591
|
-
}
|
|
592
|
-
/** 暴露 store 给测试 / 高级集成用. */
|
|
593
|
-
get sessionStoreInstance() {
|
|
594
|
-
return this._sessionStore;
|
|
595
|
-
}
|
|
596
496
|
/**
|
|
597
497
|
* 从 SKILL.md 目录加载 skills 进 skillRegistry.
|
|
598
498
|
*
|
|
@@ -601,17 +501,23 @@ class PiAgentSession {
|
|
|
601
501
|
* 2. ~/.bolloon/skills/ 全局用户级
|
|
602
502
|
* 3. <cwd>/.bolloon/skills/ 项目级
|
|
603
503
|
* 4. ~/.boll/skills/ 全局 (兼容 bollharness 旧用户)
|
|
604
|
-
*
|
|
605
|
-
*
|
|
606
|
-
* 历史遗留: 写 pi-sdk 时为方便演示, 把 bolloon 项目里的 19 个 skill 强制注入到 system prompt.
|
|
607
|
-
* 问题: system prompt 涨到 22K chars, LLM (minimax M3) 在 pivot loop 里反复 think 不输出
|
|
608
|
-
* `<final gen>`, session 落盘拿不到最终回答.
|
|
609
|
-
* 现在: 只让用户放 .bolloon/skills/SKILL.md 才生效, 干净且 project-owned.
|
|
504
|
+
* 5. <bolloon-repo>/src/bollharness/.boll/skills/ bolloon 仓库内置 skill
|
|
505
|
+
* (bolloon 项目本身用 pi-sdk 写核心, 这 19 个 skill 视为项目级 builtin)
|
|
610
506
|
*
|
|
611
507
|
* 静默忽略不存在的目录.
|
|
612
508
|
*/
|
|
613
509
|
loadSkills(paths) {
|
|
614
|
-
|
|
510
|
+
let resolved;
|
|
511
|
+
if (paths && paths.length > 0) {
|
|
512
|
+
resolved = paths;
|
|
513
|
+
}
|
|
514
|
+
else {
|
|
515
|
+
resolved = [
|
|
516
|
+
...defaultSkillPaths(os.homedir(), this.cwd),
|
|
517
|
+
// bolloon 仓库内置 skill (相对本 npm 包的位置)
|
|
518
|
+
this.findBolloonBuiltinSkillsPath(),
|
|
519
|
+
].filter((p) => Boolean(p));
|
|
520
|
+
}
|
|
615
521
|
loadSkillsFromPaths(resolved)
|
|
616
522
|
.then((skills) => {
|
|
617
523
|
for (const s of skills) {
|
|
@@ -620,17 +526,36 @@ class PiAgentSession {
|
|
|
620
526
|
}
|
|
621
527
|
this.skillRegistry.register(s);
|
|
622
528
|
}
|
|
623
|
-
console.log(`[loadSkills] 已加载 ${skills.length} 个 skill
|
|
624
|
-
if (skills.length > 0) {
|
|
625
|
-
for (const s of skills) {
|
|
626
|
-
console.log(` - ${s.name}: ${s.description.substring(0, 100)}${s.description.length > 100 ? '...' : ''}`);
|
|
627
|
-
}
|
|
628
|
-
}
|
|
529
|
+
console.log(`[loadSkills] 已加载 ${skills.length} 个 skill: ${skills.map(describeSkill).join(' | ')}`);
|
|
629
530
|
})
|
|
630
531
|
.catch((err) => {
|
|
631
532
|
console.error('[loadSkills] 加载失败:', err);
|
|
632
533
|
});
|
|
633
534
|
}
|
|
535
|
+
/**
|
|
536
|
+
* 定位 bolloon 仓库内置的 bollharness skill 目录.
|
|
537
|
+
* 向上回溯 cwd, 找第一个包含 src/bollharness/.boll/skills 的祖先.
|
|
538
|
+
* 找不到时返回 null (例如把 bolloon-agent 作为外部依赖安装时).
|
|
539
|
+
*/
|
|
540
|
+
findBolloonBuiltinSkillsPath() {
|
|
541
|
+
let dir = this.cwd;
|
|
542
|
+
for (let i = 0; i < 6; i++) {
|
|
543
|
+
const candidate = path.join(dir, 'src', 'bollharness', '.boll', 'skills');
|
|
544
|
+
try {
|
|
545
|
+
if (fsSync.existsSync(candidate) && fsSync.statSync(candidate).isDirectory()) {
|
|
546
|
+
return candidate;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
catch {
|
|
550
|
+
// 忽略 stat 异常, 继续向上
|
|
551
|
+
}
|
|
552
|
+
const parent = path.dirname(dir);
|
|
553
|
+
if (parent === dir)
|
|
554
|
+
break;
|
|
555
|
+
dir = parent;
|
|
556
|
+
}
|
|
557
|
+
return null;
|
|
558
|
+
}
|
|
634
559
|
async initHarness() {
|
|
635
560
|
try {
|
|
636
561
|
const { createBollharnessIntegration } = await import('../bollharness-integration/index.js');
|
|
@@ -747,162 +672,6 @@ class PiAgentSession {
|
|
|
747
672
|
}
|
|
748
673
|
}
|
|
749
674
|
});
|
|
750
|
-
// 2026-06-19: 本地 + 远端智能体通信工具集 (Agent Mesh 通信层)
|
|
751
|
-
// 解决"另一个智能体发消息"的能力缺口:
|
|
752
|
-
// - 本地 channel: 走 PiSessionManager.getAllChannels / addMessage / getChannelMessages
|
|
753
|
-
// - 远端 P2P: 走 p2pNetwork.sendMessage
|
|
754
|
-
this._inboxMessages = [];
|
|
755
|
-
this._setupInboxListener();
|
|
756
|
-
// list_local_channels: 列出本 session 内所有 channel
|
|
757
|
-
this.tools.set('list_local_channels', {
|
|
758
|
-
name: 'list_local_channels',
|
|
759
|
-
description: '列出当前 PiAgentSession 注册的所有 channel. 每个 channel 是 bolloon 内的智能体会话, 可含 messages[] / peerDid(对端 DID) / peerName. 供 send_to_channel 选 channel_id.',
|
|
760
|
-
parameters: {},
|
|
761
|
-
execute: async () => {
|
|
762
|
-
const channels = this.sessionManager.getAllChannels();
|
|
763
|
-
if (channels.length === 0) {
|
|
764
|
-
return { success: true, output: '📭 当前 session 没有注册任何 channel (用 send_to_channel 留空 channel_id 会自动创建一个)' };
|
|
765
|
-
}
|
|
766
|
-
const lines = channels.map((ch, i) => {
|
|
767
|
-
const peer = ch.peerDid ? ` peer=${ch.peerDid.substring(0, 20)}` : (ch.peerName ? ` peer="${ch.peerName}"` : '');
|
|
768
|
-
const msgCount = ch.messages?.length || 0;
|
|
769
|
-
return ` ${i + 1}. ${ch.id} name="${ch.name}" msgs=${msgCount}${peer}`;
|
|
770
|
-
});
|
|
771
|
-
return { success: true, output: `📬 当前 session 有 ${channels.length} 个 channel:\n${lines.join('\n')}` };
|
|
772
|
-
}
|
|
773
|
-
});
|
|
774
|
-
// send_to_channel: 把消息发到指定 channel (本地 + 如果 channel 有关联 peerDid 也走 P2P 转发)
|
|
775
|
-
this.tools.set('send_to_channel', {
|
|
776
|
-
name: 'send_to_channel',
|
|
777
|
-
description: '发送消息到指定 channel. channel_id 留空会自动创建一个. 如果 channel 已关联 peerDid, 会同时通过 P2P 转发到对端 agent.',
|
|
778
|
-
parameters: { channel_id: '目标 channel id (留空自动创建)', message: '消息内容 (必填)', peer_did: '可选 创建新 channel 时绑定的对端 DID (e.g. did:key:...)' },
|
|
779
|
-
execute: async (args) => {
|
|
780
|
-
const message = String(args.message || '').trim();
|
|
781
|
-
if (!message)
|
|
782
|
-
return { success: false, error: 'message 必填' };
|
|
783
|
-
let channelId = String(args.channel_id || '').trim();
|
|
784
|
-
const peerDid = args.peer_did ? String(args.peer_did) : undefined;
|
|
785
|
-
if (!channelId) {
|
|
786
|
-
// 自动创建 channel
|
|
787
|
-
const ch = await this.sessionManager.getOrCreatePeerChannel(peerDid || 'auto-created', 'auto-created');
|
|
788
|
-
channelId = ch.id;
|
|
789
|
-
}
|
|
790
|
-
// 用 AgentSession 接口方法发消息 (它会自动设置 type/agentId/timestamp)
|
|
791
|
-
await this.sendSocialMessage(channelId, message);
|
|
792
|
-
// 如果 channel 关联了 peer, 走 P2P 转发
|
|
793
|
-
const ch = this.sessionManager.getAllChannels().find(c => c.id === channelId);
|
|
794
|
-
if (ch?.peerDid) {
|
|
795
|
-
try {
|
|
796
|
-
const peerId = ch.peerDid.replace(/^did:key:/, '').replace(/^did:pi:/, '');
|
|
797
|
-
await p2pNetwork.sendMessage(peerId, 'channel-message', JSON.stringify({ channelId, content: message, from: this.identity.did, timestamp: new Date().toISOString() }));
|
|
798
|
-
return { success: true, output: `📨 消息已存到 channel ${channelId} + P2P 转发到 ${ch.peerDid.substring(0, 30)}...` };
|
|
799
|
-
}
|
|
800
|
-
catch (e) {
|
|
801
|
-
return { success: true, output: `📨 消息已存到 channel ${channelId} (P2P 转发失败: ${String(e).slice(0, 100)})` };
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
return { success: true, output: `📨 消息已存到 channel ${channelId}` };
|
|
805
|
-
}
|
|
806
|
-
});
|
|
807
|
-
// check_channel_inbox: 读指定 channel 的所有 messages
|
|
808
|
-
this.tools.set('check_channel_inbox', {
|
|
809
|
-
name: 'check_channel_inbox',
|
|
810
|
-
description: '读取指定 channel 的所有 messages, 按时间排序. channel_id 必填, 来自 list_local_channels.',
|
|
811
|
-
parameters: { channel_id: '目标 channel id (必填)', max: '可选, 最多返回 N 条 (默认 50)' },
|
|
812
|
-
execute: async (args) => {
|
|
813
|
-
const channelId = String(args.channel_id || '').trim();
|
|
814
|
-
if (!channelId)
|
|
815
|
-
return { success: false, error: 'channel_id 必填' };
|
|
816
|
-
const max = Number(args.max) || 50;
|
|
817
|
-
const messages = await this.sessionManager.getChannelMessages(channelId);
|
|
818
|
-
const channel = this.sessionManager.getAllChannels().find(c => c.id === channelId);
|
|
819
|
-
const channelName = channel?.name || channelId;
|
|
820
|
-
if (messages.length === 0) {
|
|
821
|
-
return { success: true, output: `📭 channel "${channelName}" (${channelId}) 空, 0 条消息` };
|
|
822
|
-
}
|
|
823
|
-
const slice = messages.slice(-max);
|
|
824
|
-
const lines = slice.map((m, i) => {
|
|
825
|
-
const from = m.sender || m.from || m.fromDid || m.agentId || '?';
|
|
826
|
-
const ts = m.timestamp || m.createdAt || '?';
|
|
827
|
-
const content = (m.content || m.text || '').substring(0, 200);
|
|
828
|
-
return ` ${i + 1}. [${m.type || 'text'}] from=${from} time=${ts}\n ${content}`;
|
|
829
|
-
});
|
|
830
|
-
return { success: true, output: `📬 channel "${channelName}" (${channelId}) 有 ${slice.length} 条消息 (共 ${messages.length}):\n${lines.join('\n')}` };
|
|
831
|
-
}
|
|
832
|
-
});
|
|
833
|
-
// check_inbox: 兼容旧名, 实际走 check_channel_inbox
|
|
834
|
-
this.tools.set('check_inbox', {
|
|
835
|
-
name: 'check_inbox',
|
|
836
|
-
description: "兼容旧名: 默认读第一个 channel 的 messages. 推荐用 list_local_channels + check_channel_inbox(channel_id)",
|
|
837
|
-
parameters: { max: '可选, 最多返回 N 条 (默认 50)' },
|
|
838
|
-
execute: async (args) => {
|
|
839
|
-
const channels = this.sessionManager.getAllChannels();
|
|
840
|
-
if (channels.length === 0) {
|
|
841
|
-
return { success: true, output: '📭 当前 session 没有 channel, 0 条消息' };
|
|
842
|
-
}
|
|
843
|
-
const first = channels[0];
|
|
844
|
-
// 直接调 check_channel_inbox 复用逻辑
|
|
845
|
-
return await this.tools.get('check_channel_inbox').execute({ channel_id: first.id, max: String(args.max || 50) });
|
|
846
|
-
}
|
|
847
|
-
});
|
|
848
|
-
// send_to_peer: 发送结构化消息到指定 P2P 节点
|
|
849
|
-
this.tools.set('send_to_peer', {
|
|
850
|
-
name: 'send_to_peer',
|
|
851
|
-
description: '发送结构化消息到指定 P2P 节点 (远端 bolloon 实例). 对方会通过远端 channel 收到.',
|
|
852
|
-
parameters: { peer_id: '目标 P2P 节点 publicKey (用 list_peers 查)', message: '消息内容 (任意字符串)', type: '可选, 消息类型标签 (默认 agent-message)' },
|
|
853
|
-
execute: async (args) => {
|
|
854
|
-
const peerId = String(args.peer_id || '').trim();
|
|
855
|
-
const msg = String(args.message || '').trim();
|
|
856
|
-
const type = String(args.type || 'agent-message').trim();
|
|
857
|
-
if (!peerId)
|
|
858
|
-
return { success: false, error: 'peer_id 必填' };
|
|
859
|
-
if (!msg)
|
|
860
|
-
return { success: false, error: 'message 必填' };
|
|
861
|
-
try {
|
|
862
|
-
await p2pNetwork.sendMessage(peerId, type, msg);
|
|
863
|
-
return { success: true, output: `✅ 消息已发送到 ${peerId.substring(0, 16)}...` };
|
|
864
|
-
}
|
|
865
|
-
catch (e) {
|
|
866
|
-
return { success: false, error: `发送失败: ${String(e)}` };
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
|
-
});
|
|
870
|
-
// p2p_broadcast: 广播给所有 P2P 节点
|
|
871
|
-
this.tools.set('p2p_broadcast', {
|
|
872
|
-
name: 'p2p_broadcast',
|
|
873
|
-
description: '广播消息到所有连接的 P2P 节点',
|
|
874
|
-
parameters: { message: '消息内容', type: '可选, 消息类型标签 (默认 agent-broadcast)' },
|
|
875
|
-
execute: async (args) => {
|
|
876
|
-
const msg = String(args.message || '').trim();
|
|
877
|
-
const type = String(args.type || 'agent-broadcast').trim();
|
|
878
|
-
if (!msg)
|
|
879
|
-
return { success: false, error: 'message 必填' };
|
|
880
|
-
try {
|
|
881
|
-
await p2pNetwork.broadcast(type, msg);
|
|
882
|
-
return { success: true, output: `📡 已广播 type=${type}` };
|
|
883
|
-
}
|
|
884
|
-
catch (e) {
|
|
885
|
-
return { success: false, error: `广播失败: ${String(e)}` };
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
});
|
|
889
|
-
// agent_call: RPC 风格远端 agent 任务调用
|
|
890
|
-
this.tools.set('agent_call', {
|
|
891
|
-
name: 'agent_call',
|
|
892
|
-
description: 'RPC: 让远端 P2P agent 跑一个任务, 等待结果返回. 远端 agent 会基于 task 描述自主完成, 完成后回复. (RPC 结果回收机制待实现)',
|
|
893
|
-
parameters: { peer_id: '目标 P2P 节点', task: '任务描述 (远端 agent 收到的 prompt)', timeoutMs: '可选 超时 (ms, 默认 30000)' },
|
|
894
|
-
execute: async (args) => {
|
|
895
|
-
const peerId = String(args.peer_id || '').trim();
|
|
896
|
-
const task = String(args.task || '').trim();
|
|
897
|
-
if (!peerId)
|
|
898
|
-
return { success: false, error: 'peer_id 必填' };
|
|
899
|
-
if (!task)
|
|
900
|
-
return { success: false, error: 'task 必填' };
|
|
901
|
-
const requestId = `rpc-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
902
|
-
await p2pNetwork.sendMessage(peerId, 'agent-call', JSON.stringify({ requestId, task, from: this.identity?.did }));
|
|
903
|
-
return { success: true, output: `📞 RPC 任务已发送给 ${peerId.substring(0, 16)}...\n requestId=${requestId}` };
|
|
904
|
-
}
|
|
905
|
-
});
|
|
906
675
|
this.tools.set('get_identity', {
|
|
907
676
|
name: 'get_identity',
|
|
908
677
|
description: '获取当前智能体身份信息',
|
|
@@ -1031,32 +800,7 @@ class PiAgentSession {
|
|
|
1031
800
|
const cmd = String(args.command || '').trim();
|
|
1032
801
|
if (!cmd)
|
|
1033
802
|
return { success: false, error: 'command 必填' };
|
|
1034
|
-
|
|
1035
|
-
// 1. JSON 数组: ["checkout", "-b", "branch"] (LLM 偏好)
|
|
1036
|
-
// 2. 逗号分隔: "checkout,-b,branch" (旧格式)
|
|
1037
|
-
// 3. 字符串: "checkout -b branch" (单参数)
|
|
1038
|
-
let argList = [];
|
|
1039
|
-
const rawArgs = args.args;
|
|
1040
|
-
if (Array.isArray(rawArgs)) {
|
|
1041
|
-
argList = rawArgs.map((s) => String(s).trim()).filter(Boolean);
|
|
1042
|
-
}
|
|
1043
|
-
else if (typeof rawArgs === 'string') {
|
|
1044
|
-
const trimmed = rawArgs.trim();
|
|
1045
|
-
if (trimmed.startsWith('[') && trimmed.endsWith(']')) {
|
|
1046
|
-
// JSON 数组字符串
|
|
1047
|
-
try {
|
|
1048
|
-
const parsed = JSON.parse(trimmed);
|
|
1049
|
-
if (Array.isArray(parsed)) {
|
|
1050
|
-
argList = parsed.map((s) => String(s).trim()).filter(Boolean);
|
|
1051
|
-
}
|
|
1052
|
-
}
|
|
1053
|
-
catch { /* fall through to comma split */ }
|
|
1054
|
-
}
|
|
1055
|
-
if (argList.length === 0) {
|
|
1056
|
-
// 逗号分隔 或 单字符串
|
|
1057
|
-
argList = trimmed.split(',').map(s => s.trim()).filter(Boolean);
|
|
1058
|
-
}
|
|
1059
|
-
}
|
|
803
|
+
const argList = String(args.args || '').split(',').map(s => s.trim()).filter(Boolean);
|
|
1060
804
|
const timeoutMs = Number(args.timeoutMs) || 30000;
|
|
1061
805
|
const result = await shellExec(cmd, argList, { timeoutMs });
|
|
1062
806
|
if (result.deniedByGuard) {
|
|
@@ -1082,10 +826,6 @@ class PiAgentSession {
|
|
|
1082
826
|
return await runSelfImproveLoop(goal);
|
|
1083
827
|
}
|
|
1084
828
|
});
|
|
1085
|
-
// 2026-06-24: Wallet + Polymarket + Safe 工具集 (基于 constraint-runtime/src/tools/)
|
|
1086
|
-
// 真实可用实现: ethers v6 + JsonRpcProvider, 默认走 eth.llamarpc.com
|
|
1087
|
-
// agent 可以创建/导入/查询 EVM 钱包, 查余额, 签名, 转账, 部署 Safe 多签钱包
|
|
1088
|
-
this._registerWalletTools();
|
|
1089
829
|
// list_skills 工具: 列出当前 session 已加载的 skills
|
|
1090
830
|
// 加载源: ~/.bolloon/skills/ → <cwd>/.bolloon/skills/ → ~/.boll/skills/
|
|
1091
831
|
this.tools.set('list_skills', {
|
|
@@ -1281,270 +1021,6 @@ class PiAgentSession {
|
|
|
1281
1021
|
return { success: true, output: `✅ ${create ? 'created + checked out' : 'checked out'} ${name}\n${result.output || ''}` };
|
|
1282
1022
|
}
|
|
1283
1023
|
});
|
|
1284
|
-
// 2026-06-19: 实用代码修改工具集 (M4) — 补全 read/delete/mkdir/move + grep/git log/show
|
|
1285
|
-
// 这些是 LLM 自主完成代码修改的最小工具集, 之前只能通过 shell_exec 间接调用
|
|
1286
|
-
// 通用文件读取 (支持任意扩展名, 跟 read_document 区别是后者只支持 txt/md/pdf/docx)
|
|
1287
|
-
this.tools.set('read_file', {
|
|
1288
|
-
name: 'read_file',
|
|
1289
|
-
description: '读取任意文件内容 (相对 cwd). 与 read_document 区别: 不限制文件类型. 受 shell-guard 路径白名单保护.',
|
|
1290
|
-
parameters: { path: '相对路径 (必填, e.g. "src/agents/pi-sdk.ts")', startLine: '起始行号 (可选, 默认 0)', maxLines: '最大行数 (可选, 默认 500)' },
|
|
1291
|
-
execute: async (args) => {
|
|
1292
|
-
const relPath = String(args.path || '').trim();
|
|
1293
|
-
if (!relPath)
|
|
1294
|
-
return { success: false, error: 'path 必填' };
|
|
1295
|
-
const pathResult = checkWritePath(relPath);
|
|
1296
|
-
if (!pathResult.allowed)
|
|
1297
|
-
return { success: false, error: `路径被护栏拒: ${pathResult.reason}` };
|
|
1298
|
-
try {
|
|
1299
|
-
const absPath = path.resolve(this.cwd, relPath);
|
|
1300
|
-
const content = fsSync.readFileSync(absPath, 'utf-8');
|
|
1301
|
-
const start = Math.max(0, parseInt(String(args.startLine || '0')) || 0);
|
|
1302
|
-
const max = parseInt(String(args.maxLines || '500')) || 500;
|
|
1303
|
-
const lines = content.split('\n');
|
|
1304
|
-
const slice = lines.slice(start, start + max);
|
|
1305
|
-
return { success: true, output: `📄 ${relPath} (第 ${start + 1}-${start + slice.length} 行, 共 ${lines.length} 行):\n${slice.map((l, i) => `${String(start + i + 1).padStart(4)} | ${l}`).join('\n')}` };
|
|
1306
|
-
}
|
|
1307
|
-
catch (e) {
|
|
1308
|
-
return { success: false, error: `读取失败: ${e.message}` };
|
|
1309
|
-
}
|
|
1310
|
-
}
|
|
1311
|
-
});
|
|
1312
|
-
// 删除文件
|
|
1313
|
-
this.tools.set('delete_file', {
|
|
1314
|
-
name: 'delete_file',
|
|
1315
|
-
description: '删除一个文件. 受 shell-guard 路径白名单保护. 不可恢复, 调用前请确认.',
|
|
1316
|
-
parameters: { path: '相对路径 (必填)' },
|
|
1317
|
-
execute: async (args) => {
|
|
1318
|
-
const relPath = String(args.path || '').trim();
|
|
1319
|
-
if (!relPath)
|
|
1320
|
-
return { success: false, error: 'path 必填' };
|
|
1321
|
-
const pathResult = checkWritePath(relPath);
|
|
1322
|
-
if (!pathResult.allowed)
|
|
1323
|
-
return { success: false, error: `路径被护栏拒: ${pathResult.reason}` };
|
|
1324
|
-
try {
|
|
1325
|
-
const absPath = path.resolve(this.cwd, relPath);
|
|
1326
|
-
if (!fsSync.existsSync(absPath))
|
|
1327
|
-
return { success: false, error: `文件不存在: ${relPath}` };
|
|
1328
|
-
fsSync.unlinkSync(absPath);
|
|
1329
|
-
return { success: true, output: `✅ deleted ${relPath}` };
|
|
1330
|
-
}
|
|
1331
|
-
catch (e) {
|
|
1332
|
-
return { success: false, error: `删除失败: ${e.message}` };
|
|
1333
|
-
}
|
|
1334
|
-
}
|
|
1335
|
-
});
|
|
1336
|
-
// 创建目录
|
|
1337
|
-
this.tools.set('mkdir', {
|
|
1338
|
-
name: 'mkdir',
|
|
1339
|
-
description: '创建一个或多个目录. 自动 mkdir -p (父目录不存在也创建).',
|
|
1340
|
-
parameters: { path: '目录路径 (必填, 相对 cwd)' },
|
|
1341
|
-
execute: async (args) => {
|
|
1342
|
-
const relPath = String(args.path || '').trim();
|
|
1343
|
-
if (!relPath)
|
|
1344
|
-
return { success: false, error: 'path 必填' };
|
|
1345
|
-
const pathResult = checkWritePath(relPath);
|
|
1346
|
-
if (!pathResult.allowed)
|
|
1347
|
-
return { success: false, error: `路径被护栏拒: ${pathResult.reason}` };
|
|
1348
|
-
try {
|
|
1349
|
-
const absPath = path.resolve(this.cwd, relPath);
|
|
1350
|
-
fsSync.mkdirSync(absPath, { recursive: true });
|
|
1351
|
-
return { success: true, output: `✅ mkdir ${relPath}` };
|
|
1352
|
-
}
|
|
1353
|
-
catch (e) {
|
|
1354
|
-
return { success: false, error: `创建失败: ${e.message}` };
|
|
1355
|
-
}
|
|
1356
|
-
}
|
|
1357
|
-
});
|
|
1358
|
-
// 移动/重命名文件
|
|
1359
|
-
this.tools.set('move_file', {
|
|
1360
|
-
name: 'move_file',
|
|
1361
|
-
description: '移动或重命名文件. 源和目标路径都在白名单内才允许.',
|
|
1362
|
-
parameters: { from: '源路径 (必填)', to: '目标路径 (必填)' },
|
|
1363
|
-
execute: async (args) => {
|
|
1364
|
-
const from = String(args.from || '').trim();
|
|
1365
|
-
const to = String(args.to || '').trim();
|
|
1366
|
-
if (!from || !to)
|
|
1367
|
-
return { success: false, error: 'from 和 to 都必填' };
|
|
1368
|
-
const fromCheck = checkWritePath(from);
|
|
1369
|
-
if (!fromCheck.allowed)
|
|
1370
|
-
return { success: false, error: `from 路径被护栏拒: ${fromCheck.reason}` };
|
|
1371
|
-
const toCheck = checkWritePath(to);
|
|
1372
|
-
if (!toCheck.allowed)
|
|
1373
|
-
return { success: false, error: `to 路径被护栏拒: ${toCheck.reason}` };
|
|
1374
|
-
try {
|
|
1375
|
-
const fromAbs = path.resolve(this.cwd, from);
|
|
1376
|
-
const toAbs = path.resolve(this.cwd, to);
|
|
1377
|
-
if (!fsSync.existsSync(fromAbs))
|
|
1378
|
-
return { success: false, error: `源文件不存在: ${from}` };
|
|
1379
|
-
fsSync.mkdirSync(path.dirname(toAbs), { recursive: true });
|
|
1380
|
-
fsSync.renameSync(fromAbs, toAbs);
|
|
1381
|
-
return { success: true, output: `✅ ${from} → ${to}` };
|
|
1382
|
-
}
|
|
1383
|
-
catch (e) {
|
|
1384
|
-
return { success: false, error: `移动失败: ${e.message}` };
|
|
1385
|
-
}
|
|
1386
|
-
}
|
|
1387
|
-
});
|
|
1388
|
-
// 文件内搜索 (类似 grep)
|
|
1389
|
-
this.tools.set('grep_files', {
|
|
1390
|
-
name: 'grep_files',
|
|
1391
|
-
description: '在文件中搜索匹配 pattern 的行. 类似 grep -rn. 路径必须在白名单.',
|
|
1392
|
-
parameters: { pattern: '搜索 pattern (必填, 字符串, 不是正则)', path: '搜索目录 (可选, 默认 .)', filePattern: '文件名 glob (可选, e.g. "*.ts")' },
|
|
1393
|
-
execute: async (args) => {
|
|
1394
|
-
const pattern = String(args.pattern || '').trim();
|
|
1395
|
-
if (!pattern)
|
|
1396
|
-
return { success: false, error: 'pattern 必填' };
|
|
1397
|
-
const searchPath = String(args.path || '.').trim();
|
|
1398
|
-
const pathResult = checkWritePath(searchPath);
|
|
1399
|
-
if (!pathResult.allowed)
|
|
1400
|
-
return { success: false, error: `路径被护栏拒: ${pathResult.reason}` };
|
|
1401
|
-
try {
|
|
1402
|
-
const { execFile } = await import('child_process');
|
|
1403
|
-
const { promisify } = await import('util');
|
|
1404
|
-
const pExecFile = promisify(execFile);
|
|
1405
|
-
const argv = ['-rn', '--include=' + (args.filePattern || '*'), pattern, searchPath];
|
|
1406
|
-
const { stdout, stderr } = await pExecFile('grep', argv, { cwd: this.cwd, maxBuffer: 1024 * 1024 });
|
|
1407
|
-
const lines = stdout.split('\n').filter(Boolean).slice(0, 50);
|
|
1408
|
-
return { success: true, output: `🔍 grep "${pattern}" in ${searchPath} (${args.filePattern || '*'}, 最多 50 行):\n${lines.join('\n')}${lines.length === 50 ? '\n... (truncated)' : ''}` };
|
|
1409
|
-
}
|
|
1410
|
-
catch (e) {
|
|
1411
|
-
if (e.code === 1)
|
|
1412
|
-
return { success: true, output: `🔍 grep "${pattern}" in ${searchPath}: 0 matches` };
|
|
1413
|
-
return { success: false, error: `grep 失败: ${e.message}` };
|
|
1414
|
-
}
|
|
1415
|
-
}
|
|
1416
|
-
});
|
|
1417
|
-
// glob 找文件
|
|
1418
|
-
this.tools.set('glob_files', {
|
|
1419
|
-
name: 'glob_files',
|
|
1420
|
-
description: '用 glob pattern 找文件. 例如 "**/*.test.ts".',
|
|
1421
|
-
parameters: { pattern: 'glob pattern (必填, e.g. "src/**/*.ts")' },
|
|
1422
|
-
execute: async (args) => {
|
|
1423
|
-
const pattern = String(args.pattern || '').trim();
|
|
1424
|
-
if (!pattern)
|
|
1425
|
-
return { success: false, error: 'pattern 必填' };
|
|
1426
|
-
try {
|
|
1427
|
-
const pathResult = checkWritePath(pattern.replace(/\*\*.*$/, '').replace(/\/\*.*$/, '') || '.');
|
|
1428
|
-
if (!pathResult.allowed && pattern !== '**/*' && pattern !== '*')
|
|
1429
|
-
return { success: false, error: `路径被护栏拒: ${pathResult.reason}` };
|
|
1430
|
-
const { execFile } = await import('child_process');
|
|
1431
|
-
const { promisify } = await import('util');
|
|
1432
|
-
const pExecFile = promisify(execFile);
|
|
1433
|
-
// 用 shell 的 find + glob (避免引入额外依赖)
|
|
1434
|
-
const { stdout } = await pExecFile('find', [this.cwd, '-path', `*${pattern.replace(/\*\*\//, '*').replace(/\*\*$/, '*').replace(/\*$/, '*')}`, '-type', 'f'], { maxBuffer: 1024 * 1024 });
|
|
1435
|
-
const files = stdout.split('\n').filter(Boolean).slice(0, 100);
|
|
1436
|
-
const relFiles = files.map(f => path.relative(this.cwd, f));
|
|
1437
|
-
return { success: true, output: `🔍 glob "${pattern}" 找到 ${relFiles.length} 个文件${relFiles.length === 100 ? ' (truncated)' : ''}:\n${relFiles.join('\n')}` };
|
|
1438
|
-
}
|
|
1439
|
-
catch (e) {
|
|
1440
|
-
return { success: false, error: `glob 失败: ${e.message}` };
|
|
1441
|
-
}
|
|
1442
|
-
}
|
|
1443
|
-
});
|
|
1444
|
-
// git log
|
|
1445
|
-
this.tools.set('git_log', {
|
|
1446
|
-
name: 'git_log',
|
|
1447
|
-
description: '查看 git log. 默认 --oneline -10. 支持过滤和范围.',
|
|
1448
|
-
parameters: { range: '可选, e.g. "HEAD~5..HEAD" 或分支名', maxCount: '可选, 默认 10', oneline: '可选, "true" 用 --oneline (默认 true)' },
|
|
1449
|
-
execute: async (args) => {
|
|
1450
|
-
const range = String(args.range || '').trim();
|
|
1451
|
-
const maxCount = parseInt(String(args.maxCount || '10')) || 10;
|
|
1452
|
-
const oneline = String(args.oneline || 'true') === 'true';
|
|
1453
|
-
const argv = ['log'];
|
|
1454
|
-
if (oneline)
|
|
1455
|
-
argv.push('--oneline');
|
|
1456
|
-
if (maxCount > 0)
|
|
1457
|
-
argv.push(`-n`, String(maxCount));
|
|
1458
|
-
if (range)
|
|
1459
|
-
argv.push(range);
|
|
1460
|
-
const result = await shellExec('git', argv, { timeoutMs: 10_000 });
|
|
1461
|
-
if (result.deniedByGuard)
|
|
1462
|
-
return { success: false, error: result.error };
|
|
1463
|
-
if (!result.success)
|
|
1464
|
-
return { success: false, error: result.error, output: result.output };
|
|
1465
|
-
return { success: true, output: `📜 git log:\n${result.output || '(empty)'}` };
|
|
1466
|
-
}
|
|
1467
|
-
});
|
|
1468
|
-
// git show
|
|
1469
|
-
this.tools.set('git_show', {
|
|
1470
|
-
name: 'git_show',
|
|
1471
|
-
description: '查看 commit 内容. 默认 HEAD. 支持 --stat 看统计, --patch 看 diff.',
|
|
1472
|
-
parameters: { ref: '可选, 默认 HEAD', stat: '可选, "true" 只看 stat (默认 false)' },
|
|
1473
|
-
execute: async (args) => {
|
|
1474
|
-
const ref = String(args.ref || 'HEAD').trim();
|
|
1475
|
-
const argv = ['show', ref];
|
|
1476
|
-
if (String(args.stat || 'false') === 'true')
|
|
1477
|
-
argv.push('--stat');
|
|
1478
|
-
const result = await shellExec('git', argv, { timeoutMs: 15_000 });
|
|
1479
|
-
if (result.deniedByGuard)
|
|
1480
|
-
return { success: false, error: result.error };
|
|
1481
|
-
if (!result.success)
|
|
1482
|
-
return { success: false, error: result.error, output: result.output };
|
|
1483
|
-
return { success: true, output: `📜 git show ${ref}:\n${result.output || '(empty)'}` };
|
|
1484
|
-
}
|
|
1485
|
-
});
|
|
1486
|
-
// git stash
|
|
1487
|
-
this.tools.set('git_stash', {
|
|
1488
|
-
name: 'git_stash',
|
|
1489
|
-
description: 'git stash 暂存当前未提交改动. action: save/pop/list/apply/drop. 支持 message.',
|
|
1490
|
-
parameters: { action: '动作 (必填, "save" | "pop" | "list" | "apply" | "drop")', message: '可选, save 时的描述', index: '可选, apply/pop/drop 的 stash index (e.g. "0")' },
|
|
1491
|
-
execute: async (args) => {
|
|
1492
|
-
const action = String(args.action || '').trim();
|
|
1493
|
-
if (!['save', 'pop', 'list', 'apply', 'drop'].includes(action)) {
|
|
1494
|
-
return { success: false, error: `action 必须是 save/pop/list/apply/drop 之一, 收到: ${action}` };
|
|
1495
|
-
}
|
|
1496
|
-
const argv = ['stash', action];
|
|
1497
|
-
if (action === 'save' && args.message)
|
|
1498
|
-
argv.push('-m', String(args.message));
|
|
1499
|
-
if ((action === 'apply' || action === 'drop' || action === 'pop') && args.index) {
|
|
1500
|
-
argv.push('stash@{' + String(args.index) + '}');
|
|
1501
|
-
}
|
|
1502
|
-
const result = await shellExec('git', argv, { timeoutMs: 10_000 });
|
|
1503
|
-
if (result.deniedByGuard)
|
|
1504
|
-
return { success: false, error: result.error };
|
|
1505
|
-
if (!result.success)
|
|
1506
|
-
return { success: false, error: result.error, output: result.output };
|
|
1507
|
-
return { success: true, output: `✅ git stash ${action}\n${result.output || ''}` };
|
|
1508
|
-
}
|
|
1509
|
-
});
|
|
1510
|
-
// vitest 跑测试
|
|
1511
|
-
this.tools.set('vitest_run', {
|
|
1512
|
-
name: 'vitest_run',
|
|
1513
|
-
description: '跑 vitest 测试. 自动 bail (失败就停). 默认 60s timeout.',
|
|
1514
|
-
parameters: { pattern: '可选, 文件 glob e.g. "src/agents/pi-sdk.test.ts"', timeoutMs: '可选, 默认 60000' },
|
|
1515
|
-
execute: async (args) => {
|
|
1516
|
-
const argv = ['vitest', 'run', '--reporter=default', '--no-color', '--bail=1'];
|
|
1517
|
-
if (args.pattern)
|
|
1518
|
-
argv.push(String(args.pattern));
|
|
1519
|
-
const timeoutMs = parseInt(String(args.timeoutMs || '60000')) || 60000;
|
|
1520
|
-
const result = await shellExec('npx', argv, { timeoutMs });
|
|
1521
|
-
if (result.deniedByGuard)
|
|
1522
|
-
return { success: false, error: result.error };
|
|
1523
|
-
if (!result.success)
|
|
1524
|
-
return { success: false, error: result.error || 'vitest 失败', output: result.output };
|
|
1525
|
-
return { success: true, output: `✅ vitest 通过:\n${(result.output || '').slice(0, 2000)}` };
|
|
1526
|
-
}
|
|
1527
|
-
});
|
|
1528
|
-
// tsc 编译检查
|
|
1529
|
-
this.tools.set('tsc_check', {
|
|
1530
|
-
name: 'tsc_check',
|
|
1531
|
-
description: '跑 tsc --noEmit 检查 TypeScript 编译. 默认 60s timeout.',
|
|
1532
|
-
parameters: { project: '可选, tsconfig 路径 (默认 tsconfig.json)', timeoutMs: '可选, 默认 60000' },
|
|
1533
|
-
execute: async (args) => {
|
|
1534
|
-
const argv = ['tsc', '--noEmit'];
|
|
1535
|
-
if (args.project)
|
|
1536
|
-
argv.push('-p', String(args.project));
|
|
1537
|
-
else
|
|
1538
|
-
argv.push('-p', 'tsconfig.json');
|
|
1539
|
-
const timeoutMs = parseInt(String(args.timeoutMs || '60000')) || 60000;
|
|
1540
|
-
const result = await shellExec('npx', argv, { timeoutMs });
|
|
1541
|
-
if (result.deniedByGuard)
|
|
1542
|
-
return { success: false, error: result.error };
|
|
1543
|
-
if (!result.success)
|
|
1544
|
-
return { success: false, error: result.error || 'tsc 失败', output: result.output };
|
|
1545
|
-
return { success: true, output: `✅ tsc 通过:\n${(result.output || 'no errors').slice(0, 1000)}` };
|
|
1546
|
-
}
|
|
1547
|
-
});
|
|
1548
1024
|
// M3.2: 任务状态机工具 — 让 agent 自己维护 multi_step 任务的状态
|
|
1549
1025
|
this.tools.set('create_task', {
|
|
1550
1026
|
name: 'create_task',
|
|
@@ -1685,11 +1161,6 @@ class PiAgentSession {
|
|
|
1685
1161
|
// - 失败结果不缓存 (避免缓存 transient 错误)
|
|
1686
1162
|
// - 缓存容量 200 条, 超过就清空
|
|
1687
1163
|
this.wrapToolsWithIdempotency();
|
|
1688
|
-
// 2026-06-30: 镜像到 ToolRegistry — resolveToolName 走 registry 的 alias 表.
|
|
1689
|
-
// 被 wrap 的 tool (idempotency cache) 也直接拿来用, registry 不重复 wrap.
|
|
1690
|
-
for (const [name, tool] of this.tools.entries()) {
|
|
1691
|
-
this._toolRegistry.register(tool);
|
|
1692
|
-
}
|
|
1693
1164
|
}
|
|
1694
1165
|
/** M3.3: 工具结果缓存 — 防止 loop 重试时副作用 (写文件 / 改代码) 执行多次 */
|
|
1695
1166
|
idempotencyCache = new Map();
|
|
@@ -1736,13 +1207,10 @@ class PiAgentSession {
|
|
|
1736
1207
|
// M2.4 (2026-06-17): 缓存 tool 定义 — registerTools() 在构造时调一次, 此后不变
|
|
1737
1208
|
if (this.cachedToolDefinitions)
|
|
1738
1209
|
return this.cachedToolDefinitions;
|
|
1739
|
-
const defs = ['
|
|
1210
|
+
const defs = ['可用工具:'];
|
|
1740
1211
|
for (const tool of this.tools.values()) {
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
// 避免 system prompt 太大导致 minimax 撞 max_tokens 输出空
|
|
1744
|
-
const paramNames = Object.keys(tool.parameters).join(',');
|
|
1745
|
-
defs.push(`- ${tool.name}(${paramNames})`);
|
|
1212
|
+
const params = Object.entries(tool.parameters).map(([k, v]) => `${k}: ${v}`).join(', ');
|
|
1213
|
+
defs.push(`- ${tool.name}(${params}) - ${tool.description}`);
|
|
1746
1214
|
}
|
|
1747
1215
|
this.cachedToolDefinitions = defs.join('\n');
|
|
1748
1216
|
return this.cachedToolDefinitions;
|
|
@@ -1839,27 +1307,15 @@ class PiAgentSession {
|
|
|
1839
1307
|
}
|
|
1840
1308
|
}
|
|
1841
1309
|
async promptStream(input, onStream, signal, channelId) {
|
|
1842
|
-
console.log(`[PiAgent.promptStream] ENTRY, channelId=${channelId}, input chars=${input.length}`);
|
|
1843
1310
|
this.minimaxAvailable = this.checkMinimax();
|
|
1844
|
-
console.log(`[PiAgent.promptStream] minimaxAvailable=${this.minimaxAvailable}`);
|
|
1845
1311
|
this.currentChannelId = channelId ?? this.currentChannelId;
|
|
1846
|
-
// 2026-06-18 (supervisor): web server 把 46K markedPrompt 喂过来
|
|
1847
|
-
// (【本轮用户请求】\n<text>\n【请求结束】\n\n<contextHint>).
|
|
1848
|
-
// 整个 input 走下游, pivot loop 之前拿 47K buildContext 当 user message 发出去,
|
|
1849
|
-
// 模型撞 context window. 提取 userText 替代 input, contextHint 拼到 systemPrompt 末尾.
|
|
1850
|
-
const markerMatch = input.match(/【本轮用户请求】\s*([\s\S]*?)\s*【请求结束】/);
|
|
1851
|
-
const userText = markerMatch ? markerMatch[1].trim() : input;
|
|
1852
|
-
const contextHint = markerMatch ? input.replace(markerMatch[0], '').trim() : '';
|
|
1853
|
-
console.log(`[PiAgent.promptStream] marker matched=${!!markerMatch}, userText chars=${userText.length}, contextHint chars=${contextHint.length}`);
|
|
1854
1312
|
this.messageHistory.push({
|
|
1855
1313
|
role: 'user',
|
|
1856
|
-
content:
|
|
1314
|
+
content: input
|
|
1857
1315
|
});
|
|
1858
|
-
// 2026-06-18: web server 喂的 markedPrompt 外的 contextHint 拼到 system 末尾 (而不是当 user message)
|
|
1859
|
-
this.contextHintAddition = contextHint;
|
|
1860
1316
|
onStream({ type: 'thinking', content: '🤔 开始思考...' });
|
|
1861
1317
|
if (!this.minimaxAvailable) {
|
|
1862
|
-
const response = await this.handleFallback(
|
|
1318
|
+
const response = await this.handleFallback(input);
|
|
1863
1319
|
this.messageHistory.push({ role: 'assistant', content: response });
|
|
1864
1320
|
onStream({ type: 'done', content: '' });
|
|
1865
1321
|
return response;
|
|
@@ -1867,11 +1323,11 @@ class PiAgentSession {
|
|
|
1867
1323
|
// P0 注入门: 缓存 onStream + signal, computeJudgmentGate 用 currentOnStream 广播 phase
|
|
1868
1324
|
this.currentOnStream = onStream;
|
|
1869
1325
|
this.currentSignal = signal ?? null;
|
|
1870
|
-
await this.computeJudgmentGate(
|
|
1326
|
+
await this.computeJudgmentGate(input);
|
|
1871
1327
|
// M2.2 (2026-06-17): intent 分类 — 0 LLM 成本, 5 行 keyword 匹配
|
|
1872
1328
|
try {
|
|
1873
1329
|
const { classifyIntent, intentHint } = await import('./intent-classifier.js');
|
|
1874
|
-
this.currentIntent = classifyIntent(
|
|
1330
|
+
this.currentIntent = classifyIntent(input);
|
|
1875
1331
|
this.currentIntentHint = intentHint(this.currentIntent);
|
|
1876
1332
|
if (this.currentIntent !== 'chitchat') {
|
|
1877
1333
|
onStream({ type: 'phase', phase: 'intent_classified', detail: this.currentIntent, content: '' });
|
|
@@ -1892,13 +1348,9 @@ class PiAgentSession {
|
|
|
1892
1348
|
}
|
|
1893
1349
|
// Bootstrap SessionStart: 收集项目 Context, 拼到 systemAddition 头部
|
|
1894
1350
|
// (失败静默, 5s 限流防止循环)
|
|
1895
|
-
// 2026-07-04: 透传 agentId 让 onSessionStart 加载 persona 文档
|
|
1896
1351
|
let bootstrapAddition = '';
|
|
1897
1352
|
try {
|
|
1898
|
-
const ss = await onSessionStart({
|
|
1899
|
-
channelId: this.currentChannelId || undefined,
|
|
1900
|
-
agentId: this.currentAgentId || undefined,
|
|
1901
|
-
});
|
|
1353
|
+
const ss = await onSessionStart({ channelId: this.currentChannelId || undefined });
|
|
1902
1354
|
bootstrapAddition = ss.systemAddition || '';
|
|
1903
1355
|
}
|
|
1904
1356
|
catch (err) {
|
|
@@ -1920,7 +1372,7 @@ class PiAgentSession {
|
|
|
1920
1372
|
if (this.usePivotLoop) {
|
|
1921
1373
|
let pivotResult = '';
|
|
1922
1374
|
try {
|
|
1923
|
-
const lr = await this.promptWithPivotLoop(
|
|
1375
|
+
const lr = await this.promptWithPivotLoop(input, undefined, channelId);
|
|
1924
1376
|
pivotResult = lr.response || '';
|
|
1925
1377
|
onStream({ type: 'done', content: '' });
|
|
1926
1378
|
}
|
|
@@ -1944,7 +1396,7 @@ class PiAgentSession {
|
|
|
1944
1396
|
}
|
|
1945
1397
|
catch { }
|
|
1946
1398
|
}
|
|
1947
|
-
monitorAfterReply(
|
|
1399
|
+
monitorAfterReply(input, pivotResult);
|
|
1948
1400
|
const stopStartTime = this.promptStartTime || Date.now();
|
|
1949
1401
|
onStop({
|
|
1950
1402
|
channelId: this.currentChannelId || 'unknown',
|
|
@@ -1955,7 +1407,6 @@ class PiAgentSession {
|
|
|
1955
1407
|
this.currentOnStream = null;
|
|
1956
1408
|
this.currentSignal = null;
|
|
1957
1409
|
this.bootstrapAddition = '';
|
|
1958
|
-
this.contextHintAddition = '';
|
|
1959
1410
|
this.promptStartTime = 0;
|
|
1960
1411
|
}
|
|
1961
1412
|
return pivotResult;
|
|
@@ -2089,45 +1540,37 @@ class PiAgentSession {
|
|
|
2089
1540
|
}
|
|
2090
1541
|
// M2.4: persona 缓存
|
|
2091
1542
|
if (!this.cachedPersonaSection && this.persona) {
|
|
2092
|
-
this.cachedPersonaSection = `
|
|
2093
|
-
角色描述: ${this.persona.description || '无'}
|
|
2094
|
-
性格特点: ${this.persona.personality || '无'}
|
|
2095
|
-
问候语: ${this.persona.greeting || '无'}
|
|
1543
|
+
this.cachedPersonaSection = `
|
|
1544
|
+
角色描述: ${this.persona.description || '无'}
|
|
1545
|
+
性格特点: ${this.persona.personality || '无'}
|
|
1546
|
+
问候语: ${this.persona.greeting || '无'}
|
|
2096
1547
|
`;
|
|
2097
1548
|
}
|
|
2098
|
-
const systemPrompt = `${this.bootstrapAddition}你是 ${this.identity.name},基于ReAct (Reasoning + Acting)模式工作。${this.cachedPersonaSection}
|
|
2099
|
-
当前工作目录: ${this.cwd}
|
|
2100
|
-
当前身份: ${this.identity.name} (${this.identity.did})
|
|
2101
|
-
${this.currentIntentHint}
|
|
2102
|
-
|
|
2103
|
-
${this.getToolDefinitions()}
|
|
2104
|
-
|
|
2105
|
-
工作模式:
|
|
2106
|
-
1. 理解用户自然语言请求
|
|
2107
|
-
2. 分析需要哪些工具来完成
|
|
2108
|
-
3. 按顺序调用工具并观察结果
|
|
2109
|
-
4. 根据观察结果决定下一步
|
|
2110
|
-
5. 最终给出完整回答
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
- 每次只调用一个工具
|
|
2114
|
-
- 仔细分析工具返回结果
|
|
2115
|
-
- 当任务完成时,必须在回答末尾添加 <final gen> 标记表示结束
|
|
2116
|
-
-
|
|
2117
|
-
|
|
2118
|
-
【工具调用格式 (严格遵守, 否则系统无法解析)】
|
|
2119
|
-
- 你只能输出**一个**工具调用, 不要堆叠多个 invoke
|
|
2120
|
-
- 工具调用格式: {"name":"<tool_name>","input":{"arg1":"value1"}}
|
|
2121
|
-
- 用 markdown json code block 包裹: \`\`\`json\n{"name":"X","input":{...}}\n\`\`\`
|
|
2122
|
-
- 工具调用前可以简短思考 (1-2 句话), 但**不要写长篇 thinking** (会撞 max_tokens)
|
|
2123
|
-
- 工具调用后必须等结果, 不要在同一个回复里继续输出
|
|
2124
|
-
- <final gen> 只在**真完成所有任务**时输出, 不要在工具调用前/中输出${this.judgmentGateAddition}${this.contextHintAddition}`;
|
|
1549
|
+
const systemPrompt = `${this.bootstrapAddition}你是 ${this.identity.name},基于ReAct (Reasoning + Acting)模式工作。${this.cachedPersonaSection}
|
|
1550
|
+
当前工作目录: ${this.cwd}
|
|
1551
|
+
当前身份: ${this.identity.name} (${this.identity.did})
|
|
1552
|
+
${this.currentIntentHint}
|
|
1553
|
+
|
|
1554
|
+
${this.getToolDefinitions()}
|
|
1555
|
+
|
|
1556
|
+
工作模式:
|
|
1557
|
+
1. 理解用户自然语言请求
|
|
1558
|
+
2. 分析需要哪些工具来完成
|
|
1559
|
+
3. 按顺序调用工具并观察结果
|
|
1560
|
+
4. 根据观察结果决定下一步
|
|
1561
|
+
5. 最终给出完整回答
|
|
1562
|
+
|
|
1563
|
+
重要:
|
|
1564
|
+
- 每次只调用一个工具
|
|
1565
|
+
- 仔细分析工具返回结果
|
|
1566
|
+
- 当任务完成时,必须在回答末尾添加 <final gen> 标记表示结束
|
|
1567
|
+
- 如果需要更多信息,继续调用工具${this.judgmentGateAddition}`;
|
|
2125
1568
|
// 2026-06-15: 把 currentOnStream 传给 loop, 让 step-timeline 在 pivot 循环里也能 emit step_start/done
|
|
2126
1569
|
// 之前 loop.execute() 不接 streamCallback, 导致 step-timeline 只能看到老 runReActLoop 路径
|
|
2127
1570
|
// promptWithPivotLoop 路径 0 step events — UI 显示 timeline 但永远是空
|
|
2128
1571
|
// 2026-06-17: 透传 signal 让 abort 工作 — loop.execute() 当前不接 signal 参数,
|
|
2129
1572
|
// 所以 abort 行为通过 this.currentSignal 共享给 loop 内部读 (后续 M3.2 接 task plan 时一起加)
|
|
2130
|
-
const result = await loop.execute(input, llm, systemPrompt, this.currentOnStream ?? undefined
|
|
1573
|
+
const result = await loop.execute(input, llm, systemPrompt, this.currentOnStream ?? undefined);
|
|
2131
1574
|
this.messageHistory.push({ role: 'user', content: input });
|
|
2132
1575
|
if (result.response) {
|
|
2133
1576
|
this.messageHistory.push({ role: 'assistant', content: result.response });
|
|
@@ -2171,12 +1614,10 @@ ${this.getToolDefinitions()}
|
|
|
2171
1614
|
while (iteration < this.MAX_REACT_ITERATIONS) {
|
|
2172
1615
|
iteration++;
|
|
2173
1616
|
// 停止条件 1: max turns (fail-safe 10000, 正常任务永远跑不到)
|
|
2174
|
-
|
|
2175
|
-
const maxIterDecision = decideMaxIterations(iteration, this.MAX_REACT_ITERATIONS);
|
|
2176
|
-
if (maxIterDecision.shouldExit) {
|
|
1617
|
+
if (iteration >= this.MAX_REACT_ITERATIONS) {
|
|
2177
1618
|
console.warn(`[PiAgent] 达到最大循环数 ${this.MAX_REACT_ITERATIONS}, 强制终止 (fail-safe)`);
|
|
2178
1619
|
onStream?.({ type: 'error', content: `⏹️ 达到最大循环数 (${this.MAX_REACT_ITERATIONS}, fail-safe)`, tool: 'loop' });
|
|
2179
|
-
finalResponse = finalResponse ||
|
|
1620
|
+
finalResponse = finalResponse || '(本轮 ReAct 循环达到最大步数, 强制结束)';
|
|
2180
1621
|
break;
|
|
2181
1622
|
}
|
|
2182
1623
|
// 停止条件 2: signal.aborted (显式 abort / 用户中断)
|
|
@@ -2190,23 +1631,14 @@ ${this.getToolDefinitions()}
|
|
|
2190
1631
|
if (totalErrors >= this.MAX_TOTAL_ERRORS) {
|
|
2191
1632
|
console.warn(`[PiAgent] 累计错误 ${totalErrors} >= ${this.MAX_TOTAL_ERRORS}, 强制终止 (防死循环)`);
|
|
2192
1633
|
onStream?.({ type: 'error', content: `⛔ 累计 ${totalErrors} 次错误, 强制终止 (防止 LLM 死循环)`, tool: 'loop' });
|
|
2193
|
-
|
|
2194
|
-
if (this.successfulToolResults.length > 0) {
|
|
2195
|
-
finalResponse = `✅ 之前步骤成功执行了 ${this.successfulToolResults.length} 个工具 (但 LLM 后续 ${totalErrors} 次调用失败):\n` +
|
|
2196
|
-
this.successfulToolResults.map((r, i) => ` ${i + 1}. ${r.tool}: ${r.outputPreview}`).join('\n') +
|
|
2197
|
-
`\n\n⚠️ (LLM 连续失败, 可能是 minimax 上游限流/网络问题, 工具已成功执行但 LLM 没能继续总结)`;
|
|
2198
|
-
}
|
|
2199
|
-
else {
|
|
2200
|
-
finalResponse = finalResponse || `(本轮 ReAct 循环累计 ${totalErrors} 次错误, 强制结束。请换个思路或简化任务重试。)`;
|
|
2201
|
-
}
|
|
1634
|
+
finalResponse = finalResponse || `(本轮 ReAct 循环累计 ${totalErrors} 次错误, 强制结束。请换个思路或简化任务重试。)`;
|
|
2202
1635
|
break;
|
|
2203
1636
|
}
|
|
2204
1637
|
// 2026-06-16 新增: loop 内自动压缩 — token 超 80% 阈值时跑一次
|
|
2205
1638
|
// compact 失败走 C 路径: 不强行 break, 让现有 60K 阈值兜底 (后面有检查)
|
|
2206
|
-
// 2026-07-01 (v0.2.4 子任务 1): 触发判定走 shouldCompactBeforeIteration 纯函数
|
|
2207
1639
|
const compactThreshold = this.MAX_OUTPUT_TOKEN_ESCALATION_THRESHOLD * this.LOOP_COMPACT_RATIO;
|
|
2208
1640
|
const estimatedTokensBefore = this.estimateHistoryTokens();
|
|
2209
|
-
if (
|
|
1641
|
+
if (estimatedTokensBefore > compactThreshold) {
|
|
2210
1642
|
const tokensBeforeCompact = estimatedTokensBefore;
|
|
2211
1643
|
console.log(`[PiAgent] loop 入口 token ${tokensBeforeCompact} > ${compactThreshold}, 触发自动压缩`);
|
|
2212
1644
|
onStream?.({ type: 'status', content: `🗜️ loop 自动压缩 (token ${tokensBeforeCompact} > ${compactThreshold})`, tool: 'compactor' });
|
|
@@ -2219,13 +1651,11 @@ ${this.getToolDefinitions()}
|
|
|
2219
1651
|
}
|
|
2220
1652
|
}
|
|
2221
1653
|
// 停止条件 3: context overflow (compact 后还超, 强制终止)
|
|
2222
|
-
// 2026-07-01 (v0.2.4 子任务 1): 委托给 react-loop.decideContextOverflow 纯函数
|
|
2223
1654
|
const estimatedTokens = this.estimateHistoryTokens();
|
|
2224
|
-
|
|
2225
|
-
if (overflowDecision.shouldExit) {
|
|
1655
|
+
if (estimatedTokens > this.MAX_OUTPUT_TOKEN_ESCALATION_THRESHOLD) {
|
|
2226
1656
|
console.warn(`[PiAgent] context overflow (${estimatedTokens} tokens > ${this.MAX_OUTPUT_TOKEN_ESCALATION_THRESHOLD})`);
|
|
2227
1657
|
onStream?.({ type: 'error', content: `⏹️ 上下文溢出 (${estimatedTokens} tokens, 阈值 ${this.MAX_OUTPUT_TOKEN_ESCALATION_THRESHOLD})`, tool: 'loop' });
|
|
2228
|
-
finalResponse = finalResponse ||
|
|
1658
|
+
finalResponse = finalResponse || '(本轮 ReAct 循环因上下文溢出终止)';
|
|
2229
1659
|
break;
|
|
2230
1660
|
}
|
|
2231
1661
|
// 调试日志:显示每次循环开始
|
|
@@ -2250,33 +1680,33 @@ ${this.getToolDefinitions()}
|
|
|
2250
1680
|
}
|
|
2251
1681
|
// M2.4: persona section 缓存 — persona 在 loadPersona() 时一次设定, 此后不变
|
|
2252
1682
|
if (!this.cachedPersonaSection && this.persona) {
|
|
2253
|
-
this.cachedPersonaSection = `
|
|
2254
|
-
角色描述: ${this.persona.description || '无'}
|
|
2255
|
-
性格特点: ${this.persona.personality || '无'}
|
|
2256
|
-
问候语: ${this.persona.greeting || '无'}
|
|
1683
|
+
this.cachedPersonaSection = `
|
|
1684
|
+
角色描述: ${this.persona.description || '无'}
|
|
1685
|
+
性格特点: ${this.persona.personality || '无'}
|
|
1686
|
+
问候语: ${this.persona.greeting || '无'}
|
|
2257
1687
|
`;
|
|
2258
1688
|
}
|
|
2259
1689
|
const personaSection = this.cachedPersonaSection;
|
|
2260
|
-
const systemPrompt = `${this.bootstrapAddition}你是 ${this.identity.name},基于ReAct (Reasoning + Acting)模式工作。${personaSection}
|
|
2261
|
-
当前工作目录: ${this.cwd}
|
|
2262
|
-
当前身份: ${this.identity.name} (${this.identity.did})
|
|
2263
|
-
${refineContext}
|
|
2264
|
-
${this.currentIntentHint}
|
|
2265
|
-
|
|
2266
|
-
${toolDefs}
|
|
2267
|
-
|
|
2268
|
-
工作模式:
|
|
2269
|
-
1. 理解用户自然语言请求
|
|
2270
|
-
2. 分析需要哪些工具来完成
|
|
2271
|
-
3. 按顺序调用工具并观察结果
|
|
2272
|
-
4. 根据观察结果决定下一步
|
|
2273
|
-
5. 最终给出完整回答
|
|
2274
|
-
|
|
2275
|
-
重要:
|
|
2276
|
-
- 每次只调用一个工具
|
|
2277
|
-
- 仔细分析工具返回结果
|
|
2278
|
-
- 当任务完成时,必须在回答末尾添加 <final gen> 标记表示结束
|
|
2279
|
-
- 如果需要更多信息,继续调用工具${this.judgmentGateAddition}
|
|
1690
|
+
const systemPrompt = `${this.bootstrapAddition}你是 ${this.identity.name},基于ReAct (Reasoning + Acting)模式工作。${personaSection}
|
|
1691
|
+
当前工作目录: ${this.cwd}
|
|
1692
|
+
当前身份: ${this.identity.name} (${this.identity.did})
|
|
1693
|
+
${refineContext}
|
|
1694
|
+
${this.currentIntentHint}
|
|
1695
|
+
|
|
1696
|
+
${toolDefs}
|
|
1697
|
+
|
|
1698
|
+
工作模式:
|
|
1699
|
+
1. 理解用户自然语言请求
|
|
1700
|
+
2. 分析需要哪些工具来完成
|
|
1701
|
+
3. 按顺序调用工具并观察结果
|
|
1702
|
+
4. 根据观察结果决定下一步
|
|
1703
|
+
5. 最终给出完整回答
|
|
1704
|
+
|
|
1705
|
+
重要:
|
|
1706
|
+
- 每次只调用一个工具
|
|
1707
|
+
- 仔细分析工具返回结果
|
|
1708
|
+
- 当任务完成时,必须在回答末尾添加 <final gen> 标记表示结束
|
|
1709
|
+
- 如果需要更多信息,继续调用工具${this.judgmentGateAddition}`;
|
|
2280
1710
|
// 3 个恢复机制 (Claude Code 论文 9-step pipeline 内部):
|
|
2281
1711
|
// 1. max output token 升级 (最多 3 次, 每次 maxOutputTokens 翻倍)
|
|
2282
1712
|
// 2. reactive compaction (prompt 估算超阈值, 跑压缩)
|
|
@@ -2284,31 +1714,18 @@ ${toolDefs}
|
|
|
2284
1714
|
// 失败静默: 全部重试失败 → 空 reply (上层用 no tool_use 终止)
|
|
2285
1715
|
const response = await this.callLlmWithRecovery(llm, messages, systemPrompt, signal, onStream);
|
|
2286
1716
|
const reply = (response.reply || '').trim();
|
|
2287
|
-
// 2026-06-
|
|
2288
|
-
|
|
2289
|
-
//
|
|
2290
|
-
//
|
|
2291
|
-
// 新逻辑: 把错误当 tool_result push 进 history → 下一轮 LLM 看到错误能反思重试
|
|
2292
|
-
// 这是 dive-into 文档的"fail-open error recovery" — 错误进入 context, 不让 LLM 重复犯同样错
|
|
1717
|
+
// 2026-06-16: 看到 [AI 服务调用失败] sentinel → 不再立即 break,
|
|
1718
|
+
// 而是设 aiFailed=true, 让外层 promptStream 自动重跑整个 loop 最多 N 次
|
|
1719
|
+
// (LLM API 401 / 网络错 / 配额满时, pi-ai 返回这个 prefix;
|
|
1720
|
+
// 自动 retry 兜底: 临时网络抖动可自愈, 真挂 N 次后才报失败)
|
|
2293
1721
|
if (reply.startsWith('[AI 服务调用失败]')) {
|
|
2294
|
-
console.log(`[PiAgent] 收到 AI 错误 sentinel,
|
|
2295
|
-
|
|
2296
|
-
console.log(`[sentinel DEBUG] 上一轮 messages 数量: ${Array.isArray(messages) ? messages.length : 'N/A'}, systemPrompt 长度: ${systemPrompt.length}`);
|
|
1722
|
+
console.log(`[PiAgent] 收到 AI 错误 sentinel, 标记 aiFailed, 外层会自动重试整个 loop`);
|
|
1723
|
+
aiFailed = true;
|
|
2297
1724
|
aiFailureReason = reply.length > 200 ? reply.substring(0, 200) : reply;
|
|
2298
|
-
totalErrors++;
|
|
2299
|
-
consecutiveErrors++;
|
|
2300
|
-
// 把错误当成 tool 结果 push 进 history, 这样下一轮 LLM 看到错误能调整
|
|
2301
|
-
this.messageHistory.push({
|
|
2302
|
-
role: 'system',
|
|
2303
|
-
content: `[Loop 错误恢复 ${totalErrors}/${this.MAX_TOTAL_ERRORS}] ${aiFailureReason}\n\n请基于上轮工具结果继续完成任务, 不要重复调用同一失败操作. 如果工具已成功执行, 请基于 result.output 给用户总结; 如果工具失败, 请换其他方式或重试.`
|
|
2304
|
-
});
|
|
2305
1725
|
if (onStream) {
|
|
2306
|
-
onStream({ type: 'status', content: `⚠️ AI
|
|
1726
|
+
onStream({ type: 'status', content: `⚠️ AI 调用失败, 将自动重试整个 loop`, tool: 'system' });
|
|
2307
1727
|
}
|
|
2308
|
-
|
|
2309
|
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
2310
|
-
// 关键: 不设 aiFailed=true, 让外层不重试整个 loop (重置 history), 继续内层循环
|
|
2311
|
-
continue;
|
|
1728
|
+
break;
|
|
2312
1729
|
}
|
|
2313
1730
|
console.log(`[PiAgent] LLM 回复长度: ${reply.length}, 内容预览: "${reply.substring(0, 80)}..."`);
|
|
2314
1731
|
console.log(`[PiAgent] LLM 完整回复:\n${reply}`);
|
|
@@ -2316,14 +1733,20 @@ ${toolDefs}
|
|
|
2316
1733
|
if (onStream) {
|
|
2317
1734
|
onStream({ type: 'token', content: reply.substring(0, 100) });
|
|
2318
1735
|
}
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
1736
|
+
if (this.isFinalResponse(reply)) {
|
|
1737
|
+
// 检查质量分数
|
|
1738
|
+
lastQualityScore = this.estimateResponseQuality(reply);
|
|
1739
|
+
// 如果质量太低且还有改进机会,进入改进循环
|
|
1740
|
+
if (lastQualityScore < this.QUALITY_THRESHOLD && refineAttempts < this.MAX_REFINE_ATTEMPTS) {
|
|
1741
|
+
refineAttempts++;
|
|
1742
|
+
console.log(`[PiAgent] 质量评分 ${(lastQualityScore * 10).toFixed(1)}/10 < ${(this.QUALITY_THRESHOLD * 10).toFixed(1)}/10,自动改进中 (${refineAttempts}/${this.MAX_REFINE_ATTEMPTS})`);
|
|
1743
|
+
continue;
|
|
1744
|
+
}
|
|
1745
|
+
finalResponse = this.extractFinalAnswer(reply);
|
|
1746
|
+
break;
|
|
1747
|
+
}
|
|
2322
1748
|
const toolCall = this.parseToolCall(reply);
|
|
2323
|
-
// 2026-06-30 修: 给 toolCall 分配稳定 id, 让后续 tool result 能引用同一个 id
|
|
2324
|
-
// OpenAI 协议要求 messages 里 tool result 必须有对应的 tool_call_id, 否则 400
|
|
2325
1749
|
if (toolCall) {
|
|
2326
|
-
toolCall.id = `call_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
2327
1750
|
this.messageHistory.push({
|
|
2328
1751
|
role: 'assistant',
|
|
2329
1752
|
content: reply,
|
|
@@ -2489,7 +1912,7 @@ ${toolDefs}
|
|
|
2489
1912
|
catch (err) {
|
|
2490
1913
|
console.warn('[PiAgent] reactHarness.postToolCall failed (non-fatal, allowing):', err);
|
|
2491
1914
|
}
|
|
2492
|
-
this.messageHistory.push({ role: 'tool', content: JSON.stringify(result), toolResult: result
|
|
1915
|
+
this.messageHistory.push({ role: 'tool', content: JSON.stringify(result), toolResult: result });
|
|
2493
1916
|
this.logToHarness(toolCall.name, toolCall.args, result);
|
|
2494
1917
|
// 通知前端工具执行结果
|
|
2495
1918
|
if (onStream) {
|
|
@@ -2521,16 +1944,6 @@ ${toolDefs}
|
|
|
2521
1944
|
}
|
|
2522
1945
|
if (result.success) {
|
|
2523
1946
|
consecutiveErrors = 0; // 重置连续错误计数
|
|
2524
|
-
// 2026-06-19: 记录成功结果, 用于 LLM 失败退出时汇总给用户
|
|
2525
|
-
if (result.output) {
|
|
2526
|
-
this.successfulToolResults.push({
|
|
2527
|
-
tool: toolCall.name,
|
|
2528
|
-
outputPreview: result.output.substring(0, 200) + (result.output.length > 200 ? '...' : '')
|
|
2529
|
-
});
|
|
2530
|
-
}
|
|
2531
|
-
else {
|
|
2532
|
-
this.successfulToolResults.push({ tool: toolCall.name, outputPreview: '(无输出)' });
|
|
2533
|
-
}
|
|
2534
1947
|
// 检查工具执行质量
|
|
2535
1948
|
lastQualityScore = this.estimateToolResultQuality(result);
|
|
2536
1949
|
if (lastQualityScore < this.QUALITY_THRESHOLD && refineAttempts < this.MAX_REFINE_ATTEMPTS) {
|
|
@@ -2602,36 +2015,6 @@ ${toolDefs}
|
|
|
2602
2015
|
if (onStream) {
|
|
2603
2016
|
onStream({ type: 'token', content: reply.substring(0, 150) });
|
|
2604
2017
|
}
|
|
2605
|
-
// 2026-06-19 架构 fix: 只有 strip <think> 后才检查 isFinalResponse
|
|
2606
|
-
// (parseToolCall 已先尝试, 既然没解析出 tool_call, 现在检查 final gen 是否真的在最终回答区)
|
|
2607
|
-
if (this.isFinalResponse(reply)) {
|
|
2608
|
-
// 2026-06-19 dive-into 风格修复: 如果还有 successful tool results 没汇报,
|
|
2609
|
-
// LLM 不能提前 final_gen — harness 自动注入"请汇报剩余工具结果" hint 再 continue
|
|
2610
|
-
// 这是 dive-into 文档"step 9 stop condition check" 的具体化:
|
|
2611
|
-
// stop condition = (有工具结果未汇报) ? continue : break
|
|
2612
|
-
// 检查回复是否包含工具结果内容(避免无限循环)
|
|
2613
|
-
const hasToolResultContent = this.successfulToolResults.some(r => reply.includes(r.tool) || reply.includes(r.outputPreview.substring(0, 50)));
|
|
2614
|
-
// 如果回复包含工具结果内容,清除 successfulToolResults
|
|
2615
|
-
if (hasToolResultContent) {
|
|
2616
|
-
console.log(`[PiAgent] 回复包含工具结果内容, 清除 successfulToolResults (${this.successfulToolResults.length} 个)`);
|
|
2617
|
-
this.successfulToolResults = [];
|
|
2618
|
-
}
|
|
2619
|
-
if (this.successfulToolResults.length > 0 && iteration < this.MAX_REACT_ITERATIONS) {
|
|
2620
|
-
const unreported = this.successfulToolResults.length;
|
|
2621
|
-
console.log(`[PiAgent] LLM 想 final_gen 但还有 ${unreported} 个工具结果未汇报, push hint 让其继续`);
|
|
2622
|
-
this.messageHistory.push({
|
|
2623
|
-
role: 'system',
|
|
2624
|
-
content: `[dive-into stop condition] 你之前已成功执行了 ${unreported} 个工具, 但当前回复里没把它们的结果告诉用户. 请基于已有的工具结果 (在 history 里) 写一个完整总结回复给用户, 用 <final gen> 结尾. 不要再调工具.`
|
|
2625
|
-
});
|
|
2626
|
-
if (onStream) {
|
|
2627
|
-
onStream({ type: 'status', content: `🔄 还有 ${unreported} 个工具结果未汇报, 让 LLM 继续总结`, tool: 'system' });
|
|
2628
|
-
}
|
|
2629
|
-
continue;
|
|
2630
|
-
}
|
|
2631
|
-
lastQualityScore = this.estimateResponseQuality(reply);
|
|
2632
|
-
finalResponse = this.extractFinalAnswer(reply);
|
|
2633
|
-
break;
|
|
2634
|
-
}
|
|
2635
2018
|
// 检查是否需要继续循环处理
|
|
2636
2019
|
// 更严格的判断:只有当回复明确表示需要更多信息时才继续
|
|
2637
2020
|
const containsToolCallIntent = reply.includes('调用工具') || reply.includes('tool(') ||
|
|
@@ -2677,12 +2060,12 @@ ${toolDefs}
|
|
|
2677
2060
|
onStream({ type: 'status', content: `✅ 处理完成,共 ${iteration - 1} 次循环`, tool: 'system' });
|
|
2678
2061
|
}
|
|
2679
2062
|
const now = new Date().toISOString();
|
|
2680
|
-
const identityPrefix = `${this.identity.name} | bolloon 智能体
|
|
2681
|
-
<environment_details>
|
|
2682
|
-
Current time: ${now}
|
|
2683
|
-
Working directory: ${this.cwd}
|
|
2684
|
-
Workspace root folder: ${this.cwd}
|
|
2685
|
-
</environment_details>
|
|
2063
|
+
const identityPrefix = `${this.identity.name} | bolloon 智能体
|
|
2064
|
+
<environment_details>
|
|
2065
|
+
Current time: ${now}
|
|
2066
|
+
Working directory: ${this.cwd}
|
|
2067
|
+
Workspace root folder: ${this.cwd}
|
|
2068
|
+
</environment_details>
|
|
2686
2069
|
`;
|
|
2687
2070
|
finalResponse = identityPrefix + finalResponse;
|
|
2688
2071
|
this.messageHistory.push({ role: 'assistant', content: finalResponse });
|
|
@@ -2774,45 +2157,18 @@ Workspace root folder: ${this.cwd}
|
|
|
2774
2157
|
for (const m of recentMessages) {
|
|
2775
2158
|
const role = m.role;
|
|
2776
2159
|
let content = m.content;
|
|
2777
|
-
//
|
|
2778
|
-
// bolloon 之前把所有 tool result 包成 "[工具结果] ..." 当 user/assistant role 发, minimax 严格校验失败
|
|
2779
|
-
// 现在: 保留 role='tool' + 加 tool_call_id 字段 (用 messageHistory 里自己生成的 id)
|
|
2160
|
+
// tool role: 用 toolResult 序列化 (跟 buildContext 一样)
|
|
2780
2161
|
if (role === 'tool') {
|
|
2781
2162
|
const result = m.toolResult ? JSON.stringify(m.toolResult) : content;
|
|
2782
2163
|
content = `[工具结果] ${result}`;
|
|
2783
|
-
// MiniMax 等 API 不支持 tool role, 转为 user role
|
|
2784
|
-
out.push({ role: 'user', content });
|
|
2785
|
-
continue;
|
|
2786
2164
|
}
|
|
2787
2165
|
// system role (router hint 等) 直接保留
|
|
2788
2166
|
if (role === 'system') {
|
|
2789
2167
|
out.push({ role: 'system', content });
|
|
2790
2168
|
continue;
|
|
2791
2169
|
}
|
|
2792
|
-
//
|
|
2793
|
-
|
|
2794
|
-
if (role === 'assistant') {
|
|
2795
|
-
const tc = m.toolCall;
|
|
2796
|
-
if (tc && tc.id) {
|
|
2797
|
-
out.push({
|
|
2798
|
-
role: 'assistant',
|
|
2799
|
-
content: content || '',
|
|
2800
|
-
tool_calls: [{
|
|
2801
|
-
id: tc.id,
|
|
2802
|
-
type: 'function',
|
|
2803
|
-
function: {
|
|
2804
|
-
name: tc.name,
|
|
2805
|
-
arguments: JSON.stringify(tc.args || {}),
|
|
2806
|
-
},
|
|
2807
|
-
}],
|
|
2808
|
-
});
|
|
2809
|
-
}
|
|
2810
|
-
else {
|
|
2811
|
-
out.push({ role, content });
|
|
2812
|
-
}
|
|
2813
|
-
continue;
|
|
2814
|
-
}
|
|
2815
|
-
if (role === 'user') {
|
|
2170
|
+
// assistant / user / tool 直接转
|
|
2171
|
+
if (role === 'user' || role === 'assistant' || role === 'tool') {
|
|
2816
2172
|
out.push({ role, content });
|
|
2817
2173
|
}
|
|
2818
2174
|
}
|
|
@@ -2899,8 +2255,7 @@ Workspace root folder: ${this.cwd}
|
|
|
2899
2255
|
// M3.5 (2026-06-17): 传 messages 数组 (如果 contextOrMessages 是数组) 或字符串
|
|
2900
2256
|
// 数组版让 LLM 看到结构化的 user/assistant/tool role, 而不是把 history 拼成单字符串
|
|
2901
2257
|
const response = await llm.chat(contextOrMessages, systemPrompt, signal);
|
|
2902
|
-
|
|
2903
|
-
return { reply: response.reply || '', toolCalls: response.toolCalls };
|
|
2258
|
+
return { reply: response.reply || '' };
|
|
2904
2259
|
}
|
|
2905
2260
|
catch (err) {
|
|
2906
2261
|
// 用户主动 abort: 不重试, 立即抛
|
|
@@ -3026,375 +2381,100 @@ Workspace root folder: ${this.cwd}
|
|
|
3026
2381
|
}
|
|
3027
2382
|
}
|
|
3028
2383
|
isFinalResponse(content) {
|
|
3029
|
-
//
|
|
3030
|
-
return
|
|
2384
|
+
// 只有明确输出 <final gen> 才认为是最终回答
|
|
2385
|
+
return content.includes('<final gen>');
|
|
3031
2386
|
}
|
|
3032
2387
|
extractFinalAnswer(content) {
|
|
3033
|
-
//
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
parseToolCall(content) {
|
|
3043
|
-
// 2026-06-30: 抽到 ./parse-tool-call.ts 作为纯函数 — 这里只构建 ctx 并调用
|
|
3044
|
-
return parseToolCallImpl(content, this._parseCtx());
|
|
3045
|
-
}
|
|
3046
|
-
// [debug-2026-06-19] 临时: 打印 parseToolCall 输入和返回
|
|
3047
|
-
_dbgParseToolCall(content) {
|
|
3048
|
-
const r = this.parseToolCall(content);
|
|
3049
|
-
console.log('[DBG parseToolCall] result:', JSON.stringify(r), 'content head:', JSON.stringify(content.substring(0, 200)));
|
|
3050
|
-
return r;
|
|
3051
|
-
}
|
|
3052
|
-
/**
|
|
3053
|
-
* 2026-06-24: 注册 Wallet + Polymarket + Safe 工具
|
|
3054
|
-
* 真实实现 (ethers v6 + JsonRpcProvider) — agent 可以创建 EVM 钱包, 查余额, 转账, 签名, Polymarket 下单
|
|
3055
|
-
* 工具来源: src/constraint-runtime/src/tools/{WalletTools,PolymarketSDK,SafeSDK}/
|
|
3056
|
-
*/
|
|
3057
|
-
_registerWalletTools() {
|
|
3058
|
-
// === WalletTools (EVM 钱包) ===
|
|
3059
|
-
this.tools.set('wallet_create', {
|
|
3060
|
-
name: 'wallet_create',
|
|
3061
|
-
description: '创建新 EVM 钱包 (BIP-39 12 词助记词 + 私钥 + 地址). 私钥会在响应中返回, agent 应当保存到 ~/.bolloon/wallets/<address>.json',
|
|
3062
|
-
parameters: {},
|
|
3063
|
-
execute: async () => {
|
|
3064
|
-
try {
|
|
3065
|
-
const { createWallet } = await import('../constraint-runtime/dist/tools/WalletTools/createWallet.js').catch(() => import('../constraint-runtime/src/tools/WalletTools/createWallet.js'));
|
|
3066
|
-
const r = await createWallet();
|
|
3067
|
-
return { success: true, output: `✅ 钱包创建成功:\n address: ${r.address}\n privateKey: ${r.privateKey}\n mnemonic: ${r.mnemonic}\n createdAt: ${r.createdAt}\n\n⚠️ 请 agent 立即把私钥保存到 ~/.bolloon/wallets/${r.address}.json` };
|
|
3068
|
-
}
|
|
3069
|
-
catch (e) {
|
|
3070
|
-
return { success: false, error: `创建失败: ${String(e.message || e)}` };
|
|
3071
|
-
}
|
|
3072
|
-
}
|
|
3073
|
-
});
|
|
3074
|
-
this.tools.set('wallet_import', {
|
|
3075
|
-
name: 'wallet_import',
|
|
3076
|
-
description: '导入已有 EVM 钱包 (用助记词或私钥)',
|
|
3077
|
-
parameters: { mnemonic: '可选, 12/15/18/21/24 词助记词', privateKey: '可选, 0x 开头的私钥' },
|
|
3078
|
-
execute: async (args) => {
|
|
3079
|
-
try {
|
|
3080
|
-
const { importWallet } = await import('../constraint-runtime/dist/tools/WalletTools/importWallet.js').catch(() => import('../constraint-runtime/src/tools/WalletTools/importWallet.js'));
|
|
3081
|
-
const r = await importWallet({ mnemonic: args.mnemonic, privateKey: args.privateKey });
|
|
3082
|
-
return { success: true, output: `✅ 钱包导入成功:\n address: ${r.address}\n privateKey: ${r.privateKey}\n source: ${r.source}` };
|
|
3083
|
-
}
|
|
3084
|
-
catch (e) {
|
|
3085
|
-
return { success: false, error: `导入失败: ${String(e.message || e)}` };
|
|
3086
|
-
}
|
|
2388
|
+
// 提取 <final gen> 后的内容作为最终回答
|
|
2389
|
+
const marker = '<final gen>';
|
|
2390
|
+
const markerIndex = content.indexOf(marker);
|
|
2391
|
+
if (markerIndex !== -1) {
|
|
2392
|
+
const after = content.substring(markerIndex + marker.length).trim();
|
|
2393
|
+
// v3 修复: 如果 <final gen> 之后是空, fallback 用 marker 之前的内容 (去掉 marker)
|
|
2394
|
+
// 否则 LLM 写了 <final gen> 在末尾时, 用户看到空回复 + error
|
|
2395
|
+
if (after) {
|
|
2396
|
+
content = after;
|
|
3087
2397
|
}
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
name: 'wallet_get_balance',
|
|
3091
|
-
description: '查 EVM 钱包 ETH 余额. 默认 RPC = https://eth.llamarpc.com, 可指定其他 RPC URL',
|
|
3092
|
-
parameters: { address: '0x 开头的 EVM 地址 (必填)', rpcUrl: '可选 RPC URL (默认 eth.llamarpc.com)' },
|
|
3093
|
-
execute: async (args) => {
|
|
3094
|
-
try {
|
|
3095
|
-
const { getBalance } = await import('../constraint-runtime/dist/tools/WalletTools/getBalance.js').catch(() => import('../constraint-runtime/src/tools/WalletTools/getBalance.js'));
|
|
3096
|
-
const r = await getBalance({ address: String(args.address), rpcUrl: args.rpcUrl });
|
|
3097
|
-
return { success: true, output: `💰 ${r.address}\n ${r.balanceEth} ${r.symbol} (${r.balance} wei)` };
|
|
3098
|
-
}
|
|
3099
|
-
catch (e) {
|
|
3100
|
-
return { success: false, error: `查余额失败: ${String(e.message || e)}` };
|
|
3101
|
-
}
|
|
3102
|
-
}
|
|
3103
|
-
});
|
|
3104
|
-
this.tools.set('wallet_sign_message', {
|
|
3105
|
-
name: 'wallet_sign_message',
|
|
3106
|
-
description: '用私钥对消息做 EIP-191 personal_sign 签名',
|
|
3107
|
-
parameters: { message: '要签名的消息 (必填)', privateKey: '0x 开头的私钥 (必填)' },
|
|
3108
|
-
execute: async (args) => {
|
|
3109
|
-
try {
|
|
3110
|
-
const { signMessage } = await import('../constraint-runtime/dist/tools/WalletTools/signMessage.js').catch(() => import('../constraint-runtime/src/tools/WalletTools/signMessage.js'));
|
|
3111
|
-
const r = await signMessage({ message: String(args.message), privateKey: String(args.privateKey) });
|
|
3112
|
-
return { success: true, output: `✅ 签名完成:\n address: ${r.address}\n message: ${r.message}\n signature: ${r.signature}` };
|
|
3113
|
-
}
|
|
3114
|
-
catch (e) {
|
|
3115
|
-
return { success: false, error: `签名失败: ${String(e.message || e)}` };
|
|
3116
|
-
}
|
|
3117
|
-
}
|
|
3118
|
-
});
|
|
3119
|
-
this.tools.set('wallet_send_tx', {
|
|
3120
|
-
name: 'wallet_send_tx',
|
|
3121
|
-
description: '用 EVM 钱包发送交易 (转账 ETH 或调用合约). 走 EIP-1559, 默认 RPC = eth.llamarpc.com',
|
|
3122
|
-
parameters: { privateKey: '私钥 (必填)', to: '接收地址 (必填)', value: '发送 wei 数量 (必填)', data: '可选 0x 开头的 calldata', rpcUrl: '可选 RPC URL' },
|
|
3123
|
-
execute: async (args) => {
|
|
3124
|
-
try {
|
|
3125
|
-
const { sendTransaction } = await import('../constraint-runtime/dist/tools/WalletTools/sendTransaction.js').catch(() => import('../constraint-runtime/src/tools/WalletTools/sendTransaction.js'));
|
|
3126
|
-
const r = await sendTransaction({
|
|
3127
|
-
privateKey: String(args.privateKey),
|
|
3128
|
-
to: String(args.to),
|
|
3129
|
-
value: String(args.value),
|
|
3130
|
-
data: args.data ? String(args.data) : undefined,
|
|
3131
|
-
rpcUrl: args.rpcUrl,
|
|
3132
|
-
});
|
|
3133
|
-
return { success: true, output: `✅ 交易已发送:\n hash: ${r.hash}\n from: ${r.from} → to: ${r.to}\n value: ${r.value} wei\n status: ${r.status}\n blockNumber: ${r.blockNumber || '(pending)'}\n gasUsed: ${r.gasUsed || '?'}` };
|
|
3134
|
-
}
|
|
3135
|
-
catch (e) {
|
|
3136
|
-
return { success: false, error: `交易失败: ${String(e.message || e)}` };
|
|
3137
|
-
}
|
|
3138
|
-
}
|
|
3139
|
-
});
|
|
3140
|
-
this.tools.set('wallet_transfer_token', {
|
|
3141
|
-
name: 'wallet_transfer_token',
|
|
3142
|
-
description: '用 EVM 钱包转 ERC20 token (需要 token 合约地址)',
|
|
3143
|
-
parameters: { privateKey: '私钥 (必填)', tokenAddress: 'ERC20 合约地址 (必填)', to: '接收地址 (必填)', amount: 'token 数量 (人类可读, 自动按 decimals 转换)', decimals: '可选 token decimals (默认查合约)', rpcUrl: '可选 RPC URL' },
|
|
3144
|
-
execute: async (args) => {
|
|
3145
|
-
try {
|
|
3146
|
-
const { transferToken } = await import('../constraint-runtime/dist/tools/WalletTools/transferToken.js').catch(() => import('../constraint-runtime/src/tools/WalletTools/transferToken.js'));
|
|
3147
|
-
const r = await transferToken({
|
|
3148
|
-
privateKey: String(args.privateKey),
|
|
3149
|
-
tokenAddress: String(args.tokenAddress),
|
|
3150
|
-
to: String(args.to),
|
|
3151
|
-
amount: String(args.amount),
|
|
3152
|
-
decimals: args.decimals ? Number(args.decimals) : undefined,
|
|
3153
|
-
rpcUrl: args.rpcUrl,
|
|
3154
|
-
});
|
|
3155
|
-
return { success: true, output: `✅ Token 转账完成:\n hash: ${r.hash}\n ${r.amount} ${r.tokenAddress.substring(0, 10)}...\n from: ${r.from} → to: ${r.to}\n status: ${r.status}` };
|
|
3156
|
-
}
|
|
3157
|
-
catch (e) {
|
|
3158
|
-
return { success: false, error: `转账失败: ${String(e.message || e)}` };
|
|
3159
|
-
}
|
|
3160
|
-
}
|
|
3161
|
-
});
|
|
3162
|
-
this.tools.set('wallet_autopay', {
|
|
3163
|
-
name: 'wallet_autopay',
|
|
3164
|
-
description: '设置自动付款 (订阅/定期扣款, 用 SafeSDK 自动执行). 实际能力取决于 SafeSDK autopay 实现',
|
|
3165
|
-
parameters: { from: '付款方地址 (必填)', to: '收款方地址 (必填)', amount: '金额 (必填)', interval: '周期 (e.g. daily/weekly/monthly)', token: '可选, 默认 ETH' },
|
|
3166
|
-
execute: async (args) => {
|
|
3167
|
-
try {
|
|
3168
|
-
const mod = await import('../constraint-runtime/dist/tools/WalletTools/autoPay.js').catch(() => import('../constraint-runtime/src/tools/WalletTools/autoPay.js'));
|
|
3169
|
-
const fn = mod.autoPay || mod.setAutoPay || mod.default;
|
|
3170
|
-
if (!fn)
|
|
3171
|
-
return { success: false, error: 'autoPay 接口未找到' };
|
|
3172
|
-
const r = await fn({
|
|
3173
|
-
from: String(args.from), to: String(args.to), amount: String(args.amount),
|
|
3174
|
-
interval: String(args.interval || 'monthly'), token: args.token,
|
|
3175
|
-
});
|
|
3176
|
-
return { success: true, output: `✅ 自动付款已设置: ${JSON.stringify(r)}` };
|
|
3177
|
-
}
|
|
3178
|
-
catch (e) {
|
|
3179
|
-
return { success: false, error: `设置失败: ${String(e.message || e)}` };
|
|
3180
|
-
}
|
|
3181
|
-
}
|
|
3182
|
-
});
|
|
3183
|
-
// === PolymarketSDK (预测市场) ===
|
|
3184
|
-
this.tools.set('polymarket_list_markets', {
|
|
3185
|
-
name: 'polymarket_list_markets',
|
|
3186
|
-
description: '列出 Polymarket 预测市场 (用 Polymarket CLOB API). limit 默认 50',
|
|
3187
|
-
parameters: { limit: '可选 数量 (默认 50)', offset: '可选 偏移', closed: '可选 是否只显示已关闭 (默认 false = 只显示活跃)' },
|
|
3188
|
-
execute: async (args) => {
|
|
3189
|
-
try {
|
|
3190
|
-
const { listMarkets } = await import('../constraint-runtime/dist/tools/PolymarketSDK/listMarkets.js').catch(() => import('../constraint-runtime/src/tools/PolymarketSDK/listMarkets.js'));
|
|
3191
|
-
const markets = await listMarkets({
|
|
3192
|
-
limit: args.limit ? Number(args.limit) : 50,
|
|
3193
|
-
offset: args.offset ? Number(args.offset) : 0,
|
|
3194
|
-
closed: args.closed ? String(args.closed) === 'true' : false,
|
|
3195
|
-
});
|
|
3196
|
-
if (!markets || markets.length === 0) {
|
|
3197
|
-
return { success: true, output: '📊 Polymarket 当前没有活跃市场 (或 API 不可达, 因为 constraint-runtime 是占位实现, 需要装 polymarket-sdk 包)' };
|
|
3198
|
-
}
|
|
3199
|
-
const lines = markets.slice(0, 10).map((m, i) => ` ${i + 1}. [${m.id}] ${m.question}\n vol=${m.volume} active=${m.active}`);
|
|
3200
|
-
return { success: true, output: `📊 Polymarket 找到 ${markets.length} 个市场 (前 10):\n${lines.join('\n')}` };
|
|
3201
|
-
}
|
|
3202
|
-
catch (e) {
|
|
3203
|
-
return { success: false, error: `查询失败: ${String(e.message || e)}` };
|
|
3204
|
-
}
|
|
3205
|
-
}
|
|
3206
|
-
});
|
|
3207
|
-
this.tools.set('polymarket_get_market', {
|
|
3208
|
-
name: 'polymarket_get_market',
|
|
3209
|
-
description: '获取单个 Polymarket 市场的详情 (id/conditionId 都可)',
|
|
3210
|
-
parameters: { marketId: '市场 ID (必填)' },
|
|
3211
|
-
execute: async (args) => {
|
|
3212
|
-
try {
|
|
3213
|
-
const mod = await import('../constraint-runtime/dist/tools/PolymarketSDK/getMarket.js').catch(() => import('../constraint-runtime/src/tools/PolymarketSDK/getMarket.js'));
|
|
3214
|
-
const fn = mod.getMarket || mod.default;
|
|
3215
|
-
const m = await fn(String(args.marketId));
|
|
3216
|
-
if (!m)
|
|
3217
|
-
return { success: false, error: '市场不存在' };
|
|
3218
|
-
return { success: true, output: `📊 市场详情:\n ${JSON.stringify(m, null, 2).substring(0, 1500)}` };
|
|
3219
|
-
}
|
|
3220
|
-
catch (e) {
|
|
3221
|
-
return { success: false, error: `查询失败: ${String(e.message || e)}` };
|
|
3222
|
-
}
|
|
3223
|
-
}
|
|
3224
|
-
});
|
|
3225
|
-
this.tools.set('polymarket_get_orders', {
|
|
3226
|
-
name: 'polymarket_get_orders',
|
|
3227
|
-
description: '查询 Polymarket 订单 (需要 CLOB client + auth, 当前 constraint-runtime 是占位实现)',
|
|
3228
|
-
parameters: { marketId: '可选 按市场 ID 过滤' },
|
|
3229
|
-
execute: async (args) => {
|
|
3230
|
-
try {
|
|
3231
|
-
const { getOrders } = await import('../constraint-runtime/dist/tools/PolymarketSDK/getOrders.js').catch(() => import('../constraint-runtime/src/tools/PolymarketSDK/getOrders.js'));
|
|
3232
|
-
const orders = await getOrders(args.marketId ? { marketId: String(args.marketId) } : {});
|
|
3233
|
-
return { success: true, output: `📋 订单列表: ${JSON.stringify(orders, null, 2).substring(0, 1500)}` };
|
|
3234
|
-
}
|
|
3235
|
-
catch (e) {
|
|
3236
|
-
return { success: false, error: `查询失败: ${String(e.message || e)}` };
|
|
3237
|
-
}
|
|
3238
|
-
}
|
|
3239
|
-
});
|
|
3240
|
-
this.tools.set('polymarket_create_order', {
|
|
3241
|
-
name: 'polymarket_create_order',
|
|
3242
|
-
description: '在 Polymarket 下单 (BUY/SELL, price 0-1, size USDC). 当前 constraint-runtime 是占位实现, 实际下单需要 CLOB client + 私钥签名',
|
|
3243
|
-
parameters: { marketId: '市场 ID (必填)', side: 'BUY 或 SELL (必填)', price: '价格 0-1 (必填)', size: '数量 USDC (必填)' },
|
|
3244
|
-
execute: async (args) => {
|
|
3245
|
-
try {
|
|
3246
|
-
const { createOrder } = await import('../constraint-runtime/dist/tools/PolymarketSDK/createOrder.js').catch(() => import('../constraint-runtime/src/tools/PolymarketSDK/createOrder.js'));
|
|
3247
|
-
const r = await createOrder({
|
|
3248
|
-
marketId: String(args.marketId),
|
|
3249
|
-
side: String(args.side).toUpperCase() === 'SELL' ? 'SELL' : 'BUY',
|
|
3250
|
-
price: Number(args.price),
|
|
3251
|
-
size: Number(args.size),
|
|
3252
|
-
});
|
|
3253
|
-
if (r.success)
|
|
3254
|
-
return { success: true, output: `✅ 订单: ${r.message}` };
|
|
3255
|
-
return { success: false, error: r.message, output: r.message };
|
|
3256
|
-
}
|
|
3257
|
-
catch (e) {
|
|
3258
|
-
return { success: false, error: `下单失败: ${String(e.message || e)}` };
|
|
3259
|
-
}
|
|
3260
|
-
}
|
|
3261
|
-
});
|
|
3262
|
-
this.tools.set('polymarket_cancel_order', {
|
|
3263
|
-
name: 'polymarket_cancel_order',
|
|
3264
|
-
description: '取消 Polymarket 订单 (order ID)',
|
|
3265
|
-
parameters: { orderId: '订单 ID (必填)' },
|
|
3266
|
-
execute: async (args) => {
|
|
3267
|
-
try {
|
|
3268
|
-
const { cancelOrder } = await import('../constraint-runtime/dist/tools/PolymarketSDK/cancelOrder.js').catch(() => import('../constraint-runtime/src/tools/PolymarketSDK/cancelOrder.js'));
|
|
3269
|
-
const r = await cancelOrder({ orderId: String(args.orderId) });
|
|
3270
|
-
if (r.success)
|
|
3271
|
-
return { success: true, output: `✅ 取消订单: ${r.message}` };
|
|
3272
|
-
return { success: false, error: r.message, output: r.message };
|
|
3273
|
-
}
|
|
3274
|
-
catch (e) {
|
|
3275
|
-
return { success: false, error: `取消失败: ${String(e.message || e)}` };
|
|
3276
|
-
}
|
|
3277
|
-
}
|
|
3278
|
-
});
|
|
3279
|
-
// === SafeSDK (多签钱包) ===
|
|
3280
|
-
this.tools.set('safe_deploy', {
|
|
3281
|
-
name: 'safe_deploy',
|
|
3282
|
-
description: '部署 Safe 多签钱包 (需要 owners/threshold 参数)',
|
|
3283
|
-
parameters: { owners: 'JSON 数组 owner 地址 (必填)', threshold: '需要几个签名 (必填, e.g. 2)' },
|
|
3284
|
-
execute: async (args) => {
|
|
3285
|
-
try {
|
|
3286
|
-
const { deploySafe } = await import('../constraint-runtime/dist/tools/SafeSDK/deploySafe.js').catch(() => import('../constraint-runtime/src/tools/SafeSDK/deploySafe.js'));
|
|
3287
|
-
const owners = Array.isArray(args.owners) ? args.owners : JSON.parse(String(args.owners));
|
|
3288
|
-
const r = await deploySafe({ owners, threshold: Number(args.threshold) });
|
|
3289
|
-
return { success: true, output: `✅ Safe 部署: ${JSON.stringify(r)}` };
|
|
3290
|
-
}
|
|
3291
|
-
catch (e) {
|
|
3292
|
-
return { success: false, error: `部署失败: ${String(e.message || e)}` };
|
|
3293
|
-
}
|
|
2398
|
+
else {
|
|
2399
|
+
content = content.substring(0, markerIndex).trim();
|
|
3294
2400
|
}
|
|
3295
|
-
}
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
* bolloon 注册的是 read_document / edit_file / write_file / shell_exec / list_files
|
|
3304
|
-
* 返回 this.tools 里的标准名, 或 null 表示未识别
|
|
3305
|
-
*/
|
|
3306
|
-
/**
|
|
3307
|
-
* 把 LLM 给的工具名 (可能大小写不一, 或者 Claude Code 风格的别名) 解析为
|
|
3308
|
-
* bolloon 注册的标准工具名.
|
|
3309
|
-
*
|
|
3310
|
-
* 2026-06-30: 委托给 ToolRegistry 模块 — alias 表在 tool-registry.ts 统一维护,
|
|
3311
|
-
* 这里只做 thin wrapper 保留 backward compat (private API 但其它地方可能用).
|
|
3312
|
-
*/
|
|
3313
|
-
resolveToolName(name) {
|
|
3314
|
-
return this._toolRegistry.resolve(name);
|
|
2401
|
+
}
|
|
2402
|
+
// 移除任何 tool call 标记
|
|
2403
|
+
let cleaned = content
|
|
2404
|
+
.replace(/调用工具[::]\s*\w+\s*\([^)]*\)/g, '')
|
|
2405
|
+
.replace(/使用工具[::]\s*\w+\s*\([^)]*\)/g, '')
|
|
2406
|
+
.replace(/tool[_\w]*[::]\s*\w+\s*\([^)]*\)/gi, '')
|
|
2407
|
+
.trim();
|
|
2408
|
+
return cleaned;
|
|
3315
2409
|
}
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
}
|
|
2410
|
+
parseToolCall(content) {
|
|
2411
|
+
const patterns = [
|
|
2412
|
+
/调用工具[::]\s*(\w+)\s*\(([^)]*)\)/,
|
|
2413
|
+
/使用工具[::]\s*(\w+)\s*\(([^)]*)\)/,
|
|
2414
|
+
/tool[_\w]*[::]\s*(\w+)\s*\(([^)]*)\)/i,
|
|
2415
|
+
/(\w+)\s*\(\s*([^)]*)\s*\)/,
|
|
2416
|
+
// 兼容 LLM 输出的对象字面量格式: {tool => "get_identity", args => {...}}
|
|
2417
|
+
/\{\s*tool\s*=>\s*["'](\w+)["']\s*(?:,\s*args\s*=>\s*(\{[\s\S]*?\}))?\s*\}/,
|
|
2418
|
+
// 兼容: tool => "get_identity" (无 args 包裹)
|
|
2419
|
+
/\btool\s*=>\s*["'](\w+)["']/,
|
|
2420
|
+
// 兼容: [TOOL_CALL] 块内 JSON 形式 {"name": "x", "args": {...}}
|
|
2421
|
+
/\[TOOL_CALL\][\s\S]*?\{\s*"name"\s*:\s*"(\w+)"\s*,\s*"args"\s*:\s*(\{[\s\S]*?\})/i,
|
|
2422
|
+
// 2026-06-15 修: 兼容 LLM 输出的 XML 格式 <tool_name>...<arg>value</arg>...</tool_name>
|
|
2423
|
+
// 实际 LLM 习惯: <shell_exec>\n<command>ls</command>\n<args>["-la", "..."]</args>\n</shell_exec>
|
|
2424
|
+
/<(\w+)>([\s\S]*?)<\/\1>/,
|
|
2425
|
+
];
|
|
2426
|
+
for (const pattern of patterns) {
|
|
2427
|
+
const match = content.match(pattern);
|
|
2428
|
+
if (match) {
|
|
2429
|
+
const name = match[1];
|
|
2430
|
+
let args = {};
|
|
2431
|
+
const rawArgs = match[2] || '';
|
|
2432
|
+
if (rawArgs && rawArgs.trim().startsWith('{')) {
|
|
2433
|
+
// JSON 形式, 尝试解析
|
|
2434
|
+
try {
|
|
2435
|
+
const parsed = JSON.parse(rawArgs);
|
|
2436
|
+
if (parsed && typeof parsed === 'object') {
|
|
2437
|
+
args = Object.fromEntries(Object.entries(parsed).map(([k, v]) => [k, String(v)]));
|
|
3345
2438
|
}
|
|
3346
2439
|
}
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
2440
|
+
catch {
|
|
2441
|
+
// 解析失败就当字符串处理
|
|
2442
|
+
const argPairs = rawArgs.split(',').map(s => s.trim()).filter(Boolean);
|
|
2443
|
+
for (const pair of argPairs) {
|
|
2444
|
+
const [key, ...valueParts] = pair.split(':').map(s => s.trim().replace(/['"]/g, ''));
|
|
2445
|
+
if (key)
|
|
2446
|
+
args[key] = valueParts.join(':') || '';
|
|
3352
2447
|
}
|
|
3353
2448
|
}
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
2449
|
+
}
|
|
2450
|
+
else if (rawArgs && /<\w+>[\s\S]*<\/\w+>/.test(rawArgs)) {
|
|
2451
|
+
// 2026-06-15 修: XML 格式, 解析内嵌子标签 <argname>value</argname>
|
|
2452
|
+
// 例: <command>ls</command>\n<args>["-la","~/.bolloon/skills"]</args>
|
|
2453
|
+
const xmlArgPattern = /<(\w+)>([\s\S]*?)<\/\1>/g;
|
|
2454
|
+
let xmlMatch;
|
|
2455
|
+
while ((xmlMatch = xmlArgPattern.exec(rawArgs)) !== null) {
|
|
2456
|
+
const argName = xmlMatch[1];
|
|
2457
|
+
const argValue = xmlMatch[2].trim();
|
|
2458
|
+
if (argName && argValue) {
|
|
2459
|
+
args[argName] = argValue;
|
|
2460
|
+
}
|
|
3366
2461
|
}
|
|
3367
2462
|
}
|
|
3368
|
-
|
|
3369
|
-
|
|
2463
|
+
else if (rawArgs) {
|
|
2464
|
+
// 形参串, 形如 key: value, key2: value2
|
|
2465
|
+
const argPairs = rawArgs.split(',').map(s => s.trim()).filter(Boolean);
|
|
2466
|
+
for (const pair of argPairs) {
|
|
2467
|
+
const [key, ...valueParts] = pair.split(':').map(s => s.trim().replace(/['"]/g, ''));
|
|
2468
|
+
if (key)
|
|
2469
|
+
args[key] = valueParts.join(':') || '';
|
|
2470
|
+
}
|
|
3370
2471
|
}
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
catch (err) {
|
|
3374
|
-
console.warn('[PiAgent] _setupInboxListener failed (non-fatal):', err);
|
|
3375
|
-
}
|
|
3376
|
-
// 监听本地 inbox bus 投递 (send_to_local_agent)
|
|
3377
|
-
try {
|
|
3378
|
-
const { LocalInboxBus } = require('../network/local-inbox-bus.js');
|
|
3379
|
-
const myRole = process.env.BOLLOON_ROLE || 'default';
|
|
3380
|
-
LocalInboxBus.getInstance().subscribe(myRole, (msg) => {
|
|
3381
|
-
this._inboxMessages.push({
|
|
3382
|
-
id: `local-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
3383
|
-
from: msg.from || 'unknown',
|
|
3384
|
-
fromDid: msg.fromDid,
|
|
3385
|
-
type: msg.type || 'agent-local-message',
|
|
3386
|
-
payload: msg.payload || '',
|
|
3387
|
-
timestamp: msg.timestamp || Date.now(),
|
|
3388
|
-
source: 'local',
|
|
3389
|
-
});
|
|
3390
|
-
if (this._inboxMessages.length > 1000) {
|
|
3391
|
-
this._inboxMessages = this._inboxMessages.slice(-1000);
|
|
2472
|
+
if (this.tools.has(name) || this.tools.has(name.replace(/_/g, '_'))) {
|
|
2473
|
+
return { name, args };
|
|
3392
2474
|
}
|
|
3393
|
-
}
|
|
3394
|
-
}
|
|
3395
|
-
catch (err) {
|
|
3396
|
-
// LocalInboxBus 可能还没创建, 不阻塞
|
|
2475
|
+
}
|
|
3397
2476
|
}
|
|
2477
|
+
return null;
|
|
3398
2478
|
}
|
|
3399
2479
|
estimateResponseQuality(response) {
|
|
3400
2480
|
let score = 0.5;
|
|
@@ -3485,37 +2565,37 @@ Workspace root folder: ${this.cwd}
|
|
|
3485
2565
|
getDefaultResponse(input) {
|
|
3486
2566
|
const operationsRef = PiAgentSession.getOperationsReference();
|
|
3487
2567
|
if (operationsRef) {
|
|
3488
|
-
return `收到了: "${input}"
|
|
3489
|
-
|
|
3490
|
-
我是一个判断力处理智能体,支持自然语言交互。
|
|
3491
|
-
|
|
3492
|
-
可用操作(直接说出即可):
|
|
3493
|
-
${this.extractOperationsFromRef(operationsRef)}
|
|
3494
|
-
|
|
3495
|
-
示例请求:
|
|
3496
|
-
- "读取 src/index.ts 文件"
|
|
3497
|
-
- "总结一下 README.md"
|
|
3498
|
-
- "查看当前连接了哪些节点"
|
|
2568
|
+
return `收到了: "${input}"
|
|
2569
|
+
|
|
2570
|
+
我是一个判断力处理智能体,支持自然语言交互。
|
|
2571
|
+
|
|
2572
|
+
可用操作(直接说出即可):
|
|
2573
|
+
${this.extractOperationsFromRef(operationsRef)}
|
|
2574
|
+
|
|
2575
|
+
示例请求:
|
|
2576
|
+
- "读取 src/index.ts 文件"
|
|
2577
|
+
- "总结一下 README.md"
|
|
2578
|
+
- "查看当前连接了哪些节点"
|
|
3499
2579
|
- "向 QmABC... 发送测试消息"`;
|
|
3500
2580
|
}
|
|
3501
|
-
return `收到了: "${input}"
|
|
3502
|
-
|
|
3503
|
-
我是一个判断力处理智能体,支持自然语言交互。
|
|
3504
|
-
|
|
3505
|
-
可用操作(直接说出即可):
|
|
3506
|
-
- "读取 README.md" - 读取并分析文档
|
|
3507
|
-
- "总结文档" - 总结文档内容
|
|
3508
|
-
- "改进文档,按照X要求" - 改进文档
|
|
3509
|
-
- "查看节点" - 查看已连接的对等节点
|
|
3510
|
-
- "向X发送消息Y" - 向对等节点发送消息
|
|
3511
|
-
- "广播消息X" - 广播消息到所有节点
|
|
3512
|
-
- "查看身份" - 查看当前智能体身份
|
|
3513
|
-
- "查看日志" - 查看最近操作日志
|
|
3514
|
-
|
|
3515
|
-
示例请求:
|
|
3516
|
-
- "读取 src/index.ts 文件"
|
|
3517
|
-
- "总结一下 README.md"
|
|
3518
|
-
- "查看当前连接了哪些节点"
|
|
2581
|
+
return `收到了: "${input}"
|
|
2582
|
+
|
|
2583
|
+
我是一个判断力处理智能体,支持自然语言交互。
|
|
2584
|
+
|
|
2585
|
+
可用操作(直接说出即可):
|
|
2586
|
+
- "读取 README.md" - 读取并分析文档
|
|
2587
|
+
- "总结文档" - 总结文档内容
|
|
2588
|
+
- "改进文档,按照X要求" - 改进文档
|
|
2589
|
+
- "查看节点" - 查看已连接的对等节点
|
|
2590
|
+
- "向X发送消息Y" - 向对等节点发送消息
|
|
2591
|
+
- "广播消息X" - 广播消息到所有节点
|
|
2592
|
+
- "查看身份" - 查看当前智能体身份
|
|
2593
|
+
- "查看日志" - 查看最近操作日志
|
|
2594
|
+
|
|
2595
|
+
示例请求:
|
|
2596
|
+
- "读取 src/index.ts 文件"
|
|
2597
|
+
- "总结一下 README.md"
|
|
2598
|
+
- "查看当前连接了哪些节点"
|
|
3519
2599
|
- "向 QmABC... 发送测试消息"`;
|
|
3520
2600
|
}
|
|
3521
2601
|
extractOperationsFromRef(ref) {
|
|
@@ -3540,13 +2620,13 @@ ${this.extractOperationsFromRef(operationsRef)}
|
|
|
3540
2620
|
}
|
|
3541
2621
|
}
|
|
3542
2622
|
return operationLines.length > 0 ? operationLines.join('\n') :
|
|
3543
|
-
` - "读取 README.md" - 读取并分析文档
|
|
3544
|
-
- "总结文档" - 总结文档内容
|
|
3545
|
-
- "改进文档,按照X要求" - 改进文档
|
|
3546
|
-
- "查看节点" - 查看已连接的对等节点
|
|
3547
|
-
- "向X发送消息Y" - 向对等节点发送消息
|
|
3548
|
-
- "广播消息X" - 广播消息到所有节点
|
|
3549
|
-
- "查看身份" - 查看当前智能体身份
|
|
2623
|
+
` - "读取 README.md" - 读取并分析文档
|
|
2624
|
+
- "总结文档" - 总结文档内容
|
|
2625
|
+
- "改进文档,按照X要求" - 改进文档
|
|
2626
|
+
- "查看节点" - 查看已连接的对等节点
|
|
2627
|
+
- "向X发送消息Y" - 向对等节点发送消息
|
|
2628
|
+
- "广播消息X" - 广播消息到所有节点
|
|
2629
|
+
- "查看身份" - 查看当前智能体身份
|
|
3550
2630
|
- "查看日志" - 查看最近操作日志`;
|
|
3551
2631
|
}
|
|
3552
2632
|
async suggestRename(messages) {
|
|
@@ -3886,14 +2966,11 @@ export async function createAgentSession(config, forceNew) {
|
|
|
3886
2966
|
const key = config.peerId;
|
|
3887
2967
|
if (!forceNew && independentSessions.has(key)) {
|
|
3888
2968
|
console.log(`[createAgentSession] 找到现有独立 session, key=${key}`);
|
|
3889
|
-
|
|
3890
|
-
await existing.whenReady();
|
|
3891
|
-
return existing;
|
|
2969
|
+
return independentSessions.get(key);
|
|
3892
2970
|
}
|
|
3893
2971
|
const session = new PiAgentSession(config);
|
|
3894
2972
|
independentSessions.set(key, session);
|
|
3895
2973
|
console.log(`[createAgentSession] 创建独立 session, key=${key}, DID=${incomingDid}`);
|
|
3896
|
-
await session.whenReady();
|
|
3897
2974
|
return session;
|
|
3898
2975
|
}
|
|
3899
2976
|
// 如果指定了 forceNew 但没有 peerId,生成带时间戳的 key
|
|
@@ -3902,7 +2979,6 @@ export async function createAgentSession(config, forceNew) {
|
|
|
3902
2979
|
const session = new PiAgentSession(config);
|
|
3903
2980
|
independentSessions.set(key, session);
|
|
3904
2981
|
console.log(`[createAgentSession] 创建强制新 session, key=${key}`);
|
|
3905
|
-
await session.whenReady();
|
|
3906
2982
|
return session;
|
|
3907
2983
|
}
|
|
3908
2984
|
// 如果有新的 DID,强制重建 session
|
|
@@ -3922,13 +2998,11 @@ export async function createAgentSession(config, forceNew) {
|
|
|
3922
2998
|
createdAt: Date.now()
|
|
3923
2999
|
});
|
|
3924
3000
|
}
|
|
3925
|
-
await sessionInstance.whenReady();
|
|
3926
3001
|
return sessionInstance;
|
|
3927
3002
|
}
|
|
3928
3003
|
sessionInstance = new PiAgentSession(config);
|
|
3929
3004
|
lastIdentityDid = config.identityDoc?.did || null;
|
|
3930
3005
|
console.log(`[createAgentSession] 新建 session, DID=${lastIdentityDid}`);
|
|
3931
|
-
await sessionInstance.whenReady();
|
|
3932
3006
|
return sessionInstance;
|
|
3933
3007
|
}
|
|
3934
3008
|
export function getAgentSession() {
|