muonroi-cli 1.8.4 → 1.9.0
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/LICENSE +17 -5
- package/dist/packages/agent-harness-core/src/driver.d.ts +27 -1
- package/dist/packages/agent-harness-core/src/driver.js +46 -0
- package/dist/packages/agent-harness-core/src/event-filter.js +11 -0
- package/dist/packages/agent-harness-core/src/event-redact.js +7 -0
- package/dist/packages/agent-harness-core/src/event-tee.d.ts +64 -0
- package/dist/packages/agent-harness-core/src/event-tee.js +104 -0
- package/dist/packages/agent-harness-core/src/mcp-server.d.ts +25 -0
- package/dist/packages/agent-harness-core/src/mcp-server.js +169 -21
- package/dist/packages/agent-harness-core/src/predicate.d.ts +1 -1
- package/dist/packages/agent-harness-core/src/protocol.d.ts +90 -4
- package/dist/packages/agent-harness-core/src/protocol.js +15 -0
- package/dist/packages/agent-harness-core/src/visual-quality.d.ts +58 -0
- package/dist/packages/agent-harness-core/src/visual-quality.js +141 -0
- package/dist/packages/agent-harness-opentui/src/agent-mode.d.ts +6 -0
- package/dist/packages/agent-harness-opentui/src/agent-mode.js +14 -1
- package/dist/packages/agent-harness-opentui/src/input-bridge.d.ts +2 -10
- package/dist/packages/agent-harness-opentui/src/input-bridge.js +103 -16
- package/dist/packages/agent-harness-opentui/src/install.d.ts +8 -0
- package/dist/packages/agent-harness-opentui/src/install.js +10 -0
- package/dist/packages/agent-harness-opentui/src/semantic.js +12 -10
- package/dist/packages/agent-harness-opentui/src/visual-capture.d.ts +56 -0
- package/dist/packages/agent-harness-opentui/src/visual-capture.js +103 -0
- package/dist/src/agent-harness/mock-model.d.ts +38 -0
- package/dist/src/agent-harness/mock-model.js +69 -3
- package/dist/src/agent-harness/test-spawn.js +31 -0
- package/dist/src/chat/chat-keychain.d.ts +7 -12
- package/dist/src/chat/chat-keychain.js +19 -86
- package/dist/src/cli/config/screen-providers.js +1 -1
- package/dist/src/cli/cost-forensics.d.ts +10 -0
- package/dist/src/cli/cost-forensics.js +18 -3
- package/dist/src/cli/keys-bundle.d.ts +1 -1
- package/dist/src/cli/keys-bundle.js +1 -1
- package/dist/src/cli/keys.d.ts +10 -47
- package/dist/src/cli/keys.js +31 -399
- package/dist/src/council/clarifier.d.ts +31 -3
- package/dist/src/council/clarifier.js +220 -32
- package/dist/src/council/context.js +49 -15
- package/dist/src/council/debate-checkpoint.d.ts +129 -0
- package/dist/src/council/debate-checkpoint.js +176 -0
- package/dist/src/council/debate-planner.js +54 -5
- package/dist/src/council/debate-summary.d.ts +25 -0
- package/dist/src/council/debate-summary.js +85 -0
- package/dist/src/council/debate.d.ts +169 -2
- package/dist/src/council/debate.js +1265 -135
- package/dist/src/council/index.d.ts +108 -1
- package/dist/src/council/index.js +670 -197
- package/dist/src/council/leader.d.ts +26 -0
- package/dist/src/council/leader.js +150 -9
- package/dist/src/council/llm.d.ts +94 -0
- package/dist/src/council/llm.js +348 -55
- package/dist/src/council/panel-select.d.ts +30 -0
- package/dist/src/council/panel-select.js +82 -0
- package/dist/src/council/planner.js +40 -0
- package/dist/src/council/preflight.d.ts +17 -0
- package/dist/src/council/preflight.js +50 -2
- package/dist/src/council/prompts.d.ts +39 -4
- package/dist/src/council/prompts.js +256 -69
- package/dist/src/council/stance-recall.d.ts +42 -0
- package/dist/src/council/stance-recall.js +57 -0
- package/dist/src/council/strip-think.d.ts +17 -0
- package/dist/src/council/strip-think.js +33 -0
- package/dist/src/council/types.d.ts +138 -0
- package/dist/src/ee/artifact-cache.d.ts +16 -0
- package/dist/src/ee/artifact-cache.js +32 -0
- package/dist/src/ee/auth.d.ts +20 -0
- package/dist/src/ee/auth.js +54 -2
- package/dist/src/ee/bridge.d.ts +10 -0
- package/dist/src/ee/bridge.js +58 -0
- package/dist/src/ee/client.js +109 -21
- package/dist/src/ee/ee-onboarding.js +6 -26
- package/dist/src/ee/export-transcripts.d.ts +1 -0
- package/dist/src/ee/export-transcripts.js +8 -10
- package/dist/src/ee/extract-session.js +29 -0
- package/dist/src/ee/extract-style.d.ts +58 -0
- package/dist/src/ee/extract-style.js +270 -0
- package/dist/src/ee/recall-ledger.d.ts +9 -0
- package/dist/src/ee/recall-ledger.js +3 -0
- package/dist/src/ee/scope.d.ts +1 -0
- package/dist/src/ee/scope.js +26 -1
- package/dist/src/ee/search.d.ts +7 -0
- package/dist/src/ee/search.js +24 -0
- package/dist/src/ee/transcript-emit.js +2 -0
- package/dist/src/ee/types.d.ts +22 -0
- package/dist/src/ee/who-am-i-brain.d.ts +35 -0
- package/dist/src/ee/who-am-i-brain.js +220 -0
- package/dist/src/ee/who-am-i.d.ts +10 -3
- package/dist/src/ee/who-am-i.js +12 -0
- package/dist/src/ee/workflow-event.d.ts +48 -0
- package/dist/src/ee/workflow-event.js +81 -0
- package/dist/src/flow/compaction/compress.d.ts +3 -3
- package/dist/src/flow/compaction/compress.js +58 -8
- package/dist/src/flow/compaction/extract.d.ts +4 -7
- package/dist/src/flow/compaction/extract.js +50 -10
- package/dist/src/flow/compaction/index.d.ts +14 -1
- package/dist/src/flow/compaction/index.js +96 -3
- package/dist/src/flow/compaction/input-guard.d.ts +24 -0
- package/dist/src/flow/compaction/input-guard.js +43 -0
- package/dist/src/flow/compaction/progress.d.ts +35 -0
- package/dist/src/flow/compaction/progress.js +35 -0
- package/dist/src/flow/fold-planning.d.ts +36 -0
- package/dist/src/flow/fold-planning.js +83 -0
- package/dist/src/flow/hierarchy.d.ts +146 -0
- package/dist/src/flow/hierarchy.js +427 -0
- package/dist/src/flow/index.d.ts +1 -0
- package/dist/src/flow/index.js +2 -0
- package/dist/src/flow/run-artifacts.d.ts +102 -0
- package/dist/src/flow/run-artifacts.js +208 -0
- package/dist/src/generated/version.d.ts +1 -1
- package/dist/src/generated/version.js +1 -1
- package/dist/src/gsd/assessment-schema.d.ts +44 -0
- package/dist/src/gsd/assessment-schema.js +134 -0
- package/dist/src/gsd/capability-registry.d.ts +45 -0
- package/dist/src/gsd/capability-registry.js +337 -0
- package/dist/src/gsd/complexity-assessor.d.ts +39 -0
- package/dist/src/gsd/complexity-assessor.js +152 -0
- package/dist/src/gsd/config-bridge.d.ts +7 -0
- package/dist/src/gsd/config-bridge.js +114 -0
- package/dist/src/gsd/config-loader.d.ts +27 -0
- package/dist/src/gsd/config-loader.js +50 -0
- package/dist/src/gsd/council-context.d.ts +44 -0
- package/dist/src/gsd/council-context.js +114 -0
- package/dist/src/gsd/ee-closure.d.ts +28 -0
- package/dist/src/gsd/ee-closure.js +49 -0
- package/dist/src/gsd/flags.d.ts +66 -0
- package/dist/src/gsd/flags.js +102 -0
- package/dist/src/gsd/gsd-dispatch.d.ts +58 -0
- package/dist/src/gsd/gsd-dispatch.js +131 -0
- package/dist/src/gsd/gsd-runtime.d.ts +22 -0
- package/dist/src/gsd/gsd-runtime.js +37 -0
- package/dist/src/gsd/host-adapter.d.ts +11 -0
- package/dist/src/gsd/host-adapter.js +29 -0
- package/dist/src/gsd/index.d.ts +24 -1
- package/dist/src/gsd/index.js +27 -0
- package/dist/src/gsd/loop-host-contract.d.ts +21 -0
- package/dist/src/gsd/loop-host-contract.js +39 -0
- package/dist/src/gsd/loop-host.d.ts +69 -0
- package/dist/src/gsd/loop-host.js +245 -0
- package/dist/src/gsd/loop-resolver.d.ts +36 -0
- package/dist/src/gsd/loop-resolver.js +79 -0
- package/dist/src/gsd/model-tier.d.ts +13 -0
- package/dist/src/gsd/model-tier.js +45 -0
- package/dist/src/gsd/mutation-gate.d.ts +16 -0
- package/dist/src/gsd/mutation-gate.js +41 -0
- package/dist/src/gsd/native-roadmap.d.ts +89 -0
- package/dist/src/gsd/native-roadmap.js +343 -0
- package/dist/src/gsd/native-state.d.ts +47 -0
- package/dist/src/gsd/native-state.js +220 -0
- package/dist/src/gsd/paths.d.ts +23 -0
- package/dist/src/gsd/paths.js +66 -0
- package/dist/src/gsd/phase-dag.d.ts +12 -0
- package/dist/src/gsd/phase-dag.js +94 -0
- package/dist/src/gsd/phase-sync.d.ts +42 -0
- package/dist/src/gsd/phase-sync.js +321 -0
- package/dist/src/gsd/pil-gate-context.d.ts +13 -0
- package/dist/src/gsd/pil-gate-context.js +64 -0
- package/dist/src/gsd/pil-gate-critic.d.ts +19 -0
- package/dist/src/gsd/pil-gate-critic.js +74 -0
- package/dist/src/gsd/plan-council-prompts.d.ts +25 -0
- package/dist/src/gsd/plan-council-prompts.js +79 -0
- package/dist/src/gsd/plan-council.d.ts +44 -0
- package/dist/src/gsd/plan-council.js +283 -0
- package/dist/src/gsd/plan-gate-vocabulary.d.ts +40 -0
- package/dist/src/gsd/plan-gate-vocabulary.js +64 -0
- package/dist/src/gsd/product-workspace.d.ts +13 -0
- package/dist/src/gsd/product-workspace.js +124 -0
- package/dist/src/gsd/ship-bridge.d.ts +25 -0
- package/dist/src/gsd/ship-bridge.js +65 -0
- package/dist/src/gsd/state-document.d.ts +40 -0
- package/dist/src/gsd/state-document.js +163 -0
- package/dist/src/gsd/verdict-schema.d.ts +39 -0
- package/dist/src/gsd/verdict-schema.js +144 -0
- package/dist/src/gsd/verify-context.d.ts +22 -0
- package/dist/src/gsd/verify-context.js +27 -0
- package/dist/src/gsd/verify-council-prompts.d.ts +19 -0
- package/dist/src/gsd/verify-council-prompts.js +85 -0
- package/dist/src/gsd/verify-council.d.ts +25 -0
- package/dist/src/gsd/verify-council.js +119 -0
- package/dist/src/gsd/verify-gate-vocabulary.d.ts +25 -0
- package/dist/src/gsd/verify-gate-vocabulary.js +46 -0
- package/dist/src/gsd/workflow-engine.d.ts +60 -0
- package/dist/src/gsd/workflow-engine.js +207 -0
- package/dist/src/gsd/workflow-tools.d.ts +13 -0
- package/dist/src/gsd/workflow-tools.js +277 -0
- package/dist/src/headless/council-answers.js +4 -0
- package/dist/src/hooks/index.js +1 -1
- package/dist/src/index.js +172 -270
- package/dist/src/lsp/builtins.js +3 -1
- package/dist/src/lsp/manager.d.ts +5 -1
- package/dist/src/lsp/manager.js +249 -3
- package/dist/src/lsp/npm-cache.d.ts +11 -1
- package/dist/src/lsp/npm-cache.js +17 -1
- package/dist/src/lsp/runtime.d.ts +6 -1
- package/dist/src/lsp/runtime.js +17 -1
- package/dist/src/lsp/types.d.ts +83 -1
- package/dist/src/lsp/types.js +10 -0
- package/dist/src/maintain/pr-builder.js +23 -13
- package/dist/src/mcp/auto-setup.js +57 -32
- package/dist/src/mcp/client-pool.js +44 -16
- package/dist/src/mcp/lsp-tools.d.ts +5 -1
- package/dist/src/mcp/lsp-tools.js +93 -2
- package/dist/src/mcp/mcp-keychain.d.ts +3 -5
- package/dist/src/mcp/mcp-keychain.js +9 -49
- package/dist/src/mcp/research-onboarding.js +8 -7
- package/dist/src/mcp/runtime.js +34 -2
- package/dist/src/mcp/setup-guide-text.d.ts +1 -1
- package/dist/src/mcp/setup-guide-text.js +22 -2
- package/dist/src/mcp/tools-server.d.ts +10 -0
- package/dist/src/mcp/tools-server.js +10 -2
- package/dist/src/models/catalog-client.d.ts +87 -0
- package/dist/src/models/catalog-client.js +105 -38
- package/dist/src/models/catalog.json +528 -265
- package/dist/src/models/registry.d.ts +22 -7
- package/dist/src/models/registry.js +73 -10
- package/dist/src/ops/doctor.js +1 -1
- package/dist/src/orchestrator/ask-user.d.ts +61 -0
- package/dist/src/orchestrator/ask-user.js +65 -0
- package/dist/src/orchestrator/auto-commit.js +1 -1
- package/dist/src/orchestrator/batch-turn-runner.js +2 -2
- package/dist/src/orchestrator/cache-prefix.d.ts +67 -0
- package/dist/src/orchestrator/cache-prefix.js +83 -0
- package/dist/src/orchestrator/compact-request.d.ts +32 -0
- package/dist/src/orchestrator/compact-request.js +41 -0
- package/dist/src/orchestrator/compaction.d.ts +12 -3
- package/dist/src/orchestrator/compaction.js +35 -15
- package/dist/src/orchestrator/council-manager.d.ts +12 -3
- package/dist/src/orchestrator/council-manager.js +74 -32
- package/dist/src/orchestrator/council-request.d.ts +49 -0
- package/dist/src/orchestrator/council-request.js +62 -0
- package/dist/src/orchestrator/cross-turn-dedup.d.ts +6 -0
- package/dist/src/orchestrator/cross-turn-dedup.js +22 -1
- package/dist/src/orchestrator/error-utils.d.ts +29 -0
- package/dist/src/orchestrator/error-utils.js +132 -24
- package/dist/src/orchestrator/grounding-check.js +39 -1
- package/dist/src/orchestrator/interactive-pause.d.ts +26 -0
- package/dist/src/orchestrator/interactive-pause.js +36 -0
- package/dist/src/orchestrator/message-processor.d.ts +4 -0
- package/dist/src/orchestrator/message-processor.js +268 -41
- package/dist/src/orchestrator/orchestrator.d.ts +64 -3
- package/dist/src/orchestrator/orchestrator.js +823 -120
- package/dist/src/orchestrator/preprocessor.js +3 -3
- package/dist/src/orchestrator/proactive-compact-detector.d.ts +26 -0
- package/dist/src/orchestrator/proactive-compact-detector.js +36 -0
- package/dist/src/orchestrator/prompts.js +17 -17
- package/dist/src/orchestrator/reactive-delegation.d.ts +39 -0
- package/dist/src/orchestrator/reactive-delegation.js +59 -0
- package/dist/src/orchestrator/retry-classifier.d.ts +2 -1
- package/dist/src/orchestrator/retry-classifier.js +46 -2
- package/dist/src/orchestrator/safety-askcard.d.ts +1 -1
- package/dist/src/orchestrator/safety-askcard.js +5 -2
- package/dist/src/orchestrator/safety-intercept.d.ts +50 -0
- package/dist/src/orchestrator/safety-intercept.js +62 -0
- package/dist/src/orchestrator/scope-reminder.js +1 -1
- package/dist/src/orchestrator/session-experience.d.ts +2 -1
- package/dist/src/orchestrator/session-experience.js +2 -1
- package/dist/src/orchestrator/should-run-gate.d.ts +5 -0
- package/dist/src/orchestrator/should-run-gate.js +18 -0
- package/dist/src/orchestrator/stall-watchdog.d.ts +31 -3
- package/dist/src/orchestrator/stall-watchdog.js +65 -10
- package/dist/src/orchestrator/stream-runner.d.ts +13 -3
- package/dist/src/orchestrator/stream-runner.js +115 -49
- package/dist/src/orchestrator/sub-agent-cap.d.ts +13 -1
- package/dist/src/orchestrator/sub-agent-cap.js +16 -1
- package/dist/src/orchestrator/sub-agent-model-tier.d.ts +13 -1
- package/dist/src/orchestrator/sub-agent-model-tier.js +16 -2
- package/dist/src/orchestrator/subagent-compactor.d.ts +53 -1
- package/dist/src/orchestrator/subagent-compactor.js +126 -15
- package/dist/src/orchestrator/tool-engine.d.ts +41 -0
- package/dist/src/orchestrator/tool-engine.js +846 -66
- package/dist/src/orchestrator/tool-limit-auto-recover.d.ts +22 -0
- package/dist/src/orchestrator/tool-limit-auto-recover.js +30 -0
- package/dist/src/orchestrator/turn-runner-deps.d.ts +19 -0
- package/dist/src/orchestrator/turn-watchdog.d.ts +44 -0
- package/dist/src/orchestrator/turn-watchdog.js +84 -0
- package/dist/src/pil/agent-operating-contract.d.ts +1 -1
- package/dist/src/pil/agent-operating-contract.js +6 -4
- package/dist/src/pil/cheap-model-playbook.d.ts +1 -1
- package/dist/src/pil/cheap-model-playbook.js +5 -1
- package/dist/src/pil/cheap-model-workbooks.d.ts +1 -1
- package/dist/src/pil/cheap-model-workbooks.js +1 -1
- package/dist/src/pil/discovery-types.d.ts +1 -0
- package/dist/src/pil/discovery.d.ts +1 -1
- package/dist/src/pil/discovery.js +18 -13
- package/dist/src/pil/layer1-intent.d.ts +18 -6
- package/dist/src/pil/layer1-intent.js +66 -757
- package/dist/src/pil/layer15-context-scan.js +15 -1
- package/dist/src/pil/layer1_5-complexity-size.d.ts +7 -0
- package/dist/src/pil/layer1_5-complexity-size.js +31 -5
- package/dist/src/pil/layer3-ee-injection.js +23 -8
- package/dist/src/pil/layer4-gsd.js +69 -16
- package/dist/src/pil/layer5-context.js +7 -3
- package/dist/src/pil/layer6-output.d.ts +23 -0
- package/dist/src/pil/layer6-output.js +5 -1
- package/dist/src/pil/llm-classify.d.ts +111 -5
- package/dist/src/pil/llm-classify.js +421 -189
- package/dist/src/pil/native-capabilities-workbook.d.ts +1 -1
- package/dist/src/pil/native-capabilities-workbook.js +8 -0
- package/dist/src/pil/pipeline.js +36 -2
- package/dist/src/pil/repo-grounding-probe.d.ts +15 -0
- package/dist/src/pil/repo-grounding-probe.js +136 -0
- package/dist/src/pil/repo-structure-hints.d.ts +7 -0
- package/dist/src/pil/repo-structure-hints.js +45 -0
- package/dist/src/pil/response-tools.js +5 -3
- package/dist/src/pil/schema.d.ts +1 -0
- package/dist/src/pil/schema.js +2 -0
- package/dist/src/pil/types.d.ts +18 -0
- package/dist/src/playbook/directives.d.ts +4 -0
- package/dist/src/playbook/directives.js +17 -5
- package/dist/src/product-loop/artifact-io.js +4 -0
- package/dist/src/product-loop/backlog-builder.d.ts +14 -1
- package/dist/src/product-loop/backlog-builder.js +30 -6
- package/dist/src/product-loop/criteria-seed.d.ts +51 -0
- package/dist/src/product-loop/criteria-seed.js +200 -0
- package/dist/src/product-loop/discovery-context-format.js +3 -1
- package/dist/src/product-loop/discovery-ecosystem.js +4 -1
- package/dist/src/product-loop/discovery-interview.d.ts +9 -0
- package/dist/src/product-loop/discovery-interview.js +60 -12
- package/dist/src/product-loop/discovery-recommender.js +2 -1
- package/dist/src/product-loop/discovery-schema.js +19 -2
- package/dist/src/product-loop/discovery-triage.d.ts +23 -0
- package/dist/src/product-loop/discovery-triage.js +109 -0
- package/dist/src/product-loop/gather.js +150 -2
- package/dist/src/product-loop/ideal-trace.d.ts +7 -0
- package/dist/src/product-loop/ideal-trace.js +64 -0
- package/dist/src/product-loop/index.d.ts +13 -1
- package/dist/src/product-loop/index.js +340 -52
- package/dist/src/product-loop/loop-driver.d.ts +7 -0
- package/dist/src/product-loop/loop-driver.js +330 -106
- package/dist/src/product-loop/phase-plan.d.ts +21 -0
- package/dist/src/product-loop/phase-plan.js +81 -6
- package/dist/src/product-loop/phase-rituals.d.ts +3 -0
- package/dist/src/product-loop/phase-rituals.js +8 -3
- package/dist/src/product-loop/phase-runner.js +39 -12
- package/dist/src/product-loop/plan-adherence-review.d.ts +26 -0
- package/dist/src/product-loop/plan-adherence-review.js +144 -0
- package/dist/src/product-loop/sprint-runner.d.ts +173 -0
- package/dist/src/product-loop/sprint-runner.js +863 -19
- package/dist/src/product-loop/types.d.ts +61 -5
- package/dist/src/providers/adapter.d.ts +1 -1
- package/dist/src/providers/adapter.js +3 -4
- package/dist/src/providers/anthropic.d.ts +9 -8
- package/dist/src/providers/anthropic.js +13 -47
- package/dist/src/providers/auth/browser-flow.d.ts +1 -1
- package/dist/src/providers/auth/browser-flow.js +1 -1
- package/dist/src/providers/auth/grok-oauth.d.ts +1 -0
- package/dist/src/providers/auth/grok-oauth.js +30 -5
- package/dist/src/providers/auth/openai-oauth.d.ts +1 -0
- package/dist/src/providers/auth/openai-oauth.js +15 -1
- package/dist/src/providers/auth/registry.js +0 -34
- package/dist/src/providers/auth/token-store.d.ts +9 -9
- package/dist/src/providers/auth/token-store.js +8 -67
- package/dist/src/providers/auth/types.d.ts +9 -1
- package/dist/src/providers/auth/types.js +1 -1
- package/dist/src/providers/capabilities.d.ts +24 -5
- package/dist/src/providers/capabilities.js +42 -24
- package/dist/src/providers/endpoints.d.ts +2 -2
- package/dist/src/providers/endpoints.js +11 -10
- package/dist/src/providers/env-store.d.ts +17 -0
- package/dist/src/providers/env-store.js +228 -0
- package/dist/src/providers/keychain.d.ts +22 -18
- package/dist/src/providers/keychain.js +127 -140
- package/dist/src/providers/mcp-vision-bridge.js +56 -146
- package/dist/src/providers/openai-compatible.js +8 -1
- package/dist/src/providers/pricing.d.ts +2 -2
- package/dist/src/providers/pricing.js +3 -13
- package/dist/src/providers/runtime.d.ts +43 -3
- package/dist/src/providers/runtime.js +88 -14
- package/dist/src/providers/strategies/base.strategy.d.ts +16 -0
- package/dist/src/providers/strategies/base.strategy.js +24 -1
- package/dist/src/providers/strategies/{siliconflow.strategy.d.ts → opencode-go.strategy.d.ts} +3 -3
- package/dist/src/providers/strategies/opencode-go.strategy.js +83 -0
- package/dist/src/providers/strategies/registry.js +4 -4
- package/dist/src/providers/strategies/thinking-mode.d.ts +109 -0
- package/dist/src/providers/strategies/thinking-mode.js +288 -1
- package/dist/src/providers/strategies/xai.strategy.js +27 -0
- package/dist/src/providers/strategies/zai.strategy.d.ts +14 -0
- package/dist/src/providers/strategies/zai.strategy.js +44 -0
- package/dist/src/providers/types.d.ts +5 -6
- package/dist/src/providers/types.js +2 -2
- package/dist/src/providers/vision-backend.d.ts +47 -0
- package/dist/src/providers/vision-backend.js +258 -0
- package/dist/src/providers/vision-proxy.d.ts +22 -9
- package/dist/src/providers/vision-proxy.js +63 -132
- package/dist/src/providers/warm.d.ts +65 -0
- package/dist/src/providers/warm.js +145 -0
- package/dist/src/providers/wire-debug.js +95 -0
- package/dist/src/router/decide.d.ts +13 -0
- package/dist/src/router/decide.js +138 -36
- package/dist/src/router/peak-hour.d.ts +38 -0
- package/dist/src/router/peak-hour.js +107 -0
- package/dist/src/router/step-router.js +3 -2
- package/dist/src/router/warm.js +4 -5
- package/dist/src/scaffold/continuation-prompt.d.ts +11 -0
- package/dist/src/scaffold/continuation-prompt.js +26 -0
- package/dist/src/scaffold/point-to-existing.d.ts +21 -0
- package/dist/src/scaffold/point-to-existing.js +25 -0
- package/dist/src/self-qa/agentic-loop.js +6 -5
- package/dist/src/{ui/state → state}/active-run.d.ts +19 -0
- package/dist/src/{ui/state → state}/active-run.js +21 -0
- package/dist/src/{ui/status-bar/store.d.ts → state/status-bar-store.d.ts} +4 -1
- package/dist/src/{ui/status-bar/store.js → state/status-bar-store.js} +12 -9
- package/dist/src/state/turn-trace.d.ts +43 -0
- package/dist/src/state/turn-trace.js +32 -0
- package/dist/src/storage/db.js +2 -1
- package/dist/src/storage/index.d.ts +1 -1
- package/dist/src/storage/index.js +1 -1
- package/dist/src/storage/interaction-log.d.ts +1 -1
- package/dist/src/storage/migrations.js +71 -1
- package/dist/src/storage/sessions.d.ts +28 -10
- package/dist/src/storage/sessions.js +78 -21
- package/dist/src/storage/transcript-view.js +1 -1
- package/dist/src/storage/transcript.d.ts +51 -0
- package/dist/src/storage/transcript.js +340 -15
- package/dist/src/tools/file.d.ts +15 -0
- package/dist/src/tools/file.js +32 -0
- package/dist/src/tools/git-safety.d.ts +19 -0
- package/dist/src/tools/git-safety.js +168 -0
- package/dist/src/tools/native-tools.d.ts +1 -1
- package/dist/src/tools/native-tools.js +81 -1
- package/dist/src/tools/registry.d.ts +20 -0
- package/dist/src/tools/registry.js +576 -23
- package/dist/src/tools/research.d.ts +29 -0
- package/dist/src/tools/research.js +233 -0
- package/dist/src/types/index.d.ts +147 -4
- package/dist/src/ui/app.js +0 -0
- package/dist/src/ui/cards/product-status-card.js +1 -1
- package/dist/src/ui/components/agent-rail-activities.d.ts +26 -0
- package/dist/src/ui/components/agent-rail-activities.js +47 -0
- package/dist/src/ui/components/bubble-body-guard.d.ts +27 -0
- package/dist/src/ui/components/bubble-body-guard.js +50 -0
- package/dist/src/ui/components/compact-progress-card.d.ts +24 -0
- package/dist/src/ui/components/compact-progress-card.js +42 -0
- package/dist/src/ui/components/context-rail.d.ts +26 -0
- package/dist/src/ui/components/context-rail.js +33 -0
- package/dist/src/ui/components/council-conclusion-card.d.ts +73 -0
- package/dist/src/ui/components/council-conclusion-card.js +420 -0
- package/dist/src/ui/components/council-debate-pill.d.ts +36 -0
- package/dist/src/ui/components/council-debate-pill.js +34 -0
- package/dist/src/ui/components/council-info-card.js +2 -2
- package/dist/src/ui/components/council-leader-bubble.d.ts +10 -3
- package/dist/src/ui/components/council-leader-bubble.js +21 -11
- package/dist/src/ui/components/council-message-bubble.d.ts +15 -2
- package/dist/src/ui/components/council-message-bubble.js +16 -15
- package/dist/src/ui/components/council-phase-timeline.d.ts +9 -1
- package/dist/src/ui/components/council-phase-timeline.js +66 -17
- package/dist/src/ui/components/council-placeholder-bubble.d.ts +16 -9
- package/dist/src/ui/components/council-placeholder-bubble.js +32 -29
- package/dist/src/ui/components/council-question-card.js +13 -12
- package/dist/src/ui/components/council-rail-rounds.d.ts +26 -0
- package/dist/src/ui/components/council-rail-rounds.js +57 -0
- package/dist/src/ui/components/council-round-group.d.ts +38 -0
- package/dist/src/ui/components/council-round-group.js +88 -0
- package/dist/src/ui/components/council-status-list.d.ts +3 -1
- package/dist/src/ui/components/council-status-list.js +36 -24
- package/dist/src/ui/components/council-synthesis-banner.d.ts +7 -2
- package/dist/src/ui/components/council-synthesis-banner.js +20 -5
- package/dist/src/ui/components/halt-recovery-card.js +9 -5
- package/dist/src/ui/components/jump-to-latest-pill.d.ts +11 -0
- package/dist/src/ui/components/jump-to-latest-pill.js +14 -0
- package/dist/src/ui/components/message-view.d.ts +15 -0
- package/dist/src/ui/components/message-view.js +50 -1
- package/dist/src/ui/components/prompt-box.js +18 -16
- package/dist/src/ui/components/session-tree-card.d.ts +14 -0
- package/dist/src/ui/components/session-tree-card.js +46 -0
- package/dist/src/ui/components/slash-inline-menu.d.ts +12 -0
- package/dist/src/ui/components/slash-inline-menu.js +26 -5
- package/dist/src/ui/components/task-list-panel.d.ts +14 -1
- package/dist/src/ui/components/task-list-panel.js +22 -2
- package/dist/src/ui/components/tool-group.d.ts +15 -3
- package/dist/src/ui/components/tool-group.js +69 -11
- package/dist/src/ui/containers/modals-layer.d.ts +4 -2
- package/dist/src/ui/containers/modals-layer.js +2 -2
- package/dist/src/ui/council-harness-event.d.ts +57 -0
- package/dist/src/ui/council-harness-event.js +46 -0
- package/dist/src/ui/heartbeat-debug.d.ts +29 -0
- package/dist/src/ui/heartbeat-debug.js +45 -0
- package/dist/src/ui/hooks/use-session-picker.d.ts +3 -3
- package/dist/src/ui/mcp-modal.js +2 -4
- package/dist/src/ui/modals/api-key-modal.js +1 -1
- package/dist/src/ui/modals/connect-modal.js +4 -3
- package/dist/src/ui/modals/model-picker-modal.d.ts +8 -18
- package/dist/src/ui/modals/model-picker-modal.js +8 -10
- package/dist/src/ui/modals/session-picker-modal.d.ts +2 -2
- package/dist/src/ui/modals/session-picker-modal.js +3 -5
- package/dist/src/ui/picker-providers.d.ts +1 -1
- package/dist/src/ui/picker-providers.js +1 -1
- package/dist/src/ui/primitives/index.d.ts +1 -0
- package/dist/src/ui/primitives/index.js +2 -0
- package/dist/src/ui/primitives/semantic-primitives.d.ts +76 -0
- package/dist/src/ui/primitives/semantic-primitives.js +81 -0
- package/dist/src/ui/slash/compact.js +5 -7
- package/dist/src/ui/slash/cost.js +1 -1
- package/dist/src/ui/slash/council.js +19 -1
- package/dist/src/ui/slash/debug.d.ts +3 -31
- package/dist/src/ui/slash/debug.js +9 -20
- package/dist/src/ui/slash/ee.js +81 -0
- package/dist/src/ui/slash/ideal.d.ts +6 -2
- package/dist/src/ui/slash/ideal.js +97 -7
- package/dist/src/ui/slash/menu-items.d.ts +7 -0
- package/dist/src/ui/slash/menu-items.js +23 -20
- package/dist/src/ui/slash/registry.d.ts +2 -0
- package/dist/src/ui/slash/registry.js +4 -0
- package/dist/src/ui/status-bar/cache-hit.d.ts +6 -0
- package/dist/src/ui/status-bar/cache-hit.js +9 -0
- package/dist/src/ui/status-bar/index.d.ts +1 -1
- package/dist/src/ui/status-bar/index.js +7 -3
- package/dist/src/ui/status-bar/usd-meter.d.ts +5 -4
- package/dist/src/ui/status-bar/usd-meter.js +6 -4
- package/dist/src/ui/theme.d.ts +1 -0
- package/dist/src/ui/theme.js +2 -0
- package/dist/src/ui/types.d.ts +7 -0
- package/dist/src/ui/use-app-logic.js +0 -0
- package/dist/src/ui/utils/agent-activities.d.ts +39 -0
- package/dist/src/ui/utils/agent-activities.js +96 -0
- package/dist/src/ui/utils/format.d.ts +14 -0
- package/dist/src/ui/utils/format.js +23 -3
- package/dist/src/ui/utils/group-tool-entries.d.ts +26 -0
- package/dist/src/ui/utils/group-tool-entries.js +111 -0
- package/dist/src/ui/utils/tool-summary.d.ts +21 -0
- package/dist/src/ui/utils/tool-summary.js +91 -0
- package/dist/src/usage/downgrade.js +2 -2
- package/dist/src/usage/product-ledger.js +2 -2
- package/dist/src/utils/event-loop-monitor.d.ts +85 -0
- package/dist/src/utils/event-loop-monitor.js +107 -0
- package/dist/src/utils/install-manager.js +2 -1
- package/dist/src/utils/llm-deadline.d.ts +14 -0
- package/dist/src/utils/llm-deadline.js +19 -0
- package/dist/src/utils/logger.js +2 -2
- package/dist/src/utils/loop-profiler.d.ts +102 -0
- package/dist/src/utils/loop-profiler.js +202 -0
- package/dist/src/utils/permission-mode.js +5 -3
- package/dist/src/utils/redactor.js +1 -1
- package/dist/src/utils/settings.d.ts +180 -5
- package/dist/src/utils/settings.js +271 -31
- package/dist/src/utils/side-question.d.ts +1 -2
- package/dist/src/utils/side-question.js +2 -2
- package/dist/src/utils/visible-retry.d.ts +11 -0
- package/dist/src/utils/visible-retry.js +10 -1
- package/dist/src/verify/entrypoint.d.ts +1 -1
- package/dist/src/verify/entrypoint.js +52 -17
- package/dist/src/verify/orchestrator.d.ts +1 -1
- package/dist/src/verify/orchestrator.js +20 -3
- package/dist/src/verify/recipes.d.ts +13 -0
- package/dist/src/verify/recipes.js +15 -0
- package/package.json +134 -132
- package/dist/src/cli/bw-vault.d.ts +0 -55
- package/dist/src/cli/bw-vault.js +0 -133
- package/dist/src/mcp/ee-tools.d.ts +0 -46
- package/dist/src/mcp/ee-tools.js +0 -193
- package/dist/src/providers/auth/gcloud.d.ts +0 -28
- package/dist/src/providers/auth/gcloud.js +0 -102
- package/dist/src/providers/auth/gemini-oauth.d.ts +0 -82
- package/dist/src/providers/auth/gemini-oauth.js +0 -472
- package/dist/src/providers/gemini.d.ts +0 -11
- package/dist/src/providers/gemini.js +0 -45
- package/dist/src/providers/siliconflow-sse-repair.d.ts +0 -58
- package/dist/src/providers/siliconflow-sse-repair.js +0 -177
- package/dist/src/providers/strategies/google.strategy.d.ts +0 -22
- package/dist/src/providers/strategies/google.strategy.js +0 -174
- package/dist/src/providers/strategies/siliconflow.strategy.js +0 -29
- package/dist/src/ui/containers/chat-feed.d.ts +0 -40
- package/dist/src/ui/containers/chat-feed.js +0 -66
|
@@ -48,43 +48,52 @@
|
|
|
48
48
|
// - A4 (tool_call write-ahead) — persistToolCallWriteAhead
|
|
49
49
|
// - A5 (message_seq write-ahead) — persistMessageWriteAhead
|
|
50
50
|
// - O1 (providerOptions shape forensics) — extractProviderOptionsShape
|
|
51
|
-
// -
|
|
51
|
+
// - reasoning-strip (provider quirk) — turnCaps.sanitizeHistory
|
|
52
52
|
import { generateText, stepCountIs, streamText } from "ai";
|
|
53
|
+
import { getEffectiveCouncilRoleCount } from "../council/leader.js";
|
|
53
54
|
import { recordArtifact } from "../ee/artifact-cache.js";
|
|
54
55
|
import { routeFeedback } from "../ee/bridge.js";
|
|
55
56
|
import { getDefaultEEClient } from "../ee/intercept.js";
|
|
57
|
+
import { isGsdHardGateEnabled } from "../gsd/flags.js";
|
|
58
|
+
import { evaluateMutationGate } from "../gsd/mutation-gate.js";
|
|
56
59
|
import { acquireMcpTools } from "../mcp/client-pool.js";
|
|
57
60
|
import { dropRedundantFsMcpTools, filterMcpServersByMessage } from "../mcp/smart-filter.js";
|
|
58
|
-
import { getModelInfo } from "../models/registry.js";
|
|
61
|
+
import { getModelInfo, isReasoningModel } from "../models/registry.js";
|
|
59
62
|
import { cheapModelShellLine, injectCheapModelPlaybook, injectCheapModelShellDirective, shouldInjectCheapModelPlaybook, } from "../pil/cheap-model-playbook.js";
|
|
60
63
|
import { injectCheapModelWorkbook, shouldInjectCheapModelWorkbook } from "../pil/cheap-model-workbooks.js";
|
|
61
64
|
import { getResponseTaskType, isResponseTool, normalizeStructuredResponseTaskType, shouldHaltOnResponseTool, } from "../pil/index.js";
|
|
62
|
-
import { isMetaAnalysisPrompt } from "../pil/layer6-output.js";
|
|
65
|
+
import { isMetaAnalysisPrompt, isSprintPlanExecution } from "../pil/layer6-output.js";
|
|
63
66
|
import { taskTypeToMaxTokens, taskTypeToReasoningEffort, taskTypeToTier } from "../pil/task-tier-map.js";
|
|
64
67
|
import { mentionsEcosystemScope } from "../playbook/directives.js";
|
|
65
68
|
import { getProviderCapabilities } from "../providers/capabilities.js";
|
|
66
69
|
import { bridgeMcpToolResult, listCachedImages, scrubImagePayloadsInMessages, } from "../providers/mcp-vision-bridge.js";
|
|
67
70
|
import { captureToolSchemas } from "../providers/patch-zod-schema.js";
|
|
68
|
-
import { buildTurnProviderOptions, requireRuntimeProvider, shouldDropParam, } from "../providers/runtime.js";
|
|
71
|
+
import { buildTurnProviderOptions, requireRuntimeProvider, resolveTemperatureParam, shouldDropParam, } from "../providers/runtime.js";
|
|
69
72
|
import { wireDebug } from "../providers/wire-debug.js";
|
|
70
73
|
import { reportRouteOutcome } from "../router/decide.js";
|
|
74
|
+
import { statusBarStore } from "../state/status-bar-store.js";
|
|
75
|
+
import { recordTurnTrace } from "../state/turn-trace.js";
|
|
71
76
|
import { getNextMessageSequence, logInteraction, markMessageErrored, markToolCallErrored, persistToolCallWriteAhead, } from "../storage/index.js";
|
|
72
77
|
import { persistSessionExperience } from "../storage/session-experience-store.js";
|
|
73
78
|
import { createBuiltinTools } from "../tools/registry.js";
|
|
74
79
|
import { snapshotFromTodoWriteArgs } from "../tools/todo-write-snapshot.js";
|
|
75
80
|
import { visionToolsNeeded } from "../tools/vision-gate.js";
|
|
76
|
-
import { recordTurnTrace } from "../ui/slash/debug.js";
|
|
77
|
-
import { statusBarStore } from "../ui/status-bar/store.js";
|
|
78
81
|
import { appendDecisionLog } from "../usage/decision-log.js";
|
|
82
|
+
import { setLoopBreadcrumb } from "../utils/event-loop-monitor.js";
|
|
79
83
|
import { logger } from "../utils/logger.js";
|
|
80
84
|
import { openUrl } from "../utils/open-url.js";
|
|
81
85
|
import { appendAudit, toolNeedsApproval } from "../utils/permission-mode.js";
|
|
82
|
-
import { getAutoCouncilConfidence, getAutoCouncilMinRoles, getProviderStallRetries, getProviderStallTimeoutMs,
|
|
86
|
+
import { getAutoCouncilConfidence, getAutoCouncilMinRoles, getProviderProgressTimeoutMs, getProviderStallRetries, getProviderStallTimeoutMs, getSteerInjectionEnabled, getTopLevelCompactHysteresis, getTopLevelCompactKeepLast, getTopLevelCompactTailBudgetChars, getTopLevelCompactThresholdChars, getTopLevelToolBudgetChars, isAutoCouncilClarifyEnabled, isAutoCouncilEnabled, loadMcpServers, } from "../utils/settings.js";
|
|
87
|
+
import { isAutoCouncilSkipReasoning } from "../utils/settings.js";
|
|
83
88
|
import { resolveShell } from "../utils/shell.js";
|
|
89
|
+
import { foldDynamicTailIntoUserMessage, splitFrontAndDynamicTail } from "./cache-prefix.js";
|
|
90
|
+
import { consumeProactiveCompact } from "./compact-request.js";
|
|
84
91
|
import { relaxCompactionSettings } from "./compaction.js";
|
|
92
|
+
import { consumeCouncilConvene, hasPendingCouncilConvene, peekCouncilConveneToolCallId } from "./council-request.js";
|
|
85
93
|
import { wrapToolSetWithDedup } from "./cross-turn-dedup.js";
|
|
86
94
|
import { humanizeApiError, isAuthenticationError, isContextLimitError, summarizeApiErrorForLog } from "./error-utils.js";
|
|
87
95
|
import { buildGroundingFootnote, findUnverifiedClaims } from "./grounding-check.js";
|
|
96
|
+
import { isInteractivePaused } from "./interactive-pause.js";
|
|
88
97
|
import { buildInterruptedTurnNote } from "./interrupted-turn.js";
|
|
89
98
|
import { stableCallId } from "./pending-calls.js";
|
|
90
99
|
import { buildMcpCapabilityBlock, MAX_LLM_CALLS_PER_TURN, } from "./prompts.js";
|
|
@@ -94,6 +103,7 @@ import { containsEncryptedReasoning, sanitizeModelMessages } from "./reasoning.j
|
|
|
94
103
|
import { repairToolCallHook } from "./repair-tool-call.js";
|
|
95
104
|
import { buildRepetitionReminder, recordAssistantBurst, shouldInjectRepetitionReminder, } from "./repetition-detector.js";
|
|
96
105
|
import { classifyStreamError } from "./retry-classifier.js";
|
|
106
|
+
import { parseSafetyBlock, shouldAutoAllowYolo } from "./safety-intercept.js";
|
|
97
107
|
import { forcedFinalize, incSessionStep, } from "./scope-ceiling.js";
|
|
98
108
|
import { attachReminderToMessages, buildCheckpointReminder, buildScopeReminder, cadenceForSize, shouldInjectCeilingCrossing, shouldInjectReminder, shouldInjectSoftWarn, shouldPreWarnCompaction, } from "./scope-reminder.js";
|
|
99
109
|
import { formatElisionManifest, getSessionExperienceCounts, recordCompaction, recordElision, } from "./session-experience.js";
|
|
@@ -101,10 +111,35 @@ import { attemptStallRescue, pushStallToolResult } from "./stall-rescue.js";
|
|
|
101
111
|
import { createStallWatchdog, STALL_ERROR_MESSAGE, shouldContinueAfterMidLoopStall, shouldRepromptStall, stallRepromptBackoffMs, } from "./stall-watchdog.js";
|
|
102
112
|
import { planSteerInjection } from "./steer-inbox.js";
|
|
103
113
|
import { wrapToolSetWithCap } from "./sub-agent-cap.js";
|
|
104
|
-
import { applyAnthropicPromptCaching, compactSubAgentMessages, cumulativeMessageChars } from "./subagent-compactor.js";
|
|
114
|
+
import { applyAnthropicPromptCaching, applyCompactionHysteresis, compactSubAgentMessages, cumulativeMessageChars, initCompactionHysteresisState, } from "./subagent-compactor.js";
|
|
105
115
|
import { detectTextEmittedToolCall, parseDsmlToolCalls } from "./text-tool-call-detector.js";
|
|
116
|
+
import { getToolLimitAutoRecoverCap, shouldAutoRecoverToolLimit } from "./tool-limit-auto-recover.js";
|
|
106
117
|
import { createToolLoopCapPredicate } from "./tool-loop-cap.js";
|
|
107
118
|
import { buildToolRepetitionAbortMessage, recordToolError as recordToolRepetitionError, recordToolSuccess as recordToolRepetitionSuccess, } from "./tool-repetition-detector.js";
|
|
119
|
+
/**
|
|
120
|
+
* Resolve the per-turn `maxOutputTokens` budget.
|
|
121
|
+
*
|
|
122
|
+
* Normally the budget is derived from the PIL-classified `taskType`
|
|
123
|
+
* (`taskTypeToMaxTokens`). But a sprint IMPLEMENTATION turn — the /ideal
|
|
124
|
+
* loop's handoff into the host orchestrator via `processMessageFn`, marked
|
|
125
|
+
* with `SPRINT_EXECUTION_MARKER` — is a KNOWN code-writing task that must not
|
|
126
|
+
* be starved by a noisy classify. Observed live (2026-07-10, gsd-core
|
|
127
|
+
* migration): the impl prompt was classified `analyze`/default → capped at
|
|
128
|
+
* 4_096 output → the model spent the whole budget narrating its plan, hit
|
|
129
|
+
* `finishReason:"length"` mid-word, produced ZERO code, and the turn wedged.
|
|
130
|
+
*
|
|
131
|
+
* Fix: for a sprint-execution turn, floor the budget at the build/generate
|
|
132
|
+
* tier (12_288) regardless of the classified type. Scoped to the marker only
|
|
133
|
+
* (NOT the broad `isImplementationIntent`) so ordinary refactor/debug turns
|
|
134
|
+
* keep their intentionally tighter L6 budgets.
|
|
135
|
+
*/
|
|
136
|
+
export function resolveTurnMaxOutputTokens(pilCtx) {
|
|
137
|
+
const base = taskTypeToMaxTokens(pilCtx.taskType);
|
|
138
|
+
if (isSprintPlanExecution(pilCtx.raw ?? "")) {
|
|
139
|
+
return Math.max(base, taskTypeToMaxTokens("build"));
|
|
140
|
+
}
|
|
141
|
+
return base;
|
|
142
|
+
}
|
|
108
143
|
/**
|
|
109
144
|
* F2 — approximate the char cost of the FIXED prompt envelope (system +
|
|
110
145
|
* tools JSON-Schema) that streamText re-sends on every step. Used to feed
|
|
@@ -193,6 +228,38 @@ export function rewriteSafetyApprovedToolResults(messages) {
|
|
|
193
228
|
return changed ? { ...m, content: newContent } : m;
|
|
194
229
|
});
|
|
195
230
|
}
|
|
231
|
+
/**
|
|
232
|
+
* Replace the `result` value of a single tool-result part (matched by
|
|
233
|
+
* toolCallId) in an AI-SDK message history, in place, preserving the
|
|
234
|
+
* tool-call/tool-result pairing. Used by the convene_council path to splice the
|
|
235
|
+
* council synthesis into the placeholder tool_result the model saw, so on the
|
|
236
|
+
* restarted step the model reads the conclusion as that tool's result. Pure +
|
|
237
|
+
* exported for unit testing. Returns the same array reference untouched when the
|
|
238
|
+
* toolCallId is absent (with a caller-visible `replaced` flag).
|
|
239
|
+
*/
|
|
240
|
+
export function spliceConveneToolResult(messages, toolCallId, value) {
|
|
241
|
+
if (!toolCallId)
|
|
242
|
+
return { messages, replaced: false };
|
|
243
|
+
let replaced = false;
|
|
244
|
+
const out = messages.map((m) => {
|
|
245
|
+
if (m.role !== "tool" || !Array.isArray(m.content))
|
|
246
|
+
return m;
|
|
247
|
+
let changed = false;
|
|
248
|
+
const newContent = m.content.map((part) => {
|
|
249
|
+
if (part?.type === "tool-result" && part?.toolCallId === toolCallId) {
|
|
250
|
+
changed = true;
|
|
251
|
+
replaced = true;
|
|
252
|
+
// AI SDK v6 tool-result parts carry the value under `output` (typed) or
|
|
253
|
+
// `result` (legacy). Set both so whichever the provider serializer reads
|
|
254
|
+
// sees the synthesis, and clear any error flag.
|
|
255
|
+
return { ...part, isError: false, output: value, result: value };
|
|
256
|
+
}
|
|
257
|
+
return part;
|
|
258
|
+
});
|
|
259
|
+
return changed ? { ...m, content: newContent } : m;
|
|
260
|
+
});
|
|
261
|
+
return { messages: replaced ? out : messages, replaced };
|
|
262
|
+
}
|
|
196
263
|
export class SimpleMutex {
|
|
197
264
|
queue = Promise.resolve();
|
|
198
265
|
async run(fn) {
|
|
@@ -212,9 +279,57 @@ export class SimpleMutex {
|
|
|
212
279
|
}
|
|
213
280
|
}
|
|
214
281
|
import { stripDsmlMarkup } from "./message-processor.js";
|
|
282
|
+
/** Tools that produce zero side-effects — safe for Q&A-only (direct-answer) mode. */
|
|
283
|
+
function stripWriteTools(tools) {
|
|
284
|
+
const readonly = new Set([
|
|
285
|
+
"read_file",
|
|
286
|
+
"grep",
|
|
287
|
+
"bash_output_get",
|
|
288
|
+
"process_list",
|
|
289
|
+
"delegation_read",
|
|
290
|
+
"delegation_list",
|
|
291
|
+
"ee_query",
|
|
292
|
+
"ee_health",
|
|
293
|
+
"usage_forensics",
|
|
294
|
+
"lsp_query",
|
|
295
|
+
"setup_guide",
|
|
296
|
+
"selfverify_status",
|
|
297
|
+
"selfverify_result",
|
|
298
|
+
"selfverify_list",
|
|
299
|
+
"list_vision_cache",
|
|
300
|
+
"ee_feedback",
|
|
301
|
+
"ee_write",
|
|
302
|
+
]);
|
|
303
|
+
const result = {};
|
|
304
|
+
for (const [name, tool] of Object.entries(tools)) {
|
|
305
|
+
if (readonly.has(name) || name.startsWith("respond_") || name.startsWith("mcp_")) {
|
|
306
|
+
result[name] = tool;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return result;
|
|
310
|
+
}
|
|
215
311
|
export async function* executeToolEngine(args) {
|
|
216
312
|
let { deps, stepRouterPhase, phase2Runtime, runtime, modelInfo, _debugSteps, _ceilingHit, userMessage, pilCtx, turnModelId, turnProvider, _stepCeiling, userModelMessage, userEnrichedMessage, signal, observer, taskHash, provider, system, routerStore, attemptedOverflowRecovery, patternLoopForceHalt, userWriteAheadSeq, streamRetryCount, MAX_STREAM_RETRIES, subagents, systemParts, toolTurnSystem, playwrightGuidance, _hasResponseTools, _pilResponseTools, patternLoopInjectCount, agentLoopDecisionCount, MAX_AGENT_LOOP_DECISIONS, _naturalCeiling, _ceilingTaskType, _ceilingSize, textToolReSteerCount, MAX_TEXT_TOOL_RESTEER, turnStartMs, _debugOn, _debugTurnId, _pilEnrichmentDeltaSnapshot, isChitchat, } = args;
|
|
217
313
|
// Put all extracted code here:
|
|
314
|
+
// Auto-recover budget for "cap" (tool-round ceiling) halts: compact
|
|
315
|
+
// the history and keep going instead of stopping and asking the user
|
|
316
|
+
// to /compact. Turn-scoped (not per-stream-attempt) so a stream-error
|
|
317
|
+
// retry or stall reprompt (`continue streamAttempt`) cannot reset the
|
|
318
|
+
// counter and exceed the intended cap of auto-compactions per turn
|
|
319
|
+
// (default 6, env MUONROI_TOOL_LIMIT_AUTO_RECOVER_CAP).
|
|
320
|
+
let toolLimitAutoRecoverCount = 0;
|
|
321
|
+
const TOOL_LIMIT_AUTO_RECOVER_CAP = getToolLimitAutoRecoverCap();
|
|
322
|
+
// Convene-council loop guard. The agent may convene the council at most once
|
|
323
|
+
// per turn — a second convene_council call in the SAME turn does NOT re-run a
|
|
324
|
+
// full (5-10min) council; instead its tool-result is a non-binding suggestion
|
|
325
|
+
// to USE the synthesis already above (respond, or ask_user). Turn-scoped (not
|
|
326
|
+
// per-stream-attempt) so a `continue` restart cannot reset it. Live-caught:
|
|
327
|
+
// the model looped convene→synthesis→convene, burning a second council.
|
|
328
|
+
let conveneRunsThisTurn = 0;
|
|
329
|
+
const COUNCIL_MAX_CONVENES_PER_TURN = (() => {
|
|
330
|
+
const raw = Number.parseInt(process.env.MUONROI_MAX_CONVENES_PER_TURN ?? "", 10);
|
|
331
|
+
return Number.isFinite(raw) && raw >= 1 ? raw : 1;
|
|
332
|
+
})();
|
|
218
333
|
let stallTriggered = false;
|
|
219
334
|
// Time-to-first-byte stall RE-PROMPT: some providers (observed:
|
|
220
335
|
// xai/grok-build-0.1) accept the request then never send the first byte —
|
|
@@ -243,6 +358,10 @@ export async function* executeToolEngine(args) {
|
|
|
243
358
|
// the turn when exceeded. Prevents the session 526a83cf22df pattern
|
|
244
359
|
// where 3 user messages burnt 82% of 2.44M tokens in 36 LLM calls.
|
|
245
360
|
let llmCallsThisTurn = 0;
|
|
361
|
+
// Reactive delegation signal: reference to the top-level cap's live state so
|
|
362
|
+
// this turn's cumulative tool-output load can be reported to the Agent at
|
|
363
|
+
// turn end (drives next-turn sub-session escalation). See reactive-delegation.ts.
|
|
364
|
+
let _topLevelCapState = null;
|
|
246
365
|
// Live-queue steering: messages the user typed mid-turn are drained at a
|
|
247
366
|
// prepareStep boundary and accumulated here, then re-appended (deduped) to
|
|
248
367
|
// the messages returned for each subsequent step. Loop-persistent so they
|
|
@@ -269,11 +388,20 @@ export async function* executeToolEngine(args) {
|
|
|
269
388
|
// and stay productive. `plan` keeps the old behaviour (architectural
|
|
270
389
|
// decisions deserve debate regardless of length).
|
|
271
390
|
const autoCouncilTypes = new Set(["plan", "analyze"]);
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
|
|
391
|
+
const configuredRoleCount = getEffectiveCouncilRoleCount();
|
|
392
|
+
// Task 8 Step 7: prefer the complexity assessor's own auto-council verdict
|
|
393
|
+
// (pilCtx.gsdAutoCouncil, set at message-processor.ts:685 when the assessor ran)
|
|
394
|
+
// over the raw heavy-tier heuristic below — the assessor already reasoned about
|
|
395
|
+
// depth + task shape, so its verdict is the more intelligent router. Fall back to
|
|
396
|
+
// the heuristic when the assessor didn't run (gsdAutoCouncil undefined).
|
|
397
|
+
const assessorAutoCouncil = pilCtx.gsdAutoCouncil;
|
|
398
|
+
const heavyTier = typeof assessorAutoCouncil === "boolean"
|
|
399
|
+
? assessorAutoCouncil
|
|
400
|
+
: pilCtx.complexityTier === "heavy";
|
|
275
401
|
const autoCouncilConfidence = getAutoCouncilConfidence();
|
|
276
402
|
const autoCouncilMinRoles = getAutoCouncilMinRoles();
|
|
403
|
+
const sessionModelIsReasoning = isReasoningModel(deps.modelId);
|
|
404
|
+
const skipReasoningSetting = isAutoCouncilSkipReasoning();
|
|
277
405
|
const _complexityFromTrace = pilCtx._intentTrace
|
|
278
406
|
?.complexity;
|
|
279
407
|
const _complexityGatePassed = pilCtx.taskType === "plan" || _complexityFromTrace === undefined || _complexityFromTrace !== "low";
|
|
@@ -281,9 +409,13 @@ export async function* executeToolEngine(args) {
|
|
|
281
409
|
autoCouncilTypes.has(pilCtx.taskType) &&
|
|
282
410
|
pilCtx.confidence >= autoCouncilConfidence &&
|
|
283
411
|
_complexityGatePassed;
|
|
412
|
+
// Skip reasoning-model skip for heavy/complex tasks — they benefit from
|
|
413
|
+
// multi-role diversity even when the session model already does extended thinking.
|
|
414
|
+
const shouldSkipForReasoning = sessionModelIsReasoning && skipReasoningSetting && !heavyTier;
|
|
284
415
|
const shouldAutoCouncil = !deps.councilManager.isContinuation &&
|
|
285
416
|
isAutoCouncilEnabled() &&
|
|
286
417
|
configuredRoleCount >= autoCouncilMinRoles &&
|
|
418
|
+
!shouldSkipForReasoning &&
|
|
287
419
|
(taskTypeMatch || heavyTier);
|
|
288
420
|
// Always log the auto-council decision (taken or skipped) with the gate
|
|
289
421
|
// values that decided it. Lets reports answer "why did this turn cost
|
|
@@ -295,6 +427,8 @@ export async function* executeToolEngine(args) {
|
|
|
295
427
|
return "feature-disabled";
|
|
296
428
|
if (configuredRoleCount < autoCouncilMinRoles)
|
|
297
429
|
return `role-count<${autoCouncilMinRoles} (have ${configuredRoleCount})`;
|
|
430
|
+
if (shouldSkipForReasoning)
|
|
431
|
+
return `reasoning-model=${deps.modelId} (internal self-debate active; skip with MUONROI_AUTOCOUNCIL_SKIP_REASONING=0)`;
|
|
298
432
|
if (!taskTypeMatch && !heavyTier) {
|
|
299
433
|
if (!pilCtx.taskType || !autoCouncilTypes.has(pilCtx.taskType))
|
|
300
434
|
return `taskType=${pilCtx.taskType ?? "null"} not in plan|analyze`;
|
|
@@ -322,6 +456,8 @@ export async function* executeToolEngine(args) {
|
|
|
322
456
|
autoCouncilConfidence,
|
|
323
457
|
autoCouncilMinRoles,
|
|
324
458
|
heavyTier,
|
|
459
|
+
sessionModelIsReasoning,
|
|
460
|
+
skipReasoningSetting,
|
|
325
461
|
isContinuation: deps.councilManager.isContinuation,
|
|
326
462
|
},
|
|
327
463
|
}).catch(() => undefined);
|
|
@@ -330,14 +466,40 @@ export async function* executeToolEngine(args) {
|
|
|
330
466
|
? `complexity=heavy${pilCtx.taskType ? ` task=${pilCtx.taskType}` : ""}`
|
|
331
467
|
: `${pilCtx.taskType} task detected with ${(pilCtx.confidence * 100).toFixed(0)}% confidence`;
|
|
332
468
|
yield { type: "content", content: `\n[Auto-council triggered: ${reason}]\n` };
|
|
333
|
-
|
|
469
|
+
// Pre-debate interview: unless disabled, run the model-designed clarification
|
|
470
|
+
// askcards BEFORE the debate so a broadly-scoped "debate mode" request is
|
|
471
|
+
// chốt-ed first (each card's options carry a recommended default + per-option
|
|
472
|
+
// why — see runClarification/buildClarifyOptions). The clarifier is ROI-gated
|
|
473
|
+
// and yields 0 cards on already-detailed topics, so this stays quiet when the
|
|
474
|
+
// prompt is already specific. Skip only when the user turned it off. The
|
|
475
|
+
// clarifier reuses PIL gray-areas as seed questions (no hardcoded questions),
|
|
476
|
+
// and its models come from pickCouncilTaskModel (no hardcoded model/provider).
|
|
477
|
+
yield* deps.runCouncilV2(userMessage, {
|
|
478
|
+
skipClarification: !isAutoCouncilClarifyEnabled(),
|
|
479
|
+
observer,
|
|
480
|
+
userModelMessage,
|
|
481
|
+
// Suppress the CLI-hardcoded post-debate option card. The follow-up is
|
|
482
|
+
// decided by the agent's own intent via the neutral continuation below,
|
|
483
|
+
// not a fixed CLI menu. (Pre-debate clarification is orthogonal and still
|
|
484
|
+
// runs per skipClarification.)
|
|
485
|
+
convenePath: true,
|
|
486
|
+
});
|
|
334
487
|
const synthesis = deps.councilManager.lastSynthesis;
|
|
488
|
+
const chosenAction = deps.councilManager.lastPostDebateAction;
|
|
335
489
|
deps.councilManager.setLastSynthesis(null);
|
|
336
|
-
|
|
490
|
+
deps.councilManager.setLastPostDebateAction(null);
|
|
491
|
+
// convenePath suppressed the hardcoded card, so there is no chosenAction to
|
|
492
|
+
// branch on. Hand the synthesis to a normal agent turn with a non-binding
|
|
493
|
+
// nudge and let the agent decide the next step (respond / ask_user /
|
|
494
|
+
// implement). Re-entry is guarded by setContinuation(true) below so
|
|
495
|
+
// shouldAutoCouncil (which checks !isContinuation) can't re-fire into a loop.
|
|
496
|
+
const { buildNeutralPostCouncilContinuation } = await import("../council/index.js");
|
|
497
|
+
const continuationPrompt = synthesis ? buildNeutralPostCouncilContinuation(synthesis) || null : null;
|
|
498
|
+
if (continuationPrompt) {
|
|
337
499
|
yield { type: "content", content: "\n[Auto-continuing with council recommendations...]\n" };
|
|
338
500
|
deps.councilManager.setContinuation(true);
|
|
339
501
|
try {
|
|
340
|
-
yield* deps.processMessage(
|
|
502
|
+
yield* deps.processMessage(continuationPrompt, observer);
|
|
341
503
|
}
|
|
342
504
|
finally {
|
|
343
505
|
deps.councilManager.setContinuation(false);
|
|
@@ -345,6 +507,10 @@ export async function* executeToolEngine(args) {
|
|
|
345
507
|
}
|
|
346
508
|
return;
|
|
347
509
|
}
|
|
510
|
+
// Skipping auto-council is the normal, expected path for a reasoning model —
|
|
511
|
+
// not an event the user needs narrated on every turn. The decision (and the
|
|
512
|
+
// gate values behind it) is still recorded via autoCouncilSkipReason above,
|
|
513
|
+
// so forensics keep the full story without the transcript noise.
|
|
348
514
|
if (deps.batchApi) {
|
|
349
515
|
try {
|
|
350
516
|
yield* deps.processMessageBatchTurn({
|
|
@@ -482,14 +648,58 @@ export async function* executeToolEngine(args) {
|
|
|
482
648
|
cachedImageCount: listCachedImages().length,
|
|
483
649
|
priorTurnHadTools: deps.messages.some((m) => m?.role === "tool"),
|
|
484
650
|
});
|
|
651
|
+
const depthTier = pilCtx.modelDepthTier ??
|
|
652
|
+
pilCtx.complexityTier ??
|
|
653
|
+
"standard";
|
|
485
654
|
const baseToolsRaw = createBuiltinTools(deps.bash, deps.mode, {
|
|
486
655
|
runTask: (request, abortSignal) => deps.runTask(request, combineAbortSignals(signal, abortSignal)),
|
|
487
656
|
runDelegation: (request, abortSignal) => deps.runDelegation(request, combineAbortSignals(signal, abortSignal)),
|
|
488
657
|
readDelegation: (id) => deps.readDelegation(id),
|
|
489
658
|
listDelegations: () => deps.listDelegations(),
|
|
490
659
|
modelId: turnModelId,
|
|
660
|
+
depthTier,
|
|
661
|
+
sessionId: deps.session?.id,
|
|
491
662
|
includeVisionTools,
|
|
492
663
|
consultParentSession: deps.consultParentSession,
|
|
664
|
+
// Register convene_council only when the council is actually usable
|
|
665
|
+
// for this session (enough configured roles) — else the model could
|
|
666
|
+
// call a council that can't convene.
|
|
667
|
+
councilConfigured: configuredRoleCount >= autoCouncilMinRoles,
|
|
668
|
+
askUser: deps.askUser,
|
|
669
|
+
runDebate: async (topic) => {
|
|
670
|
+
// Reset before draining so a generator that throws BEFORE setting
|
|
671
|
+
// synthesis (orchestrator.setLastSynthesis) cannot return a STALE
|
|
672
|
+
// synthesis from a prior council run.
|
|
673
|
+
deps.councilManager.setLastSynthesis(null);
|
|
674
|
+
const gen = deps.runCouncilV2(topic, {
|
|
675
|
+
skipClarification: true,
|
|
676
|
+
userModelMessage: { role: "user", content: `/council ${topic}` },
|
|
677
|
+
// Model-callable debate: the synthesis is returned to the model as
|
|
678
|
+
// the tool result and the model decides the follow-up — so suppress
|
|
679
|
+
// the CLI-hardcoded post-debate card, same as convene_council.
|
|
680
|
+
convenePath: true,
|
|
681
|
+
});
|
|
682
|
+
// Capture a tail of content chunks so an empty-synthesis failure
|
|
683
|
+
// (provider unreachable / sub-phase fail-open / abort — all of which
|
|
684
|
+
// yield a content hint then `return null`) surfaces a real reason in
|
|
685
|
+
// the log instead of a silent "".
|
|
686
|
+
let lastContentHint = "";
|
|
687
|
+
for await (const chunk of gen) {
|
|
688
|
+
const text = chunk?.content;
|
|
689
|
+
if (chunk?.type === "content" && typeof text === "string" && text.trim()) {
|
|
690
|
+
lastContentHint = text.trim().slice(-200);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
const synthesis = deps.councilManager.lastSynthesis ?? "";
|
|
694
|
+
if (!synthesis.trim()) {
|
|
695
|
+
// No-Silent-Catch: the debate produced no synthesis. plan-council
|
|
696
|
+
// will retry then fall back to the perspective path — log why here
|
|
697
|
+
// so the failure is diagnosable remotely.
|
|
698
|
+
console.error(`[tool-engine] plan-review runDebate returned empty synthesis` +
|
|
699
|
+
`${lastContentHint ? ` (last council chunk: ${lastContentHint})` : ""}`);
|
|
700
|
+
}
|
|
701
|
+
return synthesis;
|
|
702
|
+
},
|
|
493
703
|
});
|
|
494
704
|
// Top-level cumulative cap state. We accumulate the raw tool set
|
|
495
705
|
// (base + MCP + PIL response tools) across the assembly below,
|
|
@@ -513,16 +723,23 @@ export async function* executeToolEngine(args) {
|
|
|
513
723
|
// history) is preserved.
|
|
514
724
|
const turnCaps = getProviderCapabilities(requireRuntimeProvider(runtime));
|
|
515
725
|
const _priorTurnHadTools = deps.messages.some((m) => m?.role === "tool");
|
|
726
|
+
// Direct-answer mode: purely informational Q&A (no code changes).
|
|
727
|
+
// Strip write tools (bash, edit_file, write_file) AND skip MCP;
|
|
728
|
+
// only readonly tools remain (read_file, grep, ee_query, etc.).
|
|
729
|
+
const isDirectAnswer = pilCtx.directAnswer === true;
|
|
516
730
|
let rawToolSet = !turnCaps.supportsClientTools(runtime.modelInfo)
|
|
517
731
|
? {}
|
|
518
732
|
: isChitchat && !_priorTurnHadTools
|
|
519
733
|
? {}
|
|
520
|
-
:
|
|
521
|
-
|
|
734
|
+
: isDirectAnswer && !_priorTurnHadTools
|
|
735
|
+
? stripWriteTools(baseToolsRaw)
|
|
736
|
+
: baseToolsRaw;
|
|
737
|
+
// MCP skip: chitchat / direct-answer / greeting inputs don't need 7 MCP servers'
|
|
522
738
|
// worth of tool schemas (~20K input tokens). PIL Layer 1 already
|
|
523
739
|
// gates this conservatively (≤10 chars + ≤2 words OR brain "none").
|
|
524
740
|
if (deps.mode === "agent" &&
|
|
525
741
|
(!isChitchat || _priorTurnHadTools) &&
|
|
742
|
+
!isDirectAnswer &&
|
|
526
743
|
turnCaps.supportsClientTools(runtime.modelInfo)) {
|
|
527
744
|
// Smart MCP filter: drop OPTIONAL MCP servers whose category the
|
|
528
745
|
// current message gives no signal for. Browser/vision servers
|
|
@@ -629,11 +846,14 @@ export async function* executeToolEngine(args) {
|
|
|
629
846
|
// Apply the top-level cumulative cap once over the fully-assembled
|
|
630
847
|
// raw tool set. State is per-turn; each turn gets a fresh budget.
|
|
631
848
|
const topLevelCap = wrapToolSetWithCap(rawToolSet, {
|
|
632
|
-
maxCumulativeChars: getTopLevelToolBudgetChars(deps.maxToolRounds),
|
|
849
|
+
maxCumulativeChars: getTopLevelToolBudgetChars(deps.maxToolRounds, contextWindow),
|
|
633
850
|
midTierRatio: 0.5,
|
|
634
851
|
highTierRatio: 0.8,
|
|
635
852
|
label: "top-level",
|
|
636
853
|
});
|
|
854
|
+
// Expose the cap state so the reactive-delegation signal can read this
|
|
855
|
+
// turn's cumulative tool load at turn end (see report at success exit).
|
|
856
|
+
_topLevelCapState = topLevelCap.state;
|
|
637
857
|
// Phase C3: layer cross-turn dedup on top of the top-level cap.
|
|
638
858
|
const tools = wrapToolSetWithReadBudget(wrapToolSetWithDedup(topLevelCap.tools, deps.crossTurnDedup), deps.readBudget);
|
|
639
859
|
// Wrap non-read-only tools in a turn-scoped mutex to prevent race conditions during parallel execution.
|
|
@@ -654,18 +874,56 @@ export async function* executeToolEngine(args) {
|
|
|
654
874
|
"selfverify_result",
|
|
655
875
|
"selfverify_list",
|
|
656
876
|
"list_vision_cache",
|
|
877
|
+
"ee_feedback",
|
|
878
|
+
"ee_write",
|
|
657
879
|
]);
|
|
880
|
+
/**
|
|
881
|
+
* Tools that perform mutations (writes, edits, side-effects).
|
|
882
|
+
* MUTATION_TOOLS are read from this set to route them through the mutation
|
|
883
|
+
* gate and prevent accidental execution when GSD hard gate is active.
|
|
884
|
+
* Registration: add the tool name string to this set and ensure the tool
|
|
885
|
+
* itself is wired in the tool assembly step.
|
|
886
|
+
*/
|
|
887
|
+
const MUTATION_TOOLS = new Set(["lsp_mutation_preview"]);
|
|
888
|
+
// Task 8: native GSD mutation gate. Read once per turn (not per call) since
|
|
889
|
+
// hardGateEnabled/directAnswer don't change mid-turn; the gate itself
|
|
890
|
+
// re-reads STATE.md per call (cheap fs read) so it stays live if the
|
|
891
|
+
// model advances phase/verdict mid-turn via gsd_* tools.
|
|
892
|
+
const gsdHardGateEnabled = isGsdHardGateEnabled();
|
|
893
|
+
const gsdDirectAnswer = pilCtx.directAnswer;
|
|
658
894
|
for (const name of Object.keys(tools)) {
|
|
659
895
|
const tool = tools[name];
|
|
660
|
-
if (tool
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
896
|
+
if (!tool || typeof tool.execute !== "function")
|
|
897
|
+
continue;
|
|
898
|
+
const originalExecute = tool.execute;
|
|
899
|
+
// Read-only tools skip the mutation gate and the write mutex, but ALL
|
|
900
|
+
// tools get a breadcrumb: a freeze can start under any of them, and a
|
|
901
|
+
// block report that cannot name a suspect is much weaker evidence.
|
|
902
|
+
const guarded = !READ_ONLY_TOOLS.has(name) && !name.startsWith("respond_");
|
|
903
|
+
tool.execute = async (input, context) => {
|
|
904
|
+
// Never cleared, only overwritten — a block often starts just AFTER
|
|
905
|
+
// a tool returns (the 2026-07-16 freeze began on the tick after a
|
|
906
|
+
// bash result), so "after:bash" is exactly the clue we want. Also
|
|
907
|
+
// why this is not a stack: with parallel tool calls, last-writer-
|
|
908
|
+
// wins is the honest summary; the CPU profile is the real evidence.
|
|
909
|
+
setLoopBreadcrumb(`tool:${name}`);
|
|
910
|
+
try {
|
|
911
|
+
if (!guarded)
|
|
912
|
+
return await originalExecute(input, context);
|
|
913
|
+
const gate = evaluateMutationGate(deps.bash.getCwd(), {
|
|
914
|
+
toolName: name,
|
|
915
|
+
hardGateEnabled: gsdHardGateEnabled,
|
|
916
|
+
directAnswer: gsdDirectAnswer,
|
|
917
|
+
});
|
|
918
|
+
if (gate.blocked) {
|
|
919
|
+
return { success: false, output: gate.reason, error: gate.reason };
|
|
920
|
+
}
|
|
921
|
+
return await writeMutex.run(() => originalExecute(input, context));
|
|
922
|
+
}
|
|
923
|
+
finally {
|
|
924
|
+
setLoopBreadcrumb(`after-tool:${name}`);
|
|
925
|
+
}
|
|
926
|
+
};
|
|
669
927
|
}
|
|
670
928
|
captureToolSchemas(tools);
|
|
671
929
|
let responseToolCalled = false;
|
|
@@ -761,7 +1019,40 @@ export async function* executeToolEngine(args) {
|
|
|
761
1019
|
// (non-cached) system message via the slice below.
|
|
762
1020
|
const mcpCapabilityBlock = buildMcpCapabilityBlock(Object.keys(tools));
|
|
763
1021
|
const systemWithCaps = mcpCapabilityBlock ? `${systemWithShell}${mcpCapabilityBlock}` : systemWithShell;
|
|
764
|
-
|
|
1022
|
+
// Task 3 — non-Claude prompt-cache prefix stability. On non-Claude the
|
|
1023
|
+
// system is a single string; per-turn-dynamic content (dynamicSuffix +
|
|
1024
|
+
// PIL suffix + MCP roster) that sits AFTER the byte-stable staticPrefix
|
|
1025
|
+
// but BEFORE the conversation shifts the cached prefix and nukes the
|
|
1026
|
+
// cache on PIL-active turns (session 47a774d272da: pil_active=1 ⟺
|
|
1027
|
+
// cache_read=0). We keep the front byte-stable and relocate the dynamic
|
|
1028
|
+
// tail into the trailing user message (variant b) — NOT a mid-conversation
|
|
1029
|
+
// system-role message, which OpenAI-compatible providers (DeepSeek/GLM)
|
|
1030
|
+
// do not reliably accept. Claude keeps its untouched two-block split.
|
|
1031
|
+
const _isClaudeModel = runtime.modelId.startsWith("claude");
|
|
1032
|
+
const { front: _nonClaudeFront, dynamicTail: _nonClaudeDynamicTail } = _isClaudeModel
|
|
1033
|
+
? { front: systemWithCaps, dynamicTail: "" }
|
|
1034
|
+
: splitFrontAndDynamicTail({
|
|
1035
|
+
modelId: runtime.modelId,
|
|
1036
|
+
systemWithCaps,
|
|
1037
|
+
staticPrefix: systemParts.staticPrefix,
|
|
1038
|
+
});
|
|
1039
|
+
// Only relocate when the enriched user message is actually present to
|
|
1040
|
+
// receive the tail; otherwise fall back to the original single string so
|
|
1041
|
+
// no instruction content is dropped.
|
|
1042
|
+
const _willFoldDynamicTail = !_isClaudeModel &&
|
|
1043
|
+
_nonClaudeDynamicTail.trim().length > 0 &&
|
|
1044
|
+
deps.messages.includes(userModelMessage);
|
|
1045
|
+
if (process.env.MUONROI_DEBUG_CACHE_PREFIX === "1") {
|
|
1046
|
+
try {
|
|
1047
|
+
console.error(`[cache-prefix] model=${runtime.modelId} staticPrefixLen=${systemParts.staticPrefix.length} ` +
|
|
1048
|
+
`frontLen=${_nonClaudeFront.length} dynTailLen=${_nonClaudeDynamicTail.length} ` +
|
|
1049
|
+
`fold=${_willFoldDynamicTail} msgCount=${deps.messages.length}`);
|
|
1050
|
+
}
|
|
1051
|
+
catch (err) {
|
|
1052
|
+
console.error(`[cache-prefix] log failed: ${err?.message}`);
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
const systemForModel = _isClaudeModel
|
|
765
1056
|
? [
|
|
766
1057
|
{
|
|
767
1058
|
role: "system",
|
|
@@ -773,7 +1064,9 @@ export async function* executeToolEngine(args) {
|
|
|
773
1064
|
content: systemWithCaps.slice(systemParts.staticPrefix.length),
|
|
774
1065
|
},
|
|
775
1066
|
]
|
|
776
|
-
:
|
|
1067
|
+
: _willFoldDynamicTail
|
|
1068
|
+
? _nonClaudeFront
|
|
1069
|
+
: systemWithCaps;
|
|
777
1070
|
// Capture prompt-size breakdown so recordUsage can attach it to the
|
|
778
1071
|
// cost-log entry. Without this, "system prompt is huge" is unfalsifiable.
|
|
779
1072
|
// chars/4 ≈ tokens for English; reported as chars to keep math obvious.
|
|
@@ -837,13 +1130,27 @@ export async function* executeToolEngine(args) {
|
|
|
837
1130
|
// stubs. Symmetric to the B3 sub-agent path; reuses the same module
|
|
838
1131
|
// with `label: "top-level"` so the stub text reflects which loop
|
|
839
1132
|
// elided the content.
|
|
840
|
-
const topLevelCompactThreshold = getTopLevelCompactThresholdChars();
|
|
841
|
-
const topLevelCompactKeepLast = getTopLevelCompactKeepLast();
|
|
1133
|
+
const topLevelCompactThreshold = getTopLevelCompactThresholdChars(contextWindow);
|
|
1134
|
+
const topLevelCompactKeepLast = getTopLevelCompactKeepLast(contextWindow);
|
|
1135
|
+
// O2 — byte budget for the verbatim tail; shrinks keepLast on read-heavy
|
|
1136
|
+
// turns that stay large at low fill (the 60-80k-per-call bucket).
|
|
1137
|
+
const topLevelCompactTailBudget = getTopLevelCompactTailBudgetChars(contextWindow);
|
|
1138
|
+
// O3 — compaction hysteresis state (per-turn; this scope runs once per
|
|
1139
|
+
// streamText turn). Once we compact, freeze the compacted prefix and
|
|
1140
|
+
// only append new messages until size grows past the hysteresis ceiling,
|
|
1141
|
+
// so the provider prompt-cache prefix stays byte-stable across steps
|
|
1142
|
+
// instead of breaking every step as the keepLast boundary slides.
|
|
1143
|
+
const compactHysteresis = getTopLevelCompactHysteresis();
|
|
1144
|
+
let hysteresisState = initCompactionHysteresisState();
|
|
842
1145
|
// Phase O1 — capture providerOptions SHAPE (types only) for forensics.
|
|
843
1146
|
deps.setLastProviderOptionsShape(Object.keys(providerOpts).length > 0 ? extractProviderOptionsShape(providerOpts) : null);
|
|
844
1147
|
// Substitute the enriched user message for the current turn so the LLM sees PIL additions,
|
|
845
1148
|
// while leaving the DB-persisted `deps.messages` clean for future turns.
|
|
846
|
-
const _messagesForCall = deps.messages.map((m) => m === userModelMessage
|
|
1149
|
+
const _messagesForCall = deps.messages.map((m) => m === userModelMessage
|
|
1150
|
+
? _willFoldDynamicTail
|
|
1151
|
+
? foldDynamicTailIntoUserMessage(userEnrichedMessage, _nonClaudeDynamicTail)
|
|
1152
|
+
: userEnrichedMessage
|
|
1153
|
+
: m);
|
|
847
1154
|
if (wireDebug.enabled) {
|
|
848
1155
|
wireDebug.logRequest({
|
|
849
1156
|
providerId: runtime.modelInfo?.provider ?? "unknown",
|
|
@@ -865,6 +1172,37 @@ export async function* executeToolEngine(args) {
|
|
|
865
1172
|
const _baseDynamicStopWhen = createToolLoopCapPredicate({
|
|
866
1173
|
initialCap: deps.maxToolRounds,
|
|
867
1174
|
ask: async (info) => {
|
|
1175
|
+
// Auto-recover a "cap" (tool-round ceiling) halt: compact the history and keep
|
|
1176
|
+
// going, instead of stopping and telling the user to /compact
|
|
1177
|
+
// (prompts.ts). Capped so a runaway turn still terminates;
|
|
1178
|
+
// pattern-loop halts are excluded (agent is stuck).
|
|
1179
|
+
if (shouldAutoRecoverToolLimit(info, toolLimitAutoRecoverCount, TOOL_LIMIT_AUTO_RECOVER_CAP)) {
|
|
1180
|
+
toolLimitAutoRecoverCount++;
|
|
1181
|
+
try {
|
|
1182
|
+
const _cw = runtime.modelInfo?.contextWindow ?? 0;
|
|
1183
|
+
if (_cw > 0) {
|
|
1184
|
+
await deps.compactForContext(provider, system, _cw, signal, deps.getCompactionSettings(_cw), false);
|
|
1185
|
+
// A compacted round resets context to O(N) input (cheap), so the
|
|
1186
|
+
// hard-cap's cost-runaway purpose is served — grant the turn more
|
|
1187
|
+
// headroom instead of letting the 1.5× hard ceiling strand a
|
|
1188
|
+
// genuinely productive long task. Bounded inside the Agent.
|
|
1189
|
+
deps.extendHardCeilingForAutoCompaction?.();
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
catch (err) {
|
|
1193
|
+
logger.error("orchestrator", "tool-limit auto-recover compaction failed", {
|
|
1194
|
+
message: err instanceof Error ? err.message : String(err),
|
|
1195
|
+
});
|
|
1196
|
+
}
|
|
1197
|
+
const _ar2 = globalThis.__muonroiAgentRuntime;
|
|
1198
|
+
_ar2?.emitEvent({
|
|
1199
|
+
t: "event",
|
|
1200
|
+
kind: "toast",
|
|
1201
|
+
level: "info",
|
|
1202
|
+
text: `đạt giới hạn bước — đã tự nén ngữ cảnh và tiếp tục (lần ${toolLimitAutoRecoverCount}/${TOOL_LIMIT_AUTO_RECOVER_CAP})`,
|
|
1203
|
+
});
|
|
1204
|
+
return "continue";
|
|
1205
|
+
}
|
|
868
1206
|
if (info.kind === "pattern") {
|
|
869
1207
|
if (patternLoopInjectCount < 1) {
|
|
870
1208
|
patternLoopInjectCount++;
|
|
@@ -987,6 +1325,14 @@ export async function* executeToolEngine(args) {
|
|
|
987
1325
|
_hardCapHit = true;
|
|
988
1326
|
return true;
|
|
989
1327
|
}
|
|
1328
|
+
// convene_council fast-path: if the model queued a council convening
|
|
1329
|
+
// during this step, STOP now so the outer loop runs the council and
|
|
1330
|
+
// splices the synthesis BEFORE the model consumes the placeholder
|
|
1331
|
+
// tool_result. This is a fast-path only — the authoritative
|
|
1332
|
+
// consumption is the outer-loop check after the stream drains (a
|
|
1333
|
+
// phase-1 SAMR step ends on stepCountIs(1) and never reaches here).
|
|
1334
|
+
if (hasPendingCouncilConvene())
|
|
1335
|
+
return true;
|
|
990
1336
|
// Terminal response tool: a `respond_*` call IS the model's final
|
|
991
1337
|
// structured answer (its `execute` is identity — the payload lives
|
|
992
1338
|
// in the tool-call args). `shouldHaltOnResponseTool` decides if the
|
|
@@ -1081,9 +1427,27 @@ export async function* executeToolEngine(args) {
|
|
|
1081
1427
|
// on every chunk via stall.pet(), so it never kills an actively
|
|
1082
1428
|
// streaming call. Disposed when the stream ends or errors.
|
|
1083
1429
|
stallTriggered = false;
|
|
1430
|
+
// Second timer (progressTimeoutMs) is the no-forward-progress guard.
|
|
1431
|
+
// stall.pet() re-arms the any-activity timer on EVERY chunk — including
|
|
1432
|
+
// a reasoning model's reasoning-delta — so an endless chain-of-thought
|
|
1433
|
+
// keeps it alive and it never fires (observed live 2026-07-10: a
|
|
1434
|
+
// deepseek-v4-flash sub-SESSION churned reasoning 30+ min, 1.4M input
|
|
1435
|
+
// tokens, ZERO text/tool output; the 2-min stall watchdog never tripped).
|
|
1436
|
+
// The progress timer is reset ONLY by stall.petProgress() on real output
|
|
1437
|
+
// (text-delta / tool-call), aborting a runaway-reasoning loop while a
|
|
1438
|
+
// legitimately long reasoning burst that DOES emit output survives.
|
|
1084
1439
|
const stall = createStallWatchdog(getProviderStallTimeoutMs(), () => {
|
|
1085
1440
|
stallTriggered = true;
|
|
1086
|
-
}
|
|
1441
|
+
}, {
|
|
1442
|
+
progressTimeoutMs: getProviderProgressTimeoutMs(),
|
|
1443
|
+
onProgressFire: () => {
|
|
1444
|
+
stallTriggered = true;
|
|
1445
|
+
console.error(`[tool-engine] stream aborted: no text/tool output for ${getProviderProgressTimeoutMs()}ms ` +
|
|
1446
|
+
`(runaway reasoning / no forward progress) model=${runtime.modelId}`);
|
|
1447
|
+
},
|
|
1448
|
+
},
|
|
1449
|
+
// Hold the stream open while a blocking `ask_user` card awaits a human.
|
|
1450
|
+
isInteractivePaused);
|
|
1087
1451
|
// F3c — hard-cap LLM calls per turn before this streamText()
|
|
1088
1452
|
if (++llmCallsThisTurn > MAX_LLM_CALLS_PER_TURN) {
|
|
1089
1453
|
stall.dispose();
|
|
@@ -1260,10 +1624,17 @@ export async function* executeToolEngine(args) {
|
|
|
1260
1624
|
};
|
|
1261
1625
|
// T1.1 + T1.2 — reasoning models (DeepSeek V4 Flash, R1) emit 2K-5K
|
|
1262
1626
|
// CoT tokens per turn that accumulate across the multi-step loop.
|
|
1263
|
-
// Strip old reasoning and compact earlier
|
|
1264
|
-
//
|
|
1627
|
+
// Strip old reasoning and compact earlier to cut cumulative input.
|
|
1628
|
+
// Small-context reasoning models (< 100K) use ratio 0.2 (fire at
|
|
1629
|
+
// 20% fill) because their per-step overhead (system + tools + CoT)
|
|
1630
|
+
// already consumes ~30-40% of the window, leaving little headroom.
|
|
1265
1631
|
const isReasoningModel = runtime.modelInfo?.reasoning === true;
|
|
1266
|
-
const
|
|
1632
|
+
const reasoningFillRatio = isReasoningModel
|
|
1633
|
+
? contextWindowTokens > 0 && contextWindowTokens < 100_000
|
|
1634
|
+
? 0.2
|
|
1635
|
+
: 0.3
|
|
1636
|
+
: undefined;
|
|
1637
|
+
const runCompaction = () => compactSubAgentMessages(stripped, {
|
|
1267
1638
|
thresholdChars: topLevelCompactThreshold,
|
|
1268
1639
|
// Rec #1 (cheap part): on meta/self-eval turns keep a couple more
|
|
1269
1640
|
// trailing tool turns verbatim — those carry the reasoning the
|
|
@@ -1274,28 +1645,120 @@ export async function* executeToolEngine(args) {
|
|
|
1274
1645
|
label: "top-level",
|
|
1275
1646
|
envelopeChars,
|
|
1276
1647
|
contextWindowTokens,
|
|
1277
|
-
contextFillRatio:
|
|
1648
|
+
contextFillRatio: reasoningFillRatio,
|
|
1278
1649
|
keepToolIds: keepToolIds.length ? keepToolIds : undefined,
|
|
1279
1650
|
persistArtifact,
|
|
1280
1651
|
stripOldReasoning: isReasoningModel,
|
|
1652
|
+
tailBudgetChars: topLevelCompactTailBudget,
|
|
1281
1653
|
});
|
|
1282
|
-
|
|
1283
|
-
|
|
1654
|
+
// O3 — compaction hysteresis (holds the frozen compacted prefix
|
|
1655
|
+
// between compactions so the provider prompt-cache prefix stays
|
|
1656
|
+
// byte-stable across steps instead of breaking as the keepLast
|
|
1657
|
+
// boundary slides). See applyCompactionHysteresis.
|
|
1658
|
+
const currChars = cumulativeMessageChars(stripped) + envelopeChars;
|
|
1659
|
+
// Proactive compaction (agent called the `compact` tool). Consume the
|
|
1660
|
+
// one-shot request and FORCE a compaction this step, bypassing the
|
|
1661
|
+
// hysteresis threshold — the agent explicitly asked to shed context,
|
|
1662
|
+
// so a one-time cache-prefix break is the intended trade. Re-seed the
|
|
1663
|
+
// hysteresis state to the fresh compacted prefix so the following
|
|
1664
|
+
// steps hold it steady (no per-step churn) until it grows again.
|
|
1665
|
+
const _proactiveCompact = consumeProactiveCompact();
|
|
1666
|
+
let compacted;
|
|
1667
|
+
if (_proactiveCompact) {
|
|
1668
|
+
const _forced = runCompaction();
|
|
1669
|
+
const _didForce = _forced !== stripped;
|
|
1670
|
+
compacted = _forced;
|
|
1671
|
+
hysteresisState = _didForce
|
|
1672
|
+
? { frozenCompacted: _forced, frozenStrippedLen: stripped.length, lastCompactTriggerChars: currChars }
|
|
1673
|
+
: hysteresisState;
|
|
1674
|
+
if (_didForce)
|
|
1675
|
+
recordCompaction(sn);
|
|
1676
|
+
try {
|
|
1677
|
+
const _arPc = globalThis.__muonroiAgentRuntime;
|
|
1678
|
+
_arPc?.emitEvent({
|
|
1679
|
+
t: "event",
|
|
1680
|
+
kind: "toast",
|
|
1681
|
+
level: "info",
|
|
1682
|
+
text: _didForce
|
|
1683
|
+
? "đã nén ngữ cảnh theo yêu cầu của agent — tiếp tục tác vụ"
|
|
1684
|
+
: "agent yêu cầu nén nhưng chưa có gì để nén — tiếp tục",
|
|
1685
|
+
});
|
|
1686
|
+
}
|
|
1687
|
+
catch {
|
|
1688
|
+
/* toast best-effort */
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
else {
|
|
1692
|
+
const _hyst = applyCompactionHysteresis({
|
|
1693
|
+
stripped,
|
|
1694
|
+
currChars,
|
|
1695
|
+
hysteresis: compactHysteresis,
|
|
1696
|
+
state: hysteresisState,
|
|
1697
|
+
runCompaction,
|
|
1698
|
+
});
|
|
1699
|
+
compacted = _hyst.compacted;
|
|
1700
|
+
hysteresisState = _hyst.state;
|
|
1701
|
+
// Count only ACTUAL (re)compactions, not held-boundary steps — the
|
|
1702
|
+
// compaction counter drives the cache-churn telemetry this fixes.
|
|
1703
|
+
if (_hyst.didRecompact)
|
|
1704
|
+
recordCompaction(sn);
|
|
1705
|
+
}
|
|
1706
|
+
const coalesced = coalesceReadOnlyMessages(compacted);
|
|
1707
|
+
// (recordCompaction already handled per-branch above.)
|
|
1284
1708
|
// Pre-compaction visibility: give the agent one step of notice
|
|
1285
1709
|
// before B4 actually rewrites history into stubs. This is the
|
|
1286
1710
|
// advance warning that was missing — agent can now decide to
|
|
1287
1711
|
// summarize, finish, or request preservation. Fires when we did
|
|
1288
1712
|
// NOT compact this step (compacted === stripped, restored by the
|
|
1289
|
-
// compactSubAgentMessages no-op ref contract) AND the prompt is
|
|
1290
|
-
// approaching the threshold. Must compare CHARS (messages +
|
|
1291
|
-
// envelope), not stripped.length (a message count that never
|
|
1292
|
-
// exceeds a char-scaled threshold) — session 2b7a10219499.
|
|
1293
1713
|
const _preWarnChars = cumulativeMessageChars(stripped) + envelopeChars;
|
|
1294
|
-
if (
|
|
1714
|
+
if (coalesced === stripped && shouldPreWarnCompaction(_preWarnChars, topLevelCompactThreshold)) {
|
|
1295
1715
|
const _cp = buildCheckpointReminder(sn, true);
|
|
1296
1716
|
const _pre = `[pre-compaction warning at step ${sn} — next step(s) will likely rewrite older tool results to stubs (threshold ${topLevelCompactThreshold}, keepLast=${topLevelCompactKeepLast}). ${_cp} Summarize or finish if possible, or warn the user they can run the "/compact" command if they want a clean compressed history.]`;
|
|
1297
1717
|
return withSteers({ messages: attachReminderToMessages(stripped, _pre) });
|
|
1298
1718
|
}
|
|
1719
|
+
// ---- Read-only tool batching interceptor ----
|
|
1720
|
+
// The system prompt already instructs batching (BATCH ALL TOOL
|
|
1721
|
+
// CALLS — HARD RULE), but models like DeepSeek frequently ignore
|
|
1722
|
+
// it and emit 1 read-only call per step. This injects a concrete,
|
|
1723
|
+
// in-context reminder right before the next LLM call when the
|
|
1724
|
+
// previous step had ≤2 all-read-only tool calls. Cost: ~0 extra
|
|
1725
|
+
// context. Effect: reduces tool rounds 2-3x on the same work.
|
|
1726
|
+
if (sn >= 1) {
|
|
1727
|
+
let _lastAsst = null;
|
|
1728
|
+
for (let _i = stepMessages.length - 1; _i >= 0; _i--) {
|
|
1729
|
+
if (stepMessages[_i].role === "assistant") {
|
|
1730
|
+
_lastAsst = stepMessages[_i];
|
|
1731
|
+
break;
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
if (_lastAsst && Array.isArray(_lastAsst.content)) {
|
|
1735
|
+
let _total = 0, _ro = 0;
|
|
1736
|
+
for (const _p of _lastAsst.content) {
|
|
1737
|
+
if (_p.type === "tool-call") {
|
|
1738
|
+
_total++;
|
|
1739
|
+
if (READ_ONLY_TOOLS.has(_p.toolName))
|
|
1740
|
+
_ro++;
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
if (_total > 0 && _ro === _total && _total <= 2) {
|
|
1744
|
+
// Prefer the SINGLE-CALL multi-path form (read_file file_paths=[…])
|
|
1745
|
+
// over "parallel tool_calls": a batch of parallel tool_calls is
|
|
1746
|
+
// reshaped into sequential single-call turns for the kimi/glm/
|
|
1747
|
+
// deepseek-go cohort (splitParallelToolCalls) — which re-inflates
|
|
1748
|
+
// history and defeats the batching win. One read_file carrying N
|
|
1749
|
+
// paths is ONE tool_call → never split → the token cut holds on
|
|
1750
|
+
// every provider.
|
|
1751
|
+
const _b = `[Tool batching: you called ${_total} read-only tool(s) one-at-a-time — each extra call re-sends the whole conversation, so N single reads cost O(N²) tokens. To read MULTIPLE files, call read_file ONCE with file_paths=["a","b","c"] — a SINGLE tool_call that no provider splits. For other read-only tools (grep, bash_output_get), emit all pending calls in ONE assistant turn. Do NOT sequence reads across steps.]`;
|
|
1752
|
+
// Attach to `coalesced` (the B4-compacted history), NOT `stripped`:
|
|
1753
|
+
// read-only tools (read_file/grep/…) are exactly what triggers this
|
|
1754
|
+
// reminder, and returning `stripped` here silently discarded the
|
|
1755
|
+
// compaction computed above — so on any loop where the model emits
|
|
1756
|
+
// ≤2 read-only calls per step (DeepSeek does this constantly) older
|
|
1757
|
+
// tool results were never elided and cumulative input grew unbounded.
|
|
1758
|
+
return withSteers({ messages: attachReminderToMessages(coalesced, _b) });
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1299
1762
|
// Phase 4A — scope reminder injection (REQ-005).
|
|
1300
1763
|
// Cadence K = 3/5/8 for small/medium/large. Soft-warn fires
|
|
1301
1764
|
// ONCE per session at floor(ceiling*0.7). Reminder lives in
|
|
@@ -1355,10 +1818,10 @@ export async function* executeToolEngine(args) {
|
|
|
1355
1818
|
? `${buildRepetitionReminder(_ceilingSessionId)}\n${_scopePart}`
|
|
1356
1819
|
: buildRepetitionReminder(_ceilingSessionId)
|
|
1357
1820
|
: _scopePart;
|
|
1358
|
-
const withReminder = attachReminderToMessages(
|
|
1821
|
+
const withReminder = attachReminderToMessages(coalesced, _reminder);
|
|
1359
1822
|
return withSteers({ messages: withReminder });
|
|
1360
1823
|
}
|
|
1361
|
-
if (
|
|
1824
|
+
if (coalesced === stripped && stripped === stepMessages)
|
|
1362
1825
|
return withSteers({});
|
|
1363
1826
|
// Self-awareness note: tell the model compaction happened so it
|
|
1364
1827
|
// knows earlier context was elided and can adjust its behavior.
|
|
@@ -1377,12 +1840,12 @@ export async function* executeToolEngine(args) {
|
|
|
1377
1840
|
})()
|
|
1378
1841
|
: null;
|
|
1379
1842
|
if (_compactNote) {
|
|
1380
|
-
return withSteers({ messages: attachReminderToMessages(
|
|
1843
|
+
return withSteers({ messages: attachReminderToMessages(coalesced, _compactNote) });
|
|
1381
1844
|
}
|
|
1382
|
-
return withSteers({ messages:
|
|
1845
|
+
return withSteers({ messages: coalesced });
|
|
1383
1846
|
},
|
|
1384
|
-
...(
|
|
1385
|
-
...(dropParam("maxOutputTokens") ? {} : { maxOutputTokens:
|
|
1847
|
+
...resolveTemperatureParam(runtime, 0.7),
|
|
1848
|
+
...(dropParam("maxOutputTokens") ? {} : { maxOutputTokens: resolveTurnMaxOutputTokens(pilCtx) }),
|
|
1386
1849
|
...(Object.keys(providerOpts).length > 0 ? { providerOptions: providerOpts } : {}),
|
|
1387
1850
|
experimental_onStepStart: (event) => {
|
|
1388
1851
|
stepNumber = getStepNumber(event, stepNumber + 1);
|
|
@@ -1401,6 +1864,10 @@ export async function* executeToolEngine(args) {
|
|
|
1401
1864
|
finishReason: getFinishReason(event),
|
|
1402
1865
|
usage: stepUsage,
|
|
1403
1866
|
});
|
|
1867
|
+
// Pull any completed background delegations so their results can be
|
|
1868
|
+
// injected (as system messages) for the *next* LLM step in this same turn.
|
|
1869
|
+
// This improves "self wake" for background jobs without waiting for a new user turn.
|
|
1870
|
+
void deps.consumeBackgroundNotifications?.().catch(() => { });
|
|
1404
1871
|
// Realtime status bar update per step
|
|
1405
1872
|
if (stepUsage.inputTokens || stepUsage.outputTokens) {
|
|
1406
1873
|
// O1 — thread THIS turn's providerOptions shape per step so every
|
|
@@ -1451,6 +1918,9 @@ export async function* executeToolEngine(args) {
|
|
|
1451
1918
|
const _wireProviderIdTop = runtime.modelInfo?.provider ?? "unknown";
|
|
1452
1919
|
for await (const part of result.fullStream) {
|
|
1453
1920
|
stall.pet(); // chunk arrived — reset the stall watchdog
|
|
1921
|
+
// Breadcrumb the chunk type: if the loop blocks while draining the
|
|
1922
|
+
// stream, this says which part kind we were handling when it froze.
|
|
1923
|
+
setLoopBreadcrumb(`stream:${String(part.type ?? "unknown")}`);
|
|
1454
1924
|
// Count only real content parts. The watchdog abort itself surfaces
|
|
1455
1925
|
// as an "abort" part — counting it would defeat the TTFB-stall gate
|
|
1456
1926
|
// (a frozen-before-first-byte stall yields ONLY the abort part).
|
|
@@ -1475,8 +1945,23 @@ export async function* executeToolEngine(args) {
|
|
|
1475
1945
|
wireDebug.logError(_wireProviderIdTop, part.error);
|
|
1476
1946
|
}
|
|
1477
1947
|
}
|
|
1948
|
+
// Terminal part of the ENTIRE multi-step turn (AI SDK v6 emits exactly
|
|
1949
|
+
// one `finish` after every step's `finish-step`; it carries the final
|
|
1950
|
+
// finishReason/totalUsage). onFinish has already run by now (usage
|
|
1951
|
+
// recorded + llm-done emitted, and result.response is resolved), so
|
|
1952
|
+
// there is nothing left to drain. Some providers — observed live:
|
|
1953
|
+
// xai/grok-composer-2.5-fast — emit `finish` but then never CLOSE the
|
|
1954
|
+
// fullStream async iterator, so `for await` would block on the next
|
|
1955
|
+
// `.next()` until the turn watchdog fires (up to MUONROI_TURN_IDLE_MS).
|
|
1956
|
+
// Breaking on `finish` finalizes the turn immediately instead. Safe:
|
|
1957
|
+
// `finish` is strictly last, so this can never truncate a multi-step
|
|
1958
|
+
// turn (per-step boundaries are `finish-step`, handled by fall-through).
|
|
1959
|
+
if (part.type === "finish") {
|
|
1960
|
+
break;
|
|
1961
|
+
}
|
|
1478
1962
|
switch (part.type) {
|
|
1479
1963
|
case "text-delta":
|
|
1964
|
+
stall.petProgress(); // real forward progress — reset the no-progress guard
|
|
1480
1965
|
assistantText += part.text;
|
|
1481
1966
|
// Task 2.6b — emit llm-token (agent-mode only; high-volume, default-off per Phase 4).
|
|
1482
1967
|
try {
|
|
@@ -1508,6 +1993,7 @@ export async function* executeToolEngine(args) {
|
|
|
1508
1993
|
yield { type: "reasoning", content: part.text };
|
|
1509
1994
|
break;
|
|
1510
1995
|
case "tool-call": {
|
|
1996
|
+
stall.petProgress(); // real forward progress — reset the no-progress guard
|
|
1511
1997
|
const tc = toToolCall(part);
|
|
1512
1998
|
activeToolCalls.push(tc);
|
|
1513
1999
|
// SAMR: track that Phase 1 produced tool calls → transition to Phase 2
|
|
@@ -1728,10 +2214,10 @@ export async function* executeToolEngine(args) {
|
|
|
1728
2214
|
// the global __muonroiSafetyApproved map so registry.ts's
|
|
1729
2215
|
// bash.execute bypasses the block on the retry.
|
|
1730
2216
|
const _outputText = [tr.output, tr.error].filter((x) => typeof x === "string").join("\n");
|
|
1731
|
-
const
|
|
1732
|
-
if (
|
|
1733
|
-
const _blockKind =
|
|
1734
|
-
const _blockReason =
|
|
2217
|
+
const _parsedBlock = parseSafetyBlock(_outputText);
|
|
2218
|
+
if (_parsedBlock && part.toolName === "bash") {
|
|
2219
|
+
const _blockKind = _parsedBlock.kind;
|
|
2220
|
+
const _blockReason = _parsedBlock.reason;
|
|
1735
2221
|
const _command = typeof part.input === "object" && part.input != null
|
|
1736
2222
|
? String(part.input.command ?? "")
|
|
1737
2223
|
: "";
|
|
@@ -1744,15 +2230,33 @@ export async function* executeToolEngine(args) {
|
|
|
1744
2230
|
tr = { ...tr, success: false, error: _outputText, output: _outputText };
|
|
1745
2231
|
}
|
|
1746
2232
|
else {
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
2233
|
+
// yolo mode auto-approves lower-severity blocks (git-safety /
|
|
2234
|
+
// dangerous) so "don't ask me" actually stops asking — but a
|
|
2235
|
+
// catastrophic, irreversible command STILL shows the askcard,
|
|
2236
|
+
// even in yolo. Everything else surfaces the interactive
|
|
2237
|
+
// safety-override card via deps.askSafetyOverride (registered
|
|
2238
|
+
// by the TUI); when no handler is wired (headless / batch),
|
|
2239
|
+
// that resolves to { action: "block" }, preserving the
|
|
2240
|
+
// backward-compatible hard-stop for non-interactive runs.
|
|
2241
|
+
let _verdict;
|
|
2242
|
+
if (shouldAutoAllowYolo(_blockKind, deps.permissionMode)) {
|
|
2243
|
+
_verdict = { action: "allow-once" };
|
|
2244
|
+
yield {
|
|
2245
|
+
type: "content",
|
|
2246
|
+
content: `[yolo: auto-approved blocked command: ${_blockKind}]\n`,
|
|
2247
|
+
};
|
|
2248
|
+
}
|
|
2249
|
+
else {
|
|
2250
|
+
_verdict = deps.askSafetyOverride
|
|
2251
|
+
? await deps.askSafetyOverride({
|
|
2252
|
+
kind: _blockKind,
|
|
2253
|
+
toolName: part.toolName,
|
|
2254
|
+
blockedItem: _command,
|
|
2255
|
+
reason: _blockReason,
|
|
2256
|
+
source: "bash.execute",
|
|
2257
|
+
})
|
|
2258
|
+
: { action: "block" };
|
|
2259
|
+
}
|
|
1756
2260
|
if (_verdict.action === "allow-once" || _verdict.action === "allow-session") {
|
|
1757
2261
|
// Store approval so registry.ts can bypass the block on retry.
|
|
1758
2262
|
const _globalSafety = globalThis;
|
|
@@ -1886,10 +2390,26 @@ export async function* executeToolEngine(args) {
|
|
|
1886
2390
|
// dedicated chunk so the sticky checklist panel can re-render
|
|
1887
2391
|
// without parsing tool args itself. Skipped when the snapshot
|
|
1888
2392
|
// doesn't parse (malformed args) so the UI is never poisoned.
|
|
1889
|
-
if (tc.function.name === "todo_write"
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
2393
|
+
if (tr.success && (tc.function.name === "todo_write" || tc.function.name.startsWith("gsd_"))) {
|
|
2394
|
+
try {
|
|
2395
|
+
const { getTaskListSnapshotFromGsd } = require("../gsd/phase-sync.js");
|
|
2396
|
+
const snap = getTaskListSnapshotFromGsd(deps.bash.getCwd());
|
|
2397
|
+
if (snap) {
|
|
2398
|
+
yield { type: "task_list_update", taskListSnapshot: snap };
|
|
2399
|
+
}
|
|
2400
|
+
else if (tc.function.name === "todo_write") {
|
|
2401
|
+
const snapLegacy = snapshotFromTodoWriteArgs(tc.function.arguments);
|
|
2402
|
+
if (snapLegacy)
|
|
2403
|
+
yield { type: "task_list_update", taskListSnapshot: snapLegacy };
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
2406
|
+
catch (err) {
|
|
2407
|
+
if (tc.function.name === "todo_write") {
|
|
2408
|
+
const snapLegacy = snapshotFromTodoWriteArgs(tc.function.arguments);
|
|
2409
|
+
if (snapLegacy)
|
|
2410
|
+
yield { type: "task_list_update", taskListSnapshot: snapLegacy };
|
|
2411
|
+
}
|
|
2412
|
+
}
|
|
1893
2413
|
}
|
|
1894
2414
|
break;
|
|
1895
2415
|
}
|
|
@@ -2454,6 +2974,109 @@ export async function* executeToolEngine(args) {
|
|
|
2454
2974
|
}
|
|
2455
2975
|
}
|
|
2456
2976
|
stall.dispose(); // stream drained normally — stop the stall watchdog
|
|
2977
|
+
// ─── convene_council consumption ───────────────────────────────
|
|
2978
|
+
// The convene_council tool queued a request during THIS step's execute().
|
|
2979
|
+
// Consume it here in the OUTER loop after every stream drain — NOT solely
|
|
2980
|
+
// via dynamicStopWhen, because a phase-1 SAMR step ends on stepCountIs(1)
|
|
2981
|
+
// and never evaluates the stop hook (design-debate BUG 2). Runs the
|
|
2982
|
+
// council autonomously (convenePath suppresses ALL post-debate decision
|
|
2983
|
+
// surface — no card, no continuation), splices the synthesis into the
|
|
2984
|
+
// convene tool_result, grafts into deps.messages, and restarts the step
|
|
2985
|
+
// so the model reads the conclusion as the tool result and continues.
|
|
2986
|
+
if (hasPendingCouncilConvene()) {
|
|
2987
|
+
if (responseToolCalled) {
|
|
2988
|
+
// The same step also emitted a terminal respond_* answer — the model
|
|
2989
|
+
// is done; discard the convene request rather than override the
|
|
2990
|
+
// answer (design-debate BUG 1: never let the flag leak across turns).
|
|
2991
|
+
consumeCouncilConvene();
|
|
2992
|
+
logger.warn("orchestrator", "convene: discarded — step also emitted a terminal response tool");
|
|
2993
|
+
}
|
|
2994
|
+
else {
|
|
2995
|
+
const pendingId = peekCouncilConveneToolCallId();
|
|
2996
|
+
let conveneResponse = null;
|
|
2997
|
+
try {
|
|
2998
|
+
conveneResponse = await result.response;
|
|
2999
|
+
}
|
|
3000
|
+
catch (err) {
|
|
3001
|
+
logger.error("orchestrator", "convene: failed to read response.messages", {
|
|
3002
|
+
error: err?.message,
|
|
3003
|
+
});
|
|
3004
|
+
}
|
|
3005
|
+
// BUG 3 guard: only run council when the pending convene toolCallId is
|
|
3006
|
+
// a recorded tool-result in THIS drain's messages — else a nested
|
|
3007
|
+
// frame's convene call would be wrongly consumed by this loop.
|
|
3008
|
+
const belongsHere = !!conveneResponse &&
|
|
3009
|
+
!!pendingId &&
|
|
3010
|
+
conveneResponse.messages.some((m) => m?.role === "tool" &&
|
|
3011
|
+
Array.isArray(m.content) &&
|
|
3012
|
+
m.content.some((p) => p?.type === "tool-result" && p?.toolCallId === pendingId));
|
|
3013
|
+
if (belongsHere && conveneResponse) {
|
|
3014
|
+
const req = consumeCouncilConvene();
|
|
3015
|
+
// Loop guard: a second convene in the SAME turn short-circuits —
|
|
3016
|
+
// do NOT pay for another council. Splice a non-binding suggestion
|
|
3017
|
+
// pointing the model at the synthesis already in the transcript so
|
|
3018
|
+
// it responds (or asks the user) instead of re-convening forever.
|
|
3019
|
+
if (conveneRunsThisTurn >= COUNCIL_MAX_CONVENES_PER_TURN) {
|
|
3020
|
+
logger.warn("orchestrator", "convene: loop guard hit — suppressing re-convene", {
|
|
3021
|
+
conveneRunsThisTurn,
|
|
3022
|
+
cap: COUNCIL_MAX_CONVENES_PER_TURN,
|
|
3023
|
+
});
|
|
3024
|
+
const suggestion = "[The council already convened this turn — its synthesis is in the transcript above. " +
|
|
3025
|
+
"Do NOT convene again for the same question. Use that synthesis now: give the user your " +
|
|
3026
|
+
"recommendation, or call ask_user if you need their go-ahead before implementing.]";
|
|
3027
|
+
const { messages: guardSpliced, replaced: guardReplaced } = spliceConveneToolResult(conveneResponse.messages, req?.toolCallId ?? null, suggestion);
|
|
3028
|
+
if (!guardReplaced) {
|
|
3029
|
+
logger.warn("orchestrator", "convene: loop-guard splice found no matching toolCallId", {
|
|
3030
|
+
toolCallId: req?.toolCallId ?? null,
|
|
3031
|
+
});
|
|
3032
|
+
}
|
|
3033
|
+
const guardNewMsgs = guardSpliced.slice(deps.messages.length);
|
|
3034
|
+
for (const msg of guardNewMsgs)
|
|
3035
|
+
deps.messages.push(msg);
|
|
3036
|
+
continue; // re-enter with the suggestion as the tool result
|
|
3037
|
+
}
|
|
3038
|
+
conveneRunsThisTurn++;
|
|
3039
|
+
yield { type: "content", content: "\n[Convening the council…]\n" };
|
|
3040
|
+
// Filter the terminal `done` runCouncilV2 emits on its
|
|
3041
|
+
// non-continuation path (orchestrator.ts ~2274): letting it through
|
|
3042
|
+
// would finalize the UI turn BEFORE we restart streamText below, so
|
|
3043
|
+
// the model's post-council continuation would be orphaned/invisible
|
|
3044
|
+
// (live-caught: council ran, synthesis produced, but no final answer
|
|
3045
|
+
// rendered). We continue the SAME turn ourselves via the splice +
|
|
3046
|
+
// `continue` restart, so the council's `done` must not propagate.
|
|
3047
|
+
for await (const chunk of deps.runCouncilV2(userMessage, {
|
|
3048
|
+
convenePath: true,
|
|
3049
|
+
skipClarification: true,
|
|
3050
|
+
observer,
|
|
3051
|
+
userModelMessage,
|
|
3052
|
+
})) {
|
|
3053
|
+
if (chunk.type === "done")
|
|
3054
|
+
continue;
|
|
3055
|
+
yield chunk;
|
|
3056
|
+
}
|
|
3057
|
+
const synthesis = deps.councilManager.lastSynthesis;
|
|
3058
|
+
const spliceValue = synthesis && synthesis.trim().length > 0
|
|
3059
|
+
? synthesis
|
|
3060
|
+
: `[Council could not produce a conclusion${req?.reason ? ` for: ${req.reason}` : ""}. Proceed using your own judgment.]`;
|
|
3061
|
+
if (!synthesis || synthesis.trim().length === 0) {
|
|
3062
|
+
logger.warn("orchestrator", "convene: council returned no synthesis", { reason: req?.reason ?? null });
|
|
3063
|
+
}
|
|
3064
|
+
const { messages: spliced, replaced } = spliceConveneToolResult(conveneResponse.messages, req?.toolCallId ?? null, spliceValue);
|
|
3065
|
+
if (!replaced) {
|
|
3066
|
+
logger.warn("orchestrator", "convene: tool_result splice found no matching toolCallId", {
|
|
3067
|
+
toolCallId: req?.toolCallId ?? null,
|
|
3068
|
+
});
|
|
3069
|
+
}
|
|
3070
|
+
// Graft the new messages (assistant tool-call + spliced tool-result)
|
|
3071
|
+
// into deps.messages, then restart the step so the model reads the
|
|
3072
|
+
// synthesis as the convene tool's result (SAMR restart precedent).
|
|
3073
|
+
const newMsgs = spliced.slice(deps.messages.length);
|
|
3074
|
+
for (const msg of newMsgs)
|
|
3075
|
+
deps.messages.push(msg);
|
|
3076
|
+
continue; // re-enter the loop with the spliced history
|
|
3077
|
+
}
|
|
3078
|
+
}
|
|
3079
|
+
}
|
|
2457
3080
|
// ─── SAMR Phase 1 → Phase 2 transition ─────────────────────────
|
|
2458
3081
|
// Phase 1 (premium model) produced tool calls but the SDK stopped
|
|
2459
3082
|
// before executing them (stopWhen: stepCountIs(1)). Append the
|
|
@@ -3021,6 +3644,10 @@ export async function* executeToolEngine(args) {
|
|
|
3021
3644
|
if (modelInfo?.contextWindow) {
|
|
3022
3645
|
void deps.postTurnCompact(provider, system, modelInfo.contextWindow, signal).catch(() => { });
|
|
3023
3646
|
}
|
|
3647
|
+
// Reactive delegation: report this turn's observed tool-output load so
|
|
3648
|
+
// the next turn can escalate to an isolated sub-session when it proves
|
|
3649
|
+
// heavy — independent of the fragile upfront router. See reactive-delegation.ts.
|
|
3650
|
+
deps.reportTurnToolLoad?.(_topLevelCapState?.cumulative ?? 0);
|
|
3024
3651
|
yield { type: "done" };
|
|
3025
3652
|
return;
|
|
3026
3653
|
}
|
|
@@ -3167,6 +3794,10 @@ export async function* executeToolEngine(args) {
|
|
|
3167
3794
|
if (modelInfo?.contextWindow) {
|
|
3168
3795
|
void deps.postTurnCompact(provider, system, modelInfo.contextWindow, signal).catch(() => { });
|
|
3169
3796
|
}
|
|
3797
|
+
// Reactive delegation: report this turn's observed tool-output load so
|
|
3798
|
+
// the next turn can escalate to an isolated sub-session when it proves
|
|
3799
|
+
// heavy — independent of the fragile upfront router. See reactive-delegation.ts.
|
|
3800
|
+
deps.reportTurnToolLoad?.(_topLevelCapState?.cumulative ?? 0);
|
|
3170
3801
|
yield { type: "done" };
|
|
3171
3802
|
return;
|
|
3172
3803
|
}
|
|
@@ -3184,4 +3815,153 @@ export async function* executeToolEngine(args) {
|
|
|
3184
3815
|
}
|
|
3185
3816
|
}
|
|
3186
3817
|
}
|
|
3818
|
+
export function coalesceReadOnlyMessages(messages) {
|
|
3819
|
+
if (!messages || messages.length === 0)
|
|
3820
|
+
return messages;
|
|
3821
|
+
const READ_ONLY_TOOLS = new Set([
|
|
3822
|
+
"read_file",
|
|
3823
|
+
"grep",
|
|
3824
|
+
"bash_output_get",
|
|
3825
|
+
"process_list",
|
|
3826
|
+
"delegation_read",
|
|
3827
|
+
"delegation_list",
|
|
3828
|
+
"ee_query",
|
|
3829
|
+
"ee_health",
|
|
3830
|
+
"usage_forensics",
|
|
3831
|
+
"lsp_query",
|
|
3832
|
+
"setup_guide",
|
|
3833
|
+
"selfverify_status",
|
|
3834
|
+
"selfverify_result",
|
|
3835
|
+
"selfverify_list",
|
|
3836
|
+
"list_vision_cache",
|
|
3837
|
+
"ee_feedback",
|
|
3838
|
+
"ee_write",
|
|
3839
|
+
]);
|
|
3840
|
+
const result = [];
|
|
3841
|
+
let lastUserIdx = -1;
|
|
3842
|
+
for (let k = messages.length - 1; k >= 0; k--) {
|
|
3843
|
+
if (messages[k].role === "user") {
|
|
3844
|
+
lastUserIdx = k;
|
|
3845
|
+
break;
|
|
3846
|
+
}
|
|
3847
|
+
}
|
|
3848
|
+
if (lastUserIdx === -1)
|
|
3849
|
+
return messages;
|
|
3850
|
+
for (let k = 0; k <= lastUserIdx; k++) {
|
|
3851
|
+
result.push(messages[k]);
|
|
3852
|
+
}
|
|
3853
|
+
const postUser = messages.slice(lastUserIdx + 1);
|
|
3854
|
+
const groups = [];
|
|
3855
|
+
let currentAsst = null;
|
|
3856
|
+
for (const msg of postUser) {
|
|
3857
|
+
if (msg.role === "assistant") {
|
|
3858
|
+
if (currentAsst) {
|
|
3859
|
+
groups.push({ assistant: currentAsst, tool: null });
|
|
3860
|
+
}
|
|
3861
|
+
currentAsst = msg;
|
|
3862
|
+
}
|
|
3863
|
+
else if (msg.role === "tool") {
|
|
3864
|
+
if (currentAsst) {
|
|
3865
|
+
groups.push({ assistant: currentAsst, tool: msg });
|
|
3866
|
+
currentAsst = null;
|
|
3867
|
+
}
|
|
3868
|
+
else {
|
|
3869
|
+
result.push(msg);
|
|
3870
|
+
}
|
|
3871
|
+
}
|
|
3872
|
+
else {
|
|
3873
|
+
if (currentAsst) {
|
|
3874
|
+
groups.push({ assistant: currentAsst, tool: null });
|
|
3875
|
+
currentAsst = null;
|
|
3876
|
+
}
|
|
3877
|
+
result.push(msg);
|
|
3878
|
+
}
|
|
3879
|
+
}
|
|
3880
|
+
if (currentAsst) {
|
|
3881
|
+
groups.push({ assistant: currentAsst, tool: null });
|
|
3882
|
+
}
|
|
3883
|
+
const coalescedGroups = [];
|
|
3884
|
+
for (const group of groups) {
|
|
3885
|
+
const isReadOnly = (() => {
|
|
3886
|
+
if (!group.tool || !group.assistant)
|
|
3887
|
+
return false;
|
|
3888
|
+
const toolCalls = getToolCalls(group.assistant);
|
|
3889
|
+
if (toolCalls.length === 0)
|
|
3890
|
+
return false;
|
|
3891
|
+
return toolCalls.every((tc) => {
|
|
3892
|
+
const name = tc.toolName || tc.function?.name;
|
|
3893
|
+
return name && READ_ONLY_TOOLS.has(name);
|
|
3894
|
+
});
|
|
3895
|
+
})();
|
|
3896
|
+
if (isReadOnly) {
|
|
3897
|
+
const prev = coalescedGroups[coalescedGroups.length - 1];
|
|
3898
|
+
const prevIsReadOnly = prev &&
|
|
3899
|
+
(() => {
|
|
3900
|
+
if (!prev.tool || !prev.assistant)
|
|
3901
|
+
return false;
|
|
3902
|
+
const toolCalls = getToolCalls(prev.assistant);
|
|
3903
|
+
if (toolCalls.length === 0)
|
|
3904
|
+
return false;
|
|
3905
|
+
return toolCalls.every((tc) => {
|
|
3906
|
+
const name = tc.toolName || tc.function?.name;
|
|
3907
|
+
return name && READ_ONLY_TOOLS.has(name);
|
|
3908
|
+
});
|
|
3909
|
+
})();
|
|
3910
|
+
if (prevIsReadOnly) {
|
|
3911
|
+
prev.assistant = mergeAssistantMessages(prev.assistant, group.assistant);
|
|
3912
|
+
prev.tool = mergeToolMessages(prev.tool, group.tool);
|
|
3913
|
+
}
|
|
3914
|
+
else {
|
|
3915
|
+
coalescedGroups.push({ ...group });
|
|
3916
|
+
}
|
|
3917
|
+
}
|
|
3918
|
+
else {
|
|
3919
|
+
coalescedGroups.push(group);
|
|
3920
|
+
}
|
|
3921
|
+
}
|
|
3922
|
+
for (const group of coalescedGroups) {
|
|
3923
|
+
if (group.assistant)
|
|
3924
|
+
result.push(group.assistant);
|
|
3925
|
+
if (group.tool)
|
|
3926
|
+
result.push(group.tool);
|
|
3927
|
+
}
|
|
3928
|
+
return result;
|
|
3929
|
+
}
|
|
3930
|
+
function getToolCalls(msg) {
|
|
3931
|
+
if (Array.isArray(msg.toolCalls))
|
|
3932
|
+
return msg.toolCalls;
|
|
3933
|
+
if (Array.isArray(msg.content)) {
|
|
3934
|
+
return msg.content.filter((p) => p && p.type === "tool-call");
|
|
3935
|
+
}
|
|
3936
|
+
return [];
|
|
3937
|
+
}
|
|
3938
|
+
function mergeAssistantMessages(msg1, msg2) {
|
|
3939
|
+
const parts1 = Array.isArray(msg1.content)
|
|
3940
|
+
? msg1.content
|
|
3941
|
+
: typeof msg1.content === "string" && msg1.content
|
|
3942
|
+
? [{ type: "text", text: msg1.content }]
|
|
3943
|
+
: [];
|
|
3944
|
+
const parts2 = Array.isArray(msg2.content)
|
|
3945
|
+
? msg2.content
|
|
3946
|
+
: typeof msg2.content === "string" && msg2.content
|
|
3947
|
+
? [{ type: "text", text: msg2.content }]
|
|
3948
|
+
: [];
|
|
3949
|
+
const mergedContent = [...parts1, ...parts2];
|
|
3950
|
+
const res = {
|
|
3951
|
+
role: "assistant",
|
|
3952
|
+
content: mergedContent,
|
|
3953
|
+
};
|
|
3954
|
+
if (Array.isArray(msg1.toolCalls) || Array.isArray(msg2.toolCalls)) {
|
|
3955
|
+
res.toolCalls = [...(msg1.toolCalls ?? []), ...(msg2.toolCalls ?? [])];
|
|
3956
|
+
}
|
|
3957
|
+
return res;
|
|
3958
|
+
}
|
|
3959
|
+
function mergeToolMessages(msg1, msg2) {
|
|
3960
|
+
const parts1 = Array.isArray(msg1.content) ? msg1.content : [];
|
|
3961
|
+
const parts2 = Array.isArray(msg2.content) ? msg2.content : [];
|
|
3962
|
+
return {
|
|
3963
|
+
role: "tool",
|
|
3964
|
+
content: [...parts1, ...parts2],
|
|
3965
|
+
};
|
|
3966
|
+
}
|
|
3187
3967
|
//# sourceMappingURL=tool-engine.js.map
|