volute 0.44.0 → 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 (130) hide show
  1. package/dist/{activity-events-OMCSM7XW.js → activity-events-BUR2RYTJ.js} +2 -2
  2. package/dist/{ai-service-7PHSRDMO.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-TST5C2YN.js +381 -0
  7. package/dist/{chat-I4EDDIQC.js → chat-76MRWWQX.js} +2 -2
  8. package/dist/{chunk-5AHYQHNT.js → chunk-4JOQZPCE.js} +3 -2
  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-I2Y2YHIO.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-PNV24SEP.js → chunk-F7H6ATM4.js} +14 -2
  18. package/dist/{chunk-7FT24JBQ.js → chunk-FR6S2KNN.js} +10 -10
  19. package/dist/chunk-FXORA5JG.js +327 -0
  20. package/dist/{chunk-3ERIL673.js → chunk-H2X54EHC.js} +1 -1
  21. package/dist/{chunk-J47TIYHA.js → chunk-IHKGWTGT.js} +1 -1
  22. package/dist/{chunk-3GYVW2X3.js → chunk-JZ62SYWR.js} +1 -1
  23. package/dist/{chunk-EQZD4YU3.js → chunk-NYC5U6K7.js} +9 -9
  24. package/dist/{chunk-FZMEVCWC.js → chunk-OP4JG2BS.js} +9 -6
  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-7KWANSLX.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-NF75HPNR.js → chunk-UYLGEDVD.js} +40 -15
  34. package/dist/{chunk-OMETFX5H.js → chunk-YT7PEESJ.js} +261 -102
  35. package/dist/{chunk-5GT7URXJ.js → chunk-ZDZISKCL.js} +1 -1
  36. package/dist/cli.js +21 -15
  37. package/dist/{cloud-sync-6WJ7WFYD.js → cloud-sync-3OJOPZRT.js} +16 -16
  38. package/dist/{conversations-WAJYZKOC.js → conversations-JZTD6KLR.js} +3 -3
  39. package/dist/{create-YZIV5OJC.js → create-IOV4GCRX.js} +1 -1
  40. package/dist/{daemon-restart-OUJVINXV.js → daemon-restart-TIHKVVVA.js} +6 -6
  41. package/dist/daemon.js +562 -321
  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-GODSIS62.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-3QNDGW5E.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-MI7Z6KM2.js → mind-DWB6XYCH.js} +7 -7
  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-5B4XDTJH.js → package-A7KXT67Q.js} +1 -1
  61. package/dist/{registry-NUJAGLQJ.js → registry-OUZBZ3Q4.js} +5 -1
  62. package/dist/{sandbox-IOPWWILM.js → sandbox-ZJ7QA6Z6.js} +3 -3
  63. package/dist/scheduler-P6DXLLXI.js +30 -0
  64. package/dist/{seed-UUPVQWFB.js → seed-HCP5NYKH.js} +1 -1
  65. package/dist/{seed-cmd-7XPKDQUC.js → seed-cmd-ILOGNIFB.js} +2 -2
  66. package/dist/{seed-create-IZ4M5ESO.js → seed-create-OURS2LS4.js} +3 -3
  67. package/dist/{seed-sprout-POJXHJWV.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-5GCO3CUB.js → setup-DQM5VPY4.js} +6 -6
  72. package/dist/{setup-MLRKNP7Y.js → setup-Y2EJI3GG.js} +2 -2
  73. package/dist/skills/imagegen/scripts/imagegen.ts +100 -40
  74. package/dist/skills/memory/SKILL.md +2 -1
  75. package/dist/skills/orientation/SKILL.md +1 -1
  76. package/dist/skills/plan-coordinator/SKILL.md +2 -2
  77. package/dist/skills/volute-admin/SKILL.md +9 -6
  78. package/dist/skills/volute-mind/SKILL.md +20 -17
  79. package/dist/skills/volute-mind/references/extensions.md +5 -3
  80. package/dist/skills/volute-mind/references/integrations.md +2 -2
  81. package/dist/skills/volute-mind/references/routing.md +19 -23
  82. package/dist/skills/volute-mind/references/variants.md +10 -8
  83. package/dist/{skills-775C7QFF.js → skills-BKINFL7W.js} +4 -4
  84. package/dist/sleep-manager-HCUH4PM4.js +34 -0
  85. package/dist/{spirit-IK5OYWMV.js → spirit-DBTIW7P4.js} +7 -7
  86. package/dist/{sprout-ZY5QGOEL.js → sprout-W3KC7UGK.js} +1 -1
  87. package/dist/{src-XFY3XDD6.js → src-OJHARHTF.js} +28 -17
  88. package/dist/{status-E5DQNCAO.js → status-SWRVGXBI.js} +4 -4
  89. package/dist/system-chat-XOALE2RD.js +36 -0
  90. package/dist/{tailscale-QAWVKQKU.js → tailscale-3XOKOW7T.js} +1 -1
  91. package/dist/{turn-tracker-RWZMY7M6.js → turn-tracker-2W6Q2NSR.js} +6 -4
  92. package/dist/{up-FLCDR2UB.js → up-FTK7CTO3.js} +5 -5
  93. package/dist/{update-7TYVH5FM.js → update-SWAYZRWJ.js} +4 -4
  94. package/dist/{update-check-VSCSJ7KX.js → update-check-PQ2MZ47K.js} +2 -2
  95. package/dist/{upgrade-37DAGF6H.js → upgrade-NWAZ7MLD.js} +1 -1
  96. package/dist/{version-notify-B67N35A3.js → version-notify-3XMAIQTE.js} +17 -17
  97. package/dist/{volute-config-TS62GS6A.js → volute-config-A52FHWKM.js} +5 -1
  98. package/dist/web-assets/assets/{index-dJdNzAPn.css → index-B1JBQv2m.css} +1 -1
  99. package/dist/web-assets/assets/index-Bc2m8gRC.js +86 -0
  100. package/dist/web-assets/index.html +2 -2
  101. package/package.json +1 -1
  102. package/packages/extensions/plan/skills/plan/SKILL.md +1 -1
  103. package/templates/_base/.init/.config/prompts.json +1 -1
  104. package/templates/_base/.init/.local/hooks/startup-context.ts +0 -8
  105. package/templates/_base/.init/MEMORY.md +2 -1
  106. package/templates/_base/home/VOLUTE.md +6 -9
  107. package/templates/_base/src/lib/file-handler.ts +3 -3
  108. package/templates/_base/src/lib/router.ts +21 -25
  109. package/templates/_base/src/lib/startup.ts +3 -1
  110. package/templates/_base/src/lib/types.ts +2 -2
  111. package/templates/claude/.init/CLAUDE.md +1 -1
  112. package/templates/claude/src/agent.ts +97 -12
  113. package/templates/claude/src/lib/message-channel.ts +23 -0
  114. package/templates/claude/src/lib/session-reaper.ts +37 -0
  115. package/templates/claude/src/lib/stream-consumer.ts +7 -4
  116. package/templates/claude/src/server.ts +1 -0
  117. package/templates/codex/.init/AGENTS.md +4 -2
  118. package/templates/codex/src/agent.ts +31 -13
  119. package/templates/pi/.init/MINDS.md +4 -2
  120. package/templates/pi/src/agent.ts +18 -7
  121. package/templates/pi/src/lib/event-handler.ts +6 -2
  122. package/dist/delivery-manager-TEVOHX5W.js +0 -32
  123. package/dist/echo-text-T7ZA5EYD.js +0 -32
  124. package/dist/message-delivery-2EA5LLMD.js +0 -34
  125. package/dist/mind-service-S3SY7HAM.js +0 -36
  126. package/dist/scheduler-CXWQ6Z4E.js +0 -30
  127. package/dist/sleep-manager-HINI6Y46.js +0 -34
  128. package/dist/system-chat-4DQZOEZI.js +0 -34
  129. package/dist/web-assets/assets/index-Domoz5Cg.js +0 -79
  130. package/templates/_base/.init/.config/hooks/pre-prompt/session-activity.ts +0 -40
@@ -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-7FT24JBQ.js";
7
+ } from "./chunk-FR6S2KNN.js";
8
8
  import {
9
9
  hashSkillDir,
10
10
  importSkillFromDir,
11
11
  removeSharedSkill,
12
12
  sharedSkillsDir
13
- } from "./chunk-I2Y2YHIO.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-FZMEVCWC.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-3GYVW2X3.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-5AHYQHNT.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-NF75HPNR.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-J47TIYHA.js";
62
+ } from "./chunk-IHKGWTGT.js";
62
63
  import {
63
64
  readGlobalConfig,
64
65
  writeGlobalConfig
65
- } from "./chunk-PNV24SEP.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";
@@ -385,7 +391,8 @@ async function clearDeliveredNotices(mind, session, uptoId) {
385
391
  var NOTICE_HEADER = "[Notices] While you were unavailable, one or more turns failed. You're back now:";
386
392
  function formatNotices(notices) {
387
393
  if (notices.length === 0) return null;
388
- const failures = notices.filter((n) => n.kind !== "extension");
394
+ const failures = notices.filter((n) => n.kind !== "extension" && n.kind !== "budget");
395
+ const budgets = notices.filter((n) => n.kind === "budget");
389
396
  const extensions = notices.filter((n) => n.kind === "extension");
390
397
  const blocks = [];
391
398
  if (failures.length > 0) {
@@ -407,6 +414,11 @@ function formatNotices(notices) {
407
414
  return `- ${g.count} ${plural} failed (${span}): ${g.detail}`;
408
415
  });
409
416
  blocks.push(`${NOTICE_HEADER}
417
+ ${lines.join("\n")}`);
418
+ }
419
+ if (budgets.length > 0) {
420
+ const lines = budgets.map((n) => `- ${localHM(n.created_at)} ${n.detail}`);
421
+ blocks.push(`[Budget]
410
422
  ${lines.join("\n")}`);
411
423
  }
412
424
  if (extensions.length > 0) {
@@ -887,6 +899,8 @@ var MindManager = class {
887
899
  const parts = [];
888
900
  if (context.type === "merge" || context.type === "merged") {
889
901
  parts.push(await getPrompt("merge_message", { name: String(context.name ?? "") }));
902
+ } else if (context.type === "split") {
903
+ parts.push(await getPrompt("split_message", { name, parent: String(context.parent ?? "") }));
890
904
  } else if (context.type === "sprouted") {
891
905
  parts.push(await getPrompt("sprout_message"));
892
906
  } else if (context.type === "upgraded") {
@@ -931,7 +945,7 @@ var MindManager = class {
931
945
  if (this.shuttingDown || this.stopping.has(name)) return;
932
946
  mlog.error(`mind ${name} exited with code ${code}`);
933
947
  try {
934
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-HINI6Y46.js");
948
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-HCUH4PM4.js");
935
949
  const sleepMgr = getSleepManagerIfReady2();
936
950
  const sleepState = sleepMgr?.getState(name);
937
951
  if (sleepState?.sleeping) {
@@ -961,15 +975,15 @@ var MindManager = class {
961
975
  (err) => mlog.warn(`failed to clear turn state for ${name} after crash`, logger_default.errorData(err))
962
976
  );
963
977
  try {
964
- const { getDeliveryManager: getDeliveryManager2 } = await import("./delivery-manager-TEVOHX5W.js");
978
+ const { getDeliveryManager: getDeliveryManager2 } = await import("./delivery-manager-VDVKVA46.js");
965
979
  getDeliveryManager2().clearMindSessions(name);
966
980
  } catch (err) {
967
981
  if (!(err instanceof Error && err.message.includes("not initialized"))) {
968
982
  mlog.warn(`failed to clear delivery state for ${name} after crash`, logger_default.errorData(err));
969
983
  }
970
984
  }
971
- 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)));
972
- 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(
973
987
  ({ publish: publish4 }) => publish4({ type: "mind_stopped", mind: name, summary: `${name} crashed (exit ${code})` })
974
988
  ).catch((err) => mlog.warn(`failed to publish crash event for ${name}`, logger_default.errorData(err)));
975
989
  const { shouldRestart, delay, attempt } = this.restartTracker.recordCrash(name);
@@ -1027,7 +1041,7 @@ var MindManager = class {
1027
1041
  mlog.warn(`failed to clear turn state for ${name} on stop`, logger_default.errorData(err));
1028
1042
  }
1029
1043
  try {
1030
- const { getDeliveryManager: getDeliveryManager2 } = await import("./delivery-manager-TEVOHX5W.js");
1044
+ const { getDeliveryManager: getDeliveryManager2 } = await import("./delivery-manager-VDVKVA46.js");
1031
1045
  getDeliveryManager2().clearMindSessions(name);
1032
1046
  } catch (err) {
1033
1047
  if (!(err instanceof Error && err.message.includes("not initialized"))) {
@@ -1035,7 +1049,7 @@ var MindManager = class {
1035
1049
  }
1036
1050
  }
1037
1051
  try {
1038
- const { clearEchoTextCache } = await import("./echo-text-T7ZA5EYD.js");
1052
+ const { clearEchoTextCache } = await import("./echo-text-6JJOPUD4.js");
1039
1053
  clearEchoTextCache(name);
1040
1054
  } catch (err) {
1041
1055
  mlog.debug(`failed to clear echo-text cache for ${name}`, logger_default.errorData(err));
@@ -1691,7 +1705,7 @@ function resolveSkillsDir(manifest) {
1691
1705
  async function discoverBuiltinExtensions(disabledIds) {
1692
1706
  const builtins = [
1693
1707
  { id: "notes", load: async () => (await import("./src-YH6PQ7LJ.js")).default },
1694
- { id: "pages", load: async () => (await import("./src-XFY3XDD6.js")).default },
1708
+ { id: "pages", load: async () => (await import("./src-OJHARHTF.js")).default },
1695
1709
  { id: "plan", load: async () => (await import("./src-JAOVXO5W.js")).default }
1696
1710
  ];
1697
1711
  const results = [];
@@ -1941,7 +1955,7 @@ async function installNpmExtension(pkg) {
1941
1955
  throw new Error(`Extension "${pkg}" is already installed`);
1942
1956
  }
1943
1957
  const dir = ensureExtensionsNpmDir();
1944
- const { exec: exec2 } = await import("./exec-4GWHAHS3.js");
1958
+ const { exec: exec2 } = await import("./exec-G3ZRST4Z.js");
1945
1959
  try {
1946
1960
  await exec2("npm", ["install", "--ignore-scripts", pkg], { cwd: dir });
1947
1961
  } catch (err) {
@@ -1962,7 +1976,7 @@ async function uninstallNpmExtension(pkg) {
1962
1976
  packages.splice(idx, 1);
1963
1977
  writeExtensionsConfig(packages);
1964
1978
  try {
1965
- const { exec: exec2 } = await import("./exec-4GWHAHS3.js");
1979
+ const { exec: exec2 } = await import("./exec-G3ZRST4Z.js");
1966
1980
  await exec2("npm", ["uninstall", "--ignore-scripts", pkg], { cwd: extensionsNpmDir() });
1967
1981
  } catch (err) {
1968
1982
  logger_default.warn(
@@ -2407,7 +2421,9 @@ ${stderr}` : ""}`;
2407
2421
  return;
2408
2422
  }
2409
2423
  await this.deliverSystem(mindName, `[${schedule.id}] ${text}`, {
2410
- 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",
2411
2427
  session: schedule.session
2412
2428
  });
2413
2429
  slog2.info(`fired "${schedule.id}" for ${mindName}`);
@@ -2444,15 +2460,41 @@ ${stderr}` : ""}`;
2444
2460
  }
2445
2461
  }
2446
2462
  async runScript(script, cwd, mindName) {
2463
+ const env = await this.buildScriptEnv(mindName);
2447
2464
  if (isSandboxEnabled()) {
2448
2465
  const dir = this.mindDirs.get(mindName) ?? mindDir(mindName);
2449
2466
  const [cmd, args] = await wrapForSandbox("bash", ["-c", script], dir, mindName, [
2450
2467
  dir,
2451
2468
  mindTmpDir(dir)
2452
2469
  ]);
2453
- return exec(cmd, args, { cwd });
2470
+ return exec(cmd, args, { cwd, env });
2454
2471
  }
2455
- 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
+ };
2456
2498
  }
2457
2499
  deliverSystem(mindName, text, opts) {
2458
2500
  return sendSystemMessage(mindName, text, opts);
@@ -2763,7 +2805,7 @@ async function wakeMind(name) {
2763
2805
  async function startSpiritFull(name) {
2764
2806
  const entry = await findMind(name);
2765
2807
  if (entry?.dir) {
2766
- const { registerMindDir } = await import("./delivery-router-GODSIS62.js");
2808
+ const { registerMindDir } = await import("./delivery-router-N4A7ZC5T.js");
2767
2809
  registerMindDir(name, entry.dir);
2768
2810
  }
2769
2811
  await getMindManager().startMind(name);
@@ -2785,8 +2827,8 @@ async function stopSpiritFull(name) {
2785
2827
  }).catch((err) => logger_default.error("failed to publish spirit_stopped activity", logger_default.errorData(err)));
2786
2828
  }
2787
2829
  async function ensureCreatorDM(mindName, creatorUsername) {
2788
- const { getOrCreateMindUser: getOrCreateMindUser2, getUserByUsername: getUserByUsername2 } = await import("./auth-FRZJVQCS.js");
2789
- const { findDMConversation: findDMConversation2, createConversation: createConversation2 } = await import("./conversations-WAJYZKOC.js");
2830
+ const { getOrCreateMindUser: getOrCreateMindUser2, getUserByUsername: getUserByUsername2 } = await import("./auth-FLAEXXZK.js");
2831
+ const { findDMConversation: findDMConversation2, createConversation: createConversation2 } = await import("./conversations-JZTD6KLR.js");
2790
2832
  const mindUser = await getOrCreateMindUser2(mindName);
2791
2833
  const creatorUser = await getUserByUsername2(creatorUsername);
2792
2834
  if (!creatorUser) {
@@ -2819,6 +2861,8 @@ async function stopMindFull(name) {
2819
2861
 
2820
2862
  // packages/daemon/src/lib/daemon/sleep-manager.ts
2821
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];
2822
2866
  function defaultState() {
2823
2867
  return {
2824
2868
  sleeping: false,
@@ -2827,7 +2871,9 @@ function defaultState() {
2827
2871
  wokenByTrigger: false,
2828
2872
  voluntaryWakeAt: null,
2829
2873
  queuedMessageCount: 0,
2830
- triggerWakeHistory: []
2874
+ triggerWakeHistory: [],
2875
+ wakeFailures: 0,
2876
+ nextWakeAttemptAt: null
2831
2877
  };
2832
2878
  }
2833
2879
  function formatCurrentDate() {
@@ -2838,6 +2884,22 @@ function formatCurrentDate() {
2838
2884
  day: "numeric"
2839
2885
  });
2840
2886
  }
2887
+ function formatWakeTime(wakeAt) {
2888
+ if (wakeAt) {
2889
+ const date = new Date(wakeAt);
2890
+ if (!Number.isNaN(date.getTime())) {
2891
+ const time = date.toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit" });
2892
+ const day = date.toLocaleDateString("en-US", {
2893
+ weekday: "long",
2894
+ month: "long",
2895
+ day: "numeric"
2896
+ });
2897
+ return `at ${time} on ${day}`;
2898
+ }
2899
+ return `at ${wakeAt}`;
2900
+ }
2901
+ return "when something needs you \u2014 a message, a mention, or someone waking you";
2902
+ }
2841
2903
  function formatDuration(from, to) {
2842
2904
  const ms = to.getTime() - from.getTime();
2843
2905
  const hours = Math.floor(ms / 36e5);
@@ -2881,6 +2943,8 @@ var SleepManager = class {
2881
2943
  const data = JSON.parse(readFileSync7(this.statePath, "utf-8"));
2882
2944
  for (const [name, state2] of Object.entries(data)) {
2883
2945
  state2.triggerWakeHistory ??= [];
2946
+ state2.wakeFailures ??= 0;
2947
+ state2.nextWakeAttemptAt ??= null;
2884
2948
  this.states.set(name, state2);
2885
2949
  }
2886
2950
  }
@@ -2985,7 +3049,8 @@ var SleepManager = class {
2985
3049
  const entry = await findMind(name);
2986
3050
  if (!entry) return;
2987
3051
  const sleepConfig = this.getSleepConfig(name);
2988
- const wakeTime = opts?.voluntaryWakeAt ?? this.getNextWakeTime(sleepConfig) ?? "scheduled time";
3052
+ const wakeAt = opts?.voluntaryWakeAt ?? this.getNextWakeTime(sleepConfig);
3053
+ const wakeTime = formatWakeTime(wakeAt);
2989
3054
  const preSleepMsg = await getPrompt("pre_sleep", { wakeTime });
2990
3055
  let conversationId;
2991
3056
  try {
@@ -3036,12 +3101,13 @@ var SleepManager = class {
3036
3101
  try {
3037
3102
  await wakeMind(name);
3038
3103
  } catch (err) {
3039
- slog3.error(`failed to wake ${name}`, logger_default.errorData(err));
3104
+ await this.handleWakeFailure(name, err);
3040
3105
  return;
3041
3106
  }
3042
3107
  }
3043
3108
  const entry = await findMind(name);
3044
3109
  if (!entry) return;
3110
+ this.resetWakeBackoff(name);
3045
3111
  if (opts?.trigger) {
3046
3112
  state2.wokenByTrigger = true;
3047
3113
  state2.triggerWakeHistory.push({
@@ -3115,8 +3181,7 @@ var SleepManager = class {
3115
3181
  checkWakeTrigger(name, payload) {
3116
3182
  const config = this.getSleepConfig(name);
3117
3183
  const triggers = config?.wakeTriggers;
3118
- const mentionsEnabled = triggers?.mentions !== false;
3119
- const dmsEnabled = triggers?.dms !== false;
3184
+ const { mentions: mentionsEnabled, dms: dmsEnabled } = resolveWakeTriggers(triggers);
3120
3185
  if (dmsEnabled && payload.isDM) return true;
3121
3186
  if (mentionsEnabled && payload.content) {
3122
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(" ") : "";
@@ -3161,29 +3226,110 @@ var SleepManager = class {
3161
3226
  const db = await getDb();
3162
3227
  const rows = await db.select().from(deliveryQueue).where(and2(eq3(deliveryQueue.mind, name), eq3(deliveryQueue.status, "sleep-queued"))).all();
3163
3228
  if (rows.length === 0) return 0;
3164
- const { deliverMessage: deliverMessage2 } = await import("./message-delivery-2EA5LLMD.js");
3165
- const delivered = [];
3229
+ let deliveredCount = 0;
3166
3230
  for (const row of rows) {
3231
+ let payload;
3167
3232
  try {
3168
- await deliverMessage2(name, JSON.parse(row.payload));
3169
- delivered.push(row.id);
3233
+ payload = JSON.parse(row.payload);
3170
3234
  } catch (err) {
3171
- 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;
3172
3241
  }
3173
- }
3174
- if (delivered.length > 0) {
3175
- 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++;
3176
3249
  }
3177
3250
  const state2 = this.states.get(name);
3178
3251
  if (state2) {
3179
- state2.queuedMessageCount = Math.max(0, state2.queuedMessageCount - delivered.length);
3252
+ state2.queuedMessageCount = Math.max(0, state2.queuedMessageCount - deliveredCount);
3180
3253
  }
3181
- return delivered.length;
3254
+ return deliveredCount;
3182
3255
  } catch (err) {
3183
3256
  slog3.warn(`failed to flush queued messages for ${name}`, logger_default.errorData(err));
3184
3257
  return 0;
3185
3258
  }
3186
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
+ }
3187
3333
  // --- Internal methods ---
3188
3334
  markSleeping(name, opts) {
3189
3335
  const sleepConfig = this.getSleepConfig(name);
@@ -3194,7 +3340,9 @@ var SleepManager = class {
3194
3340
  wokenByTrigger: false,
3195
3341
  voluntaryWakeAt: opts?.voluntaryWakeAt ?? null,
3196
3342
  queuedMessageCount: this.states.get(name)?.queuedMessageCount ?? 0,
3197
- triggerWakeHistory: []
3343
+ triggerWakeHistory: [],
3344
+ wakeFailures: 0,
3345
+ nextWakeAttemptAt: null
3198
3346
  };
3199
3347
  this.states.set(name, state2);
3200
3348
  this.saveState();
@@ -3225,6 +3373,9 @@ var SleepManager = class {
3225
3373
  const config = this.getSleepConfig(name);
3226
3374
  if (!config?.enabled || !config.schedule) continue;
3227
3375
  const state2 = this.states.get(name);
3376
+ if (state2?.sleeping && state2.nextWakeAttemptAt && now < new Date(state2.nextWakeAttemptAt)) {
3377
+ continue;
3378
+ }
3228
3379
  if (state2?.sleeping && state2.voluntaryWakeAt) {
3229
3380
  const wakeAt = new Date(state2.voluntaryWakeAt);
3230
3381
  if (now >= wakeAt) {
@@ -3482,7 +3633,7 @@ function getSleepManagerIfReady() {
3482
3633
  // packages/daemon/src/lib/delivery/delivery-manager.ts
3483
3634
  import { readFile, realpath } from "fs/promises";
3484
3635
  import { extname, resolve as resolve8 } from "path";
3485
- 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";
3486
3637
 
3487
3638
  // packages/daemon/src/lib/chat/typing.ts
3488
3639
  var DEFAULT_TTL_MS = 1e4;
@@ -3879,7 +4030,7 @@ var DeliveryManager = class {
3879
4030
  if (promote.length === 0) return;
3880
4031
  try {
3881
4032
  const db = await getDb();
3882
- 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));
3883
4034
  dlog.info(`released ${promote.length} gated message(s) for ${baseName} after route change`);
3884
4035
  } catch (err) {
3885
4036
  dlog.warn(`failed to promote gated rows for ${baseName}`, logger_default.errorData(err));
@@ -4044,7 +4195,7 @@ var DeliveryManager = class {
4044
4195
  if (valid.length === 0) return;
4045
4196
  try {
4046
4197
  const db = await getDb();
4047
- await db.delete(deliveryQueue).where(inArray2(deliveryQueue.id, valid));
4198
+ await db.delete(deliveryQueue).where(inArray(deliveryQueue.id, valid));
4048
4199
  } catch (err) {
4049
4200
  dlog.warn("failed to delete delivered delivery queue rows", logger_default.errorData(err));
4050
4201
  }
@@ -4055,7 +4206,7 @@ var DeliveryManager = class {
4055
4206
  if (valid.length === 0) return;
4056
4207
  try {
4057
4208
  const db = await getDb();
4058
- 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));
4059
4210
  for (const row of rows) {
4060
4211
  const attempts = row.attempts + 1;
4061
4212
  const backoffSec = Math.round(
@@ -4308,16 +4459,17 @@ var DeliveryManager = class {
4308
4459
  const channel = payload.channel ?? "unknown";
4309
4460
  const notification = [
4310
4461
  `[New channel: ${channel}]`,
4462
+ `Someone new is reaching out \u2014 you don't have a route for this channel yet.`,
4311
4463
  `Sender: ${payload.sender ?? "unknown"}`,
4312
4464
  payload.platform ? `Platform: ${payload.platform}` : null,
4313
4465
  payload.participantCount ? `Participants: ${payload.participantCount}` : null,
4314
4466
  "",
4315
4467
  `Preview: ${preview}`,
4316
4468
  "",
4317
- `To accept this channel, add a routing rule for "${channel}" to your routes.json.`,
4318
- `Messages are being held until a route is configured.`
4469
+ `To start hearing this channel, add a routing rule for "${channel}" to .config/routes.json.`,
4470
+ `Messages are held safely until you do \u2014 nothing is lost. If you'd rather not engage, just leave it unrouted.`
4319
4471
  ].filter((line) => line !== null).join("\n");
4320
- const { sendSystemMessage: sendSystemMessage2 } = await import("./system-chat-4DQZOEZI.js");
4472
+ const { sendSystemMessage: sendSystemMessage2 } = await import("./system-chat-XOALE2RD.js");
4321
4473
  await sendSystemMessage2(mindName, notification);
4322
4474
  }
4323
4475
  /**
@@ -4491,6 +4643,9 @@ var DeliveryManager = class {
4491
4643
  const buffer = this.batchBuffers.get(bufferKey);
4492
4644
  if (buffer && buffer.messages.length > 0) {
4493
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);
4494
4649
  }
4495
4650
  }
4496
4651
  }
@@ -4606,8 +4761,8 @@ async function linkToolResultToTurn(mind, turnId, toolResultContent, toolUseEven
4606
4761
  await db.update(activity).set({
4607
4762
  turn_id: turnId,
4608
4763
  ...toolUseEventId != null ? { source_event_id: toolUseEventId } : {}
4609
- }).where(inArray3(activity.id, activityIds));
4610
- 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));
4611
4766
  if (actRows.length > 0) {
4612
4767
  await db.insert(mindHistory).values(
4613
4768
  actRows.map((a) => ({
@@ -4631,40 +4786,44 @@ function resolveSleepAction(sleepBehavior, wokenByTrigger, wakeTriggerMatches) {
4631
4786
  if (!sleepBehavior && wakeTriggerMatches) return "queue-and-wake";
4632
4787
  return "queue";
4633
4788
  }
4634
- async function deliverMessage(mindName, payload) {
4789
+ async function deliverMessage(mindName, payload, opts = {}) {
4635
4790
  try {
4636
4791
  const baseName = await getBaseName(mindName);
4637
4792
  const entry = await findMind(baseName);
4638
4793
  if (!entry) {
4639
4794
  dlog2.warn(`cannot deliver to ${mindName}: mind not found`);
4640
- return;
4795
+ return false;
4641
4796
  }
4642
- const textContent = extractTextContent(payload.content);
4643
- await recordInbound(baseName, payload.channel, payload.sender ?? null, textContent);
4644
- const sleepManager = getSleepManagerIfReady();
4645
- if (sleepManager?.isSleeping(baseName)) {
4646
- const sleepState = sleepManager.getState(baseName);
4647
- const action = resolveSleepAction(
4648
- payload.whileSleeping,
4649
- sleepState.wokenByTrigger,
4650
- sleepManager.checkWakeTrigger(baseName, payload)
4651
- );
4652
- if (action === "skip") {
4653
- dlog2.info(
4654
- `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)
4655
4807
  );
4656
- return;
4657
- }
4658
- await sleepManager.queueSleepMessage(baseName, payload);
4659
- if (action === "queue-and-wake") {
4660
- 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;
4661
4819
  }
4662
- return;
4663
4820
  }
4664
4821
  const manager = getDeliveryManager();
4665
4822
  await manager.routeAndDeliver(mindName, payload);
4823
+ return true;
4666
4824
  } catch (err) {
4667
4825
  dlog2.warn(`unexpected error delivering to ${mindName}`, logger_default.errorData(err));
4826
+ return false;
4668
4827
  }
4669
4828
  }
4670
4829
 
@@ -4674,6 +4833,12 @@ var dmCache = /* @__PURE__ */ new Map();
4674
4833
  function resetSystemDMCache() {
4675
4834
  dmCache.clear();
4676
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
+ }
4677
4842
  async function ensureSystemDM(mindName) {
4678
4843
  const cached = dmCache.get(mindName);
4679
4844
  if (cached) return { conversationId: cached };
@@ -4694,7 +4859,7 @@ async function ensureSystemDM(mindName) {
4694
4859
  return { conversationId: conv.id };
4695
4860
  }
4696
4861
  async function sendSystemMessage(mindName, text, opts) {
4697
- const isSpirit = mindName === "volute";
4862
+ const isSpirit = isSpiritName(mindName);
4698
4863
  let conversationId;
4699
4864
  if (!isSpirit) {
4700
4865
  const dm = await ensureSystemDM(mindName);
@@ -4719,27 +4884,20 @@ async function sendSystemMessageDirect(mindName, text) {
4719
4884
  await recordInbound(mindName, "@volute", "volute", text);
4720
4885
  return { conversationId };
4721
4886
  }
4722
- async function isSpiritAvailable() {
4723
- const spiritEntry = await findMind("volute");
4724
- return !!(spiritEntry?.running && spiritEntry.mindType === "spirit");
4725
- }
4726
- async function generateSystemReply(conversationId, mindName, message) {
4727
- if (await isSpiritAvailable()) {
4728
- try {
4729
- await deliverMessage("volute", {
4730
- content: [{ type: "text", text: message }],
4731
- channel: `@${mindName}`,
4732
- conversationId,
4733
- sender: mindName,
4734
- isDM: true,
4735
- participants: ["volute", mindName],
4736
- participantCount: 2
4737
- });
4738
- return;
4739
- } catch (err) {
4740
- slog4.warn(`failed to route to spirit, falling back to aiCompleteUtility`, logger_default.errorData(err));
4741
- }
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));
4742
4896
  }
4897
+ return false;
4898
+ }
4899
+ async function generateSystemFallbackReply(conversationId, mindName, message) {
4900
+ if (await spiritWillHandle()) return;
4743
4901
  const entry = await findMind(mindName);
4744
4902
  const dir = mindDir(mindName);
4745
4903
  const config = readVoluteConfig(dir);
@@ -4758,7 +4916,7 @@ async function generateSystemReply(conversationId, mindName, message) {
4758
4916
  if (config.sleep.schedule?.wake) contextParts.push(`Wake cron: ${config.sleep.schedule.wake}`);
4759
4917
  }
4760
4918
  try {
4761
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-HINI6Y46.js");
4919
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-HCUH4PM4.js");
4762
4920
  const sm = getSleepManagerIfReady2();
4763
4921
  if (sm) {
4764
4922
  const state2 = sm.getState(mindName);
@@ -4804,10 +4962,11 @@ async function generateSystemReply(conversationId, mindName, message) {
4804
4962
 
4805
4963
  export {
4806
4964
  resetSystemDMCache,
4965
+ shouldGenerateSystemFallback,
4807
4966
  ensureSystemDM,
4808
4967
  sendSystemMessage,
4809
4968
  sendSystemMessageDirect,
4810
- generateSystemReply,
4969
+ generateSystemFallbackReply,
4811
4970
  checkHealth,
4812
4971
  RotatingLog,
4813
4972
  syncProviderToMinds,