attocode 0.2.4 → 0.2.5
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/CHANGELOG.md +56 -1
- package/dist/src/adapters.d.ts +2 -1
- package/dist/src/adapters.d.ts.map +1 -1
- package/dist/src/adapters.js +60 -2
- package/dist/src/adapters.js.map +1 -1
- package/dist/src/agent/agent-builder.d.ts +117 -0
- package/dist/src/agent/agent-builder.d.ts.map +1 -0
- package/dist/src/agent/agent-builder.js +204 -0
- package/dist/src/agent/agent-builder.js.map +1 -0
- package/dist/src/agent/feature-initializer.d.ts +80 -0
- package/dist/src/agent/feature-initializer.d.ts.map +1 -0
- package/dist/src/agent/feature-initializer.js +677 -0
- package/dist/src/agent/feature-initializer.js.map +1 -0
- package/dist/src/agent/index.d.ts +13 -0
- package/dist/src/agent/index.d.ts.map +1 -0
- package/dist/src/agent/index.js +13 -0
- package/dist/src/agent/index.js.map +1 -0
- package/dist/src/agent/message-builder.d.ts +50 -0
- package/dist/src/agent/message-builder.d.ts.map +1 -0
- package/dist/src/agent/message-builder.js +173 -0
- package/dist/src/agent/message-builder.js.map +1 -0
- package/dist/src/agent/session-api.d.ts +94 -0
- package/dist/src/agent/session-api.d.ts.map +1 -0
- package/dist/src/agent/session-api.js +262 -0
- package/dist/src/agent/session-api.js.map +1 -0
- package/dist/src/agent-tools/lsp-file-tools.d.ts +1 -1
- package/dist/src/agent-tools/lsp-file-tools.d.ts.map +1 -1
- package/dist/src/agent.d.ts +14 -115
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +36 -1177
- package/dist/src/agent.js.map +1 -1
- package/dist/src/cli.js +1 -1
- package/dist/src/cli.js.map +1 -1
- package/dist/src/commands/handler.d.ts.map +1 -1
- package/dist/src/commands/handler.js +8 -7
- package/dist/src/commands/handler.js.map +1 -1
- package/dist/src/commands/init.js +1 -1
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/config/schema.d.ts +6 -6
- package/dist/src/core/execution-loop.d.ts.map +1 -1
- package/dist/src/core/execution-loop.js +155 -16
- package/dist/src/core/execution-loop.js.map +1 -1
- package/dist/src/core/response-handler.d.ts.map +1 -1
- package/dist/src/core/response-handler.js +3 -2
- package/dist/src/core/response-handler.js.map +1 -1
- package/dist/src/core/subagent-spawner.d.ts.map +1 -1
- package/dist/src/core/subagent-spawner.js +13 -6
- package/dist/src/core/subagent-spawner.js.map +1 -1
- package/dist/src/core/tool-executor.d.ts.map +1 -1
- package/dist/src/core/tool-executor.js +7 -2
- package/dist/src/core/tool-executor.js.map +1 -1
- package/dist/src/core/types.d.ts +1 -0
- package/dist/src/core/types.d.ts.map +1 -1
- package/dist/src/core/types.js.map +1 -1
- package/dist/src/integrations/agents/agent-registry.d.ts +262 -0
- package/dist/src/integrations/agents/agent-registry.d.ts.map +1 -0
- package/dist/src/integrations/agents/agent-registry.js +686 -0
- package/dist/src/integrations/agents/agent-registry.js.map +1 -0
- package/dist/src/integrations/agents/async-subagent.d.ts +135 -0
- package/dist/src/integrations/agents/async-subagent.d.ts.map +1 -0
- package/dist/src/integrations/agents/async-subagent.js +213 -0
- package/dist/src/integrations/agents/async-subagent.js.map +1 -0
- package/dist/src/integrations/agents/complexity-classifier.d.ts +86 -0
- package/dist/src/integrations/agents/complexity-classifier.d.ts.map +1 -0
- package/dist/src/integrations/agents/complexity-classifier.js +233 -0
- package/dist/src/integrations/agents/complexity-classifier.js.map +1 -0
- package/dist/src/integrations/agents/delegation-protocol.d.ts +86 -0
- package/dist/src/integrations/agents/delegation-protocol.d.ts.map +1 -0
- package/dist/src/integrations/agents/delegation-protocol.js +127 -0
- package/dist/src/integrations/agents/delegation-protocol.js.map +1 -0
- package/dist/src/integrations/agents/multi-agent.d.ts +150 -0
- package/dist/src/integrations/agents/multi-agent.d.ts.map +1 -0
- package/dist/src/integrations/agents/multi-agent.js +306 -0
- package/dist/src/integrations/agents/multi-agent.js.map +1 -0
- package/dist/src/integrations/agents/result-synthesizer.d.ts +389 -0
- package/dist/src/integrations/agents/result-synthesizer.d.ts.map +1 -0
- package/dist/src/integrations/agents/result-synthesizer.js +951 -0
- package/dist/src/integrations/agents/result-synthesizer.js.map +1 -0
- package/dist/src/integrations/agents/shared-blackboard.d.ts +406 -0
- package/dist/src/integrations/agents/shared-blackboard.d.ts.map +1 -0
- package/dist/src/integrations/agents/shared-blackboard.js +757 -0
- package/dist/src/integrations/agents/shared-blackboard.js.map +1 -0
- package/dist/src/integrations/agents/subagent-output-store.d.ts +91 -0
- package/dist/src/integrations/agents/subagent-output-store.d.ts.map +1 -0
- package/dist/src/integrations/agents/subagent-output-store.js +257 -0
- package/dist/src/integrations/agents/subagent-output-store.js.map +1 -0
- package/dist/src/integrations/budget/budget-pool.d.ts +115 -0
- package/dist/src/integrations/budget/budget-pool.d.ts.map +1 -0
- package/dist/src/integrations/budget/budget-pool.js +205 -0
- package/dist/src/integrations/budget/budget-pool.js.map +1 -0
- package/dist/src/integrations/budget/cancellation.d.ts +229 -0
- package/dist/src/integrations/budget/cancellation.d.ts.map +1 -0
- package/dist/src/integrations/budget/cancellation.js +520 -0
- package/dist/src/integrations/budget/cancellation.js.map +1 -0
- package/dist/src/integrations/budget/dynamic-budget.d.ts +81 -0
- package/dist/src/integrations/budget/dynamic-budget.d.ts.map +1 -0
- package/dist/src/integrations/budget/dynamic-budget.js +151 -0
- package/dist/src/integrations/budget/dynamic-budget.js.map +1 -0
- package/dist/src/integrations/budget/economics.d.ts +435 -0
- package/dist/src/integrations/budget/economics.d.ts.map +1 -0
- package/dist/src/integrations/budget/economics.js +1007 -0
- package/dist/src/integrations/budget/economics.js.map +1 -0
- package/dist/src/integrations/budget/injection-budget.d.ts +71 -0
- package/dist/src/integrations/budget/injection-budget.d.ts.map +1 -0
- package/dist/src/integrations/budget/injection-budget.js +137 -0
- package/dist/src/integrations/budget/injection-budget.js.map +1 -0
- package/dist/src/integrations/budget/loop-detector.d.ts +105 -0
- package/dist/src/integrations/budget/loop-detector.d.ts.map +1 -0
- package/dist/src/integrations/budget/loop-detector.js +287 -0
- package/dist/src/integrations/budget/loop-detector.js.map +1 -0
- package/dist/src/integrations/budget/phase-tracker.d.ts +114 -0
- package/dist/src/integrations/budget/phase-tracker.d.ts.map +1 -0
- package/dist/src/integrations/budget/phase-tracker.js +262 -0
- package/dist/src/integrations/budget/phase-tracker.js.map +1 -0
- package/dist/src/integrations/budget/resources.d.ts +182 -0
- package/dist/src/integrations/budget/resources.d.ts.map +1 -0
- package/dist/src/integrations/budget/resources.js +318 -0
- package/dist/src/integrations/budget/resources.js.map +1 -0
- package/dist/src/integrations/context/auto-compaction.d.ts +210 -0
- package/dist/src/integrations/context/auto-compaction.d.ts.map +1 -0
- package/dist/src/integrations/context/auto-compaction.js +477 -0
- package/dist/src/integrations/context/auto-compaction.js.map +1 -0
- package/dist/src/integrations/context/code-analyzer.d.ts +71 -0
- package/dist/src/integrations/context/code-analyzer.d.ts.map +1 -0
- package/dist/src/integrations/context/code-analyzer.js +448 -0
- package/dist/src/integrations/context/code-analyzer.js.map +1 -0
- package/dist/src/integrations/context/code-selector.d.ts +78 -0
- package/dist/src/integrations/context/code-selector.d.ts.map +1 -0
- package/dist/src/integrations/context/code-selector.js +649 -0
- package/dist/src/integrations/context/code-selector.js.map +1 -0
- package/dist/src/integrations/context/codebase-ast.d.ts +138 -0
- package/dist/src/integrations/context/codebase-ast.d.ts.map +1 -0
- package/dist/src/integrations/context/codebase-ast.js +818 -0
- package/dist/src/integrations/context/codebase-ast.js.map +1 -0
- package/dist/src/integrations/context/codebase-context.d.ts +473 -0
- package/dist/src/integrations/context/codebase-context.d.ts.map +1 -0
- package/dist/src/integrations/context/codebase-context.js +685 -0
- package/dist/src/integrations/context/codebase-context.js.map +1 -0
- package/dist/src/integrations/context/compaction.d.ts +191 -0
- package/dist/src/integrations/context/compaction.d.ts.map +1 -0
- package/dist/src/integrations/context/compaction.js +384 -0
- package/dist/src/integrations/context/compaction.js.map +1 -0
- package/dist/src/integrations/context/context-engineering.d.ts +274 -0
- package/dist/src/integrations/context/context-engineering.d.ts.map +1 -0
- package/dist/src/integrations/context/context-engineering.js +437 -0
- package/dist/src/integrations/context/context-engineering.js.map +1 -0
- package/dist/src/integrations/context/file-cache.d.ts +97 -0
- package/dist/src/integrations/context/file-cache.d.ts.map +1 -0
- package/dist/src/integrations/context/file-cache.js +218 -0
- package/dist/src/integrations/context/file-cache.js.map +1 -0
- package/dist/src/integrations/context/semantic-cache.d.ts +178 -0
- package/dist/src/integrations/context/semantic-cache.d.ts.map +1 -0
- package/dist/src/integrations/context/semantic-cache.js +372 -0
- package/dist/src/integrations/context/semantic-cache.js.map +1 -0
- package/dist/src/integrations/index.d.ts +72 -68
- package/dist/src/integrations/index.d.ts.map +1 -1
- package/dist/src/integrations/index.js +76 -68
- package/dist/src/integrations/index.js.map +1 -1
- package/dist/src/integrations/lsp/lsp.d.ts +196 -0
- package/dist/src/integrations/lsp/lsp.d.ts.map +1 -0
- package/dist/src/integrations/lsp/lsp.js +583 -0
- package/dist/src/integrations/lsp/lsp.js.map +1 -0
- package/dist/src/integrations/mcp/mcp-client.d.ts +279 -0
- package/dist/src/integrations/mcp/mcp-client.d.ts.map +1 -0
- package/dist/src/integrations/mcp/mcp-client.js +755 -0
- package/dist/src/integrations/mcp/mcp-client.js.map +1 -0
- package/dist/src/integrations/mcp/mcp-custom-tools.d.ts +102 -0
- package/dist/src/integrations/mcp/mcp-custom-tools.d.ts.map +1 -0
- package/dist/src/integrations/mcp/mcp-custom-tools.js +232 -0
- package/dist/src/integrations/mcp/mcp-custom-tools.js.map +1 -0
- package/dist/src/integrations/mcp/mcp-tool-search.d.ts +77 -0
- package/dist/src/integrations/mcp/mcp-tool-search.d.ts.map +1 -0
- package/dist/src/integrations/mcp/mcp-tool-search.js +220 -0
- package/dist/src/integrations/mcp/mcp-tool-search.js.map +1 -0
- package/dist/src/integrations/mcp/mcp-tool-validator.d.ts +60 -0
- package/dist/src/integrations/mcp/mcp-tool-validator.d.ts.map +1 -0
- package/dist/src/integrations/mcp/mcp-tool-validator.js +141 -0
- package/dist/src/integrations/mcp/mcp-tool-validator.js.map +1 -0
- package/dist/src/integrations/persistence/codebase-repository.d.ts +45 -0
- package/dist/src/integrations/persistence/codebase-repository.d.ts.map +1 -0
- package/dist/src/integrations/persistence/codebase-repository.js +81 -0
- package/dist/src/integrations/persistence/codebase-repository.js.map +1 -0
- package/dist/src/integrations/persistence/goal-repository.d.ts +71 -0
- package/dist/src/integrations/persistence/goal-repository.d.ts.map +1 -0
- package/dist/src/integrations/persistence/goal-repository.js +184 -0
- package/dist/src/integrations/persistence/goal-repository.js.map +1 -0
- package/dist/src/integrations/persistence/history.d.ts +72 -0
- package/dist/src/integrations/persistence/history.d.ts.map +1 -0
- package/dist/src/integrations/persistence/history.js +165 -0
- package/dist/src/integrations/persistence/history.js.map +1 -0
- package/dist/src/integrations/persistence/persistence.d.ts +49 -0
- package/dist/src/integrations/persistence/persistence.d.ts.map +1 -0
- package/dist/src/integrations/persistence/persistence.js +197 -0
- package/dist/src/integrations/persistence/persistence.js.map +1 -0
- package/dist/src/integrations/persistence/session-repository.d.ts +212 -0
- package/dist/src/integrations/persistence/session-repository.d.ts.map +1 -0
- package/dist/src/integrations/persistence/session-repository.js +770 -0
- package/dist/src/integrations/persistence/session-repository.js.map +1 -0
- package/dist/src/integrations/persistence/session-store.d.ts +184 -0
- package/dist/src/integrations/persistence/session-store.d.ts.map +1 -0
- package/dist/src/integrations/persistence/session-store.js +346 -0
- package/dist/src/integrations/persistence/session-store.js.map +1 -0
- package/dist/src/integrations/persistence/sqlite-store.d.ts +453 -0
- package/dist/src/integrations/persistence/sqlite-store.d.ts.map +1 -0
- package/dist/src/integrations/persistence/sqlite-store.js +676 -0
- package/dist/src/integrations/persistence/sqlite-store.js.map +1 -0
- package/dist/src/integrations/persistence/worker-repository.d.ts +65 -0
- package/dist/src/integrations/persistence/worker-repository.d.ts.map +1 -0
- package/dist/src/integrations/persistence/worker-repository.js +183 -0
- package/dist/src/integrations/persistence/worker-repository.js.map +1 -0
- package/dist/src/integrations/quality/auto-checkpoint.d.ts +98 -0
- package/dist/src/integrations/quality/auto-checkpoint.d.ts.map +1 -0
- package/dist/src/integrations/quality/auto-checkpoint.js +252 -0
- package/dist/src/integrations/quality/auto-checkpoint.js.map +1 -0
- package/dist/src/integrations/quality/dead-letter-queue.d.ts +233 -0
- package/dist/src/integrations/quality/dead-letter-queue.d.ts.map +1 -0
- package/dist/src/integrations/quality/dead-letter-queue.js +543 -0
- package/dist/src/integrations/quality/dead-letter-queue.js.map +1 -0
- package/dist/src/integrations/quality/health-check.d.ts +218 -0
- package/dist/src/integrations/quality/health-check.d.ts.map +1 -0
- package/dist/src/integrations/quality/health-check.js +415 -0
- package/dist/src/integrations/quality/health-check.js.map +1 -0
- package/dist/src/integrations/quality/learning-store.d.ts +291 -0
- package/dist/src/integrations/quality/learning-store.d.ts.map +1 -0
- package/dist/src/integrations/quality/learning-store.js +646 -0
- package/dist/src/integrations/quality/learning-store.js.map +1 -0
- package/dist/src/integrations/quality/self-improvement.d.ts +90 -0
- package/dist/src/integrations/quality/self-improvement.d.ts.map +1 -0
- package/dist/src/integrations/quality/self-improvement.js +229 -0
- package/dist/src/integrations/quality/self-improvement.js.map +1 -0
- package/dist/src/integrations/quality/tool-recommendation.d.ts +61 -0
- package/dist/src/integrations/quality/tool-recommendation.d.ts.map +1 -0
- package/dist/src/integrations/quality/tool-recommendation.js +268 -0
- package/dist/src/integrations/quality/tool-recommendation.js.map +1 -0
- package/dist/src/integrations/safety/bash-policy.d.ts +33 -0
- package/dist/src/integrations/safety/bash-policy.d.ts.map +1 -0
- package/dist/src/integrations/safety/bash-policy.js +144 -0
- package/dist/src/integrations/safety/bash-policy.js.map +1 -0
- package/dist/src/integrations/safety/edit-validator.d.ts +30 -0
- package/dist/src/integrations/safety/edit-validator.d.ts.map +1 -0
- package/dist/src/integrations/safety/edit-validator.js +87 -0
- package/dist/src/integrations/safety/edit-validator.js.map +1 -0
- package/dist/src/integrations/safety/execution-policy.d.ts +189 -0
- package/dist/src/integrations/safety/execution-policy.d.ts.map +1 -0
- package/dist/src/integrations/safety/execution-policy.js +352 -0
- package/dist/src/integrations/safety/execution-policy.js.map +1 -0
- package/dist/src/integrations/safety/policy-engine.d.ts +55 -0
- package/dist/src/integrations/safety/policy-engine.d.ts.map +1 -0
- package/dist/src/integrations/safety/policy-engine.js +247 -0
- package/dist/src/integrations/safety/policy-engine.js.map +1 -0
- package/dist/src/integrations/safety/safety.d.ts +174 -0
- package/dist/src/integrations/safety/safety.d.ts.map +1 -0
- package/dist/src/integrations/safety/safety.js +470 -0
- package/dist/src/integrations/safety/safety.js.map +1 -0
- package/dist/src/integrations/safety/sandbox/basic.d.ts +81 -0
- package/dist/src/integrations/safety/sandbox/basic.d.ts.map +1 -0
- package/dist/src/integrations/safety/sandbox/basic.js +335 -0
- package/dist/src/integrations/safety/sandbox/basic.js.map +1 -0
- package/dist/src/integrations/safety/sandbox/docker.d.ts +94 -0
- package/dist/src/integrations/safety/sandbox/docker.d.ts.map +1 -0
- package/dist/src/integrations/safety/sandbox/docker.js +294 -0
- package/dist/src/integrations/safety/sandbox/docker.js.map +1 -0
- package/dist/src/integrations/safety/sandbox/index.d.ts +188 -0
- package/dist/src/integrations/safety/sandbox/index.d.ts.map +1 -0
- package/dist/src/integrations/safety/sandbox/index.js +386 -0
- package/dist/src/integrations/safety/sandbox/index.js.map +1 -0
- package/dist/src/integrations/safety/sandbox/landlock.d.ts +59 -0
- package/dist/src/integrations/safety/sandbox/landlock.d.ts.map +1 -0
- package/dist/src/integrations/safety/sandbox/landlock.js +329 -0
- package/dist/src/integrations/safety/sandbox/landlock.js.map +1 -0
- package/dist/src/integrations/safety/sandbox/seatbelt.d.ts +68 -0
- package/dist/src/integrations/safety/sandbox/seatbelt.d.ts.map +1 -0
- package/dist/src/integrations/safety/sandbox/seatbelt.js +298 -0
- package/dist/src/integrations/safety/sandbox/seatbelt.js.map +1 -0
- package/dist/src/integrations/safety/type-checker.d.ts +53 -0
- package/dist/src/integrations/safety/type-checker.d.ts.map +1 -0
- package/dist/src/integrations/safety/type-checker.js +142 -0
- package/dist/src/integrations/safety/type-checker.js.map +1 -0
- package/dist/src/integrations/skills/skill-executor.d.ts +113 -0
- package/dist/src/integrations/skills/skill-executor.d.ts.map +1 -0
- package/dist/src/integrations/skills/skill-executor.js +270 -0
- package/dist/src/integrations/skills/skill-executor.js.map +1 -0
- package/dist/src/integrations/skills/skills.d.ts +262 -0
- package/dist/src/integrations/skills/skills.d.ts.map +1 -0
- package/dist/src/integrations/skills/skills.js +602 -0
- package/dist/src/integrations/skills/skills.js.map +1 -0
- package/dist/src/integrations/streaming/pty-shell.d.ts +169 -0
- package/dist/src/integrations/streaming/pty-shell.d.ts.map +1 -0
- package/dist/src/integrations/streaming/pty-shell.js +367 -0
- package/dist/src/integrations/streaming/pty-shell.js.map +1 -0
- package/dist/src/integrations/streaming/streaming.d.ts +102 -0
- package/dist/src/integrations/streaming/streaming.d.ts.map +1 -0
- package/dist/src/integrations/streaming/streaming.js +362 -0
- package/dist/src/integrations/streaming/streaming.js.map +1 -0
- package/dist/src/integrations/swarm/index.d.ts +2 -1
- package/dist/src/integrations/swarm/index.d.ts.map +1 -1
- package/dist/src/integrations/swarm/index.js +2 -0
- package/dist/src/integrations/swarm/index.js.map +1 -1
- package/dist/src/integrations/swarm/model-selector.js +1 -1
- package/dist/src/integrations/swarm/model-selector.js.map +1 -1
- package/dist/src/integrations/swarm/swarm-budget.d.ts +1 -1
- package/dist/src/integrations/swarm/swarm-budget.d.ts.map +1 -1
- package/dist/src/integrations/swarm/swarm-budget.js +1 -1
- package/dist/src/integrations/swarm/swarm-budget.js.map +1 -1
- package/dist/src/integrations/swarm/swarm-config-loader.d.ts.map +1 -1
- package/dist/src/integrations/swarm/swarm-config-loader.js +7 -0
- package/dist/src/integrations/swarm/swarm-config-loader.js.map +1 -1
- package/dist/src/integrations/swarm/swarm-events.d.ts +1 -1
- package/dist/src/integrations/swarm/swarm-events.d.ts.map +1 -1
- package/dist/src/integrations/swarm/swarm-execution.d.ts +27 -0
- package/dist/src/integrations/swarm/swarm-execution.d.ts.map +1 -0
- package/dist/src/integrations/swarm/swarm-execution.js +1021 -0
- package/dist/src/integrations/swarm/swarm-execution.js.map +1 -0
- package/dist/src/integrations/swarm/swarm-helpers.d.ts +26 -0
- package/dist/src/integrations/swarm/swarm-helpers.d.ts.map +1 -0
- package/dist/src/integrations/swarm/swarm-helpers.js +95 -0
- package/dist/src/integrations/swarm/swarm-helpers.js.map +1 -0
- package/dist/src/integrations/swarm/swarm-lifecycle.d.ts +100 -0
- package/dist/src/integrations/swarm/swarm-lifecycle.d.ts.map +1 -0
- package/dist/src/integrations/swarm/swarm-lifecycle.js +922 -0
- package/dist/src/integrations/swarm/swarm-lifecycle.js.map +1 -0
- package/dist/src/integrations/swarm/swarm-orchestrator.d.ts +84 -203
- package/dist/src/integrations/swarm/swarm-orchestrator.d.ts.map +1 -1
- package/dist/src/integrations/swarm/swarm-orchestrator.js +251 -2870
- package/dist/src/integrations/swarm/swarm-orchestrator.js.map +1 -1
- package/dist/src/integrations/swarm/swarm-quality-gate.js +1 -1
- package/dist/src/integrations/swarm/swarm-quality-gate.js.map +1 -1
- package/dist/src/integrations/swarm/swarm-recovery.d.ts +75 -0
- package/dist/src/integrations/swarm/swarm-recovery.d.ts.map +1 -0
- package/dist/src/integrations/swarm/swarm-recovery.js +550 -0
- package/dist/src/integrations/swarm/swarm-recovery.js.map +1 -0
- package/dist/src/integrations/swarm/swarm-state-store.d.ts.map +1 -1
- package/dist/src/integrations/swarm/swarm-state-store.js +6 -0
- package/dist/src/integrations/swarm/swarm-state-store.js.map +1 -1
- package/dist/src/integrations/swarm/task-queue.d.ts +1 -1
- package/dist/src/integrations/swarm/task-queue.d.ts.map +1 -1
- package/dist/src/integrations/swarm/task-queue.js +28 -1
- package/dist/src/integrations/swarm/task-queue.js.map +1 -1
- package/dist/src/integrations/swarm/types.d.ts +17 -5
- package/dist/src/integrations/swarm/types.d.ts.map +1 -1
- package/dist/src/integrations/swarm/types.js.map +1 -1
- package/dist/src/integrations/swarm/worker-pool.d.ts +1 -1
- package/dist/src/integrations/swarm/worker-pool.d.ts.map +1 -1
- package/dist/src/integrations/swarm/worker-pool.js +13 -9
- package/dist/src/integrations/swarm/worker-pool.js.map +1 -1
- package/dist/src/integrations/tasks/dependency-analyzer.d.ts +34 -0
- package/dist/src/integrations/tasks/dependency-analyzer.d.ts.map +1 -0
- package/dist/src/integrations/tasks/dependency-analyzer.js +232 -0
- package/dist/src/integrations/tasks/dependency-analyzer.js.map +1 -0
- package/dist/src/integrations/tasks/interactive-planning.d.ts +322 -0
- package/dist/src/integrations/tasks/interactive-planning.d.ts.map +1 -0
- package/dist/src/integrations/tasks/interactive-planning.js +655 -0
- package/dist/src/integrations/tasks/interactive-planning.js.map +1 -0
- package/dist/src/integrations/tasks/pending-plan.d.ts +196 -0
- package/dist/src/integrations/tasks/pending-plan.d.ts.map +1 -0
- package/dist/src/integrations/tasks/pending-plan.js +431 -0
- package/dist/src/integrations/tasks/pending-plan.js.map +1 -0
- package/dist/src/integrations/tasks/planning.d.ts +115 -0
- package/dist/src/integrations/tasks/planning.d.ts.map +1 -0
- package/dist/src/integrations/tasks/planning.js +413 -0
- package/dist/src/integrations/tasks/planning.js.map +1 -0
- package/dist/src/integrations/tasks/smart-decomposer.d.ts +316 -0
- package/dist/src/integrations/tasks/smart-decomposer.d.ts.map +1 -0
- package/dist/src/integrations/tasks/smart-decomposer.js +661 -0
- package/dist/src/integrations/tasks/smart-decomposer.js.map +1 -0
- package/dist/src/integrations/tasks/task-manager.d.ts +164 -0
- package/dist/src/integrations/tasks/task-manager.d.ts.map +1 -0
- package/dist/src/integrations/tasks/task-manager.js +383 -0
- package/dist/src/integrations/tasks/task-manager.js.map +1 -0
- package/dist/src/integrations/tasks/task-splitter.d.ts +56 -0
- package/dist/src/integrations/tasks/task-splitter.d.ts.map +1 -0
- package/dist/src/integrations/tasks/task-splitter.js +537 -0
- package/dist/src/integrations/tasks/task-splitter.js.map +1 -0
- package/dist/src/integrations/tasks/verification-gate.d.ts +103 -0
- package/dist/src/integrations/tasks/verification-gate.d.ts.map +1 -0
- package/dist/src/integrations/tasks/verification-gate.js +193 -0
- package/dist/src/integrations/tasks/verification-gate.js.map +1 -0
- package/dist/src/integrations/tasks/work-log.d.ts +87 -0
- package/dist/src/integrations/tasks/work-log.d.ts.map +1 -0
- package/dist/src/integrations/tasks/work-log.js +275 -0
- package/dist/src/integrations/tasks/work-log.js.map +1 -0
- package/dist/src/integrations/utilities/capabilities.d.ts +160 -0
- package/dist/src/integrations/utilities/capabilities.d.ts.map +1 -0
- package/dist/src/integrations/utilities/capabilities.js +426 -0
- package/dist/src/integrations/utilities/capabilities.js.map +1 -0
- package/dist/src/integrations/utilities/diff-utils.d.ts +105 -0
- package/dist/src/integrations/utilities/diff-utils.d.ts.map +1 -0
- package/dist/src/integrations/utilities/diff-utils.js +497 -0
- package/dist/src/integrations/utilities/diff-utils.js.map +1 -0
- package/dist/src/integrations/utilities/environment-facts.d.ts +52 -0
- package/dist/src/integrations/utilities/environment-facts.d.ts.map +1 -0
- package/dist/src/integrations/utilities/environment-facts.js +84 -0
- package/dist/src/integrations/utilities/environment-facts.js.map +1 -0
- package/dist/src/integrations/utilities/file-change-tracker.d.ts +162 -0
- package/dist/src/integrations/utilities/file-change-tracker.d.ts.map +1 -0
- package/dist/src/integrations/utilities/file-change-tracker.js +538 -0
- package/dist/src/integrations/utilities/file-change-tracker.js.map +1 -0
- package/dist/src/integrations/utilities/graph-visualization.d.ts +72 -0
- package/dist/src/integrations/utilities/graph-visualization.d.ts.map +1 -0
- package/dist/src/integrations/utilities/graph-visualization.js +383 -0
- package/dist/src/integrations/utilities/graph-visualization.js.map +1 -0
- package/dist/src/integrations/utilities/hierarchical-config.d.ts +215 -0
- package/dist/src/integrations/utilities/hierarchical-config.d.ts.map +1 -0
- package/dist/src/integrations/utilities/hierarchical-config.js +504 -0
- package/dist/src/integrations/utilities/hierarchical-config.js.map +1 -0
- package/dist/src/integrations/utilities/hooks.d.ts +116 -0
- package/dist/src/integrations/utilities/hooks.d.ts.map +1 -0
- package/dist/src/integrations/utilities/hooks.js +410 -0
- package/dist/src/integrations/utilities/hooks.js.map +1 -0
- package/dist/src/integrations/utilities/ignore.d.ts +143 -0
- package/dist/src/integrations/utilities/ignore.d.ts.map +1 -0
- package/dist/src/integrations/utilities/ignore.js +417 -0
- package/dist/src/integrations/utilities/ignore.js.map +1 -0
- package/dist/src/integrations/utilities/image-renderer.d.ts +119 -0
- package/dist/src/integrations/utilities/image-renderer.d.ts.map +1 -0
- package/dist/src/integrations/utilities/image-renderer.js +306 -0
- package/dist/src/integrations/utilities/image-renderer.js.map +1 -0
- package/dist/src/integrations/utilities/logger.d.ts +104 -0
- package/dist/src/integrations/utilities/logger.d.ts.map +1 -0
- package/dist/src/integrations/utilities/logger.js +219 -0
- package/dist/src/integrations/utilities/logger.js.map +1 -0
- package/dist/src/integrations/utilities/memory.d.ts +116 -0
- package/dist/src/integrations/utilities/memory.d.ts.map +1 -0
- package/dist/src/integrations/utilities/memory.js +311 -0
- package/dist/src/integrations/utilities/memory.js.map +1 -0
- package/dist/src/integrations/utilities/observability.d.ts +162 -0
- package/dist/src/integrations/utilities/observability.d.ts.map +1 -0
- package/dist/src/integrations/utilities/observability.js +407 -0
- package/dist/src/integrations/utilities/observability.js.map +1 -0
- package/dist/src/integrations/utilities/openrouter-pricing.d.ts +67 -0
- package/dist/src/integrations/utilities/openrouter-pricing.d.ts.map +1 -0
- package/dist/src/integrations/utilities/openrouter-pricing.js +166 -0
- package/dist/src/integrations/utilities/openrouter-pricing.js.map +1 -0
- package/dist/src/integrations/utilities/react.d.ts +139 -0
- package/dist/src/integrations/utilities/react.d.ts.map +1 -0
- package/dist/src/integrations/utilities/react.js +273 -0
- package/dist/src/integrations/utilities/react.js.map +1 -0
- package/dist/src/integrations/utilities/retry.d.ts +132 -0
- package/dist/src/integrations/utilities/retry.d.ts.map +1 -0
- package/dist/src/integrations/utilities/retry.js +233 -0
- package/dist/src/integrations/utilities/retry.js.map +1 -0
- package/dist/src/integrations/utilities/routing.d.ts +118 -0
- package/dist/src/integrations/utilities/routing.d.ts.map +1 -0
- package/dist/src/integrations/utilities/routing.js +348 -0
- package/dist/src/integrations/utilities/routing.js.map +1 -0
- package/dist/src/integrations/utilities/rules.d.ts +131 -0
- package/dist/src/integrations/utilities/rules.d.ts.map +1 -0
- package/dist/src/integrations/utilities/rules.js +284 -0
- package/dist/src/integrations/utilities/rules.js.map +1 -0
- package/dist/src/integrations/utilities/sourcegraph.d.ts +169 -0
- package/dist/src/integrations/utilities/sourcegraph.d.ts.map +1 -0
- package/dist/src/integrations/utilities/sourcegraph.js +379 -0
- package/dist/src/integrations/utilities/sourcegraph.js.map +1 -0
- package/dist/src/integrations/utilities/thinking-strategy.d.ts +52 -0
- package/dist/src/integrations/utilities/thinking-strategy.d.ts.map +1 -0
- package/dist/src/integrations/utilities/thinking-strategy.js +129 -0
- package/dist/src/integrations/utilities/thinking-strategy.js.map +1 -0
- package/dist/src/integrations/utilities/thread-manager.d.ts +199 -0
- package/dist/src/integrations/utilities/thread-manager.d.ts.map +1 -0
- package/dist/src/integrations/utilities/thread-manager.js +357 -0
- package/dist/src/integrations/utilities/thread-manager.js.map +1 -0
- package/dist/src/integrations/utilities/token-estimate.d.ts +11 -0
- package/dist/src/integrations/utilities/token-estimate.d.ts.map +1 -0
- package/dist/src/integrations/utilities/token-estimate.js +14 -0
- package/dist/src/integrations/utilities/token-estimate.js.map +1 -0
- package/dist/src/main.js +10 -4
- package/dist/src/main.js.map +1 -1
- package/dist/src/modes/repl.d.ts.map +1 -1
- package/dist/src/modes/repl.js +22 -5
- package/dist/src/modes/repl.js.map +1 -1
- package/dist/src/modes/tui.d.ts.map +1 -1
- package/dist/src/modes/tui.js +23 -6
- package/dist/src/modes/tui.js.map +1 -1
- package/dist/src/modes.js +1 -1
- package/dist/src/modes.js.map +1 -1
- package/dist/src/observability/tracer.js +1 -1
- package/dist/src/observability/tracer.js.map +1 -1
- package/dist/src/persistence/schema.d.ts +2 -0
- package/dist/src/persistence/schema.d.ts.map +1 -1
- package/dist/src/persistence/schema.js +31 -0
- package/dist/src/persistence/schema.js.map +1 -1
- package/dist/src/providers/adapters/anthropic.d.ts +6 -0
- package/dist/src/providers/adapters/anthropic.d.ts.map +1 -1
- package/dist/src/providers/adapters/anthropic.js +99 -15
- package/dist/src/providers/adapters/anthropic.js.map +1 -1
- package/dist/src/providers/adapters/azure.d.ts +74 -0
- package/dist/src/providers/adapters/azure.d.ts.map +1 -0
- package/dist/src/providers/adapters/azure.js +354 -0
- package/dist/src/providers/adapters/azure.js.map +1 -0
- package/dist/src/providers/adapters/mock.d.ts +16 -2
- package/dist/src/providers/adapters/mock.d.ts.map +1 -1
- package/dist/src/providers/adapters/mock.js +44 -3
- package/dist/src/providers/adapters/mock.js.map +1 -1
- package/dist/src/providers/adapters/openai.d.ts +6 -1
- package/dist/src/providers/adapters/openai.d.ts.map +1 -1
- package/dist/src/providers/adapters/openai.js +39 -8
- package/dist/src/providers/adapters/openai.js.map +1 -1
- package/dist/src/providers/adapters/openrouter.d.ts +6 -0
- package/dist/src/providers/adapters/openrouter.d.ts.map +1 -1
- package/dist/src/providers/adapters/openrouter.js +73 -3
- package/dist/src/providers/adapters/openrouter.js.map +1 -1
- package/dist/src/providers/provider.js +1 -1
- package/dist/src/providers/provider.js.map +1 -1
- package/dist/src/providers/resilient-provider.js +1 -1
- package/dist/src/providers/resilient-provider.js.map +1 -1
- package/dist/src/providers/types.d.ts +23 -2
- package/dist/src/providers/types.d.ts.map +1 -1
- package/dist/src/session-picker.d.ts +1 -1
- package/dist/src/session-picker.d.ts.map +1 -1
- package/dist/src/session-picker.js +1 -1
- package/dist/src/session-picker.js.map +1 -1
- package/dist/src/shared/budget-tracker.js +1 -1
- package/dist/src/shared/budget-tracker.js.map +1 -1
- package/dist/src/tools/agent.d.ts +1 -1
- package/dist/src/tools/agent.d.ts.map +1 -1
- package/dist/src/tools/bash.js +1 -1
- package/dist/src/tools/bash.js.map +1 -1
- package/dist/src/tools/file.js +1 -1
- package/dist/src/tools/file.js.map +1 -1
- package/dist/src/tools/permission.js +2 -2
- package/dist/src/tools/permission.js.map +1 -1
- package/dist/src/tools/registry.d.ts +1 -1
- package/dist/src/tools/registry.d.ts.map +1 -1
- package/dist/src/tools/registry.js +1 -1
- package/dist/src/tools/registry.js.map +1 -1
- package/dist/src/tools/tasks.d.ts +1 -1
- package/dist/src/tools/tasks.d.ts.map +1 -1
- package/dist/src/tools/undo.d.ts +1 -1
- package/dist/src/tools/undo.d.ts.map +1 -1
- package/dist/src/tracing/cache-boundary-tracker.d.ts.map +1 -1
- package/dist/src/tracing/cache-boundary-tracker.js +2 -2
- package/dist/src/tracing/cache-boundary-tracker.js.map +1 -1
- package/dist/src/tracing/trace-collector.d.ts +22 -0
- package/dist/src/tracing/trace-collector.d.ts.map +1 -1
- package/dist/src/tracing/trace-collector.js +27 -3
- package/dist/src/tracing/trace-collector.js.map +1 -1
- package/dist/src/tracing/types.d.ts +17 -1
- package/dist/src/tracing/types.d.ts.map +1 -1
- package/dist/src/tracing/types.js.map +1 -1
- package/dist/src/tricks/failure-evidence.js +1 -1
- package/dist/src/tricks/failure-evidence.js.map +1 -1
- package/dist/src/tricks/recitation.d.ts.map +1 -1
- package/dist/src/tricks/recitation.js +2 -1
- package/dist/src/tricks/recitation.js.map +1 -1
- package/dist/src/tricks/recursive-context.d.ts.map +1 -1
- package/dist/src/tricks/recursive-context.js +2 -2
- package/dist/src/tricks/recursive-context.js.map +1 -1
- package/dist/src/tricks/reversible-compaction.d.ts.map +1 -1
- package/dist/src/tricks/reversible-compaction.js +6 -2
- package/dist/src/tricks/reversible-compaction.js.map +1 -1
- package/dist/src/tui/app.d.ts +3 -3
- package/dist/src/tui/app.d.ts.map +1 -1
- package/dist/src/tui/app.js +86 -14
- package/dist/src/tui/app.js.map +1 -1
- package/dist/src/tui/components/CollapsibleDiffView.d.ts +1 -1
- package/dist/src/tui/components/CollapsibleDiffView.d.ts.map +1 -1
- package/dist/src/tui/components/DiagnosticsPanel.d.ts +24 -0
- package/dist/src/tui/components/DiagnosticsPanel.d.ts.map +1 -0
- package/dist/src/tui/components/DiagnosticsPanel.js +47 -0
- package/dist/src/tui/components/DiagnosticsPanel.js.map +1 -0
- package/dist/src/tui/components/DiffView.d.ts +1 -1
- package/dist/src/tui/components/DiffView.d.ts.map +1 -1
- package/dist/src/tui/components/ErrorBoundary.js +1 -1
- package/dist/src/tui/components/ErrorBoundary.js.map +1 -1
- package/dist/src/tui/components/TasksPanel.d.ts +1 -1
- package/dist/src/tui/components/TasksPanel.d.ts.map +1 -1
- package/dist/src/tui/event-display.js +1 -1
- package/dist/src/tui/event-display.js.map +1 -1
- package/dist/src/tui/index.js +1 -1
- package/dist/src/tui/index.js.map +1 -1
- package/dist/src/tui/transparency-aggregator.d.ts +13 -0
- package/dist/src/tui/transparency-aggregator.d.ts.map +1 -1
- package/dist/src/tui/transparency-aggregator.js +21 -0
- package/dist/src/tui/transparency-aggregator.js.map +1 -1
- package/dist/src/types.d.ts +27 -2
- package/dist/src/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,1021 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Swarm Execution — Task dispatch loop, wave management, and completion handling.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from swarm-orchestrator.ts (Phase 3a).
|
|
5
|
+
* Contains: executeWaves, executeWave, dispatchTask, handleTaskCompletion.
|
|
6
|
+
*/
|
|
7
|
+
import { getTaskTypeConfig } from './types.js';
|
|
8
|
+
import { selectAlternativeModel } from './model-selector.js';
|
|
9
|
+
import { evaluateWorkerOutput, runPreFlightChecks, checkArtifacts, checkArtifactsEnhanced, runConcreteChecks } from './swarm-quality-gate.js';
|
|
10
|
+
import { classifySwarmFailure } from './failure-classifier.js';
|
|
11
|
+
import { isHollowCompletion, FAILURE_INDICATORS, hasFutureIntentLanguage } from './swarm-helpers.js';
|
|
12
|
+
import { reviewWave, saveCheckpoint, emitBudgetUpdate, getEffectiveRetries, getSwarmProgressSummary, getModelHealthSummary, extractFileArtifacts, } from './swarm-lifecycle.js';
|
|
13
|
+
import { tryResilienceRecovery, rescueCascadeSkipped, assessAndAdapt, shouldAutoSplit, judgeSplit, recordRateLimit, isCircuitBreakerActive, decreaseStagger, getStaggerMs, } from './swarm-recovery.js';
|
|
14
|
+
// ─── Wave Execution ─────────────────────────────────────────────────────
|
|
15
|
+
/**
|
|
16
|
+
* Execute all waves in sequence, with review after each.
|
|
17
|
+
*/
|
|
18
|
+
export async function executeWaves(ctx, recoveryState, getStatus) {
|
|
19
|
+
let waveIndex = ctx.taskQueue.getCurrentWave();
|
|
20
|
+
const totalWaves = ctx.taskQueue.getTotalWaves();
|
|
21
|
+
const dispatchLeaseStaleMs = ctx.config.dispatchLeaseStaleMs ?? 5 * 60 * 1000;
|
|
22
|
+
while (waveIndex < totalWaves && !ctx.cancelled) {
|
|
23
|
+
const activeTaskIds = new Set(ctx.workerPool.getActiveWorkerStatus().map(w => w.taskId));
|
|
24
|
+
const recovered = ctx.taskQueue.reconcileStaleDispatched({
|
|
25
|
+
staleAfterMs: dispatchLeaseStaleMs,
|
|
26
|
+
activeTaskIds,
|
|
27
|
+
});
|
|
28
|
+
if (recovered.length > 0) {
|
|
29
|
+
ctx.logDecision('lease-recovery', `Recovered ${recovered.length} stale dispatched task(s)`, recovered.join(', '));
|
|
30
|
+
}
|
|
31
|
+
const readyTasks = ctx.taskQueue.getReadyTasks();
|
|
32
|
+
const queueStats = ctx.taskQueue.getStats();
|
|
33
|
+
// F18: Skip empty waves
|
|
34
|
+
if (readyTasks.length === 0 && queueStats.running === 0 && queueStats.ready === 0) {
|
|
35
|
+
ctx.logDecision('wave-skip', `Skipping waves ${waveIndex + 1}-${totalWaves}: no dispatchable tasks remain`, `Stats: ${queueStats.completed} completed, ${queueStats.failed} failed, ${queueStats.skipped} skipped`);
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
ctx.emit({
|
|
39
|
+
type: 'swarm.wave.start',
|
|
40
|
+
wave: waveIndex + 1,
|
|
41
|
+
totalWaves,
|
|
42
|
+
taskCount: readyTasks.length,
|
|
43
|
+
});
|
|
44
|
+
// Dispatch tasks up to concurrency limit
|
|
45
|
+
await executeWave(ctx, recoveryState, readyTasks, getStatus);
|
|
46
|
+
// Wave complete stats
|
|
47
|
+
const afterStats = ctx.taskQueue.getStats();
|
|
48
|
+
const waveCompleted = afterStats.completed - (queueStats.completed);
|
|
49
|
+
const waveFailed = afterStats.failed - (queueStats.failed);
|
|
50
|
+
const waveSkipped = afterStats.skipped - (queueStats.skipped);
|
|
51
|
+
ctx.emit({
|
|
52
|
+
type: 'swarm.wave.complete',
|
|
53
|
+
wave: waveIndex + 1,
|
|
54
|
+
totalWaves,
|
|
55
|
+
completed: waveCompleted,
|
|
56
|
+
failed: waveFailed,
|
|
57
|
+
skipped: waveSkipped,
|
|
58
|
+
});
|
|
59
|
+
// Wave failure recovery: if ALL tasks in a wave failed, retry with adapted context
|
|
60
|
+
if (waveCompleted === 0 && waveFailed > 0 && readyTasks.length > 0) {
|
|
61
|
+
ctx.emit({ type: 'swarm.wave.allFailed', wave: waveIndex + 1 });
|
|
62
|
+
ctx.logDecision('wave-recovery', `Entire wave ${waveIndex + 1} failed (${waveFailed} tasks)`, 'Checking if budget allows retry with adapted strategy');
|
|
63
|
+
const budgetRemaining = ctx.budgetPool.hasCapacity();
|
|
64
|
+
const failedWaveTasks = readyTasks.filter(t => {
|
|
65
|
+
const task = ctx.taskQueue.getTask(t.id);
|
|
66
|
+
return task && task.status === 'failed' && task.attempts < (ctx.config.workerRetries + 1);
|
|
67
|
+
});
|
|
68
|
+
if (budgetRemaining && failedWaveTasks.length > 0) {
|
|
69
|
+
for (const t of failedWaveTasks) {
|
|
70
|
+
const task = ctx.taskQueue.getTask(t.id);
|
|
71
|
+
if (!task)
|
|
72
|
+
continue;
|
|
73
|
+
task.status = 'ready';
|
|
74
|
+
task.retryContext = {
|
|
75
|
+
previousFeedback: 'All tasks in this batch failed. Try a fundamentally different approach — the previous strategy did not work.',
|
|
76
|
+
previousScore: 0,
|
|
77
|
+
attempt: task.attempts,
|
|
78
|
+
previousModel: task.assignedModel,
|
|
79
|
+
swarmProgress: getSwarmProgressSummary(ctx),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
ctx.logDecision('wave-recovery', `Re-queued ${failedWaveTasks.length} tasks with adapted retry context`, 'Budget allows retry');
|
|
83
|
+
await executeWave(ctx, recoveryState, failedWaveTasks.map(t => ctx.taskQueue.getTask(t.id)).filter(t => t.status === 'ready'), getStatus);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// F5: Adaptive re-decomposition signal
|
|
87
|
+
const waveTotal = waveCompleted + waveFailed + waveSkipped;
|
|
88
|
+
const waveSuccessRate = waveTotal > 0 ? waveCompleted / waveTotal : 0;
|
|
89
|
+
if (waveSuccessRate < 0.5 && waveTotal >= 2) {
|
|
90
|
+
ctx.logDecision('decomposition-quality', `Wave ${waveIndex + 1} success rate ${(waveSuccessRate * 100).toFixed(0)}% (${waveCompleted}/${waveTotal})`, 'Low success rate may indicate decomposition quality issues');
|
|
91
|
+
}
|
|
92
|
+
// V2: Review wave outputs
|
|
93
|
+
const review = await reviewWave(ctx, waveIndex);
|
|
94
|
+
if (review && review.fixupTasks.length > 0) {
|
|
95
|
+
await executeWave(ctx, recoveryState, review.fixupTasks, getStatus);
|
|
96
|
+
}
|
|
97
|
+
// Rescue cascade-skipped tasks that can still run
|
|
98
|
+
const rescued = rescueCascadeSkipped(ctx);
|
|
99
|
+
if (rescued.length > 0) {
|
|
100
|
+
ctx.logDecision('cascade-rescue', `Rescued ${rescued.length} cascade-skipped tasks after wave ${waveIndex + 1}`, rescued.map(t => t.id).join(', '));
|
|
101
|
+
await executeWave(ctx, recoveryState, rescued, getStatus);
|
|
102
|
+
}
|
|
103
|
+
// Reset quality circuit breaker at wave boundary
|
|
104
|
+
if (recoveryState.qualityGateDisabledModels.size > 0) {
|
|
105
|
+
recoveryState.qualityGateDisabledModels.clear();
|
|
106
|
+
recoveryState.perModelQualityRejections.clear();
|
|
107
|
+
ctx.logDecision('quality-circuit-breaker', `Re-enabled quality gates for all models at wave ${waveIndex + 1} boundary`, 'Each wave gets a fresh quality evaluation window');
|
|
108
|
+
}
|
|
109
|
+
// F3: Log budget reallocation after wave completion
|
|
110
|
+
const budgetStats = ctx.budgetPool.getStats();
|
|
111
|
+
ctx.logDecision('budget-reallocation', `After wave ${waveIndex + 1}: ${budgetStats.tokensRemaining} tokens remaining (${(budgetStats.utilization * 100).toFixed(0)}% utilized)`, '');
|
|
112
|
+
ctx.budgetPool.reallocateUnused(budgetStats.tokensRemaining);
|
|
113
|
+
// F21: Mid-swarm situational assessment
|
|
114
|
+
await assessAndAdapt(ctx, recoveryState, waveIndex);
|
|
115
|
+
// V2: Checkpoint after each wave
|
|
116
|
+
saveCheckpoint(ctx, `wave-${waveIndex}`);
|
|
117
|
+
// Advance to next wave
|
|
118
|
+
if (!ctx.taskQueue.advanceWave())
|
|
119
|
+
break;
|
|
120
|
+
waveIndex++;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Execute a single wave's tasks with concurrency control.
|
|
125
|
+
*/
|
|
126
|
+
export async function executeWave(ctx, recoveryState, tasks, getStatus) {
|
|
127
|
+
// Dispatch initial batch with stagger
|
|
128
|
+
let taskIndex = 0;
|
|
129
|
+
while (taskIndex < tasks.length && ctx.workerPool.availableSlots > 0 && !ctx.cancelled) {
|
|
130
|
+
if (isCircuitBreakerActive(recoveryState, ctx)) {
|
|
131
|
+
const waitMs = recoveryState.circuitBreakerUntil - Date.now();
|
|
132
|
+
if (waitMs > 0)
|
|
133
|
+
await new Promise(resolve => setTimeout(resolve, waitMs));
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
const task = tasks[taskIndex];
|
|
137
|
+
await dispatchTask(ctx, recoveryState, task, getStatus);
|
|
138
|
+
taskIndex++;
|
|
139
|
+
if (taskIndex < tasks.length && ctx.workerPool.availableSlots > 0) {
|
|
140
|
+
await new Promise(resolve => setTimeout(resolve, getStaggerMs(recoveryState)));
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// Process completions and dispatch more tasks as slots open
|
|
144
|
+
while (ctx.workerPool.activeCount > 0 && !ctx.cancelled) {
|
|
145
|
+
const completed = await ctx.workerPool.waitForAny();
|
|
146
|
+
if (!completed)
|
|
147
|
+
break;
|
|
148
|
+
await handleTaskCompletion(ctx, recoveryState, completed.taskId, completed.result, completed.startedAt, getStatus);
|
|
149
|
+
emitBudgetUpdate(ctx);
|
|
150
|
+
ctx.emit({ type: 'swarm.status', status: getStatus() });
|
|
151
|
+
// Dispatch more tasks if slots available and tasks remain
|
|
152
|
+
while (taskIndex < tasks.length && ctx.workerPool.availableSlots > 0 && !ctx.cancelled) {
|
|
153
|
+
const task = tasks[taskIndex];
|
|
154
|
+
if (task.status === 'ready') {
|
|
155
|
+
await dispatchTask(ctx, recoveryState, task, getStatus);
|
|
156
|
+
if (taskIndex + 1 < tasks.length && ctx.workerPool.availableSlots > 0) {
|
|
157
|
+
await new Promise(resolve => setTimeout(resolve, getStaggerMs(recoveryState)));
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
taskIndex++;
|
|
161
|
+
}
|
|
162
|
+
// Also check for cross-wave ready tasks to fill slots
|
|
163
|
+
if (ctx.workerPool.availableSlots > 0 && !isCircuitBreakerActive(recoveryState, ctx)) {
|
|
164
|
+
const moreReady = ctx.taskQueue.getAllReadyTasks()
|
|
165
|
+
.filter(t => !ctx.workerPool.getActiveWorkerStatus().some(w => w.taskId === t.id));
|
|
166
|
+
for (let i = 0; i < moreReady.length; i++) {
|
|
167
|
+
if (ctx.workerPool.availableSlots <= 0)
|
|
168
|
+
break;
|
|
169
|
+
await dispatchTask(ctx, recoveryState, moreReady[i], getStatus);
|
|
170
|
+
if (i + 1 < moreReady.length && ctx.workerPool.availableSlots > 0) {
|
|
171
|
+
await new Promise(resolve => setTimeout(resolve, getStaggerMs(recoveryState)));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// F20: Re-dispatch pass — after all workers finish, budget may have been freed
|
|
177
|
+
if (!ctx.cancelled && ctx.budgetPool.hasCapacity()) {
|
|
178
|
+
const stillReady = ctx.taskQueue.getAllReadyTasks()
|
|
179
|
+
.filter(t => !ctx.workerPool.getActiveWorkerStatus().some(w => w.taskId === t.id));
|
|
180
|
+
if (stillReady.length > 0) {
|
|
181
|
+
ctx.logDecision('budget-redispatch', `Budget freed after wave — re-dispatching ${stillReady.length} ready task(s)`, `Budget: ${JSON.stringify(ctx.budgetPool.getStats())}`);
|
|
182
|
+
for (const task of stillReady) {
|
|
183
|
+
if (ctx.workerPool.availableSlots <= 0 || !ctx.budgetPool.hasCapacity())
|
|
184
|
+
break;
|
|
185
|
+
await dispatchTask(ctx, recoveryState, task, getStatus);
|
|
186
|
+
if (ctx.workerPool.availableSlots > 0) {
|
|
187
|
+
await new Promise(resolve => setTimeout(resolve, getStaggerMs(recoveryState)));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
while (ctx.workerPool.activeCount > 0 && !ctx.cancelled) {
|
|
191
|
+
const completed = await ctx.workerPool.waitForAny();
|
|
192
|
+
if (!completed)
|
|
193
|
+
break;
|
|
194
|
+
await handleTaskCompletion(ctx, recoveryState, completed.taskId, completed.result, completed.startedAt, getStatus);
|
|
195
|
+
emitBudgetUpdate(ctx);
|
|
196
|
+
ctx.emit({ type: 'swarm.status', status: getStatus() });
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
// ─── Task Dispatch ──────────────────────────────────────────────────────
|
|
202
|
+
/**
|
|
203
|
+
* Dispatch a single task to a worker.
|
|
204
|
+
*/
|
|
205
|
+
export async function dispatchTask(ctx, recoveryState, task, _getStatus) {
|
|
206
|
+
const worker = ctx.workerPool.selectWorker(task);
|
|
207
|
+
if (!worker) {
|
|
208
|
+
ctx.logDecision('no-worker', `${task.id}: no worker for type ${task.type}`, '');
|
|
209
|
+
if (task.attempts > 0) {
|
|
210
|
+
const syntheticTaskResult = { success: false, output: '', tokensUsed: 0, costUsed: 0, durationMs: 0, model: 'none' };
|
|
211
|
+
const syntheticSpawn = { success: false, output: '', metrics: { tokens: 0, duration: 0, toolCalls: 0 } };
|
|
212
|
+
if (await tryResilienceRecovery(ctx, recoveryState, task, task.id, syntheticTaskResult, syntheticSpawn)) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
ctx.taskQueue.markFailedWithoutCascade(task.id, 0);
|
|
217
|
+
ctx.taskQueue.triggerCascadeSkip(task.id);
|
|
218
|
+
ctx.emit({
|
|
219
|
+
type: 'swarm.task.failed',
|
|
220
|
+
taskId: task.id,
|
|
221
|
+
error: `No worker available for task type: ${task.type}`,
|
|
222
|
+
attempt: task.attempts,
|
|
223
|
+
maxAttempts: 0,
|
|
224
|
+
willRetry: false,
|
|
225
|
+
failureMode: 'error',
|
|
226
|
+
});
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
try {
|
|
230
|
+
// Pre-dispatch auto-split for critical-path bottlenecks
|
|
231
|
+
if (shouldAutoSplit(ctx, task)) {
|
|
232
|
+
try {
|
|
233
|
+
const splitResult = await judgeSplit(ctx, task);
|
|
234
|
+
if (splitResult.shouldSplit && splitResult.subtasks) {
|
|
235
|
+
task.status = 'dispatched';
|
|
236
|
+
ctx.taskQueue.replaceWithSubtasks(task.id, splitResult.subtasks);
|
|
237
|
+
ctx.emit({
|
|
238
|
+
type: 'swarm.task.resilience',
|
|
239
|
+
taskId: task.id,
|
|
240
|
+
strategy: 'auto-split',
|
|
241
|
+
succeeded: true,
|
|
242
|
+
reason: `Pre-dispatch split into ${splitResult.subtasks.length} parallel subtasks`,
|
|
243
|
+
artifactsFound: 0,
|
|
244
|
+
toolCalls: 0,
|
|
245
|
+
});
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
catch (err) {
|
|
250
|
+
ctx.logDecision('auto-split', `${task.id}: split judge failed — ${err.message}`, '');
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
ctx.totalDispatches++;
|
|
254
|
+
const dispatchedModel = task.assignedModel ?? worker.model;
|
|
255
|
+
ctx.taskQueue.markDispatched(task.id, dispatchedModel);
|
|
256
|
+
if (task.assignedModel && task.assignedModel !== worker.model) {
|
|
257
|
+
ctx.logDecision('failover', `Dispatching ${task.id} with failover model ${task.assignedModel} (worker default: ${worker.model})`, 'Retry model override is active');
|
|
258
|
+
}
|
|
259
|
+
await ctx.workerPool.dispatch(task, worker);
|
|
260
|
+
ctx.emit({
|
|
261
|
+
type: 'swarm.task.dispatched',
|
|
262
|
+
taskId: task.id,
|
|
263
|
+
description: task.description,
|
|
264
|
+
model: dispatchedModel,
|
|
265
|
+
workerName: worker.name,
|
|
266
|
+
toolCount: worker.allowedTools?.length ?? -1,
|
|
267
|
+
tools: worker.allowedTools,
|
|
268
|
+
retryContext: task.retryContext,
|
|
269
|
+
fromModel: task.retryContext ? task.retryContext.previousModel : undefined,
|
|
270
|
+
attempts: task.attempts,
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
catch (error) {
|
|
274
|
+
const errorMsg = error.message;
|
|
275
|
+
// F20: Budget exhaustion is NOT a task failure
|
|
276
|
+
if (errorMsg.includes('Budget pool exhausted')) {
|
|
277
|
+
task.status = 'ready';
|
|
278
|
+
ctx.logDecision('budget-pause', `Cannot dispatch ${task.id}: budget exhausted — task kept ready for potential re-dispatch`, `Budget stats: ${JSON.stringify(ctx.budgetPool.getStats())}`);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
ctx.errors.push({
|
|
282
|
+
taskId: task.id,
|
|
283
|
+
phase: 'dispatch',
|
|
284
|
+
message: errorMsg,
|
|
285
|
+
recovered: false,
|
|
286
|
+
});
|
|
287
|
+
ctx.logDecision('dispatch-error', `${task.id}: dispatch failed: ${errorMsg.slice(0, 100)}`, `attempts: ${task.attempts}`);
|
|
288
|
+
if (task.attempts > 0) {
|
|
289
|
+
const syntheticTaskResult = { success: false, output: '', tokensUsed: 0, costUsed: 0, durationMs: 0, model: 'none' };
|
|
290
|
+
const syntheticSpawn = { success: false, output: '', metrics: { tokens: 0, duration: 0, toolCalls: 0 } };
|
|
291
|
+
if (await tryResilienceRecovery(ctx, recoveryState, task, task.id, syntheticTaskResult, syntheticSpawn)) {
|
|
292
|
+
ctx.errors[ctx.errors.length - 1].recovered = true;
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
ctx.taskQueue.markFailedWithoutCascade(task.id, 0);
|
|
297
|
+
ctx.taskQueue.triggerCascadeSkip(task.id);
|
|
298
|
+
ctx.emit({
|
|
299
|
+
type: 'swarm.task.failed',
|
|
300
|
+
taskId: task.id,
|
|
301
|
+
error: errorMsg,
|
|
302
|
+
attempt: task.attempts,
|
|
303
|
+
maxAttempts: 1 + ctx.config.workerRetries,
|
|
304
|
+
willRetry: false,
|
|
305
|
+
failureMode: 'error',
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
// ─── Task Completion Handling ───────────────────────────────────────────
|
|
310
|
+
/**
|
|
311
|
+
* Handle a completed task: quality gate, bookkeeping, retry logic, model health, failover.
|
|
312
|
+
*/
|
|
313
|
+
export async function handleTaskCompletion(ctx, recoveryState, taskId, spawnResult, startedAt, _getStatus) {
|
|
314
|
+
const task = ctx.taskQueue.getTask(taskId);
|
|
315
|
+
if (!task)
|
|
316
|
+
return;
|
|
317
|
+
// Guard: task was terminally resolved while its worker was running
|
|
318
|
+
if ((task.status === 'skipped' || task.status === 'failed') && !task.pendingCascadeSkip)
|
|
319
|
+
return;
|
|
320
|
+
// V7: Global dispatch cap
|
|
321
|
+
const maxDispatches = ctx.config.maxDispatchesPerTask ?? 5;
|
|
322
|
+
if (task.attempts >= maxDispatches) {
|
|
323
|
+
const durationMs = Date.now() - startedAt;
|
|
324
|
+
const taskResult = ctx.workerPool.toTaskResult(spawnResult, task, durationMs);
|
|
325
|
+
ctx.totalTokens += taskResult.tokensUsed;
|
|
326
|
+
ctx.totalCost += taskResult.costUsed;
|
|
327
|
+
if (await tryResilienceRecovery(ctx, recoveryState, task, taskId, taskResult, spawnResult)) {
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
ctx.taskQueue.markFailedWithoutCascade(taskId, 0);
|
|
331
|
+
ctx.taskQueue.triggerCascadeSkip(taskId);
|
|
332
|
+
ctx.emit({
|
|
333
|
+
type: 'swarm.task.failed',
|
|
334
|
+
taskId,
|
|
335
|
+
error: `Dispatch cap reached (${maxDispatches} attempts)`,
|
|
336
|
+
attempt: task.attempts,
|
|
337
|
+
maxAttempts: maxDispatches,
|
|
338
|
+
willRetry: false,
|
|
339
|
+
failureMode: task.failureMode,
|
|
340
|
+
});
|
|
341
|
+
ctx.logDecision('dispatch-cap', `${taskId}: hard cap reached (${task.attempts}/${maxDispatches})`, 'No more retries — resilience recovery also failed');
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
const durationMs = Date.now() - startedAt;
|
|
345
|
+
const taskResult = ctx.workerPool.toTaskResult(spawnResult, task, durationMs);
|
|
346
|
+
// Track model usage
|
|
347
|
+
const model = task.assignedModel ?? 'unknown';
|
|
348
|
+
const usage = ctx.modelUsage.get(model) ?? { tasks: 0, tokens: 0, cost: 0 };
|
|
349
|
+
usage.tasks++;
|
|
350
|
+
usage.tokens += taskResult.tokensUsed;
|
|
351
|
+
usage.cost += taskResult.costUsed;
|
|
352
|
+
ctx.modelUsage.set(model, usage);
|
|
353
|
+
ctx.totalTokens += taskResult.tokensUsed;
|
|
354
|
+
ctx.totalCost += taskResult.costUsed;
|
|
355
|
+
if (taskResult.budgetUtilization) {
|
|
356
|
+
ctx.logDecision('budget-utilization', `${taskId}: token ${taskResult.budgetUtilization.tokenPercent}%, iter ${taskResult.budgetUtilization.iterationPercent}%`, `model=${model}, tokens=${taskResult.tokensUsed}, duration=${durationMs}ms`);
|
|
357
|
+
}
|
|
358
|
+
// V10: Emit per-attempt event
|
|
359
|
+
ctx.emit({
|
|
360
|
+
type: 'swarm.task.attempt',
|
|
361
|
+
taskId,
|
|
362
|
+
attempt: task.attempts,
|
|
363
|
+
model,
|
|
364
|
+
success: spawnResult.success,
|
|
365
|
+
durationMs,
|
|
366
|
+
toolCalls: spawnResult.metrics.toolCalls ?? 0,
|
|
367
|
+
failureMode: !spawnResult.success ? task.failureMode : undefined,
|
|
368
|
+
qualityScore: taskResult.qualityScore,
|
|
369
|
+
output: taskResult.output.slice(0, 500),
|
|
370
|
+
});
|
|
371
|
+
if (!spawnResult.success) {
|
|
372
|
+
return handleFailedCompletion(ctx, recoveryState, task, taskId, spawnResult, taskResult, model, durationMs, startedAt, maxDispatches);
|
|
373
|
+
}
|
|
374
|
+
// V6: Hollow completion detection
|
|
375
|
+
if (isHollowCompletion(spawnResult, task.type, ctx.config)) {
|
|
376
|
+
return handleHollowCompletion(ctx, recoveryState, task, taskId, spawnResult, taskResult, model, maxDispatches);
|
|
377
|
+
}
|
|
378
|
+
// F4: Task had pendingCascadeSkip but produced non-hollow results
|
|
379
|
+
if (task.pendingCascadeSkip) {
|
|
380
|
+
const cachedReport = checkArtifacts(task);
|
|
381
|
+
const preFlight = runPreFlightChecks(task, taskResult, ctx.config, cachedReport);
|
|
382
|
+
if (preFlight && !preFlight.passed) {
|
|
383
|
+
task.pendingCascadeSkip = undefined;
|
|
384
|
+
task.status = 'skipped';
|
|
385
|
+
ctx.logDecision('cascade-skip', `${taskId}: pending cascade skip honored (pre-flight failed: ${preFlight.feedback})`, '');
|
|
386
|
+
ctx.emit({ type: 'swarm.task.skipped', taskId, reason: `cascade skip honored — output failed pre-flight: ${preFlight.feedback}` });
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
task.pendingCascadeSkip = undefined;
|
|
390
|
+
task.status = 'dispatched';
|
|
391
|
+
ctx.logDecision('cascade-skip', `${taskId}: pending cascade skip overridden — worker produced valid output`, '');
|
|
392
|
+
}
|
|
393
|
+
// Record model health on success
|
|
394
|
+
ctx.healthTracker.recordSuccess(model, durationMs);
|
|
395
|
+
decreaseStagger(recoveryState);
|
|
396
|
+
// Run quality gate if enabled
|
|
397
|
+
const effectiveRetries = getEffectiveRetries(ctx, task);
|
|
398
|
+
const recentRLCount = recoveryState.recentRateLimits.filter(t => t > Date.now() - 30_000).length;
|
|
399
|
+
const isLastAttempt = task.attempts >= (effectiveRetries + 1);
|
|
400
|
+
const shouldRunQualityGate = ctx.config.qualityGates
|
|
401
|
+
&& !recoveryState.qualityGateDisabledModels.has(model)
|
|
402
|
+
&& !isLastAttempt
|
|
403
|
+
&& Date.now() >= recoveryState.circuitBreakerUntil
|
|
404
|
+
&& recentRLCount < 2;
|
|
405
|
+
const cachedArtifactReport = checkArtifacts(task);
|
|
406
|
+
if (shouldRunQualityGate) {
|
|
407
|
+
const rejected = await runQualityGate(ctx, recoveryState, task, taskId, spawnResult, taskResult, model, effectiveRetries, cachedArtifactReport);
|
|
408
|
+
if (rejected)
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
// F7: When quality gate was skipped, still run pre-flight + concrete checks
|
|
412
|
+
if (!shouldRunQualityGate && ctx.config.qualityGates) {
|
|
413
|
+
const preFlight = runPreFlightChecks(task, taskResult, ctx.config, cachedArtifactReport);
|
|
414
|
+
if (preFlight && !preFlight.passed) {
|
|
415
|
+
taskResult.qualityScore = preFlight.score;
|
|
416
|
+
taskResult.qualityFeedback = preFlight.feedback;
|
|
417
|
+
ctx.qualityRejections++;
|
|
418
|
+
const canRetry = ctx.taskQueue.markFailedWithoutCascade(taskId, effectiveRetries);
|
|
419
|
+
if (canRetry) {
|
|
420
|
+
ctx.retries++;
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
ctx.logDecision('preflight-reject', `${taskId}: pre-flight failed: ${preFlight.feedback}`, '');
|
|
424
|
+
if (await tryResilienceRecovery(ctx, recoveryState, task, taskId, taskResult, spawnResult)) {
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
ctx.taskQueue.triggerCascadeSkip(taskId);
|
|
428
|
+
}
|
|
429
|
+
ctx.emit({
|
|
430
|
+
type: 'swarm.quality.rejected',
|
|
431
|
+
taskId,
|
|
432
|
+
score: preFlight.score,
|
|
433
|
+
feedback: preFlight.feedback,
|
|
434
|
+
artifactCount: 0,
|
|
435
|
+
outputLength: taskResult.output.length,
|
|
436
|
+
preFlightReject: true,
|
|
437
|
+
});
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
// F2: Run concrete validation when pre-flight passes but gate was skipped
|
|
441
|
+
if (ctx.config.enableConcreteValidation !== false) {
|
|
442
|
+
const concreteResult = runConcreteChecks(task, taskResult);
|
|
443
|
+
if (!concreteResult.passed) {
|
|
444
|
+
taskResult.qualityScore = 2;
|
|
445
|
+
taskResult.qualityFeedback = `Concrete validation failed: ${concreteResult.issues.join('; ')}`;
|
|
446
|
+
ctx.qualityRejections++;
|
|
447
|
+
const canRetry = ctx.taskQueue.markFailedWithoutCascade(taskId, effectiveRetries);
|
|
448
|
+
if (canRetry) {
|
|
449
|
+
ctx.retries++;
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
452
|
+
ctx.logDecision('concrete-reject', `${taskId}: concrete validation failed: ${concreteResult.issues.join('; ')}`, '');
|
|
453
|
+
if (await tryResilienceRecovery(ctx, recoveryState, task, taskId, taskResult, spawnResult)) {
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
ctx.taskQueue.triggerCascadeSkip(taskId);
|
|
457
|
+
}
|
|
458
|
+
ctx.emit({
|
|
459
|
+
type: 'swarm.quality.rejected',
|
|
460
|
+
taskId,
|
|
461
|
+
score: 2,
|
|
462
|
+
feedback: taskResult.qualityFeedback,
|
|
463
|
+
artifactCount: 0,
|
|
464
|
+
outputLength: taskResult.output.length,
|
|
465
|
+
preFlightReject: false,
|
|
466
|
+
});
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
// Final completion guard: block "narrative success" for action tasks
|
|
472
|
+
const completionGuard = ctx.config.completionGuard ?? {};
|
|
473
|
+
const rejectFutureIntentOutputs = completionGuard.rejectFutureIntentOutputs ?? true;
|
|
474
|
+
const requireConcreteArtifactsForActionTasks = completionGuard.requireConcreteArtifactsForActionTasks ?? true;
|
|
475
|
+
const typeConfig = getTaskTypeConfig(task.type, ctx.config);
|
|
476
|
+
const artifactReport = checkArtifactsEnhanced(task, taskResult);
|
|
477
|
+
const filesOnDisk = artifactReport.files.filter(f => f.exists && f.sizeBytes > 0).length;
|
|
478
|
+
const hasConcreteArtifacts = filesOnDisk > 0 || (taskResult.filesModified?.length ?? 0) > 0;
|
|
479
|
+
const isActionTask = !!typeConfig.requiresToolCalls;
|
|
480
|
+
if (rejectFutureIntentOutputs && hasFutureIntentLanguage(taskResult.output ?? '')) {
|
|
481
|
+
taskResult.qualityScore = 1;
|
|
482
|
+
taskResult.qualityFeedback = 'Completion rejected: output indicates pending, unexecuted work';
|
|
483
|
+
const canRetry = ctx.taskQueue.markFailedWithoutCascade(taskId, effectiveRetries);
|
|
484
|
+
if (canRetry) {
|
|
485
|
+
ctx.retries++;
|
|
486
|
+
}
|
|
487
|
+
else {
|
|
488
|
+
if (await tryResilienceRecovery(ctx, recoveryState, task, taskId, taskResult, spawnResult)) {
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
ctx.taskQueue.triggerCascadeSkip(taskId);
|
|
492
|
+
}
|
|
493
|
+
ctx.emit({
|
|
494
|
+
type: 'swarm.quality.rejected',
|
|
495
|
+
taskId,
|
|
496
|
+
score: 1,
|
|
497
|
+
feedback: taskResult.qualityFeedback,
|
|
498
|
+
artifactCount: filesOnDisk,
|
|
499
|
+
outputLength: taskResult.output.length,
|
|
500
|
+
preFlightReject: true,
|
|
501
|
+
filesOnDisk,
|
|
502
|
+
});
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
if (requireConcreteArtifactsForActionTasks && isActionTask && !hasConcreteArtifacts) {
|
|
506
|
+
taskResult.qualityScore = 1;
|
|
507
|
+
taskResult.qualityFeedback = 'Completion rejected: action task produced no concrete artifacts';
|
|
508
|
+
const canRetry = ctx.taskQueue.markFailedWithoutCascade(taskId, effectiveRetries);
|
|
509
|
+
if (canRetry) {
|
|
510
|
+
ctx.retries++;
|
|
511
|
+
}
|
|
512
|
+
else {
|
|
513
|
+
if (await tryResilienceRecovery(ctx, recoveryState, task, taskId, taskResult, spawnResult)) {
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
ctx.taskQueue.triggerCascadeSkip(taskId);
|
|
517
|
+
}
|
|
518
|
+
ctx.emit({
|
|
519
|
+
type: 'swarm.quality.rejected',
|
|
520
|
+
taskId,
|
|
521
|
+
score: 1,
|
|
522
|
+
feedback: taskResult.qualityFeedback,
|
|
523
|
+
artifactCount: filesOnDisk,
|
|
524
|
+
outputLength: taskResult.output.length,
|
|
525
|
+
preFlightReject: true,
|
|
526
|
+
filesOnDisk,
|
|
527
|
+
});
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
// Task passed — mark completed
|
|
531
|
+
ctx.taskQueue.markCompleted(taskId, taskResult);
|
|
532
|
+
ctx.hollowStreak = 0;
|
|
533
|
+
recoveryState.taskTimeoutCounts.delete(taskId);
|
|
534
|
+
// H6: Post findings to blackboard
|
|
535
|
+
if (ctx.blackboard && taskResult.findings) {
|
|
536
|
+
try {
|
|
537
|
+
for (const finding of taskResult.findings) {
|
|
538
|
+
ctx.blackboard.post(`swarm-worker-${taskId}`, {
|
|
539
|
+
topic: `swarm.task.${task.type}`,
|
|
540
|
+
content: finding,
|
|
541
|
+
type: 'progress',
|
|
542
|
+
confidence: (taskResult.qualityScore ?? 3) / 5,
|
|
543
|
+
tags: ['swarm', task.type],
|
|
544
|
+
relatedFiles: task.targetFiles,
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
catch {
|
|
549
|
+
ctx.errors.push({
|
|
550
|
+
taskId,
|
|
551
|
+
phase: 'execution',
|
|
552
|
+
message: 'Failed to post findings to blackboard',
|
|
553
|
+
recovered: true,
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
ctx.emit({
|
|
558
|
+
type: 'swarm.task.completed',
|
|
559
|
+
taskId,
|
|
560
|
+
success: true,
|
|
561
|
+
tokensUsed: taskResult.tokensUsed,
|
|
562
|
+
costUsed: taskResult.costUsed,
|
|
563
|
+
durationMs: taskResult.durationMs,
|
|
564
|
+
qualityScore: taskResult.qualityScore,
|
|
565
|
+
qualityFeedback: taskResult.qualityFeedback,
|
|
566
|
+
output: taskResult.output,
|
|
567
|
+
closureReport: taskResult.closureReport,
|
|
568
|
+
toolCalls: spawnResult.metrics.toolCalls,
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
// ─── Internal Helpers ───────────────────────────────────────────────────
|
|
572
|
+
/**
|
|
573
|
+
* Handle the case where a worker fails (spawnResult.success === false).
|
|
574
|
+
*/
|
|
575
|
+
async function handleFailedCompletion(ctx, recoveryState, task, taskId, spawnResult, taskResult, model, durationMs, startedAt, maxDispatches) {
|
|
576
|
+
const failure = classifySwarmFailure(spawnResult.output, spawnResult.metrics.toolCalls);
|
|
577
|
+
const { failureClass, retryable, errorType, failureMode, reason } = failure;
|
|
578
|
+
const isTimeout = failureMode === 'timeout';
|
|
579
|
+
const isRateLimited = failureClass === 'rate_limited';
|
|
580
|
+
const isSpendLimit = failureClass === 'provider_spend_limit';
|
|
581
|
+
const isNonRetryable = !retryable;
|
|
582
|
+
ctx.healthTracker.recordFailure(model, errorType);
|
|
583
|
+
ctx.emit({ type: 'swarm.model.health', record: { model, ...getModelHealthSummary(ctx, model) } });
|
|
584
|
+
task.failureMode = failureMode;
|
|
585
|
+
if (isRateLimited) {
|
|
586
|
+
recordRateLimit(recoveryState, ctx);
|
|
587
|
+
}
|
|
588
|
+
// F25a: Consecutive timeout tracking
|
|
589
|
+
if (isTimeout) {
|
|
590
|
+
const count = (recoveryState.taskTimeoutCounts.get(taskId) ?? 0) + 1;
|
|
591
|
+
recoveryState.taskTimeoutCounts.set(taskId, count);
|
|
592
|
+
const timeoutLimit = ctx.config.consecutiveTimeoutLimit ?? 3;
|
|
593
|
+
ctx.logDecision('timeout-tracking', `${taskId}: consecutive timeout ${count}/${timeoutLimit}`, '');
|
|
594
|
+
if (count >= timeoutLimit) {
|
|
595
|
+
let failoverSucceeded = false;
|
|
596
|
+
if (ctx.config.enableModelFailover) {
|
|
597
|
+
const capability = getTaskTypeConfig(task.type, ctx.config).capability ?? 'code';
|
|
598
|
+
const alternative = selectAlternativeModel(ctx.config.workers, model, capability, ctx.healthTracker);
|
|
599
|
+
if (alternative) {
|
|
600
|
+
ctx.emit({
|
|
601
|
+
type: 'swarm.model.failover',
|
|
602
|
+
taskId,
|
|
603
|
+
fromModel: model,
|
|
604
|
+
toModel: alternative.model,
|
|
605
|
+
reason: 'consecutive-timeouts',
|
|
606
|
+
});
|
|
607
|
+
task.assignedModel = alternative.model;
|
|
608
|
+
recoveryState.taskTimeoutCounts.set(taskId, 0);
|
|
609
|
+
ctx.logDecision('failover', `Timeout failover ${taskId}: ${model} → ${alternative.model}`, `${count} consecutive timeouts`);
|
|
610
|
+
failoverSucceeded = true;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
if (!failoverSucceeded) {
|
|
614
|
+
task.failureMode = 'timeout';
|
|
615
|
+
const timeoutTaskResult = ctx.workerPool.toTaskResult(spawnResult, task, Date.now() - startedAt);
|
|
616
|
+
if (await tryResilienceRecovery(ctx, recoveryState, task, taskId, timeoutTaskResult, spawnResult)) {
|
|
617
|
+
recoveryState.taskTimeoutCounts.delete(taskId);
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
ctx.taskQueue.markFailedWithoutCascade(taskId, 0);
|
|
621
|
+
ctx.taskQueue.triggerCascadeSkip(taskId);
|
|
622
|
+
ctx.emit({
|
|
623
|
+
type: 'swarm.task.failed',
|
|
624
|
+
taskId,
|
|
625
|
+
error: `${count} consecutive timeouts — no alternative model available`,
|
|
626
|
+
attempt: task.attempts,
|
|
627
|
+
maxAttempts: maxDispatches,
|
|
628
|
+
willRetry: false,
|
|
629
|
+
failureMode: 'timeout',
|
|
630
|
+
failureClass: 'timeout',
|
|
631
|
+
retrySuppressed: true,
|
|
632
|
+
retryReason: 'Consecutive timeout limit reached with no alternative model',
|
|
633
|
+
});
|
|
634
|
+
ctx.logDecision('timeout-early-fail', `${taskId}: ${count} consecutive timeouts, no alt model — resilience recovery also failed`, '');
|
|
635
|
+
recoveryState.taskTimeoutCounts.delete(taskId);
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
else {
|
|
641
|
+
recoveryState.taskTimeoutCounts.delete(taskId);
|
|
642
|
+
}
|
|
643
|
+
// V2: Model failover on retryable rate limits
|
|
644
|
+
if (isRateLimited && ctx.config.enableModelFailover) {
|
|
645
|
+
const capability = getTaskTypeConfig(task.type, ctx.config).capability ?? 'code';
|
|
646
|
+
const alternative = selectAlternativeModel(ctx.config.workers, model, capability, ctx.healthTracker);
|
|
647
|
+
if (alternative) {
|
|
648
|
+
ctx.emit({
|
|
649
|
+
type: 'swarm.model.failover',
|
|
650
|
+
taskId,
|
|
651
|
+
fromModel: model,
|
|
652
|
+
toModel: alternative.model,
|
|
653
|
+
reason: errorType,
|
|
654
|
+
});
|
|
655
|
+
task.assignedModel = alternative.model;
|
|
656
|
+
ctx.logDecision('failover', `Switched ${taskId} from ${model} to ${alternative.model}`, `${errorType} error`);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
// V5/V7: Store error context so retry gets different prompt
|
|
660
|
+
if (!(isRateLimited || isSpendLimit)) {
|
|
661
|
+
const timeoutSeconds = isTimeout ? Math.round(durationMs / 1000) : 0;
|
|
662
|
+
task.retryContext = {
|
|
663
|
+
previousFeedback: isTimeout
|
|
664
|
+
? `Previous attempt timed out after ${timeoutSeconds}s. You must complete this task more efficiently — work faster, use fewer tool calls, and produce your result sooner.`
|
|
665
|
+
: spawnResult.output.slice(0, 2000),
|
|
666
|
+
previousScore: 0,
|
|
667
|
+
attempt: task.attempts,
|
|
668
|
+
previousModel: model,
|
|
669
|
+
previousFiles: taskResult.filesModified,
|
|
670
|
+
swarmProgress: getSwarmProgressSummary(ctx),
|
|
671
|
+
};
|
|
672
|
+
ctx.sharedContextEngine.reportFailure(taskId, {
|
|
673
|
+
action: task.description.slice(0, 200),
|
|
674
|
+
error: spawnResult.output.slice(0, 500),
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
// V7: Reset hollow streak on non-hollow failure
|
|
678
|
+
ctx.hollowStreak = 0;
|
|
679
|
+
// Worker failed — use higher retry limit for rate limit errors
|
|
680
|
+
const baseRetries = getEffectiveRetries(ctx, task);
|
|
681
|
+
const retryLimit = isNonRetryable
|
|
682
|
+
? 0
|
|
683
|
+
: isRateLimited
|
|
684
|
+
? Math.min(ctx.config.rateLimitRetries ?? 3, baseRetries + 1)
|
|
685
|
+
: baseRetries;
|
|
686
|
+
const canRetry = ctx.taskQueue.markFailedWithoutCascade(taskId, retryLimit);
|
|
687
|
+
if (isNonRetryable) {
|
|
688
|
+
ctx.logDecision('retry-suppressed', `${taskId}: ${failureClass}`, reason);
|
|
689
|
+
}
|
|
690
|
+
if (canRetry) {
|
|
691
|
+
ctx.retries++;
|
|
692
|
+
if (isRateLimited) {
|
|
693
|
+
const baseDelay = ctx.config.retryBaseDelayMs ?? 5000;
|
|
694
|
+
const cooldownMs = Math.min(baseDelay * Math.pow(2, task.attempts - 1), 30000);
|
|
695
|
+
ctx.taskQueue.setRetryAfter(taskId, cooldownMs);
|
|
696
|
+
ctx.logDecision('rate-limit-cooldown', `${taskId}: ${errorType} cooldown ${cooldownMs}ms, model ${model}`, '');
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
else if (!isRateLimited) {
|
|
700
|
+
if (await tryResilienceRecovery(ctx, recoveryState, task, taskId, taskResult, spawnResult)) {
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
ctx.taskQueue.triggerCascadeSkip(taskId);
|
|
704
|
+
}
|
|
705
|
+
else {
|
|
706
|
+
ctx.taskQueue.triggerCascadeSkip(taskId);
|
|
707
|
+
}
|
|
708
|
+
ctx.emit({
|
|
709
|
+
type: 'swarm.task.failed',
|
|
710
|
+
taskId,
|
|
711
|
+
error: spawnResult.output.slice(0, 200),
|
|
712
|
+
attempt: task.attempts,
|
|
713
|
+
maxAttempts: 1 + ctx.config.workerRetries,
|
|
714
|
+
willRetry: canRetry,
|
|
715
|
+
toolCalls: spawnResult.metrics.toolCalls,
|
|
716
|
+
failoverModel: task.assignedModel !== model ? task.assignedModel : undefined,
|
|
717
|
+
failureMode: task.failureMode,
|
|
718
|
+
failureClass,
|
|
719
|
+
retrySuppressed: isNonRetryable,
|
|
720
|
+
retryReason: reason,
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* Handle hollow completion — workers that "succeed" without doing any work.
|
|
725
|
+
*/
|
|
726
|
+
async function handleHollowCompletion(ctx, recoveryState, task, taskId, spawnResult, taskResult, model, maxDispatches) {
|
|
727
|
+
// F4: Hollow result + pendingCascadeSkip
|
|
728
|
+
if (task.pendingCascadeSkip) {
|
|
729
|
+
task.pendingCascadeSkip = undefined;
|
|
730
|
+
task.status = 'skipped';
|
|
731
|
+
ctx.totalHollows++;
|
|
732
|
+
ctx.logDecision('cascade-skip', `${taskId}: pending cascade skip honored (hollow completion)`, '');
|
|
733
|
+
ctx.emit({ type: 'swarm.task.skipped', taskId, reason: 'cascade skip honored — hollow completion' });
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
task.failureMode = 'hollow';
|
|
737
|
+
ctx.healthTracker.recordHollow(model);
|
|
738
|
+
const admitsFailure = spawnResult.success && FAILURE_INDICATORS.some(f => (spawnResult.output ?? '').toLowerCase().includes(f));
|
|
739
|
+
task.retryContext = {
|
|
740
|
+
previousFeedback: admitsFailure
|
|
741
|
+
? 'Previous attempt reported success but admitted failure (e.g., "budget exhausted", "unable to complete"). You MUST execute tool calls and produce concrete output this time.'
|
|
742
|
+
: 'Previous attempt produced no meaningful output. Try again with a concrete approach.',
|
|
743
|
+
previousScore: 1,
|
|
744
|
+
attempt: task.attempts,
|
|
745
|
+
previousModel: model,
|
|
746
|
+
previousFiles: taskResult.filesModified,
|
|
747
|
+
swarmProgress: getSwarmProgressSummary(ctx),
|
|
748
|
+
};
|
|
749
|
+
ctx.sharedContextEngine.reportFailure(taskId, {
|
|
750
|
+
action: task.description.slice(0, 200),
|
|
751
|
+
error: 'Hollow completion: worker produced no meaningful output',
|
|
752
|
+
});
|
|
753
|
+
// Model failover for hollow completions
|
|
754
|
+
if (ctx.config.enableModelFailover) {
|
|
755
|
+
const capability = getTaskTypeConfig(task.type, ctx.config).capability ?? 'code';
|
|
756
|
+
const alternative = selectAlternativeModel(ctx.config.workers, model, capability, ctx.healthTracker);
|
|
757
|
+
if (alternative) {
|
|
758
|
+
ctx.emit({
|
|
759
|
+
type: 'swarm.model.failover',
|
|
760
|
+
taskId,
|
|
761
|
+
fromModel: model,
|
|
762
|
+
toModel: alternative.model,
|
|
763
|
+
reason: 'hollow-completion',
|
|
764
|
+
});
|
|
765
|
+
task.assignedModel = alternative.model;
|
|
766
|
+
ctx.logDecision('failover', `Hollow failover ${taskId}: ${model} → ${alternative.model}`, 'Model produced hollow completion');
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
const hollowRetries = getEffectiveRetries(ctx, task);
|
|
770
|
+
const canRetry = ctx.taskQueue.markFailedWithoutCascade(taskId, hollowRetries);
|
|
771
|
+
if (canRetry) {
|
|
772
|
+
ctx.retries++;
|
|
773
|
+
}
|
|
774
|
+
else {
|
|
775
|
+
if (await tryResilienceRecovery(ctx, recoveryState, task, taskId, taskResult, spawnResult)) {
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
ctx.taskQueue.triggerCascadeSkip(taskId);
|
|
779
|
+
}
|
|
780
|
+
ctx.emit({
|
|
781
|
+
type: 'swarm.task.failed',
|
|
782
|
+
taskId,
|
|
783
|
+
error: 'Hollow completion: worker used no tools',
|
|
784
|
+
attempt: task.attempts,
|
|
785
|
+
maxAttempts: 1 + ctx.config.workerRetries,
|
|
786
|
+
willRetry: canRetry,
|
|
787
|
+
toolCalls: spawnResult.metrics.toolCalls,
|
|
788
|
+
failoverModel: task.assignedModel !== model ? task.assignedModel : undefined,
|
|
789
|
+
failureMode: 'hollow',
|
|
790
|
+
});
|
|
791
|
+
ctx.hollowStreak++;
|
|
792
|
+
ctx.totalHollows++;
|
|
793
|
+
ctx.logDecision('hollow-completion', `${taskId}: worker completed with 0 tool calls (streak: ${ctx.hollowStreak}, total hollows: ${ctx.totalHollows}/${ctx.totalDispatches})`, canRetry ? 'Marking as failed for retry' : 'Retries exhausted — hard fail');
|
|
794
|
+
// B2: Hollow streak handling
|
|
795
|
+
const HOLLOW_STREAK_THRESHOLD = 3;
|
|
796
|
+
if (ctx.hollowStreak >= HOLLOW_STREAK_THRESHOLD) {
|
|
797
|
+
const uniqueModels = new Set(ctx.config.workers.map(w => w.model));
|
|
798
|
+
const singleModel = uniqueModels.size === 1;
|
|
799
|
+
const onlyModel = [...uniqueModels][0];
|
|
800
|
+
const modelUnhealthy = singleModel && !ctx.healthTracker.getAllRecords().find(r => r.model === onlyModel)?.healthy;
|
|
801
|
+
if (singleModel && modelUnhealthy) {
|
|
802
|
+
if (ctx.config.enableHollowTermination) {
|
|
803
|
+
ctx.logDecision('early-termination', `Terminating swarm: ${ctx.hollowStreak} consecutive hollow completions on sole model ${onlyModel}`, 'Single-model swarm with unhealthy model — enableHollowTermination is on');
|
|
804
|
+
skipRemainingTasksInternal(ctx, `Single-model hollow streak (${ctx.hollowStreak}x on ${onlyModel})`);
|
|
805
|
+
}
|
|
806
|
+
else {
|
|
807
|
+
ctx.logDecision('stall-mode', `${ctx.hollowStreak} consecutive hollows on sole model ${onlyModel} — entering stall mode`, 'Will attempt model failover or simplified retry on next dispatch');
|
|
808
|
+
ctx.hollowStreak = 0;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
// V7: Multi-model hollow ratio
|
|
813
|
+
const minDispatches = ctx.config.hollowTerminationMinDispatches ?? 8;
|
|
814
|
+
const threshold = ctx.config.hollowTerminationRatio ?? 0.55;
|
|
815
|
+
if (ctx.totalDispatches >= minDispatches) {
|
|
816
|
+
const ratio = ctx.totalHollows / ctx.totalDispatches;
|
|
817
|
+
if (ratio > threshold) {
|
|
818
|
+
if (ctx.config.enableHollowTermination) {
|
|
819
|
+
ctx.logDecision('early-termination', `Terminating swarm: hollow ratio ${(ratio * 100).toFixed(0)}% (${ctx.totalHollows}/${ctx.totalDispatches})`, `Exceeds threshold ${(threshold * 100).toFixed(0)}% after ${minDispatches}+ dispatches — enableHollowTermination is on`);
|
|
820
|
+
skipRemainingTasksInternal(ctx, `Hollow ratio ${(ratio * 100).toFixed(0)}% — models cannot execute tasks`);
|
|
821
|
+
}
|
|
822
|
+
else if (!recoveryState.hollowRatioWarned) {
|
|
823
|
+
recoveryState.hollowRatioWarned = true;
|
|
824
|
+
ctx.logDecision('stall-warning', `Hollow ratio ${(ratio * 100).toFixed(0)}% (${ctx.totalHollows}/${ctx.totalDispatches})`, 'High hollow rate but continuing — tasks may still recover via resilience');
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* Run the quality gate (LLM judge) on a task completion.
|
|
831
|
+
* Returns true if the task was rejected (caller should return), false if it passed.
|
|
832
|
+
*/
|
|
833
|
+
async function runQualityGate(ctx, recoveryState, task, taskId, spawnResult, taskResult, model, effectiveRetries, cachedArtifactReport) {
|
|
834
|
+
const judgeModel = ctx.config.hierarchy?.judge?.model
|
|
835
|
+
?? ctx.config.qualityGateModel ?? ctx.config.orchestratorModel;
|
|
836
|
+
const judgeConfig = {
|
|
837
|
+
model: judgeModel,
|
|
838
|
+
persona: ctx.config.hierarchy?.judge?.persona,
|
|
839
|
+
};
|
|
840
|
+
ctx.emit({ type: 'swarm.role.action', role: 'judge', action: 'quality-gate', model: judgeModel, taskId });
|
|
841
|
+
const fileArtifacts = extractFileArtifacts(ctx, task, taskResult);
|
|
842
|
+
const baseThreshold = ctx.config.qualityThreshold ?? 3;
|
|
843
|
+
const qualityThreshold = task.isFoundation ? Math.max(2, baseThreshold - 1) : baseThreshold;
|
|
844
|
+
const quality = await evaluateWorkerOutput(ctx.provider, judgeModel, task, taskResult, judgeConfig, qualityThreshold, (resp, purpose) => ctx.trackOrchestratorUsage(resp, purpose), fileArtifacts, ctx.config, cachedArtifactReport);
|
|
845
|
+
taskResult.qualityScore = quality.score;
|
|
846
|
+
taskResult.qualityFeedback = quality.feedback;
|
|
847
|
+
// F11: Foundation tasks that barely pass
|
|
848
|
+
if (quality.passed && task.isFoundation && quality.score <= baseThreshold - 1) {
|
|
849
|
+
const concreteResult = runConcreteChecks(task, taskResult);
|
|
850
|
+
if (!concreteResult.passed) {
|
|
851
|
+
quality.passed = false;
|
|
852
|
+
quality.feedback += ` [F11: foundation task barely passed (${quality.score}/${baseThreshold}) but concrete validation failed: ${concreteResult.issues.join('; ')}]`;
|
|
853
|
+
ctx.logDecision('foundation-concrete-gate', `${taskId}: foundation task scored ${quality.score} (relaxed threshold ${qualityThreshold}) but concrete checks failed — rejecting`, concreteResult.issues.join('; '));
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
if (!quality.passed) {
|
|
857
|
+
// F7: Gate error fallback
|
|
858
|
+
if (quality.gateError && (ctx.config.enableConcreteValidation !== false)) {
|
|
859
|
+
const concreteResult = runConcreteChecks(task, taskResult);
|
|
860
|
+
if (concreteResult.passed) {
|
|
861
|
+
ctx.logDecision('gate-error-fallback', `${taskId}: gate error but concrete checks passed — tentatively accepting`, quality.gateErrorMessage ?? 'unknown');
|
|
862
|
+
taskResult.qualityScore = quality.score;
|
|
863
|
+
taskResult.qualityFeedback = `${quality.feedback} [concrete validation passed — tentative accept]`;
|
|
864
|
+
recoveryState.perModelQualityRejections.delete(model);
|
|
865
|
+
return false; // passed
|
|
866
|
+
}
|
|
867
|
+
else {
|
|
868
|
+
ctx.logDecision('gate-error-fallback', `${taskId}: gate error AND concrete checks failed — rejecting`, `Concrete issues: ${concreteResult.issues.join('; ')}`);
|
|
869
|
+
ctx.qualityRejections++;
|
|
870
|
+
task.failureMode = 'quality';
|
|
871
|
+
ctx.healthTracker.recordQualityRejection(model, quality.score);
|
|
872
|
+
ctx.emit({ type: 'swarm.model.health', record: { model, ...getModelHealthSummary(ctx, model) } });
|
|
873
|
+
ctx.hollowStreak = 0;
|
|
874
|
+
task.retryContext = {
|
|
875
|
+
previousFeedback: `Gate error + concrete validation failed: ${concreteResult.issues.join('; ')}`,
|
|
876
|
+
previousScore: quality.score,
|
|
877
|
+
attempt: task.attempts,
|
|
878
|
+
previousModel: model,
|
|
879
|
+
previousFiles: taskResult.filesModified,
|
|
880
|
+
swarmProgress: getSwarmProgressSummary(ctx),
|
|
881
|
+
};
|
|
882
|
+
const canRetry = ctx.taskQueue.markFailedWithoutCascade(taskId, effectiveRetries);
|
|
883
|
+
if (canRetry) {
|
|
884
|
+
ctx.retries++;
|
|
885
|
+
}
|
|
886
|
+
else {
|
|
887
|
+
if (await tryResilienceRecovery(ctx, recoveryState, task, taskId, taskResult, spawnResult)) {
|
|
888
|
+
return true;
|
|
889
|
+
}
|
|
890
|
+
ctx.taskQueue.triggerCascadeSkip(taskId);
|
|
891
|
+
}
|
|
892
|
+
ctx.emit({
|
|
893
|
+
type: 'swarm.quality.rejected',
|
|
894
|
+
taskId,
|
|
895
|
+
score: quality.score,
|
|
896
|
+
feedback: quality.feedback,
|
|
897
|
+
artifactCount: fileArtifacts.length,
|
|
898
|
+
outputLength: taskResult.output.length,
|
|
899
|
+
preFlightReject: false,
|
|
900
|
+
filesOnDisk: checkArtifactsEnhanced(task, taskResult).files.filter(f => f.exists && f.sizeBytes > 0).length,
|
|
901
|
+
});
|
|
902
|
+
return true;
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
else if (!quality.gateError) {
|
|
906
|
+
// Normal quality rejection
|
|
907
|
+
ctx.qualityRejections++;
|
|
908
|
+
task.failureMode = 'quality';
|
|
909
|
+
ctx.healthTracker.recordQualityRejection(model, quality.score);
|
|
910
|
+
ctx.emit({ type: 'swarm.model.health', record: { model, ...getModelHealthSummary(ctx, model) } });
|
|
911
|
+
ctx.hollowStreak = 0;
|
|
912
|
+
// F7: Per-model circuit breaker
|
|
913
|
+
if (!quality.preFlightReject) {
|
|
914
|
+
const QUALITY_CIRCUIT_BREAKER_THRESHOLD = 5;
|
|
915
|
+
const modelRejections = (recoveryState.perModelQualityRejections.get(model) ?? 0) + 1;
|
|
916
|
+
recoveryState.perModelQualityRejections.set(model, modelRejections);
|
|
917
|
+
if (modelRejections >= QUALITY_CIRCUIT_BREAKER_THRESHOLD) {
|
|
918
|
+
recoveryState.qualityGateDisabledModels.add(model);
|
|
919
|
+
ctx.logDecision('quality-circuit-breaker', `Switched model ${model} to pre-flight-only mode after ${modelRejections} rejections`, 'Skipping LLM judge but keeping pre-flight checks mandatory');
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
task.retryContext = {
|
|
923
|
+
previousFeedback: quality.feedback,
|
|
924
|
+
previousScore: quality.score,
|
|
925
|
+
attempt: task.attempts,
|
|
926
|
+
previousModel: model,
|
|
927
|
+
previousFiles: taskResult.filesModified,
|
|
928
|
+
swarmProgress: getSwarmProgressSummary(ctx),
|
|
929
|
+
};
|
|
930
|
+
ctx.sharedContextEngine.reportFailure(taskId, {
|
|
931
|
+
action: task.description.slice(0, 200),
|
|
932
|
+
error: `Quality gate rejection (score ${quality.score}): ${quality.feedback.slice(0, 300)}`,
|
|
933
|
+
});
|
|
934
|
+
// V5: Model failover on quality rejection
|
|
935
|
+
if (quality.score < qualityThreshold && ctx.config.enableModelFailover && !quality.artifactAutoFail) {
|
|
936
|
+
const capability = getTaskTypeConfig(task.type, ctx.config).capability ?? 'code';
|
|
937
|
+
const alternative = selectAlternativeModel(ctx.config.workers, model, capability, ctx.healthTracker);
|
|
938
|
+
if (alternative) {
|
|
939
|
+
ctx.emit({
|
|
940
|
+
type: 'swarm.model.failover',
|
|
941
|
+
taskId,
|
|
942
|
+
fromModel: model,
|
|
943
|
+
toModel: alternative.model,
|
|
944
|
+
reason: `quality-score-${quality.score}`,
|
|
945
|
+
});
|
|
946
|
+
task.assignedModel = alternative.model;
|
|
947
|
+
ctx.logDecision('failover', `Quality failover ${taskId}: ${model} → ${alternative.model}`, `Score ${quality.score}/5`);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
const canRetry = ctx.taskQueue.markFailedWithoutCascade(taskId, effectiveRetries);
|
|
951
|
+
if (canRetry) {
|
|
952
|
+
ctx.retries++;
|
|
953
|
+
}
|
|
954
|
+
else {
|
|
955
|
+
if (await tryResilienceRecovery(ctx, recoveryState, task, taskId, taskResult, spawnResult)) {
|
|
956
|
+
return true;
|
|
957
|
+
}
|
|
958
|
+
ctx.taskQueue.triggerCascadeSkip(taskId);
|
|
959
|
+
}
|
|
960
|
+
ctx.emit({
|
|
961
|
+
type: 'swarm.quality.rejected',
|
|
962
|
+
taskId,
|
|
963
|
+
score: quality.score,
|
|
964
|
+
feedback: quality.feedback,
|
|
965
|
+
artifactCount: fileArtifacts.length,
|
|
966
|
+
outputLength: taskResult.output.length,
|
|
967
|
+
preFlightReject: quality.preFlightReject,
|
|
968
|
+
filesOnDisk: checkArtifactsEnhanced(task, taskResult).files.filter(f => f.exists && f.sizeBytes > 0).length,
|
|
969
|
+
});
|
|
970
|
+
return true;
|
|
971
|
+
}
|
|
972
|
+
else {
|
|
973
|
+
// gateError=true but concrete validation disabled
|
|
974
|
+
ctx.qualityRejections++;
|
|
975
|
+
task.failureMode = 'quality';
|
|
976
|
+
ctx.hollowStreak = 0;
|
|
977
|
+
task.retryContext = {
|
|
978
|
+
previousFeedback: quality.feedback,
|
|
979
|
+
previousScore: quality.score,
|
|
980
|
+
attempt: task.attempts,
|
|
981
|
+
previousModel: model,
|
|
982
|
+
previousFiles: taskResult.filesModified,
|
|
983
|
+
swarmProgress: getSwarmProgressSummary(ctx),
|
|
984
|
+
};
|
|
985
|
+
const canRetry = ctx.taskQueue.markFailedWithoutCascade(taskId, effectiveRetries);
|
|
986
|
+
if (canRetry) {
|
|
987
|
+
ctx.retries++;
|
|
988
|
+
}
|
|
989
|
+
else {
|
|
990
|
+
if (await tryResilienceRecovery(ctx, recoveryState, task, taskId, taskResult, spawnResult)) {
|
|
991
|
+
return true;
|
|
992
|
+
}
|
|
993
|
+
ctx.taskQueue.triggerCascadeSkip(taskId);
|
|
994
|
+
}
|
|
995
|
+
ctx.emit({
|
|
996
|
+
type: 'swarm.quality.rejected',
|
|
997
|
+
taskId,
|
|
998
|
+
score: quality.score,
|
|
999
|
+
feedback: quality.feedback,
|
|
1000
|
+
artifactCount: fileArtifacts.length,
|
|
1001
|
+
outputLength: taskResult.output.length,
|
|
1002
|
+
preFlightReject: false,
|
|
1003
|
+
filesOnDisk: checkArtifactsEnhanced(task, taskResult).files.filter(f => f.exists && f.sizeBytes > 0).length,
|
|
1004
|
+
});
|
|
1005
|
+
return true;
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
// Quality passed — reset per-model rejection counter
|
|
1009
|
+
recoveryState.perModelQualityRejections.delete(model);
|
|
1010
|
+
return false; // passed
|
|
1011
|
+
}
|
|
1012
|
+
/** Internal helper for skipRemainingTasks within execution context */
|
|
1013
|
+
function skipRemainingTasksInternal(ctx, reason) {
|
|
1014
|
+
for (const task of ctx.taskQueue.getAllTasks()) {
|
|
1015
|
+
if (task.status === 'pending' || task.status === 'ready') {
|
|
1016
|
+
task.status = 'skipped';
|
|
1017
|
+
ctx.emit({ type: 'swarm.task.skipped', taskId: task.id, reason });
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
//# sourceMappingURL=swarm-execution.js.map
|