@calliopelabs/cli 2.3.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -644
- package/dist/acp.d.ts +78 -0
- package/dist/acp.d.ts.map +1 -0
- package/dist/acp.js +572 -0
- package/dist/acp.js.map +1 -0
- package/dist/auto-compressor.d.ts.map +1 -1
- package/dist/auto-compressor.js +9 -5
- package/dist/auto-compressor.js.map +1 -1
- package/dist/bin.d.ts +9 -5
- package/dist/bin.d.ts.map +1 -1
- package/dist/bin.js +108 -76
- package/dist/bin.js.map +1 -1
- package/dist/budget.d.ts +64 -0
- package/dist/budget.d.ts.map +1 -0
- package/dist/budget.js +157 -0
- package/dist/budget.js.map +1 -0
- package/dist/checkpoint.d.ts +63 -30
- package/dist/checkpoint.d.ts.map +1 -1
- package/dist/checkpoint.js +249 -150
- package/dist/checkpoint.js.map +1 -1
- package/dist/circuit-breaker/breaker.js.map +1 -1
- package/dist/config.d.ts +86 -81
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +237 -305
- package/dist/config.js.map +1 -1
- package/dist/cost.d.ts +114 -0
- package/dist/cost.d.ts.map +1 -0
- package/dist/cost.js +499 -0
- package/dist/cost.js.map +1 -0
- package/dist/diff.d.ts.map +1 -1
- package/dist/diff.js +53 -11
- package/dist/diff.js.map +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +33 -6
- package/dist/errors.js.map +1 -1
- package/dist/files.js.map +1 -1
- package/dist/fleet.d.ts +30 -0
- package/dist/fleet.d.ts.map +1 -0
- package/dist/fleet.js +86 -0
- package/dist/fleet.js.map +1 -0
- package/dist/fuzzy-search.d.ts.map +1 -1
- package/dist/fuzzy-search.js +2 -1
- package/dist/fuzzy-search.js.map +1 -1
- package/dist/git-status.js.map +1 -1
- package/dist/headless.d.ts +1 -2
- package/dist/headless.d.ts.map +1 -1
- package/dist/headless.js +222 -26
- package/dist/headless.js.map +1 -1
- package/dist/hooks.d.ts +8 -2
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +97 -11
- package/dist/hooks.js.map +1 -1
- package/dist/hud/api.d.ts +3 -18
- package/dist/hud/api.d.ts.map +1 -1
- package/dist/hud/api.js +9 -352
- package/dist/hud/api.js.map +1 -1
- package/dist/hud/palettes.d.ts.map +1 -1
- package/dist/hud/palettes.js +0 -180
- package/dist/hud/palettes.js.map +1 -1
- package/dist/hud/skins.d.ts.map +1 -1
- package/dist/hud/skins.js +0 -263
- package/dist/hud/skins.js.map +1 -1
- package/dist/hud/types.d.ts +2 -5
- package/dist/hud/types.d.ts.map +1 -1
- package/dist/index.d.ts +1 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -4
- package/dist/index.js.map +1 -1
- package/dist/iteration-ledger.d.ts.map +1 -1
- package/dist/iteration-ledger.js +3 -2
- package/dist/iteration-ledger.js.map +1 -1
- package/dist/local-model.d.ts +150 -0
- package/dist/local-model.d.ts.map +1 -0
- package/dist/local-model.js +408 -0
- package/dist/local-model.js.map +1 -0
- package/dist/markdown.d.ts.map +1 -1
- package/dist/markdown.js +32 -10
- package/dist/markdown.js.map +1 -1
- package/dist/mcp.d.ts +35 -5
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +190 -13
- package/dist/mcp.js.map +1 -1
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +6 -4
- package/dist/memory.js.map +1 -1
- package/dist/model-detection.d.ts +14 -1
- package/dist/model-detection.d.ts.map +1 -1
- package/dist/model-detection.js +308 -115
- package/dist/model-detection.js.map +1 -1
- package/dist/parallel-tools.d.ts +9 -1
- package/dist/parallel-tools.d.ts.map +1 -1
- package/dist/parallel-tools.js +6 -5
- package/dist/parallel-tools.js.map +1 -1
- package/dist/plugins.d.ts +68 -0
- package/dist/plugins.d.ts.map +1 -1
- package/dist/plugins.js +201 -3
- package/dist/plugins.js.map +1 -1
- package/dist/policy.d.ts +43 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/policy.js +139 -0
- package/dist/policy.js.map +1 -0
- package/dist/project-config.d.ts +1 -2
- package/dist/project-config.d.ts.map +1 -1
- package/dist/project-config.js +0 -3
- package/dist/project-config.js.map +1 -1
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +36 -2
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/bedrock.d.ts.map +1 -1
- package/dist/providers/bedrock.js +83 -22
- package/dist/providers/bedrock.js.map +1 -1
- package/dist/providers/compat.d.ts +21 -0
- package/dist/providers/compat.d.ts.map +1 -1
- package/dist/providers/compat.js +176 -6
- package/dist/providers/compat.js.map +1 -1
- package/dist/providers/google.js.map +1 -1
- package/dist/providers/index.d.ts +19 -4
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js +62 -9
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/ollama.d.ts +2 -2
- package/dist/providers/ollama.d.ts.map +1 -1
- package/dist/providers/ollama.js +26 -6
- package/dist/providers/ollama.js.map +1 -1
- package/dist/providers/openai.d.ts.map +1 -1
- package/dist/providers/openai.js +14 -10
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/types.d.ts +8 -0
- package/dist/providers/types.d.ts.map +1 -1
- package/dist/providers/types.js +19 -10
- package/dist/providers/types.js.map +1 -1
- package/dist/replay.d.ts +37 -0
- package/dist/replay.d.ts.map +1 -0
- package/dist/replay.js +168 -0
- package/dist/replay.js.map +1 -0
- package/dist/risk.d.ts.map +1 -1
- package/dist/risk.js +15 -5
- package/dist/risk.js.map +1 -1
- package/dist/router.d.ts +133 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/{model-router.js → router.js} +336 -10
- package/dist/router.js.map +1 -0
- package/dist/runlog.d.ts +185 -0
- package/dist/runlog.d.ts.map +1 -0
- package/dist/runlog.js +398 -0
- package/dist/runlog.js.map +1 -0
- package/dist/{sandbox.d.ts → sandbox/docker.d.ts} +1 -1
- package/dist/sandbox/docker.d.ts.map +1 -0
- package/dist/{sandbox.js → sandbox/docker.js} +1 -1
- package/dist/sandbox/docker.js.map +1 -0
- package/dist/sandbox/index.d.ts +36 -0
- package/dist/sandbox/index.d.ts.map +1 -0
- package/dist/sandbox/index.js +62 -0
- package/dist/sandbox/index.js.map +1 -0
- package/dist/{sandbox-native.d.ts → sandbox/native.d.ts} +2 -1
- package/dist/sandbox/native.d.ts.map +1 -0
- package/dist/{sandbox-native.js → sandbox/native.js} +38 -6
- package/dist/sandbox/native.js.map +1 -0
- package/dist/scope.d.ts +10 -0
- package/dist/scope.d.ts.map +1 -1
- package/dist/scope.js +76 -16
- package/dist/scope.js.map +1 -1
- package/dist/scuttlebot/client.d.ts +83 -0
- package/dist/scuttlebot/client.d.ts.map +1 -0
- package/dist/scuttlebot/client.js +350 -0
- package/dist/scuttlebot/client.js.map +1 -0
- package/dist/scuttlebot/config.d.ts +28 -0
- package/dist/scuttlebot/config.d.ts.map +1 -0
- package/dist/scuttlebot/config.js +91 -0
- package/dist/scuttlebot/config.js.map +1 -0
- package/dist/scuttlebot/http-client.d.ts +63 -0
- package/dist/scuttlebot/http-client.d.ts.map +1 -0
- package/dist/scuttlebot/http-client.js +124 -0
- package/dist/scuttlebot/http-client.js.map +1 -0
- package/dist/scuttlebot/index.d.ts +13 -0
- package/dist/scuttlebot/index.d.ts.map +1 -0
- package/dist/scuttlebot/index.js +10 -0
- package/dist/scuttlebot/index.js.map +1 -0
- package/dist/scuttlebot/irc-client.d.ts +124 -0
- package/dist/scuttlebot/irc-client.d.ts.map +1 -0
- package/dist/scuttlebot/irc-client.js +599 -0
- package/dist/scuttlebot/irc-client.js.map +1 -0
- package/dist/setup.d.ts.map +1 -1
- package/dist/setup.js +22 -54
- package/dist/setup.js.map +1 -1
- package/dist/skills.d.ts +58 -0
- package/dist/skills.d.ts.map +1 -1
- package/dist/skills.js +186 -16
- package/dist/skills.js.map +1 -1
- package/dist/storage.d.ts +0 -4
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +87 -8
- package/dist/storage.js.map +1 -1
- package/dist/streaming.d.ts +25 -0
- package/dist/streaming.d.ts.map +1 -1
- package/dist/streaming.js +59 -0
- package/dist/streaming.js.map +1 -1
- package/dist/styles.d.ts +6 -0
- package/dist/styles.d.ts.map +1 -1
- package/dist/styles.js +11 -0
- package/dist/styles.js.map +1 -1
- package/dist/summarization.js.map +1 -1
- package/dist/terminal-image.js +1 -1
- package/dist/terminal-image.js.map +1 -1
- package/dist/themes.d.ts +5 -4
- package/dist/themes.d.ts.map +1 -1
- package/dist/themes.js +35 -90
- package/dist/themes.js.map +1 -1
- package/dist/tools.d.ts +27 -3
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +352 -209
- package/dist/tools.js.map +1 -1
- package/dist/trust.d.ts +16 -3
- package/dist/trust.d.ts.map +1 -1
- package/dist/trust.js +23 -4
- package/dist/trust.js.map +1 -1
- package/dist/types.d.ts +4 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +34 -44
- package/dist/types.js.map +1 -1
- package/dist/ui/agent.d.ts +4 -7
- package/dist/ui/agent.d.ts.map +1 -1
- package/dist/ui/agent.js +354 -73
- package/dist/ui/agent.js.map +1 -1
- package/dist/ui/animations.d.ts +37 -0
- package/dist/ui/animations.d.ts.map +1 -0
- package/dist/ui/animations.js +227 -0
- package/dist/ui/animations.js.map +1 -0
- package/dist/ui/chat-input-keys.d.ts +42 -0
- package/dist/ui/chat-input-keys.d.ts.map +1 -0
- package/dist/ui/chat-input-keys.js +360 -0
- package/dist/ui/chat-input-keys.js.map +1 -0
- package/dist/ui/chat-input.d.ts +5 -2
- package/dist/ui/chat-input.d.ts.map +1 -1
- package/dist/ui/chat-input.js +54 -308
- package/dist/ui/chat-input.js.map +1 -1
- package/dist/ui/commands.d.ts +15 -45
- package/dist/ui/commands.d.ts.map +1 -1
- package/dist/ui/commands.js +461 -2636
- package/dist/ui/commands.js.map +1 -1
- package/dist/ui/completions.d.ts +1 -0
- package/dist/ui/completions.d.ts.map +1 -1
- package/dist/ui/completions.js +36 -107
- package/dist/ui/completions.js.map +1 -1
- package/dist/ui/components.d.ts +14 -6
- package/dist/ui/components.d.ts.map +1 -1
- package/dist/ui/components.js +17 -226
- package/dist/ui/components.js.map +1 -1
- package/dist/ui/context.js +6 -6
- package/dist/ui/context.js.map +1 -1
- package/dist/ui/debug-log.d.ts +11 -0
- package/dist/ui/debug-log.d.ts.map +1 -0
- package/dist/ui/debug-log.js +21 -0
- package/dist/ui/debug-log.js.map +1 -0
- package/dist/ui/error-boundary.d.ts.map +1 -1
- package/dist/ui/error-boundary.js.map +1 -1
- package/dist/ui/frame.d.ts.map +1 -1
- package/dist/ui/frame.js +1 -3
- package/dist/ui/frame.js.map +1 -1
- package/dist/ui/index.d.ts +11 -3
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/index.js +46 -887
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/input-utils.d.ts +20 -0
- package/dist/ui/input-utils.d.ts.map +1 -0
- package/dist/ui/input-utils.js +35 -0
- package/dist/ui/input-utils.js.map +1 -0
- package/dist/ui/messages.d.ts +8 -7
- package/dist/ui/messages.d.ts.map +1 -1
- package/dist/ui/messages.js +51 -35
- package/dist/ui/messages.js.map +1 -1
- package/dist/ui/modals/index.d.ts +11 -0
- package/dist/ui/modals/index.d.ts.map +1 -0
- package/dist/ui/modals/index.js +10 -0
- package/dist/ui/modals/index.js.map +1 -0
- package/dist/ui/{modals.d.ts → modals/prompts.d.ts} +5 -20
- package/dist/ui/modals/prompts.d.ts.map +1 -0
- package/dist/ui/{modals.js → modals/prompts.js} +14 -94
- package/dist/ui/modals/prompts.js.map +1 -0
- package/dist/ui/modals/selectors.d.ts +33 -0
- package/dist/ui/modals/selectors.d.ts.map +1 -0
- package/dist/ui/modals/selectors.js +111 -0
- package/dist/ui/modals/selectors.js.map +1 -0
- package/dist/ui/modals/setup.d.ts +17 -0
- package/dist/ui/modals/setup.d.ts.map +1 -0
- package/dist/ui/modals/setup.js +51 -0
- package/dist/ui/modals/setup.js.map +1 -0
- package/dist/ui/regions/input-region.d.ts +40 -0
- package/dist/ui/regions/input-region.d.ts.map +1 -0
- package/dist/ui/regions/input-region.js +116 -0
- package/dist/ui/regions/input-region.js.map +1 -0
- package/dist/ui/regions/modal-host.d.ts +41 -0
- package/dist/ui/regions/modal-host.d.ts.map +1 -0
- package/dist/ui/regions/modal-host.js +51 -0
- package/dist/ui/regions/modal-host.js.map +1 -0
- package/dist/ui/regions/render-probe.d.ts +22 -0
- package/dist/ui/regions/render-probe.d.ts.map +1 -0
- package/dist/ui/regions/render-probe.js +45 -0
- package/dist/ui/regions/render-probe.js.map +1 -0
- package/dist/ui/regions/static-scrollback.d.ts +39 -0
- package/dist/ui/regions/static-scrollback.d.ts.map +1 -0
- package/dist/ui/regions/static-scrollback.js +68 -0
- package/dist/ui/regions/static-scrollback.js.map +1 -0
- package/dist/ui/regions/status-region.d.ts +24 -0
- package/dist/ui/regions/status-region.d.ts.map +1 -0
- package/dist/ui/regions/status-region.js +17 -0
- package/dist/ui/regions/status-region.js.map +1 -0
- package/dist/ui/regions/transcript-region.d.ts +41 -0
- package/dist/ui/regions/transcript-region.d.ts.map +1 -0
- package/dist/ui/regions/transcript-region.js +52 -0
- package/dist/ui/regions/transcript-region.js.map +1 -0
- package/dist/ui/self-restart.d.ts +9 -0
- package/dist/ui/self-restart.d.ts.map +1 -0
- package/dist/ui/self-restart.js +24 -0
- package/dist/ui/self-restart.js.map +1 -0
- package/dist/ui/state/use-chat-controller.d.ts +23 -0
- package/dist/ui/state/use-chat-controller.d.ts.map +1 -0
- package/dist/ui/state/use-chat-controller.js +643 -0
- package/dist/ui/state/use-chat-controller.js.map +1 -0
- package/dist/ui/state/use-loop-state.d.ts +23 -0
- package/dist/ui/state/use-loop-state.d.ts.map +1 -0
- package/dist/ui/state/use-loop-state.js +39 -0
- package/dist/ui/state/use-loop-state.js.map +1 -0
- package/dist/ui/state/use-modal-state.d.ts +52 -0
- package/dist/ui/state/use-modal-state.d.ts.map +1 -0
- package/dist/ui/state/use-modal-state.js +44 -0
- package/dist/ui/state/use-modal-state.js.map +1 -0
- package/dist/ui/state/use-model-state.d.ts +29 -0
- package/dist/ui/state/use-model-state.d.ts.map +1 -0
- package/dist/ui/state/use-model-state.js +47 -0
- package/dist/ui/state/use-model-state.js.map +1 -0
- package/dist/ui/state/use-processing-state.d.ts +24 -0
- package/dist/ui/state/use-processing-state.d.ts.map +1 -0
- package/dist/ui/state/use-processing-state.js +46 -0
- package/dist/ui/state/use-processing-state.js.map +1 -0
- package/dist/ui/state/use-queue-state.d.ts +19 -0
- package/dist/ui/state/use-queue-state.d.ts.map +1 -0
- package/dist/ui/state/use-queue-state.js +30 -0
- package/dist/ui/state/use-queue-state.js.map +1 -0
- package/dist/ui/state/use-session-init.d.ts +22 -0
- package/dist/ui/state/use-session-init.d.ts.map +1 -0
- package/dist/ui/state/use-session-init.js +77 -0
- package/dist/ui/state/use-session-init.js.map +1 -0
- package/dist/ui/state/use-session-stats.d.ts +16 -0
- package/dist/ui/state/use-session-stats.d.ts.map +1 -0
- package/dist/ui/state/use-session-stats.js +18 -0
- package/dist/ui/state/use-session-stats.js.map +1 -0
- package/dist/ui/state/use-terminal-width.d.ts +8 -0
- package/dist/ui/state/use-terminal-width.d.ts.map +1 -0
- package/dist/ui/state/use-terminal-width.js +22 -0
- package/dist/ui/state/use-terminal-width.js.map +1 -0
- package/dist/ui/state/use-transcript-state.d.ts +21 -0
- package/dist/ui/state/use-transcript-state.d.ts.map +1 -0
- package/dist/ui/state/use-transcript-state.js +49 -0
- package/dist/ui/state/use-transcript-state.js.map +1 -0
- package/dist/ui/status-bar.d.ts +4 -1
- package/dist/ui/status-bar.d.ts.map +1 -1
- package/dist/ui/status-bar.js +13 -3
- package/dist/ui/status-bar.js.map +1 -1
- package/dist/ui/types.d.ts +3 -0
- package/dist/ui/types.d.ts.map +1 -1
- package/dist/version-check.d.ts.map +1 -1
- package/dist/version-check.js +10 -0
- package/dist/version-check.js.map +1 -1
- package/package.json +12 -8
- package/dist/agents/agent-config-loader.d.ts +0 -60
- package/dist/agents/agent-config-loader.d.ts.map +0 -1
- package/dist/agents/agent-config-loader.js +0 -402
- package/dist/agents/agent-config-loader.js.map +0 -1
- package/dist/agents/agent-config-presets.d.ts +0 -10
- package/dist/agents/agent-config-presets.d.ts.map +0 -1
- package/dist/agents/agent-config-presets.js +0 -940
- package/dist/agents/agent-config-presets.js.map +0 -1
- package/dist/agents/agent-config-types.d.ts +0 -145
- package/dist/agents/agent-config-types.d.ts.map +0 -1
- package/dist/agents/agent-config-types.js +0 -12
- package/dist/agents/agent-config-types.js.map +0 -1
- package/dist/agents/agent-detection.d.ts +0 -35
- package/dist/agents/agent-detection.d.ts.map +0 -1
- package/dist/agents/agent-detection.js +0 -110
- package/dist/agents/agent-detection.js.map +0 -1
- package/dist/agents/aggregator.d.ts +0 -19
- package/dist/agents/aggregator.d.ts.map +0 -1
- package/dist/agents/aggregator.js +0 -141
- package/dist/agents/aggregator.js.map +0 -1
- package/dist/agents/cli-backend.d.ts +0 -32
- package/dist/agents/cli-backend.d.ts.map +0 -1
- package/dist/agents/cli-backend.js +0 -271
- package/dist/agents/cli-backend.js.map +0 -1
- package/dist/agents/council-types.d.ts +0 -115
- package/dist/agents/council-types.d.ts.map +0 -1
- package/dist/agents/council-types.js +0 -81
- package/dist/agents/council-types.js.map +0 -1
- package/dist/agents/council.d.ts +0 -112
- package/dist/agents/council.d.ts.map +0 -1
- package/dist/agents/council.js +0 -722
- package/dist/agents/council.js.map +0 -1
- package/dist/agents/decomposer.d.ts +0 -33
- package/dist/agents/decomposer.d.ts.map +0 -1
- package/dist/agents/decomposer.js +0 -138
- package/dist/agents/decomposer.js.map +0 -1
- package/dist/agents/dynamic-tools.d.ts +0 -52
- package/dist/agents/dynamic-tools.d.ts.map +0 -1
- package/dist/agents/dynamic-tools.js +0 -395
- package/dist/agents/dynamic-tools.js.map +0 -1
- package/dist/agents/index.d.ts +0 -29
- package/dist/agents/index.d.ts.map +0 -1
- package/dist/agents/index.js +0 -29
- package/dist/agents/index.js.map +0 -1
- package/dist/agents/installer.d.ts +0 -39
- package/dist/agents/installer.d.ts.map +0 -1
- package/dist/agents/installer.js +0 -205
- package/dist/agents/installer.js.map +0 -1
- package/dist/agents/orchestrator.d.ts +0 -106
- package/dist/agents/orchestrator.d.ts.map +0 -1
- package/dist/agents/orchestrator.js +0 -363
- package/dist/agents/orchestrator.js.map +0 -1
- package/dist/agents/sdk-backend.d.ts +0 -63
- package/dist/agents/sdk-backend.d.ts.map +0 -1
- package/dist/agents/sdk-backend.js +0 -489
- package/dist/agents/sdk-backend.js.map +0 -1
- package/dist/agents/swarm-types.d.ts +0 -84
- package/dist/agents/swarm-types.d.ts.map +0 -1
- package/dist/agents/swarm-types.js +0 -20
- package/dist/agents/swarm-types.js.map +0 -1
- package/dist/agents/swarm.d.ts +0 -79
- package/dist/agents/swarm.d.ts.map +0 -1
- package/dist/agents/swarm.js +0 -375
- package/dist/agents/swarm.js.map +0 -1
- package/dist/agents/tools.d.ts +0 -26
- package/dist/agents/tools.d.ts.map +0 -1
- package/dist/agents/tools.js +0 -776
- package/dist/agents/tools.js.map +0 -1
- package/dist/agents/types.d.ts +0 -108
- package/dist/agents/types.d.ts.map +0 -1
- package/dist/agents/types.js +0 -43
- package/dist/agents/types.js.map +0 -1
- package/dist/api-server.d.ts +0 -36
- package/dist/api-server.d.ts.map +0 -1
- package/dist/api-server.js +0 -255
- package/dist/api-server.js.map +0 -1
- package/dist/auto-checkpoint.d.ts +0 -35
- package/dist/auto-checkpoint.d.ts.map +0 -1
- package/dist/auto-checkpoint.js +0 -143
- package/dist/auto-checkpoint.js.map +0 -1
- package/dist/background-jobs.d.ts +0 -45
- package/dist/background-jobs.d.ts.map +0 -1
- package/dist/background-jobs.js +0 -127
- package/dist/background-jobs.js.map +0 -1
- package/dist/branching.d.ts +0 -56
- package/dist/branching.d.ts.map +0 -1
- package/dist/branching.js +0 -211
- package/dist/branching.js.map +0 -1
- package/dist/cli/agent.d.ts +0 -9
- package/dist/cli/agent.d.ts.map +0 -1
- package/dist/cli/agent.js +0 -314
- package/dist/cli/agent.js.map +0 -1
- package/dist/cli/commands.d.ts +0 -12
- package/dist/cli/commands.d.ts.map +0 -1
- package/dist/cli/commands.js +0 -943
- package/dist/cli/commands.js.map +0 -1
- package/dist/cli/index.d.ts +0 -8
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/index.js +0 -237
- package/dist/cli/index.js.map +0 -1
- package/dist/cli/types.d.ts +0 -33
- package/dist/cli/types.d.ts.map +0 -1
- package/dist/cli/types.js +0 -21
- package/dist/cli/types.js.map +0 -1
- package/dist/companions.d.ts +0 -54
- package/dist/companions.d.ts.map +0 -1
- package/dist/companions.js +0 -440
- package/dist/companions.js.map +0 -1
- package/dist/completion.d.ts +0 -75
- package/dist/completion.d.ts.map +0 -1
- package/dist/completion.js +0 -234
- package/dist/completion.js.map +0 -1
- package/dist/file-watcher.d.ts +0 -91
- package/dist/file-watcher.d.ts.map +0 -1
- package/dist/file-watcher.js +0 -269
- package/dist/file-watcher.js.map +0 -1
- package/dist/hud/theme-packs/api.d.ts +0 -51
- package/dist/hud/theme-packs/api.d.ts.map +0 -1
- package/dist/hud/theme-packs/api.js +0 -145
- package/dist/hud/theme-packs/api.js.map +0 -1
- package/dist/hud/theme-packs/index.d.ts +0 -18
- package/dist/hud/theme-packs/index.d.ts.map +0 -1
- package/dist/hud/theme-packs/index.js +0 -38
- package/dist/hud/theme-packs/index.js.map +0 -1
- package/dist/hud/theme-packs/types.d.ts +0 -29
- package/dist/hud/theme-packs/types.d.ts.map +0 -1
- package/dist/hud/theme-packs/types.js +0 -9
- package/dist/hud/theme-packs/types.js.map +0 -1
- package/dist/idle-eviction.d.ts +0 -34
- package/dist/idle-eviction.d.ts.map +0 -1
- package/dist/idle-eviction.js +0 -78
- package/dist/idle-eviction.js.map +0 -1
- package/dist/keyboard.d.ts +0 -57
- package/dist/keyboard.d.ts.map +0 -1
- package/dist/keyboard.js +0 -265
- package/dist/keyboard.js.map +0 -1
- package/dist/model-router.d.ts +0 -67
- package/dist/model-router.d.ts.map +0 -1
- package/dist/model-router.js.map +0 -1
- package/dist/providers/openai-compat-shims.d.ts +0 -31
- package/dist/providers/openai-compat-shims.d.ts.map +0 -1
- package/dist/providers/openai-compat-shims.js +0 -179
- package/dist/providers/openai-compat-shims.js.map +0 -1
- package/dist/sandbox-native.d.ts.map +0 -1
- package/dist/sandbox-native.js.map +0 -1
- package/dist/sandbox.d.ts.map +0 -1
- package/dist/sandbox.js.map +0 -1
- package/dist/session-timeout.d.ts +0 -31
- package/dist/session-timeout.d.ts.map +0 -1
- package/dist/session-timeout.js +0 -100
- package/dist/session-timeout.js.map +0 -1
- package/dist/smart-router.d.ts +0 -73
- package/dist/smart-router.d.ts.map +0 -1
- package/dist/smart-router.js +0 -332
- package/dist/smart-router.js.map +0 -1
- package/dist/terminal-recording.d.ts +0 -55
- package/dist/terminal-recording.d.ts.map +0 -1
- package/dist/terminal-recording.js +0 -182
- package/dist/terminal-recording.js.map +0 -1
- package/dist/tmux.d.ts +0 -35
- package/dist/tmux.d.ts.map +0 -1
- package/dist/tmux.js +0 -106
- package/dist/tmux.js.map +0 -1
- package/dist/ui/modals.d.ts.map +0 -1
- package/dist/ui/modals.js.map +0 -1
- package/dist/ui/pack-picker.d.ts +0 -12
- package/dist/ui/pack-picker.d.ts.map +0 -1
- package/dist/ui/pack-picker.js +0 -101
- package/dist/ui/pack-picker.js.map +0 -1
- package/dist/ui/theme-picker.d.ts +0 -24
- package/dist/ui/theme-picker.d.ts.map +0 -1
- package/dist/ui/theme-picker.js +0 -190
- package/dist/ui/theme-picker.js.map +0 -1
package/dist/ui/commands.js
CHANGED
|
@@ -1,100 +1,97 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* UI Module - Command Handler
|
|
3
3
|
*
|
|
4
|
-
* Handles
|
|
5
|
-
*
|
|
4
|
+
* Handles the slash commands exposed by the TUI. The surface is intentionally
|
|
5
|
+
* small: see COMMAND_NAMES for the canonical list. Legacy/verbose commands have
|
|
6
|
+
* been folded into subcommands of the survivors (e.g. /models -> /model list,
|
|
7
|
+
* /add-dir -> /scope add, /set -> /config set, /breakloop -> /loop stop).
|
|
6
8
|
*/
|
|
7
9
|
import * as fs from 'fs';
|
|
8
10
|
import * as path from 'path';
|
|
9
11
|
import * as config from '../config.js';
|
|
10
12
|
import { selectProvider, getAvailableProviders } from '../providers/index.js';
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import { getAvailableModels } from '../model-detection.js';
|
|
13
|
+
import { MODE_CONFIG } from '../types.js';
|
|
14
|
+
import { getSystemPromptForProvider } from '../local-model.js';
|
|
15
|
+
import { getAvailableModels, getModelContextLimit } from '../model-detection.js';
|
|
14
16
|
import * as storage from '../storage.js';
|
|
15
17
|
import * as mcp from '../mcp.js';
|
|
16
18
|
import * as skills from '../skills.js';
|
|
17
|
-
import * as modelRouter from '../model-router.js';
|
|
18
19
|
import * as summarization from '../summarization.js';
|
|
19
20
|
import { addToScope, removeFromScope, getScopeSummary, getScopeDetails, resetScope } from '../scope.js';
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import { getCurrentSkin, getCurrentPalette, applySkin, applyPalette, listSkins, listPalettes } from '../hud/api.js';
|
|
23
|
-
import { getCurrentCompanion, applyCompanion, listCompanions, getMoodText } from '../companions.js';
|
|
24
|
-
import { createJob, runJob, cancelJob, getJob, formatJob, formatJobsList, clearFinishedJobs } from '../background-jobs.js';
|
|
25
|
-
import { listRecordings, loadRecording, formatRecording, deleteRecording } from '../terminal-recording.js';
|
|
26
|
-
import { startApiServer, stopApiServer, isApiServerRunning } from '../api-server.js';
|
|
27
|
-
import { getTerminalImageInfo, getImageModeLabel, renderSkinBanner, renderTransition } from '../terminal-image.js';
|
|
28
|
-
import { applyThemePack, listThemePacks, getCurrentPack, getCompanionMode, setCompanionMode, getThemePack } from '../hud/theme-packs/api.js';
|
|
29
|
-
import { getModelContextLimit } from '../model-detection.js';
|
|
21
|
+
import { fleetConfigured, fleetActive, fleetStatus, fleetEnable, fleetDisable, fleetPostMessage } from '../fleet.js';
|
|
22
|
+
import { getTerminalImageInfo, getImageModeLabel } from '../terminal-image.js';
|
|
30
23
|
import { resetContextWarnings } from './context.js';
|
|
31
24
|
import * as memory from '../memory.js';
|
|
25
|
+
import { getBudgetCaps, hasBudgetCaps, loadProjectSpend } from '../budget.js';
|
|
32
26
|
import { resolveIterationLimit, formatIterationLimit, isFiniteIterationLimit, } from '../iteration-limit.js';
|
|
27
|
+
// ============================================================================
|
|
28
|
+
// Command registry
|
|
29
|
+
// ============================================================================
|
|
30
|
+
/**
|
|
31
|
+
* Canonical list of every slash-command label handled by the switch in
|
|
32
|
+
* handleCommand(). Tests assert this stays in lockstep with the switch and a
|
|
33
|
+
* superset of the completion roots. `/quit` is the sole alias (of `/exit`);
|
|
34
|
+
* `/fleet` is flag-gated in completions but always handled here.
|
|
35
|
+
*/
|
|
36
|
+
export const COMMAND_NAMES = [
|
|
37
|
+
'/help',
|
|
38
|
+
'/status',
|
|
39
|
+
'/clear',
|
|
40
|
+
'/exit',
|
|
41
|
+
'/quit',
|
|
42
|
+
'/model',
|
|
43
|
+
'/provider',
|
|
44
|
+
'/mode',
|
|
45
|
+
'/undo',
|
|
46
|
+
'/export',
|
|
47
|
+
'/resume',
|
|
48
|
+
'/compact',
|
|
49
|
+
'/scope',
|
|
50
|
+
'/memory',
|
|
51
|
+
'/mcp',
|
|
52
|
+
'/skills',
|
|
53
|
+
'/config',
|
|
54
|
+
'/setup',
|
|
55
|
+
'/trust',
|
|
56
|
+
'/cost',
|
|
57
|
+
'/loop',
|
|
58
|
+
'/restore',
|
|
59
|
+
'/debug',
|
|
60
|
+
'/fleet',
|
|
61
|
+
];
|
|
33
62
|
// Builds the full system prompt including memory context (project + global).
|
|
34
|
-
// dir should be the project directory for the active/resumed session
|
|
35
|
-
|
|
36
|
-
|
|
63
|
+
// dir should be the project directory for the active/resumed session; provider
|
|
64
|
+
// selects the compact (local) vs full (cloud) base prompt (feature 5).
|
|
65
|
+
function buildFullSystemPrompt(dir, provider) {
|
|
66
|
+
const base = getSystemPromptForProvider(provider);
|
|
37
67
|
const mem = memory.buildMemoryContext(dir);
|
|
38
68
|
return mem.trim() ? base + '\n\n--- Project Context ---\n' + mem : base;
|
|
39
69
|
}
|
|
40
70
|
function getActiveProjectDir(ctx) {
|
|
41
71
|
return ctx.sessionRef.current?.projectPath ?? process.cwd();
|
|
42
72
|
}
|
|
43
|
-
function formatLedgerDuration(durationMs) {
|
|
44
|
-
if (durationMs < 1000)
|
|
45
|
-
return `${durationMs}ms`;
|
|
46
|
-
if (durationMs < 60_000)
|
|
47
|
-
return `${(durationMs / 1000).toFixed(1)}s`;
|
|
48
|
-
return `${(durationMs / 60_000).toFixed(1)}m`;
|
|
49
|
-
}
|
|
50
73
|
function formatSessionLogLimit(limit) {
|
|
51
74
|
return limit > 0 ? String(limit) : 'unlimited';
|
|
52
75
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
if (
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
parts.push('unlimited');
|
|
76
|
+
/**
|
|
77
|
+
* Apply a `budget.*` config key. `off`/`none`/empty clears the cap. Throws on an
|
|
78
|
+
* invalid number so the caller's try/catch surfaces the message.
|
|
79
|
+
*/
|
|
80
|
+
function applyBudgetKey(field, value) {
|
|
81
|
+
const budget = { ...(config.get('budget') ?? {}) };
|
|
82
|
+
if (value === 'off' || value === 'none' || value === '') {
|
|
83
|
+
delete budget[field];
|
|
84
|
+
config.set('budget', budget);
|
|
85
|
+
return `✓ ${field} cleared`;
|
|
64
86
|
}
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
line += ` (${run.errorSummary})`;
|
|
87
|
+
const num = Number(value);
|
|
88
|
+
if (isNaN(num) || num < 0) {
|
|
89
|
+
throw new Error(`${field} must be a non-negative number (or 'off' to clear)`);
|
|
69
90
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
return;
|
|
75
|
-
const runId = ledger.startRun(kind, prompt);
|
|
76
|
-
void (async () => {
|
|
77
|
-
for (;;) {
|
|
78
|
-
const current = getStatus();
|
|
79
|
-
if (!current) {
|
|
80
|
-
ledger.finishRun(runId, 'failed', { errorSummary: 'Run state no longer available' });
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
if (current.status === 'completed') {
|
|
84
|
-
ledger.finishRun(runId, 'completed');
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
if (current.status === 'failed') {
|
|
88
|
-
ledger.finishRun(runId, 'failed', { errorSummary: current.error });
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
if (current.status === 'cancelled') {
|
|
92
|
-
ledger.finishRun(runId, 'cancelled', { errorSummary: current.error || 'Cancelled' });
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
await new Promise(resolve => setTimeout(resolve, 500));
|
|
96
|
-
}
|
|
97
|
-
})();
|
|
91
|
+
budget[field] = field === 'maxTokensPerRun' ? Math.floor(num) : num;
|
|
92
|
+
config.set('budget', budget);
|
|
93
|
+
const unit = field === 'maxTokensPerRun' ? ' tokens' : ' USD';
|
|
94
|
+
return `✓ ${field} set to ${budget[field]}${unit}`;
|
|
98
95
|
}
|
|
99
96
|
// ============================================================================
|
|
100
97
|
// handleCommand
|
|
@@ -103,108 +100,79 @@ export async function handleCommand(cmd, ctx) {
|
|
|
103
100
|
const parts = cmd.split(/\s+/);
|
|
104
101
|
const command = parts[0].toLowerCase();
|
|
105
102
|
switch (command) {
|
|
106
|
-
case '/help':
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
/
|
|
110
|
-
/
|
|
111
|
-
/
|
|
112
|
-
/mode [plan|hybrid|work] - Switch modes (Shift+Tab to cycle)
|
|
113
|
-
/persona [name] - Switch personality
|
|
114
|
-
/route [on|off] - Auto model routing (/autoroute)
|
|
115
|
-
/smart [on|off|cost|test] - Cross-provider smart routing
|
|
116
|
-
/breaker [status|resume] - Circuit breaker control (/cb)
|
|
117
|
-
|
|
118
|
-
--- Conversation ---
|
|
119
|
-
/edit - Edit and resend last message
|
|
120
|
-
/undo / /redo - Undo/redo (up to 10 steps)
|
|
121
|
-
/copy - Copy last response to clipboard
|
|
122
|
-
/export [file.md] - Export conversation to markdown
|
|
123
|
-
/branch [new|switch] - Conversation branches
|
|
124
|
-
/clear - Clear conversation (/c)
|
|
103
|
+
case '/help': {
|
|
104
|
+
let help = `Session
|
|
105
|
+
/help Show this help
|
|
106
|
+
/status Provider, model, tokens, terminal, fleet
|
|
107
|
+
/clear Clear the conversation
|
|
108
|
+
/exit Exit Calliope (alias /quit)
|
|
125
109
|
|
|
126
|
-
|
|
127
|
-
/
|
|
128
|
-
/
|
|
129
|
-
/
|
|
130
|
-
/checkpoint [list|clear] - File checkpoints (/cp)
|
|
131
|
-
/restore <path> [index] - Restore file from checkpoint
|
|
132
|
-
/bookmark [name|list|goto] - Bookmarks (/bm)
|
|
133
|
-
/queue [show|clear|flush] - Message queue (/q)
|
|
110
|
+
Model & Mode
|
|
111
|
+
/model [name|list] Switch model, or list/pick available models
|
|
112
|
+
/provider [name|list] Switch provider, or list providers
|
|
113
|
+
/mode [plan|hybrid|work] Switch mode (Shift+Tab to cycle)
|
|
134
114
|
|
|
135
|
-
|
|
136
|
-
/
|
|
137
|
-
/
|
|
138
|
-
/
|
|
139
|
-
/
|
|
140
|
-
/trust [add|remove|list] - Project trust registry
|
|
141
|
-
/find <pattern> - Fuzzy file search
|
|
142
|
-
/search <query> - Search conversation
|
|
115
|
+
Conversation
|
|
116
|
+
/undo Undo the last change (up to 10 steps)
|
|
117
|
+
/export [file.md] Export conversation to markdown
|
|
118
|
+
/resume [sessionId] Resume a saved session (restores full context)
|
|
119
|
+
/compact [status] Compress conversation context; status shows a summary
|
|
143
120
|
|
|
144
|
-
|
|
145
|
-
/
|
|
146
|
-
/
|
|
147
|
-
/
|
|
148
|
-
/
|
|
149
|
-
/approve [notes] - Approve pending plan & execute
|
|
121
|
+
Workspace
|
|
122
|
+
/scope [add|remove <dir>] File-access scope (also details|reset)
|
|
123
|
+
/memory [init|add|show] Project memory (CALLIOPE.md)
|
|
124
|
+
/trust [add|remove|list] Project trust registry
|
|
125
|
+
/restore [<path> [index]] List checkpoints, or restore a file
|
|
150
126
|
|
|
151
|
-
|
|
152
|
-
/
|
|
153
|
-
/
|
|
154
|
-
/companion [name] - Switch companion personality
|
|
155
|
-
/pack [name] - Apply theme pack (skin+palette+companion)
|
|
156
|
-
/theme [name] - Color themes
|
|
157
|
-
/layout [name] - UI layout (classic/split/etc)
|
|
158
|
-
/density [normal|compact] - Display density
|
|
159
|
-
/collapse [tools|all|off] - Tool output visibility
|
|
160
|
-
/intensity [1-5] - Immersion level
|
|
161
|
-
/emoji [on|off] - Toggle emoji
|
|
162
|
-
/banner - Show skin banner art
|
|
127
|
+
Extend
|
|
128
|
+
/mcp [add|remove|tools] MCP servers
|
|
129
|
+
/skills [add|remove] Agent skills
|
|
163
130
|
|
|
164
|
-
|
|
165
|
-
/
|
|
166
|
-
/
|
|
167
|
-
/
|
|
168
|
-
/
|
|
169
|
-
/
|
|
170
|
-
|
|
131
|
+
System
|
|
132
|
+
/config [set <key> <value>] Show or change settings (maxIterations, sessionLogLimit, diffStyle, theme)
|
|
133
|
+
/setup Reconfigure Calliope
|
|
134
|
+
/cost Cost tracking summary
|
|
135
|
+
/loop ["prompt"|stop] Iterative agent loop
|
|
136
|
+
/debug [on|off] Debug state/logging`;
|
|
137
|
+
if (fleetConfigured()) {
|
|
138
|
+
help += `
|
|
171
139
|
|
|
172
|
-
|
|
173
|
-
/
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
/cancel-loop - Stop running loop (/stop, /breakloop)
|
|
177
|
-
|
|
178
|
-
--- System ---
|
|
179
|
-
/status - Show status (/s)
|
|
180
|
-
/config - Show config
|
|
181
|
-
/set <key> <value> - Set config variable
|
|
182
|
-
/cost - Cost tracking summary
|
|
183
|
-
/confirm [on|off] - Toggle risky op confirmation
|
|
184
|
-
/debug [on|off] - Debug state/logging
|
|
185
|
-
/unstick - Emergency reset
|
|
186
|
-
/upgrade - Check for updates
|
|
187
|
-
/keys or /? - Keyboard shortcuts
|
|
188
|
-
/exit - Exit (/quit)
|
|
140
|
+
Fleet
|
|
141
|
+
/fleet [message|disable] Multi-agent fleet coordination`;
|
|
142
|
+
}
|
|
143
|
+
help += `
|
|
189
144
|
|
|
190
|
-
File references: @filename, ./path, /absolute/path
|
|
191
|
-
|
|
145
|
+
File references: @filename, ./path, /absolute/path`;
|
|
146
|
+
ctx.addMessage('system', help);
|
|
192
147
|
break;
|
|
148
|
+
}
|
|
193
149
|
case '/provider':
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
150
|
+
if (parts[1] && parts[1] !== 'list') {
|
|
151
|
+
const requested = parts[1].toLowerCase();
|
|
152
|
+
const available = getAvailableProviders();
|
|
153
|
+
if (!available.includes(requested)) {
|
|
154
|
+
ctx.addMessage('error', `Provider "${requested}" is not configured. Run /provider (no args) for an interactive picker with setup.`);
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
ctx.setProvider(requested);
|
|
158
|
+
// Explicit switches persist as the new defaults; a stale model from
|
|
159
|
+
// the previous provider must not leak into this one (#233).
|
|
160
|
+
config.set('defaultProvider', requested);
|
|
161
|
+
config.unset('defaultModel');
|
|
162
|
+
ctx.addMessage('system', `Provider: ${selectProvider(requested)} (saved as default)`);
|
|
163
|
+
}
|
|
164
|
+
else if (parts[1] === 'list') {
|
|
165
|
+
ctx.addMessage('system', `Provider: ${ctx.actualProvider} | Available: ${getAvailableProviders().join(', ')}`);
|
|
166
|
+
}
|
|
167
|
+
else if (ctx.openProviderPicker) {
|
|
168
|
+
ctx.openProviderPicker();
|
|
200
169
|
}
|
|
201
170
|
else {
|
|
202
171
|
ctx.addMessage('system', `Provider: ${ctx.actualProvider} | Available: ${getAvailableProviders().join(', ')}`);
|
|
203
172
|
}
|
|
204
173
|
break;
|
|
205
174
|
case '/model':
|
|
206
|
-
|
|
207
|
-
if (parts[1]) {
|
|
175
|
+
if (parts[1] && parts[1] !== 'list') {
|
|
208
176
|
const newModel = parts[1];
|
|
209
177
|
const oldModel = ctx.model || ctx.actualModel;
|
|
210
178
|
// Check context compatibility before switching (#26)
|
|
@@ -213,20 +181,22 @@ Modes: Plan | Hybrid | Work | Auto-route: ${ctx.autoRoute ? 'ON' : 'OFF'}${ctx.a
|
|
|
213
181
|
const currentTokens = ctx.estimateContextTokens();
|
|
214
182
|
const newPct = Math.round((currentTokens / newLimit) * 100);
|
|
215
183
|
ctx.setModel(newModel);
|
|
184
|
+
config.set('defaultModel', newModel);
|
|
216
185
|
ctx.setContextTokens(currentTokens);
|
|
217
186
|
let switchWarning = '';
|
|
218
187
|
if (newPct > 80) {
|
|
219
|
-
switchWarning = `\n⚠️ Context at ${newPct}% of new model limit (${Math.round(currentTokens / 1000)}K/${Math.round(newLimit / 1000)}K). Consider /
|
|
188
|
+
switchWarning = `\n⚠️ Context at ${newPct}% of new model limit (${Math.round(currentTokens / 1000)}K/${Math.round(newLimit / 1000)}K). Consider /compact.`;
|
|
220
189
|
}
|
|
221
190
|
else if (newLimit < oldLimit) {
|
|
222
191
|
switchWarning = `\n📉 Context window: ${Math.round(oldLimit / 1000)}K → ${Math.round(newLimit / 1000)}K (${newPct}% used)`;
|
|
223
192
|
}
|
|
224
|
-
ctx.addMessage('system', `Model: ${oldModel} → ${newModel}${switchWarning}`);
|
|
193
|
+
ctx.addMessage('system', `Model: ${oldModel} → ${newModel} (saved as default)${switchWarning}`);
|
|
225
194
|
}
|
|
226
195
|
else {
|
|
196
|
+
// No arg or `list`: open the model picker (absorbs the old /models listing)
|
|
227
197
|
ctx.addMessage('system', `Fetching models for ${ctx.actualProvider}...`);
|
|
228
198
|
try {
|
|
229
|
-
const models = await getAvailableModels(ctx.actualProvider);
|
|
199
|
+
const models = await getAvailableModels(ctx.actualProvider, { throwOnError: true });
|
|
230
200
|
if (models.length > 0) {
|
|
231
201
|
ctx.setAvailableModels(models);
|
|
232
202
|
ctx.setModalMode('model');
|
|
@@ -240,22 +210,6 @@ Modes: Plan | Hybrid | Work | Auto-route: ${ctx.autoRoute ? 'ON' : 'OFF'}${ctx.a
|
|
|
240
210
|
}
|
|
241
211
|
}
|
|
242
212
|
break;
|
|
243
|
-
case '/models':
|
|
244
|
-
ctx.addMessage('system', `Fetching models for ${ctx.actualProvider}...`);
|
|
245
|
-
try {
|
|
246
|
-
const models = await getAvailableModels(ctx.actualProvider);
|
|
247
|
-
if (models.length > 0) {
|
|
248
|
-
ctx.setAvailableModels(models);
|
|
249
|
-
ctx.setModalMode('model');
|
|
250
|
-
}
|
|
251
|
-
else {
|
|
252
|
-
ctx.addMessage('error', `No models found for ${ctx.actualProvider} — API key may be invalid`);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
catch (e) {
|
|
256
|
-
ctx.addMessage('error', `Failed to fetch models for ${ctx.actualProvider}: ${e instanceof Error ? e.message : String(e)}. Check your API key.`);
|
|
257
|
-
}
|
|
258
|
-
break;
|
|
259
213
|
case '/mode':
|
|
260
214
|
if (parts[1] && ['plan', 'hybrid', 'work'].includes(parts[1])) {
|
|
261
215
|
const m = parts[1];
|
|
@@ -267,64 +221,13 @@ Modes: Plan | Hybrid | Work | Auto-route: ${ctx.autoRoute ? 'ON' : 'OFF'}${ctx.a
|
|
|
267
221
|
ctx.addMessage('system', `Mode: ${currentConfig.icon} ${currentConfig.label}\nOptions: plan (\u{1F4CB}), hybrid (\u{1F504}), work (\u{1F527})\nUse Shift+Tab to cycle`);
|
|
268
222
|
}
|
|
269
223
|
break;
|
|
270
|
-
case '/persona':
|
|
271
|
-
if (parts[1] && ['calliope', 'muse', 'minimal'].includes(parts[1])) {
|
|
272
|
-
const p = parts[1];
|
|
273
|
-
ctx.setPersona(p);
|
|
274
|
-
ctx.llmMessages.current = [{ role: 'system', content: buildFullSystemPrompt(p, getActiveProjectDir(ctx)) }];
|
|
275
|
-
ctx.addMessage('system', `Persona: ${p}`);
|
|
276
|
-
}
|
|
277
|
-
else {
|
|
278
|
-
ctx.addMessage('system', `Persona: ${ctx.persona} | Options: calliope, muse, minimal`);
|
|
279
|
-
}
|
|
280
|
-
break;
|
|
281
224
|
case '/clear':
|
|
282
|
-
case '/c':
|
|
283
225
|
ctx.setMessages([]);
|
|
284
|
-
ctx.llmMessages.current = [{ role: 'system', content: buildFullSystemPrompt(ctx
|
|
226
|
+
ctx.llmMessages.current = [{ role: 'system', content: buildFullSystemPrompt(getActiveProjectDir(ctx), ctx.actualProvider) }];
|
|
285
227
|
ctx.ledger?.reset();
|
|
286
228
|
ctx.setStats({ inputTokens: 0, outputTokens: 0, cost: 0, messageCount: 0 });
|
|
287
229
|
resetContextWarnings(); // Reset context warning state
|
|
288
230
|
break;
|
|
289
|
-
case '/copy': {
|
|
290
|
-
// Copy last assistant response to clipboard
|
|
291
|
-
const lastAssistant = [...ctx.messages].reverse().find(m => m.type === 'assistant');
|
|
292
|
-
if (lastAssistant) {
|
|
293
|
-
try {
|
|
294
|
-
const { execSync } = await import('child_process');
|
|
295
|
-
// Try different clipboard commands based on platform
|
|
296
|
-
const content = lastAssistant.content;
|
|
297
|
-
if (process.platform === 'darwin') {
|
|
298
|
-
execSync('pbcopy', { input: content });
|
|
299
|
-
}
|
|
300
|
-
else if (process.platform === 'win32') {
|
|
301
|
-
execSync('clip', { input: content });
|
|
302
|
-
}
|
|
303
|
-
else {
|
|
304
|
-
// Linux - try xclip, xsel, or wl-copy
|
|
305
|
-
try {
|
|
306
|
-
execSync('xclip -selection clipboard', { input: content });
|
|
307
|
-
}
|
|
308
|
-
catch {
|
|
309
|
-
try {
|
|
310
|
-
execSync('xsel --clipboard --input', { input: content });
|
|
311
|
-
}
|
|
312
|
-
catch {
|
|
313
|
-
execSync('wl-copy', { input: content });
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
ctx.addMessage('system', '\u2713 Copied to clipboard');
|
|
318
|
-
}
|
|
319
|
-
catch (e) {
|
|
320
|
-
ctx.addMessage('error', `Clipboard not available: ${e instanceof Error ? e.message : String(e)}`);
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
else {
|
|
324
|
-
ctx.addMessage('system', 'No assistant message to copy');
|
|
325
|
-
}
|
|
326
|
-
break;
|
|
327
|
-
}
|
|
328
231
|
case '/export': {
|
|
329
232
|
// Export conversation to markdown
|
|
330
233
|
const filename = parts[1] || `calliope-export-${Date.now()}.md`;
|
|
@@ -353,20 +256,7 @@ Modes: Plan | Hybrid | Work | Auto-route: ${ctx.autoRoute ? 'ON' : 'OFF'}${ctx.a
|
|
|
353
256
|
}
|
|
354
257
|
const filepath = path.resolve(process.cwd(), filename);
|
|
355
258
|
fsModule.writeFileSync(filepath, markdown);
|
|
356
|
-
ctx.addMessage('system',
|
|
357
|
-
break;
|
|
358
|
-
}
|
|
359
|
-
case '/edit': {
|
|
360
|
-
// Edit last user message
|
|
361
|
-
const lastUserIdx = [...ctx.messages].reverse().findIndex(m => m.type === 'user');
|
|
362
|
-
if (lastUserIdx >= 0) {
|
|
363
|
-
const lastUser = ctx.messages[ctx.messages.length - 1 - lastUserIdx];
|
|
364
|
-
ctx.setInput(lastUser.content);
|
|
365
|
-
ctx.addMessage('system', 'Edit the message above and press Enter to resend');
|
|
366
|
-
}
|
|
367
|
-
else {
|
|
368
|
-
ctx.addMessage('system', 'No user message to edit');
|
|
369
|
-
}
|
|
259
|
+
ctx.addMessage('system', `✓ Exported to ${filename}`);
|
|
370
260
|
break;
|
|
371
261
|
}
|
|
372
262
|
case '/undo': {
|
|
@@ -385,651 +275,251 @@ Modes: Plan | Hybrid | Work | Auto-route: ${ctx.autoRoute ? 'ON' : 'OFF'}${ctx.a
|
|
|
385
275
|
ctx.setMessages(prevState.messages);
|
|
386
276
|
ctx.llmMessages.current = prevState.llmMessages;
|
|
387
277
|
ctx.setContextTokens(ctx.estimateContextTokens());
|
|
388
|
-
ctx.addMessage('system',
|
|
389
|
-
break;
|
|
390
|
-
}
|
|
391
|
-
case '/redo': {
|
|
392
|
-
if (ctx.redoStack.current.length === 0) {
|
|
393
|
-
ctx.addMessage('system', 'Nothing to redo.');
|
|
394
|
-
break;
|
|
395
|
-
}
|
|
396
|
-
// Save current state to undo stack
|
|
397
|
-
ctx.undoStack.current.push({
|
|
398
|
-
messages: [...ctx.messages],
|
|
399
|
-
llmMessages: [...ctx.llmMessages.current],
|
|
400
|
-
timestamp: new Date(),
|
|
401
|
-
});
|
|
402
|
-
// Restore redo state
|
|
403
|
-
const redoState = ctx.redoStack.current.pop();
|
|
404
|
-
ctx.setMessages(redoState.messages);
|
|
405
|
-
ctx.llmMessages.current = redoState.llmMessages;
|
|
406
|
-
ctx.setContextTokens(ctx.estimateContextTokens());
|
|
407
|
-
ctx.addMessage('system', `\u2713 Redone (${ctx.redoStack.current.length} more available)`);
|
|
278
|
+
ctx.addMessage('system', `✓ Undone (${ctx.undoStack.current.length} more available)`);
|
|
408
279
|
break;
|
|
409
280
|
}
|
|
410
|
-
case '/status':
|
|
411
|
-
case '/s': {
|
|
281
|
+
case '/status': {
|
|
412
282
|
const imgInfo = getTerminalImageInfo();
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
283
|
+
let statusMsg = `${ctx.actualProvider}:${ctx.actualModel} | ${ctx.stats.messageCount} msgs | ${ctx.stats.inputTokens + ctx.stats.outputTokens} tokens | terminal: ${getImageModeLabel(imgInfo.mode)}${imgInfo.truecolor ? ' (truecolor)' : ''} ${imgInfo.width}cols`;
|
|
284
|
+
// Add fleet status if active
|
|
285
|
+
const fleetSt = fleetStatus();
|
|
286
|
+
if (fleetSt) {
|
|
287
|
+
statusMsg += `\nFleet: active (${fleetSt.nick}) | irc:${fleetSt.config?.ircAddr} | #${fleetSt.config?.channel}`;
|
|
288
|
+
}
|
|
289
|
+
// Show budget state when any cap is configured.
|
|
290
|
+
const caps = getBudgetCaps();
|
|
291
|
+
if (hasBudgetCaps(caps)) {
|
|
292
|
+
const parts = [];
|
|
293
|
+
if (typeof caps.maxCostPerRun === 'number')
|
|
294
|
+
parts.push(`run<=$${caps.maxCostPerRun}`);
|
|
295
|
+
if (typeof caps.maxTokensPerRun === 'number')
|
|
296
|
+
parts.push(`run<=${caps.maxTokensPerRun}tok`);
|
|
297
|
+
if (typeof caps.maxCostPerProject === 'number') {
|
|
298
|
+
const spent = loadProjectSpend(getActiveProjectDir(ctx)).spentUsd;
|
|
299
|
+
parts.push(`project $${spent.toFixed(4)}/$${caps.maxCostPerProject}`);
|
|
300
|
+
}
|
|
301
|
+
statusMsg += `\nBudget: ${parts.join(' | ')}`;
|
|
302
|
+
}
|
|
303
|
+
ctx.addMessage('system', statusMsg);
|
|
304
|
+
break;
|
|
305
|
+
}
|
|
306
|
+
case '/fleet': {
|
|
307
|
+
const subCmd = parts[1];
|
|
308
|
+
if (subCmd === 'enable') {
|
|
309
|
+
if (fleetActive()) {
|
|
310
|
+
ctx.addMessage('system', 'Fleet mode is already active.');
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
const sessionId = ctx.sessionRef.current?.id || 'default';
|
|
314
|
+
const cwd = getActiveProjectDir(ctx);
|
|
315
|
+
fleetEnable(sessionId, cwd).then((enabled) => {
|
|
316
|
+
if (enabled) {
|
|
317
|
+
const status = fleetStatus();
|
|
318
|
+
let msg = '✓ Fleet mode enabled\n';
|
|
319
|
+
msg += ` Nick: ${status?.nick}\n`;
|
|
320
|
+
msg += ` IRC: ${status?.config?.ircAddr}\n`;
|
|
321
|
+
msg += ` Channel: #${status?.config?.channel}`;
|
|
322
|
+
ctx.addMessage('system', msg);
|
|
323
|
+
ctx.startFleetPolling();
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
ctx.addMessage('system', 'Failed to enable fleet mode — check relay config (~/.config/scuttlebot-relay.env, .scuttlebot.yaml).');
|
|
327
|
+
}
|
|
328
|
+
}).catch((err) => {
|
|
329
|
+
ctx.addMessage('system', `Failed to enable fleet mode: ${err instanceof Error ? err.message : String(err)}`);
|
|
330
|
+
});
|
|
422
331
|
break;
|
|
423
332
|
}
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
else if (sub === 'defs' || sub === 'list') {
|
|
429
|
-
const defs = listAgentDefs(process.cwd());
|
|
430
|
-
if (defs.length === 0) {
|
|
431
|
-
ctx.addMessage('system', 'No agent definitions loaded. Run /agents init to create examples.');
|
|
432
|
-
}
|
|
433
|
-
else {
|
|
434
|
-
const lines = defs.map(d => {
|
|
435
|
-
const src = d._source || 'unknown';
|
|
436
|
-
return ` ${d.name} — ${d.engine}/${d.provider || 'auto'}/${d.model || 'default'} [${src}]${d.description ? `\n ${d.description}` : ''}`;
|
|
437
|
-
});
|
|
438
|
-
ctx.addMessage('system', `Agent Definitions (${defs.length}):\n${lines.join('\n')}`);
|
|
333
|
+
if (subCmd === 'disable') {
|
|
334
|
+
if (!fleetConfigured() && !fleetActive()) {
|
|
335
|
+
ctx.addMessage('system', 'Fleet mode is not enabled.');
|
|
336
|
+
break;
|
|
439
337
|
}
|
|
338
|
+
fleetDisable().then(() => {
|
|
339
|
+
ctx.addMessage('system', 'Fleet mode disabled.');
|
|
340
|
+
}).catch((err) => {
|
|
341
|
+
ctx.addMessage('system', `Error disabling fleet mode: ${err instanceof Error ? err.message : String(err)}`);
|
|
342
|
+
});
|
|
343
|
+
break;
|
|
440
344
|
}
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
ctx.addMessage('system', 'No team definitions loaded. Run /agents init to create examples.');
|
|
445
|
-
}
|
|
446
|
-
else {
|
|
447
|
-
const lines = teams.map(t => {
|
|
448
|
-
const src = t._source || 'unknown';
|
|
449
|
-
return ` ${t.name} — ${t.mode}, ${t.members.length} members [${src}]${t.description ? `\n ${t.description}` : ''}`;
|
|
450
|
-
});
|
|
451
|
-
ctx.addMessage('system', `Team Definitions (${teams.length}):\n${lines.join('\n')}`);
|
|
452
|
-
}
|
|
345
|
+
if (!fleetActive()) {
|
|
346
|
+
ctx.addMessage('system', 'Fleet mode is off.\n\nRun: /fleet enable\n\nRelay config is read from ~/.config/scuttlebot-relay.env and .scuttlebot.yaml.\nDocs: docs/fleet.md');
|
|
347
|
+
break;
|
|
453
348
|
}
|
|
454
|
-
|
|
455
|
-
const
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
else {
|
|
460
|
-
ctx.addMessage('system', '.calliope/agents/ already exists.');
|
|
461
|
-
}
|
|
349
|
+
if (subCmd) {
|
|
350
|
+
const message = parts.slice(1).join(' ');
|
|
351
|
+
fleetPostMessage(message);
|
|
352
|
+
ctx.addMessage('system', 'Message posted to the fleet channel.');
|
|
353
|
+
break;
|
|
462
354
|
}
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
355
|
+
const status = fleetStatus();
|
|
356
|
+
let statusText = 'Fleet Status\n────────────────────\n';
|
|
357
|
+
statusText += `Nick: ${status?.nick}\n`;
|
|
358
|
+
statusText += `IRC: ${status?.config?.ircAddr}\n`;
|
|
359
|
+
statusText += `Channel: #${status?.config?.channel}\n`;
|
|
360
|
+
statusText += `Connected: ${status?.connected ? 'yes' : 'no'}`;
|
|
361
|
+
statusText += '\n\nCommands:\n /fleet <message> Post to the fleet channel\n /fleet disable Disable fleet mode';
|
|
362
|
+
ctx.addMessage('system', statusText);
|
|
363
|
+
break;
|
|
364
|
+
}
|
|
365
|
+
case '/config': {
|
|
366
|
+
if (parts[1] === 'set') {
|
|
367
|
+
// /config set <key> <value> (absorbs the old /set)
|
|
368
|
+
const key = parts[2];
|
|
369
|
+
const value = parts.slice(3).join(' ');
|
|
370
|
+
if (!key || !value) {
|
|
371
|
+
ctx.addMessage('system', `Usage: /config set <key> <value>
|
|
372
|
+
Available keys:
|
|
373
|
+
maxIterations <number> - Max agent iterations (current: ${config.get('maxIterations')})
|
|
374
|
+
sessionLogLimit <number> - Cap retained session log items (current: ${formatSessionLogLimit(config.get('sessionLogLimit'))}, 0 = unlimited)
|
|
375
|
+
collapseTools <bool> - Auto-collapse tool output
|
|
376
|
+
toolDisplayLimit <number> - Tools shown expanded (0 = all)
|
|
377
|
+
diffStyle <inline|unified|side-by-side> - Diff display style
|
|
378
|
+
sandboxMode <auto|native|docker|off> - Code execution sandbox
|
|
379
|
+
routing.enabled <bool> - Smart model routing
|
|
380
|
+
routing.costSensitivity <0-1> - Cost vs quality (0 = best, 1 = cheapest)
|
|
381
|
+
theme <dark|light|no-color> - Color theme
|
|
382
|
+
budget.maxCostPerRun <usd|off> - Halt a run at this spend (0/off = no cap)
|
|
383
|
+
budget.maxTokensPerRun <n|off> - Halt a run at this token count
|
|
384
|
+
budget.maxCostPerProject <usd|off> - Halt when project spend reaches this
|
|
385
|
+
audit.enabled <bool> - Audit run log (on by default)
|
|
386
|
+
policy.command <path|off> - Pre-tool policy hook script`);
|
|
387
|
+
break;
|
|
467
388
|
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
`Model: ${agentDef.model || 'default'}`,
|
|
478
|
-
agentDef.role ? `Role: ${agentDef.role}` : '',
|
|
479
|
-
agentDef.weight !== undefined ? `Weight: ${agentDef.weight}` : '',
|
|
480
|
-
agentDef.instructions ? `Instructions:\n ${agentDef.instructions.split('\n').join('\n ')}` : '',
|
|
481
|
-
`Source: ${agentDef._source || 'unknown'}`,
|
|
482
|
-
agentDef._filePath ? `File: ${agentDef._filePath}` : '',
|
|
483
|
-
].filter(Boolean);
|
|
484
|
-
ctx.addMessage('system', lines.join('\n'));
|
|
389
|
+
try {
|
|
390
|
+
if (key === 'maxIterations') {
|
|
391
|
+
const num = parseInt(value, 10);
|
|
392
|
+
if (isNaN(num) || num < 0 || num > 1000000) {
|
|
393
|
+
ctx.addMessage('error', 'maxIterations must be 0-1000000 (0 = unlimited)');
|
|
394
|
+
break;
|
|
395
|
+
}
|
|
396
|
+
config.set('maxIterations', num);
|
|
397
|
+
ctx.addMessage('system', `✓ maxIterations set to ${formatIterationLimit(resolveIterationLimit(num))}`);
|
|
485
398
|
}
|
|
486
|
-
else if (
|
|
487
|
-
const
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
ctx.addMessage('system',
|
|
399
|
+
else if (key === 'sessionLogLimit') {
|
|
400
|
+
const num = parseInt(value, 10);
|
|
401
|
+
if (isNaN(num) || num < 0 || num > 100000) {
|
|
402
|
+
ctx.addMessage('error', 'sessionLogLimit must be 0-100000 (0 = unlimited)');
|
|
403
|
+
break;
|
|
404
|
+
}
|
|
405
|
+
config.set('sessionLogLimit', num);
|
|
406
|
+
ctx.ledger?.setRetentionLimit(num);
|
|
407
|
+
ctx.addMessage('system', `✓ sessionLogLimit set to ${num === 0 ? 'unlimited (set > 0 to cap)' : num}`);
|
|
495
408
|
}
|
|
496
|
-
else {
|
|
497
|
-
|
|
409
|
+
else if (key === 'collapseTools') {
|
|
410
|
+
const bool = value === 'true';
|
|
411
|
+
config.set('collapseTools', bool);
|
|
412
|
+
ctx.addMessage('system', `✓ collapseTools set to ${bool}`);
|
|
498
413
|
}
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
// /install-agents [name] — install missing agent CLIs and SDK backends
|
|
508
|
-
const target = parts[1];
|
|
509
|
-
if (target) {
|
|
510
|
-
ctx.addMessage('system', `Installing ${target}...`);
|
|
511
|
-
const result = installItem(target);
|
|
512
|
-
ctx.addMessage('system', result.success
|
|
513
|
-
? `✓ ${target} installed successfully.`
|
|
514
|
-
: `✗ ${result.output}`);
|
|
515
|
-
}
|
|
516
|
-
else if (parts.includes('--all')) {
|
|
517
|
-
ctx.addMessage('system', 'Installing all missing agents and SDK backends...');
|
|
518
|
-
const result = await installAllMissing();
|
|
519
|
-
const lines = [];
|
|
520
|
-
if (result.installed.length > 0) {
|
|
521
|
-
lines.push(`✓ Installed: ${result.installed.join(', ')}`);
|
|
522
|
-
}
|
|
523
|
-
if (result.failed.length > 0) {
|
|
524
|
-
lines.push(`✗ Failed:\n ${result.failed.join('\n ')}`);
|
|
525
|
-
}
|
|
526
|
-
if (result.installed.length === 0 && result.failed.length === 0) {
|
|
527
|
-
lines.push('Everything is already installed!');
|
|
528
|
-
}
|
|
529
|
-
ctx.addMessage('system', lines.join('\n'));
|
|
530
|
-
}
|
|
531
|
-
else {
|
|
532
|
-
const report = await getInstallReport();
|
|
533
|
-
ctx.addMessage('system', `${report}\n\nUsage:\n /install-agents <name> Install a specific agent or SDK\n /install-agents --all Install everything missing`);
|
|
534
|
-
}
|
|
535
|
-
break;
|
|
536
|
-
}
|
|
537
|
-
case '/build-agent': {
|
|
538
|
-
// Interactive agent definition builder
|
|
539
|
-
// Parse inline args: /build-agent [name] [--engine X] [--provider X] [--model X]
|
|
540
|
-
const agentName = parts[1] && !parts[1].startsWith('--') ? parts[1] : undefined;
|
|
541
|
-
const engineArg = parts.includes('--engine') ? parts[parts.indexOf('--engine') + 1] : undefined;
|
|
542
|
-
const providerArg = parts.includes('--provider') ? parts[parts.indexOf('--provider') + 1] : undefined;
|
|
543
|
-
const modelArg = parts.includes('--model') ? parts[parts.indexOf('--model') + 1] : undefined;
|
|
544
|
-
const roleArg = parts.includes('--role') ? parts[parts.indexOf('--role') + 1] : undefined;
|
|
545
|
-
if (!agentName) {
|
|
546
|
-
// Interactive wizard — show guided step-by-step builder
|
|
547
|
-
const executors = await getAvailableExecutors();
|
|
548
|
-
const providers = config.getConfiguredProviders();
|
|
549
|
-
// Engine descriptions
|
|
550
|
-
const engineDescriptions = {
|
|
551
|
-
'cli': 'Built-in CLI agent loop (works with all providers, most flexible)',
|
|
552
|
-
'claude-sdk': 'Anthropic Claude SDK (native tool use, streaming, best for Anthropic models)',
|
|
553
|
-
'openai-sdk': 'OpenAI SDK (function calling, JSON mode, best for OpenAI models)',
|
|
554
|
-
'google-adk': 'Google ADK (grounding, search, best for Gemini models)',
|
|
555
|
-
};
|
|
556
|
-
// Provider-to-engine recommendations
|
|
557
|
-
const providerEngineMap = {
|
|
558
|
-
anthropic: 'claude-sdk',
|
|
559
|
-
openai: 'openai-sdk',
|
|
560
|
-
google: 'google-adk',
|
|
561
|
-
together: 'cli',
|
|
562
|
-
openrouter: 'cli',
|
|
563
|
-
groq: 'cli',
|
|
564
|
-
fireworks: 'cli',
|
|
565
|
-
mistral: 'cli',
|
|
566
|
-
ollama: 'cli',
|
|
567
|
-
bedrock: 'cli',
|
|
568
|
-
};
|
|
569
|
-
// Common roles
|
|
570
|
-
const roles = [
|
|
571
|
-
{ name: 'coder', desc: 'Writes and refactors code' },
|
|
572
|
-
{ name: 'reviewer', desc: 'Reviews code for bugs, style, and security' },
|
|
573
|
-
{ name: 'architect', desc: 'Designs system architecture and APIs' },
|
|
574
|
-
{ name: 'researcher', desc: 'Gathers information and summarizes findings' },
|
|
575
|
-
{ name: 'qa-engineer', desc: 'Writes tests and validates correctness' },
|
|
576
|
-
{ name: 'devops', desc: 'Infrastructure, CI/CD, and deployment' },
|
|
577
|
-
{ name: 'writer', desc: 'Documentation, READMEs, and technical writing' },
|
|
578
|
-
{ name: 'analyst', desc: 'Data analysis and insights' },
|
|
579
|
-
];
|
|
580
|
-
const lines = [];
|
|
581
|
-
lines.push('=== Build Agent Wizard ===');
|
|
582
|
-
lines.push('');
|
|
583
|
-
// Step 1: Engine
|
|
584
|
-
lines.push('STEP 1: Choose an engine');
|
|
585
|
-
lines.push('The engine determines how the agent executes (SDK backend or CLI loop).');
|
|
586
|
-
lines.push('');
|
|
587
|
-
for (const eng of ['cli', 'claude-sdk', 'openai-sdk', 'google-adk']) {
|
|
588
|
-
const available = executors.includes(eng);
|
|
589
|
-
const marker = available ? '[installed]' : '[not installed]';
|
|
590
|
-
lines.push(` ${eng} ${marker}`);
|
|
591
|
-
lines.push(` ${engineDescriptions[eng] || ''}`);
|
|
592
|
-
}
|
|
593
|
-
lines.push('');
|
|
594
|
-
if (executors.length === 1) {
|
|
595
|
-
lines.push(' Recommendation: Only "cli" is available. Install SDK packages for more options.');
|
|
596
|
-
lines.push(' Run /install-agents to see what can be installed.');
|
|
597
|
-
}
|
|
598
|
-
else {
|
|
599
|
-
lines.push(` Available on this system: ${executors.join(', ')}`);
|
|
600
|
-
}
|
|
601
|
-
lines.push('');
|
|
602
|
-
// Step 2: Provider
|
|
603
|
-
lines.push('STEP 2: Choose a provider');
|
|
604
|
-
lines.push('The provider determines which API your agent calls.');
|
|
605
|
-
lines.push('');
|
|
606
|
-
if (providers.length === 0) {
|
|
607
|
-
lines.push(' No providers configured! Run calliope --setup to add API keys.');
|
|
608
|
-
}
|
|
609
|
-
else {
|
|
610
|
-
for (const p of providers) {
|
|
611
|
-
const recEngine = providerEngineMap[p] || 'cli';
|
|
612
|
-
const model = DEFAULT_MODELS[p] || 'auto';
|
|
613
|
-
lines.push(` ${p} (best with --engine ${recEngine}, default model: ${model})`);
|
|
414
|
+
else if (key === 'toolDisplayLimit') {
|
|
415
|
+
const num = parseInt(value, 10);
|
|
416
|
+
if (isNaN(num) || num < 0 || num > 100) {
|
|
417
|
+
ctx.addMessage('error', 'toolDisplayLimit must be 0-100 (0 = all expanded)');
|
|
418
|
+
break;
|
|
419
|
+
}
|
|
420
|
+
config.set('toolDisplayLimit', num);
|
|
421
|
+
ctx.addMessage('system', `✓ toolDisplayLimit set to ${num}`);
|
|
614
422
|
}
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
for (const p of providers) {
|
|
623
|
-
lines.push(` ${p}: ${DEFAULT_MODELS[p] || 'auto'}`);
|
|
423
|
+
else if (key === 'diffStyle') {
|
|
424
|
+
if (value !== 'inline' && value !== 'unified' && value !== 'side-by-side') {
|
|
425
|
+
ctx.addMessage('error', 'diffStyle must be inline, unified, or side-by-side');
|
|
426
|
+
break;
|
|
427
|
+
}
|
|
428
|
+
config.set('diffStyle', value);
|
|
429
|
+
ctx.addMessage('system', `✓ diffStyle set to ${value}`);
|
|
624
430
|
}
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
lines.push('');
|
|
633
|
-
for (const r of roles) {
|
|
634
|
-
lines.push(` ${r.name.padEnd(14)} ${r.desc}`);
|
|
635
|
-
}
|
|
636
|
-
lines.push('');
|
|
637
|
-
lines.push(' You can also use any custom role name.');
|
|
638
|
-
lines.push('');
|
|
639
|
-
// Step 5: Instructions
|
|
640
|
-
lines.push('STEP 5: Write instructions (after creation)');
|
|
641
|
-
lines.push('After creating the agent, edit its YAML file to add detailed instructions.');
|
|
642
|
-
lines.push('');
|
|
643
|
-
lines.push(' Template:');
|
|
644
|
-
lines.push(' instructions: |');
|
|
645
|
-
lines.push(' You are a specialized {role} agent.');
|
|
646
|
-
lines.push(' Your focus areas: ...');
|
|
647
|
-
lines.push(' When reviewing code: ...');
|
|
648
|
-
lines.push(' Output format: ...');
|
|
649
|
-
lines.push('');
|
|
650
|
-
// Final command
|
|
651
|
-
lines.push('--- Ready to build? ---');
|
|
652
|
-
lines.push('');
|
|
653
|
-
if (providers.includes('anthropic') && executors.includes('claude-sdk')) {
|
|
654
|
-
lines.push(' /build-agent my-agent --engine claude-sdk --provider anthropic --role coder');
|
|
655
|
-
}
|
|
656
|
-
else if (providers.includes('openai') && executors.includes('openai-sdk')) {
|
|
657
|
-
lines.push(' /build-agent my-agent --engine openai-sdk --provider openai --role coder');
|
|
658
|
-
}
|
|
659
|
-
else if (providers.length > 0) {
|
|
660
|
-
const firstProvider = providers[0];
|
|
661
|
-
lines.push(` /build-agent my-agent --engine cli --provider ${firstProvider} --role coder`);
|
|
662
|
-
}
|
|
663
|
-
else {
|
|
664
|
-
lines.push(' /build-agent my-agent --engine cli --provider anthropic --role coder');
|
|
665
|
-
}
|
|
666
|
-
lines.push('');
|
|
667
|
-
lines.push('Add --model <model> to override the default model for the chosen provider.');
|
|
668
|
-
ctx.addMessage('system', lines.join('\n'));
|
|
669
|
-
}
|
|
670
|
-
else if (!engineArg && !providerArg && !modelArg && !roleArg) {
|
|
671
|
-
// Name provided but no flags — show a concise personalized wizard
|
|
672
|
-
const executors = await getAvailableExecutors();
|
|
673
|
-
const providers = config.getConfiguredProviders();
|
|
674
|
-
const providerEngineMap = {
|
|
675
|
-
anthropic: 'claude-sdk', openai: 'openai-sdk', google: 'google-adk',
|
|
676
|
-
together: 'cli', openrouter: 'cli', groq: 'cli', fireworks: 'cli',
|
|
677
|
-
mistral: 'cli', ollama: 'cli', bedrock: 'cli',
|
|
678
|
-
};
|
|
679
|
-
const lines = [];
|
|
680
|
-
lines.push(`=== Building agent: ${agentName} ===`);
|
|
681
|
-
lines.push('');
|
|
682
|
-
lines.push('Choose your configuration:');
|
|
683
|
-
lines.push('');
|
|
684
|
-
// Show ready-to-run commands for each configured provider
|
|
685
|
-
let optionNum = 0;
|
|
686
|
-
for (const p of providers) {
|
|
687
|
-
const recEngine = providerEngineMap[p] || 'cli';
|
|
688
|
-
const engineAvailable = executors.includes(recEngine);
|
|
689
|
-
const engine = engineAvailable ? recEngine : 'cli';
|
|
690
|
-
const model = DEFAULT_MODELS[p] || 'auto';
|
|
691
|
-
optionNum++;
|
|
692
|
-
lines.push(` Option ${optionNum}: ${p}`);
|
|
693
|
-
lines.push(` /build-agent ${agentName} --engine ${engine} --provider ${p} --model ${model} --role coder`);
|
|
694
|
-
lines.push('');
|
|
695
|
-
}
|
|
696
|
-
if (optionNum === 0) {
|
|
697
|
-
lines.push(' No providers configured. Run calliope --setup to add API keys.');
|
|
698
|
-
lines.push(` Or build with defaults: /build-agent ${agentName} --engine cli --provider anthropic --role coder`);
|
|
699
|
-
}
|
|
700
|
-
lines.push('Common roles: coder, reviewer, architect, researcher, qa-engineer, devops, writer');
|
|
701
|
-
lines.push('');
|
|
702
|
-
lines.push('Pick an option above and change --role to match your needs.');
|
|
703
|
-
lines.push(`After creation, customize instructions in .calliope/agents/${agentName}.yaml`);
|
|
704
|
-
ctx.addMessage('system', lines.join('\n'));
|
|
705
|
-
}
|
|
706
|
-
else {
|
|
707
|
-
// Build and save agent definition (flags provided)
|
|
708
|
-
const newDef = {
|
|
709
|
-
name: agentName,
|
|
710
|
-
engine: engineArg || 'cli',
|
|
711
|
-
provider: providerArg,
|
|
712
|
-
model: modelArg,
|
|
713
|
-
role: roleArg,
|
|
714
|
-
weight: 1.0,
|
|
715
|
-
instructions: `You are a specialized agent (${roleArg || agentName}). Complete assigned tasks thoroughly.`,
|
|
716
|
-
limits: { timeout: 600000 },
|
|
717
|
-
};
|
|
718
|
-
const filePath = saveAgentDef(process.cwd(), newDef);
|
|
719
|
-
ctx.addMessage('system', `Agent '${agentName}' created: ${filePath}\n\nEdit the file to customize instructions and settings.\nUse with: spawn_agent agentDef="${agentName}"`);
|
|
720
|
-
}
|
|
721
|
-
break;
|
|
722
|
-
}
|
|
723
|
-
case '/build-team': {
|
|
724
|
-
// Interactive team builder
|
|
725
|
-
const teamNameArg = parts[1] && !parts[1].startsWith('--') ? parts[1] : undefined;
|
|
726
|
-
const modeArg = parts.includes('--mode') ? parts[parts.indexOf('--mode') + 1] : undefined;
|
|
727
|
-
if (!teamNameArg) {
|
|
728
|
-
// Interactive wizard — guided team builder
|
|
729
|
-
const teams = listTeamDefs(process.cwd());
|
|
730
|
-
const agents = listAgentDefs(process.cwd());
|
|
731
|
-
const lines = [];
|
|
732
|
-
lines.push('=== Build Team Wizard ===');
|
|
733
|
-
lines.push('');
|
|
734
|
-
// Step 1: Available agents
|
|
735
|
-
lines.push('STEP 1: Review your available agents');
|
|
736
|
-
lines.push('Teams are composed of agent definitions. Here is what you have:');
|
|
737
|
-
lines.push('');
|
|
738
|
-
if (agents.length === 0) {
|
|
739
|
-
lines.push(' No agents defined yet!');
|
|
740
|
-
lines.push(' Create agents first: /build-agent (run without args for the wizard)');
|
|
741
|
-
lines.push('');
|
|
742
|
-
}
|
|
743
|
-
else {
|
|
744
|
-
for (const a of agents) {
|
|
745
|
-
const src = a._source === 'builtin' ? ' (built-in)' : '';
|
|
746
|
-
lines.push(` ${a.name.padEnd(20)} engine: ${a.engine.padEnd(12)} provider: ${(a.provider || 'auto').padEnd(12)} role: ${a.role || 'any'}${src}`);
|
|
431
|
+
else if (key === 'sandboxMode') {
|
|
432
|
+
if (value !== 'auto' && value !== 'native' && value !== 'docker' && value !== 'off') {
|
|
433
|
+
ctx.addMessage('error', 'sandboxMode must be auto, native, docker, or off');
|
|
434
|
+
break;
|
|
435
|
+
}
|
|
436
|
+
config.set('sandboxMode', value);
|
|
437
|
+
ctx.addMessage('system', `✓ sandboxMode set to ${value}`);
|
|
747
438
|
}
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
439
|
+
else if (key === 'routing.enabled') {
|
|
440
|
+
const bool = value === 'true';
|
|
441
|
+
const routing = config.get('routing') ?? { enabled: false, costSensitivity: 0.3 };
|
|
442
|
+
config.set('routing', { ...routing, enabled: bool });
|
|
443
|
+
ctx.addMessage('system', `✓ routing.enabled set to ${bool}`);
|
|
752
444
|
}
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
lines.push(' Best for: code review, creative alternatives, getting multiple perspectives.');
|
|
763
|
-
lines.push('');
|
|
764
|
-
lines.push(' collaborative');
|
|
765
|
-
lines.push(' Agents work in a pipeline, each building on the previous output.');
|
|
766
|
-
lines.push(' First agent plans, next implements, last reviews.');
|
|
767
|
-
lines.push(' Best for: refactoring, multi-step tasks, plan-implement-review workflows.');
|
|
768
|
-
lines.push('');
|
|
769
|
-
lines.push(' consensus');
|
|
770
|
-
lines.push(' All agents must agree before a result is accepted.');
|
|
771
|
-
lines.push(' Disagreements trigger additional rounds of discussion.');
|
|
772
|
-
lines.push(' Best for: security audits, critical decisions, high-stakes analysis.');
|
|
773
|
-
lines.push('');
|
|
774
|
-
lines.push(' overseer');
|
|
775
|
-
lines.push(' A lead agent decomposes the task and delegates to workers.');
|
|
776
|
-
lines.push(' The overseer reviews and merges all results.');
|
|
777
|
-
lines.push(' Best for: complex projects, research, large feature implementation.');
|
|
778
|
-
lines.push('');
|
|
779
|
-
// Step 3: Strategy recommendations
|
|
780
|
-
lines.push('STEP 3: Pick a strategy based on your task');
|
|
781
|
-
lines.push('');
|
|
782
|
-
lines.push(' Task Type Mode Recommended Agents');
|
|
783
|
-
lines.push(' ─────────────────────────────────────────────────────────');
|
|
784
|
-
lines.push(' Code review competitive code-reviewer, qa-engineer');
|
|
785
|
-
lines.push(' Refactoring collaborative architect, coder, reviewer');
|
|
786
|
-
lines.push(' Security audit consensus security-analyst, code-reviewer, qa-engineer');
|
|
787
|
-
lines.push(' Research overseer researcher, analyst, writer');
|
|
788
|
-
lines.push(' Feature development overseer architect, coder, qa-engineer, reviewer');
|
|
789
|
-
lines.push(' Documentation collaborative researcher, writer');
|
|
790
|
-
lines.push('');
|
|
791
|
-
// Existing teams
|
|
792
|
-
if (teams.length > 0) {
|
|
793
|
-
lines.push('Existing teams:');
|
|
794
|
-
for (const t of teams) {
|
|
795
|
-
lines.push(` ${t.name.padEnd(20)} mode: ${t.mode.padEnd(14)} members: ${t.members.length}`);
|
|
445
|
+
else if (key === 'routing.costSensitivity') {
|
|
446
|
+
const num = Number(value);
|
|
447
|
+
if (isNaN(num) || num < 0 || num > 1) {
|
|
448
|
+
ctx.addMessage('error', 'routing.costSensitivity must be between 0 and 1');
|
|
449
|
+
break;
|
|
450
|
+
}
|
|
451
|
+
const routing = config.get('routing') ?? { enabled: false, costSensitivity: 0.3 };
|
|
452
|
+
config.set('routing', { ...routing, costSensitivity: num });
|
|
453
|
+
ctx.addMessage('system', `✓ routing.costSensitivity set to ${num}`);
|
|
796
454
|
}
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
lines.push(` This will auto-compose a team from your available agents (${sampleAgents}${agents.length > 3 ? ', ...' : ''}).`);
|
|
807
|
-
lines.push(' After creation, edit the YAML to fine-tune members and settings.');
|
|
808
|
-
}
|
|
809
|
-
else {
|
|
810
|
-
lines.push(' First, create at least 2 agents:');
|
|
811
|
-
lines.push(' /build-agent planner --engine cli --provider anthropic --role architect');
|
|
812
|
-
lines.push(' /build-agent coder --engine cli --provider anthropic --role coder');
|
|
813
|
-
lines.push('');
|
|
814
|
-
lines.push(' Then build a team:');
|
|
815
|
-
lines.push(' /build-team my-team --mode collaborative');
|
|
816
|
-
}
|
|
817
|
-
lines.push('');
|
|
818
|
-
lines.push('After creation, use the team with:');
|
|
819
|
-
lines.push(' /swarm --team <name> <prompt>');
|
|
820
|
-
lines.push(' spawn_agent team="<name>" prompt="..."');
|
|
821
|
-
ctx.addMessage('system', lines.join('\n'));
|
|
822
|
-
}
|
|
823
|
-
else {
|
|
824
|
-
// Create a team with available agents
|
|
825
|
-
const availableAgents = listAgentDefs(process.cwd());
|
|
826
|
-
const mode = (modeArg || 'collaborative');
|
|
827
|
-
// Auto-compose a reasonable team from available agent definitions
|
|
828
|
-
const memberAgents = availableAgents
|
|
829
|
-
.filter(a => a._source !== 'builtin' || ['code-reviewer', 'architect', 'qa-engineer', 'researcher'].includes(a.name))
|
|
830
|
-
.slice(0, 4);
|
|
831
|
-
if (memberAgents.length < 2) {
|
|
832
|
-
ctx.addMessage('system', `Need at least 2 agent definitions to build a team.\nCreate agents first with /build-agent <name>, then compose them with /build-team.`);
|
|
833
|
-
}
|
|
834
|
-
else {
|
|
835
|
-
const { saveTeamDef } = await import('../agents/index.js');
|
|
836
|
-
const teamDef = {
|
|
837
|
-
name: teamNameArg,
|
|
838
|
-
description: `${teamNameArg} team`,
|
|
839
|
-
mode,
|
|
840
|
-
members: memberAgents.map(a => ({ agent: a.name, role: a.role, weight: a.weight })),
|
|
841
|
-
swarm: { strategy: 'parallel', aggregation: 'structured', maxWorkers: 3 },
|
|
842
|
-
council: { maxRounds: 2, consensusThreshold: 0.67 },
|
|
843
|
-
};
|
|
844
|
-
const filePath = saveTeamDef(process.cwd(), teamDef);
|
|
845
|
-
ctx.addMessage('system', `Team '${teamNameArg}' created: ${filePath}
|
|
846
|
-
|
|
847
|
-
Members:
|
|
848
|
-
${memberAgents.map(a => ` ${a.name} [${a.role || 'any'}]`).join('\n')}
|
|
849
|
-
|
|
850
|
-
Mode: ${mode}
|
|
851
|
-
|
|
852
|
-
Use with:
|
|
853
|
-
/swarm --team ${teamNameArg} <prompt>
|
|
854
|
-
/swarm coord --team ${teamNameArg} <prompt>
|
|
855
|
-
spawn_agent team="${teamNameArg}" prompt="..."
|
|
856
|
-
|
|
857
|
-
Edit the YAML to customize members, strategy, and coordination settings.`);
|
|
858
|
-
}
|
|
859
|
-
}
|
|
860
|
-
break;
|
|
861
|
-
}
|
|
862
|
-
case '/set': {
|
|
863
|
-
// /set <key> <value>
|
|
864
|
-
const key = parts[1];
|
|
865
|
-
const value = parts.slice(2).join(' ');
|
|
866
|
-
if (!key || !value) {
|
|
867
|
-
ctx.addMessage('system', `Usage: /set <key> <value>
|
|
868
|
-
Available keys:
|
|
869
|
-
maxIterations <number> - Max agent iterations (current: ${config.get('maxIterations')})
|
|
870
|
-
sessionLogLimit <number> - Cap retained session log items (current: ${formatSessionLogLimit(config.get('sessionLogLimit'))}, 0 = unlimited)
|
|
871
|
-
persona <name> - calliope, muse, minimal
|
|
872
|
-
fancyOutput <bool> - true/false`);
|
|
873
|
-
break;
|
|
874
|
-
}
|
|
875
|
-
try {
|
|
876
|
-
if (key === 'maxIterations') {
|
|
877
|
-
const num = parseInt(value, 10);
|
|
878
|
-
if (isNaN(num) || num < 0 || num > 1000000) {
|
|
879
|
-
ctx.addMessage('error', 'maxIterations must be 0-1000000 (0 = unlimited)');
|
|
880
|
-
break;
|
|
455
|
+
else if (key === 'theme') {
|
|
456
|
+
const themes = await import('../themes.js');
|
|
457
|
+
if (themes.setCurrentTheme(value)) {
|
|
458
|
+
themes.clearThemeCache();
|
|
459
|
+
ctx.addMessage('system', `✓ theme set to ${value}`);
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
ctx.addMessage('error', `Theme not found: ${value}. Options: dark, light, no-color`);
|
|
463
|
+
}
|
|
881
464
|
}
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
}
|
|
885
|
-
else if (key === 'sessionLogLimit') {
|
|
886
|
-
const num = parseInt(value, 10);
|
|
887
|
-
if (isNaN(num) || num < 0 || num > 100000) {
|
|
888
|
-
ctx.addMessage('error', 'sessionLogLimit must be 0-100000 (0 = unlimited)');
|
|
889
|
-
break;
|
|
465
|
+
else if (key === 'budget.maxCostPerRun') {
|
|
466
|
+
ctx.addMessage('system', applyBudgetKey('maxCostPerRun', value));
|
|
890
467
|
}
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
468
|
+
else if (key === 'budget.maxTokensPerRun') {
|
|
469
|
+
ctx.addMessage('system', applyBudgetKey('maxTokensPerRun', value));
|
|
470
|
+
}
|
|
471
|
+
else if (key === 'budget.maxCostPerProject') {
|
|
472
|
+
ctx.addMessage('system', applyBudgetKey('maxCostPerProject', value));
|
|
473
|
+
}
|
|
474
|
+
else if (key === 'audit.enabled') {
|
|
475
|
+
const bool = value === 'true';
|
|
476
|
+
config.set('audit', { ...(config.get('audit') ?? {}), enabled: bool });
|
|
477
|
+
ctx.addMessage('system', `✓ audit.enabled set to ${bool}`);
|
|
478
|
+
}
|
|
479
|
+
else if (key === 'policy.command') {
|
|
480
|
+
const policy = { ...(config.get('policy') ?? {}) };
|
|
481
|
+
if (value === 'off' || value === 'none') {
|
|
482
|
+
delete policy.command;
|
|
483
|
+
config.set('policy', policy);
|
|
484
|
+
ctx.addMessage('system', '✓ policy.command cleared');
|
|
485
|
+
}
|
|
486
|
+
else {
|
|
487
|
+
config.set('policy', { ...policy, command: value });
|
|
488
|
+
ctx.addMessage('system', '✓ policy.command set');
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
else {
|
|
492
|
+
ctx.addMessage('error', `Unknown config key: ${key}`);
|
|
899
493
|
}
|
|
900
|
-
config.set('persona', value);
|
|
901
|
-
ctx.setPersona(value);
|
|
902
|
-
ctx.addMessage('system', `\u2713 persona set to ${value}`);
|
|
903
|
-
}
|
|
904
|
-
else if (key === 'fancyOutput') {
|
|
905
|
-
const bool = value === 'true';
|
|
906
|
-
config.set('fancyOutput', bool);
|
|
907
|
-
ctx.addMessage('system', `\u2713 fancyOutput set to ${bool}`);
|
|
908
494
|
}
|
|
909
|
-
|
|
910
|
-
ctx.addMessage('error', `
|
|
495
|
+
catch (err) {
|
|
496
|
+
ctx.addMessage('error', `Failed to set ${key}: ${err instanceof Error ? err.message : String(err)}`);
|
|
911
497
|
}
|
|
912
498
|
}
|
|
913
|
-
|
|
914
|
-
ctx.addMessage('
|
|
499
|
+
else {
|
|
500
|
+
ctx.addMessage('system', `Config: ${config.getConfigPath()}\nProviders: ${config.getConfiguredProviders().join(', ') || 'none'}\nmaxIterations: ${config.get('maxIterations')}\nsessionLogLimit: ${formatSessionLogLimit(config.get('sessionLogLimit'))} (set > 0 to cap)\n\nChange a setting with /config set <key> <value>`);
|
|
915
501
|
}
|
|
916
502
|
break;
|
|
917
503
|
}
|
|
918
504
|
case '/setup':
|
|
919
505
|
ctx.addMessage('system', 'Run `calliope --setup` to reconfigure.');
|
|
920
506
|
break;
|
|
921
|
-
case '/
|
|
922
|
-
// /
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
classic - Everything in chronological order
|
|
929
|
-
response-top - Calliope response at top, tools below
|
|
930
|
-
response-bottom - Tools at top, response at bottom (default)
|
|
931
|
-
split - Side by side: tools left, response right
|
|
932
|
-
zen - Response only, tools hidden — distraction-free
|
|
933
|
-
focus - Latest response pinned top, compact tool log
|
|
934
|
-
dashboard - Three-panel: stats, response, tools
|
|
935
|
-
minimal - No decorations, raw text output
|
|
936
|
-
|
|
937
|
-
Usage: /layout <name>`);
|
|
938
|
-
break;
|
|
939
|
-
}
|
|
940
|
-
const validLayouts = ['classic', 'response-top', 'response-bottom', 'split', 'zen', 'focus', 'dashboard', 'minimal'];
|
|
941
|
-
if (!validLayouts.includes(layoutArg)) {
|
|
942
|
-
ctx.addMessage('error', `Invalid layout. Choose: ${validLayouts.join(', ')}`);
|
|
943
|
-
break;
|
|
944
|
-
}
|
|
945
|
-
config.set('layout', layoutArg);
|
|
946
|
-
ctx.setLayout(layoutArg);
|
|
947
|
-
ctx.addMessage('system', `\u2713 Layout set to: ${layoutArg}`);
|
|
948
|
-
break;
|
|
949
|
-
}
|
|
950
|
-
case '/density': {
|
|
951
|
-
// /density [normal|compact]
|
|
952
|
-
const densityArg = parts[1];
|
|
953
|
-
if (!densityArg) {
|
|
954
|
-
ctx.addMessage('system', `Current density: ${ctx.density}
|
|
955
|
-
|
|
956
|
-
Available densities:
|
|
957
|
-
normal - Standard spacing
|
|
958
|
-
compact - Reduced whitespace for more info
|
|
959
|
-
|
|
960
|
-
Usage: /density <normal|compact>`);
|
|
961
|
-
break;
|
|
962
|
-
}
|
|
963
|
-
const validDensities = ['normal', 'compact'];
|
|
964
|
-
if (!validDensities.includes(densityArg)) {
|
|
965
|
-
ctx.addMessage('error', `Invalid density. Choose: normal, compact`);
|
|
966
|
-
break;
|
|
967
|
-
}
|
|
968
|
-
config.set('density', densityArg);
|
|
969
|
-
ctx.setDensity(densityArg);
|
|
970
|
-
ctx.addMessage('system', `\u2713 Density set to: ${densityArg}`);
|
|
971
|
-
break;
|
|
972
|
-
}
|
|
973
|
-
case '/collapse': {
|
|
974
|
-
// /collapse [tools|thinking|all|off] [limit N]
|
|
975
|
-
const subCmd = parts[1];
|
|
976
|
-
if (!subCmd) {
|
|
977
|
-
ctx.addMessage('system', `Collapse settings:
|
|
978
|
-
collapseTools: ${ctx.collapseSettings.collapseTools}
|
|
979
|
-
collapseThinking: ${ctx.collapseSettings.collapseThinking}
|
|
980
|
-
toolDisplayLimit: ${ctx.collapseSettings.toolDisplayLimit} (0 = all expanded)
|
|
981
|
-
|
|
982
|
-
Usage:
|
|
983
|
-
/collapse tools - Toggle tool output collapsing
|
|
984
|
-
/collapse thinking - Toggle thinking block collapsing
|
|
985
|
-
/collapse all - Collapse both tools and thinking
|
|
986
|
-
/collapse off - Expand everything
|
|
987
|
-
/collapse limit <N> - Show last N tools expanded (0 = all)`);
|
|
988
|
-
break;
|
|
989
|
-
}
|
|
990
|
-
if (subCmd === 'tools') {
|
|
991
|
-
const newVal = !ctx.collapseSettings.collapseTools;
|
|
992
|
-
config.set('collapseTools', newVal);
|
|
993
|
-
ctx.setCollapseSettings(prev => ({ ...prev, collapseTools: newVal }));
|
|
994
|
-
ctx.addMessage('system', `\u2713 collapseTools set to ${newVal}`);
|
|
995
|
-
}
|
|
996
|
-
else if (subCmd === 'thinking') {
|
|
997
|
-
const newVal = !ctx.collapseSettings.collapseThinking;
|
|
998
|
-
config.set('collapseThinking', newVal);
|
|
999
|
-
ctx.setCollapseSettings(prev => ({ ...prev, collapseThinking: newVal }));
|
|
1000
|
-
ctx.addMessage('system', `\u2713 collapseThinking set to ${newVal}`);
|
|
1001
|
-
}
|
|
1002
|
-
else if (subCmd === 'all') {
|
|
1003
|
-
config.set('collapseTools', true);
|
|
1004
|
-
config.set('collapseThinking', true);
|
|
1005
|
-
ctx.setCollapseSettings(prev => ({ ...prev, collapseTools: true, collapseThinking: true }));
|
|
1006
|
-
ctx.addMessage('system', '\u2713 Collapsing tools and thinking');
|
|
1007
|
-
}
|
|
1008
|
-
else if (subCmd === 'off') {
|
|
1009
|
-
config.set('collapseTools', false);
|
|
1010
|
-
config.set('collapseThinking', false);
|
|
1011
|
-
ctx.setCollapseSettings(prev => ({ ...prev, collapseTools: false, collapseThinking: false }));
|
|
1012
|
-
ctx.addMessage('system', '\u2713 Expanding all output');
|
|
1013
|
-
}
|
|
1014
|
-
else if (subCmd === 'limit') {
|
|
1015
|
-
const limit = parseInt(parts[2], 10);
|
|
1016
|
-
if (isNaN(limit) || limit < 0 || limit > 100) {
|
|
1017
|
-
ctx.addMessage('error', 'Limit must be 0-100');
|
|
1018
|
-
break;
|
|
507
|
+
case '/loop': {
|
|
508
|
+
// /loop stop absorbs the old /cancel-loop, /breakloop, /stop
|
|
509
|
+
if (parts[1] === 'stop') {
|
|
510
|
+
if (ctx.loopActive) {
|
|
511
|
+
ctx.loopCancelledRef.current = true;
|
|
512
|
+
ctx.setLoopActive(false);
|
|
513
|
+
ctx.addMessage('system', '\u{1F6D1} Loop cancelled');
|
|
1019
514
|
}
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
else {
|
|
1025
|
-
ctx.addMessage('error', 'Unknown collapse option. Use: tools, thinking, all, off, or limit <N>');
|
|
515
|
+
else {
|
|
516
|
+
ctx.addMessage('system', 'No active loop to cancel');
|
|
517
|
+
}
|
|
518
|
+
break;
|
|
1026
519
|
}
|
|
1027
|
-
break;
|
|
1028
|
-
}
|
|
1029
|
-
case '/loop': {
|
|
1030
520
|
// Parse /loop "<prompt>" [--max-iterations N] [--completion-promise "text"]
|
|
1031
521
|
if (ctx.loopActive) {
|
|
1032
|
-
ctx.addMessage('system', 'Loop already running. Use /
|
|
522
|
+
ctx.addMessage('system', 'Loop already running. Use /loop stop to stop it first.');
|
|
1033
523
|
break;
|
|
1034
524
|
}
|
|
1035
525
|
const loopArgs = parts.slice(1).join(' ');
|
|
@@ -1045,7 +535,8 @@ Usage:
|
|
|
1045
535
|
prompt = quotedMatch[1];
|
|
1046
536
|
if (!prompt) {
|
|
1047
537
|
ctx.addMessage('system', `Usage: /loop "<prompt>" [--max-iterations N] [--completion-promise "text"]
|
|
1048
|
-
Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE"
|
|
538
|
+
Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE"
|
|
539
|
+
Stop a running loop with /loop stop`);
|
|
1049
540
|
break;
|
|
1050
541
|
}
|
|
1051
542
|
const defaultMaxIterations = resolveIterationLimit(config.get('maxIterations'));
|
|
@@ -1063,86 +554,11 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
1063
554
|
Prompt: "${prompt.substring(0, 50)}${prompt.length > 50 ? '...' : ''}"
|
|
1064
555
|
Max iterations: ${formatIterationLimit(loopMaxIterations)}
|
|
1065
556
|
${completionMatch ? `Completion promise: "${completionMatch[1]}"` : isFiniteIterationLimit(loopMaxIterations) ? 'No completion promise (runs until max iterations)' : 'No completion promise (runs until stopped)'}
|
|
1066
|
-
Use /
|
|
557
|
+
Use /loop stop to stop`);
|
|
1067
558
|
// Start the loop execution (non-blocking)
|
|
1068
559
|
ctx.runLoop(prompt, loopMaxIterations, completionMatch?.[1]);
|
|
1069
560
|
break;
|
|
1070
561
|
}
|
|
1071
|
-
case '/cancel-loop':
|
|
1072
|
-
case '/breakloop':
|
|
1073
|
-
case '/stop':
|
|
1074
|
-
if (ctx.loopActive) {
|
|
1075
|
-
ctx.loopCancelledRef.current = true;
|
|
1076
|
-
ctx.setLoopActive(false);
|
|
1077
|
-
ctx.addMessage('system', '\u{1F6D1} Loop cancelled');
|
|
1078
|
-
}
|
|
1079
|
-
else {
|
|
1080
|
-
ctx.addMessage('system', 'No active loop to cancel');
|
|
1081
|
-
}
|
|
1082
|
-
break;
|
|
1083
|
-
case '/confirm':
|
|
1084
|
-
if (parts[1] === 'on') {
|
|
1085
|
-
ctx.setConfirmMode(true);
|
|
1086
|
-
ctx.addMessage('system', '\u2713 Confirmation mode ON - will ask before risky operations');
|
|
1087
|
-
}
|
|
1088
|
-
else if (parts[1] === 'off') {
|
|
1089
|
-
ctx.setConfirmMode(false);
|
|
1090
|
-
ctx.addMessage('system', '\u26A0\uFE0F Confirmation mode OFF - risky operations will auto-execute');
|
|
1091
|
-
}
|
|
1092
|
-
else {
|
|
1093
|
-
ctx.addMessage('system', `Confirm mode: ${ctx.confirmMode ? 'ON' : 'OFF'}\nUsage: /confirm [on|off]`);
|
|
1094
|
-
}
|
|
1095
|
-
break;
|
|
1096
|
-
case '/profile': {
|
|
1097
|
-
const subCmd = parts[1];
|
|
1098
|
-
if (subCmd === 'list' || !subCmd) {
|
|
1099
|
-
const profiles = config.listProfiles();
|
|
1100
|
-
const active = config.getActiveProfile();
|
|
1101
|
-
const list = profiles.map(p => {
|
|
1102
|
-
const marker = p.name === active ? '\u2192 ' : ' ';
|
|
1103
|
-
const tag = p.builtin ? '(built-in)' : '(custom)';
|
|
1104
|
-
return `${marker}${p.name}: ${p.profile.provider}/${p.profile.model || 'default'} ${tag}`;
|
|
1105
|
-
}).join('\n');
|
|
1106
|
-
ctx.addMessage('system', `Profiles:\n${list}\n\nUsage: /profile <name> | /profile save <name>`);
|
|
1107
|
-
}
|
|
1108
|
-
else if (subCmd === 'save' && parts[2]) {
|
|
1109
|
-
const name = parts[2];
|
|
1110
|
-
config.saveProfile(name, {
|
|
1111
|
-
provider: ctx.provider,
|
|
1112
|
-
model: ctx.model,
|
|
1113
|
-
persona: ctx.persona,
|
|
1114
|
-
confirmMode: ctx.confirmMode,
|
|
1115
|
-
});
|
|
1116
|
-
ctx.addMessage('system', `\u2713 Saved profile: ${name}`);
|
|
1117
|
-
}
|
|
1118
|
-
else if (subCmd === 'delete' && parts[2]) {
|
|
1119
|
-
const name = parts[2];
|
|
1120
|
-
if (config.deleteProfile(name)) {
|
|
1121
|
-
ctx.addMessage('system', `\u2713 Deleted profile: ${name}`);
|
|
1122
|
-
}
|
|
1123
|
-
else {
|
|
1124
|
-
ctx.addMessage('error', `Cannot delete profile: ${name} (built-in or not found)`);
|
|
1125
|
-
}
|
|
1126
|
-
}
|
|
1127
|
-
else {
|
|
1128
|
-
// Load profile
|
|
1129
|
-
const profile = config.getProfile(subCmd);
|
|
1130
|
-
if (profile) {
|
|
1131
|
-
ctx.setProvider(profile.provider);
|
|
1132
|
-
if (profile.model)
|
|
1133
|
-
ctx.setModel(profile.model);
|
|
1134
|
-
ctx.setPersona(profile.persona);
|
|
1135
|
-
if (profile.confirmMode !== undefined)
|
|
1136
|
-
ctx.setConfirmMode(profile.confirmMode);
|
|
1137
|
-
config.setActiveProfile(subCmd);
|
|
1138
|
-
ctx.addMessage('system', `\u2713 Loaded profile: ${subCmd} (${profile.provider}/${profile.model || 'default'})`);
|
|
1139
|
-
}
|
|
1140
|
-
else {
|
|
1141
|
-
ctx.addMessage('error', `Profile not found: ${subCmd}\nBuilt-in: fast, smart, cheap, local`);
|
|
1142
|
-
}
|
|
1143
|
-
}
|
|
1144
|
-
break;
|
|
1145
|
-
}
|
|
1146
562
|
case '/mcp': {
|
|
1147
563
|
const subCmd = parts[1];
|
|
1148
564
|
if (subCmd === 'list' || !subCmd) {
|
|
@@ -1152,7 +568,7 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
1152
568
|
}
|
|
1153
569
|
else {
|
|
1154
570
|
const list = servers.map(s => {
|
|
1155
|
-
const status = s.status === 'connected' ? '\u{1F7E2}' : s.status === 'error' ? '\u{1F534}' : '
|
|
571
|
+
const status = s.status === 'connected' ? '\u{1F7E2}' : s.status === 'error' ? '\u{1F534}' : '⚪';
|
|
1156
572
|
return `${status} ${s.name} (${s.tools.length} tools)\n ${s.url}`;
|
|
1157
573
|
}).join('\n\n');
|
|
1158
574
|
ctx.addMessage('system', `MCP Servers:\n\n${list}`);
|
|
@@ -1163,7 +579,7 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
1163
579
|
ctx.addMessage('system', `Registering MCP server: ${url}...`);
|
|
1164
580
|
try {
|
|
1165
581
|
const server = await mcp.registerServer(url);
|
|
1166
|
-
ctx.addMessage('system',
|
|
582
|
+
ctx.addMessage('system', `✓ Registered: ${server.name} (${server.tools.length} tools)`);
|
|
1167
583
|
}
|
|
1168
584
|
catch (e) {
|
|
1169
585
|
ctx.addMessage('error', `Failed to register: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -1171,7 +587,7 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
1171
587
|
}
|
|
1172
588
|
else if ((subCmd === 'remove' || subCmd === 'rm') && parts[2]) {
|
|
1173
589
|
if (mcp.unregisterServer(parts[2])) {
|
|
1174
|
-
ctx.addMessage('system', '
|
|
590
|
+
ctx.addMessage('system', '✓ Server removed');
|
|
1175
591
|
}
|
|
1176
592
|
else {
|
|
1177
593
|
ctx.addMessage('error', 'Server not found');
|
|
@@ -1193,7 +609,7 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
1193
609
|
ctx.addMessage('system', 'No MCP tools available. Add servers with /mcp add <url>');
|
|
1194
610
|
}
|
|
1195
611
|
else {
|
|
1196
|
-
const list = tools.map(t =>
|
|
612
|
+
const list = tools.map(t => `• ${t.name}\n ${t.description}`).join('\n\n');
|
|
1197
613
|
ctx.addMessage('system', `MCP Tools:\n\n${list}`);
|
|
1198
614
|
}
|
|
1199
615
|
}
|
|
@@ -1205,14 +621,20 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
1205
621
|
case '/skills': {
|
|
1206
622
|
const subCmd = parts[1];
|
|
1207
623
|
if (subCmd === 'list' || !subCmd) {
|
|
1208
|
-
|
|
1209
|
-
|
|
624
|
+
// Show every installed skill with its trust state (#137) — including
|
|
625
|
+
// CHANGED entries that getSkills() withholds from the model.
|
|
626
|
+
const listed = skills.listSkills();
|
|
627
|
+
if (listed.length === 0) {
|
|
1210
628
|
ctx.addMessage('system', 'No skills installed.\n\nUsage:\n /skills add <name> - Install from agentskills.io\n /skills add <github-url> - Install from GitHub\n /skills add <path> - Install from local directory');
|
|
1211
629
|
}
|
|
1212
630
|
else {
|
|
1213
|
-
const list =
|
|
631
|
+
const list = listed.map(s => {
|
|
1214
632
|
const src = s.source === 'github' ? '(GitHub)' : s.source === 'registry' ? '(agentskills.io)' : '(local)';
|
|
1215
|
-
|
|
633
|
+
const trust = s.trust === 'pinned' ? (s.fingerprint ?? 'pinned') :
|
|
634
|
+
s.trust === 'changed' ? 'CHANGED — reinstall to re-trust' :
|
|
635
|
+
'UNVERIFIED';
|
|
636
|
+
const desc = s.description ? `${s.description.substring(0, 80)}...` : '';
|
|
637
|
+
return `• ${s.name} ${src} [${trust}]\n ${desc}`;
|
|
1216
638
|
}).join('\n\n');
|
|
1217
639
|
ctx.addMessage('system', `Installed Skills:\n\n${list}`);
|
|
1218
640
|
}
|
|
@@ -1220,6 +642,10 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
1220
642
|
else if (subCmd === 'add' && parts[2]) {
|
|
1221
643
|
const source = parts[2];
|
|
1222
644
|
ctx.addMessage('system', `Installing skill: ${source}...`);
|
|
645
|
+
// Snapshot pinned fingerprints so a re-install can show old→new rather
|
|
646
|
+
// than re-pinning silently (#137). The remote name is only known after
|
|
647
|
+
// the fetch, so we key the lookup by the returned skill's name.
|
|
648
|
+
const priorFingerprints = new Map(skills.listSkills().map(s => [s.name, s.fingerprint]));
|
|
1223
649
|
try {
|
|
1224
650
|
let skill;
|
|
1225
651
|
if (source.startsWith('http')) {
|
|
@@ -1232,7 +658,14 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
1232
658
|
skill = await skills.installFromRegistry(source);
|
|
1233
659
|
}
|
|
1234
660
|
if (skill) {
|
|
1235
|
-
|
|
661
|
+
const prev = priorFingerprints.get(skill.metadata.name);
|
|
662
|
+
const now = skill.fingerprint ?? '(unverified)';
|
|
663
|
+
if (prev && prev !== skill.fingerprint) {
|
|
664
|
+
ctx.addMessage('system', `✓ Re-pinned ${skill.metadata.name}: ${prev} → ${now} (content updated)`);
|
|
665
|
+
}
|
|
666
|
+
else {
|
|
667
|
+
ctx.addMessage('system', `✓ Installed ${skill.metadata.name} — pinned ${now} (trust-on-first-use)`);
|
|
668
|
+
}
|
|
1236
669
|
}
|
|
1237
670
|
else {
|
|
1238
671
|
ctx.addMessage('error', 'Failed to install skill');
|
|
@@ -1244,7 +677,7 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
1244
677
|
}
|
|
1245
678
|
else if ((subCmd === 'remove' || subCmd === 'rm') && parts[2]) {
|
|
1246
679
|
if (skills.uninstallSkill(parts[2])) {
|
|
1247
|
-
ctx.addMessage('system', '
|
|
680
|
+
ctx.addMessage('system', '✓ Skill removed');
|
|
1248
681
|
}
|
|
1249
682
|
else {
|
|
1250
683
|
ctx.addMessage('error', 'Skill not found');
|
|
@@ -1261,10 +694,19 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
1261
694
|
info += `License: ${skill.metadata.license}\n`;
|
|
1262
695
|
if (skill.sourceUrl)
|
|
1263
696
|
info += `Source: ${skill.sourceUrl}\n`;
|
|
697
|
+
info += skill.fingerprint ? `Fingerprint: ${skill.fingerprint} (${skill.trust})\n` : 'Fingerprint: none (UNVERIFIED)\n';
|
|
1264
698
|
ctx.addMessage('system', info);
|
|
1265
699
|
}
|
|
1266
700
|
else {
|
|
1267
|
-
|
|
701
|
+
// getSkill withholds a tampered skill; surface WHY, not a bare
|
|
702
|
+
// "not found", so the trust state stays visible (#137).
|
|
703
|
+
const changed = skills.listSkills().find(s => s.name === parts[2] && s.trust === 'changed');
|
|
704
|
+
if (changed) {
|
|
705
|
+
ctx.addMessage('error', `Skill "${parts[2]}" CHANGED since install (pinned ${changed.fingerprint}) — content no longer matches. Reinstall to re-trust.`);
|
|
706
|
+
}
|
|
707
|
+
else {
|
|
708
|
+
ctx.addMessage('error', 'Skill not found');
|
|
709
|
+
}
|
|
1268
710
|
}
|
|
1269
711
|
}
|
|
1270
712
|
else {
|
|
@@ -1341,894 +783,55 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
1341
783
|
}
|
|
1342
784
|
break;
|
|
1343
785
|
}
|
|
1344
|
-
case '/
|
|
1345
|
-
const fuzzy = await import('../fuzzy-search.js');
|
|
1346
|
-
const query = parts.slice(1).join(' ');
|
|
1347
|
-
if (!query) {
|
|
1348
|
-
ctx.addMessage('system', 'Usage: /find <pattern>\nFuzzy search for files');
|
|
1349
|
-
}
|
|
1350
|
-
else {
|
|
1351
|
-
const results = fuzzy.searchWithHighlight(process.cwd(), query, { maxResults: 20 });
|
|
1352
|
-
if (results.length === 0) {
|
|
1353
|
-
ctx.addMessage('system', 'No files found');
|
|
1354
|
-
}
|
|
1355
|
-
else {
|
|
1356
|
-
const list = results.map((r, i) => `${i + 1}. ${r.highlighted}`).join('\n');
|
|
1357
|
-
ctx.addMessage('system', `Found ${results.length} files:\n\n${list}`);
|
|
1358
|
-
}
|
|
1359
|
-
}
|
|
1360
|
-
break;
|
|
1361
|
-
}
|
|
1362
|
-
case '/branch': {
|
|
1363
|
-
const branching = await import('../branching.js');
|
|
786
|
+
case '/scope': {
|
|
1364
787
|
const subCmd = parts[1];
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
const
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
else if (subCmd === 'new' && parts[2]) {
|
|
1371
|
-
const branch = branching.createBranch(sessionId, parts[2], ctx.llmMessages.current, parts.slice(3).join(' '));
|
|
1372
|
-
ctx.addMessage('system', `Created branch: ${branch.name}`);
|
|
1373
|
-
}
|
|
1374
|
-
else if (subCmd === 'switch' && parts[2]) {
|
|
1375
|
-
const msgs = branching.switchBranch(sessionId, parts[2], ctx.llmMessages.current);
|
|
1376
|
-
if (msgs) {
|
|
1377
|
-
ctx.llmMessages.current = msgs;
|
|
1378
|
-
ctx.addMessage('system', `Switched to branch: ${parts[2]}`);
|
|
788
|
+
if (subCmd === 'add') {
|
|
789
|
+
// Absorbs the old /add-dir
|
|
790
|
+
const dirPath = parts.slice(2).join(' ').replace(/^["']|["']$/g, '');
|
|
791
|
+
if (!dirPath) {
|
|
792
|
+
ctx.addMessage('system', 'Usage: /scope add <path>\n\nAdd a directory to the allowed scope.\nThe agent can only access files within scope.');
|
|
1379
793
|
}
|
|
1380
794
|
else {
|
|
1381
|
-
|
|
795
|
+
const result = addToScope(dirPath);
|
|
796
|
+
if (result.success) {
|
|
797
|
+
ctx.addMessage('system', `✓ ${result.message}`);
|
|
798
|
+
}
|
|
799
|
+
else {
|
|
800
|
+
ctx.addMessage('error', result.message);
|
|
801
|
+
}
|
|
1382
802
|
}
|
|
1383
803
|
}
|
|
1384
|
-
else if (subCmd === '
|
|
1385
|
-
|
|
1386
|
-
|
|
804
|
+
else if (subCmd === 'remove') {
|
|
805
|
+
// Absorbs the old /remove-dir
|
|
806
|
+
const dirPath = parts.slice(2).join(' ').replace(/^["']|["']$/g, '');
|
|
807
|
+
if (!dirPath) {
|
|
808
|
+
ctx.addMessage('system', 'Usage: /scope remove <path>\n\nRemove a directory from the allowed scope.');
|
|
1387
809
|
}
|
|
1388
810
|
else {
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
else {
|
|
1393
|
-
ctx.addMessage('system', 'Usage: /branch [list|new <name>|switch <name>|delete <name>]');
|
|
1394
|
-
}
|
|
1395
|
-
break;
|
|
1396
|
-
}
|
|
1397
|
-
case '/theme': {
|
|
1398
|
-
const subCmd = parts[1];
|
|
1399
|
-
if (!subCmd) {
|
|
1400
|
-
// Open interactive theme picker
|
|
1401
|
-
ctx.setModalMode('theme-picker');
|
|
1402
|
-
}
|
|
1403
|
-
else if (subCmd === 'list') {
|
|
1404
|
-
const themes = await import('../themes.js');
|
|
1405
|
-
const list = themes.listThemes();
|
|
1406
|
-
const current = themes.getCurrentThemeName();
|
|
1407
|
-
const formatted = list.map((t) => {
|
|
1408
|
-
const marker = t.name === current ? ' *' : '';
|
|
1409
|
-
const custom = t.custom ? ' (custom)' : '';
|
|
1410
|
-
return ` ${t.name}${marker}${custom} - ${t.description || 'No description'}`;
|
|
1411
|
-
}).join('\n');
|
|
1412
|
-
ctx.addMessage('system', `Available themes:\n${formatted}`);
|
|
1413
|
-
}
|
|
1414
|
-
else {
|
|
1415
|
-
const themes = await import('../themes.js');
|
|
1416
|
-
if (themes.setCurrentTheme(subCmd)) {
|
|
1417
|
-
themes.clearThemeCache();
|
|
1418
|
-
ctx.addMessage('system', `Theme set to: ${subCmd}`);
|
|
1419
|
-
}
|
|
1420
|
-
else {
|
|
1421
|
-
ctx.addMessage('error', `Theme not found: ${subCmd}`);
|
|
1422
|
-
}
|
|
1423
|
-
}
|
|
1424
|
-
break;
|
|
1425
|
-
}
|
|
1426
|
-
case '/skin': {
|
|
1427
|
-
const subCmd = parts[1];
|
|
1428
|
-
if (subCmd === 'list' || !subCmd) {
|
|
1429
|
-
const skins = listSkins();
|
|
1430
|
-
const current = getCurrentSkin();
|
|
1431
|
-
const formatted = skins.map((s) => {
|
|
1432
|
-
const marker = s.name === current.name ? ' *' : '';
|
|
1433
|
-
const custom = s.custom ? ' (custom)' : '';
|
|
1434
|
-
return ` ${s.name}${marker}${custom} - ${s.description}`;
|
|
1435
|
-
}).join('\n');
|
|
1436
|
-
ctx.addMessage('system', `Active: ${current.name}\nAvailable skins:\n${formatted}`);
|
|
1437
|
-
}
|
|
1438
|
-
else {
|
|
1439
|
-
applySkin(subCmd);
|
|
1440
|
-
const newSkin = getCurrentSkin();
|
|
1441
|
-
if (newSkin.name === subCmd) {
|
|
1442
|
-
config.set('activeSkin', subCmd);
|
|
1443
|
-
ctx.addMessage('system', `Skin set to: ${subCmd} \u2014 ${newSkin.description}`);
|
|
1444
|
-
}
|
|
1445
|
-
else {
|
|
1446
|
-
ctx.addMessage('error', `Skin not found: ${subCmd}. Available: ${listSkins().map((s) => s.name).join(', ')}`);
|
|
1447
|
-
}
|
|
1448
|
-
}
|
|
1449
|
-
break;
|
|
1450
|
-
}
|
|
1451
|
-
case '/palette': {
|
|
1452
|
-
const subCmd = parts[1];
|
|
1453
|
-
if (subCmd === 'list' || !subCmd) {
|
|
1454
|
-
const palettes = listPalettes();
|
|
1455
|
-
const current = getCurrentPalette();
|
|
1456
|
-
const formatted = palettes.map((p) => {
|
|
1457
|
-
const marker = p.name === current.name ? ' *' : '';
|
|
1458
|
-
const custom = p.custom ? ' (custom)' : '';
|
|
1459
|
-
return ` ${p.name}${marker}${custom} - ${p.description}`;
|
|
1460
|
-
}).join('\n');
|
|
1461
|
-
ctx.addMessage('system', `Active: ${current.name}\nAvailable palettes:\n${formatted}`);
|
|
1462
|
-
}
|
|
1463
|
-
else {
|
|
1464
|
-
applyPalette(subCmd);
|
|
1465
|
-
const newPal = getCurrentPalette();
|
|
1466
|
-
if (newPal.name === subCmd) {
|
|
1467
|
-
config.set('activePalette', subCmd);
|
|
1468
|
-
ctx.addMessage('system', `Palette set to: ${subCmd} \u2014 ${newPal.description}`);
|
|
1469
|
-
}
|
|
1470
|
-
else {
|
|
1471
|
-
ctx.addMessage('error', `Palette not found: ${subCmd}. Available: ${listPalettes().map((p) => p.name).join(', ')}`);
|
|
1472
|
-
}
|
|
1473
|
-
}
|
|
1474
|
-
break;
|
|
1475
|
-
}
|
|
1476
|
-
case '/companion': {
|
|
1477
|
-
const subCmd = parts[1];
|
|
1478
|
-
if (subCmd === 'list' || !subCmd) {
|
|
1479
|
-
const companions = listCompanions();
|
|
1480
|
-
const current = getCurrentCompanion();
|
|
1481
|
-
const formatted = companions.map((comp) => {
|
|
1482
|
-
const marker = comp.name === current.name ? ' *' : '';
|
|
1483
|
-
return ` ${comp.name}${marker} - ${comp.description}`;
|
|
1484
|
-
}).join('\n');
|
|
1485
|
-
ctx.addMessage('system', `Active: ${current.name}\nAvailable companions:\n${formatted}`);
|
|
1486
|
-
}
|
|
1487
|
-
else {
|
|
1488
|
-
applyCompanion(subCmd);
|
|
1489
|
-
const newComp = getCurrentCompanion();
|
|
1490
|
-
if (newComp.name === subCmd) {
|
|
1491
|
-
config.set('activeCompanion', subCmd);
|
|
1492
|
-
ctx.llmMessages.current = [{ role: 'system', content: buildFullSystemPrompt(ctx.persona, getActiveProjectDir(ctx)) }];
|
|
1493
|
-
ctx.addMessage('system', `Companion set to: ${subCmd} \u2014 "${newComp.greeting}"`);
|
|
1494
|
-
}
|
|
1495
|
-
else {
|
|
1496
|
-
ctx.addMessage('error', `Companion not found: ${subCmd}. Available: ${listCompanions().map((c) => c.name).join(', ')}`);
|
|
1497
|
-
}
|
|
1498
|
-
}
|
|
1499
|
-
break;
|
|
1500
|
-
}
|
|
1501
|
-
case '/banner': {
|
|
1502
|
-
const bannerSkin = getCurrentSkin();
|
|
1503
|
-
const bannerPalette = getCurrentPalette();
|
|
1504
|
-
const bannerColor = bannerPalette.colors.primary;
|
|
1505
|
-
const imgInfo = getTerminalImageInfo();
|
|
1506
|
-
const rendered = renderSkinBanner(bannerSkin.banner.art, bannerColor, bannerSkin.banner.tagline ?? undefined, imgInfo.mode);
|
|
1507
|
-
ctx.addMessage('system', `${rendered}\n\nSkin: ${bannerSkin.name} | Terminal: ${getImageModeLabel(imgInfo.mode)}${imgInfo.truecolor ? ' (truecolor)' : ''}`);
|
|
1508
|
-
break;
|
|
1509
|
-
}
|
|
1510
|
-
case '/hud': {
|
|
1511
|
-
const hudSkin = getCurrentSkin();
|
|
1512
|
-
const hudPalette = getCurrentPalette();
|
|
1513
|
-
const hudCompanion = getCurrentCompanion();
|
|
1514
|
-
const hudPack = getCurrentPack();
|
|
1515
|
-
const hudIntensity = getCompanionMode();
|
|
1516
|
-
ctx.addMessage('system', `HUD Configuration\n` +
|
|
1517
|
-
(hudPack ? ` Pack: ${hudPack.name} — ${hudPack.description}\n` : '') +
|
|
1518
|
-
` Skin: ${hudSkin.name} — ${hudSkin.description}\n` +
|
|
1519
|
-
` Palette: ${hudPalette.name} — ${hudPalette.description}\n` +
|
|
1520
|
-
` Companion: ${hudCompanion.name} — ${hudCompanion.description}\n` +
|
|
1521
|
-
` Intensity: ${hudIntensity}\n` +
|
|
1522
|
-
` Emojis: ${config.get('useEmojis') !== false ? 'ON' : 'OFF'}\n` +
|
|
1523
|
-
` Mood: ${getMoodText()}\n\n` +
|
|
1524
|
-
` /pack <name> /intensity <pro|immersive> /emoji [on|off]\n` +
|
|
1525
|
-
` /skin <name> /palette <name> /companion <name>`);
|
|
1526
|
-
break;
|
|
1527
|
-
}
|
|
1528
|
-
case '/pack': {
|
|
1529
|
-
const subCmd = parts[1];
|
|
1530
|
-
if (!subCmd) {
|
|
1531
|
-
ctx.setModalMode('pack-picker');
|
|
1532
|
-
break;
|
|
1533
|
-
}
|
|
1534
|
-
if (subCmd === 'list') {
|
|
1535
|
-
const category = parts[2];
|
|
1536
|
-
const packs = listThemePacks(category || undefined);
|
|
1537
|
-
const currentP = getCurrentPack();
|
|
1538
|
-
// Group by category
|
|
1539
|
-
const grouped = new Map();
|
|
1540
|
-
for (const p of packs) {
|
|
1541
|
-
const group = grouped.get(p.category) || [];
|
|
1542
|
-
group.push(p);
|
|
1543
|
-
grouped.set(p.category, group);
|
|
1544
|
-
}
|
|
1545
|
-
let output = 'Theme Packs:\n';
|
|
1546
|
-
for (const [cat, catPacks] of grouped) {
|
|
1547
|
-
output += `\n [${cat}]\n`;
|
|
1548
|
-
for (const p of catPacks) {
|
|
1549
|
-
const marker = currentP && p.name === currentP.name ? ' *' : '';
|
|
1550
|
-
output += ` ${p.name}${marker} — ${p.description}\n`;
|
|
1551
|
-
}
|
|
1552
|
-
}
|
|
1553
|
-
output += '\nUse: /pack <name>';
|
|
1554
|
-
ctx.addMessage('system', output);
|
|
1555
|
-
}
|
|
1556
|
-
else {
|
|
1557
|
-
// Run theme transition animation before applying
|
|
1558
|
-
const targetPack = getThemePack(subCmd);
|
|
1559
|
-
if (targetPack?.skin.splash?.transition) {
|
|
1560
|
-
await renderTransition(targetPack.skin.splash.transition);
|
|
1561
|
-
}
|
|
1562
|
-
const success = applyThemePack(subCmd, getCompanionMode());
|
|
1563
|
-
if (success) {
|
|
1564
|
-
const pack = getCurrentPack();
|
|
1565
|
-
config.set('activeThemePack', subCmd);
|
|
1566
|
-
config.set('activeSkin', pack.skin.name);
|
|
1567
|
-
config.set('activePalette', pack.palette.name);
|
|
1568
|
-
const companion = getCompanionMode() === 'professional'
|
|
1569
|
-
? pack.companions.professional
|
|
1570
|
-
: pack.companions.immersive;
|
|
1571
|
-
config.set('activeCompanion', companion.name);
|
|
1572
|
-
// Reset LLM system prompt to use the companion's persona
|
|
1573
|
-
ctx.llmMessages.current = [{ role: 'system', content: buildFullSystemPrompt(ctx.persona, getActiveProjectDir(ctx)) }];
|
|
1574
|
-
ctx.addMessage('system', `Theme pack: ${subCmd}\n` +
|
|
1575
|
-
` Skin: ${pack.skin.name}, Palette: ${pack.palette.name}, Companion: ${companion.name}\n` +
|
|
1576
|
-
` "${companion.greeting}"`);
|
|
1577
|
-
}
|
|
1578
|
-
else {
|
|
1579
|
-
ctx.addMessage('error', `Theme pack not found: ${subCmd}. Use /pack list to see available packs.`);
|
|
1580
|
-
}
|
|
1581
|
-
}
|
|
1582
|
-
break;
|
|
1583
|
-
}
|
|
1584
|
-
case '/intensity': {
|
|
1585
|
-
const intensity = parts[1];
|
|
1586
|
-
if (intensity === 'professional' || intensity === 'pro') {
|
|
1587
|
-
const success = setCompanionMode('professional');
|
|
1588
|
-
if (success) {
|
|
1589
|
-
const pack = getCurrentPack();
|
|
1590
|
-
config.set('companionIntensity', 'professional');
|
|
1591
|
-
config.set('activeCompanion', pack.companions.professional.name);
|
|
1592
|
-
ctx.llmMessages.current = [{ role: 'system', content: buildFullSystemPrompt(ctx.persona, getActiveProjectDir(ctx)) }];
|
|
1593
|
-
ctx.addMessage('system', `Switched to professional mode — ${pack.companions.professional.description}`);
|
|
1594
|
-
}
|
|
1595
|
-
else {
|
|
1596
|
-
ctx.addMessage('error', 'No theme pack active. Use /pack <name> first.');
|
|
1597
|
-
}
|
|
1598
|
-
}
|
|
1599
|
-
else if (intensity === 'immersive' || intensity === 'imm') {
|
|
1600
|
-
const success = setCompanionMode('immersive');
|
|
1601
|
-
if (success) {
|
|
1602
|
-
const pack = getCurrentPack();
|
|
1603
|
-
config.set('companionIntensity', 'immersive');
|
|
1604
|
-
config.set('activeCompanion', pack.companions.immersive.name);
|
|
1605
|
-
ctx.llmMessages.current = [{ role: 'system', content: buildFullSystemPrompt(ctx.persona, getActiveProjectDir(ctx)) }];
|
|
1606
|
-
ctx.addMessage('system', `Switched to immersive mode — ${pack.companions.immersive.description}`);
|
|
1607
|
-
}
|
|
1608
|
-
else {
|
|
1609
|
-
ctx.addMessage('error', 'No theme pack active. Use /pack <name> first.');
|
|
1610
|
-
}
|
|
1611
|
-
}
|
|
1612
|
-
else {
|
|
1613
|
-
const currentIntensity = getCompanionMode();
|
|
1614
|
-
ctx.addMessage('system', `Intensity: ${currentIntensity}\nOptions: /intensity professional (pro), /intensity immersive (imm)`);
|
|
1615
|
-
}
|
|
1616
|
-
break;
|
|
1617
|
-
}
|
|
1618
|
-
case '/emoji': {
|
|
1619
|
-
const emojiArg = parts[1];
|
|
1620
|
-
const current = config.get('useEmojis') !== false;
|
|
1621
|
-
if (emojiArg === 'on') {
|
|
1622
|
-
config.set('useEmojis', true);
|
|
1623
|
-
ctx.addMessage('system', '\u2713 Emojis enabled');
|
|
1624
|
-
}
|
|
1625
|
-
else if (emojiArg === 'off') {
|
|
1626
|
-
config.set('useEmojis', false);
|
|
1627
|
-
ctx.addMessage('system', '\u2713 Emojis disabled — text fallbacks will be used');
|
|
1628
|
-
}
|
|
1629
|
-
else if (emojiArg === 'toggle') {
|
|
1630
|
-
config.set('useEmojis', !current);
|
|
1631
|
-
ctx.addMessage('system', `\u2713 Emojis ${!current ? 'enabled' : 'disabled'}`);
|
|
1632
|
-
}
|
|
1633
|
-
else {
|
|
1634
|
-
ctx.addMessage('system', `Emojis: ${current ? 'ON' : 'OFF'}\nUsage: /emoji [on|off|toggle]`);
|
|
1635
|
-
}
|
|
1636
|
-
break;
|
|
1637
|
-
}
|
|
1638
|
-
case '/hooks': {
|
|
1639
|
-
const hooksModule = await import('../hooks.js');
|
|
1640
|
-
const subCmd = parts[1];
|
|
1641
|
-
if (subCmd === 'list' || !subCmd) {
|
|
1642
|
-
ctx.addMessage('system', hooksModule.listHooksFormatted());
|
|
1643
|
-
}
|
|
1644
|
-
else if (subCmd === 'add' && parts[2]) {
|
|
1645
|
-
const event = parts[2];
|
|
1646
|
-
const hookCommand = parts.slice(3).join(' ');
|
|
1647
|
-
if (!hookCommand) {
|
|
1648
|
-
ctx.addMessage('system', 'Usage: /hooks add <event> <command>');
|
|
1649
|
-
}
|
|
1650
|
-
else {
|
|
1651
|
-
hooksModule.addHook({ event, name: `Hook for ${event}`, command: hookCommand, enabled: true, async: false });
|
|
1652
|
-
ctx.addMessage('system', 'Hook added');
|
|
1653
|
-
}
|
|
1654
|
-
}
|
|
1655
|
-
else if (subCmd === 'init') {
|
|
1656
|
-
hooksModule.initDefaultHooks();
|
|
1657
|
-
ctx.addMessage('system', 'Default hooks initialized');
|
|
1658
|
-
}
|
|
1659
|
-
else {
|
|
1660
|
-
ctx.addMessage('system', 'Usage: /hooks [list|add <event> <command>|init]');
|
|
1661
|
-
}
|
|
1662
|
-
break;
|
|
1663
|
-
}
|
|
1664
|
-
case '/search': {
|
|
1665
|
-
const query = parts.slice(1).join(' ');
|
|
1666
|
-
if (!query) {
|
|
1667
|
-
ctx.addMessage('system', 'Usage: /search <query>\nSearch conversation history');
|
|
1668
|
-
}
|
|
1669
|
-
else {
|
|
1670
|
-
const lower = query.toLowerCase();
|
|
1671
|
-
const matches = ctx.messages.filter(m => m.content.toLowerCase().includes(lower));
|
|
1672
|
-
if (matches.length === 0) {
|
|
1673
|
-
ctx.addMessage('system', 'No matches found');
|
|
1674
|
-
}
|
|
1675
|
-
else {
|
|
1676
|
-
const results = matches.slice(-10).map(m => {
|
|
1677
|
-
const preview = m.content.slice(0, 100).replace(/\n/g, ' ');
|
|
1678
|
-
return `[${m.type}] ${preview}...`;
|
|
1679
|
-
}).join('\n\n');
|
|
1680
|
-
ctx.addMessage('system', `Found ${matches.length} matches:\n\n${results}`);
|
|
1681
|
-
}
|
|
1682
|
-
}
|
|
1683
|
-
break;
|
|
1684
|
-
}
|
|
1685
|
-
case '/project': {
|
|
1686
|
-
const projectConfig = await import('../project-config.js');
|
|
1687
|
-
const subCmd = parts[1];
|
|
1688
|
-
const cwd = process.cwd();
|
|
1689
|
-
if (subCmd === 'init') {
|
|
1690
|
-
const configPath = projectConfig.createProjectConfig(cwd);
|
|
1691
|
-
ctx.addMessage('system', `Created project config: ${configPath}\nEdit the file to customize settings.`);
|
|
1692
|
-
}
|
|
1693
|
-
else if (subCmd === 'show' || !subCmd) {
|
|
1694
|
-
const configPath = projectConfig.findProjectConfig(cwd);
|
|
1695
|
-
if (!configPath) {
|
|
1696
|
-
ctx.addMessage('system', 'No project config found.\nRun /project init to create one.');
|
|
1697
|
-
}
|
|
1698
|
-
else {
|
|
1699
|
-
const cfg = projectConfig.loadProjectConfig(configPath);
|
|
1700
|
-
if (cfg) {
|
|
1701
|
-
let info = `Config: ${configPath}\n\n`;
|
|
1702
|
-
if (cfg.project)
|
|
1703
|
-
info += `Project: ${cfg.project}\n`;
|
|
1704
|
-
if (cfg.provider)
|
|
1705
|
-
info += `Provider: ${cfg.provider}\n`;
|
|
1706
|
-
if (cfg.model)
|
|
1707
|
-
info += `Model: ${cfg.model}\n`;
|
|
1708
|
-
if (cfg.tech?.length)
|
|
1709
|
-
info += `Tech: ${cfg.tech.join(', ')}\n`;
|
|
1710
|
-
if (cfg.conventions?.length)
|
|
1711
|
-
info += `\nConventions:\n${cfg.conventions.map((c) => ` - ${c}`).join('\n')}\n`;
|
|
1712
|
-
if (cfg.commands)
|
|
1713
|
-
info += `\nCommands: ${Object.keys(cfg.commands).join(', ')}\n`;
|
|
1714
|
-
ctx.addMessage('system', info);
|
|
811
|
+
const result = removeFromScope(dirPath);
|
|
812
|
+
if (result.success) {
|
|
813
|
+
ctx.addMessage('system', `✓ ${result.message}`);
|
|
1715
814
|
}
|
|
1716
815
|
else {
|
|
1717
|
-
ctx.addMessage('error',
|
|
816
|
+
ctx.addMessage('error', result.message);
|
|
1718
817
|
}
|
|
1719
818
|
}
|
|
1720
819
|
}
|
|
1721
|
-
else if (subCmd === '
|
|
1722
|
-
const configPath = projectConfig.findProjectConfig(cwd);
|
|
1723
|
-
const cfg = configPath ? projectConfig.loadProjectConfig(configPath) : null;
|
|
1724
|
-
const cmdName = parts[2];
|
|
1725
|
-
if (cfg?.commands?.[cmdName]) {
|
|
1726
|
-
const commandToRun = cfg.commands[cmdName];
|
|
1727
|
-
// Show the command and source for user awareness
|
|
1728
|
-
ctx.addMessage('system', `Project command "${cmdName}" from ${configPath}:\n` +
|
|
1729
|
-
` $ ${commandToRun}\n\n` +
|
|
1730
|
-
`Type "/project run-confirm ${cmdName}" to execute, or review the .calliope config first.`);
|
|
1731
|
-
}
|
|
1732
|
-
else {
|
|
1733
|
-
ctx.addMessage('error', `Command not found: ${cmdName}`);
|
|
1734
|
-
}
|
|
1735
|
-
}
|
|
1736
|
-
else if (subCmd === 'run-confirm' && parts[2]) {
|
|
1737
|
-
const configPath = projectConfig.findProjectConfig(cwd);
|
|
1738
|
-
const cfg = configPath ? projectConfig.loadProjectConfig(configPath) : null;
|
|
1739
|
-
const cmdName = parts[2];
|
|
1740
|
-
if (cfg?.commands?.[cmdName]) {
|
|
1741
|
-
const commandToRun = cfg.commands[cmdName];
|
|
1742
|
-
ctx.addMessage('system', `Running: ${commandToRun}`);
|
|
1743
|
-
const { spawn } = await import('child_process');
|
|
1744
|
-
const proc = spawn('sh', ['-c', commandToRun], { cwd, stdio: 'pipe' });
|
|
1745
|
-
let output = '';
|
|
1746
|
-
proc.stdout?.on('data', (d) => output += d.toString());
|
|
1747
|
-
proc.stderr?.on('data', (d) => output += d.toString());
|
|
1748
|
-
proc.on('close', (code) => {
|
|
1749
|
-
ctx.addMessage('system', `Exit ${code}\n${output}`);
|
|
1750
|
-
});
|
|
1751
|
-
}
|
|
1752
|
-
else {
|
|
1753
|
-
ctx.addMessage('error', `Command not found: ${cmdName}`);
|
|
1754
|
-
}
|
|
1755
|
-
}
|
|
1756
|
-
else {
|
|
1757
|
-
ctx.addMessage('system', 'Usage: /project [init|show|run <cmd>|run-confirm <cmd>]');
|
|
1758
|
-
}
|
|
1759
|
-
break;
|
|
1760
|
-
}
|
|
1761
|
-
case '/route':
|
|
1762
|
-
case '/autoroute': {
|
|
1763
|
-
if (parts[1] === 'on') {
|
|
1764
|
-
ctx.setAutoRoute(true);
|
|
1765
|
-
ctx.addMessage('system', '\u2713 Auto-routing ON - model selected based on task complexity');
|
|
1766
|
-
}
|
|
1767
|
-
else if (parts[1] === 'off') {
|
|
1768
|
-
ctx.setAutoRoute(false);
|
|
1769
|
-
ctx.addMessage('system', '\u2713 Auto-routing OFF - using fixed model');
|
|
1770
|
-
}
|
|
1771
|
-
else if (parts[1] === 'test' && parts[2]) {
|
|
1772
|
-
const testMsg = parts.slice(2).join(' ');
|
|
1773
|
-
const decision = modelRouter.routeRequest(testMsg, ctx.actualProvider);
|
|
1774
|
-
ctx.addMessage('system', `Route test: ${decision.tier} tier (${decision.complexity})\nModel: ${decision.model.model}\nReason: ${decision.reason}\nConfidence: ${Math.round(decision.confidence * 100)}%`);
|
|
1775
|
-
}
|
|
1776
|
-
else {
|
|
1777
|
-
const tiers = modelRouter.getAllTiers(ctx.actualProvider);
|
|
1778
|
-
ctx.addMessage('system', `Auto-route: ${ctx.autoRoute ? 'ON' : 'OFF'}\n\nModel tiers for ${ctx.actualProvider}:\n fast: ${tiers.fast.model}\n balanced: ${tiers.balanced.model}\n smart: ${tiers.smart.model}\n\nUsage: /route [on|off|test <message>]`);
|
|
1779
|
-
}
|
|
1780
|
-
break;
|
|
1781
|
-
}
|
|
1782
|
-
case '/summarize': {
|
|
1783
|
-
const subCmd = parts[1];
|
|
1784
|
-
if (subCmd === 'context' || !subCmd) {
|
|
1785
|
-
const msgCount = ctx.llmMessages.current.length;
|
|
1786
|
-
if (msgCount < 5) {
|
|
1787
|
-
ctx.addMessage('system', 'Not enough messages to summarize.');
|
|
1788
|
-
}
|
|
1789
|
-
else {
|
|
1790
|
-
const summary = summarization.extractKeyInfo(ctx.llmMessages.current);
|
|
1791
|
-
let info = 'Context Summary:\n\n';
|
|
1792
|
-
if (summary.topics.length)
|
|
1793
|
-
info += `**Topics:** ${summary.topics.join(', ')}\n`;
|
|
1794
|
-
if (summary.decisions.length)
|
|
1795
|
-
info += `**Decisions:**\n${summary.decisions.map((d) => ` - ${d}`).join('\n')}\n`;
|
|
1796
|
-
if (summary.actions.length)
|
|
1797
|
-
info += `**Actions:**\n${summary.actions.map((a) => ` - ${a}`).join('\n')}\n`;
|
|
1798
|
-
if (summary.codeChanges.length)
|
|
1799
|
-
info += `**Code Changes:**\n${summary.codeChanges.slice(0, 5).map((c) => ` - ${c}`).join('\n')}\n`;
|
|
1800
|
-
ctx.addMessage('system', info || 'No key information extracted.');
|
|
1801
|
-
}
|
|
1802
|
-
}
|
|
1803
|
-
else if (subCmd === 'compact') {
|
|
1804
|
-
// Summarize and compact the conversation
|
|
1805
|
-
const result = summarization.summarizeConversation(ctx.llmMessages.current, { maxTokens: 50000 });
|
|
1806
|
-
if (result.summarizedCount > 0) {
|
|
1807
|
-
ctx.llmMessages.current = result.messages;
|
|
1808
|
-
ctx.setContextTokens(ctx.estimateContextTokens());
|
|
1809
|
-
ctx.addMessage('system', `\u2713 Compacted ${result.summarizedCount} messages (${result.originalTokens} \u2192 ${result.reducedTokens} tokens)`);
|
|
1810
|
-
}
|
|
1811
|
-
else {
|
|
1812
|
-
ctx.addMessage('system', 'Context already within limits, no compaction needed.');
|
|
1813
|
-
}
|
|
1814
|
-
}
|
|
1815
|
-
else {
|
|
1816
|
-
ctx.addMessage('system', 'Usage: /summarize [context|compact]');
|
|
1817
|
-
}
|
|
1818
|
-
break;
|
|
1819
|
-
}
|
|
1820
|
-
case '/upgrade':
|
|
1821
|
-
ctx.addMessage('system', 'Checking for updates...');
|
|
1822
|
-
try {
|
|
1823
|
-
const current = getVersion();
|
|
1824
|
-
const latest = await getLatestVersion();
|
|
1825
|
-
if (!latest) {
|
|
1826
|
-
ctx.addMessage('error', 'Could not check for updates');
|
|
1827
|
-
break;
|
|
1828
|
-
}
|
|
1829
|
-
const [cMaj, cMin, cPat] = current.split('.').map(Number);
|
|
1830
|
-
const [lMaj, lMin, lPat] = latest.split('.').map(Number);
|
|
1831
|
-
const hasUpdate = lMaj > cMaj || (lMaj === cMaj && lMin > cMin) || (lMaj === cMaj && lMin === cMin && lPat > cPat);
|
|
1832
|
-
if (hasUpdate) {
|
|
1833
|
-
ctx.setLatestVersion(latest);
|
|
1834
|
-
ctx.setModalMode('upgrade');
|
|
1835
|
-
}
|
|
1836
|
-
else {
|
|
1837
|
-
ctx.addMessage('system', `You're on the latest version (v${current})`);
|
|
1838
|
-
}
|
|
1839
|
-
}
|
|
1840
|
-
catch (e) {
|
|
1841
|
-
ctx.addMessage('error', `Failed to check for updates: ${e instanceof Error ? e.message : String(e)}`);
|
|
1842
|
-
}
|
|
1843
|
-
break;
|
|
1844
|
-
case '/session':
|
|
1845
|
-
case '/sessions':
|
|
1846
|
-
if (parts[1] === 'list' || !parts[1]) {
|
|
1847
|
-
const sessions = storage.listSessions(20);
|
|
1848
|
-
if (sessions.length === 0) {
|
|
1849
|
-
ctx.addMessage('system', 'No previous sessions found.');
|
|
1850
|
-
}
|
|
1851
|
-
else {
|
|
1852
|
-
ctx.setAvailableSessions(sessions);
|
|
1853
|
-
ctx.setModalMode('sessions');
|
|
1854
|
-
}
|
|
1855
|
-
}
|
|
1856
|
-
else if (parts[1] === 'info') {
|
|
1857
|
-
const session = ctx.sessionRef.current;
|
|
1858
|
-
if (session) {
|
|
1859
|
-
const savedMessages = storage.loadMessageHistory();
|
|
1860
|
-
const savedCount = savedMessages ? savedMessages.length : 0;
|
|
1861
|
-
const ledgerTotals = ctx.ledger?.getTotals();
|
|
1862
|
-
const latestRun = ctx.ledger?.getLatestRun();
|
|
1863
|
-
const lines = [
|
|
1864
|
-
`Session: ${session.projectName}`,
|
|
1865
|
-
`ID: ${session.id}`,
|
|
1866
|
-
`Created: ${new Date(session.createdAt).toLocaleString()}`,
|
|
1867
|
-
`Messages: ${session.messageCount}`,
|
|
1868
|
-
`Saved LLM messages: ${savedCount}`,
|
|
1869
|
-
];
|
|
1870
|
-
if (ledgerTotals) {
|
|
1871
|
-
lines.push(`Iterations logged: ${ledgerTotals.iterations}`, `Failed approaches: ${ctx.ledger?.getFailedApproachCount() ?? 0}`);
|
|
1872
|
-
}
|
|
1873
|
-
if (latestRun) {
|
|
1874
|
-
lines.push(`Latest run: ${formatLedgerRun(latestRun)}`);
|
|
1875
|
-
}
|
|
1876
|
-
ctx.addMessage('system', lines.join('\n'));
|
|
1877
|
-
}
|
|
1878
|
-
else {
|
|
1879
|
-
ctx.addMessage('system', 'No active session.');
|
|
1880
|
-
}
|
|
1881
|
-
}
|
|
1882
|
-
else if (parts[1] === 'fork') {
|
|
1883
|
-
const session = ctx.sessionRef.current;
|
|
1884
|
-
if (!session) {
|
|
1885
|
-
ctx.addMessage('error', 'No active session to fork.');
|
|
1886
|
-
}
|
|
1887
|
-
else {
|
|
1888
|
-
// Save current messages before forking
|
|
1889
|
-
storage.saveMessageHistory(ctx.llmMessages.current);
|
|
1890
|
-
if (ctx.ledger) {
|
|
1891
|
-
storage.saveIterationLedger(ctx.ledger);
|
|
1892
|
-
}
|
|
1893
|
-
const forked = storage.forkSession(session.projectPath);
|
|
1894
|
-
if (forked) {
|
|
1895
|
-
ctx.sessionRef.current = forked;
|
|
1896
|
-
ctx.addMessage('system', `Forked session: ${forked.id}\nMessages carried over: ${ctx.llmMessages.current.length}\n\nYou are now on the forked session. The original session is preserved.`);
|
|
1897
|
-
}
|
|
1898
|
-
else {
|
|
1899
|
-
ctx.addMessage('error', 'Failed to fork session. No saved messages found.');
|
|
1900
|
-
}
|
|
1901
|
-
}
|
|
1902
|
-
}
|
|
1903
|
-
else if (parts[1] === 'save') {
|
|
1904
|
-
storage.saveMessageHistory(ctx.llmMessages.current);
|
|
1905
|
-
if (ctx.ledger) {
|
|
1906
|
-
storage.saveIterationLedger(ctx.ledger);
|
|
1907
|
-
}
|
|
1908
|
-
ctx.addMessage('system', `Saved ${ctx.llmMessages.current.length} LLM messages and current log state to session.`);
|
|
1909
|
-
}
|
|
1910
|
-
else {
|
|
1911
|
-
ctx.addMessage('system', 'Usage: /session [list|info|fork|save] or just /sessions');
|
|
1912
|
-
}
|
|
1913
|
-
break;
|
|
1914
|
-
case '/log': {
|
|
1915
|
-
if (!ctx.ledger) {
|
|
1916
|
-
ctx.addMessage('system', 'No session log available.');
|
|
1917
|
-
break;
|
|
1918
|
-
}
|
|
1919
|
-
const subCmd = parts[1] || 'summary';
|
|
1920
|
-
if (subCmd === 'summary') {
|
|
1921
|
-
const totals = ctx.ledger.getTotals();
|
|
1922
|
-
const runs = ctx.ledger.getRuns(5);
|
|
1923
|
-
const allFailures = ctx.ledger.getFailedApproaches();
|
|
1924
|
-
const failures = allFailures.slice(-5);
|
|
1925
|
-
const lines = [
|
|
1926
|
-
'Session Log',
|
|
1927
|
-
`Iterations: ${totals.iterations}`,
|
|
1928
|
-
`Failed approaches: ${ctx.ledger.getFailedApproachCount()}`,
|
|
1929
|
-
`Tokens: ${totals.totalTokens}`,
|
|
1930
|
-
`Cost: $${totals.totalCost.toFixed(4)}`,
|
|
1931
|
-
`Duration: ${formatLedgerDuration(totals.totalDurationMs)}`,
|
|
1932
|
-
];
|
|
1933
|
-
if (runs.length > 0) {
|
|
1934
|
-
lines.push('', 'Recent runs:');
|
|
1935
|
-
for (const run of runs) {
|
|
1936
|
-
lines.push(` - ${formatLedgerRun(run)}`);
|
|
1937
|
-
}
|
|
1938
|
-
}
|
|
1939
|
-
if (failures.length > 0) {
|
|
1940
|
-
lines.push('', 'Recent failures:');
|
|
1941
|
-
for (const failure of failures) {
|
|
1942
|
-
lines.push(` - #${failure.iteration} ${failure.description} — ${failure.reason}`);
|
|
1943
|
-
}
|
|
1944
|
-
}
|
|
1945
|
-
ctx.addMessage('system', lines.join('\n'));
|
|
1946
|
-
}
|
|
1947
|
-
else if (subCmd === 'tail') {
|
|
1948
|
-
const limit = parts[2] ? parseInt(parts[2], 10) : 10;
|
|
1949
|
-
if (isNaN(limit) || limit <= 0 || limit > 100) {
|
|
1950
|
-
ctx.addMessage('error', 'Usage: /log tail [1-100]');
|
|
1951
|
-
break;
|
|
1952
|
-
}
|
|
1953
|
-
const entries = ctx.ledger.getEntries().slice(-limit);
|
|
1954
|
-
if (entries.length === 0) {
|
|
1955
|
-
ctx.addMessage('system', 'No logged iterations yet.');
|
|
1956
|
-
break;
|
|
1957
|
-
}
|
|
1958
|
-
const lines = ['Recent iterations:'];
|
|
1959
|
-
for (const entry of entries) {
|
|
1960
|
-
const actions = entry.actions.length > 0
|
|
1961
|
-
? entry.actions.map(action => `${action.tool}(${action.args})${action.result === 'error' ? ' FAILED' : action.result === 'blocked' ? ' BLOCKED' : ''}`).join(', ')
|
|
1962
|
-
: 'no tool actions';
|
|
1963
|
-
lines.push(` #${entry.iteration} [${entry.outcome}] ${formatLedgerDuration(entry.durationMs)} — ${actions}`);
|
|
1964
|
-
}
|
|
1965
|
-
ctx.addMessage('system', lines.join('\n'));
|
|
1966
|
-
}
|
|
1967
|
-
else if (subCmd === 'failures') {
|
|
1968
|
-
const failures = ctx.ledger.getFailedApproaches();
|
|
1969
|
-
if (failures.length === 0) {
|
|
1970
|
-
ctx.addMessage('system', 'No failed approaches recorded.');
|
|
1971
|
-
break;
|
|
1972
|
-
}
|
|
1973
|
-
const lines = ['Failed approaches:'];
|
|
1974
|
-
for (const failure of failures.slice(-10)) {
|
|
1975
|
-
lines.push(` - #${failure.iteration} ${failure.description} — ${failure.reason}`);
|
|
1976
|
-
}
|
|
1977
|
-
ctx.addMessage('system', lines.join('\n'));
|
|
1978
|
-
}
|
|
1979
|
-
else if (subCmd === 'reset') {
|
|
1980
|
-
ctx.ledger.reset();
|
|
1981
|
-
ctx.addMessage('system', 'Session log reset.');
|
|
1982
|
-
}
|
|
1983
|
-
else {
|
|
1984
|
-
ctx.addMessage('system', 'Usage: /log [summary|tail [N]|failures|reset]');
|
|
1985
|
-
}
|
|
1986
|
-
break;
|
|
1987
|
-
}
|
|
1988
|
-
case '/todo': {
|
|
1989
|
-
const subCommand = parts[1];
|
|
1990
|
-
if (subCommand === 'add' && parts.length > 2) {
|
|
1991
|
-
const content = parts.slice(2).join(' ');
|
|
1992
|
-
const isGlobal = content.includes('--global');
|
|
1993
|
-
const isHigh = content.includes('--priority') && content.includes('high');
|
|
1994
|
-
const cleanContent = content.replace(/--global|--priority\s*\w+/g, '').trim();
|
|
1995
|
-
const todo = storage.addTodo(cleanContent, {
|
|
1996
|
-
global: isGlobal,
|
|
1997
|
-
priority: isHigh ? 'high' : 'normal',
|
|
1998
|
-
});
|
|
1999
|
-
ctx.addMessage('system', `\u2713 TODO added (#${todo.id.slice(-4)}${isGlobal ? ', global' : ''})`);
|
|
2000
|
-
}
|
|
2001
|
-
else if (subCommand === 'done' && parts[2]) {
|
|
2002
|
-
const id = parts[2];
|
|
2003
|
-
const todos = [...storage.getSessionTodos(), ...storage.getGlobalTodos()];
|
|
2004
|
-
const todo = todos.find(t => t.id.endsWith(id) || t.id === id);
|
|
2005
|
-
if (todo) {
|
|
2006
|
-
storage.updateTodo(todo.id, { status: 'completed' });
|
|
2007
|
-
ctx.addMessage('system', `\u2713 TODO #${id} marked done`);
|
|
2008
|
-
}
|
|
2009
|
-
else {
|
|
2010
|
-
ctx.addMessage('error', `TODO #${id} not found`);
|
|
2011
|
-
}
|
|
2012
|
-
}
|
|
2013
|
-
else if (subCommand === 'list' || !subCommand) {
|
|
2014
|
-
const sessionTodos = storage.getSessionTodos();
|
|
2015
|
-
const globalTodos = storage.getGlobalTodos();
|
|
2016
|
-
const pending = [...sessionTodos, ...globalTodos].filter(t => t.status !== 'completed');
|
|
2017
|
-
const completed = [...sessionTodos, ...globalTodos].filter(t => t.status === 'completed').slice(-3);
|
|
2018
|
-
if (pending.length === 0 && completed.length === 0) {
|
|
2019
|
-
ctx.addMessage('system', 'No TODOs. Use /todo add <task> to create one.');
|
|
2020
|
-
}
|
|
2021
|
-
else {
|
|
2022
|
-
let output = '\u{1F4CB} TODOs:\n';
|
|
2023
|
-
if (pending.length > 0) {
|
|
2024
|
-
output += pending.map(t => ` ${t.priority === 'high' ? '!' : '\u25A1'} #${t.id.slice(-4)} ${t.content}`).join('\n');
|
|
2025
|
-
}
|
|
2026
|
-
if (completed.length > 0) {
|
|
2027
|
-
output += '\n\nCompleted:\n' + completed.map(t => ` \u2713 #${t.id.slice(-4)} ${t.content}`).join('\n');
|
|
2028
|
-
}
|
|
2029
|
-
ctx.addMessage('system', output);
|
|
2030
|
-
}
|
|
2031
|
-
}
|
|
2032
|
-
else if (subCommand === 'work' && parts[2]) {
|
|
2033
|
-
const id = parts[2];
|
|
2034
|
-
const todos = [...storage.getSessionTodos(), ...storage.getGlobalTodos()];
|
|
2035
|
-
const todo = todos.find(t => t.id.endsWith(id) || t.id === id);
|
|
2036
|
-
if (todo) {
|
|
2037
|
-
storage.setActiveTodo(todo.id);
|
|
2038
|
-
storage.updateTodo(todo.id, { status: 'in_progress' });
|
|
2039
|
-
ctx.addMessage('system', `\u2713 Working on: ${todo.content}\n\nTip: I'll help you complete this task. Describe what you need.`);
|
|
2040
|
-
}
|
|
2041
|
-
else {
|
|
2042
|
-
ctx.addMessage('error', `TODO #${id} not found`);
|
|
2043
|
-
}
|
|
2044
|
-
}
|
|
2045
|
-
else if (subCommand === 'clear') {
|
|
2046
|
-
storage.setActiveTodo(null);
|
|
2047
|
-
ctx.addMessage('system', '\u2713 Active TODO cleared');
|
|
2048
|
-
}
|
|
2049
|
-
else {
|
|
2050
|
-
ctx.addMessage('system', 'Usage: /todo [add <task>|done <id>|work <id>|clear|list]');
|
|
2051
|
-
}
|
|
2052
|
-
break;
|
|
2053
|
-
}
|
|
2054
|
-
case '/plans': {
|
|
2055
|
-
const subCommand = parts[1];
|
|
2056
|
-
if (subCommand === 'list' || !subCommand) {
|
|
2057
|
-
const plans = storage.getPlans();
|
|
2058
|
-
if (plans.length === 0) {
|
|
2059
|
-
ctx.addMessage('system', 'No plans yet. Plans are created in hybrid mode.');
|
|
2060
|
-
}
|
|
2061
|
-
else {
|
|
2062
|
-
const list = plans.slice(0, 5).map((p) => `${p.status === 'completed' ? '\u2713' : '\u25CB'} ${p.id.slice(-4)}: ${p.title}`).join('\n');
|
|
2063
|
-
ctx.addMessage('system', `\u{1F4CB} Plans:\n${list}`);
|
|
2064
|
-
}
|
|
2065
|
-
}
|
|
2066
|
-
else if (subCommand === 'view' && parts[2]) {
|
|
2067
|
-
const plans = storage.getPlans();
|
|
2068
|
-
const plan = plans.find((p) => p.id.endsWith(parts[2]) || p.id === parts[2]);
|
|
2069
|
-
if (plan) {
|
|
2070
|
-
const phases = plan.phases.map((ph) => ` ${ph.status === 'completed' ? '\u2713' : '\u25CB'} ${ph.name} (${ph.risk} risk)`).join('\n');
|
|
2071
|
-
ctx.addMessage('system', `Plan: ${plan.title}\nStatus: ${plan.status}\n\nPhases:\n${phases}`);
|
|
2072
|
-
}
|
|
2073
|
-
else {
|
|
2074
|
-
ctx.addMessage('error', `Plan #${parts[2]} not found`);
|
|
2075
|
-
}
|
|
2076
|
-
}
|
|
2077
|
-
else if (subCommand === 'rerun' && parts[2]) {
|
|
2078
|
-
const plans = storage.getPlans();
|
|
2079
|
-
const plan = plans.find((p) => p.id.endsWith(parts[2]) || p.id === parts[2]);
|
|
2080
|
-
if (plan) {
|
|
2081
|
-
// Reset plan status and activate
|
|
2082
|
-
plan.status = 'in_progress';
|
|
2083
|
-
plan.phases.forEach((ph) => ph.status = 'pending');
|
|
2084
|
-
storage.savePlan(plan);
|
|
2085
|
-
storage.setActivePlan(plan);
|
|
2086
|
-
// Generate prompt for re-execution
|
|
2087
|
-
const phaseList = plan.phases.map((ph) => `- ${ph.name}`).join('\n');
|
|
2088
|
-
const prompt = `Please help me execute this plan:\n\n**${plan.title}**\n\nPhases:\n${phaseList}\n\nStart with the first phase.`;
|
|
2089
|
-
ctx.setInput(prompt);
|
|
2090
|
-
ctx.addMessage('system', `\u2713 Plan loaded: ${plan.title}\nPress Enter to start execution.`);
|
|
2091
|
-
}
|
|
2092
|
-
else {
|
|
2093
|
-
ctx.addMessage('error', `Plan #${parts[2]} not found`);
|
|
2094
|
-
}
|
|
2095
|
-
}
|
|
2096
|
-
else {
|
|
2097
|
-
ctx.addMessage('system', 'Usage: /plans [list|view <id>|rerun <id>]');
|
|
2098
|
-
}
|
|
2099
|
-
break;
|
|
2100
|
-
}
|
|
2101
|
-
case '/history': {
|
|
2102
|
-
const subCommand = parts[1];
|
|
2103
|
-
if (subCommand === 'search' && parts[2]) {
|
|
2104
|
-
const query = parts.slice(2).join(' ');
|
|
2105
|
-
const results = storage.searchChatHistory(query);
|
|
2106
|
-
if (results.length === 0) {
|
|
2107
|
-
ctx.addMessage('system', `No matches for "${query}"`);
|
|
2108
|
-
}
|
|
2109
|
-
else {
|
|
2110
|
-
const list = results.slice(-5).map((m) => `${new Date(m.timestamp).toLocaleTimeString()}: ${m.content.substring(0, 60)}...`).join('\n');
|
|
2111
|
-
ctx.addMessage('system', `\u{1F50D} Found ${results.length} matches:\n${list}`);
|
|
2112
|
-
}
|
|
2113
|
-
}
|
|
2114
|
-
else if (subCommand === 'clear') {
|
|
2115
|
-
ctx.addMessage('system', 'History is preserved per session. Start a new session for fresh history.');
|
|
2116
|
-
}
|
|
2117
|
-
else {
|
|
2118
|
-
const history = storage.getChatHistory(5);
|
|
2119
|
-
if (history.length === 0) {
|
|
2120
|
-
ctx.addMessage('system', 'No chat history yet.');
|
|
2121
|
-
}
|
|
2122
|
-
else {
|
|
2123
|
-
const list = history.map((m) => `${m.role}: ${m.content.substring(0, 50)}...`).join('\n');
|
|
2124
|
-
ctx.addMessage('system', `Recent history:\n${list}\n\nUse /history search <query> to search.`);
|
|
2125
|
-
}
|
|
2126
|
-
}
|
|
2127
|
-
break;
|
|
2128
|
-
}
|
|
2129
|
-
case '/context': {
|
|
2130
|
-
const subCommand = parts[1];
|
|
2131
|
-
if (subCommand === 'load') {
|
|
2132
|
-
const limit = parseInt(parts[2]) || 20;
|
|
2133
|
-
const history = storage.getChatHistory(limit);
|
|
2134
|
-
if (history.length > 0) {
|
|
2135
|
-
// Load history into LLM context
|
|
2136
|
-
for (const msg of history) {
|
|
2137
|
-
if (msg.role === 'user' || msg.role === 'assistant') {
|
|
2138
|
-
ctx.llmMessages.current.push({
|
|
2139
|
-
role: msg.role,
|
|
2140
|
-
content: msg.content,
|
|
2141
|
-
});
|
|
2142
|
-
}
|
|
2143
|
-
}
|
|
2144
|
-
ctx.addMessage('system', `\u2713 Loaded ${history.length} messages into context`);
|
|
2145
|
-
}
|
|
2146
|
-
else {
|
|
2147
|
-
ctx.addMessage('system', 'No history to load.');
|
|
2148
|
-
}
|
|
2149
|
-
}
|
|
2150
|
-
else if (subCommand === 'summary' || !subCommand) {
|
|
2151
|
-
// Enhanced context summary with model limits
|
|
2152
|
-
const msgCount = ctx.llmMessages.current.length;
|
|
2153
|
-
const estTokens = ctx.estimateContextTokens();
|
|
2154
|
-
const modelLimit = getModelContextLimit(ctx.actualProvider, ctx.actualModel);
|
|
2155
|
-
const percentage = Math.round((estTokens / modelLimit) * 100);
|
|
2156
|
-
const formatK = (n) => n >= 1000 ? `${Math.round(n / 1000)}K` : String(n);
|
|
2157
|
-
let status = '\u{1F7E2} Healthy';
|
|
2158
|
-
if (percentage > 90)
|
|
2159
|
-
status = '\u{1F534} Critical';
|
|
2160
|
-
else if (percentage > 80)
|
|
2161
|
-
status = '\u{1F7E1} Warning';
|
|
2162
|
-
else if (percentage > 60)
|
|
2163
|
-
status = '\u{1F7E0} Caution';
|
|
2164
|
-
ctx.addMessage('system', `**Context Status: ${status}**
|
|
2165
|
-
|
|
2166
|
-
**Usage:** ${formatK(estTokens)} / ${formatK(modelLimit)} tokens (${percentage}%)
|
|
2167
|
-
**Messages:** ${msgCount}
|
|
2168
|
-
**Provider:** ${ctx.actualProvider}
|
|
2169
|
-
**Model:** ${ctx.actualModel}
|
|
2170
|
-
|
|
2171
|
-
**Commands:**
|
|
2172
|
-
/summarize compact - Auto-compress context
|
|
2173
|
-
/context load [n] - Load n messages from history
|
|
2174
|
-
/clear - Start fresh`);
|
|
2175
|
-
}
|
|
2176
|
-
else {
|
|
2177
|
-
ctx.addMessage('system', 'Usage: /context [load [n]|summary]\n\nShow context status or load history.');
|
|
2178
|
-
}
|
|
2179
|
-
break;
|
|
2180
|
-
}
|
|
2181
|
-
case '/scope':
|
|
2182
|
-
case '/dirs': {
|
|
2183
|
-
const subCmd = parts[1];
|
|
2184
|
-
if (subCmd === 'details' || subCmd === 'full') {
|
|
820
|
+
else if (subCmd === 'details' || subCmd === 'full') {
|
|
2185
821
|
ctx.addMessage('system', getScopeDetails());
|
|
2186
822
|
}
|
|
2187
823
|
else if (subCmd === 'reset') {
|
|
2188
824
|
resetScope(process.cwd());
|
|
2189
|
-
ctx.addMessage('system', '
|
|
825
|
+
ctx.addMessage('system', '✓ Scope reset to current directory only');
|
|
2190
826
|
}
|
|
2191
827
|
else {
|
|
2192
828
|
ctx.addMessage('system', getScopeSummary());
|
|
2193
829
|
}
|
|
2194
830
|
break;
|
|
2195
831
|
}
|
|
2196
|
-
case '/
|
|
2197
|
-
const dirPath = parts.slice(1).join(' ').replace(/^["']|["']$/g, '');
|
|
2198
|
-
if (!dirPath) {
|
|
2199
|
-
ctx.addMessage('system', 'Usage: /add-dir <path>\n\nAdd a directory to the allowed scope.\nThe agent can only access files within scope.');
|
|
2200
|
-
}
|
|
2201
|
-
else {
|
|
2202
|
-
const result = addToScope(dirPath);
|
|
2203
|
-
if (result.success) {
|
|
2204
|
-
ctx.addMessage('system', `\u2713 ${result.message}`);
|
|
2205
|
-
}
|
|
2206
|
-
else {
|
|
2207
|
-
ctx.addMessage('error', result.message);
|
|
2208
|
-
}
|
|
2209
|
-
}
|
|
2210
|
-
break;
|
|
2211
|
-
}
|
|
2212
|
-
case '/remove-dir': {
|
|
2213
|
-
const dirPath = parts.slice(1).join(' ').replace(/^["']|["']$/g, '');
|
|
2214
|
-
if (!dirPath) {
|
|
2215
|
-
ctx.addMessage('system', 'Usage: /remove-dir <path>\n\nRemove a directory from the allowed scope.');
|
|
2216
|
-
}
|
|
2217
|
-
else {
|
|
2218
|
-
const result = removeFromScope(dirPath);
|
|
2219
|
-
if (result.success) {
|
|
2220
|
-
ctx.addMessage('system', `\u2713 ${result.message}`);
|
|
2221
|
-
}
|
|
2222
|
-
else {
|
|
2223
|
-
ctx.addMessage('error', result.message);
|
|
2224
|
-
}
|
|
2225
|
-
}
|
|
2226
|
-
break;
|
|
2227
|
-
}
|
|
2228
|
-
case '/trust':
|
|
2229
|
-
case '/untrust': {
|
|
832
|
+
case '/trust': {
|
|
2230
833
|
const { checkTrust, trustProject, untrustProject, listTrustedProjects, removeFromRegistry } = await import('../trust.js');
|
|
2231
|
-
const trustSubCmd =
|
|
834
|
+
const trustSubCmd = parts[1] || 'status';
|
|
2232
835
|
if (trustSubCmd === 'status') {
|
|
2233
836
|
const trust = checkTrust(process.cwd());
|
|
2234
837
|
ctx.addMessage('system', `Trust: ${trust.trusted ? '✓ Trusted' : '✗ Untrusted'}\n${trust.reason}${trust.changed ? '\n⚠️ CALLIOPE.md has changed since trust was granted' : ''}`);
|
|
@@ -2238,241 +841,76 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
2238
841
|
trustProject(dir, parts.slice(3).join(' ') || undefined);
|
|
2239
842
|
ctx.addMessage('system', `✓ Trusted: ${dir}`);
|
|
2240
843
|
}
|
|
2241
|
-
else if (trustSubCmd === 'remove' || trustSubCmd === 'no') {
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
}
|
|
2246
|
-
else {
|
|
2247
|
-
untrustProject(dir);
|
|
2248
|
-
}
|
|
2249
|
-
ctx.addMessage('system', `✗ Untrusted: ${command === '/untrust' ? (parts[1] || process.cwd()) : dir}`);
|
|
2250
|
-
}
|
|
2251
|
-
else if (trustSubCmd === 'list') {
|
|
2252
|
-
const projects = listTrustedProjects();
|
|
2253
|
-
if (projects.length === 0) {
|
|
2254
|
-
ctx.addMessage('system', 'No projects in trust registry.');
|
|
2255
|
-
}
|
|
2256
|
-
else {
|
|
2257
|
-
const list = projects.map(p => ` ${p.entry.trusted ? '✓' : '✗'} ${p.path}${p.entry.note ? ` (${p.entry.note})` : ''}`).join('\n');
|
|
2258
|
-
ctx.addMessage('system', `Trust registry:\n${list}`);
|
|
2259
|
-
}
|
|
2260
|
-
}
|
|
2261
|
-
else if (trustSubCmd === 'clear') {
|
|
2262
|
-
const dir = parts[2] || process.cwd();
|
|
2263
|
-
removeFromRegistry(dir);
|
|
2264
|
-
ctx.addMessage('system', `Removed from trust registry: ${dir}`);
|
|
2265
|
-
}
|
|
2266
|
-
else {
|
|
2267
|
-
ctx.addMessage('system', 'Usage: /trust [status|add|remove|list|clear]\n /trust add [path] - trust a project\n /trust remove [path] - untrust a project\n /untrust [path] - shortcut for /trust remove');
|
|
2268
|
-
}
|
|
2269
|
-
break;
|
|
2270
|
-
}
|
|
2271
|
-
case '/template':
|
|
2272
|
-
case '/t': {
|
|
2273
|
-
const subCmd = parts[1];
|
|
2274
|
-
if (subCmd === 'list' || !subCmd) {
|
|
2275
|
-
if (ctx.templates.length === 0) {
|
|
2276
|
-
ctx.addMessage('system', 'No templates saved.\n\nUsage:\n /template save <name> <prompt>\n /template use <name>\n /template delete <name>');
|
|
2277
|
-
}
|
|
2278
|
-
else {
|
|
2279
|
-
const list = ctx.templates.map((t, i) => ` ${i + 1}. ${t.name}: "${t.prompt.substring(0, 50)}${t.prompt.length > 50 ? '...' : ''}"`).join('\n');
|
|
2280
|
-
ctx.addMessage('system', `Templates:\n${list}`);
|
|
2281
|
-
}
|
|
2282
|
-
}
|
|
2283
|
-
else if (subCmd === 'save' && parts[2]) {
|
|
2284
|
-
const name = parts[2];
|
|
2285
|
-
const prompt = parts.slice(3).join(' ').replace(/^["']|["']$/g, '');
|
|
2286
|
-
if (!prompt) {
|
|
2287
|
-
ctx.addMessage('error', 'Usage: /template save <name> "<prompt>"');
|
|
2288
|
-
}
|
|
2289
|
-
else {
|
|
2290
|
-
storage.saveTemplate(name, prompt);
|
|
2291
|
-
ctx.setTemplates(prev => {
|
|
2292
|
-
const filtered = prev.filter(t => t.name !== name);
|
|
2293
|
-
return [...filtered, { name, prompt, createdAt: new Date() }];
|
|
2294
|
-
});
|
|
2295
|
-
ctx.addMessage('system', `\u2713 Template saved: ${name}`);
|
|
2296
|
-
}
|
|
844
|
+
else if (trustSubCmd === 'remove' || trustSubCmd === 'no') {
|
|
845
|
+
// Absorbs the old /untrust
|
|
846
|
+
const dir = parts[2] || process.cwd();
|
|
847
|
+
untrustProject(dir);
|
|
848
|
+
ctx.addMessage('system', `✗ Untrusted: ${dir}`);
|
|
2297
849
|
}
|
|
2298
|
-
else if (
|
|
2299
|
-
const
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
ctx.setInput(template.prompt);
|
|
2303
|
-
ctx.addMessage('system', `\u2713 Template loaded: ${name} (press Enter to send)`);
|
|
850
|
+
else if (trustSubCmd === 'list') {
|
|
851
|
+
const projects = listTrustedProjects();
|
|
852
|
+
if (projects.length === 0) {
|
|
853
|
+
ctx.addMessage('system', 'No projects in trust registry.');
|
|
2304
854
|
}
|
|
2305
855
|
else {
|
|
2306
|
-
|
|
856
|
+
const list = projects.map(p => ` ${p.entry.trusted ? '✓' : '✗'} ${p.path}${p.entry.note ? ` (${p.entry.note})` : ''}`).join('\n');
|
|
857
|
+
ctx.addMessage('system', `Trust registry:\n${list}`);
|
|
2307
858
|
}
|
|
2308
859
|
}
|
|
2309
|
-
else if (
|
|
2310
|
-
const
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
storage.deleteTemplate(name);
|
|
2314
|
-
ctx.setTemplates(prev => prev.filter(t => t.name !== name));
|
|
2315
|
-
ctx.addMessage('system', `\u2713 Template deleted: ${name}`);
|
|
2316
|
-
}
|
|
2317
|
-
else {
|
|
2318
|
-
ctx.addMessage('error', `Template not found: ${name}`);
|
|
2319
|
-
}
|
|
860
|
+
else if (trustSubCmd === 'clear') {
|
|
861
|
+
const dir = parts[2] || process.cwd();
|
|
862
|
+
removeFromRegistry(dir);
|
|
863
|
+
ctx.addMessage('system', `Removed from trust registry: ${dir}`);
|
|
2320
864
|
}
|
|
2321
865
|
else {
|
|
2322
|
-
ctx.addMessage('system', 'Usage: /
|
|
866
|
+
ctx.addMessage('system', 'Usage: /trust [status|add|remove|list|clear]\n /trust add [path] - trust a project\n /trust remove [path] - untrust a project');
|
|
2323
867
|
}
|
|
2324
868
|
break;
|
|
2325
869
|
}
|
|
2326
|
-
case '/cost':
|
|
2327
|
-
case '/costs': {
|
|
870
|
+
case '/cost': {
|
|
2328
871
|
const subCmd = parts[1];
|
|
2329
872
|
if (subCmd === 'reset') {
|
|
2330
873
|
storage.resetCosts();
|
|
2331
|
-
ctx.addMessage('system', '
|
|
874
|
+
ctx.addMessage('system', '✓ Cost tracking reset');
|
|
2332
875
|
}
|
|
2333
876
|
else {
|
|
2334
877
|
ctx.addMessage('system', storage.getCostSummary());
|
|
2335
878
|
}
|
|
2336
879
|
break;
|
|
2337
880
|
}
|
|
2338
|
-
case '/
|
|
2339
|
-
case '/bm': {
|
|
881
|
+
case '/compact': {
|
|
2340
882
|
const subCmd = parts[1];
|
|
2341
|
-
if (
|
|
2342
|
-
|
|
2343
|
-
if (
|
|
2344
|
-
ctx.addMessage('system', '
|
|
2345
|
-
}
|
|
2346
|
-
else {
|
|
2347
|
-
const list = ctx.bookmarks.map((b, i) => ` ${i + 1}. \u{1F516} ${b.name} (message #${b.messageIndex})`).join('\n');
|
|
2348
|
-
ctx.addMessage('system', `Bookmarks:\n${list}\n\nUse /bookmark goto <number> to jump.`);
|
|
2349
|
-
}
|
|
2350
|
-
}
|
|
2351
|
-
else if (subCmd === 'goto' && parts[2]) {
|
|
2352
|
-
const idx = parseInt(parts[2]) - 1;
|
|
2353
|
-
if (idx >= 0 && idx < ctx.bookmarks.length) {
|
|
2354
|
-
const bm = ctx.bookmarks[idx];
|
|
2355
|
-
// Save current state for undo
|
|
2356
|
-
ctx.saveUndoState();
|
|
2357
|
-
// Restore to bookmark point
|
|
2358
|
-
ctx.setMessages(ctx.messages.slice(0, bm.messageIndex + 1));
|
|
2359
|
-
ctx.llmMessages.current = ctx.llmMessages.current.slice(0, bm.llmMessageIndex + 1);
|
|
2360
|
-
ctx.setContextTokens(ctx.estimateContextTokens());
|
|
2361
|
-
ctx.addMessage('system', `\u2713 Jumped to bookmark: ${bm.name}`);
|
|
2362
|
-
}
|
|
2363
|
-
else {
|
|
2364
|
-
ctx.addMessage('error', `Invalid bookmark number. Use /bookmark list to see available.`);
|
|
2365
|
-
}
|
|
2366
|
-
}
|
|
2367
|
-
else if (subCmd === 'delete' && parts[2]) {
|
|
2368
|
-
const idx = parseInt(parts[2]) - 1;
|
|
2369
|
-
if (idx >= 0 && idx < ctx.bookmarks.length) {
|
|
2370
|
-
const removed = ctx.bookmarks[idx];
|
|
2371
|
-
ctx.setBookmarks(prev => prev.filter((_, i) => i !== idx));
|
|
2372
|
-
ctx.addMessage('system', `\u2713 Deleted bookmark: ${removed.name}`);
|
|
883
|
+
if (subCmd === 'status') {
|
|
884
|
+
const msgCount = ctx.llmMessages.current.length;
|
|
885
|
+
if (msgCount < 5) {
|
|
886
|
+
ctx.addMessage('system', 'Not enough messages to summarize.');
|
|
2373
887
|
}
|
|
2374
888
|
else {
|
|
2375
|
-
|
|
889
|
+
const summary = summarization.extractKeyInfo(ctx.llmMessages.current);
|
|
890
|
+
let info = 'Context Summary:\n\n';
|
|
891
|
+
if (summary.topics.length)
|
|
892
|
+
info += `**Topics:** ${summary.topics.join(', ')}\n`;
|
|
893
|
+
if (summary.decisions.length)
|
|
894
|
+
info += `**Decisions:**\n${summary.decisions.map((d) => ` - ${d}`).join('\n')}\n`;
|
|
895
|
+
if (summary.actions.length)
|
|
896
|
+
info += `**Actions:**\n${summary.actions.map((a) => ` - ${a}`).join('\n')}\n`;
|
|
897
|
+
if (summary.codeChanges.length)
|
|
898
|
+
info += `**Code Changes:**\n${summary.codeChanges.slice(0, 5).map((c) => ` - ${c}`).join('\n')}\n`;
|
|
899
|
+
ctx.addMessage('system', info || 'No key information extracted.');
|
|
2376
900
|
}
|
|
2377
901
|
}
|
|
2378
902
|
else {
|
|
2379
|
-
//
|
|
2380
|
-
const
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
llmMessageIndex: ctx.llmMessages.current.length - 1,
|
|
2386
|
-
timestamp: new Date(),
|
|
2387
|
-
};
|
|
2388
|
-
ctx.setBookmarks(prev => [...prev, bm]);
|
|
2389
|
-
ctx.addMessage('system', `\u{1F516} Bookmark created: "${name}"`);
|
|
2390
|
-
}
|
|
2391
|
-
break;
|
|
2392
|
-
}
|
|
2393
|
-
case '/queue':
|
|
2394
|
-
case '/q': {
|
|
2395
|
-
// /q is now queue, use /exit to quit
|
|
2396
|
-
if (command === '/q' && !parts[1]) {
|
|
2397
|
-
// Just /q with no args shows queue
|
|
2398
|
-
if (ctx.queuedMessages.length === 0) {
|
|
2399
|
-
ctx.addMessage('system', 'No messages queued. Type while agent is processing to queue feedback.');
|
|
2400
|
-
}
|
|
2401
|
-
else {
|
|
2402
|
-
const list = ctx.queuedMessages.map((m, i) => ` ${i + 1}. ${m.substring(0, 60)}${m.length > 60 ? '...' : ''}`).join('\n');
|
|
2403
|
-
ctx.addMessage('system', `\u{1F4E8} Queued messages (${ctx.queuedMessages.length}):\n${list}\n\nUse /queue clear to remove all.`);
|
|
2404
|
-
}
|
|
2405
|
-
break;
|
|
2406
|
-
}
|
|
2407
|
-
const subCmd = parts[1];
|
|
2408
|
-
if (subCmd === 'clear') {
|
|
2409
|
-
const count = ctx.queuedMessages.length;
|
|
2410
|
-
ctx.setQueuedMessages([]);
|
|
2411
|
-
ctx.addMessage('system', `\u2713 Cleared ${count} queued message${count !== 1 ? 's' : ''}`);
|
|
2412
|
-
}
|
|
2413
|
-
else if (subCmd === 'show' || !subCmd) {
|
|
2414
|
-
if (ctx.queuedMessages.length === 0) {
|
|
2415
|
-
ctx.addMessage('system', 'No messages queued.');
|
|
2416
|
-
}
|
|
2417
|
-
else {
|
|
2418
|
-
const list = ctx.queuedMessages.map((m, i) => ` ${i + 1}. ${m}`).join('\n');
|
|
2419
|
-
ctx.addMessage('system', `\u{1F4E8} Queued messages:\n${list}`);
|
|
2420
|
-
}
|
|
2421
|
-
}
|
|
2422
|
-
else if (subCmd === 'flush') {
|
|
2423
|
-
// Force-process queued messages even if stuck
|
|
2424
|
-
if (ctx.queuedMessages.length === 0) {
|
|
2425
|
-
ctx.addMessage('system', 'No messages to flush.');
|
|
903
|
+
// Default: summarize and compact the conversation
|
|
904
|
+
const result = summarization.summarizeConversation(ctx.llmMessages.current, { maxTokens: 50000 });
|
|
905
|
+
if (result.summarizedCount > 0) {
|
|
906
|
+
ctx.llmMessages.current = result.messages;
|
|
907
|
+
ctx.setContextTokens(ctx.estimateContextTokens());
|
|
908
|
+
ctx.addMessage('system', `✓ Compacted ${result.summarizedCount} messages (${result.originalTokens} → ${result.reducedTokens} tokens)`);
|
|
2426
909
|
}
|
|
2427
910
|
else {
|
|
2428
|
-
|
|
2429
|
-
ctx.setQueuedMessages([]);
|
|
2430
|
-
ctx.setIsProcessing(false); // Force reset processing state
|
|
2431
|
-
ctx.setThinkingState(null);
|
|
2432
|
-
ctx.setStreamingResponse('');
|
|
2433
|
-
ctx.addMessage('system', `\u{1F504} Flushing ${queued.length} queued message(s)...`);
|
|
2434
|
-
const followUp = queued.length === 1
|
|
2435
|
-
? queued[0]
|
|
2436
|
-
: `[Multiple follow-up messages:]\n${queued.map((m, i) => `${i + 1}. ${m}`).join('\n')}`;
|
|
2437
|
-
setTimeout(() => {
|
|
2438
|
-
ctx.setIsProcessing(true);
|
|
2439
|
-
ctx.runAgent(followUp).finally(() => {
|
|
2440
|
-
ctx.setIsProcessing(false);
|
|
2441
|
-
ctx.setThinkingState(null);
|
|
2442
|
-
ctx.setStreamingResponse('');
|
|
2443
|
-
});
|
|
2444
|
-
}, 50);
|
|
911
|
+
ctx.addMessage('system', 'Context already within limits, no compaction needed.');
|
|
2445
912
|
}
|
|
2446
913
|
}
|
|
2447
|
-
else {
|
|
2448
|
-
ctx.addMessage('system', 'Usage: /queue [show|clear|flush]\n\nTip: Type while agent is processing to queue follow-up messages.');
|
|
2449
|
-
}
|
|
2450
|
-
break;
|
|
2451
|
-
}
|
|
2452
|
-
case '/flush': {
|
|
2453
|
-
// Shortcut for /queue flush - force-process queued messages
|
|
2454
|
-
if (ctx.queuedMessages.length === 0) {
|
|
2455
|
-
ctx.addMessage('system', 'No messages to flush. Use /debug to see current state.');
|
|
2456
|
-
}
|
|
2457
|
-
else {
|
|
2458
|
-
const queued = [...ctx.queuedMessages];
|
|
2459
|
-
ctx.setQueuedMessages([]);
|
|
2460
|
-
ctx.setIsProcessing(false); // Force reset processing state
|
|
2461
|
-
ctx.setThinkingState(null);
|
|
2462
|
-
ctx.setStreamingResponse('');
|
|
2463
|
-
ctx.addMessage('system', `\u{1F504} Flushing ${queued.length} queued message(s)...`);
|
|
2464
|
-
const followUp = queued.length === 1
|
|
2465
|
-
? queued[0]
|
|
2466
|
-
: `[Multiple follow-up messages:]\n${queued.map((m, i) => `${i + 1}. ${m}`).join('\n')}`;
|
|
2467
|
-
setTimeout(() => {
|
|
2468
|
-
ctx.setIsProcessing(true);
|
|
2469
|
-
ctx.runAgent(followUp).finally(() => {
|
|
2470
|
-
ctx.setIsProcessing(false);
|
|
2471
|
-
ctx.setThinkingState(null);
|
|
2472
|
-
ctx.setStreamingResponse('');
|
|
2473
|
-
});
|
|
2474
|
-
}, 50);
|
|
2475
|
-
}
|
|
2476
914
|
break;
|
|
2477
915
|
}
|
|
2478
916
|
case '/debug': {
|
|
@@ -2503,55 +941,6 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
2503
941
|
}
|
|
2504
942
|
break;
|
|
2505
943
|
}
|
|
2506
|
-
case '/unstick': {
|
|
2507
|
-
// Emergency reset of processing state
|
|
2508
|
-
ctx.setIsProcessing(false);
|
|
2509
|
-
ctx.setThinkingState(null);
|
|
2510
|
-
ctx.setStreamingResponse('');
|
|
2511
|
-
ctx.setLoopActive(false);
|
|
2512
|
-
ctx.setModalMode('none');
|
|
2513
|
-
ctx.setPendingComplexPrompt(null);
|
|
2514
|
-
// Also reset to hybrid mode if stuck in plan mode
|
|
2515
|
-
if (ctx.mode === 'plan') {
|
|
2516
|
-
ctx.setMode('hybrid');
|
|
2517
|
-
ctx.addMessage('system', '\u{1F527} Reset processing state + switched from plan to hybrid mode.');
|
|
2518
|
-
}
|
|
2519
|
-
else {
|
|
2520
|
-
ctx.addMessage('system', '\u{1F527} Reset processing state. You can now submit new messages.');
|
|
2521
|
-
}
|
|
2522
|
-
break;
|
|
2523
|
-
}
|
|
2524
|
-
case '/keys':
|
|
2525
|
-
case '/?': {
|
|
2526
|
-
// Show keybindings modal
|
|
2527
|
-
ctx.setModalMode('keys');
|
|
2528
|
-
break;
|
|
2529
|
-
}
|
|
2530
|
-
case '/work': {
|
|
2531
|
-
// Quick shortcut to enter work mode
|
|
2532
|
-
ctx.setMode('work');
|
|
2533
|
-
ctx.addMessage('system', `Mode: ${MODE_CONFIG['work'].icon} ${MODE_CONFIG['work'].label} - ${MODE_CONFIG['work'].description}`);
|
|
2534
|
-
break;
|
|
2535
|
-
}
|
|
2536
|
-
case '/plan': {
|
|
2537
|
-
// Quick shortcut to enter plan mode
|
|
2538
|
-
ctx.setMode('plan');
|
|
2539
|
-
ctx.addMessage('system', `Mode: ${MODE_CONFIG['plan'].icon} ${MODE_CONFIG['plan'].label} - ${MODE_CONFIG['plan'].description}`);
|
|
2540
|
-
break;
|
|
2541
|
-
}
|
|
2542
|
-
case '/approve': {
|
|
2543
|
-
// Approve a pending plan and start execution (#19)
|
|
2544
|
-
const approveMsg = parts.length > 1
|
|
2545
|
-
? `Plan approved with notes: ${parts.slice(1).join(' ')}. Execute it step by step, updating progress as you complete each step.`
|
|
2546
|
-
: 'Plan approved. Execute it step by step, updating progress as you complete each step.';
|
|
2547
|
-
// Switch to work mode for execution
|
|
2548
|
-
ctx.setMode('work');
|
|
2549
|
-
ctx.addMessage('system', `${MODE_CONFIG['work'].icon} Switched to work mode for plan execution`);
|
|
2550
|
-
// Send approval as user message to the agent
|
|
2551
|
-
ctx.addMessage('user', approveMsg);
|
|
2552
|
-
await ctx.runAgent(approveMsg);
|
|
2553
|
-
break;
|
|
2554
|
-
}
|
|
2555
944
|
case '/resume': {
|
|
2556
945
|
// Resume a session by loading saved LLM message history
|
|
2557
946
|
// Usage: /resume [sessionId] - resume a specific session, or current session if no ID
|
|
@@ -2591,7 +980,7 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
2591
980
|
ctx.llmMessages.current.length = 0;
|
|
2592
981
|
ctx.llmMessages.current.push({
|
|
2593
982
|
role: 'system',
|
|
2594
|
-
content: buildFullSystemPrompt(ctx
|
|
983
|
+
content: buildFullSystemPrompt(getActiveProjectDir(ctx), ctx.actualProvider),
|
|
2595
984
|
});
|
|
2596
985
|
for (const msg of history) {
|
|
2597
986
|
if (msg.role === 'user' || msg.role === 'assistant') {
|
|
@@ -2607,429 +996,22 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
2607
996
|
}
|
|
2608
997
|
break;
|
|
2609
998
|
}
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
if (!ctx.circuitBreaker) {
|
|
2617
|
-
ctx.addMessage('system', 'Circuit breakers not initialized. They activate automatically during agent runs.');
|
|
2618
|
-
break;
|
|
2619
|
-
}
|
|
2620
|
-
if (subCmd === 'status' || !subCmd) {
|
|
2621
|
-
const statuses = ctx.circuitBreaker.getStatus();
|
|
2622
|
-
const stats = ctx.circuitBreaker.getTrackingStats();
|
|
2623
|
-
const health = ctx.circuitBreaker.getHealth();
|
|
2624
|
-
let msg = `Circuit Breaker Health: ${health.toUpperCase()}\n\n`;
|
|
2625
|
-
for (const s of statuses) {
|
|
2626
|
-
const icon = s.state === 'open' ? '\u{1f534}' : s.state === 'half-open' ? '\u{1f7e1}' : '\u{1f7e2}';
|
|
2627
|
-
msg += `${icon} ${s.type}: ${s.state} (tripped ${s.tripCount}x)`;
|
|
2628
|
-
if (s.lastEvent)
|
|
2629
|
-
msg += ` - ${s.lastEvent.message}`;
|
|
2630
|
-
msg += '\n';
|
|
2631
|
-
}
|
|
2632
|
-
msg += `\nTracking: ${stats.consecutiveErrors} consecutive errors, ${stats.totalTokens.toLocaleString()} total tokens, $${stats.totalCost.toFixed(2)} cost, ${stats.idleCount} idle`;
|
|
2633
|
-
ctx.addMessage('system', msg);
|
|
2634
|
-
}
|
|
2635
|
-
else if (subCmd === 'resume') {
|
|
2636
|
-
const breakerType = parts[2];
|
|
2637
|
-
ctx.circuitBreaker.resume(breakerType);
|
|
2638
|
-
ctx.setBreakerHealth(ctx.circuitBreaker.getHealth());
|
|
2639
|
-
ctx.addMessage('system', `\u2713 Circuit breaker${breakerType ? ` "${breakerType}"` : 's'} resumed (half-open mode, 50% more generous thresholds)`);
|
|
2640
|
-
}
|
|
2641
|
-
else if (subCmd === 'reset') {
|
|
2642
|
-
const breakerType = parts[2];
|
|
2643
|
-
ctx.circuitBreaker.reset(breakerType);
|
|
2644
|
-
ctx.setBreakerHealth(ctx.circuitBreaker.getHealth());
|
|
2645
|
-
ctx.addMessage('system', `\u2713 Circuit breaker${breakerType ? ` "${breakerType}"` : 's'} reset to closed`);
|
|
2646
|
-
}
|
|
2647
|
-
else if (subCmd === 'off') {
|
|
2648
|
-
config.set('circuitBreakersEnabled', false);
|
|
2649
|
-
ctx.addMessage('system', '\u2713 Circuit breakers disabled (will take effect on next agent run)');
|
|
2650
|
-
}
|
|
2651
|
-
else if (subCmd === 'on') {
|
|
2652
|
-
config.set('circuitBreakersEnabled', true);
|
|
2653
|
-
ctx.addMessage('system', '\u2713 Circuit breakers enabled');
|
|
2654
|
-
}
|
|
2655
|
-
else {
|
|
2656
|
-
ctx.addMessage('system', `Usage: /breaker [status|resume|reset|on|off]
|
|
2657
|
-
/breaker resume [type] - Resume tripped breaker (half-open)
|
|
2658
|
-
/breaker reset [type] - Reset breaker to closed
|
|
2659
|
-
/breaker on|off - Enable/disable circuit breakers
|
|
2660
|
-
|
|
2661
|
-
Breaker types: repeated-failure, cost-runaway, infinite-loop, token-burn, stall`);
|
|
2662
|
-
}
|
|
2663
|
-
break;
|
|
2664
|
-
}
|
|
2665
|
-
// ================================================================
|
|
2666
|
-
// Smart Routing
|
|
2667
|
-
// ================================================================
|
|
2668
|
-
case '/smart': {
|
|
2669
|
-
const subCmd = parts[1];
|
|
2670
|
-
if (subCmd === 'on') {
|
|
2671
|
-
ctx.setSmartRouteActive(true);
|
|
2672
|
-
config.set('smartRoutingEnabled', true);
|
|
2673
|
-
ctx.addMessage('system', '\u2713 Smart routing ON - best model selected across all providers');
|
|
2674
|
-
}
|
|
2675
|
-
else if (subCmd === 'off') {
|
|
2676
|
-
ctx.setSmartRouteActive(false);
|
|
2677
|
-
config.set('smartRoutingEnabled', false);
|
|
2678
|
-
ctx.addMessage('system', '\u2713 Smart routing OFF - using fixed provider/model');
|
|
2679
|
-
}
|
|
2680
|
-
else if (subCmd === 'cost' && parts[2]) {
|
|
2681
|
-
const sensitivity = parseFloat(parts[2]);
|
|
2682
|
-
if (isNaN(sensitivity) || sensitivity < 0 || sensitivity > 1) {
|
|
2683
|
-
ctx.addMessage('error', 'Cost sensitivity must be between 0 (best quality) and 1 (cheapest)');
|
|
2684
|
-
}
|
|
2685
|
-
else {
|
|
2686
|
-
config.set('smartRoutingCostSensitivity', sensitivity);
|
|
2687
|
-
ctx.addMessage('system', `\u2713 Smart routing cost sensitivity set to ${sensitivity} (0=quality, 1=cost)`);
|
|
2688
|
-
}
|
|
2689
|
-
}
|
|
2690
|
-
else if (subCmd === 'test' && parts[2]) {
|
|
2691
|
-
const testMsg = parts.slice(2).join(' ');
|
|
2692
|
-
const routingConfig = ctx.smartRoutingConfig || {
|
|
2693
|
-
...getDefaultSmartRoutingConfig(),
|
|
2694
|
-
enabled: true,
|
|
2695
|
-
costSensitivity: config.get('smartRoutingCostSensitivity') ?? 0.3,
|
|
2696
|
-
};
|
|
2697
|
-
const decision = smartRoute(testMsg, routingConfig);
|
|
2698
|
-
const taskInfo = detectTaskType(testMsg);
|
|
2699
|
-
let msg = `Smart Route Test:\n`;
|
|
2700
|
-
msg += ` Task type: ${taskInfo.taskType} (${Math.round(taskInfo.confidence * 100)}% confidence)\n`;
|
|
2701
|
-
msg += ` Complexity: ${decision.complexity}\n`;
|
|
2702
|
-
msg += ` Selected: ${decision.selected.provider}/${decision.selected.model} (${decision.selected.tier} tier)\n`;
|
|
2703
|
-
msg += ` Score: ${(decision.selected.score * 100).toFixed(1)}\n`;
|
|
2704
|
-
if (decision.alternatives.length > 0) {
|
|
2705
|
-
msg += ` Alternatives:\n`;
|
|
2706
|
-
for (const alt of decision.alternatives.slice(0, 3)) {
|
|
2707
|
-
msg += ` - ${alt.provider}/${alt.model} (score: ${(alt.score * 100).toFixed(1)})\n`;
|
|
2708
|
-
}
|
|
2709
|
-
}
|
|
2710
|
-
ctx.addMessage('system', msg);
|
|
2711
|
-
}
|
|
2712
|
-
else {
|
|
2713
|
-
ctx.addMessage('system', `Smart routing: ${ctx.smartRouteActive ? 'ON' : 'OFF'}
|
|
2714
|
-
Cost sensitivity: ${config.get('smartRoutingCostSensitivity') ?? 0.3}
|
|
2715
|
-
|
|
2716
|
-
Usage: /smart [on|off|cost <0-1>|test <message>]
|
|
2717
|
-
/smart on - Enable cross-provider routing
|
|
2718
|
-
/smart off - Disable, use fixed provider
|
|
2719
|
-
/smart cost <0-1> - Set cost sensitivity (0=quality, 1=cheapest)
|
|
2720
|
-
/smart test <msg> - Test routing decision for a message`);
|
|
2721
|
-
}
|
|
2722
|
-
break;
|
|
2723
|
-
}
|
|
2724
|
-
// ================================================================
|
|
2725
|
-
// Swarm Mode
|
|
2726
|
-
// ================================================================
|
|
2727
|
-
case '/swarm':
|
|
2728
|
-
case '/council': {
|
|
2729
|
-
const subCmd = parts[1];
|
|
2730
|
-
const swarmCwd = ctx.sessionRef.current?.projectPath ?? process.cwd();
|
|
2731
|
-
if (!ctx.agtermEnabled) {
|
|
2732
|
-
ctx.addMessage('system', 'Agents mode not enabled. Start with --agents flag to use agent swarms.');
|
|
2733
|
-
break;
|
|
2734
|
-
}
|
|
2735
|
-
// /swarm coordinate — multi-agent coordination (formerly /council)
|
|
2736
|
-
if (subCmd === 'coordinate' || subCmd === 'coord') {
|
|
2737
|
-
const coordSubCmd = parts[2];
|
|
2738
|
-
if (coordSubCmd === 'templates') {
|
|
2739
|
-
const templates = Object.values(COUNCIL_TEMPLATES);
|
|
2740
|
-
let msg = 'Swarm Coordination Templates:\n';
|
|
2741
|
-
for (const t of templates) {
|
|
2742
|
-
msg += `\n ${t.name} - ${t.description}`;
|
|
2743
|
-
msg += `\n Mode: ${t.mode}, Agents: ${t.members.map(m => m.name).join(', ')}`;
|
|
2744
|
-
}
|
|
2745
|
-
ctx.addMessage('system', msg);
|
|
2746
|
-
}
|
|
2747
|
-
else if (coordSubCmd === 'status') {
|
|
2748
|
-
const sessionId = parts[3];
|
|
2749
|
-
if (sessionId) {
|
|
2750
|
-
const session = councilManager.getAllSessions().find(s => s.id.startsWith(sessionId));
|
|
2751
|
-
if (session) {
|
|
2752
|
-
let msg = councilManager.formatSessionStatus(session);
|
|
2753
|
-
if (session.status === 'completed' && session.result) {
|
|
2754
|
-
msg += `\n\nResult:\n${session.result.slice(0, 500)}${(session.result.length > 500) ? '...' : ''}`;
|
|
2755
|
-
}
|
|
2756
|
-
ctx.addMessage('system', msg);
|
|
2757
|
-
}
|
|
2758
|
-
else {
|
|
2759
|
-
ctx.addMessage('system', `Coordination session not found: ${sessionId}`);
|
|
2760
|
-
}
|
|
2761
|
-
}
|
|
2762
|
-
else {
|
|
2763
|
-
const sessions = councilManager.getAllSessions();
|
|
2764
|
-
if (sessions.length === 0) {
|
|
2765
|
-
ctx.addMessage('system', 'No coordination sessions.');
|
|
2766
|
-
}
|
|
2767
|
-
else {
|
|
2768
|
-
const lines = sessions.map(s => ` ${s.id.slice(0, 8)} [${s.config.mode}] ${s.status} - ${s.prompt.slice(0, 50)}`);
|
|
2769
|
-
ctx.addMessage('system', `Coordination Sessions:\n${lines.join('\n')}`);
|
|
2770
|
-
}
|
|
2771
|
-
}
|
|
2772
|
-
}
|
|
2773
|
-
else if (coordSubCmd === 'list') {
|
|
2774
|
-
const sessions = councilManager.getAllSessions();
|
|
2775
|
-
const stats = councilManager.getStats();
|
|
2776
|
-
let msg = `Coordination Stats: ${stats.totalSessions} total, ${stats.activeSessions} active, ${stats.completedSessions} completed, ${stats.failedSessions} failed\n`;
|
|
2777
|
-
for (const s of sessions) {
|
|
2778
|
-
msg += `\n ${s.id.slice(0, 8)} [${s.config.mode}] ${s.status} - ${s.prompt.slice(0, 60)}`;
|
|
2779
|
-
}
|
|
2780
|
-
ctx.addMessage('system', msg);
|
|
2781
|
-
}
|
|
2782
|
-
else if (coordSubCmd === 'cancel' && parts[3]) {
|
|
2783
|
-
const session = councilManager.getAllSessions().find(s => s.id.startsWith(parts[3]));
|
|
2784
|
-
if (session) {
|
|
2785
|
-
await councilManager.cancelCouncil(session.id);
|
|
2786
|
-
ctx.addMessage('system', `\u2713 Coordination ${parts[3]} cancelled.`);
|
|
2787
|
-
}
|
|
2788
|
-
else {
|
|
2789
|
-
ctx.addMessage('system', `Coordination session not found: ${parts[3]}`);
|
|
2790
|
-
}
|
|
2791
|
-
}
|
|
2792
|
-
else if (coordSubCmd && !['help'].includes(coordSubCmd)) {
|
|
2793
|
-
// /swarm coordinate <objective>
|
|
2794
|
-
const prompt = parts.slice(2).join(' ');
|
|
2795
|
-
let cleanPrompt = prompt;
|
|
2796
|
-
let template;
|
|
2797
|
-
let mode = 'competitive';
|
|
2798
|
-
const templateMatch = prompt.match(/--template\s+(\S+)/);
|
|
2799
|
-
if (templateMatch) {
|
|
2800
|
-
template = templateMatch[1];
|
|
2801
|
-
cleanPrompt = cleanPrompt.replace(templateMatch[0], '').trim();
|
|
2802
|
-
}
|
|
2803
|
-
const modeMatch = prompt.match(/--mode\s+(competitive|collaborative|consensus|overseer)/);
|
|
2804
|
-
if (modeMatch) {
|
|
2805
|
-
mode = modeMatch[1];
|
|
2806
|
-
cleanPrompt = cleanPrompt.replace(modeMatch[0], '').trim();
|
|
2807
|
-
}
|
|
2808
|
-
try {
|
|
2809
|
-
let session;
|
|
2810
|
-
if (template) {
|
|
2811
|
-
session = await councilManager.startFromTemplate(template, cleanPrompt, swarmCwd);
|
|
2812
|
-
}
|
|
2813
|
-
else {
|
|
2814
|
-
const { randomUUID } = await import('crypto');
|
|
2815
|
-
const members = [
|
|
2816
|
-
{ id: randomUUID(), name: 'Agent A', agent: 'claude', weight: 1.0 },
|
|
2817
|
-
{ id: randomUUID(), name: 'Agent B', agent: 'claude', weight: 1.0 },
|
|
2818
|
-
{ id: randomUUID(), name: 'Agent C', agent: 'claude', weight: 1.0 },
|
|
2819
|
-
];
|
|
2820
|
-
session = await councilManager.startCouncil(cleanPrompt, { mode, members }, swarmCwd);
|
|
2821
|
-
}
|
|
2822
|
-
watchAsyncLedgerRun(ctx.ledger, 'council', cleanPrompt, () => councilManager.getSession(session.id));
|
|
2823
|
-
ctx.addMessage('system', `\u2713 Swarm coordination started: ${session.id.slice(0, 8)}\nMode: ${session.config.mode}\nAgents: ${session.config.members.map(m => m.name).join(', ')}\n\nUse /swarm coord status ${session.id.slice(0, 8)} to check progress.`);
|
|
2824
|
-
}
|
|
2825
|
-
catch (err) {
|
|
2826
|
-
ctx.addMessage('error', `Failed to start coordination: ${err instanceof Error ? err.message : String(err)}`);
|
|
2827
|
-
}
|
|
2828
|
-
}
|
|
2829
|
-
else {
|
|
2830
|
-
ctx.addMessage('system', `Swarm Coordination: Multi-agent teams working toward shared objectives.
|
|
2831
|
-
|
|
2832
|
-
Modes:
|
|
2833
|
-
competitive — Agents work independently, best result wins
|
|
2834
|
-
collaborative — Pipeline: each agent builds on previous work
|
|
2835
|
-
consensus — Agents vote on approach, supermajority decides
|
|
2836
|
-
overseer — Lead decomposes, delegates, reviews, coordinates
|
|
2837
|
-
|
|
2838
|
-
Usage:
|
|
2839
|
-
/swarm coord <objective> Start with default competitive mode
|
|
2840
|
-
/swarm coord <objective> [options] Start with explicit options
|
|
2841
|
-
/swarm coord status [id] Show session status
|
|
2842
|
-
/swarm coord list List all sessions
|
|
2843
|
-
/swarm coord templates Show available templates
|
|
2844
|
-
/swarm coord cancel <id> Cancel a running session
|
|
2845
|
-
|
|
2846
|
-
Options:
|
|
2847
|
-
--template code-review|architecture|security-audit|brainstorm|debate
|
|
2848
|
-
--mode competitive|collaborative|consensus|overseer`);
|
|
2849
|
-
}
|
|
2850
|
-
}
|
|
2851
|
-
else if (subCmd === 'templates') {
|
|
2852
|
-
// Shortcut: /swarm templates → show coordination templates
|
|
2853
|
-
const templates = Object.values(COUNCIL_TEMPLATES);
|
|
2854
|
-
let msg = 'Swarm Coordination Templates:\n';
|
|
2855
|
-
for (const t of templates) {
|
|
2856
|
-
msg += `\n ${t.name} - ${t.description}`;
|
|
2857
|
-
msg += `\n Mode: ${t.mode}, Agents: ${t.members.map(m => m.name).join(', ')}`;
|
|
2858
|
-
}
|
|
2859
|
-
ctx.addMessage('system', msg);
|
|
2860
|
-
}
|
|
2861
|
-
else if (subCmd === 'start' || (subCmd && !['status', 'list', 'cancel', 'templates', 'coordinate', 'coord', 'help'].includes(subCmd))) {
|
|
2862
|
-
// /swarm start <prompt> or /swarm <prompt> — task decomposition
|
|
2863
|
-
const promptStart = subCmd === 'start' ? 2 : 1;
|
|
2864
|
-
const prompt = parts.slice(promptStart).join(' ');
|
|
2865
|
-
if (!prompt) {
|
|
2866
|
-
ctx.addMessage('system', 'Usage: /swarm <task description>\n /swarm start <task> [--strategy parallel|sequential|map-reduce|pipeline] [--aggregation concatenate|merge-dedupe|summarize|structured]');
|
|
2867
|
-
break;
|
|
2868
|
-
}
|
|
2869
|
-
let strategy = 'parallel';
|
|
2870
|
-
let aggregation = 'concatenate';
|
|
2871
|
-
let cleanPrompt = prompt;
|
|
2872
|
-
const strategyMatch = prompt.match(/--strategy\s+(parallel|sequential|map-reduce|pipeline)/);
|
|
2873
|
-
if (strategyMatch) {
|
|
2874
|
-
strategy = strategyMatch[1];
|
|
2875
|
-
cleanPrompt = cleanPrompt.replace(strategyMatch[0], '').trim();
|
|
2876
|
-
}
|
|
2877
|
-
const aggMatch = prompt.match(/--aggregation\s+(concatenate|merge-dedupe|summarize|structured)/);
|
|
2878
|
-
if (aggMatch) {
|
|
2879
|
-
aggregation = aggMatch[1];
|
|
2880
|
-
cleanPrompt = cleanPrompt.replace(aggMatch[0], '').trim();
|
|
2881
|
-
}
|
|
2882
|
-
try {
|
|
2883
|
-
const session = await swarmManager.startSwarm(cleanPrompt, { decomposition: strategy, aggregation }, swarmCwd);
|
|
2884
|
-
watchAsyncLedgerRun(ctx.ledger, 'swarm', cleanPrompt, () => swarmManager.getSession(session.id));
|
|
2885
|
-
ctx.addMessage('system', `\u2713 Swarm started: ${session.id.slice(0, 8)}\nStrategy: ${strategy} \u2192 ${aggregation}\nStatus: ${session.status}\n\nUse /swarm status ${session.id.slice(0, 8)} to check progress.`);
|
|
2886
|
-
}
|
|
2887
|
-
catch (err) {
|
|
2888
|
-
ctx.addMessage('error', `Failed to start swarm: ${err instanceof Error ? err.message : String(err)}`);
|
|
2889
|
-
}
|
|
2890
|
-
}
|
|
2891
|
-
else if (subCmd === 'status') {
|
|
2892
|
-
// Show both swarm and coordination sessions
|
|
2893
|
-
const sessionId = parts[2];
|
|
2894
|
-
if (sessionId) {
|
|
2895
|
-
// Check decomposition swarms first, then coordination
|
|
2896
|
-
const swarmSession = swarmManager.getAllSessions().find(s => s.id.startsWith(sessionId));
|
|
2897
|
-
const coordSession = councilManager.getAllSessions().find(s => s.id.startsWith(sessionId));
|
|
2898
|
-
if (swarmSession) {
|
|
2899
|
-
let msg = swarmManager.formatSessionStatus(swarmSession);
|
|
2900
|
-
if (swarmSession.status === 'completed' && swarmSession.result) {
|
|
2901
|
-
msg += `\n\nResult:\n${swarmSession.result.slice(0, 500)}${(swarmSession.result.length > 500) ? '...' : ''}`;
|
|
2902
|
-
}
|
|
2903
|
-
ctx.addMessage('system', msg);
|
|
2904
|
-
}
|
|
2905
|
-
else if (coordSession) {
|
|
2906
|
-
let msg = councilManager.formatSessionStatus(coordSession);
|
|
2907
|
-
if (coordSession.status === 'completed' && coordSession.result) {
|
|
2908
|
-
msg += `\n\nResult:\n${coordSession.result.slice(0, 500)}${(coordSession.result.length > 500) ? '...' : ''}`;
|
|
2909
|
-
}
|
|
2910
|
-
ctx.addMessage('system', msg);
|
|
2911
|
-
}
|
|
2912
|
-
else {
|
|
2913
|
-
ctx.addMessage('system', `Swarm session not found: ${sessionId}`);
|
|
2914
|
-
}
|
|
2915
|
-
}
|
|
2916
|
-
else {
|
|
2917
|
-
const swarmSessions = swarmManager.getAllSessions();
|
|
2918
|
-
const coordSessions = councilManager.getAllSessions();
|
|
2919
|
-
if (swarmSessions.length === 0 && coordSessions.length === 0) {
|
|
2920
|
-
ctx.addMessage('system', 'No swarm sessions.');
|
|
2921
|
-
}
|
|
2922
|
-
else {
|
|
2923
|
-
const lines = [];
|
|
2924
|
-
for (const s of swarmSessions) {
|
|
2925
|
-
const subtaskInfo = s.subtasks.length > 0
|
|
2926
|
-
? ` (${s.subtasks.filter(st => st.status === 'completed').length}/${s.subtasks.length} done)`
|
|
2927
|
-
: '';
|
|
2928
|
-
lines.push(` ${s.id.slice(0, 8)} [decompose] ${s.status}${subtaskInfo} - ${s.prompt.slice(0, 50)}`);
|
|
2929
|
-
}
|
|
2930
|
-
for (const s of coordSessions) {
|
|
2931
|
-
lines.push(` ${s.id.slice(0, 8)} [${s.config.mode}] ${s.status} - ${s.prompt.slice(0, 50)}`);
|
|
2932
|
-
}
|
|
2933
|
-
ctx.addMessage('system', `Swarm Sessions:\n${lines.join('\n')}`);
|
|
2934
|
-
}
|
|
2935
|
-
}
|
|
2936
|
-
}
|
|
2937
|
-
else if (subCmd === 'list') {
|
|
2938
|
-
const swarmSessions = swarmManager.getAllSessions();
|
|
2939
|
-
const swarmStats = swarmManager.getStats();
|
|
2940
|
-
const coordSessions = councilManager.getAllSessions();
|
|
2941
|
-
const coordStats = councilManager.getStats();
|
|
2942
|
-
let msg = `Decomposition: ${swarmStats.totalSessions} total, ${swarmStats.activeSessions} active, ${swarmStats.completedSessions} completed\n`;
|
|
2943
|
-
msg += `Coordination: ${coordStats.totalSessions} total, ${coordStats.activeSessions} active, ${coordStats.completedSessions} completed\n`;
|
|
2944
|
-
for (const s of swarmSessions) {
|
|
2945
|
-
msg += `\n ${s.id.slice(0, 8)} [decompose] ${s.status} - ${s.prompt.slice(0, 60)}`;
|
|
2946
|
-
}
|
|
2947
|
-
for (const s of coordSessions) {
|
|
2948
|
-
msg += `\n ${s.id.slice(0, 8)} [${s.config.mode}] ${s.status} - ${s.prompt.slice(0, 60)}`;
|
|
2949
|
-
}
|
|
2950
|
-
ctx.addMessage('system', msg);
|
|
2951
|
-
}
|
|
2952
|
-
else if (subCmd === 'cancel' && parts[2]) {
|
|
2953
|
-
const swarmSession = swarmManager.getAllSessions().find(s => s.id.startsWith(parts[2]));
|
|
2954
|
-
const coordSession = councilManager.getAllSessions().find(s => s.id.startsWith(parts[2]));
|
|
2955
|
-
if (swarmSession) {
|
|
2956
|
-
await swarmManager.cancelSwarm(swarmSession.id);
|
|
2957
|
-
ctx.addMessage('system', `\u2713 Swarm ${parts[2]} cancelled.`);
|
|
2958
|
-
}
|
|
2959
|
-
else if (coordSession) {
|
|
2960
|
-
await councilManager.cancelCouncil(coordSession.id);
|
|
2961
|
-
ctx.addMessage('system', `\u2713 Coordination ${parts[2]} cancelled.`);
|
|
2962
|
-
}
|
|
2963
|
-
else {
|
|
2964
|
-
ctx.addMessage('system', `Swarm session not found: ${parts[2]}`);
|
|
2965
|
-
}
|
|
2966
|
-
}
|
|
2967
|
-
else {
|
|
2968
|
-
ctx.addMessage('system', `Agent Swarms: Decompose tasks and coordinate multi-agent teams.
|
|
2969
|
-
|
|
2970
|
-
Decomposition (parallel workers):
|
|
2971
|
-
/swarm <task> Decompose and execute in parallel
|
|
2972
|
-
/swarm start <task> [options] Start with explicit strategy
|
|
2973
|
-
|
|
2974
|
-
Coordination (multi-agent teamwork):
|
|
2975
|
-
/swarm coord <objective> Start coordinated agents
|
|
2976
|
-
/swarm coord <objective> [options] Start with explicit mode
|
|
2977
|
-
/swarm coord templates Show coordination templates
|
|
2978
|
-
|
|
2979
|
-
Management:
|
|
2980
|
-
/swarm status [id] Show session status
|
|
2981
|
-
/swarm list List all sessions
|
|
2982
|
-
/swarm cancel <id> Cancel a session
|
|
2983
|
-
|
|
2984
|
-
Decomposition options:
|
|
2985
|
-
--strategy parallel|sequential|map-reduce|pipeline
|
|
2986
|
-
--aggregation concatenate|merge-dedupe|summarize|structured
|
|
2987
|
-
|
|
2988
|
-
Coordination options:
|
|
2989
|
-
--mode competitive|collaborative|consensus|overseer
|
|
2990
|
-
--template code-review|architecture|security-audit|brainstorm|debate
|
|
2991
|
-
|
|
2992
|
-
Requires --agents flag.`);
|
|
2993
|
-
}
|
|
2994
|
-
break;
|
|
2995
|
-
}
|
|
2996
|
-
case '/checkpoint':
|
|
2997
|
-
case '/cp': {
|
|
2998
|
-
const { listCheckpoints, clearCheckpoints } = await import('../checkpoint.js');
|
|
2999
|
-
const cpSubCmd = parts[1] || 'list';
|
|
3000
|
-
if (cpSubCmd === 'list') {
|
|
3001
|
-
const filterPath = parts[2];
|
|
3002
|
-
const checkpoints = listCheckpoints(filterPath);
|
|
999
|
+
case '/restore': {
|
|
1000
|
+
const { restoreFromCheckpoint, listCheckpoints } = await import('../checkpoint.js');
|
|
1001
|
+
const restorePath = parts[1];
|
|
1002
|
+
if (!restorePath) {
|
|
1003
|
+
// No arg: list available checkpoints (absorbs the old /checkpoint list)
|
|
1004
|
+
const checkpoints = listCheckpoints();
|
|
3003
1005
|
if (checkpoints.length === 0) {
|
|
3004
|
-
ctx.addMessage('system',
|
|
3005
|
-
? `No checkpoints found for: ${filterPath}`
|
|
3006
|
-
: 'No checkpoints found. Checkpoints are created automatically when files are overwritten.');
|
|
1006
|
+
ctx.addMessage('system', 'No checkpoints found. Checkpoints are created automatically (as git commits) before destructive tool calls, and require a git repository.');
|
|
3007
1007
|
}
|
|
3008
1008
|
else {
|
|
3009
1009
|
const list = checkpoints.slice(0, 20).map((cp, i) => {
|
|
3010
|
-
const relPath = path.relative(process.cwd(), cp.filePath);
|
|
3011
1010
|
const time = new Date(cp.timestamp).toLocaleString();
|
|
3012
|
-
|
|
3013
|
-
return ` ${i}. ${relPath} (${size}) - ${time}`;
|
|
1011
|
+
return ` ${i}. ${cp.hash} ${cp.subject} - ${time}`;
|
|
3014
1012
|
}).join('\n');
|
|
3015
|
-
ctx.addMessage('system', `Checkpoints (newest first):\n${list}${checkpoints.length > 20 ? `\n ... and ${checkpoints.length - 20} more` : ''}
|
|
1013
|
+
ctx.addMessage('system', `Checkpoints (newest first):\n${list}${checkpoints.length > 20 ? `\n ... and ${checkpoints.length - 20} more` : ''}\n\nUse /restore <path> [index] to restore a file.`);
|
|
3016
1014
|
}
|
|
3017
|
-
}
|
|
3018
|
-
else if (cpSubCmd === 'clear') {
|
|
3019
|
-
const days = parts[2] ? parseInt(parts[2]) : undefined;
|
|
3020
|
-
const removed = clearCheckpoints(days);
|
|
3021
|
-
ctx.addMessage('system', `Cleared ${removed} checkpoint${removed !== 1 ? 's' : ''}${days ? ` older than ${days} days` : ''}.`);
|
|
3022
|
-
}
|
|
3023
|
-
else {
|
|
3024
|
-
ctx.addMessage('system', 'Usage: /checkpoint [list|clear]\n /checkpoint list [path] - list checkpoints\n /checkpoint clear [days] - clear old checkpoints\n /restore <path> [index] - restore a file');
|
|
3025
|
-
}
|
|
3026
|
-
break;
|
|
3027
|
-
}
|
|
3028
|
-
case '/restore': {
|
|
3029
|
-
const { restoreCheckpoint, listCheckpoints } = await import('../checkpoint.js');
|
|
3030
|
-
const restorePath = parts[1];
|
|
3031
|
-
if (!restorePath) {
|
|
3032
|
-
ctx.addMessage('error', 'Usage: /restore <path> [index]\n Restores a file from its most recent checkpoint.\n Use /checkpoint list to see available checkpoints.');
|
|
3033
1015
|
break;
|
|
3034
1016
|
}
|
|
3035
1017
|
const idx = parts[2] ? parseInt(parts[2]) : 0;
|
|
@@ -3039,174 +1021,17 @@ Requires --agents flag.`);
|
|
|
3039
1021
|
ctx.addMessage('error', `No checkpoints found for: ${restorePath}`);
|
|
3040
1022
|
break;
|
|
3041
1023
|
}
|
|
3042
|
-
const restored =
|
|
1024
|
+
const restored = restoreFromCheckpoint(absRestorePath, idx);
|
|
3043
1025
|
if (restored !== undefined) {
|
|
3044
1026
|
const relPath = path.relative(process.cwd(), absRestorePath);
|
|
3045
1027
|
const cp = checkpoints[idx];
|
|
3046
|
-
ctx.addMessage('system', `✓ Restored ${relPath} from checkpoint (${new Date(cp.timestamp).toLocaleString()})`);
|
|
1028
|
+
ctx.addMessage('system', `✓ Restored ${relPath} from checkpoint ${cp.hash} (${new Date(cp.timestamp).toLocaleString()})`);
|
|
3047
1029
|
}
|
|
3048
1030
|
else {
|
|
3049
1031
|
ctx.addMessage('error', `Failed to restore: checkpoint index ${idx} not found for ${restorePath}`);
|
|
3050
1032
|
}
|
|
3051
1033
|
break;
|
|
3052
1034
|
}
|
|
3053
|
-
// ================================================================
|
|
3054
|
-
// Background Jobs
|
|
3055
|
-
// ================================================================
|
|
3056
|
-
case '/bg': {
|
|
3057
|
-
const bgPrompt = parts.slice(1).join(' ');
|
|
3058
|
-
if (!bgPrompt) {
|
|
3059
|
-
ctx.addMessage('error', 'Usage: /bg <prompt> — run a task in the background');
|
|
3060
|
-
break;
|
|
3061
|
-
}
|
|
3062
|
-
const bgJob = createJob(bgPrompt, { provider: ctx.actualProvider, model: ctx.actualModel });
|
|
3063
|
-
ctx.addMessage('system', `Background job ${bgJob.id} created: "${bgPrompt.length > 60 ? bgPrompt.slice(0, 57) + '...' : bgPrompt}"`);
|
|
3064
|
-
// Run the job using the agent
|
|
3065
|
-
runJob(bgJob.id, async (prompt, signal) => {
|
|
3066
|
-
const { chat } = await import('../providers/index.js');
|
|
3067
|
-
const { TOOLS } = await import('../tools.js');
|
|
3068
|
-
const { executeTool: execTool } = await import('../tools.js');
|
|
3069
|
-
const cwd = ctx.sessionRef.current?.projectPath || process.cwd();
|
|
3070
|
-
const maxIterations = resolveIterationLimit(config.get('maxIterations'));
|
|
3071
|
-
const bgMessages = [
|
|
3072
|
-
{ role: 'system', content: buildFullSystemPrompt(ctx.persona, cwd) },
|
|
3073
|
-
{ role: 'user', content: prompt },
|
|
3074
|
-
];
|
|
3075
|
-
let iterations = 0;
|
|
3076
|
-
let lastContent = '';
|
|
3077
|
-
while (!signal.aborted && iterations < maxIterations) {
|
|
3078
|
-
iterations++;
|
|
3079
|
-
const response = await chat(ctx.provider, bgMessages, TOOLS, ctx.model);
|
|
3080
|
-
if (!response.toolCalls?.length) {
|
|
3081
|
-
lastContent = response.content;
|
|
3082
|
-
break;
|
|
3083
|
-
}
|
|
3084
|
-
bgMessages.push({ role: 'assistant', content: response.content, toolCalls: response.toolCalls });
|
|
3085
|
-
for (const tc of response.toolCalls) {
|
|
3086
|
-
const result = await execTool(tc, cwd);
|
|
3087
|
-
bgMessages.push({ role: 'tool', content: result.result, toolCallId: tc.id });
|
|
3088
|
-
if (signal.aborted)
|
|
3089
|
-
break;
|
|
3090
|
-
}
|
|
3091
|
-
}
|
|
3092
|
-
if (signal.aborted) {
|
|
3093
|
-
const error = new Error('Background job cancelled');
|
|
3094
|
-
error.name = 'AbortError';
|
|
3095
|
-
throw error;
|
|
3096
|
-
}
|
|
3097
|
-
return { result: lastContent, iterations };
|
|
3098
|
-
}).then(completed => {
|
|
3099
|
-
ctx.addMessage('system', `Background job ${completed.id} ${completed.status}: ${completed.result?.slice(0, 200) || completed.error || 'done'}`);
|
|
3100
|
-
}).catch(() => { });
|
|
3101
|
-
break;
|
|
3102
|
-
}
|
|
3103
|
-
case '/jobs': {
|
|
3104
|
-
ctx.addMessage('system', formatJobsList());
|
|
3105
|
-
break;
|
|
3106
|
-
}
|
|
3107
|
-
case '/job': {
|
|
3108
|
-
const jobId = parts[1];
|
|
3109
|
-
if (!jobId) {
|
|
3110
|
-
ctx.addMessage('error', 'Usage: /job <id> — show job details');
|
|
3111
|
-
break;
|
|
3112
|
-
}
|
|
3113
|
-
const jobInfo = getJob(jobId);
|
|
3114
|
-
if (!jobInfo) {
|
|
3115
|
-
ctx.addMessage('error', `Job "${jobId}" not found.`);
|
|
3116
|
-
}
|
|
3117
|
-
else {
|
|
3118
|
-
ctx.addMessage('system', formatJob(jobInfo) + (jobInfo.result ? `\n\nResult:\n${jobInfo.result.slice(0, 2000)}` : ''));
|
|
3119
|
-
}
|
|
3120
|
-
break;
|
|
3121
|
-
}
|
|
3122
|
-
case '/cancel': {
|
|
3123
|
-
const cancelId = parts[1];
|
|
3124
|
-
if (!cancelId) {
|
|
3125
|
-
ctx.addMessage('error', 'Usage: /cancel <job-id>');
|
|
3126
|
-
break;
|
|
3127
|
-
}
|
|
3128
|
-
const cancelled = cancelJob(cancelId);
|
|
3129
|
-
ctx.addMessage('system', cancelled ? `Cancelled job ${cancelId}` : `Could not cancel ${cancelId} (not running or not found)`);
|
|
3130
|
-
break;
|
|
3131
|
-
}
|
|
3132
|
-
case '/clear-jobs': {
|
|
3133
|
-
const cleared = clearFinishedJobs();
|
|
3134
|
-
ctx.addMessage('system', cleared > 0 ? `Cleared ${cleared} finished job(s).` : 'No finished jobs to clear.');
|
|
3135
|
-
break;
|
|
3136
|
-
}
|
|
3137
|
-
// ================================================================
|
|
3138
|
-
// Recordings
|
|
3139
|
-
// ================================================================
|
|
3140
|
-
case '/recordings': {
|
|
3141
|
-
const recs = listRecordings();
|
|
3142
|
-
if (recs.length === 0) {
|
|
3143
|
-
ctx.addMessage('system', 'No recordings found.');
|
|
3144
|
-
}
|
|
3145
|
-
else {
|
|
3146
|
-
const lines = recs.slice(0, 20).map(r => {
|
|
3147
|
-
const dur = r.duration > 0 ? ` (${Math.floor(r.duration / 60000)}m${Math.floor((r.duration % 60000) / 1000)}s)` : '';
|
|
3148
|
-
return ` ${r.id} ${r.startTime} ${r.eventCount} events${dur}`;
|
|
3149
|
-
});
|
|
3150
|
-
ctx.addMessage('system', `Recordings (${recs.length} total):\n${lines.join('\n')}`);
|
|
3151
|
-
}
|
|
3152
|
-
break;
|
|
3153
|
-
}
|
|
3154
|
-
case '/recording': {
|
|
3155
|
-
const recId = parts[1];
|
|
3156
|
-
if (!recId) {
|
|
3157
|
-
ctx.addMessage('error', 'Usage: /recording <id> — show recording details');
|
|
3158
|
-
break;
|
|
3159
|
-
}
|
|
3160
|
-
const rec = loadRecording(recId);
|
|
3161
|
-
if (!rec) {
|
|
3162
|
-
ctx.addMessage('error', `Recording "${recId}" not found.`);
|
|
3163
|
-
}
|
|
3164
|
-
else {
|
|
3165
|
-
ctx.addMessage('system', formatRecording(rec));
|
|
3166
|
-
}
|
|
3167
|
-
break;
|
|
3168
|
-
}
|
|
3169
|
-
case '/delete-recording': {
|
|
3170
|
-
const delRecId = parts[1];
|
|
3171
|
-
if (!delRecId) {
|
|
3172
|
-
ctx.addMessage('error', 'Usage: /delete-recording <id>');
|
|
3173
|
-
break;
|
|
3174
|
-
}
|
|
3175
|
-
const delResult = deleteRecording(delRecId);
|
|
3176
|
-
ctx.addMessage('system', delResult ? `Deleted recording ${delRecId}` : `Recording "${delRecId}" not found.`);
|
|
3177
|
-
break;
|
|
3178
|
-
}
|
|
3179
|
-
// ================================================================
|
|
3180
|
-
// API Server
|
|
3181
|
-
// ================================================================
|
|
3182
|
-
case '/serve': {
|
|
3183
|
-
if (isApiServerRunning()) {
|
|
3184
|
-
ctx.addMessage('system', 'API server is already running.');
|
|
3185
|
-
break;
|
|
3186
|
-
}
|
|
3187
|
-
const servePort = parts[1] ? parseInt(parts[1], 10) : 3100;
|
|
3188
|
-
if (isNaN(servePort) || servePort < 1 || servePort > 65535) {
|
|
3189
|
-
ctx.addMessage('error', 'Invalid port. Usage: /serve [port]');
|
|
3190
|
-
break;
|
|
3191
|
-
}
|
|
3192
|
-
try {
|
|
3193
|
-
const info = await startApiServer({ port: servePort, host: '127.0.0.1' });
|
|
3194
|
-
ctx.addMessage('system', `API server started on http://${info.host}:${info.port}`);
|
|
3195
|
-
}
|
|
3196
|
-
catch (err) {
|
|
3197
|
-
ctx.addMessage('error', `Failed to start API server: ${err instanceof Error ? err.message : String(err)}`);
|
|
3198
|
-
}
|
|
3199
|
-
break;
|
|
3200
|
-
}
|
|
3201
|
-
case '/serve-stop': {
|
|
3202
|
-
if (!isApiServerRunning()) {
|
|
3203
|
-
ctx.addMessage('system', 'API server is not running.');
|
|
3204
|
-
break;
|
|
3205
|
-
}
|
|
3206
|
-
await stopApiServer();
|
|
3207
|
-
ctx.addMessage('system', 'API server stopped.');
|
|
3208
|
-
break;
|
|
3209
|
-
}
|
|
3210
1035
|
case '/exit':
|
|
3211
1036
|
case '/quit':
|
|
3212
1037
|
process.exit(0);
|