@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.
- package/dist/index.js +309 -126
- 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
|
|
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 {
|
|
12
|
-
|
|
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
|
-
|
|
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[
|
|
111
|
+
return LEVELS[level].rank >= LEVELS[shared.level].rank;
|
|
56
112
|
}
|
|
57
113
|
formatTimestamp() {
|
|
58
114
|
const now = new Date;
|
|
59
|
-
|
|
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 =
|
|
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
|
-
|
|
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 (!
|
|
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 (
|
|
99
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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"]) :
|
|
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 = () =>
|
|
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(
|
|
185
|
-
|
|
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(
|
|
203
|
-
|
|
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
|
|
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
|
|
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
|
|
1821
|
+
var pad22 = (n) => n.toString().padStart(2, "0");
|
|
1650
1822
|
function amzDate(now) {
|
|
1651
1823
|
return {
|
|
1652
|
-
date: `${now.getUTCFullYear()}${
|
|
1653
|
-
datetime: `${now.getUTCFullYear()}${
|
|
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
|
|
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 =
|
|
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
|
|
2647
|
+
function pad23(n) {
|
|
2476
2648
|
return n < 10 ? `0${n}` : `${n}`;
|
|
2477
2649
|
}
|
|
2478
2650
|
function formatDateKey(d) {
|
|
2479
|
-
return `${d.getFullYear()}-${
|
|
2651
|
+
return `${d.getFullYear()}-${pad23(d.getMonth() + 1)}-${pad23(d.getDate())}`;
|
|
2480
2652
|
}
|
|
2481
2653
|
function formatMonthKey(d) {
|
|
2482
|
-
return `${d.getFullYear()}-${
|
|
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
|
|
2581
|
-
log7.debug(`createDailySchedule: starting for ${
|
|
2752
|
+
const dateKey2 = formatDateKey(datetime);
|
|
2753
|
+
log7.debug(`createDailySchedule: starting for ${dateKey2}`);
|
|
2582
2754
|
try {
|
|
2583
|
-
const existing = await this.memory.get(`daily-schedule:${
|
|
2755
|
+
const existing = await this.memory.get(`daily-schedule:${dateKey2}`);
|
|
2584
2756
|
if (existing) {
|
|
2585
|
-
log7.debug(`createDailySchedule: cache hit for ${
|
|
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: ${
|
|
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:${
|
|
2804
|
+
customId: `daily-schedule:${dateKey2}`,
|
|
2632
2805
|
content: JSON.stringify(schedule),
|
|
2633
2806
|
metadata: {
|
|
2634
2807
|
kind: "schedule",
|
|
2635
2808
|
source: "createDailySchedule",
|
|
2636
|
-
date:
|
|
2809
|
+
date: dateKey2
|
|
2637
2810
|
}
|
|
2638
2811
|
});
|
|
2639
|
-
log7.debug(`createDailySchedule: persisted ${
|
|
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}-${
|
|
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()}-${
|
|
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
|
|
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: ${
|
|
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:${
|
|
2925
|
+
customId: `daily-journal:${dateKey2}`,
|
|
2751
2926
|
content: memoir,
|
|
2752
2927
|
metadata: {
|
|
2753
2928
|
kind: "daily-journal",
|
|
2754
2929
|
source: "sleepMemory",
|
|
2755
|
-
date:
|
|
2930
|
+
date: dateKey2
|
|
2756
2931
|
}
|
|
2757
2932
|
});
|
|
2758
|
-
log7.debug(`sleepMemory: journal persisted for ${
|
|
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
|
|
2942
|
+
const dateKey2 = formatDateKey(datetime);
|
|
2768
2943
|
try {
|
|
2769
|
-
const cached = this.availabilityCache.get(
|
|
2944
|
+
const cached = this.availabilityCache.get(dateKey2);
|
|
2770
2945
|
if (cached) {
|
|
2771
|
-
log7.debug(`getTodayScheduledAvailability: cache hit for ${
|
|
2946
|
+
log7.debug(`getTodayScheduledAvailability: cache hit for ${dateKey2}`);
|
|
2772
2947
|
return cached;
|
|
2773
2948
|
}
|
|
2774
|
-
const stored = await this.memory.get(`daily-schedule:${
|
|
2949
|
+
const stored = await this.memory.get(`daily-schedule:${dateKey2}`);
|
|
2775
2950
|
if (!stored) {
|
|
2776
|
-
log7.debug(`getTodayScheduledAvailability: no schedule for ${
|
|
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(
|
|
2782
|
-
log7.debug(`getTodayScheduledAvailability: cached ${availability.items.length} windows for ${
|
|
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 = `${
|
|
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
|
|
2807
|
-
const stored = await this.memory.get(`daily-schedule:${
|
|
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 ${
|
|
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
|
|
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 (${
|
|
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(
|
|
3192
|
+
async getDailyScheduleSummary(dateKey2) {
|
|
3016
3193
|
try {
|
|
3017
|
-
const stored = await this.memory.get(`daily-schedule:${
|
|
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
|
|
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:${
|
|
3464
|
+
const existing = await this.brain.memory.get(`daily-journal:${dateKey2}`);
|
|
3286
3465
|
if (existing) {
|
|
3287
|
-
logger.debug(`sleepMemory cron: skip — journal for ${
|
|
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
|
|
3317
|
-
const count = this.startConversationCounters.get(
|
|
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(
|
|
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
|
|
3903
|
-
var DAEMON_SOCKET_PATH =
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 {
|