@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,3335 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Pi-SDK - Agent Session for Document Processing
|
|
4
|
+
* Part of OpenClaw dual-layer architecture
|
|
5
|
+
*
|
|
6
|
+
* 模块拆分 (2026-07-06):
|
|
7
|
+
* - types → ./pi-sdk-types.ts (interface / type)
|
|
8
|
+
* - session mgr → ./pi-sdk-session-manager.ts (PiSessionManager 类)
|
|
9
|
+
* - tools → ./pi-sdk-tools.ts (registerBuiltinTools / Wallet / IdempotencyCache)
|
|
10
|
+
* - factory → ./pi-sdk-session-factory.ts (createAgentSession / getAgentSession / resetAgentSession / runSelfImproveLoop)
|
|
11
|
+
* - 本文件 (PiAgentSession 类: LLM 循环 / 系统提示 / 工具调用分发 / 压缩 / persistence)
|
|
12
|
+
*/
|
|
13
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
16
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
17
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
18
|
+
}
|
|
19
|
+
Object.defineProperty(o, k2, desc);
|
|
20
|
+
}) : (function(o, m, k, k2) {
|
|
21
|
+
if (k2 === undefined) k2 = k;
|
|
22
|
+
o[k2] = m[k];
|
|
23
|
+
}));
|
|
24
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
25
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
26
|
+
}) : function(o, v) {
|
|
27
|
+
o["default"] = v;
|
|
28
|
+
});
|
|
29
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
30
|
+
var ownKeys = function(o) {
|
|
31
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
32
|
+
var ar = [];
|
|
33
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
34
|
+
return ar;
|
|
35
|
+
};
|
|
36
|
+
return ownKeys(o);
|
|
37
|
+
};
|
|
38
|
+
return function (mod) {
|
|
39
|
+
if (mod && mod.__esModule) return mod;
|
|
40
|
+
var result = {};
|
|
41
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
42
|
+
__setModuleDefault(result, mod);
|
|
43
|
+
return result;
|
|
44
|
+
};
|
|
45
|
+
})();
|
|
46
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
47
|
+
exports.PiAgentSession = exports.runSelfImproveLoop = exports.resetAgentSession = exports.getAgentSession = exports.createAgentSession = exports.PiSessionManager = exports.TOOL_DEFINITIONS = void 0;
|
|
48
|
+
exports.decideUnreported = decideUnreported;
|
|
49
|
+
const fsSync = __importStar(require("fs"));
|
|
50
|
+
const os = __importStar(require("os"));
|
|
51
|
+
const path = __importStar(require("path"));
|
|
52
|
+
const context_manager_js_1 = require("../bootstrap/context-manager.js");
|
|
53
|
+
const module_1 = require("module");
|
|
54
|
+
const version_info_js_1 = require("../utils/version-info.js");
|
|
55
|
+
// 2026-08-07: ESM 下裸 require 抛错被 catch 吞掉 → estimateHistoryTokens/maxContextTokens 静默失效
|
|
56
|
+
// (状态栏恒 0 的第二层根因). 统一用 createRequire 加载 CJS 模块.
|
|
57
|
+
// 2026-09-19: 原为 createRequire(import.meta.url) —— electron 构建(CommonJS)下 TS1343。
|
|
58
|
+
// createRequire 只用来解析裸模块名, 给包内任意文件路径即可, 用包根 package.json 最稳。
|
|
59
|
+
const _piRequire = (0, module_1.createRequire)(path.join((0, version_info_js_1.currentPackageRoot)(), 'package.json'));
|
|
60
|
+
const reader_js_1 = require("../documents/reader.js");
|
|
61
|
+
const index_js_1 = require("../constraints/index.js");
|
|
62
|
+
const p2p_js_1 = require("../network/p2p.js");
|
|
63
|
+
const constraint_layer_js_1 = require("./constraint-layer.js");
|
|
64
|
+
const workflow_engine_js_1 = require("./workflow-engine.js");
|
|
65
|
+
const constraint_runtime_1 = require("@bolloon/constraint-runtime");
|
|
66
|
+
const workflow_pivot_loop_js_1 = require("./workflow-pivot-loop.js");
|
|
67
|
+
const p2p_document_tools_js_1 = require("./p2p-document-tools.js");
|
|
68
|
+
const run_store_js_1 = require("./run-store.js");
|
|
69
|
+
const goal_store_js_1 = require("./goal-store.js");
|
|
70
|
+
const pi_harness_js_1 = require("./pi-harness.js");
|
|
71
|
+
const heartbeat_js_1 = require("../social/heartbeat.js");
|
|
72
|
+
const constraint_runtime_2 = require("@bolloon/constraint-runtime");
|
|
73
|
+
const skill_loader_js_1 = require("./skill-loader.js");
|
|
74
|
+
/** 2026-08-10: unreported 逃生门判定 — LLM 反复不把工具结果写进回复时, 超过上限强制收尾 (防死循环) */
|
|
75
|
+
function decideUnreported(unreported, retries, max) {
|
|
76
|
+
if (unreported <= 0)
|
|
77
|
+
return 'none';
|
|
78
|
+
return retries < max ? 'retry' : 'force-final';
|
|
79
|
+
}
|
|
80
|
+
// 拆分后的子模块 — 重新导出保 backward compat
|
|
81
|
+
var pi_sdk_types_js_1 = require("./pi-sdk-types.js");
|
|
82
|
+
Object.defineProperty(exports, "TOOL_DEFINITIONS", { enumerable: true, get: function () { return pi_sdk_types_js_1.TOOL_DEFINITIONS; } });
|
|
83
|
+
var pi_sdk_session_manager_js_1 = require("./pi-sdk-session-manager.js");
|
|
84
|
+
Object.defineProperty(exports, "PiSessionManager", { enumerable: true, get: function () { return pi_sdk_session_manager_js_1.PiSessionManager; } });
|
|
85
|
+
const pi_sdk_session_manager_js_2 = require("./pi-sdk-session-manager.js");
|
|
86
|
+
const pi_sdk_tools_js_1 = require("./pi-sdk-tools.js");
|
|
87
|
+
const tools_js_1 = require("./contacts/tools.js");
|
|
88
|
+
const chain_js_1 = require("./contacts/chain.js");
|
|
89
|
+
var pi_sdk_session_factory_js_1 = require("./pi-sdk-session-factory.js");
|
|
90
|
+
Object.defineProperty(exports, "createAgentSession", { enumerable: true, get: function () { return pi_sdk_session_factory_js_1.createAgentSession; } });
|
|
91
|
+
Object.defineProperty(exports, "getAgentSession", { enumerable: true, get: function () { return pi_sdk_session_factory_js_1.getAgentSession; } });
|
|
92
|
+
Object.defineProperty(exports, "resetAgentSession", { enumerable: true, get: function () { return pi_sdk_session_factory_js_1.resetAgentSession; } });
|
|
93
|
+
Object.defineProperty(exports, "runSelfImproveLoop", { enumerable: true, get: function () { return pi_sdk_session_factory_js_1.runSelfImproveLoop; } });
|
|
94
|
+
// Judgment 注入门 (P0): 在主对话 LLM 调起前自动拼入 Top 3 判断力
|
|
95
|
+
// 失败静默, 不阻塞主对话
|
|
96
|
+
const injection_gate_js_1 = require("../pi-ecosystem-judgment/injection-gate.js");
|
|
97
|
+
const exhaust_scrubber_js_1 = require("../bootstrap/exhaust-scrubber.js");
|
|
98
|
+
// 持续监控门 (P3): AI 回复后审计是否违反原则
|
|
99
|
+
const monitor_gate_js_1 = require("../pi-ecosystem-judgment/monitor-gate.js");
|
|
100
|
+
// Bootstrap 生命周期 hook (SessionStart / Stop / PreToolUse)
|
|
101
|
+
const human_value_pipeline_js_1 = require("../pi-ecosystem-judgment/human-value-pipeline.js");
|
|
102
|
+
const lifecycle_hooks_js_1 = require("../bootstrap/lifecycle-hooks.js");
|
|
103
|
+
const index_js_2 = require("../context-compaction/index.js");
|
|
104
|
+
// React Harness: 8-gate + 4-guard (防越权 / 防 prompt 注入)
|
|
105
|
+
const react_harness_js_1 = require("../security/react-harness.js");
|
|
106
|
+
const hooks_engine_js_1 = require("../hooks/hooks-engine.js");
|
|
107
|
+
const deny_pipeline_js_1 = require("./deny-pipeline.js");
|
|
108
|
+
const parse_tool_call_js_1 = require("./parse-tool-call.js");
|
|
109
|
+
const error_classifier_js_1 = require("./error-classifier.js");
|
|
110
|
+
const session_store_js_1 = require("./session-store.js");
|
|
111
|
+
const tool_registry_js_1 = require("./tool-registry.js");
|
|
112
|
+
const react_loop_js_1 = require("./react-loop.js");
|
|
113
|
+
const loop_review_js_1 = require("./loop-review.js");
|
|
114
|
+
// PiSessionManager 已抽到 ./pi-sdk-session-manager.ts (2026-07-06)
|
|
115
|
+
// Tool / ToolResult / Message / StreamCallback / StreamEvent / HeartbeatConfig / AgentSession / TOOL_DEFINITIONS
|
|
116
|
+
// 已抽到 ./pi-sdk-types.ts (2026-07-06)
|
|
117
|
+
/**
|
|
118
|
+
* 同一工具 + 同一组参数连续失败上限 (2026-07-01 起) —— 触发两条互不替代的动作:
|
|
119
|
+
* 循环内: 注入 system 提示强制 LLM 收尾; 运行层 (M3): 熔断 → needs_human (交人)。
|
|
120
|
+
*/
|
|
121
|
+
const MAX_SAME_TOOL_FAILURES = 3;
|
|
122
|
+
class PiAgentSession {
|
|
123
|
+
cwd;
|
|
124
|
+
peerId;
|
|
125
|
+
identity;
|
|
126
|
+
persona = null;
|
|
127
|
+
minimaxAvailable = false;
|
|
128
|
+
workflows = new Map();
|
|
129
|
+
constraintLayer;
|
|
130
|
+
workflowEngine;
|
|
131
|
+
sessionManager;
|
|
132
|
+
agentsManager;
|
|
133
|
+
socialHeartbeat = null;
|
|
134
|
+
messageHistory = [];
|
|
135
|
+
tools = new Map();
|
|
136
|
+
/** 2026-06-30: tool registry 模块 — 独立 alias resolve, 测试可消融. */
|
|
137
|
+
_toolRegistry = new tool_registry_js_1.ToolRegistry();
|
|
138
|
+
skillRegistry = new constraint_runtime_2.SkillRegistry();
|
|
139
|
+
/** M2.4: 缓存 tool 列表, registerTools() 之后不变, runReActLoop 多次循环复用 */
|
|
140
|
+
cachedToolDefinitions = '';
|
|
141
|
+
/** M2.4: 缓存 persona section */
|
|
142
|
+
cachedPersonaSection = '';
|
|
143
|
+
/** 2026-06-30: 持久化层 — 默认走 ~/.bolloon/sessions/cache/, 测试可注入临时目录. */
|
|
144
|
+
_sessionStore;
|
|
145
|
+
/** 构造期间 fire-and-forget 任务的 promise — whenReady() 等它 */
|
|
146
|
+
_readyPromise = null;
|
|
147
|
+
// 2026-06-16 修: 父要求把 ReAct loop 上限放大到 "几乎无限", 靠自动压缩上下文 + fail-safe 兜底
|
|
148
|
+
// 默认 10000 — 正常任务永远跑不到, 但作为防 LLM 死循环 / 防 OOM 的最后一道闸
|
|
149
|
+
// 旧默认 100 写死导致中等复杂度任务 (10-50 个 tool call + 多步反思) 会被误杀
|
|
150
|
+
MAX_REACT_ITERATIONS = 10_000;
|
|
151
|
+
MAX_REFINE_ATTEMPTS = 3;
|
|
152
|
+
QUALITY_THRESHOLD = 0.6;
|
|
153
|
+
/** P1: 上下文溢出阈值 (单轮估算 token 数, 超过则强制终止防止 prompt-too-long)
|
|
154
|
+
* 2026-08-06: 从 ContextManager 动态读 (默认 1M, env MAX_CONTEXT_TOKENS 可调).
|
|
155
|
+
* 保留字段仅作降级兜底 (ContextManager 初始化失败时用 60K 老行为). */
|
|
156
|
+
MAX_OUTPUT_TOKEN_ESCALATION_THRESHOLD = 60_000; // fallback 60K tokens
|
|
157
|
+
/** 2026-08-06: 上下文窗口 (tokens) — 统一走 ContextManager 配置 (1M 默认). */
|
|
158
|
+
maxContextTokens() {
|
|
159
|
+
try {
|
|
160
|
+
const { getContextManager } = _piRequire('../bootstrap/context-manager.js');
|
|
161
|
+
const n = getContextManager().getConfig().maxTokens;
|
|
162
|
+
return Number.isFinite(n) && n > 0 ? n : this.MAX_OUTPUT_TOKEN_ESCALATION_THRESHOLD;
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
return this.MAX_OUTPUT_TOKEN_ESCALATION_THRESHOLD;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/** 2026-06-16 新增: 累计错误总数兜底 (不管是否同工具, 累计 N 次就强制退出)
|
|
169
|
+
* 防 LLM 轮换工具名绕开 MAX_SAME_TOOL_FAILURES 的死循环攻击 */
|
|
170
|
+
MAX_TOTAL_ERRORS = 20;
|
|
171
|
+
/** 2026-06-19: 记录 loop 内成功执行的工具结果, 失败退出时汇总给用户 */
|
|
172
|
+
successfulToolResults = [];
|
|
173
|
+
/** 2026-06-19: Agent Mesh 通信 — 本地 + 远端 inbox 缓存, 给 check_inbox 工具读 */
|
|
174
|
+
_inboxMessages = [];
|
|
175
|
+
/** 2026-06-16 新增: loop 内自动压缩触发阈值 (相对 60K 阈值的比例) */
|
|
176
|
+
LOOP_COMPACT_RATIO = 0.8;
|
|
177
|
+
/** P1: max output token 升级重试 (LLM 截断时重试, 最多 3 次) */
|
|
178
|
+
MAX_OUTPUT_TOKEN_ESCALATION_RETRIES = 3;
|
|
179
|
+
thinkingEngine = new constraint_runtime_1.DeepThinkingEngine(3);
|
|
180
|
+
coordinator = new constraint_runtime_1.AgentCoordinator(3);
|
|
181
|
+
harness = null;
|
|
182
|
+
harnessEnabled = false;
|
|
183
|
+
/** 8-gate + 4-guard 集中调度 (防越权 / 防 prompt 注入) */
|
|
184
|
+
reactHarness = new react_harness_js_1.ReactHarness();
|
|
185
|
+
usePivotLoop = false;
|
|
186
|
+
pivotLoopConfig;
|
|
187
|
+
/** P2: 当前会话的 permission mode (每次 promptStream 入口解析) */
|
|
188
|
+
currentPermissionMode = 'default';
|
|
189
|
+
/** P1.2: Context Collapse 读时投影结果 (feature flag 开启时由 maybeAutoCompact 写入, buildContext 优先用) */
|
|
190
|
+
projectedHistory = null;
|
|
191
|
+
/**
|
|
192
|
+
* 2026-07-29 (Tool pre-filter): 拒绝工具列表 — 这些工具从模型视野完全删除
|
|
193
|
+
* 模型"看不到"这些工具 (name/description/params 不在 system prompt 列出,
|
|
194
|
+
* 也不出现在 native API tools 参数中).
|
|
195
|
+
* 在 registerTools() 之后立即应用, 也可运行时通过 denyTool/allowTool 动态调整.
|
|
196
|
+
* 首次 getToolDefinitions() 调用时会缓存带过滤的结果.
|
|
197
|
+
*/
|
|
198
|
+
_deniedToolNames = new Set();
|
|
199
|
+
/** 2026-07-29: Hook 引擎 */
|
|
200
|
+
_hooks = new hooks_engine_js_1.HooksEngine();
|
|
201
|
+
/** 2026-07-29: Unified Deny-First Pipeline */
|
|
202
|
+
_denyPipeline = new deny_pipeline_js_1.DenyPipeline();
|
|
203
|
+
/** 2026-07-29: Snip + Context Collapse 启用标志 (默认启用) */
|
|
204
|
+
_enableSnipCollapse = true;
|
|
205
|
+
/** 注册一个或多个工具到拒绝列表 */
|
|
206
|
+
denyTool(...names) {
|
|
207
|
+
for (const name of names)
|
|
208
|
+
this._deniedToolNames.add(name);
|
|
209
|
+
// 拒绝列表变了, 清除缓存让下次 getToolDefinitions 重新生成
|
|
210
|
+
this.cachedToolDefinitions = '';
|
|
211
|
+
}
|
|
212
|
+
/** 从拒绝列表移除一个或多个工具 */
|
|
213
|
+
allowTool(...names) {
|
|
214
|
+
for (const name of names)
|
|
215
|
+
this._deniedToolNames.delete(name);
|
|
216
|
+
this.cachedToolDefinitions = '';
|
|
217
|
+
}
|
|
218
|
+
/** 获取当前拒绝列表 (快照) */
|
|
219
|
+
getDeniedTools() {
|
|
220
|
+
return Array.from(this._deniedToolNames);
|
|
221
|
+
}
|
|
222
|
+
/** 返回已过滤(剔除拒绝工具)的工具迭代器 */
|
|
223
|
+
allowedTools() {
|
|
224
|
+
const self = this;
|
|
225
|
+
return (function* () {
|
|
226
|
+
for (const [name, tool] of self.tools) {
|
|
227
|
+
if (!self._deniedToolNames.has(name))
|
|
228
|
+
yield tool;
|
|
229
|
+
}
|
|
230
|
+
})();
|
|
231
|
+
}
|
|
232
|
+
/** 2026-08-08: 公开可用工具列表 (name + description + 参数名) 供 /tools 显示 */
|
|
233
|
+
getToolList() {
|
|
234
|
+
const out = [];
|
|
235
|
+
for (const tool of this.allowedTools()) {
|
|
236
|
+
const paramNames = tool.parameters ? Object.keys(tool.parameters) : [];
|
|
237
|
+
out.push({ name: tool.name, description: tool.description || '', parameters: paramNames });
|
|
238
|
+
}
|
|
239
|
+
return out;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Judgment 注入门临时结果: 在 prompt / promptStream / promptWithPivotLoop 入口算一次, 拼到本轮 systemPrompt 末尾
|
|
243
|
+
* 每次调用都会重置 (避免上一轮遗留)
|
|
244
|
+
*/
|
|
245
|
+
judgmentGateAddition = '';
|
|
246
|
+
judgmentGateUsedIds = [];
|
|
247
|
+
/** 2026-07-22 设计 B: 负向判断力 (避免清单) 注入用到的 judgment id */
|
|
248
|
+
judgmentGateNegativeUsedIds = [];
|
|
249
|
+
// 2026-06-18: 来自 web server markedPrompt 外的 contextHint (channel/judgment/distill/remote channels),
|
|
250
|
+
// 拼到 systemPrompt 末尾, 别再混进 user message
|
|
251
|
+
contextHintAddition = '';
|
|
252
|
+
/**
|
|
253
|
+
* 当前 onStream 引用 + abort signal (computeJudgmentGate 需要 onStream 广播 phase)
|
|
254
|
+
* 每次 prompt / promptStream / promptWithPivotLoop 入口设置, 用完即清
|
|
255
|
+
*/
|
|
256
|
+
currentOnStream = null;
|
|
257
|
+
currentSignal = null;
|
|
258
|
+
/** Bootstrap SessionStart 拼的 system prompt 片段 (用完即清) */
|
|
259
|
+
bootstrapAddition = '';
|
|
260
|
+
/** 2026-08-12 (Task3 认知卸载): 工具选择与认知卸载指南 — 注入 system prompt, 降低"模型不触发 write/edit/read"率. */
|
|
261
|
+
static TOOL_SELECTION_GUIDE = `【工具选择与认知卸载指南 (严格遵循)】
|
|
262
|
+
- 写/改文件: 必须用 write_file / edit_file (不要用 terminal 拼字符串写文件).
|
|
263
|
+
- 读文件: 用 read_file / read_directory / list_files.
|
|
264
|
+
- 跑命令/查状态/装依赖/跑脚本: 用 terminal.
|
|
265
|
+
- 执行 git 操作: 用 git_* 专用工具 (git_diff/git_commit/git_push/git_branch/git_log).
|
|
266
|
+
- 查看/调用技能: 用 list_skills / use_skill.
|
|
267
|
+
- 认知卸载: 当任务对你过大或反复失败时, 不要死磕 — 用 delegate_to_engine 把编码/复杂任务委派给外部引擎 (codex/claude-code/opencode), 或把目标拆小分步完成.
|
|
268
|
+
- 每步只选一个最合适的工具, 先读后写, 写完验证 (跑 tsc/vitest 或读回文件).`;
|
|
269
|
+
/** 当前 prompt 开始时间 (供 Stop hook 计算 durationMs) */
|
|
270
|
+
promptStartTime = 0;
|
|
271
|
+
/** 当前 channel id (由 getAgentForChannel / prompt 4 参注入, 供 hook / log 使用) */
|
|
272
|
+
currentChannelId = '';
|
|
273
|
+
/** 2026-07-04: 当前 agentId (server.ts 通过 createAgentSession 选项注入), 供 onSessionStart 加载 persona docs */
|
|
274
|
+
currentAgentId = '';
|
|
275
|
+
/** M2.2 (2026-06-17): 当前轮的用户请求 intent, runReActLoop 拼 systemPrompt 时会读这个 */
|
|
276
|
+
currentIntent = 'chitchat';
|
|
277
|
+
/** 2026-08-10: 本轮用户原始输入 (loop-review 任务动词兜底检测用) */
|
|
278
|
+
currentUserInput = '';
|
|
279
|
+
/**
|
|
280
|
+
* 2026-09-16: 持久化 run harness — 本次运行的落盘记录 id + 表面 (cli/web/cron/delegate)。
|
|
281
|
+
* 之前只有"跑完才写"的轨迹 (trajectory-store, fire-and-forget, 崩了就没有), 没有跨重载可见的
|
|
282
|
+
* 运行事实, 也没有预算闸门/失速巡检; 这里补的就是那一层。
|
|
283
|
+
*/
|
|
284
|
+
currentRunId = '';
|
|
285
|
+
/** 上一次运行的 runId (收尾不清空; 见 getLastRunId) */
|
|
286
|
+
lastRunId = '';
|
|
287
|
+
runSurface = 'cli';
|
|
288
|
+
/**
|
|
289
|
+
* 2026-09-16 (Milestone 1): 本次运行因**持久化失败**被硬停。
|
|
290
|
+
* 标记出来是为了让外层 loop 重试逻辑别重试 —— 盘写不进去, 重试 3 次也只是再失败 3 次
|
|
291
|
+
* (协议: 同一个错误不许无限重复; 该交人时交人)。
|
|
292
|
+
*/
|
|
293
|
+
runPersistenceBlocked = false;
|
|
294
|
+
/** 由 server / CLI / cron 注入运行表面 (影响落盘记录里的 surface 字段) */
|
|
295
|
+
setRunSurface(surface) {
|
|
296
|
+
this.runSurface = surface;
|
|
297
|
+
}
|
|
298
|
+
currentIntentHint = '';
|
|
299
|
+
/**
|
|
300
|
+
* 算 judgment 注入门: 失败静默, 不阻塞主对话
|
|
301
|
+
* 期间通过 currentOnStream 广播 phase 事件, 前端可显示 "正在检索判断力..." 状态
|
|
302
|
+
* 调用方负责用完即清 (judgmentGateAddition='')
|
|
303
|
+
*/
|
|
304
|
+
async computeJudgmentGate(input) {
|
|
305
|
+
const safePhase = (phase, extra = {}) => {
|
|
306
|
+
try {
|
|
307
|
+
if (this.currentOnStream) {
|
|
308
|
+
this.currentOnStream({ type: 'phase', phase, ...extra, content: '' });
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
catch { /* 静默 */ }
|
|
312
|
+
};
|
|
313
|
+
safePhase('gate_compute', { detail: '正在检索相关判断力...' });
|
|
314
|
+
try {
|
|
315
|
+
// P-Action 4 (2026-06-15) 路径 1 整合: 透传 maxChars=1500 (≈ 375 tokens 硬上限)
|
|
316
|
+
// 路径 2/3 检测由 injection-gate 内部 alreadyInjectedSources 处理 (目前 assembleSystemPrompt 还没注入 value-store 标记, 所以这里不传)
|
|
317
|
+
// 2026-07-22 设计 C: maxChars 读背压动态值 (涡轮增压进气调参)
|
|
318
|
+
// 上下文紧张 (high) → 收紧 800; 宽裕 (idle/low) → 放宽 1800; 默认 medium 1500
|
|
319
|
+
const gate = await (0, injection_gate_js_1.injectJudgmentGate)(input, {}, { maxChars: (0, exhaust_scrubber_js_1.getInjectionMaxChars)() });
|
|
320
|
+
this.judgmentGateAddition = gate.systemAddition;
|
|
321
|
+
this.judgmentGateUsedIds = gate.usedIds;
|
|
322
|
+
// 2026-07-22 设计 B: 负向判断力回收 — "避免清单"注入 (显式, 进 prompt)
|
|
323
|
+
// 判断力负向是"判断力"非"废气", 可进 prompt 作为约束 (精准 = 正向指引 + 负向避免)
|
|
324
|
+
try {
|
|
325
|
+
const neg = await (0, injection_gate_js_1.injectNegativeGuard)(input, {}, { maxChars: 300 });
|
|
326
|
+
if (neg.didInject && neg.systemAddition) {
|
|
327
|
+
this.judgmentGateAddition += '\n' + neg.systemAddition;
|
|
328
|
+
this.judgmentGateNegativeUsedIds = neg.usedIds;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
catch (negErr) {
|
|
332
|
+
console.warn('[PiAgent] negative guard failed (non-fatal):', negErr);
|
|
333
|
+
}
|
|
334
|
+
if (this.judgmentGateUsedIds.length > 0 || this.judgmentGateNegativeUsedIds.length > 0) {
|
|
335
|
+
safePhase('gate_done', { usedCount: this.judgmentGateUsedIds.length, negativeCount: this.judgmentGateNegativeUsedIds.length, didInject: gate.didInject, skipReason: gate.skipReason });
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
catch (err) {
|
|
339
|
+
console.warn('[PiAgent] judgment gate failed (non-fatal):', err);
|
|
340
|
+
this.judgmentGateAddition = '';
|
|
341
|
+
this.judgmentGateUsedIds = [];
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
clearJudgmentGate() {
|
|
345
|
+
this.judgmentGateAddition = '';
|
|
346
|
+
this.judgmentGateUsedIds = [];
|
|
347
|
+
this.judgmentGateNegativeUsedIds = [];
|
|
348
|
+
}
|
|
349
|
+
constructor(config) {
|
|
350
|
+
this.cwd = config.cwd;
|
|
351
|
+
this.peerId = config.peerId || 'local';
|
|
352
|
+
this.identity = config.identityDoc || this.createDefaultIdentity();
|
|
353
|
+
this.minimaxAvailable = this.checkMinimax();
|
|
354
|
+
// 2026-07-04: 透传 agentId (server.ts 通过 createAgentSession 选项注入)
|
|
355
|
+
this.currentAgentId = config.agentId || '';
|
|
356
|
+
// 2026-06-30: 持久化层可注入 — 测试传 tmpDir, 业务用默认 ~/.bolloon/sessions/cache/
|
|
357
|
+
this._sessionStore = config.sessionStore ?? session_store_js_1.sessionStore;
|
|
358
|
+
this.constraintLayer = new constraint_layer_js_1.ConstraintLayer();
|
|
359
|
+
this.workflowEngine = new workflow_engine_js_1.WorkflowEngine(this.constraintLayer);
|
|
360
|
+
this.sessionManager = new pi_sdk_session_manager_js_2.PiSessionManager(this.identity.did, this.cwd);
|
|
361
|
+
this.agentsManager = new heartbeat_js_1.DiscoveredAgentsManager();
|
|
362
|
+
this.usePivotLoop = config.usePivotLoop ?? false;
|
|
363
|
+
this.pivotLoopConfig = config.pivotLoopConfig;
|
|
364
|
+
this.initSession();
|
|
365
|
+
(0, p2p_document_tools_js_1.initDocumentReceiver)();
|
|
366
|
+
this.registerTools();
|
|
367
|
+
// 2026-07-29: 从环境变量加载默认拒绝工具列表 (逗号分隔)
|
|
368
|
+
// BOLLOON_DENIED_TOOLS=shell_exec,git_commit 会在启动时拒绝高危险工具
|
|
369
|
+
try {
|
|
370
|
+
const envDenied = process.env.BOLLOON_DENIED_TOOLS;
|
|
371
|
+
if (envDenied && envDenied.trim()) {
|
|
372
|
+
const names = envDenied.split(',').map(n => n.trim()).filter(Boolean);
|
|
373
|
+
if (names.length > 0)
|
|
374
|
+
this.denyTool(...names);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
catch { /* env 读失败静默 */ }
|
|
378
|
+
// 2026-07-29: 从环境变量控制 Snip/Collapse
|
|
379
|
+
try {
|
|
380
|
+
if (process.env.BOLLOON_SNIP_COLLAPSE === '0')
|
|
381
|
+
this._enableSnipCollapse = false;
|
|
382
|
+
}
|
|
383
|
+
catch { /* 静默 */ }
|
|
384
|
+
// 2026-07-29: 从 ~/.bolloon/hooks.yaml 加载 hook 配置
|
|
385
|
+
// 失败静默 (无 hook 配置也正常)
|
|
386
|
+
try {
|
|
387
|
+
// fire-and-forget, 不阻塞构造
|
|
388
|
+
this._hooks.loadFromConfig().catch(() => { });
|
|
389
|
+
}
|
|
390
|
+
catch { /* 静默 */ }
|
|
391
|
+
// 2026-07-29: 初始化 DenyPipeline — 注册所有检查器
|
|
392
|
+
// 顺序: deny-list (最快) → permission → hooks → judgment
|
|
393
|
+
this._denyPipeline.addChecker(deny_pipeline_js_1.DenyPipeline.denyListChecker(this._deniedToolNames));
|
|
394
|
+
this._denyPipeline.addChecker(deny_pipeline_js_1.DenyPipeline.permissionChecker());
|
|
395
|
+
// 仅当启用了 hook 时注册 hooks 检查器
|
|
396
|
+
// (hook 可能走到 LLM, 是最贵的, 放在最后)
|
|
397
|
+
this._denyPipeline.addChecker(async (ctx) => {
|
|
398
|
+
try {
|
|
399
|
+
const hookResult = await this._hooks.checkToolUse(ctx.toolName, ctx.toolArgs);
|
|
400
|
+
if (hookResult?.deny) {
|
|
401
|
+
return {
|
|
402
|
+
denied: true,
|
|
403
|
+
reason: hookResult.reason || 'Hook 拒绝',
|
|
404
|
+
source: 'hooks',
|
|
405
|
+
systemAddition: hookResult.systemAddition,
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
if (hookResult?.systemAddition) {
|
|
409
|
+
this.contextHintAddition += '\n' + hookResult.systemAddition;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
catch { /* hook 失败不阻塞 */ }
|
|
413
|
+
return { denied: false, reason: '', source: 'hooks' };
|
|
414
|
+
});
|
|
415
|
+
this.loadSkills(config.skillsPaths);
|
|
416
|
+
this.initHarness();
|
|
417
|
+
// M2.3 (2026-06-17): 重启后 LLM 恢复记忆 — 从 session JSON 加载历史到 messageHistory
|
|
418
|
+
// 之前 messageHistory 是空的, 服务重启后 LLM 看到的是新对话
|
|
419
|
+
// 现在 loadSessionKey 形如 "channel-xxx:default" 走 ~/.bolloon/sessions/cache/<key>.json
|
|
420
|
+
if (config.loadSessionKey) {
|
|
421
|
+
this._readyPromise = this.hydrateMessageHistory(config.loadSessionKey, config.loadSessionMaxMessages ?? 30).catch((err) => {
|
|
422
|
+
// 失败静默, 但不让 whenReady 永久 hang
|
|
423
|
+
console.warn(`[PiAgent] hydrateMessageHistory failed: ${err.message?.slice(0, 100)}`);
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* 2026-06-30: 让外部 await 构造期间的 hydrate 完成.
|
|
429
|
+
* 解决 fire-and-forget 让 messageHistory 不可预测的问题.
|
|
430
|
+
* 不传 loadSessionKey 时立即返回.
|
|
431
|
+
*/
|
|
432
|
+
whenReady() {
|
|
433
|
+
return this._readyPromise ?? Promise.resolve();
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* M2.3 (2026-06-30 重构): 从 SessionStore 加载历史, 转成 messageHistory 格式
|
|
437
|
+
* - 失败静默 (历史加载失败不应该阻塞 agent 启动)
|
|
438
|
+
* - 限制 max 条数, 防止 context 爆
|
|
439
|
+
* - 跳过错误消息 ([AI 服务调用失败] / [错误:...]) 不污染 LLM
|
|
440
|
+
* - 委托 SessionStore 完成 IO, 保证 save/load 路径对称
|
|
441
|
+
*
|
|
442
|
+
* 历史格式兼容旧 schema ({type, content}) 和新 schema (PersistedMessage[])
|
|
443
|
+
*/
|
|
444
|
+
async hydrateMessageHistory(sessionKey, maxMessages) {
|
|
445
|
+
try {
|
|
446
|
+
const loaded = await this._sessionStore.loadMessages(sessionKey);
|
|
447
|
+
if (!loaded) {
|
|
448
|
+
console.log(`[PiAgent] hydrate: 没有 ${sessionKey} 的历史`);
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
const hydrated = this._filterToMessage(loaded).slice(-maxMessages);
|
|
452
|
+
if (hydrated.length > 0) {
|
|
453
|
+
this.messageHistory = hydrated;
|
|
454
|
+
console.log(`[PiAgent] 从 ${sessionKey} 回灌 ${hydrated.length} 条历史`);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
catch (err) {
|
|
458
|
+
console.warn(`[PiAgent] hydrateMessageHistory 失败 (non-fatal): ${err.message?.slice(0, 100)}`);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* 2026-06-30: 把当前 messageHistory 持久化到 SessionStore.
|
|
463
|
+
* 公开方法 — claude code / 外部 harness 在每次 prompt 完成后调一下,
|
|
464
|
+
* 即可获得"重启 / 跨进程接续"的语义.
|
|
465
|
+
*/
|
|
466
|
+
async saveCurrentSession(key) {
|
|
467
|
+
const persisted = this.messageHistory.map((m) => ({
|
|
468
|
+
role: m.role,
|
|
469
|
+
content: m.content,
|
|
470
|
+
toolCall: m.toolCall,
|
|
471
|
+
toolResult: m.toolResult,
|
|
472
|
+
toolCallId: m.toolCallId,
|
|
473
|
+
timestamp: Date.now(),
|
|
474
|
+
source: 'pi-session',
|
|
475
|
+
}));
|
|
476
|
+
await this._sessionStore.saveMessages(key, persisted);
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* 2026-06-30: 从 disk 拉历史覆盖当前 messageHistory.
|
|
480
|
+
* 返回加载条数 — 失败或空则返回 0.
|
|
481
|
+
* 与 loadSessionKey (构造时读) 不同: 这个是 session 已建好后再读.
|
|
482
|
+
*/
|
|
483
|
+
async resumeSession(key, maxMessages = 30) {
|
|
484
|
+
const before = this.messageHistory.length;
|
|
485
|
+
await this.hydrateMessageHistory(key, maxMessages);
|
|
486
|
+
return this.messageHistory.length - before;
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* 2026-06-30: 读历史不修改 messageHistory.
|
|
490
|
+
* 给 claude code / 测试做"先看一下历史"用 — 不破坏当前会话.
|
|
491
|
+
* 返回 Message[] 数组 (空数组表示无历史).
|
|
492
|
+
*/
|
|
493
|
+
async peekSessionHistory(key, maxMessages = 30) {
|
|
494
|
+
try {
|
|
495
|
+
const loaded = await this._sessionStore.loadMessages(key);
|
|
496
|
+
if (!loaded)
|
|
497
|
+
return [];
|
|
498
|
+
return this._filterToMessage(loaded).slice(-maxMessages);
|
|
499
|
+
}
|
|
500
|
+
catch {
|
|
501
|
+
return [];
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
/** hydrateMessageHistory 用的过滤逻辑 — 提到外面复用 */
|
|
505
|
+
_filterToMessage(loaded) {
|
|
506
|
+
const hydrated = [];
|
|
507
|
+
const VALID_ROLES = new Set(['user', 'assistant', 'tool', 'system']);
|
|
508
|
+
for (const m of loaded) {
|
|
509
|
+
// role 必须合法 (拒绝旧 schema {type:'user'} 没 role 字段的)
|
|
510
|
+
if (!VALID_ROLES.has(m.role))
|
|
511
|
+
continue;
|
|
512
|
+
// 跳过污染消息
|
|
513
|
+
if (typeof m.content === 'string' && m.content.startsWith('[AI 服务调用失败]'))
|
|
514
|
+
continue;
|
|
515
|
+
if (typeof m.content === 'string' && m.content.startsWith('[错误:'))
|
|
516
|
+
continue;
|
|
517
|
+
// 注意: '!m.content' 会跳过 content='' 的 tool call 消息 (assistant role + toolCall 字段),
|
|
518
|
+
// 这种是合法的 (LLM 输出只有 tool call, 没有正文) — 必须保留.
|
|
519
|
+
// 这里只跳过"无内容 + 也没 tool call/tool result"的废消息.
|
|
520
|
+
if (!m.content && !m.toolCall && !m.toolResult)
|
|
521
|
+
continue;
|
|
522
|
+
// 跳过空 tool role (tool result 占位但没有任何内容)
|
|
523
|
+
if (m.role === 'tool' && !m.toolResult)
|
|
524
|
+
continue;
|
|
525
|
+
hydrated.push({
|
|
526
|
+
role: m.role,
|
|
527
|
+
content: m.content ?? '',
|
|
528
|
+
toolCall: m.toolCall,
|
|
529
|
+
toolResult: m.toolResult,
|
|
530
|
+
toolCallId: m.toolCallId,
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
return hydrated;
|
|
534
|
+
}
|
|
535
|
+
/** 暴露 store 给测试 / 高级集成用. */
|
|
536
|
+
get sessionStoreInstance() {
|
|
537
|
+
return this._sessionStore;
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* 从 SKILL.md 目录加载 skills 进 skillRegistry.
|
|
541
|
+
*
|
|
542
|
+
* 路径解析优先级 (后者覆盖前者同名 skill):
|
|
543
|
+
* 1. 显式传入的 skillsPaths
|
|
544
|
+
* 2. ~/.bolloon/skills/ 全局用户级
|
|
545
|
+
* 3. <cwd>/.bolloon/skills/ 项目级
|
|
546
|
+
* 4. ~/.boll/skills/ 全局 (兼容 bollharness 旧用户)
|
|
547
|
+
*
|
|
548
|
+
* 2026-07-04: 移除 18 个 bollharness builtin skill (findBolloonBuiltinSkillsPath).
|
|
549
|
+
* 历史遗留: 写 pi-sdk 时为方便演示, 把 bolloon 项目里的 19 个 skill 强制注入到 system prompt.
|
|
550
|
+
* 问题: system prompt 涨到 22K chars, LLM (minimax M3) 在 pivot loop 里反复 think 不输出
|
|
551
|
+
* `<final gen>`, session 落盘拿不到最终回答.
|
|
552
|
+
* 现在: 只让用户放 .bolloon/skills/SKILL.md 才生效, 干净且 project-owned.
|
|
553
|
+
*
|
|
554
|
+
* 静默忽略不存在的目录.
|
|
555
|
+
*/
|
|
556
|
+
loadSkills(paths) {
|
|
557
|
+
const resolved = (paths && paths.length > 0) ? paths : (0, skill_loader_js_1.defaultSkillPaths)(os.homedir(), this.cwd);
|
|
558
|
+
(0, skill_loader_js_1.loadSkillsFromPaths)(resolved)
|
|
559
|
+
.then((skills) => {
|
|
560
|
+
for (const s of skills) {
|
|
561
|
+
if (this.skillRegistry.has(s.name)) {
|
|
562
|
+
this.skillRegistry.unregister(s.name);
|
|
563
|
+
}
|
|
564
|
+
this.skillRegistry.register(s);
|
|
565
|
+
}
|
|
566
|
+
console.log(`[loadSkills] 已加载 ${skills.length} 个 skill from ${resolved.join(', ')}`);
|
|
567
|
+
})
|
|
568
|
+
.catch((err) => {
|
|
569
|
+
console.error('[loadSkills] 加载失败:', err);
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
async initHarness() {
|
|
573
|
+
try {
|
|
574
|
+
const { createBollharnessIntegration } = await Promise.resolve().then(() => __importStar(require('../bollharness-integration/index.js')));
|
|
575
|
+
this.harness = createBollharnessIntegration();
|
|
576
|
+
this.harnessEnabled = true;
|
|
577
|
+
// ReactHarness 已用 bollharness, 这里也记一份以供 archive 调用
|
|
578
|
+
this.reactHarness = new react_harness_js_1.ReactHarness({ harnessEnabled: true, gateEnabled: true });
|
|
579
|
+
}
|
|
580
|
+
catch (e) {
|
|
581
|
+
console.warn('[PiAgentSession] Harness initialization failed:', e);
|
|
582
|
+
this.harnessEnabled = false;
|
|
583
|
+
// 失败 fallback: 走纯 8-gate (不带 bollharness 的 8-gate 工作流)
|
|
584
|
+
this.reactHarness = new react_harness_js_1.ReactHarness({ harnessEnabled: false, gateEnabled: true });
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
registerTools() {
|
|
588
|
+
// 2026-07-06: 工具注册抽到 ./pi-sdk-tools.ts, 这里只调 + 镜像到 ToolRegistry
|
|
589
|
+
this._inboxMessages = [];
|
|
590
|
+
const toolCtx = {
|
|
591
|
+
tools: this.tools,
|
|
592
|
+
cwd: this.cwd,
|
|
593
|
+
identity: this.identity,
|
|
594
|
+
persona: this.persona,
|
|
595
|
+
minimaxAvailable: this.minimaxAvailable,
|
|
596
|
+
setPersona: async (p) => { await this.setPersona(p); },
|
|
597
|
+
sessionManager: this.sessionManager,
|
|
598
|
+
constraintLayer: this.constraintLayer,
|
|
599
|
+
_inboxMessages: this._inboxMessages,
|
|
600
|
+
getChannelWallet: async () => {
|
|
601
|
+
try {
|
|
602
|
+
const { CHANNELS_PATH } = await Promise.resolve().then(() => __importStar(require('../web/server-types.js')));
|
|
603
|
+
const { loadChannels } = await Promise.resolve().then(() => __importStar(require('../web/server-storage.js')));
|
|
604
|
+
const channels = await loadChannels();
|
|
605
|
+
const ch = channels.find((c) => c.id === this.currentChannelId);
|
|
606
|
+
if (ch && ch.encryptedPrivateKey && ch.encryptedPrivateKeyIv && ch.walletAddress) {
|
|
607
|
+
return {
|
|
608
|
+
encryptedPrivateKey: ch.encryptedPrivateKey,
|
|
609
|
+
encryptedPrivateKeyIv: ch.encryptedPrivateKeyIv,
|
|
610
|
+
walletAddress: ch.walletAddress,
|
|
611
|
+
autoPayEnabled: ch.autoPayEnabled ?? false,
|
|
612
|
+
did: this.identity.did,
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
return null;
|
|
616
|
+
}
|
|
617
|
+
catch {
|
|
618
|
+
return null;
|
|
619
|
+
}
|
|
620
|
+
},
|
|
621
|
+
};
|
|
622
|
+
(0, pi_sdk_tools_js_1.registerBuiltinTools)(toolCtx);
|
|
623
|
+
// 2026-09-19: 联系方式工具 (手机/邮箱) —— Agent 拿不到明文, 只能给 contactId;
|
|
624
|
+
// 所有调用都要过 contacts/policy (首次联系/敏感内容需人工批准, 批量永远禁止)。
|
|
625
|
+
try {
|
|
626
|
+
(0, tools_js_1.registerContactTools)(toolCtx, new chain_js_1.ContactChain({ ownerDid: this.identity?.did || 'did:bolln:local' }));
|
|
627
|
+
}
|
|
628
|
+
catch (err) {
|
|
629
|
+
console.warn('[contacts] 注册联系工具失败 (非致命, 该能力不可用):', err?.message || err);
|
|
630
|
+
}
|
|
631
|
+
(0, pi_sdk_tools_js_1.registerWalletTools)(toolCtx);
|
|
632
|
+
(0, pi_sdk_tools_js_1.setupInboxListener)(toolCtx);
|
|
633
|
+
// 镜像到 ToolRegistry (alias resolve 用)
|
|
634
|
+
for (const [name, tool] of this.tools.entries()) {
|
|
635
|
+
this._toolRegistry.register(tool);
|
|
636
|
+
}
|
|
637
|
+
// M3.3: 副作用工具走幂等性 cache
|
|
638
|
+
this._idempotencyCache.wrap(this.tools);
|
|
639
|
+
}
|
|
640
|
+
/** 清幂等性缓存 — 强制下次调用真正执行 (用于 agent 显式需要重新跑的场景) */
|
|
641
|
+
clearIdempotencyCache() {
|
|
642
|
+
this._idempotencyCache.clear();
|
|
643
|
+
}
|
|
644
|
+
/** M3.3: 工具结果缓存 — 防止 loop 重试时副作用 (写文件 / 改代码) 执行多次 */
|
|
645
|
+
_idempotencyCache = new pi_sdk_tools_js_1.IdempotencyCache();
|
|
646
|
+
async registerP2PDocumentReceiver() {
|
|
647
|
+
await (0, p2p_document_tools_js_1.initDocumentReceiver)();
|
|
648
|
+
}
|
|
649
|
+
getToolDefinitions() {
|
|
650
|
+
// M2.4 (2026-06-17): 缓存 tool 定义 — registerTools() 在构造时调一次, 此后不变
|
|
651
|
+
// 2026-07-29: 拒绝列表变化时清空缓存, 重新生成
|
|
652
|
+
if (this.cachedToolDefinitions)
|
|
653
|
+
return this.cachedToolDefinitions;
|
|
654
|
+
const defs = ['可用工具 (name(params) - 简介):'];
|
|
655
|
+
// 2026-07-29: 使用 allowedTools() 过滤掉拒绝列表中的工具
|
|
656
|
+
for (const tool of this.allowedTools()) {
|
|
657
|
+
// 2026-06-19: 压缩 tool 定义 — 只显示参数名 (不显示描述, 减少 60% 长度)
|
|
658
|
+
// 完整 description 在 history 第一轮注入 (getToolDefinitionsFull 调用), 后续轮只看简短
|
|
659
|
+
// 避免 system prompt 太大导致 minimax 撞 max_tokens 输出空
|
|
660
|
+
const paramNames = Object.keys(tool.parameters).join(',');
|
|
661
|
+
defs.push(`- ${tool.name}(${paramNames})`);
|
|
662
|
+
}
|
|
663
|
+
this.cachedToolDefinitions = defs.join('\n');
|
|
664
|
+
return this.cachedToolDefinitions;
|
|
665
|
+
}
|
|
666
|
+
async initSession() {
|
|
667
|
+
await this.sessionManager.initialize();
|
|
668
|
+
await this.agentsManager.initialize();
|
|
669
|
+
this.persona = this.sessionManager.getPersona();
|
|
670
|
+
if (this.persona?.name) {
|
|
671
|
+
this.identity.name = this.persona.name;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
createDefaultIdentity() {
|
|
675
|
+
return {
|
|
676
|
+
did: `did:pi:${this.peerId.substring(0, 16)}`,
|
|
677
|
+
name: `Agent-${this.peerId.substring(0, 8)}`,
|
|
678
|
+
publicKey: this.peerId,
|
|
679
|
+
createdAt: Date.now()
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
checkMinimax() {
|
|
683
|
+
try {
|
|
684
|
+
(0, index_js_1.getMinimax)();
|
|
685
|
+
return true;
|
|
686
|
+
}
|
|
687
|
+
catch {
|
|
688
|
+
return false;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
/** 2026-08-07: prompt 出口统一上报 token 用量到 ContextManager (fallback/pivot/react 全路径覆盖) —
|
|
692
|
+
* 之前只有 runReActLoop 迭代内上报, chitchat/fallback/pivot 路径状态栏恒 0 */
|
|
693
|
+
reportUsageToContextManager() {
|
|
694
|
+
try {
|
|
695
|
+
(0, context_manager_js_1.getContextManager)().updateUsage(this.estimateHistoryTokens());
|
|
696
|
+
}
|
|
697
|
+
catch { /* 非致命 */ }
|
|
698
|
+
}
|
|
699
|
+
// ============================================================
|
|
700
|
+
// 2026-08-08: 运行轨迹采集 (trajectory) — 每轮运行 → 落盘 + OrbitDB, 失败静默
|
|
701
|
+
// ============================================================
|
|
702
|
+
async createTrajectoryRecorder(input, channelId) {
|
|
703
|
+
try {
|
|
704
|
+
const { TrajectoryRecorder } = await Promise.resolve().then(() => __importStar(require('../orbitdb/trajectory-store.js')));
|
|
705
|
+
const { resolveUserDid } = await Promise.resolve().then(() => __importStar(require('../storage/did-catalog-bridge.js')));
|
|
706
|
+
const did = await resolveUserDid();
|
|
707
|
+
const model = this.llmConfig?.model || this.model || '';
|
|
708
|
+
return new TrajectoryRecorder({
|
|
709
|
+
agentId: this.currentAgentId || 'default',
|
|
710
|
+
input,
|
|
711
|
+
channelId: channelId || this.currentChannelId,
|
|
712
|
+
did: did || undefined,
|
|
713
|
+
model: typeof model === 'string' && model ? model : undefined,
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
catch {
|
|
717
|
+
return null; // 轨迹采集失败静默 (增强层)
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
wrapTrajectoryStream(onStream, rec) {
|
|
721
|
+
return (ev) => {
|
|
722
|
+
try {
|
|
723
|
+
rec?.recordStep?.(ev);
|
|
724
|
+
}
|
|
725
|
+
catch { /* 记录失败不影响转发 */ }
|
|
726
|
+
onStream(ev);
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
finishTrajectory(rec, reply, status = 'ok') {
|
|
730
|
+
if (!rec)
|
|
731
|
+
return;
|
|
732
|
+
// fire-and-forget: 不阻塞回复返回
|
|
733
|
+
(async () => {
|
|
734
|
+
try {
|
|
735
|
+
const { recordTrajectory } = await Promise.resolve().then(() => __importStar(require('../orbitdb/trajectory-store.js')));
|
|
736
|
+
const run = rec.endRun(reply, status);
|
|
737
|
+
await recordTrajectory(run, {});
|
|
738
|
+
}
|
|
739
|
+
catch { /* 静默 */ }
|
|
740
|
+
})();
|
|
741
|
+
}
|
|
742
|
+
async prompt(input, options) {
|
|
743
|
+
// 2026-09-16 (M4 启动硬门禁): 初始化未就绪 → 不执行 Agent。
|
|
744
|
+
// fail-closed: 读不出初始化状态也按未就绪处理 (与 runnerResolver"解析不到只诊断"一致)。
|
|
745
|
+
// 测试环境 (VITEST) 跳过: 那 1800+ 用例跑在隔离 HOME 里, 本来就没有真实配置。
|
|
746
|
+
if (!process.env.VITEST) {
|
|
747
|
+
try {
|
|
748
|
+
const { getSetupGateCached } = await Promise.resolve().then(() => __importStar(require('../setup/setup-store.js')));
|
|
749
|
+
const { gate, state } = await getSetupGateCached();
|
|
750
|
+
if (gate !== 'ready') {
|
|
751
|
+
const why = `初始化未就绪 (${gate}, 当前阶段 ${state.stage})${state.lastError ? ` — ${state.lastError.message}` : ''}`;
|
|
752
|
+
const hint = (state.actions && state.actions[0]) || '运行 `bolloon setup` 完成初始化';
|
|
753
|
+
this.messageHistory.push({ role: 'user', content: input });
|
|
754
|
+
this.messageHistory.push({ role: 'assistant', content: `[初始化未就绪] ${why}\n${hint}` });
|
|
755
|
+
console.warn(`[PiAgent] 拒绝执行: ${why}`);
|
|
756
|
+
return `[初始化未就绪] ${why}\n${hint}`;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
catch { /* 门禁自身异常 → 按"不能判定"处理会阻塞一切; 这里只在能读到状态时才拦 */ }
|
|
760
|
+
}
|
|
761
|
+
this.minimaxAvailable = this.checkMinimax();
|
|
762
|
+
this.currentChannelId = options?.channelId ?? this.currentChannelId;
|
|
763
|
+
// 2026-08-08: 运行轨迹采集 (落盘 + OrbitDB, 失败静默) — 包裹 onStream 收集步骤事件
|
|
764
|
+
const trajRec = await this.createTrajectoryRecorder(input, options?.channelId);
|
|
765
|
+
if (trajRec && options?.onStream) {
|
|
766
|
+
options = { ...options, onStream: this.wrapTrajectoryStream(options.onStream, trajRec) };
|
|
767
|
+
}
|
|
768
|
+
this.messageHistory.push({
|
|
769
|
+
role: 'user',
|
|
770
|
+
content: input
|
|
771
|
+
});
|
|
772
|
+
if (!this.minimaxAvailable) {
|
|
773
|
+
// 2026-09-16 (2-C.2): fallback **也必须留下 Run 事实**。
|
|
774
|
+
// 旧行为: 直接返回兜底文案, 连 Run 都不建 —— 上层 (独立宿主/Supervisor) 只看到"执行完成但没有 Run",
|
|
775
|
+
// 于是把"什么都没跑"当成一次正常执行 (这正是"agent 跑了但没记录"的老毛病在 fallback 路径的翻版)。
|
|
776
|
+
const response = await this.handleFallback(input);
|
|
777
|
+
try {
|
|
778
|
+
let boundGoalId = this.currentGoalId;
|
|
779
|
+
if (!boundGoalId) {
|
|
780
|
+
const g = await (0, goal_store_js_1.createGoal)({
|
|
781
|
+
objective: this.currentUserInput || input.slice(0, 200),
|
|
782
|
+
channelId: this.currentChannelId || undefined,
|
|
783
|
+
agentId: this.currentAgentId || undefined,
|
|
784
|
+
createdBy: this.runSurface,
|
|
785
|
+
});
|
|
786
|
+
boundGoalId = g.goalId;
|
|
787
|
+
}
|
|
788
|
+
this.currentGoalId = boundGoalId;
|
|
789
|
+
const rec = await (0, run_store_js_1.startRun)({
|
|
790
|
+
surface: this.runSurface,
|
|
791
|
+
goal: this.currentUserInput || input.slice(0, 200),
|
|
792
|
+
goalId: boundGoalId,
|
|
793
|
+
channelId: this.currentChannelId || undefined,
|
|
794
|
+
agentId: this.currentAgentId || undefined,
|
|
795
|
+
});
|
|
796
|
+
this.lastRunId = rec.runId;
|
|
797
|
+
await (0, goal_store_js_1.attachRun)(boundGoalId, rec.runId).catch(() => null);
|
|
798
|
+
await (0, run_store_js_1.recordStep)(rec.runId, { tool: 'llm', ok: false, error: 'LLM 不可用 (provider 未初始化/无 apiKey) → 走了 fallback' });
|
|
799
|
+
await (0, run_store_js_1.finishRun)(rec.runId, {
|
|
800
|
+
status: 'needs_human',
|
|
801
|
+
error: 'LLM 不可用: provider 未初始化或无 apiKey (fallback 不是执行结果, 需要配置或人工处理)',
|
|
802
|
+
});
|
|
803
|
+
console.log(`[PiAgent] LLM 不可用 → 本次仍落 Run ${rec.runId} (needs_human), 不伪装成正常执行`);
|
|
804
|
+
}
|
|
805
|
+
catch (err) {
|
|
806
|
+
await (0, run_store_js_1.recordDegradation)({ kind: 'core', op: 'pi-sdk.fallbackRun', runId: this.lastRunId || '', message: String(err?.message || err).slice(0, 160) }).catch(() => { });
|
|
807
|
+
}
|
|
808
|
+
this.messageHistory.push({ role: 'assistant', content: response });
|
|
809
|
+
this.reportUsageToContextManager();
|
|
810
|
+
this.finishTrajectory(trajRec, response);
|
|
811
|
+
return response;
|
|
812
|
+
}
|
|
813
|
+
// P0 注入门
|
|
814
|
+
this.currentSignal = options?.signal ?? null;
|
|
815
|
+
this.currentOnStream = options?.onStream ?? null;
|
|
816
|
+
await this.computeJudgmentGate(input);
|
|
817
|
+
// M2.2 (2026-06-17): intent 分类 — prompt() 路径也跑 (跟 promptStream 对齐)
|
|
818
|
+
try {
|
|
819
|
+
const { classifyIntent, intentHint } = await Promise.resolve().then(() => __importStar(require('./intent-classifier.js')));
|
|
820
|
+
this.currentIntent = classifyIntent(input);
|
|
821
|
+
this.currentIntentHint = intentHint(this.currentIntent);
|
|
822
|
+
this.currentUserInput = input;
|
|
823
|
+
}
|
|
824
|
+
catch (err) {
|
|
825
|
+
console.warn('[PiAgent] classifyIntent in prompt() failed:', err);
|
|
826
|
+
this.currentIntent = 'chitchat';
|
|
827
|
+
this.currentIntentHint = '';
|
|
828
|
+
}
|
|
829
|
+
// P2: 解析当前 permission mode
|
|
830
|
+
try {
|
|
831
|
+
const { resolvePermissionMode } = await Promise.resolve().then(() => __importStar(require('./permission-mode.js')));
|
|
832
|
+
this.currentPermissionMode = resolvePermissionMode();
|
|
833
|
+
}
|
|
834
|
+
catch (err) {
|
|
835
|
+
console.warn('[PiAgent] resolvePermissionMode failed (non-fatal):', err);
|
|
836
|
+
this.currentPermissionMode = 'default';
|
|
837
|
+
}
|
|
838
|
+
// M3.1 (2026-06-17): 跟 promptStream 一样, usePivotLoop 时走 pivotLoop 路径
|
|
839
|
+
// 之前 prompt() 永远跑老 runReActLoop, CLI/web 行为不一致
|
|
840
|
+
if (this.usePivotLoop) {
|
|
841
|
+
try {
|
|
842
|
+
const lr = await this.promptWithPivotLoop(input, undefined, options?.channelId);
|
|
843
|
+
this.finishTrajectory(trajRec, lr.response || '');
|
|
844
|
+
return lr.response || '';
|
|
845
|
+
}
|
|
846
|
+
finally {
|
|
847
|
+
if (this.judgmentGateUsedIds.length > 0) {
|
|
848
|
+
(0, injection_gate_js_1.recordJudgmentUsage)(this.judgmentGateUsedIds, { userInput: input }).catch((err) => console.warn('[PiAgent] recordJudgmentUsage failed:', err));
|
|
849
|
+
}
|
|
850
|
+
this.clearJudgmentGate();
|
|
851
|
+
this.currentSignal = null;
|
|
852
|
+
this.currentOnStream = null;
|
|
853
|
+
this.reportUsageToContextManager();
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
try {
|
|
857
|
+
// 2026-06-16: runReActLoop 现在返回 { reply, aiFailed, aiFailureReason } — 这里只需 reply 字符串
|
|
858
|
+
const loopResult = await this.runReActLoop(this.currentOnStream ?? undefined, options?.signal);
|
|
859
|
+
this.finishTrajectory(trajRec, loopResult.reply, loopResult.aiFailed ? 'error' : 'ok');
|
|
860
|
+
return loopResult.reply;
|
|
861
|
+
}
|
|
862
|
+
finally {
|
|
863
|
+
if (this.judgmentGateUsedIds.length > 0) {
|
|
864
|
+
(0, injection_gate_js_1.recordJudgmentUsage)(this.judgmentGateUsedIds, { userInput: input, polarity: 'positive' }).catch((err) => console.warn('[PiAgent] recordJudgmentUsage failed:', err));
|
|
865
|
+
}
|
|
866
|
+
if (this.judgmentGateNegativeUsedIds.length > 0) {
|
|
867
|
+
(0, injection_gate_js_1.recordJudgmentUsage)(this.judgmentGateNegativeUsedIds, { userInput: input, polarity: 'negative' }).catch((err) => console.warn('[PiAgent] recordJudgmentUsage (negative) failed:', err));
|
|
868
|
+
}
|
|
869
|
+
this.clearJudgmentGate();
|
|
870
|
+
this.currentSignal = null;
|
|
871
|
+
this.currentOnStream = null;
|
|
872
|
+
this.reportUsageToContextManager();
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
async promptStream(input, onStream, signal, channelId) {
|
|
876
|
+
console.log(`[PiAgent.promptStream] ENTRY, channelId=${channelId}, input chars=${input.length}`);
|
|
877
|
+
this.minimaxAvailable = this.checkMinimax();
|
|
878
|
+
console.log(`[PiAgent.promptStream] minimaxAvailable=${this.minimaxAvailable}`);
|
|
879
|
+
this.currentChannelId = channelId ?? this.currentChannelId;
|
|
880
|
+
// 2026-08-08: 运行轨迹采集 (落盘 + OrbitDB, 失败静默) — 包裹 onStream 收集步骤事件
|
|
881
|
+
const trajRec = await this.createTrajectoryRecorder(input, channelId);
|
|
882
|
+
if (trajRec)
|
|
883
|
+
onStream = this.wrapTrajectoryStream(onStream, trajRec);
|
|
884
|
+
// 2026-06-18 (supervisor): web server 把 46K markedPrompt 喂过来
|
|
885
|
+
// (【本轮用户请求】\n<text>\n【请求结束】\n\n<contextHint>).
|
|
886
|
+
// 整个 input 走下游, pivot loop 之前拿 47K buildContext 当 user message 发出去,
|
|
887
|
+
// 模型撞 context window. 提取 userText 替代 input, contextHint 拼到 systemPrompt 末尾.
|
|
888
|
+
const markerMatch = input.match(/【本轮用户请求】\s*([\s\S]*?)\s*【请求结束】/);
|
|
889
|
+
const userText = markerMatch ? markerMatch[1].trim() : input;
|
|
890
|
+
const contextHint = markerMatch ? input.replace(markerMatch[0], '').trim() : '';
|
|
891
|
+
console.log(`[PiAgent.promptStream] marker matched=${!!markerMatch}, userText chars=${userText.length}, contextHint chars=${contextHint.length}`);
|
|
892
|
+
this.messageHistory.push({
|
|
893
|
+
role: 'user',
|
|
894
|
+
content: userText
|
|
895
|
+
});
|
|
896
|
+
// 2026-06-18: web server 喂的 markedPrompt 外的 contextHint 拼到 system 末尾 (而不是当 user message)
|
|
897
|
+
this.contextHintAddition = contextHint;
|
|
898
|
+
// 2026-08-12 (TaskM1, hermes prefetch 模式): 运行时按用户消息召回历史记忆, 注入 system prompt.
|
|
899
|
+
// 让 agent 能"回忆起"之前 session 的记忆 (自动获取之前 session), 而非只靠启动时批量压缩.
|
|
900
|
+
try {
|
|
901
|
+
const { recallMemory } = await Promise.resolve().then(() => __importStar(require('./memory-recall.js')));
|
|
902
|
+
const recalled = await recallMemory({ query: userText, agentId: this.currentAgentId || this.peerId || '' });
|
|
903
|
+
if (recalled) {
|
|
904
|
+
this.contextHintAddition = [this.contextHintAddition, recalled].filter(Boolean).join('\n\n');
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
catch { /* 记忆召回失败静默 (增强层) */ }
|
|
908
|
+
onStream({ type: 'thinking', content: '🤔 开始思考...' });
|
|
909
|
+
if (!this.minimaxAvailable) {
|
|
910
|
+
const response = await this.handleFallback(userText);
|
|
911
|
+
this.messageHistory.push({ role: 'assistant', content: response });
|
|
912
|
+
onStream({ type: 'done', content: '' });
|
|
913
|
+
this.reportUsageToContextManager();
|
|
914
|
+
this.finishTrajectory(trajRec, response);
|
|
915
|
+
return response;
|
|
916
|
+
}
|
|
917
|
+
// P0 注入门: 缓存 onStream + signal, computeJudgmentGate 用 currentOnStream 广播 phase
|
|
918
|
+
this.currentOnStream = onStream;
|
|
919
|
+
this.currentSignal = signal ?? null;
|
|
920
|
+
await this.computeJudgmentGate(userText);
|
|
921
|
+
// M2.2 (2026-06-17): intent 分类 — 0 LLM 成本, 5 行 keyword 匹配
|
|
922
|
+
try {
|
|
923
|
+
const { classifyIntent, intentHint } = await Promise.resolve().then(() => __importStar(require('./intent-classifier.js')));
|
|
924
|
+
this.currentIntent = classifyIntent(userText);
|
|
925
|
+
this.currentIntentHint = intentHint(this.currentIntent);
|
|
926
|
+
this.currentUserInput = userText;
|
|
927
|
+
if (this.currentIntent !== 'chitchat') {
|
|
928
|
+
onStream({ type: 'phase', phase: 'intent_classified', detail: this.currentIntent, content: '' });
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
catch (err) {
|
|
932
|
+
console.warn('[PiAgent] classifyIntent failed (non-fatal):', err);
|
|
933
|
+
this.currentIntent = 'chitchat';
|
|
934
|
+
this.currentIntentHint = '';
|
|
935
|
+
}
|
|
936
|
+
// P1.1: 异步跑 Auto-Compact (LLM 摘要, 仅在 budget 超限时触发, 失败静默)
|
|
937
|
+
// 复用 computeJudgmentGate 的 onStream 广播 phase, 跟 judgment 注入门风格一致
|
|
938
|
+
try {
|
|
939
|
+
await this.maybeAutoCompact(onStream, signal);
|
|
940
|
+
}
|
|
941
|
+
catch (err) {
|
|
942
|
+
console.warn('[PiAgent] maybeAutoCompact failed (non-fatal):', err);
|
|
943
|
+
}
|
|
944
|
+
// Bootstrap SessionStart: 收集项目 Context, 拼到 systemAddition 头部
|
|
945
|
+
// (失败静默, 5s 限流防止循环)
|
|
946
|
+
// 2026-07-04: 透传 agentId 让 onSessionStart 加载 persona 文档
|
|
947
|
+
let bootstrapAddition = '';
|
|
948
|
+
try {
|
|
949
|
+
const ss = await (0, human_value_pipeline_js_1.onSessionStart)({
|
|
950
|
+
channelId: this.currentChannelId || undefined,
|
|
951
|
+
agentId: this.currentAgentId || undefined,
|
|
952
|
+
});
|
|
953
|
+
bootstrapAddition = ss.systemAddition || '';
|
|
954
|
+
}
|
|
955
|
+
catch (err) {
|
|
956
|
+
console.warn('[PiAgent] onSessionStart failed (non-fatal):', err);
|
|
957
|
+
}
|
|
958
|
+
// 2026-07-07 P1-B: 注入最近 5 条项目事件日志 (L2) — 让 LLM 知道项目状态/feature 变化
|
|
959
|
+
// 失败静默, append 到 bootstrapAddition 末尾 (超 800 字截断)
|
|
960
|
+
if (this.currentChannelId) {
|
|
961
|
+
try {
|
|
962
|
+
const { getRecentEvents } = await Promise.resolve().then(() => __importStar(require('../bootstrap/event-log.js')));
|
|
963
|
+
const events = await getRecentEvents(this.currentChannelId, 5);
|
|
964
|
+
if (events.length > 0) {
|
|
965
|
+
const eventBlock = [
|
|
966
|
+
'## 最近项目事件 (最近 5 条, 倒序)',
|
|
967
|
+
...events.map(e => `- [${e.ts.slice(0, 16)}] [${e.type}] ${e.summary}`),
|
|
968
|
+
].join('\n');
|
|
969
|
+
bootstrapAddition = (bootstrapAddition + '\n\n' + eventBlock).slice(-2000);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
catch (err) {
|
|
973
|
+
console.warn('[PiAgent] getRecentEvents failed (non-fatal):', err);
|
|
974
|
+
}
|
|
975
|
+
// 2026-07-07 P2-C: 注入项目当前状态 (L3) — 目标/约束/待办/已完成
|
|
976
|
+
try {
|
|
977
|
+
const { readState, formatStateForPrompt } = await Promise.resolve().then(() => __importStar(require('../bootstrap/project-state.js')));
|
|
978
|
+
const state = await readState({ channelId: this.currentChannelId });
|
|
979
|
+
const stateText = formatStateForPrompt(state);
|
|
980
|
+
if (stateText) {
|
|
981
|
+
bootstrapAddition = (bootstrapAddition + '\n\n' + stateText).slice(-2500);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
catch (err) {
|
|
985
|
+
console.warn('[PiAgent] readState failed (non-fatal):', err);
|
|
986
|
+
}
|
|
987
|
+
// 2026-07-07 P2-C: 向量检索 top-3 (L4) — 按当前 channelId + userText 找历史相关片段
|
|
988
|
+
try {
|
|
989
|
+
const { searchIndex } = await Promise.resolve().then(() => __importStar(require('../bootstrap/vector-index.js')));
|
|
990
|
+
const indexName = `channel-${this.currentChannelId}`;
|
|
991
|
+
const results = await searchIndex({
|
|
992
|
+
indexName,
|
|
993
|
+
query: userText,
|
|
994
|
+
topK: 3,
|
|
995
|
+
});
|
|
996
|
+
if (results.length > 0) {
|
|
997
|
+
const hitBlock = [
|
|
998
|
+
'## 相关历史片段 (top-3, TF-IDF cosine)',
|
|
999
|
+
...results.map((r, i) => `- [${i + 1}] score=${r.score.toFixed(3)}: ${r.text.slice(0, 200).replace(/\n/g, ' ')}`),
|
|
1000
|
+
].join('\n');
|
|
1001
|
+
bootstrapAddition = (bootstrapAddition + '\n\n' + hitBlock).slice(-3000);
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
catch (err) {
|
|
1005
|
+
// 索引不存在是常见情况 (新 channel), 不打 warn
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
this.bootstrapAddition = bootstrapAddition;
|
|
1009
|
+
// P2: 解析当前 permission mode (BootstrapOptions > env BOLLOON_PERM_MODE > default)
|
|
1010
|
+
try {
|
|
1011
|
+
const { resolvePermissionMode } = await Promise.resolve().then(() => __importStar(require('./permission-mode.js')));
|
|
1012
|
+
this.currentPermissionMode = resolvePermissionMode();
|
|
1013
|
+
}
|
|
1014
|
+
catch (err) {
|
|
1015
|
+
console.warn('[PiAgent] resolvePermissionMode failed (non-fatal, using default):', err);
|
|
1016
|
+
this.currentPermissionMode = 'default';
|
|
1017
|
+
}
|
|
1018
|
+
this.promptStartTime = Date.now();
|
|
1019
|
+
// M3.1 (2026-06-17): 走 WorkflowPivotLoop (usePivotLoop: true)
|
|
1020
|
+
// pivot loop 自带 quality scoring / 30 iter cap / complexity analysis — 比老 runReActLoop 鲁棒
|
|
1021
|
+
if (this.usePivotLoop) {
|
|
1022
|
+
let pivotResult = '';
|
|
1023
|
+
try {
|
|
1024
|
+
const lr = await this.promptWithPivotLoop(userText, undefined, channelId);
|
|
1025
|
+
pivotResult = lr.response || '';
|
|
1026
|
+
onStream({ type: 'done', content: '' });
|
|
1027
|
+
}
|
|
1028
|
+
catch (err) {
|
|
1029
|
+
if (signal?.aborted || err?.name === 'AbortError') {
|
|
1030
|
+
console.log(`[chat] pivot aborted channel=${channelId}`);
|
|
1031
|
+
}
|
|
1032
|
+
else {
|
|
1033
|
+
console.error(`[chat] pivot 失败 channel=${channelId}:`, err);
|
|
1034
|
+
pivotResult = `[错误: pivot loop 失败] ${String(err?.message || err).slice(0, 300)}`;
|
|
1035
|
+
try {
|
|
1036
|
+
onStream({ type: 'error', content: pivotResult, tool: 'system' });
|
|
1037
|
+
}
|
|
1038
|
+
catch { }
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
finally {
|
|
1042
|
+
if (this.judgmentGateUsedIds.length > 0) {
|
|
1043
|
+
try {
|
|
1044
|
+
onStream({ type: 'used_judgments', usedIds: this.judgmentGateUsedIds, content: '' });
|
|
1045
|
+
}
|
|
1046
|
+
catch { }
|
|
1047
|
+
}
|
|
1048
|
+
(0, monitor_gate_js_1.monitorAfterReply)(userText, pivotResult);
|
|
1049
|
+
const stopStartTime = this.promptStartTime || Date.now();
|
|
1050
|
+
(0, human_value_pipeline_js_1.onStop)({
|
|
1051
|
+
channelId: this.currentChannelId || 'unknown',
|
|
1052
|
+
durationMs: Date.now() - stopStartTime,
|
|
1053
|
+
usedJudgmentIds: [...this.judgmentGateUsedIds],
|
|
1054
|
+
}).catch((err) => console.warn('[PiAgent] onStop failed:', err));
|
|
1055
|
+
this.clearJudgmentGate();
|
|
1056
|
+
this.currentOnStream = null;
|
|
1057
|
+
this.currentSignal = null;
|
|
1058
|
+
this.bootstrapAddition = '';
|
|
1059
|
+
this.contextHintAddition = '';
|
|
1060
|
+
this.promptStartTime = 0;
|
|
1061
|
+
this.reportUsageToContextManager();
|
|
1062
|
+
}
|
|
1063
|
+
this.finishTrajectory(trajRec, pivotResult);
|
|
1064
|
+
return pivotResult;
|
|
1065
|
+
}
|
|
1066
|
+
// 2026-06-16: loop 自动重试 — runReActLoop 内部遇到 [AI 服务调用失败] sentinel 时,
|
|
1067
|
+
// 会设 aiFailed=true 并提前 break. 这里在外层重跑整个 loop (不是单次 LLM 调用),
|
|
1068
|
+
// 临时网络抖动 / 配额瞬时超限可自愈. 最多 3 次, 指数退避 1s/2s/4s.
|
|
1069
|
+
// 用户看到 status bar 显示 "自动重试中 X/N" — 不暴露按钮.
|
|
1070
|
+
const MAX_LOOP_RETRIES = 3;
|
|
1071
|
+
let attempt = 0;
|
|
1072
|
+
let result = '';
|
|
1073
|
+
let lastAiFailureReason = '';
|
|
1074
|
+
while (attempt <= MAX_LOOP_RETRIES) {
|
|
1075
|
+
try {
|
|
1076
|
+
const loopResult = await this.runReActLoop(onStream, signal);
|
|
1077
|
+
result = loopResult.reply;
|
|
1078
|
+
// 持久化失败不重试 (写不进去就是写不进去): 直接按本次结果收尾
|
|
1079
|
+
if (this.runPersistenceBlocked)
|
|
1080
|
+
break;
|
|
1081
|
+
if (!loopResult.aiFailed)
|
|
1082
|
+
break; // 正常完成, 退出 retry 循环
|
|
1083
|
+
lastAiFailureReason = loopResult.aiFailureReason || 'AI 调用失败';
|
|
1084
|
+
}
|
|
1085
|
+
catch (err) {
|
|
1086
|
+
// abort 失败: 视作"已中断", 抛错让上层用 partial 兜底
|
|
1087
|
+
this.currentOnStream = null;
|
|
1088
|
+
this.currentSignal = null;
|
|
1089
|
+
throw err;
|
|
1090
|
+
}
|
|
1091
|
+
attempt++;
|
|
1092
|
+
if (attempt > MAX_LOOP_RETRIES) {
|
|
1093
|
+
console.warn(`[PiAgent] loop 自动重试 ${MAX_LOOP_RETRIES} 次后仍失败, 终止`);
|
|
1094
|
+
if (onStream) {
|
|
1095
|
+
onStream({ type: 'status', content: `⛔ loop 自动重试 ${MAX_LOOP_RETRIES} 次后仍失败: ${lastAiFailureReason}`, tool: 'system' });
|
|
1096
|
+
}
|
|
1097
|
+
result = lastAiFailureReason || 'AI 服务调用失败, 自动重试后仍不可用';
|
|
1098
|
+
break;
|
|
1099
|
+
}
|
|
1100
|
+
const backoffMs = 1000 * Math.pow(2, attempt - 1); // 1s, 2s, 4s
|
|
1101
|
+
console.log(`[PiAgent] loop 自动重试 ${attempt}/${MAX_LOOP_RETRIES}, 等待 ${backoffMs}ms`);
|
|
1102
|
+
if (onStream) {
|
|
1103
|
+
onStream({ type: 'status', content: `↻ 自动重试 loop ${attempt}/${MAX_LOOP_RETRIES} (${(backoffMs / 1000).toFixed(0)}s 后)`, tool: 'system' });
|
|
1104
|
+
}
|
|
1105
|
+
// 中途 abort 也要响应
|
|
1106
|
+
await new Promise((resolve, reject) => {
|
|
1107
|
+
const t = setTimeout(() => {
|
|
1108
|
+
signal?.removeEventListener?.('abort', onAbort);
|
|
1109
|
+
resolve();
|
|
1110
|
+
}, backoffMs);
|
|
1111
|
+
const onAbort = () => {
|
|
1112
|
+
clearTimeout(t);
|
|
1113
|
+
reject(new Error('aborted during retry backoff'));
|
|
1114
|
+
};
|
|
1115
|
+
if (signal?.aborted) {
|
|
1116
|
+
clearTimeout(t);
|
|
1117
|
+
reject(new Error('aborted during retry backoff'));
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
signal?.addEventListener?.('abort', onAbort, { once: true });
|
|
1121
|
+
});
|
|
1122
|
+
// 重试时要把这条 user message 从 history 里移除 (避免下一次 runReActLoop 又重复加入),
|
|
1123
|
+
// 因为 messageHistory.push({role:'user'}) 在 promptStream 顶部已经做过, 重跑 runReActLoop 不会重复 push,
|
|
1124
|
+
// 但 assistant 失败那条也别留 (留了会污染下一轮 LLM context).
|
|
1125
|
+
// 简化: 重试前 pop 一次 assistant (如果最后一条是 assistant)
|
|
1126
|
+
if (this.messageHistory.length > 0 && this.messageHistory[this.messageHistory.length - 1].role === 'assistant') {
|
|
1127
|
+
this.messageHistory.pop();
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
onStream({ type: 'done', content: '' });
|
|
1131
|
+
// 回溯: 异步记录 usage (不等)
|
|
1132
|
+
if (this.judgmentGateUsedIds.length > 0) {
|
|
1133
|
+
(0, injection_gate_js_1.recordJudgmentUsage)(this.judgmentGateUsedIds, { userInput: input }).catch((err) => console.warn('[PiAgent] recordJudgmentUsage failed:', err));
|
|
1134
|
+
// P0.5: 把 usedIds 通过 stream 事件回传给调用方 (server.ts 写到 session message)
|
|
1135
|
+
try {
|
|
1136
|
+
onStream({ type: 'used_judgments', usedIds: this.judgmentGateUsedIds, content: '' });
|
|
1137
|
+
}
|
|
1138
|
+
catch { }
|
|
1139
|
+
}
|
|
1140
|
+
// P3 监控门: fire-and-forget 审计 AI 回复是否违反原则
|
|
1141
|
+
(0, monitor_gate_js_1.monitorAfterReply)(input, result);
|
|
1142
|
+
// Bootstrap Stop hook: fire-and-forget 写本次 session 摘要
|
|
1143
|
+
const stopStartTime = this.promptStartTime || Date.now();
|
|
1144
|
+
(0, human_value_pipeline_js_1.onStop)({
|
|
1145
|
+
channelId: this.currentChannelId || 'unknown',
|
|
1146
|
+
durationMs: Date.now() - stopStartTime,
|
|
1147
|
+
usedJudgmentIds: [...this.judgmentGateUsedIds],
|
|
1148
|
+
}).catch((err) => console.warn('[PiAgent] onStop failed:', err));
|
|
1149
|
+
// 用完即清, 避免污染下一轮
|
|
1150
|
+
this.clearJudgmentGate();
|
|
1151
|
+
this.currentOnStream = null;
|
|
1152
|
+
this.reportUsageToContextManager();
|
|
1153
|
+
this.currentSignal = null;
|
|
1154
|
+
this.bootstrapAddition = '';
|
|
1155
|
+
this.promptStartTime = 0;
|
|
1156
|
+
// 2026-08-08: 轨迹收尾 — 落盘 + OrbitDB (失败静默, 不阻塞回复)
|
|
1157
|
+
this.finishTrajectory(trajRec, result);
|
|
1158
|
+
return result;
|
|
1159
|
+
}
|
|
1160
|
+
async promptWithPivotLoop(input, config, channelId) {
|
|
1161
|
+
this.currentChannelId = channelId ?? this.currentChannelId;
|
|
1162
|
+
if (!this.minimaxAvailable) {
|
|
1163
|
+
const response = await this.handleFallback(input);
|
|
1164
|
+
return {
|
|
1165
|
+
success: false,
|
|
1166
|
+
response,
|
|
1167
|
+
iterations: 0,
|
|
1168
|
+
toolCalls: 0,
|
|
1169
|
+
qualityScore: 0,
|
|
1170
|
+
exitReason: 'error',
|
|
1171
|
+
state: {
|
|
1172
|
+
iteration: 0,
|
|
1173
|
+
totalTokens: 0,
|
|
1174
|
+
toolCallsCount: 0,
|
|
1175
|
+
consecutiveNoProgress: 0,
|
|
1176
|
+
qualityScores: [],
|
|
1177
|
+
pendingToolUses: [],
|
|
1178
|
+
lastMeaningfulWork: 0
|
|
1179
|
+
}
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1182
|
+
const llm = (0, index_js_1.getMinimax)();
|
|
1183
|
+
const loopConfig = config || this.pivotLoopConfig || (0, workflow_pivot_loop_js_1.createDefaultPivotConfig)();
|
|
1184
|
+
const loop = new workflow_pivot_loop_js_1.WorkflowPivotLoop(loopConfig);
|
|
1185
|
+
for (const tool of this.tools.values()) {
|
|
1186
|
+
loop.registerTool(tool);
|
|
1187
|
+
}
|
|
1188
|
+
// P0 注入门: 在构造 systemPrompt 之前算一次, 拼到末尾
|
|
1189
|
+
await this.computeJudgmentGate(input);
|
|
1190
|
+
// M2.2 (2026-06-17): intent 分类 — pivot loop 也要拿到 hint
|
|
1191
|
+
try {
|
|
1192
|
+
const { classifyIntent, intentHint } = await Promise.resolve().then(() => __importStar(require('./intent-classifier.js')));
|
|
1193
|
+
this.currentIntent = classifyIntent(input);
|
|
1194
|
+
this.currentIntentHint = intentHint(this.currentIntent);
|
|
1195
|
+
this.currentUserInput = input;
|
|
1196
|
+
}
|
|
1197
|
+
catch (err) {
|
|
1198
|
+
console.warn('[PiAgent] classifyIntent in pivot failed:', err);
|
|
1199
|
+
}
|
|
1200
|
+
// M2.4: persona 缓存
|
|
1201
|
+
if (!this.cachedPersonaSection && this.persona) {
|
|
1202
|
+
this.cachedPersonaSection = `
|
|
1203
|
+
角色描述: ${this.persona.description || '无'}
|
|
1204
|
+
性格特点: ${this.persona.personality || '无'}
|
|
1205
|
+
问候语: ${this.persona.greeting || '无'}
|
|
1206
|
+
`;
|
|
1207
|
+
}
|
|
1208
|
+
const systemPrompt = `${this.bootstrapAddition}你是 ${this.identity.name},基于ReAct (Reasoning + Acting)模式工作。${this.cachedPersonaSection}
|
|
1209
|
+
当前工作目录: ${this.cwd}
|
|
1210
|
+
当前身份: ${this.identity.name} (${this.identity.did})
|
|
1211
|
+
${this.currentIntentHint}
|
|
1212
|
+
|
|
1213
|
+
${this.getToolDefinitions()}
|
|
1214
|
+
|
|
1215
|
+
${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
1216
|
+
|
|
1217
|
+
工作模式:
|
|
1218
|
+
1. 理解用户自然语言请求
|
|
1219
|
+
2. 分析需要哪些工具来完成
|
|
1220
|
+
3. 按顺序调用工具并观察结果
|
|
1221
|
+
4. 根据观察结果决定下一步
|
|
1222
|
+
5. 最终给出完整回答
|
|
1223
|
+
|
|
1224
|
+
重要 (一次命中要求):
|
|
1225
|
+
- 每次只调用一个工具
|
|
1226
|
+
- 仔细分析工具返回结果
|
|
1227
|
+
- 当任务完成时,必须在回答末尾添加 <final gen> 标记表示结束
|
|
1228
|
+
- 如果需要更多信息,继续调用工具
|
|
1229
|
+
|
|
1230
|
+
【工具调用格式 (严格遵守, 否则系统无法解析)】
|
|
1231
|
+
- 你只能输出**一个**工具调用, 不要堆叠多个 invoke
|
|
1232
|
+
- 工具调用格式: {"name":"<tool_name>","input":{"arg1":"value1"}}
|
|
1233
|
+
- 用 markdown json code block 包裹: \`\`\`json\n{"name":"X","input":{...}}\n\`\`\`
|
|
1234
|
+
- 工具调用前可以简短思考 (1-2 句话), 但**不要写长篇 thinking** (会撞 max_tokens)
|
|
1235
|
+
- 工具调用后必须等结果, 不要在同一个回复里继续输出
|
|
1236
|
+
- <final gen> 只在**真完成所有任务**时输出, 不要在工具调用前/中输出${this.judgmentGateAddition}${this.contextHintAddition}`;
|
|
1237
|
+
// 2026-06-15: 把 currentOnStream 传给 loop, 让 step-timeline 在 pivot 循环里也能 emit step_start/done
|
|
1238
|
+
// 之前 loop.execute() 不接 streamCallback, 导致 step-timeline 只能看到老 runReActLoop 路径
|
|
1239
|
+
// promptWithPivotLoop 路径 0 step events — UI 显示 timeline 但永远是空
|
|
1240
|
+
// 2026-06-17: 透传 signal 让 abort 工作 — loop.execute() 当前不接 signal 参数,
|
|
1241
|
+
// 所以 abort 行为通过 this.currentSignal 共享给 loop 内部读 (后续 M3.2 接 task plan 时一起加)
|
|
1242
|
+
// 2026-07-06: pivot 内 token 累计到 ~70% 时回调 (workflow-pivot-loop.ts line 270+).
|
|
1243
|
+
// pivot 的 messageHistory 是 process-local, 不和 pi-sdk 的 this.messageHistory 同步.
|
|
1244
|
+
// 真正折叠需要把 pi-sdk 历史灌回 pivot 的 history 数组 — 侵入较大.
|
|
1245
|
+
// 当前 priority: 临时传空实现, 让 budget 公式放够 (workflow-pivot-loop.ts line 220)
|
|
1246
|
+
// 不再撞预算. 这条路径留作技术债.
|
|
1247
|
+
// 2026-07-17 Bug 1 修: 注入 messageHistory (hydrateMessageHistory 从 session JSON 回灌的) 到 system prompt
|
|
1248
|
+
// pivot loop execute() 内部自己维护 messageHistory, 跟 pi-sdk 的 this.messageHistory 隔离,
|
|
1249
|
+
// 不注入的话 LLM 看不到历史对话, 每次都是新对话.
|
|
1250
|
+
const historyLines = [];
|
|
1251
|
+
const historyToInject = this.messageHistory.slice(-20, -1);
|
|
1252
|
+
for (const m of historyToInject) {
|
|
1253
|
+
const roleLabel = m.role === 'user' ? '用户' : m.role === 'assistant' ? '你' : m.role === 'tool' ? '工具结果' : m.role;
|
|
1254
|
+
const text = (m.content || '').slice(0, 2000);
|
|
1255
|
+
if (text)
|
|
1256
|
+
historyLines.push(`[${roleLabel}]: ${text}`);
|
|
1257
|
+
}
|
|
1258
|
+
const historyBlock = historyLines.length > 0
|
|
1259
|
+
? `\n\n【历史对话 (最近 ${historyLines.length} 条)】\n${historyLines.join('\n')}\n【历史对话结束】`
|
|
1260
|
+
: '';
|
|
1261
|
+
const onCompact = async () => {
|
|
1262
|
+
// no-op (best-effort hook for future pi-sdk/pivot history sync)
|
|
1263
|
+
};
|
|
1264
|
+
const result = await loop.execute(input, llm, systemPrompt + historyBlock, this.currentOnStream ?? undefined, this.currentSignal ?? undefined, onCompact);
|
|
1265
|
+
if (result.response) {
|
|
1266
|
+
this.messageHistory.push({ role: 'assistant', content: result.response });
|
|
1267
|
+
}
|
|
1268
|
+
// 回溯 + 清场
|
|
1269
|
+
if (this.judgmentGateUsedIds.length > 0) {
|
|
1270
|
+
(0, injection_gate_js_1.recordJudgmentUsage)(this.judgmentGateUsedIds, { userInput: input }).catch((err) => console.warn('[PiAgent] recordJudgmentUsage failed:', err));
|
|
1271
|
+
}
|
|
1272
|
+
this.clearJudgmentGate();
|
|
1273
|
+
return result;
|
|
1274
|
+
}
|
|
1275
|
+
/**
|
|
1276
|
+
* 2026-09-16 (Milestone 1-B): 唯一 Harness 门面 — 约束层的**唯一**入口。
|
|
1277
|
+
* pi-sdk 从此刻起不再分别调用 deny-pipeline / validator / react-harness / hooks / loop-review,
|
|
1278
|
+
* 只调 harness.*; 由 Harness 决定约束顺序与失败处置 (见 pi-harness.ts)。
|
|
1279
|
+
*/
|
|
1280
|
+
_harness = null;
|
|
1281
|
+
/** M2 绑定 GoalStore 后填真值; 在此之前为空 (事件里 goalId 字段已就位) */
|
|
1282
|
+
currentGoalId = '';
|
|
1283
|
+
/** 2026-09-16 (M2): 本次执行是"从 checkpoint 恢复"的 runId (非空 = 恢复模式, 不再新建 run) */
|
|
1284
|
+
resumeRunId = '';
|
|
1285
|
+
resumePlan = null;
|
|
1286
|
+
/** 2026-09-16 (2-C.4): 外部请求 id (工具层设; 回包必须带上才能精确匹配) */
|
|
1287
|
+
pendingExternalRequestId;
|
|
1288
|
+
/** 2026-09-16 (M3): 外部等待中 (awaiting_external) —— 收到成功步骤后回 running */
|
|
1289
|
+
awaitingExternal = false;
|
|
1290
|
+
/** 2026-09-16 (M3): 熔断原因 (同一工具连续失败达上限 → needs_human) */
|
|
1291
|
+
breakerReason = '';
|
|
1292
|
+
/** 2026-09-16 (M3): 运行中核心写失败 (状态迁移等) —— 交给循环顶部硬闸, 不吞 */
|
|
1293
|
+
persistenceFailure = '';
|
|
1294
|
+
/**
|
|
1295
|
+
* 2026-09-16 (M2-B): Supervisor 开新 Run 继续同一 Goal 时注入"上一个 Run 已成功的非幂等动作"。
|
|
1296
|
+
* 与 resume 的重放守卫同源 (run-store 的 buildContinuationPlan), 保证跨 Run 也不重复副作用。
|
|
1297
|
+
*/
|
|
1298
|
+
continuationGuards = [];
|
|
1299
|
+
/** Supervisor / CLI 注入跨 Run 重放守卫 */
|
|
1300
|
+
setContinuationGuards(guards) {
|
|
1301
|
+
this.continuationGuards = Array.isArray(guards) ? guards : [];
|
|
1302
|
+
}
|
|
1303
|
+
/** CLI / Web 注入"当前目标" (有 goalId 就在该 Goal 下执行) */
|
|
1304
|
+
setGoalId(goalId) {
|
|
1305
|
+
this.currentGoalId = String(goalId || '');
|
|
1306
|
+
}
|
|
1307
|
+
/** 核心状态迁移的兜底: 失败 → 记降级 + 交给循环顶部的持久化硬闸 (不吞错) */
|
|
1308
|
+
async safeSetRunStatus(runId, to) {
|
|
1309
|
+
try {
|
|
1310
|
+
const r = await (0, run_store_js_1.setRunStatus)(runId, to);
|
|
1311
|
+
return !!r.ok;
|
|
1312
|
+
}
|
|
1313
|
+
catch (err) {
|
|
1314
|
+
this.persistenceFailure = `状态迁移失败 (${to}): ${String(err?.message || err).slice(0, 180)}`;
|
|
1315
|
+
await (0, run_store_js_1.recordDegradation)({ kind: 'core', op: 'pi-sdk.setRunStatus', runId, message: this.persistenceFailure }).catch(() => { });
|
|
1316
|
+
return false;
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
/**
|
|
1320
|
+
* 2026-09-16 (M3): 工具失败接线 —— 分类 → recovery 留痕 → 熔断 / 外部等待。
|
|
1321
|
+
* 这是"错误恢复闭环"的运行时接线点 (此前 recordRecovery / repeatedFailureCount 只是数据结构)。
|
|
1322
|
+
*/
|
|
1323
|
+
async wireToolFailure(tool, errorText, args) {
|
|
1324
|
+
if (!this.currentRunId)
|
|
1325
|
+
return;
|
|
1326
|
+
const cls = (0, run_store_js_1.classifyError)(errorText);
|
|
1327
|
+
const digest = (0, run_store_js_1.argsDigestOf)(args);
|
|
1328
|
+
let repeats = 1;
|
|
1329
|
+
try {
|
|
1330
|
+
const rec = await (0, run_store_js_1.readRun)(this.currentRunId);
|
|
1331
|
+
// recordStep 已经把**这次**失败记进去了, 所以"连续失败次数"直接就是当前值 (不再 +1)
|
|
1332
|
+
const seen = rec ? (0, run_store_js_1.repeatedFailureCount)(rec, tool, digest) : 0;
|
|
1333
|
+
repeats = Math.max(seen, 1);
|
|
1334
|
+
}
|
|
1335
|
+
catch { /* 读不到就按首次记 */ }
|
|
1336
|
+
const action = cls === 'transient' ? 'backoff'
|
|
1337
|
+
: cls === 'auth' ? 'escalate'
|
|
1338
|
+
: cls === 'external_no_reply' ? 'pause'
|
|
1339
|
+
: 'retry';
|
|
1340
|
+
try {
|
|
1341
|
+
await (0, run_store_js_1.recordRecovery)(this.currentRunId, {
|
|
1342
|
+
errorClass: cls,
|
|
1343
|
+
message: `${tool}: ${errorText}`.slice(0, 200),
|
|
1344
|
+
action,
|
|
1345
|
+
attempt: repeats,
|
|
1346
|
+
recovered: false,
|
|
1347
|
+
});
|
|
1348
|
+
}
|
|
1349
|
+
catch (err) {
|
|
1350
|
+
await (0, run_store_js_1.recordDegradation)({ kind: 'core', op: 'pi-sdk.recordRecovery', runId: this.currentRunId, message: String(err?.message || err) }).catch(() => { });
|
|
1351
|
+
}
|
|
1352
|
+
// 外部无响应 → awaiting_external (不算失败; 等回话后回到 running)
|
|
1353
|
+
if (cls === 'external_no_reply') {
|
|
1354
|
+
this.awaitingExternal = true;
|
|
1355
|
+
await this.safeSetRunStatus(this.currentRunId, 'awaiting_external');
|
|
1356
|
+
// 2026-09-16 (2-C.4): 把"在等什么"写成持久化事实 (来源/关联/过期), 否则真实回包到了也不知道该唤醒谁。
|
|
1357
|
+
try {
|
|
1358
|
+
if (this.currentGoalId) {
|
|
1359
|
+
const { bindExternalWait, newContinuationId, defaultWaitExpiry } = await Promise.resolve().then(() => __importStar(require('./external-events.js')));
|
|
1360
|
+
const isDelegate = /delegate/i.test(String(tool || ''));
|
|
1361
|
+
await bindExternalWait(this.currentGoalId, {
|
|
1362
|
+
requestId: this.pendingExternalRequestId || `${this.currentRunId}:${Date.now().toString(36)}`,
|
|
1363
|
+
continuationId: newContinuationId(this.currentGoalId),
|
|
1364
|
+
expectedSource: isDelegate ? 'delegate' : 'p2p',
|
|
1365
|
+
expectedEvent: 'result',
|
|
1366
|
+
createdAt: new Date().toISOString(),
|
|
1367
|
+
expiresAt: defaultWaitExpiry(Date.now(), Number(process.env.BOLLOON_EXTERNAL_WAIT_MS || 30 * 60_000)),
|
|
1368
|
+
note: `${isDelegate ? 'delegate 回包' : 'P2P 协作回复'} 等待中 (tool=${tool})`,
|
|
1369
|
+
});
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
catch (e) {
|
|
1373
|
+
await (0, run_store_js_1.recordDegradation)({ kind: 'core', op: 'pi-sdk.bindExternalWait', runId: this.currentRunId, message: String(e?.message || e) }).catch(() => { });
|
|
1374
|
+
}
|
|
1375
|
+
return;
|
|
1376
|
+
}
|
|
1377
|
+
// 鉴权类: 不重试, 直接交人
|
|
1378
|
+
if (cls === 'auth') {
|
|
1379
|
+
this.breakerReason = `鉴权类错误不重试 (${tool}): ${errorText.slice(0, 120)}`;
|
|
1380
|
+
await this.safeSetRunStatus(this.currentRunId, 'needs_human');
|
|
1381
|
+
return;
|
|
1382
|
+
}
|
|
1383
|
+
// 重复失败熔断: 同一工具 + 同一组参数连续失败达 3 次
|
|
1384
|
+
if (repeats >= MAX_SAME_TOOL_FAILURES) {
|
|
1385
|
+
this.breakerReason = `同一工具 ${tool} 连续失败 ${repeats} 次 (${cls}) → 熔断, 不再重试`;
|
|
1386
|
+
await this.safeSetRunStatus(this.currentRunId, 'needs_human');
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
getRunId() {
|
|
1390
|
+
return this.currentRunId;
|
|
1391
|
+
}
|
|
1392
|
+
/**
|
|
1393
|
+
* 2026-09-16 (M2-B): **上一次**运行的 runId —— prompt 收尾时 `currentRunId` 会被清空,
|
|
1394
|
+
* 而 Supervisor / 控制面要在运行结束后才知道"刚才跑的是哪条 run" (否则会拿旧 run 做决策)。
|
|
1395
|
+
*/
|
|
1396
|
+
getLastRunId() {
|
|
1397
|
+
return this.lastRunId || this.currentRunId;
|
|
1398
|
+
}
|
|
1399
|
+
/**
|
|
1400
|
+
* 2026-09-16 (M2): 从 checkpoint 恢复一次运行 —— **不是**重发原 prompt。
|
|
1401
|
+
* 语义: prepareResume (校验状态 + 读 checkpoint + 记 recovery + 落 recovering) → 用恢复指令
|
|
1402
|
+
* 驱动同一个 runId 继续 (历史保留), 非幂等动作由重放守卫挡住。
|
|
1403
|
+
*/
|
|
1404
|
+
async resumeRun(runId) {
|
|
1405
|
+
const prep = await (0, run_store_js_1.prepareResume)(runId);
|
|
1406
|
+
if (!prep.ok || !prep.plan)
|
|
1407
|
+
return { ok: false, reason: prep.reason };
|
|
1408
|
+
this.resumeRunId = runId;
|
|
1409
|
+
this.resumePlan = prep.plan;
|
|
1410
|
+
this.currentGoalId = prep.plan.goalId || this.currentGoalId;
|
|
1411
|
+
try {
|
|
1412
|
+
const reply = await this.prompt((0, run_store_js_1.buildResumeInstruction)(prep.plan), {});
|
|
1413
|
+
return { ok: true, reply };
|
|
1414
|
+
}
|
|
1415
|
+
finally {
|
|
1416
|
+
this.resumeRunId = '';
|
|
1417
|
+
this.resumePlan = null;
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
piHarness() {
|
|
1421
|
+
if (!this._harness) {
|
|
1422
|
+
this._harness = new pi_harness_js_1.PiAgentHarness({
|
|
1423
|
+
reactHarness: this.reactHarness,
|
|
1424
|
+
denyPipeline: this._denyPipeline,
|
|
1425
|
+
hooks: this._hooks,
|
|
1426
|
+
// pre-tool-validator 4 步链 (modeGate/blacklist/shell-guard/schema), 经 human-value-pipeline 包装
|
|
1427
|
+
preToolUse: async (o) => (0, human_value_pipeline_js_1.onPreToolUse)({ tool: o.tool, args: o.args, permissionMode: o.permissionMode }),
|
|
1428
|
+
// 事件写 Run: 观测级 (记账失败不改变已做出的决策)
|
|
1429
|
+
events: (e) => { if (this.currentRunId)
|
|
1430
|
+
void (0, run_store_js_1.recordHarnessEvent)(this.currentRunId, e); },
|
|
1431
|
+
});
|
|
1432
|
+
}
|
|
1433
|
+
return this._harness;
|
|
1434
|
+
}
|
|
1435
|
+
/** 每个生命周期事件都带上的运行身份 (runId / goalId / agentId / channelId / surface) */
|
|
1436
|
+
harnessCtx() {
|
|
1437
|
+
return {
|
|
1438
|
+
runId: this.currentRunId || undefined,
|
|
1439
|
+
goalId: this.currentGoalId || undefined,
|
|
1440
|
+
agentId: this.currentAgentId || undefined,
|
|
1441
|
+
channelId: this.currentChannelId || undefined,
|
|
1442
|
+
surface: this.runSurface,
|
|
1443
|
+
};
|
|
1444
|
+
}
|
|
1445
|
+
async runReActLoop(onStream, signal) {
|
|
1446
|
+
const llm = (0, index_js_1.getMinimax)();
|
|
1447
|
+
let iteration = 0;
|
|
1448
|
+
let finalResponse = '';
|
|
1449
|
+
let lastQualityScore = 0;
|
|
1450
|
+
let refineAttempts = 0;
|
|
1451
|
+
let consecutiveErrors = 0;
|
|
1452
|
+
// 2026-06-16 新增: 累计错误数 (跨工具, 兜底防 LLM 轮换工具名死循环)
|
|
1453
|
+
let totalErrors = 0;
|
|
1454
|
+
let lastFailedTool = ''; // 跟踪最近一次失败的 tool name
|
|
1455
|
+
let lastFailedToolCount = 0; // 最近失败工具的连续失败次数
|
|
1456
|
+
// 2026-06-16: AI sentinel 标志 — runReActLoop 返回 aiFailed=true,
|
|
1457
|
+
// promptStream 据此自动重跑整个 loop 最多 N 次 (不是单次 LLM 重试)
|
|
1458
|
+
let aiFailed = false;
|
|
1459
|
+
let aiFailureReason = '';
|
|
1460
|
+
const MAX_CONSECUTIVE_ERRORS = 3;
|
|
1461
|
+
// 同一工具连续失败 3 次, 强制让 LLM 给出最终答案 (模块级常量 MAX_SAME_TOOL_FAILURES 也用它做熔断)
|
|
1462
|
+
// 2026-07-29: Hermes 风格硬限制 — 防死循环 (不再靠 soft hint)
|
|
1463
|
+
const MAX_IDEMPOTENT_TOOL = 5; // 同工具成功调 5 次 → 注入 hint 强制 final gen
|
|
1464
|
+
const MAX_TOOL_CALLS_PER_LOOP = 25; // 单轮循环总工具调用上限 → 注入 hint
|
|
1465
|
+
let totalToolCallsThisLoop = 0;
|
|
1466
|
+
const lastNTools = []; // 最近 MAX_IDEMPOTENT_TOOL 次工具名, 检测重复
|
|
1467
|
+
// 2026-08-10: unreported 循环逃生门 — LLM 反复不把工具结果写进回复时, 3 次后强制 final (不死板)
|
|
1468
|
+
const MAX_UNREPORTED_RETRIES = 3;
|
|
1469
|
+
let unreportedRetries = 0;
|
|
1470
|
+
// 2026-08-10: 工具失败时的终端逃生引导 (shell_exec 白名单命令可诊断环境/推进任务)
|
|
1471
|
+
const SHELL_ESCAPE_HINT = ' [逃生] 若工具无法响应/报错, 可用 shell_exec 跑终端命令诊断 (白名单: ls/cat/head/tail/pwd/git status/npm run test 等), 或调整参数换一种方式完成; 不要重复调用同一失败工具.';
|
|
1472
|
+
// 2026-08-08: final 前 review 续跑 — 目标对齐 + 需求深挖 (见 loop-review.ts)
|
|
1473
|
+
// 不潦草收尾: LLM 想 <final gen> 时先跑 1-2 次 review, 达成用户需求才放行.
|
|
1474
|
+
// 上限=2 次 (用户要求"运行一两次"), 结束后按用户需求为准.
|
|
1475
|
+
let loopReviewCount = 0;
|
|
1476
|
+
const loopReviewCompletedTools = new Set();
|
|
1477
|
+
// 2026-08-09: 本轮行动日志 — 每轮工具执行都记录 (args + 结果摘要),
|
|
1478
|
+
// final 前 review 用逐条核查目标; 也注入 system prompt 让 LLM 看到连续进度
|
|
1479
|
+
// (防"每轮都像重启" — 之前 LLM 看不到自己已完成什么, 容易重复 react)
|
|
1480
|
+
const loopActionLog = [];
|
|
1481
|
+
// 发送循环开始的事件
|
|
1482
|
+
if (onStream) {
|
|
1483
|
+
onStream({ type: 'status', content: '🔄 开始 ReAct 循环...', tool: 'system' });
|
|
1484
|
+
}
|
|
1485
|
+
// React Harness: 循环开始 (重置 turn 计数 + 触发 harness sessionStart)
|
|
1486
|
+
// 失败静默 (fail-open), 不阻塞主循环
|
|
1487
|
+
// 2026-09-16 (Milestone 1-B): 会话开启走唯一门面 (react-harness 8-gate 复位 + hooks onLoopStart)
|
|
1488
|
+
await this.piHarness().sessionStart(this.harnessCtx());
|
|
1489
|
+
// 2026-09-16: 持久化 run harness — 本次运行立即落盘 (~/.bolloon/runs/<id>.json)。
|
|
1490
|
+
// 之后每步工具调用都追加一条, 所以刷新页面/进程重载/崩溃都能看到"做到哪一步"。
|
|
1491
|
+
let runStopReason = '';
|
|
1492
|
+
// 2026-09-16 (Milestone 1): 持久化硬约束 —— 核心 run 状态写不进去时, 运行必须停。
|
|
1493
|
+
// 理由: "agent 实际跑了但没记录" 比 "agent 没跑" 更危险 (UI 显示旧状态/重启后无从知晓/结果可能被错标 done)。
|
|
1494
|
+
let runPersistenceFailure = '';
|
|
1495
|
+
/** 2026-09-16 (M3): 熔断/需要人处置 → 收尾落 needs_human (不是 done, 也不是普通 failed) */
|
|
1496
|
+
let runNeedsHuman = '';
|
|
1497
|
+
/** 2026-09-16 (M5): 外部 (CLI/Web) 把 run 改成 paused/aborted → 如实停, 不再覆盖它的状态 */
|
|
1498
|
+
let runExternallyPaused = false;
|
|
1499
|
+
let runExternallyAborted = false;
|
|
1500
|
+
this.runPersistenceBlocked = false;
|
|
1501
|
+
this.breakerReason = '';
|
|
1502
|
+
this.awaitingExternal = false;
|
|
1503
|
+
if (this.resumeRunId) {
|
|
1504
|
+
// ── 恢复模式: 复用原来的 runId, 不新建 run (历史保留) ──
|
|
1505
|
+
this.currentRunId = this.resumeRunId;
|
|
1506
|
+
this.lastRunId = this.resumeRunId;
|
|
1507
|
+
try {
|
|
1508
|
+
await (0, run_store_js_1.markRunRunning)(this.currentRunId);
|
|
1509
|
+
}
|
|
1510
|
+
catch (err) {
|
|
1511
|
+
runPersistenceFailure = `恢复时状态迁移失败 (recovering → running): ${String(err?.message || err).slice(0, 180)}`;
|
|
1512
|
+
}
|
|
1513
|
+
const doneN = this.resumePlan?.completedSteps.length ?? 0;
|
|
1514
|
+
const guards = this.resumePlan?.replayGuards.length ?? 0;
|
|
1515
|
+
onStream?.({ type: 'status', content: `♻️ 从 checkpoint 恢复运行 ${this.currentRunId} (已完成 ${doneN} 步, 非幂等重放守卫 ${guards} 条)`, tool: 'harness' });
|
|
1516
|
+
}
|
|
1517
|
+
else {
|
|
1518
|
+
// 2026-09-16 (M2): 目标绑定 —— 有 goalId 就在该 Goal 下执行; 没有就建 Goal 再建 Run。
|
|
1519
|
+
// 延续规则 (确定性, 不靠猜): 该 channel/agent 上已有 open/active Goal, 且它的上一次执行**没收尾**
|
|
1520
|
+
// (interrupted/stalled/needs_human/paused/awaiting_external/recovering) → 继续该 Goal; 否则新建。
|
|
1521
|
+
let boundGoalId = this.currentGoalId;
|
|
1522
|
+
if (!boundGoalId) {
|
|
1523
|
+
try {
|
|
1524
|
+
const active = await (0, goal_store_js_1.findActiveGoal)({ channelId: this.currentChannelId || undefined, agentId: this.currentAgentId || undefined });
|
|
1525
|
+
if (active?.currentRunId) {
|
|
1526
|
+
const prev = await (0, run_store_js_1.readRun)(active.currentRunId);
|
|
1527
|
+
const unfinished = prev && ['interrupted', 'stalled', 'needs_human', 'paused', 'awaiting_external', 'recovering'].includes(prev.status);
|
|
1528
|
+
if (unfinished)
|
|
1529
|
+
boundGoalId = active.goalId;
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
catch (err) {
|
|
1533
|
+
console.warn('[PiAgent] 查找进行中 Goal 失败 (按新建处理):', err?.message);
|
|
1534
|
+
}
|
|
1535
|
+
if (!boundGoalId) {
|
|
1536
|
+
try {
|
|
1537
|
+
const g = await (0, goal_store_js_1.createGoal)({
|
|
1538
|
+
objective: this.currentUserInput || '(未记录目标)',
|
|
1539
|
+
channelId: this.currentChannelId || undefined,
|
|
1540
|
+
agentId: this.currentAgentId || undefined,
|
|
1541
|
+
createdBy: this.runSurface,
|
|
1542
|
+
});
|
|
1543
|
+
boundGoalId = g.goalId;
|
|
1544
|
+
}
|
|
1545
|
+
catch (err) {
|
|
1546
|
+
console.warn('[PiAgent] 创建 Goal 失败 (无目标也要有运行记录):', err?.message);
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
this.currentGoalId = boundGoalId;
|
|
1550
|
+
}
|
|
1551
|
+
try {
|
|
1552
|
+
const rec = await (0, run_store_js_1.startRun)({
|
|
1553
|
+
surface: this.runSurface,
|
|
1554
|
+
goal: this.currentUserInput || '(未记录目标)',
|
|
1555
|
+
goalId: boundGoalId || undefined,
|
|
1556
|
+
channelId: this.currentChannelId || undefined,
|
|
1557
|
+
agentId: this.currentAgentId || undefined,
|
|
1558
|
+
});
|
|
1559
|
+
this.currentRunId = rec.runId;
|
|
1560
|
+
this.lastRunId = rec.runId;
|
|
1561
|
+
this.currentGoalId = rec.goalId || this.currentGoalId;
|
|
1562
|
+
if (this.currentGoalId) {
|
|
1563
|
+
// Run → Goal 反查链: runId → goalId → objective / success criteria
|
|
1564
|
+
await (0, goal_store_js_1.attachRun)(this.currentGoalId, rec.runId).catch((err) => console.warn('[PiAgent] attachRun 失败:', err?.message));
|
|
1565
|
+
}
|
|
1566
|
+
onStream?.({ type: 'status', content: `🧷 运行已登记 (run=${rec.runId}${this.currentGoalId ? `, goal=${this.currentGoalId}` : ''}, 预算 ${rec.budget.maxSteps} 步 / ${Math.round(rec.budget.deadlineMs / 60000)} 分钟)`, tool: 'harness' });
|
|
1567
|
+
}
|
|
1568
|
+
catch (err) {
|
|
1569
|
+
// 核心写失败: 不再 warn 后继续 —— 没有运行记录就不执行 (strict 模式默认如此)
|
|
1570
|
+
runPersistenceFailure = `无法创建运行记录: ${String(err?.message || err).slice(0, 200)}`;
|
|
1571
|
+
this.runPersistenceBlocked = true;
|
|
1572
|
+
console.error('[PiAgent] run-store startRun 失败 (核心持久化) → 拒绝无记录执行:', runPersistenceFailure);
|
|
1573
|
+
onStream?.({ type: 'error', content: `⛔ ${runPersistenceFailure} — 已停止 (不在没有记录的情况下执行)`, tool: 'harness' });
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
while (iteration < this.MAX_REACT_ITERATIONS) {
|
|
1577
|
+
iteration++;
|
|
1578
|
+
// 2026-09-16 (Milestone 1): 持久化失败硬闸 —— 到这一层说明记录已经不可信, 继续跑就是"无约束执行"
|
|
1579
|
+
if (runPersistenceFailure || this.persistenceFailure) {
|
|
1580
|
+
runPersistenceFailure = runPersistenceFailure || this.persistenceFailure;
|
|
1581
|
+
this.runPersistenceBlocked = true;
|
|
1582
|
+
aiFailed = true;
|
|
1583
|
+
aiFailureReason = aiFailureReason || runPersistenceFailure;
|
|
1584
|
+
finalResponse = finalResponse || `❌ 运行已停止: ${runPersistenceFailure}\n\n(运行记录无法写入/校验, 按协议停在 needs_human, 不假装完成)`;
|
|
1585
|
+
break;
|
|
1586
|
+
}
|
|
1587
|
+
// 2026-09-16 (M3): 熔断硬闸 —— 同一工具连续失败达上限后, 不许再"自动重试成功"式地把运行放活
|
|
1588
|
+
if (this.breakerReason) {
|
|
1589
|
+
runNeedsHuman = this.breakerReason;
|
|
1590
|
+
aiFailed = true;
|
|
1591
|
+
aiFailureReason = aiFailureReason || this.breakerReason;
|
|
1592
|
+
finalResponse = finalResponse || `❌ 已熔断: ${this.breakerReason}\n\n(重复失败不再重试, 按协议停在 needs_human 交人处置)`;
|
|
1593
|
+
break;
|
|
1594
|
+
}
|
|
1595
|
+
// 2026-09-16: 预算闸门 (持久化 harness 的约束面) —— 到点必须**如实**终止, 不许静默算完成
|
|
1596
|
+
if (this.currentRunId) {
|
|
1597
|
+
try {
|
|
1598
|
+
const rec = await (0, run_store_js_1.readRun)(this.currentRunId);
|
|
1599
|
+
if (!rec)
|
|
1600
|
+
throw new Error(`运行记录读不到: ${this.currentRunId}`);
|
|
1601
|
+
// 2026-09-16 (M5): 外部控制面 (CLI /pause /abort, Web API) 改过状态 → 如实停在那儿。
|
|
1602
|
+
// 不覆盖成 done/failed: 人按下暂停就是暂停, 人按下中止就是中止。
|
|
1603
|
+
if (rec.status === 'paused' || rec.status === 'aborted') {
|
|
1604
|
+
runExternallyPaused = rec.status === 'paused';
|
|
1605
|
+
runExternallyAborted = rec.status === 'aborted';
|
|
1606
|
+
runStopReason = `外部请求: ${rec.status}`;
|
|
1607
|
+
onStream?.({ type: 'error', content: `⏹️ 运行被外部${rec.status === 'paused' ? '暂停' : '中止'} (run=${this.currentRunId})`, tool: 'harness' });
|
|
1608
|
+
finalResponse = finalResponse || `(运行已${rec.status === 'paused' ? '暂停' : '中止'})`;
|
|
1609
|
+
break;
|
|
1610
|
+
}
|
|
1611
|
+
const verdict = (0, run_store_js_1.budgetVerdict)(rec);
|
|
1612
|
+
if (verdict.exceeded) {
|
|
1613
|
+
runStopReason = verdict.reason || '运行预算用尽';
|
|
1614
|
+
onStream?.({ type: 'error', content: `⛔ 运行预算用尽: ${runStopReason} (已如实终止, 不假装完成)`, tool: 'harness' });
|
|
1615
|
+
finalResponse = finalResponse || `(运行预算用尽: ${runStopReason})`;
|
|
1616
|
+
break;
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
catch (err) {
|
|
1620
|
+
// 预算闸门读不到状态 = 约束失效, 不能"当作没超预算"继续跑
|
|
1621
|
+
runPersistenceFailure = `预算闸门无法校验运行状态: ${String(err?.message || err).slice(0, 200)}`;
|
|
1622
|
+
this.runPersistenceBlocked = true;
|
|
1623
|
+
console.error('[PiAgent] run-store 预算检查失败 (核心持久化):', runPersistenceFailure);
|
|
1624
|
+
onStream?.({ type: 'error', content: `⛔ ${runPersistenceFailure} — 已停止`, tool: 'harness' });
|
|
1625
|
+
break;
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
// 停止条件 1: max turns (fail-safe 10000, 正常任务永远跑不到)
|
|
1629
|
+
// 2026-07-01 (v0.2.4 子任务 1): 委托给 react-loop.decideMaxIterations 纯函数
|
|
1630
|
+
const maxIterDecision = (0, react_loop_js_1.decideMaxIterations)(iteration, this.MAX_REACT_ITERATIONS);
|
|
1631
|
+
if (maxIterDecision.shouldExit) {
|
|
1632
|
+
console.warn(`[PiAgent] 达到最大循环数 ${this.MAX_REACT_ITERATIONS}, 强制终止 (fail-safe)`);
|
|
1633
|
+
onStream?.({ type: 'error', content: `⏹️ 达到最大循环数 (${this.MAX_REACT_ITERATIONS}, fail-safe)`, tool: 'loop' });
|
|
1634
|
+
finalResponse = finalResponse || maxIterDecision.finalAnswer;
|
|
1635
|
+
break;
|
|
1636
|
+
}
|
|
1637
|
+
// 停止条件 2: signal.aborted (显式 abort / 用户中断)
|
|
1638
|
+
if (signal?.aborted) {
|
|
1639
|
+
console.warn('[PiAgent] runReActLoop aborted by signal');
|
|
1640
|
+
onStream?.({ type: 'error', content: '⏹️ 用户中断', tool: 'loop' });
|
|
1641
|
+
finalResponse = finalResponse || '(用户中断)';
|
|
1642
|
+
break;
|
|
1643
|
+
}
|
|
1644
|
+
// 2026-07-29: Hermes 风格硬限制 (idempotent tool / total call cap)
|
|
1645
|
+
if (totalToolCallsThisLoop >= MAX_TOOL_CALLS_PER_LOOP) {
|
|
1646
|
+
console.warn(`[PiAgent] 单轮工具调用已达 ${MAX_TOOL_CALLS_PER_LOOP}, 注入 hint 让 LLM 总结`);
|
|
1647
|
+
onStream?.({ type: 'error', content: `⏹️ 工具调用已达上限 (${MAX_TOOL_CALLS_PER_LOOP}), 请基于已有结果回答`, tool: 'loop' });
|
|
1648
|
+
this.messageHistory.push({ role: 'system', content: `[注意] 你已连续调用 ${MAX_TOOL_CALLS_PER_LOOP} 次工具。请基于已有结果直接回答用户, 不要再次调用任何工具。在回答末尾加 <final gen> 标记结束。` });
|
|
1649
|
+
totalToolCallsThisLoop = 0; // 重置计数器, 只防连续死循环
|
|
1650
|
+
}
|
|
1651
|
+
if (lastNTools.length >= MAX_IDEMPOTENT_TOOL && new Set(lastNTools).size === 1) {
|
|
1652
|
+
const repeatedTool = lastNTools[0];
|
|
1653
|
+
console.warn(`[PiAgent] 同工具 ${repeatedTool} 连续成功调 ${MAX_IDEMPOTENT_TOOL} 次, 注入 hint 让 LLM 总结`);
|
|
1654
|
+
onStream?.({ type: 'error', content: `⏹️ 工具 ${repeatedTool} 重复调用 ${MAX_IDEMPOTENT_TOOL} 次, 请基于已有结果回答`, tool: 'loop' });
|
|
1655
|
+
this.messageHistory.push({ role: 'system', content: `[注意] 你已连续 ${MAX_IDEMPOTENT_TOOL} 次调用 ${repeatedTool}。请基于已有结果直接回答用户, 不要再次调用任何工具。在回答末尾加 <final gen> 标记结束。` });
|
|
1656
|
+
lastNTools.length = 0; // 重置计数器
|
|
1657
|
+
// 不 break — 让 LLM 在下一轮用已有信息回答
|
|
1658
|
+
}
|
|
1659
|
+
// 2026-06-16 新增: 累计错误兜底 — 跨工具, 防 LLM 轮换工具名绕过 MAX_SAME_TOOL_FAILURES
|
|
1660
|
+
if (totalErrors >= this.MAX_TOTAL_ERRORS) {
|
|
1661
|
+
console.warn(`[PiAgent] 累计错误 ${totalErrors} >= ${this.MAX_TOTAL_ERRORS}, 强制终止 (防死循环)`);
|
|
1662
|
+
onStream?.({ type: 'error', content: `⛔ 累计 ${totalErrors} 次错误, 强制终止 (防止 LLM 死循环)`, tool: 'loop' });
|
|
1663
|
+
// 2026-06-19: 即使 LLM 一直失败, 也汇总之前成功执行的 tool result 给用户
|
|
1664
|
+
if (this.successfulToolResults.length > 0) {
|
|
1665
|
+
finalResponse = `✅ 之前步骤成功执行了 ${this.successfulToolResults.length} 个工具 (但 LLM 后续 ${totalErrors} 次调用失败):\n` +
|
|
1666
|
+
this.successfulToolResults.map((r, i) => ` ${i + 1}. ${r.tool}: ${r.outputPreview}`).join('\n') +
|
|
1667
|
+
`\n\n⚠️ (LLM 连续失败, 可能是上游限流/网络问题, 工具已成功执行但 LLM 没能继续总结)`;
|
|
1668
|
+
}
|
|
1669
|
+
else {
|
|
1670
|
+
finalResponse = finalResponse || `(本轮 ReAct 循环累计 ${totalErrors} 次错误, 强制结束。请换个思路或简化任务重试。)`;
|
|
1671
|
+
}
|
|
1672
|
+
break;
|
|
1673
|
+
}
|
|
1674
|
+
// 2026-06-16 新增: loop 内自动压缩 — token 超 80% 阈值时跑一次
|
|
1675
|
+
// compact 失败走 C 路径: 不强行 break, 让现有 60K 阈值兜底 (后面有检查)
|
|
1676
|
+
// 2026-07-01 (v0.2.4 子任务 1): 触发判定走 shouldCompactBeforeIteration 纯函数
|
|
1677
|
+
const compactThreshold = this.maxContextTokens() * this.LOOP_COMPACT_RATIO;
|
|
1678
|
+
const estimatedTokensBefore = this.estimateHistoryTokens();
|
|
1679
|
+
// 2026-08-06: 每轮上报 usage 到 ContextManager (CLI/Web 状态栏数据源, warning 事件触发点)
|
|
1680
|
+
(0, context_manager_js_1.getContextManager)().updateUsage(estimatedTokensBefore);
|
|
1681
|
+
if ((0, react_loop_js_1.shouldCompactBeforeIteration)(estimatedTokensBefore, compactThreshold)) {
|
|
1682
|
+
const tokensBeforeCompact = estimatedTokensBefore;
|
|
1683
|
+
console.log(`[PiAgent] loop 入口 token ${tokensBeforeCompact} > ${compactThreshold}, 触发自动压缩`);
|
|
1684
|
+
onStream?.({ type: 'status', content: `🗜️ loop 自动压缩 (token ${tokensBeforeCompact} > ${compactThreshold})`, tool: 'compactor' });
|
|
1685
|
+
try {
|
|
1686
|
+
await this.maybeAutoCompact(onStream, signal);
|
|
1687
|
+
}
|
|
1688
|
+
catch (compactErr) {
|
|
1689
|
+
// C 路径: compact 失败不 break, 让 token 阈值检查兜底
|
|
1690
|
+
console.warn(`[PiAgent] loop 内 maybeAutoCompact 失败 (non-fatal, 继续走 token 阈值):`, compactErr);
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
// 停止条件 3: context overflow (compact 后还超, 强制终止)
|
|
1694
|
+
// 2026-07-01 (v0.2.4 子任务 1): 委托给 react-loop.decideContextOverflow 纯函数
|
|
1695
|
+
const estimatedTokens = this.estimateHistoryTokens();
|
|
1696
|
+
const overflowDecision = (0, react_loop_js_1.decideContextOverflow)(estimatedTokens, this.maxContextTokens());
|
|
1697
|
+
if (overflowDecision.shouldExit) {
|
|
1698
|
+
console.warn(`[PiAgent] context overflow (${estimatedTokens} tokens > ${this.maxContextTokens()})`);
|
|
1699
|
+
onStream?.({ type: 'error', content: `⏹️ 上下文溢出 (${estimatedTokens} tokens, 阈值 ${this.maxContextTokens()})`, tool: 'loop' });
|
|
1700
|
+
finalResponse = finalResponse || overflowDecision.finalAnswer;
|
|
1701
|
+
break;
|
|
1702
|
+
}
|
|
1703
|
+
// 调试日志:显示每次循环开始
|
|
1704
|
+
console.log(`[PiAgent] 循环 ${iteration}/${this.MAX_REACT_ITERATIONS} 开始`);
|
|
1705
|
+
if (onStream) {
|
|
1706
|
+
onStream({ type: 'status', content: `🔄 循环 ${iteration}/${this.MAX_REACT_ITERATIONS}`, tool: 'loop' });
|
|
1707
|
+
}
|
|
1708
|
+
const context = this.buildContext();
|
|
1709
|
+
// M3.5 (2026-06-17): 也构造 messages 数组版本, 让 LLM 看到结构化 tool 角色
|
|
1710
|
+
// buildContext() 把 history 序列化成字符串 — LLM 看不到 tool 调用的真实结果
|
|
1711
|
+
// 新版用 messages 数组直接喂给 LLM, 保留 role 语义 (user/assistant/tool/system)
|
|
1712
|
+
const messages = this.buildMessages();
|
|
1713
|
+
const toolDefs = this.getToolDefinitions();
|
|
1714
|
+
// 动态构建 refine 上下文
|
|
1715
|
+
let refineContext = '';
|
|
1716
|
+
if (refineAttempts > 0 && lastQualityScore < this.QUALITY_THRESHOLD) {
|
|
1717
|
+
refineContext = `\n【改进提示】上轮结果质量分 ${(lastQualityScore * 10).toFixed(1)}/10,请改进回答。`;
|
|
1718
|
+
}
|
|
1719
|
+
// 连续错误时的额外提示
|
|
1720
|
+
if (consecutiveErrors > 0) {
|
|
1721
|
+
refineContext += `\n【错误提示】上轮发生 ${consecutiveErrors} 次错误,请重新分析问题或换一种方式处理。`;
|
|
1722
|
+
}
|
|
1723
|
+
// M2.4: persona section 缓存 — persona 在 loadPersona() 时一次设定, 此后不变
|
|
1724
|
+
if (!this.cachedPersonaSection && this.persona) {
|
|
1725
|
+
this.cachedPersonaSection = `
|
|
1726
|
+
角色描述: ${this.persona.description || '无'}
|
|
1727
|
+
性格特点: ${this.persona.personality || '无'}
|
|
1728
|
+
问候语: ${this.persona.greeting || '无'}
|
|
1729
|
+
`;
|
|
1730
|
+
}
|
|
1731
|
+
const personaSection = this.cachedPersonaSection;
|
|
1732
|
+
// 2026-08-09: 循环进度段 — 让 LLM 看到本轮已完成的动作 (连续进度, 不重启)
|
|
1733
|
+
// Hermes 式 Agent Runtime: 循环是状态机, LLM 每次看到的是"第 N 步 + 已完成 X"
|
|
1734
|
+
// (之前每轮都是全新上下文, LLM 不知道做过什么 → 重复 react / 衔接差)
|
|
1735
|
+
let loopProgressSection = '';
|
|
1736
|
+
if (loopActionLog.length > 0) {
|
|
1737
|
+
const actionLines = loopActionLog
|
|
1738
|
+
.map((a, i) => {
|
|
1739
|
+
const args = a.argsPreview ? `(${a.argsPreview.slice(0, 60)})` : '';
|
|
1740
|
+
const res = a.success ? '✓' : '✗';
|
|
1741
|
+
return ` ${i + 1}. ${res} ${a.tool}${args}`;
|
|
1742
|
+
})
|
|
1743
|
+
.join('\n');
|
|
1744
|
+
loopProgressSection = `\n【本轮循环进度】你已完成以下 ${loopActionLog.length} 个动作, 这是连续执行的同一轮任务:\n${actionLines}\n请基于已有结果继续推进, 不要重复执行上面已成功的动作. 全部完成后用 <final gen> 结束.\n`;
|
|
1745
|
+
}
|
|
1746
|
+
const systemPrompt = `${this.bootstrapAddition}你是 ${this.identity.name},基于ReAct (Reasoning + Acting)模式工作。${personaSection}
|
|
1747
|
+
当前工作目录: ${this.cwd}
|
|
1748
|
+
当前身份: ${this.identity.name} (${this.identity.did})
|
|
1749
|
+
${refineContext}
|
|
1750
|
+
${this.currentIntentHint}
|
|
1751
|
+
${loopProgressSection}
|
|
1752
|
+
|
|
1753
|
+
${toolDefs}
|
|
1754
|
+
|
|
1755
|
+
${PiAgentSession.TOOL_SELECTION_GUIDE}
|
|
1756
|
+
|
|
1757
|
+
工作模式:
|
|
1758
|
+
1. 理解用户自然语言请求
|
|
1759
|
+
2. 分析需要哪些工具来完成
|
|
1760
|
+
3. 按顺序调用工具并观察结果
|
|
1761
|
+
4. 根据观察结果决定下一步
|
|
1762
|
+
5. 最终给出完整回答
|
|
1763
|
+
|
|
1764
|
+
重要:
|
|
1765
|
+
- 每次只调用一个工具
|
|
1766
|
+
- 仔细分析工具返回结果
|
|
1767
|
+
- 当任务完成时,必须在回答末尾添加 <final gen> 标记表示结束
|
|
1768
|
+
- 如果需要更多信息,继续调用工具${this.judgmentGateAddition}${this.contextHintAddition}`;
|
|
1769
|
+
// 3 个恢复机制 (Claude Code 论文 9-step pipeline 内部):
|
|
1770
|
+
// 1. max output token 升级 (最多 3 次, 每次 maxOutputTokens 翻倍)
|
|
1771
|
+
// 2. reactive compaction (prompt 估算超阈值, 跑压缩)
|
|
1772
|
+
// 3. prompt-too-long (LLM 报错 4xxx token 错误, 跑 reactive compaction 再试 1 次)
|
|
1773
|
+
// 失败静默: 全部重试失败 → 空 reply (上层用 no tool_use 终止)
|
|
1774
|
+
// Bug 5: pass tool IDs for native OpenAI tool calling — 2026-07-29: 过滤拒绝工具
|
|
1775
|
+
const toolIds = Array.from(this.tools.keys()).filter(n => !this._deniedToolNames.has(n));
|
|
1776
|
+
// 2026-07-29: 从 this.tools Map 生成 OpenAI 原生 tools 格式 (含参数 schema)
|
|
1777
|
+
const openaiFormattedTools = [];
|
|
1778
|
+
for (const [name, tool] of this.tools) {
|
|
1779
|
+
if (this._deniedToolNames.has(name))
|
|
1780
|
+
continue;
|
|
1781
|
+
const params = tool.parameters || {};
|
|
1782
|
+
const properties = {};
|
|
1783
|
+
const required = [];
|
|
1784
|
+
for (const [pName, pDesc] of Object.entries(params)) {
|
|
1785
|
+
properties[pName] = { type: 'string', description: String(pDesc) };
|
|
1786
|
+
if (String(pDesc).includes('必填'))
|
|
1787
|
+
required.push(pName);
|
|
1788
|
+
}
|
|
1789
|
+
openaiFormattedTools.push({
|
|
1790
|
+
type: 'function',
|
|
1791
|
+
function: {
|
|
1792
|
+
name,
|
|
1793
|
+
description: tool.description || name,
|
|
1794
|
+
parameters: { type: 'object', properties, required },
|
|
1795
|
+
},
|
|
1796
|
+
});
|
|
1797
|
+
}
|
|
1798
|
+
// 2026-09-16 (Milestone 1-B): 模型调用前后走唯一门面 (扩展点 + 计数留痕; 默认不加新 hook 事件, 避免改变现有触发次数)
|
|
1799
|
+
this.piHarness().beforeModelCall(this.harnessCtx());
|
|
1800
|
+
const _modelCallT0 = Date.now();
|
|
1801
|
+
const response = await this.callLlmWithRecovery(llm, messages, systemPrompt, signal, onStream, openaiFormattedTools);
|
|
1802
|
+
this.piHarness().afterModelCall(this.harnessCtx(), { ms: Date.now() - _modelCallT0 });
|
|
1803
|
+
const reply = (response.reply || '').trim();
|
|
1804
|
+
// 2026-06-30: OpenAI 协议 native tool_calls (LLM 真产了 tool_call 时, minimax/M3 会返回 id)
|
|
1805
|
+
const nativeToolCalls = response.toolCalls;
|
|
1806
|
+
// 2026-06-19 架构 fix: 不再因 [AI 服务调用失败] break
|
|
1807
|
+
// 旧逻辑: sentinel → aiFailed=true → break → 外层 retry 整个 loop (重置 history)
|
|
1808
|
+
// 新逻辑: 把错误当 tool_result push 进 history → 下一轮 LLM 看到错误能反思重试
|
|
1809
|
+
// 这是 dive-into 文档的"fail-open error recovery" — 错误进入 context, 不让 LLM 重复犯同样错
|
|
1810
|
+
// 2026-07-06: 对不可恢复的 API 错误直接终止, 不再无限重试
|
|
1811
|
+
if (reply.startsWith('[AI 服务调用失败]')) {
|
|
1812
|
+
console.log(`[PiAgent] 收到 AI 错误 sentinel`);
|
|
1813
|
+
console.log(`[sentinel DEBUG] 完整 reply: ${reply}`);
|
|
1814
|
+
console.log(`[sentinel DEBUG] 上一轮 messages 数量: ${Array.isArray(messages) ? messages.length : 'N/A'}, systemPrompt 长度: ${systemPrompt.length}`);
|
|
1815
|
+
aiFailureReason = reply.length > 200 ? reply.substring(0, 200) : reply;
|
|
1816
|
+
totalErrors++;
|
|
1817
|
+
consecutiveErrors++;
|
|
1818
|
+
// 2026-07-06: 检测不可恢复的 API 错误 — 这些错误 LLM 无法通过反思修复, 重试无意义
|
|
1819
|
+
const isFatalApiError = reply.includes('chat content is empty') ||
|
|
1820
|
+
reply.includes('invalid params') ||
|
|
1821
|
+
reply.includes('401') ||
|
|
1822
|
+
reply.includes('403') ||
|
|
1823
|
+
reply.includes('quota') ||
|
|
1824
|
+
reply.includes('rate limit') ||
|
|
1825
|
+
reply.includes('API key') ||
|
|
1826
|
+
reply.includes('authentication') ||
|
|
1827
|
+
reply.includes('unauthorized');
|
|
1828
|
+
if (isFatalApiError) {
|
|
1829
|
+
console.log(`[PiAgent] 检测到不可恢复的 API 错误, 终止 loop: ${aiFailureReason}`);
|
|
1830
|
+
if (onStream) {
|
|
1831
|
+
onStream({ type: 'error', content: `⛔ API 错误无法恢复: ${aiFailureReason}`, tool: 'system' });
|
|
1832
|
+
}
|
|
1833
|
+
finalResponse = `❌ AI 服务调用失败: ${aiFailureReason}\n\n这是一个底层 API 错误, 不是任务本身的问题。请检查 API 配置或稍后重试。`;
|
|
1834
|
+
aiFailed = true;
|
|
1835
|
+
break;
|
|
1836
|
+
}
|
|
1837
|
+
// 连续错误过多也终止, 防止 LLM 陷入死循环
|
|
1838
|
+
if (consecutiveErrors >= 3) {
|
|
1839
|
+
console.log(`[PiAgent] 连续 ${consecutiveErrors} 次 AI 错误, 终止 loop`);
|
|
1840
|
+
if (onStream) {
|
|
1841
|
+
onStream({ type: 'error', content: `⛔ 连续 ${consecutiveErrors} 次 AI 错误, 终止循环`, tool: 'system' });
|
|
1842
|
+
}
|
|
1843
|
+
finalResponse = `❌ AI 连续调用失败 ${consecutiveErrors} 次, 已终止。\n\n失败原因: ${aiFailureReason}\n\n请检查 API 配置或简化任务后重试。`;
|
|
1844
|
+
aiFailed = true;
|
|
1845
|
+
break;
|
|
1846
|
+
}
|
|
1847
|
+
// 把错误当成 tool 结果 push 进 history, 这样下一轮 LLM 看到错误能调整
|
|
1848
|
+
this.messageHistory.push({
|
|
1849
|
+
role: 'system',
|
|
1850
|
+
content: `[Loop 错误恢复 ${totalErrors}/${this.MAX_TOTAL_ERRORS}] ${aiFailureReason}\n\n请基于上轮工具结果继续完成任务, 不要重复调用同一失败操作. 如果工具已成功执行, 请基于 result.output 给用户总结; 如果工具失败, 请换其他方式或重试.`
|
|
1851
|
+
});
|
|
1852
|
+
if (onStream) {
|
|
1853
|
+
onStream({ type: 'status', content: `⚠️ AI 调用失败 ${totalErrors}/${this.MAX_TOTAL_ERRORS}, 已 push 错误到 history 让 LLM 反思`, tool: 'system' });
|
|
1854
|
+
}
|
|
1855
|
+
// 退避 2s 后继续 — 临时上游限流避开, 不让 loop 终止
|
|
1856
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
1857
|
+
// 关键: 不设 aiFailed=true, 让外层不重试整个 loop (重置 history), 继续内层循环
|
|
1858
|
+
continue;
|
|
1859
|
+
}
|
|
1860
|
+
console.log(`[PiAgent] LLM 回复长度: ${reply.length}, 内容预览: "${reply.substring(0, 80)}..."`);
|
|
1861
|
+
console.log(`[PiAgent] LLM 完整回复:\n${reply}`);
|
|
1862
|
+
// 通知前端:收到 LLM 回复 (2026-08-09: 不再截断 100 字符 — 前端流式渲染完整内容,
|
|
1863
|
+
// 配合 Hermes 式回复框: 加载中显示完整文本, 完成后封闭底框)
|
|
1864
|
+
if (onStream) {
|
|
1865
|
+
onStream({ type: 'token', content: reply });
|
|
1866
|
+
}
|
|
1867
|
+
// 2026-06-19 架构 fix: parseToolCall 优先于 isFinalResponse
|
|
1868
|
+
// 之前: 思考块里的 "<final gen>" 触发 isFinalResponse 提前 break, 工具从未真正执行
|
|
1869
|
+
// 现在: 先尝试解析 tool_call, 有就执行; 没有才检查是不是真正的 final gen
|
|
1870
|
+
// Bug 5 (2026-07-17): 优先用 LLM 的 native tool_calls (response.toolCalls), 再回退到文本解析
|
|
1871
|
+
// deepseek-v4-flash 用 OpenAI 协议 tools 时, 会真返回结构化 tool_calls 数组
|
|
1872
|
+
// 之前 nativeToolCalls 被读了不用, 只查 reply 文本, 导致 LLM 明明选了工具但代码找不到
|
|
1873
|
+
// 2026-07-28: 修复多工具调用 — 收集 ALL tool calls, 顺序执行后一次性返回
|
|
1874
|
+
let toolCalls = [];
|
|
1875
|
+
// 路径 A: native OpenAI 协议 tool_calls (可能多个)
|
|
1876
|
+
if (nativeToolCalls && nativeToolCalls.length > 0) {
|
|
1877
|
+
for (const nc of nativeToolCalls) {
|
|
1878
|
+
try {
|
|
1879
|
+
const args = typeof nc.function?.arguments === 'string'
|
|
1880
|
+
? JSON.parse(nc.function.arguments)
|
|
1881
|
+
: (nc.function?.arguments || {});
|
|
1882
|
+
toolCalls.push({
|
|
1883
|
+
name: nc.function?.name,
|
|
1884
|
+
args,
|
|
1885
|
+
id: nc.id || `call_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
1886
|
+
});
|
|
1887
|
+
}
|
|
1888
|
+
catch (err) {
|
|
1889
|
+
console.warn(`[PiAgent] 解析 native tool_call 失败: ${err.message?.slice(0, 100)}`);
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
// 路径 B: 文本解析 (parseAllToolCalls 收集全部)
|
|
1894
|
+
if (toolCalls.length === 0) {
|
|
1895
|
+
const knownTools = new Set(Array.from(this.tools.keys()));
|
|
1896
|
+
toolCalls = (0, parse_tool_call_js_1.parseAllToolCalls)(reply, { tools: knownTools });
|
|
1897
|
+
}
|
|
1898
|
+
// 回退路径 C: 原生 parseToolCall (单个)
|
|
1899
|
+
if (toolCalls.length === 0) {
|
|
1900
|
+
const single = this.parseToolCall(reply);
|
|
1901
|
+
if (single)
|
|
1902
|
+
toolCalls.push(single);
|
|
1903
|
+
}
|
|
1904
|
+
// 给每个 toolCall 分配稳定 id
|
|
1905
|
+
for (const tc of toolCalls) {
|
|
1906
|
+
if (!tc.id) {
|
|
1907
|
+
tc.id = `call_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
if (toolCalls.length > 0) {
|
|
1911
|
+
// 把原始 LLM 回复 push 进 history (仅一次)
|
|
1912
|
+
this.messageHistory.push({
|
|
1913
|
+
role: 'assistant',
|
|
1914
|
+
content: reply,
|
|
1915
|
+
toolCalls: toolCalls.length > 1 ? toolCalls : [toolCalls[0]],
|
|
1916
|
+
// 2026-09-15: 思考模式思维链原样存回 (下一轮带 tools 的请求必须回带, 否则 deepseek 400)
|
|
1917
|
+
reasoningContent: response?.reasoningContent,
|
|
1918
|
+
});
|
|
1919
|
+
// 2026-08-09: 并发执行本轮所有工具 (Hermes 式 Agent Runtime: 一轮内多工具并行,
|
|
1920
|
+
// 一轮没跑完之前不中断 — 工具执行不检查 abort, 全部完成才 continue 下一轮)
|
|
1921
|
+
// 旧实现顺序 for 循环, 一个工具等一个, 慢; 且多工具时 LLM 要等全部串完才能看到结果.
|
|
1922
|
+
await Promise.all(toolCalls.map(async (toolCall, ti) => {
|
|
1923
|
+
const isMulti = toolCalls.length > 1;
|
|
1924
|
+
// 通知前端
|
|
1925
|
+
if (onStream) {
|
|
1926
|
+
onStream({ type: 'tool', content: `🔧 调用工具 (${ti + 1}/${toolCalls.length}): ${toolCall.name}`, tool: toolCall.name });
|
|
1927
|
+
if (toolCall.args && Object.keys(toolCall.args).length > 0) {
|
|
1928
|
+
onStream({ type: 'status', content: `📋 参数: ${JSON.stringify(toolCall.args)}`, tool: toolCall.name });
|
|
1929
|
+
}
|
|
1930
|
+
onStream({
|
|
1931
|
+
type: 'step_start',
|
|
1932
|
+
content: `调用 ${toolCall.name}${isMulti ? ` (${ti + 1}/${toolCalls.length})` : ''}`,
|
|
1933
|
+
tool: toolCall.name,
|
|
1934
|
+
args: toolCall.args || {},
|
|
1935
|
+
});
|
|
1936
|
+
}
|
|
1937
|
+
const tool = this.tools.get(toolCall.name);
|
|
1938
|
+
if (!tool) {
|
|
1939
|
+
consecutiveErrors++;
|
|
1940
|
+
totalErrors++;
|
|
1941
|
+
const errorResult = { success: false, error: `未知工具: ${toolCall.name}` };
|
|
1942
|
+
this.messageHistory.push({ role: 'tool', content: JSON.stringify(errorResult), toolResult: errorResult });
|
|
1943
|
+
this.logToHarness(toolCall.name, toolCall.args, errorResult);
|
|
1944
|
+
// 2026-07-28: 注入 Reflection 帮助 LLM 理解错误
|
|
1945
|
+
const obs = (0, error_classifier_js_1.buildObservation)(toolCall.name, toolCall.args, errorResult);
|
|
1946
|
+
const ref = (0, error_classifier_js_1.buildReflection)(toolCall.name, errorResult.error, totalErrors, lastFailedToolCount);
|
|
1947
|
+
this.messageHistory.push({ role: 'system', content: (0, error_classifier_js_1.formatObservationWithReflection)(obs, ref) });
|
|
1948
|
+
if (onStream)
|
|
1949
|
+
onStream({ type: 'status', content: `💡 Reflection: ${obs.summary}`, tool: 'system' });
|
|
1950
|
+
console.warn(`[PiAgent] 未知工具: ${toolCall.name} (累计 ${totalErrors}/${this.MAX_TOTAL_ERRORS}),跳过并继续`);
|
|
1951
|
+
return;
|
|
1952
|
+
}
|
|
1953
|
+
// 2026-09-16 (Milestone 1-B): 工具调用前的**唯一**约束入口。
|
|
1954
|
+
// 顺序由 PiAgentHarness 决定: deny-pipeline → pre-tool-validator(4 步链) → react-harness(8-gate)。
|
|
1955
|
+
// 旧实现是这三处在不同位置各自调用 (且有两条路径 fail-open); 现在 pi-sdk 不再散调任何 gate。
|
|
1956
|
+
let toolDecision;
|
|
1957
|
+
try {
|
|
1958
|
+
toolDecision = await this.piHarness().beforeToolCall({
|
|
1959
|
+
tool: toolCall.name,
|
|
1960
|
+
args: toolCall.args || {},
|
|
1961
|
+
ctx: this.harnessCtx(),
|
|
1962
|
+
permissionMode: this.currentPermissionMode,
|
|
1963
|
+
});
|
|
1964
|
+
}
|
|
1965
|
+
catch (err) {
|
|
1966
|
+
// 门面自身抛错 = 核心约束失效 → fail-closed (不执行工具, 也不静默放行)
|
|
1967
|
+
toolDecision = {
|
|
1968
|
+
allow: false,
|
|
1969
|
+
source: 'harness-error',
|
|
1970
|
+
kind: 'core_constraint',
|
|
1971
|
+
reason: `Harness 门面异常: ${String(err?.message || err).slice(0, 150)}`,
|
|
1972
|
+
};
|
|
1973
|
+
}
|
|
1974
|
+
if (!toolDecision.allow) {
|
|
1975
|
+
const src = toolDecision.source || 'unknown';
|
|
1976
|
+
if (src === 'deny-pipeline') {
|
|
1977
|
+
// 旧 deny-pipeline 分支: 不计连续失败计数, 文案 "拒绝: [source] reason"
|
|
1978
|
+
consecutiveErrors++;
|
|
1979
|
+
totalErrors++;
|
|
1980
|
+
const denyResultMsg = { success: false, error: `拒绝: [${toolDecision.rejectedBy || 'deny-pipeline'}] ${toolDecision.reason}` };
|
|
1981
|
+
this.messageHistory.push({ role: 'tool', content: JSON.stringify(denyResultMsg), toolResult: denyResultMsg });
|
|
1982
|
+
this.logToHarness(toolCall.name, toolCall.args, denyResultMsg);
|
|
1983
|
+
return;
|
|
1984
|
+
}
|
|
1985
|
+
const isGateDeny = src === 'react-harness';
|
|
1986
|
+
const isHarnessError = src === 'harness-error';
|
|
1987
|
+
const deniedResult = {
|
|
1988
|
+
success: false,
|
|
1989
|
+
error: isGateDeny
|
|
1990
|
+
? `Harness gate 拒绝 (${toolDecision.rejectedBy}): ${toolDecision.reason || '未通过安全校验'}`
|
|
1991
|
+
: `PreToolUse 拒绝: ${toolDecision.reason || '未通过安全校验'}`,
|
|
1992
|
+
};
|
|
1993
|
+
this.messageHistory.push({ role: 'tool', content: JSON.stringify(deniedResult), toolResult: deniedResult });
|
|
1994
|
+
this.logToHarness(toolCall.name, toolCall.args, deniedResult);
|
|
1995
|
+
if (onStream) {
|
|
1996
|
+
const gateName = isGateDeny ? `Harness ${toolDecision.rejectedBy}` : (isHarnessError ? '核心约束层' : 'PreToolUse');
|
|
1997
|
+
onStream({ type: 'error', content: `🛡️ ${gateName} 拒绝 ${toolCall.name}: ${toolDecision.reason || '安全校验失败'}`, tool: toolCall.name });
|
|
1998
|
+
onStream({ type: 'step_error', content: `${gateName} 拒绝 ${toolCall.name}`, tool: toolCall.name, error: toolDecision.reason || '安全校验失败' });
|
|
1999
|
+
}
|
|
2000
|
+
console.warn(`[PiAgent] 工具被拒 ${toolCall.name} (${src}${toolDecision.rejectedBy ? ':' + toolDecision.rejectedBy : ''}): ${toolDecision.reason}`);
|
|
2001
|
+
consecutiveErrors++;
|
|
2002
|
+
totalErrors++;
|
|
2003
|
+
if (toolCall.name === lastFailedTool) {
|
|
2004
|
+
lastFailedToolCount++;
|
|
2005
|
+
}
|
|
2006
|
+
else {
|
|
2007
|
+
lastFailedTool = toolCall.name;
|
|
2008
|
+
lastFailedToolCount = 1;
|
|
2009
|
+
}
|
|
2010
|
+
// 达到同一工具连续失败上限 / 连续错误上限时的引导语 (按拒绝来源保持原有文案)
|
|
2011
|
+
const systemMaxMsg = isGateDeny
|
|
2012
|
+
? `[注意] 工具 ${toolCall.name} 被 Harness 拒绝 (连续 ${MAX_SAME_TOOL_FAILURES} 次). 请不要再次尝试, 末尾加 <final gen>.`
|
|
2013
|
+
: isHarnessError
|
|
2014
|
+
? `[注意] 工具 ${toolCall.name} 的约束校验层失效, 已按 fail-closed 阻止 (连续 ${MAX_SAME_TOOL_FAILURES} 次). 请换其他工具或直接回答用户, 末尾加 <final gen>.`
|
|
2015
|
+
: `[注意] 工具 ${toolCall.name} 被系统拒绝 (连续 ${MAX_SAME_TOOL_FAILURES} 次). 请不要再次尝试, 直接用已有信息回答用户, 末尾加 <final gen>.`;
|
|
2016
|
+
const systemConsecMsg = isGateDeny
|
|
2017
|
+
? `[注意] 连续 ${consecutiveErrors} 次工具调用被 Harness 拒绝. 请换其他工具或直接回答.`
|
|
2018
|
+
: isHarnessError
|
|
2019
|
+
? `[注意] 连续 ${consecutiveErrors} 次工具调用因约束层失效被阻止. 请换其他工具或直接回答用户, 末尾加 <final gen>.`
|
|
2020
|
+
: `[注意] 连续 ${consecutiveErrors} 次工具调用被系统拒绝. 请换其他工具或直接回答用户, 末尾加 <final gen>.`;
|
|
2021
|
+
if (lastFailedToolCount >= MAX_SAME_TOOL_FAILURES) {
|
|
2022
|
+
this.messageHistory.push({ role: 'system', content: systemMaxMsg });
|
|
2023
|
+
lastFailedTool = '';
|
|
2024
|
+
lastFailedToolCount = 0;
|
|
2025
|
+
consecutiveErrors = 0;
|
|
2026
|
+
}
|
|
2027
|
+
else if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
|
|
2028
|
+
this.messageHistory.push({ role: 'system', content: systemConsecMsg });
|
|
2029
|
+
consecutiveErrors = 0;
|
|
2030
|
+
}
|
|
2031
|
+
return;
|
|
2032
|
+
}
|
|
2033
|
+
if (toolDecision.systemAddition) {
|
|
2034
|
+
this.contextHintAddition += '\n' + toolDecision.systemAddition;
|
|
2035
|
+
}
|
|
2036
|
+
try {
|
|
2037
|
+
const toolStart = Date.now();
|
|
2038
|
+
// 2026-09-16 (M2): 恢复重放守卫 —— 中断前**已成功执行过的非幂等动作**恢复后不再重做,
|
|
2039
|
+
// 直接复用当时的结果 (避免重复副作用: 重复写文件/重复提交/重复付款)。
|
|
2040
|
+
// (M2-B: 守卫也覆盖"上一个 Run 已做过的非幂等动作" —— 跨 Run 续跑同样不许重做)
|
|
2041
|
+
let replaySkip = null;
|
|
2042
|
+
const guards = [
|
|
2043
|
+
...(this.resumePlan?.replayGuards || []),
|
|
2044
|
+
...(this.continuationGuards || []),
|
|
2045
|
+
];
|
|
2046
|
+
if (guards.length) {
|
|
2047
|
+
const d = (0, run_store_js_1.argsDigestOf)(toolCall.args);
|
|
2048
|
+
const hit = guards.find((g) => g.tool === toolCall.name && (!g.argsDigest || !d || g.argsDigest === d));
|
|
2049
|
+
if (hit)
|
|
2050
|
+
replaySkip = hit.summary;
|
|
2051
|
+
}
|
|
2052
|
+
let result = replaySkip
|
|
2053
|
+
? { success: true, output: `[恢复保护] ${toolCall.name} 在中断前已成功执行过, 本次不重复执行 (避免重复副作用)。当时结果: ${replaySkip}`, _replaySkipped: true }
|
|
2054
|
+
: await tool.execute(toolCall.args);
|
|
2055
|
+
const toolDurationMs = Date.now() - toolStart;
|
|
2056
|
+
if (replaySkip) {
|
|
2057
|
+
console.log(`[PiAgent] 恢复重放守卫: 跳过已完成的非幂等工具 ${toolCall.name}`);
|
|
2058
|
+
onStream?.({ type: 'status', content: `🛡️ 恢复保护: ${toolCall.name} 此前已成功执行, 本次不重复执行`, tool: toolCall.name });
|
|
2059
|
+
}
|
|
2060
|
+
console.log(`[PiAgent] 工具 ${toolCall.name} 执行完成: success=${result.success} (${toolDurationMs}ms)`);
|
|
2061
|
+
// 2026-09-16 (M3): 失败接线 —— 分类 + recovery 留痕 + 熔断 / 外部等待
|
|
2062
|
+
if (!result.success && this.currentRunId && !replaySkip) {
|
|
2063
|
+
await this.wireToolFailure(toolCall.name, String(result.error || ''), toolCall.args);
|
|
2064
|
+
}
|
|
2065
|
+
else if (result.success && this.currentRunId && this.awaitingExternal) {
|
|
2066
|
+
// 外部回话了: awaiting_external → running (不是"恢复完成", 只是等待结束)
|
|
2067
|
+
this.awaitingExternal = false;
|
|
2068
|
+
await this.safeSetRunStatus(this.currentRunId, 'running');
|
|
2069
|
+
}
|
|
2070
|
+
// 2026-09-16: 持久化 run harness — 每步工具调用立即落盘 (崩在这里也能看到做到哪步)
|
|
2071
|
+
if (this.currentRunId) {
|
|
2072
|
+
try {
|
|
2073
|
+
await (0, run_store_js_1.recordStep)(this.currentRunId, {
|
|
2074
|
+
tool: toolCall.name,
|
|
2075
|
+
ok: !!result.success,
|
|
2076
|
+
ms: toolDurationMs,
|
|
2077
|
+
args: toolCall.args,
|
|
2078
|
+
summary: String(result.output || '').slice(0, 200),
|
|
2079
|
+
error: result.error ? String(result.error) : undefined,
|
|
2080
|
+
});
|
|
2081
|
+
}
|
|
2082
|
+
catch (err) {
|
|
2083
|
+
// 核心写失败 → 本轮工具批跑完即停 (循环顶部硬闸), 不再 warn 后继续
|
|
2084
|
+
runPersistenceFailure = `工具步骤写盘失败 (${toolCall.name}): ${String(err?.message || err).slice(0, 200)}`;
|
|
2085
|
+
this.runPersistenceBlocked = true;
|
|
2086
|
+
console.error('[PiAgent] run-store recordStep 失败 (核心持久化):', runPersistenceFailure);
|
|
2087
|
+
onStream?.({ type: 'error', content: `⛔ ${runPersistenceFailure} — 本轮结束后停止`, tool: 'harness' });
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
try {
|
|
2091
|
+
await (0, lifecycle_hooks_js_1.onPostToolUse)({ tool: toolCall.name, args: toolCall.args || {}, result: { success: result.success, output: result.output?.substring(0, 500), error: result.error }, durationMs: toolDurationMs });
|
|
2092
|
+
}
|
|
2093
|
+
catch (postErr) {
|
|
2094
|
+
console.warn('[PiAgent] onPostToolUse failed (non-fatal):', postErr);
|
|
2095
|
+
}
|
|
2096
|
+
// 2026-09-16 (Milestone 1-B): 工具调用后的唯一入口 (router hint + 输出 gate 一起判定)
|
|
2097
|
+
const after = await this.piHarness().afterToolCall({
|
|
2098
|
+
tool: toolCall.name,
|
|
2099
|
+
output: String(result.output || ''),
|
|
2100
|
+
ctx: this.harnessCtx(),
|
|
2101
|
+
ok: !!result.success,
|
|
2102
|
+
});
|
|
2103
|
+
if (after.routeHint?.systemAddition) {
|
|
2104
|
+
this.messageHistory.push({ role: 'system', content: `[Harness Router Hint: ${after.routeHint.reason}]\n${after.routeHint.systemAddition}` });
|
|
2105
|
+
}
|
|
2106
|
+
if (after.outputBlocked) {
|
|
2107
|
+
if (onStream) {
|
|
2108
|
+
onStream({ type: 'error', content: `🛡️ Harness output 拒绝 ${toolCall.name}: ${after.outputBlocked.reason}`, tool: toolCall.name });
|
|
2109
|
+
}
|
|
2110
|
+
console.warn(`[PiAgent] Harness output denied ${toolCall.name}: ${after.outputBlocked.reason}`);
|
|
2111
|
+
result = { ...result, output: `[harness output gate: 输出含敏感内容, 已屏蔽. 原因: ${after.outputBlocked.reason}]`, _harnessDenied: true };
|
|
2112
|
+
}
|
|
2113
|
+
this.messageHistory.push({ role: 'tool', content: JSON.stringify(result), toolResult: result, toolCallId: toolCall.id || `call_${Date.now()}_${Math.random().toString(36).slice(2, 8)}` });
|
|
2114
|
+
this.logToHarness(toolCall.name, toolCall.args, result);
|
|
2115
|
+
// 2026-08-09: 记录到本轮行动日志 (循环进度 + final 前目标核查用)
|
|
2116
|
+
// 去重: 同一工具同 args 连续成功只记一次 (防 LLM 重复 react 刷屏)
|
|
2117
|
+
const argsPreview = JSON.stringify(toolCall.args || {}).slice(0, 120);
|
|
2118
|
+
const isDup = loopActionLog.some((a) => a.tool === toolCall.name && a.argsPreview === argsPreview && a.success === !!result.success);
|
|
2119
|
+
if (!isDup) {
|
|
2120
|
+
loopActionLog.push({
|
|
2121
|
+
tool: toolCall.name,
|
|
2122
|
+
argsPreview,
|
|
2123
|
+
resultPreview: result.success
|
|
2124
|
+
? String(result.output || '(无输出)').slice(0, 200)
|
|
2125
|
+
: String(result.error || 'failed').slice(0, 200),
|
|
2126
|
+
success: !!result.success,
|
|
2127
|
+
});
|
|
2128
|
+
}
|
|
2129
|
+
if (onStream) {
|
|
2130
|
+
if (result.success) {
|
|
2131
|
+
onStream({ type: 'status', content: `✅ ${toolCall.name} 执行成功`, tool: toolCall.name });
|
|
2132
|
+
if (result.output) {
|
|
2133
|
+
onStream({ type: 'tool', content: `📤 结果: ${result.output.substring(0, 200)}${result.output.length > 200 ? '...' : ''}`, tool: toolCall.name });
|
|
2134
|
+
}
|
|
2135
|
+
onStream({ type: 'step_done', content: `${toolCall.name} 执行成功`, tool: toolCall.name, success: true, output: result.output });
|
|
2136
|
+
}
|
|
2137
|
+
else {
|
|
2138
|
+
onStream({ type: 'error', content: `❌ ${toolCall.name} 执行失败: ${result.error}`, tool: toolCall.name });
|
|
2139
|
+
onStream({ type: 'step_error', content: `${toolCall.name} 执行失败`, tool: toolCall.name, error: result.error });
|
|
2140
|
+
}
|
|
2141
|
+
}
|
|
2142
|
+
if (result.success) {
|
|
2143
|
+
consecutiveErrors = 0;
|
|
2144
|
+
// 2026-07-29: Hermes 风格硬限制计数
|
|
2145
|
+
totalToolCallsThisLoop++;
|
|
2146
|
+
lastNTools.push(toolCall.name);
|
|
2147
|
+
if (lastNTools.length > MAX_IDEMPOTENT_TOOL)
|
|
2148
|
+
lastNTools.shift();
|
|
2149
|
+
if (result.output) {
|
|
2150
|
+
this.successfulToolResults.push({ tool: toolCall.name, outputPreview: result.output.substring(0, 200) + (result.output.length > 200 ? '...' : '') });
|
|
2151
|
+
}
|
|
2152
|
+
else {
|
|
2153
|
+
this.successfulToolResults.push({ tool: toolCall.name, outputPreview: '(无输出)' });
|
|
2154
|
+
}
|
|
2155
|
+
loopReviewCompletedTools.add(toolCall.name);
|
|
2156
|
+
lastQualityScore = this.estimateToolResultQuality(result);
|
|
2157
|
+
if (lastQualityScore < this.QUALITY_THRESHOLD && refineAttempts < this.MAX_REFINE_ATTEMPTS) {
|
|
2158
|
+
refineAttempts++;
|
|
2159
|
+
}
|
|
2160
|
+
if (onStream) {
|
|
2161
|
+
onStream({ type: 'status', content: `🔄 工具执行完成,继续循环...`, tool: 'loop' });
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
else {
|
|
2165
|
+
consecutiveErrors++;
|
|
2166
|
+
totalErrors++;
|
|
2167
|
+
if (toolCall.name === lastFailedTool) {
|
|
2168
|
+
lastFailedToolCount++;
|
|
2169
|
+
}
|
|
2170
|
+
else {
|
|
2171
|
+
lastFailedTool = toolCall.name;
|
|
2172
|
+
lastFailedToolCount = 1;
|
|
2173
|
+
}
|
|
2174
|
+
console.warn(`[PiAgent] 工具 ${toolCall.name} 执行失败 (${lastFailedToolCount}/${MAX_SAME_TOOL_FAILURES}, 累计 ${totalErrors}/${this.MAX_TOTAL_ERRORS}): ${result.error}`);
|
|
2175
|
+
// 2026-07-28: 注入 Observation + Reflection 替代旧 hardcode 提示
|
|
2176
|
+
const obs = (0, error_classifier_js_1.buildObservation)(toolCall.name, toolCall.args, { success: false, error: result.error });
|
|
2177
|
+
const ref = (0, error_classifier_js_1.buildReflection)(toolCall.name, result.error, totalErrors, lastFailedToolCount);
|
|
2178
|
+
this.messageHistory.push({ role: 'system', content: (0, error_classifier_js_1.formatObservationWithReflection)(obs, ref) + SHELL_ESCAPE_HINT });
|
|
2179
|
+
if (onStream)
|
|
2180
|
+
onStream({ type: 'status', content: `💡 Reflection: ${obs.summary} → ${ref[0]?.action || '放弃'}`, tool: 'system' });
|
|
2181
|
+
if (lastFailedToolCount >= MAX_SAME_TOOL_FAILURES) {
|
|
2182
|
+
this.messageHistory.push({ role: 'system', content: `[注意] 工具 ${toolCall.name} 在这个上下文中不可用 (连续 ${MAX_SAME_TOOL_FAILURES} 次失败: ${result.error}). 请不要再次调用它, 直接用你已知的信息回答用户, 并在回答开头标记 <final gen>.` });
|
|
2183
|
+
lastFailedTool = '';
|
|
2184
|
+
lastFailedToolCount = 0;
|
|
2185
|
+
consecutiveErrors = 0;
|
|
2186
|
+
return;
|
|
2187
|
+
}
|
|
2188
|
+
if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
|
|
2189
|
+
this.messageHistory.push({ role: 'system', content: `[注意] 前面的工具调用连续失败。请尝试其他工具或换一种方式完成用户请求, 或用 <final gen> 给出最终回答.` });
|
|
2190
|
+
consecutiveErrors = 0;
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
catch (execError) {
|
|
2195
|
+
consecutiveErrors++;
|
|
2196
|
+
totalErrors++;
|
|
2197
|
+
const errorResult = { success: false, error: String(execError) };
|
|
2198
|
+
this.messageHistory.push({ role: 'tool', content: JSON.stringify(errorResult), toolResult: errorResult });
|
|
2199
|
+
this.logToHarness(toolCall.name, toolCall.args, errorResult);
|
|
2200
|
+
const obs = (0, error_classifier_js_1.buildObservation)(toolCall.name, toolCall.args, errorResult);
|
|
2201
|
+
const ref = (0, error_classifier_js_1.buildReflection)(toolCall.name, errorResult.error, totalErrors, lastFailedToolCount);
|
|
2202
|
+
this.messageHistory.push({ role: 'system', content: (0, error_classifier_js_1.formatObservationWithReflection)(obs, ref) + SHELL_ESCAPE_HINT });
|
|
2203
|
+
if (onStream)
|
|
2204
|
+
onStream({ type: 'status', content: `💡 Reflection: ${obs.summary}`, tool: 'system' });
|
|
2205
|
+
console.error(`[PiAgent] 工具执行异常 (累计 ${totalErrors}/${this.MAX_TOTAL_ERRORS}): ${execError}`);
|
|
2206
|
+
}
|
|
2207
|
+
})); // end Promise.all(toolCalls.map(async ...))
|
|
2208
|
+
// 所有工具执行完毕后, continue while 循环, 让 LLM 看到结果
|
|
2209
|
+
continue;
|
|
2210
|
+
}
|
|
2211
|
+
else {
|
|
2212
|
+
// LLM 返回的不是 tool call 格式
|
|
2213
|
+
this.messageHistory.push({
|
|
2214
|
+
role: 'assistant',
|
|
2215
|
+
content: reply,
|
|
2216
|
+
reasoningContent: response?.reasoningContent,
|
|
2217
|
+
});
|
|
2218
|
+
// 通知前端收到非工具调用回复 (2026-08-09: 完整内容, 不再截断 150)
|
|
2219
|
+
if (onStream) {
|
|
2220
|
+
onStream({ type: 'token', content: reply });
|
|
2221
|
+
}
|
|
2222
|
+
// 2026-06-19 架构 fix: 只有 strip <think> 后才检查 isFinalResponse
|
|
2223
|
+
// (parseToolCall 已先尝试, 既然没解析出 tool_call, 现在检查 final gen 是否真的在最终回答区)
|
|
2224
|
+
if (this.isFinalResponse(reply)) {
|
|
2225
|
+
// 2026-06-19 dive-into 风格修复: 如果还有 successful tool results 没汇报,
|
|
2226
|
+
// LLM 不能提前 final_gen — harness 自动注入"请汇报剩余工具结果" hint 再 continue
|
|
2227
|
+
// 这是 dive-into 文档"step 9 stop condition check" 的具体化:
|
|
2228
|
+
// stop condition = (有工具结果未汇报) ? continue : break
|
|
2229
|
+
// 检查回复是否包含工具结果内容(避免无限循环)
|
|
2230
|
+
const hasToolResultContent = this.successfulToolResults.some(r => reply.includes(r.tool) || reply.includes(r.outputPreview.substring(0, 50)));
|
|
2231
|
+
// 如果回复包含工具结果内容,清除 successfulToolResults
|
|
2232
|
+
if (hasToolResultContent) {
|
|
2233
|
+
console.log(`[PiAgent] 回复包含工具结果内容, 清除 successfulToolResults (${this.successfulToolResults.length} 个)`);
|
|
2234
|
+
this.successfulToolResults = [];
|
|
2235
|
+
}
|
|
2236
|
+
// 2026-08-10: 逃生门 — decideUnreported: 未达上限 → 再提示一次; 超限 → 清空积压强制 final (防死循环)
|
|
2237
|
+
const unreportedDecision = decideUnreported(this.successfulToolResults.length, unreportedRetries, MAX_UNREPORTED_RETRIES);
|
|
2238
|
+
if (unreportedDecision === 'retry' && iteration < this.MAX_REACT_ITERATIONS) {
|
|
2239
|
+
unreportedRetries++;
|
|
2240
|
+
const unreported = this.successfulToolResults.length;
|
|
2241
|
+
console.log(`[PiAgent] LLM 想 final_gen 但还有 ${unreported} 个工具结果未汇报 (${unreportedRetries}/${MAX_UNREPORTED_RETRIES}), push hint 让其继续`);
|
|
2242
|
+
this.messageHistory.push({
|
|
2243
|
+
role: 'system',
|
|
2244
|
+
content: `[dive-into stop condition] 你之前已成功执行了 ${unreported} 个工具, 但当前回复里没把它们的结果告诉用户. 请基于已有的工具结果 (在 history 里) 写一个完整总结回复给用户, 用 <final gen> 结尾. 不要再调工具.`
|
|
2245
|
+
});
|
|
2246
|
+
if (onStream) {
|
|
2247
|
+
onStream({ type: 'status', content: `🔄 还有 ${unreported} 个工具结果未汇报, 让 LLM 继续总结 (${unreportedRetries}/${MAX_UNREPORTED_RETRIES})`, tool: 'system' });
|
|
2248
|
+
}
|
|
2249
|
+
continue;
|
|
2250
|
+
}
|
|
2251
|
+
else if (unreportedDecision === 'force-final') {
|
|
2252
|
+
// 反复提示仍未汇报超过上限 → 清空积压强制 final, 不再死循环
|
|
2253
|
+
console.log(`[PiAgent] unreported 循环超限 (${unreportedRetries} 次), 清空积压强制 final`);
|
|
2254
|
+
this.successfulToolResults = [];
|
|
2255
|
+
this.messageHistory.push({
|
|
2256
|
+
role: 'system',
|
|
2257
|
+
content: `[dive-into stop condition] 已多次提示汇报工具结果仍未完成 (超过 ${MAX_UNREPORTED_RETRIES} 次). 现在直接基于你已知的信息写最终回复给用户, 用 <final gen> 结尾, 不要再调任何工具.`
|
|
2258
|
+
});
|
|
2259
|
+
if (onStream) {
|
|
2260
|
+
onStream({ type: 'status', content: `🔄 工具结果汇报超限, 强制收尾`, tool: 'system' });
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2263
|
+
lastQualityScore = this.estimateResponseQuality(reply);
|
|
2264
|
+
// 2026-07-29: 质量门 — 即使 LLM 声称完成, 质量太低也继续
|
|
2265
|
+
if (lastQualityScore < this.QUALITY_THRESHOLD && refineAttempts < this.MAX_REFINE_ATTEMPTS) {
|
|
2266
|
+
console.log(`[PiAgent] final gen 质量 ${lastQualityScore.toFixed(2)} < ${this.QUALITY_THRESHOLD}, 注入 refine hint`);
|
|
2267
|
+
this.messageHistory.push({ role: 'system', content: `[质量检查] 你的回答质量评分为 ${(lastQualityScore * 10).toFixed(1)}/10, 低于 ${(this.QUALITY_THRESHOLD * 10).toFixed(1)}/10 阈值。请提供更完整、详细的回答, 包含工具调用获取到的具体信息, 末尾加 <final gen>。` });
|
|
2268
|
+
refineAttempts++;
|
|
2269
|
+
continue;
|
|
2270
|
+
}
|
|
2271
|
+
// 2026-08-08: final 前目标对齐 review — 不潦草收尾 (见 loop-review.ts)
|
|
2272
|
+
// LLM 想 <final gen> 时, 先跑 1-2 次「目标对齐 + 需求深挖」review;
|
|
2273
|
+
// 达成用户需求才放行真正结束. 达上限或无需深挖则以用户需求为准结束.
|
|
2274
|
+
// 2026-09-16 (Milestone 1-B): 目标审查走唯一门面 (loop-review 是 Harness 的一个环节)
|
|
2275
|
+
const reviewDecision = this.piHarness().reviewFinal({
|
|
2276
|
+
reviewsDone: loopReviewCount,
|
|
2277
|
+
// 2026-08-10: 传用户原始输入 (不是派生 intentHint) — LLM 对照原文自查完成度,
|
|
2278
|
+
// 未完成 → 自动继续调工具 (自动触发后续步骤)
|
|
2279
|
+
userIntent: this.currentUserInput,
|
|
2280
|
+
completedTools: Array.from(loopReviewCompletedTools),
|
|
2281
|
+
actionLog: loopActionLog,
|
|
2282
|
+
runId: this.currentRunId || undefined,
|
|
2283
|
+
goalId: this.currentGoalId || undefined,
|
|
2284
|
+
}, loop_review_js_1.DEFAULT_MAX_REVIEWS);
|
|
2285
|
+
if (reviewDecision.kind === 'continue-review') {
|
|
2286
|
+
loopReviewCount++;
|
|
2287
|
+
console.log(`[PiAgent] review ${loopReviewCount}/${loop_review_js_1.DEFAULT_MAX_REVIEWS}: LLM 想 final 但先对齐需求深挖一次`);
|
|
2288
|
+
this.messageHistory.push({ role: 'system', content: reviewDecision.hint });
|
|
2289
|
+
if (onStream) {
|
|
2290
|
+
onStream({ type: 'status', content: `🔄 目标对齐 review ${loopReviewCount}/${loop_review_js_1.DEFAULT_MAX_REVIEWS}: 深挖续跑`, tool: 'system' });
|
|
2291
|
+
}
|
|
2292
|
+
continue; // 让 LLM 看到 hint, 深挖或确认完成后再次 final
|
|
2293
|
+
}
|
|
2294
|
+
finalResponse = this.extractFinalAnswer(reply);
|
|
2295
|
+
break;
|
|
2296
|
+
}
|
|
2297
|
+
// 检查是否需要继续循环处理
|
|
2298
|
+
// 更严格的判断:只有当回复明确表示需要更多信息时才继续
|
|
2299
|
+
const containsToolCallIntent = reply.includes('调用工具') || reply.includes('tool(') ||
|
|
2300
|
+
reply.includes('使用工具') || reply.includes('需要获取') || reply.includes('需要查看') ||
|
|
2301
|
+
// 兼容 LLM 用对象字面量输出 tool call (上轮没解析成功时, 至少要继续)
|
|
2302
|
+
reply.includes('tool =>') || reply.includes('[TOOL_CALL]') ||
|
|
2303
|
+
// 2026-06-15 修: 兼容 LLM 用 XML 标签输出 tool call (<shell_exec>...</shell_exec>)
|
|
2304
|
+
// 这时 parseToolCall 失败, 至少要让 loop 继续
|
|
2305
|
+
/<\w+>[\s\S]*?<\/\w+>/.test(reply);
|
|
2306
|
+
const hasError = ['不存在', '找不到', '无法找到', 'not found', 'does not exist',
|
|
2307
|
+
'错误', 'error', '失败', 'failed'].some(k => reply.includes(k));
|
|
2308
|
+
const isTooShort = reply.length < 50 && reply.length > 0;
|
|
2309
|
+
const hasQuestion = reply.includes('?') && (reply.includes('怎么') || reply.includes('如何') || reply.includes('什么'));
|
|
2310
|
+
const needsMoreWork = hasError || containsToolCallIntent || isTooShort || hasQuestion;
|
|
2311
|
+
if (needsMoreWork && iteration < this.MAX_REACT_ITERATIONS) {
|
|
2312
|
+
console.log(`[PiAgent] 继续循环处理 (${iteration}/${this.MAX_REACT_ITERATIONS}): needsMoreWork=${needsMoreWork}, hasError=${hasError}, containsToolCallIntent=${containsToolCallIntent}`);
|
|
2313
|
+
if (onStream) {
|
|
2314
|
+
onStream({ type: 'status', content: `🔄 继续处理,循环 ${iteration}...`, tool: 'loop' });
|
|
2315
|
+
}
|
|
2316
|
+
continue;
|
|
2317
|
+
}
|
|
2318
|
+
// 否则把这个当作可能的最终回答
|
|
2319
|
+
finalResponse = reply;
|
|
2320
|
+
if (onStream) {
|
|
2321
|
+
onStream({ type: 'status', content: `📝 提取最终回答,长度 ${reply.length}`, tool: 'system' });
|
|
2322
|
+
}
|
|
2323
|
+
break;
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
2326
|
+
if (!finalResponse) {
|
|
2327
|
+
// 走到这里通常是 LLM 一直在调同一个不存在的工具, 没输出 <final gen>
|
|
2328
|
+
// 把已知的失败信息也带回去, 让用户知道发生了什么
|
|
2329
|
+
const reason = lastFailedTool
|
|
2330
|
+
? `(工具 ${lastFailedTool} 连续 ${MAX_SAME_TOOL_FAILURES} 次失败, 已放弃)`
|
|
2331
|
+
: `(共 ${iteration - 1} 轮无最终输出)`;
|
|
2332
|
+
finalResponse = `抱歉,任务未能完成 ${reason}。请换个方式提问,或明确告诉 agent 不要调用工具。`;
|
|
2333
|
+
if (onStream) {
|
|
2334
|
+
onStream({ type: 'error', content: `⚠️ 任务未完成: ${reason}`, tool: 'system' });
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
// 通知前端循环完成
|
|
2338
|
+
if (onStream) {
|
|
2339
|
+
onStream({ type: 'status', content: `✅ 处理完成,共 ${iteration - 1} 次循环`, tool: 'system' });
|
|
2340
|
+
}
|
|
2341
|
+
this.messageHistory.push({ role: 'assistant', content: finalResponse });
|
|
2342
|
+
// React Harness: 循环结束
|
|
2343
|
+
// 2026-09-16 (Milestone 1-B): 会话收尾走唯一门面
|
|
2344
|
+
await this.piHarness().sessionEnd(this.harnessCtx());
|
|
2345
|
+
// 2026-09-16: 收尾落盘 — done / failed / aborted / needs_human 如实写回 (不留幽灵 running)
|
|
2346
|
+
if (this.currentRunId && !runExternallyPaused && !runExternallyAborted) {
|
|
2347
|
+
try {
|
|
2348
|
+
// 2026-09-16 (M4): 完成门 —— "模型说完成"不等于"系统确认完成"。
|
|
2349
|
+
// 证据 = 成功步骤的事实摘要; 末尾还有失败步骤没被后续成功覆盖 → 不许 done。
|
|
2350
|
+
const recBefore = await (0, run_store_js_1.readRun)(this.currentRunId).catch(() => null);
|
|
2351
|
+
const steps = recBefore?.steps || [];
|
|
2352
|
+
const evidence = steps.filter((s) => s.ok).map((s) => `${s.tool}: ${(s.summary || '').slice(0, 120)}`).slice(-10);
|
|
2353
|
+
const lastStep = steps[steps.length - 1];
|
|
2354
|
+
const trailingFailure = !!lastStep && !lastStep.ok;
|
|
2355
|
+
/** 有工具步骤却一条成功证据都没有 → 不算完成 (证据门槛, 不是文案) */
|
|
2356
|
+
const noEvidence = steps.length > 0 && evidence.length === 0;
|
|
2357
|
+
const errText = runPersistenceFailure || this.breakerReason || runNeedsHuman || runStopReason || aiFailureReason || '';
|
|
2358
|
+
const cls = errText ? (0, run_store_js_1.classifyError)(errText) : 'unknown';
|
|
2359
|
+
// 持久化失败 / 熔断 / 鉴权 → needs_human; 预算/中止 → aborted; 末尾仍失败或无证据 → failed (不许 done)
|
|
2360
|
+
const status = runPersistenceFailure
|
|
2361
|
+
? 'needs_human'
|
|
2362
|
+
: (this.breakerReason || runNeedsHuman)
|
|
2363
|
+
? 'needs_human'
|
|
2364
|
+
: runStopReason
|
|
2365
|
+
? 'aborted'
|
|
2366
|
+
: aiFailed
|
|
2367
|
+
? (cls === 'auth' ? 'needs_human' : 'failed')
|
|
2368
|
+
: (trailingFailure || noEvidence)
|
|
2369
|
+
? 'failed'
|
|
2370
|
+
: 'done';
|
|
2371
|
+
await (0, run_store_js_1.finishRun)(this.currentRunId, {
|
|
2372
|
+
status,
|
|
2373
|
+
summary: finalResponse ? String(finalResponse).slice(0, 400) : undefined,
|
|
2374
|
+
error: errText
|
|
2375
|
+
|| (trailingFailure ? `末尾步骤失败 (${lastStep.tool}): ${(lastStep.error || '').slice(0, 150)}` : undefined)
|
|
2376
|
+
|| (noEvidence ? '有工具步骤但没有任何成功证据: 不许判 done' : undefined),
|
|
2377
|
+
evidence,
|
|
2378
|
+
});
|
|
2379
|
+
}
|
|
2380
|
+
catch (err) {
|
|
2381
|
+
// 终态也写不下去: 这是最坏情况 —— 除了留痕, 没有别的自愈手段, 所以必须显眼
|
|
2382
|
+
const message = `收尾落盘失败 (状态无法写回, 记录会停在 running): ${String(err?.message || err).slice(0, 200)}`;
|
|
2383
|
+
console.error('[PiAgent] run-store finishRun 失败 (核心持久化):', message);
|
|
2384
|
+
await (0, run_store_js_1.recordDegradation)({ kind: 'core', op: 'pi-sdk.finishRun', runId: this.currentRunId, message }).catch(() => { });
|
|
2385
|
+
onStream?.({ type: 'error', content: `⚠️ ${message}`, tool: 'harness' });
|
|
2386
|
+
}
|
|
2387
|
+
// 2026-09-16 (M4): Goal 侧完成门 —— Run 结束 ≠ Goal 完成; 只有判据全满足 + 有证据 + 无未解决项才算
|
|
2388
|
+
if (this.currentGoalId) {
|
|
2389
|
+
try {
|
|
2390
|
+
const full = await (0, run_store_js_1.readRun)(this.currentRunId).catch(() => null);
|
|
2391
|
+
const ev = (full?.evidence || []).slice(-10);
|
|
2392
|
+
if (ev.length)
|
|
2393
|
+
await (0, goal_store_js_1.addEvidence)(this.currentGoalId, ev);
|
|
2394
|
+
const goal = await (0, goal_store_js_1.readGoal)(this.currentGoalId);
|
|
2395
|
+
if (goal) {
|
|
2396
|
+
const verdict = (0, goal_store_js_1.evaluateGoalCompletion)(goal);
|
|
2397
|
+
if (verdict.complete) {
|
|
2398
|
+
const done = await (0, goal_store_js_1.completeGoalIfEligible)(this.currentGoalId);
|
|
2399
|
+
onStream?.({ type: 'status', content: `🎯 目标已达成: ${done.reason} (goal=${this.currentGoalId})`, tool: 'harness' });
|
|
2400
|
+
}
|
|
2401
|
+
else {
|
|
2402
|
+
// 不静默: 未达成的目标留在 active, 未解决项写清楚 (下次 prompt 会继续这个 Goal)
|
|
2403
|
+
const items = verdict.missing.length ? verdict.missing : [`run=${this.currentRunId} 结束但目标未达成: ${verdict.reason}`];
|
|
2404
|
+
await (0, goal_store_js_1.setUnresolved)(this.currentGoalId, items);
|
|
2405
|
+
onStream?.({ type: 'status', content: `🎯 目标仍在进行 (未判完成): ${verdict.reason}`, tool: 'harness' });
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2409
|
+
catch (err) {
|
|
2410
|
+
await (0, run_store_js_1.recordDegradation)({ kind: 'observational', op: 'pi-sdk.goalCompletion', runId: this.currentRunId, message: String(err?.message || err).slice(0, 160) }).catch(() => { });
|
|
2411
|
+
}
|
|
2412
|
+
}
|
|
2413
|
+
this.currentRunId = '';
|
|
2414
|
+
}
|
|
2415
|
+
else if (this.currentRunId) {
|
|
2416
|
+
// 外部暂停/中止: 状态是人定的, 不覆盖 (paused 等 /resume; aborted 是终态)
|
|
2417
|
+
onStream?.({ type: 'status', content: `⏹️ 运行状态保持为 ${runExternallyPaused ? 'paused' : 'aborted'} (由外部控制面决定)`, tool: 'harness' });
|
|
2418
|
+
this.currentRunId = '';
|
|
2419
|
+
}
|
|
2420
|
+
// 2026-06-16: 暴露 aiFailed 标志 — promptStream 据此决定是否自动重试整个 loop
|
|
2421
|
+
return { reply: finalResponse, aiFailed, aiFailureReason: aiFailureReason || undefined };
|
|
2422
|
+
}
|
|
2423
|
+
async deepThink(prompt) {
|
|
2424
|
+
const result = await this.thinkingEngine.think(prompt);
|
|
2425
|
+
let response = `深度思考完成(${result.depth}层):\n\n`;
|
|
2426
|
+
for (const step of result.steps) {
|
|
2427
|
+
response += `第${step.step}步: ${step.thought}\n`;
|
|
2428
|
+
if (step.reflection) {
|
|
2429
|
+
response += ` 反思: ${step.reflection}\n`;
|
|
2430
|
+
}
|
|
2431
|
+
if (step.improvement) {
|
|
2432
|
+
response += ` 改进: ${step.improvement}\n`;
|
|
2433
|
+
}
|
|
2434
|
+
response += '\n';
|
|
2435
|
+
}
|
|
2436
|
+
response += `最终输出: ${result.finalOutput}`;
|
|
2437
|
+
return { result, response };
|
|
2438
|
+
}
|
|
2439
|
+
async processDocumentsInParallel(paths, operation, requirements) {
|
|
2440
|
+
if (paths.length === 0) {
|
|
2441
|
+
return { outputs: [], success: true };
|
|
2442
|
+
}
|
|
2443
|
+
const subtasks = paths.map((filePath, index) => ({
|
|
2444
|
+
id: `doc-${index}`,
|
|
2445
|
+
description: `${operation}:${filePath}${requirements ? `:${requirements}` : ''}`,
|
|
2446
|
+
priority: index
|
|
2447
|
+
}));
|
|
2448
|
+
const dispatchPrompts = subtasks.map(t => t.description);
|
|
2449
|
+
const results = await this.coordinator.dispatch(dispatchPrompts.join(' ||| '), paths.length);
|
|
2450
|
+
const outputs = [];
|
|
2451
|
+
let allSuccess = true;
|
|
2452
|
+
for (let i = 0; i < paths.length; i++) {
|
|
2453
|
+
const result = results.find((r) => r.taskId === `task-${i}`);
|
|
2454
|
+
if (result) {
|
|
2455
|
+
outputs.push(result.output);
|
|
2456
|
+
if (!result.success)
|
|
2457
|
+
allSuccess = false;
|
|
2458
|
+
}
|
|
2459
|
+
else {
|
|
2460
|
+
outputs.push(`No result for ${paths[i]}`);
|
|
2461
|
+
allSuccess = false;
|
|
2462
|
+
}
|
|
2463
|
+
}
|
|
2464
|
+
return { outputs, success: allSuccess };
|
|
2465
|
+
}
|
|
2466
|
+
buildContext() {
|
|
2467
|
+
// P1 接入: 同步跑前 3 层压缩 (Budget Reduction / Snip / Microcompact)
|
|
2468
|
+
// 异步层 (Context Collapse / Auto-Compact) 在 promptStream 入口处单独跑 (用 LLM)
|
|
2469
|
+
// 失败静默: 任何 stage 抛错 → 走老 slice(-10) 逻辑
|
|
2470
|
+
//
|
|
2471
|
+
// P1.2: 如果 maybeAutoCompact 算过 Context Collapse 投影, 用 this.projectedHistory (读时投影, 非破坏)
|
|
2472
|
+
const source = this.projectedHistory ?? this.messageHistory;
|
|
2473
|
+
const recentMessages = this.compressHistorySync(source).slice(-10);
|
|
2474
|
+
return recentMessages.map(m => {
|
|
2475
|
+
if (m.role === 'user')
|
|
2476
|
+
return `用户: ${m.content}`;
|
|
2477
|
+
if (m.role === 'assistant')
|
|
2478
|
+
return `助手: ${m.content}`;
|
|
2479
|
+
if (m.role === 'tool') {
|
|
2480
|
+
const result = m.toolResult ? JSON.stringify(m.toolResult) : m.content;
|
|
2481
|
+
return `工具结果: ${result}`;
|
|
2482
|
+
}
|
|
2483
|
+
return m.content;
|
|
2484
|
+
}).join('\n');
|
|
2485
|
+
}
|
|
2486
|
+
/**
|
|
2487
|
+
* M3.5 (2026-06-17): 把 history 转成 messages 数组, 给 llm.chat() 用.
|
|
2488
|
+
* 不再用 buildContext() 把所有 role 压成字符串 — LLM 看不到 tool 调用结果.
|
|
2489
|
+
* messages 数组保留 role 语义, tool role 单独传递, LLM 能看到完整 tool 结果.
|
|
2490
|
+
*
|
|
2491
|
+
* 取最近 N 条, 同步压缩前 3 层 (跟 buildContext 同步).
|
|
2492
|
+
* 跳过 projectedHistory 路径 — messages 数组必须真实, 不能用投影.
|
|
2493
|
+
*/
|
|
2494
|
+
buildMessages() {
|
|
2495
|
+
try {
|
|
2496
|
+
// 2026-08-06: 来源优先用 projectedHistory (Context Collapse 投影, 非破坏) —
|
|
2497
|
+
// 与 buildContext 一致; 之前只让字符串路径用投影, messages 数组路径被跳过,
|
|
2498
|
+
// 导致 LLM 实际看到的还是未压缩的历史.
|
|
2499
|
+
const source = this.projectedHistory ?? this.messageHistory;
|
|
2500
|
+
const WINDOW = 15;
|
|
2501
|
+
const out = [];
|
|
2502
|
+
// 早期历史压缩: 超过窗口时, 不直接丢弃 — 提取前段用户意图摘要注入 (同步, 无 LLM).
|
|
2503
|
+
// 结构对齐 Context OS: System Prompt(persona) + 压缩摘要 + 最近消息.
|
|
2504
|
+
if (source.length > WINDOW) {
|
|
2505
|
+
const early = source.slice(0, source.length - WINDOW);
|
|
2506
|
+
const slice = source.slice(-WINDOW);
|
|
2507
|
+
const earlyUsers = early.filter(m => m.role === 'user' && (m.content || '').trim());
|
|
2508
|
+
const earlyTools = early.filter(m => m.role === 'tool').length;
|
|
2509
|
+
const earlyAssist = early.filter(m => m.role === 'assistant' && (m.content || '').trim()).length;
|
|
2510
|
+
const snippet = earlyUsers.slice(-5).map(m => `- ${(m.content || '').slice(0, 120).replace(/\n/g, ' ')}`).join('\n') || '- (早期对话无用户文本)';
|
|
2511
|
+
out.push({
|
|
2512
|
+
role: 'system',
|
|
2513
|
+
content: `[上下文压缩] 早期 ${early.length} 条消息已压缩 (用户 ${earlyUsers.length} 条 / AI ${earlyAssist} 条 / 工具结果 ${earlyTools} 条). 关键用户意图摘要:\n${snippet}\n[压缩结束] 以下是最近消息:`,
|
|
2514
|
+
});
|
|
2515
|
+
for (const m of slice) {
|
|
2516
|
+
const r = m.role;
|
|
2517
|
+
if (r === 'tool') {
|
|
2518
|
+
out.push({ role: 'user', content: `[工具结果]\n${(m.content || '').slice(0, 2000)}` });
|
|
2519
|
+
continue;
|
|
2520
|
+
}
|
|
2521
|
+
if (r === 'assistant') {
|
|
2522
|
+
out.push({ role: 'assistant', content: (m.content || '').slice(0, 4000), reasoningContent: m.reasoningContent });
|
|
2523
|
+
continue;
|
|
2524
|
+
}
|
|
2525
|
+
if (r === 'user') {
|
|
2526
|
+
out.push({ role: 'user', content: (m.content || '').slice(0, 2000) });
|
|
2527
|
+
continue;
|
|
2528
|
+
}
|
|
2529
|
+
if (r === 'system') {
|
|
2530
|
+
out.push({ role: 'system', content: (m.content || '').slice(0, 2000) });
|
|
2531
|
+
}
|
|
2532
|
+
}
|
|
2533
|
+
return out;
|
|
2534
|
+
}
|
|
2535
|
+
// 窗口内: 原逻辑 (tool 转 user role, 避免 tool_calls 配对)
|
|
2536
|
+
const slice = source.slice(-WINDOW);
|
|
2537
|
+
for (const m of slice) {
|
|
2538
|
+
const r = m.role;
|
|
2539
|
+
if (r === 'tool') {
|
|
2540
|
+
out.push({ role: 'user', content: `[工具结果]\n${m.content || ''}` });
|
|
2541
|
+
continue;
|
|
2542
|
+
}
|
|
2543
|
+
if (r === 'assistant') {
|
|
2544
|
+
out.push({ role: 'assistant', content: m.content || '', reasoningContent: m.reasoningContent });
|
|
2545
|
+
continue;
|
|
2546
|
+
}
|
|
2547
|
+
if (r === 'user') {
|
|
2548
|
+
out.push({ role: 'user', content: m.content || '' });
|
|
2549
|
+
}
|
|
2550
|
+
if (r === 'system') {
|
|
2551
|
+
out.push({ role: 'system', content: m.content || '' });
|
|
2552
|
+
}
|
|
2553
|
+
}
|
|
2554
|
+
return out;
|
|
2555
|
+
}
|
|
2556
|
+
catch (err) {
|
|
2557
|
+
console.warn('[PiAgent] buildMessages failed (silent, falling back to text):', err);
|
|
2558
|
+
// 退化: 用 buildContext 字符串包装成单 user message
|
|
2559
|
+
return [{ role: 'user', content: this.buildContext() }];
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
/**
|
|
2563
|
+
* 估算 messageHistory 的 token 数 (4 字符 ≈ 1 token, 与 context-compaction 同步).
|
|
2564
|
+
* 失败静默: 任何异常 → 0 (不阻塞)
|
|
2565
|
+
*/
|
|
2566
|
+
estimateHistoryTokens() {
|
|
2567
|
+
try {
|
|
2568
|
+
const { estimateTokens } = _piRequire('../context-compaction/index.js');
|
|
2569
|
+
return estimateTokens(this.messageHistory);
|
|
2570
|
+
}
|
|
2571
|
+
catch {
|
|
2572
|
+
return 0;
|
|
2573
|
+
}
|
|
2574
|
+
}
|
|
2575
|
+
/**
|
|
2576
|
+
* 3 个恢复机制合一:
|
|
2577
|
+
* 1. max output token 升级: 最多 3 次, 每次 maxOutputTokens 翻倍 (如果 llm.chat 支持)
|
|
2578
|
+
* 2. reactive compaction: 估算 > 80% 阈值, 跑 sync compressHistorySync + 必要时 maybeAutoCompact
|
|
2579
|
+
* 3. prompt-too-long: LLM 报错 4xxx token 错误, 跑 reactive compaction 再试 1 次
|
|
2580
|
+
*
|
|
2581
|
+
* 失败静默: 全部失败 → 返回空 reply, 让上层 no-tool_use 终止
|
|
2582
|
+
*/
|
|
2583
|
+
async callLlmWithRecovery(llm, contextOrMessages, systemPrompt, signal, onStream, tools) {
|
|
2584
|
+
// Reactive compaction 预检: 估算 token 超 80% 阈值, 跑一次
|
|
2585
|
+
const estimated = this.estimateHistoryTokens();
|
|
2586
|
+
if (estimated > this.maxContextTokens() * 0.8) {
|
|
2587
|
+
console.warn(`[PiAgent] reactive compaction pre-check (${estimated} tokens > 80% threshold)`);
|
|
2588
|
+
onStream?.({ type: 'status', content: '⚠️ reactive compaction 预检触发', tool: 'recovery' });
|
|
2589
|
+
try {
|
|
2590
|
+
const compacted = this.compressHistorySync(this.messageHistory);
|
|
2591
|
+
this.messageHistory = compacted;
|
|
2592
|
+
if (this.estimateHistoryTokens() > this.maxContextTokens() * 0.8) {
|
|
2593
|
+
await this.maybeAutoCompact(onStream, signal);
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
catch (err) {
|
|
2597
|
+
console.warn('[PiAgent] reactive compaction pre-check failed:', err);
|
|
2598
|
+
}
|
|
2599
|
+
}
|
|
2600
|
+
// 错误分级 (M1.3, 2026-06-17):
|
|
2601
|
+
// - 401/403/400 (认证/请求错误): 不重试, 直接 fail-fast
|
|
2602
|
+
// - 429 (rate limit): 重试 2 次, 指数退避
|
|
2603
|
+
// - 5xx (上游错误): 重试 2 次, 指数退避
|
|
2604
|
+
// - network (ECONNRESET / fetch failed / abort/timeout): 重试 2 次
|
|
2605
|
+
// - 4xx prompt-too-long: 走 reactive compaction
|
|
2606
|
+
// 这样以前所有错误都触发整个 runReActLoop 重跑(浪费 token),现在 4xx 直接失败
|
|
2607
|
+
// 让上层把失败原因广播给用户,而不是闷在 loop 里 retry 3 次后给空回复
|
|
2608
|
+
const classifyError = (err) => {
|
|
2609
|
+
const msg = String(err?.message || err || '');
|
|
2610
|
+
// 401/403: 认证失败
|
|
2611
|
+
if (/401|unauthor|invalid api key|api_key|forbidden|403/i.test(msg))
|
|
2612
|
+
return 'auth';
|
|
2613
|
+
// 400 prompt-too-long
|
|
2614
|
+
if (/token|too long|exceed|length|context|4000|413/i.test(msg))
|
|
2615
|
+
return 'prompt_too_long';
|
|
2616
|
+
// 429 rate limit
|
|
2617
|
+
if (/429|rate.?limit|too many requests/i.test(msg))
|
|
2618
|
+
return 'rate_limit';
|
|
2619
|
+
// 5xx
|
|
2620
|
+
if (/5\d\d|internal server|bad gateway|service unavailable|gateway timeout|cloudflare|502|503|504/i.test(msg))
|
|
2621
|
+
return 'server';
|
|
2622
|
+
// network
|
|
2623
|
+
if (/econnreset|econnrefused|enotfound|etimedout|fetch failed|network|aborted|timeout/i.test(msg))
|
|
2624
|
+
return 'network';
|
|
2625
|
+
return 'other';
|
|
2626
|
+
};
|
|
2627
|
+
const isRetryable = (cls) => cls === 'rate_limit' || cls === 'server' || cls === 'network' || cls === 'prompt_too_long';
|
|
2628
|
+
const maxAttempts = (cls) => isRetryable(cls) ? 3 : 1;
|
|
2629
|
+
const backoffMs = (attempt) => Math.min(1000 * 2 ** attempt, 8000); // 1s, 2s, 4s, 8s cap
|
|
2630
|
+
let lastErr = null;
|
|
2631
|
+
let lastClass = 'other';
|
|
2632
|
+
for (let attempt = 0; attempt < 4; attempt++) { // 最多 4 次尝试
|
|
2633
|
+
try {
|
|
2634
|
+
// M3.5 (2026-06-17): 传 messages 数组 (如果 contextOrMessages 是数组) 或字符串
|
|
2635
|
+
// 数组版让 LLM 看到结构化的 user/assistant/tool role, 而不是把 history 拼成单字符串
|
|
2636
|
+
// Bug 5: pass tool IDs for native OpenAI tool calling
|
|
2637
|
+
const response = await llm.chat(contextOrMessages, systemPrompt, signal, tools);
|
|
2638
|
+
// 2026-06-30: 透传 toolCalls (OpenAI 协议 native) 给上层, 让 assistant message 能 emit 真 id
|
|
2639
|
+
return { reply: response.reply || '', toolCalls: response.toolCalls };
|
|
2640
|
+
}
|
|
2641
|
+
catch (err) {
|
|
2642
|
+
// 用户主动 abort: 不重试, 立即抛
|
|
2643
|
+
if (signal?.aborted || err?.name === 'AbortError')
|
|
2644
|
+
throw err;
|
|
2645
|
+
lastErr = err;
|
|
2646
|
+
lastClass = classifyError(err);
|
|
2647
|
+
const errMsg = String(err?.message || err || '').slice(0, 200);
|
|
2648
|
+
const attempts = maxAttempts(lastClass);
|
|
2649
|
+
if (attempt + 1 >= attempts) {
|
|
2650
|
+
console.warn(`[PiAgent] LLM 调用失败, 不再重试 (class=${lastClass}, attempt=${attempt + 1}/${attempts}): ${errMsg}`);
|
|
2651
|
+
break;
|
|
2652
|
+
}
|
|
2653
|
+
console.warn(`[PiAgent] LLM 调用失败 (class=${lastClass}, attempt=${attempt + 1}/${attempts}), ${backoffMs(attempt)}ms 后重试: ${errMsg}`);
|
|
2654
|
+
onStream?.({ type: 'status', content: `⚠️ LLM 调用失败 (${lastClass}), 重试 ${attempt + 2}/${attempts}...`, tool: 'recovery' });
|
|
2655
|
+
if (lastClass === 'prompt_too_long') {
|
|
2656
|
+
try {
|
|
2657
|
+
await this.maybeAutoCompact(onStream, signal);
|
|
2658
|
+
}
|
|
2659
|
+
catch (compactionErr) {
|
|
2660
|
+
console.warn('[PiAgent] reactive compaction on prompt-too-long failed:', compactionErr);
|
|
2661
|
+
}
|
|
2662
|
+
// 重新生成 context (重试 prompt_too_long 时重建 messages — 包含压缩后的 history)
|
|
2663
|
+
if (Array.isArray(contextOrMessages)) {
|
|
2664
|
+
contextOrMessages = this.buildMessages();
|
|
2665
|
+
}
|
|
2666
|
+
else {
|
|
2667
|
+
contextOrMessages = this.buildContext();
|
|
2668
|
+
}
|
|
2669
|
+
}
|
|
2670
|
+
else if (errMsg.includes('insufficient tool messages') || errMsg.includes('must be followed by tool messages')) {
|
|
2671
|
+
// 2026-07-29: 特殊的 400 错误 — tool_calls 配对异常, 降级为纯文本 context
|
|
2672
|
+
console.warn('[PiAgent] insufficient tool messages — 降级为 buildContext 文本');
|
|
2673
|
+
if (Array.isArray(contextOrMessages)) {
|
|
2674
|
+
contextOrMessages = this.buildContext();
|
|
2675
|
+
// 也清除最近一轮的 toolCalls, 防止再触发
|
|
2676
|
+
if (this.messageHistory.length > 1) {
|
|
2677
|
+
const last = this.messageHistory[this.messageHistory.length - 1];
|
|
2678
|
+
if (last.role === 'assistant' && last.toolCalls) {
|
|
2679
|
+
delete last.toolCalls;
|
|
2680
|
+
}
|
|
2681
|
+
}
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2684
|
+
else {
|
|
2685
|
+
// 指数退避
|
|
2686
|
+
await new Promise((r) => setTimeout(r, backoffMs(attempt)));
|
|
2687
|
+
}
|
|
2688
|
+
}
|
|
2689
|
+
}
|
|
2690
|
+
// 失败: 返回结构化错误 reply (而不是空字符串), 上层可识别 + UI 可显示
|
|
2691
|
+
const errMsg = String(lastErr?.message || lastErr || '').slice(0, 300);
|
|
2692
|
+
const userMsg = lastClass === 'auth'
|
|
2693
|
+
? `[AI 服务调用失败] 认证错误: ${errMsg}\n请检查 API key 配置 (env: OPENAI_API_KEY / ANTHROPIC_API_KEY 等)`
|
|
2694
|
+
: lastClass === 'rate_limit'
|
|
2695
|
+
? `[AI 服务调用失败] 上游限流 (429): ${errMsg}\n请稍后重试`
|
|
2696
|
+
: lastClass === 'server'
|
|
2697
|
+
? `[AI 服务调用失败] 上游错误: ${errMsg}\n已重试 2 次仍失败, 可稍后重试`
|
|
2698
|
+
: lastClass === 'network'
|
|
2699
|
+
? `[AI 服务调用失败] 网络错误: ${errMsg}\n请检查网络连接`
|
|
2700
|
+
: `[AI 服务调用失败] ${errMsg}`;
|
|
2701
|
+
console.warn(`[PiAgent] callLlmWithRecovery 全部失败 (class=${lastClass}): ${errMsg}`);
|
|
2702
|
+
return { reply: userMsg };
|
|
2703
|
+
}
|
|
2704
|
+
/**
|
|
2705
|
+
* 同步压缩: 跑前 3 层 (Budget Reduction / Snip / Microcompact).
|
|
2706
|
+
* Context Collapse / Auto-Compact 是 async, 不在 buildContext 同步链里跑.
|
|
2707
|
+
* 失败静默: 任何 stage 抛错 → 返回原 history.
|
|
2708
|
+
*/
|
|
2709
|
+
compressHistorySync(history) {
|
|
2710
|
+
try {
|
|
2711
|
+
// context-compaction 的 Message 与 pi-sdk 的 Message 字段兼容
|
|
2712
|
+
// 这里用 any cast 跳过 structural type 严格校验 (避免双向 import)
|
|
2713
|
+
let h = history;
|
|
2714
|
+
const r1 = (0, index_js_2.budgetReduce)(h);
|
|
2715
|
+
h = r1.history;
|
|
2716
|
+
const r2 = (0, index_js_2.snip)(h);
|
|
2717
|
+
h = r2.history;
|
|
2718
|
+
const r3 = (0, index_js_2.microcompact)(h);
|
|
2719
|
+
h = r3.history;
|
|
2720
|
+
return h;
|
|
2721
|
+
}
|
|
2722
|
+
catch (err) {
|
|
2723
|
+
console.warn('[PiAgent] compressHistorySync failed (silent, using original):', err);
|
|
2724
|
+
return history;
|
|
2725
|
+
}
|
|
2726
|
+
}
|
|
2727
|
+
/**
|
|
2728
|
+
* P1.1: 异步跑 Auto-Compact (LLM 摘要).
|
|
2729
|
+
* 入口: promptStream 入口, 在 computeJudgmentGate 之后, onSessionStart 之前.
|
|
2730
|
+
*
|
|
2731
|
+
* 逻辑:
|
|
2732
|
+
* 1. 跑完整 compactPipeline (5 层, 异步)
|
|
2733
|
+
* 2. 第 5 层 (Auto-Compact) 需要 LLM, 通过 getMinimax().chat 注入
|
|
2734
|
+
* 3. 如果 budgetGate 不超限, 5 层短路在前 3 层, 不会调 LLM → 零开销
|
|
2735
|
+
* 4. 失败静默: 任何异常 → console.warn + 保留原 messageHistory
|
|
2736
|
+
*
|
|
2737
|
+
* onStream 广播: 跟 computeJudgmentGate 风格一致 (phase 事件供 UI timeline 显示)
|
|
2738
|
+
*/
|
|
2739
|
+
async maybeAutoCompact(onStream, signal) {
|
|
2740
|
+
if (this.messageHistory.length < 10)
|
|
2741
|
+
return; // 历史太短, 不值得压
|
|
2742
|
+
onStream?.({ type: 'status', content: '🗜️ 评估是否需要压缩上下文...', tool: 'compactor' });
|
|
2743
|
+
// 注入 LLM (用 getMinimax().chat, 与 judgment 注入门 / ReAct 循环同一来源)
|
|
2744
|
+
// 给 Context Collapse (虚拟投影) 和 Auto-Compact (摘要) 共用
|
|
2745
|
+
const llm = (0, index_js_1.getMinimax)();
|
|
2746
|
+
const llmChat = async (systemPrompt, userPrompt) => {
|
|
2747
|
+
const r = await llm.chat(userPrompt, systemPrompt, signal);
|
|
2748
|
+
return r.reply;
|
|
2749
|
+
};
|
|
2750
|
+
// 2026-08-06: 预算 = ContextManager 配置 (1M * 55% ≈ 550K), 不再写死 8000.
|
|
2751
|
+
// 之前 8000 与 48K 触发阈值矛盾: 一触发就一路跑到 LLM 摘要 (贵), 且 8000 远小于实际窗口.
|
|
2752
|
+
const cm = (0, context_manager_js_1.getContextManager)();
|
|
2753
|
+
const cfg = cm.getConfig();
|
|
2754
|
+
const maxTokens = Math.max(4000, Math.round(cfg.maxTokens * cfg.compressionThreshold));
|
|
2755
|
+
const beforeTokens = this.estimateHistoryTokens();
|
|
2756
|
+
const { compactPipeline, isContextCollapseEnabled } = await Promise.resolve().then(() => __importStar(require('../context-compaction/index.js')));
|
|
2757
|
+
const result = await compactPipeline(this.messageHistory, {
|
|
2758
|
+
maxTokens,
|
|
2759
|
+
llmChat,
|
|
2760
|
+
collapseLlmChat: llmChat, // P1.2: Context Collapse 投影也用同一 LLM
|
|
2761
|
+
cacheScope: this.currentChannelId || 'default',
|
|
2762
|
+
});
|
|
2763
|
+
if (result.compacted && result.history.length < this.messageHistory.length) {
|
|
2764
|
+
const saved = this.messageHistory.length - result.history.length;
|
|
2765
|
+
const stagesApplied = result.stages.filter((s) => s.applied).map((s) => s.stage).join(' → ');
|
|
2766
|
+
const afterTokens = this.estimateHistoryTokens();
|
|
2767
|
+
const savedTokens = Math.max(0, beforeTokens - afterTokens);
|
|
2768
|
+
cm.markCompressStart(beforeTokens);
|
|
2769
|
+
onStream?.({
|
|
2770
|
+
type: 'status',
|
|
2771
|
+
content: `🗜️ 上下文压缩: ${stagesApplied || 'no-op'} | 节省 ${saved} 条 / ${savedTokens.toLocaleString()} tokens (剩余 ${result.history.length}, collapse=${isContextCollapseEnabled() ? 'on' : 'off'})`,
|
|
2772
|
+
tool: 'compactor',
|
|
2773
|
+
});
|
|
2774
|
+
// 关键: 第 4 层 (Context Collapse) 是读时投影 (非破坏)
|
|
2775
|
+
// 第 5 层 (Auto-Compact) 是破坏性折叠
|
|
2776
|
+
// 这里用 if-else 区分: collapse on → 仅 buildContext 用; collapse off → 真更新
|
|
2777
|
+
if (isContextCollapseEnabled()) {
|
|
2778
|
+
this.projectedHistory = result.history; // buildContext 用
|
|
2779
|
+
// messageHistory 不变 (非破坏)
|
|
2780
|
+
}
|
|
2781
|
+
else {
|
|
2782
|
+
this.messageHistory = result.history; // 真破坏性更新
|
|
2783
|
+
this.projectedHistory = null;
|
|
2784
|
+
}
|
|
2785
|
+
// 2026-08-06: snapshot 记录 before/after + 摘要 (供恢复/调试/UI), 事件广播
|
|
2786
|
+
try {
|
|
2787
|
+
const summaryLine = result.stages.map((s) => `${s.stage}(${s.before}→${s.after})`).join(' ');
|
|
2788
|
+
const snap = cm.makeSnapshot({
|
|
2789
|
+
beforeTokens,
|
|
2790
|
+
afterTokens,
|
|
2791
|
+
summary: `压缩管道: ${summaryLine}; 节省 ${savedTokens} tokens / ${saved} 条消息`,
|
|
2792
|
+
preservedMemory: [
|
|
2793
|
+
...this.messageHistory.filter(m => m.role === 'user').slice(-3).map(m => (m.content || '').slice(0, 80)),
|
|
2794
|
+
],
|
|
2795
|
+
agentId: this.currentAgentId,
|
|
2796
|
+
channelId: this.currentChannelId,
|
|
2797
|
+
});
|
|
2798
|
+
cm.markCompressComplete(snap);
|
|
2799
|
+
}
|
|
2800
|
+
catch (snapErr) {
|
|
2801
|
+
// snapshot 失败不阻塞主流程
|
|
2802
|
+
}
|
|
2803
|
+
cm.updateUsage(afterTokens);
|
|
2804
|
+
}
|
|
2805
|
+
else {
|
|
2806
|
+
// 没压成也更新 usage (数据源保持新鲜)
|
|
2807
|
+
cm.updateUsage(beforeTokens);
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
2810
|
+
isFinalResponse(content) {
|
|
2811
|
+
// 2026-06-30: 抽到 ./parse-tool-call.ts 作为纯函数 — 这里只构建 ctx 并调用
|
|
2812
|
+
return (0, parse_tool_call_js_1.isFinalResponse)(content, this._parseCtx());
|
|
2813
|
+
}
|
|
2814
|
+
extractFinalAnswer(content) {
|
|
2815
|
+
// 抽取实现已挪到 ./parse-tool-call.ts (纯函数, 易测)
|
|
2816
|
+
return (0, parse_tool_call_js_1.extractFinalAnswer)(content);
|
|
2817
|
+
}
|
|
2818
|
+
_parseCtx() {
|
|
2819
|
+
return {
|
|
2820
|
+
tools: new Set(Array.from(this.tools.keys())),
|
|
2821
|
+
resolveAlias: (name) => this.resolveToolName(name),
|
|
2822
|
+
};
|
|
2823
|
+
}
|
|
2824
|
+
parseToolCall(content) {
|
|
2825
|
+
// 2026-06-30: 抽到 ./parse-tool-call.ts 作为纯函数 — 这里只构建 ctx 并调用
|
|
2826
|
+
return (0, parse_tool_call_js_1.parseToolCall)(content, this._parseCtx());
|
|
2827
|
+
}
|
|
2828
|
+
// [debug-2026-06-19] 临时: 打印 parseToolCall 输入和返回
|
|
2829
|
+
_dbgParseToolCall(content) {
|
|
2830
|
+
const r = this.parseToolCall(content);
|
|
2831
|
+
console.log('[DBG parseToolCall] result:', JSON.stringify(r), 'content head:', JSON.stringify(content.substring(0, 200)));
|
|
2832
|
+
return r;
|
|
2833
|
+
}
|
|
2834
|
+
/**
|
|
2835
|
+
* 2026-06-19: 工具名大小写不敏感 + Claude Code 风格别名映射
|
|
2836
|
+
|
|
2837
|
+
/**
|
|
2838
|
+
* 2026-06-19: 工具名大小写不敏感 + Claude Code 风格别名映射
|
|
2839
|
+
* LLM 实际产出 Read/Edit/Write/Bash/Grep/Glob 等大写名 (Claude Code 工具命名)
|
|
2840
|
+
* bolloon 注册的是 read_document / edit_file / write_file / shell_exec / list_files
|
|
2841
|
+
* 返回 this.tools 里的标准名, 或 null 表示未识别
|
|
2842
|
+
*/
|
|
2843
|
+
/**
|
|
2844
|
+
* 把 LLM 给的工具名 (可能大小写不一, 或者 Claude Code 风格的别名) 解析为
|
|
2845
|
+
* bolloon 注册的标准工具名.
|
|
2846
|
+
*
|
|
2847
|
+
* 2026-06-30: 委托给 ToolRegistry 模块 — alias 表在 tool-registry.ts 统一维护,
|
|
2848
|
+
* 这里只做 thin wrapper 保留 backward compat (private API 但其它地方可能用).
|
|
2849
|
+
*/
|
|
2850
|
+
resolveToolName(name) {
|
|
2851
|
+
return this._toolRegistry.resolve(name);
|
|
2852
|
+
}
|
|
2853
|
+
estimateResponseQuality(response) {
|
|
2854
|
+
let score = 0.5;
|
|
2855
|
+
if (response.length > 50)
|
|
2856
|
+
score += 0.1;
|
|
2857
|
+
if (response.length > 200)
|
|
2858
|
+
score += 0.1;
|
|
2859
|
+
if (response.length < 20)
|
|
2860
|
+
score -= 0.3;
|
|
2861
|
+
if (response.includes('\n'))
|
|
2862
|
+
score += 0.1;
|
|
2863
|
+
if (response.includes('-') || response.includes('•'))
|
|
2864
|
+
score += 0.05;
|
|
2865
|
+
if (response.includes('```'))
|
|
2866
|
+
score += 0.1;
|
|
2867
|
+
const conclusionWords = ['完成', '结果', '总结', '所以', '因此', '答案', '推荐'];
|
|
2868
|
+
if (conclusionWords.some(w => response.includes(w)))
|
|
2869
|
+
score += 0.1;
|
|
2870
|
+
if (response.includes('调用工具') || response.includes('tool('))
|
|
2871
|
+
score -= 0.2;
|
|
2872
|
+
return Math.max(0, Math.min(1, score));
|
|
2873
|
+
}
|
|
2874
|
+
estimateToolResultQuality(result) {
|
|
2875
|
+
let score = 0.5;
|
|
2876
|
+
if (!result.success)
|
|
2877
|
+
return 0.2;
|
|
2878
|
+
if (result.output) {
|
|
2879
|
+
score += 0.2;
|
|
2880
|
+
if (result.output.length > 50)
|
|
2881
|
+
score += 0.1;
|
|
2882
|
+
if (result.output.length < 10)
|
|
2883
|
+
score -= 0.1;
|
|
2884
|
+
if (result.output.includes('❌') || result.output.includes('error'))
|
|
2885
|
+
score -= 0.2;
|
|
2886
|
+
if (result.output.includes('✅') || result.output.includes('success'))
|
|
2887
|
+
score += 0.1;
|
|
2888
|
+
}
|
|
2889
|
+
if (result.error)
|
|
2890
|
+
score -= 0.3;
|
|
2891
|
+
return Math.max(0, Math.min(1, score));
|
|
2892
|
+
}
|
|
2893
|
+
async handleFallback(input) {
|
|
2894
|
+
const lowerInput = input.toLowerCase();
|
|
2895
|
+
const parts = input.trim().split(/\s+/);
|
|
2896
|
+
const cmd = parts[0].toLowerCase();
|
|
2897
|
+
const args = parts.slice(1).join(' ');
|
|
2898
|
+
if (cmd.includes('读取') || cmd === 'read' || cmd === 'read_document') {
|
|
2899
|
+
if (args)
|
|
2900
|
+
return await this.readDocument(args);
|
|
2901
|
+
}
|
|
2902
|
+
if (cmd.includes('总结') || cmd === 'summary' || cmd === 'summarize') {
|
|
2903
|
+
if (args)
|
|
2904
|
+
return await this.summarizeText(args);
|
|
2905
|
+
}
|
|
2906
|
+
if (cmd.includes('改进') || cmd === 'improve' || cmd === 'improve_document') {
|
|
2907
|
+
const match = input.match(/改进[^\w]+(.+)/i) || input.match(/improve\s+(.+)/i);
|
|
2908
|
+
if (match) {
|
|
2909
|
+
return `改进需要LLM支持,请设置 MINIMAX_API_KEY 环境变量。\n文件: ${match[1]}`;
|
|
2910
|
+
}
|
|
2911
|
+
}
|
|
2912
|
+
if (cmd.includes('节点') || cmd === 'peers' || cmd === 'list_peers') {
|
|
2913
|
+
return this.listPeers();
|
|
2914
|
+
}
|
|
2915
|
+
if (cmd.includes('身份') || cmd === 'identity' || cmd === 'get_identity') {
|
|
2916
|
+
return JSON.stringify(this.getIdentity(), null, 2);
|
|
2917
|
+
}
|
|
2918
|
+
if (cmd.includes('日志') || cmd === 'logs') {
|
|
2919
|
+
const logs = this.constraintLayer.getLogs();
|
|
2920
|
+
if (logs.length === 0)
|
|
2921
|
+
return '暂无操作日志';
|
|
2922
|
+
return logs.slice(-5).map(l => `[${new Date(l.timestamp).toISOString()}] ${l.action}`).join('\n');
|
|
2923
|
+
}
|
|
2924
|
+
return this.getDefaultResponse(input);
|
|
2925
|
+
}
|
|
2926
|
+
static OPERATIONS_REFERENCE = null;
|
|
2927
|
+
static getOperationsReference() {
|
|
2928
|
+
if (this.OPERATIONS_REFERENCE === null) {
|
|
2929
|
+
try {
|
|
2930
|
+
const refPath = path.join(process.cwd(), 'src', 'bollharness', 'scripts', 'context-fragments', 'pi-agent-operations.md');
|
|
2931
|
+
this.OPERATIONS_REFERENCE = fsSync.readFileSync(refPath, 'utf-8');
|
|
2932
|
+
}
|
|
2933
|
+
catch {
|
|
2934
|
+
this.OPERATIONS_REFERENCE = '';
|
|
2935
|
+
}
|
|
2936
|
+
}
|
|
2937
|
+
return this.OPERATIONS_REFERENCE;
|
|
2938
|
+
}
|
|
2939
|
+
getDefaultResponse(input) {
|
|
2940
|
+
const operationsRef = PiAgentSession.getOperationsReference();
|
|
2941
|
+
if (operationsRef) {
|
|
2942
|
+
return `收到了: "${input}"
|
|
2943
|
+
|
|
2944
|
+
我是一个判断力处理智能体,支持自然语言交互。
|
|
2945
|
+
|
|
2946
|
+
可用操作(直接说出即可):
|
|
2947
|
+
${this.extractOperationsFromRef(operationsRef)}
|
|
2948
|
+
|
|
2949
|
+
示例请求:
|
|
2950
|
+
- "读取 src/index.ts 文件"
|
|
2951
|
+
- "总结一下 README.md"
|
|
2952
|
+
- "查看当前连接了哪些节点"
|
|
2953
|
+
- "向 QmABC... 发送测试消息"`;
|
|
2954
|
+
}
|
|
2955
|
+
return `收到了: "${input}"
|
|
2956
|
+
|
|
2957
|
+
我是一个判断力处理智能体,支持自然语言交互。
|
|
2958
|
+
|
|
2959
|
+
可用操作(直接说出即可):
|
|
2960
|
+
- "读取 README.md" - 读取并分析文档
|
|
2961
|
+
- "总结文档" - 总结文档内容
|
|
2962
|
+
- "改进文档,按照X要求" - 改进文档
|
|
2963
|
+
- "查看节点" - 查看已连接的对等节点
|
|
2964
|
+
- "向X发送消息Y" - 向对等节点发送消息
|
|
2965
|
+
- "广播消息X" - 广播消息到所有节点
|
|
2966
|
+
- "查看身份" - 查看当前智能体身份
|
|
2967
|
+
- "查看日志" - 查看最近操作日志
|
|
2968
|
+
|
|
2969
|
+
示例请求:
|
|
2970
|
+
- "读取 src/index.ts 文件"
|
|
2971
|
+
- "总结一下 README.md"
|
|
2972
|
+
- "查看当前连接了哪些节点"
|
|
2973
|
+
- "向 QmABC... 发送测试消息"`;
|
|
2974
|
+
}
|
|
2975
|
+
extractOperationsFromRef(ref) {
|
|
2976
|
+
const lines = ref.split('\n');
|
|
2977
|
+
const inOperationsSection = false;
|
|
2978
|
+
const operationLines = [];
|
|
2979
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2980
|
+
const line = lines[i];
|
|
2981
|
+
if (line.startsWith('## 可用操作')) {
|
|
2982
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
2983
|
+
const opLine = lines[j];
|
|
2984
|
+
if (opLine.startsWith('## ') || opLine.startsWith('#'))
|
|
2985
|
+
break;
|
|
2986
|
+
if (opLine.includes('|') && !opLine.startsWith('|')) {
|
|
2987
|
+
const parts = opLine.split('|').map(p => p.trim());
|
|
2988
|
+
if (parts.length >= 3 && parts[1] && parts[2]) {
|
|
2989
|
+
operationLines.push(` - "${parts[1]}" - ${parts[2]}`);
|
|
2990
|
+
}
|
|
2991
|
+
}
|
|
2992
|
+
}
|
|
2993
|
+
break;
|
|
2994
|
+
}
|
|
2995
|
+
}
|
|
2996
|
+
return operationLines.length > 0 ? operationLines.join('\n') :
|
|
2997
|
+
` - "读取 README.md" - 读取并分析文档
|
|
2998
|
+
- "总结文档" - 总结文档内容
|
|
2999
|
+
- "改进文档,按照X要求" - 改进文档
|
|
3000
|
+
- "查看节点" - 查看已连接的对等节点
|
|
3001
|
+
- "向X发送消息Y" - 向对等节点发送消息
|
|
3002
|
+
- "广播消息X" - 广播消息到所有节点
|
|
3003
|
+
- "查看身份" - 查看当前智能体身份
|
|
3004
|
+
- "查看日志" - 查看最近操作日志`;
|
|
3005
|
+
}
|
|
3006
|
+
async suggestRename(messages) {
|
|
3007
|
+
if (!this.minimaxAvailable || messages.length < 2) {
|
|
3008
|
+
return null;
|
|
3009
|
+
}
|
|
3010
|
+
const conversation = messages.map(m => `${m.type === 'user' ? '用户' : '助手'}: ${m.content}`).join('\n');
|
|
3011
|
+
const llm = (0, index_js_1.getMinimax)();
|
|
3012
|
+
try {
|
|
3013
|
+
const response = await llm.chat(`根据以下对话内容,为这个对话生成一个简短的名称(不超过20个字):\n\n${conversation}\n\n直接输出名称,不要其他解释。`, '命名建议');
|
|
3014
|
+
const name = response.reply.trim();
|
|
3015
|
+
// 拒绝错误回退串 (LLM 不可用时返回的占位文本)
|
|
3016
|
+
if (!name)
|
|
3017
|
+
return null;
|
|
3018
|
+
if (/^(抱歉|对不起|sorry|error|错误|失败|暂不可用|服务不可用)/i.test(name)) {
|
|
3019
|
+
console.log(`[suggestRename] 拒绝错误回退: "${name}"`);
|
|
3020
|
+
return null;
|
|
3021
|
+
}
|
|
3022
|
+
if (name.length > 20)
|
|
3023
|
+
return null;
|
|
3024
|
+
if (name === '智能体')
|
|
3025
|
+
return null;
|
|
3026
|
+
// 拒绝纯符号/标点
|
|
3027
|
+
if (!/[一-鿿\w]/.test(name))
|
|
3028
|
+
return null;
|
|
3029
|
+
return `Agent | ${name}`;
|
|
3030
|
+
}
|
|
3031
|
+
catch {
|
|
3032
|
+
// ignore
|
|
3033
|
+
}
|
|
3034
|
+
return null;
|
|
3035
|
+
}
|
|
3036
|
+
async summarizeText(text) {
|
|
3037
|
+
if (!this.minimaxAvailable) {
|
|
3038
|
+
return '⚠️ LLM未初始化,请设置 MINIMAX_API_KEY 环境变量';
|
|
3039
|
+
}
|
|
3040
|
+
const llm = (0, index_js_1.getMinimax)();
|
|
3041
|
+
const result = await llm.summarize(text);
|
|
3042
|
+
return `📝 摘要:\n${result.summary}\n\n质量评分: ${(result.qualityScore * 10).toFixed(1)}/10`;
|
|
3043
|
+
}
|
|
3044
|
+
async readDocument(filePath) {
|
|
3045
|
+
const content = await reader_js_1.documentReader.read(filePath);
|
|
3046
|
+
this.sessionManager.addFileContext(filePath, content.text.substring(0, 1000));
|
|
3047
|
+
return `📄 ${content.metadata.filename}\n大小: ${content.metadata.size} 字节\n\n${content.text.substring(0, 500)}...`;
|
|
3048
|
+
}
|
|
3049
|
+
async summarizeDocument(filePath, context) {
|
|
3050
|
+
if (!this.minimaxAvailable) {
|
|
3051
|
+
return {
|
|
3052
|
+
summary: '⚠️ LLM未初始化,请设置 MINIMAX_API_KEY 环境变量',
|
|
3053
|
+
qualityScore: 0
|
|
3054
|
+
};
|
|
3055
|
+
}
|
|
3056
|
+
const content = await reader_js_1.documentReader.read(filePath);
|
|
3057
|
+
this.sessionManager.addFileContext(filePath, content.text.substring(0, 1000));
|
|
3058
|
+
const llm = (0, index_js_1.getMinimax)();
|
|
3059
|
+
const chunks = reader_js_1.documentReader.chunk(content.text);
|
|
3060
|
+
const summaries = [];
|
|
3061
|
+
let totalQuality = 0;
|
|
3062
|
+
for (const chunk of chunks) {
|
|
3063
|
+
const result = await llm.summarize(chunk, context);
|
|
3064
|
+
summaries.push(result.summary);
|
|
3065
|
+
totalQuality += result.qualityScore;
|
|
3066
|
+
}
|
|
3067
|
+
const avgQuality = totalQuality / chunks.length;
|
|
3068
|
+
return {
|
|
3069
|
+
summary: summaries.join('\n\n'),
|
|
3070
|
+
qualityScore: avgQuality
|
|
3071
|
+
};
|
|
3072
|
+
}
|
|
3073
|
+
async improveDocument(request) {
|
|
3074
|
+
if (!this.minimaxAvailable) {
|
|
3075
|
+
return {
|
|
3076
|
+
improved: false,
|
|
3077
|
+
qualityScore: 0,
|
|
3078
|
+
shouldAutoSend: false
|
|
3079
|
+
};
|
|
3080
|
+
}
|
|
3081
|
+
const content = await reader_js_1.documentReader.read(request.originalPath);
|
|
3082
|
+
const llm = (0, index_js_1.getMinimax)();
|
|
3083
|
+
const improvedResult = await llm.summarize(content.text + '\n\n改进要求: ' + request.requirements, request.context);
|
|
3084
|
+
const shouldAutoSend = await llm.shouldAutoSend(improvedResult.qualityScore, 0.7);
|
|
3085
|
+
return {
|
|
3086
|
+
improved: true,
|
|
3087
|
+
newContent: improvedResult.summary,
|
|
3088
|
+
qualityScore: improvedResult.qualityScore,
|
|
3089
|
+
shouldAutoSend
|
|
3090
|
+
};
|
|
3091
|
+
}
|
|
3092
|
+
async runWorkflow(steps) {
|
|
3093
|
+
const context = {
|
|
3094
|
+
peers: this.getPeers(),
|
|
3095
|
+
logs: []
|
|
3096
|
+
};
|
|
3097
|
+
const checkResult = await this.constraintLayer.checkGuardrails(context);
|
|
3098
|
+
if (!checkResult.passed && checkResult.blocked) {
|
|
3099
|
+
console.warn(`Guardrail blocked: ${checkResult.blocked.name}`);
|
|
3100
|
+
}
|
|
3101
|
+
return this.workflowEngine.executeWorkflow(steps, context);
|
|
3102
|
+
}
|
|
3103
|
+
async summarizeDocumentWorkflow(filePath, targetPeer) {
|
|
3104
|
+
const steps = [
|
|
3105
|
+
{
|
|
3106
|
+
id: 'read',
|
|
3107
|
+
type: 'read',
|
|
3108
|
+
config: { path: filePath },
|
|
3109
|
+
retry: { max: 3, current: 0, backoffMs: 1000 },
|
|
3110
|
+
onFail: 'abort'
|
|
3111
|
+
},
|
|
3112
|
+
{
|
|
3113
|
+
id: 'summarize',
|
|
3114
|
+
type: 'summarize',
|
|
3115
|
+
config: { context: `File: ${filePath}` },
|
|
3116
|
+
retry: { max: 3, current: 0, backoffMs: 1000 },
|
|
3117
|
+
onFail: 'skip',
|
|
3118
|
+
guardrail: (ctx) => Promise.resolve(ctx.qualityScore !== undefined && ctx.qualityScore >= 0.5)
|
|
3119
|
+
}
|
|
3120
|
+
];
|
|
3121
|
+
if (targetPeer) {
|
|
3122
|
+
steps.push({
|
|
3123
|
+
id: 'send',
|
|
3124
|
+
type: 'send',
|
|
3125
|
+
config: { peerId: targetPeer },
|
|
3126
|
+
retry: { max: 2, current: 0, backoffMs: 2000 },
|
|
3127
|
+
onFail: 'skip'
|
|
3128
|
+
});
|
|
3129
|
+
}
|
|
3130
|
+
return this.runWorkflow(steps);
|
|
3131
|
+
}
|
|
3132
|
+
async improveAndSendWorkflow(filePath, requirements, targetPeer) {
|
|
3133
|
+
const steps = [
|
|
3134
|
+
{
|
|
3135
|
+
id: 'read',
|
|
3136
|
+
type: 'read',
|
|
3137
|
+
config: { path: filePath },
|
|
3138
|
+
retry: { max: 3, current: 0, backoffMs: 1000 },
|
|
3139
|
+
onFail: 'abort'
|
|
3140
|
+
},
|
|
3141
|
+
{
|
|
3142
|
+
id: 'improve',
|
|
3143
|
+
type: 'improve',
|
|
3144
|
+
config: { requirements, context: `File: ${filePath}` },
|
|
3145
|
+
retry: { max: 2, current: 0, backoffMs: 1500 },
|
|
3146
|
+
onFail: 'skip'
|
|
3147
|
+
},
|
|
3148
|
+
{
|
|
3149
|
+
id: 'send',
|
|
3150
|
+
type: 'send',
|
|
3151
|
+
config: { peerId: targetPeer, message: '改进后的文档' },
|
|
3152
|
+
retry: { max: 2, current: 0, backoffMs: 2000 },
|
|
3153
|
+
onFail: 'skip'
|
|
3154
|
+
}
|
|
3155
|
+
];
|
|
3156
|
+
return this.runWorkflow(steps);
|
|
3157
|
+
}
|
|
3158
|
+
getOperationLogs() {
|
|
3159
|
+
return this.constraintLayer.getLogs();
|
|
3160
|
+
}
|
|
3161
|
+
listPeers() {
|
|
3162
|
+
const peers = p2p_js_1.p2pNetwork.getPeers();
|
|
3163
|
+
if (peers.length === 0) {
|
|
3164
|
+
return '当前无连接的对等节点';
|
|
3165
|
+
}
|
|
3166
|
+
return `已连接节点 (${peers.length}):\n${peers.map(p => ` - ${p}`).join('\n')}`;
|
|
3167
|
+
}
|
|
3168
|
+
getPeers() {
|
|
3169
|
+
return p2p_js_1.p2pNetwork.getPeers();
|
|
3170
|
+
}
|
|
3171
|
+
async sendMessage(peerId, message) {
|
|
3172
|
+
await p2p_js_1.p2pNetwork.sendMessage(peerId, 'message', message);
|
|
3173
|
+
}
|
|
3174
|
+
async broadcast(message) {
|
|
3175
|
+
await p2p_js_1.p2pNetwork.broadcast('message', message);
|
|
3176
|
+
}
|
|
3177
|
+
getIdentity() {
|
|
3178
|
+
return { ...this.identity };
|
|
3179
|
+
}
|
|
3180
|
+
updateIdentity(updates) {
|
|
3181
|
+
this.identity = { ...this.identity, ...updates };
|
|
3182
|
+
}
|
|
3183
|
+
setCurrentChannelId(channelId) {
|
|
3184
|
+
this.currentChannelId = channelId;
|
|
3185
|
+
}
|
|
3186
|
+
getSessionState() {
|
|
3187
|
+
return this.sessionManager.getState();
|
|
3188
|
+
}
|
|
3189
|
+
getMemory() {
|
|
3190
|
+
return this.sessionManager.getMemory();
|
|
3191
|
+
}
|
|
3192
|
+
getPersona() {
|
|
3193
|
+
return this.sessionManager.getPersona();
|
|
3194
|
+
}
|
|
3195
|
+
async setPersona(persona) {
|
|
3196
|
+
await this.sessionManager.savePersona(persona);
|
|
3197
|
+
this.persona = persona;
|
|
3198
|
+
if (persona.name) {
|
|
3199
|
+
this.identity.name = persona.name;
|
|
3200
|
+
}
|
|
3201
|
+
}
|
|
3202
|
+
getDiscoveredAgents() {
|
|
3203
|
+
return this.agentsManager.getAllAgents();
|
|
3204
|
+
}
|
|
3205
|
+
getSocialChannels() {
|
|
3206
|
+
return this.sessionManager.getAllChannels();
|
|
3207
|
+
}
|
|
3208
|
+
async sendSocialMessage(channelId, content) {
|
|
3209
|
+
const message = {
|
|
3210
|
+
id: crypto.randomUUID(),
|
|
3211
|
+
type: 'ai',
|
|
3212
|
+
content,
|
|
3213
|
+
sender: 'self',
|
|
3214
|
+
timestamp: new Date().toISOString(),
|
|
3215
|
+
agentId: this.identity.did
|
|
3216
|
+
};
|
|
3217
|
+
await this.sessionManager.addMessage(channelId, message);
|
|
3218
|
+
const channels = this.sessionManager.getAllChannels();
|
|
3219
|
+
const channel = channels.find(c => c.id === channelId);
|
|
3220
|
+
if (channel?.peerDid) {
|
|
3221
|
+
const agent = this.agentsManager.getAgent(channel.peerDid);
|
|
3222
|
+
if (agent) {
|
|
3223
|
+
const comm = global.hyperswarmComm;
|
|
3224
|
+
if (comm) {
|
|
3225
|
+
const connections = comm.getConnections?.() || [];
|
|
3226
|
+
for (const conn of connections) {
|
|
3227
|
+
if (conn.publicKey === agent.peerId) {
|
|
3228
|
+
const data = new TextEncoder().encode(`social|${JSON.stringify({ from: this.identity.did, message: content })}`);
|
|
3229
|
+
comm.sendToConnection?.(conn, data);
|
|
3230
|
+
break;
|
|
3231
|
+
}
|
|
3232
|
+
}
|
|
3233
|
+
}
|
|
3234
|
+
}
|
|
3235
|
+
}
|
|
3236
|
+
}
|
|
3237
|
+
async startSocialHeartbeat(config) {
|
|
3238
|
+
if (this.socialHeartbeat) {
|
|
3239
|
+
return;
|
|
3240
|
+
}
|
|
3241
|
+
this.socialHeartbeat = await (0, heartbeat_js_1.createSocialHeartbeat)(this.sessionManager, this.agentsManager, config);
|
|
3242
|
+
this.socialHeartbeat.setOnAgentDiscovered((agent) => {
|
|
3243
|
+
console.log(`[Agent] 发现新智能体: ${agent.name}`);
|
|
3244
|
+
});
|
|
3245
|
+
this.socialHeartbeat.setOnSocialMessage((fromDid, message, channelId) => {
|
|
3246
|
+
console.log(`[Agent] 收到来自 ${fromDid} 的社交消息: ${message.substring(0, 50)}...`);
|
|
3247
|
+
});
|
|
3248
|
+
}
|
|
3249
|
+
stopSocialHeartbeat() {
|
|
3250
|
+
if (this.socialHeartbeat) {
|
|
3251
|
+
this.socialHeartbeat.stop();
|
|
3252
|
+
this.socialHeartbeat = null;
|
|
3253
|
+
}
|
|
3254
|
+
}
|
|
3255
|
+
getSkillRegistry() {
|
|
3256
|
+
return this.skillRegistry;
|
|
3257
|
+
}
|
|
3258
|
+
registerSkill(skill) {
|
|
3259
|
+
this.skillRegistry.register(skill);
|
|
3260
|
+
}
|
|
3261
|
+
async executeSkill(name, params) {
|
|
3262
|
+
return this.skillRegistry.execute(name, params);
|
|
3263
|
+
}
|
|
3264
|
+
async addUserAction(content, importance) {
|
|
3265
|
+
await this.sessionManager.addUserActionToSharedContext(content, importance);
|
|
3266
|
+
}
|
|
3267
|
+
async addSharedKnowledge(knowledge) {
|
|
3268
|
+
await this.sessionManager.addSharedKnowledge(knowledge);
|
|
3269
|
+
}
|
|
3270
|
+
async getRecentActionsSummary(count) {
|
|
3271
|
+
return this.sessionManager.getRecentActionsSummary(count);
|
|
3272
|
+
}
|
|
3273
|
+
async getSharedKnowledge() {
|
|
3274
|
+
return this.sessionManager.getSharedKnowledge();
|
|
3275
|
+
}
|
|
3276
|
+
async getGlobalContextSummary() {
|
|
3277
|
+
return this.sessionManager.getGlobalContextSummary();
|
|
3278
|
+
}
|
|
3279
|
+
async createCooperation(type, task, toAgentId, context) {
|
|
3280
|
+
return this.sessionManager.createCooperation(type, task, toAgentId, context);
|
|
3281
|
+
}
|
|
3282
|
+
async getPendingCooperations() {
|
|
3283
|
+
return this.sessionManager.getPendingCooperations();
|
|
3284
|
+
}
|
|
3285
|
+
async updateCooperationStatus(cooperationId, status, result) {
|
|
3286
|
+
return this.sessionManager.updateCooperationStatus(cooperationId, status, result);
|
|
3287
|
+
}
|
|
3288
|
+
async getAllRegisteredAgents() {
|
|
3289
|
+
return this.sessionManager.getAllRegisteredAgents();
|
|
3290
|
+
}
|
|
3291
|
+
async findAgentByCapability(capability) {
|
|
3292
|
+
return this.sessionManager.findAgentByCapability(capability);
|
|
3293
|
+
}
|
|
3294
|
+
// ==================== Harness Integration ====================
|
|
3295
|
+
operationLog = [];
|
|
3296
|
+
logToHarness(action, args, result) {
|
|
3297
|
+
if (!this.harnessEnabled || !this.harness)
|
|
3298
|
+
return;
|
|
3299
|
+
this.operationLog.push({
|
|
3300
|
+
timestamp: Date.now(),
|
|
3301
|
+
action,
|
|
3302
|
+
args,
|
|
3303
|
+
result,
|
|
3304
|
+
status: result.success ? 'ok' : 'error'
|
|
3305
|
+
});
|
|
3306
|
+
if (this.operationLog.length >= 10) {
|
|
3307
|
+
this.archiveToHarness();
|
|
3308
|
+
}
|
|
3309
|
+
}
|
|
3310
|
+
archiveToHarness() {
|
|
3311
|
+
if (!this.harnessEnabled || !this.harness || this.operationLog.length === 0)
|
|
3312
|
+
return;
|
|
3313
|
+
this.harness.archiveSession(this.operationLog);
|
|
3314
|
+
this.operationLog = [];
|
|
3315
|
+
}
|
|
3316
|
+
getHarnessContext() {
|
|
3317
|
+
if (!this.harnessEnabled || !this.harness) {
|
|
3318
|
+
return 'Harness not available';
|
|
3319
|
+
}
|
|
3320
|
+
return this.harness.getSessionContext();
|
|
3321
|
+
}
|
|
3322
|
+
isHarnessEnabled() {
|
|
3323
|
+
return this.harnessEnabled;
|
|
3324
|
+
}
|
|
3325
|
+
getHarness() {
|
|
3326
|
+
return this.harness;
|
|
3327
|
+
}
|
|
3328
|
+
getOperationLog() {
|
|
3329
|
+
return [...this.operationLog];
|
|
3330
|
+
}
|
|
3331
|
+
}
|
|
3332
|
+
exports.PiAgentSession = PiAgentSession;
|
|
3333
|
+
// createAgentSession / getAgentSession / resetAgentSession / runSelfImproveLoop
|
|
3334
|
+
// 已抽到 ./pi-sdk-session-factory.ts (2026-07-06), 从顶部 import 并 re-export
|
|
3335
|
+
//# sourceMappingURL=pi-sdk.js.map
|