attocode 0.2.3 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +67 -1
- package/README.md +65 -5
- package/dist/src/adapters.d.ts.map +1 -1
- package/dist/src/adapters.js +15 -11
- package/dist/src/adapters.js.map +1 -1
- package/dist/src/agent.d.ts +38 -98
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +505 -2892
- package/dist/src/agent.js.map +1 -1
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +2 -1
- package/dist/src/cli.js.map +1 -1
- package/dist/src/commands/handler.d.ts.map +1 -1
- package/dist/src/commands/handler.js +11 -3
- package/dist/src/commands/handler.js.map +1 -1
- package/dist/src/commands/init-commands.d.ts.map +1 -1
- package/dist/src/commands/init-commands.js +16 -1
- package/dist/src/commands/init-commands.js.map +1 -1
- package/dist/src/commands/init.d.ts.map +1 -1
- package/dist/src/commands/init.js +31 -0
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/config/base-types.d.ts +45 -0
- package/dist/src/config/base-types.d.ts.map +1 -0
- package/dist/src/config/base-types.js +9 -0
- package/dist/src/config/base-types.js.map +1 -0
- package/dist/src/config/config-manager.d.ts +35 -0
- package/dist/src/config/config-manager.d.ts.map +1 -0
- package/dist/src/config/config-manager.js +108 -0
- package/dist/src/config/config-manager.js.map +1 -0
- package/dist/src/config/index.d.ts +4 -0
- package/dist/src/config/index.d.ts.map +1 -0
- package/dist/src/config/index.js +3 -0
- package/dist/src/config/index.js.map +1 -0
- package/dist/src/config/schema.d.ts +1546 -0
- package/dist/src/config/schema.d.ts.map +1 -0
- package/dist/src/config/schema.js +268 -0
- package/dist/src/config/schema.js.map +1 -0
- package/dist/src/config.d.ts +4 -1
- package/dist/src/config.d.ts.map +1 -1
- package/dist/src/config.js +8 -12
- package/dist/src/config.js.map +1 -1
- package/dist/src/core/agent-state-machine.d.ts +131 -0
- package/dist/src/core/agent-state-machine.d.ts.map +1 -0
- package/dist/src/core/agent-state-machine.js +302 -0
- package/dist/src/core/agent-state-machine.js.map +1 -0
- package/dist/src/core/base-manager.d.ts +79 -0
- package/dist/src/core/base-manager.d.ts.map +1 -0
- package/dist/src/core/base-manager.js +170 -0
- package/dist/src/core/base-manager.js.map +1 -0
- package/dist/src/core/completion-analyzer.d.ts +15 -0
- package/dist/src/core/completion-analyzer.d.ts.map +1 -0
- package/dist/src/core/completion-analyzer.js +53 -0
- package/dist/src/core/completion-analyzer.js.map +1 -0
- package/dist/src/core/execution-loop.d.ts +46 -0
- package/dist/src/core/execution-loop.d.ts.map +1 -0
- package/dist/src/core/execution-loop.js +1258 -0
- package/dist/src/core/execution-loop.js.map +1 -0
- package/dist/src/core/index.d.ts +7 -0
- package/dist/src/core/index.d.ts.map +1 -1
- package/dist/src/core/index.js +9 -0
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/core/process-handlers.d.ts.map +1 -1
- package/dist/src/core/process-handlers.js +14 -0
- package/dist/src/core/process-handlers.js.map +1 -1
- package/dist/src/core/protocol/types.d.ts +12 -12
- package/dist/src/core/response-handler.d.ts +16 -0
- package/dist/src/core/response-handler.d.ts.map +1 -0
- package/dist/src/core/response-handler.js +234 -0
- package/dist/src/core/response-handler.js.map +1 -0
- package/dist/src/core/subagent-spawner.d.ts +43 -0
- package/dist/src/core/subagent-spawner.d.ts.map +1 -0
- package/dist/src/core/subagent-spawner.js +966 -0
- package/dist/src/core/subagent-spawner.js.map +1 -0
- package/dist/src/core/tool-executor.d.ts +59 -0
- package/dist/src/core/tool-executor.d.ts.map +1 -0
- package/dist/src/core/tool-executor.js +677 -0
- package/dist/src/core/tool-executor.js.map +1 -0
- package/dist/src/core/types.d.ts +133 -0
- package/dist/src/core/types.d.ts.map +1 -0
- package/dist/src/core/types.js +12 -0
- package/dist/src/core/types.js.map +1 -0
- package/dist/src/defaults.d.ts +2 -2
- package/dist/src/defaults.d.ts.map +1 -1
- package/dist/src/defaults.js +29 -1
- package/dist/src/defaults.js.map +1 -1
- package/dist/src/integrations/auto-compaction.d.ts.map +1 -1
- package/dist/src/integrations/auto-compaction.js +3 -2
- package/dist/src/integrations/auto-compaction.js.map +1 -1
- package/dist/src/integrations/budget-pool.d.ts +7 -0
- package/dist/src/integrations/budget-pool.d.ts.map +1 -1
- package/dist/src/integrations/budget-pool.js +43 -0
- package/dist/src/integrations/budget-pool.js.map +1 -1
- package/dist/src/integrations/codebase-ast.d.ts +52 -0
- package/dist/src/integrations/codebase-ast.d.ts.map +1 -0
- package/dist/src/integrations/codebase-ast.js +457 -0
- package/dist/src/integrations/codebase-ast.js.map +1 -0
- package/dist/src/integrations/codebase-context.d.ts +18 -0
- package/dist/src/integrations/codebase-context.d.ts.map +1 -1
- package/dist/src/integrations/codebase-context.js +197 -17
- package/dist/src/integrations/codebase-context.js.map +1 -1
- package/dist/src/integrations/compaction.d.ts.map +1 -1
- package/dist/src/integrations/compaction.js +14 -6
- package/dist/src/integrations/compaction.js.map +1 -1
- package/dist/src/integrations/context-engineering.d.ts +8 -0
- package/dist/src/integrations/context-engineering.d.ts.map +1 -1
- package/dist/src/integrations/context-engineering.js +19 -0
- package/dist/src/integrations/context-engineering.js.map +1 -1
- package/dist/src/integrations/economics.d.ts +25 -1
- package/dist/src/integrations/economics.d.ts.map +1 -1
- package/dist/src/integrations/economics.js +217 -38
- package/dist/src/integrations/economics.js.map +1 -1
- package/dist/src/integrations/edit-validator.d.ts +30 -0
- package/dist/src/integrations/edit-validator.d.ts.map +1 -0
- package/dist/src/integrations/edit-validator.js +85 -0
- package/dist/src/integrations/edit-validator.js.map +1 -0
- package/dist/src/integrations/file-cache.d.ts +7 -0
- package/dist/src/integrations/file-cache.d.ts.map +1 -1
- package/dist/src/integrations/file-cache.js +54 -0
- package/dist/src/integrations/file-cache.js.map +1 -1
- package/dist/src/integrations/health-check.d.ts.map +1 -1
- package/dist/src/integrations/health-check.js +3 -2
- package/dist/src/integrations/health-check.js.map +1 -1
- package/dist/src/integrations/hierarchical-config.d.ts +3 -0
- package/dist/src/integrations/hierarchical-config.d.ts.map +1 -1
- package/dist/src/integrations/hierarchical-config.js +3 -0
- package/dist/src/integrations/hierarchical-config.js.map +1 -1
- package/dist/src/integrations/hooks.d.ts +2 -0
- package/dist/src/integrations/hooks.d.ts.map +1 -1
- package/dist/src/integrations/hooks.js +99 -15
- package/dist/src/integrations/hooks.js.map +1 -1
- package/dist/src/integrations/index.d.ts +7 -0
- package/dist/src/integrations/index.d.ts.map +1 -1
- package/dist/src/integrations/index.js +9 -1
- package/dist/src/integrations/index.js.map +1 -1
- package/dist/src/integrations/logger.d.ts +104 -0
- package/dist/src/integrations/logger.d.ts.map +1 -0
- package/dist/src/integrations/logger.js +219 -0
- package/dist/src/integrations/logger.js.map +1 -0
- package/dist/src/integrations/lsp.d.ts.map +1 -1
- package/dist/src/integrations/lsp.js +5 -4
- package/dist/src/integrations/lsp.js.map +1 -1
- package/dist/src/integrations/mcp-client.d.ts.map +1 -1
- package/dist/src/integrations/mcp-client.js +8 -7
- package/dist/src/integrations/mcp-client.js.map +1 -1
- package/dist/src/integrations/observability.d.ts.map +1 -1
- package/dist/src/integrations/observability.js +5 -4
- package/dist/src/integrations/observability.js.map +1 -1
- package/dist/src/integrations/openrouter-pricing.d.ts.map +1 -1
- package/dist/src/integrations/openrouter-pricing.js +4 -3
- package/dist/src/integrations/openrouter-pricing.js.map +1 -1
- package/dist/src/integrations/persistence.d.ts.map +1 -1
- package/dist/src/integrations/persistence.js +5 -4
- package/dist/src/integrations/persistence.js.map +1 -1
- package/dist/src/integrations/planning.d.ts.map +1 -1
- package/dist/src/integrations/planning.js +5 -4
- package/dist/src/integrations/planning.js.map +1 -1
- package/dist/src/integrations/retry.d.ts +1 -0
- package/dist/src/integrations/retry.d.ts.map +1 -1
- package/dist/src/integrations/retry.js.map +1 -1
- package/dist/src/integrations/routing.d.ts.map +1 -1
- package/dist/src/integrations/routing.js +2 -1
- package/dist/src/integrations/routing.js.map +1 -1
- package/dist/src/integrations/safety.d.ts.map +1 -1
- package/dist/src/integrations/safety.js +13 -13
- package/dist/src/integrations/safety.js.map +1 -1
- package/dist/src/integrations/sandbox/docker.d.ts.map +1 -1
- package/dist/src/integrations/sandbox/docker.js +2 -1
- package/dist/src/integrations/sandbox/docker.js.map +1 -1
- package/dist/src/integrations/sandbox/index.d.ts.map +1 -1
- package/dist/src/integrations/sandbox/index.js +5 -4
- package/dist/src/integrations/sandbox/index.js.map +1 -1
- package/dist/src/integrations/session-store.d.ts +1 -0
- package/dist/src/integrations/session-store.d.ts.map +1 -1
- package/dist/src/integrations/session-store.js +1 -0
- package/dist/src/integrations/session-store.js.map +1 -1
- package/dist/src/integrations/shared-blackboard.d.ts +3 -0
- package/dist/src/integrations/shared-blackboard.d.ts.map +1 -1
- package/dist/src/integrations/shared-blackboard.js +47 -0
- package/dist/src/integrations/shared-blackboard.js.map +1 -1
- package/dist/src/integrations/smart-decomposer.d.ts +27 -0
- package/dist/src/integrations/smart-decomposer.d.ts.map +1 -1
- package/dist/src/integrations/smart-decomposer.js +414 -30
- package/dist/src/integrations/smart-decomposer.js.map +1 -1
- package/dist/src/integrations/sqlite-store.d.ts +2 -0
- package/dist/src/integrations/sqlite-store.d.ts.map +1 -1
- package/dist/src/integrations/sqlite-store.js +18 -6
- package/dist/src/integrations/sqlite-store.js.map +1 -1
- package/dist/src/integrations/swarm/failure-classifier.d.ts +11 -0
- package/dist/src/integrations/swarm/failure-classifier.d.ts.map +1 -0
- package/dist/src/integrations/swarm/failure-classifier.js +95 -0
- package/dist/src/integrations/swarm/failure-classifier.js.map +1 -0
- package/dist/src/integrations/swarm/model-selector.d.ts.map +1 -1
- package/dist/src/integrations/swarm/model-selector.js +2 -1
- package/dist/src/integrations/swarm/model-selector.js.map +1 -1
- package/dist/src/integrations/swarm/swarm-config-loader.d.ts +8 -0
- package/dist/src/integrations/swarm/swarm-config-loader.d.ts.map +1 -1
- package/dist/src/integrations/swarm/swarm-config-loader.js +95 -0
- package/dist/src/integrations/swarm/swarm-config-loader.js.map +1 -1
- package/dist/src/integrations/swarm/swarm-event-bridge.d.ts +74 -0
- package/dist/src/integrations/swarm/swarm-event-bridge.d.ts.map +1 -1
- package/dist/src/integrations/swarm/swarm-event-bridge.js +37 -0
- package/dist/src/integrations/swarm/swarm-event-bridge.js.map +1 -1
- package/dist/src/integrations/swarm/swarm-events.d.ts +3 -0
- package/dist/src/integrations/swarm/swarm-events.d.ts.map +1 -1
- package/dist/src/integrations/swarm/swarm-events.js +1 -1
- package/dist/src/integrations/swarm/swarm-events.js.map +1 -1
- package/dist/src/integrations/swarm/swarm-orchestrator.d.ts +23 -0
- package/dist/src/integrations/swarm/swarm-orchestrator.d.ts.map +1 -1
- package/dist/src/integrations/swarm/swarm-orchestrator.js +530 -55
- package/dist/src/integrations/swarm/swarm-orchestrator.js.map +1 -1
- package/dist/src/integrations/swarm/swarm-state-store.d.ts +4 -1
- package/dist/src/integrations/swarm/swarm-state-store.d.ts.map +1 -1
- package/dist/src/integrations/swarm/swarm-state-store.js +8 -1
- package/dist/src/integrations/swarm/swarm-state-store.js.map +1 -1
- package/dist/src/integrations/swarm/task-queue.d.ts +10 -0
- package/dist/src/integrations/swarm/task-queue.d.ts.map +1 -1
- package/dist/src/integrations/swarm/task-queue.js +36 -1
- package/dist/src/integrations/swarm/task-queue.js.map +1 -1
- package/dist/src/integrations/swarm/types.d.ts +41 -0
- package/dist/src/integrations/swarm/types.d.ts.map +1 -1
- package/dist/src/integrations/swarm/types.js +9 -0
- package/dist/src/integrations/swarm/types.js.map +1 -1
- package/dist/src/integrations/swarm/worker-pool.d.ts +12 -2
- package/dist/src/integrations/swarm/worker-pool.d.ts.map +1 -1
- package/dist/src/integrations/swarm/worker-pool.js +53 -4
- package/dist/src/integrations/swarm/worker-pool.js.map +1 -1
- package/dist/src/integrations/task-manager.d.ts +33 -1
- package/dist/src/integrations/task-manager.d.ts.map +1 -1
- package/dist/src/integrations/task-manager.js +78 -4
- package/dist/src/integrations/task-manager.js.map +1 -1
- package/dist/src/main.js +83 -32
- package/dist/src/main.js.map +1 -1
- package/dist/src/modes/repl.d.ts.map +1 -1
- package/dist/src/modes/repl.js +40 -8
- package/dist/src/modes/repl.js.map +1 -1
- package/dist/src/modes/tui.d.ts.map +1 -1
- package/dist/src/modes/tui.js +36 -6
- package/dist/src/modes/tui.js.map +1 -1
- package/dist/src/observability/tracer.d.ts.map +1 -1
- package/dist/src/observability/tracer.js +2 -1
- package/dist/src/observability/tracer.js.map +1 -1
- package/dist/src/persistence/schema.d.ts.map +1 -1
- package/dist/src/persistence/schema.js +11 -0
- package/dist/src/persistence/schema.js.map +1 -1
- package/dist/src/providers/adapters/anthropic.d.ts.map +1 -1
- package/dist/src/providers/adapters/anthropic.js +3 -2
- package/dist/src/providers/adapters/anthropic.js.map +1 -1
- package/dist/src/providers/adapters/openai.d.ts.map +1 -1
- package/dist/src/providers/adapters/openai.js +3 -2
- package/dist/src/providers/adapters/openai.js.map +1 -1
- package/dist/src/providers/adapters/openrouter.d.ts.map +1 -1
- package/dist/src/providers/adapters/openrouter.js +11 -11
- package/dist/src/providers/adapters/openrouter.js.map +1 -1
- package/dist/src/providers/circuit-breaker.d.ts +1 -0
- package/dist/src/providers/circuit-breaker.d.ts.map +1 -1
- package/dist/src/providers/circuit-breaker.js.map +1 -1
- package/dist/src/providers/provider.d.ts.map +1 -1
- package/dist/src/providers/provider.js +2 -1
- package/dist/src/providers/provider.js.map +1 -1
- package/dist/src/providers/resilient-provider.d.ts.map +1 -1
- package/dist/src/providers/resilient-provider.js +2 -1
- package/dist/src/providers/resilient-provider.js.map +1 -1
- package/dist/src/session-picker.d.ts.map +1 -1
- package/dist/src/session-picker.js +40 -5
- package/dist/src/session-picker.js.map +1 -1
- package/dist/src/shared/budget-tracker.d.ts +65 -0
- package/dist/src/shared/budget-tracker.d.ts.map +1 -0
- package/dist/src/shared/budget-tracker.js +128 -0
- package/dist/src/shared/budget-tracker.js.map +1 -0
- package/dist/src/shared/context-engine.d.ts +64 -0
- package/dist/src/shared/context-engine.d.ts.map +1 -0
- package/dist/src/shared/context-engine.js +117 -0
- package/dist/src/shared/context-engine.js.map +1 -0
- package/dist/src/shared/index.d.ts +12 -0
- package/dist/src/shared/index.d.ts.map +1 -0
- package/dist/src/shared/index.js +12 -0
- package/dist/src/shared/index.js.map +1 -0
- package/dist/src/shared/persistence.d.ts +57 -0
- package/dist/src/shared/persistence.d.ts.map +1 -0
- package/dist/src/shared/persistence.js +168 -0
- package/dist/src/shared/persistence.js.map +1 -0
- package/dist/src/shared/shared-context-state.d.ts +89 -0
- package/dist/src/shared/shared-context-state.d.ts.map +1 -0
- package/dist/src/shared/shared-context-state.js +175 -0
- package/dist/src/shared/shared-context-state.js.map +1 -0
- package/dist/src/shared/shared-economics-state.d.ts +61 -0
- package/dist/src/shared/shared-economics-state.d.ts.map +1 -0
- package/dist/src/shared/shared-economics-state.js +100 -0
- package/dist/src/shared/shared-economics-state.js.map +1 -0
- package/dist/src/tools/bash.d.ts +3 -3
- package/dist/src/tools/bash.d.ts.map +1 -1
- package/dist/src/tools/bash.js +2 -1
- package/dist/src/tools/bash.js.map +1 -1
- package/dist/src/tools/file.d.ts +3 -3
- package/dist/src/tools/permission.d.ts.map +1 -1
- package/dist/src/tools/permission.js +6 -5
- package/dist/src/tools/permission.js.map +1 -1
- package/dist/src/tools/types.d.ts +1 -0
- package/dist/src/tools/types.d.ts.map +1 -1
- package/dist/src/tools/types.js.map +1 -1
- package/dist/src/tracing/trace-collector.d.ts +125 -0
- package/dist/src/tracing/trace-collector.d.ts.map +1 -1
- package/dist/src/tracing/trace-collector.js +112 -5
- package/dist/src/tracing/trace-collector.js.map +1 -1
- package/dist/src/tracing/types.d.ts +96 -1
- package/dist/src/tracing/types.d.ts.map +1 -1
- package/dist/src/tracing/types.js.map +1 -1
- package/dist/src/tricks/failure-evidence.d.ts.map +1 -1
- package/dist/src/tricks/failure-evidence.js +2 -1
- package/dist/src/tricks/failure-evidence.js.map +1 -1
- package/dist/src/tui/app.d.ts +13 -0
- package/dist/src/tui/app.d.ts.map +1 -1
- package/dist/src/tui/app.js +129 -15
- package/dist/src/tui/app.js.map +1 -1
- package/dist/src/tui/components/ErrorBoundary.d.ts.map +1 -1
- package/dist/src/tui/components/ErrorBoundary.js +3 -2
- package/dist/src/tui/components/ErrorBoundary.js.map +1 -1
- package/dist/src/tui/event-display.d.ts.map +1 -1
- package/dist/src/tui/event-display.js +36 -62
- package/dist/src/tui/event-display.js.map +1 -1
- package/dist/src/tui/index.d.ts +4 -0
- package/dist/src/tui/index.d.ts.map +1 -1
- package/dist/src/tui/index.js +17 -0
- package/dist/src/tui/index.js.map +1 -1
- package/dist/src/types.d.ts +143 -1
- package/dist/src/types.d.ts.map +1 -1
- package/package.json +18 -3
|
@@ -0,0 +1,677 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool Executor Module (Phase 2.1)
|
|
3
|
+
*
|
|
4
|
+
* Extracted from ProductionAgent.executeToolCalls() and executeSingleToolCall().
|
|
5
|
+
* Handles batch grouping, parallel/sequential dispatch, plan mode interception,
|
|
6
|
+
* policy enforcement, safety validation, blackboard coordination, file cache,
|
|
7
|
+
* and actual tool execution.
|
|
8
|
+
*/
|
|
9
|
+
import { createComponentLogger } from '../integrations/logger.js';
|
|
10
|
+
// =============================================================================
|
|
11
|
+
// TOOL BATCHING CONSTANTS & UTILITIES (moved from agent.ts to break cycle)
|
|
12
|
+
// =============================================================================
|
|
13
|
+
/**
|
|
14
|
+
* Tools that are safe to execute in parallel (read-only, no side effects).
|
|
15
|
+
* These tools don't modify state, so running them concurrently is safe.
|
|
16
|
+
*/
|
|
17
|
+
export const PARALLELIZABLE_TOOLS = new Set([
|
|
18
|
+
'read_file', 'glob', 'grep', 'list_files', 'search_files',
|
|
19
|
+
'search_code', 'get_file_info',
|
|
20
|
+
'task_create', 'task_update', 'task_get', 'task_list',
|
|
21
|
+
]);
|
|
22
|
+
/**
|
|
23
|
+
* Tools that can run in parallel IF they target different files.
|
|
24
|
+
* write_file and edit_file on different paths are safe to parallelize.
|
|
25
|
+
*/
|
|
26
|
+
export const CONDITIONALLY_PARALLEL_TOOLS = new Set([
|
|
27
|
+
'write_file', 'edit_file',
|
|
28
|
+
]);
|
|
29
|
+
/**
|
|
30
|
+
* Extract the target file path from a tool call's arguments.
|
|
31
|
+
* Returns null if no file path can be determined.
|
|
32
|
+
*/
|
|
33
|
+
export function extractToolFilePath(toolCall) {
|
|
34
|
+
const args = toolCall;
|
|
35
|
+
for (const key of ['path', 'file_path', 'filename', 'file']) {
|
|
36
|
+
if (typeof args[key] === 'string')
|
|
37
|
+
return args[key];
|
|
38
|
+
}
|
|
39
|
+
if (args.args && typeof args.args === 'object') {
|
|
40
|
+
const nested = args.args;
|
|
41
|
+
for (const key of ['path', 'file_path', 'filename', 'file']) {
|
|
42
|
+
if (typeof nested[key] === 'string')
|
|
43
|
+
return nested[key];
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (args.input && typeof args.input === 'object') {
|
|
47
|
+
const input = args.input;
|
|
48
|
+
for (const key of ['path', 'file_path', 'filename', 'file']) {
|
|
49
|
+
if (typeof input[key] === 'string')
|
|
50
|
+
return input[key];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Check if a conditionally-parallel tool call conflicts with any tool
|
|
57
|
+
* in the current accumulator (same file path).
|
|
58
|
+
*/
|
|
59
|
+
function hasFileConflict(toolCall, accumulator) {
|
|
60
|
+
const path = extractToolFilePath(toolCall);
|
|
61
|
+
if (!path)
|
|
62
|
+
return true;
|
|
63
|
+
for (const existing of accumulator) {
|
|
64
|
+
const existingPath = extractToolFilePath(existing);
|
|
65
|
+
if (existingPath === path)
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Groups tool calls into batches for parallel/sequential execution.
|
|
72
|
+
*/
|
|
73
|
+
export function groupToolCallsIntoBatches(toolCalls, isParallelizable = (tc) => PARALLELIZABLE_TOOLS.has(tc.name), isConditionallyParallel = (tc) => CONDITIONALLY_PARALLEL_TOOLS.has(tc.name)) {
|
|
74
|
+
if (toolCalls.length === 0)
|
|
75
|
+
return [];
|
|
76
|
+
const batches = [];
|
|
77
|
+
let parallelAccum = [];
|
|
78
|
+
for (const toolCall of toolCalls) {
|
|
79
|
+
if (isParallelizable(toolCall)) {
|
|
80
|
+
parallelAccum.push(toolCall);
|
|
81
|
+
}
|
|
82
|
+
else if (isConditionallyParallel(toolCall)) {
|
|
83
|
+
if (!hasFileConflict(toolCall, parallelAccum)) {
|
|
84
|
+
parallelAccum.push(toolCall);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
if (parallelAccum.length > 0) {
|
|
88
|
+
batches.push(parallelAccum);
|
|
89
|
+
parallelAccum = [];
|
|
90
|
+
}
|
|
91
|
+
parallelAccum.push(toolCall);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
if (parallelAccum.length > 0) {
|
|
96
|
+
batches.push(parallelAccum);
|
|
97
|
+
parallelAccum = [];
|
|
98
|
+
}
|
|
99
|
+
batches.push([toolCall]);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (parallelAccum.length > 0) {
|
|
103
|
+
batches.push(parallelAccum);
|
|
104
|
+
}
|
|
105
|
+
return batches;
|
|
106
|
+
}
|
|
107
|
+
const log = createComponentLogger('ToolExecutor');
|
|
108
|
+
/**
|
|
109
|
+
* Execute tool calls with safety checks and execution policy enforcement.
|
|
110
|
+
* Parallelizable read-only tools are batched and executed concurrently.
|
|
111
|
+
*/
|
|
112
|
+
export async function executeToolCalls(toolCalls, ctx) {
|
|
113
|
+
const results = [];
|
|
114
|
+
// Circuit breaker state
|
|
115
|
+
const circuitBreakerThreshold = ctx.economics?.getBudget()?.tuning?.circuitBreakerFailureThreshold ?? 5;
|
|
116
|
+
let consecutiveFailures = 0;
|
|
117
|
+
let circuitBroken = false;
|
|
118
|
+
// Group consecutive parallelizable tool calls into batches
|
|
119
|
+
const batches = groupToolCallsIntoBatches(toolCalls);
|
|
120
|
+
// Execute batches: parallel batches use Promise.allSettled, sequential execute one-by-one
|
|
121
|
+
for (const batch of batches) {
|
|
122
|
+
// SAFEGUARD: Circuit breaker — skip remaining batches with stub results
|
|
123
|
+
if (circuitBroken) {
|
|
124
|
+
for (const tc of batch) {
|
|
125
|
+
results.push({
|
|
126
|
+
callId: tc.id,
|
|
127
|
+
result: `Error: Skipped — circuit breaker tripped after ${circuitBreakerThreshold} consecutive failures`,
|
|
128
|
+
error: `Circuit breaker tripped after ${circuitBreakerThreshold} consecutive failures`,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (batch.length > 1 && PARALLELIZABLE_TOOLS.has(batch[0].name)) {
|
|
134
|
+
// Execute parallelizable batch concurrently
|
|
135
|
+
const batchResults = await Promise.allSettled(batch.map(tc => executeSingleToolCall(tc, ctx)));
|
|
136
|
+
let batchFailures = 0;
|
|
137
|
+
for (const result of batchResults) {
|
|
138
|
+
if (result.status === 'fulfilled') {
|
|
139
|
+
results.push(result.value);
|
|
140
|
+
if (result.value.error) {
|
|
141
|
+
batchFailures++;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
const error = result.reason instanceof Error ? result.reason.message : String(result.reason);
|
|
146
|
+
results.push({ callId: 'unknown', result: `Error: ${error}`, error });
|
|
147
|
+
batchFailures++;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// If entire parallel batch failed, add to consecutive failures; any success resets
|
|
151
|
+
if (batchFailures === batchResults.length) {
|
|
152
|
+
consecutiveFailures += batchFailures;
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
consecutiveFailures = 0;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
// Execute sequentially
|
|
160
|
+
for (const tc of batch) {
|
|
161
|
+
if (circuitBroken) {
|
|
162
|
+
results.push({
|
|
163
|
+
callId: tc.id,
|
|
164
|
+
result: `Error: Skipped — circuit breaker tripped after ${circuitBreakerThreshold} consecutive failures`,
|
|
165
|
+
error: `Circuit breaker tripped after ${circuitBreakerThreshold} consecutive failures`,
|
|
166
|
+
});
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
const result = await executeSingleToolCall(tc, ctx);
|
|
170
|
+
results.push(result);
|
|
171
|
+
if (result.error) {
|
|
172
|
+
consecutiveFailures++;
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
consecutiveFailures = 0;
|
|
176
|
+
}
|
|
177
|
+
if (consecutiveFailures >= circuitBreakerThreshold) {
|
|
178
|
+
circuitBroken = true;
|
|
179
|
+
const skipped = toolCalls.length - results.length;
|
|
180
|
+
log.warn('Circuit breaker tripped — stopping tool execution', {
|
|
181
|
+
totalInBatch: toolCalls.length,
|
|
182
|
+
failures: consecutiveFailures,
|
|
183
|
+
threshold: circuitBreakerThreshold,
|
|
184
|
+
skipped,
|
|
185
|
+
});
|
|
186
|
+
ctx.emit({
|
|
187
|
+
type: 'safeguard.circuit_breaker',
|
|
188
|
+
totalInBatch: toolCalls.length,
|
|
189
|
+
failures: consecutiveFailures,
|
|
190
|
+
threshold: circuitBreakerThreshold,
|
|
191
|
+
skipped,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
// Check circuit breaker after parallel batches too
|
|
197
|
+
if (!circuitBroken && consecutiveFailures >= circuitBreakerThreshold) {
|
|
198
|
+
circuitBroken = true;
|
|
199
|
+
const skipped = toolCalls.length - results.length;
|
|
200
|
+
log.warn('Circuit breaker tripped — stopping tool execution', {
|
|
201
|
+
totalInBatch: toolCalls.length,
|
|
202
|
+
failures: consecutiveFailures,
|
|
203
|
+
threshold: circuitBreakerThreshold,
|
|
204
|
+
skipped,
|
|
205
|
+
});
|
|
206
|
+
ctx.emit({
|
|
207
|
+
type: 'safeguard.circuit_breaker',
|
|
208
|
+
totalInBatch: toolCalls.length,
|
|
209
|
+
failures: consecutiveFailures,
|
|
210
|
+
threshold: circuitBreakerThreshold,
|
|
211
|
+
skipped,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return results;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Execute a single tool call with all safety checks, tracing, and error handling.
|
|
219
|
+
*/
|
|
220
|
+
export async function executeSingleToolCall(toolCall, ctx) {
|
|
221
|
+
const spanId = ctx.observability?.tracer?.startSpan(`tool.${toolCall.name}`);
|
|
222
|
+
const executionId = `exec-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
223
|
+
ctx.emit({ type: 'tool.start', tool: toolCall.name, args: toolCall.arguments });
|
|
224
|
+
const startTime = Date.now();
|
|
225
|
+
// Short-circuit if tool call arguments failed to parse
|
|
226
|
+
if (toolCall.parseError) {
|
|
227
|
+
const errorMsg = `Tool arguments could not be parsed: ${toolCall.parseError}. Please retry with complete, valid JSON.`;
|
|
228
|
+
ctx.emit({ type: 'tool.blocked', tool: toolCall.name, reason: errorMsg });
|
|
229
|
+
ctx.traceCollector?.record({
|
|
230
|
+
type: 'tool.end',
|
|
231
|
+
data: { executionId, status: 'error', error: new Error(errorMsg), durationMs: Date.now() - startTime },
|
|
232
|
+
});
|
|
233
|
+
ctx.observability?.tracer?.endSpan(spanId);
|
|
234
|
+
return { callId: toolCall.id, result: `Error: ${errorMsg}`, error: errorMsg };
|
|
235
|
+
}
|
|
236
|
+
// Record tool start for tracing
|
|
237
|
+
ctx.traceCollector?.record({
|
|
238
|
+
type: 'tool.start',
|
|
239
|
+
data: {
|
|
240
|
+
executionId,
|
|
241
|
+
toolName: toolCall.name,
|
|
242
|
+
arguments: toolCall.arguments,
|
|
243
|
+
},
|
|
244
|
+
});
|
|
245
|
+
try {
|
|
246
|
+
// =====================================================================
|
|
247
|
+
// PLAN MODE WRITE INTERCEPTION
|
|
248
|
+
// =====================================================================
|
|
249
|
+
if (ctx.modeManager.shouldInterceptTool(toolCall.name, toolCall.arguments)) {
|
|
250
|
+
const reason = extractChangeReasoning(toolCall, ctx.state.messages);
|
|
251
|
+
// Start a new plan if needed
|
|
252
|
+
if (!ctx.pendingPlanManager.hasPendingPlan()) {
|
|
253
|
+
const lastUserMsg = [...ctx.state.messages].reverse().find(m => m.role === 'user');
|
|
254
|
+
const task = typeof lastUserMsg?.content === 'string' ? lastUserMsg.content : 'Plan';
|
|
255
|
+
ctx.pendingPlanManager.startPlan(task);
|
|
256
|
+
}
|
|
257
|
+
// Queue the write operation
|
|
258
|
+
const change = ctx.pendingPlanManager.addProposedChange(toolCall.name, toolCall.arguments, reason, toolCall.id);
|
|
259
|
+
// Emit event for UI
|
|
260
|
+
ctx.emit({
|
|
261
|
+
type: 'plan.change.queued',
|
|
262
|
+
tool: toolCall.name,
|
|
263
|
+
changeId: change?.id,
|
|
264
|
+
summary: formatToolArgsForPlan(toolCall.name, toolCall.arguments),
|
|
265
|
+
});
|
|
266
|
+
// Return a message indicating the change was queued
|
|
267
|
+
const queueMessage = `[PLAN MODE] Change queued for approval:\n` +
|
|
268
|
+
`Tool: ${toolCall.name}\n` +
|
|
269
|
+
`${formatToolArgsForPlan(toolCall.name, toolCall.arguments)}\n` +
|
|
270
|
+
`Use /show-plan to see all pending changes, /approve to execute, /reject to discard.`;
|
|
271
|
+
ctx.observability?.tracer?.endSpan(spanId);
|
|
272
|
+
return { callId: toolCall.id, result: queueMessage };
|
|
273
|
+
}
|
|
274
|
+
// =====================================================================
|
|
275
|
+
// EXECUTION POLICY ENFORCEMENT (Lesson 23)
|
|
276
|
+
// =====================================================================
|
|
277
|
+
let policyApprovedByUser = false;
|
|
278
|
+
if (ctx.executionPolicy) {
|
|
279
|
+
const policyContext = {
|
|
280
|
+
messages: ctx.state.messages,
|
|
281
|
+
currentMessage: ctx.state.messages.find(m => m.role === 'user')?.content,
|
|
282
|
+
previousToolCalls: [],
|
|
283
|
+
};
|
|
284
|
+
const evaluation = ctx.executionPolicy.evaluate(toolCall, policyContext);
|
|
285
|
+
// Emit policy event
|
|
286
|
+
ctx.emit({
|
|
287
|
+
type: 'policy.evaluated',
|
|
288
|
+
tool: toolCall.name,
|
|
289
|
+
policy: evaluation.policy,
|
|
290
|
+
reason: evaluation.reason,
|
|
291
|
+
});
|
|
292
|
+
// Emit decision transparency event
|
|
293
|
+
ctx.emit({
|
|
294
|
+
type: 'decision.tool',
|
|
295
|
+
tool: toolCall.name,
|
|
296
|
+
decision: evaluation.policy === 'forbidden' ? 'blocked'
|
|
297
|
+
: evaluation.policy === 'prompt' ? 'prompted'
|
|
298
|
+
: 'allowed',
|
|
299
|
+
policyMatch: evaluation.reason,
|
|
300
|
+
});
|
|
301
|
+
// Enhanced tracing: Record policy decision
|
|
302
|
+
ctx.traceCollector?.record({
|
|
303
|
+
type: 'decision',
|
|
304
|
+
data: {
|
|
305
|
+
type: 'policy',
|
|
306
|
+
decision: `Tool ${toolCall.name}: ${evaluation.policy}`,
|
|
307
|
+
outcome: evaluation.policy === 'forbidden' ? 'blocked'
|
|
308
|
+
: evaluation.policy === 'prompt' ? 'deferred'
|
|
309
|
+
: 'allowed',
|
|
310
|
+
reasoning: evaluation.reason,
|
|
311
|
+
factors: [
|
|
312
|
+
{ name: 'policy', value: evaluation.policy },
|
|
313
|
+
{ name: 'requiresApproval', value: evaluation.requiresApproval ?? false },
|
|
314
|
+
],
|
|
315
|
+
confidence: evaluation.intent?.confidence ?? 0.8,
|
|
316
|
+
},
|
|
317
|
+
});
|
|
318
|
+
// Handle forbidden policy - always block
|
|
319
|
+
if (evaluation.policy === 'forbidden') {
|
|
320
|
+
ctx.emit({
|
|
321
|
+
type: 'policy.tool.blocked',
|
|
322
|
+
tool: toolCall.name,
|
|
323
|
+
phase: 'enforced',
|
|
324
|
+
reason: `Forbidden by execution policy: ${evaluation.reason}`,
|
|
325
|
+
});
|
|
326
|
+
throw new Error(`Forbidden by policy: ${evaluation.reason}`);
|
|
327
|
+
}
|
|
328
|
+
// Handle prompt policy - requires approval
|
|
329
|
+
if (evaluation.policy === 'prompt' && evaluation.requiresApproval) {
|
|
330
|
+
const humanInLoop = ctx.safety?.humanInLoop;
|
|
331
|
+
if (humanInLoop) {
|
|
332
|
+
const approval = await withPausedDuration(ctx, () => humanInLoop.requestApproval(toolCall, `Policy requires approval: ${evaluation.reason}`));
|
|
333
|
+
if (!approval.approved) {
|
|
334
|
+
throw new Error(`Denied by user: ${approval.reason || 'No reason provided'}`);
|
|
335
|
+
}
|
|
336
|
+
policyApprovedByUser = true;
|
|
337
|
+
// Create a grant for future similar calls if approved
|
|
338
|
+
ctx.executionPolicy.createGrant({
|
|
339
|
+
toolName: toolCall.name,
|
|
340
|
+
grantedBy: 'user',
|
|
341
|
+
reason: 'Approved during execution',
|
|
342
|
+
maxUsages: 5,
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
// No approval handler — auto-allow with warning (defense-in-depth)
|
|
347
|
+
ctx.emit({
|
|
348
|
+
type: 'policy.tool.auto-allowed',
|
|
349
|
+
tool: toolCall.name,
|
|
350
|
+
reason: `No approval handler — auto-allowing (policy: ${evaluation.reason})`,
|
|
351
|
+
});
|
|
352
|
+
policyApprovedByUser = true;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
// Log intent classification if available
|
|
356
|
+
if (evaluation.intent) {
|
|
357
|
+
ctx.emit({
|
|
358
|
+
type: 'intent.classified',
|
|
359
|
+
tool: toolCall.name,
|
|
360
|
+
intent: evaluation.intent.type,
|
|
361
|
+
confidence: evaluation.intent.confidence,
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
// =====================================================================
|
|
366
|
+
// SAFETY VALIDATION (Lesson 20-21)
|
|
367
|
+
// =====================================================================
|
|
368
|
+
if (ctx.safety) {
|
|
369
|
+
const safety = ctx.safety;
|
|
370
|
+
const validation = await withPausedDuration(ctx, () => safety.validateAndApprove(toolCall, `Executing tool: ${toolCall.name}`, { skipHumanApproval: policyApprovedByUser }));
|
|
371
|
+
if (!validation.allowed) {
|
|
372
|
+
ctx.emit({
|
|
373
|
+
type: 'policy.tool.blocked',
|
|
374
|
+
tool: toolCall.name,
|
|
375
|
+
phase: 'enforced',
|
|
376
|
+
reason: validation.reason || 'Blocked by safety manager',
|
|
377
|
+
});
|
|
378
|
+
if (toolCall.name === 'bash') {
|
|
379
|
+
const args = toolCall.arguments;
|
|
380
|
+
ctx.emit({
|
|
381
|
+
type: 'policy.bash.blocked',
|
|
382
|
+
phase: 'enforced',
|
|
383
|
+
command: String(args.command || args.cmd || ''),
|
|
384
|
+
reason: validation.reason || 'Blocked by safety manager',
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
throw new Error(`Tool call blocked: ${validation.reason}`);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
// Get tool definition (with lazy-loading support for MCP tools)
|
|
391
|
+
let tool = ctx.tools.get(toolCall.name);
|
|
392
|
+
const wasPreloaded = !!tool;
|
|
393
|
+
if (!tool && ctx.toolResolver) {
|
|
394
|
+
const resolved = ctx.toolResolver(toolCall.name);
|
|
395
|
+
if (resolved) {
|
|
396
|
+
ctx.addTool(resolved);
|
|
397
|
+
tool = resolved;
|
|
398
|
+
if (process.env.DEBUG)
|
|
399
|
+
log.debug('Auto-loaded MCP tool', { tool: toolCall.name });
|
|
400
|
+
ctx.observability?.logger?.info('Tool auto-loaded', { tool: toolCall.name });
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
if (!tool) {
|
|
404
|
+
throw new Error(`Unknown tool: ${toolCall.name}`);
|
|
405
|
+
}
|
|
406
|
+
if (process.env.DEBUG && toolCall.name.startsWith('mcp_') && wasPreloaded) {
|
|
407
|
+
log.debug('Using pre-loaded MCP tool', { tool: toolCall.name });
|
|
408
|
+
}
|
|
409
|
+
// =====================================================================
|
|
410
|
+
// BLACKBOARD FILE COORDINATION (Parallel Subagent Support)
|
|
411
|
+
// =====================================================================
|
|
412
|
+
if (ctx.blackboard && (toolCall.name === 'write_file' || toolCall.name === 'edit_file')) {
|
|
413
|
+
const args = toolCall.arguments;
|
|
414
|
+
const filePath = String(args.path || args.file_path || '');
|
|
415
|
+
if (filePath) {
|
|
416
|
+
const agentId = ctx.agentId;
|
|
417
|
+
const claimed = ctx.blackboard.claim(filePath, agentId, 'write', {
|
|
418
|
+
ttl: 60000,
|
|
419
|
+
intent: `${toolCall.name}: ${filePath}`,
|
|
420
|
+
});
|
|
421
|
+
if (!claimed) {
|
|
422
|
+
const existingClaim = ctx.blackboard.getClaim(filePath);
|
|
423
|
+
throw new Error(`File "${filePath}" is being edited by another agent (${existingClaim?.agentId || 'unknown'}). ` +
|
|
424
|
+
`Wait for the other agent to complete or choose a different file.`);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
// FILE CACHE: Check cache for read_file operations before executing
|
|
429
|
+
if (ctx.fileCache && toolCall.name === 'read_file') {
|
|
430
|
+
const args = toolCall.arguments;
|
|
431
|
+
const readPath = String(args.path || '');
|
|
432
|
+
if (readPath) {
|
|
433
|
+
const cached = ctx.fileCache.get(readPath);
|
|
434
|
+
if (cached !== undefined) {
|
|
435
|
+
const lines = cached.split('\n').length;
|
|
436
|
+
const cacheResult = { success: true, output: cached, metadata: { lines, bytes: cached.length, cached: true } };
|
|
437
|
+
const duration = Date.now() - startTime;
|
|
438
|
+
ctx.traceCollector?.record({ type: 'tool.end', data: { executionId, status: 'success', result: cacheResult, durationMs: duration } });
|
|
439
|
+
ctx.observability?.metrics?.recordToolCall(toolCall.name, duration, true);
|
|
440
|
+
ctx.state.metrics.toolCalls++;
|
|
441
|
+
ctx.emit({ type: 'tool.complete', tool: toolCall.name, result: cacheResult });
|
|
442
|
+
ctx.observability?.tracer?.endSpan(spanId);
|
|
443
|
+
return {
|
|
444
|
+
callId: toolCall.id,
|
|
445
|
+
result: typeof cacheResult === 'string' ? cacheResult : JSON.stringify(cacheResult),
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
// Execute tool (with sandbox if available)
|
|
451
|
+
let result;
|
|
452
|
+
if (ctx.safety?.sandbox) {
|
|
453
|
+
const isSpawnAgent = toolCall.name === 'spawn_agent';
|
|
454
|
+
const isSpawnParallel = toolCall.name === 'spawn_agents_parallel';
|
|
455
|
+
const isSubagentTool = isSpawnAgent || isSpawnParallel;
|
|
456
|
+
const subagentConfig = ctx.config.subagent;
|
|
457
|
+
const hasSubagentConfig = subagentConfig !== false && subagentConfig !== undefined;
|
|
458
|
+
const subagentTimeout = hasSubagentConfig
|
|
459
|
+
? subagentConfig.defaultTimeout ?? 600000
|
|
460
|
+
: 600000;
|
|
461
|
+
const toolTimeout = isSubagentTool ? subagentTimeout + 30000 : undefined;
|
|
462
|
+
result = await ctx.safety.sandbox.executeWithLimits(() => tool.execute(toolCall.arguments), toolTimeout);
|
|
463
|
+
}
|
|
464
|
+
else {
|
|
465
|
+
result = await tool.execute(toolCall.arguments);
|
|
466
|
+
}
|
|
467
|
+
const duration = Date.now() - startTime;
|
|
468
|
+
// Record tool completion for tracing
|
|
469
|
+
ctx.traceCollector?.record({
|
|
470
|
+
type: 'tool.end',
|
|
471
|
+
data: {
|
|
472
|
+
executionId,
|
|
473
|
+
status: 'success',
|
|
474
|
+
result,
|
|
475
|
+
durationMs: duration,
|
|
476
|
+
},
|
|
477
|
+
});
|
|
478
|
+
// Record metrics
|
|
479
|
+
ctx.observability?.metrics?.recordToolCall(toolCall.name, duration, true);
|
|
480
|
+
ctx.state.metrics.toolCalls++;
|
|
481
|
+
ctx.emit({ type: 'tool.complete', tool: toolCall.name, result });
|
|
482
|
+
// FILE CACHE: Store read results and invalidate on writes
|
|
483
|
+
if (ctx.fileCache) {
|
|
484
|
+
const args = toolCall.arguments;
|
|
485
|
+
const filePath = String(args.path || args.file_path || '');
|
|
486
|
+
if (toolCall.name === 'read_file' && filePath) {
|
|
487
|
+
const resultObj = result;
|
|
488
|
+
if (resultObj?.success && typeof resultObj.output === 'string') {
|
|
489
|
+
ctx.fileCache.set(filePath, resultObj.output);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
else if ((toolCall.name === 'write_file' || toolCall.name === 'edit_file' || toolCall.name === 'undo_file_change') && filePath) {
|
|
493
|
+
ctx.fileCache.invalidate(filePath);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
// Emit tool insight with result summary
|
|
497
|
+
const summary = summarizeToolResult(toolCall.name, result);
|
|
498
|
+
ctx.emit({
|
|
499
|
+
type: 'insight.tool',
|
|
500
|
+
tool: toolCall.name,
|
|
501
|
+
summary,
|
|
502
|
+
durationMs: duration,
|
|
503
|
+
success: true,
|
|
504
|
+
});
|
|
505
|
+
// Release blackboard claim after successful file write
|
|
506
|
+
if (ctx.blackboard && (toolCall.name === 'write_file' || toolCall.name === 'edit_file')) {
|
|
507
|
+
const args = toolCall.arguments;
|
|
508
|
+
const filePath = String(args.path || args.file_path || '');
|
|
509
|
+
if (filePath) {
|
|
510
|
+
const agentId = ctx.agentId;
|
|
511
|
+
ctx.blackboard.release(filePath, agentId);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
// Self-improvement: record success pattern
|
|
515
|
+
ctx.selfImprovement?.recordSuccess(toolCall.name, toolCall.arguments, typeof result === 'string' ? result.slice(0, 200) : JSON.stringify(result).slice(0, 200));
|
|
516
|
+
ctx.observability?.tracer?.endSpan(spanId);
|
|
517
|
+
return { callId: toolCall.id, result };
|
|
518
|
+
}
|
|
519
|
+
catch (err) {
|
|
520
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
521
|
+
const duration = Date.now() - startTime;
|
|
522
|
+
// Record tool error for tracing
|
|
523
|
+
ctx.traceCollector?.record({
|
|
524
|
+
type: 'tool.end',
|
|
525
|
+
data: {
|
|
526
|
+
executionId,
|
|
527
|
+
status: error.message.includes('Blocked') || error.message.includes('Policy') ? 'blocked' : 'error',
|
|
528
|
+
error,
|
|
529
|
+
durationMs: duration,
|
|
530
|
+
},
|
|
531
|
+
});
|
|
532
|
+
ctx.observability?.metrics?.recordToolCall(toolCall.name, duration, false);
|
|
533
|
+
ctx.observability?.tracer?.recordError(error);
|
|
534
|
+
ctx.observability?.tracer?.endSpan(spanId);
|
|
535
|
+
// FAILURE EVIDENCE RECORDING (Trick S)
|
|
536
|
+
ctx.contextEngineering?.recordFailure({
|
|
537
|
+
action: toolCall.name,
|
|
538
|
+
args: toolCall.arguments,
|
|
539
|
+
error,
|
|
540
|
+
intent: `Execute tool ${toolCall.name}`,
|
|
541
|
+
});
|
|
542
|
+
// FILE CACHE INVALIDATION ON FAILURE — ensure stale cache doesn't cause repeated failures
|
|
543
|
+
if (ctx.fileCache && ['write_file', 'edit_file'].includes(toolCall.name)) {
|
|
544
|
+
const args = toolCall.arguments;
|
|
545
|
+
const filePath = String(args.path || args.file_path || '');
|
|
546
|
+
if (filePath) {
|
|
547
|
+
ctx.fileCache.invalidate(filePath);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
// Self-improvement: enhance error message with diagnosis
|
|
551
|
+
if (ctx.selfImprovement) {
|
|
552
|
+
const enhanced = ctx.selfImprovement.enhanceErrorMessage(toolCall.name, error.message, toolCall.arguments);
|
|
553
|
+
ctx.emit({ type: 'tool.blocked', tool: toolCall.name, reason: enhanced });
|
|
554
|
+
return { callId: toolCall.id, result: `Error: ${enhanced}`, error: enhanced };
|
|
555
|
+
}
|
|
556
|
+
ctx.emit({ type: 'tool.blocked', tool: toolCall.name, reason: error.message });
|
|
557
|
+
return { callId: toolCall.id, result: `Error: ${error.message}`, error: error.message };
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
// =============================================================================
|
|
561
|
+
// HELPER FUNCTIONS (extracted from ProductionAgent private methods)
|
|
562
|
+
// =============================================================================
|
|
563
|
+
/**
|
|
564
|
+
* Execute an async callback while excluding wall-clock wait time from duration budgeting.
|
|
565
|
+
*/
|
|
566
|
+
async function withPausedDuration(ctx, fn) {
|
|
567
|
+
ctx.economics?.pauseDuration();
|
|
568
|
+
try {
|
|
569
|
+
return await fn();
|
|
570
|
+
}
|
|
571
|
+
finally {
|
|
572
|
+
ctx.economics?.resumeDuration();
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* Create a brief summary of a tool result for insight display.
|
|
577
|
+
*/
|
|
578
|
+
export function summarizeToolResult(toolName, result) {
|
|
579
|
+
if (result === null || result === undefined) {
|
|
580
|
+
return 'No output';
|
|
581
|
+
}
|
|
582
|
+
const resultStr = typeof result === 'string' ? result : JSON.stringify(result);
|
|
583
|
+
if (toolName === 'list_files' || toolName === 'glob') {
|
|
584
|
+
const lines = resultStr.split('\n').filter(l => l.trim());
|
|
585
|
+
return `Found ${lines.length} file${lines.length !== 1 ? 's' : ''}`;
|
|
586
|
+
}
|
|
587
|
+
if (toolName === 'bash' || toolName === 'execute_command') {
|
|
588
|
+
const lines = resultStr.split('\n').filter(l => l.trim());
|
|
589
|
+
if (resultStr.includes('exit code: 0') || !resultStr.includes('exit code:')) {
|
|
590
|
+
return lines.length > 1 ? `Success (${lines.length} lines)` : 'Success';
|
|
591
|
+
}
|
|
592
|
+
return `Failed - ${lines[0]?.slice(0, 50) || 'see output'}`;
|
|
593
|
+
}
|
|
594
|
+
if (toolName === 'read_file') {
|
|
595
|
+
const lines = resultStr.split('\n').length;
|
|
596
|
+
return `Read ${lines} line${lines !== 1 ? 's' : ''}`;
|
|
597
|
+
}
|
|
598
|
+
if (toolName === 'write_file' || toolName === 'edit_file') {
|
|
599
|
+
return 'File updated';
|
|
600
|
+
}
|
|
601
|
+
if (toolName === 'search' || toolName === 'grep') {
|
|
602
|
+
const matches = (resultStr.match(/\n/g) || []).length;
|
|
603
|
+
return `${matches} match${matches !== 1 ? 'es' : ''}`;
|
|
604
|
+
}
|
|
605
|
+
if (resultStr.length <= 50) {
|
|
606
|
+
return resultStr;
|
|
607
|
+
}
|
|
608
|
+
return `${resultStr.slice(0, 47)}...`;
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* Format tool arguments for plan display.
|
|
612
|
+
*/
|
|
613
|
+
export function formatToolArgsForPlan(toolName, args) {
|
|
614
|
+
if (toolName === 'write_file') {
|
|
615
|
+
const path = args.path || args.file_path;
|
|
616
|
+
const content = String(args.content || '');
|
|
617
|
+
const preview = content.slice(0, 100).replace(/\n/g, '\\n');
|
|
618
|
+
return `File: ${path}\nContent preview: ${preview}${content.length > 100 ? '...' : ''}`;
|
|
619
|
+
}
|
|
620
|
+
if (toolName === 'edit_file') {
|
|
621
|
+
const path = args.path || args.file_path;
|
|
622
|
+
return `File: ${path}\nOld: ${String(args.old_string || args.search || '').slice(0, 50)}...\nNew: ${String(args.new_string || args.replace || '').slice(0, 50)}...`;
|
|
623
|
+
}
|
|
624
|
+
if (toolName === 'bash') {
|
|
625
|
+
return `Command: ${String(args.command || '').slice(0, 100)}`;
|
|
626
|
+
}
|
|
627
|
+
if (toolName === 'delete_file') {
|
|
628
|
+
return `Delete: ${args.path || args.file_path}`;
|
|
629
|
+
}
|
|
630
|
+
if (toolName === 'spawn_agent' || toolName === 'researcher') {
|
|
631
|
+
const task = String(args.task || args.prompt || args.goal || '');
|
|
632
|
+
const model = args.model ? ` (${args.model})` : '';
|
|
633
|
+
const firstLine = task.split('\n')[0].slice(0, 100);
|
|
634
|
+
return `${firstLine}${task.length > 100 ? '...' : ''}${model}`;
|
|
635
|
+
}
|
|
636
|
+
return `Args: ${JSON.stringify(args).slice(0, 100)}...`;
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Extract contextual reasoning for a proposed change in plan mode.
|
|
640
|
+
*/
|
|
641
|
+
export function extractChangeReasoning(toolCall, messages) {
|
|
642
|
+
const assistantMsgs = messages
|
|
643
|
+
.filter(m => m.role === 'assistant' && typeof m.content === 'string')
|
|
644
|
+
.slice(-3)
|
|
645
|
+
.reverse();
|
|
646
|
+
if (assistantMsgs.length === 0) {
|
|
647
|
+
return `Proposed change: ${toolCall.name}`;
|
|
648
|
+
}
|
|
649
|
+
const lastMsg = assistantMsgs[0];
|
|
650
|
+
const content = lastMsg.content;
|
|
651
|
+
if (toolCall.name === 'spawn_agent') {
|
|
652
|
+
const args = toolCall.arguments;
|
|
653
|
+
const task = String(args.task || args.prompt || args.goal || '');
|
|
654
|
+
if (task.length > 0) {
|
|
655
|
+
const firstPara = task.split(/\n\n/)[0];
|
|
656
|
+
return firstPara.length > 500 ? firstPara.slice(0, 500) + '...' : firstPara;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
if (['write_file', 'edit_file'].includes(toolCall.name)) {
|
|
660
|
+
const args = toolCall.arguments;
|
|
661
|
+
const path = String(args.path || args.file_path || '');
|
|
662
|
+
if (path && content.toLowerCase().includes(path.toLowerCase().split('/').pop() || '')) {
|
|
663
|
+
const sentences = content.split(/[.!?\n]+/).filter(s => s.toLowerCase().includes(path.toLowerCase().split('/').pop() || ''));
|
|
664
|
+
if (sentences.length > 0) {
|
|
665
|
+
const relevant = sentences.slice(0, 2).join('. ').trim();
|
|
666
|
+
return relevant.length > 500 ? relevant.slice(0, 500) + '...' : relevant;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
const paragraphs = content.split(/\n\n+/).filter(p => p.trim().length > 20);
|
|
671
|
+
if (paragraphs.length > 0) {
|
|
672
|
+
const firstPara = paragraphs[0].trim();
|
|
673
|
+
return firstPara.length > 500 ? firstPara.slice(0, 500) + '...' : firstPara;
|
|
674
|
+
}
|
|
675
|
+
return content.length > 500 ? content.slice(0, 500) + '...' : content;
|
|
676
|
+
}
|
|
677
|
+
//# sourceMappingURL=tool-executor.js.map
|