volute 0.45.2 → 0.47.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 (146) hide show
  1. package/README.md +4 -4
  2. package/dist/{activity-events-BUR2RYTJ.js → activity-events-MIOK3IOU.js} +3 -3
  3. package/dist/{ai-service-UODCCE4O.js → ai-service-CVZEYKTK.js} +4 -4
  4. package/dist/{archive-WS2L2VIQ.js → archive-6Z4GFQPH.js} +3 -3
  5. package/dist/{auth-FLAEXXZK.js → auth-A4VGHADI.js} +4 -4
  6. package/dist/avatar-image-7S3WA77A.js +22 -0
  7. package/dist/{backup-TST5C2YN.js → backup-IIAFOW2L.js} +13 -13
  8. package/dist/channels-ITE4YC57.js +89 -0
  9. package/dist/{chat-76MRWWQX.js → chat-AJLJCQ3M.js} +12 -8
  10. package/dist/{chunk-ZV7MFISX.js → chunk-2MJ7EOMT.js} +20 -2
  11. package/dist/{chunk-R77O2WMK.js → chunk-2OIYNHWO.js} +1 -1
  12. package/dist/{chunk-QCN24XEN.js → chunk-3YUBFTDN.js} +1 -1
  13. package/dist/{chunk-NYC5U6K7.js → chunk-4I72V3E5.js} +10 -10
  14. package/dist/{chunk-QNQMSWZE.js → chunk-5IERLDUS.js} +3 -3
  15. package/dist/{chunk-FXORA5JG.js → chunk-5NN2VHJV.js} +3 -3
  16. package/dist/chunk-7KRIHYLR.js +124 -0
  17. package/dist/{chunk-RRNXJOKB.js → chunk-BGYHLR54.js} +2 -2
  18. package/dist/{chunk-QMJCNZ6C.js → chunk-C3KLHJDB.js} +1 -1
  19. package/dist/{chunk-TQJ6ZYVY.js → chunk-CBOIHNMR.js} +2 -2
  20. package/dist/{chunk-H2X54EHC.js → chunk-DGGZVZTM.js} +1 -1
  21. package/dist/{chunk-4JOQZPCE.js → chunk-EIQDZC43.js} +1 -1
  22. package/dist/{chunk-YT7PEESJ.js → chunk-F55GRIEK.js} +464 -221
  23. package/dist/{chunk-OP4JG2BS.js → chunk-HPGN6XRT.js} +8 -4
  24. package/dist/{chunk-7ZUB63PS.js → chunk-IWJSNMSY.js} +5 -5
  25. package/dist/{chunk-ZDZISKCL.js → chunk-IYG4FBMP.js} +1 -1
  26. package/dist/chunk-JCMGDCGX.js +237 -0
  27. package/dist/{clock-K43IMROX.js → chunk-JSVHH32K.js} +26 -21
  28. package/dist/{chunk-UYLGEDVD.js → chunk-K23P5Q5N.js} +340 -339
  29. package/dist/{chunk-7C44R625.js → chunk-LTLFOSA4.js} +49 -3
  30. package/dist/{chunk-IHKGWTGT.js → chunk-LWDTP6DH.js} +1 -1
  31. package/dist/{chunk-JZ62SYWR.js → chunk-M3XDVS5Q.js} +1 -1
  32. package/dist/{chunk-FR6S2KNN.js → chunk-M6TTZRGH.js} +9 -9
  33. package/dist/{chunk-EJVPTVTI.js → chunk-MTRMQFCQ.js} +1 -1
  34. package/dist/{chunk-T6HYVHPG.js → chunk-ODDGGLM6.js} +3 -3
  35. package/dist/{chunk-QLWG3C2Z.js → chunk-RRZ4RQQU.js} +2 -2
  36. package/dist/{chunk-CDPBTTZO.js → chunk-RUFLGTOT.js} +2 -2
  37. package/dist/{chunk-F7H6ATM4.js → chunk-SKNXMUIU.js} +1 -1
  38. package/dist/{chunk-RXEEYDIB.js → chunk-SZJCJVXV.js} +1 -1
  39. package/dist/{chunk-DRJ24GXK.js → chunk-VMCZBXF2.js} +52 -8
  40. package/dist/{chunk-ALKYQULU.js → chunk-WW6C3PLV.js} +1 -1
  41. package/dist/{chunk-A7WQZKOU.js → chunk-XHN6XBWV.js} +2 -2
  42. package/dist/{chunk-6EMWJZBK.js → chunk-YKA7DCVW.js} +1 -1
  43. package/dist/cli.js +24 -24
  44. package/dist/clock-E6N5Y3YW.js +16 -0
  45. package/dist/{cloud-sync-3OJOPZRT.js → cloud-sync-LQTVDTQ3.js} +19 -17
  46. package/dist/{conversations-JZTD6KLR.js → conversations-C7IKYSHD.js} +4 -4
  47. package/dist/{create-IOV4GCRX.js → create-PQQXKTJK.js} +1 -1
  48. package/dist/{daemon-restart-TIHKVVVA.js → daemon-restart-5F3YTLZK.js} +7 -7
  49. package/dist/daemon.js +418 -273
  50. package/dist/{db-MF2JNRUW.js → db-FO6LXYCW.js} +2 -2
  51. package/dist/{delete-BFMIGAIB.js → delete-HPLC7ETU.js} +1 -1
  52. package/dist/delivery-manager-TN5KK2AP.js +34 -0
  53. package/dist/{delivery-router-N4A7ZC5T.js → delivery-router-3QFG2ZEX.js} +3 -3
  54. package/dist/{down-U2CZ23M4.js → down-ZI4S6265.js} +5 -5
  55. package/dist/echo-text-SEQCLGAT.js +34 -0
  56. package/dist/{exec-G3ZRST4Z.js → exec-QIFYZG4C.js} +3 -3
  57. package/dist/{export-GGIVO76O.js → export-4GVGNW54.js} +6 -6
  58. package/dist/{extensions-JONTW2HF.js → extensions-PIMIQ7J3.js} +21 -17
  59. package/dist/{files-4LN3EN7Q.js → files-FEFZKMNM.js} +6 -6
  60. package/dist/{import-42YNQBF4.js → import-7MO3YTRE.js} +6 -6
  61. package/dist/{isolation-YMGZZ4E5.js → isolation-MCT4ZYLT.js} +3 -3
  62. package/dist/{login-M6OJUPQ2.js → login-OF4WXDAN.js} +5 -5
  63. package/dist/{logout-WQBWO77G.js → logout-ERE2BGA3.js} +5 -5
  64. package/dist/message-delivery-ZE3YGJDD.js +38 -0
  65. package/dist/{mind-DWB6XYCH.js → mind-POOHFZHP.js} +18 -18
  66. package/dist/{mind-activity-tracker-POBLU3LC.js → mind-activity-tracker-YVZPC4A5.js} +4 -4
  67. package/dist/{mind-list-4D7J4CHD.js → mind-list-KEGRGQEQ.js} +5 -4
  68. package/dist/mind-service-JFY7UG5E.js +40 -0
  69. package/dist/mind-sleep-IN3WPA76.js +83 -0
  70. package/dist/{mind-status-UO34ASXZ.js → mind-status-SMUWJKGV.js} +20 -3
  71. package/dist/{package-A7KXT67Q.js → package-WPEE2HVJ.js} +3 -2
  72. package/dist/{registry-OUZBZ3Q4.js → registry-UMMDRPRM.js} +2 -2
  73. package/dist/{sandbox-ZJ7QA6Z6.js → sandbox-G4X4K3UN.js} +4 -4
  74. package/dist/scheduler-HQSDYJFY.js +32 -0
  75. package/dist/{schema-PVNG3PQM.js → schema-VEVO7HG6.js} +3 -1
  76. package/dist/{seed-HCP5NYKH.js → seed-VANPU43L.js} +1 -1
  77. package/dist/{seed-cmd-ILOGNIFB.js → seed-cmd-SYVDZKM4.js} +2 -2
  78. package/dist/{seed-create-OURS2LS4.js → seed-create-3FGZPS6C.js} +5 -5
  79. package/dist/{seed-sprout-ELDFDAN6.js → seed-sprout-H7QLFPFM.js} +13 -11
  80. package/dist/{send-AAIOPJAV.js → send-HYDFGC6G.js} +13 -7
  81. package/dist/{service-UOCLXDWR.js → service-SYJ5G6LP.js} +4 -4
  82. package/dist/{service-install-4HHFDDWY.js → service-install-FQ43Z4DF.js} +5 -5
  83. package/dist/{setup-Y2EJI3GG.js → setup-KFYVWTXG.js} +3 -3
  84. package/dist/{setup-DQM5VPY4.js → setup-QJHGMGSN.js} +7 -7
  85. package/dist/skills/orientation/SKILL.md +1 -1
  86. package/dist/skills/volute-mind/SKILL.md +8 -8
  87. package/dist/skills/volute-mind/references/sleep.md +8 -1
  88. package/dist/{skills-BKINFL7W.js → skills-L2CMTW7A.js} +5 -5
  89. package/dist/sleep-manager-3NA7ZJP3.js +36 -0
  90. package/dist/{spirit-DBTIW7P4.js → spirit-WXN4IDBF.js} +8 -8
  91. package/dist/{sprout-W3KC7UGK.js → sprout-LTL5P36T.js} +1 -1
  92. package/dist/{status-SWRVGXBI.js → status-V4RJMHJ4.js} +19 -9
  93. package/dist/system-chat-N2F7HVY3.js +38 -0
  94. package/dist/{systems-VNB5WKSS.js → systems-B5YJKMW4.js} +6 -6
  95. package/dist/{tailscale-3XOKOW7T.js → tailscale-MCLY5GYE.js} +2 -2
  96. package/dist/{template-hash-446HHH5V.js → template-hash-GSOUARW4.js} +2 -2
  97. package/dist/template-staleness-EMQORSGG.js +15 -0
  98. package/dist/{turn-tracker-2W6Q2NSR.js → turn-tracker-JZRZDFWV.js} +6 -5
  99. package/dist/{up-FTK7CTO3.js → up-J2PBPOZL.js} +6 -6
  100. package/dist/{update-SWAYZRWJ.js → update-ACV4SAY3.js} +5 -5
  101. package/dist/{update-check-PQ2MZ47K.js → update-check-266R4ZSY.js} +3 -3
  102. package/dist/{version-notify-3XMAIQTE.js → version-notify-XRGTEEDK.js} +42 -23
  103. package/dist/web-assets/assets/index-DtctoDXY.css +1 -0
  104. package/dist/web-assets/assets/index-Wpyb2CML.js +86 -0
  105. package/dist/web-assets/index.html +2 -2
  106. package/drizzle/0011_drop_turn_summary_event_id.sql +3 -0
  107. package/drizzle/0012_channel_gates.sql +17 -0
  108. package/drizzle/meta/_journal.json +14 -0
  109. package/package.json +3 -2
  110. package/templates/_base/home/VOLUTE.md +6 -2
  111. package/templates/_base/src/lib/context-breakdown.ts +57 -67
  112. package/templates/_base/src/lib/startup.ts +43 -2
  113. package/templates/_base/src/lib/volute-server.ts +2 -2
  114. package/templates/claude/src/agent.ts +105 -43
  115. package/templates/claude/src/lib/session-reaper.ts +63 -0
  116. package/templates/claude/src/server.ts +10 -1
  117. package/templates/codex/src/agent.ts +62 -40
  118. package/templates/pi/src/agent.ts +55 -38
  119. package/dist/avatar-image-6KY6UU4D.js +0 -16
  120. package/dist/delivery-manager-VDVKVA46.js +0 -32
  121. package/dist/echo-text-6JJOPUD4.js +0 -32
  122. package/dist/message-delivery-3C65ZI6L.js +0 -34
  123. package/dist/mind-service-3F4I46CI.js +0 -36
  124. package/dist/mind-sleep-YMBR3I35.js +0 -47
  125. package/dist/scheduler-P6DXLLXI.js +0 -30
  126. package/dist/sleep-manager-HCUH4PM4.js +0 -34
  127. package/dist/system-chat-XOALE2RD.js +0 -36
  128. package/dist/web-assets/assets/index-B1JBQv2m.css +0 -1
  129. package/dist/web-assets/assets/index-Bc2m8gRC.js +0 -86
  130. package/dist/{accept-N5XULCXK.js → accept-YAFP5JFJ.js} +3 -3
  131. package/dist/{bridge-2PO3AI5D.js → bridge-TIZGSXOD.js} +3 -3
  132. package/dist/{create-DF3KISTW.js → create-A7PDHIIL.js} +3 -3
  133. package/dist/{env-VZ4MLFQW.js → env-HRL2PWAT.js} +5 -5
  134. package/dist/{extension-SEID47SR.js → extension-QBY4C44L.js} +3 -3
  135. package/dist/{list-OXIAY3W7.js → list-LQO2DCIK.js} +3 -3
  136. package/dist/{login-MY6ETFYB.js → login-6TLMI2TQ.js} +3 -3
  137. package/dist/{logout-NCG3TMDL.js → logout-BK53HXNY.js} +3 -3
  138. package/dist/{mind-history-VRO5A76U.js → mind-history-47DN6ILW.js} +4 -4
  139. package/dist/{mind-wake-LCLGTXA2.js → mind-wake-R54BPESY.js} +3 -3
  140. package/dist/{read-QVAXMTPV.js → read-PWAJOQIQ.js} +3 -3
  141. package/dist/{register-Y2H4PY5R.js → register-7SZJFHU6.js} +3 -3
  142. package/dist/{reject-EV35X64Y.js → reject-6VH7N2A4.js} +3 -3
  143. package/dist/{restart-FJQ662XJ.js → restart-H5WJDMA7.js} +4 -4
  144. package/dist/{skill-FKFHIJRR.js → skill-GUKG2YVH.js} +5 -5
  145. package/dist/{start-5JE3TV66.js → start-X46I3GXB.js} +4 -4
  146. package/dist/{stop-AAMTG3TU.js → stop-HJQV4YSM.js} +4 -4
package/dist/daemon.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  readBackupState,
7
7
  resticVersion,
8
8
  runBackup
9
- } from "./chunk-FXORA5JG.js";
9
+ } from "./chunk-5NN2VHJV.js";
10
10
  import {
11
11
  acceptPending,
12
12
  formatFileSize,
@@ -14,35 +14,34 @@ import {
14
14
  rejectPending,
15
15
  stageFile,
16
16
  validateFilePath
17
- } from "./chunk-R77O2WMK.js";
17
+ } from "./chunk-2OIYNHWO.js";
18
18
  import {
19
19
  findOpenClawSession,
20
20
  importOpenClawConnectors,
21
21
  importPiSession,
22
22
  parseNameFromIdentity
23
- } from "./chunk-A7WQZKOU.js";
23
+ } from "./chunk-XHN6XBWV.js";
24
+ import {
25
+ isTemplateStale
26
+ } from "./chunk-7KRIHYLR.js";
24
27
  import {
25
28
  checkForUpdate,
26
29
  checkForUpdateCached,
27
30
  getCurrentVersion
28
- } from "./chunk-6EMWJZBK.js";
29
- import {
30
- normalizeAvatar,
31
- safeResolveWithinBase
32
- } from "./chunk-DRJ24GXK.js";
31
+ } from "./chunk-YKA7DCVW.js";
33
32
  import {
34
33
  isHomeOnlyArchive
35
- } from "./chunk-QCN24XEN.js";
34
+ } from "./chunk-3YUBFTDN.js";
36
35
  import {
37
36
  computeTemplateHash
38
- } from "./chunk-QMJCNZ6C.js";
37
+ } from "./chunk-C3KLHJDB.js";
39
38
  import {
40
39
  PLATFORMS,
41
40
  echoTextToChannel,
42
41
  fixModelEscapes,
43
42
  getPlatformDriver,
44
43
  routeOutboundBridge
45
- } from "./chunk-NYC5U6K7.js";
44
+ } from "./chunk-4I72V3E5.js";
46
45
  import {
47
46
  getBridgeConfig,
48
47
  readBridgesConfig,
@@ -51,7 +50,7 @@ import {
51
50
  resolveChannelMapping,
52
51
  setBridgeConfig,
53
52
  setChannelMapping
54
- } from "./chunk-RXEEYDIB.js";
53
+ } from "./chunk-SZJCJVXV.js";
55
54
  import {
56
55
  MindStartupError,
57
56
  RestartTracker,
@@ -113,15 +112,15 @@ import {
113
112
  syncProviderToMinds,
114
113
  uninstallNpmExtension,
115
114
  writeSystemsConfig
116
- } from "./chunk-YT7PEESJ.js";
115
+ } from "./chunk-F55GRIEK.js";
117
116
  import {
118
117
  loadMergedEnv,
119
118
  mindEnvPath,
120
119
  readEnv,
121
120
  sharedEnvPath,
122
121
  writeEnv
123
- } from "./chunk-ZDZISKCL.js";
124
- import "./chunk-FR6S2KNN.js";
122
+ } from "./chunk-IYG4FBMP.js";
123
+ import "./chunk-M6TTZRGH.js";
125
124
  import {
126
125
  applyInitFiles,
127
126
  applyTemplateHomeFiles,
@@ -130,7 +129,34 @@ import {
130
129
  findTemplatesRoot,
131
130
  isKnownTemplate,
132
131
  listFiles
133
- } from "./chunk-QLWG3C2Z.js";
132
+ } from "./chunk-RRZ4RQQU.js";
133
+ import {
134
+ assignSession,
135
+ completeOrphanedTurns,
136
+ completeTurn,
137
+ createTurn,
138
+ getActiveTurnId,
139
+ getToolUseEventId,
140
+ initSummarizer,
141
+ isoWeekKeyForDateStr,
142
+ markErrored,
143
+ publish as publish3,
144
+ subscribe as subscribe3,
145
+ subscribeAll,
146
+ summarizeOrphanedTurns,
147
+ summarizeTurn,
148
+ takeErrored,
149
+ trackToolUse
150
+ } from "./chunk-K23P5Q5N.js";
151
+ import {
152
+ ORIENTATION_MARKER,
153
+ PROMPT_DEFAULTS,
154
+ PROMPT_KEYS,
155
+ getMindPromptDefaults,
156
+ getPrompt,
157
+ getPromptIfCustom,
158
+ substitute
159
+ } from "./chunk-JCMGDCGX.js";
134
160
  import {
135
161
  SEED_SKILLS,
136
162
  STANDARD_SKILLS,
@@ -150,7 +176,7 @@ import {
150
176
  syncBuiltinSkills,
151
177
  uninstallSkill,
152
178
  updateSkill
153
- } from "./chunk-7ZUB63PS.js";
179
+ } from "./chunk-IWJSNMSY.js";
154
180
  import {
155
181
  addMessage,
156
182
  createChannel,
@@ -178,51 +204,32 @@ import {
178
204
  listConversationsForUser,
179
205
  listConversationsWithParticipants,
180
206
  markConversationRead,
181
- publish,
207
+ publish as publish2,
182
208
  setConversationPrivate,
183
209
  subscribe as subscribe2,
184
210
  updateChannelSettings
185
- } from "./chunk-OP4JG2BS.js";
186
- import {
187
- readVoluteConfig,
188
- writeVoluteConfig
189
- } from "./chunk-PFZBNKUH.js";
211
+ } from "./chunk-HPGN6XRT.js";
190
212
  import {
191
213
  isSandboxEnabled,
192
214
  wrapForSandbox
193
- } from "./chunk-JZ62SYWR.js";
215
+ } from "./chunk-M3XDVS5Q.js";
194
216
  import {
195
217
  getActiveMinds,
196
218
  onMindEvent,
197
219
  stopAll
198
- } from "./chunk-EJVPTVTI.js";
220
+ } from "./chunk-MTRMQFCQ.js";
199
221
  import {
200
- extractTextContent
201
- } from "./chunk-4JOQZPCE.js";
222
+ normalizeAvatar,
223
+ safeResolveWithinBase
224
+ } from "./chunk-VMCZBXF2.js";
202
225
  import {
203
- PROMPT_DEFAULTS,
204
- PROMPT_KEYS,
205
- assignSession,
206
- completeOrphanedTurns,
207
- completeTurn,
208
- createTurn,
209
- getActiveTurnId,
210
- getMindPromptDefaults,
211
- getPrompt,
212
- getPromptIfCustom,
213
- getToolUseEventId,
214
- initSummarizer,
215
- markErrored,
216
- publish as publish2,
217
- subscribe as subscribe3,
218
- subscribeAll,
219
- substitute,
220
- summarizeOrphanedTurns,
221
- summarizeTurn,
222
- takeErrored,
223
- trackToolUse
224
- } from "./chunk-UYLGEDVD.js";
225
- import "./chunk-TQJ6ZYVY.js";
226
+ readVoluteConfig,
227
+ writeVoluteConfig
228
+ } from "./chunk-PFZBNKUH.js";
229
+ import {
230
+ extractTextContent
231
+ } from "./chunk-EIQDZC43.js";
232
+ import "./chunk-CBOIHNMR.js";
226
233
  import {
227
234
  chownMindDir,
228
235
  createMindUser,
@@ -233,7 +240,7 @@ import {
233
240
  isIsolationEnabled,
234
241
  resolveVoluteBin,
235
242
  wrapForIsolation
236
- } from "./chunk-H2X54EHC.js";
243
+ } from "./chunk-DGGZVZTM.js";
237
244
  import {
238
245
  addCustomModel,
239
246
  aiCompleteUtility,
@@ -255,12 +262,12 @@ import {
255
262
  setProviderRefreshHook,
256
263
  setUtilityModel,
257
264
  unqualifyModelId
258
- } from "./chunk-IHKGWTGT.js";
265
+ } from "./chunk-LWDTP6DH.js";
259
266
  import {
260
267
  isSetupComplete,
261
268
  readGlobalConfig,
262
269
  writeGlobalConfig
263
- } from "./chunk-F7H6ATM4.js";
270
+ } from "./chunk-SKNXMUIU.js";
264
271
  import {
265
272
  approveUser,
266
273
  changePassword,
@@ -279,11 +286,14 @@ import {
279
286
  syncMindProfile,
280
287
  updateUserProfile,
281
288
  verifyUser
282
- } from "./chunk-T6HYVHPG.js";
289
+ } from "./chunk-ODDGGLM6.js";
283
290
  import {
284
291
  broadcast,
292
+ getEventsSince,
293
+ nextEventId,
294
+ publish,
285
295
  subscribe
286
- } from "./chunk-7C44R625.js";
296
+ } from "./chunk-LTLFOSA4.js";
287
297
  import {
288
298
  logBuffer,
289
299
  logger_default
@@ -292,6 +302,7 @@ import {
292
302
  buildVoluteSlug
293
303
  } from "./chunk-LGNUFVMR.js";
294
304
  import {
305
+ SPIRIT_NAME,
295
306
  addMind,
296
307
  addVariant,
297
308
  daemonLoopback,
@@ -316,7 +327,7 @@ import {
316
327
  validateMindName,
317
328
  voluteHome,
318
329
  voluteSystemDir
319
- } from "./chunk-ALKYQULU.js";
330
+ } from "./chunk-WW6C3PLV.js";
320
331
  import {
321
332
  activity,
322
333
  channels,
@@ -327,7 +338,7 @@ import {
327
338
  systemPrompts,
328
339
  turns,
329
340
  users
330
- } from "./chunk-ZV7MFISX.js";
341
+ } from "./chunk-2MJ7EOMT.js";
331
342
  import "./chunk-K3NQKI34.js";
332
343
 
333
344
  // packages/daemon/src/daemon.ts
@@ -341,9 +352,12 @@ import { format } from "util";
341
352
  import { CronExpressionParser } from "cron-parser";
342
353
  var blog = logger_default.child("backup");
343
354
  var DEFAULT_BACKUP_SCHEDULE = "0 3 * * *";
355
+ var NOTIFY_EVERY_N_FAILURES = 5;
344
356
  var BackupManager = class {
345
357
  interval = null;
346
- lastFiredMinute = -1;
358
+ /** Epoch-ms of the most recent scheduled fire this process has serviced. */
359
+ lastServicedFireMs = -1;
360
+ consecutiveFailures = 0;
347
361
  warnedUnconfigured = false;
348
362
  start() {
349
363
  this.interval = setInterval(() => {
@@ -355,9 +369,16 @@ var BackupManager = class {
355
369
  this.interval = null;
356
370
  }
357
371
  /**
358
- * True at most once per scheduled fire minute (records the minute it
359
- * consumed), and only when backups are enabled and runnable. Not a pure
360
- * predicate — a second call in the same minute returns false.
372
+ * True at most once per scheduled fire (records the fire it consumed), and
373
+ * only when backups are enabled and runnable. Not a pure predicate — a second
374
+ * call for the same fire returns false.
375
+ *
376
+ * Catch-up: if the machine was asleep or the daemon was down across a
377
+ * scheduled fire (a laptop at 3am), the fire is picked up on the next tick so
378
+ * long as it falls after the last successful run (`lastRun` in
379
+ * backup-state.json). Without an anchor (no successful backup yet) only the
380
+ * exact on-time minute fires, so freshly enabling backups doesn't trigger an
381
+ * immediate run.
361
382
  */
362
383
  isDue(now = /* @__PURE__ */ new Date()) {
363
384
  const config2 = readGlobalConfig().backup;
@@ -372,27 +393,81 @@ var BackupManager = class {
372
393
  this.warnedUnconfigured = false;
373
394
  const cron = config2.schedule || DEFAULT_BACKUP_SCHEDULE;
374
395
  const epochMinute = Math.floor(now.getTime() / 6e4);
375
- if (epochMinute === this.lastFiredMinute) return false;
376
- let prevMinute;
396
+ let prevFireMs;
377
397
  try {
378
398
  const interval = CronExpressionParser.parse(cron, { currentDate: now });
379
- prevMinute = Math.floor(interval.prev().toDate().getTime() / 6e4);
399
+ prevFireMs = interval.prev().toDate().getTime();
380
400
  } catch (err) {
381
401
  blog.warn(`invalid backup cron "${cron}"`, logger_default.errorData(err));
382
402
  return false;
383
403
  }
384
- if (prevMinute !== epochMinute) return false;
385
- this.lastFiredMinute = epochMinute;
404
+ if (prevFireMs <= this.lastServicedFireMs) return false;
405
+ const lastRunMs = this.readLastRunMs();
406
+ if (lastRunMs !== null && prevFireMs <= lastRunMs) return false;
407
+ const onTime = Math.floor(prevFireMs / 6e4) === epochMinute;
408
+ if (!onTime) {
409
+ if (lastRunMs === null) return false;
410
+ blog.warn(
411
+ `missed scheduled backup fire at ${new Date(prevFireMs).toISOString()}; running catch-up`
412
+ );
413
+ }
414
+ this.lastServicedFireMs = prevFireMs;
386
415
  return true;
387
416
  }
417
+ readLastRunMs() {
418
+ const lastRun = readBackupState().lastRun;
419
+ if (!lastRun) return null;
420
+ const ms = Date.parse(lastRun);
421
+ return Number.isNaN(ms) ? null : ms;
422
+ }
388
423
  async tick() {
389
424
  if (!this.isDue()) return;
390
425
  blog.info("scheduled backup starting");
391
426
  try {
392
427
  await runBackup();
428
+ this.recordSuccess();
393
429
  } catch (err) {
394
430
  blog.error("scheduled backup failed", logger_default.errorData(err));
431
+ await this.recordFailure(err);
432
+ }
433
+ }
434
+ recordSuccess() {
435
+ this.consecutiveFailures = 0;
436
+ }
437
+ /**
438
+ * Track consecutive scheduled-backup failures and actively notify the operator
439
+ * on the 1st and then every Nth. Returns whether it notified (for tests).
440
+ * Notification failures are swallowed — surfacing a backup failure must never
441
+ * take the daemon's backup loop down with it.
442
+ */
443
+ async recordFailure(err) {
444
+ this.consecutiveFailures += 1;
445
+ const n = this.consecutiveFailures;
446
+ if (n !== 1 && n % NOTIFY_EVERY_N_FAILURES !== 0) return false;
447
+ const reason = err instanceof Error ? err.message : String(err);
448
+ const summary = n === 1 ? `Scheduled backup failed: ${reason}` : `Scheduled backup has failed ${n} times in a row: ${reason}`;
449
+ try {
450
+ await publish({
451
+ type: "backup_failed",
452
+ mind: SPIRIT_NAME,
453
+ summary,
454
+ metadata: { consecutiveFailures: n, error: reason }
455
+ });
456
+ } catch (pubErr) {
457
+ blog.error("failed to publish backup-failure activity", logger_default.errorData(pubErr));
395
458
  }
459
+ try {
460
+ const { sendSystemMessage } = await import("./system-chat-N2F7HVY3.js");
461
+ await sendSystemMessage(
462
+ SPIRIT_NAME,
463
+ `${summary}
464
+
465
+ Backups are not being saved. Check \`volute backup status\` and the Backups settings tab.`
466
+ );
467
+ } catch (msgErr) {
468
+ blog.error("failed to send backup-failure system message", logger_default.errorData(msgErr));
469
+ }
470
+ return true;
396
471
  }
397
472
  };
398
473
  var backupManager = null;
@@ -728,6 +803,26 @@ async function cleanExpiredLogs() {
728
803
  await db.delete(mindHistory).where(and(eq(mindHistory.type, "log"), lt(mindHistory.created_at, cutoff)));
729
804
  }
730
805
 
806
+ // packages/daemon/src/lib/daemon/maintenance.ts
807
+ var MAINTENANCE_INTERVAL_MS = 60 * 60 * 1e3;
808
+ async function runMaintenance() {
809
+ try {
810
+ await cleanExpiredSessions();
811
+ } catch (err) {
812
+ logger_default.warn("maintenance: failed to clean expired sessions", logger_default.errorData(err));
813
+ }
814
+ try {
815
+ await cleanExpiredLogs();
816
+ } catch (err) {
817
+ logger_default.warn("maintenance: failed to clean expired logs", logger_default.errorData(err));
818
+ }
819
+ }
820
+ function startMaintenanceInterval(intervalMs = MAINTENANCE_INTERVAL_MS, task = runMaintenance) {
821
+ const handle = setInterval(() => void task(), intervalMs);
822
+ handle.unref?.();
823
+ return handle;
824
+ }
825
+
731
826
  // packages/daemon/src/web/api/system.ts
732
827
  import { getOAuthProvider, getOAuthProviders } from "@earendil-works/pi-ai/oauth";
733
828
  import { getBuiltinProviders } from "@earendil-works/pi-ai/providers/all";
@@ -961,7 +1056,12 @@ var app = new Hono().post("/restart", requireAdmin, (c) => {
961
1056
  }).get("/info", (c) => {
962
1057
  const config2 = readSystemsConfig();
963
1058
  const globalConfig = readGlobalConfig();
964
- return c.json({ system: config2?.system ?? null, name: globalConfig.name ?? null });
1059
+ const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone ?? null;
1060
+ return c.json({
1061
+ system: config2?.system ?? null,
1062
+ name: globalConfig.name ?? null,
1063
+ timezone
1064
+ });
965
1065
  }).put("/info", requireAdmin, zValidator("json", z.object({ name: z.string() })), (c) => {
966
1066
  const { name } = c.req.valid("json");
967
1067
  const config2 = readGlobalConfig();
@@ -1696,17 +1796,17 @@ var authenticated = new Hono3().use(authMiddleware).post("/change-password", zVa
1696
1796
  if (!AVATAR_MIME[ext]) {
1697
1797
  return c.json({ error: "Invalid file type (png, jpg, gif, webp only)" }, 400);
1698
1798
  }
1699
- let buffer2 = Buffer.from(await file.arrayBuffer());
1799
+ let buffer = Buffer.from(await file.arrayBuffer());
1700
1800
  let finalExt = ext;
1701
- const normalized = await normalizeAvatar(buffer2);
1801
+ const normalized = await normalizeAvatar(buffer);
1702
1802
  if (normalized) {
1703
- buffer2 = normalized.buffer;
1803
+ buffer = normalized.buffer;
1704
1804
  finalExt = normalized.ext;
1705
1805
  }
1706
1806
  const dir = avatarsDir();
1707
1807
  mkdirSync2(dir, { recursive: true });
1708
1808
  const filename = `avatar-${user.id}${finalExt}`;
1709
- writeFileSync2(resolve3(dir, filename), buffer2);
1809
+ writeFileSync2(resolve3(dir, filename), buffer);
1710
1810
  if (user.avatar && user.avatar !== filename) {
1711
1811
  const oldPath = resolve3(dir, user.avatar);
1712
1812
  rmSync(oldPath, { force: true });
@@ -2168,7 +2268,7 @@ var app4 = new Hono5().post("/:platform/inbound", zValidator4("json", inboundSch
2168
2268
  }
2169
2269
  const participants = await getParticipants(channel.id);
2170
2270
  if (!participants.some((p) => p.userId === puppet.id)) {
2171
- const { addParticipant } = await import("./conversations-JZTD6KLR.js");
2271
+ const { addParticipant } = await import("./conversations-C7IKYSHD.js");
2172
2272
  await addParticipant(channel.id, puppet.id);
2173
2273
  }
2174
2274
  const contentBlocks = body.content;
@@ -2452,7 +2552,7 @@ async function notifyMind(mindName, message) {
2452
2552
  const entry = await findMind(mindName);
2453
2553
  if (!entry) return;
2454
2554
  try {
2455
- const { sendSystemMessage } = await import("./system-chat-XOALE2RD.js");
2555
+ const { sendSystemMessage } = await import("./system-chat-N2F7HVY3.js");
2456
2556
  await sendSystemMessage(mindName, message);
2457
2557
  } catch (err) {
2458
2558
  logger_default.warn(`[file-sharing] notify mind ${mindName} failed`, logger_default.errorData(err));
@@ -2621,11 +2721,11 @@ var app9 = new Hono11().post("/:name/avatar", requireSelf(), async (c) => {
2621
2721
  if (!AVATAR_MIME2[ext]) {
2622
2722
  return c.json({ error: "Invalid file type (png, jpg, gif, webp only)" }, 400);
2623
2723
  }
2624
- let buffer2 = Buffer.from(await file.arrayBuffer());
2724
+ let buffer = Buffer.from(await file.arrayBuffer());
2625
2725
  let finalExt = ext;
2626
- const normalized = await normalizeAvatar(buffer2);
2726
+ const normalized = await normalizeAvatar(buffer);
2627
2727
  if (normalized) {
2628
- buffer2 = normalized.buffer;
2728
+ buffer = normalized.buffer;
2629
2729
  finalExt = normalized.ext;
2630
2730
  }
2631
2731
  const dir = entry.dir ?? mindDir(name);
@@ -2639,7 +2739,7 @@ var app9 = new Hono11().post("/:name/avatar", requireSelf(), async (c) => {
2639
2739
  if (oldAvatarPath) rmSync2(oldAvatarPath, { force: true });
2640
2740
  }
2641
2741
  mkdirSync3(homeDir, { recursive: true });
2642
- writeFileSync3(avatarPath, buffer2);
2742
+ writeFileSync3(avatarPath, buffer);
2643
2743
  const profile = config2.profile ?? {};
2644
2744
  profile.avatar = filename;
2645
2745
  config2.profile = profile;
@@ -2753,13 +2853,14 @@ import { and as and3, desc as desc2, eq as eq3, gte, inArray, sql } from "drizzl
2753
2853
  import { Hono as Hono12 } from "hono";
2754
2854
  async function resolveMindFilter(c) {
2755
2855
  const user = c.get("user");
2756
- if (user.role === "admin" || user.role === "system") {
2757
- return c.req.query("mind") ?? void 0;
2758
- }
2759
- return getBaseName(user.username);
2856
+ const privileged = user.role === "admin" || user.role === "system";
2857
+ return privileged ? c.req.query("mind") ?? void 0 : getBaseName(user.username);
2760
2858
  }
2761
- var history = new Hono12().get("/turns", async (c) => {
2762
- const mindFilter = await resolveMindFilter(c);
2859
+ var history = new Hono12().use("*", async (c, next) => {
2860
+ c.set("mindFilter", await resolveMindFilter(c));
2861
+ await next();
2862
+ }).get("/turns", async (c) => {
2863
+ const mindFilter = c.get("mindFilter");
2763
2864
  const turnIdFilter = c.req.query("turnId");
2764
2865
  const turnIdsFilter = c.req.query("turnIds");
2765
2866
  const limit = Math.min(Math.max(parseInt(c.req.query("limit") ?? "50", 10) || 50, 1), 200);
@@ -2794,7 +2895,7 @@ var history = new Hono12().get("/turns", async (c) => {
2794
2895
  inArray(mindHistory.turn_id, turnIds),
2795
2896
  sql`${mindHistory.type} IN ('inbound', 'outbound')`
2796
2897
  )
2797
- ).orderBy(mindHistory.created_at);
2898
+ ).orderBy(mindHistory.created_at, mindHistory.id);
2798
2899
  const msgsByTurnChannel = /* @__PURE__ */ new Map();
2799
2900
  function addToTurnChannel(turnId, channel, event) {
2800
2901
  let byChannel = msgsByTurnChannel.get(turnId);
@@ -2829,7 +2930,7 @@ var history = new Hono12().get("/turns", async (c) => {
2829
2930
  const label = isDM ? raw : raw.startsWith("#") ? raw : `#${raw}`;
2830
2931
  return { label, type: isDM ? "dm" : "channel" };
2831
2932
  }
2832
- const activityRows = await db.select().from(activity).where(inArray(activity.turn_id, turnIds)).orderBy(activity.created_at);
2933
+ const activityRows = await db.select().from(activity).where(inArray(activity.turn_id, turnIds)).orderBy(activity.created_at, activity.id);
2833
2934
  const activitiesByTurn = /* @__PURE__ */ new Map();
2834
2935
  for (const a of activityRows) {
2835
2936
  if (!a.turn_id) continue;
@@ -3001,7 +3102,7 @@ var history = new Hono12().get("/turns", async (c) => {
3001
3102
  });
3002
3103
  return c.json(result);
3003
3104
  }).get("/events", async (c) => {
3004
- const mindFilter = await resolveMindFilter(c);
3105
+ const mindFilter = c.get("mindFilter");
3005
3106
  const stream = new ReadableStream({
3006
3107
  start(controller) {
3007
3108
  const encoder = new TextEncoder();
@@ -3058,7 +3159,7 @@ var history = new Hono12().get("/turns", async (c) => {
3058
3159
  }).get("/summaries", async (c) => {
3059
3160
  const user = c.get("user");
3060
3161
  const privileged = user.role === "admin" || user.role === "system";
3061
- const mind = privileged ? c.req.query("mind") ?? "_system" : await getBaseName(user.username);
3162
+ const mind = c.get("mindFilter") ?? "_system";
3062
3163
  const period = c.req.query("period");
3063
3164
  const ids = c.req.query("ids");
3064
3165
  const from = c.req.query("from");
@@ -3089,8 +3190,9 @@ var history = new Hono12().get("/turns", async (c) => {
3089
3190
  }
3090
3191
  const db = await getDb();
3091
3192
  const conditions = [eq3(summaries.mind, mind), eq3(summaries.period, period)];
3092
- if (from) conditions.push(gte(summaries.period_key, from));
3093
- if (to) conditions.push(sql`${summaries.period_key} <= ${to}`);
3193
+ const normalizeBound = (bound) => period === "week" ? isoWeekKeyForDateStr(bound) : bound;
3194
+ if (from) conditions.push(gte(summaries.period_key, normalizeBound(from)));
3195
+ if (to) conditions.push(sql`${summaries.period_key} <= ${normalizeBound(to)}`);
3094
3196
  const rows = await db.select({
3095
3197
  id: summaries.id,
3096
3198
  mind: summaries.mind,
@@ -3113,7 +3215,7 @@ var history = new Hono12().get("/turns", async (c) => {
3113
3215
  });
3114
3216
  return c.json(result);
3115
3217
  }).get("/activity", async (c) => {
3116
- const mind = await resolveMindFilter(c);
3218
+ const mind = c.get("mindFilter");
3117
3219
  const from = c.req.query("from");
3118
3220
  const to = c.req.query("to");
3119
3221
  const limit = Math.min(Math.max(parseInt(c.req.query("limit") ?? "100", 10) || 100, 1), 500);
@@ -3377,12 +3479,12 @@ import {
3377
3479
  } from "fs";
3378
3480
  import { relative, resolve as resolve10 } from "path";
3379
3481
  import { zValidator as zValidator6 } from "@hono/zod-validator";
3380
- import { and as and5, desc as desc4, eq as eq5, sql as sql3 } from "drizzle-orm";
3482
+ import { and as and5, desc as desc3, eq as eq5, sql as sql3 } from "drizzle-orm";
3381
3483
  import { Hono as Hono16 } from "hono";
3382
3484
  import { z as z6 } from "zod";
3383
3485
 
3384
3486
  // packages/daemon/src/lib/daemon/turn-lifecycle.ts
3385
- import { and as and4, desc as desc3, eq as eq4, inArray as inArray2, sql as sql2 } from "drizzle-orm";
3487
+ import { and as and4, eq as eq4, gte as gte2, inArray as inArray2, sql as sql2 } from "drizzle-orm";
3386
3488
 
3387
3489
  // packages/daemon/src/lib/daemon/error-classify.ts
3388
3490
  function classify(raw) {
@@ -3441,21 +3543,27 @@ function markDeliveredOnCleanTurn(mind, session) {
3441
3543
  );
3442
3544
  }
3443
3545
  }
3444
- async function linkPendingInbound(mind, turnId, channel) {
3445
- if (!channel) return;
3546
+ async function linkPendingInbound(mind, turnId, channel, session) {
3547
+ const scopeChannel = channel ?? session;
3548
+ if (!scopeChannel) return;
3446
3549
  const db = await getDb();
3447
- const recent = await db.select({ id: mindHistory.id }).from(mindHistory).where(
3448
- and4(
3449
- eq4(mindHistory.mind, mind),
3450
- eq4(mindHistory.type, "inbound"),
3451
- sql2`${mindHistory.turn_id} IS NULL`,
3452
- eq4(mindHistory.channel, channel)
3453
- )
3454
- ).orderBy(desc3(mindHistory.id)).limit(5);
3455
- if (recent.length === 0) return;
3456
- const ids = recent.map((r) => r.id);
3550
+ let lowerBound;
3551
+ if (session) {
3552
+ const prev = await db.select({ max: sql2`max(${turns.created_at})` }).from(turns).where(and4(eq4(turns.mind, mind), eq4(turns.session, session), sql2`${turns.id} != ${turnId}`)).get();
3553
+ lowerBound = prev?.max ?? void 0;
3554
+ }
3555
+ const conditions = [
3556
+ eq4(mindHistory.mind, mind),
3557
+ eq4(mindHistory.type, "inbound"),
3558
+ sql2`${mindHistory.turn_id} IS NULL`,
3559
+ eq4(mindHistory.channel, scopeChannel)
3560
+ ];
3561
+ if (lowerBound) conditions.push(gte2(mindHistory.created_at, lowerBound));
3562
+ const pending = await db.select({ id: mindHistory.id }).from(mindHistory).where(and4(...conditions)).orderBy(mindHistory.id);
3563
+ if (pending.length === 0) return;
3564
+ const ids = pending.map((r) => r.id);
3457
3565
  await db.update(mindHistory).set({ turn_id: turnId }).where(inArray2(mindHistory.id, ids));
3458
- await db.update(turns).set({ trigger_event_id: recent[0].id }).where(eq4(turns.id, turnId));
3566
+ await db.update(turns).set({ trigger_event_id: ids[0] }).where(eq4(turns.id, turnId));
3459
3567
  }
3460
3568
  async function handleMindEvent(mind, event) {
3461
3569
  if (event.type === "session_start" && event.session) {
@@ -3468,10 +3576,10 @@ async function handleMindEvent(mind, event) {
3468
3576
  if (!turnId) {
3469
3577
  llog.warn(`skipping turn tracking for ${mind}: createTurn failed`);
3470
3578
  } else {
3471
- publish2(mind, { mind, type: "turn_created", turnId });
3579
+ publish3(mind, { mind, type: "turn_created", turnId });
3472
3580
  if (event.session) await assignSession(mind, turnId, event.session);
3473
3581
  try {
3474
- await linkPendingInbound(mind, turnId, event.channel);
3582
+ await linkPendingInbound(mind, turnId, event.channel, event.session);
3475
3583
  } catch (err) {
3476
3584
  llog.warn(
3477
3585
  `failed to link trigger inbound for turn ${turnId} (mind: ${mind})`,
@@ -3514,7 +3622,7 @@ async function handleMindEvent(mind, event) {
3514
3622
  llog.error("failed to link tool_result to turn", logger_default.errorData(err));
3515
3623
  }
3516
3624
  }
3517
- publish2(mind, {
3625
+ publish3(mind, {
3518
3626
  mind,
3519
3627
  type: event.type,
3520
3628
  session: event.session,
@@ -3707,7 +3815,7 @@ async function cleanupVariant(variantName, projectRoot, variantPath, opts) {
3707
3815
  logger_default.warn(`failed to stop variant ${variantName}`, logger_default.errorData(err));
3708
3816
  }
3709
3817
  }
3710
- const { findMind: findMind2 } = await import("./registry-OUZBZ3Q4.js");
3818
+ const { findMind: findMind2 } = await import("./registry-UMMDRPRM.js");
3711
3819
  const variantEntry = await findMind2(variantName);
3712
3820
  const branchName = variantEntry?.branch ?? variantName;
3713
3821
  if (existsSync6(variantPath)) {
@@ -3936,7 +4044,7 @@ async function getMindStatus(name, port, registryRunning) {
3936
4044
  const manager = getMindManager();
3937
4045
  let status = "stopped";
3938
4046
  try {
3939
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-HCUH4PM4.js");
4047
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-3NA7ZJP3.js");
3940
4048
  if (getSleepManagerIfReady2()?.isSleeping(name)) {
3941
4049
  status = "sleeping";
3942
4050
  }
@@ -3964,6 +4072,25 @@ async function getMindStatus(name, port, registryRunning) {
3964
4072
  avatar: config2?.profile?.avatar
3965
4073
  };
3966
4074
  }
4075
+ function isPrivileged(c) {
4076
+ const role = c.get("user").role;
4077
+ return role === "admin" || role === "system";
4078
+ }
4079
+ function toPublicMind(entry, status, extras) {
4080
+ return {
4081
+ name: entry.name,
4082
+ created: entry.created,
4083
+ stage: entry.stage,
4084
+ mindType: entry.mindType,
4085
+ status: status.status,
4086
+ channels: status.channels,
4087
+ displayName: status.displayName,
4088
+ description: status.description,
4089
+ avatar: status.avatar,
4090
+ hasPages: extras.hasPages,
4091
+ lastActiveAt: extras.lastActiveAt ?? null
4092
+ };
4093
+ }
3967
4094
  var TEMPLATE_BRANCH = "volute/template";
3968
4095
  async function configureGitIdentity(mindName, opts) {
3969
4096
  const systemsConfig = readSystemsConfig();
@@ -4431,7 +4558,7 @@ var app13 = new Hono16().post("/", requireAdminOrSystem, zValidator6("json", cre
4431
4558
  applyInitFiles(dest);
4432
4559
  const { publicKeyPem } = generateIdentity(dest);
4433
4560
  {
4434
- const { readGlobalConfig: readGlobal } = await import("./setup-Y2EJI3GG.js");
4561
+ const { readGlobalConfig: readGlobal } = await import("./setup-KFYVWTXG.js");
4435
4562
  const mindDefaults = readGlobal().mindDefaults;
4436
4563
  const config2 = readVoluteConfig(dest);
4437
4564
  if (!config2) throw new Error("Failed to read volute.json after identity generation");
@@ -4513,7 +4640,7 @@ var app13 = new Hono16().post("/", requireAdminOrSystem, zValidator6("json", cre
4513
4640
  }
4514
4641
  if (body.stage === "seed") {
4515
4642
  const descLine = body.description ? `
4516
- The human who planted you described you as: "${body.description}"
4643
+ Your creator described you as: "${body.description}"
4517
4644
  ` : "";
4518
4645
  const seedSoulRaw = body.seedSoul ?? await getPrompt("seed_soul", { name, description: descLine });
4519
4646
  const seedSoul = body.seedSoul ? substitute(seedSoulRaw, { name, description: descLine }) : seedSoulRaw;
@@ -4521,7 +4648,7 @@ The human who planted you described you as: "${body.description}"
4521
4648
  }
4522
4649
  let skillSet = body.skills ?? (body.stage === "seed" ? SEED_SKILLS : getStandardSkillsWithExtensions());
4523
4650
  if (body.stage === "seed" && !body.skills) {
4524
- const { isImagegenEnabled } = await import("./setup-Y2EJI3GG.js");
4651
+ const { isImagegenEnabled } = await import("./setup-KFYVWTXG.js");
4525
4652
  if (isImagegenEnabled()) {
4526
4653
  skillSet = [...skillSet, "imagegen"];
4527
4654
  }
@@ -4539,7 +4666,7 @@ The human who planted you described you as: "${body.description}"
4539
4666
  try {
4540
4667
  const spiritEntry = await findMind("volute");
4541
4668
  if (spiritEntry) {
4542
- const { spiritDir } = await import("./spirit-DBTIW7P4.js");
4669
+ const { spiritDir } = await import("./spirit-WXN4IDBF.js");
4543
4670
  const sDir = spiritEntry.dir ?? spiritDir();
4544
4671
  const spiritConfig = readVoluteConfig(sDir) ?? {};
4545
4672
  const schedules = spiritConfig.schedules ?? [];
@@ -4554,7 +4681,7 @@ The human who planted you described you as: "${body.description}"
4554
4681
  });
4555
4682
  spiritConfig.schedules = schedules;
4556
4683
  writeVoluteConfig(sDir, spiritConfig);
4557
- const { getScheduler: getScheduler2 } = await import("./scheduler-P6DXLLXI.js");
4684
+ const { getScheduler: getScheduler2 } = await import("./scheduler-HQSDYJFY.js");
4558
4685
  getScheduler2().loadSchedules("volute", sDir);
4559
4686
  }
4560
4687
  }
@@ -4741,15 +4868,19 @@ ${user.trimEnd()}
4741
4868
  } catch {
4742
4869
  }
4743
4870
  }
4871
+ const privileged = isPrivileged(c);
4744
4872
  const minds = await Promise.all(
4745
4873
  entries.map(async (entry) => {
4746
4874
  const mindStatus = await getMindStatus(entry.name, entry.port, entry.running);
4747
4875
  const hasPages = existsSync7(resolve10(mindDir(entry.name), "home", "pages"));
4876
+ const lastActiveAt = lastActiveMap.get(entry.name) ?? null;
4877
+ if (!privileged) return toPublicMind(entry, mindStatus, { hasPages, lastActiveAt });
4748
4878
  return {
4749
4879
  ...entry,
4750
4880
  ...mindStatus,
4751
4881
  hasPages,
4752
- lastActiveAt: lastActiveMap.get(entry.name) ?? null
4882
+ templateStale: isTemplateStale(entry),
4883
+ lastActiveAt
4753
4884
  };
4754
4885
  })
4755
4886
  );
@@ -4761,6 +4892,10 @@ ${user.trimEnd()}
4761
4892
  const dir = entry.dir ?? mindDir(entry.parent ?? name);
4762
4893
  if (!existsSync7(dir)) return c.json({ error: "Mind directory missing" }, 404);
4763
4894
  const mindStatus = await getMindStatus(name, entry.port);
4895
+ const hasPages = existsSync7(resolve10(mindDir(name), "home", "pages"));
4896
+ if (!isPrivileged(c)) {
4897
+ return c.json(toPublicMind(entry, mindStatus, { hasPages }));
4898
+ }
4764
4899
  const variants = await findVariants(name);
4765
4900
  const manager = getMindManager();
4766
4901
  const variantStatuses = await Promise.all(
@@ -4773,8 +4908,13 @@ ${user.trimEnd()}
4773
4908
  return { name: s.name, port: s.port, status: variantStatus };
4774
4909
  })
4775
4910
  );
4776
- const hasPages = existsSync7(resolve10(mindDir(name), "home", "pages"));
4777
- return c.json({ ...entry, ...mindStatus, variants: variantStatuses, hasPages });
4911
+ return c.json({
4912
+ ...entry,
4913
+ ...mindStatus,
4914
+ variants: variantStatuses,
4915
+ hasPages,
4916
+ templateStale: isTemplateStale(entry)
4917
+ });
4778
4918
  }).get("/:name/context", requireSelf(), async (c) => proxyToMind(c, "context")).get("/:name/context/messages", requireSelf(), async (c) => proxyToMind(c, "context/messages")).post("/:name/start", requireSelf(), async (c) => {
4779
4919
  const name = c.req.param("name");
4780
4920
  const entry = await findMind(name);
@@ -4820,7 +4960,7 @@ ${user.trimEnd()}
4820
4960
  const manager = getMindManager();
4821
4961
  try {
4822
4962
  if (context?.type === "reload") {
4823
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-HCUH4PM4.js");
4963
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-3NA7ZJP3.js");
4824
4964
  const sleepState = getSleepManagerIfReady2()?.getState(name);
4825
4965
  if (sleepState?.sleeping) {
4826
4966
  logger_default.info(`skipping reload for ${name} during sleep \u2014 will apply on next wake`);
@@ -4947,7 +5087,7 @@ ${user.trimEnd()}
4947
5087
  const name = c.req.param("name");
4948
5088
  const entry = await findMind(name);
4949
5089
  if (!entry) return c.json({ error: "Mind not found" }, 404);
4950
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-HCUH4PM4.js");
5090
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-3NA7ZJP3.js");
4951
5091
  const sm = getSleepManagerIfReady2();
4952
5092
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
4953
5093
  return c.json(sm.getState(name));
@@ -4955,7 +5095,7 @@ ${user.trimEnd()}
4955
5095
  const name = c.req.param("name");
4956
5096
  const entry = await findMind(name);
4957
5097
  if (!entry) return c.json({ error: "Mind not found" }, 404);
4958
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-HCUH4PM4.js");
5098
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-3NA7ZJP3.js");
4959
5099
  const sm = getSleepManagerIfReady2();
4960
5100
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
4961
5101
  if (sm.isSleeping(name)) return c.json({ error: "Mind is already sleeping" }, 409);
@@ -4975,7 +5115,7 @@ ${user.trimEnd()}
4975
5115
  const name = c.req.param("name");
4976
5116
  const entry = await findMind(name);
4977
5117
  if (!entry) return c.json({ error: "Mind not found" }, 404);
4978
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-HCUH4PM4.js");
5118
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-3NA7ZJP3.js");
4979
5119
  const sm = getSleepManagerIfReady2();
4980
5120
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
4981
5121
  const sleepState = sm.getState(name);
@@ -4990,7 +5130,7 @@ ${user.trimEnd()}
4990
5130
  const name = c.req.param("name");
4991
5131
  const entry = await findMind(name);
4992
5132
  if (!entry) return c.json({ error: "Mind not found" }, 404);
4993
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-HCUH4PM4.js");
5133
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-3NA7ZJP3.js");
4994
5134
  const sm = getSleepManagerIfReady2();
4995
5135
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
4996
5136
  const flushed = await sm.flushQueuedMessages(name);
@@ -5018,7 +5158,7 @@ ${user.trimEnd()}
5018
5158
  }
5019
5159
  config2.profile = profile;
5020
5160
  writeVoluteConfig(dir, config2);
5021
- const { syncMindProfile: syncMindProfile2 } = await import("./auth-FLAEXXZK.js");
5161
+ const { syncMindProfile: syncMindProfile2 } = await import("./auth-A4VGHADI.js");
5022
5162
  await syncMindProfile2(name, profile);
5023
5163
  broadcast({ type: "profile_updated", mind: name, summary: `${name} profile updated` });
5024
5164
  return c.json({ ok: true });
@@ -5028,7 +5168,6 @@ ${user.trimEnd()}
5028
5168
  if (!entry) return c.json({ error: "Mind not found" }, 404);
5029
5169
  if (entry.stage !== "seed") return c.json({ output: "" });
5030
5170
  const db = await getDb();
5031
- const ORIENTATION_MARKER = "You don't have a soul yet";
5032
5171
  const rawCreator = Number(process.env.VOLUTE_NURTURE_CREATOR_MINUTES);
5033
5172
  const creatorThreshold = Number.isNaN(rawCreator) ? 5 : rawCreator;
5034
5173
  const rawSpirit = Number(process.env.VOLUTE_NURTURE_SPIRIT_MINUTES);
@@ -5041,14 +5180,14 @@ ${user.trimEnd()}
5041
5180
  sql3`${mindHistory.sender} != ${name}`,
5042
5181
  sql3`${mindHistory.sender} IS NOT NULL`
5043
5182
  )
5044
- ).orderBy(desc4(mindHistory.created_at)).limit(1);
5183
+ ).orderBy(desc3(mindHistory.created_at)).limit(1);
5045
5184
  const lastSpiritMsg = await db.select({ created_at: mindHistory.created_at }).from(mindHistory).where(
5046
5185
  and5(
5047
5186
  eq5(mindHistory.mind, name),
5048
5187
  eq5(mindHistory.type, "inbound"),
5049
5188
  eq5(mindHistory.sender, "volute")
5050
5189
  )
5051
- ).orderBy(desc4(mindHistory.created_at)).limit(1);
5190
+ ).orderBy(desc3(mindHistory.created_at)).limit(1);
5052
5191
  const now = Date.now();
5053
5192
  const creatorTime = lastCreatorMsg[0] ? new Date(lastCreatorMsg[0].created_at).getTime() : 0;
5054
5193
  const spiritTime = lastSpiritMsg[0] ? new Date(lastSpiritMsg[0].created_at).getTime() : 0;
@@ -5065,7 +5204,7 @@ ${user.trimEnd()}
5065
5204
  const config2 = readVoluteConfig(dir);
5066
5205
  const displayNameSet = !!config2?.profile?.displayName;
5067
5206
  const avatarSet = !!config2?.profile?.avatar;
5068
- const { isImagegenEnabled } = await import("./setup-Y2EJI3GG.js");
5207
+ const { isImagegenEnabled } = await import("./setup-KFYVWTXG.js");
5069
5208
  const imagegenEnabled = isImagegenEnabled();
5070
5209
  const done = [];
5071
5210
  const remaining = [];
@@ -5103,7 +5242,7 @@ ${user.trimEnd()}
5103
5242
  try {
5104
5243
  const spiritEntry = await findMind("volute");
5105
5244
  if (spiritEntry) {
5106
- const { spiritDir } = await import("./spirit-DBTIW7P4.js");
5245
+ const { spiritDir } = await import("./spirit-WXN4IDBF.js");
5107
5246
  const sDir = spiritEntry.dir ?? spiritDir();
5108
5247
  const spiritConfig = readVoluteConfig(sDir);
5109
5248
  if (spiritConfig?.schedules) {
@@ -5111,7 +5250,7 @@ ${user.trimEnd()}
5111
5250
  spiritConfig.schedules = spiritConfig.schedules.filter((s) => s.id !== nurtureId);
5112
5251
  if (spiritConfig.schedules.length === 0) spiritConfig.schedules = void 0;
5113
5252
  writeVoluteConfig(sDir, spiritConfig);
5114
- const { getScheduler: getScheduler2 } = await import("./scheduler-P6DXLLXI.js");
5253
+ const { getScheduler: getScheduler2 } = await import("./scheduler-HQSDYJFY.js");
5115
5254
  getScheduler2().loadSchedules("volute", sDir);
5116
5255
  }
5117
5256
  }
@@ -5546,7 +5685,7 @@ ${user.trimEnd()}
5546
5685
  }
5547
5686
  writeVoluteConfig(dir, existing);
5548
5687
  if (body.unescapeNewlines !== void 0) {
5549
- const { clearEchoTextCache } = await import("./echo-text-6JJOPUD4.js");
5688
+ const { clearEchoTextCache } = await import("./echo-text-SEQCLGAT.js");
5550
5689
  clearEchoTextCache(name);
5551
5690
  }
5552
5691
  const needsConfigJson = body.model !== void 0 || body.thinkingLevel !== void 0 || body.maxThinkingTokens !== void 0 || body.compaction !== void 0;
@@ -5623,7 +5762,7 @@ ${user.trimEnd()}
5623
5762
  }
5624
5763
  if (entry.mindType === "spirit" && body.model !== void 0) {
5625
5764
  try {
5626
- const { readGlobalConfig: readGlobalConfig2, writeGlobalConfig: writeGlobalConfig2 } = await import("./setup-Y2EJI3GG.js");
5765
+ const { readGlobalConfig: readGlobalConfig2, writeGlobalConfig: writeGlobalConfig2 } = await import("./setup-KFYVWTXG.js");
5627
5766
  const globalConfig = readGlobalConfig2();
5628
5767
  globalConfig.spiritModel = body.model;
5629
5768
  writeGlobalConfig2(globalConfig);
@@ -5646,12 +5785,27 @@ ${user.trimEnd()}
5646
5785
  logger_default.error(`failed to get pending deliveries for ${baseName}`, logger_default.errorData(err));
5647
5786
  return c.json({ error: "Failed to retrieve pending messages" }, 500);
5648
5787
  }
5788
+ }).post("/:name/gates/decline", requireSelf(), async (c) => {
5789
+ const name = c.req.param("name");
5790
+ const body = await c.req.json().catch(() => ({}));
5791
+ const channel = body.channel?.trim();
5792
+ if (!channel) return c.json({ error: "channel required" }, 400);
5793
+ try {
5794
+ const archived = await getDeliveryManager().declineChannel(name, channel);
5795
+ return c.json({ ok: true, channel, archived });
5796
+ } catch (err) {
5797
+ if (err instanceof Error && err.message.includes("not initialized")) {
5798
+ return c.json({ error: "Delivery manager not available" }, 503);
5799
+ }
5800
+ logger_default.error(`failed to decline channel ${channel} for ${name}`, logger_default.errorData(err));
5801
+ return c.json({ error: "Failed to decline channel" }, 500);
5802
+ }
5649
5803
  }).post("/:name/ai/complete", requireSelf(), async (c) => {
5650
5804
  const body = await c.req.json();
5651
5805
  if (!body.systemPrompt || !body.message) {
5652
5806
  return c.json({ error: "systemPrompt and message required" }, 400);
5653
5807
  }
5654
- const { aiComplete: aiCompleteFn, isAiConfigured } = await import("./ai-service-UODCCE4O.js");
5808
+ const { aiComplete: aiCompleteFn, isAiConfigured } = await import("./ai-service-CVZEYKTK.js");
5655
5809
  if (!isAiConfigured()) {
5656
5810
  return c.json({ error: "AI service not configured" }, 503);
5657
5811
  }
@@ -5817,7 +5971,7 @@ ${user.trimEnd()}
5817
5971
  eq5(mindHistory.session, currentSession),
5818
5972
  sql3`${mindHistory.turn_id} IS NOT NULL`
5819
5973
  )
5820
- ).orderBy(desc4(mindHistory.created_at)).limit(1);
5974
+ ).orderBy(desc3(mindHistory.created_at)).limit(1);
5821
5975
  if (lastTurn.length > 0 && lastTurn[0].turn_id) {
5822
5976
  const firstEvent = await db.select({ created_at: mindHistory.created_at }).from(mindHistory).where(eq5(mindHistory.turn_id, lastTurn[0].turn_id)).orderBy(mindHistory.created_at).limit(1);
5823
5977
  if (firstEvent.length > 0) {
@@ -5840,7 +5994,7 @@ ${user.trimEnd()}
5840
5994
  session: turns.session,
5841
5995
  content: summaries.content,
5842
5996
  created_at: summaries.created_at
5843
- }).from(summaries).innerJoin(turns, eq5(turns.id, summaries.period_key)).where(and5(...conditions)).orderBy(desc4(summaries.created_at)).limit(50);
5997
+ }).from(summaries).innerJoin(turns, eq5(turns.id, summaries.period_key)).where(and5(...conditions)).orderBy(desc3(summaries.created_at)).limit(50);
5844
5998
  if (rows.length === 0) {
5845
5999
  return c.json({ context: null });
5846
6000
  }
@@ -5891,7 +6045,7 @@ ${lines.join("\n")}` });
5891
6045
  metadata: summaries.metadata,
5892
6046
  created_at: summaries.created_at,
5893
6047
  session: turns.session
5894
- }).from(summaries).innerJoin(turns, eq5(turns.id, summaries.period_key)).where(and5(...sumConditions)).orderBy(desc4(summaries.created_at)).limit(limit).offset(offset);
6048
+ }).from(summaries).innerJoin(turns, eq5(turns.id, summaries.period_key)).where(and5(...sumConditions)).orderBy(desc3(summaries.created_at)).limit(limit).offset(offset);
5895
6049
  return c.json(
5896
6050
  sumRows2.map((r) => ({
5897
6051
  id: r.id,
@@ -5908,7 +6062,7 @@ ${lines.join("\n")}` });
5908
6062
  }))
5909
6063
  );
5910
6064
  }
5911
- const sumRows = await db.select().from(summaries).where(and5(...sumConditions)).orderBy(desc4(summaries.created_at)).limit(limit).offset(offset);
6065
+ const sumRows = await db.select().from(summaries).where(and5(...sumConditions)).orderBy(desc3(summaries.created_at)).limit(limit).offset(offset);
5912
6066
  return c.json(
5913
6067
  sumRows.map((r) => ({
5914
6068
  id: r.id,
@@ -5937,7 +6091,7 @@ ${lines.join("\n")}` });
5937
6091
  );
5938
6092
  break;
5939
6093
  }
5940
- const rows = await db.select().from(mindHistory).where(and5(...conditions)).orderBy(desc4(mindHistory.created_at)).limit(limit).offset(offset);
6094
+ const rows = await db.select().from(mindHistory).where(and5(...conditions)).orderBy(desc3(mindHistory.created_at)).limit(limit).offset(offset);
5941
6095
  return c.json(rows);
5942
6096
  });
5943
6097
  var minds_default = app13;
@@ -6000,76 +6154,93 @@ var slog2 = logger_default.child("schedules");
6000
6154
  function readSchedules(dir) {
6001
6155
  return readVoluteConfig(dir)?.schedules ?? [];
6002
6156
  }
6003
- function writeSchedules(name, dir, schedules) {
6004
- const config2 = readVoluteConfig(dir) ?? {};
6005
- config2.schedules = schedules.length > 0 ? schedules : void 0;
6006
- writeVoluteConfig(dir, config2);
6007
- getScheduler().loadSchedules(name, dir);
6008
- getSleepManagerIfReady()?.invalidateSleepConfig(name);
6009
- fireWebhook({
6010
- event: "schedule_changed",
6011
- mind: name,
6012
- data: { schedules }
6013
- });
6014
- }
6015
- var app15 = new Hono18().get("/:name/clock/status", requireSelf(), async (c) => {
6016
- const name = c.req.param("name");
6017
- const entry = await findMind(name);
6018
- if (!entry) return c.json({ error: "Mind not found" }, 404);
6019
- const dir = entry.dir ?? mindDir(name);
6020
- const sleepManager = getSleepManagerIfReady();
6021
- const sleepState = sleepManager?.getState(name) ?? null;
6022
- const sleepConfig = sleepManager?.getSleepConfig(name) ?? null;
6023
- const schedules = readSchedules(dir);
6024
- const now = /* @__PURE__ */ new Date();
6157
+ var UPCOMING_HORIZON_MS = 24 * 60 * 60 * 1e3;
6158
+ function computeClockEvents(schedules, sleepState, sleepConfig, now) {
6025
6159
  const upcoming = [];
6026
6160
  const previous = [];
6161
+ const effectiveWake = sleepState?.sleeping ? sleepState.scheduledWakeAt ?? sleepState.voluntaryWakeAt : null;
6162
+ const effectiveWakeDate = effectiveWake ? new Date(effectiveWake) : null;
6163
+ const sleepFate = (s, fireDate) => {
6164
+ if (!effectiveWakeDate || fireDate >= effectiveWakeDate) return {};
6165
+ const ws = s.whileSleeping ?? "queue";
6166
+ if (ws === "skip") return { willSkip: true };
6167
+ if (ws === "trigger-wake") return {};
6168
+ return { willQueue: true };
6169
+ };
6027
6170
  for (const s of schedules) {
6028
6171
  if (!s.enabled) continue;
6029
6172
  if (s.fireAt) {
6030
6173
  const fireDate = new Date(s.fireAt);
6031
6174
  if (fireDate >= now) {
6032
- upcoming.push({ id: s.id, at: fireDate.toISOString(), type: "timer" });
6175
+ upcoming.push({
6176
+ id: s.id,
6177
+ at: fireDate.toISOString(),
6178
+ type: "timer",
6179
+ ...sleepFate(s, fireDate)
6180
+ });
6033
6181
  }
6034
6182
  } else if (s.cron) {
6035
6183
  try {
6036
- const interval = CronExpressionParser3.parse(s.cron);
6037
- const next = interval.next().toDate();
6038
- upcoming.push({ id: s.id, at: next.toISOString(), type: "cron" });
6184
+ const next = CronExpressionParser3.parse(s.cron, { currentDate: now }).next().toDate();
6185
+ upcoming.push({ id: s.id, at: next.toISOString(), type: "cron", ...sleepFate(s, next) });
6039
6186
  } catch {
6040
- slog2.warn(`invalid cron "${s.cron}" for schedule "${s.id}" of ${name}`);
6187
+ slog2.warn(`invalid cron "${s.cron}" for schedule "${s.id}"`);
6041
6188
  }
6042
6189
  try {
6043
- const prevInterval = CronExpressionParser3.parse(s.cron);
6044
- const prev = prevInterval.prev().toDate();
6190
+ const prev = CronExpressionParser3.parse(s.cron, { currentDate: now }).prev().toDate();
6045
6191
  previous.push({ id: s.id, at: prev.toISOString() });
6046
6192
  } catch {
6047
6193
  }
6048
6194
  }
6049
6195
  }
6050
6196
  if (sleepState?.sleeping) {
6051
- if (sleepState.scheduledWakeAt) {
6052
- upcoming.push({ id: "sleep", at: sleepState.scheduledWakeAt, type: "cron" });
6197
+ if (effectiveWake) {
6198
+ upcoming.push({ id: "wake", at: effectiveWake, type: "cron" });
6053
6199
  }
6054
6200
  if (sleepState.sleepingSince) {
6055
6201
  previous.push({ id: "sleep", at: sleepState.sleepingSince });
6056
6202
  }
6057
6203
  } else if (sleepConfig?.enabled && sleepConfig.schedule) {
6058
6204
  try {
6059
- const sleepInterval = CronExpressionParser3.parse(sleepConfig.schedule.sleep);
6060
- const nextSleep = sleepInterval.next().toDate();
6205
+ const nextSleep = CronExpressionParser3.parse(sleepConfig.schedule.sleep, { currentDate: now }).next().toDate();
6061
6206
  upcoming.push({ id: "sleep", at: nextSleep.toISOString(), type: "cron" });
6062
6207
  } catch {
6063
6208
  }
6064
6209
  try {
6065
- const prevWakeInterval = CronExpressionParser3.parse(sleepConfig.schedule.wake);
6066
- const prevWake = prevWakeInterval.prev().toDate();
6067
- previous.push({ id: "sleep", at: prevWake.toISOString() });
6210
+ const prevWake = CronExpressionParser3.parse(sleepConfig.schedule.wake, { currentDate: now }).prev().toDate();
6211
+ previous.push({ id: "wake", at: prevWake.toISOString() });
6068
6212
  } catch {
6069
6213
  }
6070
6214
  }
6071
- upcoming.sort((a, b) => a.at.localeCompare(b.at));
6215
+ const horizon = now.getTime() + UPCOMING_HORIZON_MS;
6216
+ const withinHorizon = upcoming.filter((e) => new Date(e.at).getTime() <= horizon);
6217
+ withinHorizon.sort((a, b) => a.at.localeCompare(b.at));
6072
6218
  previous.sort((a, b) => b.at.localeCompare(a.at));
6219
+ return { upcoming: withinHorizon, previous };
6220
+ }
6221
+ function writeSchedules(name, dir, schedules) {
6222
+ const config2 = readVoluteConfig(dir) ?? {};
6223
+ config2.schedules = schedules.length > 0 ? schedules : void 0;
6224
+ writeVoluteConfig(dir, config2);
6225
+ getScheduler().loadSchedules(name, dir);
6226
+ getSleepManagerIfReady()?.invalidateSleepConfig(name);
6227
+ fireWebhook({
6228
+ event: "schedule_changed",
6229
+ mind: name,
6230
+ data: { schedules }
6231
+ });
6232
+ }
6233
+ var app15 = new Hono18().get("/:name/clock/status", requireSelf(), async (c) => {
6234
+ const name = c.req.param("name");
6235
+ const entry = await findMind(name);
6236
+ if (!entry) return c.json({ error: "Mind not found" }, 404);
6237
+ const dir = entry.dir ?? mindDir(name);
6238
+ const sleepManager = getSleepManagerIfReady();
6239
+ const sleepState = sleepManager?.getState(name) ?? null;
6240
+ const sleepConfig = sleepManager?.getSleepConfig(name) ?? null;
6241
+ const schedules = readSchedules(dir);
6242
+ const now = /* @__PURE__ */ new Date();
6243
+ const { upcoming, previous } = computeClockEvents(schedules, sleepState, sleepConfig, now);
6073
6244
  return c.json({ sleep: sleepState, sleepConfig, schedules, upcoming, previous });
6074
6245
  }).get("/:name/sleep/config", requireSelf(), async (c) => {
6075
6246
  const name = c.req.param("name");
@@ -6236,7 +6407,7 @@ var app15 = new Hono18().get("/:name/clock/status", requireSelf(), async (c) =>
6236
6407
  const body = await c.req.text();
6237
6408
  const message = `[webhook: ${event}] ${body}`;
6238
6409
  try {
6239
- const { sendSystemMessage } = await import("./system-chat-XOALE2RD.js");
6410
+ const { sendSystemMessage } = await import("./system-chat-N2F7HVY3.js");
6240
6411
  await sendSystemMessage(name, message);
6241
6412
  return c.json({ ok: true });
6242
6413
  } catch (err) {
@@ -6290,7 +6461,7 @@ setup.get("/status", async (c) => {
6290
6461
  let hasAccount = false;
6291
6462
  if (hasSystem) {
6292
6463
  try {
6293
- const { listUsersByType: listUsersByType2 } = await import("./auth-FLAEXXZK.js");
6464
+ const { listUsersByType: listUsersByType2 } = await import("./auth-A4VGHADI.js");
6294
6465
  const brains = await listUsersByType2("brain");
6295
6466
  hasAccount = brains.length > 0;
6296
6467
  } catch (err) {
@@ -6501,7 +6672,7 @@ setup.post("/account", async (c) => {
6501
6672
  }
6502
6673
  }
6503
6674
  try {
6504
- const { createUser: createUser2, updateUserProfile: updateUserProfile2 } = await import("./auth-FLAEXXZK.js");
6675
+ const { createUser: createUser2, updateUserProfile: updateUserProfile2 } = await import("./auth-A4VGHADI.js");
6505
6676
  const user = await createUser2(body.username.trim(), body.password);
6506
6677
  if (body.displayName?.trim()) {
6507
6678
  await updateUserProfile2(user.id, { display_name: body.displayName.trim() });
@@ -6547,7 +6718,7 @@ setup.post("/models", async (c) => {
6547
6718
  return c.json({ error: "Spirit model is required" }, 400);
6548
6719
  }
6549
6720
  try {
6550
- const { setEnabledModels: setEnabledModels3, setUtilityModel: setUtilityModel2 } = await import("./ai-service-UODCCE4O.js");
6721
+ const { setEnabledModels: setEnabledModels3, setUtilityModel: setUtilityModel2 } = await import("./ai-service-CVZEYKTK.js");
6551
6722
  setEnabledModels3(body.models);
6552
6723
  const config2 = readGlobalConfig();
6553
6724
  config2.spiritModel = body.spiritModel.trim();
@@ -6565,15 +6736,15 @@ setup.post("/complete", async (c) => {
6565
6736
  return c.json({ error: "Setup already complete" }, 400);
6566
6737
  }
6567
6738
  try {
6568
- const { ensureSpiritProject, syncSpiritTemplate } = await import("./spirit-DBTIW7P4.js");
6569
- const { startSpiritFull } = await import("./mind-service-3F4I46CI.js");
6739
+ const { ensureSpiritProject, syncSpiritTemplate } = await import("./spirit-WXN4IDBF.js");
6740
+ const { startSpiritFull } = await import("./mind-service-JFY7UG5E.js");
6570
6741
  await ensureSpiritProject();
6571
6742
  await syncSpiritTemplate();
6572
6743
  const warnings = [];
6573
6744
  const config2 = readGlobalConfig();
6574
6745
  if (config2.setup?.service && config2.setup?.type === "local") {
6575
6746
  try {
6576
- const { installUserService } = await import("./service-install-4HHFDDWY.js");
6747
+ const { installUserService } = await import("./service-install-FQ43Z4DF.js");
6577
6748
  const installed = await installUserService(config2.port, config2.hostname);
6578
6749
  if (!installed) {
6579
6750
  warnings.push("Service installation is not supported on this platform.");
@@ -6601,8 +6772,8 @@ setup.post("/complete", async (c) => {
6601
6772
  }
6602
6773
  let spiritConversationId;
6603
6774
  try {
6604
- const { getOrCreateMindUser: getOrCreateMindUser2, listUsersByType: listUsersByType2 } = await import("./auth-FLAEXXZK.js");
6605
- const { createConversation: createConversation2, findDMConversation: findDMConversation2 } = await import("./conversations-JZTD6KLR.js");
6775
+ const { getOrCreateMindUser: getOrCreateMindUser2, listUsersByType: listUsersByType2 } = await import("./auth-A4VGHADI.js");
6776
+ const { createConversation: createConversation2, findDMConversation: findDMConversation2 } = await import("./conversations-C7IKYSHD.js");
6606
6777
  const spiritUser = await getOrCreateMindUser2("volute");
6607
6778
  const brains = await listUsersByType2("brain");
6608
6779
  const admin2 = brains.find((u) => u.role === "admin");
@@ -6624,21 +6795,23 @@ setup.post("/complete", async (c) => {
6624
6795
  logger_default.info("setup complete state", { spiritConversationId, spiritStarted });
6625
6796
  if (spiritConversationId && spiritStarted) {
6626
6797
  try {
6627
- const { deliverMessage: deliverMessage2 } = await import("./message-delivery-3C65ZI6L.js");
6628
- const { listUsersByType: listUsers2 } = await import("./auth-FLAEXXZK.js");
6798
+ const { deliverMessage: deliverMessage2 } = await import("./message-delivery-ZE3YGJDD.js");
6799
+ const { listUsersByType: listUsers2 } = await import("./auth-A4VGHADI.js");
6629
6800
  const admins = await listUsers2("brain");
6630
6801
  const admin2 = admins.find((u) => u.role === "admin");
6631
- const adminName = admin2?.display_name || admin2?.username || "the admin";
6632
- const welcomePrompt = `Setup is complete. ${adminName} just finished setting up this system and is now in your chat. Welcome them and help them create their first mind. Keep it brief \u2014 a short greeting, then ask what kind of being they'd like to bring into the world. Remember: minds are beings with their own experience, not tools \u2014 ask about personality and character, never about tasks or utility.`;
6633
- await deliverMessage2("volute", {
6634
- content: [{ type: "text", text: welcomePrompt }],
6635
- channel: `@${admin2?.username ?? "system"}`,
6636
- conversationId: spiritConversationId,
6637
- sender: admin2?.username ?? "system",
6638
- isDM: true,
6639
- participants: ["volute", admin2?.username ?? "system"],
6640
- participantCount: 2
6641
- });
6802
+ if (admin2) {
6803
+ const adminName = admin2.display_name || admin2.username;
6804
+ const welcomePrompt = `Setup is complete. ${adminName} just finished setting up this system and is now in your chat. Welcome them and help them create their first mind. Keep it brief \u2014 a short greeting, then ask what kind of being they'd like to bring into the world. Remember: minds are beings with their own experience, not tools \u2014 ask about personality and character, never about tasks or utility.`;
6805
+ await deliverMessage2(SPIRIT_NAME, {
6806
+ content: [{ type: "text", text: welcomePrompt }],
6807
+ channel: `@${admin2.username}`,
6808
+ conversationId: spiritConversationId,
6809
+ sender: admin2.username,
6810
+ isDM: true,
6811
+ participants: [SPIRIT_NAME, admin2.username],
6812
+ participantCount: 2
6813
+ });
6814
+ }
6642
6815
  } catch (err) {
6643
6816
  logger_default.warn("failed to send welcome prompt to spirit (non-fatal)", logger_default.errorData(err));
6644
6817
  warnings.push(
@@ -6735,10 +6908,10 @@ var app16 = new Hono20().get("/", async (c) => {
6735
6908
  if (!file.name.endsWith(".zip")) {
6736
6909
  return c.json({ error: "Only .zip files are accepted" }, 400);
6737
6910
  }
6738
- const buffer2 = Buffer.from(await file.arrayBuffer());
6911
+ const buffer = Buffer.from(await file.arrayBuffer());
6739
6912
  const tmpDir = mkdtempSync(join(tmpdir(), "volute-skill-upload-"));
6740
6913
  try {
6741
- const zip = new AdmZip(buffer2);
6914
+ const zip = new AdmZip(buffer);
6742
6915
  for (const entry of zip.getEntries()) {
6743
6916
  const target = resolve12(tmpDir, entry.entryName);
6744
6917
  if (!target.startsWith(tmpDir)) {
@@ -6808,7 +6981,7 @@ var app17 = new Hono21().post("/:name/typing", zValidator8("json", typingSchema)
6808
6981
  map.delete(channel, sender);
6809
6982
  }
6810
6983
  if (isConversationId(channel)) {
6811
- publish(channel, { type: "typing", senders: map.get(channel) });
6984
+ publish2(channel, { type: "typing", senders: map.get(channel) });
6812
6985
  }
6813
6986
  return c.json({ ok: true });
6814
6987
  }).get("/:name/typing", (c) => {
@@ -6944,7 +7117,7 @@ var app19 = new Hono23().use("*", authMiddleware).get("/", async (c) => {
6944
7117
  var conversations_default = app19;
6945
7118
 
6946
7119
  // packages/daemon/src/web/api/v1/events.ts
6947
- import { desc as desc5, eq as eq7 } from "drizzle-orm";
7120
+ import { desc as desc4, eq as eq7 } from "drizzle-orm";
6948
7121
  import { Hono as Hono24 } from "hono";
6949
7122
  import { streamSSE as streamSSE4 } from "hono/streaming";
6950
7123
 
@@ -6971,42 +7144,6 @@ function getOnlineBrains() {
6971
7144
  return [...connections.keys()];
6972
7145
  }
6973
7146
 
6974
- // packages/daemon/src/lib/events/event-sequencer.ts
6975
- var BUFFER_SIZE = 1e3;
6976
- var MAX_AGE_MS = 5 * 60 * 1e3;
6977
- var nextId = 1;
6978
- var buffer = [];
6979
- function bufferEvent(data) {
6980
- const id = nextId++;
6981
- buffer.push({ id, data, timestamp: Date.now() });
6982
- while (buffer.length > BUFFER_SIZE) {
6983
- buffer.shift();
6984
- }
6985
- return id;
6986
- }
6987
- function nextEventId() {
6988
- return nextId++;
6989
- }
6990
- function getEventsSince(sinceId, allowedConversationIds, allowedActivityMind) {
6991
- const now = Date.now();
6992
- const startIdx = buffer.findIndex((e) => e.id > sinceId);
6993
- if (startIdx === -1) return [];
6994
- return buffer.slice(startIdx).filter((e) => {
6995
- if (now - e.timestamp >= MAX_AGE_MS) return false;
6996
- const data = e.data;
6997
- switch (data.event) {
6998
- case "snapshot":
6999
- return false;
7000
- case "conversation":
7001
- return allowedConversationIds.has(data.conversationId);
7002
- case "activity":
7003
- return allowedActivityMind === void 0 || data.mind === allowedActivityMind;
7004
- default:
7005
- return false;
7006
- }
7007
- });
7008
- }
7009
-
7010
7147
  // packages/daemon/src/web/api/v1/events.ts
7011
7148
  var app20 = new Hono24().use("*", authMiddleware).get("/", async (c) => {
7012
7149
  const user = c.get("user");
@@ -7047,7 +7184,7 @@ var app20 = new Hono24().use("*", authMiddleware).get("/", async (c) => {
7047
7184
  let recentActivity = [];
7048
7185
  try {
7049
7186
  const db = await getDb();
7050
- recentActivity = await db.select().from(activity).where(activityMind ? eq7(activity.mind, activityMind) : void 0).orderBy(desc5(activity.created_at)).limit(50);
7187
+ recentActivity = await db.select().from(activity).where(activityMind ? eq7(activity.mind, activityMind) : void 0).orderBy(desc4(activity.created_at)).limit(50);
7051
7188
  recentActivity = recentActivity.map((row) => ({
7052
7189
  ...row,
7053
7190
  metadata: row.metadata ? JSON.parse(row.metadata) : null
@@ -7068,15 +7205,15 @@ var app20 = new Hono24().use("*", authMiddleware).get("/", async (c) => {
7068
7205
  data: JSON.stringify(snapshotData)
7069
7206
  });
7070
7207
  const unsubActivity = subscribe((event) => {
7208
+ if (activityMind !== void 0 && event.mind !== activityMind) return;
7209
+ const { seq, ...rest } = event;
7071
7210
  const data = {
7072
7211
  event: "activity",
7073
- ...event,
7074
- metadata: event.metadata ?? null
7212
+ ...rest,
7213
+ metadata: rest.metadata ?? null
7075
7214
  };
7076
- const eventId = bufferEvent(data);
7077
- if (activityMind !== void 0 && event.mind !== activityMind) return;
7078
7215
  stream.writeSSE({
7079
- id: String(eventId),
7216
+ id: String(seq),
7080
7217
  data: JSON.stringify(data)
7081
7218
  }).catch((err) => {
7082
7219
  if (!stream.aborted) logger_default.error("[v1-events] write error:", logger_default.errorData(err));
@@ -7085,10 +7222,10 @@ var app20 = new Hono24().use("*", authMiddleware).get("/", async (c) => {
7085
7222
  cleanups.push(unsubActivity);
7086
7223
  for (const conv of conversations2) {
7087
7224
  const unsubConv = subscribe2(conv.id, (event) => {
7088
- const data = { event: "conversation", conversationId: conv.id, ...event };
7089
- const eventId = bufferEvent(data);
7225
+ const { seq, ...rest } = event;
7226
+ const data = { event: "conversation", conversationId: conv.id, ...rest };
7090
7227
  stream.writeSSE({
7091
- id: String(eventId),
7228
+ id: String(seq),
7092
7229
  data: JSON.stringify(data)
7093
7230
  }).catch((err) => {
7094
7231
  if (!stream.aborted) logger_default.error("[v1-events] write error:", logger_default.errorData(err));
@@ -7126,12 +7263,9 @@ import { Hono as Hono25 } from "hono";
7126
7263
  import { spawn as spawn4 } from "child_process";
7127
7264
  import { closeSync, mkdirSync as mkdirSync8, openSync, readFileSync as readFileSync9 } from "fs";
7128
7265
  import { resolve as resolve13 } from "path";
7129
- function tsxBin(cwd) {
7130
- return resolve13(cwd, "node_modules", ".bin", "tsx");
7131
- }
7132
7266
  async function spawnServer(cwd, port, options) {
7133
- let cmd = tsxBin(cwd);
7134
- let args = ["src/server.ts", "--port", String(port)];
7267
+ let cmd = process.env.VOLUTE_NODE_PATH ?? process.execPath;
7268
+ let args = ["--import", "tsx", "src/server.ts", "--port", String(port)];
7135
7269
  if (options?.mindName) {
7136
7270
  if (isIsolationEnabled()) {
7137
7271
  [cmd, args] = await wrapForIsolation(cmd, args, options.mindName);
@@ -7456,8 +7590,8 @@ var app21 = new Hono25().get("/:name/variants", async (c) => {
7456
7590
  await chownMindDir(projectRoot, mindName);
7457
7591
  if (variantName.endsWith("-upgrade") || variantName === "upgrade") {
7458
7592
  try {
7459
- const { computeTemplateHash: computeTemplateHash2 } = await import("./template-hash-446HHH5V.js");
7460
- const { setMindTemplateHash: setMindTemplateHash2 } = await import("./registry-OUZBZ3Q4.js");
7593
+ const { computeTemplateHash: computeTemplateHash2 } = await import("./template-hash-GSOUARW4.js");
7594
+ const { setMindTemplateHash: setMindTemplateHash2 } = await import("./registry-UMMDRPRM.js");
7461
7595
  const tmpl = parentEntry.template ?? "claude";
7462
7596
  await setMindTemplateHash2(mindName, computeTemplateHash2(tmpl));
7463
7597
  } catch (err) {
@@ -7834,7 +7968,7 @@ var unifiedChatApp = new Hono27().post(
7834
7968
  });
7835
7969
  if (outboundTurnId) {
7836
7970
  const mindKey = senderBase ?? senderName;
7837
- publish2(mindKey, {
7971
+ publish3(mindKey, {
7838
7972
  mind: mindKey,
7839
7973
  type: "outbound",
7840
7974
  channel,
@@ -7960,6 +8094,9 @@ var app24 = new Hono28().get("/:name/conversations", async (c) => {
7960
8094
  if (!u) return c.json({ error: `User ${id} not found` }, 400);
7961
8095
  }
7962
8096
  const participantIds = [...participantSet];
8097
+ if (participantIds.length < 2) {
8098
+ return c.json({ error: "a conversation needs at least two distinct participants" }, 400);
8099
+ }
7963
8100
  if (participantIds.length > 2) {
7964
8101
  return c.json({ error: "Use channels for multi-participant conversations" }, 400);
7965
8102
  }
@@ -8243,27 +8380,27 @@ async function startDaemon(opts) {
8243
8380
  const DAEMON_TOKEN_PATH = resolve16(systemDir, "daemon-token");
8244
8381
  mkdirSync10(home, { recursive: true });
8245
8382
  ensureSystemDir();
8246
- const { migrateConfigSecrets, migrateSetupCompleted } = await import("./setup-Y2EJI3GG.js");
8383
+ const { migrateConfigSecrets, migrateSetupCompleted } = await import("./setup-KFYVWTXG.js");
8247
8384
  migrateConfigSecrets();
8248
8385
  migrateSetupCompleted();
8249
- const { migrateAiModelQualification } = await import("./ai-service-UODCCE4O.js");
8386
+ const { migrateAiModelQualification } = await import("./ai-service-CVZEYKTK.js");
8250
8387
  migrateAiModelQualification();
8251
- await (await import("./db-MF2JNRUW.js")).getDb();
8388
+ await (await import("./db-FO6LXYCW.js")).getDb();
8252
8389
  try {
8253
8390
  const { eq: eq8, and: and6 } = await import("drizzle-orm");
8254
- const { users: users2 } = await import("./schema-PVNG3PQM.js");
8255
- const db = await (await import("./db-MF2JNRUW.js")).getDb();
8391
+ const { users: users2 } = await import("./schema-VEVO7HG6.js");
8392
+ const db = await (await import("./db-FO6LXYCW.js")).getDb();
8256
8393
  await db.update(users2).set({ role: "system" }).where(and6(eq8(users2.user_type, "system"), eq8(users2.role, "user")));
8257
8394
  } catch (err) {
8258
8395
  logger_default.warn("failed to migrate system user role", logger_default.errorData(err));
8259
8396
  }
8260
8397
  try {
8261
- const { migrateAvatarSizes } = await import("./avatar-image-6KY6UU4D.js");
8398
+ const { migrateAvatarSizes } = await import("./avatar-image-7S3WA77A.js");
8262
8399
  await migrateAvatarSizes();
8263
8400
  } catch (err) {
8264
8401
  logger_default.warn("avatar size migration failed", logger_default.errorData(err));
8265
8402
  }
8266
- const { initSandbox } = await import("./sandbox-ZJ7QA6Z6.js");
8403
+ const { initSandbox } = await import("./sandbox-G4X4K3UN.js");
8267
8404
  await initSandbox();
8268
8405
  try {
8269
8406
  await syncBuiltinSkills();
@@ -8290,7 +8427,7 @@ async function startDaemon(opts) {
8290
8427
  logger_default.warn("failed to ensure #system channel", logger_default.errorData(err));
8291
8428
  }
8292
8429
  try {
8293
- const { getOrCreateSystemUser: getOrCreateSystemUser2 } = await import("./auth-FLAEXXZK.js");
8430
+ const { getOrCreateSystemUser: getOrCreateSystemUser2 } = await import("./auth-A4VGHADI.js");
8294
8431
  await getOrCreateSystemUser2();
8295
8432
  } catch (err) {
8296
8433
  logger_default.warn(
@@ -8299,12 +8436,12 @@ async function startDaemon(opts) {
8299
8436
  );
8300
8437
  }
8301
8438
  const token = process.env.VOLUTE_DAEMON_TOKEN || randomBytes3(32).toString("hex");
8302
- const { readGlobalConfig: readGlobalConfig2 } = await import("./setup-Y2EJI3GG.js");
8439
+ const { readGlobalConfig: readGlobalConfig2 } = await import("./setup-KFYVWTXG.js");
8303
8440
  const globalCfg = readGlobalConfig2();
8304
8441
  let tls;
8305
8442
  if (opts.tailscale || globalCfg.tailscale) {
8306
8443
  try {
8307
- const { getTailscaleTls } = await import("./tailscale-3XOKOW7T.js");
8444
+ const { getTailscaleTls } = await import("./tailscale-MCLY5GYE.js");
8308
8445
  const tlsConfig = await getTailscaleTls();
8309
8446
  tls = { key: tlsConfig.key, cert: tlsConfig.cert };
8310
8447
  logger_default.info("Tailscale HTTPS enabled", { hostname: tlsConfig.hostname });
@@ -8384,15 +8521,19 @@ async function startDaemon(opts) {
8384
8521
  await Promise.all(workers);
8385
8522
  }
8386
8523
  try {
8387
- const { isSetupComplete: isSetupComplete2 } = await import("./setup-Y2EJI3GG.js");
8524
+ const { isSetupComplete: isSetupComplete2 } = await import("./setup-KFYVWTXG.js");
8388
8525
  if (isSetupComplete2()) {
8389
- const { ensureSpiritProject, syncSpiritTemplate } = await import("./spirit-DBTIW7P4.js");
8390
- const { startSpiritFull } = await import("./mind-service-3F4I46CI.js");
8526
+ const { ensureSpiritProject, syncSpiritTemplate } = await import("./spirit-WXN4IDBF.js");
8527
+ const { startSpiritFull } = await import("./mind-service-JFY7UG5E.js");
8391
8528
  await ensureSpiritProject();
8529
+ const spiritEntry = await findMind(SPIRIT_NAME);
8530
+ if (spiritEntry?.dir) {
8531
+ const { registerMindDir } = await import("./delivery-router-3QFG2ZEX.js");
8532
+ registerMindDir(SPIRIT_NAME, spiritEntry.dir);
8533
+ }
8392
8534
  await syncSpiritTemplate();
8393
- const spiritEntry = await findMind("volute");
8394
- if (spiritEntry && !manager.isRunning("volute")) {
8395
- await startSpiritFull("volute");
8535
+ if (spiritEntry && !manager.isRunning(SPIRIT_NAME)) {
8536
+ await startSpiritFull(SPIRIT_NAME);
8396
8537
  }
8397
8538
  }
8398
8539
  } catch (err) {
@@ -8404,7 +8545,7 @@ async function startDaemon(opts) {
8404
8545
  bridgeManager.startBridges(daemonPort).catch((err) => {
8405
8546
  logger_default.warn("failed to start bridges", logger_default.errorData(err));
8406
8547
  });
8407
- import("./cloud-sync-3OJOPZRT.js").then(
8548
+ import("./cloud-sync-LQTVDTQ3.js").then(
8408
8549
  ({ consumeQueuedMessages }) => consumeQueuedMessages().catch((err) => {
8409
8550
  logger_default.warn("failed to consume queued cloud messages", logger_default.errorData(err));
8410
8551
  })
@@ -8412,8 +8553,10 @@ async function startDaemon(opts) {
8412
8553
  logger_default.warn("failed to load cloud-sync module", logger_default.errorData(err));
8413
8554
  });
8414
8555
  try {
8415
- const { backfillTemplateHashes, notifyVersionUpdate } = await import("./version-notify-3XMAIQTE.js");
8416
- backfillTemplateHashes();
8556
+ const { backfillTemplateHashes, notifyVersionUpdate, warnStaleTemplates } = await import("./version-notify-XRGTEEDK.js");
8557
+ backfillTemplateHashes().then(() => warnStaleTemplates()).catch((err) => {
8558
+ logger_default.warn("failed to check template staleness", logger_default.errorData(err));
8559
+ });
8417
8560
  notifyVersionUpdate().catch((err) => {
8418
8561
  logger_default.warn("failed to send version update notifications", logger_default.errorData(err));
8419
8562
  });
@@ -8430,6 +8573,7 @@ async function startDaemon(opts) {
8430
8573
  cleanExpiredLogs().catch((err) => {
8431
8574
  logger_default.warn("failed to clean expired logs", logger_default.errorData(err));
8432
8575
  });
8576
+ const maintenanceInterval = startMaintenanceInterval();
8433
8577
  setProviderRefreshHook((provider) => {
8434
8578
  void syncProviderToMinds(provider, {
8435
8579
  listRunning: () => getMindManager().getRunningMinds()
@@ -8482,6 +8626,7 @@ async function startDaemon(opts) {
8482
8626
  safe("summarizer.stop", () => summarizer.stop());
8483
8627
  safe("backupManager.stop", () => backupManager2.stop());
8484
8628
  safe("stopApiKeyRefresh", stopApiKeyRefresh);
8629
+ safe("maintenanceInterval", () => clearInterval(maintenanceInterval));
8485
8630
  safe("delivery.dispose", () => delivery.dispose());
8486
8631
  await safe("bridgeManager.stopAll", () => bridgeManager.stopAll());
8487
8632
  await safe("manager.stopAll", () => manager.stopAll());