@p-sw/brainbox 0.1.2-alpha.12 → 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 +432 -962
  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)
@@ -663,19 +509,6 @@ class LLMExecutor {
663
509
  };
664
510
  }
665
511
  }
666
- function resolveLlmCaller(options, fallback = "llm") {
667
- return options.caller ?? options.jsonSchemaName ?? fallback;
668
- }
669
- function logLlmWire(caller, request, response) {
670
- if (!isLlmLogEnabled())
671
- return;
672
- writeLlmExchange(caller, `======== REQUEST ========
673
- ${request}
674
-
675
- ======== RESPONSE ========
676
- ${response}
677
- `);
678
- }
679
512
  function listProviderNames() {
680
513
  return LLMExecutor.listProviderNames();
681
514
  }
@@ -713,7 +546,7 @@ function fromOrChoice(choice) {
713
546
  }));
714
547
  return {
715
548
  message: {
716
- content: typeof msg.content === "string" ? stripThinkTags(msg.content) : undefined,
549
+ content: typeof msg.content === "string" ? msg.content : undefined,
717
550
  toolCalls
718
551
  }
719
552
  };
@@ -744,54 +577,53 @@ class OpenRouterExecutor extends LLMExecutor {
744
577
  async call(model, options) {
745
578
  const jsonMode = "jsonSchemaName" in options;
746
579
  log3.debug(`call: model=${model} jsonSchema=${jsonMode ? options.jsonSchemaName : "-"} msgLen=${options.message.length}`);
747
- const chatRequest = {
748
- model,
749
- messages: [
750
- { role: "system", content: options.instruction },
751
- { role: "user", content: options.message }
752
- ],
753
- reasoning: {
754
- effort: options.reasoningEffort ?? (model === this.models.identity ? REASONING_EFFORT_MAP.medium : REASONING_EFFORT_MAP.none)
755
- },
756
- responseFormat: jsonMode ? {
757
- type: "json_schema",
758
- jsonSchema: {
759
- name: options.jsonSchemaName,
760
- schema: options.jsonSchema,
761
- strict: true
762
- }
763
- } : { type: "text" },
764
- stream: false
765
- };
766
- const result = await this.client.chat.send({ chatRequest });
767
- logLlmWire(resolveLlmCaller(options), JSON.stringify(chatRequest), JSON.stringify(result));
768
- const raw = result.choices[0]?.message?.content;
769
- 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;
770
602
  if (!content) {
771
603
  log3.debug(`call: empty content in choice 0`);
772
604
  throw new Error("Empty response from model");
773
605
  }
774
606
  log3.debug(`call: response ${content.length} chars`);
775
- return jsonMode ? parseModelJson(content) : content;
607
+ return jsonMode ? JSON.parse(content) : content;
776
608
  }
777
609
  async chatWithTools(model, options) {
778
610
  log3.debug(`chatWithTools: model=${model} msgs=${options.messages.length} tools=${options.tools.length}`);
779
- const chatRequest = {
780
- model,
781
- messages: [
782
- { role: "system", content: options.instruction },
783
- ...options.messages.map(toOrMessage)
784
- ],
785
- reasoning: {
786
- effort: options.reasoningEffort ?? (model === this.models.identity ? REASONING_EFFORT_MAP.medium : REASONING_EFFORT_MAP.none)
787
- },
788
- responseFormat: { type: "text" },
789
- tools: options.tools.map(toOrTool),
790
- parallelToolCalls: options.parallelToolCalls ?? false,
791
- stream: false
792
- };
793
- const result = await this.client.chat.send({ chatRequest });
794
- 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
+ });
795
627
  const choice = result.choices[0];
796
628
  if (!choice) {
797
629
  log3.debug(`chatWithTools: no choice in response`);
@@ -831,7 +663,7 @@ function fromChoice(choice) {
831
663
  }));
832
664
  return {
833
665
  message: {
834
- content: typeof msg.content === "string" ? stripThinkTags(msg.content) : undefined,
666
+ content: typeof msg.content === "string" ? msg.content : undefined,
835
667
  toolCalls
836
668
  }
837
669
  };
@@ -866,7 +698,6 @@ class OpenAICompatibleExecutor extends LLMExecutor {
866
698
  chatPath;
867
699
  noBearerPrefix;
868
700
  reasoningEffortInQuery;
869
- supportsResponseFormat;
870
701
  constructor(opts) {
871
702
  super();
872
703
  this.providerName = opts.providerName;
@@ -876,7 +707,6 @@ class OpenAICompatibleExecutor extends LLMExecutor {
876
707
  this.chatPath = opts.chatPath ?? "/chat/completions";
877
708
  this.noBearerPrefix = opts.noBearerPrefix ?? false;
878
709
  this.reasoningEffortInQuery = opts.reasoningEffortInQuery ?? false;
879
- this.supportsResponseFormat = opts.supportsResponseFormat ?? true;
880
710
  this.models = {
881
711
  conversation: opts.conversationModel,
882
712
  identity: opts.identityModel
@@ -907,7 +737,7 @@ class OpenAICompatibleExecutor extends LLMExecutor {
907
737
  }
908
738
  return url.toString();
909
739
  }
910
- async sendRequest(body, reasoningEffort, caller) {
740
+ async sendRequest(body, reasoningEffort) {
911
741
  const modelName = body["model"];
912
742
  const modelStr = typeof modelName === "string" ? modelName : "";
913
743
  const url = this.buildRequestUrl(modelStr, reasoningEffort);
@@ -917,40 +747,24 @@ class OpenAICompatibleExecutor extends LLMExecutor {
917
747
  ...authHeader,
918
748
  ...this.defaultHeaders
919
749
  };
920
- const requestRaw = JSON.stringify(body);
921
750
  const res = await fetch(url, {
922
751
  method: "POST",
923
752
  headers,
924
- body: requestRaw
753
+ body: JSON.stringify(body)
925
754
  });
926
- const responseRaw = await res.text().catch(() => "");
927
- logLlmWire(caller, requestRaw, responseRaw);
928
755
  if (!res.ok) {
929
- 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)}`);
930
758
  throw new Error(`${this.providerName} request failed: ${res.status} ${res.statusText}`);
931
759
  }
932
- let data;
933
- try {
934
- data = JSON.parse(responseRaw);
935
- } catch {
936
- throw new Error(`${this.providerName}: invalid JSON response`);
937
- }
760
+ const data = await res.json();
938
761
  if (data.error) {
939
762
  log4.error(`${this.providerName}: API error ${data.error.type ?? ""} ${data.error.message ?? ""}`);
940
763
  throw new Error(`${this.providerName} API error: ${data.error.message ?? "unknown"}`);
941
764
  }
942
- const baseCode = data.base_resp?.status_code;
943
- if (typeof baseCode === "number" && baseCode !== 0) {
944
- const msg = data.base_resp?.status_msg?.trim() || `status_code ${baseCode}`;
945
- log4.error(`${this.providerName}: base_resp ${baseCode} ${msg}`);
946
- throw new Error(`${this.providerName} API error: ${msg}`);
947
- }
948
765
  return data;
949
766
  }
950
767
  async call(model, options) {
951
- if ("jsonSchemaName" in options && !this.supportsResponseFormat) {
952
- return this.callJsonViaTool(model, options);
953
- }
954
768
  const jsonMode = "jsonSchemaName" in options;
955
769
  const reasoning = defaultReasoningEffort(options.reasoningEffort, model, this.models.identity);
956
770
  log4.debug(`call: provider=${this.providerName} model=${model} jsonSchema=${jsonMode ? options.jsonSchemaName : "-"} msgLen=${options.message.length}`);
@@ -963,31 +777,14 @@ class OpenAICompatibleExecutor extends LLMExecutor {
963
777
  responseFormat: jsonMode ? buildResponseFormat(options.jsonSchemaName, options.jsonSchema) : undefined,
964
778
  reasoningEffort: reasoning
965
779
  });
966
- const data = await this.sendRequest(body, options.reasoningEffort, resolveLlmCaller(options));
967
- const choice = data.choices?.[0];
968
- const raw = choice?.message?.content;
969
- 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;
970
782
  if (!content) {
971
- const finish = choice?.finish_reason ?? "no-choice";
972
- const reasoningLen = typeof choice?.message?.reasoning_content === "string" ? choice.message.reasoning_content.length : 0;
973
- log4.debug(`call: empty content in choice 0 finish_reason=${finish} reasoning_len=${reasoningLen} rawType=${raw === null ? "null" : typeof raw}`);
974
- 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");
975
785
  }
976
786
  log4.debug(`call: response ${content.length} chars`);
977
- return jsonMode ? parseModelJson(content) : content;
978
- }
979
- async callJsonViaTool(model, options) {
980
- const { toolName, tool, instruction } = buildStructuredJsonRequest(options);
981
- log4.debug(`callJsonViaTool: provider=${this.providerName} model=${model} tool=${toolName}`);
982
- const choice = await this.chatWithTools(model, {
983
- caller: options.caller ?? options.jsonSchemaName,
984
- instruction,
985
- messages: [{ role: "user", content: options.message }],
986
- tools: [tool],
987
- reasoningEffort: "none",
988
- parallelToolCalls: false
989
- });
990
- return parseStructuredJsonResult(choice, toolName);
787
+ return jsonMode ? JSON.parse(content) : content;
991
788
  }
992
789
  async chatWithTools(model, options) {
993
790
  const reasoning = defaultReasoningEffort(options.reasoningEffort, model, this.models.identity);
@@ -1002,7 +799,7 @@ class OpenAICompatibleExecutor extends LLMExecutor {
1002
799
  parallelToolCalls: options.parallelToolCalls ?? false,
1003
800
  reasoningEffort: reasoning
1004
801
  });
1005
- const data = await this.sendRequest(body, options.reasoningEffort, resolveLlmCaller(options));
802
+ const data = await this.sendRequest(body, options.reasoningEffort);
1006
803
  const choice = data.choices?.[0];
1007
804
  if (!choice) {
1008
805
  log4.debug(`chatWithTools: no choice in response`);
@@ -1301,41 +1098,16 @@ class ZenMuxExecutor extends OpenAICompatibleExecutor {
1301
1098
  }
1302
1099
  }
1303
1100
 
1304
- // src/provider/providers/minimax.ts
1305
- class MiniMaxBase extends OpenAICompatibleExecutor {
1306
- buildBody(opts) {
1307
- const jsonMode = opts.responseFormat !== undefined;
1308
- const body = super.buildBody(opts);
1309
- delete body["reasoning_effort"];
1310
- delete body["response_format"];
1311
- delete body["parallel_tool_calls"];
1312
- body["reasoning_split"] = true;
1313
- body["max_completion_tokens"] = 16384;
1314
- const wantThink = !jsonMode && opts.reasoningEffort !== undefined && opts.reasoningEffort !== "none";
1315
- body["thinking"] = wantThink ? { type: "adaptive" } : { type: "disabled" };
1316
- return body;
1317
- }
1318
- }
1319
- function minimaxOpts(providerName, baseURL, opts) {
1320
- return {
1321
- providerName,
1322
- baseURL,
1323
- apiKey: opts.apiKey,
1324
- conversationModel: opts.conversationModel,
1325
- identityModel: opts.identityModel,
1326
- supportsResponseFormat: false
1327
- };
1328
- }
1329
-
1330
- class MiniMaxExecutor extends MiniMaxBase {
1331
- constructor(opts) {
1332
- super(minimaxOpts("minimax", "https://api.minimax.io/v1", opts));
1333
- }
1334
- }
1335
-
1336
- class MiniMaxCnExecutor extends MiniMaxBase {
1101
+ // src/provider/providers/MiniMax.ts
1102
+ class MiniMaxExecutor extends OpenAICompatibleExecutor {
1337
1103
  constructor(opts) {
1338
- 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
+ });
1339
1111
  }
1340
1112
  }
1341
1113
 
@@ -1399,8 +1171,7 @@ class LmStudioExecutor extends OpenAICompatibleExecutor {
1399
1171
  baseURL: "http://127.0.0.1:1234/v1",
1400
1172
  apiKey: opts.apiKey || "lm-studio",
1401
1173
  conversationModel: opts.conversationModel,
1402
- identityModel: opts.identityModel,
1403
- supportsResponseFormat: false
1174
+ identityModel: opts.identityModel
1404
1175
  });
1405
1176
  }
1406
1177
  }
@@ -1413,8 +1184,7 @@ class OllamaExecutor extends OpenAICompatibleExecutor {
1413
1184
  baseURL: "http://127.0.0.1:11434/v1",
1414
1185
  apiKey: opts.apiKey || "ollama",
1415
1186
  conversationModel: opts.conversationModel,
1416
- identityModel: opts.identityModel,
1417
- supportsResponseFormat: false
1187
+ identityModel: opts.identityModel
1418
1188
  });
1419
1189
  }
1420
1190
  }
@@ -1440,8 +1210,7 @@ class LlamaCppExecutor extends OpenAICompatibleExecutor {
1440
1210
  baseURL: "http://127.0.0.1:8080/v1",
1441
1211
  apiKey: opts.apiKey || "llama.cpp",
1442
1212
  conversationModel: opts.conversationModel,
1443
- identityModel: opts.identityModel,
1444
- supportsResponseFormat: false
1213
+ identityModel: opts.identityModel
1445
1214
  });
1446
1215
  }
1447
1216
  }
@@ -1491,17 +1260,7 @@ class CloudflareGatewayExecutor extends OpenAICompatibleExecutor {
1491
1260
  // src/provider/providers/cloudflare_workers.ts
1492
1261
  function toCloudflareMessage(m) {
1493
1262
  if (m.role === "assistant") {
1494
- return {
1495
- role: "assistant",
1496
- content: m.content ?? "",
1497
- tool_calls: m.toolCalls?.map((c) => {
1498
- let args = c.function.arguments;
1499
- try {
1500
- args = JSON.parse(c.function.arguments);
1501
- } catch {}
1502
- return { id: c.id, name: c.function.name, arguments: args };
1503
- })
1504
- };
1263
+ return { role: "assistant", content: m.content ?? "" };
1505
1264
  }
1506
1265
  if (m.role === "tool") {
1507
1266
  return { role: "tool", content: m.content, tool_call_id: m.toolCallId };
@@ -1524,23 +1283,21 @@ class CloudflareWorkersExecutor extends LLMExecutor {
1524
1283
  const accountId = readAuthString(opts.auth, "accountId", "CLOUDFLARE_ACCOUNT_ID");
1525
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";
1526
1285
  }
1527
- async run(model, body, caller) {
1286
+ async run(model, body) {
1528
1287
  const url = `${this.baseURL}/${encodeURIComponent(model)}`;
1529
- const requestRaw = JSON.stringify(body);
1530
1288
  const res = await fetch(url, {
1531
1289
  method: "POST",
1532
1290
  headers: {
1533
1291
  Authorization: `Bearer ${this.apiKey}`,
1534
1292
  "Content-Type": "application/json"
1535
1293
  },
1536
- body: requestRaw
1294
+ body: JSON.stringify(body)
1537
1295
  });
1538
- const responseRaw = await res.text().catch(() => "");
1539
- logLlmWire(caller, requestRaw, responseRaw);
1540
1296
  if (!res.ok) {
1541
- 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)}`);
1542
1299
  }
1543
- return JSON.parse(responseRaw);
1300
+ return await res.json();
1544
1301
  }
1545
1302
  async call(model, options) {
1546
1303
  const jsonMode = "jsonSchemaName" in options;
@@ -1561,15 +1318,15 @@ class CloudflareWorkersExecutor extends LLMExecutor {
1561
1318
  if (reasoning !== "none") {
1562
1319
  body["reasoning_effort"] = reasoning;
1563
1320
  }
1564
- const data = await this.run(model, body, resolveLlmCaller(options));
1321
+ const data = await this.run(model, body);
1565
1322
  if (data.errors && data.errors.length > 0) {
1566
1323
  throw new Error(`cloudflare-workers API error: ${data.errors.map((e) => e.message).join("; ")}`);
1567
1324
  }
1568
- const content = stripThinkTags(data.result?.response ?? "");
1325
+ const content = data.result?.response ?? "";
1569
1326
  if (!content) {
1570
1327
  throw new Error("Empty response from model");
1571
1328
  }
1572
- return jsonMode ? parseModelJson(content) : content;
1329
+ return jsonMode ? JSON.parse(content) : content;
1573
1330
  }
1574
1331
  async chatWithTools(model, options) {
1575
1332
  const reasoning = defaultReasoningEffort(options.reasoningEffort, model, this.models.identity);
@@ -1590,11 +1347,11 @@ class CloudflareWorkersExecutor extends LLMExecutor {
1590
1347
  if (reasoning !== "none") {
1591
1348
  body["reasoning_effort"] = reasoning;
1592
1349
  }
1593
- const data = await this.run(model, body, resolveLlmCaller(options));
1350
+ const data = await this.run(model, body);
1594
1351
  if (data.errors && data.errors.length > 0) {
1595
1352
  throw new Error(`cloudflare-workers API error: ${data.errors.map((e) => e.message).join("; ")}`);
1596
1353
  }
1597
- const content = stripThinkTags(data.result?.response ?? "");
1354
+ const content = data.result?.response ?? "";
1598
1355
  const toolCalls = data.result?.tool_calls?.map((c, idx) => ({
1599
1356
  id: `call_${idx}`,
1600
1357
  function: {
@@ -1632,8 +1389,7 @@ class AzureOpenAIExecutor extends OpenAICompatibleExecutor {
1632
1389
  super({
1633
1390
  providerName: "azure-openai",
1634
1391
  baseURL: `https://${resource || "__resource__"}.openai.azure.com/openai/deployments`,
1635
- apiKey: "",
1636
- defaultHeaders: { "api-key": opts.apiKey },
1392
+ apiKey: opts.apiKey,
1637
1393
  conversationModel: opts.conversationModel,
1638
1394
  identityModel: opts.identityModel
1639
1395
  });
@@ -1653,8 +1409,7 @@ class AzureCognitiveExecutor extends OpenAICompatibleExecutor {
1653
1409
  super({
1654
1410
  providerName: "azure-cognitive",
1655
1411
  baseURL: `https://${resource || "__resource__"}.cognitiveservices.azure.com/openai/deployments`,
1656
- apiKey: "",
1657
- defaultHeaders: { "api-key": opts.apiKey },
1412
+ apiKey: opts.apiKey,
1658
1413
  conversationModel: opts.conversationModel,
1659
1414
  identityModel: opts.identityModel
1660
1415
  });
@@ -1680,17 +1435,17 @@ var AnthropicAuthSchema = z4.object({
1680
1435
  apiVersion: z4.string().optional()
1681
1436
  }).loose();
1682
1437
  function toAnthropicMessages(messages) {
1683
- let system2;
1684
- const msgs2 = [];
1438
+ let system;
1439
+ const msgs = [];
1685
1440
  for (const m of messages) {
1686
1441
  if (m.role === "system") {
1687
- system2 = (system2 ? system2 + `
1442
+ system = (system ? system + `
1688
1443
 
1689
1444
  ` : "") + m.content;
1690
1445
  continue;
1691
1446
  }
1692
1447
  if (m.role === "user") {
1693
- msgs2.push({ role: "user", content: m.content });
1448
+ msgs.push({ role: "user", content: m.content });
1694
1449
  continue;
1695
1450
  }
1696
1451
  if (m.role === "assistant") {
@@ -1713,11 +1468,11 @@ function toAnthropicMessages(messages) {
1713
1468
  });
1714
1469
  }
1715
1470
  }
1716
- msgs2.push({ role: "assistant", content: blocks });
1471
+ msgs.push({ role: "assistant", content: blocks });
1717
1472
  continue;
1718
1473
  }
1719
1474
  if (m.role === "tool") {
1720
- msgs2.push({
1475
+ msgs.push({
1721
1476
  role: "user",
1722
1477
  content: [
1723
1478
  {
@@ -1729,7 +1484,7 @@ function toAnthropicMessages(messages) {
1729
1484
  });
1730
1485
  }
1731
1486
  }
1732
- return { system: system2, msgs: msgs2 };
1487
+ return { system, msgs };
1733
1488
  }
1734
1489
  function toAnthropicTool(t) {
1735
1490
  return {
@@ -1757,8 +1512,7 @@ class AnthropicExecutor extends LLMExecutor {
1757
1512
  this.baseURL = (extra.baseURL ?? readAuthString(opts.auth, "baseURL", "ANTHROPIC_BASE_URL") ?? "https://api.anthropic.com").replace(/\/v1\/?$/, "");
1758
1513
  this.apiVersion = extra.apiVersion ?? "2023-06-01";
1759
1514
  }
1760
- async send(body, caller) {
1761
- const requestRaw = JSON.stringify(body);
1515
+ async send(body) {
1762
1516
  const res = await fetch(`${this.baseURL}/v1/messages`, {
1763
1517
  method: "POST",
1764
1518
  headers: {
@@ -1766,93 +1520,65 @@ class AnthropicExecutor extends LLMExecutor {
1766
1520
  "anthropic-version": this.apiVersion,
1767
1521
  "Content-Type": "application/json"
1768
1522
  },
1769
- body: requestRaw
1523
+ body: JSON.stringify(body)
1770
1524
  });
1771
- const responseRaw = await res.text().catch(() => "");
1772
- logLlmWire(caller, requestRaw, responseRaw);
1773
1525
  if (!res.ok) {
1774
- 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)}`);
1775
1528
  }
1776
- return JSON.parse(responseRaw);
1529
+ return await res.json();
1777
1530
  }
1778
1531
  async call(model, options) {
1779
- if ("jsonSchemaName" in options) {
1780
- const { toolName, tool, instruction } = buildStructuredJsonRequest({
1781
- instruction: options.instruction,
1782
- jsonSchemaName: options.jsonSchemaName,
1783
- jsonSchema: options.jsonSchema
1784
- });
1785
- const choice = await this.chatWithTools(model, {
1786
- caller: options.caller ?? options.jsonSchemaName,
1787
- instruction,
1788
- messages: [{ role: "user", content: options.message }],
1789
- tools: [tool],
1790
- reasoningEffort: "none",
1791
- toolChoice: { type: "tool", name: toolName }
1792
- });
1793
- return parseStructuredJsonResult(choice, toolName);
1794
- }
1532
+ const jsonMode = "jsonSchemaName" in options;
1795
1533
  const reasoning = defaultReasoningEffort(options.reasoningEffort, model, this.models.identity);
1796
1534
  const log5 = logger.child("llm:anthropic");
1797
- log5.debug(`call: model=${model} jsonSchema=- msgLen=${options.message.length}`);
1798
- const outputCap = 4096;
1535
+ log5.debug(`call: model=${model} jsonSchema=${jsonMode ? options.jsonSchemaName : "-"} msgLen=${options.message.length}`);
1799
1536
  const body = {
1800
1537
  model,
1801
- max_tokens: outputCap,
1538
+ max_tokens: 4096,
1802
1539
  system: options.instruction,
1803
1540
  messages: [{ role: "user", content: options.message }]
1804
1541
  };
1805
1542
  if (reasoning !== "none") {
1806
- const budget = REASONING_BUDGET[reasoning];
1807
- body["max_tokens"] = budget + outputCap;
1808
1543
  body["thinking"] = {
1809
1544
  type: "enabled",
1810
- budget_tokens: budget
1545
+ budget_tokens: REASONING_BUDGET[reasoning]
1811
1546
  };
1812
1547
  }
1813
- const data = await this.send(body, resolveLlmCaller(options));
1548
+ const data = await this.send(body);
1814
1549
  if (data.error) {
1815
1550
  throw new Error(`anthropic API error: ${data.error.message ?? "unknown"}`);
1816
1551
  }
1817
- 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("");
1818
1553
  if (!text) {
1819
1554
  throw new Error("Empty response from model");
1820
1555
  }
1821
- return text;
1556
+ return jsonMode ? JSON.parse(text) : text;
1822
1557
  }
1823
1558
  async chatWithTools(model, options) {
1824
1559
  const reasoning = defaultReasoningEffort(options.reasoningEffort, model, this.models.identity);
1825
1560
  const log5 = logger.child("llm:anthropic");
1826
1561
  log5.debug(`chatWithTools: model=${model} msgs=${options.messages.length} tools=${options.tools.length}`);
1827
- const { system: system2, msgs: msgs2 } = toAnthropicMessages(options.messages);
1828
- const outputCap = 4096;
1562
+ const { system, msgs } = toAnthropicMessages(options.messages);
1829
1563
  const body = {
1830
1564
  model,
1831
- max_tokens: outputCap,
1832
- system: system2 ?? options.instruction,
1833
- messages: msgs2,
1565
+ max_tokens: 4096,
1566
+ system: system ?? options.instruction,
1567
+ messages: msgs,
1834
1568
  tools: options.tools.map(toAnthropicTool)
1835
1569
  };
1836
- if (options.toolChoice) {
1837
- body["tool_choice"] = {
1838
- type: "tool",
1839
- name: options.toolChoice.name
1840
- };
1841
- }
1842
1570
  if (reasoning !== "none") {
1843
- const budget = REASONING_BUDGET[reasoning];
1844
- body["max_tokens"] = budget + outputCap;
1845
1571
  body["thinking"] = {
1846
1572
  type: "enabled",
1847
- budget_tokens: budget
1573
+ budget_tokens: REASONING_BUDGET[reasoning]
1848
1574
  };
1849
1575
  }
1850
- const data = await this.send(body, resolveLlmCaller(options));
1576
+ const data = await this.send(body);
1851
1577
  if (data.error) {
1852
1578
  throw new Error(`anthropic API error: ${data.error.message ?? "unknown"}`);
1853
1579
  }
1854
1580
  const blocks = data.content ?? [];
1855
- 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("");
1856
1582
  const toolCalls = blocks.filter((b) => b.type === "tool_use").map((b) => ({
1857
1583
  id: b.id,
1858
1584
  function: {
@@ -1866,11 +1592,11 @@ class AnthropicExecutor extends LLMExecutor {
1866
1592
 
1867
1593
  // src/provider/providers/bedrock.ts
1868
1594
  import { createHmac, createHash } from "node:crypto";
1869
- var pad22 = (n) => n.toString().padStart(2, "0");
1595
+ var pad2 = (n) => n.toString().padStart(2, "0");
1870
1596
  function amzDate(now) {
1871
1597
  return {
1872
- date: `${now.getUTCFullYear()}${pad22(now.getUTCMonth() + 1)}${pad22(now.getUTCDate())}`,
1873
- 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`
1874
1600
  };
1875
1601
  }
1876
1602
  function signRequest(opts) {
@@ -1917,6 +1643,58 @@ function signRequest(opts) {
1917
1643
  headers["X-Amz-Security-Token"] = opts.sessionToken;
1918
1644
  return headers;
1919
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
+ }
1920
1698
  function toAnthropicTool2(t) {
1921
1699
  return {
1922
1700
  name: t.name,
@@ -1926,7 +1704,7 @@ function toAnthropicTool2(t) {
1926
1704
  }
1927
1705
  function extractAnthropicContent(data) {
1928
1706
  const content = data["content"] ?? [];
1929
- 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("");
1930
1708
  const toolCalls = content.filter((b) => b["type"] === "tool_use").map((b) => ({
1931
1709
  id: b["id"],
1932
1710
  function: {
@@ -1958,7 +1736,7 @@ class BedrockExecutor extends LLMExecutor {
1958
1736
  this.sessionToken = readAuthString(opts.auth, "sessionToken", "AWS_SESSION_TOKEN");
1959
1737
  this.baseURL = `https://bedrock-runtime.${this.region}.amazonaws.com`;
1960
1738
  }
1961
- async invoke(model, body, caller) {
1739
+ async invoke(model, body) {
1962
1740
  const bodyStr = JSON.stringify(body);
1963
1741
  const path = `/model/${encodeURIComponent(model)}/invoke`;
1964
1742
  const headers = signRequest({
@@ -1977,32 +1755,16 @@ class BedrockExecutor extends LLMExecutor {
1977
1755
  headers,
1978
1756
  body: bodyStr
1979
1757
  });
1980
- const responseRaw = await res.text().catch(() => "");
1981
- logLlmWire(caller, bodyStr, responseRaw);
1982
1758
  if (!res.ok) {
1983
- 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)}`);
1984
1761
  }
1985
- return JSON.parse(responseRaw);
1762
+ return await res.json();
1986
1763
  }
1987
1764
  async call(model, options) {
1765
+ const jsonMode = "jsonSchemaName" in options;
1988
1766
  const log5 = logger.child("llm:bedrock");
1989
- if ("jsonSchemaName" in options) {
1990
- log5.debug(`call: model=${model} jsonSchema=${options.jsonSchemaName} via tool`);
1991
- const { toolName, tool, instruction } = buildStructuredJsonRequest({
1992
- instruction: options.instruction,
1993
- jsonSchemaName: options.jsonSchemaName,
1994
- jsonSchema: options.jsonSchema
1995
- });
1996
- const choice = await this.chatWithTools(model, {
1997
- caller: options.caller ?? options.jsonSchemaName,
1998
- instruction,
1999
- messages: [{ role: "user", content: options.message }],
2000
- tools: [tool],
2001
- toolChoice: { type: "tool", name: toolName }
2002
- });
2003
- return parseStructuredJsonResult(choice, toolName);
2004
- }
2005
- log5.debug(`call: model=${model} jsonSchema=- msgLen=${options.message.length}`);
1767
+ log5.debug(`call: model=${model} jsonSchema=${jsonMode ? options.jsonSchemaName : "-"} msgLen=${options.message.length}`);
2006
1768
  if (!model.startsWith("anthropic.") && !model.startsWith("us.anthropic.")) {
2007
1769
  throw new Error(`bedrock provider currently only supports Anthropic models (got ${model})`);
2008
1770
  }
@@ -2012,12 +1774,12 @@ class BedrockExecutor extends LLMExecutor {
2012
1774
  system: options.instruction,
2013
1775
  messages: [{ role: "user", content: options.message }]
2014
1776
  };
2015
- const data = await this.invoke(model, body, resolveLlmCaller(options));
1777
+ const data = await this.invoke(model, body);
2016
1778
  const { text } = extractAnthropicContent(data);
2017
1779
  if (!text) {
2018
1780
  throw new Error("Empty response from model");
2019
1781
  }
2020
- return text;
1782
+ return jsonMode ? JSON.parse(text) : text;
2021
1783
  }
2022
1784
  async chatWithTools(model, options) {
2023
1785
  const log5 = logger.child("llm:bedrock");
@@ -2025,6 +1787,7 @@ class BedrockExecutor extends LLMExecutor {
2025
1787
  if (!model.startsWith("anthropic.") && !model.startsWith("us.anthropic.")) {
2026
1788
  throw new Error(`bedrock provider currently only supports Anthropic models (got ${model})`);
2027
1789
  }
1790
+ const { system, msgs } = toAnthropicMessages2(options.messages);
2028
1791
  const body = {
2029
1792
  anthropic_version: "bedrock-2023-05-31",
2030
1793
  max_tokens: 4096,
@@ -2032,13 +1795,7 @@ class BedrockExecutor extends LLMExecutor {
2032
1795
  messages: msgs,
2033
1796
  tools: options.tools.map(toAnthropicTool2)
2034
1797
  };
2035
- if (options.toolChoice) {
2036
- body.tool_choice = {
2037
- type: "tool",
2038
- name: options.toolChoice.name
2039
- };
2040
- }
2041
- const data = await this.invoke(model, body, resolveLlmCaller(options));
1798
+ const data = await this.invoke(model, body);
2042
1799
  const { text, toolCalls } = extractAnthropicContent(data);
2043
1800
  return { message: { content: text || undefined, toolCalls } };
2044
1801
  }
@@ -2052,7 +1809,6 @@ var VertexAuthSchema = z5.object({
2052
1809
  }).loose();
2053
1810
  function toGeminiContents(messages) {
2054
1811
  const out = [];
2055
- const nameByCallId = new Map;
2056
1812
  for (const m of messages) {
2057
1813
  if (m.role === "system")
2058
1814
  continue;
@@ -2066,7 +1822,6 @@ function toGeminiContents(messages) {
2066
1822
  parts.push({ text: m.content });
2067
1823
  if (m.toolCalls) {
2068
1824
  for (const c of m.toolCalls) {
2069
- nameByCallId.set(c.id, c.function.name);
2070
1825
  let args = {};
2071
1826
  try {
2072
1827
  args = c.function.arguments ? JSON.parse(c.function.arguments) : {};
@@ -2088,26 +1843,21 @@ function toGeminiContents(messages) {
2088
1843
  }
2089
1844
  out.push({
2090
1845
  role: "user",
2091
- parts: [
2092
- {
2093
- functionResponse: {
2094
- name: nameByCallId.get(m.toolCallId) ?? "",
2095
- response
2096
- }
2097
- }
2098
- ]
1846
+ parts: [{ functionResponse: { name: "", response } }]
2099
1847
  });
2100
1848
  }
2101
1849
  }
2102
1850
  return out;
2103
1851
  }
2104
- function toGeminiTools(tools) {
1852
+ function toGeminiTool(t) {
2105
1853
  return {
2106
- functionDeclarations: tools.map((t) => ({
2107
- name: t.name,
2108
- description: t.description,
2109
- parameters: t.parameters ?? { type: "object", properties: {} }
2110
- }))
1854
+ functionDeclarations: [
1855
+ {
1856
+ name: t.name,
1857
+ description: t.description,
1858
+ parameters: t.parameters ?? { type: "object", properties: {} }
1859
+ }
1860
+ ]
2111
1861
  };
2112
1862
  }
2113
1863
  function extractFromGemini(data) {
@@ -2128,10 +1878,7 @@ function extractFromGemini(data) {
2128
1878
  });
2129
1879
  }
2130
1880
  }
2131
- return {
2132
- text: stripThinkTags(text),
2133
- toolCalls: toolCalls.length > 0 ? toolCalls : undefined
2134
- };
1881
+ return { text, toolCalls: toolCalls.length > 0 ? toolCalls : undefined };
2135
1882
  }
2136
1883
 
2137
1884
  class VertexExecutor extends LLMExecutor {
@@ -2154,23 +1901,21 @@ class VertexExecutor extends LLMExecutor {
2154
1901
  this.region = extra.region ?? readAuthString(opts.auth, "region", "GOOGLE_CLOUD_REGION") ?? "us-central1";
2155
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";
2156
1903
  }
2157
- async generate(model, body, caller) {
1904
+ async generate(model, body) {
2158
1905
  const url = `${this.baseURL}/${encodeURIComponent(model)}:generateContent`;
2159
- const requestRaw = JSON.stringify(body);
2160
1906
  const res = await fetch(url, {
2161
1907
  method: "POST",
2162
1908
  headers: {
2163
1909
  Authorization: `Bearer ${this.apiKey}`,
2164
1910
  "Content-Type": "application/json"
2165
1911
  },
2166
- body: requestRaw
1912
+ body: JSON.stringify(body)
2167
1913
  });
2168
- const responseRaw = await res.text().catch(() => "");
2169
- logLlmWire(caller, requestRaw, responseRaw);
2170
1914
  if (!res.ok) {
2171
- 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)}`);
2172
1917
  }
2173
- return JSON.parse(responseRaw);
1918
+ return await res.json();
2174
1919
  }
2175
1920
  async call(model, options) {
2176
1921
  const jsonMode = "jsonSchemaName" in options;
@@ -2191,7 +1936,7 @@ class VertexExecutor extends LLMExecutor {
2191
1936
  responseSchema: options.jsonSchema
2192
1937
  };
2193
1938
  }
2194
- const data = await this.generate(model, body, resolveLlmCaller(options));
1939
+ const data = await this.generate(model, body);
2195
1940
  if (data.error) {
2196
1941
  throw new Error(`vertex API error: ${data.error.message ?? "unknown"}`);
2197
1942
  }
@@ -2199,7 +1944,7 @@ class VertexExecutor extends LLMExecutor {
2199
1944
  if (!text) {
2200
1945
  throw new Error("Empty response from model");
2201
1946
  }
2202
- return jsonMode ? parseModelJson(text) : text;
1947
+ return jsonMode ? JSON.parse(text) : text;
2203
1948
  }
2204
1949
  async chatWithTools(model, options) {
2205
1950
  const log5 = logger.child("llm:vertex");
@@ -2208,9 +1953,9 @@ class VertexExecutor extends LLMExecutor {
2208
1953
  const body = {
2209
1954
  contents,
2210
1955
  systemInstruction: { parts: [{ text: options.instruction }] },
2211
- tools: options.tools.length > 0 ? [toGeminiTools(options.tools)] : undefined
1956
+ tools: options.tools.length > 0 ? [toGeminiTool(options.tools[0])] : undefined
2212
1957
  };
2213
- const data = await this.generate(model, body, resolveLlmCaller(options));
1958
+ const data = await this.generate(model, body);
2214
1959
  if (data.error) {
2215
1960
  throw new Error(`vertex API error: ${data.error.message ?? "unknown"}`);
2216
1961
  }
@@ -2286,41 +2031,25 @@ class GitLabDuoExecutor extends LLMExecutor {
2286
2031
  const extra = parsed.success ? parsed.data : {};
2287
2032
  this.baseURL = (extra.baseURL ?? readAuthString(opts.auth, "baseURL", "GITLAB_BASE_URL") ?? "https://gitlab.com/api/v4/ai/llm/proxy").replace(/\/+$/, "");
2288
2033
  }
2289
- async send(body, caller) {
2290
- const requestRaw = JSON.stringify(body);
2034
+ async send(body) {
2291
2035
  const res = await fetch(this.baseURL, {
2292
2036
  method: "POST",
2293
2037
  headers: {
2294
2038
  Authorization: `Bearer ${this.apiKey}`,
2295
2039
  "Content-Type": "application/json"
2296
2040
  },
2297
- body: requestRaw
2041
+ body: JSON.stringify(body)
2298
2042
  });
2299
- const responseRaw = await res.text().catch(() => "");
2300
- logLlmWire(caller, requestRaw, responseRaw);
2301
2043
  if (!res.ok) {
2302
- 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)}`);
2303
2046
  }
2304
- return JSON.parse(responseRaw);
2047
+ return await res.json();
2305
2048
  }
2306
2049
  async call(model, options) {
2050
+ const jsonMode = "jsonSchemaName" in options;
2307
2051
  const log5 = logger.child("llm:gitlab-duo");
2308
- if ("jsonSchemaName" in options) {
2309
- log5.debug(`call: model=${model} jsonSchema=${options.jsonSchemaName} via tool`);
2310
- const { toolName, tool, instruction } = buildStructuredJsonRequest({
2311
- instruction: options.instruction,
2312
- jsonSchemaName: options.jsonSchemaName,
2313
- jsonSchema: options.jsonSchema
2314
- });
2315
- const choice = await this.chatWithTools(model, {
2316
- caller: options.caller ?? options.jsonSchemaName,
2317
- instruction,
2318
- messages: [{ role: "user", content: options.message }],
2319
- tools: [tool]
2320
- });
2321
- return parseStructuredJsonResult(choice, toolName);
2322
- }
2323
- log5.debug(`call: model=${model} jsonSchema=- msgLen=${options.message.length}`);
2052
+ log5.debug(`call: model=${model} jsonSchema=${jsonMode ? options.jsonSchemaName : "-"} msgLen=${options.message.length}`);
2324
2053
  const body = {
2325
2054
  model,
2326
2055
  messages: [
@@ -2328,15 +2057,15 @@ class GitLabDuoExecutor extends LLMExecutor {
2328
2057
  { role: "user", content: options.message }
2329
2058
  ]
2330
2059
  };
2331
- const data = await this.send(body, resolveLlmCaller(options));
2060
+ const data = await this.send(body);
2332
2061
  if (data.error) {
2333
2062
  throw new Error(`gitlab-duo API error: ${data.error.message ?? "unknown"}`);
2334
2063
  }
2335
- const content = stripThinkTags(data.choices?.[0]?.message?.content ?? "");
2064
+ const content = data.choices?.[0]?.message?.content ?? "";
2336
2065
  if (!content) {
2337
2066
  throw new Error("Empty response from model");
2338
2067
  }
2339
- return content;
2068
+ return jsonMode ? JSON.parse(content) : content;
2340
2069
  }
2341
2070
  async chatWithTools(model, options) {
2342
2071
  const log5 = logger.child("llm:gitlab-duo");
@@ -2349,7 +2078,7 @@ class GitLabDuoExecutor extends LLMExecutor {
2349
2078
  ],
2350
2079
  tools: options.tools.map(toTool)
2351
2080
  };
2352
- const data = await this.send(body, resolveLlmCaller(options));
2081
+ const data = await this.send(body);
2353
2082
  if (data.error) {
2354
2083
  throw new Error(`gitlab-duo API error: ${data.error.message ?? "unknown"}`);
2355
2084
  }
@@ -2360,7 +2089,7 @@ class GitLabDuoExecutor extends LLMExecutor {
2360
2089
  }));
2361
2090
  return {
2362
2091
  message: {
2363
- content: choice?.message?.content ? stripThinkTags(choice.message.content) : undefined,
2092
+ content: choice?.message?.content ?? undefined,
2364
2093
  toolCalls
2365
2094
  }
2366
2095
  };
@@ -2417,43 +2146,26 @@ class SnowflakeCortexExecutor extends LLMExecutor {
2417
2146
  const account = extra.account ?? readAuthString(opts.auth, "account", "SNOWFLAKE_ACCOUNT") ?? "";
2418
2147
  this.baseURL = account ? `https://${account}.snowflakecomputing.com/api/v2/cortex/inference:complete` : "https://__account__.snowflakecomputing.com/api/v2/cortex/inference:complete";
2419
2148
  }
2420
- async send(body, caller) {
2421
- const requestRaw = JSON.stringify(body);
2149
+ async send(body) {
2422
2150
  const res = await fetch(this.baseURL, {
2423
2151
  method: "POST",
2424
2152
  headers: {
2425
2153
  Authorization: `Bearer ${this.apiKey}`,
2426
2154
  "Content-Type": "application/json"
2427
2155
  },
2428
- body: requestRaw
2156
+ body: JSON.stringify(body)
2429
2157
  });
2430
- const responseRaw = await res.text().catch(() => "");
2431
- logLlmWire(caller, requestRaw, responseRaw);
2432
2158
  if (!res.ok) {
2433
- 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)}`);
2434
2161
  }
2435
- return JSON.parse(responseRaw);
2162
+ return await res.json();
2436
2163
  }
2437
2164
  async call(model, options) {
2438
- const log5 = logger.child("llm:snowflake-cortex");
2439
- if ("jsonSchemaName" in options) {
2440
- log5.debug(`call: model=${model} jsonSchema=${options.jsonSchemaName} via tool`);
2441
- const { toolName, tool, instruction } = buildStructuredJsonRequest({
2442
- instruction: options.instruction,
2443
- jsonSchemaName: options.jsonSchemaName,
2444
- jsonSchema: options.jsonSchema
2445
- });
2446
- const choice = await this.chatWithTools(model, {
2447
- caller: options.caller ?? options.jsonSchemaName,
2448
- instruction,
2449
- messages: [{ role: "user", content: options.message }],
2450
- tools: [tool],
2451
- reasoningEffort: "none"
2452
- });
2453
- return parseStructuredJsonResult(choice, toolName);
2454
- }
2165
+ const jsonMode = "jsonSchemaName" in options;
2455
2166
  const reasoning = defaultReasoningEffort(options.reasoningEffort, model, this.models.identity);
2456
- 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}`);
2457
2169
  const body = {
2458
2170
  model,
2459
2171
  messages: [
@@ -2464,15 +2176,15 @@ class SnowflakeCortexExecutor extends LLMExecutor {
2464
2176
  if (reasoning !== "none") {
2465
2177
  body["reasoning_effort"] = reasoning;
2466
2178
  }
2467
- const data = await this.send(body, resolveLlmCaller(options));
2179
+ const data = await this.send(body);
2468
2180
  if (data.error) {
2469
2181
  throw new Error(`snowflake-cortex API error: ${data.error.message ?? "unknown"}`);
2470
2182
  }
2471
- const content = stripThinkTags(data.choices?.[0]?.message?.content ?? data.message ?? "");
2183
+ const content = data.choices?.[0]?.message?.content ?? data.message ?? "";
2472
2184
  if (!content) {
2473
2185
  throw new Error("Empty response from model");
2474
2186
  }
2475
- return content;
2187
+ return jsonMode ? JSON.parse(content) : content;
2476
2188
  }
2477
2189
  async chatWithTools(model, options) {
2478
2190
  const log5 = logger.child("llm:snowflake-cortex");
@@ -2485,12 +2197,12 @@ class SnowflakeCortexExecutor extends LLMExecutor {
2485
2197
  ],
2486
2198
  tools: options.tools.map(toCortexTool)
2487
2199
  };
2488
- const data = await this.send(body, resolveLlmCaller(options));
2200
+ const data = await this.send(body);
2489
2201
  if (data.error) {
2490
2202
  throw new Error(`snowflake-cortex API error: ${data.error.message ?? "unknown"}`);
2491
2203
  }
2492
2204
  const choice = data.choices?.[0];
2493
- const content = stripThinkTags(choice?.message?.content ?? data.message ?? "");
2205
+ const content = choice?.message?.content ?? data.message ?? "";
2494
2206
  const toolCalls = choice?.message?.tool_calls?.map((c) => ({
2495
2207
  id: c.id,
2496
2208
  function: {
@@ -2529,8 +2241,7 @@ register("stackit", StackitExecutor);
2529
2241
  register("gmi", GmiExecutor);
2530
2242
  register("zai", ZaiExecutor);
2531
2243
  register("zenmux", ZenMuxExecutor);
2532
- register("minimax", MiniMaxExecutor);
2533
- register("minimax-cn", MiniMaxCnExecutor);
2244
+ register("MiniMax", MiniMaxExecutor);
2534
2245
  register("ionet", IoNetExecutor);
2535
2246
  register("baseten", BasetenExecutor);
2536
2247
  register("cortecs", CortecsExecutor);
@@ -2564,14 +2275,14 @@ var llm = new Proxy({}, {
2564
2275
  // src/provider/promptLoader.ts
2565
2276
  import { existsSync as existsSync2 } from "fs";
2566
2277
  import { readFile as readFile2 } from "fs/promises";
2567
- import path, { dirname as dirname2 } from "path";
2278
+ import path, { dirname as dirname3 } from "path";
2568
2279
  import { fileURLToPath } from "url";
2569
2280
  var log5 = logger.child("prompt-loader");
2570
2281
  function fileName(promptKey) {
2571
2282
  return promptKey.toLowerCase() + ".md";
2572
2283
  }
2573
2284
  var __filename2 = fileURLToPath(import.meta.url);
2574
- var __dirname2 = dirname2(__filename2);
2285
+ var __dirname2 = dirname3(__filename2);
2575
2286
  var PROMPTS_DIR = existsSync2(path.resolve(__dirname2, "prompts")) ? path.resolve(__dirname2, "prompts") : path.resolve(__dirname2, "../../prompts");
2576
2287
  async function loadPrompt(promptKey) {
2577
2288
  const filePath = path.join(PROMPTS_DIR, fileName(promptKey));
@@ -2696,14 +2407,14 @@ function translateMessageHistory(personaName, entries) {
2696
2407
  }
2697
2408
 
2698
2409
  // src/brain/schedule.ts
2699
- function pad23(n) {
2410
+ function pad22(n) {
2700
2411
  return n < 10 ? `0${n}` : `${n}`;
2701
2412
  }
2702
2413
  function formatDateKey(d) {
2703
- return `${d.getFullYear()}-${pad23(d.getMonth() + 1)}-${pad23(d.getDate())}`;
2414
+ return `${d.getFullYear()}-${pad22(d.getMonth() + 1)}-${pad22(d.getDate())}`;
2704
2415
  }
2705
2416
  function formatMonthKey(d) {
2706
- return `${d.getFullYear()}-${pad23(d.getMonth() + 1)}`;
2417
+ return `${d.getFullYear()}-${pad22(d.getMonth() + 1)}`;
2707
2418
  }
2708
2419
 
2709
2420
  // src/brain/memory.ts
@@ -2791,22 +2502,22 @@ class Brain {
2791
2502
  db;
2792
2503
  space;
2793
2504
  brainbase;
2794
- availabilityCache = new Map;
2795
2505
  memory;
2796
- constructor(db, space, brainbase, memory) {
2506
+ availabilityCache = new Map;
2507
+ constructor(db, space, brainbase, memory = new Memory(this.db, this.space)) {
2797
2508
  this.db = db;
2798
2509
  this.space = space;
2799
2510
  this.brainbase = brainbase;
2800
- this.memory = memory ?? new Memory(this.db, this.space);
2511
+ this.memory = memory;
2801
2512
  log7.debug(`Brain constructed: id=${brainbase.brainId} name=${brainbase.displayName} space=${space.name}`);
2802
2513
  }
2803
2514
  async createDailySchedule(datetime) {
2804
- const dateKey2 = formatDateKey(datetime);
2805
- log7.debug(`createDailySchedule: starting for ${dateKey2}`);
2515
+ const dateKey = formatDateKey(datetime);
2516
+ log7.debug(`createDailySchedule: starting for ${dateKey}`);
2806
2517
  try {
2807
- const existing = await this.memory.get(`daily-schedule:${dateKey2}`);
2518
+ const existing = await this.memory.get(`daily-schedule:${dateKey}`);
2808
2519
  if (existing) {
2809
- log7.debug(`createDailySchedule: cache hit for ${dateKey2}`);
2520
+ log7.debug(`createDailySchedule: cache hit for ${dateKey}`);
2810
2521
  try {
2811
2522
  return JSON.parse(existing.content);
2812
2523
  } catch (parseErr) {
@@ -2834,7 +2545,7 @@ class Brain {
2834
2545
  }
2835
2546
  const instruction = await loadPrompt("DAILY_SCHEDULE");
2836
2547
  const promptMessage = [
2837
- `Target date: ${dateKey2} (${datetime.toLocaleDateString("en-US", { weekday: "long" })})`,
2548
+ `Target date: ${dateKey} (${datetime.toLocaleDateString("en-US", { weekday: "long" })})`,
2838
2549
  `Personality: ${this.brainbase.baseSystemPrompt}`,
2839
2550
  monthlySummary ? `Monthly summary for this day: ${monthlySummary}` : "(no monthly summary available for this date)",
2840
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)"}`,
@@ -2845,7 +2556,6 @@ class Brain {
2845
2556
  `);
2846
2557
  log7.debug(`createDailySchedule: calling identity model`);
2847
2558
  const schedule = await llm.call(llm.models.identity, {
2848
- caller: "daily-schedule",
2849
2559
  instruction,
2850
2560
  message: promptMessage,
2851
2561
  jsonSchemaName: "daily-schedule",
@@ -2853,15 +2563,15 @@ class Brain {
2853
2563
  });
2854
2564
  log7.debug(`createDailySchedule: model returned ${schedule.items.length} slots`);
2855
2565
  await this.memory.add({
2856
- customId: `daily-schedule:${dateKey2}`,
2566
+ customId: `daily-schedule:${dateKey}`,
2857
2567
  content: JSON.stringify(schedule),
2858
2568
  metadata: {
2859
2569
  kind: "schedule",
2860
2570
  source: "createDailySchedule",
2861
- date: dateKey2
2571
+ date: dateKey
2862
2572
  }
2863
2573
  });
2864
- log7.debug(`createDailySchedule: persisted ${dateKey2}`);
2574
+ log7.debug(`createDailySchedule: persisted ${dateKey}`);
2865
2575
  return schedule;
2866
2576
  } catch (error) {
2867
2577
  let reason = error instanceof Error ? error.message + `(${error.name})` : String(error);
@@ -2873,21 +2583,17 @@ class Brain {
2873
2583
  }
2874
2584
  async regenerateSchedules() {
2875
2585
  const today = new Date;
2876
- const nextMonth = new Date(today.getFullYear(), today.getMonth() + 1, today.getDate());
2877
- const monthly = await this.createMonthlySchedule(nextMonth);
2878
- if (!monthly) {
2879
- log7.debug(`regenerateSchedules: skip daily — monthly schedule generation failed`);
2880
- return;
2881
- }
2882
2586
  const tomorrow = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 1);
2883
2587
  await this.createDailySchedule(tomorrow);
2884
2588
  await this.createDailySchedule(today);
2589
+ const nextMonth = new Date(today.getFullYear(), today.getMonth() + 1, today.getDate());
2590
+ await this.createMonthlySchedule(nextMonth);
2885
2591
  }
2886
2592
  async createMonthlySchedule(datetime) {
2887
2593
  const year = datetime.getMonth() === 11 ? datetime.getFullYear() + 1 : datetime.getFullYear();
2888
2594
  const month = (datetime.getMonth() + 1) % 12;
2889
2595
  const daysInMonth = new Date(year, month + 1, 0).getDate();
2890
- const monthKey = `${year}-${pad23(month + 1)}`;
2596
+ const monthKey = `${year}-${pad22(month + 1)}`;
2891
2597
  log7.debug(`createMonthlySchedule: starting for ${monthKey}`);
2892
2598
  try {
2893
2599
  const existing = await this.memory.get(`monthly-schedule:${monthKey}`);
@@ -2900,7 +2606,7 @@ class Brain {
2900
2606
  }
2901
2607
  }
2902
2608
  const twoMonthsAgo = new Date(year, month - 2, 1);
2903
- const twoMonthsAgoKey = `${twoMonthsAgo.getFullYear()}-${pad23(twoMonthsAgo.getMonth() + 1)}`;
2609
+ const twoMonthsAgoKey = `${twoMonthsAgo.getFullYear()}-${pad22(twoMonthsAgo.getMonth() + 1)}`;
2904
2610
  log7.debug(`createMonthlySchedule: gathering context (history, ${twoMonthsAgoKey})`);
2905
2611
  const [history, twoMonthsAgoStored] = await Promise.all([
2906
2612
  this.getHistoryFacts(),
@@ -2928,7 +2634,6 @@ class Brain {
2928
2634
  `);
2929
2635
  log7.debug(`createMonthlySchedule: calling identity model`);
2930
2636
  const schedule = await llm.call(llm.models.identity, {
2931
- caller: "monthly-schedule",
2932
2637
  instruction,
2933
2638
  message: promptMessage,
2934
2639
  jsonSchemaName: "monthly-schedule",
@@ -2959,11 +2664,11 @@ class Brain {
2959
2664
  }
2960
2665
  log7.debug(`sleepMemory: starting, ${history.length} messages`);
2961
2666
  try {
2962
- const dateKey2 = formatDateKey(datetime);
2667
+ const dateKey = formatDateKey(datetime);
2963
2668
  const instruction = await loadPrompt("MEMOIR");
2964
2669
  const historyBlock = translateMessageHistory(this.brainbase.displayName, history);
2965
2670
  const promptMessage = [
2966
- `Date: ${dateKey2}`,
2671
+ `Date: ${dateKey}`,
2967
2672
  `Personality: ${this.brainbase.baseSystemPrompt}`,
2968
2673
  `Conversation log:`,
2969
2674
  historyBlock
@@ -2972,21 +2677,20 @@ class Brain {
2972
2677
  `);
2973
2678
  log7.debug(`sleepMemory: calling identity model`);
2974
2679
  const memoir = await llm.call(llm.models.identity, {
2975
- caller: "sleep-memory",
2976
2680
  instruction,
2977
2681
  message: promptMessage
2978
2682
  });
2979
2683
  log7.debug(`sleepMemory: model returned ${memoir.length} chars`);
2980
2684
  await this.memory.add({
2981
- customId: `daily-journal:${dateKey2}`,
2685
+ customId: `daily-journal:${dateKey}`,
2982
2686
  content: memoir,
2983
2687
  metadata: {
2984
2688
  kind: "daily-journal",
2985
2689
  source: "sleepMemory",
2986
- date: dateKey2
2690
+ date: dateKey
2987
2691
  }
2988
2692
  });
2989
- log7.debug(`sleepMemory: journal persisted for ${dateKey2}`);
2693
+ log7.debug(`sleepMemory: journal persisted for ${dateKey}`);
2990
2694
  return memoir;
2991
2695
  } catch (error) {
2992
2696
  const reason = error instanceof Error ? error.message : String(error);
@@ -2995,22 +2699,22 @@ class Brain {
2995
2699
  }
2996
2700
  }
2997
2701
  async getTodayScheduledAvailability(datetime) {
2998
- const dateKey2 = formatDateKey(datetime);
2702
+ const dateKey = formatDateKey(datetime);
2999
2703
  try {
3000
- const cached = this.availabilityCache.get(dateKey2);
2704
+ const cached = this.availabilityCache.get(dateKey);
3001
2705
  if (cached) {
3002
- log7.debug(`getTodayScheduledAvailability: cache hit for ${dateKey2}`);
2706
+ log7.debug(`getTodayScheduledAvailability: cache hit for ${dateKey}`);
3003
2707
  return cached;
3004
2708
  }
3005
- const stored = await this.memory.get(`daily-schedule:${dateKey2}`);
2709
+ const stored = await this.memory.get(`daily-schedule:${dateKey}`);
3006
2710
  if (!stored) {
3007
- log7.debug(`getTodayScheduledAvailability: no schedule for ${dateKey2}`);
2711
+ log7.debug(`getTodayScheduledAvailability: no schedule for ${dateKey}`);
3008
2712
  return null;
3009
2713
  }
3010
2714
  const dailySchedule = JSON.parse(stored.content);
3011
2715
  const availability = await this.deriveAvailabilityFromSchedule(dailySchedule);
3012
- this.availabilityCache.set(dateKey2, availability);
3013
- 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}`);
3014
2718
  return availability;
3015
2719
  } catch (error) {
3016
2720
  const reason = error instanceof Error ? error.message : String(error);
@@ -3021,7 +2725,7 @@ class Brain {
3021
2725
  async getAvailability(datetime = new Date) {
3022
2726
  const h = datetime.getHours();
3023
2727
  const m = datetime.getMinutes();
3024
- const hhmm = `${pad23(h)}:${pad23(m)}`;
2728
+ const hhmm = `${pad22(h)}:${pad22(m)}`;
3025
2729
  const current = h * 60 + m;
3026
2730
  const toMinutes = (s) => {
3027
2731
  const [hh = 0, mm = 0] = s.split(":").map((x) => parseInt(x, 10));
@@ -3034,10 +2738,10 @@ class Brain {
3034
2738
  return result;
3035
2739
  }
3036
2740
  async getCurrentAndAdjacentSlots(now) {
3037
- const dateKey2 = formatDateKey(now);
3038
- const stored = await this.memory.get(`daily-schedule:${dateKey2}`);
2741
+ const dateKey = formatDateKey(now);
2742
+ const stored = await this.memory.get(`daily-schedule:${dateKey}`);
3039
2743
  if (!stored) {
3040
- log7.debug(`getCurrentAndAdjacentSlots: no schedule for ${dateKey2}`);
2744
+ log7.debug(`getCurrentAndAdjacentSlots: no schedule for ${dateKey}`);
3041
2745
  return [];
3042
2746
  }
3043
2747
  let schedule;
@@ -3070,7 +2774,6 @@ class Brain {
3070
2774
  personality: this.brainbase.baseSystemPrompt
3071
2775
  });
3072
2776
  const result = await llm.call(llm.models.identity, {
3073
- caller: "availability",
3074
2777
  instruction,
3075
2778
  message: promptMessage,
3076
2779
  jsonSchemaName: "availability",
@@ -3146,7 +2849,6 @@ class Brain {
3146
2849
  let choice;
3147
2850
  try {
3148
2851
  choice = await llm.chatWithTools(llm.models.conversation, {
3149
- caller: initiate ? "start-conversation" : "send-message",
3150
2852
  instruction: `${this.brainbase.baseSystemPrompt}
3151
2853
 
3152
2854
  ${instruction}`,
@@ -3218,11 +2920,11 @@ ${instruction}`,
3218
2920
  ${facts || "(none indexed)"}`;
3219
2921
  }
3220
2922
  async buildScheduleBlock(now) {
3221
- const dateKey2 = formatDateKey(now);
2923
+ const dateKey = formatDateKey(now);
3222
2924
  const currentSlots = await this.getCurrentAndAdjacentSlots(now);
3223
2925
  const currentBlock = currentSlots.length > 0 ? `Currently (around ${now.toTimeString().slice(0, 5)}):
3224
2926
  ${currentSlots.map((s) => ` ${s.start}-${s.end} ${s.activity}${s.notes ? ` (${s.notes})` : ""}`).join(`
3225
- `)}` : `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)`;
3226
2928
  const days = [
3227
2929
  {
3228
2930
  label: "Yesterday",
@@ -3245,9 +2947,9 @@ ${currentBlock}
3245
2947
  ${blocks.join(`
3246
2948
  `)}`;
3247
2949
  }
3248
- async getDailyScheduleSummary(dateKey2) {
2950
+ async getDailyScheduleSummary(dateKey) {
3249
2951
  try {
3250
- const stored = await this.memory.get(`daily-schedule:${dateKey2}`);
2952
+ const stored = await this.memory.get(`daily-schedule:${dateKey}`);
3251
2953
  if (!stored)
3252
2954
  return null;
3253
2955
  const schedule = JSON.parse(stored.content);
@@ -3318,7 +3020,6 @@ ${blocks.join(`
3318
3020
  const personaInitInstruction = await loadPrompt("PERSONA_INIT");
3319
3021
  log7.debug(`Brain.create: generating description`);
3320
3022
  const description = await llm.call(llm.models.identity, {
3321
- caller: "persona-init",
3322
3023
  instruction: personaInitInstruction,
3323
3024
  message: seed
3324
3025
  });
@@ -3326,7 +3027,6 @@ ${blocks.join(`
3326
3027
  const personaSystemInstruction = await loadPrompt("PERSONA_BASE_SYSTEM_PROMPT");
3327
3028
  log7.debug(`Brain.create: generating base system prompt + dials`);
3328
3029
  const generated = await llm.call(llm.models.identity, {
3329
- caller: "base-system-prompt",
3330
3030
  instruction: personaSystemInstruction,
3331
3031
  message: description,
3332
3032
  jsonSchemaName: "base-system-prompt",
@@ -3364,12 +3064,9 @@ ${personaSystemFixed}`;
3364
3064
  log7.debug(`Brain.create: brainbase saved (id=${brainId})`);
3365
3065
  return { brainId, brain: new Brain(db, space, brainbase, memory) };
3366
3066
  } catch (error) {
3367
- let reason = error instanceof Error ? `${error.message} (${error.name})` : String(error);
3368
- if (error instanceof BadRequestResponseError) {
3369
- reason = `${reason} ${JSON.stringify(error.body)}`;
3370
- }
3371
- log7.debug(`Brain.create failed: ${reason}`);
3372
- 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;
3373
3070
  }
3374
3071
  }
3375
3072
  static async delete(brainId) {
@@ -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 {