attocode 0.2.2 → 0.2.4
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 +169 -3
- package/README.md +65 -5
- package/dist/src/adapters.d.ts.map +1 -1
- package/dist/src/adapters.js +15 -11
- package/dist/src/adapters.js.map +1 -1
- package/dist/src/agent.d.ts +44 -98
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +716 -2648
- package/dist/src/agent.js.map +1 -1
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +25 -3
- package/dist/src/cli.js.map +1 -1
- package/dist/src/commands/handler.d.ts.map +1 -1
- package/dist/src/commands/handler.js +11 -3
- package/dist/src/commands/handler.js.map +1 -1
- package/dist/src/commands/init-commands.d.ts.map +1 -1
- package/dist/src/commands/init-commands.js +16 -1
- package/dist/src/commands/init-commands.js.map +1 -1
- package/dist/src/commands/init.d.ts.map +1 -1
- package/dist/src/commands/init.js +31 -0
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/config/base-types.d.ts +45 -0
- package/dist/src/config/base-types.d.ts.map +1 -0
- package/dist/src/config/base-types.js +9 -0
- package/dist/src/config/base-types.js.map +1 -0
- package/dist/src/config/config-manager.d.ts +35 -0
- package/dist/src/config/config-manager.d.ts.map +1 -0
- package/dist/src/config/config-manager.js +108 -0
- package/dist/src/config/config-manager.js.map +1 -0
- package/dist/src/config/index.d.ts +4 -0
- package/dist/src/config/index.d.ts.map +1 -0
- package/dist/src/config/index.js +3 -0
- package/dist/src/config/index.js.map +1 -0
- package/dist/src/config/schema.d.ts +1546 -0
- package/dist/src/config/schema.d.ts.map +1 -0
- package/dist/src/config/schema.js +268 -0
- package/dist/src/config/schema.js.map +1 -0
- package/dist/src/config.d.ts +4 -1
- package/dist/src/config.d.ts.map +1 -1
- package/dist/src/config.js +8 -12
- package/dist/src/config.js.map +1 -1
- package/dist/src/core/agent-state-machine.d.ts +131 -0
- package/dist/src/core/agent-state-machine.d.ts.map +1 -0
- package/dist/src/core/agent-state-machine.js +302 -0
- package/dist/src/core/agent-state-machine.js.map +1 -0
- package/dist/src/core/base-manager.d.ts +79 -0
- package/dist/src/core/base-manager.d.ts.map +1 -0
- package/dist/src/core/base-manager.js +170 -0
- package/dist/src/core/base-manager.js.map +1 -0
- package/dist/src/core/completion-analyzer.d.ts +15 -0
- package/dist/src/core/completion-analyzer.d.ts.map +1 -0
- package/dist/src/core/completion-analyzer.js +53 -0
- package/dist/src/core/completion-analyzer.js.map +1 -0
- package/dist/src/core/execution-loop.d.ts +46 -0
- package/dist/src/core/execution-loop.d.ts.map +1 -0
- package/dist/src/core/execution-loop.js +1258 -0
- package/dist/src/core/execution-loop.js.map +1 -0
- package/dist/src/core/index.d.ts +7 -0
- package/dist/src/core/index.d.ts.map +1 -1
- package/dist/src/core/index.js +9 -0
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/core/process-handlers.d.ts.map +1 -1
- package/dist/src/core/process-handlers.js +14 -0
- package/dist/src/core/process-handlers.js.map +1 -1
- package/dist/src/core/protocol/types.d.ts +4 -4
- package/dist/src/core/response-handler.d.ts +16 -0
- package/dist/src/core/response-handler.d.ts.map +1 -0
- package/dist/src/core/response-handler.js +234 -0
- package/dist/src/core/response-handler.js.map +1 -0
- package/dist/src/core/subagent-spawner.d.ts +43 -0
- package/dist/src/core/subagent-spawner.d.ts.map +1 -0
- package/dist/src/core/subagent-spawner.js +966 -0
- package/dist/src/core/subagent-spawner.js.map +1 -0
- package/dist/src/core/tool-executor.d.ts +59 -0
- package/dist/src/core/tool-executor.d.ts.map +1 -0
- package/dist/src/core/tool-executor.js +677 -0
- package/dist/src/core/tool-executor.js.map +1 -0
- package/dist/src/core/types.d.ts +133 -0
- package/dist/src/core/types.d.ts.map +1 -0
- package/dist/src/core/types.js +12 -0
- package/dist/src/core/types.js.map +1 -0
- package/dist/src/defaults.d.ts +8 -3
- package/dist/src/defaults.d.ts.map +1 -1
- package/dist/src/defaults.js +65 -3
- package/dist/src/defaults.js.map +1 -1
- package/dist/src/integrations/agent-registry.d.ts +11 -0
- package/dist/src/integrations/agent-registry.d.ts.map +1 -1
- package/dist/src/integrations/agent-registry.js.map +1 -1
- package/dist/src/integrations/auto-compaction.d.ts.map +1 -1
- package/dist/src/integrations/auto-compaction.js +8 -3
- package/dist/src/integrations/auto-compaction.js.map +1 -1
- package/dist/src/integrations/bash-policy.d.ts +33 -0
- package/dist/src/integrations/bash-policy.d.ts.map +1 -0
- package/dist/src/integrations/bash-policy.js +142 -0
- package/dist/src/integrations/bash-policy.js.map +1 -0
- package/dist/src/integrations/budget-pool.d.ts +7 -0
- package/dist/src/integrations/budget-pool.d.ts.map +1 -1
- package/dist/src/integrations/budget-pool.js +43 -0
- package/dist/src/integrations/budget-pool.js.map +1 -1
- package/dist/src/integrations/codebase-ast.d.ts +52 -0
- package/dist/src/integrations/codebase-ast.d.ts.map +1 -0
- package/dist/src/integrations/codebase-ast.js +457 -0
- package/dist/src/integrations/codebase-ast.js.map +1 -0
- package/dist/src/integrations/codebase-context.d.ts +23 -0
- package/dist/src/integrations/codebase-context.d.ts.map +1 -1
- package/dist/src/integrations/codebase-context.js +230 -17
- package/dist/src/integrations/codebase-context.js.map +1 -1
- package/dist/src/integrations/compaction.d.ts.map +1 -1
- package/dist/src/integrations/compaction.js +14 -6
- package/dist/src/integrations/compaction.js.map +1 -1
- package/dist/src/integrations/context-engineering.d.ts +8 -0
- package/dist/src/integrations/context-engineering.d.ts.map +1 -1
- package/dist/src/integrations/context-engineering.js +19 -0
- package/dist/src/integrations/context-engineering.js.map +1 -1
- package/dist/src/integrations/delegation-protocol.js +2 -2
- package/dist/src/integrations/delegation-protocol.js.map +1 -1
- package/dist/src/integrations/economics.d.ts +67 -1
- package/dist/src/integrations/economics.d.ts.map +1 -1
- package/dist/src/integrations/economics.js +328 -33
- package/dist/src/integrations/economics.js.map +1 -1
- package/dist/src/integrations/edit-validator.d.ts +30 -0
- package/dist/src/integrations/edit-validator.d.ts.map +1 -0
- package/dist/src/integrations/edit-validator.js +85 -0
- package/dist/src/integrations/edit-validator.js.map +1 -0
- package/dist/src/integrations/file-cache.d.ts +7 -0
- package/dist/src/integrations/file-cache.d.ts.map +1 -1
- package/dist/src/integrations/file-cache.js +54 -0
- package/dist/src/integrations/file-cache.js.map +1 -1
- package/dist/src/integrations/health-check.d.ts.map +1 -1
- package/dist/src/integrations/health-check.js +3 -2
- package/dist/src/integrations/health-check.js.map +1 -1
- package/dist/src/integrations/hierarchical-config.d.ts +3 -0
- package/dist/src/integrations/hierarchical-config.d.ts.map +1 -1
- package/dist/src/integrations/hierarchical-config.js +20 -0
- package/dist/src/integrations/hierarchical-config.js.map +1 -1
- package/dist/src/integrations/hooks.d.ts +2 -0
- package/dist/src/integrations/hooks.d.ts.map +1 -1
- package/dist/src/integrations/hooks.js +99 -15
- package/dist/src/integrations/hooks.js.map +1 -1
- package/dist/src/integrations/index.d.ts +10 -1
- package/dist/src/integrations/index.d.ts.map +1 -1
- package/dist/src/integrations/index.js +12 -2
- package/dist/src/integrations/index.js.map +1 -1
- package/dist/src/integrations/logger.d.ts +104 -0
- package/dist/src/integrations/logger.d.ts.map +1 -0
- package/dist/src/integrations/logger.js +219 -0
- package/dist/src/integrations/logger.js.map +1 -0
- package/dist/src/integrations/lsp.d.ts.map +1 -1
- package/dist/src/integrations/lsp.js +5 -4
- package/dist/src/integrations/lsp.js.map +1 -1
- package/dist/src/integrations/mcp-client.d.ts.map +1 -1
- package/dist/src/integrations/mcp-client.js +8 -7
- package/dist/src/integrations/mcp-client.js.map +1 -1
- package/dist/src/integrations/observability.d.ts.map +1 -1
- package/dist/src/integrations/observability.js +5 -4
- package/dist/src/integrations/observability.js.map +1 -1
- package/dist/src/integrations/openrouter-pricing.d.ts.map +1 -1
- package/dist/src/integrations/openrouter-pricing.js +4 -3
- package/dist/src/integrations/openrouter-pricing.js.map +1 -1
- package/dist/src/integrations/persistence.d.ts.map +1 -1
- package/dist/src/integrations/persistence.js +5 -4
- package/dist/src/integrations/persistence.js.map +1 -1
- package/dist/src/integrations/planning.d.ts.map +1 -1
- package/dist/src/integrations/planning.js +5 -4
- package/dist/src/integrations/planning.js.map +1 -1
- package/dist/src/integrations/policy-engine.d.ts +55 -0
- package/dist/src/integrations/policy-engine.d.ts.map +1 -0
- package/dist/src/integrations/policy-engine.js +247 -0
- package/dist/src/integrations/policy-engine.js.map +1 -0
- package/dist/src/integrations/retry.d.ts +1 -0
- package/dist/src/integrations/retry.d.ts.map +1 -1
- package/dist/src/integrations/retry.js.map +1 -1
- package/dist/src/integrations/routing.d.ts.map +1 -1
- package/dist/src/integrations/routing.js +2 -1
- package/dist/src/integrations/routing.js.map +1 -1
- package/dist/src/integrations/safety.d.ts +5 -4
- package/dist/src/integrations/safety.d.ts.map +1 -1
- package/dist/src/integrations/safety.js +45 -20
- package/dist/src/integrations/safety.js.map +1 -1
- package/dist/src/integrations/sandbox/basic.d.ts +7 -0
- package/dist/src/integrations/sandbox/basic.d.ts.map +1 -1
- package/dist/src/integrations/sandbox/basic.js +27 -2
- package/dist/src/integrations/sandbox/basic.js.map +1 -1
- package/dist/src/integrations/sandbox/docker.d.ts.map +1 -1
- package/dist/src/integrations/sandbox/docker.js +2 -1
- package/dist/src/integrations/sandbox/docker.js.map +1 -1
- package/dist/src/integrations/sandbox/index.d.ts +6 -0
- package/dist/src/integrations/sandbox/index.d.ts.map +1 -1
- package/dist/src/integrations/sandbox/index.js +8 -4
- package/dist/src/integrations/sandbox/index.js.map +1 -1
- package/dist/src/integrations/sandbox/landlock.d.ts.map +1 -1
- package/dist/src/integrations/sandbox/landlock.js +3 -0
- package/dist/src/integrations/sandbox/landlock.js.map +1 -1
- package/dist/src/integrations/self-improvement.d.ts.map +1 -1
- package/dist/src/integrations/self-improvement.js +12 -0
- package/dist/src/integrations/self-improvement.js.map +1 -1
- package/dist/src/integrations/session-store.d.ts +1 -0
- package/dist/src/integrations/session-store.d.ts.map +1 -1
- package/dist/src/integrations/session-store.js +1 -0
- package/dist/src/integrations/session-store.js.map +1 -1
- package/dist/src/integrations/shared-blackboard.d.ts +3 -0
- package/dist/src/integrations/shared-blackboard.d.ts.map +1 -1
- package/dist/src/integrations/shared-blackboard.js +47 -0
- package/dist/src/integrations/shared-blackboard.js.map +1 -1
- package/dist/src/integrations/smart-decomposer.d.ts +45 -1
- package/dist/src/integrations/smart-decomposer.d.ts.map +1 -1
- package/dist/src/integrations/smart-decomposer.js +486 -30
- package/dist/src/integrations/smart-decomposer.js.map +1 -1
- package/dist/src/integrations/sqlite-store.d.ts +2 -0
- package/dist/src/integrations/sqlite-store.d.ts.map +1 -1
- package/dist/src/integrations/sqlite-store.js +18 -6
- package/dist/src/integrations/sqlite-store.js.map +1 -1
- package/dist/src/integrations/swarm/failure-classifier.d.ts +11 -0
- package/dist/src/integrations/swarm/failure-classifier.d.ts.map +1 -0
- package/dist/src/integrations/swarm/failure-classifier.js +95 -0
- package/dist/src/integrations/swarm/failure-classifier.js.map +1 -0
- package/dist/src/integrations/swarm/index.d.ts +1 -1
- package/dist/src/integrations/swarm/index.d.ts.map +1 -1
- package/dist/src/integrations/swarm/index.js.map +1 -1
- package/dist/src/integrations/swarm/model-selector.d.ts +15 -0
- package/dist/src/integrations/swarm/model-selector.d.ts.map +1 -1
- package/dist/src/integrations/swarm/model-selector.js +100 -20
- package/dist/src/integrations/swarm/model-selector.js.map +1 -1
- package/dist/src/integrations/swarm/swarm-budget.d.ts +4 -0
- package/dist/src/integrations/swarm/swarm-budget.d.ts.map +1 -1
- package/dist/src/integrations/swarm/swarm-budget.js +6 -0
- package/dist/src/integrations/swarm/swarm-budget.js.map +1 -1
- package/dist/src/integrations/swarm/swarm-config-loader.d.ts +8 -0
- package/dist/src/integrations/swarm/swarm-config-loader.d.ts.map +1 -1
- package/dist/src/integrations/swarm/swarm-config-loader.js +249 -7
- package/dist/src/integrations/swarm/swarm-config-loader.js.map +1 -1
- package/dist/src/integrations/swarm/swarm-event-bridge.d.ts +86 -1
- package/dist/src/integrations/swarm/swarm-event-bridge.d.ts.map +1 -1
- package/dist/src/integrations/swarm/swarm-event-bridge.js +207 -23
- package/dist/src/integrations/swarm/swarm-event-bridge.js.map +1 -1
- package/dist/src/integrations/swarm/swarm-events.d.ts +58 -1
- package/dist/src/integrations/swarm/swarm-events.d.ts.map +1 -1
- package/dist/src/integrations/swarm/swarm-events.js +22 -5
- package/dist/src/integrations/swarm/swarm-events.js.map +1 -1
- package/dist/src/integrations/swarm/swarm-orchestrator.d.ts +147 -8
- package/dist/src/integrations/swarm/swarm-orchestrator.d.ts.map +1 -1
- package/dist/src/integrations/swarm/swarm-orchestrator.js +2179 -132
- package/dist/src/integrations/swarm/swarm-orchestrator.js.map +1 -1
- package/dist/src/integrations/swarm/swarm-quality-gate.d.ts +83 -2
- package/dist/src/integrations/swarm/swarm-quality-gate.d.ts.map +1 -1
- package/dist/src/integrations/swarm/swarm-quality-gate.js +278 -19
- package/dist/src/integrations/swarm/swarm-quality-gate.js.map +1 -1
- package/dist/src/integrations/swarm/swarm-state-store.d.ts +4 -1
- package/dist/src/integrations/swarm/swarm-state-store.d.ts.map +1 -1
- package/dist/src/integrations/swarm/swarm-state-store.js +8 -1
- package/dist/src/integrations/swarm/swarm-state-store.js.map +1 -1
- package/dist/src/integrations/swarm/task-queue.d.ts +54 -0
- package/dist/src/integrations/swarm/task-queue.d.ts.map +1 -1
- package/dist/src/integrations/swarm/task-queue.js +310 -12
- package/dist/src/integrations/swarm/task-queue.js.map +1 -1
- package/dist/src/integrations/swarm/types.d.ts +251 -13
- package/dist/src/integrations/swarm/types.d.ts.map +1 -1
- package/dist/src/integrations/swarm/types.js +70 -8
- package/dist/src/integrations/swarm/types.js.map +1 -1
- package/dist/src/integrations/swarm/worker-pool.d.ts +21 -4
- package/dist/src/integrations/swarm/worker-pool.d.ts.map +1 -1
- package/dist/src/integrations/swarm/worker-pool.js +223 -44
- package/dist/src/integrations/swarm/worker-pool.js.map +1 -1
- package/dist/src/integrations/task-manager.d.ts +33 -1
- package/dist/src/integrations/task-manager.d.ts.map +1 -1
- package/dist/src/integrations/task-manager.js +78 -4
- package/dist/src/integrations/task-manager.js.map +1 -1
- package/dist/src/integrations/tool-recommendation.d.ts +7 -4
- package/dist/src/integrations/tool-recommendation.d.ts.map +1 -1
- package/dist/src/integrations/tool-recommendation.js +58 -5
- package/dist/src/integrations/tool-recommendation.js.map +1 -1
- package/dist/src/integrations/work-log.js +4 -4
- package/dist/src/integrations/work-log.js.map +1 -1
- package/dist/src/main.js +106 -30
- package/dist/src/main.js.map +1 -1
- package/dist/src/modes/repl.d.ts.map +1 -1
- package/dist/src/modes/repl.js +50 -12
- 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 +41 -6
- package/dist/src/modes/tui.js.map +1 -1
- package/dist/src/modes.d.ts.map +1 -1
- package/dist/src/modes.js +4 -27
- package/dist/src/modes.js.map +1 -1
- package/dist/src/observability/tracer.d.ts.map +1 -1
- package/dist/src/observability/tracer.js +2 -1
- package/dist/src/observability/tracer.js.map +1 -1
- package/dist/src/persistence/schema.d.ts.map +1 -1
- package/dist/src/persistence/schema.js +11 -0
- package/dist/src/persistence/schema.js.map +1 -1
- package/dist/src/providers/adapters/anthropic.d.ts.map +1 -1
- package/dist/src/providers/adapters/anthropic.js +3 -2
- package/dist/src/providers/adapters/anthropic.js.map +1 -1
- package/dist/src/providers/adapters/openai.d.ts.map +1 -1
- package/dist/src/providers/adapters/openai.js +3 -2
- package/dist/src/providers/adapters/openai.js.map +1 -1
- package/dist/src/providers/adapters/openrouter.d.ts.map +1 -1
- package/dist/src/providers/adapters/openrouter.js +11 -11
- package/dist/src/providers/adapters/openrouter.js.map +1 -1
- package/dist/src/providers/circuit-breaker.d.ts +1 -0
- package/dist/src/providers/circuit-breaker.d.ts.map +1 -1
- package/dist/src/providers/circuit-breaker.js.map +1 -1
- package/dist/src/providers/provider.d.ts.map +1 -1
- package/dist/src/providers/provider.js +2 -1
- package/dist/src/providers/provider.js.map +1 -1
- package/dist/src/providers/resilient-provider.d.ts.map +1 -1
- package/dist/src/providers/resilient-provider.js +2 -1
- package/dist/src/providers/resilient-provider.js.map +1 -1
- package/dist/src/session-picker.d.ts.map +1 -1
- package/dist/src/session-picker.js +40 -5
- package/dist/src/session-picker.js.map +1 -1
- package/dist/src/shared/budget-tracker.d.ts +65 -0
- package/dist/src/shared/budget-tracker.d.ts.map +1 -0
- package/dist/src/shared/budget-tracker.js +128 -0
- package/dist/src/shared/budget-tracker.js.map +1 -0
- package/dist/src/shared/context-engine.d.ts +64 -0
- package/dist/src/shared/context-engine.d.ts.map +1 -0
- package/dist/src/shared/context-engine.js +117 -0
- package/dist/src/shared/context-engine.js.map +1 -0
- package/dist/src/shared/index.d.ts +12 -0
- package/dist/src/shared/index.d.ts.map +1 -0
- package/dist/src/shared/index.js +12 -0
- package/dist/src/shared/index.js.map +1 -0
- package/dist/src/shared/persistence.d.ts +57 -0
- package/dist/src/shared/persistence.d.ts.map +1 -0
- package/dist/src/shared/persistence.js +168 -0
- package/dist/src/shared/persistence.js.map +1 -0
- package/dist/src/shared/shared-context-state.d.ts +89 -0
- package/dist/src/shared/shared-context-state.d.ts.map +1 -0
- package/dist/src/shared/shared-context-state.js +175 -0
- package/dist/src/shared/shared-context-state.js.map +1 -0
- package/dist/src/shared/shared-economics-state.d.ts +61 -0
- package/dist/src/shared/shared-economics-state.d.ts.map +1 -0
- package/dist/src/shared/shared-economics-state.js +100 -0
- package/dist/src/shared/shared-economics-state.js.map +1 -0
- package/dist/src/tools/agent.d.ts.map +1 -1
- package/dist/src/tools/agent.js +11 -2
- package/dist/src/tools/agent.js.map +1 -1
- package/dist/src/tools/bash.d.ts +1 -1
- package/dist/src/tools/bash.d.ts.map +1 -1
- package/dist/src/tools/bash.js +2 -1
- package/dist/src/tools/bash.js.map +1 -1
- package/dist/src/tools/coercion.d.ts +6 -0
- package/dist/src/tools/coercion.d.ts.map +1 -1
- package/dist/src/tools/coercion.js +13 -0
- package/dist/src/tools/coercion.js.map +1 -1
- package/dist/src/tools/file.d.ts +5 -5
- package/dist/src/tools/file.js +2 -2
- package/dist/src/tools/file.js.map +1 -1
- package/dist/src/tools/permission.d.ts.map +1 -1
- package/dist/src/tools/permission.js +10 -116
- package/dist/src/tools/permission.js.map +1 -1
- package/dist/src/tools/types.d.ts +1 -0
- package/dist/src/tools/types.d.ts.map +1 -1
- package/dist/src/tools/types.js.map +1 -1
- package/dist/src/tracing/trace-collector.d.ts +292 -0
- package/dist/src/tracing/trace-collector.d.ts.map +1 -1
- package/dist/src/tracing/trace-collector.js +249 -5
- package/dist/src/tracing/trace-collector.js.map +1 -1
- package/dist/src/tracing/types.d.ts +200 -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.d.ts.map +1 -1
- package/dist/src/tricks/failure-evidence.js +2 -1
- package/dist/src/tricks/failure-evidence.js.map +1 -1
- package/dist/src/tui/app.d.ts +13 -0
- package/dist/src/tui/app.d.ts.map +1 -1
- package/dist/src/tui/app.js +162 -19
- package/dist/src/tui/app.js.map +1 -1
- package/dist/src/tui/components/ErrorBoundary.d.ts.map +1 -1
- package/dist/src/tui/components/ErrorBoundary.js +3 -2
- package/dist/src/tui/components/ErrorBoundary.js.map +1 -1
- package/dist/src/tui/event-display.d.ts.map +1 -1
- package/dist/src/tui/event-display.js +36 -62
- package/dist/src/tui/event-display.js.map +1 -1
- package/dist/src/tui/index.d.ts +4 -0
- package/dist/src/tui/index.d.ts.map +1 -1
- package/dist/src/tui/index.js +17 -0
- package/dist/src/tui/index.js.map +1 -1
- package/dist/src/types.d.ts +214 -1
- package/dist/src/types.d.ts.map +1 -1
- package/package.json +18 -3
|
@@ -0,0 +1,1258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Execution Loop Module (Phase 2.1)
|
|
3
|
+
*
|
|
4
|
+
* Extracted from ProductionAgent.executeDirectly().
|
|
5
|
+
* Contains the main ReAct while(true) loop: cancellation checks, resource checks,
|
|
6
|
+
* economics/budget checks with compaction recovery, loop detection, context
|
|
7
|
+
* engineering injection (recitation, failure evidence), LLM call dispatch,
|
|
8
|
+
* resilience handling, tool execution, and compaction.
|
|
9
|
+
*/
|
|
10
|
+
import { isFeatureEnabled } from '../defaults.js';
|
|
11
|
+
import { callLLM } from './response-handler.js';
|
|
12
|
+
import { executeToolCalls } from './tool-executor.js';
|
|
13
|
+
import { TIMEOUT_WRAPUP_PROMPT, stableStringify, } from '../integrations/index.js';
|
|
14
|
+
import { detectIncompleteActionResponse } from './completion-analyzer.js';
|
|
15
|
+
export { detectIncompleteActionResponse } from './completion-analyzer.js';
|
|
16
|
+
import { createComponentLogger } from '../integrations/logger.js';
|
|
17
|
+
import { validateSyntax } from '../integrations/edit-validator.js';
|
|
18
|
+
import * as fs from 'node:fs';
|
|
19
|
+
const log = createComponentLogger('ExecutionLoop');
|
|
20
|
+
// =============================================================================
|
|
21
|
+
// HELPER FUNCTIONS (extracted from ProductionAgent private methods)
|
|
22
|
+
// =============================================================================
|
|
23
|
+
/**
|
|
24
|
+
* Estimate total tokens in a message array.
|
|
25
|
+
* Uses ~4 chars per token heuristic for fast estimation.
|
|
26
|
+
*/
|
|
27
|
+
export function estimateContextTokens(messages) {
|
|
28
|
+
let totalChars = 0;
|
|
29
|
+
for (const msg of messages) {
|
|
30
|
+
if (msg.content) {
|
|
31
|
+
totalChars += msg.content.length;
|
|
32
|
+
}
|
|
33
|
+
if (msg.toolCalls) {
|
|
34
|
+
for (const tc of msg.toolCalls) {
|
|
35
|
+
totalChars += tc.name.length;
|
|
36
|
+
totalChars += JSON.stringify(tc.arguments).length;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return Math.ceil(totalChars / 4);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Compact tool outputs to save context.
|
|
44
|
+
*/
|
|
45
|
+
export function compactToolOutputs(messages) {
|
|
46
|
+
const COMPACT_PREVIEW_LENGTH = 200;
|
|
47
|
+
const MAX_PRESERVED_EXPENSIVE_RESULTS = 6;
|
|
48
|
+
let compactedCount = 0;
|
|
49
|
+
let savedChars = 0;
|
|
50
|
+
const preservedExpensiveIndexes = messages
|
|
51
|
+
.map((msg, index) => ({ msg, index }))
|
|
52
|
+
.filter(({ msg }) => msg.role === 'tool' && msg.metadata?.preserveFromCompaction === true)
|
|
53
|
+
.map(({ index }) => index);
|
|
54
|
+
const preserveSet = new Set(preservedExpensiveIndexes.slice(-MAX_PRESERVED_EXPENSIVE_RESULTS));
|
|
55
|
+
for (let i = 0; i < messages.length; i++) {
|
|
56
|
+
const msg = messages[i];
|
|
57
|
+
if (msg.role === 'tool' && msg.content && msg.content.length > COMPACT_PREVIEW_LENGTH * 2) {
|
|
58
|
+
if (msg.metadata?.preserveFromCompaction === true && preserveSet.has(i)) {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const originalLength = msg.content.length;
|
|
62
|
+
const preview = msg.content.slice(0, COMPACT_PREVIEW_LENGTH).replace(/\n/g, ' ');
|
|
63
|
+
msg.content = `[${preview}...] (${originalLength} chars, compacted)`;
|
|
64
|
+
savedChars += originalLength - msg.content.length;
|
|
65
|
+
compactedCount++;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (compactedCount > 0 && process.env.DEBUG) {
|
|
69
|
+
log.debug('Compacted tool outputs', { compactedCount, savedTokens: Math.round(savedChars / 4) });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Extract a requested markdown artifact filename from a task prompt.
|
|
74
|
+
*/
|
|
75
|
+
export function extractRequestedArtifact(task) {
|
|
76
|
+
const markdownArtifactMatch = task.match(/(?:write|save|create)[^.\n]{0,120}\b([A-Za-z0-9._/-]+\.md)\b/i);
|
|
77
|
+
return markdownArtifactMatch?.[1] ?? null;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Check whether a requested artifact appears to be missing.
|
|
81
|
+
*/
|
|
82
|
+
export function isRequestedArtifactMissing(requestedArtifact, executedToolNames) {
|
|
83
|
+
if (!requestedArtifact)
|
|
84
|
+
return false;
|
|
85
|
+
const artifactWriteTools = ['write_file', 'edit_file', 'apply_patch', 'append_file'];
|
|
86
|
+
return !artifactWriteTools.some(toolName => executedToolNames.has(toolName));
|
|
87
|
+
}
|
|
88
|
+
function getOpenTaskSummary(ctx) {
|
|
89
|
+
if (!ctx.taskManager)
|
|
90
|
+
return undefined;
|
|
91
|
+
const tasks = ctx.taskManager.list();
|
|
92
|
+
const pending = tasks.filter(t => t.status === 'pending').length;
|
|
93
|
+
const inProgress = tasks.filter(t => t.status === 'in_progress').length;
|
|
94
|
+
const blocked = tasks.filter(t => t.status === 'pending' && ctx.taskManager?.isBlocked(t.id)).length;
|
|
95
|
+
return { pending, inProgress, blocked };
|
|
96
|
+
}
|
|
97
|
+
function getPendingWithOwnerCount(ctx) {
|
|
98
|
+
if (!ctx.taskManager)
|
|
99
|
+
return 0;
|
|
100
|
+
return ctx.taskManager.list().filter(t => t.status === 'pending' && !!t.owner).length;
|
|
101
|
+
}
|
|
102
|
+
// =============================================================================
|
|
103
|
+
// MAIN EXECUTION LOOP
|
|
104
|
+
// =============================================================================
|
|
105
|
+
/**
|
|
106
|
+
* Execute a task directly without planning.
|
|
107
|
+
* This is the main ReAct loop extracted from ProductionAgent.executeDirectly().
|
|
108
|
+
*
|
|
109
|
+
* @param task - The user task to execute
|
|
110
|
+
* @param ctx - Agent context (managers, config, state)
|
|
111
|
+
* @param mutators - Functions to update mutable agent state
|
|
112
|
+
*/
|
|
113
|
+
export async function executeDirectly(task, messages, ctx, mutators) {
|
|
114
|
+
// Reset economics for new task
|
|
115
|
+
ctx.economics?.reset();
|
|
116
|
+
const taskLeaseStaleMs = typeof ctx.config.resilience === 'object'
|
|
117
|
+
? (ctx.config.resilience.taskLeaseStaleMs ?? 5 * 60 * 1000)
|
|
118
|
+
: 5 * 60 * 1000;
|
|
119
|
+
// Recover orphaned in-progress tasks left behind by interrupted runs.
|
|
120
|
+
if (ctx.taskManager) {
|
|
121
|
+
const recovered = ctx.taskManager.reconcileStaleInProgress({
|
|
122
|
+
staleAfterMs: taskLeaseStaleMs,
|
|
123
|
+
reason: 'execution_loop_start',
|
|
124
|
+
});
|
|
125
|
+
if (recovered.reconciled > 0) {
|
|
126
|
+
log.info('Recovered stale in-progress tasks at execution start', {
|
|
127
|
+
recovered: recovered.reconciled,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// Reflection configuration
|
|
132
|
+
const reflectionConfig = ctx.config.reflection;
|
|
133
|
+
const reflectionEnabled = isFeatureEnabled(reflectionConfig);
|
|
134
|
+
const autoReflect = reflectionEnabled && reflectionConfig.autoReflect;
|
|
135
|
+
const maxReflectionAttempts = reflectionEnabled
|
|
136
|
+
? (reflectionConfig.maxAttempts || 3)
|
|
137
|
+
: 1;
|
|
138
|
+
const confidenceThreshold = reflectionEnabled
|
|
139
|
+
? (reflectionConfig.confidenceThreshold || 0.8)
|
|
140
|
+
: 0.8;
|
|
141
|
+
let reflectionAttempt = 0;
|
|
142
|
+
let lastResponse = '';
|
|
143
|
+
let incompleteActionRetries = 0;
|
|
144
|
+
const requestedArtifact = extractRequestedArtifact(task);
|
|
145
|
+
const executedToolNames = new Set();
|
|
146
|
+
let result = {
|
|
147
|
+
success: true,
|
|
148
|
+
terminationReason: 'completed',
|
|
149
|
+
};
|
|
150
|
+
// Outer loop for reflection (if enabled)
|
|
151
|
+
while (reflectionAttempt < maxReflectionAttempts) {
|
|
152
|
+
reflectionAttempt++;
|
|
153
|
+
// Agent loop - uses economics-based budget checking
|
|
154
|
+
while (true) {
|
|
155
|
+
ctx.state.iteration++;
|
|
156
|
+
ctx.emit({ type: 'iteration.before', iteration: ctx.state.iteration });
|
|
157
|
+
// Record iteration start for tracing
|
|
158
|
+
ctx.traceCollector?.record({
|
|
159
|
+
type: 'iteration.start',
|
|
160
|
+
data: { iterationNumber: ctx.state.iteration },
|
|
161
|
+
});
|
|
162
|
+
// =======================================================================
|
|
163
|
+
// CANCELLATION CHECK
|
|
164
|
+
// =======================================================================
|
|
165
|
+
if (ctx.cancellation?.isCancelled) {
|
|
166
|
+
ctx.cancellation.token.throwIfCancellationRequested();
|
|
167
|
+
}
|
|
168
|
+
// =======================================================================
|
|
169
|
+
// RESOURCE CHECK
|
|
170
|
+
// =======================================================================
|
|
171
|
+
if (ctx.resourceManager) {
|
|
172
|
+
const resourceCheck = ctx.resourceManager.check();
|
|
173
|
+
if (!resourceCheck.canContinue) {
|
|
174
|
+
ctx.observability?.logger?.warn('Resource limit reached', {
|
|
175
|
+
status: resourceCheck.status,
|
|
176
|
+
message: resourceCheck.message,
|
|
177
|
+
});
|
|
178
|
+
const reason = resourceCheck.message || 'Resource limit exceeded';
|
|
179
|
+
ctx.emit({ type: 'error', error: reason });
|
|
180
|
+
result = {
|
|
181
|
+
success: false,
|
|
182
|
+
terminationReason: 'resource_limit',
|
|
183
|
+
failureReason: reason,
|
|
184
|
+
};
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
if (resourceCheck.status === 'warning' || resourceCheck.status === 'critical') {
|
|
188
|
+
ctx.observability?.logger?.info(`Resource status: ${resourceCheck.status}`, {
|
|
189
|
+
message: resourceCheck.message,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// =======================================================================
|
|
194
|
+
// ECONOMICS CHECK (Token Budget)
|
|
195
|
+
// =======================================================================
|
|
196
|
+
let forceTextOnly = false;
|
|
197
|
+
let budgetInjectedPrompt;
|
|
198
|
+
if (ctx.economics) {
|
|
199
|
+
const budgetCheck = ctx.economics.checkBudget();
|
|
200
|
+
forceTextOnly = budgetCheck.forceTextOnly ?? false;
|
|
201
|
+
budgetInjectedPrompt = budgetCheck.injectedPrompt;
|
|
202
|
+
if (!budgetCheck.canContinue) {
|
|
203
|
+
// RECOVERY ATTEMPT: Try emergency context reduction
|
|
204
|
+
const isTokenLimit = budgetCheck.budgetType === 'tokens' || budgetCheck.budgetType === 'cost';
|
|
205
|
+
const alreadyTriedRecovery = ctx.state._recoveryAttempted === true;
|
|
206
|
+
if (isTokenLimit && !alreadyTriedRecovery) {
|
|
207
|
+
ctx.observability?.logger?.info('Budget limit reached, attempting recovery via context reduction', {
|
|
208
|
+
reason: budgetCheck.reason,
|
|
209
|
+
percentUsed: budgetCheck.percentUsed,
|
|
210
|
+
});
|
|
211
|
+
ctx.emit({
|
|
212
|
+
type: 'resilience.retry',
|
|
213
|
+
reason: 'budget_limit_compaction',
|
|
214
|
+
attempt: 1,
|
|
215
|
+
maxAttempts: 1,
|
|
216
|
+
});
|
|
217
|
+
ctx.state.metrics.retryCount = (ctx.state.metrics.retryCount ?? 0) + 1;
|
|
218
|
+
ctx.state._recoveryAttempted = true;
|
|
219
|
+
const tokensBefore = estimateContextTokens(messages);
|
|
220
|
+
// Step 1: Compact tool outputs aggressively
|
|
221
|
+
compactToolOutputs(ctx.state.messages);
|
|
222
|
+
// Step 2: Emergency truncation - keep system + last N messages
|
|
223
|
+
const PRESERVE_RECENT = 10;
|
|
224
|
+
if (messages.length > PRESERVE_RECENT + 2) {
|
|
225
|
+
const systemMessage = messages.find(m => m.role === 'system');
|
|
226
|
+
const recentMessages = messages.slice(-(PRESERVE_RECENT));
|
|
227
|
+
messages.length = 0;
|
|
228
|
+
if (systemMessage) {
|
|
229
|
+
messages.push(systemMessage);
|
|
230
|
+
}
|
|
231
|
+
messages.push({
|
|
232
|
+
role: 'system',
|
|
233
|
+
content: `[CONTEXT REDUCED: Earlier messages were removed to stay within budget. Conversation continues from recent context.]`,
|
|
234
|
+
});
|
|
235
|
+
messages.push(...recentMessages);
|
|
236
|
+
// Inject work log after emergency truncation
|
|
237
|
+
if (ctx.workLog?.hasContent()) {
|
|
238
|
+
const workLogMessage = {
|
|
239
|
+
role: 'user',
|
|
240
|
+
content: ctx.workLog.toCompactString(),
|
|
241
|
+
};
|
|
242
|
+
messages.push(workLogMessage);
|
|
243
|
+
}
|
|
244
|
+
// Update state messages too
|
|
245
|
+
ctx.state.messages.length = 0;
|
|
246
|
+
ctx.state.messages.push(...messages);
|
|
247
|
+
}
|
|
248
|
+
const tokensAfter = estimateContextTokens(messages);
|
|
249
|
+
const reduction = Math.round((1 - tokensAfter / tokensBefore) * 100);
|
|
250
|
+
if (tokensAfter < tokensBefore * 0.8) {
|
|
251
|
+
ctx.observability?.logger?.info('Context reduction successful, continuing execution', {
|
|
252
|
+
tokensBefore,
|
|
253
|
+
tokensAfter,
|
|
254
|
+
reduction,
|
|
255
|
+
});
|
|
256
|
+
ctx.emit({
|
|
257
|
+
type: 'resilience.recovered',
|
|
258
|
+
reason: 'budget_limit_compaction',
|
|
259
|
+
attempts: 1,
|
|
260
|
+
});
|
|
261
|
+
ctx.emit({
|
|
262
|
+
type: 'compaction.auto',
|
|
263
|
+
tokensBefore,
|
|
264
|
+
tokensAfter,
|
|
265
|
+
messagesCompacted: tokensBefore - tokensAfter,
|
|
266
|
+
});
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
ctx.observability?.logger?.warn('Context reduction insufficient', {
|
|
270
|
+
tokensBefore,
|
|
271
|
+
tokensAfter,
|
|
272
|
+
reduction,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
// Hard limit reached and recovery failed
|
|
276
|
+
ctx.observability?.logger?.warn('Budget limit reached', {
|
|
277
|
+
reason: budgetCheck.reason,
|
|
278
|
+
budgetType: budgetCheck.budgetType,
|
|
279
|
+
});
|
|
280
|
+
if (budgetCheck.budgetType === 'iterations') {
|
|
281
|
+
const totalIter = ctx.getTotalIterations();
|
|
282
|
+
const iterMsg = ctx.parentIterations > 0
|
|
283
|
+
? `${ctx.state.iteration} + ${ctx.parentIterations} parent = ${totalIter}`
|
|
284
|
+
: `${ctx.state.iteration}`;
|
|
285
|
+
const reason = `Max iterations reached (${iterMsg})`;
|
|
286
|
+
ctx.emit({ type: 'error', error: reason });
|
|
287
|
+
result = {
|
|
288
|
+
success: false,
|
|
289
|
+
terminationReason: 'max_iterations',
|
|
290
|
+
failureReason: reason,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
const reason = budgetCheck.reason || 'Budget exceeded';
|
|
295
|
+
ctx.emit({ type: 'error', error: reason });
|
|
296
|
+
result = {
|
|
297
|
+
success: false,
|
|
298
|
+
terminationReason: 'budget_limit',
|
|
299
|
+
failureReason: reason,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
break;
|
|
303
|
+
}
|
|
304
|
+
// Check for soft limits and potential extension
|
|
305
|
+
if (budgetCheck.isSoftLimit && budgetCheck.suggestedAction === 'request_extension') {
|
|
306
|
+
ctx.observability?.logger?.info('Approaching budget limit', {
|
|
307
|
+
reason: budgetCheck.reason,
|
|
308
|
+
percentUsed: budgetCheck.percentUsed,
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
// Fallback to simple iteration check
|
|
314
|
+
if (ctx.getTotalIterations() >= ctx.config.maxIterations) {
|
|
315
|
+
const totalIter = ctx.getTotalIterations();
|
|
316
|
+
const reason = `Max iterations reached (${totalIter})`;
|
|
317
|
+
ctx.observability?.logger?.warn('Max iterations reached', {
|
|
318
|
+
iteration: ctx.state.iteration,
|
|
319
|
+
parentIterations: ctx.parentIterations,
|
|
320
|
+
total: totalIter,
|
|
321
|
+
});
|
|
322
|
+
ctx.emit({ type: 'error', error: reason });
|
|
323
|
+
result = {
|
|
324
|
+
success: false,
|
|
325
|
+
terminationReason: 'max_iterations',
|
|
326
|
+
failureReason: reason,
|
|
327
|
+
};
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
// =======================================================================
|
|
332
|
+
// GRACEFUL WRAPUP CHECK
|
|
333
|
+
// =======================================================================
|
|
334
|
+
if (ctx.wrapupRequested && !forceTextOnly) {
|
|
335
|
+
forceTextOnly = true;
|
|
336
|
+
budgetInjectedPrompt = TIMEOUT_WRAPUP_PROMPT;
|
|
337
|
+
mutators.setWrapupRequested(false);
|
|
338
|
+
}
|
|
339
|
+
// =======================================================================
|
|
340
|
+
// EXTERNAL CANCELLATION CHECK (deferred)
|
|
341
|
+
// =======================================================================
|
|
342
|
+
if (ctx.externalCancellationToken?.isCancellationRequested && !forceTextOnly) {
|
|
343
|
+
ctx.externalCancellationToken.throwIfCancellationRequested();
|
|
344
|
+
}
|
|
345
|
+
// =======================================================================
|
|
346
|
+
// INTELLIGENT LOOP DETECTION & NUDGE INJECTION
|
|
347
|
+
// =======================================================================
|
|
348
|
+
if (ctx.economics && budgetInjectedPrompt) {
|
|
349
|
+
messages.push({
|
|
350
|
+
role: 'user',
|
|
351
|
+
content: budgetInjectedPrompt,
|
|
352
|
+
});
|
|
353
|
+
const loopState = ctx.economics.getLoopState();
|
|
354
|
+
const phaseState = ctx.economics.getPhaseState();
|
|
355
|
+
ctx.observability?.logger?.info('Loop detection - injecting guidance', {
|
|
356
|
+
iteration: ctx.state.iteration,
|
|
357
|
+
doomLoop: loopState.doomLoopDetected,
|
|
358
|
+
phase: phaseState.phase,
|
|
359
|
+
filesRead: phaseState.uniqueFilesRead,
|
|
360
|
+
filesModified: phaseState.filesModified,
|
|
361
|
+
shouldTransition: phaseState.shouldTransition,
|
|
362
|
+
forceTextOnly,
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
// =======================================================================
|
|
366
|
+
// RECITATION INJECTION (Trick Q)
|
|
367
|
+
// =======================================================================
|
|
368
|
+
if (ctx.contextEngineering) {
|
|
369
|
+
if (process.env.DEBUG_LLM) {
|
|
370
|
+
if (process.env.DEBUG)
|
|
371
|
+
log.debug('Recitation before', { messageCount: messages.length });
|
|
372
|
+
}
|
|
373
|
+
const enrichedMessages = ctx.contextEngineering.injectRecitation(messages, {
|
|
374
|
+
goal: task,
|
|
375
|
+
plan: ctx.state.plan ? {
|
|
376
|
+
description: ctx.state.plan.goal || task,
|
|
377
|
+
tasks: ctx.state.plan.tasks.map(t => ({
|
|
378
|
+
id: t.id,
|
|
379
|
+
description: t.description,
|
|
380
|
+
status: t.status,
|
|
381
|
+
})),
|
|
382
|
+
currentTaskIndex: ctx.state.plan.tasks.findIndex(t => t.status === 'in_progress'),
|
|
383
|
+
} : undefined,
|
|
384
|
+
activeFiles: ctx.economics?.getProgress().filesModified
|
|
385
|
+
? [`${ctx.economics.getProgress().filesModified} files modified`]
|
|
386
|
+
: undefined,
|
|
387
|
+
recentErrors: ctx.contextEngineering.getFailureInsights().slice(0, 2),
|
|
388
|
+
});
|
|
389
|
+
if (process.env.DEBUG_LLM) {
|
|
390
|
+
if (process.env.DEBUG)
|
|
391
|
+
log.debug('Recitation after', { messageCount: enrichedMessages?.length ?? 'null/undefined' });
|
|
392
|
+
}
|
|
393
|
+
if (enrichedMessages && enrichedMessages !== messages && enrichedMessages.length > 0) {
|
|
394
|
+
messages.length = 0;
|
|
395
|
+
messages.push(...enrichedMessages);
|
|
396
|
+
}
|
|
397
|
+
else if (!enrichedMessages || enrichedMessages.length === 0) {
|
|
398
|
+
log.warn('Recitation returned empty/null messages, keeping original');
|
|
399
|
+
}
|
|
400
|
+
const contextTokens = messages.reduce((sum, m) => sum + (m.content?.length || 0) / 4, 0);
|
|
401
|
+
ctx.contextEngineering.updateRecitationFrequency(contextTokens);
|
|
402
|
+
}
|
|
403
|
+
// =======================================================================
|
|
404
|
+
// FAILURE CONTEXT INJECTION (Trick S)
|
|
405
|
+
// =======================================================================
|
|
406
|
+
if (ctx.contextEngineering) {
|
|
407
|
+
const failureContext = ctx.contextEngineering.getFailureContext(5);
|
|
408
|
+
if (failureContext) {
|
|
409
|
+
let lastUserIdx = -1;
|
|
410
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
411
|
+
if (messages[i].role === 'user') {
|
|
412
|
+
lastUserIdx = i;
|
|
413
|
+
break;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
if (lastUserIdx > 0) {
|
|
417
|
+
messages.splice(lastUserIdx, 0, {
|
|
418
|
+
role: 'system',
|
|
419
|
+
content: failureContext,
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
// =======================================================================
|
|
425
|
+
// RESUME ORIENTATION — after compaction, nudge the agent to act, not re-summarize
|
|
426
|
+
// =======================================================================
|
|
427
|
+
const hasCompactionSummary = messages.some(m => m.role === 'system' && typeof m.content === 'string' && m.content.includes('[Conversation Summary'));
|
|
428
|
+
if (hasCompactionSummary && ctx.state.iteration <= 2) {
|
|
429
|
+
messages.push({
|
|
430
|
+
role: 'user',
|
|
431
|
+
content: `[System] Context was compacted. Review the summary above for what's been done and what remains. Do NOT repeat the summary — start working on the next task immediately using your tools.`,
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
// =======================================================================
|
|
435
|
+
// INJECTION BUDGET ANALYSIS (Phase 2 - monitoring mode)
|
|
436
|
+
// =======================================================================
|
|
437
|
+
if (ctx.injectionBudget) {
|
|
438
|
+
const proposals = [];
|
|
439
|
+
if (budgetInjectedPrompt) {
|
|
440
|
+
proposals.push({ name: 'budget_warning', priority: 0, maxTokens: 500, content: budgetInjectedPrompt });
|
|
441
|
+
}
|
|
442
|
+
if (ctx.contextEngineering) {
|
|
443
|
+
const failureCtx = ctx.contextEngineering.getFailureContext(5);
|
|
444
|
+
if (failureCtx) {
|
|
445
|
+
proposals.push({ name: 'failure_context', priority: 2, maxTokens: 300, content: failureCtx });
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
if (proposals.length > 0) {
|
|
449
|
+
const accepted = ctx.injectionBudget.allocate(proposals);
|
|
450
|
+
const stats = ctx.injectionBudget.getLastStats();
|
|
451
|
+
if (stats && stats.droppedNames.length > 0 && process.env.DEBUG) {
|
|
452
|
+
log.debug('Injection budget dropped items', { droppedNames: stats.droppedNames.join(', '), proposedTokens: stats.proposedTokens, acceptedTokens: stats.acceptedTokens });
|
|
453
|
+
}
|
|
454
|
+
if (stats && process.env.DEBUG_LLM) {
|
|
455
|
+
log.debug('Injection budget summary', { iteration: ctx.state.iteration, accepted: accepted.length, total: proposals.length, tokens: stats.acceptedTokens });
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
// =======================================================================
|
|
460
|
+
// RESILIENT LLM CALL
|
|
461
|
+
// =======================================================================
|
|
462
|
+
const resilienceConfig = typeof ctx.config.resilience === 'object'
|
|
463
|
+
? ctx.config.resilience
|
|
464
|
+
: {};
|
|
465
|
+
const resilienceEnabled = isFeatureEnabled(ctx.config.resilience);
|
|
466
|
+
const MAX_EMPTY_RETRIES = resilienceConfig.maxEmptyRetries ?? 2;
|
|
467
|
+
const MAX_CONTINUATIONS = resilienceConfig.maxContinuations ?? 3;
|
|
468
|
+
const AUTO_CONTINUE = resilienceConfig.autoContinue ?? true;
|
|
469
|
+
const MIN_CONTENT_LENGTH = resilienceConfig.minContentLength ?? 1;
|
|
470
|
+
const INCOMPLETE_ACTION_RECOVERY = resilienceConfig.incompleteActionRecovery ?? true;
|
|
471
|
+
const MAX_INCOMPLETE_ACTION_RETRIES = resilienceConfig.maxIncompleteActionRetries ?? 2;
|
|
472
|
+
const ENFORCE_REQUESTED_ARTIFACTS = resilienceConfig.enforceRequestedArtifacts ?? true;
|
|
473
|
+
// PRE-FLIGHT BUDGET CHECK
|
|
474
|
+
if (ctx.economics && !forceTextOnly) {
|
|
475
|
+
const estimatedInputTokens = estimateContextTokens(messages);
|
|
476
|
+
const currentUsage = ctx.economics.getUsage();
|
|
477
|
+
const budget = ctx.economics.getBudget();
|
|
478
|
+
// Use proportional output estimate: 10% of remaining budget, capped at 4096, floored at 512.
|
|
479
|
+
// The old hardcoded 4096 caused premature wrapup for subagents with smaller budgets.
|
|
480
|
+
const remainingTokens = budget.maxTokens - currentUsage.tokens - estimatedInputTokens;
|
|
481
|
+
const estimatedOutputTokens = Math.min(4096, Math.max(512, Math.floor(remainingTokens * 0.1)));
|
|
482
|
+
const projectedTotal = currentUsage.tokens + estimatedInputTokens + estimatedOutputTokens;
|
|
483
|
+
if (projectedTotal > budget.maxTokens) {
|
|
484
|
+
ctx.observability?.logger?.warn('Pre-flight budget check: projected overshoot', {
|
|
485
|
+
currentTokens: currentUsage.tokens,
|
|
486
|
+
estimatedInput: estimatedInputTokens,
|
|
487
|
+
projectedTotal,
|
|
488
|
+
maxTokens: budget.maxTokens,
|
|
489
|
+
});
|
|
490
|
+
if (!budgetInjectedPrompt) {
|
|
491
|
+
messages.push({
|
|
492
|
+
role: 'user',
|
|
493
|
+
content: '[System] BUDGET CRITICAL: This is your LAST response. Summarize findings concisely and stop. Do NOT call tools.',
|
|
494
|
+
});
|
|
495
|
+
ctx.state.messages.push({
|
|
496
|
+
role: 'user',
|
|
497
|
+
content: '[System] BUDGET CRITICAL: This is your LAST response. Summarize findings concisely and stop. Do NOT call tools.',
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
forceTextOnly = true;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
let response = await callLLM(messages, ctx);
|
|
504
|
+
let emptyRetries = 0;
|
|
505
|
+
let continuations = 0;
|
|
506
|
+
// Phase 1: Handle empty responses with retry
|
|
507
|
+
while (resilienceEnabled && emptyRetries < MAX_EMPTY_RETRIES) {
|
|
508
|
+
const hasContent = response.content && response.content.length >= MIN_CONTENT_LENGTH;
|
|
509
|
+
const hasToolCalls = response.toolCalls && response.toolCalls.length > 0;
|
|
510
|
+
const hasThinking = response.thinking && response.thinking.length > 0;
|
|
511
|
+
if (hasContent || hasToolCalls) {
|
|
512
|
+
if (emptyRetries > 0) {
|
|
513
|
+
ctx.emit({
|
|
514
|
+
type: 'resilience.recovered',
|
|
515
|
+
reason: 'empty_response',
|
|
516
|
+
attempts: emptyRetries,
|
|
517
|
+
});
|
|
518
|
+
ctx.observability?.logger?.info('Recovered from empty response', {
|
|
519
|
+
retries: emptyRetries,
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
break;
|
|
523
|
+
}
|
|
524
|
+
if (hasThinking && !hasContent && !hasToolCalls) {
|
|
525
|
+
if (emptyRetries === 0) {
|
|
526
|
+
emptyRetries++;
|
|
527
|
+
ctx.emit({
|
|
528
|
+
type: 'resilience.retry',
|
|
529
|
+
reason: 'thinking_only_response',
|
|
530
|
+
attempt: emptyRetries,
|
|
531
|
+
maxAttempts: MAX_EMPTY_RETRIES,
|
|
532
|
+
});
|
|
533
|
+
ctx.state.metrics.retryCount = (ctx.state.metrics.retryCount ?? 0) + 1;
|
|
534
|
+
ctx.observability?.logger?.warn('Thinking-only response (no visible content), nudging', {
|
|
535
|
+
thinkingLength: response.thinking.length,
|
|
536
|
+
});
|
|
537
|
+
const thinkingNudge = {
|
|
538
|
+
role: 'user',
|
|
539
|
+
content: '[System: You produced reasoning but no visible response. Please provide your answer based on your analysis.]',
|
|
540
|
+
};
|
|
541
|
+
messages.push(thinkingNudge);
|
|
542
|
+
ctx.state.messages.push(thinkingNudge);
|
|
543
|
+
response = await callLLM(messages, ctx);
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
546
|
+
ctx.observability?.logger?.info('Accepting thinking as content after nudge failed', {
|
|
547
|
+
thinkingLength: response.thinking.length,
|
|
548
|
+
});
|
|
549
|
+
response = { ...response, content: response.thinking };
|
|
550
|
+
break;
|
|
551
|
+
}
|
|
552
|
+
emptyRetries++;
|
|
553
|
+
ctx.emit({
|
|
554
|
+
type: 'resilience.retry',
|
|
555
|
+
reason: 'empty_response',
|
|
556
|
+
attempt: emptyRetries,
|
|
557
|
+
maxAttempts: MAX_EMPTY_RETRIES,
|
|
558
|
+
});
|
|
559
|
+
ctx.state.metrics.retryCount = (ctx.state.metrics.retryCount ?? 0) + 1;
|
|
560
|
+
ctx.observability?.logger?.warn('Empty LLM response, retrying', {
|
|
561
|
+
attempt: emptyRetries,
|
|
562
|
+
maxAttempts: MAX_EMPTY_RETRIES,
|
|
563
|
+
});
|
|
564
|
+
const nudgeMessage = {
|
|
565
|
+
role: 'user',
|
|
566
|
+
content: '[System: Your previous response was empty. Please provide a response or use a tool.]',
|
|
567
|
+
};
|
|
568
|
+
messages.push(nudgeMessage);
|
|
569
|
+
ctx.state.messages.push(nudgeMessage);
|
|
570
|
+
response = await callLLM(messages, ctx);
|
|
571
|
+
}
|
|
572
|
+
// Phase 2: Handle max_tokens truncation with continuation
|
|
573
|
+
if (resilienceEnabled && AUTO_CONTINUE && response.stopReason === 'max_tokens' && !response.toolCalls?.length) {
|
|
574
|
+
let accumulatedContent = response.content || '';
|
|
575
|
+
while (continuations < MAX_CONTINUATIONS && response.stopReason === 'max_tokens') {
|
|
576
|
+
continuations++;
|
|
577
|
+
ctx.emit({
|
|
578
|
+
type: 'resilience.continue',
|
|
579
|
+
reason: 'max_tokens',
|
|
580
|
+
continuation: continuations,
|
|
581
|
+
maxContinuations: MAX_CONTINUATIONS,
|
|
582
|
+
accumulatedLength: accumulatedContent.length,
|
|
583
|
+
});
|
|
584
|
+
ctx.observability?.logger?.info('Response truncated at max_tokens, continuing', {
|
|
585
|
+
continuation: continuations,
|
|
586
|
+
accumulatedLength: accumulatedContent.length,
|
|
587
|
+
});
|
|
588
|
+
const continuationMessage = {
|
|
589
|
+
role: 'assistant',
|
|
590
|
+
content: accumulatedContent,
|
|
591
|
+
};
|
|
592
|
+
const continueRequest = {
|
|
593
|
+
role: 'user',
|
|
594
|
+
content: '[System: Please continue from where you left off. Do not repeat what you already said.]',
|
|
595
|
+
};
|
|
596
|
+
messages.push(continuationMessage, continueRequest);
|
|
597
|
+
ctx.state.messages.push(continuationMessage, continueRequest);
|
|
598
|
+
response = await callLLM(messages, ctx);
|
|
599
|
+
if (response.content) {
|
|
600
|
+
accumulatedContent += response.content;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
if (continuations > 0) {
|
|
604
|
+
response = { ...response, content: accumulatedContent };
|
|
605
|
+
ctx.emit({
|
|
606
|
+
type: 'resilience.completed',
|
|
607
|
+
reason: 'max_tokens_continuation',
|
|
608
|
+
continuations,
|
|
609
|
+
finalLength: accumulatedContent.length,
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
// Phase 2b: Handle truncated tool calls
|
|
614
|
+
if (resilienceEnabled && response.stopReason === 'max_tokens' && response.toolCalls?.length) {
|
|
615
|
+
ctx.emit({
|
|
616
|
+
type: 'resilience.truncated_tool_call',
|
|
617
|
+
toolNames: response.toolCalls.map(tc => tc.name),
|
|
618
|
+
});
|
|
619
|
+
ctx.observability?.logger?.warn('Tool call truncated at max_tokens', {
|
|
620
|
+
toolNames: response.toolCalls.map(tc => tc.name),
|
|
621
|
+
outputTokens: response.usage?.outputTokens,
|
|
622
|
+
});
|
|
623
|
+
const truncatedResponse = response;
|
|
624
|
+
response = { ...response, toolCalls: undefined };
|
|
625
|
+
const recoveryMessage = {
|
|
626
|
+
role: 'user',
|
|
627
|
+
content: '[System: Your previous tool call was truncated because the output exceeded the token limit. ' +
|
|
628
|
+
'The tool call arguments were cut off and could not be parsed. ' +
|
|
629
|
+
'Please retry with a smaller approach: for write_file, break the content into smaller chunks ' +
|
|
630
|
+
'or use edit_file for targeted changes instead of rewriting entire files.]',
|
|
631
|
+
};
|
|
632
|
+
messages.push({ role: 'assistant', content: truncatedResponse.content || '' });
|
|
633
|
+
messages.push(recoveryMessage);
|
|
634
|
+
ctx.state.messages.push({ role: 'assistant', content: truncatedResponse.content || '' });
|
|
635
|
+
ctx.state.messages.push(recoveryMessage);
|
|
636
|
+
response = await callLLM(messages, ctx);
|
|
637
|
+
}
|
|
638
|
+
// Record LLM usage for economics
|
|
639
|
+
if (ctx.economics && response.usage) {
|
|
640
|
+
ctx.economics.recordLLMUsage(response.usage.inputTokens, response.usage.outputTokens, ctx.config.model, response.usage.cost);
|
|
641
|
+
// POST-LLM BUDGET CHECK
|
|
642
|
+
if (!forceTextOnly) {
|
|
643
|
+
const postCheck = ctx.economics.checkBudget();
|
|
644
|
+
if (!postCheck.canContinue) {
|
|
645
|
+
ctx.observability?.logger?.warn('Budget exceeded after LLM call, skipping tool execution', {
|
|
646
|
+
reason: postCheck.reason,
|
|
647
|
+
});
|
|
648
|
+
forceTextOnly = true;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
// Add assistant message
|
|
653
|
+
const assistantMessage = {
|
|
654
|
+
role: 'assistant',
|
|
655
|
+
content: response.content,
|
|
656
|
+
toolCalls: response.toolCalls,
|
|
657
|
+
...(response.thinking ? { metadata: { thinking: response.thinking } } : {}),
|
|
658
|
+
};
|
|
659
|
+
messages.push(assistantMessage);
|
|
660
|
+
ctx.state.messages.push(assistantMessage);
|
|
661
|
+
lastResponse = response.content || (response.thinking ? response.thinking : '');
|
|
662
|
+
// Plan mode: capture exploration findings
|
|
663
|
+
if (ctx.modeManager.getMode() === 'plan' && response.content && response.content.length > 50) {
|
|
664
|
+
const hasReadOnlyTools = response.toolCalls?.every(tc => ['read_file', 'list_files', 'glob', 'grep', 'search', 'mcp_'].some(prefix => tc.name.startsWith(prefix) || tc.name === prefix));
|
|
665
|
+
if (hasReadOnlyTools && !response.content.match(/^(Let me|I'll|I will|I need to|First,)/i)) {
|
|
666
|
+
ctx.pendingPlanManager.appendExplorationFinding(response.content.slice(0, 1000));
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
// Check for tool calls
|
|
670
|
+
const hasToolCalls = response.toolCalls && response.toolCalls.length > 0;
|
|
671
|
+
if (!hasToolCalls || forceTextOnly) {
|
|
672
|
+
if (forceTextOnly && hasToolCalls) {
|
|
673
|
+
ctx.observability?.logger?.info('Ignoring tool calls due to forceTextOnly (max steps reached)', {
|
|
674
|
+
toolCallCount: response.toolCalls?.length,
|
|
675
|
+
iteration: ctx.state.iteration,
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
// Track text-only turns for summary-loop detection (skip forceTextOnly — that's expected)
|
|
679
|
+
if (!hasToolCalls && !forceTextOnly) {
|
|
680
|
+
ctx.economics?.recordTextOnlyTurn();
|
|
681
|
+
}
|
|
682
|
+
const incompleteAction = detectIncompleteActionResponse(response.content || '');
|
|
683
|
+
const missingRequiredArtifact = ENFORCE_REQUESTED_ARTIFACTS
|
|
684
|
+
? isRequestedArtifactMissing(requestedArtifact, executedToolNames)
|
|
685
|
+
: false;
|
|
686
|
+
const shouldRecoverIncompleteAction = resilienceEnabled
|
|
687
|
+
&& INCOMPLETE_ACTION_RECOVERY
|
|
688
|
+
&& !forceTextOnly
|
|
689
|
+
&& (incompleteAction || missingRequiredArtifact);
|
|
690
|
+
if (shouldRecoverIncompleteAction) {
|
|
691
|
+
ctx.emit({
|
|
692
|
+
type: 'completion.before',
|
|
693
|
+
reason: missingRequiredArtifact && requestedArtifact
|
|
694
|
+
? `missing_requested_artifact:${requestedArtifact}`
|
|
695
|
+
: 'future_intent_without_action',
|
|
696
|
+
attempt: incompleteActionRetries + 1,
|
|
697
|
+
maxAttempts: MAX_INCOMPLETE_ACTION_RETRIES,
|
|
698
|
+
});
|
|
699
|
+
if (incompleteActionRetries < MAX_INCOMPLETE_ACTION_RETRIES) {
|
|
700
|
+
incompleteActionRetries++;
|
|
701
|
+
const reason = missingRequiredArtifact && requestedArtifact
|
|
702
|
+
? `missing_requested_artifact:${requestedArtifact}`
|
|
703
|
+
: 'future_intent_without_action';
|
|
704
|
+
ctx.emit({
|
|
705
|
+
type: 'recovery.before',
|
|
706
|
+
reason,
|
|
707
|
+
attempt: incompleteActionRetries,
|
|
708
|
+
maxAttempts: MAX_INCOMPLETE_ACTION_RETRIES,
|
|
709
|
+
});
|
|
710
|
+
ctx.emit({
|
|
711
|
+
type: 'resilience.incomplete_action_detected',
|
|
712
|
+
reason,
|
|
713
|
+
attempt: incompleteActionRetries,
|
|
714
|
+
maxAttempts: MAX_INCOMPLETE_ACTION_RETRIES,
|
|
715
|
+
requiresArtifact: missingRequiredArtifact,
|
|
716
|
+
});
|
|
717
|
+
ctx.observability?.logger?.warn('Incomplete action detected, retrying with nudge', {
|
|
718
|
+
reason,
|
|
719
|
+
attempt: incompleteActionRetries,
|
|
720
|
+
maxAttempts: MAX_INCOMPLETE_ACTION_RETRIES,
|
|
721
|
+
});
|
|
722
|
+
const nudgeMessage = {
|
|
723
|
+
role: 'user',
|
|
724
|
+
content: missingRequiredArtifact && requestedArtifact
|
|
725
|
+
? `[System: You said you would complete the next action, but no tool call was made. The task requires creating or updating "${requestedArtifact}". Execute the required tool now, or explicitly explain why it cannot be produced.]`
|
|
726
|
+
: '[System: You described a next action but did not execute it. If work remains, call the required tool now. If the task is complete, provide a final answer with no pending action language.]',
|
|
727
|
+
};
|
|
728
|
+
messages.push(nudgeMessage);
|
|
729
|
+
ctx.state.messages.push(nudgeMessage);
|
|
730
|
+
ctx.emit({
|
|
731
|
+
type: 'iteration.after',
|
|
732
|
+
iteration: ctx.state.iteration,
|
|
733
|
+
hadToolCalls: false,
|
|
734
|
+
completionCandidate: false,
|
|
735
|
+
});
|
|
736
|
+
continue;
|
|
737
|
+
}
|
|
738
|
+
const failureReason = missingRequiredArtifact && requestedArtifact
|
|
739
|
+
? `incomplete_action_missing_artifact:${requestedArtifact}`
|
|
740
|
+
: 'incomplete_action_unresolved';
|
|
741
|
+
ctx.emit({
|
|
742
|
+
type: 'resilience.incomplete_action_failed',
|
|
743
|
+
reason: failureReason,
|
|
744
|
+
attempts: incompleteActionRetries,
|
|
745
|
+
maxAttempts: MAX_INCOMPLETE_ACTION_RETRIES,
|
|
746
|
+
});
|
|
747
|
+
ctx.emit({
|
|
748
|
+
type: 'recovery.after',
|
|
749
|
+
reason: failureReason,
|
|
750
|
+
recovered: false,
|
|
751
|
+
attempts: incompleteActionRetries,
|
|
752
|
+
});
|
|
753
|
+
const reason = `LLM failed to complete requested action after ${incompleteActionRetries} retries (${failureReason})`;
|
|
754
|
+
result = {
|
|
755
|
+
success: false,
|
|
756
|
+
terminationReason: 'incomplete_action',
|
|
757
|
+
failureReason: reason,
|
|
758
|
+
};
|
|
759
|
+
ctx.emit({
|
|
760
|
+
type: 'completion.after',
|
|
761
|
+
success: false,
|
|
762
|
+
reason: 'incomplete_action',
|
|
763
|
+
details: reason,
|
|
764
|
+
});
|
|
765
|
+
throw new Error(reason);
|
|
766
|
+
}
|
|
767
|
+
if (incompleteActionRetries > 0) {
|
|
768
|
+
ctx.emit({
|
|
769
|
+
type: 'resilience.incomplete_action_recovered',
|
|
770
|
+
reason: 'incomplete_action',
|
|
771
|
+
attempts: incompleteActionRetries,
|
|
772
|
+
});
|
|
773
|
+
ctx.emit({
|
|
774
|
+
type: 'recovery.after',
|
|
775
|
+
reason: 'incomplete_action',
|
|
776
|
+
recovered: true,
|
|
777
|
+
attempts: incompleteActionRetries,
|
|
778
|
+
});
|
|
779
|
+
incompleteActionRetries = 0;
|
|
780
|
+
}
|
|
781
|
+
// Verification gate
|
|
782
|
+
if (ctx.verificationGate && !forceTextOnly) {
|
|
783
|
+
const vResult = ctx.verificationGate.check();
|
|
784
|
+
if (!vResult.satisfied && !vResult.forceAllow && vResult.nudge) {
|
|
785
|
+
const nudgeMessage = {
|
|
786
|
+
role: 'user',
|
|
787
|
+
content: vResult.nudge,
|
|
788
|
+
};
|
|
789
|
+
messages.push(nudgeMessage);
|
|
790
|
+
ctx.state.messages.push(nudgeMessage);
|
|
791
|
+
ctx.observability?.logger?.info('Verification gate nudge', {
|
|
792
|
+
missing: vResult.missing,
|
|
793
|
+
nudgeCount: ctx.verificationGate.getState().nudgeCount,
|
|
794
|
+
});
|
|
795
|
+
ctx.emit({
|
|
796
|
+
type: 'iteration.after',
|
|
797
|
+
iteration: ctx.state.iteration,
|
|
798
|
+
hadToolCalls: false,
|
|
799
|
+
completionCandidate: false,
|
|
800
|
+
});
|
|
801
|
+
continue;
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
// No tool calls — agent is done
|
|
805
|
+
compactToolOutputs(ctx.state.messages);
|
|
806
|
+
// Plan mode: capture exploration summary
|
|
807
|
+
if (ctx.modeManager.getMode() === 'plan' && ctx.pendingPlanManager.hasPendingPlan()) {
|
|
808
|
+
const explorationContent = response.content || '';
|
|
809
|
+
if (explorationContent.length > 0) {
|
|
810
|
+
ctx.pendingPlanManager.setExplorationSummary(explorationContent);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
// Final validation
|
|
814
|
+
if (!response.content || response.content.length === 0) {
|
|
815
|
+
ctx.observability?.logger?.error('Agent finished with empty response after all retries', {
|
|
816
|
+
emptyRetries,
|
|
817
|
+
continuations,
|
|
818
|
+
iteration: ctx.state.iteration,
|
|
819
|
+
});
|
|
820
|
+
ctx.emit({
|
|
821
|
+
type: 'resilience.failed',
|
|
822
|
+
reason: 'empty_final_response',
|
|
823
|
+
emptyRetries,
|
|
824
|
+
continuations,
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
ctx.emit({
|
|
828
|
+
type: 'completion.after',
|
|
829
|
+
success: true,
|
|
830
|
+
reason: 'completed',
|
|
831
|
+
});
|
|
832
|
+
ctx.emit({
|
|
833
|
+
type: 'iteration.after',
|
|
834
|
+
iteration: ctx.state.iteration,
|
|
835
|
+
hadToolCalls: false,
|
|
836
|
+
completionCandidate: true,
|
|
837
|
+
});
|
|
838
|
+
// Record iteration end for tracing (no tool calls case)
|
|
839
|
+
ctx.traceCollector?.record({
|
|
840
|
+
type: 'iteration.end',
|
|
841
|
+
data: { iterationNumber: ctx.state.iteration },
|
|
842
|
+
});
|
|
843
|
+
// =====================================================================
|
|
844
|
+
// TASK EXECUTION LOOP — pick up next available task before exiting
|
|
845
|
+
// =====================================================================
|
|
846
|
+
if (ctx.taskManager) {
|
|
847
|
+
// Reconcile stale in-progress tasks before deciding there is no more work.
|
|
848
|
+
ctx.taskManager.reconcileStaleInProgress({
|
|
849
|
+
staleAfterMs: taskLeaseStaleMs,
|
|
850
|
+
reason: 'completion_gate',
|
|
851
|
+
});
|
|
852
|
+
const pendingWithOwner = getPendingWithOwnerCount(ctx);
|
|
853
|
+
const availableTasks = ctx.taskManager.getAvailableTasks();
|
|
854
|
+
if (!forceTextOnly && availableTasks.length > 0) {
|
|
855
|
+
const nextTask = availableTasks[0];
|
|
856
|
+
ctx.taskManager.claim(nextTask.id, ctx.agentId);
|
|
857
|
+
log.info('Picking up next task from task list', {
|
|
858
|
+
taskId: nextTask.id,
|
|
859
|
+
subject: nextTask.subject,
|
|
860
|
+
});
|
|
861
|
+
const taskPrompt = {
|
|
862
|
+
role: 'user',
|
|
863
|
+
content: `[System] Previous work is done. Now work on the next task:\n\n**Task ${nextTask.id}: ${nextTask.subject}**\n${nextTask.description}\n\nStart working on this task now using your tools.`,
|
|
864
|
+
};
|
|
865
|
+
messages.push(taskPrompt);
|
|
866
|
+
ctx.state.messages.push(taskPrompt);
|
|
867
|
+
ctx.emit({
|
|
868
|
+
type: 'iteration.after',
|
|
869
|
+
iteration: ctx.state.iteration,
|
|
870
|
+
hadToolCalls: false,
|
|
871
|
+
completionCandidate: false,
|
|
872
|
+
});
|
|
873
|
+
continue; // Re-enter the main loop for the next task
|
|
874
|
+
}
|
|
875
|
+
const openTasks = getOpenTaskSummary(ctx);
|
|
876
|
+
if (openTasks && (openTasks.inProgress > 0 || openTasks.pending > 0)) {
|
|
877
|
+
if (forceTextOnly) {
|
|
878
|
+
const reason = `Task continuation suppressed by forceTextOnly mode: ${openTasks.pending} pending, ${openTasks.inProgress} in_progress`;
|
|
879
|
+
ctx.emit({
|
|
880
|
+
type: 'completion.blocked',
|
|
881
|
+
reasons: [reason],
|
|
882
|
+
openTasks,
|
|
883
|
+
diagnostics: {
|
|
884
|
+
forceTextOnly: true,
|
|
885
|
+
availableTasks: availableTasks.length,
|
|
886
|
+
pendingWithOwner,
|
|
887
|
+
},
|
|
888
|
+
});
|
|
889
|
+
result = {
|
|
890
|
+
success: false,
|
|
891
|
+
terminationReason: 'budget_limit',
|
|
892
|
+
failureReason: reason,
|
|
893
|
+
openTasks,
|
|
894
|
+
};
|
|
895
|
+
break;
|
|
896
|
+
}
|
|
897
|
+
const reasons = [
|
|
898
|
+
`Open tasks remain: ${openTasks.pending} pending, ${openTasks.inProgress} in_progress`,
|
|
899
|
+
openTasks.blocked > 0 ? `${openTasks.blocked} pending tasks are currently blocked` : '',
|
|
900
|
+
].filter(Boolean);
|
|
901
|
+
ctx.emit({
|
|
902
|
+
type: 'completion.blocked',
|
|
903
|
+
reasons,
|
|
904
|
+
openTasks,
|
|
905
|
+
diagnostics: {
|
|
906
|
+
forceTextOnly: false,
|
|
907
|
+
availableTasks: availableTasks.length,
|
|
908
|
+
pendingWithOwner,
|
|
909
|
+
},
|
|
910
|
+
});
|
|
911
|
+
result = {
|
|
912
|
+
success: false,
|
|
913
|
+
terminationReason: 'open_tasks',
|
|
914
|
+
failureReason: reasons.join('; '),
|
|
915
|
+
openTasks,
|
|
916
|
+
};
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
break;
|
|
920
|
+
}
|
|
921
|
+
// Execute tool calls
|
|
922
|
+
const toolCalls = response.toolCalls;
|
|
923
|
+
// SAFEGUARD: Hard cap on tool calls per LLM response
|
|
924
|
+
const maxToolCallsPerResponse = ctx.economics?.getBudget()?.tuning?.maxToolCallsPerResponse ?? 25;
|
|
925
|
+
if (toolCalls.length > maxToolCallsPerResponse) {
|
|
926
|
+
log.warn('Tool call explosion detected — capping', {
|
|
927
|
+
requested: toolCalls.length,
|
|
928
|
+
cap: maxToolCallsPerResponse,
|
|
929
|
+
toolNames: [...new Set(toolCalls.map(tc => tc.name))],
|
|
930
|
+
});
|
|
931
|
+
ctx.emit({
|
|
932
|
+
type: 'safeguard.tool_call_cap',
|
|
933
|
+
requested: toolCalls.length,
|
|
934
|
+
cap: maxToolCallsPerResponse,
|
|
935
|
+
droppedCount: toolCalls.length - maxToolCallsPerResponse,
|
|
936
|
+
});
|
|
937
|
+
toolCalls.splice(maxToolCallsPerResponse);
|
|
938
|
+
}
|
|
939
|
+
const toolResults = await executeToolCalls(toolCalls, ctx);
|
|
940
|
+
ctx.emit({
|
|
941
|
+
type: 'iteration.after',
|
|
942
|
+
iteration: ctx.state.iteration,
|
|
943
|
+
hadToolCalls: true,
|
|
944
|
+
completionCandidate: false,
|
|
945
|
+
});
|
|
946
|
+
// Record tool calls for economics/progress tracking + work log
|
|
947
|
+
for (let i = 0; i < toolCalls.length; i++) {
|
|
948
|
+
const toolCall = toolCalls[i];
|
|
949
|
+
const result = toolResults[i];
|
|
950
|
+
executedToolNames.add(toolCall.name);
|
|
951
|
+
ctx.economics?.recordToolCall(toolCall.name, toolCall.arguments, result?.result);
|
|
952
|
+
ctx.stateMachine?.recordToolCall(toolCall.name, toolCall.arguments, result?.result);
|
|
953
|
+
// Record in work log
|
|
954
|
+
const toolOutput = result?.result && typeof result.result === 'object' && 'output' in result.result
|
|
955
|
+
? String(result.result.output)
|
|
956
|
+
: typeof result?.result === 'string' ? result.result : undefined;
|
|
957
|
+
ctx.workLog?.recordToolExecution(toolCall.name, toolCall.arguments, toolOutput);
|
|
958
|
+
// Record in verification gate
|
|
959
|
+
if (ctx.verificationGate) {
|
|
960
|
+
if (toolCall.name === 'bash') {
|
|
961
|
+
const toolRes = result?.result;
|
|
962
|
+
const output = toolRes && typeof toolRes === 'object' && 'output' in toolRes
|
|
963
|
+
? String(toolRes.output)
|
|
964
|
+
: typeof toolRes === 'string' ? toolRes : '';
|
|
965
|
+
const exitCode = toolRes && typeof toolRes === 'object' && toolRes.metadata
|
|
966
|
+
? toolRes.metadata.exitCode ?? null
|
|
967
|
+
: null;
|
|
968
|
+
ctx.verificationGate.recordBashExecution(String(toolCall.arguments.command || ''), output, exitCode);
|
|
969
|
+
}
|
|
970
|
+
if (['write_file', 'edit_file'].includes(toolCall.name)) {
|
|
971
|
+
ctx.verificationGate.recordFileChange();
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
// Phase 5.1: Post-edit syntax validation
|
|
975
|
+
if (['write_file', 'edit_file'].includes(toolCall.name) && result?.result && result.result.success) {
|
|
976
|
+
const filePath = String(toolCall.arguments.path || '');
|
|
977
|
+
if (filePath) {
|
|
978
|
+
try {
|
|
979
|
+
const content = toolCall.name === 'write_file'
|
|
980
|
+
? String(toolCall.arguments.content || '')
|
|
981
|
+
: await fs.promises.readFile(filePath, 'utf-8');
|
|
982
|
+
const validation = validateSyntax(content, filePath);
|
|
983
|
+
if (!validation.valid && result.result && typeof result.result === 'object') {
|
|
984
|
+
const errorSummary = validation.errors
|
|
985
|
+
.slice(0, 3)
|
|
986
|
+
.map(e => ` L${e.line}:${e.column}: ${e.message}`)
|
|
987
|
+
.join('\n');
|
|
988
|
+
result.result.output += `\n\n⚠ Syntax validation warning:\n${errorSummary}`;
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
catch {
|
|
992
|
+
// Validation failure is non-blocking
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
// Add tool results to messages (with truncation and proactive budget management)
|
|
998
|
+
const MAX_TOOL_OUTPUT_CHARS = 8000;
|
|
999
|
+
// PROACTIVE BUDGET CHECK
|
|
1000
|
+
const currentContextTokens = estimateContextTokens(messages);
|
|
1001
|
+
if (ctx.autoCompactionManager) {
|
|
1002
|
+
const compactionResult = await ctx.autoCompactionManager.checkAndMaybeCompact({
|
|
1003
|
+
currentTokens: currentContextTokens,
|
|
1004
|
+
messages: messages,
|
|
1005
|
+
});
|
|
1006
|
+
if (compactionResult.status === 'compacted' && compactionResult.compactedMessages) {
|
|
1007
|
+
if (!ctx.compactionPending) {
|
|
1008
|
+
mutators.setCompactionPending(true);
|
|
1009
|
+
const preCompactionMsg = {
|
|
1010
|
+
role: 'user',
|
|
1011
|
+
content: '[SYSTEM] Context compaction is imminent. Summarize your current progress, key findings, and next steps into a single concise message. This will be preserved after compaction.',
|
|
1012
|
+
};
|
|
1013
|
+
messages.push(preCompactionMsg);
|
|
1014
|
+
ctx.state.messages.push(preCompactionMsg);
|
|
1015
|
+
ctx.observability?.logger?.info('Pre-compaction agentic turn: injected summary request');
|
|
1016
|
+
}
|
|
1017
|
+
else {
|
|
1018
|
+
mutators.setCompactionPending(false);
|
|
1019
|
+
// Pre-compaction checkpoint
|
|
1020
|
+
// NOTE: autoCheckpoint is called via the agent's method, not directly here
|
|
1021
|
+
// The agent wires this through the mutators pattern
|
|
1022
|
+
// Replace messages with compacted version
|
|
1023
|
+
messages.length = 0;
|
|
1024
|
+
messages.push(...compactionResult.compactedMessages);
|
|
1025
|
+
ctx.state.messages.length = 0;
|
|
1026
|
+
ctx.state.messages.push(...compactionResult.compactedMessages);
|
|
1027
|
+
// Inject work log after compaction
|
|
1028
|
+
if (ctx.workLog?.hasContent()) {
|
|
1029
|
+
const workLogMessage = {
|
|
1030
|
+
role: 'user',
|
|
1031
|
+
content: ctx.workLog.toCompactString(),
|
|
1032
|
+
};
|
|
1033
|
+
messages.push(workLogMessage);
|
|
1034
|
+
ctx.state.messages.push(workLogMessage);
|
|
1035
|
+
}
|
|
1036
|
+
// Context recovery
|
|
1037
|
+
const recoveryParts = [];
|
|
1038
|
+
if (ctx.store) {
|
|
1039
|
+
const goalsSummary = ctx.store.getGoalsSummary();
|
|
1040
|
+
if (goalsSummary && goalsSummary !== 'No active goals.' && goalsSummary !== 'Goals feature not available.') {
|
|
1041
|
+
recoveryParts.push(goalsSummary);
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
if (ctx.store) {
|
|
1045
|
+
const juncturesSummary = ctx.store.getJuncturesSummary(undefined, 5);
|
|
1046
|
+
if (juncturesSummary) {
|
|
1047
|
+
recoveryParts.push(juncturesSummary);
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
if (ctx.learningStore) {
|
|
1051
|
+
const learnings = ctx.learningStore.getLearningContext({ maxLearnings: 3 });
|
|
1052
|
+
if (learnings) {
|
|
1053
|
+
recoveryParts.push(learnings);
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
if (recoveryParts.length > 0) {
|
|
1057
|
+
const recoveryMessage = {
|
|
1058
|
+
role: 'user',
|
|
1059
|
+
content: `[CONTEXT RECOVERY — Re-injected after compaction]\n\n${recoveryParts.join('\n\n')}`,
|
|
1060
|
+
};
|
|
1061
|
+
messages.push(recoveryMessage);
|
|
1062
|
+
ctx.state.messages.push(recoveryMessage);
|
|
1063
|
+
}
|
|
1064
|
+
// Emit compaction event
|
|
1065
|
+
const compactionTokensAfter = estimateContextTokens(messages);
|
|
1066
|
+
const compactionRecoveryInjected = recoveryParts.length > 0;
|
|
1067
|
+
const compactionEvent = {
|
|
1068
|
+
type: 'context.compacted',
|
|
1069
|
+
tokensBefore: currentContextTokens,
|
|
1070
|
+
tokensAfter: compactionTokensAfter,
|
|
1071
|
+
recoveryInjected: compactionRecoveryInjected,
|
|
1072
|
+
};
|
|
1073
|
+
ctx.emit(compactionEvent);
|
|
1074
|
+
if (ctx.traceCollector) {
|
|
1075
|
+
ctx.traceCollector.record({
|
|
1076
|
+
type: 'context.compacted',
|
|
1077
|
+
data: {
|
|
1078
|
+
tokensBefore: currentContextTokens,
|
|
1079
|
+
tokensAfter: compactionTokensAfter,
|
|
1080
|
+
recoveryInjected: compactionRecoveryInjected,
|
|
1081
|
+
},
|
|
1082
|
+
});
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
else if (compactionResult.status === 'hard_limit') {
|
|
1087
|
+
const reason = `Context hard limit reached (${Math.round(compactionResult.ratio * 100)}% of max tokens)`;
|
|
1088
|
+
ctx.emit({ type: 'error', error: reason });
|
|
1089
|
+
result = {
|
|
1090
|
+
success: false,
|
|
1091
|
+
terminationReason: 'hard_context_limit',
|
|
1092
|
+
failureReason: reason,
|
|
1093
|
+
};
|
|
1094
|
+
break;
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
else if (ctx.economics) {
|
|
1098
|
+
// Fallback to simple compaction
|
|
1099
|
+
const currentUsage = ctx.economics.getUsage();
|
|
1100
|
+
const budget = ctx.economics.getBudget();
|
|
1101
|
+
const percentUsed = (currentUsage.tokens / budget.maxTokens) * 100;
|
|
1102
|
+
if (percentUsed >= 70) {
|
|
1103
|
+
ctx.observability?.logger?.info('Proactive compaction triggered', {
|
|
1104
|
+
percentUsed: Math.round(percentUsed),
|
|
1105
|
+
currentTokens: currentUsage.tokens,
|
|
1106
|
+
maxTokens: budget.maxTokens,
|
|
1107
|
+
});
|
|
1108
|
+
compactToolOutputs(ctx.state.messages);
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
// SAFEGUARD: Aggregate context guard — prevent mass small results from overflowing
|
|
1112
|
+
if (ctx.economics && toolResults.length > 10) {
|
|
1113
|
+
const preAccumTokens = estimateContextTokens(messages);
|
|
1114
|
+
const budget = ctx.economics.getBudget();
|
|
1115
|
+
const availableTokens = budget.maxTokens * 0.90 - preAccumTokens;
|
|
1116
|
+
// Estimate total result tokens
|
|
1117
|
+
let totalResultTokens = 0;
|
|
1118
|
+
for (const r of toolResults) {
|
|
1119
|
+
const c = typeof r.result === 'string' ? r.result : stableStringify(r.result);
|
|
1120
|
+
totalResultTokens += Math.ceil(Math.min(c.length, MAX_TOOL_OUTPUT_CHARS) / 4);
|
|
1121
|
+
}
|
|
1122
|
+
if (totalResultTokens > availableTokens && availableTokens > 0) {
|
|
1123
|
+
log.warn('Tool results would exceed context budget — truncating batch', {
|
|
1124
|
+
resultCount: toolResults.length,
|
|
1125
|
+
estimatedTokens: totalResultTokens,
|
|
1126
|
+
availableTokens: Math.round(availableTokens),
|
|
1127
|
+
});
|
|
1128
|
+
let tokenBudget = availableTokens;
|
|
1129
|
+
for (let i = 0; i < toolResults.length; i++) {
|
|
1130
|
+
const c = typeof toolResults[i].result === 'string'
|
|
1131
|
+
? toolResults[i].result
|
|
1132
|
+
: stableStringify(toolResults[i].result);
|
|
1133
|
+
const tokens = Math.ceil(Math.min(c.length, MAX_TOOL_OUTPUT_CHARS) / 4);
|
|
1134
|
+
if (tokens > tokenBudget) {
|
|
1135
|
+
const skipped = toolResults.length - i;
|
|
1136
|
+
for (let j = i; j < toolResults.length; j++) {
|
|
1137
|
+
toolResults[j] = {
|
|
1138
|
+
callId: toolResults[j].callId,
|
|
1139
|
+
result: `[Result omitted: context overflow guard — ${skipped} of ${toolResults.length} results skipped]`,
|
|
1140
|
+
};
|
|
1141
|
+
}
|
|
1142
|
+
ctx.emit({
|
|
1143
|
+
type: 'safeguard.context_overflow_guard',
|
|
1144
|
+
estimatedTokens: totalResultTokens,
|
|
1145
|
+
maxTokens: budget.maxTokens,
|
|
1146
|
+
toolResultsSkipped: skipped,
|
|
1147
|
+
});
|
|
1148
|
+
break;
|
|
1149
|
+
}
|
|
1150
|
+
tokenBudget -= tokens;
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
const toolCallNameById = new Map(toolCalls.map(tc => [tc.id, tc.name]));
|
|
1155
|
+
for (const result of toolResults) {
|
|
1156
|
+
let content = typeof result.result === 'string' ? result.result : stableStringify(result.result);
|
|
1157
|
+
const sourceToolName = toolCallNameById.get(result.callId);
|
|
1158
|
+
const isExpensiveResult = sourceToolName === 'spawn_agent' || sourceToolName === 'spawn_agents_parallel';
|
|
1159
|
+
const effectiveMaxChars = isExpensiveResult ? MAX_TOOL_OUTPUT_CHARS * 2 : MAX_TOOL_OUTPUT_CHARS;
|
|
1160
|
+
if (content.length > effectiveMaxChars) {
|
|
1161
|
+
content = content.slice(0, effectiveMaxChars) + `\n\n... [truncated ${content.length - effectiveMaxChars} chars]`;
|
|
1162
|
+
}
|
|
1163
|
+
// Check if adding this result would exceed budget
|
|
1164
|
+
if (ctx.economics) {
|
|
1165
|
+
const estimatedNewTokens = Math.ceil(content.length / 4);
|
|
1166
|
+
const currentCtxTokens = estimateContextTokens(messages);
|
|
1167
|
+
const budget = ctx.economics.getBudget();
|
|
1168
|
+
if (currentCtxTokens + estimatedNewTokens > budget.maxTokens * 0.95) {
|
|
1169
|
+
ctx.observability?.logger?.warn('Skipping tool result to stay within budget', {
|
|
1170
|
+
toolCallId: result.callId,
|
|
1171
|
+
estimatedTokens: estimatedNewTokens,
|
|
1172
|
+
currentContext: currentCtxTokens,
|
|
1173
|
+
limit: budget.maxTokens,
|
|
1174
|
+
});
|
|
1175
|
+
const toolMessage = {
|
|
1176
|
+
role: 'tool',
|
|
1177
|
+
content: `[Result omitted to stay within token budget. Original size: ${content.length} chars]`,
|
|
1178
|
+
toolCallId: result.callId,
|
|
1179
|
+
};
|
|
1180
|
+
messages.push(toolMessage);
|
|
1181
|
+
ctx.state.messages.push(toolMessage);
|
|
1182
|
+
continue;
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
const toolMessage = {
|
|
1186
|
+
role: 'tool',
|
|
1187
|
+
content,
|
|
1188
|
+
toolCallId: result.callId,
|
|
1189
|
+
...(isExpensiveResult
|
|
1190
|
+
? {
|
|
1191
|
+
metadata: {
|
|
1192
|
+
preserveFromCompaction: true,
|
|
1193
|
+
costToRegenerate: 'high',
|
|
1194
|
+
source: sourceToolName,
|
|
1195
|
+
},
|
|
1196
|
+
}
|
|
1197
|
+
: {}),
|
|
1198
|
+
};
|
|
1199
|
+
messages.push(toolMessage);
|
|
1200
|
+
ctx.state.messages.push(toolMessage);
|
|
1201
|
+
}
|
|
1202
|
+
// Emit context health
|
|
1203
|
+
const currentTokenEstimate = estimateContextTokens(messages);
|
|
1204
|
+
const contextLimit = ctx.getMaxContextTokens();
|
|
1205
|
+
const percentUsed = Math.round((currentTokenEstimate / contextLimit) * 100);
|
|
1206
|
+
const avgTokensPerExchange = currentTokenEstimate / Math.max(1, ctx.state.iteration);
|
|
1207
|
+
const remainingTokens = contextLimit - currentTokenEstimate;
|
|
1208
|
+
const estimatedExchanges = Math.floor(remainingTokens / Math.max(1, avgTokensPerExchange));
|
|
1209
|
+
ctx.emit({
|
|
1210
|
+
type: 'context.health',
|
|
1211
|
+
currentTokens: currentTokenEstimate,
|
|
1212
|
+
maxTokens: contextLimit,
|
|
1213
|
+
estimatedExchanges,
|
|
1214
|
+
percentUsed,
|
|
1215
|
+
});
|
|
1216
|
+
// Record iteration end
|
|
1217
|
+
ctx.traceCollector?.record({
|
|
1218
|
+
type: 'iteration.end',
|
|
1219
|
+
data: { iterationNumber: ctx.state.iteration },
|
|
1220
|
+
});
|
|
1221
|
+
}
|
|
1222
|
+
// =======================================================================
|
|
1223
|
+
// REFLECTION (Lesson 16)
|
|
1224
|
+
// =======================================================================
|
|
1225
|
+
if (!result.success) {
|
|
1226
|
+
break;
|
|
1227
|
+
}
|
|
1228
|
+
if (autoReflect && ctx.planning && reflectionAttempt < maxReflectionAttempts) {
|
|
1229
|
+
ctx.emit({ type: 'reflection', attempt: reflectionAttempt, satisfied: false });
|
|
1230
|
+
const reflectionResult = await ctx.planning.reflect(task, lastResponse, ctx.provider);
|
|
1231
|
+
ctx.state.metrics.reflectionAttempts = reflectionAttempt;
|
|
1232
|
+
if (reflectionResult.satisfied && reflectionResult.confidence >= confidenceThreshold) {
|
|
1233
|
+
ctx.emit({ type: 'reflection', attempt: reflectionAttempt, satisfied: true });
|
|
1234
|
+
break;
|
|
1235
|
+
}
|
|
1236
|
+
const feedbackMessage = {
|
|
1237
|
+
role: 'user',
|
|
1238
|
+
content: `[Reflection feedback]\nThe previous output needs improvement:\n- Critique: ${reflectionResult.critique}\n- Suggestions: ${reflectionResult.suggestions.join(', ')}\n\nPlease improve the output.`,
|
|
1239
|
+
};
|
|
1240
|
+
messages.push(feedbackMessage);
|
|
1241
|
+
ctx.state.messages.push(feedbackMessage);
|
|
1242
|
+
ctx.observability?.logger?.info('Reflection not satisfied, retrying', {
|
|
1243
|
+
attempt: reflectionAttempt,
|
|
1244
|
+
confidence: reflectionResult.confidence,
|
|
1245
|
+
critique: reflectionResult.critique,
|
|
1246
|
+
});
|
|
1247
|
+
}
|
|
1248
|
+
else {
|
|
1249
|
+
break;
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
// Store conversation in memory
|
|
1253
|
+
ctx.memory?.storeConversation(ctx.state.messages);
|
|
1254
|
+
// Memory stats update (hook point)
|
|
1255
|
+
ctx.memory?.getStats();
|
|
1256
|
+
return result;
|
|
1257
|
+
}
|
|
1258
|
+
//# sourceMappingURL=execution-loop.js.map
|