@calliopelabs/cli 2.2.0 → 2.5.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 +17 -0
- package/dist/agents/agent-config-loader.js +1 -1
- package/dist/agents/agent-config-presets.js +13 -13
- package/dist/agents/agent-config-presets.js.map +1 -1
- package/dist/agents/agent-config-types.d.ts +1 -1
- package/dist/agents/agent-config-types.d.ts.map +1 -1
- package/dist/agents/council-types.d.ts +2 -0
- package/dist/agents/council-types.d.ts.map +1 -1
- package/dist/agents/council-types.js.map +1 -1
- package/dist/agents/council.d.ts +5 -0
- package/dist/agents/council.d.ts.map +1 -1
- package/dist/agents/council.js +150 -14
- package/dist/agents/council.js.map +1 -1
- package/dist/agents/dynamic-tools.d.ts.map +1 -1
- package/dist/agents/dynamic-tools.js +39 -10
- package/dist/agents/dynamic-tools.js.map +1 -1
- package/dist/agents/orchestrator.d.ts +7 -0
- package/dist/agents/orchestrator.d.ts.map +1 -1
- package/dist/agents/orchestrator.js +48 -7
- package/dist/agents/orchestrator.js.map +1 -1
- package/dist/agents/sdk-backend.js +1 -1
- package/dist/agents/sdk-backend.js.map +1 -1
- package/dist/agents/swarm-types.d.ts +1 -0
- package/dist/agents/swarm-types.d.ts.map +1 -1
- package/dist/agents/swarm.d.ts +5 -0
- package/dist/agents/swarm.d.ts.map +1 -1
- package/dist/agents/swarm.js +85 -17
- package/dist/agents/swarm.js.map +1 -1
- package/dist/agents/types.d.ts +1 -0
- package/dist/agents/types.d.ts.map +1 -1
- package/dist/agents/types.js.map +1 -1
- package/dist/api-server.d.ts +9 -0
- package/dist/api-server.d.ts.map +1 -1
- package/dist/api-server.js +75 -4
- package/dist/api-server.js.map +1 -1
- package/dist/auto-checkpoint.d.ts.map +1 -1
- package/dist/auto-checkpoint.js +50 -17
- package/dist/auto-checkpoint.js.map +1 -1
- package/dist/auto-compressor.d.ts +14 -0
- package/dist/auto-compressor.d.ts.map +1 -1
- package/dist/auto-compressor.js +67 -10
- package/dist/auto-compressor.js.map +1 -1
- package/dist/background-jobs.d.ts.map +1 -1
- package/dist/background-jobs.js +8 -3
- package/dist/background-jobs.js.map +1 -1
- package/dist/bin.d.ts +8 -0
- package/dist/bin.d.ts.map +1 -1
- package/dist/bin.js +63 -4
- package/dist/bin.js.map +1 -1
- package/dist/branching.d.ts.map +1 -1
- package/dist/branching.js +14 -1
- package/dist/branching.js.map +1 -1
- package/dist/checkpoint.d.ts.map +1 -1
- package/dist/checkpoint.js +13 -1
- package/dist/checkpoint.js.map +1 -1
- package/dist/cli/agent.d.ts.map +1 -1
- package/dist/cli/agent.js +130 -62
- package/dist/cli/agent.js.map +1 -1
- package/dist/cli/commands.d.ts.map +1 -1
- package/dist/cli/commands.js +218 -11
- package/dist/cli/commands.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +55 -6
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/types.d.ts +3 -0
- package/dist/cli/types.d.ts.map +1 -1
- package/dist/cli/types.js +2 -1
- package/dist/cli/types.js.map +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +15 -3
- package/dist/config.js.map +1 -1
- package/dist/diff.d.ts.map +1 -1
- package/dist/diff.js +42 -4
- package/dist/diff.js.map +1 -1
- package/dist/env-expansion.d.ts +15 -0
- package/dist/env-expansion.d.ts.map +1 -0
- package/dist/env-expansion.js +43 -0
- package/dist/env-expansion.js.map +1 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +30 -3
- package/dist/errors.js.map +1 -1
- package/dist/headless.d.ts.map +1 -1
- package/dist/headless.js +59 -4
- 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/idle-eviction.d.ts.map +1 -1
- package/dist/idle-eviction.js +8 -1
- package/dist/idle-eviction.js.map +1 -1
- package/dist/iteration-ledger.d.ts +111 -2
- package/dist/iteration-ledger.d.ts.map +1 -1
- package/dist/iteration-ledger.js +327 -19
- package/dist/iteration-ledger.js.map +1 -1
- package/dist/iteration-limit.d.ts +5 -0
- package/dist/iteration-limit.d.ts.map +1 -0
- package/dist/iteration-limit.js +17 -0
- package/dist/iteration-limit.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 +191 -12
- package/dist/mcp.js.map +1 -1
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +4 -9
- 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 +307 -114
- package/dist/model-detection.js.map +1 -1
- package/dist/model-router.js +7 -7
- package/dist/model-router.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 +37 -0
- package/dist/plugins.d.ts.map +1 -1
- package/dist/plugins.js +87 -0
- package/dist/plugins.js.map +1 -1
- package/dist/prevent-sleep.d.ts +10 -0
- package/dist/prevent-sleep.d.ts.map +1 -0
- package/dist/prevent-sleep.js +85 -0
- package/dist/prevent-sleep.js.map +1 -0
- 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 +81 -17
- package/dist/providers/bedrock.js.map +1 -1
- package/dist/providers/compat.d.ts.map +1 -1
- package/dist/providers/compat.js +21 -6
- package/dist/providers/compat.js.map +1 -1
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js +2 -0
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/openai-compat-shims.d.ts +31 -0
- package/dist/providers/openai-compat-shims.d.ts.map +1 -0
- package/dist/providers/openai-compat-shims.js +179 -0
- package/dist/providers/openai-compat-shims.js.map +1 -0
- package/dist/providers/openai.d.ts.map +1 -1
- 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/risk.d.ts.map +1 -1
- package/dist/risk.js +15 -5
- package/dist/risk.js.map +1 -1
- package/dist/sandbox-native.d.ts +1 -0
- package/dist/sandbox-native.d.ts.map +1 -1
- package/dist/sandbox-native.js +37 -5
- package/dist/sandbox-native.js.map +1 -1
- package/dist/scope.d.ts +10 -0
- package/dist/scope.d.ts.map +1 -1
- package/dist/scope.js +75 -15
- 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/skills.d.ts +19 -0
- package/dist/skills.d.ts.map +1 -1
- package/dist/skills.js +98 -10
- package/dist/skills.js.map +1 -1
- package/dist/smart-router.js +4 -4
- package/dist/smart-router.js.map +1 -1
- package/dist/storage.d.ts +18 -3
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +182 -14
- package/dist/storage.js.map +1 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +233 -39
- 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.map +1 -1
- package/dist/types.js +18 -12
- package/dist/types.js.map +1 -1
- package/dist/ui/agent.d.ts +1 -1
- package/dist/ui/agent.d.ts.map +1 -1
- package/dist/ui/agent.js +175 -121
- package/dist/ui/agent.js.map +1 -1
- package/dist/ui/chat-input.d.ts +3 -1
- package/dist/ui/chat-input.d.ts.map +1 -1
- package/dist/ui/chat-input.js +82 -17
- package/dist/ui/chat-input.js.map +1 -1
- package/dist/ui/commands.d.ts +4 -0
- package/dist/ui/commands.d.ts.map +1 -1
- package/dist/ui/commands.js +562 -39
- package/dist/ui/commands.js.map +1 -1
- package/dist/ui/completions.d.ts.map +1 -1
- package/dist/ui/completions.js +2 -0
- package/dist/ui/completions.js.map +1 -1
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/index.js +288 -60
- 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 +6 -2
- package/dist/ui/messages.d.ts.map +1 -1
- package/dist/ui/messages.js +42 -11
- package/dist/ui/messages.js.map +1 -1
- package/dist/ui/modals.d.ts +21 -1
- package/dist/ui/modals.d.ts.map +1 -1
- package/dist/ui/modals.js +67 -5
- package/dist/ui/modals.js.map +1 -1
- 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 +12 -1
- 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/package.json +4 -7
- 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/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/ui/commands.js
CHANGED
|
@@ -21,6 +21,7 @@ import { getAgentStatusReport, swarmManager, councilManager, COUNCIL_TEMPLATES,
|
|
|
21
21
|
import { smartRoute, getDefaultSmartRoutingConfig, detectTaskType } from '../smart-router.js';
|
|
22
22
|
import { getCurrentSkin, getCurrentPalette, applySkin, applyPalette, listSkins, listPalettes } from '../hud/api.js';
|
|
23
23
|
import { getCurrentCompanion, applyCompanion, listCompanions, getMoodText } from '../companions.js';
|
|
24
|
+
import { scuttlebotClient } from '../scuttlebot/index.js';
|
|
24
25
|
import { createJob, runJob, cancelJob, getJob, formatJob, formatJobsList, clearFinishedJobs } from '../background-jobs.js';
|
|
25
26
|
import { listRecordings, loadRecording, formatRecording, deleteRecording } from '../terminal-recording.js';
|
|
26
27
|
import { startApiServer, stopApiServer, isApiServerRunning } from '../api-server.js';
|
|
@@ -28,6 +29,74 @@ import { getTerminalImageInfo, getImageModeLabel, renderSkinBanner, renderTransi
|
|
|
28
29
|
import { applyThemePack, listThemePacks, getCurrentPack, getCompanionMode, setCompanionMode, getThemePack } from '../hud/theme-packs/api.js';
|
|
29
30
|
import { getModelContextLimit } from '../model-detection.js';
|
|
30
31
|
import { resetContextWarnings } from './context.js';
|
|
32
|
+
import * as memory from '../memory.js';
|
|
33
|
+
import { resolveIterationLimit, formatIterationLimit, isFiniteIterationLimit, } from '../iteration-limit.js';
|
|
34
|
+
// Builds the full system prompt including memory context (project + global).
|
|
35
|
+
// dir should be the project directory for the active/resumed session.
|
|
36
|
+
function buildFullSystemPrompt(persona, dir) {
|
|
37
|
+
const base = getSystemPrompt(persona);
|
|
38
|
+
const mem = memory.buildMemoryContext(dir);
|
|
39
|
+
return mem.trim() ? base + '\n\n--- Project Context ---\n' + mem : base;
|
|
40
|
+
}
|
|
41
|
+
function getActiveProjectDir(ctx) {
|
|
42
|
+
return ctx.sessionRef.current?.projectPath ?? process.cwd();
|
|
43
|
+
}
|
|
44
|
+
function formatLedgerDuration(durationMs) {
|
|
45
|
+
if (durationMs < 1000)
|
|
46
|
+
return `${durationMs}ms`;
|
|
47
|
+
if (durationMs < 60_000)
|
|
48
|
+
return `${(durationMs / 1000).toFixed(1)}s`;
|
|
49
|
+
return `${(durationMs / 60_000).toFixed(1)}m`;
|
|
50
|
+
}
|
|
51
|
+
function formatSessionLogLimit(limit) {
|
|
52
|
+
return limit > 0 ? String(limit) : 'unlimited';
|
|
53
|
+
}
|
|
54
|
+
function formatLedgerRun(run) {
|
|
55
|
+
const iterationCount = Math.max(0, (run.entryCountAtEnd ?? run.entryCountAtStart) - run.entryCountAtStart);
|
|
56
|
+
const parts = [`${run.kind}`, `[${run.status}]`];
|
|
57
|
+
if (iterationCount > 0) {
|
|
58
|
+
parts.push(`${iterationCount} iteration${iterationCount === 1 ? '' : 's'}`);
|
|
59
|
+
}
|
|
60
|
+
if (run.maxIterations != null && Number.isFinite(run.maxIterations)) {
|
|
61
|
+
parts.push(`max ${run.maxIterations}`);
|
|
62
|
+
}
|
|
63
|
+
else if (run.maxIterations === null) {
|
|
64
|
+
parts.push('unlimited');
|
|
65
|
+
}
|
|
66
|
+
const prompt = run.prompt.length > 90 ? `${run.prompt.slice(0, 90)}...` : run.prompt;
|
|
67
|
+
let line = `${parts.join(' ')} — ${prompt}`;
|
|
68
|
+
if (run.errorSummary) {
|
|
69
|
+
line += ` (${run.errorSummary})`;
|
|
70
|
+
}
|
|
71
|
+
return line;
|
|
72
|
+
}
|
|
73
|
+
function watchAsyncLedgerRun(ledger, kind, prompt, getStatus) {
|
|
74
|
+
if (!ledger)
|
|
75
|
+
return;
|
|
76
|
+
const runId = ledger.startRun(kind, prompt);
|
|
77
|
+
void (async () => {
|
|
78
|
+
for (;;) {
|
|
79
|
+
const current = getStatus();
|
|
80
|
+
if (!current) {
|
|
81
|
+
ledger.finishRun(runId, 'failed', { errorSummary: 'Run state no longer available' });
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (current.status === 'completed') {
|
|
85
|
+
ledger.finishRun(runId, 'completed');
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (current.status === 'failed') {
|
|
89
|
+
ledger.finishRun(runId, 'failed', { errorSummary: current.error });
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (current.status === 'cancelled') {
|
|
93
|
+
ledger.finishRun(runId, 'cancelled', { errorSummary: current.error || 'Cancelled' });
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
97
|
+
}
|
|
98
|
+
})();
|
|
99
|
+
}
|
|
31
100
|
// ============================================================================
|
|
32
101
|
// handleCommand
|
|
33
102
|
// ============================================================================
|
|
@@ -45,7 +114,7 @@ export async function handleCommand(cmd, ctx) {
|
|
|
45
114
|
/persona [name] - Switch personality
|
|
46
115
|
/route [on|off] - Auto model routing (/autoroute)
|
|
47
116
|
/smart [on|off|cost|test] - Cross-provider smart routing
|
|
48
|
-
/breaker [status|
|
|
117
|
+
/breaker [status|adjust] - Circuit breaker control (/cb)
|
|
49
118
|
|
|
50
119
|
--- Conversation ---
|
|
51
120
|
/edit - Edit and resend last message
|
|
@@ -57,6 +126,7 @@ export async function handleCommand(cmd, ctx) {
|
|
|
57
126
|
|
|
58
127
|
--- Session & State ---
|
|
59
128
|
/session [list|info|fork|save] - Session management (/sessions)
|
|
129
|
+
/log [summary|tail|failures|reset] - Iteration/run log
|
|
60
130
|
/resume [sessionId] - Resume session (restores full context)
|
|
61
131
|
/checkpoint [list|clear] - File checkpoints (/cp)
|
|
62
132
|
/restore <path> [index] - Restore file from checkpoint
|
|
@@ -103,8 +173,8 @@ export async function handleCommand(cmd, ctx) {
|
|
|
103
173
|
--- Multi-Agent ---
|
|
104
174
|
/agents - Sub-agent status (--agents mode)
|
|
105
175
|
/swarm [start|coord|status] - Agent swarms & coordination
|
|
106
|
-
/loop [prompt]
|
|
107
|
-
/cancel-loop - Stop running loop (/stop)
|
|
176
|
+
/loop [prompt] - Iterative agent loop (default: unlimited)
|
|
177
|
+
/cancel-loop - Stop running loop (/stop, /breakloop)
|
|
108
178
|
|
|
109
179
|
--- System ---
|
|
110
180
|
/status - Show status (/s)
|
|
@@ -125,9 +195,17 @@ Modes: Plan | Hybrid | Work | Auto-route: ${ctx.autoRoute ? 'ON' : 'OFF'}${ctx.a
|
|
|
125
195
|
case '/providers':
|
|
126
196
|
case '/p':
|
|
127
197
|
if (parts[1]) {
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
198
|
+
const requested = parts[1].toLowerCase();
|
|
199
|
+
const available = getAvailableProviders();
|
|
200
|
+
if (!available.includes(requested)) {
|
|
201
|
+
ctx.addMessage('error', `Provider "${requested}" is not configured. Run /provider (no args) for an interactive picker with setup.`);
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
ctx.setProvider(requested);
|
|
205
|
+
ctx.addMessage('system', `Provider: ${selectProvider(requested)}`);
|
|
206
|
+
}
|
|
207
|
+
else if (ctx.openProviderPicker) {
|
|
208
|
+
ctx.openProviderPicker();
|
|
131
209
|
}
|
|
132
210
|
else {
|
|
133
211
|
ctx.addMessage('system', `Provider: ${ctx.actualProvider} | Available: ${getAvailableProviders().join(', ')}`);
|
|
@@ -157,7 +235,7 @@ Modes: Plan | Hybrid | Work | Auto-route: ${ctx.autoRoute ? 'ON' : 'OFF'}${ctx.a
|
|
|
157
235
|
else {
|
|
158
236
|
ctx.addMessage('system', `Fetching models for ${ctx.actualProvider}...`);
|
|
159
237
|
try {
|
|
160
|
-
const models = await getAvailableModels(ctx.actualProvider);
|
|
238
|
+
const models = await getAvailableModels(ctx.actualProvider, { throwOnError: true });
|
|
161
239
|
if (models.length > 0) {
|
|
162
240
|
ctx.setAvailableModels(models);
|
|
163
241
|
ctx.setModalMode('model');
|
|
@@ -174,7 +252,7 @@ Modes: Plan | Hybrid | Work | Auto-route: ${ctx.autoRoute ? 'ON' : 'OFF'}${ctx.a
|
|
|
174
252
|
case '/models':
|
|
175
253
|
ctx.addMessage('system', `Fetching models for ${ctx.actualProvider}...`);
|
|
176
254
|
try {
|
|
177
|
-
const models = await getAvailableModels(ctx.actualProvider);
|
|
255
|
+
const models = await getAvailableModels(ctx.actualProvider, { throwOnError: true });
|
|
178
256
|
if (models.length > 0) {
|
|
179
257
|
ctx.setAvailableModels(models);
|
|
180
258
|
ctx.setModalMode('model');
|
|
@@ -202,7 +280,7 @@ Modes: Plan | Hybrid | Work | Auto-route: ${ctx.autoRoute ? 'ON' : 'OFF'}${ctx.a
|
|
|
202
280
|
if (parts[1] && ['calliope', 'muse', 'minimal'].includes(parts[1])) {
|
|
203
281
|
const p = parts[1];
|
|
204
282
|
ctx.setPersona(p);
|
|
205
|
-
ctx.llmMessages.current = [{ role: 'system', content:
|
|
283
|
+
ctx.llmMessages.current = [{ role: 'system', content: buildFullSystemPrompt(p, getActiveProjectDir(ctx)) }];
|
|
206
284
|
ctx.addMessage('system', `Persona: ${p}`);
|
|
207
285
|
}
|
|
208
286
|
else {
|
|
@@ -212,7 +290,8 @@ Modes: Plan | Hybrid | Work | Auto-route: ${ctx.autoRoute ? 'ON' : 'OFF'}${ctx.a
|
|
|
212
290
|
case '/clear':
|
|
213
291
|
case '/c':
|
|
214
292
|
ctx.setMessages([]);
|
|
215
|
-
ctx.llmMessages.current = [{ role: 'system', content:
|
|
293
|
+
ctx.llmMessages.current = [{ role: 'system', content: buildFullSystemPrompt(ctx.persona, getActiveProjectDir(ctx)) }];
|
|
294
|
+
ctx.ledger?.reset();
|
|
216
295
|
ctx.setStats({ inputTokens: 0, outputTokens: 0, cost: 0, messageCount: 0 });
|
|
217
296
|
resetContextWarnings(); // Reset context warning state
|
|
218
297
|
break;
|
|
@@ -340,11 +419,95 @@ Modes: Plan | Hybrid | Work | Auto-route: ${ctx.autoRoute ? 'ON' : 'OFF'}${ctx.a
|
|
|
340
419
|
case '/status':
|
|
341
420
|
case '/s': {
|
|
342
421
|
const imgInfo = getTerminalImageInfo();
|
|
343
|
-
|
|
422
|
+
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`;
|
|
423
|
+
// Add scuttlebot status if enabled
|
|
424
|
+
if (scuttlebotClient.isEnabled()) {
|
|
425
|
+
const sbStatus = scuttlebotClient.getStatus();
|
|
426
|
+
statusMsg += `\nScuttlebot: enabled (${sbStatus.nick}) | irc:${sbStatus.config?.ircAddr} | #${sbStatus.config?.channel}`;
|
|
427
|
+
}
|
|
428
|
+
ctx.addMessage('system', statusMsg);
|
|
429
|
+
break;
|
|
430
|
+
}
|
|
431
|
+
case '/scuttlebot': {
|
|
432
|
+
const subCmd = parts[1];
|
|
433
|
+
const sbStatus = scuttlebotClient.getStatus();
|
|
434
|
+
// /scuttlebot enable - enable mid-session
|
|
435
|
+
if (subCmd === 'enable') {
|
|
436
|
+
if (sbStatus.enabled) {
|
|
437
|
+
ctx.addMessage('system', 'Scuttlebot is already enabled.');
|
|
438
|
+
break;
|
|
439
|
+
}
|
|
440
|
+
// Initialize scuttlebot — config is loaded from ~/.config/scuttlebot-relay.env,
|
|
441
|
+
// process.env, and .scuttlebot.yaml inside initialize()
|
|
442
|
+
const sessionId = ctx.sessionRef.current?.id || 'default';
|
|
443
|
+
const cwd = getActiveProjectDir(ctx);
|
|
444
|
+
scuttlebotClient.initialize(sessionId, cwd).then(async (enabled) => {
|
|
445
|
+
if (enabled) {
|
|
446
|
+
const status = scuttlebotClient.getStatus();
|
|
447
|
+
let msg = '✓ Scuttlebot enabled!\n';
|
|
448
|
+
msg += ` Nick: ${status.nick}\n`;
|
|
449
|
+
msg += ` IRC: ${status.config?.ircAddr}\n`;
|
|
450
|
+
msg += ` Channel: #${status.config?.channel}`;
|
|
451
|
+
if (status.config?.channels && status.config.channels.length > 1) {
|
|
452
|
+
msg += `\n Channels: ${status.config.channels.map((c) => '#' + c).join(', ')}`;
|
|
453
|
+
}
|
|
454
|
+
ctx.addMessage('system', msg);
|
|
455
|
+
// Post online status and start routing IRC instructions
|
|
456
|
+
await scuttlebotClient.postOnline();
|
|
457
|
+
ctx.startScuttlebotPolling();
|
|
458
|
+
}
|
|
459
|
+
else {
|
|
460
|
+
ctx.addMessage('system', 'Failed to enable scuttlebot');
|
|
461
|
+
}
|
|
462
|
+
}).catch((err) => {
|
|
463
|
+
ctx.addMessage('system', `Failed to enable scuttlebot: ${err instanceof Error ? err.message : String(err)}`);
|
|
464
|
+
});
|
|
465
|
+
break;
|
|
466
|
+
}
|
|
467
|
+
// /scuttlebot disable - disable mid-session
|
|
468
|
+
if (subCmd === 'disable') {
|
|
469
|
+
if (!sbStatus.enabled) {
|
|
470
|
+
ctx.addMessage('system', 'Scuttlebot is not enabled.');
|
|
471
|
+
break;
|
|
472
|
+
}
|
|
473
|
+
scuttlebotClient.postOffline().then(() => {
|
|
474
|
+
return scuttlebotClient.disconnect();
|
|
475
|
+
}).then(() => {
|
|
476
|
+
ctx.addMessage('system', 'Scuttlebot disabled');
|
|
477
|
+
}).catch((err) => {
|
|
478
|
+
ctx.addMessage('system', `Error disabling scuttlebot: ${err instanceof Error ? err.message : String(err)}`);
|
|
479
|
+
});
|
|
480
|
+
break;
|
|
481
|
+
}
|
|
482
|
+
// Show status
|
|
483
|
+
if (!sbStatus.enabled) {
|
|
484
|
+
ctx.addMessage('system', 'Scuttlebot not enabled.\n\nRun: /scuttlebot enable\n\nConfig is loaded automatically from ~/.config/scuttlebot-relay.env\nChannel is read from .scuttlebot.yaml');
|
|
485
|
+
break;
|
|
486
|
+
}
|
|
487
|
+
let statusText = 'Scuttlebot Status\n────────────────────────────────────────\n';
|
|
488
|
+
statusText += `Enabled: yes\n`;
|
|
489
|
+
statusText += `Nick: ${sbStatus.nick}\n`;
|
|
490
|
+
statusText += `IRC: ${sbStatus.config?.ircAddr}\n`;
|
|
491
|
+
statusText += `Channel: #${sbStatus.config?.channel}\n`;
|
|
492
|
+
statusText += `Connected: ${sbStatus.connected ? 'yes' : 'no'}`;
|
|
493
|
+
if (sbStatus.config?.channels && sbStatus.config.channels.length > 1) {
|
|
494
|
+
statusText += `\nChannels: ${sbStatus.config.channels.map((c) => '#' + c).join(', ')}`;
|
|
495
|
+
}
|
|
496
|
+
statusText += '\n\nCommands:\n /scuttlebot <message> Post a message\n /scuttlebot disable Disable integration';
|
|
497
|
+
ctx.addMessage('system', statusText);
|
|
498
|
+
// Allow manual message posting
|
|
499
|
+
if (subCmd && subCmd !== 'enable' && subCmd !== 'disable') {
|
|
500
|
+
const message = parts.slice(1).join(' ');
|
|
501
|
+
scuttlebotClient.postMessage(message).then(() => {
|
|
502
|
+
ctx.addMessage('system', 'Message posted to scuttlebot.');
|
|
503
|
+
}).catch((err) => {
|
|
504
|
+
ctx.addMessage('system', `Failed to post message: ${err instanceof Error ? err.message : String(err)}`);
|
|
505
|
+
});
|
|
506
|
+
}
|
|
344
507
|
break;
|
|
345
508
|
}
|
|
346
509
|
case '/config':
|
|
347
|
-
ctx.addMessage('system', `Config: ${config.getConfigPath()}\nProviders: ${config.getConfiguredProviders().join(', ') || 'none'}\nmaxIterations: ${config.get('maxIterations')}`);
|
|
510
|
+
ctx.addMessage('system', `Config: ${config.getConfigPath()}\nProviders: ${config.getConfiguredProviders().join(', ') || 'none'}\nmaxIterations: ${config.get('maxIterations')}\nsessionLogLimit: ${formatSessionLogLimit(config.get('sessionLogLimit'))} (set > 0 to cap)`);
|
|
348
511
|
break;
|
|
349
512
|
case '/agents': {
|
|
350
513
|
if (!ctx.agtermEnabled) {
|
|
@@ -797,6 +960,7 @@ Edit the YAML to customize members, strategy, and coordination settings.`);
|
|
|
797
960
|
ctx.addMessage('system', `Usage: /set <key> <value>
|
|
798
961
|
Available keys:
|
|
799
962
|
maxIterations <number> - Max agent iterations (current: ${config.get('maxIterations')})
|
|
963
|
+
sessionLogLimit <number> - Cap retained session log items (current: ${formatSessionLogLimit(config.get('sessionLogLimit'))}, 0 = unlimited)
|
|
800
964
|
persona <name> - calliope, muse, minimal
|
|
801
965
|
fancyOutput <bool> - true/false`);
|
|
802
966
|
break;
|
|
@@ -804,12 +968,22 @@ Available keys:
|
|
|
804
968
|
try {
|
|
805
969
|
if (key === 'maxIterations') {
|
|
806
970
|
const num = parseInt(value, 10);
|
|
807
|
-
if (isNaN(num) || num <
|
|
808
|
-
ctx.addMessage('error', 'maxIterations must be
|
|
971
|
+
if (isNaN(num) || num < 0 || num > 1000000) {
|
|
972
|
+
ctx.addMessage('error', 'maxIterations must be 0-1000000 (0 = unlimited)');
|
|
809
973
|
break;
|
|
810
974
|
}
|
|
811
975
|
config.set('maxIterations', num);
|
|
812
|
-
ctx.addMessage('system', `\u2713 maxIterations set to ${num}`);
|
|
976
|
+
ctx.addMessage('system', `\u2713 maxIterations set to ${formatIterationLimit(resolveIterationLimit(num))}`);
|
|
977
|
+
}
|
|
978
|
+
else if (key === 'sessionLogLimit') {
|
|
979
|
+
const num = parseInt(value, 10);
|
|
980
|
+
if (isNaN(num) || num < 0 || num > 100000) {
|
|
981
|
+
ctx.addMessage('error', 'sessionLogLimit must be 0-100000 (0 = unlimited)');
|
|
982
|
+
break;
|
|
983
|
+
}
|
|
984
|
+
config.set('sessionLogLimit', num);
|
|
985
|
+
ctx.ledger?.setRetentionLimit(num);
|
|
986
|
+
ctx.addMessage('system', `\u2713 sessionLogLimit set to ${num === 0 ? 'unlimited (set > 0 to cap)' : num}`);
|
|
813
987
|
}
|
|
814
988
|
else if (key === 'persona') {
|
|
815
989
|
if (!['calliope', 'muse', 'minimal'].includes(value)) {
|
|
@@ -947,6 +1121,10 @@ Usage:
|
|
|
947
1121
|
}
|
|
948
1122
|
case '/loop': {
|
|
949
1123
|
// Parse /loop "<prompt>" [--max-iterations N] [--completion-promise "text"]
|
|
1124
|
+
if (ctx.loopActive) {
|
|
1125
|
+
ctx.addMessage('system', 'Loop already running. Use /breakloop to stop it first.');
|
|
1126
|
+
break;
|
|
1127
|
+
}
|
|
950
1128
|
const loopArgs = parts.slice(1).join(' ');
|
|
951
1129
|
const maxIterMatch = loopArgs.match(/--max-iterations\s+(\d+)/);
|
|
952
1130
|
const completionMatch = loopArgs.match(/--completion-promise\s+"([^"]+)"/);
|
|
@@ -963,23 +1141,28 @@ Usage:
|
|
|
963
1141
|
Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE"`);
|
|
964
1142
|
break;
|
|
965
1143
|
}
|
|
1144
|
+
const defaultMaxIterations = resolveIterationLimit(config.get('maxIterations'));
|
|
1145
|
+
const loopMaxIterations = maxIterMatch
|
|
1146
|
+
? resolveIterationLimit(parseInt(maxIterMatch[1], 10))
|
|
1147
|
+
: defaultMaxIterations;
|
|
966
1148
|
// Start the loop
|
|
967
1149
|
ctx.setLoopActive(true);
|
|
968
1150
|
ctx.setLoopPrompt(prompt);
|
|
969
|
-
ctx.setLoopMaxIterations(
|
|
1151
|
+
ctx.setLoopMaxIterations(loopMaxIterations);
|
|
970
1152
|
ctx.setLoopCompletionPromise(completionMatch ? completionMatch[1] : undefined);
|
|
971
1153
|
ctx.setLoopIteration(0);
|
|
972
1154
|
ctx.loopCancelledRef.current = false;
|
|
973
1155
|
ctx.addMessage('system', `\u{1F504} Agent Loop Started
|
|
974
1156
|
Prompt: "${prompt.substring(0, 50)}${prompt.length > 50 ? '...' : ''}"
|
|
975
|
-
Max iterations: ${
|
|
976
|
-
${completionMatch ? `Completion promise: "${completionMatch[1]}"` : 'No completion promise (runs until max iterations)'}
|
|
977
|
-
Use /
|
|
1157
|
+
Max iterations: ${formatIterationLimit(loopMaxIterations)}
|
|
1158
|
+
${completionMatch ? `Completion promise: "${completionMatch[1]}"` : isFiniteIterationLimit(loopMaxIterations) ? 'No completion promise (runs until max iterations)' : 'No completion promise (runs until stopped)'}
|
|
1159
|
+
Use /breakloop to stop`);
|
|
978
1160
|
// Start the loop execution (non-blocking)
|
|
979
|
-
ctx.runLoop(prompt,
|
|
1161
|
+
ctx.runLoop(prompt, loopMaxIterations, completionMatch?.[1]);
|
|
980
1162
|
break;
|
|
981
1163
|
}
|
|
982
1164
|
case '/cancel-loop':
|
|
1165
|
+
case '/breakloop':
|
|
983
1166
|
case '/stop':
|
|
984
1167
|
if (ctx.loopActive) {
|
|
985
1168
|
ctx.loopCancelledRef.current = true;
|
|
@@ -1272,7 +1455,7 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
1272
1455
|
case '/branch': {
|
|
1273
1456
|
const branching = await import('../branching.js');
|
|
1274
1457
|
const subCmd = parts[1];
|
|
1275
|
-
const sessionId = ctx.sessionRef.current?.id ||
|
|
1458
|
+
const sessionId = ctx.sessionRef.current?.id || storage.createSessionId();
|
|
1276
1459
|
if (subCmd === 'list' || !subCmd) {
|
|
1277
1460
|
const tree = branching.getBranchTree(sessionId);
|
|
1278
1461
|
ctx.addMessage('system', `Branches:\n${tree}`);
|
|
@@ -1399,7 +1582,7 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
1399
1582
|
const newComp = getCurrentCompanion();
|
|
1400
1583
|
if (newComp.name === subCmd) {
|
|
1401
1584
|
config.set('activeCompanion', subCmd);
|
|
1402
|
-
ctx.llmMessages.current = [{ role: 'system', content:
|
|
1585
|
+
ctx.llmMessages.current = [{ role: 'system', content: buildFullSystemPrompt(ctx.persona, getActiveProjectDir(ctx)) }];
|
|
1403
1586
|
ctx.addMessage('system', `Companion set to: ${subCmd} \u2014 "${newComp.greeting}"`);
|
|
1404
1587
|
}
|
|
1405
1588
|
else {
|
|
@@ -1480,7 +1663,7 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
1480
1663
|
: pack.companions.immersive;
|
|
1481
1664
|
config.set('activeCompanion', companion.name);
|
|
1482
1665
|
// Reset LLM system prompt to use the companion's persona
|
|
1483
|
-
ctx.llmMessages.current = [{ role: 'system', content:
|
|
1666
|
+
ctx.llmMessages.current = [{ role: 'system', content: buildFullSystemPrompt(ctx.persona, getActiveProjectDir(ctx)) }];
|
|
1484
1667
|
ctx.addMessage('system', `Theme pack: ${subCmd}\n` +
|
|
1485
1668
|
` Skin: ${pack.skin.name}, Palette: ${pack.palette.name}, Companion: ${companion.name}\n` +
|
|
1486
1669
|
` "${companion.greeting}"`);
|
|
@@ -1499,7 +1682,7 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
1499
1682
|
const pack = getCurrentPack();
|
|
1500
1683
|
config.set('companionIntensity', 'professional');
|
|
1501
1684
|
config.set('activeCompanion', pack.companions.professional.name);
|
|
1502
|
-
ctx.llmMessages.current = [{ role: 'system', content:
|
|
1685
|
+
ctx.llmMessages.current = [{ role: 'system', content: buildFullSystemPrompt(ctx.persona, getActiveProjectDir(ctx)) }];
|
|
1503
1686
|
ctx.addMessage('system', `Switched to professional mode — ${pack.companions.professional.description}`);
|
|
1504
1687
|
}
|
|
1505
1688
|
else {
|
|
@@ -1512,7 +1695,7 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
1512
1695
|
const pack = getCurrentPack();
|
|
1513
1696
|
config.set('companionIntensity', 'immersive');
|
|
1514
1697
|
config.set('activeCompanion', pack.companions.immersive.name);
|
|
1515
|
-
ctx.llmMessages.current = [{ role: 'system', content:
|
|
1698
|
+
ctx.llmMessages.current = [{ role: 'system', content: buildFullSystemPrompt(ctx.persona, getActiveProjectDir(ctx)) }];
|
|
1516
1699
|
ctx.addMessage('system', `Switched to immersive mode — ${pack.companions.immersive.description}`);
|
|
1517
1700
|
}
|
|
1518
1701
|
else {
|
|
@@ -1768,7 +1951,22 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
1768
1951
|
if (session) {
|
|
1769
1952
|
const savedMessages = storage.loadMessageHistory();
|
|
1770
1953
|
const savedCount = savedMessages ? savedMessages.length : 0;
|
|
1771
|
-
|
|
1954
|
+
const ledgerTotals = ctx.ledger?.getTotals();
|
|
1955
|
+
const latestRun = ctx.ledger?.getLatestRun();
|
|
1956
|
+
const lines = [
|
|
1957
|
+
`Session: ${session.projectName}`,
|
|
1958
|
+
`ID: ${session.id}`,
|
|
1959
|
+
`Created: ${new Date(session.createdAt).toLocaleString()}`,
|
|
1960
|
+
`Messages: ${session.messageCount}`,
|
|
1961
|
+
`Saved LLM messages: ${savedCount}`,
|
|
1962
|
+
];
|
|
1963
|
+
if (ledgerTotals) {
|
|
1964
|
+
lines.push(`Iterations logged: ${ledgerTotals.iterations}`, `Failed approaches: ${ctx.ledger?.getFailedApproachCount() ?? 0}`);
|
|
1965
|
+
}
|
|
1966
|
+
if (latestRun) {
|
|
1967
|
+
lines.push(`Latest run: ${formatLedgerRun(latestRun)}`);
|
|
1968
|
+
}
|
|
1969
|
+
ctx.addMessage('system', lines.join('\n'));
|
|
1772
1970
|
}
|
|
1773
1971
|
else {
|
|
1774
1972
|
ctx.addMessage('system', 'No active session.');
|
|
@@ -1782,6 +1980,9 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
1782
1980
|
else {
|
|
1783
1981
|
// Save current messages before forking
|
|
1784
1982
|
storage.saveMessageHistory(ctx.llmMessages.current);
|
|
1983
|
+
if (ctx.ledger) {
|
|
1984
|
+
storage.saveIterationLedger(ctx.ledger);
|
|
1985
|
+
}
|
|
1785
1986
|
const forked = storage.forkSession(session.projectPath);
|
|
1786
1987
|
if (forked) {
|
|
1787
1988
|
ctx.sessionRef.current = forked;
|
|
@@ -1794,12 +1995,89 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
1794
1995
|
}
|
|
1795
1996
|
else if (parts[1] === 'save') {
|
|
1796
1997
|
storage.saveMessageHistory(ctx.llmMessages.current);
|
|
1797
|
-
ctx.
|
|
1998
|
+
if (ctx.ledger) {
|
|
1999
|
+
storage.saveIterationLedger(ctx.ledger);
|
|
2000
|
+
}
|
|
2001
|
+
ctx.addMessage('system', `Saved ${ctx.llmMessages.current.length} LLM messages and current log state to session.`);
|
|
1798
2002
|
}
|
|
1799
2003
|
else {
|
|
1800
2004
|
ctx.addMessage('system', 'Usage: /session [list|info|fork|save] or just /sessions');
|
|
1801
2005
|
}
|
|
1802
2006
|
break;
|
|
2007
|
+
case '/log': {
|
|
2008
|
+
if (!ctx.ledger) {
|
|
2009
|
+
ctx.addMessage('system', 'No session log available.');
|
|
2010
|
+
break;
|
|
2011
|
+
}
|
|
2012
|
+
const subCmd = parts[1] || 'summary';
|
|
2013
|
+
if (subCmd === 'summary') {
|
|
2014
|
+
const totals = ctx.ledger.getTotals();
|
|
2015
|
+
const runs = ctx.ledger.getRuns(5);
|
|
2016
|
+
const allFailures = ctx.ledger.getFailedApproaches();
|
|
2017
|
+
const failures = allFailures.slice(-5);
|
|
2018
|
+
const lines = [
|
|
2019
|
+
'Session Log',
|
|
2020
|
+
`Iterations: ${totals.iterations}`,
|
|
2021
|
+
`Failed approaches: ${ctx.ledger.getFailedApproachCount()}`,
|
|
2022
|
+
`Tokens: ${totals.totalTokens}`,
|
|
2023
|
+
`Cost: $${totals.totalCost.toFixed(4)}`,
|
|
2024
|
+
`Duration: ${formatLedgerDuration(totals.totalDurationMs)}`,
|
|
2025
|
+
];
|
|
2026
|
+
if (runs.length > 0) {
|
|
2027
|
+
lines.push('', 'Recent runs:');
|
|
2028
|
+
for (const run of runs) {
|
|
2029
|
+
lines.push(` - ${formatLedgerRun(run)}`);
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
if (failures.length > 0) {
|
|
2033
|
+
lines.push('', 'Recent failures:');
|
|
2034
|
+
for (const failure of failures) {
|
|
2035
|
+
lines.push(` - #${failure.iteration} ${failure.description} — ${failure.reason}`);
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
ctx.addMessage('system', lines.join('\n'));
|
|
2039
|
+
}
|
|
2040
|
+
else if (subCmd === 'tail') {
|
|
2041
|
+
const limit = parts[2] ? parseInt(parts[2], 10) : 10;
|
|
2042
|
+
if (isNaN(limit) || limit <= 0 || limit > 100) {
|
|
2043
|
+
ctx.addMessage('error', 'Usage: /log tail [1-100]');
|
|
2044
|
+
break;
|
|
2045
|
+
}
|
|
2046
|
+
const entries = ctx.ledger.getEntries().slice(-limit);
|
|
2047
|
+
if (entries.length === 0) {
|
|
2048
|
+
ctx.addMessage('system', 'No logged iterations yet.');
|
|
2049
|
+
break;
|
|
2050
|
+
}
|
|
2051
|
+
const lines = ['Recent iterations:'];
|
|
2052
|
+
for (const entry of entries) {
|
|
2053
|
+
const actions = entry.actions.length > 0
|
|
2054
|
+
? entry.actions.map(action => `${action.tool}(${action.args})${action.result === 'error' ? ' FAILED' : action.result === 'blocked' ? ' BLOCKED' : ''}`).join(', ')
|
|
2055
|
+
: 'no tool actions';
|
|
2056
|
+
lines.push(` #${entry.iteration} [${entry.outcome}] ${formatLedgerDuration(entry.durationMs)} — ${actions}`);
|
|
2057
|
+
}
|
|
2058
|
+
ctx.addMessage('system', lines.join('\n'));
|
|
2059
|
+
}
|
|
2060
|
+
else if (subCmd === 'failures') {
|
|
2061
|
+
const failures = ctx.ledger.getFailedApproaches();
|
|
2062
|
+
if (failures.length === 0) {
|
|
2063
|
+
ctx.addMessage('system', 'No failed approaches recorded.');
|
|
2064
|
+
break;
|
|
2065
|
+
}
|
|
2066
|
+
const lines = ['Failed approaches:'];
|
|
2067
|
+
for (const failure of failures.slice(-10)) {
|
|
2068
|
+
lines.push(` - #${failure.iteration} ${failure.description} — ${failure.reason}`);
|
|
2069
|
+
}
|
|
2070
|
+
ctx.addMessage('system', lines.join('\n'));
|
|
2071
|
+
}
|
|
2072
|
+
else if (subCmd === 'reset') {
|
|
2073
|
+
ctx.ledger.reset();
|
|
2074
|
+
ctx.addMessage('system', 'Session log reset.');
|
|
2075
|
+
}
|
|
2076
|
+
else {
|
|
2077
|
+
ctx.addMessage('system', 'Usage: /log [summary|tail [N]|failures|reset]');
|
|
2078
|
+
}
|
|
2079
|
+
break;
|
|
2080
|
+
}
|
|
1803
2081
|
case '/todo': {
|
|
1804
2082
|
const subCommand = parts[1];
|
|
1805
2083
|
if (subCommand === 'add' && parts.length > 2) {
|
|
@@ -2371,6 +2649,20 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
2371
2649
|
// Resume a session by loading saved LLM message history
|
|
2372
2650
|
// Usage: /resume [sessionId] - resume a specific session, or current session if no ID
|
|
2373
2651
|
const targetSessionId = parts[1];
|
|
2652
|
+
if (targetSessionId) {
|
|
2653
|
+
const resumedSession = storage.setCurrentSessionById(targetSessionId);
|
|
2654
|
+
if (!resumedSession) {
|
|
2655
|
+
ctx.addMessage('system', `Session not found: ${targetSessionId}`);
|
|
2656
|
+
break;
|
|
2657
|
+
}
|
|
2658
|
+
ctx.sessionRef.current = resumedSession;
|
|
2659
|
+
}
|
|
2660
|
+
if (ctx.ledger) {
|
|
2661
|
+
ctx.ledger.loadSnapshot(storage.loadIterationLedger(targetSessionId || ctx.sessionRef.current?.id));
|
|
2662
|
+
if (ctx.sessionRef.current?.id) {
|
|
2663
|
+
storage.saveIterationLedger(ctx.ledger, ctx.sessionRef.current.id);
|
|
2664
|
+
}
|
|
2665
|
+
}
|
|
2374
2666
|
// Try loading full message history first (preferred - preserves tool calls etc.)
|
|
2375
2667
|
const savedMessages = storage.loadMessageHistory(targetSessionId);
|
|
2376
2668
|
if (savedMessages && savedMessages.length > 0) {
|
|
@@ -2384,11 +2676,16 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
2384
2676
|
}
|
|
2385
2677
|
else {
|
|
2386
2678
|
// Fall back to chat.log history (legacy format, user/assistant only)
|
|
2387
|
-
const history = storage.getChatHistory(20);
|
|
2679
|
+
const history = storage.getChatHistory(20, targetSessionId);
|
|
2388
2680
|
if (history.length === 0) {
|
|
2389
2681
|
ctx.addMessage('system', 'No previous messages to resume. Start a conversation first, messages are auto-saved.');
|
|
2390
2682
|
}
|
|
2391
2683
|
else {
|
|
2684
|
+
ctx.llmMessages.current.length = 0;
|
|
2685
|
+
ctx.llmMessages.current.push({
|
|
2686
|
+
role: 'system',
|
|
2687
|
+
content: buildFullSystemPrompt(ctx.persona, getActiveProjectDir(ctx)),
|
|
2688
|
+
});
|
|
2392
2689
|
for (const msg of history) {
|
|
2393
2690
|
if (msg.role === 'user' || msg.role === 'assistant') {
|
|
2394
2691
|
ctx.llmMessages.current.push({
|
|
@@ -2442,19 +2739,234 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
2442
2739
|
}
|
|
2443
2740
|
else if (subCmd === 'off') {
|
|
2444
2741
|
config.set('circuitBreakersEnabled', false);
|
|
2445
|
-
|
|
2742
|
+
// Also disable the current circuit breaker instance if it exists
|
|
2743
|
+
if (ctx.circuitBreaker) {
|
|
2744
|
+
ctx.circuitBreaker = undefined;
|
|
2745
|
+
ctx.setBreakerHealth?.('ok');
|
|
2746
|
+
}
|
|
2747
|
+
ctx.addMessage('system', '\u2713 Circuit breakers disabled');
|
|
2446
2748
|
}
|
|
2447
2749
|
else if (subCmd === 'on') {
|
|
2448
2750
|
config.set('circuitBreakersEnabled', true);
|
|
2449
2751
|
ctx.addMessage('system', '\u2713 Circuit breakers enabled');
|
|
2450
2752
|
}
|
|
2753
|
+
else if (subCmd === 'adjust') {
|
|
2754
|
+
const breakerTypeString = parts[2];
|
|
2755
|
+
const param = parts[3];
|
|
2756
|
+
const rawValue = parts[4];
|
|
2757
|
+
// Handle special 'list' command to show detailed parameter info
|
|
2758
|
+
if (breakerTypeString === 'list') {
|
|
2759
|
+
ctx.addMessage('system', `Circuit Breaker Configuration Reference:
|
|
2760
|
+
|
|
2761
|
+
📊 REPEATED-FAILURE (Consecutive Errors)
|
|
2762
|
+
• maxConsecutiveErrors: Number of consecutive errors before tripping (default: 3)
|
|
2763
|
+
Example: /breaker adjust repeated-failure maxConsecutiveErrors 5
|
|
2764
|
+
|
|
2765
|
+
💰 COST-RUNAWAY (Spending Control)
|
|
2766
|
+
• maxSessionCost: Maximum total cost per session in USD (default: $5.0)
|
|
2767
|
+
• maxCostPerMinute: Maximum spending rate per minute in USD (default: $1.0)
|
|
2768
|
+
• windowSizeMs: Sliding window size for rate calculation in milliseconds (default: 60000)
|
|
2769
|
+
Examples:
|
|
2770
|
+
/breaker adjust cost-runaway maxSessionCost 10.0
|
|
2771
|
+
/breaker adjust cost-runaway maxCostPerMinute 2.0
|
|
2772
|
+
|
|
2773
|
+
🔄 INFINITE-LOOP (Repetitive Behavior)
|
|
2774
|
+
• maxIdenticalInWindow: Max identical tool calls in window before tripping (default: 3)
|
|
2775
|
+
• windowSize: Number of recent tool calls to analyze (default: 6)
|
|
2776
|
+
• detectOscillation: Detect A-B-A-B oscillation patterns (default: true)
|
|
2777
|
+
Examples:
|
|
2778
|
+
/breaker adjust infinite-loop maxIdenticalInWindow 5
|
|
2779
|
+
/breaker adjust infinite-loop detectOscillation false
|
|
2780
|
+
|
|
2781
|
+
🔥 TOKEN-BURN (Token Usage Limits)
|
|
2782
|
+
• maxTokensPerIteration: Max tokens per single iteration (default: 200,000)
|
|
2783
|
+
• maxTotalTokens: Max total tokens per session (default: 5,000,000)
|
|
2784
|
+
Examples:
|
|
2785
|
+
/breaker adjust token-burn maxTokensPerIteration 100000
|
|
2786
|
+
/breaker adjust token-burn maxTotalTokens 1000000
|
|
2787
|
+
|
|
2788
|
+
⏸️ STALL (Progress Detection)
|
|
2789
|
+
• maxIdleIterations: Max iterations with no tool calls/content (default: 5)
|
|
2790
|
+
Example: /breaker adjust stall maxIdleIterations 3
|
|
2791
|
+
|
|
2792
|
+
⏰ WALL-CLOCK (Time Limits)
|
|
2793
|
+
• maxSessionDurationMs: Max session duration in milliseconds (0 = unlimited, default: 0)
|
|
2794
|
+
• maxIterationDurationMs: Max single iteration duration in milliseconds (default: 600000 = 10 min)
|
|
2795
|
+
Examples:
|
|
2796
|
+
/breaker adjust wall-clock maxSessionDurationMs 3600000 # 1 hour
|
|
2797
|
+
/breaker adjust wall-clock maxIterationDurationMs 300000 # 5 minutes
|
|
2798
|
+
|
|
2799
|
+
Quick Commands:
|
|
2800
|
+
/breaker adjust <type> - Show current settings for that type
|
|
2801
|
+
/breaker adjust - Show types overview
|
|
2802
|
+
/breaker status - Show current breaker states`);
|
|
2803
|
+
break;
|
|
2804
|
+
}
|
|
2805
|
+
// Show basic types overview if no type specified
|
|
2806
|
+
if (!breakerTypeString) {
|
|
2807
|
+
ctx.addMessage('system', `Circuit Breaker Types Available for Configuration:
|
|
2808
|
+
|
|
2809
|
+
repeated-failure - Consecutive errors before tripping
|
|
2810
|
+
cost-runaway - Spending rate and total cost limits
|
|
2811
|
+
infinite-loop - Identical tool calls and oscillation detection
|
|
2812
|
+
token-burn - Token usage per iteration and total limits
|
|
2813
|
+
stall - Idle iterations without progress
|
|
2814
|
+
wall-clock - Time-based session and iteration limits
|
|
2815
|
+
|
|
2816
|
+
Usage: /breaker adjust <type> [param] [value]
|
|
2817
|
+
/breaker adjust list - Show detailed parameter reference
|
|
2818
|
+
|
|
2819
|
+
Examples:
|
|
2820
|
+
/breaker adjust repeated-failure - Show current settings
|
|
2821
|
+
/breaker adjust cost-runaway maxSessionCost 10.0
|
|
2822
|
+
/breaker adjust infinite-loop detectOscillation true`);
|
|
2823
|
+
break;
|
|
2824
|
+
}
|
|
2825
|
+
// Cast to BreakerType and validate
|
|
2826
|
+
const breakerType = breakerTypeString;
|
|
2827
|
+
const validTypes = ['repeated-failure', 'cost-runaway', 'infinite-loop', 'token-burn', 'stall', 'wall-clock'];
|
|
2828
|
+
if (!validTypes.includes(breakerType)) {
|
|
2829
|
+
ctx.addMessage('error', `Invalid breaker type "${breakerType}". Valid types: ${validTypes.join(', ')}`);
|
|
2830
|
+
break;
|
|
2831
|
+
}
|
|
2832
|
+
const currentConfig = ctx.circuitBreaker.getConfig();
|
|
2833
|
+
const breakerConfig = currentConfig.breakers[breakerType];
|
|
2834
|
+
// Show current configuration if no param specified
|
|
2835
|
+
if (!param) {
|
|
2836
|
+
let configDisplay = `${breakerType} Circuit Breaker Settings:\n`;
|
|
2837
|
+
switch (breakerType) {
|
|
2838
|
+
case 'repeated-failure':
|
|
2839
|
+
configDisplay += ` maxConsecutiveErrors: ${breakerConfig.maxConsecutiveErrors} errors\n\nUsage: /breaker adjust repeated-failure <param> <value>\n /breaker adjust repeated-failure maxConsecutiveErrors <number>`;
|
|
2840
|
+
break;
|
|
2841
|
+
case 'cost-runaway':
|
|
2842
|
+
configDisplay += ` maxSessionCost: ${breakerConfig.maxSessionCost} per session\n maxCostPerMinute: ${breakerConfig.maxCostPerMinute} per minute\n windowSizeMs: ${breakerConfig.windowSizeMs}ms\n\nUsage: /breaker adjust cost-runaway <param> <value>\n /breaker adjust cost-runaway maxSessionCost <dollars>\n /breaker adjust cost-runaway maxCostPerMinute <dollars>\n /breaker adjust cost-runaway windowSizeMs <milliseconds>`;
|
|
2843
|
+
break;
|
|
2844
|
+
case 'infinite-loop':
|
|
2845
|
+
configDisplay += ` maxIdenticalInWindow: ${breakerConfig.maxIdenticalInWindow} calls\n windowSize: ${breakerConfig.windowSize} recent calls\n detectOscillation: ${breakerConfig.detectOscillation}\n\nUsage: /breaker adjust infinite-loop <param> <value>\n /breaker adjust infinite-loop maxIdenticalInWindow <number>\n /breaker adjust infinite-loop windowSize <number>\n /breaker adjust infinite-loop detectOscillation <true|false>`;
|
|
2846
|
+
break;
|
|
2847
|
+
case 'token-burn':
|
|
2848
|
+
configDisplay += ` maxTokensPerIteration: ${breakerConfig.maxTokensPerIteration.toLocaleString()} tokens\n maxTotalTokens: ${breakerConfig.maxTotalTokens.toLocaleString()} tokens\n\nUsage: /breaker adjust token-burn <param> <value>\n /breaker adjust token-burn maxTokensPerIteration <number>\n /breaker adjust token-burn maxTotalTokens <number>`;
|
|
2849
|
+
break;
|
|
2850
|
+
case 'stall':
|
|
2851
|
+
configDisplay += ` maxIdleIterations: ${breakerConfig.maxIdleIterations} iterations\n\nUsage: /breaker adjust stall <param> <value>\n /breaker adjust stall maxIdleIterations <number>`;
|
|
2852
|
+
break;
|
|
2853
|
+
case 'wall-clock':
|
|
2854
|
+
const sessionDuration = breakerConfig.maxSessionDurationMs;
|
|
2855
|
+
const iterationDuration = breakerConfig.maxIterationDurationMs;
|
|
2856
|
+
configDisplay += ` maxSessionDurationMs: ${sessionDuration === 0 ? 'unlimited' : sessionDuration + 'ms'}\n maxIterationDurationMs: ${iterationDuration}ms (${Math.round(iterationDuration / 60000)} minutes)\n\nUsage: /breaker adjust wall-clock <param> <value>\n /breaker adjust wall-clock maxSessionDurationMs <milliseconds>\n /breaker adjust wall-clock maxIterationDurationMs <milliseconds>`;
|
|
2857
|
+
break;
|
|
2858
|
+
}
|
|
2859
|
+
ctx.addMessage('system', configDisplay);
|
|
2860
|
+
break;
|
|
2861
|
+
}
|
|
2862
|
+
// Parse and validate the value
|
|
2863
|
+
let parsedValue;
|
|
2864
|
+
// Handle boolean parameters
|
|
2865
|
+
if (param === 'detectOscillation') {
|
|
2866
|
+
if (rawValue === 'true')
|
|
2867
|
+
parsedValue = true;
|
|
2868
|
+
else if (rawValue === 'false')
|
|
2869
|
+
parsedValue = false;
|
|
2870
|
+
else {
|
|
2871
|
+
ctx.addMessage('error', 'detectOscillation must be "true" or "false"');
|
|
2872
|
+
break;
|
|
2873
|
+
}
|
|
2874
|
+
}
|
|
2875
|
+
else {
|
|
2876
|
+
// Handle numeric parameters
|
|
2877
|
+
parsedValue = parseFloat(rawValue);
|
|
2878
|
+
if (isNaN(parsedValue) || parsedValue < 0) {
|
|
2879
|
+
ctx.addMessage('error', 'Value must be a non-negative number');
|
|
2880
|
+
break;
|
|
2881
|
+
}
|
|
2882
|
+
}
|
|
2883
|
+
// Validate parameter names for each breaker type
|
|
2884
|
+
const paramValidations = {
|
|
2885
|
+
'repeated-failure': {
|
|
2886
|
+
params: ['maxConsecutiveErrors'],
|
|
2887
|
+
validate: (param, value) => value <= 0 ? 'maxConsecutiveErrors must be > 0' : null
|
|
2888
|
+
},
|
|
2889
|
+
'cost-runaway': {
|
|
2890
|
+
params: ['maxSessionCost', 'maxCostPerMinute', 'windowSizeMs'],
|
|
2891
|
+
validate: (param, value) => value <= 0 ? `${param} must be > 0` : null
|
|
2892
|
+
},
|
|
2893
|
+
'infinite-loop': {
|
|
2894
|
+
params: ['maxIdenticalInWindow', 'windowSize', 'detectOscillation'],
|
|
2895
|
+
validate: (param, value) => {
|
|
2896
|
+
if (param === 'detectOscillation')
|
|
2897
|
+
return null; // boolean is already validated above
|
|
2898
|
+
return value <= 0 ? `${param} must be > 0` : null;
|
|
2899
|
+
}
|
|
2900
|
+
},
|
|
2901
|
+
'token-burn': {
|
|
2902
|
+
params: ['maxTokensPerIteration', 'maxTotalTokens'],
|
|
2903
|
+
validate: (param, value) => value <= 0 ? `${param} must be > 0` : null
|
|
2904
|
+
},
|
|
2905
|
+
'stall': {
|
|
2906
|
+
params: ['maxIdleIterations'],
|
|
2907
|
+
validate: (param, value) => value <= 0 ? 'maxIdleIterations must be > 0' : null
|
|
2908
|
+
},
|
|
2909
|
+
'wall-clock': {
|
|
2910
|
+
params: ['maxSessionDurationMs', 'maxIterationDurationMs'],
|
|
2911
|
+
validate: (param, value) => {
|
|
2912
|
+
if (param === 'maxSessionDurationMs' && value === 0)
|
|
2913
|
+
return null; // 0 = unlimited is valid
|
|
2914
|
+
return value < 0 ? `${param} cannot be negative` : null;
|
|
2915
|
+
}
|
|
2916
|
+
}
|
|
2917
|
+
};
|
|
2918
|
+
const validation = paramValidations[breakerType];
|
|
2919
|
+
if (!validation.params.includes(param)) {
|
|
2920
|
+
ctx.addMessage('error', `Invalid parameter "${param}" for ${breakerType}. Valid parameters: ${validation.params.join(', ')}`);
|
|
2921
|
+
break;
|
|
2922
|
+
}
|
|
2923
|
+
// Run custom validation if provided
|
|
2924
|
+
if (validation.validate) {
|
|
2925
|
+
const error = validation.validate(param, parsedValue);
|
|
2926
|
+
if (error) {
|
|
2927
|
+
ctx.addMessage('error', error);
|
|
2928
|
+
break;
|
|
2929
|
+
}
|
|
2930
|
+
}
|
|
2931
|
+
// Update the configuration
|
|
2932
|
+
const oldValue = breakerConfig[param];
|
|
2933
|
+
ctx.circuitBreaker.adjust(breakerType, { [param]: parsedValue });
|
|
2934
|
+
// Format the success message based on parameter type
|
|
2935
|
+
let formattedOld, formattedNew;
|
|
2936
|
+
if (param === 'detectOscillation') {
|
|
2937
|
+
formattedOld = String(oldValue);
|
|
2938
|
+
formattedNew = String(parsedValue);
|
|
2939
|
+
}
|
|
2940
|
+
else if (param.includes('Cost')) {
|
|
2941
|
+
formattedOld = `${oldValue}`;
|
|
2942
|
+
formattedNew = `${parsedValue}`;
|
|
2943
|
+
}
|
|
2944
|
+
else if (param.includes('Ms')) {
|
|
2945
|
+
formattedOld = oldValue === 0 ? 'unlimited' : `${oldValue}ms`;
|
|
2946
|
+
formattedNew = parsedValue === 0 ? 'unlimited' : `${parsedValue}ms`;
|
|
2947
|
+
}
|
|
2948
|
+
else if (param.includes('Tokens')) {
|
|
2949
|
+
formattedOld = oldValue.toLocaleString();
|
|
2950
|
+
formattedNew = parsedValue.toLocaleString();
|
|
2951
|
+
}
|
|
2952
|
+
else {
|
|
2953
|
+
formattedOld = String(oldValue);
|
|
2954
|
+
formattedNew = String(parsedValue);
|
|
2955
|
+
}
|
|
2956
|
+
ctx.addMessage('system', `✅ ${breakerType} ${param}: ${formattedOld} → ${formattedNew}`);
|
|
2957
|
+
}
|
|
2451
2958
|
else {
|
|
2452
|
-
ctx.addMessage('system', `Usage: /breaker [status|resume|reset|on|off]
|
|
2959
|
+
ctx.addMessage('system', `Usage: /breaker [status|resume|reset|adjust|on|off]
|
|
2453
2960
|
/breaker resume [type] - Resume tripped breaker (half-open)
|
|
2454
2961
|
/breaker reset [type] - Reset breaker to closed
|
|
2962
|
+
/breaker adjust [type] [param] [value] - Configure breaker thresholds
|
|
2455
2963
|
/breaker on|off - Enable/disable circuit breakers
|
|
2456
2964
|
|
|
2457
|
-
Breaker types: repeated-failure, cost-runaway, infinite-loop, token-burn, stall
|
|
2965
|
+
Breaker types: repeated-failure, cost-runaway, infinite-loop, token-burn, stall, wall-clock
|
|
2966
|
+
|
|
2967
|
+
Quick help:
|
|
2968
|
+
/breaker adjust - Show types overview
|
|
2969
|
+
/breaker adjust list - Show detailed parameter reference`);
|
|
2458
2970
|
}
|
|
2459
2971
|
break;
|
|
2460
2972
|
}
|
|
@@ -2523,6 +3035,7 @@ Usage: /smart [on|off|cost <0-1>|test <message>]
|
|
|
2523
3035
|
case '/swarm':
|
|
2524
3036
|
case '/council': {
|
|
2525
3037
|
const subCmd = parts[1];
|
|
3038
|
+
const swarmCwd = ctx.sessionRef.current?.projectPath ?? process.cwd();
|
|
2526
3039
|
if (!ctx.agtermEnabled) {
|
|
2527
3040
|
ctx.addMessage('system', 'Agents mode not enabled. Start with --agents flag to use agent swarms.');
|
|
2528
3041
|
break;
|
|
@@ -2603,7 +3116,7 @@ Usage: /smart [on|off|cost <0-1>|test <message>]
|
|
|
2603
3116
|
try {
|
|
2604
3117
|
let session;
|
|
2605
3118
|
if (template) {
|
|
2606
|
-
session = await councilManager.startFromTemplate(template, cleanPrompt);
|
|
3119
|
+
session = await councilManager.startFromTemplate(template, cleanPrompt, swarmCwd);
|
|
2607
3120
|
}
|
|
2608
3121
|
else {
|
|
2609
3122
|
const { randomUUID } = await import('crypto');
|
|
@@ -2612,8 +3125,9 @@ Usage: /smart [on|off|cost <0-1>|test <message>]
|
|
|
2612
3125
|
{ id: randomUUID(), name: 'Agent B', agent: 'claude', weight: 1.0 },
|
|
2613
3126
|
{ id: randomUUID(), name: 'Agent C', agent: 'claude', weight: 1.0 },
|
|
2614
3127
|
];
|
|
2615
|
-
session = await councilManager.startCouncil(cleanPrompt, { mode, members });
|
|
3128
|
+
session = await councilManager.startCouncil(cleanPrompt, { mode, members }, swarmCwd);
|
|
2616
3129
|
}
|
|
3130
|
+
watchAsyncLedgerRun(ctx.ledger, 'council', cleanPrompt, () => councilManager.getSession(session.id));
|
|
2617
3131
|
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.`);
|
|
2618
3132
|
}
|
|
2619
3133
|
catch (err) {
|
|
@@ -2674,7 +3188,8 @@ Options:
|
|
|
2674
3188
|
cleanPrompt = cleanPrompt.replace(aggMatch[0], '').trim();
|
|
2675
3189
|
}
|
|
2676
3190
|
try {
|
|
2677
|
-
const session = await swarmManager.startSwarm(cleanPrompt, { decomposition: strategy, aggregation });
|
|
3191
|
+
const session = await swarmManager.startSwarm(cleanPrompt, { decomposition: strategy, aggregation }, swarmCwd);
|
|
3192
|
+
watchAsyncLedgerRun(ctx.ledger, 'swarm', cleanPrompt, () => swarmManager.getSession(session.id));
|
|
2678
3193
|
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.`);
|
|
2679
3194
|
}
|
|
2680
3195
|
catch (err) {
|
|
@@ -2858,14 +3373,16 @@ Requires --agents flag.`);
|
|
|
2858
3373
|
runJob(bgJob.id, async (prompt, signal) => {
|
|
2859
3374
|
const { chat } = await import('../providers/index.js');
|
|
2860
3375
|
const { TOOLS } = await import('../tools.js');
|
|
2861
|
-
const {
|
|
3376
|
+
const { executeTool: execTool } = await import('../tools.js');
|
|
3377
|
+
const cwd = ctx.sessionRef.current?.projectPath || process.cwd();
|
|
3378
|
+
const maxIterations = resolveIterationLimit(config.get('maxIterations'));
|
|
2862
3379
|
const bgMessages = [
|
|
2863
|
-
{ role: 'system', content:
|
|
3380
|
+
{ role: 'system', content: buildFullSystemPrompt(ctx.persona, cwd) },
|
|
2864
3381
|
{ role: 'user', content: prompt },
|
|
2865
3382
|
];
|
|
2866
3383
|
let iterations = 0;
|
|
2867
3384
|
let lastContent = '';
|
|
2868
|
-
while (iterations <
|
|
3385
|
+
while (!signal.aborted && iterations < maxIterations) {
|
|
2869
3386
|
iterations++;
|
|
2870
3387
|
const response = await chat(ctx.provider, bgMessages, TOOLS, ctx.model);
|
|
2871
3388
|
if (!response.toolCalls?.length) {
|
|
@@ -2873,12 +3390,18 @@ Requires --agents flag.`);
|
|
|
2873
3390
|
break;
|
|
2874
3391
|
}
|
|
2875
3392
|
bgMessages.push({ role: 'assistant', content: response.content, toolCalls: response.toolCalls });
|
|
2876
|
-
const { executeTool: execTool } = await import('../tools.js');
|
|
2877
3393
|
for (const tc of response.toolCalls) {
|
|
2878
|
-
const result = await execTool(tc,
|
|
3394
|
+
const result = await execTool(tc, cwd);
|
|
2879
3395
|
bgMessages.push({ role: 'tool', content: result.result, toolCallId: tc.id });
|
|
3396
|
+
if (signal.aborted)
|
|
3397
|
+
break;
|
|
2880
3398
|
}
|
|
2881
3399
|
}
|
|
3400
|
+
if (signal.aborted) {
|
|
3401
|
+
const error = new Error('Background job cancelled');
|
|
3402
|
+
error.name = 'AbortError';
|
|
3403
|
+
throw error;
|
|
3404
|
+
}
|
|
2882
3405
|
return { result: lastContent, iterations };
|
|
2883
3406
|
}).then(completed => {
|
|
2884
3407
|
ctx.addMessage('system', `Background job ${completed.id} ${completed.status}: ${completed.result?.slice(0, 200) || completed.error || 'done'}`);
|