fixo-cli 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/agent-client.d.ts +3 -0
- package/dist/agent/agent-client.d.ts.map +1 -1
- package/dist/agent/agent-client.js +63 -9
- package/dist/agent/agent-client.js.map +1 -1
- package/dist/agent/command-parser.d.ts +37 -0
- package/dist/agent/command-parser.d.ts.map +1 -1
- package/dist/agent/command-parser.js +297 -1
- package/dist/agent/command-parser.js.map +1 -1
- package/dist/agent/conversation.d.ts +18 -1
- package/dist/agent/conversation.d.ts.map +1 -1
- package/dist/agent/conversation.js +31 -2
- package/dist/agent/conversation.js.map +1 -1
- package/dist/agent/duration.d.ts +24 -0
- package/dist/agent/duration.d.ts.map +1 -0
- package/dist/agent/duration.js +42 -0
- package/dist/agent/duration.js.map +1 -0
- package/dist/agent/file-writing-rules.d.ts +19 -0
- package/dist/agent/file-writing-rules.d.ts.map +1 -0
- package/dist/agent/file-writing-rules.js +31 -0
- package/dist/agent/file-writing-rules.js.map +1 -0
- package/dist/agent/parser-adapter.d.ts.map +1 -1
- package/dist/agent/parser-adapter.js +57 -5
- package/dist/agent/parser-adapter.js.map +1 -1
- package/dist/agent/provider-cooldown.d.ts.map +1 -1
- package/dist/agent/provider-cooldown.js +3 -2
- package/dist/agent/provider-cooldown.js.map +1 -1
- package/dist/agent/providers-manager.d.ts +6 -0
- package/dist/agent/providers-manager.d.ts.map +1 -1
- package/dist/agent/providers-manager.js +20 -0
- package/dist/agent/providers-manager.js.map +1 -1
- package/dist/agent/single-agent.d.ts +25 -0
- package/dist/agent/single-agent.d.ts.map +1 -1
- package/dist/agent/single-agent.js +136 -27
- package/dist/agent/single-agent.js.map +1 -1
- package/dist/agent/telemetry.d.ts.map +1 -1
- package/dist/agent/telemetry.js +4 -1
- package/dist/agent/telemetry.js.map +1 -1
- package/dist/agent/tool-executor.d.ts +5 -0
- package/dist/agent/tool-executor.d.ts.map +1 -1
- package/dist/agent/tool-executor.js +17 -4
- package/dist/agent/tool-executor.js.map +1 -1
- package/dist/agent/worker-agent.d.ts +8 -0
- package/dist/agent/worker-agent.d.ts.map +1 -1
- package/dist/agent/worker-agent.js +25 -3
- package/dist/agent/worker-agent.js.map +1 -1
- package/dist/config.d.ts +11 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -1
- package/dist/config.js.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/project-memory.d.ts +1 -2
- package/dist/project-memory.d.ts.map +1 -1
- package/dist/project-memory.js +13 -1
- package/dist/project-memory.js.map +1 -1
- package/dist/runtime/loop-mitigation.d.ts +48 -0
- package/dist/runtime/loop-mitigation.d.ts.map +1 -0
- package/dist/runtime/loop-mitigation.js +79 -0
- package/dist/runtime/loop-mitigation.js.map +1 -0
- package/dist/runtime/session-snapshots.d.ts +52 -2
- package/dist/runtime/session-snapshots.d.ts.map +1 -1
- package/dist/runtime/session-snapshots.js +76 -1
- package/dist/runtime/session-snapshots.js.map +1 -1
- package/dist/setup-wizard.d.ts +1 -0
- package/dist/setup-wizard.d.ts.map +1 -1
- package/dist/setup-wizard.js +42 -1
- package/dist/setup-wizard.js.map +1 -1
- package/dist/ui/prompt.d.ts.map +1 -1
- package/dist/ui/prompt.js +195 -10
- package/dist/ui/prompt.js.map +1 -1
- package/dist/ui/render-primitives.d.ts +20 -0
- package/dist/ui/render-primitives.d.ts.map +1 -1
- package/dist/ui/render-primitives.js +41 -0
- package/dist/ui/render-primitives.js.map +1 -1
- package/dist/ui/session-header.d.ts +13 -0
- package/dist/ui/session-header.d.ts.map +1 -1
- package/dist/ui/session-header.js +6 -0
- package/dist/ui/session-header.js.map +1 -1
- package/package.json +6 -2
- package/scripts/check-vendor-wasm.js +44 -0
- package/vendor/tree-sitter-bash.wasm +0 -0
- package/vendor/tree-sitter.wasm +0 -0
|
@@ -51,7 +51,7 @@ export function snapshotPath(cwd, id) {
|
|
|
51
51
|
return path.join(dirForCwd(cwd), `${id}.json`);
|
|
52
52
|
}
|
|
53
53
|
export function saveSnapshot(input, now = new Date()) {
|
|
54
|
-
const id = makeSnapshotId(now);
|
|
54
|
+
const id = input.id ?? makeSnapshotId(now);
|
|
55
55
|
const file = snapshotPath(input.cwd, id);
|
|
56
56
|
const dir = path.dirname(file);
|
|
57
57
|
try {
|
|
@@ -73,6 +73,7 @@ export function saveSnapshot(input, now = new Date()) {
|
|
|
73
73
|
mode: input.mode,
|
|
74
74
|
selectedFiles: input.selectedFiles,
|
|
75
75
|
summary: input.summary,
|
|
76
|
+
label: input.label,
|
|
76
77
|
fixedInstructions: input.fixedInstructions,
|
|
77
78
|
todo,
|
|
78
79
|
};
|
|
@@ -154,6 +155,7 @@ export function listSnapshots(cwd) {
|
|
|
154
155
|
tokens: typeof obj.tokens === 'number' ? obj.tokens : 0,
|
|
155
156
|
items,
|
|
156
157
|
summary: typeof obj.summary === 'string' ? obj.summary : undefined,
|
|
158
|
+
label: typeof obj.label === 'string' ? obj.label : undefined,
|
|
157
159
|
});
|
|
158
160
|
}
|
|
159
161
|
catch {
|
|
@@ -163,4 +165,77 @@ export function listSnapshots(cwd) {
|
|
|
163
165
|
out.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
164
166
|
return out;
|
|
165
167
|
}
|
|
168
|
+
/* ──────────────────────── rename ──────────────────────── */
|
|
169
|
+
/**
|
|
170
|
+
* Maximum label length. Long enough for "clinic-website-accessibility-audit"
|
|
171
|
+
* but short enough to fit in a session header pill on a 100-col terminal.
|
|
172
|
+
*/
|
|
173
|
+
export const MAX_LABEL_LENGTH = 64;
|
|
174
|
+
/**
|
|
175
|
+
* Returns true if the label is safe to store, render, and search.
|
|
176
|
+
*
|
|
177
|
+
* Rules:
|
|
178
|
+
* - 1..MAX_LABEL_LENGTH characters
|
|
179
|
+
* - letters, digits, dash, underscore, dot, and spaces only
|
|
180
|
+
* - no path separators (`/`, `\`) so a label can never affect
|
|
181
|
+
* where the snapshot file lives on disk
|
|
182
|
+
* - no shell metacharacters (backtick, `$`, `;`, `|`, `&`, `<`, `>`)
|
|
183
|
+
* so a future render path that interpolates the label cannot be
|
|
184
|
+
* hijacked
|
|
185
|
+
*
|
|
186
|
+
* The validator is conservative on purpose — labels are user-facing,
|
|
187
|
+
* not creative writing. The file id stays as the durable identifier.
|
|
188
|
+
*/
|
|
189
|
+
export function isValidSessionLabel(label) {
|
|
190
|
+
if (typeof label !== 'string')
|
|
191
|
+
return false;
|
|
192
|
+
const trimmed = label.trim();
|
|
193
|
+
if (trimmed.length === 0 || trimmed.length > MAX_LABEL_LENGTH)
|
|
194
|
+
return false;
|
|
195
|
+
// Allow Unicode letters/digits + space + dash + underscore + dot.
|
|
196
|
+
return /^[\p{L}\p{N}._\- ]+$/u.test(trimmed);
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Atomically rewrite the `label` field of an existing snapshot.
|
|
200
|
+
* The on-disk file basename (the snapshot id) is **never** renamed —
|
|
201
|
+
* existing `/resume <id>` invocations keep working after a relabel.
|
|
202
|
+
*
|
|
203
|
+
* Pass `label: undefined` to clear an existing label.
|
|
204
|
+
*/
|
|
205
|
+
export function renameSnapshot(cwd, id, label) {
|
|
206
|
+
if (label !== undefined && !isValidSessionLabel(label)) {
|
|
207
|
+
return {
|
|
208
|
+
ok: false,
|
|
209
|
+
id,
|
|
210
|
+
error: `invalid label: must be 1..${MAX_LABEL_LENGTH} chars, letters/digits/space/dash/underscore/dot only`,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
const loaded = loadSnapshot(cwd, id);
|
|
214
|
+
if (!loaded.ok || !loaded.snapshot) {
|
|
215
|
+
return { ok: false, id, error: loaded.error ?? 'snapshot not found' };
|
|
216
|
+
}
|
|
217
|
+
const updated = {
|
|
218
|
+
...loaded.snapshot,
|
|
219
|
+
label: label?.trim() || undefined,
|
|
220
|
+
};
|
|
221
|
+
const file = snapshotPath(cwd, id);
|
|
222
|
+
const tmp = `${file}.tmp`;
|
|
223
|
+
try {
|
|
224
|
+
fs.writeFileSync(tmp, JSON.stringify(updated, null, 2), {
|
|
225
|
+
encoding: 'utf-8',
|
|
226
|
+
mode: 0o600,
|
|
227
|
+
});
|
|
228
|
+
fs.renameSync(tmp, file);
|
|
229
|
+
}
|
|
230
|
+
catch (err) {
|
|
231
|
+
try {
|
|
232
|
+
fs.unlinkSync(tmp);
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
/* ignore tmp cleanup failure */
|
|
236
|
+
}
|
|
237
|
+
return { ok: false, id, error: err.message };
|
|
238
|
+
}
|
|
239
|
+
return { ok: true, id, label: updated.label };
|
|
240
|
+
}
|
|
166
241
|
//# sourceMappingURL=session-snapshots.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-snapshots.js","sourceRoot":"","sources":["../../src/runtime/session-snapshots.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,MAAM,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"session-snapshots.js","sourceRoot":"","sources":["../../src/runtime/session-snapshots.ts"],"names":[],"mappings":"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"}
|
package/dist/setup-wizard.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { FreeLLMConfig } from './config.js';
|
|
|
3
3
|
* Runs the interactive first-run setup wizard for FixO CLI.
|
|
4
4
|
* Links the CLI terminal to the FreeLLMAPI SaaS cloud by prompting
|
|
5
5
|
* for the master API key, destination URL, and saving it to the configuration.
|
|
6
|
+
* Also offers to configure individual provider API keys for direct access.
|
|
6
7
|
*/
|
|
7
8
|
export declare function runSetupWizard(): Promise<FreeLLMConfig>;
|
|
8
9
|
//# sourceMappingURL=setup-wizard.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup-wizard.d.ts","sourceRoot":"","sources":["../src/setup-wizard.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGjD
|
|
1
|
+
{"version":3,"file":"setup-wizard.d.ts","sourceRoot":"","sources":["../src/setup-wizard.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGjD;;;;;GAKG;AACH,wBAAsB,cAAc,IAAI,OAAO,CAAC,aAAa,CAAC,CAuH7D"}
|
package/dist/setup-wizard.js
CHANGED
|
@@ -4,6 +4,7 @@ import { getDefaultConfig, saveConfig, DEFAULT_API_URL } from './config.js';
|
|
|
4
4
|
* Runs the interactive first-run setup wizard for FixO CLI.
|
|
5
5
|
* Links the CLI terminal to the FreeLLMAPI SaaS cloud by prompting
|
|
6
6
|
* for the master API key, destination URL, and saving it to the configuration.
|
|
7
|
+
* Also offers to configure individual provider API keys for direct access.
|
|
7
8
|
*/
|
|
8
9
|
export async function runSetupWizard() {
|
|
9
10
|
p.intro('🚀 Welcome to FixO CLI Setup');
|
|
@@ -67,7 +68,47 @@ export async function runSetupWizard() {
|
|
|
67
68
|
config.apiUrl = apiUrl;
|
|
68
69
|
config._firstRunComplete = true;
|
|
69
70
|
saveConfig(config);
|
|
70
|
-
p.outro('✓
|
|
71
|
+
p.outro('✓ FreeLLMAPI configuration saved to ~/.fixocli/config.json');
|
|
72
|
+
// ──── Optional: Configure individual provider API keys ────
|
|
73
|
+
const configureProviders = await p.confirm({
|
|
74
|
+
message: 'Would you like to add API keys for individual AI providers? (You can also do this later via /providers add)',
|
|
75
|
+
initialValue: false,
|
|
76
|
+
});
|
|
77
|
+
if (configureProviders) {
|
|
78
|
+
// Dynamic import to avoid any module-load-time side effects
|
|
79
|
+
const { ProvidersManager, PROVIDER_REGISTRY } = await import('./agent/providers-manager.js');
|
|
80
|
+
const selectedProviders = await p.multiselect({
|
|
81
|
+
message: 'Select providers to configure (you can add more later via /providers add):',
|
|
82
|
+
options: PROVIDER_REGISTRY.map(def => ({
|
|
83
|
+
value: def.name,
|
|
84
|
+
label: def.displayName,
|
|
85
|
+
hint: def.docsUrl,
|
|
86
|
+
})),
|
|
87
|
+
required: false,
|
|
88
|
+
});
|
|
89
|
+
if (!p.isCancel(selectedProviders) && selectedProviders.length > 0) {
|
|
90
|
+
let configuredCount = 0;
|
|
91
|
+
for (const name of selectedProviders) {
|
|
92
|
+
const def = PROVIDER_REGISTRY.find(d => d.name === name);
|
|
93
|
+
const apiKey = await p.password({
|
|
94
|
+
message: `Enter API key for ${def.displayName}:`,
|
|
95
|
+
validate: (val) => {
|
|
96
|
+
if (!val.trim())
|
|
97
|
+
return 'API key is required';
|
|
98
|
+
return;
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
if (!p.isCancel(apiKey) && apiKey) {
|
|
102
|
+
ProvidersManager.add(name, apiKey);
|
|
103
|
+
configuredCount++;
|
|
104
|
+
console.log(` ✓ ${def.displayName} key saved`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (configuredCount > 0) {
|
|
108
|
+
p.outro(`✓ ${configuredCount} provider key(s) saved to ~/.fixocli/providers.json`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
71
112
|
return config;
|
|
72
113
|
}
|
|
73
114
|
//# sourceMappingURL=setup-wizard.js.map
|
package/dist/setup-wizard.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup-wizard.js","sourceRoot":"","sources":["../src/setup-wizard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,gBAAgB,CAAC;AAEpC,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE5E
|
|
1
|
+
{"version":3,"file":"setup-wizard.js","sourceRoot":"","sources":["../src/setup-wizard.ts"],"names":[],"mappings":"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"}
|
package/dist/ui/prompt.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../../src/ui/prompt.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAgB,aAAa,EAAE,MAAM,aAAa,CAAC;AAG/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AA2DlD,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,aAAa,CAAC;IACtB,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,SAAS,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../../src/ui/prompt.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAgB,aAAa,EAAE,MAAM,aAAa,CAAC;AAG/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AA2DlD,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,aAAa,CAAC;IACtB,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,SAAS,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAkrErE"}
|
package/dist/ui/prompt.js
CHANGED
|
@@ -45,6 +45,7 @@ export async function startREPL(options) {
|
|
|
45
45
|
await mcpBridgeManager.initialize(cwd);
|
|
46
46
|
const { randomUUID } = await import('node:crypto');
|
|
47
47
|
let currentSessionId = randomUUID();
|
|
48
|
+
let currentSessionLabel;
|
|
48
49
|
let sessionModifiedFiles = [];
|
|
49
50
|
let currentMode = 'BUILD';
|
|
50
51
|
let currentModel = projectConfig?.model ?? config.defaultModel ?? 'auto';
|
|
@@ -76,6 +77,8 @@ export async function startREPL(options) {
|
|
|
76
77
|
conversation.setContextLimit(currentModel);
|
|
77
78
|
currentMode = snap.mode;
|
|
78
79
|
selectedFiles = [...snap.selectedFiles];
|
|
80
|
+
currentSessionId = snap.id;
|
|
81
|
+
currentSessionLabel = snap.label;
|
|
79
82
|
console.log(`\n${c.green}✓ Resumed session${c.reset} ${c.dim}${snap.id}${c.reset}`);
|
|
80
83
|
console.log(` ${c.dim}messages=${snap.conversation.length} tokens=${snap.tokens} model=${snap.model} mode=${snap.mode}${c.reset}`);
|
|
81
84
|
if (snap.summary) {
|
|
@@ -88,6 +91,8 @@ export async function startREPL(options) {
|
|
|
88
91
|
}
|
|
89
92
|
}
|
|
90
93
|
let isPrompting = false;
|
|
94
|
+
let isTaskRunning = false;
|
|
95
|
+
let currentRunningAgent = null;
|
|
91
96
|
let activeSuggestionsCount = 0;
|
|
92
97
|
let currentMatches = [];
|
|
93
98
|
let highlightedIndex = 0;
|
|
@@ -181,7 +186,10 @@ export async function startREPL(options) {
|
|
|
181
186
|
mode: modeForState,
|
|
182
187
|
routing: 'auto',
|
|
183
188
|
model: currentModel,
|
|
184
|
-
|
|
189
|
+
// Show '(detached HEAD)' instead of bare 'detached' so the
|
|
190
|
+
// status bar is unambiguous — the previous label read as "the
|
|
191
|
+
// CLI is detached from the API server" to several users.
|
|
192
|
+
branch: currentBranch || '(detached HEAD)',
|
|
185
193
|
contextPercent,
|
|
186
194
|
providersCount,
|
|
187
195
|
transport: 'freellmapi',
|
|
@@ -273,12 +281,48 @@ export async function startREPL(options) {
|
|
|
273
281
|
}
|
|
274
282
|
};
|
|
275
283
|
process.on('exit', exitCleanup);
|
|
276
|
-
// ────
|
|
284
|
+
// ──── Double-Ctrl+C (and task-abort) handler ────
|
|
285
|
+
const SIGINT_RESET_MS = 2000;
|
|
286
|
+
let sigintCount = 0;
|
|
287
|
+
let lastSigintTime = 0;
|
|
288
|
+
let sigintResetTimer = null;
|
|
277
289
|
const sigintHandler = () => {
|
|
290
|
+
if (isTaskRunning && currentRunningAgent) {
|
|
291
|
+
// A task is running — cancel it instead of exiting
|
|
292
|
+
currentRunningAgent.abort();
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
const now = Date.now();
|
|
296
|
+
if (now - lastSigintTime > SIGINT_RESET_MS) {
|
|
297
|
+
// First press (or after reset window)
|
|
298
|
+
sigintCount = 1;
|
|
299
|
+
lastSigintTime = now;
|
|
300
|
+
// Write hint and redraw the prompt
|
|
301
|
+
const promptStr = `${C.LAVA}›${C.RESET} `;
|
|
302
|
+
process.stdout.write(`\n${c.yellow}⚠ Press Ctrl+C again to exit${c.reset}\n`);
|
|
303
|
+
drawLavaStatusBar();
|
|
304
|
+
process.stdout.write(promptStr);
|
|
305
|
+
// Auto-reset after the window expires
|
|
306
|
+
if (sigintResetTimer)
|
|
307
|
+
clearTimeout(sigintResetTimer);
|
|
308
|
+
sigintResetTimer = setTimeout(() => {
|
|
309
|
+
sigintCount = 0;
|
|
310
|
+
sigintResetTimer = null;
|
|
311
|
+
}, SIGINT_RESET_MS);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
// Second press within the window — exit
|
|
315
|
+
if (sigintResetTimer)
|
|
316
|
+
clearTimeout(sigintResetTimer);
|
|
317
|
+
sigintResetTimer = null;
|
|
318
|
+
sigintCount = 0;
|
|
278
319
|
exitCleanup();
|
|
279
320
|
console.log('\n\n👋 FixO CLI session ended safely. Core engine offline.');
|
|
280
321
|
process.exit(0);
|
|
281
322
|
};
|
|
323
|
+
// Listen on both the readline interface (catches Ctrl+C during rl.question())
|
|
324
|
+
// and the process (fallback for non-readline scenarios).
|
|
325
|
+
rl.on('SIGINT', sigintHandler);
|
|
282
326
|
process.on('SIGINT', sigintHandler);
|
|
283
327
|
const sigtermHandler = () => {
|
|
284
328
|
exitCleanup();
|
|
@@ -458,7 +502,15 @@ export async function startREPL(options) {
|
|
|
458
502
|
const keypressHandler = (_char, key) => {
|
|
459
503
|
if (!isPrompting)
|
|
460
504
|
return;
|
|
461
|
-
|
|
505
|
+
// Intercept Escape to cancel a running task even when readline is in a question state
|
|
506
|
+
if (key && key.name === 'escape') {
|
|
507
|
+
if (isTaskRunning && currentRunningAgent) {
|
|
508
|
+
currentRunningAgent.abort();
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
if (key && (key.name === 'up' || key.name === 'down' || key.name === 'tab' || key.name === 'enter' || key.name === 'return')) {
|
|
462
514
|
return;
|
|
463
515
|
}
|
|
464
516
|
process.nextTick(() => {
|
|
@@ -582,6 +634,15 @@ export async function startREPL(options) {
|
|
|
582
634
|
}
|
|
583
635
|
if (event === 'keypress') {
|
|
584
636
|
const [char, key] = args;
|
|
637
|
+
// Intercept Escape or Ctrl+C to cancel a running task (when not prompting)
|
|
638
|
+
if (key && key.name === 'escape' && isTaskRunning && currentRunningAgent) {
|
|
639
|
+
currentRunningAgent.abort();
|
|
640
|
+
return true;
|
|
641
|
+
}
|
|
642
|
+
if (key && key.name === 'c' && key.ctrl && isTaskRunning && currentRunningAgent) {
|
|
643
|
+
currentRunningAgent.abort();
|
|
644
|
+
return true;
|
|
645
|
+
}
|
|
585
646
|
// Tab on empty line → cycle mode (BEFORE suggestion handling, so it always works)
|
|
586
647
|
if (isPrompting && key && key.name === 'tab' && rl.line.trim() === '') {
|
|
587
648
|
const modes = ['BUILD', 'EXPLORE', 'SCOUT', 'PLAN'];
|
|
@@ -671,13 +732,20 @@ export async function startREPL(options) {
|
|
|
671
732
|
case '/model': {
|
|
672
733
|
if (args[0] === 'list') {
|
|
673
734
|
// Print full model table grouped by provider
|
|
735
|
+
// Uses live-fetched cached models when available, otherwise falls
|
|
736
|
+
// back to the static registry list (tagged [unverified]).
|
|
674
737
|
console.log(`\n${c.bold}${c.cyan}Available Models by Provider${c.reset}`);
|
|
675
738
|
console.log(`${c.dim}${'─'.repeat(60)}${c.reset}`);
|
|
676
739
|
for (const def of PROVIDER_REGISTRY) {
|
|
677
740
|
const hasKey = ProvidersManager.has(def.name);
|
|
678
741
|
const keyStatus = hasKey ? `${c.green}[key ✓]${c.reset}` : `${c.dim}[no key]${c.reset}`;
|
|
679
|
-
|
|
680
|
-
|
|
742
|
+
const cached = ProvidersManager.getCachedModels(def.name);
|
|
743
|
+
const modelList = cached?.models?.length ? cached.models : def.models;
|
|
744
|
+
const sourceTag = cached?.source === 'live'
|
|
745
|
+
? ''
|
|
746
|
+
: ` ${c.dim}[unverified]${c.reset}`;
|
|
747
|
+
console.log(`\n ${c.snow}${c.bold}${def.displayName}${c.reset} ${keyStatus}${sourceTag}`);
|
|
748
|
+
for (const model of modelList) {
|
|
681
749
|
console.log(` ${c.cyan}•${c.reset} ${model}`);
|
|
682
750
|
}
|
|
683
751
|
}
|
|
@@ -742,6 +810,11 @@ export async function startREPL(options) {
|
|
|
742
810
|
return;
|
|
743
811
|
}
|
|
744
812
|
currentModel = picked;
|
|
813
|
+
// Store hint — find which provider this model belongs to
|
|
814
|
+
const owningDef = PROVIDER_REGISTRY.find(d => d.models.includes(currentModel)
|
|
815
|
+
|| ProvidersManager.getCachedModels(d.name)?.models?.includes(currentModel));
|
|
816
|
+
if (owningDef)
|
|
817
|
+
ProvidersManager.setModelProviderHint(currentModel, owningDef.name);
|
|
745
818
|
conversation.setContextLimit(currentModel);
|
|
746
819
|
console.log(`\n${c.green}✓ Model set to: ${c.bold}${currentModel}${c.reset}`);
|
|
747
820
|
return;
|
|
@@ -777,6 +850,11 @@ export async function startREPL(options) {
|
|
|
777
850
|
return;
|
|
778
851
|
}
|
|
779
852
|
currentModel = picked;
|
|
853
|
+
// Store explicit model-provider association so
|
|
854
|
+
// resolveDirectConfig can route this model directly
|
|
855
|
+
// to this provider (critical for live-fetched models
|
|
856
|
+
// that don't appear in the static registry).
|
|
857
|
+
ProvidersManager.setModelProviderHint(currentModel, def.name);
|
|
780
858
|
conversation.setContextLimit(currentModel);
|
|
781
859
|
console.log(`\n${c.green}✓ Model set to: ${c.bold}${currentModel}${c.reset}`);
|
|
782
860
|
return;
|
|
@@ -1192,6 +1270,8 @@ export async function startREPL(options) {
|
|
|
1192
1270
|
yes: true,
|
|
1193
1271
|
};
|
|
1194
1272
|
try {
|
|
1273
|
+
isTaskRunning = true;
|
|
1274
|
+
currentRunningAgent = agent;
|
|
1195
1275
|
const result = await agent.runStreaming(context, conversation, rl);
|
|
1196
1276
|
for (const file of result.modifiedFiles) {
|
|
1197
1277
|
if (!modifiedFiles.includes(file)) {
|
|
@@ -1202,6 +1282,11 @@ export async function startREPL(options) {
|
|
|
1202
1282
|
catch (err) {
|
|
1203
1283
|
console.log(`\n${c.red}✗ Repair agent failed on attempt ${attempt}: ${err.message || err}${c.reset}`);
|
|
1204
1284
|
}
|
|
1285
|
+
finally {
|
|
1286
|
+
isTaskRunning = false;
|
|
1287
|
+
currentRunningAgent = null;
|
|
1288
|
+
agent.reset();
|
|
1289
|
+
}
|
|
1205
1290
|
testResult = runProjectTests(cwd);
|
|
1206
1291
|
if (testResult.includes('Status: 0')) {
|
|
1207
1292
|
console.log(`\n${c.green}✓ All tests passed after repair attempt ${attempt}!${c.reset}`);
|
|
@@ -1307,9 +1392,59 @@ export async function startREPL(options) {
|
|
|
1307
1392
|
}
|
|
1308
1393
|
return;
|
|
1309
1394
|
}
|
|
1395
|
+
case '/rename': {
|
|
1396
|
+
// Renames the *active* session. Accepts the rest of the
|
|
1397
|
+
// input as a free-form label (so spaces don't need quoting).
|
|
1398
|
+
const rawLabel = args.join(' ').trim();
|
|
1399
|
+
const { isValidSessionLabel, MAX_LABEL_LENGTH } = await import('../runtime/session-snapshots.js');
|
|
1400
|
+
const { SessionManager } = await import('../agent/conversation.js');
|
|
1401
|
+
if (!rawLabel) {
|
|
1402
|
+
console.log(`\n${c.yellow}Usage: /rename <label>${c.reset}\n` +
|
|
1403
|
+
`${c.dim} Labels are 1..${MAX_LABEL_LENGTH} chars: letters, digits, space, dash, underscore, dot.${c.reset}`);
|
|
1404
|
+
return;
|
|
1405
|
+
}
|
|
1406
|
+
if (!isValidSessionLabel(rawLabel)) {
|
|
1407
|
+
console.log(`\n${c.red}✗ Invalid label.${c.reset} ${c.dim}Allowed: letters, digits, space, dash, underscore, dot — max ${MAX_LABEL_LENGTH} chars.${c.reset}`);
|
|
1408
|
+
return;
|
|
1409
|
+
}
|
|
1410
|
+
// Persist if the session has already been saved at least
|
|
1411
|
+
// once; otherwise just remember the label in memory until
|
|
1412
|
+
// the next save fires.
|
|
1413
|
+
try {
|
|
1414
|
+
SessionManager.renameSession(currentSessionId, rawLabel);
|
|
1415
|
+
}
|
|
1416
|
+
catch {
|
|
1417
|
+
/* tolerate first-rename-before-save */
|
|
1418
|
+
}
|
|
1419
|
+
currentSessionLabel = rawLabel;
|
|
1420
|
+
console.log(`\n${c.green}✓ Session renamed:${c.reset} ${c.cyan}${rawLabel}${c.reset} ${c.dim}(id: ${currentSessionId})${c.reset}`);
|
|
1421
|
+
return;
|
|
1422
|
+
}
|
|
1310
1423
|
case '/session': {
|
|
1311
1424
|
const sub = args[0];
|
|
1312
1425
|
const { SessionManager } = await import('../agent/conversation.js');
|
|
1426
|
+
if (sub === 'rename') {
|
|
1427
|
+
const id = args[1];
|
|
1428
|
+
const rawLabel = args.slice(2).join(' ').trim();
|
|
1429
|
+
const { isValidSessionLabel, MAX_LABEL_LENGTH } = await import('../runtime/session-snapshots.js');
|
|
1430
|
+
if (!id || !rawLabel) {
|
|
1431
|
+
console.log(`\n${c.yellow}Usage: /session rename <id> <label>${c.reset}`);
|
|
1432
|
+
return;
|
|
1433
|
+
}
|
|
1434
|
+
if (!isValidSessionLabel(rawLabel)) {
|
|
1435
|
+
console.log(`\n${c.red}✗ Invalid label.${c.reset} ${c.dim}Max ${MAX_LABEL_LENGTH} chars; letters, digits, space, dash, underscore, dot only.${c.reset}`);
|
|
1436
|
+
return;
|
|
1437
|
+
}
|
|
1438
|
+
const ok = SessionManager.renameSession(id, rawLabel);
|
|
1439
|
+
if (!ok) {
|
|
1440
|
+
console.log(`\n${c.red}✗ Session not found: ${id}${c.reset}`);
|
|
1441
|
+
return;
|
|
1442
|
+
}
|
|
1443
|
+
if (id === currentSessionId)
|
|
1444
|
+
currentSessionLabel = rawLabel;
|
|
1445
|
+
console.log(`\n${c.green}✓ Renamed${c.reset} ${c.dim}${id}${c.reset} → ${c.cyan}${rawLabel}${c.reset}`);
|
|
1446
|
+
return;
|
|
1447
|
+
}
|
|
1313
1448
|
if (sub === 'list') {
|
|
1314
1449
|
const list = SessionManager.listSessions();
|
|
1315
1450
|
if (list.length === 0) {
|
|
@@ -1319,7 +1454,10 @@ export async function startREPL(options) {
|
|
|
1319
1454
|
console.log(`\n${c.cyan}${c.bold}Saved Sessions:${c.reset}`);
|
|
1320
1455
|
for (const s of list) {
|
|
1321
1456
|
const date = new Date(s.timestamp).toLocaleString();
|
|
1322
|
-
|
|
1457
|
+
const labelDisplay = s.label
|
|
1458
|
+
? `${c.cyan}${s.label}${c.reset} ${c.dim}(${s.sessionId.slice(0, 8)})${c.reset}`
|
|
1459
|
+
: `${c.cyan}${s.sessionId}${c.reset}`;
|
|
1460
|
+
console.log(` ${labelDisplay} - ${c.bold}${s.model}${c.reset} (${s.messageCount} msgs)`);
|
|
1323
1461
|
console.log(` ${c.dim}Created: ${date} | Tokens: ${s.totalTokens.toLocaleString()}${c.reset}`);
|
|
1324
1462
|
if (s.summary) {
|
|
1325
1463
|
console.log(` ${c.dim}Summary: ${s.summary.slice(0, 80)}...${c.reset}`);
|
|
@@ -1342,6 +1480,7 @@ export async function startREPL(options) {
|
|
|
1342
1480
|
conversation.setContextLimit(currentModel);
|
|
1343
1481
|
sessionModifiedFiles = data.modifiedFiles || [];
|
|
1344
1482
|
currentSessionId = data.sessionId;
|
|
1483
|
+
currentSessionLabel = data.label;
|
|
1345
1484
|
stats.totalPromptTokens = data.tokenUsage?.prompt_tokens || 0;
|
|
1346
1485
|
stats.totalCompletionTokens = data.tokenUsage?.completion_tokens || 0;
|
|
1347
1486
|
console.log(`\n${c.green}✓ Session restored successfully: ${c.bold}${uuid}${c.reset}`);
|
|
@@ -1361,15 +1500,34 @@ export async function startREPL(options) {
|
|
|
1361
1500
|
stats.totalDurationMs = 0;
|
|
1362
1501
|
const { randomUUID } = await import('node:crypto');
|
|
1363
1502
|
currentSessionId = randomUUID();
|
|
1503
|
+
currentSessionLabel = undefined;
|
|
1364
1504
|
SessionManager.saveSession(conversation, currentModel, sessionModifiedFiles, {
|
|
1365
1505
|
prompt_tokens: stats.totalPromptTokens,
|
|
1366
1506
|
completion_tokens: stats.totalCompletionTokens,
|
|
1367
1507
|
total_tokens: stats.totalPromptTokens + stats.totalCompletionTokens,
|
|
1368
|
-
}, currentSessionId);
|
|
1508
|
+
}, currentSessionId, currentSessionLabel);
|
|
1509
|
+
try {
|
|
1510
|
+
const { saveSnapshot } = await import('../runtime/session-snapshots.js');
|
|
1511
|
+
saveSnapshot({
|
|
1512
|
+
cwd,
|
|
1513
|
+
conversation: [],
|
|
1514
|
+
tokens: 0,
|
|
1515
|
+
model: currentModel,
|
|
1516
|
+
mode: currentMode,
|
|
1517
|
+
selectedFiles: [],
|
|
1518
|
+
summary: '',
|
|
1519
|
+
label: undefined,
|
|
1520
|
+
id: currentSessionId,
|
|
1521
|
+
fixedInstructions: projectConfig?.systemPrompt,
|
|
1522
|
+
});
|
|
1523
|
+
}
|
|
1524
|
+
catch {
|
|
1525
|
+
// Ignore snapshot save errors on new session
|
|
1526
|
+
}
|
|
1369
1527
|
console.log(`\n${c.green}✓ Active conversation memory purged. New session initialized: ${c.bold}${currentSessionId}${c.reset}`);
|
|
1370
1528
|
}
|
|
1371
1529
|
else {
|
|
1372
|
-
console.log(`\n${c.yellow}Usage: /session [list | load <uuid> | new]${c.reset}`);
|
|
1530
|
+
console.log(`\n${c.yellow}Usage: /session [list | load <uuid> | new | rename <id> <label>]${c.reset}`);
|
|
1373
1531
|
}
|
|
1374
1532
|
return;
|
|
1375
1533
|
}
|
|
@@ -1825,7 +1983,16 @@ export async function startREPL(options) {
|
|
|
1825
1983
|
}
|
|
1826
1984
|
else {
|
|
1827
1985
|
console.log(`\n${c.cyan}[Routing Engine] Simple task detected (${classification.reason}). Routing to SingleAgent...${c.reset}`);
|
|
1828
|
-
|
|
1986
|
+
isTaskRunning = true;
|
|
1987
|
+
currentRunningAgent = agent;
|
|
1988
|
+
try {
|
|
1989
|
+
result = await agent.runStreaming(context, conversation, rl);
|
|
1990
|
+
}
|
|
1991
|
+
finally {
|
|
1992
|
+
isTaskRunning = false;
|
|
1993
|
+
currentRunningAgent = null;
|
|
1994
|
+
agent.reset();
|
|
1995
|
+
}
|
|
1829
1996
|
}
|
|
1830
1997
|
// Print result summary
|
|
1831
1998
|
console.log('');
|
|
@@ -1897,7 +2064,25 @@ export async function startREPL(options) {
|
|
|
1897
2064
|
prompt_tokens: stats.totalPromptTokens,
|
|
1898
2065
|
completion_tokens: stats.totalCompletionTokens,
|
|
1899
2066
|
total_tokens: stats.totalPromptTokens + stats.totalCompletionTokens,
|
|
1900
|
-
}, currentSessionId);
|
|
2067
|
+
}, currentSessionId, currentSessionLabel);
|
|
2068
|
+
const { saveSnapshot } = await import('../runtime/session-snapshots.js');
|
|
2069
|
+
saveSnapshot({
|
|
2070
|
+
cwd,
|
|
2071
|
+
conversation: conversation.exportHistory().map((m, idx) => ({
|
|
2072
|
+
role: m.role,
|
|
2073
|
+
content: m.content || '',
|
|
2074
|
+
name: m.name,
|
|
2075
|
+
index: idx,
|
|
2076
|
+
})),
|
|
2077
|
+
tokens: stats.totalPromptTokens + stats.totalCompletionTokens,
|
|
2078
|
+
model: currentModel,
|
|
2079
|
+
mode: currentMode,
|
|
2080
|
+
selectedFiles: [...selectedFiles],
|
|
2081
|
+
summary: conversation.getSummary(),
|
|
2082
|
+
label: currentSessionLabel,
|
|
2083
|
+
id: currentSessionId,
|
|
2084
|
+
fixedInstructions: projectConfig?.systemPrompt,
|
|
2085
|
+
});
|
|
1901
2086
|
}
|
|
1902
2087
|
catch (err) {
|
|
1903
2088
|
// Ignore session save errors
|