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.
Files changed (60) hide show
  1. package/README.md +162 -39
  2. package/dist/agent.d.ts +17 -2
  3. package/dist/agent.js +160 -7
  4. package/dist/agent.js.map +1 -1
  5. package/dist/config.d.ts +10 -2
  6. package/dist/config.js +36 -11
  7. package/dist/config.js.map +1 -1
  8. package/dist/context.d.ts +4 -2
  9. package/dist/context.js +9 -6
  10. package/dist/context.js.map +1 -1
  11. package/dist/domains.js +35 -0
  12. package/dist/domains.js.map +1 -1
  13. package/dist/index.js +1 -0
  14. package/dist/index.js.map +1 -1
  15. package/dist/output.d.ts +18 -0
  16. package/dist/output.js +79 -5
  17. package/dist/output.js.map +1 -1
  18. package/dist/paths.d.ts +2 -0
  19. package/dist/paths.js +3 -1
  20. package/dist/paths.js.map +1 -1
  21. package/dist/rag-worker.js +16 -0
  22. package/dist/rag-worker.js.map +1 -1
  23. package/dist/repl.js +372 -7
  24. package/dist/repl.js.map +1 -1
  25. package/dist/reserved-names.d.ts +5 -0
  26. package/dist/reserved-names.js +31 -0
  27. package/dist/reserved-names.js.map +1 -0
  28. package/dist/routines.js +10 -19
  29. package/dist/routines.js.map +1 -1
  30. package/dist/specialist-candidates.d.ts +45 -0
  31. package/dist/specialist-candidates.js +154 -0
  32. package/dist/specialist-candidates.js.map +1 -0
  33. package/dist/specialist-detector.d.ts +12 -0
  34. package/dist/specialist-detector.js +124 -0
  35. package/dist/specialist-detector.js.map +1 -0
  36. package/dist/specialists.d.ts +50 -0
  37. package/dist/specialists.js +173 -0
  38. package/dist/specialists.js.map +1 -0
  39. package/dist/tools/agent-pool.d.ts +20 -0
  40. package/dist/tools/agent-pool.js +41 -0
  41. package/dist/tools/agent-pool.js.map +1 -0
  42. package/dist/tools/index.d.ts +2 -1
  43. package/dist/tools/index.js +3 -1
  44. package/dist/tools/index.js.map +1 -1
  45. package/dist/tools/specialist-run.d.ts +39 -0
  46. package/dist/tools/specialist-run.js +123 -0
  47. package/dist/tools/specialist-run.js.map +1 -0
  48. package/dist/tools/specialist.d.ts +40 -0
  49. package/dist/tools/specialist.js +107 -0
  50. package/dist/tools/specialist.js.map +1 -0
  51. package/dist/tools/subagent.d.ts +1 -1
  52. package/dist/tools/subagent.js +8 -11
  53. package/dist/tools/subagent.js.map +1 -1
  54. package/dist/tools/task.d.ts +45 -0
  55. package/dist/tools/task.js +155 -0
  56. package/dist/tools/task.js.map +1 -0
  57. package/dist/update.d.ts +7 -0
  58. package/dist/update.js +15 -2
  59. package/dist/update.js.map +1 -1
  60. 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 history and scratch notes' },
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
- { command: '/options', description: 'View and set options (max-tokens, shell-timeout)' },
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
- return `${ansi.prompt}bernard>${ansi.reset} `;
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
- const agent = new agent_js_1.Agent(config, toolOptions, memoryStore, mcpTools, mcpServerNames, alertContext, initialHistory, ragStore, routineStore);
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
- if (val > 0) {
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)(' Invalid value. Must be a positive integer.');
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));