bernard-agent 0.5.2 → 0.6.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 +162 -39
- package/dist/agent.d.ts +17 -2
- package/dist/agent.js +160 -7
- package/dist/agent.js.map +1 -1
- package/dist/config.d.ts +10 -2
- package/dist/config.js +36 -11
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +4 -2
- package/dist/context.js +9 -6
- package/dist/context.js.map +1 -1
- package/dist/domains.js +35 -0
- package/dist/domains.js.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/output.d.ts +18 -0
- package/dist/output.js +79 -5
- package/dist/output.js.map +1 -1
- package/dist/paths.d.ts +2 -0
- package/dist/paths.js +3 -1
- package/dist/paths.js.map +1 -1
- package/dist/rag-worker.js +16 -0
- package/dist/rag-worker.js.map +1 -1
- package/dist/repl.js +372 -7
- package/dist/repl.js.map +1 -1
- package/dist/reserved-names.d.ts +5 -0
- package/dist/reserved-names.js +31 -0
- package/dist/reserved-names.js.map +1 -0
- package/dist/routines.js +10 -19
- package/dist/routines.js.map +1 -1
- package/dist/specialist-candidates.d.ts +45 -0
- package/dist/specialist-candidates.js +154 -0
- package/dist/specialist-candidates.js.map +1 -0
- package/dist/specialist-detector.d.ts +12 -0
- package/dist/specialist-detector.js +124 -0
- package/dist/specialist-detector.js.map +1 -0
- package/dist/specialists.d.ts +50 -0
- package/dist/specialists.js +173 -0
- package/dist/specialists.js.map +1 -0
- package/dist/tools/agent-pool.d.ts +20 -0
- package/dist/tools/agent-pool.js +41 -0
- package/dist/tools/agent-pool.js.map +1 -0
- package/dist/tools/index.d.ts +2 -1
- package/dist/tools/index.js +3 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/specialist-run.d.ts +39 -0
- package/dist/tools/specialist-run.js +123 -0
- package/dist/tools/specialist-run.js.map +1 -0
- package/dist/tools/specialist.d.ts +40 -0
- package/dist/tools/specialist.js +107 -0
- package/dist/tools/specialist.js.map +1 -0
- package/dist/tools/subagent.d.ts +1 -1
- package/dist/tools/subagent.js +8 -11
- package/dist/tools/subagent.js.map +1 -1
- package/dist/tools/task.d.ts +45 -0
- package/dist/tools/task.js +155 -0
- package/dist/tools/task.js.map +1 -0
- package/dist/update.d.ts +7 -0
- package/dist/update.js +15 -2
- package/dist/update.js.map +1 -1
- package/package.json +1 -1
package/dist/repl.js
CHANGED
|
@@ -51,9 +51,19 @@ const update_js_1 = require("./update.js");
|
|
|
51
51
|
const store_js_1 = require("./cron/store.js");
|
|
52
52
|
const client_js_1 = require("./cron/client.js");
|
|
53
53
|
const history_js_1 = require("./history.js");
|
|
54
|
+
const ai_1 = require("ai");
|
|
55
|
+
const index_js_1 = require("./providers/index.js");
|
|
54
56
|
const context_js_1 = require("./context.js");
|
|
55
57
|
const domains_js_1 = require("./domains.js");
|
|
56
58
|
const routines_js_1 = require("./routines.js");
|
|
59
|
+
const specialists_js_1 = require("./specialists.js");
|
|
60
|
+
const specialist_candidates_js_1 = require("./specialist-candidates.js");
|
|
61
|
+
const specialist_detector_js_1 = require("./specialist-detector.js");
|
|
62
|
+
const task_js_1 = require("./tools/task.js");
|
|
63
|
+
const index_js_2 = require("./tools/index.js");
|
|
64
|
+
const output_js_2 = require("./output.js");
|
|
65
|
+
const memory_context_js_1 = require("./memory-context.js");
|
|
66
|
+
const logger_js_1 = require("./logger.js");
|
|
57
67
|
/**
|
|
58
68
|
* Launch the interactive REPL, wiring up readline, MCP servers, memory stores, and the agent loop.
|
|
59
69
|
* @param config - Resolved runtime configuration (provider, model, tokens, etc.).
|
|
@@ -63,7 +73,8 @@ const routines_js_1 = require("./routines.js");
|
|
|
63
73
|
async function startRepl(config, alertContext, resume) {
|
|
64
74
|
const SLASH_COMMANDS = [
|
|
65
75
|
{ command: '/help', description: 'Show this help' },
|
|
66
|
-
{ command: '/clear', description: 'Clear conversation
|
|
76
|
+
{ command: '/clear', description: 'Clear conversation (--save/-s to summarize first)' },
|
|
77
|
+
{ command: '/compact', description: 'Compress conversation history in-place' },
|
|
67
78
|
{ command: '/memory', description: 'List persistent memories' },
|
|
68
79
|
{ command: '/scratch', description: 'List session scratch notes' },
|
|
69
80
|
{ command: '/mcp', description: 'List MCP servers and tools' },
|
|
@@ -73,13 +84,23 @@ async function startRepl(config, alertContext, resume) {
|
|
|
73
84
|
{ command: '/provider', description: 'Switch LLM provider' },
|
|
74
85
|
{ command: '/model', description: 'Switch model for current provider' },
|
|
75
86
|
{ command: '/theme', description: 'Switch color theme' },
|
|
76
|
-
{
|
|
87
|
+
{
|
|
88
|
+
command: '/options',
|
|
89
|
+
description: 'View and set options (max-tokens, shell-timeout, token-window)',
|
|
90
|
+
},
|
|
77
91
|
{ command: '/update', description: 'Check for and install updates' },
|
|
92
|
+
{ command: '/task', description: 'Run an isolated task (no history, structured output)' },
|
|
78
93
|
{ command: '/routines', description: 'List saved routines' },
|
|
79
94
|
{ command: '/create-routine', description: 'Create a routine with guided AI assistance' },
|
|
95
|
+
{ command: '/specialists', description: 'List specialist agents' },
|
|
96
|
+
{ command: '/create-specialist', description: 'Create a specialist with guided AI assistance' },
|
|
97
|
+
{ command: '/candidates', description: 'Review specialist suggestions' },
|
|
98
|
+
{ command: '/critic', description: 'Toggle critic mode for response verification' },
|
|
80
99
|
{ command: '/exit', description: 'Quit Bernard' },
|
|
81
100
|
];
|
|
82
101
|
const routineStore = new routines_js_1.RoutineStore();
|
|
102
|
+
const specialistStore = new specialists_js_1.SpecialistStore();
|
|
103
|
+
const candidateStore = new specialist_candidates_js_1.CandidateStore();
|
|
83
104
|
let cachedAllCommands = null;
|
|
84
105
|
let cachedAllCommandsAt = 0;
|
|
85
106
|
const COMMAND_CACHE_TTL = 5_000; // 5 seconds
|
|
@@ -113,7 +134,8 @@ async function startRepl(config, alertContext, resume) {
|
|
|
113
134
|
let isPasting = false;
|
|
114
135
|
function getPromptStr() {
|
|
115
136
|
const { ansi } = (0, theme_js_1.getTheme)();
|
|
116
|
-
|
|
137
|
+
const criticLabel = config.criticMode ? `${ansi.warning}[CRITIC]${ansi.reset} ` : '';
|
|
138
|
+
return `${criticLabel}${ansi.prompt}bernard>${ansi.reset} `;
|
|
117
139
|
}
|
|
118
140
|
if (process.stdin.isTTY) {
|
|
119
141
|
process.stdout.write('\x1b[?2004h'); // enable bracket paste mode
|
|
@@ -149,10 +171,14 @@ async function startRepl(config, alertContext, resume) {
|
|
|
149
171
|
}
|
|
150
172
|
let processing = false;
|
|
151
173
|
let interrupted = false;
|
|
174
|
+
let taskAbortController = null;
|
|
152
175
|
process.stdin.on('keypress', (_str, key) => {
|
|
153
176
|
if (!key)
|
|
154
177
|
return;
|
|
155
178
|
if (key.name === 'escape' && processing) {
|
|
179
|
+
if (taskAbortController) {
|
|
180
|
+
taskAbortController.abort();
|
|
181
|
+
}
|
|
156
182
|
agent.abort();
|
|
157
183
|
interrupted = true;
|
|
158
184
|
return;
|
|
@@ -274,7 +300,15 @@ async function startRepl(config, alertContext, resume) {
|
|
|
274
300
|
(0, output_js_1.printInfo)('No previous conversation found — starting fresh.');
|
|
275
301
|
}
|
|
276
302
|
}
|
|
277
|
-
|
|
303
|
+
// Surface pending specialist candidates at session start
|
|
304
|
+
candidateStore.pruneOld();
|
|
305
|
+
const pendingCandidates = candidateStore.listPending();
|
|
306
|
+
if (pendingCandidates.length > 0) {
|
|
307
|
+
(0, output_js_1.printInfo)(` ${pendingCandidates.length} specialist suggestion(s) pending. Use /candidates to review.`);
|
|
308
|
+
const candidateContext = `## Specialist Suggestions\n\nBernard detected patterns in previous sessions that might benefit from saved specialists. Mention these when relevant.\n\n${pendingCandidates.map((c) => `- "${c.name}" (${c.draftId}): ${c.description}`).join('\n')}`;
|
|
309
|
+
alertContext = alertContext ? alertContext + '\n\n' + candidateContext : candidateContext;
|
|
310
|
+
}
|
|
311
|
+
const agent = new agent_js_1.Agent(config, toolOptions, memoryStore, mcpTools, mcpServerNames, alertContext, initialHistory, ragStore, routineStore, specialistStore);
|
|
278
312
|
let cleanedUp = false;
|
|
279
313
|
const cleanup = async () => {
|
|
280
314
|
if (cleanedUp)
|
|
@@ -341,7 +375,80 @@ async function startRepl(config, alertContext, resume) {
|
|
|
341
375
|
void prompt();
|
|
342
376
|
return;
|
|
343
377
|
}
|
|
344
|
-
if (trimmed === '/clear') {
|
|
378
|
+
if (trimmed === '/clear' || trimmed.startsWith('/clear ')) {
|
|
379
|
+
const clearArgs = trimmed.slice('/clear'.length).trim();
|
|
380
|
+
const shouldSave = clearArgs === '--save' || clearArgs === '-s';
|
|
381
|
+
if (clearArgs && !shouldSave) {
|
|
382
|
+
(0, output_js_1.printError)('Usage: /clear [--save|-s]');
|
|
383
|
+
void prompt();
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
if (shouldSave) {
|
|
387
|
+
const history = agent.getHistory();
|
|
388
|
+
if (history.length < 2) {
|
|
389
|
+
(0, output_js_1.printInfo)('Not enough conversation to summarize.');
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
processing = true;
|
|
393
|
+
(0, output_js_1.startSpinner)('Summarizing conversation...');
|
|
394
|
+
try {
|
|
395
|
+
const serialized = (0, context_js_1.serializeMessages)(history);
|
|
396
|
+
const [summaryResult, domainFacts, candidateResult] = await Promise.all([
|
|
397
|
+
(0, ai_1.generateText)({
|
|
398
|
+
model: (0, index_js_1.getModel)(config.provider, config.model),
|
|
399
|
+
maxTokens: 2048,
|
|
400
|
+
system: context_js_1.SUMMARIZATION_PROMPT,
|
|
401
|
+
messages: [
|
|
402
|
+
{ role: 'user', content: `Summarize this conversation:\n\n${serialized}` },
|
|
403
|
+
],
|
|
404
|
+
}),
|
|
405
|
+
(0, context_js_1.extractDomainFacts)(serialized, config),
|
|
406
|
+
(0, specialist_detector_js_1.detectSpecialistCandidate)(serialized, config, specialistStore.getSummaries(), candidateStore.listPending()).catch(() => null),
|
|
407
|
+
]);
|
|
408
|
+
const summary = summaryResult.text?.trim();
|
|
409
|
+
if (summary) {
|
|
410
|
+
const key = `session-summary-${new Date().toISOString().replace(/[:.]/g, '-')}`;
|
|
411
|
+
memoryStore.writeMemory(key, summary);
|
|
412
|
+
(0, output_js_1.printInfo)(`Summary saved to memory: ${key}`);
|
|
413
|
+
}
|
|
414
|
+
if (ragStore && domainFacts.length > 0) {
|
|
415
|
+
const totalFacts = domainFacts.reduce((sum, df) => sum + df.facts.length, 0);
|
|
416
|
+
const results = await Promise.allSettled(domainFacts.map((df) => ragStore.addFacts(df.facts, 'clear-save', df.domain)));
|
|
417
|
+
let storedFacts = 0;
|
|
418
|
+
results.forEach((result, i) => {
|
|
419
|
+
if (result.status === 'fulfilled') {
|
|
420
|
+
storedFacts += domainFacts[i].facts.length;
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
(0, logger_js_1.debugLog)('repl:clear-save:rag', `Failed to store facts for domain ${domainFacts[i].domain}: ${result.reason instanceof Error ? result.reason.message : String(result.reason)}`);
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
if (storedFacts > 0) {
|
|
427
|
+
(0, output_js_1.printInfo)(storedFacts === totalFacts
|
|
428
|
+
? `Extracted ${storedFacts} facts to RAG memory.`
|
|
429
|
+
: `Extracted ${storedFacts}/${totalFacts} facts to RAG memory.`);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
if (candidateResult) {
|
|
433
|
+
try {
|
|
434
|
+
candidateStore.create(candidateResult, 'clear-save');
|
|
435
|
+
(0, output_js_1.printInfo)(`Specialist suggestion detected: "${candidateResult.name}". Use /candidates to review.`);
|
|
436
|
+
}
|
|
437
|
+
catch {
|
|
438
|
+
// Silent — candidate storage failure is non-critical
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
catch (err) {
|
|
443
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
444
|
+
(0, output_js_1.printError)(`Failed to summarize: ${message}. Clearing anyway.`);
|
|
445
|
+
}
|
|
446
|
+
finally {
|
|
447
|
+
processing = false;
|
|
448
|
+
(0, output_js_1.stopSpinner)();
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
345
452
|
agent.clearHistory();
|
|
346
453
|
historyStore.clear();
|
|
347
454
|
console.clear();
|
|
@@ -350,6 +457,38 @@ async function startRepl(config, alertContext, resume) {
|
|
|
350
457
|
void prompt();
|
|
351
458
|
return;
|
|
352
459
|
}
|
|
460
|
+
if (trimmed === '/compact') {
|
|
461
|
+
const history = agent.getHistory();
|
|
462
|
+
if (history.length < 2) {
|
|
463
|
+
(0, output_js_1.printInfo)('Not enough conversation to compact.');
|
|
464
|
+
void prompt();
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
processing = true;
|
|
468
|
+
(0, output_js_1.startSpinner)('Compacting conversation...');
|
|
469
|
+
try {
|
|
470
|
+
const result = await agent.compactHistory();
|
|
471
|
+
(0, output_js_1.stopSpinner)();
|
|
472
|
+
if (!result.compacted) {
|
|
473
|
+
(0, output_js_1.printInfo)('Nothing to compact — conversation is already short enough.');
|
|
474
|
+
}
|
|
475
|
+
else {
|
|
476
|
+
const pct = Math.round(((result.tokensBefore - result.tokensAfter) / result.tokensBefore) * 100);
|
|
477
|
+
(0, output_js_1.printInfo)(`Compacted: ~${(0, output_js_1.formatTokenCount)(result.tokensBefore)} → ~${(0, output_js_1.formatTokenCount)(result.tokensAfter)} tokens (${pct}% reduction)`);
|
|
478
|
+
}
|
|
479
|
+
historyStore.save(agent.getHistory());
|
|
480
|
+
}
|
|
481
|
+
catch (err) {
|
|
482
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
483
|
+
(0, output_js_1.printError)(`Compaction failed: ${message}`);
|
|
484
|
+
}
|
|
485
|
+
finally {
|
|
486
|
+
processing = false;
|
|
487
|
+
(0, output_js_1.stopSpinner)();
|
|
488
|
+
}
|
|
489
|
+
void prompt();
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
353
492
|
if (trimmed === '/memory') {
|
|
354
493
|
const keys = memoryStore.listMemory();
|
|
355
494
|
if (keys.length === 0) {
|
|
@@ -520,6 +659,7 @@ async function startRepl(config, alertContext, resume) {
|
|
|
520
659
|
model: config.model,
|
|
521
660
|
maxTokens: config.maxTokens,
|
|
522
661
|
shellTimeout: config.shellTimeout,
|
|
662
|
+
tokenWindow: config.tokenWindow,
|
|
523
663
|
theme: config.theme,
|
|
524
664
|
});
|
|
525
665
|
(0, output_js_1.printInfo)(` Switched to ${config.provider} (${config.model})`);
|
|
@@ -554,6 +694,7 @@ async function startRepl(config, alertContext, resume) {
|
|
|
554
694
|
model: config.model,
|
|
555
695
|
maxTokens: config.maxTokens,
|
|
556
696
|
shellTimeout: config.shellTimeout,
|
|
697
|
+
tokenWindow: config.tokenWindow,
|
|
557
698
|
theme: config.theme,
|
|
558
699
|
});
|
|
559
700
|
(0, output_js_1.printInfo)(` Switched to ${config.model}`);
|
|
@@ -598,6 +739,7 @@ async function startRepl(config, alertContext, resume) {
|
|
|
598
739
|
model: config.model,
|
|
599
740
|
maxTokens: config.maxTokens,
|
|
600
741
|
shellTimeout: config.shellTimeout,
|
|
742
|
+
tokenWindow: config.tokenWindow,
|
|
601
743
|
theme: chosen,
|
|
602
744
|
});
|
|
603
745
|
(0, output_js_1.printInfo)(` Switched to ${theme_js_1.THEMES[chosen].name} theme.`);
|
|
@@ -628,16 +770,23 @@ async function startRepl(config, alertContext, resume) {
|
|
|
628
770
|
const [name, opt] = entries[num - 1];
|
|
629
771
|
rl.question(` New value for ${name} (Enter to cancel): `, (valAnswer) => {
|
|
630
772
|
const val = parseInt(valAnswer.trim(), 10);
|
|
631
|
-
|
|
773
|
+
const minVal = opt.default === 0 ? 0 : 1;
|
|
774
|
+
if (!isNaN(val) && val >= minVal) {
|
|
632
775
|
(0, config_js_1.saveOption)(name, val);
|
|
633
776
|
config[opt.configKey] = val;
|
|
634
777
|
(0, output_js_1.printInfo)(` ${name} set to ${val}`);
|
|
778
|
+
if (name === 'token-window') {
|
|
779
|
+
const modelWindow = (0, context_js_1.getContextWindow)(config.model);
|
|
780
|
+
if (val > modelWindow) {
|
|
781
|
+
(0, output_js_1.printInfo)(` Warning: ${val} exceeds ${config.model}'s context window (${modelWindow})`);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
635
784
|
}
|
|
636
785
|
else if (valAnswer.trim() === '') {
|
|
637
786
|
(0, output_js_1.printInfo)(' Cancelled.');
|
|
638
787
|
}
|
|
639
788
|
else {
|
|
640
|
-
(0, output_js_1.printError)(
|
|
789
|
+
(0, output_js_1.printError)(` Invalid value. Must be ${minVal === 0 ? 'a non-negative integer' : 'a positive integer'}.`);
|
|
641
790
|
}
|
|
642
791
|
console.log();
|
|
643
792
|
void prompt();
|
|
@@ -697,6 +846,73 @@ Remember: routine content should be written as clear instructions that Bernard c
|
|
|
697
846
|
totalCompletionTokens: 0,
|
|
698
847
|
latestPromptTokens: 0,
|
|
699
848
|
model: config.model,
|
|
849
|
+
contextWindowOverride: config.tokenWindow || undefined,
|
|
850
|
+
};
|
|
851
|
+
agent.setSpinnerStats(spinnerStats);
|
|
852
|
+
(0, output_js_1.startSpinner)(() => (0, output_js_1.buildSpinnerMessage)(spinnerStats));
|
|
853
|
+
await agent.processInput(message);
|
|
854
|
+
historyStore.save(agent.getHistory());
|
|
855
|
+
}
|
|
856
|
+
catch (err) {
|
|
857
|
+
if (!interrupted) {
|
|
858
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
859
|
+
(0, output_js_1.printError)(message);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
finally {
|
|
863
|
+
processing = false;
|
|
864
|
+
(0, output_js_1.stopSpinner)();
|
|
865
|
+
}
|
|
866
|
+
if (interrupted) {
|
|
867
|
+
(0, output_js_1.printInfo)('Interrupted.');
|
|
868
|
+
interrupted = false;
|
|
869
|
+
}
|
|
870
|
+
console.log();
|
|
871
|
+
void prompt();
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
874
|
+
if (trimmed === '/specialists') {
|
|
875
|
+
const specialists = specialistStore.list();
|
|
876
|
+
if (specialists.length === 0) {
|
|
877
|
+
(0, output_js_1.printInfo)('No specialist agents defined yet. Ask me to create one or use /create-specialist.');
|
|
878
|
+
}
|
|
879
|
+
else {
|
|
880
|
+
(0, output_js_1.printInfo)(`\n Specialists (${specialists.length}):`);
|
|
881
|
+
for (const s of specialists) {
|
|
882
|
+
(0, output_js_1.printInfo)(` ${s.id} — ${s.name}: ${s.description}`);
|
|
883
|
+
}
|
|
884
|
+
console.log();
|
|
885
|
+
}
|
|
886
|
+
void prompt();
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
if (trimmed === '/create-specialist') {
|
|
890
|
+
const message = `The user wants to create a new specialist agent interactively. Guide them through the process:
|
|
891
|
+
|
|
892
|
+
1. Ask what domain or recurring task pattern the specialist covers (e.g., email triage, code review, data analysis)
|
|
893
|
+
2. Ask about behavioral preferences — how should the specialist approach work? What tone, priorities, output formats, or decision rules should it follow?
|
|
894
|
+
3. Ask about specific guidelines — are there things it should always or never do?
|
|
895
|
+
4. Once you have enough information, draft the specialist by creating:
|
|
896
|
+
- **id**: kebab-case slug (e.g., "email-triage")
|
|
897
|
+
- **name**: display name (e.g., "Email Triage Specialist")
|
|
898
|
+
- **description**: one-line summary
|
|
899
|
+
- **systemPrompt**: the specialist's persona and behavioral instructions (this is the core — write it like a focused system prompt)
|
|
900
|
+
- **guidelines**: short behavioral rules as a list of strings
|
|
901
|
+
5. Present the draft to the user for review
|
|
902
|
+
6. Make any requested changes
|
|
903
|
+
7. Use the specialist tool to save it once the user approves
|
|
904
|
+
|
|
905
|
+
Remember: the systemPrompt should read like a persona definition — who this specialist is, what they care about, how they work. Guidelines are individual rules that can be added/removed independently.`;
|
|
906
|
+
processing = true;
|
|
907
|
+
interrupted = false;
|
|
908
|
+
try {
|
|
909
|
+
const spinnerStats = {
|
|
910
|
+
startTime: Date.now(),
|
|
911
|
+
totalPromptTokens: 0,
|
|
912
|
+
totalCompletionTokens: 0,
|
|
913
|
+
latestPromptTokens: 0,
|
|
914
|
+
model: config.model,
|
|
915
|
+
contextWindowOverride: config.tokenWindow || undefined,
|
|
700
916
|
};
|
|
701
917
|
agent.setSpinnerStats(spinnerStats);
|
|
702
918
|
(0, output_js_1.startSpinner)(() => (0, output_js_1.buildSpinnerMessage)(spinnerStats));
|
|
@@ -721,6 +937,153 @@ Remember: routine content should be written as clear instructions that Bernard c
|
|
|
721
937
|
void prompt();
|
|
722
938
|
return;
|
|
723
939
|
}
|
|
940
|
+
if (trimmed === '/candidates') {
|
|
941
|
+
const pending = candidateStore.listPending();
|
|
942
|
+
if (pending.length === 0) {
|
|
943
|
+
(0, output_js_1.printInfo)('No pending specialist suggestions.');
|
|
944
|
+
}
|
|
945
|
+
else {
|
|
946
|
+
const t = (0, theme_js_1.getTheme)();
|
|
947
|
+
(0, output_js_1.printInfo)(`\n Specialist Suggestions (${pending.length}):\n`);
|
|
948
|
+
for (const c of pending) {
|
|
949
|
+
const pct = Math.round(c.confidence * 100);
|
|
950
|
+
const date = new Date(c.detectedAt).toLocaleDateString();
|
|
951
|
+
console.log(t.text(` ${c.name}`) + t.muted(` (${c.draftId})`));
|
|
952
|
+
console.log(t.muted(` ${c.description}`));
|
|
953
|
+
console.log(t.muted(` Confidence: ${pct}% | Detected: ${date}`));
|
|
954
|
+
console.log(t.muted(` Reasoning: ${c.reasoning}`));
|
|
955
|
+
console.log();
|
|
956
|
+
candidateStore.acknowledge(c.id);
|
|
957
|
+
}
|
|
958
|
+
(0, output_js_1.printInfo)(' To accept or reject, tell Bernard conversationally (e.g., "accept the code-review candidate").');
|
|
959
|
+
(0, output_js_1.printInfo)(' The agent can create the specialist via the specialist tool, then update candidate status.\n');
|
|
960
|
+
// Inject candidate context so the agent knows about them for the rest of the session
|
|
961
|
+
const candidateContext = `## Specialist Suggestions\n\nBernard detected patterns in previous sessions that might benefit from saved specialists. Mention these when relevant.\n\n${pending.map((c) => `- "${c.name}" (${c.draftId}): ${c.description}`).join('\n')}`;
|
|
962
|
+
agent.setAlertContext(candidateContext);
|
|
963
|
+
}
|
|
964
|
+
void prompt();
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
if (trimmed === '/critic' || trimmed.startsWith('/critic ')) {
|
|
968
|
+
const arg = trimmed.slice('/critic'.length).trim().toLowerCase();
|
|
969
|
+
if (arg === 'on') {
|
|
970
|
+
config.criticMode = true;
|
|
971
|
+
(0, config_js_1.savePreferences)({
|
|
972
|
+
provider: config.provider,
|
|
973
|
+
model: config.model,
|
|
974
|
+
maxTokens: config.maxTokens,
|
|
975
|
+
shellTimeout: config.shellTimeout,
|
|
976
|
+
tokenWindow: config.tokenWindow,
|
|
977
|
+
theme: config.theme,
|
|
978
|
+
criticMode: true,
|
|
979
|
+
});
|
|
980
|
+
(0, output_js_1.printInfo)('[CRITIC:ON] Responses will be planned and verified.');
|
|
981
|
+
}
|
|
982
|
+
else if (arg === 'off') {
|
|
983
|
+
config.criticMode = false;
|
|
984
|
+
(0, config_js_1.savePreferences)({
|
|
985
|
+
provider: config.provider,
|
|
986
|
+
model: config.model,
|
|
987
|
+
maxTokens: config.maxTokens,
|
|
988
|
+
shellTimeout: config.shellTimeout,
|
|
989
|
+
tokenWindow: config.tokenWindow,
|
|
990
|
+
theme: config.theme,
|
|
991
|
+
criticMode: false,
|
|
992
|
+
});
|
|
993
|
+
(0, output_js_1.printInfo)('[CRITIC:OFF] Critic mode disabled.');
|
|
994
|
+
}
|
|
995
|
+
else {
|
|
996
|
+
(0, output_js_1.printInfo)(`Critic mode: ${config.criticMode ? 'ON' : 'OFF'}. Usage: /critic on|off`);
|
|
997
|
+
}
|
|
998
|
+
void prompt();
|
|
999
|
+
return;
|
|
1000
|
+
}
|
|
1001
|
+
if (trimmed === '/task' || trimmed.startsWith('/task ')) {
|
|
1002
|
+
const taskDescription = trimmed.slice('/task'.length).trim();
|
|
1003
|
+
if (!taskDescription) {
|
|
1004
|
+
(0, output_js_1.printError)('Usage: /task <description>');
|
|
1005
|
+
(0, output_js_1.printInfo)(' Example: /task List all .ts files in the src directory');
|
|
1006
|
+
void prompt();
|
|
1007
|
+
return;
|
|
1008
|
+
}
|
|
1009
|
+
processing = true;
|
|
1010
|
+
interrupted = false;
|
|
1011
|
+
taskAbortController = new AbortController();
|
|
1012
|
+
(0, output_js_2.printTaskStart)(taskDescription);
|
|
1013
|
+
(0, output_js_1.startSpinner)('Running task...');
|
|
1014
|
+
try {
|
|
1015
|
+
const baseTools = (0, index_js_2.createTools)(toolOptions, memoryStore, mcpTools);
|
|
1016
|
+
// Optional RAG search for context
|
|
1017
|
+
let ragResults;
|
|
1018
|
+
if (ragStore) {
|
|
1019
|
+
try {
|
|
1020
|
+
ragResults = await ragStore.search(taskDescription);
|
|
1021
|
+
if (ragResults.length > 0) {
|
|
1022
|
+
(0, logger_js_1.debugLog)('repl:task:rag', {
|
|
1023
|
+
query: taskDescription.slice(0, 100),
|
|
1024
|
+
results: ragResults.length,
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
catch (err) {
|
|
1029
|
+
(0, logger_js_1.debugLog)('repl:task:rag:error', err instanceof Error ? err.message : String(err));
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
const systemPrompt = task_js_1.TASK_SYSTEM_PROMPT +
|
|
1033
|
+
(0, memory_context_js_1.buildMemoryContext)({ memoryStore, ragResults, includeScratch: false });
|
|
1034
|
+
const result = await (0, ai_1.generateText)({
|
|
1035
|
+
model: (0, index_js_1.getModel)(config.provider, config.model),
|
|
1036
|
+
tools: baseTools,
|
|
1037
|
+
maxSteps: 5,
|
|
1038
|
+
maxTokens: config.maxTokens,
|
|
1039
|
+
system: systemPrompt,
|
|
1040
|
+
messages: [{ role: 'user', content: `Task: ${taskDescription}` }],
|
|
1041
|
+
abortSignal: taskAbortController.signal,
|
|
1042
|
+
onStepFinish: ({ text, toolCalls, toolResults }) => {
|
|
1043
|
+
for (const tc of toolCalls) {
|
|
1044
|
+
(0, output_js_2.printToolCall)(tc.toolName, tc.args);
|
|
1045
|
+
}
|
|
1046
|
+
for (const tr of toolResults) {
|
|
1047
|
+
(0, output_js_2.printToolResult)(tr.toolName, tr.result);
|
|
1048
|
+
}
|
|
1049
|
+
if (text) {
|
|
1050
|
+
(0, output_js_2.printAssistantText)(text);
|
|
1051
|
+
}
|
|
1052
|
+
},
|
|
1053
|
+
});
|
|
1054
|
+
(0, output_js_1.stopSpinner)();
|
|
1055
|
+
const taskResult = (0, task_js_1.wrapTaskResult)(result.text);
|
|
1056
|
+
(0, output_js_2.printTaskEnd)(JSON.stringify(taskResult));
|
|
1057
|
+
// Print the full output for the user
|
|
1058
|
+
const t = (0, theme_js_1.getTheme)();
|
|
1059
|
+
if (taskResult.details) {
|
|
1060
|
+
console.log(t.text(`\n${taskResult.output}\n${taskResult.details}`));
|
|
1061
|
+
}
|
|
1062
|
+
else {
|
|
1063
|
+
console.log(t.text(`\n${taskResult.output}`));
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
catch (err) {
|
|
1067
|
+
(0, output_js_1.stopSpinner)();
|
|
1068
|
+
if (!interrupted) {
|
|
1069
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1070
|
+
(0, output_js_2.printTaskEnd)(JSON.stringify({ status: 'error', output: message }));
|
|
1071
|
+
(0, output_js_1.printError)(message);
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
finally {
|
|
1075
|
+
processing = false;
|
|
1076
|
+
taskAbortController = null;
|
|
1077
|
+
(0, output_js_1.stopSpinner)();
|
|
1078
|
+
}
|
|
1079
|
+
if (interrupted) {
|
|
1080
|
+
(0, output_js_1.printInfo)('Interrupted.');
|
|
1081
|
+
interrupted = false;
|
|
1082
|
+
}
|
|
1083
|
+
console.log();
|
|
1084
|
+
void prompt();
|
|
1085
|
+
return;
|
|
1086
|
+
}
|
|
724
1087
|
// Dynamic routine invocation: /{routine-id} [args...]
|
|
725
1088
|
{
|
|
726
1089
|
const parts = trimmed.slice(1).split(/\s+/);
|
|
@@ -743,6 +1106,7 @@ Remember: routine content should be written as clear instructions that Bernard c
|
|
|
743
1106
|
totalCompletionTokens: 0,
|
|
744
1107
|
latestPromptTokens: 0,
|
|
745
1108
|
model: config.model,
|
|
1109
|
+
contextWindowOverride: config.tokenWindow || undefined,
|
|
746
1110
|
};
|
|
747
1111
|
agent.setSpinnerStats(spinnerStats);
|
|
748
1112
|
(0, output_js_1.startSpinner)(() => (0, output_js_1.buildSpinnerMessage)(spinnerStats));
|
|
@@ -778,6 +1142,7 @@ Remember: routine content should be written as clear instructions that Bernard c
|
|
|
778
1142
|
totalCompletionTokens: 0,
|
|
779
1143
|
latestPromptTokens: 0,
|
|
780
1144
|
model: config.model,
|
|
1145
|
+
contextWindowOverride: config.tokenWindow || undefined,
|
|
781
1146
|
};
|
|
782
1147
|
agent.setSpinnerStats(spinnerStats);
|
|
783
1148
|
(0, output_js_1.startSpinner)(() => (0, output_js_1.buildSpinnerMessage)(spinnerStats));
|