@p-sw/brainbox 0.1.2-alpha.13 → 0.1.2-alpha.2

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 (2) hide show
  1. package/dist/index.js +500 -1030
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4,17 +4,12 @@
4
4
  import { Command } from "commander";
5
5
  import { readFileSync as readFileSync2 } from "fs";
6
6
  import { fileURLToPath as fileURLToPath2 } from "url";
7
- import { dirname as dirname3, join as join6 } from "path";
7
+ import { dirname as dirname4, join as join4 } from "path";
8
8
 
9
9
  // src/utils/logger.ts
10
10
  import chalk from "chalk";
11
- import {
12
- existsSync,
13
- mkdirSync,
14
- createWriteStream,
15
- writeFileSync
16
- } from "fs";
17
- import { join } from "path";
11
+ import { existsSync, mkdirSync, createWriteStream } from "fs";
12
+ import { dirname } from "path";
18
13
  var LEVELS = {
19
14
  debug: { rank: 0, color: chalk.gray, stderr: false },
20
15
  info: { rank: 1, color: chalk.blue, stderr: false },
@@ -31,98 +26,48 @@ var ICONS = {
31
26
  error: "✖",
32
27
  fatal: "▲"
33
28
  };
34
- function pad2(n) {
35
- return n < 10 ? `0${n}` : `${n}`;
36
- }
37
- function dateKey(d = new Date) {
38
- return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}`;
39
- }
40
-
41
- class DailyFileSink {
42
- dir;
43
- date;
44
- stream;
45
- setDir(dir) {
46
- if (dir === this.dir)
47
- return;
48
- this.stream?.end();
49
- this.stream = undefined;
50
- this.date = undefined;
51
- this.dir = dir;
52
- if (dir && !existsSync(dir))
53
- mkdirSync(dir, { recursive: true });
54
- }
55
- write(data) {
56
- if (!this.dir)
57
- return;
58
- const today = dateKey();
59
- if (!this.stream || this.date !== today) {
60
- this.stream?.end();
61
- this.date = today;
62
- if (!existsSync(this.dir))
63
- mkdirSync(this.dir, { recursive: true });
64
- this.stream = createWriteStream(join(this.dir, `${today}.log`), {
65
- flags: "a"
66
- });
67
- }
68
- this.stream.write(data);
69
- }
70
- close() {
71
- this.stream?.end();
72
- this.stream = undefined;
73
- this.date = undefined;
74
- this.dir = undefined;
75
- }
76
- get enabled() {
77
- return this.dir !== undefined;
78
- }
79
- }
80
- var shared = {
81
- level: "info",
82
- timestamps: true,
83
- colors: chalk.level > 0,
84
- json: false,
85
- silent: false
86
- };
87
- var mainSink = new DailyFileSink;
88
- var llmLogDir;
89
- function applyShared(options) {
90
- if (options.level !== undefined)
91
- shared.level = options.level;
92
- if (options.timestamps !== undefined)
93
- shared.timestamps = options.timestamps;
94
- if (options.colors !== undefined)
95
- shared.colors = options.colors;
96
- if (options.silent !== undefined)
97
- shared.silent = options.silent;
98
- if (options.json !== undefined)
99
- shared.json = options.json;
100
- if ("logDir" in options)
101
- mainSink.setDir(options.logDir);
102
- }
103
29
 
104
30
  class Logger {
31
+ level;
32
+ timestamps;
33
+ colors;
105
34
  tag;
35
+ file;
36
+ json;
37
+ silent;
38
+ fileStream;
106
39
  constructor(options = {}) {
40
+ this.level = options.level ?? "info";
41
+ this.timestamps = options.timestamps ?? true;
42
+ this.colors = options.colors ?? chalk.level > 0;
107
43
  this.tag = options.tag;
108
- applyShared(options);
44
+ this.file = options.file;
45
+ this.json = options.json ?? false;
46
+ this.silent = options.silent ?? false;
47
+ if (this.file) {
48
+ const dir = dirname(this.file);
49
+ if (!existsSync(dir))
50
+ mkdirSync(dir, { recursive: true });
51
+ this.fileStream = createWriteStream(this.file, { flags: "a" });
52
+ }
109
53
  }
110
54
  shouldLog(level) {
111
- return LEVELS[level].rank >= LEVELS[shared.level].rank;
55
+ return LEVELS[level].rank >= LEVELS[this.level].rank;
112
56
  }
113
57
  formatTimestamp() {
114
58
  const now = new Date;
115
- return `${now.getFullYear()}-${pad2(now.getMonth() + 1)}-${pad2(now.getDate())} ${pad2(now.getHours())}:${pad2(now.getMinutes())}:${pad2(now.getSeconds())}`;
59
+ const pad = (n) => n.toString().padStart(2, "0");
60
+ return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;
116
61
  }
117
62
  format(level, message) {
118
- const ts = shared.timestamps ? `[${this.formatTimestamp()}]` : "";
63
+ const ts = this.timestamps ? `[${this.formatTimestamp()}]` : "";
119
64
  const tag = this.tag ? `[${this.tag}]` : "";
120
65
  const icon = ICONS[level];
121
66
  const levelStr = level.toUpperCase();
122
67
  const consoleParts = [
123
68
  ts,
124
69
  tag,
125
- shared.colors ? LEVELS[level].color(icon) : icon,
70
+ this.colors ? LEVELS[level].color(icon) : icon,
126
71
  message
127
72
  ].filter(Boolean);
128
73
  const consoleLine = consoleParts.join(" ");
@@ -145,13 +90,13 @@ class Logger {
145
90
  return;
146
91
  const { console: consoleLine, file: fileLine } = this.format(level, message);
147
92
  const jsonLine = this.formatJson(level, message);
148
- if (!shared.silent) {
93
+ if (!this.silent) {
149
94
  const out = LEVELS[level].stderr ? process.stderr : process.stdout;
150
95
  out.write(consoleLine + `
151
96
  `);
152
97
  }
153
- if (mainSink.enabled) {
154
- mainSink.write(shared.json ? jsonLine : fileLine);
98
+ if (this.fileStream) {
99
+ this.fileStream.write(this.json ? jsonLine : fileLine);
155
100
  }
156
101
  }
157
102
  debug(message) {
@@ -174,56 +119,57 @@ class Logger {
174
119
  }
175
120
  child(tag) {
176
121
  const combined = this.tag ? `${this.tag}:${tag}` : tag;
177
- return new Logger({ tag: combined });
122
+ return new Logger({
123
+ level: this.level,
124
+ timestamps: this.timestamps,
125
+ colors: this.colors,
126
+ tag: combined,
127
+ file: this.file,
128
+ json: this.json,
129
+ silent: this.silent
130
+ });
178
131
  }
179
132
  configure(options) {
133
+ if (options.level !== undefined)
134
+ this.level = options.level;
135
+ if (options.timestamps !== undefined)
136
+ this.timestamps = options.timestamps;
137
+ if (options.colors !== undefined)
138
+ this.colors = options.colors;
180
139
  if (options.tag !== undefined)
181
140
  this.tag = options.tag;
182
- applyShared(options);
141
+ if (options.silent !== undefined)
142
+ this.silent = options.silent;
143
+ if (options.json !== undefined)
144
+ this.json = options.json;
145
+ if (options.file !== undefined && options.file !== this.file) {
146
+ this.fileStream?.end();
147
+ this.file = options.file;
148
+ if (this.file) {
149
+ const dir = dirname(this.file);
150
+ if (!existsSync(dir))
151
+ mkdirSync(dir, { recursive: true });
152
+ this.fileStream = createWriteStream(this.file, { flags: "a" });
153
+ } else {
154
+ this.fileStream = undefined;
155
+ }
156
+ }
183
157
  }
184
158
  close() {
185
- mainSink.close();
186
- llmLogDir = undefined;
159
+ this.fileStream?.end();
187
160
  }
188
161
  }
189
162
  var logger = new Logger;
190
- function configureLlmLog(logDir) {
191
- llmLogDir = logDir;
192
- if (logDir && !existsSync(logDir))
193
- mkdirSync(logDir, { recursive: true });
194
- }
195
- function isLlmLogEnabled() {
196
- return llmLogDir !== undefined;
197
- }
198
- function sanitizeCaller(caller) {
199
- const s = caller.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
200
- return s.length > 0 ? s : "unknown";
201
- }
202
- function dateTimeKey(d = new Date) {
203
- return `${dateKey(d)}-${pad2(d.getHours())}-${pad2(d.getMinutes())}-${pad2(d.getSeconds())}`;
204
- }
205
- function writeLlmExchange(caller, content) {
206
- if (!llmLogDir)
207
- return;
208
- if (!existsSync(llmLogDir))
209
- mkdirSync(llmLogDir, { recursive: true });
210
- const base = `${dateTimeKey()}-${sanitizeCaller(caller)}`;
211
- let path = join(llmLogDir, `${base}.log`);
212
- for (let n = 2;existsSync(path); n += 1) {
213
- path = join(llmLogDir, `${base}-${n}.log`);
214
- }
215
- writeFileSync(path, content, "utf8");
216
- }
217
163
 
218
164
  // src/config/loader.ts
219
- import { dirname, join as join2, resolve } from "path";
165
+ import { dirname as dirname2, join, resolve } from "path";
220
166
  import { z, ZodError } from "zod";
221
167
  import { homedir } from "os";
222
168
  import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
223
- import { mkdirSync as mkdirSync2, readFileSync, writeFileSync as writeFileSync2 } from "fs";
224
- var brainboxRoot = process.env["BRAINBOX_ROOT_PATH"] ? resolve(process.cwd(), process.env["BRAINBOX_ROOT_PATH"]) : join2(homedir(), ".brainbox");
169
+ import { mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "fs";
170
+ var brainboxRoot = process.env["BRAINBOX_ROOT_PATH"] ? resolve(process.cwd(), process.env["BRAINBOX_ROOT_PATH"]) : join(homedir(), ".brainbox");
225
171
  function configFile(file, options) {
226
- const path = () => join2(brainboxRoot, file);
172
+ const path = () => join(brainboxRoot, file);
227
173
  const read = () => {
228
174
  const p = path();
229
175
  let raw;
@@ -235,8 +181,8 @@ function configFile(file, options) {
235
181
  const defaults = defaultsFromSchema(options.schema);
236
182
  const templateStr = (options.header ? options.header + `
237
183
  ` : "") + (defaults === undefined ? "" : stringifyYaml(defaults));
238
- mkdirSync2(dirname(p), { recursive: true });
239
- writeFileSync2(p, templateStr);
184
+ mkdirSync2(dirname2(p), { recursive: true });
185
+ writeFileSync(p, templateStr);
240
186
  raw = defaults ?? {};
241
187
  }
242
188
  try {
@@ -253,8 +199,8 @@ function configFile(file, options) {
253
199
  };
254
200
  const write = (value) => {
255
201
  const p = path();
256
- mkdirSync2(dirname(p), { recursive: true });
257
- writeFileSync2(p, stringifyYaml(value));
202
+ mkdirSync2(dirname2(p), { recursive: true });
203
+ writeFileSync(p, stringifyYaml(value));
258
204
  };
259
205
  const update = (fn) => {
260
206
  const next = fn(read());
@@ -349,7 +295,7 @@ function removeProviderAuth(provider) {
349
295
  // src/config/index.ts
350
296
  var config = {
351
297
  get debug() {
352
- return process.env.DEBUG_MODE ? process.env.DEBUG_MODE.toLowerCase() === "true" : readRootFile().debug;
298
+ return readRootFile().debug;
353
299
  },
354
300
  brainboxRoot,
355
301
  get supermemoryApiKey() {
@@ -375,7 +321,7 @@ function registerCommand(program, config2) {
375
321
 
376
322
  // src/brain/manager.ts
377
323
  import { mkdir, readFile, writeFile } from "fs/promises";
378
- import { join as join3 } from "path";
324
+ import { join as join2 } from "path";
379
325
  var log = logger.child("brain-manager");
380
326
 
381
327
  class BrainDBManager {
@@ -384,7 +330,7 @@ class BrainDBManager {
384
330
  this.root = root;
385
331
  }
386
332
  dbFile() {
387
- return join3(this.root, "brains.json");
333
+ return join2(this.root, "brains.json");
388
334
  }
389
335
  async readDB() {
390
336
  try {
@@ -484,106 +430,6 @@ function defaultReasoningEffort(effort, model, identityModel) {
484
430
  return effort;
485
431
  return model === identityModel ? "medium" : "none";
486
432
  }
487
- function stripThinkTags(content) {
488
- return content.replace(/<think\b[^>]*>[\s\S]*?<\/think>/gi, "").trim();
489
- }
490
- function parseModelJson(content) {
491
- const cleaned = stripThinkTags(content);
492
- const candidates = [cleaned];
493
- const fence = cleaned.match(/```(?:json)?\s*([\s\S]*?)```/i);
494
- if (fence?.[1])
495
- candidates.push(fence[1].trim());
496
- const extracted = extractJsonSlice(cleaned);
497
- if (extracted)
498
- candidates.push(extracted);
499
- let lastErr;
500
- for (const candidate of candidates) {
501
- try {
502
- return decodeJsonValue(candidate);
503
- } catch (err) {
504
- lastErr = err;
505
- }
506
- }
507
- throw lastErr instanceof Error ? lastErr : new Error("Failed to parse model JSON");
508
- }
509
- function decodeJsonValue(text) {
510
- let value = JSON.parse(text);
511
- for (let i = 0;i < 2 && typeof value === "string"; i++) {
512
- const inner = value.trim();
513
- if (!(inner.startsWith("{") && inner.endsWith("}") || inner.startsWith("[") && inner.endsWith("]") || inner.startsWith('"') && inner.endsWith('"'))) {
514
- break;
515
- }
516
- value = JSON.parse(inner);
517
- }
518
- return value;
519
- }
520
- function extractJsonSlice(text) {
521
- const start = text.search(/[\{\[]/);
522
- if (start < 0)
523
- return;
524
- const open = text[start];
525
- const close = open === "{" ? "}" : "]";
526
- let depth = 0;
527
- let inString = false;
528
- let escape = false;
529
- for (let i = start;i < text.length; i++) {
530
- const ch = text[i];
531
- if (inString) {
532
- if (escape) {
533
- escape = false;
534
- } else if (ch === "\\") {
535
- escape = true;
536
- } else if (ch === '"') {
537
- inString = false;
538
- }
539
- continue;
540
- }
541
- if (ch === '"') {
542
- inString = true;
543
- continue;
544
- }
545
- if (ch === open)
546
- depth++;
547
- else if (ch === close) {
548
- depth--;
549
- if (depth === 0)
550
- return text.slice(start, i + 1);
551
- }
552
- }
553
- return;
554
- }
555
- function schemaToolName(name) {
556
- const cleaned = name.replace(/[^a-zA-Z0-9_]/g, "_").replace(/^(\d)/, "_$1");
557
- return cleaned.length > 0 ? cleaned : "submit_result";
558
- }
559
- function buildStructuredJsonRequest(options) {
560
- const toolName = schemaToolName(options.jsonSchemaName);
561
- return {
562
- toolName,
563
- tool: {
564
- name: toolName,
565
- description: `Submit the structured ${options.jsonSchemaName} result.`,
566
- parameters: options.jsonSchema ?? {
567
- type: "object",
568
- additionalProperties: true
569
- }
570
- },
571
- instruction: `${options.instruction}
572
-
573
- You MUST call the \`${toolName}\` tool exactly once with the complete answer.
574
- Do not write the JSON as plain text or inside a markdown code fence.`
575
- };
576
- }
577
- function parseStructuredJsonResult(choice, toolName) {
578
- const call = choice.message.toolCalls?.find((c) => c.function.name === toolName) ?? choice.message.toolCalls?.[0];
579
- if (call?.function.arguments) {
580
- return parseModelJson(call.function.arguments);
581
- }
582
- if (choice.message.content) {
583
- return parseModelJson(choice.message.content);
584
- }
585
- throw new Error("Empty response from model");
586
- }
587
433
  function readAuthString(auth, key, envName) {
588
434
  const fromAuth = typeof auth?.[key] === "string" ? auth[key] : undefined;
589
435
  if (fromAuth)
@@ -597,42 +443,6 @@ function readAuthString(auth, key, envName) {
597
443
  }
598
444
 
599
445
  class LLMExecutor {
600
- async chatWithToolExecution(model, options) {
601
- const messages = options.messages.slice();
602
- const maxSteps = options.maxSteps ?? 20;
603
- let last;
604
- for (let step = 0;step < maxSteps; step += 1) {
605
- log2.debug(`chatWithToolExecution: step ${step + 1}/${maxSteps} msgs=${messages.length}`);
606
- last = await this.chatWithTools(model, {
607
- instruction: options.instruction,
608
- messages,
609
- tools: options.tools,
610
- caller: options.caller,
611
- reasoningEffort: options.reasoningEffort,
612
- parallelToolCalls: options.parallelToolCalls,
613
- toolChoice: options.toolChoice
614
- });
615
- const toolCalls = last.message.toolCalls ?? [];
616
- log2.debug(`chatWithToolExecution: step ${step + 1} toolCalls=${toolCalls.length}`);
617
- if (toolCalls.length === 0)
618
- return last;
619
- messages.push({
620
- role: "assistant",
621
- content: last.message.content,
622
- toolCalls
623
- });
624
- for (const call of toolCalls) {
625
- const content = await options.executeTool(call);
626
- messages.push({
627
- role: "tool",
628
- toolCallId: call.id,
629
- content
630
- });
631
- }
632
- }
633
- log2.warn(`chatWithToolExecution: reached maxSteps (${maxSteps}) without final reply`);
634
- return last;
635
- }
636
446
  static providers = [];
637
447
  static registerProvider(p) {
638
448
  LLMExecutor.providers.push(p);
@@ -699,19 +509,6 @@ class LLMExecutor {
699
509
  };
700
510
  }
701
511
  }
702
- function resolveLlmCaller(options, fallback = "llm") {
703
- return options.caller ?? options.jsonSchemaName ?? fallback;
704
- }
705
- function logLlmWire(caller, request, response) {
706
- if (!isLlmLogEnabled())
707
- return;
708
- writeLlmExchange(caller, `======== REQUEST ========
709
- ${request}
710
-
711
- ======== RESPONSE ========
712
- ${response}
713
- `);
714
- }
715
512
  function listProviderNames() {
716
513
  return LLMExecutor.listProviderNames();
717
514
  }
@@ -749,7 +546,7 @@ function fromOrChoice(choice) {
749
546
  }));
750
547
  return {
751
548
  message: {
752
- content: typeof msg.content === "string" ? stripThinkTags(msg.content) : undefined,
549
+ content: typeof msg.content === "string" ? msg.content : undefined,
753
550
  toolCalls
754
551
  }
755
552
  };
@@ -780,54 +577,53 @@ class OpenRouterExecutor extends LLMExecutor {
780
577
  async call(model, options) {
781
578
  const jsonMode = "jsonSchemaName" in options;
782
579
  log3.debug(`call: model=${model} jsonSchema=${jsonMode ? options.jsonSchemaName : "-"} msgLen=${options.message.length}`);
783
- const chatRequest = {
784
- model,
785
- messages: [
786
- { role: "system", content: options.instruction },
787
- { role: "user", content: options.message }
788
- ],
789
- reasoning: {
790
- effort: options.reasoningEffort ?? (model === this.models.identity ? REASONING_EFFORT_MAP.medium : REASONING_EFFORT_MAP.none)
791
- },
792
- responseFormat: jsonMode ? {
793
- type: "json_schema",
794
- jsonSchema: {
795
- name: options.jsonSchemaName,
796
- schema: options.jsonSchema,
797
- strict: true
798
- }
799
- } : { type: "text" },
800
- stream: false
801
- };
802
- const result = await this.client.chat.send({ chatRequest });
803
- logLlmWire(resolveLlmCaller(options), JSON.stringify(chatRequest), JSON.stringify(result));
804
- const raw = result.choices[0]?.message?.content;
805
- const content = typeof raw === "string" ? stripThinkTags(raw) : raw;
580
+ const result = await this.client.chat.send({
581
+ chatRequest: {
582
+ model,
583
+ messages: [
584
+ { role: "system", content: options.instruction },
585
+ { role: "user", content: options.message }
586
+ ],
587
+ reasoning: {
588
+ effort: options.reasoningEffort ?? (model === this.models.identity ? REASONING_EFFORT_MAP.medium : REASONING_EFFORT_MAP.none)
589
+ },
590
+ responseFormat: jsonMode ? {
591
+ type: "json_schema",
592
+ jsonSchema: {
593
+ name: options.jsonSchemaName,
594
+ schema: options.jsonSchema,
595
+ strict: true
596
+ }
597
+ } : { type: "text" },
598
+ stream: false
599
+ }
600
+ });
601
+ const content = result.choices[0]?.message?.content;
806
602
  if (!content) {
807
603
  log3.debug(`call: empty content in choice 0`);
808
604
  throw new Error("Empty response from model");
809
605
  }
810
606
  log3.debug(`call: response ${content.length} chars`);
811
- return jsonMode ? parseModelJson(content) : content;
607
+ return jsonMode ? JSON.parse(content) : content;
812
608
  }
813
609
  async chatWithTools(model, options) {
814
610
  log3.debug(`chatWithTools: model=${model} msgs=${options.messages.length} tools=${options.tools.length}`);
815
- const chatRequest = {
816
- model,
817
- messages: [
818
- { role: "system", content: options.instruction },
819
- ...options.messages.map(toOrMessage)
820
- ],
821
- reasoning: {
822
- effort: options.reasoningEffort ?? (model === this.models.identity ? REASONING_EFFORT_MAP.medium : REASONING_EFFORT_MAP.none)
823
- },
824
- responseFormat: { type: "text" },
825
- tools: options.tools.map(toOrTool),
826
- parallelToolCalls: options.parallelToolCalls ?? false,
827
- stream: false
828
- };
829
- const result = await this.client.chat.send({ chatRequest });
830
- logLlmWire(resolveLlmCaller(options), JSON.stringify(chatRequest), JSON.stringify(result));
611
+ const result = await this.client.chat.send({
612
+ chatRequest: {
613
+ model,
614
+ messages: [
615
+ { role: "system", content: options.instruction },
616
+ ...options.messages.map(toOrMessage)
617
+ ],
618
+ reasoning: {
619
+ effort: options.reasoningEffort ?? (model === this.models.identity ? REASONING_EFFORT_MAP.medium : REASONING_EFFORT_MAP.none)
620
+ },
621
+ responseFormat: { type: "text" },
622
+ tools: options.tools.map(toOrTool),
623
+ parallelToolCalls: options.parallelToolCalls ?? false,
624
+ stream: false
625
+ }
626
+ });
831
627
  const choice = result.choices[0];
832
628
  if (!choice) {
833
629
  log3.debug(`chatWithTools: no choice in response`);
@@ -867,7 +663,7 @@ function fromChoice(choice) {
867
663
  }));
868
664
  return {
869
665
  message: {
870
- content: typeof msg.content === "string" ? stripThinkTags(msg.content) : undefined,
666
+ content: typeof msg.content === "string" ? msg.content : undefined,
871
667
  toolCalls
872
668
  }
873
669
  };
@@ -902,7 +698,6 @@ class OpenAICompatibleExecutor extends LLMExecutor {
902
698
  chatPath;
903
699
  noBearerPrefix;
904
700
  reasoningEffortInQuery;
905
- supportsResponseFormat;
906
701
  constructor(opts) {
907
702
  super();
908
703
  this.providerName = opts.providerName;
@@ -912,7 +707,6 @@ class OpenAICompatibleExecutor extends LLMExecutor {
912
707
  this.chatPath = opts.chatPath ?? "/chat/completions";
913
708
  this.noBearerPrefix = opts.noBearerPrefix ?? false;
914
709
  this.reasoningEffortInQuery = opts.reasoningEffortInQuery ?? false;
915
- this.supportsResponseFormat = opts.supportsResponseFormat ?? true;
916
710
  this.models = {
917
711
  conversation: opts.conversationModel,
918
712
  identity: opts.identityModel
@@ -943,7 +737,7 @@ class OpenAICompatibleExecutor extends LLMExecutor {
943
737
  }
944
738
  return url.toString();
945
739
  }
946
- async sendRequest(body, reasoningEffort, caller) {
740
+ async sendRequest(body, reasoningEffort) {
947
741
  const modelName = body["model"];
948
742
  const modelStr = typeof modelName === "string" ? modelName : "";
949
743
  const url = this.buildRequestUrl(modelStr, reasoningEffort);
@@ -953,40 +747,24 @@ class OpenAICompatibleExecutor extends LLMExecutor {
953
747
  ...authHeader,
954
748
  ...this.defaultHeaders
955
749
  };
956
- const requestRaw = JSON.stringify(body);
957
750
  const res = await fetch(url, {
958
751
  method: "POST",
959
752
  headers,
960
- body: requestRaw
753
+ body: JSON.stringify(body)
961
754
  });
962
- const responseRaw = await res.text().catch(() => "");
963
- logLlmWire(caller, requestRaw, responseRaw);
964
755
  if (!res.ok) {
965
- log4.error(`${this.providerName}: HTTP ${res.status} ${res.statusText} body=${responseRaw.slice(0, 500)}`);
756
+ const text = await res.text().catch(() => "");
757
+ log4.error(`${this.providerName}: HTTP ${res.status} ${res.statusText} body=${text.slice(0, 500)}`);
966
758
  throw new Error(`${this.providerName} request failed: ${res.status} ${res.statusText}`);
967
759
  }
968
- let data;
969
- try {
970
- data = JSON.parse(responseRaw);
971
- } catch {
972
- throw new Error(`${this.providerName}: invalid JSON response`);
973
- }
760
+ const data = await res.json();
974
761
  if (data.error) {
975
762
  log4.error(`${this.providerName}: API error ${data.error.type ?? ""} ${data.error.message ?? ""}`);
976
763
  throw new Error(`${this.providerName} API error: ${data.error.message ?? "unknown"}`);
977
764
  }
978
- const baseCode = data.base_resp?.status_code;
979
- if (typeof baseCode === "number" && baseCode !== 0) {
980
- const msg = data.base_resp?.status_msg?.trim() || `status_code ${baseCode}`;
981
- log4.error(`${this.providerName}: base_resp ${baseCode} ${msg}`);
982
- throw new Error(`${this.providerName} API error: ${msg}`);
983
- }
984
765
  return data;
985
766
  }
986
767
  async call(model, options) {
987
- if ("jsonSchemaName" in options && !this.supportsResponseFormat) {
988
- return this.callJsonViaTool(model, options);
989
- }
990
768
  const jsonMode = "jsonSchemaName" in options;
991
769
  const reasoning = defaultReasoningEffort(options.reasoningEffort, model, this.models.identity);
992
770
  log4.debug(`call: provider=${this.providerName} model=${model} jsonSchema=${jsonMode ? options.jsonSchemaName : "-"} msgLen=${options.message.length}`);
@@ -999,31 +777,14 @@ class OpenAICompatibleExecutor extends LLMExecutor {
999
777
  responseFormat: jsonMode ? buildResponseFormat(options.jsonSchemaName, options.jsonSchema) : undefined,
1000
778
  reasoningEffort: reasoning
1001
779
  });
1002
- const data = await this.sendRequest(body, options.reasoningEffort, resolveLlmCaller(options));
1003
- const choice = data.choices?.[0];
1004
- const raw = choice?.message?.content;
1005
- const content = typeof raw === "string" ? stripThinkTags(raw) : raw;
780
+ const data = await this.sendRequest(body, options.reasoningEffort);
781
+ const content = data.choices?.[0]?.message?.content;
1006
782
  if (!content) {
1007
- const finish = choice?.finish_reason ?? "no-choice";
1008
- const reasoningLen = typeof choice?.message?.reasoning_content === "string" ? choice.message.reasoning_content.length : 0;
1009
- log4.debug(`call: empty content in choice 0 finish_reason=${finish} reasoning_len=${reasoningLen} rawType=${raw === null ? "null" : typeof raw}`);
1010
- throw new Error(reasoningLen > 0 ? `Empty response from model (finish_reason=${finish}; reasoning present but no content)` : "Empty response from model");
783
+ log4.debug(`call: empty content in choice 0`);
784
+ throw new Error("Empty response from model");
1011
785
  }
1012
786
  log4.debug(`call: response ${content.length} chars`);
1013
- return jsonMode ? parseModelJson(content) : content;
1014
- }
1015
- async callJsonViaTool(model, options) {
1016
- const { toolName, tool, instruction } = buildStructuredJsonRequest(options);
1017
- log4.debug(`callJsonViaTool: provider=${this.providerName} model=${model} tool=${toolName}`);
1018
- const choice = await this.chatWithTools(model, {
1019
- caller: options.caller ?? options.jsonSchemaName,
1020
- instruction,
1021
- messages: [{ role: "user", content: options.message }],
1022
- tools: [tool],
1023
- reasoningEffort: "none",
1024
- parallelToolCalls: false
1025
- });
1026
- return parseStructuredJsonResult(choice, toolName);
787
+ return jsonMode ? JSON.parse(content) : content;
1027
788
  }
1028
789
  async chatWithTools(model, options) {
1029
790
  const reasoning = defaultReasoningEffort(options.reasoningEffort, model, this.models.identity);
@@ -1038,7 +799,7 @@ class OpenAICompatibleExecutor extends LLMExecutor {
1038
799
  parallelToolCalls: options.parallelToolCalls ?? false,
1039
800
  reasoningEffort: reasoning
1040
801
  });
1041
- const data = await this.sendRequest(body, options.reasoningEffort, resolveLlmCaller(options));
802
+ const data = await this.sendRequest(body, options.reasoningEffort);
1042
803
  const choice = data.choices?.[0];
1043
804
  if (!choice) {
1044
805
  log4.debug(`chatWithTools: no choice in response`);
@@ -1337,41 +1098,16 @@ class ZenMuxExecutor extends OpenAICompatibleExecutor {
1337
1098
  }
1338
1099
  }
1339
1100
 
1340
- // src/provider/providers/minimax.ts
1341
- class MiniMaxBase extends OpenAICompatibleExecutor {
1342
- buildBody(opts) {
1343
- const jsonMode = opts.responseFormat !== undefined;
1344
- const body = super.buildBody(opts);
1345
- delete body["reasoning_effort"];
1346
- delete body["response_format"];
1347
- delete body["parallel_tool_calls"];
1348
- body["reasoning_split"] = true;
1349
- body["max_completion_tokens"] = 16384;
1350
- const wantThink = !jsonMode && opts.reasoningEffort !== undefined && opts.reasoningEffort !== "none";
1351
- body["thinking"] = wantThink ? { type: "adaptive" } : { type: "disabled" };
1352
- return body;
1353
- }
1354
- }
1355
- function minimaxOpts(providerName, baseURL, opts) {
1356
- return {
1357
- providerName,
1358
- baseURL,
1359
- apiKey: opts.apiKey,
1360
- conversationModel: opts.conversationModel,
1361
- identityModel: opts.identityModel,
1362
- supportsResponseFormat: false
1363
- };
1364
- }
1365
-
1366
- class MiniMaxExecutor extends MiniMaxBase {
1367
- constructor(opts) {
1368
- super(minimaxOpts("minimax", "https://api.minimax.io/v1", opts));
1369
- }
1370
- }
1371
-
1372
- class MiniMaxCnExecutor extends MiniMaxBase {
1101
+ // src/provider/providers/MiniMax.ts
1102
+ class MiniMaxExecutor extends OpenAICompatibleExecutor {
1373
1103
  constructor(opts) {
1374
- super(minimaxOpts("minimax-cn", "https://api.minimaxi.com/v1", opts));
1104
+ super({
1105
+ providerName: "MiniMax",
1106
+ baseURL: "https://api.MiniMax.chat/v1",
1107
+ apiKey: opts.apiKey,
1108
+ conversationModel: opts.conversationModel,
1109
+ identityModel: opts.identityModel
1110
+ });
1375
1111
  }
1376
1112
  }
1377
1113
 
@@ -1435,8 +1171,7 @@ class LmStudioExecutor extends OpenAICompatibleExecutor {
1435
1171
  baseURL: "http://127.0.0.1:1234/v1",
1436
1172
  apiKey: opts.apiKey || "lm-studio",
1437
1173
  conversationModel: opts.conversationModel,
1438
- identityModel: opts.identityModel,
1439
- supportsResponseFormat: false
1174
+ identityModel: opts.identityModel
1440
1175
  });
1441
1176
  }
1442
1177
  }
@@ -1449,8 +1184,7 @@ class OllamaExecutor extends OpenAICompatibleExecutor {
1449
1184
  baseURL: "http://127.0.0.1:11434/v1",
1450
1185
  apiKey: opts.apiKey || "ollama",
1451
1186
  conversationModel: opts.conversationModel,
1452
- identityModel: opts.identityModel,
1453
- supportsResponseFormat: false
1187
+ identityModel: opts.identityModel
1454
1188
  });
1455
1189
  }
1456
1190
  }
@@ -1476,8 +1210,7 @@ class LlamaCppExecutor extends OpenAICompatibleExecutor {
1476
1210
  baseURL: "http://127.0.0.1:8080/v1",
1477
1211
  apiKey: opts.apiKey || "llama.cpp",
1478
1212
  conversationModel: opts.conversationModel,
1479
- identityModel: opts.identityModel,
1480
- supportsResponseFormat: false
1213
+ identityModel: opts.identityModel
1481
1214
  });
1482
1215
  }
1483
1216
  }
@@ -1527,17 +1260,7 @@ class CloudflareGatewayExecutor extends OpenAICompatibleExecutor {
1527
1260
  // src/provider/providers/cloudflare_workers.ts
1528
1261
  function toCloudflareMessage(m) {
1529
1262
  if (m.role === "assistant") {
1530
- return {
1531
- role: "assistant",
1532
- content: m.content ?? "",
1533
- tool_calls: m.toolCalls?.map((c) => {
1534
- let args = c.function.arguments;
1535
- try {
1536
- args = JSON.parse(c.function.arguments);
1537
- } catch {}
1538
- return { id: c.id, name: c.function.name, arguments: args };
1539
- })
1540
- };
1263
+ return { role: "assistant", content: m.content ?? "" };
1541
1264
  }
1542
1265
  if (m.role === "tool") {
1543
1266
  return { role: "tool", content: m.content, tool_call_id: m.toolCallId };
@@ -1560,23 +1283,21 @@ class CloudflareWorkersExecutor extends LLMExecutor {
1560
1283
  const accountId = readAuthString(opts.auth, "accountId", "CLOUDFLARE_ACCOUNT_ID");
1561
1284
  this.baseURL = accountId ? `https://api.cloudflare.com/client/v4/accounts/${accountId}/ai/run` : "https://api.cloudflare.com/client/v4/accounts/__cf_account_id__/ai/run";
1562
1285
  }
1563
- async run(model, body, caller) {
1286
+ async run(model, body) {
1564
1287
  const url = `${this.baseURL}/${encodeURIComponent(model)}`;
1565
- const requestRaw = JSON.stringify(body);
1566
1288
  const res = await fetch(url, {
1567
1289
  method: "POST",
1568
1290
  headers: {
1569
1291
  Authorization: `Bearer ${this.apiKey}`,
1570
1292
  "Content-Type": "application/json"
1571
1293
  },
1572
- body: requestRaw
1294
+ body: JSON.stringify(body)
1573
1295
  });
1574
- const responseRaw = await res.text().catch(() => "");
1575
- logLlmWire(caller, requestRaw, responseRaw);
1576
1296
  if (!res.ok) {
1577
- throw new Error(`cloudflare-workers request failed: ${res.status} ${res.statusText} body=${responseRaw.slice(0, 500)}`);
1297
+ const text = await res.text().catch(() => "");
1298
+ throw new Error(`cloudflare-workers request failed: ${res.status} ${res.statusText} body=${text.slice(0, 500)}`);
1578
1299
  }
1579
- return JSON.parse(responseRaw);
1300
+ return await res.json();
1580
1301
  }
1581
1302
  async call(model, options) {
1582
1303
  const jsonMode = "jsonSchemaName" in options;
@@ -1597,15 +1318,15 @@ class CloudflareWorkersExecutor extends LLMExecutor {
1597
1318
  if (reasoning !== "none") {
1598
1319
  body["reasoning_effort"] = reasoning;
1599
1320
  }
1600
- const data = await this.run(model, body, resolveLlmCaller(options));
1321
+ const data = await this.run(model, body);
1601
1322
  if (data.errors && data.errors.length > 0) {
1602
1323
  throw new Error(`cloudflare-workers API error: ${data.errors.map((e) => e.message).join("; ")}`);
1603
1324
  }
1604
- const content = stripThinkTags(data.result?.response ?? "");
1325
+ const content = data.result?.response ?? "";
1605
1326
  if (!content) {
1606
1327
  throw new Error("Empty response from model");
1607
1328
  }
1608
- return jsonMode ? parseModelJson(content) : content;
1329
+ return jsonMode ? JSON.parse(content) : content;
1609
1330
  }
1610
1331
  async chatWithTools(model, options) {
1611
1332
  const reasoning = defaultReasoningEffort(options.reasoningEffort, model, this.models.identity);
@@ -1626,11 +1347,11 @@ class CloudflareWorkersExecutor extends LLMExecutor {
1626
1347
  if (reasoning !== "none") {
1627
1348
  body["reasoning_effort"] = reasoning;
1628
1349
  }
1629
- const data = await this.run(model, body, resolveLlmCaller(options));
1350
+ const data = await this.run(model, body);
1630
1351
  if (data.errors && data.errors.length > 0) {
1631
1352
  throw new Error(`cloudflare-workers API error: ${data.errors.map((e) => e.message).join("; ")}`);
1632
1353
  }
1633
- const content = stripThinkTags(data.result?.response ?? "");
1354
+ const content = data.result?.response ?? "";
1634
1355
  const toolCalls = data.result?.tool_calls?.map((c, idx) => ({
1635
1356
  id: `call_${idx}`,
1636
1357
  function: {
@@ -1668,8 +1389,7 @@ class AzureOpenAIExecutor extends OpenAICompatibleExecutor {
1668
1389
  super({
1669
1390
  providerName: "azure-openai",
1670
1391
  baseURL: `https://${resource || "__resource__"}.openai.azure.com/openai/deployments`,
1671
- apiKey: "",
1672
- defaultHeaders: { "api-key": opts.apiKey },
1392
+ apiKey: opts.apiKey,
1673
1393
  conversationModel: opts.conversationModel,
1674
1394
  identityModel: opts.identityModel
1675
1395
  });
@@ -1689,8 +1409,7 @@ class AzureCognitiveExecutor extends OpenAICompatibleExecutor {
1689
1409
  super({
1690
1410
  providerName: "azure-cognitive",
1691
1411
  baseURL: `https://${resource || "__resource__"}.cognitiveservices.azure.com/openai/deployments`,
1692
- apiKey: "",
1693
- defaultHeaders: { "api-key": opts.apiKey },
1412
+ apiKey: opts.apiKey,
1694
1413
  conversationModel: opts.conversationModel,
1695
1414
  identityModel: opts.identityModel
1696
1415
  });
@@ -1716,17 +1435,17 @@ var AnthropicAuthSchema = z4.object({
1716
1435
  apiVersion: z4.string().optional()
1717
1436
  }).loose();
1718
1437
  function toAnthropicMessages(messages) {
1719
- let system2;
1720
- const msgs2 = [];
1438
+ let system;
1439
+ const msgs = [];
1721
1440
  for (const m of messages) {
1722
1441
  if (m.role === "system") {
1723
- system2 = (system2 ? system2 + `
1442
+ system = (system ? system + `
1724
1443
 
1725
1444
  ` : "") + m.content;
1726
1445
  continue;
1727
1446
  }
1728
1447
  if (m.role === "user") {
1729
- msgs2.push({ role: "user", content: m.content });
1448
+ msgs.push({ role: "user", content: m.content });
1730
1449
  continue;
1731
1450
  }
1732
1451
  if (m.role === "assistant") {
@@ -1749,11 +1468,11 @@ function toAnthropicMessages(messages) {
1749
1468
  });
1750
1469
  }
1751
1470
  }
1752
- msgs2.push({ role: "assistant", content: blocks });
1471
+ msgs.push({ role: "assistant", content: blocks });
1753
1472
  continue;
1754
1473
  }
1755
1474
  if (m.role === "tool") {
1756
- msgs2.push({
1475
+ msgs.push({
1757
1476
  role: "user",
1758
1477
  content: [
1759
1478
  {
@@ -1765,7 +1484,7 @@ function toAnthropicMessages(messages) {
1765
1484
  });
1766
1485
  }
1767
1486
  }
1768
- return { system: system2, msgs: msgs2 };
1487
+ return { system, msgs };
1769
1488
  }
1770
1489
  function toAnthropicTool(t) {
1771
1490
  return {
@@ -1793,8 +1512,7 @@ class AnthropicExecutor extends LLMExecutor {
1793
1512
  this.baseURL = (extra.baseURL ?? readAuthString(opts.auth, "baseURL", "ANTHROPIC_BASE_URL") ?? "https://api.anthropic.com").replace(/\/v1\/?$/, "");
1794
1513
  this.apiVersion = extra.apiVersion ?? "2023-06-01";
1795
1514
  }
1796
- async send(body, caller) {
1797
- const requestRaw = JSON.stringify(body);
1515
+ async send(body) {
1798
1516
  const res = await fetch(`${this.baseURL}/v1/messages`, {
1799
1517
  method: "POST",
1800
1518
  headers: {
@@ -1802,93 +1520,65 @@ class AnthropicExecutor extends LLMExecutor {
1802
1520
  "anthropic-version": this.apiVersion,
1803
1521
  "Content-Type": "application/json"
1804
1522
  },
1805
- body: requestRaw
1523
+ body: JSON.stringify(body)
1806
1524
  });
1807
- const responseRaw = await res.text().catch(() => "");
1808
- logLlmWire(caller, requestRaw, responseRaw);
1809
1525
  if (!res.ok) {
1810
- throw new Error(`anthropic request failed: ${res.status} ${res.statusText} body=${responseRaw.slice(0, 500)}`);
1526
+ const text = await res.text().catch(() => "");
1527
+ throw new Error(`anthropic request failed: ${res.status} ${res.statusText} body=${text.slice(0, 500)}`);
1811
1528
  }
1812
- return JSON.parse(responseRaw);
1529
+ return await res.json();
1813
1530
  }
1814
1531
  async call(model, options) {
1815
- if ("jsonSchemaName" in options) {
1816
- const { toolName, tool, instruction } = buildStructuredJsonRequest({
1817
- instruction: options.instruction,
1818
- jsonSchemaName: options.jsonSchemaName,
1819
- jsonSchema: options.jsonSchema
1820
- });
1821
- const choice = await this.chatWithTools(model, {
1822
- caller: options.caller ?? options.jsonSchemaName,
1823
- instruction,
1824
- messages: [{ role: "user", content: options.message }],
1825
- tools: [tool],
1826
- reasoningEffort: "none",
1827
- toolChoice: { type: "tool", name: toolName }
1828
- });
1829
- return parseStructuredJsonResult(choice, toolName);
1830
- }
1532
+ const jsonMode = "jsonSchemaName" in options;
1831
1533
  const reasoning = defaultReasoningEffort(options.reasoningEffort, model, this.models.identity);
1832
1534
  const log5 = logger.child("llm:anthropic");
1833
- log5.debug(`call: model=${model} jsonSchema=- msgLen=${options.message.length}`);
1834
- const outputCap = 4096;
1535
+ log5.debug(`call: model=${model} jsonSchema=${jsonMode ? options.jsonSchemaName : "-"} msgLen=${options.message.length}`);
1835
1536
  const body = {
1836
1537
  model,
1837
- max_tokens: outputCap,
1538
+ max_tokens: 4096,
1838
1539
  system: options.instruction,
1839
1540
  messages: [{ role: "user", content: options.message }]
1840
1541
  };
1841
1542
  if (reasoning !== "none") {
1842
- const budget = REASONING_BUDGET[reasoning];
1843
- body["max_tokens"] = budget + outputCap;
1844
1543
  body["thinking"] = {
1845
1544
  type: "enabled",
1846
- budget_tokens: budget
1545
+ budget_tokens: REASONING_BUDGET[reasoning]
1847
1546
  };
1848
1547
  }
1849
- const data = await this.send(body, resolveLlmCaller(options));
1548
+ const data = await this.send(body);
1850
1549
  if (data.error) {
1851
1550
  throw new Error(`anthropic API error: ${data.error.message ?? "unknown"}`);
1852
1551
  }
1853
- const text = stripThinkTags((data.content ?? []).filter((b) => b.type === "text").map((b) => b.text).join(""));
1552
+ const text = (data.content ?? []).filter((b) => b.type === "text").map((b) => b.text).join("");
1854
1553
  if (!text) {
1855
1554
  throw new Error("Empty response from model");
1856
1555
  }
1857
- return text;
1556
+ return jsonMode ? JSON.parse(text) : text;
1858
1557
  }
1859
1558
  async chatWithTools(model, options) {
1860
1559
  const reasoning = defaultReasoningEffort(options.reasoningEffort, model, this.models.identity);
1861
1560
  const log5 = logger.child("llm:anthropic");
1862
1561
  log5.debug(`chatWithTools: model=${model} msgs=${options.messages.length} tools=${options.tools.length}`);
1863
- const { system: system2, msgs: msgs2 } = toAnthropicMessages(options.messages);
1864
- const outputCap = 4096;
1562
+ const { system, msgs } = toAnthropicMessages(options.messages);
1865
1563
  const body = {
1866
1564
  model,
1867
- max_tokens: outputCap,
1868
- system: system2 ?? options.instruction,
1869
- messages: msgs2,
1565
+ max_tokens: 4096,
1566
+ system: system ?? options.instruction,
1567
+ messages: msgs,
1870
1568
  tools: options.tools.map(toAnthropicTool)
1871
1569
  };
1872
- if (options.toolChoice) {
1873
- body["tool_choice"] = {
1874
- type: "tool",
1875
- name: options.toolChoice.name
1876
- };
1877
- }
1878
1570
  if (reasoning !== "none") {
1879
- const budget = REASONING_BUDGET[reasoning];
1880
- body["max_tokens"] = budget + outputCap;
1881
1571
  body["thinking"] = {
1882
1572
  type: "enabled",
1883
- budget_tokens: budget
1573
+ budget_tokens: REASONING_BUDGET[reasoning]
1884
1574
  };
1885
1575
  }
1886
- const data = await this.send(body, resolveLlmCaller(options));
1576
+ const data = await this.send(body);
1887
1577
  if (data.error) {
1888
1578
  throw new Error(`anthropic API error: ${data.error.message ?? "unknown"}`);
1889
1579
  }
1890
1580
  const blocks = data.content ?? [];
1891
- const text = stripThinkTags(blocks.filter((b) => b.type === "text").map((b) => b.text).join(""));
1581
+ const text = blocks.filter((b) => b.type === "text").map((b) => b.text).join("");
1892
1582
  const toolCalls = blocks.filter((b) => b.type === "tool_use").map((b) => ({
1893
1583
  id: b.id,
1894
1584
  function: {
@@ -1902,11 +1592,11 @@ class AnthropicExecutor extends LLMExecutor {
1902
1592
 
1903
1593
  // src/provider/providers/bedrock.ts
1904
1594
  import { createHmac, createHash } from "node:crypto";
1905
- var pad22 = (n) => n.toString().padStart(2, "0");
1595
+ var pad2 = (n) => n.toString().padStart(2, "0");
1906
1596
  function amzDate(now) {
1907
1597
  return {
1908
- date: `${now.getUTCFullYear()}${pad22(now.getUTCMonth() + 1)}${pad22(now.getUTCDate())}`,
1909
- datetime: `${now.getUTCFullYear()}${pad22(now.getUTCMonth() + 1)}${pad22(now.getUTCDate())}T` + `${pad22(now.getUTCHours())}${pad22(now.getUTCMinutes())}${pad22(now.getUTCSeconds())}Z`
1598
+ date: `${now.getUTCFullYear()}${pad2(now.getUTCMonth() + 1)}${pad2(now.getUTCDate())}`,
1599
+ datetime: `${now.getUTCFullYear()}${pad2(now.getUTCMonth() + 1)}${pad2(now.getUTCDate())}T` + `${pad2(now.getUTCHours())}${pad2(now.getUTCMinutes())}${pad2(now.getUTCSeconds())}Z`
1910
1600
  };
1911
1601
  }
1912
1602
  function signRequest(opts) {
@@ -1953,6 +1643,58 @@ function signRequest(opts) {
1953
1643
  headers["X-Amz-Security-Token"] = opts.sessionToken;
1954
1644
  return headers;
1955
1645
  }
1646
+ function toAnthropicMessages2(messages) {
1647
+ let system;
1648
+ const msgs = [];
1649
+ for (const m of messages) {
1650
+ if (m.role === "system") {
1651
+ system = (system ? system + `
1652
+
1653
+ ` : "") + m.content;
1654
+ continue;
1655
+ }
1656
+ if (m.role === "user") {
1657
+ msgs.push({ role: "user", content: [{ type: "text", text: m.content }] });
1658
+ continue;
1659
+ }
1660
+ if (m.role === "assistant") {
1661
+ const blocks = [];
1662
+ if (m.content)
1663
+ blocks.push({ type: "text", text: m.content });
1664
+ if (m.toolCalls) {
1665
+ for (const c of m.toolCalls) {
1666
+ let input = {};
1667
+ try {
1668
+ input = c.function.arguments ? JSON.parse(c.function.arguments) : {};
1669
+ } catch {
1670
+ input = {};
1671
+ }
1672
+ blocks.push({
1673
+ type: "tool_use",
1674
+ id: c.id,
1675
+ name: c.function.name,
1676
+ input
1677
+ });
1678
+ }
1679
+ }
1680
+ msgs.push({ role: "assistant", content: blocks });
1681
+ continue;
1682
+ }
1683
+ if (m.role === "tool") {
1684
+ msgs.push({
1685
+ role: "user",
1686
+ content: [
1687
+ {
1688
+ type: "tool_result",
1689
+ tool_use_id: m.toolCallId,
1690
+ content: [{ type: "text", text: m.content }]
1691
+ }
1692
+ ]
1693
+ });
1694
+ }
1695
+ }
1696
+ return { system, msgs };
1697
+ }
1956
1698
  function toAnthropicTool2(t) {
1957
1699
  return {
1958
1700
  name: t.name,
@@ -1962,7 +1704,7 @@ function toAnthropicTool2(t) {
1962
1704
  }
1963
1705
  function extractAnthropicContent(data) {
1964
1706
  const content = data["content"] ?? [];
1965
- const text = stripThinkTags(content.filter((b) => b["type"] === "text").map((b) => b["text"]).join(""));
1707
+ const text = content.filter((b) => b["type"] === "text").map((b) => b["text"]).join("");
1966
1708
  const toolCalls = content.filter((b) => b["type"] === "tool_use").map((b) => ({
1967
1709
  id: b["id"],
1968
1710
  function: {
@@ -1994,7 +1736,7 @@ class BedrockExecutor extends LLMExecutor {
1994
1736
  this.sessionToken = readAuthString(opts.auth, "sessionToken", "AWS_SESSION_TOKEN");
1995
1737
  this.baseURL = `https://bedrock-runtime.${this.region}.amazonaws.com`;
1996
1738
  }
1997
- async invoke(model, body, caller) {
1739
+ async invoke(model, body) {
1998
1740
  const bodyStr = JSON.stringify(body);
1999
1741
  const path = `/model/${encodeURIComponent(model)}/invoke`;
2000
1742
  const headers = signRequest({
@@ -2013,32 +1755,16 @@ class BedrockExecutor extends LLMExecutor {
2013
1755
  headers,
2014
1756
  body: bodyStr
2015
1757
  });
2016
- const responseRaw = await res.text().catch(() => "");
2017
- logLlmWire(caller, bodyStr, responseRaw);
2018
1758
  if (!res.ok) {
2019
- throw new Error(`bedrock request failed: ${res.status} ${res.statusText} body=${responseRaw.slice(0, 500)}`);
1759
+ const text = await res.text().catch(() => "");
1760
+ throw new Error(`bedrock request failed: ${res.status} ${res.statusText} body=${text.slice(0, 500)}`);
2020
1761
  }
2021
- return JSON.parse(responseRaw);
1762
+ return await res.json();
2022
1763
  }
2023
1764
  async call(model, options) {
1765
+ const jsonMode = "jsonSchemaName" in options;
2024
1766
  const log5 = logger.child("llm:bedrock");
2025
- if ("jsonSchemaName" in options) {
2026
- log5.debug(`call: model=${model} jsonSchema=${options.jsonSchemaName} via tool`);
2027
- const { toolName, tool, instruction } = buildStructuredJsonRequest({
2028
- instruction: options.instruction,
2029
- jsonSchemaName: options.jsonSchemaName,
2030
- jsonSchema: options.jsonSchema
2031
- });
2032
- const choice = await this.chatWithTools(model, {
2033
- caller: options.caller ?? options.jsonSchemaName,
2034
- instruction,
2035
- messages: [{ role: "user", content: options.message }],
2036
- tools: [tool],
2037
- toolChoice: { type: "tool", name: toolName }
2038
- });
2039
- return parseStructuredJsonResult(choice, toolName);
2040
- }
2041
- log5.debug(`call: model=${model} jsonSchema=- msgLen=${options.message.length}`);
1767
+ log5.debug(`call: model=${model} jsonSchema=${jsonMode ? options.jsonSchemaName : "-"} msgLen=${options.message.length}`);
2042
1768
  if (!model.startsWith("anthropic.") && !model.startsWith("us.anthropic.")) {
2043
1769
  throw new Error(`bedrock provider currently only supports Anthropic models (got ${model})`);
2044
1770
  }
@@ -2048,12 +1774,12 @@ class BedrockExecutor extends LLMExecutor {
2048
1774
  system: options.instruction,
2049
1775
  messages: [{ role: "user", content: options.message }]
2050
1776
  };
2051
- const data = await this.invoke(model, body, resolveLlmCaller(options));
1777
+ const data = await this.invoke(model, body);
2052
1778
  const { text } = extractAnthropicContent(data);
2053
1779
  if (!text) {
2054
1780
  throw new Error("Empty response from model");
2055
1781
  }
2056
- return text;
1782
+ return jsonMode ? JSON.parse(text) : text;
2057
1783
  }
2058
1784
  async chatWithTools(model, options) {
2059
1785
  const log5 = logger.child("llm:bedrock");
@@ -2061,6 +1787,7 @@ class BedrockExecutor extends LLMExecutor {
2061
1787
  if (!model.startsWith("anthropic.") && !model.startsWith("us.anthropic.")) {
2062
1788
  throw new Error(`bedrock provider currently only supports Anthropic models (got ${model})`);
2063
1789
  }
1790
+ const { system, msgs } = toAnthropicMessages2(options.messages);
2064
1791
  const body = {
2065
1792
  anthropic_version: "bedrock-2023-05-31",
2066
1793
  max_tokens: 4096,
@@ -2068,13 +1795,7 @@ class BedrockExecutor extends LLMExecutor {
2068
1795
  messages: msgs,
2069
1796
  tools: options.tools.map(toAnthropicTool2)
2070
1797
  };
2071
- if (options.toolChoice) {
2072
- body.tool_choice = {
2073
- type: "tool",
2074
- name: options.toolChoice.name
2075
- };
2076
- }
2077
- const data = await this.invoke(model, body, resolveLlmCaller(options));
1798
+ const data = await this.invoke(model, body);
2078
1799
  const { text, toolCalls } = extractAnthropicContent(data);
2079
1800
  return { message: { content: text || undefined, toolCalls } };
2080
1801
  }
@@ -2088,7 +1809,6 @@ var VertexAuthSchema = z5.object({
2088
1809
  }).loose();
2089
1810
  function toGeminiContents(messages) {
2090
1811
  const out = [];
2091
- const nameByCallId = new Map;
2092
1812
  for (const m of messages) {
2093
1813
  if (m.role === "system")
2094
1814
  continue;
@@ -2102,7 +1822,6 @@ function toGeminiContents(messages) {
2102
1822
  parts.push({ text: m.content });
2103
1823
  if (m.toolCalls) {
2104
1824
  for (const c of m.toolCalls) {
2105
- nameByCallId.set(c.id, c.function.name);
2106
1825
  let args = {};
2107
1826
  try {
2108
1827
  args = c.function.arguments ? JSON.parse(c.function.arguments) : {};
@@ -2124,26 +1843,21 @@ function toGeminiContents(messages) {
2124
1843
  }
2125
1844
  out.push({
2126
1845
  role: "user",
2127
- parts: [
2128
- {
2129
- functionResponse: {
2130
- name: nameByCallId.get(m.toolCallId) ?? "",
2131
- response
2132
- }
2133
- }
2134
- ]
1846
+ parts: [{ functionResponse: { name: "", response } }]
2135
1847
  });
2136
1848
  }
2137
1849
  }
2138
1850
  return out;
2139
1851
  }
2140
- function toGeminiTools(tools) {
1852
+ function toGeminiTool(t) {
2141
1853
  return {
2142
- functionDeclarations: tools.map((t) => ({
2143
- name: t.name,
2144
- description: t.description,
2145
- parameters: t.parameters ?? { type: "object", properties: {} }
2146
- }))
1854
+ functionDeclarations: [
1855
+ {
1856
+ name: t.name,
1857
+ description: t.description,
1858
+ parameters: t.parameters ?? { type: "object", properties: {} }
1859
+ }
1860
+ ]
2147
1861
  };
2148
1862
  }
2149
1863
  function extractFromGemini(data) {
@@ -2164,10 +1878,7 @@ function extractFromGemini(data) {
2164
1878
  });
2165
1879
  }
2166
1880
  }
2167
- return {
2168
- text: stripThinkTags(text),
2169
- toolCalls: toolCalls.length > 0 ? toolCalls : undefined
2170
- };
1881
+ return { text, toolCalls: toolCalls.length > 0 ? toolCalls : undefined };
2171
1882
  }
2172
1883
 
2173
1884
  class VertexExecutor extends LLMExecutor {
@@ -2190,23 +1901,21 @@ class VertexExecutor extends LLMExecutor {
2190
1901
  this.region = extra.region ?? readAuthString(opts.auth, "region", "GOOGLE_CLOUD_REGION") ?? "us-central1";
2191
1902
  this.baseURL = this.project ? `https://${this.region}-aiplatform.googleapis.com/v1/projects/${this.project}/locations/${this.region}/publishers/google/models` : "https://us-central1-aiplatform.googleapis.com/v1";
2192
1903
  }
2193
- async generate(model, body, caller) {
1904
+ async generate(model, body) {
2194
1905
  const url = `${this.baseURL}/${encodeURIComponent(model)}:generateContent`;
2195
- const requestRaw = JSON.stringify(body);
2196
1906
  const res = await fetch(url, {
2197
1907
  method: "POST",
2198
1908
  headers: {
2199
1909
  Authorization: `Bearer ${this.apiKey}`,
2200
1910
  "Content-Type": "application/json"
2201
1911
  },
2202
- body: requestRaw
1912
+ body: JSON.stringify(body)
2203
1913
  });
2204
- const responseRaw = await res.text().catch(() => "");
2205
- logLlmWire(caller, requestRaw, responseRaw);
2206
1914
  if (!res.ok) {
2207
- throw new Error(`vertex request failed: ${res.status} ${res.statusText} body=${responseRaw.slice(0, 500)}`);
1915
+ const text = await res.text().catch(() => "");
1916
+ throw new Error(`vertex request failed: ${res.status} ${res.statusText} body=${text.slice(0, 500)}`);
2208
1917
  }
2209
- return JSON.parse(responseRaw);
1918
+ return await res.json();
2210
1919
  }
2211
1920
  async call(model, options) {
2212
1921
  const jsonMode = "jsonSchemaName" in options;
@@ -2227,7 +1936,7 @@ class VertexExecutor extends LLMExecutor {
2227
1936
  responseSchema: options.jsonSchema
2228
1937
  };
2229
1938
  }
2230
- const data = await this.generate(model, body, resolveLlmCaller(options));
1939
+ const data = await this.generate(model, body);
2231
1940
  if (data.error) {
2232
1941
  throw new Error(`vertex API error: ${data.error.message ?? "unknown"}`);
2233
1942
  }
@@ -2235,7 +1944,7 @@ class VertexExecutor extends LLMExecutor {
2235
1944
  if (!text) {
2236
1945
  throw new Error("Empty response from model");
2237
1946
  }
2238
- return jsonMode ? parseModelJson(text) : text;
1947
+ return jsonMode ? JSON.parse(text) : text;
2239
1948
  }
2240
1949
  async chatWithTools(model, options) {
2241
1950
  const log5 = logger.child("llm:vertex");
@@ -2244,9 +1953,9 @@ class VertexExecutor extends LLMExecutor {
2244
1953
  const body = {
2245
1954
  contents,
2246
1955
  systemInstruction: { parts: [{ text: options.instruction }] },
2247
- tools: options.tools.length > 0 ? [toGeminiTools(options.tools)] : undefined
1956
+ tools: options.tools.length > 0 ? [toGeminiTool(options.tools[0])] : undefined
2248
1957
  };
2249
- const data = await this.generate(model, body, resolveLlmCaller(options));
1958
+ const data = await this.generate(model, body);
2250
1959
  if (data.error) {
2251
1960
  throw new Error(`vertex API error: ${data.error.message ?? "unknown"}`);
2252
1961
  }
@@ -2322,41 +2031,25 @@ class GitLabDuoExecutor extends LLMExecutor {
2322
2031
  const extra = parsed.success ? parsed.data : {};
2323
2032
  this.baseURL = (extra.baseURL ?? readAuthString(opts.auth, "baseURL", "GITLAB_BASE_URL") ?? "https://gitlab.com/api/v4/ai/llm/proxy").replace(/\/+$/, "");
2324
2033
  }
2325
- async send(body, caller) {
2326
- const requestRaw = JSON.stringify(body);
2034
+ async send(body) {
2327
2035
  const res = await fetch(this.baseURL, {
2328
2036
  method: "POST",
2329
2037
  headers: {
2330
2038
  Authorization: `Bearer ${this.apiKey}`,
2331
2039
  "Content-Type": "application/json"
2332
2040
  },
2333
- body: requestRaw
2041
+ body: JSON.stringify(body)
2334
2042
  });
2335
- const responseRaw = await res.text().catch(() => "");
2336
- logLlmWire(caller, requestRaw, responseRaw);
2337
2043
  if (!res.ok) {
2338
- throw new Error(`gitlab-duo request failed: ${res.status} ${res.statusText} body=${responseRaw.slice(0, 500)}`);
2044
+ const text = await res.text().catch(() => "");
2045
+ throw new Error(`gitlab-duo request failed: ${res.status} ${res.statusText} body=${text.slice(0, 500)}`);
2339
2046
  }
2340
- return JSON.parse(responseRaw);
2047
+ return await res.json();
2341
2048
  }
2342
2049
  async call(model, options) {
2050
+ const jsonMode = "jsonSchemaName" in options;
2343
2051
  const log5 = logger.child("llm:gitlab-duo");
2344
- if ("jsonSchemaName" in options) {
2345
- log5.debug(`call: model=${model} jsonSchema=${options.jsonSchemaName} via tool`);
2346
- const { toolName, tool, instruction } = buildStructuredJsonRequest({
2347
- instruction: options.instruction,
2348
- jsonSchemaName: options.jsonSchemaName,
2349
- jsonSchema: options.jsonSchema
2350
- });
2351
- const choice = await this.chatWithTools(model, {
2352
- caller: options.caller ?? options.jsonSchemaName,
2353
- instruction,
2354
- messages: [{ role: "user", content: options.message }],
2355
- tools: [tool]
2356
- });
2357
- return parseStructuredJsonResult(choice, toolName);
2358
- }
2359
- log5.debug(`call: model=${model} jsonSchema=- msgLen=${options.message.length}`);
2052
+ log5.debug(`call: model=${model} jsonSchema=${jsonMode ? options.jsonSchemaName : "-"} msgLen=${options.message.length}`);
2360
2053
  const body = {
2361
2054
  model,
2362
2055
  messages: [
@@ -2364,15 +2057,15 @@ class GitLabDuoExecutor extends LLMExecutor {
2364
2057
  { role: "user", content: options.message }
2365
2058
  ]
2366
2059
  };
2367
- const data = await this.send(body, resolveLlmCaller(options));
2060
+ const data = await this.send(body);
2368
2061
  if (data.error) {
2369
2062
  throw new Error(`gitlab-duo API error: ${data.error.message ?? "unknown"}`);
2370
2063
  }
2371
- const content = stripThinkTags(data.choices?.[0]?.message?.content ?? "");
2064
+ const content = data.choices?.[0]?.message?.content ?? "";
2372
2065
  if (!content) {
2373
2066
  throw new Error("Empty response from model");
2374
2067
  }
2375
- return content;
2068
+ return jsonMode ? JSON.parse(content) : content;
2376
2069
  }
2377
2070
  async chatWithTools(model, options) {
2378
2071
  const log5 = logger.child("llm:gitlab-duo");
@@ -2385,7 +2078,7 @@ class GitLabDuoExecutor extends LLMExecutor {
2385
2078
  ],
2386
2079
  tools: options.tools.map(toTool)
2387
2080
  };
2388
- const data = await this.send(body, resolveLlmCaller(options));
2081
+ const data = await this.send(body);
2389
2082
  if (data.error) {
2390
2083
  throw new Error(`gitlab-duo API error: ${data.error.message ?? "unknown"}`);
2391
2084
  }
@@ -2396,7 +2089,7 @@ class GitLabDuoExecutor extends LLMExecutor {
2396
2089
  }));
2397
2090
  return {
2398
2091
  message: {
2399
- content: choice?.message?.content ? stripThinkTags(choice.message.content) : undefined,
2092
+ content: choice?.message?.content ?? undefined,
2400
2093
  toolCalls
2401
2094
  }
2402
2095
  };
@@ -2453,43 +2146,26 @@ class SnowflakeCortexExecutor extends LLMExecutor {
2453
2146
  const account = extra.account ?? readAuthString(opts.auth, "account", "SNOWFLAKE_ACCOUNT") ?? "";
2454
2147
  this.baseURL = account ? `https://${account}.snowflakecomputing.com/api/v2/cortex/inference:complete` : "https://__account__.snowflakecomputing.com/api/v2/cortex/inference:complete";
2455
2148
  }
2456
- async send(body, caller) {
2457
- const requestRaw = JSON.stringify(body);
2149
+ async send(body) {
2458
2150
  const res = await fetch(this.baseURL, {
2459
2151
  method: "POST",
2460
2152
  headers: {
2461
2153
  Authorization: `Bearer ${this.apiKey}`,
2462
2154
  "Content-Type": "application/json"
2463
2155
  },
2464
- body: requestRaw
2156
+ body: JSON.stringify(body)
2465
2157
  });
2466
- const responseRaw = await res.text().catch(() => "");
2467
- logLlmWire(caller, requestRaw, responseRaw);
2468
2158
  if (!res.ok) {
2469
- throw new Error(`snowflake-cortex request failed: ${res.status} ${res.statusText} body=${responseRaw.slice(0, 500)}`);
2159
+ const text = await res.text().catch(() => "");
2160
+ throw new Error(`snowflake-cortex request failed: ${res.status} ${res.statusText} body=${text.slice(0, 500)}`);
2470
2161
  }
2471
- return JSON.parse(responseRaw);
2162
+ return await res.json();
2472
2163
  }
2473
2164
  async call(model, options) {
2474
- const log5 = logger.child("llm:snowflake-cortex");
2475
- if ("jsonSchemaName" in options) {
2476
- log5.debug(`call: model=${model} jsonSchema=${options.jsonSchemaName} via tool`);
2477
- const { toolName, tool, instruction } = buildStructuredJsonRequest({
2478
- instruction: options.instruction,
2479
- jsonSchemaName: options.jsonSchemaName,
2480
- jsonSchema: options.jsonSchema
2481
- });
2482
- const choice = await this.chatWithTools(model, {
2483
- caller: options.caller ?? options.jsonSchemaName,
2484
- instruction,
2485
- messages: [{ role: "user", content: options.message }],
2486
- tools: [tool],
2487
- reasoningEffort: "none"
2488
- });
2489
- return parseStructuredJsonResult(choice, toolName);
2490
- }
2165
+ const jsonMode = "jsonSchemaName" in options;
2491
2166
  const reasoning = defaultReasoningEffort(options.reasoningEffort, model, this.models.identity);
2492
- log5.debug(`call: model=${model} jsonSchema=- msgLen=${options.message.length}`);
2167
+ const log5 = logger.child("llm:snowflake-cortex");
2168
+ log5.debug(`call: model=${model} jsonSchema=${jsonMode ? options.jsonSchemaName : "-"} msgLen=${options.message.length}`);
2493
2169
  const body = {
2494
2170
  model,
2495
2171
  messages: [
@@ -2500,15 +2176,15 @@ class SnowflakeCortexExecutor extends LLMExecutor {
2500
2176
  if (reasoning !== "none") {
2501
2177
  body["reasoning_effort"] = reasoning;
2502
2178
  }
2503
- const data = await this.send(body, resolveLlmCaller(options));
2179
+ const data = await this.send(body);
2504
2180
  if (data.error) {
2505
2181
  throw new Error(`snowflake-cortex API error: ${data.error.message ?? "unknown"}`);
2506
2182
  }
2507
- const content = stripThinkTags(data.choices?.[0]?.message?.content ?? data.message ?? "");
2183
+ const content = data.choices?.[0]?.message?.content ?? data.message ?? "";
2508
2184
  if (!content) {
2509
2185
  throw new Error("Empty response from model");
2510
2186
  }
2511
- return content;
2187
+ return jsonMode ? JSON.parse(content) : content;
2512
2188
  }
2513
2189
  async chatWithTools(model, options) {
2514
2190
  const log5 = logger.child("llm:snowflake-cortex");
@@ -2521,12 +2197,12 @@ class SnowflakeCortexExecutor extends LLMExecutor {
2521
2197
  ],
2522
2198
  tools: options.tools.map(toCortexTool)
2523
2199
  };
2524
- const data = await this.send(body, resolveLlmCaller(options));
2200
+ const data = await this.send(body);
2525
2201
  if (data.error) {
2526
2202
  throw new Error(`snowflake-cortex API error: ${data.error.message ?? "unknown"}`);
2527
2203
  }
2528
2204
  const choice = data.choices?.[0];
2529
- const content = stripThinkTags(choice?.message?.content ?? data.message ?? "");
2205
+ const content = choice?.message?.content ?? data.message ?? "";
2530
2206
  const toolCalls = choice?.message?.tool_calls?.map((c) => ({
2531
2207
  id: c.id,
2532
2208
  function: {
@@ -2565,8 +2241,7 @@ register("stackit", StackitExecutor);
2565
2241
  register("gmi", GmiExecutor);
2566
2242
  register("zai", ZaiExecutor);
2567
2243
  register("zenmux", ZenMuxExecutor);
2568
- register("minimax", MiniMaxExecutor);
2569
- register("minimax-cn", MiniMaxCnExecutor);
2244
+ register("MiniMax", MiniMaxExecutor);
2570
2245
  register("ionet", IoNetExecutor);
2571
2246
  register("baseten", BasetenExecutor);
2572
2247
  register("cortecs", CortecsExecutor);
@@ -2600,14 +2275,14 @@ var llm = new Proxy({}, {
2600
2275
  // src/provider/promptLoader.ts
2601
2276
  import { existsSync as existsSync2 } from "fs";
2602
2277
  import { readFile as readFile2 } from "fs/promises";
2603
- import path, { dirname as dirname2 } from "path";
2278
+ import path, { dirname as dirname3 } from "path";
2604
2279
  import { fileURLToPath } from "url";
2605
2280
  var log5 = logger.child("prompt-loader");
2606
2281
  function fileName(promptKey) {
2607
2282
  return promptKey.toLowerCase() + ".md";
2608
2283
  }
2609
2284
  var __filename2 = fileURLToPath(import.meta.url);
2610
- var __dirname2 = dirname2(__filename2);
2285
+ var __dirname2 = dirname3(__filename2);
2611
2286
  var PROMPTS_DIR = existsSync2(path.resolve(__dirname2, "prompts")) ? path.resolve(__dirname2, "prompts") : path.resolve(__dirname2, "../../prompts");
2612
2287
  async function loadPrompt(promptKey) {
2613
2288
  const filePath = path.join(PROMPTS_DIR, fileName(promptKey));
@@ -2732,14 +2407,14 @@ function translateMessageHistory(personaName, entries) {
2732
2407
  }
2733
2408
 
2734
2409
  // src/brain/schedule.ts
2735
- function pad23(n) {
2410
+ function pad22(n) {
2736
2411
  return n < 10 ? `0${n}` : `${n}`;
2737
2412
  }
2738
2413
  function formatDateKey(d) {
2739
- return `${d.getFullYear()}-${pad23(d.getMonth() + 1)}-${pad23(d.getDate())}`;
2414
+ return `${d.getFullYear()}-${pad22(d.getMonth() + 1)}-${pad22(d.getDate())}`;
2740
2415
  }
2741
2416
  function formatMonthKey(d) {
2742
- return `${d.getFullYear()}-${pad23(d.getMonth() + 1)}`;
2417
+ return `${d.getFullYear()}-${pad22(d.getMonth() + 1)}`;
2743
2418
  }
2744
2419
 
2745
2420
  // src/brain/memory.ts
@@ -2827,22 +2502,22 @@ class Brain {
2827
2502
  db;
2828
2503
  space;
2829
2504
  brainbase;
2830
- availabilityCache = new Map;
2831
2505
  memory;
2832
- constructor(db, space, brainbase, memory) {
2506
+ availabilityCache = new Map;
2507
+ constructor(db, space, brainbase, memory = new Memory(this.db, this.space)) {
2833
2508
  this.db = db;
2834
2509
  this.space = space;
2835
2510
  this.brainbase = brainbase;
2836
- this.memory = memory ?? new Memory(this.db, this.space);
2511
+ this.memory = memory;
2837
2512
  log7.debug(`Brain constructed: id=${brainbase.brainId} name=${brainbase.displayName} space=${space.name}`);
2838
2513
  }
2839
2514
  async createDailySchedule(datetime) {
2840
- const dateKey2 = formatDateKey(datetime);
2841
- log7.debug(`createDailySchedule: starting for ${dateKey2}`);
2515
+ const dateKey = formatDateKey(datetime);
2516
+ log7.debug(`createDailySchedule: starting for ${dateKey}`);
2842
2517
  try {
2843
- const existing = await this.memory.get(`daily-schedule:${dateKey2}`);
2518
+ const existing = await this.memory.get(`daily-schedule:${dateKey}`);
2844
2519
  if (existing) {
2845
- log7.debug(`createDailySchedule: cache hit for ${dateKey2}`);
2520
+ log7.debug(`createDailySchedule: cache hit for ${dateKey}`);
2846
2521
  try {
2847
2522
  return JSON.parse(existing.content);
2848
2523
  } catch (parseErr) {
@@ -2870,7 +2545,7 @@ class Brain {
2870
2545
  }
2871
2546
  const instruction = await loadPrompt("DAILY_SCHEDULE");
2872
2547
  const promptMessage = [
2873
- `Target date: ${dateKey2} (${datetime.toLocaleDateString("en-US", { weekday: "long" })})`,
2548
+ `Target date: ${dateKey} (${datetime.toLocaleDateString("en-US", { weekday: "long" })})`,
2874
2549
  `Personality: ${this.brainbase.baseSystemPrompt}`,
2875
2550
  monthlySummary ? `Monthly summary for this day: ${monthlySummary}` : "(no monthly summary available for this date)",
2876
2551
  `Recent schedule (${twoDaysAgoKey}, 2 days ago): ${twoDaysAgoSchedule ? twoDaysAgoSchedule.items.map((s) => `${s.start} ${s.activity}`).join(", ") : "(no schedule on file for 2 days ago)"}`,
@@ -2881,7 +2556,6 @@ class Brain {
2881
2556
  `);
2882
2557
  log7.debug(`createDailySchedule: calling identity model`);
2883
2558
  const schedule = await llm.call(llm.models.identity, {
2884
- caller: "daily-schedule",
2885
2559
  instruction,
2886
2560
  message: promptMessage,
2887
2561
  jsonSchemaName: "daily-schedule",
@@ -2889,15 +2563,15 @@ class Brain {
2889
2563
  });
2890
2564
  log7.debug(`createDailySchedule: model returned ${schedule.items.length} slots`);
2891
2565
  await this.memory.add({
2892
- customId: `daily-schedule:${dateKey2}`,
2566
+ customId: `daily-schedule:${dateKey}`,
2893
2567
  content: JSON.stringify(schedule),
2894
2568
  metadata: {
2895
2569
  kind: "schedule",
2896
2570
  source: "createDailySchedule",
2897
- date: dateKey2
2571
+ date: dateKey
2898
2572
  }
2899
2573
  });
2900
- log7.debug(`createDailySchedule: persisted ${dateKey2}`);
2574
+ log7.debug(`createDailySchedule: persisted ${dateKey}`);
2901
2575
  return schedule;
2902
2576
  } catch (error) {
2903
2577
  let reason = error instanceof Error ? error.message + `(${error.name})` : String(error);
@@ -2909,21 +2583,17 @@ class Brain {
2909
2583
  }
2910
2584
  async regenerateSchedules() {
2911
2585
  const today = new Date;
2912
- const nextMonth = new Date(today.getFullYear(), today.getMonth() + 1, today.getDate());
2913
- const monthly = await this.createMonthlySchedule(nextMonth);
2914
- if (!monthly) {
2915
- log7.debug(`regenerateSchedules: skip daily — monthly schedule generation failed`);
2916
- return;
2917
- }
2918
2586
  const tomorrow = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 1);
2919
2587
  await this.createDailySchedule(tomorrow);
2920
2588
  await this.createDailySchedule(today);
2589
+ const nextMonth = new Date(today.getFullYear(), today.getMonth() + 1, today.getDate());
2590
+ await this.createMonthlySchedule(nextMonth);
2921
2591
  }
2922
2592
  async createMonthlySchedule(datetime) {
2923
2593
  const year = datetime.getMonth() === 11 ? datetime.getFullYear() + 1 : datetime.getFullYear();
2924
2594
  const month = (datetime.getMonth() + 1) % 12;
2925
2595
  const daysInMonth = new Date(year, month + 1, 0).getDate();
2926
- const monthKey = `${year}-${pad23(month + 1)}`;
2596
+ const monthKey = `${year}-${pad22(month + 1)}`;
2927
2597
  log7.debug(`createMonthlySchedule: starting for ${monthKey}`);
2928
2598
  try {
2929
2599
  const existing = await this.memory.get(`monthly-schedule:${monthKey}`);
@@ -2936,7 +2606,7 @@ class Brain {
2936
2606
  }
2937
2607
  }
2938
2608
  const twoMonthsAgo = new Date(year, month - 2, 1);
2939
- const twoMonthsAgoKey = `${twoMonthsAgo.getFullYear()}-${pad23(twoMonthsAgo.getMonth() + 1)}`;
2609
+ const twoMonthsAgoKey = `${twoMonthsAgo.getFullYear()}-${pad22(twoMonthsAgo.getMonth() + 1)}`;
2940
2610
  log7.debug(`createMonthlySchedule: gathering context (history, ${twoMonthsAgoKey})`);
2941
2611
  const [history, twoMonthsAgoStored] = await Promise.all([
2942
2612
  this.getHistoryFacts(),
@@ -2964,7 +2634,6 @@ class Brain {
2964
2634
  `);
2965
2635
  log7.debug(`createMonthlySchedule: calling identity model`);
2966
2636
  const schedule = await llm.call(llm.models.identity, {
2967
- caller: "monthly-schedule",
2968
2637
  instruction,
2969
2638
  message: promptMessage,
2970
2639
  jsonSchemaName: "monthly-schedule",
@@ -2995,11 +2664,11 @@ class Brain {
2995
2664
  }
2996
2665
  log7.debug(`sleepMemory: starting, ${history.length} messages`);
2997
2666
  try {
2998
- const dateKey2 = formatDateKey(datetime);
2667
+ const dateKey = formatDateKey(datetime);
2999
2668
  const instruction = await loadPrompt("MEMOIR");
3000
2669
  const historyBlock = translateMessageHistory(this.brainbase.displayName, history);
3001
2670
  const promptMessage = [
3002
- `Date: ${dateKey2}`,
2671
+ `Date: ${dateKey}`,
3003
2672
  `Personality: ${this.brainbase.baseSystemPrompt}`,
3004
2673
  `Conversation log:`,
3005
2674
  historyBlock
@@ -3008,21 +2677,20 @@ class Brain {
3008
2677
  `);
3009
2678
  log7.debug(`sleepMemory: calling identity model`);
3010
2679
  const memoir = await llm.call(llm.models.identity, {
3011
- caller: "sleep-memory",
3012
2680
  instruction,
3013
2681
  message: promptMessage
3014
2682
  });
3015
2683
  log7.debug(`sleepMemory: model returned ${memoir.length} chars`);
3016
2684
  await this.memory.add({
3017
- customId: `daily-journal:${dateKey2}`,
2685
+ customId: `daily-journal:${dateKey}`,
3018
2686
  content: memoir,
3019
2687
  metadata: {
3020
2688
  kind: "daily-journal",
3021
2689
  source: "sleepMemory",
3022
- date: dateKey2
2690
+ date: dateKey
3023
2691
  }
3024
2692
  });
3025
- log7.debug(`sleepMemory: journal persisted for ${dateKey2}`);
2693
+ log7.debug(`sleepMemory: journal persisted for ${dateKey}`);
3026
2694
  return memoir;
3027
2695
  } catch (error) {
3028
2696
  const reason = error instanceof Error ? error.message : String(error);
@@ -3031,22 +2699,22 @@ class Brain {
3031
2699
  }
3032
2700
  }
3033
2701
  async getTodayScheduledAvailability(datetime) {
3034
- const dateKey2 = formatDateKey(datetime);
2702
+ const dateKey = formatDateKey(datetime);
3035
2703
  try {
3036
- const cached = this.availabilityCache.get(dateKey2);
2704
+ const cached = this.availabilityCache.get(dateKey);
3037
2705
  if (cached) {
3038
- log7.debug(`getTodayScheduledAvailability: cache hit for ${dateKey2}`);
2706
+ log7.debug(`getTodayScheduledAvailability: cache hit for ${dateKey}`);
3039
2707
  return cached;
3040
2708
  }
3041
- const stored = await this.memory.get(`daily-schedule:${dateKey2}`);
2709
+ const stored = await this.memory.get(`daily-schedule:${dateKey}`);
3042
2710
  if (!stored) {
3043
- log7.debug(`getTodayScheduledAvailability: no schedule for ${dateKey2}`);
2711
+ log7.debug(`getTodayScheduledAvailability: no schedule for ${dateKey}`);
3044
2712
  return null;
3045
2713
  }
3046
2714
  const dailySchedule = JSON.parse(stored.content);
3047
2715
  const availability = await this.deriveAvailabilityFromSchedule(dailySchedule);
3048
- this.availabilityCache.set(dateKey2, availability);
3049
- log7.debug(`getTodayScheduledAvailability: cached ${availability.items.length} windows for ${dateKey2}`);
2716
+ this.availabilityCache.set(dateKey, availability);
2717
+ log7.debug(`getTodayScheduledAvailability: cached ${availability.items.length} windows for ${dateKey}`);
3050
2718
  return availability;
3051
2719
  } catch (error) {
3052
2720
  const reason = error instanceof Error ? error.message : String(error);
@@ -3057,7 +2725,7 @@ class Brain {
3057
2725
  async getAvailability(datetime = new Date) {
3058
2726
  const h = datetime.getHours();
3059
2727
  const m = datetime.getMinutes();
3060
- const hhmm = `${pad23(h)}:${pad23(m)}`;
2728
+ const hhmm = `${pad22(h)}:${pad22(m)}`;
3061
2729
  const current = h * 60 + m;
3062
2730
  const toMinutes = (s) => {
3063
2731
  const [hh = 0, mm = 0] = s.split(":").map((x) => parseInt(x, 10));
@@ -3070,10 +2738,10 @@ class Brain {
3070
2738
  return result;
3071
2739
  }
3072
2740
  async getCurrentAndAdjacentSlots(now) {
3073
- const dateKey2 = formatDateKey(now);
3074
- const stored = await this.memory.get(`daily-schedule:${dateKey2}`);
2741
+ const dateKey = formatDateKey(now);
2742
+ const stored = await this.memory.get(`daily-schedule:${dateKey}`);
3075
2743
  if (!stored) {
3076
- log7.debug(`getCurrentAndAdjacentSlots: no schedule for ${dateKey2}`);
2744
+ log7.debug(`getCurrentAndAdjacentSlots: no schedule for ${dateKey}`);
3077
2745
  return [];
3078
2746
  }
3079
2747
  let schedule;
@@ -3106,7 +2774,6 @@ class Brain {
3106
2774
  personality: this.brainbase.baseSystemPrompt
3107
2775
  });
3108
2776
  const result = await llm.call(llm.models.identity, {
3109
- caller: "availability",
3110
2777
  instruction,
3111
2778
  message: promptMessage,
3112
2779
  jsonSchemaName: "availability",
@@ -3177,46 +2844,74 @@ class Brain {
3177
2844
  content: userPrompt
3178
2845
  }
3179
2846
  ];
3180
- try {
3181
- await llm.chatWithToolExecution(llm.models.conversation, {
3182
- caller: initiate ? "start-conversation" : "send-message",
3183
- instruction: `${this.brainbase.baseSystemPrompt}
2847
+ for (let step = 0;step < maxSteps; step += 1) {
2848
+ log7.debug(`sendMessage: step ${step + 1}/${maxSteps} → model`);
2849
+ let choice;
2850
+ try {
2851
+ choice = await llm.chatWithTools(llm.models.conversation, {
2852
+ instruction: `${this.brainbase.baseSystemPrompt}
3184
2853
 
3185
2854
  ${instruction}`,
3186
- messages,
3187
- tools,
3188
- maxSteps,
3189
- executeTool: async (call) => {
3190
- if (call.function.name === "addReplyMessage") {
3191
- const content = parseAddReplyMessageArguments(call.function.arguments);
3192
- if (content !== null) {
3193
- log7.debug(`sendMessage: addReplyMessage[${replyMessages.length}] (${content.length} chars)`);
3194
- await send(content);
3195
- replyMessages.push(content);
3196
- return JSON.stringify({
3197
- ok: true,
3198
- index: replyMessages.length - 1
3199
- });
3200
- }
2855
+ messages,
2856
+ tools
2857
+ });
2858
+ } catch (error) {
2859
+ const reason = error instanceof Error ? error.message : String(error);
2860
+ logger.error(`sendMessage: LLM call failed at step ${step}: ${reason}`);
2861
+ return replyMessages;
2862
+ }
2863
+ const assistantMessage = choice.message;
2864
+ const toolCalls = assistantMessage.toolCalls ?? [];
2865
+ const hasContent = typeof assistantMessage.content === "string" && assistantMessage.content.length > 0;
2866
+ log7.debug(`sendMessage: step ${step + 1} → toolCalls=${toolCalls.length} hasContent=${hasContent}`);
2867
+ if (toolCalls.length === 0) {
2868
+ log7.debug(`sendMessage: model returned no tool calls; finalising with ${replyMessages.length} replies`);
2869
+ return replyMessages;
2870
+ }
2871
+ messages.push(stripAssistantForHistory(assistantMessage));
2872
+ for (const call of toolCalls) {
2873
+ if (call.function.name === "addReplyMessage") {
2874
+ const content = parseAddReplyMessageArguments(call.function.arguments);
2875
+ if (content !== null) {
2876
+ log7.debug(`sendMessage: addReplyMessage[${replyMessages.length}] (${content.length} chars)`);
2877
+ send(content);
2878
+ replyMessages.push(content);
2879
+ } else {
3201
2880
  log7.debug(`sendMessage: addReplyMessage rejected (invalid arguments: ${call.function.arguments})`);
3202
- return JSON.stringify({ ok: false, error: "invalid arguments" });
3203
- }
3204
- if (call.function.name === "searchMemory") {
3205
- log7.debug(`sendMessage: searchMemory tool call`);
3206
- return this.executeSearchTool(call.function.arguments);
3207
2881
  }
3208
- log7.debug(`sendMessage: unknown tool "${call.function.name}"`);
3209
- return JSON.stringify({
3210
- ok: false,
3211
- error: `Unknown tool: ${call.function.name}`
2882
+ messages.push({
2883
+ role: "tool",
2884
+ toolCallId: call.id,
2885
+ content: content === null ? JSON.stringify({ ok: false, error: "invalid arguments" }) : JSON.stringify({ ok: true, index: replyMessages.length - 1 })
3212
2886
  });
2887
+ continue;
3213
2888
  }
3214
- });
3215
- } catch (error) {
3216
- const reason = error instanceof Error ? error.message : String(error);
3217
- logger.error(`sendMessage: LLM call failed: ${reason}`);
2889
+ if (call.function.name === "searchMemory") {
2890
+ log7.debug(`sendMessage: searchMemory tool call`);
2891
+ const result = await this.executeSearchTool(call.function.arguments);
2892
+ messages.push({
2893
+ role: "tool",
2894
+ toolCallId: call.id,
2895
+ content: result
2896
+ });
2897
+ continue;
2898
+ }
2899
+ log7.debug(`sendMessage: unknown tool "${call.function.name}"`);
2900
+ messages.push({
2901
+ role: "tool",
2902
+ toolCallId: call.id,
2903
+ content: JSON.stringify({
2904
+ ok: false,
2905
+ error: `Unknown tool: ${call.function.name}`
2906
+ })
2907
+ });
2908
+ }
2909
+ if (!hasContent && toolCalls.every((c) => c.function.name === "searchMemory")) {
2910
+ log7.debug(`sendMessage: step was pure searchMemory, looping back`);
2911
+ continue;
2912
+ }
3218
2913
  }
3219
- log7.debug(`sendMessage: done with ${replyMessages.length} replies`);
2914
+ logger.warn(`sendMessage: reached maxSteps (${maxSteps}) without final reply`);
3220
2915
  return replyMessages;
3221
2916
  }
3222
2917
  async buildMemoryBlock() {
@@ -3225,11 +2920,11 @@ ${instruction}`,
3225
2920
  ${facts || "(none indexed)"}`;
3226
2921
  }
3227
2922
  async buildScheduleBlock(now) {
3228
- const dateKey2 = formatDateKey(now);
2923
+ const dateKey = formatDateKey(now);
3229
2924
  const currentSlots = await this.getCurrentAndAdjacentSlots(now);
3230
2925
  const currentBlock = currentSlots.length > 0 ? `Currently (around ${now.toTimeString().slice(0, 5)}):
3231
2926
  ${currentSlots.map((s) => ` ${s.start}-${s.end} ${s.activity}${s.notes ? ` (${s.notes})` : ""}`).join(`
3232
- `)}` : `Currently (${dateKey2} ${now.toTimeString().slice(0, 5)}): (no matching slot in today's schedule)`;
2927
+ `)}` : `Currently (${dateKey} ${now.toTimeString().slice(0, 5)}): (no matching slot in today's schedule)`;
3233
2928
  const days = [
3234
2929
  {
3235
2930
  label: "Yesterday",
@@ -3252,9 +2947,9 @@ ${currentBlock}
3252
2947
  ${blocks.join(`
3253
2948
  `)}`;
3254
2949
  }
3255
- async getDailyScheduleSummary(dateKey2) {
2950
+ async getDailyScheduleSummary(dateKey) {
3256
2951
  try {
3257
- const stored = await this.memory.get(`daily-schedule:${dateKey2}`);
2952
+ const stored = await this.memory.get(`daily-schedule:${dateKey}`);
3258
2953
  if (!stored)
3259
2954
  return null;
3260
2955
  const schedule = JSON.parse(stored.content);
@@ -3325,7 +3020,6 @@ ${blocks.join(`
3325
3020
  const personaInitInstruction = await loadPrompt("PERSONA_INIT");
3326
3021
  log7.debug(`Brain.create: generating description`);
3327
3022
  const description = await llm.call(llm.models.identity, {
3328
- caller: "persona-init",
3329
3023
  instruction: personaInitInstruction,
3330
3024
  message: seed
3331
3025
  });
@@ -3333,7 +3027,6 @@ ${blocks.join(`
3333
3027
  const personaSystemInstruction = await loadPrompt("PERSONA_BASE_SYSTEM_PROMPT");
3334
3028
  log7.debug(`Brain.create: generating base system prompt + dials`);
3335
3029
  const generated = await llm.call(llm.models.identity, {
3336
- caller: "base-system-prompt",
3337
3030
  instruction: personaSystemInstruction,
3338
3031
  message: description,
3339
3032
  jsonSchemaName: "base-system-prompt",
@@ -3371,12 +3064,9 @@ ${personaSystemFixed}`;
3371
3064
  log7.debug(`Brain.create: brainbase saved (id=${brainId})`);
3372
3065
  return { brainId, brain: new Brain(db, space, brainbase, memory) };
3373
3066
  } catch (error) {
3374
- let reason = error instanceof Error ? `${error.message} (${error.name})` : String(error);
3375
- if (error instanceof BadRequestResponseError) {
3376
- reason = `${reason} ${JSON.stringify(error.body)}`;
3377
- }
3378
- log7.debug(`Brain.create failed: ${reason}`);
3379
- return { error: reason };
3067
+ const reason = error instanceof Error ? error.message : String(error);
3068
+ logger.error(`Failed to create brain "${displayName}": ${reason}`);
3069
+ return null;
3380
3070
  }
3381
3071
  }
3382
3072
  static async delete(brainId) {
@@ -3468,6 +3158,13 @@ function parseSearchArguments(json) {
3468
3158
  }
3469
3159
  return null;
3470
3160
  }
3161
+ function stripAssistantForHistory(message) {
3162
+ return {
3163
+ role: "assistant",
3164
+ content: message.content,
3165
+ toolCalls: message.toolCalls
3166
+ };
3167
+ }
3471
3168
 
3472
3169
  // src/channel/discord.ts
3473
3170
  import {
@@ -3487,18 +3184,10 @@ var SLEEP_MEMORY_CRON_KEY = "__sleep-memory__";
3487
3184
  var SLEEP_MEMORY_CRON_PATTERN = "0 * * * *";
3488
3185
  var START_CONVERSATION_CRON_KEY = "__start-conversation__";
3489
3186
  var START_CONVERSATION_CRON_PATTERN = "*/10 * * * *";
3490
- var SCHEDULE_CRON_KEY = "__schedule__";
3491
- var SCHEDULE_CRON_PATTERN = "0 0 * * *";
3492
- var SCHEDULE_NOON_CRON_KEY = "__schedule-noon__";
3493
- var SCHEDULE_NOON_CRON_PATTERN = "0 12 * * *";
3494
- var DO_ACTIONS = ["generateSchedule", "sleepMemory"];
3495
- var VIEW_THINGS = [
3496
- "daily-schedule",
3497
- "monthly-schedule",
3498
- "sending-queue",
3499
- "deferred-queue",
3500
- "today-availability"
3501
- ];
3187
+ var DAILY_SCHEDULE_CRON_KEY = "__daily-schedule__";
3188
+ var DAILY_SCHEDULE_CRON_PATTERN = "0 0 * * *";
3189
+ var DAILY_SCHEDULE_NOON_CRON_KEY = "__daily-schedule-noon__";
3190
+ var DAILY_SCHEDULE_NOON_CRON_PATTERN = "0 12 * * *";
3502
3191
 
3503
3192
  class BaseChannel {
3504
3193
  brain;
@@ -3518,27 +3207,24 @@ class BaseChannel {
3518
3207
  static activeChannels = new Map;
3519
3208
  constructor(brain) {
3520
3209
  this.brain = brain;
3521
- this.registerCron(SLEEP_MEMORY_CRON_KEY, SLEEP_MEMORY_CRON_PATTERN, () => this.runSleepMemory());
3522
- this.registerCron(SCHEDULE_CRON_KEY, SCHEDULE_CRON_PATTERN, () => this.regenerateSchedules());
3523
- this.registerCron(SCHEDULE_NOON_CRON_KEY, SCHEDULE_NOON_CRON_PATTERN, () => this.regenerateSchedules());
3524
- this.registerCron(START_CONVERSATION_CRON_KEY, START_CONVERSATION_CRON_PATTERN, () => this.runStartConversation());
3525
- }
3526
- async runSleepMemory(force = false) {
3527
- const dateKey2 = formatDateKey(new Date);
3528
- if (!force) {
3210
+ this.registerCron(SLEEP_MEMORY_CRON_KEY, SLEEP_MEMORY_CRON_PATTERN, async () => {
3211
+ const dateKey = formatDateKey(new Date);
3529
3212
  const availability = await this.brain.getAvailability();
3530
3213
  if (availability.status !== "offline") {
3531
3214
  logger.debug(`sleepMemory cron: skip — availability=${availability.status}`);
3532
3215
  return;
3533
3216
  }
3534
- const existing = await this.brain.memory.get(`daily-journal:${dateKey2}`);
3217
+ const existing = await this.brain.memory.get(`daily-journal:${dateKey}`);
3535
3218
  if (existing) {
3536
- logger.debug(`sleepMemory cron: skip — journal for ${dateKey2} exists`);
3219
+ logger.debug(`sleepMemory cron: skip — journal for ${dateKey} exists`);
3537
3220
  return;
3538
3221
  }
3539
- }
3540
- const history = await this.getMessageHistoryBetween(new Date(Date.now() - 24 * 60 * 60 * 1000), new Date);
3541
- await this.brain.sleepMemory(new Date, history);
3222
+ const history = await this.getMessageHistoryBetween(new Date(Date.now() - 24 * 60 * 60 * 1000), new Date);
3223
+ await this.brain.sleepMemory(new Date, history);
3224
+ });
3225
+ this.registerCron(DAILY_SCHEDULE_CRON_KEY, DAILY_SCHEDULE_CRON_PATTERN, () => this.regenerateSchedules());
3226
+ this.registerCron(DAILY_SCHEDULE_NOON_CRON_KEY, DAILY_SCHEDULE_NOON_CRON_PATTERN, () => this.regenerateSchedules());
3227
+ this.registerCron(START_CONVERSATION_CRON_KEY, START_CONVERSATION_CRON_PATTERN, () => this.runStartConversation());
3542
3228
  }
3543
3229
  async regenerateSchedules() {
3544
3230
  logger.debug(`regenerateSchedules: tick for ${this.brain.brainbase.displayName}`);
@@ -3559,8 +3245,8 @@ class BaseChannel {
3559
3245
  return;
3560
3246
  }
3561
3247
  const now = new Date;
3562
- const dateKey2 = formatDateKey(now);
3563
- const count = this.startConversationCounters.get(dateKey2) ?? 0;
3248
+ const dateKey = formatDateKey(now);
3249
+ const count = this.startConversationCounters.get(dateKey) ?? 0;
3564
3250
  const countThreshold = this.brain.brainbase.startConversationCountThreshold;
3565
3251
  if (count >= countThreshold)
3566
3252
  return;
@@ -3573,7 +3259,7 @@ class BaseChannel {
3573
3259
  });
3574
3260
  if (replies.length === 0)
3575
3261
  return;
3576
- this.startConversationCounters.set(dateKey2, count + 1);
3262
+ this.startConversationCounters.set(dateKey, count + 1);
3577
3263
  this.startConversationTimeout = true;
3578
3264
  setTimeout(() => {
3579
3265
  this.startConversationTimeout = false;
@@ -3602,7 +3288,7 @@ class BaseChannel {
3602
3288
  }, callback);
3603
3289
  }
3604
3290
  getRegisteredCrons() {
3605
- return scheduledJobs.filter((c) => c.name && c.name.startsWith(this.resolveCronPrefix()));
3291
+ return scheduledJobs.filter((c) => c.name && c.name.startsWith(this.resolveCronPrefix())).map((c) => c.name);
3606
3292
  }
3607
3293
  pauseCron(key) {
3608
3294
  const job = scheduledJobs.find((c) => c.name === this.resolveCronName(key));
@@ -3693,13 +3379,6 @@ class BaseChannel {
3693
3379
  this.isChattingDebounce = null;
3694
3380
  }, IS_CHATTING_DEBOUNCE_MS);
3695
3381
  }
3696
- async initAvailability() {
3697
- const current = await this.brain.getAvailability();
3698
- this.previousAvailability = current.status;
3699
- logger.debug(`initAvailability: ${current.status}`);
3700
- await this.setAvailability(current.status);
3701
- this.ensureAvailabilityWatcher();
3702
- }
3703
3382
  ensureAvailabilityWatcher() {
3704
3383
  if (this.isCronStarted(AVAILABILITY_WATCHER_KEY))
3705
3384
  return;
@@ -3709,9 +3388,6 @@ class BaseChannel {
3709
3388
  const prev = this.previousAvailability;
3710
3389
  this.previousAvailability = current.status;
3711
3390
  logger.debug(`availabilityWatcher: ${prev ?? "(initial)"} → ${current.status}`);
3712
- if (prev !== current.status) {
3713
- await this.setAvailability(current.status);
3714
- }
3715
3391
  if (prev !== null && prev !== "online" && current.status === "online") {
3716
3392
  await this.flushDeferred();
3717
3393
  }
@@ -3760,88 +3436,6 @@ class BaseChannel {
3760
3436
  static all() {
3761
3437
  return Array.from(BaseChannel.activeChannels.values());
3762
3438
  }
3763
- static forceDo(brainId, action) {
3764
- const channel = BaseChannel.activeChannels.get(brainId);
3765
- if (!channel) {
3766
- return {
3767
- ok: false,
3768
- error: `no active channel for brain "${brainId}" (is it activated and daemon running?)`
3769
- };
3770
- }
3771
- const displayName = channel.brain.brainbase.displayName;
3772
- logger.info(`do ${action}: queued for "${displayName}" (${brainId})`);
3773
- (async () => {
3774
- try {
3775
- if (action === "generateSchedule") {
3776
- await channel.regenerateSchedules();
3777
- } else {
3778
- await channel.runSleepMemory(true);
3779
- }
3780
- logger.success(`do ${action}: done for "${displayName}" (${brainId})`);
3781
- } catch (error) {
3782
- const reason = error instanceof Error ? error.message : String(error);
3783
- logger.error(`do ${action}: failed for "${displayName}" (${brainId}): ${reason}`);
3784
- }
3785
- })();
3786
- return { ok: true, displayName };
3787
- }
3788
- static async view(brainId, thing) {
3789
- const channel = BaseChannel.activeChannels.get(brainId);
3790
- if (!channel) {
3791
- return {
3792
- ok: false,
3793
- error: `no active channel for brain "${brainId}" (is it activated and daemon running?)`
3794
- };
3795
- }
3796
- const displayName = channel.brain.brainbase.displayName;
3797
- logger.debug(`view ${thing}: "${displayName}" (${brainId})`);
3798
- return {
3799
- ok: true,
3800
- displayName,
3801
- value: await channel.readView(thing)
3802
- };
3803
- }
3804
- async readView(thing) {
3805
- const now = new Date;
3806
- switch (thing) {
3807
- case "daily-schedule": {
3808
- const key = formatDateKey(now);
3809
- const stored = await this.brain.memory.get(`daily-schedule:${key}`);
3810
- if (!stored)
3811
- return null;
3812
- try {
3813
- return { key, schedule: JSON.parse(stored.content) };
3814
- } catch {
3815
- return { key, raw: stored.content };
3816
- }
3817
- }
3818
- case "monthly-schedule": {
3819
- const key = formatMonthKey(now);
3820
- const stored = await this.brain.memory.get(`monthly-schedule:${key}`);
3821
- if (!stored)
3822
- return null;
3823
- try {
3824
- return { key, schedule: JSON.parse(stored.content) };
3825
- } catch {
3826
- return { key, raw: stored.content };
3827
- }
3828
- }
3829
- case "sending-queue":
3830
- return this.isSendingQueue.map((m) => ({
3831
- sender: m.sender,
3832
- time: m.time.toISOString(),
3833
- content: m.content
3834
- }));
3835
- case "deferred-queue":
3836
- return this.deferredQueue.map((m) => ({
3837
- sender: m.sender,
3838
- time: m.time.toISOString(),
3839
- content: m.content
3840
- }));
3841
- case "today-availability":
3842
- return await this.brain.getTodayScheduledAvailability(now);
3843
- }
3844
- }
3845
3439
  static async shutdownAll() {
3846
3440
  await Promise.all(BaseChannel.all().map((c) => c.shutdown()));
3847
3441
  }
@@ -3854,8 +3448,8 @@ class BaseChannel {
3854
3448
  logger.debug(`shutdown: done`);
3855
3449
  }
3856
3450
  stopOwnCrons() {
3857
- for (const cron of this.getRegisteredCrons()) {
3858
- cron.stop();
3451
+ for (const key of this.getRegisteredCrons()) {
3452
+ this.removeCron(key);
3859
3453
  }
3860
3454
  }
3861
3455
  clearTimers() {
@@ -3965,7 +3559,6 @@ class DiscordChannel extends BaseChannel {
3965
3559
  logger.debug(`DiscordClientReady: resolving configured channel ${channelId}`);
3966
3560
  this.resolveConfiguredChannel(channelId);
3967
3561
  }
3968
- this.initAvailability();
3969
3562
  });
3970
3563
  this.client.on(Events.MessageCreate, (msg) => {
3971
3564
  if (msg.author.bot)
@@ -4134,7 +3727,6 @@ class TelegramChannel extends BaseChannel {
4134
3727
  this.registerActive();
4135
3728
  this.bot.onStart(({ info }) => {
4136
3729
  logger.success(`Telegram ready as @${info.username}`);
4137
- this.initAvailability();
4138
3730
  });
4139
3731
  this.bot.on("message", (ctx) => {
4140
3732
  if (ctx.from?.isBot())
@@ -4227,8 +3819,8 @@ class TelegramChannel extends BaseChannel {
4227
3819
 
4228
3820
  // src/utils/daemonClient.ts
4229
3821
  import { connect } from "node:net";
4230
- import { join as join4 } from "node:path";
4231
- var DAEMON_SOCKET_PATH = join4(config.brainboxRoot, "daemon.sock");
3822
+ import { join as join3 } from "node:path";
3823
+ var DAEMON_SOCKET_PATH = join3(config.brainboxRoot, "daemon.sock");
4232
3824
  async function sendToDaemon(payload) {
4233
3825
  logger.debug(`sendToDaemon: command="${payload.command}" args=${JSON.stringify(payload.args) ?? "null"}`);
4234
3826
  let reply;
@@ -4290,7 +3882,6 @@ function exchangeOnce(payload) {
4290
3882
  // src/commands/daemon.ts
4291
3883
  import { createServer } from "node:net";
4292
3884
  import { chmodSync, unlinkSync } from "node:fs";
4293
- import { join as join5 } from "node:path";
4294
3885
 
4295
3886
  // src/commands/daemon/commands.ts
4296
3887
  var log8 = logger.child("daemon-cmd");
@@ -4351,63 +3942,6 @@ defineCommand({
4351
3942
  }
4352
3943
  });
4353
3944
 
4354
- // src/commands/daemon/doCommand.ts
4355
- defineCommand({
4356
- name: "do",
4357
- handler: async (args) => {
4358
- const action = args?.action;
4359
- const brainId = args?.brainId;
4360
- logger.debug(`do handler: action="${action}" brainId="${brainId}"`);
4361
- if (typeof action !== "string" || !DO_ACTIONS.includes(action)) {
4362
- return {
4363
- ok: false,
4364
- error: `invalid action (expected one of: ${DO_ACTIONS.join(", ")})`
4365
- };
4366
- }
4367
- if (typeof brainId !== "string" || brainId.trim().length === 0) {
4368
- return { ok: false, error: "missing brainId" };
4369
- }
4370
- const result = BaseChannel.forceDo(brainId.trim(), action);
4371
- if (!result.ok)
4372
- return result;
4373
- return {
4374
- ok: true,
4375
- result: { action, brainId: brainId.trim(), displayName: result.displayName }
4376
- };
4377
- }
4378
- });
4379
-
4380
- // src/commands/daemon/viewCommand.ts
4381
- defineCommand({
4382
- name: "view",
4383
- handler: async (args) => {
4384
- const thing = args?.thing;
4385
- const brainId = args?.brainId;
4386
- logger.debug(`view handler: thing="${thing}" brainId="${brainId}"`);
4387
- if (typeof thing !== "string" || !VIEW_THINGS.includes(thing)) {
4388
- return {
4389
- ok: false,
4390
- error: `invalid thing (expected one of: ${VIEW_THINGS.join(", ")})`
4391
- };
4392
- }
4393
- if (typeof brainId !== "string" || brainId.trim().length === 0) {
4394
- return { ok: false, error: "missing brainId" };
4395
- }
4396
- const result = await BaseChannel.view(brainId.trim(), thing);
4397
- if (!result.ok)
4398
- return result;
4399
- return {
4400
- ok: true,
4401
- result: {
4402
- thing,
4403
- brainId: brainId.trim(),
4404
- displayName: result.displayName,
4405
- value: result.value
4406
- }
4407
- };
4408
- }
4409
- });
4410
-
4411
3945
  // src/commands/daemon.ts
4412
3946
  async function startChannels() {
4413
3947
  const items = await brainManager.listAvailableBrain();
@@ -4442,10 +3976,7 @@ async function startChannels() {
4442
3976
  return started;
4443
3977
  }
4444
3978
  async function daemon() {
4445
- const logDir = join5(config.brainboxRoot, "logs");
4446
- logger.configure({ logDir });
4447
- configureLlmLog(config.debug ? join5(logDir, "llm") : undefined);
4448
- logger.debug(`daemon: boot (debug=${config.debug}, logDir=${logDir}, llmLog=${config.debug})`);
3979
+ logger.debug(`daemon: boot`);
4449
3980
  const started = await startChannels();
4450
3981
  if (started === 0) {
4451
3982
  logger.info("No activated brains with channels. Daemon idling.");
@@ -4585,8 +4116,7 @@ async function deactivateBrain(brainId) {
4585
4116
  async function createBrain(displayName, seed, options) {
4586
4117
  logger.debug(`createBrain: name="${displayName}" seed length=${seed.length} schedule=${options.schedule}`);
4587
4118
  const result = await Brain.create(displayName, seed);
4588
- if ("error" in result) {
4589
- logger.error(`Failed to create brain "${displayName}": ${result.error}`);
4119
+ if (!result) {
4590
4120
  process.exitCode = 1;
4591
4121
  return;
4592
4122
  }
@@ -4612,33 +4142,6 @@ async function removeBrain(brainId) {
4612
4142
  }
4613
4143
  logger.success(`Removed brain "${brain.displayName}" (${chalk2.cyan(brainId)})`);
4614
4144
  }
4615
- async function doAction(action, brainId) {
4616
- if (!DO_ACTIONS.includes(action)) {
4617
- logger.error(`Unknown action "${action}". Expected one of: ${DO_ACTIONS.join(", ")}`);
4618
- process.exit(1);
4619
- }
4620
- logger.debug(`do: action=${action} brainId=${brainId}`);
4621
- const response = await sendToDaemon({
4622
- command: "do",
4623
- args: { action, brainId }
4624
- });
4625
- const name = response.result?.displayName ?? brainId;
4626
- logger.success(`Successfully sent ${action} for "${name}" (${brainId}).`);
4627
- }
4628
- async function viewThing(thing, brainId) {
4629
- if (!VIEW_THINGS.includes(thing)) {
4630
- logger.error(`Unknown thing "${thing}". Expected one of: ${VIEW_THINGS.join(", ")}`);
4631
- process.exit(1);
4632
- }
4633
- logger.debug(`view: thing=${thing} brainId=${brainId}`);
4634
- const response = await sendToDaemon({
4635
- command: "view",
4636
- args: { thing, brainId }
4637
- });
4638
- const name = response.result?.displayName ?? brainId;
4639
- logger.info(`${thing} — "${name}" (${brainId})`);
4640
- console.log(JSON.stringify(response.result?.value ?? null, null, 2));
4641
- }
4642
4145
  function register3(program) {
4643
4146
  const cmd = registerCommand(program, {
4644
4147
  name: "brain",
@@ -4649,8 +4152,6 @@ function register3(program) {
4649
4152
  cmd.command("remove <brainId>").description("Remove a brain and its memory").action(removeBrain);
4650
4153
  cmd.command("activate <brainId>").description("Activate a brain").action(activateBrain);
4651
4154
  cmd.command("deactivate <brainId>").description("Deactivate a brain").action(deactivateBrain);
4652
- cmd.command("do <action> <brainId>").description(`Force-run a daemon job (${DO_ACTIONS.join(" | ")}) for a live brain`).action(doAction);
4653
- cmd.command("view <thing> <brainId>").description(`Inspect a live brain value (${VIEW_THINGS.join(" | ")})`).action(viewThing);
4654
4155
  return cmd;
4655
4156
  }
4656
4157
 
@@ -4772,9 +4273,6 @@ function RawInput({
4772
4273
  onSubmit
4773
4274
  }) {
4774
4275
  const [value, setValue] = useState(initialValue);
4775
- useEffect(() => {
4776
- setValue(initialValue);
4777
- }, [prompt, initialValue]);
4778
4276
  useInput((input, key) => {
4779
4277
  if (key.return) {
4780
4278
  onSubmit(value);
@@ -5470,19 +4968,6 @@ function Select(props) {
5470
4968
 
5471
4969
  // src/commands/onboard.tsx
5472
4970
  import { jsxDEV as jsxDEV5 } from "react/jsx-dev-runtime";
5473
- function ok(msg) {
5474
- console.log(chalk3.green(`✔ ${msg}`));
5475
- }
5476
- function info(msg) {
5477
- console.log(msg);
5478
- }
5479
- function show(active, node, status) {
5480
- active.current.unmount();
5481
- console.clear();
5482
- if (status)
5483
- ok(status);
5484
- active.current = render3(node);
5485
- }
5486
4971
  function ProviderApp({
5487
4972
  providers,
5488
4973
  onDone
@@ -5554,6 +5039,7 @@ function ProviderApp({
5554
5039
  const extras = PROVIDER_EXTRA_FIELDS[stage.provider] ?? [];
5555
5040
  if (extras.length === 0) {
5556
5041
  setProviderAuth(stage.provider, { apiKey });
5042
+ logger.success(`Saved ${stage.provider} to auth.yaml`);
5557
5043
  onDone({ provider: stage.provider });
5558
5044
  return;
5559
5045
  }
@@ -5574,6 +5060,13 @@ function ProviderApp({
5574
5060
  }, undefined, true, undefined, this);
5575
5061
  }
5576
5062
  const nextField = stage.fields[0];
5063
+ if (!nextField) {
5064
+ setProviderAuth(stage.provider, stage.values);
5065
+ logger.success(`Saved ${stage.provider} to auth.yaml`);
5066
+ return /* @__PURE__ */ jsxDEV5(Text5, {
5067
+ children: "Continuing…"
5068
+ }, undefined, false, undefined, this);
5069
+ }
5577
5070
  return /* @__PURE__ */ jsxDEV5(Box4, {
5578
5071
  flexDirection: "column",
5579
5072
  children: [
@@ -5599,11 +5092,6 @@ function ProviderApp({
5599
5092
  const values = { ...stage.values };
5600
5093
  if (value)
5601
5094
  values[nextField] = value;
5602
- if (remaining.length === 0) {
5603
- setProviderAuth(stage.provider, values);
5604
- onDone({ provider: stage.provider });
5605
- return;
5606
- }
5607
5095
  setStage({
5608
5096
  kind: "extras",
5609
5097
  provider: stage.provider,
@@ -5632,7 +5120,7 @@ function ModelApp2({
5632
5120
  /* @__PURE__ */ jsxDEV5(Text5, {
5633
5121
  dimColor: true,
5634
5122
  children: [
5635
- "model name, or ",
5123
+ "e.g. ",
5636
5124
  /* @__PURE__ */ jsxDEV5(Text5, {
5637
5125
  color: "cyan",
5638
5126
  children: [
@@ -5640,7 +5128,7 @@ function ModelApp2({
5640
5128
  "/"
5641
5129
  ]
5642
5130
  }, undefined, true, undefined, this),
5643
- "model — fine-tune later with ",
5131
+ "model-name — fine-tune later with ",
5644
5132
  /* @__PURE__ */ jsxDEV5(Text5, {
5645
5133
  color: "cyan",
5646
5134
  children: "brainbox model"
@@ -5655,15 +5143,14 @@ function ModelApp2({
5655
5143
  setError("Model cannot be empty");
5656
5144
  return;
5657
5145
  }
5658
- const prefix = `${provider}/`;
5659
- const full = value.startsWith(prefix) ? value : `${prefix}${value}`;
5660
- if (full === prefix) {
5661
- setError("Model cannot be empty");
5146
+ if (!value.startsWith(`${provider}/`)) {
5147
+ setError(`Must start with "${provider}/"`);
5662
5148
  return;
5663
5149
  }
5664
- setModelSlot("identity", full);
5665
- setModelSlot("conversation", full);
5666
- onDone(full);
5150
+ setModelSlot("identity", value);
5151
+ setModelSlot("conversation", value);
5152
+ logger.success(`Set identity + conversation model to ${value}`);
5153
+ onDone();
5667
5154
  }
5668
5155
  }, undefined, false, undefined, this),
5669
5156
  error && /* @__PURE__ */ jsxDEV5(Text5, {
@@ -5699,6 +5186,7 @@ function SuperMemoryApp({
5699
5186
  return;
5700
5187
  }
5701
5188
  setSupermemoryKey(key);
5189
+ logger.success("Saved supermemory key to brainbox.yaml");
5702
5190
  onDone();
5703
5191
  }
5704
5192
  }, undefined, false, undefined, this),
@@ -5714,7 +5202,6 @@ function BrainApp({
5714
5202
  }) {
5715
5203
  const [stage, setStage] = useState5({ kind: "name" });
5716
5204
  const [error, setError] = useState5(null);
5717
- const [busy, setBusy] = useState5(false);
5718
5205
  if (stage.kind === "name") {
5719
5206
  return /* @__PURE__ */ jsxDEV5(Box4, {
5720
5207
  flexDirection: "column",
@@ -5766,14 +5253,12 @@ function BrainApp({
5766
5253
  dimColor: true,
5767
5254
  children: "One sentence about who they are. The model will expand it."
5768
5255
  }, undefined, false, undefined, this),
5769
- busy ? /* @__PURE__ */ jsxDEV5(Text5, {
5770
- dimColor: true,
5771
- children: "Creating brain… (This can take few minutes depending on the model's response speed)"
5772
- }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV5(TextInput, {
5256
+ /* @__PURE__ */ jsxDEV5(TextInput, {
5773
5257
  prompt: "seed> ",
5774
- onSubmit: (raw) => {
5258
+ onSubmit: async (raw) => {
5775
5259
  const seed = raw.trim();
5776
5260
  if (seed === "skip") {
5261
+ logger.info("Skipped brain creation.");
5777
5262
  onDone({ brainId: "", displayName: stage.displayName });
5778
5263
  return;
5779
5264
  }
@@ -5781,19 +5266,13 @@ function BrainApp({
5781
5266
  setError("Seed cannot be empty (or type 'skip')");
5782
5267
  return;
5783
5268
  }
5784
- setBusy(true);
5785
- setError(null);
5786
- Brain.create(stage.displayName, seed).then((result) => {
5787
- setBusy(false);
5788
- if ("error" in result) {
5789
- setError(`Brain creation failed: ${result.error} (fix seed, or type 'skip')`);
5790
- return;
5791
- }
5792
- onDone({
5793
- brainId: result.brainId,
5794
- displayName: stage.displayName
5795
- });
5796
- });
5269
+ const result = await Brain.create(stage.displayName, seed);
5270
+ if (!result) {
5271
+ setError("Brain creation failed (check logs above, or type 'skip')");
5272
+ return;
5273
+ }
5274
+ logger.success(`Created brain "${stage.displayName}" (${chalk3.cyan(result.brainId)})`);
5275
+ onDone({ brainId: result.brainId, displayName: stage.displayName });
5797
5276
  }
5798
5277
  }, undefined, false, undefined, this),
5799
5278
  error && /* @__PURE__ */ jsxDEV5(Text5, {
@@ -5834,7 +5313,8 @@ function ChannelApp({
5834
5313
  items: ["discord", "telegram", "skip"],
5835
5314
  onSelect: (v) => {
5836
5315
  if (v === "skip") {
5837
- onDone("Skipped channel setup.");
5316
+ logger.info("Skipped channel setup.");
5317
+ onDone();
5838
5318
  return;
5839
5319
  }
5840
5320
  setError(null);
@@ -5893,41 +5373,37 @@ function ChannelApp({
5893
5373
  }, undefined, true, undefined, this),
5894
5374
  /* @__PURE__ */ jsxDEV5(TextInput, {
5895
5375
  prompt: `${stage.kind_ === "discord" ? "channelId" : "chatId"}> `,
5896
- onSubmit: (raw) => {
5376
+ onSubmit: async (raw) => {
5897
5377
  const target = raw.trim();
5898
- (async () => {
5899
- const existing = await brainManager.loadBrain(brainId);
5900
- if (!existing) {
5901
- setError(`Brain ${brainId} no longer exists`);
5378
+ const existing = await brainManager.loadBrain(brainId);
5379
+ if (!existing) {
5380
+ setError(`Brain ${brainId} no longer exists`);
5381
+ return;
5382
+ }
5383
+ let updated;
5384
+ if (stage.kind_ === "discord") {
5385
+ updated = {
5386
+ ...existing,
5387
+ channel: "discord",
5388
+ discord: { token: stage.token, channelId: target || undefined },
5389
+ activated: true
5390
+ };
5391
+ } else {
5392
+ const chatId = target ? Number(target) : undefined;
5393
+ if (target && Number.isNaN(chatId)) {
5394
+ setError("chatId must be a number");
5902
5395
  return;
5903
5396
  }
5904
- let updated;
5905
- if (stage.kind_ === "discord") {
5906
- updated = {
5907
- ...existing,
5908
- channel: "discord",
5909
- discord: {
5910
- token: stage.token,
5911
- channelId: target || undefined
5912
- },
5913
- activated: true
5914
- };
5915
- } else {
5916
- const chatId = target ? Number(target) : undefined;
5917
- if (target && Number.isNaN(chatId)) {
5918
- setError("chatId must be a number");
5919
- return;
5920
- }
5921
- updated = {
5922
- ...existing,
5923
- channel: "telegram",
5924
- telegram: { token: stage.token, chatId },
5925
- activated: true
5926
- };
5927
- }
5928
- await brainManager.saveBrain(brainId, updated);
5929
- onDone(`Bound ${displayName} → ${stage.kind_}${target ? ` (${target})` : " (pairing mode)"}`);
5930
- })();
5397
+ updated = {
5398
+ ...existing,
5399
+ channel: "telegram",
5400
+ telegram: { token: stage.token, chatId },
5401
+ activated: true
5402
+ };
5403
+ }
5404
+ await brainManager.saveBrain(brainId, updated);
5405
+ logger.success(`Bound ${displayName} → ${stage.kind_}${target ? ` (${target})` : " (pairing mode)"}`);
5406
+ onDone();
5931
5407
  }
5932
5408
  }, undefined, false, undefined, this),
5933
5409
  error && /* @__PURE__ */ jsxDEV5(Text5, {
@@ -5938,52 +5414,46 @@ function ChannelApp({
5938
5414
  }, undefined, true, undefined, this);
5939
5415
  }
5940
5416
  async function runOnboard() {
5941
- console.clear();
5942
- info(`Welcome — let's get ${chalk3.bold("brainbox")} ready.`);
5417
+ logger.info(`Welcome — let's get ${chalk3.bold("brainbox")} ready.`);
5943
5418
  const providers = listProviderNames().slice().sort();
5944
5419
  const { promise, resolve: resolve2 } = Promise.withResolvers();
5945
- const active = { current: null };
5946
- active.current = render3(/* @__PURE__ */ jsxDEV5(ProviderApp, {
5420
+ let active = render3(/* @__PURE__ */ jsxDEV5(ProviderApp, {
5947
5421
  providers,
5948
5422
  onDone: (p) => {
5949
- show(active, /* @__PURE__ */ jsxDEV5(ModelApp2, {
5423
+ active.unmount();
5424
+ active = render3(/* @__PURE__ */ jsxDEV5(ModelApp2, {
5950
5425
  provider: p.provider,
5951
- onDone: (model) => {
5952
- show(active, /* @__PURE__ */ jsxDEV5(SuperMemoryApp, {
5426
+ onDone: () => {
5427
+ active.unmount();
5428
+ active = render3(/* @__PURE__ */ jsxDEV5(SuperMemoryApp, {
5953
5429
  onDone: () => {
5954
- show(active, /* @__PURE__ */ jsxDEV5(BrainApp, {
5430
+ active.unmount();
5431
+ active = render3(/* @__PURE__ */ jsxDEV5(BrainApp, {
5955
5432
  onDone: (b) => {
5433
+ active.unmount();
5956
5434
  if (!b.brainId) {
5957
- active.current.unmount();
5958
- console.clear();
5959
- info("Skipped brain creation.");
5960
5435
  resolve2();
5961
5436
  return;
5962
5437
  }
5963
- show(active, /* @__PURE__ */ jsxDEV5(ChannelApp, {
5438
+ active = render3(/* @__PURE__ */ jsxDEV5(ChannelApp, {
5964
5439
  brainId: b.brainId,
5965
5440
  displayName: b.displayName,
5966
- onDone: (status) => {
5967
- active.current.unmount();
5968
- console.clear();
5969
- if (status.startsWith("Skipped"))
5970
- info(status);
5971
- else
5972
- ok(status);
5441
+ onDone: () => {
5442
+ active.unmount();
5973
5443
  resolve2();
5974
5444
  }
5975
- }, undefined, false, undefined, this), `Created brain "${b.displayName}" (${chalk3.cyan(b.brainId)})`);
5445
+ }, undefined, false, undefined, this));
5976
5446
  }
5977
- }, undefined, false, undefined, this), "Saved supermemory key to brainbox.yaml");
5447
+ }, undefined, false, undefined, this));
5978
5448
  }
5979
- }, undefined, false, undefined, this), `Set identity + conversation model to ${model}`);
5449
+ }, undefined, false, undefined, this));
5980
5450
  }
5981
- }, undefined, false, undefined, this), `Saved ${p.provider} to auth.yaml`);
5451
+ }, undefined, false, undefined, this));
5982
5452
  }
5983
5453
  }, undefined, false, undefined, this));
5984
5454
  await promise;
5985
- ok("Onboarding complete.");
5986
- info(`Run ${chalk3.cyan("brainbox daemon")} to bring it online.`);
5455
+ logger.success("Onboarding complete.");
5456
+ logger.info(`Run ${chalk3.cyan("brainbox daemon")} to bring it online.`);
5987
5457
  }
5988
5458
  function register8(program) {
5989
5459
  return registerCommand(program, {
@@ -5995,12 +5465,12 @@ function register8(program) {
5995
5465
 
5996
5466
  // src/index.ts
5997
5467
  var __filename3 = fileURLToPath2(import.meta.url);
5998
- var __dirname3 = dirname3(__filename3);
5468
+ var __dirname3 = dirname4(__filename3);
5999
5469
  logger.configure({ level: config.debug ? "debug" : "info" });
6000
5470
  logger.debug(`brainbox starting (debug=${config.debug}, root=${config.brainboxRoot})`);
6001
5471
  function getVersion() {
6002
5472
  try {
6003
- const pkgPath = join6(__dirname3, "..", "package.json");
5473
+ const pkgPath = join4(__dirname3, "..", "package.json");
6004
5474
  const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
6005
5475
  return pkg.version ?? "0.0.0";
6006
5476
  } catch {