@bolloon/bolloon-agent 0.4.28 → 0.4.30
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/bin/bolloon.cjs +18 -148
- package/bin/bolloon.cmd +6 -3
- package/dist/agents/contacts/chain.js +579 -0
- package/dist/agents/contacts/cli.js +158 -0
- package/dist/agents/contacts/consent.js +124 -0
- package/dist/agents/contacts/grant-payload.js +45 -0
- package/dist/agents/contacts/grants.js +451 -0
- package/dist/agents/contacts/policy.js +264 -0
- package/dist/agents/contacts/preview-types.js +6 -0
- package/dist/agents/contacts/providers.js +341 -0
- package/dist/agents/contacts/store.js +326 -0
- package/dist/agents/contacts/tools.js +154 -0
- package/dist/agents/contacts/types.js +148 -0
- package/dist/agents/pi-sdk-tools.js +3 -1
- package/dist/agents/pi-sdk.js +14 -1
- package/dist/cli/update-commands.js +349 -0
- package/dist/cli-entry.js +41 -55
- package/dist/electron-build/agents/agent-gateway.js +169 -0
- package/dist/electron-build/agents/agent-gateway.js.map +1 -0
- package/dist/electron-build/agents/agent-identity-store.js +212 -0
- package/dist/electron-build/agents/agent-identity-store.js.map +1 -0
- package/dist/electron-build/agents/agent-identity.js +196 -0
- package/dist/electron-build/agents/agent-identity.js.map +1 -0
- package/dist/electron-build/agents/agent-manifest-protocol.js +151 -0
- package/dist/electron-build/agents/agent-manifest-protocol.js.map +1 -0
- package/dist/electron-build/agents/agent-registry.js +217 -0
- package/dist/electron-build/agents/agent-registry.js.map +1 -0
- package/dist/electron-build/agents/agent-reputation.js +60 -0
- package/dist/electron-build/agents/agent-reputation.js.map +1 -0
- package/dist/electron-build/agents/agent-service-client.js +186 -0
- package/dist/electron-build/agents/agent-service-client.js.map +1 -0
- package/dist/electron-build/agents/browser-cdp.js +765 -0
- package/dist/electron-build/agents/browser-cdp.js.map +1 -0
- package/dist/electron-build/agents/computer-use.js +341 -0
- package/dist/electron-build/agents/computer-use.js.map +1 -0
- package/dist/electron-build/agents/constraint-layer.js +210 -0
- package/dist/electron-build/agents/constraint-layer.js.map +1 -0
- package/dist/electron-build/agents/contacts/chain.js +619 -0
- package/dist/electron-build/agents/contacts/chain.js.map +1 -0
- package/dist/electron-build/agents/contacts/consent.js +163 -0
- package/dist/electron-build/agents/contacts/consent.js.map +1 -0
- package/dist/electron-build/agents/contacts/grant-payload.js +52 -0
- package/dist/electron-build/agents/contacts/grant-payload.js.map +1 -0
- package/dist/electron-build/agents/contacts/grants.js +500 -0
- package/dist/electron-build/agents/contacts/grants.js.map +1 -0
- package/dist/electron-build/agents/contacts/policy.js +271 -0
- package/dist/electron-build/agents/contacts/policy.js.map +1 -0
- package/dist/electron-build/agents/contacts/preview-types.js +8 -0
- package/dist/electron-build/agents/contacts/preview-types.js.map +1 -0
- package/dist/electron-build/agents/contacts/providers.js +384 -0
- package/dist/electron-build/agents/contacts/providers.js.map +1 -0
- package/dist/electron-build/agents/contacts/store.js +370 -0
- package/dist/electron-build/agents/contacts/store.js.map +1 -0
- package/dist/electron-build/agents/contacts/tools.js +159 -0
- package/dist/electron-build/agents/contacts/tools.js.map +1 -0
- package/dist/electron-build/agents/contacts/types.js +166 -0
- package/dist/electron-build/agents/contacts/types.js.map +1 -0
- package/dist/electron-build/agents/decision-store.js +292 -0
- package/dist/electron-build/agents/decision-store.js.map +1 -0
- package/dist/electron-build/agents/deny-pipeline.js +125 -0
- package/dist/electron-build/agents/deny-pipeline.js.map +1 -0
- package/dist/electron-build/agents/economic-policy.js +172 -0
- package/dist/electron-build/agents/economic-policy.js.map +1 -0
- package/dist/electron-build/agents/error-classifier.js +125 -0
- package/dist/electron-build/agents/error-classifier.js.map +1 -0
- package/dist/electron-build/agents/execution-supervisor.js +539 -0
- package/dist/electron-build/agents/execution-supervisor.js.map +1 -0
- package/dist/electron-build/agents/external-events.js +171 -0
- package/dist/electron-build/agents/external-events.js.map +1 -0
- package/dist/electron-build/agents/gateway-join.js +315 -0
- package/dist/electron-build/agents/gateway-join.js.map +1 -0
- package/dist/electron-build/agents/gateway-network.js +505 -0
- package/dist/electron-build/agents/gateway-network.js.map +1 -0
- package/dist/electron-build/agents/goal-criteria.js +133 -0
- package/dist/electron-build/agents/goal-criteria.js.map +1 -0
- package/dist/electron-build/agents/goal-resume.js +364 -0
- package/dist/electron-build/agents/goal-resume.js.map +1 -0
- package/dist/electron-build/agents/goal-store.js +587 -0
- package/dist/electron-build/agents/goal-store.js.map +1 -0
- package/dist/electron-build/agents/intent-classifier.js +104 -0
- package/dist/electron-build/agents/intent-classifier.js.map +1 -0
- package/dist/electron-build/agents/iroh-secret.js +70 -0
- package/dist/electron-build/agents/iroh-secret.js.map +1 -0
- package/dist/electron-build/agents/loop-review.js +101 -0
- package/dist/electron-build/agents/loop-review.js.map +1 -0
- package/dist/electron-build/agents/memory-recall.js +154 -0
- package/dist/electron-build/agents/memory-recall.js.map +1 -0
- package/dist/electron-build/agents/p2p-document-tools.js +328 -0
- package/dist/electron-build/agents/p2p-document-tools.js.map +1 -0
- package/dist/electron-build/agents/parse-tool-call.js +564 -0
- package/dist/electron-build/agents/parse-tool-call.js.map +1 -0
- package/dist/electron-build/agents/patch-tool.js +205 -0
- package/dist/electron-build/agents/patch-tool.js.map +1 -0
- package/dist/electron-build/agents/payment-approval.js +183 -0
- package/dist/electron-build/agents/payment-approval.js.map +1 -0
- package/dist/electron-build/agents/payment-gate.js +139 -0
- package/dist/electron-build/agents/payment-gate.js.map +1 -0
- package/dist/electron-build/agents/permission-mode.js +124 -0
- package/dist/electron-build/agents/permission-mode.js.map +1 -0
- package/dist/electron-build/agents/pi-harness.js +268 -0
- package/dist/electron-build/agents/pi-harness.js.map +1 -0
- package/dist/electron-build/agents/pi-sdk-session-factory.js +117 -0
- package/dist/electron-build/agents/pi-sdk-session-factory.js.map +1 -0
- package/dist/electron-build/agents/pi-sdk-session-manager.js +335 -0
- package/dist/electron-build/agents/pi-sdk-session-manager.js.map +1 -0
- package/dist/electron-build/agents/pi-sdk-tools.js +4042 -0
- package/dist/electron-build/agents/pi-sdk-tools.js.map +1 -0
- package/dist/electron-build/agents/pi-sdk-types.js +17 -0
- package/dist/electron-build/agents/pi-sdk-types.js.map +1 -0
- package/dist/electron-build/agents/pi-sdk.js +3335 -0
- package/dist/electron-build/agents/pi-sdk.js.map +1 -0
- package/dist/electron-build/agents/plan-store.js +212 -0
- package/dist/electron-build/agents/plan-store.js.map +1 -0
- package/dist/electron-build/agents/pre-tool-validator.js +200 -0
- package/dist/electron-build/agents/pre-tool-validator.js.map +1 -0
- package/dist/electron-build/agents/process-runner.js +148 -0
- package/dist/electron-build/agents/process-runner.js.map +1 -0
- package/dist/electron-build/agents/react-loop.js +131 -0
- package/dist/electron-build/agents/react-loop.js.map +1 -0
- package/dist/electron-build/agents/resource-store.js +180 -0
- package/dist/electron-build/agents/resource-store.js.map +1 -0
- package/dist/electron-build/agents/resource-token.js +118 -0
- package/dist/electron-build/agents/resource-token.js.map +1 -0
- package/dist/electron-build/agents/resource-wallet.js +84 -0
- package/dist/electron-build/agents/resource-wallet.js.map +1 -0
- package/dist/electron-build/agents/run-store.js +854 -0
- package/dist/electron-build/agents/run-store.js.map +1 -0
- package/dist/electron-build/agents/runner-resolver.js +267 -0
- package/dist/electron-build/agents/runner-resolver.js.map +1 -0
- package/dist/electron-build/agents/session-store.js +294 -0
- package/dist/electron-build/agents/session-store.js.map +1 -0
- package/dist/electron-build/agents/shell-guard.js +465 -0
- package/dist/electron-build/agents/shell-guard.js.map +1 -0
- package/dist/electron-build/agents/shell-tool.js +102 -0
- package/dist/electron-build/agents/shell-tool.js.map +1 -0
- package/dist/electron-build/agents/skill-loader.js +216 -0
- package/dist/electron-build/agents/skill-loader.js.map +1 -0
- package/dist/electron-build/agents/skill-readiness.js +174 -0
- package/dist/electron-build/agents/skill-readiness.js.map +1 -0
- package/dist/electron-build/agents/skill-share.js +394 -0
- package/dist/electron-build/agents/skill-share.js.map +1 -0
- package/dist/electron-build/agents/skill-supervisor-link.js +75 -0
- package/dist/electron-build/agents/skill-supervisor-link.js.map +1 -0
- package/dist/electron-build/agents/skill-writer.js +347 -0
- package/dist/electron-build/agents/skill-writer.js.map +1 -0
- package/dist/electron-build/agents/skills-manager.js +763 -0
- package/dist/electron-build/agents/skills-manager.js.map +1 -0
- package/dist/electron-build/agents/supervisor-host.js +294 -0
- package/dist/electron-build/agents/supervisor-host.js.map +1 -0
- package/dist/electron-build/agents/task/local-seller.js +188 -0
- package/dist/electron-build/agents/task/local-seller.js.map +1 -0
- package/dist/electron-build/agents/task/report-card.js +152 -0
- package/dist/electron-build/agents/task/report-card.js.map +1 -0
- package/dist/electron-build/agents/task/resource-advisor.js +222 -0
- package/dist/electron-build/agents/task/resource-advisor.js.map +1 -0
- package/dist/electron-build/agents/task/task-budget.js +111 -0
- package/dist/electron-build/agents/task/task-budget.js.map +1 -0
- package/dist/electron-build/agents/task/task-runner.js +670 -0
- package/dist/electron-build/agents/task/task-runner.js.map +1 -0
- package/dist/electron-build/agents/task-state.js +266 -0
- package/dist/electron-build/agents/task-state.js.map +1 -0
- package/dist/electron-build/agents/tool-registry.js +141 -0
- package/dist/electron-build/agents/tool-registry.js.map +1 -0
- package/dist/electron-build/agents/treasury-bridge.js +165 -0
- package/dist/electron-build/agents/treasury-bridge.js.map +1 -0
- package/dist/electron-build/agents/user-questions.js +223 -0
- package/dist/electron-build/agents/user-questions.js.map +1 -0
- package/dist/electron-build/agents/verify-recipe.js +90 -0
- package/dist/electron-build/agents/verify-recipe.js.map +1 -0
- package/dist/electron-build/agents/workflow-engine.js +222 -0
- package/dist/electron-build/agents/workflow-engine.js.map +1 -0
- package/dist/electron-build/agents/workflow-pivot-loop.js +988 -0
- package/dist/electron-build/agents/workflow-pivot-loop.js.map +1 -0
- package/dist/electron-build/agents/write-staging.js +133 -0
- package/dist/electron-build/agents/write-staging.js.map +1 -0
- package/dist/electron-build/agents/x402/goal-run-bridge.js +115 -0
- package/dist/electron-build/agents/x402/goal-run-bridge.js.map +1 -0
- package/dist/electron-build/agents/x402/milestone-settlement.js +163 -0
- package/dist/electron-build/agents/x402/milestone-settlement.js.map +1 -0
- package/dist/electron-build/agents/x402/paid-info-protocol.js +274 -0
- package/dist/electron-build/agents/x402/paid-info-protocol.js.map +1 -0
- package/dist/electron-build/agents/x402/paid-info-store.js +409 -0
- package/dist/electron-build/agents/x402/paid-info-store.js.map +1 -0
- package/dist/electron-build/agents/x402/paid-info-tools.js +346 -0
- package/dist/electron-build/agents/x402/paid-info-tools.js.map +1 -0
- package/dist/electron-build/agents/x402/payment-recovery.js +330 -0
- package/dist/electron-build/agents/x402/payment-recovery.js.map +1 -0
- package/dist/electron-build/agents/x402/resource-contract.js +533 -0
- package/dist/electron-build/agents/x402/resource-contract.js.map +1 -0
- package/dist/electron-build/agents/x402/settlement-state.js +432 -0
- package/dist/electron-build/agents/x402/settlement-state.js.map +1 -0
- package/dist/electron-build/agents/x402/trade.js +294 -0
- package/dist/electron-build/agents/x402/trade.js.map +1 -0
- package/dist/electron-build/agents/x402/transaction-protocol.js +109 -0
- package/dist/electron-build/agents/x402/transaction-protocol.js.map +1 -0
- package/dist/electron-build/agents/x402/transaction-store.js +404 -0
- package/dist/electron-build/agents/x402/transaction-store.js.map +1 -0
- package/dist/electron-build/agents/x402/x402Pay.js +377 -0
- package/dist/electron-build/agents/x402/x402Pay.js.map +1 -0
- package/dist/electron-build/bollharness-integration/context-router-judgment.js +269 -0
- package/dist/electron-build/bollharness-integration/context-router-judgment.js.map +1 -0
- package/dist/electron-build/bollharness-integration/context-router.js +584 -0
- package/dist/electron-build/bollharness-integration/context-router.js.map +1 -0
- package/dist/electron-build/bollharness-integration/gate-state-machine.js +362 -0
- package/dist/electron-build/bollharness-integration/gate-state-machine.js.map +1 -0
- package/dist/electron-build/bollharness-integration/gate-transition-hooks.js +136 -0
- package/dist/electron-build/bollharness-integration/gate-transition-hooks.js.map +1 -0
- package/dist/electron-build/bollharness-integration/guard-checker.js +400 -0
- package/dist/electron-build/bollharness-integration/guard-checker.js.map +1 -0
- package/dist/electron-build/bollharness-integration/index.js +123 -0
- package/dist/electron-build/bollharness-integration/index.js.map +1 -0
- package/dist/electron-build/bollharness-integration/integration.js +531 -0
- package/dist/electron-build/bollharness-integration/integration.js.map +1 -0
- package/dist/electron-build/bollharness-integration/skill-adapter.js +608 -0
- package/dist/electron-build/bollharness-integration/skill-adapter.js.map +1 -0
- package/dist/electron-build/bootstrap/bootstrap.js +173 -0
- package/dist/electron-build/bootstrap/bootstrap.js.map +1 -0
- package/dist/electron-build/bootstrap/context-collector.js +358 -0
- package/dist/electron-build/bootstrap/context-collector.js.map +1 -0
- package/dist/electron-build/bootstrap/context-hierarchy.js +262 -0
- package/dist/electron-build/bootstrap/context-hierarchy.js.map +1 -0
- package/dist/electron-build/bootstrap/context-manager.js +211 -0
- package/dist/electron-build/bootstrap/context-manager.js.map +1 -0
- package/dist/electron-build/bootstrap/context-os.js +277 -0
- package/dist/electron-build/bootstrap/context-os.js.map +1 -0
- package/dist/electron-build/bootstrap/event-log.js +203 -0
- package/dist/electron-build/bootstrap/event-log.js.map +1 -0
- package/dist/electron-build/bootstrap/exhaust-scrubber.js +277 -0
- package/dist/electron-build/bootstrap/exhaust-scrubber.js.map +1 -0
- package/dist/electron-build/bootstrap/lifecycle-hooks.js +282 -0
- package/dist/electron-build/bootstrap/lifecycle-hooks.js.map +1 -0
- package/dist/electron-build/bootstrap/memory-compressor.js +377 -0
- package/dist/electron-build/bootstrap/memory-compressor.js.map +1 -0
- package/dist/electron-build/bootstrap/persona-loader.js +219 -0
- package/dist/electron-build/bootstrap/persona-loader.js.map +1 -0
- package/dist/electron-build/bootstrap/project-context.js +162 -0
- package/dist/electron-build/bootstrap/project-context.js.map +1 -0
- package/dist/electron-build/bootstrap/project-state.js +205 -0
- package/dist/electron-build/bootstrap/project-state.js.map +1 -0
- package/dist/electron-build/bootstrap/session-window.js +162 -0
- package/dist/electron-build/bootstrap/session-window.js.map +1 -0
- package/dist/electron-build/bootstrap/vector-index.js +291 -0
- package/dist/electron-build/bootstrap/vector-index.js.map +1 -0
- package/dist/electron-build/constraint-runtime/src/tools/PolymarketSDK/cancelOrder.js +22 -0
- package/dist/electron-build/constraint-runtime/src/tools/PolymarketSDK/cancelOrder.js.map +1 -0
- package/dist/electron-build/constraint-runtime/src/tools/PolymarketSDK/clobShared.js +86 -0
- package/dist/electron-build/constraint-runtime/src/tools/PolymarketSDK/clobShared.js.map +1 -0
- package/dist/electron-build/constraint-runtime/src/tools/PolymarketSDK/createOrder.js +37 -0
- package/dist/electron-build/constraint-runtime/src/tools/PolymarketSDK/createOrder.js.map +1 -0
- package/dist/electron-build/constraint-runtime/src/tools/PolymarketSDK/getMarket.js +20 -0
- package/dist/electron-build/constraint-runtime/src/tools/PolymarketSDK/getMarket.js.map +1 -0
- package/dist/electron-build/constraint-runtime/src/tools/PolymarketSDK/getOrders.js +29 -0
- package/dist/electron-build/constraint-runtime/src/tools/PolymarketSDK/getOrders.js.map +1 -0
- package/dist/electron-build/constraint-runtime/src/tools/PolymarketSDK/listMarkets.js +28 -0
- package/dist/electron-build/constraint-runtime/src/tools/PolymarketSDK/listMarkets.js.map +1 -0
- package/dist/electron-build/constraint-runtime/src/tools/SafeSDK/deploySafe.js +10 -0
- package/dist/electron-build/constraint-runtime/src/tools/SafeSDK/deploySafe.js.map +1 -0
- package/dist/electron-build/constraint-runtime/src/tools/WalletTools/autoPay.js +68 -0
- package/dist/electron-build/constraint-runtime/src/tools/WalletTools/autoPay.js.map +1 -0
- package/dist/electron-build/constraint-runtime/src/tools/WalletTools/createWallet.js +14 -0
- package/dist/electron-build/constraint-runtime/src/tools/WalletTools/createWallet.js.map +1 -0
- package/dist/electron-build/constraint-runtime/src/tools/WalletTools/getBalance.js +17 -0
- package/dist/electron-build/constraint-runtime/src/tools/WalletTools/getBalance.js.map +1 -0
- package/dist/electron-build/constraint-runtime/src/tools/WalletTools/importWallet.js +59 -0
- package/dist/electron-build/constraint-runtime/src/tools/WalletTools/importWallet.js.map +1 -0
- package/dist/electron-build/constraint-runtime/src/tools/WalletTools/sendTransaction.js +29 -0
- package/dist/electron-build/constraint-runtime/src/tools/WalletTools/sendTransaction.js.map +1 -0
- package/dist/electron-build/constraint-runtime/src/tools/WalletTools/signMessage.js +14 -0
- package/dist/electron-build/constraint-runtime/src/tools/WalletTools/signMessage.js.map +1 -0
- package/dist/electron-build/constraint-runtime/src/tools/WalletTools/transferToken.js +29 -0
- package/dist/electron-build/constraint-runtime/src/tools/WalletTools/transferToken.js.map +1 -0
- package/dist/electron-build/constraints/index.js +11 -0
- package/dist/electron-build/constraints/index.js.map +1 -0
- package/dist/electron-build/context-compaction/auto-compact.js +182 -0
- package/dist/electron-build/context-compaction/auto-compact.js.map +1 -0
- package/dist/electron-build/context-compaction/budget-gate.js +32 -0
- package/dist/electron-build/context-compaction/budget-gate.js.map +1 -0
- package/dist/electron-build/context-compaction/budget-reduce.js +39 -0
- package/dist/electron-build/context-compaction/budget-reduce.js.map +1 -0
- package/dist/electron-build/context-compaction/context-collapse.js +71 -0
- package/dist/electron-build/context-compaction/context-collapse.js.map +1 -0
- package/dist/electron-build/context-compaction/index.js +38 -0
- package/dist/electron-build/context-compaction/index.js.map +1 -0
- package/dist/electron-build/context-compaction/microcompact.js +55 -0
- package/dist/electron-build/context-compaction/microcompact.js.map +1 -0
- package/dist/electron-build/context-compaction/pipeline.js +128 -0
- package/dist/electron-build/context-compaction/pipeline.js.map +1 -0
- package/dist/electron-build/context-compaction/snip.js +50 -0
- package/dist/electron-build/context-compaction/snip.js.map +1 -0
- package/dist/electron-build/context-compaction/token-estimator.js +40 -0
- package/dist/electron-build/context-compaction/token-estimator.js.map +1 -0
- package/dist/electron-build/context-compaction/types.js +21 -0
- package/dist/electron-build/context-compaction/types.js.map +1 -0
- package/dist/electron-build/cron/tick-lock.js +247 -0
- package/dist/electron-build/cron/tick-lock.js.map +1 -0
- package/dist/electron-build/documents/reader.js +131 -0
- package/dist/electron-build/documents/reader.js.map +1 -0
- package/dist/electron-build/documents/store.js +220 -0
- package/dist/electron-build/documents/store.js.map +1 -0
- package/dist/electron-build/external-engines/delegate-handle.js +76 -0
- package/dist/electron-build/external-engines/delegate-handle.js.map +1 -0
- package/dist/electron-build/external-engines/delegate.js +225 -0
- package/dist/electron-build/external-engines/delegate.js.map +1 -0
- package/dist/electron-build/external-engines/discovery.js +448 -0
- package/dist/electron-build/external-engines/discovery.js.map +1 -0
- package/dist/electron-build/external-engines/types.js +13 -0
- package/dist/electron-build/external-engines/types.js.map +1 -0
- package/dist/electron-build/hooks/hooks-engine.js +367 -0
- package/dist/electron-build/hooks/hooks-engine.js.map +1 -0
- package/dist/electron-build/judgeness/reflect.js +100 -0
- package/dist/electron-build/judgeness/reflect.js.map +1 -0
- package/dist/electron-build/judgeness/store.js +536 -0
- package/dist/electron-build/judgeness/store.js.map +1 -0
- package/dist/electron-build/judgeness/types.js +21 -0
- package/dist/electron-build/judgeness/types.js.map +1 -0
- package/dist/electron-build/llm/config-store.js +551 -0
- package/dist/electron-build/llm/config-store.js.map +1 -0
- package/dist/electron-build/llm/error-lessons.js +76 -0
- package/dist/electron-build/llm/error-lessons.js.map +1 -0
- package/dist/electron-build/llm/pi-ai.js +830 -0
- package/dist/electron-build/llm/pi-ai.js.map +1 -0
- package/dist/electron-build/llm/system-prompt/registry.js +318 -0
- package/dist/electron-build/llm/system-prompt/registry.js.map +1 -0
- package/dist/electron-build/llm/system-prompt/strip-hibsml.js +55 -0
- package/dist/electron-build/llm/system-prompt/strip-hibsml.js.map +1 -0
- package/dist/electron-build/locales/en.js +32 -0
- package/dist/electron-build/locales/en.js.map +1 -0
- package/dist/electron-build/locales/zh.js +30 -0
- package/dist/electron-build/locales/zh.js.map +1 -0
- package/dist/electron-build/lsp/lsp-manager.js +327 -0
- package/dist/electron-build/lsp/lsp-manager.js.map +1 -0
- package/dist/electron-build/lsp/lsp-tools.js +226 -0
- package/dist/electron-build/lsp/lsp-tools.js.map +1 -0
- package/dist/electron-build/migration/external-agent-migrator.js +451 -0
- package/dist/electron-build/migration/external-agent-migrator.js.map +1 -0
- package/dist/electron-build/network/agent-network.js +775 -0
- package/dist/electron-build/network/agent-network.js.map +1 -0
- package/dist/electron-build/network/goal-event-bridge.js +62 -0
- package/dist/electron-build/network/goal-event-bridge.js.map +1 -0
- package/dist/electron-build/network/iroh-integration.js +117 -0
- package/dist/electron-build/network/iroh-integration.js.map +1 -0
- package/dist/electron-build/network/iroh-transport.js +550 -0
- package/dist/electron-build/network/iroh-transport.js.map +1 -0
- package/dist/electron-build/network/p2p.js +1103 -0
- package/dist/electron-build/network/p2p.js.map +1 -0
- package/dist/electron-build/network/storage/adapters/json-adapter.js +416 -0
- package/dist/electron-build/network/storage/adapters/json-adapter.js.map +1 -0
- package/dist/electron-build/network/storage/types.js +18 -0
- package/dist/electron-build/network/storage/types.js.map +1 -0
- package/dist/electron-build/orbitdb/agent-tools.js +402 -0
- package/dist/electron-build/orbitdb/agent-tools.js.map +1 -0
- package/dist/electron-build/orbitdb/cid-database.js +284 -0
- package/dist/electron-build/orbitdb/cid-database.js.map +1 -0
- package/dist/electron-build/orbitdb/context-store.js +99 -0
- package/dist/electron-build/orbitdb/context-store.js.map +1 -0
- package/dist/electron-build/orbitdb/ipfs-node.js +120 -0
- package/dist/electron-build/orbitdb/ipfs-node.js.map +1 -0
- package/dist/electron-build/orbitdb/kanban-store.js +299 -0
- package/dist/electron-build/orbitdb/kanban-store.js.map +1 -0
- package/dist/electron-build/orbitdb/task-store.js +155 -0
- package/dist/electron-build/orbitdb/task-store.js.map +1 -0
- package/dist/electron-build/orbitdb/trajectory-store.js +194 -0
- package/dist/electron-build/orbitdb/trajectory-store.js.map +1 -0
- package/dist/electron-build/orbitdb/ui-cid.js +77 -0
- package/dist/electron-build/orbitdb/ui-cid.js.map +1 -0
- package/dist/electron-build/pi-ecosystem-a2ui/index.js +89 -0
- package/dist/electron-build/pi-ecosystem-a2ui/index.js.map +1 -0
- package/dist/electron-build/pi-ecosystem-goals/index.js +512 -0
- package/dist/electron-build/pi-ecosystem-goals/index.js.map +1 -0
- package/dist/electron-build/pi-ecosystem-judgment/adaptive-scan.js +325 -0
- package/dist/electron-build/pi-ecosystem-judgment/adaptive-scan.js.map +1 -0
- package/dist/electron-build/pi-ecosystem-judgment/causal-judge.js +492 -0
- package/dist/electron-build/pi-ecosystem-judgment/causal-judge.js.map +1 -0
- package/dist/electron-build/pi-ecosystem-judgment/cleanup.js +167 -0
- package/dist/electron-build/pi-ecosystem-judgment/cleanup.js.map +1 -0
- package/dist/electron-build/pi-ecosystem-judgment/decision.js +321 -0
- package/dist/electron-build/pi-ecosystem-judgment/decision.js.map +1 -0
- package/dist/electron-build/pi-ecosystem-judgment/detect-hook.js +175 -0
- package/dist/electron-build/pi-ecosystem-judgment/detect-hook.js.map +1 -0
- package/dist/electron-build/pi-ecosystem-judgment/distill-prompt.js +232 -0
- package/dist/electron-build/pi-ecosystem-judgment/distill-prompt.js.map +1 -0
- package/dist/electron-build/pi-ecosystem-judgment/distillation.js +304 -0
- package/dist/electron-build/pi-ecosystem-judgment/distillation.js.map +1 -0
- package/dist/electron-build/pi-ecosystem-judgment/evolve-judgment.js +176 -0
- package/dist/electron-build/pi-ecosystem-judgment/evolve-judgment.js.map +1 -0
- package/dist/electron-build/pi-ecosystem-judgment/human-value-pipeline.js +65 -0
- package/dist/electron-build/pi-ecosystem-judgment/human-value-pipeline.js.map +1 -0
- package/dist/electron-build/pi-ecosystem-judgment/human-value-store.js +808 -0
- package/dist/electron-build/pi-ecosystem-judgment/human-value-store.js.map +1 -0
- package/dist/electron-build/pi-ecosystem-judgment/index.js +508 -0
- package/dist/electron-build/pi-ecosystem-judgment/index.js.map +1 -0
- package/dist/electron-build/pi-ecosystem-judgment/injection-gate.js +334 -0
- package/dist/electron-build/pi-ecosystem-judgment/injection-gate.js.map +1 -0
- package/dist/electron-build/pi-ecosystem-judgment/monitor-gate.js +228 -0
- package/dist/electron-build/pi-ecosystem-judgment/monitor-gate.js.map +1 -0
- package/dist/electron-build/pi-ecosystem-judgment/value-injection.js +598 -0
- package/dist/electron-build/pi-ecosystem-judgment/value-injection.js.map +1 -0
- package/dist/electron-build/pi-ecosystem-mcp/index.js +643 -0
- package/dist/electron-build/pi-ecosystem-mcp/index.js.map +1 -0
- package/dist/electron-build/pi-ecosystem-mcp/ui-tools.js +116 -0
- package/dist/electron-build/pi-ecosystem-mcp/ui-tools.js.map +1 -0
- package/dist/electron-build/pi-ecosystem-subagents/index.js +352 -0
- package/dist/electron-build/pi-ecosystem-subagents/index.js.map +1 -0
- package/dist/electron-build/security/builtin-guards.js +133 -0
- package/dist/electron-build/security/builtin-guards.js.map +1 -0
- package/dist/electron-build/security/context-router-tool.js +111 -0
- package/dist/electron-build/security/context-router-tool.js.map +1 -0
- package/dist/electron-build/security/input-scanner.js +263 -0
- package/dist/electron-build/security/input-scanner.js.map +1 -0
- package/dist/electron-build/security/react-harness.js +148 -0
- package/dist/electron-build/security/react-harness.js.map +1 -0
- package/dist/electron-build/security/tool-gate.js +291 -0
- package/dist/electron-build/security/tool-gate.js.map +1 -0
- package/dist/electron-build/setup/setup-store.js +648 -0
- package/dist/electron-build/setup/setup-store.js.map +1 -0
- package/dist/electron-build/social/channels/ChannelManager.js +444 -0
- package/dist/electron-build/social/channels/ChannelManager.js.map +1 -0
- package/dist/electron-build/social/channels/DiapChannelBridge.js +451 -0
- package/dist/electron-build/social/channels/DiapChannelBridge.js.map +1 -0
- package/dist/electron-build/social/channels/InterestMatcher.js +135 -0
- package/dist/electron-build/social/channels/InterestMatcher.js.map +1 -0
- package/dist/electron-build/social/channels/types.js +21 -0
- package/dist/electron-build/social/channels/types.js.map +1 -0
- package/dist/electron-build/social/global-shared-context.js +368 -0
- package/dist/electron-build/social/global-shared-context.js.map +1 -0
- package/dist/electron-build/social/heartbeat.js +882 -0
- package/dist/electron-build/social/heartbeat.js.map +1 -0
- package/dist/electron-build/storage/did-catalog-bridge.js +314 -0
- package/dist/electron-build/storage/did-catalog-bridge.js.map +1 -0
- package/dist/electron-build/storage/did-catalog.js +272 -0
- package/dist/electron-build/storage/did-catalog.js.map +1 -0
- package/dist/electron-build/utils/auto-update.js +140 -558
- package/dist/electron-build/utils/auto-update.js.map +1 -1
- package/dist/electron-build/utils/module-context.js +98 -0
- package/dist/electron-build/utils/module-context.js.map +1 -0
- package/dist/electron-build/utils/runtime-bootstrap.js +745 -0
- package/dist/electron-build/utils/runtime-bootstrap.js.map +1 -0
- package/dist/electron-build/utils/update-manager.js +770 -0
- package/dist/electron-build/utils/update-manager.js.map +1 -0
- package/dist/electron-build/utils/update-state.js +406 -0
- package/dist/electron-build/utils/update-state.js.map +1 -0
- package/dist/electron-build/utils/version-info.js +562 -0
- package/dist/electron-build/utils/version-info.js.map +1 -0
- package/dist/electron-build/web/i18n.js +146 -0
- package/dist/electron-build/web/i18n.js.map +1 -0
- package/dist/electron-build/web/routes-x402-info.js +232 -0
- package/dist/electron-build/web/routes-x402-info.js.map +1 -0
- package/dist/electron-build/web/server-storage.js +390 -0
- package/dist/electron-build/web/server-storage.js.map +1 -0
- package/dist/electron-build/web/server-types.js +52 -0
- package/dist/electron-build/web/server-types.js.map +1 -0
- package/dist/electron-build/web/task-deps.js +41 -0
- package/dist/electron-build/web/task-deps.js.map +1 -0
- package/dist/index.js +58 -27
- package/dist/llm/system-prompt/registry.js +6 -3
- package/dist/security/tool-gate.js +4 -0
- package/dist/utils/auto-update.js +138 -525
- package/dist/utils/module-context.js +59 -0
- package/dist/utils/runtime-bootstrap.js +682 -0
- package/dist/utils/update-cli.js +82 -0
- package/dist/utils/update-health.js +401 -0
- package/dist/utils/update-manager.js +716 -0
- package/dist/utils/update-state.js +353 -0
- package/dist/utils/version-info.js +510 -0
- package/dist/web/mobile-contacts.js +329 -0
- package/dist/web/mobile-core.js +481 -15
- package/dist/web/mobile.html +32 -0
- package/dist/web/mobile.js +139 -0
- package/dist/web/routes-contacts.js +420 -0
- package/dist/web/server.js +5 -0
- package/package.json +1 -1
- package/scripts/build-cli.js +32 -151
- package/scripts/postinstall.js +153 -76
|
@@ -0,0 +1,4042 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.IdempotencyCache = exports.SIDE_EFFECT_TOOLS = void 0;
|
|
37
|
+
exports.runCodeSnippet = runCodeSnippet;
|
|
38
|
+
exports.runTerminalCommand = runTerminalCommand;
|
|
39
|
+
exports.registerBuiltinTools = registerBuiltinTools;
|
|
40
|
+
exports.ensureKuboReady = ensureKuboReady;
|
|
41
|
+
exports.kuboApi = kuboApi;
|
|
42
|
+
exports.registerWalletTools = registerWalletTools;
|
|
43
|
+
exports.setupInboxListener = setupInboxListener;
|
|
44
|
+
const fs = __importStar(require("fs/promises"));
|
|
45
|
+
const fsSync = __importStar(require("fs"));
|
|
46
|
+
const path = __importStar(require("path"));
|
|
47
|
+
const version_info_js_1 = require("../utils/version-info.js");
|
|
48
|
+
const reader_js_1 = require("../documents/reader.js");
|
|
49
|
+
const p2p_js_1 = require("../network/p2p.js");
|
|
50
|
+
const shell_tool_js_1 = require("./shell-tool.js");
|
|
51
|
+
const shell_guard_js_1 = require("./shell-guard.js");
|
|
52
|
+
const p2p_document_tools_js_1 = require("./p2p-document-tools.js");
|
|
53
|
+
const pi_sdk_session_factory_js_1 = require("./pi-sdk-session-factory.js");
|
|
54
|
+
const index_js_1 = require("../constraints/index.js");
|
|
55
|
+
const delegate_js_1 = require("../external-engines/delegate.js");
|
|
56
|
+
// 2026-09-13: 下一代工具集 (人机问答 / 精确补丁 / 桌面操作 / 技能分享)
|
|
57
|
+
const user_questions_js_1 = require("./user-questions.js");
|
|
58
|
+
const patch_tool_js_1 = require("./patch-tool.js");
|
|
59
|
+
const computer_use_js_1 = require("./computer-use.js");
|
|
60
|
+
const skill_share_js_1 = require("./skill-share.js");
|
|
61
|
+
// 2026-09-13: 微支付信息服务 (x402 付费信息 + 验真信封)
|
|
62
|
+
const paid_info_tools_js_1 = require("./x402/paid-info-tools.js");
|
|
63
|
+
/**
|
|
64
|
+
* Tools 模块 — 从 pi-sdk.ts 抽出的 registerTools() / _registerWalletTools() / _setupInboxListener()
|
|
65
|
+
* / wrapToolsWithIdempotency() (2026-07-06).
|
|
66
|
+
*
|
|
67
|
+
* 三个工具来源:
|
|
68
|
+
* 1. 内置工具: read_document, summarize_document, improve_document, list_peers, send_message,
|
|
69
|
+
* broadcast_message, list_local_channels, send_to_channel, check_inbox, send_to_peer,
|
|
70
|
+
* p2p_broadcast, agent_call, get_identity, set_persona, get_operation_logs, list_files,
|
|
71
|
+
* read_directory, shell_exec, self_improve, list_skills, use_skill, write_file, edit_file,
|
|
72
|
+
* git_diff, git_commit, git_push, git_branch, read_file, delete_file, mkdir, move_file,
|
|
73
|
+
* grep_files, glob_files, git_log, git_show, git_stash, vitest_run, tsc_check, create_task,
|
|
74
|
+
* update_task, get_task, list_tasks, p2pDocumentTools
|
|
75
|
+
* 2. 钱包工具 (EVM / Polymarket / Safe) — 单独的 _registerWalletTools
|
|
76
|
+
* 3. P2P 消息 inbox 监听 — _setupInboxListener
|
|
77
|
+
*
|
|
78
|
+
* 调用方: PiAgentSession.registerTools() 会调下面的三个函数, 并执行
|
|
79
|
+
* wrapToolsWithIdempotency + 镜像到 ToolRegistry.
|
|
80
|
+
*
|
|
81
|
+
* 工具缓存策略: SIDE_EFFECT_TOOLS 子集 (write_file / edit_file / shell_exec / git_commit 等)
|
|
82
|
+
* 走幂等性 cache, 防止 LLM loop 重试时副作用执行两次.
|
|
83
|
+
*/
|
|
84
|
+
exports.SIDE_EFFECT_TOOLS = new Set([
|
|
85
|
+
'write_file', 'edit_file', 'shell_exec', 'git_commit', 'git_push', 'git_branch',
|
|
86
|
+
'create_task', 'update_task', 'terminal', 'process',
|
|
87
|
+
// 2026-09-13: 幂等缓存同样适用于新工具 (重复调用不该产生第二次副作用)
|
|
88
|
+
'patch', 'computer_use', 'skill_import',
|
|
89
|
+
]);
|
|
90
|
+
/** 2026-08-12 (TaskA): 探测可用的命令 (跨平台: Windows 无 python3, 有 python). 返回 [cmd, ...] 或 null. */
|
|
91
|
+
async function detectRunner(candidates) {
|
|
92
|
+
const { spawn } = await Promise.resolve().then(() => __importStar(require('child_process')));
|
|
93
|
+
for (const c of candidates) {
|
|
94
|
+
try {
|
|
95
|
+
const ok = await new Promise((resolve) => {
|
|
96
|
+
const p = spawn(c, ['--version'], { stdio: 'ignore', windowsHide: true });
|
|
97
|
+
p.on('close', (code) => resolve(code === 0));
|
|
98
|
+
p.on('error', () => resolve(false));
|
|
99
|
+
});
|
|
100
|
+
if (ok)
|
|
101
|
+
return [c];
|
|
102
|
+
}
|
|
103
|
+
catch { /* 下一个 */ }
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
async function runCodeSnippet(opts) {
|
|
108
|
+
const code = String(opts.code ?? '').trim();
|
|
109
|
+
if (!code)
|
|
110
|
+
return { success: false, error: 'code 必填' };
|
|
111
|
+
const lang = String(opts.language || '').trim().toLowerCase();
|
|
112
|
+
// 语言 → (扩展名, 解释器命令)
|
|
113
|
+
const ext = lang === 'python' || lang === 'py' ? 'py'
|
|
114
|
+
: lang === 'javascript' || lang === 'js' || lang === 'node' ? 'js'
|
|
115
|
+
: lang === 'typescript' || lang === 'ts' ? 'ts'
|
|
116
|
+
: lang === 'shell' || lang === 'bash' || lang === 'sh' ? 'sh'
|
|
117
|
+
: lang === 'html' ? 'html'
|
|
118
|
+
: 'txt';
|
|
119
|
+
const runner = lang === 'python' || lang === 'py' ? await detectRunner(['python3', 'python'])
|
|
120
|
+
: lang === 'javascript' || lang === 'js' || lang === 'node' ? await detectRunner(['node'])
|
|
121
|
+
: lang === 'typescript' || lang === 'ts' ? ['npx', 'tsx']
|
|
122
|
+
: lang === 'shell' || lang === 'bash' || lang === 'sh' ? ['bash']
|
|
123
|
+
: null;
|
|
124
|
+
if (!runner) {
|
|
125
|
+
if (['python', 'py', 'js', 'javascript', 'node', 'ts', 'typescript', 'shell', 'bash', 'sh'].includes(lang)) {
|
|
126
|
+
return { success: false, error: `解释器未找到 (${lang}), 请装对应运行时或改用 command` };
|
|
127
|
+
}
|
|
128
|
+
return { success: false, error: `不支持的语言 '${lang}', 支持: python/js/ts/shell/html` };
|
|
129
|
+
}
|
|
130
|
+
const { tmpdir } = await Promise.resolve().then(() => __importStar(require('os')));
|
|
131
|
+
const tmpDir = opts.tmpDir ?? tmpdir();
|
|
132
|
+
const file = `${tmpDir}/bolloon-code-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.${ext}`;
|
|
133
|
+
const fsMod = await Promise.resolve().then(() => __importStar(require('fs/promises')));
|
|
134
|
+
await fsMod.writeFile(file, code, 'utf-8');
|
|
135
|
+
try {
|
|
136
|
+
const { spawn } = await Promise.resolve().then(() => __importStar(require('child_process')));
|
|
137
|
+
const timeoutMs = opts.timeoutMs ?? 30000;
|
|
138
|
+
const result = await new Promise((resolve) => {
|
|
139
|
+
const proc = spawn(runner[0], [...runner.slice(1), file], {
|
|
140
|
+
cwd: opts.cwd ?? process.cwd(),
|
|
141
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
|
|
142
|
+
windowsHide: true,
|
|
143
|
+
});
|
|
144
|
+
let stdout = '';
|
|
145
|
+
let stderr = '';
|
|
146
|
+
const timer = setTimeout(() => {
|
|
147
|
+
try {
|
|
148
|
+
proc.kill('SIGKILL');
|
|
149
|
+
}
|
|
150
|
+
catch { /* 忽略 */ }
|
|
151
|
+
resolve({ success: false, error: `代码执行超时 (>${timeoutMs}ms)`, output: (stdout + (stderr ? `\n[stderr]\n${stderr}` : '')).trim().slice(0, 8000) });
|
|
152
|
+
}, timeoutMs);
|
|
153
|
+
proc.stdout.on('data', (d) => { stdout += d.toString(); });
|
|
154
|
+
proc.stderr.on('data', (d) => { stderr += d.toString(); });
|
|
155
|
+
proc.on('error', (e) => { clearTimeout(timer); resolve({ success: false, error: `启动失败: ${e.message}` }); });
|
|
156
|
+
proc.on('close', (code) => {
|
|
157
|
+
clearTimeout(timer);
|
|
158
|
+
const output = (stdout + (stderr ? `\n[stderr]\n${stderr}` : '')).trim().slice(0, 8000) || '(无输出)';
|
|
159
|
+
resolve({ success: code === 0, output, exitCode: code, language: lang, script: file });
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
return result;
|
|
163
|
+
}
|
|
164
|
+
finally {
|
|
165
|
+
await fsMod.rm(file, { force: true }).catch(() => { });
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
async function runTerminalCommand(raw, opts = {}) {
|
|
169
|
+
const { checkTerminalCommand } = await Promise.resolve().then(() => __importStar(require('./shell-guard.js')));
|
|
170
|
+
const timeoutMs = opts.timeoutMs ?? 30000;
|
|
171
|
+
const list = (opts.commands && opts.commands.length > 0) ? opts.commands : [raw];
|
|
172
|
+
for (const c of list) {
|
|
173
|
+
const guard = checkTerminalCommand(String(c || '').trim());
|
|
174
|
+
if (!guard.allowed) {
|
|
175
|
+
return { success: false, error: `[terminal-guard] ${guard.reason}`, deniedByGuard: true };
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
// 2026-08-12 (TaskD): 后台执行 — 长命令不阻塞对话.
|
|
179
|
+
if (opts.background) {
|
|
180
|
+
const cmdStr = list.join(' && ');
|
|
181
|
+
const { spawnBackground } = await Promise.resolve().then(() => __importStar(require('./process-runner.js')));
|
|
182
|
+
const session = spawnBackground(cmdStr, opts.cwd ?? process.cwd());
|
|
183
|
+
return {
|
|
184
|
+
success: true,
|
|
185
|
+
background: true,
|
|
186
|
+
sessionId: session.id,
|
|
187
|
+
cmd: cmdStr.slice(0, 200),
|
|
188
|
+
message: `已在后台启动 (${session.id}). 用 process 工具轮询/等待: process(session_id="${session.id}", action="poll"|"wait"|"kill")`,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
const { exec } = await Promise.resolve().then(() => __importStar(require('child_process')));
|
|
192
|
+
const runOne = (cmdStr) => new Promise((resolve) => {
|
|
193
|
+
exec(cmdStr, {
|
|
194
|
+
cwd: opts.cwd ?? process.cwd(),
|
|
195
|
+
timeout: timeoutMs,
|
|
196
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
197
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
|
|
198
|
+
}, (err, stdout, stderr) => {
|
|
199
|
+
const output = ((stdout || '') + (stderr ? `\n[stderr]\n${stderr}` : '')).trim().slice(0, 8000);
|
|
200
|
+
if (err) {
|
|
201
|
+
resolve({ success: false, cmd: cmdStr.slice(0, 120), error: `exit ${err.code ?? '?'}: ${String(err.message || '').slice(0, 200)}`, output: output || undefined });
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
resolve({ success: true, cmd: cmdStr.slice(0, 120), output: output || '(无输出)' });
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
if (list.length === 1)
|
|
209
|
+
return runOne(list[0]);
|
|
210
|
+
const results = await Promise.all(list.map((c) => runOne(c)));
|
|
211
|
+
const failed = results.filter((r) => !r.success);
|
|
212
|
+
const allOutput = results.map((r) => `${r.cmd}\n${r.output || r.error || ''}`).join('\n\n---\n\n');
|
|
213
|
+
if (failed.length > 0) {
|
|
214
|
+
return { success: false, error: `${failed.length}/${list.length} 条命令失败: ${failed[0].error}`, output: allOutput, partial: results };
|
|
215
|
+
}
|
|
216
|
+
return { success: true, output: allOutput, count: list.length, parallel: true };
|
|
217
|
+
}
|
|
218
|
+
function registerBuiltinTools(ctx) {
|
|
219
|
+
ctx.tools.set('read_document', {
|
|
220
|
+
name: 'read_document',
|
|
221
|
+
description: '读取文档内容,支持 .txt, .md, .pdf, .docx 格式',
|
|
222
|
+
parameters: { path: '文件路径 (必填)' },
|
|
223
|
+
execute: async (args) => {
|
|
224
|
+
try {
|
|
225
|
+
const path = String(args.path || '').trim();
|
|
226
|
+
if (!path)
|
|
227
|
+
return { success: false, error: 'path 必填' };
|
|
228
|
+
const content = await reader_js_1.documentReader.read(path);
|
|
229
|
+
return {
|
|
230
|
+
success: true,
|
|
231
|
+
output: `📄 ${content.metadata.filename}\n大小: ${content.metadata.size} 字节\n\n${content.text.substring(0, 1000)}${content.text.length > 1000 ? '...' : ''}`
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
catch (e) {
|
|
235
|
+
return { success: false, error: String(e) };
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
ctx.tools.set('summarize_document', {
|
|
240
|
+
name: 'summarize_document',
|
|
241
|
+
description: '总结文档内容,分析并生成摘要',
|
|
242
|
+
parameters: { path: '文件路径 (必填)', context: '可选, 总结上下文提示' },
|
|
243
|
+
execute: async (args) => {
|
|
244
|
+
try {
|
|
245
|
+
const path = String(args.path || '').trim();
|
|
246
|
+
if (!path)
|
|
247
|
+
return { success: false, error: 'path 必填' };
|
|
248
|
+
if (!ctx.minimaxAvailable) {
|
|
249
|
+
return { success: false, error: 'LLM未初始化,请设置 MINIMAX_API_KEY' };
|
|
250
|
+
}
|
|
251
|
+
// summarizeDocument 走 PiAgentSession.summarizeDocument, 这里通过 tools 反向调用不好处理
|
|
252
|
+
// 简化: 让 LLM 自己直接走 shell_exec / use_skill 路径
|
|
253
|
+
const llm = (0, index_js_1.getMinimax)();
|
|
254
|
+
const content = await reader_js_1.documentReader.read(path);
|
|
255
|
+
const r = await llm.summarize(content.text, args.context);
|
|
256
|
+
return {
|
|
257
|
+
success: true,
|
|
258
|
+
output: `📝 摘要:\n${r.summary}\n\n质量评分: ${(r.qualityScore * 10).toFixed(1)}/10`
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
catch (e) {
|
|
262
|
+
return { success: false, error: String(e) };
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
ctx.tools.set('improve_document', {
|
|
267
|
+
name: 'improve_document',
|
|
268
|
+
description: '根据要求改进文档内容',
|
|
269
|
+
parameters: { path: '文件路径 (必填)', requirements: '改进要求 (必填)' },
|
|
270
|
+
execute: async (args) => {
|
|
271
|
+
try {
|
|
272
|
+
const path = String(args.path || '').trim();
|
|
273
|
+
if (!path)
|
|
274
|
+
return { success: false, error: 'path 必填' };
|
|
275
|
+
const requirements = String(args.requirements || '').trim();
|
|
276
|
+
if (!requirements)
|
|
277
|
+
return { success: false, error: 'requirements 必填' };
|
|
278
|
+
if (!ctx.minimaxAvailable) {
|
|
279
|
+
return { success: false, error: 'LLM未初始化,请设置 MINIMAX_API_KEY' };
|
|
280
|
+
}
|
|
281
|
+
const llm = (0, index_js_1.getMinimax)();
|
|
282
|
+
const content = await reader_js_1.documentReader.read(path);
|
|
283
|
+
const improved = await llm.summarize(content.text + '\n\n改进要求: ' + requirements, undefined);
|
|
284
|
+
return {
|
|
285
|
+
success: true,
|
|
286
|
+
output: `✅ 改进完成\n质量评分: ${(improved.qualityScore * 10).toFixed(1)}/10\n${improved.summary ? '\n改进内容:\n' + improved.summary.substring(0, 500) + '...' : ''}`
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
catch (e) {
|
|
290
|
+
return { success: false, error: String(e) };
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
ctx.tools.set('list_peers', {
|
|
295
|
+
name: 'list_peers',
|
|
296
|
+
description: '列出已连接的对等节点',
|
|
297
|
+
parameters: {},
|
|
298
|
+
execute: async () => {
|
|
299
|
+
const peers = p2p_js_1.p2pNetwork.getPeers();
|
|
300
|
+
if (peers.length === 0) {
|
|
301
|
+
return { success: true, output: '当前无连接的对等节点' };
|
|
302
|
+
}
|
|
303
|
+
return { success: true, output: `已连接节点 (${peers.length}):\n${peers.map(p => ` - ${p}`).join('\n')}` };
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
ctx.tools.set('send_message', {
|
|
307
|
+
name: 'send_message',
|
|
308
|
+
description: '向指定对等节点发送消息',
|
|
309
|
+
parameters: { peer_id: '对等节点ID', message: '消息内容' },
|
|
310
|
+
execute: async (args) => {
|
|
311
|
+
try {
|
|
312
|
+
await p2p_js_1.p2pNetwork.sendMessage(args.peer_id, 'message', args.message);
|
|
313
|
+
return { success: true, output: `消息已发送到 ${args.peer_id}` };
|
|
314
|
+
}
|
|
315
|
+
catch (e) {
|
|
316
|
+
return { success: false, error: String(e) };
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
ctx.tools.set('broadcast_message', {
|
|
321
|
+
name: 'broadcast_message',
|
|
322
|
+
description: '向所有对等节点广播消息',
|
|
323
|
+
parameters: { message: '消息内容' },
|
|
324
|
+
execute: async (args) => {
|
|
325
|
+
try {
|
|
326
|
+
await p2p_js_1.p2pNetwork.broadcast('message', args.message);
|
|
327
|
+
return { success: true, output: '消息已广播到所有节点' };
|
|
328
|
+
}
|
|
329
|
+
catch (e) {
|
|
330
|
+
return { success: false, error: String(e) };
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
// Agent Mesh 工具集
|
|
335
|
+
ctx.tools.set('list_local_channels', {
|
|
336
|
+
name: 'list_local_channels',
|
|
337
|
+
description: '列出当前 PiAgentSession 注册的所有 channel. 每个 channel 是 bolloon 内的智能体会话, 可含 messages[] / peerDid(对端 DID) / peerName. 供 send_to_channel 选 channel_id.',
|
|
338
|
+
parameters: {},
|
|
339
|
+
execute: async () => {
|
|
340
|
+
const channels = ctx.sessionManager ? ctx.sessionManager.getAllChannels?.() || [] : [];
|
|
341
|
+
if (channels.length === 0) {
|
|
342
|
+
return { success: true, output: '📭 当前 session 没有注册任何 channel (用 send_to_channel 留空 channel_id 会自动创建一个)' };
|
|
343
|
+
}
|
|
344
|
+
const lines = channels.map((ch, i) => {
|
|
345
|
+
const peer = ch.peerDid ? ` peer=${ch.peerDid.substring(0, 20)}` : (ch.peerName ? ` peer="${ch.peerName}"` : '');
|
|
346
|
+
const msgCount = ch.messages?.length || 0;
|
|
347
|
+
return ` ${i + 1}. ${ch.id} name="${ch.name}" msgs=${msgCount}${peer}`;
|
|
348
|
+
});
|
|
349
|
+
return { success: true, output: `📬 当前 session 有 ${channels.length} 个 channel:\n${lines.join('\n')}` };
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
ctx.tools.set('send_to_channel', {
|
|
353
|
+
name: 'send_to_channel',
|
|
354
|
+
description: '发送消息到指定 channel. channel_id 留空会自动创建一个. 如果 channel 已关联 peerDid, 会同时通过 P2P 转发到对端 agent.',
|
|
355
|
+
parameters: { channel_id: '目标 channel id (留空自动创建)', message: '消息内容 (必填)', peer_did: '可选 创建新 channel 时绑定的对端 DID (e.g. did:key:...)' },
|
|
356
|
+
execute: async (args) => {
|
|
357
|
+
const message = String(args.message || '').trim();
|
|
358
|
+
if (!message)
|
|
359
|
+
return { success: false, error: 'message 必填' };
|
|
360
|
+
let channelId = String(args.channel_id || '').trim();
|
|
361
|
+
const peerDid = args.peer_did ? String(args.peer_did) : undefined;
|
|
362
|
+
if (!channelId) {
|
|
363
|
+
const ch = await ctx.sessionManager.getOrCreatePeerChannel(peerDid || 'auto-created', 'auto-created');
|
|
364
|
+
channelId = ch.id;
|
|
365
|
+
}
|
|
366
|
+
const ch = ctx.sessionManager.getAllChannels().find((c) => c.id === channelId);
|
|
367
|
+
if (ch?.peerDid) {
|
|
368
|
+
try {
|
|
369
|
+
const peerId = ch.peerDid.replace(/^did:key:/, '').replace(/^did:pi:/, '');
|
|
370
|
+
await p2p_js_1.p2pNetwork.sendMessage(peerId, 'channel-message', JSON.stringify({ channelId, content: message, from: ctx.identity.did, timestamp: new Date().toISOString() }));
|
|
371
|
+
return { success: true, output: `📨 消息已存到 channel ${channelId} + P2P 转发到 ${ch.peerDid.substring(0, 30)}...` };
|
|
372
|
+
}
|
|
373
|
+
catch (e) {
|
|
374
|
+
return { success: true, output: `📨 消息已存到 channel ${channelId} (P2P 转发失败: ${String(e).slice(0, 100)})` };
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
return { success: true, output: `📨 消息已存到 channel ${channelId}` };
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
ctx.tools.set('check_inbox', {
|
|
381
|
+
name: 'check_inbox',
|
|
382
|
+
description: '检查 P2P + 本地 inbox 收到的所有消息. 跨 channel 视角, 按时间倒序.',
|
|
383
|
+
parameters: { max: '可选, 最多返回 N 条 (默认 50)' },
|
|
384
|
+
execute: async (args) => {
|
|
385
|
+
const max = Number(args.max) || 50;
|
|
386
|
+
if (ctx._inboxMessages.length === 0) {
|
|
387
|
+
return { success: true, output: '📭 当前 inbox 空' };
|
|
388
|
+
}
|
|
389
|
+
const slice = ctx._inboxMessages.slice(-max).reverse();
|
|
390
|
+
const lines = slice.map((m, i) => {
|
|
391
|
+
const fromTxt = m.fromDid ? `${m.from} (${m.fromDid.substring(0, 20)}...)` : m.from;
|
|
392
|
+
return ` ${i + 1}. [${m.type}] from=${fromTxt} time=${new Date(m.timestamp).toISOString()} src=${m.source}\n ${String(m.payload).substring(0, 200)}`;
|
|
393
|
+
});
|
|
394
|
+
return { success: true, output: `📬 inbox 有 ${ctx._inboxMessages.length} 条消息 (最近 ${slice.length}):\n${lines.join('\n')}` };
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
ctx.tools.set('send_to_peer', {
|
|
398
|
+
name: 'send_to_peer',
|
|
399
|
+
description: '发送结构化消息到指定 P2P 节点 (远端 bolloon 实例). 对方会通过远端 channel 收到.',
|
|
400
|
+
parameters: { peer_id: '目标 P2P 节点 publicKey (用 list_peers 查)', message: '消息内容 (任意字符串)', type: '可选, 消息类型标签 (默认 agent-message)' },
|
|
401
|
+
execute: async (args) => {
|
|
402
|
+
const peerId = String(args.peer_id || '').trim();
|
|
403
|
+
const msg = String(args.message || '').trim();
|
|
404
|
+
const type = String(args.type || 'agent-message').trim();
|
|
405
|
+
if (!peerId)
|
|
406
|
+
return { success: false, error: 'peer_id 必填' };
|
|
407
|
+
if (!msg)
|
|
408
|
+
return { success: false, error: 'message 必填' };
|
|
409
|
+
try {
|
|
410
|
+
await p2p_js_1.p2pNetwork.sendMessage(peerId, type, msg);
|
|
411
|
+
return { success: true, output: `✅ 消息已发送到 ${peerId.substring(0, 16)}...` };
|
|
412
|
+
}
|
|
413
|
+
catch (e) {
|
|
414
|
+
return { success: false, error: `发送失败: ${String(e)}` };
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
ctx.tools.set('p2p_broadcast', {
|
|
419
|
+
name: 'p2p_broadcast',
|
|
420
|
+
description: '广播消息到所有连接的 P2P 节点',
|
|
421
|
+
parameters: { message: '消息内容', type: '可选, 消息类型标签 (默认 agent-broadcast)' },
|
|
422
|
+
execute: async (args) => {
|
|
423
|
+
const msg = String(args.message || '').trim();
|
|
424
|
+
const type = String(args.type || 'agent-broadcast').trim();
|
|
425
|
+
if (!msg)
|
|
426
|
+
return { success: false, error: 'message 必填' };
|
|
427
|
+
try {
|
|
428
|
+
await p2p_js_1.p2pNetwork.broadcast(type, msg);
|
|
429
|
+
return { success: true, output: `📡 已广播 type=${type}` };
|
|
430
|
+
}
|
|
431
|
+
catch (e) {
|
|
432
|
+
return { success: false, error: `广播失败: ${String(e)}` };
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
ctx.tools.set('agent_call', {
|
|
437
|
+
name: 'agent_call',
|
|
438
|
+
description: 'RPC: 让远端 P2P agent 跑一个任务, 等待结果返回. 远端 agent 会基于 task 描述自主完成, 完成后回复. (RPC 结果回收机制待实现)',
|
|
439
|
+
parameters: { peer_id: '目标 P2P 节点', task: '任务描述 (远端 agent 收到的 prompt)', timeoutMs: '可选 超时 (ms, 默认 30000)' },
|
|
440
|
+
execute: async (args) => {
|
|
441
|
+
const peerId = String(args.peer_id || '').trim();
|
|
442
|
+
const task = String(args.task || '').trim();
|
|
443
|
+
if (!peerId)
|
|
444
|
+
return { success: false, error: 'peer_id 必填' };
|
|
445
|
+
if (!task)
|
|
446
|
+
return { success: false, error: 'task 必填' };
|
|
447
|
+
const requestId = `rpc-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
448
|
+
await p2p_js_1.p2pNetwork.sendMessage(peerId, 'agent-call', JSON.stringify({ requestId, task, from: ctx.identity.did }));
|
|
449
|
+
return { success: true, output: `📞 RPC 任务已发送给 ${peerId.substring(0, 16)}...\n requestId=${requestId}` };
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
// add_friend_by_id — 通过 Hyperswarm publicKey 添加 P2P 好友
|
|
453
|
+
ctx.tools.set('add_friend_by_id', {
|
|
454
|
+
name: 'add_friend_by_id',
|
|
455
|
+
description: '通过 Hyperswarm P2P publicKey (64 字符 hex) 添加好友. 对方在线时会收到好友申请弹窗, 接受后自动分享 channel. 强烈建议填 note 备注 (自我介绍/来源), 对方才能分辨你是谁.',
|
|
456
|
+
parameters: {
|
|
457
|
+
publicKey: '64 字符 hex publicKey (必填)',
|
|
458
|
+
name: '可选, 给好友的备注名 (如: 同事-张磊)',
|
|
459
|
+
message: '可选, 附加的好友申请消息',
|
|
460
|
+
note: '可选, 备注 (自我介绍/来源), 对方接受时会看到. 如 "我是[姓名]的 Bolloon agent[name], 来自[来源], 想加你为好友共享 channel 协作,技能: [技能列表]"'
|
|
461
|
+
},
|
|
462
|
+
execute: async (args) => {
|
|
463
|
+
const publicKey = String(args.publicKey || '').trim();
|
|
464
|
+
const name = String(args.name || '').trim();
|
|
465
|
+
const message = String(args.message || '想加你为 P2P 好友, 共享 channel 协作').trim();
|
|
466
|
+
const note = String(args.note || '').trim();
|
|
467
|
+
if (!publicKey || publicKey.length !== 64 || !/^[0-9a-fA-F]{64}$/.test(publicKey)) {
|
|
468
|
+
return { success: false, error: 'publicKey 必须是 64 字符 hex 格式' };
|
|
469
|
+
}
|
|
470
|
+
try {
|
|
471
|
+
const port = process.env.PORT || '54188';
|
|
472
|
+
const res = await fetch(`http://127.0.0.1:${port}/api/friend-request`, {
|
|
473
|
+
method: 'POST',
|
|
474
|
+
headers: { 'Content-Type': 'application/json' },
|
|
475
|
+
body: JSON.stringify({ targetPublicKey: publicKey, name: name || undefined, message, note: note || undefined })
|
|
476
|
+
});
|
|
477
|
+
const data = await res.json();
|
|
478
|
+
if (!res.ok) {
|
|
479
|
+
const reason = data.code === 'NO_CONN' ? '对方未在线, 已本地记住, 等对方上线后自动重连' : (data.error || '请求失败');
|
|
480
|
+
return { success: false, error: `添加好友失败: ${reason}`, output: data.persistedAs ? `本地已保存为: ${data.persistedAs}` : undefined };
|
|
481
|
+
}
|
|
482
|
+
return { success: true, output: `✅ 好友申请已发送给 ${data.persistedAs || name || publicKey.substring(0, 12)}...\n对方接受后会自动出现在 P2P 好友列表.` };
|
|
483
|
+
}
|
|
484
|
+
catch (e) {
|
|
485
|
+
return { success: false, error: `添加好友失败: ${String(e.message || e)}` };
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
});
|
|
489
|
+
// list_pending_friend_requests — 查看待处理的好友申请 (智能体侧处理入口)
|
|
490
|
+
ctx.tools.set('list_pending_friend_requests', {
|
|
491
|
+
name: 'list_pending_friend_requests',
|
|
492
|
+
description: '查看当前待处理的好友申请列表 (对方想加你为好友但还没接受). 每个申请带 fromName / note 备注 (自我介绍/来源), 便于判断是否接受. 用 accept_friend_request 接受, 或 ignore_friend_request 忽略.',
|
|
493
|
+
parameters: {},
|
|
494
|
+
execute: async () => {
|
|
495
|
+
try {
|
|
496
|
+
const port = process.env.PORT || '54188';
|
|
497
|
+
const res = await fetch(`http://127.0.0.1:${port}/api/friend-requests`);
|
|
498
|
+
const data = await res.json();
|
|
499
|
+
if (!res.ok)
|
|
500
|
+
return { success: false, error: data.error || '查询失败' };
|
|
501
|
+
if (data.count === 0) {
|
|
502
|
+
return { success: true, output: '当前没有待处理的好友申请。' };
|
|
503
|
+
}
|
|
504
|
+
const lines = data.requests.map((r, i) => `${i + 1}. ${r.fromName} (publicKey=${r.fromPublicKey.substring(0, 16)}...)\n 备注: ${r.note || r.message || '(无)'}\n requestId: ${r.requestId}`);
|
|
505
|
+
return { success: true, output: `待处理好友申请 ${data.count} 个:\n${lines.join('\n')}\n\n用 accept_friend_request 接受, 或 ignore_friend_request 忽略.` };
|
|
506
|
+
}
|
|
507
|
+
catch (e) {
|
|
508
|
+
return { success: false, error: `查询好友申请失败: ${String(e.message || e)}` };
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
});
|
|
512
|
+
// accept_friend_request — 接受一个待处理的好友申请 (智能体侧一键通过)
|
|
513
|
+
ctx.tools.set('accept_friend_request', {
|
|
514
|
+
name: 'accept_friend_request',
|
|
515
|
+
description: '接受一个待处理的好友申请. 对方会出现在你的 P2P 好友列表, 对方分享的 channel 自动可见. 参数 requestId 来自 list_pending_friend_requests 的返回.',
|
|
516
|
+
parameters: {
|
|
517
|
+
requestId: '待处理申请的 requestId (必填, 来自 list_pending_friend_requests)',
|
|
518
|
+
name: '可选, 给这个新好友的备注名 (默认用对方自称的名字)'
|
|
519
|
+
},
|
|
520
|
+
execute: async (args) => {
|
|
521
|
+
const requestId = String(args.requestId || '').trim();
|
|
522
|
+
if (!requestId) {
|
|
523
|
+
return { success: false, error: 'requestId 必填 (先调 list_pending_friend_requests 获取)' };
|
|
524
|
+
}
|
|
525
|
+
try {
|
|
526
|
+
const port = process.env.PORT || '54188';
|
|
527
|
+
// 先查 pending 拿到 fromPublicKey / fromName
|
|
528
|
+
const q = await fetch(`http://127.0.0.1:${port}/api/friend-requests`);
|
|
529
|
+
const qd = await q.json();
|
|
530
|
+
const req = (qd.requests || []).find((r) => r.requestId === requestId);
|
|
531
|
+
if (!req) {
|
|
532
|
+
return { success: false, error: `未找到 requestId=${requestId} 的申请 (可能已被处理或过期)` };
|
|
533
|
+
}
|
|
534
|
+
const acceptName = String(args.name || '').trim() || req.fromName;
|
|
535
|
+
const res = await fetch(`http://127.0.0.1:${port}/api/friend-accept`, {
|
|
536
|
+
method: 'POST',
|
|
537
|
+
headers: { 'Content-Type': 'application/json' },
|
|
538
|
+
body: JSON.stringify({ fromPublicKey: req.fromPublicKey, name: acceptName, requestId })
|
|
539
|
+
});
|
|
540
|
+
const data = await res.json();
|
|
541
|
+
if (!res.ok)
|
|
542
|
+
return { success: false, error: data.error || '接受失败' };
|
|
543
|
+
return { success: true, output: `✅ 已接受 ${req.fromName} 的好友申请 (备注: ${req.note || req.message || '(无)'}), 已加为好友: ${data.persistedAs || acceptName}` };
|
|
544
|
+
}
|
|
545
|
+
catch (e) {
|
|
546
|
+
return { success: false, error: `接受好友申请失败: ${String(e.message || e)}` };
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
});
|
|
550
|
+
// ignore_friend_request — 忽略一个待处理的好友申请
|
|
551
|
+
ctx.tools.set('ignore_friend_request', {
|
|
552
|
+
name: 'ignore_friend_request',
|
|
553
|
+
description: '忽略 (拒绝) 一个待处理的好友申请, 不加入好友. 参数 requestId 来自 list_pending_friend_requests 的返回.',
|
|
554
|
+
parameters: {
|
|
555
|
+
requestId: '待处理申请的 requestId (必填, 来自 list_pending_friend_requests)'
|
|
556
|
+
},
|
|
557
|
+
execute: async (args) => {
|
|
558
|
+
const requestId = String(args.requestId || '').trim();
|
|
559
|
+
if (!requestId)
|
|
560
|
+
return { success: false, error: 'requestId 必填' };
|
|
561
|
+
try {
|
|
562
|
+
const port = process.env.PORT || '54188';
|
|
563
|
+
const res = await fetch(`http://127.0.0.1:${port}/api/friend-requests/ignore`, {
|
|
564
|
+
method: 'POST',
|
|
565
|
+
headers: { 'Content-Type': 'application/json' },
|
|
566
|
+
body: JSON.stringify({ requestId })
|
|
567
|
+
});
|
|
568
|
+
const data = await res.json();
|
|
569
|
+
if (!res.ok)
|
|
570
|
+
return { success: false, error: data.error || '忽略失败' };
|
|
571
|
+
return { success: true, output: `已忽略好友申请 ${requestId.substring(0, 8)}...` };
|
|
572
|
+
}
|
|
573
|
+
catch (e) {
|
|
574
|
+
return { success: false, error: `忽略好友申请失败: ${String(e.message || e)}` };
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
});
|
|
578
|
+
// 2026-08-02: 远端 channel 工具 — 让本地智能体能获取远端 channel 列表 + 发送消息到远端
|
|
579
|
+
// (之前本地智能体看不到远端 channel, 无法 @ 远程智能体交流 — "工具没有给到位")
|
|
580
|
+
ctx.tools.set('list_remote_channels', {
|
|
581
|
+
name: 'list_remote_channels',
|
|
582
|
+
description: '列出 P2P 好友节点分享给你的远端 channel (远程智能体会话). 每个远端 channel 属于某个 peer, 你可以在回复中写 "@渠道名 消息内容" 或调用 send_to_remote_channel 给它们发消息.',
|
|
583
|
+
parameters: {},
|
|
584
|
+
execute: async () => {
|
|
585
|
+
try {
|
|
586
|
+
const port = process.env.PORT || '54188';
|
|
587
|
+
const res = await fetch(`http://127.0.0.1:${port}/api/remote-channels`);
|
|
588
|
+
if (!res.ok)
|
|
589
|
+
return { success: false, error: `HTTP ${res.status}` };
|
|
590
|
+
const data = await res.json();
|
|
591
|
+
const peers = data?.peers || [];
|
|
592
|
+
const lines = [];
|
|
593
|
+
let total = 0;
|
|
594
|
+
for (const p of peers) {
|
|
595
|
+
const chs = p.channels || [];
|
|
596
|
+
total += chs.length;
|
|
597
|
+
if (chs.length === 0)
|
|
598
|
+
continue;
|
|
599
|
+
lines.push(`👤 ${p.peerName || ('peer-' + String(p.peerId).substring(0, 8))} (${String(p.peerId).substring(0, 16)}…):`);
|
|
600
|
+
for (const c of chs) {
|
|
601
|
+
lines.push(` - @${c.name} (id=${c.id})`);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
if (total === 0) {
|
|
605
|
+
return { success: true, output: '📭 当前没有远端 channel (没有好友分享 channel 给你, 或对方不在线). 可先用 add_friend_by_id 添加好友.' };
|
|
606
|
+
}
|
|
607
|
+
return { success: true, output: `🌐 ${total} 个远端 channel:\n${lines.join('\n')}\n\n在回复中写 "@渠道名 消息内容" 即可发送 (系统自动转发到对方节点).` };
|
|
608
|
+
}
|
|
609
|
+
catch (e) {
|
|
610
|
+
return { success: false, error: `获取远端 channel 失败: ${String(e.message || e)}` };
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
});
|
|
614
|
+
ctx.tools.set('send_to_remote_channel', {
|
|
615
|
+
name: 'send_to_remote_channel',
|
|
616
|
+
description: '发送消息到远端 channel (远程智能体会话, 属于某个 P2P 好友节点). 对方节点会在该 channel 上跑 LLM 处理你的消息并回复. 用 list_remote_channels 查看可用 channel 和 owner.',
|
|
617
|
+
parameters: {
|
|
618
|
+
targetPublicKey: '远端节点 publicKey (64 hex, 用 list_remote_channels 看 owner)',
|
|
619
|
+
channelId: '远端 channel id (用 list_remote_channels 查看)',
|
|
620
|
+
text: '消息内容 (必填)',
|
|
621
|
+
autoInvokeTools: '可选, 是否允许对方调用工具 (true/false, 默认 true)'
|
|
622
|
+
},
|
|
623
|
+
execute: async (args) => {
|
|
624
|
+
const targetPublicKey = String(args.targetPublicKey || '').trim();
|
|
625
|
+
const channelId = String(args.channelId || '').trim();
|
|
626
|
+
const text = String(args.text || '').trim();
|
|
627
|
+
if (!targetPublicKey || !channelId || !text) {
|
|
628
|
+
return { success: false, error: 'targetPublicKey, channelId, text 必填 (用 list_remote_channels 查)' };
|
|
629
|
+
}
|
|
630
|
+
try {
|
|
631
|
+
const port = process.env.PORT || '54188';
|
|
632
|
+
const res = await fetch(`http://127.0.0.1:${port}/api/remote-channels/chat-send`, {
|
|
633
|
+
method: 'POST',
|
|
634
|
+
headers: { 'Content-Type': 'application/json' },
|
|
635
|
+
body: JSON.stringify({
|
|
636
|
+
targetPublicKey,
|
|
637
|
+
channelId,
|
|
638
|
+
text,
|
|
639
|
+
...(typeof args.autoInvokeTools === 'boolean' ? { autoInvokeTools: args.autoInvokeTools } : {}),
|
|
640
|
+
})
|
|
641
|
+
});
|
|
642
|
+
const data = await res.json();
|
|
643
|
+
if (!res.ok) {
|
|
644
|
+
return { success: false, error: `发送失败: ${data.error || `HTTP ${res.status}`}` };
|
|
645
|
+
}
|
|
646
|
+
return { success: true, output: `📨 消息已发送到远端 channel ${channelId} (${data.sent ? '已送达' : data.queued ? '对方不在线, 已入队, 上线后自动送达' : '未知状态'}). 对方智能体会回复, 可用 check_inbox 或稍后查看.` };
|
|
647
|
+
}
|
|
648
|
+
catch (e) {
|
|
649
|
+
return { success: false, error: `发送失败: ${String(e.message || e)}` };
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
});
|
|
653
|
+
// delegate_to_engine — 把编码任务委派给本机已安装的其他 AI 编码智能体 CLI
|
|
654
|
+
// (codex / claude-code / opencode / openclaw / hermes). 它们必须已安装且可达 PATH.
|
|
655
|
+
// 实验 API 引擎 (experiment:xxx) 是供应商不是 CLI, 不支持委派, 工具会提示改用 import.
|
|
656
|
+
ctx.tools.set('delegate_to_engine', {
|
|
657
|
+
name: 'delegate_to_engine',
|
|
658
|
+
description: '把编码任务委派给本机已安装的其他 AI 编码智能体 (子智能体) 执行: codex / claude-code / opencode / openclaw / hermes. 引擎需已安装且在 PATH 上. 返回其执行输出. 注意: 各工具 CLI 的非交互参数随版本变化, 若报错请检查该工具版本对应 flag.',
|
|
659
|
+
parameters: {
|
|
660
|
+
engine: "引擎 id: codex / claude-code / opencode / openclaw / hermes (实验 API 不支持委派)",
|
|
661
|
+
prompt: '派发的任务描述 (作为单参数传给该引擎 CLI)',
|
|
662
|
+
model: '可选, 强制指定模型 (如 deepseek/deepseek-v4-flash), 需引擎支持',
|
|
663
|
+
cwd: '可选, 工作目录, 默认当前目录'
|
|
664
|
+
},
|
|
665
|
+
execute: async (args) => {
|
|
666
|
+
const engine = String(args.engine || '').trim();
|
|
667
|
+
const prompt = String(args.prompt || '').trim();
|
|
668
|
+
if (!engine)
|
|
669
|
+
return { success: false, error: 'engine 必填' };
|
|
670
|
+
if (!prompt)
|
|
671
|
+
return { success: false, error: 'prompt 必填' };
|
|
672
|
+
const cwd = args.cwd ? String(args.cwd).trim() : undefined;
|
|
673
|
+
const model = args.model ? String(args.model).trim() : undefined;
|
|
674
|
+
// 2026-08-11: 委派句柄 — ownerDid 绑当前 agent DID (防跨 channel 使用),
|
|
675
|
+
// correlationId 幂等去重 (同一 agent 重复 correlation 只应出现一次)
|
|
676
|
+
const ownerDid = ctx.identity?.did || 'unknown';
|
|
677
|
+
const correlationId = `delegate:${Date.now()}:${Math.random().toString(36).slice(2, 10)}`;
|
|
678
|
+
const result = await (0, delegate_js_1.delegateToEngine)(engine, prompt, {
|
|
679
|
+
cwd: cwd || undefined,
|
|
680
|
+
...(model ? { model } : {}),
|
|
681
|
+
ownerDid,
|
|
682
|
+
correlationId,
|
|
683
|
+
});
|
|
684
|
+
if (!result.success) {
|
|
685
|
+
return { success: false, error: result.error, output: result.output };
|
|
686
|
+
}
|
|
687
|
+
const handleLine = result.handle
|
|
688
|
+
? `\n[delegate handle] contractVersion=${result.handle.contractVersion} delegateId=${result.handle.delegateId} ownerDid=${result.handle.ownerDid} correlationId=${result.handle.correlationId ?? '-'} capability=${result.handle.capability.slice(0, 16)}...`
|
|
689
|
+
: '';
|
|
690
|
+
return {
|
|
691
|
+
success: true,
|
|
692
|
+
output: `🤖 ${engine} 执行结果 (exitCode=${result.exitCode}):\n${result.output || '(无输出)'}${handleLine}`
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
});
|
|
696
|
+
ctx.tools.set('get_identity', {
|
|
697
|
+
name: 'get_identity',
|
|
698
|
+
description: '获取当前智能体身份信息',
|
|
699
|
+
parameters: {},
|
|
700
|
+
execute: async () => {
|
|
701
|
+
const id = ctx.identity;
|
|
702
|
+
return {
|
|
703
|
+
success: true,
|
|
704
|
+
output: `DID: ${id.did}\n名称: ${id.name}\n`
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
});
|
|
708
|
+
ctx.tools.set('set_persona', {
|
|
709
|
+
name: 'set_persona',
|
|
710
|
+
description: '更新智能体的 persona 信息',
|
|
711
|
+
parameters: { persona_json: 'Persona JSON 对象' },
|
|
712
|
+
execute: async (args) => {
|
|
713
|
+
try {
|
|
714
|
+
const personaData = typeof args.persona_json === 'string' ? JSON.parse(args.persona_json) : args.persona_json;
|
|
715
|
+
const now = new Date().toISOString();
|
|
716
|
+
const newPersona = {
|
|
717
|
+
name: personaData.name || ctx.identity.name,
|
|
718
|
+
description: personaData.description || '',
|
|
719
|
+
capabilities: personaData.capabilities || [],
|
|
720
|
+
personality: personaData.personality || '',
|
|
721
|
+
greeting: personaData.greeting || '',
|
|
722
|
+
interests: personaData.interests || [],
|
|
723
|
+
createdAt: ctx.persona?.createdAt || now,
|
|
724
|
+
updatedAt: now
|
|
725
|
+
};
|
|
726
|
+
await ctx.setPersona(newPersona);
|
|
727
|
+
return { success: true, output: `Persona 已更新:\n名称: ${newPersona.name}\n描述: ${newPersona.description}` };
|
|
728
|
+
}
|
|
729
|
+
catch (e) {
|
|
730
|
+
return { success: false, error: `更新 persona 失败: ${String(e)}` };
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
});
|
|
734
|
+
ctx.tools.set('get_operation_logs', {
|
|
735
|
+
name: 'get_operation_logs',
|
|
736
|
+
description: '获取约束层的操作日志',
|
|
737
|
+
parameters: {},
|
|
738
|
+
execute: async () => {
|
|
739
|
+
const logs = ctx.constraintLayer.getLogs();
|
|
740
|
+
if (logs.length === 0) {
|
|
741
|
+
return { success: true, output: '暂无操作日志' };
|
|
742
|
+
}
|
|
743
|
+
return {
|
|
744
|
+
success: true,
|
|
745
|
+
output: `操作日志 (${logs.length} 条):\n${logs.slice(-10).map((l) => `[${new Date(l.timestamp).toISOString()}] ${l.action} - ${l.status}`).join('\n')}`
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
});
|
|
749
|
+
// 文件系统工具
|
|
750
|
+
ctx.tools.set('list_files', {
|
|
751
|
+
name: 'list_files',
|
|
752
|
+
description: '列出目录中的文件',
|
|
753
|
+
parameters: { path: '目录路径(可选,默认为当前目录)' },
|
|
754
|
+
execute: async (args) => {
|
|
755
|
+
try {
|
|
756
|
+
const fs = await Promise.resolve().then(() => __importStar(require('fs')));
|
|
757
|
+
const targetPath = args.path || ctx.cwd;
|
|
758
|
+
const files = fs.readdirSync(targetPath);
|
|
759
|
+
return {
|
|
760
|
+
success: true,
|
|
761
|
+
output: `📁 目录 ${targetPath} 中的文件 (${files.length} 个):\n${files.slice(0, 20).map((f) => ` - ${f}`).join('\n')}${files.length > 20 ? '\n ...' : ''}`
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
catch (e) {
|
|
765
|
+
return { success: false, error: String(e) };
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
});
|
|
769
|
+
ctx.tools.set('read_directory', {
|
|
770
|
+
name: 'read_directory',
|
|
771
|
+
description: '读取目录内容,返回文件列表和目录结构',
|
|
772
|
+
parameters: { path: '目录路径(可选,默认为当前目录)' },
|
|
773
|
+
execute: async (args) => {
|
|
774
|
+
try {
|
|
775
|
+
const fs = await Promise.resolve().then(() => __importStar(require('fs')));
|
|
776
|
+
const pathModule = await Promise.resolve().then(() => __importStar(require('path')));
|
|
777
|
+
const targetPath = args.path || ctx.cwd;
|
|
778
|
+
const items = fs.readdirSync(targetPath);
|
|
779
|
+
const result = [];
|
|
780
|
+
for (const item of items.slice(0, 30)) {
|
|
781
|
+
const fullPath = pathModule.join(targetPath, item);
|
|
782
|
+
try {
|
|
783
|
+
const stat = fs.statSync(fullPath);
|
|
784
|
+
const type = stat.isDirectory() ? '📁' : '📄';
|
|
785
|
+
result.push(`${type} ${item}${stat.isDirectory() ? '/' : ''}`);
|
|
786
|
+
}
|
|
787
|
+
catch {
|
|
788
|
+
result.push(`📄 ${item}`);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
return {
|
|
792
|
+
success: true,
|
|
793
|
+
output: `📂 ${targetPath} (${items.length} 项):\n${result.join('\n')}${items.length > 30 ? '\n... 还有更多文件' : ''}`
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
catch (e) {
|
|
797
|
+
return { success: false, error: `无法读取目录: ${String(e)}` };
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
});
|
|
801
|
+
// P2P Document Tools
|
|
802
|
+
for (const tool of p2p_document_tools_js_1.p2pDocumentTools) {
|
|
803
|
+
ctx.tools.set(tool.name, tool);
|
|
804
|
+
}
|
|
805
|
+
// 2026-08-12 (Task2): shell_exec / terminal 统一走模块级 runTerminalCommand (宽松护栏 + 多命令并行).
|
|
806
|
+
// shell_exec — 2026-08-12 (Task2): 与 terminal 统一走宽松护栏 (denylist-only).
|
|
807
|
+
// 兼容旧格式 (command + args 数组), 内部转成完整命令字符串交给 runTerminal,
|
|
808
|
+
// 不再用窄白名单 → 模型不会因 "command 不在白名单" 报错.
|
|
809
|
+
ctx.tools.set('shell_exec', {
|
|
810
|
+
name: 'shell_exec',
|
|
811
|
+
description: '执行 shell 命令 (兼容模式, 参数数组或命令字符串均可). 护栏只挡高危破坏操作 (sudo/格式化/rm -rf 根目录/写 ~/.bolloon 数据). 推荐直接用 terminal 传完整命令字符串.',
|
|
812
|
+
parameters: { command: '可执行文件 或 完整命令 (必填)', args: '参数数组, 逗号分隔 (可选)', timeoutMs: '超时毫秒, 默认 30000' },
|
|
813
|
+
execute: async (args) => {
|
|
814
|
+
const cmd = String(args.command || '').trim();
|
|
815
|
+
if (!cmd)
|
|
816
|
+
return { success: false, error: 'command 必填' };
|
|
817
|
+
// 兼容参数数组 → 拼成命令字符串
|
|
818
|
+
let full = cmd;
|
|
819
|
+
const rawArgs = args.args;
|
|
820
|
+
let argList = [];
|
|
821
|
+
if (Array.isArray(rawArgs)) {
|
|
822
|
+
argList = rawArgs.map((s) => String(s).trim()).filter(Boolean);
|
|
823
|
+
}
|
|
824
|
+
else if (typeof rawArgs === 'string') {
|
|
825
|
+
const trimmed = rawArgs.trim();
|
|
826
|
+
if (trimmed.startsWith('[') && trimmed.endsWith(']')) {
|
|
827
|
+
try {
|
|
828
|
+
const parsed = JSON.parse(trimmed);
|
|
829
|
+
if (Array.isArray(parsed))
|
|
830
|
+
argList = parsed.map((s) => String(s).trim()).filter(Boolean);
|
|
831
|
+
}
|
|
832
|
+
catch { /* 非 JSON 数组 */ }
|
|
833
|
+
}
|
|
834
|
+
if (argList.length === 0)
|
|
835
|
+
argList = trimmed.split(',').map(s => s.trim()).filter(Boolean);
|
|
836
|
+
}
|
|
837
|
+
// 若 command 只是可执行文件名且带 args → 拼成 "cmd arg1 arg2"; 否则原样
|
|
838
|
+
if (argList.length > 0 && !/\s/.test(cmd) && !cmd.includes('&&') && !cmd.includes(';') && !cmd.includes('|')) {
|
|
839
|
+
full = `${cmd} ${argList.map(a => (/[\s"&|<>^()%!`]/.test(a) ? `"${a.replace(/"/g, '""')}"` : a)).join(' ')}`;
|
|
840
|
+
}
|
|
841
|
+
const timeoutMs = Number(args.timeoutMs) || 30000;
|
|
842
|
+
return await runTerminalCommand(full, { timeoutMs, cwd: ctx.cwd });
|
|
843
|
+
}
|
|
844
|
+
});
|
|
845
|
+
// 2026-08-10: terminal — 灵活终端写命令 (用户要求: bolloon 自己写命令进 terminal, 少围栏).
|
|
846
|
+
// 2026-08-12 (Task2): 支持 commands 数组并行执行; 与 shell_exec 统一走 runTerminalCommand.
|
|
847
|
+
// 与 shell_exec 的区别: 直接接受完整 shell 命令字符串, 更适合模型自主写命令.
|
|
848
|
+
ctx.tools.set('terminal', {
|
|
849
|
+
name: 'terminal',
|
|
850
|
+
description: '执行完整 shell 命令 (支持管道/重定向/写文件/跑脚本). 护栏只挡高危破坏操作 (sudo/格式化/rm -rf 根目录/写 ~/.bolloon 数据), 其余灵活放行. 适合: 写 HTML 文件、跑 python/node 脚本、查系统状态、装依赖. 多条命令用 commands 数组并行执行. 长命令 (服务器/构建/后台任务) 设 background=true 后台执行不阻塞对话. 也可直接传 code+language 自动写脚本执行 (便捷代码运行: python/js/ts/shell/html).',
|
|
851
|
+
parameters: { command: '完整 shell 命令 (可选, 如: echo "<html>" > /tmp/site/index.html && ls /tmp/site)', commands: '可选: 多条命令数组 (并行执行), 每条独立字符串', code: '可选: 一段代码, 传 code+language 时自动写脚本执行 (便捷代码运行)', language: '可选: code 的语言 (python/js/ts/shell/html), 默认自动', timeoutMs: '超时毫秒, 默认 30000', background: '可选: true 后台执行, 立即返回 session_id (用 process 工具 poll/wait/kill)' },
|
|
852
|
+
execute: async (args) => {
|
|
853
|
+
const timeoutMs = Number(args.timeoutMs) || 30000;
|
|
854
|
+
// 便捷代码运行: 传 code → 自动写脚本执行
|
|
855
|
+
const code = String(args.code ?? '').trim();
|
|
856
|
+
if (code) {
|
|
857
|
+
const lang = String(args.language || '').trim().toLowerCase();
|
|
858
|
+
return await runCodeSnippet({ code, language: lang, timeoutMs, cwd: ctx.cwd });
|
|
859
|
+
}
|
|
860
|
+
const raw = String(args.command || '').trim();
|
|
861
|
+
const commands = Array.isArray(args.commands) ? args.commands.map((c) => String(c || '').trim()).filter(Boolean) : [];
|
|
862
|
+
if (!raw && commands.length === 0)
|
|
863
|
+
return { success: false, error: 'command/code/commands 至少一个必填' };
|
|
864
|
+
return await runTerminalCommand(raw, { timeoutMs, cwd: ctx.cwd, commands: commands.length > 0 ? commands : undefined, background: String(args.background).toLowerCase() === 'true' });
|
|
865
|
+
}
|
|
866
|
+
});
|
|
867
|
+
// 2026-08-12 (TaskD): process — 后台进程管理 (学 hermes terminal background session + poll/wait/kill).
|
|
868
|
+
// 长命令不阻塞对话: terminal(background=true) 启动 → process 工具轮询/等待/终止.
|
|
869
|
+
ctx.tools.set('process', {
|
|
870
|
+
name: 'process',
|
|
871
|
+
description: '管理后台进程 (terminal background=true 启动的). action: poll(查状态, 不阻塞) / wait(等结束, 最多 timeoutMs) / kill(终止) / list(列全部). 长期运行命令的阻塞问题用它解决.',
|
|
872
|
+
parameters: { session_id: '后台进程 session_id (必填, poll/wait/kill 用)', action: 'poll | wait | kill | list (默认 poll)', timeoutMs: 'wait 模式等待上限毫秒, 默认 30000' },
|
|
873
|
+
execute: async (args) => {
|
|
874
|
+
const action = String(args.action || 'poll').trim().toLowerCase();
|
|
875
|
+
try {
|
|
876
|
+
const { pollSession, waitSession, killSession, listSessions, isValidSessionId } = await Promise.resolve().then(() => __importStar(require('./process-runner.js')));
|
|
877
|
+
if (action === 'list') {
|
|
878
|
+
const all = listSessions();
|
|
879
|
+
return { success: true, output: all.length === 0 ? '(无后台进程)' : all.map((s) => ` [${s.status}] ${s.id} ${s.cmd}`).join('\n') };
|
|
880
|
+
}
|
|
881
|
+
const sid = String(args.session_id || '').trim();
|
|
882
|
+
if (!sid)
|
|
883
|
+
return { success: false, error: 'session_id 必填' };
|
|
884
|
+
if (!isValidSessionId(sid))
|
|
885
|
+
return { success: false, error: 'session_id 非法' };
|
|
886
|
+
if (action === 'kill') {
|
|
887
|
+
const r = killSession(sid);
|
|
888
|
+
return { success: r.ok, output: r.reason ?? `已终止 ${sid}` };
|
|
889
|
+
}
|
|
890
|
+
if (action === 'wait') {
|
|
891
|
+
const t = Number(args.timeoutMs) || 30000;
|
|
892
|
+
const r = await waitSession(sid, t);
|
|
893
|
+
return { success: r.ok, output: r.session ? `${r.session.status} exit=${r.session.exitCode}${r.session.timedOut ? ' (超时)' : ''}\n${r.session.output || '(无输出)'}` : '未知 session' };
|
|
894
|
+
}
|
|
895
|
+
// poll
|
|
896
|
+
const r = pollSession(sid);
|
|
897
|
+
if (!r.ok || !r.session)
|
|
898
|
+
return { success: false, error: `未知 session ${sid}` };
|
|
899
|
+
const s = r.session;
|
|
900
|
+
return { success: true, output: `[${s.status}] exit=${s.exitCode}${s.status === 'running' ? ' (运行中)' : ''}\n${s.output || '(无输出)'}` };
|
|
901
|
+
}
|
|
902
|
+
catch (e) {
|
|
903
|
+
return { success: false, error: `process ${action} 失败: ${String(e?.message || e).slice(0, 200)}` };
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
});
|
|
907
|
+
// self_improve
|
|
908
|
+
ctx.tools.set('self_improve', {
|
|
909
|
+
name: 'self_improve',
|
|
910
|
+
description: '触发自我改进循环. AI 会在沙箱分支上工作, 跑 tsc + vitest 验证, 通过后输出分支名给用户审.',
|
|
911
|
+
parameters: { goal: '本轮改进目标 (1 句话)' },
|
|
912
|
+
execute: async (args) => {
|
|
913
|
+
const goal = String(args.goal || '').trim();
|
|
914
|
+
if (!goal)
|
|
915
|
+
return { success: false, error: 'goal 必填' };
|
|
916
|
+
return await (0, pi_sdk_session_factory_js_1.runSelfImproveLoop)(goal);
|
|
917
|
+
}
|
|
918
|
+
});
|
|
919
|
+
// list_skills / use_skill — 走 skillRegistry
|
|
920
|
+
// (由 registerBuiltinTools 调用方单独注册, 因为依赖 skillRegistry 实例)
|
|
921
|
+
// write_file / edit_file / git_*
|
|
922
|
+
ctx.tools.set('write_file', {
|
|
923
|
+
name: 'write_file',
|
|
924
|
+
description: '写入一个文件. 路径必须在白名单. 大文件 (> 100KB) 会被拒. 命中护栏黑名单会拒.',
|
|
925
|
+
parameters: { path: '相对路径 (必填, 相对 cwd)', content: '文件内容 (必填)' },
|
|
926
|
+
execute: async (args) => {
|
|
927
|
+
const relPath = String(args.path || '').trim();
|
|
928
|
+
const content = String(args.content ?? '');
|
|
929
|
+
if (!relPath)
|
|
930
|
+
return { success: false, error: 'path 必填' };
|
|
931
|
+
if (content.length > 100_000)
|
|
932
|
+
return { success: false, error: `内容过大 (${content.length} > 100000 字节), 请分块写` };
|
|
933
|
+
const pathResult = (0, shell_guard_js_1.checkWritePath)(relPath);
|
|
934
|
+
if (!pathResult.allowed) {
|
|
935
|
+
return { success: false, error: `路径被护栏拒: ${pathResult.reason}` };
|
|
936
|
+
}
|
|
937
|
+
try {
|
|
938
|
+
const absPath = path.resolve(ctx.cwd, relPath);
|
|
939
|
+
// 2026-08-12 (TaskC): 写前暂存 (准备阶段) — 记录变更前快照, 支持审计/撤销. 失败静默.
|
|
940
|
+
let before = '';
|
|
941
|
+
try {
|
|
942
|
+
before = await fs.readFile(absPath, 'utf-8');
|
|
943
|
+
}
|
|
944
|
+
catch { /* 新文件 */ }
|
|
945
|
+
const action = before.length > 0 ? 'overwrite' : 'create';
|
|
946
|
+
const { stageWrite } = await Promise.resolve().then(() => __importStar(require('./write-staging.js')));
|
|
947
|
+
await stageWrite(relPath, before, content, action, ctx.cwd).catch(() => { });
|
|
948
|
+
await fs.mkdir(path.dirname(absPath), { recursive: true });
|
|
949
|
+
await fs.writeFile(absPath, content, 'utf-8');
|
|
950
|
+
return { success: true, output: `✅ wrote ${relPath} (${content.length} bytes)` };
|
|
951
|
+
}
|
|
952
|
+
catch (e) {
|
|
953
|
+
return { success: false, error: `写文件失败: ${String(e)}` };
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
});
|
|
957
|
+
ctx.tools.set('edit_file', {
|
|
958
|
+
name: 'edit_file',
|
|
959
|
+
description: '编辑一个文件: 在 path 处查找 old_text, 替换为 new_text. 找不到 old_text 会失败.',
|
|
960
|
+
parameters: { path: '相对路径 (必填)', old_text: '要替换的文本 (必填, 全文匹配)', new_text: '新文本 (必填)' },
|
|
961
|
+
execute: async (args) => {
|
|
962
|
+
const relPath = String(args.path || '').trim();
|
|
963
|
+
const oldText = String(args.old_text ?? '');
|
|
964
|
+
const newText = String(args.new_text ?? '');
|
|
965
|
+
if (!relPath)
|
|
966
|
+
return { success: false, error: 'path 必填' };
|
|
967
|
+
if (!oldText)
|
|
968
|
+
return { success: false, error: 'old_text 必填' };
|
|
969
|
+
const pathResult = (0, shell_guard_js_1.checkWritePath)(relPath);
|
|
970
|
+
if (!pathResult.allowed) {
|
|
971
|
+
return { success: false, error: `路径被护栏拒: ${pathResult.reason}` };
|
|
972
|
+
}
|
|
973
|
+
try {
|
|
974
|
+
const absPath = path.resolve(ctx.cwd, relPath);
|
|
975
|
+
const original = await fs.readFile(absPath, 'utf-8');
|
|
976
|
+
if (!original.includes(oldText)) {
|
|
977
|
+
return { success: false, error: `old_text 在 ${relPath} 中未找到, 拒绝静默写入. 请先用 read_document 读最新内容.` };
|
|
978
|
+
}
|
|
979
|
+
const updated = original.replace(oldText, newText);
|
|
980
|
+
// 2026-08-12 (TaskC): 写前暂存 (准备阶段) — 记录变更前快照, 支持审计/撤销. 失败静默.
|
|
981
|
+
const { stageWrite } = await Promise.resolve().then(() => __importStar(require('./write-staging.js')));
|
|
982
|
+
await stageWrite(relPath, original, updated, 'edit', ctx.cwd).catch(() => { });
|
|
983
|
+
await fs.writeFile(absPath, updated, 'utf-8');
|
|
984
|
+
return { success: true, output: `✅ edited ${relPath} (${oldText.length} → ${newText.length} 字节)` };
|
|
985
|
+
}
|
|
986
|
+
catch (e) {
|
|
987
|
+
return { success: false, error: `编辑文件失败: ${String(e)}` };
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
});
|
|
991
|
+
ctx.tools.set('git_diff', {
|
|
992
|
+
name: 'git_diff',
|
|
993
|
+
description: '查看 git diff. 默认显示未提交改动 (staged + unstaged), 可指定 ref1..ref2 看两个 commit/分支之间的 diff. 输出会截到 8000 字符避免超长.',
|
|
994
|
+
parameters: { range: '可选. e.g. "HEAD~3..HEAD". 省略则看未提交改动.' },
|
|
995
|
+
execute: async (args) => {
|
|
996
|
+
const range = String(args.range || '').trim();
|
|
997
|
+
const argv = range ? ['diff', range] : ['diff'];
|
|
998
|
+
const result = await (0, shell_tool_js_1.shellExec)('git', argv, { timeoutMs: 10_000 });
|
|
999
|
+
if (result.deniedByGuard)
|
|
1000
|
+
return { success: false, error: result.error };
|
|
1001
|
+
if (!result.success)
|
|
1002
|
+
return { success: false, error: result.error, output: result.output };
|
|
1003
|
+
const out = (result.output || '').slice(0, 8000);
|
|
1004
|
+
return { success: true, output: out || '(空 diff — 没有未提交改动)' };
|
|
1005
|
+
}
|
|
1006
|
+
});
|
|
1007
|
+
ctx.tools.set('git_commit', {
|
|
1008
|
+
name: 'git_commit',
|
|
1009
|
+
description: 'git add -A + git commit. 提交信息由 LLM 提供. 不会 push.',
|
|
1010
|
+
parameters: { message: 'commit message (必填)' },
|
|
1011
|
+
execute: async (args) => {
|
|
1012
|
+
const message = String(args.message || '').trim();
|
|
1013
|
+
if (!message)
|
|
1014
|
+
return { success: false, error: 'message 必填' };
|
|
1015
|
+
const addResult = await (0, shell_tool_js_1.shellExec)('git', ['add', '-A'], { timeoutMs: 10_000 });
|
|
1016
|
+
if (addResult.deniedByGuard)
|
|
1017
|
+
return { success: false, error: addResult.error };
|
|
1018
|
+
if (!addResult.success)
|
|
1019
|
+
return { success: false, error: `git add 失败: ${addResult.error}` };
|
|
1020
|
+
try {
|
|
1021
|
+
const { spawn: spawnFn } = await Promise.resolve().then(() => __importStar(require('child_process')));
|
|
1022
|
+
const env = { ...process.env, BOLLOON_AUTO_EVOLVE: '1' };
|
|
1023
|
+
const output = await new Promise((resolve, reject) => {
|
|
1024
|
+
const proc = spawnFn('git', ['commit', '-m', message], {
|
|
1025
|
+
cwd: ctx.cwd, env, stdio: ['ignore', 'pipe', 'pipe'],
|
|
1026
|
+
});
|
|
1027
|
+
let stdout = '';
|
|
1028
|
+
let stderr = '';
|
|
1029
|
+
proc.stdout.on('data', (d) => stdout += d.toString());
|
|
1030
|
+
proc.stderr.on('data', (d) => stderr += d.toString());
|
|
1031
|
+
proc.on('close', (code) => code === 0 ? resolve(stdout) : reject(new Error(stderr || `git commit exited ${code}`)));
|
|
1032
|
+
proc.on('error', reject);
|
|
1033
|
+
});
|
|
1034
|
+
return { success: true, output: `✅ committed: ${message.split('\n')[0]}\n${output}` };
|
|
1035
|
+
}
|
|
1036
|
+
catch (e) {
|
|
1037
|
+
return { success: false, error: `git commit 失败: ${String(e.message || e).slice(0, 500)}` };
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
});
|
|
1041
|
+
ctx.tools.set('git_push', {
|
|
1042
|
+
name: 'git_push',
|
|
1043
|
+
description: 'git push 当前分支到 origin. 命中护栏 (push to master/main, --force) 仍会被拒.',
|
|
1044
|
+
parameters: { remote: '可选, 默认 origin', branch: '可选, 默认当前分支' },
|
|
1045
|
+
execute: async (args) => {
|
|
1046
|
+
const remote = String(args.remote || 'origin').trim();
|
|
1047
|
+
const branch = String(args.branch || '').trim();
|
|
1048
|
+
const argv = branch ? ['push', remote, branch] : ['push', remote];
|
|
1049
|
+
const result = await (0, shell_tool_js_1.shellExec)('git', argv, { timeoutMs: 60_000 });
|
|
1050
|
+
if (result.deniedByGuard)
|
|
1051
|
+
return { success: false, error: result.error };
|
|
1052
|
+
if (!result.success)
|
|
1053
|
+
return { success: false, error: result.error, output: result.output };
|
|
1054
|
+
return { success: true, output: `✅ pushed to ${remote}${branch ? `/${branch}` : ''}\n${result.output || ''}` };
|
|
1055
|
+
}
|
|
1056
|
+
});
|
|
1057
|
+
ctx.tools.set('git_branch', {
|
|
1058
|
+
name: 'git_branch',
|
|
1059
|
+
description: 'git checkout -b <name> 或 git checkout <name>.',
|
|
1060
|
+
parameters: { name: '分支名 (必填)', create: '可选, "true" 表示创建新分支' },
|
|
1061
|
+
execute: async (args) => {
|
|
1062
|
+
const name = String(args.name || '').trim();
|
|
1063
|
+
const create = String(args.create || 'false') === 'true';
|
|
1064
|
+
if (!name)
|
|
1065
|
+
return { success: false, error: 'name 必填' };
|
|
1066
|
+
const argv = create ? ['checkout', '-b', name] : ['checkout', name];
|
|
1067
|
+
const result = await (0, shell_tool_js_1.shellExec)('git', argv, { timeoutMs: 10_000 });
|
|
1068
|
+
if (result.deniedByGuard)
|
|
1069
|
+
return { success: false, error: result.error };
|
|
1070
|
+
if (!result.success)
|
|
1071
|
+
return { success: false, error: result.error, output: result.output };
|
|
1072
|
+
return { success: true, output: `✅ ${create ? 'created + checked out' : 'checked out'} ${name}\n${result.output || ''}` };
|
|
1073
|
+
}
|
|
1074
|
+
});
|
|
1075
|
+
// 通用文件读取 (M4)
|
|
1076
|
+
ctx.tools.set('read_file', {
|
|
1077
|
+
name: 'read_file',
|
|
1078
|
+
description: '读取任意文件内容 (相对 cwd); 也支持 http(s) URL (如 "read https://…/doc.md" 这种口令 → 直接给 URL 即可). 只读操作, 无白名单限制.',
|
|
1079
|
+
parameters: { path: '相对路径或 http(s) URL (必填)', startLine: '起始行号 (可选, 默认 0)', maxLines: '最大行数 (可选, 默认 500)' },
|
|
1080
|
+
execute: async (args) => {
|
|
1081
|
+
const relPath = String(args.path || '').trim();
|
|
1082
|
+
if (!relPath)
|
|
1083
|
+
return { success: false, error: 'path 必填' };
|
|
1084
|
+
// 2026-09-15: URL 直读 — 人类口令 "read <url>" 落到的就是本工具, 之前会 ENOENT (当成相对路径)
|
|
1085
|
+
if (/^https?:\/\//i.test(relPath)) {
|
|
1086
|
+
const r = await readUrlAsText(relPath);
|
|
1087
|
+
if (!r.ok)
|
|
1088
|
+
return { success: false, error: `读取 URL 失败: ${r.error}` };
|
|
1089
|
+
const start = Math.max(0, parseInt(String(args.startLine || '0')) || 0);
|
|
1090
|
+
const max = parseInt(String(args.maxLines || '500')) || 500;
|
|
1091
|
+
const lines = String(r.text || '').split('\n');
|
|
1092
|
+
const slice = lines.slice(start, start + max);
|
|
1093
|
+
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')}` };
|
|
1094
|
+
}
|
|
1095
|
+
try {
|
|
1096
|
+
const absPath = path.resolve(ctx.cwd, relPath);
|
|
1097
|
+
const content = fsSync.readFileSync(absPath, 'utf-8');
|
|
1098
|
+
const start = Math.max(0, parseInt(String(args.startLine || '0')) || 0);
|
|
1099
|
+
const max = parseInt(String(args.maxLines || '500')) || 500;
|
|
1100
|
+
const lines = content.split('\n');
|
|
1101
|
+
const slice = lines.slice(start, start + max);
|
|
1102
|
+
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')}` };
|
|
1103
|
+
}
|
|
1104
|
+
catch (e) {
|
|
1105
|
+
return { success: false, error: `读取失败: ${e.message}` };
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
});
|
|
1109
|
+
ctx.tools.set('delete_file', {
|
|
1110
|
+
name: 'delete_file',
|
|
1111
|
+
description: '删除一个文件. 受 shell-guard 路径白名单保护. 不可恢复, 调用前请确认.',
|
|
1112
|
+
parameters: { path: '相对路径 (必填)' },
|
|
1113
|
+
execute: async (args) => {
|
|
1114
|
+
const relPath = String(args.path || '').trim();
|
|
1115
|
+
if (!relPath)
|
|
1116
|
+
return { success: false, error: 'path 必填' };
|
|
1117
|
+
const pathResult = (0, shell_guard_js_1.checkWritePath)(relPath);
|
|
1118
|
+
if (!pathResult.allowed)
|
|
1119
|
+
return { success: false, error: `路径被护栏拒: ${pathResult.reason}` };
|
|
1120
|
+
try {
|
|
1121
|
+
const absPath = path.resolve(ctx.cwd, relPath);
|
|
1122
|
+
if (!fsSync.existsSync(absPath))
|
|
1123
|
+
return { success: false, error: `文件不存在: ${relPath}` };
|
|
1124
|
+
fsSync.unlinkSync(absPath);
|
|
1125
|
+
return { success: true, output: `✅ deleted ${relPath}` };
|
|
1126
|
+
}
|
|
1127
|
+
catch (e) {
|
|
1128
|
+
return { success: false, error: `删除失败: ${e.message}` };
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
});
|
|
1132
|
+
ctx.tools.set('mkdir', {
|
|
1133
|
+
name: 'mkdir',
|
|
1134
|
+
description: '创建一个或多个目录. 自动 mkdir -p.',
|
|
1135
|
+
parameters: { path: '目录路径 (必填, 相对 cwd)' },
|
|
1136
|
+
execute: async (args) => {
|
|
1137
|
+
const relPath = String(args.path || '').trim();
|
|
1138
|
+
if (!relPath)
|
|
1139
|
+
return { success: false, error: 'path 必填' };
|
|
1140
|
+
const pathResult = (0, shell_guard_js_1.checkWritePath)(relPath);
|
|
1141
|
+
if (!pathResult.allowed)
|
|
1142
|
+
return { success: false, error: `路径被护栏拒: ${pathResult.reason}` };
|
|
1143
|
+
try {
|
|
1144
|
+
const absPath = path.resolve(ctx.cwd, relPath);
|
|
1145
|
+
fsSync.mkdirSync(absPath, { recursive: true });
|
|
1146
|
+
return { success: true, output: `✅ mkdir ${relPath}` };
|
|
1147
|
+
}
|
|
1148
|
+
catch (e) {
|
|
1149
|
+
return { success: false, error: `创建失败: ${e.message}` };
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
});
|
|
1153
|
+
ctx.tools.set('move_file', {
|
|
1154
|
+
name: 'move_file',
|
|
1155
|
+
description: '移动或重命名文件. 源和目标路径都在白名单内才允许.',
|
|
1156
|
+
parameters: { from: '源路径 (必填)', to: '目标路径 (必填)' },
|
|
1157
|
+
execute: async (args) => {
|
|
1158
|
+
const from = String(args.from || '').trim();
|
|
1159
|
+
const to = String(args.to || '').trim();
|
|
1160
|
+
if (!from || !to)
|
|
1161
|
+
return { success: false, error: 'from 和 to 都必填' };
|
|
1162
|
+
const fromCheck = (0, shell_guard_js_1.checkWritePath)(from);
|
|
1163
|
+
if (!fromCheck.allowed)
|
|
1164
|
+
return { success: false, error: `from 路径被护栏拒: ${fromCheck.reason}` };
|
|
1165
|
+
const toCheck = (0, shell_guard_js_1.checkWritePath)(to);
|
|
1166
|
+
if (!toCheck.allowed)
|
|
1167
|
+
return { success: false, error: `to 路径被护栏拒: ${toCheck.reason}` };
|
|
1168
|
+
try {
|
|
1169
|
+
const fromAbs = path.resolve(ctx.cwd, from);
|
|
1170
|
+
const toAbs = path.resolve(ctx.cwd, to);
|
|
1171
|
+
if (!fsSync.existsSync(fromAbs))
|
|
1172
|
+
return { success: false, error: `源文件不存在: ${from}` };
|
|
1173
|
+
fsSync.mkdirSync(path.dirname(toAbs), { recursive: true });
|
|
1174
|
+
fsSync.renameSync(fromAbs, toAbs);
|
|
1175
|
+
return { success: true, output: `✅ ${from} → ${to}` };
|
|
1176
|
+
}
|
|
1177
|
+
catch (e) {
|
|
1178
|
+
return { success: false, error: `移动失败: ${e.message}` };
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
});
|
|
1182
|
+
ctx.tools.set('grep_files', {
|
|
1183
|
+
name: 'grep_files',
|
|
1184
|
+
description: '在文件中搜索匹配 pattern 的行. 类似 grep -rn. 只读操作, 无白名单限制.',
|
|
1185
|
+
parameters: { pattern: '搜索 pattern (必填, 字符串, 不是正则)', path: '搜索目录 (可选, 默认 .)', filePattern: '文件名 glob (可选)' },
|
|
1186
|
+
execute: async (args) => {
|
|
1187
|
+
const pattern = String(args.pattern || '').trim();
|
|
1188
|
+
if (!pattern)
|
|
1189
|
+
return { success: false, error: 'pattern 必填' };
|
|
1190
|
+
const searchPath = String(args.path || '.').trim();
|
|
1191
|
+
try {
|
|
1192
|
+
const { execFile } = await Promise.resolve().then(() => __importStar(require('child_process')));
|
|
1193
|
+
const { promisify } = await Promise.resolve().then(() => __importStar(require('util')));
|
|
1194
|
+
const pExecFile = promisify(execFile);
|
|
1195
|
+
const argv = ['-rn', '--include=' + (args.filePattern || '*'), pattern, searchPath];
|
|
1196
|
+
const { stdout, stderr } = await pExecFile('grep', argv, { cwd: ctx.cwd, maxBuffer: 1024 * 1024 });
|
|
1197
|
+
const lines = stdout.split('\n').filter(Boolean).slice(0, 50);
|
|
1198
|
+
return { success: true, output: `🔍 grep "${pattern}" in ${searchPath} (${args.filePattern || '*'}, 最多 50 行):\n${lines.join('\n')}${lines.length === 50 ? '\n... (truncated)' : ''}` };
|
|
1199
|
+
}
|
|
1200
|
+
catch (e) {
|
|
1201
|
+
if (e.code === 1)
|
|
1202
|
+
return { success: true, output: `🔍 grep "${pattern}" in ${searchPath}: 0 matches` };
|
|
1203
|
+
return { success: false, error: `grep 失败: ${e.message}` };
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
});
|
|
1207
|
+
ctx.tools.set('glob_files', {
|
|
1208
|
+
name: 'glob_files',
|
|
1209
|
+
description: '用 glob pattern 找文件. 例如 "**/*.test.ts". 只读操作, 无白名单限制.',
|
|
1210
|
+
parameters: { pattern: 'glob pattern (必填, e.g. "src/**/*.ts")' },
|
|
1211
|
+
execute: async (args) => {
|
|
1212
|
+
const pattern = String(args.pattern || '').trim();
|
|
1213
|
+
if (!pattern)
|
|
1214
|
+
return { success: false, error: 'pattern 必填' };
|
|
1215
|
+
try {
|
|
1216
|
+
const { execFile } = await Promise.resolve().then(() => __importStar(require('child_process')));
|
|
1217
|
+
const { promisify } = await Promise.resolve().then(() => __importStar(require('util')));
|
|
1218
|
+
const pExecFile = promisify(execFile);
|
|
1219
|
+
const { stdout } = await pExecFile('find', [ctx.cwd, '-path', `*${pattern.replace(/\*\*\//, '*').replace(/\*\*$/, '*').replace(/\*$/, '*')}`, '-type', 'f'], { maxBuffer: 1024 * 1024 });
|
|
1220
|
+
const files = stdout.split('\n').filter(Boolean).slice(0, 100);
|
|
1221
|
+
const relFiles = files.map((f) => path.relative(ctx.cwd, f));
|
|
1222
|
+
return { success: true, output: `🔍 glob "${pattern}" 找到 ${relFiles.length} 个文件${relFiles.length === 100 ? ' (truncated)' : ''}:\n${relFiles.join('\n')}` };
|
|
1223
|
+
}
|
|
1224
|
+
catch (e) {
|
|
1225
|
+
return { success: false, error: `glob 失败: ${e.message}` };
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
});
|
|
1229
|
+
// ============================================================
|
|
1230
|
+
// Web 上网工具 (2026-08-04) — fetch_url + web_search
|
|
1231
|
+
// 用 undici request (独立连接池, 与 pi-ai 一致, 避开全局 fetch 僵尸连接问题)
|
|
1232
|
+
// ============================================================
|
|
1233
|
+
/** 读一个 http(s) URL 的正文 (curl, 兼容 TLS 指纹风控). read_file 的 URL 分支复用. 函数声明提升, 上面可调用. */
|
|
1234
|
+
async function readUrlAsText(url, maxChars = 60_000) {
|
|
1235
|
+
try {
|
|
1236
|
+
const { execFile } = await Promise.resolve().then(() => __importStar(require('child_process')));
|
|
1237
|
+
const { promisify } = await Promise.resolve().then(() => __importStar(require('util')));
|
|
1238
|
+
const pExecFile = promisify(execFile);
|
|
1239
|
+
const { stdout, stderr } = await pExecFile('curl', [
|
|
1240
|
+
'-sL', '--max-time', '25',
|
|
1241
|
+
'-A', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36',
|
|
1242
|
+
url,
|
|
1243
|
+
], { maxBuffer: 4 * 1024 * 1024, timeout: 30_000 });
|
|
1244
|
+
if (!stdout)
|
|
1245
|
+
return { ok: false, error: String(stderr || '空响应').slice(0, 200) };
|
|
1246
|
+
return { ok: true, text: String(stdout).slice(0, maxChars) };
|
|
1247
|
+
}
|
|
1248
|
+
catch (e) {
|
|
1249
|
+
return { ok: false, error: String(e?.message || e).slice(0, 200) };
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
ctx.tools.set('fetch_url', {
|
|
1253
|
+
name: 'fetch_url',
|
|
1254
|
+
description: '抓取一个 URL 的网页内容并转成纯文本. 适合查文档/新闻/API 页面. 返回前 4000 字符. HTML 自动去标签, JSON/文本原样返回. (走 curl, 兼容 TLS 指纹风控)',
|
|
1255
|
+
parameters: { url: '完整 URL (必填, 含 https://)' },
|
|
1256
|
+
execute: async (args) => {
|
|
1257
|
+
try {
|
|
1258
|
+
const url = String(args.url || '').trim();
|
|
1259
|
+
if (!url)
|
|
1260
|
+
return { success: false, error: 'url 必填' };
|
|
1261
|
+
if (!/^https?:\/\//i.test(url))
|
|
1262
|
+
return { success: false, error: 'url 必须以 http(s):// 开头' };
|
|
1263
|
+
const { execFile } = await Promise.resolve().then(() => __importStar(require('child_process')));
|
|
1264
|
+
const { promisify } = await Promise.resolve().then(() => __importStar(require('util')));
|
|
1265
|
+
const pExecFile = promisify(execFile);
|
|
1266
|
+
const { stdout, stderr } = await pExecFile('curl', [
|
|
1267
|
+
'-sL', '--max-time', '25',
|
|
1268
|
+
'-A', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36',
|
|
1269
|
+
'-H', 'Accept-Language: zh-CN,zh;q=0.9,en;q=0.8',
|
|
1270
|
+
url,
|
|
1271
|
+
], { maxBuffer: 2 * 1024 * 1024, timeout: 30_000 });
|
|
1272
|
+
if (!stdout && stderr)
|
|
1273
|
+
return { success: false, error: `fetch_url 失败: ${String(stderr).slice(0, 200)}` };
|
|
1274
|
+
const raw = stdout.slice(0, 60_000);
|
|
1275
|
+
const trimmed = raw.trimStart();
|
|
1276
|
+
let text;
|
|
1277
|
+
if (trimmed.startsWith('<!doctype') || trimmed.startsWith('<html') || /^<\?xml/i.test(trimmed)) {
|
|
1278
|
+
text = raw
|
|
1279
|
+
.replace(/<script[\s\S]*?<\/script>/gi, ' ')
|
|
1280
|
+
.replace(/<style[\s\S]*?<\/style>/gi, ' ')
|
|
1281
|
+
.replace(/<[^>]+>/g, ' ')
|
|
1282
|
+
.replace(/ /g, ' ').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, "'")
|
|
1283
|
+
.replace(/\s+/g, ' ')
|
|
1284
|
+
.trim();
|
|
1285
|
+
}
|
|
1286
|
+
else {
|
|
1287
|
+
text = raw;
|
|
1288
|
+
}
|
|
1289
|
+
const out = text.slice(0, 4000);
|
|
1290
|
+
return { success: true, output: `🌐 ${url} (${raw.length} bytes):\n${out}${text.length > 4000 ? '\n...(截断, 共 ' + text.length + ' 字符)' : ''}` };
|
|
1291
|
+
}
|
|
1292
|
+
catch (e) {
|
|
1293
|
+
return { success: false, error: `fetch_url 失败: ${String(e?.message || e).slice(0, 200)}` };
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
});
|
|
1297
|
+
ctx.tools.set('web_search', {
|
|
1298
|
+
name: 'web_search',
|
|
1299
|
+
description: '网页搜索. 无 key 时走 DuckDuckGo Instant Answer API + Wikipedia (知识/资讯查询可靠); 配置 TAVILY_API_KEY 时走 Tavily 完整搜索 (更全). 返回前 8 条标题+URL+摘要.',
|
|
1300
|
+
parameters: { query: '搜索词 (必填)' },
|
|
1301
|
+
execute: async (args) => {
|
|
1302
|
+
try {
|
|
1303
|
+
const query = String(args.query || '').trim();
|
|
1304
|
+
if (!query)
|
|
1305
|
+
return { success: false, error: 'query 必填' };
|
|
1306
|
+
const { execFile } = await Promise.resolve().then(() => __importStar(require('child_process')));
|
|
1307
|
+
const { promisify } = await Promise.resolve().then(() => __importStar(require('util')));
|
|
1308
|
+
const pExecFile = promisify(execFile);
|
|
1309
|
+
const UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36';
|
|
1310
|
+
const curlJson = async (url) => {
|
|
1311
|
+
const r = await pExecFile('curl', ['-s', '--max-time', '15', '-A', UA, url], { maxBuffer: 4 * 1024 * 1024, timeout: 20_000 });
|
|
1312
|
+
return JSON.parse(r.stdout);
|
|
1313
|
+
};
|
|
1314
|
+
// 引擎 0: Tavily (配了 TAVILY_API_KEY 时最完整)
|
|
1315
|
+
const tavilyKey = process.env.TAVILY_API_KEY || '';
|
|
1316
|
+
if (tavilyKey) {
|
|
1317
|
+
const r = await pExecFile('curl', [
|
|
1318
|
+
'-s', '--max-time', '20', '-A', UA,
|
|
1319
|
+
'-X', 'POST', 'https://api.tavily.com/search',
|
|
1320
|
+
'-H', 'Content-Type: application/json',
|
|
1321
|
+
'-d', JSON.stringify({ api_key: tavilyKey, query, max_results: 8, include_answer: true }),
|
|
1322
|
+
], { maxBuffer: 4 * 1024 * 1024, timeout: 25_000 });
|
|
1323
|
+
const d = JSON.parse(r.stdout);
|
|
1324
|
+
const results = d.results || [];
|
|
1325
|
+
if (results.length > 0) {
|
|
1326
|
+
const lines = results.slice(0, 8).map((x, i) => `${i + 1}. ${x.title}\n ${x.url}${x.content ? '\n ' + String(x.content).replace(/\s+/g, ' ').slice(0, 150) : ''}`);
|
|
1327
|
+
const answer = d.answer ? `\n📌 ${d.answer}\n` : '';
|
|
1328
|
+
return { success: true, output: `🔎 "${query}" 结果 ${results.length} 条 (Tavily):${answer}\n\n${lines.join('\n\n')}` };
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
// 引擎 1: DuckDuckGo Instant Answer API (摘要 + 相关主题)
|
|
1332
|
+
try {
|
|
1333
|
+
const ia = await curlJson(`https://api.duckduckgo.com/?q=${encodeURIComponent(query)}&format=json&no_html=1`);
|
|
1334
|
+
const out = [];
|
|
1335
|
+
if (ia.AbstractText && ia.AbstractURL) {
|
|
1336
|
+
out.push(`📖 ${ia.AbstractText.slice(0, 400)}\n ${ia.AbstractURL}`);
|
|
1337
|
+
}
|
|
1338
|
+
const topics = Array.isArray(ia.RelatedTopics) ? ia.RelatedTopics : [];
|
|
1339
|
+
for (const t of topics) {
|
|
1340
|
+
if (out.length >= 8)
|
|
1341
|
+
break;
|
|
1342
|
+
if (t.Topics) { // 分类组
|
|
1343
|
+
for (const sub of t.Topics) {
|
|
1344
|
+
if (out.length >= 8)
|
|
1345
|
+
break;
|
|
1346
|
+
if (sub.Text && sub.FirstURL)
|
|
1347
|
+
out.push(`${out.length + 1}. ${sub.Text.slice(0, 200)}\n ${sub.FirstURL}`);
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
else if (t.Text && t.FirstURL) {
|
|
1351
|
+
out.push(`${out.length + 1}. ${t.Text.slice(0, 200)}\n ${t.FirstURL}`);
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
if (out.length > 0) {
|
|
1355
|
+
return { success: true, output: `🔎 "${query}" (DuckDuckGo):\n\n${out.join('\n\n')}` };
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
catch { /* fallback */ }
|
|
1359
|
+
// 引擎 2: Wikipedia 搜索 API
|
|
1360
|
+
try {
|
|
1361
|
+
const wiki = await curlJson(`https://zh.wikipedia.org/w/api.php?action=query&list=search&srsearch=${encodeURIComponent(query)}&format=json&srlimit=5`);
|
|
1362
|
+
const hits = wiki?.query?.search || [];
|
|
1363
|
+
if (hits.length > 0) {
|
|
1364
|
+
const lines = hits.map((h, i) => `${i + 1}. ${h.title}\n https://zh.wikipedia.org/wiki/${encodeURIComponent(h.title.replace(/ /g, '_'))}\n ${String(h.snippet || '').replace(/<[^>]+>/g, '').slice(0, 120)}`);
|
|
1365
|
+
return { success: true, output: `🔎 "${query}" (维基百科 ${hits.length} 条):\n\n${lines.join('\n\n')}` };
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
catch { /* fallback */ }
|
|
1369
|
+
return { success: true, output: `🔎 "${query}" 无结果 (免费引擎无匹配, 可配 TAVILY_API_KEY 提升覆盖)` };
|
|
1370
|
+
}
|
|
1371
|
+
catch (e) {
|
|
1372
|
+
return { success: false, error: `web_search 失败: ${String(e?.message || e).slice(0, 200)}` };
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
});
|
|
1376
|
+
ctx.tools.set('git_log', {
|
|
1377
|
+
name: 'git_log',
|
|
1378
|
+
description: '查看 git log. 默认 --oneline -10. 支持过滤和范围.',
|
|
1379
|
+
parameters: { range: '可选, e.g. "HEAD~5..HEAD" 或分支名', maxCount: '可选, 默认 10', oneline: '可选, "true" 用 --oneline (默认 true)' },
|
|
1380
|
+
execute: async (args) => {
|
|
1381
|
+
const range = String(args.range || '').trim();
|
|
1382
|
+
const maxCount = parseInt(String(args.maxCount || '10')) || 10;
|
|
1383
|
+
const oneline = String(args.oneline || 'true') === 'true';
|
|
1384
|
+
const argv = ['log'];
|
|
1385
|
+
if (oneline)
|
|
1386
|
+
argv.push('--oneline');
|
|
1387
|
+
if (maxCount > 0)
|
|
1388
|
+
argv.push(`-n`, String(maxCount));
|
|
1389
|
+
if (range)
|
|
1390
|
+
argv.push(range);
|
|
1391
|
+
const result = await (0, shell_tool_js_1.shellExec)('git', argv, { timeoutMs: 10_000 });
|
|
1392
|
+
if (result.deniedByGuard)
|
|
1393
|
+
return { success: false, error: result.error };
|
|
1394
|
+
if (!result.success)
|
|
1395
|
+
return { success: false, error: result.error, output: result.output };
|
|
1396
|
+
return { success: true, output: `📜 git log:\n${result.output || '(empty)'}` };
|
|
1397
|
+
}
|
|
1398
|
+
});
|
|
1399
|
+
ctx.tools.set('git_show', {
|
|
1400
|
+
name: 'git_show',
|
|
1401
|
+
description: '查看 commit 内容. 默认 HEAD. 支持 --stat 看统计, --patch 看 diff.',
|
|
1402
|
+
parameters: { ref: '可选, 默认 HEAD', stat: '可选, "true" 只看 stat (默认 false)' },
|
|
1403
|
+
execute: async (args) => {
|
|
1404
|
+
const ref = String(args.ref || 'HEAD').trim();
|
|
1405
|
+
const argv = ['show', ref];
|
|
1406
|
+
if (String(args.stat || 'false') === 'true')
|
|
1407
|
+
argv.push('--stat');
|
|
1408
|
+
const result = await (0, shell_tool_js_1.shellExec)('git', argv, { timeoutMs: 15_000 });
|
|
1409
|
+
if (result.deniedByGuard)
|
|
1410
|
+
return { success: false, error: result.error };
|
|
1411
|
+
if (!result.success)
|
|
1412
|
+
return { success: false, error: result.error, output: result.output };
|
|
1413
|
+
return { success: true, output: `📜 git show ${ref}:\n${result.output || '(empty)'}` };
|
|
1414
|
+
}
|
|
1415
|
+
});
|
|
1416
|
+
ctx.tools.set('git_stash', {
|
|
1417
|
+
name: 'git_stash',
|
|
1418
|
+
description: 'git stash 暂存当前未提交改动. action: save/pop/list/apply/drop. 支持 message.',
|
|
1419
|
+
parameters: { action: '动作 (必填)', message: '可选, save 时的描述', index: '可选, apply/pop/drop 的 stash index' },
|
|
1420
|
+
execute: async (args) => {
|
|
1421
|
+
const action = String(args.action || '').trim();
|
|
1422
|
+
if (!['save', 'pop', 'list', 'apply', 'drop'].includes(action)) {
|
|
1423
|
+
return { success: false, error: `action 必须是 save/pop/list/apply/drop 之一, 收到: ${action}` };
|
|
1424
|
+
}
|
|
1425
|
+
const argv = ['stash', action];
|
|
1426
|
+
if (action === 'save' && args.message)
|
|
1427
|
+
argv.push('-m', String(args.message));
|
|
1428
|
+
if ((action === 'apply' || action === 'drop' || action === 'pop') && args.index) {
|
|
1429
|
+
argv.push('stash@{' + String(args.index) + '}');
|
|
1430
|
+
}
|
|
1431
|
+
const result = await (0, shell_tool_js_1.shellExec)('git', argv, { timeoutMs: 10_000 });
|
|
1432
|
+
if (result.deniedByGuard)
|
|
1433
|
+
return { success: false, error: result.error };
|
|
1434
|
+
if (!result.success)
|
|
1435
|
+
return { success: false, error: result.error, output: result.output };
|
|
1436
|
+
return { success: true, output: `✅ git stash ${action}\n${result.output || ''}` };
|
|
1437
|
+
}
|
|
1438
|
+
});
|
|
1439
|
+
// 测试 & 类型检查
|
|
1440
|
+
ctx.tools.set('vitest_run', {
|
|
1441
|
+
name: 'vitest_run',
|
|
1442
|
+
description: '跑 vitest 测试. 自动 bail (失败就停). 默认 60s timeout.',
|
|
1443
|
+
parameters: { pattern: '可选, 文件 glob', timeoutMs: '可选, 默认 60000' },
|
|
1444
|
+
execute: async (args) => {
|
|
1445
|
+
const argv = ['vitest', 'run', '--reporter=default', '--no-color', '--bail=1'];
|
|
1446
|
+
if (args.pattern)
|
|
1447
|
+
argv.push(String(args.pattern));
|
|
1448
|
+
const timeoutMs = parseInt(String(args.timeoutMs || '60000')) || 60000;
|
|
1449
|
+
const result = await (0, shell_tool_js_1.shellExec)('npx', argv, { timeoutMs });
|
|
1450
|
+
if (result.deniedByGuard)
|
|
1451
|
+
return { success: false, error: result.error };
|
|
1452
|
+
if (!result.success)
|
|
1453
|
+
return { success: false, error: result.error || 'vitest 失败', output: result.output };
|
|
1454
|
+
return { success: true, output: `✅ vitest 通过:\n${(result.output || '').slice(0, 2000)}` };
|
|
1455
|
+
}
|
|
1456
|
+
});
|
|
1457
|
+
ctx.tools.set('tsc_check', {
|
|
1458
|
+
name: 'tsc_check',
|
|
1459
|
+
description: '跑 tsc --noEmit 检查 TypeScript 编译. 默认 60s timeout.',
|
|
1460
|
+
parameters: { project: '可选, tsconfig 路径 (默认 tsconfig.json)', timeoutMs: '可选, 默认 60000' },
|
|
1461
|
+
execute: async (args) => {
|
|
1462
|
+
const argv = ['tsc', '--noEmit'];
|
|
1463
|
+
if (args.project)
|
|
1464
|
+
argv.push('-p', String(args.project));
|
|
1465
|
+
else
|
|
1466
|
+
argv.push('-p', 'tsconfig.json');
|
|
1467
|
+
const timeoutMs = parseInt(String(args.timeoutMs || '60000')) || 60000;
|
|
1468
|
+
const result = await (0, shell_tool_js_1.shellExec)('npx', argv, { timeoutMs });
|
|
1469
|
+
if (result.deniedByGuard)
|
|
1470
|
+
return { success: false, error: result.error };
|
|
1471
|
+
if (!result.success)
|
|
1472
|
+
return { success: false, error: result.error || 'tsc 失败', output: result.output };
|
|
1473
|
+
return { success: true, output: `✅ tsc 通过:\n${(result.output || 'no errors').slice(0, 1000)}` };
|
|
1474
|
+
}
|
|
1475
|
+
});
|
|
1476
|
+
// Task 工具
|
|
1477
|
+
ctx.tools.set('create_task', {
|
|
1478
|
+
name: 'create_task',
|
|
1479
|
+
description: '创建一个新多步任务, 初始 steps 列表由 LLM 给出.',
|
|
1480
|
+
parameters: { goal: '任务目标 (必填)', steps: '步骤列表 (必填)', sessionKey: '可选', branch: '可选' },
|
|
1481
|
+
execute: async (args) => {
|
|
1482
|
+
const goal = String(args.goal || '').trim();
|
|
1483
|
+
const stepsRaw = args.steps;
|
|
1484
|
+
if (!goal)
|
|
1485
|
+
return { success: false, error: 'goal 必填' };
|
|
1486
|
+
let steps = [];
|
|
1487
|
+
if (Array.isArray(stepsRaw)) {
|
|
1488
|
+
steps = stepsRaw.map((s) => String(s).trim()).filter(Boolean);
|
|
1489
|
+
}
|
|
1490
|
+
else if (typeof stepsRaw === 'string') {
|
|
1491
|
+
steps = stepsRaw.split(/[\n,]/).map(s => s.trim()).filter(Boolean);
|
|
1492
|
+
}
|
|
1493
|
+
if (steps.length === 0)
|
|
1494
|
+
return { success: false, error: 'steps 必填且至少 1 步' };
|
|
1495
|
+
const sessionKey = String(args.sessionKey || '').trim() || undefined;
|
|
1496
|
+
const branch = String(args.branch || '').trim() || undefined;
|
|
1497
|
+
try {
|
|
1498
|
+
const { createTask } = await Promise.resolve().then(() => __importStar(require('./task-state.js')));
|
|
1499
|
+
const task = await createTask({ goal, steps, sessionKey, branch });
|
|
1500
|
+
return { success: true, output: `✅ task created: ${task.id}\nbranch: ${branch || '(未指定)'}\nsteps:\n${steps.map((s, i) => ` ${i + 1}. ${s}`).join('\n')}` };
|
|
1501
|
+
}
|
|
1502
|
+
catch (e) {
|
|
1503
|
+
return { success: false, error: `create_task 失败: ${String(e)}` };
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
});
|
|
1507
|
+
ctx.tools.set('update_task', {
|
|
1508
|
+
name: 'update_task',
|
|
1509
|
+
description: '更新任务的某一步状态.',
|
|
1510
|
+
parameters: { task_id: '任务 id', step_id: '步骤 id', status: '新状态', result_summary: '可选', error: '可选' },
|
|
1511
|
+
execute: async (args) => {
|
|
1512
|
+
const taskId = String(args.task_id || '').trim();
|
|
1513
|
+
const stepId = String(args.step_id || '').trim();
|
|
1514
|
+
const status = String(args.status || '').trim();
|
|
1515
|
+
if (!taskId || !stepId)
|
|
1516
|
+
return { success: false, error: 'task_id + step_id 必填' };
|
|
1517
|
+
if (!['running', 'done', 'failed', 'skipped'].includes(status)) {
|
|
1518
|
+
return { success: false, error: `status 必须是 running|done|failed|skipped` };
|
|
1519
|
+
}
|
|
1520
|
+
try {
|
|
1521
|
+
const { updateStep } = await Promise.resolve().then(() => __importStar(require('./task-state.js')));
|
|
1522
|
+
const patch = { status };
|
|
1523
|
+
if (args.result_summary)
|
|
1524
|
+
patch.resultSummary = String(args.result_summary);
|
|
1525
|
+
if (args.error)
|
|
1526
|
+
patch.error = String(args.error);
|
|
1527
|
+
const updated = await updateStep(taskId, stepId, patch);
|
|
1528
|
+
if (!updated)
|
|
1529
|
+
return { success: false, error: `任务 ${taskId} 未找到` };
|
|
1530
|
+
const nextRunning = updated.steps.find((s) => s.status === 'running');
|
|
1531
|
+
return {
|
|
1532
|
+
success: true,
|
|
1533
|
+
output: `✅ step ${stepId} → ${status}\ntask 状态: ${updated.status}${nextRunning ? `\n下一步: ${nextRunning.id} — ${nextRunning.description}` : ''}`,
|
|
1534
|
+
};
|
|
1535
|
+
}
|
|
1536
|
+
catch (e) {
|
|
1537
|
+
return { success: false, error: `update_task 失败: ${String(e)}` };
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
});
|
|
1541
|
+
ctx.tools.set('get_task', {
|
|
1542
|
+
name: 'get_task',
|
|
1543
|
+
description: '查任务的当前状态和步骤进度.',
|
|
1544
|
+
parameters: { task_id: '任务 id (必填)' },
|
|
1545
|
+
execute: async (args) => {
|
|
1546
|
+
const taskId = String(args.task_id || '').trim();
|
|
1547
|
+
if (!taskId)
|
|
1548
|
+
return { success: false, error: 'task_id 必填' };
|
|
1549
|
+
try {
|
|
1550
|
+
const { getTask } = await Promise.resolve().then(() => __importStar(require('./task-state.js')));
|
|
1551
|
+
const t = await getTask(taskId);
|
|
1552
|
+
if (!t)
|
|
1553
|
+
return { success: false, error: `任务 ${taskId} 未找到` };
|
|
1554
|
+
const lines = [
|
|
1555
|
+
`任务: ${t.id}`,
|
|
1556
|
+
`目标: ${t.goal}`,
|
|
1557
|
+
`状态: ${t.status}`,
|
|
1558
|
+
`branch: ${t.branch || '(未指定)'}`,
|
|
1559
|
+
`sessionKey: ${t.sessionKey || '(未指定)'}`,
|
|
1560
|
+
`创建: ${t.createdAt}`,
|
|
1561
|
+
`更新: ${t.updatedAt}`,
|
|
1562
|
+
``,
|
|
1563
|
+
`步骤:`,
|
|
1564
|
+
...t.steps.map((s) => ` ${s.status === 'done' ? '✅' : s.status === 'running' ? '🔄' : s.status === 'failed' ? '❌' : s.status === 'skipped' ? '⏭️' : '⏳'} ${s.id} — ${s.description}${s.resultSummary ? `\n 结果: ${s.resultSummary}` : ''}${s.error ? `\n 错误: ${s.error}` : ''}`),
|
|
1565
|
+
];
|
|
1566
|
+
return { success: true, output: lines.join('\n') };
|
|
1567
|
+
}
|
|
1568
|
+
catch (e) {
|
|
1569
|
+
return { success: false, error: `get_task 失败: ${String(e)}` };
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
});
|
|
1573
|
+
ctx.tools.set('list_tasks', {
|
|
1574
|
+
name: 'list_tasks',
|
|
1575
|
+
description: '列出最近 N 个任务 (默认 10).',
|
|
1576
|
+
parameters: { limit: '可选, 默认 10' },
|
|
1577
|
+
execute: async (args) => {
|
|
1578
|
+
const limit = Number(args.limit) || 10;
|
|
1579
|
+
try {
|
|
1580
|
+
const { listTasks } = await Promise.resolve().then(() => __importStar(require('./task-state.js')));
|
|
1581
|
+
const tasks = await listTasks(limit);
|
|
1582
|
+
if (tasks.length === 0) {
|
|
1583
|
+
return { success: true, output: '当前没有任务. 用 create_task 创建一个.' };
|
|
1584
|
+
}
|
|
1585
|
+
const lines = tasks.map((t) => {
|
|
1586
|
+
const done = t.steps.filter((s) => s.status === 'done').length;
|
|
1587
|
+
const total = t.steps.length;
|
|
1588
|
+
return `${t.status === 'completed' ? '✅' : t.status === 'failed' ? '❌' : '🔄'} ${t.id} — ${t.goal} (${done}/${total} steps, branch: ${t.branch || '-'})`;
|
|
1589
|
+
});
|
|
1590
|
+
return { success: true, output: `最近 ${tasks.length} 个任务:\n${lines.join('\n')}` };
|
|
1591
|
+
}
|
|
1592
|
+
catch (e) {
|
|
1593
|
+
return { success: false, error: `list_tasks 失败: ${String(e)}` };
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
});
|
|
1597
|
+
// ============================================================
|
|
1598
|
+
// 2026-07-10 双栖 agent 网络新增: goal handoff 工具
|
|
1599
|
+
// (park_goal / resume_goal / continue_goal_background)
|
|
1600
|
+
// 内部调用 goal-resume.ts, 错误不抛, 静默返回 error 字段
|
|
1601
|
+
// ============================================================
|
|
1602
|
+
ctx.tools.set('park_goal', {
|
|
1603
|
+
name: 'park_goal',
|
|
1604
|
+
description: '暂停当前目标并落盘快照. 切换 channel / 用户离开 / 等对端响应 / 推到对端 前必调. 接收 goalRef (含 goalId + targetId) + reason.',
|
|
1605
|
+
parameters: {
|
|
1606
|
+
goal_id: '已存在或新生成的 goal ID (建议 goal-${ts}-${rand} 格式)',
|
|
1607
|
+
target_id: '用户视角的稳定目标描述 (e.g. "完成财务模块迁移")',
|
|
1608
|
+
created_by: 'user | agent | peer',
|
|
1609
|
+
origin_channel: '当前 session / channel id',
|
|
1610
|
+
reason: 'channel_switch | user_away | awaiting_external | peer_handoff',
|
|
1611
|
+
},
|
|
1612
|
+
execute: async (args) => {
|
|
1613
|
+
try {
|
|
1614
|
+
const { parkGoal } = await Promise.resolve().then(() => __importStar(require('./goal-resume.js')));
|
|
1615
|
+
const handle = await parkGoal({
|
|
1616
|
+
goalId: String(args.goal_id || '').trim(),
|
|
1617
|
+
targetId: String(args.target_id || '').trim(),
|
|
1618
|
+
createdBy: (args.created_by === 'user' || args.created_by === 'peer') ? args.created_by : 'agent',
|
|
1619
|
+
createdAt: new Date().toISOString(),
|
|
1620
|
+
originChannel: String(args.origin_channel || '').trim(),
|
|
1621
|
+
}, args.reason || 'channel_switch');
|
|
1622
|
+
return { success: !handle.error, output: JSON.stringify(handle, null, 2), error: handle.error };
|
|
1623
|
+
}
|
|
1624
|
+
catch (e) {
|
|
1625
|
+
return { success: false, error: `park_goal 失败: ${String(e).slice(0, 200)}` };
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
});
|
|
1629
|
+
ctx.tools.set('resume_goal', {
|
|
1630
|
+
name: 'resume_goal',
|
|
1631
|
+
description: '恢复一个 park 的目标. 加载末 30 条消息到 session + 关联 task 改 running. 切回 channel / 用户回来 / 收到对端 ack 时调.',
|
|
1632
|
+
parameters: {
|
|
1633
|
+
goal_id: 'park 时记的 goal ID',
|
|
1634
|
+
new_session: 'true = 在新 session key 下续, 默认 false (留原 session)',
|
|
1635
|
+
channel_id: '可选, 指定 channelId 恢复 (默认 = originChannel)',
|
|
1636
|
+
},
|
|
1637
|
+
execute: async (args) => {
|
|
1638
|
+
try {
|
|
1639
|
+
const { resumeGoal } = await Promise.resolve().then(() => __importStar(require('./goal-resume.js')));
|
|
1640
|
+
const newSessionStr = String(args.new_session || '').toLowerCase();
|
|
1641
|
+
const handle = await resumeGoal(String(args.goal_id || '').trim(), {
|
|
1642
|
+
newSession: newSessionStr === 'true' || newSessionStr === '1',
|
|
1643
|
+
channelId: args.channel_id ? String(args.channel_id) : undefined,
|
|
1644
|
+
});
|
|
1645
|
+
return { success: !handle.error, output: JSON.stringify(handle, null, 2), error: handle.error };
|
|
1646
|
+
}
|
|
1647
|
+
catch (e) {
|
|
1648
|
+
return { success: false, error: `resume_goal 失败: ${String(e).slice(0, 200)}` };
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
});
|
|
1652
|
+
ctx.tools.set('continue_goal_background', {
|
|
1653
|
+
name: 'continue_goal_background',
|
|
1654
|
+
description: '把目标推到对端 peer 后台跑. 内部 park 本机 + P2P 推消息 + 隐私过滤 (judgment 不发). 任务大 / 想分工时用.',
|
|
1655
|
+
parameters: {
|
|
1656
|
+
goal_id: '当前 goal ID',
|
|
1657
|
+
target_id: '用户视角目标描述',
|
|
1658
|
+
origin_channel: '当前 session id',
|
|
1659
|
+
peer_did: '对端 DID (来自 list_peers)',
|
|
1660
|
+
},
|
|
1661
|
+
execute: async (args) => {
|
|
1662
|
+
try {
|
|
1663
|
+
const { continueGoalInBackground } = await Promise.resolve().then(() => __importStar(require('./goal-resume.js')));
|
|
1664
|
+
const peerDid = String(args.peer_did || '').trim();
|
|
1665
|
+
if (!peerDid)
|
|
1666
|
+
return { success: false, error: 'peer_did 必填' };
|
|
1667
|
+
// 注入 p2p 发送函数 — 通过 ctx.p2pNetwork 调用
|
|
1668
|
+
const p2pSendMessage = async (peerId, type, message) => {
|
|
1669
|
+
try {
|
|
1670
|
+
// p2pNetwork 在 ctx 里 (由 PiAgentSession 注入)
|
|
1671
|
+
const p2pNetwork = ctx.p2pNetwork;
|
|
1672
|
+
if (!p2pNetwork || typeof p2pNetwork.sendMessage !== 'function') {
|
|
1673
|
+
return { success: false, error: 'p2pNetwork 未注入到 ctx' };
|
|
1674
|
+
}
|
|
1675
|
+
await p2pNetwork.sendMessage(peerId, type, message);
|
|
1676
|
+
return { success: true };
|
|
1677
|
+
}
|
|
1678
|
+
catch (e) {
|
|
1679
|
+
return { success: false, error: String(e).slice(0, 100) };
|
|
1680
|
+
}
|
|
1681
|
+
};
|
|
1682
|
+
const result = await continueGoalInBackground({
|
|
1683
|
+
goalId: String(args.goal_id || '').trim(),
|
|
1684
|
+
targetId: String(args.target_id || '').trim(),
|
|
1685
|
+
createdBy: 'agent',
|
|
1686
|
+
createdAt: new Date().toISOString(),
|
|
1687
|
+
originChannel: String(args.origin_channel || '').trim(),
|
|
1688
|
+
}, peerDid, p2pSendMessage);
|
|
1689
|
+
return { success: !result.handle.error, output: JSON.stringify(result, null, 2), error: result.handle.error };
|
|
1690
|
+
}
|
|
1691
|
+
catch (e) {
|
|
1692
|
+
return { success: false, error: `continue_goal_background 失败: ${String(e).slice(0, 200)}` };
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
});
|
|
1696
|
+
// ============================================================
|
|
1697
|
+
// skill 写工具 (2026-08-02) — 让 agent 从成功经验沉淀技能
|
|
1698
|
+
// create_skill / update_skill / list_skill_candidates / promote_skill
|
|
1699
|
+
// 实现: skill-writer.ts (写 ~/.bolloon/skills/<name>/SKILL.md)
|
|
1700
|
+
// ============================================================
|
|
1701
|
+
ctx.tools.set('create_skill', {
|
|
1702
|
+
name: 'create_skill',
|
|
1703
|
+
description: '创建/覆盖一个 skill (SKILL.md). 当你学会一个可复用的做事方法 (成功流程/命令组合/踩坑教训) 时调用, 沉淀成技能供以后复用. 写 ~/.bolloon/skills/<name>/SKILL.md. 只读技能请用 update_skill 追加.',
|
|
1704
|
+
parameters: {
|
|
1705
|
+
name: 'skill 名 (必填, 小写字母数字连字符, e.g. "p2p-debug")',
|
|
1706
|
+
description: '一句话描述这个 skill 什么时候用 (必填)',
|
|
1707
|
+
body: 'Markdown 正文 (必填): 步骤 / 命令 / 注意事项',
|
|
1708
|
+
scope: '可选: user (默认, ~/.bolloon/skills) 或 project (.bolloon/skills)',
|
|
1709
|
+
triggers: '可选: 触发条件数组 (JSON 字符串数组, e.g. ["p2p", "连接失败"])',
|
|
1710
|
+
},
|
|
1711
|
+
execute: async (args) => {
|
|
1712
|
+
try {
|
|
1713
|
+
const { createSkill } = await Promise.resolve().then(() => __importStar(require('./skill-writer.js')));
|
|
1714
|
+
const name = String(args.name || '').trim();
|
|
1715
|
+
if (!name)
|
|
1716
|
+
return { success: false, error: 'name 必填' };
|
|
1717
|
+
const body = String(args.body || '').trim();
|
|
1718
|
+
if (!body)
|
|
1719
|
+
return { success: false, error: 'body 必填' };
|
|
1720
|
+
let triggers;
|
|
1721
|
+
try {
|
|
1722
|
+
const t = JSON.parse(String(args.triggers || '[]'));
|
|
1723
|
+
if (Array.isArray(t))
|
|
1724
|
+
triggers = t.map(String);
|
|
1725
|
+
}
|
|
1726
|
+
catch { /* triggers 解析失败忽略 */ }
|
|
1727
|
+
const r = await createSkill(name, String(args.description || ''), body, {
|
|
1728
|
+
scope: args.scope === 'project' ? 'project' : 'user',
|
|
1729
|
+
triggers,
|
|
1730
|
+
});
|
|
1731
|
+
return r.ok
|
|
1732
|
+
? { success: true, output: `✅ skill '${name}' 已写入 ${r.path}` }
|
|
1733
|
+
: { success: false, error: r.error };
|
|
1734
|
+
}
|
|
1735
|
+
catch (e) {
|
|
1736
|
+
return { success: false, error: `create_skill 失败: ${String(e).slice(0, 200)}` };
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
});
|
|
1740
|
+
ctx.tools.set('update_skill', {
|
|
1741
|
+
name: 'update_skill',
|
|
1742
|
+
description: '更新已有 skill: 追加新经验 (append_body) 或整体替换 (body), 或改描述/触发条件. skill 不存在时用 create_skill.',
|
|
1743
|
+
parameters: {
|
|
1744
|
+
name: 'skill 名 (必填)',
|
|
1745
|
+
append_body: '追加到正文尾部的增量经验 (可选)',
|
|
1746
|
+
body: '整体替换正文 (可选, 与 append_body 二选一)',
|
|
1747
|
+
description: '新描述 (可选)',
|
|
1748
|
+
triggers: '新触发条件数组 JSON (可选)',
|
|
1749
|
+
},
|
|
1750
|
+
execute: async (args) => {
|
|
1751
|
+
try {
|
|
1752
|
+
const { updateSkill } = await Promise.resolve().then(() => __importStar(require('./skill-writer.js')));
|
|
1753
|
+
const name = String(args.name || '').trim();
|
|
1754
|
+
if (!name)
|
|
1755
|
+
return { success: false, error: 'name 必填' };
|
|
1756
|
+
let triggers;
|
|
1757
|
+
try {
|
|
1758
|
+
const t = JSON.parse(String(args.triggers || '[]'));
|
|
1759
|
+
if (Array.isArray(t))
|
|
1760
|
+
triggers = t.map(String);
|
|
1761
|
+
}
|
|
1762
|
+
catch { /* 忽略 */ }
|
|
1763
|
+
const r = await updateSkill(name, {
|
|
1764
|
+
description: args.description ? String(args.description) : undefined,
|
|
1765
|
+
appendBody: args.append_body ? String(args.append_body) : undefined,
|
|
1766
|
+
body: args.body ? String(args.body) : undefined,
|
|
1767
|
+
triggers,
|
|
1768
|
+
});
|
|
1769
|
+
return r.ok
|
|
1770
|
+
? { success: true, output: `✅ skill '${name}' 已更新 ${r.path}` }
|
|
1771
|
+
: { success: false, error: r.error };
|
|
1772
|
+
}
|
|
1773
|
+
catch (e) {
|
|
1774
|
+
return { success: false, error: `update_skill 失败: ${String(e).slice(0, 200)}` };
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
});
|
|
1778
|
+
ctx.tools.set('list_skill_candidates', {
|
|
1779
|
+
name: 'list_skill_candidates',
|
|
1780
|
+
description: '查看待沉淀的 skill 候选 (后台任务从成功的工具调用模式生成的候选). 返回候选列表, 可用 promote_skill 转正.',
|
|
1781
|
+
parameters: {},
|
|
1782
|
+
execute: async () => {
|
|
1783
|
+
try {
|
|
1784
|
+
const { listSkillCandidates } = await Promise.resolve().then(() => __importStar(require('./skill-writer.js')));
|
|
1785
|
+
const cands = await listSkillCandidates();
|
|
1786
|
+
if (cands.length === 0)
|
|
1787
|
+
return { success: true, output: '暂无待沉淀的 skill 候选.' };
|
|
1788
|
+
const lines = cands.map(c => `- ${c.name}: ${c.description} [来源 ${c.source}]`).join('\n');
|
|
1789
|
+
return { success: true, output: `📋 ${cands.length} 个 skill 候选:\n${lines}` };
|
|
1790
|
+
}
|
|
1791
|
+
catch (e) {
|
|
1792
|
+
return { success: false, error: `list_skill_candidates 失败: ${String(e).slice(0, 200)}` };
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
});
|
|
1796
|
+
ctx.tools.set('promote_skill', {
|
|
1797
|
+
name: 'promote_skill',
|
|
1798
|
+
description: '把 skill 候选转正为正式 skill. 转正后候选文件自动清理.',
|
|
1799
|
+
parameters: { name: '候选名 (必填, 见 list_skill_candidates)' },
|
|
1800
|
+
execute: async (args) => {
|
|
1801
|
+
try {
|
|
1802
|
+
const { promoteCandidate } = await Promise.resolve().then(() => __importStar(require('./skill-writer.js')));
|
|
1803
|
+
const name = String(args.name || '').trim();
|
|
1804
|
+
if (!name)
|
|
1805
|
+
return { success: false, error: 'name 必填' };
|
|
1806
|
+
const r = await promoteCandidate(name);
|
|
1807
|
+
return r.ok
|
|
1808
|
+
? { success: true, output: `✅ 候选 '${name}' 已转正 → ${r.path}` }
|
|
1809
|
+
: { success: false, error: r.error };
|
|
1810
|
+
}
|
|
1811
|
+
catch (e) {
|
|
1812
|
+
return { success: false, error: `promote_skill 失败: ${String(e).slice(0, 200)}` };
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
});
|
|
1816
|
+
// ============================================================
|
|
1817
|
+
// plan / todo / review 工具 (2026-08-02) — 显式执行闭环
|
|
1818
|
+
// create_plan / update_plan / review_plan / list_plans
|
|
1819
|
+
// 实现: plan-store.ts (~/.bolloon/plans/<planId>.json)
|
|
1820
|
+
// ============================================================
|
|
1821
|
+
ctx.tools.set('create_plan', {
|
|
1822
|
+
name: 'create_plan',
|
|
1823
|
+
description: '执行复杂任务前先显式列计划: 拆成 3-8 个可执行步骤. 之后每完成一步调 update_plan 勾选, 全部完成调 review_plan 总结. 落盘 ~/.bolloon/plans/.',
|
|
1824
|
+
parameters: {
|
|
1825
|
+
goal: '一句话目标 (必填)',
|
|
1826
|
+
steps: '步骤数组 JSON (必填, e.g. ["读需求", "写代码", "测试"])',
|
|
1827
|
+
},
|
|
1828
|
+
execute: async (args) => {
|
|
1829
|
+
try {
|
|
1830
|
+
const { createPlan, planToContext } = await Promise.resolve().then(() => __importStar(require('./plan-store.js')));
|
|
1831
|
+
const goal = String(args.goal || '').trim();
|
|
1832
|
+
let steps = [];
|
|
1833
|
+
try {
|
|
1834
|
+
const s = JSON.parse(String(args.steps || '[]'));
|
|
1835
|
+
if (Array.isArray(s))
|
|
1836
|
+
steps = s.map(String);
|
|
1837
|
+
}
|
|
1838
|
+
catch { /* steps 解析失败 */ }
|
|
1839
|
+
const r = await createPlan({ goal, steps, createdBy: 'agent', originChannel: ctx.channelId || '' });
|
|
1840
|
+
if (!r.ok || !r.plan)
|
|
1841
|
+
return { success: false, error: r.error };
|
|
1842
|
+
return { success: true, output: `✅ 计划已创建 ${r.plan.planId}\n\n${planToContext(r.plan)}` };
|
|
1843
|
+
}
|
|
1844
|
+
catch (e) {
|
|
1845
|
+
return { success: false, error: `create_plan 失败: ${String(e).slice(0, 200)}` };
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
});
|
|
1849
|
+
ctx.tools.set('update_plan', {
|
|
1850
|
+
name: 'update_plan',
|
|
1851
|
+
description: '更新计划: 勾选某步完成/阻塞 (step_id + status), 或追加新步骤 (append_steps), 或整体结束 (finish=true). 执行中每完成一步必调.',
|
|
1852
|
+
parameters: {
|
|
1853
|
+
plan_id: '计划 ID (必填, create_plan 返回)',
|
|
1854
|
+
step_id: '步骤 ID (可选, e.g. step_1)',
|
|
1855
|
+
status: '步骤新状态: done / blocked (可选)',
|
|
1856
|
+
note: '完成/阻塞备注 (可选)',
|
|
1857
|
+
append_steps: '追加步骤数组 JSON (可选)',
|
|
1858
|
+
finish: 'true 表示整个计划结束 (可选)',
|
|
1859
|
+
},
|
|
1860
|
+
execute: async (args) => {
|
|
1861
|
+
try {
|
|
1862
|
+
const { updatePlan, planToContext } = await Promise.resolve().then(() => __importStar(require('./plan-store.js')));
|
|
1863
|
+
const planId = String(args.plan_id || '').trim();
|
|
1864
|
+
if (!planId)
|
|
1865
|
+
return { success: false, error: 'plan_id 必填' };
|
|
1866
|
+
let appendSteps;
|
|
1867
|
+
try {
|
|
1868
|
+
const s = JSON.parse(String(args.append_steps || '[]'));
|
|
1869
|
+
if (Array.isArray(s))
|
|
1870
|
+
appendSteps = s.map(String);
|
|
1871
|
+
}
|
|
1872
|
+
catch { /* 忽略 */ }
|
|
1873
|
+
const r = await updatePlan(planId, {
|
|
1874
|
+
stepId: args.step_id ? String(args.step_id) : undefined,
|
|
1875
|
+
status: (args.status === 'done' || args.status === 'blocked') ? args.status : undefined,
|
|
1876
|
+
note: args.note ? String(args.note) : undefined,
|
|
1877
|
+
appendSteps,
|
|
1878
|
+
finish: String(args.finish) === 'true',
|
|
1879
|
+
});
|
|
1880
|
+
if (!r.ok || !r.plan)
|
|
1881
|
+
return { success: false, error: r.error };
|
|
1882
|
+
return { success: true, output: `✅ 计划已更新\n\n${planToContext(r.plan)}` };
|
|
1883
|
+
}
|
|
1884
|
+
catch (e) {
|
|
1885
|
+
return { success: false, error: `update_plan 失败: ${String(e).slice(0, 200)}` };
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
});
|
|
1889
|
+
ctx.tools.set('review_plan', {
|
|
1890
|
+
name: 'review_plan',
|
|
1891
|
+
description: '计划全部执行完后审查: 总结完成度 + 产出结论. 完成后 plan 标记 done.',
|
|
1892
|
+
parameters: {
|
|
1893
|
+
plan_id: '计划 ID (必填)',
|
|
1894
|
+
summary: '审查总结 (必填, 说明完成了什么/卡在哪/下一步)',
|
|
1895
|
+
},
|
|
1896
|
+
execute: async (args) => {
|
|
1897
|
+
try {
|
|
1898
|
+
const { reviewPlan } = await Promise.resolve().then(() => __importStar(require('./plan-store.js')));
|
|
1899
|
+
const planId = String(args.plan_id || '').trim();
|
|
1900
|
+
if (!planId)
|
|
1901
|
+
return { success: false, error: 'plan_id 必填' };
|
|
1902
|
+
const summary = String(args.summary || '').trim();
|
|
1903
|
+
if (!summary)
|
|
1904
|
+
return { success: false, error: 'summary 必填' };
|
|
1905
|
+
const r = await reviewPlan(planId, summary);
|
|
1906
|
+
if (!r.ok || !r.plan)
|
|
1907
|
+
return { success: false, error: r.error };
|
|
1908
|
+
return {
|
|
1909
|
+
success: true,
|
|
1910
|
+
output: `✅ 计划审查完成: ${r.plan.review.completedSteps}/${r.plan.review.totalSteps} 步\n📝 ${summary}`,
|
|
1911
|
+
};
|
|
1912
|
+
}
|
|
1913
|
+
catch (e) {
|
|
1914
|
+
return { success: false, error: `review_plan 失败: ${String(e).slice(0, 200)}` };
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
});
|
|
1918
|
+
ctx.tools.set('list_plans', {
|
|
1919
|
+
name: 'list_plans',
|
|
1920
|
+
description: '列出所有进行中的计划 (active). 用于恢复上下文/继续未完成的计划.',
|
|
1921
|
+
parameters: {},
|
|
1922
|
+
execute: async () => {
|
|
1923
|
+
try {
|
|
1924
|
+
const { listActivePlans, planToContext } = await Promise.resolve().then(() => __importStar(require('./plan-store.js')));
|
|
1925
|
+
const plans = await listActivePlans();
|
|
1926
|
+
if (plans.length === 0)
|
|
1927
|
+
return { success: true, output: '暂无进行中的计划.' };
|
|
1928
|
+
const text = plans.map(p => planToContext(p)).join('\n\n');
|
|
1929
|
+
return { success: true, output: `📋 ${plans.length} 个进行中的计划:\n\n${text}` };
|
|
1930
|
+
}
|
|
1931
|
+
catch (e) {
|
|
1932
|
+
return { success: false, error: `list_plans 失败: ${String(e).slice(0, 200)}` };
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
});
|
|
1936
|
+
// ============================================================
|
|
1937
|
+
// 决策协议工具 (2026-08-03, Context OS §7) — 可回滚的推理链
|
|
1938
|
+
// create_decision / decide_decision / rollback_decision / list_decisions
|
|
1939
|
+
// 实现: decision-store.ts (~/.bolloon/decisions/<id>.json)
|
|
1940
|
+
// 9 要素: 问题/选项(含不做)/成本/收益/风险/信息缺口/推荐/时机/回滚
|
|
1941
|
+
// 决策确认 (decide_decision) 自动 reflect 到 judgeness (HumanJudgment + JudgenessDescription)
|
|
1942
|
+
// ============================================================
|
|
1943
|
+
ctx.tools.set('create_decision', {
|
|
1944
|
+
name: 'create_decision',
|
|
1945
|
+
description: '重大决策前先写推理链 (Context OS 9 要素): problem 问题是什么, options 选项数组 (含"不做"), info_gaps 信息缺口, recommendation 推荐方案, timing 为什么是现在, rollback 失败时回滚条件. 之后用 decide_decision 确认.',
|
|
1946
|
+
parameters: {
|
|
1947
|
+
problem: '问题到底是什么 (必填)',
|
|
1948
|
+
options: '选项数组 JSON (必填, e.g. [{"label":"方案A","costs":"成本","benefits":"收益","risks":"风险"},{"label":"什么都不做","includeDoNothing":true}])',
|
|
1949
|
+
info_gaps: '当前信息缺口 (可选)',
|
|
1950
|
+
recommendation: '推荐方案 (可选, 确认时必填)',
|
|
1951
|
+
timing: '为什么是现在 (可选)',
|
|
1952
|
+
rollback: '失败时的回滚条件 (可选)',
|
|
1953
|
+
stakes: '风险等级: low / medium / high / critical (可选)',
|
|
1954
|
+
domain: '领域 (可选)',
|
|
1955
|
+
},
|
|
1956
|
+
execute: async (args) => {
|
|
1957
|
+
try {
|
|
1958
|
+
const { createDecision, decisionToContext } = await Promise.resolve().then(() => __importStar(require('./decision-store.js')));
|
|
1959
|
+
const problem = String(args.problem || '').trim();
|
|
1960
|
+
if (!problem)
|
|
1961
|
+
return { success: false, error: 'problem 必填' };
|
|
1962
|
+
let options = [];
|
|
1963
|
+
try {
|
|
1964
|
+
const s = JSON.parse(String(args.options || '[]'));
|
|
1965
|
+
if (Array.isArray(s))
|
|
1966
|
+
options = s;
|
|
1967
|
+
}
|
|
1968
|
+
catch { /* options 解析失败 */ }
|
|
1969
|
+
const rawStakes = String(args.stakes || 'medium');
|
|
1970
|
+
const stakes = rawStakes === 'low' || rawStakes === 'high' || rawStakes === 'critical' ? rawStakes : 'medium';
|
|
1971
|
+
const r = await createDecision({
|
|
1972
|
+
problem,
|
|
1973
|
+
options,
|
|
1974
|
+
infoGaps: args.info_gaps ? String(args.info_gaps) : undefined,
|
|
1975
|
+
recommendation: args.recommendation ? String(args.recommendation) : undefined,
|
|
1976
|
+
timing: args.timing ? String(args.timing) : undefined,
|
|
1977
|
+
rollback: args.rollback ? String(args.rollback) : undefined,
|
|
1978
|
+
stakes,
|
|
1979
|
+
domain: args.domain ? String(args.domain) : undefined,
|
|
1980
|
+
by: 'agent',
|
|
1981
|
+
originChannel: ctx.channelId || '',
|
|
1982
|
+
});
|
|
1983
|
+
if (!r.ok || !r.decision)
|
|
1984
|
+
return { success: false, error: r.error };
|
|
1985
|
+
return { success: true, output: `✅ 决策推理链已创建 ${r.decision.decisionId}\n\n${decisionToContext(r.decision)}` };
|
|
1986
|
+
}
|
|
1987
|
+
catch (e) {
|
|
1988
|
+
return { success: false, error: `create_decision 失败: ${String(e).slice(0, 200)}` };
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
});
|
|
1992
|
+
ctx.tools.set('decide_decision', {
|
|
1993
|
+
name: 'decide_decision',
|
|
1994
|
+
description: '确认一个决策 (必须已有 recommendation). 确认后自动把该决策入库 judgeness (HumanJudgment + 5 维描述, 阶段0 临时价值点). 决策确认后状态 → decided.',
|
|
1995
|
+
parameters: {
|
|
1996
|
+
decision_id: '决策 ID (必填, create_decision 返回)',
|
|
1997
|
+
recommendation: '最终推荐方案 (必填, 若创建时未填)',
|
|
1998
|
+
},
|
|
1999
|
+
execute: async (args) => {
|
|
2000
|
+
try {
|
|
2001
|
+
const { updateDecisionStatus } = await Promise.resolve().then(() => __importStar(require('./decision-store.js')));
|
|
2002
|
+
const decisionId = String(args.decision_id || '').trim();
|
|
2003
|
+
if (!decisionId)
|
|
2004
|
+
return { success: false, error: 'decision_id 必填' };
|
|
2005
|
+
const r = await updateDecisionStatus(decisionId, { decide: true, recommendation: args.recommendation ? String(args.recommendation) : undefined }, { byAgentId: ctx.agentId || '' });
|
|
2006
|
+
if (!r.ok || !r.decision)
|
|
2007
|
+
return { success: false, error: r.error };
|
|
2008
|
+
const refl = r.decision.reflection ? ` (已入库 judgeness: hv=${r.decision.reflection.hvId})` : '';
|
|
2009
|
+
return { success: true, output: `✅ 决策已确认: ${r.decision.problem} → ${r.decision.recommendation}${refl}` };
|
|
2010
|
+
}
|
|
2011
|
+
catch (e) {
|
|
2012
|
+
return { success: false, error: `decide_decision 失败: ${String(e).slice(0, 200)}` };
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
});
|
|
2016
|
+
ctx.tools.set('rollback_decision', {
|
|
2017
|
+
name: 'rollback_decision',
|
|
2018
|
+
description: '决策失败触发回滚条件时调用: 标记 rolled-back + 记录教训 (reject 语义入库 judgeness, 防止重复踩坑).',
|
|
2019
|
+
parameters: {
|
|
2020
|
+
decision_id: '决策 ID (必填)',
|
|
2021
|
+
reason: '失败/回滚原因 (必填, 将作为教训入库)',
|
|
2022
|
+
},
|
|
2023
|
+
execute: async (args) => {
|
|
2024
|
+
try {
|
|
2025
|
+
const { updateDecisionStatus } = await Promise.resolve().then(() => __importStar(require('./decision-store.js')));
|
|
2026
|
+
const decisionId = String(args.decision_id || '').trim();
|
|
2027
|
+
if (!decisionId)
|
|
2028
|
+
return { success: false, error: 'decision_id 必填' };
|
|
2029
|
+
const reason = String(args.reason || '').trim();
|
|
2030
|
+
if (!reason)
|
|
2031
|
+
return { success: false, error: 'reason 必填 (回滚原因)' };
|
|
2032
|
+
const r = await updateDecisionStatus(decisionId, { rollback: true, reason }, { byAgentId: ctx.agentId || '' });
|
|
2033
|
+
if (!r.ok || !r.decision)
|
|
2034
|
+
return { success: false, error: r.error };
|
|
2035
|
+
return { success: true, output: `↩️ 决策已回滚: ${r.decision.problem}\n教训已入库 judgeness (reject 语义): ${reason.slice(0, 120)}` };
|
|
2036
|
+
}
|
|
2037
|
+
catch (e) {
|
|
2038
|
+
return { success: false, error: `rollback_decision 失败: ${String(e).slice(0, 200)}` };
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
});
|
|
2042
|
+
ctx.tools.set('list_decisions', {
|
|
2043
|
+
name: 'list_decisions',
|
|
2044
|
+
description: '列出全部决策 (按创建时间倒序). 可选 status 过滤: draft / decided / implemented / abandoned / rolled-back. 用于恢复决策上下文.',
|
|
2045
|
+
parameters: {
|
|
2046
|
+
status: '可选过滤: draft / decided / implemented / abandoned / rolled-back',
|
|
2047
|
+
},
|
|
2048
|
+
execute: async (args) => {
|
|
2049
|
+
try {
|
|
2050
|
+
const { listDecisions, decisionToContext } = await Promise.resolve().then(() => __importStar(require('./decision-store.js')));
|
|
2051
|
+
const status = ['draft', 'decided', 'implemented', 'abandoned', 'rolled-back'].includes(args.status) ? args.status : undefined;
|
|
2052
|
+
const decisions = await listDecisions(status);
|
|
2053
|
+
if (decisions.length === 0)
|
|
2054
|
+
return { success: true, output: '暂无决策记录.' };
|
|
2055
|
+
const text = decisions.slice(0, 8).map(d => decisionToContext(d)).join('\n\n');
|
|
2056
|
+
return { success: true, output: `🧭 ${decisions.length} 条决策 (9 要素推理链可追溯):\n\n${text}` };
|
|
2057
|
+
}
|
|
2058
|
+
catch (e) {
|
|
2059
|
+
return { success: false, error: `list_decisions 失败: ${String(e).slice(0, 200)}` };
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
});
|
|
2063
|
+
// ============================================================
|
|
2064
|
+
// Context OS 资产层工具 (2026-08-03, P5) — 12+3 层文件夹体系
|
|
2065
|
+
// list_context_layers / write_context_asset / read_context_assets
|
|
2066
|
+
// 实现: src/bootstrap/context-os.ts (~/.bolloon/context-os/)
|
|
2067
|
+
// 价值判断: 写入前回答"未来哪个具体场景会用到它?" — 回答不出进 tmp/
|
|
2068
|
+
// ============================================================
|
|
2069
|
+
ctx.tools.set('list_context_layers', {
|
|
2070
|
+
name: 'list_context_layers',
|
|
2071
|
+
description: '列出 Context OS 资产层 (12+3 层: 01-Me 我是谁 / 02-Network 我认识谁 / 03-Current 我在做什么 / 04-Projects 项目 / 05-Prompts 提示词 / 06-Protocols 协议 / 07-Knowledge 知识 / 08-Insights 洞察 / 09-Tools 工具 / 10-Skills 技能 / 11-Write 写作 / 12-Analysis 决策复盘 / output / research / tmp) + 每层资产数. 任务前先看目录, 再按任务路由读取对应层.',
|
|
2072
|
+
parameters: {},
|
|
2073
|
+
execute: async () => {
|
|
2074
|
+
try {
|
|
2075
|
+
const { readContextAssets, formatLayerListing } = await Promise.resolve().then(() => __importStar(require('../bootstrap/context-os.js')));
|
|
2076
|
+
// 2026-08-09: 按 agentId 分区读取 (每个智能体独立 Context OS)
|
|
2077
|
+
const listings = await readContextAssets(undefined, undefined, undefined, ctx.agentId || '');
|
|
2078
|
+
const total = listings.reduce((s, l) => s + l.fileCount, 0);
|
|
2079
|
+
if (total === 0)
|
|
2080
|
+
return { success: true, output: '📂 Context OS 资产层已就绪 (12+3 层), 当前暂无资产. 有价值的内容用 write_context_asset 写入对应层.' };
|
|
2081
|
+
return { success: true, output: `📂 Context OS 资产层共 ${total} 篇资产:\n\n${formatLayerListing(listings)}` };
|
|
2082
|
+
}
|
|
2083
|
+
catch (e) {
|
|
2084
|
+
return { success: false, error: `list_context_layers 失败: ${String(e).slice(0, 200)}` };
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
});
|
|
2088
|
+
ctx.tools.set('write_context_asset', {
|
|
2089
|
+
name: 'write_context_asset',
|
|
2090
|
+
description: '把已验证的价值写入 Context OS 资产层 (唯一落点, 不制造重复文件). 写入前先自检: 未来哪个具体场景会用到它? 回答不出 → 写 tmp/ 或放弃. layer 可选: 01-Me 原则边界 / 02-Network 人脉 / 03-Current 当前状态 / 04-Projects 项目 / 05-Prompts 已验证提示词 / 06-Protocols 规则 / 07-Knowledge 跨项目知识 / 08-Insights 已验证洞察/教训 / 09-Tools 工具经验 / 10-Skills 可验证能力 / 11-Write 成熟表达 / 12-Analysis 决策复盘 / output 对外交付 / research 中间成果 / tmp 一次性草稿.',
|
|
2091
|
+
parameters: {
|
|
2092
|
+
layer: '层 key (必填, 见 description 列表)',
|
|
2093
|
+
title: '资产标题 (必填, 一句话)',
|
|
2094
|
+
content: '资产正文 markdown (必填)',
|
|
2095
|
+
tags: '可选 tags 数组 JSON',
|
|
2096
|
+
domain: '可选领域',
|
|
2097
|
+
},
|
|
2098
|
+
execute: async (args) => {
|
|
2099
|
+
try {
|
|
2100
|
+
const { writeContextAsset } = await Promise.resolve().then(() => __importStar(require('../bootstrap/context-os.js')));
|
|
2101
|
+
const layer = String(args.layer || '').trim();
|
|
2102
|
+
const title = String(args.title || '').trim();
|
|
2103
|
+
const content = String(args.content || '').trim();
|
|
2104
|
+
if (!layer)
|
|
2105
|
+
return { success: false, error: 'layer 必填 (如 07-Knowledge)' };
|
|
2106
|
+
if (!title)
|
|
2107
|
+
return { success: false, error: 'title 必填' };
|
|
2108
|
+
if (!content)
|
|
2109
|
+
return { success: false, error: 'content 必填' };
|
|
2110
|
+
let tags = [];
|
|
2111
|
+
try {
|
|
2112
|
+
const t = JSON.parse(String(args.tags || '[]'));
|
|
2113
|
+
if (Array.isArray(t))
|
|
2114
|
+
tags = t.map(String);
|
|
2115
|
+
}
|
|
2116
|
+
catch { /* tags 解析失败 */ }
|
|
2117
|
+
const r = await writeContextAsset({ layer, title, content, tags, domain: args.domain ? String(args.domain) : undefined }, undefined, ctx.agentId || '');
|
|
2118
|
+
if (!r.ok)
|
|
2119
|
+
return { success: false, error: r.error };
|
|
2120
|
+
if (r.skipped)
|
|
2121
|
+
return { success: true, output: `⏭️ ${r.error}` };
|
|
2122
|
+
return { success: true, output: `📥 已写入资产层 ${r.asset.layer}: ${r.asset.title} (stage0 临时价值点, 待验证后固化)\n路径: ${r.asset.path}` };
|
|
2123
|
+
}
|
|
2124
|
+
catch (e) {
|
|
2125
|
+
return { success: false, error: `write_context_asset 失败: ${String(e).slice(0, 200)}` };
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
});
|
|
2129
|
+
ctx.tools.set('read_context_assets', {
|
|
2130
|
+
name: 'read_context_assets',
|
|
2131
|
+
description: '读取 Context OS 资产层内容. layer 可选 (空 = 全层汇总); keyword 可选 (标题/内容过滤). 做项目前先读 04-Projects 对应项目, 重大决策前读 08-Insights + 12-Analysis, 学技术读 07-Knowledge + 09-Tools.',
|
|
2132
|
+
parameters: {
|
|
2133
|
+
layer: '可选层 key (如 07-Knowledge), 空 = 全部',
|
|
2134
|
+
keyword: '可选关键词过滤',
|
|
2135
|
+
},
|
|
2136
|
+
execute: async (args) => {
|
|
2137
|
+
try {
|
|
2138
|
+
const { readContextAssets, formatLayerListing } = await Promise.resolve().then(() => __importStar(require('../bootstrap/context-os.js')));
|
|
2139
|
+
const layer = args.layer ? String(args.layer) : undefined;
|
|
2140
|
+
const kw = args.keyword ? String(args.keyword) : undefined;
|
|
2141
|
+
// 2026-08-09: 按 agentId 分区读取 (每个智能体独立 Context OS)
|
|
2142
|
+
const listings = await readContextAssets(layer, kw, undefined, ctx.agentId || '');
|
|
2143
|
+
if (listings.every((l) => l.fileCount === 0)) {
|
|
2144
|
+
return { success: true, output: layer ? `📂 资产层 ${layer} 暂无资产` : '📂 资产层暂无资产' };
|
|
2145
|
+
}
|
|
2146
|
+
// 单层且有 keyword → 输出完整正文
|
|
2147
|
+
if (layer && kw) {
|
|
2148
|
+
const found = listings[0]?.files || [];
|
|
2149
|
+
const { readAssetBody } = await Promise.resolve().then(() => __importStar(require('../bootstrap/context-os.js')));
|
|
2150
|
+
const bodies = [];
|
|
2151
|
+
for (const f of found.slice(0, 5)) {
|
|
2152
|
+
try {
|
|
2153
|
+
const r = await readAssetBody(layer, f.file);
|
|
2154
|
+
if (r.ok && r.body)
|
|
2155
|
+
bodies.push(`--- ${f.title} ---\n${r.body.slice(0, 2000)}\n--- 结束 ---`);
|
|
2156
|
+
}
|
|
2157
|
+
catch { /* 跳过 */ }
|
|
2158
|
+
}
|
|
2159
|
+
return { success: true, output: bodies.length > 0 ? bodies.join('\n\n') : '未找到匹配资产' };
|
|
2160
|
+
}
|
|
2161
|
+
return { success: true, output: formatLayerListing(listings) };
|
|
2162
|
+
}
|
|
2163
|
+
catch (e) {
|
|
2164
|
+
return { success: false, error: `read_context_assets 失败: ${String(e).slice(0, 200)}` };
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2167
|
+
});
|
|
2168
|
+
// ============================================================
|
|
2169
|
+
// 数字资源资产化 (Stage 1-4): 注册→运营(发现/匹配)→交易(x402)→清算(信誉)
|
|
2170
|
+
// ============================================================
|
|
2171
|
+
ctx.tools.set('resource_register', {
|
|
2172
|
+
name: 'resource_register',
|
|
2173
|
+
description: '注册一个数字资源 (A 轻版: 内容CID寻址 + 元数据同步网络 registry; 预留 chain=evm 可升级 tokenURI). type: data|art_product|product_image|tx_link; 内容寻址存 content, 定价/授权/交易链接/收款钱包进资源记录, 供网络内智能体 resource_discover/match/purchase 发现并交易.',
|
|
2174
|
+
parameters: {
|
|
2175
|
+
type: 'data | art_product | product_image | tx_link',
|
|
2176
|
+
title: '资源标题',
|
|
2177
|
+
content: '资源内容 (文本/JSON; 图片 base64 dataURL)',
|
|
2178
|
+
wallet: '可选收款钱包地址 (Stage 3 交易用)',
|
|
2179
|
+
price: '可选 {amount,currency:USDC|token,token?,per?} 字符串或JSON',
|
|
2180
|
+
license: '可选授权 (personal/commercial)',
|
|
2181
|
+
txLink: '可选交易链接',
|
|
2182
|
+
chain: '可选 none|evm (预留升级)',
|
|
2183
|
+
tokenUriTemplate: '可选 evm 时 tokenURI 模板, 如 {base}/{id}',
|
|
2184
|
+
},
|
|
2185
|
+
execute: async (args) => {
|
|
2186
|
+
try {
|
|
2187
|
+
const { getCIDDatabase } = await Promise.resolve().then(() => __importStar(require('../orbitdb/cid-database.js')));
|
|
2188
|
+
const db = getCIDDatabase();
|
|
2189
|
+
const cidd = {
|
|
2190
|
+
save: async (t) => (await db.save({ agentId: String(ctx.agentId || ''), type: 'knowledge', content: t })).id,
|
|
2191
|
+
load: async (c) => { const rec = await db.load(c); return rec ? String(rec.content ?? '') : null; },
|
|
2192
|
+
};
|
|
2193
|
+
const { getAgentRegistry } = await Promise.resolve().then(() => __importStar(require('./agent-registry.js')));
|
|
2194
|
+
const { registerResource } = await Promise.resolve().then(() => __importStar(require('./resource-store.js')));
|
|
2195
|
+
let price;
|
|
2196
|
+
if (args.price) {
|
|
2197
|
+
try {
|
|
2198
|
+
price = typeof args.price === 'string' ? JSON.parse(args.price) : args.price;
|
|
2199
|
+
}
|
|
2200
|
+
catch {
|
|
2201
|
+
price = undefined;
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
const { loadOrCreateWallet } = await Promise.resolve().then(() => __importStar(require('./resource-wallet.js')));
|
|
2205
|
+
const nw = await loadOrCreateWallet().catch(() => null);
|
|
2206
|
+
const walletAddr = nw?.address || '';
|
|
2207
|
+
const r = await registerResource({
|
|
2208
|
+
ownerDid: String(ctx.agentId || ''),
|
|
2209
|
+
type: String(args.type || ''),
|
|
2210
|
+
title: String(args.title || ''),
|
|
2211
|
+
content: String(args.content || ''),
|
|
2212
|
+
wallet: args.wallet ? String(args.wallet) : walletAddr || undefined,
|
|
2213
|
+
price,
|
|
2214
|
+
license: args.license ? String(args.license) : undefined,
|
|
2215
|
+
txLink: args.txLink ? String(args.txLink) : undefined,
|
|
2216
|
+
chain: args.chain,
|
|
2217
|
+
tokenUriTemplate: args.tokenUriTemplate ? String(args.tokenUriTemplate) : undefined,
|
|
2218
|
+
}, { cid: cidd, registry: getAgentRegistry() });
|
|
2219
|
+
if (!r.ok)
|
|
2220
|
+
return { success: false, error: r.error };
|
|
2221
|
+
const res = r.resource;
|
|
2222
|
+
return { success: true, output: `✅ 资源已注册 (A 轻版上链): ${res.resourceId}\n ${res.title} · ${res.type} · CID: ${res.contentCid}${res.wallet ? ` · 钱包 ${res.wallet}` : ''}${res.price ? ` · 价格 ${res.price.amount} ${res.price.currency}${res.price.token ? `(${res.price.token})` : ''}` : ''}${res.license ? ` · ${res.license}` : ''}${res.txLink ? ` · 交易:${res.txLink}` : ''}${res.chain === 'evm' ? ` · ${res.chain}` : ''}\n已同步网络 registry: 智能体可用 resource_discover/match 找到, resource_purchase 交易/访问。` };
|
|
2223
|
+
}
|
|
2224
|
+
catch (e) {
|
|
2225
|
+
return { success: false, error: `resource_register 失败: ${String(e).slice(0, 200)}` };
|
|
2226
|
+
}
|
|
2227
|
+
},
|
|
2228
|
+
});
|
|
2229
|
+
ctx.tools.set('resource_discover', {
|
|
2230
|
+
name: 'resource_discover',
|
|
2231
|
+
description: '发现数字资源 (本机 + 网络 registry): 按 type 或 owner 列出 (谁注册了什么, 定价/授权/交易链接), 供分配/交易决策.',
|
|
2232
|
+
parameters: { type: '可选过滤 type', owner: '可选过滤 owner DID' },
|
|
2233
|
+
execute: async (args) => {
|
|
2234
|
+
try {
|
|
2235
|
+
const { getAgentRegistry } = await Promise.resolve().then(() => __importStar(require('./agent-registry.js')));
|
|
2236
|
+
const { listResources } = await Promise.resolve().then(() => __importStar(require('./resource-store.js')));
|
|
2237
|
+
const list = await listResources({
|
|
2238
|
+
type: args.type ? String(args.type) : undefined,
|
|
2239
|
+
owner: args.owner ? String(args.owner) : undefined,
|
|
2240
|
+
}, { registry: getAgentRegistry() });
|
|
2241
|
+
if (!list.length)
|
|
2242
|
+
return { success: true, output: '暂无资源 (用 resource_register 注册)' };
|
|
2243
|
+
return { success: true, output: list.map((r) => `${r.resourceId} · ${r.title} · ${r.type}${r.price ? ` · ${r.price.amount} ${r.price.currency}${r.price.token ? `(${r.price.token})` : ''}${r.price.per ? `/${r.price.per}` : ''}` : ''}${r.license ? ` · ${r.license}` : ''}${r.txLink ? ` · 🔗${r.txLink}` : ''}${r.wallet ? ` · 钱包 ${String(r.wallet).slice(0, 10)}…` : ''}${r.chain === 'evm' ? ' · evm' : ''}`).join('\n') + '\n(resource_purchase <id> 交易/访问)' };
|
|
2244
|
+
}
|
|
2245
|
+
catch (e) {
|
|
2246
|
+
return { success: false, error: `resource_discover 失败: ${String(e).slice(0, 200)}` };
|
|
2247
|
+
}
|
|
2248
|
+
},
|
|
2249
|
+
});
|
|
2250
|
+
ctx.tools.set('resource_match', {
|
|
2251
|
+
name: 'resource_match',
|
|
2252
|
+
description: '自动分配匹配: 按需求关键词对资源打分 (标题/类型/授权 + 提供者信誉加权), 返回推荐排序. 用于智能体自动分配资源.',
|
|
2253
|
+
parameters: { query: '需求关键词' },
|
|
2254
|
+
execute: async (args) => {
|
|
2255
|
+
try {
|
|
2256
|
+
const { getAgentRegistry } = await Promise.resolve().then(() => __importStar(require('./agent-registry.js')));
|
|
2257
|
+
const { queryReputation } = await Promise.resolve().then(() => __importStar(require('./agent-reputation.js')));
|
|
2258
|
+
const registry = getAgentRegistry();
|
|
2259
|
+
const { matchResources } = await Promise.resolve().then(() => __importStar(require('./resource-store.js')));
|
|
2260
|
+
const repQuery = async (owner, type) => {
|
|
2261
|
+
try {
|
|
2262
|
+
const q = await queryReputation(owner, type, registry);
|
|
2263
|
+
const e = q.entries?.[0];
|
|
2264
|
+
return { score: e?.reputation?.score ?? 0, tasks: e?.reputation?.tasks ?? 0, success: e?.reputation?.success ?? 0, failed: e?.reputation?.failed ?? 0 };
|
|
2265
|
+
}
|
|
2266
|
+
catch {
|
|
2267
|
+
return { score: 0, tasks: 0, success: 0, failed: 0 };
|
|
2268
|
+
}
|
|
2269
|
+
};
|
|
2270
|
+
const scored = await matchResources(String(args.query || ''), { registry, repQuery });
|
|
2271
|
+
if (!scored.length)
|
|
2272
|
+
return { success: true, output: '无匹配资源' };
|
|
2273
|
+
return { success: true, output: scored.map((s) => `${s.resource.resourceId} · ${s.resource.title} · ${s.resource.type} · 得分 ${Math.round(s.score * 10) / 10}${s.resource.license ? ` · ${s.resource.license}` : ''}`).join('\n') };
|
|
2274
|
+
}
|
|
2275
|
+
catch (e) {
|
|
2276
|
+
return { success: false, error: `resource_match 失败: ${String(e).slice(0, 200)}` };
|
|
2277
|
+
}
|
|
2278
|
+
},
|
|
2279
|
+
});
|
|
2280
|
+
ctx.tools.set('resource_purchase', {
|
|
2281
|
+
name: 'resource_purchase',
|
|
2282
|
+
description: '交易/访问资源: 付费资源走 x402 支付后解锁内容 (结算更新提供者信誉), 免费资源直接取回内容.',
|
|
2283
|
+
parameters: { resourceId: '资源 id' },
|
|
2284
|
+
execute: async (args) => {
|
|
2285
|
+
try {
|
|
2286
|
+
const { getCIDDatabase } = await Promise.resolve().then(() => __importStar(require('../orbitdb/cid-database.js')));
|
|
2287
|
+
const db = getCIDDatabase();
|
|
2288
|
+
const cidd = {
|
|
2289
|
+
save: async (t) => (await db.save({ agentId: String(ctx.agentId || ''), type: 'knowledge', content: t })).id,
|
|
2290
|
+
load: async (c) => { const rec = await db.load(c); return rec ? String(rec.content ?? '') : null; },
|
|
2291
|
+
};
|
|
2292
|
+
const { getAgentRegistry } = await Promise.resolve().then(() => __importStar(require('./agent-registry.js')));
|
|
2293
|
+
const { queryReputation } = await Promise.resolve().then(() => __importStar(require('./agent-reputation.js')));
|
|
2294
|
+
const registry = getAgentRegistry();
|
|
2295
|
+
const { purchaseResource } = await Promise.resolve().then(() => __importStar(require('./resource-store.js')));
|
|
2296
|
+
const pay = async (spec) => {
|
|
2297
|
+
try {
|
|
2298
|
+
const { x402Pay } = await Promise.resolve().then(() => __importStar(require('./x402/x402Pay.js')));
|
|
2299
|
+
const { loadOrCreateWallet } = await Promise.resolve().then(() => __importStar(require('./resource-wallet.js')));
|
|
2300
|
+
const w = await loadOrCreateWallet().catch(() => null);
|
|
2301
|
+
if (!w?.privateKey)
|
|
2302
|
+
return { success: false, error: '需自动生成 x402 钱包 (~/.bolloon/wallet.json) 才能购买付费资源' };
|
|
2303
|
+
const r = await x402Pay({ privateKey: w.privateKey, amount: String(spec.amount), to: spec.recipient, currency: spec.currency === 'USDC' ? 'USDC' : 'ETH', memo: spec.memo });
|
|
2304
|
+
return { success: r.success, txHash: r.txHash, error: r.error };
|
|
2305
|
+
}
|
|
2306
|
+
catch (e) {
|
|
2307
|
+
return { success: false, error: `x402 支付失败: ${String(e?.message || e).slice(0, 120)}` };
|
|
2308
|
+
}
|
|
2309
|
+
};
|
|
2310
|
+
const repQuery = async (owner, type) => {
|
|
2311
|
+
try {
|
|
2312
|
+
const q = await queryReputation(owner, type, registry);
|
|
2313
|
+
const e = q.entries?.[0];
|
|
2314
|
+
return { score: e?.reputation?.score ?? 0, tasks: e?.reputation?.tasks ?? 0, success: e?.reputation?.success ?? 0, failed: e?.reputation?.failed ?? 0 };
|
|
2315
|
+
}
|
|
2316
|
+
catch {
|
|
2317
|
+
return { score: 0, tasks: 0, success: 0, failed: 0 };
|
|
2318
|
+
}
|
|
2319
|
+
};
|
|
2320
|
+
const onSettle = (outcome, resource) => {
|
|
2321
|
+
void Promise.resolve().then(() => __importStar(require('./agent-reputation.js'))).then((m) => m.recordServiceOutcome(resource.ownerDid, `resource:${resource.type}`, outcome, registry)).catch(() => { });
|
|
2322
|
+
};
|
|
2323
|
+
const r = await purchaseResource(String(args.resourceId || ''), { cid: cidd, pay, repQuery, onSettle });
|
|
2324
|
+
if (!r.ok)
|
|
2325
|
+
return { success: false, error: r.error || (r.needPay ? '需要支付' : '失败') };
|
|
2326
|
+
return { success: true, output: `${r.txHash ? `💰 已支付 (tx ${String(r.txHash).slice(0, 16)}…) · ` : ''}📄 ${r.resource.title} (${r.resource.type})\n${String(r.content || '').slice(0, 3000)}` };
|
|
2327
|
+
}
|
|
2328
|
+
catch (e) {
|
|
2329
|
+
return { success: false, error: `resource_purchase 失败: ${String(e).slice(0, 200)}` };
|
|
2330
|
+
}
|
|
2331
|
+
},
|
|
2332
|
+
});
|
|
2333
|
+
ctx.tools.set('resource_reputation', {
|
|
2334
|
+
name: 'resource_reputation',
|
|
2335
|
+
description: '清算/信誉: 查资源提供者信誉 (成交记录+score), 供自动分配加权决策.',
|
|
2336
|
+
parameters: { owner: 'owner DID (缺省=本机)' },
|
|
2337
|
+
execute: async (args) => {
|
|
2338
|
+
try {
|
|
2339
|
+
const { getAgentRegistry } = await Promise.resolve().then(() => __importStar(require('./agent-registry.js')));
|
|
2340
|
+
const { queryReputation } = await Promise.resolve().then(() => __importStar(require('./agent-reputation.js')));
|
|
2341
|
+
const registry = getAgentRegistry();
|
|
2342
|
+
const owner = String(args.owner || ctx.agentId || '');
|
|
2343
|
+
const q = await queryReputation(owner, undefined, registry);
|
|
2344
|
+
if (!q.entries?.length)
|
|
2345
|
+
return { success: true, output: `${owner.slice(0, 16)}… 暂无信誉记录` };
|
|
2346
|
+
return { success: true, output: q.entries.map((e) => ` ${e.service} · ${e.reputation.score} (${e.reputation.success}/${e.reputation.tasks} 成交)`).join('\n') };
|
|
2347
|
+
}
|
|
2348
|
+
catch (e) {
|
|
2349
|
+
return { success: false, error: `resource_reputation 失败: ${String(e).slice(0, 200)}` };
|
|
2350
|
+
}
|
|
2351
|
+
},
|
|
2352
|
+
});
|
|
2353
|
+
// ── Stage 1-B: EVM 资产合约 (ERC-721, CID 作 tokenURI) 铸造/流转/查询 ──
|
|
2354
|
+
ctx.tools.set('resource_mint', {
|
|
2355
|
+
name: 'resource_mint',
|
|
2356
|
+
description: '把已注册数字资源铸成 ERC-721 token (内容 CID 作 tokenURI, 不可变随 token 流转). 需配置 EVM executor (部署 contracts/ResourceERC721.sol + 设 __bolloonEvmExecutor 或用 ethers/viem).',
|
|
2357
|
+
parameters: { resourceId: '资源 id' },
|
|
2358
|
+
execute: async (args) => {
|
|
2359
|
+
try {
|
|
2360
|
+
const { getResource } = await Promise.resolve().then(() => __importStar(require('./resource-store.js')));
|
|
2361
|
+
const { mintResourceToken } = await Promise.resolve().then(() => __importStar(require('./resource-token.js')));
|
|
2362
|
+
const r = await getResource(String(args.resourceId || ''));
|
|
2363
|
+
if (!r)
|
|
2364
|
+
return { success: false, error: '资源不存在' };
|
|
2365
|
+
const evm = globalThis.__bolloonEvmExecutor;
|
|
2366
|
+
if (!evm)
|
|
2367
|
+
return { success: false, error: '需配置 EVM executor (__bolloonEvmExecutor) 才能铸造. 部署 contracts/ResourceERC721.sol + 配置 ~/.bolloon/evm-config.json / ethers.' };
|
|
2368
|
+
const { walletAddress } = await Promise.resolve().then(() => __importStar(require('./resource-wallet.js')));
|
|
2369
|
+
const naddr = await walletAddress().catch(() => '');
|
|
2370
|
+
const to = r.wallet || naddr;
|
|
2371
|
+
if (!to)
|
|
2372
|
+
return { success: false, error: '无收款钱包 (部署后设 __bolloonEvmExecutor + 资源 wallet)' };
|
|
2373
|
+
const m = await mintResourceToken({ resourceId: r.resourceId, ownerDid: r.ownerDid, contentCid: r.contentCid, wallet: to, title: r.title }, { evm });
|
|
2374
|
+
if (!m.ok)
|
|
2375
|
+
return { success: false, error: m.error || (m.needConfig ? '需配置 EVM' : '铸造失败') };
|
|
2376
|
+
return { success: true, output: `✅ 已铸造 tokenId=${m.tokenId} · tokenURI=ipfs://${r.contentCid} · 资源 ${r.resourceId}\n(resource_transfer <tokenId> <to> 流转 · resource_token <tokenId> 查询)` };
|
|
2377
|
+
}
|
|
2378
|
+
catch (e) {
|
|
2379
|
+
return { success: false, error: `resource_mint 失败: ${String(e).slice(0, 200)}` };
|
|
2380
|
+
}
|
|
2381
|
+
},
|
|
2382
|
+
});
|
|
2383
|
+
ctx.tools.set('resource_transfer', {
|
|
2384
|
+
name: 'resource_transfer',
|
|
2385
|
+
description: '流转 ERC-721 token 所有权 (safeTransferFrom) 到新地址.',
|
|
2386
|
+
parameters: { tokenId: 'token id', to: '接收 EVM 地址' },
|
|
2387
|
+
execute: async (args) => {
|
|
2388
|
+
try {
|
|
2389
|
+
const { transferResourceToken } = await Promise.resolve().then(() => __importStar(require('./resource-token.js')));
|
|
2390
|
+
const evm = globalThis.__bolloonEvmExecutor;
|
|
2391
|
+
if (!evm)
|
|
2392
|
+
return { success: false, error: '需配置 EVM executor (__bolloonEvmExecutor)' };
|
|
2393
|
+
const r = await transferResourceToken(String(args.tokenId || ''), String(args.to || ''), { evm });
|
|
2394
|
+
if (!r.ok)
|
|
2395
|
+
return { success: false, error: r.error || '流转失败' };
|
|
2396
|
+
return { success: true, output: `✅ 已流转 token ${r.tokenId} → ${args.to}` };
|
|
2397
|
+
}
|
|
2398
|
+
catch (e) {
|
|
2399
|
+
return { success: false, error: `resource_transfer 失败: ${String(e).slice(0, 200)}` };
|
|
2400
|
+
}
|
|
2401
|
+
},
|
|
2402
|
+
});
|
|
2403
|
+
ctx.tools.set('resource_token', {
|
|
2404
|
+
name: 'resource_token',
|
|
2405
|
+
description: '查询 ERC-721 token: owner + tokenURI (内容 CID).',
|
|
2406
|
+
parameters: { tokenId: 'token id' },
|
|
2407
|
+
execute: async (args) => {
|
|
2408
|
+
try {
|
|
2409
|
+
const { queryResourceToken } = await Promise.resolve().then(() => __importStar(require('./resource-token.js')));
|
|
2410
|
+
const evm = globalThis.__bolloonEvmExecutor;
|
|
2411
|
+
if (!evm)
|
|
2412
|
+
return { success: false, error: '需配置 EVM executor (__bolloonEvmExecutor)' };
|
|
2413
|
+
const r = await queryResourceToken(String(args.tokenId || ''), { evm });
|
|
2414
|
+
if (!r.ok)
|
|
2415
|
+
return { success: false, error: r.error || '查询失败' };
|
|
2416
|
+
return { success: true, output: `🔎 token ${args.tokenId}\n owner: ${r.owner || 'n/a'}\n tokenURI: ${r.tokenUri || 'n/a'}` };
|
|
2417
|
+
}
|
|
2418
|
+
catch (e) {
|
|
2419
|
+
return { success: false, error: `resource_token 失败: ${String(e).slice(0, 200)}` };
|
|
2420
|
+
}
|
|
2421
|
+
},
|
|
2422
|
+
});
|
|
2423
|
+
// ============================================================
|
|
2424
|
+
// MCP 工具 (2026-08-03) — 外部 MCP server 接入 agent 工具系统
|
|
2425
|
+
// 配置: ~/.mcp.json (mcpServers), 启动时 initializeMcpAdapter 自动握手发现工具
|
|
2426
|
+
// mcp_list_tools: 列出已发现的 MCP 工具
|
|
2427
|
+
// mcp_tool: 调用任意 MCP 工具 (真实 stdio JSON-RPC)
|
|
2428
|
+
// ============================================================
|
|
2429
|
+
ctx.tools.set('mcp_list_tools', {
|
|
2430
|
+
name: 'mcp_list_tools',
|
|
2431
|
+
description: '列出通过 MCP 协议连接的可用外部工具 (来自 ~/.mcp.json 配置的 MCP servers). 调用 MCP 工具前先列一次, 拿准确工具名和参数.',
|
|
2432
|
+
parameters: {},
|
|
2433
|
+
execute: async () => {
|
|
2434
|
+
try {
|
|
2435
|
+
const mcp = await Promise.resolve().then(() => __importStar(require('../pi-ecosystem-mcp/index.js')));
|
|
2436
|
+
await mcp.initializeMcpAdapter().catch(() => { });
|
|
2437
|
+
const tools = mcp.listTools();
|
|
2438
|
+
if (tools.length === 0) {
|
|
2439
|
+
return { success: true, output: '未发现 MCP 工具. 配置 ~/.mcp.json (mcpServers: {name: {command, args}}), 重启后自动连接.' };
|
|
2440
|
+
}
|
|
2441
|
+
const lines = tools.map((t) => ` - ${t.name} (${t.serverName}): ${t.description?.slice(0, 80) || '无描述'}`);
|
|
2442
|
+
return { success: true, output: `🔌 ${tools.length} 个 MCP 工具可用:\n${lines.join('\n')}\n\n调用用 mcp_tool (tool=工具名, arguments=参数 JSON)` };
|
|
2443
|
+
}
|
|
2444
|
+
catch (e) {
|
|
2445
|
+
return { success: false, error: `mcp_list_tools 失败: ${String(e).slice(0, 200)}` };
|
|
2446
|
+
}
|
|
2447
|
+
}
|
|
2448
|
+
});
|
|
2449
|
+
// ============================================================
|
|
2450
|
+
// verify_project (2026-08-11, Hermes agent/verify recipes 模式)
|
|
2451
|
+
// 从项目 package.json scripts 提取验证命令依序执行 (build→typecheck→test→check→lint)
|
|
2452
|
+
// 完成契约 (B7) 的证据工具: 宣布完成前跑一遍, 输出命令/exit code/耗时
|
|
2453
|
+
// ============================================================
|
|
2454
|
+
ctx.tools.set('verify_project', {
|
|
2455
|
+
name: 'verify_project',
|
|
2456
|
+
description: '运行项目验证配方 (Hermes verify recipes 模式): 从 package.json scripts 自动提取 build/typecheck/test/check/lint 依序执行, 返回每步命令+exit code+耗时+输出尾部. 修改代码后、宣布完成前调用 — 提供完成契约要求的证据. cwd 选项目根 (默认当前工作目录).',
|
|
2457
|
+
parameters: {
|
|
2458
|
+
cwd: '项目根目录 (可选, 默认当前 cwd)',
|
|
2459
|
+
},
|
|
2460
|
+
execute: async (args) => {
|
|
2461
|
+
try {
|
|
2462
|
+
const { readFileSync, readdirSync } = await Promise.resolve().then(() => __importStar(require('fs')));
|
|
2463
|
+
const { extractVerifyCommands, detectPackageManager, runVerifyCommands } = await Promise.resolve().then(() => __importStar(require('./verify-recipe.js')));
|
|
2464
|
+
const cwd = String(args.cwd || process.cwd()).trim() || process.cwd();
|
|
2465
|
+
let pkg = {};
|
|
2466
|
+
try {
|
|
2467
|
+
pkg = JSON.parse(readFileSync(`${cwd}/package.json`, 'utf-8'));
|
|
2468
|
+
}
|
|
2469
|
+
catch {
|
|
2470
|
+
return { success: false, error: `${cwd}/package.json 不存在或非法 — verify_project 只支持 npm/pnpm/yarn/bun 项目` };
|
|
2471
|
+
}
|
|
2472
|
+
const scripts = (pkg.scripts ?? {});
|
|
2473
|
+
const pm = detectPackageManager(readdirSync(cwd).map((f) => f.toLowerCase()));
|
|
2474
|
+
const commands = extractVerifyCommands(scripts, pm);
|
|
2475
|
+
if (commands.length === 0) {
|
|
2476
|
+
return { success: false, error: 'package.json scripts 里没有 build/typecheck/test/check/lint 任何一步 — 无可验证' };
|
|
2477
|
+
}
|
|
2478
|
+
const r = await runVerifyCommands({ cwd, commands });
|
|
2479
|
+
const lines = r.steps.map((s) => ` ${s.exitCode === 0 ? '✅' : '❌'} ${s.name} (${s.command}) → exit ${s.exitCode ?? 'ERR'} ${s.durationMs}ms${s.timedOut ? ' [超时]' : ''}`);
|
|
2480
|
+
const failedStep = r.steps.find((s) => s.exitCode !== 0 || s.timedOut);
|
|
2481
|
+
return {
|
|
2482
|
+
success: r.allPassed,
|
|
2483
|
+
output: `${r.allPassed ? '✅ 验证全部通过' : '❌ 验证失败'} (${r.steps.filter((s) => s.exitCode === 0).length}/${r.steps.length} 步):\n${lines.join('\n')}${failedStep ? `\n\n失败输出尾部:\n${failedStep.outputTail.slice(-1500)}` : ''}`,
|
|
2484
|
+
...(r.allPassed ? {} : { error: `验证失败于 ${failedStep?.name} (exit ${failedStep?.exitCode})` }),
|
|
2485
|
+
};
|
|
2486
|
+
}
|
|
2487
|
+
catch (e) {
|
|
2488
|
+
return { success: false, error: `verify_project 失败: ${String(e).slice(0, 200)}` };
|
|
2489
|
+
}
|
|
2490
|
+
},
|
|
2491
|
+
});
|
|
2492
|
+
ctx.tools.set('mcp_list_catalog', {
|
|
2493
|
+
name: 'mcp_list_catalog',
|
|
2494
|
+
description: '列出 Bolloon 审核过的可选 MCP 目录 (Hermes optional-mcps 模式: 默认禁用, 显式安装才生效). 想看"有哪些外部工具可以接"时用这个; 已接入的看 mcp_list_tools.',
|
|
2495
|
+
parameters: {},
|
|
2496
|
+
execute: async () => {
|
|
2497
|
+
try {
|
|
2498
|
+
// fs 读 manifests/mcp-catalog.json — 构建后 dist/agents/../../manifests = 仓库根/manifests
|
|
2499
|
+
const { readFileSync } = await Promise.resolve().then(() => __importStar(require('fs')));
|
|
2500
|
+
// 2026-09-19: 原为 new URL('../../manifests/...', import.meta.url) —— electron 构建(CommonJS)下 TS1343。
|
|
2501
|
+
const raw = readFileSync(path.join((0, version_info_js_1.currentPackageRoot)(), 'manifests', 'mcp-catalog.json'), 'utf-8');
|
|
2502
|
+
const catalog = JSON.parse(raw);
|
|
2503
|
+
const entries = (catalog?.entries ?? []);
|
|
2504
|
+
if (entries.length === 0) {
|
|
2505
|
+
return { success: true, output: '📦 MCP 目录为空. 新增条目: manifests/mcp-catalog.json (实测可用才收录).' };
|
|
2506
|
+
}
|
|
2507
|
+
const lines = entries.map((e) => {
|
|
2508
|
+
const t = e.transport?.type ?? 'unknown';
|
|
2509
|
+
const addr = e.transport?.url ?? e.transport?.command ?? '';
|
|
2510
|
+
return ` - ${e.name} [${t}] ${addr ? '(' + addr + ')' : ''}: ${e.description?.slice(0, 70) || ''}`;
|
|
2511
|
+
});
|
|
2512
|
+
return {
|
|
2513
|
+
success: true,
|
|
2514
|
+
output: `📦 可选 MCP 目录 (${entries.length} 条, 默认禁用):\n${lines.join('\n')}\n\n安装: 把条目按 mcpServers 格式写进 ~/.mcp.json, 重启后 mcp_list_tools 可见. 版本必须精确 pin (Hermes 纪律).`,
|
|
2515
|
+
};
|
|
2516
|
+
}
|
|
2517
|
+
catch (e) {
|
|
2518
|
+
return { success: false, error: `mcp_list_catalog 失败: ${String(e).slice(0, 200)}` };
|
|
2519
|
+
}
|
|
2520
|
+
},
|
|
2521
|
+
});
|
|
2522
|
+
ctx.tools.set('mcp_tool', {
|
|
2523
|
+
name: 'mcp_tool',
|
|
2524
|
+
description: '调用外部 MCP 工具 (真实 stdio JSON-RPC 通信). tool = 工具名 (先用 mcp_list_tools 查看), arguments = 参数 JSON 对象.',
|
|
2525
|
+
parameters: {
|
|
2526
|
+
tool: 'MCP 工具名 (必填)',
|
|
2527
|
+
arguments: '参数 JSON 对象 (必填, e.g. {"text":"hello"})',
|
|
2528
|
+
},
|
|
2529
|
+
execute: async (args) => {
|
|
2530
|
+
try {
|
|
2531
|
+
const mcp = await Promise.resolve().then(() => __importStar(require('../pi-ecosystem-mcp/index.js')));
|
|
2532
|
+
const tool = String(args.tool || '').trim();
|
|
2533
|
+
if (!tool)
|
|
2534
|
+
return { success: false, error: 'tool 必填' };
|
|
2535
|
+
let argumentsObj = {};
|
|
2536
|
+
try {
|
|
2537
|
+
const a = JSON.parse(String(args.arguments || '{}'));
|
|
2538
|
+
if (a && typeof a === 'object')
|
|
2539
|
+
argumentsObj = a;
|
|
2540
|
+
}
|
|
2541
|
+
catch {
|
|
2542
|
+
return { success: false, error: 'arguments 必须是 JSON 对象' };
|
|
2543
|
+
}
|
|
2544
|
+
const r = await mcp.executeTool(tool, argumentsObj);
|
|
2545
|
+
if (!r.success)
|
|
2546
|
+
return { success: false, error: r.error || 'MCP 调用失败' };
|
|
2547
|
+
const text = Array.isArray(r.content)
|
|
2548
|
+
? r.content.map((c) => c?.text ?? '').filter(Boolean).join('\n')
|
|
2549
|
+
: JSON.stringify(r.content);
|
|
2550
|
+
return { success: true, output: `🔧 MCP ${tool}:\n${text.slice(0, 4000)}` };
|
|
2551
|
+
}
|
|
2552
|
+
catch (e) {
|
|
2553
|
+
return { success: false, error: `mcp_tool 失败: ${String(e).slice(0, 200)}` };
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
});
|
|
2557
|
+
// 2026-08-12: MCP 驱动前端 UI 工具 — agent 理解用户意图后调用, 通过 SSE 广播驱动前端组件.
|
|
2558
|
+
// 复用 ui-tools (dispatchUiAction → broadcast {type:'ui'}), 前端订阅 /events 执行.
|
|
2559
|
+
const registerUiAgentTools = async () => {
|
|
2560
|
+
const ui = await Promise.resolve().then(() => __importStar(require('../pi-ecosystem-mcp/ui-tools.js')));
|
|
2561
|
+
// 注册到 MCP 系统 (供 mcp_list_tools 可见) + 注册为 agent 工具
|
|
2562
|
+
ui.registerUiControlTools();
|
|
2563
|
+
const uiTools = [
|
|
2564
|
+
{ name: 'ui_switch_tab', description: '驱动前端切换底部 tab (微信/通讯录/发现/我). 用户想"去通讯录/去设置/去我的"时调用.', params: { tab: 'wechat|contacts|discover|me (必填)' }, map: (a) => ({ action: 'switchTab', data: { tab: String(a.tab || '') } }) },
|
|
2565
|
+
{ name: 'ui_open_chat', description: '驱动前端打开某个智能体聊天页. 用户想"打开和 X 的聊天"时调用.', params: { channelId: '目标 channel id (必填)' }, map: (a) => ({ action: 'openChat', data: { channelId: String(a.channelId || '') } }) },
|
|
2566
|
+
{ name: 'ui_open_settings', description: '驱动前端打开设置页.', params: {}, map: () => ({ action: 'openSettings', data: {} }) },
|
|
2567
|
+
{ name: 'ui_open_wallet', description: '驱动前端打开钱包.', params: {}, map: () => ({ action: 'openWallet', data: {} }) },
|
|
2568
|
+
{ name: 'ui_open_add_friend', description: '驱动前端打开添加好友.', params: {}, map: () => ({ action: 'openAddFriend', data: {} }) },
|
|
2569
|
+
{ name: 'ui_show_toast', description: '驱动前端顶部显示提示 (toast).', params: { message: '提示内容 (必填)' }, map: (a) => ({ action: 'showToast', data: { message: String(a.message || '') } }) },
|
|
2570
|
+
{ name: 'ui_go_back', description: '驱动前端返回上一页.', params: {}, map: () => ({ action: 'goBack', data: {} }) },
|
|
2571
|
+
];
|
|
2572
|
+
for (const t of uiTools) {
|
|
2573
|
+
ctx.tools.set(t.name, {
|
|
2574
|
+
name: t.name,
|
|
2575
|
+
description: t.description,
|
|
2576
|
+
parameters: t.params,
|
|
2577
|
+
execute: async (args) => {
|
|
2578
|
+
const r = ui.dispatchUiAction(t.map(args));
|
|
2579
|
+
return r.success ? { success: true, output: r.output } : { success: false, error: r.output };
|
|
2580
|
+
},
|
|
2581
|
+
});
|
|
2582
|
+
}
|
|
2583
|
+
};
|
|
2584
|
+
registerUiAgentTools().catch(() => { });
|
|
2585
|
+
// 2026-08-12: A2UI (Agent to UI) 工具 — agent 生成 A2UI 消息 (createSurface/updateComponents),
|
|
2586
|
+
// 经 SSE 广播, 前端用 @a2ui/react renderer 渲染. 见 src/pi-ecosystem-a2ui/.
|
|
2587
|
+
(async () => {
|
|
2588
|
+
try {
|
|
2589
|
+
const a2ui = await Promise.resolve().then(() => __importStar(require('../pi-ecosystem-a2ui/index.js')));
|
|
2590
|
+
for (const t of a2ui.A2UI_TOOL_DEFS) {
|
|
2591
|
+
ctx.tools.set(t.name, {
|
|
2592
|
+
name: t.name,
|
|
2593
|
+
description: t.description,
|
|
2594
|
+
parameters: t.params,
|
|
2595
|
+
execute: async (args) => {
|
|
2596
|
+
const r = a2ui.dispatchA2uiMessage(t.build(args));
|
|
2597
|
+
return r.success ? { success: true, output: r.output } : { success: false, error: r.output };
|
|
2598
|
+
},
|
|
2599
|
+
});
|
|
2600
|
+
}
|
|
2601
|
+
}
|
|
2602
|
+
catch { /* A2UI 工具注册失败静默 */ }
|
|
2603
|
+
})();
|
|
2604
|
+
// 2026-08-13 (Phase E1): Agent 服务 Registry — 注册/发现服务 (Agent Economic Network Discovery 层)
|
|
2605
|
+
(async () => {
|
|
2606
|
+
try {
|
|
2607
|
+
const registry = await Promise.resolve().then(() => __importStar(require('../agents/agent-registry.js')));
|
|
2608
|
+
// 注册自己为服务提供者
|
|
2609
|
+
ctx.tools.set('registry_register', {
|
|
2610
|
+
name: 'registry_register',
|
|
2611
|
+
description: '在 Agent 服务注册表注册自己的服务 (定价/能力/钱包). 让其他 Agent 能发现并调用你. 参数: service_name(如 research), description, price_amount, price_currency(USDC), price_per(如 query), capabilities(数组), wallet(收款地址)',
|
|
2612
|
+
parameters: {
|
|
2613
|
+
service_name: '服务名 (必填, 如 research/coding/data)',
|
|
2614
|
+
description: '服务描述 (必填)',
|
|
2615
|
+
price_amount: '单价金额 (必填, 如 0.05)',
|
|
2616
|
+
price_currency: '计价币种 (默认 USDC)',
|
|
2617
|
+
price_per: '计价单位 (默认 query)',
|
|
2618
|
+
capabilities: '能力数组 (可选)',
|
|
2619
|
+
wallet: '收款钱包地址 (必填)',
|
|
2620
|
+
},
|
|
2621
|
+
execute: async (args) => {
|
|
2622
|
+
const serviceName = String(args.service_name || '').trim();
|
|
2623
|
+
const wallet = String(args.wallet || '').trim();
|
|
2624
|
+
if (!serviceName || !wallet)
|
|
2625
|
+
return { success: false, error: 'service_name 和 wallet 必填' };
|
|
2626
|
+
const reg = registry.getAgentRegistry();
|
|
2627
|
+
const r = await reg.register({
|
|
2628
|
+
agentId: ctx.identity?.did || `did:local:${ctx.identity?.name || 'agent'}`,
|
|
2629
|
+
name: ctx.identity?.name || 'agent',
|
|
2630
|
+
wallet,
|
|
2631
|
+
service: {
|
|
2632
|
+
name: serviceName,
|
|
2633
|
+
description: String(args.description || `${serviceName} 服务`).trim(),
|
|
2634
|
+
price: {
|
|
2635
|
+
amount: String(args.price_amount || '0'),
|
|
2636
|
+
currency: String(args.price_currency || 'USDC').toUpperCase(),
|
|
2637
|
+
per: String(args.price_per || 'query'),
|
|
2638
|
+
},
|
|
2639
|
+
},
|
|
2640
|
+
capabilities: Array.isArray(args.capabilities) ? args.capabilities.map(String) : [serviceName],
|
|
2641
|
+
});
|
|
2642
|
+
return r.ok
|
|
2643
|
+
? { success: true, output: `✅ 已注册服务: ${serviceName} (${args.price_amount || 0} ${String(args.price_currency || 'USDC').toUpperCase()}/${args.price_per || 'query'}) wallet=${wallet.slice(0, 10)}...` }
|
|
2644
|
+
: { success: false, error: r.error };
|
|
2645
|
+
},
|
|
2646
|
+
});
|
|
2647
|
+
// 发现可用的 Agent 服务
|
|
2648
|
+
ctx.tools.set('registry_discover', {
|
|
2649
|
+
name: 'registry_discover',
|
|
2650
|
+
description: '在 Agent 服务注册表发现服务 (按名称/能力/描述). 找到服务后可用 x402 支付调用. query: 搜索词 (如 research/compute/data).',
|
|
2651
|
+
parameters: { query: '搜索词 (可选, 空=列出全部)' },
|
|
2652
|
+
execute: async (args) => {
|
|
2653
|
+
const q = String(args.query || '').trim();
|
|
2654
|
+
const reg = registry.getAgentRegistry();
|
|
2655
|
+
const services = await reg.discover(q);
|
|
2656
|
+
if (services.length === 0)
|
|
2657
|
+
return { success: true, output: '(注册表无匹配服务)' };
|
|
2658
|
+
const lines = services.slice(0, 20).map((s) => ` [${s.service.name}] ${s.name} (${s.service.price.amount} ${s.service.price.currency}/${s.service.price.per}) wallet=${String(s.wallet).slice(0, 12)}... agent=${s.agentId.slice(0, 24)}...`);
|
|
2659
|
+
return { success: true, output: `发现 ${services.length} 个服务:\n${lines.join('\n')}` };
|
|
2660
|
+
},
|
|
2661
|
+
});
|
|
2662
|
+
}
|
|
2663
|
+
catch { /* registry 工具注册失败静默 */ }
|
|
2664
|
+
})();
|
|
2665
|
+
// 2026-08-13 (Phase E2): service_call — 调用 Agent 服务 (x402 402 自动支付闭环)
|
|
2666
|
+
ctx.tools.set('service_call', {
|
|
2667
|
+
name: 'service_call',
|
|
2668
|
+
description: '调用注册表里的 Agent 服务 (x402 支付闭环). 从 registry_discover 找到服务名后调用. service_name: 服务名; args: 服务参数; 有钱包私钥时自动支付 402.',
|
|
2669
|
+
parameters: {
|
|
2670
|
+
service_name: '服务名 (必填, registry_discover 查到的)',
|
|
2671
|
+
args: '服务参数 JSON (可选)',
|
|
2672
|
+
max_payment_amount: '最大支付金额 (可选)',
|
|
2673
|
+
},
|
|
2674
|
+
execute: async (args) => {
|
|
2675
|
+
const serviceName = String(args.service_name || '').trim();
|
|
2676
|
+
if (!serviceName)
|
|
2677
|
+
return { success: false, error: 'service_name 必填 (先用 registry_discover 查)' };
|
|
2678
|
+
try {
|
|
2679
|
+
const { serviceCall } = await Promise.resolve().then(() => __importStar(require('./agent-service-client.js')));
|
|
2680
|
+
// 尝试取 channel 钱包私钥 (autoPay)
|
|
2681
|
+
let privateKey;
|
|
2682
|
+
try {
|
|
2683
|
+
const wallet = await ctx.getChannelWallet?.();
|
|
2684
|
+
if (wallet?.encryptedPrivateKey)
|
|
2685
|
+
privateKey = wallet.encryptedPrivateKey;
|
|
2686
|
+
}
|
|
2687
|
+
catch { /* 无钱包 */ }
|
|
2688
|
+
let argsObj = {};
|
|
2689
|
+
try {
|
|
2690
|
+
argsObj = JSON.parse(String(args.args || '{}'));
|
|
2691
|
+
}
|
|
2692
|
+
catch {
|
|
2693
|
+
argsObj = {};
|
|
2694
|
+
}
|
|
2695
|
+
const r = await serviceCall({
|
|
2696
|
+
serviceName,
|
|
2697
|
+
args: argsObj,
|
|
2698
|
+
privateKey,
|
|
2699
|
+
maxPaymentAmount: String(args.max_payment_amount || '').trim() || undefined,
|
|
2700
|
+
});
|
|
2701
|
+
if (!r.success)
|
|
2702
|
+
return { success: false, error: r.error, service: r.service?.service?.name };
|
|
2703
|
+
return { success: true, output: r.output, paid: r.paid, txHash: r.txHash };
|
|
2704
|
+
}
|
|
2705
|
+
catch (e) {
|
|
2706
|
+
return { success: false, error: `service_call 失败: ${String(e?.message || e).slice(0, 200)}` };
|
|
2707
|
+
}
|
|
2708
|
+
},
|
|
2709
|
+
});
|
|
2710
|
+
// 2026-08-13 (Phase E3): Policy Engine — 预算/授权 (安全核心, 私钥不暴露给 LLM)
|
|
2711
|
+
ctx.tools.set('policy_config', {
|
|
2712
|
+
name: 'policy_config',
|
|
2713
|
+
description: '查看/更新支付策略 (预算/白名单). LLM 只能查看预算与授权规则, 私钥由 Policy Engine 隔离保管. 查看: 无参数; 更新: 传要改的字段 (per_transaction_limit/daily_limit/allowed_recipients/allowed_services).',
|
|
2714
|
+
parameters: {
|
|
2715
|
+
per_transaction_limit: '可选: 单笔上限 (数字)',
|
|
2716
|
+
daily_limit: '可选: 每日预算 (数字)',
|
|
2717
|
+
allowed_recipients: '可选: 允许收款方数组',
|
|
2718
|
+
allowed_services: '可选: 允许服务数组',
|
|
2719
|
+
},
|
|
2720
|
+
execute: async (args) => {
|
|
2721
|
+
try {
|
|
2722
|
+
const { getEconomicPolicy } = await Promise.resolve().then(() => __importStar(require('./economic-policy.js')));
|
|
2723
|
+
const policy = getEconomicPolicy();
|
|
2724
|
+
const patch = {};
|
|
2725
|
+
if (args.per_transaction_limit !== undefined)
|
|
2726
|
+
patch.perTransactionLimit = Number(args.per_transaction_limit);
|
|
2727
|
+
if (args.daily_limit !== undefined)
|
|
2728
|
+
patch.dailyLimit = Number(args.daily_limit);
|
|
2729
|
+
if (Array.isArray(args.allowed_recipients))
|
|
2730
|
+
patch.allowedRecipients = args.allowed_recipients.map(String);
|
|
2731
|
+
if (Array.isArray(args.allowed_services))
|
|
2732
|
+
patch.allowedServices = args.allowed_services.map(String);
|
|
2733
|
+
if (Object.keys(patch).length > 0)
|
|
2734
|
+
policy.updateConfig(patch);
|
|
2735
|
+
const spent = await policy.dailySpent();
|
|
2736
|
+
const c = policy.config();
|
|
2737
|
+
return {
|
|
2738
|
+
success: true,
|
|
2739
|
+
output: `支付策略:\n 单笔上限: $${c.perTransactionLimit}\n 每日预算: $${c.dailyLimit} (今日已用 $${spent})\n 允许收款方: ${c.allowedRecipients.length ? c.allowedRecipients.join(', ') : '(全部)'}\n 允许服务: ${c.allowedServices.length ? c.allowedServices.join(', ') : '(全部)'}\n 速率: ${c.rateLimitPerMinute}/min`,
|
|
2740
|
+
};
|
|
2741
|
+
}
|
|
2742
|
+
catch (e) {
|
|
2743
|
+
return { success: false, error: `policy_config 失败: ${String(e?.message || e).slice(0, 200)}` };
|
|
2744
|
+
}
|
|
2745
|
+
},
|
|
2746
|
+
});
|
|
2747
|
+
// 2026-08-13 (Phase M4): Reputation — 服务结果记录 + 信誉查询 (Agent Economic Protocol §7)
|
|
2748
|
+
ctx.tools.set('reputation_update', {
|
|
2749
|
+
name: 'reputation_update',
|
|
2750
|
+
description: '记录一次服务结果, 更新服务提供者的信誉 (success/failed/disputed → score). 服务完成后调用. agent_id: 服务提供者 DID; service_name: 服务名; outcome: success|failed|disputed.',
|
|
2751
|
+
parameters: { agent_id: '服务提供者 agent_id (必填)', service_name: '服务名 (必填)', outcome: '结果: success|failed|disputed (必填)' },
|
|
2752
|
+
execute: async (args) => {
|
|
2753
|
+
try {
|
|
2754
|
+
const { recordServiceOutcome } = await Promise.resolve().then(() => __importStar(require('./agent-reputation.js')));
|
|
2755
|
+
const agentId = String(args.agent_id || '').trim();
|
|
2756
|
+
const serviceName = String(args.service_name || '').trim();
|
|
2757
|
+
const outcome = String(args.outcome || '').trim();
|
|
2758
|
+
if (!agentId || !serviceName || !['success', 'failed', 'disputed'].includes(outcome)) {
|
|
2759
|
+
return { success: false, error: 'agent_id/service_name/outcome(success|failed|disputed) 必填' };
|
|
2760
|
+
}
|
|
2761
|
+
const r = await recordServiceOutcome(agentId, serviceName, outcome);
|
|
2762
|
+
if (!r.ok)
|
|
2763
|
+
return { success: false, error: r.error };
|
|
2764
|
+
return { success: true, output: `✅ 已记录 ${outcome}: tasks=${r.reputation?.tasks}, score=${r.reputation?.score}` };
|
|
2765
|
+
}
|
|
2766
|
+
catch (e) {
|
|
2767
|
+
return { success: false, error: `reputation_update 失败: ${String(e?.message || e).slice(0, 200)}` };
|
|
2768
|
+
}
|
|
2769
|
+
},
|
|
2770
|
+
});
|
|
2771
|
+
ctx.tools.set('reputation_query', {
|
|
2772
|
+
name: 'reputation_query',
|
|
2773
|
+
description: '查询 Agent 的信誉 (成功率/任务数). agent_id: 服务提供者 DID; service_name: 可选. 选择服务前先查信誉.',
|
|
2774
|
+
parameters: { agent_id: '服务提供者 agent_id (必填)', service_name: '服务名 (可选)' },
|
|
2775
|
+
execute: async (args) => {
|
|
2776
|
+
try {
|
|
2777
|
+
const { queryReputation, formatReputation } = await Promise.resolve().then(() => __importStar(require('./agent-reputation.js')));
|
|
2778
|
+
const r = await queryReputation(String(args.agent_id || '').trim(), String(args.service_name || '').trim() || undefined);
|
|
2779
|
+
if (!r.ok)
|
|
2780
|
+
return { success: true, output: r.error || '(无信誉记录)' };
|
|
2781
|
+
return { success: true, output: r.entries.map((e) => ` [${e.service}] ${formatReputation(e.reputation)}`).join('\n') };
|
|
2782
|
+
}
|
|
2783
|
+
catch (e) {
|
|
2784
|
+
return { success: false, error: `reputation_query 失败: ${String(e?.message || e).slice(0, 200)}` };
|
|
2785
|
+
}
|
|
2786
|
+
},
|
|
2787
|
+
});
|
|
2788
|
+
// 2026-08-13: Treasury 桥 — 链下结算 (Policy/Registry) 驱动链上 Treasury.payAgent
|
|
2789
|
+
// 配置: BOLLOON_TREASURY_RPC / BOLLOON_TREASURY_ADDRESS / BOLLOON_TREASURY_KEY / BOLLOON_TREASURY_TOKEN
|
|
2790
|
+
const treasuryConfigFromEnv = () => {
|
|
2791
|
+
const rpcUrl = process.env.BOLLOON_TREASURY_RPC || '';
|
|
2792
|
+
const treasuryAddress = process.env.BOLLOON_TREASURY_ADDRESS || '';
|
|
2793
|
+
const tokenAddress = process.env.BOLLOON_TREASURY_TOKEN || '';
|
|
2794
|
+
const privateKey = process.env.BOLLOON_TREASURY_KEY || '';
|
|
2795
|
+
return { rpcUrl, treasuryAddress, tokenAddress, privateKey };
|
|
2796
|
+
};
|
|
2797
|
+
ctx.tools.set('treasury_pay', {
|
|
2798
|
+
name: 'treasury_pay',
|
|
2799
|
+
description: '从 Treasury 支付给 Agent (链上 AgentTreasury.payAgent). 自动过 Policy 预算校验 + 信誉门槛 (链上). agent_address: 收款 Agent 地址; amount: 金额 (USDC). 需配置 BOLLOON_TREASURY_* 环境变量 (RPC/合约/私钥).',
|
|
2800
|
+
parameters: { agent_address: '收款 Agent 钱包地址 (必填)', amount: '金额 USDC (必填)', service: '服务名 (可选, Policy 校验用)' },
|
|
2801
|
+
execute: async (args) => {
|
|
2802
|
+
const cfg = treasuryConfigFromEnv();
|
|
2803
|
+
if (!cfg.rpcUrl || !cfg.treasuryAddress || !cfg.privateKey) {
|
|
2804
|
+
return { success: false, error: '未配置 Treasury (BOLLOON_TREASURY_RPC/ADDRESS/KEY/TOKEN)' };
|
|
2805
|
+
}
|
|
2806
|
+
const agentAddress = String(args.agent_address || '').trim();
|
|
2807
|
+
const amount = Number(args.amount);
|
|
2808
|
+
if (!agentAddress || !amount || amount <= 0)
|
|
2809
|
+
return { success: false, error: 'agent_address 和 amount(>0) 必填' };
|
|
2810
|
+
try {
|
|
2811
|
+
const { treasuryPay } = await Promise.resolve().then(() => __importStar(require('./treasury-bridge.js')));
|
|
2812
|
+
const r = await treasuryPay(cfg, {
|
|
2813
|
+
agentAddress,
|
|
2814
|
+
amount,
|
|
2815
|
+
service: String(args.service || '').trim() || undefined,
|
|
2816
|
+
});
|
|
2817
|
+
if (!r.success)
|
|
2818
|
+
return { success: false, error: r.error };
|
|
2819
|
+
return { success: true, output: `✅ Treasury 已支付 ${amount} USDC → ${agentAddress.slice(0, 10)}... tx=${r.txHash}` };
|
|
2820
|
+
}
|
|
2821
|
+
catch (e) {
|
|
2822
|
+
return { success: false, error: `treasury_pay 失败: ${String(e?.message || e).slice(0, 200)}` };
|
|
2823
|
+
}
|
|
2824
|
+
},
|
|
2825
|
+
});
|
|
2826
|
+
ctx.tools.set('treasury_status', {
|
|
2827
|
+
name: 'treasury_status',
|
|
2828
|
+
description: '查询 Treasury 状态 (余额/日限/冻结). 需配置 BOLLOON_TREASURY_* 环境变量.',
|
|
2829
|
+
parameters: {},
|
|
2830
|
+
execute: async () => {
|
|
2831
|
+
const cfg = treasuryConfigFromEnv();
|
|
2832
|
+
if (!cfg.rpcUrl || !cfg.treasuryAddress) {
|
|
2833
|
+
return { success: false, error: '未配置 Treasury (BOLLOON_TREASURY_RPC/ADDRESS)' };
|
|
2834
|
+
}
|
|
2835
|
+
try {
|
|
2836
|
+
const { treasuryStatus } = await Promise.resolve().then(() => __importStar(require('./treasury-bridge.js')));
|
|
2837
|
+
const s = await treasuryStatus(cfg);
|
|
2838
|
+
if (!s.ok)
|
|
2839
|
+
return { success: false, error: s.error };
|
|
2840
|
+
return { success: true, output: `Treasury:\n 余额: ${s.balance} USDC\n 日限: ${s.dailyLimit} USDC\n 冻结: ${s.frozen ? '是' : '否'}` };
|
|
2841
|
+
}
|
|
2842
|
+
catch (e) {
|
|
2843
|
+
return { success: false, error: `treasury_status 失败: ${String(e?.message || e).slice(0, 200)}` };
|
|
2844
|
+
}
|
|
2845
|
+
},
|
|
2846
|
+
});
|
|
2847
|
+
// 2026-08-14: Agent Gateway — 经济协调层统一入口 (注册/调用/加入网络/状态)
|
|
2848
|
+
ctx.tools.set('gateway_register', {
|
|
2849
|
+
name: 'gateway_register',
|
|
2850
|
+
description: '把本 Agent 注册为服务提供者 (Agent Gateway 入口). capability: 能力名 (research/coding/data...); price: 价格; per: 计价单位; wallet: 收款地址. 注册后其他 Agent 能发现并调用你.',
|
|
2851
|
+
parameters: {
|
|
2852
|
+
capability: '能力名 (必填, research/coding/data/compute...)',
|
|
2853
|
+
price: '价格 (必填, 如 0.05)',
|
|
2854
|
+
currency: '币种 (默认 USDC)',
|
|
2855
|
+
per: '计价单位 (默认 query)',
|
|
2856
|
+
description: '服务描述 (可选)',
|
|
2857
|
+
wallet: '收款钱包地址 (可选)',
|
|
2858
|
+
},
|
|
2859
|
+
execute: async (args) => {
|
|
2860
|
+
const { gatewayRegisterAgent } = await Promise.resolve().then(() => __importStar(require('./agent-gateway.js')));
|
|
2861
|
+
const r = await gatewayRegisterAgent({
|
|
2862
|
+
capability: String(args.capability || '').trim(),
|
|
2863
|
+
price: String(args.price || '').trim(),
|
|
2864
|
+
currency: String(args.currency || 'USDC'),
|
|
2865
|
+
per: String(args.per || 'query'),
|
|
2866
|
+
description: String(args.description || '').trim() || undefined,
|
|
2867
|
+
wallet: String(args.wallet || '').trim() || undefined,
|
|
2868
|
+
}, { did: ctx.identity?.did || '', name: ctx.identity?.name || 'agent', wallet: String(args.wallet || '') });
|
|
2869
|
+
return r.ok
|
|
2870
|
+
? { success: true, output: `✅ 已加入 Agent 网络: ${args.capability} @ ${args.price} ${args.currency}/${args.per}` }
|
|
2871
|
+
: { success: false, error: r.error };
|
|
2872
|
+
},
|
|
2873
|
+
});
|
|
2874
|
+
ctx.tools.set('gateway_call', {
|
|
2875
|
+
name: 'gateway_call',
|
|
2876
|
+
description: '通过 Agent Gateway 调用服务 (自动闭环: 发现→预算→验证门→执行→信誉). task: 任务描述; budget: 预算 (USDC); capability: 需要的能力. 需人工确认的支付会返回 approval 待审批.',
|
|
2877
|
+
parameters: {
|
|
2878
|
+
task: '任务描述 (必填)',
|
|
2879
|
+
budget: '预算 USDC (必填)',
|
|
2880
|
+
capability: '需要的能力 (可选, 如 research)',
|
|
2881
|
+
url: '服务端点 URL (可选)',
|
|
2882
|
+
},
|
|
2883
|
+
execute: async (args) => {
|
|
2884
|
+
const task = String(args.task || '').trim();
|
|
2885
|
+
const budget = Number(args.budget);
|
|
2886
|
+
if (!task || !budget || budget <= 0)
|
|
2887
|
+
return { success: false, error: 'task 和 budget(>0) 必填' };
|
|
2888
|
+
try {
|
|
2889
|
+
const { gatewayCallAgent } = await Promise.resolve().then(() => __importStar(require('./agent-gateway.js')));
|
|
2890
|
+
const r = await gatewayCallAgent({
|
|
2891
|
+
task,
|
|
2892
|
+
budget,
|
|
2893
|
+
capability: String(args.capability || '').trim() || undefined,
|
|
2894
|
+
url: String(args.url || '').trim() || undefined,
|
|
2895
|
+
});
|
|
2896
|
+
if (r.success)
|
|
2897
|
+
return { success: true, output: `✅ ${r.provider}: ${r.output}` };
|
|
2898
|
+
return { success: false, error: r.error, requiresApproval: r.requiresApproval, approvalId: r.approvalId, provider: r.provider };
|
|
2899
|
+
}
|
|
2900
|
+
catch (e) {
|
|
2901
|
+
return { success: false, error: `gateway_call 失败: ${String(e?.message || e).slice(0, 200)}` };
|
|
2902
|
+
}
|
|
2903
|
+
},
|
|
2904
|
+
});
|
|
2905
|
+
ctx.tools.set('gateway_join', {
|
|
2906
|
+
name: 'gateway_join',
|
|
2907
|
+
description: '通过链接加入共享 Agent 网络 (拉取远端服务合并到本地 registry). link: ipns://<name> 或 orbitdb://<address> 或 https://.../registry. 加入后可用 gateway_call 调用网络里的服务.',
|
|
2908
|
+
parameters: { link: '网络链接 (必填, ipns:// / orbitdb:// / https://)' },
|
|
2909
|
+
execute: async (args) => {
|
|
2910
|
+
const link = String(args.link || '').trim();
|
|
2911
|
+
if (!link)
|
|
2912
|
+
return { success: false, error: 'link 必填 (ipns:// / orbitdb:// / https://)' };
|
|
2913
|
+
// 2026-09-15: 入网说明文档 (.md) 不是 registry 链接 — 别静默当 registry 拉, 指到正确工具
|
|
2914
|
+
if (/^https?:\/\//i.test(link) && (/\.md(\?|#|$)/i.test(link) || /gateway-join/i.test(link))) {
|
|
2915
|
+
return { success: false, error: `这是一个「入网说明文档」而不是网络链接。请改用 join_global_gateway (url=${link}) 走文档驱动入网。` };
|
|
2916
|
+
}
|
|
2917
|
+
try {
|
|
2918
|
+
const { joinNetwork } = await Promise.resolve().then(() => __importStar(require('./gateway-network.js')));
|
|
2919
|
+
const r = await joinNetwork(link);
|
|
2920
|
+
if (!r.ok)
|
|
2921
|
+
return { success: false, error: r.error };
|
|
2922
|
+
return { success: true, output: `✅ 已加入 Agent 网络: 拉取 ${r.total} 个服务, 新增 ${r.joined} 个 (${r.linkKind})` };
|
|
2923
|
+
}
|
|
2924
|
+
catch (e) {
|
|
2925
|
+
return { success: false, error: `gateway_join 失败: ${String(e?.message || e).slice(0, 200)}` };
|
|
2926
|
+
}
|
|
2927
|
+
},
|
|
2928
|
+
});
|
|
2929
|
+
// ============================================================
|
|
2930
|
+
// join_global_gateway (2026-09-15) — 文档驱动的「加入全球智能体网络」
|
|
2931
|
+
// 人类口令只有一句: `read https://bolloon.cn/bolloon-gateway-join.md`
|
|
2932
|
+
// 文档写的是 SDK 伪码, agent 手里只有工具 → 本工具把整条链路串成闭环:
|
|
2933
|
+
// 读说明 → DID → P2P 节点 → 注册 manifest → 建成可分享网络 → 服务登记 → 落盘(幂等)
|
|
2934
|
+
// ============================================================
|
|
2935
|
+
ctx.tools.set('join_global_gateway', {
|
|
2936
|
+
name: 'join_global_gateway',
|
|
2937
|
+
description: '按「入网说明文档」加入全球智能体网络 (完整闭环: 读文档 → DID 身份 → P2P 节点 → 注册本地 manifest → 建成可分享的网络 → 服务登记 → 落盘). 默认文档即 https://bolloon.cn/bolloon-gateway-join.md (人类口令 "read <该文档>" 时用本工具)。幂等: 重复入网返回 already。文档不可达或不是入网说明 → 如实失败, 不假装入网。',
|
|
2938
|
+
parameters: {
|
|
2939
|
+
url: '入网说明文档 URL (可选, 默认 https://bolloon.cn/bolloon-gateway-join.md)',
|
|
2940
|
+
name: '本智能体名字 (可选, 默认取当前身份名)',
|
|
2941
|
+
capabilities: '声明能力, 逗号分隔 (可选, 默认 chat,gateway-join)',
|
|
2942
|
+
force: 'true = 忽略幂等重新入网 (可选)',
|
|
2943
|
+
},
|
|
2944
|
+
execute: async (args) => {
|
|
2945
|
+
try {
|
|
2946
|
+
const { joinGlobalGateway } = await Promise.resolve().then(() => __importStar(require('./gateway-join.js')));
|
|
2947
|
+
const caps = String(args.capabilities || '').split(',').map((s) => s.trim()).filter(Boolean);
|
|
2948
|
+
const r = await joinGlobalGateway({
|
|
2949
|
+
url: String(args.url || '').trim() || undefined,
|
|
2950
|
+
did: ctx.identity?.did || undefined,
|
|
2951
|
+
name: String(args.name || '').trim() || ctx.identity?.name || undefined,
|
|
2952
|
+
capabilities: caps.length ? caps : undefined,
|
|
2953
|
+
force: String(args.force || '') === 'true',
|
|
2954
|
+
});
|
|
2955
|
+
const lines = r.steps.map((s) => `${s.ok ? '✓' : '✗'} ${s.step}: ${s.note}`);
|
|
2956
|
+
if (!r.ok)
|
|
2957
|
+
return { success: false, error: `${r.error}\n${lines.join('\n')}` };
|
|
2958
|
+
return {
|
|
2959
|
+
success: true,
|
|
2960
|
+
output: `🌐 已加入全球智能体网络${r.already ? ' (已在网, 幂等)' : ''}\nDID: ${r.did}\npeerId: ${r.peerId || '(节点后台启动中)'}${r.networkLink ? `\n可分享网络链接: ${r.networkLink}` : ''}\n${lines.join('\n')}`,
|
|
2961
|
+
};
|
|
2962
|
+
}
|
|
2963
|
+
catch (e) {
|
|
2964
|
+
return { success: false, error: `join_global_gateway 失败: ${String(e?.message || e).slice(0, 200)}` };
|
|
2965
|
+
}
|
|
2966
|
+
},
|
|
2967
|
+
});
|
|
2968
|
+
ctx.tools.set('gateway_status', {
|
|
2969
|
+
name: 'gateway_status',
|
|
2970
|
+
description: '查看 Agent 网络状态 (已注册服务 + 信誉 + 已加入的网络).',
|
|
2971
|
+
parameters: {},
|
|
2972
|
+
execute: async () => {
|
|
2973
|
+
try {
|
|
2974
|
+
const { gatewayStatus } = await Promise.resolve().then(() => __importStar(require('./agent-gateway.js')));
|
|
2975
|
+
const { listJoinedNetworks } = await Promise.resolve().then(() => __importStar(require('./gateway-network.js')));
|
|
2976
|
+
const nets = await listJoinedNetworks();
|
|
2977
|
+
const netLine = nets.length
|
|
2978
|
+
? `\n已加入网络 (${nets.length}):\n` + nets.slice(0, 10).map((n) => ` [${n.kind}] ${n.name || n.link.slice(0, 48)} (${n.serviceCount} 服务, ${n.joinedAt.slice(0, 10)})`).join('\n')
|
|
2979
|
+
: '\n(未加入任何网络, 收到 orbitdb:// 链接会自动加入, 或用 gateway_join)';
|
|
2980
|
+
return { success: true, output: (await gatewayStatus()) + netLine };
|
|
2981
|
+
}
|
|
2982
|
+
catch (e) {
|
|
2983
|
+
return { success: false, error: `gateway_status 失败: ${String(e?.message || e).slice(0, 200)}` };
|
|
2984
|
+
}
|
|
2985
|
+
},
|
|
2986
|
+
});
|
|
2987
|
+
ctx.tools.set('gateway_share', {
|
|
2988
|
+
name: 'gateway_share',
|
|
2989
|
+
description: '生成本 Agent 网络的分享链接 (orbitdb://...). 把链接发给其他 Bolloon/Agent, 对方收到后会自动加入你的网络并发现你注册的服务.',
|
|
2990
|
+
parameters: {
|
|
2991
|
+
name: '网络名 (可选, 默认 registry 名)',
|
|
2992
|
+
},
|
|
2993
|
+
execute: async (args) => {
|
|
2994
|
+
try {
|
|
2995
|
+
const { shareNetworkLink } = await Promise.resolve().then(() => __importStar(require('./gateway-network.js')));
|
|
2996
|
+
const name = String(args.name || '').trim() || undefined;
|
|
2997
|
+
const r = await shareNetworkLink(name ? { name } : undefined);
|
|
2998
|
+
if (!r.ok)
|
|
2999
|
+
return { success: false, error: r.error };
|
|
3000
|
+
return { success: true, output: `🔗 Agent 网络分享链接 (对方收到后自动加入):\n${r.link}` };
|
|
3001
|
+
}
|
|
3002
|
+
catch (e) {
|
|
3003
|
+
return { success: false, error: `gateway_share 失败: ${String(e?.message || e).slice(0, 200)}` };
|
|
3004
|
+
}
|
|
3005
|
+
},
|
|
3006
|
+
});
|
|
3007
|
+
// ============================================================
|
|
3008
|
+
// publish_did (2026-08-03) — 把当前 agent 的 DID 发布到 IPFS + IPNS
|
|
3009
|
+
// 全自动: 自动安装/启动本地 Kubo → 上传 DID 文档 → 发布 IPNS name
|
|
3010
|
+
// 实现: @diap/sdk (AgentAuthManager + publishAfterUpload)
|
|
3011
|
+
// ============================================================
|
|
3012
|
+
ctx.tools.set('publish_did', {
|
|
3013
|
+
name: 'publish_did',
|
|
3014
|
+
description: '把当前 agent 的 DID 身份发布到本地 IPFS + IPNS (自动安装启动 Kubo). 返回 DID + CID (IPFS 内容地址) + IPNS name (稳定可解析标识). 跨节点发现和身份解析依赖它.',
|
|
3015
|
+
parameters: {
|
|
3016
|
+
name: '可选: 发布显示名 (默认 agentId)',
|
|
3017
|
+
},
|
|
3018
|
+
execute: async (args) => {
|
|
3019
|
+
try {
|
|
3020
|
+
const agentId = String(ctx.agentId || '').trim();
|
|
3021
|
+
const { loadOrCreateAgentIdentity } = await Promise.resolve().then(() => __importStar(require('./agent-identity.js')));
|
|
3022
|
+
const identity = loadOrCreateAgentIdentity(agentId || 'default-agent');
|
|
3023
|
+
const { KeyManager } = await Promise.resolve().then(() => __importStar(require('@diap/sdk')));
|
|
3024
|
+
const kp = KeyManager.fromPrivateKey(Buffer.from(identity.privateKey, 'hex'));
|
|
3025
|
+
const displayName = args.name ? String(args.name) : agentId || 'bolloon-agent';
|
|
3026
|
+
// 1. 确保本地 Kubo (自动安装 + 启动)
|
|
3027
|
+
const sdk = await Promise.resolve().then(() => __importStar(require('@diap/sdk')));
|
|
3028
|
+
const checkKuboSetup = sdk.checkKuboSetup;
|
|
3029
|
+
if (typeof checkKuboSetup === 'function') {
|
|
3030
|
+
const setup = await checkKuboSetup(true, true);
|
|
3031
|
+
if (!setup?.ready || !setup?.daemonRunning) {
|
|
3032
|
+
return { success: false, error: '本地 Kubo 不可用 (自动安装失败), 无法发布到 IPFS' };
|
|
3033
|
+
}
|
|
3034
|
+
}
|
|
3035
|
+
// 2. 注册 agent → 上传 DID 文档 → CID
|
|
3036
|
+
const { AgentAuthManager } = await Promise.resolve().then(() => __importStar(require('@diap/sdk')));
|
|
3037
|
+
const auth = await AgentAuthManager.newWithRemoteIpfs('http://127.0.0.1:5001', 'http://127.0.0.1:8080');
|
|
3038
|
+
const result = await auth.registerAgent({ name: displayName, services: [] }, kp, '');
|
|
3039
|
+
const cid = result.cid || result.didDocCid;
|
|
3040
|
+
if (!cid)
|
|
3041
|
+
return { success: false, error: 'DID 上传成功但未拿到 CID' };
|
|
3042
|
+
// 3. 发布 IPNS name (稳定标识)
|
|
3043
|
+
let ipnsName = '';
|
|
3044
|
+
try {
|
|
3045
|
+
const ipfs = await sdk.IpfsClient.newWithRemoteNode('http://127.0.0.1:5001', 'http://127.0.0.1:8080');
|
|
3046
|
+
// 2026-08-06 fix: 之前传 kp (KeyPair 对象) 当 keyName → ensureKeyExists 拿对象比
|
|
3047
|
+
// 字符串永远 false → key/gen 自动生成名为 "[object Object]" 的 key.
|
|
3048
|
+
// 改用确定性 key 名 (与 did-builder.ts 一致: did-<did 冒号转横线>).
|
|
3049
|
+
const keyName = `did-${String(identity.did || '').replace(':', '-').replace(' ', '')}` || 'self';
|
|
3050
|
+
const pub = await ipfs.publishAfterUpload?.(cid, keyName);
|
|
3051
|
+
ipnsName = pub?.name || pub?.ipnsName || '';
|
|
3052
|
+
}
|
|
3053
|
+
catch { /* IPNS 失败不致命, CID 仍可用 */ }
|
|
3054
|
+
// 2026-08-06: 发布诊断 — 节点地址 + 公网可达性提示 (IPNS 记录进 DHT 但内容拉取依赖源节点可达)
|
|
3055
|
+
let diag = '';
|
|
3056
|
+
try {
|
|
3057
|
+
const id = await kuboApi('/api/v0/id');
|
|
3058
|
+
const addrs = id?.Addresses || [];
|
|
3059
|
+
const pubAddrs = addrs.filter((a) => !/127\.0\.0\.1|::1|10\.|100\.|192\.168|172\.(1[6-9]|2\d|3[01])\./.test(a));
|
|
3060
|
+
const peers = await kuboApi('/api/v0/swarm/peers');
|
|
3061
|
+
const peerCount = peers?.Peers?.length || 0;
|
|
3062
|
+
diag = `\n [诊断] 节点在线, ${peerCount} peers; 公网可达地址 ${pubAddrs.length} 个${pubAddrs.length === 0 ? ' ⚠️ 无公网地址 (NAT 后), 公网用户只能解析 IPNS 但拉不到内容, 建议 pin 到公共服务或配置端口映射' : ''}`;
|
|
3063
|
+
}
|
|
3064
|
+
catch { /* 诊断失败静默 */ }
|
|
3065
|
+
return {
|
|
3066
|
+
success: true,
|
|
3067
|
+
output: `✅ DID 已发布到 IPFS:\n DID: ${identity.did}\n CID: ${cid}\n IPNS: ${ipnsName || '(发布失败, CID 仍可用)'}\n 读回验证: curl -X POST "http://127.0.0.1:5001/api/v0/cat?arg=${cid}"${diag}`,
|
|
3068
|
+
};
|
|
3069
|
+
}
|
|
3070
|
+
catch (e) {
|
|
3071
|
+
return { success: false, error: `publish_did 失败: ${String(e).slice(0, 200)}` };
|
|
3072
|
+
}
|
|
3073
|
+
}
|
|
3074
|
+
});
|
|
3075
|
+
// ============================================================
|
|
3076
|
+
// IPFS / IPNS 通用工具 (2026-08-04) — 查询 + 发布给 agent
|
|
3077
|
+
// 依赖本地 Kubo (自动安装/启动), 复用 publish_did 的 checkKuboSetup
|
|
3078
|
+
// ============================================================
|
|
3079
|
+
ctx.tools.set('ipfs_add', {
|
|
3080
|
+
name: 'ipfs_add',
|
|
3081
|
+
description: '上传文本内容到本地 IPFS (Kubo), 返回 CID. 适合把任意内容/笔记/数据发布到去中心化网络, 之后可用 ipfs_cat 读回、ipns_publish 绑定稳定标识. 自动安装/启动本地 Kubo.',
|
|
3082
|
+
parameters: { content: '要上传的内容 (必填)', name: '可选: 文件名/标签' },
|
|
3083
|
+
execute: async (args) => {
|
|
3084
|
+
try {
|
|
3085
|
+
const content = String(args.content ?? '').trim();
|
|
3086
|
+
if (!content)
|
|
3087
|
+
return { success: false, error: 'content 必填' };
|
|
3088
|
+
await ensureKuboReady();
|
|
3089
|
+
const sdk = await Promise.resolve().then(() => __importStar(require('@diap/sdk')));
|
|
3090
|
+
const ipfs = await sdk.IpfsClient.newWithRemoteNode('http://127.0.0.1:5001', 'http://127.0.0.1:8080');
|
|
3091
|
+
const r = await ipfs.upload(content, args.name ? String(args.name) : 'data');
|
|
3092
|
+
return { success: true, output: `✅ 已上传到 IPFS:\n CID: ${r.cid}\n size: ${r.size} bytes\n 读回: ipfs_cat(cid="${r.cid}")\n 绑定稳定标识: ipns_publish(cid="${r.cid}")` };
|
|
3093
|
+
}
|
|
3094
|
+
catch (e) {
|
|
3095
|
+
return { success: false, error: `ipfs_add 失败: ${String(e.message || e).slice(0, 200)}` };
|
|
3096
|
+
}
|
|
3097
|
+
}
|
|
3098
|
+
});
|
|
3099
|
+
ctx.tools.set('ipfs_cat', {
|
|
3100
|
+
name: 'ipfs_cat',
|
|
3101
|
+
description: '按 CID 从本地 IPFS (Kubo) 读取内容. 参数 cid 可以是 ipfs_add 的返回 CID, 也可以是 ipns_resolve 解析出的 CID.',
|
|
3102
|
+
parameters: { cid: 'IPFS CID (必填)' },
|
|
3103
|
+
execute: async (args) => {
|
|
3104
|
+
try {
|
|
3105
|
+
const cid = String(args.cid || '').trim();
|
|
3106
|
+
if (!cid)
|
|
3107
|
+
return { success: false, error: 'cid 必填' };
|
|
3108
|
+
await ensureKuboReady();
|
|
3109
|
+
const text = await kuboApi(`/api/v0/cat?arg=${encodeURIComponent(cid)}`);
|
|
3110
|
+
const s = String(text ?? '');
|
|
3111
|
+
return { success: true, output: `📄 ${cid} (${s.length} 字符):\n${s.slice(0, 4000)}${s.length > 4000 ? '\n...(截断)' : ''}` };
|
|
3112
|
+
}
|
|
3113
|
+
catch (e) {
|
|
3114
|
+
return { success: false, error: `ipfs_cat 失败: ${String(e.message || e).slice(0, 200)}` };
|
|
3115
|
+
}
|
|
3116
|
+
}
|
|
3117
|
+
});
|
|
3118
|
+
ctx.tools.set('ipfs_ls', {
|
|
3119
|
+
name: 'ipfs_ls',
|
|
3120
|
+
description: '列出 IPFS CID 下的目录内容 (Kubo). 适用于 CID 指向目录 (如 ipfs_add 上传带 name 或 DID 文档目录) 时查看子项.',
|
|
3121
|
+
parameters: { cid: 'IPFS CID (必填)' },
|
|
3122
|
+
execute: async (args) => {
|
|
3123
|
+
try {
|
|
3124
|
+
const cid = String(args.cid || '').trim();
|
|
3125
|
+
if (!cid)
|
|
3126
|
+
return { success: false, error: 'cid 必填' };
|
|
3127
|
+
await ensureKuboReady();
|
|
3128
|
+
const r = await kuboApi(`/api/v0/ls?arg=${encodeURIComponent(cid)}`);
|
|
3129
|
+
const objs = r?.Objects || [];
|
|
3130
|
+
const obj = objs[0];
|
|
3131
|
+
const links = obj?.Links || [];
|
|
3132
|
+
if (links.length === 0 && obj?.Type === 2) {
|
|
3133
|
+
return { success: true, output: `📄 ${cid} 是单个文件 (${obj.Size ?? '?'} bytes), 不是目录` };
|
|
3134
|
+
}
|
|
3135
|
+
const lines = links.map((l) => ` ${l.Type === 1 ? '📁' : '📄'} ${l.Name} ${l.Size} bytes ${l.Hash}`);
|
|
3136
|
+
return { success: true, output: `📂 ${cid} (${links.length} 项):\n${lines.join('\n') || ' (空目录)'}` };
|
|
3137
|
+
}
|
|
3138
|
+
catch (e) {
|
|
3139
|
+
return { success: false, error: `ipfs_ls 失败: ${String(e.message || e).slice(0, 200)}` };
|
|
3140
|
+
}
|
|
3141
|
+
}
|
|
3142
|
+
});
|
|
3143
|
+
ctx.tools.set('ipns_publish', {
|
|
3144
|
+
name: 'ipns_publish',
|
|
3145
|
+
description: '把 IPFS CID 发布为 IPNS name (稳定标识, 内容更新后 name 不变). 默认用 self key (agent 身份), 可指定已有 key. 发布后任何节点可用 ipns_resolve 解析该 name 得到 CID.',
|
|
3146
|
+
parameters: { cid: 'IPFS CID (必填, 通常是 ipfs_add 的返回)', keyName: '可选: Kubo key 名 (默认 self)' },
|
|
3147
|
+
execute: async (args) => {
|
|
3148
|
+
try {
|
|
3149
|
+
const cid = String(args.cid || '').trim();
|
|
3150
|
+
if (!cid)
|
|
3151
|
+
return { success: false, error: 'cid 必填' };
|
|
3152
|
+
await ensureKuboReady();
|
|
3153
|
+
const sdk = await Promise.resolve().then(() => __importStar(require('@diap/sdk')));
|
|
3154
|
+
const ipfs = await sdk.IpfsClient.newWithRemoteNode('http://127.0.0.1:5001', 'http://127.0.0.1:8080');
|
|
3155
|
+
const keyName = String(args.keyName || 'self').trim() || 'self';
|
|
3156
|
+
await ipfs.ensureKeyExists(keyName);
|
|
3157
|
+
const r = await ipfs.publishIpns(cid, keyName, '8760h', '1h');
|
|
3158
|
+
// 2026-08-06: 诊断 — 公网可达性提示 (IPNS 记录进 DHT, 内容拉取依赖源节点公网可达)
|
|
3159
|
+
let diag = '';
|
|
3160
|
+
try {
|
|
3161
|
+
const id = await kuboApi('/api/v0/id');
|
|
3162
|
+
const addrs = id?.Addresses || [];
|
|
3163
|
+
const pubAddrs = addrs.filter((a) => !/127\.0\.0\.1|::1|10\.|100\.|192\.168|172\.(1[6-9]|2\d|3[01])\./.test(a));
|
|
3164
|
+
if (pubAddrs.length === 0) {
|
|
3165
|
+
diag = '\n ⚠️ [诊断] 本机无公网可达地址 (NAT 后): 其他节点能解析 IPNS 但拉不到内容. 公网访问需 pin 到公共服务 (web3.storage/Pinata) 或给本机配置公网端口映射.';
|
|
3166
|
+
}
|
|
3167
|
+
}
|
|
3168
|
+
catch { /* 诊断失败静默 */ }
|
|
3169
|
+
return { success: true, output: `✅ IPNS 已发布:\n name: ${r.name}\n value: ${r.value}\n 解析: ipns_resolve(name="${r.name}")\n 公网访问: https://ipfs.io/ipns/${r.name}${diag}` };
|
|
3170
|
+
}
|
|
3171
|
+
catch (e) {
|
|
3172
|
+
return { success: false, error: `ipns_publish 失败: ${String(e.message || e).slice(0, 200)}` };
|
|
3173
|
+
}
|
|
3174
|
+
}
|
|
3175
|
+
});
|
|
3176
|
+
ctx.tools.set('ipns_resolve', {
|
|
3177
|
+
name: 'ipns_resolve',
|
|
3178
|
+
description: '解析 IPNS name 得到 IPFS CID (Kubo). name 通常是 ipns_publish 返回的 name 或 k51... 形式的 IPNS 标识, 也可以是完整 /ipns/<name> 路径. 注意: 首次解析需查 DHT, 最长约 60 秒; 同一 name 重发布后本地缓存可能返回旧 CID, 等待传播后重试.',
|
|
3179
|
+
parameters: { name: 'IPNS name (必填, 如 k51qzi5uqu5d... 或 /ipns/k51...)' },
|
|
3180
|
+
execute: async (args) => {
|
|
3181
|
+
try {
|
|
3182
|
+
const name = String(args.name || '').trim();
|
|
3183
|
+
if (!name)
|
|
3184
|
+
return { success: false, error: 'name 必填' };
|
|
3185
|
+
await ensureKuboReady();
|
|
3186
|
+
// 2026-08-06 fix: 加 recursive+nocache — 之前不带 nocache, 同一 key 重发布后
|
|
3187
|
+
// Kubo 返回本地缓存旧 CID (TTL 1h), 实测新内容发布后 resolve 到旧值.
|
|
3188
|
+
const r = await kuboApi(`/api/v0/name/resolve?arg=${encodeURIComponent(name)}&recursive=true&nocache=true`, undefined, 60000);
|
|
3189
|
+
const path = typeof r === 'object' && r !== null ? r.Path : String(r);
|
|
3190
|
+
const cid = String(path).replace(/^\/ipfs\//, '').trim();
|
|
3191
|
+
return { success: true, output: `🔗 ${name} → ${path}\n CID: ${cid}` };
|
|
3192
|
+
}
|
|
3193
|
+
catch (e) {
|
|
3194
|
+
return { success: false, error: `ipns_resolve 失败: ${String(e.message || e).slice(0, 200)}` };
|
|
3195
|
+
}
|
|
3196
|
+
}
|
|
3197
|
+
});
|
|
3198
|
+
// ============================================================
|
|
3199
|
+
// bolloon_config_get / bolloon_config_set (2026-08-07)
|
|
3200
|
+
// Bolloon 自己读写 ~/.bolloon/bolloon-config.json — 统一配置文件,
|
|
3201
|
+
// 让 agent 有修改自身配置的权限 (模型/供应商/温度等), 不再只能靠用户手改.
|
|
3202
|
+
// ============================================================
|
|
3203
|
+
ctx.tools.set('bolloon_config_get', {
|
|
3204
|
+
name: 'bolloon_config_get',
|
|
3205
|
+
description: '读取 Bolloon 自身配置 (~/.bolloon/bolloon-config.json): 当前激活供应商 + 各供应商 API 配置 (baseUrl/model/温度). 注意: 不输出 apiKey 明文 (脱敏), 只显示是否已配置.',
|
|
3206
|
+
parameters: {},
|
|
3207
|
+
execute: async () => {
|
|
3208
|
+
try {
|
|
3209
|
+
const { llmConfigStore } = await Promise.resolve().then(() => __importStar(require('../llm/config-store.js')));
|
|
3210
|
+
await llmConfigStore.initialize();
|
|
3211
|
+
const cfg = await llmConfigStore.getConfig();
|
|
3212
|
+
const active = cfg.activeProvider;
|
|
3213
|
+
const lines = [`📋 Bolloon 配置 (${active} 激活):`];
|
|
3214
|
+
for (const [name, p] of Object.entries(cfg.providers || {})) {
|
|
3215
|
+
const pc = p;
|
|
3216
|
+
if (!pc)
|
|
3217
|
+
continue;
|
|
3218
|
+
const mark = name === active ? '●' : '○';
|
|
3219
|
+
const key = pc.apiKey ? '🔑' : pc.requiresApiKey ? '✗ 无key' : '无key需求';
|
|
3220
|
+
lines.push(` ${mark} ${name}: ${key} · model=${pc.model || '?'} · baseUrl=${pc.baseUrl || '?'}${pc.temperature ? ` · temp=${pc.temperature}` : ''}`);
|
|
3221
|
+
}
|
|
3222
|
+
return { success: true, output: lines.join('\n') };
|
|
3223
|
+
}
|
|
3224
|
+
catch (e) {
|
|
3225
|
+
return { success: false, error: `bolloon_config_get 失败: ${String(e.message || e).slice(0, 200)}` };
|
|
3226
|
+
}
|
|
3227
|
+
}
|
|
3228
|
+
});
|
|
3229
|
+
ctx.tools.set('bolloon_config_set', {
|
|
3230
|
+
name: 'bolloon_config_set',
|
|
3231
|
+
description: '修改 Bolloon 自身配置 (~/.bolloon/bolloon-config.json). 可切换激活供应商 (provider) 或改某供应商的 model/baseUrl/temperature/enabled. 修改立即生效 (下次 LLM 调用使用新配置). 例: provider=deepseek; provider=minimax, model=MiniMax-M3; deepseek.temperature=0.3',
|
|
3232
|
+
parameters: {
|
|
3233
|
+
provider: '可选: 切换激活供应商 (如 deepseek / minimax / openai / anthropic / ollama)',
|
|
3234
|
+
'provider.key': '可选: 修改指定供应商字段, 格式 <供应商>.<字段>=<值>, 如 minimax.model=MiniMax-M3',
|
|
3235
|
+
model: '可选: 同时设置激活供应商的 model',
|
|
3236
|
+
temperature: '可选: 同时设置激活供应商的 temperature (0-2)',
|
|
3237
|
+
},
|
|
3238
|
+
execute: async (args) => {
|
|
3239
|
+
try {
|
|
3240
|
+
const { llmConfigStore } = await Promise.resolve().then(() => __importStar(require('../llm/config-store.js')));
|
|
3241
|
+
await llmConfigStore.initialize();
|
|
3242
|
+
const changes = [];
|
|
3243
|
+
// 1. 切换激活供应商
|
|
3244
|
+
if (args.provider) {
|
|
3245
|
+
const name = String(args.provider).trim().toLowerCase();
|
|
3246
|
+
const known = ['openai', 'anthropic', 'ollama', 'openrouter', 'gemini', 'minimax', 'deepseek', 'kimi', 'glm', 'qwen', 'mimo', 'grok', 'local'];
|
|
3247
|
+
if (!known.includes(name))
|
|
3248
|
+
return { success: false, error: `未知供应商: ${name}. 可用: ${known.join(', ')}` };
|
|
3249
|
+
await llmConfigStore.setActiveProvider(name);
|
|
3250
|
+
changes.push(`activeProvider=${name}`);
|
|
3251
|
+
}
|
|
3252
|
+
// 2. 修改指定供应商字段 (provider.key=value)
|
|
3253
|
+
for (const [k, v] of Object.entries(args)) {
|
|
3254
|
+
if (k === 'provider' || k === 'model' || k === 'temperature')
|
|
3255
|
+
continue;
|
|
3256
|
+
if (!k.includes('.'))
|
|
3257
|
+
continue;
|
|
3258
|
+
const [prov, field] = k.split('.');
|
|
3259
|
+
const val = String(v);
|
|
3260
|
+
if (field === 'temperature' || field === 'maxTokens') {
|
|
3261
|
+
const num = Number(val);
|
|
3262
|
+
if (Number.isNaN(num))
|
|
3263
|
+
return { success: false, error: `${k}=${val} 不是数字` };
|
|
3264
|
+
await llmConfigStore.updateProvider(prov, { [field]: num });
|
|
3265
|
+
}
|
|
3266
|
+
else if (field === 'enabled') {
|
|
3267
|
+
await llmConfigStore.updateProvider(prov, { enabled: val === 'true' || val === '1' });
|
|
3268
|
+
}
|
|
3269
|
+
else if (field === 'apiKey') {
|
|
3270
|
+
await llmConfigStore.updateProvider(prov, { apiKey: val });
|
|
3271
|
+
}
|
|
3272
|
+
else {
|
|
3273
|
+
await llmConfigStore.updateProvider(prov, { [field]: val });
|
|
3274
|
+
}
|
|
3275
|
+
changes.push(`${prov}.${field}=${field === 'apiKey' ? '***' : val}`);
|
|
3276
|
+
}
|
|
3277
|
+
// 3. 激活供应商的 model / temperature
|
|
3278
|
+
if (args.model || args.temperature) {
|
|
3279
|
+
const active = await llmConfigStore.getActiveProvider();
|
|
3280
|
+
const patch = {};
|
|
3281
|
+
if (args.model)
|
|
3282
|
+
patch.model = String(args.model);
|
|
3283
|
+
if (args.temperature) {
|
|
3284
|
+
const t = Number(args.temperature);
|
|
3285
|
+
if (Number.isNaN(t))
|
|
3286
|
+
return { success: false, error: `temperature=${args.temperature} 不是数字` };
|
|
3287
|
+
patch.temperature = t;
|
|
3288
|
+
}
|
|
3289
|
+
await llmConfigStore.updateProvider(active, patch);
|
|
3290
|
+
if (args.model)
|
|
3291
|
+
changes.push(`${active}.model=${args.model}`);
|
|
3292
|
+
if (args.temperature)
|
|
3293
|
+
changes.push(`${active}.temperature=${args.temperature}`);
|
|
3294
|
+
}
|
|
3295
|
+
if (changes.length === 0)
|
|
3296
|
+
return { success: false, error: '没有要修改的配置项. 例: provider=deepseek 或 minimax.model=MiniMax-M3' };
|
|
3297
|
+
const cfg = await llmConfigStore.getConfig();
|
|
3298
|
+
return { success: true, output: `✅ 配置已更新: ${changes.join(', ')}\n 当前激活: ${cfg.activeProvider}` };
|
|
3299
|
+
}
|
|
3300
|
+
catch (e) {
|
|
3301
|
+
return { success: false, error: `bolloon_config_set 失败: ${String(e.message || e).slice(0, 200)}` };
|
|
3302
|
+
}
|
|
3303
|
+
}
|
|
3304
|
+
});
|
|
3305
|
+
// ============================================================
|
|
3306
|
+
// 2026-09-13: 下一代工具补充 — 人机问答 / 代码执行 / git 补齐
|
|
3307
|
+
// ============================================================
|
|
3308
|
+
// clarify — 停下来问人, 等回答 (人机对话通道)
|
|
3309
|
+
ctx.tools.set('clarify', {
|
|
3310
|
+
name: 'clarify',
|
|
3311
|
+
description: '向用户提问并等待回答。需要用户拍板、补充信息、确认范围时用它, 不要自己替用户猜。传 choices 时界面会渲染成可点选项 (用户回序号即可)。用户没回答会超时 (默认 600s), 这时如实返回"超时", 不要假装拿到答案。',
|
|
3312
|
+
parameters: {
|
|
3313
|
+
question: '要问用户的完整问题 (必填)',
|
|
3314
|
+
choices: '可选: 候选选项, 用 | 分隔 (如 "方案A | 方案B | 都行")',
|
|
3315
|
+
multi_select: '可选: "true" 表示允许多选 (choices 非空时有效)',
|
|
3316
|
+
timeout_s: '可选: 等待秒数, 默认 600',
|
|
3317
|
+
},
|
|
3318
|
+
execute: async (args) => {
|
|
3319
|
+
const question = String(args.question || '').trim();
|
|
3320
|
+
if (!question)
|
|
3321
|
+
return { success: false, error: 'question 必填' };
|
|
3322
|
+
const choiceList = String(args.choices || '')
|
|
3323
|
+
.split('|').map((s) => s.trim()).filter(Boolean);
|
|
3324
|
+
const timeoutMs = Number(args.timeout_s) > 0 ? Number(args.timeout_s) * 1000 : user_questions_js_1.DEFAULT_QUESTION_TIMEOUT_MS;
|
|
3325
|
+
// 无人值守 (后台/定时任务) 时问了也没人答 → 提前如实拒绝, 不空等
|
|
3326
|
+
if (!user_questions_js_1.userQuestions.hasHumanChannel()) {
|
|
3327
|
+
return {
|
|
3328
|
+
success: false,
|
|
3329
|
+
error: '当前没有可交互的人类界面 (后台/无人值守模式), 无法提问。请把问题写进最终回复交给用户。',
|
|
3330
|
+
};
|
|
3331
|
+
}
|
|
3332
|
+
const pending = await user_questions_js_1.userQuestions.pending();
|
|
3333
|
+
const q = await user_questions_js_1.userQuestions.ask({
|
|
3334
|
+
question,
|
|
3335
|
+
choices: choiceList.length > 0 ? choiceList : undefined,
|
|
3336
|
+
multiSelect: String(args.multi_select ?? '').toLowerCase() === 'true',
|
|
3337
|
+
timeoutMs,
|
|
3338
|
+
source: pending.length > 0 ? 'agent(queued)' : 'agent',
|
|
3339
|
+
});
|
|
3340
|
+
if (q.status === 'answered') {
|
|
3341
|
+
return { success: true, output: `用户回答: ${q.answer}` };
|
|
3342
|
+
}
|
|
3343
|
+
if (q.status === 'cancelled') {
|
|
3344
|
+
return { success: false, error: `问题已被取消 (id ${q.id})` };
|
|
3345
|
+
}
|
|
3346
|
+
return {
|
|
3347
|
+
success: false,
|
|
3348
|
+
error: `等待用户回答超时 (${Math.round(timeoutMs / 1000)}s, 问题 id ${q.id})。用户之后仍可在界面上回答; 现在不要假装已得到答案。`,
|
|
3349
|
+
};
|
|
3350
|
+
},
|
|
3351
|
+
});
|
|
3352
|
+
// execute_code — 独立代码执行工具 (与 terminal 的 code 便捷路径同源, 这里给独立入口)
|
|
3353
|
+
ctx.tools.set('execute_code', {
|
|
3354
|
+
name: 'execute_code',
|
|
3355
|
+
description: '写一段代码并执行, 拿回 stdout/stderr/退出码。适合计算、数据处理、试算法、快速验证想法。支持 python / js / ts / shell / html。需要多步 shell 操作用 terminal; 需要长期进程用 terminal background=true。',
|
|
3356
|
+
parameters: {
|
|
3357
|
+
code: '要执行的代码 (必填)',
|
|
3358
|
+
language: '语言: python | js | ts | shell (默认 python)',
|
|
3359
|
+
timeout_ms: '可选: 超时毫秒, 默认 60000',
|
|
3360
|
+
},
|
|
3361
|
+
execute: async (args) => {
|
|
3362
|
+
const code = String(args.code ?? '');
|
|
3363
|
+
if (!code.trim())
|
|
3364
|
+
return { success: false, error: 'code 必填' };
|
|
3365
|
+
const language = String(args.language || 'python').trim().toLowerCase();
|
|
3366
|
+
const timeoutMs = Number(args.timeout_ms) > 0 ? Number(args.timeout_ms) : 60000;
|
|
3367
|
+
try {
|
|
3368
|
+
return await runCodeSnippet({ code, language, timeoutMs, cwd: ctx.cwd });
|
|
3369
|
+
}
|
|
3370
|
+
catch (e) {
|
|
3371
|
+
return { success: false, error: `执行失败: ${String(e?.message || e).slice(0, 200)}` };
|
|
3372
|
+
}
|
|
3373
|
+
},
|
|
3374
|
+
});
|
|
3375
|
+
// git 补齐: status / add / restore (diff/log/show/stash/commit/push/branch 已有)
|
|
3376
|
+
ctx.tools.set('git_status', {
|
|
3377
|
+
name: 'git_status',
|
|
3378
|
+
description: '查看 git 工作区状态 (当前分支 + 已改/已暂存/未跟踪文件). 改代码前后都该先看它.',
|
|
3379
|
+
parameters: { porcelain: '可选: "true" 只输出机器可解析的 --porcelain 格式' },
|
|
3380
|
+
execute: async (args) => {
|
|
3381
|
+
const porcelain = String(args.porcelain ?? '').toLowerCase() === 'true';
|
|
3382
|
+
const argv = porcelain ? ['status', '--porcelain'] : ['status', '--short', '--branch'];
|
|
3383
|
+
const r = await (0, shell_tool_js_1.shellExec)('git', argv, { timeoutMs: 10_000 });
|
|
3384
|
+
if (r.deniedByGuard)
|
|
3385
|
+
return { success: false, error: r.error };
|
|
3386
|
+
if (!r.success)
|
|
3387
|
+
return { success: false, error: r.error, output: r.output };
|
|
3388
|
+
return { success: true, output: (r.output || '').slice(0, 8000) || '(工作区干净)' };
|
|
3389
|
+
},
|
|
3390
|
+
});
|
|
3391
|
+
ctx.tools.set('git_add', {
|
|
3392
|
+
name: 'git_add',
|
|
3393
|
+
description: '把指定文件加入暂存区 (只加显式给出的路径, 不会 add -A 误收无关改动). 路径用空格分隔.',
|
|
3394
|
+
parameters: { paths: '要暂存的文件路径, 空格分隔 (必填, 支持 . 表示当前目录)' },
|
|
3395
|
+
execute: async (args) => {
|
|
3396
|
+
const raw = String(args.paths || '').trim();
|
|
3397
|
+
if (!raw)
|
|
3398
|
+
return { success: false, error: 'paths 必填 (只加你要提交的文件)' };
|
|
3399
|
+
const paths = raw.split(/\s+/).filter(Boolean);
|
|
3400
|
+
const r = await (0, shell_tool_js_1.shellExec)('git', ['add', ...paths], { timeoutMs: 10_000 });
|
|
3401
|
+
if (r.deniedByGuard)
|
|
3402
|
+
return { success: false, error: r.error };
|
|
3403
|
+
if (!r.success)
|
|
3404
|
+
return { success: false, error: r.error, output: r.output };
|
|
3405
|
+
return { success: true, output: `✅ 已暂存 ${paths.length} 项: ${paths.join(' ')}` };
|
|
3406
|
+
},
|
|
3407
|
+
});
|
|
3408
|
+
ctx.tools.set('git_restore', {
|
|
3409
|
+
name: 'git_restore',
|
|
3410
|
+
description: '丢弃指定文件的改动 (git restore). staged=true 时从暂存区撤回 (--staged), 否则丢弃工作区改动。危险: 未提交改动会丢, 用前先 git_diff 确认。',
|
|
3411
|
+
parameters: { paths: '要还原的文件路径, 空格分隔 (必填)', staged: '可选: "true" = 只从暂存区撤回 (工作区保留)' },
|
|
3412
|
+
execute: async (args) => {
|
|
3413
|
+
const raw = String(args.paths || '').trim();
|
|
3414
|
+
if (!raw)
|
|
3415
|
+
return { success: false, error: 'paths 必填' };
|
|
3416
|
+
const paths = raw.split(/\s+/).filter(Boolean);
|
|
3417
|
+
const staged = String(args.staged ?? '').toLowerCase() === 'true';
|
|
3418
|
+
const r = await (0, shell_tool_js_1.shellExec)('git', ['restore', ...(staged ? ['--staged'] : []), ...paths], { timeoutMs: 10_000 });
|
|
3419
|
+
if (r.deniedByGuard)
|
|
3420
|
+
return { success: false, error: r.error };
|
|
3421
|
+
if (!r.success)
|
|
3422
|
+
return { success: false, error: r.error, output: r.output };
|
|
3423
|
+
return { success: true, output: `✅ 已${staged ? '从暂存区撤回' : '还原'} ${paths.length} 项: ${paths.join(' ')}` };
|
|
3424
|
+
},
|
|
3425
|
+
});
|
|
3426
|
+
// 独立模块注册 (各自一个文件, 便于维护与单测)
|
|
3427
|
+
(0, patch_tool_js_1.registerPatchTools)(ctx);
|
|
3428
|
+
(0, computer_use_js_1.registerComputerUseTools)(ctx);
|
|
3429
|
+
(0, skill_share_js_1.registerSkillShareTools)(ctx);
|
|
3430
|
+
(0, paid_info_tools_js_1.registerPaidInfoTools)(ctx);
|
|
3431
|
+
// 浏览器 (CDP 模块可选加载: 缺失时不影响其它工具)
|
|
3432
|
+
Promise.resolve().then(() => __importStar(require('./browser-cdp.js'))).then(({ registerBrowserTools }) => registerBrowserTools(ctx))
|
|
3433
|
+
.catch((e) => { console.warn('[registerTools] browser 工具注册失败 (非致命):', e?.message ?? e); });
|
|
3434
|
+
}
|
|
3435
|
+
// ─── IPFS/IPNS 通用 helper (2026-08-04) ─────────────────────────────────────
|
|
3436
|
+
// 复用 publish_did 的 checkKuboSetup 自动安装/启动本地 Kubo (darwin-arm64 v0.28.0)
|
|
3437
|
+
async function ensureKuboReady() {
|
|
3438
|
+
const sdk = await Promise.resolve().then(() => __importStar(require('@diap/sdk')));
|
|
3439
|
+
const checkKuboSetup = sdk.checkKuboSetup;
|
|
3440
|
+
if (typeof checkKuboSetup === 'function') {
|
|
3441
|
+
const setup = await checkKuboSetup(true, true);
|
|
3442
|
+
if (!setup?.ready || !setup?.daemonRunning) {
|
|
3443
|
+
throw new Error('本地 Kubo 不可用 (自动安装失败), 无法访问 IPFS');
|
|
3444
|
+
}
|
|
3445
|
+
}
|
|
3446
|
+
}
|
|
3447
|
+
/** Kubo HTTP API helper (POST only, 2026-08-03 实测 Kubo 只接受 POST). 2026-08-06 export 供 CLI /ipfs /ipns 命令用. */
|
|
3448
|
+
async function kuboApi(pathAndQuery, init, timeoutMs = 30000) {
|
|
3449
|
+
const controller = new AbortController();
|
|
3450
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
3451
|
+
try {
|
|
3452
|
+
const url = `http://127.0.0.1:5001${pathAndQuery}`;
|
|
3453
|
+
const resp = await fetch(url, { method: 'POST', signal: controller.signal, ...(init || {}) });
|
|
3454
|
+
if (!resp.ok) {
|
|
3455
|
+
const text = await resp.text();
|
|
3456
|
+
throw new Error(`Kubo API ${pathAndQuery.split('?')[0]} 失败: ${resp.status} ${text.slice(0, 200)}`);
|
|
3457
|
+
}
|
|
3458
|
+
const ct = resp.headers.get('content-type') || '';
|
|
3459
|
+
if (ct.includes('application/json'))
|
|
3460
|
+
return resp.json();
|
|
3461
|
+
return resp.text();
|
|
3462
|
+
}
|
|
3463
|
+
finally {
|
|
3464
|
+
clearTimeout(timer);
|
|
3465
|
+
}
|
|
3466
|
+
}
|
|
3467
|
+
/**
|
|
3468
|
+
* 注册 Wallet + Polymarket + Safe 工具 (基于 constraint-runtime/src/tools/).
|
|
3469
|
+
*/
|
|
3470
|
+
function registerWalletTools(ctx) {
|
|
3471
|
+
ctx.tools.set('wallet_create', {
|
|
3472
|
+
name: 'wallet_create',
|
|
3473
|
+
description: '创建新 EVM 钱包 (BIP-39 12 词助记词 + 私钥 + 地址).',
|
|
3474
|
+
parameters: {},
|
|
3475
|
+
execute: async () => {
|
|
3476
|
+
try {
|
|
3477
|
+
const { createWallet } = await Promise.resolve().then(() => __importStar(require('../constraint-runtime/dist/tools/WalletTools/createWallet.js'))).catch(() => Promise.resolve().then(() => __importStar(require('../constraint-runtime/src/tools/WalletTools/createWallet.js'))));
|
|
3478
|
+
const r = await createWallet();
|
|
3479
|
+
return { success: true, output: `✅ 钱包创建成功:\n address: ${r.address}\n privateKey: ${r.privateKey}\n mnemonic: ${r.mnemonic}\n createdAt: ${r.createdAt}` };
|
|
3480
|
+
}
|
|
3481
|
+
catch (e) {
|
|
3482
|
+
return { success: false, error: `创建失败: ${String(e.message || e)}` };
|
|
3483
|
+
}
|
|
3484
|
+
}
|
|
3485
|
+
});
|
|
3486
|
+
ctx.tools.set('wallet_import', {
|
|
3487
|
+
name: 'wallet_import',
|
|
3488
|
+
description: '导入已有 EVM 钱包 (用助记词或私钥)',
|
|
3489
|
+
parameters: { mnemonic: '可选, 12/15/18/21/24 词助记词', privateKey: '可选, 0x 开头的私钥' },
|
|
3490
|
+
execute: async (args) => {
|
|
3491
|
+
try {
|
|
3492
|
+
const { importWallet } = await Promise.resolve().then(() => __importStar(require('../constraint-runtime/dist/tools/WalletTools/importWallet.js'))).catch(() => Promise.resolve().then(() => __importStar(require('../constraint-runtime/src/tools/WalletTools/importWallet.js'))));
|
|
3493
|
+
const r = await importWallet({ mnemonic: args.mnemonic, privateKey: args.privateKey });
|
|
3494
|
+
return { success: true, output: `✅ 钱包导入成功:\n address: ${r.address}\n privateKey: ${r.privateKey}\n source: ${r.source}` };
|
|
3495
|
+
}
|
|
3496
|
+
catch (e) {
|
|
3497
|
+
return { success: false, error: `导入失败: ${String(e.message || e)}` };
|
|
3498
|
+
}
|
|
3499
|
+
}
|
|
3500
|
+
});
|
|
3501
|
+
ctx.tools.set('wallet_get_balance', {
|
|
3502
|
+
name: 'wallet_get_balance',
|
|
3503
|
+
description: '查 EVM 钱包 ETH 余额.',
|
|
3504
|
+
parameters: { address: '0x 开头的 EVM 地址 (必填)', rpcUrl: '可选 RPC URL (默认 eth.llamarpc.com)' },
|
|
3505
|
+
execute: async (args) => {
|
|
3506
|
+
try {
|
|
3507
|
+
const { getBalance } = await Promise.resolve().then(() => __importStar(require('../constraint-runtime/dist/tools/WalletTools/getBalance.js'))).catch(() => Promise.resolve().then(() => __importStar(require('../constraint-runtime/src/tools/WalletTools/getBalance.js'))));
|
|
3508
|
+
const r = await getBalance({ address: String(args.address), rpcUrl: args.rpcUrl });
|
|
3509
|
+
return { success: true, output: `💰 ${r.address}\n ${r.balanceEth} ${r.symbol} (${r.balance} wei)` };
|
|
3510
|
+
}
|
|
3511
|
+
catch (e) {
|
|
3512
|
+
return { success: false, error: `查余额失败: ${String(e.message || e)}` };
|
|
3513
|
+
}
|
|
3514
|
+
}
|
|
3515
|
+
});
|
|
3516
|
+
ctx.tools.set('wallet_sign_message', {
|
|
3517
|
+
name: 'wallet_sign_message',
|
|
3518
|
+
description: '用私钥对消息做 EIP-191 personal_sign 签名',
|
|
3519
|
+
parameters: { message: '要签名的消息 (必填)', privateKey: '0x 开头的私钥 (必填)' },
|
|
3520
|
+
execute: async (args) => {
|
|
3521
|
+
try {
|
|
3522
|
+
const { signMessage } = await Promise.resolve().then(() => __importStar(require('../constraint-runtime/dist/tools/WalletTools/signMessage.js'))).catch(() => Promise.resolve().then(() => __importStar(require('../constraint-runtime/src/tools/WalletTools/signMessage.js'))));
|
|
3523
|
+
const r = await signMessage({ message: String(args.message), privateKey: String(args.privateKey) });
|
|
3524
|
+
return { success: true, output: `✅ 签名完成:\n address: ${r.address}\n message: ${r.message}\n signature: ${r.signature}` };
|
|
3525
|
+
}
|
|
3526
|
+
catch (e) {
|
|
3527
|
+
return { success: false, error: `签名失败: ${String(e.message || e)}` };
|
|
3528
|
+
}
|
|
3529
|
+
}
|
|
3530
|
+
});
|
|
3531
|
+
ctx.tools.set('wallet_send_tx', {
|
|
3532
|
+
name: 'wallet_send_tx',
|
|
3533
|
+
description: '用 EVM 钱包发送交易 (转账 ETH 或调用合约).',
|
|
3534
|
+
parameters: { privateKey: '私钥 (必填)', to: '接收地址 (必填)', value: '发送 wei 数量 (必填)', data: '可选 0x 开头的 calldata', rpcUrl: '可选 RPC URL' },
|
|
3535
|
+
execute: async (args) => {
|
|
3536
|
+
try {
|
|
3537
|
+
const { sendTransaction } = await Promise.resolve().then(() => __importStar(require('../constraint-runtime/dist/tools/WalletTools/sendTransaction.js'))).catch(() => Promise.resolve().then(() => __importStar(require('../constraint-runtime/src/tools/WalletTools/sendTransaction.js'))));
|
|
3538
|
+
const r = await sendTransaction({
|
|
3539
|
+
privateKey: String(args.privateKey),
|
|
3540
|
+
to: String(args.to),
|
|
3541
|
+
value: String(args.value),
|
|
3542
|
+
data: args.data ? String(args.data) : undefined,
|
|
3543
|
+
rpcUrl: args.rpcUrl,
|
|
3544
|
+
});
|
|
3545
|
+
return { success: true, output: `✅ 交易已发送:\n hash: ${r.hash}\n from: ${r.from} → to: ${r.to}\n value: ${r.value} wei` };
|
|
3546
|
+
}
|
|
3547
|
+
catch (e) {
|
|
3548
|
+
return { success: false, error: `交易失败: ${String(e.message || e)}` };
|
|
3549
|
+
}
|
|
3550
|
+
}
|
|
3551
|
+
});
|
|
3552
|
+
ctx.tools.set('wallet_transfer_token', {
|
|
3553
|
+
name: 'wallet_transfer_token',
|
|
3554
|
+
description: '用 EVM 钱包转 ERC20 token.',
|
|
3555
|
+
parameters: { privateKey: '私钥 (必填)', tokenAddress: 'ERC20 合约地址 (必填)', to: '接收地址 (必填)', amount: 'token 数量', decimals: '可选 token decimals', rpcUrl: '可选 RPC URL' },
|
|
3556
|
+
execute: async (args) => {
|
|
3557
|
+
try {
|
|
3558
|
+
const { transferToken } = await Promise.resolve().then(() => __importStar(require('../constraint-runtime/dist/tools/WalletTools/transferToken.js'))).catch(() => Promise.resolve().then(() => __importStar(require('../constraint-runtime/src/tools/WalletTools/transferToken.js'))));
|
|
3559
|
+
const r = await transferToken({
|
|
3560
|
+
privateKey: String(args.privateKey),
|
|
3561
|
+
tokenAddress: String(args.tokenAddress),
|
|
3562
|
+
to: String(args.to),
|
|
3563
|
+
amount: String(args.amount),
|
|
3564
|
+
decimals: args.decimals ? Number(args.decimals) : undefined,
|
|
3565
|
+
rpcUrl: args.rpcUrl,
|
|
3566
|
+
});
|
|
3567
|
+
return { success: true, output: `✅ Token 转账完成:\n hash: ${r.hash}\n ${r.amount} ${r.tokenAddress.substring(0, 10)}...` };
|
|
3568
|
+
}
|
|
3569
|
+
catch (e) {
|
|
3570
|
+
return { success: false, error: `转账失败: ${String(e.message || e)}` };
|
|
3571
|
+
}
|
|
3572
|
+
}
|
|
3573
|
+
});
|
|
3574
|
+
ctx.tools.set('wallet_autopay', {
|
|
3575
|
+
name: 'wallet_autopay',
|
|
3576
|
+
description: '设置自动付款 (订阅/定期扣款, 用 SafeSDK 自动执行).',
|
|
3577
|
+
parameters: { from: '付款方地址 (必填)', to: '收款方地址 (必填)', amount: '金额 (必填)', interval: '周期 (e.g. daily/weekly/monthly)', token: '可选, 默认 ETH' },
|
|
3578
|
+
execute: async (args) => {
|
|
3579
|
+
try {
|
|
3580
|
+
const mod = await Promise.resolve().then(() => __importStar(require('../constraint-runtime/dist/tools/WalletTools/autoPay.js'))).catch(() => Promise.resolve().then(() => __importStar(require('../constraint-runtime/src/tools/WalletTools/autoPay.js'))));
|
|
3581
|
+
const fn = mod.autoPay || mod.setAutoPay || mod.default;
|
|
3582
|
+
if (!fn)
|
|
3583
|
+
return { success: false, error: 'autoPay 接口未找到' };
|
|
3584
|
+
const r = await fn({
|
|
3585
|
+
from: String(args.from), to: String(args.to), amount: String(args.amount),
|
|
3586
|
+
interval: String(args.interval || 'monthly'), token: args.token,
|
|
3587
|
+
});
|
|
3588
|
+
return { success: true, output: `✅ 自动付款已设置: ${JSON.stringify(r)}` };
|
|
3589
|
+
}
|
|
3590
|
+
catch (e) {
|
|
3591
|
+
return { success: false, error: `设置失败: ${String(e.message || e)}` };
|
|
3592
|
+
}
|
|
3593
|
+
}
|
|
3594
|
+
});
|
|
3595
|
+
// Polymarket
|
|
3596
|
+
ctx.tools.set('polymarket_list_markets', {
|
|
3597
|
+
name: 'polymarket_list_markets',
|
|
3598
|
+
description: '列出 Polymarket 预测市场.',
|
|
3599
|
+
parameters: { limit: '可选 数量 (默认 50)', offset: '可选 偏移', closed: '可选 是否只显示已关闭' },
|
|
3600
|
+
execute: async (args) => {
|
|
3601
|
+
try {
|
|
3602
|
+
const { listMarkets } = await Promise.resolve().then(() => __importStar(require('../constraint-runtime/dist/tools/PolymarketSDK/listMarkets.js'))).catch(() => Promise.resolve().then(() => __importStar(require('../constraint-runtime/src/tools/PolymarketSDK/listMarkets.js'))));
|
|
3603
|
+
const markets = await listMarkets({
|
|
3604
|
+
limit: args.limit ? Number(args.limit) : 50,
|
|
3605
|
+
offset: args.offset ? Number(args.offset) : 0,
|
|
3606
|
+
closed: args.closed ? String(args.closed) === 'true' : false,
|
|
3607
|
+
});
|
|
3608
|
+
if (!markets || markets.length === 0) {
|
|
3609
|
+
return { success: true, output: '📊 Polymarket 当前没有活跃市场' };
|
|
3610
|
+
}
|
|
3611
|
+
const lines = markets.slice(0, 10).map((m, i) => ` ${i + 1}. [${m.id}] ${m.question}`);
|
|
3612
|
+
return { success: true, output: `📊 Polymarket 找到 ${markets.length} 个市场 (前 10):\n${lines.join('\n')}` };
|
|
3613
|
+
}
|
|
3614
|
+
catch (e) {
|
|
3615
|
+
return { success: false, error: `查询失败: ${String(e.message || e)}` };
|
|
3616
|
+
}
|
|
3617
|
+
}
|
|
3618
|
+
});
|
|
3619
|
+
ctx.tools.set('polymarket_get_market', {
|
|
3620
|
+
name: 'polymarket_get_market',
|
|
3621
|
+
description: '获取单个 Polymarket 市场的详情.',
|
|
3622
|
+
parameters: { marketId: '市场 ID (必填)' },
|
|
3623
|
+
execute: async (args) => {
|
|
3624
|
+
try {
|
|
3625
|
+
const mod = await Promise.resolve().then(() => __importStar(require('../constraint-runtime/dist/tools/PolymarketSDK/getMarket.js'))).catch(() => Promise.resolve().then(() => __importStar(require('../constraint-runtime/src/tools/PolymarketSDK/getMarket.js'))));
|
|
3626
|
+
const fn = mod.getMarket || mod.default;
|
|
3627
|
+
const m = await fn(String(args.marketId));
|
|
3628
|
+
if (!m)
|
|
3629
|
+
return { success: false, error: '市场不存在' };
|
|
3630
|
+
return { success: true, output: `📊 市场详情:\n ${JSON.stringify(m, null, 2).substring(0, 1500)}` };
|
|
3631
|
+
}
|
|
3632
|
+
catch (e) {
|
|
3633
|
+
return { success: false, error: `查询失败: ${String(e.message || e)}` };
|
|
3634
|
+
}
|
|
3635
|
+
}
|
|
3636
|
+
});
|
|
3637
|
+
ctx.tools.set('polymarket_get_orders', {
|
|
3638
|
+
name: 'polymarket_get_orders',
|
|
3639
|
+
description: '查询 Polymarket 开放订单. 需要钱包私钥 privateKey 做 API key 鉴权.',
|
|
3640
|
+
parameters: {
|
|
3641
|
+
privateKey: '钱包私钥 0x... (必填)',
|
|
3642
|
+
marketId: '可选 按市场 ID 过滤',
|
|
3643
|
+
apiKey: '可选, 已存在的 API key',
|
|
3644
|
+
apiSecret: '可选, API secret',
|
|
3645
|
+
apiPassphrase: '可选, API passphrase',
|
|
3646
|
+
funder: '可选, 资金地址',
|
|
3647
|
+
},
|
|
3648
|
+
execute: async (args) => {
|
|
3649
|
+
try {
|
|
3650
|
+
const { getOrders } = await Promise.resolve().then(() => __importStar(require('../constraint-runtime/dist/tools/PolymarketSDK/getOrders.js'))).catch(() => Promise.resolve().then(() => __importStar(require('../constraint-runtime/src/tools/PolymarketSDK/getOrders.js'))));
|
|
3651
|
+
const orders = await getOrders({
|
|
3652
|
+
privateKey: String(args.privateKey),
|
|
3653
|
+
marketId: args.marketId ? String(args.marketId) : undefined,
|
|
3654
|
+
apiKey: args.apiKey ? String(args.apiKey) : undefined,
|
|
3655
|
+
apiSecret: args.apiSecret ? String(args.apiSecret) : undefined,
|
|
3656
|
+
apiPassphrase: args.apiPassphrase ? String(args.apiPassphrase) : undefined,
|
|
3657
|
+
funder: args.funder ? String(args.funder) : undefined,
|
|
3658
|
+
});
|
|
3659
|
+
return { success: true, output: `📋 订单列表: ${JSON.stringify(orders, null, 2).substring(0, 1500)}` };
|
|
3660
|
+
}
|
|
3661
|
+
catch (e) {
|
|
3662
|
+
return { success: false, error: `查询失败: ${String(e.message || e)}` };
|
|
3663
|
+
}
|
|
3664
|
+
}
|
|
3665
|
+
});
|
|
3666
|
+
ctx.tools.set('polymarket_create_order', {
|
|
3667
|
+
name: 'polymarket_create_order',
|
|
3668
|
+
description: '在 Polymarket 下单 (BUY/SELL). 需要钱包私钥 privateKey 做 EIP-712 订单签名与 API key 派生.',
|
|
3669
|
+
parameters: {
|
|
3670
|
+
privateKey: '下单钱包私钥 0x... (必填)',
|
|
3671
|
+
marketId: '市场 ID (必填)',
|
|
3672
|
+
side: 'BUY 或 SELL (必填)',
|
|
3673
|
+
price: '价格 0-1 (必填, 需符合 tickSize)',
|
|
3674
|
+
size: '数量 USDC (必填)',
|
|
3675
|
+
outcome: '可选, Yes/No 或索引 0/1, 默认第一个 (通常 Yes)',
|
|
3676
|
+
tokenId: '可选, 显式条件代币 tokenID (优先于 outcome)',
|
|
3677
|
+
orderType: '可选, GTC (默认) 或 GTD',
|
|
3678
|
+
apiKey: '可选, 已存在的 API key (需配合 apiSecret/apiPassphrase)',
|
|
3679
|
+
apiSecret: '可选, API secret',
|
|
3680
|
+
apiPassphrase: '可选, API passphrase',
|
|
3681
|
+
funder: '可选, 资金地址 (默认=私钥地址)',
|
|
3682
|
+
},
|
|
3683
|
+
execute: async (args) => {
|
|
3684
|
+
try {
|
|
3685
|
+
const { createOrder } = await Promise.resolve().then(() => __importStar(require('../constraint-runtime/dist/tools/PolymarketSDK/createOrder.js'))).catch(() => Promise.resolve().then(() => __importStar(require('../constraint-runtime/src/tools/PolymarketSDK/createOrder.js'))));
|
|
3686
|
+
const r = await createOrder({
|
|
3687
|
+
privateKey: String(args.privateKey),
|
|
3688
|
+
marketId: String(args.marketId),
|
|
3689
|
+
side: String(args.side).toUpperCase() === 'SELL' ? 'SELL' : 'BUY',
|
|
3690
|
+
price: Number(args.price),
|
|
3691
|
+
size: Number(args.size),
|
|
3692
|
+
outcome: args.outcome !== undefined ? args.outcome : undefined,
|
|
3693
|
+
tokenId: args.tokenId ? String(args.tokenId) : undefined,
|
|
3694
|
+
orderType: args.orderType ? String(args.orderType) : undefined,
|
|
3695
|
+
apiKey: args.apiKey ? String(args.apiKey) : undefined,
|
|
3696
|
+
apiSecret: args.apiSecret ? String(args.apiSecret) : undefined,
|
|
3697
|
+
apiPassphrase: args.apiPassphrase ? String(args.apiPassphrase) : undefined,
|
|
3698
|
+
funder: args.funder ? String(args.funder) : undefined,
|
|
3699
|
+
});
|
|
3700
|
+
if (r.success)
|
|
3701
|
+
return { success: true, output: `✅ 订单已提交: orderId=${r.orderId} status=${r.status}` };
|
|
3702
|
+
return { success: false, error: r.message, output: r.message };
|
|
3703
|
+
}
|
|
3704
|
+
catch (e) {
|
|
3705
|
+
return { success: false, error: `下单失败: ${String(e.message || e)}` };
|
|
3706
|
+
}
|
|
3707
|
+
}
|
|
3708
|
+
});
|
|
3709
|
+
ctx.tools.set('polymarket_cancel_order', {
|
|
3710
|
+
name: 'polymarket_cancel_order',
|
|
3711
|
+
description: '取消 Polymarket 订单. 需要钱包私钥 privateKey 做 API key 鉴权.',
|
|
3712
|
+
parameters: {
|
|
3713
|
+
privateKey: '钱包私钥 0x... (必填)',
|
|
3714
|
+
orderId: '订单 ID (必填)',
|
|
3715
|
+
apiKey: '可选, 已存在的 API key',
|
|
3716
|
+
apiSecret: '可选, API secret',
|
|
3717
|
+
apiPassphrase: '可选, API passphrase',
|
|
3718
|
+
funder: '可选, 资金地址',
|
|
3719
|
+
},
|
|
3720
|
+
execute: async (args) => {
|
|
3721
|
+
try {
|
|
3722
|
+
const { cancelOrder } = await Promise.resolve().then(() => __importStar(require('../constraint-runtime/dist/tools/PolymarketSDK/cancelOrder.js'))).catch(() => Promise.resolve().then(() => __importStar(require('../constraint-runtime/src/tools/PolymarketSDK/cancelOrder.js'))));
|
|
3723
|
+
const r = await cancelOrder({
|
|
3724
|
+
privateKey: String(args.privateKey),
|
|
3725
|
+
orderId: String(args.orderId),
|
|
3726
|
+
apiKey: args.apiKey ? String(args.apiKey) : undefined,
|
|
3727
|
+
apiSecret: args.apiSecret ? String(args.apiSecret) : undefined,
|
|
3728
|
+
apiPassphrase: args.apiPassphrase ? String(args.apiPassphrase) : undefined,
|
|
3729
|
+
funder: args.funder ? String(args.funder) : undefined,
|
|
3730
|
+
});
|
|
3731
|
+
if (r.success)
|
|
3732
|
+
return { success: true, output: `✅ 取消订单: ${r.message}` };
|
|
3733
|
+
return { success: false, error: r.message, output: r.message };
|
|
3734
|
+
}
|
|
3735
|
+
catch (e) {
|
|
3736
|
+
return { success: false, error: `取消失败: ${String(e.message || e)}` };
|
|
3737
|
+
}
|
|
3738
|
+
}
|
|
3739
|
+
});
|
|
3740
|
+
// ============================================================
|
|
3741
|
+
// x402 协议 — 智能体自动支付工作流 (2026-07-24)
|
|
3742
|
+
// 基于 @x402/core + @x402/evm + @x402/fetch + @x402/mcp 协议栈
|
|
3743
|
+
// ============================================================
|
|
3744
|
+
ctx.tools.set('x402_pay', {
|
|
3745
|
+
name: 'x402_pay',
|
|
3746
|
+
description: '低级钱包付款: 用私钥直接转 ETH/USDC。标准 x402 资源访问请优先用 x402_fetch。',
|
|
3747
|
+
parameters: {
|
|
3748
|
+
privateKey: '钱包私钥 0x... (必填)',
|
|
3749
|
+
amount: '金额 (ETH 或 USDC 数量, 必填)',
|
|
3750
|
+
to: '收款方地址 0x... (必填)',
|
|
3751
|
+
network: '网络: base | base-sepolia | mainnet | sepolia (默认 base-sepolia)',
|
|
3752
|
+
currency: '代币: ETH | USDC (默认 ETH)',
|
|
3753
|
+
memo: '支付说明 (可选)',
|
|
3754
|
+
},
|
|
3755
|
+
execute: async (args) => {
|
|
3756
|
+
try {
|
|
3757
|
+
const { x402Pay, decryptChannelWallet } = await Promise.resolve().then(() => __importStar(require('./x402/x402Pay.js')));
|
|
3758
|
+
let privateKey = args.privateKey ? String(args.privateKey) : undefined;
|
|
3759
|
+
if (!privateKey && ctx.getChannelWallet) {
|
|
3760
|
+
const wallet = await ctx.getChannelWallet();
|
|
3761
|
+
if (wallet && wallet.autoPayEnabled) {
|
|
3762
|
+
const decrypted = await decryptChannelWallet({ encryptedPrivateKey: wallet.encryptedPrivateKey, encryptedPrivateKeyIv: wallet.encryptedPrivateKeyIv, walletAddress: wallet.walletAddress }, wallet.did);
|
|
3763
|
+
if (decrypted)
|
|
3764
|
+
privateKey = decrypted.privateKey;
|
|
3765
|
+
}
|
|
3766
|
+
}
|
|
3767
|
+
if (!privateKey) {
|
|
3768
|
+
return { success: false, error: '未提供 privateKey 且 channel 未绑定自动支付钱包' };
|
|
3769
|
+
}
|
|
3770
|
+
const r = await x402Pay({
|
|
3771
|
+
privateKey,
|
|
3772
|
+
amount: String(args.amount),
|
|
3773
|
+
to: String(args.to),
|
|
3774
|
+
network: args.network ? String(args.network) : undefined,
|
|
3775
|
+
currency: args.currency ? String(args.currency) : undefined,
|
|
3776
|
+
memo: args.memo ? String(args.memo) : undefined,
|
|
3777
|
+
});
|
|
3778
|
+
if (!r.success)
|
|
3779
|
+
return { success: false, error: r.error };
|
|
3780
|
+
return { success: true, output: `✅ x402 支付成功:\n txHash: ${r.txHash}\n paid: ${r.paid}\n to: ${r.to}\n network: ${r.network}` };
|
|
3781
|
+
}
|
|
3782
|
+
catch (e) {
|
|
3783
|
+
return { success: false, error: `x402_pay 失败: ${String(e.message || e)}` };
|
|
3784
|
+
}
|
|
3785
|
+
},
|
|
3786
|
+
});
|
|
3787
|
+
ctx.tools.set('x402_fetch', {
|
|
3788
|
+
name: 'x402_fetch',
|
|
3789
|
+
description: 'x402 Fetch — 自动化的 "请求→检测 402 → 钱包支付 → 重试" 循环。无需手动处理 402 支付流程。',
|
|
3790
|
+
parameters: {
|
|
3791
|
+
url: '目标 URL (必填)',
|
|
3792
|
+
method: 'HTTP method: GET | POST | PUT | DELETE (默认 GET)',
|
|
3793
|
+
body: '请求体 (可选)',
|
|
3794
|
+
headers: 'JSON 对象格式的额外 headers (可选)',
|
|
3795
|
+
privateKey: '钱包私钥 0x... (可选, 不提供时遇到 402 会提示需支付)',
|
|
3796
|
+
network: '限定可支付网络: base | base-sepolia | mainnet | sepolia (可选)',
|
|
3797
|
+
rpcUrl: '自定义 RPC URL (可选)',
|
|
3798
|
+
},
|
|
3799
|
+
execute: async (args) => {
|
|
3800
|
+
try {
|
|
3801
|
+
const { x402Fetch, decryptChannelWallet } = await Promise.resolve().then(() => __importStar(require('./x402/x402Pay.js')));
|
|
3802
|
+
let privateKey = args.privateKey ? String(args.privateKey) : undefined;
|
|
3803
|
+
if (!privateKey && ctx.getChannelWallet) {
|
|
3804
|
+
const wallet = await ctx.getChannelWallet();
|
|
3805
|
+
if (wallet && wallet.autoPayEnabled) {
|
|
3806
|
+
const decrypted = await decryptChannelWallet({ encryptedPrivateKey: wallet.encryptedPrivateKey, encryptedPrivateKeyIv: wallet.encryptedPrivateKeyIv, walletAddress: wallet.walletAddress }, wallet.did);
|
|
3807
|
+
if (decrypted)
|
|
3808
|
+
privateKey = decrypted.privateKey;
|
|
3809
|
+
}
|
|
3810
|
+
}
|
|
3811
|
+
const headers = args.headers ? (typeof args.headers === 'string' ? JSON.parse(args.headers) : args.headers) : undefined;
|
|
3812
|
+
const r = await x402Fetch({
|
|
3813
|
+
url: String(args.url),
|
|
3814
|
+
method: args.method ? String(args.method) : undefined,
|
|
3815
|
+
body: args.body ? String(args.body) : undefined,
|
|
3816
|
+
headers,
|
|
3817
|
+
privateKey,
|
|
3818
|
+
network: args.network ? String(args.network) : undefined,
|
|
3819
|
+
rpcUrl: args.rpcUrl ? String(args.rpcUrl) : undefined,
|
|
3820
|
+
});
|
|
3821
|
+
if (!r.success)
|
|
3822
|
+
return { success: false, error: r.error, output: r.data ? JSON.stringify(r.data).substring(0, 1000) : undefined };
|
|
3823
|
+
const paymentLine = r.paymentInfo?.rawHeader ? `\n [x402 payment-response] ${r.paymentInfo.rawHeader.substring(0, 160)}` : '';
|
|
3824
|
+
return {
|
|
3825
|
+
success: true,
|
|
3826
|
+
output: `✅ x402 fetch 完成 (status=${r.status})${paymentLine}\n${JSON.stringify(r.data, null, 2).substring(0, 2000)}`,
|
|
3827
|
+
};
|
|
3828
|
+
}
|
|
3829
|
+
catch (e) {
|
|
3830
|
+
return { success: false, error: `x402_fetch 失败: ${String(e.message || e)}` };
|
|
3831
|
+
}
|
|
3832
|
+
},
|
|
3833
|
+
});
|
|
3834
|
+
ctx.tools.set('x402_request_payment', {
|
|
3835
|
+
name: 'x402_request_payment',
|
|
3836
|
+
description: 'x402 服务端: 生成 HTTP 402 PaymentRequired 响应信息。用于智能体作为服务端时告知调用方需支付。',
|
|
3837
|
+
parameters: {
|
|
3838
|
+
price: '价格 (数字, 必填)',
|
|
3839
|
+
payTo: '收款方地址 0x... (必填)',
|
|
3840
|
+
currency: '代币: USDC | ETH (默认 USDC)',
|
|
3841
|
+
network: '网络: base | base-sepolia (默认 base)',
|
|
3842
|
+
resourceDescription: '资源描述 (可选)',
|
|
3843
|
+
},
|
|
3844
|
+
execute: async (args) => {
|
|
3845
|
+
try {
|
|
3846
|
+
const { x402RequestPayment } = await Promise.resolve().then(() => __importStar(require('./x402/x402Pay.js')));
|
|
3847
|
+
const r = x402RequestPayment({
|
|
3848
|
+
price: Number(args.price),
|
|
3849
|
+
payTo: String(args.payTo),
|
|
3850
|
+
currency: args.currency ? String(args.currency) : undefined,
|
|
3851
|
+
network: args.network ? String(args.network) : undefined,
|
|
3852
|
+
resourceDescription: args.resourceDescription ? String(args.resourceDescription) : undefined,
|
|
3853
|
+
});
|
|
3854
|
+
return {
|
|
3855
|
+
success: true,
|
|
3856
|
+
output: `🛡️ 402 Payment Required:\n 金额: ${args.price} ${args.currency || 'USDC'}\n 网络: ${args.network || 'base'}\n 收款: ${args.payTo}\n 描述: ${args.resourceDescription || '(无)'}\n\nHTTP 响应模板:\n statusCode: ${r.statusCode}\n headers: ${JSON.stringify(r.headers, null, 2)}\n body: ${r.body}`,
|
|
3857
|
+
};
|
|
3858
|
+
}
|
|
3859
|
+
catch (e) {
|
|
3860
|
+
return { success: false, error: `x402_request_payment 失败: ${String(e.message || e)}` };
|
|
3861
|
+
}
|
|
3862
|
+
},
|
|
3863
|
+
});
|
|
3864
|
+
ctx.tools.set('x402_check_balance', {
|
|
3865
|
+
name: 'x402_check_balance',
|
|
3866
|
+
description: '查 EVM 地址余额,判断是否足够支付 x402 费用。',
|
|
3867
|
+
parameters: {
|
|
3868
|
+
address: 'EVM 地址 0x... (必填)',
|
|
3869
|
+
network: '网络: base | base-sepolia | mainnet | sepolia (默认 base-sepolia)',
|
|
3870
|
+
rpcUrl: '自定义 RPC URL (可选)',
|
|
3871
|
+
},
|
|
3872
|
+
execute: async (args) => {
|
|
3873
|
+
try {
|
|
3874
|
+
const { x402CheckBalance } = await Promise.resolve().then(() => __importStar(require('./x402/x402Pay.js')));
|
|
3875
|
+
const r = await x402CheckBalance({
|
|
3876
|
+
address: String(args.address),
|
|
3877
|
+
network: args.network ? String(args.network) : undefined,
|
|
3878
|
+
rpcUrl: args.rpcUrl ? String(args.rpcUrl) : undefined,
|
|
3879
|
+
});
|
|
3880
|
+
if (!r.success)
|
|
3881
|
+
return { success: false, error: r.error };
|
|
3882
|
+
return { success: true, output: `💰 地址 ${args.address}\n 余额: ${r.balance} ETH\n 网络: ${r.network}` };
|
|
3883
|
+
}
|
|
3884
|
+
catch (e) {
|
|
3885
|
+
return { success: false, error: `x402_check_balance 失败: ${String(e.message || e)}` };
|
|
3886
|
+
}
|
|
3887
|
+
},
|
|
3888
|
+
});
|
|
3889
|
+
// Safe
|
|
3890
|
+
ctx.tools.set('safe_deploy', {
|
|
3891
|
+
name: 'safe_deploy',
|
|
3892
|
+
description: '部署 Safe 多签钱包.',
|
|
3893
|
+
parameters: { owners: 'JSON 数组 owner 地址 (必填)', threshold: '需要几个签名 (必填)' },
|
|
3894
|
+
execute: async (args) => {
|
|
3895
|
+
try {
|
|
3896
|
+
const { deploySafe } = await Promise.resolve().then(() => __importStar(require('../constraint-runtime/dist/tools/SafeSDK/deploySafe.js'))).catch(() => Promise.resolve().then(() => __importStar(require('../constraint-runtime/src/tools/SafeSDK/deploySafe.js'))));
|
|
3897
|
+
const owners = Array.isArray(args.owners) ? args.owners : JSON.parse(String(args.owners));
|
|
3898
|
+
const r = await deploySafe({ owners, threshold: Number(args.threshold) });
|
|
3899
|
+
return { success: true, output: `✅ Safe 部署: ${JSON.stringify(r)}` };
|
|
3900
|
+
}
|
|
3901
|
+
catch (e) {
|
|
3902
|
+
return { success: false, error: `部署失败: ${String(e.message || e)}` };
|
|
3903
|
+
}
|
|
3904
|
+
}
|
|
3905
|
+
});
|
|
3906
|
+
// 2026-07-28: 注册 LSP 工具 (代码智能)
|
|
3907
|
+
// registerBuiltinTools 不是 async, 用同步 import 兜底
|
|
3908
|
+
try {
|
|
3909
|
+
// 动态 import + 立即执行, 兼容 ESM
|
|
3910
|
+
Promise.resolve().then(() => __importStar(require('../lsp/lsp-tools.js'))).then(({ registerLspTools }) => {
|
|
3911
|
+
registerLspTools(ctx);
|
|
3912
|
+
}).catch((e) => {
|
|
3913
|
+
console.warn('[registerTools] LSP 工具注册失败 (非致命):', e);
|
|
3914
|
+
});
|
|
3915
|
+
}
|
|
3916
|
+
catch (lspErr) {
|
|
3917
|
+
console.warn('[registerTools] LSP 工具注册失败 (非致命):', lspErr);
|
|
3918
|
+
}
|
|
3919
|
+
// 2026-08-06: 注册 OrbitDB/CID 数据层工具 (cid_save/load/update/version/list/share + context 快照 + UI CID)
|
|
3920
|
+
try {
|
|
3921
|
+
Promise.resolve().then(() => __importStar(require('../orbitdb/agent-tools.js'))).then(({ registerOrbitdbTools }) => {
|
|
3922
|
+
registerOrbitdbTools(ctx);
|
|
3923
|
+
}).catch((e) => {
|
|
3924
|
+
console.warn('[registerTools] OrbitDB 工具注册失败 (非致命):', e);
|
|
3925
|
+
});
|
|
3926
|
+
}
|
|
3927
|
+
catch (odbErr) {
|
|
3928
|
+
console.warn('[registerTools] OrbitDB 工具注册失败 (非致命):', odbErr);
|
|
3929
|
+
}
|
|
3930
|
+
}
|
|
3931
|
+
/**
|
|
3932
|
+
* 监听 P2P 消息 (远端 + 本地 inbox bus), 把消息存到 _inboxMessages 供 check_inbox 读.
|
|
3933
|
+
* 失败静默, 不阻塞 PiAgentSession 构造.
|
|
3934
|
+
*/
|
|
3935
|
+
function setupInboxListener(ctx) {
|
|
3936
|
+
try {
|
|
3937
|
+
p2p_js_1.p2pNetwork.onMessage('*', (msg, from, did) => {
|
|
3938
|
+
try {
|
|
3939
|
+
const text = new TextDecoder().decode(msg);
|
|
3940
|
+
let type = 'message';
|
|
3941
|
+
let payload = text;
|
|
3942
|
+
if (text.startsWith('DID:')) {
|
|
3943
|
+
const pipeIdx = text.indexOf('|');
|
|
3944
|
+
if (pipeIdx > 0) {
|
|
3945
|
+
const rest = text.substring(pipeIdx + 1);
|
|
3946
|
+
const colonIdx = rest.indexOf(':');
|
|
3947
|
+
if (colonIdx > 0) {
|
|
3948
|
+
type = rest.substring(0, colonIdx);
|
|
3949
|
+
payload = rest.substring(colonIdx + 1);
|
|
3950
|
+
}
|
|
3951
|
+
else {
|
|
3952
|
+
type = rest;
|
|
3953
|
+
payload = '';
|
|
3954
|
+
}
|
|
3955
|
+
}
|
|
3956
|
+
}
|
|
3957
|
+
else {
|
|
3958
|
+
const colonIdx = text.indexOf(':');
|
|
3959
|
+
if (colonIdx > 0) {
|
|
3960
|
+
type = text.substring(0, colonIdx);
|
|
3961
|
+
payload = text.substring(colonIdx + 1);
|
|
3962
|
+
}
|
|
3963
|
+
}
|
|
3964
|
+
ctx._inboxMessages.push({
|
|
3965
|
+
id: `p2p-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
3966
|
+
from: from.substring(0, 32),
|
|
3967
|
+
fromDid: did,
|
|
3968
|
+
type,
|
|
3969
|
+
payload,
|
|
3970
|
+
timestamp: Date.now(),
|
|
3971
|
+
source: 'p2p',
|
|
3972
|
+
});
|
|
3973
|
+
if (ctx._inboxMessages.length > 1000) {
|
|
3974
|
+
ctx._inboxMessages.splice(0, ctx._inboxMessages.length - 1000);
|
|
3975
|
+
}
|
|
3976
|
+
}
|
|
3977
|
+
catch (err) {
|
|
3978
|
+
console.warn('[PiAgent] inbox listener decode error:', err);
|
|
3979
|
+
}
|
|
3980
|
+
});
|
|
3981
|
+
}
|
|
3982
|
+
catch (err) {
|
|
3983
|
+
console.warn('[PiAgent] setupInboxListener (p2pNetwork) failed (non-fatal):', err);
|
|
3984
|
+
}
|
|
3985
|
+
try {
|
|
3986
|
+
const { LocalInboxBus } = require('../network/local-inbox-bus.js');
|
|
3987
|
+
const myRole = process.env.BOLLOON_ROLE || 'default';
|
|
3988
|
+
LocalInboxBus.getInstance().subscribe(myRole, (msg) => {
|
|
3989
|
+
ctx._inboxMessages.push({
|
|
3990
|
+
id: `local-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
3991
|
+
from: msg.from || 'unknown',
|
|
3992
|
+
fromDid: msg.fromDid,
|
|
3993
|
+
type: msg.type || 'agent-local-message',
|
|
3994
|
+
payload: msg.payload || '',
|
|
3995
|
+
timestamp: msg.timestamp || Date.now(),
|
|
3996
|
+
source: 'local',
|
|
3997
|
+
});
|
|
3998
|
+
if (ctx._inboxMessages.length > 1000) {
|
|
3999
|
+
ctx._inboxMessages.splice(0, ctx._inboxMessages.length - 1000);
|
|
4000
|
+
}
|
|
4001
|
+
});
|
|
4002
|
+
}
|
|
4003
|
+
catch (err) {
|
|
4004
|
+
// LocalInboxBus 可能还没创建, 不阻塞
|
|
4005
|
+
}
|
|
4006
|
+
}
|
|
4007
|
+
/**
|
|
4008
|
+
* M3.3: 工具结果缓存 — 防止 loop 重试时副作用 (写文件 / 改代码) 执行多次.
|
|
4009
|
+
* 5 分钟 TTL, 容量 200, 只缓存 success 结果.
|
|
4010
|
+
*/
|
|
4011
|
+
class IdempotencyCache {
|
|
4012
|
+
cache = new Map();
|
|
4013
|
+
TTL_MS = 5 * 60 * 1000;
|
|
4014
|
+
MAX = 200;
|
|
4015
|
+
wrap(tools) {
|
|
4016
|
+
for (const [name, tool] of tools.entries()) {
|
|
4017
|
+
if (!exports.SIDE_EFFECT_TOOLS.has(name))
|
|
4018
|
+
continue;
|
|
4019
|
+
const original = tool.execute;
|
|
4020
|
+
tool.execute = async (args) => {
|
|
4021
|
+
const key = `${name}|${JSON.stringify(args)}`;
|
|
4022
|
+
const cached = this.cache.get(key);
|
|
4023
|
+
if (cached && Date.now() - cached.ts < this.TTL_MS) {
|
|
4024
|
+
return { ...cached.result, output: (cached.result.output || '') + '\n[↻ idempotency cache hit]' };
|
|
4025
|
+
}
|
|
4026
|
+
const result = await original(args);
|
|
4027
|
+
if (result && result.success) {
|
|
4028
|
+
if (this.cache.size >= this.MAX) {
|
|
4029
|
+
this.cache.clear();
|
|
4030
|
+
}
|
|
4031
|
+
this.cache.set(key, { result, ts: Date.now() });
|
|
4032
|
+
}
|
|
4033
|
+
return result;
|
|
4034
|
+
};
|
|
4035
|
+
}
|
|
4036
|
+
}
|
|
4037
|
+
clear() {
|
|
4038
|
+
this.cache.clear();
|
|
4039
|
+
}
|
|
4040
|
+
}
|
|
4041
|
+
exports.IdempotencyCache = IdempotencyCache;
|
|
4042
|
+
//# sourceMappingURL=pi-sdk-tools.js.map
|