volute 0.48.0 → 0.50.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 (215) hide show
  1. package/README.md +79 -328
  2. package/dist/{accept-KYNC5X3U.js → accept-5NK5XJB5.js} +2 -1
  3. package/dist/{activity-events-MIOK3IOU.js → activity-events-H7YI3BTF.js} +4 -4
  4. package/dist/{ai-service-643TNFPH.js → ai-service-V4CTNGI3.js} +6 -5
  5. package/dist/{archive-6Z4GFQPH.js → archive-ORM3ENHI.js} +3 -3
  6. package/dist/{auth-A4VGHADI.js → auth-DT7GY7EZ.js} +11 -7
  7. package/dist/{avatar-image-Y45R7LW5.js → avatar-image-QEPAP3A5.js} +4 -4
  8. package/dist/{backup-UDH4GMSW.js → backup-IE6PMXJO.js} +17 -18
  9. package/dist/{bridge-3FNSWLNQ.js → bridge-YK7NTRU5.js} +2 -1
  10. package/dist/{channels-SGQZHNQJ.js → channels-FSAHOQWI.js} +2 -1
  11. package/dist/{chat-X43VYHH2.js → chat-W5K6HCBE.js} +9 -9
  12. package/dist/{chunk-LTLFOSA4.js → chunk-3BAFMGCJ.js} +3 -3
  13. package/dist/{chunk-XVBSFLM7.js → chunk-4CN7OCJE.js} +2 -2
  14. package/dist/{chunk-HPGN6XRT.js → chunk-574M6I7F.js} +39 -6
  15. package/dist/{chunk-FJ2MZ5PH.js → chunk-5FVANDON.js} +2 -2
  16. package/dist/{chunk-2MJ7EOMT.js → chunk-5IIVOWSP.js} +28 -19
  17. package/dist/{chunk-4FCL4DVZ.js → chunk-5PKFJHCR.js} +2 -2
  18. package/dist/{chunk-MOERAQXH.js → chunk-5TPC4KCV.js} +38 -31
  19. package/dist/{chunk-YNBXEQAP.js → chunk-632FJ23O.js} +24 -20
  20. package/dist/{chunk-T4VS7UDW.js → chunk-636GFQNE.js} +12 -12
  21. package/dist/{chunk-EIQDZC43.js → chunk-6BM2I736.js} +25 -6
  22. package/dist/{chunk-WARH6GIV.js → chunk-74T3CEXK.js} +17 -15
  23. package/dist/{chunk-OQJMTHEH.js → chunk-77IZUVBB.js} +2 -2
  24. package/dist/{chunk-USD45JKR.js → chunk-7DXLIYPY.js} +77 -64
  25. package/dist/chunk-7FDWPQCU.js +289 -0
  26. package/dist/chunk-7OZWKHA3.js +548 -0
  27. package/dist/{chunk-IJPMIEVE.js → chunk-7UWXAJMK.js} +1 -1
  28. package/dist/{chunk-SZJCJVXV.js → chunk-7XDOTN4R.js} +2 -2
  29. package/dist/{chunk-YKA7DCVW.js → chunk-AFSLNUTT.js} +1 -1
  30. package/dist/{chunk-DGGZVZTM.js → chunk-AJWFDIEY.js} +17 -2
  31. package/dist/chunk-ALIVXDM4.js +1014 -0
  32. package/dist/chunk-CAIYV33P.js +523 -0
  33. package/dist/{chunk-MTRMQFCQ.js → chunk-E5S3ILVA.js} +2 -2
  34. package/dist/{chunk-SHKM5RGD.js → chunk-EEOMA4DX.js} +1 -1
  35. package/dist/chunk-EQKSRVDE.js +2857 -0
  36. package/dist/chunk-FNJCBRD3.js +31 -0
  37. package/dist/chunk-HW6Q7AMB.js +113 -0
  38. package/dist/chunk-JI67D7O2.js +1222 -0
  39. package/dist/{chunk-3YUBFTDN.js → chunk-KAEZOVP6.js} +1 -1
  40. package/dist/chunk-LNNI3MS4.js +54 -0
  41. package/dist/{chunk-BCG45AQB.js → chunk-M74I3HHF.js} +15 -5
  42. package/dist/chunk-MLZBBLMT.js +109 -0
  43. package/dist/{chunk-5T5YMX6S.js → chunk-PW65BXY5.js} +5 -0
  44. package/dist/{chunk-2OIYNHWO.js → chunk-QFVGPKJI.js} +1 -1
  45. package/dist/{chunk-T2TP6ZC6.js → chunk-QUSMYGNQ.js} +6 -0
  46. package/dist/{chunk-HNGFKPNS.js → chunk-SFRYRW37.js} +85 -36
  47. package/dist/{chunk-7KRIHYLR.js → chunk-SSNZ6ZNN.js} +2 -2
  48. package/dist/{chunk-HUNDXZBZ.js → chunk-TGJHGVBZ.js} +48 -19
  49. package/dist/{chunk-PJVPDY2Z.js → chunk-U3EJJ7JN.js} +2 -2
  50. package/dist/{chunk-ODDGGLM6.js → chunk-UC6BNMGT.js} +43 -13
  51. package/dist/{chunk-Q7HOAVFR.js → chunk-WBBKYUGF.js} +4 -6
  52. package/dist/{chunk-WQ46E33M.js → chunk-X67VT4QO.js} +2 -2
  53. package/dist/{chunk-MJRXFUMS.js → chunk-YQOYZ6G7.js} +3 -5
  54. package/dist/{chunk-VZRA5TYO.js → chunk-ZUG76D3P.js} +36 -6
  55. package/dist/cli.js +34 -27
  56. package/dist/{clock-CNRF6F7T.js → clock-LH7UFNLJ.js} +5 -4
  57. package/dist/{cloud-sync-WHKCB4CD.js → cloud-sync-IDJFVDEP.js} +23 -20
  58. package/dist/{config-RPSLA2HX.js → config-UGJS57VT.js} +3 -3
  59. package/dist/{conversations-C7IKYSHD.js → conversations-S2232Y5I.js} +9 -5
  60. package/dist/{create-GJKHABJB.js → create-2M7DAHQX.js} +2 -2
  61. package/dist/{create-VIQVP3QB.js → create-M727VUDF.js} +2 -1
  62. package/dist/{daemon-client-ZFY5MOAL.js → daemon-client-VVYFZI2D.js} +2 -1
  63. package/dist/{daemon-restart-7KUHVZIN.js → daemon-restart-2AAGYFHE.js} +6 -7
  64. package/dist/daemon.js +2166 -833
  65. package/dist/{db-FO6LXYCW.js → db-QAY45Z56.js} +2 -2
  66. package/dist/{delete-ESEP7NGQ.js → delete-O7PEFQDF.js} +1 -1
  67. package/dist/delivery-manager-B4OKWD2P.js +33 -0
  68. package/dist/{delivery-router-3QFG2ZEX.js → delivery-router-JE7SHYNU.js} +10 -6
  69. package/dist/{down-G6IADJXV.js → down-D4IRNEMJ.js} +5 -5
  70. package/dist/echo-text-WYAAODBV.js +39 -0
  71. package/dist/{env-C7Z55QCJ.js → env-ABM5IMA7.js} +8 -7
  72. package/dist/{env-6TGPO5WG.js → env-AZK5X4W7.js} +3 -3
  73. package/dist/{exec-QIFYZG4C.js → exec-2H65LMTA.js} +3 -3
  74. package/dist/{export-C2K3NIRD.js → export-3E3EL6W3.js} +4 -4
  75. package/dist/{extension-J4ZQ2GHV.js → extension-MRYJWSMV.js} +2 -1
  76. package/dist/{extensions-GEKF47A4.js → extensions-7KP3X6G7.js} +23 -20
  77. package/dist/{files-LXQSQOFG.js → files-6BMKVG5G.js} +5 -4
  78. package/dist/{import-SX3ZXHB4.js → import-UPNTEBBT.js} +9 -9
  79. package/dist/{isolation-MCT4ZYLT.js → isolation-OKDFM6WA.js} +5 -3
  80. package/dist/{join-34TR2GXQ.js → join-PYA36HEC.js} +1 -1
  81. package/dist/{list-PMUXZNAM.js → list-4BVGH7AN.js} +8 -3
  82. package/dist/{login-CL4IT2ED.js → login-MOXVLULE.js} +7 -6
  83. package/dist/{login-ARRZ4ORJ.js → login-RJJTCVR3.js} +4 -3
  84. package/dist/{logout-YZRMZ3O2.js → logout-BYV433X6.js} +2 -1
  85. package/dist/{logout-HZYJCYCA.js → logout-DWD3OQVT.js} +4 -3
  86. package/dist/message-delivery-3456PXB7.js +43 -0
  87. package/dist/migrate-thread-config-4A5LW36Z.js +90 -0
  88. package/dist/{mind-46AOK373.js → mind-NYRALSV2.js} +19 -19
  89. package/dist/{mind-activity-tracker-YVZPC4A5.js → mind-activity-tracker-L7GCYXQR.js} +5 -5
  90. package/dist/{mind-contacts-GV7XYL5D.js → mind-contacts-QYU4TPEA.js} +8 -7
  91. package/dist/{mind-history-TTGA5BHZ.js → mind-history-NXXOOTX6.js} +34 -11
  92. package/dist/{mind-list-OTHV4ZO4.js → mind-list-SYRA7CH5.js} +3 -2
  93. package/dist/{mind-profile-LUG353ZT.js → mind-profile-MRCVZPYT.js} +1 -1
  94. package/dist/mind-service-5TA7UJAT.js +47 -0
  95. package/dist/{mind-sleep-DV7KP255.js → mind-sleep-TH6BRQ5X.js} +5 -4
  96. package/dist/{mind-status-BJNPGXKC.js → mind-status-Z2LQFA6F.js} +3 -2
  97. package/dist/{mind-wake-LVTZ55EZ.js → mind-wake-LDGPRA42.js} +2 -1
  98. package/dist/{package-RTN2PS7O.js → package-XYSV7NUT.js} +27 -26
  99. package/dist/prompts-QKFPX6DQ.js +24 -0
  100. package/dist/{read-YSWUXYL7.js → read-ZXGUT4QC.js} +5 -3
  101. package/dist/{register-42VBJKTP.js → register-OX4HRSGC.js} +4 -3
  102. package/dist/{registry-UMMDRPRM.js → registry-4UF6OCOE.js} +10 -4
  103. package/dist/{reject-SXGCYN4A.js → reject-TWB4QJHG.js} +2 -1
  104. package/dist/{restart-YYEGZJDD.js → restart-AGEGNSYQ.js} +6 -5
  105. package/dist/{sandbox-5VQ454XP.js → sandbox-EGEU5GGE.js} +8 -5
  106. package/dist/scheduler-UV6RUJ7P.js +25 -0
  107. package/dist/{schema-VEVO7HG6.js → schema-C2GIPD4V.js} +3 -3
  108. package/dist/{seed-F2HXZQU4.js → seed-3IMBOAGK.js} +1 -1
  109. package/dist/{seed-check-TYD7RRZY.js → seed-check-NRJEQZST.js} +14 -5
  110. package/dist/{seed-cmd-MLCTGSX5.js → seed-cmd-U73XVQMI.js} +3 -3
  111. package/dist/{seed-create-KQ7OILWB.js → seed-create-FBLNVSUP.js} +4 -4
  112. package/dist/{seed-readiness-RBPGOLSZ.js → seed-readiness-QRA46ZJQ.js} +4 -5
  113. package/dist/{seed-sprout-HVYCFJLA.js → seed-sprout-3B4SZX7J.js} +12 -12
  114. package/dist/{send-TD2X5U3K.js → send-NBRZJXKX.js} +34 -12
  115. package/dist/{service-CVZMQYK6.js → service-XMXAXGJD.js} +4 -4
  116. package/dist/{service-install-VCRMFWX5.js → service-install-MOKTEG7B.js} +5 -5
  117. package/dist/{setup-5ECWTYKG.js → setup-CAJFOR2N.js} +12 -3
  118. package/dist/{setup-AE4CVUC4.js → setup-JGYAXXW7.js} +6 -7
  119. package/dist/{skill-UUM4JZRF.js → skill-NXIHDQCG.js} +7 -6
  120. package/dist/skills/dreaming/references/INSTALL.md +2 -2
  121. package/dist/skills/imagegen/SKILL.md +21 -4
  122. package/dist/skills/imagegen/scripts/imagegen.ts +124 -21
  123. package/dist/skills/tending/SKILL.md +7 -7
  124. package/dist/skills/volute-mind/SKILL.md +6 -6
  125. package/dist/skills/volute-mind/references/integrations.md +2 -2
  126. package/dist/skills/volute-mind/references/routing.md +19 -18
  127. package/dist/skills/volute-mind/references/variants.md +48 -14
  128. package/dist/{skills-B3SVDCEB.js → skills-5MSMT7PT.js} +7 -6
  129. package/dist/sleep-manager-LIEPO6WG.js +41 -0
  130. package/dist/spirit-IL5EMWYZ.js +34 -0
  131. package/dist/spirit-availability-YFEGJNJ2.js +42 -0
  132. package/dist/{split-SKZ4Y2D6.js → split-BU5VWX6P.js} +5 -2
  133. package/dist/{sprout-KQ43BWZJ.js → sprout-UPXGJ5HX.js} +1 -1
  134. package/dist/{src-YH6PQ7LJ.js → src-HGNE2XG4.js} +7 -1
  135. package/dist/{src-WLJE73NP.js → src-LCSOQY6Z.js} +60 -54
  136. package/dist/{src-JAOVXO5W.js → src-UDMJZ6MH.js} +8 -8
  137. package/dist/{start-O6UKK7BG.js → start-MUMNFR52.js} +4 -3
  138. package/dist/{status-F4PGREUU.js → status-IL67CYM4.js} +7 -6
  139. package/dist/{stop-UBX6X5RA.js → stop-EKERNYTX.js} +6 -5
  140. package/dist/system-events-A6IGCEC3.js +46 -0
  141. package/dist/system-install-YZSWCPDK.js +8 -0
  142. package/dist/{systems-JJNGQHAO.js → systems-VDRUZXNF.js} +5 -4
  143. package/dist/{tailscale-MCLY5GYE.js → tailscale-SJUW2ETR.js} +3 -3
  144. package/dist/{template-staleness-EMQORSGG.js → template-staleness-T27VCGYA.js} +4 -4
  145. package/dist/{turn-tracker-KLPV3PAU.js → turn-tracker-6EI2EL7G.js} +7 -7
  146. package/dist/{up-RVGDE6CQ.js → up-CLL4665A.js} +5 -6
  147. package/dist/{update-OXQEGGXD.js → update-HVAUCQPN.js} +5 -5
  148. package/dist/{update-check-266R4ZSY.js → update-check-2MGBBAFF.js} +3 -3
  149. package/dist/{upgrade-K5FIQPQD.js → upgrade-LIE46UBB.js} +1 -1
  150. package/dist/variant-cleanup-HJ7Z75R6.js +22 -0
  151. package/dist/{version-notify-73YURTFR.js → version-notify-7BUHWZNT.js} +11 -28
  152. package/dist/web-assets/assets/index-ClsUcXiW.js +95 -0
  153. package/dist/web-assets/assets/index-Dz3wTbtR.css +1 -0
  154. package/dist/web-assets/index.html +2 -2
  155. package/dist/xai-P42CRCZA.js +129 -0
  156. package/drizzle/0013_variant_purpose.sql +3 -0
  157. package/drizzle/0014_system_events.sql +26 -0
  158. package/drizzle/0015_rename_session_to_thread.sql +12 -0
  159. package/drizzle/0016_brain_to_human.sql +4 -0
  160. package/drizzle/0017_event_history_rows.sql +9 -0
  161. package/drizzle/meta/_journal.json +35 -0
  162. package/package.json +27 -26
  163. package/packages/extensions/notes/dist/ui/assets/index-BBs8pyel.js +67 -0
  164. package/packages/extensions/notes/dist/ui/index.html +1 -1
  165. package/packages/extensions/pages/dist/ui/assets/index-B589e8Xe.js +2 -0
  166. package/packages/extensions/pages/dist/ui/index.html +1 -1
  167. package/packages/extensions/plan/dist/ui/assets/index-B8s8wxyt.js +67 -0
  168. package/packages/extensions/plan/dist/ui/index.html +1 -1
  169. package/templates/_base/.init/.config/prompts.json +1 -0
  170. package/templates/_base/.init/.local/hooks/startup-context.ts +34 -2
  171. package/templates/_base/home/.config/routes.json +2 -2
  172. package/templates/_base/home/VOLUTE.md +30 -5
  173. package/templates/_base/src/lib/daemon-client.ts +28 -8
  174. package/templates/_base/src/lib/event-turn.ts +39 -0
  175. package/templates/_base/src/lib/format-prefix.ts +21 -2
  176. package/templates/_base/src/lib/router.ts +6 -2
  177. package/templates/_base/src/lib/routing.ts +8 -8
  178. package/templates/_base/src/lib/startup.ts +9 -1
  179. package/templates/_base/src/lib/types.ts +7 -1
  180. package/templates/_base/src/lib/volute-server.ts +30 -1
  181. package/templates/claude/.init/.config/routes.json +3 -3
  182. package/templates/claude/.init/CLAUDE.md +1 -1
  183. package/templates/claude/package.json.tmpl +4 -4
  184. package/templates/claude/src/agent.ts +5 -2
  185. package/templates/claude/src/lib/hooks/identity-reload.ts +12 -3
  186. package/templates/claude/src/lib/hooks/reply-instructions.ts +26 -1
  187. package/templates/codex/.init/.config/routes.json +3 -3
  188. package/templates/codex/.init/AGENTS.md +1 -1
  189. package/templates/codex/package.json.tmpl +4 -4
  190. package/templates/codex/src/agent.ts +11 -11
  191. package/templates/codex/src/lib/turn-context.ts +51 -0
  192. package/templates/pi/.init/.config/routes.json +3 -3
  193. package/templates/pi/.init/MINDS.md +1 -1
  194. package/templates/pi/package.json.tmpl +6 -6
  195. package/templates/pi/src/agent.ts +9 -1
  196. package/templates/pi/src/lib/event-handler.ts +47 -6
  197. package/templates/pi/src/lib/reply-instructions-extension.ts +28 -1
  198. package/dist/chunk-5KXCM5AW.js +0 -169
  199. package/dist/chunk-IYCV34IU.js +0 -158
  200. package/dist/chunk-VB2QXUJQ.js +0 -5260
  201. package/dist/chunk-WW6C3PLV.js +0 -280
  202. package/dist/delivery-manager-HZTT5SJR.js +0 -36
  203. package/dist/echo-text-H3SH337Q.js +0 -36
  204. package/dist/message-delivery-GPQ5SE5N.js +0 -40
  205. package/dist/mind-service-2ARGJNKP.js +0 -44
  206. package/dist/notices-2KU5NTRY.js +0 -25
  207. package/dist/scheduler-I7JZCT47.js +0 -34
  208. package/dist/sleep-manager-Z27R3WTW.js +0 -38
  209. package/dist/spirit-WXS3HCLN.js +0 -25
  210. package/dist/system-chat-63ABFF6V.js +0 -40
  211. package/dist/web-assets/assets/index-WdZ60R6c.css +0 -1
  212. package/dist/web-assets/assets/index-taX6Ezfa.js +0 -92
  213. package/packages/extensions/notes/dist/ui/assets/index-D0-DgYyj.js +0 -67
  214. package/packages/extensions/pages/dist/ui/assets/index-DtzftuPA.js +0 -2
  215. package/packages/extensions/plan/dist/ui/assets/index-Cfzs-0G9.js +0 -67
@@ -0,0 +1,289 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ buildMindBaseEnv,
4
+ clearJsonMap,
5
+ generateMindToken,
6
+ getMindToken,
7
+ loadJsonMap,
8
+ saveJsonMapAsync
9
+ } from "./chunk-ALIVXDM4.js";
10
+ import {
11
+ deliverEvent
12
+ } from "./chunk-7OZWKHA3.js";
13
+ import {
14
+ isSandboxEnabled,
15
+ wrapForSandbox
16
+ } from "./chunk-ZUG76D3P.js";
17
+ import {
18
+ readVoluteConfig,
19
+ writeVoluteConfig
20
+ } from "./chunk-WCFSBH33.js";
21
+ import {
22
+ exec
23
+ } from "./chunk-AJWFDIEY.js";
24
+ import {
25
+ loadMergedEnv
26
+ } from "./chunk-7UWXAJMK.js";
27
+ import {
28
+ logger_default
29
+ } from "./chunk-QUSMYGNQ.js";
30
+ import {
31
+ findMind,
32
+ mindDir,
33
+ mindTmpDir,
34
+ mindTmpEnv,
35
+ stateDir,
36
+ voluteSystemDir
37
+ } from "./chunk-CAIYV33P.js";
38
+
39
+ // packages/daemon/src/lib/daemon/scheduler.ts
40
+ import { resolve } from "path";
41
+ import { CronExpressionParser } from "cron-parser";
42
+ var slog = logger_default.child("scheduler");
43
+ var CATCHUP_STALE_MINUTES = 10;
44
+ var Scheduler = class {
45
+ schedules = /* @__PURE__ */ new Map();
46
+ mindDirs = /* @__PURE__ */ new Map();
47
+ // mindName → dir override
48
+ interval = null;
49
+ lastFired = /* @__PURE__ */ new Map();
50
+ // "mind:scheduleId" → epoch minute
51
+ get statePath() {
52
+ return resolve(voluteSystemDir(), "scheduler-state.json");
53
+ }
54
+ start() {
55
+ this.loadState();
56
+ this.interval = setInterval(() => {
57
+ this.tick().catch((err) => slog.error("scheduler tick failed", logger_default.errorData(err)));
58
+ }, 6e4);
59
+ }
60
+ stop() {
61
+ if (this.interval) clearInterval(this.interval);
62
+ }
63
+ loadState() {
64
+ this.lastFired = loadJsonMap(this.statePath);
65
+ }
66
+ async saveState() {
67
+ await saveJsonMapAsync(this.statePath, this.lastFired);
68
+ }
69
+ clearState() {
70
+ clearJsonMap(this.statePath, this.lastFired);
71
+ }
72
+ loadSchedules(mindName, dir) {
73
+ if (dir) this.mindDirs.set(mindName, dir);
74
+ const resolvedDir = this.mindDirs.get(mindName) ?? mindDir(mindName);
75
+ const config = readVoluteConfig(resolvedDir);
76
+ if (!config) return;
77
+ const schedules = config.schedules ?? [];
78
+ if (schedules.length > 0) {
79
+ this.schedules.set(mindName, schedules);
80
+ } else {
81
+ this.schedules.delete(mindName);
82
+ }
83
+ const epochMinute = Math.floor(Date.now() / 6e4);
84
+ const validKeys = new Set(schedules.map((s) => `${mindName}:${s.id}`));
85
+ let changed = false;
86
+ for (const key of this.lastFired.keys()) {
87
+ if (key.startsWith(`${mindName}:`) && !validKeys.has(key)) {
88
+ this.lastFired.delete(key);
89
+ changed = true;
90
+ }
91
+ }
92
+ for (const key of validKeys) {
93
+ if (!this.lastFired.has(key)) {
94
+ this.lastFired.set(key, epochMinute);
95
+ changed = true;
96
+ }
97
+ }
98
+ if (changed) {
99
+ this.saveState().catch(
100
+ (err) => slog.warn(`failed to persist scheduler state for ${mindName}`, logger_default.errorData(err))
101
+ );
102
+ }
103
+ }
104
+ unloadSchedules(mindName) {
105
+ this.schedules.delete(mindName);
106
+ this.mindDirs.delete(mindName);
107
+ }
108
+ async tick() {
109
+ const now = /* @__PURE__ */ new Date();
110
+ const epochMinute = Math.floor(now.getTime() / 6e4);
111
+ const cronCache = /* @__PURE__ */ new Map();
112
+ let anyFired = false;
113
+ for (const [mind, schedules] of this.schedules) {
114
+ for (const schedule of schedules) {
115
+ if (!schedule.enabled) continue;
116
+ if (this.shouldFire(schedule, epochMinute, mind, cronCache)) {
117
+ anyFired = true;
118
+ this.fire(mind, schedule);
119
+ }
120
+ }
121
+ }
122
+ if (anyFired) await this.saveState();
123
+ }
124
+ shouldFire(schedule, epochMinute, mind, cronCache) {
125
+ const key = `${mind}:${schedule.id}`;
126
+ if (this.lastFired.get(key) === epochMinute) return false;
127
+ if (schedule.fireAt) {
128
+ const fireTime = Math.floor(new Date(schedule.fireAt).getTime() / 6e4);
129
+ if (epochMinute >= fireTime) {
130
+ this.lastFired.set(key, epochMinute);
131
+ return true;
132
+ }
133
+ return false;
134
+ }
135
+ if (!schedule.cron) return false;
136
+ let prevMinute = cronCache.get(schedule.cron);
137
+ if (prevMinute === void 0) {
138
+ try {
139
+ const interval = CronExpressionParser.parse(schedule.cron);
140
+ const prev = interval.prev().toDate();
141
+ prevMinute = Math.floor(prev.getTime() / 6e4);
142
+ cronCache.set(schedule.cron, prevMinute);
143
+ } catch (err) {
144
+ slog.warn(`invalid cron "${schedule.cron}" for ${mind}:${schedule.id}`, logger_default.errorData(err));
145
+ return false;
146
+ }
147
+ }
148
+ const lastFired = this.lastFired.get(key) ?? epochMinute;
149
+ if (prevMinute <= lastFired) return false;
150
+ this.lastFired.set(key, prevMinute);
151
+ if (epochMinute - prevMinute > CATCHUP_STALE_MINUTES) {
152
+ slog.info(`skipping stale catch-up fire for ${key} (${epochMinute - prevMinute}min late)`);
153
+ return false;
154
+ }
155
+ return true;
156
+ }
157
+ async fire(mindName, schedule) {
158
+ try {
159
+ let text;
160
+ if (schedule.script) {
161
+ const homeDir = resolve(this.mindDirs.get(mindName) ?? mindDir(mindName), "home");
162
+ try {
163
+ const output = await this.runScript(schedule.script, homeDir, mindName);
164
+ if (!output.trim()) {
165
+ slog.info(`fired script "${schedule.id}" for ${mindName} (no output)`);
166
+ return;
167
+ }
168
+ text = output;
169
+ } catch (err) {
170
+ const stderr = err.stderr ?? "";
171
+ text = `[script error] ${err.message}${stderr ? `
172
+ ${stderr}` : ""}`;
173
+ slog.warn(`script "${schedule.id}" failed for ${mindName}`, logger_default.errorData(err));
174
+ }
175
+ } else if (Array.isArray(schedule.messages) && schedule.messages.length > 0) {
176
+ const pick = schedule.messages[Math.floor(Math.random() * schedule.messages.length)];
177
+ if (typeof pick !== "string" || !pick.trim()) {
178
+ slog.warn(`schedule "${schedule.id}" for ${mindName} has a malformed messages pool`);
179
+ return;
180
+ }
181
+ text = pick;
182
+ } else if (schedule.message) {
183
+ text = schedule.message;
184
+ } else {
185
+ slog.warn(`schedule "${schedule.id}" for ${mindName} has no message, messages, or script`);
186
+ return;
187
+ }
188
+ const { id, delivered } = await this.deliverSystem(mindName, schedule.id, text, {
189
+ // Default schedule fires to "queue" while asleep so an unadorned cron
190
+ // schedule doesn't inherit the DM wake-trigger fallback and wake the mind.
191
+ whileSleeping: schedule.whileSleeping ?? "queue",
192
+ thread: schedule.thread
193
+ });
194
+ slog.info(`fired "${schedule.id}" for ${mindName}${delivered ? "" : " (event pending)"}`);
195
+ if (schedule.fireAt && id != null) {
196
+ this.removeSchedule(mindName, schedule.id);
197
+ }
198
+ } catch (err) {
199
+ slog.warn(`failed to fire "${schedule.id}" for ${mindName}`, logger_default.errorData(err));
200
+ }
201
+ }
202
+ removeSchedule(mindName, scheduleId) {
203
+ const memSchedules = this.schedules.get(mindName);
204
+ if (memSchedules) {
205
+ const filtered = memSchedules.filter((s) => s.id !== scheduleId);
206
+ if (filtered.length > 0) {
207
+ this.schedules.set(mindName, filtered);
208
+ } else {
209
+ this.schedules.delete(mindName);
210
+ }
211
+ }
212
+ try {
213
+ const dir = this.mindDirs.get(mindName) ?? mindDir(mindName);
214
+ const config = readVoluteConfig(dir);
215
+ if (!config?.schedules) return;
216
+ config.schedules = config.schedules.filter((s) => s.id !== scheduleId);
217
+ if (config.schedules.length === 0) config.schedules = void 0;
218
+ writeVoluteConfig(dir, config);
219
+ slog.info(`removed one-time schedule "${scheduleId}" for ${mindName}`);
220
+ } catch (err) {
221
+ slog.error(
222
+ `failed to persist removal of schedule "${scheduleId}" for ${mindName} (removed from memory)`,
223
+ logger_default.errorData(err)
224
+ );
225
+ }
226
+ }
227
+ async runScript(script, cwd, mindName) {
228
+ const env = await this.buildScriptEnv(mindName);
229
+ if (isSandboxEnabled()) {
230
+ const dir = this.mindDirs.get(mindName) ?? mindDir(mindName);
231
+ const [cmd, args] = await wrapForSandbox("bash", ["-c", script], dir, mindName, [
232
+ dir,
233
+ mindTmpDir(dir)
234
+ ]);
235
+ return exec(cmd, args, { cwd, env });
236
+ }
237
+ return exec("bash", ["-c", script], { cwd, mindName, env });
238
+ }
239
+ /**
240
+ * Build the environment for a scheduled script — mirrors the mind process env
241
+ * (allowlisted base + merged mind env + VOLUTE_* runtime vars), authenticated
242
+ * with the mind's own per-mind, non-admin token. Reuses the running mind's
243
+ * token when present; otherwise mints one (stable across fires, regenerated on
244
+ * next mind start). The daemon admin token is never included.
245
+ */
246
+ async buildScriptEnv(mindName) {
247
+ const dir = this.mindDirs.get(mindName) ?? mindDir(mindName);
248
+ const entry = await findMind(mindName);
249
+ const token = getMindToken(mindName) ?? generateMindToken(mindName);
250
+ const mindLocalBin = resolve(dir, "home", ".local", "bin");
251
+ const currentPath = process.env.PATH ?? "";
252
+ return {
253
+ ...buildMindBaseEnv(),
254
+ ...loadMergedEnv(mindName),
255
+ VOLUTE_MIND: mindName,
256
+ VOLUTE_STATE_DIR: stateDir(mindName),
257
+ VOLUTE_MIND_DIR: dir,
258
+ VOLUTE_MIND_PORT: entry ? String(entry.port) : void 0,
259
+ VOLUTE_MIND_TOKEN: token,
260
+ ...mindTmpEnv(dir),
261
+ PATH: `${mindLocalBin}:${currentPath}`
262
+ };
263
+ }
264
+ async deliverSystem(mindName, scheduleId, text, opts) {
265
+ return deliverEvent(mindName, {
266
+ type: "schedule",
267
+ body: text,
268
+ meta: { scheduleId },
269
+ thread: opts?.thread,
270
+ whileSleeping: opts?.whileSleeping
271
+ });
272
+ }
273
+ };
274
+ var instance = null;
275
+ function initScheduler() {
276
+ if (instance) throw new Error("Scheduler already initialized");
277
+ instance = new Scheduler();
278
+ return instance;
279
+ }
280
+ function getScheduler() {
281
+ if (!instance) throw new Error("Scheduler not initialized \u2014 call initScheduler() first");
282
+ return instance;
283
+ }
284
+
285
+ export {
286
+ Scheduler,
287
+ initScheduler,
288
+ getScheduler
289
+ };