@p-sw/brainbox 0.2.2 → 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.
Files changed (3) hide show
  1. package/README.md +3 -0
  2. package/dist/index.js +92 -54
  3. 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 join6 } from "path";
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(db, space) {
2766
- this.db = 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(db, space, brainbase, memory) {
2847
- this.db = 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 db = new Supermemory({ apiKey: config.supermemoryApiKey });
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(db, space);
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(db, space, brainbase, memory) };
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 db = new Supermemory({ apiKey: config.supermemoryApiKey });
3429
- const memory = new Memory(db, { name: brainbase.spaceName });
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 db = new Supermemory({ apiKey: config.supermemoryApiKey });
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(db, space, brainbase);
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 = await this.getMessageHistoryBetween(new Date(Date.now() - 24 * 60 * 60 * 1000), new Date);
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 = await this.getMessageHistoryBetween(new Date(nowMs - 24 * 60 * 60 * 1000), now);
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.send.bind(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
- await this.brain.sendMessage(await this.getMessageHistoryBetween(twoDaysAgo, now), newUserMessages, { send: this.send.bind(this) });
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
- const entry = {
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
- const entry = {
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 join4 } from "node:path";
4279
- var DAEMON_SOCKET_PATH = join4(config.brainboxRoot, "daemon.sock");
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 join5 } from "node:path";
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 = join5(config.brainboxRoot, "logs");
4531
+ const logDir = join6(config.brainboxRoot, "logs");
4494
4532
  logger.configure({ logDir });
4495
- configureLlmLog(config.debug ? join5(logDir, "llm") : undefined);
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 = join6(__dirname3, "..", "package.json");
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.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": {