@p-sw/brainbox 0.1.2-alpha.7 → 0.1.2-alpha.9

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 +309 -126
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4,12 +4,17 @@
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 dirname4, join as join4 } from "path";
7
+ import { dirname as dirname3, join as join6 } from "path";
8
8
 
9
9
  // src/utils/logger.ts
10
10
  import chalk from "chalk";
11
- import { existsSync, mkdirSync, createWriteStream } from "fs";
12
- import { dirname } from "path";
11
+ import {
12
+ existsSync,
13
+ mkdirSync,
14
+ createWriteStream,
15
+ writeFileSync
16
+ } from "fs";
17
+ import { join } from "path";
13
18
  var LEVELS = {
14
19
  debug: { rank: 0, color: chalk.gray, stderr: false },
15
20
  info: { rank: 1, color: chalk.blue, stderr: false },
@@ -26,48 +31,98 @@ var ICONS = {
26
31
  error: "✖",
27
32
  fatal: "▲"
28
33
  };
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
+ }
29
103
 
30
104
  class Logger {
31
- level;
32
- timestamps;
33
- colors;
34
105
  tag;
35
- file;
36
- json;
37
- silent;
38
- fileStream;
39
106
  constructor(options = {}) {
40
- this.level = options.level ?? "info";
41
- this.timestamps = options.timestamps ?? true;
42
- this.colors = options.colors ?? chalk.level > 0;
43
107
  this.tag = options.tag;
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
- }
108
+ applyShared(options);
53
109
  }
54
110
  shouldLog(level) {
55
- return LEVELS[level].rank >= LEVELS[this.level].rank;
111
+ return LEVELS[level].rank >= LEVELS[shared.level].rank;
56
112
  }
57
113
  formatTimestamp() {
58
114
  const now = new Date;
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())}`;
115
+ return `${now.getFullYear()}-${pad2(now.getMonth() + 1)}-${pad2(now.getDate())} ${pad2(now.getHours())}:${pad2(now.getMinutes())}:${pad2(now.getSeconds())}`;
61
116
  }
62
117
  format(level, message) {
63
- const ts = this.timestamps ? `[${this.formatTimestamp()}]` : "";
118
+ const ts = shared.timestamps ? `[${this.formatTimestamp()}]` : "";
64
119
  const tag = this.tag ? `[${this.tag}]` : "";
65
120
  const icon = ICONS[level];
66
121
  const levelStr = level.toUpperCase();
67
122
  const consoleParts = [
68
123
  ts,
69
124
  tag,
70
- this.colors ? LEVELS[level].color(icon) : icon,
125
+ shared.colors ? LEVELS[level].color(icon) : icon,
71
126
  message
72
127
  ].filter(Boolean);
73
128
  const consoleLine = consoleParts.join(" ");
@@ -90,13 +145,13 @@ class Logger {
90
145
  return;
91
146
  const { console: consoleLine, file: fileLine } = this.format(level, message);
92
147
  const jsonLine = this.formatJson(level, message);
93
- if (!this.silent) {
148
+ if (!shared.silent) {
94
149
  const out = LEVELS[level].stderr ? process.stderr : process.stdout;
95
150
  out.write(consoleLine + `
96
151
  `);
97
152
  }
98
- if (this.fileStream) {
99
- this.fileStream.write(this.json ? jsonLine : fileLine);
153
+ if (mainSink.enabled) {
154
+ mainSink.write(shared.json ? jsonLine : fileLine);
100
155
  }
101
156
  }
102
157
  debug(message) {
@@ -119,57 +174,56 @@ class Logger {
119
174
  }
120
175
  child(tag) {
121
176
  const combined = this.tag ? `${this.tag}:${tag}` : tag;
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
- });
177
+ return new Logger({ tag: combined });
131
178
  }
132
179
  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;
139
180
  if (options.tag !== undefined)
140
181
  this.tag = options.tag;
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
- }
182
+ applyShared(options);
157
183
  }
158
184
  close() {
159
- this.fileStream?.end();
185
+ mainSink.close();
186
+ llmLogDir = undefined;
160
187
  }
161
188
  }
162
189
  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
+ }
163
217
 
164
218
  // src/config/loader.ts
165
- import { dirname as dirname2, join, resolve } from "path";
219
+ import { dirname, join as join2, resolve } from "path";
166
220
  import { z, ZodError } from "zod";
167
221
  import { homedir } from "os";
168
222
  import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
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");
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");
171
225
  function configFile(file, options) {
172
- const path = () => join(brainboxRoot, file);
226
+ const path = () => join2(brainboxRoot, file);
173
227
  const read = () => {
174
228
  const p = path();
175
229
  let raw;
@@ -181,8 +235,8 @@ function configFile(file, options) {
181
235
  const defaults = defaultsFromSchema(options.schema);
182
236
  const templateStr = (options.header ? options.header + `
183
237
  ` : "") + (defaults === undefined ? "" : stringifyYaml(defaults));
184
- mkdirSync2(dirname2(p), { recursive: true });
185
- writeFileSync(p, templateStr);
238
+ mkdirSync2(dirname(p), { recursive: true });
239
+ writeFileSync2(p, templateStr);
186
240
  raw = defaults ?? {};
187
241
  }
188
242
  try {
@@ -199,8 +253,8 @@ function configFile(file, options) {
199
253
  };
200
254
  const write = (value) => {
201
255
  const p = path();
202
- mkdirSync2(dirname2(p), { recursive: true });
203
- writeFileSync(p, stringifyYaml(value));
256
+ mkdirSync2(dirname(p), { recursive: true });
257
+ writeFileSync2(p, stringifyYaml(value));
204
258
  };
205
259
  const update = (fn) => {
206
260
  const next = fn(read());
@@ -321,7 +375,7 @@ function registerCommand(program, config2) {
321
375
 
322
376
  // src/brain/manager.ts
323
377
  import { mkdir, readFile, writeFile } from "fs/promises";
324
- import { join as join2 } from "path";
378
+ import { join as join3 } from "path";
325
379
  var log = logger.child("brain-manager");
326
380
 
327
381
  class BrainDBManager {
@@ -330,7 +384,7 @@ class BrainDBManager {
330
384
  this.root = root;
331
385
  }
332
386
  dbFile() {
333
- return join2(this.root, "brains.json");
387
+ return join3(this.root, "brains.json");
334
388
  }
335
389
  async readDB() {
336
390
  try {
@@ -496,7 +550,7 @@ class LLMExecutor {
496
550
  });
497
551
  };
498
552
  if (conv.provider === id.provider) {
499
- return build(conv.provider);
553
+ return withLlmLogging(build(conv.provider));
500
554
  }
501
555
  const modelToProvider = {
502
556
  [conv.model]: conv.provider,
@@ -507,7 +561,7 @@ class LLMExecutor {
507
561
  [id.provider]: build(id.provider)
508
562
  };
509
563
  const fallback = instances[conv.provider];
510
- return new class extends LLMExecutor {
564
+ return withLlmLogging(new class extends LLMExecutor {
511
565
  providerName = "dispatch";
512
566
  models = {
513
567
  conversation: conv.model,
@@ -521,9 +575,127 @@ class LLMExecutor {
521
575
  const exec = instances[modelToProvider[model] ?? ""] ?? fallback;
522
576
  return exec.chatWithTools(model, options);
523
577
  }
524
- };
578
+ });
525
579
  }
526
580
  }
581
+ function formatPayload(value) {
582
+ if (typeof value === "string")
583
+ return value;
584
+ try {
585
+ return JSON.stringify(value, null, 2);
586
+ } catch {
587
+ return String(value);
588
+ }
589
+ }
590
+ function resolveCaller(options, fallback) {
591
+ return options.caller ?? options.jsonSchemaName ?? fallback;
592
+ }
593
+ function withLlmLogging(inner) {
594
+ return new class extends LLMExecutor {
595
+ providerName = inner.providerName;
596
+ models = inner.models;
597
+ async call(model, options) {
598
+ const jsonMode = "jsonSchemaName" in options;
599
+ const caller = resolveCaller({
600
+ caller: options.caller,
601
+ jsonSchemaName: jsonMode ? options.jsonSchemaName : undefined
602
+ }, "call");
603
+ const requestBody = [
604
+ `provider: ${inner.providerName}`,
605
+ `model: ${model}`,
606
+ `caller: ${caller}`,
607
+ `reasoningEffort: ${options.reasoningEffort ?? "-"}`,
608
+ `jsonSchemaName: ${jsonMode ? options.jsonSchemaName : "-"}`,
609
+ "",
610
+ "--- instruction ---",
611
+ options.instruction,
612
+ "",
613
+ "--- message ---",
614
+ options.message,
615
+ ...jsonMode ? ["", "--- jsonSchema ---", formatPayload(options.jsonSchema)] : []
616
+ ].join(`
617
+ `);
618
+ try {
619
+ const result = await inner.call(model, options);
620
+ if (isLlmLogEnabled()) {
621
+ writeLlmExchange(caller, [
622
+ "======== REQUEST ========",
623
+ requestBody,
624
+ "",
625
+ "======== RESPONSE ========",
626
+ formatPayload(result),
627
+ ""
628
+ ].join(`
629
+ `));
630
+ }
631
+ return result;
632
+ } catch (err) {
633
+ const reason = err instanceof Error ? err.message : String(err);
634
+ if (isLlmLogEnabled()) {
635
+ writeLlmExchange(caller, [
636
+ "======== REQUEST ========",
637
+ requestBody,
638
+ "",
639
+ "======== ERROR ========",
640
+ reason,
641
+ ""
642
+ ].join(`
643
+ `));
644
+ }
645
+ throw err;
646
+ }
647
+ }
648
+ async chatWithTools(model, options) {
649
+ const caller = resolveCaller(options, "chat");
650
+ const requestBody = [
651
+ `provider: ${inner.providerName}`,
652
+ `model: ${model}`,
653
+ `caller: ${caller}`,
654
+ `reasoningEffort: ${options.reasoningEffort ?? "-"}`,
655
+ `parallelToolCalls: ${options.parallelToolCalls ?? false}`,
656
+ "",
657
+ "--- instruction ---",
658
+ options.instruction,
659
+ "",
660
+ "--- messages ---",
661
+ formatPayload(options.messages),
662
+ "",
663
+ "--- tools ---",
664
+ formatPayload(options.tools)
665
+ ].join(`
666
+ `);
667
+ try {
668
+ const result = await inner.chatWithTools(model, options);
669
+ if (isLlmLogEnabled()) {
670
+ writeLlmExchange(caller, [
671
+ "======== REQUEST ========",
672
+ requestBody,
673
+ "",
674
+ "======== RESPONSE ========",
675
+ formatPayload(result),
676
+ ""
677
+ ].join(`
678
+ `));
679
+ }
680
+ return result;
681
+ } catch (err) {
682
+ const reason = err instanceof Error ? err.message : String(err);
683
+ if (isLlmLogEnabled()) {
684
+ writeLlmExchange(caller, [
685
+ "======== REQUEST ========",
686
+ requestBody,
687
+ "",
688
+ "======== ERROR ========",
689
+ reason,
690
+ ""
691
+ ].join(`
692
+ `));
693
+ }
694
+ throw err;
695
+ }
696
+ }
697
+ };
698
+ }
527
699
  function listProviderNames() {
528
700
  return LLMExecutor.listProviderNames();
529
701
  }
@@ -1646,11 +1818,11 @@ class AnthropicExecutor extends LLMExecutor {
1646
1818
 
1647
1819
  // src/provider/providers/bedrock.ts
1648
1820
  import { createHmac, createHash } from "node:crypto";
1649
- var pad2 = (n) => n.toString().padStart(2, "0");
1821
+ var pad22 = (n) => n.toString().padStart(2, "0");
1650
1822
  function amzDate(now) {
1651
1823
  return {
1652
- date: `${now.getUTCFullYear()}${pad2(now.getUTCMonth() + 1)}${pad2(now.getUTCDate())}`,
1653
- datetime: `${now.getUTCFullYear()}${pad2(now.getUTCMonth() + 1)}${pad2(now.getUTCDate())}T` + `${pad2(now.getUTCHours())}${pad2(now.getUTCMinutes())}${pad2(now.getUTCSeconds())}Z`
1824
+ date: `${now.getUTCFullYear()}${pad22(now.getUTCMonth() + 1)}${pad22(now.getUTCDate())}`,
1825
+ datetime: `${now.getUTCFullYear()}${pad22(now.getUTCMonth() + 1)}${pad22(now.getUTCDate())}T` + `${pad22(now.getUTCHours())}${pad22(now.getUTCMinutes())}${pad22(now.getUTCSeconds())}Z`
1654
1826
  };
1655
1827
  }
1656
1828
  function signRequest(opts) {
@@ -2340,14 +2512,14 @@ var llm = new Proxy({}, {
2340
2512
  // src/provider/promptLoader.ts
2341
2513
  import { existsSync as existsSync2 } from "fs";
2342
2514
  import { readFile as readFile2 } from "fs/promises";
2343
- import path, { dirname as dirname3 } from "path";
2515
+ import path, { dirname as dirname2 } from "path";
2344
2516
  import { fileURLToPath } from "url";
2345
2517
  var log5 = logger.child("prompt-loader");
2346
2518
  function fileName(promptKey) {
2347
2519
  return promptKey.toLowerCase() + ".md";
2348
2520
  }
2349
2521
  var __filename2 = fileURLToPath(import.meta.url);
2350
- var __dirname2 = dirname3(__filename2);
2522
+ var __dirname2 = dirname2(__filename2);
2351
2523
  var PROMPTS_DIR = existsSync2(path.resolve(__dirname2, "prompts")) ? path.resolve(__dirname2, "prompts") : path.resolve(__dirname2, "../../prompts");
2352
2524
  async function loadPrompt(promptKey) {
2353
2525
  const filePath = path.join(PROMPTS_DIR, fileName(promptKey));
@@ -2472,14 +2644,14 @@ function translateMessageHistory(personaName, entries) {
2472
2644
  }
2473
2645
 
2474
2646
  // src/brain/schedule.ts
2475
- function pad22(n) {
2647
+ function pad23(n) {
2476
2648
  return n < 10 ? `0${n}` : `${n}`;
2477
2649
  }
2478
2650
  function formatDateKey(d) {
2479
- return `${d.getFullYear()}-${pad22(d.getMonth() + 1)}-${pad22(d.getDate())}`;
2651
+ return `${d.getFullYear()}-${pad23(d.getMonth() + 1)}-${pad23(d.getDate())}`;
2480
2652
  }
2481
2653
  function formatMonthKey(d) {
2482
- return `${d.getFullYear()}-${pad22(d.getMonth() + 1)}`;
2654
+ return `${d.getFullYear()}-${pad23(d.getMonth() + 1)}`;
2483
2655
  }
2484
2656
 
2485
2657
  // src/brain/memory.ts
@@ -2577,12 +2749,12 @@ class Brain {
2577
2749
  log7.debug(`Brain constructed: id=${brainbase.brainId} name=${brainbase.displayName} space=${space.name}`);
2578
2750
  }
2579
2751
  async createDailySchedule(datetime) {
2580
- const dateKey = formatDateKey(datetime);
2581
- log7.debug(`createDailySchedule: starting for ${dateKey}`);
2752
+ const dateKey2 = formatDateKey(datetime);
2753
+ log7.debug(`createDailySchedule: starting for ${dateKey2}`);
2582
2754
  try {
2583
- const existing = await this.memory.get(`daily-schedule:${dateKey}`);
2755
+ const existing = await this.memory.get(`daily-schedule:${dateKey2}`);
2584
2756
  if (existing) {
2585
- log7.debug(`createDailySchedule: cache hit for ${dateKey}`);
2757
+ log7.debug(`createDailySchedule: cache hit for ${dateKey2}`);
2586
2758
  try {
2587
2759
  return JSON.parse(existing.content);
2588
2760
  } catch (parseErr) {
@@ -2610,7 +2782,7 @@ class Brain {
2610
2782
  }
2611
2783
  const instruction = await loadPrompt("DAILY_SCHEDULE");
2612
2784
  const promptMessage = [
2613
- `Target date: ${dateKey} (${datetime.toLocaleDateString("en-US", { weekday: "long" })})`,
2785
+ `Target date: ${dateKey2} (${datetime.toLocaleDateString("en-US", { weekday: "long" })})`,
2614
2786
  `Personality: ${this.brainbase.baseSystemPrompt}`,
2615
2787
  monthlySummary ? `Monthly summary for this day: ${monthlySummary}` : "(no monthly summary available for this date)",
2616
2788
  `Recent schedule (${twoDaysAgoKey}, 2 days ago): ${twoDaysAgoSchedule ? twoDaysAgoSchedule.items.map((s) => `${s.start} ${s.activity}`).join(", ") : "(no schedule on file for 2 days ago)"}`,
@@ -2621,6 +2793,7 @@ class Brain {
2621
2793
  `);
2622
2794
  log7.debug(`createDailySchedule: calling identity model`);
2623
2795
  const schedule = await llm.call(llm.models.identity, {
2796
+ caller: "daily-schedule",
2624
2797
  instruction,
2625
2798
  message: promptMessage,
2626
2799
  jsonSchemaName: "daily-schedule",
@@ -2628,15 +2801,15 @@ class Brain {
2628
2801
  });
2629
2802
  log7.debug(`createDailySchedule: model returned ${schedule.items.length} slots`);
2630
2803
  await this.memory.add({
2631
- customId: `daily-schedule:${dateKey}`,
2804
+ customId: `daily-schedule:${dateKey2}`,
2632
2805
  content: JSON.stringify(schedule),
2633
2806
  metadata: {
2634
2807
  kind: "schedule",
2635
2808
  source: "createDailySchedule",
2636
- date: dateKey
2809
+ date: dateKey2
2637
2810
  }
2638
2811
  });
2639
- log7.debug(`createDailySchedule: persisted ${dateKey}`);
2812
+ log7.debug(`createDailySchedule: persisted ${dateKey2}`);
2640
2813
  return schedule;
2641
2814
  } catch (error) {
2642
2815
  let reason = error instanceof Error ? error.message + `(${error.name})` : String(error);
@@ -2648,17 +2821,17 @@ class Brain {
2648
2821
  }
2649
2822
  async regenerateSchedules() {
2650
2823
  const today = new Date;
2824
+ const nextMonth = new Date(today.getFullYear(), today.getMonth() + 1, today.getDate());
2825
+ await this.createMonthlySchedule(nextMonth);
2651
2826
  const tomorrow = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 1);
2652
2827
  await this.createDailySchedule(tomorrow);
2653
2828
  await this.createDailySchedule(today);
2654
- const nextMonth = new Date(today.getFullYear(), today.getMonth() + 1, today.getDate());
2655
- await this.createMonthlySchedule(nextMonth);
2656
2829
  }
2657
2830
  async createMonthlySchedule(datetime) {
2658
2831
  const year = datetime.getMonth() === 11 ? datetime.getFullYear() + 1 : datetime.getFullYear();
2659
2832
  const month = (datetime.getMonth() + 1) % 12;
2660
2833
  const daysInMonth = new Date(year, month + 1, 0).getDate();
2661
- const monthKey = `${year}-${pad22(month + 1)}`;
2834
+ const monthKey = `${year}-${pad23(month + 1)}`;
2662
2835
  log7.debug(`createMonthlySchedule: starting for ${monthKey}`);
2663
2836
  try {
2664
2837
  const existing = await this.memory.get(`monthly-schedule:${monthKey}`);
@@ -2671,7 +2844,7 @@ class Brain {
2671
2844
  }
2672
2845
  }
2673
2846
  const twoMonthsAgo = new Date(year, month - 2, 1);
2674
- const twoMonthsAgoKey = `${twoMonthsAgo.getFullYear()}-${pad22(twoMonthsAgo.getMonth() + 1)}`;
2847
+ const twoMonthsAgoKey = `${twoMonthsAgo.getFullYear()}-${pad23(twoMonthsAgo.getMonth() + 1)}`;
2675
2848
  log7.debug(`createMonthlySchedule: gathering context (history, ${twoMonthsAgoKey})`);
2676
2849
  const [history, twoMonthsAgoStored] = await Promise.all([
2677
2850
  this.getHistoryFacts(),
@@ -2699,6 +2872,7 @@ class Brain {
2699
2872
  `);
2700
2873
  log7.debug(`createMonthlySchedule: calling identity model`);
2701
2874
  const schedule = await llm.call(llm.models.identity, {
2875
+ caller: "monthly-schedule",
2702
2876
  instruction,
2703
2877
  message: promptMessage,
2704
2878
  jsonSchemaName: "monthly-schedule",
@@ -2729,11 +2903,11 @@ class Brain {
2729
2903
  }
2730
2904
  log7.debug(`sleepMemory: starting, ${history.length} messages`);
2731
2905
  try {
2732
- const dateKey = formatDateKey(datetime);
2906
+ const dateKey2 = formatDateKey(datetime);
2733
2907
  const instruction = await loadPrompt("MEMOIR");
2734
2908
  const historyBlock = translateMessageHistory(this.brainbase.displayName, history);
2735
2909
  const promptMessage = [
2736
- `Date: ${dateKey}`,
2910
+ `Date: ${dateKey2}`,
2737
2911
  `Personality: ${this.brainbase.baseSystemPrompt}`,
2738
2912
  `Conversation log:`,
2739
2913
  historyBlock
@@ -2742,20 +2916,21 @@ class Brain {
2742
2916
  `);
2743
2917
  log7.debug(`sleepMemory: calling identity model`);
2744
2918
  const memoir = await llm.call(llm.models.identity, {
2919
+ caller: "sleep-memory",
2745
2920
  instruction,
2746
2921
  message: promptMessage
2747
2922
  });
2748
2923
  log7.debug(`sleepMemory: model returned ${memoir.length} chars`);
2749
2924
  await this.memory.add({
2750
- customId: `daily-journal:${dateKey}`,
2925
+ customId: `daily-journal:${dateKey2}`,
2751
2926
  content: memoir,
2752
2927
  metadata: {
2753
2928
  kind: "daily-journal",
2754
2929
  source: "sleepMemory",
2755
- date: dateKey
2930
+ date: dateKey2
2756
2931
  }
2757
2932
  });
2758
- log7.debug(`sleepMemory: journal persisted for ${dateKey}`);
2933
+ log7.debug(`sleepMemory: journal persisted for ${dateKey2}`);
2759
2934
  return memoir;
2760
2935
  } catch (error) {
2761
2936
  const reason = error instanceof Error ? error.message : String(error);
@@ -2764,22 +2939,22 @@ class Brain {
2764
2939
  }
2765
2940
  }
2766
2941
  async getTodayScheduledAvailability(datetime) {
2767
- const dateKey = formatDateKey(datetime);
2942
+ const dateKey2 = formatDateKey(datetime);
2768
2943
  try {
2769
- const cached = this.availabilityCache.get(dateKey);
2944
+ const cached = this.availabilityCache.get(dateKey2);
2770
2945
  if (cached) {
2771
- log7.debug(`getTodayScheduledAvailability: cache hit for ${dateKey}`);
2946
+ log7.debug(`getTodayScheduledAvailability: cache hit for ${dateKey2}`);
2772
2947
  return cached;
2773
2948
  }
2774
- const stored = await this.memory.get(`daily-schedule:${dateKey}`);
2949
+ const stored = await this.memory.get(`daily-schedule:${dateKey2}`);
2775
2950
  if (!stored) {
2776
- log7.debug(`getTodayScheduledAvailability: no schedule for ${dateKey}`);
2951
+ log7.debug(`getTodayScheduledAvailability: no schedule for ${dateKey2}`);
2777
2952
  return null;
2778
2953
  }
2779
2954
  const dailySchedule = JSON.parse(stored.content);
2780
2955
  const availability = await this.deriveAvailabilityFromSchedule(dailySchedule);
2781
- this.availabilityCache.set(dateKey, availability);
2782
- log7.debug(`getTodayScheduledAvailability: cached ${availability.items.length} windows for ${dateKey}`);
2956
+ this.availabilityCache.set(dateKey2, availability);
2957
+ log7.debug(`getTodayScheduledAvailability: cached ${availability.items.length} windows for ${dateKey2}`);
2783
2958
  return availability;
2784
2959
  } catch (error) {
2785
2960
  const reason = error instanceof Error ? error.message : String(error);
@@ -2790,7 +2965,7 @@ class Brain {
2790
2965
  async getAvailability(datetime = new Date) {
2791
2966
  const h = datetime.getHours();
2792
2967
  const m = datetime.getMinutes();
2793
- const hhmm = `${pad22(h)}:${pad22(m)}`;
2968
+ const hhmm = `${pad23(h)}:${pad23(m)}`;
2794
2969
  const current = h * 60 + m;
2795
2970
  const toMinutes = (s) => {
2796
2971
  const [hh = 0, mm = 0] = s.split(":").map((x) => parseInt(x, 10));
@@ -2803,10 +2978,10 @@ class Brain {
2803
2978
  return result;
2804
2979
  }
2805
2980
  async getCurrentAndAdjacentSlots(now) {
2806
- const dateKey = formatDateKey(now);
2807
- const stored = await this.memory.get(`daily-schedule:${dateKey}`);
2981
+ const dateKey2 = formatDateKey(now);
2982
+ const stored = await this.memory.get(`daily-schedule:${dateKey2}`);
2808
2983
  if (!stored) {
2809
- log7.debug(`getCurrentAndAdjacentSlots: no schedule for ${dateKey}`);
2984
+ log7.debug(`getCurrentAndAdjacentSlots: no schedule for ${dateKey2}`);
2810
2985
  return [];
2811
2986
  }
2812
2987
  let schedule;
@@ -2839,6 +3014,7 @@ class Brain {
2839
3014
  personality: this.brainbase.baseSystemPrompt
2840
3015
  });
2841
3016
  const result = await llm.call(llm.models.identity, {
3017
+ caller: "availability",
2842
3018
  instruction,
2843
3019
  message: promptMessage,
2844
3020
  jsonSchemaName: "availability",
@@ -2914,6 +3090,7 @@ class Brain {
2914
3090
  let choice;
2915
3091
  try {
2916
3092
  choice = await llm.chatWithTools(llm.models.conversation, {
3093
+ caller: initiate ? "start-conversation" : "send-message",
2917
3094
  instruction: `${this.brainbase.baseSystemPrompt}
2918
3095
 
2919
3096
  ${instruction}`,
@@ -2985,11 +3162,11 @@ ${instruction}`,
2985
3162
  ${facts || "(none indexed)"}`;
2986
3163
  }
2987
3164
  async buildScheduleBlock(now) {
2988
- const dateKey = formatDateKey(now);
3165
+ const dateKey2 = formatDateKey(now);
2989
3166
  const currentSlots = await this.getCurrentAndAdjacentSlots(now);
2990
3167
  const currentBlock = currentSlots.length > 0 ? `Currently (around ${now.toTimeString().slice(0, 5)}):
2991
3168
  ${currentSlots.map((s) => ` ${s.start}-${s.end} ${s.activity}${s.notes ? ` (${s.notes})` : ""}`).join(`
2992
- `)}` : `Currently (${dateKey} ${now.toTimeString().slice(0, 5)}): (no matching slot in today's schedule)`;
3169
+ `)}` : `Currently (${dateKey2} ${now.toTimeString().slice(0, 5)}): (no matching slot in today's schedule)`;
2993
3170
  const days = [
2994
3171
  {
2995
3172
  label: "Yesterday",
@@ -3012,9 +3189,9 @@ ${currentBlock}
3012
3189
  ${blocks.join(`
3013
3190
  `)}`;
3014
3191
  }
3015
- async getDailyScheduleSummary(dateKey) {
3192
+ async getDailyScheduleSummary(dateKey2) {
3016
3193
  try {
3017
- const stored = await this.memory.get(`daily-schedule:${dateKey}`);
3194
+ const stored = await this.memory.get(`daily-schedule:${dateKey2}`);
3018
3195
  if (!stored)
3019
3196
  return null;
3020
3197
  const schedule = JSON.parse(stored.content);
@@ -3085,6 +3262,7 @@ ${blocks.join(`
3085
3262
  const personaInitInstruction = await loadPrompt("PERSONA_INIT");
3086
3263
  log7.debug(`Brain.create: generating description`);
3087
3264
  const description = await llm.call(llm.models.identity, {
3265
+ caller: "persona-init",
3088
3266
  instruction: personaInitInstruction,
3089
3267
  message: seed
3090
3268
  });
@@ -3092,6 +3270,7 @@ ${blocks.join(`
3092
3270
  const personaSystemInstruction = await loadPrompt("PERSONA_BASE_SYSTEM_PROMPT");
3093
3271
  log7.debug(`Brain.create: generating base system prompt + dials`);
3094
3272
  const generated = await llm.call(llm.models.identity, {
3273
+ caller: "base-system-prompt",
3095
3274
  instruction: personaSystemInstruction,
3096
3275
  message: description,
3097
3276
  jsonSchemaName: "base-system-prompt",
@@ -3276,15 +3455,15 @@ class BaseChannel {
3276
3455
  constructor(brain) {
3277
3456
  this.brain = brain;
3278
3457
  this.registerCron(SLEEP_MEMORY_CRON_KEY, SLEEP_MEMORY_CRON_PATTERN, async () => {
3279
- const dateKey = formatDateKey(new Date);
3458
+ const dateKey2 = formatDateKey(new Date);
3280
3459
  const availability = await this.brain.getAvailability();
3281
3460
  if (availability.status !== "offline") {
3282
3461
  logger.debug(`sleepMemory cron: skip — availability=${availability.status}`);
3283
3462
  return;
3284
3463
  }
3285
- const existing = await this.brain.memory.get(`daily-journal:${dateKey}`);
3464
+ const existing = await this.brain.memory.get(`daily-journal:${dateKey2}`);
3286
3465
  if (existing) {
3287
- logger.debug(`sleepMemory cron: skip — journal for ${dateKey} exists`);
3466
+ logger.debug(`sleepMemory cron: skip — journal for ${dateKey2} exists`);
3288
3467
  return;
3289
3468
  }
3290
3469
  const history = await this.getMessageHistoryBetween(new Date(Date.now() - 24 * 60 * 60 * 1000), new Date);
@@ -3313,8 +3492,8 @@ class BaseChannel {
3313
3492
  return;
3314
3493
  }
3315
3494
  const now = new Date;
3316
- const dateKey = formatDateKey(now);
3317
- const count = this.startConversationCounters.get(dateKey) ?? 0;
3495
+ const dateKey2 = formatDateKey(now);
3496
+ const count = this.startConversationCounters.get(dateKey2) ?? 0;
3318
3497
  const countThreshold = this.brain.brainbase.startConversationCountThreshold;
3319
3498
  if (count >= countThreshold)
3320
3499
  return;
@@ -3327,7 +3506,7 @@ class BaseChannel {
3327
3506
  });
3328
3507
  if (replies.length === 0)
3329
3508
  return;
3330
- this.startConversationCounters.set(dateKey, count + 1);
3509
+ this.startConversationCounters.set(dateKey2, count + 1);
3331
3510
  this.startConversationTimeout = true;
3332
3511
  setTimeout(() => {
3333
3512
  this.startConversationTimeout = false;
@@ -3899,8 +4078,8 @@ class TelegramChannel extends BaseChannel {
3899
4078
 
3900
4079
  // src/utils/daemonClient.ts
3901
4080
  import { connect } from "node:net";
3902
- import { join as join3 } from "node:path";
3903
- var DAEMON_SOCKET_PATH = join3(config.brainboxRoot, "daemon.sock");
4081
+ import { join as join4 } from "node:path";
4082
+ var DAEMON_SOCKET_PATH = join4(config.brainboxRoot, "daemon.sock");
3904
4083
  async function sendToDaemon(payload) {
3905
4084
  logger.debug(`sendToDaemon: command="${payload.command}" args=${JSON.stringify(payload.args) ?? "null"}`);
3906
4085
  let reply;
@@ -3962,6 +4141,7 @@ function exchangeOnce(payload) {
3962
4141
  // src/commands/daemon.ts
3963
4142
  import { createServer } from "node:net";
3964
4143
  import { chmodSync, unlinkSync } from "node:fs";
4144
+ import { join as join5 } from "node:path";
3965
4145
 
3966
4146
  // src/commands/daemon/commands.ts
3967
4147
  var log8 = logger.child("daemon-cmd");
@@ -4056,7 +4236,10 @@ async function startChannels() {
4056
4236
  return started;
4057
4237
  }
4058
4238
  async function daemon() {
4059
- logger.debug(`daemon: boot`);
4239
+ const logDir = join5(config.brainboxRoot, "logs");
4240
+ logger.configure({ logDir });
4241
+ configureLlmLog(join5(logDir, "llm"));
4242
+ logger.debug(`daemon: boot (logDir=${logDir}, llmLogDir=${join5(logDir, "llm")})`);
4060
4243
  const started = await startChannels();
4061
4244
  if (started === 0) {
4062
4245
  logger.info("No activated brains with channels. Daemon idling.");
@@ -5577,12 +5760,12 @@ function register8(program) {
5577
5760
 
5578
5761
  // src/index.ts
5579
5762
  var __filename3 = fileURLToPath2(import.meta.url);
5580
- var __dirname3 = dirname4(__filename3);
5763
+ var __dirname3 = dirname3(__filename3);
5581
5764
  logger.configure({ level: config.debug ? "debug" : "info" });
5582
5765
  logger.debug(`brainbox starting (debug=${config.debug}, root=${config.brainboxRoot})`);
5583
5766
  function getVersion() {
5584
5767
  try {
5585
- const pkgPath = join4(__dirname3, "..", "package.json");
5768
+ const pkgPath = join6(__dirname3, "..", "package.json");
5586
5769
  const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
5587
5770
  return pkg.version ?? "0.0.0";
5588
5771
  } catch {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@p-sw/brainbox",
3
- "version": "0.1.2-alpha.7",
3
+ "version": "0.1.2-alpha.9",
4
4
  "module": "dist/index.js",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",