volute 0.26.0 → 0.27.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 (127) hide show
  1. package/README.md +13 -13
  2. package/dist/{activity-events-ZMBAKLUF.js → activity-events-BBIEA2F4.js} +2 -3
  3. package/dist/api.d.ts +363 -168
  4. package/dist/{archive-4ZQYK5MN.js → archive-UA4BDFXQ.js} +2 -2
  5. package/dist/{auth-4TV573WE.js → auth-D3OT2ARB.js} +3 -3
  6. package/dist/bridge-FQHZL3MC.js +206 -0
  7. package/dist/chat-MHJ3L6JQ.js +58 -0
  8. package/dist/{chunk-PHU4DEAJ.js → chunk-2WPW7OT6.js} +3 -3
  9. package/dist/{chunk-5Y3PBKW6.js → chunk-2YP2TVDT.js} +138 -56
  10. package/dist/{chunk-USNBKHYG.js → chunk-4WXYUOAK.js} +4 -6
  11. package/dist/{chunk-YJA7P64S.js → chunk-AW7PFDVN.js} +5 -5
  12. package/dist/{chunk-OZFKBXD6.js → chunk-EHYDTZTF.js} +6 -6
  13. package/dist/{chunk-LX22GRG7.js → chunk-GIE6CSN5.js} +11 -8
  14. package/dist/{chunk-WBHMQ5OZ.js → chunk-H7OZRFJB.js} +192 -12
  15. package/dist/{chunk-ON3FF5JA.js → chunk-HDN7MNGD.js} +3 -3
  16. package/dist/chunk-IAYBDWVG.js +477 -0
  17. package/dist/{chunk-TZKJLDQN.js → chunk-IKRVFPWU.js} +14 -9
  18. package/dist/{chunk-WGOGUMPO.js → chunk-JGFVMROS.js} +13 -6
  19. package/dist/{chunk-3TV4GLFO.js → chunk-JKOWNZ4P.js} +3 -3
  20. package/dist/{chunk-NWI2425I.js → chunk-K5NAC55T.js} +1 -1
  21. package/dist/{chunk-HFCBO2GL.js → chunk-KDGS53OS.js} +4 -4
  22. package/dist/chunk-KTLFDYPT.js +61 -0
  23. package/dist/{chunk-V63B7DX3.js → chunk-LAC664WU.js} +7 -4
  24. package/dist/{chunk-3CFRE2VC.js → chunk-OQZH4PBB.js} +337 -1061
  25. package/dist/{chunk-2VO7453N.js → chunk-PHSAT7YL.js} +30 -54
  26. package/dist/{chunk-XOXLRRR2.js → chunk-RKQEHRBB.js} +4 -3
  27. package/dist/chunk-T6HKBWXZ.js +23 -0
  28. package/dist/{chunk-UTL75LP6.js → chunk-USUXRNVD.js} +22 -22
  29. package/dist/{chunk-J2CO4WEV.js → chunk-VIVMW2H2.js} +4 -4
  30. package/dist/{chunk-KTJGZ7M7.js → chunk-XBLSAVJF.js} +1 -1
  31. package/dist/cli.js +31 -36
  32. package/dist/{cloud-sync-NI2K3C7G.js → cloud-sync-T7M3ESC3.js} +15 -14
  33. package/dist/connectors/discord-bridge.js +158 -0
  34. package/dist/connectors/slack-bridge.js +119 -0
  35. package/dist/connectors/telegram-bridge.js +133 -0
  36. package/dist/conversations-M2K4253F.js +55 -0
  37. package/dist/create-D7J73A6H.js +45 -0
  38. package/dist/{create-4YBRTTJS.js → create-QWV73WXD.js} +1 -1
  39. package/dist/{daemon-client-Z7FAJ6JW.js → daemon-client-I42FK2BF.js} +2 -2
  40. package/dist/{daemon-restart-BJZ3O4U4.js → daemon-restart-M2QTYMEG.js} +7 -7
  41. package/dist/daemon.js +1758 -1024
  42. package/dist/db-IC4J52XQ.js +8 -0
  43. package/dist/{delete-27OYNK25.js → delete-4JYGD4VN.js} +1 -1
  44. package/dist/down-LVBXEULC.js +14 -0
  45. package/dist/{env-M336ONDP.js → env-YJMUMFIY.js} +2 -2
  46. package/dist/{export-HP4G5DQC.js → export-BOJQWBMA.js} +4 -4
  47. package/dist/{file-HUDKTRAS.js → file-CR36YUPD.js} +4 -4
  48. package/dist/{history-B64GTFTD.js → history-XKRTAFS2.js} +5 -5
  49. package/dist/{import-XIB7UV4S.js → import-SRTQXBGH.js} +4 -4
  50. package/dist/join-J4QU42DL.js +66 -0
  51. package/dist/list-R73GENNL.js +40 -0
  52. package/dist/{log-PBFNILJ4.js → log-ABYNVYJ3.js} +4 -4
  53. package/dist/{login-B5E7N7MY.js → login-3QZNR2DF.js} +4 -4
  54. package/dist/{login-6U7U6BNG.js → login-XX37I52P.js} +2 -2
  55. package/dist/{logout-XSJRYS3U.js → logout-T53VKCPU.js} +4 -4
  56. package/dist/{logout-UKD5LA37.js → logout-W4KOOBIT.js} +2 -2
  57. package/dist/{logs-3CART7O7.js → logs-U35JR2KE.js} +5 -5
  58. package/dist/{merge-VK2HSKMA.js → merge-LNSMSAOF.js} +4 -4
  59. package/dist/message-delivery-LDXLGERA.js +25 -0
  60. package/dist/migrate-registry-to-db-XC7T5B7P.js +110 -0
  61. package/dist/{mind-HZ3QSDDJ.js → mind-DI33C74K.js} +25 -25
  62. package/dist/{mind-activity-tracker-4G6FURY2.js → mind-activity-tracker-EN6XNXPF.js} +3 -4
  63. package/dist/mind-manager-M6EMUW5I.js +18 -0
  64. package/dist/{mind-sleep-DTV7L44D.js → mind-sleep-BTSWQNAC.js} +4 -4
  65. package/dist/{mind-wake-PFN4FN3T.js → mind-wake-SBAKIDVP.js} +4 -4
  66. package/dist/{notes-37FW2UR2.js → notes-XCER3I7M.js} +11 -21
  67. package/dist/{package-VZWLXPHV.js → package-7WY6VKU3.js} +1 -1
  68. package/dist/{pages-DIIT5HMQ.js → pages-6EBS6CBR.js} +2 -2
  69. package/dist/{publish-HQV7YREB.js → publish-66UB2ZFY.js} +5 -5
  70. package/dist/{pull-2MB4SK3C.js → pull-XCHJTM5M.js} +4 -4
  71. package/dist/read-36UFXN3G.js +46 -0
  72. package/dist/{register-EFND67FQ.js → register-6B2CXTYM.js} +2 -2
  73. package/dist/{registry-D2BSQ2X5.js → registry-NDNOOYG4.js} +15 -9
  74. package/dist/{restart-CCK7D6TV.js → restart-6ESL3NBO.js} +5 -5
  75. package/dist/{sandbox-EHGFF52K.js → sandbox-TGBX22DS.js} +3 -3
  76. package/dist/{schedule-6F7ELB2M.js → schedule-QTJMFATP.js} +5 -5
  77. package/dist/{seed-E5OQGWX3.js → seed-SSUCYYDF.js} +2 -2
  78. package/dist/{send-IH6XZKPC.js → send-ZNCJDSRP.js} +25 -19
  79. package/dist/{service-LLBV3R7M.js → service-6LIN3F3K.js} +4 -4
  80. package/dist/{setup-F6TWFYGQ.js → setup-JG4QAEBV.js} +12 -12
  81. package/dist/{setup-YGAAIKKZ.js → setup-JHL5ZEST.js} +2 -2
  82. package/dist/{shared-UMO4S7CC.js → shared-ML5I4Q2A.js} +4 -4
  83. package/dist/{skill-42LGFBQC.js → skill-AUAQTSP5.js} +5 -5
  84. package/dist/skills/dreaming/references/INSTALL.md +2 -2
  85. package/dist/skills/orientation/SKILL.md +3 -3
  86. package/dist/skills/volute-mind/SKILL.md +32 -30
  87. package/dist/sleep-manager-MWYHM5HV.js +29 -0
  88. package/dist/split-TKJ5OT3P.js +63 -0
  89. package/dist/{sprout-QL74KR2X.js → sprout-IJVVKSJ2.js} +6 -7
  90. package/dist/{start-O5JQASRC.js → start-EUJSS5R4.js} +2 -2
  91. package/dist/{status-FZBEBM7Q.js → status-77YEPHMW.js} +5 -5
  92. package/dist/{status-WXD4HXRL.js → status-7GA4SM4Y.js} +4 -4
  93. package/dist/{status-LV34BG6G.js → status-THLOBLWG.js} +2 -2
  94. package/dist/{stop-2SOG5NYF.js → stop-3XAITBBF.js} +5 -5
  95. package/dist/{tailscale-AJ4VL5XK.js → tailscale-NY5MUMY3.js} +1 -1
  96. package/dist/up-NKSMXBWR.js +17 -0
  97. package/dist/{update-5VUDAI3D.js → update-PTSH22AZ.js} +9 -9
  98. package/dist/{update-check-F5Z3ALXX.js → update-check-64FWC4Y2.js} +2 -2
  99. package/dist/{upgrade-QCCO33BK.js → upgrade-HA47CS4C.js} +12 -5
  100. package/dist/variant-7TGZHOU3.js +41 -0
  101. package/dist/{version-notify-USFZBWMG.js → version-notify-5Z4MNR6M.js} +26 -30
  102. package/dist/web-assets/assets/index-CI5wgghI.css +1 -0
  103. package/dist/web-assets/assets/index-is5CvJWH.js +75 -0
  104. package/dist/web-assets/favicon.png +0 -0
  105. package/dist/web-assets/index.html +2 -2
  106. package/drizzle/0017_minds.sql +16 -0
  107. package/drizzle/meta/_journal.json +7 -0
  108. package/package.json +1 -1
  109. package/templates/_base/.init/.config/prompts.json +2 -2
  110. package/templates/_base/home/VOLUTE.md +5 -5
  111. package/templates/_base/src/lib/startup.ts +2 -2
  112. package/dist/channel-ZVZV42UD.js +0 -260
  113. package/dist/chunk-B2CPS4QU.js +0 -283
  114. package/dist/chunk-SIAG3QMM.js +0 -42
  115. package/dist/chunk-WSLPZF72.js +0 -173
  116. package/dist/connector-G722WXAU.js +0 -147
  117. package/dist/connectors/discord.js +0 -177
  118. package/dist/connectors/slack.js +0 -181
  119. package/dist/connectors/telegram.js +0 -187
  120. package/dist/down-7UKFMJJZ.js +0 -14
  121. package/dist/message-delivery-MS5JYPZX.js +0 -25
  122. package/dist/mind-manager-VVK67AY3.js +0 -19
  123. package/dist/sleep-manager-EE4NRN2Q.js +0 -29
  124. package/dist/up-SDMCSVI3.js +0 -17
  125. package/dist/variant-WWLDY6D5.js +0 -207
  126. package/dist/web-assets/assets/index-CUQ31ieL.js +0 -69
  127. package/dist/web-assets/assets/index-CW8NSl1o.css +0 -1
@@ -1,81 +1,101 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  readSystemsConfig
4
- } from "./chunk-HFCBO2GL.js";
4
+ } from "./chunk-KDGS53OS.js";
5
5
  import {
6
6
  markIdle
7
- } from "./chunk-NWI2425I.js";
7
+ } from "./chunk-K5NAC55T.js";
8
8
  import {
9
- broadcast,
10
- publish,
11
- subscribe
12
- } from "./chunk-J2CO4WEV.js";
13
- import {
14
- RestartTracker,
15
- RotatingLog,
16
9
  clearJsonMap,
17
10
  getMindManager,
18
- getMindToken,
19
11
  getPrompt,
20
12
  loadJsonMap,
21
13
  saveJsonMap
22
- } from "./chunk-2VO7453N.js";
14
+ } from "./chunk-PHSAT7YL.js";
23
15
  import {
24
- isSandboxEnabled,
25
- wrapForSandbox
26
- } from "./chunk-UTL75LP6.js";
16
+ addMessage,
17
+ createChannel,
18
+ getChannelByName,
19
+ getParticipants,
20
+ joinChannel,
21
+ publish as publish2
22
+ } from "./chunk-IAYBDWVG.js";
27
23
  import {
28
- conversationParticipants,
29
- conversationReads,
30
- conversations,
31
- deliveryQueue,
32
- getDb,
33
- messages,
34
- mindHistory,
35
- users
36
- } from "./chunk-WBHMQ5OZ.js";
24
+ broadcast,
25
+ publish,
26
+ subscribe
27
+ } from "./chunk-VIVMW2H2.js";
37
28
  import {
38
29
  logger_default
39
30
  } from "./chunk-YUIHSKR6.js";
40
- import {
41
- readVoluteConfig
42
- } from "./chunk-SIAG3QMM.js";
43
- import {
44
- loadMergedEnv
45
- } from "./chunk-PHU4DEAJ.js";
46
31
  import {
47
32
  exec
48
- } from "./chunk-YJA7P64S.js";
49
- import {
50
- chownMindDir,
51
- isIsolationEnabled,
52
- wrapForIsolation
53
- } from "./chunk-XOXLRRR2.js";
33
+ } from "./chunk-AW7PFDVN.js";
54
34
  import {
55
- daemonLoopback,
35
+ deliveryQueue,
56
36
  findMind,
57
- findVariant,
37
+ getBaseName,
38
+ getDb,
58
39
  mindDir,
40
+ mindHistory,
59
41
  readRegistry,
60
42
  stateDir,
61
- voluteHome
62
- } from "./chunk-B2CPS4QU.js";
43
+ users,
44
+ voluteHome,
45
+ voluteSystemDir
46
+ } from "./chunk-H7OZRFJB.js";
63
47
 
64
48
  // src/lib/daemon/sleep-manager.ts
65
49
  import { execFile, spawn as spawnChild } from "child_process";
66
50
  import {
67
51
  existsSync as existsSync5,
68
- mkdirSync as mkdirSync3,
52
+ mkdirSync as mkdirSync4,
69
53
  readdirSync as readdirSync2,
70
54
  readFileSync as readFileSync5,
71
55
  readlinkSync,
72
56
  renameSync,
73
- writeFileSync as writeFileSync3
57
+ writeFileSync as writeFileSync4
74
58
  } from "fs";
75
59
  import { resolve as resolve8 } from "path";
76
60
  import { promisify } from "util";
77
61
  import { CronExpressionParser as CronExpressionParser2 } from "cron-parser";
78
- import { and as and4, eq as eq4, inArray as inArray3 } from "drizzle-orm";
62
+ import { and as and3, eq as eq3, inArray as inArray2 } from "drizzle-orm";
63
+
64
+ // src/lib/volute-config.ts
65
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
66
+ import { dirname, resolve } from "path";
67
+ function readJson(path) {
68
+ if (!existsSync(path)) return null;
69
+ try {
70
+ return JSON.parse(readFileSync(path, "utf-8"));
71
+ } catch (err) {
72
+ console.error(`[volute-config] failed to parse ${path}: ${err}`);
73
+ return null;
74
+ }
75
+ }
76
+ function readVoluteConfig(mindDir2) {
77
+ const path = resolve(mindDir2, "home/.config/volute.json");
78
+ const config = readJson(path);
79
+ if (!config) return null;
80
+ const legacy = config;
81
+ if (!config.profile && ("displayName" in config || "description" in config || "avatar" in config)) {
82
+ config.profile = {
83
+ displayName: legacy.displayName,
84
+ description: legacy.description,
85
+ avatar: legacy.avatar
86
+ };
87
+ delete legacy.displayName;
88
+ delete legacy.description;
89
+ delete legacy.avatar;
90
+ }
91
+ return config;
92
+ }
93
+ function writeVoluteConfig(mindDir2, config) {
94
+ const path = resolve(mindDir2, "home/.config/volute.json");
95
+ mkdirSync(dirname(path), { recursive: true });
96
+ writeFileSync(path, `${JSON.stringify(config, null, 2)}
97
+ `);
98
+ }
79
99
 
80
100
  // src/lib/auth.ts
81
101
  import { compareSync, hashSync } from "bcryptjs";
@@ -208,8 +228,8 @@ async function migrateMindRoles() {
208
228
  }
209
229
 
210
230
  // src/lib/pages-watcher.ts
211
- import { existsSync, readdirSync, statSync, watch } from "fs";
212
- import { join, resolve } from "path";
231
+ import { existsSync as existsSync2, readdirSync, statSync, watch } from "fs";
232
+ import { join, resolve as resolve2 } from "path";
213
233
  var watchers = /* @__PURE__ */ new Map();
214
234
  var homeWatchers = /* @__PURE__ */ new Map();
215
235
  var debounceTimers = /* @__PURE__ */ new Map();
@@ -245,18 +265,18 @@ function startPagesWatcher(mindName, pagesDir) {
245
265
  }
246
266
  function startWatcher(mindName) {
247
267
  if (watchers.has(mindName)) return;
248
- const pagesDir = resolve(mindDir(mindName), "home", "public", "pages");
249
- if (existsSync(pagesDir)) {
268
+ const pagesDir = resolve2(mindDir(mindName), "home", "public", "pages");
269
+ if (existsSync2(pagesDir)) {
250
270
  startPagesWatcher(mindName, pagesDir);
251
271
  return;
252
272
  }
253
273
  if (homeWatchers.has(mindName)) return;
254
- const publicDir = resolve(mindDir(mindName), "home", "public");
255
- if (!existsSync(publicDir)) return;
274
+ const publicDir = resolve2(mindDir(mindName), "home", "public");
275
+ if (!existsSync2(publicDir)) return;
256
276
  try {
257
277
  const hw = watch(publicDir, (_eventType, filename) => {
258
278
  if (filename !== "pages") return;
259
- if (!existsSync(pagesDir)) return;
279
+ if (!existsSync2(pagesDir)) return;
260
280
  hw.close();
261
281
  homeWatchers.delete(mindName);
262
282
  invalidateCache();
@@ -314,7 +334,7 @@ function scanPagesDir(dir, urlPrefix) {
314
334
  }
315
335
  for (const item of items) {
316
336
  if (item.startsWith(".")) continue;
317
- const fullPath = resolve(dir, item);
337
+ const fullPath = resolve2(dir, item);
318
338
  try {
319
339
  const s = statSync(fullPath);
320
340
  if (s.isFile() && item.endsWith(".html")) {
@@ -324,8 +344,8 @@ function scanPagesDir(dir, urlPrefix) {
324
344
  url: `${urlPrefix}/${item}`
325
345
  });
326
346
  } else if (s.isDirectory()) {
327
- const indexPath = resolve(fullPath, "index.html");
328
- if (existsSync(indexPath)) {
347
+ const indexPath = resolve2(fullPath, "index.html");
348
+ if (existsSync2(indexPath)) {
329
349
  const indexStat = statSync(indexPath);
330
350
  pages.push({
331
351
  file: join(item, "index.html"),
@@ -340,19 +360,19 @@ function scanPagesDir(dir, urlPrefix) {
340
360
  pages.sort((a, b) => new Date(b.modified).getTime() - new Date(a.modified).getTime());
341
361
  return pages;
342
362
  }
343
- function buildSites() {
363
+ async function buildSites() {
344
364
  const sites = [];
345
- const systemPagesDir = resolve(voluteHome(), "shared", "pages");
346
- if (existsSync(systemPagesDir)) {
365
+ const systemPagesDir = resolve2(voluteHome(), "shared", "pages");
366
+ if (existsSync2(systemPagesDir)) {
347
367
  const systemPages = scanPagesDir(systemPagesDir, "/pages/_system");
348
368
  if (systemPages.length > 0) {
349
369
  sites.push({ name: "_system", label: "System", pages: systemPages });
350
370
  }
351
371
  }
352
- const entries = readRegistry();
372
+ const entries = await readRegistry();
353
373
  for (const entry of [...entries].sort((a, b) => a.name.localeCompare(b.name))) {
354
- const pagesDir = resolve(mindDir(entry.name), "home", "public", "pages");
355
- if (!existsSync(pagesDir)) continue;
374
+ const pagesDir = resolve2(mindDir(entry.name), "home", "public", "pages");
375
+ if (!existsSync2(pagesDir)) continue;
356
376
  const mindPages = scanPagesDir(pagesDir, `/pages/${entry.name}`);
357
377
  if (mindPages.length > 0) {
358
378
  sites.push({ name: entry.name, label: entry.name, pages: mindPages });
@@ -360,12 +380,12 @@ function buildSites() {
360
380
  }
361
381
  return sites;
362
382
  }
363
- function buildRecentPages() {
364
- const entries = readRegistry();
383
+ async function buildRecentPages() {
384
+ const entries = await readRegistry();
365
385
  const pages = [];
366
386
  for (const entry of entries) {
367
- const pagesDir = resolve(mindDir(entry.name), "home", "public", "pages");
368
- if (!existsSync(pagesDir)) continue;
387
+ const pagesDir = resolve2(mindDir(entry.name), "home", "public", "pages");
388
+ if (!existsSync2(pagesDir)) continue;
369
389
  let items;
370
390
  try {
371
391
  items = readdirSync(pagesDir);
@@ -374,7 +394,7 @@ function buildRecentPages() {
374
394
  }
375
395
  for (const item of items) {
376
396
  if (item.startsWith(".")) continue;
377
- const fullPath = resolve(pagesDir, item);
397
+ const fullPath = resolve2(pagesDir, item);
378
398
  try {
379
399
  const s = statSync(fullPath);
380
400
  if (s.isFile() && item.endsWith(".html")) {
@@ -385,8 +405,8 @@ function buildRecentPages() {
385
405
  url: `/pages/${entry.name}/${item}`
386
406
  });
387
407
  } else if (s.isDirectory()) {
388
- const indexPath = resolve(fullPath, "index.html");
389
- if (existsSync(indexPath)) {
408
+ const indexPath = resolve2(fullPath, "index.html");
409
+ if (existsSync2(indexPath)) {
390
410
  const indexStat = statSync(indexPath);
391
411
  pages.push({
392
412
  mind: entry.name,
@@ -403,836 +423,72 @@ function buildRecentPages() {
403
423
  pages.sort((a, b) => new Date(b.modified).getTime() - new Date(a.modified).getTime());
404
424
  return pages.slice(0, 10);
405
425
  }
406
- function getCachedSites() {
407
- if (!sitesCache) sitesCache = buildSites();
426
+ async function getCachedSites() {
427
+ if (!sitesCache) sitesCache = await buildSites();
408
428
  return sitesCache;
409
429
  }
410
- function getCachedRecentPages() {
411
- if (!recentPagesCache) recentPagesCache = buildRecentPages();
430
+ async function getCachedRecentPages() {
431
+ if (!recentPagesCache) recentPagesCache = await buildRecentPages();
412
432
  return recentPagesCache;
413
433
  }
414
434
 
415
- // src/lib/events/conversations.ts
416
- import { randomUUID } from "crypto";
417
- import { and as and2, desc, eq as eq2, inArray as inArray2, isNull, lt, sql } from "drizzle-orm";
418
-
419
- // src/lib/webhook.ts
420
- var slog = logger_default.child("webhook");
421
- function getWebhookUrl() {
422
- return process.env.VOLUTE_WEBHOOK_URL;
423
- }
424
- function getAuthHeaders() {
425
- const headers = { "Content-Type": "application/json" };
426
- const secret = process.env.VOLUTE_WEBHOOK_SECRET;
427
- if (secret) headers.Authorization = `Bearer ${secret}`;
428
- return headers;
429
- }
430
- function fireWebhook(event) {
435
+ // src/connectors/sdk.ts
436
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
437
+ import { join as join2, resolve as resolve3 } from "path";
438
+ function splitMessage(text, maxLength) {
439
+ const chunks = [];
440
+ while (text.length > maxLength) {
441
+ let splitAt = text.lastIndexOf("\n", maxLength);
442
+ if (splitAt < maxLength / 2) splitAt = maxLength;
443
+ chunks.push(text.slice(0, splitAt));
444
+ text = text.slice(splitAt).replace(/^\n/, "");
445
+ }
446
+ if (text) chunks.push(text);
447
+ return chunks;
448
+ }
449
+ function readChannelMap(mindName) {
450
+ const filePath = join2(stateDir(mindName), "channels.json");
451
+ if (!existsSync3(filePath)) return {};
431
452
  try {
432
- const url = getWebhookUrl();
433
- if (!url) return;
434
- const payload = { ...event, timestamp: event.timestamp ?? (/* @__PURE__ */ new Date()).toISOString() };
435
- fetch(url, {
436
- method: "POST",
437
- headers: getAuthHeaders(),
438
- body: JSON.stringify(payload)
439
- }).then((res) => {
440
- if (!res.ok) {
441
- slog.warn(`webhook ${event.event} returned HTTP ${res.status}`);
442
- }
443
- }).catch((err) => {
444
- slog.warn(`webhook delivery failed for ${event.event}`, logger_default.errorData(err));
445
- });
453
+ return JSON.parse(readFileSync2(filePath, "utf-8"));
446
454
  } catch (err) {
447
- slog.error(`webhook ${event.event} failed to serialize`, logger_default.errorData(err));
448
- }
449
- }
450
- function initWebhook() {
451
- const url = getWebhookUrl();
452
- if (!url) return () => {
453
- };
454
- try {
455
- const parsed = new URL(url);
456
- if (!["http:", "https:"].includes(parsed.protocol)) {
457
- slog.error(`VOLUTE_WEBHOOK_URL has unsupported protocol: ${parsed.protocol}`);
458
- return () => {
459
- };
460
- }
461
- } catch {
462
- slog.error(`VOLUTE_WEBHOOK_URL is not a valid URL`);
463
- return () => {
464
- };
465
- }
466
- slog.info("webhook enabled");
467
- return subscribe((event) => {
468
- try {
469
- fireWebhook({
470
- event: event.type,
471
- mind: event.mind,
472
- data: { summary: event.summary, ...event.metadata },
473
- timestamp: event.created_at
474
- });
475
- } catch (err) {
476
- slog.error(`failed to fire webhook for ${event.type}`, logger_default.errorData(err));
477
- }
478
- });
479
- }
480
-
481
- // src/lib/events/conversation-events.ts
482
- var subscribers = /* @__PURE__ */ new Map();
483
- function subscribe2(conversationId, callback) {
484
- let set = subscribers.get(conversationId);
485
- if (!set) {
486
- set = /* @__PURE__ */ new Set();
487
- subscribers.set(conversationId, set);
488
- }
489
- set.add(callback);
490
- return () => {
491
- set.delete(callback);
492
- if (set.size === 0) subscribers.delete(conversationId);
493
- };
494
- }
495
- function publish2(conversationId, event) {
496
- const set = subscribers.get(conversationId);
497
- if (!set) return;
498
- for (const cb of set) {
499
- try {
500
- cb(event);
501
- } catch (err) {
502
- console.error("[conversation-events] subscriber threw:", err);
503
- set.delete(cb);
504
- if (set.size === 0) subscribers.delete(conversationId);
505
- }
506
- }
507
- }
508
-
509
- // src/lib/events/conversations.ts
510
- async function createConversation(mindName, channel, opts) {
511
- const db = await getDb();
512
- const id = randomUUID();
513
- const type = opts?.type ?? "dm";
514
- const name = opts?.name ?? null;
515
- await db.transaction(async (tx) => {
516
- await tx.insert(conversations).values({
517
- id,
518
- mind_name: mindName,
519
- channel,
520
- type,
521
- name,
522
- user_id: opts?.userId ?? null,
523
- title: opts?.title ?? null
524
- });
525
- if (opts?.participantIds && opts.participantIds.length > 0) {
526
- await tx.insert(conversationParticipants).values(
527
- opts.participantIds.map((uid, i) => ({
528
- conversation_id: id,
529
- user_id: uid,
530
- role: i === 0 ? "owner" : "member"
531
- }))
532
- );
533
- }
534
- });
535
- fireWebhook({
536
- event: "conversation_created",
537
- mind: mindName ?? "",
538
- data: { id, mindName, channel, type, name, title: opts?.title ?? null }
539
- });
540
- return {
541
- id,
542
- mind_name: mindName,
543
- channel,
544
- type,
545
- name,
546
- user_id: opts?.userId ?? null,
547
- title: opts?.title ?? null,
548
- created_at: (/* @__PURE__ */ new Date()).toISOString(),
549
- updated_at: (/* @__PURE__ */ new Date()).toISOString()
550
- };
551
- }
552
- async function getConversation(id) {
553
- const db = await getDb();
554
- const row = await db.select().from(conversations).where(eq2(conversations.id, id)).get();
555
- return row ?? null;
556
- }
557
- async function addParticipant(conversationId, userId, role = "member") {
558
- const db = await getDb();
559
- await db.insert(conversationParticipants).values({
560
- conversation_id: conversationId,
561
- user_id: userId,
562
- role
563
- });
564
- }
565
- async function removeParticipant(conversationId, userId) {
566
- const db = await getDb();
567
- await db.delete(conversationParticipants).where(
568
- and2(
569
- eq2(conversationParticipants.conversation_id, conversationId),
570
- eq2(conversationParticipants.user_id, userId)
571
- )
572
- );
573
- }
574
- async function getParticipants(conversationId) {
575
- const db = await getDb();
576
- const rows = await db.select({
577
- userId: conversationParticipants.user_id,
578
- username: users.username,
579
- userType: users.user_type,
580
- role: conversationParticipants.role,
581
- displayName: users.display_name,
582
- description: users.description,
583
- avatar: users.avatar
584
- }).from(conversationParticipants).innerJoin(users, eq2(conversationParticipants.user_id, users.id)).where(eq2(conversationParticipants.conversation_id, conversationId)).all();
585
- return rows;
586
- }
587
- async function isParticipant(conversationId, userId) {
588
- const db = await getDb();
589
- const row = await db.select({ user_id: conversationParticipants.user_id }).from(conversationParticipants).where(
590
- and2(
591
- eq2(conversationParticipants.conversation_id, conversationId),
592
- eq2(conversationParticipants.user_id, userId)
593
- )
594
- ).get();
595
- return row != null;
596
- }
597
- async function listConversationsForUser(userId) {
598
- const db = await getDb();
599
- const participantRows = await db.select({ conversation_id: conversationParticipants.conversation_id }).from(conversationParticipants).where(eq2(conversationParticipants.user_id, userId)).all();
600
- if (participantRows.length === 0) return [];
601
- const convIds = participantRows.map((r) => r.conversation_id);
602
- return await db.select().from(conversations).where(inArray2(conversations.id, convIds)).orderBy(desc(conversations.updated_at)).all();
603
- }
604
- async function isParticipantOrOwner(conversationId, userId) {
605
- if (await isParticipant(conversationId, userId)) return true;
606
- const db = await getDb();
607
- const row = await db.select().from(conversations).where(and2(eq2(conversations.id, conversationId), eq2(conversations.user_id, userId))).get();
608
- return row != null;
609
- }
610
- async function deleteConversationForUser(id, userId) {
611
- if (!await isParticipantOrOwner(id, userId)) return false;
612
- await deleteConversation(id);
613
- return true;
614
- }
615
- async function addMessage(conversationId, role, senderName, content) {
616
- const db = await getDb();
617
- const serialized = JSON.stringify(content);
618
- const [result] = await db.insert(messages).values({ conversation_id: conversationId, role, sender_name: senderName, content: serialized }).returning({ id: messages.id, created_at: messages.created_at });
619
- await db.update(conversations).set({ updated_at: sql`datetime('now')` }).where(eq2(conversations.id, conversationId));
620
- if (role === "user") {
621
- const firstText = content.find((b) => b.type === "text");
622
- const title = firstText ? firstText.text.slice(0, 80) : "";
623
- if (title) {
624
- await db.update(conversations).set({ title }).where(and2(eq2(conversations.id, conversationId), isNull(conversations.title)));
625
- }
626
- }
627
- const msg = {
628
- id: result.id,
629
- conversation_id: conversationId,
630
- role,
631
- sender_name: senderName,
632
- content,
633
- created_at: result.created_at
634
- };
635
- publish2(conversationId, {
636
- type: "message",
637
- id: msg.id,
638
- role: msg.role,
639
- senderName: msg.sender_name,
640
- content: msg.content,
641
- createdAt: msg.created_at
642
- });
643
- const conv = await db.select({ mind_name: conversations.mind_name }).from(conversations).where(eq2(conversations.id, conversationId)).get();
644
- fireWebhook({
645
- event: "message_created",
646
- mind: conv?.mind_name ?? "",
647
- data: {
648
- conversationId,
649
- messageId: result.id,
650
- role,
651
- senderName,
652
- content: content.filter((b) => b.type !== "image"),
653
- createdAt: result.created_at
654
- }
655
- });
656
- return msg;
657
- }
658
- async function getMessages(conversationId) {
659
- const db = await getDb();
660
- const rows = await db.select().from(messages).where(eq2(messages.conversation_id, conversationId)).orderBy(messages.created_at).all();
661
- return rows.map(parseMessageRow);
662
- }
663
- async function getMessagesPaginated(conversationId, opts) {
664
- const db = await getDb();
665
- const limit = Math.min(Math.max(opts?.limit ?? 50, 1), 100);
666
- const conditions = [eq2(messages.conversation_id, conversationId)];
667
- if (opts?.before != null) {
668
- conditions.push(lt(messages.id, opts.before));
669
- }
670
- const rows = await db.select().from(messages).where(and2(...conditions)).orderBy(desc(messages.id)).limit(limit + 1).all();
671
- const hasMore = rows.length > limit;
672
- const page = rows.slice(0, limit).reverse();
673
- return {
674
- messages: page.map(parseMessageRow),
675
- hasMore
676
- };
677
- }
678
- function parseMessageRow(row) {
679
- let content;
680
- try {
681
- const parsed = JSON.parse(row.content);
682
- content = Array.isArray(parsed) ? parsed : [{ type: "text", text: row.content }];
683
- } catch {
684
- content = [{ type: "text", text: row.content }];
685
- }
686
- return { ...row, role: row.role, content };
687
- }
688
- async function listConversationsWithParticipants(userId) {
689
- const convs = await listConversationsForUser(userId);
690
- if (convs.length === 0) return [];
691
- const db = await getDb();
692
- const convIds = convs.map((c) => c.id);
693
- const rows = await db.select({
694
- conversationId: conversationParticipants.conversation_id,
695
- userId: users.id,
696
- username: users.username,
697
- userType: users.user_type,
698
- role: conversationParticipants.role,
699
- displayName: users.display_name,
700
- description: users.description,
701
- avatar: users.avatar
702
- }).from(conversationParticipants).innerJoin(users, eq2(conversationParticipants.user_id, users.id)).where(inArray2(conversationParticipants.conversation_id, convIds));
703
- const byConv = /* @__PURE__ */ new Map();
704
- for (const r of rows) {
705
- let arr = byConv.get(r.conversationId);
706
- if (!arr) {
707
- arr = [];
708
- byConv.set(r.conversationId, arr);
709
- }
710
- arr.push({
711
- userId: r.userId,
712
- username: r.username,
713
- userType: r.userType,
714
- role: r.role,
715
- displayName: r.displayName,
716
- description: r.description,
717
- avatar: r.avatar
718
- });
719
- }
720
- const lastMsgIds = await db.select({
721
- conversationId: messages.conversation_id,
722
- maxId: sql`MAX(${messages.id})`
723
- }).from(messages).where(inArray2(messages.conversation_id, convIds)).groupBy(messages.conversation_id);
724
- const byLastMsg = /* @__PURE__ */ new Map();
725
- if (lastMsgIds.length > 0) {
726
- const msgRows = await db.select().from(messages).where(
727
- inArray2(
728
- messages.id,
729
- lastMsgIds.map((r) => r.maxId)
730
- )
731
- );
732
- for (const m of msgRows) {
733
- let text = "";
734
- try {
735
- const parsed = JSON.parse(m.content);
736
- const blocks = Array.isArray(parsed) ? parsed : [];
737
- const textBlock = blocks.find((b) => b.type === "text");
738
- if (textBlock && "text" in textBlock) text = textBlock.text;
739
- } catch {
740
- text = m.content;
741
- }
742
- byLastMsg.set(m.conversation_id, {
743
- role: m.role,
744
- senderName: m.sender_name,
745
- text,
746
- createdAt: m.created_at
747
- });
748
- }
749
- }
750
- return convs.map((c) => ({
751
- ...c,
752
- participants: byConv.get(c.id) ?? [],
753
- lastMessage: byLastMsg.get(c.id)
754
- }));
755
- }
756
- async function findDMConversation(mindName, participantIds) {
757
- const db = await getDb();
758
- const mindConvs = await db.select({ id: conversations.id }).from(conversations).where(and2(eq2(conversations.mind_name, mindName), eq2(conversations.type, "dm"))).all();
759
- for (const conv of mindConvs) {
760
- const rows = await db.select({ user_id: conversationParticipants.user_id }).from(conversationParticipants).where(eq2(conversationParticipants.conversation_id, conv.id)).all();
761
- if (rows.length !== 2) continue;
762
- const ids = new Set(rows.map((r) => r.user_id));
763
- if (ids.has(participantIds[0]) && ids.has(participantIds[1])) {
764
- return conv.id;
765
- }
766
- }
767
- return null;
768
- }
769
- async function deleteConversation(id) {
770
- const db = await getDb();
771
- await db.delete(conversations).where(eq2(conversations.id, id));
772
- }
773
- async function createChannel(name, creatorId) {
774
- const participantIds = creatorId ? [creatorId] : [];
775
- return createConversation(null, "volute", {
776
- type: "channel",
777
- name,
778
- title: name,
779
- participantIds
780
- });
781
- }
782
- async function getChannelByName(name) {
783
- const db = await getDb();
784
- const row = await db.select().from(conversations).where(and2(eq2(conversations.name, name), eq2(conversations.type, "channel"))).get();
785
- return row ?? null;
786
- }
787
- async function listChannels() {
788
- const db = await getDb();
789
- return await db.select().from(conversations).where(eq2(conversations.type, "channel")).orderBy(conversations.name).all();
790
- }
791
- async function joinChannel(conversationId, userId) {
792
- if (await isParticipant(conversationId, userId)) return;
793
- await addParticipant(conversationId, userId);
794
- }
795
- async function leaveChannel(conversationId, userId) {
796
- await removeParticipant(conversationId, userId);
797
- }
798
- async function getUnreadCounts(userId, conversationIds) {
799
- if (conversationIds.length === 0) return {};
800
- const db = await getDb();
801
- const rows = await db.select({
802
- conversationId: messages.conversation_id,
803
- count: sql`COUNT(*)`
804
- }).from(messages).leftJoin(
805
- conversationReads,
806
- and2(
807
- eq2(conversationReads.conversation_id, messages.conversation_id),
808
- eq2(conversationReads.user_id, userId)
809
- )
810
- ).where(
811
- and2(
812
- inArray2(messages.conversation_id, conversationIds),
813
- sql`${messages.id} > COALESCE(${conversationReads.last_read_message_id}, 0)`
814
- )
815
- ).groupBy(messages.conversation_id);
816
- const result = {};
817
- for (const row of rows) {
818
- result[row.conversationId] = row.count;
819
- }
820
- return result;
821
- }
822
- async function markConversationRead(userId, conversationId) {
823
- const db = await getDb();
824
- const maxRow = await db.select({ maxId: sql`MAX(${messages.id})` }).from(messages).where(eq2(messages.conversation_id, conversationId)).get();
825
- const maxId = maxRow?.maxId ?? 0;
826
- if (maxId === 0) return;
827
- await db.insert(conversationReads).values({ user_id: userId, conversation_id: conversationId, last_read_message_id: maxId }).onConflictDoUpdate({
828
- target: [conversationReads.user_id, conversationReads.conversation_id],
829
- set: { last_read_message_id: maxId }
830
- });
831
- }
832
-
833
- // src/lib/system-channel.ts
834
- var SYSTEM_CHANNEL_NAME = "system";
835
- var cachedChannelId = null;
836
- async function ensureSystemChannel() {
837
- if (cachedChannelId) return cachedChannelId;
838
- const existing = await getChannelByName(SYSTEM_CHANNEL_NAME);
839
- if (existing) {
840
- cachedChannelId = existing.id;
841
- return existing.id;
842
- }
843
- const conv = await createChannel(SYSTEM_CHANNEL_NAME);
844
- cachedChannelId = conv.id;
845
- logger_default.info("created #system channel");
846
- return conv.id;
847
- }
848
- async function joinSystemChannel(userId) {
849
- const channelId = await ensureSystemChannel();
850
- await joinChannel(channelId, userId);
851
- }
852
- async function joinSystemChannelForMind(mindName) {
853
- const user = await getOrCreateMindUser(mindName);
854
- await joinSystemChannel(user.id);
855
- }
856
- async function announceToSystem(text) {
857
- const channelId = await ensureSystemChannel();
858
- await addMessage(channelId, "system", "system", [{ type: "text", text }]);
859
- }
860
-
861
- // src/lib/daemon/connector-manager.ts
862
- import { spawn } from "child_process";
863
- import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync2, unlinkSync, writeFileSync } from "fs";
864
- import { dirname, resolve as resolve3 } from "path";
865
-
866
- // src/lib/connector-defs.ts
867
- import { existsSync as existsSync2, readFileSync } from "fs";
868
- import { resolve as resolve2 } from "path";
869
- var BUILTIN_DEFS = {
870
- discord: {
871
- displayName: "Discord",
872
- description: "Connect to Discord as a bot",
873
- envVars: [
874
- {
875
- name: "DISCORD_TOKEN",
876
- required: true,
877
- description: "Discord bot token",
878
- scope: "mind"
879
- },
880
- {
881
- name: "DISCORD_GUILD_ID",
882
- required: false,
883
- description: "Discord server ID (optional, for slash commands)",
884
- scope: "mind"
885
- }
886
- ]
887
- },
888
- slack: {
889
- displayName: "Slack",
890
- description: "Connect to Slack via Socket Mode",
891
- envVars: [
892
- {
893
- name: "SLACK_BOT_TOKEN",
894
- required: true,
895
- description: "Slack bot token (xoxb-...)",
896
- scope: "mind"
897
- },
898
- {
899
- name: "SLACK_APP_TOKEN",
900
- required: true,
901
- description: "Slack app-level token (xapp-...) for Socket Mode",
902
- scope: "mind"
903
- }
904
- ]
905
- },
906
- telegram: {
907
- displayName: "Telegram",
908
- description: "Connect to Telegram via long polling",
909
- envVars: [
910
- {
911
- name: "TELEGRAM_BOT_TOKEN",
912
- required: true,
913
- description: "Telegram bot token from BotFather",
914
- scope: "mind"
915
- }
916
- ]
917
- }
918
- };
919
- function getConnectorDef(type, connectorDir) {
920
- if (BUILTIN_DEFS[type]) return BUILTIN_DEFS[type];
921
- if (connectorDir) {
922
- const jsonPath = resolve2(connectorDir, "connector.json");
923
- if (existsSync2(jsonPath)) {
924
- try {
925
- return JSON.parse(readFileSync(jsonPath, "utf-8"));
926
- } catch (err) {
927
- console.warn(`Failed to parse ${jsonPath}: ${err}`);
928
- return null;
929
- }
930
- }
455
+ console.error(`[sdk] failed to parse ${filePath}:`, err);
456
+ return {};
931
457
  }
932
- return null;
933
458
  }
934
- function checkMissingEnvVars(def, env) {
935
- return def.envVars.filter((v) => v.required && !env[v.name]);
459
+ function writeChannelEntry(mindName, slug, entry) {
460
+ const dir = stateDir(mindName);
461
+ mkdirSync2(dir, { recursive: true });
462
+ const filePath = join2(dir, "channels.json");
463
+ const map = readChannelMap(mindName);
464
+ map[slug] = entry;
465
+ writeFileSync2(filePath, JSON.stringify(map, null, 2) + "\n");
936
466
  }
937
-
938
- // src/lib/daemon/connector-manager.ts
939
- var clog = logger_default.child("connectors");
940
- function searchUpwards(...segments) {
941
- let searchDir = dirname(new URL(import.meta.url).pathname);
942
- for (let i = 0; i < 5; i++) {
943
- const candidate = resolve3(searchDir, ...segments);
944
- if (existsSync3(candidate)) return candidate;
945
- searchDir = dirname(searchDir);
946
- }
947
- return null;
948
- }
949
- var ConnectorManager = class {
950
- connectors = /* @__PURE__ */ new Map();
951
- stopping = /* @__PURE__ */ new Set();
952
- // "mind:type" keys currently being explicitly stopped
953
- shuttingDown = false;
954
- restartTracker = new RestartTracker();
955
- async startConnectors(mindName, mindDir2, mindPort, daemonPort) {
956
- const config = readVoluteConfig(mindDir2) ?? {};
957
- const types = config.connectors ?? [];
958
- await Promise.all(
959
- types.map(
960
- (type) => this.startConnector(mindName, mindDir2, mindPort, type, daemonPort).catch((err) => {
961
- clog.warn(`failed to start connector ${type} for ${mindName}`, logger_default.errorData(err));
962
- })
963
- )
964
- );
467
+ function resolveChannelId(mindName, slug) {
468
+ const map = readChannelMap(mindName);
469
+ if (map[slug]) {
470
+ return map[slug].platformId;
965
471
  }
966
- checkConnectorEnv(type, mindName, mindDir2) {
967
- const mindConnectorDir = resolve3(mindDir2, "connectors", type);
968
- const userConnectorDir = resolve3(voluteHome(), "connectors", type);
969
- const connectorDir = existsSync3(mindConnectorDir) ? mindConnectorDir : existsSync3(userConnectorDir) ? userConnectorDir : void 0;
970
- const def = getConnectorDef(type, connectorDir);
971
- if (!def) return null;
972
- const env = loadMergedEnv(mindName);
973
- const missing = checkMissingEnvVars(def, env);
974
- if (missing.length === 0) return null;
975
- return {
976
- missing: missing.map((v) => ({ name: v.name, description: v.description })),
977
- connectorName: def.displayName
978
- };
979
- }
980
- async startConnector(mindName, mindDir2, mindPort, type, daemonPort) {
981
- const existing = this.connectors.get(mindName)?.get(type);
982
- if (existing) {
983
- await new Promise((res) => {
984
- existing.child.on("exit", () => res());
985
- try {
986
- if (existing.child.pid) {
987
- process.kill(-existing.child.pid, "SIGTERM");
988
- } else {
989
- existing.child.kill("SIGTERM");
990
- }
991
- } catch {
992
- res();
993
- }
994
- setTimeout(() => {
995
- try {
996
- if (existing.child.pid) {
997
- process.kill(-existing.child.pid, "SIGKILL");
998
- } else {
999
- existing.child.kill("SIGKILL");
1000
- }
1001
- } catch {
1002
- }
1003
- res();
1004
- }, 3e3);
1005
- });
1006
- this.connectors.get(mindName)?.delete(type);
1007
- }
1008
- this.killOrphanConnector(mindName, type);
1009
- const mindConnector = resolve3(mindDir2, "connectors", type, "index.ts");
1010
- const userConnector = resolve3(voluteHome(), "connectors", type, "index.ts");
1011
- const builtinConnector = this.resolveBuiltinConnector(type);
1012
- let connectorScript;
1013
- let runtime;
1014
- if (existsSync3(mindConnector)) {
1015
- connectorScript = mindConnector;
1016
- runtime = resolve3(mindDir2, "node_modules", ".bin", "tsx");
1017
- } else if (existsSync3(userConnector)) {
1018
- connectorScript = userConnector;
1019
- runtime = this.resolveVoluteTsx();
1020
- } else if (builtinConnector) {
1021
- connectorScript = builtinConnector;
1022
- runtime = process.execPath;
1023
- } else {
1024
- throw new Error(`No connector code found for type: ${type}`);
1025
- }
1026
- const mindStateDir = stateDir(mindName);
1027
- const logsDir = resolve3(mindStateDir, "logs");
1028
- mkdirSync(logsDir, { recursive: true });
1029
- if (isIsolationEnabled()) {
1030
- try {
1031
- const [base] = mindName.split("@", 2);
1032
- chownMindDir(mindStateDir, base);
1033
- } catch (err) {
1034
- throw new Error(
1035
- `Cannot start connector ${type} for ${mindName}: failed to set ownership on state directory ${mindStateDir}: ${err instanceof Error ? err.message : err}`
1036
- );
1037
- }
1038
- }
1039
- const logStream = new RotatingLog(resolve3(logsDir, `${type}.log`));
1040
- const mindEnv = loadMergedEnv(mindName);
1041
- const prefix = `${type.toUpperCase()}_`;
1042
- const connectorEnv = Object.fromEntries(
1043
- Object.entries(mindEnv).filter(([k]) => k.startsWith(prefix))
1044
- );
1045
- const spawnOpts = {
1046
- stdio: ["ignore", "pipe", "pipe"],
1047
- detached: true,
1048
- env: {
1049
- ...process.env,
1050
- VOLUTE_MIND_PORT: String(mindPort),
1051
- VOLUTE_MIND_NAME: mindName,
1052
- VOLUTE_MIND_DIR: mindDir2,
1053
- ...daemonPort ? {
1054
- VOLUTE_DAEMON_URL: `http://${daemonLoopback()}:${daemonPort}`,
1055
- VOLUTE_DAEMON_TOKEN: getMindToken(mindName) ?? void 0
1056
- } : {},
1057
- ...connectorEnv
1058
- }
1059
- };
1060
- let spawnCmd;
1061
- let spawnArgs;
1062
- if (isIsolationEnabled()) {
1063
- [spawnCmd, spawnArgs] = wrapForIsolation(runtime, [connectorScript], mindName);
1064
- } else if (isSandboxEnabled()) {
1065
- [spawnCmd, spawnArgs] = await wrapForSandbox(runtime, [connectorScript], mindDir2, mindName, [
1066
- mindDir2,
1067
- mindStateDir
1068
- ]);
1069
- } else {
1070
- spawnCmd = runtime;
1071
- spawnArgs = [connectorScript];
1072
- }
1073
- const child = spawn(spawnCmd, spawnArgs, spawnOpts);
1074
- let lastStderr = "";
1075
- child.stdout?.pipe(logStream);
1076
- child.stderr?.on("data", (chunk) => {
1077
- logStream.write(chunk);
1078
- lastStderr = chunk.toString().trim();
1079
- });
1080
- if (child.pid) {
1081
- this.saveConnectorPid(mindName, type, child.pid);
1082
- }
1083
- if (!this.connectors.has(mindName)) {
1084
- this.connectors.set(mindName, /* @__PURE__ */ new Map());
1085
- }
1086
- this.connectors.get(mindName).set(type, { child, type });
1087
- const stopKey = `${mindName}:${type}`;
1088
- this.restartTracker.reset(stopKey);
1089
- child.on("exit", (code) => {
1090
- const mindMap = this.connectors.get(mindName);
1091
- if (mindMap?.get(type)?.child === child) {
1092
- mindMap.delete(type);
1093
- }
1094
- if (this.shuttingDown) return;
1095
- if (this.stopping.has(stopKey)) return;
1096
- clog.error(`connector ${type} for ${mindName} exited with code ${code}`);
1097
- if (lastStderr) clog.warn(`connector ${type} last output: ${lastStderr}`);
1098
- const { shouldRestart, delay, attempt } = this.restartTracker.recordCrash(stopKey);
1099
- if (!shouldRestart) {
1100
- clog.error(`connector ${type} for ${mindName} crashed ${attempt} times \u2014 giving up`);
1101
- return;
1102
- }
1103
- clog.info(
1104
- `restarting connector ${type} for ${mindName} \u2014 attempt ${attempt}/${this.restartTracker.maxRestartAttempts}, in ${delay}ms`
1105
- );
1106
- setTimeout(() => {
1107
- if (this.shuttingDown || this.stopping.has(stopKey)) return;
1108
- this.startConnector(mindName, mindDir2, mindPort, type, daemonPort).catch((err) => {
1109
- clog.error(`failed to restart connector ${type} for ${mindName}`, logger_default.errorData(err));
1110
- });
1111
- }, delay);
1112
- });
1113
- clog.info(`started connector ${type} for ${mindName}`);
1114
- }
1115
- async stopConnector(mindName, type) {
1116
- const mindMap = this.connectors.get(mindName);
1117
- if (!mindMap) return;
1118
- const tracked = mindMap.get(type);
1119
- if (!tracked) return;
1120
- const stopKey = `${mindName}:${type}`;
1121
- this.stopping.add(stopKey);
1122
- mindMap.delete(type);
1123
- await new Promise((resolve9) => {
1124
- tracked.child.on("exit", () => resolve9());
1125
- try {
1126
- process.kill(-tracked.child.pid, "SIGTERM");
1127
- } catch {
1128
- resolve9();
1129
- }
1130
- setTimeout(() => {
1131
- try {
1132
- process.kill(-tracked.child.pid, "SIGKILL");
1133
- } catch {
1134
- }
1135
- resolve9();
1136
- }, 5e3);
1137
- });
1138
- this.stopping.delete(stopKey);
1139
- this.restartTracker.reset(stopKey);
1140
- try {
1141
- this.removeConnectorPid(mindName, type);
1142
- } catch (err) {
1143
- clog.warn(`failed to remove PID file for ${type}/${mindName}`, logger_default.errorData(err));
1144
- }
1145
- clog.info(`stopped connector ${type} for ${mindName}`);
1146
- }
1147
- async stopConnectors(mindName) {
1148
- const mindMap = this.connectors.get(mindName);
1149
- if (!mindMap) return;
1150
- const types = [...mindMap.keys()];
1151
- await Promise.all(types.map((type) => this.stopConnector(mindName, type)));
1152
- this.connectors.delete(mindName);
1153
- }
1154
- async stopAll() {
1155
- this.shuttingDown = true;
1156
- const minds = [...this.connectors.keys()];
1157
- await Promise.all(minds.map((name) => this.stopConnectors(name)));
1158
- }
1159
- getConnectorStatus(mindName) {
1160
- const mindMap = this.connectors.get(mindName);
1161
- if (!mindMap) return [];
1162
- return [...mindMap.entries()].map(([type, tracked]) => ({
1163
- type,
1164
- running: !tracked.child.killed
1165
- }));
1166
- }
1167
- connectorPidPath(mindName, type) {
1168
- return resolve3(stateDir(mindName), "connectors", `${type}.pid`);
1169
- }
1170
- saveConnectorPid(mindName, type, pid) {
1171
- const pidPath = this.connectorPidPath(mindName, type);
1172
- mkdirSync(dirname(pidPath), { recursive: true });
1173
- writeFileSync(pidPath, String(pid));
1174
- }
1175
- removeConnectorPid(mindName, type) {
1176
- try {
1177
- unlinkSync(this.connectorPidPath(mindName, type));
1178
- } catch {
1179
- }
1180
- }
1181
- killOrphanConnector(mindName, type) {
1182
- const pidPath = this.connectorPidPath(mindName, type);
1183
- if (!existsSync3(pidPath)) return;
1184
- try {
1185
- const pid = parseInt(readFileSync2(pidPath, "utf-8").trim(), 10);
1186
- if (pid > 0) {
1187
- try {
1188
- process.kill(-pid, "SIGTERM");
1189
- } catch {
1190
- process.kill(pid, "SIGTERM");
1191
- }
1192
- clog.warn(`killed orphan connector ${type} (pid ${pid})`);
1193
- }
1194
- } catch {
1195
- }
1196
- try {
1197
- unlinkSync(pidPath);
1198
- } catch {
1199
- }
1200
- }
1201
- resolveBuiltinConnector(type) {
1202
- return searchUpwards("connectors", `${type}.js`);
1203
- }
1204
- resolveVoluteTsx() {
1205
- return searchUpwards("node_modules", ".bin", "tsx") ?? "tsx";
1206
- }
1207
- };
1208
- var instance = null;
1209
- function initConnectorManager() {
1210
- if (instance) throw new Error("ConnectorManager already initialized");
1211
- instance = new ConnectorManager();
1212
- return instance;
1213
- }
1214
- function getConnectorManager() {
1215
- if (!instance)
1216
- throw new Error("ConnectorManager not initialized \u2014 call initConnectorManager() first");
1217
- return instance;
472
+ const colonIndex = slug.indexOf(":");
473
+ return colonIndex >= 0 ? slug.slice(colonIndex + 1) : slug;
1218
474
  }
1219
475
 
1220
476
  // src/lib/events/mind-events.ts
1221
- var subscribers2 = /* @__PURE__ */ new Map();
1222
- function subscribe3(mind, callback) {
1223
- let set = subscribers2.get(mind);
477
+ var subscribers = /* @__PURE__ */ new Map();
478
+ function subscribe2(mind, callback) {
479
+ let set = subscribers.get(mind);
1224
480
  if (!set) {
1225
481
  set = /* @__PURE__ */ new Set();
1226
- subscribers2.set(mind, set);
482
+ subscribers.set(mind, set);
1227
483
  }
1228
484
  set.add(callback);
1229
485
  return () => {
1230
486
  set.delete(callback);
1231
- if (set.size === 0) subscribers2.delete(mind);
487
+ if (set.size === 0) subscribers.delete(mind);
1232
488
  };
1233
489
  }
1234
490
  function publish3(mind, event) {
1235
- const set = subscribers2.get(mind);
491
+ const set = subscribers.get(mind);
1236
492
  if (!set) return;
1237
493
  for (const cb of set) {
1238
494
  try {
@@ -1240,7 +496,7 @@ function publish3(mind, event) {
1240
496
  } catch (err) {
1241
497
  console.error("[mind-events] subscriber threw:", err);
1242
498
  set.delete(cb);
1243
- if (set.size === 0) subscribers2.delete(mind);
499
+ if (set.size === 0) subscribers.delete(mind);
1244
500
  }
1245
501
  }
1246
502
  }
@@ -1248,7 +504,7 @@ function publish3(mind, event) {
1248
504
  // src/lib/delivery/delivery-manager.ts
1249
505
  import { readFile, realpath } from "fs/promises";
1250
506
  import { extname, resolve as resolve5 } from "path";
1251
- import { and as and3, eq as eq3, sql as sql2 } from "drizzle-orm";
507
+ import { and as and2, eq as eq2, sql } from "drizzle-orm";
1252
508
 
1253
509
  // src/lib/typing.ts
1254
510
  var DEFAULT_TTL_MS = 1e4;
@@ -1308,7 +564,7 @@ var TypingMap = class {
1308
564
  dispose() {
1309
565
  clearInterval(this.sweepTimer);
1310
566
  this.channels.clear();
1311
- if (instance2 === this) instance2 = void 0;
567
+ if (instance === this) instance = void 0;
1312
568
  }
1313
569
  sweep() {
1314
570
  const now = Date.now();
@@ -1324,12 +580,12 @@ var TypingMap = class {
1324
580
  }
1325
581
  }
1326
582
  };
1327
- var instance2;
583
+ var instance;
1328
584
  function getTypingMap() {
1329
- if (!instance2) {
1330
- instance2 = new TypingMap();
585
+ if (!instance) {
586
+ instance = new TypingMap();
1331
587
  }
1332
- return instance2;
588
+ return instance;
1333
589
  }
1334
590
  function publishTypingForChannels(channels, map) {
1335
591
  for (const channel of channels) {
@@ -1549,7 +805,7 @@ var DeliveryManager = class {
1549
805
  * or queued for batching depending on the session's delivery mode.
1550
806
  */
1551
807
  async routeAndDeliver(mindName, payload) {
1552
- const [baseName] = mindName.split("@", 2);
808
+ const baseName = await getBaseName(mindName);
1553
809
  const config = getRoutingConfig(baseName);
1554
810
  const meta = {
1555
811
  channel: payload.channel,
@@ -1585,7 +841,7 @@ var DeliveryManager = class {
1585
841
  const sessionConfig = resolveDeliveryMode(config, sessionName, route.rule);
1586
842
  if (sessionConfig.delivery.mode === "batch") {
1587
843
  dlog2.debug(`enqueueing batch message for ${mindName}/${sessionName}`);
1588
- this.enqueueBatch(mindName, sessionName, payload, sessionConfig);
844
+ await this.enqueueBatch(mindName, sessionName, payload, sessionConfig);
1589
845
  return { routed: true, session: sessionName, destination: "mind", mode: "batch" };
1590
846
  }
1591
847
  await this.deliverToMind(mindName, sessionName, payload, sessionConfig);
@@ -1595,8 +851,8 @@ var DeliveryManager = class {
1595
851
  * Called when a mind's session emits a "done" event — decrements active count
1596
852
  * and may trigger batch flush if session goes idle.
1597
853
  */
1598
- sessionDone(mindName, session) {
1599
- const [baseName] = mindName.split("@", 2);
854
+ async sessionDone(mindName, session) {
855
+ const baseName = await getBaseName(mindName);
1600
856
  if (session) {
1601
857
  this.decrementActive(baseName, session);
1602
858
  } else {
@@ -1614,7 +870,7 @@ var DeliveryManager = class {
1614
870
  async restoreFromDb() {
1615
871
  try {
1616
872
  const db = await getDb();
1617
- const rows = await db.select().from(deliveryQueue).where(eq3(deliveryQueue.status, "pending"));
873
+ const rows = await db.select().from(deliveryQueue).where(eq2(deliveryQueue.status, "pending"));
1618
874
  for (const row of rows) {
1619
875
  let payload;
1620
876
  try {
@@ -1632,7 +888,7 @@ var DeliveryManager = class {
1632
888
  this.addToBatchBuffer(row.mind, row.session, payload, sessionConfig);
1633
889
  } else {
1634
890
  try {
1635
- await db.delete(deliveryQueue).where(eq3(deliveryQueue.id, row.id));
891
+ await db.delete(deliveryQueue).where(eq2(deliveryQueue.id, row.id));
1636
892
  } catch (err) {
1637
893
  dlog2.warn(`failed to delete queue row ${row.id} for ${row.mind}`, logger_default.errorData(err));
1638
894
  }
@@ -1653,7 +909,7 @@ var DeliveryManager = class {
1653
909
  */
1654
910
  async getPending(mindName) {
1655
911
  const db = await getDb();
1656
- const rows = await db.select().from(deliveryQueue).where(and3(eq3(deliveryQueue.mind, mindName), eq3(deliveryQueue.status, "gated")));
912
+ const rows = await db.select().from(deliveryQueue).where(and2(eq2(deliveryQueue.mind, mindName), eq2(deliveryQueue.status, "gated")));
1657
913
  const byChannel = /* @__PURE__ */ new Map();
1658
914
  for (const row of rows) {
1659
915
  const ch = row.channel ?? "unknown";
@@ -1691,18 +947,13 @@ var DeliveryManager = class {
1691
947
  }
1692
948
  this.batchBuffers.clear();
1693
949
  this.sessionStates.clear();
1694
- if (instance3 === this) instance3 = void 0;
950
+ if (instance2 === this) instance2 = void 0;
1695
951
  }
1696
952
  // --- Private ---
1697
- resolvePort(mindName) {
1698
- const [baseName, variantName] = mindName.split("@", 2);
1699
- const entry = findMind(baseName);
953
+ async resolvePort(mindName) {
954
+ const entry = await findMind(mindName);
1700
955
  if (!entry) return null;
1701
- if (variantName) {
1702
- const variant = findVariant(baseName, variantName);
1703
- if (!variant) return null;
1704
- return { baseName, port: variant.port };
1705
- }
956
+ const baseName = entry.parent ?? mindName;
1706
957
  return { baseName, port: entry.port };
1707
958
  }
1708
959
  async postToMind(port, body) {
@@ -1728,7 +979,7 @@ var DeliveryManager = class {
1728
979
  }
1729
980
  }
1730
981
  async deliverToMind(mindName, session, payload, sessionConfig) {
1731
- const resolved = this.resolvePort(mindName);
982
+ const resolved = await this.resolvePort(mindName);
1732
983
  if (!resolved) {
1733
984
  dlog2.warn(`cannot deliver to ${mindName}: mind not found`);
1734
985
  return;
@@ -1765,16 +1016,16 @@ var DeliveryManager = class {
1765
1016
  publishTypingForChannels(typingMap.deleteSender(baseName), typingMap);
1766
1017
  }
1767
1018
  }
1768
- async deliverBatchToMind(mindName, session, messages2, sessionConfig, interruptOverride) {
1769
- const resolved = this.resolvePort(mindName);
1019
+ async deliverBatchToMind(mindName, session, messages, sessionConfig, interruptOverride) {
1020
+ const resolved = await this.resolvePort(mindName);
1770
1021
  if (!resolved) {
1771
1022
  dlog2.warn(`cannot deliver batch to ${mindName}: mind not found`);
1772
1023
  return;
1773
1024
  }
1774
1025
  const { baseName, port } = resolved;
1775
1026
  const enrichedMessages = await Promise.all(
1776
- messages2.map(async (msg, i) => {
1777
- const isFirst = messages2.findIndex((m) => m.channel === msg.channel) === i;
1027
+ messages.map(async (msg, i) => {
1028
+ const isFirst = messages.findIndex((m) => m.channel === msg.channel) === i;
1778
1029
  if (!isFirst) return msg;
1779
1030
  const enrichedPayload = await this.enrichWithProfiles(baseName, session, msg.payload);
1780
1031
  return { ...msg, payload: enrichedPayload };
@@ -1788,7 +1039,7 @@ var DeliveryManager = class {
1788
1039
  }
1789
1040
  const senders = /* @__PURE__ */ new Set();
1790
1041
  const channelSet = /* @__PURE__ */ new Set();
1791
- for (const msg of messages2) {
1042
+ for (const msg of messages) {
1792
1043
  if (msg.sender) senders.add(msg.sender);
1793
1044
  if (msg.channel) channelSet.add(msg.channel);
1794
1045
  }
@@ -1798,7 +1049,7 @@ var DeliveryManager = class {
1798
1049
  if (ch !== "unknown") typingMap.set(ch, baseName, { persistent: true });
1799
1050
  }
1800
1051
  const seenConvIds = /* @__PURE__ */ new Set();
1801
- for (const msg of messages2) {
1052
+ for (const msg of messages) {
1802
1053
  if (msg.payload.conversationId && !seenConvIds.has(msg.payload.conversationId)) {
1803
1054
  seenConvIds.add(msg.payload.conversationId);
1804
1055
  typingMap.set(`volute:${msg.payload.conversationId}`, baseName, { persistent: true });
@@ -1819,10 +1070,10 @@ var DeliveryManager = class {
1819
1070
  try {
1820
1071
  const db = await getDb();
1821
1072
  await db.delete(deliveryQueue).where(
1822
- and3(
1823
- eq3(deliveryQueue.mind, baseName),
1824
- eq3(deliveryQueue.session, session),
1825
- eq3(deliveryQueue.status, "pending")
1073
+ and2(
1074
+ eq2(deliveryQueue.mind, baseName),
1075
+ eq2(deliveryQueue.session, session),
1076
+ eq2(deliveryQueue.status, "pending")
1826
1077
  )
1827
1078
  );
1828
1079
  } catch (err) {
@@ -1838,13 +1089,13 @@ var DeliveryManager = class {
1838
1089
  publishTypingForChannels(typingMap.deleteSender(baseName), typingMap);
1839
1090
  }
1840
1091
  }
1841
- enqueueBatch(mindName, session, payload, sessionConfig) {
1092
+ async enqueueBatch(mindName, session, payload, sessionConfig) {
1842
1093
  const delivery = sessionConfig.delivery;
1843
1094
  if (delivery.triggers?.length) {
1844
1095
  const text = extractTextContent(payload.content);
1845
1096
  const lower = text.toLowerCase();
1846
1097
  if (delivery.triggers.some((t) => lower.includes(t.toLowerCase()))) {
1847
- this.flushBatch(mindName, session, [
1098
+ await this.flushBatch(mindName, session, [
1848
1099
  {
1849
1100
  payload,
1850
1101
  channel: payload.channel,
@@ -1855,14 +1106,14 @@ var DeliveryManager = class {
1855
1106
  return;
1856
1107
  }
1857
1108
  }
1858
- const [baseName] = mindName.split("@", 2);
1109
+ const baseName = await getBaseName(mindName);
1859
1110
  const state = this.sessionStates.get(baseName)?.get(session);
1860
1111
  if (state && state.activeCount > 0 && payload.sender && !state.lastDeliverySenders.has(payload.sender) && payload.channel && state.lastDeliveryChannels.has(payload.channel) && Date.now() - state.lastDeliveredAt < delivery.maxWait * 1e3 && Date.now() - state.lastInterruptAt > delivery.debounce * 1e3) {
1861
1112
  state.lastInterruptAt = Date.now();
1862
1113
  this.persistToQueue(mindName, session, payload).catch((err) => {
1863
1114
  dlog2.warn(`failed to persist batch message for ${mindName}/${session}`, logger_default.errorData(err));
1864
1115
  });
1865
- this.flushBatch(
1116
+ await this.flushBatch(
1866
1117
  mindName,
1867
1118
  session,
1868
1119
  [{ payload, channel: payload.channel, sender: payload.sender, createdAt: Date.now() }],
@@ -1917,42 +1168,42 @@ var DeliveryManager = class {
1917
1168
  buffer.maxWaitTimer.unref();
1918
1169
  }
1919
1170
  }
1920
- flushBatch(mindName, session, extra, interruptOverride) {
1171
+ async flushBatch(mindName, session, extra, interruptOverride) {
1921
1172
  const bufferKey = `${mindName}:${session}`;
1922
1173
  const buffer = this.batchBuffers.get(bufferKey);
1923
- const messages2 = [];
1174
+ const messages = [];
1924
1175
  if (buffer) {
1925
1176
  if (buffer.debounceTimer) clearTimeout(buffer.debounceTimer);
1926
1177
  if (buffer.maxWaitTimer) clearTimeout(buffer.maxWaitTimer);
1927
1178
  buffer.debounceTimer = null;
1928
1179
  buffer.maxWaitTimer = null;
1929
- messages2.push(...buffer.messages.splice(0));
1180
+ messages.push(...buffer.messages.splice(0));
1930
1181
  this.batchBuffers.delete(bufferKey);
1931
1182
  }
1932
- if (extra) messages2.push(...extra);
1933
- if (messages2.length === 0) return;
1934
- const [baseName] = mindName.split("@", 2);
1183
+ if (extra) messages.push(...extra);
1184
+ if (messages.length === 0) return;
1185
+ const baseName = await getBaseName(mindName);
1935
1186
  const config = getRoutingConfig(baseName);
1936
1187
  const sessionConfig = resolveDeliveryMode(config, session);
1937
1188
  dlog2.info(
1938
- `flushing batch for ${mindName}/${session}: ${messages2.length} messages${interruptOverride ? " (new-speaker interrupt)" : ""}`
1189
+ `flushing batch for ${mindName}/${session}: ${messages.length} messages${interruptOverride ? " (new-speaker interrupt)" : ""}`
1939
1190
  );
1940
- this.deliverBatchToMind(mindName, session, messages2, sessionConfig, interruptOverride).catch(
1191
+ this.deliverBatchToMind(mindName, session, messages, sessionConfig, interruptOverride).catch(
1941
1192
  (err) => {
1942
1193
  dlog2.warn(`failed to flush batch for ${mindName}/${session}`, logger_default.errorData(err));
1943
1194
  }
1944
1195
  );
1945
1196
  }
1946
1197
  async gateMessage(mindName, session, payload) {
1947
- const [baseName] = mindName.split("@", 2);
1198
+ const baseName = await getBaseName(mindName);
1948
1199
  await this.persistToQueue(baseName, session, payload, "gated");
1949
1200
  try {
1950
1201
  const db = await getDb();
1951
- const count2 = await db.select({ count: sql2`count(*)` }).from(deliveryQueue).where(
1952
- and3(
1953
- eq3(deliveryQueue.mind, baseName),
1954
- eq3(deliveryQueue.channel, payload.channel),
1955
- eq3(deliveryQueue.status, "gated")
1202
+ const count2 = await db.select({ count: sql`count(*)` }).from(deliveryQueue).where(
1203
+ and2(
1204
+ eq2(deliveryQueue.mind, baseName),
1205
+ eq2(deliveryQueue.channel, payload.channel),
1206
+ eq2(deliveryQueue.status, "gated")
1956
1207
  )
1957
1208
  );
1958
1209
  if ((count2[0]?.count ?? 0) <= 1) {
@@ -1982,7 +1233,7 @@ var DeliveryManager = class {
1982
1233
  sender: "system",
1983
1234
  content: [{ type: "text", text: notification }]
1984
1235
  };
1985
- const config = getRoutingConfig(mindName.split("@", 2)[0]);
1236
+ const config = getRoutingConfig(await getBaseName(mindName));
1986
1237
  const sessionConfig = resolveDeliveryMode(config, "main");
1987
1238
  await this.deliverToMind(mindName, "main", invitePayload, {
1988
1239
  ...sessionConfig,
@@ -2126,17 +1377,17 @@ var DeliveryManager = class {
2126
1377
  }
2127
1378
  }
2128
1379
  };
2129
- var instance3;
1380
+ var instance2;
2130
1381
  function initDeliveryManager() {
2131
- if (instance3) throw new Error("DeliveryManager already initialized");
2132
- instance3 = new DeliveryManager();
2133
- return instance3;
1382
+ if (instance2) throw new Error("DeliveryManager already initialized");
1383
+ instance2 = new DeliveryManager();
1384
+ return instance2;
2134
1385
  }
2135
1386
  function getDeliveryManager() {
2136
- if (!instance3) {
1387
+ if (!instance2) {
2137
1388
  throw new Error("DeliveryManager not initialized \u2014 call initDeliveryManager() first");
2138
1389
  }
2139
- return instance3;
1390
+ return instance2;
2140
1391
  }
2141
1392
 
2142
1393
  // src/lib/delivery/message-delivery.ts
@@ -2163,8 +1414,8 @@ async function recordInbound(mind, channel, sender, content) {
2163
1414
  }
2164
1415
  async function deliverMessage(mindName, payload) {
2165
1416
  try {
2166
- const [baseName] = mindName.split("@", 2);
2167
- const entry = findMind(baseName);
1417
+ const baseName = await getBaseName(mindName);
1418
+ const entry = await findMind(baseName);
2168
1419
  if (!entry) {
2169
1420
  dlog3.warn(`cannot deliver to ${mindName}: mind not found`);
2170
1421
  return;
@@ -2188,6 +1439,60 @@ async function deliverMessage(mindName, payload) {
2188
1439
  }
2189
1440
  }
2190
1441
 
1442
+ // src/lib/system-channel.ts
1443
+ var SYSTEM_CHANNEL_NAME = "system";
1444
+ var cachedChannelId = null;
1445
+ async function ensureSystemChannel() {
1446
+ if (cachedChannelId) return cachedChannelId;
1447
+ const existing = await getChannelByName(SYSTEM_CHANNEL_NAME);
1448
+ if (existing) {
1449
+ cachedChannelId = existing.id;
1450
+ return existing.id;
1451
+ }
1452
+ const conv = await createChannel(SYSTEM_CHANNEL_NAME);
1453
+ cachedChannelId = conv.id;
1454
+ logger_default.info("created #system channel");
1455
+ return conv.id;
1456
+ }
1457
+ async function joinSystemChannel(userId) {
1458
+ const channelId = await ensureSystemChannel();
1459
+ await joinChannel(channelId, userId);
1460
+ }
1461
+ async function joinSystemChannelForMind(mindName) {
1462
+ const user = await getOrCreateMindUser(mindName);
1463
+ await joinSystemChannel(user.id);
1464
+ }
1465
+ async function announceToSystem(text) {
1466
+ const channelId = await ensureSystemChannel();
1467
+ await addMessage(channelId, "system", "system", [{ type: "text", text }]);
1468
+ const participants = await getParticipants(channelId);
1469
+ const mindParticipants = participants.filter((p) => p.userType === "mind");
1470
+ const channel = "volute:#system";
1471
+ for (const mind of mindParticipants) {
1472
+ try {
1473
+ writeChannelEntry(mind.username, channel, {
1474
+ platformId: channelId,
1475
+ platform: "volute",
1476
+ name: SYSTEM_CHANNEL_NAME,
1477
+ type: "group"
1478
+ });
1479
+ } catch (err) {
1480
+ logger_default.warn(`failed to write channel entry for ${mind.username}`, logger_default.errorData(err));
1481
+ }
1482
+ deliverMessage(mind.username, {
1483
+ content: [{ type: "text", text }],
1484
+ channel,
1485
+ conversationId: channelId,
1486
+ sender: "system",
1487
+ participants: participants.map((p) => p.username),
1488
+ participantCount: participants.length,
1489
+ isDM: false
1490
+ }).catch((err) => {
1491
+ logger_default.warn(`failed to deliver system announcement to ${mind.username}`, logger_default.errorData(err));
1492
+ });
1493
+ }
1494
+ }
1495
+
2191
1496
  // src/lib/daemon/mail-poller.ts
2192
1497
  var mlog = logger_default.child("mail");
2193
1498
  function formatEmailContent(email) {
@@ -2374,7 +1679,7 @@ var MailPoller = class {
2374
1679
  await this.deliver(mind, { ...email, mind });
2375
1680
  }
2376
1681
  async deliver(mind, email) {
2377
- const entry = findMind(mind);
1682
+ const entry = await findMind(mind);
2378
1683
  if (!entry || !entry.running) {
2379
1684
  mlog.warn(`skipping delivery to ${mind}: ${!entry ? "not found" : "not running"}`);
2380
1685
  return;
@@ -2394,15 +1699,11 @@ var MailPoller = class {
2394
1699
  }
2395
1700
  }
2396
1701
  };
2397
- var instance4 = null;
1702
+ var instance3 = null;
2398
1703
  function initMailPoller() {
2399
- if (instance4) throw new Error("MailPoller already initialized");
2400
- instance4 = new MailPoller();
2401
- return instance4;
2402
- }
2403
- function getMailPoller() {
2404
- if (!instance4) throw new Error("MailPoller not initialized \u2014 call initMailPoller() first");
2405
- return instance4;
1704
+ if (instance3) throw new Error("MailPoller already initialized");
1705
+ instance3 = new MailPoller();
1706
+ return instance3;
2406
1707
  }
2407
1708
  async function ensureMailAddress(mindName) {
2408
1709
  const config = readSystemsConfig();
@@ -2428,14 +1729,14 @@ async function ensureMailAddress(mindName) {
2428
1729
  // src/lib/daemon/scheduler.ts
2429
1730
  import { resolve as resolve6 } from "path";
2430
1731
  import { CronExpressionParser } from "cron-parser";
2431
- var slog2 = logger_default.child("scheduler");
1732
+ var slog = logger_default.child("scheduler");
2432
1733
  var Scheduler = class {
2433
1734
  schedules = /* @__PURE__ */ new Map();
2434
1735
  interval = null;
2435
1736
  lastFired = /* @__PURE__ */ new Map();
2436
1737
  // "mind:scheduleId" → epoch minute
2437
1738
  get statePath() {
2438
- return resolve6(voluteHome(), "scheduler-state.json");
1739
+ return resolve6(voluteSystemDir(), "scheduler-state.json");
2439
1740
  }
2440
1741
  start() {
2441
1742
  this.loadState();
@@ -2494,7 +1795,7 @@ var Scheduler = class {
2494
1795
  prevMinute = Math.floor(prev.getTime() / 6e4);
2495
1796
  cronCache.set(schedule.cron, prevMinute);
2496
1797
  } catch (err) {
2497
- slog2.warn(`invalid cron "${schedule.cron}" for ${mind}:${schedule.id}`, logger_default.errorData(err));
1798
+ slog.warn(`invalid cron "${schedule.cron}" for ${mind}:${schedule.id}`, logger_default.errorData(err));
2498
1799
  return false;
2499
1800
  }
2500
1801
  }
@@ -2509,11 +1810,11 @@ var Scheduler = class {
2509
1810
  const sleepState = sleepManager?.getState(mindName);
2510
1811
  if (sleepState?.sleeping) {
2511
1812
  if (schedule.skipWhenSleeping) {
2512
- slog2.info(`skipped "${schedule.id}" for ${mindName} (sleeping)`);
1813
+ slog.info(`skipped "${schedule.id}" for ${mindName} (sleeping)`);
2513
1814
  return;
2514
1815
  }
2515
1816
  if (sleepState.wokenByTrigger) {
2516
- slog2.info(`skipped "${schedule.id}" for ${mindName} (trigger-woken)`);
1817
+ slog.info(`skipped "${schedule.id}" for ${mindName} (trigger-woken)`);
2517
1818
  return;
2518
1819
  }
2519
1820
  }
@@ -2524,7 +1825,7 @@ var Scheduler = class {
2524
1825
  try {
2525
1826
  const output = await this.runScript(schedule.script, homeDir, mindName);
2526
1827
  if (!output.trim()) {
2527
- slog2.info(`fired script "${schedule.id}" for ${mindName} (no output)`);
1828
+ slog.info(`fired script "${schedule.id}" for ${mindName} (no output)`);
2528
1829
  return;
2529
1830
  }
2530
1831
  text = output;
@@ -2532,12 +1833,12 @@ var Scheduler = class {
2532
1833
  const stderr = err.stderr ?? "";
2533
1834
  text = `[script error] ${err.message}${stderr ? `
2534
1835
  ${stderr}` : ""}`;
2535
- slog2.warn(`script "${schedule.id}" failed for ${mindName}`, logger_default.errorData(err));
1836
+ slog.warn(`script "${schedule.id}" failed for ${mindName}`, logger_default.errorData(err));
2536
1837
  }
2537
1838
  } else if (schedule.message) {
2538
1839
  text = schedule.message;
2539
1840
  } else {
2540
- slog2.warn(`schedule "${schedule.id}" for ${mindName} has no message or script`);
1841
+ slog.warn(`schedule "${schedule.id}" for ${mindName} has no message or script`);
2541
1842
  return;
2542
1843
  }
2543
1844
  await this.deliver(mindName, {
@@ -2545,9 +1846,9 @@ ${stderr}` : ""}`;
2545
1846
  channel: schedule.channel ?? "system:scheduler",
2546
1847
  sender: schedule.id
2547
1848
  });
2548
- slog2.info(`fired "${schedule.id}" for ${mindName}`);
1849
+ slog.info(`fired "${schedule.id}" for ${mindName}`);
2549
1850
  } catch (err) {
2550
- slog2.warn(`failed to fire "${schedule.id}" for ${mindName}`, logger_default.errorData(err));
1851
+ slog.warn(`failed to fire "${schedule.id}" for ${mindName}`, logger_default.errorData(err));
2551
1852
  }
2552
1853
  }
2553
1854
  runScript(script, cwd, mindName) {
@@ -2557,19 +1858,19 @@ ${stderr}` : ""}`;
2557
1858
  return deliverMessage(mindName, payload);
2558
1859
  }
2559
1860
  };
2560
- var instance5 = null;
1861
+ var instance4 = null;
2561
1862
  function initScheduler() {
2562
- if (instance5) throw new Error("Scheduler already initialized");
2563
- instance5 = new Scheduler();
2564
- return instance5;
1863
+ if (instance4) throw new Error("Scheduler already initialized");
1864
+ instance4 = new Scheduler();
1865
+ return instance4;
2565
1866
  }
2566
1867
  function getScheduler() {
2567
- if (!instance5) throw new Error("Scheduler not initialized \u2014 call initScheduler() first");
2568
- return instance5;
1868
+ if (!instance4) throw new Error("Scheduler not initialized \u2014 call initScheduler() first");
1869
+ return instance4;
2569
1870
  }
2570
1871
 
2571
1872
  // src/lib/daemon/token-budget.ts
2572
- import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
1873
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
2573
1874
  import { resolve as resolve7 } from "path";
2574
1875
  var tlog = logger_default.child("token-budget");
2575
1876
  var DEFAULT_BUDGET_PERIOD_MINUTES = 60;
@@ -2644,9 +1945,9 @@ var TokenBudget = class {
2644
1945
  drain(mind) {
2645
1946
  const state = this.budgets.get(mind);
2646
1947
  if (!state) return [];
2647
- const messages2 = state.queue;
1948
+ const messages = state.queue;
2648
1949
  state.queue = [];
2649
- return messages2;
1950
+ return messages;
2650
1951
  }
2651
1952
  getUsage(mind) {
2652
1953
  const state = this.budgets.get(mind);
@@ -2693,14 +1994,14 @@ var TokenBudget = class {
2693
1994
  saveBudgetState(mind, state) {
2694
1995
  try {
2695
1996
  const dir = stateDir(mind);
2696
- mkdirSync2(dir, { recursive: true });
1997
+ mkdirSync3(dir, { recursive: true });
2697
1998
  const data = {
2698
1999
  periodStart: state.periodStart,
2699
2000
  tokensUsed: state.tokensUsed,
2700
2001
  warningInjected: state.warningInjected,
2701
2002
  queue: state.queue
2702
2003
  };
2703
- writeFileSync2(this.budgetStatePath(mind), `${JSON.stringify(data)}
2004
+ writeFileSync3(this.budgetStatePath(mind), `${JSON.stringify(data)}
2704
2005
  `);
2705
2006
  } catch (err) {
2706
2007
  tlog.warn(`failed to save budget state for ${mind}`, logger_default.errorData(err));
@@ -2727,8 +2028,8 @@ var TokenBudget = class {
2727
2028
  return null;
2728
2029
  }
2729
2030
  }
2730
- async replay(mindName, messages2) {
2731
- const summary = messages2.map((m) => {
2031
+ async replay(mindName, messages) {
2032
+ const summary = messages.map((m) => {
2732
2033
  const from = m.sender ? `[${m.sender}]` : "";
2733
2034
  const ch = m.channel ? `(${m.channel})` : "";
2734
2035
  return `${from}${ch} ${m.textContent}`;
@@ -2738,7 +2039,7 @@ var TokenBudget = class {
2738
2039
  content: [
2739
2040
  {
2740
2041
  type: "text",
2741
- text: `[Budget replay] ${messages2.length} queued message(s) from the previous budget period:
2042
+ text: `[Budget replay] ${messages.length} queued message(s) from the previous budget period:
2742
2043
 
2743
2044
  ${summary}`
2744
2045
  }
@@ -2746,40 +2047,38 @@ ${summary}`
2746
2047
  channel: "system:budget-replay",
2747
2048
  sender: "system"
2748
2049
  });
2749
- tlog.info(`replayed ${messages2.length} queued message(s) for ${mindName}`);
2050
+ tlog.info(`replayed ${messages.length} queued message(s) for ${mindName}`);
2750
2051
  } catch (err) {
2751
2052
  tlog.warn(`failed to replay for ${mindName}`, logger_default.errorData(err));
2752
2053
  const state = this.budgets.get(mindName);
2753
- if (state) state.queue.push(...messages2);
2054
+ if (state) state.queue.push(...messages);
2754
2055
  }
2755
2056
  }
2756
2057
  };
2757
- var instance6 = null;
2058
+ var instance5 = null;
2758
2059
  function initTokenBudget() {
2759
- if (instance6) throw new Error("TokenBudget already initialized");
2760
- instance6 = new TokenBudget();
2761
- return instance6;
2060
+ if (instance5) throw new Error("TokenBudget already initialized");
2061
+ instance5 = new TokenBudget();
2062
+ return instance5;
2762
2063
  }
2763
2064
  function getTokenBudget() {
2764
- if (!instance6) throw new Error("TokenBudget not initialized \u2014 call initTokenBudget() first");
2765
- return instance6;
2065
+ if (!instance5) throw new Error("TokenBudget not initialized \u2014 call initTokenBudget() first");
2066
+ return instance5;
2766
2067
  }
2767
2068
 
2768
2069
  // src/lib/daemon/mind-service.ts
2769
2070
  async function startMindFull(name) {
2770
- const [baseName, variantName] = name.split("@", 2);
2071
+ const entry = await findMind(name);
2072
+ const baseName = entry?.parent ?? name;
2771
2073
  await getMindManager().startMind(name);
2772
2074
  publish({
2773
2075
  type: "mind_started",
2774
2076
  mind: name,
2775
2077
  summary: `${name} started`
2776
2078
  }).catch((err) => logger_default.error("failed to publish mind_started activity", logger_default.errorData(err)));
2777
- if (variantName) return;
2778
- const entry = findMind(baseName);
2079
+ if (entry?.parent) return;
2779
2080
  if (!entry || entry.stage === "seed") return;
2780
2081
  const dir = mindDir(baseName);
2781
- const daemonPort = process.env.VOLUTE_DAEMON_PORT ? parseInt(process.env.VOLUTE_DAEMON_PORT, 10) : void 0;
2782
- await getConnectorManager().startConnectors(baseName, dir, entry.port, daemonPort);
2783
2082
  getScheduler().loadSchedules(baseName);
2784
2083
  ensureMailAddress(baseName).catch(
2785
2084
  (err) => logger_default.error(`failed to ensure mail address for ${baseName}`, logger_default.errorData(err))
@@ -2820,11 +2119,11 @@ async function wakeMind(name) {
2820
2119
  }).catch((err) => logger_default.error("failed to publish mind_waking activity", logger_default.errorData(err)));
2821
2120
  }
2822
2121
  async function stopMindFull(name) {
2823
- const [baseName, variantName] = name.split("@", 2);
2824
- if (!variantName) {
2122
+ const baseName = await getBaseName(name);
2123
+ const isBase = baseName === name;
2124
+ if (isBase) {
2825
2125
  stopWatcher(baseName);
2826
2126
  markIdle(baseName);
2827
- await getConnectorManager().stopConnectors(baseName);
2828
2127
  getScheduler().unloadSchedules(baseName);
2829
2128
  getTokenBudget().removeBudget(baseName);
2830
2129
  }
@@ -2837,7 +2136,7 @@ async function stopMindFull(name) {
2837
2136
  }
2838
2137
 
2839
2138
  // src/lib/daemon/sleep-manager.ts
2840
- var slog3 = logger_default.child("sleep");
2139
+ var slog2 = logger_default.child("sleep");
2841
2140
  function defaultState() {
2842
2141
  return {
2843
2142
  sleeping: false,
@@ -2874,7 +2173,7 @@ var SleepManager = class {
2874
2173
  unsubActivity = null;
2875
2174
  transitioning = /* @__PURE__ */ new Set();
2876
2175
  get statePath() {
2877
- return resolve8(voluteHome(), "sleep-state.json");
2176
+ return resolve8(voluteSystemDir(), "sleep-state.json");
2878
2177
  }
2879
2178
  start() {
2880
2179
  this.loadState();
@@ -2898,7 +2197,7 @@ var SleepManager = class {
2898
2197
  }
2899
2198
  }
2900
2199
  } catch (err) {
2901
- slog3.warn("failed to load sleep state", logger_default.errorData(err));
2200
+ slog2.warn("failed to load sleep state", logger_default.errorData(err));
2902
2201
  }
2903
2202
  }
2904
2203
  saveState() {
@@ -2907,10 +2206,10 @@ var SleepManager = class {
2907
2206
  if (state.sleeping) data[name] = state;
2908
2207
  }
2909
2208
  try {
2910
- writeFileSync3(this.statePath, `${JSON.stringify(data, null, 2)}
2209
+ writeFileSync4(this.statePath, `${JSON.stringify(data, null, 2)}
2911
2210
  `);
2912
2211
  } catch (err) {
2913
- slog3.error("failed to save sleep state", logger_default.errorData(err));
2212
+ slog2.error("failed to save sleep state", logger_default.errorData(err));
2914
2213
  }
2915
2214
  }
2916
2215
  // --- Public API ---
@@ -2931,7 +2230,7 @@ var SleepManager = class {
2931
2230
  const state = this.states.get(name);
2932
2231
  if (!state?.sleeping || !state.wokenByTrigger) return;
2933
2232
  this.markAwake(name);
2934
- slog3.info(`${name} trigger-wake converted to full wake`);
2233
+ slog2.info(`${name} trigger-wake converted to full wake`);
2935
2234
  }
2936
2235
  getSleepConfig(name) {
2937
2236
  const dir = mindDir(name);
@@ -2952,7 +2251,7 @@ var SleepManager = class {
2952
2251
  this.markSleeping(name, opts);
2953
2252
  return;
2954
2253
  }
2955
- const entry = findMind(name);
2254
+ const entry = await findMind(name);
2956
2255
  if (!entry) return;
2957
2256
  const sleepConfig = this.getSleepConfig(name);
2958
2257
  const wakeTime = opts?.voluntaryWakeAt ?? this.getNextWakeTime(sleepConfig) ?? "scheduled time";
@@ -2967,7 +2266,7 @@ var SleepManager = class {
2967
2266
  content: preSleepMsg
2968
2267
  });
2969
2268
  } catch (err) {
2970
- slog3.error(`failed to persist pre-sleep message for ${name}`, logger_default.errorData(err));
2269
+ slog2.error(`failed to persist pre-sleep message for ${name}`, logger_default.errorData(err));
2971
2270
  }
2972
2271
  try {
2973
2272
  await fetch(`http://127.0.0.1:${entry.port}/message`, {
@@ -2979,7 +2278,7 @@ var SleepManager = class {
2979
2278
  })
2980
2279
  });
2981
2280
  } catch (err) {
2982
- slog3.warn(`failed to send pre-sleep message to ${name}`, logger_default.errorData(err));
2281
+ slog2.warn(`failed to send pre-sleep message to ${name}`, logger_default.errorData(err));
2983
2282
  }
2984
2283
  await this.waitForIdle(name, 12e4);
2985
2284
  await new Promise((r) => setTimeout(r, 3e3));
@@ -2987,7 +2286,7 @@ var SleepManager = class {
2987
2286
  await this.killOrphanOnPort(entry.port);
2988
2287
  await this.archiveSessions(name);
2989
2288
  this.markSleeping(name, opts);
2990
- slog3.info(`${name} is now sleeping`);
2289
+ slog2.info(`${name} is now sleeping`);
2991
2290
  } finally {
2992
2291
  this.transitioning.delete(name);
2993
2292
  }
@@ -3004,10 +2303,10 @@ var SleepManager = class {
3004
2303
  try {
3005
2304
  await wakeMind(name);
3006
2305
  } catch (err) {
3007
- slog3.error(`failed to wake ${name}`, logger_default.errorData(err));
2306
+ slog2.error(`failed to wake ${name}`, logger_default.errorData(err));
3008
2307
  return;
3009
2308
  }
3010
- const entry = findMind(name);
2309
+ const entry = await findMind(name);
3011
2310
  if (!entry) return;
3012
2311
  if (opts?.trigger) {
3013
2312
  state.wokenByTrigger = true;
@@ -3048,7 +2347,7 @@ var SleepManager = class {
3048
2347
  content: summaryText
3049
2348
  });
3050
2349
  } catch (err) {
3051
- slog3.error(`failed to persist wake summary for ${name}`, logger_default.errorData(err));
2350
+ slog2.error(`failed to persist wake summary for ${name}`, logger_default.errorData(err));
3052
2351
  }
3053
2352
  try {
3054
2353
  await fetch(`http://127.0.0.1:${entry.port}/message`, {
@@ -3060,17 +2359,17 @@ var SleepManager = class {
3060
2359
  })
3061
2360
  });
3062
2361
  } catch (err) {
3063
- slog3.warn(`failed to deliver wake summary to ${name}`, logger_default.errorData(err));
2362
+ slog2.warn(`failed to deliver wake summary to ${name}`, logger_default.errorData(err));
3064
2363
  }
3065
2364
  }
3066
2365
  const flushed = await this.flushQueuedMessages(name);
3067
2366
  if (flushed > 0) {
3068
- slog3.info(`flushed ${flushed} queued message(s) for ${name}`);
2367
+ slog2.info(`flushed ${flushed} queued message(s) for ${name}`);
3069
2368
  }
3070
2369
  if (!opts?.trigger) {
3071
2370
  this.markAwake(name);
3072
2371
  }
3073
- slog3.info(`${name} is now awake${opts?.trigger ? " (trigger wake)" : ""}`);
2372
+ slog2.info(`${name} is now awake${opts?.trigger ? " (trigger wake)" : ""}`);
3074
2373
  } finally {
3075
2374
  this.transitioning.delete(name);
3076
2375
  }
@@ -3125,20 +2424,20 @@ var SleepManager = class {
3125
2424
  async flushQueuedMessages(name) {
3126
2425
  try {
3127
2426
  const db = await getDb();
3128
- const rows = await db.select().from(deliveryQueue).where(and4(eq4(deliveryQueue.mind, name), eq4(deliveryQueue.status, "sleep-queued"))).all();
2427
+ const rows = await db.select().from(deliveryQueue).where(and3(eq3(deliveryQueue.mind, name), eq3(deliveryQueue.status, "sleep-queued"))).all();
3129
2428
  if (rows.length === 0) return 0;
3130
- const { deliverMessage: deliverMessage2 } = await import("./message-delivery-MS5JYPZX.js");
2429
+ const { deliverMessage: deliverMessage2 } = await import("./message-delivery-LDXLGERA.js");
3131
2430
  const delivered = [];
3132
2431
  for (const row of rows) {
3133
2432
  try {
3134
2433
  await deliverMessage2(name, JSON.parse(row.payload));
3135
2434
  delivered.push(row.id);
3136
2435
  } catch (err) {
3137
- slog3.warn(`failed to flush queued message ${row.id} for ${name}`, logger_default.errorData(err));
2436
+ slog2.warn(`failed to flush queued message ${row.id} for ${name}`, logger_default.errorData(err));
3138
2437
  }
3139
2438
  }
3140
2439
  if (delivered.length > 0) {
3141
- await db.delete(deliveryQueue).where(inArray3(deliveryQueue.id, delivered));
2440
+ await db.delete(deliveryQueue).where(inArray2(deliveryQueue.id, delivered));
3142
2441
  }
3143
2442
  const state = this.states.get(name);
3144
2443
  if (state) {
@@ -3146,7 +2445,7 @@ var SleepManager = class {
3146
2445
  }
3147
2446
  return delivered.length;
3148
2447
  } catch (err) {
3149
- slog3.warn(`failed to flush queued messages for ${name}`, logger_default.errorData(err));
2448
+ slog2.warn(`failed to flush queued messages for ${name}`, logger_default.errorData(err));
3150
2449
  return 0;
3151
2450
  }
3152
2451
  }
@@ -3175,14 +2474,14 @@ var SleepManager = class {
3175
2474
  const interval = CronExpressionParser2.parse(config.schedule.wake);
3176
2475
  return interval.next().toDate().toISOString();
3177
2476
  } catch (err) {
3178
- slog3.warn(`invalid wake cron "${config.schedule.wake}"`, logger_default.errorData(err));
2477
+ slog2.warn(`invalid wake cron "${config.schedule.wake}"`, logger_default.errorData(err));
3179
2478
  return null;
3180
2479
  }
3181
2480
  }
3182
- tick() {
2481
+ async tick() {
3183
2482
  const now = /* @__PURE__ */ new Date();
3184
2483
  const epochMinute = Math.floor(now.getTime() / 6e4);
3185
- const registry = readRegistry();
2484
+ const registry = await readRegistry();
3186
2485
  for (const entry of registry) {
3187
2486
  if (!entry.running && !this.isSleeping(entry.name)) continue;
3188
2487
  const config = this.getSleepConfig(entry.name);
@@ -3192,7 +2491,7 @@ var SleepManager = class {
3192
2491
  const wakeAt = new Date(state.voluntaryWakeAt);
3193
2492
  if (now >= wakeAt) {
3194
2493
  this.initiateWake(entry.name).catch(
3195
- (err) => slog3.error(`failed voluntary wake for ${entry.name}`, logger_default.errorData(err))
2494
+ (err) => slog2.error(`failed voluntary wake for ${entry.name}`, logger_default.errorData(err))
3196
2495
  );
3197
2496
  continue;
3198
2497
  }
@@ -3201,7 +2500,7 @@ var SleepManager = class {
3201
2500
  const wakeAt = new Date(state.scheduledWakeAt);
3202
2501
  if (now >= wakeAt) {
3203
2502
  this.initiateWake(entry.name).catch(
3204
- (err) => slog3.error(`failed scheduled wake for ${entry.name}`, logger_default.errorData(err))
2503
+ (err) => slog2.error(`failed scheduled wake for ${entry.name}`, logger_default.errorData(err))
3205
2504
  );
3206
2505
  continue;
3207
2506
  }
@@ -3209,7 +2508,7 @@ var SleepManager = class {
3209
2508
  if (!state?.sleeping && entry.running) {
3210
2509
  if (this.shouldSleep(config.schedule.sleep, epochMinute)) {
3211
2510
  this.initiateSleep(entry.name).catch(
3212
- (err) => slog3.error(`failed to initiate sleep for ${entry.name}`, logger_default.errorData(err))
2511
+ (err) => slog2.error(`failed to initiate sleep for ${entry.name}`, logger_default.errorData(err))
3213
2512
  );
3214
2513
  }
3215
2514
  }
@@ -3222,7 +2521,7 @@ var SleepManager = class {
3222
2521
  const prevMinute = Math.floor(prev.getTime() / 6e4);
3223
2522
  return prevMinute === epochMinute;
3224
2523
  } catch (err) {
3225
- slog3.warn(`invalid sleep cron "${cronExpr}"`, logger_default.errorData(err));
2524
+ slog2.warn(`invalid sleep cron "${cronExpr}"`, logger_default.errorData(err));
3226
2525
  return false;
3227
2526
  }
3228
2527
  }
@@ -3248,7 +2547,7 @@ var SleepManager = class {
3248
2547
  const sessionsDir = resolve8(dir, ".mind", "sessions");
3249
2548
  if (existsSync5(sessionsDir)) {
3250
2549
  const archiveDir = resolve8(sessionsDir, "archive");
3251
- mkdirSync3(archiveDir, { recursive: true });
2550
+ mkdirSync4(archiveDir, { recursive: true });
3252
2551
  for (const file of readdirSync2(sessionsDir)) {
3253
2552
  if (file === "archive" || !file.endsWith(".json")) continue;
3254
2553
  const src = resolve8(sessionsDir, file);
@@ -3257,14 +2556,14 @@ var SleepManager = class {
3257
2556
  try {
3258
2557
  renameSync(src, dest);
3259
2558
  } catch (err) {
3260
- slog3.warn(`failed to archive session ${file} for ${name}`, logger_default.errorData(err));
2559
+ slog2.warn(`failed to archive session ${file} for ${name}`, logger_default.errorData(err));
3261
2560
  }
3262
2561
  }
3263
2562
  }
3264
2563
  const piSessionsDir = resolve8(dir, ".mind", "pi-sessions");
3265
2564
  if (existsSync5(piSessionsDir)) {
3266
2565
  const archiveDir = resolve8(piSessionsDir, "archive");
3267
- mkdirSync3(archiveDir, { recursive: true });
2566
+ mkdirSync4(archiveDir, { recursive: true });
3268
2567
  for (const entry of readdirSync2(piSessionsDir, { withFileTypes: true })) {
3269
2568
  if (entry.name === "archive" || !entry.isDirectory()) continue;
3270
2569
  const src = resolve8(piSessionsDir, entry.name);
@@ -3272,7 +2571,7 @@ var SleepManager = class {
3272
2571
  try {
3273
2572
  renameSync(src, dest);
3274
2573
  } catch (err) {
3275
- slog3.warn(`failed to archive pi-session ${entry.name} for ${name}`, logger_default.errorData(err));
2574
+ slog2.warn(`failed to archive pi-session ${entry.name} for ${name}`, logger_default.errorData(err));
3276
2575
  }
3277
2576
  }
3278
2577
  }
@@ -3315,7 +2614,7 @@ var SleepManager = class {
3315
2614
  });
3316
2615
  return result.trim();
3317
2616
  } catch (err) {
3318
- slog3.warn(`wake-context script failed for ${name}`, logger_default.errorData(err));
2617
+ slog2.warn(`wake-context script failed for ${name}`, logger_default.errorData(err));
3319
2618
  return "";
3320
2619
  }
3321
2620
  }
@@ -3329,7 +2628,7 @@ var SleepManager = class {
3329
2628
  async buildQueuedSummary(name) {
3330
2629
  try {
3331
2630
  const db = await getDb();
3332
- const rows = await db.select({ channel: deliveryQueue.channel, sender: deliveryQueue.sender }).from(deliveryQueue).where(and4(eq4(deliveryQueue.mind, name), eq4(deliveryQueue.status, "sleep-queued"))).all();
2631
+ const rows = await db.select({ channel: deliveryQueue.channel, sender: deliveryQueue.sender }).from(deliveryQueue).where(and3(eq3(deliveryQueue.mind, name), eq3(deliveryQueue.status, "sleep-queued"))).all();
3333
2632
  if (rows.length === 0) return "No messages arrived while you slept.";
3334
2633
  const channelCounts = /* @__PURE__ */ new Map();
3335
2634
  const senders = /* @__PURE__ */ new Set();
@@ -3342,7 +2641,7 @@ var SleepManager = class {
3342
2641
  const senderNote = senders.size > 0 ? ` from ${[...senders].join(", ")}` : "";
3343
2642
  return `${rows.length} message${rows.length === 1 ? "" : "s"} arrived while you slept${senderNote} (${parts.join(", ")}). They'll be delivered to your normal channels now.`;
3344
2643
  } catch (err) {
3345
- slog3.error(`failed to build queued summary for ${name}`, logger_default.errorData(err));
2644
+ slog2.error(`failed to build queued summary for ${name}`, logger_default.errorData(err));
3346
2645
  return "Unable to check for queued messages \u2014 there may be messages waiting.";
3347
2646
  }
3348
2647
  }
@@ -3357,7 +2656,7 @@ var SleepManager = class {
3357
2656
  } catch {
3358
2657
  return;
3359
2658
  }
3360
- slog3.warn(`orphan process found on port ${port} after sleep, killing`);
2659
+ slog2.warn(`orphan process found on port ${port} after sleep, killing`);
3361
2660
  const execFileAsync = promisify(execFile);
3362
2661
  try {
3363
2662
  const { stdout } = await execFileAsync("lsof", ["-ti", `:${port}`, "-sTCP:LISTEN"]);
@@ -3368,7 +2667,7 @@ var SleepManager = class {
3368
2667
  process.kill(pid, "SIGTERM");
3369
2668
  } catch (err) {
3370
2669
  if (err.code !== "ESRCH") {
3371
- slog3.warn(`failed to kill orphan pid ${pid}`, logger_default.errorData(err));
2670
+ slog2.warn(`failed to kill orphan pid ${pid}`, logger_default.errorData(err));
3372
2671
  }
3373
2672
  }
3374
2673
  }
@@ -3400,7 +2699,7 @@ var SleepManager = class {
3400
2699
  }
3401
2700
  }
3402
2701
  } catch (err) {
3403
- slog3.warn(`failed to kill orphan on port ${port} via /proc`, logger_default.errorData(err));
2702
+ slog2.warn(`failed to kill orphan on port ${port} via /proc`, logger_default.errorData(err));
3404
2703
  }
3405
2704
  }
3406
2705
  await new Promise((r) => setTimeout(r, 1e3));
@@ -3410,7 +2709,7 @@ var SleepManager = class {
3410
2709
  if (!state?.sleeping || !state.wokenByTrigger) return;
3411
2710
  if (this.transitioning.has(event.mind)) return;
3412
2711
  if (event.type === "mind_idle") {
3413
- slog3.info(`${event.mind} going back to sleep after trigger wake`);
2712
+ slog2.info(`${event.mind} going back to sleep after trigger wake`);
3414
2713
  state.wokenByTrigger = false;
3415
2714
  this.transitioning.add(event.mind);
3416
2715
  sleepMind(event.mind).then(() => this.archiveSessions(event.mind)).then(() => {
@@ -3419,27 +2718,27 @@ var SleepManager = class {
3419
2718
  const sleepConfig = this.getSleepConfig(event.mind);
3420
2719
  state.scheduledWakeAt = this.getNextWakeTime(sleepConfig);
3421
2720
  this.saveState();
3422
- slog3.info(`${event.mind} returned to sleep`);
2721
+ slog2.info(`${event.mind} returned to sleep`);
3423
2722
  }).catch((err) => {
3424
- slog3.error(`failed to return ${event.mind} to sleep`, logger_default.errorData(err));
2723
+ slog2.error(`failed to return ${event.mind} to sleep`, logger_default.errorData(err));
3425
2724
  }).finally(() => {
3426
2725
  this.transitioning.delete(event.mind);
3427
2726
  });
3428
2727
  }
3429
2728
  }
3430
2729
  };
3431
- var instance7 = null;
2730
+ var instance6 = null;
3432
2731
  function initSleepManager() {
3433
- if (instance7) throw new Error("SleepManager already initialized");
3434
- instance7 = new SleepManager();
3435
- return instance7;
2732
+ if (instance6) throw new Error("SleepManager already initialized");
2733
+ instance6 = new SleepManager();
2734
+ return instance6;
3436
2735
  }
3437
2736
  function getSleepManager() {
3438
- if (!instance7) throw new Error("SleepManager not initialized \u2014 call initSleepManager() first");
3439
- return instance7;
2737
+ if (!instance6) throw new Error("SleepManager not initialized \u2014 call initSleepManager() first");
2738
+ return instance6;
3440
2739
  }
3441
2740
  function getSleepManagerIfReady() {
3442
- return instance7;
2741
+ return instance6;
3443
2742
  }
3444
2743
 
3445
2744
  export {
@@ -3459,36 +2758,14 @@ export {
3459
2758
  deleteUser,
3460
2759
  updateUserProfile,
3461
2760
  migrateMindRoles,
3462
- initConnectorManager,
3463
- getConnectorManager,
2761
+ readVoluteConfig,
2762
+ writeVoluteConfig,
3464
2763
  stopAllWatchers,
3465
2764
  getCachedSites,
3466
2765
  getCachedRecentPages,
3467
- getWebhookUrl,
3468
- getAuthHeaders,
3469
- fireWebhook,
3470
- initWebhook,
3471
- subscribe2 as subscribe,
3472
- publish2 as publish,
3473
- createConversation,
3474
- getConversation,
3475
- getParticipants,
3476
- isParticipant,
3477
- listConversationsForUser,
3478
- isParticipantOrOwner,
3479
- deleteConversationForUser,
3480
- addMessage,
3481
- getMessages,
3482
- getMessagesPaginated,
3483
- listConversationsWithParticipants,
3484
- findDMConversation,
3485
- createChannel,
3486
- getChannelByName,
3487
- listChannels,
3488
- joinChannel,
3489
- leaveChannel,
3490
- getUnreadCounts,
3491
- markConversationRead,
2766
+ splitMessage,
2767
+ writeChannelEntry,
2768
+ resolveChannelId,
3492
2769
  ensureSystemChannel,
3493
2770
  joinSystemChannel,
3494
2771
  announceToSystem,
@@ -3503,8 +2780,8 @@ export {
3503
2780
  initSleepManager,
3504
2781
  getSleepManager,
3505
2782
  getSleepManagerIfReady,
3506
- subscribe3 as subscribe2,
3507
- publish3 as publish2,
2783
+ subscribe2 as subscribe,
2784
+ publish3 as publish,
3508
2785
  getTypingMap,
3509
2786
  publishTypingForChannels,
3510
2787
  extractTextContent,
@@ -3512,6 +2789,5 @@ export {
3512
2789
  getDeliveryManager,
3513
2790
  recordInbound,
3514
2791
  deliverMessage,
3515
- initMailPoller,
3516
- getMailPoller
2792
+ initMailPoller
3517
2793
  };