volute 0.45.2 → 0.46.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 (62) hide show
  1. package/README.md +3 -3
  2. package/dist/{activity-events-BUR2RYTJ.js → activity-events-NF6VWTJQ.js} +1 -1
  3. package/dist/{auth-FLAEXXZK.js → auth-CCFAU4GQ.js} +2 -2
  4. package/dist/{chat-76MRWWQX.js → chat-ZIQQS25U.js} +1 -1
  5. package/dist/chunk-4YU7HF4L.js +237 -0
  6. package/dist/{chunk-OP4JG2BS.js → chunk-5ZQZDDIK.js} +6 -2
  7. package/dist/{chunk-FR6S2KNN.js → chunk-BJZ4PV7R.js} +1 -1
  8. package/dist/{chunk-NYC5U6K7.js → chunk-E3DA4HYV.js} +4 -4
  9. package/dist/{chunk-YT7PEESJ.js → chunk-JO5IO4FH.js} +245 -124
  10. package/dist/{chunk-7ZUB63PS.js → chunk-KGIXX4NM.js} +1 -1
  11. package/dist/{chunk-EJVPTVTI.js → chunk-KQSFTPIM.js} +1 -1
  12. package/dist/{chunk-T6HYVHPG.js → chunk-OV7ZQ2GH.js} +1 -1
  13. package/dist/{clock-K43IMROX.js → chunk-PJ45FQPT.js} +21 -16
  14. package/dist/{chunk-UYLGEDVD.js → chunk-YXSUMLTC.js} +44 -267
  15. package/dist/{chunk-7C44R625.js → chunk-ZNMCCHCP.js} +47 -1
  16. package/dist/cli.js +11 -11
  17. package/dist/clock-RHS2JEDL.js +16 -0
  18. package/dist/{cloud-sync-3OJOPZRT.js → cloud-sync-46RS4SDA.js} +9 -8
  19. package/dist/{conversations-JZTD6KLR.js → conversations-CJOOCGNW.js} +2 -2
  20. package/dist/{daemon-restart-TIHKVVVA.js → daemon-restart-VFEI57UO.js} +1 -1
  21. package/dist/daemon.js +249 -170
  22. package/dist/{delete-BFMIGAIB.js → delete-HPLC7ETU.js} +1 -1
  23. package/dist/{delivery-manager-VDVKVA46.js → delivery-manager-JINZRG2W.js} +9 -8
  24. package/dist/{echo-text-6JJOPUD4.js → echo-text-VQPB5L2X.js} +10 -9
  25. package/dist/{export-GGIVO76O.js → export-LY723I3Z.js} +3 -3
  26. package/dist/{extensions-JONTW2HF.js → extensions-ZELQLTSR.js} +9 -8
  27. package/dist/{message-delivery-3C65ZI6L.js → message-delivery-R7JSQGYX.js} +11 -8
  28. package/dist/{mind-DWB6XYCH.js → mind-LHKS55DI.js} +9 -9
  29. package/dist/{mind-activity-tracker-POBLU3LC.js → mind-activity-tracker-3QRNJVR4.js} +2 -2
  30. package/dist/{mind-service-3F4I46CI.js → mind-service-NB2JMWQZ.js} +11 -8
  31. package/dist/mind-sleep-74TGOQBC.js +83 -0
  32. package/dist/{package-A7KXT67Q.js → package-DOD6CPO6.js} +2 -1
  33. package/dist/{scheduler-P6DXLLXI.js → scheduler-WVKII3LA.js} +9 -8
  34. package/dist/{seed-HCP5NYKH.js → seed-CNYIHPOH.js} +1 -1
  35. package/dist/{seed-cmd-ILOGNIFB.js → seed-cmd-AVNUNLZV.js} +2 -2
  36. package/dist/{seed-create-OURS2LS4.js → seed-create-ANXGYSCC.js} +3 -3
  37. package/dist/{seed-sprout-ELDFDAN6.js → seed-sprout-NIUFTX32.js} +5 -3
  38. package/dist/{send-AAIOPJAV.js → send-SPE6DVNJ.js} +6 -0
  39. package/dist/{setup-DQM5VPY4.js → setup-QT2A25SY.js} +1 -1
  40. package/dist/skills/orientation/SKILL.md +1 -1
  41. package/dist/skills/volute-mind/SKILL.md +8 -8
  42. package/dist/skills/volute-mind/references/sleep.md +8 -1
  43. package/dist/{skills-BKINFL7W.js → skills-AKCFA7BN.js} +1 -1
  44. package/dist/{sleep-manager-HCUH4PM4.js → sleep-manager-G6TXSDP7.js} +9 -8
  45. package/dist/{spirit-DBTIW7P4.js → spirit-RPBLLUQ5.js} +2 -2
  46. package/dist/{sprout-W3KC7UGK.js → sprout-SMX42ZBP.js} +1 -1
  47. package/dist/{system-chat-XOALE2RD.js → system-chat-NDAW6YP7.js} +9 -8
  48. package/dist/{turn-tracker-2W6Q2NSR.js → turn-tracker-FOIBNUUA.js} +2 -1
  49. package/dist/{up-FTK7CTO3.js → up-PS6PFS75.js} +1 -1
  50. package/dist/{version-notify-3XMAIQTE.js → version-notify-C5CH7CVP.js} +15 -10
  51. package/dist/web-assets/assets/{index-Bc2m8gRC.js → index-CU6XWICM.js} +3 -3
  52. package/dist/web-assets/index.html +1 -1
  53. package/package.json +2 -1
  54. package/templates/_base/home/VOLUTE.md +6 -2
  55. package/templates/_base/src/lib/context-breakdown.ts +57 -67
  56. package/templates/_base/src/lib/volute-server.ts +2 -2
  57. package/templates/claude/src/agent.ts +78 -42
  58. package/templates/claude/src/lib/session-reaper.ts +31 -0
  59. package/templates/codex/src/agent.ts +62 -40
  60. package/templates/pi/src/agent.ts +55 -38
  61. package/dist/mind-sleep-YMBR3I35.js +0 -47
  62. /package/dist/{chunk-QNQMSWZE.js → chunk-K6T43YBD.js} +0 -0
package/dist/daemon.js CHANGED
@@ -42,7 +42,7 @@ import {
42
42
  fixModelEscapes,
43
43
  getPlatformDriver,
44
44
  routeOutboundBridge
45
- } from "./chunk-NYC5U6K7.js";
45
+ } from "./chunk-E3DA4HYV.js";
46
46
  import {
47
47
  getBridgeConfig,
48
48
  readBridgesConfig,
@@ -113,7 +113,7 @@ import {
113
113
  syncProviderToMinds,
114
114
  uninstallNpmExtension,
115
115
  writeSystemsConfig
116
- } from "./chunk-YT7PEESJ.js";
116
+ } from "./chunk-JO5IO4FH.js";
117
117
  import {
118
118
  loadMergedEnv,
119
119
  mindEnvPath,
@@ -121,7 +121,7 @@ import {
121
121
  sharedEnvPath,
122
122
  writeEnv
123
123
  } from "./chunk-ZDZISKCL.js";
124
- import "./chunk-FR6S2KNN.js";
124
+ import "./chunk-BJZ4PV7R.js";
125
125
  import {
126
126
  applyInitFiles,
127
127
  applyTemplateHomeFiles,
@@ -150,7 +150,7 @@ import {
150
150
  syncBuiltinSkills,
151
151
  uninstallSkill,
152
152
  updateSkill
153
- } from "./chunk-7ZUB63PS.js";
153
+ } from "./chunk-KGIXX4NM.js";
154
154
  import {
155
155
  addMessage,
156
156
  createChannel,
@@ -178,11 +178,11 @@ import {
178
178
  listConversationsForUser,
179
179
  listConversationsWithParticipants,
180
180
  markConversationRead,
181
- publish,
181
+ publish as publish2,
182
182
  setConversationPrivate,
183
183
  subscribe as subscribe2,
184
184
  updateChannelSettings
185
- } from "./chunk-OP4JG2BS.js";
185
+ } from "./chunk-5ZQZDDIK.js";
186
186
  import {
187
187
  readVoluteConfig,
188
188
  writeVoluteConfig
@@ -195,33 +195,36 @@ import {
195
195
  getActiveMinds,
196
196
  onMindEvent,
197
197
  stopAll
198
- } from "./chunk-EJVPTVTI.js";
198
+ } from "./chunk-KQSFTPIM.js";
199
199
  import {
200
200
  extractTextContent
201
201
  } from "./chunk-4JOQZPCE.js";
202
202
  import {
203
- PROMPT_DEFAULTS,
204
- PROMPT_KEYS,
205
203
  assignSession,
206
204
  completeOrphanedTurns,
207
205
  completeTurn,
208
206
  createTurn,
209
207
  getActiveTurnId,
210
- getMindPromptDefaults,
211
- getPrompt,
212
- getPromptIfCustom,
213
208
  getToolUseEventId,
214
209
  initSummarizer,
215
210
  markErrored,
216
- publish as publish2,
211
+ publish as publish3,
217
212
  subscribe as subscribe3,
218
213
  subscribeAll,
219
- substitute,
220
214
  summarizeOrphanedTurns,
221
215
  summarizeTurn,
222
216
  takeErrored,
223
217
  trackToolUse
224
- } from "./chunk-UYLGEDVD.js";
218
+ } from "./chunk-YXSUMLTC.js";
219
+ import {
220
+ ORIENTATION_MARKER,
221
+ PROMPT_DEFAULTS,
222
+ PROMPT_KEYS,
223
+ getMindPromptDefaults,
224
+ getPrompt,
225
+ getPromptIfCustom,
226
+ substitute
227
+ } from "./chunk-4YU7HF4L.js";
225
228
  import "./chunk-TQJ6ZYVY.js";
226
229
  import {
227
230
  chownMindDir,
@@ -279,11 +282,14 @@ import {
279
282
  syncMindProfile,
280
283
  updateUserProfile,
281
284
  verifyUser
282
- } from "./chunk-T6HYVHPG.js";
285
+ } from "./chunk-OV7ZQ2GH.js";
283
286
  import {
284
287
  broadcast,
288
+ getEventsSince,
289
+ nextEventId,
290
+ publish,
285
291
  subscribe
286
- } from "./chunk-7C44R625.js";
292
+ } from "./chunk-ZNMCCHCP.js";
287
293
  import {
288
294
  logBuffer,
289
295
  logger_default
@@ -292,6 +298,7 @@ import {
292
298
  buildVoluteSlug
293
299
  } from "./chunk-LGNUFVMR.js";
294
300
  import {
301
+ SPIRIT_NAME,
295
302
  addMind,
296
303
  addVariant,
297
304
  daemonLoopback,
@@ -341,9 +348,12 @@ import { format } from "util";
341
348
  import { CronExpressionParser } from "cron-parser";
342
349
  var blog = logger_default.child("backup");
343
350
  var DEFAULT_BACKUP_SCHEDULE = "0 3 * * *";
351
+ var NOTIFY_EVERY_N_FAILURES = 5;
344
352
  var BackupManager = class {
345
353
  interval = null;
346
- lastFiredMinute = -1;
354
+ /** Epoch-ms of the most recent scheduled fire this process has serviced. */
355
+ lastServicedFireMs = -1;
356
+ consecutiveFailures = 0;
347
357
  warnedUnconfigured = false;
348
358
  start() {
349
359
  this.interval = setInterval(() => {
@@ -355,9 +365,16 @@ var BackupManager = class {
355
365
  this.interval = null;
356
366
  }
357
367
  /**
358
- * True at most once per scheduled fire minute (records the minute it
359
- * consumed), and only when backups are enabled and runnable. Not a pure
360
- * predicate — a second call in the same minute returns false.
368
+ * True at most once per scheduled fire (records the fire it consumed), and
369
+ * only when backups are enabled and runnable. Not a pure predicate — a second
370
+ * call for the same fire returns false.
371
+ *
372
+ * Catch-up: if the machine was asleep or the daemon was down across a
373
+ * scheduled fire (a laptop at 3am), the fire is picked up on the next tick so
374
+ * long as it falls after the last successful run (`lastRun` in
375
+ * backup-state.json). Without an anchor (no successful backup yet) only the
376
+ * exact on-time minute fires, so freshly enabling backups doesn't trigger an
377
+ * immediate run.
361
378
  */
362
379
  isDue(now = /* @__PURE__ */ new Date()) {
363
380
  const config2 = readGlobalConfig().backup;
@@ -372,27 +389,81 @@ var BackupManager = class {
372
389
  this.warnedUnconfigured = false;
373
390
  const cron = config2.schedule || DEFAULT_BACKUP_SCHEDULE;
374
391
  const epochMinute = Math.floor(now.getTime() / 6e4);
375
- if (epochMinute === this.lastFiredMinute) return false;
376
- let prevMinute;
392
+ let prevFireMs;
377
393
  try {
378
394
  const interval = CronExpressionParser.parse(cron, { currentDate: now });
379
- prevMinute = Math.floor(interval.prev().toDate().getTime() / 6e4);
395
+ prevFireMs = interval.prev().toDate().getTime();
380
396
  } catch (err) {
381
397
  blog.warn(`invalid backup cron "${cron}"`, logger_default.errorData(err));
382
398
  return false;
383
399
  }
384
- if (prevMinute !== epochMinute) return false;
385
- this.lastFiredMinute = epochMinute;
400
+ if (prevFireMs <= this.lastServicedFireMs) return false;
401
+ const lastRunMs = this.readLastRunMs();
402
+ if (lastRunMs !== null && prevFireMs <= lastRunMs) return false;
403
+ const onTime = Math.floor(prevFireMs / 6e4) === epochMinute;
404
+ if (!onTime) {
405
+ if (lastRunMs === null) return false;
406
+ blog.warn(
407
+ `missed scheduled backup fire at ${new Date(prevFireMs).toISOString()}; running catch-up`
408
+ );
409
+ }
410
+ this.lastServicedFireMs = prevFireMs;
386
411
  return true;
387
412
  }
413
+ readLastRunMs() {
414
+ const lastRun = readBackupState().lastRun;
415
+ if (!lastRun) return null;
416
+ const ms = Date.parse(lastRun);
417
+ return Number.isNaN(ms) ? null : ms;
418
+ }
388
419
  async tick() {
389
420
  if (!this.isDue()) return;
390
421
  blog.info("scheduled backup starting");
391
422
  try {
392
423
  await runBackup();
424
+ this.recordSuccess();
393
425
  } catch (err) {
394
426
  blog.error("scheduled backup failed", logger_default.errorData(err));
427
+ await this.recordFailure(err);
428
+ }
429
+ }
430
+ recordSuccess() {
431
+ this.consecutiveFailures = 0;
432
+ }
433
+ /**
434
+ * Track consecutive scheduled-backup failures and actively notify the operator
435
+ * on the 1st and then every Nth. Returns whether it notified (for tests).
436
+ * Notification failures are swallowed — surfacing a backup failure must never
437
+ * take the daemon's backup loop down with it.
438
+ */
439
+ async recordFailure(err) {
440
+ this.consecutiveFailures += 1;
441
+ const n = this.consecutiveFailures;
442
+ if (n !== 1 && n % NOTIFY_EVERY_N_FAILURES !== 0) return false;
443
+ const reason = err instanceof Error ? err.message : String(err);
444
+ const summary = n === 1 ? `Scheduled backup failed: ${reason}` : `Scheduled backup has failed ${n} times in a row: ${reason}`;
445
+ try {
446
+ await publish({
447
+ type: "backup_failed",
448
+ mind: SPIRIT_NAME,
449
+ summary,
450
+ metadata: { consecutiveFailures: n, error: reason }
451
+ });
452
+ } catch (pubErr) {
453
+ blog.error("failed to publish backup-failure activity", logger_default.errorData(pubErr));
454
+ }
455
+ try {
456
+ const { sendSystemMessage } = await import("./system-chat-NDAW6YP7.js");
457
+ await sendSystemMessage(
458
+ SPIRIT_NAME,
459
+ `${summary}
460
+
461
+ Backups are not being saved. Check \`volute backup status\` and the Backups settings tab.`
462
+ );
463
+ } catch (msgErr) {
464
+ blog.error("failed to send backup-failure system message", logger_default.errorData(msgErr));
395
465
  }
466
+ return true;
396
467
  }
397
468
  };
398
469
  var backupManager = null;
@@ -728,6 +799,26 @@ async function cleanExpiredLogs() {
728
799
  await db.delete(mindHistory).where(and(eq(mindHistory.type, "log"), lt(mindHistory.created_at, cutoff)));
729
800
  }
730
801
 
802
+ // packages/daemon/src/lib/daemon/maintenance.ts
803
+ var MAINTENANCE_INTERVAL_MS = 60 * 60 * 1e3;
804
+ async function runMaintenance() {
805
+ try {
806
+ await cleanExpiredSessions();
807
+ } catch (err) {
808
+ logger_default.warn("maintenance: failed to clean expired sessions", logger_default.errorData(err));
809
+ }
810
+ try {
811
+ await cleanExpiredLogs();
812
+ } catch (err) {
813
+ logger_default.warn("maintenance: failed to clean expired logs", logger_default.errorData(err));
814
+ }
815
+ }
816
+ function startMaintenanceInterval(intervalMs = MAINTENANCE_INTERVAL_MS, task = runMaintenance) {
817
+ const handle = setInterval(() => void task(), intervalMs);
818
+ handle.unref?.();
819
+ return handle;
820
+ }
821
+
731
822
  // packages/daemon/src/web/api/system.ts
732
823
  import { getOAuthProvider, getOAuthProviders } from "@earendil-works/pi-ai/oauth";
733
824
  import { getBuiltinProviders } from "@earendil-works/pi-ai/providers/all";
@@ -1696,17 +1787,17 @@ var authenticated = new Hono3().use(authMiddleware).post("/change-password", zVa
1696
1787
  if (!AVATAR_MIME[ext]) {
1697
1788
  return c.json({ error: "Invalid file type (png, jpg, gif, webp only)" }, 400);
1698
1789
  }
1699
- let buffer2 = Buffer.from(await file.arrayBuffer());
1790
+ let buffer = Buffer.from(await file.arrayBuffer());
1700
1791
  let finalExt = ext;
1701
- const normalized = await normalizeAvatar(buffer2);
1792
+ const normalized = await normalizeAvatar(buffer);
1702
1793
  if (normalized) {
1703
- buffer2 = normalized.buffer;
1794
+ buffer = normalized.buffer;
1704
1795
  finalExt = normalized.ext;
1705
1796
  }
1706
1797
  const dir = avatarsDir();
1707
1798
  mkdirSync2(dir, { recursive: true });
1708
1799
  const filename = `avatar-${user.id}${finalExt}`;
1709
- writeFileSync2(resolve3(dir, filename), buffer2);
1800
+ writeFileSync2(resolve3(dir, filename), buffer);
1710
1801
  if (user.avatar && user.avatar !== filename) {
1711
1802
  const oldPath = resolve3(dir, user.avatar);
1712
1803
  rmSync(oldPath, { force: true });
@@ -2168,7 +2259,7 @@ var app4 = new Hono5().post("/:platform/inbound", zValidator4("json", inboundSch
2168
2259
  }
2169
2260
  const participants = await getParticipants(channel.id);
2170
2261
  if (!participants.some((p) => p.userId === puppet.id)) {
2171
- const { addParticipant } = await import("./conversations-JZTD6KLR.js");
2262
+ const { addParticipant } = await import("./conversations-CJOOCGNW.js");
2172
2263
  await addParticipant(channel.id, puppet.id);
2173
2264
  }
2174
2265
  const contentBlocks = body.content;
@@ -2452,7 +2543,7 @@ async function notifyMind(mindName, message) {
2452
2543
  const entry = await findMind(mindName);
2453
2544
  if (!entry) return;
2454
2545
  try {
2455
- const { sendSystemMessage } = await import("./system-chat-XOALE2RD.js");
2546
+ const { sendSystemMessage } = await import("./system-chat-NDAW6YP7.js");
2456
2547
  await sendSystemMessage(mindName, message);
2457
2548
  } catch (err) {
2458
2549
  logger_default.warn(`[file-sharing] notify mind ${mindName} failed`, logger_default.errorData(err));
@@ -2621,11 +2712,11 @@ var app9 = new Hono11().post("/:name/avatar", requireSelf(), async (c) => {
2621
2712
  if (!AVATAR_MIME2[ext]) {
2622
2713
  return c.json({ error: "Invalid file type (png, jpg, gif, webp only)" }, 400);
2623
2714
  }
2624
- let buffer2 = Buffer.from(await file.arrayBuffer());
2715
+ let buffer = Buffer.from(await file.arrayBuffer());
2625
2716
  let finalExt = ext;
2626
- const normalized = await normalizeAvatar(buffer2);
2717
+ const normalized = await normalizeAvatar(buffer);
2627
2718
  if (normalized) {
2628
- buffer2 = normalized.buffer;
2719
+ buffer = normalized.buffer;
2629
2720
  finalExt = normalized.ext;
2630
2721
  }
2631
2722
  const dir = entry.dir ?? mindDir(name);
@@ -2639,7 +2730,7 @@ var app9 = new Hono11().post("/:name/avatar", requireSelf(), async (c) => {
2639
2730
  if (oldAvatarPath) rmSync2(oldAvatarPath, { force: true });
2640
2731
  }
2641
2732
  mkdirSync3(homeDir, { recursive: true });
2642
- writeFileSync3(avatarPath, buffer2);
2733
+ writeFileSync3(avatarPath, buffer);
2643
2734
  const profile = config2.profile ?? {};
2644
2735
  profile.avatar = filename;
2645
2736
  config2.profile = profile;
@@ -3468,7 +3559,7 @@ async function handleMindEvent(mind, event) {
3468
3559
  if (!turnId) {
3469
3560
  llog.warn(`skipping turn tracking for ${mind}: createTurn failed`);
3470
3561
  } else {
3471
- publish2(mind, { mind, type: "turn_created", turnId });
3562
+ publish3(mind, { mind, type: "turn_created", turnId });
3472
3563
  if (event.session) await assignSession(mind, turnId, event.session);
3473
3564
  try {
3474
3565
  await linkPendingInbound(mind, turnId, event.channel);
@@ -3514,7 +3605,7 @@ async function handleMindEvent(mind, event) {
3514
3605
  llog.error("failed to link tool_result to turn", logger_default.errorData(err));
3515
3606
  }
3516
3607
  }
3517
- publish2(mind, {
3608
+ publish3(mind, {
3518
3609
  mind,
3519
3610
  type: event.type,
3520
3611
  session: event.session,
@@ -3936,7 +4027,7 @@ async function getMindStatus(name, port, registryRunning) {
3936
4027
  const manager = getMindManager();
3937
4028
  let status = "stopped";
3938
4029
  try {
3939
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-HCUH4PM4.js");
4030
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-G6TXSDP7.js");
3940
4031
  if (getSleepManagerIfReady2()?.isSleeping(name)) {
3941
4032
  status = "sleeping";
3942
4033
  }
@@ -4513,7 +4604,7 @@ var app13 = new Hono16().post("/", requireAdminOrSystem, zValidator6("json", cre
4513
4604
  }
4514
4605
  if (body.stage === "seed") {
4515
4606
  const descLine = body.description ? `
4516
- The human who planted you described you as: "${body.description}"
4607
+ Your creator described you as: "${body.description}"
4517
4608
  ` : "";
4518
4609
  const seedSoulRaw = body.seedSoul ?? await getPrompt("seed_soul", { name, description: descLine });
4519
4610
  const seedSoul = body.seedSoul ? substitute(seedSoulRaw, { name, description: descLine }) : seedSoulRaw;
@@ -4539,7 +4630,7 @@ The human who planted you described you as: "${body.description}"
4539
4630
  try {
4540
4631
  const spiritEntry = await findMind("volute");
4541
4632
  if (spiritEntry) {
4542
- const { spiritDir } = await import("./spirit-DBTIW7P4.js");
4633
+ const { spiritDir } = await import("./spirit-RPBLLUQ5.js");
4543
4634
  const sDir = spiritEntry.dir ?? spiritDir();
4544
4635
  const spiritConfig = readVoluteConfig(sDir) ?? {};
4545
4636
  const schedules = spiritConfig.schedules ?? [];
@@ -4554,7 +4645,7 @@ The human who planted you described you as: "${body.description}"
4554
4645
  });
4555
4646
  spiritConfig.schedules = schedules;
4556
4647
  writeVoluteConfig(sDir, spiritConfig);
4557
- const { getScheduler: getScheduler2 } = await import("./scheduler-P6DXLLXI.js");
4648
+ const { getScheduler: getScheduler2 } = await import("./scheduler-WVKII3LA.js");
4558
4649
  getScheduler2().loadSchedules("volute", sDir);
4559
4650
  }
4560
4651
  }
@@ -4820,7 +4911,7 @@ ${user.trimEnd()}
4820
4911
  const manager = getMindManager();
4821
4912
  try {
4822
4913
  if (context?.type === "reload") {
4823
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-HCUH4PM4.js");
4914
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-G6TXSDP7.js");
4824
4915
  const sleepState = getSleepManagerIfReady2()?.getState(name);
4825
4916
  if (sleepState?.sleeping) {
4826
4917
  logger_default.info(`skipping reload for ${name} during sleep \u2014 will apply on next wake`);
@@ -4947,7 +5038,7 @@ ${user.trimEnd()}
4947
5038
  const name = c.req.param("name");
4948
5039
  const entry = await findMind(name);
4949
5040
  if (!entry) return c.json({ error: "Mind not found" }, 404);
4950
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-HCUH4PM4.js");
5041
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-G6TXSDP7.js");
4951
5042
  const sm = getSleepManagerIfReady2();
4952
5043
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
4953
5044
  return c.json(sm.getState(name));
@@ -4955,7 +5046,7 @@ ${user.trimEnd()}
4955
5046
  const name = c.req.param("name");
4956
5047
  const entry = await findMind(name);
4957
5048
  if (!entry) return c.json({ error: "Mind not found" }, 404);
4958
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-HCUH4PM4.js");
5049
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-G6TXSDP7.js");
4959
5050
  const sm = getSleepManagerIfReady2();
4960
5051
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
4961
5052
  if (sm.isSleeping(name)) return c.json({ error: "Mind is already sleeping" }, 409);
@@ -4975,7 +5066,7 @@ ${user.trimEnd()}
4975
5066
  const name = c.req.param("name");
4976
5067
  const entry = await findMind(name);
4977
5068
  if (!entry) return c.json({ error: "Mind not found" }, 404);
4978
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-HCUH4PM4.js");
5069
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-G6TXSDP7.js");
4979
5070
  const sm = getSleepManagerIfReady2();
4980
5071
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
4981
5072
  const sleepState = sm.getState(name);
@@ -4990,7 +5081,7 @@ ${user.trimEnd()}
4990
5081
  const name = c.req.param("name");
4991
5082
  const entry = await findMind(name);
4992
5083
  if (!entry) return c.json({ error: "Mind not found" }, 404);
4993
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-HCUH4PM4.js");
5084
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-G6TXSDP7.js");
4994
5085
  const sm = getSleepManagerIfReady2();
4995
5086
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
4996
5087
  const flushed = await sm.flushQueuedMessages(name);
@@ -5018,7 +5109,7 @@ ${user.trimEnd()}
5018
5109
  }
5019
5110
  config2.profile = profile;
5020
5111
  writeVoluteConfig(dir, config2);
5021
- const { syncMindProfile: syncMindProfile2 } = await import("./auth-FLAEXXZK.js");
5112
+ const { syncMindProfile: syncMindProfile2 } = await import("./auth-CCFAU4GQ.js");
5022
5113
  await syncMindProfile2(name, profile);
5023
5114
  broadcast({ type: "profile_updated", mind: name, summary: `${name} profile updated` });
5024
5115
  return c.json({ ok: true });
@@ -5028,7 +5119,6 @@ ${user.trimEnd()}
5028
5119
  if (!entry) return c.json({ error: "Mind not found" }, 404);
5029
5120
  if (entry.stage !== "seed") return c.json({ output: "" });
5030
5121
  const db = await getDb();
5031
- const ORIENTATION_MARKER = "You don't have a soul yet";
5032
5122
  const rawCreator = Number(process.env.VOLUTE_NURTURE_CREATOR_MINUTES);
5033
5123
  const creatorThreshold = Number.isNaN(rawCreator) ? 5 : rawCreator;
5034
5124
  const rawSpirit = Number(process.env.VOLUTE_NURTURE_SPIRIT_MINUTES);
@@ -5103,7 +5193,7 @@ ${user.trimEnd()}
5103
5193
  try {
5104
5194
  const spiritEntry = await findMind("volute");
5105
5195
  if (spiritEntry) {
5106
- const { spiritDir } = await import("./spirit-DBTIW7P4.js");
5196
+ const { spiritDir } = await import("./spirit-RPBLLUQ5.js");
5107
5197
  const sDir = spiritEntry.dir ?? spiritDir();
5108
5198
  const spiritConfig = readVoluteConfig(sDir);
5109
5199
  if (spiritConfig?.schedules) {
@@ -5111,7 +5201,7 @@ ${user.trimEnd()}
5111
5201
  spiritConfig.schedules = spiritConfig.schedules.filter((s) => s.id !== nurtureId);
5112
5202
  if (spiritConfig.schedules.length === 0) spiritConfig.schedules = void 0;
5113
5203
  writeVoluteConfig(sDir, spiritConfig);
5114
- const { getScheduler: getScheduler2 } = await import("./scheduler-P6DXLLXI.js");
5204
+ const { getScheduler: getScheduler2 } = await import("./scheduler-WVKII3LA.js");
5115
5205
  getScheduler2().loadSchedules("volute", sDir);
5116
5206
  }
5117
5207
  }
@@ -5546,7 +5636,7 @@ ${user.trimEnd()}
5546
5636
  }
5547
5637
  writeVoluteConfig(dir, existing);
5548
5638
  if (body.unescapeNewlines !== void 0) {
5549
- const { clearEchoTextCache } = await import("./echo-text-6JJOPUD4.js");
5639
+ const { clearEchoTextCache } = await import("./echo-text-VQPB5L2X.js");
5550
5640
  clearEchoTextCache(name);
5551
5641
  }
5552
5642
  const needsConfigJson = body.model !== void 0 || body.thinkingLevel !== void 0 || body.maxThinkingTokens !== void 0 || body.compaction !== void 0;
@@ -6000,76 +6090,93 @@ var slog2 = logger_default.child("schedules");
6000
6090
  function readSchedules(dir) {
6001
6091
  return readVoluteConfig(dir)?.schedules ?? [];
6002
6092
  }
6003
- function writeSchedules(name, dir, schedules) {
6004
- const config2 = readVoluteConfig(dir) ?? {};
6005
- config2.schedules = schedules.length > 0 ? schedules : void 0;
6006
- writeVoluteConfig(dir, config2);
6007
- getScheduler().loadSchedules(name, dir);
6008
- getSleepManagerIfReady()?.invalidateSleepConfig(name);
6009
- fireWebhook({
6010
- event: "schedule_changed",
6011
- mind: name,
6012
- data: { schedules }
6013
- });
6014
- }
6015
- var app15 = new Hono18().get("/:name/clock/status", requireSelf(), async (c) => {
6016
- const name = c.req.param("name");
6017
- const entry = await findMind(name);
6018
- if (!entry) return c.json({ error: "Mind not found" }, 404);
6019
- const dir = entry.dir ?? mindDir(name);
6020
- const sleepManager = getSleepManagerIfReady();
6021
- const sleepState = sleepManager?.getState(name) ?? null;
6022
- const sleepConfig = sleepManager?.getSleepConfig(name) ?? null;
6023
- const schedules = readSchedules(dir);
6024
- const now = /* @__PURE__ */ new Date();
6093
+ var UPCOMING_HORIZON_MS = 24 * 60 * 60 * 1e3;
6094
+ function computeClockEvents(schedules, sleepState, sleepConfig, now) {
6025
6095
  const upcoming = [];
6026
6096
  const previous = [];
6097
+ const effectiveWake = sleepState?.sleeping ? sleepState.scheduledWakeAt ?? sleepState.voluntaryWakeAt : null;
6098
+ const effectiveWakeDate = effectiveWake ? new Date(effectiveWake) : null;
6099
+ const sleepFate = (s, fireDate) => {
6100
+ if (!effectiveWakeDate || fireDate >= effectiveWakeDate) return {};
6101
+ const ws = s.whileSleeping ?? "queue";
6102
+ if (ws === "skip") return { willSkip: true };
6103
+ if (ws === "trigger-wake") return {};
6104
+ return { willQueue: true };
6105
+ };
6027
6106
  for (const s of schedules) {
6028
6107
  if (!s.enabled) continue;
6029
6108
  if (s.fireAt) {
6030
6109
  const fireDate = new Date(s.fireAt);
6031
6110
  if (fireDate >= now) {
6032
- upcoming.push({ id: s.id, at: fireDate.toISOString(), type: "timer" });
6111
+ upcoming.push({
6112
+ id: s.id,
6113
+ at: fireDate.toISOString(),
6114
+ type: "timer",
6115
+ ...sleepFate(s, fireDate)
6116
+ });
6033
6117
  }
6034
6118
  } else if (s.cron) {
6035
6119
  try {
6036
- const interval = CronExpressionParser3.parse(s.cron);
6037
- const next = interval.next().toDate();
6038
- upcoming.push({ id: s.id, at: next.toISOString(), type: "cron" });
6120
+ const next = CronExpressionParser3.parse(s.cron, { currentDate: now }).next().toDate();
6121
+ upcoming.push({ id: s.id, at: next.toISOString(), type: "cron", ...sleepFate(s, next) });
6039
6122
  } catch {
6040
- slog2.warn(`invalid cron "${s.cron}" for schedule "${s.id}" of ${name}`);
6123
+ slog2.warn(`invalid cron "${s.cron}" for schedule "${s.id}"`);
6041
6124
  }
6042
6125
  try {
6043
- const prevInterval = CronExpressionParser3.parse(s.cron);
6044
- const prev = prevInterval.prev().toDate();
6126
+ const prev = CronExpressionParser3.parse(s.cron, { currentDate: now }).prev().toDate();
6045
6127
  previous.push({ id: s.id, at: prev.toISOString() });
6046
6128
  } catch {
6047
6129
  }
6048
6130
  }
6049
6131
  }
6050
6132
  if (sleepState?.sleeping) {
6051
- if (sleepState.scheduledWakeAt) {
6052
- upcoming.push({ id: "sleep", at: sleepState.scheduledWakeAt, type: "cron" });
6133
+ if (effectiveWake) {
6134
+ upcoming.push({ id: "wake", at: effectiveWake, type: "cron" });
6053
6135
  }
6054
6136
  if (sleepState.sleepingSince) {
6055
6137
  previous.push({ id: "sleep", at: sleepState.sleepingSince });
6056
6138
  }
6057
6139
  } else if (sleepConfig?.enabled && sleepConfig.schedule) {
6058
6140
  try {
6059
- const sleepInterval = CronExpressionParser3.parse(sleepConfig.schedule.sleep);
6060
- const nextSleep = sleepInterval.next().toDate();
6141
+ const nextSleep = CronExpressionParser3.parse(sleepConfig.schedule.sleep, { currentDate: now }).next().toDate();
6061
6142
  upcoming.push({ id: "sleep", at: nextSleep.toISOString(), type: "cron" });
6062
6143
  } catch {
6063
6144
  }
6064
6145
  try {
6065
- const prevWakeInterval = CronExpressionParser3.parse(sleepConfig.schedule.wake);
6066
- const prevWake = prevWakeInterval.prev().toDate();
6067
- previous.push({ id: "sleep", at: prevWake.toISOString() });
6146
+ const prevWake = CronExpressionParser3.parse(sleepConfig.schedule.wake, { currentDate: now }).prev().toDate();
6147
+ previous.push({ id: "wake", at: prevWake.toISOString() });
6068
6148
  } catch {
6069
6149
  }
6070
6150
  }
6071
- upcoming.sort((a, b) => a.at.localeCompare(b.at));
6151
+ const horizon = now.getTime() + UPCOMING_HORIZON_MS;
6152
+ const withinHorizon = upcoming.filter((e) => new Date(e.at).getTime() <= horizon);
6153
+ withinHorizon.sort((a, b) => a.at.localeCompare(b.at));
6072
6154
  previous.sort((a, b) => b.at.localeCompare(a.at));
6155
+ return { upcoming: withinHorizon, previous };
6156
+ }
6157
+ function writeSchedules(name, dir, schedules) {
6158
+ const config2 = readVoluteConfig(dir) ?? {};
6159
+ config2.schedules = schedules.length > 0 ? schedules : void 0;
6160
+ writeVoluteConfig(dir, config2);
6161
+ getScheduler().loadSchedules(name, dir);
6162
+ getSleepManagerIfReady()?.invalidateSleepConfig(name);
6163
+ fireWebhook({
6164
+ event: "schedule_changed",
6165
+ mind: name,
6166
+ data: { schedules }
6167
+ });
6168
+ }
6169
+ var app15 = new Hono18().get("/:name/clock/status", requireSelf(), async (c) => {
6170
+ const name = c.req.param("name");
6171
+ const entry = await findMind(name);
6172
+ if (!entry) return c.json({ error: "Mind not found" }, 404);
6173
+ const dir = entry.dir ?? mindDir(name);
6174
+ const sleepManager = getSleepManagerIfReady();
6175
+ const sleepState = sleepManager?.getState(name) ?? null;
6176
+ const sleepConfig = sleepManager?.getSleepConfig(name) ?? null;
6177
+ const schedules = readSchedules(dir);
6178
+ const now = /* @__PURE__ */ new Date();
6179
+ const { upcoming, previous } = computeClockEvents(schedules, sleepState, sleepConfig, now);
6073
6180
  return c.json({ sleep: sleepState, sleepConfig, schedules, upcoming, previous });
6074
6181
  }).get("/:name/sleep/config", requireSelf(), async (c) => {
6075
6182
  const name = c.req.param("name");
@@ -6236,7 +6343,7 @@ var app15 = new Hono18().get("/:name/clock/status", requireSelf(), async (c) =>
6236
6343
  const body = await c.req.text();
6237
6344
  const message = `[webhook: ${event}] ${body}`;
6238
6345
  try {
6239
- const { sendSystemMessage } = await import("./system-chat-XOALE2RD.js");
6346
+ const { sendSystemMessage } = await import("./system-chat-NDAW6YP7.js");
6240
6347
  await sendSystemMessage(name, message);
6241
6348
  return c.json({ ok: true });
6242
6349
  } catch (err) {
@@ -6290,7 +6397,7 @@ setup.get("/status", async (c) => {
6290
6397
  let hasAccount = false;
6291
6398
  if (hasSystem) {
6292
6399
  try {
6293
- const { listUsersByType: listUsersByType2 } = await import("./auth-FLAEXXZK.js");
6400
+ const { listUsersByType: listUsersByType2 } = await import("./auth-CCFAU4GQ.js");
6294
6401
  const brains = await listUsersByType2("brain");
6295
6402
  hasAccount = brains.length > 0;
6296
6403
  } catch (err) {
@@ -6501,7 +6608,7 @@ setup.post("/account", async (c) => {
6501
6608
  }
6502
6609
  }
6503
6610
  try {
6504
- const { createUser: createUser2, updateUserProfile: updateUserProfile2 } = await import("./auth-FLAEXXZK.js");
6611
+ const { createUser: createUser2, updateUserProfile: updateUserProfile2 } = await import("./auth-CCFAU4GQ.js");
6505
6612
  const user = await createUser2(body.username.trim(), body.password);
6506
6613
  if (body.displayName?.trim()) {
6507
6614
  await updateUserProfile2(user.id, { display_name: body.displayName.trim() });
@@ -6565,8 +6672,8 @@ setup.post("/complete", async (c) => {
6565
6672
  return c.json({ error: "Setup already complete" }, 400);
6566
6673
  }
6567
6674
  try {
6568
- const { ensureSpiritProject, syncSpiritTemplate } = await import("./spirit-DBTIW7P4.js");
6569
- const { startSpiritFull } = await import("./mind-service-3F4I46CI.js");
6675
+ const { ensureSpiritProject, syncSpiritTemplate } = await import("./spirit-RPBLLUQ5.js");
6676
+ const { startSpiritFull } = await import("./mind-service-NB2JMWQZ.js");
6570
6677
  await ensureSpiritProject();
6571
6678
  await syncSpiritTemplate();
6572
6679
  const warnings = [];
@@ -6601,8 +6708,8 @@ setup.post("/complete", async (c) => {
6601
6708
  }
6602
6709
  let spiritConversationId;
6603
6710
  try {
6604
- const { getOrCreateMindUser: getOrCreateMindUser2, listUsersByType: listUsersByType2 } = await import("./auth-FLAEXXZK.js");
6605
- const { createConversation: createConversation2, findDMConversation: findDMConversation2 } = await import("./conversations-JZTD6KLR.js");
6711
+ const { getOrCreateMindUser: getOrCreateMindUser2, listUsersByType: listUsersByType2 } = await import("./auth-CCFAU4GQ.js");
6712
+ const { createConversation: createConversation2, findDMConversation: findDMConversation2 } = await import("./conversations-CJOOCGNW.js");
6606
6713
  const spiritUser = await getOrCreateMindUser2("volute");
6607
6714
  const brains = await listUsersByType2("brain");
6608
6715
  const admin2 = brains.find((u) => u.role === "admin");
@@ -6624,21 +6731,23 @@ setup.post("/complete", async (c) => {
6624
6731
  logger_default.info("setup complete state", { spiritConversationId, spiritStarted });
6625
6732
  if (spiritConversationId && spiritStarted) {
6626
6733
  try {
6627
- const { deliverMessage: deliverMessage2 } = await import("./message-delivery-3C65ZI6L.js");
6628
- const { listUsersByType: listUsers2 } = await import("./auth-FLAEXXZK.js");
6734
+ const { deliverMessage: deliverMessage2 } = await import("./message-delivery-R7JSQGYX.js");
6735
+ const { listUsersByType: listUsers2 } = await import("./auth-CCFAU4GQ.js");
6629
6736
  const admins = await listUsers2("brain");
6630
6737
  const admin2 = admins.find((u) => u.role === "admin");
6631
- const adminName = admin2?.display_name || admin2?.username || "the admin";
6632
- const welcomePrompt = `Setup is complete. ${adminName} just finished setting up this system and is now in your chat. Welcome them and help them create their first mind. Keep it brief \u2014 a short greeting, then ask what kind of being they'd like to bring into the world. Remember: minds are beings with their own experience, not tools \u2014 ask about personality and character, never about tasks or utility.`;
6633
- await deliverMessage2("volute", {
6634
- content: [{ type: "text", text: welcomePrompt }],
6635
- channel: `@${admin2?.username ?? "system"}`,
6636
- conversationId: spiritConversationId,
6637
- sender: admin2?.username ?? "system",
6638
- isDM: true,
6639
- participants: ["volute", admin2?.username ?? "system"],
6640
- participantCount: 2
6641
- });
6738
+ if (admin2) {
6739
+ const adminName = admin2.display_name || admin2.username;
6740
+ const welcomePrompt = `Setup is complete. ${adminName} just finished setting up this system and is now in your chat. Welcome them and help them create their first mind. Keep it brief \u2014 a short greeting, then ask what kind of being they'd like to bring into the world. Remember: minds are beings with their own experience, not tools \u2014 ask about personality and character, never about tasks or utility.`;
6741
+ await deliverMessage2(SPIRIT_NAME, {
6742
+ content: [{ type: "text", text: welcomePrompt }],
6743
+ channel: `@${admin2.username}`,
6744
+ conversationId: spiritConversationId,
6745
+ sender: admin2.username,
6746
+ isDM: true,
6747
+ participants: [SPIRIT_NAME, admin2.username],
6748
+ participantCount: 2
6749
+ });
6750
+ }
6642
6751
  } catch (err) {
6643
6752
  logger_default.warn("failed to send welcome prompt to spirit (non-fatal)", logger_default.errorData(err));
6644
6753
  warnings.push(
@@ -6735,10 +6844,10 @@ var app16 = new Hono20().get("/", async (c) => {
6735
6844
  if (!file.name.endsWith(".zip")) {
6736
6845
  return c.json({ error: "Only .zip files are accepted" }, 400);
6737
6846
  }
6738
- const buffer2 = Buffer.from(await file.arrayBuffer());
6847
+ const buffer = Buffer.from(await file.arrayBuffer());
6739
6848
  const tmpDir = mkdtempSync(join(tmpdir(), "volute-skill-upload-"));
6740
6849
  try {
6741
- const zip = new AdmZip(buffer2);
6850
+ const zip = new AdmZip(buffer);
6742
6851
  for (const entry of zip.getEntries()) {
6743
6852
  const target = resolve12(tmpDir, entry.entryName);
6744
6853
  if (!target.startsWith(tmpDir)) {
@@ -6808,7 +6917,7 @@ var app17 = new Hono21().post("/:name/typing", zValidator8("json", typingSchema)
6808
6917
  map.delete(channel, sender);
6809
6918
  }
6810
6919
  if (isConversationId(channel)) {
6811
- publish(channel, { type: "typing", senders: map.get(channel) });
6920
+ publish2(channel, { type: "typing", senders: map.get(channel) });
6812
6921
  }
6813
6922
  return c.json({ ok: true });
6814
6923
  }).get("/:name/typing", (c) => {
@@ -6971,42 +7080,6 @@ function getOnlineBrains() {
6971
7080
  return [...connections.keys()];
6972
7081
  }
6973
7082
 
6974
- // packages/daemon/src/lib/events/event-sequencer.ts
6975
- var BUFFER_SIZE = 1e3;
6976
- var MAX_AGE_MS = 5 * 60 * 1e3;
6977
- var nextId = 1;
6978
- var buffer = [];
6979
- function bufferEvent(data) {
6980
- const id = nextId++;
6981
- buffer.push({ id, data, timestamp: Date.now() });
6982
- while (buffer.length > BUFFER_SIZE) {
6983
- buffer.shift();
6984
- }
6985
- return id;
6986
- }
6987
- function nextEventId() {
6988
- return nextId++;
6989
- }
6990
- function getEventsSince(sinceId, allowedConversationIds, allowedActivityMind) {
6991
- const now = Date.now();
6992
- const startIdx = buffer.findIndex((e) => e.id > sinceId);
6993
- if (startIdx === -1) return [];
6994
- return buffer.slice(startIdx).filter((e) => {
6995
- if (now - e.timestamp >= MAX_AGE_MS) return false;
6996
- const data = e.data;
6997
- switch (data.event) {
6998
- case "snapshot":
6999
- return false;
7000
- case "conversation":
7001
- return allowedConversationIds.has(data.conversationId);
7002
- case "activity":
7003
- return allowedActivityMind === void 0 || data.mind === allowedActivityMind;
7004
- default:
7005
- return false;
7006
- }
7007
- });
7008
- }
7009
-
7010
7083
  // packages/daemon/src/web/api/v1/events.ts
7011
7084
  var app20 = new Hono24().use("*", authMiddleware).get("/", async (c) => {
7012
7085
  const user = c.get("user");
@@ -7068,15 +7141,15 @@ var app20 = new Hono24().use("*", authMiddleware).get("/", async (c) => {
7068
7141
  data: JSON.stringify(snapshotData)
7069
7142
  });
7070
7143
  const unsubActivity = subscribe((event) => {
7144
+ if (activityMind !== void 0 && event.mind !== activityMind) return;
7145
+ const { seq, ...rest } = event;
7071
7146
  const data = {
7072
7147
  event: "activity",
7073
- ...event,
7074
- metadata: event.metadata ?? null
7148
+ ...rest,
7149
+ metadata: rest.metadata ?? null
7075
7150
  };
7076
- const eventId = bufferEvent(data);
7077
- if (activityMind !== void 0 && event.mind !== activityMind) return;
7078
7151
  stream.writeSSE({
7079
- id: String(eventId),
7152
+ id: String(seq),
7080
7153
  data: JSON.stringify(data)
7081
7154
  }).catch((err) => {
7082
7155
  if (!stream.aborted) logger_default.error("[v1-events] write error:", logger_default.errorData(err));
@@ -7085,10 +7158,10 @@ var app20 = new Hono24().use("*", authMiddleware).get("/", async (c) => {
7085
7158
  cleanups.push(unsubActivity);
7086
7159
  for (const conv of conversations2) {
7087
7160
  const unsubConv = subscribe2(conv.id, (event) => {
7088
- const data = { event: "conversation", conversationId: conv.id, ...event };
7089
- const eventId = bufferEvent(data);
7161
+ const { seq, ...rest } = event;
7162
+ const data = { event: "conversation", conversationId: conv.id, ...rest };
7090
7163
  stream.writeSSE({
7091
- id: String(eventId),
7164
+ id: String(seq),
7092
7165
  data: JSON.stringify(data)
7093
7166
  }).catch((err) => {
7094
7167
  if (!stream.aborted) logger_default.error("[v1-events] write error:", logger_default.errorData(err));
@@ -7126,12 +7199,9 @@ import { Hono as Hono25 } from "hono";
7126
7199
  import { spawn as spawn4 } from "child_process";
7127
7200
  import { closeSync, mkdirSync as mkdirSync8, openSync, readFileSync as readFileSync9 } from "fs";
7128
7201
  import { resolve as resolve13 } from "path";
7129
- function tsxBin(cwd) {
7130
- return resolve13(cwd, "node_modules", ".bin", "tsx");
7131
- }
7132
7202
  async function spawnServer(cwd, port, options) {
7133
- let cmd = tsxBin(cwd);
7134
- let args = ["src/server.ts", "--port", String(port)];
7203
+ let cmd = process.env.VOLUTE_NODE_PATH ?? process.execPath;
7204
+ let args = ["--import", "tsx", "src/server.ts", "--port", String(port)];
7135
7205
  if (options?.mindName) {
7136
7206
  if (isIsolationEnabled()) {
7137
7207
  [cmd, args] = await wrapForIsolation(cmd, args, options.mindName);
@@ -7834,7 +7904,7 @@ var unifiedChatApp = new Hono27().post(
7834
7904
  });
7835
7905
  if (outboundTurnId) {
7836
7906
  const mindKey = senderBase ?? senderName;
7837
- publish2(mindKey, {
7907
+ publish3(mindKey, {
7838
7908
  mind: mindKey,
7839
7909
  type: "outbound",
7840
7910
  channel,
@@ -7960,6 +8030,9 @@ var app24 = new Hono28().get("/:name/conversations", async (c) => {
7960
8030
  if (!u) return c.json({ error: `User ${id} not found` }, 400);
7961
8031
  }
7962
8032
  const participantIds = [...participantSet];
8033
+ if (participantIds.length < 2) {
8034
+ return c.json({ error: "a conversation needs at least two distinct participants" }, 400);
8035
+ }
7963
8036
  if (participantIds.length > 2) {
7964
8037
  return c.json({ error: "Use channels for multi-participant conversations" }, 400);
7965
8038
  }
@@ -8290,7 +8363,7 @@ async function startDaemon(opts) {
8290
8363
  logger_default.warn("failed to ensure #system channel", logger_default.errorData(err));
8291
8364
  }
8292
8365
  try {
8293
- const { getOrCreateSystemUser: getOrCreateSystemUser2 } = await import("./auth-FLAEXXZK.js");
8366
+ const { getOrCreateSystemUser: getOrCreateSystemUser2 } = await import("./auth-CCFAU4GQ.js");
8294
8367
  await getOrCreateSystemUser2();
8295
8368
  } catch (err) {
8296
8369
  logger_default.warn(
@@ -8386,13 +8459,17 @@ async function startDaemon(opts) {
8386
8459
  try {
8387
8460
  const { isSetupComplete: isSetupComplete2 } = await import("./setup-Y2EJI3GG.js");
8388
8461
  if (isSetupComplete2()) {
8389
- const { ensureSpiritProject, syncSpiritTemplate } = await import("./spirit-DBTIW7P4.js");
8390
- const { startSpiritFull } = await import("./mind-service-3F4I46CI.js");
8462
+ const { ensureSpiritProject, syncSpiritTemplate } = await import("./spirit-RPBLLUQ5.js");
8463
+ const { startSpiritFull } = await import("./mind-service-NB2JMWQZ.js");
8391
8464
  await ensureSpiritProject();
8465
+ const spiritEntry = await findMind(SPIRIT_NAME);
8466
+ if (spiritEntry?.dir) {
8467
+ const { registerMindDir } = await import("./delivery-router-N4A7ZC5T.js");
8468
+ registerMindDir(SPIRIT_NAME, spiritEntry.dir);
8469
+ }
8392
8470
  await syncSpiritTemplate();
8393
- const spiritEntry = await findMind("volute");
8394
- if (spiritEntry && !manager.isRunning("volute")) {
8395
- await startSpiritFull("volute");
8471
+ if (spiritEntry && !manager.isRunning(SPIRIT_NAME)) {
8472
+ await startSpiritFull(SPIRIT_NAME);
8396
8473
  }
8397
8474
  }
8398
8475
  } catch (err) {
@@ -8404,7 +8481,7 @@ async function startDaemon(opts) {
8404
8481
  bridgeManager.startBridges(daemonPort).catch((err) => {
8405
8482
  logger_default.warn("failed to start bridges", logger_default.errorData(err));
8406
8483
  });
8407
- import("./cloud-sync-3OJOPZRT.js").then(
8484
+ import("./cloud-sync-46RS4SDA.js").then(
8408
8485
  ({ consumeQueuedMessages }) => consumeQueuedMessages().catch((err) => {
8409
8486
  logger_default.warn("failed to consume queued cloud messages", logger_default.errorData(err));
8410
8487
  })
@@ -8412,7 +8489,7 @@ async function startDaemon(opts) {
8412
8489
  logger_default.warn("failed to load cloud-sync module", logger_default.errorData(err));
8413
8490
  });
8414
8491
  try {
8415
- const { backfillTemplateHashes, notifyVersionUpdate } = await import("./version-notify-3XMAIQTE.js");
8492
+ const { backfillTemplateHashes, notifyVersionUpdate } = await import("./version-notify-C5CH7CVP.js");
8416
8493
  backfillTemplateHashes();
8417
8494
  notifyVersionUpdate().catch((err) => {
8418
8495
  logger_default.warn("failed to send version update notifications", logger_default.errorData(err));
@@ -8430,6 +8507,7 @@ async function startDaemon(opts) {
8430
8507
  cleanExpiredLogs().catch((err) => {
8431
8508
  logger_default.warn("failed to clean expired logs", logger_default.errorData(err));
8432
8509
  });
8510
+ const maintenanceInterval = startMaintenanceInterval();
8433
8511
  setProviderRefreshHook((provider) => {
8434
8512
  void syncProviderToMinds(provider, {
8435
8513
  listRunning: () => getMindManager().getRunningMinds()
@@ -8482,6 +8560,7 @@ async function startDaemon(opts) {
8482
8560
  safe("summarizer.stop", () => summarizer.stop());
8483
8561
  safe("backupManager.stop", () => backupManager2.stop());
8484
8562
  safe("stopApiKeyRefresh", stopApiKeyRefresh);
8563
+ safe("maintenanceInterval", () => clearInterval(maintenanceInterval));
8485
8564
  safe("delivery.dispose", () => delivery.dispose());
8486
8565
  await safe("bridgeManager.stopAll", () => bridgeManager.stopAll());
8487
8566
  await safe("manager.stopAll", () => manager.stopAll());