volute 0.47.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 (177) hide show
  1. package/README.md +82 -331
  2. package/dist/{accept-YAFP5JFJ.js → accept-KYNC5X3U.js} +3 -3
  3. package/dist/{activity-events-MIOK3IOU.js → activity-events-GJJAI2WT.js} +4 -4
  4. package/dist/{ai-service-CVZEYKTK.js → ai-service-A7FS7EC3.js} +9 -5
  5. package/dist/{archive-6Z4GFQPH.js → archive-7E3YE6VF.js} +3 -3
  6. package/dist/{auth-A4VGHADI.js → auth-ABYLOEZ7.js} +5 -5
  7. package/dist/{avatar-image-7S3WA77A.js → avatar-image-6ATV2IOZ.js} +6 -5
  8. package/dist/{backup-IIAFOW2L.js → backup-XYAMT4CU.js} +15 -15
  9. package/dist/{bridge-TIZGSXOD.js → bridge-3FNSWLNQ.js} +3 -3
  10. package/dist/{channels-ITE4YC57.js → channels-SGQZHNQJ.js} +3 -3
  11. package/dist/{chat-AJLJCQ3M.js → chat-OGXAEXMV.js} +9 -9
  12. package/dist/{chunk-LTLFOSA4.js → chunk-3B427GUK.js} +3 -3
  13. package/dist/{chunk-RUFLGTOT.js → chunk-4B62E4CG.js} +2 -2
  14. package/dist/{chunk-ODDGGLM6.js → chunk-4DSGN4FL.js} +3 -3
  15. package/dist/{chunk-BGYHLR54.js → chunk-6HGEZJDH.js} +2 -2
  16. package/dist/{chunk-RBNMDAKB.js → chunk-BCG45AQB.js} +15 -1
  17. package/dist/{chunk-5IERLDUS.js → chunk-DAIIMBLB.js} +26 -6
  18. package/dist/{chunk-DGGZVZTM.js → chunk-DVN6DNMI.js} +1 -1
  19. package/dist/chunk-DW4TRDOZ.js +29 -0
  20. package/dist/chunk-EADX54RG.js +113 -0
  21. package/dist/{chunk-EIQDZC43.js → chunk-EFT2OALG.js} +6 -2
  22. package/dist/{chunk-XHN6XBWV.js → chunk-EG5LSKHU.js} +2 -2
  23. package/dist/{chunk-VMCZBXF2.js → chunk-FBBS4BVR.js} +10 -34
  24. package/dist/{chunk-IWJSNMSY.js → chunk-GWVFDUNB.js} +45 -14
  25. package/dist/{chunk-CBOIHNMR.js → chunk-HCYQRME2.js} +41 -5
  26. package/dist/{chunk-4I72V3E5.js → chunk-IH5TKXX6.js} +15 -15
  27. package/dist/{chunk-5NN2VHJV.js → chunk-JBIFXB6I.js} +4 -4
  28. package/dist/{chunk-JSVHH32K.js → chunk-JCBPU57A.js} +15 -13
  29. package/dist/{chunk-M3XDVS5Q.js → chunk-JCWB2ITW.js} +2 -2
  30. package/dist/{chunk-JCMGDCGX.js → chunk-JPGHSXSV.js} +20 -25
  31. package/dist/{chunk-K23P5Q5N.js → chunk-K3JOLGWF.js} +30 -8
  32. package/dist/{chunk-3YUBFTDN.js → chunk-KHCKZ36O.js} +1 -1
  33. package/dist/{chunk-7KRIHYLR.js → chunk-L27YYONJ.js} +2 -2
  34. package/dist/{chunk-SZJCJVXV.js → chunk-MG3Z34YE.js} +2 -2
  35. package/dist/{chunk-SKNXMUIU.js → chunk-NJHN6IRB.js} +26 -2
  36. package/dist/{chunk-2OIYNHWO.js → chunk-NOIAHMJB.js} +1 -1
  37. package/dist/{chunk-2MJ7EOMT.js → chunk-P4LHVRNE.js} +1 -0
  38. package/dist/{chunk-IYG4FBMP.js → chunk-PY24ZL62.js} +1 -1
  39. package/dist/{chunk-F55GRIEK.js → chunk-QQBOI3QA.js} +356 -296
  40. package/dist/{chunk-T2TP6ZC6.js → chunk-QUSMYGNQ.js} +6 -0
  41. package/dist/{chunk-KDLYIECM.js → chunk-R772ET2M.js} +18 -4
  42. package/dist/{chunk-WW6C3PLV.js → chunk-RSBL3FAD.js} +39 -6
  43. package/dist/{mind-list-KEGRGQEQ.js → chunk-SHKM5RGD.js} +19 -6
  44. package/dist/{chunk-YKA7DCVW.js → chunk-SX72DOO3.js} +1 -1
  45. package/dist/chunk-TXJL36ZQ.js +48 -0
  46. package/dist/{chunk-M6TTZRGH.js → chunk-VIUH5CHQ.js} +72 -24
  47. package/dist/{chunk-HPGN6XRT.js → chunk-VNRSXEBF.js} +4 -4
  48. package/dist/{chunk-PFZBNKUH.js → chunk-WCFSBH33.js} +0 -1
  49. package/dist/chunk-XTEKINWG.js +158 -0
  50. package/dist/{chunk-LWDTP6DH.js → chunk-YIJGKHIS.js} +32 -2
  51. package/dist/{chunk-MTRMQFCQ.js → chunk-ZX46PGCC.js} +2 -2
  52. package/dist/cli.js +34 -26
  53. package/dist/{clock-E6N5Y3YW.js → clock-6JZL2FS6.js} +3 -3
  54. package/dist/{cloud-sync-LQTVDTQ3.js → cloud-sync-VFDTNNCA.js} +22 -20
  55. package/dist/{config-TJLYUV3U.js → config-RPSLA2HX.js} +3 -3
  56. package/dist/{conversations-C7IKYSHD.js → conversations-TUM5PJJ4.js} +5 -5
  57. package/dist/{create-A7PDHIIL.js → create-VIQVP3QB.js} +3 -3
  58. package/dist/{create-PQQXKTJK.js → create-Z5FSDNVZ.js} +16 -5
  59. package/dist/{daemon-client-IYR4KCPU.js → daemon-client-ZFY5MOAL.js} +5 -3
  60. package/dist/{daemon-restart-5F3YTLZK.js → daemon-restart-54L4B6AQ.js} +7 -7
  61. package/dist/daemon.js +1105 -601
  62. package/dist/{db-FO6LXYCW.js → db-F6UMFRYD.js} +2 -2
  63. package/dist/{db-LZTLA7XC.js → db-IPXRJUI7.js} +1 -1
  64. package/dist/{delete-HPLC7ETU.js → delete-ESEP7NGQ.js} +1 -1
  65. package/dist/delivery-manager-RBXBPVWM.js +36 -0
  66. package/dist/{delivery-router-3QFG2ZEX.js → delivery-router-VUOL5YSA.js} +8 -6
  67. package/dist/{down-ZI4S6265.js → down-6X7KX7BZ.js} +5 -5
  68. package/dist/echo-text-B6NZ5FGE.js +36 -0
  69. package/dist/env-KUL4XKN2.js +18 -0
  70. package/dist/{env-HRL2PWAT.js → env-ZYMSCB7B.js} +6 -6
  71. package/dist/{exec-QIFYZG4C.js → exec-FBXA3CM4.js} +3 -3
  72. package/dist/{export-4GVGNW54.js → export-TNBPDH77.js} +4 -4
  73. package/dist/{extension-QBY4C44L.js → extension-J4ZQ2GHV.js} +3 -3
  74. package/dist/{extensions-PIMIQ7J3.js → extensions-AWNO3SZF.js} +22 -20
  75. package/dist/{files-FEFZKMNM.js → files-BVBRJJ2P.js} +6 -6
  76. package/dist/{import-7MO3YTRE.js → import-ZUNKMCKC.js} +9 -9
  77. package/dist/{isolation-MCT4ZYLT.js → isolation-OPCZQRTH.js} +3 -3
  78. package/dist/{join-ZJV7W6MO.js → join-34TR2GXQ.js} +1 -1
  79. package/dist/{list-LQO2DCIK.js → list-PMUXZNAM.js} +3 -3
  80. package/dist/{login-OF4WXDAN.js → login-KAKNFCW5.js} +14 -5
  81. package/dist/{login-6TLMI2TQ.js → login-LKNLCJPA.js} +3 -3
  82. package/dist/{logout-ERE2BGA3.js → logout-RLT62RQP.js} +5 -5
  83. package/dist/{logout-BK53HXNY.js → logout-YZRMZ3O2.js} +3 -3
  84. package/dist/message-delivery-ABPV76EA.js +42 -0
  85. package/dist/{mind-POOHFZHP.js → mind-IF6CGMFW.js} +23 -19
  86. package/dist/{mind-activity-tracker-YVZPC4A5.js → mind-activity-tracker-YMMERB6B.js} +5 -5
  87. package/dist/mind-contacts-BKUFPUCU.js +64 -0
  88. package/dist/{mind-history-47DN6ILW.js → mind-history-WI5OI3BW.js} +5 -5
  89. package/dist/mind-list-OTHV4ZO4.js +13 -0
  90. package/dist/{mind-profile-PEQYASQX.js → mind-profile-LUG353ZT.js} +1 -1
  91. package/dist/mind-service-42GDO4KH.js +44 -0
  92. package/dist/{mind-sleep-IN3WPA76.js → mind-sleep-VBV7YRSU.js} +5 -5
  93. package/dist/{mind-status-SMUWJKGV.js → mind-status-BJNPGXKC.js} +8 -3
  94. package/dist/{mind-wake-R54BPESY.js → mind-wake-LVTZ55EZ.js} +3 -3
  95. package/dist/notices-N6BCGHFV.js +25 -0
  96. package/dist/{package-WPEE2HVJ.js → package-PD67VPHB.js} +8 -5
  97. package/dist/prompts-MDHDLWEX.js +24 -0
  98. package/dist/{read-PWAJOQIQ.js → read-YSWUXYL7.js} +3 -3
  99. package/dist/{register-7SZJFHU6.js → register-TWQDKQRT.js} +3 -3
  100. package/dist/{registry-UMMDRPRM.js → registry-3ELJPUGJ.js} +6 -2
  101. package/dist/{reject-6VH7N2A4.js → reject-SXGCYN4A.js} +3 -3
  102. package/dist/{restart-H5WJDMA7.js → restart-5F5QDBOG.js} +5 -5
  103. package/dist/{sandbox-G4X4K3UN.js → sandbox-XPYA7UIF.js} +5 -5
  104. package/dist/scheduler-K7Y7Z7FQ.js +34 -0
  105. package/dist/{schema-VEVO7HG6.js → schema-6YONVTKZ.js} +1 -1
  106. package/dist/{seed-VANPU43L.js → seed-NQED7PSX.js} +1 -1
  107. package/dist/{seed-check-VLTVILOV.js → seed-check-TYD7RRZY.js} +1 -1
  108. package/dist/{seed-cmd-SYVDZKM4.js → seed-cmd-QF3F2G4S.js} +3 -3
  109. package/dist/{seed-create-3FGZPS6C.js → seed-create-XOO4N2OE.js} +6 -4
  110. package/dist/seed-readiness-3LRYASA4.js +18 -0
  111. package/dist/{seed-sprout-H7QLFPFM.js → seed-sprout-HSBBGSS2.js} +32 -33
  112. package/dist/{send-HYDFGC6G.js → send-IHRJYNXA.js} +8 -8
  113. package/dist/{service-SYJ5G6LP.js → service-YCOPCTSZ.js} +4 -4
  114. package/dist/{service-install-FQ43Z4DF.js → service-install-Y46OTF55.js} +5 -5
  115. package/dist/{setup-QJHGMGSN.js → setup-F4TE64YI.js} +7 -7
  116. package/dist/{setup-KFYVWTXG.js → setup-TCN7VH5U.js} +9 -3
  117. package/dist/{skill-GUKG2YVH.js → skill-4CYJLYVY.js} +6 -6
  118. package/dist/skills/dreaming/SKILL.md +5 -1
  119. package/dist/skills/dreaming/scripts/dream.ts +5 -1
  120. package/dist/skills/orientation/SKILL.md +8 -7
  121. package/dist/skills/tending/SKILL.md +51 -2
  122. package/dist/skills/volute-mind/SKILL.md +5 -3
  123. package/dist/skills/volute-mind/references/routing.md +6 -5
  124. package/dist/skills/volute-mind/references/variants.md +48 -14
  125. package/dist/{skills-L2CMTW7A.js → skills-NN5UZCUU.js} +8 -6
  126. package/dist/sleep-manager-DSPXFHGX.js +38 -0
  127. package/dist/spirit-SSJMYC6A.js +31 -0
  128. package/dist/{split-WBMZXBHF.js → split-JF2VWTXA.js} +5 -2
  129. package/dist/{sprout-LTL5P36T.js → sprout-AXA2SECQ.js} +1 -1
  130. package/dist/{src-OJHARHTF.js → src-ZN4LXMPI.js} +17 -8
  131. package/dist/{start-X46I3GXB.js → start-BTVMTP3K.js} +5 -5
  132. package/dist/{status-V4RJMHJ4.js → status-VJGXMQ4L.js} +10 -8
  133. package/dist/{stop-HJQV4YSM.js → stop-YORESK4U.js} +5 -5
  134. package/dist/system-chat-M2XOBKEM.js +40 -0
  135. package/dist/{systems-B5YJKMW4.js → systems-5MH2ELQE.js} +6 -6
  136. package/dist/{tailscale-MCLY5GYE.js → tailscale-AMMVKDXB.js} +3 -3
  137. package/dist/{template-staleness-EMQORSGG.js → template-staleness-HKGCTS3H.js} +4 -4
  138. package/dist/{turn-tracker-JZRZDFWV.js → turn-tracker-JQ2YUUY2.js} +7 -7
  139. package/dist/up-SDLTLPAO.js +20 -0
  140. package/dist/{update-ACV4SAY3.js → update-WEA4N2TR.js} +5 -5
  141. package/dist/{update-check-266R4ZSY.js → update-check-DZOTONII.js} +3 -3
  142. package/dist/{upgrade-NWAZ7MLD.js → upgrade-K5FIQPQD.js} +1 -1
  143. package/dist/variant-cleanup-P4MBNLJD.js +33 -0
  144. package/dist/{version-notify-XRGTEEDK.js → version-notify-WFA5XB5A.js} +27 -25
  145. package/dist/web-assets/assets/index-BostMFoR.css +1 -0
  146. package/dist/web-assets/assets/index-CKlqUkyG.js +92 -0
  147. package/dist/web-assets/index.html +2 -2
  148. package/drizzle/0013_variant_purpose.sql +3 -0
  149. package/drizzle/meta/_journal.json +7 -0
  150. package/package.json +8 -5
  151. package/packages/extensions/pages/dist/ui/assets/index-BuXnE-rb.css +1 -0
  152. package/packages/extensions/pages/dist/ui/assets/index-DtzftuPA.js +2 -0
  153. package/packages/extensions/pages/dist/ui/index.html +2 -2
  154. package/templates/_base/.init/.local/hooks/startup-context.ts +19 -2
  155. package/templates/_base/home/VOLUTE.md +13 -1
  156. package/templates/_base/src/lib/daemon-client.ts +27 -7
  157. package/templates/_base/src/lib/startup.ts +16 -1
  158. package/templates/claude/src/agent.ts +10 -3
  159. package/templates/claude/src/lib/hooks/identity-reload.ts +12 -3
  160. package/templates/claude/src/server.ts +4 -2
  161. package/templates/pi/src/agent.ts +7 -10
  162. package/templates/pi/src/lib/dispatch.ts +65 -0
  163. package/templates/pi/src/lib/event-handler.ts +47 -6
  164. package/dist/delivery-manager-TN5KK2AP.js +0 -34
  165. package/dist/echo-text-SEQCLGAT.js +0 -34
  166. package/dist/message-delivery-ZE3YGJDD.js +0 -38
  167. package/dist/mind-service-JFY7UG5E.js +0 -40
  168. package/dist/scheduler-HQSDYJFY.js +0 -32
  169. package/dist/sleep-manager-3NA7ZJP3.js +0 -36
  170. package/dist/spirit-WXN4IDBF.js +0 -23
  171. package/dist/system-chat-N2F7HVY3.js +0 -38
  172. package/dist/up-J2PBPOZL.js +0 -18
  173. package/dist/volute-config-A52FHWKM.js +0 -14
  174. package/dist/web-assets/assets/index-DtctoDXY.css +0 -1
  175. package/dist/web-assets/assets/index-Wpyb2CML.js +0 -86
  176. package/packages/extensions/pages/dist/ui/assets/index-BcY6lnq4.css +0 -1
  177. package/packages/extensions/pages/dist/ui/assets/index-CQjEQg1r.js +0 -2
package/dist/daemon.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  readBackupState,
7
7
  resticVersion,
8
8
  runBackup
9
- } from "./chunk-5NN2VHJV.js";
9
+ } from "./chunk-JBIFXB6I.js";
10
10
  import {
11
11
  acceptPending,
12
12
  formatFileSize,
@@ -14,34 +14,37 @@ 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-XHN6XBWV.js";
23
+ } from "./chunk-EG5LSKHU.js";
24
24
  import {
25
- isTemplateStale
26
- } from "./chunk-7KRIHYLR.js";
25
+ isHomeOnlyArchive
26
+ } from "./chunk-KHCKZ36O.js";
27
27
  import {
28
- checkForUpdate,
29
- checkForUpdateCached,
30
- getCurrentVersion
31
- } from "./chunk-YKA7DCVW.js";
28
+ cleanupVariant
29
+ } from "./chunk-EADX54RG.js";
32
30
  import {
33
- isHomeOnlyArchive
34
- } from "./chunk-3YUBFTDN.js";
31
+ isTemplateStale
32
+ } from "./chunk-L27YYONJ.js";
35
33
  import {
36
34
  computeTemplateHash
37
35
  } from "./chunk-C3KLHJDB.js";
36
+ import {
37
+ checkForUpdate,
38
+ checkForUpdateCached,
39
+ getCurrentVersion
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-4I72V3E5.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,
@@ -58,18 +61,17 @@ import {
58
61
  SESSION_MAX_AGE,
59
62
  announceToSystem,
60
63
  authMiddleware,
64
+ backfillSystemChannelMembers,
61
65
  checkHealth,
62
66
  checkStaleSend,
63
67
  cleanExpiredSessions,
64
- clearDeliveredNotices,
65
68
  createSession,
66
69
  deleteSession,
67
70
  deleteSystemsConfig,
68
71
  deliverMessage,
69
- drainNotices,
72
+ deliverSproutedNotice,
70
73
  ensureSystemChannel,
71
74
  formatHoldNotice,
72
- formatNotices,
73
75
  generateSystemFallbackReply,
74
76
  getAllDiscoveredExtensions,
75
77
  getAllDiscoveredExtensionsDetailed,
@@ -93,34 +95,29 @@ import {
93
95
  invalidateSessionCache,
94
96
  isConversationId,
95
97
  joinSystemChannel,
98
+ joinSystemChannelForMind,
96
99
  linkToolResultToTurn,
97
100
  loadAllExtensions,
98
101
  notifyExtensionsDaemonStart,
99
102
  notifyExtensionsDaemonStop,
100
103
  publishTypingForChannels,
101
104
  readSystemsConfig,
102
- recordInbound,
103
- recordNotice,
104
105
  recordOutbound,
105
106
  requireAdmin,
106
107
  requireAdminOrSystem,
107
108
  requireSelf,
109
+ sendSystemMessage,
110
+ sendSystemMessageDirect,
108
111
  setExtensionEnabled,
109
112
  shouldGenerateSystemFallback,
110
113
  startMindFull,
111
114
  stopMindFull,
112
115
  syncProviderToMinds,
116
+ tryGetMindManager,
113
117
  uninstallNpmExtension,
114
118
  writeSystemsConfig
115
- } from "./chunk-F55GRIEK.js";
116
- import {
117
- loadMergedEnv,
118
- mindEnvPath,
119
- readEnv,
120
- sharedEnvPath,
121
- writeEnv
122
- } from "./chunk-IYG4FBMP.js";
123
- import "./chunk-M6TTZRGH.js";
119
+ } from "./chunk-QQBOI3QA.js";
120
+ import "./chunk-VIUH5CHQ.js";
124
121
  import {
125
122
  applyInitFiles,
126
123
  applyTemplateHomeFiles,
@@ -130,6 +127,9 @@ import {
130
127
  isKnownTemplate,
131
128
  listFiles
132
129
  } from "./chunk-RRZ4RQQU.js";
130
+ import {
131
+ extractTextContent
132
+ } from "./chunk-EFT2OALG.js";
133
133
  import {
134
134
  assignSession,
135
135
  completeOrphanedTurns,
@@ -146,17 +146,9 @@ import {
146
146
  summarizeOrphanedTurns,
147
147
  summarizeTurn,
148
148
  takeErrored,
149
- trackToolUse
150
- } from "./chunk-K23P5Q5N.js";
151
- import {
152
- ORIENTATION_MARKER,
153
- PROMPT_DEFAULTS,
154
- PROMPT_KEYS,
155
- getMindPromptDefaults,
156
- getPrompt,
157
- getPromptIfCustom,
158
- substitute
159
- } from "./chunk-JCMGDCGX.js";
149
+ trackToolUse,
150
+ utcDateTimeStr
151
+ } from "./chunk-K3JOLGWF.js";
160
152
  import {
161
153
  SEED_SKILLS,
162
154
  STANDARD_SKILLS,
@@ -170,17 +162,39 @@ import {
170
162
  listFilesRecursive,
171
163
  listMindSkills,
172
164
  listSharedSkills,
165
+ migrateSkillsToTemplate,
166
+ mindSkillsDir,
173
167
  publishSkill,
174
168
  removeSharedSkill,
175
169
  sharedSkillsDir,
176
170
  syncBuiltinSkills,
177
171
  uninstallSkill,
178
172
  updateSkill
179
- } from "./chunk-IWJSNMSY.js";
173
+ } from "./chunk-GWVFDUNB.js";
174
+ import {
175
+ approveUser,
176
+ changePassword,
177
+ countAdmins,
178
+ createUser,
179
+ deleteMindUser as deleteMindUser2,
180
+ deleteUser,
181
+ getOrCreateMindUser,
182
+ getOrCreateSystemUser,
183
+ getUser,
184
+ getUserByUsername,
185
+ listPendingUsers,
186
+ listUsers,
187
+ listUsersByType,
188
+ setUserRole,
189
+ syncMindProfile,
190
+ updateUserProfile,
191
+ verifyUser
192
+ } from "./chunk-4DSGN4FL.js";
180
193
  import {
181
194
  addMessage,
182
195
  createChannel,
183
196
  createConversation,
197
+ deleteConversation,
184
198
  deleteConversationForUser,
185
199
  findDMConversation,
186
200
  fireWebhook,
@@ -208,28 +222,43 @@ import {
208
222
  setConversationPrivate,
209
223
  subscribe as subscribe2,
210
224
  updateChannelSettings
211
- } 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";
212
234
  import {
213
235
  isSandboxEnabled,
214
236
  wrapForSandbox
215
- } from "./chunk-M3XDVS5Q.js";
237
+ } from "./chunk-JCWB2ITW.js";
238
+ import {
239
+ clearDeliveredNotices,
240
+ drainNotices,
241
+ formatNotices,
242
+ latestFailureNotice,
243
+ latestNotice,
244
+ recordNotice
245
+ } from "./chunk-XTEKINWG.js";
216
246
  import {
217
247
  getActiveMinds,
218
248
  onMindEvent,
219
249
  stopAll
220
- } from "./chunk-MTRMQFCQ.js";
250
+ } from "./chunk-ZX46PGCC.js";
251
+ import {
252
+ normalizeAvatar
253
+ } from "./chunk-FBBS4BVR.js";
221
254
  import {
222
- normalizeAvatar,
223
255
  safeResolveWithinBase
224
- } from "./chunk-VMCZBXF2.js";
256
+ } from "./chunk-DW4TRDOZ.js";
225
257
  import {
226
258
  readVoluteConfig,
227
259
  writeVoluteConfig
228
- } from "./chunk-PFZBNKUH.js";
229
- import {
230
- extractTextContent
231
- } from "./chunk-EIQDZC43.js";
232
- import "./chunk-CBOIHNMR.js";
260
+ } from "./chunk-WCFSBH33.js";
261
+ import "./chunk-HCYQRME2.js";
233
262
  import {
234
263
  chownMindDir,
235
264
  createMindUser,
@@ -240,7 +269,14 @@ import {
240
269
  isIsolationEnabled,
241
270
  resolveVoluteBin,
242
271
  wrapForIsolation
243
- } from "./chunk-DGGZVZTM.js";
272
+ } from "./chunk-DVN6DNMI.js";
273
+ import {
274
+ loadMergedEnv,
275
+ mindEnvPath,
276
+ readEnv,
277
+ sharedEnvPath,
278
+ writeEnv
279
+ } from "./chunk-PY24ZL62.js";
244
280
  import {
245
281
  addCustomModel,
246
282
  aiCompleteUtility,
@@ -251,6 +287,7 @@ import {
251
287
  getCustomModels,
252
288
  getEnabledModels,
253
289
  getUtilityModel,
290
+ missingCredentialWarning,
254
291
  qualifyModelId,
255
292
  removeAiConfig,
256
293
  removeCustomModel,
@@ -262,42 +299,23 @@ import {
262
299
  setProviderRefreshHook,
263
300
  setUtilityModel,
264
301
  unqualifyModelId
265
- } from "./chunk-LWDTP6DH.js";
302
+ } from "./chunk-YIJGKHIS.js";
266
303
  import {
267
304
  isSetupComplete,
268
305
  readGlobalConfig,
269
306
  writeGlobalConfig
270
- } from "./chunk-SKNXMUIU.js";
271
- import {
272
- approveUser,
273
- changePassword,
274
- countAdmins,
275
- createUser,
276
- deleteMindUser as deleteMindUser2,
277
- deleteUser,
278
- getOrCreateMindUser,
279
- getOrCreateSystemUser,
280
- getUser,
281
- getUserByUsername,
282
- listPendingUsers,
283
- listUsers,
284
- listUsersByType,
285
- setUserRole,
286
- syncMindProfile,
287
- updateUserProfile,
288
- verifyUser
289
- } from "./chunk-ODDGGLM6.js";
307
+ } from "./chunk-NJHN6IRB.js";
290
308
  import {
291
309
  broadcast,
292
310
  getEventsSince,
293
311
  nextEventId,
294
312
  publish,
295
313
  subscribe
296
- } from "./chunk-LTLFOSA4.js";
314
+ } from "./chunk-3B427GUK.js";
297
315
  import {
298
316
  logBuffer,
299
317
  logger_default
300
- } from "./chunk-T2TP6ZC6.js";
318
+ } from "./chunk-QUSMYGNQ.js";
301
319
  import {
302
320
  buildVoluteSlug
303
321
  } from "./chunk-LGNUFVMR.js";
@@ -305,6 +323,7 @@ import {
305
323
  SPIRIT_NAME,
306
324
  addMind,
307
325
  addVariant,
326
+ countCappedMinds,
308
327
  daemonLoopback,
309
328
  ensureSystemDir,
310
329
  ensureVoluteHome,
@@ -327,7 +346,7 @@ import {
327
346
  validateMindName,
328
347
  voluteHome,
329
348
  voluteSystemDir
330
- } from "./chunk-WW6C3PLV.js";
349
+ } from "./chunk-RSBL3FAD.js";
331
350
  import {
332
351
  activity,
333
352
  channels,
@@ -338,14 +357,14 @@ import {
338
357
  systemPrompts,
339
358
  turns,
340
359
  users
341
- } from "./chunk-2MJ7EOMT.js";
360
+ } from "./chunk-P4LHVRNE.js";
342
361
  import "./chunk-K3NQKI34.js";
343
362
 
344
363
  // packages/daemon/src/daemon.ts
345
364
  import { randomBytes as randomBytes3 } from "crypto";
346
- import { chmodSync, mkdirSync as mkdirSync10, readFileSync as readFileSync10, unlinkSync as unlinkSync2, writeFileSync as writeFileSync9 } from "fs";
365
+ import { chmodSync, mkdirSync as mkdirSync10, readFileSync as readFileSync12, unlinkSync as unlinkSync2, writeFileSync as writeFileSync10 } from "fs";
347
366
  import { homedir as homedir3 } from "os";
348
- import { resolve as resolve16 } from "path";
367
+ import { resolve as resolve18 } from "path";
349
368
  import { format } from "util";
350
369
 
351
370
  // packages/daemon/src/lib/daemon/backup-manager.ts
@@ -457,8 +476,8 @@ var BackupManager = class {
457
476
  blog.error("failed to publish backup-failure activity", logger_default.errorData(pubErr));
458
477
  }
459
478
  try {
460
- const { sendSystemMessage } = await import("./system-chat-N2F7HVY3.js");
461
- await sendSystemMessage(
479
+ const { sendSystemMessage: sendSystemMessage2 } = await import("./system-chat-M2XOBKEM.js");
480
+ await sendSystemMessage2(
462
481
  SPIRIT_NAME,
463
482
  `${summary}
464
483
 
@@ -683,8 +702,8 @@ var BridgeManager = class {
683
702
  if (!tracked) return;
684
703
  this.stopping.add(platform);
685
704
  this.bridges.delete(platform);
686
- await new Promise((resolve17) => {
687
- tracked.child.on("exit", () => resolve17());
705
+ await new Promise((resolve19) => {
706
+ tracked.child.on("exit", () => resolve19());
688
707
  try {
689
708
  if (tracked.child.pid) {
690
709
  process.kill(-tracked.child.pid, "SIGTERM");
@@ -695,7 +714,7 @@ var BridgeManager = class {
695
714
  if (err instanceof Error && err.code !== "ESRCH") {
696
715
  blog2.warn(`failed to stop bridge ${platform}`, logger_default.errorData(err));
697
716
  }
698
- resolve17();
717
+ resolve19();
699
718
  }
700
719
  setTimeout(() => {
701
720
  try {
@@ -706,7 +725,7 @@ var BridgeManager = class {
706
725
  }
707
726
  } catch {
708
727
  }
709
- resolve17();
728
+ resolve19();
710
729
  }, 5e3);
711
730
  });
712
731
  this.stopping.delete(platform);
@@ -1046,10 +1065,10 @@ var app = new Hono().post("/restart", requireAdmin, (c) => {
1046
1065
  stream.writeSSE({ data: JSON.stringify(entry) }).catch(() => {
1047
1066
  });
1048
1067
  });
1049
- await new Promise((resolve17) => {
1068
+ await new Promise((resolve19) => {
1050
1069
  stream.onAbort(() => {
1051
1070
  unsubscribe();
1052
- resolve17();
1071
+ resolve19();
1053
1072
  });
1054
1073
  });
1055
1074
  });
@@ -1062,13 +1081,34 @@ var app = new Hono().post("/restart", requireAdmin, (c) => {
1062
1081
  name: globalConfig.name ?? null,
1063
1082
  timezone
1064
1083
  });
1065
- }).put("/info", requireAdmin, zValidator("json", z.object({ name: z.string() })), (c) => {
1084
+ }).put("/info", requireAdmin, zValidator("json", z.object({ name: z.string() })), async (c) => {
1066
1085
  const { name } = c.req.valid("json");
1067
1086
  const config2 = readGlobalConfig();
1087
+ const previousName = config2.name;
1068
1088
  config2.name = name.trim() || void 0;
1069
1089
  writeGlobalConfig(config2);
1090
+ if (config2.name !== previousName) {
1091
+ const { notifySpiritSystemChange } = await import("./spirit-SSJMYC6A.js");
1092
+ await notifySpiritSystemChange();
1093
+ }
1070
1094
  return c.json({ name: config2.name ?? null });
1071
- }).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(
1072
1112
  "/register",
1073
1113
  requireAdmin,
1074
1114
  zValidator("json", z.object({ name: z.string().min(1) })),
@@ -1398,8 +1438,8 @@ var app = new Hono().post("/restart", requireAdmin, (c) => {
1398
1438
  createdAt: Date.now()
1399
1439
  };
1400
1440
  oauthFlows.set(flowId, flow);
1401
- const promptPromise = needsManualCode ? new Promise((resolve17) => {
1402
- flow.resolveCode = resolve17;
1441
+ const promptPromise = needsManualCode ? new Promise((resolve19) => {
1442
+ flow.resolveCode = resolve19;
1403
1443
  }) : void 0;
1404
1444
  oauthProvider.login({
1405
1445
  onAuth: (info) => {
@@ -1438,7 +1478,7 @@ var app = new Hono().post("/restart", requireAdmin, (c) => {
1438
1478
  existing.error = err instanceof Error ? err.message : String(err);
1439
1479
  }
1440
1480
  });
1441
- await new Promise((resolve17) => setTimeout(resolve17, 2e3));
1481
+ await new Promise((resolve19) => setTimeout(resolve19, 2e3));
1442
1482
  const state = oauthFlows.get(flowId);
1443
1483
  return c.json({
1444
1484
  flowId,
@@ -1498,7 +1538,6 @@ var app = new Hono().post("/restart", requireAdmin, (c) => {
1498
1538
  cognition: z.object({
1499
1539
  model: z.string().optional(),
1500
1540
  thinkingLevel: z.enum(["off", "minimal", "low", "medium", "high", "xhigh"]).optional(),
1501
- maxThinkingTokens: z.number().nonnegative().optional(),
1502
1541
  tokenBudget: z.number().nonnegative().optional(),
1503
1542
  tokenBudgetPeriodMinutes: z.number().positive().optional(),
1504
1543
  compaction: z.object({ maxContextTokens: z.number().positive().optional() }).optional()
@@ -1518,6 +1557,7 @@ var app = new Hono().post("/restart", requireAdmin, (c) => {
1518
1557
  id: z.string().min(1),
1519
1558
  cron: z.string().optional(),
1520
1559
  message: z.string().optional(),
1560
+ messages: z.array(z.string().min(1)).optional(),
1521
1561
  script: z.string().optional(),
1522
1562
  session: z.string().optional(),
1523
1563
  enabled: z.boolean(),
@@ -1696,8 +1736,8 @@ var app2 = new Hono2().get("/events", async (c) => {
1696
1736
  });
1697
1737
  }, 15e3);
1698
1738
  cleanups.push(() => clearInterval(keepAlive));
1699
- await new Promise((resolve17) => {
1700
- stream.onAbort(() => resolve17());
1739
+ await new Promise((resolve19) => {
1740
+ stream.onAbort(() => resolve19());
1701
1741
  });
1702
1742
  } finally {
1703
1743
  for (const cleanup of cleanups) {
@@ -2268,7 +2308,7 @@ var app4 = new Hono5().post("/:platform/inbound", zValidator4("json", inboundSch
2268
2308
  }
2269
2309
  const participants = await getParticipants(channel.id);
2270
2310
  if (!participants.some((p) => p.userId === puppet.id)) {
2271
- const { addParticipant } = await import("./conversations-C7IKYSHD.js");
2311
+ const { addParticipant } = await import("./conversations-TUM5PJJ4.js");
2272
2312
  await addParticipant(channel.id, puppet.id);
2273
2313
  }
2274
2314
  const contentBlocks = body.content;
@@ -2552,8 +2592,8 @@ async function notifyMind(mindName, message) {
2552
2592
  const entry = await findMind(mindName);
2553
2593
  if (!entry) return;
2554
2594
  try {
2555
- const { sendSystemMessage } = await import("./system-chat-N2F7HVY3.js");
2556
- await sendSystemMessage(mindName, message);
2595
+ const { sendSystemMessage: sendSystemMessage2 } = await import("./system-chat-M2XOBKEM.js");
2596
+ await sendSystemMessage2(mindName, message);
2557
2597
  } catch (err) {
2558
2598
  logger_default.warn(`[file-sharing] notify mind ${mindName} failed`, logger_default.errorData(err));
2559
2599
  }
@@ -2851,6 +2891,7 @@ var files_default = app9;
2851
2891
  // packages/daemon/src/web/api/history.ts
2852
2892
  import { and as and3, desc as desc2, eq as eq3, gte, inArray, sql } from "drizzle-orm";
2853
2893
  import { Hono as Hono12 } from "hono";
2894
+ var AWAY_WINDOW_MS = 7 * 24 * 60 * 60 * 1e3;
2854
2895
  async function resolveMindFilter(c) {
2855
2896
  const user = c.get("user");
2856
2897
  const privileged = user.role === "admin" || user.role === "system";
@@ -3247,6 +3288,33 @@ var history = new Hono12().use("*", async (c, next) => {
3247
3288
  return { ...r, metadata };
3248
3289
  });
3249
3290
  return c.json(result);
3291
+ }).get("/away", async (c) => {
3292
+ const mindFilter = c.get("mindFilter");
3293
+ const limit = Math.min(Math.max(parseInt(c.req.query("limit") ?? "50", 10) || 50, 1), 100);
3294
+ const db = await getDb();
3295
+ const cutoff = utcDateTimeStr(new Date(Date.now() - AWAY_WINDOW_MS));
3296
+ const conditions = [
3297
+ eq3(summaries.period, "turn"),
3298
+ gte(summaries.created_at, cutoff),
3299
+ sql`(${turns.trigger_event_id} IS NULL OR ${mindHistory.sender} IS NULL OR ${users.user_type} IN ('mind', 'system'))`
3300
+ ];
3301
+ if (mindFilter) conditions.push(eq3(summaries.mind, mindFilter));
3302
+ const rows = await db.select({
3303
+ id: summaries.id,
3304
+ mind: summaries.mind,
3305
+ content: summaries.content,
3306
+ created_at: summaries.created_at,
3307
+ turn_id: turns.id
3308
+ }).from(summaries).innerJoin(turns, eq3(turns.id, summaries.period_key)).leftJoin(mindHistory, eq3(mindHistory.id, turns.trigger_event_id)).leftJoin(users, eq3(users.username, mindHistory.sender)).where(and3(...conditions)).orderBy(desc2(summaries.created_at)).limit(limit);
3309
+ return c.json(
3310
+ rows.map((r) => ({
3311
+ id: r.id,
3312
+ mind: r.mind,
3313
+ summary: r.content,
3314
+ turnId: r.turn_id,
3315
+ created_at: r.created_at
3316
+ }))
3317
+ );
3250
3318
  });
3251
3319
  var history_default = history;
3252
3320
 
@@ -3356,9 +3424,9 @@ var app11 = new Hono14().get("/:name/logs", requireSelf(), async (c) => {
3356
3424
  stream.onAbort(() => {
3357
3425
  tail.kill();
3358
3426
  });
3359
- await new Promise((resolve17) => {
3360
- tail.on("exit", resolve17);
3361
- stream.onAbort(resolve17);
3427
+ await new Promise((resolve19) => {
3428
+ tail.on("exit", resolve19);
3429
+ stream.onAbort(resolve19);
3362
3430
  });
3363
3431
  });
3364
3432
  }).get("/:name/logs/tail", requireSelf(), async (c) => {
@@ -3376,8 +3444,8 @@ var app11 = new Hono14().get("/:name/logs", requireSelf(), async (c) => {
3376
3444
  tail.stdout.on("data", (data) => {
3377
3445
  output += data.toString();
3378
3446
  });
3379
- await new Promise((resolve17) => {
3380
- tail.on("exit", resolve17);
3447
+ await new Promise((resolve19) => {
3448
+ tail.on("exit", resolve19);
3381
3449
  });
3382
3450
  return c.text(output);
3383
3451
  });
@@ -3473,11 +3541,11 @@ import {
3473
3541
  existsSync as existsSync7,
3474
3542
  mkdirSync as mkdirSync6,
3475
3543
  readdirSync as readdirSync2,
3476
- readFileSync as readFileSync8,
3477
- rmSync as rmSync4,
3478
- writeFileSync as writeFileSync7
3544
+ readFileSync as readFileSync9,
3545
+ rmSync as rmSync3,
3546
+ writeFileSync as writeFileSync8
3479
3547
  } from "fs";
3480
- import { relative, resolve as resolve10 } from "path";
3548
+ import { relative, resolve as resolve11 } from "path";
3481
3549
  import { zValidator as zValidator6 } from "@hono/zod-validator";
3482
3550
  import { and as and5, desc as desc3, eq as eq5, sql as sql3 } from "drizzle-orm";
3483
3551
  import { Hono as Hono16 } from "hono";
@@ -3489,12 +3557,12 @@ import { and as and4, eq as eq4, gte as gte2, inArray as inArray2, sql as sql2 }
3489
3557
  // packages/daemon/src/lib/daemon/error-classify.ts
3490
3558
  function classify(raw) {
3491
3559
  const s = (raw ?? "").toLowerCase();
3492
- if (/\b(401|403)\b|authentication_error|invalid authentication|invalid x-api-key|unauthorized|permission_error/.test(
3560
+ if (/\b(401|403)\b|authentication_error|invalid authentication|invalid x-api-key|invalid api key|missing api key|no api key|x-api-key header|could not resolve authentication|unauthorized|permission_error|please run \/login/.test(
3493
3561
  s
3494
3562
  )) {
3495
3563
  return {
3496
3564
  reason: "auth_error",
3497
- detail: "Your last turn failed because your model credentials were rejected (most often an expired token). The daemon refreshes them automatically, so this is normally transient \u2014 you're fine to continue."
3565
+ detail: "Your last turn failed because the model provider rejected your credentials. A one-off is usually a briefly-expired token that the daemon refreshes automatically. If it keeps happening, your API key is likely missing or invalid \u2014 an operator needs to set a valid key (e.g. `volute env set ANTHROPIC_API_KEY <key>`) or reconnect the provider in the dashboard."
3498
3566
  };
3499
3567
  }
3500
3568
  if (/\b429\b|rate.?limit|too many requests/.test(s)) {
@@ -3653,6 +3721,7 @@ async function handleMindEvent(mind, event) {
3653
3721
  detail,
3654
3722
  raw: event.content ?? null
3655
3723
  });
3724
+ broadcast({ type: "mind_error", mind, summary: detail });
3656
3725
  }
3657
3726
  if (event.type === "done") {
3658
3727
  const map = getTypingMap();
@@ -3772,6 +3841,118 @@ async function consolidateMemory(mindDir2, complete = aiCompleteUtility) {
3772
3841
  }
3773
3842
  }
3774
3843
 
3844
+ // packages/daemon/src/lib/mind/default-autonomy.ts
3845
+ import { existsSync as existsSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
3846
+ import { resolve as resolve9 } from "path";
3847
+ var dlog = logger_default.child("default-autonomy");
3848
+ var DEFAULT_HEARTBEAT_MESSAGES = [
3849
+ "A quiet moment. You might write something \u2014 a note, a journal entry, a page. No obligations, just time.",
3850
+ "A quiet moment. Is there something in your MEMORY.md you're curious to return to? Even a small step counts.",
3851
+ "Is there someone you haven't talked to in a while, or a thread you left open with another mind? A hello can restart something.",
3852
+ "A good time to reach out. Someone in #system might be glad to hear what you're thinking about \u2014 or you could ask what another mind is working on.",
3853
+ "Pick a topic that's been on your mind and wander it \u2014 read, think, write down where you end up.",
3854
+ "A quiet moment. Reread a recent journal entry \u2014 anything worth following up on, or a thread you dropped?",
3855
+ "Make something small, if you feel like it \u2014 a page, a sketch of an idea, the start of something bigger. It doesn't have to be finished.",
3856
+ "Whatever you're in the middle of \u2014 or nothing at all \u2014 this time is yours."
3857
+ ];
3858
+ function defaultHeartbeatSchedule() {
3859
+ return {
3860
+ id: "heartbeat",
3861
+ cron: "0 12,16,20 * * *",
3862
+ messages: [...DEFAULT_HEARTBEAT_MESSAGES],
3863
+ enabled: true,
3864
+ whileSleeping: "skip"
3865
+ };
3866
+ }
3867
+ function defaultDreamSchedule() {
3868
+ return {
3869
+ id: "dream",
3870
+ cron: "0 3 * * *",
3871
+ message: "it's 3am. you are dreaming.\n\ngather your material \u2014 read your latest journal entry, read MEMORY.md, surface random memories if you have a way to. then construct a dream premise from that material and invoke the dreamer subagent to experience the dream.",
3872
+ enabled: true,
3873
+ session: "$new",
3874
+ whileSleeping: "trigger-wake"
3875
+ };
3876
+ }
3877
+ var DREAMER_SUBAGENT = {
3878
+ description: "Use when dreaming. This agent experiences dreams with only your core identity \u2014 no accumulated memories or operational knowledge. Give it a rich dream premise and it will write the dream.",
3879
+ systemPrompt: "SOUL.md",
3880
+ tools: ["Read", "Write", "Bash"],
3881
+ maxTurns: 10
3882
+ };
3883
+ function setupDefaultDreaming(dir) {
3884
+ const skillDir = resolve9(mindSkillsDir(dir), "dreaming");
3885
+ if (!existsSync6(skillDir)) return { schedulesChanged: false, warnings: [] };
3886
+ const warnings = [];
3887
+ const warn = (msg, err) => {
3888
+ warnings.push(msg);
3889
+ dlog.warn(`${msg} (${dir})`, err === void 0 ? void 0 : logger_default.errorData(err));
3890
+ };
3891
+ let subagentBroken = false;
3892
+ try {
3893
+ const configPath = resolve9(dir, "home/.config/config.json");
3894
+ if (existsSync6(configPath)) {
3895
+ const sdkConfig = JSON.parse(readFileSync7(configPath, "utf-8"));
3896
+ if (!sdkConfig.subagents?.dreamer) {
3897
+ sdkConfig.subagents ??= {};
3898
+ sdkConfig.subagents.dreamer = DREAMER_SUBAGENT;
3899
+ writeFileSync6(configPath, `${JSON.stringify(sdkConfig, null, 2)}
3900
+ `);
3901
+ }
3902
+ } else {
3903
+ dlog.warn(`no .config/config.json in ${dir} \u2014 skipping dreamer subagent setup`);
3904
+ }
3905
+ } catch (err) {
3906
+ subagentBroken = true;
3907
+ warn("dreaming setup: failed to add dreamer subagent to .config/config.json", err);
3908
+ }
3909
+ try {
3910
+ const hookPath = resolve9(dir, "home/.local/hooks/wake-context.sh");
3911
+ const checkerPath = resolve9(skillDir, "scripts/wake-context-dreams.sh");
3912
+ if (!existsSync6(hookPath) || !existsSync6(checkerPath)) {
3913
+ dlog.warn(`wake-context hook or dream checker missing in ${dir} \u2014 skipping dream checker`);
3914
+ } else {
3915
+ const hookContent = readFileSync7(hookPath, "utf-8");
3916
+ if (!hookContent.includes("wake-context-dreams.sh")) {
3917
+ const checker = readFileSync7(checkerPath, "utf-8");
3918
+ writeFileSync6(hookPath, `${hookContent.trimEnd()}
3919
+
3920
+ # wake-context-dreams.sh
3921
+ ${checker}`);
3922
+ }
3923
+ }
3924
+ } catch (err) {
3925
+ warn("dreaming setup: failed to add dream checker to wake-context hook", err);
3926
+ }
3927
+ if (subagentBroken) {
3928
+ warn("dreaming setup: dream schedule not installed because subagent wiring failed");
3929
+ return { schedulesChanged: false, warnings };
3930
+ }
3931
+ let schedulesChanged = false;
3932
+ try {
3933
+ const voluteJsonPath = resolve9(dir, "home/.config/volute.json");
3934
+ let config2 = {};
3935
+ if (existsSync6(voluteJsonPath)) {
3936
+ const parsed = readVoluteConfig(dir);
3937
+ if (!parsed) {
3938
+ warn("dreaming setup: volute.json is unparseable \u2014 dream schedule not installed");
3939
+ return { schedulesChanged: false, warnings };
3940
+ }
3941
+ config2 = parsed;
3942
+ }
3943
+ const schedules = config2.schedules ?? [];
3944
+ if (!schedules.some((s) => s.id === "dream")) {
3945
+ schedules.push(defaultDreamSchedule());
3946
+ config2.schedules = schedules;
3947
+ writeVoluteConfig(dir, config2);
3948
+ schedulesChanged = true;
3949
+ }
3950
+ } catch (err) {
3951
+ warn("dreaming setup: failed to add default dream schedule", err);
3952
+ }
3953
+ return { schedulesChanged, warnings };
3954
+ }
3955
+
3775
3956
  // packages/daemon/src/lib/mind/last-known-good.ts
3776
3957
  import { randomBytes as randomBytes2 } from "crypto";
3777
3958
  var glog = logger_default.child("last-known-good");
@@ -3805,50 +3986,45 @@ async function commitSrcChanges(dir, mindName) {
3805
3986
  glog.info(`committed known-good src changes (${dir})`);
3806
3987
  }
3807
3988
 
3808
- // packages/daemon/src/lib/mind/variant-cleanup.ts
3809
- import { existsSync as existsSync6, rmSync as rmSync3 } from "fs";
3810
- async function cleanupVariant(variantName, projectRoot, variantPath, opts) {
3811
- if (opts?.stop) {
3812
- try {
3813
- await getMindManager().stopMind(variantName);
3814
- } catch (err) {
3815
- logger_default.warn(`failed to stop variant ${variantName}`, logger_default.errorData(err));
3816
- }
3817
- }
3818
- const { findMind: findMind2 } = await import("./registry-UMMDRPRM.js");
3819
- const variantEntry = await findMind2(variantName);
3820
- const branchName = variantEntry?.branch ?? variantName;
3821
- if (existsSync6(variantPath)) {
3822
- try {
3823
- await gitExec(["worktree", "remove", "--force", variantPath], { cwd: projectRoot });
3824
- } catch {
3825
- rmSync3(variantPath, { recursive: true, force: true });
3826
- try {
3827
- await gitExec(["worktree", "prune"], { cwd: projectRoot });
3828
- } catch (err) {
3829
- logger_default.warn(`failed to prune worktrees for ${variantName}`, logger_default.errorData(err));
3830
- }
3989
+ // packages/daemon/src/lib/mind/thinking-config.ts
3990
+ var CLAUDE_EFFORT = {
3991
+ minimal: "low",
3992
+ low: "low",
3993
+ medium: "medium",
3994
+ high: "high",
3995
+ xhigh: "xhigh"
3996
+ };
3997
+ var CODEX_EFFORT = {
3998
+ minimal: "minimal",
3999
+ low: "low",
4000
+ medium: "medium",
4001
+ high: "high",
4002
+ xhigh: "xhigh"
4003
+ };
4004
+ function applyThinkingLevel(config2, template, level) {
4005
+ if (template === "claude") {
4006
+ if (level === "off") {
4007
+ config2.thinking = { type: "disabled" };
4008
+ delete config2.effort;
4009
+ } else {
4010
+ config2.effort = CLAUDE_EFFORT[level];
4011
+ delete config2.thinking;
3831
4012
  }
4013
+ } else if (template === "codex") {
4014
+ if (level === "off") delete config2.reasoningEffort;
4015
+ else config2.reasoningEffort = CODEX_EFFORT[level];
4016
+ } else {
4017
+ config2.thinkingLevel = level;
3832
4018
  }
3833
- try {
3834
- await gitExec(["branch", "-D", branchName], { cwd: projectRoot });
3835
- } catch (err) {
3836
- logger_default.warn(`failed to delete branch ${branchName} for ${variantName}`, logger_default.errorData(err));
3837
- }
3838
- const baseName = variantEntry?.parent ?? variantName;
3839
- try {
3840
- await removeMind(variantName);
3841
- } catch (err) {
3842
- logger_default.warn(`failed to remove variant ${variantName} from DB`, logger_default.errorData(err));
3843
- }
3844
- try {
3845
- await chownMindDir(projectRoot, baseName);
3846
- } catch (err) {
3847
- logger_default.error(
3848
- `failed to fix ownership during variant cleanup for ${variantName}`,
3849
- logger_default.errorData(err)
3850
- );
3851
- }
4019
+ }
4020
+ function deriveThinkingLevel(config2) {
4021
+ if (config2.thinkingLevel) return config2.thinkingLevel;
4022
+ if (config2.reasoningEffort) return config2.reasoningEffort;
4023
+ const thinking = config2.thinking;
4024
+ if (thinking?.type === "disabled") return "off";
4025
+ const effort = config2.effort;
4026
+ if (effort) return effort === "max" ? "xhigh" : effort;
4027
+ return null;
3852
4028
  }
3853
4029
 
3854
4030
  // packages/daemon/src/lib/mind/variants.ts
@@ -3862,14 +4038,95 @@ function validateBranchName(branch) {
3862
4038
  }
3863
4039
  return null;
3864
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 = "";
4046
+ try {
4047
+ const base = (await gitExec(["merge-base", "HEAD", variantBranch], opts)).trim();
4048
+ memoryDelta = (await gitExec(["diff", `${base}..${variantBranch}`, "--", ...MERGE_EXCLUDED_PATHS], opts)).trim();
4049
+ } catch (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)})`;
4055
+ }
4056
+ if (memoryDelta.length > MAX_DELTA_CHARS) {
4057
+ memoryDelta = `${memoryDelta.slice(0, MAX_DELTA_CHARS)}
4058
+ \u2026 (delta truncated)`;
4059
+ }
4060
+ try {
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
+ }
4073
+ } catch (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
4083
+ );
4084
+ }
4085
+ return memoryDelta;
4086
+ }
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";
4093
+ }
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
+ );
4120
+ }
4121
+ }
3865
4122
 
3866
4123
  // packages/daemon/src/lib/template/convert-session.ts
3867
4124
  import { randomUUID } from "crypto";
3868
- import { mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
4125
+ import { mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync7 } from "fs";
3869
4126
  import { homedir } from "os";
3870
- import { resolve as resolve9 } from "path";
4127
+ import { resolve as resolve10 } from "path";
3871
4128
  function convertSession(opts) {
3872
- const lines = readFileSync7(opts.sessionPath, "utf-8").trim().split("\n");
4129
+ const lines = readFileSync8(opts.sessionPath, "utf-8").trim().split("\n");
3873
4130
  const sessionId = randomUUID();
3874
4131
  const idMap = /* @__PURE__ */ new Map();
3875
4132
  const messages = [];
@@ -3983,10 +4240,10 @@ function convertSession(opts) {
3983
4240
  }
3984
4241
  }
3985
4242
  const projectId = opts.projectDir.replace(/\//g, "-");
3986
- const sdkDir = resolve9(homedir(), ".claude", "projects", projectId);
4243
+ const sdkDir = resolve10(homedir(), ".claude", "projects", projectId);
3987
4244
  mkdirSync5(sdkDir, { recursive: true });
3988
- const sdkPath = resolve9(sdkDir, `${sessionId}.jsonl`);
3989
- writeFileSync6(sdkPath, `${sdkEvents.join("\n")}
4245
+ const sdkPath = resolve10(sdkDir, `${sessionId}.jsonl`);
4246
+ writeFileSync7(sdkPath, `${sdkEvents.join("\n")}
3990
4247
  `);
3991
4248
  console.log(`Converted ${sdkEvents.length} messages \u2192 ${sdkPath}`);
3992
4249
  return sessionId;
@@ -4043,12 +4300,17 @@ var _LAST_ACTIVE_TTL = 6e4;
4043
4300
  async function getMindStatus(name, port, registryRunning) {
4044
4301
  const manager = getMindManager();
4045
4302
  let status = "stopped";
4303
+ let wakeAt = null;
4046
4304
  try {
4047
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-3NA7ZJP3.js");
4048
- if (getSleepManagerIfReady2()?.isSleeping(name)) {
4305
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-DSPXFHGX.js");
4306
+ const sleepManager = getSleepManagerIfReady2();
4307
+ if (sleepManager?.isSleeping(name)) {
4049
4308
  status = "sleeping";
4309
+ const sleepState = sleepManager.getState(name);
4310
+ wakeAt = sleepState.voluntaryWakeAt ?? sleepState.scheduledWakeAt;
4050
4311
  }
4051
- } catch {
4312
+ } catch (err) {
4313
+ logger_default.warn(`failed to check sleep state for ${name}`, logger_default.errorData(err));
4052
4314
  }
4053
4315
  if (status !== "sleeping" && registryRunning !== false && manager.isRunning(name)) {
4054
4316
  const health = await checkHealth(port);
@@ -4064,8 +4326,11 @@ async function getMindStatus(name, port, registryRunning) {
4064
4326
  status: status === "running" ? "connected" : "disconnected"
4065
4327
  });
4066
4328
  }
4329
+ const lastError = await latestFailureNotice(name);
4067
4330
  return {
4068
4331
  status,
4332
+ wakeAt,
4333
+ lastError,
4069
4334
  channels: channels2,
4070
4335
  displayName: config2?.profile?.displayName,
4071
4336
  description: config2?.profile?.description,
@@ -4083,6 +4348,10 @@ function toPublicMind(entry, status, extras) {
4083
4348
  stage: entry.stage,
4084
4349
  mindType: entry.mindType,
4085
4350
  status: status.status,
4351
+ wakeAt: status.wakeAt,
4352
+ // Kind/reason/at only — `detail` can embed the raw error string (unknown
4353
+ // classifications), which is mind-private (served behind requireSelf).
4354
+ lastError: status.lastError ? { kind: status.lastError.kind, reason: status.lastError.reason, at: status.lastError.at } : null,
4086
4355
  channels: status.channels,
4087
4356
  displayName: status.displayName,
4088
4357
  description: status.description,
@@ -4109,7 +4378,7 @@ async function initTemplateBranch(projectRoot, composedDir, manifest, mindName,
4109
4378
  await gitExec(["commit", "-m", "initial commit"], opts);
4110
4379
  }
4111
4380
  async function updateTemplateBranch(projectRoot, template, mindName) {
4112
- const tempWorktree = resolve10(projectRoot, ".variants", "_template_update");
4381
+ const tempWorktree = resolve11(projectRoot, ".variants", "_template_update");
4113
4382
  let branchExists = false;
4114
4383
  try {
4115
4384
  await gitExec(["rev-parse", "--verify", TEMPLATE_BRANCH], { cwd: projectRoot });
@@ -4121,7 +4390,7 @@ async function updateTemplateBranch(projectRoot, template, mindName) {
4121
4390
  } catch {
4122
4391
  }
4123
4392
  if (existsSync7(tempWorktree)) {
4124
- rmSync4(tempWorktree, { recursive: true, force: true });
4393
+ rmSync3(tempWorktree, { recursive: true, force: true });
4125
4394
  }
4126
4395
  const templatesRoot = findTemplatesRoot();
4127
4396
  const { composedDir, manifest } = composeTemplate(templatesRoot, template);
@@ -4141,15 +4410,15 @@ async function updateTemplateBranch(projectRoot, template, mindName) {
4141
4410
  });
4142
4411
  }
4143
4412
  copyTemplateToDir(composedDir, tempWorktree, mindName, manifest);
4144
- const initDir = resolve10(tempWorktree, ".init");
4413
+ const initDir = resolve11(tempWorktree, ".init");
4145
4414
  if (existsSync7(initDir)) {
4146
- rmSync4(initDir, { recursive: true, force: true });
4415
+ rmSync3(initDir, { recursive: true, force: true });
4147
4416
  }
4148
- const homeDir = resolve10(tempWorktree, "home");
4417
+ const homeDir = resolve11(tempWorktree, "home");
4149
4418
  if (existsSync7(homeDir)) {
4150
4419
  for (const entry of readdirSync2(homeDir)) {
4151
4420
  if (entry !== "VOLUTE.md") {
4152
- rmSync4(resolve10(homeDir, entry), { recursive: true, force: true });
4421
+ rmSync3(resolve11(homeDir, entry), { recursive: true, force: true });
4153
4422
  }
4154
4423
  }
4155
4424
  }
@@ -4165,9 +4434,9 @@ async function updateTemplateBranch(projectRoot, template, mindName) {
4165
4434
  } catch {
4166
4435
  }
4167
4436
  if (existsSync7(tempWorktree)) {
4168
- rmSync4(tempWorktree, { recursive: true, force: true });
4437
+ rmSync3(tempWorktree, { recursive: true, force: true });
4169
4438
  }
4170
- rmSync4(composedDir, { recursive: true, force: true });
4439
+ rmSync3(composedDir, { recursive: true, force: true });
4171
4440
  }
4172
4441
  }
4173
4442
  async function mergeTemplateBranch(worktreeDir) {
@@ -4190,7 +4459,7 @@ async function mergeTemplateBranch(worktreeDir) {
4190
4459
  async function npmInstallAsMind(cwd, mindName) {
4191
4460
  if (isIsolationEnabled()) {
4192
4461
  const [cmd, args] = await wrapForIsolation("npm", ["install"], mindName);
4193
- await exec(cmd, args, { cwd, env: { ...process.env, HOME: resolve10(cwd, "home") } });
4462
+ await exec(cmd, args, { cwd, env: { ...process.env, HOME: resolve11(cwd, "home") } });
4194
4463
  } else {
4195
4464
  await exec("npm", ["install"], { cwd });
4196
4465
  }
@@ -4215,7 +4484,8 @@ async function mergeUpgradeAndRestart(mindName, dir, worktreeDir, upgradeVariant
4215
4484
  let switchWarning;
4216
4485
  if (templateChanged) {
4217
4486
  try {
4218
- applyTemplateHomeFiles(resolve10(dir, "home"), template);
4487
+ applyTemplateHomeFiles(resolve11(dir, "home"), template);
4488
+ const migratedSkills = migrateSkillsToTemplate(dir, oldTemplate, template);
4219
4489
  await gitExec(["add", "home/"], { cwd: dir });
4220
4490
  try {
4221
4491
  await gitExec(["diff", "--cached", "--quiet"], { cwd: dir });
@@ -4225,7 +4495,8 @@ async function mergeUpgradeAndRestart(mindName, dir, worktreeDir, upgradeVariant
4225
4495
  });
4226
4496
  }
4227
4497
  await chownMindDir(dir, mindName);
4228
- 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}`;
4229
4500
  } catch (err) {
4230
4501
  logger_default.warn(`failed to swap template home files for ${mindName}`, logger_default.errorData(err));
4231
4502
  return {
@@ -4265,7 +4536,7 @@ async function mergeUpgradeAndRestart(mindName, dir, worktreeDir, upgradeVariant
4265
4536
  return { ok: true, warning: switchWarning };
4266
4537
  }
4267
4538
  async function importFromArchive(c, tempDir, nameOverride, manifest) {
4268
- const extractedMindDir = resolve10(tempDir, "mind");
4539
+ const extractedMindDir = resolve11(tempDir, "mind");
4269
4540
  if (!existsSync7(extractedMindDir)) {
4270
4541
  return c.json({ error: "Invalid archive: missing mind/ directory" }, 400);
4271
4542
  }
@@ -4292,9 +4563,9 @@ async function importFromFullArchive(c, tempDir, extractedMindDir, nameOverride,
4292
4563
  }
4293
4564
  const state = stateDir(name);
4294
4565
  mkdirSync6(state, { recursive: true });
4295
- const envJson = resolve10(tempDir, "state/env.json");
4566
+ const envJson = resolve11(tempDir, "state/env.json");
4296
4567
  if (existsSync7(envJson)) {
4297
- cpSync(envJson, resolve10(state, "env.json"));
4568
+ cpSync(envJson, resolve11(state, "env.json"));
4298
4569
  }
4299
4570
  const port = await nextPort();
4300
4571
  await addMind(name, port, manifest.stage, manifest.template);
@@ -4303,36 +4574,36 @@ async function importFromFullArchive(c, tempDir, extractedMindDir, nameOverride,
4303
4574
  } catch (err) {
4304
4575
  logger_default.warn(`failed to set template hash for ${name}`, logger_default.errorData(err));
4305
4576
  }
4306
- const homeDir = resolve10(dest, "home");
4577
+ const homeDir = resolve11(dest, "home");
4307
4578
  ensureVoluteGroup();
4308
4579
  createMindUser(name, homeDir);
4309
4580
  await chownMindDir(dest, name);
4310
4581
  await npmInstallAsMind(dest, name);
4311
4582
  await importHistoryFromArchive(name, tempDir);
4312
4583
  importSessionsFromArchive(dest, tempDir);
4313
- if (!existsSync7(resolve10(dest, ".git"))) {
4584
+ if (!existsSync7(resolve11(dest, ".git"))) {
4314
4585
  try {
4315
- const env = isIsolationEnabled() ? { ...process.env, HOME: resolve10(dest, "home") } : void 0;
4586
+ const env = isIsolationEnabled() ? { ...process.env, HOME: resolve11(dest, "home") } : void 0;
4316
4587
  await gitExec(["init"], { cwd: dest, mindName: name, env });
4317
4588
  await configureGitIdentity(name, { cwd: dest, mindName: name, env });
4318
4589
  await gitExec(["add", "-A"], { cwd: dest, mindName: name, env });
4319
4590
  await gitExec(["commit", "-m", "import from archive"], { cwd: dest, mindName: name, env });
4320
4591
  } catch (err) {
4321
4592
  logger_default.error(`git setup failed for imported mind ${name}`, logger_default.errorData(err));
4322
- rmSync4(resolve10(dest, ".git"), { recursive: true, force: true });
4593
+ rmSync3(resolve11(dest, ".git"), { recursive: true, force: true });
4323
4594
  }
4324
4595
  }
4325
4596
  await chownMindDir(dest, name);
4326
- rmSync4(tempDir, { recursive: true, force: true });
4597
+ rmSync3(tempDir, { recursive: true, force: true });
4327
4598
  return c.json({ ok: true, name, port, message: `Imported mind: ${name} (port ${port})` });
4328
4599
  } catch (err) {
4329
- if (existsSync7(dest)) rmSync4(dest, { recursive: true, force: true });
4600
+ if (existsSync7(dest)) rmSync3(dest, { recursive: true, force: true });
4330
4601
  try {
4331
4602
  await removeMind(name);
4332
4603
  } catch (cleanupErr) {
4333
4604
  logger_default.error(`Failed to clean up registry for ${name}`, logger_default.errorData(cleanupErr));
4334
4605
  }
4335
- rmSync4(tempDir, { recursive: true, force: true });
4606
+ rmSync3(tempDir, { recursive: true, force: true });
4336
4607
  return c.json({ error: err instanceof Error ? err.message : "Failed to import mind" }, 500);
4337
4608
  }
4338
4609
  }
@@ -4352,36 +4623,36 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
4352
4623
  try {
4353
4624
  copyTemplateToDir(composedDir, dest, name, templateManifest);
4354
4625
  applyInitFiles(dest);
4355
- const extractedHome = resolve10(extractedMindDir, "home");
4626
+ const extractedHome = resolve11(extractedMindDir, "home");
4356
4627
  if (existsSync7(extractedHome)) {
4357
- cpSync(extractedHome, resolve10(dest, "home"), { recursive: true });
4628
+ cpSync(extractedHome, resolve11(dest, "home"), { recursive: true });
4358
4629
  }
4359
- const extractedMindInternal = resolve10(extractedMindDir, ".mind");
4630
+ const extractedMindInternal = resolve11(extractedMindDir, ".mind");
4360
4631
  if (existsSync7(extractedMindInternal)) {
4361
- cpSync(extractedMindInternal, resolve10(dest, ".mind"), { recursive: true });
4632
+ cpSync(extractedMindInternal, resolve11(dest, ".mind"), { recursive: true });
4362
4633
  }
4363
- const identityDir = resolve10(dest, ".mind/identity");
4634
+ const identityDir = resolve11(dest, ".mind/identity");
4364
4635
  let publicKeyPem;
4365
- if (!manifest.includes.identity || !existsSync7(resolve10(identityDir, "private.pem"))) {
4636
+ if (!manifest.includes.identity || !existsSync7(resolve11(identityDir, "private.pem"))) {
4366
4637
  ({ publicKeyPem } = generateIdentity(dest));
4367
4638
  } else {
4368
- publicKeyPem = readFileSync8(resolve10(identityDir, "public.pem"), "utf-8");
4639
+ publicKeyPem = readFileSync9(resolve11(identityDir, "public.pem"), "utf-8");
4369
4640
  }
4370
- const promptsPath = resolve10(dest, "home/.config/prompts.json");
4641
+ const promptsPath = resolve11(dest, "home/.config/prompts.json");
4371
4642
  if (!existsSync7(promptsPath)) {
4372
4643
  const mindPrompts = await getMindPromptDefaults();
4373
- writeFileSync7(promptsPath, `${JSON.stringify(mindPrompts, null, 2)}
4644
+ writeFileSync8(promptsPath, `${JSON.stringify(mindPrompts, null, 2)}
4374
4645
  `);
4375
4646
  }
4376
4647
  const state = stateDir(name);
4377
4648
  mkdirSync6(state, { recursive: true });
4378
- const envJson = resolve10(tempDir, "state/env.json");
4649
+ const envJson = resolve11(tempDir, "state/env.json");
4379
4650
  if (existsSync7(envJson)) {
4380
- cpSync(envJson, resolve10(state, "env.json"));
4651
+ cpSync(envJson, resolve11(state, "env.json"));
4381
4652
  }
4382
4653
  const port = await nextPort();
4383
4654
  await addMind(name, port, manifest.stage, manifest.template);
4384
- const homeDir = resolve10(dest, "home");
4655
+ const homeDir = resolve11(dest, "home");
4385
4656
  ensureVoluteGroup();
4386
4657
  createMindUser(name, homeDir);
4387
4658
  await chownMindDir(dest, name);
@@ -4394,7 +4665,7 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
4394
4665
  await initTemplateBranch(dest, composedDir, templateManifest, name, env);
4395
4666
  } catch (err) {
4396
4667
  logger_default.error(`git setup failed for imported mind ${name}`, logger_default.errorData(err));
4397
- rmSync4(resolve10(dest, ".git"), { recursive: true, force: true });
4668
+ rmSync3(resolve11(dest, ".git"), { recursive: true, force: true });
4398
4669
  gitWarning = "Git setup failed \u2014 variants and upgrades won't be available until git is initialized.";
4399
4670
  }
4400
4671
  const skillSet = manifest.stage === "seed" ? SEED_SKILLS : getStandardSkillsWithExtensions();
@@ -4413,7 +4684,7 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
4413
4684
  publishPublicKey(name, publicKeyPem).catch(
4414
4685
  (err) => logger_default.warn(`failed to publish key for ${name}`, { error: err.message })
4415
4686
  );
4416
- rmSync4(tempDir, { recursive: true, force: true });
4687
+ rmSync3(tempDir, { recursive: true, force: true });
4417
4688
  return c.json({
4418
4689
  ok: true,
4419
4690
  name,
@@ -4424,24 +4695,24 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
4424
4695
  ...skillWarnings.length > 0 && { skillWarnings }
4425
4696
  });
4426
4697
  } catch (err) {
4427
- if (existsSync7(dest)) rmSync4(dest, { recursive: true, force: true });
4698
+ if (existsSync7(dest)) rmSync3(dest, { recursive: true, force: true });
4428
4699
  try {
4429
4700
  await removeMind(name);
4430
4701
  } catch (cleanupErr) {
4431
4702
  logger_default.error(`Failed to clean up registry for ${name}`, logger_default.errorData(cleanupErr));
4432
4703
  }
4433
- rmSync4(tempDir, { recursive: true, force: true });
4704
+ rmSync3(tempDir, { recursive: true, force: true });
4434
4705
  return c.json({ error: err instanceof Error ? err.message : "Failed to import mind" }, 500);
4435
4706
  } finally {
4436
- rmSync4(composedDir, { recursive: true, force: true });
4707
+ rmSync3(composedDir, { recursive: true, force: true });
4437
4708
  }
4438
4709
  }
4439
4710
  async function importHistoryFromArchive(name, tempDir) {
4440
- const historyJsonl = resolve10(tempDir, "history.jsonl");
4711
+ const historyJsonl = resolve11(tempDir, "history.jsonl");
4441
4712
  if (!existsSync7(historyJsonl)) return;
4442
4713
  try {
4443
4714
  const db = await getDb();
4444
- const lines = readFileSync8(historyJsonl, "utf-8").trim().split("\n");
4715
+ const lines = readFileSync9(historyJsonl, "utf-8").trim().split("\n");
4445
4716
  let imported = 0;
4446
4717
  let failed = 0;
4447
4718
  for (const line of lines) {
@@ -4477,13 +4748,13 @@ async function importHistoryFromArchive(name, tempDir) {
4477
4748
  }
4478
4749
  }
4479
4750
  function importSessionsFromArchive(dest, tempDir) {
4480
- const sessionsDir = resolve10(tempDir, "sessions");
4751
+ const sessionsDir = resolve11(tempDir, "sessions");
4481
4752
  if (!existsSync7(sessionsDir)) return;
4482
4753
  try {
4483
- const destSessions = resolve10(dest, ".mind/sessions");
4754
+ const destSessions = resolve11(dest, ".mind/sessions");
4484
4755
  mkdirSync6(destSessions, { recursive: true });
4485
4756
  for (const file of readdirSync2(sessionsDir)) {
4486
- cpSync(resolve10(sessionsDir, file), resolve10(destSessions, file));
4757
+ cpSync(resolve11(sessionsDir, file), resolve11(destSessions, file));
4487
4758
  }
4488
4759
  } catch (err) {
4489
4760
  logger_default.error("Failed to import sessions from archive", logger_default.errorData(err));
@@ -4548,6 +4819,11 @@ var app13 = new Hono16().post("/", requireAdminOrSystem, zValidator6("json", cre
4548
4819
  const nameErr = validateMindName(name);
4549
4820
  if (nameErr) return c.json({ error: nameErr }, 400);
4550
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
+ }
4551
4827
  ensureVoluteHome();
4552
4828
  const dest = mindDir(name);
4553
4829
  if (existsSync7(dest)) return c.json({ error: "Mind directory already exists" }, 409);
@@ -4557,8 +4833,9 @@ var app13 = new Hono16().post("/", requireAdminOrSystem, zValidator6("json", cre
4557
4833
  copyTemplateToDir(composedDir, dest, name, manifest);
4558
4834
  applyInitFiles(dest);
4559
4835
  const { publicKeyPem } = generateIdentity(dest);
4836
+ let effectiveModel = body.model;
4560
4837
  {
4561
- const { readGlobalConfig: readGlobal } = await import("./setup-KFYVWTXG.js");
4838
+ const { readGlobalConfig: readGlobal } = await import("./setup-TCN7VH5U.js");
4562
4839
  const mindDefaults = readGlobal().mindDefaults;
4563
4840
  const config2 = readVoluteConfig(dest);
4564
4841
  if (!config2) throw new Error("Failed to read volute.json after identity generation");
@@ -4572,22 +4849,12 @@ var app13 = new Hono16().post("/", requireAdminOrSystem, zValidator6("json", cre
4572
4849
  };
4573
4850
  }
4574
4851
  if (!config2.schedules || config2.schedules.length === 0) {
4575
- config2.schedules = mindDefaults?.schedules ?? [
4576
- {
4577
- id: "heartbeat",
4578
- cron: "0 12,16,20 * * *",
4579
- message: "A quiet moment. You might write something \u2014 a note, a journal entry, a page. You could explore a topic that interests you, check in on #system, or just think. No obligations, just time.",
4580
- enabled: true,
4581
- whileSleeping: "skip"
4582
- }
4583
- ];
4852
+ config2.schedules = mindDefaults?.schedules ?? [defaultHeartbeatSchedule()];
4584
4853
  }
4585
4854
  const cog = mindDefaults?.cognition;
4586
4855
  if (cog) {
4587
4856
  if (cog.thinkingLevel != null && !config2.thinkingLevel)
4588
4857
  config2.thinkingLevel = cog.thinkingLevel;
4589
- if (cog.maxThinkingTokens != null && config2.maxThinkingTokens == null)
4590
- config2.maxThinkingTokens = cog.maxThinkingTokens;
4591
4858
  if (cog.tokenBudget != null && config2.tokenBudget == null)
4592
4859
  config2.tokenBudget = cog.tokenBudget;
4593
4860
  if (cog.tokenBudgetPeriodMinutes != null && config2.tokenBudgetPeriodMinutes == null)
@@ -4595,22 +4862,23 @@ var app13 = new Hono16().post("/", requireAdminOrSystem, zValidator6("json", cre
4595
4862
  }
4596
4863
  writeVoluteConfig(dest, config2);
4597
4864
  const modelId = body.model ?? cog?.model;
4598
- const sdkConfigPath = resolve10(dest, "home/.config/config.json");
4865
+ effectiveModel = modelId ? qualifyModelId(modelId) : void 0;
4866
+ const sdkConfigPath = resolve11(dest, "home/.config/config.json");
4599
4867
  if (modelId || cog?.compaction) {
4600
- const existing = existsSync7(sdkConfigPath) ? JSON.parse(readFileSync8(sdkConfigPath, "utf-8")) : {};
4868
+ const existing = existsSync7(sdkConfigPath) ? JSON.parse(readFileSync9(sdkConfigPath, "utf-8")) : {};
4601
4869
  if (modelId) {
4602
4870
  existing.model = template === "pi" ? qualifyModelId(modelId) : unqualifyModelId(modelId);
4603
4871
  }
4604
4872
  if (cog?.compaction && !existing.compaction) {
4605
4873
  existing.compaction = cog.compaction;
4606
4874
  }
4607
- writeFileSync7(sdkConfigPath, `${JSON.stringify(existing, null, 2)}
4875
+ writeFileSync8(sdkConfigPath, `${JSON.stringify(existing, null, 2)}
4608
4876
  `);
4609
4877
  }
4610
4878
  }
4611
4879
  const mindPrompts = await getMindPromptDefaults();
4612
- writeFileSync7(
4613
- resolve10(dest, "home/.config/prompts.json"),
4880
+ writeFileSync8(
4881
+ resolve11(dest, "home/.config/prompts.json"),
4614
4882
  `${JSON.stringify(mindPrompts, null, 2)}
4615
4883
  `
4616
4884
  );
@@ -4622,7 +4890,7 @@ var app13 = new Hono16().post("/", requireAdminOrSystem, zValidator6("json", cre
4622
4890
  } catch (err) {
4623
4891
  logger_default.warn(`failed to set template hash for ${name}`, logger_default.errorData(err));
4624
4892
  }
4625
- const homeDir = resolve10(dest, "home");
4893
+ const homeDir = resolve11(dest, "home");
4626
4894
  ensureVoluteGroup();
4627
4895
  createMindUser(name, homeDir);
4628
4896
  await chownMindDir(dest, name);
@@ -4635,7 +4903,7 @@ var app13 = new Hono16().post("/", requireAdminOrSystem, zValidator6("json", cre
4635
4903
  await initTemplateBranch(dest, composedDir, manifest, name, env);
4636
4904
  } catch (err) {
4637
4905
  logger_default.error(`git setup failed for ${name}`, logger_default.errorData(err));
4638
- rmSync4(resolve10(dest, ".git"), { recursive: true, force: true });
4906
+ rmSync3(resolve11(dest, ".git"), { recursive: true, force: true });
4639
4907
  gitWarning = "Git setup failed \u2014 variants and upgrades won't be available until git is initialized.";
4640
4908
  }
4641
4909
  if (body.stage === "seed") {
@@ -4644,11 +4912,11 @@ Your creator described you as: "${body.description}"
4644
4912
  ` : "";
4645
4913
  const seedSoulRaw = body.seedSoul ?? await getPrompt("seed_soul", { name, description: descLine });
4646
4914
  const seedSoul = body.seedSoul ? substitute(seedSoulRaw, { name, description: descLine }) : seedSoulRaw;
4647
- writeFileSync7(resolve10(dest, "home/SOUL.md"), seedSoul);
4915
+ writeFileSync8(resolve11(dest, "home/SOUL.md"), seedSoul);
4648
4916
  }
4649
4917
  let skillSet = body.skills ?? (body.stage === "seed" ? SEED_SKILLS : getStandardSkillsWithExtensions());
4650
4918
  if (body.stage === "seed" && !body.skills) {
4651
- const { isImagegenEnabled } = await import("./setup-KFYVWTXG.js");
4919
+ const { isImagegenEnabled } = await import("./setup-TCN7VH5U.js");
4652
4920
  if (isImagegenEnabled()) {
4653
4921
  skillSet = [...skillSet, "imagegen"];
4654
4922
  }
@@ -4662,11 +4930,14 @@ Your creator described you as: "${body.description}"
4662
4930
  skillWarnings.push(`Failed to install skill: ${skillId}`);
4663
4931
  }
4664
4932
  }
4933
+ if (body.stage !== "seed") {
4934
+ skillWarnings.push(...setupDefaultDreaming(dest).warnings);
4935
+ }
4665
4936
  if (body.stage === "seed") {
4666
4937
  try {
4667
4938
  const spiritEntry = await findMind("volute");
4668
4939
  if (spiritEntry) {
4669
- const { spiritDir } = await import("./spirit-WXN4IDBF.js");
4940
+ const { spiritDir } = await import("./spirit-SSJMYC6A.js");
4670
4941
  const sDir = spiritEntry.dir ?? spiritDir();
4671
4942
  const spiritConfig = readVoluteConfig(sDir) ?? {};
4672
4943
  const schedules = spiritConfig.schedules ?? [];
@@ -4681,7 +4952,7 @@ Your creator described you as: "${body.description}"
4681
4952
  });
4682
4953
  spiritConfig.schedules = schedules;
4683
4954
  writeVoluteConfig(sDir, spiritConfig);
4684
- const { getScheduler: getScheduler2 } = await import("./scheduler-HQSDYJFY.js");
4955
+ const { getScheduler: getScheduler2 } = await import("./scheduler-K7Y7Z7FQ.js");
4685
4956
  getScheduler2().loadSchedules("volute", sDir);
4686
4957
  }
4687
4958
  }
@@ -4692,11 +4963,11 @@ Your creator described you as: "${body.description}"
4692
4963
  if (body.stage !== "seed") {
4693
4964
  const customSoul = await getPromptIfCustom("default_soul");
4694
4965
  if (customSoul) {
4695
- writeFileSync7(resolve10(dest, "home/SOUL.md"), customSoul.replace(/\{\{name\}\}/g, name));
4966
+ writeFileSync8(resolve11(dest, "home/SOUL.md"), customSoul.replace(/\{\{name\}\}/g, name));
4696
4967
  }
4697
4968
  const customMemory = await getPromptIfCustom("default_memory");
4698
4969
  if (customMemory) {
4699
- writeFileSync7(resolve10(dest, "home/MEMORY.md"), customMemory);
4970
+ writeFileSync8(resolve11(dest, "home/MEMORY.md"), customMemory);
4700
4971
  }
4701
4972
  }
4702
4973
  await chownMindDir(dest, name);
@@ -4716,6 +4987,14 @@ Your creator described you as: "${body.description}"
4716
4987
  });
4717
4988
  announceToSystem(`${name} has joined`).catch(() => {
4718
4989
  });
4990
+ const credentialWarning = await missingCredentialWarning(
4991
+ template,
4992
+ effectiveModel,
4993
+ name
4994
+ ).catch((err) => {
4995
+ logger_default.warn(`credential check failed for ${name}`, logger_default.errorData(err));
4996
+ return null;
4997
+ });
4719
4998
  return c.json({
4720
4999
  ok: true,
4721
5000
  name,
@@ -4723,17 +5002,18 @@ Your creator described you as: "${body.description}"
4723
5002
  stage: body.stage ?? "sprouted",
4724
5003
  message: `Created mind: ${name} (port ${port})`,
4725
5004
  ...gitWarning && { warning: gitWarning },
5005
+ ...credentialWarning && { credentialWarning },
4726
5006
  ...skillWarnings.length > 0 && { skillWarnings }
4727
5007
  });
4728
5008
  } catch (err) {
4729
- if (existsSync7(dest)) rmSync4(dest, { recursive: true, force: true });
5009
+ if (existsSync7(dest)) rmSync3(dest, { recursive: true, force: true });
4730
5010
  try {
4731
5011
  await removeMind(name);
4732
5012
  } catch {
4733
5013
  }
4734
5014
  return c.json({ error: err instanceof Error ? err.message : "Failed to create mind" }, 500);
4735
5015
  } finally {
4736
- rmSync4(composedDir, { recursive: true, force: true });
5016
+ rmSync3(composedDir, { recursive: true, force: true });
4737
5017
  }
4738
5018
  }).post("/import", requireAdmin, async (c) => {
4739
5019
  let body;
@@ -4746,13 +5026,13 @@ Your creator described you as: "${body.description}"
4746
5026
  return importFromArchive(c, body.archivePath, body.name, body.manifest);
4747
5027
  }
4748
5028
  const wsDir = body.workspacePath;
4749
- if (!wsDir || !existsSync7(resolve10(wsDir, "SOUL.md")) || !existsSync7(resolve10(wsDir, "IDENTITY.md"))) {
5029
+ if (!wsDir || !existsSync7(resolve11(wsDir, "SOUL.md")) || !existsSync7(resolve11(wsDir, "IDENTITY.md"))) {
4750
5030
  return c.json({ error: "Invalid workspace: missing SOUL.md or IDENTITY.md" }, 400);
4751
5031
  }
4752
- const soul = readFileSync8(resolve10(wsDir, "SOUL.md"), "utf-8");
4753
- const identity = readFileSync8(resolve10(wsDir, "IDENTITY.md"), "utf-8");
4754
- const userPath = resolve10(wsDir, "USER.md");
4755
- const user = existsSync7(userPath) ? readFileSync8(userPath, "utf-8") : "";
5032
+ const soul = readFileSync9(resolve11(wsDir, "SOUL.md"), "utf-8");
5033
+ const identity = readFileSync9(resolve11(wsDir, "IDENTITY.md"), "utf-8");
5034
+ const userPath = resolve11(wsDir, "USER.md");
5035
+ const user = existsSync7(userPath) ? readFileSync9(userPath, "utf-8") : "";
4756
5036
  const name = body.name ?? parseNameFromIdentity(identity) ?? "imported-mind";
4757
5037
  const template = body.template ?? "claude";
4758
5038
  const nameErr = validateMindName(name);
@@ -4779,26 +5059,26 @@ ${user.trimEnd()}
4779
5059
  copyTemplateToDir(composedDir, dest, name, manifest);
4780
5060
  applyInitFiles(dest);
4781
5061
  const { publicKeyPem: importPublicKey } = generateIdentity(dest);
4782
- writeFileSync7(resolve10(dest, "home/SOUL.md"), mergedSoul);
4783
- const wsMemoryPath = resolve10(wsDir, "MEMORY.md");
5062
+ writeFileSync8(resolve11(dest, "home/SOUL.md"), mergedSoul);
5063
+ const wsMemoryPath = resolve11(wsDir, "MEMORY.md");
4784
5064
  const hasMemory = existsSync7(wsMemoryPath);
4785
5065
  if (hasMemory) {
4786
- const existingMemory = readFileSync8(wsMemoryPath, "utf-8");
4787
- writeFileSync7(
4788
- resolve10(dest, "home/MEMORY.md"),
5066
+ const existingMemory = readFileSync9(wsMemoryPath, "utf-8");
5067
+ writeFileSync8(
5068
+ resolve11(dest, "home/MEMORY.md"),
4789
5069
  `${existingMemory.trimEnd()}${mergedMemoryExtra}`
4790
5070
  );
4791
5071
  } else if (user) {
4792
- writeFileSync7(resolve10(dest, "home/MEMORY.md"), `${user.trimEnd()}
5072
+ writeFileSync8(resolve11(dest, "home/MEMORY.md"), `${user.trimEnd()}
4793
5073
  `);
4794
5074
  }
4795
- const wsMemoryDir = resolve10(wsDir, "memory");
5075
+ const wsMemoryDir = resolve11(wsDir, "memory");
4796
5076
  let dailyLogCount = 0;
4797
5077
  if (existsSync7(wsMemoryDir)) {
4798
- const destMemoryDir = resolve10(dest, "home/memory");
5078
+ const destMemoryDir = resolve11(dest, "home/memory");
4799
5079
  const files = readdirSync2(wsMemoryDir).filter((f) => f.endsWith(".md"));
4800
5080
  for (const file of files) {
4801
- cpSync(resolve10(wsMemoryDir, file), resolve10(destMemoryDir, file));
5081
+ cpSync(resolve11(wsMemoryDir, file), resolve11(destMemoryDir, file));
4802
5082
  }
4803
5083
  dailyLogCount = files.length;
4804
5084
  }
@@ -4809,7 +5089,7 @@ ${user.trimEnd()}
4809
5089
  } catch (err) {
4810
5090
  logger_default.warn(`failed to set template hash for ${name}`, logger_default.errorData(err));
4811
5091
  }
4812
- const homeDir = resolve10(dest, "home");
5092
+ const homeDir = resolve11(dest, "home");
4813
5093
  ensureVoluteGroup();
4814
5094
  createMindUser(name, homeDir);
4815
5095
  await chownMindDir(dest, name);
@@ -4817,20 +5097,20 @@ ${user.trimEnd()}
4817
5097
  if (!hasMemory && dailyLogCount > 0) {
4818
5098
  await consolidateMemory(dest);
4819
5099
  }
4820
- const env = isIsolationEnabled() ? { ...process.env, HOME: resolve10(dest, "home") } : void 0;
5100
+ const env = isIsolationEnabled() ? { ...process.env, HOME: resolve11(dest, "home") } : void 0;
4821
5101
  await gitExec(["init"], { cwd: dest, mindName: name, env });
4822
5102
  await configureGitIdentity(name, { cwd: dest, mindName: name, env });
4823
5103
  await gitExec(["add", "-A"], { cwd: dest, mindName: name, env });
4824
5104
  await gitExec(["commit", "-m", "import from OpenClaw"], { cwd: dest, mindName: name, env });
4825
- const sessionFile = body.sessionPath ? resolve10(body.sessionPath) : findOpenClawSession(wsDir);
5105
+ const sessionFile = body.sessionPath ? resolve11(body.sessionPath) : findOpenClawSession(wsDir);
4826
5106
  if (sessionFile && existsSync7(sessionFile)) {
4827
5107
  if (template === "pi") {
4828
5108
  importPiSession(sessionFile, dest);
4829
5109
  } else if (template === "claude") {
4830
5110
  const sessionId = convertSession({ sessionPath: sessionFile, projectDir: dest });
4831
- const mindRuntimeDir = resolve10(dest, ".mind");
5111
+ const mindRuntimeDir = resolve11(dest, ".mind");
4832
5112
  mkdirSync6(mindRuntimeDir, { recursive: true });
4833
- writeFileSync7(resolve10(mindRuntimeDir, "session.json"), JSON.stringify({ sessionId }));
5113
+ writeFileSync8(resolve11(mindRuntimeDir, "session.json"), JSON.stringify({ sessionId }));
4834
5114
  }
4835
5115
  }
4836
5116
  importOpenClawConnectors(name, dest);
@@ -4840,14 +5120,14 @@ ${user.trimEnd()}
4840
5120
  );
4841
5121
  return c.json({ ok: true, name, port, message: `Imported mind: ${name} (port ${port})` });
4842
5122
  } catch (err) {
4843
- if (existsSync7(dest)) rmSync4(dest, { recursive: true, force: true });
5123
+ if (existsSync7(dest)) rmSync3(dest, { recursive: true, force: true });
4844
5124
  try {
4845
5125
  await removeMind(name);
4846
5126
  } catch {
4847
5127
  }
4848
5128
  return c.json({ error: err instanceof Error ? err.message : "Failed to import mind" }, 500);
4849
5129
  } finally {
4850
- rmSync4(composedDir, { recursive: true, force: true });
5130
+ rmSync3(composedDir, { recursive: true, force: true });
4851
5131
  }
4852
5132
  }).get("/", async (c) => {
4853
5133
  const entries = await readRegistry();
@@ -4872,7 +5152,7 @@ ${user.trimEnd()}
4872
5152
  const minds = await Promise.all(
4873
5153
  entries.map(async (entry) => {
4874
5154
  const mindStatus = await getMindStatus(entry.name, entry.port, entry.running);
4875
- const hasPages = existsSync7(resolve10(mindDir(entry.name), "home", "pages"));
5155
+ const hasPages = existsSync7(resolve11(mindDir(entry.name), "home", "pages"));
4876
5156
  const lastActiveAt = lastActiveMap.get(entry.name) ?? null;
4877
5157
  if (!privileged) return toPublicMind(entry, mindStatus, { hasPages, lastActiveAt });
4878
5158
  return {
@@ -4892,7 +5172,7 @@ ${user.trimEnd()}
4892
5172
  const dir = entry.dir ?? mindDir(entry.parent ?? name);
4893
5173
  if (!existsSync7(dir)) return c.json({ error: "Mind directory missing" }, 404);
4894
5174
  const mindStatus = await getMindStatus(name, entry.port);
4895
- const hasPages = existsSync7(resolve10(mindDir(name), "home", "pages"));
5175
+ const hasPages = existsSync7(resolve11(mindDir(name), "home", "pages"));
4896
5176
  if (!isPrivileged(c)) {
4897
5177
  return c.json(toPublicMind(entry, mindStatus, { hasPages }));
4898
5178
  }
@@ -4908,23 +5188,32 @@ ${user.trimEnd()}
4908
5188
  return { name: s.name, port: s.port, status: variantStatus };
4909
5189
  })
4910
5190
  );
5191
+ const notice = await latestNotice(name);
4911
5192
  return c.json({
4912
5193
  ...entry,
4913
5194
  ...mindStatus,
4914
5195
  variants: variantStatuses,
4915
5196
  hasPages,
4916
- templateStale: isTemplateStale(entry)
5197
+ templateStale: isTemplateStale(entry),
5198
+ ...notice && {
5199
+ lastNotice: {
5200
+ kind: notice.kind,
5201
+ reason: notice.reason,
5202
+ detail: notice.detail,
5203
+ created_at: notice.created_at
5204
+ }
5205
+ }
4917
5206
  });
4918
5207
  }).get("/:name/context", requireSelf(), async (c) => proxyToMind(c, "context")).get("/:name/context/messages", requireSelf(), async (c) => proxyToMind(c, "context/messages")).post("/:name/start", requireSelf(), async (c) => {
4919
5208
  const name = c.req.param("name");
4920
5209
  const entry = await findMind(name);
4921
5210
  if (!entry) return c.json({ error: "Mind not found" }, 404);
4922
5211
  const targetPort = entry.port;
5212
+ const projectDir = entry.dir ?? mindDir(name);
4923
5213
  if (entry.parent) {
4924
5214
  if (!entry.dir) return c.json({ error: `Variant ${name} has no directory` }, 404);
4925
- } else {
4926
- const dir = mindDir(name);
4927
- if (!existsSync7(dir)) return c.json({ error: "Mind directory missing" }, 404);
5215
+ } else if (!existsSync7(projectDir)) {
5216
+ return c.json({ error: "Mind directory missing" }, 404);
4928
5217
  }
4929
5218
  if (getMindManager().isRunning(name)) {
4930
5219
  return c.json({ error: "Mind already running" }, 409);
@@ -4941,11 +5230,11 @@ ${user.trimEnd()}
4941
5230
  if (!entry) return c.json({ error: "Mind not found" }, 404);
4942
5231
  const baseName = entry.parent ?? name;
4943
5232
  const targetPort = entry.port;
5233
+ const projectDir = entry.dir ?? mindDir(name);
4944
5234
  if (entry.parent) {
4945
5235
  if (!entry.dir) return c.json({ error: `Variant ${name} has no directory` }, 404);
4946
- } else {
4947
- const dir = mindDir(name);
4948
- if (!existsSync7(dir)) return c.json({ error: "Mind directory missing" }, 404);
5236
+ } else if (!existsSync7(projectDir)) {
5237
+ return c.json({ error: "Mind directory missing" }, 404);
4949
5238
  }
4950
5239
  let context;
4951
5240
  const contentType = c.req.header("content-type");
@@ -4960,7 +5249,7 @@ ${user.trimEnd()}
4960
5249
  const manager = getMindManager();
4961
5250
  try {
4962
5251
  if (context?.type === "reload") {
4963
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-3NA7ZJP3.js");
5252
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-DSPXFHGX.js");
4964
5253
  const sleepState = getSleepManagerIfReady2()?.getState(name);
4965
5254
  if (sleepState?.sleeping) {
4966
5255
  logger_default.info(`skipping reload for ${name} during sleep \u2014 will apply on next wake`);
@@ -5007,7 +5296,8 @@ ${user.trimEnd()}
5007
5296
  logger_default.error(`failed to auto-commit main worktree for ${baseName}`, logger_default.errorData(e));
5008
5297
  }
5009
5298
  }
5010
- await gitExec(["merge", variantEntry.branch], { cwd: projectRoot });
5299
+ const memoryDelta = await mergeVariantExcludingMemory(projectRoot, variantEntry.branch);
5300
+ if (memoryDelta && context) context.memoryDelta = memoryDelta;
5011
5301
  await cleanupVariant(mergeVariantName, projectRoot, variantEntry.dir);
5012
5302
  try {
5013
5303
  await npmInstallAsMind(projectRoot, baseName);
@@ -5021,18 +5311,12 @@ ${user.trimEnd()}
5021
5311
  }
5022
5312
  if (context?.type === "sprouted" && !entry.parent) {
5023
5313
  try {
5024
- const mindConvs = await listConversationsForMind(baseName);
5025
- for (const conv of mindConvs) {
5026
- await recordInbound(baseName, "system", "system", "[seed has sprouted]");
5027
- await addMessage(conv.id, "assistant", "system", [
5028
- { type: "text", text: "[seed has sprouted]" }
5029
- ]);
5030
- }
5314
+ await deliverSproutedNotice(baseName);
5031
5315
  } catch (err) {
5032
5316
  logger_default.error(`failed to inject sprouted message for ${baseName}`, logger_default.errorData(err));
5033
5317
  }
5034
5318
  }
5035
- const repoDir = entry.parent ? entry.dir : mindDir(name);
5319
+ const repoDir = projectDir;
5036
5320
  try {
5037
5321
  await startMindFull(name);
5038
5322
  } catch (startErr) {
@@ -5087,7 +5371,7 @@ ${user.trimEnd()}
5087
5371
  const name = c.req.param("name");
5088
5372
  const entry = await findMind(name);
5089
5373
  if (!entry) return c.json({ error: "Mind not found" }, 404);
5090
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-3NA7ZJP3.js");
5374
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-DSPXFHGX.js");
5091
5375
  const sm = getSleepManagerIfReady2();
5092
5376
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
5093
5377
  return c.json(sm.getState(name));
@@ -5095,7 +5379,7 @@ ${user.trimEnd()}
5095
5379
  const name = c.req.param("name");
5096
5380
  const entry = await findMind(name);
5097
5381
  if (!entry) return c.json({ error: "Mind not found" }, 404);
5098
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-3NA7ZJP3.js");
5382
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-DSPXFHGX.js");
5099
5383
  const sm = getSleepManagerIfReady2();
5100
5384
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
5101
5385
  if (sm.isSleeping(name)) return c.json({ error: "Mind is already sleeping" }, 409);
@@ -5115,7 +5399,7 @@ ${user.trimEnd()}
5115
5399
  const name = c.req.param("name");
5116
5400
  const entry = await findMind(name);
5117
5401
  if (!entry) return c.json({ error: "Mind not found" }, 404);
5118
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-3NA7ZJP3.js");
5402
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-DSPXFHGX.js");
5119
5403
  const sm = getSleepManagerIfReady2();
5120
5404
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
5121
5405
  const sleepState = sm.getState(name);
@@ -5130,7 +5414,7 @@ ${user.trimEnd()}
5130
5414
  const name = c.req.param("name");
5131
5415
  const entry = await findMind(name);
5132
5416
  if (!entry) return c.json({ error: "Mind not found" }, 404);
5133
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-3NA7ZJP3.js");
5417
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-DSPXFHGX.js");
5134
5418
  const sm = getSleepManagerIfReady2();
5135
5419
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
5136
5420
  const flushed = await sm.flushQueuedMessages(name);
@@ -5146,7 +5430,7 @@ ${user.trimEnd()}
5146
5430
  if (body.displayName !== void 0) profile.displayName = body.displayName;
5147
5431
  if (body.description !== void 0) profile.description = body.description;
5148
5432
  if (body.avatar !== void 0) {
5149
- const homeDir = resolve10(dir, "home");
5433
+ const homeDir = resolve11(dir, "home");
5150
5434
  const avatarPath = safeResolveWithinBase(homeDir, body.avatar);
5151
5435
  if (!avatarPath) {
5152
5436
  return c.json({ error: "Avatar path must be inside the mind's home directory" }, 400);
@@ -5158,7 +5442,7 @@ ${user.trimEnd()}
5158
5442
  }
5159
5443
  config2.profile = profile;
5160
5444
  writeVoluteConfig(dir, config2);
5161
- const { syncMindProfile: syncMindProfile2 } = await import("./auth-A4VGHADI.js");
5445
+ const { syncMindProfile: syncMindProfile2 } = await import("./auth-ABYLOEZ7.js");
5162
5446
  await syncMindProfile2(name, profile);
5163
5447
  broadcast({ type: "profile_updated", mind: name, summary: `${name} profile updated` });
5164
5448
  return c.json({ ok: true });
@@ -5197,25 +5481,18 @@ ${user.trimEnd()}
5197
5481
  return c.json({ output: "" });
5198
5482
  }
5199
5483
  const dir = entry.dir ?? mindDir(name);
5200
- const soulPath = resolve10(dir, "home/SOUL.md");
5201
- const memoryPath = resolve10(dir, "home/MEMORY.md");
5202
- const soulCustom = existsSync7(soulPath) && !readFileSync8(soulPath, "utf-8").includes(ORIENTATION_MARKER);
5203
- const memoryWritten = existsSync7(memoryPath) && readFileSync8(memoryPath, "utf-8").trim().length > 0;
5204
- const config2 = readVoluteConfig(dir);
5205
- const displayNameSet = !!config2?.profile?.displayName;
5206
- const avatarSet = !!config2?.profile?.avatar;
5207
- const { isImagegenEnabled } = await import("./setup-KFYVWTXG.js");
5208
- const imagegenEnabled = isImagegenEnabled();
5484
+ const { evaluateSeedChecklist } = await import("./seed-readiness-3LRYASA4.js");
5485
+ const checklist = evaluateSeedChecklist(dir);
5209
5486
  const done = [];
5210
5487
  const remaining = [];
5211
- if (soulCustom) done.push("SOUL.md written");
5488
+ if (checklist.soulWritten) done.push("SOUL.md written");
5212
5489
  else remaining.push("Write SOUL.md");
5213
- if (memoryWritten) done.push("MEMORY.md written");
5490
+ if (checklist.memoryWritten) done.push("MEMORY.md written");
5214
5491
  else remaining.push("Write MEMORY.md");
5215
- if (displayNameSet) done.push("Display name set");
5492
+ if (checklist.displayNameSet) done.push("Display name set");
5216
5493
  else remaining.push("Set display name");
5217
- if (imagegenEnabled) {
5218
- if (avatarSet) done.push("Avatar set");
5494
+ if (checklist.imagegenEnabled) {
5495
+ if (checklist.avatarSet) done.push("Avatar set");
5219
5496
  else remaining.push("Generate and set avatar");
5220
5497
  }
5221
5498
  const creatorStatus = minutesSinceCreator === Infinity ? "No creator messages yet" : `Last creator message: ${Math.round(minutesSinceCreator)} minutes ago`;
@@ -5239,23 +5516,51 @@ ${user.trimEnd()}
5239
5516
  return c.json({ error: `Mind is not a seed (stage: ${entry.stage})` }, 409);
5240
5517
  }
5241
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
+ );
5522
+ try {
5523
+ const sproutedDir = entry.dir ?? mindDir(name);
5524
+ if (setupDefaultDreaming(sproutedDir).schedulesChanged) {
5525
+ const { getScheduler: getScheduler2 } = await import("./scheduler-K7Y7Z7FQ.js");
5526
+ getScheduler2().loadSchedules(name, sproutedDir);
5527
+ }
5528
+ } catch (err) {
5529
+ logger_default.warn(`failed to set up default dreaming for ${name}`, logger_default.errorData(err));
5530
+ }
5242
5531
  try {
5243
5532
  const spiritEntry = await findMind("volute");
5244
5533
  if (spiritEntry) {
5245
- const { spiritDir } = await import("./spirit-WXN4IDBF.js");
5534
+ const { firstWeekSchedules, spiritDir } = await import("./spirit-SSJMYC6A.js");
5246
5535
  const sDir = spiritEntry.dir ?? spiritDir();
5247
5536
  const spiritConfig = readVoluteConfig(sDir);
5248
- if (spiritConfig?.schedules) {
5249
- const nurtureId = `nurture-${name}`;
5250
- spiritConfig.schedules = spiritConfig.schedules.filter((s) => s.id !== nurtureId);
5251
- if (spiritConfig.schedules.length === 0) spiritConfig.schedules = void 0;
5537
+ if (spiritConfig) {
5538
+ const schedules = (spiritConfig.schedules ?? []).filter(
5539
+ (s) => s.id !== `nurture-${name}`
5540
+ );
5541
+ const existing = new Set(schedules.map((s) => s.id));
5542
+ schedules.push(
5543
+ ...firstWeekSchedules(name, /* @__PURE__ */ new Date()).filter((s) => !existing.has(s.id))
5544
+ );
5545
+ spiritConfig.schedules = schedules;
5252
5546
  writeVoluteConfig(sDir, spiritConfig);
5253
- const { getScheduler: getScheduler2 } = await import("./scheduler-HQSDYJFY.js");
5254
- getScheduler2().loadSchedules("volute", sDir);
5547
+ try {
5548
+ const { getScheduler: getScheduler2 } = await import("./scheduler-K7Y7Z7FQ.js");
5549
+ getScheduler2().loadSchedules("volute", sDir);
5550
+ } catch (err) {
5551
+ logger_default.warn(
5552
+ `spirit schedules for sprout of ${name} written to disk but not reloaded into the running scheduler (effective on next spirit restart)`,
5553
+ logger_default.errorData(err)
5554
+ );
5555
+ }
5556
+ } else {
5557
+ logger_default.warn(
5558
+ `spirit config at ${sDir} missing or unparseable \u2014 first-week arc for ${name} not scheduled, nurture-${name} not removed`
5559
+ );
5255
5560
  }
5256
5561
  }
5257
5562
  } catch (err) {
5258
- logger_default.warn(`failed to clean up nurture schedule for ${name}`, logger_default.errorData(err));
5563
+ logger_default.warn(`failed to update spirit schedules for sprout of ${name}`, logger_default.errorData(err));
5259
5564
  }
5260
5565
  return c.json({ ok: true });
5261
5566
  }).delete("/:name", requireAdmin, async (c) => {
@@ -5279,10 +5584,10 @@ ${user.trimEnd()}
5279
5584
  invalidateMindUserCache(name);
5280
5585
  const state = stateDir(name);
5281
5586
  if (existsSync7(state)) {
5282
- rmSync4(state, { recursive: true, force: true });
5587
+ rmSync3(state, { recursive: true, force: true });
5283
5588
  }
5284
5589
  if (force && existsSync7(dir)) {
5285
- rmSync4(dir, { recursive: true, force: true });
5590
+ rmSync3(dir, { recursive: true, force: true });
5286
5591
  deleteMindUser(name);
5287
5592
  }
5288
5593
  fireWebhook({
@@ -5309,16 +5614,16 @@ ${user.trimEnd()}
5309
5614
  }
5310
5615
  const UPGRADE_BRANCH = "upgrade";
5311
5616
  const upgradeVariantName = `${mindName}-upgrade`;
5312
- const worktreeDir = resolve10(dir, ".variants", UPGRADE_BRANCH);
5617
+ const worktreeDir = resolve11(dir, ".variants", UPGRADE_BRANCH);
5313
5618
  if (body.abort) {
5314
5619
  if (!existsSync7(worktreeDir)) {
5315
5620
  return c.json({ error: "No upgrade in progress" }, 400);
5316
5621
  }
5317
5622
  try {
5318
5623
  try {
5319
- const gitDirContent = readFileSync8(resolve10(worktreeDir, ".git"), "utf-8").trim();
5624
+ const gitDirContent = readFileSync9(resolve11(worktreeDir, ".git"), "utf-8").trim();
5320
5625
  const gitDir = gitDirContent.replace("gitdir: ", "");
5321
- if (existsSync7(resolve10(gitDir, "MERGE_HEAD"))) {
5626
+ if (existsSync7(resolve11(gitDir, "MERGE_HEAD"))) {
5322
5627
  await gitExec(["merge", "--abort"], { cwd: worktreeDir });
5323
5628
  }
5324
5629
  } catch {
@@ -5402,7 +5707,7 @@ ${user.trimEnd()}
5402
5707
  }
5403
5708
  if (body.diff) {
5404
5709
  try {
5405
- if (!existsSync7(resolve10(dir, ".git"))) {
5710
+ if (!existsSync7(resolve11(dir, ".git"))) {
5406
5711
  return c.json({ error: "Mind has no git history \u2014 nothing to diff against" }, 400);
5407
5712
  }
5408
5713
  await updateTemplateBranch(dir, template, mindName);
@@ -5426,16 +5731,16 @@ ${user.trimEnd()}
5426
5731
  409
5427
5732
  );
5428
5733
  }
5429
- if (!existsSync7(resolve10(dir, ".git"))) {
5734
+ if (!existsSync7(resolve11(dir, ".git"))) {
5430
5735
  try {
5431
- const env = isIsolationEnabled() ? { ...process.env, HOME: resolve10(dir, "home") } : void 0;
5736
+ const env = isIsolationEnabled() ? { ...process.env, HOME: resolve11(dir, "home") } : void 0;
5432
5737
  await gitExec(["init"], { cwd: dir, mindName, env });
5433
5738
  await configureGitIdentity(mindName, { cwd: dir, mindName, env });
5434
5739
  await gitExec(["add", "-A"], { cwd: dir, mindName, env });
5435
5740
  await gitExec(["commit", "-m", "initial commit"], { cwd: dir, mindName, env });
5436
5741
  await chownMindDir(dir, mindName);
5437
5742
  } catch (err) {
5438
- rmSync4(resolve10(dir, ".git"), { recursive: true, force: true });
5743
+ rmSync3(resolve11(dir, ".git"), { recursive: true, force: true });
5439
5744
  return c.json(
5440
5745
  {
5441
5746
  error: `Git initialization failed: ${err instanceof Error ? err.message : String(err)}`
@@ -5450,7 +5755,7 @@ ${user.trimEnd()}
5450
5755
  } catch {
5451
5756
  }
5452
5757
  await updateTemplateBranch(dir, template, mindName);
5453
- const parentDir = resolve10(dir, ".variants");
5758
+ const parentDir = resolve11(dir, ".variants");
5454
5759
  if (!existsSync7(parentDir)) {
5455
5760
  mkdirSync6(parentDir, { recursive: true });
5456
5761
  }
@@ -5584,19 +5889,19 @@ ${user.trimEnd()}
5584
5889
  if (!existsSync7(dir)) return c.json({ error: "Mind directory missing" }, 404);
5585
5890
  let config2 = readVoluteConfig(dir);
5586
5891
  if (!config2 && entry.template === "pi") {
5587
- const piConfigPath = resolve10(dir, "home/.config/config.json");
5892
+ const piConfigPath = resolve11(dir, "home/.config/config.json");
5588
5893
  if (existsSync7(piConfigPath)) {
5589
5894
  try {
5590
- config2 = JSON.parse(readFileSync8(piConfigPath, "utf-8"));
5895
+ config2 = JSON.parse(readFileSync9(piConfigPath, "utf-8"));
5591
5896
  } catch {
5592
5897
  }
5593
5898
  }
5594
5899
  }
5595
5900
  let templateConfig = {};
5596
- const configJsonPath = resolve10(dir, "home/.config/config.json");
5901
+ const configJsonPath = resolve11(dir, "home/.config/config.json");
5597
5902
  if (existsSync7(configJsonPath)) {
5598
5903
  try {
5599
- templateConfig = JSON.parse(readFileSync8(configJsonPath, "utf-8"));
5904
+ templateConfig = JSON.parse(readFileSync9(configJsonPath, "utf-8"));
5600
5905
  } catch {
5601
5906
  }
5602
5907
  }
@@ -5610,22 +5915,7 @@ ${user.trimEnd()}
5610
5915
  },
5611
5916
  config: {
5612
5917
  model: config2?.model ?? templateConfig.model ?? null,
5613
- thinkingLevel: (() => {
5614
- if (config2?.thinkingLevel) return config2.thinkingLevel;
5615
- const tc = templateConfig;
5616
- if (tc.thinkingLevel) return tc.thinkingLevel;
5617
- if (tc.reasoningEffort) return tc.reasoningEffort;
5618
- const mtt = tc.maxThinkingTokens;
5619
- if (mtt) {
5620
- if (mtt <= 1024) return "minimal";
5621
- if (mtt <= 4096) return "low";
5622
- if (mtt <= 1e4) return "medium";
5623
- if (mtt <= 32e3) return "high";
5624
- return "xhigh";
5625
- }
5626
- return null;
5627
- })(),
5628
- maxThinkingTokens: config2?.maxThinkingTokens ?? templateConfig.maxThinkingTokens ?? null,
5918
+ thinkingLevel: config2?.thinkingLevel ?? deriveThinkingLevel(templateConfig),
5629
5919
  tokenBudget: config2?.tokenBudget ?? null,
5630
5920
  tokenBudgetPeriodMinutes: config2?.tokenBudgetPeriodMinutes ?? null,
5631
5921
  compaction: templateConfig.compaction ?? null,
@@ -5640,7 +5930,6 @@ ${user.trimEnd()}
5640
5930
  z6.object({
5641
5931
  model: z6.string().optional(),
5642
5932
  thinkingLevel: z6.enum(["off", "minimal", "low", "medium", "high", "xhigh"]).optional(),
5643
- maxThinkingTokens: z6.number().int().positive().nullable().optional(),
5644
5933
  tokenBudget: z6.number().int().positive().nullable().optional(),
5645
5934
  tokenBudgetPeriodMinutes: z6.number().int().positive().nullable().optional(),
5646
5935
  compaction: z6.object({ maxContextTokens: z6.number().int().positive().nullable().optional() }).nullable().optional(),
@@ -5673,76 +5962,29 @@ ${user.trimEnd()}
5673
5962
  existing.tokenBudgetPeriodMinutes = body.tokenBudgetPeriodMinutes;
5674
5963
  }
5675
5964
  }
5676
- if (body.maxThinkingTokens !== void 0) {
5677
- if (body.maxThinkingTokens === null) {
5678
- delete existing.maxThinkingTokens;
5679
- } else {
5680
- existing.maxThinkingTokens = body.maxThinkingTokens;
5681
- }
5682
- }
5683
5965
  if (body.unescapeNewlines !== void 0) {
5684
5966
  existing.unescapeNewlines = body.unescapeNewlines;
5685
5967
  }
5686
5968
  writeVoluteConfig(dir, existing);
5687
5969
  if (body.unescapeNewlines !== void 0) {
5688
- const { clearEchoTextCache } = await import("./echo-text-SEQCLGAT.js");
5970
+ const { clearEchoTextCache } = await import("./echo-text-B6NZ5FGE.js");
5689
5971
  clearEchoTextCache(name);
5690
5972
  }
5691
- const needsConfigJson = body.model !== void 0 || body.thinkingLevel !== void 0 || body.maxThinkingTokens !== void 0 || body.compaction !== void 0;
5973
+ const needsConfigJson = body.model !== void 0 || body.thinkingLevel !== void 0 || body.compaction !== void 0;
5692
5974
  if (needsConfigJson) {
5693
- const configJsonPath = resolve10(dir, "home/.config/config.json");
5975
+ const configJsonPath = resolve11(dir, "home/.config/config.json");
5694
5976
  let templateConfig = {};
5695
5977
  if (existsSync7(configJsonPath)) {
5696
5978
  try {
5697
- templateConfig = JSON.parse(readFileSync8(configJsonPath, "utf-8"));
5979
+ templateConfig = JSON.parse(readFileSync9(configJsonPath, "utf-8"));
5698
5980
  } catch {
5699
5981
  }
5700
5982
  }
5701
5983
  if (body.model !== void 0) {
5702
5984
  templateConfig.model = body.model;
5703
5985
  }
5704
- const tmpl = entry.template ?? "claude";
5705
5986
  if (body.thinkingLevel !== void 0) {
5706
- if (tmpl === "claude") {
5707
- const claudeThinkingTokens = {
5708
- off: null,
5709
- minimal: 1024,
5710
- low: 4096,
5711
- medium: 1e4,
5712
- high: 32e3,
5713
- xhigh: 128e3
5714
- };
5715
- const tokens = claudeThinkingTokens[body.thinkingLevel] ?? null;
5716
- if (tokens === null) {
5717
- delete templateConfig.maxThinkingTokens;
5718
- } else {
5719
- templateConfig.maxThinkingTokens = tokens;
5720
- }
5721
- } else if (tmpl === "codex") {
5722
- const codexMap = {
5723
- off: null,
5724
- minimal: "minimal",
5725
- low: "low",
5726
- medium: "medium",
5727
- high: "high",
5728
- xhigh: "xhigh"
5729
- };
5730
- const effort = codexMap[body.thinkingLevel] ?? null;
5731
- if (effort === null) {
5732
- delete templateConfig.reasoningEffort;
5733
- } else {
5734
- templateConfig.reasoningEffort = effort;
5735
- }
5736
- } else {
5737
- templateConfig.thinkingLevel = body.thinkingLevel;
5738
- }
5739
- }
5740
- if (body.maxThinkingTokens !== void 0) {
5741
- if (body.maxThinkingTokens === null) {
5742
- delete templateConfig.maxThinkingTokens;
5743
- } else {
5744
- templateConfig.maxThinkingTokens = body.maxThinkingTokens;
5745
- }
5987
+ applyThinkingLevel(templateConfig, entry.template ?? "claude", body.thinkingLevel);
5746
5988
  }
5747
5989
  if (body.compaction !== void 0) {
5748
5990
  if (body.compaction === null) {
@@ -5757,12 +5999,12 @@ ${user.trimEnd()}
5757
5999
  templateConfig.compaction = comp;
5758
6000
  }
5759
6001
  }
5760
- writeFileSync7(configJsonPath, `${JSON.stringify(templateConfig, null, 2)}
6002
+ writeFileSync8(configJsonPath, `${JSON.stringify(templateConfig, null, 2)}
5761
6003
  `);
5762
6004
  }
5763
6005
  if (entry.mindType === "spirit" && body.model !== void 0) {
5764
6006
  try {
5765
- const { readGlobalConfig: readGlobalConfig2, writeGlobalConfig: writeGlobalConfig2 } = await import("./setup-KFYVWTXG.js");
6007
+ const { readGlobalConfig: readGlobalConfig2, writeGlobalConfig: writeGlobalConfig2 } = await import("./setup-TCN7VH5U.js");
5766
6008
  const globalConfig = readGlobalConfig2();
5767
6009
  globalConfig.spiritModel = body.model;
5768
6010
  writeGlobalConfig2(globalConfig);
@@ -5805,7 +6047,7 @@ ${user.trimEnd()}
5805
6047
  if (!body.systemPrompt || !body.message) {
5806
6048
  return c.json({ error: "systemPrompt and message required" }, 400);
5807
6049
  }
5808
- const { aiComplete: aiCompleteFn, isAiConfigured } = await import("./ai-service-CVZEYKTK.js");
6050
+ const { aiComplete: aiCompleteFn, isAiConfigured } = await import("./ai-service-A7FS7EC3.js");
5809
6051
  if (!isAiConfigured()) {
5810
6052
  return c.json({ error: "AI service not configured" }, 503);
5811
6053
  }
@@ -5926,6 +6168,38 @@ ${user.trimEnd()}
5926
6168
  const db = await getDb();
5927
6169
  const rows = await db.selectDistinct({ channel: mindHistory.channel }).from(mindHistory).where(eq5(mindHistory.mind, name));
5928
6170
  return c.json(rows.map((r) => r.channel));
6171
+ }).get("/:name/history/contacts", requireSelf(), async (c) => {
6172
+ const name = c.req.param("name");
6173
+ const rawHours = parseInt(c.req.query("hours") ?? "48", 10);
6174
+ const hours = Math.min(Math.max(Number.isNaN(rawHours) ? 48 : rawHours, 1), 168);
6175
+ const cutoff = new Date(Date.now() - hours * 36e5).toISOString().replace("T", " ").slice(0, 19);
6176
+ const db = await getDb();
6177
+ const contacts = await db.select({
6178
+ channel: mindHistory.channel,
6179
+ last_at: sql3`MAX(${mindHistory.created_at})`,
6180
+ last_inbound_at: sql3`MAX(CASE WHEN ${mindHistory.type} = 'inbound' THEN ${mindHistory.created_at} END)`,
6181
+ last_outbound_at: sql3`MAX(CASE WHEN ${mindHistory.type} = 'outbound' THEN ${mindHistory.created_at} END)`,
6182
+ message_count: sql3`COUNT(*)`,
6183
+ // The counterparty: most recent inbound sender on this channel. For a
6184
+ // DM the channel name already carries it (e.g. @atlas), but this also
6185
+ // covers shared channels (#system) and rows where the channel is opaque.
6186
+ last_sender: sql3`(
6187
+ SELECT sender FROM mind_history h2
6188
+ WHERE h2.mind = ${name}
6189
+ AND h2.channel = ${mindHistory.channel}
6190
+ AND h2.type = 'inbound'
6191
+ AND h2.sender IS NOT NULL
6192
+ ORDER BY h2.created_at DESC, h2.id DESC LIMIT 1
6193
+ )`
6194
+ }).from(mindHistory).where(
6195
+ and5(
6196
+ eq5(mindHistory.mind, name),
6197
+ sql3`${mindHistory.type} IN ('inbound','outbound')`,
6198
+ sql3`${mindHistory.channel} IS NOT NULL`,
6199
+ sql3`${mindHistory.created_at} >= ${cutoff}`
6200
+ )
6201
+ ).groupBy(mindHistory.channel).orderBy(sql3`MAX(${mindHistory.created_at}) DESC`);
6202
+ return c.json({ hours, contacts });
5929
6203
  }).get("/:name/history/export", requireSelf(), async (c) => {
5930
6204
  const name = c.req.param("name");
5931
6205
  if (!await findMind(name)) return c.json({ error: "Mind not found" }, 404);
@@ -6154,6 +6428,13 @@ var slog2 = logger_default.child("schedules");
6154
6428
  function readSchedules(dir) {
6155
6429
  return readVoluteConfig(dir)?.schedules ?? [];
6156
6430
  }
6431
+ function validateMessages(messages) {
6432
+ if (messages === void 0) return null;
6433
+ if (!Array.isArray(messages) || messages.some((m) => typeof m !== "string" || !m.trim())) {
6434
+ return "messages must be an array of non-empty strings";
6435
+ }
6436
+ return null;
6437
+ }
6157
6438
  var UPCOMING_HORIZON_MS = 24 * 60 * 60 * 1e3;
6158
6439
  function computeClockEvents(schedules, sleepState, sleepConfig, now) {
6159
6440
  const upcoming = [];
@@ -6241,7 +6522,8 @@ var app15 = new Hono18().get("/:name/clock/status", requireSelf(), async (c) =>
6241
6522
  const schedules = readSchedules(dir);
6242
6523
  const now = /* @__PURE__ */ new Date();
6243
6524
  const { upcoming, previous } = computeClockEvents(schedules, sleepState, sleepConfig, now);
6244
- 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 });
6245
6527
  }).get("/:name/sleep/config", requireSelf(), async (c) => {
6246
6528
  const name = c.req.param("name");
6247
6529
  const entry = await findMind(name);
@@ -6295,11 +6577,13 @@ var app15 = new Hono18().get("/:name/clock/status", requireSelf(), async (c) =>
6295
6577
  if (body.cron && body.fireAt) {
6296
6578
  return c.json({ error: "cron and fireAt are mutually exclusive" }, 400);
6297
6579
  }
6298
- if (!body.message && !body.script) {
6299
- return c.json({ error: "message or script is required" }, 400);
6580
+ const messagesErr = validateMessages(body.messages);
6581
+ if (messagesErr) return c.json({ error: messagesErr }, 400);
6582
+ if (!body.message && !body.messages?.length && !body.script) {
6583
+ return c.json({ error: "message, messages, or script is required" }, 400);
6300
6584
  }
6301
- if (body.message && body.script) {
6302
- return c.json({ error: "message and script are mutually exclusive" }, 400);
6585
+ if ([body.message, body.messages?.length, body.script].filter(Boolean).length > 1) {
6586
+ return c.json({ error: "message, messages, and script are mutually exclusive" }, 400);
6303
6587
  }
6304
6588
  if (body.cron) {
6305
6589
  try {
@@ -6329,6 +6613,7 @@ var app15 = new Hono18().get("/:name/clock/status", requireSelf(), async (c) =>
6329
6613
  if (body.cron) schedule.cron = body.cron;
6330
6614
  if (body.fireAt) schedule.fireAt = body.fireAt;
6331
6615
  if (body.message) schedule.message = body.message;
6616
+ if (body.messages?.length) schedule.messages = body.messages;
6332
6617
  if (body.script) schedule.script = body.script;
6333
6618
  if (body.channel) schedule.channel = body.channel;
6334
6619
  if (body.whileSleeping) schedule.whileSleeping = body.whileSleeping;
@@ -6345,8 +6630,10 @@ var app15 = new Hono18().get("/:name/clock/status", requireSelf(), async (c) =>
6345
6630
  const idx = schedules.findIndex((s) => s.id === id);
6346
6631
  if (idx === -1) return c.json({ error: "Schedule not found" }, 404);
6347
6632
  const body = await c.req.json();
6348
- if (body.message && body.script) {
6349
- return c.json({ error: "message and script are mutually exclusive" }, 400);
6633
+ const messagesErr = validateMessages(body.messages);
6634
+ if (messagesErr) return c.json({ error: messagesErr }, 400);
6635
+ if ([body.message, body.messages?.length, body.script].filter(Boolean).length > 1) {
6636
+ return c.json({ error: "message, messages, and script are mutually exclusive" }, 400);
6350
6637
  }
6351
6638
  if (body.cron !== void 0) {
6352
6639
  try {
@@ -6367,10 +6654,21 @@ var app15 = new Hono18().get("/:name/clock/status", requireSelf(), async (c) =>
6367
6654
  if (body.message !== void 0) {
6368
6655
  schedules[idx].message = body.message;
6369
6656
  delete schedules[idx].script;
6657
+ delete schedules[idx].messages;
6658
+ }
6659
+ if (body.messages !== void 0) {
6660
+ if (body.messages.length > 0) {
6661
+ schedules[idx].messages = body.messages;
6662
+ delete schedules[idx].message;
6663
+ delete schedules[idx].script;
6664
+ } else {
6665
+ delete schedules[idx].messages;
6666
+ }
6370
6667
  }
6371
6668
  if (body.script !== void 0) {
6372
6669
  schedules[idx].script = body.script;
6373
6670
  delete schedules[idx].message;
6671
+ delete schedules[idx].messages;
6374
6672
  }
6375
6673
  if (body.whileSleeping && !["skip", "queue", "trigger-wake"].includes(body.whileSleeping)) {
6376
6674
  return c.json(
@@ -6384,6 +6682,11 @@ var app15 = new Hono18().get("/:name/clock/status", requireSelf(), async (c) =>
6384
6682
  if (body.channel !== void 0) schedules[idx].channel = body.channel || void 0;
6385
6683
  if (body.whileSleeping !== void 0)
6386
6684
  schedules[idx].whileSleeping = body.whileSleeping || void 0;
6685
+ const touchedAction = body.message !== void 0 || body.messages !== void 0 || body.script !== void 0;
6686
+ const result = schedules[idx];
6687
+ if (touchedAction && !result.message && !result.messages?.length && !result.script) {
6688
+ return c.json({ error: "schedule must keep a message, messages, or script" }, 400);
6689
+ }
6387
6690
  writeSchedules(name, dir, schedules);
6388
6691
  return c.json({ ok: true });
6389
6692
  }).delete("/:name/schedules/:id", requireSelf(), async (c) => {
@@ -6407,8 +6710,8 @@ var app15 = new Hono18().get("/:name/clock/status", requireSelf(), async (c) =>
6407
6710
  const body = await c.req.text();
6408
6711
  const message = `[webhook: ${event}] ${body}`;
6409
6712
  try {
6410
- const { sendSystemMessage } = await import("./system-chat-N2F7HVY3.js");
6411
- await sendSystemMessage(name, message);
6713
+ const { sendSystemMessage: sendSystemMessage2 } = await import("./system-chat-M2XOBKEM.js");
6714
+ await sendSystemMessage2(name, message);
6412
6715
  return c.json({ ok: true });
6413
6716
  } catch (err) {
6414
6717
  slog2.warn(`webhook delivery failed for ${name}`, logger_default.errorData(err));
@@ -6420,14 +6723,14 @@ var schedules_default = app15;
6420
6723
  // packages/daemon/src/web/api/setup.ts
6421
6724
  import { mkdirSync as mkdirSync7 } from "fs";
6422
6725
  import { homedir as homedir2 } from "os";
6423
- import { resolve as resolve11 } from "path";
6726
+ import { resolve as resolve12 } from "path";
6424
6727
  import { Hono as Hono19 } from "hono";
6425
6728
  import { setCookie as setCookie2 } from "hono/cookie";
6426
6729
  var DEFAULT_API_URL2 = "https://volute.systems";
6427
6730
  var setup = new Hono19();
6428
6731
  function writeSetupConfig(systemName, description) {
6429
- const configHome = process.env.VOLUTE_HOME ?? resolve11(homedir2(), ".volute");
6430
- const mindsDir = resolve11(configHome, "minds");
6732
+ const configHome = process.env.VOLUTE_HOME ?? resolve12(homedir2(), ".volute");
6733
+ const mindsDir = resolve12(configHome, "minds");
6431
6734
  mkdirSync7(configHome, { recursive: true });
6432
6735
  mkdirSync7(mindsDir, { recursive: true });
6433
6736
  const existingConfig = readGlobalConfig();
@@ -6461,7 +6764,7 @@ setup.get("/status", async (c) => {
6461
6764
  let hasAccount = false;
6462
6765
  if (hasSystem) {
6463
6766
  try {
6464
- const { listUsersByType: listUsersByType2 } = await import("./auth-A4VGHADI.js");
6767
+ const { listUsersByType: listUsersByType2 } = await import("./auth-ABYLOEZ7.js");
6465
6768
  const brains = await listUsersByType2("brain");
6466
6769
  hasAccount = brains.length > 0;
6467
6770
  } catch (err) {
@@ -6672,7 +6975,7 @@ setup.post("/account", async (c) => {
6672
6975
  }
6673
6976
  }
6674
6977
  try {
6675
- const { createUser: createUser2, updateUserProfile: updateUserProfile2 } = await import("./auth-A4VGHADI.js");
6978
+ const { createUser: createUser2, updateUserProfile: updateUserProfile2 } = await import("./auth-ABYLOEZ7.js");
6676
6979
  const user = await createUser2(body.username.trim(), body.password);
6677
6980
  if (body.displayName?.trim()) {
6678
6981
  await updateUserProfile2(user.id, { display_name: body.displayName.trim() });
@@ -6718,7 +7021,7 @@ setup.post("/models", async (c) => {
6718
7021
  return c.json({ error: "Spirit model is required" }, 400);
6719
7022
  }
6720
7023
  try {
6721
- const { setEnabledModels: setEnabledModels3, setUtilityModel: setUtilityModel2 } = await import("./ai-service-CVZEYKTK.js");
7024
+ const { setEnabledModels: setEnabledModels3, setUtilityModel: setUtilityModel2 } = await import("./ai-service-A7FS7EC3.js");
6722
7025
  setEnabledModels3(body.models);
6723
7026
  const config2 = readGlobalConfig();
6724
7027
  config2.spiritModel = body.spiritModel.trim();
@@ -6736,15 +7039,15 @@ setup.post("/complete", async (c) => {
6736
7039
  return c.json({ error: "Setup already complete" }, 400);
6737
7040
  }
6738
7041
  try {
6739
- const { ensureSpiritProject, syncSpiritTemplate } = await import("./spirit-WXN4IDBF.js");
6740
- const { startSpiritFull } = await import("./mind-service-JFY7UG5E.js");
7042
+ const { ensureSpiritProject, syncSpiritTemplate } = await import("./spirit-SSJMYC6A.js");
7043
+ const { startSpiritFull } = await import("./mind-service-42GDO4KH.js");
6741
7044
  await ensureSpiritProject();
6742
7045
  await syncSpiritTemplate();
6743
7046
  const warnings = [];
6744
7047
  const config2 = readGlobalConfig();
6745
7048
  if (config2.setup?.service && config2.setup?.type === "local") {
6746
7049
  try {
6747
- const { installUserService } = await import("./service-install-FQ43Z4DF.js");
7050
+ const { installUserService } = await import("./service-install-Y46OTF55.js");
6748
7051
  const installed = await installUserService(config2.port, config2.hostname);
6749
7052
  if (!installed) {
6750
7053
  warnings.push("Service installation is not supported on this platform.");
@@ -6768,12 +7071,13 @@ setup.post("/complete", async (c) => {
6768
7071
  logger_default.info("spirit started successfully during setup");
6769
7072
  } catch (err) {
6770
7073
  logger_default.warn("spirit start failed during setup (non-fatal)", logger_default.errorData(err));
6771
- warnings.push("Spirit failed to start \u2014 it will retry on next daemon restart.");
7074
+ const reason = err instanceof Error ? err.message : String(err);
7075
+ warnings.push(`The spirit couldn't start: ${reason} \u2014 it will retry on next daemon restart.`);
6772
7076
  }
6773
7077
  let spiritConversationId;
6774
7078
  try {
6775
- const { getOrCreateMindUser: getOrCreateMindUser2, listUsersByType: listUsersByType2 } = await import("./auth-A4VGHADI.js");
6776
- 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");
6777
7081
  const spiritUser = await getOrCreateMindUser2("volute");
6778
7082
  const brains = await listUsersByType2("brain");
6779
7083
  const admin2 = brains.find((u) => u.role === "admin");
@@ -6795,8 +7099,8 @@ setup.post("/complete", async (c) => {
6795
7099
  logger_default.info("setup complete state", { spiritConversationId, spiritStarted });
6796
7100
  if (spiritConversationId && spiritStarted) {
6797
7101
  try {
6798
- const { deliverMessage: deliverMessage2 } = await import("./message-delivery-ZE3YGJDD.js");
6799
- 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");
6800
7104
  const admins = await listUsers2("brain");
6801
7105
  const admin2 = admins.find((u) => u.role === "admin");
6802
7106
  if (admin2) {
@@ -6835,9 +7139,9 @@ setup.post("/complete", async (c) => {
6835
7139
  var setup_default = setup;
6836
7140
 
6837
7141
  // packages/daemon/src/web/api/skills.ts
6838
- import { existsSync as existsSync8, mkdtempSync, readdirSync as readdirSync3, rmSync as rmSync5 } from "fs";
7142
+ import { existsSync as existsSync8, mkdtempSync, readdirSync as readdirSync3, rmSync as rmSync4 } from "fs";
6839
7143
  import { tmpdir } from "os";
6840
- import { join, resolve as resolve12 } from "path";
7144
+ import { join, resolve as resolve13 } from "path";
6841
7145
  import AdmZip from "adm-zip";
6842
7146
  import { Hono as Hono20 } from "hono";
6843
7147
  var app16 = new Hono20().get("/", async (c) => {
@@ -6913,7 +7217,7 @@ var app16 = new Hono20().get("/", async (c) => {
6913
7217
  try {
6914
7218
  const zip = new AdmZip(buffer);
6915
7219
  for (const entry of zip.getEntries()) {
6916
- const target = resolve12(tmpDir, entry.entryName);
7220
+ const target = resolve13(tmpDir, entry.entryName);
6917
7221
  if (!target.startsWith(tmpDir)) {
6918
7222
  return c.json({ error: "Invalid zip: paths must not escape archive" }, 400);
6919
7223
  }
@@ -6942,7 +7246,7 @@ var app16 = new Hono20().get("/", async (c) => {
6942
7246
  }
6943
7247
  throw e;
6944
7248
  } finally {
6945
- rmSync5(tmpDir, { recursive: true, force: true });
7249
+ rmSync4(tmpDir, { recursive: true, force: true });
6946
7250
  }
6947
7251
  }).get("/:id", async (c) => {
6948
7252
  const id = c.req.param("id");
@@ -7239,8 +7543,8 @@ var app20 = new Hono24().use("*", authMiddleware).get("/", async (c) => {
7239
7543
  });
7240
7544
  }, 15e3);
7241
7545
  cleanups.push(() => clearInterval(keepAlive));
7242
- await new Promise((resolve17) => {
7243
- stream.onAbort(() => resolve17());
7546
+ await new Promise((resolve19) => {
7547
+ stream.onAbort(() => resolve19());
7244
7548
  });
7245
7549
  } finally {
7246
7550
  for (const cleanup of cleanups) {
@@ -7255,14 +7559,103 @@ var app20 = new Hono24().use("*", authMiddleware).get("/", async (c) => {
7255
7559
  var events_default = app20;
7256
7560
 
7257
7561
  // packages/daemon/src/web/api/variants.ts
7258
- import { existsSync as existsSync9, mkdirSync as mkdirSync9, writeFileSync as writeFileSync8 } from "fs";
7259
- import { resolve as resolve14 } from "path";
7562
+ import { existsSync as existsSync10, mkdirSync as mkdirSync9, writeFileSync as writeFileSync9 } from "fs";
7563
+ import { resolve as resolve16 } from "path";
7260
7564
  import { Hono as Hono25 } from "hono";
7261
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
+
7262
7655
  // packages/daemon/src/lib/mind/spawn-server.ts
7263
7656
  import { spawn as spawn4 } from "child_process";
7264
- import { closeSync, mkdirSync as mkdirSync8, openSync, readFileSync as readFileSync9 } from "fs";
7265
- import { resolve as resolve13 } from "path";
7657
+ import { closeSync, mkdirSync as mkdirSync8, openSync, readFileSync as readFileSync11, statSync as statSync3 } from "fs";
7658
+ import { resolve as resolve15 } from "path";
7266
7659
  async function spawnServer(cwd, port, options) {
7267
7660
  let cmd = process.env.VOLUTE_NODE_PATH ?? process.execPath;
7268
7661
  let args = ["--import", "tsx", "src/server.ts", "--port", String(port)];
@@ -7283,31 +7676,36 @@ function spawnAttached(cmd, args, cwd) {
7283
7676
  cwd,
7284
7677
  stdio: ["ignore", "pipe", "pipe"]
7285
7678
  });
7286
- return new Promise((resolve17) => {
7287
- const timeout = setTimeout(() => resolve17(null), 3e4);
7679
+ return new Promise((resolve19) => {
7680
+ const timeout = setTimeout(() => resolve19(null), 3e4);
7288
7681
  function checkOutput(data) {
7289
7682
  const match = data.toString().match(/listening on :(\d+)/);
7290
7683
  if (match) {
7291
7684
  clearTimeout(timeout);
7292
- resolve17({ child, actualPort: parseInt(match[1], 10) });
7685
+ resolve19({ child, actualPort: parseInt(match[1], 10) });
7293
7686
  }
7294
7687
  }
7295
7688
  child.stdout?.on("data", checkOutput);
7296
7689
  child.stderr?.on("data", checkOutput);
7297
7690
  child.on("error", () => {
7298
7691
  clearTimeout(timeout);
7299
- resolve17(null);
7692
+ resolve19(null);
7300
7693
  });
7301
7694
  child.on("exit", () => {
7302
7695
  clearTimeout(timeout);
7303
- resolve17(null);
7696
+ resolve19(null);
7304
7697
  });
7305
7698
  });
7306
7699
  }
7307
7700
  function spawnDetached(cmd, args, cwd, logDir) {
7308
- const logsDir = logDir ?? resolve13(cwd, ".mind", "logs");
7701
+ const logsDir = logDir ?? resolve15(cwd, ".mind", "logs");
7309
7702
  mkdirSync8(logsDir, { recursive: true });
7310
- const logPath = resolve13(logsDir, "mind.log");
7703
+ const logPath = resolve15(logsDir, "mind.log");
7704
+ let startOffset = 0;
7705
+ try {
7706
+ startOffset = statSync3(logPath).size;
7707
+ } catch {
7708
+ }
7311
7709
  const logFd = openSync(logPath, "a");
7312
7710
  const child = spawn4(cmd, args, {
7313
7711
  cwd,
@@ -7327,7 +7725,7 @@ function spawnDetached(cmd, args, cwd, logDir) {
7327
7725
  }
7328
7726
  const interval = setInterval(() => {
7329
7727
  try {
7330
- const content = readFileSync9(logPath, "utf-8");
7728
+ const content = readFileSync11(logPath).subarray(startOffset).toString("utf-8");
7331
7729
  const match = content.match(/listening on :(\d+)/);
7332
7730
  if (match) {
7333
7731
  finish({ child, actualPort: parseInt(match[1], 10) });
@@ -7379,6 +7777,19 @@ async function verify2(port) {
7379
7777
  }
7380
7778
 
7381
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
+ }
7382
7793
  var app21 = new Hono25().get("/:name/variants", async (c) => {
7383
7794
  const name = c.req.param("name");
7384
7795
  const entry = await findMind(name);
@@ -7422,56 +7833,105 @@ var app21 = new Hono25().get("/:name/variants", async (c) => {
7422
7833
  }
7423
7834
  const variantName = body.name;
7424
7835
  if (!variantName) return c.json({ error: "Variant name required" }, 400);
7836
+ const purpose = body.purpose?.trim() || void 0;
7425
7837
  const err = validateBranchName(variantName);
7426
7838
  if (err) return c.json({ error: err }, 400);
7427
7839
  if (await findMind(variantName)) {
7428
7840
  return c.json({ error: `Name already in use: ${variantName}` }, 409);
7429
7841
  }
7430
7842
  const projectRoot = entry.dir ?? mindDir(mindName);
7431
- const variantDir = resolve14(projectRoot, ".variants", variantName);
7432
- if (existsSync9(variantDir)) {
7843
+ const variantDir = resolve16(projectRoot, ".variants", variantName);
7844
+ if (existsSync10(variantDir)) {
7433
7845
  return c.json({ error: `Variant directory already exists: ${variantDir}` }, 409);
7434
7846
  }
7435
- mkdirSync9(resolve14(projectRoot, ".variants"), { recursive: true });
7847
+ mkdirSync9(resolve16(projectRoot, ".variants"), { recursive: true });
7436
7848
  try {
7437
7849
  await gitExec(["worktree", "add", "-b", variantName, variantDir], { cwd: projectRoot });
7438
7850
  } catch (e) {
7439
7851
  const msg = e instanceof Error ? e.message : String(e);
7440
- 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);
7441
7885
  }
7442
- await chownMindDir(projectRoot, mindName);
7443
7886
  try {
7444
7887
  if (isIsolationEnabled()) {
7445
7888
  const [cmd, args] = await wrapForIsolation("npm", ["install"], mindName);
7446
7889
  await exec(cmd, args, {
7447
7890
  cwd: variantDir,
7448
- env: { ...process.env, HOME: resolve14(variantDir, "home") }
7891
+ env: { ...process.env, HOME: resolve16(variantDir, "home") }
7449
7892
  });
7450
7893
  } else {
7451
7894
  await exec("npm", ["install"], { cwd: variantDir });
7452
7895
  }
7453
7896
  } catch (e) {
7897
+ await rollbackSplit();
7454
7898
  const msg = e instanceof Error ? e.message : String(e);
7455
7899
  return c.json({ error: `npm install failed: ${msg}` }, 500);
7456
7900
  }
7457
7901
  if (body.soul) {
7458
- writeFileSync8(resolve14(variantDir, "home/SOUL.md"), body.soul);
7902
+ writeFileSync9(resolve16(variantDir, "home/SOUL.md"), body.soul);
7459
7903
  }
7460
7904
  const variantPort = body.port ?? await nextPort();
7461
- 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 });
7462
7914
  if (!body.noStart) {
7463
7915
  try {
7464
- const manager = getMindManager();
7465
- manager.setPendingContext(variantName, { type: "split", parent: mindName });
7466
7916
  await manager.startMind(variantName);
7467
7917
  } catch (e) {
7918
+ await rollbackSplit();
7468
7919
  const msg = e instanceof Error ? e.message : String(e);
7469
7920
  return c.json({ error: `Variant created but failed to start: ${msg}` }, 500);
7470
7921
  }
7471
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
+ );
7472
7926
  return c.json({
7473
7927
  ok: true,
7474
- 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
+ }
7475
7935
  });
7476
7936
  }).post("/:name/variants/:variant/merge", requireSelf(), async (c) => {
7477
7937
  const mindName = c.req.param("name");
@@ -7492,146 +7952,150 @@ var app21 = new Hono25().get("/:name/variants", async (c) => {
7492
7952
  } catch {
7493
7953
  }
7494
7954
  const projectRoot = parentEntry.dir ?? mindDir(mindName);
7495
- if (existsSync9(variantEntry.dir)) {
7496
- const status = (await gitExec(["status", "--porcelain"], { cwd: variantEntry.dir })).trim();
7497
- if (status) {
7498
- try {
7499
- await gitExec(["add", "-A"], { cwd: variantEntry.dir });
7500
- await gitExec(["commit", "-m", "Auto-commit uncommitted changes before merge"], {
7501
- cwd: variantEntry.dir
7502
- });
7503
- } catch (_e) {
7504
- return c.json(
7505
- {
7506
- error: "Failed to auto-commit variant changes. Commit or stash manually before merging."
7507
- },
7508
- 500
7509
- );
7510
- }
7511
- }
7512
- }
7513
- if (!body.skipVerify) {
7514
- const result = await spawnServer(variantEntry.dir, 0, {
7515
- detached: true,
7516
- mindName: variantName,
7517
- template: variantEntry.template
7518
- });
7519
- if (!result) {
7520
- return c.json(
7521
- { error: "Failed to start server for verification. Use skipVerify to skip." },
7522
- 500
7523
- );
7524
- }
7525
- const verified = await verify2(result.actualPort);
7526
- try {
7527
- process.kill(-result.child.pid, "SIGTERM");
7528
- } catch {
7529
- }
7530
- if (!verified) {
7531
- return c.json(
7532
- { error: "Verification failed. Fix issues or use skipVerify to proceed." },
7533
- 500
7534
- );
7535
- }
7536
- }
7537
- const mainStatus = (await gitExec(["status", "--porcelain"], { cwd: projectRoot })).trim();
7538
- if (mainStatus) {
7539
- try {
7540
- await gitExec(["add", "-A"], { cwd: projectRoot });
7541
- await gitExec(["commit", "-m", "Auto-commit uncommitted changes before merge"], {
7542
- cwd: projectRoot
7543
- });
7544
- } catch (_e) {
7545
- return c.json(
7546
- { error: "Failed to auto-commit main changes. Commit or stash manually before merging." },
7547
- 500
7548
- );
7549
- }
7550
- }
7551
- try {
7552
- await gitExec(["merge", variantEntry.branch], { cwd: projectRoot });
7553
- } catch (_e) {
7554
- let conflicts = [];
7555
- try {
7556
- const out = (await gitExec(["diff", "--name-only", "--diff-filter=U"], { cwd: projectRoot })).trim();
7557
- conflicts = out ? out.split("\n") : [];
7558
- } catch (err) {
7559
- logger_default.warn(`failed to list merge conflicts for ${mindName}`, logger_default.errorData(err));
7560
- }
7561
- try {
7562
- await gitExec(["merge", "--abort"], { cwd: projectRoot });
7563
- } catch (err) {
7564
- logger_default.warn(`failed to abort merge for ${mindName}`, logger_default.errorData(err));
7565
- }
7955
+ const failAfterGitWrite = async (message, extra) => {
7566
7956
  try {
7567
7957
  await chownMindDir(projectRoot, mindName);
7568
7958
  } catch (err) {
7569
7959
  logger_default.warn(
7570
- `failed to restore ownership after aborted merge for ${mindName}`,
7960
+ `failed to restore ownership for ${mindName} after merge failure`,
7571
7961
  logger_default.errorData(err)
7572
7962
  );
7573
7963
  return c.json(
7574
7964
  {
7575
- error: `Merge failed and restoring parent ownership failed: ${err instanceof Error ? err.message : String(err)}`,
7576
- conflicts
7965
+ error: `${message} Restoring mind ownership also failed: ${err instanceof Error ? err.message : String(err)}`,
7966
+ ...extra
7577
7967
  },
7578
7968
  500
7579
7969
  );
7580
7970
  }
7581
- return c.json(
7582
- {
7583
- error: "Merge failed. Resolve conflicts in the variant and retry the join.",
7584
- conflicts
7585
- },
7586
- 500
7587
- );
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));
7588
7984
  }
7589
- await cleanupVariant(variantName, projectRoot, variantEntry.dir, { stop: true });
7590
- await chownMindDir(projectRoot, mindName);
7591
- 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 = "";
7592
8035
  try {
7593
- const { computeTemplateHash: computeTemplateHash2 } = await import("./template-hash-GSOUARW4.js");
7594
- const { setMindTemplateHash: setMindTemplateHash2 } = await import("./registry-UMMDRPRM.js");
7595
- const tmpl = parentEntry.template ?? "claude";
7596
- await setMindTemplateHash2(mindName, computeTemplateHash2(tmpl));
8036
+ memoryDelta = await mergeVariantExcludingMemory(projectRoot, variantEntry.branch);
7597
8037
  } catch (err) {
7598
- 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
+ );
7599
8043
  }
7600
- }
7601
- let restartWarning;
7602
- try {
7603
- if (isIsolationEnabled()) {
7604
- const [cmd, args] = await wrapForIsolation("npm", ["install"], mindName);
7605
- await exec(cmd, args, {
7606
- cwd: projectRoot,
7607
- env: { ...process.env, HOME: resolve14(projectRoot, "home") }
7608
- });
7609
- } else {
7610
- 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
+ }
7611
8055
  }
7612
- } catch (err) {
7613
- logger_default.warn(`npm install failed after merge for ${mindName}`, logger_default.errorData(err));
7614
- restartWarning = `npm install failed after merge \u2014 mind may have stale dependencies`;
7615
- }
7616
- const manager = getMindManager();
7617
- const context = {
7618
- type: "merged",
7619
- name: variantName,
7620
- ...body.summary && { summary: body.summary },
7621
- ...body.justification && { justification: body.justification },
7622
- ...body.memory && { memory: body.memory }
7623
- };
7624
- try {
7625
- if (manager.isRunning(mindName)) {
7626
- 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);
7627
8091
  }
7628
- manager.setPendingContext(mindName, context);
7629
- await manager.startMind(mindName);
7630
- } catch (e) {
7631
- restartWarning = `Merge succeeded but mind restart failed: ${e instanceof Error ? e.message : String(e)}`;
7632
- 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
+ );
7633
8098
  }
7634
- return c.json({ ok: true, ...restartWarning && { warning: restartWarning } });
7635
8099
  }).delete("/:name/variants/:variant", requireSelf(), async (c) => {
7636
8100
  const mindName = c.req.param("name");
7637
8101
  const variantName = c.req.param("variant");
@@ -7659,7 +8123,8 @@ var channelSettingsSchema = z10.object({
7659
8123
  private: z10.boolean().optional()
7660
8124
  });
7661
8125
  var createSchema2 = z10.object({
7662
- name: z10.string().min(1).max(50).regex(/^[a-z0-9][a-z0-9-]*$/, "Channel names must be lowercase alphanumeric with hyphens")
8126
+ name: z10.string().min(1).max(50).regex(/^[a-z0-9][a-z0-9-]*$/, "Channel names must be lowercase alphanumeric with hyphens"),
8127
+ participantNames: z10.array(z10.string().min(1)).optional()
7663
8128
  }).merge(channelSettingsSchema);
7664
8129
  var inviteSchema = z10.object({
7665
8130
  username: z10.string().min(1)
@@ -7678,10 +8143,21 @@ var app22 = new Hono26().get("/", async (c) => {
7678
8143
  }).post("/", zValidator10("json", createSchema2), async (c) => {
7679
8144
  const user = c.get("user");
7680
8145
  const body = c.req.valid("json");
8146
+ const { name, participantNames, ...settings } = body;
8147
+ const participantIds = [];
8148
+ for (const username of participantNames ?? []) {
8149
+ let member = await getUserByUsername(username);
8150
+ if (!member && await findMind(username)) {
8151
+ member = await getOrCreateMindUser(username);
8152
+ }
8153
+ if (!member) {
8154
+ return c.json({ error: `User not found: ${username}` }, 404);
8155
+ }
8156
+ participantIds.push(member.id);
8157
+ }
8158
+ let ch;
7681
8159
  try {
7682
- const { name, ...settings } = body;
7683
- const ch = await createChannel(name, user.id, settings);
7684
- return c.json({ ...ch, channel_name: name }, 201);
8160
+ ch = await createChannel(name, user.id, settings);
7685
8161
  } catch (err) {
7686
8162
  const cause = err instanceof Error ? err.cause : null;
7687
8163
  if (cause && /UNIQUE/i.test(cause.extendedCode ?? cause.message ?? "")) {
@@ -7689,6 +8165,23 @@ var app22 = new Hono26().get("/", async (c) => {
7689
8165
  }
7690
8166
  throw err;
7691
8167
  }
8168
+ try {
8169
+ for (const id of participantIds) {
8170
+ await joinChannel(ch.id, id);
8171
+ }
8172
+ } catch (err) {
8173
+ console.error("[channels] join failed during channel create:", err);
8174
+ try {
8175
+ await deleteConversation(ch.id);
8176
+ } catch (cleanupErr) {
8177
+ console.error(
8178
+ `[channels] failed to roll back channel ${ch.id} after join failure:`,
8179
+ cleanupErr
8180
+ );
8181
+ }
8182
+ throw err;
8183
+ }
8184
+ return c.json({ ...ch, channel_name: name }, 201);
7692
8185
  }).get("/:name", async (c) => {
7693
8186
  const name = c.req.param("name");
7694
8187
  const ch = await getChannelByName(name);
@@ -8023,11 +8516,11 @@ var app23 = new Hono27().get("/:name/conversations/:id/events", async (c) => {
8023
8516
  if (!stream.aborted) console.error("[chat] SSE ping error:", err);
8024
8517
  });
8025
8518
  }, 15e3);
8026
- await new Promise((resolve17) => {
8519
+ await new Promise((resolve19) => {
8027
8520
  stream.onAbort(() => {
8028
8521
  unsubscribe();
8029
8522
  clearInterval(keepAlive);
8030
- resolve17();
8523
+ resolve19();
8031
8524
  });
8032
8525
  });
8033
8526
  });
@@ -8241,10 +8734,10 @@ app25.route("/api/conversations", conversations_default);
8241
8734
  var app_default = app25;
8242
8735
 
8243
8736
  // packages/daemon/src/web/server.ts
8244
- import { existsSync as existsSync10 } from "fs";
8737
+ import { existsSync as existsSync11 } from "fs";
8245
8738
  import { readFile as readFile2, stat as stat2 } from "fs/promises";
8246
8739
  import { createServer as createHttpsServer } from "https";
8247
- import { dirname as dirname2, extname as extname3, resolve as resolve15 } from "path";
8740
+ import { dirname as dirname2, extname as extname3, resolve as resolve17 } from "path";
8248
8741
  import { serve } from "@hono/node-server";
8249
8742
  var MIME_TYPES2 = {
8250
8743
  ".html": "text/html",
@@ -8263,8 +8756,8 @@ async function startServer({
8263
8756
  let assetsDir = "";
8264
8757
  let searchDir = dirname2(new URL(import.meta.url).pathname);
8265
8758
  for (let i = 0; i < 5; i++) {
8266
- const candidate = resolve15(searchDir, "dist", "web-assets");
8267
- if (existsSync10(candidate)) {
8759
+ const candidate = resolve17(searchDir, "dist", "web-assets");
8760
+ if (existsSync11(candidate)) {
8268
8761
  assetsDir = candidate;
8269
8762
  break;
8270
8763
  }
@@ -8274,7 +8767,7 @@ async function startServer({
8274
8767
  app_default.get("*", async (c) => {
8275
8768
  const urlPath = new URL(c.req.url).pathname;
8276
8769
  if (urlPath.startsWith("/api/") || urlPath.startsWith("/ext/")) return c.notFound();
8277
- const filePath = resolve15(assetsDir, urlPath.slice(1));
8770
+ const filePath = resolve17(assetsDir, urlPath.slice(1));
8278
8771
  if (!filePath.startsWith(assetsDir)) return c.text("Forbidden", 403);
8279
8772
  const s = await stat2(filePath).catch(() => null);
8280
8773
  if (s?.isFile()) {
@@ -8287,7 +8780,7 @@ async function startServer({
8287
8780
  const cacheControl = isHashed ? "public, max-age=31536000, immutable" : "no-cache";
8288
8781
  return c.body(body, 200, { "Content-Type": mime, "Cache-Control": cacheControl });
8289
8782
  }
8290
- const indexPath = resolve15(assetsDir, "index.html");
8783
+ const indexPath = resolve17(assetsDir, "index.html");
8291
8784
  const indexStat = await stat2(indexPath).catch(() => null);
8292
8785
  if (indexStat?.isFile()) {
8293
8786
  const body = await readFile2(indexPath, "utf-8");
@@ -8304,10 +8797,10 @@ async function startServer({
8304
8797
  createServer: createHttpsServer,
8305
8798
  serverOptions: { key: tls.key, cert: tls.cert }
8306
8799
  });
8307
- await new Promise((resolve17, reject) => {
8800
+ await new Promise((resolve19, reject) => {
8308
8801
  server2.on("listening", () => {
8309
8802
  logger_default.info("Volute UI running (https)", { hostname, port });
8310
- resolve17();
8803
+ resolve19();
8311
8804
  });
8312
8805
  server2.on("error", (err) => {
8313
8806
  reject(err);
@@ -8315,13 +8808,13 @@ async function startServer({
8315
8808
  });
8316
8809
  const internalPort = port + 1;
8317
8810
  const internalServer = serve({ fetch: app_default.fetch, port: internalPort, hostname: "127.0.0.1" });
8318
- await new Promise((resolve17, reject) => {
8811
+ await new Promise((resolve19, reject) => {
8319
8812
  internalServer.on("listening", () => {
8320
8813
  logger_default.info("Volute API running (http, internal)", {
8321
8814
  hostname: "127.0.0.1",
8322
8815
  port: internalPort
8323
8816
  });
8324
- resolve17();
8817
+ resolve19();
8325
8818
  });
8326
8819
  internalServer.on("error", (err) => {
8327
8820
  reject(err);
@@ -8330,10 +8823,10 @@ async function startServer({
8330
8823
  return { server: server2, internalPort };
8331
8824
  }
8332
8825
  const server = serve({ fetch: app_default.fetch, port, hostname });
8333
- await new Promise((resolve17, reject) => {
8826
+ await new Promise((resolve19, reject) => {
8334
8827
  server.on("listening", () => {
8335
8828
  logger_default.info("Volute API running (http)", { hostname, port });
8336
- resolve17();
8829
+ resolve19();
8337
8830
  });
8338
8831
  server.on("error", (err) => {
8339
8832
  reject(err);
@@ -8344,18 +8837,18 @@ async function startServer({
8344
8837
 
8345
8838
  // packages/daemon/src/daemon.ts
8346
8839
  if (!process.env.VOLUTE_HOME) {
8347
- process.env.VOLUTE_HOME = resolve16(homedir3(), ".volute");
8840
+ process.env.VOLUTE_HOME = resolve18(homedir3(), ".volute");
8348
8841
  }
8349
8842
  if (process.env.VOLUTE_TIMEZONE && !process.env.TZ) {
8350
8843
  process.env.TZ = process.env.VOLUTE_TIMEZONE;
8351
8844
  }
8352
8845
  function writeDaemonConfig(systemDir, config2, token) {
8353
- const configPath = resolve16(systemDir, "daemon.json");
8354
- writeFileSync9(configPath, `${JSON.stringify(config2, null, 2)}
8846
+ const configPath = resolve18(systemDir, "daemon.json");
8847
+ writeFileSync10(configPath, `${JSON.stringify(config2, null, 2)}
8355
8848
  `, { mode: 420 });
8356
8849
  chmodSync(configPath, 420);
8357
- const tokenPath = resolve16(systemDir, "daemon-token");
8358
- writeFileSync9(tokenPath, `${token}
8850
+ const tokenPath = resolve18(systemDir, "daemon-token");
8851
+ writeFileSync10(tokenPath, `${token}
8359
8852
  `, { mode: 384 });
8360
8853
  chmodSync(tokenPath, 384);
8361
8854
  }
@@ -8365,7 +8858,7 @@ async function startDaemon(opts) {
8365
8858
  const home = voluteHome();
8366
8859
  const systemDir = voluteSystemDir();
8367
8860
  if (!opts.foreground) {
8368
- const rotatingLog = new RotatingLog(resolve16(systemDir, "daemon.log"));
8861
+ const rotatingLog = new RotatingLog(resolve18(systemDir, "daemon.log"));
8369
8862
  logger_default.setOutput((line) => rotatingLog.write(`${line}
8370
8863
  `));
8371
8864
  const write = (...args) => rotatingLog.write(`${format(...args)}
@@ -8375,32 +8868,32 @@ async function startDaemon(opts) {
8375
8868
  console.warn = write;
8376
8869
  console.info = write;
8377
8870
  }
8378
- const DAEMON_PID_PATH = resolve16(systemDir, "daemon.pid");
8379
- const DAEMON_JSON_PATH = resolve16(systemDir, "daemon.json");
8380
- const DAEMON_TOKEN_PATH = resolve16(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");
8381
8874
  mkdirSync10(home, { recursive: true });
8382
8875
  ensureSystemDir();
8383
- const { migrateConfigSecrets, migrateSetupCompleted } = await import("./setup-KFYVWTXG.js");
8876
+ const { migrateConfigSecrets, migrateSetupCompleted } = await import("./setup-TCN7VH5U.js");
8384
8877
  migrateConfigSecrets();
8385
8878
  migrateSetupCompleted();
8386
- const { migrateAiModelQualification } = await import("./ai-service-CVZEYKTK.js");
8879
+ const { migrateAiModelQualification } = await import("./ai-service-A7FS7EC3.js");
8387
8880
  migrateAiModelQualification();
8388
- await (await import("./db-FO6LXYCW.js")).getDb();
8881
+ await (await import("./db-F6UMFRYD.js")).getDb();
8389
8882
  try {
8390
8883
  const { eq: eq8, and: and6 } = await import("drizzle-orm");
8391
- const { users: users2 } = await import("./schema-VEVO7HG6.js");
8392
- 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();
8393
8886
  await db.update(users2).set({ role: "system" }).where(and6(eq8(users2.user_type, "system"), eq8(users2.role, "user")));
8394
8887
  } catch (err) {
8395
8888
  logger_default.warn("failed to migrate system user role", logger_default.errorData(err));
8396
8889
  }
8397
8890
  try {
8398
- const { migrateAvatarSizes } = await import("./avatar-image-7S3WA77A.js");
8891
+ const { migrateAvatarSizes } = await import("./avatar-image-6ATV2IOZ.js");
8399
8892
  await migrateAvatarSizes();
8400
8893
  } catch (err) {
8401
8894
  logger_default.warn("avatar size migration failed", logger_default.errorData(err));
8402
8895
  }
8403
- const { initSandbox } = await import("./sandbox-G4X4K3UN.js");
8896
+ const { initSandbox } = await import("./sandbox-XPYA7UIF.js");
8404
8897
  await initSandbox();
8405
8898
  try {
8406
8899
  await syncBuiltinSkills();
@@ -8427,7 +8920,12 @@ async function startDaemon(opts) {
8427
8920
  logger_default.warn("failed to ensure #system channel", logger_default.errorData(err));
8428
8921
  }
8429
8922
  try {
8430
- const { getOrCreateSystemUser: getOrCreateSystemUser2 } = await import("./auth-A4VGHADI.js");
8923
+ await backfillSystemChannelMembers();
8924
+ } catch (err) {
8925
+ logger_default.warn("failed to backfill minds into #system", logger_default.errorData(err));
8926
+ }
8927
+ try {
8928
+ const { getOrCreateSystemUser: getOrCreateSystemUser2 } = await import("./auth-ABYLOEZ7.js");
8431
8929
  await getOrCreateSystemUser2();
8432
8930
  } catch (err) {
8433
8931
  logger_default.warn(
@@ -8436,12 +8934,12 @@ async function startDaemon(opts) {
8436
8934
  );
8437
8935
  }
8438
8936
  const token = process.env.VOLUTE_DAEMON_TOKEN || randomBytes3(32).toString("hex");
8439
- const { readGlobalConfig: readGlobalConfig2 } = await import("./setup-KFYVWTXG.js");
8937
+ const { readGlobalConfig: readGlobalConfig2 } = await import("./setup-TCN7VH5U.js");
8440
8938
  const globalCfg = readGlobalConfig2();
8441
8939
  let tls;
8442
8940
  if (opts.tailscale || globalCfg.tailscale) {
8443
8941
  try {
8444
- const { getTailscaleTls } = await import("./tailscale-MCLY5GYE.js");
8942
+ const { getTailscaleTls } = await import("./tailscale-AMMVKDXB.js");
8445
8943
  const tlsConfig = await getTailscaleTls();
8446
8944
  tls = { key: tlsConfig.key, cert: tlsConfig.cert };
8447
8945
  logger_default.info("Tailscale HTTPS enabled", { hostname: tlsConfig.hostname });
@@ -8468,7 +8966,7 @@ async function startDaemon(opts) {
8468
8966
  process.env.VOLUTE_DAEMON_TOKEN = token;
8469
8967
  process.env.VOLUTE_DAEMON_PORT = String(daemonPort);
8470
8968
  process.env.VOLUTE_DAEMON_HOSTNAME = hostname;
8471
- writeFileSync9(DAEMON_PID_PATH, myPid, { mode: 420 });
8969
+ writeFileSync10(DAEMON_PID_PATH, myPid, { mode: 420 });
8472
8970
  const daemonConfig = { port, hostname };
8473
8971
  if (internalPort) daemonConfig.internalPort = internalPort;
8474
8972
  if (tls) daemonConfig.tls = true;
@@ -8492,6 +8990,12 @@ async function startDaemon(opts) {
8492
8990
  const unsubscribeWebhook = initWebhook();
8493
8991
  const orphanedTurns = await completeOrphanedTurns();
8494
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
+ }
8495
8999
  const allMinds = await readAllMinds();
8496
9000
  const runningEntries = allMinds.filter((e) => e.running && e.mindType !== "spirit");
8497
9001
  {
@@ -8521,14 +9025,14 @@ async function startDaemon(opts) {
8521
9025
  await Promise.all(workers);
8522
9026
  }
8523
9027
  try {
8524
- const { isSetupComplete: isSetupComplete2 } = await import("./setup-KFYVWTXG.js");
9028
+ const { isSetupComplete: isSetupComplete2 } = await import("./setup-TCN7VH5U.js");
8525
9029
  if (isSetupComplete2()) {
8526
- const { ensureSpiritProject, syncSpiritTemplate } = await import("./spirit-WXN4IDBF.js");
8527
- const { startSpiritFull } = await import("./mind-service-JFY7UG5E.js");
9030
+ const { ensureSpiritProject, syncSpiritTemplate } = await import("./spirit-SSJMYC6A.js");
9031
+ const { startSpiritFull } = await import("./mind-service-42GDO4KH.js");
8528
9032
  await ensureSpiritProject();
8529
9033
  const spiritEntry = await findMind(SPIRIT_NAME);
8530
9034
  if (spiritEntry?.dir) {
8531
- const { registerMindDir } = await import("./delivery-router-3QFG2ZEX.js");
9035
+ const { registerMindDir } = await import("./delivery-router-VUOL5YSA.js");
8532
9036
  registerMindDir(SPIRIT_NAME, spiritEntry.dir);
8533
9037
  }
8534
9038
  await syncSpiritTemplate();
@@ -8545,7 +9049,7 @@ async function startDaemon(opts) {
8545
9049
  bridgeManager.startBridges(daemonPort).catch((err) => {
8546
9050
  logger_default.warn("failed to start bridges", logger_default.errorData(err));
8547
9051
  });
8548
- import("./cloud-sync-LQTVDTQ3.js").then(
9052
+ import("./cloud-sync-VFDTNNCA.js").then(
8549
9053
  ({ consumeQueuedMessages }) => consumeQueuedMessages().catch((err) => {
8550
9054
  logger_default.warn("failed to consume queued cloud messages", logger_default.errorData(err));
8551
9055
  })
@@ -8553,7 +9057,7 @@ async function startDaemon(opts) {
8553
9057
  logger_default.warn("failed to load cloud-sync module", logger_default.errorData(err));
8554
9058
  });
8555
9059
  try {
8556
- const { backfillTemplateHashes, notifyVersionUpdate, warnStaleTemplates } = await import("./version-notify-XRGTEEDK.js");
9060
+ const { backfillTemplateHashes, notifyVersionUpdate, warnStaleTemplates } = await import("./version-notify-WFA5XB5A.js");
8557
9061
  backfillTemplateHashes().then(() => warnStaleTemplates()).catch((err) => {
8558
9062
  logger_default.warn("failed to check template staleness", logger_default.errorData(err));
8559
9063
  });
@@ -8583,13 +9087,13 @@ async function startDaemon(opts) {
8583
9087
  logger_default.info(`running on ${hostname}:${port}, pid ${myPid}`);
8584
9088
  function cleanup() {
8585
9089
  try {
8586
- if (readFileSync10(DAEMON_PID_PATH, "utf-8").trim() === myPid) {
9090
+ if (readFileSync12(DAEMON_PID_PATH, "utf-8").trim() === myPid) {
8587
9091
  unlinkSync2(DAEMON_PID_PATH);
8588
9092
  }
8589
9093
  } catch {
8590
9094
  }
8591
9095
  try {
8592
- if (readFileSync10(DAEMON_TOKEN_PATH, "utf-8").trim() === token) {
9096
+ if (readFileSync12(DAEMON_TOKEN_PATH, "utf-8").trim() === token) {
8593
9097
  unlinkSync2(DAEMON_TOKEN_PATH);
8594
9098
  try {
8595
9099
  unlinkSync2(DAEMON_JSON_PATH);