attocode 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +48 -0
- package/LICENSE +21 -0
- package/README.md +164 -0
- package/dist/src/adapters.d.ts +83 -0
- package/dist/src/adapters.d.ts.map +1 -0
- package/dist/src/adapters.js +221 -0
- package/dist/src/adapters.js.map +1 -0
- package/dist/src/agent-tools/index.d.ts +7 -0
- package/dist/src/agent-tools/index.d.ts.map +1 -0
- package/dist/src/agent-tools/index.js +8 -0
- package/dist/src/agent-tools/index.js.map +1 -0
- package/dist/src/agent-tools/lsp-file-tools.d.ts +33 -0
- package/dist/src/agent-tools/lsp-file-tools.d.ts.map +1 -0
- package/dist/src/agent-tools/lsp-file-tools.js +200 -0
- package/dist/src/agent-tools/lsp-file-tools.js.map +1 -0
- package/dist/src/agent.d.ts +667 -0
- package/dist/src/agent.d.ts.map +1 -0
- package/dist/src/agent.js +2824 -0
- package/dist/src/agent.js.map +1 -0
- package/dist/src/cli.d.ts +36 -0
- package/dist/src/cli.d.ts.map +1 -0
- package/dist/src/cli.js +176 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/commands/handler.d.ts +22 -0
- package/dist/src/commands/handler.d.ts.map +1 -0
- package/dist/src/commands/handler.js +1320 -0
- package/dist/src/commands/handler.js.map +1 -0
- package/dist/src/commands/init.d.ts +7 -0
- package/dist/src/commands/init.d.ts.map +1 -0
- package/dist/src/commands/init.js +153 -0
- package/dist/src/commands/init.js.map +1 -0
- package/dist/src/commands/types.d.ts +70 -0
- package/dist/src/commands/types.d.ts.map +1 -0
- package/dist/src/commands/types.js +8 -0
- package/dist/src/commands/types.js.map +1 -0
- package/dist/src/config.d.ts +22 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/config.js +25 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/core/index.d.ts +32 -0
- package/dist/src/core/index.d.ts.map +1 -0
- package/dist/src/core/index.js +35 -0
- package/dist/src/core/index.js.map +1 -0
- package/dist/src/core/process-handlers.d.ts +43 -0
- package/dist/src/core/process-handlers.d.ts.map +1 -0
- package/dist/src/core/process-handlers.js +117 -0
- package/dist/src/core/process-handlers.js.map +1 -0
- package/dist/src/core/protocol/bridge.d.ts +117 -0
- package/dist/src/core/protocol/bridge.d.ts.map +1 -0
- package/dist/src/core/protocol/bridge.js +149 -0
- package/dist/src/core/protocol/bridge.js.map +1 -0
- package/dist/src/core/protocol/index.d.ts +8 -0
- package/dist/src/core/protocol/index.d.ts.map +1 -0
- package/dist/src/core/protocol/index.js +8 -0
- package/dist/src/core/protocol/index.js.map +1 -0
- package/dist/src/core/protocol/types.d.ts +539 -0
- package/dist/src/core/protocol/types.d.ts.map +1 -0
- package/dist/src/core/protocol/types.js +149 -0
- package/dist/src/core/protocol/types.js.map +1 -0
- package/dist/src/core/queues/atomic-counter.d.ts +36 -0
- package/dist/src/core/queues/atomic-counter.d.ts.map +1 -0
- package/dist/src/core/queues/atomic-counter.js +46 -0
- package/dist/src/core/queues/atomic-counter.js.map +1 -0
- package/dist/src/core/queues/event-queue.d.ts +126 -0
- package/dist/src/core/queues/event-queue.d.ts.map +1 -0
- package/dist/src/core/queues/event-queue.js +208 -0
- package/dist/src/core/queues/event-queue.js.map +1 -0
- package/dist/src/core/queues/index.d.ts +12 -0
- package/dist/src/core/queues/index.d.ts.map +1 -0
- package/dist/src/core/queues/index.js +15 -0
- package/dist/src/core/queues/index.js.map +1 -0
- package/dist/src/core/queues/submission-queue.d.ts +116 -0
- package/dist/src/core/queues/submission-queue.d.ts.map +1 -0
- package/dist/src/core/queues/submission-queue.js +236 -0
- package/dist/src/core/queues/submission-queue.js.map +1 -0
- package/dist/src/costs/index.d.ts +22 -0
- package/dist/src/costs/index.d.ts.map +1 -0
- package/dist/src/costs/index.js +22 -0
- package/dist/src/costs/index.js.map +1 -0
- package/dist/src/costs/model-registry.d.ts +80 -0
- package/dist/src/costs/model-registry.d.ts.map +1 -0
- package/dist/src/costs/model-registry.js +237 -0
- package/dist/src/costs/model-registry.js.map +1 -0
- package/dist/src/costs/types.d.ts +50 -0
- package/dist/src/costs/types.d.ts.map +1 -0
- package/dist/src/costs/types.js +2 -0
- package/dist/src/costs/types.js.map +1 -0
- package/dist/src/defaults.d.ts +114 -0
- package/dist/src/defaults.d.ts.map +1 -0
- package/dist/src/defaults.js +457 -0
- package/dist/src/defaults.js.map +1 -0
- package/dist/src/first-run.d.ts +35 -0
- package/dist/src/first-run.d.ts.map +1 -0
- package/dist/src/first-run.js +94 -0
- package/dist/src/first-run.js.map +1 -0
- package/dist/src/hello.d.ts +2 -0
- package/dist/src/hello.d.ts.map +1 -0
- package/dist/src/hello.js +4 -0
- package/dist/src/hello.js.map +1 -0
- package/dist/src/integrations/agent-registry.d.ts +160 -0
- package/dist/src/integrations/agent-registry.d.ts.map +1 -0
- package/dist/src/integrations/agent-registry.js +446 -0
- package/dist/src/integrations/agent-registry.js.map +1 -0
- package/dist/src/integrations/auto-compaction.d.ts +177 -0
- package/dist/src/integrations/auto-compaction.d.ts.map +1 -0
- package/dist/src/integrations/auto-compaction.js +428 -0
- package/dist/src/integrations/auto-compaction.js.map +1 -0
- package/dist/src/integrations/cancellation.d.ts +162 -0
- package/dist/src/integrations/cancellation.d.ts.map +1 -0
- package/dist/src/integrations/cancellation.js +339 -0
- package/dist/src/integrations/cancellation.js.map +1 -0
- package/dist/src/integrations/codebase-context.d.ts +319 -0
- package/dist/src/integrations/codebase-context.d.ts.map +1 -0
- package/dist/src/integrations/codebase-context.js +816 -0
- package/dist/src/integrations/codebase-context.js.map +1 -0
- package/dist/src/integrations/compaction.d.ts +192 -0
- package/dist/src/integrations/compaction.d.ts.map +1 -0
- package/dist/src/integrations/compaction.js +376 -0
- package/dist/src/integrations/compaction.js.map +1 -0
- package/dist/src/integrations/context-engineering.d.ts +246 -0
- package/dist/src/integrations/context-engineering.d.ts.map +1 -0
- package/dist/src/integrations/context-engineering.js +394 -0
- package/dist/src/integrations/context-engineering.js.map +1 -0
- package/dist/src/integrations/diff-utils.d.ts +105 -0
- package/dist/src/integrations/diff-utils.d.ts.map +1 -0
- package/dist/src/integrations/diff-utils.js +497 -0
- package/dist/src/integrations/diff-utils.js.map +1 -0
- package/dist/src/integrations/economics.d.ts +192 -0
- package/dist/src/integrations/economics.d.ts.map +1 -0
- package/dist/src/integrations/economics.js +431 -0
- package/dist/src/integrations/economics.js.map +1 -0
- package/dist/src/integrations/execution-policy.d.ts +189 -0
- package/dist/src/integrations/execution-policy.d.ts.map +1 -0
- package/dist/src/integrations/execution-policy.js +352 -0
- package/dist/src/integrations/execution-policy.js.map +1 -0
- package/dist/src/integrations/file-change-tracker.d.ts +161 -0
- package/dist/src/integrations/file-change-tracker.d.ts.map +1 -0
- package/dist/src/integrations/file-change-tracker.js +520 -0
- package/dist/src/integrations/file-change-tracker.js.map +1 -0
- package/dist/src/integrations/hierarchical-config.d.ts +212 -0
- package/dist/src/integrations/hierarchical-config.d.ts.map +1 -0
- package/dist/src/integrations/hierarchical-config.js +484 -0
- package/dist/src/integrations/hierarchical-config.js.map +1 -0
- package/dist/src/integrations/hooks.d.ts +114 -0
- package/dist/src/integrations/hooks.d.ts.map +1 -0
- package/dist/src/integrations/hooks.js +326 -0
- package/dist/src/integrations/hooks.js.map +1 -0
- package/dist/src/integrations/ignore.d.ts +143 -0
- package/dist/src/integrations/ignore.d.ts.map +1 -0
- package/dist/src/integrations/ignore.js +417 -0
- package/dist/src/integrations/ignore.js.map +1 -0
- package/dist/src/integrations/image-renderer.d.ts +119 -0
- package/dist/src/integrations/image-renderer.d.ts.map +1 -0
- package/dist/src/integrations/image-renderer.js +306 -0
- package/dist/src/integrations/image-renderer.js.map +1 -0
- package/dist/src/integrations/index.d.ts +42 -0
- package/dist/src/integrations/index.d.ts.map +1 -0
- package/dist/src/integrations/index.js +73 -0
- package/dist/src/integrations/index.js.map +1 -0
- package/dist/src/integrations/lsp.d.ts +196 -0
- package/dist/src/integrations/lsp.d.ts.map +1 -0
- package/dist/src/integrations/lsp.js +582 -0
- package/dist/src/integrations/lsp.js.map +1 -0
- package/dist/src/integrations/mcp-client.d.ts +270 -0
- package/dist/src/integrations/mcp-client.d.ts.map +1 -0
- package/dist/src/integrations/mcp-client.js +698 -0
- package/dist/src/integrations/mcp-client.js.map +1 -0
- package/dist/src/integrations/mcp-tool-search.d.ts +77 -0
- package/dist/src/integrations/mcp-tool-search.d.ts.map +1 -0
- package/dist/src/integrations/mcp-tool-search.js +220 -0
- package/dist/src/integrations/mcp-tool-search.js.map +1 -0
- package/dist/src/integrations/memory.d.ts +108 -0
- package/dist/src/integrations/memory.d.ts.map +1 -0
- package/dist/src/integrations/memory.js +288 -0
- package/dist/src/integrations/memory.js.map +1 -0
- package/dist/src/integrations/multi-agent.d.ts +150 -0
- package/dist/src/integrations/multi-agent.d.ts.map +1 -0
- package/dist/src/integrations/multi-agent.js +306 -0
- package/dist/src/integrations/multi-agent.js.map +1 -0
- package/dist/src/integrations/observability.d.ts +162 -0
- package/dist/src/integrations/observability.d.ts.map +1 -0
- package/dist/src/integrations/observability.js +406 -0
- package/dist/src/integrations/observability.js.map +1 -0
- package/dist/src/integrations/openrouter-pricing.d.ts +42 -0
- package/dist/src/integrations/openrouter-pricing.d.ts.map +1 -0
- package/dist/src/integrations/openrouter-pricing.js +124 -0
- package/dist/src/integrations/openrouter-pricing.js.map +1 -0
- package/dist/src/integrations/pending-plan.d.ts +171 -0
- package/dist/src/integrations/pending-plan.d.ts.map +1 -0
- package/dist/src/integrations/pending-plan.js +244 -0
- package/dist/src/integrations/pending-plan.js.map +1 -0
- package/dist/src/integrations/persistence.d.ts +48 -0
- package/dist/src/integrations/persistence.d.ts.map +1 -0
- package/dist/src/integrations/persistence.js +196 -0
- package/dist/src/integrations/persistence.js.map +1 -0
- package/dist/src/integrations/planning.d.ts +96 -0
- package/dist/src/integrations/planning.d.ts.map +1 -0
- package/dist/src/integrations/planning.js +338 -0
- package/dist/src/integrations/planning.js.map +1 -0
- package/dist/src/integrations/pty-shell.d.ts +169 -0
- package/dist/src/integrations/pty-shell.d.ts.map +1 -0
- package/dist/src/integrations/pty-shell.js +367 -0
- package/dist/src/integrations/pty-shell.js.map +1 -0
- package/dist/src/integrations/react.d.ts +139 -0
- package/dist/src/integrations/react.d.ts.map +1 -0
- package/dist/src/integrations/react.js +273 -0
- package/dist/src/integrations/react.js.map +1 -0
- package/dist/src/integrations/resources.d.ts +177 -0
- package/dist/src/integrations/resources.d.ts.map +1 -0
- package/dist/src/integrations/resources.js +311 -0
- package/dist/src/integrations/resources.js.map +1 -0
- package/dist/src/integrations/result-synthesizer.d.ts +389 -0
- package/dist/src/integrations/result-synthesizer.d.ts.map +1 -0
- package/dist/src/integrations/result-synthesizer.js +951 -0
- package/dist/src/integrations/result-synthesizer.js.map +1 -0
- package/dist/src/integrations/routing.d.ts +117 -0
- package/dist/src/integrations/routing.d.ts.map +1 -0
- package/dist/src/integrations/routing.js +347 -0
- package/dist/src/integrations/routing.js.map +1 -0
- package/dist/src/integrations/rules.d.ts +131 -0
- package/dist/src/integrations/rules.d.ts.map +1 -0
- package/dist/src/integrations/rules.js +284 -0
- package/dist/src/integrations/rules.js.map +1 -0
- package/dist/src/integrations/safety.d.ts +142 -0
- package/dist/src/integrations/safety.d.ts.map +1 -0
- package/dist/src/integrations/safety.js +342 -0
- package/dist/src/integrations/safety.js.map +1 -0
- package/dist/src/integrations/sandbox/basic.d.ts +74 -0
- package/dist/src/integrations/sandbox/basic.d.ts.map +1 -0
- package/dist/src/integrations/sandbox/basic.js +310 -0
- package/dist/src/integrations/sandbox/basic.js.map +1 -0
- package/dist/src/integrations/sandbox/docker.d.ts +94 -0
- package/dist/src/integrations/sandbox/docker.d.ts.map +1 -0
- package/dist/src/integrations/sandbox/docker.js +293 -0
- package/dist/src/integrations/sandbox/docker.js.map +1 -0
- package/dist/src/integrations/sandbox/index.d.ts +182 -0
- package/dist/src/integrations/sandbox/index.d.ts.map +1 -0
- package/dist/src/integrations/sandbox/index.js +382 -0
- package/dist/src/integrations/sandbox/index.js.map +1 -0
- package/dist/src/integrations/sandbox/landlock.d.ts +59 -0
- package/dist/src/integrations/sandbox/landlock.d.ts.map +1 -0
- package/dist/src/integrations/sandbox/landlock.js +326 -0
- package/dist/src/integrations/sandbox/landlock.js.map +1 -0
- package/dist/src/integrations/sandbox/seatbelt.d.ts +68 -0
- package/dist/src/integrations/sandbox/seatbelt.d.ts.map +1 -0
- package/dist/src/integrations/sandbox/seatbelt.js +298 -0
- package/dist/src/integrations/sandbox/seatbelt.js.map +1 -0
- package/dist/src/integrations/semantic-cache.d.ts +178 -0
- package/dist/src/integrations/semantic-cache.d.ts.map +1 -0
- package/dist/src/integrations/semantic-cache.js +372 -0
- package/dist/src/integrations/semantic-cache.js.map +1 -0
- package/dist/src/integrations/session-store.d.ts +183 -0
- package/dist/src/integrations/session-store.d.ts.map +1 -0
- package/dist/src/integrations/session-store.js +345 -0
- package/dist/src/integrations/session-store.js.map +1 -0
- package/dist/src/integrations/shared-blackboard.d.ts +403 -0
- package/dist/src/integrations/shared-blackboard.d.ts.map +1 -0
- package/dist/src/integrations/shared-blackboard.js +710 -0
- package/dist/src/integrations/shared-blackboard.js.map +1 -0
- package/dist/src/integrations/skills.d.ts +171 -0
- package/dist/src/integrations/skills.d.ts.map +1 -0
- package/dist/src/integrations/skills.js +403 -0
- package/dist/src/integrations/skills.js.map +1 -0
- package/dist/src/integrations/smart-decomposer.d.ts +322 -0
- package/dist/src/integrations/smart-decomposer.d.ts.map +1 -0
- package/dist/src/integrations/smart-decomposer.js +856 -0
- package/dist/src/integrations/smart-decomposer.js.map +1 -0
- package/dist/src/integrations/sourcegraph.d.ts +169 -0
- package/dist/src/integrations/sourcegraph.d.ts.map +1 -0
- package/dist/src/integrations/sourcegraph.js +379 -0
- package/dist/src/integrations/sourcegraph.js.map +1 -0
- package/dist/src/integrations/sqlite-store.d.ts +518 -0
- package/dist/src/integrations/sqlite-store.d.ts.map +1 -0
- package/dist/src/integrations/sqlite-store.js +1423 -0
- package/dist/src/integrations/sqlite-store.js.map +1 -0
- package/dist/src/integrations/streaming.d.ts +102 -0
- package/dist/src/integrations/streaming.d.ts.map +1 -0
- package/dist/src/integrations/streaming.js +362 -0
- package/dist/src/integrations/streaming.js.map +1 -0
- package/dist/src/integrations/thread-manager.d.ts +199 -0
- package/dist/src/integrations/thread-manager.d.ts.map +1 -0
- package/dist/src/integrations/thread-manager.js +357 -0
- package/dist/src/integrations/thread-manager.js.map +1 -0
- package/dist/src/main.d.ts +26 -0
- package/dist/src/main.d.ts.map +1 -0
- package/dist/src/main.js +170 -0
- package/dist/src/main.js.map +1 -0
- package/dist/src/modes/index.d.ts +10 -0
- package/dist/src/modes/index.d.ts.map +1 -0
- package/dist/src/modes/index.js +10 -0
- package/dist/src/modes/index.js.map +1 -0
- package/dist/src/modes/repl.d.ts +19 -0
- package/dist/src/modes/repl.d.ts.map +1 -0
- package/dist/src/modes/repl.js +393 -0
- package/dist/src/modes/repl.js.map +1 -0
- package/dist/src/modes/tui.d.ts +29 -0
- package/dist/src/modes/tui.d.ts.map +1 -0
- package/dist/src/modes/tui.js +272 -0
- package/dist/src/modes/tui.js.map +1 -0
- package/dist/src/modes.d.ts +179 -0
- package/dist/src/modes.d.ts.map +1 -0
- package/dist/src/modes.js +385 -0
- package/dist/src/modes.js.map +1 -0
- package/dist/src/observability/tracer.d.ts +111 -0
- package/dist/src/observability/tracer.d.ts.map +1 -0
- package/dist/src/observability/tracer.js +300 -0
- package/dist/src/observability/tracer.js.map +1 -0
- package/dist/src/observability/types.d.ts +271 -0
- package/dist/src/observability/types.d.ts.map +1 -0
- package/dist/src/observability/types.js +24 -0
- package/dist/src/observability/types.js.map +1 -0
- package/dist/src/paths.d.ts +101 -0
- package/dist/src/paths.d.ts.map +1 -0
- package/dist/src/paths.js +148 -0
- package/dist/src/paths.js.map +1 -0
- package/dist/src/persistence/index.d.ts +38 -0
- package/dist/src/persistence/index.d.ts.map +1 -0
- package/dist/src/persistence/index.js +48 -0
- package/dist/src/persistence/index.js.map +1 -0
- package/dist/src/persistence/migrator.d.ts +135 -0
- package/dist/src/persistence/migrator.d.ts.map +1 -0
- package/dist/src/persistence/migrator.js +303 -0
- package/dist/src/persistence/migrator.js.map +1 -0
- package/dist/src/persistence/schema.d.ts +101 -0
- package/dist/src/persistence/schema.d.ts.map +1 -0
- package/dist/src/persistence/schema.js +395 -0
- package/dist/src/persistence/schema.js.map +1 -0
- package/dist/src/providers/adapters/anthropic.d.ts +20 -0
- package/dist/src/providers/adapters/anthropic.d.ts.map +1 -0
- package/dist/src/providers/adapters/anthropic.js +124 -0
- package/dist/src/providers/adapters/anthropic.js.map +1 -0
- package/dist/src/providers/adapters/mock.d.ts +25 -0
- package/dist/src/providers/adapters/mock.d.ts.map +1 -0
- package/dist/src/providers/adapters/mock.js +133 -0
- package/dist/src/providers/adapters/mock.js.map +1 -0
- package/dist/src/providers/adapters/openai.d.ts +21 -0
- package/dist/src/providers/adapters/openai.d.ts.map +1 -0
- package/dist/src/providers/adapters/openai.js +126 -0
- package/dist/src/providers/adapters/openai.js.map +1 -0
- package/dist/src/providers/adapters/openrouter.d.ts +49 -0
- package/dist/src/providers/adapters/openrouter.d.ts.map +1 -0
- package/dist/src/providers/adapters/openrouter.js +363 -0
- package/dist/src/providers/adapters/openrouter.js.map +1 -0
- package/dist/src/providers/provider.d.ts +54 -0
- package/dist/src/providers/provider.d.ts.map +1 -0
- package/dist/src/providers/provider.js +111 -0
- package/dist/src/providers/provider.js.map +1 -0
- package/dist/src/providers/resilient-fetch.d.ts +99 -0
- package/dist/src/providers/resilient-fetch.d.ts.map +1 -0
- package/dist/src/providers/resilient-fetch.js +208 -0
- package/dist/src/providers/resilient-fetch.js.map +1 -0
- package/dist/src/providers/types.d.ts +227 -0
- package/dist/src/providers/types.d.ts.map +1 -0
- package/dist/src/providers/types.js +24 -0
- package/dist/src/providers/types.js.map +1 -0
- package/dist/src/session-picker.d.ts +28 -0
- package/dist/src/session-picker.d.ts.map +1 -0
- package/dist/src/session-picker.js +256 -0
- package/dist/src/session-picker.js.map +1 -0
- package/dist/src/test-sqlite.d.ts +2 -0
- package/dist/src/test-sqlite.d.ts.map +1 -0
- package/dist/src/test-sqlite.js +114 -0
- package/dist/src/test-sqlite.js.map +1 -0
- package/dist/src/tools/agent.d.ts +44 -0
- package/dist/src/tools/agent.d.ts.map +1 -0
- package/dist/src/tools/agent.js +110 -0
- package/dist/src/tools/agent.js.map +1 -0
- package/dist/src/tools/bash.d.ts +52 -0
- package/dist/src/tools/bash.d.ts.map +1 -0
- package/dist/src/tools/bash.js +141 -0
- package/dist/src/tools/bash.js.map +1 -0
- package/dist/src/tools/file.d.ts +47 -0
- package/dist/src/tools/file.d.ts.map +1 -0
- package/dist/src/tools/file.js +263 -0
- package/dist/src/tools/file.js.map +1 -0
- package/dist/src/tools/permission.d.ts +43 -0
- package/dist/src/tools/permission.d.ts.map +1 -0
- package/dist/src/tools/permission.js +216 -0
- package/dist/src/tools/permission.js.map +1 -0
- package/dist/src/tools/registry.d.ts +63 -0
- package/dist/src/tools/registry.d.ts.map +1 -0
- package/dist/src/tools/registry.js +250 -0
- package/dist/src/tools/registry.js.map +1 -0
- package/dist/src/tools/standard.d.ts +57 -0
- package/dist/src/tools/standard.d.ts.map +1 -0
- package/dist/src/tools/standard.js +113 -0
- package/dist/src/tools/standard.js.map +1 -0
- package/dist/src/tools/types.d.ts +146 -0
- package/dist/src/tools/types.d.ts.map +1 -0
- package/dist/src/tools/types.js +28 -0
- package/dist/src/tools/types.js.map +1 -0
- package/dist/src/tools/undo.d.ts +71 -0
- package/dist/src/tools/undo.d.ts.map +1 -0
- package/dist/src/tools/undo.js +123 -0
- package/dist/src/tools/undo.js.map +1 -0
- package/dist/src/tracing/cache-boundary-tracker.d.ts +189 -0
- package/dist/src/tracing/cache-boundary-tracker.d.ts.map +1 -0
- package/dist/src/tracing/cache-boundary-tracker.js +411 -0
- package/dist/src/tracing/cache-boundary-tracker.js.map +1 -0
- package/dist/src/tracing/trace-collector.d.ts +274 -0
- package/dist/src/tracing/trace-collector.d.ts.map +1 -0
- package/dist/src/tracing/trace-collector.js +727 -0
- package/dist/src/tracing/trace-collector.js.map +1 -0
- package/dist/src/tracing/types.d.ts +657 -0
- package/dist/src/tracing/types.d.ts.map +1 -0
- package/dist/src/tracing/types.js +39 -0
- package/dist/src/tracing/types.js.map +1 -0
- package/dist/src/tricks/failure-evidence.d.ts +268 -0
- package/dist/src/tricks/failure-evidence.d.ts.map +1 -0
- package/dist/src/tricks/failure-evidence.js +544 -0
- package/dist/src/tricks/failure-evidence.js.map +1 -0
- package/dist/src/tricks/json-utils.d.ts +77 -0
- package/dist/src/tricks/json-utils.d.ts.map +1 -0
- package/dist/src/tricks/json-utils.js +247 -0
- package/dist/src/tricks/json-utils.js.map +1 -0
- package/dist/src/tricks/kv-cache-context.d.ts +227 -0
- package/dist/src/tricks/kv-cache-context.d.ts.map +1 -0
- package/dist/src/tricks/kv-cache-context.js +377 -0
- package/dist/src/tricks/kv-cache-context.js.map +1 -0
- package/dist/src/tricks/recitation.d.ts +208 -0
- package/dist/src/tricks/recitation.d.ts.map +1 -0
- package/dist/src/tricks/recitation.js +374 -0
- package/dist/src/tricks/recitation.js.map +1 -0
- package/dist/src/tricks/reversible-compaction.d.ts +251 -0
- package/dist/src/tricks/reversible-compaction.d.ts.map +1 -0
- package/dist/src/tricks/reversible-compaction.js +555 -0
- package/dist/src/tricks/reversible-compaction.js.map +1 -0
- package/dist/src/tricks/serialization-diversity.d.ts +197 -0
- package/dist/src/tricks/serialization-diversity.d.ts.map +1 -0
- package/dist/src/tricks/serialization-diversity.js +460 -0
- package/dist/src/tricks/serialization-diversity.js.map +1 -0
- package/dist/src/tui/app.d.ts +42 -0
- package/dist/src/tui/app.d.ts.map +1 -0
- package/dist/src/tui/app.js +1076 -0
- package/dist/src/tui/app.js.map +1 -0
- package/dist/src/tui/components/ApprovalDialog.d.ts +28 -0
- package/dist/src/tui/components/ApprovalDialog.d.ts.map +1 -0
- package/dist/src/tui/components/ApprovalDialog.js +59 -0
- package/dist/src/tui/components/ApprovalDialog.js.map +1 -0
- package/dist/src/tui/components/InputArea.d.ts +35 -0
- package/dist/src/tui/components/InputArea.d.ts.map +1 -0
- package/dist/src/tui/components/InputArea.js +144 -0
- package/dist/src/tui/components/InputArea.js.map +1 -0
- package/dist/src/tui/components/MessageItem.d.ts +28 -0
- package/dist/src/tui/components/MessageItem.d.ts.map +1 -0
- package/dist/src/tui/components/MessageItem.js +27 -0
- package/dist/src/tui/components/MessageItem.js.map +1 -0
- package/dist/src/tui/components/ScrollableBox.d.ts +41 -0
- package/dist/src/tui/components/ScrollableBox.d.ts.map +1 -0
- package/dist/src/tui/components/ScrollableBox.js +101 -0
- package/dist/src/tui/components/ScrollableBox.js.map +1 -0
- package/dist/src/tui/components/ToolCallItem.d.ts +33 -0
- package/dist/src/tui/components/ToolCallItem.d.ts.map +1 -0
- package/dist/src/tui/components/ToolCallItem.js +91 -0
- package/dist/src/tui/components/ToolCallItem.js.map +1 -0
- package/dist/src/tui/components/index.d.ts +13 -0
- package/dist/src/tui/components/index.d.ts.map +1 -0
- package/dist/src/tui/components/index.js +15 -0
- package/dist/src/tui/components/index.js.map +1 -0
- package/dist/src/tui/event-display.d.ts +19 -0
- package/dist/src/tui/event-display.d.ts.map +1 -0
- package/dist/src/tui/event-display.js +178 -0
- package/dist/src/tui/event-display.js.map +1 -0
- package/dist/src/tui/index.d.ts +105 -0
- package/dist/src/tui/index.d.ts.map +1 -0
- package/dist/src/tui/index.js +214 -0
- package/dist/src/tui/index.js.map +1 -0
- package/dist/src/tui/input/CommandPalette.d.ts +55 -0
- package/dist/src/tui/input/CommandPalette.d.ts.map +1 -0
- package/dist/src/tui/input/CommandPalette.js +135 -0
- package/dist/src/tui/input/CommandPalette.js.map +1 -0
- package/dist/src/tui/input/index.d.ts +7 -0
- package/dist/src/tui/input/index.d.ts.map +1 -0
- package/dist/src/tui/input/index.js +7 -0
- package/dist/src/tui/input/index.js.map +1 -0
- package/dist/src/tui/theme/index.d.ts +45 -0
- package/dist/src/tui/theme/index.d.ts.map +1 -0
- package/dist/src/tui/theme/index.js +215 -0
- package/dist/src/tui/theme/index.js.map +1 -0
- package/dist/src/tui/types.d.ts +214 -0
- package/dist/src/tui/types.d.ts.map +1 -0
- package/dist/src/tui/types.js +27 -0
- package/dist/src/tui/types.js.map +1 -0
- package/dist/src/types.d.ts +905 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +9 -0
- package/dist/src/types.js.map +1 -0
- package/package.json +89 -0
|
@@ -0,0 +1,2824 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lesson 25: Production Agent
|
|
3
|
+
*
|
|
4
|
+
* The capstone agent that composes all features from previous lessons
|
|
5
|
+
* into a production-ready, modular system.
|
|
6
|
+
*
|
|
7
|
+
* Features integrated:
|
|
8
|
+
* - Hooks & Plugins (Lessons 10-11)
|
|
9
|
+
* - Rules System (Lesson 12)
|
|
10
|
+
* - Memory Systems (Lesson 14)
|
|
11
|
+
* - Planning & Reflection (Lessons 15-16)
|
|
12
|
+
* - Multi-Agent Coordination (Lesson 17)
|
|
13
|
+
* - ReAct Pattern (Lesson 18)
|
|
14
|
+
* - Observability (Lesson 19)
|
|
15
|
+
* - Sandboxing (Lesson 20)
|
|
16
|
+
* - Human-in-the-Loop (Lesson 21)
|
|
17
|
+
* - Model Routing (Lesson 22)
|
|
18
|
+
* - Execution Policies (Lesson 23)
|
|
19
|
+
* - Thread Management (Lesson 24)
|
|
20
|
+
*/
|
|
21
|
+
import { buildConfig, isFeatureEnabled, getEnabledFeatures, } from './defaults.js';
|
|
22
|
+
import { createModeManager, formatModeList, parseMode, } from './modes.js';
|
|
23
|
+
import { createLSPFileTools, } from './agent-tools/index.js';
|
|
24
|
+
import { HookManager, MemoryManager, PlanningManager, ObservabilityManager, SafetyManager, RoutingManager, MultiAgentManager, ReActManager, ExecutionPolicyManager, ThreadManager, RulesManager, DEFAULT_RULE_SOURCES, ExecutionEconomicsManager, STANDARD_BUDGET, AgentRegistry, filterToolsForAgent, formatAgentList, createCancellationManager, isCancellationError, createResourceManager, createLSPManager, createSemanticCacheManager, createSkillManager, formatSkillList, createContextEngineering, stableStringify, createCodebaseContext, buildContextFromChunks, createPendingPlanManager, } from './integrations/index.js';
|
|
25
|
+
// Lesson 26: Tracing & Evaluation integration
|
|
26
|
+
import { createTraceCollector } from './tracing/trace-collector.js';
|
|
27
|
+
// Spawn agent tool for LLM-driven subagent delegation
|
|
28
|
+
import { createBoundSpawnAgentTool } from './tools/agent.js';
|
|
29
|
+
// =============================================================================
|
|
30
|
+
// PRODUCTION AGENT
|
|
31
|
+
// =============================================================================
|
|
32
|
+
/**
|
|
33
|
+
* Production-ready agent that composes all features.
|
|
34
|
+
*/
|
|
35
|
+
export class ProductionAgent {
|
|
36
|
+
config;
|
|
37
|
+
provider;
|
|
38
|
+
tools;
|
|
39
|
+
// Integration managers
|
|
40
|
+
hooks = null;
|
|
41
|
+
memory = null;
|
|
42
|
+
planning = null;
|
|
43
|
+
observability = null;
|
|
44
|
+
safety = null;
|
|
45
|
+
routing = null;
|
|
46
|
+
multiAgent = null;
|
|
47
|
+
react = null;
|
|
48
|
+
executionPolicy = null;
|
|
49
|
+
threadManager = null;
|
|
50
|
+
rules = null;
|
|
51
|
+
economics = null;
|
|
52
|
+
agentRegistry = null;
|
|
53
|
+
cancellation = null;
|
|
54
|
+
resourceManager = null;
|
|
55
|
+
lspManager = null;
|
|
56
|
+
semanticCache = null;
|
|
57
|
+
skillManager = null;
|
|
58
|
+
contextEngineering = null;
|
|
59
|
+
codebaseContext = null;
|
|
60
|
+
traceCollector = null;
|
|
61
|
+
modeManager;
|
|
62
|
+
pendingPlanManager;
|
|
63
|
+
toolResolver = null;
|
|
64
|
+
// Initialization tracking
|
|
65
|
+
initPromises = [];
|
|
66
|
+
initComplete = false;
|
|
67
|
+
// State
|
|
68
|
+
state = {
|
|
69
|
+
status: 'idle',
|
|
70
|
+
messages: [],
|
|
71
|
+
plan: undefined,
|
|
72
|
+
memoryContext: [],
|
|
73
|
+
metrics: {
|
|
74
|
+
totalTokens: 0,
|
|
75
|
+
inputTokens: 0,
|
|
76
|
+
outputTokens: 0,
|
|
77
|
+
estimatedCost: 0,
|
|
78
|
+
llmCalls: 0,
|
|
79
|
+
toolCalls: 0,
|
|
80
|
+
duration: 0,
|
|
81
|
+
},
|
|
82
|
+
iteration: 0,
|
|
83
|
+
};
|
|
84
|
+
constructor(userConfig) {
|
|
85
|
+
// Build complete config with defaults
|
|
86
|
+
this.config = buildConfig(userConfig);
|
|
87
|
+
this.provider = userConfig.provider;
|
|
88
|
+
// Initialize tool registry
|
|
89
|
+
this.tools = new Map();
|
|
90
|
+
for (const tool of this.config.tools) {
|
|
91
|
+
this.tools.set(tool.name, tool);
|
|
92
|
+
}
|
|
93
|
+
// Store tool resolver for lazy-loading unknown tools (e.g., MCP tools)
|
|
94
|
+
this.toolResolver = userConfig.toolResolver || null;
|
|
95
|
+
// Initialize mode manager (always enabled)
|
|
96
|
+
this.modeManager = createModeManager(this.config.tools);
|
|
97
|
+
// Initialize pending plan manager for plan mode write interception
|
|
98
|
+
this.pendingPlanManager = createPendingPlanManager();
|
|
99
|
+
// Initialize enabled features
|
|
100
|
+
this.initializeFeatures();
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Initialize all enabled features.
|
|
104
|
+
*/
|
|
105
|
+
initializeFeatures() {
|
|
106
|
+
// Debug output only when DEBUG env var is set
|
|
107
|
+
if (process.env.DEBUG) {
|
|
108
|
+
const features = getEnabledFeatures(this.config);
|
|
109
|
+
console.log(`[ProductionAgent] Initializing with features: ${features.join(', ')}`);
|
|
110
|
+
}
|
|
111
|
+
// Hooks & Plugins
|
|
112
|
+
if (isFeatureEnabled(this.config.hooks) && isFeatureEnabled(this.config.plugins)) {
|
|
113
|
+
this.hooks = new HookManager(this.config.hooks, this.config.plugins);
|
|
114
|
+
}
|
|
115
|
+
// Memory
|
|
116
|
+
if (isFeatureEnabled(this.config.memory)) {
|
|
117
|
+
this.memory = new MemoryManager(this.config.memory);
|
|
118
|
+
}
|
|
119
|
+
// Planning & Reflection
|
|
120
|
+
if (isFeatureEnabled(this.config.planning) && isFeatureEnabled(this.config.reflection)) {
|
|
121
|
+
this.planning = new PlanningManager(this.config.planning, this.config.reflection);
|
|
122
|
+
}
|
|
123
|
+
// Observability
|
|
124
|
+
if (isFeatureEnabled(this.config.observability)) {
|
|
125
|
+
this.observability = new ObservabilityManager(this.config.observability);
|
|
126
|
+
// Lesson 26: Full trace capture
|
|
127
|
+
const traceCaptureConfig = this.config.observability.traceCapture;
|
|
128
|
+
if (traceCaptureConfig?.enabled) {
|
|
129
|
+
this.traceCollector = createTraceCollector({
|
|
130
|
+
enabled: true,
|
|
131
|
+
outputDir: traceCaptureConfig.outputDir ?? '.traces',
|
|
132
|
+
captureMessageContent: traceCaptureConfig.captureMessageContent ?? true,
|
|
133
|
+
captureToolResults: traceCaptureConfig.captureToolResults ?? true,
|
|
134
|
+
analyzeCacheBoundaries: traceCaptureConfig.analyzeCacheBoundaries ?? true,
|
|
135
|
+
filePattern: traceCaptureConfig.filePattern ?? 'trace-{sessionId}-{timestamp}.jsonl',
|
|
136
|
+
enableConsoleOutput: false,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// Safety (Sandbox + Human-in-Loop)
|
|
141
|
+
if (isFeatureEnabled(this.config.sandbox) || isFeatureEnabled(this.config.humanInLoop)) {
|
|
142
|
+
this.safety = new SafetyManager(isFeatureEnabled(this.config.sandbox) ? this.config.sandbox : false, isFeatureEnabled(this.config.humanInLoop) ? this.config.humanInLoop : false);
|
|
143
|
+
}
|
|
144
|
+
// Routing
|
|
145
|
+
if (isFeatureEnabled(this.config.routing)) {
|
|
146
|
+
this.routing = new RoutingManager(this.config.routing);
|
|
147
|
+
}
|
|
148
|
+
// Multi-Agent (Lesson 17)
|
|
149
|
+
if (isFeatureEnabled(this.config.multiAgent)) {
|
|
150
|
+
const roles = (this.config.multiAgent.roles || []).map((r) => ({
|
|
151
|
+
name: r.name,
|
|
152
|
+
description: r.description,
|
|
153
|
+
systemPrompt: r.systemPrompt,
|
|
154
|
+
capabilities: r.capabilities,
|
|
155
|
+
authority: r.authority,
|
|
156
|
+
model: r.model,
|
|
157
|
+
}));
|
|
158
|
+
this.multiAgent = new MultiAgentManager(this.provider, Array.from(this.tools.values()), roles);
|
|
159
|
+
}
|
|
160
|
+
// ReAct (Lesson 18)
|
|
161
|
+
if (isFeatureEnabled(this.config.react)) {
|
|
162
|
+
this.react = new ReActManager(this.provider, Array.from(this.tools.values()), {
|
|
163
|
+
maxSteps: this.config.react.maxSteps,
|
|
164
|
+
stopOnAnswer: this.config.react.stopOnAnswer,
|
|
165
|
+
includeReasoning: this.config.react.includeReasoning,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
// Execution Policies (Lesson 23)
|
|
169
|
+
if (isFeatureEnabled(this.config.executionPolicy)) {
|
|
170
|
+
this.executionPolicy = new ExecutionPolicyManager({
|
|
171
|
+
defaultPolicy: this.config.executionPolicy.defaultPolicy,
|
|
172
|
+
toolPolicies: this.config.executionPolicy.toolPolicies,
|
|
173
|
+
intentAware: this.config.executionPolicy.intentAware,
|
|
174
|
+
intentConfidenceThreshold: this.config.executionPolicy.intentConfidenceThreshold,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
// Thread Management (Lesson 24)
|
|
178
|
+
if (isFeatureEnabled(this.config.threads)) {
|
|
179
|
+
this.threadManager = new ThreadManager();
|
|
180
|
+
}
|
|
181
|
+
// Rules System (Lesson 12)
|
|
182
|
+
if (isFeatureEnabled(this.config.rules)) {
|
|
183
|
+
const ruleSources = this.config.rules.sources || DEFAULT_RULE_SOURCES;
|
|
184
|
+
this.rules = new RulesManager({
|
|
185
|
+
enabled: true,
|
|
186
|
+
sources: ruleSources,
|
|
187
|
+
watch: this.config.rules.watch,
|
|
188
|
+
});
|
|
189
|
+
// Load rules asynchronously - tracked for ensureReady()
|
|
190
|
+
this.initPromises.push(this.rules.loadRules().catch(err => {
|
|
191
|
+
console.warn('[ProductionAgent] Failed to load rules:', err);
|
|
192
|
+
}));
|
|
193
|
+
}
|
|
194
|
+
// Economics System (Token Budget) - always enabled
|
|
195
|
+
this.economics = new ExecutionEconomicsManager({
|
|
196
|
+
...STANDARD_BUDGET,
|
|
197
|
+
// Use maxIterations from config as absolute safety cap
|
|
198
|
+
maxIterations: this.config.maxIterations,
|
|
199
|
+
targetIterations: Math.min(20, this.config.maxIterations),
|
|
200
|
+
});
|
|
201
|
+
// Agent Registry - always enabled for subagent support
|
|
202
|
+
this.agentRegistry = new AgentRegistry();
|
|
203
|
+
// Load user agents asynchronously - tracked for ensureReady()
|
|
204
|
+
this.initPromises.push(this.agentRegistry.loadUserAgents().catch(err => {
|
|
205
|
+
console.warn('[ProductionAgent] Failed to load user agents:', err);
|
|
206
|
+
}));
|
|
207
|
+
// Register spawn_agent tool so LLM can delegate to subagents
|
|
208
|
+
const boundSpawnTool = createBoundSpawnAgentTool((name, task) => this.spawnAgent(name, task));
|
|
209
|
+
this.tools.set(boundSpawnTool.name, boundSpawnTool);
|
|
210
|
+
// Cancellation Support
|
|
211
|
+
if (isFeatureEnabled(this.config.cancellation)) {
|
|
212
|
+
this.cancellation = createCancellationManager();
|
|
213
|
+
// Forward cancellation events
|
|
214
|
+
this.cancellation.subscribe(event => {
|
|
215
|
+
if (event.type === 'cancellation.requested') {
|
|
216
|
+
this.emit({ type: 'cancellation.requested', reason: event.reason });
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
// Resource Monitoring
|
|
221
|
+
if (isFeatureEnabled(this.config.resources)) {
|
|
222
|
+
this.resourceManager = createResourceManager({
|
|
223
|
+
enabled: this.config.resources.enabled,
|
|
224
|
+
maxMemoryMB: this.config.resources.maxMemoryMB,
|
|
225
|
+
maxCpuTimeSec: this.config.resources.maxCpuTimeSec,
|
|
226
|
+
maxConcurrentOps: this.config.resources.maxConcurrentOps,
|
|
227
|
+
warnThreshold: this.config.resources.warnThreshold,
|
|
228
|
+
criticalThreshold: this.config.resources.criticalThreshold,
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
// LSP (Language Server Protocol) Support
|
|
232
|
+
if (isFeatureEnabled(this.config.lsp)) {
|
|
233
|
+
this.lspManager = createLSPManager({
|
|
234
|
+
enabled: this.config.lsp.enabled,
|
|
235
|
+
autoDetect: this.config.lsp.autoDetect,
|
|
236
|
+
servers: this.config.lsp.servers,
|
|
237
|
+
timeout: this.config.lsp.timeout,
|
|
238
|
+
});
|
|
239
|
+
// Auto-start is done lazily on first use to avoid startup delays
|
|
240
|
+
}
|
|
241
|
+
// Semantic Cache Support
|
|
242
|
+
if (isFeatureEnabled(this.config.semanticCache)) {
|
|
243
|
+
this.semanticCache = createSemanticCacheManager({
|
|
244
|
+
enabled: this.config.semanticCache.enabled,
|
|
245
|
+
threshold: this.config.semanticCache.threshold,
|
|
246
|
+
maxSize: this.config.semanticCache.maxSize,
|
|
247
|
+
ttl: this.config.semanticCache.ttl,
|
|
248
|
+
});
|
|
249
|
+
// Forward cache events
|
|
250
|
+
this.semanticCache.subscribe(event => {
|
|
251
|
+
if (event.type === 'cache.hit') {
|
|
252
|
+
this.emit({ type: 'cache.hit', query: event.query, similarity: event.similarity });
|
|
253
|
+
}
|
|
254
|
+
else if (event.type === 'cache.miss') {
|
|
255
|
+
this.emit({ type: 'cache.miss', query: event.query });
|
|
256
|
+
}
|
|
257
|
+
else if (event.type === 'cache.set') {
|
|
258
|
+
this.emit({ type: 'cache.set', query: event.query });
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
// Skills Support
|
|
263
|
+
if (isFeatureEnabled(this.config.skills)) {
|
|
264
|
+
this.skillManager = createSkillManager({
|
|
265
|
+
enabled: this.config.skills.enabled,
|
|
266
|
+
directories: this.config.skills.directories,
|
|
267
|
+
loadBuiltIn: this.config.skills.loadBuiltIn,
|
|
268
|
+
autoActivate: this.config.skills.autoActivate,
|
|
269
|
+
});
|
|
270
|
+
// Load skills asynchronously - tracked for ensureReady()
|
|
271
|
+
this.initPromises.push(this.skillManager.loadSkills()
|
|
272
|
+
.then(() => { }) // Convert to void
|
|
273
|
+
.catch(err => {
|
|
274
|
+
console.warn('[ProductionAgent] Failed to load skills:', err);
|
|
275
|
+
}));
|
|
276
|
+
}
|
|
277
|
+
// Context Engineering (Manus-inspired tricks P, Q, R, S, T)
|
|
278
|
+
// Always enabled - these are performance optimizations
|
|
279
|
+
this.contextEngineering = createContextEngineering({
|
|
280
|
+
enableCacheOptimization: true,
|
|
281
|
+
enableRecitation: true,
|
|
282
|
+
enableReversibleCompaction: true,
|
|
283
|
+
enableFailureTracking: true,
|
|
284
|
+
enableDiversity: false, // Off by default - can cause unexpected behavior
|
|
285
|
+
staticPrefix: this.config.systemPrompt,
|
|
286
|
+
recitationFrequency: 5,
|
|
287
|
+
maxFailures: 30,
|
|
288
|
+
maxReferences: 50,
|
|
289
|
+
});
|
|
290
|
+
// Codebase Context - intelligent code selection for context management
|
|
291
|
+
// Analyzes repo structure and selects relevant code within token budgets
|
|
292
|
+
if (this.config.codebaseContext !== false) {
|
|
293
|
+
const codebaseConfig = typeof this.config.codebaseContext === 'object'
|
|
294
|
+
? this.config.codebaseContext
|
|
295
|
+
: {};
|
|
296
|
+
this.codebaseContext = createCodebaseContext({
|
|
297
|
+
root: codebaseConfig.root ?? process.cwd(),
|
|
298
|
+
includePatterns: codebaseConfig.includePatterns,
|
|
299
|
+
excludePatterns: codebaseConfig.excludePatterns,
|
|
300
|
+
maxFileSize: codebaseConfig.maxFileSize ?? 100 * 1024, // 100KB
|
|
301
|
+
tokensPerChar: 0.25,
|
|
302
|
+
analyzeDependencies: true,
|
|
303
|
+
cacheResults: true,
|
|
304
|
+
cacheTTL: 5 * 60 * 1000, // 5 minutes
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
// Forward context engineering events
|
|
308
|
+
this.contextEngineering.on(event => {
|
|
309
|
+
switch (event.type) {
|
|
310
|
+
case 'failure.recorded':
|
|
311
|
+
this.observability?.logger?.warn('Failure recorded', {
|
|
312
|
+
action: event.failure.action,
|
|
313
|
+
category: event.failure.category,
|
|
314
|
+
});
|
|
315
|
+
break;
|
|
316
|
+
case 'failure.pattern':
|
|
317
|
+
this.observability?.logger?.warn('Failure pattern detected', {
|
|
318
|
+
type: event.pattern.type,
|
|
319
|
+
description: event.pattern.description,
|
|
320
|
+
});
|
|
321
|
+
this.emit({ type: 'error', error: `Pattern: ${event.pattern.description}` });
|
|
322
|
+
break;
|
|
323
|
+
case 'recitation.injected':
|
|
324
|
+
this.observability?.logger?.debug('Recitation injected', {
|
|
325
|
+
iteration: event.iteration,
|
|
326
|
+
});
|
|
327
|
+
break;
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Ensure all async initialization is complete before running.
|
|
333
|
+
* Call this at the start of run() to prevent race conditions.
|
|
334
|
+
*/
|
|
335
|
+
async ensureReady() {
|
|
336
|
+
if (this.initComplete) {
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
if (this.initPromises.length > 0) {
|
|
340
|
+
await Promise.all(this.initPromises);
|
|
341
|
+
}
|
|
342
|
+
this.initComplete = true;
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Run the agent on a task.
|
|
346
|
+
*/
|
|
347
|
+
async run(task) {
|
|
348
|
+
// Ensure all integrations are ready before running
|
|
349
|
+
await this.ensureReady();
|
|
350
|
+
const startTime = Date.now();
|
|
351
|
+
// Create cancellation context if enabled
|
|
352
|
+
const cancellationConfig = isFeatureEnabled(this.config.cancellation) ? this.config.cancellation : null;
|
|
353
|
+
const cancellationToken = this.cancellation?.createContext(cancellationConfig?.defaultTimeout || undefined);
|
|
354
|
+
// Start tracing
|
|
355
|
+
const traceId = this.observability?.tracer?.startTrace('agent.run') || `trace-${Date.now()}`;
|
|
356
|
+
this.emit({ type: 'start', task, traceId });
|
|
357
|
+
this.observability?.logger?.info('Agent started', { task });
|
|
358
|
+
// Lesson 26: Start trace capture session
|
|
359
|
+
const traceSessionId = `session-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
360
|
+
await this.traceCollector?.startSession(traceSessionId, task, this.config.model || 'default', {});
|
|
361
|
+
try {
|
|
362
|
+
// Check for cancellation before starting
|
|
363
|
+
cancellationToken?.throwIfCancellationRequested();
|
|
364
|
+
// Check if planning is needed
|
|
365
|
+
if (this.planning?.shouldPlan(task)) {
|
|
366
|
+
await this.createAndExecutePlan(task);
|
|
367
|
+
}
|
|
368
|
+
else {
|
|
369
|
+
await this.executeDirectly(task);
|
|
370
|
+
}
|
|
371
|
+
// Get final response - find the LAST assistant message (not just check if last message is assistant)
|
|
372
|
+
const assistantMessages = this.state.messages.filter(m => m.role === 'assistant');
|
|
373
|
+
const lastAssistantMessage = assistantMessages[assistantMessages.length - 1];
|
|
374
|
+
const response = typeof lastAssistantMessage?.content === 'string'
|
|
375
|
+
? lastAssistantMessage.content
|
|
376
|
+
: '';
|
|
377
|
+
// Finalize
|
|
378
|
+
const duration = Date.now() - startTime;
|
|
379
|
+
this.state.metrics.duration = duration;
|
|
380
|
+
await this.observability?.tracer?.endTrace();
|
|
381
|
+
const result = {
|
|
382
|
+
success: true,
|
|
383
|
+
response,
|
|
384
|
+
metrics: this.getMetrics(),
|
|
385
|
+
messages: this.state.messages,
|
|
386
|
+
traceId,
|
|
387
|
+
plan: this.state.plan,
|
|
388
|
+
};
|
|
389
|
+
this.emit({ type: 'complete', result });
|
|
390
|
+
this.observability?.logger?.info('Agent completed', { duration, success: true });
|
|
391
|
+
// Lesson 26: End trace capture session
|
|
392
|
+
await this.traceCollector?.endSession({ success: true, output: response });
|
|
393
|
+
return result;
|
|
394
|
+
}
|
|
395
|
+
catch (err) {
|
|
396
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
397
|
+
// Handle cancellation specially
|
|
398
|
+
if (isCancellationError(err)) {
|
|
399
|
+
const cleanupStart = Date.now();
|
|
400
|
+
this.cancellation?.disposeContext();
|
|
401
|
+
const cleanupDuration = Date.now() - cleanupStart;
|
|
402
|
+
this.emit({ type: 'cancellation.completed', cleanupDuration });
|
|
403
|
+
this.observability?.logger?.info('Agent cancelled', { reason: error.message, cleanupDuration });
|
|
404
|
+
// Lesson 26: End trace capture session on cancellation
|
|
405
|
+
await this.traceCollector?.endSession({ success: false, failureReason: `Cancelled: ${error.message}` });
|
|
406
|
+
return {
|
|
407
|
+
success: false,
|
|
408
|
+
response: '',
|
|
409
|
+
error: `Cancelled: ${error.message}`,
|
|
410
|
+
metrics: this.getMetrics(),
|
|
411
|
+
messages: this.state.messages,
|
|
412
|
+
traceId,
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
this.observability?.tracer?.recordError(error);
|
|
416
|
+
await this.observability?.tracer?.endTrace();
|
|
417
|
+
this.emit({ type: 'error', error: error.message });
|
|
418
|
+
this.observability?.logger?.error('Agent failed', { error: error.message });
|
|
419
|
+
// Lesson 26: End trace capture session on error
|
|
420
|
+
await this.traceCollector?.endSession({ success: false, failureReason: error.message });
|
|
421
|
+
return {
|
|
422
|
+
success: false,
|
|
423
|
+
response: '',
|
|
424
|
+
error: error.message,
|
|
425
|
+
metrics: this.getMetrics(),
|
|
426
|
+
messages: this.state.messages,
|
|
427
|
+
traceId,
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
finally {
|
|
431
|
+
// Dispose cancellation context on completion
|
|
432
|
+
this.cancellation?.disposeContext();
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Create and execute a plan for complex tasks.
|
|
437
|
+
*/
|
|
438
|
+
async createAndExecutePlan(task) {
|
|
439
|
+
this.observability?.logger?.info('Creating plan for complex task');
|
|
440
|
+
const plan = await this.planning.createPlan(task, this.provider);
|
|
441
|
+
this.state.plan = plan;
|
|
442
|
+
this.emit({ type: 'planning', plan });
|
|
443
|
+
// Execute each task in the plan
|
|
444
|
+
while (!this.planning.isPlanComplete()) {
|
|
445
|
+
const currentTask = this.planning.getNextTask();
|
|
446
|
+
if (!currentTask)
|
|
447
|
+
break;
|
|
448
|
+
this.planning.startTask(currentTask.id);
|
|
449
|
+
this.emit({ type: 'task.start', task: currentTask });
|
|
450
|
+
try {
|
|
451
|
+
await this.executeDirectly(currentTask.description);
|
|
452
|
+
this.planning.completeTask(currentTask.id);
|
|
453
|
+
this.emit({ type: 'task.complete', task: currentTask });
|
|
454
|
+
}
|
|
455
|
+
catch (err) {
|
|
456
|
+
this.planning.failTask(currentTask.id);
|
|
457
|
+
this.observability?.logger?.warn('Plan task failed', { taskId: currentTask.id });
|
|
458
|
+
// Continue with other tasks if possible
|
|
459
|
+
}
|
|
460
|
+
// Check iteration limit
|
|
461
|
+
if (this.state.iteration >= this.config.maxIterations) {
|
|
462
|
+
this.observability?.logger?.warn('Max iterations reached');
|
|
463
|
+
break;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Execute a task directly without planning.
|
|
469
|
+
*/
|
|
470
|
+
async executeDirectly(task) {
|
|
471
|
+
// Build messages
|
|
472
|
+
const messages = this.buildMessages(task);
|
|
473
|
+
// Reset economics for new task
|
|
474
|
+
this.economics?.reset();
|
|
475
|
+
// Reflection configuration
|
|
476
|
+
const reflectionConfig = this.config.reflection;
|
|
477
|
+
const reflectionEnabled = isFeatureEnabled(reflectionConfig);
|
|
478
|
+
const autoReflect = reflectionEnabled && reflectionConfig.autoReflect;
|
|
479
|
+
const maxReflectionAttempts = reflectionEnabled
|
|
480
|
+
? (reflectionConfig.maxAttempts || 3)
|
|
481
|
+
: 1;
|
|
482
|
+
const confidenceThreshold = reflectionEnabled
|
|
483
|
+
? (reflectionConfig.confidenceThreshold || 0.8)
|
|
484
|
+
: 0.8;
|
|
485
|
+
let reflectionAttempt = 0;
|
|
486
|
+
let lastResponse = '';
|
|
487
|
+
// Outer loop for reflection (if enabled)
|
|
488
|
+
while (reflectionAttempt < maxReflectionAttempts) {
|
|
489
|
+
reflectionAttempt++;
|
|
490
|
+
// Agent loop - now uses economics-based budget checking
|
|
491
|
+
while (true) {
|
|
492
|
+
this.state.iteration++;
|
|
493
|
+
// =======================================================================
|
|
494
|
+
// CANCELLATION CHECK
|
|
495
|
+
// =======================================================================
|
|
496
|
+
if (this.cancellation?.isCancelled) {
|
|
497
|
+
this.cancellation.token.throwIfCancellationRequested();
|
|
498
|
+
}
|
|
499
|
+
// =======================================================================
|
|
500
|
+
// RESOURCE CHECK - system resource limits
|
|
501
|
+
// =======================================================================
|
|
502
|
+
if (this.resourceManager) {
|
|
503
|
+
const resourceCheck = this.resourceManager.check();
|
|
504
|
+
if (!resourceCheck.canContinue) {
|
|
505
|
+
this.observability?.logger?.warn('Resource limit reached', {
|
|
506
|
+
status: resourceCheck.status,
|
|
507
|
+
message: resourceCheck.message,
|
|
508
|
+
});
|
|
509
|
+
this.emit({ type: 'error', error: resourceCheck.message || 'Resource limit exceeded' });
|
|
510
|
+
break;
|
|
511
|
+
}
|
|
512
|
+
// Log warnings for elevated usage
|
|
513
|
+
if (resourceCheck.status === 'warning' || resourceCheck.status === 'critical') {
|
|
514
|
+
this.observability?.logger?.info(`Resource status: ${resourceCheck.status}`, {
|
|
515
|
+
message: resourceCheck.message,
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
// =======================================================================
|
|
520
|
+
// ECONOMICS CHECK (Token Budget) - replaces hard iteration limit
|
|
521
|
+
// =======================================================================
|
|
522
|
+
if (this.economics) {
|
|
523
|
+
const budgetCheck = this.economics.checkBudget();
|
|
524
|
+
if (!budgetCheck.canContinue) {
|
|
525
|
+
// Hard limit reached
|
|
526
|
+
this.observability?.logger?.warn('Budget limit reached', {
|
|
527
|
+
reason: budgetCheck.reason,
|
|
528
|
+
budgetType: budgetCheck.budgetType,
|
|
529
|
+
});
|
|
530
|
+
// Emit appropriate event
|
|
531
|
+
if (budgetCheck.budgetType === 'iterations') {
|
|
532
|
+
this.emit({ type: 'error', error: `Max iterations reached (${this.state.iteration})` });
|
|
533
|
+
}
|
|
534
|
+
else {
|
|
535
|
+
this.emit({ type: 'error', error: budgetCheck.reason || 'Budget exceeded' });
|
|
536
|
+
}
|
|
537
|
+
break;
|
|
538
|
+
}
|
|
539
|
+
// Check for soft limits and potential extension
|
|
540
|
+
if (budgetCheck.isSoftLimit && budgetCheck.suggestedAction === 'request_extension') {
|
|
541
|
+
this.observability?.logger?.info('Approaching budget limit', {
|
|
542
|
+
reason: budgetCheck.reason,
|
|
543
|
+
percentUsed: budgetCheck.percentUsed,
|
|
544
|
+
});
|
|
545
|
+
// Could request extension here if handler is set
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
else {
|
|
549
|
+
// Fallback to simple iteration check if economics not available
|
|
550
|
+
if (this.state.iteration >= this.config.maxIterations) {
|
|
551
|
+
this.observability?.logger?.warn('Max iterations reached');
|
|
552
|
+
break;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
// =======================================================================
|
|
556
|
+
// RECITATION INJECTION (Trick Q) - Combat "lost in middle" attention
|
|
557
|
+
// =======================================================================
|
|
558
|
+
if (this.contextEngineering) {
|
|
559
|
+
if (process.env.DEBUG_LLM) {
|
|
560
|
+
if (process.env.DEBUG)
|
|
561
|
+
console.log(`[recitation] Before: ${messages.length} messages`);
|
|
562
|
+
}
|
|
563
|
+
const enrichedMessages = this.contextEngineering.injectRecitation(messages, {
|
|
564
|
+
goal: task,
|
|
565
|
+
plan: this.state.plan ? {
|
|
566
|
+
description: this.state.plan.goal || task,
|
|
567
|
+
tasks: this.state.plan.tasks.map(t => ({
|
|
568
|
+
id: t.id,
|
|
569
|
+
description: t.description,
|
|
570
|
+
status: t.status,
|
|
571
|
+
})),
|
|
572
|
+
currentTaskIndex: this.state.plan.tasks.findIndex(t => t.status === 'in_progress'),
|
|
573
|
+
} : undefined,
|
|
574
|
+
activeFiles: this.economics?.getProgress().filesModified
|
|
575
|
+
? [`${this.economics.getProgress().filesModified} files modified`]
|
|
576
|
+
: undefined,
|
|
577
|
+
recentErrors: this.contextEngineering.getFailureInsights().slice(0, 2),
|
|
578
|
+
});
|
|
579
|
+
if (process.env.DEBUG_LLM) {
|
|
580
|
+
if (process.env.DEBUG)
|
|
581
|
+
console.log(`[recitation] After: ${enrichedMessages?.length ?? 'null/undefined'} messages`);
|
|
582
|
+
}
|
|
583
|
+
// Only replace if we got a DIFFERENT array back (avoid clearing same reference)
|
|
584
|
+
// When no injection needed, injectRecitation returns the same array reference
|
|
585
|
+
if (enrichedMessages && enrichedMessages !== messages && enrichedMessages.length > 0) {
|
|
586
|
+
messages.length = 0;
|
|
587
|
+
messages.push(...enrichedMessages);
|
|
588
|
+
}
|
|
589
|
+
else if (!enrichedMessages || enrichedMessages.length === 0) {
|
|
590
|
+
console.warn('[executeDirectly] Recitation returned empty/null messages, keeping original');
|
|
591
|
+
}
|
|
592
|
+
// If enrichedMessages === messages, we don't need to do anything (same reference)
|
|
593
|
+
// Update recitation frequency based on context size
|
|
594
|
+
const contextTokens = messages.reduce((sum, m) => sum + (m.content?.length || 0) / 4, 0);
|
|
595
|
+
this.contextEngineering.updateRecitationFrequency(contextTokens);
|
|
596
|
+
}
|
|
597
|
+
// =======================================================================
|
|
598
|
+
// FAILURE CONTEXT INJECTION (Trick S) - Learn from mistakes
|
|
599
|
+
// =======================================================================
|
|
600
|
+
if (this.contextEngineering) {
|
|
601
|
+
const failureContext = this.contextEngineering.getFailureContext(5);
|
|
602
|
+
if (failureContext) {
|
|
603
|
+
// Insert failure context before the last user message
|
|
604
|
+
// (Using reverse iteration for ES2022 compatibility)
|
|
605
|
+
let lastUserIdx = -1;
|
|
606
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
607
|
+
if (messages[i].role === 'user') {
|
|
608
|
+
lastUserIdx = i;
|
|
609
|
+
break;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
if (lastUserIdx > 0) {
|
|
613
|
+
messages.splice(lastUserIdx, 0, {
|
|
614
|
+
role: 'system',
|
|
615
|
+
content: failureContext,
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
// Make LLM call
|
|
621
|
+
const response = await this.callLLM(messages);
|
|
622
|
+
// Record LLM usage for economics
|
|
623
|
+
if (this.economics && response.usage) {
|
|
624
|
+
this.economics.recordLLMUsage(response.usage.inputTokens, response.usage.outputTokens, this.config.model, response.usage.cost // Use actual cost from provider when available
|
|
625
|
+
);
|
|
626
|
+
}
|
|
627
|
+
// Add assistant message
|
|
628
|
+
const assistantMessage = {
|
|
629
|
+
role: 'assistant',
|
|
630
|
+
content: response.content,
|
|
631
|
+
toolCalls: response.toolCalls,
|
|
632
|
+
};
|
|
633
|
+
messages.push(assistantMessage);
|
|
634
|
+
this.state.messages.push(assistantMessage);
|
|
635
|
+
lastResponse = response.content;
|
|
636
|
+
// Check for tool calls
|
|
637
|
+
if (!response.toolCalls || response.toolCalls.length === 0) {
|
|
638
|
+
// No tool calls, agent is done - compact tool outputs to save context
|
|
639
|
+
// The model has "consumed" the tool outputs and produced a response,
|
|
640
|
+
// so we can replace verbose outputs with compact summaries
|
|
641
|
+
this.compactToolOutputs();
|
|
642
|
+
break;
|
|
643
|
+
}
|
|
644
|
+
// Execute tool calls
|
|
645
|
+
const toolResults = await this.executeToolCalls(response.toolCalls);
|
|
646
|
+
// Record tool calls for economics/progress tracking
|
|
647
|
+
for (let i = 0; i < response.toolCalls.length; i++) {
|
|
648
|
+
const toolCall = response.toolCalls[i];
|
|
649
|
+
const result = toolResults[i];
|
|
650
|
+
this.economics?.recordToolCall(toolCall.name, toolCall.arguments, result?.result);
|
|
651
|
+
}
|
|
652
|
+
// Add tool results to messages (with truncation and proactive budget management)
|
|
653
|
+
const MAX_TOOL_OUTPUT_CHARS = 8000; // ~2000 tokens max per tool output
|
|
654
|
+
// =======================================================================
|
|
655
|
+
// PROACTIVE BUDGET CHECK - compact BEFORE we overflow, not after
|
|
656
|
+
// =======================================================================
|
|
657
|
+
if (this.economics) {
|
|
658
|
+
const currentUsage = this.economics.getUsage();
|
|
659
|
+
const budget = this.economics.getBudget();
|
|
660
|
+
const percentUsed = (currentUsage.tokens / budget.maxTokens) * 100;
|
|
661
|
+
// If we're at 70%+ of budget, proactively compact to make room
|
|
662
|
+
if (percentUsed >= 70) {
|
|
663
|
+
this.observability?.logger?.info('Proactive compaction triggered', {
|
|
664
|
+
percentUsed: Math.round(percentUsed),
|
|
665
|
+
currentTokens: currentUsage.tokens,
|
|
666
|
+
maxTokens: budget.maxTokens,
|
|
667
|
+
});
|
|
668
|
+
this.compactToolOutputs();
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
for (const result of toolResults) {
|
|
672
|
+
let content = typeof result.result === 'string' ? result.result : stableStringify(result.result);
|
|
673
|
+
// Truncate long outputs to save context
|
|
674
|
+
if (content.length > MAX_TOOL_OUTPUT_CHARS) {
|
|
675
|
+
content = content.slice(0, MAX_TOOL_OUTPUT_CHARS) + `\n\n... [truncated ${content.length - MAX_TOOL_OUTPUT_CHARS} chars]`;
|
|
676
|
+
}
|
|
677
|
+
// =======================================================================
|
|
678
|
+
// ESTIMATE if adding this result would exceed budget
|
|
679
|
+
// =======================================================================
|
|
680
|
+
if (this.economics) {
|
|
681
|
+
const estimatedNewTokens = Math.ceil(content.length / 4); // ~4 chars per token
|
|
682
|
+
const currentContextTokens = this.estimateContextTokens(messages);
|
|
683
|
+
const budget = this.economics.getBudget();
|
|
684
|
+
// Check if adding this would push us over the hard limit
|
|
685
|
+
if (currentContextTokens + estimatedNewTokens > budget.maxTokens * 0.95) {
|
|
686
|
+
this.observability?.logger?.warn('Skipping tool result to stay within budget', {
|
|
687
|
+
toolCallId: result.callId,
|
|
688
|
+
estimatedTokens: estimatedNewTokens,
|
|
689
|
+
currentContext: currentContextTokens,
|
|
690
|
+
limit: budget.maxTokens,
|
|
691
|
+
});
|
|
692
|
+
// Add a truncated placeholder instead
|
|
693
|
+
const toolMessage = {
|
|
694
|
+
role: 'tool',
|
|
695
|
+
content: `[Result omitted to stay within token budget. Original size: ${content.length} chars]`,
|
|
696
|
+
toolCallId: result.callId,
|
|
697
|
+
};
|
|
698
|
+
messages.push(toolMessage);
|
|
699
|
+
this.state.messages.push(toolMessage);
|
|
700
|
+
continue;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
const toolMessage = {
|
|
704
|
+
role: 'tool',
|
|
705
|
+
content,
|
|
706
|
+
toolCallId: result.callId,
|
|
707
|
+
};
|
|
708
|
+
messages.push(toolMessage);
|
|
709
|
+
this.state.messages.push(toolMessage);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
// =======================================================================
|
|
713
|
+
// REFLECTION (Lesson 16)
|
|
714
|
+
// =======================================================================
|
|
715
|
+
if (autoReflect && this.planning && reflectionAttempt < maxReflectionAttempts) {
|
|
716
|
+
this.emit({ type: 'reflection', attempt: reflectionAttempt, satisfied: false });
|
|
717
|
+
const reflectionResult = await this.planning.reflect(task, lastResponse, this.provider);
|
|
718
|
+
this.state.metrics.reflectionAttempts = reflectionAttempt;
|
|
719
|
+
if (reflectionResult.satisfied && reflectionResult.confidence >= confidenceThreshold) {
|
|
720
|
+
// Output is satisfactory
|
|
721
|
+
this.emit({ type: 'reflection', attempt: reflectionAttempt, satisfied: true });
|
|
722
|
+
break;
|
|
723
|
+
}
|
|
724
|
+
// Not satisfied - add feedback and continue
|
|
725
|
+
const feedbackMessage = {
|
|
726
|
+
role: 'user',
|
|
727
|
+
content: `[Reflection feedback]\nThe previous output needs improvement:\n- Critique: ${reflectionResult.critique}\n- Suggestions: ${reflectionResult.suggestions.join(', ')}\n\nPlease improve the output.`,
|
|
728
|
+
};
|
|
729
|
+
messages.push(feedbackMessage);
|
|
730
|
+
this.state.messages.push(feedbackMessage);
|
|
731
|
+
this.observability?.logger?.info('Reflection not satisfied, retrying', {
|
|
732
|
+
attempt: reflectionAttempt,
|
|
733
|
+
confidence: reflectionResult.confidence,
|
|
734
|
+
critique: reflectionResult.critique,
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
else {
|
|
738
|
+
// No reflection or already satisfied
|
|
739
|
+
break;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
// Store conversation in memory
|
|
743
|
+
this.memory?.storeConversation(this.state.messages);
|
|
744
|
+
this.updateMemoryStats();
|
|
745
|
+
}
|
|
746
|
+
/**
|
|
747
|
+
* Build messages for LLM call.
|
|
748
|
+
*
|
|
749
|
+
* Uses cache-aware system prompt building (Trick P) when contextEngineering
|
|
750
|
+
* is available, ensuring static content is ordered for optimal KV-cache reuse.
|
|
751
|
+
*/
|
|
752
|
+
buildMessages(task) {
|
|
753
|
+
const messages = [];
|
|
754
|
+
// Gather all context components
|
|
755
|
+
const rulesContent = this.rules?.getRulesContent() ?? '';
|
|
756
|
+
const skillsPrompt = this.skillManager?.getActiveSkillsPrompt() ?? '';
|
|
757
|
+
const memoryContext = this.memory?.getContextStrings(task) ?? [];
|
|
758
|
+
// Budget-aware codebase context selection
|
|
759
|
+
let codebaseContextStr = '';
|
|
760
|
+
if (this.codebaseContext) {
|
|
761
|
+
// Calculate available budget for codebase context
|
|
762
|
+
// Reserve tokens for: rules (~2000), tools (~2500), memory (~1000), conversation (~5000)
|
|
763
|
+
const reservedTokens = 10500;
|
|
764
|
+
const maxContextTokens = (this.config.maxContextTokens ?? 80000) - reservedTokens;
|
|
765
|
+
const codebaseBudget = Math.min(maxContextTokens * 0.3, 15000); // Up to 30% or 15K tokens
|
|
766
|
+
try {
|
|
767
|
+
// Use synchronous cache if available, otherwise skip
|
|
768
|
+
const repoMap = this.codebaseContext.getRepoMap();
|
|
769
|
+
if (repoMap) {
|
|
770
|
+
const selection = this.selectRelevantCodeSync(task, codebaseBudget);
|
|
771
|
+
if (selection.chunks.length > 0) {
|
|
772
|
+
codebaseContextStr = buildContextFromChunks(selection.chunks, {
|
|
773
|
+
includeFilePaths: true,
|
|
774
|
+
includeSeparators: true,
|
|
775
|
+
maxTotalTokens: codebaseBudget,
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
catch {
|
|
781
|
+
// Codebase analysis not ready yet - skip for this call
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
// Build tool descriptions
|
|
785
|
+
let toolDescriptions = '';
|
|
786
|
+
if (this.tools.size > 0) {
|
|
787
|
+
const toolLines = [];
|
|
788
|
+
for (const tool of this.tools.values()) {
|
|
789
|
+
toolLines.push(`- ${tool.name}: ${tool.description}`);
|
|
790
|
+
}
|
|
791
|
+
toolDescriptions = toolLines.join('\n');
|
|
792
|
+
}
|
|
793
|
+
// Add MCP tool summaries
|
|
794
|
+
if (this.config.mcpToolSummaries && this.config.mcpToolSummaries.length > 0) {
|
|
795
|
+
const mcpLines = this.config.mcpToolSummaries.map(s => `- ${s.name}: ${s.description}`);
|
|
796
|
+
if (toolDescriptions) {
|
|
797
|
+
toolDescriptions += '\n\nMCP tools (call directly, they auto-load):\n' + mcpLines.join('\n');
|
|
798
|
+
}
|
|
799
|
+
else {
|
|
800
|
+
toolDescriptions = 'MCP tools (call directly, they auto-load):\n' + mcpLines.join('\n');
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
// Build system prompt using cache-aware builder if available (Trick P)
|
|
804
|
+
let systemPrompt;
|
|
805
|
+
// Combine memory and codebase context
|
|
806
|
+
const combinedContext = [
|
|
807
|
+
...(memoryContext.length > 0 ? memoryContext : []),
|
|
808
|
+
...(codebaseContextStr ? [`\n## Relevant Code\n${codebaseContextStr}`] : []),
|
|
809
|
+
].join('\n');
|
|
810
|
+
if (this.contextEngineering) {
|
|
811
|
+
// Use cache-optimized prompt builder - orders sections for KV-cache reuse:
|
|
812
|
+
// static prefix -> rules -> tools -> memory/codebase -> dynamic
|
|
813
|
+
systemPrompt = this.contextEngineering.buildSystemPrompt({
|
|
814
|
+
rules: rulesContent + (skillsPrompt ? '\n\n' + skillsPrompt : ''),
|
|
815
|
+
tools: toolDescriptions,
|
|
816
|
+
memory: combinedContext.length > 0 ? combinedContext : undefined,
|
|
817
|
+
dynamic: {
|
|
818
|
+
mode: this.modeManager?.getMode() ?? 'default',
|
|
819
|
+
},
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
else {
|
|
823
|
+
// Fallback: manual concatenation (original behavior)
|
|
824
|
+
systemPrompt = this.config.systemPrompt;
|
|
825
|
+
if (rulesContent)
|
|
826
|
+
systemPrompt += '\n\n' + rulesContent;
|
|
827
|
+
if (skillsPrompt)
|
|
828
|
+
systemPrompt += skillsPrompt;
|
|
829
|
+
if (combinedContext.length > 0) {
|
|
830
|
+
systemPrompt += '\n\nRelevant context:\n' + combinedContext;
|
|
831
|
+
}
|
|
832
|
+
if (toolDescriptions) {
|
|
833
|
+
systemPrompt += '\n\nAvailable tools:\n' + toolDescriptions;
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
// Safety check: ensure system prompt is not empty
|
|
837
|
+
if (!systemPrompt || systemPrompt.trim().length === 0) {
|
|
838
|
+
console.warn('[buildMessages] Warning: Empty system prompt detected, using fallback');
|
|
839
|
+
systemPrompt = this.config.systemPrompt || 'You are a helpful AI assistant.';
|
|
840
|
+
}
|
|
841
|
+
messages.push({ role: 'system', content: systemPrompt });
|
|
842
|
+
// Add existing conversation
|
|
843
|
+
for (const msg of this.state.messages) {
|
|
844
|
+
if (msg.role !== 'system') {
|
|
845
|
+
messages.push(msg);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
// Add current task
|
|
849
|
+
messages.push({ role: 'user', content: task });
|
|
850
|
+
return messages;
|
|
851
|
+
}
|
|
852
|
+
/**
|
|
853
|
+
* Call the LLM with routing and observability.
|
|
854
|
+
*/
|
|
855
|
+
async callLLM(messages) {
|
|
856
|
+
const spanId = this.observability?.tracer?.startSpan('llm.call');
|
|
857
|
+
this.emit({ type: 'llm.start', model: this.config.model || 'default' });
|
|
858
|
+
// Emit context insight for verbose feedback
|
|
859
|
+
const estimatedTokens = messages.reduce((sum, m) => {
|
|
860
|
+
const content = typeof m.content === 'string' ? m.content : JSON.stringify(m.content);
|
|
861
|
+
return sum + Math.ceil(content.length / 3.5); // ~3.5 chars per token estimate
|
|
862
|
+
}, 0);
|
|
863
|
+
// Use context window size, not output token limit
|
|
864
|
+
const contextLimit = this.config.maxContextTokens || 100000;
|
|
865
|
+
this.emit({
|
|
866
|
+
type: 'insight.context',
|
|
867
|
+
currentTokens: estimatedTokens,
|
|
868
|
+
maxTokens: contextLimit,
|
|
869
|
+
messageCount: messages.length,
|
|
870
|
+
percentUsed: Math.round((estimatedTokens / contextLimit) * 100),
|
|
871
|
+
});
|
|
872
|
+
const startTime = Date.now();
|
|
873
|
+
const requestId = `req-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
874
|
+
// Debug: Log message count and structure (helps diagnose API errors)
|
|
875
|
+
if (process.env.DEBUG_LLM) {
|
|
876
|
+
console.log(`[callLLM] Sending ${messages.length} messages:`);
|
|
877
|
+
messages.forEach((m, i) => {
|
|
878
|
+
console.log(` [${i}] ${m.role}: ${m.content?.slice(0, 50)}...`);
|
|
879
|
+
});
|
|
880
|
+
}
|
|
881
|
+
// Validate messages are not empty
|
|
882
|
+
if (!messages || messages.length === 0) {
|
|
883
|
+
throw new Error('No messages to send to LLM');
|
|
884
|
+
}
|
|
885
|
+
// Lesson 26: Record LLM request for tracing
|
|
886
|
+
const model = this.config.model || 'default';
|
|
887
|
+
const provider = this.config.provider?.name || 'unknown';
|
|
888
|
+
this.traceCollector?.record({
|
|
889
|
+
type: 'llm.request',
|
|
890
|
+
data: {
|
|
891
|
+
requestId,
|
|
892
|
+
model,
|
|
893
|
+
provider,
|
|
894
|
+
messages: messages.map(m => ({
|
|
895
|
+
role: m.role,
|
|
896
|
+
content: m.content,
|
|
897
|
+
toolCallId: m.toolCallId,
|
|
898
|
+
toolCalls: m.toolCalls?.map(tc => ({
|
|
899
|
+
id: tc.id,
|
|
900
|
+
name: tc.name,
|
|
901
|
+
arguments: tc.arguments,
|
|
902
|
+
})),
|
|
903
|
+
})),
|
|
904
|
+
tools: Array.from(this.tools.values()).map(t => ({
|
|
905
|
+
name: t.name,
|
|
906
|
+
description: t.description,
|
|
907
|
+
parametersSchema: t.parameters,
|
|
908
|
+
})),
|
|
909
|
+
parameters: {
|
|
910
|
+
maxTokens: this.config.maxTokens,
|
|
911
|
+
temperature: this.config.temperature,
|
|
912
|
+
},
|
|
913
|
+
},
|
|
914
|
+
});
|
|
915
|
+
try {
|
|
916
|
+
let response;
|
|
917
|
+
let actualModel = model;
|
|
918
|
+
// Use routing if enabled
|
|
919
|
+
if (this.routing) {
|
|
920
|
+
const complexity = this.routing.estimateComplexity(messages[messages.length - 1]?.content || '');
|
|
921
|
+
const context = {
|
|
922
|
+
task: messages[messages.length - 1]?.content || '',
|
|
923
|
+
complexity,
|
|
924
|
+
hasTools: this.tools.size > 0,
|
|
925
|
+
hasImages: false,
|
|
926
|
+
taskType: 'general',
|
|
927
|
+
estimatedTokens: messages.reduce((sum, m) => sum + m.content.length / 4, 0),
|
|
928
|
+
};
|
|
929
|
+
const result = await this.routing.executeWithFallback(messages, context);
|
|
930
|
+
response = result.response;
|
|
931
|
+
actualModel = result.model;
|
|
932
|
+
// Emit routing insight
|
|
933
|
+
this.emit({
|
|
934
|
+
type: 'insight.routing',
|
|
935
|
+
model: actualModel,
|
|
936
|
+
reason: actualModel !== model ? 'Routed based on complexity' : 'Default model',
|
|
937
|
+
complexity: complexity <= 0.3 ? 'low' : complexity <= 0.7 ? 'medium' : 'high',
|
|
938
|
+
});
|
|
939
|
+
}
|
|
940
|
+
else {
|
|
941
|
+
response = await this.provider.chat(messages, {
|
|
942
|
+
model: this.config.model,
|
|
943
|
+
tools: Array.from(this.tools.values()),
|
|
944
|
+
});
|
|
945
|
+
}
|
|
946
|
+
const duration = Date.now() - startTime;
|
|
947
|
+
// Lesson 26: Record LLM response for tracing
|
|
948
|
+
this.traceCollector?.record({
|
|
949
|
+
type: 'llm.response',
|
|
950
|
+
data: {
|
|
951
|
+
requestId,
|
|
952
|
+
content: response.content || '',
|
|
953
|
+
toolCalls: response.toolCalls?.map(tc => ({
|
|
954
|
+
id: tc.id,
|
|
955
|
+
name: tc.name,
|
|
956
|
+
arguments: tc.arguments,
|
|
957
|
+
})),
|
|
958
|
+
stopReason: response.stopReason === 'end_turn' ? 'end_turn'
|
|
959
|
+
: response.stopReason === 'tool_use' ? 'tool_use'
|
|
960
|
+
: response.stopReason === 'max_tokens' ? 'max_tokens'
|
|
961
|
+
: 'stop_sequence',
|
|
962
|
+
usage: {
|
|
963
|
+
inputTokens: response.usage?.inputTokens || 0,
|
|
964
|
+
outputTokens: response.usage?.outputTokens || 0,
|
|
965
|
+
cacheReadTokens: response.usage?.cacheReadTokens,
|
|
966
|
+
cacheWriteTokens: response.usage?.cacheWriteTokens,
|
|
967
|
+
cost: response.usage?.cost, // Actual cost from provider (e.g., OpenRouter)
|
|
968
|
+
},
|
|
969
|
+
durationMs: duration,
|
|
970
|
+
},
|
|
971
|
+
});
|
|
972
|
+
// Record metrics
|
|
973
|
+
this.observability?.metrics?.recordLLMCall(response.usage?.inputTokens || 0, response.usage?.outputTokens || 0, duration, actualModel, response.usage?.cost // Actual cost from provider (e.g., OpenRouter)
|
|
974
|
+
);
|
|
975
|
+
this.state.metrics.llmCalls++;
|
|
976
|
+
this.state.metrics.inputTokens += response.usage?.inputTokens || 0;
|
|
977
|
+
this.state.metrics.outputTokens += response.usage?.outputTokens || 0;
|
|
978
|
+
this.state.metrics.totalTokens = this.state.metrics.inputTokens + this.state.metrics.outputTokens;
|
|
979
|
+
this.emit({ type: 'llm.complete', response });
|
|
980
|
+
// Emit token usage insight for verbose feedback
|
|
981
|
+
if (response.usage) {
|
|
982
|
+
this.emit({
|
|
983
|
+
type: 'insight.tokens',
|
|
984
|
+
inputTokens: response.usage.inputTokens,
|
|
985
|
+
outputTokens: response.usage.outputTokens,
|
|
986
|
+
cacheReadTokens: response.usage.cacheReadTokens,
|
|
987
|
+
cacheWriteTokens: response.usage.cacheWriteTokens,
|
|
988
|
+
cost: response.usage.cost,
|
|
989
|
+
model: actualModel,
|
|
990
|
+
});
|
|
991
|
+
}
|
|
992
|
+
this.observability?.tracer?.endSpan(spanId);
|
|
993
|
+
return response;
|
|
994
|
+
}
|
|
995
|
+
catch (err) {
|
|
996
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
997
|
+
this.observability?.tracer?.recordError(error);
|
|
998
|
+
this.observability?.tracer?.endSpan(spanId);
|
|
999
|
+
throw error;
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
/**
|
|
1003
|
+
* Execute tool calls with safety checks and execution policy enforcement.
|
|
1004
|
+
*/
|
|
1005
|
+
async executeToolCalls(toolCalls) {
|
|
1006
|
+
const results = [];
|
|
1007
|
+
for (const toolCall of toolCalls) {
|
|
1008
|
+
const spanId = this.observability?.tracer?.startSpan(`tool.${toolCall.name}`);
|
|
1009
|
+
const executionId = `exec-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
1010
|
+
this.emit({ type: 'tool.start', tool: toolCall.name, args: toolCall.arguments });
|
|
1011
|
+
const startTime = Date.now();
|
|
1012
|
+
// Lesson 26: Record tool start for tracing
|
|
1013
|
+
this.traceCollector?.record({
|
|
1014
|
+
type: 'tool.start',
|
|
1015
|
+
data: {
|
|
1016
|
+
executionId,
|
|
1017
|
+
toolName: toolCall.name,
|
|
1018
|
+
arguments: toolCall.arguments,
|
|
1019
|
+
},
|
|
1020
|
+
});
|
|
1021
|
+
try {
|
|
1022
|
+
// =====================================================================
|
|
1023
|
+
// PLAN MODE WRITE INTERCEPTION
|
|
1024
|
+
// =====================================================================
|
|
1025
|
+
// In plan mode, intercept write operations and queue them as proposed changes
|
|
1026
|
+
if (this.modeManager.shouldInterceptTool(toolCall.name, toolCall.arguments)) {
|
|
1027
|
+
// Extract reason from context - use last assistant message or generate one
|
|
1028
|
+
const lastAssistantMsg = [...this.state.messages].reverse().find(m => m.role === 'assistant');
|
|
1029
|
+
const reason = typeof lastAssistantMsg?.content === 'string'
|
|
1030
|
+
? lastAssistantMsg.content.slice(0, 200)
|
|
1031
|
+
: `Proposed change: ${toolCall.name}`;
|
|
1032
|
+
// Start a new plan if needed
|
|
1033
|
+
if (!this.pendingPlanManager.hasPendingPlan()) {
|
|
1034
|
+
const lastUserMsg = [...this.state.messages].reverse().find(m => m.role === 'user');
|
|
1035
|
+
const task = typeof lastUserMsg?.content === 'string' ? lastUserMsg.content : 'Plan';
|
|
1036
|
+
this.pendingPlanManager.startPlan(task);
|
|
1037
|
+
}
|
|
1038
|
+
// Queue the write operation
|
|
1039
|
+
const change = this.pendingPlanManager.addProposedChange(toolCall.name, toolCall.arguments, reason, toolCall.id);
|
|
1040
|
+
// Emit event for UI
|
|
1041
|
+
this.emit({
|
|
1042
|
+
type: 'plan.change.queued',
|
|
1043
|
+
tool: toolCall.name,
|
|
1044
|
+
changeId: change?.id,
|
|
1045
|
+
});
|
|
1046
|
+
// Return a message indicating the change was queued
|
|
1047
|
+
const queueMessage = `[PLAN MODE] Change queued for approval:\n` +
|
|
1048
|
+
`Tool: ${toolCall.name}\n` +
|
|
1049
|
+
`${this.formatToolArgsForPlan(toolCall.name, toolCall.arguments)}\n` +
|
|
1050
|
+
`Use /show-plan to see all pending changes, /approve to execute, /reject to discard.`;
|
|
1051
|
+
results.push({
|
|
1052
|
+
callId: toolCall.id,
|
|
1053
|
+
result: queueMessage,
|
|
1054
|
+
});
|
|
1055
|
+
this.observability?.tracer?.endSpan(spanId);
|
|
1056
|
+
continue; // Skip actual execution
|
|
1057
|
+
}
|
|
1058
|
+
// =====================================================================
|
|
1059
|
+
// EXECUTION POLICY ENFORCEMENT (Lesson 23)
|
|
1060
|
+
// =====================================================================
|
|
1061
|
+
if (this.executionPolicy) {
|
|
1062
|
+
const policyContext = {
|
|
1063
|
+
messages: this.state.messages,
|
|
1064
|
+
currentMessage: this.state.messages.find(m => m.role === 'user')?.content,
|
|
1065
|
+
previousToolCalls: toolCalls.slice(0, toolCalls.indexOf(toolCall)),
|
|
1066
|
+
};
|
|
1067
|
+
const evaluation = this.executionPolicy.evaluate(toolCall, policyContext);
|
|
1068
|
+
// Emit policy event
|
|
1069
|
+
this.emit({
|
|
1070
|
+
type: 'policy.evaluated',
|
|
1071
|
+
tool: toolCall.name,
|
|
1072
|
+
policy: evaluation.policy,
|
|
1073
|
+
reason: evaluation.reason,
|
|
1074
|
+
});
|
|
1075
|
+
// Handle forbidden policy - always block
|
|
1076
|
+
if (evaluation.policy === 'forbidden') {
|
|
1077
|
+
throw new Error(`Forbidden by policy: ${evaluation.reason}`);
|
|
1078
|
+
}
|
|
1079
|
+
// Handle prompt policy - requires approval
|
|
1080
|
+
if (evaluation.policy === 'prompt' && evaluation.requiresApproval) {
|
|
1081
|
+
// Try to get approval through safety manager's human-in-loop
|
|
1082
|
+
if (this.safety?.humanInLoop) {
|
|
1083
|
+
const approval = await this.safety.humanInLoop.requestApproval(toolCall, `Policy requires approval: ${evaluation.reason}`);
|
|
1084
|
+
if (!approval.approved) {
|
|
1085
|
+
throw new Error(`Denied by user: ${approval.reason || 'No reason provided'}`);
|
|
1086
|
+
}
|
|
1087
|
+
// Create a grant for future similar calls if approved
|
|
1088
|
+
this.executionPolicy.createGrant({
|
|
1089
|
+
toolName: toolCall.name,
|
|
1090
|
+
grantedBy: 'user',
|
|
1091
|
+
reason: 'Approved during execution',
|
|
1092
|
+
maxUsages: 5, // Allow 5 more similar calls
|
|
1093
|
+
});
|
|
1094
|
+
}
|
|
1095
|
+
else {
|
|
1096
|
+
// No approval handler - block by default for safety
|
|
1097
|
+
throw new Error(`Policy requires approval but no approval handler available: ${evaluation.reason}`);
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
// Log intent classification if available
|
|
1101
|
+
if (evaluation.intent) {
|
|
1102
|
+
this.emit({
|
|
1103
|
+
type: 'intent.classified',
|
|
1104
|
+
tool: toolCall.name,
|
|
1105
|
+
intent: evaluation.intent.type,
|
|
1106
|
+
confidence: evaluation.intent.confidence,
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
// =====================================================================
|
|
1111
|
+
// SAFETY VALIDATION (Lesson 20-21)
|
|
1112
|
+
// =====================================================================
|
|
1113
|
+
if (this.safety) {
|
|
1114
|
+
const validation = await this.safety.validateAndApprove(toolCall, `Executing tool: ${toolCall.name}`);
|
|
1115
|
+
if (!validation.allowed) {
|
|
1116
|
+
throw new Error(`Tool call blocked: ${validation.reason}`);
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
// Get tool definition (with lazy-loading support for MCP tools)
|
|
1120
|
+
let tool = this.tools.get(toolCall.name);
|
|
1121
|
+
const wasPreloaded = !!tool;
|
|
1122
|
+
if (!tool && this.toolResolver) {
|
|
1123
|
+
// Try to resolve and load the tool on-demand
|
|
1124
|
+
const resolved = this.toolResolver(toolCall.name);
|
|
1125
|
+
if (resolved) {
|
|
1126
|
+
this.addTool(resolved);
|
|
1127
|
+
tool = resolved;
|
|
1128
|
+
if (process.env.DEBUG)
|
|
1129
|
+
console.log(` 🔄 Auto-loaded MCP tool: ${toolCall.name}`);
|
|
1130
|
+
this.observability?.logger?.info('Tool auto-loaded', { tool: toolCall.name });
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
if (!tool) {
|
|
1134
|
+
throw new Error(`Unknown tool: ${toolCall.name}`);
|
|
1135
|
+
}
|
|
1136
|
+
// Log whether tool was pre-loaded or auto-loaded (for MCP tools)
|
|
1137
|
+
if (process.env.DEBUG && toolCall.name.startsWith('mcp_') && wasPreloaded) {
|
|
1138
|
+
console.log(` ✓ Using pre-loaded MCP tool: ${toolCall.name}`);
|
|
1139
|
+
}
|
|
1140
|
+
// Execute tool (with sandbox if available)
|
|
1141
|
+
let result;
|
|
1142
|
+
if (this.safety?.sandbox) {
|
|
1143
|
+
result = await this.safety.sandbox.executeWithLimits(() => tool.execute(toolCall.arguments));
|
|
1144
|
+
}
|
|
1145
|
+
else {
|
|
1146
|
+
result = await tool.execute(toolCall.arguments);
|
|
1147
|
+
}
|
|
1148
|
+
const duration = Date.now() - startTime;
|
|
1149
|
+
// Lesson 26: Record tool completion for tracing
|
|
1150
|
+
this.traceCollector?.record({
|
|
1151
|
+
type: 'tool.end',
|
|
1152
|
+
data: {
|
|
1153
|
+
executionId,
|
|
1154
|
+
status: 'success',
|
|
1155
|
+
result,
|
|
1156
|
+
durationMs: duration,
|
|
1157
|
+
},
|
|
1158
|
+
});
|
|
1159
|
+
// Record metrics
|
|
1160
|
+
this.observability?.metrics?.recordToolCall(toolCall.name, duration, true);
|
|
1161
|
+
this.state.metrics.toolCalls++;
|
|
1162
|
+
this.emit({ type: 'tool.complete', tool: toolCall.name, result });
|
|
1163
|
+
// Emit tool insight with result summary
|
|
1164
|
+
const summary = this.summarizeToolResult(toolCall.name, result);
|
|
1165
|
+
this.emit({
|
|
1166
|
+
type: 'insight.tool',
|
|
1167
|
+
tool: toolCall.name,
|
|
1168
|
+
summary,
|
|
1169
|
+
durationMs: duration,
|
|
1170
|
+
success: true,
|
|
1171
|
+
});
|
|
1172
|
+
results.push({
|
|
1173
|
+
callId: toolCall.id,
|
|
1174
|
+
result,
|
|
1175
|
+
});
|
|
1176
|
+
this.observability?.tracer?.endSpan(spanId);
|
|
1177
|
+
}
|
|
1178
|
+
catch (err) {
|
|
1179
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
1180
|
+
const duration = Date.now() - startTime;
|
|
1181
|
+
// Lesson 26: Record tool error for tracing
|
|
1182
|
+
this.traceCollector?.record({
|
|
1183
|
+
type: 'tool.end',
|
|
1184
|
+
data: {
|
|
1185
|
+
executionId,
|
|
1186
|
+
status: error.message.includes('Blocked') || error.message.includes('Policy') ? 'blocked' : 'error',
|
|
1187
|
+
error,
|
|
1188
|
+
durationMs: duration,
|
|
1189
|
+
},
|
|
1190
|
+
});
|
|
1191
|
+
this.observability?.metrics?.recordToolCall(toolCall.name, duration, false);
|
|
1192
|
+
this.observability?.tracer?.recordError(error);
|
|
1193
|
+
this.observability?.tracer?.endSpan(spanId);
|
|
1194
|
+
// FAILURE EVIDENCE RECORDING (Trick S)
|
|
1195
|
+
// Track failed tool calls to prevent loops and provide context
|
|
1196
|
+
this.contextEngineering?.recordFailure({
|
|
1197
|
+
action: toolCall.name,
|
|
1198
|
+
args: toolCall.arguments,
|
|
1199
|
+
error,
|
|
1200
|
+
intent: `Execute tool ${toolCall.name}`,
|
|
1201
|
+
});
|
|
1202
|
+
results.push({
|
|
1203
|
+
callId: toolCall.id,
|
|
1204
|
+
result: `Error: ${error.message}`,
|
|
1205
|
+
error: error.message,
|
|
1206
|
+
});
|
|
1207
|
+
this.emit({ type: 'tool.blocked', tool: toolCall.name, reason: error.message });
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
return results;
|
|
1211
|
+
}
|
|
1212
|
+
/**
|
|
1213
|
+
* Select relevant code synchronously using cached repo analysis.
|
|
1214
|
+
* Returns empty result if analysis hasn't been run yet.
|
|
1215
|
+
*/
|
|
1216
|
+
selectRelevantCodeSync(task, maxTokens) {
|
|
1217
|
+
if (!this.codebaseContext) {
|
|
1218
|
+
return { chunks: [], totalTokens: 0 };
|
|
1219
|
+
}
|
|
1220
|
+
const repoMap = this.codebaseContext.getRepoMap();
|
|
1221
|
+
if (!repoMap) {
|
|
1222
|
+
return { chunks: [], totalTokens: 0 };
|
|
1223
|
+
}
|
|
1224
|
+
// Get all chunks and score by relevance
|
|
1225
|
+
const allChunks = Array.from(repoMap.chunks.values());
|
|
1226
|
+
const taskLower = task.toLowerCase();
|
|
1227
|
+
const taskWords = taskLower.split(/\s+/).filter((w) => w.length > 2);
|
|
1228
|
+
// Score chunks by task relevance
|
|
1229
|
+
const scored = allChunks.map((chunk) => {
|
|
1230
|
+
let relevance = 0;
|
|
1231
|
+
// Check file path
|
|
1232
|
+
const pathLower = chunk.filePath.toLowerCase();
|
|
1233
|
+
for (const word of taskWords) {
|
|
1234
|
+
if (pathLower.includes(word))
|
|
1235
|
+
relevance += 0.3;
|
|
1236
|
+
}
|
|
1237
|
+
// Check symbols
|
|
1238
|
+
for (const symbol of chunk.symbols) {
|
|
1239
|
+
const symbolLower = symbol.toLowerCase();
|
|
1240
|
+
for (const word of taskWords) {
|
|
1241
|
+
if (symbolLower.includes(word) || word.includes(symbolLower)) {
|
|
1242
|
+
relevance += 0.2;
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
// Combine with base importance
|
|
1247
|
+
const combinedScore = chunk.importance * 0.4 + Math.min(relevance, 1) * 0.6;
|
|
1248
|
+
return { chunk, score: combinedScore };
|
|
1249
|
+
});
|
|
1250
|
+
// Sort by score and select within budget
|
|
1251
|
+
scored.sort((a, b) => b.score - a.score);
|
|
1252
|
+
const selected = [];
|
|
1253
|
+
let totalTokens = 0;
|
|
1254
|
+
for (const { chunk, score } of scored) {
|
|
1255
|
+
if (score < 0.1)
|
|
1256
|
+
continue; // Skip very low relevance
|
|
1257
|
+
if (totalTokens + chunk.tokenCount > maxTokens)
|
|
1258
|
+
continue;
|
|
1259
|
+
selected.push({
|
|
1260
|
+
filePath: chunk.filePath,
|
|
1261
|
+
content: chunk.content,
|
|
1262
|
+
tokenCount: chunk.tokenCount,
|
|
1263
|
+
importance: score,
|
|
1264
|
+
});
|
|
1265
|
+
totalTokens += chunk.tokenCount;
|
|
1266
|
+
}
|
|
1267
|
+
return { chunks: selected, totalTokens };
|
|
1268
|
+
}
|
|
1269
|
+
/**
|
|
1270
|
+
* Analyze the codebase (async). Call this once at startup for optimal performance.
|
|
1271
|
+
*/
|
|
1272
|
+
async analyzeCodebase(root) {
|
|
1273
|
+
if (this.codebaseContext) {
|
|
1274
|
+
await this.codebaseContext.analyze(root);
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
/**
|
|
1278
|
+
* Emit an event.
|
|
1279
|
+
*/
|
|
1280
|
+
emit(event) {
|
|
1281
|
+
this.hooks?.emit(event);
|
|
1282
|
+
}
|
|
1283
|
+
/**
|
|
1284
|
+
* Create a brief summary of a tool result for insight display.
|
|
1285
|
+
*/
|
|
1286
|
+
summarizeToolResult(toolName, result) {
|
|
1287
|
+
if (result === null || result === undefined) {
|
|
1288
|
+
return 'No output';
|
|
1289
|
+
}
|
|
1290
|
+
const resultStr = typeof result === 'string' ? result : JSON.stringify(result);
|
|
1291
|
+
// Tool-specific summaries
|
|
1292
|
+
if (toolName === 'list_files' || toolName === 'glob') {
|
|
1293
|
+
const lines = resultStr.split('\n').filter(l => l.trim());
|
|
1294
|
+
return `Found ${lines.length} file${lines.length !== 1 ? 's' : ''}`;
|
|
1295
|
+
}
|
|
1296
|
+
if (toolName === 'bash' || toolName === 'execute_command') {
|
|
1297
|
+
const lines = resultStr.split('\n').filter(l => l.trim());
|
|
1298
|
+
if (resultStr.includes('exit code: 0') || !resultStr.includes('exit code:')) {
|
|
1299
|
+
return lines.length > 1 ? `Success (${lines.length} lines)` : 'Success';
|
|
1300
|
+
}
|
|
1301
|
+
return `Failed - ${lines[0]?.slice(0, 50) || 'see output'}`;
|
|
1302
|
+
}
|
|
1303
|
+
if (toolName === 'read_file') {
|
|
1304
|
+
const lines = resultStr.split('\n').length;
|
|
1305
|
+
return `Read ${lines} line${lines !== 1 ? 's' : ''}`;
|
|
1306
|
+
}
|
|
1307
|
+
if (toolName === 'write_file' || toolName === 'edit_file') {
|
|
1308
|
+
return 'File updated';
|
|
1309
|
+
}
|
|
1310
|
+
if (toolName === 'search' || toolName === 'grep') {
|
|
1311
|
+
const matches = (resultStr.match(/\n/g) || []).length;
|
|
1312
|
+
return `${matches} match${matches !== 1 ? 'es' : ''}`;
|
|
1313
|
+
}
|
|
1314
|
+
// Generic summary
|
|
1315
|
+
if (resultStr.length <= 50) {
|
|
1316
|
+
return resultStr;
|
|
1317
|
+
}
|
|
1318
|
+
return `${resultStr.slice(0, 47)}...`;
|
|
1319
|
+
}
|
|
1320
|
+
/**
|
|
1321
|
+
* Format tool arguments for plan display.
|
|
1322
|
+
*/
|
|
1323
|
+
formatToolArgsForPlan(toolName, args) {
|
|
1324
|
+
if (toolName === 'write_file') {
|
|
1325
|
+
const path = args.path || args.file_path;
|
|
1326
|
+
const content = String(args.content || '');
|
|
1327
|
+
const preview = content.slice(0, 100).replace(/\n/g, '\\n');
|
|
1328
|
+
return `File: ${path}\nContent preview: ${preview}${content.length > 100 ? '...' : ''}`;
|
|
1329
|
+
}
|
|
1330
|
+
if (toolName === 'edit_file') {
|
|
1331
|
+
const path = args.path || args.file_path;
|
|
1332
|
+
return `File: ${path}\nOld: ${String(args.old_string || args.search || '').slice(0, 50)}...\nNew: ${String(args.new_string || args.replace || '').slice(0, 50)}...`;
|
|
1333
|
+
}
|
|
1334
|
+
if (toolName === 'bash') {
|
|
1335
|
+
return `Command: ${String(args.command || '').slice(0, 100)}`;
|
|
1336
|
+
}
|
|
1337
|
+
if (toolName === 'delete_file') {
|
|
1338
|
+
return `Delete: ${args.path || args.file_path}`;
|
|
1339
|
+
}
|
|
1340
|
+
// Generic
|
|
1341
|
+
return `Args: ${JSON.stringify(args).slice(0, 100)}...`;
|
|
1342
|
+
}
|
|
1343
|
+
/**
|
|
1344
|
+
* Update memory statistics.
|
|
1345
|
+
* Memory stats are retrieved via memory manager, not stored in state.
|
|
1346
|
+
*/
|
|
1347
|
+
updateMemoryStats() {
|
|
1348
|
+
// Memory stats are accessed via getMetrics() when needed
|
|
1349
|
+
// This method exists for hook/extension points
|
|
1350
|
+
this.memory?.getStats();
|
|
1351
|
+
}
|
|
1352
|
+
/**
|
|
1353
|
+
* Get current metrics.
|
|
1354
|
+
*/
|
|
1355
|
+
getMetrics() {
|
|
1356
|
+
if (this.observability?.metrics) {
|
|
1357
|
+
return this.observability.metrics.getMetrics();
|
|
1358
|
+
}
|
|
1359
|
+
return this.state.metrics;
|
|
1360
|
+
}
|
|
1361
|
+
/**
|
|
1362
|
+
* Get current state.
|
|
1363
|
+
*/
|
|
1364
|
+
getState() {
|
|
1365
|
+
return { ...this.state };
|
|
1366
|
+
}
|
|
1367
|
+
/**
|
|
1368
|
+
* Get the trace collector (Lesson 26).
|
|
1369
|
+
* Returns null if trace capture is not enabled.
|
|
1370
|
+
*/
|
|
1371
|
+
getTraceCollector() {
|
|
1372
|
+
return this.traceCollector;
|
|
1373
|
+
}
|
|
1374
|
+
/**
|
|
1375
|
+
* Subscribe to events.
|
|
1376
|
+
*/
|
|
1377
|
+
subscribe(listener) {
|
|
1378
|
+
if (this.hooks) {
|
|
1379
|
+
return this.hooks.subscribe(listener);
|
|
1380
|
+
}
|
|
1381
|
+
return () => { };
|
|
1382
|
+
}
|
|
1383
|
+
/**
|
|
1384
|
+
* Reset agent state.
|
|
1385
|
+
*/
|
|
1386
|
+
reset() {
|
|
1387
|
+
this.state = {
|
|
1388
|
+
status: 'idle',
|
|
1389
|
+
messages: [],
|
|
1390
|
+
plan: undefined,
|
|
1391
|
+
memoryContext: [],
|
|
1392
|
+
metrics: {
|
|
1393
|
+
totalTokens: 0,
|
|
1394
|
+
inputTokens: 0,
|
|
1395
|
+
outputTokens: 0,
|
|
1396
|
+
estimatedCost: 0,
|
|
1397
|
+
llmCalls: 0,
|
|
1398
|
+
toolCalls: 0,
|
|
1399
|
+
duration: 0,
|
|
1400
|
+
},
|
|
1401
|
+
iteration: 0,
|
|
1402
|
+
};
|
|
1403
|
+
this.memory?.clear();
|
|
1404
|
+
this.observability?.metrics?.reset();
|
|
1405
|
+
this.planning?.clearPlan();
|
|
1406
|
+
this.observability?.logger?.info('Agent state reset');
|
|
1407
|
+
}
|
|
1408
|
+
/**
|
|
1409
|
+
* Load messages from a previous session.
|
|
1410
|
+
* @deprecated Use loadState() for full state restoration
|
|
1411
|
+
*/
|
|
1412
|
+
loadMessages(messages) {
|
|
1413
|
+
this.state.messages = [...messages];
|
|
1414
|
+
// Sync to threadManager if enabled
|
|
1415
|
+
if (this.threadManager) {
|
|
1416
|
+
const thread = this.threadManager.getActiveThread();
|
|
1417
|
+
thread.messages = [...messages];
|
|
1418
|
+
}
|
|
1419
|
+
this.observability?.logger?.info('Messages loaded', { count: messages.length });
|
|
1420
|
+
}
|
|
1421
|
+
/**
|
|
1422
|
+
* Serializable state for checkpoints (excludes non-serializable fields).
|
|
1423
|
+
*/
|
|
1424
|
+
getSerializableState() {
|
|
1425
|
+
return {
|
|
1426
|
+
messages: this.state.messages,
|
|
1427
|
+
iteration: this.state.iteration,
|
|
1428
|
+
metrics: { ...this.state.metrics },
|
|
1429
|
+
plan: this.state.plan ? { ...this.state.plan } : undefined,
|
|
1430
|
+
memoryContext: this.state.memoryContext ? [...this.state.memoryContext] : undefined,
|
|
1431
|
+
};
|
|
1432
|
+
}
|
|
1433
|
+
/**
|
|
1434
|
+
* Validate checkpoint data before loading.
|
|
1435
|
+
* Returns validation result with errors and warnings.
|
|
1436
|
+
*/
|
|
1437
|
+
validateCheckpoint(data) {
|
|
1438
|
+
const errors = [];
|
|
1439
|
+
const warnings = [];
|
|
1440
|
+
// Check if data is an object
|
|
1441
|
+
if (!data || typeof data !== 'object') {
|
|
1442
|
+
errors.push('Checkpoint data must be an object');
|
|
1443
|
+
return { valid: false, errors, warnings, sanitized: null };
|
|
1444
|
+
}
|
|
1445
|
+
const checkpoint = data;
|
|
1446
|
+
// Validate messages array (required)
|
|
1447
|
+
if (!checkpoint.messages) {
|
|
1448
|
+
errors.push('Checkpoint missing required "messages" field');
|
|
1449
|
+
}
|
|
1450
|
+
else if (!Array.isArray(checkpoint.messages)) {
|
|
1451
|
+
errors.push('Checkpoint "messages" must be an array');
|
|
1452
|
+
}
|
|
1453
|
+
else {
|
|
1454
|
+
// Validate each message has required fields
|
|
1455
|
+
for (let i = 0; i < checkpoint.messages.length; i++) {
|
|
1456
|
+
const msg = checkpoint.messages[i];
|
|
1457
|
+
if (!msg || typeof msg !== 'object') {
|
|
1458
|
+
errors.push(`Message at index ${i} is not an object`);
|
|
1459
|
+
continue;
|
|
1460
|
+
}
|
|
1461
|
+
if (!msg.role || typeof msg.role !== 'string') {
|
|
1462
|
+
errors.push(`Message at index ${i} missing valid "role" field`);
|
|
1463
|
+
}
|
|
1464
|
+
if (msg.content !== undefined && msg.content !== null && typeof msg.content !== 'string') {
|
|
1465
|
+
// Content can be undefined for tool call messages
|
|
1466
|
+
warnings.push(`Message at index ${i} has non-string content (type: ${typeof msg.content})`);
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
// Validate iteration (optional but should be non-negative number)
|
|
1471
|
+
if (checkpoint.iteration !== undefined) {
|
|
1472
|
+
if (typeof checkpoint.iteration !== 'number' || checkpoint.iteration < 0) {
|
|
1473
|
+
warnings.push(`Invalid iteration value: ${checkpoint.iteration}, will use default`);
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
// Validate metrics (optional)
|
|
1477
|
+
if (checkpoint.metrics !== undefined && checkpoint.metrics !== null) {
|
|
1478
|
+
if (typeof checkpoint.metrics !== 'object') {
|
|
1479
|
+
warnings.push('Metrics field is not an object, will be ignored');
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
// Validate memoryContext (optional)
|
|
1483
|
+
if (checkpoint.memoryContext !== undefined && checkpoint.memoryContext !== null) {
|
|
1484
|
+
if (!Array.isArray(checkpoint.memoryContext)) {
|
|
1485
|
+
warnings.push('memoryContext is not an array, will be ignored');
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
// If we have critical errors, fail validation
|
|
1489
|
+
if (errors.length > 0) {
|
|
1490
|
+
return { valid: false, errors, warnings, sanitized: null };
|
|
1491
|
+
}
|
|
1492
|
+
// Build sanitized checkpoint
|
|
1493
|
+
const messages = checkpoint.messages.filter((msg) => msg && typeof msg === 'object' && typeof msg.role === 'string');
|
|
1494
|
+
const sanitized = {
|
|
1495
|
+
messages,
|
|
1496
|
+
iteration: typeof checkpoint.iteration === 'number' && checkpoint.iteration >= 0
|
|
1497
|
+
? checkpoint.iteration
|
|
1498
|
+
: Math.floor(messages.length / 2),
|
|
1499
|
+
metrics: typeof checkpoint.metrics === 'object' && checkpoint.metrics !== null
|
|
1500
|
+
? checkpoint.metrics
|
|
1501
|
+
: undefined,
|
|
1502
|
+
plan: checkpoint.plan,
|
|
1503
|
+
memoryContext: Array.isArray(checkpoint.memoryContext)
|
|
1504
|
+
? checkpoint.memoryContext
|
|
1505
|
+
: undefined,
|
|
1506
|
+
};
|
|
1507
|
+
return { valid: true, errors, warnings, sanitized };
|
|
1508
|
+
}
|
|
1509
|
+
/**
|
|
1510
|
+
* Load full state from a checkpoint.
|
|
1511
|
+
* Restores messages, iteration, metrics, plan, and memory context.
|
|
1512
|
+
* Validates checkpoint data before loading to prevent corrupted state.
|
|
1513
|
+
*/
|
|
1514
|
+
loadState(savedState) {
|
|
1515
|
+
// Validate checkpoint data
|
|
1516
|
+
const validation = this.validateCheckpoint(savedState);
|
|
1517
|
+
// Log warnings
|
|
1518
|
+
for (const warning of validation.warnings) {
|
|
1519
|
+
console.warn(`[Checkpoint] Warning: ${warning}`);
|
|
1520
|
+
this.observability?.logger?.warn('Checkpoint validation warning', { warning });
|
|
1521
|
+
}
|
|
1522
|
+
// Fail on validation errors
|
|
1523
|
+
if (!validation.valid || !validation.sanitized) {
|
|
1524
|
+
const errorMsg = `Invalid checkpoint: ${validation.errors.join('; ')}`;
|
|
1525
|
+
this.observability?.logger?.error('Checkpoint validation failed', { errors: validation.errors });
|
|
1526
|
+
throw new Error(errorMsg);
|
|
1527
|
+
}
|
|
1528
|
+
// Use sanitized data
|
|
1529
|
+
const sanitized = validation.sanitized;
|
|
1530
|
+
// Restore messages
|
|
1531
|
+
this.state.messages = [...sanitized.messages];
|
|
1532
|
+
// Restore iteration (already validated/defaulted in sanitized)
|
|
1533
|
+
this.state.iteration = sanitized.iteration;
|
|
1534
|
+
// Restore metrics (merge with defaults)
|
|
1535
|
+
if (sanitized.metrics) {
|
|
1536
|
+
this.state.metrics = {
|
|
1537
|
+
totalTokens: sanitized.metrics.totalTokens ?? 0,
|
|
1538
|
+
inputTokens: sanitized.metrics.inputTokens ?? 0,
|
|
1539
|
+
outputTokens: sanitized.metrics.outputTokens ?? 0,
|
|
1540
|
+
estimatedCost: sanitized.metrics.estimatedCost ?? 0,
|
|
1541
|
+
llmCalls: sanitized.metrics.llmCalls ?? 0,
|
|
1542
|
+
toolCalls: sanitized.metrics.toolCalls ?? 0,
|
|
1543
|
+
duration: sanitized.metrics.duration ?? 0,
|
|
1544
|
+
reflectionAttempts: sanitized.metrics.reflectionAttempts,
|
|
1545
|
+
};
|
|
1546
|
+
}
|
|
1547
|
+
// Restore plan if present
|
|
1548
|
+
if (sanitized.plan) {
|
|
1549
|
+
this.state.plan = { ...sanitized.plan };
|
|
1550
|
+
// Sync with planning manager if enabled
|
|
1551
|
+
if (this.planning) {
|
|
1552
|
+
this.planning.loadPlan(sanitized.plan);
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
// Restore memory context if present
|
|
1556
|
+
if (sanitized.memoryContext) {
|
|
1557
|
+
this.state.memoryContext = [...sanitized.memoryContext];
|
|
1558
|
+
}
|
|
1559
|
+
// Sync to threadManager if enabled
|
|
1560
|
+
if (this.threadManager) {
|
|
1561
|
+
const thread = this.threadManager.getActiveThread();
|
|
1562
|
+
thread.messages = [...sanitized.messages];
|
|
1563
|
+
}
|
|
1564
|
+
this.observability?.logger?.info('State loaded', {
|
|
1565
|
+
messageCount: sanitized.messages.length,
|
|
1566
|
+
iteration: this.state.iteration,
|
|
1567
|
+
hasPlan: !!sanitized.plan,
|
|
1568
|
+
hasMemoryContext: !!sanitized.memoryContext,
|
|
1569
|
+
});
|
|
1570
|
+
}
|
|
1571
|
+
/**
|
|
1572
|
+
* Add a tool dynamically.
|
|
1573
|
+
*/
|
|
1574
|
+
addTool(tool) {
|
|
1575
|
+
this.tools.set(tool.name, tool);
|
|
1576
|
+
this.observability?.logger?.debug('Tool added', { tool: tool.name });
|
|
1577
|
+
}
|
|
1578
|
+
/**
|
|
1579
|
+
* Remove a tool.
|
|
1580
|
+
*/
|
|
1581
|
+
removeTool(name) {
|
|
1582
|
+
this.tools.delete(name);
|
|
1583
|
+
this.observability?.logger?.debug('Tool removed', { tool: name });
|
|
1584
|
+
}
|
|
1585
|
+
/**
|
|
1586
|
+
* Compact tool outputs to save context.
|
|
1587
|
+
* Called after model produces a response - replaces verbose tool outputs
|
|
1588
|
+
* with compact summaries since the model has already "consumed" them.
|
|
1589
|
+
*/
|
|
1590
|
+
compactToolOutputs() {
|
|
1591
|
+
const COMPACT_PREVIEW_LENGTH = 200; // Keep first 200 chars as preview
|
|
1592
|
+
let compactedCount = 0;
|
|
1593
|
+
let savedChars = 0;
|
|
1594
|
+
for (const msg of this.state.messages) {
|
|
1595
|
+
if (msg.role === 'tool' && msg.content && msg.content.length > COMPACT_PREVIEW_LENGTH * 2) {
|
|
1596
|
+
const originalLength = msg.content.length;
|
|
1597
|
+
const preview = msg.content.slice(0, COMPACT_PREVIEW_LENGTH).replace(/\n/g, ' ');
|
|
1598
|
+
msg.content = `[${preview}...] (${originalLength} chars, compacted)`;
|
|
1599
|
+
savedChars += originalLength - msg.content.length;
|
|
1600
|
+
compactedCount++;
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
if (compactedCount > 0 && process.env.DEBUG) {
|
|
1604
|
+
console.log(` 📦 Compacted ${compactedCount} tool outputs (saved ~${Math.round(savedChars / 4)} tokens)`);
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
/**
|
|
1608
|
+
* Estimate total tokens in a message array.
|
|
1609
|
+
* Uses ~4 chars per token heuristic for fast estimation.
|
|
1610
|
+
*/
|
|
1611
|
+
estimateContextTokens(messages) {
|
|
1612
|
+
let totalChars = 0;
|
|
1613
|
+
for (const msg of messages) {
|
|
1614
|
+
if (msg.content) {
|
|
1615
|
+
totalChars += msg.content.length;
|
|
1616
|
+
}
|
|
1617
|
+
// Account for tool calls in assistant messages
|
|
1618
|
+
if (msg.toolCalls) {
|
|
1619
|
+
for (const tc of msg.toolCalls) {
|
|
1620
|
+
totalChars += tc.name.length;
|
|
1621
|
+
totalChars += JSON.stringify(tc.arguments).length;
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
return Math.ceil(totalChars / 4); // ~4 chars per token
|
|
1626
|
+
}
|
|
1627
|
+
/**
|
|
1628
|
+
* Get audit log (if human-in-loop is enabled).
|
|
1629
|
+
*/
|
|
1630
|
+
getAuditLog() {
|
|
1631
|
+
return this.safety?.humanInLoop?.getAuditLog() || [];
|
|
1632
|
+
}
|
|
1633
|
+
// =========================================================================
|
|
1634
|
+
// MULTI-AGENT METHODS (Lesson 17)
|
|
1635
|
+
// =========================================================================
|
|
1636
|
+
/**
|
|
1637
|
+
* Run a task with a multi-agent team.
|
|
1638
|
+
* Requires multiAgent to be enabled in config.
|
|
1639
|
+
*/
|
|
1640
|
+
async runWithTeam(task, roles) {
|
|
1641
|
+
if (!this.multiAgent) {
|
|
1642
|
+
throw new Error('Multi-agent not enabled. Enable it in config to use runWithTeam()');
|
|
1643
|
+
}
|
|
1644
|
+
this.observability?.logger?.info('Running with team', { task: task.goal, roles: roles.map(r => r.name) });
|
|
1645
|
+
// Register roles if not already registered
|
|
1646
|
+
for (const role of roles) {
|
|
1647
|
+
this.multiAgent.registerRole(role);
|
|
1648
|
+
}
|
|
1649
|
+
// Set up event forwarding
|
|
1650
|
+
this.multiAgent.on(event => {
|
|
1651
|
+
switch (event.type) {
|
|
1652
|
+
case 'agent.spawn':
|
|
1653
|
+
this.emit({ type: 'multiagent.spawn', agentId: event.agentId, role: event.role });
|
|
1654
|
+
break;
|
|
1655
|
+
case 'agent.complete':
|
|
1656
|
+
this.emit({ type: 'multiagent.complete', agentId: event.agentId, success: event.result.success });
|
|
1657
|
+
break;
|
|
1658
|
+
case 'consensus.start':
|
|
1659
|
+
this.emit({ type: 'consensus.start', strategy: event.strategy });
|
|
1660
|
+
break;
|
|
1661
|
+
case 'consensus.reached':
|
|
1662
|
+
this.emit({ type: 'consensus.reached', agreed: event.decision.agreed, result: event.decision.result });
|
|
1663
|
+
break;
|
|
1664
|
+
}
|
|
1665
|
+
});
|
|
1666
|
+
const result = await this.multiAgent.runWithTeam(task, {
|
|
1667
|
+
roles,
|
|
1668
|
+
consensusStrategy: this.config.multiAgent && isFeatureEnabled(this.config.multiAgent)
|
|
1669
|
+
? this.config.multiAgent.consensusStrategy || 'voting'
|
|
1670
|
+
: 'voting',
|
|
1671
|
+
communicationMode: 'broadcast',
|
|
1672
|
+
});
|
|
1673
|
+
return result;
|
|
1674
|
+
}
|
|
1675
|
+
/**
|
|
1676
|
+
* Add a role to the multi-agent manager.
|
|
1677
|
+
*/
|
|
1678
|
+
addRole(role) {
|
|
1679
|
+
if (!this.multiAgent) {
|
|
1680
|
+
throw new Error('Multi-agent not enabled');
|
|
1681
|
+
}
|
|
1682
|
+
this.multiAgent.registerRole(role);
|
|
1683
|
+
}
|
|
1684
|
+
// =========================================================================
|
|
1685
|
+
// REACT METHODS (Lesson 18)
|
|
1686
|
+
// =========================================================================
|
|
1687
|
+
/**
|
|
1688
|
+
* Run a task using the ReAct (Reasoning + Acting) pattern.
|
|
1689
|
+
* Provides explicit reasoning traces.
|
|
1690
|
+
*/
|
|
1691
|
+
async runWithReAct(task) {
|
|
1692
|
+
if (!this.react) {
|
|
1693
|
+
throw new Error('ReAct not enabled. Enable it in config to use runWithReAct()');
|
|
1694
|
+
}
|
|
1695
|
+
this.observability?.logger?.info('Running with ReAct', { task });
|
|
1696
|
+
// Set up event forwarding
|
|
1697
|
+
this.react.on(event => {
|
|
1698
|
+
switch (event.type) {
|
|
1699
|
+
case 'react.thought':
|
|
1700
|
+
this.emit({ type: 'react.thought', step: event.step, thought: event.thought });
|
|
1701
|
+
break;
|
|
1702
|
+
case 'react.action':
|
|
1703
|
+
this.emit({ type: 'react.action', step: event.step, action: event.action.tool, input: event.action.input });
|
|
1704
|
+
break;
|
|
1705
|
+
case 'react.observation':
|
|
1706
|
+
this.emit({ type: 'react.observation', step: event.step, observation: event.observation });
|
|
1707
|
+
break;
|
|
1708
|
+
case 'react.answer':
|
|
1709
|
+
this.emit({ type: 'react.answer', answer: event.answer });
|
|
1710
|
+
break;
|
|
1711
|
+
}
|
|
1712
|
+
});
|
|
1713
|
+
const trace = await this.react.run(task);
|
|
1714
|
+
// Store trace in memory if available
|
|
1715
|
+
if (this.memory && trace.finalAnswer) {
|
|
1716
|
+
this.memory.storeConversation([
|
|
1717
|
+
{ role: 'user', content: task },
|
|
1718
|
+
{ role: 'assistant', content: trace.finalAnswer },
|
|
1719
|
+
]);
|
|
1720
|
+
}
|
|
1721
|
+
return trace;
|
|
1722
|
+
}
|
|
1723
|
+
/**
|
|
1724
|
+
* Get the ReAct trace formatted as a string.
|
|
1725
|
+
*/
|
|
1726
|
+
formatReActTrace(trace) {
|
|
1727
|
+
if (!this.react) {
|
|
1728
|
+
throw new Error('ReAct not enabled');
|
|
1729
|
+
}
|
|
1730
|
+
return this.react.formatTrace(trace);
|
|
1731
|
+
}
|
|
1732
|
+
// =========================================================================
|
|
1733
|
+
// EXECUTION POLICY METHODS (Lesson 23)
|
|
1734
|
+
// =========================================================================
|
|
1735
|
+
/**
|
|
1736
|
+
* Create a permission grant for a tool.
|
|
1737
|
+
* Allows temporary, scoped permissions.
|
|
1738
|
+
*/
|
|
1739
|
+
createPermissionGrant(options) {
|
|
1740
|
+
if (!this.executionPolicy) {
|
|
1741
|
+
throw new Error('Execution policies not enabled');
|
|
1742
|
+
}
|
|
1743
|
+
const grant = this.executionPolicy.createGrant({
|
|
1744
|
+
toolName: options.toolName,
|
|
1745
|
+
argPattern: options.argPattern,
|
|
1746
|
+
grantedBy: options.grantedBy || 'user',
|
|
1747
|
+
expiresAt: options.expiresAt,
|
|
1748
|
+
maxUsages: options.maxUsages,
|
|
1749
|
+
reason: options.reason,
|
|
1750
|
+
});
|
|
1751
|
+
this.emit({ type: 'grant.created', grantId: grant.id, tool: options.toolName });
|
|
1752
|
+
return grant.id;
|
|
1753
|
+
}
|
|
1754
|
+
/**
|
|
1755
|
+
* Revoke a permission grant.
|
|
1756
|
+
*/
|
|
1757
|
+
revokePermissionGrant(grantId) {
|
|
1758
|
+
if (!this.executionPolicy) {
|
|
1759
|
+
throw new Error('Execution policies not enabled');
|
|
1760
|
+
}
|
|
1761
|
+
return this.executionPolicy.revokeGrant(grantId);
|
|
1762
|
+
}
|
|
1763
|
+
/**
|
|
1764
|
+
* Get active permission grants.
|
|
1765
|
+
*/
|
|
1766
|
+
getActiveGrants() {
|
|
1767
|
+
if (!this.executionPolicy) {
|
|
1768
|
+
return [];
|
|
1769
|
+
}
|
|
1770
|
+
return this.executionPolicy.getActiveGrants();
|
|
1771
|
+
}
|
|
1772
|
+
// =========================================================================
|
|
1773
|
+
// ECONOMICS METHODS (Token Budget)
|
|
1774
|
+
// =========================================================================
|
|
1775
|
+
/**
|
|
1776
|
+
* Get current budget usage.
|
|
1777
|
+
*/
|
|
1778
|
+
getBudgetUsage() {
|
|
1779
|
+
if (!this.economics)
|
|
1780
|
+
return null;
|
|
1781
|
+
const usage = this.economics.getUsage();
|
|
1782
|
+
const budget = this.economics.getBudget();
|
|
1783
|
+
return {
|
|
1784
|
+
tokens: usage.tokens,
|
|
1785
|
+
cost: usage.cost,
|
|
1786
|
+
duration: usage.duration,
|
|
1787
|
+
iterations: usage.iterations,
|
|
1788
|
+
percentUsed: Math.max((usage.tokens / budget.maxTokens) * 100, (usage.cost / budget.maxCost) * 100, (usage.duration / budget.maxDuration) * 100),
|
|
1789
|
+
};
|
|
1790
|
+
}
|
|
1791
|
+
/**
|
|
1792
|
+
* Get current budget limits.
|
|
1793
|
+
*/
|
|
1794
|
+
getBudgetLimits() {
|
|
1795
|
+
if (!this.economics)
|
|
1796
|
+
return null;
|
|
1797
|
+
const budget = this.economics.getBudget();
|
|
1798
|
+
return {
|
|
1799
|
+
maxTokens: budget.maxTokens,
|
|
1800
|
+
maxCost: budget.maxCost,
|
|
1801
|
+
maxDuration: budget.maxDuration,
|
|
1802
|
+
maxIterations: budget.maxIterations,
|
|
1803
|
+
};
|
|
1804
|
+
}
|
|
1805
|
+
/**
|
|
1806
|
+
* Get progress tracking info.
|
|
1807
|
+
*/
|
|
1808
|
+
getProgress() {
|
|
1809
|
+
if (!this.economics)
|
|
1810
|
+
return null;
|
|
1811
|
+
return this.economics.getProgress();
|
|
1812
|
+
}
|
|
1813
|
+
/**
|
|
1814
|
+
* Extend the budget limits.
|
|
1815
|
+
*/
|
|
1816
|
+
extendBudget(extension) {
|
|
1817
|
+
if (this.economics) {
|
|
1818
|
+
this.economics.extendBudget(extension);
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
// =========================================================================
|
|
1822
|
+
// THREAD MANAGEMENT METHODS (Lesson 24)
|
|
1823
|
+
// =========================================================================
|
|
1824
|
+
/**
|
|
1825
|
+
* Create a checkpoint of the current state.
|
|
1826
|
+
* Useful before risky operations.
|
|
1827
|
+
*/
|
|
1828
|
+
createCheckpoint(label) {
|
|
1829
|
+
if (!this.threadManager) {
|
|
1830
|
+
throw new Error('Thread management not enabled. Enable it in config to use createCheckpoint()');
|
|
1831
|
+
}
|
|
1832
|
+
const checkpoint = this.threadManager.createCheckpoint({
|
|
1833
|
+
label,
|
|
1834
|
+
agentState: this.state,
|
|
1835
|
+
});
|
|
1836
|
+
this.emit({ type: 'checkpoint.created', checkpointId: checkpoint.id, label });
|
|
1837
|
+
this.observability?.logger?.info('Checkpoint created', { checkpointId: checkpoint.id, label });
|
|
1838
|
+
return checkpoint;
|
|
1839
|
+
}
|
|
1840
|
+
/**
|
|
1841
|
+
* Restore from a checkpoint.
|
|
1842
|
+
*/
|
|
1843
|
+
restoreCheckpoint(checkpointId) {
|
|
1844
|
+
if (!this.threadManager) {
|
|
1845
|
+
throw new Error('Thread management not enabled');
|
|
1846
|
+
}
|
|
1847
|
+
const state = this.threadManager.restoreCheckpoint(checkpointId);
|
|
1848
|
+
if (!state) {
|
|
1849
|
+
return false;
|
|
1850
|
+
}
|
|
1851
|
+
// Restore agent state
|
|
1852
|
+
this.state.messages = state.messages;
|
|
1853
|
+
this.state.metrics = state.metrics;
|
|
1854
|
+
this.state.iteration = state.iteration;
|
|
1855
|
+
this.emit({ type: 'checkpoint.restored', checkpointId });
|
|
1856
|
+
this.observability?.logger?.info('Checkpoint restored', { checkpointId });
|
|
1857
|
+
return true;
|
|
1858
|
+
}
|
|
1859
|
+
/**
|
|
1860
|
+
* Rollback the conversation by N messages.
|
|
1861
|
+
*/
|
|
1862
|
+
rollback(steps) {
|
|
1863
|
+
if (!this.threadManager) {
|
|
1864
|
+
throw new Error('Thread management not enabled');
|
|
1865
|
+
}
|
|
1866
|
+
// Sync state.messages to threadManager before rollback (messages may have been added directly)
|
|
1867
|
+
const thread = this.threadManager.getActiveThread();
|
|
1868
|
+
thread.messages = [...this.state.messages];
|
|
1869
|
+
const success = this.threadManager.rollback(steps);
|
|
1870
|
+
if (success) {
|
|
1871
|
+
// Sync back to state
|
|
1872
|
+
this.state.messages = this.threadManager.getMessages();
|
|
1873
|
+
this.emit({ type: 'rollback', steps });
|
|
1874
|
+
this.observability?.logger?.info('Rolled back', { steps });
|
|
1875
|
+
}
|
|
1876
|
+
return success;
|
|
1877
|
+
}
|
|
1878
|
+
/**
|
|
1879
|
+
* Fork the current conversation into a new branch.
|
|
1880
|
+
* Useful for exploring alternatives.
|
|
1881
|
+
*/
|
|
1882
|
+
fork(name) {
|
|
1883
|
+
if (!this.threadManager) {
|
|
1884
|
+
throw new Error('Thread management not enabled');
|
|
1885
|
+
}
|
|
1886
|
+
const thread = this.threadManager.fork({ name });
|
|
1887
|
+
this.emit({ type: 'thread.forked', threadId: thread.id, parentId: thread.parentId || 'main' });
|
|
1888
|
+
this.observability?.logger?.info('Thread forked', { threadId: thread.id, name });
|
|
1889
|
+
return thread.id;
|
|
1890
|
+
}
|
|
1891
|
+
/**
|
|
1892
|
+
* Switch to a different thread.
|
|
1893
|
+
*/
|
|
1894
|
+
switchThread(threadId) {
|
|
1895
|
+
if (!this.threadManager) {
|
|
1896
|
+
throw new Error('Thread management not enabled');
|
|
1897
|
+
}
|
|
1898
|
+
const fromId = this.threadManager.getActiveThread().id;
|
|
1899
|
+
const success = this.threadManager.switchThread(threadId);
|
|
1900
|
+
if (success) {
|
|
1901
|
+
this.state.messages = this.threadManager.getMessages();
|
|
1902
|
+
this.emit({ type: 'thread.switched', fromId, toId: threadId });
|
|
1903
|
+
}
|
|
1904
|
+
return success;
|
|
1905
|
+
}
|
|
1906
|
+
/**
|
|
1907
|
+
* Get all threads.
|
|
1908
|
+
*/
|
|
1909
|
+
getAllThreads() {
|
|
1910
|
+
if (!this.threadManager) {
|
|
1911
|
+
return [];
|
|
1912
|
+
}
|
|
1913
|
+
return this.threadManager.getAllThreads();
|
|
1914
|
+
}
|
|
1915
|
+
/**
|
|
1916
|
+
* Get checkpoints for the current thread.
|
|
1917
|
+
*/
|
|
1918
|
+
getCheckpoints() {
|
|
1919
|
+
if (!this.threadManager) {
|
|
1920
|
+
return [];
|
|
1921
|
+
}
|
|
1922
|
+
return this.threadManager.getThreadCheckpoints();
|
|
1923
|
+
}
|
|
1924
|
+
/**
|
|
1925
|
+
* Automatically create checkpoint if enabled in config.
|
|
1926
|
+
* Safe to call after each Q&A cycle - handles all checks internally.
|
|
1927
|
+
* @param force - If true, bypasses frequency check and always creates checkpoint
|
|
1928
|
+
* @returns The created checkpoint, or null if conditions not met
|
|
1929
|
+
*/
|
|
1930
|
+
autoCheckpoint(force = false) {
|
|
1931
|
+
// Check if thread management is enabled
|
|
1932
|
+
if (!this.threadManager) {
|
|
1933
|
+
return null;
|
|
1934
|
+
}
|
|
1935
|
+
// Check if auto-checkpoint is enabled
|
|
1936
|
+
const threadsConfig = this.config.threads;
|
|
1937
|
+
if (!threadsConfig || typeof threadsConfig === 'boolean' || !threadsConfig.autoCheckpoint) {
|
|
1938
|
+
return null;
|
|
1939
|
+
}
|
|
1940
|
+
// Check frequency (every N iterations, default 5) - unless forced
|
|
1941
|
+
if (!force) {
|
|
1942
|
+
const frequency = threadsConfig.checkpointFrequency || 5;
|
|
1943
|
+
if (this.state.iteration % frequency !== 0) {
|
|
1944
|
+
return null;
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
// Create the checkpoint
|
|
1948
|
+
const label = `auto-iter-${this.state.iteration}`;
|
|
1949
|
+
return this.createCheckpoint(label);
|
|
1950
|
+
}
|
|
1951
|
+
// =========================================================================
|
|
1952
|
+
// AGENT REGISTRY METHODS (Subagent Support)
|
|
1953
|
+
// =========================================================================
|
|
1954
|
+
/**
|
|
1955
|
+
* Get all registered agents (built-in + user-defined).
|
|
1956
|
+
*/
|
|
1957
|
+
getAgents() {
|
|
1958
|
+
if (!this.agentRegistry) {
|
|
1959
|
+
return [];
|
|
1960
|
+
}
|
|
1961
|
+
return this.agentRegistry.getAllAgents();
|
|
1962
|
+
}
|
|
1963
|
+
/**
|
|
1964
|
+
* Get a specific agent by name.
|
|
1965
|
+
*/
|
|
1966
|
+
getAgent(name) {
|
|
1967
|
+
return this.agentRegistry?.getAgent(name);
|
|
1968
|
+
}
|
|
1969
|
+
/**
|
|
1970
|
+
* Find agents matching a natural language query.
|
|
1971
|
+
* Use this for NL-based agent selection.
|
|
1972
|
+
*/
|
|
1973
|
+
findAgentsForTask(query, limit = 3) {
|
|
1974
|
+
if (!this.agentRegistry) {
|
|
1975
|
+
return [];
|
|
1976
|
+
}
|
|
1977
|
+
return this.agentRegistry.findMatchingAgents(query, limit);
|
|
1978
|
+
}
|
|
1979
|
+
/**
|
|
1980
|
+
* Register a custom agent at runtime.
|
|
1981
|
+
*/
|
|
1982
|
+
registerAgent(definition) {
|
|
1983
|
+
if (!this.agentRegistry) {
|
|
1984
|
+
throw new Error('Agent registry not initialized');
|
|
1985
|
+
}
|
|
1986
|
+
this.agentRegistry.registerAgent(definition);
|
|
1987
|
+
this.emit({ type: 'agent.registered', name: definition.name });
|
|
1988
|
+
this.observability?.logger?.info('Agent registered', { name: definition.name });
|
|
1989
|
+
}
|
|
1990
|
+
/**
|
|
1991
|
+
* Unregister an agent.
|
|
1992
|
+
*/
|
|
1993
|
+
unregisterAgent(name) {
|
|
1994
|
+
if (!this.agentRegistry) {
|
|
1995
|
+
return false;
|
|
1996
|
+
}
|
|
1997
|
+
const success = this.agentRegistry.unregisterAgent(name);
|
|
1998
|
+
if (success) {
|
|
1999
|
+
this.emit({ type: 'agent.unregistered', name });
|
|
2000
|
+
}
|
|
2001
|
+
return success;
|
|
2002
|
+
}
|
|
2003
|
+
/**
|
|
2004
|
+
* Spawn an agent to execute a task.
|
|
2005
|
+
* Returns the result when the agent completes.
|
|
2006
|
+
*/
|
|
2007
|
+
async spawnAgent(agentName, task) {
|
|
2008
|
+
if (!this.agentRegistry) {
|
|
2009
|
+
return {
|
|
2010
|
+
success: false,
|
|
2011
|
+
output: 'Agent registry not initialized',
|
|
2012
|
+
metrics: { tokens: 0, duration: 0, toolCalls: 0 },
|
|
2013
|
+
};
|
|
2014
|
+
}
|
|
2015
|
+
const agentDef = this.agentRegistry.getAgent(agentName);
|
|
2016
|
+
if (!agentDef) {
|
|
2017
|
+
return {
|
|
2018
|
+
success: false,
|
|
2019
|
+
output: `Agent not found: ${agentName}`,
|
|
2020
|
+
metrics: { tokens: 0, duration: 0, toolCalls: 0 },
|
|
2021
|
+
};
|
|
2022
|
+
}
|
|
2023
|
+
this.emit({ type: 'agent.spawn', agentId: `spawn-${Date.now()}`, name: agentName, task });
|
|
2024
|
+
this.observability?.logger?.info('Spawning agent', { name: agentName, task });
|
|
2025
|
+
const startTime = Date.now();
|
|
2026
|
+
try {
|
|
2027
|
+
// Filter tools for this agent
|
|
2028
|
+
const agentTools = filterToolsForAgent(agentDef, Array.from(this.tools.values()));
|
|
2029
|
+
// Resolve model - abstract tiers (fast/balanced/quality) should use parent's model
|
|
2030
|
+
// Only use agentDef.model if it's an actual model ID (contains '/')
|
|
2031
|
+
const resolvedModel = (agentDef.model && agentDef.model.includes('/'))
|
|
2032
|
+
? agentDef.model
|
|
2033
|
+
: this.config.model;
|
|
2034
|
+
// Create a sub-agent with the agent's config
|
|
2035
|
+
const subAgent = new ProductionAgent({
|
|
2036
|
+
provider: this.provider,
|
|
2037
|
+
tools: agentTools,
|
|
2038
|
+
systemPrompt: agentDef.systemPrompt,
|
|
2039
|
+
model: resolvedModel,
|
|
2040
|
+
maxIterations: agentDef.maxIterations || 30,
|
|
2041
|
+
// Inherit some features but keep subagent simpler
|
|
2042
|
+
memory: false,
|
|
2043
|
+
planning: false,
|
|
2044
|
+
reflection: false,
|
|
2045
|
+
observability: this.config.observability,
|
|
2046
|
+
sandbox: this.config.sandbox,
|
|
2047
|
+
humanInLoop: this.config.humanInLoop,
|
|
2048
|
+
executionPolicy: this.config.executionPolicy,
|
|
2049
|
+
threads: false,
|
|
2050
|
+
// Disable hooks console output in subagents - parent handles event display
|
|
2051
|
+
hooks: this.config.hooks === false ? false : {
|
|
2052
|
+
enabled: true,
|
|
2053
|
+
builtIn: { logging: false, timing: false, metrics: false },
|
|
2054
|
+
custom: [],
|
|
2055
|
+
},
|
|
2056
|
+
});
|
|
2057
|
+
// Forward events from subagent
|
|
2058
|
+
subAgent.subscribe(event => {
|
|
2059
|
+
// Just forward the event as-is - the agent.spawn event already logged the agent name
|
|
2060
|
+
this.emit(event);
|
|
2061
|
+
});
|
|
2062
|
+
// Run the task
|
|
2063
|
+
const result = await subAgent.run(task);
|
|
2064
|
+
const duration = Date.now() - startTime;
|
|
2065
|
+
const spawnResult = {
|
|
2066
|
+
success: result.success,
|
|
2067
|
+
output: result.response || result.error || '',
|
|
2068
|
+
metrics: {
|
|
2069
|
+
tokens: result.metrics.totalTokens,
|
|
2070
|
+
duration,
|
|
2071
|
+
toolCalls: result.metrics.toolCalls,
|
|
2072
|
+
},
|
|
2073
|
+
};
|
|
2074
|
+
this.emit({ type: 'agent.complete', agentId: agentName, success: result.success });
|
|
2075
|
+
await subAgent.cleanup();
|
|
2076
|
+
return spawnResult;
|
|
2077
|
+
}
|
|
2078
|
+
catch (err) {
|
|
2079
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
2080
|
+
this.emit({ type: 'agent.error', agentId: agentName, error });
|
|
2081
|
+
return {
|
|
2082
|
+
success: false,
|
|
2083
|
+
output: `Agent error: ${error}`,
|
|
2084
|
+
metrics: { tokens: 0, duration: Date.now() - startTime, toolCalls: 0 },
|
|
2085
|
+
};
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
/**
|
|
2089
|
+
* Get a formatted list of available agents.
|
|
2090
|
+
*/
|
|
2091
|
+
formatAgentList() {
|
|
2092
|
+
if (!this.agentRegistry) {
|
|
2093
|
+
return 'No agents available';
|
|
2094
|
+
}
|
|
2095
|
+
return formatAgentList(this.agentRegistry.getAllAgents());
|
|
2096
|
+
}
|
|
2097
|
+
// =========================================================================
|
|
2098
|
+
// NL ROUTING METHODS (Intelligent Agent Selection)
|
|
2099
|
+
// =========================================================================
|
|
2100
|
+
/**
|
|
2101
|
+
* Use LLM to suggest the best agent(s) for a given task.
|
|
2102
|
+
* Returns ranked suggestions with confidence scores.
|
|
2103
|
+
*/
|
|
2104
|
+
async suggestAgentForTask(task) {
|
|
2105
|
+
if (!this.agentRegistry) {
|
|
2106
|
+
return { suggestions: [], shouldDelegate: false };
|
|
2107
|
+
}
|
|
2108
|
+
// First, get keyword-based matches
|
|
2109
|
+
const keywordMatches = this.agentRegistry.findMatchingAgents(task, 5);
|
|
2110
|
+
// If no LLM provider, fall back to keyword matching
|
|
2111
|
+
if (!this.provider) {
|
|
2112
|
+
return {
|
|
2113
|
+
suggestions: keywordMatches.map((agent, i) => ({
|
|
2114
|
+
agent,
|
|
2115
|
+
confidence: 0.9 - i * 0.15,
|
|
2116
|
+
reason: `Keyword match: ${agent.capabilities?.slice(0, 3).join(', ') || agent.description.split('.')[0]}`,
|
|
2117
|
+
})),
|
|
2118
|
+
shouldDelegate: keywordMatches.length > 0,
|
|
2119
|
+
delegateAgent: keywordMatches[0]?.name,
|
|
2120
|
+
};
|
|
2121
|
+
}
|
|
2122
|
+
// Build agent descriptions for LLM
|
|
2123
|
+
const agents = this.agentRegistry.getAllAgents();
|
|
2124
|
+
const agentDescriptions = agents.map(a => `- ${a.name}: ${a.description}${a.capabilities?.length ? ` (can: ${a.capabilities.join(', ')})` : ''}`).join('\n');
|
|
2125
|
+
// Ask LLM to classify the task
|
|
2126
|
+
const classificationPrompt = `You are a task routing assistant. Given a user task, determine which specialized agent (if any) should handle it.
|
|
2127
|
+
|
|
2128
|
+
Available agents:
|
|
2129
|
+
${agentDescriptions}
|
|
2130
|
+
|
|
2131
|
+
User task: "${task}"
|
|
2132
|
+
|
|
2133
|
+
Respond in JSON format:
|
|
2134
|
+
{
|
|
2135
|
+
"analysis": "Brief analysis of what the task requires",
|
|
2136
|
+
"bestAgent": "agent name or null if main agent should handle it",
|
|
2137
|
+
"confidence": 0.0 to 1.0,
|
|
2138
|
+
"reason": "Why this agent is best suited",
|
|
2139
|
+
"alternatives": ["other suitable agents in order of preference"]
|
|
2140
|
+
}
|
|
2141
|
+
|
|
2142
|
+
If the task is a simple question or doesn't need specialized handling, set bestAgent to null.`;
|
|
2143
|
+
try {
|
|
2144
|
+
const response = await this.provider.chat([
|
|
2145
|
+
{ role: 'system', content: 'You are a task routing classifier. Always respond with valid JSON.' },
|
|
2146
|
+
{ role: 'user', content: classificationPrompt },
|
|
2147
|
+
], {
|
|
2148
|
+
model: this.config.model,
|
|
2149
|
+
});
|
|
2150
|
+
// Parse the JSON response
|
|
2151
|
+
const jsonMatch = response.content.match(/\{[\s\S]*\}/);
|
|
2152
|
+
if (!jsonMatch) {
|
|
2153
|
+
// Fallback to keyword matching
|
|
2154
|
+
return {
|
|
2155
|
+
suggestions: keywordMatches.map((agent, i) => ({
|
|
2156
|
+
agent,
|
|
2157
|
+
confidence: 0.7 - i * 0.1,
|
|
2158
|
+
reason: 'Keyword-based match',
|
|
2159
|
+
})),
|
|
2160
|
+
shouldDelegate: false,
|
|
2161
|
+
};
|
|
2162
|
+
}
|
|
2163
|
+
const classification = JSON.parse(jsonMatch[0]);
|
|
2164
|
+
// Build suggestions
|
|
2165
|
+
const suggestions = [];
|
|
2166
|
+
if (classification.bestAgent) {
|
|
2167
|
+
const bestAgent = this.agentRegistry.getAgent(classification.bestAgent);
|
|
2168
|
+
if (bestAgent) {
|
|
2169
|
+
suggestions.push({
|
|
2170
|
+
agent: bestAgent,
|
|
2171
|
+
confidence: classification.confidence,
|
|
2172
|
+
reason: classification.reason,
|
|
2173
|
+
});
|
|
2174
|
+
}
|
|
2175
|
+
}
|
|
2176
|
+
// Add alternatives
|
|
2177
|
+
for (let i = 0; i < (classification.alternatives || []).length; i++) {
|
|
2178
|
+
const altName = classification.alternatives[i];
|
|
2179
|
+
const altAgent = this.agentRegistry.getAgent(altName);
|
|
2180
|
+
if (altAgent && !suggestions.find(s => s.agent.name === altName)) {
|
|
2181
|
+
suggestions.push({
|
|
2182
|
+
agent: altAgent,
|
|
2183
|
+
confidence: Math.max(0.3, classification.confidence - 0.2 - i * 0.1),
|
|
2184
|
+
reason: 'Alternative option',
|
|
2185
|
+
});
|
|
2186
|
+
}
|
|
2187
|
+
}
|
|
2188
|
+
// Determine if we should delegate
|
|
2189
|
+
const shouldDelegate = classification.confidence >= 0.7 && classification.bestAgent !== null;
|
|
2190
|
+
return {
|
|
2191
|
+
suggestions,
|
|
2192
|
+
shouldDelegate,
|
|
2193
|
+
delegateAgent: shouldDelegate ? classification.bestAgent || undefined : undefined,
|
|
2194
|
+
};
|
|
2195
|
+
}
|
|
2196
|
+
catch (error) {
|
|
2197
|
+
// On error, fall back to keyword matching
|
|
2198
|
+
this.observability?.logger?.warn('Agent suggestion LLM call failed', { error });
|
|
2199
|
+
return {
|
|
2200
|
+
suggestions: keywordMatches.map((agent, i) => ({
|
|
2201
|
+
agent,
|
|
2202
|
+
confidence: 0.6 - i * 0.1,
|
|
2203
|
+
reason: 'Keyword-based fallback',
|
|
2204
|
+
})),
|
|
2205
|
+
shouldDelegate: false,
|
|
2206
|
+
};
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
/**
|
|
2210
|
+
* Run a task with automatic agent routing.
|
|
2211
|
+
* If a specialized agent is highly suited, delegates to it.
|
|
2212
|
+
* Otherwise runs with the main agent.
|
|
2213
|
+
*/
|
|
2214
|
+
async runWithAutoRouting(task, options = {}) {
|
|
2215
|
+
const { confidenceThreshold = 0.8, confirmDelegate } = options;
|
|
2216
|
+
// Get agent suggestions
|
|
2217
|
+
const { suggestions, shouldDelegate, delegateAgent } = await this.suggestAgentForTask(task);
|
|
2218
|
+
// Check if we should delegate
|
|
2219
|
+
if (shouldDelegate && delegateAgent) {
|
|
2220
|
+
const topSuggestion = suggestions[0];
|
|
2221
|
+
// If confirmation callback provided, ask user
|
|
2222
|
+
if (confirmDelegate && topSuggestion) {
|
|
2223
|
+
const confirmed = await confirmDelegate(topSuggestion.agent, topSuggestion.reason);
|
|
2224
|
+
if (!confirmed) {
|
|
2225
|
+
// User declined, run with main agent
|
|
2226
|
+
return this.run(task);
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
// Only auto-delegate if confidence exceeds threshold
|
|
2230
|
+
if (topSuggestion && topSuggestion.confidence >= confidenceThreshold) {
|
|
2231
|
+
this.emit({
|
|
2232
|
+
type: 'agent.spawn',
|
|
2233
|
+
agentId: `auto-${Date.now()}`,
|
|
2234
|
+
name: delegateAgent,
|
|
2235
|
+
task,
|
|
2236
|
+
});
|
|
2237
|
+
return this.spawnAgent(delegateAgent, task);
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
// Run with main agent
|
|
2241
|
+
return this.run(task);
|
|
2242
|
+
}
|
|
2243
|
+
// =========================================================================
|
|
2244
|
+
// CANCELLATION METHODS
|
|
2245
|
+
// =========================================================================
|
|
2246
|
+
/**
|
|
2247
|
+
* Request cancellation of the current operation.
|
|
2248
|
+
* The agent will attempt to stop gracefully.
|
|
2249
|
+
*/
|
|
2250
|
+
cancel(reason) {
|
|
2251
|
+
if (!this.cancellation) {
|
|
2252
|
+
console.warn('[ProductionAgent] Cancellation not enabled');
|
|
2253
|
+
return;
|
|
2254
|
+
}
|
|
2255
|
+
this.cancellation.cancel(reason);
|
|
2256
|
+
this.state.status = 'paused';
|
|
2257
|
+
this.observability?.logger?.info('Cancellation requested', { reason });
|
|
2258
|
+
}
|
|
2259
|
+
/**
|
|
2260
|
+
* Check if cancellation has been requested.
|
|
2261
|
+
*/
|
|
2262
|
+
isCancelled() {
|
|
2263
|
+
return this.cancellation?.isCancelled ?? false;
|
|
2264
|
+
}
|
|
2265
|
+
// =========================================================================
|
|
2266
|
+
// RESOURCE MONITORING METHODS
|
|
2267
|
+
// =========================================================================
|
|
2268
|
+
/**
|
|
2269
|
+
* Get current resource usage.
|
|
2270
|
+
*/
|
|
2271
|
+
getResourceUsage() {
|
|
2272
|
+
return this.resourceManager?.getUsage() || null;
|
|
2273
|
+
}
|
|
2274
|
+
/**
|
|
2275
|
+
* Get formatted resource status string.
|
|
2276
|
+
*/
|
|
2277
|
+
getResourceStatus() {
|
|
2278
|
+
return this.resourceManager?.getStatusString() || null;
|
|
2279
|
+
}
|
|
2280
|
+
// =========================================================================
|
|
2281
|
+
// LSP (LANGUAGE SERVER) METHODS
|
|
2282
|
+
// =========================================================================
|
|
2283
|
+
/**
|
|
2284
|
+
* Start LSP servers for the workspace.
|
|
2285
|
+
* Auto-detects languages based on project files.
|
|
2286
|
+
*/
|
|
2287
|
+
async startLSP(workspaceRoot) {
|
|
2288
|
+
if (!this.lspManager)
|
|
2289
|
+
return [];
|
|
2290
|
+
return this.lspManager.autoStart(workspaceRoot);
|
|
2291
|
+
}
|
|
2292
|
+
/**
|
|
2293
|
+
* Get code definition location.
|
|
2294
|
+
*/
|
|
2295
|
+
async getLSPDefinition(file, line, col) {
|
|
2296
|
+
return this.lspManager?.getDefinition(file, line, col) || null;
|
|
2297
|
+
}
|
|
2298
|
+
/**
|
|
2299
|
+
* Get code completions.
|
|
2300
|
+
*/
|
|
2301
|
+
async getLSPCompletions(file, line, col) {
|
|
2302
|
+
return this.lspManager?.getCompletions(file, line, col) || [];
|
|
2303
|
+
}
|
|
2304
|
+
/**
|
|
2305
|
+
* Get hover documentation.
|
|
2306
|
+
*/
|
|
2307
|
+
async getLSPHover(file, line, col) {
|
|
2308
|
+
return this.lspManager?.getHover(file, line, col) || null;
|
|
2309
|
+
}
|
|
2310
|
+
/**
|
|
2311
|
+
* Get all references to a symbol.
|
|
2312
|
+
*/
|
|
2313
|
+
async getLSPReferences(file, line, col) {
|
|
2314
|
+
return this.lspManager?.getReferences(file, line, col) || [];
|
|
2315
|
+
}
|
|
2316
|
+
/**
|
|
2317
|
+
* Get active LSP servers.
|
|
2318
|
+
*/
|
|
2319
|
+
getActiveLSPServers() {
|
|
2320
|
+
return this.lspManager?.getActiveServers() || [];
|
|
2321
|
+
}
|
|
2322
|
+
/**
|
|
2323
|
+
* Get LSP diagnostics for a file.
|
|
2324
|
+
*/
|
|
2325
|
+
getLSPDiagnostics(file) {
|
|
2326
|
+
return this.lspManager?.getDiagnostics(file) || [];
|
|
2327
|
+
}
|
|
2328
|
+
/**
|
|
2329
|
+
* Get the LSP manager instance (for advanced use).
|
|
2330
|
+
*/
|
|
2331
|
+
getLSPManager() {
|
|
2332
|
+
return this.lspManager;
|
|
2333
|
+
}
|
|
2334
|
+
/**
|
|
2335
|
+
* Get LSP-aware file tools.
|
|
2336
|
+
* These tools provide diagnostic feedback after edit/write operations.
|
|
2337
|
+
* Returns the tools if LSP is enabled, empty array otherwise.
|
|
2338
|
+
*/
|
|
2339
|
+
getLSPFileTools(options) {
|
|
2340
|
+
if (!this.lspManager) {
|
|
2341
|
+
return [];
|
|
2342
|
+
}
|
|
2343
|
+
return createLSPFileTools({
|
|
2344
|
+
lspManager: this.lspManager,
|
|
2345
|
+
diagnosticDelay: options?.diagnosticDelay ?? 500,
|
|
2346
|
+
includeWarnings: options?.includeWarnings ?? true,
|
|
2347
|
+
});
|
|
2348
|
+
}
|
|
2349
|
+
/**
|
|
2350
|
+
* Replace standard file tools with LSP-aware versions.
|
|
2351
|
+
* Call this after enabling LSP to get diagnostic feedback on edits.
|
|
2352
|
+
*/
|
|
2353
|
+
enableLSPFileTools(options) {
|
|
2354
|
+
if (!this.lspManager) {
|
|
2355
|
+
console.warn('[ProductionAgent] LSP not enabled, cannot enable LSP file tools');
|
|
2356
|
+
return;
|
|
2357
|
+
}
|
|
2358
|
+
const lspTools = this.getLSPFileTools(options);
|
|
2359
|
+
for (const tool of lspTools) {
|
|
2360
|
+
this.tools.set(tool.name, tool);
|
|
2361
|
+
}
|
|
2362
|
+
this.observability?.logger?.info('LSP file tools enabled', {
|
|
2363
|
+
tools: lspTools.map(t => t.name),
|
|
2364
|
+
});
|
|
2365
|
+
}
|
|
2366
|
+
// =========================================================================
|
|
2367
|
+
// SEMANTIC CACHE METHODS
|
|
2368
|
+
// =========================================================================
|
|
2369
|
+
/**
|
|
2370
|
+
* Check if a cached response exists for a similar query.
|
|
2371
|
+
*/
|
|
2372
|
+
async getCachedResponse(query) {
|
|
2373
|
+
const hit = await this.semanticCache?.get(query);
|
|
2374
|
+
if (hit) {
|
|
2375
|
+
return { response: hit.entry.response, similarity: hit.similarity };
|
|
2376
|
+
}
|
|
2377
|
+
return null;
|
|
2378
|
+
}
|
|
2379
|
+
/**
|
|
2380
|
+
* Cache an LLM response for a query.
|
|
2381
|
+
*/
|
|
2382
|
+
async cacheResponse(query, response, metadata) {
|
|
2383
|
+
return await this.semanticCache?.set(query, response, metadata) || null;
|
|
2384
|
+
}
|
|
2385
|
+
/**
|
|
2386
|
+
* Check if a similar query exists in cache (without retrieving).
|
|
2387
|
+
*/
|
|
2388
|
+
async hasCachedQuery(query) {
|
|
2389
|
+
return await this.semanticCache?.has(query) || false;
|
|
2390
|
+
}
|
|
2391
|
+
/**
|
|
2392
|
+
* Get cache statistics.
|
|
2393
|
+
*/
|
|
2394
|
+
getCacheStats() {
|
|
2395
|
+
return this.semanticCache?.getStats() || { size: 0, totalHits: 0, avgHits: 0, hitRate: 0, totalQueries: 0 };
|
|
2396
|
+
}
|
|
2397
|
+
/**
|
|
2398
|
+
* Clear the semantic cache.
|
|
2399
|
+
*/
|
|
2400
|
+
clearCache() {
|
|
2401
|
+
this.semanticCache?.clear();
|
|
2402
|
+
}
|
|
2403
|
+
// =========================================================================
|
|
2404
|
+
// MODE MANAGEMENT METHODS
|
|
2405
|
+
// =========================================================================
|
|
2406
|
+
/**
|
|
2407
|
+
* Get the current agent mode.
|
|
2408
|
+
*/
|
|
2409
|
+
getMode() {
|
|
2410
|
+
return this.modeManager.getMode();
|
|
2411
|
+
}
|
|
2412
|
+
/**
|
|
2413
|
+
* Set the agent mode.
|
|
2414
|
+
*/
|
|
2415
|
+
setMode(mode) {
|
|
2416
|
+
const parsed = typeof mode === 'string' ? parseMode(mode) : mode;
|
|
2417
|
+
if (parsed) {
|
|
2418
|
+
this.modeManager.setMode(parsed);
|
|
2419
|
+
this.emit({ type: 'mode.changed', from: this.getMode(), to: parsed });
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
/**
|
|
2423
|
+
* Cycle to the next mode (for Tab key).
|
|
2424
|
+
*/
|
|
2425
|
+
cycleMode() {
|
|
2426
|
+
return this.modeManager.cycleMode();
|
|
2427
|
+
}
|
|
2428
|
+
/**
|
|
2429
|
+
* Get all registered tools.
|
|
2430
|
+
*/
|
|
2431
|
+
getTools() {
|
|
2432
|
+
return Array.from(this.tools.values());
|
|
2433
|
+
}
|
|
2434
|
+
/**
|
|
2435
|
+
* Get available tools filtered by current mode.
|
|
2436
|
+
*/
|
|
2437
|
+
getModeFilteredTools() {
|
|
2438
|
+
return this.modeManager.filterTools(Array.from(this.tools.values()));
|
|
2439
|
+
}
|
|
2440
|
+
/**
|
|
2441
|
+
* Get mode info for display.
|
|
2442
|
+
*/
|
|
2443
|
+
getModeInfo() {
|
|
2444
|
+
return this.modeManager.getModeInfo();
|
|
2445
|
+
}
|
|
2446
|
+
/**
|
|
2447
|
+
* Format mode for terminal prompt.
|
|
2448
|
+
*/
|
|
2449
|
+
formatModePrompt() {
|
|
2450
|
+
return this.modeManager.formatModePrompt();
|
|
2451
|
+
}
|
|
2452
|
+
/**
|
|
2453
|
+
* Get list of all available modes.
|
|
2454
|
+
*/
|
|
2455
|
+
getAvailableModes() {
|
|
2456
|
+
return formatModeList();
|
|
2457
|
+
}
|
|
2458
|
+
/**
|
|
2459
|
+
* Get system prompt with mode-specific additions.
|
|
2460
|
+
*/
|
|
2461
|
+
getSystemPromptWithMode() {
|
|
2462
|
+
const base = this.config.systemPrompt;
|
|
2463
|
+
const modeAddition = this.modeManager.getSystemPromptAddition();
|
|
2464
|
+
return `${base}\n\n${modeAddition}`;
|
|
2465
|
+
}
|
|
2466
|
+
/**
|
|
2467
|
+
* Toggle between build and plan modes.
|
|
2468
|
+
*/
|
|
2469
|
+
togglePlanMode() {
|
|
2470
|
+
return this.modeManager.togglePlanMode();
|
|
2471
|
+
}
|
|
2472
|
+
// =========================================================================
|
|
2473
|
+
// PENDING PLAN METHODS (Plan Mode)
|
|
2474
|
+
// =========================================================================
|
|
2475
|
+
/**
|
|
2476
|
+
* Get the current pending plan.
|
|
2477
|
+
*/
|
|
2478
|
+
getPendingPlan() {
|
|
2479
|
+
return this.pendingPlanManager.getPendingPlan();
|
|
2480
|
+
}
|
|
2481
|
+
/**
|
|
2482
|
+
* Check if there's a pending plan awaiting approval.
|
|
2483
|
+
*/
|
|
2484
|
+
hasPendingPlan() {
|
|
2485
|
+
return this.pendingPlanManager.hasPendingPlan();
|
|
2486
|
+
}
|
|
2487
|
+
/**
|
|
2488
|
+
* Get formatted plan for display.
|
|
2489
|
+
*/
|
|
2490
|
+
formatPendingPlan() {
|
|
2491
|
+
return this.pendingPlanManager.formatPlan();
|
|
2492
|
+
}
|
|
2493
|
+
/**
|
|
2494
|
+
* Approve the pending plan and execute the changes.
|
|
2495
|
+
* @param count - If provided, only approve first N changes
|
|
2496
|
+
* @returns Result of executing the approved changes
|
|
2497
|
+
*/
|
|
2498
|
+
async approvePlan(count) {
|
|
2499
|
+
const result = this.pendingPlanManager.approve(count);
|
|
2500
|
+
if (result.changes.length === 0) {
|
|
2501
|
+
return { success: true, executed: 0, errors: [] };
|
|
2502
|
+
}
|
|
2503
|
+
// Switch to build mode for execution
|
|
2504
|
+
const previousMode = this.getMode();
|
|
2505
|
+
this.setMode('build');
|
|
2506
|
+
this.emit({ type: 'plan.approved', changeCount: result.changes.length });
|
|
2507
|
+
const errors = [];
|
|
2508
|
+
let executed = 0;
|
|
2509
|
+
// Execute each change
|
|
2510
|
+
for (let i = 0; i < result.changes.length; i++) {
|
|
2511
|
+
const change = result.changes[i];
|
|
2512
|
+
this.emit({ type: 'plan.executing', changeIndex: i, totalChanges: result.changes.length });
|
|
2513
|
+
try {
|
|
2514
|
+
const tool = this.tools.get(change.tool);
|
|
2515
|
+
if (!tool) {
|
|
2516
|
+
errors.push(`Unknown tool: ${change.tool}`);
|
|
2517
|
+
continue;
|
|
2518
|
+
}
|
|
2519
|
+
await tool.execute(change.args);
|
|
2520
|
+
executed++;
|
|
2521
|
+
}
|
|
2522
|
+
catch (err) {
|
|
2523
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
2524
|
+
errors.push(`${change.tool}: ${error}`);
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2527
|
+
// Restore previous mode if it wasn't build
|
|
2528
|
+
if (previousMode !== 'build' && previousMode !== 'plan') {
|
|
2529
|
+
this.setMode(previousMode);
|
|
2530
|
+
}
|
|
2531
|
+
return {
|
|
2532
|
+
success: errors.length === 0,
|
|
2533
|
+
executed,
|
|
2534
|
+
errors,
|
|
2535
|
+
};
|
|
2536
|
+
}
|
|
2537
|
+
/**
|
|
2538
|
+
* Reject the pending plan and discard all proposed changes.
|
|
2539
|
+
*/
|
|
2540
|
+
rejectPlan() {
|
|
2541
|
+
this.pendingPlanManager.reject();
|
|
2542
|
+
this.emit({ type: 'plan.rejected' });
|
|
2543
|
+
}
|
|
2544
|
+
/**
|
|
2545
|
+
* Clear the pending plan without emitting rejection event.
|
|
2546
|
+
*/
|
|
2547
|
+
clearPlan() {
|
|
2548
|
+
this.pendingPlanManager.clear();
|
|
2549
|
+
}
|
|
2550
|
+
/**
|
|
2551
|
+
* Get the number of pending changes.
|
|
2552
|
+
*/
|
|
2553
|
+
getPendingChangeCount() {
|
|
2554
|
+
return this.pendingPlanManager.getChangeCount();
|
|
2555
|
+
}
|
|
2556
|
+
// =========================================================================
|
|
2557
|
+
// SKILLS METHODS
|
|
2558
|
+
// =========================================================================
|
|
2559
|
+
/**
|
|
2560
|
+
* Get all loaded skills.
|
|
2561
|
+
*/
|
|
2562
|
+
getSkills() {
|
|
2563
|
+
return this.skillManager?.getAllSkills() || [];
|
|
2564
|
+
}
|
|
2565
|
+
/**
|
|
2566
|
+
* Get a specific skill by name.
|
|
2567
|
+
*/
|
|
2568
|
+
getSkill(name) {
|
|
2569
|
+
return this.skillManager?.getSkill(name);
|
|
2570
|
+
}
|
|
2571
|
+
/**
|
|
2572
|
+
* Activate a skill by name.
|
|
2573
|
+
*/
|
|
2574
|
+
activateSkill(name) {
|
|
2575
|
+
if (!this.skillManager)
|
|
2576
|
+
return false;
|
|
2577
|
+
return this.skillManager.activateSkill(name);
|
|
2578
|
+
}
|
|
2579
|
+
/**
|
|
2580
|
+
* Deactivate a skill by name.
|
|
2581
|
+
*/
|
|
2582
|
+
deactivateSkill(name) {
|
|
2583
|
+
if (!this.skillManager)
|
|
2584
|
+
return false;
|
|
2585
|
+
return this.skillManager.deactivateSkill(name);
|
|
2586
|
+
}
|
|
2587
|
+
/**
|
|
2588
|
+
* Get currently active skills.
|
|
2589
|
+
*/
|
|
2590
|
+
getActiveSkills() {
|
|
2591
|
+
return this.skillManager?.getActiveSkills() || [];
|
|
2592
|
+
}
|
|
2593
|
+
/**
|
|
2594
|
+
* Check if a skill is active.
|
|
2595
|
+
*/
|
|
2596
|
+
isSkillActive(name) {
|
|
2597
|
+
return this.skillManager?.isSkillActive(name) || false;
|
|
2598
|
+
}
|
|
2599
|
+
/**
|
|
2600
|
+
* Find skills matching a query (for auto-activation).
|
|
2601
|
+
*/
|
|
2602
|
+
findMatchingSkills(query) {
|
|
2603
|
+
return this.skillManager?.findMatchingSkills(query) || [];
|
|
2604
|
+
}
|
|
2605
|
+
/**
|
|
2606
|
+
* Get formatted list of available skills.
|
|
2607
|
+
*/
|
|
2608
|
+
formatSkillList() {
|
|
2609
|
+
if (!this.skillManager)
|
|
2610
|
+
return 'Skills not enabled';
|
|
2611
|
+
return formatSkillList(this.skillManager.getAllSkills());
|
|
2612
|
+
}
|
|
2613
|
+
/**
|
|
2614
|
+
* Cleanup resources.
|
|
2615
|
+
*/
|
|
2616
|
+
async cleanup() {
|
|
2617
|
+
this.cancellation?.cleanup();
|
|
2618
|
+
this.resourceManager?.cleanup();
|
|
2619
|
+
await this.lspManager?.cleanup();
|
|
2620
|
+
this.semanticCache?.cleanup();
|
|
2621
|
+
this.skillManager?.cleanup();
|
|
2622
|
+
await this.hooks?.cleanup();
|
|
2623
|
+
this.rules?.cleanup();
|
|
2624
|
+
this.agentRegistry?.cleanup();
|
|
2625
|
+
this.observability?.logger?.info('Agent cleanup complete');
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2628
|
+
// =============================================================================
|
|
2629
|
+
// FACTORY
|
|
2630
|
+
// =============================================================================
|
|
2631
|
+
/**
|
|
2632
|
+
* Create a production agent with the given configuration.
|
|
2633
|
+
*/
|
|
2634
|
+
export function createProductionAgent(config) {
|
|
2635
|
+
return new ProductionAgent(config);
|
|
2636
|
+
}
|
|
2637
|
+
// =============================================================================
|
|
2638
|
+
// BUILDER PATTERN
|
|
2639
|
+
// =============================================================================
|
|
2640
|
+
/**
|
|
2641
|
+
* Builder for creating customized production agents.
|
|
2642
|
+
*/
|
|
2643
|
+
export class ProductionAgentBuilder {
|
|
2644
|
+
config = {};
|
|
2645
|
+
/**
|
|
2646
|
+
* Set the LLM provider.
|
|
2647
|
+
*/
|
|
2648
|
+
provider(provider) {
|
|
2649
|
+
this.config.provider = provider;
|
|
2650
|
+
return this;
|
|
2651
|
+
}
|
|
2652
|
+
/**
|
|
2653
|
+
* Set the model.
|
|
2654
|
+
*/
|
|
2655
|
+
model(model) {
|
|
2656
|
+
this.config.model = model;
|
|
2657
|
+
return this;
|
|
2658
|
+
}
|
|
2659
|
+
/**
|
|
2660
|
+
* Set the system prompt.
|
|
2661
|
+
*/
|
|
2662
|
+
systemPrompt(prompt) {
|
|
2663
|
+
this.config.systemPrompt = prompt;
|
|
2664
|
+
return this;
|
|
2665
|
+
}
|
|
2666
|
+
/**
|
|
2667
|
+
* Add tools.
|
|
2668
|
+
*/
|
|
2669
|
+
tools(tools) {
|
|
2670
|
+
this.config.tools = tools;
|
|
2671
|
+
return this;
|
|
2672
|
+
}
|
|
2673
|
+
/**
|
|
2674
|
+
* Configure hooks.
|
|
2675
|
+
*/
|
|
2676
|
+
hooks(config) {
|
|
2677
|
+
this.config.hooks = config;
|
|
2678
|
+
return this;
|
|
2679
|
+
}
|
|
2680
|
+
/**
|
|
2681
|
+
* Configure plugins.
|
|
2682
|
+
*/
|
|
2683
|
+
plugins(config) {
|
|
2684
|
+
this.config.plugins = config;
|
|
2685
|
+
return this;
|
|
2686
|
+
}
|
|
2687
|
+
/**
|
|
2688
|
+
* Configure memory.
|
|
2689
|
+
*/
|
|
2690
|
+
memory(config) {
|
|
2691
|
+
this.config.memory = config;
|
|
2692
|
+
return this;
|
|
2693
|
+
}
|
|
2694
|
+
/**
|
|
2695
|
+
* Configure planning.
|
|
2696
|
+
*/
|
|
2697
|
+
planning(config) {
|
|
2698
|
+
this.config.planning = config;
|
|
2699
|
+
return this;
|
|
2700
|
+
}
|
|
2701
|
+
/**
|
|
2702
|
+
* Configure reflection.
|
|
2703
|
+
*/
|
|
2704
|
+
reflection(config) {
|
|
2705
|
+
this.config.reflection = config;
|
|
2706
|
+
return this;
|
|
2707
|
+
}
|
|
2708
|
+
/**
|
|
2709
|
+
* Configure observability.
|
|
2710
|
+
*/
|
|
2711
|
+
observability(config) {
|
|
2712
|
+
this.config.observability = config;
|
|
2713
|
+
return this;
|
|
2714
|
+
}
|
|
2715
|
+
/**
|
|
2716
|
+
* Configure sandbox.
|
|
2717
|
+
*/
|
|
2718
|
+
sandbox(config) {
|
|
2719
|
+
this.config.sandbox = config;
|
|
2720
|
+
return this;
|
|
2721
|
+
}
|
|
2722
|
+
/**
|
|
2723
|
+
* Configure human-in-the-loop.
|
|
2724
|
+
*/
|
|
2725
|
+
humanInLoop(config) {
|
|
2726
|
+
this.config.humanInLoop = config;
|
|
2727
|
+
return this;
|
|
2728
|
+
}
|
|
2729
|
+
/**
|
|
2730
|
+
* Configure routing.
|
|
2731
|
+
*/
|
|
2732
|
+
routing(config) {
|
|
2733
|
+
this.config.routing = config;
|
|
2734
|
+
return this;
|
|
2735
|
+
}
|
|
2736
|
+
/**
|
|
2737
|
+
* Configure multi-agent coordination (Lesson 17).
|
|
2738
|
+
*/
|
|
2739
|
+
multiAgent(config) {
|
|
2740
|
+
this.config.multiAgent = config;
|
|
2741
|
+
return this;
|
|
2742
|
+
}
|
|
2743
|
+
/**
|
|
2744
|
+
* Add a role to multi-agent config.
|
|
2745
|
+
*/
|
|
2746
|
+
addRole(role) {
|
|
2747
|
+
// Handle undefined, false, or disabled config
|
|
2748
|
+
if (!this.config.multiAgent) {
|
|
2749
|
+
this.config.multiAgent = { enabled: true, roles: [] };
|
|
2750
|
+
}
|
|
2751
|
+
// Ensure roles array exists
|
|
2752
|
+
const multiAgentConfig = this.config.multiAgent;
|
|
2753
|
+
if (!multiAgentConfig.roles) {
|
|
2754
|
+
multiAgentConfig.roles = [];
|
|
2755
|
+
}
|
|
2756
|
+
multiAgentConfig.roles.push(role);
|
|
2757
|
+
return this;
|
|
2758
|
+
}
|
|
2759
|
+
/**
|
|
2760
|
+
* Configure ReAct pattern (Lesson 18).
|
|
2761
|
+
*/
|
|
2762
|
+
react(config) {
|
|
2763
|
+
this.config.react = config;
|
|
2764
|
+
return this;
|
|
2765
|
+
}
|
|
2766
|
+
/**
|
|
2767
|
+
* Configure execution policies (Lesson 23).
|
|
2768
|
+
*/
|
|
2769
|
+
executionPolicy(config) {
|
|
2770
|
+
this.config.executionPolicy = config;
|
|
2771
|
+
return this;
|
|
2772
|
+
}
|
|
2773
|
+
/**
|
|
2774
|
+
* Configure thread management (Lesson 24).
|
|
2775
|
+
*/
|
|
2776
|
+
threads(config) {
|
|
2777
|
+
this.config.threads = config;
|
|
2778
|
+
return this;
|
|
2779
|
+
}
|
|
2780
|
+
/**
|
|
2781
|
+
* Configure skills system.
|
|
2782
|
+
*/
|
|
2783
|
+
skills(config) {
|
|
2784
|
+
this.config.skills = config;
|
|
2785
|
+
return this;
|
|
2786
|
+
}
|
|
2787
|
+
/**
|
|
2788
|
+
* Set max iterations.
|
|
2789
|
+
*/
|
|
2790
|
+
maxIterations(max) {
|
|
2791
|
+
this.config.maxIterations = max;
|
|
2792
|
+
return this;
|
|
2793
|
+
}
|
|
2794
|
+
/**
|
|
2795
|
+
* Set timeout.
|
|
2796
|
+
*/
|
|
2797
|
+
timeout(ms) {
|
|
2798
|
+
this.config.timeout = ms;
|
|
2799
|
+
return this;
|
|
2800
|
+
}
|
|
2801
|
+
/**
|
|
2802
|
+
* Disable a feature.
|
|
2803
|
+
*/
|
|
2804
|
+
disable(feature) {
|
|
2805
|
+
this.config[feature] = false;
|
|
2806
|
+
return this;
|
|
2807
|
+
}
|
|
2808
|
+
/**
|
|
2809
|
+
* Build the agent.
|
|
2810
|
+
*/
|
|
2811
|
+
build() {
|
|
2812
|
+
if (!this.config.provider) {
|
|
2813
|
+
throw new Error('Provider is required');
|
|
2814
|
+
}
|
|
2815
|
+
return new ProductionAgent(this.config);
|
|
2816
|
+
}
|
|
2817
|
+
}
|
|
2818
|
+
/**
|
|
2819
|
+
* Start building a production agent.
|
|
2820
|
+
*/
|
|
2821
|
+
export function buildAgent() {
|
|
2822
|
+
return new ProductionAgentBuilder();
|
|
2823
|
+
}
|
|
2824
|
+
//# sourceMappingURL=agent.js.map
|