@calliopelabs/cli 2.2.0 → 2.3.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/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/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/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.map +1 -1
- package/dist/api-server.js +1 -1
- package/dist/api-server.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 +58 -5
- 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.js +4 -0
- package/dist/bin.js.map +1 -1
- package/dist/cli/agent.d.ts.map +1 -1
- package/dist/cli/agent.js +111 -59
- package/dist/cli/agent.js.map +1 -1
- package/dist/cli/commands.d.ts.map +1 -1
- package/dist/cli/commands.js +119 -11
- package/dist/cli/commands.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +23 -5
- 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 +13 -1
- package/dist/config.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/headless.d.ts.map +1 -1
- package/dist/headless.js +3 -2
- package/dist/headless.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/mcp.d.ts.map +1 -1
- package/dist/mcp.js +6 -1
- 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/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/compat.d.ts.map +1 -1
- package/dist/providers/compat.js +21 -6
- package/dist/providers/compat.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/storage.d.ts +20 -1
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +101 -9
- package/dist/storage.js.map +1 -1
- package/dist/tools.js +1 -1
- package/dist/tools.js.map +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +5 -8
- package/dist/types.js.map +1 -1
- package/dist/ui/agent.d.ts.map +1 -1
- package/dist/ui/agent.js +140 -77
- package/dist/ui/agent.js.map +1 -1
- package/dist/ui/commands.d.ts +2 -0
- package/dist/ui/commands.d.ts.map +1 -1
- package/dist/ui/commands.js +244 -29
- 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 +52 -14
- package/dist/ui/index.js.map +1 -1
- package/package.json +1 -1
package/dist/ui/commands.js
CHANGED
|
@@ -28,6 +28,74 @@ import { getTerminalImageInfo, getImageModeLabel, renderSkinBanner, renderTransi
|
|
|
28
28
|
import { applyThemePack, listThemePacks, getCurrentPack, getCompanionMode, setCompanionMode, getThemePack } from '../hud/theme-packs/api.js';
|
|
29
29
|
import { getModelContextLimit } from '../model-detection.js';
|
|
30
30
|
import { resetContextWarnings } from './context.js';
|
|
31
|
+
import * as memory from '../memory.js';
|
|
32
|
+
import { resolveIterationLimit, formatIterationLimit, isFiniteIterationLimit, } from '../iteration-limit.js';
|
|
33
|
+
// Builds the full system prompt including memory context (project + global).
|
|
34
|
+
// dir should be the project directory for the active/resumed session.
|
|
35
|
+
function buildFullSystemPrompt(persona, dir) {
|
|
36
|
+
const base = getSystemPrompt(persona);
|
|
37
|
+
const mem = memory.buildMemoryContext(dir);
|
|
38
|
+
return mem.trim() ? base + '\n\n--- Project Context ---\n' + mem : base;
|
|
39
|
+
}
|
|
40
|
+
function getActiveProjectDir(ctx) {
|
|
41
|
+
return ctx.sessionRef.current?.projectPath ?? process.cwd();
|
|
42
|
+
}
|
|
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
|
+
function formatSessionLogLimit(limit) {
|
|
51
|
+
return limit > 0 ? String(limit) : 'unlimited';
|
|
52
|
+
}
|
|
53
|
+
function formatLedgerRun(run) {
|
|
54
|
+
const iterationCount = Math.max(0, (run.entryCountAtEnd ?? run.entryCountAtStart) - run.entryCountAtStart);
|
|
55
|
+
const parts = [`${run.kind}`, `[${run.status}]`];
|
|
56
|
+
if (iterationCount > 0) {
|
|
57
|
+
parts.push(`${iterationCount} iteration${iterationCount === 1 ? '' : 's'}`);
|
|
58
|
+
}
|
|
59
|
+
if (run.maxIterations != null && Number.isFinite(run.maxIterations)) {
|
|
60
|
+
parts.push(`max ${run.maxIterations}`);
|
|
61
|
+
}
|
|
62
|
+
else if (run.maxIterations === null) {
|
|
63
|
+
parts.push('unlimited');
|
|
64
|
+
}
|
|
65
|
+
const prompt = run.prompt.length > 90 ? `${run.prompt.slice(0, 90)}...` : run.prompt;
|
|
66
|
+
let line = `${parts.join(' ')} — ${prompt}`;
|
|
67
|
+
if (run.errorSummary) {
|
|
68
|
+
line += ` (${run.errorSummary})`;
|
|
69
|
+
}
|
|
70
|
+
return line;
|
|
71
|
+
}
|
|
72
|
+
function watchAsyncLedgerRun(ledger, kind, prompt, getStatus) {
|
|
73
|
+
if (!ledger)
|
|
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
|
+
})();
|
|
98
|
+
}
|
|
31
99
|
// ============================================================================
|
|
32
100
|
// handleCommand
|
|
33
101
|
// ============================================================================
|
|
@@ -57,6 +125,7 @@ export async function handleCommand(cmd, ctx) {
|
|
|
57
125
|
|
|
58
126
|
--- Session & State ---
|
|
59
127
|
/session [list|info|fork|save] - Session management (/sessions)
|
|
128
|
+
/log [summary|tail|failures|reset] - Iteration/run log
|
|
60
129
|
/resume [sessionId] - Resume session (restores full context)
|
|
61
130
|
/checkpoint [list|clear] - File checkpoints (/cp)
|
|
62
131
|
/restore <path> [index] - Restore file from checkpoint
|
|
@@ -103,8 +172,8 @@ export async function handleCommand(cmd, ctx) {
|
|
|
103
172
|
--- Multi-Agent ---
|
|
104
173
|
/agents - Sub-agent status (--agents mode)
|
|
105
174
|
/swarm [start|coord|status] - Agent swarms & coordination
|
|
106
|
-
/loop [prompt]
|
|
107
|
-
/cancel-loop - Stop running loop (/stop)
|
|
175
|
+
/loop [prompt] - Iterative agent loop (default: unlimited)
|
|
176
|
+
/cancel-loop - Stop running loop (/stop, /breakloop)
|
|
108
177
|
|
|
109
178
|
--- System ---
|
|
110
179
|
/status - Show status (/s)
|
|
@@ -202,7 +271,7 @@ Modes: Plan | Hybrid | Work | Auto-route: ${ctx.autoRoute ? 'ON' : 'OFF'}${ctx.a
|
|
|
202
271
|
if (parts[1] && ['calliope', 'muse', 'minimal'].includes(parts[1])) {
|
|
203
272
|
const p = parts[1];
|
|
204
273
|
ctx.setPersona(p);
|
|
205
|
-
ctx.llmMessages.current = [{ role: 'system', content:
|
|
274
|
+
ctx.llmMessages.current = [{ role: 'system', content: buildFullSystemPrompt(p, getActiveProjectDir(ctx)) }];
|
|
206
275
|
ctx.addMessage('system', `Persona: ${p}`);
|
|
207
276
|
}
|
|
208
277
|
else {
|
|
@@ -212,7 +281,8 @@ Modes: Plan | Hybrid | Work | Auto-route: ${ctx.autoRoute ? 'ON' : 'OFF'}${ctx.a
|
|
|
212
281
|
case '/clear':
|
|
213
282
|
case '/c':
|
|
214
283
|
ctx.setMessages([]);
|
|
215
|
-
ctx.llmMessages.current = [{ role: 'system', content:
|
|
284
|
+
ctx.llmMessages.current = [{ role: 'system', content: buildFullSystemPrompt(ctx.persona, getActiveProjectDir(ctx)) }];
|
|
285
|
+
ctx.ledger?.reset();
|
|
216
286
|
ctx.setStats({ inputTokens: 0, outputTokens: 0, cost: 0, messageCount: 0 });
|
|
217
287
|
resetContextWarnings(); // Reset context warning state
|
|
218
288
|
break;
|
|
@@ -344,7 +414,7 @@ Modes: Plan | Hybrid | Work | Auto-route: ${ctx.autoRoute ? 'ON' : 'OFF'}${ctx.a
|
|
|
344
414
|
break;
|
|
345
415
|
}
|
|
346
416
|
case '/config':
|
|
347
|
-
ctx.addMessage('system', `Config: ${config.getConfigPath()}\nProviders: ${config.getConfiguredProviders().join(', ') || 'none'}\nmaxIterations: ${config.get('maxIterations')}`);
|
|
417
|
+
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
418
|
break;
|
|
349
419
|
case '/agents': {
|
|
350
420
|
if (!ctx.agtermEnabled) {
|
|
@@ -797,6 +867,7 @@ Edit the YAML to customize members, strategy, and coordination settings.`);
|
|
|
797
867
|
ctx.addMessage('system', `Usage: /set <key> <value>
|
|
798
868
|
Available keys:
|
|
799
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)
|
|
800
871
|
persona <name> - calliope, muse, minimal
|
|
801
872
|
fancyOutput <bool> - true/false`);
|
|
802
873
|
break;
|
|
@@ -804,12 +875,22 @@ Available keys:
|
|
|
804
875
|
try {
|
|
805
876
|
if (key === 'maxIterations') {
|
|
806
877
|
const num = parseInt(value, 10);
|
|
807
|
-
if (isNaN(num) || num <
|
|
808
|
-
ctx.addMessage('error', 'maxIterations must be
|
|
878
|
+
if (isNaN(num) || num < 0 || num > 1000000) {
|
|
879
|
+
ctx.addMessage('error', 'maxIterations must be 0-1000000 (0 = unlimited)');
|
|
809
880
|
break;
|
|
810
881
|
}
|
|
811
882
|
config.set('maxIterations', num);
|
|
812
|
-
ctx.addMessage('system', `\u2713 maxIterations set to ${num}`);
|
|
883
|
+
ctx.addMessage('system', `\u2713 maxIterations set to ${formatIterationLimit(resolveIterationLimit(num))}`);
|
|
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;
|
|
890
|
+
}
|
|
891
|
+
config.set('sessionLogLimit', num);
|
|
892
|
+
ctx.ledger?.setRetentionLimit(num);
|
|
893
|
+
ctx.addMessage('system', `\u2713 sessionLogLimit set to ${num === 0 ? 'unlimited (set > 0 to cap)' : num}`);
|
|
813
894
|
}
|
|
814
895
|
else if (key === 'persona') {
|
|
815
896
|
if (!['calliope', 'muse', 'minimal'].includes(value)) {
|
|
@@ -947,6 +1028,10 @@ Usage:
|
|
|
947
1028
|
}
|
|
948
1029
|
case '/loop': {
|
|
949
1030
|
// Parse /loop "<prompt>" [--max-iterations N] [--completion-promise "text"]
|
|
1031
|
+
if (ctx.loopActive) {
|
|
1032
|
+
ctx.addMessage('system', 'Loop already running. Use /breakloop to stop it first.');
|
|
1033
|
+
break;
|
|
1034
|
+
}
|
|
950
1035
|
const loopArgs = parts.slice(1).join(' ');
|
|
951
1036
|
const maxIterMatch = loopArgs.match(/--max-iterations\s+(\d+)/);
|
|
952
1037
|
const completionMatch = loopArgs.match(/--completion-promise\s+"([^"]+)"/);
|
|
@@ -963,23 +1048,28 @@ Usage:
|
|
|
963
1048
|
Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE"`);
|
|
964
1049
|
break;
|
|
965
1050
|
}
|
|
1051
|
+
const defaultMaxIterations = resolveIterationLimit(config.get('maxIterations'));
|
|
1052
|
+
const loopMaxIterations = maxIterMatch
|
|
1053
|
+
? resolveIterationLimit(parseInt(maxIterMatch[1], 10))
|
|
1054
|
+
: defaultMaxIterations;
|
|
966
1055
|
// Start the loop
|
|
967
1056
|
ctx.setLoopActive(true);
|
|
968
1057
|
ctx.setLoopPrompt(prompt);
|
|
969
|
-
ctx.setLoopMaxIterations(
|
|
1058
|
+
ctx.setLoopMaxIterations(loopMaxIterations);
|
|
970
1059
|
ctx.setLoopCompletionPromise(completionMatch ? completionMatch[1] : undefined);
|
|
971
1060
|
ctx.setLoopIteration(0);
|
|
972
1061
|
ctx.loopCancelledRef.current = false;
|
|
973
1062
|
ctx.addMessage('system', `\u{1F504} Agent Loop Started
|
|
974
1063
|
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 /
|
|
1064
|
+
Max iterations: ${formatIterationLimit(loopMaxIterations)}
|
|
1065
|
+
${completionMatch ? `Completion promise: "${completionMatch[1]}"` : isFiniteIterationLimit(loopMaxIterations) ? 'No completion promise (runs until max iterations)' : 'No completion promise (runs until stopped)'}
|
|
1066
|
+
Use /breakloop to stop`);
|
|
978
1067
|
// Start the loop execution (non-blocking)
|
|
979
|
-
ctx.runLoop(prompt,
|
|
1068
|
+
ctx.runLoop(prompt, loopMaxIterations, completionMatch?.[1]);
|
|
980
1069
|
break;
|
|
981
1070
|
}
|
|
982
1071
|
case '/cancel-loop':
|
|
1072
|
+
case '/breakloop':
|
|
983
1073
|
case '/stop':
|
|
984
1074
|
if (ctx.loopActive) {
|
|
985
1075
|
ctx.loopCancelledRef.current = true;
|
|
@@ -1272,7 +1362,7 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
1272
1362
|
case '/branch': {
|
|
1273
1363
|
const branching = await import('../branching.js');
|
|
1274
1364
|
const subCmd = parts[1];
|
|
1275
|
-
const sessionId = ctx.sessionRef.current?.id ||
|
|
1365
|
+
const sessionId = ctx.sessionRef.current?.id || storage.createSessionId();
|
|
1276
1366
|
if (subCmd === 'list' || !subCmd) {
|
|
1277
1367
|
const tree = branching.getBranchTree(sessionId);
|
|
1278
1368
|
ctx.addMessage('system', `Branches:\n${tree}`);
|
|
@@ -1399,7 +1489,7 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
1399
1489
|
const newComp = getCurrentCompanion();
|
|
1400
1490
|
if (newComp.name === subCmd) {
|
|
1401
1491
|
config.set('activeCompanion', subCmd);
|
|
1402
|
-
ctx.llmMessages.current = [{ role: 'system', content:
|
|
1492
|
+
ctx.llmMessages.current = [{ role: 'system', content: buildFullSystemPrompt(ctx.persona, getActiveProjectDir(ctx)) }];
|
|
1403
1493
|
ctx.addMessage('system', `Companion set to: ${subCmd} \u2014 "${newComp.greeting}"`);
|
|
1404
1494
|
}
|
|
1405
1495
|
else {
|
|
@@ -1480,7 +1570,7 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
1480
1570
|
: pack.companions.immersive;
|
|
1481
1571
|
config.set('activeCompanion', companion.name);
|
|
1482
1572
|
// Reset LLM system prompt to use the companion's persona
|
|
1483
|
-
ctx.llmMessages.current = [{ role: 'system', content:
|
|
1573
|
+
ctx.llmMessages.current = [{ role: 'system', content: buildFullSystemPrompt(ctx.persona, getActiveProjectDir(ctx)) }];
|
|
1484
1574
|
ctx.addMessage('system', `Theme pack: ${subCmd}\n` +
|
|
1485
1575
|
` Skin: ${pack.skin.name}, Palette: ${pack.palette.name}, Companion: ${companion.name}\n` +
|
|
1486
1576
|
` "${companion.greeting}"`);
|
|
@@ -1499,7 +1589,7 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
1499
1589
|
const pack = getCurrentPack();
|
|
1500
1590
|
config.set('companionIntensity', 'professional');
|
|
1501
1591
|
config.set('activeCompanion', pack.companions.professional.name);
|
|
1502
|
-
ctx.llmMessages.current = [{ role: 'system', content:
|
|
1592
|
+
ctx.llmMessages.current = [{ role: 'system', content: buildFullSystemPrompt(ctx.persona, getActiveProjectDir(ctx)) }];
|
|
1503
1593
|
ctx.addMessage('system', `Switched to professional mode — ${pack.companions.professional.description}`);
|
|
1504
1594
|
}
|
|
1505
1595
|
else {
|
|
@@ -1512,7 +1602,7 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
1512
1602
|
const pack = getCurrentPack();
|
|
1513
1603
|
config.set('companionIntensity', 'immersive');
|
|
1514
1604
|
config.set('activeCompanion', pack.companions.immersive.name);
|
|
1515
|
-
ctx.llmMessages.current = [{ role: 'system', content:
|
|
1605
|
+
ctx.llmMessages.current = [{ role: 'system', content: buildFullSystemPrompt(ctx.persona, getActiveProjectDir(ctx)) }];
|
|
1516
1606
|
ctx.addMessage('system', `Switched to immersive mode — ${pack.companions.immersive.description}`);
|
|
1517
1607
|
}
|
|
1518
1608
|
else {
|
|
@@ -1768,7 +1858,22 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
1768
1858
|
if (session) {
|
|
1769
1859
|
const savedMessages = storage.loadMessageHistory();
|
|
1770
1860
|
const savedCount = savedMessages ? savedMessages.length : 0;
|
|
1771
|
-
|
|
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'));
|
|
1772
1877
|
}
|
|
1773
1878
|
else {
|
|
1774
1879
|
ctx.addMessage('system', 'No active session.');
|
|
@@ -1782,6 +1887,9 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
1782
1887
|
else {
|
|
1783
1888
|
// Save current messages before forking
|
|
1784
1889
|
storage.saveMessageHistory(ctx.llmMessages.current);
|
|
1890
|
+
if (ctx.ledger) {
|
|
1891
|
+
storage.saveIterationLedger(ctx.ledger);
|
|
1892
|
+
}
|
|
1785
1893
|
const forked = storage.forkSession(session.projectPath);
|
|
1786
1894
|
if (forked) {
|
|
1787
1895
|
ctx.sessionRef.current = forked;
|
|
@@ -1794,12 +1902,89 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
1794
1902
|
}
|
|
1795
1903
|
else if (parts[1] === 'save') {
|
|
1796
1904
|
storage.saveMessageHistory(ctx.llmMessages.current);
|
|
1797
|
-
ctx.
|
|
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.`);
|
|
1798
1909
|
}
|
|
1799
1910
|
else {
|
|
1800
1911
|
ctx.addMessage('system', 'Usage: /session [list|info|fork|save] or just /sessions');
|
|
1801
1912
|
}
|
|
1802
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
|
+
}
|
|
1803
1988
|
case '/todo': {
|
|
1804
1989
|
const subCommand = parts[1];
|
|
1805
1990
|
if (subCommand === 'add' && parts.length > 2) {
|
|
@@ -2371,6 +2556,20 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
2371
2556
|
// Resume a session by loading saved LLM message history
|
|
2372
2557
|
// Usage: /resume [sessionId] - resume a specific session, or current session if no ID
|
|
2373
2558
|
const targetSessionId = parts[1];
|
|
2559
|
+
if (targetSessionId) {
|
|
2560
|
+
const resumedSession = storage.setCurrentSessionById(targetSessionId);
|
|
2561
|
+
if (!resumedSession) {
|
|
2562
|
+
ctx.addMessage('system', `Session not found: ${targetSessionId}`);
|
|
2563
|
+
break;
|
|
2564
|
+
}
|
|
2565
|
+
ctx.sessionRef.current = resumedSession;
|
|
2566
|
+
}
|
|
2567
|
+
if (ctx.ledger) {
|
|
2568
|
+
ctx.ledger.loadSnapshot(storage.loadIterationLedger(targetSessionId || ctx.sessionRef.current?.id));
|
|
2569
|
+
if (ctx.sessionRef.current?.id) {
|
|
2570
|
+
storage.saveIterationLedger(ctx.ledger, ctx.sessionRef.current.id);
|
|
2571
|
+
}
|
|
2572
|
+
}
|
|
2374
2573
|
// Try loading full message history first (preferred - preserves tool calls etc.)
|
|
2375
2574
|
const savedMessages = storage.loadMessageHistory(targetSessionId);
|
|
2376
2575
|
if (savedMessages && savedMessages.length > 0) {
|
|
@@ -2384,11 +2583,16 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
2384
2583
|
}
|
|
2385
2584
|
else {
|
|
2386
2585
|
// Fall back to chat.log history (legacy format, user/assistant only)
|
|
2387
|
-
const history = storage.getChatHistory(20);
|
|
2586
|
+
const history = storage.getChatHistory(20, targetSessionId);
|
|
2388
2587
|
if (history.length === 0) {
|
|
2389
2588
|
ctx.addMessage('system', 'No previous messages to resume. Start a conversation first, messages are auto-saved.');
|
|
2390
2589
|
}
|
|
2391
2590
|
else {
|
|
2591
|
+
ctx.llmMessages.current.length = 0;
|
|
2592
|
+
ctx.llmMessages.current.push({
|
|
2593
|
+
role: 'system',
|
|
2594
|
+
content: buildFullSystemPrompt(ctx.persona, getActiveProjectDir(ctx)),
|
|
2595
|
+
});
|
|
2392
2596
|
for (const msg of history) {
|
|
2393
2597
|
if (msg.role === 'user' || msg.role === 'assistant') {
|
|
2394
2598
|
ctx.llmMessages.current.push({
|
|
@@ -2523,6 +2727,7 @@ Usage: /smart [on|off|cost <0-1>|test <message>]
|
|
|
2523
2727
|
case '/swarm':
|
|
2524
2728
|
case '/council': {
|
|
2525
2729
|
const subCmd = parts[1];
|
|
2730
|
+
const swarmCwd = ctx.sessionRef.current?.projectPath ?? process.cwd();
|
|
2526
2731
|
if (!ctx.agtermEnabled) {
|
|
2527
2732
|
ctx.addMessage('system', 'Agents mode not enabled. Start with --agents flag to use agent swarms.');
|
|
2528
2733
|
break;
|
|
@@ -2603,7 +2808,7 @@ Usage: /smart [on|off|cost <0-1>|test <message>]
|
|
|
2603
2808
|
try {
|
|
2604
2809
|
let session;
|
|
2605
2810
|
if (template) {
|
|
2606
|
-
session = await councilManager.startFromTemplate(template, cleanPrompt);
|
|
2811
|
+
session = await councilManager.startFromTemplate(template, cleanPrompt, swarmCwd);
|
|
2607
2812
|
}
|
|
2608
2813
|
else {
|
|
2609
2814
|
const { randomUUID } = await import('crypto');
|
|
@@ -2612,8 +2817,9 @@ Usage: /smart [on|off|cost <0-1>|test <message>]
|
|
|
2612
2817
|
{ id: randomUUID(), name: 'Agent B', agent: 'claude', weight: 1.0 },
|
|
2613
2818
|
{ id: randomUUID(), name: 'Agent C', agent: 'claude', weight: 1.0 },
|
|
2614
2819
|
];
|
|
2615
|
-
session = await councilManager.startCouncil(cleanPrompt, { mode, members });
|
|
2820
|
+
session = await councilManager.startCouncil(cleanPrompt, { mode, members }, swarmCwd);
|
|
2616
2821
|
}
|
|
2822
|
+
watchAsyncLedgerRun(ctx.ledger, 'council', cleanPrompt, () => councilManager.getSession(session.id));
|
|
2617
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.`);
|
|
2618
2824
|
}
|
|
2619
2825
|
catch (err) {
|
|
@@ -2674,7 +2880,8 @@ Options:
|
|
|
2674
2880
|
cleanPrompt = cleanPrompt.replace(aggMatch[0], '').trim();
|
|
2675
2881
|
}
|
|
2676
2882
|
try {
|
|
2677
|
-
const session = await swarmManager.startSwarm(cleanPrompt, { decomposition: strategy, aggregation });
|
|
2883
|
+
const session = await swarmManager.startSwarm(cleanPrompt, { decomposition: strategy, aggregation }, swarmCwd);
|
|
2884
|
+
watchAsyncLedgerRun(ctx.ledger, 'swarm', cleanPrompt, () => swarmManager.getSession(session.id));
|
|
2678
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.`);
|
|
2679
2886
|
}
|
|
2680
2887
|
catch (err) {
|
|
@@ -2858,14 +3065,16 @@ Requires --agents flag.`);
|
|
|
2858
3065
|
runJob(bgJob.id, async (prompt, signal) => {
|
|
2859
3066
|
const { chat } = await import('../providers/index.js');
|
|
2860
3067
|
const { TOOLS } = await import('../tools.js');
|
|
2861
|
-
const {
|
|
3068
|
+
const { executeTool: execTool } = await import('../tools.js');
|
|
3069
|
+
const cwd = ctx.sessionRef.current?.projectPath || process.cwd();
|
|
3070
|
+
const maxIterations = resolveIterationLimit(config.get('maxIterations'));
|
|
2862
3071
|
const bgMessages = [
|
|
2863
|
-
{ role: 'system', content:
|
|
3072
|
+
{ role: 'system', content: buildFullSystemPrompt(ctx.persona, cwd) },
|
|
2864
3073
|
{ role: 'user', content: prompt },
|
|
2865
3074
|
];
|
|
2866
3075
|
let iterations = 0;
|
|
2867
3076
|
let lastContent = '';
|
|
2868
|
-
while (iterations <
|
|
3077
|
+
while (!signal.aborted && iterations < maxIterations) {
|
|
2869
3078
|
iterations++;
|
|
2870
3079
|
const response = await chat(ctx.provider, bgMessages, TOOLS, ctx.model);
|
|
2871
3080
|
if (!response.toolCalls?.length) {
|
|
@@ -2873,12 +3082,18 @@ Requires --agents flag.`);
|
|
|
2873
3082
|
break;
|
|
2874
3083
|
}
|
|
2875
3084
|
bgMessages.push({ role: 'assistant', content: response.content, toolCalls: response.toolCalls });
|
|
2876
|
-
const { executeTool: execTool } = await import('../tools.js');
|
|
2877
3085
|
for (const tc of response.toolCalls) {
|
|
2878
|
-
const result = await execTool(tc,
|
|
3086
|
+
const result = await execTool(tc, cwd);
|
|
2879
3087
|
bgMessages.push({ role: 'tool', content: result.result, toolCallId: tc.id });
|
|
3088
|
+
if (signal.aborted)
|
|
3089
|
+
break;
|
|
2880
3090
|
}
|
|
2881
3091
|
}
|
|
3092
|
+
if (signal.aborted) {
|
|
3093
|
+
const error = new Error('Background job cancelled');
|
|
3094
|
+
error.name = 'AbortError';
|
|
3095
|
+
throw error;
|
|
3096
|
+
}
|
|
2882
3097
|
return { result: lastContent, iterations };
|
|
2883
3098
|
}).then(completed => {
|
|
2884
3099
|
ctx.addMessage('system', `Background job ${completed.id} ${completed.status}: ${completed.result?.slice(0, 200) || completed.error || 'done'}`);
|