volute 0.45.1 → 0.45.2

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 (108) hide show
  1. package/dist/{activity-events-OMCSM7XW.js → activity-events-BUR2RYTJ.js} +2 -2
  2. package/dist/{ai-service-534WUSDL.js → ai-service-UODCCE4O.js} +3 -3
  3. package/dist/{archive-6R4TZMYI.js → archive-WS2L2VIQ.js} +2 -2
  4. package/dist/{auth-FRZJVQCS.js → auth-FLAEXXZK.js} +3 -3
  5. package/dist/{avatar-image-BNWEXSA2.js → avatar-image-6KY6UU4D.js} +3 -3
  6. package/dist/{backup-OZXHL3JS.js → backup-TST5C2YN.js} +7 -7
  7. package/dist/{chat-I4EDDIQC.js → chat-76MRWWQX.js} +2 -2
  8. package/dist/{chunk-ZIR7HSAX.js → chunk-4JOQZPCE.js} +1 -1
  9. package/dist/{chunk-2BONPXAF.js → chunk-6EMWJZBK.js} +1 -1
  10. package/dist/{chunk-ZMIMSIIT.js → chunk-7C44R625.js} +1 -1
  11. package/dist/{chunk-Y6OPHHGM.js → chunk-7ZUB63PS.js} +4 -4
  12. package/dist/{chunk-LAXUUY7O.js → chunk-A7WQZKOU.js} +2 -2
  13. package/dist/{chunk-H7IYFRAL.js → chunk-ALKYQULU.js} +10 -5
  14. package/dist/{chunk-KP3KWNG6.js → chunk-CDPBTTZO.js} +2 -2
  15. package/dist/{chunk-SQL7M4HJ.js → chunk-DRJ24GXK.js} +2 -2
  16. package/dist/{chunk-HVU45COX.js → chunk-EJVPTVTI.js} +1 -1
  17. package/dist/{chunk-322KE457.js → chunk-F7H6ATM4.js} +1 -1
  18. package/dist/{chunk-5AUKCNPE.js → chunk-FR6S2KNN.js} +8 -8
  19. package/dist/{chunk-KVKP6VQ5.js → chunk-FXORA5JG.js} +12 -3
  20. package/dist/{chunk-3ERIL673.js → chunk-H2X54EHC.js} +1 -1
  21. package/dist/{chunk-TSK5PI4L.js → chunk-IHKGWTGT.js} +1 -1
  22. package/dist/{chunk-OXXTPILE.js → chunk-JZ62SYWR.js} +1 -1
  23. package/dist/{chunk-77BVGCTZ.js → chunk-NYC5U6K7.js} +9 -9
  24. package/dist/{chunk-JSRMEY52.js → chunk-OP4JG2BS.js} +5 -4
  25. package/dist/{chunk-CU6OFXMM.js → chunk-PFZBNKUH.js} +9 -0
  26. package/dist/{chunk-QMJJWTNL.js → chunk-QCN24XEN.js} +1 -1
  27. package/dist/{chunk-IUXTYXFI.js → chunk-QNQMSWZE.js} +3 -3
  28. package/dist/{chunk-UMI4UIJG.js → chunk-R77O2WMK.js} +1 -1
  29. package/dist/{chunk-IEWZ7VD7.js → chunk-RRNXJOKB.js} +2 -2
  30. package/dist/{chunk-FHWUQZXE.js → chunk-RXEEYDIB.js} +1 -1
  31. package/dist/{chunk-CYI534DJ.js → chunk-T6HYVHPG.js} +2 -2
  32. package/dist/{chunk-Q2XBKQ2D.js → chunk-TQJ6ZYVY.js} +2 -2
  33. package/dist/{chunk-V4GYPD5L.js → chunk-UYLGEDVD.js} +4 -4
  34. package/dist/{chunk-U4FNXDEJ.js → chunk-YT7PEESJ.js} +231 -98
  35. package/dist/{chunk-5GT7URXJ.js → chunk-ZDZISKCL.js} +1 -1
  36. package/dist/cli.js +16 -16
  37. package/dist/{cloud-sync-Y7LQPRE3.js → cloud-sync-3OJOPZRT.js} +16 -16
  38. package/dist/{conversations-G4LYMHAA.js → conversations-JZTD6KLR.js} +3 -3
  39. package/dist/{create-2YY7IPMS.js → create-IOV4GCRX.js} +1 -1
  40. package/dist/{daemon-restart-ABP54YN6.js → daemon-restart-TIHKVVVA.js} +6 -6
  41. package/dist/daemon.js +180 -137
  42. package/dist/{db-JFLCJCUG.js → db-MF2JNRUW.js} +1 -1
  43. package/dist/delivery-manager-VDVKVA46.js +32 -0
  44. package/dist/{delivery-router-6DE3BPHO.js → delivery-router-N4A7ZC5T.js} +2 -2
  45. package/dist/{down-XCAZXWLB.js → down-U2CZ23M4.js} +4 -4
  46. package/dist/echo-text-6JJOPUD4.js +32 -0
  47. package/dist/{exec-4GWHAHS3.js → exec-G3ZRST4Z.js} +2 -2
  48. package/dist/{export-ID7YUSLZ.js → export-GGIVO76O.js} +2 -2
  49. package/dist/{extensions-AYSPKE33.js → extensions-JONTW2HF.js} +16 -16
  50. package/dist/{files-BQ2WB5KC.js → files-4LN3EN7Q.js} +2 -2
  51. package/dist/{import-SKID37QX.js → import-42YNQBF4.js} +5 -5
  52. package/dist/{isolation-5MB3J73F.js → isolation-YMGZZ4E5.js} +2 -2
  53. package/dist/{join-RXPXGV4L.js → join-ZJV7W6MO.js} +4 -0
  54. package/dist/{login-LM4NF4ZB.js → login-M6OJUPQ2.js} +1 -1
  55. package/dist/{logout-CTJVIZGA.js → logout-WQBWO77G.js} +1 -1
  56. package/dist/message-delivery-3C65ZI6L.js +34 -0
  57. package/dist/{mind-KLHC7XYR.js → mind-DWB6XYCH.js} +6 -6
  58. package/dist/{mind-activity-tracker-BGHJE7XA.js → mind-activity-tracker-POBLU3LC.js} +3 -3
  59. package/dist/mind-service-3F4I46CI.js +36 -0
  60. package/dist/{package-BMHV7UHN.js → package-A7KXT67Q.js} +1 -1
  61. package/dist/{registry-NUJAGLQJ.js → registry-OUZBZ3Q4.js} +5 -1
  62. package/dist/{sandbox-72MWV4JL.js → sandbox-ZJ7QA6Z6.js} +3 -3
  63. package/dist/scheduler-P6DXLLXI.js +30 -0
  64. package/dist/{seed-4MKQ5QKG.js → seed-HCP5NYKH.js} +1 -1
  65. package/dist/{seed-cmd-LCXMR7VV.js → seed-cmd-ILOGNIFB.js} +2 -2
  66. package/dist/{seed-create-N2BF5LQX.js → seed-create-OURS2LS4.js} +3 -3
  67. package/dist/{seed-sprout-6ZDP23YI.js → seed-sprout-ELDFDAN6.js} +7 -7
  68. package/dist/{send-O6W53VBM.js → send-AAIOPJAV.js} +2 -2
  69. package/dist/{service-AQVPUANO.js → service-UOCLXDWR.js} +3 -3
  70. package/dist/{service-install-4QNUDKLM.js → service-install-4HHFDDWY.js} +4 -4
  71. package/dist/{setup-3O6O2JZ7.js → setup-DQM5VPY4.js} +6 -6
  72. package/dist/{setup-73YBJZQL.js → setup-Y2EJI3GG.js} +2 -2
  73. package/dist/skills/imagegen/scripts/imagegen.ts +100 -40
  74. package/dist/skills/volute-mind/SKILL.md +5 -1
  75. package/dist/{skills-ND4ZFYUW.js → skills-BKINFL7W.js} +4 -4
  76. package/dist/sleep-manager-HCUH4PM4.js +34 -0
  77. package/dist/{spirit-II3HOERM.js → spirit-DBTIW7P4.js} +7 -7
  78. package/dist/{sprout-4KUG76AQ.js → sprout-W3KC7UGK.js} +1 -1
  79. package/dist/{src-XFY3XDD6.js → src-OJHARHTF.js} +28 -17
  80. package/dist/{status-E5DQNCAO.js → status-SWRVGXBI.js} +4 -4
  81. package/dist/system-chat-XOALE2RD.js +36 -0
  82. package/dist/{tailscale-QAWVKQKU.js → tailscale-3XOKOW7T.js} +1 -1
  83. package/dist/{turn-tracker-QQIVVXBI.js → turn-tracker-2W6Q2NSR.js} +4 -4
  84. package/dist/{up-LG56MPBI.js → up-FTK7CTO3.js} +5 -5
  85. package/dist/{update-7TYVH5FM.js → update-SWAYZRWJ.js} +4 -4
  86. package/dist/{update-check-VSCSJ7KX.js → update-check-PQ2MZ47K.js} +2 -2
  87. package/dist/{version-notify-CODJOUW2.js → version-notify-3XMAIQTE.js} +17 -17
  88. package/dist/{volute-config-TS62GS6A.js → volute-config-A52FHWKM.js} +5 -1
  89. package/dist/web-assets/assets/{index-Dac11sKB.js → index-Bc2m8gRC.js} +15 -15
  90. package/dist/web-assets/index.html +1 -1
  91. package/package.json +1 -1
  92. package/templates/_base/src/lib/file-handler.ts +3 -3
  93. package/templates/_base/src/lib/router.ts +21 -25
  94. package/templates/_base/src/lib/startup.ts +2 -0
  95. package/templates/_base/src/lib/types.ts +2 -2
  96. package/templates/claude/src/agent.ts +75 -10
  97. package/templates/claude/src/lib/message-channel.ts +23 -0
  98. package/templates/claude/src/lib/session-reaper.ts +37 -0
  99. package/templates/claude/src/server.ts +1 -0
  100. package/templates/codex/src/agent.ts +18 -6
  101. package/templates/pi/src/agent.ts +18 -7
  102. package/dist/delivery-manager-R5STGLXB.js +0 -32
  103. package/dist/echo-text-NQXYYYCU.js +0 -32
  104. package/dist/message-delivery-IUAXRPFE.js +0 -34
  105. package/dist/mind-service-BF2LUQMH.js +0 -36
  106. package/dist/scheduler-YHJRRV6E.js +0 -30
  107. package/dist/sleep-manager-YAHDOIU5.js +0 -34
  108. package/dist/system-chat-E65T2HSW.js +0 -34
@@ -1,16 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  loadMergedEnv
4
- } from "./chunk-5GT7URXJ.js";
4
+ } from "./chunk-ZDZISKCL.js";
5
5
  import {
6
6
  spiritDir
7
- } from "./chunk-5AUKCNPE.js";
7
+ } from "./chunk-FR6S2KNN.js";
8
8
  import {
9
9
  hashSkillDir,
10
10
  importSkillFromDir,
11
11
  removeSharedSkill,
12
12
  sharedSkillsDir
13
- } from "./chunk-Y6OPHHGM.js";
13
+ } from "./chunk-7ZUB63PS.js";
14
14
  import {
15
15
  addMessage,
16
16
  createChannel,
@@ -20,74 +20,77 @@ import {
20
20
  getParticipants,
21
21
  joinChannel,
22
22
  publish as publish2
23
- } from "./chunk-JSRMEY52.js";
23
+ } from "./chunk-OP4JG2BS.js";
24
24
  import {
25
25
  readVoluteConfig,
26
+ resolveWakeTriggers,
26
27
  writeVoluteConfig
27
- } from "./chunk-CU6OFXMM.js";
28
+ } from "./chunk-PFZBNKUH.js";
28
29
  import {
29
30
  isSandboxEnabled,
30
31
  wrapForSandbox
31
- } from "./chunk-OXXTPILE.js";
32
+ } from "./chunk-JZ62SYWR.js";
32
33
  import {
33
34
  markIdle,
34
35
  onMindEvent
35
- } from "./chunk-HVU45COX.js";
36
+ } from "./chunk-EJVPTVTI.js";
36
37
  import {
37
38
  extractTextContent,
38
39
  getRoutingConfig,
39
40
  resolveDeliveryMode,
40
41
  resolveRoute,
41
42
  setRoutesChangeListener
42
- } from "./chunk-ZIR7HSAX.js";
43
+ } from "./chunk-4JOQZPCE.js";
43
44
  import {
44
45
  clearMind,
45
46
  getPrompt,
46
47
  linkInboundToActiveTurn,
47
48
  publish as publish3,
48
49
  summarizeOrphanedTurns
49
- } from "./chunk-V4GYPD5L.js";
50
+ } from "./chunk-UYLGEDVD.js";
50
51
  import {
51
52
  chownMindDir,
52
53
  exec,
53
54
  isIsolationEnabled,
54
55
  mindUserName,
55
56
  wrapForIsolation
56
- } from "./chunk-3ERIL673.js";
57
+ } from "./chunk-H2X54EHC.js";
57
58
  import {
58
59
  aiCompleteUtility,
59
60
  getAiConfig,
60
61
  resolveApiKey
61
- } from "./chunk-TSK5PI4L.js";
62
+ } from "./chunk-IHKGWTGT.js";
62
63
  import {
63
64
  readGlobalConfig,
64
65
  writeGlobalConfig
65
- } from "./chunk-322KE457.js";
66
+ } from "./chunk-F7H6ATM4.js";
66
67
  import {
67
68
  getOrCreateMindUser,
68
69
  getOrCreateSystemUser,
69
70
  getUser,
70
71
  getUserByUsername,
71
72
  syncMindProfile
72
- } from "./chunk-CYI534DJ.js";
73
+ } from "./chunk-T6HYVHPG.js";
73
74
  import {
74
75
  publish,
75
76
  subscribe
76
- } from "./chunk-ZMIMSIIT.js";
77
+ } from "./chunk-7C44R625.js";
77
78
  import {
78
79
  logger_default
79
80
  } from "./chunk-T2TP6ZC6.js";
80
81
  import {
82
+ SPIRIT_NAME,
81
83
  findMind,
82
84
  getBaseName,
83
85
  getDb,
86
+ isSpiritName,
84
87
  mindDir,
85
88
  mindTmpDir,
86
89
  setMindRunning,
87
90
  stateDir,
88
91
  voluteHome,
89
92
  voluteSystemDir
90
- } from "./chunk-H7IYFRAL.js";
93
+ } from "./chunk-ALKYQULU.js";
91
94
  import {
92
95
  activity,
93
96
  deliveryQueue,
@@ -98,7 +101,7 @@ import {
98
101
  } from "./chunk-ZV7MFISX.js";
99
102
 
100
103
  // packages/daemon/src/lib/delivery/message-delivery.ts
101
- import { and as and5, eq as eq6, inArray as inArray3 } from "drizzle-orm";
104
+ import { and as and5, eq as eq6, inArray as inArray2 } from "drizzle-orm";
102
105
 
103
106
  // packages/daemon/src/lib/daemon/sleep-manager.ts
104
107
  import { execFile as execFile2, spawn as spawnChild } from "child_process";
@@ -114,7 +117,7 @@ import {
114
117
  import { resolve as resolve7 } from "path";
115
118
  import { promisify as promisify2 } from "util";
116
119
  import { CronExpressionParser as CronExpressionParser2 } from "cron-parser";
117
- import { and as and2, eq as eq3, inArray } from "drizzle-orm";
120
+ import { and as and2, eq as eq3 } from "drizzle-orm";
118
121
 
119
122
  // packages/daemon/src/lib/daemon/mind-manager.ts
120
123
  import { execFile, spawn } from "child_process";
@@ -335,6 +338,9 @@ function revokeMindToken(mindName) {
335
338
  function resolveMindToken(token) {
336
339
  return tokenToMind.get(token) ?? null;
337
340
  }
341
+ function getMindToken(mindName) {
342
+ return mindToToken.get(mindName) ?? null;
343
+ }
338
344
 
339
345
  // packages/daemon/src/lib/daemon/notices.ts
340
346
  import { and, asc, eq, lte, or, sql } from "drizzle-orm";
@@ -939,7 +945,7 @@ var MindManager = class {
939
945
  if (this.shuttingDown || this.stopping.has(name)) return;
940
946
  mlog.error(`mind ${name} exited with code ${code}`);
941
947
  try {
942
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-YAHDOIU5.js");
948
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-HCUH4PM4.js");
943
949
  const sleepMgr = getSleepManagerIfReady2();
944
950
  const sleepState = sleepMgr?.getState(name);
945
951
  if (sleepState?.sleeping) {
@@ -969,15 +975,15 @@ var MindManager = class {
969
975
  (err) => mlog.warn(`failed to clear turn state for ${name} after crash`, logger_default.errorData(err))
970
976
  );
971
977
  try {
972
- const { getDeliveryManager: getDeliveryManager2 } = await import("./delivery-manager-R5STGLXB.js");
978
+ const { getDeliveryManager: getDeliveryManager2 } = await import("./delivery-manager-VDVKVA46.js");
973
979
  getDeliveryManager2().clearMindSessions(name);
974
980
  } catch (err) {
975
981
  if (!(err instanceof Error && err.message.includes("not initialized"))) {
976
982
  mlog.warn(`failed to clear delivery state for ${name} after crash`, logger_default.errorData(err));
977
983
  }
978
984
  }
979
- import("./mind-activity-tracker-BGHJE7XA.js").then(({ markIdle: markIdle2 }) => markIdle2(name)).catch((err) => mlog.warn(`failed to mark ${name} idle after crash`, logger_default.errorData(err)));
980
- import("./activity-events-OMCSM7XW.js").then(
985
+ import("./mind-activity-tracker-POBLU3LC.js").then(({ markIdle: markIdle2 }) => markIdle2(name)).catch((err) => mlog.warn(`failed to mark ${name} idle after crash`, logger_default.errorData(err)));
986
+ import("./activity-events-BUR2RYTJ.js").then(
981
987
  ({ publish: publish4 }) => publish4({ type: "mind_stopped", mind: name, summary: `${name} crashed (exit ${code})` })
982
988
  ).catch((err) => mlog.warn(`failed to publish crash event for ${name}`, logger_default.errorData(err)));
983
989
  const { shouldRestart, delay, attempt } = this.restartTracker.recordCrash(name);
@@ -1035,7 +1041,7 @@ var MindManager = class {
1035
1041
  mlog.warn(`failed to clear turn state for ${name} on stop`, logger_default.errorData(err));
1036
1042
  }
1037
1043
  try {
1038
- const { getDeliveryManager: getDeliveryManager2 } = await import("./delivery-manager-R5STGLXB.js");
1044
+ const { getDeliveryManager: getDeliveryManager2 } = await import("./delivery-manager-VDVKVA46.js");
1039
1045
  getDeliveryManager2().clearMindSessions(name);
1040
1046
  } catch (err) {
1041
1047
  if (!(err instanceof Error && err.message.includes("not initialized"))) {
@@ -1043,7 +1049,7 @@ var MindManager = class {
1043
1049
  }
1044
1050
  }
1045
1051
  try {
1046
- const { clearEchoTextCache } = await import("./echo-text-NQXYYYCU.js");
1052
+ const { clearEchoTextCache } = await import("./echo-text-6JJOPUD4.js");
1047
1053
  clearEchoTextCache(name);
1048
1054
  } catch (err) {
1049
1055
  mlog.debug(`failed to clear echo-text cache for ${name}`, logger_default.errorData(err));
@@ -1699,7 +1705,7 @@ function resolveSkillsDir(manifest) {
1699
1705
  async function discoverBuiltinExtensions(disabledIds) {
1700
1706
  const builtins = [
1701
1707
  { id: "notes", load: async () => (await import("./src-YH6PQ7LJ.js")).default },
1702
- { id: "pages", load: async () => (await import("./src-XFY3XDD6.js")).default },
1708
+ { id: "pages", load: async () => (await import("./src-OJHARHTF.js")).default },
1703
1709
  { id: "plan", load: async () => (await import("./src-JAOVXO5W.js")).default }
1704
1710
  ];
1705
1711
  const results = [];
@@ -1949,7 +1955,7 @@ async function installNpmExtension(pkg) {
1949
1955
  throw new Error(`Extension "${pkg}" is already installed`);
1950
1956
  }
1951
1957
  const dir = ensureExtensionsNpmDir();
1952
- const { exec: exec2 } = await import("./exec-4GWHAHS3.js");
1958
+ const { exec: exec2 } = await import("./exec-G3ZRST4Z.js");
1953
1959
  try {
1954
1960
  await exec2("npm", ["install", "--ignore-scripts", pkg], { cwd: dir });
1955
1961
  } catch (err) {
@@ -1970,7 +1976,7 @@ async function uninstallNpmExtension(pkg) {
1970
1976
  packages.splice(idx, 1);
1971
1977
  writeExtensionsConfig(packages);
1972
1978
  try {
1973
- const { exec: exec2 } = await import("./exec-4GWHAHS3.js");
1979
+ const { exec: exec2 } = await import("./exec-G3ZRST4Z.js");
1974
1980
  await exec2("npm", ["uninstall", "--ignore-scripts", pkg], { cwd: extensionsNpmDir() });
1975
1981
  } catch (err) {
1976
1982
  logger_default.warn(
@@ -2415,7 +2421,9 @@ ${stderr}` : ""}`;
2415
2421
  return;
2416
2422
  }
2417
2423
  await this.deliverSystem(mindName, `[${schedule.id}] ${text}`, {
2418
- whileSleeping: schedule.whileSleeping,
2424
+ // Default schedule fires to "queue" while asleep so an unadorned cron
2425
+ // schedule doesn't inherit the DM wake-trigger fallback and wake the mind.
2426
+ whileSleeping: schedule.whileSleeping ?? "queue",
2419
2427
  session: schedule.session
2420
2428
  });
2421
2429
  slog2.info(`fired "${schedule.id}" for ${mindName}`);
@@ -2452,15 +2460,41 @@ ${stderr}` : ""}`;
2452
2460
  }
2453
2461
  }
2454
2462
  async runScript(script, cwd, mindName) {
2463
+ const env = await this.buildScriptEnv(mindName);
2455
2464
  if (isSandboxEnabled()) {
2456
2465
  const dir = this.mindDirs.get(mindName) ?? mindDir(mindName);
2457
2466
  const [cmd, args] = await wrapForSandbox("bash", ["-c", script], dir, mindName, [
2458
2467
  dir,
2459
2468
  mindTmpDir(dir)
2460
2469
  ]);
2461
- return exec(cmd, args, { cwd });
2470
+ return exec(cmd, args, { cwd, env });
2462
2471
  }
2463
- return exec("bash", ["-c", script], { cwd, mindName });
2472
+ return exec("bash", ["-c", script], { cwd, mindName, env });
2473
+ }
2474
+ /**
2475
+ * Build the environment for a scheduled script — mirrors the mind process env
2476
+ * (allowlisted base + merged mind env + VOLUTE_* runtime vars), authenticated
2477
+ * with the mind's own per-mind, non-admin token. Reuses the running mind's
2478
+ * token when present; otherwise mints one (stable across fires, regenerated on
2479
+ * next mind start). The daemon admin token is never included.
2480
+ */
2481
+ async buildScriptEnv(mindName) {
2482
+ const dir = this.mindDirs.get(mindName) ?? mindDir(mindName);
2483
+ const entry = await findMind(mindName);
2484
+ const token = getMindToken(mindName) ?? generateMindToken(mindName);
2485
+ const mindLocalBin = resolve5(dir, "home", ".local", "bin");
2486
+ const currentPath = process.env.PATH ?? "";
2487
+ return {
2488
+ ...buildMindBaseEnv(),
2489
+ ...loadMergedEnv(mindName),
2490
+ VOLUTE_MIND: mindName,
2491
+ VOLUTE_STATE_DIR: stateDir(mindName),
2492
+ VOLUTE_MIND_DIR: dir,
2493
+ VOLUTE_MIND_PORT: entry ? String(entry.port) : void 0,
2494
+ VOLUTE_MIND_TOKEN: token,
2495
+ TMPDIR: mindTmpDir(dir),
2496
+ PATH: `${mindLocalBin}:${currentPath}`
2497
+ };
2464
2498
  }
2465
2499
  deliverSystem(mindName, text, opts) {
2466
2500
  return sendSystemMessage(mindName, text, opts);
@@ -2771,7 +2805,7 @@ async function wakeMind(name) {
2771
2805
  async function startSpiritFull(name) {
2772
2806
  const entry = await findMind(name);
2773
2807
  if (entry?.dir) {
2774
- const { registerMindDir } = await import("./delivery-router-6DE3BPHO.js");
2808
+ const { registerMindDir } = await import("./delivery-router-N4A7ZC5T.js");
2775
2809
  registerMindDir(name, entry.dir);
2776
2810
  }
2777
2811
  await getMindManager().startMind(name);
@@ -2793,8 +2827,8 @@ async function stopSpiritFull(name) {
2793
2827
  }).catch((err) => logger_default.error("failed to publish spirit_stopped activity", logger_default.errorData(err)));
2794
2828
  }
2795
2829
  async function ensureCreatorDM(mindName, creatorUsername) {
2796
- const { getOrCreateMindUser: getOrCreateMindUser2, getUserByUsername: getUserByUsername2 } = await import("./auth-FRZJVQCS.js");
2797
- const { findDMConversation: findDMConversation2, createConversation: createConversation2 } = await import("./conversations-G4LYMHAA.js");
2830
+ const { getOrCreateMindUser: getOrCreateMindUser2, getUserByUsername: getUserByUsername2 } = await import("./auth-FLAEXXZK.js");
2831
+ const { findDMConversation: findDMConversation2, createConversation: createConversation2 } = await import("./conversations-JZTD6KLR.js");
2798
2832
  const mindUser = await getOrCreateMindUser2(mindName);
2799
2833
  const creatorUser = await getUserByUsername2(creatorUsername);
2800
2834
  if (!creatorUser) {
@@ -2827,6 +2861,8 @@ async function stopMindFull(name) {
2827
2861
 
2828
2862
  // packages/daemon/src/lib/daemon/sleep-manager.ts
2829
2863
  var slog3 = logger_default.child("sleep");
2864
+ var MAX_WAKE_ATTEMPTS = 5;
2865
+ var WAKE_BACKOFF_MS = [6e4, 5 * 6e4, 15 * 6e4, 60 * 6e4];
2830
2866
  function defaultState() {
2831
2867
  return {
2832
2868
  sleeping: false,
@@ -2835,7 +2871,9 @@ function defaultState() {
2835
2871
  wokenByTrigger: false,
2836
2872
  voluntaryWakeAt: null,
2837
2873
  queuedMessageCount: 0,
2838
- triggerWakeHistory: []
2874
+ triggerWakeHistory: [],
2875
+ wakeFailures: 0,
2876
+ nextWakeAttemptAt: null
2839
2877
  };
2840
2878
  }
2841
2879
  function formatCurrentDate() {
@@ -2905,6 +2943,8 @@ var SleepManager = class {
2905
2943
  const data = JSON.parse(readFileSync7(this.statePath, "utf-8"));
2906
2944
  for (const [name, state2] of Object.entries(data)) {
2907
2945
  state2.triggerWakeHistory ??= [];
2946
+ state2.wakeFailures ??= 0;
2947
+ state2.nextWakeAttemptAt ??= null;
2908
2948
  this.states.set(name, state2);
2909
2949
  }
2910
2950
  }
@@ -3061,12 +3101,13 @@ var SleepManager = class {
3061
3101
  try {
3062
3102
  await wakeMind(name);
3063
3103
  } catch (err) {
3064
- slog3.error(`failed to wake ${name}`, logger_default.errorData(err));
3104
+ await this.handleWakeFailure(name, err);
3065
3105
  return;
3066
3106
  }
3067
3107
  }
3068
3108
  const entry = await findMind(name);
3069
3109
  if (!entry) return;
3110
+ this.resetWakeBackoff(name);
3070
3111
  if (opts?.trigger) {
3071
3112
  state2.wokenByTrigger = true;
3072
3113
  state2.triggerWakeHistory.push({
@@ -3140,8 +3181,7 @@ var SleepManager = class {
3140
3181
  checkWakeTrigger(name, payload) {
3141
3182
  const config = this.getSleepConfig(name);
3142
3183
  const triggers = config?.wakeTriggers;
3143
- const mentionsEnabled = triggers?.mentions !== false;
3144
- const dmsEnabled = triggers?.dms !== false;
3184
+ const { mentions: mentionsEnabled, dms: dmsEnabled } = resolveWakeTriggers(triggers);
3145
3185
  if (dmsEnabled && payload.isDM) return true;
3146
3186
  if (mentionsEnabled && payload.content) {
3147
3187
  const text = typeof payload.content === "string" ? payload.content : Array.isArray(payload.content) ? payload.content.filter((b) => b.type === "text" && b.text).map((b) => b.text).join(" ") : "";
@@ -3186,29 +3226,110 @@ var SleepManager = class {
3186
3226
  const db = await getDb();
3187
3227
  const rows = await db.select().from(deliveryQueue).where(and2(eq3(deliveryQueue.mind, name), eq3(deliveryQueue.status, "sleep-queued"))).all();
3188
3228
  if (rows.length === 0) return 0;
3189
- const { deliverMessage: deliverMessage2 } = await import("./message-delivery-IUAXRPFE.js");
3190
- const delivered = [];
3229
+ let deliveredCount = 0;
3191
3230
  for (const row of rows) {
3231
+ let payload;
3192
3232
  try {
3193
- await deliverMessage2(name, JSON.parse(row.payload));
3194
- delivered.push(row.id);
3233
+ payload = JSON.parse(row.payload);
3195
3234
  } catch (err) {
3196
- slog3.warn(`failed to flush queued message ${row.id} for ${name}`, logger_default.errorData(err));
3235
+ slog3.warn(
3236
+ `dropping unparseable queued message ${row.id} for ${name}`,
3237
+ logger_default.errorData(err)
3238
+ );
3239
+ await db.delete(deliveryQueue).where(eq3(deliveryQueue.id, row.id));
3240
+ continue;
3197
3241
  }
3198
- }
3199
- if (delivered.length > 0) {
3200
- await db.delete(deliveryQueue).where(inArray(deliveryQueue.id, delivered));
3242
+ const delivered = await this.deliverQueued(name, payload);
3243
+ if (!delivered) {
3244
+ slog3.warn(`failed to flush queued message ${row.id} for ${name}; leaving it queued`);
3245
+ break;
3246
+ }
3247
+ await db.delete(deliveryQueue).where(eq3(deliveryQueue.id, row.id));
3248
+ deliveredCount++;
3201
3249
  }
3202
3250
  const state2 = this.states.get(name);
3203
3251
  if (state2) {
3204
- state2.queuedMessageCount = Math.max(0, state2.queuedMessageCount - delivered.length);
3252
+ state2.queuedMessageCount = Math.max(0, state2.queuedMessageCount - deliveredCount);
3205
3253
  }
3206
- return delivered.length;
3254
+ return deliveredCount;
3207
3255
  } catch (err) {
3208
3256
  slog3.warn(`failed to flush queued messages for ${name}`, logger_default.errorData(err));
3209
3257
  return 0;
3210
3258
  }
3211
3259
  }
3260
+ /**
3261
+ * Deliver one queued message on the wake path. `isFlush` skips the queue-time inbound
3262
+ * recording (kept once, at arrival) and bypasses the sleep re-queue so the now-waking mind
3263
+ * actually receives it. Returns whether the message was handed off to the delivery manager.
3264
+ * Extracted as a seam so the flush loop can be unit-tested without the real delivery stack.
3265
+ */
3266
+ async deliverQueued(name, payload) {
3267
+ const { deliverMessage: deliverMessage2 } = await import("./message-delivery-3C65ZI6L.js");
3268
+ return deliverMessage2(name, payload, { isFlush: true });
3269
+ }
3270
+ // --- Wake failure handling (#419) ---
3271
+ /**
3272
+ * Record a failed wake attempt. Applies exponential backoff between retries and,
3273
+ * after MAX_WAKE_ATTEMPTS consecutive failures, gives up and marks the mind errored
3274
+ * rather than retrying every tick forever.
3275
+ */
3276
+ async handleWakeFailure(name, err) {
3277
+ const state2 = this.states.get(name);
3278
+ if (!state2?.sleeping) return;
3279
+ state2.wakeFailures = (state2.wakeFailures ?? 0) + 1;
3280
+ if (state2.wakeFailures >= MAX_WAKE_ATTEMPTS) {
3281
+ await this.markWakeErrored(name, err);
3282
+ return;
3283
+ }
3284
+ const backoff = WAKE_BACKOFF_MS[Math.min(state2.wakeFailures - 1, WAKE_BACKOFF_MS.length - 1)];
3285
+ state2.nextWakeAttemptAt = new Date(Date.now() + backoff).toISOString();
3286
+ this.saveState();
3287
+ slog3.warn(
3288
+ `failed to wake ${name} (attempt ${state2.wakeFailures}/${MAX_WAKE_ATTEMPTS}); retrying in ${Math.round(backoff / 6e4)}m`,
3289
+ logger_default.errorData(err)
3290
+ );
3291
+ }
3292
+ /** Clear wake-failure backoff after a successful wake. */
3293
+ resetWakeBackoff(name) {
3294
+ const state2 = this.states.get(name);
3295
+ if (!state2) return;
3296
+ if (state2.wakeFailures === 0 && state2.nextWakeAttemptAt === null) return;
3297
+ state2.wakeFailures = 0;
3298
+ state2.nextWakeAttemptAt = null;
3299
+ this.saveState();
3300
+ }
3301
+ /**
3302
+ * A mind has failed to wake too many times — it isn't asleep, it's broken.
3303
+ * Clear the sleep state (so the tick stops retrying) and surface the failure
3304
+ * loudly: error log, activity event for the web UI, and a mind notice so the
3305
+ * mind learns what happened on its next successful session. A manual
3306
+ * `volute clock wake` / `volute mind start` is the recovery path.
3307
+ */
3308
+ async markWakeErrored(name, err) {
3309
+ const detail = err instanceof Error ? err.message : String(err);
3310
+ this.markAwake(name);
3311
+ slog3.error(
3312
+ `${name} failed to wake ${MAX_WAKE_ATTEMPTS} times in a row; giving up and marking errored`,
3313
+ logger_default.errorData(err)
3314
+ );
3315
+ try {
3316
+ await publish({
3317
+ type: "mind_stopped",
3318
+ mind: name,
3319
+ summary: `${name} failed to wake after ${MAX_WAKE_ATTEMPTS} attempts`,
3320
+ metadata: { error: "wake_failed", detail }
3321
+ });
3322
+ } catch (e) {
3323
+ slog3.error(`failed to publish wake-failure activity for ${name}`, logger_default.errorData(e));
3324
+ }
3325
+ await recordNotice({
3326
+ mind: name,
3327
+ session: MIND_LEVEL_SESSION,
3328
+ kind: "startup",
3329
+ reason: "startup_failed",
3330
+ detail: `Wake failed ${MAX_WAKE_ATTEMPTS} times in a row and was given up. Last error: ${detail}`
3331
+ });
3332
+ }
3212
3333
  // --- Internal methods ---
3213
3334
  markSleeping(name, opts) {
3214
3335
  const sleepConfig = this.getSleepConfig(name);
@@ -3219,7 +3340,9 @@ var SleepManager = class {
3219
3340
  wokenByTrigger: false,
3220
3341
  voluntaryWakeAt: opts?.voluntaryWakeAt ?? null,
3221
3342
  queuedMessageCount: this.states.get(name)?.queuedMessageCount ?? 0,
3222
- triggerWakeHistory: []
3343
+ triggerWakeHistory: [],
3344
+ wakeFailures: 0,
3345
+ nextWakeAttemptAt: null
3223
3346
  };
3224
3347
  this.states.set(name, state2);
3225
3348
  this.saveState();
@@ -3250,6 +3373,9 @@ var SleepManager = class {
3250
3373
  const config = this.getSleepConfig(name);
3251
3374
  if (!config?.enabled || !config.schedule) continue;
3252
3375
  const state2 = this.states.get(name);
3376
+ if (state2?.sleeping && state2.nextWakeAttemptAt && now < new Date(state2.nextWakeAttemptAt)) {
3377
+ continue;
3378
+ }
3253
3379
  if (state2?.sleeping && state2.voluntaryWakeAt) {
3254
3380
  const wakeAt = new Date(state2.voluntaryWakeAt);
3255
3381
  if (now >= wakeAt) {
@@ -3507,7 +3633,7 @@ function getSleepManagerIfReady() {
3507
3633
  // packages/daemon/src/lib/delivery/delivery-manager.ts
3508
3634
  import { readFile, realpath } from "fs/promises";
3509
3635
  import { extname, resolve as resolve8 } from "path";
3510
- import { and as and4, eq as eq5, inArray as inArray2, sql as sql2 } from "drizzle-orm";
3636
+ import { and as and4, eq as eq5, inArray, sql as sql2 } from "drizzle-orm";
3511
3637
 
3512
3638
  // packages/daemon/src/lib/chat/typing.ts
3513
3639
  var DEFAULT_TTL_MS = 1e4;
@@ -3904,7 +4030,7 @@ var DeliveryManager = class {
3904
4030
  if (promote.length === 0) return;
3905
4031
  try {
3906
4032
  const db = await getDb();
3907
- await db.update(deliveryQueue).set({ status: "pending", attempts: 0, next_attempt_at: null }).where(inArray2(deliveryQueue.id, promote));
4033
+ await db.update(deliveryQueue).set({ status: "pending", attempts: 0, next_attempt_at: null }).where(inArray(deliveryQueue.id, promote));
3908
4034
  dlog.info(`released ${promote.length} gated message(s) for ${baseName} after route change`);
3909
4035
  } catch (err) {
3910
4036
  dlog.warn(`failed to promote gated rows for ${baseName}`, logger_default.errorData(err));
@@ -4069,7 +4195,7 @@ var DeliveryManager = class {
4069
4195
  if (valid.length === 0) return;
4070
4196
  try {
4071
4197
  const db = await getDb();
4072
- await db.delete(deliveryQueue).where(inArray2(deliveryQueue.id, valid));
4198
+ await db.delete(deliveryQueue).where(inArray(deliveryQueue.id, valid));
4073
4199
  } catch (err) {
4074
4200
  dlog.warn("failed to delete delivered delivery queue rows", logger_default.errorData(err));
4075
4201
  }
@@ -4080,7 +4206,7 @@ var DeliveryManager = class {
4080
4206
  if (valid.length === 0) return;
4081
4207
  try {
4082
4208
  const db = await getDb();
4083
- const rows = await db.select({ id: deliveryQueue.id, attempts: deliveryQueue.attempts }).from(deliveryQueue).where(inArray2(deliveryQueue.id, valid));
4209
+ const rows = await db.select({ id: deliveryQueue.id, attempts: deliveryQueue.attempts }).from(deliveryQueue).where(inArray(deliveryQueue.id, valid));
4084
4210
  for (const row of rows) {
4085
4211
  const attempts = row.attempts + 1;
4086
4212
  const backoffSec = Math.round(
@@ -4343,7 +4469,7 @@ var DeliveryManager = class {
4343
4469
  `To start hearing this channel, add a routing rule for "${channel}" to .config/routes.json.`,
4344
4470
  `Messages are held safely until you do \u2014 nothing is lost. If you'd rather not engage, just leave it unrouted.`
4345
4471
  ].filter((line) => line !== null).join("\n");
4346
- const { sendSystemMessage: sendSystemMessage2 } = await import("./system-chat-E65T2HSW.js");
4472
+ const { sendSystemMessage: sendSystemMessage2 } = await import("./system-chat-XOALE2RD.js");
4347
4473
  await sendSystemMessage2(mindName, notification);
4348
4474
  }
4349
4475
  /**
@@ -4517,6 +4643,9 @@ var DeliveryManager = class {
4517
4643
  const buffer = this.batchBuffers.get(bufferKey);
4518
4644
  if (buffer && buffer.messages.length > 0) {
4519
4645
  this.scheduleBatchTimers(mind, session, bufferKey);
4646
+ } else if (session.startsWith("new-")) {
4647
+ mindSessions.delete(session);
4648
+ if (mindSessions.size === 0) this.sessionStates.delete(mind);
4520
4649
  }
4521
4650
  }
4522
4651
  }
@@ -4632,8 +4761,8 @@ async function linkToolResultToTurn(mind, turnId, toolResultContent, toolUseEven
4632
4761
  await db.update(activity).set({
4633
4762
  turn_id: turnId,
4634
4763
  ...toolUseEventId != null ? { source_event_id: toolUseEventId } : {}
4635
- }).where(inArray3(activity.id, activityIds));
4636
- const actRows = await db.select().from(activity).where(inArray3(activity.id, activityIds));
4764
+ }).where(inArray2(activity.id, activityIds));
4765
+ const actRows = await db.select().from(activity).where(inArray2(activity.id, activityIds));
4637
4766
  if (actRows.length > 0) {
4638
4767
  await db.insert(mindHistory).values(
4639
4768
  actRows.map((a) => ({
@@ -4657,40 +4786,44 @@ function resolveSleepAction(sleepBehavior, wokenByTrigger, wakeTriggerMatches) {
4657
4786
  if (!sleepBehavior && wakeTriggerMatches) return "queue-and-wake";
4658
4787
  return "queue";
4659
4788
  }
4660
- async function deliverMessage(mindName, payload) {
4789
+ async function deliverMessage(mindName, payload, opts = {}) {
4661
4790
  try {
4662
4791
  const baseName = await getBaseName(mindName);
4663
4792
  const entry = await findMind(baseName);
4664
4793
  if (!entry) {
4665
4794
  dlog2.warn(`cannot deliver to ${mindName}: mind not found`);
4666
- return;
4795
+ return false;
4667
4796
  }
4668
- const textContent = extractTextContent(payload.content);
4669
- await recordInbound(baseName, payload.channel, payload.sender ?? null, textContent);
4670
- const sleepManager = getSleepManagerIfReady();
4671
- if (sleepManager?.isSleeping(baseName)) {
4672
- const sleepState = sleepManager.getState(baseName);
4673
- const action = resolveSleepAction(
4674
- payload.whileSleeping,
4675
- sleepState.wokenByTrigger,
4676
- sleepManager.checkWakeTrigger(baseName, payload)
4677
- );
4678
- if (action === "skip") {
4679
- dlog2.info(
4680
- `skipped delivery to ${baseName} (sleeping, whileSleeping=skip, channel=${payload.channel})`
4797
+ if (!opts.isFlush) {
4798
+ const textContent = extractTextContent(payload.content);
4799
+ await recordInbound(baseName, payload.channel, payload.sender ?? null, textContent);
4800
+ const sleepManager = getSleepManagerIfReady();
4801
+ if (sleepManager?.isSleeping(baseName)) {
4802
+ const sleepState = sleepManager.getState(baseName);
4803
+ const action = resolveSleepAction(
4804
+ payload.whileSleeping,
4805
+ sleepState.wokenByTrigger,
4806
+ sleepManager.checkWakeTrigger(baseName, payload)
4681
4807
  );
4682
- return;
4683
- }
4684
- await sleepManager.queueSleepMessage(baseName, payload);
4685
- if (action === "queue-and-wake") {
4686
- sleepManager.initiateWake(baseName, { trigger: { channel: payload.channel } }).catch((err) => dlog2.warn(`failed to trigger-wake ${baseName}`, logger_default.errorData(err)));
4808
+ if (action === "skip") {
4809
+ dlog2.info(
4810
+ `skipped delivery to ${baseName} (sleeping, whileSleeping=skip, channel=${payload.channel})`
4811
+ );
4812
+ return true;
4813
+ }
4814
+ await sleepManager.queueSleepMessage(baseName, payload);
4815
+ if (action === "queue-and-wake") {
4816
+ sleepManager.initiateWake(baseName, { trigger: { channel: payload.channel } }).catch((err) => dlog2.warn(`failed to trigger-wake ${baseName}`, logger_default.errorData(err)));
4817
+ }
4818
+ return true;
4687
4819
  }
4688
- return;
4689
4820
  }
4690
4821
  const manager = getDeliveryManager();
4691
4822
  await manager.routeAndDeliver(mindName, payload);
4823
+ return true;
4692
4824
  } catch (err) {
4693
4825
  dlog2.warn(`unexpected error delivering to ${mindName}`, logger_default.errorData(err));
4826
+ return false;
4694
4827
  }
4695
4828
  }
4696
4829
 
@@ -4700,6 +4833,12 @@ var dmCache = /* @__PURE__ */ new Map();
4700
4833
  function resetSystemDMCache() {
4701
4834
  dmCache.clear();
4702
4835
  }
4836
+ function shouldGenerateSystemFallback(opts) {
4837
+ const { senderIsMind, hasMessage, convType, participants, replyTarget } = opts;
4838
+ if (!senderIsMind || !hasMessage) return false;
4839
+ if (isSpiritName(replyTarget)) return false;
4840
+ return convType === "dm" && participants.length === 2 && participants.some((p) => p.userType === "system");
4841
+ }
4703
4842
  async function ensureSystemDM(mindName) {
4704
4843
  const cached = dmCache.get(mindName);
4705
4844
  if (cached) return { conversationId: cached };
@@ -4720,7 +4859,7 @@ async function ensureSystemDM(mindName) {
4720
4859
  return { conversationId: conv.id };
4721
4860
  }
4722
4861
  async function sendSystemMessage(mindName, text, opts) {
4723
- const isSpirit = mindName === "volute";
4862
+ const isSpirit = isSpiritName(mindName);
4724
4863
  let conversationId;
4725
4864
  if (!isSpirit) {
4726
4865
  const dm = await ensureSystemDM(mindName);
@@ -4745,27 +4884,20 @@ async function sendSystemMessageDirect(mindName, text) {
4745
4884
  await recordInbound(mindName, "@volute", "volute", text);
4746
4885
  return { conversationId };
4747
4886
  }
4748
- async function isSpiritAvailable() {
4749
- const spiritEntry = await findMind("volute");
4750
- return !!(spiritEntry?.running && spiritEntry.mindType === "spirit");
4751
- }
4752
- async function generateSystemReply(conversationId, mindName, message) {
4753
- if (await isSpiritAvailable()) {
4754
- try {
4755
- await deliverMessage("volute", {
4756
- content: [{ type: "text", text: message }],
4757
- channel: `@${mindName}`,
4758
- conversationId,
4759
- sender: mindName,
4760
- isDM: true,
4761
- participants: ["volute", mindName],
4762
- participantCount: 2
4763
- });
4764
- return;
4765
- } catch (err) {
4766
- slog4.warn(`failed to route to spirit, falling back to aiCompleteUtility`, logger_default.errorData(err));
4767
- }
4887
+ async function spiritWillHandle() {
4888
+ const spiritEntry = await findMind(SPIRIT_NAME);
4889
+ if (spiritEntry?.running && spiritEntry.mindType === "spirit") return true;
4890
+ try {
4891
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-HCUH4PM4.js");
4892
+ const sm = getSleepManagerIfReady2();
4893
+ if (sm?.isSleeping(SPIRIT_NAME)) return true;
4894
+ } catch (err) {
4895
+ slog4.debug("could not check spirit sleep state", logger_default.errorData(err));
4768
4896
  }
4897
+ return false;
4898
+ }
4899
+ async function generateSystemFallbackReply(conversationId, mindName, message) {
4900
+ if (await spiritWillHandle()) return;
4769
4901
  const entry = await findMind(mindName);
4770
4902
  const dir = mindDir(mindName);
4771
4903
  const config = readVoluteConfig(dir);
@@ -4784,7 +4916,7 @@ async function generateSystemReply(conversationId, mindName, message) {
4784
4916
  if (config.sleep.schedule?.wake) contextParts.push(`Wake cron: ${config.sleep.schedule.wake}`);
4785
4917
  }
4786
4918
  try {
4787
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-YAHDOIU5.js");
4919
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-HCUH4PM4.js");
4788
4920
  const sm = getSleepManagerIfReady2();
4789
4921
  if (sm) {
4790
4922
  const state2 = sm.getState(mindName);
@@ -4830,10 +4962,11 @@ async function generateSystemReply(conversationId, mindName, message) {
4830
4962
 
4831
4963
  export {
4832
4964
  resetSystemDMCache,
4965
+ shouldGenerateSystemFallback,
4833
4966
  ensureSystemDM,
4834
4967
  sendSystemMessage,
4835
4968
  sendSystemMessageDirect,
4836
- generateSystemReply,
4969
+ generateSystemFallbackReply,
4837
4970
  checkHealth,
4838
4971
  RotatingLog,
4839
4972
  syncProviderToMinds,