@p-sw/brainbox 0.2.1-beta.0 → 0.2.3-beta.0
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/README.md +3 -0
- package/dist/index.js +92 -54
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -22,6 +22,9 @@ Then she runs on a Discord or Telegram account. She greets you. She replies when
|
|
|
22
22
|
|
|
23
23
|
### Getting started
|
|
24
24
|
|
|
25
|
+
|
|
26
|
+
> **Requires [Bun](https://bun.sh).** Channel message history uses `bun:sqlite`, so run BrainBox with Bun (`bun install`, `bun run …`, or install the CLI under Bun). Node alone is not enough.
|
|
27
|
+
|
|
25
28
|
**1. Install**
|
|
26
29
|
|
|
27
30
|
```
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import { readFileSync as readFileSync2 } from "fs";
|
|
6
6
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
7
|
-
import { dirname as dirname3, join as
|
|
7
|
+
import { dirname as dirname3, join as join7 } from "path";
|
|
8
8
|
|
|
9
9
|
// src/utils/logger.ts
|
|
10
10
|
import chalk from "chalk";
|
|
@@ -2733,6 +2733,42 @@ var baseSystemPromptSchema = {
|
|
|
2733
2733
|
import { BadRequestResponseError } from "@openrouter/sdk/models/errors";
|
|
2734
2734
|
|
|
2735
2735
|
// src/brain/messageHistory.ts
|
|
2736
|
+
import { Database } from "bun:sqlite";
|
|
2737
|
+
import { mkdirSync as mkdirSync3 } from "fs";
|
|
2738
|
+
import { join as join4 } from "path";
|
|
2739
|
+
var db = null;
|
|
2740
|
+
function getDb() {
|
|
2741
|
+
if (db)
|
|
2742
|
+
return db;
|
|
2743
|
+
mkdirSync3(brainboxRoot, { recursive: true });
|
|
2744
|
+
db = new Database(join4(brainboxRoot, "message-history.sqlite"));
|
|
2745
|
+
db.run(`
|
|
2746
|
+
CREATE TABLE IF NOT EXISTS messages (
|
|
2747
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
2748
|
+
brain_id TEXT NOT NULL,
|
|
2749
|
+
sender TEXT NOT NULL,
|
|
2750
|
+
time INTEGER NOT NULL,
|
|
2751
|
+
content TEXT NOT NULL
|
|
2752
|
+
);
|
|
2753
|
+
CREATE INDEX IF NOT EXISTS idx_messages_brain_time
|
|
2754
|
+
ON messages(brain_id, time);
|
|
2755
|
+
`);
|
|
2756
|
+
return db;
|
|
2757
|
+
}
|
|
2758
|
+
function appendMessageHistory(brainId, entry) {
|
|
2759
|
+
getDb().prepare(`INSERT INTO messages (brain_id, sender, time, content)
|
|
2760
|
+
VALUES (?, ?, ?, ?)`).run(brainId, entry.sender, entry.time.getTime(), entry.content);
|
|
2761
|
+
}
|
|
2762
|
+
function getMessageHistory(brainId, start, end) {
|
|
2763
|
+
const rows = getDb().prepare(`SELECT sender, time, content FROM messages
|
|
2764
|
+
WHERE brain_id = ? AND time >= ? AND time <= ?
|
|
2765
|
+
ORDER BY time ASC, id ASC`).all(brainId, start.getTime(), end.getTime());
|
|
2766
|
+
return rows.map((row) => ({
|
|
2767
|
+
sender: row.sender,
|
|
2768
|
+
time: new Date(row.time),
|
|
2769
|
+
content: row.content
|
|
2770
|
+
}));
|
|
2771
|
+
}
|
|
2736
2772
|
function formatTime(time) {
|
|
2737
2773
|
const pad = (n) => n.toString().padStart(2, "0");
|
|
2738
2774
|
return `${time.getFullYear()}-${pad(time.getMonth() + 1)}-${pad(time.getDate())} ${pad(time.getHours())}:${pad(time.getMinutes())}:${pad(time.getSeconds())}`;
|
|
@@ -2762,8 +2798,8 @@ var log6 = logger.child("memory");
|
|
|
2762
2798
|
class Memory {
|
|
2763
2799
|
db;
|
|
2764
2800
|
space;
|
|
2765
|
-
constructor(
|
|
2766
|
-
this.db =
|
|
2801
|
+
constructor(db2, space) {
|
|
2802
|
+
this.db = db2;
|
|
2767
2803
|
this.space = space;
|
|
2768
2804
|
log6.debug(`Memory constructed for space=${space.name}`);
|
|
2769
2805
|
}
|
|
@@ -2843,8 +2879,8 @@ class Brain {
|
|
|
2843
2879
|
brainbase;
|
|
2844
2880
|
availabilityCache = new Map;
|
|
2845
2881
|
memory;
|
|
2846
|
-
constructor(
|
|
2847
|
-
this.db =
|
|
2882
|
+
constructor(db2, space, brainbase, memory) {
|
|
2883
|
+
this.db = db2;
|
|
2848
2884
|
this.space = space;
|
|
2849
2885
|
this.brainbase = brainbase;
|
|
2850
2886
|
this.memory = memory ?? new Memory(this.db, this.space);
|
|
@@ -3382,7 +3418,7 @@ ${blocks.join(`
|
|
|
3382
3418
|
const baseSystemPrompt = `${generated.baseSystemPrompt}
|
|
3383
3419
|
|
|
3384
3420
|
${personaSystemFixed}`;
|
|
3385
|
-
const
|
|
3421
|
+
const db2 = new Supermemory({ apiKey: config.supermemoryApiKey });
|
|
3386
3422
|
const brainId = randomUUID();
|
|
3387
3423
|
const space = {
|
|
3388
3424
|
name: `brain:${brainId}`,
|
|
@@ -3399,7 +3435,7 @@ ${personaSystemFixed}`;
|
|
|
3399
3435
|
startConversationTimeThreshold: generated.startConversationTimeThreshold,
|
|
3400
3436
|
activated: false
|
|
3401
3437
|
};
|
|
3402
|
-
const memory = new Memory(
|
|
3438
|
+
const memory = new Memory(db2, space);
|
|
3403
3439
|
await memory.add({
|
|
3404
3440
|
customId: "persona",
|
|
3405
3441
|
content: description,
|
|
@@ -3408,7 +3444,7 @@ ${personaSystemFixed}`;
|
|
|
3408
3444
|
log7.debug(`Brain.create: persona description stored`);
|
|
3409
3445
|
await brainManager.saveBrain(brainId, brainbase);
|
|
3410
3446
|
log7.debug(`Brain.create: brainbase saved (id=${brainId})`);
|
|
3411
|
-
return { brainId, brain: new Brain(
|
|
3447
|
+
return { brainId, brain: new Brain(db2, space, brainbase, memory) };
|
|
3412
3448
|
} catch (error) {
|
|
3413
3449
|
let reason = error instanceof Error ? `${error.message} (${error.name})` : String(error);
|
|
3414
3450
|
if (error instanceof BadRequestResponseError) {
|
|
@@ -3425,8 +3461,8 @@ ${personaSystemFixed}`;
|
|
|
3425
3461
|
log7.debug(`Brain.delete: no brainbase found`);
|
|
3426
3462
|
return false;
|
|
3427
3463
|
}
|
|
3428
|
-
const
|
|
3429
|
-
const memory = new Memory(
|
|
3464
|
+
const db2 = new Supermemory({ apiKey: config.supermemoryApiKey });
|
|
3465
|
+
const memory = new Memory(db2, { name: brainbase.spaceName });
|
|
3430
3466
|
try {
|
|
3431
3467
|
await memory.clear();
|
|
3432
3468
|
} catch (error) {
|
|
@@ -3444,10 +3480,10 @@ ${personaSystemFixed}`;
|
|
|
3444
3480
|
log7.debug(`Brain.load: not loadable (missing or not ready)`);
|
|
3445
3481
|
return null;
|
|
3446
3482
|
}
|
|
3447
|
-
const
|
|
3483
|
+
const db2 = new Supermemory({ apiKey: config.supermemoryApiKey });
|
|
3448
3484
|
const space = { name: brainbase.spaceName };
|
|
3449
3485
|
log7.debug(`Brain.load: ready (channel=${brainbase.channel})`);
|
|
3450
|
-
return new Brain(
|
|
3486
|
+
return new Brain(db2, space, brainbase);
|
|
3451
3487
|
}
|
|
3452
3488
|
}
|
|
3453
3489
|
function formatDatetime(now) {
|
|
@@ -3545,7 +3581,9 @@ var VIEW_THINGS = [
|
|
|
3545
3581
|
"monthly-schedule",
|
|
3546
3582
|
"sending-queue",
|
|
3547
3583
|
"deferred-queue",
|
|
3548
|
-
"today-availability"
|
|
3584
|
+
"today-availability",
|
|
3585
|
+
"persona",
|
|
3586
|
+
"base-system-prompt"
|
|
3549
3587
|
];
|
|
3550
3588
|
|
|
3551
3589
|
class BaseChannel {
|
|
@@ -3585,7 +3623,7 @@ class BaseChannel {
|
|
|
3585
3623
|
return;
|
|
3586
3624
|
}
|
|
3587
3625
|
}
|
|
3588
|
-
const history =
|
|
3626
|
+
const history = this.getMessageHistory(new Date(Date.now() - 24 * 60 * 60 * 1000), new Date);
|
|
3589
3627
|
await this.brain.sleepMemory(new Date, history);
|
|
3590
3628
|
}
|
|
3591
3629
|
async regenerateSchedules() {
|
|
@@ -3613,11 +3651,11 @@ class BaseChannel {
|
|
|
3613
3651
|
if (count >= countThreshold)
|
|
3614
3652
|
return;
|
|
3615
3653
|
const nowMs = now.getTime();
|
|
3616
|
-
const history =
|
|
3654
|
+
const history = this.getMessageHistory(new Date(nowMs - 24 * 60 * 60 * 1000), now);
|
|
3617
3655
|
try {
|
|
3618
3656
|
const replies = await this.brain.sendMessage(history, [], {
|
|
3619
3657
|
initiate: true,
|
|
3620
|
-
send: this.
|
|
3658
|
+
send: this.sendAndRecord.bind(this)
|
|
3621
3659
|
});
|
|
3622
3660
|
if (replies.length === 0)
|
|
3623
3661
|
return;
|
|
@@ -3709,7 +3747,12 @@ class BaseChannel {
|
|
|
3709
3747
|
twoDaysAgo.setDate(now.getDate() - 2);
|
|
3710
3748
|
this.isSending = true;
|
|
3711
3749
|
try {
|
|
3712
|
-
|
|
3750
|
+
const history = this.getMessageHistory(twoDaysAgo, now);
|
|
3751
|
+
for (const m of newUserMessages)
|
|
3752
|
+
this.saveMessageHistory(m);
|
|
3753
|
+
await this.brain.sendMessage(history, newUserMessages, {
|
|
3754
|
+
send: this.sendAndRecord.bind(this)
|
|
3755
|
+
});
|
|
3713
3756
|
} catch (e) {
|
|
3714
3757
|
logger.error(`Error while sending message: ${e}`);
|
|
3715
3758
|
logger.debug(`onMessage: sendMessage threw — ${e instanceof Error ? e.stack : String(e)}`);
|
|
@@ -3888,6 +3931,12 @@ class BaseChannel {
|
|
|
3888
3931
|
}));
|
|
3889
3932
|
case "today-availability":
|
|
3890
3933
|
return await this.brain.getTodayScheduledAvailability(now);
|
|
3934
|
+
case "persona": {
|
|
3935
|
+
const stored = await this.brain.memory.get("persona");
|
|
3936
|
+
return stored?.content ?? null;
|
|
3937
|
+
}
|
|
3938
|
+
case "base-system-prompt":
|
|
3939
|
+
return this.brain.brainbase.baseSystemPrompt;
|
|
3891
3940
|
}
|
|
3892
3941
|
}
|
|
3893
3942
|
static async shutdownAll() {
|
|
@@ -3972,10 +4021,23 @@ class BaseChannel {
|
|
|
3972
4021
|
displayName: channel.brain.brainbase.displayName
|
|
3973
4022
|
};
|
|
3974
4023
|
}
|
|
4024
|
+
getMessageHistory(start, end) {
|
|
4025
|
+
return getMessageHistory(this.brain.brainbase.brainId, start, end);
|
|
4026
|
+
}
|
|
4027
|
+
saveMessageHistory(entry) {
|
|
4028
|
+
appendMessageHistory(this.brain.brainbase.brainId, entry);
|
|
4029
|
+
}
|
|
4030
|
+
async sendAndRecord(text) {
|
|
4031
|
+
await this.send(text);
|
|
4032
|
+
this.saveMessageHistory({
|
|
4033
|
+
sender: "persona",
|
|
4034
|
+
time: new Date,
|
|
4035
|
+
content: text
|
|
4036
|
+
});
|
|
4037
|
+
}
|
|
3975
4038
|
}
|
|
3976
4039
|
|
|
3977
4040
|
// src/channel/discord.ts
|
|
3978
|
-
var HISTORY_CAP = 1000;
|
|
3979
4041
|
var AVAILABILITY_STATUS_MAP = {
|
|
3980
4042
|
online: "online",
|
|
3981
4043
|
"do-not-disturb": "dnd",
|
|
@@ -3985,7 +4047,6 @@ var AVAILABILITY_STATUS_MAP = {
|
|
|
3985
4047
|
class DiscordChannel extends BaseChannel {
|
|
3986
4048
|
client;
|
|
3987
4049
|
targetChannel;
|
|
3988
|
-
history = [];
|
|
3989
4050
|
constructor(brain) {
|
|
3990
4051
|
super(brain);
|
|
3991
4052
|
}
|
|
@@ -4037,14 +4098,12 @@ class DiscordChannel extends BaseChannel {
|
|
|
4037
4098
|
this.onPairing(inbound);
|
|
4038
4099
|
return;
|
|
4039
4100
|
}
|
|
4040
|
-
|
|
4101
|
+
logger.debug(`MessageCreate: dispatching (channel=${msg.channelId})`);
|
|
4102
|
+
this.onMessage({
|
|
4041
4103
|
sender: "user",
|
|
4042
4104
|
time: msg.createdAt,
|
|
4043
4105
|
content
|
|
4044
|
-
};
|
|
4045
|
-
this.pushHistory(entry);
|
|
4046
|
-
logger.debug(`MessageCreate: stored in history, dispatching (channel=${msg.channelId})`);
|
|
4047
|
-
this.onMessage(entry);
|
|
4106
|
+
});
|
|
4048
4107
|
});
|
|
4049
4108
|
logger.debug(`DiscordChannel.init: logging in`);
|
|
4050
4109
|
await this.client.login(this.brain.brainbase.discord.token);
|
|
@@ -4104,14 +4163,6 @@ class DiscordChannel extends BaseChannel {
|
|
|
4104
4163
|
logger.debug(`setAvailability: ${status} → ${mapped}`);
|
|
4105
4164
|
this.client.user.setStatus(mapped);
|
|
4106
4165
|
}
|
|
4107
|
-
async getMessageHistoryBetween(start, end) {
|
|
4108
|
-
return this.history.filter((m) => m.time >= start && m.time <= end);
|
|
4109
|
-
}
|
|
4110
|
-
pushHistory(entry) {
|
|
4111
|
-
this.history.push(entry);
|
|
4112
|
-
if (this.history.length > HISTORY_CAP)
|
|
4113
|
-
this.history.shift();
|
|
4114
|
-
}
|
|
4115
4166
|
async resolveSendChannel() {
|
|
4116
4167
|
if (this.targetChannel) {
|
|
4117
4168
|
logger.debug(`resolveSendChannel: cache hit`);
|
|
@@ -4160,12 +4211,9 @@ class DiscordChannel extends BaseChannel {
|
|
|
4160
4211
|
|
|
4161
4212
|
// src/channel/telegram.ts
|
|
4162
4213
|
import { Bot } from "gramio";
|
|
4163
|
-
var HISTORY_CAP2 = 1000;
|
|
4164
|
-
|
|
4165
4214
|
class TelegramChannel extends BaseChannel {
|
|
4166
4215
|
bot;
|
|
4167
4216
|
chatId;
|
|
4168
|
-
history = [];
|
|
4169
4217
|
constructor(brain) {
|
|
4170
4218
|
super(brain);
|
|
4171
4219
|
}
|
|
@@ -4207,14 +4255,12 @@ class TelegramChannel extends BaseChannel {
|
|
|
4207
4255
|
return;
|
|
4208
4256
|
}
|
|
4209
4257
|
this.chatId = ctx.chat.id;
|
|
4210
|
-
|
|
4258
|
+
logger.debug(`Telegram message: dispatching (chat=${ctx.chat.id})`);
|
|
4259
|
+
this.onMessage({
|
|
4211
4260
|
sender: "user",
|
|
4212
4261
|
time: inbound.time,
|
|
4213
4262
|
content: text
|
|
4214
|
-
};
|
|
4215
|
-
this.pushHistory(entry);
|
|
4216
|
-
logger.debug(`Telegram message: stored in history, dispatching (chat=${ctx.chat.id})`);
|
|
4217
|
-
this.onMessage(entry);
|
|
4263
|
+
});
|
|
4218
4264
|
});
|
|
4219
4265
|
logger.debug(`TelegramChannel.init: starting bot`);
|
|
4220
4266
|
await this.bot.start();
|
|
@@ -4254,14 +4300,6 @@ class TelegramChannel extends BaseChannel {
|
|
|
4254
4300
|
async setAvailability(_status) {
|
|
4255
4301
|
logger.debug(`setAvailability: ${_status} (no-op, Telegram has no bot presence)`);
|
|
4256
4302
|
}
|
|
4257
|
-
async getMessageHistoryBetween(start, end) {
|
|
4258
|
-
return this.history.filter((m) => m.time >= start && m.time <= end);
|
|
4259
|
-
}
|
|
4260
|
-
pushHistory(entry) {
|
|
4261
|
-
this.history.push(entry);
|
|
4262
|
-
if (this.history.length > HISTORY_CAP2)
|
|
4263
|
-
this.history.shift();
|
|
4264
|
-
}
|
|
4265
4303
|
async teardownClient() {
|
|
4266
4304
|
if (!this.bot) {
|
|
4267
4305
|
logger.debug(`teardownClient: no bot, nothing to stop`);
|
|
@@ -4275,8 +4313,8 @@ class TelegramChannel extends BaseChannel {
|
|
|
4275
4313
|
|
|
4276
4314
|
// src/utils/daemonClient.ts
|
|
4277
4315
|
import { connect } from "node:net";
|
|
4278
|
-
import { join as
|
|
4279
|
-
var DAEMON_SOCKET_PATH =
|
|
4316
|
+
import { join as join5 } from "node:path";
|
|
4317
|
+
var DAEMON_SOCKET_PATH = join5(config.brainboxRoot, "daemon.sock");
|
|
4280
4318
|
async function sendToDaemon(payload) {
|
|
4281
4319
|
logger.debug(`sendToDaemon: command="${payload.command}" args=${JSON.stringify(payload.args) ?? "null"}`);
|
|
4282
4320
|
let reply;
|
|
@@ -4338,7 +4376,7 @@ function exchangeOnce(payload) {
|
|
|
4338
4376
|
// src/commands/daemon.ts
|
|
4339
4377
|
import { createServer } from "node:net";
|
|
4340
4378
|
import { chmodSync, unlinkSync } from "node:fs";
|
|
4341
|
-
import { join as
|
|
4379
|
+
import { join as join6 } from "node:path";
|
|
4342
4380
|
|
|
4343
4381
|
// src/commands/daemon/commands.ts
|
|
4344
4382
|
var log8 = logger.child("daemon-cmd");
|
|
@@ -4490,9 +4528,9 @@ async function startChannels() {
|
|
|
4490
4528
|
return started;
|
|
4491
4529
|
}
|
|
4492
4530
|
async function daemon() {
|
|
4493
|
-
const logDir =
|
|
4531
|
+
const logDir = join6(config.brainboxRoot, "logs");
|
|
4494
4532
|
logger.configure({ logDir });
|
|
4495
|
-
configureLlmLog(config.debug ?
|
|
4533
|
+
configureLlmLog(config.debug ? join6(logDir, "llm") : undefined);
|
|
4496
4534
|
logger.debug(`daemon: boot (debug=${config.debug}, logDir=${logDir}, llmLog=${config.debug})`);
|
|
4497
4535
|
const started = await startChannels();
|
|
4498
4536
|
if (started === 0) {
|
|
@@ -6114,7 +6152,7 @@ logger.configure({ level: config.debug ? "debug" : "info" });
|
|
|
6114
6152
|
logger.debug(`brainbox starting (debug=${config.debug}, root=${config.brainboxRoot})`);
|
|
6115
6153
|
function getVersion() {
|
|
6116
6154
|
try {
|
|
6117
|
-
const pkgPath =
|
|
6155
|
+
const pkgPath = join7(__dirname3, "..", "package.json");
|
|
6118
6156
|
const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
|
|
6119
6157
|
return pkg.version ?? "0.0.0";
|
|
6120
6158
|
} catch {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@p-sw/brainbox",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3-beta.0",
|
|
4
4
|
"module": "dist/index.js",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"prepack": "bun run build"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
|
+
"@types/bun": "^1.3.14",
|
|
17
18
|
"@types/node": "^25.9.1"
|
|
18
19
|
},
|
|
19
20
|
"peerDependencies": {
|