@p-sw/brainbox 0.1.2-alpha.0 → 0.1.2-alpha.10
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 +1023 -380
- 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());
|
|
@@ -258,7 +312,6 @@ function setSupermemoryKey(key) {
|
|
|
258
312
|
supermemory: { ...root.supermemory, apiKey: key }
|
|
259
313
|
}));
|
|
260
314
|
}
|
|
261
|
-
var root_default = rootCfg.read();
|
|
262
315
|
|
|
263
316
|
// src/config/file/auth.ts
|
|
264
317
|
import z3 from "zod";
|
|
@@ -292,16 +345,25 @@ function removeProviderAuth(provider) {
|
|
|
292
345
|
return next;
|
|
293
346
|
});
|
|
294
347
|
}
|
|
295
|
-
var auth_default = authCfg.read();
|
|
296
348
|
|
|
297
349
|
// src/config/index.ts
|
|
298
350
|
var config = {
|
|
299
|
-
debug
|
|
351
|
+
get debug() {
|
|
352
|
+
return process.env.DEBUG_MODE ? process.env.DEBUG_MODE.toLowerCase() === "true" : readRootFile().debug;
|
|
353
|
+
},
|
|
300
354
|
brainboxRoot,
|
|
301
|
-
supermemoryApiKey
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
355
|
+
get supermemoryApiKey() {
|
|
356
|
+
return readRootFile().supermemory.apiKey;
|
|
357
|
+
},
|
|
358
|
+
get conversationModel() {
|
|
359
|
+
return readRootFile().conversationModel;
|
|
360
|
+
},
|
|
361
|
+
get identityModel() {
|
|
362
|
+
return readRootFile().identityModel;
|
|
363
|
+
},
|
|
364
|
+
get auth() {
|
|
365
|
+
return readAuthFile();
|
|
366
|
+
}
|
|
305
367
|
};
|
|
306
368
|
|
|
307
369
|
// src/commands/index.ts
|
|
@@ -313,7 +375,7 @@ function registerCommand(program, config2) {
|
|
|
313
375
|
|
|
314
376
|
// src/brain/manager.ts
|
|
315
377
|
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
316
|
-
import { join as
|
|
378
|
+
import { join as join3 } from "path";
|
|
317
379
|
var log = logger.child("brain-manager");
|
|
318
380
|
|
|
319
381
|
class BrainDBManager {
|
|
@@ -322,7 +384,7 @@ class BrainDBManager {
|
|
|
322
384
|
this.root = root;
|
|
323
385
|
}
|
|
324
386
|
dbFile() {
|
|
325
|
-
return
|
|
387
|
+
return join3(this.root, "brains.json");
|
|
326
388
|
}
|
|
327
389
|
async readDB() {
|
|
328
390
|
try {
|
|
@@ -422,6 +484,21 @@ function defaultReasoningEffort(effort, model, identityModel) {
|
|
|
422
484
|
return effort;
|
|
423
485
|
return model === identityModel ? "medium" : "none";
|
|
424
486
|
}
|
|
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
|
+
try {
|
|
493
|
+
return JSON.parse(cleaned);
|
|
494
|
+
} catch (first) {
|
|
495
|
+
const fence = cleaned.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
496
|
+
if (fence?.[1]) {
|
|
497
|
+
return JSON.parse(fence[1].trim());
|
|
498
|
+
}
|
|
499
|
+
throw first;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
425
502
|
function readAuthString(auth, key, envName) {
|
|
426
503
|
const fromAuth = typeof auth?.[key] === "string" ? auth[key] : undefined;
|
|
427
504
|
if (fromAuth)
|
|
@@ -473,7 +550,7 @@ class LLMExecutor {
|
|
|
473
550
|
});
|
|
474
551
|
};
|
|
475
552
|
if (conv.provider === id.provider) {
|
|
476
|
-
return build(conv.provider);
|
|
553
|
+
return withLlmLogging(build(conv.provider));
|
|
477
554
|
}
|
|
478
555
|
const modelToProvider = {
|
|
479
556
|
[conv.model]: conv.provider,
|
|
@@ -484,7 +561,7 @@ class LLMExecutor {
|
|
|
484
561
|
[id.provider]: build(id.provider)
|
|
485
562
|
};
|
|
486
563
|
const fallback = instances[conv.provider];
|
|
487
|
-
return new class extends LLMExecutor {
|
|
564
|
+
return withLlmLogging(new class extends LLMExecutor {
|
|
488
565
|
providerName = "dispatch";
|
|
489
566
|
models = {
|
|
490
567
|
conversation: conv.model,
|
|
@@ -498,9 +575,127 @@ class LLMExecutor {
|
|
|
498
575
|
const exec = instances[modelToProvider[model] ?? ""] ?? fallback;
|
|
499
576
|
return exec.chatWithTools(model, options);
|
|
500
577
|
}
|
|
501
|
-
};
|
|
578
|
+
});
|
|
502
579
|
}
|
|
503
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
|
+
}
|
|
504
699
|
function listProviderNames() {
|
|
505
700
|
return LLMExecutor.listProviderNames();
|
|
506
701
|
}
|
|
@@ -538,7 +733,7 @@ function fromOrChoice(choice) {
|
|
|
538
733
|
}));
|
|
539
734
|
return {
|
|
540
735
|
message: {
|
|
541
|
-
content: typeof msg.content === "string" ? msg.content : undefined,
|
|
736
|
+
content: typeof msg.content === "string" ? stripThinkTags(msg.content) : undefined,
|
|
542
737
|
toolCalls
|
|
543
738
|
}
|
|
544
739
|
};
|
|
@@ -590,13 +785,14 @@ class OpenRouterExecutor extends LLMExecutor {
|
|
|
590
785
|
stream: false
|
|
591
786
|
}
|
|
592
787
|
});
|
|
593
|
-
const
|
|
788
|
+
const raw = result.choices[0]?.message?.content;
|
|
789
|
+
const content = typeof raw === "string" ? stripThinkTags(raw) : raw;
|
|
594
790
|
if (!content) {
|
|
595
791
|
log3.debug(`call: empty content in choice 0`);
|
|
596
792
|
throw new Error("Empty response from model");
|
|
597
793
|
}
|
|
598
794
|
log3.debug(`call: response ${content.length} chars`);
|
|
599
|
-
return jsonMode ?
|
|
795
|
+
return jsonMode ? parseModelJson(content) : content;
|
|
600
796
|
}
|
|
601
797
|
async chatWithTools(model, options) {
|
|
602
798
|
log3.debug(`chatWithTools: model=${model} msgs=${options.messages.length} tools=${options.tools.length}`);
|
|
@@ -655,7 +851,7 @@ function fromChoice(choice) {
|
|
|
655
851
|
}));
|
|
656
852
|
return {
|
|
657
853
|
message: {
|
|
658
|
-
content: typeof msg.content === "string" ? msg.content : undefined,
|
|
854
|
+
content: typeof msg.content === "string" ? stripThinkTags(msg.content) : undefined,
|
|
659
855
|
toolCalls
|
|
660
856
|
}
|
|
661
857
|
};
|
|
@@ -770,13 +966,14 @@ class OpenAICompatibleExecutor extends LLMExecutor {
|
|
|
770
966
|
reasoningEffort: reasoning
|
|
771
967
|
});
|
|
772
968
|
const data = await this.sendRequest(body, options.reasoningEffort);
|
|
773
|
-
const
|
|
969
|
+
const raw = data.choices?.[0]?.message?.content;
|
|
970
|
+
const content = typeof raw === "string" ? stripThinkTags(raw) : raw;
|
|
774
971
|
if (!content) {
|
|
775
972
|
log4.debug(`call: empty content in choice 0`);
|
|
776
973
|
throw new Error("Empty response from model");
|
|
777
974
|
}
|
|
778
975
|
log4.debug(`call: response ${content.length} chars`);
|
|
779
|
-
return jsonMode ?
|
|
976
|
+
return jsonMode ? parseModelJson(content) : content;
|
|
780
977
|
}
|
|
781
978
|
async chatWithTools(model, options) {
|
|
782
979
|
const reasoning = defaultReasoningEffort(options.reasoningEffort, model, this.models.identity);
|
|
@@ -1090,16 +1287,35 @@ class ZenMuxExecutor extends OpenAICompatibleExecutor {
|
|
|
1090
1287
|
}
|
|
1091
1288
|
}
|
|
1092
1289
|
|
|
1093
|
-
// src/provider/providers/
|
|
1094
|
-
class
|
|
1290
|
+
// src/provider/providers/minimax.ts
|
|
1291
|
+
class MiniMaxBase extends OpenAICompatibleExecutor {
|
|
1292
|
+
buildBody(opts) {
|
|
1293
|
+
const body = super.buildBody(opts);
|
|
1294
|
+
delete body["reasoning_effort"];
|
|
1295
|
+
body["reasoning_split"] = true;
|
|
1296
|
+
body["thinking"] = opts.reasoningEffort && opts.reasoningEffort !== "none" ? { type: "adaptive" } : { type: "disabled" };
|
|
1297
|
+
return body;
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
function minimaxOpts(providerName, baseURL, opts) {
|
|
1301
|
+
return {
|
|
1302
|
+
providerName,
|
|
1303
|
+
baseURL,
|
|
1304
|
+
apiKey: opts.apiKey,
|
|
1305
|
+
conversationModel: opts.conversationModel,
|
|
1306
|
+
identityModel: opts.identityModel
|
|
1307
|
+
};
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
class MiniMaxExecutor extends MiniMaxBase {
|
|
1095
1311
|
constructor(opts) {
|
|
1096
|
-
super(
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1312
|
+
super(minimaxOpts("minimax", "https://api.minimax.io/v1", opts));
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
class MiniMaxCnExecutor extends MiniMaxBase {
|
|
1317
|
+
constructor(opts) {
|
|
1318
|
+
super(minimaxOpts("minimax-cn", "https://api.minimaxi.com/v1", opts));
|
|
1103
1319
|
}
|
|
1104
1320
|
}
|
|
1105
1321
|
|
|
@@ -1252,7 +1468,17 @@ class CloudflareGatewayExecutor extends OpenAICompatibleExecutor {
|
|
|
1252
1468
|
// src/provider/providers/cloudflare_workers.ts
|
|
1253
1469
|
function toCloudflareMessage(m) {
|
|
1254
1470
|
if (m.role === "assistant") {
|
|
1255
|
-
return {
|
|
1471
|
+
return {
|
|
1472
|
+
role: "assistant",
|
|
1473
|
+
content: m.content ?? "",
|
|
1474
|
+
tool_calls: m.toolCalls?.map((c) => {
|
|
1475
|
+
let args = c.function.arguments;
|
|
1476
|
+
try {
|
|
1477
|
+
args = JSON.parse(c.function.arguments);
|
|
1478
|
+
} catch {}
|
|
1479
|
+
return { id: c.id, name: c.function.name, arguments: args };
|
|
1480
|
+
})
|
|
1481
|
+
};
|
|
1256
1482
|
}
|
|
1257
1483
|
if (m.role === "tool") {
|
|
1258
1484
|
return { role: "tool", content: m.content, tool_call_id: m.toolCallId };
|
|
@@ -1314,11 +1540,11 @@ class CloudflareWorkersExecutor extends LLMExecutor {
|
|
|
1314
1540
|
if (data.errors && data.errors.length > 0) {
|
|
1315
1541
|
throw new Error(`cloudflare-workers API error: ${data.errors.map((e) => e.message).join("; ")}`);
|
|
1316
1542
|
}
|
|
1317
|
-
const content = data.result?.response ?? "";
|
|
1543
|
+
const content = stripThinkTags(data.result?.response ?? "");
|
|
1318
1544
|
if (!content) {
|
|
1319
1545
|
throw new Error("Empty response from model");
|
|
1320
1546
|
}
|
|
1321
|
-
return jsonMode ?
|
|
1547
|
+
return jsonMode ? parseModelJson(content) : content;
|
|
1322
1548
|
}
|
|
1323
1549
|
async chatWithTools(model, options) {
|
|
1324
1550
|
const reasoning = defaultReasoningEffort(options.reasoningEffort, model, this.models.identity);
|
|
@@ -1343,7 +1569,7 @@ class CloudflareWorkersExecutor extends LLMExecutor {
|
|
|
1343
1569
|
if (data.errors && data.errors.length > 0) {
|
|
1344
1570
|
throw new Error(`cloudflare-workers API error: ${data.errors.map((e) => e.message).join("; ")}`);
|
|
1345
1571
|
}
|
|
1346
|
-
const content = data.result?.response ?? "";
|
|
1572
|
+
const content = stripThinkTags(data.result?.response ?? "");
|
|
1347
1573
|
const toolCalls = data.result?.tool_calls?.map((c, idx) => ({
|
|
1348
1574
|
id: `call_${idx}`,
|
|
1349
1575
|
function: {
|
|
@@ -1381,7 +1607,8 @@ class AzureOpenAIExecutor extends OpenAICompatibleExecutor {
|
|
|
1381
1607
|
super({
|
|
1382
1608
|
providerName: "azure-openai",
|
|
1383
1609
|
baseURL: `https://${resource || "__resource__"}.openai.azure.com/openai/deployments`,
|
|
1384
|
-
apiKey:
|
|
1610
|
+
apiKey: "",
|
|
1611
|
+
defaultHeaders: { "api-key": opts.apiKey },
|
|
1385
1612
|
conversationModel: opts.conversationModel,
|
|
1386
1613
|
identityModel: opts.identityModel
|
|
1387
1614
|
});
|
|
@@ -1401,7 +1628,8 @@ class AzureCognitiveExecutor extends OpenAICompatibleExecutor {
|
|
|
1401
1628
|
super({
|
|
1402
1629
|
providerName: "azure-cognitive",
|
|
1403
1630
|
baseURL: `https://${resource || "__resource__"}.cognitiveservices.azure.com/openai/deployments`,
|
|
1404
|
-
apiKey:
|
|
1631
|
+
apiKey: "",
|
|
1632
|
+
defaultHeaders: { "api-key": opts.apiKey },
|
|
1405
1633
|
conversationModel: opts.conversationModel,
|
|
1406
1634
|
identityModel: opts.identityModel
|
|
1407
1635
|
});
|
|
@@ -1525,44 +1753,50 @@ class AnthropicExecutor extends LLMExecutor {
|
|
|
1525
1753
|
const reasoning = defaultReasoningEffort(options.reasoningEffort, model, this.models.identity);
|
|
1526
1754
|
const log5 = logger.child("llm:anthropic");
|
|
1527
1755
|
log5.debug(`call: model=${model} jsonSchema=${jsonMode ? options.jsonSchemaName : "-"} msgLen=${options.message.length}`);
|
|
1756
|
+
const outputCap = 4096;
|
|
1528
1757
|
const body = {
|
|
1529
1758
|
model,
|
|
1530
|
-
max_tokens:
|
|
1759
|
+
max_tokens: outputCap,
|
|
1531
1760
|
system: options.instruction,
|
|
1532
1761
|
messages: [{ role: "user", content: options.message }]
|
|
1533
1762
|
};
|
|
1534
1763
|
if (reasoning !== "none") {
|
|
1764
|
+
const budget = REASONING_BUDGET[reasoning];
|
|
1765
|
+
body["max_tokens"] = budget + outputCap;
|
|
1535
1766
|
body["thinking"] = {
|
|
1536
1767
|
type: "enabled",
|
|
1537
|
-
budget_tokens:
|
|
1768
|
+
budget_tokens: budget
|
|
1538
1769
|
};
|
|
1539
1770
|
}
|
|
1540
1771
|
const data = await this.send(body);
|
|
1541
1772
|
if (data.error) {
|
|
1542
1773
|
throw new Error(`anthropic API error: ${data.error.message ?? "unknown"}`);
|
|
1543
1774
|
}
|
|
1544
|
-
const text = (data.content ?? []).filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
1775
|
+
const text = stripThinkTags((data.content ?? []).filter((b) => b.type === "text").map((b) => b.text).join(""));
|
|
1545
1776
|
if (!text) {
|
|
1546
1777
|
throw new Error("Empty response from model");
|
|
1547
1778
|
}
|
|
1548
|
-
return jsonMode ?
|
|
1779
|
+
return jsonMode ? parseModelJson(text) : text;
|
|
1549
1780
|
}
|
|
1550
1781
|
async chatWithTools(model, options) {
|
|
1551
1782
|
const reasoning = defaultReasoningEffort(options.reasoningEffort, model, this.models.identity);
|
|
1552
1783
|
const log5 = logger.child("llm:anthropic");
|
|
1553
1784
|
log5.debug(`chatWithTools: model=${model} msgs=${options.messages.length} tools=${options.tools.length}`);
|
|
1554
1785
|
const { system, msgs } = toAnthropicMessages(options.messages);
|
|
1786
|
+
const outputCap = 4096;
|
|
1555
1787
|
const body = {
|
|
1556
1788
|
model,
|
|
1557
|
-
max_tokens:
|
|
1789
|
+
max_tokens: outputCap,
|
|
1558
1790
|
system: system ?? options.instruction,
|
|
1559
1791
|
messages: msgs,
|
|
1560
1792
|
tools: options.tools.map(toAnthropicTool)
|
|
1561
1793
|
};
|
|
1562
1794
|
if (reasoning !== "none") {
|
|
1795
|
+
const budget = REASONING_BUDGET[reasoning];
|
|
1796
|
+
body["max_tokens"] = budget + outputCap;
|
|
1563
1797
|
body["thinking"] = {
|
|
1564
1798
|
type: "enabled",
|
|
1565
|
-
budget_tokens:
|
|
1799
|
+
budget_tokens: budget
|
|
1566
1800
|
};
|
|
1567
1801
|
}
|
|
1568
1802
|
const data = await this.send(body);
|
|
@@ -1570,7 +1804,7 @@ class AnthropicExecutor extends LLMExecutor {
|
|
|
1570
1804
|
throw new Error(`anthropic API error: ${data.error.message ?? "unknown"}`);
|
|
1571
1805
|
}
|
|
1572
1806
|
const blocks = data.content ?? [];
|
|
1573
|
-
const text = blocks.filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
1807
|
+
const text = stripThinkTags(blocks.filter((b) => b.type === "text").map((b) => b.text).join(""));
|
|
1574
1808
|
const toolCalls = blocks.filter((b) => b.type === "tool_use").map((b) => ({
|
|
1575
1809
|
id: b.id,
|
|
1576
1810
|
function: {
|
|
@@ -1584,11 +1818,11 @@ class AnthropicExecutor extends LLMExecutor {
|
|
|
1584
1818
|
|
|
1585
1819
|
// src/provider/providers/bedrock.ts
|
|
1586
1820
|
import { createHmac, createHash } from "node:crypto";
|
|
1587
|
-
var
|
|
1821
|
+
var pad22 = (n) => n.toString().padStart(2, "0");
|
|
1588
1822
|
function amzDate(now) {
|
|
1589
1823
|
return {
|
|
1590
|
-
date: `${now.getUTCFullYear()}${
|
|
1591
|
-
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`
|
|
1592
1826
|
};
|
|
1593
1827
|
}
|
|
1594
1828
|
function signRequest(opts) {
|
|
@@ -1696,7 +1930,7 @@ function toAnthropicTool2(t) {
|
|
|
1696
1930
|
}
|
|
1697
1931
|
function extractAnthropicContent(data) {
|
|
1698
1932
|
const content = data["content"] ?? [];
|
|
1699
|
-
const text = content.filter((b) => b["type"] === "text").map((b) => b["text"]).join("");
|
|
1933
|
+
const text = stripThinkTags(content.filter((b) => b["type"] === "text").map((b) => b["text"]).join(""));
|
|
1700
1934
|
const toolCalls = content.filter((b) => b["type"] === "tool_use").map((b) => ({
|
|
1701
1935
|
id: b["id"],
|
|
1702
1936
|
function: {
|
|
@@ -1771,7 +2005,7 @@ class BedrockExecutor extends LLMExecutor {
|
|
|
1771
2005
|
if (!text) {
|
|
1772
2006
|
throw new Error("Empty response from model");
|
|
1773
2007
|
}
|
|
1774
|
-
return jsonMode ?
|
|
2008
|
+
return jsonMode ? parseModelJson(text) : text;
|
|
1775
2009
|
}
|
|
1776
2010
|
async chatWithTools(model, options) {
|
|
1777
2011
|
const log5 = logger.child("llm:bedrock");
|
|
@@ -1801,6 +2035,7 @@ var VertexAuthSchema = z5.object({
|
|
|
1801
2035
|
}).loose();
|
|
1802
2036
|
function toGeminiContents(messages) {
|
|
1803
2037
|
const out = [];
|
|
2038
|
+
const nameByCallId = new Map;
|
|
1804
2039
|
for (const m of messages) {
|
|
1805
2040
|
if (m.role === "system")
|
|
1806
2041
|
continue;
|
|
@@ -1814,6 +2049,7 @@ function toGeminiContents(messages) {
|
|
|
1814
2049
|
parts.push({ text: m.content });
|
|
1815
2050
|
if (m.toolCalls) {
|
|
1816
2051
|
for (const c of m.toolCalls) {
|
|
2052
|
+
nameByCallId.set(c.id, c.function.name);
|
|
1817
2053
|
let args = {};
|
|
1818
2054
|
try {
|
|
1819
2055
|
args = c.function.arguments ? JSON.parse(c.function.arguments) : {};
|
|
@@ -1835,21 +2071,26 @@ function toGeminiContents(messages) {
|
|
|
1835
2071
|
}
|
|
1836
2072
|
out.push({
|
|
1837
2073
|
role: "user",
|
|
1838
|
-
parts: [
|
|
2074
|
+
parts: [
|
|
2075
|
+
{
|
|
2076
|
+
functionResponse: {
|
|
2077
|
+
name: nameByCallId.get(m.toolCallId) ?? "",
|
|
2078
|
+
response
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
]
|
|
1839
2082
|
});
|
|
1840
2083
|
}
|
|
1841
2084
|
}
|
|
1842
2085
|
return out;
|
|
1843
2086
|
}
|
|
1844
|
-
function
|
|
2087
|
+
function toGeminiTools(tools) {
|
|
1845
2088
|
return {
|
|
1846
|
-
functionDeclarations:
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
}
|
|
1852
|
-
]
|
|
2089
|
+
functionDeclarations: tools.map((t) => ({
|
|
2090
|
+
name: t.name,
|
|
2091
|
+
description: t.description,
|
|
2092
|
+
parameters: t.parameters ?? { type: "object", properties: {} }
|
|
2093
|
+
}))
|
|
1853
2094
|
};
|
|
1854
2095
|
}
|
|
1855
2096
|
function extractFromGemini(data) {
|
|
@@ -1870,7 +2111,10 @@ function extractFromGemini(data) {
|
|
|
1870
2111
|
});
|
|
1871
2112
|
}
|
|
1872
2113
|
}
|
|
1873
|
-
return {
|
|
2114
|
+
return {
|
|
2115
|
+
text: stripThinkTags(text),
|
|
2116
|
+
toolCalls: toolCalls.length > 0 ? toolCalls : undefined
|
|
2117
|
+
};
|
|
1874
2118
|
}
|
|
1875
2119
|
|
|
1876
2120
|
class VertexExecutor extends LLMExecutor {
|
|
@@ -1936,7 +2180,7 @@ class VertexExecutor extends LLMExecutor {
|
|
|
1936
2180
|
if (!text) {
|
|
1937
2181
|
throw new Error("Empty response from model");
|
|
1938
2182
|
}
|
|
1939
|
-
return jsonMode ?
|
|
2183
|
+
return jsonMode ? parseModelJson(text) : text;
|
|
1940
2184
|
}
|
|
1941
2185
|
async chatWithTools(model, options) {
|
|
1942
2186
|
const log5 = logger.child("llm:vertex");
|
|
@@ -1945,7 +2189,7 @@ class VertexExecutor extends LLMExecutor {
|
|
|
1945
2189
|
const body = {
|
|
1946
2190
|
contents,
|
|
1947
2191
|
systemInstruction: { parts: [{ text: options.instruction }] },
|
|
1948
|
-
tools: options.tools.length > 0 ? [
|
|
2192
|
+
tools: options.tools.length > 0 ? [toGeminiTools(options.tools)] : undefined
|
|
1949
2193
|
};
|
|
1950
2194
|
const data = await this.generate(model, body);
|
|
1951
2195
|
if (data.error) {
|
|
@@ -2053,11 +2297,11 @@ class GitLabDuoExecutor extends LLMExecutor {
|
|
|
2053
2297
|
if (data.error) {
|
|
2054
2298
|
throw new Error(`gitlab-duo API error: ${data.error.message ?? "unknown"}`);
|
|
2055
2299
|
}
|
|
2056
|
-
const content = data.choices?.[0]?.message?.content ?? "";
|
|
2300
|
+
const content = stripThinkTags(data.choices?.[0]?.message?.content ?? "");
|
|
2057
2301
|
if (!content) {
|
|
2058
2302
|
throw new Error("Empty response from model");
|
|
2059
2303
|
}
|
|
2060
|
-
return jsonMode ?
|
|
2304
|
+
return jsonMode ? parseModelJson(content) : content;
|
|
2061
2305
|
}
|
|
2062
2306
|
async chatWithTools(model, options) {
|
|
2063
2307
|
const log5 = logger.child("llm:gitlab-duo");
|
|
@@ -2081,7 +2325,7 @@ class GitLabDuoExecutor extends LLMExecutor {
|
|
|
2081
2325
|
}));
|
|
2082
2326
|
return {
|
|
2083
2327
|
message: {
|
|
2084
|
-
content: choice?.message?.content
|
|
2328
|
+
content: choice?.message?.content ? stripThinkTags(choice.message.content) : undefined,
|
|
2085
2329
|
toolCalls
|
|
2086
2330
|
}
|
|
2087
2331
|
};
|
|
@@ -2172,11 +2416,11 @@ class SnowflakeCortexExecutor extends LLMExecutor {
|
|
|
2172
2416
|
if (data.error) {
|
|
2173
2417
|
throw new Error(`snowflake-cortex API error: ${data.error.message ?? "unknown"}`);
|
|
2174
2418
|
}
|
|
2175
|
-
const content = data.choices?.[0]?.message?.content ?? data.message ?? "";
|
|
2419
|
+
const content = stripThinkTags(data.choices?.[0]?.message?.content ?? data.message ?? "");
|
|
2176
2420
|
if (!content) {
|
|
2177
2421
|
throw new Error("Empty response from model");
|
|
2178
2422
|
}
|
|
2179
|
-
return jsonMode ?
|
|
2423
|
+
return jsonMode ? parseModelJson(content) : content;
|
|
2180
2424
|
}
|
|
2181
2425
|
async chatWithTools(model, options) {
|
|
2182
2426
|
const log5 = logger.child("llm:snowflake-cortex");
|
|
@@ -2194,7 +2438,7 @@ class SnowflakeCortexExecutor extends LLMExecutor {
|
|
|
2194
2438
|
throw new Error(`snowflake-cortex API error: ${data.error.message ?? "unknown"}`);
|
|
2195
2439
|
}
|
|
2196
2440
|
const choice = data.choices?.[0];
|
|
2197
|
-
const content = choice?.message?.content ?? data.message ?? "";
|
|
2441
|
+
const content = stripThinkTags(choice?.message?.content ?? data.message ?? "");
|
|
2198
2442
|
const toolCalls = choice?.message?.tool_calls?.map((c) => ({
|
|
2199
2443
|
id: c.id,
|
|
2200
2444
|
function: {
|
|
@@ -2233,7 +2477,8 @@ register("stackit", StackitExecutor);
|
|
|
2233
2477
|
register("gmi", GmiExecutor);
|
|
2234
2478
|
register("zai", ZaiExecutor);
|
|
2235
2479
|
register("zenmux", ZenMuxExecutor);
|
|
2236
|
-
register("
|
|
2480
|
+
register("minimax", MiniMaxExecutor);
|
|
2481
|
+
register("minimax-cn", MiniMaxCnExecutor);
|
|
2237
2482
|
register("ionet", IoNetExecutor);
|
|
2238
2483
|
register("baseten", BasetenExecutor);
|
|
2239
2484
|
register("cortecs", CortecsExecutor);
|
|
@@ -2267,14 +2512,14 @@ var llm = new Proxy({}, {
|
|
|
2267
2512
|
// src/provider/promptLoader.ts
|
|
2268
2513
|
import { existsSync as existsSync2 } from "fs";
|
|
2269
2514
|
import { readFile as readFile2 } from "fs/promises";
|
|
2270
|
-
import path, { dirname as
|
|
2515
|
+
import path, { dirname as dirname2 } from "path";
|
|
2271
2516
|
import { fileURLToPath } from "url";
|
|
2272
2517
|
var log5 = logger.child("prompt-loader");
|
|
2273
2518
|
function fileName(promptKey) {
|
|
2274
2519
|
return promptKey.toLowerCase() + ".md";
|
|
2275
2520
|
}
|
|
2276
2521
|
var __filename2 = fileURLToPath(import.meta.url);
|
|
2277
|
-
var __dirname2 =
|
|
2522
|
+
var __dirname2 = dirname2(__filename2);
|
|
2278
2523
|
var PROMPTS_DIR = existsSync2(path.resolve(__dirname2, "prompts")) ? path.resolve(__dirname2, "prompts") : path.resolve(__dirname2, "../../prompts");
|
|
2279
2524
|
async function loadPrompt(promptKey) {
|
|
2280
2525
|
const filePath = path.join(PROMPTS_DIR, fileName(promptKey));
|
|
@@ -2399,14 +2644,14 @@ function translateMessageHistory(personaName, entries) {
|
|
|
2399
2644
|
}
|
|
2400
2645
|
|
|
2401
2646
|
// src/brain/schedule.ts
|
|
2402
|
-
function
|
|
2647
|
+
function pad23(n) {
|
|
2403
2648
|
return n < 10 ? `0${n}` : `${n}`;
|
|
2404
2649
|
}
|
|
2405
2650
|
function formatDateKey(d) {
|
|
2406
|
-
return `${d.getFullYear()}-${
|
|
2651
|
+
return `${d.getFullYear()}-${pad23(d.getMonth() + 1)}-${pad23(d.getDate())}`;
|
|
2407
2652
|
}
|
|
2408
2653
|
function formatMonthKey(d) {
|
|
2409
|
-
return `${d.getFullYear()}-${
|
|
2654
|
+
return `${d.getFullYear()}-${pad23(d.getMonth() + 1)}`;
|
|
2410
2655
|
}
|
|
2411
2656
|
|
|
2412
2657
|
// src/brain/memory.ts
|
|
@@ -2494,22 +2739,22 @@ class Brain {
|
|
|
2494
2739
|
db;
|
|
2495
2740
|
space;
|
|
2496
2741
|
brainbase;
|
|
2497
|
-
memory;
|
|
2498
2742
|
availabilityCache = new Map;
|
|
2499
|
-
|
|
2743
|
+
memory;
|
|
2744
|
+
constructor(db, space, brainbase, memory) {
|
|
2500
2745
|
this.db = db;
|
|
2501
2746
|
this.space = space;
|
|
2502
2747
|
this.brainbase = brainbase;
|
|
2503
|
-
this.memory = memory;
|
|
2748
|
+
this.memory = memory ?? new Memory(this.db, this.space);
|
|
2504
2749
|
log7.debug(`Brain constructed: id=${brainbase.brainId} name=${brainbase.displayName} space=${space.name}`);
|
|
2505
2750
|
}
|
|
2506
2751
|
async createDailySchedule(datetime) {
|
|
2507
|
-
const
|
|
2508
|
-
log7.debug(`createDailySchedule: starting for ${
|
|
2752
|
+
const dateKey2 = formatDateKey(datetime);
|
|
2753
|
+
log7.debug(`createDailySchedule: starting for ${dateKey2}`);
|
|
2509
2754
|
try {
|
|
2510
|
-
const existing = await this.memory.get(`daily-schedule:${
|
|
2755
|
+
const existing = await this.memory.get(`daily-schedule:${dateKey2}`);
|
|
2511
2756
|
if (existing) {
|
|
2512
|
-
log7.debug(`createDailySchedule: cache hit for ${
|
|
2757
|
+
log7.debug(`createDailySchedule: cache hit for ${dateKey2}`);
|
|
2513
2758
|
try {
|
|
2514
2759
|
return JSON.parse(existing.content);
|
|
2515
2760
|
} catch (parseErr) {
|
|
@@ -2537,7 +2782,7 @@ class Brain {
|
|
|
2537
2782
|
}
|
|
2538
2783
|
const instruction = await loadPrompt("DAILY_SCHEDULE");
|
|
2539
2784
|
const promptMessage = [
|
|
2540
|
-
`Target date: ${
|
|
2785
|
+
`Target date: ${dateKey2} (${datetime.toLocaleDateString("en-US", { weekday: "long" })})`,
|
|
2541
2786
|
`Personality: ${this.brainbase.baseSystemPrompt}`,
|
|
2542
2787
|
monthlySummary ? `Monthly summary for this day: ${monthlySummary}` : "(no monthly summary available for this date)",
|
|
2543
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)"}`,
|
|
@@ -2548,6 +2793,7 @@ class Brain {
|
|
|
2548
2793
|
`);
|
|
2549
2794
|
log7.debug(`createDailySchedule: calling identity model`);
|
|
2550
2795
|
const schedule = await llm.call(llm.models.identity, {
|
|
2796
|
+
caller: "daily-schedule",
|
|
2551
2797
|
instruction,
|
|
2552
2798
|
message: promptMessage,
|
|
2553
2799
|
jsonSchemaName: "daily-schedule",
|
|
@@ -2555,15 +2801,15 @@ class Brain {
|
|
|
2555
2801
|
});
|
|
2556
2802
|
log7.debug(`createDailySchedule: model returned ${schedule.items.length} slots`);
|
|
2557
2803
|
await this.memory.add({
|
|
2558
|
-
customId: `daily-schedule:${
|
|
2804
|
+
customId: `daily-schedule:${dateKey2}`,
|
|
2559
2805
|
content: JSON.stringify(schedule),
|
|
2560
2806
|
metadata: {
|
|
2561
2807
|
kind: "schedule",
|
|
2562
2808
|
source: "createDailySchedule",
|
|
2563
|
-
date:
|
|
2809
|
+
date: dateKey2
|
|
2564
2810
|
}
|
|
2565
2811
|
});
|
|
2566
|
-
log7.debug(`createDailySchedule: persisted ${
|
|
2812
|
+
log7.debug(`createDailySchedule: persisted ${dateKey2}`);
|
|
2567
2813
|
return schedule;
|
|
2568
2814
|
} catch (error) {
|
|
2569
2815
|
let reason = error instanceof Error ? error.message + `(${error.name})` : String(error);
|
|
@@ -2575,17 +2821,17 @@ class Brain {
|
|
|
2575
2821
|
}
|
|
2576
2822
|
async regenerateSchedules() {
|
|
2577
2823
|
const today = new Date;
|
|
2824
|
+
const nextMonth = new Date(today.getFullYear(), today.getMonth() + 1, today.getDate());
|
|
2825
|
+
await this.createMonthlySchedule(nextMonth);
|
|
2578
2826
|
const tomorrow = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 1);
|
|
2579
2827
|
await this.createDailySchedule(tomorrow);
|
|
2580
2828
|
await this.createDailySchedule(today);
|
|
2581
|
-
const nextMonth = new Date(today.getFullYear(), today.getMonth() + 1, today.getDate());
|
|
2582
|
-
await this.createMonthlySchedule(nextMonth);
|
|
2583
2829
|
}
|
|
2584
2830
|
async createMonthlySchedule(datetime) {
|
|
2585
2831
|
const year = datetime.getMonth() === 11 ? datetime.getFullYear() + 1 : datetime.getFullYear();
|
|
2586
2832
|
const month = (datetime.getMonth() + 1) % 12;
|
|
2587
2833
|
const daysInMonth = new Date(year, month + 1, 0).getDate();
|
|
2588
|
-
const monthKey = `${year}-${
|
|
2834
|
+
const monthKey = `${year}-${pad23(month + 1)}`;
|
|
2589
2835
|
log7.debug(`createMonthlySchedule: starting for ${monthKey}`);
|
|
2590
2836
|
try {
|
|
2591
2837
|
const existing = await this.memory.get(`monthly-schedule:${monthKey}`);
|
|
@@ -2598,7 +2844,7 @@ class Brain {
|
|
|
2598
2844
|
}
|
|
2599
2845
|
}
|
|
2600
2846
|
const twoMonthsAgo = new Date(year, month - 2, 1);
|
|
2601
|
-
const twoMonthsAgoKey = `${twoMonthsAgo.getFullYear()}-${
|
|
2847
|
+
const twoMonthsAgoKey = `${twoMonthsAgo.getFullYear()}-${pad23(twoMonthsAgo.getMonth() + 1)}`;
|
|
2602
2848
|
log7.debug(`createMonthlySchedule: gathering context (history, ${twoMonthsAgoKey})`);
|
|
2603
2849
|
const [history, twoMonthsAgoStored] = await Promise.all([
|
|
2604
2850
|
this.getHistoryFacts(),
|
|
@@ -2626,6 +2872,7 @@ class Brain {
|
|
|
2626
2872
|
`);
|
|
2627
2873
|
log7.debug(`createMonthlySchedule: calling identity model`);
|
|
2628
2874
|
const schedule = await llm.call(llm.models.identity, {
|
|
2875
|
+
caller: "monthly-schedule",
|
|
2629
2876
|
instruction,
|
|
2630
2877
|
message: promptMessage,
|
|
2631
2878
|
jsonSchemaName: "monthly-schedule",
|
|
@@ -2656,11 +2903,11 @@ class Brain {
|
|
|
2656
2903
|
}
|
|
2657
2904
|
log7.debug(`sleepMemory: starting, ${history.length} messages`);
|
|
2658
2905
|
try {
|
|
2659
|
-
const
|
|
2906
|
+
const dateKey2 = formatDateKey(datetime);
|
|
2660
2907
|
const instruction = await loadPrompt("MEMOIR");
|
|
2661
2908
|
const historyBlock = translateMessageHistory(this.brainbase.displayName, history);
|
|
2662
2909
|
const promptMessage = [
|
|
2663
|
-
`Date: ${
|
|
2910
|
+
`Date: ${dateKey2}`,
|
|
2664
2911
|
`Personality: ${this.brainbase.baseSystemPrompt}`,
|
|
2665
2912
|
`Conversation log:`,
|
|
2666
2913
|
historyBlock
|
|
@@ -2669,20 +2916,21 @@ class Brain {
|
|
|
2669
2916
|
`);
|
|
2670
2917
|
log7.debug(`sleepMemory: calling identity model`);
|
|
2671
2918
|
const memoir = await llm.call(llm.models.identity, {
|
|
2919
|
+
caller: "sleep-memory",
|
|
2672
2920
|
instruction,
|
|
2673
2921
|
message: promptMessage
|
|
2674
2922
|
});
|
|
2675
2923
|
log7.debug(`sleepMemory: model returned ${memoir.length} chars`);
|
|
2676
2924
|
await this.memory.add({
|
|
2677
|
-
customId: `daily-journal:${
|
|
2925
|
+
customId: `daily-journal:${dateKey2}`,
|
|
2678
2926
|
content: memoir,
|
|
2679
2927
|
metadata: {
|
|
2680
2928
|
kind: "daily-journal",
|
|
2681
2929
|
source: "sleepMemory",
|
|
2682
|
-
date:
|
|
2930
|
+
date: dateKey2
|
|
2683
2931
|
}
|
|
2684
2932
|
});
|
|
2685
|
-
log7.debug(`sleepMemory: journal persisted for ${
|
|
2933
|
+
log7.debug(`sleepMemory: journal persisted for ${dateKey2}`);
|
|
2686
2934
|
return memoir;
|
|
2687
2935
|
} catch (error) {
|
|
2688
2936
|
const reason = error instanceof Error ? error.message : String(error);
|
|
@@ -2691,22 +2939,22 @@ class Brain {
|
|
|
2691
2939
|
}
|
|
2692
2940
|
}
|
|
2693
2941
|
async getTodayScheduledAvailability(datetime) {
|
|
2694
|
-
const
|
|
2942
|
+
const dateKey2 = formatDateKey(datetime);
|
|
2695
2943
|
try {
|
|
2696
|
-
const cached = this.availabilityCache.get(
|
|
2944
|
+
const cached = this.availabilityCache.get(dateKey2);
|
|
2697
2945
|
if (cached) {
|
|
2698
|
-
log7.debug(`getTodayScheduledAvailability: cache hit for ${
|
|
2946
|
+
log7.debug(`getTodayScheduledAvailability: cache hit for ${dateKey2}`);
|
|
2699
2947
|
return cached;
|
|
2700
2948
|
}
|
|
2701
|
-
const stored = await this.memory.get(`daily-schedule:${
|
|
2949
|
+
const stored = await this.memory.get(`daily-schedule:${dateKey2}`);
|
|
2702
2950
|
if (!stored) {
|
|
2703
|
-
log7.debug(`getTodayScheduledAvailability: no schedule for ${
|
|
2951
|
+
log7.debug(`getTodayScheduledAvailability: no schedule for ${dateKey2}`);
|
|
2704
2952
|
return null;
|
|
2705
2953
|
}
|
|
2706
2954
|
const dailySchedule = JSON.parse(stored.content);
|
|
2707
2955
|
const availability = await this.deriveAvailabilityFromSchedule(dailySchedule);
|
|
2708
|
-
this.availabilityCache.set(
|
|
2709
|
-
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}`);
|
|
2710
2958
|
return availability;
|
|
2711
2959
|
} catch (error) {
|
|
2712
2960
|
const reason = error instanceof Error ? error.message : String(error);
|
|
@@ -2717,7 +2965,7 @@ class Brain {
|
|
|
2717
2965
|
async getAvailability(datetime = new Date) {
|
|
2718
2966
|
const h = datetime.getHours();
|
|
2719
2967
|
const m = datetime.getMinutes();
|
|
2720
|
-
const hhmm = `${
|
|
2968
|
+
const hhmm = `${pad23(h)}:${pad23(m)}`;
|
|
2721
2969
|
const current = h * 60 + m;
|
|
2722
2970
|
const toMinutes = (s) => {
|
|
2723
2971
|
const [hh = 0, mm = 0] = s.split(":").map((x) => parseInt(x, 10));
|
|
@@ -2730,10 +2978,10 @@ class Brain {
|
|
|
2730
2978
|
return result;
|
|
2731
2979
|
}
|
|
2732
2980
|
async getCurrentAndAdjacentSlots(now) {
|
|
2733
|
-
const
|
|
2734
|
-
const stored = await this.memory.get(`daily-schedule:${
|
|
2981
|
+
const dateKey2 = formatDateKey(now);
|
|
2982
|
+
const stored = await this.memory.get(`daily-schedule:${dateKey2}`);
|
|
2735
2983
|
if (!stored) {
|
|
2736
|
-
log7.debug(`getCurrentAndAdjacentSlots: no schedule for ${
|
|
2984
|
+
log7.debug(`getCurrentAndAdjacentSlots: no schedule for ${dateKey2}`);
|
|
2737
2985
|
return [];
|
|
2738
2986
|
}
|
|
2739
2987
|
let schedule;
|
|
@@ -2766,6 +3014,7 @@ class Brain {
|
|
|
2766
3014
|
personality: this.brainbase.baseSystemPrompt
|
|
2767
3015
|
});
|
|
2768
3016
|
const result = await llm.call(llm.models.identity, {
|
|
3017
|
+
caller: "availability",
|
|
2769
3018
|
instruction,
|
|
2770
3019
|
message: promptMessage,
|
|
2771
3020
|
jsonSchemaName: "availability",
|
|
@@ -2841,6 +3090,7 @@ class Brain {
|
|
|
2841
3090
|
let choice;
|
|
2842
3091
|
try {
|
|
2843
3092
|
choice = await llm.chatWithTools(llm.models.conversation, {
|
|
3093
|
+
caller: initiate ? "start-conversation" : "send-message",
|
|
2844
3094
|
instruction: `${this.brainbase.baseSystemPrompt}
|
|
2845
3095
|
|
|
2846
3096
|
${instruction}`,
|
|
@@ -2912,11 +3162,11 @@ ${instruction}`,
|
|
|
2912
3162
|
${facts || "(none indexed)"}`;
|
|
2913
3163
|
}
|
|
2914
3164
|
async buildScheduleBlock(now) {
|
|
2915
|
-
const
|
|
3165
|
+
const dateKey2 = formatDateKey(now);
|
|
2916
3166
|
const currentSlots = await this.getCurrentAndAdjacentSlots(now);
|
|
2917
3167
|
const currentBlock = currentSlots.length > 0 ? `Currently (around ${now.toTimeString().slice(0, 5)}):
|
|
2918
3168
|
${currentSlots.map((s) => ` ${s.start}-${s.end} ${s.activity}${s.notes ? ` (${s.notes})` : ""}`).join(`
|
|
2919
|
-
`)}` : `Currently (${
|
|
3169
|
+
`)}` : `Currently (${dateKey2} ${now.toTimeString().slice(0, 5)}): (no matching slot in today's schedule)`;
|
|
2920
3170
|
const days = [
|
|
2921
3171
|
{
|
|
2922
3172
|
label: "Yesterday",
|
|
@@ -2939,9 +3189,9 @@ ${currentBlock}
|
|
|
2939
3189
|
${blocks.join(`
|
|
2940
3190
|
`)}`;
|
|
2941
3191
|
}
|
|
2942
|
-
async getDailyScheduleSummary(
|
|
3192
|
+
async getDailyScheduleSummary(dateKey2) {
|
|
2943
3193
|
try {
|
|
2944
|
-
const stored = await this.memory.get(`daily-schedule:${
|
|
3194
|
+
const stored = await this.memory.get(`daily-schedule:${dateKey2}`);
|
|
2945
3195
|
if (!stored)
|
|
2946
3196
|
return null;
|
|
2947
3197
|
const schedule = JSON.parse(stored.content);
|
|
@@ -3012,6 +3262,7 @@ ${blocks.join(`
|
|
|
3012
3262
|
const personaInitInstruction = await loadPrompt("PERSONA_INIT");
|
|
3013
3263
|
log7.debug(`Brain.create: generating description`);
|
|
3014
3264
|
const description = await llm.call(llm.models.identity, {
|
|
3265
|
+
caller: "persona-init",
|
|
3015
3266
|
instruction: personaInitInstruction,
|
|
3016
3267
|
message: seed
|
|
3017
3268
|
});
|
|
@@ -3019,6 +3270,7 @@ ${blocks.join(`
|
|
|
3019
3270
|
const personaSystemInstruction = await loadPrompt("PERSONA_BASE_SYSTEM_PROMPT");
|
|
3020
3271
|
log7.debug(`Brain.create: generating base system prompt + dials`);
|
|
3021
3272
|
const generated = await llm.call(llm.models.identity, {
|
|
3273
|
+
caller: "base-system-prompt",
|
|
3022
3274
|
instruction: personaSystemInstruction,
|
|
3023
3275
|
message: description,
|
|
3024
3276
|
jsonSchemaName: "base-system-prompt",
|
|
@@ -3056,9 +3308,12 @@ ${personaSystemFixed}`;
|
|
|
3056
3308
|
log7.debug(`Brain.create: brainbase saved (id=${brainId})`);
|
|
3057
3309
|
return { brainId, brain: new Brain(db, space, brainbase, memory) };
|
|
3058
3310
|
} catch (error) {
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3311
|
+
let reason = error instanceof Error ? `${error.message} (${error.name})` : String(error);
|
|
3312
|
+
if (error instanceof BadRequestResponseError) {
|
|
3313
|
+
reason = `${reason} ${JSON.stringify(error.body)}`;
|
|
3314
|
+
}
|
|
3315
|
+
log7.debug(`Brain.create failed: ${reason}`);
|
|
3316
|
+
return { error: reason };
|
|
3062
3317
|
}
|
|
3063
3318
|
}
|
|
3064
3319
|
static async delete(brainId) {
|
|
@@ -3176,10 +3431,18 @@ var SLEEP_MEMORY_CRON_KEY = "__sleep-memory__";
|
|
|
3176
3431
|
var SLEEP_MEMORY_CRON_PATTERN = "0 * * * *";
|
|
3177
3432
|
var START_CONVERSATION_CRON_KEY = "__start-conversation__";
|
|
3178
3433
|
var START_CONVERSATION_CRON_PATTERN = "*/10 * * * *";
|
|
3179
|
-
var
|
|
3180
|
-
var
|
|
3181
|
-
var
|
|
3182
|
-
var
|
|
3434
|
+
var SCHEDULE_CRON_KEY = "__schedule__";
|
|
3435
|
+
var SCHEDULE_CRON_PATTERN = "0 0 * * *";
|
|
3436
|
+
var SCHEDULE_NOON_CRON_KEY = "__schedule-noon__";
|
|
3437
|
+
var SCHEDULE_NOON_CRON_PATTERN = "0 12 * * *";
|
|
3438
|
+
var DO_ACTIONS = ["generateSchedule", "sleepMemory"];
|
|
3439
|
+
var VIEW_THINGS = [
|
|
3440
|
+
"daily-schedule",
|
|
3441
|
+
"monthly-schedule",
|
|
3442
|
+
"sending-queue",
|
|
3443
|
+
"deferred-queue",
|
|
3444
|
+
"today-availability"
|
|
3445
|
+
];
|
|
3183
3446
|
|
|
3184
3447
|
class BaseChannel {
|
|
3185
3448
|
brain;
|
|
@@ -3199,24 +3462,27 @@ class BaseChannel {
|
|
|
3199
3462
|
static activeChannels = new Map;
|
|
3200
3463
|
constructor(brain) {
|
|
3201
3464
|
this.brain = brain;
|
|
3202
|
-
this.registerCron(SLEEP_MEMORY_CRON_KEY, SLEEP_MEMORY_CRON_PATTERN,
|
|
3203
|
-
|
|
3465
|
+
this.registerCron(SLEEP_MEMORY_CRON_KEY, SLEEP_MEMORY_CRON_PATTERN, () => this.runSleepMemory());
|
|
3466
|
+
this.registerCron(SCHEDULE_CRON_KEY, SCHEDULE_CRON_PATTERN, () => this.regenerateSchedules());
|
|
3467
|
+
this.registerCron(SCHEDULE_NOON_CRON_KEY, SCHEDULE_NOON_CRON_PATTERN, () => this.regenerateSchedules());
|
|
3468
|
+
this.registerCron(START_CONVERSATION_CRON_KEY, START_CONVERSATION_CRON_PATTERN, () => this.runStartConversation());
|
|
3469
|
+
}
|
|
3470
|
+
async runSleepMemory(force = false) {
|
|
3471
|
+
const dateKey2 = formatDateKey(new Date);
|
|
3472
|
+
if (!force) {
|
|
3204
3473
|
const availability = await this.brain.getAvailability();
|
|
3205
3474
|
if (availability.status !== "offline") {
|
|
3206
3475
|
logger.debug(`sleepMemory cron: skip — availability=${availability.status}`);
|
|
3207
3476
|
return;
|
|
3208
3477
|
}
|
|
3209
|
-
const existing = await this.brain.memory.get(`daily-journal:${
|
|
3478
|
+
const existing = await this.brain.memory.get(`daily-journal:${dateKey2}`);
|
|
3210
3479
|
if (existing) {
|
|
3211
|
-
logger.debug(`sleepMemory cron: skip — journal for ${
|
|
3480
|
+
logger.debug(`sleepMemory cron: skip — journal for ${dateKey2} exists`);
|
|
3212
3481
|
return;
|
|
3213
3482
|
}
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
this.registerCron(DAILY_SCHEDULE_CRON_KEY, DAILY_SCHEDULE_CRON_PATTERN, () => this.regenerateSchedules());
|
|
3218
|
-
this.registerCron(DAILY_SCHEDULE_NOON_CRON_KEY, DAILY_SCHEDULE_NOON_CRON_PATTERN, () => this.regenerateSchedules());
|
|
3219
|
-
this.registerCron(START_CONVERSATION_CRON_KEY, START_CONVERSATION_CRON_PATTERN, () => this.runStartConversation());
|
|
3483
|
+
}
|
|
3484
|
+
const history = await this.getMessageHistoryBetween(new Date(Date.now() - 24 * 60 * 60 * 1000), new Date);
|
|
3485
|
+
await this.brain.sleepMemory(new Date, history);
|
|
3220
3486
|
}
|
|
3221
3487
|
async regenerateSchedules() {
|
|
3222
3488
|
logger.debug(`regenerateSchedules: tick for ${this.brain.brainbase.displayName}`);
|
|
@@ -3237,8 +3503,8 @@ class BaseChannel {
|
|
|
3237
3503
|
return;
|
|
3238
3504
|
}
|
|
3239
3505
|
const now = new Date;
|
|
3240
|
-
const
|
|
3241
|
-
const count = this.startConversationCounters.get(
|
|
3506
|
+
const dateKey2 = formatDateKey(now);
|
|
3507
|
+
const count = this.startConversationCounters.get(dateKey2) ?? 0;
|
|
3242
3508
|
const countThreshold = this.brain.brainbase.startConversationCountThreshold;
|
|
3243
3509
|
if (count >= countThreshold)
|
|
3244
3510
|
return;
|
|
@@ -3251,7 +3517,7 @@ class BaseChannel {
|
|
|
3251
3517
|
});
|
|
3252
3518
|
if (replies.length === 0)
|
|
3253
3519
|
return;
|
|
3254
|
-
this.startConversationCounters.set(
|
|
3520
|
+
this.startConversationCounters.set(dateKey2, count + 1);
|
|
3255
3521
|
this.startConversationTimeout = true;
|
|
3256
3522
|
setTimeout(() => {
|
|
3257
3523
|
this.startConversationTimeout = false;
|
|
@@ -3280,7 +3546,7 @@ class BaseChannel {
|
|
|
3280
3546
|
}, callback);
|
|
3281
3547
|
}
|
|
3282
3548
|
getRegisteredCrons() {
|
|
3283
|
-
return scheduledJobs.filter((c) => c.name && c.name.startsWith(this.resolveCronPrefix()))
|
|
3549
|
+
return scheduledJobs.filter((c) => c.name && c.name.startsWith(this.resolveCronPrefix()));
|
|
3284
3550
|
}
|
|
3285
3551
|
pauseCron(key) {
|
|
3286
3552
|
const job = scheduledJobs.find((c) => c.name === this.resolveCronName(key));
|
|
@@ -3371,6 +3637,13 @@ class BaseChannel {
|
|
|
3371
3637
|
this.isChattingDebounce = null;
|
|
3372
3638
|
}, IS_CHATTING_DEBOUNCE_MS);
|
|
3373
3639
|
}
|
|
3640
|
+
async initAvailability() {
|
|
3641
|
+
const current = await this.brain.getAvailability();
|
|
3642
|
+
this.previousAvailability = current.status;
|
|
3643
|
+
logger.debug(`initAvailability: ${current.status}`);
|
|
3644
|
+
await this.setAvailability(current.status);
|
|
3645
|
+
this.ensureAvailabilityWatcher();
|
|
3646
|
+
}
|
|
3374
3647
|
ensureAvailabilityWatcher() {
|
|
3375
3648
|
if (this.isCronStarted(AVAILABILITY_WATCHER_KEY))
|
|
3376
3649
|
return;
|
|
@@ -3380,6 +3653,9 @@ class BaseChannel {
|
|
|
3380
3653
|
const prev = this.previousAvailability;
|
|
3381
3654
|
this.previousAvailability = current.status;
|
|
3382
3655
|
logger.debug(`availabilityWatcher: ${prev ?? "(initial)"} → ${current.status}`);
|
|
3656
|
+
if (prev !== current.status) {
|
|
3657
|
+
await this.setAvailability(current.status);
|
|
3658
|
+
}
|
|
3383
3659
|
if (prev !== null && prev !== "online" && current.status === "online") {
|
|
3384
3660
|
await this.flushDeferred();
|
|
3385
3661
|
}
|
|
@@ -3428,6 +3704,80 @@ class BaseChannel {
|
|
|
3428
3704
|
static all() {
|
|
3429
3705
|
return Array.from(BaseChannel.activeChannels.values());
|
|
3430
3706
|
}
|
|
3707
|
+
static async forceDo(brainId, action) {
|
|
3708
|
+
const channel = BaseChannel.activeChannels.get(brainId);
|
|
3709
|
+
if (!channel) {
|
|
3710
|
+
return {
|
|
3711
|
+
ok: false,
|
|
3712
|
+
error: `no active channel for brain "${brainId}" (is it activated and daemon running?)`
|
|
3713
|
+
};
|
|
3714
|
+
}
|
|
3715
|
+
const displayName = channel.brain.brainbase.displayName;
|
|
3716
|
+
logger.info(`do ${action}: forcing for "${displayName}" (${brainId})`);
|
|
3717
|
+
if (action === "generateSchedule") {
|
|
3718
|
+
await channel.regenerateSchedules();
|
|
3719
|
+
} else {
|
|
3720
|
+
await channel.runSleepMemory(true);
|
|
3721
|
+
}
|
|
3722
|
+
return { ok: true, displayName };
|
|
3723
|
+
}
|
|
3724
|
+
static async view(brainId, thing) {
|
|
3725
|
+
const channel = BaseChannel.activeChannels.get(brainId);
|
|
3726
|
+
if (!channel) {
|
|
3727
|
+
return {
|
|
3728
|
+
ok: false,
|
|
3729
|
+
error: `no active channel for brain "${brainId}" (is it activated and daemon running?)`
|
|
3730
|
+
};
|
|
3731
|
+
}
|
|
3732
|
+
const displayName = channel.brain.brainbase.displayName;
|
|
3733
|
+
logger.debug(`view ${thing}: "${displayName}" (${brainId})`);
|
|
3734
|
+
return {
|
|
3735
|
+
ok: true,
|
|
3736
|
+
displayName,
|
|
3737
|
+
value: await channel.readView(thing)
|
|
3738
|
+
};
|
|
3739
|
+
}
|
|
3740
|
+
async readView(thing) {
|
|
3741
|
+
const now = new Date;
|
|
3742
|
+
switch (thing) {
|
|
3743
|
+
case "daily-schedule": {
|
|
3744
|
+
const key = formatDateKey(now);
|
|
3745
|
+
const stored = await this.brain.memory.get(`daily-schedule:${key}`);
|
|
3746
|
+
if (!stored)
|
|
3747
|
+
return null;
|
|
3748
|
+
try {
|
|
3749
|
+
return { key, schedule: JSON.parse(stored.content) };
|
|
3750
|
+
} catch {
|
|
3751
|
+
return { key, raw: stored.content };
|
|
3752
|
+
}
|
|
3753
|
+
}
|
|
3754
|
+
case "monthly-schedule": {
|
|
3755
|
+
const key = formatMonthKey(now);
|
|
3756
|
+
const stored = await this.brain.memory.get(`monthly-schedule:${key}`);
|
|
3757
|
+
if (!stored)
|
|
3758
|
+
return null;
|
|
3759
|
+
try {
|
|
3760
|
+
return { key, schedule: JSON.parse(stored.content) };
|
|
3761
|
+
} catch {
|
|
3762
|
+
return { key, raw: stored.content };
|
|
3763
|
+
}
|
|
3764
|
+
}
|
|
3765
|
+
case "sending-queue":
|
|
3766
|
+
return this.isSendingQueue.map((m) => ({
|
|
3767
|
+
sender: m.sender,
|
|
3768
|
+
time: m.time.toISOString(),
|
|
3769
|
+
content: m.content
|
|
3770
|
+
}));
|
|
3771
|
+
case "deferred-queue":
|
|
3772
|
+
return this.deferredQueue.map((m) => ({
|
|
3773
|
+
sender: m.sender,
|
|
3774
|
+
time: m.time.toISOString(),
|
|
3775
|
+
content: m.content
|
|
3776
|
+
}));
|
|
3777
|
+
case "today-availability":
|
|
3778
|
+
return await this.brain.getTodayScheduledAvailability(now);
|
|
3779
|
+
}
|
|
3780
|
+
}
|
|
3431
3781
|
static async shutdownAll() {
|
|
3432
3782
|
await Promise.all(BaseChannel.all().map((c) => c.shutdown()));
|
|
3433
3783
|
}
|
|
@@ -3440,8 +3790,8 @@ class BaseChannel {
|
|
|
3440
3790
|
logger.debug(`shutdown: done`);
|
|
3441
3791
|
}
|
|
3442
3792
|
stopOwnCrons() {
|
|
3443
|
-
for (const
|
|
3444
|
-
|
|
3793
|
+
for (const cron of this.getRegisteredCrons()) {
|
|
3794
|
+
cron.stop();
|
|
3445
3795
|
}
|
|
3446
3796
|
}
|
|
3447
3797
|
clearTimers() {
|
|
@@ -3551,6 +3901,7 @@ class DiscordChannel extends BaseChannel {
|
|
|
3551
3901
|
logger.debug(`DiscordClientReady: resolving configured channel ${channelId}`);
|
|
3552
3902
|
this.resolveConfiguredChannel(channelId);
|
|
3553
3903
|
}
|
|
3904
|
+
this.initAvailability();
|
|
3554
3905
|
});
|
|
3555
3906
|
this.client.on(Events.MessageCreate, (msg) => {
|
|
3556
3907
|
if (msg.author.bot)
|
|
@@ -3719,6 +4070,7 @@ class TelegramChannel extends BaseChannel {
|
|
|
3719
4070
|
this.registerActive();
|
|
3720
4071
|
this.bot.onStart(({ info }) => {
|
|
3721
4072
|
logger.success(`Telegram ready as @${info.username}`);
|
|
4073
|
+
this.initAvailability();
|
|
3722
4074
|
});
|
|
3723
4075
|
this.bot.on("message", (ctx) => {
|
|
3724
4076
|
if (ctx.from?.isBot())
|
|
@@ -3811,8 +4163,8 @@ class TelegramChannel extends BaseChannel {
|
|
|
3811
4163
|
|
|
3812
4164
|
// src/utils/daemonClient.ts
|
|
3813
4165
|
import { connect } from "node:net";
|
|
3814
|
-
import { join as
|
|
3815
|
-
var DAEMON_SOCKET_PATH =
|
|
4166
|
+
import { join as join4 } from "node:path";
|
|
4167
|
+
var DAEMON_SOCKET_PATH = join4(config.brainboxRoot, "daemon.sock");
|
|
3816
4168
|
async function sendToDaemon(payload) {
|
|
3817
4169
|
logger.debug(`sendToDaemon: command="${payload.command}" args=${JSON.stringify(payload.args) ?? "null"}`);
|
|
3818
4170
|
let reply;
|
|
@@ -3874,6 +4226,7 @@ function exchangeOnce(payload) {
|
|
|
3874
4226
|
// src/commands/daemon.ts
|
|
3875
4227
|
import { createServer } from "node:net";
|
|
3876
4228
|
import { chmodSync, unlinkSync } from "node:fs";
|
|
4229
|
+
import { join as join5 } from "node:path";
|
|
3877
4230
|
|
|
3878
4231
|
// src/commands/daemon/commands.ts
|
|
3879
4232
|
var log8 = logger.child("daemon-cmd");
|
|
@@ -3934,6 +4287,63 @@ defineCommand({
|
|
|
3934
4287
|
}
|
|
3935
4288
|
});
|
|
3936
4289
|
|
|
4290
|
+
// src/commands/daemon/doCommand.ts
|
|
4291
|
+
defineCommand({
|
|
4292
|
+
name: "do",
|
|
4293
|
+
handler: async (args) => {
|
|
4294
|
+
const action = args?.action;
|
|
4295
|
+
const brainId = args?.brainId;
|
|
4296
|
+
logger.debug(`do handler: action="${action}" brainId="${brainId}"`);
|
|
4297
|
+
if (typeof action !== "string" || !DO_ACTIONS.includes(action)) {
|
|
4298
|
+
return {
|
|
4299
|
+
ok: false,
|
|
4300
|
+
error: `invalid action (expected one of: ${DO_ACTIONS.join(", ")})`
|
|
4301
|
+
};
|
|
4302
|
+
}
|
|
4303
|
+
if (typeof brainId !== "string" || brainId.trim().length === 0) {
|
|
4304
|
+
return { ok: false, error: "missing brainId" };
|
|
4305
|
+
}
|
|
4306
|
+
const result = await BaseChannel.forceDo(brainId.trim(), action);
|
|
4307
|
+
if (!result.ok)
|
|
4308
|
+
return result;
|
|
4309
|
+
return {
|
|
4310
|
+
ok: true,
|
|
4311
|
+
result: { action, brainId: brainId.trim(), displayName: result.displayName }
|
|
4312
|
+
};
|
|
4313
|
+
}
|
|
4314
|
+
});
|
|
4315
|
+
|
|
4316
|
+
// src/commands/daemon/viewCommand.ts
|
|
4317
|
+
defineCommand({
|
|
4318
|
+
name: "view",
|
|
4319
|
+
handler: async (args) => {
|
|
4320
|
+
const thing = args?.thing;
|
|
4321
|
+
const brainId = args?.brainId;
|
|
4322
|
+
logger.debug(`view handler: thing="${thing}" brainId="${brainId}"`);
|
|
4323
|
+
if (typeof thing !== "string" || !VIEW_THINGS.includes(thing)) {
|
|
4324
|
+
return {
|
|
4325
|
+
ok: false,
|
|
4326
|
+
error: `invalid thing (expected one of: ${VIEW_THINGS.join(", ")})`
|
|
4327
|
+
};
|
|
4328
|
+
}
|
|
4329
|
+
if (typeof brainId !== "string" || brainId.trim().length === 0) {
|
|
4330
|
+
return { ok: false, error: "missing brainId" };
|
|
4331
|
+
}
|
|
4332
|
+
const result = await BaseChannel.view(brainId.trim(), thing);
|
|
4333
|
+
if (!result.ok)
|
|
4334
|
+
return result;
|
|
4335
|
+
return {
|
|
4336
|
+
ok: true,
|
|
4337
|
+
result: {
|
|
4338
|
+
thing,
|
|
4339
|
+
brainId: brainId.trim(),
|
|
4340
|
+
displayName: result.displayName,
|
|
4341
|
+
value: result.value
|
|
4342
|
+
}
|
|
4343
|
+
};
|
|
4344
|
+
}
|
|
4345
|
+
});
|
|
4346
|
+
|
|
3937
4347
|
// src/commands/daemon.ts
|
|
3938
4348
|
async function startChannels() {
|
|
3939
4349
|
const items = await brainManager.listAvailableBrain();
|
|
@@ -3968,7 +4378,10 @@ async function startChannels() {
|
|
|
3968
4378
|
return started;
|
|
3969
4379
|
}
|
|
3970
4380
|
async function daemon() {
|
|
3971
|
-
|
|
4381
|
+
const logDir = join5(config.brainboxRoot, "logs");
|
|
4382
|
+
logger.configure({ logDir });
|
|
4383
|
+
configureLlmLog(join5(logDir, "llm"));
|
|
4384
|
+
logger.debug(`daemon: boot (logDir=${logDir}, llmLogDir=${join5(logDir, "llm")})`);
|
|
3972
4385
|
const started = await startChannels();
|
|
3973
4386
|
if (started === 0) {
|
|
3974
4387
|
logger.info("No activated brains with channels. Daemon idling.");
|
|
@@ -4108,7 +4521,8 @@ async function deactivateBrain(brainId) {
|
|
|
4108
4521
|
async function createBrain(displayName, seed, options) {
|
|
4109
4522
|
logger.debug(`createBrain: name="${displayName}" seed length=${seed.length} schedule=${options.schedule}`);
|
|
4110
4523
|
const result = await Brain.create(displayName, seed);
|
|
4111
|
-
if (
|
|
4524
|
+
if ("error" in result) {
|
|
4525
|
+
logger.error(`Failed to create brain "${displayName}": ${result.error}`);
|
|
4112
4526
|
process.exitCode = 1;
|
|
4113
4527
|
return;
|
|
4114
4528
|
}
|
|
@@ -4134,6 +4548,33 @@ async function removeBrain(brainId) {
|
|
|
4134
4548
|
}
|
|
4135
4549
|
logger.success(`Removed brain "${brain.displayName}" (${chalk2.cyan(brainId)})`);
|
|
4136
4550
|
}
|
|
4551
|
+
async function doAction(action, brainId) {
|
|
4552
|
+
if (!DO_ACTIONS.includes(action)) {
|
|
4553
|
+
logger.error(`Unknown action "${action}". Expected one of: ${DO_ACTIONS.join(", ")}`);
|
|
4554
|
+
process.exit(1);
|
|
4555
|
+
}
|
|
4556
|
+
logger.debug(`do: action=${action} brainId=${brainId}`);
|
|
4557
|
+
const response = await sendToDaemon({
|
|
4558
|
+
command: "do",
|
|
4559
|
+
args: { action, brainId }
|
|
4560
|
+
});
|
|
4561
|
+
const name = response.result?.displayName ?? brainId;
|
|
4562
|
+
logger.success(`Forced ${action} for "${name}" (${brainId}).`);
|
|
4563
|
+
}
|
|
4564
|
+
async function viewThing(thing, brainId) {
|
|
4565
|
+
if (!VIEW_THINGS.includes(thing)) {
|
|
4566
|
+
logger.error(`Unknown thing "${thing}". Expected one of: ${VIEW_THINGS.join(", ")}`);
|
|
4567
|
+
process.exit(1);
|
|
4568
|
+
}
|
|
4569
|
+
logger.debug(`view: thing=${thing} brainId=${brainId}`);
|
|
4570
|
+
const response = await sendToDaemon({
|
|
4571
|
+
command: "view",
|
|
4572
|
+
args: { thing, brainId }
|
|
4573
|
+
});
|
|
4574
|
+
const name = response.result?.displayName ?? brainId;
|
|
4575
|
+
logger.info(`${thing} — "${name}" (${brainId})`);
|
|
4576
|
+
console.log(JSON.stringify(response.result?.value ?? null, null, 2));
|
|
4577
|
+
}
|
|
4137
4578
|
function register3(program) {
|
|
4138
4579
|
const cmd = registerCommand(program, {
|
|
4139
4580
|
name: "brain",
|
|
@@ -4144,6 +4585,8 @@ function register3(program) {
|
|
|
4144
4585
|
cmd.command("remove <brainId>").description("Remove a brain and its memory").action(removeBrain);
|
|
4145
4586
|
cmd.command("activate <brainId>").description("Activate a brain").action(activateBrain);
|
|
4146
4587
|
cmd.command("deactivate <brainId>").description("Deactivate a brain").action(deactivateBrain);
|
|
4588
|
+
cmd.command("do <action> <brainId>").description(`Force-run a daemon job (${DO_ACTIONS.join(" | ")}) for a live brain`).action(doAction);
|
|
4589
|
+
cmd.command("view <thing> <brainId>").description(`Inspect a live brain value (${VIEW_THINGS.join(" | ")})`).action(viewThing);
|
|
4147
4590
|
return cmd;
|
|
4148
4591
|
}
|
|
4149
4592
|
|
|
@@ -4186,7 +4629,8 @@ import { Box, Text as Text2, render } from "ink";
|
|
|
4186
4629
|
// src/ui/TextInput.tsx
|
|
4187
4630
|
import { useEffect, useState } from "react";
|
|
4188
4631
|
import { Text, useInput, useStdin } from "ink";
|
|
4189
|
-
|
|
4632
|
+
|
|
4633
|
+
// src/ui/pipedStdin.ts
|
|
4190
4634
|
var pipedBuffer = null;
|
|
4191
4635
|
var pipedSubscribers = [];
|
|
4192
4636
|
function drainPiped(stdin) {
|
|
@@ -4220,6 +4664,21 @@ function drainPiped(stdin) {
|
|
|
4220
4664
|
};
|
|
4221
4665
|
tryRead();
|
|
4222
4666
|
}
|
|
4667
|
+
function takePipedLine(stdin, onLine) {
|
|
4668
|
+
drainPiped(stdin);
|
|
4669
|
+
pipedSubscribers.push(onLine);
|
|
4670
|
+
if (pipedBuffer && pipedBuffer.length > 0) {
|
|
4671
|
+
const line = pipedBuffer.shift();
|
|
4672
|
+
if (line !== undefined)
|
|
4673
|
+
onLine(line);
|
|
4674
|
+
}
|
|
4675
|
+
return () => {
|
|
4676
|
+
pipedSubscribers = pipedSubscribers.filter((s) => s !== onLine);
|
|
4677
|
+
};
|
|
4678
|
+
}
|
|
4679
|
+
|
|
4680
|
+
// src/ui/TextInput.tsx
|
|
4681
|
+
import { jsxDEV } from "react/jsx-dev-runtime";
|
|
4223
4682
|
function PipedInput({
|
|
4224
4683
|
prompt,
|
|
4225
4684
|
initialValue = "",
|
|
@@ -4229,16 +4688,7 @@ function PipedInput({
|
|
|
4229
4688
|
}) {
|
|
4230
4689
|
const [value] = useState(initialValue);
|
|
4231
4690
|
useEffect(() => {
|
|
4232
|
-
|
|
4233
|
-
pipedSubscribers.push(onSubmit);
|
|
4234
|
-
if (pipedBuffer && pipedBuffer.length > 0) {
|
|
4235
|
-
const line = pipedBuffer.shift();
|
|
4236
|
-
if (line !== undefined)
|
|
4237
|
-
onSubmit(line);
|
|
4238
|
-
}
|
|
4239
|
-
return () => {
|
|
4240
|
-
pipedSubscribers = pipedSubscribers.filter((s) => s !== onSubmit);
|
|
4241
|
-
};
|
|
4691
|
+
return takePipedLine(stdin, onSubmit);
|
|
4242
4692
|
}, [stdin, onSubmit]);
|
|
4243
4693
|
const shown = value.length > 0 ? value : placeholder;
|
|
4244
4694
|
return /* @__PURE__ */ jsxDEV(Text, {
|
|
@@ -4258,6 +4708,9 @@ function RawInput({
|
|
|
4258
4708
|
onSubmit
|
|
4259
4709
|
}) {
|
|
4260
4710
|
const [value, setValue] = useState(initialValue);
|
|
4711
|
+
useEffect(() => {
|
|
4712
|
+
setValue(initialValue);
|
|
4713
|
+
}, [prompt, initialValue]);
|
|
4261
4714
|
useInput((input, key) => {
|
|
4262
4715
|
if (key.return) {
|
|
4263
4716
|
onSubmit(value);
|
|
@@ -4765,57 +5218,236 @@ function register7(program) {
|
|
|
4765
5218
|
}
|
|
4766
5219
|
|
|
4767
5220
|
// src/commands/onboard.tsx
|
|
4768
|
-
import { useState as
|
|
4769
|
-
import { Box as
|
|
5221
|
+
import { useState as useState5 } from "react";
|
|
5222
|
+
import { Box as Box4, Text as Text5, render as render3 } from "ink";
|
|
4770
5223
|
import chalk3 from "chalk";
|
|
4771
|
-
|
|
5224
|
+
|
|
5225
|
+
// src/ui/Select.tsx
|
|
5226
|
+
import { useEffect as useEffect2, useMemo, useState as useState4 } from "react";
|
|
5227
|
+
import { Box as Box3, Text as Text4, useInput as useInput2, useStdin as useStdin2 } from "ink";
|
|
5228
|
+
import { jsxDEV as jsxDEV4, Fragment } from "react/jsx-dev-runtime";
|
|
5229
|
+
function filterItems(items, query) {
|
|
5230
|
+
const q = query.trim().toLowerCase();
|
|
5231
|
+
if (!q)
|
|
5232
|
+
return items;
|
|
5233
|
+
return items.filter((i) => i.toLowerCase().includes(q));
|
|
5234
|
+
}
|
|
5235
|
+
function windowStart(cursor, total, limit) {
|
|
5236
|
+
if (total <= limit)
|
|
5237
|
+
return 0;
|
|
5238
|
+
return Math.max(0, Math.min(cursor - Math.floor(limit / 2), total - limit));
|
|
5239
|
+
}
|
|
5240
|
+
function resolvePiped(items, line) {
|
|
5241
|
+
if (items.includes(line))
|
|
5242
|
+
return line;
|
|
5243
|
+
const exact = items.find((i) => i.toLowerCase() === line.toLowerCase());
|
|
5244
|
+
if (exact)
|
|
5245
|
+
return exact;
|
|
5246
|
+
const filtered = filterItems(items, line);
|
|
5247
|
+
if (filtered.length === 1)
|
|
5248
|
+
return filtered[0];
|
|
5249
|
+
return;
|
|
5250
|
+
}
|
|
5251
|
+
function PipedSelect({
|
|
5252
|
+
prompt = "",
|
|
5253
|
+
items,
|
|
5254
|
+
onSelect,
|
|
5255
|
+
stdin
|
|
5256
|
+
}) {
|
|
5257
|
+
useEffect2(() => {
|
|
5258
|
+
return takePipedLine(stdin, (line) => {
|
|
5259
|
+
const match = resolvePiped(items, line.trim());
|
|
5260
|
+
if (match)
|
|
5261
|
+
onSelect(match);
|
|
5262
|
+
});
|
|
5263
|
+
}, [stdin, onSelect, items]);
|
|
5264
|
+
return /* @__PURE__ */ jsxDEV4(Box3, {
|
|
5265
|
+
flexDirection: "column",
|
|
5266
|
+
children: [
|
|
5267
|
+
/* @__PURE__ */ jsxDEV4(Text4, {
|
|
5268
|
+
children: [
|
|
5269
|
+
prompt,
|
|
5270
|
+
/* @__PURE__ */ jsxDEV4(Text4, {
|
|
5271
|
+
dimColor: true,
|
|
5272
|
+
children: "(piped)"
|
|
5273
|
+
}, undefined, false, undefined, this)
|
|
5274
|
+
]
|
|
5275
|
+
}, undefined, true, undefined, this),
|
|
5276
|
+
/* @__PURE__ */ jsxDEV4(Text4, {
|
|
5277
|
+
dimColor: true,
|
|
5278
|
+
children: [
|
|
5279
|
+
"matches (",
|
|
5280
|
+
items.length,
|
|
5281
|
+
"/",
|
|
5282
|
+
items.length,
|
|
5283
|
+
"):"
|
|
5284
|
+
]
|
|
5285
|
+
}, undefined, true, undefined, this),
|
|
5286
|
+
items.slice(0, 8).map((item) => /* @__PURE__ */ jsxDEV4(Text4, {
|
|
5287
|
+
children: [
|
|
5288
|
+
" ",
|
|
5289
|
+
item
|
|
5290
|
+
]
|
|
5291
|
+
}, item, true, undefined, this))
|
|
5292
|
+
]
|
|
5293
|
+
}, undefined, true, undefined, this);
|
|
5294
|
+
}
|
|
5295
|
+
function RawSelect({
|
|
5296
|
+
prompt = "",
|
|
5297
|
+
items,
|
|
5298
|
+
limit = 8,
|
|
5299
|
+
onSelect
|
|
5300
|
+
}) {
|
|
5301
|
+
const [query, setQuery] = useState4("");
|
|
5302
|
+
const [cursor, setCursor] = useState4(0);
|
|
5303
|
+
const filtered = useMemo(() => filterItems(items, query), [items, query]);
|
|
5304
|
+
const active = Math.min(cursor, Math.max(0, filtered.length - 1));
|
|
5305
|
+
const start = windowStart(active, filtered.length, limit);
|
|
5306
|
+
const visible = filtered.slice(start, start + limit);
|
|
5307
|
+
useInput2((input, key) => {
|
|
5308
|
+
if (key.return) {
|
|
5309
|
+
const item = filtered[active];
|
|
5310
|
+
if (item)
|
|
5311
|
+
onSelect(item);
|
|
5312
|
+
return;
|
|
5313
|
+
}
|
|
5314
|
+
if (key.upArrow) {
|
|
5315
|
+
setCursor((c) => Math.max(0, Math.min(c, filtered.length - 1) - 1));
|
|
5316
|
+
return;
|
|
5317
|
+
}
|
|
5318
|
+
if (key.downArrow) {
|
|
5319
|
+
setCursor((c) => Math.min(filtered.length - 1, Math.min(c, filtered.length - 1) + 1));
|
|
5320
|
+
return;
|
|
5321
|
+
}
|
|
5322
|
+
if (key.pageUp) {
|
|
5323
|
+
setCursor((c) => Math.max(0, Math.min(c, filtered.length - 1) - limit));
|
|
5324
|
+
return;
|
|
5325
|
+
}
|
|
5326
|
+
if (key.pageDown) {
|
|
5327
|
+
setCursor((c) => Math.min(filtered.length - 1, Math.min(c, filtered.length - 1) + limit));
|
|
5328
|
+
return;
|
|
5329
|
+
}
|
|
5330
|
+
if (key.backspace || key.delete) {
|
|
5331
|
+
setQuery((q) => q.slice(0, -1));
|
|
5332
|
+
setCursor(0);
|
|
5333
|
+
return;
|
|
5334
|
+
}
|
|
5335
|
+
if (key.ctrl && input === "c") {
|
|
5336
|
+
process.exit(130);
|
|
5337
|
+
}
|
|
5338
|
+
if (input.length > 0 && !key.ctrl && !key.meta) {
|
|
5339
|
+
setQuery((q) => q + input);
|
|
5340
|
+
setCursor(0);
|
|
5341
|
+
}
|
|
5342
|
+
});
|
|
5343
|
+
return /* @__PURE__ */ jsxDEV4(Box3, {
|
|
5344
|
+
flexDirection: "column",
|
|
5345
|
+
children: [
|
|
5346
|
+
/* @__PURE__ */ jsxDEV4(Text4, {
|
|
5347
|
+
children: [
|
|
5348
|
+
prompt,
|
|
5349
|
+
query,
|
|
5350
|
+
/* @__PURE__ */ jsxDEV4(Text4, {
|
|
5351
|
+
color: "cyan",
|
|
5352
|
+
children: "|"
|
|
5353
|
+
}, undefined, false, undefined, this)
|
|
5354
|
+
]
|
|
5355
|
+
}, undefined, true, undefined, this),
|
|
5356
|
+
/* @__PURE__ */ jsxDEV4(Text4, {
|
|
5357
|
+
dimColor: true,
|
|
5358
|
+
children: [
|
|
5359
|
+
"matches (",
|
|
5360
|
+
filtered.length,
|
|
5361
|
+
"/",
|
|
5362
|
+
items.length,
|
|
5363
|
+
") · ↑↓ · type to filter · enter"
|
|
5364
|
+
]
|
|
5365
|
+
}, undefined, true, undefined, this),
|
|
5366
|
+
filtered.length === 0 ? /* @__PURE__ */ jsxDEV4(Text4, {
|
|
5367
|
+
dimColor: true,
|
|
5368
|
+
children: " (no matches)"
|
|
5369
|
+
}, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV4(Fragment, {
|
|
5370
|
+
children: [
|
|
5371
|
+
start > 0 && /* @__PURE__ */ jsxDEV4(Text4, {
|
|
5372
|
+
dimColor: true,
|
|
5373
|
+
children: " …"
|
|
5374
|
+
}, undefined, false, undefined, this),
|
|
5375
|
+
visible.map((item, i) => {
|
|
5376
|
+
const idx = start + i;
|
|
5377
|
+
const selected = idx === active;
|
|
5378
|
+
return /* @__PURE__ */ jsxDEV4(Text4, {
|
|
5379
|
+
color: selected ? "cyan" : undefined,
|
|
5380
|
+
children: [
|
|
5381
|
+
selected ? "> " : " ",
|
|
5382
|
+
item
|
|
5383
|
+
]
|
|
5384
|
+
}, item, true, undefined, this);
|
|
5385
|
+
}),
|
|
5386
|
+
start + visible.length < filtered.length && /* @__PURE__ */ jsxDEV4(Text4, {
|
|
5387
|
+
dimColor: true,
|
|
5388
|
+
children: " …"
|
|
5389
|
+
}, undefined, false, undefined, this)
|
|
5390
|
+
]
|
|
5391
|
+
}, undefined, true, undefined, this)
|
|
5392
|
+
]
|
|
5393
|
+
}, undefined, true, undefined, this);
|
|
5394
|
+
}
|
|
5395
|
+
function Select(props) {
|
|
5396
|
+
const { isRawModeSupported, stdin } = useStdin2();
|
|
5397
|
+
if (!isRawModeSupported)
|
|
5398
|
+
return /* @__PURE__ */ jsxDEV4(PipedSelect, {
|
|
5399
|
+
...props,
|
|
5400
|
+
stdin
|
|
5401
|
+
}, undefined, false, undefined, this);
|
|
5402
|
+
return /* @__PURE__ */ jsxDEV4(RawSelect, {
|
|
5403
|
+
...props
|
|
5404
|
+
}, undefined, false, undefined, this);
|
|
5405
|
+
}
|
|
5406
|
+
|
|
5407
|
+
// src/commands/onboard.tsx
|
|
5408
|
+
import { jsxDEV as jsxDEV5 } from "react/jsx-dev-runtime";
|
|
5409
|
+
function ok(msg) {
|
|
5410
|
+
console.log(chalk3.green(`✔ ${msg}`));
|
|
5411
|
+
}
|
|
5412
|
+
function info(msg) {
|
|
5413
|
+
console.log(msg);
|
|
5414
|
+
}
|
|
5415
|
+
function show(active, node, status) {
|
|
5416
|
+
active.current.unmount();
|
|
5417
|
+
console.clear();
|
|
5418
|
+
if (status)
|
|
5419
|
+
ok(status);
|
|
5420
|
+
active.current = render3(node);
|
|
5421
|
+
}
|
|
4772
5422
|
function ProviderApp({
|
|
4773
5423
|
providers,
|
|
4774
5424
|
onDone
|
|
4775
5425
|
}) {
|
|
4776
|
-
const [stage, setStage] =
|
|
4777
|
-
const [error, setError] =
|
|
5426
|
+
const [stage, setStage] = useState5({ kind: "pick" });
|
|
5427
|
+
const [error, setError] = useState5(null);
|
|
4778
5428
|
if (stage.kind === "pick") {
|
|
4779
|
-
return /* @__PURE__ */
|
|
5429
|
+
return /* @__PURE__ */ jsxDEV5(Box4, {
|
|
4780
5430
|
flexDirection: "column",
|
|
4781
5431
|
children: [
|
|
4782
|
-
/* @__PURE__ */
|
|
5432
|
+
/* @__PURE__ */ jsxDEV5(Text5, {
|
|
4783
5433
|
children: [
|
|
4784
5434
|
chalk3.bold("Step 1/4"),
|
|
4785
5435
|
" — Choose your first provider"
|
|
4786
5436
|
]
|
|
4787
5437
|
}, undefined, true, undefined, this),
|
|
4788
|
-
/* @__PURE__ */
|
|
5438
|
+
/* @__PURE__ */ jsxDEV5(Text5, {
|
|
4789
5439
|
dimColor: true,
|
|
4790
|
-
children: "
|
|
5440
|
+
children: "↑↓ to move, type to filter, enter to select"
|
|
4791
5441
|
}, undefined, false, undefined, this),
|
|
4792
|
-
/* @__PURE__ */
|
|
5442
|
+
/* @__PURE__ */ jsxDEV5(Select, {
|
|
4793
5443
|
prompt: "provider> ",
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
if (!p) {
|
|
4797
|
-
setError("Provider name is required");
|
|
4798
|
-
return;
|
|
4799
|
-
}
|
|
4800
|
-
if (!providers.includes(p)) {
|
|
4801
|
-
setError(`Unknown provider "${p}"`);
|
|
4802
|
-
return;
|
|
4803
|
-
}
|
|
5444
|
+
items: providers,
|
|
5445
|
+
onSelect: (p) => {
|
|
4804
5446
|
setError(null);
|
|
4805
5447
|
setStage({ kind: "apiKey", provider: p });
|
|
4806
5448
|
}
|
|
4807
5449
|
}, undefined, false, undefined, this),
|
|
4808
|
-
/* @__PURE__ */
|
|
4809
|
-
dimColor: true,
|
|
4810
|
-
children: [
|
|
4811
|
-
"known (",
|
|
4812
|
-
providers.length,
|
|
4813
|
-
"): ",
|
|
4814
|
-
providers.slice(0, 10).join(", "),
|
|
4815
|
-
providers.length > 10 ? "…" : ""
|
|
4816
|
-
]
|
|
4817
|
-
}, undefined, true, undefined, this),
|
|
4818
|
-
error && /* @__PURE__ */ jsxDEV4(Text4, {
|
|
5450
|
+
error && /* @__PURE__ */ jsxDEV5(Text5, {
|
|
4819
5451
|
color: "red",
|
|
4820
5452
|
children: error
|
|
4821
5453
|
}, undefined, false, undefined, this)
|
|
@@ -4824,14 +5456,14 @@ function ProviderApp({
|
|
|
4824
5456
|
}
|
|
4825
5457
|
if (stage.kind === "apiKey") {
|
|
4826
5458
|
const envName = `${stage.provider.toUpperCase().replace(/-/g, "_")}_API_KEY`;
|
|
4827
|
-
return /* @__PURE__ */
|
|
5459
|
+
return /* @__PURE__ */ jsxDEV5(Box4, {
|
|
4828
5460
|
flexDirection: "column",
|
|
4829
5461
|
children: [
|
|
4830
|
-
/* @__PURE__ */
|
|
5462
|
+
/* @__PURE__ */ jsxDEV5(Text5, {
|
|
4831
5463
|
children: [
|
|
4832
5464
|
chalk3.bold("Step 1/4"),
|
|
4833
5465
|
" — ",
|
|
4834
|
-
/* @__PURE__ */
|
|
5466
|
+
/* @__PURE__ */ jsxDEV5(Text5, {
|
|
4835
5467
|
color: "cyan",
|
|
4836
5468
|
children: stage.provider
|
|
4837
5469
|
}, undefined, false, undefined, this),
|
|
@@ -4839,7 +5471,7 @@ function ProviderApp({
|
|
|
4839
5471
|
"api key"
|
|
4840
5472
|
]
|
|
4841
5473
|
}, undefined, true, undefined, this),
|
|
4842
|
-
/* @__PURE__ */
|
|
5474
|
+
/* @__PURE__ */ jsxDEV5(Text5, {
|
|
4843
5475
|
dimColor: true,
|
|
4844
5476
|
children: [
|
|
4845
5477
|
"(blank reuses $",
|
|
@@ -4847,7 +5479,7 @@ function ProviderApp({
|
|
|
4847
5479
|
" from env)"
|
|
4848
5480
|
]
|
|
4849
5481
|
}, undefined, true, undefined, this),
|
|
4850
|
-
/* @__PURE__ */
|
|
5482
|
+
/* @__PURE__ */ jsxDEV5(TextInput, {
|
|
4851
5483
|
prompt: "apiKey> ",
|
|
4852
5484
|
onSubmit: (raw) => {
|
|
4853
5485
|
const apiKey = raw.trim() || (process.env[envName] ?? "");
|
|
@@ -4858,7 +5490,6 @@ function ProviderApp({
|
|
|
4858
5490
|
const extras = PROVIDER_EXTRA_FIELDS[stage.provider] ?? [];
|
|
4859
5491
|
if (extras.length === 0) {
|
|
4860
5492
|
setProviderAuth(stage.provider, { apiKey });
|
|
4861
|
-
logger.success(`Saved ${stage.provider} to auth.yaml`);
|
|
4862
5493
|
onDone({ provider: stage.provider });
|
|
4863
5494
|
return;
|
|
4864
5495
|
}
|
|
@@ -4871,7 +5502,7 @@ function ProviderApp({
|
|
|
4871
5502
|
});
|
|
4872
5503
|
}
|
|
4873
5504
|
}, undefined, false, undefined, this),
|
|
4874
|
-
error && /* @__PURE__ */
|
|
5505
|
+
error && /* @__PURE__ */ jsxDEV5(Text5, {
|
|
4875
5506
|
color: "red",
|
|
4876
5507
|
children: error
|
|
4877
5508
|
}, undefined, false, undefined, this)
|
|
@@ -4879,31 +5510,24 @@ function ProviderApp({
|
|
|
4879
5510
|
}, undefined, true, undefined, this);
|
|
4880
5511
|
}
|
|
4881
5512
|
const nextField = stage.fields[0];
|
|
4882
|
-
|
|
4883
|
-
setProviderAuth(stage.provider, stage.values);
|
|
4884
|
-
logger.success(`Saved ${stage.provider} to auth.yaml`);
|
|
4885
|
-
return /* @__PURE__ */ jsxDEV4(Text4, {
|
|
4886
|
-
children: "Continuing…"
|
|
4887
|
-
}, undefined, false, undefined, this);
|
|
4888
|
-
}
|
|
4889
|
-
return /* @__PURE__ */ jsxDEV4(Box3, {
|
|
5513
|
+
return /* @__PURE__ */ jsxDEV5(Box4, {
|
|
4890
5514
|
flexDirection: "column",
|
|
4891
5515
|
children: [
|
|
4892
|
-
/* @__PURE__ */
|
|
5516
|
+
/* @__PURE__ */ jsxDEV5(Text5, {
|
|
4893
5517
|
children: [
|
|
4894
5518
|
chalk3.bold("Step 1/4"),
|
|
4895
5519
|
" — ",
|
|
4896
5520
|
stage.provider,
|
|
4897
5521
|
" extra:",
|
|
4898
5522
|
" ",
|
|
4899
|
-
/* @__PURE__ */
|
|
5523
|
+
/* @__PURE__ */ jsxDEV5(Text5, {
|
|
4900
5524
|
color: "cyan",
|
|
4901
5525
|
children: nextField
|
|
4902
5526
|
}, undefined, false, undefined, this),
|
|
4903
5527
|
" (blank to skip)"
|
|
4904
5528
|
]
|
|
4905
5529
|
}, undefined, true, undefined, this),
|
|
4906
|
-
/* @__PURE__ */
|
|
5530
|
+
/* @__PURE__ */ jsxDEV5(TextInput, {
|
|
4907
5531
|
prompt: `${nextField}> `,
|
|
4908
5532
|
onSubmit: (raw) => {
|
|
4909
5533
|
const value = raw.trim();
|
|
@@ -4911,6 +5535,11 @@ function ProviderApp({
|
|
|
4911
5535
|
const values = { ...stage.values };
|
|
4912
5536
|
if (value)
|
|
4913
5537
|
values[nextField] = value;
|
|
5538
|
+
if (remaining.length === 0) {
|
|
5539
|
+
setProviderAuth(stage.provider, values);
|
|
5540
|
+
onDone({ provider: stage.provider });
|
|
5541
|
+
return;
|
|
5542
|
+
}
|
|
4914
5543
|
setStage({
|
|
4915
5544
|
kind: "extras",
|
|
4916
5545
|
provider: stage.provider,
|
|
@@ -4926,35 +5555,35 @@ function ModelApp2({
|
|
|
4926
5555
|
provider,
|
|
4927
5556
|
onDone
|
|
4928
5557
|
}) {
|
|
4929
|
-
const [error, setError] =
|
|
4930
|
-
return /* @__PURE__ */
|
|
5558
|
+
const [error, setError] = useState5(null);
|
|
5559
|
+
return /* @__PURE__ */ jsxDEV5(Box4, {
|
|
4931
5560
|
flexDirection: "column",
|
|
4932
5561
|
children: [
|
|
4933
|
-
/* @__PURE__ */
|
|
5562
|
+
/* @__PURE__ */ jsxDEV5(Text5, {
|
|
4934
5563
|
children: [
|
|
4935
5564
|
chalk3.bold("Step 1/4"),
|
|
4936
5565
|
" — Default model (both slots)"
|
|
4937
5566
|
]
|
|
4938
5567
|
}, undefined, true, undefined, this),
|
|
4939
|
-
/* @__PURE__ */
|
|
5568
|
+
/* @__PURE__ */ jsxDEV5(Text5, {
|
|
4940
5569
|
dimColor: true,
|
|
4941
5570
|
children: [
|
|
4942
|
-
"
|
|
4943
|
-
/* @__PURE__ */
|
|
5571
|
+
"model name, or ",
|
|
5572
|
+
/* @__PURE__ */ jsxDEV5(Text5, {
|
|
4944
5573
|
color: "cyan",
|
|
4945
5574
|
children: [
|
|
4946
5575
|
provider,
|
|
4947
5576
|
"/"
|
|
4948
5577
|
]
|
|
4949
5578
|
}, undefined, true, undefined, this),
|
|
4950
|
-
"model
|
|
4951
|
-
/* @__PURE__ */
|
|
5579
|
+
"model — fine-tune later with ",
|
|
5580
|
+
/* @__PURE__ */ jsxDEV5(Text5, {
|
|
4952
5581
|
color: "cyan",
|
|
4953
5582
|
children: "brainbox model"
|
|
4954
5583
|
}, undefined, false, undefined, this)
|
|
4955
5584
|
]
|
|
4956
5585
|
}, undefined, true, undefined, this),
|
|
4957
|
-
/* @__PURE__ */
|
|
5586
|
+
/* @__PURE__ */ jsxDEV5(TextInput, {
|
|
4958
5587
|
prompt: `${provider}/model> `,
|
|
4959
5588
|
onSubmit: (raw) => {
|
|
4960
5589
|
const value = raw.trim();
|
|
@@ -4962,17 +5591,18 @@ function ModelApp2({
|
|
|
4962
5591
|
setError("Model cannot be empty");
|
|
4963
5592
|
return;
|
|
4964
5593
|
}
|
|
4965
|
-
|
|
4966
|
-
|
|
5594
|
+
const prefix = `${provider}/`;
|
|
5595
|
+
const full = value.startsWith(prefix) ? value : `${prefix}${value}`;
|
|
5596
|
+
if (full === prefix) {
|
|
5597
|
+
setError("Model cannot be empty");
|
|
4967
5598
|
return;
|
|
4968
5599
|
}
|
|
4969
|
-
setModelSlot("identity",
|
|
4970
|
-
setModelSlot("conversation",
|
|
4971
|
-
|
|
4972
|
-
onDone();
|
|
5600
|
+
setModelSlot("identity", full);
|
|
5601
|
+
setModelSlot("conversation", full);
|
|
5602
|
+
onDone(full);
|
|
4973
5603
|
}
|
|
4974
5604
|
}, undefined, false, undefined, this),
|
|
4975
|
-
error && /* @__PURE__ */
|
|
5605
|
+
error && /* @__PURE__ */ jsxDEV5(Text5, {
|
|
4976
5606
|
color: "red",
|
|
4977
5607
|
children: error
|
|
4978
5608
|
}, undefined, false, undefined, this)
|
|
@@ -4982,21 +5612,21 @@ function ModelApp2({
|
|
|
4982
5612
|
function SuperMemoryApp({
|
|
4983
5613
|
onDone
|
|
4984
5614
|
}) {
|
|
4985
|
-
const [error, setError] =
|
|
4986
|
-
return /* @__PURE__ */
|
|
5615
|
+
const [error, setError] = useState5(null);
|
|
5616
|
+
return /* @__PURE__ */ jsxDEV5(Box4, {
|
|
4987
5617
|
flexDirection: "column",
|
|
4988
5618
|
children: [
|
|
4989
|
-
/* @__PURE__ */
|
|
5619
|
+
/* @__PURE__ */ jsxDEV5(Text5, {
|
|
4990
5620
|
children: [
|
|
4991
5621
|
chalk3.bold("Step 2/4"),
|
|
4992
5622
|
" — Supermemory API key"
|
|
4993
5623
|
]
|
|
4994
5624
|
}, undefined, true, undefined, this),
|
|
4995
|
-
/* @__PURE__ */
|
|
5625
|
+
/* @__PURE__ */ jsxDEV5(Text5, {
|
|
4996
5626
|
dimColor: true,
|
|
4997
5627
|
children: "powers each brain's long-term memory. Blank reuses $SUPERMEMORY_API_KEY from env."
|
|
4998
5628
|
}, undefined, false, undefined, this),
|
|
4999
|
-
/* @__PURE__ */
|
|
5629
|
+
/* @__PURE__ */ jsxDEV5(TextInput, {
|
|
5000
5630
|
prompt: "supermemory apiKey> ",
|
|
5001
5631
|
onSubmit: (raw) => {
|
|
5002
5632
|
const key = raw.trim() || (process.env["SUPERMEMORY_API_KEY"] ?? "");
|
|
@@ -5005,11 +5635,10 @@ function SuperMemoryApp({
|
|
|
5005
5635
|
return;
|
|
5006
5636
|
}
|
|
5007
5637
|
setSupermemoryKey(key);
|
|
5008
|
-
logger.success("Saved supermemory key to brainbox.yaml");
|
|
5009
5638
|
onDone();
|
|
5010
5639
|
}
|
|
5011
5640
|
}, undefined, false, undefined, this),
|
|
5012
|
-
error && /* @__PURE__ */
|
|
5641
|
+
error && /* @__PURE__ */ jsxDEV5(Text5, {
|
|
5013
5642
|
color: "red",
|
|
5014
5643
|
children: error
|
|
5015
5644
|
}, undefined, false, undefined, this)
|
|
@@ -5019,23 +5648,24 @@ function SuperMemoryApp({
|
|
|
5019
5648
|
function BrainApp({
|
|
5020
5649
|
onDone
|
|
5021
5650
|
}) {
|
|
5022
|
-
const [stage, setStage] =
|
|
5023
|
-
const [error, setError] =
|
|
5651
|
+
const [stage, setStage] = useState5({ kind: "name" });
|
|
5652
|
+
const [error, setError] = useState5(null);
|
|
5653
|
+
const [busy, setBusy] = useState5(false);
|
|
5024
5654
|
if (stage.kind === "name") {
|
|
5025
|
-
return /* @__PURE__ */
|
|
5655
|
+
return /* @__PURE__ */ jsxDEV5(Box4, {
|
|
5026
5656
|
flexDirection: "column",
|
|
5027
5657
|
children: [
|
|
5028
|
-
/* @__PURE__ */
|
|
5658
|
+
/* @__PURE__ */ jsxDEV5(Text5, {
|
|
5029
5659
|
children: [
|
|
5030
5660
|
chalk3.bold("Step 3/4"),
|
|
5031
5661
|
" — Brain name"
|
|
5032
5662
|
]
|
|
5033
5663
|
}, undefined, true, undefined, this),
|
|
5034
|
-
/* @__PURE__ */
|
|
5664
|
+
/* @__PURE__ */ jsxDEV5(Text5, {
|
|
5035
5665
|
dimColor: true,
|
|
5036
5666
|
children: "The display name your channel will see"
|
|
5037
5667
|
}, undefined, false, undefined, this),
|
|
5038
|
-
/* @__PURE__ */
|
|
5668
|
+
/* @__PURE__ */ jsxDEV5(TextInput, {
|
|
5039
5669
|
prompt: "name> ",
|
|
5040
5670
|
onSubmit: (raw) => {
|
|
5041
5671
|
const v = raw.trim();
|
|
@@ -5047,37 +5677,39 @@ function BrainApp({
|
|
|
5047
5677
|
setStage({ kind: "seed", displayName: v });
|
|
5048
5678
|
}
|
|
5049
5679
|
}, undefined, false, undefined, this),
|
|
5050
|
-
error && /* @__PURE__ */
|
|
5680
|
+
error && /* @__PURE__ */ jsxDEV5(Text5, {
|
|
5051
5681
|
color: "red",
|
|
5052
5682
|
children: error
|
|
5053
5683
|
}, undefined, false, undefined, this)
|
|
5054
5684
|
]
|
|
5055
5685
|
}, undefined, true, undefined, this);
|
|
5056
5686
|
}
|
|
5057
|
-
return /* @__PURE__ */
|
|
5687
|
+
return /* @__PURE__ */ jsxDEV5(Box4, {
|
|
5058
5688
|
flexDirection: "column",
|
|
5059
5689
|
children: [
|
|
5060
|
-
/* @__PURE__ */
|
|
5690
|
+
/* @__PURE__ */ jsxDEV5(Text5, {
|
|
5061
5691
|
children: [
|
|
5062
5692
|
chalk3.bold("Step 3/4"),
|
|
5063
5693
|
" — Seed for",
|
|
5064
5694
|
" ",
|
|
5065
|
-
/* @__PURE__ */
|
|
5695
|
+
/* @__PURE__ */ jsxDEV5(Text5, {
|
|
5066
5696
|
color: "cyan",
|
|
5067
5697
|
children: stage.displayName
|
|
5068
5698
|
}, undefined, false, undefined, this)
|
|
5069
5699
|
]
|
|
5070
5700
|
}, undefined, true, undefined, this),
|
|
5071
|
-
/* @__PURE__ */
|
|
5701
|
+
/* @__PURE__ */ jsxDEV5(Text5, {
|
|
5072
5702
|
dimColor: true,
|
|
5073
5703
|
children: "One sentence about who they are. The model will expand it."
|
|
5074
5704
|
}, undefined, false, undefined, this),
|
|
5075
|
-
/* @__PURE__ */
|
|
5705
|
+
busy ? /* @__PURE__ */ jsxDEV5(Text5, {
|
|
5706
|
+
dimColor: true,
|
|
5707
|
+
children: "Creating brain… (This can take few minutes depending on the model's response speed)"
|
|
5708
|
+
}, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV5(TextInput, {
|
|
5076
5709
|
prompt: "seed> ",
|
|
5077
|
-
onSubmit:
|
|
5710
|
+
onSubmit: (raw) => {
|
|
5078
5711
|
const seed = raw.trim();
|
|
5079
5712
|
if (seed === "skip") {
|
|
5080
|
-
logger.info("Skipped brain creation.");
|
|
5081
5713
|
onDone({ brainId: "", displayName: stage.displayName });
|
|
5082
5714
|
return;
|
|
5083
5715
|
}
|
|
@@ -5085,16 +5717,22 @@ function BrainApp({
|
|
|
5085
5717
|
setError("Seed cannot be empty (or type 'skip')");
|
|
5086
5718
|
return;
|
|
5087
5719
|
}
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
5720
|
+
setBusy(true);
|
|
5721
|
+
setError(null);
|
|
5722
|
+
Brain.create(stage.displayName, seed).then((result) => {
|
|
5723
|
+
setBusy(false);
|
|
5724
|
+
if ("error" in result) {
|
|
5725
|
+
setError(`Brain creation failed: ${result.error} (fix seed, or type 'skip')`);
|
|
5726
|
+
return;
|
|
5727
|
+
}
|
|
5728
|
+
onDone({
|
|
5729
|
+
brainId: result.brainId,
|
|
5730
|
+
displayName: stage.displayName
|
|
5731
|
+
});
|
|
5732
|
+
});
|
|
5095
5733
|
}
|
|
5096
5734
|
}, undefined, false, undefined, this),
|
|
5097
|
-
error && /* @__PURE__ */
|
|
5735
|
+
error && /* @__PURE__ */ jsxDEV5(Text5, {
|
|
5098
5736
|
color: "red",
|
|
5099
5737
|
children: error
|
|
5100
5738
|
}, undefined, false, undefined, this)
|
|
@@ -5106,45 +5744,40 @@ function ChannelApp({
|
|
|
5106
5744
|
displayName,
|
|
5107
5745
|
onDone
|
|
5108
5746
|
}) {
|
|
5109
|
-
const [stage, setStage] =
|
|
5110
|
-
const [error, setError] =
|
|
5747
|
+
const [stage, setStage] = useState5({ kind: "kind" });
|
|
5748
|
+
const [error, setError] = useState5(null);
|
|
5111
5749
|
if (stage.kind === "kind") {
|
|
5112
|
-
return /* @__PURE__ */
|
|
5750
|
+
return /* @__PURE__ */ jsxDEV5(Box4, {
|
|
5113
5751
|
flexDirection: "column",
|
|
5114
5752
|
children: [
|
|
5115
|
-
/* @__PURE__ */
|
|
5753
|
+
/* @__PURE__ */ jsxDEV5(Text5, {
|
|
5116
5754
|
children: [
|
|
5117
5755
|
chalk3.bold("Step 4/4"),
|
|
5118
5756
|
" — Channel for",
|
|
5119
5757
|
" ",
|
|
5120
|
-
/* @__PURE__ */
|
|
5758
|
+
/* @__PURE__ */ jsxDEV5(Text5, {
|
|
5121
5759
|
color: "cyan",
|
|
5122
5760
|
children: displayName
|
|
5123
5761
|
}, undefined, false, undefined, this)
|
|
5124
5762
|
]
|
|
5125
5763
|
}, undefined, true, undefined, this),
|
|
5126
|
-
/* @__PURE__ */
|
|
5764
|
+
/* @__PURE__ */ jsxDEV5(Text5, {
|
|
5127
5765
|
dimColor: true,
|
|
5128
|
-
children: "
|
|
5766
|
+
children: "↑↓ to move, type to filter, enter to select"
|
|
5129
5767
|
}, undefined, false, undefined, this),
|
|
5130
|
-
/* @__PURE__ */
|
|
5768
|
+
/* @__PURE__ */ jsxDEV5(Select, {
|
|
5131
5769
|
prompt: "channel> ",
|
|
5132
|
-
|
|
5133
|
-
|
|
5770
|
+
items: ["discord", "telegram", "skip"],
|
|
5771
|
+
onSelect: (v) => {
|
|
5134
5772
|
if (v === "skip") {
|
|
5135
|
-
|
|
5136
|
-
onDone();
|
|
5137
|
-
return;
|
|
5138
|
-
}
|
|
5139
|
-
if (v !== "discord" && v !== "telegram") {
|
|
5140
|
-
setError(`Expected "discord", "telegram", or "skip"`);
|
|
5773
|
+
onDone("Skipped channel setup.");
|
|
5141
5774
|
return;
|
|
5142
5775
|
}
|
|
5143
5776
|
setError(null);
|
|
5144
5777
|
setStage({ kind: "token", kind_: v });
|
|
5145
5778
|
}
|
|
5146
5779
|
}, undefined, false, undefined, this),
|
|
5147
|
-
error && /* @__PURE__ */
|
|
5780
|
+
error && /* @__PURE__ */ jsxDEV5(Text5, {
|
|
5148
5781
|
color: "red",
|
|
5149
5782
|
children: error
|
|
5150
5783
|
}, undefined, false, undefined, this)
|
|
@@ -5152,10 +5785,10 @@ function ChannelApp({
|
|
|
5152
5785
|
}, undefined, true, undefined, this);
|
|
5153
5786
|
}
|
|
5154
5787
|
if (stage.kind === "token") {
|
|
5155
|
-
return /* @__PURE__ */
|
|
5788
|
+
return /* @__PURE__ */ jsxDEV5(Box4, {
|
|
5156
5789
|
flexDirection: "column",
|
|
5157
5790
|
children: [
|
|
5158
|
-
/* @__PURE__ */
|
|
5791
|
+
/* @__PURE__ */ jsxDEV5(Text5, {
|
|
5159
5792
|
children: [
|
|
5160
5793
|
chalk3.bold("Step 4/4"),
|
|
5161
5794
|
" — ",
|
|
@@ -5163,7 +5796,7 @@ function ChannelApp({
|
|
|
5163
5796
|
" bot token"
|
|
5164
5797
|
]
|
|
5165
5798
|
}, undefined, true, undefined, this),
|
|
5166
|
-
/* @__PURE__ */
|
|
5799
|
+
/* @__PURE__ */ jsxDEV5(TextInput, {
|
|
5167
5800
|
prompt: "token> ",
|
|
5168
5801
|
onSubmit: (raw) => {
|
|
5169
5802
|
const token = raw.trim();
|
|
@@ -5175,17 +5808,17 @@ function ChannelApp({
|
|
|
5175
5808
|
setStage({ kind: "target", kind_: stage.kind_, token });
|
|
5176
5809
|
}
|
|
5177
5810
|
}, undefined, false, undefined, this),
|
|
5178
|
-
error && /* @__PURE__ */
|
|
5811
|
+
error && /* @__PURE__ */ jsxDEV5(Text5, {
|
|
5179
5812
|
color: "red",
|
|
5180
5813
|
children: error
|
|
5181
5814
|
}, undefined, false, undefined, this)
|
|
5182
5815
|
]
|
|
5183
5816
|
}, undefined, true, undefined, this);
|
|
5184
5817
|
}
|
|
5185
|
-
return /* @__PURE__ */
|
|
5818
|
+
return /* @__PURE__ */ jsxDEV5(Box4, {
|
|
5186
5819
|
flexDirection: "column",
|
|
5187
5820
|
children: [
|
|
5188
|
-
/* @__PURE__ */
|
|
5821
|
+
/* @__PURE__ */ jsxDEV5(Text5, {
|
|
5189
5822
|
children: [
|
|
5190
5823
|
chalk3.bold("Step 4/4"),
|
|
5191
5824
|
" — Optional",
|
|
@@ -5194,42 +5827,46 @@ function ChannelApp({
|
|
|
5194
5827
|
" (blank = pair later)"
|
|
5195
5828
|
]
|
|
5196
5829
|
}, undefined, true, undefined, this),
|
|
5197
|
-
/* @__PURE__ */
|
|
5830
|
+
/* @__PURE__ */ jsxDEV5(TextInput, {
|
|
5198
5831
|
prompt: `${stage.kind_ === "discord" ? "channelId" : "chatId"}> `,
|
|
5199
|
-
onSubmit:
|
|
5832
|
+
onSubmit: (raw) => {
|
|
5200
5833
|
const target = raw.trim();
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
}
|
|
5206
|
-
let updated;
|
|
5207
|
-
if (stage.kind_ === "discord") {
|
|
5208
|
-
updated = {
|
|
5209
|
-
...existing,
|
|
5210
|
-
channel: "discord",
|
|
5211
|
-
discord: { token: stage.token, channelId: target || undefined },
|
|
5212
|
-
activated: true
|
|
5213
|
-
};
|
|
5214
|
-
} else {
|
|
5215
|
-
const chatId = target ? Number(target) : undefined;
|
|
5216
|
-
if (target && Number.isNaN(chatId)) {
|
|
5217
|
-
setError("chatId must be a number");
|
|
5834
|
+
(async () => {
|
|
5835
|
+
const existing = await brainManager.loadBrain(brainId);
|
|
5836
|
+
if (!existing) {
|
|
5837
|
+
setError(`Brain ${brainId} no longer exists`);
|
|
5218
5838
|
return;
|
|
5219
5839
|
}
|
|
5220
|
-
updated
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
|
|
5840
|
+
let updated;
|
|
5841
|
+
if (stage.kind_ === "discord") {
|
|
5842
|
+
updated = {
|
|
5843
|
+
...existing,
|
|
5844
|
+
channel: "discord",
|
|
5845
|
+
discord: {
|
|
5846
|
+
token: stage.token,
|
|
5847
|
+
channelId: target || undefined
|
|
5848
|
+
},
|
|
5849
|
+
activated: true
|
|
5850
|
+
};
|
|
5851
|
+
} else {
|
|
5852
|
+
const chatId = target ? Number(target) : undefined;
|
|
5853
|
+
if (target && Number.isNaN(chatId)) {
|
|
5854
|
+
setError("chatId must be a number");
|
|
5855
|
+
return;
|
|
5856
|
+
}
|
|
5857
|
+
updated = {
|
|
5858
|
+
...existing,
|
|
5859
|
+
channel: "telegram",
|
|
5860
|
+
telegram: { token: stage.token, chatId },
|
|
5861
|
+
activated: true
|
|
5862
|
+
};
|
|
5863
|
+
}
|
|
5864
|
+
await brainManager.saveBrain(brainId, updated);
|
|
5865
|
+
onDone(`Bound ${displayName} → ${stage.kind_}${target ? ` (${target})` : " (pairing mode)"}`);
|
|
5866
|
+
})();
|
|
5230
5867
|
}
|
|
5231
5868
|
}, undefined, false, undefined, this),
|
|
5232
|
-
error && /* @__PURE__ */
|
|
5869
|
+
error && /* @__PURE__ */ jsxDEV5(Text5, {
|
|
5233
5870
|
color: "red",
|
|
5234
5871
|
children: error
|
|
5235
5872
|
}, undefined, false, undefined, this)
|
|
@@ -5237,46 +5874,52 @@ function ChannelApp({
|
|
|
5237
5874
|
}, undefined, true, undefined, this);
|
|
5238
5875
|
}
|
|
5239
5876
|
async function runOnboard() {
|
|
5240
|
-
|
|
5877
|
+
console.clear();
|
|
5878
|
+
info(`Welcome — let's get ${chalk3.bold("brainbox")} ready.`);
|
|
5241
5879
|
const providers = listProviderNames().slice().sort();
|
|
5242
5880
|
const { promise, resolve: resolve2 } = Promise.withResolvers();
|
|
5243
|
-
|
|
5881
|
+
const active = { current: null };
|
|
5882
|
+
active.current = render3(/* @__PURE__ */ jsxDEV5(ProviderApp, {
|
|
5244
5883
|
providers,
|
|
5245
5884
|
onDone: (p) => {
|
|
5246
|
-
active
|
|
5247
|
-
active = render3(/* @__PURE__ */ jsxDEV4(ModelApp2, {
|
|
5885
|
+
show(active, /* @__PURE__ */ jsxDEV5(ModelApp2, {
|
|
5248
5886
|
provider: p.provider,
|
|
5249
|
-
onDone: () => {
|
|
5250
|
-
active
|
|
5251
|
-
active = render3(/* @__PURE__ */ jsxDEV4(SuperMemoryApp, {
|
|
5887
|
+
onDone: (model) => {
|
|
5888
|
+
show(active, /* @__PURE__ */ jsxDEV5(SuperMemoryApp, {
|
|
5252
5889
|
onDone: () => {
|
|
5253
|
-
active
|
|
5254
|
-
active = render3(/* @__PURE__ */ jsxDEV4(BrainApp, {
|
|
5890
|
+
show(active, /* @__PURE__ */ jsxDEV5(BrainApp, {
|
|
5255
5891
|
onDone: (b) => {
|
|
5256
|
-
active.unmount();
|
|
5257
5892
|
if (!b.brainId) {
|
|
5893
|
+
active.current.unmount();
|
|
5894
|
+
console.clear();
|
|
5895
|
+
info("Skipped brain creation.");
|
|
5258
5896
|
resolve2();
|
|
5259
5897
|
return;
|
|
5260
5898
|
}
|
|
5261
|
-
active
|
|
5899
|
+
show(active, /* @__PURE__ */ jsxDEV5(ChannelApp, {
|
|
5262
5900
|
brainId: b.brainId,
|
|
5263
5901
|
displayName: b.displayName,
|
|
5264
|
-
onDone: () => {
|
|
5265
|
-
active.unmount();
|
|
5902
|
+
onDone: (status) => {
|
|
5903
|
+
active.current.unmount();
|
|
5904
|
+
console.clear();
|
|
5905
|
+
if (status.startsWith("Skipped"))
|
|
5906
|
+
info(status);
|
|
5907
|
+
else
|
|
5908
|
+
ok(status);
|
|
5266
5909
|
resolve2();
|
|
5267
5910
|
}
|
|
5268
|
-
}, undefined, false, undefined, this));
|
|
5911
|
+
}, undefined, false, undefined, this), `Created brain "${b.displayName}" (${chalk3.cyan(b.brainId)})`);
|
|
5269
5912
|
}
|
|
5270
|
-
}, undefined, false, undefined, this));
|
|
5913
|
+
}, undefined, false, undefined, this), "Saved supermemory key to brainbox.yaml");
|
|
5271
5914
|
}
|
|
5272
|
-
}, undefined, false, undefined, this));
|
|
5915
|
+
}, undefined, false, undefined, this), `Set identity + conversation model to ${model}`);
|
|
5273
5916
|
}
|
|
5274
|
-
}, undefined, false, undefined, this));
|
|
5917
|
+
}, undefined, false, undefined, this), `Saved ${p.provider} to auth.yaml`);
|
|
5275
5918
|
}
|
|
5276
5919
|
}, undefined, false, undefined, this));
|
|
5277
5920
|
await promise;
|
|
5278
|
-
|
|
5279
|
-
|
|
5921
|
+
ok("Onboarding complete.");
|
|
5922
|
+
info(`Run ${chalk3.cyan("brainbox daemon")} to bring it online.`);
|
|
5280
5923
|
}
|
|
5281
5924
|
function register8(program) {
|
|
5282
5925
|
return registerCommand(program, {
|
|
@@ -5288,12 +5931,12 @@ function register8(program) {
|
|
|
5288
5931
|
|
|
5289
5932
|
// src/index.ts
|
|
5290
5933
|
var __filename3 = fileURLToPath2(import.meta.url);
|
|
5291
|
-
var __dirname3 =
|
|
5934
|
+
var __dirname3 = dirname3(__filename3);
|
|
5292
5935
|
logger.configure({ level: config.debug ? "debug" : "info" });
|
|
5293
5936
|
logger.debug(`brainbox starting (debug=${config.debug}, root=${config.brainboxRoot})`);
|
|
5294
5937
|
function getVersion() {
|
|
5295
5938
|
try {
|
|
5296
|
-
const pkgPath =
|
|
5939
|
+
const pkgPath = join6(__dirname3, "..", "package.json");
|
|
5297
5940
|
const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
|
|
5298
5941
|
return pkg.version ?? "0.0.0";
|
|
5299
5942
|
} catch {
|