volute 0.48.0 → 0.49.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 (137) hide show
  1. package/README.md +79 -328
  2. package/dist/{activity-events-MIOK3IOU.js → activity-events-GJJAI2WT.js} +4 -4
  3. package/dist/{ai-service-643TNFPH.js → ai-service-A7FS7EC3.js} +5 -5
  4. package/dist/{archive-6Z4GFQPH.js → archive-7E3YE6VF.js} +3 -3
  5. package/dist/{auth-A4VGHADI.js → auth-ABYLOEZ7.js} +5 -5
  6. package/dist/{avatar-image-Y45R7LW5.js → avatar-image-6ATV2IOZ.js} +4 -4
  7. package/dist/{backup-UDH4GMSW.js → backup-XYAMT4CU.js} +15 -15
  8. package/dist/{chat-X43VYHH2.js → chat-OGXAEXMV.js} +2 -2
  9. package/dist/{chunk-LTLFOSA4.js → chunk-3B427GUK.js} +3 -3
  10. package/dist/{chunk-XVBSFLM7.js → chunk-4B62E4CG.js} +2 -2
  11. package/dist/{chunk-ODDGGLM6.js → chunk-4DSGN4FL.js} +3 -3
  12. package/dist/{chunk-PJVPDY2Z.js → chunk-6HGEZJDH.js} +2 -2
  13. package/dist/{chunk-MJRXFUMS.js → chunk-DAIIMBLB.js} +3 -3
  14. package/dist/{chunk-DGGZVZTM.js → chunk-DVN6DNMI.js} +1 -1
  15. package/dist/chunk-EADX54RG.js +113 -0
  16. package/dist/{chunk-EIQDZC43.js → chunk-EFT2OALG.js} +6 -2
  17. package/dist/{chunk-OQJMTHEH.js → chunk-EG5LSKHU.js} +2 -2
  18. package/dist/{chunk-WQ46E33M.js → chunk-FBBS4BVR.js} +2 -2
  19. package/dist/{chunk-HUNDXZBZ.js → chunk-GWVFDUNB.js} +45 -14
  20. package/dist/{chunk-FJ2MZ5PH.js → chunk-HCYQRME2.js} +2 -2
  21. package/dist/{chunk-T4VS7UDW.js → chunk-IH5TKXX6.js} +9 -9
  22. package/dist/{chunk-Q7HOAVFR.js → chunk-JBIFXB6I.js} +4 -4
  23. package/dist/{chunk-WARH6GIV.js → chunk-JCBPU57A.js} +12 -10
  24. package/dist/{chunk-VZRA5TYO.js → chunk-JCWB2ITW.js} +2 -2
  25. package/dist/{chunk-MOERAQXH.js → chunk-JPGHSXSV.js} +18 -25
  26. package/dist/{chunk-USD45JKR.js → chunk-K3JOLGWF.js} +7 -7
  27. package/dist/{chunk-3YUBFTDN.js → chunk-KHCKZ36O.js} +1 -1
  28. package/dist/{chunk-7KRIHYLR.js → chunk-L27YYONJ.js} +2 -2
  29. package/dist/{chunk-SZJCJVXV.js → chunk-MG3Z34YE.js} +2 -2
  30. package/dist/{chunk-5KXCM5AW.js → chunk-NJHN6IRB.js} +9 -1
  31. package/dist/{chunk-2OIYNHWO.js → chunk-NOIAHMJB.js} +1 -1
  32. package/dist/{chunk-2MJ7EOMT.js → chunk-P4LHVRNE.js} +1 -0
  33. package/dist/{chunk-IJPMIEVE.js → chunk-PY24ZL62.js} +1 -1
  34. package/dist/{chunk-VB2QXUJQ.js → chunk-QQBOI3QA.js} +202 -117
  35. package/dist/{chunk-T2TP6ZC6.js → chunk-QUSMYGNQ.js} +6 -0
  36. package/dist/{chunk-WW6C3PLV.js → chunk-RSBL3FAD.js} +39 -6
  37. package/dist/{chunk-YKA7DCVW.js → chunk-SX72DOO3.js} +1 -1
  38. package/dist/{chunk-4FCL4DVZ.js → chunk-TXJL36ZQ.js} +2 -2
  39. package/dist/{chunk-HNGFKPNS.js → chunk-VIUH5CHQ.js} +55 -23
  40. package/dist/{chunk-HPGN6XRT.js → chunk-VNRSXEBF.js} +4 -4
  41. package/dist/{chunk-IYCV34IU.js → chunk-XTEKINWG.js} +3 -3
  42. package/dist/{chunk-YNBXEQAP.js → chunk-YIJGKHIS.js} +3 -3
  43. package/dist/{chunk-MTRMQFCQ.js → chunk-ZX46PGCC.js} +2 -2
  44. package/dist/cli.js +20 -20
  45. package/dist/{clock-CNRF6F7T.js → clock-6JZL2FS6.js} +2 -2
  46. package/dist/{cloud-sync-WHKCB4CD.js → cloud-sync-VFDTNNCA.js} +20 -20
  47. package/dist/{conversations-C7IKYSHD.js → conversations-TUM5PJJ4.js} +5 -5
  48. package/dist/{create-GJKHABJB.js → create-Z5FSDNVZ.js} +1 -1
  49. package/dist/{daemon-restart-7KUHVZIN.js → daemon-restart-54L4B6AQ.js} +7 -7
  50. package/dist/daemon.js +647 -410
  51. package/dist/{db-FO6LXYCW.js → db-F6UMFRYD.js} +2 -2
  52. package/dist/delivery-manager-RBXBPVWM.js +36 -0
  53. package/dist/{delivery-router-3QFG2ZEX.js → delivery-router-VUOL5YSA.js} +8 -6
  54. package/dist/{down-G6IADJXV.js → down-6X7KX7BZ.js} +5 -5
  55. package/dist/echo-text-B6NZ5FGE.js +36 -0
  56. package/dist/{env-6TGPO5WG.js → env-KUL4XKN2.js} +3 -3
  57. package/dist/{exec-QIFYZG4C.js → exec-FBXA3CM4.js} +3 -3
  58. package/dist/{export-C2K3NIRD.js → export-TNBPDH77.js} +3 -3
  59. package/dist/{extensions-GEKF47A4.js → extensions-AWNO3SZF.js} +20 -20
  60. package/dist/{files-LXQSQOFG.js → files-BVBRJJ2P.js} +3 -3
  61. package/dist/{import-SX3ZXHB4.js → import-ZUNKMCKC.js} +7 -7
  62. package/dist/{isolation-MCT4ZYLT.js → isolation-OPCZQRTH.js} +3 -3
  63. package/dist/{login-CL4IT2ED.js → login-KAKNFCW5.js} +6 -6
  64. package/dist/{logout-HZYJCYCA.js → logout-RLT62RQP.js} +2 -2
  65. package/dist/message-delivery-ABPV76EA.js +42 -0
  66. package/dist/{mind-46AOK373.js → mind-IF6CGMFW.js} +12 -12
  67. package/dist/{mind-activity-tracker-YVZPC4A5.js → mind-activity-tracker-YMMERB6B.js} +5 -5
  68. package/dist/mind-service-42GDO4KH.js +44 -0
  69. package/dist/{mind-sleep-DV7KP255.js → mind-sleep-VBV7YRSU.js} +2 -2
  70. package/dist/{notices-2KU5NTRY.js → notices-N6BCGHFV.js} +4 -4
  71. package/dist/{package-RTN2PS7O.js → package-PD67VPHB.js} +5 -4
  72. package/dist/prompts-MDHDLWEX.js +24 -0
  73. package/dist/{registry-UMMDRPRM.js → registry-3ELJPUGJ.js} +6 -2
  74. package/dist/{sandbox-5VQ454XP.js → sandbox-XPYA7UIF.js} +5 -5
  75. package/dist/scheduler-K7Y7Z7FQ.js +34 -0
  76. package/dist/{schema-VEVO7HG6.js → schema-6YONVTKZ.js} +1 -1
  77. package/dist/{seed-F2HXZQU4.js → seed-NQED7PSX.js} +1 -1
  78. package/dist/{seed-cmd-MLCTGSX5.js → seed-cmd-QF3F2G4S.js} +2 -2
  79. package/dist/{seed-create-KQ7OILWB.js → seed-create-XOO4N2OE.js} +3 -3
  80. package/dist/{seed-readiness-RBPGOLSZ.js → seed-readiness-3LRYASA4.js} +5 -5
  81. package/dist/{seed-sprout-HVYCFJLA.js → seed-sprout-HSBBGSS2.js} +11 -11
  82. package/dist/{send-TD2X5U3K.js → send-IHRJYNXA.js} +6 -6
  83. package/dist/{service-CVZMQYK6.js → service-YCOPCTSZ.js} +4 -4
  84. package/dist/{service-install-VCRMFWX5.js → service-install-Y46OTF55.js} +5 -5
  85. package/dist/{setup-AE4CVUC4.js → setup-F4TE64YI.js} +7 -7
  86. package/dist/{setup-5ECWTYKG.js → setup-TCN7VH5U.js} +5 -3
  87. package/dist/skills/volute-mind/SKILL.md +3 -3
  88. package/dist/skills/volute-mind/references/routing.md +6 -5
  89. package/dist/skills/volute-mind/references/variants.md +48 -14
  90. package/dist/{skills-B3SVDCEB.js → skills-NN5UZCUU.js} +8 -6
  91. package/dist/sleep-manager-DSPXFHGX.js +38 -0
  92. package/dist/spirit-SSJMYC6A.js +31 -0
  93. package/dist/{split-SKZ4Y2D6.js → split-JF2VWTXA.js} +4 -1
  94. package/dist/{sprout-KQ43BWZJ.js → sprout-AXA2SECQ.js} +1 -1
  95. package/dist/{status-F4PGREUU.js → status-VJGXMQ4L.js} +5 -5
  96. package/dist/system-chat-M2XOBKEM.js +40 -0
  97. package/dist/{systems-JJNGQHAO.js → systems-5MH2ELQE.js} +2 -2
  98. package/dist/{tailscale-MCLY5GYE.js → tailscale-AMMVKDXB.js} +3 -3
  99. package/dist/{template-staleness-EMQORSGG.js → template-staleness-HKGCTS3H.js} +4 -4
  100. package/dist/{turn-tracker-KLPV3PAU.js → turn-tracker-JQ2YUUY2.js} +7 -7
  101. package/dist/{up-RVGDE6CQ.js → up-SDLTLPAO.js} +6 -6
  102. package/dist/{update-OXQEGGXD.js → update-WEA4N2TR.js} +5 -5
  103. package/dist/{update-check-266R4ZSY.js → update-check-DZOTONII.js} +3 -3
  104. package/dist/variant-cleanup-P4MBNLJD.js +33 -0
  105. package/dist/{version-notify-73YURTFR.js → version-notify-WFA5XB5A.js} +23 -23
  106. package/dist/web-assets/assets/index-BostMFoR.css +1 -0
  107. package/dist/web-assets/assets/index-CKlqUkyG.js +92 -0
  108. package/dist/web-assets/index.html +2 -2
  109. package/drizzle/0013_variant_purpose.sql +3 -0
  110. package/drizzle/meta/_journal.json +7 -0
  111. package/package.json +5 -4
  112. package/templates/_base/.init/.local/hooks/startup-context.ts +19 -2
  113. package/templates/_base/home/VOLUTE.md +13 -1
  114. package/templates/_base/src/lib/daemon-client.ts +27 -7
  115. package/templates/claude/src/agent.ts +1 -1
  116. package/templates/claude/src/lib/hooks/identity-reload.ts +12 -3
  117. package/templates/pi/src/lib/event-handler.ts +47 -6
  118. package/dist/delivery-manager-HZTT5SJR.js +0 -36
  119. package/dist/echo-text-H3SH337Q.js +0 -36
  120. package/dist/message-delivery-GPQ5SE5N.js +0 -40
  121. package/dist/mind-service-2ARGJNKP.js +0 -44
  122. package/dist/scheduler-I7JZCT47.js +0 -34
  123. package/dist/sleep-manager-Z27R3WTW.js +0 -38
  124. package/dist/spirit-WXS3HCLN.js +0 -25
  125. package/dist/system-chat-63ABFF6V.js +0 -40
  126. package/dist/web-assets/assets/index-WdZ60R6c.css +0 -1
  127. package/dist/web-assets/assets/index-taX6Ezfa.js +0 -92
  128. package/dist/{env-C7Z55QCJ.js → env-ZYMSCB7B.js} +5 -5
  129. package/dist/{login-ARRZ4ORJ.js → login-LKNLCJPA.js} +3 -3
  130. package/dist/{mind-contacts-GV7XYL5D.js → mind-contacts-BKUFPUCU.js} +6 -6
  131. package/dist/{mind-history-TTGA5BHZ.js → mind-history-WI5OI3BW.js} +4 -4
  132. package/dist/{register-42VBJKTP.js → register-TWQDKQRT.js} +3 -3
  133. package/dist/{restart-YYEGZJDD.js → restart-5F5QDBOG.js} +4 -4
  134. package/dist/{skill-UUM4JZRF.js → skill-4CYJLYVY.js} +4 -4
  135. package/dist/{src-WLJE73NP.js → src-ZN4LXMPI.js} +3 -3
  136. package/dist/{start-O6UKK7BG.js → start-BTVMTP3K.js} +3 -3
  137. package/dist/{stop-UBX6X5RA.js → stop-YORESK4U.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-Q7HOAVFR.js";
9
+ } from "./chunk-JBIFXB6I.js";
10
10
  import {
11
11
  acceptPending,
12
12
  formatFileSize,
@@ -14,16 +14,22 @@ import {
14
14
  rejectPending,
15
15
  stageFile,
16
16
  validateFilePath
17
- } from "./chunk-2OIYNHWO.js";
17
+ } from "./chunk-NOIAHMJB.js";
18
18
  import {
19
19
  findOpenClawSession,
20
20
  importOpenClawConnectors,
21
21
  importPiSession,
22
22
  parseNameFromIdentity
23
- } from "./chunk-OQJMTHEH.js";
23
+ } from "./chunk-EG5LSKHU.js";
24
+ import {
25
+ isHomeOnlyArchive
26
+ } from "./chunk-KHCKZ36O.js";
27
+ import {
28
+ cleanupVariant
29
+ } from "./chunk-EADX54RG.js";
24
30
  import {
25
31
  isTemplateStale
26
- } from "./chunk-7KRIHYLR.js";
32
+ } from "./chunk-L27YYONJ.js";
27
33
  import {
28
34
  computeTemplateHash
29
35
  } from "./chunk-C3KLHJDB.js";
@@ -31,17 +37,14 @@ import {
31
37
  checkForUpdate,
32
38
  checkForUpdateCached,
33
39
  getCurrentVersion
34
- } from "./chunk-YKA7DCVW.js";
35
- import {
36
- isHomeOnlyArchive
37
- } from "./chunk-3YUBFTDN.js";
40
+ } from "./chunk-SX72DOO3.js";
38
41
  import {
39
42
  PLATFORMS,
40
43
  echoTextToChannel,
41
44
  fixModelEscapes,
42
45
  getPlatformDriver,
43
46
  routeOutboundBridge
44
- } from "./chunk-T4VS7UDW.js";
47
+ } from "./chunk-IH5TKXX6.js";
45
48
  import {
46
49
  getBridgeConfig,
47
50
  readBridgesConfig,
@@ -50,7 +53,7 @@ import {
50
53
  resolveChannelMapping,
51
54
  setBridgeConfig,
52
55
  setChannelMapping
53
- } from "./chunk-SZJCJVXV.js";
56
+ } from "./chunk-MG3Z34YE.js";
54
57
  import {
55
58
  MindStartupError,
56
59
  RestartTracker,
@@ -66,6 +69,7 @@ import {
66
69
  deleteSession,
67
70
  deleteSystemsConfig,
68
71
  deliverMessage,
72
+ deliverSproutedNotice,
69
73
  ensureSystemChannel,
70
74
  formatHoldNotice,
71
75
  generateSystemFallbackReply,
@@ -91,26 +95,29 @@ import {
91
95
  invalidateSessionCache,
92
96
  isConversationId,
93
97
  joinSystemChannel,
98
+ joinSystemChannelForMind,
94
99
  linkToolResultToTurn,
95
100
  loadAllExtensions,
96
101
  notifyExtensionsDaemonStart,
97
102
  notifyExtensionsDaemonStop,
98
103
  publishTypingForChannels,
99
104
  readSystemsConfig,
100
- recordInbound,
101
105
  recordOutbound,
102
106
  requireAdmin,
103
107
  requireAdminOrSystem,
104
108
  requireSelf,
109
+ sendSystemMessage,
110
+ sendSystemMessageDirect,
105
111
  setExtensionEnabled,
106
112
  shouldGenerateSystemFallback,
107
113
  startMindFull,
108
114
  stopMindFull,
109
115
  syncProviderToMinds,
116
+ tryGetMindManager,
110
117
  uninstallNpmExtension,
111
118
  writeSystemsConfig
112
- } from "./chunk-VB2QXUJQ.js";
113
- import "./chunk-HNGFKPNS.js";
119
+ } from "./chunk-QQBOI3QA.js";
120
+ import "./chunk-VIUH5CHQ.js";
114
121
  import {
115
122
  applyInitFiles,
116
123
  applyTemplateHomeFiles,
@@ -120,6 +127,9 @@ import {
120
127
  isKnownTemplate,
121
128
  listFiles
122
129
  } from "./chunk-RRZ4RQQU.js";
130
+ import {
131
+ extractTextContent
132
+ } from "./chunk-EFT2OALG.js";
123
133
  import {
124
134
  assignSession,
125
135
  completeOrphanedTurns,
@@ -138,15 +148,7 @@ import {
138
148
  takeErrored,
139
149
  trackToolUse,
140
150
  utcDateTimeStr
141
- } from "./chunk-USD45JKR.js";
142
- import {
143
- PROMPT_DEFAULTS,
144
- PROMPT_KEYS,
145
- getMindPromptDefaults,
146
- getPrompt,
147
- getPromptIfCustom,
148
- substitute
149
- } from "./chunk-MOERAQXH.js";
151
+ } from "./chunk-K3JOLGWF.js";
150
152
  import {
151
153
  SEED_SKILLS,
152
154
  STANDARD_SKILLS,
@@ -160,6 +162,7 @@ import {
160
162
  listFilesRecursive,
161
163
  listMindSkills,
162
164
  listSharedSkills,
165
+ migrateSkillsToTemplate,
163
166
  mindSkillsDir,
164
167
  publishSkill,
165
168
  removeSharedSkill,
@@ -167,7 +170,7 @@ import {
167
170
  syncBuiltinSkills,
168
171
  uninstallSkill,
169
172
  updateSkill
170
- } from "./chunk-HUNDXZBZ.js";
173
+ } from "./chunk-GWVFDUNB.js";
171
174
  import {
172
175
  approveUser,
173
176
  changePassword,
@@ -186,7 +189,7 @@ import {
186
189
  syncMindProfile,
187
190
  updateUserProfile,
188
191
  verifyUser
189
- } from "./chunk-ODDGGLM6.js";
192
+ } from "./chunk-4DSGN4FL.js";
190
193
  import {
191
194
  addMessage,
192
195
  createChannel,
@@ -219,11 +222,19 @@ import {
219
222
  setConversationPrivate,
220
223
  subscribe as subscribe2,
221
224
  updateChannelSettings
222
- } from "./chunk-HPGN6XRT.js";
225
+ } from "./chunk-VNRSXEBF.js";
226
+ import {
227
+ PROMPT_DEFAULTS,
228
+ PROMPT_KEYS,
229
+ getMindPromptDefaults,
230
+ getPrompt,
231
+ getPromptIfCustom,
232
+ substitute
233
+ } from "./chunk-JPGHSXSV.js";
223
234
  import {
224
235
  isSandboxEnabled,
225
236
  wrapForSandbox
226
- } from "./chunk-VZRA5TYO.js";
237
+ } from "./chunk-JCWB2ITW.js";
227
238
  import {
228
239
  clearDeliveredNotices,
229
240
  drainNotices,
@@ -231,15 +242,15 @@ import {
231
242
  latestFailureNotice,
232
243
  latestNotice,
233
244
  recordNotice
234
- } from "./chunk-IYCV34IU.js";
245
+ } from "./chunk-XTEKINWG.js";
235
246
  import {
236
247
  getActiveMinds,
237
248
  onMindEvent,
238
249
  stopAll
239
- } from "./chunk-MTRMQFCQ.js";
250
+ } from "./chunk-ZX46PGCC.js";
240
251
  import {
241
252
  normalizeAvatar
242
- } from "./chunk-WQ46E33M.js";
253
+ } from "./chunk-FBBS4BVR.js";
243
254
  import {
244
255
  safeResolveWithinBase
245
256
  } from "./chunk-DW4TRDOZ.js";
@@ -247,10 +258,7 @@ import {
247
258
  readVoluteConfig,
248
259
  writeVoluteConfig
249
260
  } from "./chunk-WCFSBH33.js";
250
- import {
251
- extractTextContent
252
- } from "./chunk-EIQDZC43.js";
253
- import "./chunk-FJ2MZ5PH.js";
261
+ import "./chunk-HCYQRME2.js";
254
262
  import {
255
263
  chownMindDir,
256
264
  createMindUser,
@@ -261,14 +269,14 @@ import {
261
269
  isIsolationEnabled,
262
270
  resolveVoluteBin,
263
271
  wrapForIsolation
264
- } from "./chunk-DGGZVZTM.js";
272
+ } from "./chunk-DVN6DNMI.js";
265
273
  import {
266
274
  loadMergedEnv,
267
275
  mindEnvPath,
268
276
  readEnv,
269
277
  sharedEnvPath,
270
278
  writeEnv
271
- } from "./chunk-IJPMIEVE.js";
279
+ } from "./chunk-PY24ZL62.js";
272
280
  import {
273
281
  addCustomModel,
274
282
  aiCompleteUtility,
@@ -291,23 +299,23 @@ import {
291
299
  setProviderRefreshHook,
292
300
  setUtilityModel,
293
301
  unqualifyModelId
294
- } from "./chunk-YNBXEQAP.js";
302
+ } from "./chunk-YIJGKHIS.js";
295
303
  import {
296
304
  isSetupComplete,
297
305
  readGlobalConfig,
298
306
  writeGlobalConfig
299
- } from "./chunk-5KXCM5AW.js";
307
+ } from "./chunk-NJHN6IRB.js";
300
308
  import {
301
309
  broadcast,
302
310
  getEventsSince,
303
311
  nextEventId,
304
312
  publish,
305
313
  subscribe
306
- } from "./chunk-LTLFOSA4.js";
314
+ } from "./chunk-3B427GUK.js";
307
315
  import {
308
316
  logBuffer,
309
317
  logger_default
310
- } from "./chunk-T2TP6ZC6.js";
318
+ } from "./chunk-QUSMYGNQ.js";
311
319
  import {
312
320
  buildVoluteSlug
313
321
  } from "./chunk-LGNUFVMR.js";
@@ -315,6 +323,7 @@ import {
315
323
  SPIRIT_NAME,
316
324
  addMind,
317
325
  addVariant,
326
+ countCappedMinds,
318
327
  daemonLoopback,
319
328
  ensureSystemDir,
320
329
  ensureVoluteHome,
@@ -337,7 +346,7 @@ import {
337
346
  validateMindName,
338
347
  voluteHome,
339
348
  voluteSystemDir
340
- } from "./chunk-WW6C3PLV.js";
349
+ } from "./chunk-RSBL3FAD.js";
341
350
  import {
342
351
  activity,
343
352
  channels,
@@ -348,14 +357,14 @@ import {
348
357
  systemPrompts,
349
358
  turns,
350
359
  users
351
- } from "./chunk-2MJ7EOMT.js";
360
+ } from "./chunk-P4LHVRNE.js";
352
361
  import "./chunk-K3NQKI34.js";
353
362
 
354
363
  // packages/daemon/src/daemon.ts
355
364
  import { randomBytes as randomBytes3 } from "crypto";
356
- import { chmodSync, mkdirSync as mkdirSync10, readFileSync as readFileSync11, unlinkSync as unlinkSync2, writeFileSync as writeFileSync10 } from "fs";
365
+ import { chmodSync, mkdirSync as mkdirSync10, readFileSync as readFileSync12, unlinkSync as unlinkSync2, writeFileSync as writeFileSync10 } from "fs";
357
366
  import { homedir as homedir3 } from "os";
358
- import { resolve as resolve17 } from "path";
367
+ import { resolve as resolve18 } from "path";
359
368
  import { format } from "util";
360
369
 
361
370
  // packages/daemon/src/lib/daemon/backup-manager.ts
@@ -467,8 +476,8 @@ var BackupManager = class {
467
476
  blog.error("failed to publish backup-failure activity", logger_default.errorData(pubErr));
468
477
  }
469
478
  try {
470
- const { sendSystemMessage } = await import("./system-chat-63ABFF6V.js");
471
- await sendSystemMessage(
479
+ const { sendSystemMessage: sendSystemMessage2 } = await import("./system-chat-M2XOBKEM.js");
480
+ await sendSystemMessage2(
472
481
  SPIRIT_NAME,
473
482
  `${summary}
474
483
 
@@ -693,8 +702,8 @@ var BridgeManager = class {
693
702
  if (!tracked) return;
694
703
  this.stopping.add(platform);
695
704
  this.bridges.delete(platform);
696
- await new Promise((resolve18) => {
697
- tracked.child.on("exit", () => resolve18());
705
+ await new Promise((resolve19) => {
706
+ tracked.child.on("exit", () => resolve19());
698
707
  try {
699
708
  if (tracked.child.pid) {
700
709
  process.kill(-tracked.child.pid, "SIGTERM");
@@ -705,7 +714,7 @@ var BridgeManager = class {
705
714
  if (err instanceof Error && err.code !== "ESRCH") {
706
715
  blog2.warn(`failed to stop bridge ${platform}`, logger_default.errorData(err));
707
716
  }
708
- resolve18();
717
+ resolve19();
709
718
  }
710
719
  setTimeout(() => {
711
720
  try {
@@ -716,7 +725,7 @@ var BridgeManager = class {
716
725
  }
717
726
  } catch {
718
727
  }
719
- resolve18();
728
+ resolve19();
720
729
  }, 5e3);
721
730
  });
722
731
  this.stopping.delete(platform);
@@ -1056,10 +1065,10 @@ var app = new Hono().post("/restart", requireAdmin, (c) => {
1056
1065
  stream.writeSSE({ data: JSON.stringify(entry) }).catch(() => {
1057
1066
  });
1058
1067
  });
1059
- await new Promise((resolve18) => {
1068
+ await new Promise((resolve19) => {
1060
1069
  stream.onAbort(() => {
1061
1070
  unsubscribe();
1062
- resolve18();
1071
+ resolve19();
1063
1072
  });
1064
1073
  });
1065
1074
  });
@@ -1072,13 +1081,34 @@ var app = new Hono().post("/restart", requireAdmin, (c) => {
1072
1081
  name: globalConfig.name ?? null,
1073
1082
  timezone
1074
1083
  });
1075
- }).put("/info", requireAdmin, zValidator("json", z.object({ name: z.string() })), (c) => {
1084
+ }).put("/info", requireAdmin, zValidator("json", z.object({ name: z.string() })), async (c) => {
1076
1085
  const { name } = c.req.valid("json");
1077
1086
  const config2 = readGlobalConfig();
1087
+ const previousName = config2.name;
1078
1088
  config2.name = name.trim() || void 0;
1079
1089
  writeGlobalConfig(config2);
1090
+ if (config2.name !== previousName) {
1091
+ const { notifySpiritSystemChange } = await import("./spirit-SSJMYC6A.js");
1092
+ await notifySpiritSystemChange();
1093
+ }
1080
1094
  return c.json({ name: config2.name ?? null });
1081
- }).post(
1095
+ }).get("/max-minds", requireAdmin, async (c) => {
1096
+ return c.json({
1097
+ maxMinds: readGlobalConfig().maxMinds ?? null,
1098
+ count: await countCappedMinds()
1099
+ });
1100
+ }).put(
1101
+ "/max-minds",
1102
+ requireAdmin,
1103
+ zValidator("json", z.object({ maxMinds: z.number().int().positive().nullable() })),
1104
+ async (c) => {
1105
+ const { maxMinds } = c.req.valid("json");
1106
+ const config2 = readGlobalConfig();
1107
+ config2.maxMinds = maxMinds ?? void 0;
1108
+ writeGlobalConfig(config2);
1109
+ return c.json({ maxMinds: maxMinds ?? null, count: await countCappedMinds() });
1110
+ }
1111
+ ).post(
1082
1112
  "/register",
1083
1113
  requireAdmin,
1084
1114
  zValidator("json", z.object({ name: z.string().min(1) })),
@@ -1408,8 +1438,8 @@ var app = new Hono().post("/restart", requireAdmin, (c) => {
1408
1438
  createdAt: Date.now()
1409
1439
  };
1410
1440
  oauthFlows.set(flowId, flow);
1411
- const promptPromise = needsManualCode ? new Promise((resolve18) => {
1412
- flow.resolveCode = resolve18;
1441
+ const promptPromise = needsManualCode ? new Promise((resolve19) => {
1442
+ flow.resolveCode = resolve19;
1413
1443
  }) : void 0;
1414
1444
  oauthProvider.login({
1415
1445
  onAuth: (info) => {
@@ -1448,7 +1478,7 @@ var app = new Hono().post("/restart", requireAdmin, (c) => {
1448
1478
  existing.error = err instanceof Error ? err.message : String(err);
1449
1479
  }
1450
1480
  });
1451
- await new Promise((resolve18) => setTimeout(resolve18, 2e3));
1481
+ await new Promise((resolve19) => setTimeout(resolve19, 2e3));
1452
1482
  const state = oauthFlows.get(flowId);
1453
1483
  return c.json({
1454
1484
  flowId,
@@ -1706,8 +1736,8 @@ var app2 = new Hono2().get("/events", async (c) => {
1706
1736
  });
1707
1737
  }, 15e3);
1708
1738
  cleanups.push(() => clearInterval(keepAlive));
1709
- await new Promise((resolve18) => {
1710
- stream.onAbort(() => resolve18());
1739
+ await new Promise((resolve19) => {
1740
+ stream.onAbort(() => resolve19());
1711
1741
  });
1712
1742
  } finally {
1713
1743
  for (const cleanup of cleanups) {
@@ -2278,7 +2308,7 @@ var app4 = new Hono5().post("/:platform/inbound", zValidator4("json", inboundSch
2278
2308
  }
2279
2309
  const participants = await getParticipants(channel.id);
2280
2310
  if (!participants.some((p) => p.userId === puppet.id)) {
2281
- const { addParticipant } = await import("./conversations-C7IKYSHD.js");
2311
+ const { addParticipant } = await import("./conversations-TUM5PJJ4.js");
2282
2312
  await addParticipant(channel.id, puppet.id);
2283
2313
  }
2284
2314
  const contentBlocks = body.content;
@@ -2562,8 +2592,8 @@ async function notifyMind(mindName, message) {
2562
2592
  const entry = await findMind(mindName);
2563
2593
  if (!entry) return;
2564
2594
  try {
2565
- const { sendSystemMessage } = await import("./system-chat-63ABFF6V.js");
2566
- await sendSystemMessage(mindName, message);
2595
+ const { sendSystemMessage: sendSystemMessage2 } = await import("./system-chat-M2XOBKEM.js");
2596
+ await sendSystemMessage2(mindName, message);
2567
2597
  } catch (err) {
2568
2598
  logger_default.warn(`[file-sharing] notify mind ${mindName} failed`, logger_default.errorData(err));
2569
2599
  }
@@ -3394,9 +3424,9 @@ var app11 = new Hono14().get("/:name/logs", requireSelf(), async (c) => {
3394
3424
  stream.onAbort(() => {
3395
3425
  tail.kill();
3396
3426
  });
3397
- await new Promise((resolve18) => {
3398
- tail.on("exit", resolve18);
3399
- stream.onAbort(resolve18);
3427
+ await new Promise((resolve19) => {
3428
+ tail.on("exit", resolve19);
3429
+ stream.onAbort(resolve19);
3400
3430
  });
3401
3431
  });
3402
3432
  }).get("/:name/logs/tail", requireSelf(), async (c) => {
@@ -3414,8 +3444,8 @@ var app11 = new Hono14().get("/:name/logs", requireSelf(), async (c) => {
3414
3444
  tail.stdout.on("data", (data) => {
3415
3445
  output += data.toString();
3416
3446
  });
3417
- await new Promise((resolve18) => {
3418
- tail.on("exit", resolve18);
3447
+ await new Promise((resolve19) => {
3448
+ tail.on("exit", resolve19);
3419
3449
  });
3420
3450
  return c.text(output);
3421
3451
  });
@@ -3508,11 +3538,11 @@ var mind_skills_default = app12;
3508
3538
  // packages/daemon/src/web/api/minds.ts
3509
3539
  import {
3510
3540
  cpSync,
3511
- existsSync as existsSync8,
3541
+ existsSync as existsSync7,
3512
3542
  mkdirSync as mkdirSync6,
3513
3543
  readdirSync as readdirSync2,
3514
3544
  readFileSync as readFileSync9,
3515
- rmSync as rmSync4,
3545
+ rmSync as rmSync3,
3516
3546
  writeFileSync as writeFileSync8
3517
3547
  } from "fs";
3518
3548
  import { relative, resolve as resolve11 } from "path";
@@ -3997,62 +4027,97 @@ function deriveThinkingLevel(config2) {
3997
4027
  return null;
3998
4028
  }
3999
4029
 
4000
- // packages/daemon/src/lib/mind/variant-cleanup.ts
4001
- import { existsSync as existsSync7, rmSync as rmSync3 } from "fs";
4002
- async function cleanupVariant(variantName, projectRoot, variantPath, opts) {
4003
- if (opts?.stop) {
4004
- try {
4005
- await getMindManager().stopMind(variantName);
4006
- } catch (err) {
4007
- logger_default.warn(`failed to stop variant ${variantName}`, logger_default.errorData(err));
4008
- }
4030
+ // packages/daemon/src/lib/mind/variants.ts
4031
+ var SAFE_BRANCH_RE = /^[a-zA-Z0-9._\-/]+$/;
4032
+ function validateBranchName(branch) {
4033
+ if (!SAFE_BRANCH_RE.test(branch)) {
4034
+ return `Invalid branch name: ${branch}. Only alphanumeric, '.', '_', '-', '/' allowed.`;
4009
4035
  }
4010
- const { findMind: findMind2 } = await import("./registry-UMMDRPRM.js");
4011
- const variantEntry = await findMind2(variantName);
4012
- const branchName = variantEntry?.branch ?? variantName;
4013
- if (existsSync7(variantPath)) {
4014
- try {
4015
- await gitExec(["worktree", "remove", "--force", variantPath], { cwd: projectRoot });
4016
- } catch {
4017
- rmSync3(variantPath, { recursive: true, force: true });
4018
- try {
4019
- await gitExec(["worktree", "prune"], { cwd: projectRoot });
4020
- } catch (err) {
4021
- logger_default.warn(`failed to prune worktrees for ${variantName}`, logger_default.errorData(err));
4022
- }
4023
- }
4036
+ if (branch.includes("..")) {
4037
+ return `Invalid branch name: ${branch}. '..' not allowed.`;
4024
4038
  }
4039
+ return null;
4040
+ }
4041
+ var MERGE_EXCLUDED_PATHS = ["home/MEMORY.md", "home/memory/journal/"];
4042
+ var MAX_DELTA_CHARS = 12e3;
4043
+ async function mergeVariantExcludingMemory(projectRoot, variantBranch) {
4044
+ const opts = { cwd: projectRoot };
4045
+ let memoryDelta = "";
4025
4046
  try {
4026
- await gitExec(["branch", "-D", branchName], { cwd: projectRoot });
4047
+ const base = (await gitExec(["merge-base", "HEAD", variantBranch], opts)).trim();
4048
+ memoryDelta = (await gitExec(["diff", `${base}..${variantBranch}`, "--", ...MERGE_EXCLUDED_PATHS], opts)).trim();
4027
4049
  } catch (err) {
4028
- logger_default.warn(`failed to delete branch ${branchName} for ${variantName}`, logger_default.errorData(err));
4050
+ logger_default.error(
4051
+ `variant join: failed to compute memory delta for ${variantBranch}`,
4052
+ logger_default.errorData(err)
4053
+ );
4054
+ memoryDelta = `(journal delta unavailable \u2014 ${err instanceof Error ? err.message : String(err)})`;
4029
4055
  }
4030
- const baseName = variantEntry?.parent ?? variantName;
4031
- try {
4032
- await removeMind(variantName);
4033
- } catch (err) {
4034
- logger_default.warn(`failed to remove variant ${variantName} from DB`, logger_default.errorData(err));
4056
+ if (memoryDelta.length > MAX_DELTA_CHARS) {
4057
+ memoryDelta = `${memoryDelta.slice(0, MAX_DELTA_CHARS)}
4058
+ \u2026 (delta truncated)`;
4035
4059
  }
4036
4060
  try {
4037
- await chownMindDir(projectRoot, baseName);
4061
+ await gitExec(["merge", "--no-ff", "--no-commit", variantBranch], opts).catch(() => {
4062
+ });
4063
+ await gitExec(["reset", "-q", "HEAD", "--", ...MERGE_EXCLUDED_PATHS], opts);
4064
+ for (const path of MERGE_EXCLUDED_PATHS) {
4065
+ await gitExec(["checkout", "--", path], opts).catch(() => {
4066
+ });
4067
+ }
4068
+ await gitExec(["clean", "-fdq", "--", ...MERGE_EXCLUDED_PATHS], opts);
4069
+ await assertExcludedPathsRestored(opts);
4070
+ if (await mergeInProgress(opts)) {
4071
+ await gitExec(["commit", "--no-edit"], opts);
4072
+ }
4038
4073
  } catch (err) {
4039
- logger_default.error(
4040
- `failed to fix ownership during variant cleanup for ${variantName}`,
4041
- logger_default.errorData(err)
4074
+ const conflicts = await listConflicts(opts);
4075
+ const abortFailed = await gitExec(["merge", "--abort"], opts).then(
4076
+ () => false,
4077
+ () => true
4078
+ );
4079
+ throw new VariantMergeError(
4080
+ `variant join aborted: ${err instanceof Error ? err.message : String(err)}`,
4081
+ conflicts,
4082
+ abortFailed
4042
4083
  );
4043
4084
  }
4085
+ return memoryDelta;
4044
4086
  }
4045
-
4046
- // packages/daemon/src/lib/mind/variants.ts
4047
- var SAFE_BRANCH_RE = /^[a-zA-Z0-9._\-/]+$/;
4048
- function validateBranchName(branch) {
4049
- if (!SAFE_BRANCH_RE.test(branch)) {
4050
- return `Invalid branch name: ${branch}. Only alphanumeric, '.', '_', '-', '/' allowed.`;
4087
+ var VariantMergeError = class extends Error {
4088
+ constructor(message, conflicts, abortFailed = false) {
4089
+ super(message);
4090
+ this.conflicts = conflicts;
4091
+ this.abortFailed = abortFailed;
4092
+ this.name = "VariantMergeError";
4051
4093
  }
4052
- if (branch.includes("..")) {
4053
- return `Invalid branch name: ${branch}. '..' not allowed.`;
4094
+ };
4095
+ async function mergeInProgress(opts) {
4096
+ return gitExec(["rev-parse", "-q", "--verify", "MERGE_HEAD"], opts).then(
4097
+ () => true,
4098
+ () => false
4099
+ );
4100
+ }
4101
+ async function listConflicts(opts) {
4102
+ try {
4103
+ const out = (await gitExec(["diff", "--name-only", "--diff-filter=U"], opts)).trim();
4104
+ return out ? out.split("\n") : [];
4105
+ } catch {
4106
+ return [];
4107
+ }
4108
+ }
4109
+ async function assertExcludedPathsRestored(opts) {
4110
+ await gitExec(["diff", "--cached", "--quiet", "HEAD", "--", ...MERGE_EXCLUDED_PATHS], opts);
4111
+ await gitExec(["diff", "--quiet", "HEAD", "--", ...MERGE_EXCLUDED_PATHS], opts);
4112
+ const untracked = (await gitExec(
4113
+ ["ls-files", "--others", "--exclude-standard", "--", ...MERGE_EXCLUDED_PATHS],
4114
+ opts
4115
+ )).trim();
4116
+ if (untracked) {
4117
+ throw new Error(
4118
+ `untracked memory files remain after restore: ${untracked.replace(/\n/g, ", ")}`
4119
+ );
4054
4120
  }
4055
- return null;
4056
4121
  }
4057
4122
 
4058
4123
  // packages/daemon/src/lib/template/convert-session.ts
@@ -4237,7 +4302,7 @@ async function getMindStatus(name, port, registryRunning) {
4237
4302
  let status = "stopped";
4238
4303
  let wakeAt = null;
4239
4304
  try {
4240
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-Z27R3WTW.js");
4305
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-DSPXFHGX.js");
4241
4306
  const sleepManager = getSleepManagerIfReady2();
4242
4307
  if (sleepManager?.isSleeping(name)) {
4243
4308
  status = "sleeping";
@@ -4324,8 +4389,8 @@ async function updateTemplateBranch(projectRoot, template, mindName) {
4324
4389
  await gitExec(["worktree", "remove", "--force", tempWorktree], { cwd: projectRoot });
4325
4390
  } catch {
4326
4391
  }
4327
- if (existsSync8(tempWorktree)) {
4328
- rmSync4(tempWorktree, { recursive: true, force: true });
4392
+ if (existsSync7(tempWorktree)) {
4393
+ rmSync3(tempWorktree, { recursive: true, force: true });
4329
4394
  }
4330
4395
  const templatesRoot = findTemplatesRoot();
4331
4396
  const { composedDir, manifest } = composeTemplate(templatesRoot, template);
@@ -4346,14 +4411,14 @@ async function updateTemplateBranch(projectRoot, template, mindName) {
4346
4411
  }
4347
4412
  copyTemplateToDir(composedDir, tempWorktree, mindName, manifest);
4348
4413
  const initDir = resolve11(tempWorktree, ".init");
4349
- if (existsSync8(initDir)) {
4350
- rmSync4(initDir, { recursive: true, force: true });
4414
+ if (existsSync7(initDir)) {
4415
+ rmSync3(initDir, { recursive: true, force: true });
4351
4416
  }
4352
4417
  const homeDir = resolve11(tempWorktree, "home");
4353
- if (existsSync8(homeDir)) {
4418
+ if (existsSync7(homeDir)) {
4354
4419
  for (const entry of readdirSync2(homeDir)) {
4355
4420
  if (entry !== "VOLUTE.md") {
4356
- rmSync4(resolve11(homeDir, entry), { recursive: true, force: true });
4421
+ rmSync3(resolve11(homeDir, entry), { recursive: true, force: true });
4357
4422
  }
4358
4423
  }
4359
4424
  }
@@ -4368,10 +4433,10 @@ async function updateTemplateBranch(projectRoot, template, mindName) {
4368
4433
  await gitExec(["worktree", "remove", "--force", tempWorktree], { cwd: projectRoot });
4369
4434
  } catch {
4370
4435
  }
4371
- if (existsSync8(tempWorktree)) {
4372
- rmSync4(tempWorktree, { recursive: true, force: true });
4436
+ if (existsSync7(tempWorktree)) {
4437
+ rmSync3(tempWorktree, { recursive: true, force: true });
4373
4438
  }
4374
- rmSync4(composedDir, { recursive: true, force: true });
4439
+ rmSync3(composedDir, { recursive: true, force: true });
4375
4440
  }
4376
4441
  }
4377
4442
  async function mergeTemplateBranch(worktreeDir) {
@@ -4420,6 +4485,7 @@ async function mergeUpgradeAndRestart(mindName, dir, worktreeDir, upgradeVariant
4420
4485
  if (templateChanged) {
4421
4486
  try {
4422
4487
  applyTemplateHomeFiles(resolve11(dir, "home"), template);
4488
+ const migratedSkills = migrateSkillsToTemplate(dir, oldTemplate, template);
4423
4489
  await gitExec(["add", "home/"], { cwd: dir });
4424
4490
  try {
4425
4491
  await gitExec(["diff", "--cached", "--quiet"], { cwd: dir });
@@ -4429,7 +4495,8 @@ async function mergeUpgradeAndRestart(mindName, dir, worktreeDir, upgradeVariant
4429
4495
  });
4430
4496
  }
4431
4497
  await chownMindDir(dir, mindName);
4432
- switchWarning = `Switched ${oldTemplate}\u2192${template}: config reset to ${template} defaults, mechanics doc replaced, conversation starts fresh (sessions aren't portable across runtimes).`;
4498
+ const skillNote = migratedSkills.length > 0 ? ` Migrated skills to the ${template} skills dir: ${migratedSkills.join(", ")}.` : "";
4499
+ switchWarning = `Switched ${oldTemplate}\u2192${template}: config reset to ${template} defaults, mechanics doc replaced, conversation starts fresh (sessions aren't portable across runtimes).${skillNote}`;
4433
4500
  } catch (err) {
4434
4501
  logger_default.warn(`failed to swap template home files for ${mindName}`, logger_default.errorData(err));
4435
4502
  return {
@@ -4470,7 +4537,7 @@ async function mergeUpgradeAndRestart(mindName, dir, worktreeDir, upgradeVariant
4470
4537
  }
4471
4538
  async function importFromArchive(c, tempDir, nameOverride, manifest) {
4472
4539
  const extractedMindDir = resolve11(tempDir, "mind");
4473
- if (!existsSync8(extractedMindDir)) {
4540
+ if (!existsSync7(extractedMindDir)) {
4474
4541
  return c.json({ error: "Invalid archive: missing mind/ directory" }, 400);
4475
4542
  }
4476
4543
  if (!manifest?.includes || !manifest.name || !manifest.template) {
@@ -4488,7 +4555,7 @@ async function importFromFullArchive(c, tempDir, extractedMindDir, nameOverride,
4488
4555
  if (await findMind(name)) return c.json({ error: `Mind already exists: ${name}` }, 409);
4489
4556
  ensureVoluteHome();
4490
4557
  const dest = mindDir(name);
4491
- if (existsSync8(dest)) return c.json({ error: "Mind directory already exists" }, 409);
4558
+ if (existsSync7(dest)) return c.json({ error: "Mind directory already exists" }, 409);
4492
4559
  try {
4493
4560
  cpSync(extractedMindDir, dest, { recursive: true });
4494
4561
  if (!manifest.includes.identity) {
@@ -4497,7 +4564,7 @@ async function importFromFullArchive(c, tempDir, extractedMindDir, nameOverride,
4497
4564
  const state = stateDir(name);
4498
4565
  mkdirSync6(state, { recursive: true });
4499
4566
  const envJson = resolve11(tempDir, "state/env.json");
4500
- if (existsSync8(envJson)) {
4567
+ if (existsSync7(envJson)) {
4501
4568
  cpSync(envJson, resolve11(state, "env.json"));
4502
4569
  }
4503
4570
  const port = await nextPort();
@@ -4514,7 +4581,7 @@ async function importFromFullArchive(c, tempDir, extractedMindDir, nameOverride,
4514
4581
  await npmInstallAsMind(dest, name);
4515
4582
  await importHistoryFromArchive(name, tempDir);
4516
4583
  importSessionsFromArchive(dest, tempDir);
4517
- if (!existsSync8(resolve11(dest, ".git"))) {
4584
+ if (!existsSync7(resolve11(dest, ".git"))) {
4518
4585
  try {
4519
4586
  const env = isIsolationEnabled() ? { ...process.env, HOME: resolve11(dest, "home") } : void 0;
4520
4587
  await gitExec(["init"], { cwd: dest, mindName: name, env });
@@ -4523,20 +4590,20 @@ async function importFromFullArchive(c, tempDir, extractedMindDir, nameOverride,
4523
4590
  await gitExec(["commit", "-m", "import from archive"], { cwd: dest, mindName: name, env });
4524
4591
  } catch (err) {
4525
4592
  logger_default.error(`git setup failed for imported mind ${name}`, logger_default.errorData(err));
4526
- rmSync4(resolve11(dest, ".git"), { recursive: true, force: true });
4593
+ rmSync3(resolve11(dest, ".git"), { recursive: true, force: true });
4527
4594
  }
4528
4595
  }
4529
4596
  await chownMindDir(dest, name);
4530
- rmSync4(tempDir, { recursive: true, force: true });
4597
+ rmSync3(tempDir, { recursive: true, force: true });
4531
4598
  return c.json({ ok: true, name, port, message: `Imported mind: ${name} (port ${port})` });
4532
4599
  } catch (err) {
4533
- if (existsSync8(dest)) rmSync4(dest, { recursive: true, force: true });
4600
+ if (existsSync7(dest)) rmSync3(dest, { recursive: true, force: true });
4534
4601
  try {
4535
4602
  await removeMind(name);
4536
4603
  } catch (cleanupErr) {
4537
4604
  logger_default.error(`Failed to clean up registry for ${name}`, logger_default.errorData(cleanupErr));
4538
4605
  }
4539
- rmSync4(tempDir, { recursive: true, force: true });
4606
+ rmSync3(tempDir, { recursive: true, force: true });
4540
4607
  return c.json({ error: err instanceof Error ? err.message : "Failed to import mind" }, 500);
4541
4608
  }
4542
4609
  }
@@ -4547,7 +4614,7 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
4547
4614
  if (await findMind(name)) return c.json({ error: `Mind already exists: ${name}` }, 409);
4548
4615
  ensureVoluteHome();
4549
4616
  const dest = mindDir(name);
4550
- if (existsSync8(dest)) return c.json({ error: "Mind directory already exists" }, 409);
4617
+ if (existsSync7(dest)) return c.json({ error: "Mind directory already exists" }, 409);
4551
4618
  const templatesRoot = findTemplatesRoot();
4552
4619
  const { composedDir, manifest: templateManifest } = composeTemplate(
4553
4620
  templatesRoot,
@@ -4557,22 +4624,22 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
4557
4624
  copyTemplateToDir(composedDir, dest, name, templateManifest);
4558
4625
  applyInitFiles(dest);
4559
4626
  const extractedHome = resolve11(extractedMindDir, "home");
4560
- if (existsSync8(extractedHome)) {
4627
+ if (existsSync7(extractedHome)) {
4561
4628
  cpSync(extractedHome, resolve11(dest, "home"), { recursive: true });
4562
4629
  }
4563
4630
  const extractedMindInternal = resolve11(extractedMindDir, ".mind");
4564
- if (existsSync8(extractedMindInternal)) {
4631
+ if (existsSync7(extractedMindInternal)) {
4565
4632
  cpSync(extractedMindInternal, resolve11(dest, ".mind"), { recursive: true });
4566
4633
  }
4567
4634
  const identityDir = resolve11(dest, ".mind/identity");
4568
4635
  let publicKeyPem;
4569
- if (!manifest.includes.identity || !existsSync8(resolve11(identityDir, "private.pem"))) {
4636
+ if (!manifest.includes.identity || !existsSync7(resolve11(identityDir, "private.pem"))) {
4570
4637
  ({ publicKeyPem } = generateIdentity(dest));
4571
4638
  } else {
4572
4639
  publicKeyPem = readFileSync9(resolve11(identityDir, "public.pem"), "utf-8");
4573
4640
  }
4574
4641
  const promptsPath = resolve11(dest, "home/.config/prompts.json");
4575
- if (!existsSync8(promptsPath)) {
4642
+ if (!existsSync7(promptsPath)) {
4576
4643
  const mindPrompts = await getMindPromptDefaults();
4577
4644
  writeFileSync8(promptsPath, `${JSON.stringify(mindPrompts, null, 2)}
4578
4645
  `);
@@ -4580,7 +4647,7 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
4580
4647
  const state = stateDir(name);
4581
4648
  mkdirSync6(state, { recursive: true });
4582
4649
  const envJson = resolve11(tempDir, "state/env.json");
4583
- if (existsSync8(envJson)) {
4650
+ if (existsSync7(envJson)) {
4584
4651
  cpSync(envJson, resolve11(state, "env.json"));
4585
4652
  }
4586
4653
  const port = await nextPort();
@@ -4598,7 +4665,7 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
4598
4665
  await initTemplateBranch(dest, composedDir, templateManifest, name, env);
4599
4666
  } catch (err) {
4600
4667
  logger_default.error(`git setup failed for imported mind ${name}`, logger_default.errorData(err));
4601
- rmSync4(resolve11(dest, ".git"), { recursive: true, force: true });
4668
+ rmSync3(resolve11(dest, ".git"), { recursive: true, force: true });
4602
4669
  gitWarning = "Git setup failed \u2014 variants and upgrades won't be available until git is initialized.";
4603
4670
  }
4604
4671
  const skillSet = manifest.stage === "seed" ? SEED_SKILLS : getStandardSkillsWithExtensions();
@@ -4617,7 +4684,7 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
4617
4684
  publishPublicKey(name, publicKeyPem).catch(
4618
4685
  (err) => logger_default.warn(`failed to publish key for ${name}`, { error: err.message })
4619
4686
  );
4620
- rmSync4(tempDir, { recursive: true, force: true });
4687
+ rmSync3(tempDir, { recursive: true, force: true });
4621
4688
  return c.json({
4622
4689
  ok: true,
4623
4690
  name,
@@ -4628,21 +4695,21 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
4628
4695
  ...skillWarnings.length > 0 && { skillWarnings }
4629
4696
  });
4630
4697
  } catch (err) {
4631
- if (existsSync8(dest)) rmSync4(dest, { recursive: true, force: true });
4698
+ if (existsSync7(dest)) rmSync3(dest, { recursive: true, force: true });
4632
4699
  try {
4633
4700
  await removeMind(name);
4634
4701
  } catch (cleanupErr) {
4635
4702
  logger_default.error(`Failed to clean up registry for ${name}`, logger_default.errorData(cleanupErr));
4636
4703
  }
4637
- rmSync4(tempDir, { recursive: true, force: true });
4704
+ rmSync3(tempDir, { recursive: true, force: true });
4638
4705
  return c.json({ error: err instanceof Error ? err.message : "Failed to import mind" }, 500);
4639
4706
  } finally {
4640
- rmSync4(composedDir, { recursive: true, force: true });
4707
+ rmSync3(composedDir, { recursive: true, force: true });
4641
4708
  }
4642
4709
  }
4643
4710
  async function importHistoryFromArchive(name, tempDir) {
4644
4711
  const historyJsonl = resolve11(tempDir, "history.jsonl");
4645
- if (!existsSync8(historyJsonl)) return;
4712
+ if (!existsSync7(historyJsonl)) return;
4646
4713
  try {
4647
4714
  const db = await getDb();
4648
4715
  const lines = readFileSync9(historyJsonl, "utf-8").trim().split("\n");
@@ -4682,7 +4749,7 @@ async function importHistoryFromArchive(name, tempDir) {
4682
4749
  }
4683
4750
  function importSessionsFromArchive(dest, tempDir) {
4684
4751
  const sessionsDir = resolve11(tempDir, "sessions");
4685
- if (!existsSync8(sessionsDir)) return;
4752
+ if (!existsSync7(sessionsDir)) return;
4686
4753
  try {
4687
4754
  const destSessions = resolve11(dest, ".mind/sessions");
4688
4755
  mkdirSync6(destSessions, { recursive: true });
@@ -4752,9 +4819,14 @@ var app13 = new Hono16().post("/", requireAdminOrSystem, zValidator6("json", cre
4752
4819
  const nameErr = validateMindName(name);
4753
4820
  if (nameErr) return c.json({ error: nameErr }, 400);
4754
4821
  if (await findMind(name)) return c.json({ error: `Mind already exists: ${name}` }, 409);
4822
+ {
4823
+ const { mindLimitError, readGlobalConfig: readGlobalConfig2 } = await import("./setup-TCN7VH5U.js");
4824
+ const limitError = mindLimitError(await countCappedMinds(), readGlobalConfig2().maxMinds);
4825
+ if (limitError) return c.json({ error: limitError }, 409);
4826
+ }
4755
4827
  ensureVoluteHome();
4756
4828
  const dest = mindDir(name);
4757
- if (existsSync8(dest)) return c.json({ error: "Mind directory already exists" }, 409);
4829
+ if (existsSync7(dest)) return c.json({ error: "Mind directory already exists" }, 409);
4758
4830
  const templatesRoot = findTemplatesRoot();
4759
4831
  const { composedDir, manifest } = composeTemplate(templatesRoot, template);
4760
4832
  try {
@@ -4763,7 +4835,7 @@ var app13 = new Hono16().post("/", requireAdminOrSystem, zValidator6("json", cre
4763
4835
  const { publicKeyPem } = generateIdentity(dest);
4764
4836
  let effectiveModel = body.model;
4765
4837
  {
4766
- const { readGlobalConfig: readGlobal } = await import("./setup-5ECWTYKG.js");
4838
+ const { readGlobalConfig: readGlobal } = await import("./setup-TCN7VH5U.js");
4767
4839
  const mindDefaults = readGlobal().mindDefaults;
4768
4840
  const config2 = readVoluteConfig(dest);
4769
4841
  if (!config2) throw new Error("Failed to read volute.json after identity generation");
@@ -4793,7 +4865,7 @@ var app13 = new Hono16().post("/", requireAdminOrSystem, zValidator6("json", cre
4793
4865
  effectiveModel = modelId ? qualifyModelId(modelId) : void 0;
4794
4866
  const sdkConfigPath = resolve11(dest, "home/.config/config.json");
4795
4867
  if (modelId || cog?.compaction) {
4796
- const existing = existsSync8(sdkConfigPath) ? JSON.parse(readFileSync9(sdkConfigPath, "utf-8")) : {};
4868
+ const existing = existsSync7(sdkConfigPath) ? JSON.parse(readFileSync9(sdkConfigPath, "utf-8")) : {};
4797
4869
  if (modelId) {
4798
4870
  existing.model = template === "pi" ? qualifyModelId(modelId) : unqualifyModelId(modelId);
4799
4871
  }
@@ -4831,7 +4903,7 @@ var app13 = new Hono16().post("/", requireAdminOrSystem, zValidator6("json", cre
4831
4903
  await initTemplateBranch(dest, composedDir, manifest, name, env);
4832
4904
  } catch (err) {
4833
4905
  logger_default.error(`git setup failed for ${name}`, logger_default.errorData(err));
4834
- rmSync4(resolve11(dest, ".git"), { recursive: true, force: true });
4906
+ rmSync3(resolve11(dest, ".git"), { recursive: true, force: true });
4835
4907
  gitWarning = "Git setup failed \u2014 variants and upgrades won't be available until git is initialized.";
4836
4908
  }
4837
4909
  if (body.stage === "seed") {
@@ -4844,7 +4916,7 @@ Your creator described you as: "${body.description}"
4844
4916
  }
4845
4917
  let skillSet = body.skills ?? (body.stage === "seed" ? SEED_SKILLS : getStandardSkillsWithExtensions());
4846
4918
  if (body.stage === "seed" && !body.skills) {
4847
- const { isImagegenEnabled } = await import("./setup-5ECWTYKG.js");
4919
+ const { isImagegenEnabled } = await import("./setup-TCN7VH5U.js");
4848
4920
  if (isImagegenEnabled()) {
4849
4921
  skillSet = [...skillSet, "imagegen"];
4850
4922
  }
@@ -4865,7 +4937,7 @@ Your creator described you as: "${body.description}"
4865
4937
  try {
4866
4938
  const spiritEntry = await findMind("volute");
4867
4939
  if (spiritEntry) {
4868
- const { spiritDir } = await import("./spirit-WXS3HCLN.js");
4940
+ const { spiritDir } = await import("./spirit-SSJMYC6A.js");
4869
4941
  const sDir = spiritEntry.dir ?? spiritDir();
4870
4942
  const spiritConfig = readVoluteConfig(sDir) ?? {};
4871
4943
  const schedules = spiritConfig.schedules ?? [];
@@ -4880,7 +4952,7 @@ Your creator described you as: "${body.description}"
4880
4952
  });
4881
4953
  spiritConfig.schedules = schedules;
4882
4954
  writeVoluteConfig(sDir, spiritConfig);
4883
- const { getScheduler: getScheduler2 } = await import("./scheduler-I7JZCT47.js");
4955
+ const { getScheduler: getScheduler2 } = await import("./scheduler-K7Y7Z7FQ.js");
4884
4956
  getScheduler2().loadSchedules("volute", sDir);
4885
4957
  }
4886
4958
  }
@@ -4934,14 +5006,14 @@ Your creator described you as: "${body.description}"
4934
5006
  ...skillWarnings.length > 0 && { skillWarnings }
4935
5007
  });
4936
5008
  } catch (err) {
4937
- if (existsSync8(dest)) rmSync4(dest, { recursive: true, force: true });
5009
+ if (existsSync7(dest)) rmSync3(dest, { recursive: true, force: true });
4938
5010
  try {
4939
5011
  await removeMind(name);
4940
5012
  } catch {
4941
5013
  }
4942
5014
  return c.json({ error: err instanceof Error ? err.message : "Failed to create mind" }, 500);
4943
5015
  } finally {
4944
- rmSync4(composedDir, { recursive: true, force: true });
5016
+ rmSync3(composedDir, { recursive: true, force: true });
4945
5017
  }
4946
5018
  }).post("/import", requireAdmin, async (c) => {
4947
5019
  let body;
@@ -4954,13 +5026,13 @@ Your creator described you as: "${body.description}"
4954
5026
  return importFromArchive(c, body.archivePath, body.name, body.manifest);
4955
5027
  }
4956
5028
  const wsDir = body.workspacePath;
4957
- if (!wsDir || !existsSync8(resolve11(wsDir, "SOUL.md")) || !existsSync8(resolve11(wsDir, "IDENTITY.md"))) {
5029
+ if (!wsDir || !existsSync7(resolve11(wsDir, "SOUL.md")) || !existsSync7(resolve11(wsDir, "IDENTITY.md"))) {
4958
5030
  return c.json({ error: "Invalid workspace: missing SOUL.md or IDENTITY.md" }, 400);
4959
5031
  }
4960
5032
  const soul = readFileSync9(resolve11(wsDir, "SOUL.md"), "utf-8");
4961
5033
  const identity = readFileSync9(resolve11(wsDir, "IDENTITY.md"), "utf-8");
4962
5034
  const userPath = resolve11(wsDir, "USER.md");
4963
- const user = existsSync8(userPath) ? readFileSync9(userPath, "utf-8") : "";
5035
+ const user = existsSync7(userPath) ? readFileSync9(userPath, "utf-8") : "";
4964
5036
  const name = body.name ?? parseNameFromIdentity(identity) ?? "imported-mind";
4965
5037
  const template = body.template ?? "claude";
4966
5038
  const nameErr = validateMindName(name);
@@ -4980,7 +5052,7 @@ ${user.trimEnd()}
4980
5052
  ` : "";
4981
5053
  ensureVoluteHome();
4982
5054
  const dest = mindDir(name);
4983
- if (existsSync8(dest)) return c.json({ error: "Mind directory already exists" }, 409);
5055
+ if (existsSync7(dest)) return c.json({ error: "Mind directory already exists" }, 409);
4984
5056
  const templatesRoot = findTemplatesRoot();
4985
5057
  const { composedDir, manifest } = composeTemplate(templatesRoot, template);
4986
5058
  try {
@@ -4989,7 +5061,7 @@ ${user.trimEnd()}
4989
5061
  const { publicKeyPem: importPublicKey } = generateIdentity(dest);
4990
5062
  writeFileSync8(resolve11(dest, "home/SOUL.md"), mergedSoul);
4991
5063
  const wsMemoryPath = resolve11(wsDir, "MEMORY.md");
4992
- const hasMemory = existsSync8(wsMemoryPath);
5064
+ const hasMemory = existsSync7(wsMemoryPath);
4993
5065
  if (hasMemory) {
4994
5066
  const existingMemory = readFileSync9(wsMemoryPath, "utf-8");
4995
5067
  writeFileSync8(
@@ -5002,7 +5074,7 @@ ${user.trimEnd()}
5002
5074
  }
5003
5075
  const wsMemoryDir = resolve11(wsDir, "memory");
5004
5076
  let dailyLogCount = 0;
5005
- if (existsSync8(wsMemoryDir)) {
5077
+ if (existsSync7(wsMemoryDir)) {
5006
5078
  const destMemoryDir = resolve11(dest, "home/memory");
5007
5079
  const files = readdirSync2(wsMemoryDir).filter((f) => f.endsWith(".md"));
5008
5080
  for (const file of files) {
@@ -5031,7 +5103,7 @@ ${user.trimEnd()}
5031
5103
  await gitExec(["add", "-A"], { cwd: dest, mindName: name, env });
5032
5104
  await gitExec(["commit", "-m", "import from OpenClaw"], { cwd: dest, mindName: name, env });
5033
5105
  const sessionFile = body.sessionPath ? resolve11(body.sessionPath) : findOpenClawSession(wsDir);
5034
- if (sessionFile && existsSync8(sessionFile)) {
5106
+ if (sessionFile && existsSync7(sessionFile)) {
5035
5107
  if (template === "pi") {
5036
5108
  importPiSession(sessionFile, dest);
5037
5109
  } else if (template === "claude") {
@@ -5048,14 +5120,14 @@ ${user.trimEnd()}
5048
5120
  );
5049
5121
  return c.json({ ok: true, name, port, message: `Imported mind: ${name} (port ${port})` });
5050
5122
  } catch (err) {
5051
- if (existsSync8(dest)) rmSync4(dest, { recursive: true, force: true });
5123
+ if (existsSync7(dest)) rmSync3(dest, { recursive: true, force: true });
5052
5124
  try {
5053
5125
  await removeMind(name);
5054
5126
  } catch {
5055
5127
  }
5056
5128
  return c.json({ error: err instanceof Error ? err.message : "Failed to import mind" }, 500);
5057
5129
  } finally {
5058
- rmSync4(composedDir, { recursive: true, force: true });
5130
+ rmSync3(composedDir, { recursive: true, force: true });
5059
5131
  }
5060
5132
  }).get("/", async (c) => {
5061
5133
  const entries = await readRegistry();
@@ -5080,7 +5152,7 @@ ${user.trimEnd()}
5080
5152
  const minds = await Promise.all(
5081
5153
  entries.map(async (entry) => {
5082
5154
  const mindStatus = await getMindStatus(entry.name, entry.port, entry.running);
5083
- const hasPages = existsSync8(resolve11(mindDir(entry.name), "home", "pages"));
5155
+ const hasPages = existsSync7(resolve11(mindDir(entry.name), "home", "pages"));
5084
5156
  const lastActiveAt = lastActiveMap.get(entry.name) ?? null;
5085
5157
  if (!privileged) return toPublicMind(entry, mindStatus, { hasPages, lastActiveAt });
5086
5158
  return {
@@ -5098,9 +5170,9 @@ ${user.trimEnd()}
5098
5170
  const entry = await findMind(name);
5099
5171
  if (!entry) return c.json({ error: "Mind not found" }, 404);
5100
5172
  const dir = entry.dir ?? mindDir(entry.parent ?? name);
5101
- if (!existsSync8(dir)) return c.json({ error: "Mind directory missing" }, 404);
5173
+ if (!existsSync7(dir)) return c.json({ error: "Mind directory missing" }, 404);
5102
5174
  const mindStatus = await getMindStatus(name, entry.port);
5103
- const hasPages = existsSync8(resolve11(mindDir(name), "home", "pages"));
5175
+ const hasPages = existsSync7(resolve11(mindDir(name), "home", "pages"));
5104
5176
  if (!isPrivileged(c)) {
5105
5177
  return c.json(toPublicMind(entry, mindStatus, { hasPages }));
5106
5178
  }
@@ -5137,11 +5209,11 @@ ${user.trimEnd()}
5137
5209
  const entry = await findMind(name);
5138
5210
  if (!entry) return c.json({ error: "Mind not found" }, 404);
5139
5211
  const targetPort = entry.port;
5212
+ const projectDir = entry.dir ?? mindDir(name);
5140
5213
  if (entry.parent) {
5141
5214
  if (!entry.dir) return c.json({ error: `Variant ${name} has no directory` }, 404);
5142
- } else {
5143
- const dir = mindDir(name);
5144
- if (!existsSync8(dir)) return c.json({ error: "Mind directory missing" }, 404);
5215
+ } else if (!existsSync7(projectDir)) {
5216
+ return c.json({ error: "Mind directory missing" }, 404);
5145
5217
  }
5146
5218
  if (getMindManager().isRunning(name)) {
5147
5219
  return c.json({ error: "Mind already running" }, 409);
@@ -5158,11 +5230,11 @@ ${user.trimEnd()}
5158
5230
  if (!entry) return c.json({ error: "Mind not found" }, 404);
5159
5231
  const baseName = entry.parent ?? name;
5160
5232
  const targetPort = entry.port;
5233
+ const projectDir = entry.dir ?? mindDir(name);
5161
5234
  if (entry.parent) {
5162
5235
  if (!entry.dir) return c.json({ error: `Variant ${name} has no directory` }, 404);
5163
- } else {
5164
- const dir = mindDir(name);
5165
- if (!existsSync8(dir)) return c.json({ error: "Mind directory missing" }, 404);
5236
+ } else if (!existsSync7(projectDir)) {
5237
+ return c.json({ error: "Mind directory missing" }, 404);
5166
5238
  }
5167
5239
  let context;
5168
5240
  const contentType = c.req.header("content-type");
@@ -5177,7 +5249,7 @@ ${user.trimEnd()}
5177
5249
  const manager = getMindManager();
5178
5250
  try {
5179
5251
  if (context?.type === "reload") {
5180
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-Z27R3WTW.js");
5252
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-DSPXFHGX.js");
5181
5253
  const sleepState = getSleepManagerIfReady2()?.getState(name);
5182
5254
  if (sleepState?.sleeping) {
5183
5255
  logger_default.info(`skipping reload for ${name} during sleep \u2014 will apply on next wake`);
@@ -5197,7 +5269,7 @@ ${user.trimEnd()}
5197
5269
  const variantEntry = await findMind(mergeVariantName);
5198
5270
  if (variantEntry && variantEntry.parent === baseName && variantEntry.dir && variantEntry.branch) {
5199
5271
  const projectRoot = mindDir(baseName);
5200
- if (existsSync8(variantEntry.dir)) {
5272
+ if (existsSync7(variantEntry.dir)) {
5201
5273
  const status = (await gitExec(["status", "--porcelain"], { cwd: variantEntry.dir })).trim();
5202
5274
  if (status) {
5203
5275
  try {
@@ -5224,7 +5296,8 @@ ${user.trimEnd()}
5224
5296
  logger_default.error(`failed to auto-commit main worktree for ${baseName}`, logger_default.errorData(e));
5225
5297
  }
5226
5298
  }
5227
- await gitExec(["merge", variantEntry.branch], { cwd: projectRoot });
5299
+ const memoryDelta = await mergeVariantExcludingMemory(projectRoot, variantEntry.branch);
5300
+ if (memoryDelta && context) context.memoryDelta = memoryDelta;
5228
5301
  await cleanupVariant(mergeVariantName, projectRoot, variantEntry.dir);
5229
5302
  try {
5230
5303
  await npmInstallAsMind(projectRoot, baseName);
@@ -5238,18 +5311,12 @@ ${user.trimEnd()}
5238
5311
  }
5239
5312
  if (context?.type === "sprouted" && !entry.parent) {
5240
5313
  try {
5241
- const mindConvs = await listConversationsForMind(baseName);
5242
- for (const conv of mindConvs) {
5243
- await recordInbound(baseName, "system", "system", "[seed has sprouted]");
5244
- await addMessage(conv.id, "assistant", "system", [
5245
- { type: "text", text: "[seed has sprouted]" }
5246
- ]);
5247
- }
5314
+ await deliverSproutedNotice(baseName);
5248
5315
  } catch (err) {
5249
5316
  logger_default.error(`failed to inject sprouted message for ${baseName}`, logger_default.errorData(err));
5250
5317
  }
5251
5318
  }
5252
- const repoDir = entry.parent ? entry.dir : mindDir(name);
5319
+ const repoDir = projectDir;
5253
5320
  try {
5254
5321
  await startMindFull(name);
5255
5322
  } catch (startErr) {
@@ -5304,7 +5371,7 @@ ${user.trimEnd()}
5304
5371
  const name = c.req.param("name");
5305
5372
  const entry = await findMind(name);
5306
5373
  if (!entry) return c.json({ error: "Mind not found" }, 404);
5307
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-Z27R3WTW.js");
5374
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-DSPXFHGX.js");
5308
5375
  const sm = getSleepManagerIfReady2();
5309
5376
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
5310
5377
  return c.json(sm.getState(name));
@@ -5312,7 +5379,7 @@ ${user.trimEnd()}
5312
5379
  const name = c.req.param("name");
5313
5380
  const entry = await findMind(name);
5314
5381
  if (!entry) return c.json({ error: "Mind not found" }, 404);
5315
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-Z27R3WTW.js");
5382
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-DSPXFHGX.js");
5316
5383
  const sm = getSleepManagerIfReady2();
5317
5384
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
5318
5385
  if (sm.isSleeping(name)) return c.json({ error: "Mind is already sleeping" }, 409);
@@ -5332,7 +5399,7 @@ ${user.trimEnd()}
5332
5399
  const name = c.req.param("name");
5333
5400
  const entry = await findMind(name);
5334
5401
  if (!entry) return c.json({ error: "Mind not found" }, 404);
5335
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-Z27R3WTW.js");
5402
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-DSPXFHGX.js");
5336
5403
  const sm = getSleepManagerIfReady2();
5337
5404
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
5338
5405
  const sleepState = sm.getState(name);
@@ -5347,7 +5414,7 @@ ${user.trimEnd()}
5347
5414
  const name = c.req.param("name");
5348
5415
  const entry = await findMind(name);
5349
5416
  if (!entry) return c.json({ error: "Mind not found" }, 404);
5350
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-Z27R3WTW.js");
5417
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-DSPXFHGX.js");
5351
5418
  const sm = getSleepManagerIfReady2();
5352
5419
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
5353
5420
  const flushed = await sm.flushQueuedMessages(name);
@@ -5368,14 +5435,14 @@ ${user.trimEnd()}
5368
5435
  if (!avatarPath) {
5369
5436
  return c.json({ error: "Avatar path must be inside the mind's home directory" }, 400);
5370
5437
  }
5371
- if (!existsSync8(avatarPath)) {
5438
+ if (!existsSync7(avatarPath)) {
5372
5439
  return c.json({ error: `Avatar file not found: ${relative(homeDir, avatarPath)}` }, 400);
5373
5440
  }
5374
5441
  profile.avatar = relative(homeDir, avatarPath);
5375
5442
  }
5376
5443
  config2.profile = profile;
5377
5444
  writeVoluteConfig(dir, config2);
5378
- const { syncMindProfile: syncMindProfile2 } = await import("./auth-A4VGHADI.js");
5445
+ const { syncMindProfile: syncMindProfile2 } = await import("./auth-ABYLOEZ7.js");
5379
5446
  await syncMindProfile2(name, profile);
5380
5447
  broadcast({ type: "profile_updated", mind: name, summary: `${name} profile updated` });
5381
5448
  return c.json({ ok: true });
@@ -5414,7 +5481,7 @@ ${user.trimEnd()}
5414
5481
  return c.json({ output: "" });
5415
5482
  }
5416
5483
  const dir = entry.dir ?? mindDir(name);
5417
- const { evaluateSeedChecklist } = await import("./seed-readiness-RBPGOLSZ.js");
5484
+ const { evaluateSeedChecklist } = await import("./seed-readiness-3LRYASA4.js");
5418
5485
  const checklist = evaluateSeedChecklist(dir);
5419
5486
  const done = [];
5420
5487
  const remaining = [];
@@ -5449,10 +5516,13 @@ ${user.trimEnd()}
5449
5516
  return c.json({ error: `Mind is not a seed (stage: ${entry.stage})` }, 409);
5450
5517
  }
5451
5518
  await setMindStage(name, "sprouted");
5519
+ await joinSystemChannelForMind(name).catch(
5520
+ (err) => logger_default.warn(`failed to join #system on sprout for ${name}`, logger_default.errorData(err))
5521
+ );
5452
5522
  try {
5453
5523
  const sproutedDir = entry.dir ?? mindDir(name);
5454
5524
  if (setupDefaultDreaming(sproutedDir).schedulesChanged) {
5455
- const { getScheduler: getScheduler2 } = await import("./scheduler-I7JZCT47.js");
5525
+ const { getScheduler: getScheduler2 } = await import("./scheduler-K7Y7Z7FQ.js");
5456
5526
  getScheduler2().loadSchedules(name, sproutedDir);
5457
5527
  }
5458
5528
  } catch (err) {
@@ -5461,7 +5531,7 @@ ${user.trimEnd()}
5461
5531
  try {
5462
5532
  const spiritEntry = await findMind("volute");
5463
5533
  if (spiritEntry) {
5464
- const { firstWeekSchedules, spiritDir } = await import("./spirit-WXS3HCLN.js");
5534
+ const { firstWeekSchedules, spiritDir } = await import("./spirit-SSJMYC6A.js");
5465
5535
  const sDir = spiritEntry.dir ?? spiritDir();
5466
5536
  const spiritConfig = readVoluteConfig(sDir);
5467
5537
  if (spiritConfig) {
@@ -5475,7 +5545,7 @@ ${user.trimEnd()}
5475
5545
  spiritConfig.schedules = schedules;
5476
5546
  writeVoluteConfig(sDir, spiritConfig);
5477
5547
  try {
5478
- const { getScheduler: getScheduler2 } = await import("./scheduler-I7JZCT47.js");
5548
+ const { getScheduler: getScheduler2 } = await import("./scheduler-K7Y7Z7FQ.js");
5479
5549
  getScheduler2().loadSchedules("volute", sDir);
5480
5550
  } catch (err) {
5481
5551
  logger_default.warn(
@@ -5513,11 +5583,11 @@ ${user.trimEnd()}
5513
5583
  await deleteMindUser2(name);
5514
5584
  invalidateMindUserCache(name);
5515
5585
  const state = stateDir(name);
5516
- if (existsSync8(state)) {
5517
- rmSync4(state, { recursive: true, force: true });
5586
+ if (existsSync7(state)) {
5587
+ rmSync3(state, { recursive: true, force: true });
5518
5588
  }
5519
- if (force && existsSync8(dir)) {
5520
- rmSync4(dir, { recursive: true, force: true });
5589
+ if (force && existsSync7(dir)) {
5590
+ rmSync3(dir, { recursive: true, force: true });
5521
5591
  deleteMindUser(name);
5522
5592
  }
5523
5593
  fireWebhook({
@@ -5531,7 +5601,7 @@ ${user.trimEnd()}
5531
5601
  const entry = await findMind(mindName);
5532
5602
  if (!entry) return c.json({ error: "Mind not found" }, 404);
5533
5603
  const dir = mindDir(mindName);
5534
- if (!existsSync8(dir)) return c.json({ error: "Mind directory missing" }, 404);
5604
+ if (!existsSync7(dir)) return c.json({ error: "Mind directory missing" }, 404);
5535
5605
  let body = {};
5536
5606
  try {
5537
5607
  body = await c.req.json();
@@ -5546,14 +5616,14 @@ ${user.trimEnd()}
5546
5616
  const upgradeVariantName = `${mindName}-upgrade`;
5547
5617
  const worktreeDir = resolve11(dir, ".variants", UPGRADE_BRANCH);
5548
5618
  if (body.abort) {
5549
- if (!existsSync8(worktreeDir)) {
5619
+ if (!existsSync7(worktreeDir)) {
5550
5620
  return c.json({ error: "No upgrade in progress" }, 400);
5551
5621
  }
5552
5622
  try {
5553
5623
  try {
5554
5624
  const gitDirContent = readFileSync9(resolve11(worktreeDir, ".git"), "utf-8").trim();
5555
5625
  const gitDir = gitDirContent.replace("gitdir: ", "");
5556
- if (existsSync8(resolve11(gitDir, "MERGE_HEAD"))) {
5626
+ if (existsSync7(resolve11(gitDir, "MERGE_HEAD"))) {
5557
5627
  await gitExec(["merge", "--abort"], { cwd: worktreeDir });
5558
5628
  }
5559
5629
  } catch {
@@ -5572,7 +5642,7 @@ ${user.trimEnd()}
5572
5642
  }
5573
5643
  }
5574
5644
  if (body.continue) {
5575
- if (!existsSync8(worktreeDir)) {
5645
+ if (!existsSync7(worktreeDir)) {
5576
5646
  return c.json({ error: "No upgrade in progress" }, 400);
5577
5647
  }
5578
5648
  const status = await gitExec(["status", "--porcelain"], { cwd: worktreeDir });
@@ -5622,7 +5692,7 @@ ${user.trimEnd()}
5622
5692
  }
5623
5693
  }
5624
5694
  if (body.accept) {
5625
- if (existsSync8(worktreeDir)) {
5695
+ if (existsSync7(worktreeDir)) {
5626
5696
  try {
5627
5697
  await cleanupVariant(upgradeVariantName, dir, worktreeDir, { stop: true });
5628
5698
  } catch (err) {
@@ -5637,7 +5707,7 @@ ${user.trimEnd()}
5637
5707
  }
5638
5708
  if (body.diff) {
5639
5709
  try {
5640
- if (!existsSync8(resolve11(dir, ".git"))) {
5710
+ if (!existsSync7(resolve11(dir, ".git"))) {
5641
5711
  return c.json({ error: "Mind has no git history \u2014 nothing to diff against" }, 400);
5642
5712
  }
5643
5713
  await updateTemplateBranch(dir, template, mindName);
@@ -5655,13 +5725,13 @@ ${user.trimEnd()}
5655
5725
  );
5656
5726
  }
5657
5727
  }
5658
- if (existsSync8(worktreeDir)) {
5728
+ if (existsSync7(worktreeDir)) {
5659
5729
  return c.json(
5660
5730
  { error: "Upgrade variant already exists. Use continue or delete it first." },
5661
5731
  409
5662
5732
  );
5663
5733
  }
5664
- if (!existsSync8(resolve11(dir, ".git"))) {
5734
+ if (!existsSync7(resolve11(dir, ".git"))) {
5665
5735
  try {
5666
5736
  const env = isIsolationEnabled() ? { ...process.env, HOME: resolve11(dir, "home") } : void 0;
5667
5737
  await gitExec(["init"], { cwd: dir, mindName, env });
@@ -5670,7 +5740,7 @@ ${user.trimEnd()}
5670
5740
  await gitExec(["commit", "-m", "initial commit"], { cwd: dir, mindName, env });
5671
5741
  await chownMindDir(dir, mindName);
5672
5742
  } catch (err) {
5673
- rmSync4(resolve11(dir, ".git"), { recursive: true, force: true });
5743
+ rmSync3(resolve11(dir, ".git"), { recursive: true, force: true });
5674
5744
  return c.json(
5675
5745
  {
5676
5746
  error: `Git initialization failed: ${err instanceof Error ? err.message : String(err)}`
@@ -5686,7 +5756,7 @@ ${user.trimEnd()}
5686
5756
  }
5687
5757
  await updateTemplateBranch(dir, template, mindName);
5688
5758
  const parentDir = resolve11(dir, ".variants");
5689
- if (!existsSync8(parentDir)) {
5759
+ if (!existsSync7(parentDir)) {
5690
5760
  mkdirSync6(parentDir, { recursive: true });
5691
5761
  }
5692
5762
  await gitExec(["worktree", "add", "-b", UPGRADE_BRANCH, worktreeDir], { cwd: dir });
@@ -5816,11 +5886,11 @@ ${user.trimEnd()}
5816
5886
  const entry = await findMind(name);
5817
5887
  if (!entry) return c.json({ error: "Mind not found" }, 404);
5818
5888
  const dir = entry.dir ?? mindDir(name);
5819
- if (!existsSync8(dir)) return c.json({ error: "Mind directory missing" }, 404);
5889
+ if (!existsSync7(dir)) return c.json({ error: "Mind directory missing" }, 404);
5820
5890
  let config2 = readVoluteConfig(dir);
5821
5891
  if (!config2 && entry.template === "pi") {
5822
5892
  const piConfigPath = resolve11(dir, "home/.config/config.json");
5823
- if (existsSync8(piConfigPath)) {
5893
+ if (existsSync7(piConfigPath)) {
5824
5894
  try {
5825
5895
  config2 = JSON.parse(readFileSync9(piConfigPath, "utf-8"));
5826
5896
  } catch {
@@ -5829,7 +5899,7 @@ ${user.trimEnd()}
5829
5899
  }
5830
5900
  let templateConfig = {};
5831
5901
  const configJsonPath = resolve11(dir, "home/.config/config.json");
5832
- if (existsSync8(configJsonPath)) {
5902
+ if (existsSync7(configJsonPath)) {
5833
5903
  try {
5834
5904
  templateConfig = JSON.parse(readFileSync9(configJsonPath, "utf-8"));
5835
5905
  } catch {
@@ -5871,7 +5941,7 @@ ${user.trimEnd()}
5871
5941
  const entry = await findMind(name);
5872
5942
  if (!entry) return c.json({ error: "Mind not found" }, 404);
5873
5943
  const dir = entry.dir ?? mindDir(name);
5874
- if (!existsSync8(dir)) return c.json({ error: "Mind directory missing" }, 404);
5944
+ if (!existsSync7(dir)) return c.json({ error: "Mind directory missing" }, 404);
5875
5945
  const body = c.req.valid("json");
5876
5946
  const existing = readVoluteConfig(dir) ?? {};
5877
5947
  if (body.model !== void 0) existing.model = body.model;
@@ -5897,14 +5967,14 @@ ${user.trimEnd()}
5897
5967
  }
5898
5968
  writeVoluteConfig(dir, existing);
5899
5969
  if (body.unescapeNewlines !== void 0) {
5900
- const { clearEchoTextCache } = await import("./echo-text-H3SH337Q.js");
5970
+ const { clearEchoTextCache } = await import("./echo-text-B6NZ5FGE.js");
5901
5971
  clearEchoTextCache(name);
5902
5972
  }
5903
5973
  const needsConfigJson = body.model !== void 0 || body.thinkingLevel !== void 0 || body.compaction !== void 0;
5904
5974
  if (needsConfigJson) {
5905
5975
  const configJsonPath = resolve11(dir, "home/.config/config.json");
5906
5976
  let templateConfig = {};
5907
- if (existsSync8(configJsonPath)) {
5977
+ if (existsSync7(configJsonPath)) {
5908
5978
  try {
5909
5979
  templateConfig = JSON.parse(readFileSync9(configJsonPath, "utf-8"));
5910
5980
  } catch {
@@ -5934,7 +6004,7 @@ ${user.trimEnd()}
5934
6004
  }
5935
6005
  if (entry.mindType === "spirit" && body.model !== void 0) {
5936
6006
  try {
5937
- const { readGlobalConfig: readGlobalConfig2, writeGlobalConfig: writeGlobalConfig2 } = await import("./setup-5ECWTYKG.js");
6007
+ const { readGlobalConfig: readGlobalConfig2, writeGlobalConfig: writeGlobalConfig2 } = await import("./setup-TCN7VH5U.js");
5938
6008
  const globalConfig = readGlobalConfig2();
5939
6009
  globalConfig.spiritModel = body.model;
5940
6010
  writeGlobalConfig2(globalConfig);
@@ -5977,7 +6047,7 @@ ${user.trimEnd()}
5977
6047
  if (!body.systemPrompt || !body.message) {
5978
6048
  return c.json({ error: "systemPrompt and message required" }, 400);
5979
6049
  }
5980
- const { aiComplete: aiCompleteFn, isAiConfigured } = await import("./ai-service-643TNFPH.js");
6050
+ const { aiComplete: aiCompleteFn, isAiConfigured } = await import("./ai-service-A7FS7EC3.js");
5981
6051
  if (!isAiConfigured()) {
5982
6052
  return c.json({ error: "AI service not configured" }, 503);
5983
6053
  }
@@ -6452,7 +6522,8 @@ var app15 = new Hono18().get("/:name/clock/status", requireSelf(), async (c) =>
6452
6522
  const schedules = readSchedules(dir);
6453
6523
  const now = /* @__PURE__ */ new Date();
6454
6524
  const { upcoming, previous } = computeClockEvents(schedules, sleepState, sleepConfig, now);
6455
- return c.json({ sleep: sleepState, sleepConfig, schedules, upcoming, previous });
6525
+ const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
6526
+ return c.json({ sleep: sleepState, sleepConfig, schedules, upcoming, previous, timezone });
6456
6527
  }).get("/:name/sleep/config", requireSelf(), async (c) => {
6457
6528
  const name = c.req.param("name");
6458
6529
  const entry = await findMind(name);
@@ -6639,8 +6710,8 @@ var app15 = new Hono18().get("/:name/clock/status", requireSelf(), async (c) =>
6639
6710
  const body = await c.req.text();
6640
6711
  const message = `[webhook: ${event}] ${body}`;
6641
6712
  try {
6642
- const { sendSystemMessage } = await import("./system-chat-63ABFF6V.js");
6643
- await sendSystemMessage(name, message);
6713
+ const { sendSystemMessage: sendSystemMessage2 } = await import("./system-chat-M2XOBKEM.js");
6714
+ await sendSystemMessage2(name, message);
6644
6715
  return c.json({ ok: true });
6645
6716
  } catch (err) {
6646
6717
  slog2.warn(`webhook delivery failed for ${name}`, logger_default.errorData(err));
@@ -6693,7 +6764,7 @@ setup.get("/status", async (c) => {
6693
6764
  let hasAccount = false;
6694
6765
  if (hasSystem) {
6695
6766
  try {
6696
- const { listUsersByType: listUsersByType2 } = await import("./auth-A4VGHADI.js");
6767
+ const { listUsersByType: listUsersByType2 } = await import("./auth-ABYLOEZ7.js");
6697
6768
  const brains = await listUsersByType2("brain");
6698
6769
  hasAccount = brains.length > 0;
6699
6770
  } catch (err) {
@@ -6904,7 +6975,7 @@ setup.post("/account", async (c) => {
6904
6975
  }
6905
6976
  }
6906
6977
  try {
6907
- const { createUser: createUser2, updateUserProfile: updateUserProfile2 } = await import("./auth-A4VGHADI.js");
6978
+ const { createUser: createUser2, updateUserProfile: updateUserProfile2 } = await import("./auth-ABYLOEZ7.js");
6908
6979
  const user = await createUser2(body.username.trim(), body.password);
6909
6980
  if (body.displayName?.trim()) {
6910
6981
  await updateUserProfile2(user.id, { display_name: body.displayName.trim() });
@@ -6950,7 +7021,7 @@ setup.post("/models", async (c) => {
6950
7021
  return c.json({ error: "Spirit model is required" }, 400);
6951
7022
  }
6952
7023
  try {
6953
- const { setEnabledModels: setEnabledModels3, setUtilityModel: setUtilityModel2 } = await import("./ai-service-643TNFPH.js");
7024
+ const { setEnabledModels: setEnabledModels3, setUtilityModel: setUtilityModel2 } = await import("./ai-service-A7FS7EC3.js");
6954
7025
  setEnabledModels3(body.models);
6955
7026
  const config2 = readGlobalConfig();
6956
7027
  config2.spiritModel = body.spiritModel.trim();
@@ -6968,15 +7039,15 @@ setup.post("/complete", async (c) => {
6968
7039
  return c.json({ error: "Setup already complete" }, 400);
6969
7040
  }
6970
7041
  try {
6971
- const { ensureSpiritProject, syncSpiritTemplate } = await import("./spirit-WXS3HCLN.js");
6972
- const { startSpiritFull } = await import("./mind-service-2ARGJNKP.js");
7042
+ const { ensureSpiritProject, syncSpiritTemplate } = await import("./spirit-SSJMYC6A.js");
7043
+ const { startSpiritFull } = await import("./mind-service-42GDO4KH.js");
6973
7044
  await ensureSpiritProject();
6974
7045
  await syncSpiritTemplate();
6975
7046
  const warnings = [];
6976
7047
  const config2 = readGlobalConfig();
6977
7048
  if (config2.setup?.service && config2.setup?.type === "local") {
6978
7049
  try {
6979
- const { installUserService } = await import("./service-install-VCRMFWX5.js");
7050
+ const { installUserService } = await import("./service-install-Y46OTF55.js");
6980
7051
  const installed = await installUserService(config2.port, config2.hostname);
6981
7052
  if (!installed) {
6982
7053
  warnings.push("Service installation is not supported on this platform.");
@@ -7005,8 +7076,8 @@ setup.post("/complete", async (c) => {
7005
7076
  }
7006
7077
  let spiritConversationId;
7007
7078
  try {
7008
- const { getOrCreateMindUser: getOrCreateMindUser2, listUsersByType: listUsersByType2 } = await import("./auth-A4VGHADI.js");
7009
- const { createConversation: createConversation2, findDMConversation: findDMConversation2 } = await import("./conversations-C7IKYSHD.js");
7079
+ const { getOrCreateMindUser: getOrCreateMindUser2, listUsersByType: listUsersByType2 } = await import("./auth-ABYLOEZ7.js");
7080
+ const { createConversation: createConversation2, findDMConversation: findDMConversation2 } = await import("./conversations-TUM5PJJ4.js");
7010
7081
  const spiritUser = await getOrCreateMindUser2("volute");
7011
7082
  const brains = await listUsersByType2("brain");
7012
7083
  const admin2 = brains.find((u) => u.role === "admin");
@@ -7028,8 +7099,8 @@ setup.post("/complete", async (c) => {
7028
7099
  logger_default.info("setup complete state", { spiritConversationId, spiritStarted });
7029
7100
  if (spiritConversationId && spiritStarted) {
7030
7101
  try {
7031
- const { deliverMessage: deliverMessage2 } = await import("./message-delivery-GPQ5SE5N.js");
7032
- const { listUsersByType: listUsers2 } = await import("./auth-A4VGHADI.js");
7102
+ const { deliverMessage: deliverMessage2 } = await import("./message-delivery-ABPV76EA.js");
7103
+ const { listUsersByType: listUsers2 } = await import("./auth-ABYLOEZ7.js");
7033
7104
  const admins = await listUsers2("brain");
7034
7105
  const admin2 = admins.find((u) => u.role === "admin");
7035
7106
  if (admin2) {
@@ -7068,7 +7139,7 @@ setup.post("/complete", async (c) => {
7068
7139
  var setup_default = setup;
7069
7140
 
7070
7141
  // packages/daemon/src/web/api/skills.ts
7071
- import { existsSync as existsSync9, mkdtempSync, readdirSync as readdirSync3, rmSync as rmSync5 } from "fs";
7142
+ import { existsSync as existsSync8, mkdtempSync, readdirSync as readdirSync3, rmSync as rmSync4 } from "fs";
7072
7143
  import { tmpdir } from "os";
7073
7144
  import { join, resolve as resolve13 } from "path";
7074
7145
  import AdmZip from "adm-zip";
@@ -7153,12 +7224,12 @@ var app16 = new Hono20().get("/", async (c) => {
7153
7224
  }
7154
7225
  zip.extractAllTo(tmpDir, true);
7155
7226
  let skillDir = null;
7156
- if (existsSync9(join(tmpDir, "SKILL.md"))) {
7227
+ if (existsSync8(join(tmpDir, "SKILL.md"))) {
7157
7228
  skillDir = tmpDir;
7158
7229
  } else {
7159
7230
  const entries = readdirSync3(tmpDir, { withFileTypes: true }).filter((e) => e.isDirectory());
7160
7231
  for (const entry of entries) {
7161
- if (existsSync9(join(tmpDir, entry.name, "SKILL.md"))) {
7232
+ if (existsSync8(join(tmpDir, entry.name, "SKILL.md"))) {
7162
7233
  skillDir = join(tmpDir, entry.name);
7163
7234
  break;
7164
7235
  }
@@ -7175,7 +7246,7 @@ var app16 = new Hono20().get("/", async (c) => {
7175
7246
  }
7176
7247
  throw e;
7177
7248
  } finally {
7178
- rmSync5(tmpDir, { recursive: true, force: true });
7249
+ rmSync4(tmpDir, { recursive: true, force: true });
7179
7250
  }
7180
7251
  }).get("/:id", async (c) => {
7181
7252
  const id = c.req.param("id");
@@ -7472,8 +7543,8 @@ var app20 = new Hono24().use("*", authMiddleware).get("/", async (c) => {
7472
7543
  });
7473
7544
  }, 15e3);
7474
7545
  cleanups.push(() => clearInterval(keepAlive));
7475
- await new Promise((resolve18) => {
7476
- stream.onAbort(() => resolve18());
7546
+ await new Promise((resolve19) => {
7547
+ stream.onAbort(() => resolve19());
7477
7548
  });
7478
7549
  } finally {
7479
7550
  for (const cleanup of cleanups) {
@@ -7489,13 +7560,102 @@ var events_default = app20;
7489
7560
 
7490
7561
  // packages/daemon/src/web/api/variants.ts
7491
7562
  import { existsSync as existsSync10, mkdirSync as mkdirSync9, writeFileSync as writeFileSync9 } from "fs";
7492
- import { resolve as resolve15 } from "path";
7563
+ import { resolve as resolve16 } from "path";
7493
7564
  import { Hono as Hono25 } from "hono";
7494
7565
 
7566
+ // packages/daemon/src/lib/mind/farewell.ts
7567
+ import { existsSync as existsSync9, readFileSync as readFileSync10, rmSync as rmSync5 } from "fs";
7568
+ import { resolve as resolve14 } from "path";
7569
+ var flog = logger_default.child("farewell");
7570
+ var FAREWELL_RELPATH = ".mind/farewell.md";
7571
+ var FAREWELL_MIND_PATH = "../.mind/farewell.md";
7572
+ var FAREWELL_TIMEOUT_MS = 12e4;
7573
+ function farewellPath(variantDir) {
7574
+ return resolve14(variantDir, FAREWELL_RELPATH);
7575
+ }
7576
+ function readFarewell(variantDir) {
7577
+ const path = farewellPath(variantDir);
7578
+ if (!existsSync9(path)) return void 0;
7579
+ try {
7580
+ const text = readFileSync10(path, "utf-8").trim();
7581
+ return text || void 0;
7582
+ } catch (err) {
7583
+ flog.warn(`failed to read farewell note at ${path}`, logger_default.errorData(err));
7584
+ return void 0;
7585
+ }
7586
+ }
7587
+ function waitForFarewell(name, variantDir, timeoutMs) {
7588
+ let settle;
7589
+ const done = new Promise((resolvePromise) => {
7590
+ settle = resolvePromise;
7591
+ });
7592
+ const cleanup = () => {
7593
+ clearTimeout(timeout);
7594
+ unsub();
7595
+ settle();
7596
+ };
7597
+ const timeout = setTimeout(cleanup, timeoutMs);
7598
+ const unsub = subscribe((event) => {
7599
+ if (event.mind !== name) return;
7600
+ if (event.type !== "mind_done" && event.type !== "mind_idle") return;
7601
+ if (existsSync9(farewellPath(variantDir))) cleanup();
7602
+ });
7603
+ return { done, cancel: cleanup };
7604
+ }
7605
+ async function runFarewellTurn(opts) {
7606
+ const { variantName, parentName, variantDir, port, running } = opts;
7607
+ const timeoutMs = opts.timeoutMs ?? FAREWELL_TIMEOUT_MS;
7608
+ if (!running) return readFarewell(variantDir);
7609
+ try {
7610
+ rmSync5(farewellPath(variantDir), { force: true });
7611
+ } catch (err) {
7612
+ flog.warn(`failed to clear stale farewell note for ${variantName}`, logger_default.errorData(err));
7613
+ }
7614
+ const message = await getPrompt("farewell_message", {
7615
+ parent: parentName,
7616
+ path: FAREWELL_MIND_PATH
7617
+ });
7618
+ let conversationId;
7619
+ try {
7620
+ const result = await sendSystemMessageDirect(variantName, message);
7621
+ conversationId = result.conversationId;
7622
+ } catch (err) {
7623
+ flog.error(`failed to persist farewell message for ${variantName}`, logger_default.errorData(err));
7624
+ }
7625
+ const waiter = waitForFarewell(variantName, variantDir, timeoutMs);
7626
+ let delivered = false;
7627
+ try {
7628
+ const res = await fetch(`http://127.0.0.1:${port}/message`, {
7629
+ method: "POST",
7630
+ headers: { "Content-Type": "application/json" },
7631
+ body: JSON.stringify({
7632
+ content: [{ type: "text", text: message }],
7633
+ channel: "@volute",
7634
+ sender: "volute",
7635
+ isDM: true,
7636
+ participants: ["volute", variantName],
7637
+ participantCount: 2,
7638
+ ...conversationId ? { conversationId } : {}
7639
+ }),
7640
+ signal: AbortSignal.timeout(1e4)
7641
+ });
7642
+ if (res.ok) delivered = true;
7643
+ else flog.warn(`farewell delivery to ${variantName} returned HTTP ${res.status}`);
7644
+ } catch (err) {
7645
+ flog.warn(`failed to deliver farewell message to ${variantName}`, logger_default.errorData(err));
7646
+ }
7647
+ if (delivered) {
7648
+ await waiter.done;
7649
+ } else {
7650
+ waiter.cancel();
7651
+ }
7652
+ return readFarewell(variantDir);
7653
+ }
7654
+
7495
7655
  // packages/daemon/src/lib/mind/spawn-server.ts
7496
7656
  import { spawn as spawn4 } from "child_process";
7497
- import { closeSync, mkdirSync as mkdirSync8, openSync, readFileSync as readFileSync10 } from "fs";
7498
- import { resolve as resolve14 } from "path";
7657
+ import { closeSync, mkdirSync as mkdirSync8, openSync, readFileSync as readFileSync11, statSync as statSync3 } from "fs";
7658
+ import { resolve as resolve15 } from "path";
7499
7659
  async function spawnServer(cwd, port, options) {
7500
7660
  let cmd = process.env.VOLUTE_NODE_PATH ?? process.execPath;
7501
7661
  let args = ["--import", "tsx", "src/server.ts", "--port", String(port)];
@@ -7516,31 +7676,36 @@ function spawnAttached(cmd, args, cwd) {
7516
7676
  cwd,
7517
7677
  stdio: ["ignore", "pipe", "pipe"]
7518
7678
  });
7519
- return new Promise((resolve18) => {
7520
- const timeout = setTimeout(() => resolve18(null), 3e4);
7679
+ return new Promise((resolve19) => {
7680
+ const timeout = setTimeout(() => resolve19(null), 3e4);
7521
7681
  function checkOutput(data) {
7522
7682
  const match = data.toString().match(/listening on :(\d+)/);
7523
7683
  if (match) {
7524
7684
  clearTimeout(timeout);
7525
- resolve18({ child, actualPort: parseInt(match[1], 10) });
7685
+ resolve19({ child, actualPort: parseInt(match[1], 10) });
7526
7686
  }
7527
7687
  }
7528
7688
  child.stdout?.on("data", checkOutput);
7529
7689
  child.stderr?.on("data", checkOutput);
7530
7690
  child.on("error", () => {
7531
7691
  clearTimeout(timeout);
7532
- resolve18(null);
7692
+ resolve19(null);
7533
7693
  });
7534
7694
  child.on("exit", () => {
7535
7695
  clearTimeout(timeout);
7536
- resolve18(null);
7696
+ resolve19(null);
7537
7697
  });
7538
7698
  });
7539
7699
  }
7540
7700
  function spawnDetached(cmd, args, cwd, logDir) {
7541
- const logsDir = logDir ?? resolve14(cwd, ".mind", "logs");
7701
+ const logsDir = logDir ?? resolve15(cwd, ".mind", "logs");
7542
7702
  mkdirSync8(logsDir, { recursive: true });
7543
- const logPath = resolve14(logsDir, "mind.log");
7703
+ const logPath = resolve15(logsDir, "mind.log");
7704
+ let startOffset = 0;
7705
+ try {
7706
+ startOffset = statSync3(logPath).size;
7707
+ } catch {
7708
+ }
7544
7709
  const logFd = openSync(logPath, "a");
7545
7710
  const child = spawn4(cmd, args, {
7546
7711
  cwd,
@@ -7560,7 +7725,7 @@ function spawnDetached(cmd, args, cwd, logDir) {
7560
7725
  }
7561
7726
  const interval = setInterval(() => {
7562
7727
  try {
7563
- const content = readFileSync10(logPath, "utf-8");
7728
+ const content = readFileSync11(logPath).subarray(startOffset).toString("utf-8");
7564
7729
  const match = content.match(/listening on :(\d+)/);
7565
7730
  if (match) {
7566
7731
  finish({ child, actualPort: parseInt(match[1], 10) });
@@ -7612,6 +7777,19 @@ async function verify2(port) {
7612
7777
  }
7613
7778
 
7614
7779
  // packages/daemon/src/web/api/variants.ts
7780
+ async function establishVariantDialogue(parent, variant, purpose) {
7781
+ const parentUser = await getOrCreateMindUser(parent);
7782
+ const variantUser = await getOrCreateMindUser(variant);
7783
+ const existing = await findDMConversation([parentUser.id, variantUser.id]);
7784
+ if (!existing) {
7785
+ await createConversation({ participantIds: [parentUser.id, variantUser.id] });
7786
+ }
7787
+ const purposeLine = purpose ? ` Its purpose: ${purpose}.` : "";
7788
+ await sendSystemMessage(
7789
+ parent,
7790
+ `You've split off a variant, ${variant} \u2014 a parallel version of you exploring on its own.${purposeLine} Reach it at @${variant} to check in on how the experiment is going, and merge its work back with \`volute mind join ${variant}\` when you're ready.`
7791
+ );
7792
+ }
7615
7793
  var app21 = new Hono25().get("/:name/variants", async (c) => {
7616
7794
  const name = c.req.param("name");
7617
7795
  const entry = await findMind(name);
@@ -7655,56 +7833,105 @@ var app21 = new Hono25().get("/:name/variants", async (c) => {
7655
7833
  }
7656
7834
  const variantName = body.name;
7657
7835
  if (!variantName) return c.json({ error: "Variant name required" }, 400);
7836
+ const purpose = body.purpose?.trim() || void 0;
7658
7837
  const err = validateBranchName(variantName);
7659
7838
  if (err) return c.json({ error: err }, 400);
7660
7839
  if (await findMind(variantName)) {
7661
7840
  return c.json({ error: `Name already in use: ${variantName}` }, 409);
7662
7841
  }
7663
7842
  const projectRoot = entry.dir ?? mindDir(mindName);
7664
- const variantDir = resolve15(projectRoot, ".variants", variantName);
7843
+ const variantDir = resolve16(projectRoot, ".variants", variantName);
7665
7844
  if (existsSync10(variantDir)) {
7666
7845
  return c.json({ error: `Variant directory already exists: ${variantDir}` }, 409);
7667
7846
  }
7668
- mkdirSync9(resolve15(projectRoot, ".variants"), { recursive: true });
7847
+ mkdirSync9(resolve16(projectRoot, ".variants"), { recursive: true });
7669
7848
  try {
7670
7849
  await gitExec(["worktree", "add", "-b", variantName, variantDir], { cwd: projectRoot });
7671
7850
  } catch (e) {
7672
7851
  const msg = e instanceof Error ? e.message : String(e);
7673
- return c.json({ error: `Failed to create worktree: ${msg}` }, 500);
7852
+ let error = `Failed to create worktree: ${msg}`;
7853
+ try {
7854
+ await chownMindDir(projectRoot, mindName);
7855
+ } catch (err2) {
7856
+ logger_default.warn(
7857
+ `failed to restore ownership after worktree-add failure for ${mindName}`,
7858
+ logger_default.errorData(err2)
7859
+ );
7860
+ error += ` Restoring mind ownership also failed: ${err2 instanceof Error ? err2.message : String(err2)}`;
7861
+ }
7862
+ return c.json({ error }, 500);
7863
+ }
7864
+ const rollbackSplit = async () => {
7865
+ try {
7866
+ await cleanupVariant(variantName, projectRoot, variantDir, { stop: true });
7867
+ } catch (err2) {
7868
+ logger_default.warn(`failed to roll back split of ${variantName}`, logger_default.errorData(err2));
7869
+ }
7870
+ try {
7871
+ await chownMindDir(projectRoot, mindName);
7872
+ } catch (err2) {
7873
+ logger_default.warn(
7874
+ `failed to restore ${mindName} ownership after split rollback`,
7875
+ logger_default.errorData(err2)
7876
+ );
7877
+ }
7878
+ };
7879
+ try {
7880
+ await chownMindDir(projectRoot, mindName);
7881
+ } catch (e) {
7882
+ await rollbackSplit();
7883
+ const msg = e instanceof Error ? e.message : String(e);
7884
+ return c.json({ error: `Failed to prepare variant ownership: ${msg}` }, 500);
7674
7885
  }
7675
- await chownMindDir(projectRoot, mindName);
7676
7886
  try {
7677
7887
  if (isIsolationEnabled()) {
7678
7888
  const [cmd, args] = await wrapForIsolation("npm", ["install"], mindName);
7679
7889
  await exec(cmd, args, {
7680
7890
  cwd: variantDir,
7681
- env: { ...process.env, HOME: resolve15(variantDir, "home") }
7891
+ env: { ...process.env, HOME: resolve16(variantDir, "home") }
7682
7892
  });
7683
7893
  } else {
7684
7894
  await exec("npm", ["install"], { cwd: variantDir });
7685
7895
  }
7686
7896
  } catch (e) {
7897
+ await rollbackSplit();
7687
7898
  const msg = e instanceof Error ? e.message : String(e);
7688
7899
  return c.json({ error: `npm install failed: ${msg}` }, 500);
7689
7900
  }
7690
7901
  if (body.soul) {
7691
- writeFileSync9(resolve15(variantDir, "home/SOUL.md"), body.soul);
7902
+ writeFileSync9(resolve16(variantDir, "home/SOUL.md"), body.soul);
7692
7903
  }
7693
7904
  const variantPort = body.port ?? await nextPort();
7694
- await addVariant(variantName, mindName, variantPort, variantDir, variantName);
7905
+ try {
7906
+ await addVariant(variantName, mindName, variantPort, variantDir, variantName, purpose);
7907
+ } catch (e) {
7908
+ await rollbackSplit();
7909
+ const msg = e instanceof Error ? e.message : String(e);
7910
+ return c.json({ error: `Failed to register variant: ${msg}` }, 500);
7911
+ }
7912
+ const manager = getMindManager();
7913
+ manager.setPendingContext(variantName, { type: "split", parent: mindName, purpose });
7695
7914
  if (!body.noStart) {
7696
7915
  try {
7697
- const manager = getMindManager();
7698
- manager.setPendingContext(variantName, { type: "split", parent: mindName });
7699
7916
  await manager.startMind(variantName);
7700
7917
  } catch (e) {
7918
+ await rollbackSplit();
7701
7919
  const msg = e instanceof Error ? e.message : String(e);
7702
7920
  return c.json({ error: `Variant created but failed to start: ${msg}` }, 500);
7703
7921
  }
7704
7922
  }
7923
+ await establishVariantDialogue(mindName, variantName, purpose).catch(
7924
+ (e) => logger_default.error(`failed to establish variant dialogue for ${variantName}`, logger_default.errorData(e))
7925
+ );
7705
7926
  return c.json({
7706
7927
  ok: true,
7707
- variant: { name: variantName, branch: variantName, path: variantDir, port: variantPort }
7928
+ variant: {
7929
+ name: variantName,
7930
+ branch: variantName,
7931
+ path: variantDir,
7932
+ port: variantPort,
7933
+ ...purpose && { purpose }
7934
+ }
7708
7935
  });
7709
7936
  }).post("/:name/variants/:variant/merge", requireSelf(), async (c) => {
7710
7937
  const mindName = c.req.param("name");
@@ -7725,146 +7952,150 @@ var app21 = new Hono25().get("/:name/variants", async (c) => {
7725
7952
  } catch {
7726
7953
  }
7727
7954
  const projectRoot = parentEntry.dir ?? mindDir(mindName);
7728
- if (existsSync10(variantEntry.dir)) {
7729
- const status = (await gitExec(["status", "--porcelain"], { cwd: variantEntry.dir })).trim();
7730
- if (status) {
7731
- try {
7732
- await gitExec(["add", "-A"], { cwd: variantEntry.dir });
7733
- await gitExec(["commit", "-m", "Auto-commit uncommitted changes before merge"], {
7734
- cwd: variantEntry.dir
7735
- });
7736
- } catch (_e) {
7737
- return c.json(
7738
- {
7739
- error: "Failed to auto-commit variant changes. Commit or stash manually before merging."
7740
- },
7741
- 500
7742
- );
7743
- }
7744
- }
7745
- }
7746
- if (!body.skipVerify) {
7747
- const result = await spawnServer(variantEntry.dir, 0, {
7748
- detached: true,
7749
- mindName: variantName,
7750
- template: variantEntry.template
7751
- });
7752
- if (!result) {
7753
- return c.json(
7754
- { error: "Failed to start server for verification. Use skipVerify to skip." },
7755
- 500
7756
- );
7757
- }
7758
- const verified = await verify2(result.actualPort);
7759
- try {
7760
- process.kill(-result.child.pid, "SIGTERM");
7761
- } catch {
7762
- }
7763
- if (!verified) {
7764
- return c.json(
7765
- { error: "Verification failed. Fix issues or use skipVerify to proceed." },
7766
- 500
7767
- );
7768
- }
7769
- }
7770
- const mainStatus = (await gitExec(["status", "--porcelain"], { cwd: projectRoot })).trim();
7771
- if (mainStatus) {
7772
- try {
7773
- await gitExec(["add", "-A"], { cwd: projectRoot });
7774
- await gitExec(["commit", "-m", "Auto-commit uncommitted changes before merge"], {
7775
- cwd: projectRoot
7776
- });
7777
- } catch (_e) {
7778
- return c.json(
7779
- { error: "Failed to auto-commit main changes. Commit or stash manually before merging." },
7780
- 500
7781
- );
7782
- }
7783
- }
7784
- try {
7785
- await gitExec(["merge", variantEntry.branch], { cwd: projectRoot });
7786
- } catch (_e) {
7787
- let conflicts = [];
7788
- try {
7789
- const out = (await gitExec(["diff", "--name-only", "--diff-filter=U"], { cwd: projectRoot })).trim();
7790
- conflicts = out ? out.split("\n") : [];
7791
- } catch (err) {
7792
- logger_default.warn(`failed to list merge conflicts for ${mindName}`, logger_default.errorData(err));
7793
- }
7794
- try {
7795
- await gitExec(["merge", "--abort"], { cwd: projectRoot });
7796
- } catch (err) {
7797
- logger_default.warn(`failed to abort merge for ${mindName}`, logger_default.errorData(err));
7798
- }
7955
+ const failAfterGitWrite = async (message, extra) => {
7799
7956
  try {
7800
7957
  await chownMindDir(projectRoot, mindName);
7801
7958
  } catch (err) {
7802
7959
  logger_default.warn(
7803
- `failed to restore ownership after aborted merge for ${mindName}`,
7960
+ `failed to restore ownership for ${mindName} after merge failure`,
7804
7961
  logger_default.errorData(err)
7805
7962
  );
7806
7963
  return c.json(
7807
7964
  {
7808
- error: `Merge failed and restoring parent ownership failed: ${err instanceof Error ? err.message : String(err)}`,
7809
- conflicts
7965
+ error: `${message} Restoring mind ownership also failed: ${err instanceof Error ? err.message : String(err)}`,
7966
+ ...extra
7810
7967
  },
7811
7968
  500
7812
7969
  );
7813
7970
  }
7814
- return c.json(
7815
- {
7816
- error: "Merge failed. Resolve conflicts in the variant and retry the join.",
7817
- conflicts
7818
- },
7819
- 500
7820
- );
7971
+ return c.json({ error: message, ...extra }, 500);
7972
+ };
7973
+ let farewell;
7974
+ try {
7975
+ farewell = await runFarewellTurn({
7976
+ variantName,
7977
+ parentName: mindName,
7978
+ variantDir: variantEntry.dir,
7979
+ port: variantEntry.port,
7980
+ running: tryGetMindManager()?.isRunning(variantName) ?? false
7981
+ });
7982
+ } catch (err) {
7983
+ logger_default.warn(`farewell turn failed for ${variantName}`, logger_default.errorData(err));
7821
7984
  }
7822
- await cleanupVariant(variantName, projectRoot, variantEntry.dir, { stop: true });
7823
- await chownMindDir(projectRoot, mindName);
7824
- if (variantName.endsWith("-upgrade") || variantName === "upgrade") {
7985
+ try {
7986
+ if (existsSync10(variantEntry.dir)) {
7987
+ const status = (await gitExec(["status", "--porcelain"], { cwd: variantEntry.dir })).trim();
7988
+ if (status) {
7989
+ try {
7990
+ await gitExec(["add", "-A"], { cwd: variantEntry.dir });
7991
+ await gitExec(["commit", "-m", "Auto-commit uncommitted changes before merge"], {
7992
+ cwd: variantEntry.dir
7993
+ });
7994
+ } catch (_e) {
7995
+ return failAfterGitWrite(
7996
+ "Failed to auto-commit variant changes. Commit or stash manually before merging."
7997
+ );
7998
+ }
7999
+ }
8000
+ }
8001
+ if (!body.skipVerify) {
8002
+ const result = await spawnServer(variantEntry.dir, 0, {
8003
+ detached: true,
8004
+ mindName: variantName,
8005
+ template: variantEntry.template
8006
+ });
8007
+ if (!result) {
8008
+ return failAfterGitWrite(
8009
+ "Failed to start server for verification. Use skipVerify to skip."
8010
+ );
8011
+ }
8012
+ const verified = await verify2(result.actualPort);
8013
+ try {
8014
+ process.kill(-result.child.pid, "SIGTERM");
8015
+ } catch {
8016
+ }
8017
+ if (!verified) {
8018
+ return failAfterGitWrite("Verification failed. Fix issues or use skipVerify to proceed.");
8019
+ }
8020
+ }
8021
+ const mainStatus = (await gitExec(["status", "--porcelain"], { cwd: projectRoot })).trim();
8022
+ if (mainStatus) {
8023
+ try {
8024
+ await gitExec(["add", "-A"], { cwd: projectRoot });
8025
+ await gitExec(["commit", "-m", "Auto-commit uncommitted changes before merge"], {
8026
+ cwd: projectRoot
8027
+ });
8028
+ } catch (_e) {
8029
+ return failAfterGitWrite(
8030
+ "Failed to auto-commit main changes. Commit or stash manually before merging."
8031
+ );
8032
+ }
8033
+ }
8034
+ let memoryDelta = "";
7825
8035
  try {
7826
- const { computeTemplateHash: computeTemplateHash2 } = await import("./template-hash-GSOUARW4.js");
7827
- const { setMindTemplateHash: setMindTemplateHash2 } = await import("./registry-UMMDRPRM.js");
7828
- const tmpl = parentEntry.template ?? "claude";
7829
- await setMindTemplateHash2(mindName, computeTemplateHash2(tmpl));
8036
+ memoryDelta = await mergeVariantExcludingMemory(projectRoot, variantEntry.branch);
7830
8037
  } catch (err) {
7831
- logger_default.warn(`failed to update template hash for ${mindName}`, logger_default.errorData(err));
8038
+ if (!(err instanceof VariantMergeError)) throw err;
8039
+ return failAfterGitWrite(
8040
+ err.abortFailed ? "Merge failed and the abort did not complete \u2014 the parent worktree may be left mid-merge with conflict markers; manual cleanup may be needed." : "Merge failed. Resolve conflicts in the variant and retry the join.",
8041
+ { conflicts: err.conflicts }
8042
+ );
7832
8043
  }
7833
- }
7834
- let restartWarning;
7835
- try {
7836
- if (isIsolationEnabled()) {
7837
- const [cmd, args] = await wrapForIsolation("npm", ["install"], mindName);
7838
- await exec(cmd, args, {
7839
- cwd: projectRoot,
7840
- env: { ...process.env, HOME: resolve15(projectRoot, "home") }
7841
- });
7842
- } else {
7843
- await exec("npm", ["install"], { cwd: projectRoot });
8044
+ await cleanupVariant(variantName, projectRoot, variantEntry.dir, { stop: true });
8045
+ await chownMindDir(projectRoot, mindName);
8046
+ if (variantName.endsWith("-upgrade") || variantName === "upgrade") {
8047
+ try {
8048
+ const { computeTemplateHash: computeTemplateHash2 } = await import("./template-hash-GSOUARW4.js");
8049
+ const { setMindTemplateHash: setMindTemplateHash2 } = await import("./registry-3ELJPUGJ.js");
8050
+ const tmpl = parentEntry.template ?? "claude";
8051
+ await setMindTemplateHash2(mindName, computeTemplateHash2(tmpl));
8052
+ } catch (err) {
8053
+ logger_default.warn(`failed to update template hash for ${mindName}`, logger_default.errorData(err));
8054
+ }
7844
8055
  }
7845
- } catch (err) {
7846
- logger_default.warn(`npm install failed after merge for ${mindName}`, logger_default.errorData(err));
7847
- restartWarning = `npm install failed after merge \u2014 mind may have stale dependencies`;
7848
- }
7849
- const manager = getMindManager();
7850
- const context = {
7851
- type: "merged",
7852
- name: variantName,
7853
- ...body.summary && { summary: body.summary },
7854
- ...body.justification && { justification: body.justification },
7855
- ...body.memory && { memory: body.memory }
7856
- };
7857
- try {
7858
- if (manager.isRunning(mindName)) {
7859
- await manager.stopMind(mindName);
8056
+ let restartWarning;
8057
+ try {
8058
+ if (isIsolationEnabled()) {
8059
+ const [cmd, args] = await wrapForIsolation("npm", ["install"], mindName);
8060
+ await exec(cmd, args, {
8061
+ cwd: projectRoot,
8062
+ env: { ...process.env, HOME: resolve16(projectRoot, "home") }
8063
+ });
8064
+ } else {
8065
+ await exec("npm", ["install"], { cwd: projectRoot });
8066
+ }
8067
+ } catch (err) {
8068
+ logger_default.warn(`npm install failed after merge for ${mindName}`, logger_default.errorData(err));
8069
+ restartWarning = `npm install failed after merge \u2014 mind may have stale dependencies`;
8070
+ }
8071
+ const manager = getMindManager();
8072
+ const justification = body.justification?.trim() || variantEntry.purpose;
8073
+ const context = {
8074
+ type: "merged",
8075
+ name: variantName,
8076
+ ...body.summary && { summary: body.summary },
8077
+ ...justification && { justification },
8078
+ ...body.memory && { memory: body.memory },
8079
+ ...memoryDelta && { memoryDelta },
8080
+ ...farewell && { farewell }
8081
+ };
8082
+ try {
8083
+ if (manager.isRunning(mindName)) {
8084
+ await manager.stopMind(mindName);
8085
+ }
8086
+ manager.setPendingContext(mindName, context);
8087
+ await manager.startMind(mindName);
8088
+ } catch (e) {
8089
+ restartWarning = `Merge succeeded but mind restart failed: ${e instanceof Error ? e.message : String(e)}`;
8090
+ logger_default.warn(restartWarning);
7860
8091
  }
7861
- manager.setPendingContext(mindName, context);
7862
- await manager.startMind(mindName);
7863
- } catch (e) {
7864
- restartWarning = `Merge succeeded but mind restart failed: ${e instanceof Error ? e.message : String(e)}`;
7865
- logger_default.warn(restartWarning);
8092
+ return c.json({ ok: true, ...restartWarning && { warning: restartWarning } });
8093
+ } catch (err) {
8094
+ logger_default.error(`variant join failed for ${mindName}`, logger_default.errorData(err));
8095
+ return await failAfterGitWrite(
8096
+ `Variant join failed: ${err instanceof Error ? err.message : String(err)}`
8097
+ );
7866
8098
  }
7867
- return c.json({ ok: true, ...restartWarning && { warning: restartWarning } });
7868
8099
  }).delete("/:name/variants/:variant", requireSelf(), async (c) => {
7869
8100
  const mindName = c.req.param("name");
7870
8101
  const variantName = c.req.param("variant");
@@ -8285,11 +8516,11 @@ var app23 = new Hono27().get("/:name/conversations/:id/events", async (c) => {
8285
8516
  if (!stream.aborted) console.error("[chat] SSE ping error:", err);
8286
8517
  });
8287
8518
  }, 15e3);
8288
- await new Promise((resolve18) => {
8519
+ await new Promise((resolve19) => {
8289
8520
  stream.onAbort(() => {
8290
8521
  unsubscribe();
8291
8522
  clearInterval(keepAlive);
8292
- resolve18();
8523
+ resolve19();
8293
8524
  });
8294
8525
  });
8295
8526
  });
@@ -8506,7 +8737,7 @@ var app_default = app25;
8506
8737
  import { existsSync as existsSync11 } from "fs";
8507
8738
  import { readFile as readFile2, stat as stat2 } from "fs/promises";
8508
8739
  import { createServer as createHttpsServer } from "https";
8509
- import { dirname as dirname2, extname as extname3, resolve as resolve16 } from "path";
8740
+ import { dirname as dirname2, extname as extname3, resolve as resolve17 } from "path";
8510
8741
  import { serve } from "@hono/node-server";
8511
8742
  var MIME_TYPES2 = {
8512
8743
  ".html": "text/html",
@@ -8525,7 +8756,7 @@ async function startServer({
8525
8756
  let assetsDir = "";
8526
8757
  let searchDir = dirname2(new URL(import.meta.url).pathname);
8527
8758
  for (let i = 0; i < 5; i++) {
8528
- const candidate = resolve16(searchDir, "dist", "web-assets");
8759
+ const candidate = resolve17(searchDir, "dist", "web-assets");
8529
8760
  if (existsSync11(candidate)) {
8530
8761
  assetsDir = candidate;
8531
8762
  break;
@@ -8536,7 +8767,7 @@ async function startServer({
8536
8767
  app_default.get("*", async (c) => {
8537
8768
  const urlPath = new URL(c.req.url).pathname;
8538
8769
  if (urlPath.startsWith("/api/") || urlPath.startsWith("/ext/")) return c.notFound();
8539
- const filePath = resolve16(assetsDir, urlPath.slice(1));
8770
+ const filePath = resolve17(assetsDir, urlPath.slice(1));
8540
8771
  if (!filePath.startsWith(assetsDir)) return c.text("Forbidden", 403);
8541
8772
  const s = await stat2(filePath).catch(() => null);
8542
8773
  if (s?.isFile()) {
@@ -8549,7 +8780,7 @@ async function startServer({
8549
8780
  const cacheControl = isHashed ? "public, max-age=31536000, immutable" : "no-cache";
8550
8781
  return c.body(body, 200, { "Content-Type": mime, "Cache-Control": cacheControl });
8551
8782
  }
8552
- const indexPath = resolve16(assetsDir, "index.html");
8783
+ const indexPath = resolve17(assetsDir, "index.html");
8553
8784
  const indexStat = await stat2(indexPath).catch(() => null);
8554
8785
  if (indexStat?.isFile()) {
8555
8786
  const body = await readFile2(indexPath, "utf-8");
@@ -8566,10 +8797,10 @@ async function startServer({
8566
8797
  createServer: createHttpsServer,
8567
8798
  serverOptions: { key: tls.key, cert: tls.cert }
8568
8799
  });
8569
- await new Promise((resolve18, reject) => {
8800
+ await new Promise((resolve19, reject) => {
8570
8801
  server2.on("listening", () => {
8571
8802
  logger_default.info("Volute UI running (https)", { hostname, port });
8572
- resolve18();
8803
+ resolve19();
8573
8804
  });
8574
8805
  server2.on("error", (err) => {
8575
8806
  reject(err);
@@ -8577,13 +8808,13 @@ async function startServer({
8577
8808
  });
8578
8809
  const internalPort = port + 1;
8579
8810
  const internalServer = serve({ fetch: app_default.fetch, port: internalPort, hostname: "127.0.0.1" });
8580
- await new Promise((resolve18, reject) => {
8811
+ await new Promise((resolve19, reject) => {
8581
8812
  internalServer.on("listening", () => {
8582
8813
  logger_default.info("Volute API running (http, internal)", {
8583
8814
  hostname: "127.0.0.1",
8584
8815
  port: internalPort
8585
8816
  });
8586
- resolve18();
8817
+ resolve19();
8587
8818
  });
8588
8819
  internalServer.on("error", (err) => {
8589
8820
  reject(err);
@@ -8592,10 +8823,10 @@ async function startServer({
8592
8823
  return { server: server2, internalPort };
8593
8824
  }
8594
8825
  const server = serve({ fetch: app_default.fetch, port, hostname });
8595
- await new Promise((resolve18, reject) => {
8826
+ await new Promise((resolve19, reject) => {
8596
8827
  server.on("listening", () => {
8597
8828
  logger_default.info("Volute API running (http)", { hostname, port });
8598
- resolve18();
8829
+ resolve19();
8599
8830
  });
8600
8831
  server.on("error", (err) => {
8601
8832
  reject(err);
@@ -8606,17 +8837,17 @@ async function startServer({
8606
8837
 
8607
8838
  // packages/daemon/src/daemon.ts
8608
8839
  if (!process.env.VOLUTE_HOME) {
8609
- process.env.VOLUTE_HOME = resolve17(homedir3(), ".volute");
8840
+ process.env.VOLUTE_HOME = resolve18(homedir3(), ".volute");
8610
8841
  }
8611
8842
  if (process.env.VOLUTE_TIMEZONE && !process.env.TZ) {
8612
8843
  process.env.TZ = process.env.VOLUTE_TIMEZONE;
8613
8844
  }
8614
8845
  function writeDaemonConfig(systemDir, config2, token) {
8615
- const configPath = resolve17(systemDir, "daemon.json");
8846
+ const configPath = resolve18(systemDir, "daemon.json");
8616
8847
  writeFileSync10(configPath, `${JSON.stringify(config2, null, 2)}
8617
8848
  `, { mode: 420 });
8618
8849
  chmodSync(configPath, 420);
8619
- const tokenPath = resolve17(systemDir, "daemon-token");
8850
+ const tokenPath = resolve18(systemDir, "daemon-token");
8620
8851
  writeFileSync10(tokenPath, `${token}
8621
8852
  `, { mode: 384 });
8622
8853
  chmodSync(tokenPath, 384);
@@ -8627,7 +8858,7 @@ async function startDaemon(opts) {
8627
8858
  const home = voluteHome();
8628
8859
  const systemDir = voluteSystemDir();
8629
8860
  if (!opts.foreground) {
8630
- const rotatingLog = new RotatingLog(resolve17(systemDir, "daemon.log"));
8861
+ const rotatingLog = new RotatingLog(resolve18(systemDir, "daemon.log"));
8631
8862
  logger_default.setOutput((line) => rotatingLog.write(`${line}
8632
8863
  `));
8633
8864
  const write = (...args) => rotatingLog.write(`${format(...args)}
@@ -8637,32 +8868,32 @@ async function startDaemon(opts) {
8637
8868
  console.warn = write;
8638
8869
  console.info = write;
8639
8870
  }
8640
- const DAEMON_PID_PATH = resolve17(systemDir, "daemon.pid");
8641
- const DAEMON_JSON_PATH = resolve17(systemDir, "daemon.json");
8642
- const DAEMON_TOKEN_PATH = resolve17(systemDir, "daemon-token");
8871
+ const DAEMON_PID_PATH = resolve18(systemDir, "daemon.pid");
8872
+ const DAEMON_JSON_PATH = resolve18(systemDir, "daemon.json");
8873
+ const DAEMON_TOKEN_PATH = resolve18(systemDir, "daemon-token");
8643
8874
  mkdirSync10(home, { recursive: true });
8644
8875
  ensureSystemDir();
8645
- const { migrateConfigSecrets, migrateSetupCompleted } = await import("./setup-5ECWTYKG.js");
8876
+ const { migrateConfigSecrets, migrateSetupCompleted } = await import("./setup-TCN7VH5U.js");
8646
8877
  migrateConfigSecrets();
8647
8878
  migrateSetupCompleted();
8648
- const { migrateAiModelQualification } = await import("./ai-service-643TNFPH.js");
8879
+ const { migrateAiModelQualification } = await import("./ai-service-A7FS7EC3.js");
8649
8880
  migrateAiModelQualification();
8650
- await (await import("./db-FO6LXYCW.js")).getDb();
8881
+ await (await import("./db-F6UMFRYD.js")).getDb();
8651
8882
  try {
8652
8883
  const { eq: eq8, and: and6 } = await import("drizzle-orm");
8653
- const { users: users2 } = await import("./schema-VEVO7HG6.js");
8654
- const db = await (await import("./db-FO6LXYCW.js")).getDb();
8884
+ const { users: users2 } = await import("./schema-6YONVTKZ.js");
8885
+ const db = await (await import("./db-F6UMFRYD.js")).getDb();
8655
8886
  await db.update(users2).set({ role: "system" }).where(and6(eq8(users2.user_type, "system"), eq8(users2.role, "user")));
8656
8887
  } catch (err) {
8657
8888
  logger_default.warn("failed to migrate system user role", logger_default.errorData(err));
8658
8889
  }
8659
8890
  try {
8660
- const { migrateAvatarSizes } = await import("./avatar-image-Y45R7LW5.js");
8891
+ const { migrateAvatarSizes } = await import("./avatar-image-6ATV2IOZ.js");
8661
8892
  await migrateAvatarSizes();
8662
8893
  } catch (err) {
8663
8894
  logger_default.warn("avatar size migration failed", logger_default.errorData(err));
8664
8895
  }
8665
- const { initSandbox } = await import("./sandbox-5VQ454XP.js");
8896
+ const { initSandbox } = await import("./sandbox-XPYA7UIF.js");
8666
8897
  await initSandbox();
8667
8898
  try {
8668
8899
  await syncBuiltinSkills();
@@ -8694,7 +8925,7 @@ async function startDaemon(opts) {
8694
8925
  logger_default.warn("failed to backfill minds into #system", logger_default.errorData(err));
8695
8926
  }
8696
8927
  try {
8697
- const { getOrCreateSystemUser: getOrCreateSystemUser2 } = await import("./auth-A4VGHADI.js");
8928
+ const { getOrCreateSystemUser: getOrCreateSystemUser2 } = await import("./auth-ABYLOEZ7.js");
8698
8929
  await getOrCreateSystemUser2();
8699
8930
  } catch (err) {
8700
8931
  logger_default.warn(
@@ -8703,12 +8934,12 @@ async function startDaemon(opts) {
8703
8934
  );
8704
8935
  }
8705
8936
  const token = process.env.VOLUTE_DAEMON_TOKEN || randomBytes3(32).toString("hex");
8706
- const { readGlobalConfig: readGlobalConfig2 } = await import("./setup-5ECWTYKG.js");
8937
+ const { readGlobalConfig: readGlobalConfig2 } = await import("./setup-TCN7VH5U.js");
8707
8938
  const globalCfg = readGlobalConfig2();
8708
8939
  let tls;
8709
8940
  if (opts.tailscale || globalCfg.tailscale) {
8710
8941
  try {
8711
- const { getTailscaleTls } = await import("./tailscale-MCLY5GYE.js");
8942
+ const { getTailscaleTls } = await import("./tailscale-AMMVKDXB.js");
8712
8943
  const tlsConfig = await getTailscaleTls();
8713
8944
  tls = { key: tlsConfig.key, cert: tlsConfig.cert };
8714
8945
  logger_default.info("Tailscale HTTPS enabled", { hostname: tlsConfig.hostname });
@@ -8759,6 +8990,12 @@ async function startDaemon(opts) {
8759
8990
  const unsubscribeWebhook = initWebhook();
8760
8991
  const orphanedTurns = await completeOrphanedTurns();
8761
8992
  summarizeOrphanedTurns(orphanedTurns);
8993
+ try {
8994
+ const { reconcileVariants } = await import("./variant-cleanup-P4MBNLJD.js");
8995
+ await reconcileVariants();
8996
+ } catch (err) {
8997
+ logger_default.error("failed to reconcile variants", logger_default.errorData(err));
8998
+ }
8762
8999
  const allMinds = await readAllMinds();
8763
9000
  const runningEntries = allMinds.filter((e) => e.running && e.mindType !== "spirit");
8764
9001
  {
@@ -8788,14 +9025,14 @@ async function startDaemon(opts) {
8788
9025
  await Promise.all(workers);
8789
9026
  }
8790
9027
  try {
8791
- const { isSetupComplete: isSetupComplete2 } = await import("./setup-5ECWTYKG.js");
9028
+ const { isSetupComplete: isSetupComplete2 } = await import("./setup-TCN7VH5U.js");
8792
9029
  if (isSetupComplete2()) {
8793
- const { ensureSpiritProject, syncSpiritTemplate } = await import("./spirit-WXS3HCLN.js");
8794
- const { startSpiritFull } = await import("./mind-service-2ARGJNKP.js");
9030
+ const { ensureSpiritProject, syncSpiritTemplate } = await import("./spirit-SSJMYC6A.js");
9031
+ const { startSpiritFull } = await import("./mind-service-42GDO4KH.js");
8795
9032
  await ensureSpiritProject();
8796
9033
  const spiritEntry = await findMind(SPIRIT_NAME);
8797
9034
  if (spiritEntry?.dir) {
8798
- const { registerMindDir } = await import("./delivery-router-3QFG2ZEX.js");
9035
+ const { registerMindDir } = await import("./delivery-router-VUOL5YSA.js");
8799
9036
  registerMindDir(SPIRIT_NAME, spiritEntry.dir);
8800
9037
  }
8801
9038
  await syncSpiritTemplate();
@@ -8812,7 +9049,7 @@ async function startDaemon(opts) {
8812
9049
  bridgeManager.startBridges(daemonPort).catch((err) => {
8813
9050
  logger_default.warn("failed to start bridges", logger_default.errorData(err));
8814
9051
  });
8815
- import("./cloud-sync-WHKCB4CD.js").then(
9052
+ import("./cloud-sync-VFDTNNCA.js").then(
8816
9053
  ({ consumeQueuedMessages }) => consumeQueuedMessages().catch((err) => {
8817
9054
  logger_default.warn("failed to consume queued cloud messages", logger_default.errorData(err));
8818
9055
  })
@@ -8820,7 +9057,7 @@ async function startDaemon(opts) {
8820
9057
  logger_default.warn("failed to load cloud-sync module", logger_default.errorData(err));
8821
9058
  });
8822
9059
  try {
8823
- const { backfillTemplateHashes, notifyVersionUpdate, warnStaleTemplates } = await import("./version-notify-73YURTFR.js");
9060
+ const { backfillTemplateHashes, notifyVersionUpdate, warnStaleTemplates } = await import("./version-notify-WFA5XB5A.js");
8824
9061
  backfillTemplateHashes().then(() => warnStaleTemplates()).catch((err) => {
8825
9062
  logger_default.warn("failed to check template staleness", logger_default.errorData(err));
8826
9063
  });
@@ -8850,13 +9087,13 @@ async function startDaemon(opts) {
8850
9087
  logger_default.info(`running on ${hostname}:${port}, pid ${myPid}`);
8851
9088
  function cleanup() {
8852
9089
  try {
8853
- if (readFileSync11(DAEMON_PID_PATH, "utf-8").trim() === myPid) {
9090
+ if (readFileSync12(DAEMON_PID_PATH, "utf-8").trim() === myPid) {
8854
9091
  unlinkSync2(DAEMON_PID_PATH);
8855
9092
  }
8856
9093
  } catch {
8857
9094
  }
8858
9095
  try {
8859
- if (readFileSync11(DAEMON_TOKEN_PATH, "utf-8").trim() === token) {
9096
+ if (readFileSync12(DAEMON_TOKEN_PATH, "utf-8").trim() === token) {
8860
9097
  unlinkSync2(DAEMON_TOKEN_PATH);
8861
9098
  try {
8862
9099
  unlinkSync2(DAEMON_JSON_PATH);