@calliopelabs/cli 0.8.20 → 2.0.2
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/dist/agents/agent-config-loader.d.ts +60 -0
- package/dist/agents/agent-config-loader.d.ts.map +1 -0
- package/dist/agents/agent-config-loader.js +402 -0
- package/dist/agents/agent-config-loader.js.map +1 -0
- package/dist/agents/agent-config-presets.d.ts +10 -0
- package/dist/agents/agent-config-presets.d.ts.map +1 -0
- package/dist/agents/agent-config-presets.js +940 -0
- package/dist/agents/agent-config-presets.js.map +1 -0
- package/dist/agents/agent-config-types.d.ts +145 -0
- package/dist/agents/agent-config-types.d.ts.map +1 -0
- package/dist/agents/agent-config-types.js +12 -0
- package/dist/agents/agent-config-types.js.map +1 -0
- package/dist/{agterm → agents}/agent-detection.d.ts +1 -1
- package/dist/{agterm → agents}/agent-detection.d.ts.map +1 -1
- package/dist/{agterm → agents}/agent-detection.js +21 -5
- package/dist/agents/agent-detection.js.map +1 -0
- package/dist/agents/aggregator.d.ts +19 -0
- package/dist/agents/aggregator.d.ts.map +1 -0
- package/dist/agents/aggregator.js +141 -0
- package/dist/agents/aggregator.js.map +1 -0
- package/dist/{agterm → agents}/cli-backend.d.ts +1 -1
- package/dist/{agterm → agents}/cli-backend.d.ts.map +1 -1
- package/dist/{agterm → agents}/cli-backend.js +90 -12
- package/dist/agents/cli-backend.js.map +1 -0
- package/dist/agents/council-types.d.ts +113 -0
- package/dist/agents/council-types.d.ts.map +1 -0
- package/dist/agents/council-types.js +81 -0
- package/dist/agents/council-types.js.map +1 -0
- package/dist/agents/council.d.ts +107 -0
- package/dist/agents/council.d.ts.map +1 -0
- package/dist/agents/council.js +586 -0
- package/dist/agents/council.js.map +1 -0
- package/dist/agents/decomposer.d.ts +33 -0
- package/dist/agents/decomposer.d.ts.map +1 -0
- package/dist/agents/decomposer.js +138 -0
- package/dist/agents/decomposer.js.map +1 -0
- package/dist/agents/dynamic-tools.d.ts +52 -0
- package/dist/agents/dynamic-tools.d.ts.map +1 -0
- package/dist/agents/dynamic-tools.js +395 -0
- package/dist/agents/dynamic-tools.js.map +1 -0
- package/dist/agents/index.d.ts +29 -0
- package/dist/agents/index.d.ts.map +1 -0
- package/dist/agents/index.js +29 -0
- package/dist/agents/index.js.map +1 -0
- package/dist/agents/installer.d.ts +39 -0
- package/dist/agents/installer.d.ts.map +1 -0
- package/dist/agents/installer.js +205 -0
- package/dist/agents/installer.js.map +1 -0
- package/dist/{agterm → agents}/orchestrator.d.ts +7 -2
- package/dist/agents/orchestrator.d.ts.map +1 -0
- package/dist/{agterm → agents}/orchestrator.js +22 -2
- package/dist/agents/orchestrator.js.map +1 -0
- package/dist/agents/sdk-backend.d.ts +63 -0
- package/dist/agents/sdk-backend.d.ts.map +1 -0
- package/dist/agents/sdk-backend.js +489 -0
- package/dist/agents/sdk-backend.js.map +1 -0
- package/dist/agents/swarm-types.d.ts +83 -0
- package/dist/agents/swarm-types.d.ts.map +1 -0
- package/dist/agents/swarm-types.js +20 -0
- package/dist/agents/swarm-types.js.map +1 -0
- package/dist/agents/swarm.d.ts +74 -0
- package/dist/agents/swarm.d.ts.map +1 -0
- package/dist/agents/swarm.js +307 -0
- package/dist/agents/swarm.js.map +1 -0
- package/dist/{agterm → agents}/tools.d.ts +7 -5
- package/dist/agents/tools.d.ts.map +1 -0
- package/dist/agents/tools.js +776 -0
- package/dist/agents/tools.js.map +1 -0
- package/dist/{agterm → agents}/types.d.ts +14 -2
- package/dist/agents/types.d.ts.map +1 -0
- package/dist/{agterm → agents}/types.js +2 -2
- package/dist/agents/types.js.map +1 -0
- package/dist/api-server.d.ts +26 -0
- package/dist/api-server.d.ts.map +1 -0
- package/dist/api-server.js +230 -0
- package/dist/api-server.js.map +1 -0
- package/dist/auto-checkpoint.d.ts +35 -0
- package/dist/auto-checkpoint.d.ts.map +1 -0
- package/dist/auto-checkpoint.js +143 -0
- package/dist/auto-checkpoint.js.map +1 -0
- package/dist/auto-compressor.d.ts +44 -0
- package/dist/auto-compressor.d.ts.map +1 -0
- package/dist/auto-compressor.js +145 -0
- package/dist/auto-compressor.js.map +1 -0
- package/dist/background-jobs.d.ts +45 -0
- package/dist/background-jobs.d.ts.map +1 -0
- package/dist/background-jobs.js +122 -0
- package/dist/background-jobs.js.map +1 -0
- package/dist/bin.d.ts +6 -2
- package/dist/bin.d.ts.map +1 -1
- package/dist/bin.js +127 -24
- package/dist/bin.js.map +1 -1
- package/dist/checkpoint.d.ts +49 -0
- package/dist/checkpoint.d.ts.map +1 -0
- package/dist/checkpoint.js +219 -0
- package/dist/checkpoint.js.map +1 -0
- package/dist/circuit-breaker/breaker.d.ts +80 -0
- package/dist/circuit-breaker/breaker.d.ts.map +1 -0
- package/dist/circuit-breaker/breaker.js +408 -0
- package/dist/circuit-breaker/breaker.js.map +1 -0
- package/dist/circuit-breaker/defaults.d.ts +8 -0
- package/dist/circuit-breaker/defaults.d.ts.map +1 -0
- package/dist/circuit-breaker/defaults.js +35 -0
- package/dist/circuit-breaker/defaults.js.map +1 -0
- package/dist/circuit-breaker/index.d.ts +9 -0
- package/dist/circuit-breaker/index.d.ts.map +1 -0
- package/dist/circuit-breaker/index.js +8 -0
- package/dist/circuit-breaker/index.js.map +1 -0
- package/dist/circuit-breaker/types.d.ts +77 -0
- package/dist/circuit-breaker/types.d.ts.map +1 -0
- package/dist/circuit-breaker/types.js +8 -0
- package/dist/circuit-breaker/types.js.map +1 -0
- package/dist/circuit-breaker.d.ts +8 -0
- package/dist/circuit-breaker.d.ts.map +1 -0
- package/dist/circuit-breaker.js +7 -0
- package/dist/circuit-breaker.js.map +1 -0
- package/dist/cli/agent.d.ts +9 -0
- package/dist/cli/agent.d.ts.map +1 -0
- package/dist/cli/agent.js +262 -0
- package/dist/cli/agent.js.map +1 -0
- package/dist/cli/commands.d.ts +12 -0
- package/dist/cli/commands.d.ts.map +1 -0
- package/dist/{cli.js → cli/commands.js} +285 -422
- package/dist/cli/commands.js.map +1 -0
- package/dist/cli/index.d.ts +8 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +222 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/types.d.ts +30 -0
- package/dist/cli/types.d.ts.map +1 -0
- package/dist/cli/types.js +20 -0
- package/dist/cli/types.js.map +1 -0
- package/dist/companions.d.ts +54 -0
- package/dist/companions.d.ts.map +1 -0
- package/dist/companions.js +440 -0
- package/dist/companions.js.map +1 -0
- package/dist/config.d.ts +23 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +95 -22
- package/dist/config.js.map +1 -1
- package/dist/diff.d.ts +27 -0
- package/dist/diff.d.ts.map +1 -1
- package/dist/diff.js +415 -10
- package/dist/diff.js.map +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +20 -11
- package/dist/errors.js.map +1 -1
- package/dist/git-status.d.ts +23 -0
- package/dist/git-status.d.ts.map +1 -0
- package/dist/git-status.js +92 -0
- package/dist/git-status.js.map +1 -0
- package/dist/headless.d.ts +25 -0
- package/dist/headless.d.ts.map +1 -0
- package/dist/headless.js +182 -0
- package/dist/headless.js.map +1 -0
- package/dist/hud/api.d.ts +35 -0
- package/dist/hud/api.d.ts.map +1 -0
- package/dist/hud/api.js +448 -0
- package/dist/hud/api.js.map +1 -0
- package/dist/hud/palettes.d.ts +9 -0
- package/dist/hud/palettes.d.ts.map +1 -0
- package/dist/hud/palettes.js +280 -0
- package/dist/hud/palettes.js.map +1 -0
- package/dist/hud/skins.d.ts +12 -0
- package/dist/hud/skins.d.ts.map +1 -0
- package/dist/hud/skins.js +365 -0
- package/dist/hud/skins.js.map +1 -0
- package/dist/hud/theme-packs/api.d.ts +51 -0
- package/dist/hud/theme-packs/api.d.ts.map +1 -0
- package/dist/hud/theme-packs/api.js +145 -0
- package/dist/hud/theme-packs/api.js.map +1 -0
- package/dist/hud/theme-packs/index.d.ts +18 -0
- package/dist/hud/theme-packs/index.d.ts.map +1 -0
- package/dist/hud/theme-packs/index.js +38 -0
- package/dist/hud/theme-packs/index.js.map +1 -0
- package/dist/hud/theme-packs/types.d.ts +29 -0
- package/dist/hud/theme-packs/types.d.ts.map +1 -0
- package/dist/hud/theme-packs/types.js +9 -0
- package/dist/hud/theme-packs/types.js.map +1 -0
- package/dist/hud/types.d.ts +182 -0
- package/dist/hud/types.d.ts.map +1 -0
- package/dist/hud/types.js +7 -0
- package/dist/hud/types.js.map +1 -0
- package/dist/idle-eviction.d.ts +34 -0
- package/dist/idle-eviction.d.ts.map +1 -0
- package/dist/idle-eviction.js +78 -0
- package/dist/idle-eviction.js.map +1 -0
- package/dist/index.d.ts +9 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -3
- package/dist/index.js.map +1 -1
- package/dist/iteration-ledger.d.ts +105 -0
- package/dist/iteration-ledger.d.ts.map +1 -0
- package/dist/iteration-ledger.js +237 -0
- package/dist/iteration-ledger.js.map +1 -0
- package/dist/markdown.d.ts.map +1 -1
- package/dist/markdown.js +1 -27
- package/dist/markdown.js.map +1 -1
- package/dist/mcp.d.ts +35 -0
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +291 -7
- package/dist/mcp.js.map +1 -1
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +12 -2
- package/dist/memory.js.map +1 -1
- package/dist/model-detection.d.ts +5 -0
- package/dist/model-detection.d.ts.map +1 -1
- package/dist/model-detection.js +278 -10
- package/dist/model-detection.js.map +1 -1
- package/dist/model-router.d.ts.map +1 -1
- package/dist/model-router.js +33 -11
- package/dist/model-router.js.map +1 -1
- package/dist/plugins.d.ts +8 -0
- package/dist/plugins.d.ts.map +1 -1
- package/dist/plugins.js +97 -6
- package/dist/plugins.js.map +1 -1
- package/dist/providers/anthropic.d.ts +10 -0
- package/dist/providers/anthropic.d.ts.map +1 -0
- package/dist/providers/anthropic.js +221 -0
- package/dist/providers/anthropic.js.map +1 -0
- package/dist/providers/bedrock.d.ts +17 -0
- package/dist/providers/bedrock.d.ts.map +1 -0
- package/dist/providers/bedrock.js +574 -0
- package/dist/providers/bedrock.js.map +1 -0
- package/dist/providers/compat.d.ts +13 -0
- package/dist/providers/compat.d.ts.map +1 -0
- package/dist/providers/compat.js +202 -0
- package/dist/providers/compat.js.map +1 -0
- package/dist/providers/google.d.ts +10 -0
- package/dist/providers/google.d.ts.map +1 -0
- package/dist/providers/google.js +203 -0
- package/dist/providers/google.js.map +1 -0
- package/dist/providers/index.d.ts +23 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +145 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/ollama.d.ts +17 -0
- package/dist/providers/ollama.d.ts.map +1 -0
- package/dist/providers/ollama.js +289 -0
- package/dist/providers/ollama.js.map +1 -0
- package/dist/providers/openai.d.ts +121 -0
- package/dist/providers/openai.d.ts.map +1 -0
- package/dist/providers/openai.js +485 -0
- package/dist/providers/openai.js.map +1 -0
- package/dist/providers/types.d.ts +63 -0
- package/dist/providers/types.d.ts.map +1 -0
- package/dist/providers/types.js +164 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/sandbox-native.d.ts +59 -0
- package/dist/sandbox-native.d.ts.map +1 -0
- package/dist/sandbox-native.js +292 -0
- package/dist/sandbox-native.js.map +1 -0
- package/dist/sandbox.d.ts +2 -2
- package/dist/sandbox.d.ts.map +1 -1
- package/dist/sandbox.js +59 -13
- package/dist/sandbox.js.map +1 -1
- package/dist/scope.d.ts +3 -1
- package/dist/scope.d.ts.map +1 -1
- package/dist/scope.js +13 -1
- package/dist/scope.js.map +1 -1
- package/dist/session-timeout.d.ts +31 -0
- package/dist/session-timeout.d.ts.map +1 -0
- package/dist/session-timeout.js +100 -0
- package/dist/session-timeout.js.map +1 -0
- package/dist/setup.d.ts.map +1 -1
- package/dist/setup.js +29 -17
- package/dist/setup.js.map +1 -1
- package/dist/smart-router.d.ts +73 -0
- package/dist/smart-router.d.ts.map +1 -0
- package/dist/smart-router.js +332 -0
- package/dist/smart-router.js.map +1 -0
- package/dist/storage.d.ts +19 -0
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +164 -1
- package/dist/storage.js.map +1 -1
- package/dist/streaming.d.ts +4 -0
- package/dist/streaming.d.ts.map +1 -1
- package/dist/streaming.js +12 -0
- package/dist/streaming.js.map +1 -1
- package/dist/styles.d.ts +32 -0
- package/dist/styles.d.ts.map +1 -1
- package/dist/styles.js +91 -0
- package/dist/styles.js.map +1 -1
- package/dist/summarization.d.ts +1 -1
- package/dist/summarization.js +4 -4
- package/dist/summarization.js.map +1 -1
- package/dist/terminal-image.d.ts +115 -0
- package/dist/terminal-image.d.ts.map +1 -0
- package/dist/terminal-image.js +766 -0
- package/dist/terminal-image.js.map +1 -0
- package/dist/terminal-recording.d.ts +55 -0
- package/dist/terminal-recording.d.ts.map +1 -0
- package/dist/terminal-recording.js +182 -0
- package/dist/terminal-recording.js.map +1 -0
- package/dist/themes.d.ts +19 -35
- package/dist/themes.d.ts.map +1 -1
- package/dist/themes.js +101 -210
- package/dist/themes.js.map +1 -1
- package/dist/tmux.d.ts +35 -0
- package/dist/tmux.d.ts.map +1 -0
- package/dist/tmux.js +106 -0
- package/dist/tmux.js.map +1 -0
- package/dist/tools.d.ts +3 -3
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +587 -45
- package/dist/tools.js.map +1 -1
- package/dist/trust.d.ts +53 -0
- package/dist/trust.d.ts.map +1 -0
- package/dist/trust.js +154 -0
- package/dist/trust.js.map +1 -0
- package/dist/types.d.ts +7 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +70 -32
- package/dist/types.js.map +1 -1
- package/dist/ui/agent.d.ts +61 -0
- package/dist/ui/agent.d.ts.map +1 -0
- package/dist/ui/agent.js +768 -0
- package/dist/ui/agent.js.map +1 -0
- package/dist/ui/chat-input.d.ts +32 -0
- package/dist/ui/chat-input.d.ts.map +1 -0
- package/dist/ui/chat-input.js +355 -0
- package/dist/ui/chat-input.js.map +1 -0
- package/dist/ui/commands.d.ts +92 -0
- package/dist/ui/commands.d.ts.map +1 -0
- package/dist/ui/commands.js +3006 -0
- package/dist/ui/commands.js.map +1 -0
- package/dist/ui/completions.d.ts +22 -0
- package/dist/ui/completions.d.ts.map +1 -0
- package/dist/ui/completions.js +215 -0
- package/dist/ui/completions.js.map +1 -0
- package/dist/ui/components.d.ts +38 -0
- package/dist/ui/components.d.ts.map +1 -0
- package/dist/ui/components.js +422 -0
- package/dist/ui/components.js.map +1 -0
- package/dist/ui/context.d.ts +12 -0
- package/dist/ui/context.d.ts.map +1 -0
- package/dist/ui/context.js +102 -0
- package/dist/ui/context.js.map +1 -0
- package/dist/ui/error-boundary.d.ts +33 -0
- package/dist/ui/error-boundary.d.ts.map +1 -0
- package/dist/ui/error-boundary.js +94 -0
- package/dist/ui/error-boundary.js.map +1 -0
- package/dist/ui/frame.d.ts +13 -0
- package/dist/ui/frame.d.ts.map +1 -0
- package/dist/ui/frame.js +89 -0
- package/dist/ui/frame.js.map +1 -0
- package/dist/ui/index.d.ts +12 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +928 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/messages.d.ts +19 -0
- package/dist/ui/messages.d.ts.map +1 -0
- package/dist/ui/messages.js +181 -0
- package/dist/ui/messages.js.map +1 -0
- package/dist/ui/modals.d.ts +52 -0
- package/dist/ui/modals.d.ts.map +1 -0
- package/dist/ui/modals.js +204 -0
- package/dist/ui/modals.js.map +1 -0
- package/dist/ui/pack-picker.d.ts +12 -0
- package/dist/ui/pack-picker.d.ts.map +1 -0
- package/dist/ui/pack-picker.js +101 -0
- package/dist/ui/pack-picker.js.map +1 -0
- package/dist/ui/status-bar.d.ts +20 -0
- package/dist/ui/status-bar.d.ts.map +1 -0
- package/dist/ui/status-bar.js +41 -0
- package/dist/ui/status-bar.js.map +1 -0
- package/dist/ui/theme-picker.d.ts +24 -0
- package/dist/ui/theme-picker.d.ts.map +1 -0
- package/dist/ui/theme-picker.js +190 -0
- package/dist/ui/theme-picker.js.map +1 -0
- package/dist/ui/types.d.ts +62 -0
- package/dist/ui/types.d.ts.map +1 -0
- package/dist/ui/types.js +7 -0
- package/dist/ui/types.js.map +1 -0
- package/dist/version-check.d.ts.map +1 -1
- package/dist/version-check.js +1 -9
- package/dist/version-check.js.map +1 -1
- package/package.json +8 -3
- package/dist/agterm/agent-detection.js.map +0 -1
- package/dist/agterm/cli-backend.js.map +0 -1
- package/dist/agterm/index.d.ts +0 -12
- package/dist/agterm/index.d.ts.map +0 -1
- package/dist/agterm/index.js +0 -15
- package/dist/agterm/index.js.map +0 -1
- package/dist/agterm/orchestrator.d.ts.map +0 -1
- package/dist/agterm/orchestrator.js.map +0 -1
- package/dist/agterm/tools.d.ts.map +0 -1
- package/dist/agterm/tools.js +0 -278
- package/dist/agterm/tools.js.map +0 -1
- package/dist/agterm/types.d.ts.map +0 -1
- package/dist/agterm/types.js.map +0 -1
- package/dist/cli.d.ts +0 -14
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/providers.d.ts +0 -51
- package/dist/providers.d.ts.map +0 -1
- package/dist/providers.js +0 -1146
- package/dist/providers.js.map +0 -1
- package/dist/ui-cli.d.ts +0 -17
- package/dist/ui-cli.d.ts.map +0 -1
- package/dist/ui-cli.js +0 -3730
- package/dist/ui-cli.js.map +0 -1
package/dist/ui/agent.js
ADDED
|
@@ -0,0 +1,768 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UI Module - Agent Runner
|
|
3
|
+
*
|
|
4
|
+
* Core agent execution loop, tool handling, and message validation.
|
|
5
|
+
* Extracted from TerminalChat using an AgentContext state bag.
|
|
6
|
+
*/
|
|
7
|
+
import { spawn } from 'child_process';
|
|
8
|
+
import * as config from '../config.js';
|
|
9
|
+
import { chat } from '../providers/index.js';
|
|
10
|
+
import { estimateContextUsage, needsSummarization } from '../providers/types.js';
|
|
11
|
+
import { executeTool, getTools } from '../tools.js';
|
|
12
|
+
import { DEFAULT_MODELS, RISK_CONFIG, calculateCost } from '../types.js';
|
|
13
|
+
import { getModelContextLimit } from '../model-detection.js';
|
|
14
|
+
import { assessToolRisk, requiresConfirmation } from '../risk.js';
|
|
15
|
+
import { formatError, classifyError } from '../errors.js';
|
|
16
|
+
import { getAvailableProviders } from '../providers/index.js';
|
|
17
|
+
import * as storage from '../storage.js';
|
|
18
|
+
import * as hooks from '../hooks.js';
|
|
19
|
+
import * as modelRouter from '../model-router.js';
|
|
20
|
+
import * as summarization from '../summarization.js';
|
|
21
|
+
import { executeParallel, getParallelizationStats } from '../parallel-tools.js';
|
|
22
|
+
import { setMood } from '../companions.js';
|
|
23
|
+
import { checkAndWarnContextLimit } from './context.js';
|
|
24
|
+
import { smartRoute } from '../smart-router.js';
|
|
25
|
+
import { shouldCheckpoint, createCheckpoint } from '../auto-checkpoint.js';
|
|
26
|
+
import { recordEvent } from '../terminal-recording.js';
|
|
27
|
+
// ============================================================================
|
|
28
|
+
// Tool Result Truncation
|
|
29
|
+
// ============================================================================
|
|
30
|
+
/**
|
|
31
|
+
* Truncate tool result content to fit within available context.
|
|
32
|
+
* For small models (< 16K), aggressively cap tool output to prevent context overflow.
|
|
33
|
+
*/
|
|
34
|
+
function truncateToolResult(content, modelLimit) {
|
|
35
|
+
// Scale max tool result size based on model context
|
|
36
|
+
// Small models: 25% of context, large models: up to 50K chars
|
|
37
|
+
const maxChars = modelLimit < 8000 ? Math.floor(modelLimit * 0.6) // ~2.4K chars for 4K model
|
|
38
|
+
: modelLimit < 16000 ? Math.floor(modelLimit * 0.8) // ~12K chars for 16K model
|
|
39
|
+
: modelLimit < 32000 ? 20000
|
|
40
|
+
: 50000;
|
|
41
|
+
if (content.length <= maxChars)
|
|
42
|
+
return content;
|
|
43
|
+
const half = Math.floor(maxChars / 2);
|
|
44
|
+
const trimmed = content.slice(0, half) + `\n\n... [truncated ${content.length - maxChars} chars] ...\n\n` + content.slice(-half);
|
|
45
|
+
return trimmed;
|
|
46
|
+
}
|
|
47
|
+
// ============================================================================
|
|
48
|
+
// Validate and Repair Messages
|
|
49
|
+
// ============================================================================
|
|
50
|
+
/**
|
|
51
|
+
* Validate and repair message history to ensure tool_use always has tool_result.
|
|
52
|
+
*/
|
|
53
|
+
export function validateAndRepairMessagesImpl(ctx) {
|
|
54
|
+
const messages = ctx.llmMessages.current;
|
|
55
|
+
let repaired = false;
|
|
56
|
+
for (let i = 0; i < messages.length; i++) {
|
|
57
|
+
const msg = messages[i];
|
|
58
|
+
if (msg.role === 'assistant' && msg.toolCalls && msg.toolCalls.length > 0) {
|
|
59
|
+
// Check that each tool_use has a corresponding tool_result
|
|
60
|
+
for (const toolCall of msg.toolCalls) {
|
|
61
|
+
const hasResult = messages.slice(i + 1).some(m => m.role === 'tool' && m.toolCallId === toolCall.id);
|
|
62
|
+
if (!hasResult) {
|
|
63
|
+
// Add a placeholder tool_result for the missing tool call
|
|
64
|
+
ctx.debugLog('repair', 'Adding missing tool_result for', toolCall.id);
|
|
65
|
+
// Find the right position to insert (right after this assistant message or after existing tool results)
|
|
66
|
+
let insertPos = i + 1;
|
|
67
|
+
while (insertPos < messages.length && messages[insertPos].role === 'tool') {
|
|
68
|
+
insertPos++;
|
|
69
|
+
}
|
|
70
|
+
messages.splice(insertPos, 0, {
|
|
71
|
+
role: 'tool',
|
|
72
|
+
content: '[Error: Tool execution was interrupted. Please retry.]',
|
|
73
|
+
toolCallId: toolCall.id,
|
|
74
|
+
});
|
|
75
|
+
repaired = true;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (repaired) {
|
|
81
|
+
ctx.addMessage('system', '🔧 Repaired corrupted message history (missing tool results).');
|
|
82
|
+
}
|
|
83
|
+
return repaired;
|
|
84
|
+
}
|
|
85
|
+
// ============================================================================
|
|
86
|
+
// Run Agent
|
|
87
|
+
// ============================================================================
|
|
88
|
+
/**
|
|
89
|
+
* Run agent with user prompt. Core execution loop that handles LLM calls,
|
|
90
|
+
* tool execution (parallel + sequential), auto-compaction, and queued messages.
|
|
91
|
+
*/
|
|
92
|
+
export async function runAgentImpl(ctx, content) {
|
|
93
|
+
ctx.debugLog('runAgent', 'ENTER', typeof content === 'string' ? content.substring(0, 50) : '[complex]');
|
|
94
|
+
setMood('thinking');
|
|
95
|
+
// Validate message history before adding new content
|
|
96
|
+
ctx.validateAndRepairMessages();
|
|
97
|
+
ctx.llmMessages.current.push({ role: 'user', content });
|
|
98
|
+
ctx.setStats(s => ({ ...s, messageCount: s.messageCount + 1 }));
|
|
99
|
+
ctx.setStreamingResponse('');
|
|
100
|
+
// Smart routing (cross-provider) takes precedence over autoRoute (single-provider)
|
|
101
|
+
let effectiveModel = ctx.model;
|
|
102
|
+
let effectiveProvider = ctx.provider;
|
|
103
|
+
if (ctx.smartRouteActive && ctx.smartRoutingConfig?.enabled && typeof content === 'string') {
|
|
104
|
+
const decision = smartRoute(content, ctx.smartRoutingConfig, {
|
|
105
|
+
messageCount: ctx.stats.messageCount,
|
|
106
|
+
hasCode: content.includes('```') || /\.(ts|js|py|go|rs|java)/.test(content),
|
|
107
|
+
});
|
|
108
|
+
effectiveModel = decision.selected.model;
|
|
109
|
+
effectiveProvider = decision.selected.provider;
|
|
110
|
+
if (effectiveModel !== ctx.model || effectiveProvider !== ctx.provider) {
|
|
111
|
+
ctx.addMessage('system', `[Smart route: ${decision.selected.provider}/${decision.selected.tier} - ${decision.taskType}/${decision.complexity}]`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
else if (ctx.autoRoute && typeof content === 'string') {
|
|
115
|
+
const routeDecision = modelRouter.routeRequest(content, ctx.provider, {
|
|
116
|
+
messageCount: ctx.stats.messageCount,
|
|
117
|
+
hasCode: content.includes('```') || /\.(ts|js|py|go|rs|java)/.test(content),
|
|
118
|
+
});
|
|
119
|
+
effectiveModel = routeDecision.model.model;
|
|
120
|
+
if (effectiveModel !== ctx.model) {
|
|
121
|
+
ctx.addMessage('system', `[Auto-route: ${routeDecision.tier} tier - ${routeDecision.reason}]`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
const maxIterations = config.get('maxIterations') || Infinity; // 0 = unlimited
|
|
125
|
+
let completedNaturally = false;
|
|
126
|
+
// Check context limit and warn if approaching capacity
|
|
127
|
+
// Uses model's actual context length from API when available
|
|
128
|
+
let currentContextTokens = ctx.estimateContextTokens();
|
|
129
|
+
const modelLimit = getModelContextLimit(ctx.actualProvider, effectiveModel || ctx.actualModel);
|
|
130
|
+
let contextPercentage = (currentContextTokens / modelLimit) * 100;
|
|
131
|
+
// Adaptive preserveRecent: small models keep fewer messages to leave room for output
|
|
132
|
+
const preserveRecent = modelLimit < 8000 ? 2 : modelLimit < 16000 ? 4 : modelLimit < 32000 ? 6 : modelLimit < 64000 ? 10 : 15;
|
|
133
|
+
// Auto-compact if we're over 75% capacity to prevent API errors
|
|
134
|
+
if (contextPercentage > 75) {
|
|
135
|
+
ctx.addMessage('system', `🔄 Context at ${Math.round(contextPercentage)}% - auto-compacting to prevent errors...`);
|
|
136
|
+
const result = summarization.summarizeConversation(ctx.llmMessages.current, {
|
|
137
|
+
maxTokens: Math.floor(modelLimit * 0.7), // Target 70% of limit after compaction
|
|
138
|
+
preserveRecent,
|
|
139
|
+
});
|
|
140
|
+
if (result.summarizedCount > 0) {
|
|
141
|
+
ctx.llmMessages.current = result.messages;
|
|
142
|
+
currentContextTokens = ctx.estimateContextTokens();
|
|
143
|
+
contextPercentage = (currentContextTokens / modelLimit) * 100;
|
|
144
|
+
ctx.setContextTokens(currentContextTokens);
|
|
145
|
+
ctx.addMessage('system', `✓ Compacted ${result.summarizedCount} messages. Now at ${Math.round(contextPercentage)}% (${Math.round(currentContextTokens / 1000)}K/${Math.round(modelLimit / 1000)}K)`);
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
// If compaction didn't help enough, force-trim old messages
|
|
149
|
+
if (contextPercentage > 98) {
|
|
150
|
+
const systemMsgs = ctx.llmMessages.current.filter(m => m.role === 'system');
|
|
151
|
+
const recentMsgs = ctx.llmMessages.current.filter(m => m.role !== 'system').slice(-5);
|
|
152
|
+
ctx.llmMessages.current = [...systemMsgs, ...recentMsgs];
|
|
153
|
+
currentContextTokens = ctx.estimateContextTokens();
|
|
154
|
+
contextPercentage = (currentContextTokens / modelLimit) * 100;
|
|
155
|
+
ctx.setContextTokens(currentContextTokens);
|
|
156
|
+
ctx.addMessage('system', `⚠️ Force-trimmed to last 5 messages (${Math.round(contextPercentage)}%). Use /clear for a full reset.`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
else if (contextPercentage > 65) {
|
|
161
|
+
ctx.addMessage('system', `⚠️ Context at ${Math.round(contextPercentage)}% capacity (${Math.round(currentContextTokens / 1000)}K/${Math.round(modelLimit / 1000)}K tokens)
|
|
162
|
+
Consider: /summarize compact | /clear | shorter messages`);
|
|
163
|
+
}
|
|
164
|
+
// Inject failed approaches into context so the agent avoids repeating mistakes
|
|
165
|
+
if (ctx.ledger) {
|
|
166
|
+
const failedMsg = ctx.ledger.getFailedApproachesMessage();
|
|
167
|
+
if (failedMsg) {
|
|
168
|
+
ctx.llmMessages.current.push({ role: 'user', content: failedMsg });
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
for (let i = 0; i < maxIterations; i++) {
|
|
172
|
+
// Start ledger tracking for this iteration
|
|
173
|
+
ctx.ledger?.startIteration(i + 1);
|
|
174
|
+
// Safety check at start of each iteration - context may have grown from tool results
|
|
175
|
+
if (i > 0) {
|
|
176
|
+
const iterContextTokens = ctx.estimateContextTokens();
|
|
177
|
+
const iterContextPercentage = (iterContextTokens / modelLimit) * 100;
|
|
178
|
+
if (iterContextPercentage > 75) {
|
|
179
|
+
ctx.addMessage('system', `🔄 Context grew to ${Math.round(iterContextPercentage)}% - auto-compacting...`);
|
|
180
|
+
const result = summarization.summarizeConversation(ctx.llmMessages.current, {
|
|
181
|
+
maxTokens: Math.floor(modelLimit * 0.7),
|
|
182
|
+
preserveRecent,
|
|
183
|
+
});
|
|
184
|
+
if (result.summarizedCount > 0) {
|
|
185
|
+
ctx.llmMessages.current = result.messages;
|
|
186
|
+
ctx.setContextTokens(ctx.estimateContextTokens());
|
|
187
|
+
ctx.addMessage('system', `✓ Compacted ${result.summarizedCount} messages during iteration ${i + 1}`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
try {
|
|
192
|
+
// Update thinking state for LLM call
|
|
193
|
+
ctx.setThinkingState({
|
|
194
|
+
status: i === 0 ? 'Analyzing request...' : 'Processing response...',
|
|
195
|
+
detail: `Iteration ${i + 1}/${maxIterations}`,
|
|
196
|
+
iteration: i + 1,
|
|
197
|
+
maxIterations,
|
|
198
|
+
});
|
|
199
|
+
ctx.setActivityState({
|
|
200
|
+
action: i === 0 ? 'Analyzing request' : 'Processing',
|
|
201
|
+
target: `iteration ${i + 1}`,
|
|
202
|
+
startTime: Date.now(),
|
|
203
|
+
});
|
|
204
|
+
// Streaming callback for final response
|
|
205
|
+
const onToken = (token) => {
|
|
206
|
+
ctx.setThinkingState(null); // Clear thinking when streaming starts
|
|
207
|
+
ctx.setStreamingResponse(prev => prev + token);
|
|
208
|
+
};
|
|
209
|
+
// Retry callback for error recovery
|
|
210
|
+
const onRetry = (attempt, error, delayMs) => {
|
|
211
|
+
ctx.setThinkingState({
|
|
212
|
+
status: `Retrying... (attempt ${attempt + 1})`,
|
|
213
|
+
detail: `${error.message.substring(0, 40)}... Waiting ${Math.round(delayMs / 1000)}s`,
|
|
214
|
+
iteration: i + 1,
|
|
215
|
+
maxIterations,
|
|
216
|
+
});
|
|
217
|
+
};
|
|
218
|
+
ctx.debugLog('chat', 'WAITING for LLM response', `iteration=${i + 1}`);
|
|
219
|
+
// Validate message history to prevent orphaned tool_result errors
|
|
220
|
+
let validatedMessages = summarization.validateMessageHistory(ctx.llmMessages.current);
|
|
221
|
+
if (validatedMessages.length !== ctx.llmMessages.current.length) {
|
|
222
|
+
ctx.debugLog('chat', 'CLEANED orphaned tool results', `removed=${ctx.llmMessages.current.length - validatedMessages.length}`);
|
|
223
|
+
ctx.llmMessages.current = validatedMessages;
|
|
224
|
+
}
|
|
225
|
+
// Pre-request summarization check - summarize BEFORE sending if context is too large
|
|
226
|
+
const tools = getTools(ctx.agtermEnabled);
|
|
227
|
+
const contextCheck = estimateContextUsage(ctx.provider, effectiveModel || DEFAULT_MODELS[ctx.provider], validatedMessages, tools);
|
|
228
|
+
ctx.debugLog('chat', 'CONTEXT CHECK', `estimated=${contextCheck.estimated}, limit=${contextCheck.limit}, percent=${contextCheck.percent}%`);
|
|
229
|
+
if (contextCheck.needsSummarization) {
|
|
230
|
+
ctx.debugLog('chat', 'PRE-REQUEST SUMMARIZING', `estimated=${contextCheck.estimated} >= 80% of ${contextCheck.limit}`);
|
|
231
|
+
const result = summarization.summarizeConversation(validatedMessages, { maxTokens: Math.floor(contextCheck.limit * 0.6) });
|
|
232
|
+
if (result.summarizedCount > 0) {
|
|
233
|
+
ctx.llmMessages.current = result.messages;
|
|
234
|
+
validatedMessages = result.messages;
|
|
235
|
+
ctx.debugLog('chat', 'PRE-SUMMARIZED', `removed=${result.summarizedCount} messages, reduced from ${result.originalTokens} to ${result.reducedTokens}`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
const response = await chat(ctx.provider, validatedMessages, tools, effectiveModel, onToken, onRetry);
|
|
239
|
+
ctx.debugLog('chat', 'GOT response', `toolCalls=${response.toolCalls?.length ?? 0}`);
|
|
240
|
+
// Update token stats and cost
|
|
241
|
+
if (response.usage) {
|
|
242
|
+
const usageCost = calculateCost(ctx.model || DEFAULT_MODELS[ctx.provider], response.usage.inputTokens, response.usage.outputTokens);
|
|
243
|
+
ctx.setStats(s => ({
|
|
244
|
+
...s,
|
|
245
|
+
inputTokens: s.inputTokens + response.usage.inputTokens,
|
|
246
|
+
outputTokens: s.outputTokens + response.usage.outputTokens,
|
|
247
|
+
cost: s.cost + usageCost,
|
|
248
|
+
}));
|
|
249
|
+
// Record in iteration ledger
|
|
250
|
+
ctx.ledger?.recordTokens(response.usage.inputTokens, response.usage.outputTokens, usageCost);
|
|
251
|
+
// Persist cost to storage
|
|
252
|
+
storage.recordCost(usageCost, ctx.actualProvider, ctx.sessionRef.current?.id);
|
|
253
|
+
// Auto-summarize if context is getting too full (85% threshold)
|
|
254
|
+
if (needsSummarization(ctx.provider, ctx.model || DEFAULT_MODELS[ctx.provider], response.usage.inputTokens)) {
|
|
255
|
+
ctx.debugLog('chat', 'AUTO-SUMMARIZING', `inputTokens=${response.usage.inputTokens}`);
|
|
256
|
+
const postCompactLimit = Math.floor(getModelContextLimit(ctx.provider, ctx.model || DEFAULT_MODELS[ctx.provider]) * 0.6);
|
|
257
|
+
const result = summarization.summarizeConversation(ctx.llmMessages.current, { maxTokens: postCompactLimit });
|
|
258
|
+
if (result.summarizedCount > 0) {
|
|
259
|
+
ctx.llmMessages.current = result.messages;
|
|
260
|
+
ctx.debugLog('chat', 'SUMMARIZED', `removed=${result.summarizedCount} messages`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
// Circuit breaker check after each iteration
|
|
265
|
+
if (ctx.circuitBreaker) {
|
|
266
|
+
const iterData = {
|
|
267
|
+
iteration: i + 1,
|
|
268
|
+
inputTokens: response.usage?.inputTokens,
|
|
269
|
+
outputTokens: response.usage?.outputTokens,
|
|
270
|
+
cost: response.usage ? calculateCost(ctx.model || DEFAULT_MODELS[ctx.provider], response.usage.inputTokens, response.usage.outputTokens) : undefined,
|
|
271
|
+
toolCalls: response.toolCalls?.map(tc => ({ name: tc.name, arguments: tc.arguments })),
|
|
272
|
+
content: response.content,
|
|
273
|
+
timestamp: new Date(),
|
|
274
|
+
};
|
|
275
|
+
const breakerResult = ctx.circuitBreaker.check(iterData);
|
|
276
|
+
ctx.setBreakerHealth?.(ctx.circuitBreaker.getHealth());
|
|
277
|
+
if (breakerResult.tripped) {
|
|
278
|
+
ctx.addMessage('system', `\u26a0\ufe0f Circuit breaker tripped: ${breakerResult.breaker}\n${breakerResult.message}\n\nUse /breaker resume to continue, /breaker status for details.`);
|
|
279
|
+
completedNaturally = true;
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
// Handle tool calls with parallel execution support
|
|
284
|
+
if (response.toolCalls?.length) {
|
|
285
|
+
ctx.llmMessages.current.push({
|
|
286
|
+
role: 'assistant',
|
|
287
|
+
content: response.content,
|
|
288
|
+
toolCalls: response.toolCalls,
|
|
289
|
+
});
|
|
290
|
+
const preChecks = [];
|
|
291
|
+
const executableTools = [];
|
|
292
|
+
for (const toolCall of response.toolCalls) {
|
|
293
|
+
const args = toolCall.arguments;
|
|
294
|
+
const toolPreview = String(args.command || args.path || '...');
|
|
295
|
+
const risk = assessToolRisk(toolCall);
|
|
296
|
+
const riskConfig = RISK_CONFIG[risk.level];
|
|
297
|
+
const riskDisplay = risk.level !== 'none' ? ` [${riskConfig.bar}]` : '';
|
|
298
|
+
const preCheck = {
|
|
299
|
+
toolCall,
|
|
300
|
+
args,
|
|
301
|
+
preview: toolPreview,
|
|
302
|
+
risk,
|
|
303
|
+
riskDisplay,
|
|
304
|
+
blocked: false,
|
|
305
|
+
};
|
|
306
|
+
// Check blocking conditions
|
|
307
|
+
const PLAN_MODE_ALLOWED = new Set(['think', 'ask_question', 'create_plan', 'read_file', 'list_files']);
|
|
308
|
+
if (ctx.mode === 'plan' && !PLAN_MODE_ALLOWED.has(toolCall.name)) {
|
|
309
|
+
preCheck.blocked = true;
|
|
310
|
+
preCheck.blockReason = 'plan mode';
|
|
311
|
+
preCheck.blockContent = '[Plan mode: Tool not executed. Describe what this would do.]';
|
|
312
|
+
ctx.addMessage('tool', `📋 ${toolCall.name}: ${toolPreview}${riskDisplay} (plan mode - not executed)`);
|
|
313
|
+
}
|
|
314
|
+
else if (ctx.confirmMode && requiresConfirmation(risk, false) && toolCall.name !== 'think') {
|
|
315
|
+
preCheck.blocked = true;
|
|
316
|
+
preCheck.blockReason = 'confirmation required';
|
|
317
|
+
preCheck.blockContent = `[Operation blocked - ${risk.level} risk: ${risk.reason}. User confirmation required.]`;
|
|
318
|
+
const riskIcon = risk.level === 'critical' ? '🛑' : '⚠️';
|
|
319
|
+
ctx.addMessage('tool', `${riskIcon} ${toolCall.name}: ${toolPreview}${riskDisplay}\n → Requires confirmation (use /confirm off to disable)`);
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
// Check pre-tool hooks
|
|
323
|
+
const preHookResult = await hooks.checkHooksAllow('pre-tool', {
|
|
324
|
+
tool: toolCall.name,
|
|
325
|
+
toolArgs: args,
|
|
326
|
+
});
|
|
327
|
+
if (!preHookResult.allowed) {
|
|
328
|
+
preCheck.blocked = true;
|
|
329
|
+
preCheck.blockReason = 'blocked by hook';
|
|
330
|
+
preCheck.blockContent = `[Blocked by hook: ${preHookResult.reason}]`;
|
|
331
|
+
ctx.addMessage('tool', `⚡ ${toolCall.name}: ${toolPreview}${riskDisplay}`);
|
|
332
|
+
ctx.addMessage('tool', `🛑 Blocked by hook: ${preHookResult.reason}`);
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
// Tool can be executed
|
|
336
|
+
executableTools.push(toolCall);
|
|
337
|
+
ctx.addMessage('tool', `⚡ ${toolCall.name}: ${toolPreview}${riskDisplay}`);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
preChecks.push(preCheck);
|
|
341
|
+
// Record blocked tools in ledger and add to LLM messages
|
|
342
|
+
if (preCheck.blocked) {
|
|
343
|
+
ctx.ledger?.recordAction(toolCall.name, args, 'blocked', preCheck.blockReason);
|
|
344
|
+
ctx.llmMessages.current.push({
|
|
345
|
+
role: 'tool',
|
|
346
|
+
content: preCheck.blockContent,
|
|
347
|
+
toolCallId: toolCall.id,
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
// ============================================================
|
|
352
|
+
// Phase 2: Execute tools (parallel when beneficial)
|
|
353
|
+
// ============================================================
|
|
354
|
+
if (executableTools.length > 0) {
|
|
355
|
+
const parallelStats = getParallelizationStats(executableTools);
|
|
356
|
+
const useParallel = parallelStats.maxParallel > 1 && executableTools.length > 1;
|
|
357
|
+
if (useParallel) {
|
|
358
|
+
// Show parallelization info
|
|
359
|
+
ctx.setThinkingState({
|
|
360
|
+
status: `Executing ${executableTools.length} tools in parallel...`,
|
|
361
|
+
detail: `${parallelStats.stages} stages, up to ${parallelStats.maxParallel}x speedup`,
|
|
362
|
+
iteration: i + 1,
|
|
363
|
+
maxIterations,
|
|
364
|
+
});
|
|
365
|
+
ctx.setActivityState({
|
|
366
|
+
action: `Executing ${executableTools.length} tools`,
|
|
367
|
+
target: 'in parallel',
|
|
368
|
+
startTime: Date.now(),
|
|
369
|
+
});
|
|
370
|
+
// Execute in parallel using dependency-aware staging
|
|
371
|
+
ctx.debugLog('tools', 'PARALLEL exec start', `count=${executableTools.length}`);
|
|
372
|
+
const results = await executeParallel(executableTools, async (call) => {
|
|
373
|
+
const result = await executeTool(call, process.cwd());
|
|
374
|
+
return result.result;
|
|
375
|
+
}, (completed, total, current) => {
|
|
376
|
+
const args = current.arguments;
|
|
377
|
+
const target = args.path || args.command?.substring(0, 30) || current.name;
|
|
378
|
+
ctx.setActivityState({
|
|
379
|
+
action: `Running ${current.name}`,
|
|
380
|
+
target: target,
|
|
381
|
+
startTime: Date.now(),
|
|
382
|
+
});
|
|
383
|
+
ctx.setThinkingState({
|
|
384
|
+
status: `Executing tools... (${completed + 1}/${total})`,
|
|
385
|
+
detail: current.name,
|
|
386
|
+
iteration: i + 1,
|
|
387
|
+
maxIterations,
|
|
388
|
+
});
|
|
389
|
+
});
|
|
390
|
+
ctx.debugLog('tools', 'PARALLEL exec done', `results=${results.length}`);
|
|
391
|
+
// Process results sequentially for UI and LLM messages
|
|
392
|
+
for (const result of results) {
|
|
393
|
+
const toolCall = result.toolCall;
|
|
394
|
+
const args = toolCall.arguments;
|
|
395
|
+
recordEvent('tool_call', toolCall.name, { name: toolCall.name, arguments: args });
|
|
396
|
+
recordEvent('tool_result', (result.result || result.error || '').slice(0, 1000), { name: toolCall.name, isError: !!result.error });
|
|
397
|
+
// Record in iteration ledger
|
|
398
|
+
ctx.ledger?.recordAction(toolCall.name, args, result.error ? 'error' : 'ok', result.error || undefined);
|
|
399
|
+
// Execute post-tool hooks
|
|
400
|
+
hooks.executeHooks('post-tool', {
|
|
401
|
+
tool: toolCall.name,
|
|
402
|
+
toolArgs: args,
|
|
403
|
+
toolResult: result.result,
|
|
404
|
+
}).catch((err) => {
|
|
405
|
+
ctx.debugLog('hooks', `post-tool hook failed for ${toolCall.name}:`, err instanceof Error ? err.message : err);
|
|
406
|
+
});
|
|
407
|
+
// Display result
|
|
408
|
+
if (toolCall.name === 'think') {
|
|
409
|
+
const thought = String(args.thought || '');
|
|
410
|
+
ctx.addMessage('tool', thought);
|
|
411
|
+
}
|
|
412
|
+
else if (result.error) {
|
|
413
|
+
ctx.addMessage('tool', `Error: ${result.error}`);
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
416
|
+
const preview = result.result.split('\n').slice(0, 3).join('\n');
|
|
417
|
+
ctx.addMessage('tool', preview + (result.result.split('\n').length > 3 ? '\n...' : ''));
|
|
418
|
+
}
|
|
419
|
+
ctx.llmMessages.current.push({
|
|
420
|
+
role: 'tool',
|
|
421
|
+
content: truncateToolResult(result.error ? `Error: ${result.error}` : result.result, modelLimit),
|
|
422
|
+
toolCallId: toolCall.id,
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
else {
|
|
427
|
+
// Sequential execution (single tool or dependencies prevent parallelization)
|
|
428
|
+
ctx.debugLog('tools', 'SEQUENTIAL exec start', `count=${executableTools.length}`);
|
|
429
|
+
for (const toolCall of executableTools) {
|
|
430
|
+
const args = toolCall.arguments;
|
|
431
|
+
const toolPreview = String(args.command || args.path || args.content?.toString().substring(0, 30) || '...');
|
|
432
|
+
// Set activity state for streaming indicator
|
|
433
|
+
const actionMap = {
|
|
434
|
+
read_file: 'Reading',
|
|
435
|
+
write_file: 'Writing',
|
|
436
|
+
edit_file: 'Editing',
|
|
437
|
+
bash: 'Running',
|
|
438
|
+
search: 'Searching',
|
|
439
|
+
glob: 'Finding',
|
|
440
|
+
think: 'Thinking',
|
|
441
|
+
};
|
|
442
|
+
const action = actionMap[toolCall.name] || `Executing ${toolCall.name}`;
|
|
443
|
+
const target = toolCall.name === 'bash'
|
|
444
|
+
? args.command?.substring(0, 40) + (args.command?.length > 40 ? '...' : '')
|
|
445
|
+
: toolCall.name === 'think'
|
|
446
|
+
? undefined
|
|
447
|
+
: args.path || args.pattern;
|
|
448
|
+
ctx.setActivityState({ action, target, startTime: Date.now() });
|
|
449
|
+
// Special handling for think tool UI
|
|
450
|
+
if (toolCall.name === 'think') {
|
|
451
|
+
const thought = String(args.thought || '');
|
|
452
|
+
ctx.setThinkingState({
|
|
453
|
+
status: 'Reasoning...',
|
|
454
|
+
detail: thought.substring(0, 60) + (thought.length > 60 ? '...' : ''),
|
|
455
|
+
thinking: thought,
|
|
456
|
+
iteration: i + 1,
|
|
457
|
+
maxIterations,
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
else {
|
|
461
|
+
ctx.setThinkingState({
|
|
462
|
+
status: `Executing ${toolCall.name}...`,
|
|
463
|
+
detail: toolPreview.substring(0, 60),
|
|
464
|
+
thinking: undefined,
|
|
465
|
+
iteration: i + 1,
|
|
466
|
+
maxIterations,
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
ctx.debugLog('tools', 'EXEC', toolCall.name, toolPreview.substring(0, 30));
|
|
470
|
+
recordEvent('tool_call', toolCall.name, { name: toolCall.name, arguments: args });
|
|
471
|
+
// Auto-checkpoint before destructive operations
|
|
472
|
+
if (shouldCheckpoint(toolCall.name, args)) {
|
|
473
|
+
const hash = createCheckpoint(toolCall.name, args);
|
|
474
|
+
if (hash) {
|
|
475
|
+
ctx.debugLog('checkpoint', `auto-checkpoint ${hash} before ${toolCall.name}`);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
// Stream shell output in real-time (#15)
|
|
479
|
+
const shellStreamCallback = toolCall.name === 'shell' ? (chunk) => {
|
|
480
|
+
ctx.setActivityState({
|
|
481
|
+
action: 'Running shell',
|
|
482
|
+
target: args.command?.substring(0, 40),
|
|
483
|
+
startTime: Date.now(),
|
|
484
|
+
detail: chunk.trimEnd().split('\n').pop()?.substring(0, 60),
|
|
485
|
+
});
|
|
486
|
+
} : undefined;
|
|
487
|
+
const result = await executeTool(toolCall, process.cwd(), 60000, shellStreamCallback);
|
|
488
|
+
ctx.debugLog('tools', 'DONE', toolCall.name);
|
|
489
|
+
recordEvent('tool_result', result.result.slice(0, 1000), { name: toolCall.name, isError: result.isError });
|
|
490
|
+
// Record in iteration ledger
|
|
491
|
+
ctx.ledger?.recordAction(toolCall.name, args, result.isError ? 'error' : 'ok', result.isError ? result.result : undefined);
|
|
492
|
+
// Execute post-tool hooks
|
|
493
|
+
hooks.executeHooks('post-tool', {
|
|
494
|
+
tool: toolCall.name,
|
|
495
|
+
toolArgs: args,
|
|
496
|
+
toolResult: result.result,
|
|
497
|
+
}).catch((err) => {
|
|
498
|
+
ctx.debugLog('hooks', `post-tool hook failed for ${toolCall.name}:`, err instanceof Error ? err.message : err);
|
|
499
|
+
});
|
|
500
|
+
// Display result - use displayResult for UI, full result for LLM (#25)
|
|
501
|
+
if (toolCall.name === 'think') {
|
|
502
|
+
const thought = String(args.thought || '');
|
|
503
|
+
ctx.addMessage('tool', thought);
|
|
504
|
+
}
|
|
505
|
+
else if (toolCall.name === 'ask_question') {
|
|
506
|
+
// Display question prominently (#42)
|
|
507
|
+
const question = String(args.question || '');
|
|
508
|
+
const options = Array.isArray(args.options) ? args.options : undefined;
|
|
509
|
+
const contextNote = typeof args.context === 'string' ? args.context : undefined;
|
|
510
|
+
let questionMsg = `❓ ${question}`;
|
|
511
|
+
if (contextNote)
|
|
512
|
+
questionMsg += `\n ${contextNote}`;
|
|
513
|
+
if (options)
|
|
514
|
+
questionMsg += '\n' + options.map((o, i) => ` ${i + 1}. ${o}`).join('\n');
|
|
515
|
+
ctx.addMessage('assistant', questionMsg);
|
|
516
|
+
// Tell the LLM that we're waiting for user input
|
|
517
|
+
ctx.llmMessages.current.push({
|
|
518
|
+
role: 'tool',
|
|
519
|
+
content: '[Waiting for user response. The user will reply with their answer.]',
|
|
520
|
+
toolCallId: toolCall.id,
|
|
521
|
+
});
|
|
522
|
+
// Break out of tool loop - let user respond naturally
|
|
523
|
+
completedNaturally = true;
|
|
524
|
+
}
|
|
525
|
+
else if (toolCall.name === 'create_plan') {
|
|
526
|
+
// Display plan as a checklist for user approval (#19)
|
|
527
|
+
const planTitle = String(args.title || 'Plan');
|
|
528
|
+
const planSteps = Array.isArray(args.steps) ? args.steps : [];
|
|
529
|
+
const planReasoning = typeof args.reasoning === 'string' ? args.reasoning : undefined;
|
|
530
|
+
let planMsg = `📋 Plan: ${planTitle}\n`;
|
|
531
|
+
if (planReasoning)
|
|
532
|
+
planMsg += `\n ${planReasoning}\n`;
|
|
533
|
+
planMsg += '\n' + planSteps.map((s, idx) => ` ${idx + 1}. [ ] ${s}`).join('\n');
|
|
534
|
+
planMsg += '\n\n Type /approve to execute, or provide feedback to revise.';
|
|
535
|
+
ctx.addMessage('assistant', planMsg);
|
|
536
|
+
// Tell the LLM to wait for user approval
|
|
537
|
+
ctx.llmMessages.current.push({
|
|
538
|
+
role: 'tool',
|
|
539
|
+
content: '[Plan displayed to user. Waiting for approval. The user will either type /approve to execute the plan, or provide feedback to revise it. Do NOT proceed with execution until the user approves.]',
|
|
540
|
+
toolCallId: toolCall.id,
|
|
541
|
+
});
|
|
542
|
+
// Break out of tool loop - wait for user approval
|
|
543
|
+
completedNaturally = true;
|
|
544
|
+
}
|
|
545
|
+
else {
|
|
546
|
+
const display = result.displayResult || result.result;
|
|
547
|
+
const preview = display.split('\n').slice(0, 5).join('\n');
|
|
548
|
+
ctx.addMessage('tool', preview + (display.split('\n').length > 5 ? '\n...' : ''));
|
|
549
|
+
}
|
|
550
|
+
if (toolCall.name !== 'ask_question') {
|
|
551
|
+
ctx.llmMessages.current.push({
|
|
552
|
+
role: 'tool',
|
|
553
|
+
content: truncateToolResult(result.result, modelLimit),
|
|
554
|
+
toolCallId: toolCall.id,
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
ctx.ledger?.endIteration();
|
|
561
|
+
if (completedNaturally)
|
|
562
|
+
break; // ask_question pauses for user input (#42)
|
|
563
|
+
continue;
|
|
564
|
+
}
|
|
565
|
+
// Final response - move streaming content to message history
|
|
566
|
+
ctx.setThinkingState(null);
|
|
567
|
+
ctx.llmMessages.current.push({ role: 'assistant', content: response.content });
|
|
568
|
+
ctx.addMessage('assistant', response.content);
|
|
569
|
+
recordEvent('output', response.content.slice(0, 5000));
|
|
570
|
+
ctx.setStreamingResponse('');
|
|
571
|
+
ctx.setContextTokens(ctx.estimateContextTokens());
|
|
572
|
+
checkAndWarnContextLimit(ctx.actualProvider, ctx.actualModel, ctx.estimateContextTokens(), ctx.addMessage);
|
|
573
|
+
setMood('success');
|
|
574
|
+
// Auto-continue if response was truncated due to length
|
|
575
|
+
if (response.finishReason === 'length') {
|
|
576
|
+
ctx.addMessage('system', '(auto-continuing...)');
|
|
577
|
+
ctx.llmMessages.current.push({ role: 'user', content: 'Please continue where you left off.' });
|
|
578
|
+
continue; // Loop again to get continuation
|
|
579
|
+
}
|
|
580
|
+
completedNaturally = true;
|
|
581
|
+
// End iteration ledger entry
|
|
582
|
+
ctx.ledger?.endIteration('success');
|
|
583
|
+
// Auto-save full message history for session persistence
|
|
584
|
+
storage.saveMessageHistory(ctx.llmMessages.current);
|
|
585
|
+
break;
|
|
586
|
+
}
|
|
587
|
+
catch (error) {
|
|
588
|
+
ctx.setThinkingState(null);
|
|
589
|
+
ctx.setActivityState(null);
|
|
590
|
+
ctx.setStreamingResponse('');
|
|
591
|
+
// End iteration ledger entry with error
|
|
592
|
+
ctx.ledger?.endIteration('error');
|
|
593
|
+
// Format error with provider context for better suggestions
|
|
594
|
+
setMood('error');
|
|
595
|
+
const errorMsg = formatError(error, { provider: ctx.actualProvider });
|
|
596
|
+
ctx.addMessage('error', errorMsg);
|
|
597
|
+
// Classify error to provide additional recovery suggestions
|
|
598
|
+
const classified = classifyError(error);
|
|
599
|
+
const availableProviders = getAvailableProviders();
|
|
600
|
+
const otherProviders = availableProviders.filter(p => p !== ctx.actualProvider);
|
|
601
|
+
// Feed error to circuit breaker
|
|
602
|
+
if (ctx.circuitBreaker) {
|
|
603
|
+
const errorIterData = {
|
|
604
|
+
iteration: i + 1,
|
|
605
|
+
error: errorMsg,
|
|
606
|
+
timestamp: new Date(),
|
|
607
|
+
};
|
|
608
|
+
const breakerResult = ctx.circuitBreaker.check(errorIterData);
|
|
609
|
+
ctx.setBreakerHealth?.(ctx.circuitBreaker.getHealth());
|
|
610
|
+
if (breakerResult.tripped) {
|
|
611
|
+
ctx.addMessage('system', `\u26a0\ufe0f Circuit breaker tripped: ${breakerResult.breaker}\n${breakerResult.message}\n\nUse /breaker resume to continue.`);
|
|
612
|
+
completedNaturally = true;
|
|
613
|
+
break;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
// Retryable errors continue the loop (circuit breaker handles safety)
|
|
617
|
+
const isRetryable = classified.category === 'rate_limit' || classified.category === 'server' || classified.category === 'timeout' || classified.category === 'network';
|
|
618
|
+
// Suggest alternatives based on error type
|
|
619
|
+
if (classified.category === 'rate_limit' || classified.category === 'server') {
|
|
620
|
+
if (otherProviders.length > 0) {
|
|
621
|
+
ctx.addMessage('system', `\u{1f4a1} Try switching providers: /provider ${otherProviders[0]} or /models to see alternatives`);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
else if (classified.category === 'timeout' || classified.category === 'network') {
|
|
625
|
+
ctx.addMessage('system', `\u{1f4a1} Network issue detected. Check connection and try again, or use /provider to switch.`);
|
|
626
|
+
}
|
|
627
|
+
else if (classified.category === 'auth') {
|
|
628
|
+
ctx.addMessage('system', `\u{1f4a1} Run 'calliope --setup' to reconfigure API keys.`);
|
|
629
|
+
}
|
|
630
|
+
if (isRetryable && ctx.circuitBreaker) {
|
|
631
|
+
// Retryable errors: continue the loop, circuit breaker will catch repeated failures
|
|
632
|
+
ctx.addMessage('system', `Retrying... (circuit breaker will pause after ${ctx.circuitBreaker.getConfig().breakers['repeated-failure'].maxConsecutiveErrors} consecutive failures)`);
|
|
633
|
+
await new Promise(r => setTimeout(r, 2000)); // Brief delay before retry
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
636
|
+
// Non-retryable errors (auth, etc.) still kill the session
|
|
637
|
+
completedNaturally = true;
|
|
638
|
+
// On error, clear queued messages to prevent infinite retry loop
|
|
639
|
+
const currentQueuedOnError = ctx.queuedMessagesRef.current;
|
|
640
|
+
if (currentQueuedOnError.length > 0) {
|
|
641
|
+
ctx.addMessage('system', `\u26a0\ufe0f Cleared ${currentQueuedOnError.length} queued message(s) due to error. Use /clear to reset conversation.`);
|
|
642
|
+
ctx.setQueuedMessages([]);
|
|
643
|
+
}
|
|
644
|
+
return; // Exit early on error - don't process queued messages
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
// Only show warning if we actually hit the iteration limit (not errors or natural completion)
|
|
648
|
+
if (!completedNaturally) {
|
|
649
|
+
ctx.addMessage('system', `⚠️ Reached ${maxIterations} iterations limit. Task may be incomplete. Adjust with /set maxIterations <number>.`);
|
|
650
|
+
}
|
|
651
|
+
// Update context tokens after agent run
|
|
652
|
+
ctx.setContextTokens(ctx.estimateContextTokens());
|
|
653
|
+
// Process any queued messages (human-in-the-loop feedback)
|
|
654
|
+
// CRITICAL: Use ref to get current value, not stale closure
|
|
655
|
+
const currentQueued = ctx.queuedMessagesRef.current;
|
|
656
|
+
ctx.debugLog('runAgent', 'EXIT loop', `queued=${currentQueued.length}`);
|
|
657
|
+
if (currentQueued.length > 0) {
|
|
658
|
+
const queued = [...currentQueued];
|
|
659
|
+
ctx.setQueuedMessages([]); // Clear the queue
|
|
660
|
+
ctx.queuedMessagesRef.current = []; // Also clear ref immediately
|
|
661
|
+
// Combine queued messages into a single follow-up
|
|
662
|
+
const followUp = queued.length === 1
|
|
663
|
+
? queued[0]
|
|
664
|
+
: `[Multiple follow-up messages from user:]\n${queued.map((m, i) => `${i + 1}. ${m}`).join('\n')}`;
|
|
665
|
+
ctx.addMessage('system', `📨 Processing ${queued.length} queued message${queued.length > 1 ? 's' : ''}...`);
|
|
666
|
+
// Recursively run agent with follow-up
|
|
667
|
+
// Use setTimeout to avoid stack overflow and allow UI to update
|
|
668
|
+
// Note: handleSubmit's finally will set isProcessing=false, so we need to re-enable it
|
|
669
|
+
ctx.debugLog('runAgent', 'SCHEDULING recursive call for queued messages');
|
|
670
|
+
setTimeout(() => {
|
|
671
|
+
ctx.debugLog('runAgent', 'RECURSIVE call starting');
|
|
672
|
+
ctx.setIsProcessing(true);
|
|
673
|
+
runAgentImpl(ctx, followUp).finally(() => {
|
|
674
|
+
ctx.setIsProcessing(false);
|
|
675
|
+
ctx.setThinkingState(null);
|
|
676
|
+
ctx.setActivityState(null);
|
|
677
|
+
ctx.setStreamingResponse('');
|
|
678
|
+
ctx.setEditingQueueIndex(null);
|
|
679
|
+
});
|
|
680
|
+
}, 100);
|
|
681
|
+
}
|
|
682
|
+
ctx.debugLog('runAgent', 'RETURN');
|
|
683
|
+
}
|
|
684
|
+
// ============================================================================
|
|
685
|
+
// Run Loop
|
|
686
|
+
// ============================================================================
|
|
687
|
+
/**
|
|
688
|
+
* Start caffeinate to prevent system sleep during long operations (macOS).
|
|
689
|
+
*/
|
|
690
|
+
function startCaffeinate() {
|
|
691
|
+
if (process.platform !== 'darwin')
|
|
692
|
+
return null;
|
|
693
|
+
try {
|
|
694
|
+
const proc = spawn('caffeinate', ['-di'], { stdio: 'ignore', detached: true });
|
|
695
|
+
proc.unref();
|
|
696
|
+
return proc;
|
|
697
|
+
}
|
|
698
|
+
catch {
|
|
699
|
+
return null;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
function stopCaffeinate(proc) {
|
|
703
|
+
if (proc) {
|
|
704
|
+
try {
|
|
705
|
+
proc.kill('SIGTERM');
|
|
706
|
+
}
|
|
707
|
+
catch { /* already dead */ }
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* Agent loop - runs prompt repeatedly until completion promise or max iterations.
|
|
712
|
+
*/
|
|
713
|
+
export async function runLoopImpl(ctx, prompt, maxIter, completionPromise) {
|
|
714
|
+
ctx.setIsProcessing(true);
|
|
715
|
+
setMood('focused');
|
|
716
|
+
// Prevent system sleep during long agent loops (macOS)
|
|
717
|
+
const caffeinateProc = startCaffeinate();
|
|
718
|
+
for (let i = 0; i < maxIter; i++) {
|
|
719
|
+
// Check if cancelled
|
|
720
|
+
if (ctx.loopCancelledRef.current) {
|
|
721
|
+
ctx.addMessage('system', '🛑 Loop cancelled by user');
|
|
722
|
+
break;
|
|
723
|
+
}
|
|
724
|
+
ctx.setLoopIteration(i + 1);
|
|
725
|
+
ctx.addMessage('system', `🔄 Loop iteration ${i + 1}/${maxIter}`);
|
|
726
|
+
// First iteration: send original prompt. Subsequent: send continuation.
|
|
727
|
+
const iterationPrompt = i === 0
|
|
728
|
+
? prompt
|
|
729
|
+
: `Continue working on the task: "${prompt}"\n\nThis is iteration ${i + 1}. Review what you've done so far and continue making progress.`;
|
|
730
|
+
ctx.llmMessages.current.push({ role: 'user', content: iterationPrompt });
|
|
731
|
+
try {
|
|
732
|
+
// Run the agent
|
|
733
|
+
await runAgentImpl(ctx, iterationPrompt);
|
|
734
|
+
// Check for completion promise in the last assistant message
|
|
735
|
+
if (completionPromise) {
|
|
736
|
+
const lastMessage = ctx.llmMessages.current[ctx.llmMessages.current.length - 1];
|
|
737
|
+
if (lastMessage?.role === 'assistant') {
|
|
738
|
+
const content = typeof lastMessage.content === 'string'
|
|
739
|
+
? lastMessage.content
|
|
740
|
+
: JSON.stringify(lastMessage.content);
|
|
741
|
+
if (content.includes(completionPromise)) {
|
|
742
|
+
ctx.addMessage('system', `🎉 Completion promise "${completionPromise}" detected! Loop finished.`);
|
|
743
|
+
break;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
// Check cancelled again after agent run
|
|
748
|
+
if (ctx.loopCancelledRef.current) {
|
|
749
|
+
ctx.addMessage('system', '🛑 Loop cancelled by user');
|
|
750
|
+
break;
|
|
751
|
+
}
|
|
752
|
+
// Small delay between iterations
|
|
753
|
+
await new Promise(r => setTimeout(r, 500));
|
|
754
|
+
}
|
|
755
|
+
catch (error) {
|
|
756
|
+
ctx.addMessage('error', `Loop error: ${error instanceof Error ? error.message : String(error)}`);
|
|
757
|
+
break;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
// If we completed all iterations without hitting completion promise
|
|
761
|
+
if (!ctx.loopCancelledRef.current && !completionPromise) {
|
|
762
|
+
ctx.addMessage('system', `✅ Loop completed ${maxIter} iterations`);
|
|
763
|
+
}
|
|
764
|
+
ctx.setLoopActive(false);
|
|
765
|
+
ctx.setIsProcessing(false);
|
|
766
|
+
stopCaffeinate(caffeinateProc);
|
|
767
|
+
}
|
|
768
|
+
//# sourceMappingURL=agent.js.map
|