bernard-agent 0.8.1 → 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 +126 -10
- package/dist/config.js +222 -45
- 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 +1038 -371
- 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 +5 -5
- package/dist/tools/task.js +9 -42
- 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,49 +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");
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
specialistStore.create(candidate.draftId, candidate.name, candidate.description, candidate.systemPrompt, candidate.guidelines);
|
|
71
|
-
candidateStore.updateStatus(candidate.id, 'accepted');
|
|
72
|
-
(0, logger_js_1.debugLog)('repl:auto-create', {
|
|
73
|
-
candidate: candidate.name,
|
|
74
|
-
confidence: candidate.confidence,
|
|
75
|
-
threshold,
|
|
76
|
-
});
|
|
77
|
-
(0, output_js_1.printInfo)(`Specialist auto-created: "${candidate.name}" (confidence: ${Math.round(candidate.confidence * 100)}%). Use /specialists to view.`);
|
|
78
|
-
}
|
|
79
|
-
/** Re-evaluate all pending candidates and auto-create those meeting the threshold. */
|
|
80
|
-
function promotePendingCandidates(candidateStore, specialistStore, threshold) {
|
|
81
|
-
const pending = candidateStore.listPending();
|
|
82
|
-
for (const c of pending) {
|
|
83
|
-
if (c.confidence >= threshold) {
|
|
84
|
-
try {
|
|
85
|
-
promoteCandidate(c, specialistStore, candidateStore, threshold);
|
|
86
|
-
}
|
|
87
|
-
catch (error) {
|
|
88
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
89
|
-
(0, logger_js_1.debugLog)('repl:auto-create', {
|
|
90
|
-
action: 're-evaluate-failed',
|
|
91
|
-
candidate: c.name,
|
|
92
|
-
confidence: c.confidence,
|
|
93
|
-
error: errorMessage,
|
|
94
|
-
});
|
|
95
|
-
(0, output_js_2.printWarning)(`Failed to auto-create specialist "${c.name}": ${errorMessage}`);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
76
|
+
const image_js_1 = require("./image.js");
|
|
77
|
+
const menu_js_1 = require("./menu.js");
|
|
100
78
|
/**
|
|
101
79
|
* Launch the interactive REPL, wiring up readline, MCP servers, memory stores, and the agent loop.
|
|
102
80
|
* @param config - Resolved runtime configuration (provider, model, tokens, etc.).
|
|
@@ -104,6 +82,7 @@ function promotePendingCandidates(candidateStore, specialistStore, threshold) {
|
|
|
104
82
|
* @param resume - When true, reload the previous conversation from disk and continue it.
|
|
105
83
|
*/
|
|
106
84
|
async function startRepl(config, alertContext, resume) {
|
|
85
|
+
(0, output_js_2.setToolDetailsVisible)(config.toolDetails);
|
|
107
86
|
const SLASH_COMMANDS = [
|
|
108
87
|
{ command: '/help', description: 'Show this help' },
|
|
109
88
|
{ command: '/clear', description: 'Clear conversation (--save/-s to summarize first)' },
|
|
@@ -129,9 +108,11 @@ async function startRepl(config, alertContext, resume) {
|
|
|
129
108
|
{ command: '/specialists', description: 'List specialist agents' },
|
|
130
109
|
{ command: '/create-specialist', description: 'Create a specialist with guided AI assistance' },
|
|
131
110
|
{ command: '/candidates', description: 'Review specialist suggestions' },
|
|
132
|
-
{
|
|
133
|
-
|
|
134
|
-
|
|
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]' },
|
|
135
116
|
{ command: '/exit', description: 'Quit Bernard' },
|
|
136
117
|
];
|
|
137
118
|
const routineStore = new routines_js_1.RoutineStore();
|
|
@@ -171,7 +152,8 @@ async function startRepl(config, alertContext, resume) {
|
|
|
171
152
|
function getPromptStr() {
|
|
172
153
|
const { ansi } = (0, theme_js_1.getTheme)();
|
|
173
154
|
const criticLabel = config.criticMode ? `${ansi.warning}\u25C6${ansi.reset} ` : '';
|
|
174
|
-
|
|
155
|
+
const reactLabel = config.reactMode ? `${ansi.prompt}\u25B7${ansi.reset} ` : '';
|
|
156
|
+
return `${criticLabel}${reactLabel}${ansi.prompt}bernard>${ansi.reset} `;
|
|
175
157
|
}
|
|
176
158
|
if (process.stdin.isTTY) {
|
|
177
159
|
process.stdout.write('\x1b[?2004h'); // enable bracket paste mode
|
|
@@ -208,9 +190,382 @@ async function startRepl(config, alertContext, resume) {
|
|
|
208
190
|
let processing = false;
|
|
209
191
|
let interrupted = false;
|
|
210
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
|
+
}
|
|
211
562
|
process.stdin.on('keypress', (_str, key) => {
|
|
212
563
|
if (!key)
|
|
213
564
|
return;
|
|
565
|
+
if (key.name === 'escape' && menuAbortController) {
|
|
566
|
+
menuAbortController.abort();
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
214
569
|
if (key.name === 'escape' && processing) {
|
|
215
570
|
if (taskAbortController) {
|
|
216
571
|
taskAbortController.abort();
|
|
@@ -299,17 +654,80 @@ async function startRepl(config, alertContext, resume) {
|
|
|
299
654
|
}
|
|
300
655
|
const mcpTools = mcpManager.getTools();
|
|
301
656
|
const mcpServerNames = mcpManager.getConnectedServerNames();
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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(' ');
|
|
309
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];
|
|
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
|
+
});
|
|
310
727
|
const toolOptions = {
|
|
311
728
|
shellTimeout: config.shellTimeout,
|
|
312
729
|
confirmDangerous: confirmFn,
|
|
730
|
+
askUser: askUserFn,
|
|
313
731
|
};
|
|
314
732
|
const historyStore = new history_js_1.HistoryStore();
|
|
315
733
|
let initialHistory;
|
|
@@ -336,14 +754,10 @@ async function startRepl(config, alertContext, resume) {
|
|
|
336
754
|
(0, output_js_1.printInfo)('No previous conversation found — starting fresh.');
|
|
337
755
|
}
|
|
338
756
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
candidateStore.reconcileSaved(specialistStore.list());
|
|
342
|
-
const pendingCandidates = candidateStore.listPending();
|
|
343
|
-
if (pendingCandidates.length > 0) {
|
|
757
|
+
const { pending: pendingCandidates, contextBlock } = (0, candidate_bootstrap_js_1.bootstrapPendingCandidates)(candidateStore, specialistStore, config);
|
|
758
|
+
if (contextBlock) {
|
|
344
759
|
(0, output_js_1.printInfo)(` ${pendingCandidates.length} specialist suggestion(s) pending. Use /candidates to review.`);
|
|
345
|
-
|
|
346
|
-
alertContext = alertContext ? alertContext + '\n\n' + candidateContext : candidateContext;
|
|
760
|
+
alertContext = alertContext ? alertContext + '\n\n' + contextBlock : contextBlock;
|
|
347
761
|
}
|
|
348
762
|
const agent = new agent_js_1.Agent(config, toolOptions, memoryStore, mcpTools, mcpServerNames, alertContext, initialHistory, ragStore, routineStore, specialistStore, candidateStore);
|
|
349
763
|
let cleanedUp = false;
|
|
@@ -380,22 +794,66 @@ async function startRepl(config, alertContext, resume) {
|
|
|
380
794
|
catch {
|
|
381
795
|
// Silent failure — don't block exit
|
|
382
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
|
+
}
|
|
383
825
|
await mcpManager.close();
|
|
384
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
|
+
}
|
|
385
852
|
async function runGuidedCreation(message) {
|
|
386
853
|
processing = true;
|
|
387
854
|
interrupted = false;
|
|
388
855
|
try {
|
|
389
|
-
|
|
390
|
-
startTime: Date.now(),
|
|
391
|
-
totalPromptTokens: 0,
|
|
392
|
-
totalCompletionTokens: 0,
|
|
393
|
-
latestPromptTokens: 0,
|
|
394
|
-
model: config.model,
|
|
395
|
-
contextWindowOverride: config.tokenWindow || undefined,
|
|
396
|
-
};
|
|
397
|
-
agent.setSpinnerStats(spinnerStats);
|
|
398
|
-
(0, output_js_1.startSpinner)(() => (0, output_js_1.buildSpinnerMessage)(spinnerStats));
|
|
856
|
+
initSpinner();
|
|
399
857
|
await agent.processInput(message);
|
|
400
858
|
historyStore.save(agent.getHistory());
|
|
401
859
|
}
|
|
@@ -451,7 +909,8 @@ async function startRepl(config, alertContext, resume) {
|
|
|
451
909
|
}
|
|
452
910
|
const taskMaxSteps = (0, task_js_1.getTaskMaxSteps)(config);
|
|
453
911
|
const result = await (0, ai_1.generateText)({
|
|
454
|
-
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),
|
|
455
914
|
tools: baseTools,
|
|
456
915
|
maxSteps: taskMaxSteps,
|
|
457
916
|
maxTokens: config.maxTokens,
|
|
@@ -559,7 +1018,8 @@ async function startRepl(config, alertContext, resume) {
|
|
|
559
1018
|
const serialized = (0, context_js_1.serializeMessages)(history);
|
|
560
1019
|
const [summaryResult, domainFacts, candidateResult] = await Promise.all([
|
|
561
1020
|
(0, ai_1.generateText)({
|
|
562
|
-
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),
|
|
563
1023
|
maxTokens: 2048,
|
|
564
1024
|
system: context_js_1.SUMMARIZATION_PROMPT,
|
|
565
1025
|
messages: [
|
|
@@ -599,7 +1059,7 @@ async function startRepl(config, alertContext, resume) {
|
|
|
599
1059
|
const created = candidateStore.create(candidateResult.candidate, 'clear-save');
|
|
600
1060
|
if (config.autoCreateSpecialists &&
|
|
601
1061
|
candidateResult.candidate.confidence >= config.autoCreateThreshold) {
|
|
602
|
-
promoteCandidate({ ...candidateResult.candidate, id: created.id }, specialistStore, candidateStore, config.autoCreateThreshold);
|
|
1062
|
+
(0, candidate_bootstrap_js_1.promoteCandidate)({ ...candidateResult.candidate, id: created.id }, specialistStore, candidateStore, config.autoCreateThreshold);
|
|
603
1063
|
}
|
|
604
1064
|
else {
|
|
605
1065
|
(0, logger_js_1.debugLog)('repl:auto-create', {
|
|
@@ -634,7 +1094,7 @@ async function startRepl(config, alertContext, resume) {
|
|
|
634
1094
|
agent.clearHistory();
|
|
635
1095
|
historyStore.clear();
|
|
636
1096
|
console.clear();
|
|
637
|
-
(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);
|
|
638
1098
|
(0, output_js_1.printInfo)('Conversation history and scratch notes cleared.');
|
|
639
1099
|
void prompt();
|
|
640
1100
|
return;
|
|
@@ -820,73 +1280,131 @@ async function startRepl(config, alertContext, resume) {
|
|
|
820
1280
|
}
|
|
821
1281
|
if (trimmed === '/provider') {
|
|
822
1282
|
const available = (0, config_js_1.getAvailableProviders)(config);
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
(0
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
config.provider = available[num - 1];
|
|
838
|
-
config.model = (0, config_js_1.getDefaultModel)(config.provider);
|
|
839
|
-
(0, config_js_1.savePreferences)({
|
|
840
|
-
provider: config.provider,
|
|
841
|
-
model: config.model,
|
|
842
|
-
maxTokens: config.maxTokens,
|
|
843
|
-
shellTimeout: config.shellTimeout,
|
|
844
|
-
tokenWindow: config.tokenWindow,
|
|
845
|
-
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,
|
|
846
1297
|
});
|
|
847
|
-
(0, output_js_1.printInfo)(` Switched to ${config.provider} (${config.model})`);
|
|
848
1298
|
}
|
|
849
|
-
|
|
850
|
-
|
|
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
|
+
}
|
|
851
1344
|
}
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
1345
|
+
}
|
|
1346
|
+
finally {
|
|
1347
|
+
clearMenuSignal();
|
|
1348
|
+
}
|
|
1349
|
+
console.log();
|
|
1350
|
+
void prompt();
|
|
855
1351
|
return;
|
|
856
1352
|
}
|
|
857
1353
|
if (trimmed === '/model') {
|
|
858
|
-
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];
|
|
859
1357
|
if (!models || models.length === 0) {
|
|
860
1358
|
(0, output_js_1.printError)(`No models listed for provider "${config.provider}".`);
|
|
861
1359
|
void prompt();
|
|
862
1360
|
return;
|
|
863
1361
|
}
|
|
864
|
-
|
|
865
|
-
(
|
|
866
|
-
|
|
867
|
-
|
|
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__' });
|
|
868
1366
|
}
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
const
|
|
872
|
-
if (
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
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
|
+
}
|
|
886
1401
|
}
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
1402
|
+
}
|
|
1403
|
+
finally {
|
|
1404
|
+
clearMenuSignal();
|
|
1405
|
+
}
|
|
1406
|
+
console.log();
|
|
1407
|
+
void prompt();
|
|
890
1408
|
return;
|
|
891
1409
|
}
|
|
892
1410
|
if (trimmed === '/theme') {
|
|
@@ -894,26 +1412,24 @@ async function startRepl(config, alertContext, resume) {
|
|
|
894
1412
|
const currentKey = (0, theme_js_1.getActiveThemeKey)();
|
|
895
1413
|
const regularKeys = allKeys.filter((k) => k !== 'high-contrast' && k !== 'colorblind');
|
|
896
1414
|
const a11yKeys = allKeys.filter((k) => k === 'high-contrast' || k === 'colorblind');
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
if (num >= 1 && num <= ordered.length) {
|
|
916
|
-
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;
|
|
917
1433
|
(0, theme_js_1.setTheme)(chosen);
|
|
918
1434
|
config.theme = chosen;
|
|
919
1435
|
(0, config_js_1.savePreferences)({
|
|
@@ -926,60 +1442,74 @@ async function startRepl(config, alertContext, resume) {
|
|
|
926
1442
|
});
|
|
927
1443
|
(0, output_js_1.printInfo)(` Switched to ${theme_js_1.THEMES[chosen].name} theme.`);
|
|
928
1444
|
}
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
1445
|
+
}
|
|
1446
|
+
finally {
|
|
1447
|
+
clearMenuSignal();
|
|
1448
|
+
}
|
|
1449
|
+
console.log();
|
|
1450
|
+
void prompt();
|
|
935
1451
|
return;
|
|
936
1452
|
}
|
|
937
1453
|
if (trimmed === '/options') {
|
|
938
|
-
const
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
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);
|
|
947
1472
|
}
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
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})`);
|
|
965
1503
|
}
|
|
966
1504
|
}
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
}
|
|
970
|
-
|
|
971
|
-
(0, output_js_1.printError)(` Invalid value. Must be ${minVal === 0 ? 'a non-negative integer' : 'a positive integer'}.`);
|
|
972
|
-
}
|
|
973
|
-
console.log();
|
|
974
|
-
void prompt();
|
|
975
|
-
});
|
|
976
|
-
}
|
|
977
|
-
else {
|
|
978
|
-
(0, output_js_1.printInfo)(' Cancelled.');
|
|
979
|
-
console.log();
|
|
980
|
-
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
|
+
}
|
|
981
1509
|
}
|
|
982
|
-
}
|
|
1510
|
+
}
|
|
1511
|
+
console.log();
|
|
1512
|
+
void prompt();
|
|
983
1513
|
return;
|
|
984
1514
|
}
|
|
985
1515
|
if (trimmed === '/update') {
|
|
@@ -993,21 +1523,12 @@ async function startRepl(config, alertContext, resume) {
|
|
|
993
1523
|
(0, output_js_1.printInfo)('No routines saved. Teach me a workflow and I can save it as a routine.');
|
|
994
1524
|
}
|
|
995
1525
|
else {
|
|
996
|
-
const
|
|
997
|
-
const
|
|
998
|
-
if (
|
|
999
|
-
(
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
}
|
|
1003
|
-
}
|
|
1004
|
-
if (routines.length > 0) {
|
|
1005
|
-
(0, output_js_1.printInfo)(`\n Routines (${routines.length}) — multi-step workflows:`);
|
|
1006
|
-
for (const r of routines) {
|
|
1007
|
-
(0, output_js_1.printInfo)(` /${r.id} — ${r.name}: ${r.description}`);
|
|
1008
|
-
}
|
|
1009
|
-
}
|
|
1010
|
-
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');
|
|
1011
1532
|
}
|
|
1012
1533
|
void prompt();
|
|
1013
1534
|
return;
|
|
@@ -1054,17 +1575,7 @@ Remember: task content should describe a single atomic operation with clear succ
|
|
|
1054
1575
|
return;
|
|
1055
1576
|
}
|
|
1056
1577
|
if (trimmed === '/specialists') {
|
|
1057
|
-
|
|
1058
|
-
if (specialists.length === 0) {
|
|
1059
|
-
(0, output_js_1.printInfo)('No specialist agents defined yet. Ask me to create one or use /create-specialist.');
|
|
1060
|
-
}
|
|
1061
|
-
else {
|
|
1062
|
-
(0, output_js_1.printInfo)(`\n Specialists (${specialists.length}):`);
|
|
1063
|
-
for (const s of specialists) {
|
|
1064
|
-
(0, output_js_1.printInfo)(` ${s.id} — ${s.name}: ${s.description}`);
|
|
1065
|
-
}
|
|
1066
|
-
console.log();
|
|
1067
|
-
}
|
|
1578
|
+
printSpecialistsList();
|
|
1068
1579
|
void prompt();
|
|
1069
1580
|
return;
|
|
1070
1581
|
}
|
|
@@ -1109,81 +1620,81 @@ Remember: the systemPrompt should read like a persona definition — who this sp
|
|
|
1109
1620
|
(0, output_js_1.printInfo)(' To accept or reject, tell Bernard conversationally (e.g., "accept the code-review candidate").');
|
|
1110
1621
|
(0, output_js_1.printInfo)(' The agent can create the specialist via the specialist tool, then update candidate status.\n');
|
|
1111
1622
|
// Inject candidate context so the agent knows about them for the rest of the session
|
|
1112
|
-
|
|
1113
|
-
agent.setAlertContext(candidateContext);
|
|
1623
|
+
agent.setAlertContext((0, candidate_bootstrap_js_1.buildCandidateContextBlock)(pending));
|
|
1114
1624
|
}
|
|
1115
1625
|
void prompt();
|
|
1116
1626
|
return;
|
|
1117
1627
|
}
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
criticMode: true,
|
|
1130
|
-
});
|
|
1131
|
-
(0, output_js_1.printInfo)('[CRITIC:ON] Responses will be planned and verified.');
|
|
1132
|
-
}
|
|
1133
|
-
else if (arg === 'off') {
|
|
1134
|
-
config.criticMode = false;
|
|
1135
|
-
(0, config_js_1.savePreferences)({
|
|
1136
|
-
provider: config.provider,
|
|
1137
|
-
model: config.model,
|
|
1138
|
-
maxTokens: config.maxTokens,
|
|
1139
|
-
shellTimeout: config.shellTimeout,
|
|
1140
|
-
tokenWindow: config.tokenWindow,
|
|
1141
|
-
theme: config.theme,
|
|
1142
|
-
criticMode: false,
|
|
1143
|
-
});
|
|
1144
|
-
(0, output_js_1.printInfo)('[CRITIC:OFF] Critic mode disabled.');
|
|
1145
|
-
}
|
|
1146
|
-
else {
|
|
1147
|
-
(0, output_js_1.printInfo)(`Critic mode: ${config.criticMode ? 'ON' : 'OFF'}. Usage: /critic on|off`);
|
|
1148
|
-
}
|
|
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}`);
|
|
1149
1639
|
void prompt();
|
|
1150
1640
|
return;
|
|
1151
1641
|
}
|
|
1152
|
-
if (trimmed === '/agent-options'
|
|
1153
|
-
const
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
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
|
-
|
|
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);
|
|
1187
1698
|
config.autoCreateThreshold = normalized;
|
|
1188
1699
|
(0, config_js_1.savePreferences)({
|
|
1189
1700
|
...(0, config_js_1.loadPreferences)(),
|
|
@@ -1191,84 +1702,72 @@ Remember: the systemPrompt should read like a persona definition — who this sp
|
|
|
1191
1702
|
provider: config.provider,
|
|
1192
1703
|
model: config.model,
|
|
1193
1704
|
});
|
|
1194
|
-
(0, output_js_1.printInfo)(`Auto-create threshold: ${normalized} (${Math.round(normalized * 100)}%)`);
|
|
1705
|
+
(0, output_js_1.printInfo)(` Auto-create threshold: ${normalized} (${Math.round(normalized * 100)}%)`);
|
|
1195
1706
|
if (config.autoCreateSpecialists) {
|
|
1196
|
-
promotePendingCandidates(candidateStore, specialistStore, config.autoCreateThreshold);
|
|
1707
|
+
(0, candidate_bootstrap_js_1.promotePendingCandidates)(candidateStore, specialistStore, config.autoCreateThreshold);
|
|
1197
1708
|
}
|
|
1198
1709
|
}
|
|
1710
|
+
finally {
|
|
1711
|
+
clearMenuSignal();
|
|
1712
|
+
}
|
|
1199
1713
|
}
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
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);
|
|
1228
1762
|
}
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
if (s.connected) {
|
|
1232
|
-
console.log(t.muted(` ${s.name}: connected (${s.toolCount} tools)`));
|
|
1233
|
-
}
|
|
1234
|
-
else {
|
|
1235
|
-
console.log(t.muted(` ${s.name}: failed — ${s.error}`));
|
|
1236
|
-
}
|
|
1237
|
-
}
|
|
1763
|
+
finally {
|
|
1764
|
+
clearMenuSignal();
|
|
1238
1765
|
}
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1766
|
+
if (!topResult.cancelled) {
|
|
1767
|
+
const action = itemActions[topResult.index];
|
|
1768
|
+
if (action)
|
|
1769
|
+
await action();
|
|
1243
1770
|
}
|
|
1244
|
-
console.log(t.text('\n Memory:'));
|
|
1245
|
-
console.log(t.muted(` Persistent memories: ${memoryStore.listMemory().length}`));
|
|
1246
|
-
console.log(t.text('\n Cron:'));
|
|
1247
|
-
console.log(t.muted(` Daemon: ${(0, client_js_1.isDaemonRunning)() ? 'running' : 'stopped'}`));
|
|
1248
|
-
let debugJobCount = 0;
|
|
1249
|
-
try {
|
|
1250
|
-
const raw = fs.readFileSync(paths_js_1.CRON_JOBS_FILE, 'utf-8');
|
|
1251
|
-
debugJobCount = JSON.parse(raw).length;
|
|
1252
|
-
}
|
|
1253
|
-
catch {
|
|
1254
|
-
// jobs.json doesn't exist yet — that's fine
|
|
1255
|
-
}
|
|
1256
|
-
console.log(t.muted(` Jobs: ${debugJobCount}`));
|
|
1257
|
-
console.log(t.text('\n Conversation:'));
|
|
1258
|
-
console.log(t.muted(` Messages: ${agent.getHistory().length}`));
|
|
1259
|
-
console.log(t.text('\n Settings:'));
|
|
1260
|
-
console.log(t.muted(` Theme: ${(0, theme_js_1.getActiveThemeKey)()}`));
|
|
1261
|
-
console.log(t.muted(` Critic mode: ${config.criticMode ? 'on' : 'off'}`));
|
|
1262
|
-
const debugEnabled = process.env.BERNARD_DEBUG === 'true' || process.env.BERNARD_DEBUG === '1';
|
|
1263
|
-
console.log(t.muted(` Debug mode: ${debugEnabled ? 'on' : 'off'}`));
|
|
1264
|
-
console.log(t.text('\n Paths:'));
|
|
1265
|
-
if (process.env.BERNARD_HOME) {
|
|
1266
|
-
console.log(t.muted(` BERNARD_HOME: ${process.env.BERNARD_HOME}`));
|
|
1267
|
-
}
|
|
1268
|
-
console.log(t.muted(` Config: ${paths_js_1.CONFIG_DIR}`));
|
|
1269
|
-
console.log(t.muted(` Data: ${paths_js_1.DATA_DIR}`));
|
|
1270
|
-
console.log(t.muted(` Cache: ${paths_js_1.CACHE_DIR}`));
|
|
1271
|
-
console.log(t.muted(` State: ${paths_js_1.STATE_DIR}`));
|
|
1272
1771
|
console.log();
|
|
1273
1772
|
void prompt();
|
|
1274
1773
|
return;
|
|
@@ -1286,6 +1785,70 @@ Remember: the systemPrompt should read like a persona definition — who this sp
|
|
|
1286
1785
|
void prompt();
|
|
1287
1786
|
return;
|
|
1288
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
|
+
}
|
|
1289
1852
|
// Dynamic routine invocation: /{routine-id} [args...]
|
|
1290
1853
|
{
|
|
1291
1854
|
const parts = trimmed.slice(1).split(/\s+/);
|
|
@@ -1310,16 +1873,7 @@ Remember: the systemPrompt should read like a persona definition — who this sp
|
|
|
1310
1873
|
processing = true;
|
|
1311
1874
|
interrupted = false;
|
|
1312
1875
|
try {
|
|
1313
|
-
|
|
1314
|
-
startTime: Date.now(),
|
|
1315
|
-
totalPromptTokens: 0,
|
|
1316
|
-
totalCompletionTokens: 0,
|
|
1317
|
-
latestPromptTokens: 0,
|
|
1318
|
-
model: config.model,
|
|
1319
|
-
contextWindowOverride: config.tokenWindow || undefined,
|
|
1320
|
-
};
|
|
1321
|
-
agent.setSpinnerStats(spinnerStats);
|
|
1322
|
-
(0, output_js_1.startSpinner)(() => (0, output_js_1.buildSpinnerMessage)(spinnerStats));
|
|
1876
|
+
initSpinner();
|
|
1323
1877
|
await agent.processInput(message);
|
|
1324
1878
|
historyStore.save(agent.getHistory());
|
|
1325
1879
|
}
|
|
@@ -1343,20 +1897,35 @@ Remember: the systemPrompt should read like a persona definition — who this sp
|
|
|
1343
1897
|
}
|
|
1344
1898
|
}
|
|
1345
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;
|
|
1346
1924
|
processing = true;
|
|
1347
1925
|
interrupted = false;
|
|
1348
1926
|
try {
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
totalPromptTokens: 0,
|
|
1352
|
-
totalCompletionTokens: 0,
|
|
1353
|
-
latestPromptTokens: 0,
|
|
1354
|
-
model: config.model,
|
|
1355
|
-
contextWindowOverride: config.tokenWindow || undefined,
|
|
1356
|
-
};
|
|
1357
|
-
agent.setSpinnerStats(spinnerStats);
|
|
1358
|
-
(0, output_js_1.startSpinner)(() => (0, output_js_1.buildSpinnerMessage)(spinnerStats));
|
|
1359
|
-
await agent.processInput(trimmed);
|
|
1927
|
+
initSpinner();
|
|
1928
|
+
await agent.processInput(agentInput, inlineImages, resolvedEntries);
|
|
1360
1929
|
historyStore.save(agent.getHistory());
|
|
1361
1930
|
}
|
|
1362
1931
|
catch (err) {
|
|
@@ -1400,4 +1969,102 @@ Remember: the systemPrompt should read like a persona definition — who this sp
|
|
|
1400
1969
|
});
|
|
1401
1970
|
void prompt();
|
|
1402
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
|
+
}
|
|
1403
2070
|
//# sourceMappingURL=repl.js.map
|