bernard-agent 0.8.0 → 0.9.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 +80 -44
- package/dist/agent.d.ts +14 -3
- package/dist/agent.js +228 -38
- package/dist/agent.js.map +1 -1
- package/dist/builtin-specialists/correction-agent.json +32 -0
- package/dist/builtin-specialists/file-wrapper.json +43 -0
- package/dist/builtin-specialists/shell-wrapper.json +50 -0
- package/dist/builtin-specialists/specialist-creator.json +32 -0
- package/dist/builtin-specialists/web-wrapper.json +38 -0
- package/dist/candidate-bootstrap.d.ts +18 -0
- package/dist/candidate-bootstrap.js +61 -0
- package/dist/candidate-bootstrap.js.map +1 -0
- package/dist/config.d.ts +132 -10
- package/dist/config.js +234 -47
- package/dist/config.js.map +1 -1
- package/dist/context.js +23 -6
- package/dist/context.js.map +1 -1
- package/dist/correction-candidates.d.ts +54 -0
- package/dist/correction-candidates.js +138 -0
- package/dist/correction-candidates.js.map +1 -0
- package/dist/correction.d.ts +67 -0
- package/dist/correction.js +138 -0
- package/dist/correction.js.map +1 -0
- package/dist/critic.js +2 -1
- package/dist/critic.js.map +1 -1
- package/dist/cron/notes-store.d.ts +41 -0
- package/dist/cron/notes-store.js +134 -0
- package/dist/cron/notes-store.js.map +1 -0
- package/dist/cron/runner.js +25 -3
- package/dist/cron/runner.js.map +1 -1
- package/dist/cron/scoped-notes-tools.d.ts +24 -0
- package/dist/cron/scoped-notes-tools.js +50 -0
- package/dist/cron/scoped-notes-tools.js.map +1 -0
- package/dist/custom-providers.d.ts +80 -0
- package/dist/custom-providers.js +238 -0
- package/dist/custom-providers.js.map +1 -0
- package/dist/fs-utils.d.ts +2 -0
- package/dist/fs-utils.js +44 -0
- package/dist/fs-utils.js.map +1 -0
- package/dist/history.js +3 -1
- package/dist/history.js.map +1 -1
- package/dist/image.d.ts +59 -0
- package/dist/image.js +228 -0
- package/dist/image.js.map +1 -0
- package/dist/index.js +72 -4
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +1 -1
- package/dist/mcp.js.map +1 -1
- package/dist/memory.d.ts +13 -0
- package/dist/memory.js +45 -4
- package/dist/memory.js.map +1 -1
- package/dist/menu.d.ts +97 -0
- package/dist/menu.js +338 -0
- package/dist/menu.js.map +1 -0
- package/dist/os-info.d.ts +22 -0
- package/dist/os-info.js +111 -0
- package/dist/os-info.js.map +1 -0
- package/dist/output.d.ts +35 -1
- package/dist/output.js +256 -45
- package/dist/output.js.map +1 -1
- package/dist/pac.d.ts +14 -2
- package/dist/pac.js +5 -5
- package/dist/pac.js.map +1 -1
- package/dist/paths.d.ts +5 -0
- package/dist/paths.js +6 -1
- package/dist/paths.js.map +1 -1
- package/dist/plan-store.d.ts +47 -0
- package/dist/plan-store.js +94 -0
- package/dist/plan-store.js.map +1 -0
- package/dist/prompt-rewriter.d.ts +29 -0
- package/dist/prompt-rewriter.js +155 -0
- package/dist/prompt-rewriter.js.map +1 -0
- package/dist/providers/index.d.ts +56 -4
- package/dist/providers/index.js +86 -5
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/profiles.d.ts +37 -0
- package/dist/providers/profiles.js +110 -0
- package/dist/providers/profiles.js.map +1 -0
- package/dist/providers/types.d.ts +11 -2
- package/dist/providers/types.js +3 -0
- package/dist/providers/types.js.map +1 -1
- package/dist/rag-query.js +15 -1
- package/dist/rag-query.js.map +1 -1
- package/dist/react.d.ts +38 -0
- package/dist/react.js +116 -0
- package/dist/react.js.map +1 -0
- package/dist/reasoning-log.d.ts +30 -0
- package/dist/reasoning-log.js +102 -0
- package/dist/reasoning-log.js.map +1 -0
- package/dist/reference-resolver.d.ts +47 -0
- package/dist/reference-resolver.js +316 -0
- package/dist/reference-resolver.js.map +1 -0
- package/dist/reference-tool-lookup.d.ts +37 -0
- package/dist/reference-tool-lookup.js +318 -0
- package/dist/reference-tool-lookup.js.map +1 -0
- package/dist/repl.js +1053 -346
- package/dist/repl.js.map +1 -1
- package/dist/setup.js +2 -1
- package/dist/setup.js.map +1 -1
- package/dist/specialist-detector.js +2 -1
- package/dist/specialist-detector.js.map +1 -1
- package/dist/specialists.d.ts +74 -3
- package/dist/specialists.js +152 -20
- package/dist/specialists.js.map +1 -1
- package/dist/structured-output.d.ts +58 -0
- package/dist/structured-output.js +138 -0
- package/dist/structured-output.js.map +1 -0
- package/dist/theme.d.ts +2 -0
- package/dist/theme.js +18 -12
- package/dist/theme.js.map +1 -1
- package/dist/tool-call-repair.d.ts +29 -0
- package/dist/tool-call-repair.js +99 -0
- package/dist/tool-call-repair.js.map +1 -0
- package/dist/tool-profiles.d.ts +70 -0
- package/dist/tool-profiles.js +385 -0
- package/dist/tool-profiles.js.map +1 -0
- package/dist/tools/activity-summary.d.ts +15 -0
- package/dist/tools/activity-summary.js +44 -0
- package/dist/tools/activity-summary.js.map +1 -0
- package/dist/tools/ask-user.d.ts +49 -0
- package/dist/tools/ask-user.js +52 -0
- package/dist/tools/ask-user.js.map +1 -0
- package/dist/tools/augment.d.ts +17 -0
- package/dist/tools/augment.js +102 -0
- package/dist/tools/augment.js.map +1 -0
- package/dist/tools/cron-logs.js +7 -0
- package/dist/tools/cron-logs.js.map +1 -1
- package/dist/tools/cron-notes.d.ts +52 -0
- package/dist/tools/cron-notes.js +105 -0
- package/dist/tools/cron-notes.js.map +1 -0
- package/dist/tools/datetime.d.ts +7 -0
- package/dist/tools/datetime.js +29 -3
- package/dist/tools/datetime.js.map +1 -1
- package/dist/tools/evaluate.d.ts +20 -0
- package/dist/tools/evaluate.js +29 -0
- package/dist/tools/evaluate.js.map +1 -0
- package/dist/tools/index.js +4 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/mcp.d.ts +3 -3
- package/dist/tools/plan.d.ts +81 -0
- package/dist/tools/plan.js +108 -0
- package/dist/tools/plan.js.map +1 -0
- package/dist/tools/result-cap.d.ts +24 -0
- package/dist/tools/result-cap.js +44 -0
- package/dist/tools/result-cap.js.map +1 -0
- package/dist/tools/routine.d.ts +3 -3
- package/dist/tools/shell.d.ts +14 -1
- package/dist/tools/shell.js +86 -4
- package/dist/tools/shell.js.map +1 -1
- package/dist/tools/specialist-run.d.ts +5 -3
- package/dist/tools/specialist-run.js +115 -24
- package/dist/tools/specialist-run.js.map +1 -1
- package/dist/tools/specialist.d.ts +83 -3
- package/dist/tools/specialist.js +83 -3
- package/dist/tools/specialist.js.map +1 -1
- package/dist/tools/subagent.js +32 -14
- package/dist/tools/subagent.js.map +1 -1
- package/dist/tools/task.d.ts +19 -16
- package/dist/tools/task.js +69 -40
- package/dist/tools/task.js.map +1 -1
- package/dist/tools/think.d.ts +18 -0
- package/dist/tools/think.js +25 -0
- package/dist/tools/think.js.map +1 -0
- package/dist/tools/tool-wrapper-run.d.ts +121 -0
- package/dist/tools/tool-wrapper-run.js +382 -0
- package/dist/tools/tool-wrapper-run.js.map +1 -0
- package/dist/tools/types.d.ts +28 -2
- package/dist/tools/web-search.d.ts +31 -0
- package/dist/tools/web-search.js +172 -0
- package/dist/tools/web-search.js.map +1 -0
- package/dist/tools/wrap-with-specialist.d.ts +55 -0
- package/dist/tools/wrap-with-specialist.js +137 -0
- package/dist/tools/wrap-with-specialist.js.map +1 -0
- package/package.json +2 -2
package/dist/repl.js
CHANGED
|
@@ -43,10 +43,15 @@ const os = __importStar(require("node:os"));
|
|
|
43
43
|
const paths_js_1 = require("./paths.js");
|
|
44
44
|
const agent_js_1 = require("./agent.js");
|
|
45
45
|
const memory_js_1 = require("./memory.js");
|
|
46
|
+
const reference_resolver_js_1 = require("./reference-resolver.js");
|
|
47
|
+
const reference_tool_lookup_js_1 = require("./reference-tool-lookup.js");
|
|
48
|
+
const web_js_1 = require("./tools/web.js");
|
|
49
|
+
const web_search_js_1 = require("./tools/web-search.js");
|
|
46
50
|
const rag_js_1 = require("./rag.js");
|
|
47
51
|
const mcp_js_1 = require("./mcp.js");
|
|
48
52
|
const output_js_1 = require("./output.js");
|
|
49
53
|
const config_js_1 = require("./config.js");
|
|
54
|
+
const custom_providers_js_1 = require("./custom-providers.js");
|
|
50
55
|
const theme_js_1 = require("./theme.js");
|
|
51
56
|
const update_js_1 = require("./update.js");
|
|
52
57
|
const store_js_1 = require("./cron/store.js");
|
|
@@ -54,17 +59,22 @@ const client_js_1 = require("./cron/client.js");
|
|
|
54
59
|
const history_js_1 = require("./history.js");
|
|
55
60
|
const ai_1 = require("ai");
|
|
56
61
|
const index_js_1 = require("./providers/index.js");
|
|
62
|
+
const prompt_rewriter_js_1 = require("./prompt-rewriter.js");
|
|
57
63
|
const context_js_1 = require("./context.js");
|
|
58
64
|
const domains_js_1 = require("./domains.js");
|
|
59
65
|
const routines_js_1 = require("./routines.js");
|
|
60
66
|
const specialists_js_1 = require("./specialists.js");
|
|
67
|
+
const correction_js_1 = require("./correction.js");
|
|
61
68
|
const specialist_candidates_js_1 = require("./specialist-candidates.js");
|
|
69
|
+
const candidate_bootstrap_js_1 = require("./candidate-bootstrap.js");
|
|
62
70
|
const specialist_detector_js_1 = require("./specialist-detector.js");
|
|
63
71
|
const task_js_1 = require("./tools/task.js");
|
|
64
72
|
const index_js_2 = require("./tools/index.js");
|
|
65
73
|
const output_js_2 = require("./output.js");
|
|
66
74
|
const memory_context_js_1 = require("./memory-context.js");
|
|
67
75
|
const logger_js_1 = require("./logger.js");
|
|
76
|
+
const image_js_1 = require("./image.js");
|
|
77
|
+
const menu_js_1 = require("./menu.js");
|
|
68
78
|
/**
|
|
69
79
|
* Launch the interactive REPL, wiring up readline, MCP servers, memory stores, and the agent loop.
|
|
70
80
|
* @param config - Resolved runtime configuration (provider, model, tokens, etc.).
|
|
@@ -72,6 +82,7 @@ const logger_js_1 = require("./logger.js");
|
|
|
72
82
|
* @param resume - When true, reload the previous conversation from disk and continue it.
|
|
73
83
|
*/
|
|
74
84
|
async function startRepl(config, alertContext, resume) {
|
|
85
|
+
(0, output_js_2.setToolDetailsVisible)(config.toolDetails);
|
|
75
86
|
const SLASH_COMMANDS = [
|
|
76
87
|
{ command: '/help', description: 'Show this help' },
|
|
77
88
|
{ command: '/clear', description: 'Clear conversation (--save/-s to summarize first)' },
|
|
@@ -97,9 +108,11 @@ async function startRepl(config, alertContext, resume) {
|
|
|
97
108
|
{ command: '/specialists', description: 'List specialist agents' },
|
|
98
109
|
{ command: '/create-specialist', description: 'Create a specialist with guided AI assistance' },
|
|
99
110
|
{ command: '/candidates', description: 'Review specialist suggestions' },
|
|
100
|
-
{
|
|
101
|
-
|
|
102
|
-
|
|
111
|
+
{
|
|
112
|
+
command: '/agent-options',
|
|
113
|
+
description: 'Configure agent behavior (toggles, thresholds, saved assets)',
|
|
114
|
+
},
|
|
115
|
+
{ command: '/image', description: 'Attach an image: /image <path> [prompt]' },
|
|
103
116
|
{ command: '/exit', description: 'Quit Bernard' },
|
|
104
117
|
];
|
|
105
118
|
const routineStore = new routines_js_1.RoutineStore();
|
|
@@ -139,7 +152,8 @@ async function startRepl(config, alertContext, resume) {
|
|
|
139
152
|
function getPromptStr() {
|
|
140
153
|
const { ansi } = (0, theme_js_1.getTheme)();
|
|
141
154
|
const criticLabel = config.criticMode ? `${ansi.warning}\u25C6${ansi.reset} ` : '';
|
|
142
|
-
|
|
155
|
+
const reactLabel = config.reactMode ? `${ansi.prompt}\u25B7${ansi.reset} ` : '';
|
|
156
|
+
return `${criticLabel}${reactLabel}${ansi.prompt}bernard>${ansi.reset} `;
|
|
143
157
|
}
|
|
144
158
|
if (process.stdin.isTTY) {
|
|
145
159
|
process.stdout.write('\x1b[?2004h'); // enable bracket paste mode
|
|
@@ -176,9 +190,382 @@ async function startRepl(config, alertContext, resume) {
|
|
|
176
190
|
let processing = false;
|
|
177
191
|
let interrupted = false;
|
|
178
192
|
let taskAbortController = null;
|
|
193
|
+
let menuAbortController = null;
|
|
194
|
+
function createMenuSignal() {
|
|
195
|
+
menuAbortController = new AbortController();
|
|
196
|
+
return menuAbortController.signal;
|
|
197
|
+
}
|
|
198
|
+
function clearMenuSignal() {
|
|
199
|
+
menuAbortController = null;
|
|
200
|
+
}
|
|
201
|
+
async function toggleBooleanPref(key, label, onMsg, offMsg, onToggle) {
|
|
202
|
+
const entries = [
|
|
203
|
+
{ label: 'On', active: config[key] === true },
|
|
204
|
+
{ label: 'Off', active: config[key] === false },
|
|
205
|
+
];
|
|
206
|
+
const signal = createMenuSignal();
|
|
207
|
+
try {
|
|
208
|
+
const result = await (0, menu_js_1.selectFromMenu)(rl, entries, { title: `${label}: ${config[key] ? 'ON' : 'OFF'}` }, signal);
|
|
209
|
+
if (!result.cancelled) {
|
|
210
|
+
config[key] = result.index === 0;
|
|
211
|
+
(0, config_js_1.savePreferences)({
|
|
212
|
+
...(0, config_js_1.loadPreferences)(),
|
|
213
|
+
provider: config.provider,
|
|
214
|
+
model: config.model,
|
|
215
|
+
[key]: config[key],
|
|
216
|
+
});
|
|
217
|
+
onToggle?.(config[key]);
|
|
218
|
+
(0, output_js_1.printInfo)(config[key] ? onMsg : offMsg);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
finally {
|
|
222
|
+
clearMenuSignal();
|
|
223
|
+
}
|
|
224
|
+
console.log();
|
|
225
|
+
}
|
|
226
|
+
function printSpecialistsList() {
|
|
227
|
+
const specialists = specialistStore.list();
|
|
228
|
+
if (specialists.length === 0) {
|
|
229
|
+
(0, output_js_1.printInfo)('No specialist agents defined yet. Ask me to create one or use /create-specialist.');
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
const builtinIds = (0, specialists_js_1.getBuiltinSpecialistIds)();
|
|
233
|
+
const bundled = specialists.filter((s) => builtinIds.has(s.id));
|
|
234
|
+
const user = specialists.filter((s) => !builtinIds.has(s.id));
|
|
235
|
+
const t = (0, theme_js_1.getTheme)();
|
|
236
|
+
(0, output_js_1.printInfo)(`\n Specialists (${specialists.length}):`);
|
|
237
|
+
if (bundled.length > 0) {
|
|
238
|
+
console.log(t.muted('\n Bundled:'));
|
|
239
|
+
for (const s of bundled) {
|
|
240
|
+
(0, output_js_1.printInfo)(` ${s.id} — ${s.name}: ${s.description}`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (user.length > 0) {
|
|
244
|
+
console.log(t.muted('\n Yours:'));
|
|
245
|
+
for (const s of user) {
|
|
246
|
+
(0, output_js_1.printInfo)(` ${s.id} — ${s.name}: ${s.description}`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
console.log();
|
|
250
|
+
}
|
|
251
|
+
function printRoutinesList(kind) {
|
|
252
|
+
const all = routineStore.list();
|
|
253
|
+
const match = all.filter((r) => kind === 'tasks' ? r.id.startsWith('task-') : !r.id.startsWith('task-'));
|
|
254
|
+
if (match.length === 0) {
|
|
255
|
+
if (kind === 'tasks') {
|
|
256
|
+
(0, output_js_1.printInfo)('No tasks saved. Use /create-task to define one.');
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
(0, output_js_1.printInfo)('No routines saved. Teach me a workflow and I can save it as a routine.');
|
|
260
|
+
}
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
const heading = kind === 'tasks'
|
|
264
|
+
? `\n Tasks (${match.length}) — single-step, structured output:`
|
|
265
|
+
: `\n Routines (${match.length}) — multi-step workflows:`;
|
|
266
|
+
(0, output_js_1.printInfo)(heading);
|
|
267
|
+
const t = (0, theme_js_1.getTheme)();
|
|
268
|
+
for (const r of match) {
|
|
269
|
+
console.log(` ${t.accent(`/${r.id}`)} ${t.muted(`— ${r.name}: ${r.description}`)}`);
|
|
270
|
+
}
|
|
271
|
+
console.log();
|
|
272
|
+
}
|
|
273
|
+
function printDebugReport() {
|
|
274
|
+
const t = (0, theme_js_1.getTheme)();
|
|
275
|
+
console.log(t.accent('\n Bernard Diagnostic Report'));
|
|
276
|
+
console.log(t.accent(' ' + '─'.repeat(40)));
|
|
277
|
+
console.log(t.text('\n Runtime:'));
|
|
278
|
+
console.log(t.muted(` Bernard version: ${(0, update_js_1.getLocalVersion)()}`));
|
|
279
|
+
console.log(t.muted(` Node.js version: ${process.version}`));
|
|
280
|
+
console.log(t.muted(` OS: ${process.platform} ${process.arch} (${os.release()})`));
|
|
281
|
+
console.log(t.text('\n LLM:'));
|
|
282
|
+
console.log(t.muted(` Provider: ${config.provider}`));
|
|
283
|
+
console.log(t.muted(` Model: ${config.model}`));
|
|
284
|
+
console.log(t.muted(` maxTokens: ${config.maxTokens}`));
|
|
285
|
+
console.log(t.muted(` shellTimeout: ${config.shellTimeout}ms`));
|
|
286
|
+
console.log(t.muted(` tokenWindow: ${config.tokenWindow || 'auto-detect'}`));
|
|
287
|
+
console.log(t.text('\n API Keys:'));
|
|
288
|
+
for (const { provider, hasKey } of (0, config_js_1.getProviderKeyStatus)()) {
|
|
289
|
+
console.log(t.muted(` ${provider}: ${hasKey ? 'configured' : 'not set'}`));
|
|
290
|
+
}
|
|
291
|
+
const debugStatuses = mcpManager.getServerStatuses();
|
|
292
|
+
console.log(t.text('\n MCP Servers:'));
|
|
293
|
+
if (debugStatuses.length === 0) {
|
|
294
|
+
console.log(t.muted(' (none configured)'));
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
for (const s of debugStatuses) {
|
|
298
|
+
if (s.connected) {
|
|
299
|
+
console.log(t.muted(` ${s.name}: connected (${s.toolCount} tools)`));
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
console.log(t.muted(` ${s.name}: failed — ${s.error}`));
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
console.log(t.text('\n RAG:'));
|
|
307
|
+
console.log(t.muted(` Enabled: ${config.ragEnabled}`));
|
|
308
|
+
if (ragStore) {
|
|
309
|
+
console.log(t.muted(` Facts: ${ragStore.count()}`));
|
|
310
|
+
}
|
|
311
|
+
console.log(t.text('\n Memory:'));
|
|
312
|
+
console.log(t.muted(` Persistent memories: ${memoryStore.listMemory().length}`));
|
|
313
|
+
console.log(t.text('\n Cron:'));
|
|
314
|
+
console.log(t.muted(` Daemon: ${(0, client_js_1.isDaemonRunning)() ? 'running' : 'stopped'}`));
|
|
315
|
+
let debugJobCount = 0;
|
|
316
|
+
try {
|
|
317
|
+
const raw = fs.readFileSync(paths_js_1.CRON_JOBS_FILE, 'utf-8');
|
|
318
|
+
debugJobCount = JSON.parse(raw).length;
|
|
319
|
+
}
|
|
320
|
+
catch {
|
|
321
|
+
// jobs.json doesn't exist yet — that's fine
|
|
322
|
+
}
|
|
323
|
+
console.log(t.muted(` Jobs: ${debugJobCount}`));
|
|
324
|
+
console.log(t.text('\n Conversation:'));
|
|
325
|
+
console.log(t.muted(` Messages: ${agent.getHistory().length}`));
|
|
326
|
+
console.log(t.text('\n Settings:'));
|
|
327
|
+
console.log(t.muted(` Theme: ${(0, theme_js_1.getActiveThemeKey)()}`));
|
|
328
|
+
console.log(t.muted(` Critic mode: ${config.criticMode ? 'on' : 'off'}`));
|
|
329
|
+
console.log(t.muted(` Coordinator mode: ${config.reactMode ? 'on' : 'off'}`));
|
|
330
|
+
console.log(t.muted(` Tool details: ${config.toolDetails ? 'on' : 'off'}`));
|
|
331
|
+
console.log(t.muted(` Prompt rewriter: ${config.promptRewriter ? 'on' : 'off'}`));
|
|
332
|
+
const debugEnabled = process.env.BERNARD_DEBUG === 'true' || process.env.BERNARD_DEBUG === '1';
|
|
333
|
+
console.log(t.muted(` Debug mode: ${debugEnabled ? 'on' : 'off'}`));
|
|
334
|
+
console.log(t.text('\n Paths:'));
|
|
335
|
+
if (process.env.BERNARD_HOME) {
|
|
336
|
+
console.log(t.muted(` BERNARD_HOME: ${process.env.BERNARD_HOME}`));
|
|
337
|
+
}
|
|
338
|
+
console.log(t.muted(` Config: ${paths_js_1.CONFIG_DIR}`));
|
|
339
|
+
console.log(t.muted(` Data: ${paths_js_1.DATA_DIR}`));
|
|
340
|
+
console.log(t.muted(` Cache: ${paths_js_1.CACHE_DIR}`));
|
|
341
|
+
console.log(t.muted(` State: ${paths_js_1.STATE_DIR}`));
|
|
342
|
+
console.log();
|
|
343
|
+
}
|
|
344
|
+
async function promptDisambiguation(reference, candidates) {
|
|
345
|
+
const entries = [];
|
|
346
|
+
for (const c of candidates) {
|
|
347
|
+
entries.push({ label: c.label, description: c.preview });
|
|
348
|
+
}
|
|
349
|
+
entries.push({ label: 'Pass as-is (do not resolve)' });
|
|
350
|
+
for (const c of candidates) {
|
|
351
|
+
entries.push({ label: `Remember: "${reference}" → ${c.label}` });
|
|
352
|
+
}
|
|
353
|
+
const signal = createMenuSignal();
|
|
354
|
+
try {
|
|
355
|
+
const result = await (0, menu_js_1.selectFromMenu)(rl, entries, { title: `Ambiguous reference: "${reference}"`, promptLabel: 'Resolve to' }, signal);
|
|
356
|
+
if (result.cancelled)
|
|
357
|
+
return { cancelled: true };
|
|
358
|
+
const idx = result.index;
|
|
359
|
+
if (idx < candidates.length) {
|
|
360
|
+
const chosen = candidates[idx];
|
|
361
|
+
return {
|
|
362
|
+
cancelled: false,
|
|
363
|
+
passAsIs: false,
|
|
364
|
+
entry: {
|
|
365
|
+
phrase: reference,
|
|
366
|
+
resolvedTo: chosen.label,
|
|
367
|
+
sourceKey: chosen.sourceKey,
|
|
368
|
+
},
|
|
369
|
+
remember: false,
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
if (idx === candidates.length) {
|
|
373
|
+
return { cancelled: false, passAsIs: true };
|
|
374
|
+
}
|
|
375
|
+
const rememberIdx = idx - candidates.length - 1;
|
|
376
|
+
const chosen = candidates[rememberIdx];
|
|
377
|
+
return {
|
|
378
|
+
cancelled: false,
|
|
379
|
+
passAsIs: false,
|
|
380
|
+
entry: {
|
|
381
|
+
phrase: reference,
|
|
382
|
+
resolvedTo: chosen.label,
|
|
383
|
+
sourceKey: chosen.sourceKey,
|
|
384
|
+
},
|
|
385
|
+
remember: true,
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
finally {
|
|
389
|
+
clearMenuSignal();
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
function persistReference(reference, value, store) {
|
|
393
|
+
const baseKey = (0, reference_resolver_js_1.deriveKeyFromReference)(reference) || 'entity';
|
|
394
|
+
const existing = new Set(store.listMemory());
|
|
395
|
+
let key = baseKey;
|
|
396
|
+
let suffix = 2;
|
|
397
|
+
while (existing.has(key)) {
|
|
398
|
+
key = `${baseKey}-${suffix++}`;
|
|
399
|
+
}
|
|
400
|
+
store.writeMemory(key, value);
|
|
401
|
+
(0, output_js_1.printInfo)(` Saved as memory: ${key}`);
|
|
402
|
+
return { phrase: reference, resolvedTo: value, sourceKey: key };
|
|
403
|
+
}
|
|
404
|
+
async function promptUnknownReference(reference, store) {
|
|
405
|
+
(0, output_js_1.printInfo)(`\n I don't have memory for "${reference}". Tell me about them and I'll remember.\n (Enter or Esc skips — the agent will run without this resolved.)`);
|
|
406
|
+
console.log();
|
|
407
|
+
const signal = createMenuSignal();
|
|
408
|
+
try {
|
|
409
|
+
const result = await (0, menu_js_1.promptValue)(rl, { label: `"${reference}" is` }, signal);
|
|
410
|
+
if (result.cancelled)
|
|
411
|
+
return { entry: null };
|
|
412
|
+
if (!result.raw.trim())
|
|
413
|
+
return { entry: null };
|
|
414
|
+
return { entry: persistReference(reference, result.raw, store) };
|
|
415
|
+
}
|
|
416
|
+
finally {
|
|
417
|
+
clearMenuSignal();
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
// User confirmation is mandatory — the resolver lookup is a hint, not a fact,
|
|
421
|
+
// and may have matched the wrong person.
|
|
422
|
+
async function promptToolLookupConfirmation(reference, resolvedTo, toolName, store) {
|
|
423
|
+
(0, output_js_1.printInfo)(`\n Lookup via ${toolName} found a possible match for "${reference}":\n → ${resolvedTo}`);
|
|
424
|
+
console.log();
|
|
425
|
+
const SAVE = 0;
|
|
426
|
+
const EDIT = 1;
|
|
427
|
+
const SKIP = 2;
|
|
428
|
+
const entries = [
|
|
429
|
+
{ label: 'Save as memory', description: resolvedTo },
|
|
430
|
+
{ label: 'Edit before saving' },
|
|
431
|
+
{ label: 'Skip (do not resolve)' },
|
|
432
|
+
];
|
|
433
|
+
const signal = createMenuSignal();
|
|
434
|
+
try {
|
|
435
|
+
const result = await (0, menu_js_1.selectFromMenu)(rl, entries, { title: `Save lookup result for "${reference}"?`, promptLabel: 'Choose' }, signal);
|
|
436
|
+
if (result.cancelled || result.index === SKIP)
|
|
437
|
+
return { entry: null };
|
|
438
|
+
let value = resolvedTo;
|
|
439
|
+
if (result.index === EDIT) {
|
|
440
|
+
const edited = await (0, menu_js_1.promptValue)(rl, { label: `"${reference}" is` }, signal);
|
|
441
|
+
if (edited.cancelled || !edited.raw.trim())
|
|
442
|
+
return { entry: null };
|
|
443
|
+
value = edited.raw;
|
|
444
|
+
}
|
|
445
|
+
else if (result.index !== SAVE) {
|
|
446
|
+
return { entry: null };
|
|
447
|
+
}
|
|
448
|
+
return { entry: persistReference(reference, value, store) };
|
|
449
|
+
}
|
|
450
|
+
finally {
|
|
451
|
+
clearMenuSignal();
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
async function runReferenceResolver(trimmed) {
|
|
455
|
+
// Strip image-attachment paths and tool-resolvable tokens (URLs, PR/issue refs, file
|
|
456
|
+
// paths, commit hashes) so the resolver's LLM doesn't mistake them for unresolved
|
|
457
|
+
// entities. The main agent fetches those directly via shell/gh/web_read.
|
|
458
|
+
const resolverInput = (0, reference_resolver_js_1.stripToolResolvableTokens)((0, image_js_1.stripImagePaths)(trimmed));
|
|
459
|
+
if ((0, reference_resolver_js_1.shouldSkipResolver)(resolverInput) || resolverInput.length === 0)
|
|
460
|
+
return [];
|
|
461
|
+
try {
|
|
462
|
+
const hints = (0, memory_js_1.loadRewriterHints)(memoryStore);
|
|
463
|
+
const resolveSignal = createMenuSignal();
|
|
464
|
+
let resolveResult;
|
|
465
|
+
(0, output_js_1.startSpinner)();
|
|
466
|
+
try {
|
|
467
|
+
resolveResult = await (0, reference_resolver_js_1.resolveReferences)(resolverInput, memoryStore, config, hints, resolveSignal, ragStore, agent.getHistory());
|
|
468
|
+
}
|
|
469
|
+
finally {
|
|
470
|
+
(0, output_js_1.stopSpinner)();
|
|
471
|
+
clearMenuSignal();
|
|
472
|
+
}
|
|
473
|
+
let entries = [];
|
|
474
|
+
if (resolveResult.status === 'resolved') {
|
|
475
|
+
entries = resolveResult.entries;
|
|
476
|
+
}
|
|
477
|
+
else if (resolveResult.status === 'ambiguous') {
|
|
478
|
+
const outcome = await promptDisambiguation(resolveResult.reference, resolveResult.candidates);
|
|
479
|
+
// Esc/Enter is treated as "pass as-is" — the agent still runs with the original
|
|
480
|
+
// prompt, consistent with the unknown-reference skip behavior.
|
|
481
|
+
if (!outcome.cancelled && !outcome.passAsIs) {
|
|
482
|
+
entries = [outcome.entry];
|
|
483
|
+
if (outcome.remember) {
|
|
484
|
+
(0, memory_js_1.saveRewriterHint)(memoryStore, outcome.entry.phrase, outcome.entry.sourceKey);
|
|
485
|
+
(0, output_js_1.printInfo)(` Remembered: "${outcome.entry.phrase}" → ${outcome.entry.sourceKey}`);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
else if (resolveResult.status === 'unknown') {
|
|
490
|
+
// Try a read-only tool lookup before falling back to asking the user.
|
|
491
|
+
// Fail-open at every layer: any error or `none` drops through to the
|
|
492
|
+
// existing free-form prompt.
|
|
493
|
+
let lookupHandled = false;
|
|
494
|
+
if (config.referenceLookup) {
|
|
495
|
+
const lookupSignal = createMenuSignal();
|
|
496
|
+
(0, output_js_1.startSpinner)();
|
|
497
|
+
let lookup;
|
|
498
|
+
try {
|
|
499
|
+
lookup = await (0, reference_tool_lookup_js_1.runReferenceLookup)(resolveResult.reference, lookupTools, config, lookupSignal);
|
|
500
|
+
}
|
|
501
|
+
finally {
|
|
502
|
+
(0, output_js_1.stopSpinner)();
|
|
503
|
+
clearMenuSignal();
|
|
504
|
+
}
|
|
505
|
+
if (lookup.status === 'found') {
|
|
506
|
+
const outcome = await promptToolLookupConfirmation(resolveResult.reference, lookup.resolvedTo, lookup.toolName, memoryStore);
|
|
507
|
+
if (outcome.entry)
|
|
508
|
+
entries = [outcome.entry];
|
|
509
|
+
lookupHandled = true;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
if (!lookupHandled) {
|
|
513
|
+
const outcome = await promptUnknownReference(resolveResult.reference, memoryStore);
|
|
514
|
+
if (outcome.entry)
|
|
515
|
+
entries = [outcome.entry];
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
if (entries.length > 0) {
|
|
519
|
+
(0, logger_js_1.debugLog)('repl:resolved-references', {
|
|
520
|
+
prompt: trimmed,
|
|
521
|
+
entries,
|
|
522
|
+
injectedBlock: (0, reference_resolver_js_1.renderResolvedBlock)(entries),
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
return entries;
|
|
526
|
+
}
|
|
527
|
+
catch (err) {
|
|
528
|
+
(0, logger_js_1.debugLog)('repl:resolve-references', err instanceof Error ? err.message : String(err));
|
|
529
|
+
return [];
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
async function runPromptRewriter(trimmed, resolvedEntries) {
|
|
533
|
+
if (!config.promptRewriter)
|
|
534
|
+
return null;
|
|
535
|
+
try {
|
|
536
|
+
const profile = (0, index_js_1.getModelProfile)(config.provider, config.model, config.customProviders?.[config.provider]?.sdk);
|
|
537
|
+
const rewriteSignal = createMenuSignal();
|
|
538
|
+
let result;
|
|
539
|
+
(0, output_js_1.startSpinner)();
|
|
540
|
+
try {
|
|
541
|
+
result = await (0, prompt_rewriter_js_1.rewritePrompt)(trimmed, profile, resolvedEntries, config, rewriteSignal);
|
|
542
|
+
}
|
|
543
|
+
finally {
|
|
544
|
+
(0, output_js_1.stopSpinner)();
|
|
545
|
+
clearMenuSignal();
|
|
546
|
+
}
|
|
547
|
+
if (result.status === 'rewritten') {
|
|
548
|
+
(0, logger_js_1.debugLog)('repl:prompt-rewritten', {
|
|
549
|
+
original: trimmed,
|
|
550
|
+
rewritten: result.text,
|
|
551
|
+
family: profile.family,
|
|
552
|
+
});
|
|
553
|
+
return result.text;
|
|
554
|
+
}
|
|
555
|
+
return null;
|
|
556
|
+
}
|
|
557
|
+
catch (err) {
|
|
558
|
+
(0, logger_js_1.debugLog)('repl:prompt-rewriter', err instanceof Error ? err.message : String(err));
|
|
559
|
+
return null;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
179
562
|
process.stdin.on('keypress', (_str, key) => {
|
|
180
563
|
if (!key)
|
|
181
564
|
return;
|
|
565
|
+
if (key.name === 'escape' && menuAbortController) {
|
|
566
|
+
menuAbortController.abort();
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
182
569
|
if (key.name === 'escape' && processing) {
|
|
183
570
|
if (taskAbortController) {
|
|
184
571
|
taskAbortController.abort();
|
|
@@ -267,17 +654,80 @@ async function startRepl(config, alertContext, resume) {
|
|
|
267
654
|
}
|
|
268
655
|
const mcpTools = mcpManager.getTools();
|
|
269
656
|
const mcpServerNames = mcpManager.getConnectedServerNames();
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
657
|
+
// Tool registry exposed to the resolver's pre-fallback lookup pass. Limited
|
|
658
|
+
// to read-only network tools + MCP tools so a slow or write-side tool can't
|
|
659
|
+
// be selected. The lookup module further filters via `isAllowedLookupTool`.
|
|
660
|
+
const lookupTools = {
|
|
661
|
+
web_search: (0, web_search_js_1.createWebSearchTool)(),
|
|
662
|
+
web_read: (0, web_js_1.createWebReadTool)(),
|
|
663
|
+
...mcpTools,
|
|
664
|
+
};
|
|
665
|
+
// The thinking-spinner repaints stdout every 80 ms and would blank any
|
|
666
|
+
// interactive prompt, so callers that need to talk to the user must run
|
|
667
|
+
// through this helper to stop the spinner first and restart it after.
|
|
668
|
+
const withPausedSpinner = async (signal, fn) => {
|
|
669
|
+
(0, output_js_1.stopSpinner)();
|
|
670
|
+
try {
|
|
671
|
+
return await fn();
|
|
672
|
+
}
|
|
673
|
+
finally {
|
|
674
|
+
// Skip restart when the turn is over or aborted — nothing left to think about.
|
|
675
|
+
if (processing && !signal?.aborted) {
|
|
676
|
+
resumeSpinner();
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
};
|
|
680
|
+
const confirmFn = (command, signal) => withPausedSpinner(signal, async () => {
|
|
681
|
+
const result = await (0, menu_js_1.selectFromMenu)(rl, [{ label: 'Allow once' }, { label: 'Cancel' }], { title: `⚠ Dangerous command: ${command}` }, signal);
|
|
682
|
+
return !result.cancelled && result.index === 0;
|
|
683
|
+
});
|
|
684
|
+
const renderTabStrip = (currentIndex, total) => {
|
|
685
|
+
const theme = (0, theme_js_1.getTheme)();
|
|
686
|
+
const tabs = [];
|
|
687
|
+
for (let i = 0; i < total; i++) {
|
|
688
|
+
const num = String(i + 1);
|
|
689
|
+
if (i < currentIndex)
|
|
690
|
+
tabs.push(theme.success(`${num} ✓`));
|
|
691
|
+
else if (i === currentIndex)
|
|
692
|
+
tabs.push(theme.accentBold(`▸${num}◂`));
|
|
693
|
+
else
|
|
694
|
+
tabs.push(theme.dim(num));
|
|
695
|
+
}
|
|
696
|
+
return ' ' + tabs.join(' ');
|
|
697
|
+
};
|
|
698
|
+
const askSingleQuestion = async (q, headerLines, signal) => {
|
|
699
|
+
if (!q.choices || q.choices.length === 0) {
|
|
700
|
+
const r = await (0, menu_js_1.promptValue)(rl, { label: q.question, headerLines }, signal);
|
|
701
|
+
return r.cancelled ? null : r.raw;
|
|
702
|
+
}
|
|
703
|
+
const entries = q.choices.map((label) => ({ label }));
|
|
704
|
+
if (q.allowOther)
|
|
705
|
+
entries.push({ label: q.otherLabel ?? 'Other (type a custom answer)' });
|
|
706
|
+
const r = await (0, menu_js_1.selectFromMenu)(rl, entries, { title: q.question, headerLines }, signal);
|
|
707
|
+
if (r.cancelled)
|
|
708
|
+
return null;
|
|
709
|
+
if (q.allowOther && r.index === q.choices.length) {
|
|
710
|
+
const free = await (0, menu_js_1.promptValue)(rl, { label: q.question, headerLines }, signal);
|
|
711
|
+
return free.cancelled ? null : free.raw;
|
|
712
|
+
}
|
|
713
|
+
return q.choices[r.index];
|
|
277
714
|
};
|
|
715
|
+
const askUserFn = (questions, signal) => withPausedSpinner(signal, async () => {
|
|
716
|
+
const answered = [];
|
|
717
|
+
const showTabs = questions.length > 1;
|
|
718
|
+
for (let i = 0; i < questions.length; i++) {
|
|
719
|
+
const headerLines = showTabs ? [renderTabStrip(i, questions.length)] : undefined;
|
|
720
|
+
const answer = await askSingleQuestion(questions[i], headerLines, signal);
|
|
721
|
+
if (answer === null)
|
|
722
|
+
return { cancelled: true, answered };
|
|
723
|
+
answered.push(answer);
|
|
724
|
+
}
|
|
725
|
+
return { answers: answered };
|
|
726
|
+
});
|
|
278
727
|
const toolOptions = {
|
|
279
728
|
shellTimeout: config.shellTimeout,
|
|
280
729
|
confirmDangerous: confirmFn,
|
|
730
|
+
askUser: askUserFn,
|
|
281
731
|
};
|
|
282
732
|
const historyStore = new history_js_1.HistoryStore();
|
|
283
733
|
let initialHistory;
|
|
@@ -304,14 +754,10 @@ async function startRepl(config, alertContext, resume) {
|
|
|
304
754
|
(0, output_js_1.printInfo)('No previous conversation found — starting fresh.');
|
|
305
755
|
}
|
|
306
756
|
}
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
candidateStore.reconcileSaved(specialistStore.list());
|
|
310
|
-
const pendingCandidates = candidateStore.listPending();
|
|
311
|
-
if (pendingCandidates.length > 0) {
|
|
757
|
+
const { pending: pendingCandidates, contextBlock } = (0, candidate_bootstrap_js_1.bootstrapPendingCandidates)(candidateStore, specialistStore, config);
|
|
758
|
+
if (contextBlock) {
|
|
312
759
|
(0, output_js_1.printInfo)(` ${pendingCandidates.length} specialist suggestion(s) pending. Use /candidates to review.`);
|
|
313
|
-
|
|
314
|
-
alertContext = alertContext ? alertContext + '\n\n' + candidateContext : candidateContext;
|
|
760
|
+
alertContext = alertContext ? alertContext + '\n\n' + contextBlock : contextBlock;
|
|
315
761
|
}
|
|
316
762
|
const agent = new agent_js_1.Agent(config, toolOptions, memoryStore, mcpTools, mcpServerNames, alertContext, initialHistory, ragStore, routineStore, specialistStore, candidateStore);
|
|
317
763
|
let cleanedUp = false;
|
|
@@ -348,22 +794,66 @@ async function startRepl(config, alertContext, resume) {
|
|
|
348
794
|
catch {
|
|
349
795
|
// Silent failure — don't block exit
|
|
350
796
|
}
|
|
797
|
+
// Run the correction agent over any tool-wrapper failures queued this
|
|
798
|
+
// session. Best-effort; never block shutdown on errors.
|
|
799
|
+
if (config.correctionEnabled) {
|
|
800
|
+
try {
|
|
801
|
+
const correctionStore = agent.getCorrectionStore();
|
|
802
|
+
const pending = correctionStore.listPending();
|
|
803
|
+
if (pending.length > 0) {
|
|
804
|
+
(0, output_js_1.printInfo)(`Reviewing ${pending.length} tool-wrapper failure(s) for learning...`);
|
|
805
|
+
const result = await (0, correction_js_1.runCorrectionAgent)({
|
|
806
|
+
config,
|
|
807
|
+
toolOptions,
|
|
808
|
+
memoryStore,
|
|
809
|
+
specialistStore: agent.getSpecialistStore(),
|
|
810
|
+
correctionStore,
|
|
811
|
+
ragStore,
|
|
812
|
+
routineStore,
|
|
813
|
+
candidateStore,
|
|
814
|
+
mcpTools,
|
|
815
|
+
}, pending);
|
|
816
|
+
if (result.applied > 0) {
|
|
817
|
+
(0, output_js_1.printInfo)(` Learned from ${result.applied}/${result.processed} failure(s); examples updated.`);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
catch (err) {
|
|
822
|
+
(0, logger_js_1.debugLog)('correction:error', err instanceof Error ? err.message : String(err));
|
|
823
|
+
}
|
|
824
|
+
}
|
|
351
825
|
await mcpManager.close();
|
|
352
826
|
};
|
|
827
|
+
// Tracks the SpinnerStats for the in-flight turn so resumeSpinner() can
|
|
828
|
+
// re-attach the spinner after a pause (e.g. an ask_user prompt) without
|
|
829
|
+
// resetting startTime or the running token totals.
|
|
830
|
+
let activeSpinnerStats = null;
|
|
831
|
+
function initSpinner() {
|
|
832
|
+
const spinnerStats = {
|
|
833
|
+
startTime: Date.now(),
|
|
834
|
+
totalPromptTokens: 0,
|
|
835
|
+
totalCompletionTokens: 0,
|
|
836
|
+
latestPromptTokens: 0,
|
|
837
|
+
model: config.model,
|
|
838
|
+
contextWindowOverride: config.tokenWindow || undefined,
|
|
839
|
+
};
|
|
840
|
+
activeSpinnerStats = spinnerStats;
|
|
841
|
+
agent.setSpinnerStats(spinnerStats);
|
|
842
|
+
(0, output_js_1.startSpinner)(() => (0, output_js_1.buildSpinnerMessage)(spinnerStats));
|
|
843
|
+
}
|
|
844
|
+
function resumeSpinner() {
|
|
845
|
+
const stats = activeSpinnerStats;
|
|
846
|
+
if (!stats) {
|
|
847
|
+
initSpinner();
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
(0, output_js_1.startSpinner)(() => (0, output_js_1.buildSpinnerMessage)(stats));
|
|
851
|
+
}
|
|
353
852
|
async function runGuidedCreation(message) {
|
|
354
853
|
processing = true;
|
|
355
854
|
interrupted = false;
|
|
356
855
|
try {
|
|
357
|
-
|
|
358
|
-
startTime: Date.now(),
|
|
359
|
-
totalPromptTokens: 0,
|
|
360
|
-
totalCompletionTokens: 0,
|
|
361
|
-
latestPromptTokens: 0,
|
|
362
|
-
model: config.model,
|
|
363
|
-
contextWindowOverride: config.tokenWindow || undefined,
|
|
364
|
-
};
|
|
365
|
-
agent.setSpinnerStats(spinnerStats);
|
|
366
|
-
(0, output_js_1.startSpinner)(() => (0, output_js_1.buildSpinnerMessage)(spinnerStats));
|
|
856
|
+
initSpinner();
|
|
367
857
|
await agent.processInput(message);
|
|
368
858
|
historyStore.save(agent.getHistory());
|
|
369
859
|
}
|
|
@@ -384,10 +874,7 @@ async function startRepl(config, alertContext, resume) {
|
|
|
384
874
|
console.log();
|
|
385
875
|
void prompt();
|
|
386
876
|
}
|
|
387
|
-
/**
|
|
388
|
-
* Execute a task in single-step mode (maxSteps: 2) with structured JSON output.
|
|
389
|
-
* Used by both /task <description> and /task-{id} saved task invocations.
|
|
390
|
-
*/
|
|
877
|
+
/** Execute a task with structured JSON output. Used by /task and /task-{id}. */
|
|
391
878
|
async function executeTask(description, context) {
|
|
392
879
|
processing = true;
|
|
393
880
|
interrupted = false;
|
|
@@ -420,14 +907,17 @@ async function startRepl(config, alertContext, resume) {
|
|
|
420
907
|
if (context) {
|
|
421
908
|
userMessage += `\n\nAdditional context: ${context}`;
|
|
422
909
|
}
|
|
910
|
+
const taskMaxSteps = (0, task_js_1.getTaskMaxSteps)(config);
|
|
423
911
|
const result = await (0, ai_1.generateText)({
|
|
424
|
-
model: (0, index_js_1.
|
|
912
|
+
model: (0, index_js_1.getModelForConfig)(config, config.provider, config.model),
|
|
913
|
+
providerOptions: (0, index_js_1.getProviderOptionsForConfig)(config, config.provider),
|
|
425
914
|
tools: baseTools,
|
|
426
|
-
maxSteps:
|
|
915
|
+
maxSteps: taskMaxSteps,
|
|
427
916
|
maxTokens: config.maxTokens,
|
|
428
917
|
system: systemPrompt,
|
|
429
918
|
messages: [{ role: 'user', content: userMessage }],
|
|
430
919
|
abortSignal: taskAbortController.signal,
|
|
920
|
+
experimental_prepareStep: (0, task_js_1.makeLastStepTextOnly)(taskMaxSteps),
|
|
431
921
|
onStepFinish: ({ text, toolCalls, toolResults }) => {
|
|
432
922
|
for (const tc of toolCalls) {
|
|
433
923
|
(0, output_js_2.printToolCall)(tc.toolName, tc.args);
|
|
@@ -528,7 +1018,8 @@ async function startRepl(config, alertContext, resume) {
|
|
|
528
1018
|
const serialized = (0, context_js_1.serializeMessages)(history);
|
|
529
1019
|
const [summaryResult, domainFacts, candidateResult] = await Promise.all([
|
|
530
1020
|
(0, ai_1.generateText)({
|
|
531
|
-
model: (0, index_js_1.
|
|
1021
|
+
model: (0, index_js_1.getModelForConfig)(config, config.provider, config.model),
|
|
1022
|
+
providerOptions: (0, index_js_1.getProviderOptionsForConfig)(config, config.provider),
|
|
532
1023
|
maxTokens: 2048,
|
|
533
1024
|
system: context_js_1.SUMMARIZATION_PROMPT,
|
|
534
1025
|
messages: [
|
|
@@ -568,12 +1059,16 @@ async function startRepl(config, alertContext, resume) {
|
|
|
568
1059
|
const created = candidateStore.create(candidateResult.candidate, 'clear-save');
|
|
569
1060
|
if (config.autoCreateSpecialists &&
|
|
570
1061
|
candidateResult.candidate.confidence >= config.autoCreateThreshold) {
|
|
571
|
-
|
|
572
|
-
specialistStore.create(candidateResult.candidate.draftId, candidateResult.candidate.name, candidateResult.candidate.description, candidateResult.candidate.systemPrompt, candidateResult.candidate.guidelines);
|
|
573
|
-
candidateStore.updateStatus(created.id, 'accepted');
|
|
574
|
-
(0, output_js_1.printInfo)(`Specialist auto-created: "${candidateResult.candidate.name}" (confidence: ${Math.round(candidateResult.candidate.confidence * 100)}%). Use /specialists to view.`);
|
|
1062
|
+
(0, candidate_bootstrap_js_1.promoteCandidate)({ ...candidateResult.candidate, id: created.id }, specialistStore, candidateStore, config.autoCreateThreshold);
|
|
575
1063
|
}
|
|
576
1064
|
else {
|
|
1065
|
+
(0, logger_js_1.debugLog)('repl:auto-create', {
|
|
1066
|
+
action: 'skipped',
|
|
1067
|
+
candidate: candidateResult.candidate.name,
|
|
1068
|
+
confidence: candidateResult.candidate.confidence,
|
|
1069
|
+
threshold: config.autoCreateThreshold,
|
|
1070
|
+
autoCreateEnabled: config.autoCreateSpecialists,
|
|
1071
|
+
});
|
|
577
1072
|
(0, output_js_1.printInfo)(`Specialist suggestion detected: "${candidateResult.candidate.name}". Use /candidates to review.`);
|
|
578
1073
|
}
|
|
579
1074
|
}
|
|
@@ -599,7 +1094,7 @@ async function startRepl(config, alertContext, resume) {
|
|
|
599
1094
|
agent.clearHistory();
|
|
600
1095
|
historyStore.clear();
|
|
601
1096
|
console.clear();
|
|
602
|
-
(0, output_js_1.printWelcome)(config.provider, config.model, (0, update_js_1.getLocalVersion)());
|
|
1097
|
+
(0, output_js_1.printWelcome)(config.provider, config.model, (0, update_js_1.getLocalVersion)(), config.customProviders?.[config.provider]?.baseURL);
|
|
603
1098
|
(0, output_js_1.printInfo)('Conversation history and scratch notes cleared.');
|
|
604
1099
|
void prompt();
|
|
605
1100
|
return;
|
|
@@ -785,73 +1280,131 @@ async function startRepl(config, alertContext, resume) {
|
|
|
785
1280
|
}
|
|
786
1281
|
if (trimmed === '/provider') {
|
|
787
1282
|
const available = (0, config_js_1.getAvailableProviders)(config);
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
(0
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
config.provider = available[num - 1];
|
|
803
|
-
config.model = (0, config_js_1.getDefaultModel)(config.provider);
|
|
804
|
-
(0, config_js_1.savePreferences)({
|
|
805
|
-
provider: config.provider,
|
|
806
|
-
model: config.model,
|
|
807
|
-
maxTokens: config.maxTokens,
|
|
808
|
-
shellTimeout: config.shellTimeout,
|
|
809
|
-
tokenWindow: config.tokenWindow,
|
|
810
|
-
theme: config.theme,
|
|
1283
|
+
const customProviders = config.customProviders ?? {};
|
|
1284
|
+
const builtinAvailable = available.filter((p) => !customProviders[p]);
|
|
1285
|
+
const customAvailable = available.filter((p) => customProviders[p]);
|
|
1286
|
+
const entries = [];
|
|
1287
|
+
for (const p of builtinAvailable)
|
|
1288
|
+
entries.push({ label: p, value: p });
|
|
1289
|
+
if (customAvailable.length > 0) {
|
|
1290
|
+
entries.push({ type: 'section', title: 'Custom:' });
|
|
1291
|
+
for (const p of customAvailable) {
|
|
1292
|
+
const entry = customProviders[p];
|
|
1293
|
+
entries.push({
|
|
1294
|
+
label: p,
|
|
1295
|
+
annotation: `(${entry.sdk} → ${entry.baseURL})`,
|
|
1296
|
+
value: p,
|
|
811
1297
|
});
|
|
812
|
-
(0, output_js_1.printInfo)(` Switched to ${config.provider} (${config.model})`);
|
|
813
1298
|
}
|
|
814
|
-
|
|
815
|
-
|
|
1299
|
+
}
|
|
1300
|
+
if (builtinAvailable.length === 0 && customAvailable.length === 0) {
|
|
1301
|
+
(0, output_js_1.printInfo)('No providers have API keys configured yet — add one below.');
|
|
1302
|
+
}
|
|
1303
|
+
entries.push({ type: 'section', title: '' });
|
|
1304
|
+
entries.push({ label: '+ Add custom provider…', value: '__add__' });
|
|
1305
|
+
const signal = createMenuSignal();
|
|
1306
|
+
try {
|
|
1307
|
+
const result = await (0, menu_js_1.selectFromMenu)(rl, entries, { title: `Providers — current: ${config.provider} (${config.model})` }, signal);
|
|
1308
|
+
if (!result.cancelled) {
|
|
1309
|
+
const value = result.item.value;
|
|
1310
|
+
if (value === '__add__') {
|
|
1311
|
+
const added = await runAddProviderWizard(rl, createMenuSignal, clearMenuSignal);
|
|
1312
|
+
if (added) {
|
|
1313
|
+
config.customProviders = {
|
|
1314
|
+
...customProviders,
|
|
1315
|
+
[added.entry.name]: added.entry,
|
|
1316
|
+
};
|
|
1317
|
+
config.apiKeys = { ...(config.apiKeys ?? {}), [added.entry.name]: added.apiKey };
|
|
1318
|
+
config.provider = added.entry.name;
|
|
1319
|
+
config.model = added.entry.defaultModel;
|
|
1320
|
+
(0, config_js_1.savePreferences)({
|
|
1321
|
+
provider: config.provider,
|
|
1322
|
+
model: config.model,
|
|
1323
|
+
maxTokens: config.maxTokens,
|
|
1324
|
+
shellTimeout: config.shellTimeout,
|
|
1325
|
+
tokenWindow: config.tokenWindow,
|
|
1326
|
+
theme: config.theme,
|
|
1327
|
+
});
|
|
1328
|
+
(0, output_js_1.printInfo)(` Added and switched to ${added.entry.name} (${added.entry.defaultModel})`);
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
else {
|
|
1332
|
+
config.provider = value;
|
|
1333
|
+
config.model = (0, config_js_1.getDefaultModel)(config.provider, customProviders);
|
|
1334
|
+
(0, config_js_1.savePreferences)({
|
|
1335
|
+
provider: config.provider,
|
|
1336
|
+
model: config.model,
|
|
1337
|
+
maxTokens: config.maxTokens,
|
|
1338
|
+
shellTimeout: config.shellTimeout,
|
|
1339
|
+
tokenWindow: config.tokenWindow,
|
|
1340
|
+
theme: config.theme,
|
|
1341
|
+
});
|
|
1342
|
+
(0, output_js_1.printInfo)(` Switched to ${config.provider} (${config.model})`);
|
|
1343
|
+
}
|
|
816
1344
|
}
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
1345
|
+
}
|
|
1346
|
+
finally {
|
|
1347
|
+
clearMenuSignal();
|
|
1348
|
+
}
|
|
1349
|
+
console.log();
|
|
1350
|
+
void prompt();
|
|
820
1351
|
return;
|
|
821
1352
|
}
|
|
822
1353
|
if (trimmed === '/model') {
|
|
823
|
-
const
|
|
1354
|
+
const customProviders = config.customProviders ?? {};
|
|
1355
|
+
const customEntry = customProviders[config.provider];
|
|
1356
|
+
const models = customEntry ? customEntry.models : config_js_1.PROVIDER_MODELS[config.provider];
|
|
824
1357
|
if (!models || models.length === 0) {
|
|
825
1358
|
(0, output_js_1.printError)(`No models listed for provider "${config.provider}".`);
|
|
826
1359
|
void prompt();
|
|
827
1360
|
return;
|
|
828
1361
|
}
|
|
829
|
-
|
|
830
|
-
(
|
|
831
|
-
|
|
832
|
-
|
|
1362
|
+
const entries = models.map((m) => ({ label: m, value: m }));
|
|
1363
|
+
if (customEntry) {
|
|
1364
|
+
entries.push({ type: 'section', title: '' });
|
|
1365
|
+
entries.push({ label: '+ Type a new model name…', value: '__free__' });
|
|
833
1366
|
}
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
const
|
|
837
|
-
if (
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
1367
|
+
const signal = createMenuSignal();
|
|
1368
|
+
try {
|
|
1369
|
+
const result = await (0, menu_js_1.selectFromMenu)(rl, entries, { title: `Models — current: ${config.provider} / ${config.model}` }, signal);
|
|
1370
|
+
if (!result.cancelled) {
|
|
1371
|
+
const value = result.item.value;
|
|
1372
|
+
let chosenModel = null;
|
|
1373
|
+
if (value === '__free__' && customEntry) {
|
|
1374
|
+
const valueResult = await (0, menu_js_1.promptValue)(rl, { label: 'Model name' }, createMenuSignal());
|
|
1375
|
+
clearMenuSignal();
|
|
1376
|
+
if (!valueResult.cancelled) {
|
|
1377
|
+
const newModel = valueResult.raw.trim();
|
|
1378
|
+
if (newModel) {
|
|
1379
|
+
(0, custom_providers_js_1.rememberCustomModel)(config.provider, newModel);
|
|
1380
|
+
// Refresh local copy
|
|
1381
|
+
config.customProviders = (0, custom_providers_js_1.loadCustomProviders)();
|
|
1382
|
+
chosenModel = newModel;
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
else {
|
|
1387
|
+
chosenModel = value;
|
|
1388
|
+
}
|
|
1389
|
+
if (chosenModel) {
|
|
1390
|
+
config.model = chosenModel;
|
|
1391
|
+
(0, config_js_1.savePreferences)({
|
|
1392
|
+
provider: config.provider,
|
|
1393
|
+
model: config.model,
|
|
1394
|
+
maxTokens: config.maxTokens,
|
|
1395
|
+
shellTimeout: config.shellTimeout,
|
|
1396
|
+
tokenWindow: config.tokenWindow,
|
|
1397
|
+
theme: config.theme,
|
|
1398
|
+
});
|
|
1399
|
+
(0, output_js_1.printInfo)(` Switched to ${config.model}`);
|
|
1400
|
+
}
|
|
851
1401
|
}
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
1402
|
+
}
|
|
1403
|
+
finally {
|
|
1404
|
+
clearMenuSignal();
|
|
1405
|
+
}
|
|
1406
|
+
console.log();
|
|
1407
|
+
void prompt();
|
|
855
1408
|
return;
|
|
856
1409
|
}
|
|
857
1410
|
if (trimmed === '/theme') {
|
|
@@ -859,26 +1412,24 @@ async function startRepl(config, alertContext, resume) {
|
|
|
859
1412
|
const currentKey = (0, theme_js_1.getActiveThemeKey)();
|
|
860
1413
|
const regularKeys = allKeys.filter((k) => k !== 'high-contrast' && k !== 'colorblind');
|
|
861
1414
|
const a11yKeys = allKeys.filter((k) => k === 'high-contrast' || k === 'colorblind');
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
if (num >= 1 && num <= ordered.length) {
|
|
881
|
-
const chosen = ordered[num - 1];
|
|
1415
|
+
const entries = [
|
|
1416
|
+
...regularKeys.map((k) => ({
|
|
1417
|
+
label: theme_js_1.THEMES[k].name,
|
|
1418
|
+
active: k === currentKey,
|
|
1419
|
+
value: k,
|
|
1420
|
+
})),
|
|
1421
|
+
{ type: 'section', title: 'Accessibility:' },
|
|
1422
|
+
...a11yKeys.map((k) => ({
|
|
1423
|
+
label: theme_js_1.THEMES[k].name,
|
|
1424
|
+
active: k === currentKey,
|
|
1425
|
+
value: k,
|
|
1426
|
+
})),
|
|
1427
|
+
];
|
|
1428
|
+
const signal = createMenuSignal();
|
|
1429
|
+
try {
|
|
1430
|
+
const result = await (0, menu_js_1.selectFromMenu)(rl, entries, { title: `Themes — current: ${theme_js_1.THEMES[currentKey].name}` }, signal);
|
|
1431
|
+
if (!result.cancelled) {
|
|
1432
|
+
const chosen = result.item.value;
|
|
882
1433
|
(0, theme_js_1.setTheme)(chosen);
|
|
883
1434
|
config.theme = chosen;
|
|
884
1435
|
(0, config_js_1.savePreferences)({
|
|
@@ -891,60 +1442,74 @@ async function startRepl(config, alertContext, resume) {
|
|
|
891
1442
|
});
|
|
892
1443
|
(0, output_js_1.printInfo)(` Switched to ${theme_js_1.THEMES[chosen].name} theme.`);
|
|
893
1444
|
}
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
1445
|
+
}
|
|
1446
|
+
finally {
|
|
1447
|
+
clearMenuSignal();
|
|
1448
|
+
}
|
|
1449
|
+
console.log();
|
|
1450
|
+
void prompt();
|
|
900
1451
|
return;
|
|
901
1452
|
}
|
|
902
1453
|
if (trimmed === '/options') {
|
|
903
|
-
const
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
1454
|
+
const optEntries = Object.entries(config_js_1.OPTIONS_REGISTRY);
|
|
1455
|
+
const menuEntries = [
|
|
1456
|
+
...optEntries.map(([name, opt]) => {
|
|
1457
|
+
const current = config[opt.configKey];
|
|
1458
|
+
const tag = current === opt.default ? '(default)' : '(custom)';
|
|
1459
|
+
return {
|
|
1460
|
+
label: name,
|
|
1461
|
+
annotation: `= ${current} ${tag}`,
|
|
1462
|
+
description: opt.description,
|
|
1463
|
+
};
|
|
1464
|
+
}),
|
|
1465
|
+
{ type: 'section', title: 'Info' },
|
|
1466
|
+
{ label: 'Debug report', description: 'Print a diagnostic report for troubleshooting' },
|
|
1467
|
+
];
|
|
1468
|
+
const signal1 = createMenuSignal();
|
|
1469
|
+
let optResult;
|
|
1470
|
+
try {
|
|
1471
|
+
optResult = await (0, menu_js_1.selectFromMenu)(rl, menuEntries, { title: 'Options', promptLabel: 'Select option' }, signal1);
|
|
912
1472
|
}
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
1473
|
+
finally {
|
|
1474
|
+
clearMenuSignal();
|
|
1475
|
+
}
|
|
1476
|
+
if (!optResult.cancelled) {
|
|
1477
|
+
if (optResult.index >= optEntries.length) {
|
|
1478
|
+
// Debug report is the only non-editable entry beyond the option rows.
|
|
1479
|
+
printDebugReport();
|
|
1480
|
+
void prompt();
|
|
1481
|
+
return;
|
|
1482
|
+
}
|
|
1483
|
+
const [name, opt] = optEntries[optResult.index];
|
|
1484
|
+
const signal2 = createMenuSignal();
|
|
1485
|
+
let valResult;
|
|
1486
|
+
try {
|
|
1487
|
+
valResult = await (0, menu_js_1.promptValue)(rl, { label: `New value for ${name}` }, signal2);
|
|
1488
|
+
}
|
|
1489
|
+
finally {
|
|
1490
|
+
clearMenuSignal();
|
|
1491
|
+
}
|
|
1492
|
+
if (!valResult.cancelled) {
|
|
1493
|
+
const val = parseInt(valResult.raw, 10);
|
|
1494
|
+
const minVal = opt.default === 0 ? 0 : 1;
|
|
1495
|
+
if (!isNaN(val) && val >= minVal) {
|
|
1496
|
+
(0, config_js_1.saveOption)(name, val);
|
|
1497
|
+
config[opt.configKey] = val;
|
|
1498
|
+
(0, output_js_1.printInfo)(` ${name} set to ${val}`);
|
|
1499
|
+
if (name === 'token-window') {
|
|
1500
|
+
const modelWindow = (0, context_js_1.getContextWindow)(config.model);
|
|
1501
|
+
if (val > modelWindow) {
|
|
1502
|
+
(0, output_js_1.printInfo)(` Warning: ${val} exceeds ${config.model}'s context window (${modelWindow})`);
|
|
930
1503
|
}
|
|
931
1504
|
}
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
}
|
|
935
|
-
|
|
936
|
-
(0, output_js_1.printError)(` Invalid value. Must be ${minVal === 0 ? 'a non-negative integer' : 'a positive integer'}.`);
|
|
937
|
-
}
|
|
938
|
-
console.log();
|
|
939
|
-
void prompt();
|
|
940
|
-
});
|
|
941
|
-
}
|
|
942
|
-
else {
|
|
943
|
-
(0, output_js_1.printInfo)(' Cancelled.');
|
|
944
|
-
console.log();
|
|
945
|
-
void prompt();
|
|
1505
|
+
}
|
|
1506
|
+
else {
|
|
1507
|
+
(0, output_js_1.printError)(` Invalid value. Must be ${minVal === 0 ? 'a non-negative integer' : 'a positive integer'}.`);
|
|
1508
|
+
}
|
|
946
1509
|
}
|
|
947
|
-
}
|
|
1510
|
+
}
|
|
1511
|
+
console.log();
|
|
1512
|
+
void prompt();
|
|
948
1513
|
return;
|
|
949
1514
|
}
|
|
950
1515
|
if (trimmed === '/update') {
|
|
@@ -958,21 +1523,12 @@ async function startRepl(config, alertContext, resume) {
|
|
|
958
1523
|
(0, output_js_1.printInfo)('No routines saved. Teach me a workflow and I can save it as a routine.');
|
|
959
1524
|
}
|
|
960
1525
|
else {
|
|
961
|
-
const
|
|
962
|
-
const
|
|
963
|
-
if (
|
|
964
|
-
(
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
}
|
|
968
|
-
}
|
|
969
|
-
if (routines.length > 0) {
|
|
970
|
-
(0, output_js_1.printInfo)(`\n Routines (${routines.length}) — multi-step workflows:`);
|
|
971
|
-
for (const r of routines) {
|
|
972
|
-
(0, output_js_1.printInfo)(` /${r.id} — ${r.name}: ${r.description}`);
|
|
973
|
-
}
|
|
974
|
-
}
|
|
975
|
-
console.log();
|
|
1526
|
+
const hasTasks = allRoutines.some((r) => r.id.startsWith('task-'));
|
|
1527
|
+
const hasRoutines = allRoutines.some((r) => !r.id.startsWith('task-'));
|
|
1528
|
+
if (hasTasks)
|
|
1529
|
+
printRoutinesList('tasks');
|
|
1530
|
+
if (hasRoutines)
|
|
1531
|
+
printRoutinesList('routines');
|
|
976
1532
|
}
|
|
977
1533
|
void prompt();
|
|
978
1534
|
return;
|
|
@@ -1019,17 +1575,7 @@ Remember: task content should describe a single atomic operation with clear succ
|
|
|
1019
1575
|
return;
|
|
1020
1576
|
}
|
|
1021
1577
|
if (trimmed === '/specialists') {
|
|
1022
|
-
|
|
1023
|
-
if (specialists.length === 0) {
|
|
1024
|
-
(0, output_js_1.printInfo)('No specialist agents defined yet. Ask me to create one or use /create-specialist.');
|
|
1025
|
-
}
|
|
1026
|
-
else {
|
|
1027
|
-
(0, output_js_1.printInfo)(`\n Specialists (${specialists.length}):`);
|
|
1028
|
-
for (const s of specialists) {
|
|
1029
|
-
(0, output_js_1.printInfo)(` ${s.id} — ${s.name}: ${s.description}`);
|
|
1030
|
-
}
|
|
1031
|
-
console.log();
|
|
1032
|
-
}
|
|
1578
|
+
printSpecialistsList();
|
|
1033
1579
|
void prompt();
|
|
1034
1580
|
return;
|
|
1035
1581
|
}
|
|
@@ -1074,161 +1620,154 @@ Remember: the systemPrompt should read like a persona definition — who this sp
|
|
|
1074
1620
|
(0, output_js_1.printInfo)(' To accept or reject, tell Bernard conversationally (e.g., "accept the code-review candidate").');
|
|
1075
1621
|
(0, output_js_1.printInfo)(' The agent can create the specialist via the specialist tool, then update candidate status.\n');
|
|
1076
1622
|
// Inject candidate context so the agent knows about them for the rest of the session
|
|
1077
|
-
|
|
1078
|
-
agent.setAlertContext(candidateContext);
|
|
1623
|
+
agent.setAlertContext((0, candidate_bootstrap_js_1.buildCandidateContextBlock)(pending));
|
|
1079
1624
|
}
|
|
1080
1625
|
void prompt();
|
|
1081
1626
|
return;
|
|
1082
1627
|
}
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
criticMode: true,
|
|
1095
|
-
});
|
|
1096
|
-
(0, output_js_1.printInfo)('[CRITIC:ON] Responses will be planned and verified.');
|
|
1097
|
-
}
|
|
1098
|
-
else if (arg === 'off') {
|
|
1099
|
-
config.criticMode = false;
|
|
1100
|
-
(0, config_js_1.savePreferences)({
|
|
1101
|
-
provider: config.provider,
|
|
1102
|
-
model: config.model,
|
|
1103
|
-
maxTokens: config.maxTokens,
|
|
1104
|
-
shellTimeout: config.shellTimeout,
|
|
1105
|
-
tokenWindow: config.tokenWindow,
|
|
1106
|
-
theme: config.theme,
|
|
1107
|
-
criticMode: false,
|
|
1108
|
-
});
|
|
1109
|
-
(0, output_js_1.printInfo)('[CRITIC:OFF] Critic mode disabled.');
|
|
1110
|
-
}
|
|
1111
|
-
else {
|
|
1112
|
-
(0, output_js_1.printInfo)(`Critic mode: ${config.criticMode ? 'ON' : 'OFF'}. Usage: /critic on|off`);
|
|
1113
|
-
}
|
|
1628
|
+
// Backwards-compat shims: the standalone toggles (/critic, /react, /tool-details, /debug)
|
|
1629
|
+
// were consolidated into /agent-options and /options. Print a short pointer so users typing
|
|
1630
|
+
// the old command aren't silently dropped into the prompt.
|
|
1631
|
+
const legacyToggle = {
|
|
1632
|
+
'/critic': 'Critic mode → /agent-options',
|
|
1633
|
+
'/react': 'Coordinator (ReAct) mode → /agent-options',
|
|
1634
|
+
'/tool-details': 'Tool-call details → /agent-options',
|
|
1635
|
+
'/debug': 'Debug logging → /options',
|
|
1636
|
+
}[trimmed];
|
|
1637
|
+
if (legacyToggle) {
|
|
1638
|
+
(0, output_js_1.printInfo)(` This command moved. ${legacyToggle}`);
|
|
1114
1639
|
void prompt();
|
|
1115
1640
|
return;
|
|
1116
1641
|
}
|
|
1117
|
-
if (trimmed === '/agent-options'
|
|
1118
|
-
const
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1642
|
+
if (trimmed === '/agent-options') {
|
|
1643
|
+
const systemBools = [
|
|
1644
|
+
{
|
|
1645
|
+
key: 'autoCreateSpecialists',
|
|
1646
|
+
label: 'Auto-create specialists',
|
|
1647
|
+
description: 'Auto-promote pending specialist candidates whose score exceeds the threshold.',
|
|
1648
|
+
onMsg: ' Auto-create specialists: on',
|
|
1649
|
+
offMsg: ' Auto-create specialists: off',
|
|
1650
|
+
onToggle: (value) => {
|
|
1651
|
+
if (value) {
|
|
1652
|
+
(0, candidate_bootstrap_js_1.promotePendingCandidates)(candidateStore, specialistStore, config.autoCreateThreshold);
|
|
1653
|
+
}
|
|
1654
|
+
},
|
|
1655
|
+
},
|
|
1656
|
+
{
|
|
1657
|
+
key: 'criticMode',
|
|
1658
|
+
label: 'Critic mode',
|
|
1659
|
+
description: 'Plan the response, verify it with a critic pass, and retry on failure before replying.',
|
|
1660
|
+
onMsg: ' [CRITIC:ON] Responses will be planned and verified.',
|
|
1661
|
+
offMsg: ' [CRITIC:OFF] Critic mode disabled.',
|
|
1662
|
+
},
|
|
1663
|
+
{
|
|
1664
|
+
key: 'reactMode',
|
|
1665
|
+
label: 'Coordinator (ReAct) mode',
|
|
1666
|
+
description: 'Iterate think → act → evaluate; delegate subtasks to subagents for complex work.',
|
|
1667
|
+
onMsg: ' [REACT:ON] Operating as coordinator with iterative reasoning and delegation.',
|
|
1668
|
+
offMsg: ' [REACT:OFF] Coordinator mode disabled.',
|
|
1669
|
+
},
|
|
1670
|
+
{
|
|
1671
|
+
key: 'promptRewriter',
|
|
1672
|
+
label: 'Prompt rewriter',
|
|
1673
|
+
description: 'Restructure your prompt for the active model family before each turn.',
|
|
1674
|
+
onMsg: ' [REWRITER:ON] User prompts will be restructured for the active model before execution.',
|
|
1675
|
+
offMsg: ' [REWRITER:OFF] Prompts will be sent to the model verbatim.',
|
|
1676
|
+
},
|
|
1677
|
+
{
|
|
1678
|
+
key: 'toolDetails',
|
|
1679
|
+
label: 'Tool details',
|
|
1680
|
+
description: 'Show full tool call args and results in the transcript.',
|
|
1681
|
+
onMsg: ' [TOOL-DETAILS:ON] Full tool call args and results will be shown.',
|
|
1682
|
+
offMsg: ' [TOOL-DETAILS:OFF] Only tool names shown; args and results hidden.',
|
|
1683
|
+
onToggle: output_js_2.setToolDetailsVisible,
|
|
1684
|
+
},
|
|
1685
|
+
];
|
|
1686
|
+
async function runThresholdPrompt() {
|
|
1687
|
+
const signal = createMenuSignal();
|
|
1688
|
+
try {
|
|
1689
|
+
const val = await (0, menu_js_1.promptValue)(rl, { label: 'New threshold (0-100)' }, signal);
|
|
1690
|
+
if (val.cancelled)
|
|
1691
|
+
return;
|
|
1692
|
+
const parsed = parseFloat(val.raw);
|
|
1693
|
+
if (isNaN(parsed) || parsed < 0 || parsed > 100) {
|
|
1694
|
+
(0, output_js_1.printError)('Threshold must be a number between 0 and 100 (e.g. 0.8 or 80)');
|
|
1695
|
+
return;
|
|
1696
|
+
}
|
|
1697
|
+
const normalized = (0, config_js_1.normalizeThreshold)(parsed);
|
|
1698
|
+
config.autoCreateThreshold = normalized;
|
|
1151
1699
|
(0, config_js_1.savePreferences)({
|
|
1152
1700
|
...(0, config_js_1.loadPreferences)(),
|
|
1153
|
-
autoCreateThreshold:
|
|
1701
|
+
autoCreateThreshold: normalized,
|
|
1154
1702
|
provider: config.provider,
|
|
1155
1703
|
model: config.model,
|
|
1156
1704
|
});
|
|
1157
|
-
(0, output_js_1.printInfo)(`Auto-create threshold: ${
|
|
1705
|
+
(0, output_js_1.printInfo)(` Auto-create threshold: ${normalized} (${Math.round(normalized * 100)}%)`);
|
|
1706
|
+
if (config.autoCreateSpecialists) {
|
|
1707
|
+
(0, candidate_bootstrap_js_1.promotePendingCandidates)(candidateStore, specialistStore, config.autoCreateThreshold);
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
finally {
|
|
1711
|
+
clearMenuSignal();
|
|
1158
1712
|
}
|
|
1159
1713
|
}
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1714
|
+
const toggleRow = (opt) => ({
|
|
1715
|
+
kind: 'item',
|
|
1716
|
+
item: {
|
|
1717
|
+
label: opt.label,
|
|
1718
|
+
annotation: `= ${config[opt.key] ? 'on' : 'off'}`,
|
|
1719
|
+
description: opt.description,
|
|
1720
|
+
},
|
|
1721
|
+
action: () => toggleBooleanPref(opt.key, opt.label, opt.onMsg, opt.offMsg, opt.onToggle),
|
|
1722
|
+
});
|
|
1723
|
+
const rows = [
|
|
1724
|
+
{ kind: 'section', title: 'System' },
|
|
1725
|
+
toggleRow(systemBools[0]),
|
|
1726
|
+
{
|
|
1727
|
+
kind: 'item',
|
|
1728
|
+
item: {
|
|
1729
|
+
label: 'Auto-create threshold',
|
|
1730
|
+
annotation: `= ${config.autoCreateThreshold} (${Math.round(config.autoCreateThreshold * 100)}%)`,
|
|
1731
|
+
description: 'Minimum score (0-1) a pending specialist needs before auto-promotion.',
|
|
1732
|
+
},
|
|
1733
|
+
action: runThresholdPrompt,
|
|
1734
|
+
},
|
|
1735
|
+
...systemBools.slice(1).map(toggleRow),
|
|
1736
|
+
{ kind: 'section', title: 'User-created' },
|
|
1737
|
+
{
|
|
1738
|
+
kind: 'item',
|
|
1739
|
+
item: {
|
|
1740
|
+
label: 'Specialists',
|
|
1741
|
+
description: 'List bundled and user-created specialists.',
|
|
1742
|
+
},
|
|
1743
|
+
action: () => printSpecialistsList(),
|
|
1744
|
+
},
|
|
1745
|
+
{
|
|
1746
|
+
kind: 'item',
|
|
1747
|
+
item: { label: 'Tasks', description: 'List saved single-step tasks.' },
|
|
1748
|
+
action: () => printRoutinesList('tasks'),
|
|
1749
|
+
},
|
|
1750
|
+
{
|
|
1751
|
+
kind: 'item',
|
|
1752
|
+
item: { label: 'Routines', description: 'List saved multi-step routines.' },
|
|
1753
|
+
action: () => printRoutinesList('routines'),
|
|
1754
|
+
},
|
|
1755
|
+
];
|
|
1756
|
+
const topEntries = rows.map((r) => r.kind === 'section' ? { type: 'section', title: r.title } : r.item);
|
|
1757
|
+
const itemActions = rows.flatMap((r) => (r.kind === 'item' ? [r.action] : []));
|
|
1758
|
+
const signal1 = createMenuSignal();
|
|
1759
|
+
let topResult;
|
|
1760
|
+
try {
|
|
1761
|
+
topResult = await (0, menu_js_1.selectFromMenu)(rl, topEntries, { title: 'Agent Options' }, signal1);
|
|
1188
1762
|
}
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
if (s.connected) {
|
|
1192
|
-
console.log(t.muted(` ${s.name}: connected (${s.toolCount} tools)`));
|
|
1193
|
-
}
|
|
1194
|
-
else {
|
|
1195
|
-
console.log(t.muted(` ${s.name}: failed — ${s.error}`));
|
|
1196
|
-
}
|
|
1197
|
-
}
|
|
1763
|
+
finally {
|
|
1764
|
+
clearMenuSignal();
|
|
1198
1765
|
}
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1766
|
+
if (!topResult.cancelled) {
|
|
1767
|
+
const action = itemActions[topResult.index];
|
|
1768
|
+
if (action)
|
|
1769
|
+
await action();
|
|
1203
1770
|
}
|
|
1204
|
-
console.log(t.text('\n Memory:'));
|
|
1205
|
-
console.log(t.muted(` Persistent memories: ${memoryStore.listMemory().length}`));
|
|
1206
|
-
console.log(t.text('\n Cron:'));
|
|
1207
|
-
console.log(t.muted(` Daemon: ${(0, client_js_1.isDaemonRunning)() ? 'running' : 'stopped'}`));
|
|
1208
|
-
let debugJobCount = 0;
|
|
1209
|
-
try {
|
|
1210
|
-
const raw = fs.readFileSync(paths_js_1.CRON_JOBS_FILE, 'utf-8');
|
|
1211
|
-
debugJobCount = JSON.parse(raw).length;
|
|
1212
|
-
}
|
|
1213
|
-
catch {
|
|
1214
|
-
// jobs.json doesn't exist yet — that's fine
|
|
1215
|
-
}
|
|
1216
|
-
console.log(t.muted(` Jobs: ${debugJobCount}`));
|
|
1217
|
-
console.log(t.text('\n Conversation:'));
|
|
1218
|
-
console.log(t.muted(` Messages: ${agent.getHistory().length}`));
|
|
1219
|
-
console.log(t.text('\n Settings:'));
|
|
1220
|
-
console.log(t.muted(` Theme: ${(0, theme_js_1.getActiveThemeKey)()}`));
|
|
1221
|
-
console.log(t.muted(` Critic mode: ${config.criticMode ? 'on' : 'off'}`));
|
|
1222
|
-
const debugEnabled = process.env.BERNARD_DEBUG === 'true' || process.env.BERNARD_DEBUG === '1';
|
|
1223
|
-
console.log(t.muted(` Debug mode: ${debugEnabled ? 'on' : 'off'}`));
|
|
1224
|
-
console.log(t.text('\n Paths:'));
|
|
1225
|
-
if (process.env.BERNARD_HOME) {
|
|
1226
|
-
console.log(t.muted(` BERNARD_HOME: ${process.env.BERNARD_HOME}`));
|
|
1227
|
-
}
|
|
1228
|
-
console.log(t.muted(` Config: ${paths_js_1.CONFIG_DIR}`));
|
|
1229
|
-
console.log(t.muted(` Data: ${paths_js_1.DATA_DIR}`));
|
|
1230
|
-
console.log(t.muted(` Cache: ${paths_js_1.CACHE_DIR}`));
|
|
1231
|
-
console.log(t.muted(` State: ${paths_js_1.STATE_DIR}`));
|
|
1232
1771
|
console.log();
|
|
1233
1772
|
void prompt();
|
|
1234
1773
|
return;
|
|
@@ -1246,6 +1785,70 @@ Remember: the systemPrompt should read like a persona definition — who this sp
|
|
|
1246
1785
|
void prompt();
|
|
1247
1786
|
return;
|
|
1248
1787
|
}
|
|
1788
|
+
if (trimmed === '/image' || trimmed.startsWith('/image ')) {
|
|
1789
|
+
const args = trimmed.slice('/image'.length).trim();
|
|
1790
|
+
if (!args) {
|
|
1791
|
+
(0, output_js_1.printError)('Usage: /image <path> [prompt]');
|
|
1792
|
+
(0, output_js_1.printInfo)(' Example: /image ~/screenshot.png What is on the screen?');
|
|
1793
|
+
(0, output_js_1.printInfo)(' Tip: you can also paste image paths inline, e.g. "describe ~/screenshot.png"');
|
|
1794
|
+
void prompt();
|
|
1795
|
+
return;
|
|
1796
|
+
}
|
|
1797
|
+
let imagePath;
|
|
1798
|
+
let userText;
|
|
1799
|
+
const quoteMatch = args.match(/^(["'])(.+?)\1(?:\s+(.*))?$/);
|
|
1800
|
+
if (quoteMatch) {
|
|
1801
|
+
imagePath = quoteMatch[2];
|
|
1802
|
+
userText = quoteMatch[3]?.trim() || 'Describe this image.';
|
|
1803
|
+
}
|
|
1804
|
+
else {
|
|
1805
|
+
const spaceIdx = args.indexOf(' ');
|
|
1806
|
+
imagePath = spaceIdx === -1 ? args : args.slice(0, spaceIdx);
|
|
1807
|
+
userText =
|
|
1808
|
+
spaceIdx === -1
|
|
1809
|
+
? 'Describe this image.'
|
|
1810
|
+
: args.slice(spaceIdx + 1).trim() || 'Describe this image.';
|
|
1811
|
+
}
|
|
1812
|
+
if (!(0, image_js_1.isVisionCapableModel)(config.provider, config.model)) {
|
|
1813
|
+
(0, output_js_1.printError)(`Model "${config.model}" does not support image input. Switch to a vision-capable model with /model.`);
|
|
1814
|
+
void prompt();
|
|
1815
|
+
return;
|
|
1816
|
+
}
|
|
1817
|
+
let attachment;
|
|
1818
|
+
try {
|
|
1819
|
+
attachment = (0, image_js_1.loadImage)(imagePath);
|
|
1820
|
+
}
|
|
1821
|
+
catch (err) {
|
|
1822
|
+
(0, output_js_1.printError)(err instanceof Error ? err.message : String(err));
|
|
1823
|
+
void prompt();
|
|
1824
|
+
return;
|
|
1825
|
+
}
|
|
1826
|
+
(0, output_js_1.printInfo)(` Attaching image: ${attachment.path}`);
|
|
1827
|
+
(0, output_js_1.printInfo)(` Image will be sent to ${config.provider}/${config.model}`);
|
|
1828
|
+
processing = true;
|
|
1829
|
+
interrupted = false;
|
|
1830
|
+
try {
|
|
1831
|
+
initSpinner();
|
|
1832
|
+
await agent.processInput(userText, [attachment]);
|
|
1833
|
+
historyStore.save(agent.getHistory());
|
|
1834
|
+
}
|
|
1835
|
+
catch (err) {
|
|
1836
|
+
if (!interrupted) {
|
|
1837
|
+
(0, output_js_1.printError)(err instanceof Error ? err.message : String(err));
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
finally {
|
|
1841
|
+
processing = false;
|
|
1842
|
+
(0, output_js_1.stopSpinner)();
|
|
1843
|
+
}
|
|
1844
|
+
if (interrupted) {
|
|
1845
|
+
(0, output_js_1.printInfo)('Interrupted.');
|
|
1846
|
+
interrupted = false;
|
|
1847
|
+
}
|
|
1848
|
+
console.log();
|
|
1849
|
+
void prompt();
|
|
1850
|
+
return;
|
|
1851
|
+
}
|
|
1249
1852
|
// Dynamic routine invocation: /{routine-id} [args...]
|
|
1250
1853
|
{
|
|
1251
1854
|
const parts = trimmed.slice(1).split(/\s+/);
|
|
@@ -1270,16 +1873,7 @@ Remember: the systemPrompt should read like a persona definition — who this sp
|
|
|
1270
1873
|
processing = true;
|
|
1271
1874
|
interrupted = false;
|
|
1272
1875
|
try {
|
|
1273
|
-
|
|
1274
|
-
startTime: Date.now(),
|
|
1275
|
-
totalPromptTokens: 0,
|
|
1276
|
-
totalCompletionTokens: 0,
|
|
1277
|
-
latestPromptTokens: 0,
|
|
1278
|
-
model: config.model,
|
|
1279
|
-
contextWindowOverride: config.tokenWindow || undefined,
|
|
1280
|
-
};
|
|
1281
|
-
agent.setSpinnerStats(spinnerStats);
|
|
1282
|
-
(0, output_js_1.startSpinner)(() => (0, output_js_1.buildSpinnerMessage)(spinnerStats));
|
|
1876
|
+
initSpinner();
|
|
1283
1877
|
await agent.processInput(message);
|
|
1284
1878
|
historyStore.save(agent.getHistory());
|
|
1285
1879
|
}
|
|
@@ -1303,20 +1897,35 @@ Remember: the systemPrompt should read like a persona definition — who this sp
|
|
|
1303
1897
|
}
|
|
1304
1898
|
}
|
|
1305
1899
|
} // end slash command handling
|
|
1900
|
+
let inlineImages;
|
|
1901
|
+
const candidatePaths = (0, image_js_1.extractImagePaths)(trimmed);
|
|
1902
|
+
if (candidatePaths.length > 0) {
|
|
1903
|
+
if ((0, image_js_1.isVisionCapableModel)(config.provider, config.model)) {
|
|
1904
|
+
const loaded = [];
|
|
1905
|
+
for (const p of candidatePaths) {
|
|
1906
|
+
const img = (0, image_js_1.tryLoadImage)(p);
|
|
1907
|
+
if (img)
|
|
1908
|
+
loaded.push(img);
|
|
1909
|
+
}
|
|
1910
|
+
if (loaded.length > 0) {
|
|
1911
|
+
for (const img of loaded) {
|
|
1912
|
+
(0, output_js_1.printInfo)(` Attaching image: ${img.path}`);
|
|
1913
|
+
}
|
|
1914
|
+
inlineImages = loaded;
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
else {
|
|
1918
|
+
(0, output_js_2.printWarning)(`Image(s) detected but model "${config.model}" does not support vision. Sending as text only.`);
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
const resolvedEntries = await runReferenceResolver(trimmed);
|
|
1922
|
+
const rewritten = await runPromptRewriter(trimmed, resolvedEntries);
|
|
1923
|
+
const agentInput = rewritten ?? trimmed;
|
|
1306
1924
|
processing = true;
|
|
1307
1925
|
interrupted = false;
|
|
1308
1926
|
try {
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
totalPromptTokens: 0,
|
|
1312
|
-
totalCompletionTokens: 0,
|
|
1313
|
-
latestPromptTokens: 0,
|
|
1314
|
-
model: config.model,
|
|
1315
|
-
contextWindowOverride: config.tokenWindow || undefined,
|
|
1316
|
-
};
|
|
1317
|
-
agent.setSpinnerStats(spinnerStats);
|
|
1318
|
-
(0, output_js_1.startSpinner)(() => (0, output_js_1.buildSpinnerMessage)(spinnerStats));
|
|
1319
|
-
await agent.processInput(trimmed);
|
|
1927
|
+
initSpinner();
|
|
1928
|
+
await agent.processInput(agentInput, inlineImages, resolvedEntries);
|
|
1320
1929
|
historyStore.save(agent.getHistory());
|
|
1321
1930
|
}
|
|
1322
1931
|
catch (err) {
|
|
@@ -1360,4 +1969,102 @@ Remember: the systemPrompt should read like a persona definition — who this sp
|
|
|
1360
1969
|
});
|
|
1361
1970
|
void prompt();
|
|
1362
1971
|
}
|
|
1972
|
+
/**
|
|
1973
|
+
* Interactive wizard for adding a custom provider from `/provider`.
|
|
1974
|
+
* Returns the saved entry plus the freshly entered API key on success
|
|
1975
|
+
* (so the caller can update the live `config.apiKeys` map without
|
|
1976
|
+
* re-reading from disk), or `null` on cancel/error.
|
|
1977
|
+
*/
|
|
1978
|
+
async function runAddProviderWizard(rl, createMenuSignal, clearMenuSignal) {
|
|
1979
|
+
(0, output_js_1.printInfo)('\nAdd custom provider — follows the same SDK contract as built-ins.');
|
|
1980
|
+
// 1. SDK choice
|
|
1981
|
+
const sdkEntries = custom_providers_js_1.SUPPORTED_SDKS.map((s) => ({ label: s, value: s }));
|
|
1982
|
+
let sdkResult;
|
|
1983
|
+
let sig = createMenuSignal();
|
|
1984
|
+
try {
|
|
1985
|
+
sdkResult = await (0, menu_js_1.selectFromMenu)(rl, sdkEntries, { title: 'Which SDK to use?' }, sig);
|
|
1986
|
+
}
|
|
1987
|
+
finally {
|
|
1988
|
+
clearMenuSignal();
|
|
1989
|
+
}
|
|
1990
|
+
if (sdkResult.cancelled)
|
|
1991
|
+
return null;
|
|
1992
|
+
const sdk = sdkResult.item.value;
|
|
1993
|
+
// 2. Name
|
|
1994
|
+
sig = createMenuSignal();
|
|
1995
|
+
let nameResult;
|
|
1996
|
+
try {
|
|
1997
|
+
nameResult = await (0, menu_js_1.promptValue)(rl, { label: 'Provider name (lowercase, e.g. "ollama")' }, sig);
|
|
1998
|
+
}
|
|
1999
|
+
finally {
|
|
2000
|
+
clearMenuSignal();
|
|
2001
|
+
}
|
|
2002
|
+
if (nameResult.cancelled)
|
|
2003
|
+
return null;
|
|
2004
|
+
const name = nameResult.raw.trim();
|
|
2005
|
+
const nameErr = (0, custom_providers_js_1.validateProviderName)(name);
|
|
2006
|
+
if (nameErr) {
|
|
2007
|
+
(0, output_js_1.printError)(nameErr);
|
|
2008
|
+
return null;
|
|
2009
|
+
}
|
|
2010
|
+
// 3. Base URL
|
|
2011
|
+
sig = createMenuSignal();
|
|
2012
|
+
let urlResult;
|
|
2013
|
+
try {
|
|
2014
|
+
urlResult = await (0, menu_js_1.promptValue)(rl, { label: 'Base URL (e.g. http://localhost:11434/v1)' }, sig);
|
|
2015
|
+
}
|
|
2016
|
+
finally {
|
|
2017
|
+
clearMenuSignal();
|
|
2018
|
+
}
|
|
2019
|
+
if (urlResult.cancelled)
|
|
2020
|
+
return null;
|
|
2021
|
+
const baseURL = urlResult.raw.trim();
|
|
2022
|
+
const urlErr = (0, custom_providers_js_1.validateBaseURL)(baseURL);
|
|
2023
|
+
if (urlErr) {
|
|
2024
|
+
(0, output_js_1.printError)(urlErr);
|
|
2025
|
+
return null;
|
|
2026
|
+
}
|
|
2027
|
+
// 4. Default model
|
|
2028
|
+
sig = createMenuSignal();
|
|
2029
|
+
let modelResult;
|
|
2030
|
+
try {
|
|
2031
|
+
modelResult = await (0, menu_js_1.promptValue)(rl, { label: 'Default model name' }, sig);
|
|
2032
|
+
}
|
|
2033
|
+
finally {
|
|
2034
|
+
clearMenuSignal();
|
|
2035
|
+
}
|
|
2036
|
+
if (modelResult.cancelled)
|
|
2037
|
+
return null;
|
|
2038
|
+
const defaultModel = modelResult.raw.trim();
|
|
2039
|
+
if (!defaultModel) {
|
|
2040
|
+
(0, output_js_1.printError)('Default model cannot be empty.');
|
|
2041
|
+
return null;
|
|
2042
|
+
}
|
|
2043
|
+
// 5. API key
|
|
2044
|
+
sig = createMenuSignal();
|
|
2045
|
+
let keyResult;
|
|
2046
|
+
try {
|
|
2047
|
+
keyResult = await (0, menu_js_1.promptValue)(rl, { label: 'API key (any non-empty token; some local servers ignore the value)' }, sig);
|
|
2048
|
+
}
|
|
2049
|
+
finally {
|
|
2050
|
+
clearMenuSignal();
|
|
2051
|
+
}
|
|
2052
|
+
if (keyResult.cancelled)
|
|
2053
|
+
return null;
|
|
2054
|
+
const apiKey = keyResult.raw.trim();
|
|
2055
|
+
if (!apiKey) {
|
|
2056
|
+
(0, output_js_1.printError)('API key cannot be empty.');
|
|
2057
|
+
return null;
|
|
2058
|
+
}
|
|
2059
|
+
try {
|
|
2060
|
+
const entry = (0, custom_providers_js_1.saveCustomProvider)({ name, sdk, baseURL, defaultModel });
|
|
2061
|
+
(0, config_js_1.saveProviderKey)(name, apiKey);
|
|
2062
|
+
return { entry, apiKey };
|
|
2063
|
+
}
|
|
2064
|
+
catch (err) {
|
|
2065
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2066
|
+
(0, output_js_1.printError)(message);
|
|
2067
|
+
return null;
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
1363
2070
|
//# sourceMappingURL=repl.js.map
|