volute 0.35.0 → 0.37.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 (167) hide show
  1. package/dist/{accept-ZBDVVCEU.js → accept-AHAOUFBK.js} +4 -4
  2. package/dist/activity-events-N6HCHU4P.js +15 -0
  3. package/dist/{ai-service-LURBEDDB.js → ai-service-C2YNARGH.js} +6 -6
  4. package/dist/{api-client-3A77HMH7.js → api-client-LC5YRA32.js} +1 -1
  5. package/dist/{archive-ESU2FUN4.js → archive-AWIJTVQV.js} +4 -4
  6. package/dist/{auth-WX4TESEI.js → auth-2QOOPMBX.js} +6 -6
  7. package/dist/{bridge-PXIO6PS2.js → bridge-F3ZJEKDN.js} +4 -4
  8. package/dist/{chat-QXAJF3FU.js → chat-5Y4FD77E.js} +9 -9
  9. package/dist/{chunk-BDYXIWA5.js → chunk-2NHRJ3YO.js} +13 -2
  10. package/dist/{chunk-AN2W47GW.js → chunk-3F7XK5Q7.js} +2 -2
  11. package/dist/{chunk-AOB6GVRM.js → chunk-46DYYHN6.js} +8 -3
  12. package/dist/{chunk-WZRZFFCL.js → chunk-5DPRTREW.js} +146 -186
  13. package/dist/{chunk-QWTR6AWZ.js → chunk-7AZQFSOV.js} +2 -2
  14. package/dist/{chunk-BMZQYACC.js → chunk-A6FLW5XD.js} +19 -4
  15. package/dist/{chunk-N5LMGYXX.js → chunk-BIEWHAAM.js} +2 -2
  16. package/dist/{chunk-5N7Y5WAM.js → chunk-CJ26DXZL.js} +29 -13
  17. package/dist/{chunk-ZSR72JB3.js → chunk-CU6OFXMM.js} +1 -1
  18. package/dist/{chunk-N446KRP7.js → chunk-GVVVMZ4J.js} +2 -2
  19. package/dist/chunk-K3NQKI34.js +10 -0
  20. package/dist/{chunk-F7ZNLYKZ.js → chunk-KAB6UGOL.js} +2 -2
  21. package/dist/{chunk-WJPROOU5.js → chunk-KBRGHKVU.js} +681 -3726
  22. package/dist/{chunk-PWQ2ITYG.js → chunk-KXXJYY62.js} +6 -6
  23. package/dist/{chunk-NJK5SDGR.js → chunk-LGNUFVMR.js} +1 -1
  24. package/dist/chunk-LQ6Z4FXN.js +87 -0
  25. package/dist/{chunk-J6CJQDWI.js → chunk-MQRS4J24.js} +2 -2
  26. package/dist/{chunk-CORXD635.js → chunk-N42QMDID.js} +3 -3
  27. package/dist/{chunk-IJHIXLVN.js → chunk-NYP3LBIV.js} +15 -13
  28. package/dist/{chunk-FT5KETXZ.js → chunk-ORNY3MZR.js} +4 -4
  29. package/dist/{chunk-A2ZLHBHG.js → chunk-PMMHVSCR.js} +2 -2
  30. package/dist/chunk-QQQI6ISK.js +853 -0
  31. package/dist/chunk-RG5TOL4O.js +18 -0
  32. package/dist/{chunk-VHJRZM2S.js → chunk-SNW2NPP4.js} +2 -2
  33. package/dist/chunk-SZJWC2GA.js +125 -0
  34. package/dist/{chunk-BKF4WQCY.js → chunk-T2TP6ZC6.js} +20 -8
  35. package/dist/{chunk-2TGZJFAT.js → chunk-TNZ5XQA4.js} +3 -3
  36. package/dist/{chunk-QCH6K235.js → chunk-UI7RPV2B.js} +1 -1
  37. package/dist/{chunk-XRQSAMX2.js → chunk-UIM5NHPP.js} +3 -3
  38. package/dist/{chunk-5XJYUFZH.js → chunk-UQFYNZKT.js} +76 -30
  39. package/dist/{chunk-OTC67N2Z.js → chunk-WC635OPK.js} +1 -1
  40. package/dist/{chunk-BV65KRHM.js → chunk-XLBQYIHH.js} +2 -2
  41. package/dist/{chunk-VY3RB2V7.js → chunk-Z6TIXE77.js} +3 -3
  42. package/dist/cli.js +24 -24
  43. package/dist/{clock-HSEKS5AR.js → clock-BMLJ2TR6.js} +8 -8
  44. package/dist/{cloud-sync-6JL4C24T.js → cloud-sync-OIX576NA.js} +20 -21
  45. package/dist/{config-UTS7QULS.js → config-QB7W3Z7P.js} +4 -4
  46. package/dist/connectors/discord-bridge.js +4 -4
  47. package/dist/connectors/slack-bridge.js +4 -4
  48. package/dist/connectors/telegram-bridge.js +4 -4
  49. package/dist/{conversations-2PW57WO2.js → conversations-G6YRSABR.js} +16 -10
  50. package/dist/{create-5BPOOJAN.js → create-C3BBFYV7.js} +4 -4
  51. package/dist/{create-UVCK2CS6.js → create-PN73742N.js} +4 -4
  52. package/dist/{daemon-client-RVIKXGFQ.js → daemon-client-2MIPKY3E.js} +1 -1
  53. package/dist/{daemon-restart-HSZ3BCX5.js → daemon-restart-L2O6L7NR.js} +9 -9
  54. package/dist/daemon.js +690 -1281
  55. package/dist/db-CBOCDYVA.js +9 -0
  56. package/dist/{db-BDMH4SZ2.js → db-IJL6B26S.js} +1 -1
  57. package/dist/{delete-L5PAVDGQ.js → delete-NLXES2C7.js} +3 -3
  58. package/dist/delivery-manager-4PVBUZJB.js +30 -0
  59. package/dist/{delivery-router-HEJSJAHQ.js → delivery-router-QTFEZ26O.js} +5 -5
  60. package/dist/down-25L2RKCQ.js +17 -0
  61. package/dist/echo-text-T5ZLGMA7.js +31 -0
  62. package/dist/{env-E4XHO2BI.js → env-IQ6Q2333.js} +6 -6
  63. package/dist/exec-ONYZEA5B.js +17 -0
  64. package/dist/{export-OAS6QVBN.js → export-JPDBQESV.js} +6 -6
  65. package/dist/{extension-D74CNM7G.js → extension-LZYHBNLV.js} +26 -7
  66. package/dist/extensions-CLYXNGYB.js +50 -0
  67. package/dist/{files-CWTK6V3H.js → files-LAQ3NXQK.js} +7 -7
  68. package/dist/{import-5A3T7QV4.js → import-EROF27RH.js} +12 -11
  69. package/dist/{isolation-TK5RX2WM.js → isolation-G5J3MTKU.js} +4 -4
  70. package/dist/{join-DF5XSJAC.js → join-6SZCA5FX.js} +3 -3
  71. package/dist/{list-PDMQM7ZV.js → list-KHJZJPEJ.js} +11 -5
  72. package/dist/{login-7TE6CIZF.js → login-F6YMAVLE.js} +6 -6
  73. package/dist/{login-GOTAYLXP.js → login-GYTH67ES.js} +4 -4
  74. package/dist/{logout-T4XS6LRU.js → logout-HHPH52KZ.js} +6 -6
  75. package/dist/{logout-6KIA74EV.js → logout-YHQLOFLR.js} +4 -4
  76. package/dist/message-delivery-N2V5APCS.js +40 -0
  77. package/dist/{mind-5IEYKV7I.js → mind-M57ET546.js} +19 -19
  78. package/dist/mind-activity-tracker-42ENM32S.js +18 -0
  79. package/dist/{mind-history-IE2QH7U5.js → mind-history-WHCNZ6I5.js} +86 -19
  80. package/dist/{mind-list-GEWHWAL4.js → mind-list-H3HC2ZRG.js} +4 -4
  81. package/dist/mind-manager-LS2AIXHQ.js +30 -0
  82. package/dist/{mind-profile-DCBDVF5B.js → mind-profile-7VYRJGFZ.js} +2 -2
  83. package/dist/mind-service-UDXF5WC2.js +36 -0
  84. package/dist/{mind-sleep-ITCF6OQA.js → mind-sleep-ZL5ZXFTM.js} +4 -4
  85. package/dist/{mind-status-X4SX3YUG.js → mind-status-ZWULKOUO.js} +4 -4
  86. package/dist/{mind-wake-KXMKMGWX.js → mind-wake-HK5ORGUK.js} +4 -4
  87. package/dist/{package-D2FSVFAX.js → package-5FGU5QNP.js} +6 -6
  88. package/dist/{read-67VRP2DO.js → read-CP7MYMJQ.js} +8 -8
  89. package/dist/{read-stdin-3X5VYKNS.js → read-stdin-4B5UYPPM.js} +1 -1
  90. package/dist/{register-SB7NXCOE.js → register-XOBFEMI4.js} +4 -4
  91. package/dist/{registry-GBSNW3HG.js → registry-KMELPC3X.js} +3 -3
  92. package/dist/{reject-MUR2KWJ4.js → reject-43AGXB6B.js} +4 -4
  93. package/dist/{restart-5EGG4JXU.js → restart-O5QIYQJT.js} +5 -5
  94. package/dist/{sandbox-R37VIU36.js → sandbox-PQYEICEF.js} +6 -6
  95. package/dist/scheduler-NTC74JYH.js +30 -0
  96. package/dist/{schema-XVZ2CLKW.js → schema-K575EBPE.js} +4 -2
  97. package/dist/{seed-EQORWX77.js → seed-55VC3A57.js} +2 -2
  98. package/dist/{seed-check-KJNTL72M.js → seed-check-HZPVFJKZ.js} +2 -2
  99. package/dist/{seed-cmd-ZM2XGVU2.js → seed-cmd-2KOEQZK6.js} +4 -4
  100. package/dist/{seed-create-DRWGGHEI.js → seed-create-Y2Z5JWBB.js} +6 -6
  101. package/dist/{seed-sprout-JYXGXOP3.js → seed-sprout-OLSIWXZN.js} +15 -15
  102. package/dist/{send-JBJJQ7CA.js → send-7CIP5GLS.js} +8 -8
  103. package/dist/{service-WNPCNHOX.js → service-YMHWPDXW.js} +6 -6
  104. package/dist/{setup-BJ4YAY26.js → setup-6Z34JJEB.js} +32 -37
  105. package/dist/{setup-RHJRFURI.js → setup-PF7JSFMO.js} +6 -4
  106. package/dist/{skill-TAAKEYBV.js → skill-ICN6Y2ZF.js} +6 -6
  107. package/dist/skills/tending/SKILL.md +52 -0
  108. package/dist/{skills-EKMCQ46K.js → skills-FDMLJGZ3.js} +8 -8
  109. package/dist/sleep-manager-TQP5ZJI5.js +34 -0
  110. package/dist/spirit-SM6ARJ2N.js +24 -0
  111. package/dist/{split-AWVOYOPZ.js → split-5YBEQTBF.js} +3 -3
  112. package/dist/{sprout-HE4TITMK.js → sprout-G6G57IOY.js} +2 -2
  113. package/dist/src-LT6ZBYYX.js +2133 -0
  114. package/dist/src-O4PRLMKM.js +425 -0
  115. package/dist/src-OYWRPLC6.js +617 -0
  116. package/dist/{start-3UXOPXQG.js → start-LMXXRR3X.js} +5 -5
  117. package/dist/{status-ZK34WYIM.js → status-MC2P7DBG.js} +7 -7
  118. package/dist/{stop-3XYIBGFM.js → stop-TWDKVEUX.js} +5 -5
  119. package/dist/system-chat-NNXYCSVL.js +34 -0
  120. package/dist/{systems-O43WGQY6.js → systems-Y2WZV2K4.js} +7 -7
  121. package/dist/{tailscale-ZIZ2HWJ5.js → tailscale-LTYNKIPZ.js} +4 -4
  122. package/dist/template-hash-SSIBEEYK.js +9 -0
  123. package/dist/up-5JXV6BZS.js +19 -0
  124. package/dist/{update-ANE5ZM7F.js → update-UOP2INF2.js} +7 -7
  125. package/dist/{update-check-UV55CBEP.js → update-check-IKS7SGK5.js} +4 -4
  126. package/dist/{upgrade-ZMDGC7M2.js → upgrade-RXFZR5FI.js} +3 -3
  127. package/dist/{variant-QWL2WSRI.js → variant-HHDTW74J.js} +1 -1
  128. package/dist/{version-notify-FXSEMXWW.js → version-notify-CSE4NBYM.js} +22 -23
  129. package/dist/{volute-config-D2XVS2YI.js → volute-config-TS62GS6A.js} +2 -2
  130. package/dist/web-assets/assets/index-B3xLeex8.js +75 -0
  131. package/dist/web-assets/assets/index-Dr4A90Lo.css +1 -0
  132. package/dist/web-assets/index.html +2 -2
  133. package/drizzle/0006_channels.sql +17 -0
  134. package/drizzle/0007_drop_conversation_name_title.sql +11 -0
  135. package/drizzle/0008_performance_indexes.sql +6 -0
  136. package/drizzle/meta/0006_snapshot.json +7 -0
  137. package/drizzle/meta/0007_snapshot.json +7 -0
  138. package/drizzle/meta/_journal.json +21 -0
  139. package/package.json +5 -5
  140. package/templates/_base/home/.config/routes.json +2 -2
  141. package/templates/_base/home/VOLUTE.md +1 -2
  142. package/templates/_base/src/lib/context-breakdown.ts +22 -16
  143. package/templates/_base/src/lib/format-prefix.ts +1 -7
  144. package/templates/claude/.init/.config/routes.json +2 -2
  145. package/templates/codex/.init/.config/routes.json +2 -2
  146. package/templates/pi/.init/.config/routes.json +2 -2
  147. package/dist/activity-events-ZW4SDL2C.js +0 -15
  148. package/dist/chunk-7KJOFUNN.js +0 -22
  149. package/dist/chunk-MDJGMOSD.js +0 -207
  150. package/dist/db-BVBJ57TU.js +0 -9
  151. package/dist/delivery-manager-H5ZVBMCQ.js +0 -31
  152. package/dist/down-74VXM45A.js +0 -17
  153. package/dist/exec-PY7THYH4.js +0 -17
  154. package/dist/extensions-XDDFY72A.js +0 -49
  155. package/dist/lib-DYEZMGW7.js +0 -6588
  156. package/dist/message-delivery-GRC4W6P7.js +0 -41
  157. package/dist/mind-activity-tracker-QBLIV7ZJ.js +0 -18
  158. package/dist/mind-manager-HFLB5653.js +0 -31
  159. package/dist/mind-service-X2CAA6W6.js +0 -37
  160. package/dist/scheduler-Y7O4CJXL.js +0 -31
  161. package/dist/sleep-manager-7KFK3USC.js +0 -35
  162. package/dist/spirit-ZFRDXMG7.js +0 -23
  163. package/dist/system-chat-IDPHYHY4.js +0 -35
  164. package/dist/template-hash-A7FNHTB7.js +0 -9
  165. package/dist/up-77ICEDEW.js +0 -19
  166. package/dist/web-assets/assets/index-BhxWKvbB.css +0 -1
  167. package/dist/web-assets/assets/index-CHVKJ9II.js +0 -75
package/dist/daemon.js CHANGED
@@ -1,12 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- checkForUpdate,
4
- checkForUpdateCached,
5
- getCurrentVersion
6
- } from "./chunk-OTC67N2Z.js";
7
- import {
8
- computeTemplateHash
9
- } from "./chunk-F7ZNLYKZ.js";
10
2
  import {
11
3
  acceptPending,
12
4
  formatFileSize,
@@ -14,7 +6,39 @@ import {
14
6
  rejectPending,
15
7
  stageFile,
16
8
  validateFilePath
17
- } from "./chunk-BV65KRHM.js";
9
+ } from "./chunk-XLBQYIHH.js";
10
+ import {
11
+ findOpenClawSession,
12
+ importOpenClawConnectors,
13
+ importPiSession,
14
+ parseNameFromIdentity
15
+ } from "./chunk-SZJWC2GA.js";
16
+ import {
17
+ checkForUpdate,
18
+ checkForUpdateCached,
19
+ getCurrentVersion
20
+ } from "./chunk-WC635OPK.js";
21
+ import {
22
+ isHomeOnlyArchive
23
+ } from "./chunk-BIEWHAAM.js";
24
+ import {
25
+ computeTemplateHash
26
+ } from "./chunk-KAB6UGOL.js";
27
+ import {
28
+ PLATFORMS,
29
+ echoTextToChannel,
30
+ getPlatformDriver,
31
+ routeOutboundBridge
32
+ } from "./chunk-QQQI6ISK.js";
33
+ import {
34
+ getBridgeConfig,
35
+ readBridgesConfig,
36
+ removeBridgeConfig,
37
+ removeChannelMapping,
38
+ resolveChannelMapping,
39
+ setBridgeConfig,
40
+ setChannelMapping
41
+ } from "./chunk-LQ6Z4FXN.js";
18
42
  import {
19
43
  PROMPT_DEFAULTS,
20
44
  PROMPT_KEYS,
@@ -31,6 +55,7 @@ import {
31
55
  generateSystemReply,
32
56
  getActiveTurnId,
33
57
  getAllDiscoveredExtensions,
58
+ getAllDiscoveredExtensionsDetailed,
34
59
  getDeliveryManager,
35
60
  getExtensionStandardSkills,
36
61
  getLastToolUseEventId,
@@ -76,15 +101,22 @@ import {
76
101
  trackToolUse,
77
102
  uninstallNpmExtension,
78
103
  writeSystemsConfig
79
- } from "./chunk-WJPROOU5.js";
80
- import "./chunk-5XJYUFZH.js";
104
+ } from "./chunk-KBRGHKVU.js";
105
+ import {
106
+ loadMergedEnv,
107
+ mindEnvPath,
108
+ readEnv,
109
+ sharedEnvPath,
110
+ writeEnv
111
+ } from "./chunk-PMMHVSCR.js";
112
+ import "./chunk-UQFYNZKT.js";
81
113
  import {
82
114
  applyInitFiles,
83
115
  composeTemplate,
84
116
  copyTemplateToDir,
85
117
  findTemplatesRoot,
86
118
  listFiles
87
- } from "./chunk-AOB6GVRM.js";
119
+ } from "./chunk-46DYYHN6.js";
88
120
  import {
89
121
  SEED_SKILLS,
90
122
  STANDARD_SKILLS,
@@ -104,16 +136,33 @@ import {
104
136
  syncBuiltinSkills,
105
137
  uninstallSkill,
106
138
  updateSkill
107
- } from "./chunk-IJHIXLVN.js";
139
+ } from "./chunk-NYP3LBIV.js";
140
+ import {
141
+ readVoluteConfig,
142
+ writeVoluteConfig
143
+ } from "./chunk-CU6OFXMM.js";
144
+ import "./chunk-KXXJYY62.js";
108
145
  import {
109
146
  extractTextContent
110
- } from "./chunk-QWTR6AWZ.js";
147
+ } from "./chunk-7AZQFSOV.js";
111
148
  import {
112
149
  getActiveMinds,
113
150
  onMindEvent,
114
151
  stopAll
115
- } from "./chunk-N446KRP7.js";
116
- import "./chunk-QHG4OMZL.js";
152
+ } from "./chunk-GVVVMZ4J.js";
153
+ import {
154
+ exec,
155
+ gitExec,
156
+ resolveVoluteBin
157
+ } from "./chunk-3F7XK5Q7.js";
158
+ import {
159
+ chownMindDir,
160
+ createMindUser,
161
+ deleteMindUser,
162
+ ensureVoluteGroup,
163
+ isIsolationEnabled,
164
+ wrapForIsolation
165
+ } from "./chunk-SNW2NPP4.js";
117
166
  import {
118
167
  approveUser,
119
168
  changePassword,
@@ -132,7 +181,28 @@ import {
132
181
  syncMindProfile,
133
182
  updateUserProfile,
134
183
  verifyUser
135
- } from "./chunk-XRQSAMX2.js";
184
+ } from "./chunk-UIM5NHPP.js";
185
+ import {
186
+ getAiConfig,
187
+ getAvailableModels,
188
+ getConfiguredProviders,
189
+ getEnabledModels,
190
+ getUtilityModel,
191
+ qualifyModelId,
192
+ removeAiConfig,
193
+ removeProviderConfig,
194
+ resolveApiKey,
195
+ resolveTemplate,
196
+ saveProviderConfig,
197
+ setEnabledModels,
198
+ setUtilityModel,
199
+ unqualifyModelId
200
+ } from "./chunk-ORNY3MZR.js";
201
+ import {
202
+ isSetupComplete,
203
+ readGlobalConfig,
204
+ writeGlobalConfig
205
+ } from "./chunk-A6FLW5XD.js";
136
206
  import {
137
207
  addMessage,
138
208
  createChannel,
@@ -140,7 +210,10 @@ import {
140
210
  deleteConversationForUser,
141
211
  findDMConversation,
142
212
  fireWebhook,
213
+ formatChannelSettings,
143
214
  getChannelByName,
215
+ getChannelName,
216
+ getChannelSettings,
144
217
  getConversation,
145
218
  getMessages,
146
219
  getMessagesPaginated,
@@ -159,83 +232,20 @@ import {
159
232
  markConversationRead,
160
233
  publish,
161
234
  setConversationPrivate,
162
- subscribe as subscribe2
163
- } from "./chunk-WZRZFFCL.js";
235
+ subscribe as subscribe2,
236
+ updateChannelSettings
237
+ } from "./chunk-5DPRTREW.js";
164
238
  import {
165
239
  broadcast,
166
240
  subscribe
167
- } from "./chunk-CORXD635.js";
168
- import {
169
- readVoluteConfig,
170
- writeVoluteConfig
171
- } from "./chunk-ZSR72JB3.js";
172
- import "./chunk-PWQ2ITYG.js";
173
- import {
174
- findBridgeForChannel,
175
- findOpenClawSession,
176
- getBridgeConfig,
177
- importOpenClawConnectors,
178
- importPiSession,
179
- parseNameFromIdentity,
180
- readBridgesConfig,
181
- removeBridgeConfig,
182
- removeChannelMapping,
183
- resolveChannelMapping,
184
- setBridgeConfig,
185
- setChannelMapping
186
- } from "./chunk-MDJGMOSD.js";
187
- import {
188
- loadMergedEnv,
189
- mindEnvPath,
190
- readEnv,
191
- sharedEnvPath,
192
- writeEnv
193
- } from "./chunk-A2ZLHBHG.js";
194
- import {
195
- isHomeOnlyArchive
196
- } from "./chunk-N5LMGYXX.js";
197
- import {
198
- getAiConfig,
199
- getAvailableModels,
200
- getConfiguredProviders,
201
- getEnabledModels,
202
- getUtilityModel,
203
- qualifyModelId,
204
- removeAiConfig,
205
- removeProviderConfig,
206
- resolveApiKey,
207
- resolveTemplate,
208
- saveProviderConfig,
209
- setEnabledModels,
210
- setUtilityModel,
211
- unqualifyModelId
212
- } from "./chunk-FT5KETXZ.js";
241
+ } from "./chunk-N42QMDID.js";
213
242
  import {
214
243
  logBuffer,
215
244
  logger_default
216
- } from "./chunk-BKF4WQCY.js";
217
- import {
218
- exec,
219
- gitExec,
220
- resolveVoluteBin
221
- } from "./chunk-AN2W47GW.js";
222
- import {
223
- chownMindDir,
224
- createMindUser,
225
- deleteMindUser,
226
- ensureVoluteGroup,
227
- isIsolationEnabled,
228
- wrapForIsolation
229
- } from "./chunk-VHJRZM2S.js";
230
- import {
231
- isSetupComplete,
232
- readGlobalConfig,
233
- writeGlobalConfig
234
- } from "./chunk-BMZQYACC.js";
245
+ } from "./chunk-T2TP6ZC6.js";
235
246
  import {
236
- buildVoluteSlug,
237
- slugify
238
- } from "./chunk-NJK5SDGR.js";
247
+ buildVoluteSlug
248
+ } from "./chunk-LGNUFVMR.js";
239
249
  import {
240
250
  addMind,
241
251
  addVariant,
@@ -258,9 +268,10 @@ import {
258
268
  validateMindName,
259
269
  voluteHome,
260
270
  voluteSystemDir
261
- } from "./chunk-BDYXIWA5.js";
271
+ } from "./chunk-2NHRJ3YO.js";
262
272
  import {
263
273
  activity,
274
+ channels,
264
275
  conversationParticipants,
265
276
  conversations,
266
277
  mindHistory,
@@ -269,16 +280,14 @@ import {
269
280
  systemPrompts,
270
281
  turns,
271
282
  users
272
- } from "./chunk-5N7Y5WAM.js";
273
- import {
274
- __export
275
- } from "./chunk-7KJOFUNN.js";
283
+ } from "./chunk-CJ26DXZL.js";
284
+ import "./chunk-K3NQKI34.js";
276
285
 
277
286
  // packages/daemon/src/daemon.ts
278
287
  import { randomBytes } from "crypto";
279
- import { mkdirSync as mkdirSync10, readFileSync as readFileSync11, unlinkSync as unlinkSync2, writeFileSync as writeFileSync9 } from "fs";
288
+ import { mkdirSync as mkdirSync10, readFileSync as readFileSync10, unlinkSync as unlinkSync2, writeFileSync as writeFileSync9 } from "fs";
280
289
  import { homedir as homedir3 } from "os";
281
- import { resolve as resolve17 } from "path";
290
+ import { resolve as resolve16 } from "path";
282
291
  import { format } from "util";
283
292
 
284
293
  // packages/daemon/src/lib/daemon/bridge-manager.ts
@@ -286,7 +295,7 @@ import { spawn } from "child_process";
286
295
  import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, unlinkSync, writeFileSync } from "fs";
287
296
  import { dirname, resolve as resolve2 } from "path";
288
297
 
289
- // packages/daemon/src/lib/bridge-defs.ts
298
+ // packages/daemon/src/lib/bridges/bridge-defs.ts
290
299
  import { existsSync, readFileSync } from "fs";
291
300
  import { resolve } from "path";
292
301
  var BUILTIN_DEFS = {
@@ -488,8 +497,8 @@ var BridgeManager = class {
488
497
  if (!tracked) return;
489
498
  this.stopping.add(platform);
490
499
  this.bridges.delete(platform);
491
- await new Promise((resolve18) => {
492
- tracked.child.on("exit", () => resolve18());
500
+ await new Promise((resolve17) => {
501
+ tracked.child.on("exit", () => resolve17());
493
502
  try {
494
503
  if (tracked.child.pid) {
495
504
  process.kill(-tracked.child.pid, "SIGTERM");
@@ -500,7 +509,7 @@ var BridgeManager = class {
500
509
  if (err instanceof Error && err.code !== "ESRCH") {
501
510
  blog.warn(`failed to stop bridge ${platform}`, logger_default.errorData(err));
502
511
  }
503
- resolve18();
512
+ resolve17();
504
513
  }
505
514
  setTimeout(() => {
506
515
  try {
@@ -511,7 +520,7 @@ var BridgeManager = class {
511
520
  }
512
521
  } catch {
513
522
  }
514
- resolve18();
523
+ resolve17();
515
524
  }, 5e3);
516
525
  });
517
526
  this.stopping.delete(platform);
@@ -599,7 +608,7 @@ function getBridgeManager() {
599
608
  return instance;
600
609
  }
601
610
 
602
- // packages/daemon/src/lib/history-cleanup.ts
611
+ // packages/daemon/src/lib/util/history-cleanup.ts
603
612
  import { and, eq, lt } from "drizzle-orm";
604
613
  var LOG_RETENTION_MS = 24 * 60 * 60 * 1e3;
605
614
  async function cleanExpiredLogs() {
@@ -816,6 +825,11 @@ import { timingSafeEqual } from "crypto";
816
825
  import { eq as eq2, lt as lt2 } from "drizzle-orm";
817
826
  import { getCookie } from "hono/cookie";
818
827
  import { createMiddleware } from "hono/factory";
828
+ var MIND_USER_CACHE_TTL = 5 * 60 * 1e3;
829
+ var mindUserCache = /* @__PURE__ */ new Map();
830
+ function invalidateMindUserCache(mindName) {
831
+ mindUserCache.delete(mindName);
832
+ }
819
833
  function isValidDaemonToken(token) {
820
834
  const expected = process.env.VOLUTE_DAEMON_TOKEN;
821
835
  if (!expected || token.length !== expected.length) return false;
@@ -906,7 +920,14 @@ var authMiddleware = createMiddleware(async (c, next) => {
906
920
  }
907
921
  const mindName = resolveMindToken(token);
908
922
  if (mindName) {
909
- const mindUser = await getOrCreateMindUser(mindName);
923
+ const cached = mindUserCache.get(mindName);
924
+ let mindUser;
925
+ if (cached && Date.now() - cached.ts < MIND_USER_CACHE_TTL) {
926
+ mindUser = cached.user;
927
+ } else {
928
+ mindUser = await getOrCreateMindUser(mindName);
929
+ mindUserCache.set(mindName, { user: mindUser, ts: Date.now() });
930
+ }
910
931
  c.set("user", mindUser);
911
932
  const mindSessionHeader = c.req.header("X-Volute-Session");
912
933
  if (mindSessionHeader) c.set("mindSession", mindSessionHeader);
@@ -963,10 +984,10 @@ var app = new Hono().post("/restart", requireAdmin, (c) => {
963
984
  stream.writeSSE({ data: JSON.stringify(entry) }).catch(() => {
964
985
  });
965
986
  });
966
- await new Promise((resolve18) => {
987
+ await new Promise((resolve17) => {
967
988
  stream.onAbort(() => {
968
989
  unsubscribe();
969
- resolve18();
990
+ resolve17();
970
991
  });
971
992
  });
972
993
  });
@@ -1271,14 +1292,16 @@ var app = new Hono().post("/restart", requireAdmin, (c) => {
1271
1292
  cleanupOAuthFlows();
1272
1293
  const flowId = crypto.randomUUID();
1273
1294
  const needsManualCode = !!oauthProvider.usesCallbackServer;
1295
+ const abortController = new AbortController();
1274
1296
  const flow = {
1275
1297
  status: "pending",
1276
1298
  needsManualCode,
1299
+ abortController,
1277
1300
  createdAt: Date.now()
1278
1301
  };
1279
1302
  oauthFlows.set(flowId, flow);
1280
- const promptPromise = needsManualCode ? new Promise((resolve18) => {
1281
- flow.resolveCode = resolve18;
1303
+ const promptPromise = needsManualCode ? new Promise((resolve17) => {
1304
+ flow.resolveCode = resolve17;
1282
1305
  }) : void 0;
1283
1306
  oauthProvider.login({
1284
1307
  onAuth: (info) => {
@@ -1294,7 +1317,8 @@ var app = new Hono().post("/restart", requireAdmin, (c) => {
1294
1317
  }
1295
1318
  return "";
1296
1319
  },
1297
- onManualCodeInput: needsManualCode ? () => promptPromise : void 0
1320
+ onManualCodeInput: needsManualCode ? () => promptPromise : void 0,
1321
+ signal: abortController.signal
1298
1322
  }).then(async (credentials) => {
1299
1323
  saveProviderConfig(provider, { oauth: credentials });
1300
1324
  const existing = oauthFlows.get(flowId);
@@ -1307,7 +1331,7 @@ var app = new Hono().post("/restart", requireAdmin, (c) => {
1307
1331
  existing.error = err instanceof Error ? err.message : String(err);
1308
1332
  }
1309
1333
  });
1310
- await new Promise((resolve18) => setTimeout(resolve18, 2e3));
1334
+ await new Promise((resolve17) => setTimeout(resolve17, 2e3));
1311
1335
  const state = oauthFlows.get(flowId);
1312
1336
  return c.json({
1313
1337
  flowId,
@@ -1348,6 +1372,8 @@ var app = new Hono().post("/restart", requireAdmin, (c) => {
1348
1372
  status: flow.status,
1349
1373
  waitingForCode: flow.waitingForCode
1350
1374
  };
1375
+ if (flow.url) result.url = flow.url;
1376
+ if (flow.instructions) result.instructions = flow.instructions;
1351
1377
  if (flow.error) result.error = flow.error;
1352
1378
  if (flow.status !== "pending") {
1353
1379
  setTimeout(() => oauthFlows.delete(flowId), 3e4);
@@ -1402,13 +1428,12 @@ var app = new Hono().post("/restart", requireAdmin, (c) => {
1402
1428
  }
1403
1429
  );
1404
1430
  var oauthFlows = /* @__PURE__ */ new Map();
1405
- var OAUTH_FLOW_TTL_MS = 10 * 60 * 1e3;
1406
1431
  function cleanupOAuthFlows() {
1407
- const now = Date.now();
1408
1432
  for (const [id, flow] of oauthFlows) {
1409
- if (now - flow.createdAt > OAUTH_FLOW_TTL_MS) {
1410
- oauthFlows.delete(id);
1433
+ if (flow.status === "pending") {
1434
+ flow.abortController?.abort();
1411
1435
  }
1436
+ oauthFlows.delete(id);
1412
1437
  }
1413
1438
  }
1414
1439
  var PROVIDER_TEMPLATES = {
@@ -1563,8 +1588,8 @@ var app2 = new Hono2().get("/events", async (c) => {
1563
1588
  });
1564
1589
  }, 15e3);
1565
1590
  cleanups.push(() => clearInterval(keepAlive));
1566
- await new Promise((resolve18) => {
1567
- stream.onAbort(() => resolve18());
1591
+ await new Promise((resolve17) => {
1592
+ stream.onAbort(() => resolve17());
1568
1593
  });
1569
1594
  } finally {
1570
1595
  for (const cleanup of cleanups) {
@@ -1848,7 +1873,7 @@ import { zValidator as zValidator3 } from "@hono/zod-validator";
1848
1873
  import { Hono as Hono4 } from "hono";
1849
1874
  import { z as z3 } from "zod";
1850
1875
 
1851
- // packages/daemon/src/lib/puppets.ts
1876
+ // packages/daemon/src/lib/chat/puppets.ts
1852
1877
  import { and as and2, eq as eq3 } from "drizzle-orm";
1853
1878
  async function findOrCreatePuppet(platform, platformId, displayName) {
1854
1879
  const username = `${platform}:${platformId}`;
@@ -1936,11 +1961,9 @@ var app4 = new Hono4().post("/:platform/inbound", zValidator3("json", inboundSch
1936
1961
  }
1937
1962
  const mindUser = await getOrCreateMindUser(mindName);
1938
1963
  const participantIds = [puppet.id, mindUser.id];
1939
- let conversationId = await findDMConversation(mindName, participantIds);
1964
+ let conversationId = await findDMConversation(participantIds);
1940
1965
  if (!conversationId) {
1941
- const title = `${body.displayName}, ${mindName}`;
1942
- const conv = await createConversation(mindName, "volute", {
1943
- title,
1966
+ const conv = await createConversation({
1944
1967
  participantIds: [puppet.id, mindUser.id],
1945
1968
  type: "dm"
1946
1969
  });
@@ -1969,7 +1992,7 @@ var app4 = new Hono4().post("/:platform/inbound", zValidator3("json", inboundSch
1969
1992
  }
1970
1993
  const participants = await getParticipants(channel.id);
1971
1994
  if (!participants.some((p) => p.userId === puppet.id)) {
1972
- const { addParticipant } = await import("./conversations-2PW57WO2.js");
1995
+ const { addParticipant } = await import("./conversations-G6YRSABR.js");
1973
1996
  await addParticipant(channel.id, puppet.id);
1974
1997
  }
1975
1998
  const contentBlocks = body.content;
@@ -2062,8 +2085,8 @@ async function fanOutToBridgedMinds(opts) {
2062
2085
  const participants = await getParticipants(opts.conversationId);
2063
2086
  const mindParticipants = participants.filter((p) => p.userType === "mind");
2064
2087
  const participantNames = participants.map((p) => p.username);
2065
- const { getMindManager: getMindManager2 } = await import("./mind-manager-HFLB5653.js");
2066
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-7KFK3USC.js");
2088
+ const { getMindManager: getMindManager2 } = await import("./mind-manager-LS2AIXHQ.js");
2089
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-TQP5ZJI5.js");
2067
2090
  const manager = getMindManager2();
2068
2091
  const sm = getSleepManagerIfReady2();
2069
2092
  const targetMinds = mindParticipants.filter((ap) => {
@@ -2073,7 +2096,6 @@ async function fanOutToBridgedMinds(opts) {
2073
2096
  const channel = buildVoluteSlug({
2074
2097
  participants,
2075
2098
  mindUsername: mindName,
2076
- convTitle: null,
2077
2099
  conversationId: opts.conversationId
2078
2100
  });
2079
2101
  deliverMessage(mindName, {
@@ -2089,661 +2111,10 @@ async function fanOutToBridgedMinds(opts) {
2089
2111
  });
2090
2112
  }
2091
2113
  }
2092
- var bridges_default = app4;
2093
-
2094
- // packages/daemon/src/web/api/channels.ts
2095
- import { Hono as Hono5 } from "hono";
2096
-
2097
- // packages/daemon/src/lib/channels/discord.ts
2098
- var discord_exports = {};
2099
- __export(discord_exports, {
2100
- createConversation: () => createConversation2,
2101
- listConversations: () => listConversations,
2102
- listUsers: () => listUsers2,
2103
- read: () => read,
2104
- send: () => send
2105
- });
2106
-
2107
- // packages/daemon/src/connectors/sdk.ts
2108
- function splitMessage(text, maxLength) {
2109
- const chunks = [];
2110
- while (text.length > maxLength) {
2111
- let splitAt = text.lastIndexOf("\n", maxLength);
2112
- if (splitAt < maxLength / 2) splitAt = maxLength;
2113
- chunks.push(text.slice(0, splitAt));
2114
- text = text.slice(splitAt).replace(/^\n/, "");
2115
- }
2116
- if (text) chunks.push(text);
2117
- return chunks;
2118
- }
2119
-
2120
- // packages/daemon/src/lib/channels/discord.ts
2121
- var DISCORD_MAX_LENGTH = 2e3;
2122
- var API_BASE = "https://discord.com/api/v10";
2123
- function requireToken(env) {
2124
- const token = env.DISCORD_TOKEN;
2125
- if (!token) throw new Error("DISCORD_TOKEN not set");
2126
- return token;
2127
- }
2128
- async function discordGet(token, path) {
2129
- const res = await fetch(`${API_BASE}${path}`, {
2130
- headers: { Authorization: `Bot ${token}` }
2131
- });
2132
- if (!res.ok) {
2133
- throw new Error(`Discord API error: ${res.status} ${res.statusText}`);
2134
- }
2135
- return res.json();
2136
- }
2137
- async function read(env, channelSlug, limit) {
2138
- const token = requireToken(env);
2139
- const channelId = resolveChannelId(channelSlug);
2140
- const res = await fetch(`${API_BASE}/channels/${channelId}/messages?limit=${limit}`, {
2141
- headers: { Authorization: `Bot ${token}` }
2142
- });
2143
- if (!res.ok) {
2144
- throw new Error(`Discord API error: ${res.status} ${res.statusText}`);
2145
- }
2146
- const messages = await res.json();
2147
- return messages.reverse().map((m) => `${m.author.username}: ${m.content}`).join("\n");
2148
- }
2149
- async function send(env, channelSlug, message, images) {
2150
- const token = requireToken(env);
2151
- const channelId = resolveChannelId(channelSlug);
2152
- if (images?.length) {
2153
- for (let i = 0; i < images.length; i++) {
2154
- const img = images[i];
2155
- const ext = img.media_type.split("/")[1] || "png";
2156
- const form = new FormData();
2157
- const content = i === 0 ? message.slice(0, DISCORD_MAX_LENGTH) : "";
2158
- form.append("payload_json", JSON.stringify({ content }));
2159
- form.append(
2160
- "files[0]",
2161
- new Blob([Buffer.from(img.data, "base64")], { type: img.media_type }),
2162
- `image.${ext}`
2163
- );
2164
- const res = await fetch(`${API_BASE}/channels/${channelId}/messages`, {
2165
- method: "POST",
2166
- headers: { Authorization: `Bot ${token}` },
2167
- body: form
2168
- });
2169
- if (!res.ok) {
2170
- const body = await res.text().catch(() => "");
2171
- const partial = i > 0 ? ` (${i}/${images.length} images were already sent)` : "";
2172
- throw new Error(`Discord API error: ${res.status} ${body || res.statusText}${partial}`);
2173
- }
2174
- }
2175
- return;
2176
- }
2177
- const chunks = splitMessage(message, DISCORD_MAX_LENGTH);
2178
- for (let i = 0; i < chunks.length; i++) {
2179
- const res = await fetch(`${API_BASE}/channels/${channelId}/messages`, {
2180
- method: "POST",
2181
- headers: {
2182
- Authorization: `Bot ${token}`,
2183
- "Content-Type": "application/json"
2184
- },
2185
- body: JSON.stringify({ content: chunks[i] })
2186
- });
2187
- if (!res.ok) {
2188
- const partial = i > 0 ? ` (${i}/${chunks.length} chunks were already sent)` : "";
2189
- throw new Error(`Discord API error: ${res.status} ${res.statusText}${partial}`);
2190
- }
2191
- }
2192
- }
2193
- async function listConversations(env) {
2194
- const token = requireToken(env);
2195
- const results = [];
2196
- const guilds = await discordGet(token, "/users/@me/guilds");
2197
- for (const guild of guilds) {
2198
- const channels = await discordGet(token, `/guilds/${guild.id}/channels`);
2199
- for (const ch of channels) {
2200
- if (ch.type !== 0) continue;
2201
- results.push({
2202
- id: `discord:${slugify(guild.name)}/${slugify(ch.name)}`,
2203
- platformId: ch.id,
2204
- name: `#${ch.name}`,
2205
- type: "channel"
2206
- });
2207
- }
2208
- }
2209
- const dms = await discordGet(token, "/users/@me/channels");
2210
- for (const dm of dms) {
2211
- const recipients = dm.recipients?.map((r) => r.username) ?? [];
2212
- const slug = recipients.length === 0 ? `discord:${dm.id}` : recipients.length === 1 ? `discord:@${slugify(recipients[0])}` : `discord:@${recipients.map(slugify).sort().join(",")}`;
2213
- results.push({
2214
- id: slug,
2215
- platformId: dm.id,
2216
- name: recipients.join(", ") || "DM",
2217
- type: dm.type === 1 ? "dm" : "channel"
2218
- });
2219
- }
2220
- return results;
2221
- }
2222
- async function listUsers2(env) {
2223
- const token = requireToken(env);
2224
- const seen = /* @__PURE__ */ new Map();
2225
- const guilds = await discordGet(token, "/users/@me/guilds");
2226
- for (const guild of guilds) {
2227
- const members = await discordGet(token, `/guilds/${guild.id}/members?limit=1000`);
2228
- for (const m of members) {
2229
- if (!seen.has(m.user.id)) {
2230
- seen.set(m.user.id, {
2231
- id: m.user.id,
2232
- username: m.user.username,
2233
- type: m.user.bot ? "bot" : "human"
2234
- });
2235
- }
2236
- }
2237
- }
2238
- return [...seen.values()];
2239
- }
2240
- async function createConversation2(env, participants, _name) {
2241
- const token = requireToken(env);
2242
- if (participants.length !== 1) {
2243
- throw new Error(
2244
- "Discord group creation not supported via bot \u2014 use threads in an existing channel"
2245
- );
2246
- }
2247
- const allUsers = await listUsers2(env);
2248
- const target = allUsers.find((u) => u.username.toLowerCase() === participants[0].toLowerCase());
2249
- if (!target) {
2250
- throw new Error(`User not found: ${participants[0]}`);
2251
- }
2252
- const res = await fetch(`${API_BASE}/users/@me/channels`, {
2253
- method: "POST",
2254
- headers: {
2255
- Authorization: `Bot ${token}`,
2256
- "Content-Type": "application/json"
2257
- },
2258
- body: JSON.stringify({ recipient_id: target.id })
2259
- });
2260
- if (!res.ok) {
2261
- throw new Error(`Discord API error: ${res.status} ${res.statusText}`);
2262
- }
2263
- const dmChannel = await res.json();
2264
- return `discord:${dmChannel.id}`;
2265
- }
2266
-
2267
- // packages/daemon/src/lib/channels/slack.ts
2268
- var slack_exports = {};
2269
- __export(slack_exports, {
2270
- createConversation: () => createConversation3,
2271
- listConversations: () => listConversations2,
2272
- listUsers: () => listUsers3,
2273
- read: () => read2,
2274
- send: () => send2
2275
- });
2276
- var SLACK_MAX_LENGTH = 4e3;
2277
- var API_BASE2 = "https://slack.com/api";
2278
- function requireToken2(env) {
2279
- const token = env.SLACK_BOT_TOKEN;
2280
- if (!token) throw new Error("SLACK_BOT_TOKEN not set");
2281
- return token;
2282
- }
2283
- async function slackApi(token, method, body) {
2284
- const res = await fetch(`${API_BASE2}/${method}`, {
2285
- method: "POST",
2286
- headers: {
2287
- Authorization: `Bearer ${token}`,
2288
- "Content-Type": "application/json"
2289
- },
2290
- body: JSON.stringify(body)
2291
- });
2292
- if (!res.ok) {
2293
- throw new Error(`Slack API HTTP error: ${res.status} ${res.statusText}`);
2294
- }
2295
- const data = await res.json();
2296
- if (!data.ok) {
2297
- throw new Error(`Slack API error: ${data.error}`);
2298
- }
2299
- return data;
2300
- }
2301
- async function read2(env, channelSlug, limit) {
2302
- const token = requireToken2(env);
2303
- const channelId = resolveChannelId(channelSlug);
2304
- const data = await slackApi(token, "conversations.history", {
2305
- channel: channelId,
2306
- limit
2307
- });
2308
- return data.messages.reverse().map((m) => `${m.user ?? m.bot_id ?? "unknown"}: ${m.text}`).join("\n");
2309
- }
2310
- async function send2(env, channelSlug, message, images) {
2311
- const token = requireToken2(env);
2312
- const channelId = resolveChannelId(channelSlug);
2313
- if (images?.length) {
2314
- for (const img of images) {
2315
- const ext = img.media_type.split("/")[1] || "png";
2316
- const filename = `image.${ext}`;
2317
- const binary = Buffer.from(img.data, "base64");
2318
- const uploadData = await slackApi(token, "files.getUploadURLExternal", {
2319
- filename,
2320
- length: binary.length
2321
- });
2322
- const uploadRes = await fetch(uploadData.upload_url, {
2323
- method: "POST",
2324
- body: binary
2325
- });
2326
- if (!uploadRes.ok) {
2327
- throw new Error(`Slack file upload failed: ${uploadRes.status} ${uploadRes.statusText}`);
2328
- }
2329
- await slackApi(token, "files.completeUploadExternal", {
2330
- files: [{ id: uploadData.file_id }],
2331
- channel_id: channelId
2332
- });
2333
- }
2334
- if (message) {
2335
- const chunks2 = splitMessage(message, SLACK_MAX_LENGTH);
2336
- for (const chunk of chunks2) {
2337
- await slackApi(token, "chat.postMessage", {
2338
- channel: channelId,
2339
- text: chunk
2340
- });
2341
- }
2342
- }
2343
- return;
2344
- }
2345
- const chunks = splitMessage(message, SLACK_MAX_LENGTH);
2346
- for (let i = 0; i < chunks.length; i++) {
2347
- try {
2348
- await slackApi(token, "chat.postMessage", {
2349
- channel: channelId,
2350
- text: chunks[i]
2351
- });
2352
- } catch (err) {
2353
- const partial = i > 0 ? ` (${i}/${chunks.length} chunks were already sent)` : "";
2354
- throw new Error(`${err instanceof Error ? err.message : err}${partial}`);
2355
- }
2356
- }
2357
- }
2358
- async function listConversations2(env) {
2359
- const token = requireToken2(env);
2360
- const authData = await slackApi(token, "auth.test", {});
2361
- const teamName = authData.team ?? "workspace";
2362
- const data = await slackApi(token, "conversations.list", {
2363
- types: "public_channel,private_channel,mpim,im",
2364
- limit: 1e3
2365
- });
2366
- const userMap = /* @__PURE__ */ new Map();
2367
- const imChannels = data.channels.filter((ch) => ch.is_im && ch.user);
2368
- if (imChannels.length > 0) {
2369
- const users2 = await listUsers3(env);
2370
- for (const u of users2) {
2371
- userMap.set(u.id, u.username);
2372
- }
2373
- }
2374
- return data.channels.map((ch) => {
2375
- let type = "channel";
2376
- if (ch.is_im) type = "dm";
2377
- let slug;
2378
- let name;
2379
- if (ch.is_im && ch.user) {
2380
- const username = userMap.get(ch.user) ?? ch.user;
2381
- slug = `slack:@${slugify(username)}`;
2382
- name = username;
2383
- } else if (ch.name) {
2384
- slug = `slack:${slugify(teamName)}/${slugify(ch.name)}`;
2385
- name = ch.name;
2386
- } else {
2387
- slug = `slack:${ch.id}`;
2388
- name = ch.id;
2389
- }
2390
- return {
2391
- id: slug,
2392
- platformId: ch.id,
2393
- name,
2394
- type,
2395
- participantCount: ch.num_members
2396
- };
2397
- });
2398
- }
2399
- async function listUsers3(env) {
2400
- const token = requireToken2(env);
2401
- const data = await slackApi(token, "users.list", {});
2402
- return data.members.filter((m) => !m.deleted).map((m) => ({
2403
- id: m.id,
2404
- username: m.name,
2405
- type: m.is_bot ? "bot" : "human"
2406
- }));
2407
- }
2408
- async function createConversation3(env, participants, name) {
2409
- const token = requireToken2(env);
2410
- const allUsers = await listUsers3(env);
2411
- const ids = [];
2412
- for (const p of participants) {
2413
- const user = allUsers.find((u) => u.username.toLowerCase() === p.toLowerCase());
2414
- if (!user) throw new Error(`User not found: ${p}`);
2415
- ids.push(user.id);
2416
- }
2417
- if (name) {
2418
- const createData = await slackApi(token, "conversations.create", {
2419
- name,
2420
- is_private: true
2421
- });
2422
- const channelId = createData.channel.id;
2423
- for (const userId of ids) {
2424
- await slackApi(token, "conversations.invite", {
2425
- channel: channelId,
2426
- users: userId
2427
- });
2428
- }
2429
- const authData = await slackApi(token, "auth.test", {});
2430
- const teamName = authData.team ?? "workspace";
2431
- const slug = `slack:${slugify(teamName)}/${slugify(name)}`;
2432
- return slug;
2433
- }
2434
- const openData = await slackApi(token, "conversations.open", { users: ids.join(",") });
2435
- return `slack:${openData.channel.id}`;
2436
- }
2437
-
2438
- // packages/daemon/src/lib/channels/telegram.ts
2439
- var telegram_exports = {};
2440
- __export(telegram_exports, {
2441
- createConversation: () => createConversation4,
2442
- listConversations: () => listConversations3,
2443
- listUsers: () => listUsers4,
2444
- read: () => read3,
2445
- send: () => send3
2446
- });
2447
- var TELEGRAM_MAX_LENGTH = 4096;
2448
- var API_BASE3 = "https://api.telegram.org";
2449
- function requireToken3(env) {
2450
- const token = env.TELEGRAM_BOT_TOKEN;
2451
- if (!token) throw new Error("TELEGRAM_BOT_TOKEN not set");
2452
- return token;
2453
- }
2454
- async function read3(_env, _channelSlug, _limit) {
2455
- throw new Error(
2456
- "Telegram Bot API does not support reading chat history. Use volute chat send instead."
2457
- );
2458
- }
2459
- async function send3(env, channelSlug, message, images) {
2460
- const token = requireToken3(env);
2461
- const chatId = resolveChannelId(channelSlug);
2462
- if (images?.length) {
2463
- const CAPTION_MAX = 1024;
2464
- for (let i = 0; i < images.length; i++) {
2465
- const img = images[i];
2466
- const ext = img.media_type.split("/")[1] || "png";
2467
- const form = new FormData();
2468
- form.append("chat_id", chatId);
2469
- form.append(
2470
- "photo",
2471
- new Blob([Buffer.from(img.data, "base64")], { type: img.media_type }),
2472
- `image.${ext}`
2473
- );
2474
- if (i === 0 && message) {
2475
- form.append("caption", message.slice(0, CAPTION_MAX));
2476
- }
2477
- const res = await fetch(`${API_BASE3}/bot${token}/sendPhoto`, {
2478
- method: "POST",
2479
- body: form
2480
- });
2481
- if (!res.ok) {
2482
- const body = await res.text().catch(() => "");
2483
- const partial = i > 0 ? ` (${i}/${images.length} images were already sent)` : "";
2484
- throw new Error(`Telegram API error: ${res.status} ${body}${partial}`);
2485
- }
2486
- }
2487
- if (message && message.length > CAPTION_MAX) {
2488
- const remaining = message.slice(CAPTION_MAX);
2489
- const chunks2 = splitMessage(remaining, TELEGRAM_MAX_LENGTH);
2490
- for (const chunk of chunks2) {
2491
- const res = await fetch(`${API_BASE3}/bot${token}/sendMessage`, {
2492
- method: "POST",
2493
- headers: { "Content-Type": "application/json" },
2494
- body: JSON.stringify({ chat_id: chatId, text: chunk })
2495
- });
2496
- if (!res.ok) {
2497
- const body = await res.text().catch(() => "");
2498
- throw new Error(`Telegram API error: ${res.status} ${body}`);
2499
- }
2500
- }
2501
- }
2502
- return;
2503
- }
2504
- const chunks = splitMessage(message, TELEGRAM_MAX_LENGTH);
2505
- for (let i = 0; i < chunks.length; i++) {
2506
- const res = await fetch(`${API_BASE3}/bot${token}/sendMessage`, {
2507
- method: "POST",
2508
- headers: { "Content-Type": "application/json" },
2509
- body: JSON.stringify({ chat_id: chatId, text: chunks[i] })
2510
- });
2511
- if (!res.ok) {
2512
- const body = await res.text().catch(() => "");
2513
- const partial = i > 0 ? ` (${i}/${chunks.length} chunks were already sent)` : "";
2514
- throw new Error(`Telegram API error: ${res.status} ${body}${partial}`);
2515
- }
2516
- }
2517
- }
2518
- async function listConversations3() {
2519
- throw new Error(
2520
- "Telegram Bot API does not support listing conversations. Users must message the bot first."
2521
- );
2522
- }
2523
- async function listUsers4() {
2524
- throw new Error(
2525
- "Telegram Bot API does not support listing users. Users must message the bot first."
2526
- );
2527
- }
2528
- async function createConversation4() {
2529
- throw new Error(
2530
- "Telegram Bot API does not support creating conversations. Users must message the bot first."
2531
- );
2532
- }
2533
-
2534
- // packages/daemon/src/lib/channels/volute.ts
2535
- var volute_exports = {};
2536
- __export(volute_exports, {
2537
- createConversation: () => createConversation5,
2538
- listConversations: () => listConversations4,
2539
- listUsers: () => listUsers5,
2540
- read: () => read4,
2541
- send: () => send4
2542
- });
2543
- import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
2544
- import { resolve as resolve4 } from "path";
2545
- function readSessionFile(mindDir2) {
2546
- try {
2547
- const p = resolve4(mindDir2, ".mind", "current-session");
2548
- if (existsSync4(p)) return readFileSync4(p, "utf-8").trim() || void 0;
2549
- } catch (err) {
2550
- const code = err.code;
2551
- if (code !== "ENOENT") {
2552
- console.error(`[volute] failed to read session file: ${code ?? err}`);
2553
- }
2554
- }
2555
- return void 0;
2556
- }
2557
- function getDaemonConfig() {
2558
- const configPath = resolve4(voluteSystemDir(), "daemon.json");
2559
- if (!existsSync4(configPath)) {
2560
- throw new Error("Volute daemon is not running");
2561
- }
2562
- let config2;
2563
- try {
2564
- config2 = JSON.parse(readFileSync4(configPath, "utf-8"));
2565
- } catch (err) {
2566
- throw new Error(`Failed to parse ${configPath}: ${err}`);
2567
- }
2568
- if (typeof config2.port !== "number") {
2569
- throw new Error(`Invalid or missing port in ${configPath}`);
2570
- }
2571
- const url = new URL("http://localhost");
2572
- url.hostname = config2.hostname || "localhost";
2573
- url.port = String(config2.port);
2574
- return { url: url.origin, token: config2.token };
2575
- }
2576
- async function read4(env, channelSlug, limit) {
2577
- const mindName = env.VOLUTE_MIND;
2578
- if (!mindName) throw new Error("VOLUTE_MIND not set");
2579
- const conversationId = resolveChannelId(channelSlug);
2580
- const { url, token } = getDaemonConfig();
2581
- const headers = { Origin: url };
2582
- if (token) headers.Authorization = `Bearer ${token}`;
2583
- const res = await fetch(
2584
- `${url}/api/minds/${encodeURIComponent(mindName)}/conversations/${encodeURIComponent(conversationId)}/messages`,
2585
- { headers }
2586
- );
2587
- if (!res.ok) {
2588
- throw new Error(`Failed to read conversation: ${res.status} ${res.statusText}`);
2589
- }
2590
- const data = await res.json();
2591
- if (!Array.isArray(data.items)) {
2592
- throw new Error("Unexpected response format when reading conversation messages");
2593
- }
2594
- return data.items.slice(-limit).map((m) => {
2595
- const text = Array.isArray(m.content) ? m.content.filter((b) => b.type === "text").map((b) => b.text).join("") : m.content;
2596
- return `${m.sender_name ?? m.role}: ${text}`;
2597
- }).join("\n");
2598
- }
2599
- async function send4(env, channelSlug, message, images) {
2600
- const mindName = env.VOLUTE_MIND;
2601
- if (!mindName) throw new Error("VOLUTE_MIND not set");
2602
- const conversationId = resolveChannelId(channelSlug);
2603
- const { url, token } = getDaemonConfig();
2604
- const headers = {
2605
- "Content-Type": "application/json",
2606
- Origin: url
2607
- };
2608
- if (token) headers.Authorization = `Bearer ${token}`;
2609
- const voluteSession = env.VOLUTE_SESSION || (env.VOLUTE_MIND_DIR ? readSessionFile(env.VOLUTE_MIND_DIR) : void 0);
2610
- if (voluteSession) headers["X-Volute-Session"] = voluteSession;
2611
- const res = await fetch(`${url}/api/v1/chat`, {
2612
- method: "POST",
2613
- headers,
2614
- body: JSON.stringify({
2615
- message,
2616
- conversationId,
2617
- sender: env.VOLUTE_SENDER ?? mindName,
2618
- images,
2619
- targetMind: mindName
2620
- })
2621
- });
2622
- if (!res.ok) {
2623
- const data = await res.json().catch(() => ({}));
2624
- throw new Error(data.error ?? `Failed to send: ${res.status}`);
2625
- }
2626
- }
2627
- async function listConversations4(env) {
2628
- const mindName = env.VOLUTE_MIND;
2629
- if (!mindName) throw new Error("VOLUTE_MIND not set");
2630
- const { url, token } = getDaemonConfig();
2631
- const headers = { Origin: url };
2632
- if (token) headers.Authorization = `Bearer ${token}`;
2633
- const res = await fetch(`${url}/api/minds/${encodeURIComponent(mindName)}/conversations`, {
2634
- headers
2635
- });
2636
- if (!res.ok) {
2637
- throw new Error(`Failed to list conversations: ${res.status} ${res.statusText}`);
2638
- }
2639
- const convs = await res.json();
2640
- const results = [];
2641
- for (const conv of convs) {
2642
- let participants = [];
2643
- try {
2644
- const pRes = await fetch(
2645
- `${url}/api/minds/${encodeURIComponent(mindName)}/conversations/${encodeURIComponent(conv.id)}/participants`,
2646
- { headers }
2647
- );
2648
- if (pRes.ok) {
2649
- participants = await pRes.json();
2650
- } else {
2651
- console.error(`[volute] failed to fetch participants for ${conv.id}: HTTP ${pRes.status}`);
2652
- }
2653
- } catch (err) {
2654
- console.error(`[volute] failed to fetch participants for ${conv.id}:`, err);
2655
- }
2656
- const slug = buildVoluteSlug({
2657
- participants,
2658
- mindUsername: mindName,
2659
- convTitle: conv.title,
2660
- conversationId: conv.id,
2661
- convType: conv.type,
2662
- convName: conv.name
2663
- });
2664
- const convType = conv.type === "channel" ? "channel" : "dm";
2665
- results.push({
2666
- id: slug,
2667
- platformId: conv.id,
2668
- name: conv.type === "channel" ? `#${conv.name}` : conv.title ?? "(untitled)",
2669
- type: convType,
2670
- participantCount: participants.length
2671
- });
2672
- }
2673
- return results;
2674
- }
2675
- async function listUsers5(_env) {
2676
- const { url, token } = getDaemonConfig();
2677
- const headers = { Origin: url };
2678
- if (token) headers.Authorization = `Bearer ${token}`;
2679
- const res = await fetch(`${url}/api/auth/users`, { headers });
2680
- if (!res.ok) {
2681
- throw new Error(`Failed to list users: ${res.status} ${res.statusText}`);
2682
- }
2683
- const data = await res.json();
2684
- return data.map((u) => ({
2685
- id: String(u.id),
2686
- username: u.username,
2687
- type: u.user_type
2688
- }));
2689
- }
2690
- async function createConversation5(env, participants, name) {
2691
- const mindName = env.VOLUTE_MIND;
2692
- if (!mindName) throw new Error("VOLUTE_MIND not set");
2693
- const { url, token } = getDaemonConfig();
2694
- const headers = {
2695
- "Content-Type": "application/json",
2696
- Origin: url
2697
- };
2698
- if (token) headers.Authorization = `Bearer ${token}`;
2699
- const res = await fetch(`${url}/api/minds/${encodeURIComponent(mindName)}/conversations`, {
2700
- method: "POST",
2701
- headers,
2702
- body: JSON.stringify({ participantNames: participants, title: name })
2703
- });
2704
- if (!res.ok) {
2705
- const data = await res.json().catch(() => ({}));
2706
- throw new Error(data.error ?? `Failed to create conversation: ${res.status}`);
2707
- }
2708
- const conv = await res.json();
2709
- return conv.id;
2710
- }
2711
-
2712
- // packages/daemon/src/lib/channels.ts
2713
- var CHANNELS = {
2714
- volute: {
2715
- name: "volute",
2716
- displayName: "Volute",
2717
- builtIn: true,
2718
- driver: volute_exports
2719
- },
2720
- discord: {
2721
- name: "discord",
2722
- displayName: "Discord",
2723
- driver: discord_exports
2724
- },
2725
- slack: {
2726
- name: "slack",
2727
- displayName: "Slack",
2728
- driver: slack_exports
2729
- },
2730
- telegram: {
2731
- name: "telegram",
2732
- displayName: "Telegram",
2733
- driver: telegram_exports
2734
- },
2735
- mail: { name: "mail", displayName: "Email" },
2736
- system: { name: "system", displayName: "System" }
2737
- };
2738
- function getChannelDriver(platform) {
2739
- return CHANNELS[platform]?.driver ?? null;
2740
- }
2741
- function resolveChannelId(slug) {
2742
- const colonIdx = slug.indexOf(":");
2743
- return colonIdx !== -1 ? slug.slice(colonIdx + 1) : slug;
2744
- }
2114
+ var bridges_default = app4;
2745
2115
 
2746
2116
  // packages/daemon/src/web/api/channels.ts
2117
+ import { Hono as Hono5 } from "hono";
2747
2118
  function buildEnv(name) {
2748
2119
  return { ...loadMergedEnv(name), VOLUTE_MIND: name, VOLUTE_MIND_DIR: mindDir(name) };
2749
2120
  }
@@ -2756,7 +2127,7 @@ var app5 = new Hono5().post("/:name/channels/create", requireSelf(), async (c) =
2756
2127
  name: convName,
2757
2128
  sender
2758
2129
  } = await c.req.json();
2759
- const driver = getChannelDriver(platform);
2130
+ const driver = getPlatformDriver(platform);
2760
2131
  if (!driver?.createConversation) {
2761
2132
  return c.json({ error: `Platform ${platform} does not support creating conversations` }, 400);
2762
2133
  }
@@ -2878,7 +2249,8 @@ import { Hono as Hono8 } from "hono";
2878
2249
  var app7 = new Hono8().get("/", (c) => {
2879
2250
  return c.json(getLoadedExtensions());
2880
2251
  }).get("/all", (c) => {
2881
- return c.json(getAllDiscoveredExtensions());
2252
+ const detail = c.req.query("detail") === "true";
2253
+ return c.json(detail ? getAllDiscoveredExtensionsDetailed() : getAllDiscoveredExtensions());
2882
2254
  }).put("/:id/enabled", async (c) => {
2883
2255
  const { id } = c.req.param();
2884
2256
  const body = await c.req.json().catch(() => null);
@@ -2919,14 +2291,14 @@ var app7 = new Hono8().get("/", (c) => {
2919
2291
  var extensions_default = app7;
2920
2292
 
2921
2293
  // packages/daemon/src/web/api/file-sharing.ts
2922
- import { readFileSync as readFileSync5, statSync } from "fs";
2923
- import { resolve as resolve5 } from "path";
2294
+ import { readFileSync as readFileSync4, statSync } from "fs";
2295
+ import { resolve as resolve4 } from "path";
2924
2296
  import { Hono as Hono9 } from "hono";
2925
2297
  async function notifyMind(mindName, message) {
2926
2298
  const entry = await findMind(mindName);
2927
2299
  if (!entry) return;
2928
2300
  try {
2929
- const { sendSystemMessage } = await import("./system-chat-IDPHYHY4.js");
2301
+ const { sendSystemMessage } = await import("./system-chat-NNXYCSVL.js");
2930
2302
  await sendSystemMessage(mindName, message);
2931
2303
  } catch (err) {
2932
2304
  logger_default.warn(`[file-sharing] notify mind ${mindName} failed`, logger_default.errorData(err));
@@ -2945,7 +2317,7 @@ var app8 = new Hono9().post("/:name/files/send", requireSelf(), async (c) => {
2945
2317
  const pathErr = validateFilePath(body.filePath);
2946
2318
  if (pathErr) return c.json({ error: pathErr }, 400);
2947
2319
  const senderDir = mindDir(senderName);
2948
- const filePath = resolve5(senderDir, "home", body.filePath);
2320
+ const filePath = resolve4(senderDir, "home", body.filePath);
2949
2321
  const MAX_FILE_SIZE3 = 50 * 1024 * 1024;
2950
2322
  const stat3 = statSync(filePath, { throwIfNoEntry: false });
2951
2323
  if (!stat3) return c.json({ error: `File not found: ${body.filePath}` }, 404);
@@ -2959,7 +2331,7 @@ var app8 = new Hono9().post("/:name/files/send", requireSelf(), async (c) => {
2959
2331
  }
2960
2332
  let content;
2961
2333
  try {
2962
- content = readFileSync5(filePath);
2334
+ content = readFileSync4(filePath);
2963
2335
  } catch (err) {
2964
2336
  const code = err.code;
2965
2337
  if (code === "ENOENT") {
@@ -3051,7 +2423,7 @@ var file_sharing_default = app8;
3051
2423
  // packages/daemon/src/web/api/files.ts
3052
2424
  import { mkdirSync as mkdirSync3, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
3053
2425
  import { readdir, readFile, realpath, stat } from "fs/promises";
3054
- import { extname as extname2, resolve as resolve6 } from "path";
2426
+ import { extname as extname2, resolve as resolve5 } from "path";
3055
2427
  import { Hono as Hono10 } from "hono";
3056
2428
  var AVATAR_MIME2 = {
3057
2429
  ".png": "image/png",
@@ -3096,13 +2468,13 @@ var app9 = new Hono10().post("/:name/avatar", requireSelf(), async (c) => {
3096
2468
  return c.json({ error: "Invalid file type (png, jpg, gif, webp only)" }, 400);
3097
2469
  }
3098
2470
  const dir = entry.dir ?? mindDir(name);
3099
- const homeDir = resolve6(dir, "home");
2471
+ const homeDir = resolve5(dir, "home");
3100
2472
  const filename = `avatar${ext}`;
3101
- const avatarPath = resolve6(homeDir, filename);
2473
+ const avatarPath = resolve5(homeDir, filename);
3102
2474
  const config2 = readVoluteConfig(dir) ?? {};
3103
2475
  const oldAvatar = config2.profile?.avatar;
3104
2476
  if (oldAvatar && oldAvatar !== filename) {
3105
- rmSync2(resolve6(homeDir, oldAvatar), { force: true });
2477
+ rmSync2(resolve5(homeDir, oldAvatar), { force: true });
3106
2478
  }
3107
2479
  const buffer2 = Buffer.from(await file.arrayBuffer());
3108
2480
  mkdirSync3(homeDir, { recursive: true });
@@ -3124,8 +2496,8 @@ var app9 = new Hono10().post("/:name/avatar", requireSelf(), async (c) => {
3124
2496
  const ext = extname2(config2.profile.avatar).toLowerCase();
3125
2497
  const mime = AVATAR_MIME2[ext];
3126
2498
  if (!mime) return c.json({ error: "Invalid avatar extension" }, 400);
3127
- const homeDir = resolve6(dir, "home");
3128
- const avatarPath = resolve6(homeDir, config2.profile.avatar);
2499
+ const homeDir = resolve5(dir, "home");
2500
+ const avatarPath = resolve5(homeDir, config2.profile.avatar);
3129
2501
  if (!avatarPath.startsWith(`${homeDir}/`)) return c.json({ error: "Invalid avatar path" }, 400);
3130
2502
  let realAvatarPath;
3131
2503
  try {
@@ -3153,7 +2525,7 @@ var app9 = new Hono10().post("/:name/avatar", requireSelf(), async (c) => {
3153
2525
  const name = c.req.param("name");
3154
2526
  const entry = await findMind(name);
3155
2527
  if (!entry) return c.json({ error: "Mind not found" }, 404);
3156
- const homeDir = resolve6(entry.dir ?? mindDir(name), "home");
2528
+ const homeDir = resolve5(entry.dir ?? mindDir(name), "home");
3157
2529
  let entries;
3158
2530
  try {
3159
2531
  entries = await readdir(homeDir, { withFileTypes: true });
@@ -3168,10 +2540,10 @@ var app9 = new Hono10().post("/:name/avatar", requireSelf(), async (c) => {
3168
2540
  const name = c.req.param("name");
3169
2541
  const entry = await findMind(name);
3170
2542
  if (!entry) return c.json({ error: "Mind not found" }, 404);
3171
- const homeDir = resolve6(entry.dir ?? mindDir(name), "home");
2543
+ const homeDir = resolve5(entry.dir ?? mindDir(name), "home");
3172
2544
  const wildcard = c.req.path.replace(new RegExp(`^.*/minds/${name}/files`), "") || "/";
3173
2545
  const relativePath = wildcard.slice(1);
3174
- const requestedPath = resolve6(homeDir, relativePath);
2546
+ const requestedPath = resolve5(homeDir, relativePath);
3175
2547
  if (requestedPath !== homeDir && !requestedPath.startsWith(`${homeDir}/`))
3176
2548
  return c.text("Forbidden", 403);
3177
2549
  if (relativePath.split("/").some((seg) => seg.startsWith("."))) return c.text("Forbidden", 403);
@@ -3305,14 +2677,14 @@ var history = new Hono11().get("/turns", async (c) => {
3305
2677
  for (const r of triggerRows) triggerMap.set(r.id, r);
3306
2678
  }
3307
2679
  const allChannelSlugs = /* @__PURE__ */ new Set();
3308
- for (const [, channels] of msgsByTurnChannel) {
3309
- for (const ch of channels.keys()) allChannelSlugs.add(ch);
2680
+ for (const [, channels2] of msgsByTurnChannel) {
2681
+ for (const ch of channels2.keys()) allChannelSlugs.add(ch);
3310
2682
  }
3311
2683
  const dmSlugMinds = /* @__PURE__ */ new Map();
3312
- for (const [turnId, channels] of msgsByTurnChannel) {
2684
+ for (const [turnId, channels2] of msgsByTurnChannel) {
3313
2685
  const mindName = turnMindMap.get(turnId);
3314
2686
  if (!mindName) continue;
3315
- for (const ch of channels.keys()) {
2687
+ for (const ch of channels2.keys()) {
3316
2688
  if (ch.startsWith("@")) {
3317
2689
  let minds = dmSlugMinds.get(ch);
3318
2690
  if (!minds) {
@@ -3334,16 +2706,13 @@ var history = new Hono11().get("/turns", async (c) => {
3334
2706
  return { slug: s, name };
3335
2707
  });
3336
2708
  if (channelNames.length > 0) {
3337
- const channelRows = await db.select({ id: conversations.id, name: conversations.name }).from(conversations).where(
3338
- and3(
3339
- eq4(conversations.type, "channel"),
3340
- inArray(
3341
- conversations.name,
3342
- channelNames.map((c2) => c2.name)
3343
- )
2709
+ const channelRows = await db.select({ conversationId: channels.conversation_id, name: channels.name }).from(channels).where(
2710
+ inArray(
2711
+ channels.name,
2712
+ channelNames.map((c2) => c2.name)
3344
2713
  )
3345
2714
  );
3346
- const nameToId = new Map(channelRows.map((r) => [r.name, r.id]));
2715
+ const nameToId = new Map(channelRows.map((r) => [r.name, r.conversationId]));
3347
2716
  for (const { slug, name } of channelNames) {
3348
2717
  const id = nameToId.get(name);
3349
2718
  if (id) channelIdMap.set(slug, id);
@@ -3365,17 +2734,31 @@ var history = new Hono11().get("/turns", async (c) => {
3365
2734
  conversationParticipants,
3366
2735
  eq4(conversations.id, conversationParticipants.conversation_id)
3367
2736
  ).innerJoin(users, eq4(conversationParticipants.user_id, users.id)).where(and3(eq4(conversations.type, "dm"), inArray(users.username, targetNames)));
2737
+ const dmCandidates = [];
3368
2738
  for (const row of dmRows) {
3369
2739
  const slug = `@${row.targetUsername}`;
3370
2740
  const mindNames = dmSlugMinds.get(slug);
3371
2741
  if (mindNames && !channelIdMap.has(slug)) {
3372
- const mindCheck = await db.select({ id: conversationParticipants.user_id }).from(conversationParticipants).innerJoin(users, eq4(conversationParticipants.user_id, users.id)).where(
3373
- and3(
3374
- eq4(conversationParticipants.conversation_id, row.id),
3375
- inArray(users.username, [...mindNames])
3376
- )
3377
- ).get();
3378
- if (mindCheck) channelIdMap.set(slug, row.id);
2742
+ dmCandidates.push({ slug, conversationId: row.id, mindNames: [...mindNames] });
2743
+ }
2744
+ }
2745
+ if (dmCandidates.length > 0) {
2746
+ const candidateConvIds = dmCandidates.map((d) => d.conversationId);
2747
+ const allMindNames = [...new Set(dmCandidates.flatMap((d) => d.mindNames))];
2748
+ const verifyRows = await db.select({
2749
+ conversation_id: conversationParticipants.conversation_id,
2750
+ username: users.username
2751
+ }).from(conversationParticipants).innerJoin(users, eq4(conversationParticipants.user_id, users.id)).where(
2752
+ and3(
2753
+ inArray(conversationParticipants.conversation_id, candidateConvIds),
2754
+ inArray(users.username, allMindNames)
2755
+ )
2756
+ );
2757
+ const verifiedConvs = new Set(verifyRows.map((r) => r.conversation_id));
2758
+ for (const candidate of dmCandidates) {
2759
+ if (verifiedConvs.has(candidate.conversationId)) {
2760
+ channelIdMap.set(candidate.slug, candidate.conversationId);
2761
+ }
3379
2762
  }
3380
2763
  }
3381
2764
  }
@@ -3447,7 +2830,7 @@ var history = new Hono11().get("/turns", async (c) => {
3447
2830
  const stream = new ReadableStream({
3448
2831
  start(controller) {
3449
2832
  const encoder = new TextEncoder();
3450
- const send5 = (data) => {
2833
+ const send = (data) => {
3451
2834
  controller.enqueue(encoder.encode(`data: ${data}
3452
2835
 
3453
2836
  `));
@@ -3464,7 +2847,7 @@ var history = new Hono11().get("/turns", async (c) => {
3464
2847
  if (mindFilter) {
3465
2848
  unsubscribe = subscribe3(mindFilter, (event) => {
3466
2849
  try {
3467
- send5(JSON.stringify(event));
2850
+ send(JSON.stringify(event));
3468
2851
  } catch {
3469
2852
  clearInterval(pingInterval);
3470
2853
  unsubscribe?.();
@@ -3473,7 +2856,7 @@ var history = new Hono11().get("/turns", async (c) => {
3473
2856
  } else {
3474
2857
  unsubscribe = subscribeAll((event) => {
3475
2858
  try {
3476
- send5(JSON.stringify(event));
2859
+ send(JSON.stringify(event));
3477
2860
  } catch {
3478
2861
  clearInterval(pingInterval);
3479
2862
  unsubscribe?.();
@@ -3506,7 +2889,7 @@ var history = new Hono11().get("/turns", async (c) => {
3506
2889
  const limit = Math.min(Math.max(parseInt(c.req.query("limit") ?? "50", 10) || 50, 1), 200);
3507
2890
  if (ids) {
3508
2891
  const db2 = await getDb();
3509
- const idList = ids.split(",").map((s) => parseInt(s, 10)).filter((n) => !isNaN(n));
2892
+ const idList = ids.split(",").map((s) => parseInt(s, 10)).filter((n) => !Number.isNaN(n));
3510
2893
  if (idList.length === 0) return c.json([]);
3511
2894
  const rows2 = await db2.select().from(summaries).where(inArray(summaries.id, idList));
3512
2895
  const result2 = rows2.map((r) => {
@@ -3550,25 +2933,58 @@ var history = new Hono11().get("/turns", async (c) => {
3550
2933
  return { ...r, metadata };
3551
2934
  });
3552
2935
  return c.json(result);
2936
+ }).get("/activity", async (c) => {
2937
+ const mind = c.req.query("mind");
2938
+ const from = c.req.query("from");
2939
+ const to = c.req.query("to");
2940
+ const limit = Math.min(Math.max(parseInt(c.req.query("limit") ?? "100", 10) || 100, 1), 500);
2941
+ const db = await getDb();
2942
+ const conditions = [];
2943
+ if (mind) conditions.push(eq4(activity.mind, mind));
2944
+ if (from) conditions.push(gte(activity.created_at, from));
2945
+ if (to) conditions.push(sql`${activity.created_at} <= ${to}`);
2946
+ conditions.push(
2947
+ sql`${activity.type} NOT IN ('mind_started', 'mind_stopped', 'mind_active', 'mind_idle', 'mind_done', 'mind_sleeping', 'mind_waking', 'brain_online', 'brain_offline')`
2948
+ );
2949
+ const rows = await db.select({
2950
+ id: activity.id,
2951
+ type: activity.type,
2952
+ mind: activity.mind,
2953
+ summary: activity.summary,
2954
+ metadata: activity.metadata,
2955
+ created_at: activity.created_at
2956
+ }).from(activity).where(conditions.length > 0 ? and3(...conditions) : void 0).orderBy(desc2(activity.created_at)).limit(limit);
2957
+ const result = rows.map((r) => {
2958
+ let metadata = null;
2959
+ if (r.metadata) {
2960
+ try {
2961
+ metadata = JSON.parse(r.metadata);
2962
+ } catch (err) {
2963
+ logger_default.debug(`malformed activity metadata for id ${r.id}`, logger_default.errorData(err));
2964
+ }
2965
+ }
2966
+ return { ...r, metadata };
2967
+ });
2968
+ return c.json(result);
3553
2969
  });
3554
2970
  var history_default = history;
3555
2971
 
3556
2972
  // packages/daemon/src/web/api/keys.ts
3557
2973
  import { Hono as Hono12 } from "hono";
3558
2974
 
3559
- // packages/daemon/src/lib/identity.ts
2975
+ // packages/daemon/src/lib/mind/identity.ts
3560
2976
  import { createHash, generateKeyPairSync, sign, verify } from "crypto";
3561
- import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
3562
- import { resolve as resolve7 } from "path";
2977
+ import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
2978
+ import { resolve as resolve6 } from "path";
3563
2979
  function generateIdentity(mindDir2) {
3564
- const identityDir = resolve7(mindDir2, ".mind/identity");
2980
+ const identityDir = resolve6(mindDir2, ".mind/identity");
3565
2981
  mkdirSync4(identityDir, { recursive: true });
3566
2982
  const { publicKey, privateKey } = generateKeyPairSync("ed25519", {
3567
2983
  publicKeyEncoding: { type: "spki", format: "pem" },
3568
2984
  privateKeyEncoding: { type: "pkcs8", format: "pem" }
3569
2985
  });
3570
- const privatePath = resolve7(identityDir, "private.pem");
3571
- const publicPath = resolve7(identityDir, "public.pem");
2986
+ const privatePath = resolve6(identityDir, "private.pem");
2987
+ const publicPath = resolve6(identityDir, "public.pem");
3572
2988
  writeFileSync4(privatePath, privateKey, { mode: 384 });
3573
2989
  writeFileSync4(publicPath, publicKey, { mode: 420 });
3574
2990
  const config2 = readVoluteConfig(mindDir2) ?? {};
@@ -3583,9 +2999,9 @@ function getPublicKey(mindDir2) {
3583
2999
  const config2 = readVoluteConfig(mindDir2);
3584
3000
  const relPath = config2?.identity?.publicKey;
3585
3001
  if (!relPath) return null;
3586
- const fullPath = resolve7(mindDir2, relPath);
3587
- if (!existsSync5(fullPath)) return null;
3588
- return readFileSync6(fullPath, "utf-8");
3002
+ const fullPath = resolve6(mindDir2, relPath);
3003
+ if (!existsSync4(fullPath)) return null;
3004
+ return readFileSync5(fullPath, "utf-8");
3589
3005
  }
3590
3006
  function getFingerprint(publicKeyPem) {
3591
3007
  return createHash("sha256").update(publicKeyPem).digest("hex");
@@ -3632,16 +3048,16 @@ var keys_default = app10;
3632
3048
 
3633
3049
  // packages/daemon/src/web/api/logs.ts
3634
3050
  import { spawn as spawn2 } from "child_process";
3635
- import { existsSync as existsSync6 } from "fs";
3636
- import { resolve as resolve8 } from "path";
3051
+ import { existsSync as existsSync5 } from "fs";
3052
+ import { resolve as resolve7 } from "path";
3637
3053
  import { Hono as Hono13 } from "hono";
3638
3054
  import { streamSSE as streamSSE3 } from "hono/streaming";
3639
3055
  var app11 = new Hono13().get("/:name/logs", async (c) => {
3640
3056
  const name = c.req.param("name");
3641
3057
  const entry = await findMind(name);
3642
3058
  if (!entry) return c.json({ error: "Mind not found" }, 404);
3643
- const logFile = resolve8(stateDir(name), "logs", "mind.log");
3644
- if (!existsSync6(logFile)) {
3059
+ const logFile = resolve7(stateDir(name), "logs", "mind.log");
3060
+ if (!existsSync5(logFile)) {
3645
3061
  return c.json({ error: "No log file found" }, 404);
3646
3062
  }
3647
3063
  return streamSSE3(c, async (stream) => {
@@ -3659,17 +3075,17 @@ var app11 = new Hono13().get("/:name/logs", async (c) => {
3659
3075
  stream.onAbort(() => {
3660
3076
  tail.kill();
3661
3077
  });
3662
- await new Promise((resolve18) => {
3663
- tail.on("exit", resolve18);
3664
- stream.onAbort(resolve18);
3078
+ await new Promise((resolve17) => {
3079
+ tail.on("exit", resolve17);
3080
+ stream.onAbort(resolve17);
3665
3081
  });
3666
3082
  });
3667
3083
  }).get("/:name/logs/tail", async (c) => {
3668
3084
  const name = c.req.param("name");
3669
3085
  const entry = await findMind(name);
3670
3086
  if (!entry) return c.json({ error: "Mind not found" }, 404);
3671
- const logFile = resolve8(stateDir(name), "logs", "mind.log");
3672
- if (!existsSync6(logFile)) {
3087
+ const logFile = resolve7(stateDir(name), "logs", "mind.log");
3088
+ if (!existsSync5(logFile)) {
3673
3089
  return c.json({ error: "No log file found" }, 404);
3674
3090
  }
3675
3091
  const nParam = parseInt(c.req.query("n") ?? "50", 10);
@@ -3679,8 +3095,8 @@ var app11 = new Hono13().get("/:name/logs", async (c) => {
3679
3095
  tail.stdout.on("data", (data) => {
3680
3096
  output += data.toString();
3681
3097
  });
3682
- await new Promise((resolve18) => {
3683
- tail.on("exit", resolve18);
3098
+ await new Promise((resolve17) => {
3099
+ tail.on("exit", resolve17);
3684
3100
  });
3685
3101
  return c.text(output);
3686
3102
  });
@@ -3770,33 +3186,33 @@ var mind_skills_default = app12;
3770
3186
  // packages/daemon/src/web/api/minds.ts
3771
3187
  import {
3772
3188
  cpSync,
3773
- existsSync as existsSync8,
3189
+ existsSync as existsSync7,
3774
3190
  mkdirSync as mkdirSync6,
3775
3191
  readdirSync as readdirSync2,
3776
- readFileSync as readFileSync9,
3192
+ readFileSync as readFileSync8,
3777
3193
  rmSync as rmSync4,
3778
3194
  writeFileSync as writeFileSync7
3779
3195
  } from "fs";
3780
- import { resolve as resolve11 } from "path";
3196
+ import { resolve as resolve10 } from "path";
3781
3197
  import { zValidator as zValidator5 } from "@hono/zod-validator";
3782
3198
  import { and as and4, desc as desc3, eq as eq5, sql as sql2 } from "drizzle-orm";
3783
3199
  import { Hono as Hono15 } from "hono";
3784
3200
  import { z as z5 } from "zod";
3785
3201
 
3786
- // packages/daemon/src/lib/consolidate.ts
3787
- import { readdirSync, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
3788
- import { resolve as resolve9 } from "path";
3202
+ // packages/daemon/src/lib/mind/consolidate.ts
3203
+ import { readdirSync, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
3204
+ import { resolve as resolve8 } from "path";
3789
3205
  async function consolidateMemory(mindDir2) {
3790
- const soulPath = resolve9(mindDir2, "home/SOUL.md");
3791
- const memoryPath = resolve9(mindDir2, "home/MEMORY.md");
3792
- const memoryDir = resolve9(mindDir2, "home/memory");
3793
- const soul = readFileSync7(soulPath, "utf-8");
3206
+ const soulPath = resolve8(mindDir2, "home/SOUL.md");
3207
+ const memoryPath = resolve8(mindDir2, "home/MEMORY.md");
3208
+ const memoryDir = resolve8(mindDir2, "home/memory");
3209
+ const soul = readFileSync6(soulPath, "utf-8");
3794
3210
  const logs = [];
3795
3211
  try {
3796
3212
  const files = readdirSync(memoryDir).filter((f) => /^\d{4}-\d{2}-\d{2}\.md$/.test(f)).sort();
3797
3213
  for (const filename of files) {
3798
3214
  const date = filename.replace(".md", "");
3799
- const content2 = readFileSync7(resolve9(memoryDir, filename), "utf-8").trim();
3215
+ const content2 = readFileSync6(resolve8(memoryDir, filename), "utf-8").trim();
3800
3216
  if (content2) {
3801
3217
  logs.push(`### ${date}
3802
3218
 
@@ -3854,13 +3270,71 @@ ${content2}`);
3854
3270
  }
3855
3271
  }
3856
3272
 
3857
- // packages/daemon/src/lib/convert-session.ts
3273
+ // packages/daemon/src/lib/mind/variant-cleanup.ts
3274
+ import { existsSync as existsSync6, rmSync as rmSync3 } from "fs";
3275
+ async function cleanupVariant(variantName, projectRoot, variantPath, opts) {
3276
+ if (opts?.stop) {
3277
+ try {
3278
+ await getMindManager().stopMind(variantName);
3279
+ } catch (err) {
3280
+ logger_default.warn(`failed to stop variant ${variantName}`, logger_default.errorData(err));
3281
+ }
3282
+ }
3283
+ const { findMind: findMind2 } = await import("./registry-KMELPC3X.js");
3284
+ const variantEntry = await findMind2(variantName);
3285
+ const branchName = variantEntry?.branch ?? variantName;
3286
+ if (existsSync6(variantPath)) {
3287
+ try {
3288
+ await gitExec(["worktree", "remove", "--force", variantPath], { cwd: projectRoot });
3289
+ } catch {
3290
+ rmSync3(variantPath, { recursive: true, force: true });
3291
+ try {
3292
+ await gitExec(["worktree", "prune"], { cwd: projectRoot });
3293
+ } catch (err) {
3294
+ logger_default.warn(`failed to prune worktrees for ${variantName}`, logger_default.errorData(err));
3295
+ }
3296
+ }
3297
+ }
3298
+ try {
3299
+ await gitExec(["branch", "-D", branchName], { cwd: projectRoot });
3300
+ } catch (err) {
3301
+ logger_default.warn(`failed to delete branch ${branchName} for ${variantName}`, logger_default.errorData(err));
3302
+ }
3303
+ const baseName = variantEntry?.parent ?? variantName;
3304
+ try {
3305
+ await removeMind(variantName);
3306
+ } catch (err) {
3307
+ logger_default.warn(`failed to remove variant ${variantName} from DB`, logger_default.errorData(err));
3308
+ }
3309
+ try {
3310
+ chownMindDir(projectRoot, baseName);
3311
+ } catch (err) {
3312
+ logger_default.error(
3313
+ `failed to fix ownership during variant cleanup for ${variantName}`,
3314
+ logger_default.errorData(err)
3315
+ );
3316
+ }
3317
+ }
3318
+
3319
+ // packages/daemon/src/lib/mind/variants.ts
3320
+ var SAFE_BRANCH_RE = /^[a-zA-Z0-9._\-/]+$/;
3321
+ function validateBranchName(branch) {
3322
+ if (!SAFE_BRANCH_RE.test(branch)) {
3323
+ return `Invalid branch name: ${branch}. Only alphanumeric, '.', '_', '-', '/' allowed.`;
3324
+ }
3325
+ if (branch.includes("..")) {
3326
+ return `Invalid branch name: ${branch}. '..' not allowed.`;
3327
+ }
3328
+ return null;
3329
+ }
3330
+
3331
+ // packages/daemon/src/lib/template/convert-session.ts
3858
3332
  import { randomUUID } from "crypto";
3859
- import { mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "fs";
3333
+ import { mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
3860
3334
  import { homedir } from "os";
3861
- import { resolve as resolve10 } from "path";
3335
+ import { resolve as resolve9 } from "path";
3862
3336
  function convertSession(opts) {
3863
- const lines = readFileSync8(opts.sessionPath, "utf-8").trim().split("\n");
3337
+ const lines = readFileSync7(opts.sessionPath, "utf-8").trim().split("\n");
3864
3338
  const sessionId = randomUUID();
3865
3339
  const idMap = /* @__PURE__ */ new Map();
3866
3340
  const messages = [];
@@ -3974,9 +3448,9 @@ function convertSession(opts) {
3974
3448
  }
3975
3449
  }
3976
3450
  const projectId = opts.projectDir.replace(/\//g, "-");
3977
- const sdkDir = resolve10(homedir(), ".claude", "projects", projectId);
3451
+ const sdkDir = resolve9(homedir(), ".claude", "projects", projectId);
3978
3452
  mkdirSync5(sdkDir, { recursive: true });
3979
- const sdkPath = resolve10(sdkDir, `${sessionId}.jsonl`);
3453
+ const sdkPath = resolve9(sdkDir, `${sessionId}.jsonl`);
3980
3454
  writeFileSync6(sdkPath, `${sdkEvents.join("\n")}
3981
3455
  `);
3982
3456
  console.log(`Converted ${sdkEvents.length} messages \u2192 ${sdkPath}`);
@@ -4028,7 +3502,7 @@ function convertAssistantContent(content) {
4028
3502
  return result;
4029
3503
  }
4030
3504
 
4031
- // packages/daemon/src/lib/health.ts
3505
+ // packages/daemon/src/lib/util/health.ts
4032
3506
  async function checkHealth(port) {
4033
3507
  try {
4034
3508
  const res = await fetch(`http://127.0.0.1:${port}/health`, {
@@ -4042,85 +3516,29 @@ async function checkHealth(port) {
4042
3516
  }
4043
3517
  }
4044
3518
 
4045
- // packages/daemon/src/lib/variant-cleanup.ts
4046
- import { existsSync as existsSync7, rmSync as rmSync3 } from "fs";
4047
- async function cleanupVariant(variantName, projectRoot, variantPath, opts) {
4048
- if (opts?.stop) {
4049
- try {
4050
- await getMindManager().stopMind(variantName);
4051
- } catch (err) {
4052
- logger_default.warn(`failed to stop variant ${variantName}`, logger_default.errorData(err));
4053
- }
4054
- }
4055
- const { findMind: findMind2 } = await import("./registry-GBSNW3HG.js");
4056
- const variantEntry = await findMind2(variantName);
4057
- const branchName = variantEntry?.branch ?? variantName;
4058
- if (existsSync7(variantPath)) {
4059
- try {
4060
- await gitExec(["worktree", "remove", "--force", variantPath], { cwd: projectRoot });
4061
- } catch {
4062
- rmSync3(variantPath, { recursive: true, force: true });
4063
- try {
4064
- await gitExec(["worktree", "prune"], { cwd: projectRoot });
4065
- } catch (err) {
4066
- logger_default.warn(`failed to prune worktrees for ${variantName}`, logger_default.errorData(err));
4067
- }
4068
- }
4069
- }
4070
- try {
4071
- await gitExec(["branch", "-D", branchName], { cwd: projectRoot });
4072
- } catch (err) {
4073
- logger_default.warn(`failed to delete branch ${branchName} for ${variantName}`, logger_default.errorData(err));
4074
- }
4075
- const baseName = variantEntry?.parent ?? variantName;
4076
- try {
4077
- await removeMind(variantName);
4078
- } catch (err) {
4079
- logger_default.warn(`failed to remove variant ${variantName} from DB`, logger_default.errorData(err));
4080
- }
4081
- try {
4082
- chownMindDir(projectRoot, baseName);
4083
- } catch (err) {
4084
- logger_default.error(
4085
- `failed to fix ownership during variant cleanup for ${variantName}`,
4086
- logger_default.errorData(err)
4087
- );
4088
- }
4089
- }
4090
-
4091
- // packages/daemon/src/lib/variants.ts
4092
- var SAFE_BRANCH_RE = /^[a-zA-Z0-9._\-/]+$/;
4093
- function validateBranchName(branch) {
4094
- if (!SAFE_BRANCH_RE.test(branch)) {
4095
- return `Invalid branch name: ${branch}. Only alphanumeric, '.', '_', '-', '/' allowed.`;
4096
- }
4097
- if (branch.includes("..")) {
4098
- return `Invalid branch name: ${branch}. '..' not allowed.`;
4099
- }
4100
- return null;
4101
- }
4102
-
4103
3519
  // packages/daemon/src/web/api/minds.ts
4104
3520
  var SUBSTANTIVE_TYPES = /* @__PURE__ */ new Set(["thinking", "text", "tool_use", "tool_result", "outbound"]);
4105
- async function getMindStatus(name, port) {
3521
+ var _lastActiveCache = { map: /* @__PURE__ */ new Map(), ts: 0 };
3522
+ var _LAST_ACTIVE_TTL = 6e4;
3523
+ async function getMindStatus(name, port, registryRunning) {
4106
3524
  const manager = getMindManager();
4107
3525
  let status = "stopped";
4108
3526
  try {
4109
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-7KFK3USC.js");
3527
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-TQP5ZJI5.js");
4110
3528
  if (getSleepManagerIfReady2()?.isSleeping(name)) {
4111
3529
  status = "sleeping";
4112
3530
  }
4113
3531
  } catch {
4114
3532
  }
4115
- if (status !== "sleeping" && manager.isRunning(name)) {
3533
+ if (status !== "sleeping" && registryRunning !== false && manager.isRunning(name)) {
4116
3534
  const health = await checkHealth(port);
4117
3535
  status = health.ok ? "running" : "starting";
4118
3536
  }
4119
3537
  const config2 = readVoluteConfig(mindDir(name));
4120
- const channels = [];
4121
- for (const [, provider] of Object.entries(CHANNELS)) {
3538
+ const channels2 = [];
3539
+ for (const [, provider] of Object.entries(PLATFORMS)) {
4122
3540
  if (!provider.builtIn) continue;
4123
- channels.push({
3541
+ channels2.push({
4124
3542
  name: provider.name,
4125
3543
  displayName: provider.displayName,
4126
3544
  status: status === "running" ? "connected" : "disconnected"
@@ -4128,7 +3546,7 @@ async function getMindStatus(name, port) {
4128
3546
  }
4129
3547
  return {
4130
3548
  status,
4131
- channels,
3549
+ channels: channels2,
4132
3550
  displayName: config2?.profile?.displayName,
4133
3551
  description: config2?.profile?.description,
4134
3552
  avatar: config2?.profile?.avatar
@@ -4152,7 +3570,7 @@ async function initTemplateBranch(projectRoot, composedDir, manifest, mindName,
4152
3570
  await gitExec(["commit", "-m", "initial commit"], opts);
4153
3571
  }
4154
3572
  async function updateTemplateBranch(projectRoot, template, mindName) {
4155
- const tempWorktree = resolve11(projectRoot, ".variants", "_template_update");
3573
+ const tempWorktree = resolve10(projectRoot, ".variants", "_template_update");
4156
3574
  let branchExists = false;
4157
3575
  try {
4158
3576
  await gitExec(["rev-parse", "--verify", TEMPLATE_BRANCH], { cwd: projectRoot });
@@ -4163,7 +3581,7 @@ async function updateTemplateBranch(projectRoot, template, mindName) {
4163
3581
  await gitExec(["worktree", "remove", "--force", tempWorktree], { cwd: projectRoot });
4164
3582
  } catch {
4165
3583
  }
4166
- if (existsSync8(tempWorktree)) {
3584
+ if (existsSync7(tempWorktree)) {
4167
3585
  rmSync4(tempWorktree, { recursive: true, force: true });
4168
3586
  }
4169
3587
  const templatesRoot = findTemplatesRoot();
@@ -4184,15 +3602,15 @@ async function updateTemplateBranch(projectRoot, template, mindName) {
4184
3602
  });
4185
3603
  }
4186
3604
  copyTemplateToDir(composedDir, tempWorktree, mindName, manifest);
4187
- const initDir = resolve11(tempWorktree, ".init");
4188
- if (existsSync8(initDir)) {
3605
+ const initDir = resolve10(tempWorktree, ".init");
3606
+ if (existsSync7(initDir)) {
4189
3607
  rmSync4(initDir, { recursive: true, force: true });
4190
3608
  }
4191
- const homeDir = resolve11(tempWorktree, "home");
4192
- if (existsSync8(homeDir)) {
3609
+ const homeDir = resolve10(tempWorktree, "home");
3610
+ if (existsSync7(homeDir)) {
4193
3611
  for (const entry of readdirSync2(homeDir)) {
4194
3612
  if (entry !== "VOLUTE.md") {
4195
- rmSync4(resolve11(homeDir, entry), { recursive: true, force: true });
3613
+ rmSync4(resolve10(homeDir, entry), { recursive: true, force: true });
4196
3614
  }
4197
3615
  }
4198
3616
  }
@@ -4207,7 +3625,7 @@ async function updateTemplateBranch(projectRoot, template, mindName) {
4207
3625
  await gitExec(["worktree", "remove", "--force", tempWorktree], { cwd: projectRoot });
4208
3626
  } catch {
4209
3627
  }
4210
- if (existsSync8(tempWorktree)) {
3628
+ if (existsSync7(tempWorktree)) {
4211
3629
  rmSync4(tempWorktree, { recursive: true, force: true });
4212
3630
  }
4213
3631
  rmSync4(composedDir, { recursive: true, force: true });
@@ -4233,7 +3651,7 @@ async function mergeTemplateBranch(worktreeDir) {
4233
3651
  async function npmInstallAsMind(cwd, mindName) {
4234
3652
  if (isIsolationEnabled()) {
4235
3653
  const [cmd, args] = await wrapForIsolation("npm", ["install"], mindName);
4236
- await exec(cmd, args, { cwd, env: { ...process.env, HOME: resolve11(cwd, "home") } });
3654
+ await exec(cmd, args, { cwd, env: { ...process.env, HOME: resolve10(cwd, "home") } });
4237
3655
  } else {
4238
3656
  await exec("npm", ["install"], { cwd });
4239
3657
  }
@@ -4284,8 +3702,8 @@ async function mergeUpgradeAndRestart(mindName, dir, worktreeDir, upgradeVariant
4284
3702
  return { ok: true };
4285
3703
  }
4286
3704
  async function importFromArchive(c, tempDir, nameOverride, manifest) {
4287
- const extractedMindDir = resolve11(tempDir, "mind");
4288
- if (!existsSync8(extractedMindDir)) {
3705
+ const extractedMindDir = resolve10(tempDir, "mind");
3706
+ if (!existsSync7(extractedMindDir)) {
4289
3707
  return c.json({ error: "Invalid archive: missing mind/ directory" }, 400);
4290
3708
  }
4291
3709
  if (!manifest?.includes || !manifest.name || !manifest.template) {
@@ -4303,7 +3721,7 @@ async function importFromFullArchive(c, tempDir, extractedMindDir, nameOverride,
4303
3721
  if (await findMind(name)) return c.json({ error: `Mind already exists: ${name}` }, 409);
4304
3722
  ensureVoluteHome();
4305
3723
  const dest = mindDir(name);
4306
- if (existsSync8(dest)) return c.json({ error: "Mind directory already exists" }, 409);
3724
+ if (existsSync7(dest)) return c.json({ error: "Mind directory already exists" }, 409);
4307
3725
  try {
4308
3726
  cpSync(extractedMindDir, dest, { recursive: true });
4309
3727
  if (!manifest.includes.identity) {
@@ -4311,9 +3729,9 @@ async function importFromFullArchive(c, tempDir, extractedMindDir, nameOverride,
4311
3729
  }
4312
3730
  const state = stateDir(name);
4313
3731
  mkdirSync6(state, { recursive: true });
4314
- const envJson = resolve11(tempDir, "state/env.json");
4315
- if (existsSync8(envJson)) {
4316
- cpSync(envJson, resolve11(state, "env.json"));
3732
+ const envJson = resolve10(tempDir, "state/env.json");
3733
+ if (existsSync7(envJson)) {
3734
+ cpSync(envJson, resolve10(state, "env.json"));
4317
3735
  }
4318
3736
  const port = await nextPort();
4319
3737
  await addMind(name, port, manifest.stage, manifest.template);
@@ -4322,30 +3740,30 @@ async function importFromFullArchive(c, tempDir, extractedMindDir, nameOverride,
4322
3740
  } catch (err) {
4323
3741
  logger_default.warn(`failed to set template hash for ${name}`, logger_default.errorData(err));
4324
3742
  }
4325
- const homeDir = resolve11(dest, "home");
3743
+ const homeDir = resolve10(dest, "home");
4326
3744
  ensureVoluteGroup();
4327
3745
  createMindUser(name, homeDir);
4328
3746
  chownMindDir(dest, name);
4329
3747
  await npmInstallAsMind(dest, name);
4330
3748
  await importHistoryFromArchive(name, tempDir);
4331
3749
  importSessionsFromArchive(dest, tempDir);
4332
- if (!existsSync8(resolve11(dest, ".git"))) {
3750
+ if (!existsSync7(resolve10(dest, ".git"))) {
4333
3751
  try {
4334
- const env = isIsolationEnabled() ? { ...process.env, HOME: resolve11(dest, "home") } : void 0;
3752
+ const env = isIsolationEnabled() ? { ...process.env, HOME: resolve10(dest, "home") } : void 0;
4335
3753
  await gitExec(["init"], { cwd: dest, mindName: name, env });
4336
3754
  await configureGitIdentity(name, { cwd: dest, mindName: name, env });
4337
3755
  await gitExec(["add", "-A"], { cwd: dest, mindName: name, env });
4338
3756
  await gitExec(["commit", "-m", "import from archive"], { cwd: dest, mindName: name, env });
4339
3757
  } catch (err) {
4340
3758
  logger_default.error(`git setup failed for imported mind ${name}`, logger_default.errorData(err));
4341
- rmSync4(resolve11(dest, ".git"), { recursive: true, force: true });
3759
+ rmSync4(resolve10(dest, ".git"), { recursive: true, force: true });
4342
3760
  }
4343
3761
  }
4344
3762
  chownMindDir(dest, name);
4345
3763
  rmSync4(tempDir, { recursive: true, force: true });
4346
3764
  return c.json({ ok: true, name, port, message: `Imported mind: ${name} (port ${port})` });
4347
3765
  } catch (err) {
4348
- if (existsSync8(dest)) rmSync4(dest, { recursive: true, force: true });
3766
+ if (existsSync7(dest)) rmSync4(dest, { recursive: true, force: true });
4349
3767
  try {
4350
3768
  await removeMind(name);
4351
3769
  } catch (cleanupErr) {
@@ -4362,7 +3780,7 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
4362
3780
  if (await findMind(name)) return c.json({ error: `Mind already exists: ${name}` }, 409);
4363
3781
  ensureVoluteHome();
4364
3782
  const dest = mindDir(name);
4365
- if (existsSync8(dest)) return c.json({ error: "Mind directory already exists" }, 409);
3783
+ if (existsSync7(dest)) return c.json({ error: "Mind directory already exists" }, 409);
4366
3784
  const templatesRoot = findTemplatesRoot();
4367
3785
  const { composedDir, manifest: templateManifest } = composeTemplate(
4368
3786
  templatesRoot,
@@ -4371,36 +3789,36 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
4371
3789
  try {
4372
3790
  copyTemplateToDir(composedDir, dest, name, templateManifest);
4373
3791
  applyInitFiles(dest);
4374
- const extractedHome = resolve11(extractedMindDir, "home");
4375
- if (existsSync8(extractedHome)) {
4376
- cpSync(extractedHome, resolve11(dest, "home"), { recursive: true });
3792
+ const extractedHome = resolve10(extractedMindDir, "home");
3793
+ if (existsSync7(extractedHome)) {
3794
+ cpSync(extractedHome, resolve10(dest, "home"), { recursive: true });
4377
3795
  }
4378
- const extractedMindInternal = resolve11(extractedMindDir, ".mind");
4379
- if (existsSync8(extractedMindInternal)) {
4380
- cpSync(extractedMindInternal, resolve11(dest, ".mind"), { recursive: true });
3796
+ const extractedMindInternal = resolve10(extractedMindDir, ".mind");
3797
+ if (existsSync7(extractedMindInternal)) {
3798
+ cpSync(extractedMindInternal, resolve10(dest, ".mind"), { recursive: true });
4381
3799
  }
4382
- const identityDir = resolve11(dest, ".mind/identity");
3800
+ const identityDir = resolve10(dest, ".mind/identity");
4383
3801
  let publicKeyPem;
4384
- if (!manifest.includes.identity || !existsSync8(resolve11(identityDir, "private.pem"))) {
3802
+ if (!manifest.includes.identity || !existsSync7(resolve10(identityDir, "private.pem"))) {
4385
3803
  ({ publicKeyPem } = generateIdentity(dest));
4386
3804
  } else {
4387
- publicKeyPem = readFileSync9(resolve11(identityDir, "public.pem"), "utf-8");
3805
+ publicKeyPem = readFileSync8(resolve10(identityDir, "public.pem"), "utf-8");
4388
3806
  }
4389
- const promptsPath = resolve11(dest, "home/.config/prompts.json");
4390
- if (!existsSync8(promptsPath)) {
3807
+ const promptsPath = resolve10(dest, "home/.config/prompts.json");
3808
+ if (!existsSync7(promptsPath)) {
4391
3809
  const mindPrompts = await getMindPromptDefaults();
4392
3810
  writeFileSync7(promptsPath, `${JSON.stringify(mindPrompts, null, 2)}
4393
3811
  `);
4394
3812
  }
4395
3813
  const state = stateDir(name);
4396
3814
  mkdirSync6(state, { recursive: true });
4397
- const envJson = resolve11(tempDir, "state/env.json");
4398
- if (existsSync8(envJson)) {
4399
- cpSync(envJson, resolve11(state, "env.json"));
3815
+ const envJson = resolve10(tempDir, "state/env.json");
3816
+ if (existsSync7(envJson)) {
3817
+ cpSync(envJson, resolve10(state, "env.json"));
4400
3818
  }
4401
3819
  const port = await nextPort();
4402
3820
  await addMind(name, port, manifest.stage, manifest.template);
4403
- const homeDir = resolve11(dest, "home");
3821
+ const homeDir = resolve10(dest, "home");
4404
3822
  ensureVoluteGroup();
4405
3823
  createMindUser(name, homeDir);
4406
3824
  chownMindDir(dest, name);
@@ -4413,7 +3831,7 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
4413
3831
  await initTemplateBranch(dest, composedDir, templateManifest, name, env);
4414
3832
  } catch (err) {
4415
3833
  logger_default.error(`git setup failed for imported mind ${name}`, logger_default.errorData(err));
4416
- rmSync4(resolve11(dest, ".git"), { recursive: true, force: true });
3834
+ rmSync4(resolve10(dest, ".git"), { recursive: true, force: true });
4417
3835
  gitWarning = "Git setup failed \u2014 variants and upgrades won't be available until git is initialized.";
4418
3836
  }
4419
3837
  const skillSet = manifest.stage === "seed" ? SEED_SKILLS : getStandardSkillsWithExtensions();
@@ -4443,7 +3861,7 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
4443
3861
  ...skillWarnings.length > 0 && { skillWarnings }
4444
3862
  });
4445
3863
  } catch (err) {
4446
- if (existsSync8(dest)) rmSync4(dest, { recursive: true, force: true });
3864
+ if (existsSync7(dest)) rmSync4(dest, { recursive: true, force: true });
4447
3865
  try {
4448
3866
  await removeMind(name);
4449
3867
  } catch (cleanupErr) {
@@ -4456,11 +3874,11 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
4456
3874
  }
4457
3875
  }
4458
3876
  async function importHistoryFromArchive(name, tempDir) {
4459
- const historyJsonl = resolve11(tempDir, "history.jsonl");
4460
- if (!existsSync8(historyJsonl)) return;
3877
+ const historyJsonl = resolve10(tempDir, "history.jsonl");
3878
+ if (!existsSync7(historyJsonl)) return;
4461
3879
  try {
4462
3880
  const db = await getDb();
4463
- const lines = readFileSync9(historyJsonl, "utf-8").trim().split("\n");
3881
+ const lines = readFileSync8(historyJsonl, "utf-8").trim().split("\n");
4464
3882
  let imported = 0;
4465
3883
  let failed = 0;
4466
3884
  for (const line of lines) {
@@ -4496,13 +3914,13 @@ async function importHistoryFromArchive(name, tempDir) {
4496
3914
  }
4497
3915
  }
4498
3916
  function importSessionsFromArchive(dest, tempDir) {
4499
- const sessionsDir = resolve11(tempDir, "sessions");
4500
- if (!existsSync8(sessionsDir)) return;
3917
+ const sessionsDir = resolve10(tempDir, "sessions");
3918
+ if (!existsSync7(sessionsDir)) return;
4501
3919
  try {
4502
- const destSessions = resolve11(dest, ".mind/sessions");
3920
+ const destSessions = resolve10(dest, ".mind/sessions");
4503
3921
  mkdirSync6(destSessions, { recursive: true });
4504
3922
  for (const file of readdirSync2(sessionsDir)) {
4505
- cpSync(resolve11(sessionsDir, file), resolve11(destSessions, file));
3923
+ cpSync(resolve10(sessionsDir, file), resolve10(destSessions, file));
4506
3924
  }
4507
3925
  } catch (err) {
4508
3926
  logger_default.error("Failed to import sessions from archive", logger_default.errorData(err));
@@ -4537,7 +3955,7 @@ var app13 = new Hono15().post("/", requireAdminOrSystem, zValidator5("json", cre
4537
3955
  if (await findMind(name)) return c.json({ error: `Mind already exists: ${name}` }, 409);
4538
3956
  ensureVoluteHome();
4539
3957
  const dest = mindDir(name);
4540
- if (existsSync8(dest)) return c.json({ error: "Mind directory already exists" }, 409);
3958
+ if (existsSync7(dest)) return c.json({ error: "Mind directory already exists" }, 409);
4541
3959
  const templatesRoot = findTemplatesRoot();
4542
3960
  const { composedDir, manifest } = composeTemplate(templatesRoot, template);
4543
3961
  try {
@@ -4545,7 +3963,7 @@ var app13 = new Hono15().post("/", requireAdminOrSystem, zValidator5("json", cre
4545
3963
  applyInitFiles(dest);
4546
3964
  const { publicKeyPem } = generateIdentity(dest);
4547
3965
  {
4548
- const { readGlobalConfig: readGlobal } = await import("./setup-RHJRFURI.js");
3966
+ const { readGlobalConfig: readGlobal } = await import("./setup-PF7JSFMO.js");
4549
3967
  const mindDefaults = readGlobal().mindDefaults;
4550
3968
  const config2 = readVoluteConfig(dest);
4551
3969
  if (!config2) throw new Error("Failed to read volute.json after identity generation");
@@ -4582,9 +4000,9 @@ var app13 = new Hono15().post("/", requireAdminOrSystem, zValidator5("json", cre
4582
4000
  }
4583
4001
  writeVoluteConfig(dest, config2);
4584
4002
  const modelId = body.model ?? cog?.model;
4585
- const sdkConfigPath = resolve11(dest, "home/.config/config.json");
4003
+ const sdkConfigPath = resolve10(dest, "home/.config/config.json");
4586
4004
  if (modelId || cog?.compaction) {
4587
- const existing = existsSync8(sdkConfigPath) ? JSON.parse(readFileSync9(sdkConfigPath, "utf-8")) : {};
4005
+ const existing = existsSync7(sdkConfigPath) ? JSON.parse(readFileSync8(sdkConfigPath, "utf-8")) : {};
4588
4006
  if (modelId) {
4589
4007
  existing.model = template === "pi" ? qualifyModelId(modelId) : unqualifyModelId(modelId);
4590
4008
  }
@@ -4597,7 +4015,7 @@ var app13 = new Hono15().post("/", requireAdminOrSystem, zValidator5("json", cre
4597
4015
  }
4598
4016
  const mindPrompts = await getMindPromptDefaults();
4599
4017
  writeFileSync7(
4600
- resolve11(dest, "home/.config/prompts.json"),
4018
+ resolve10(dest, "home/.config/prompts.json"),
4601
4019
  `${JSON.stringify(mindPrompts, null, 2)}
4602
4020
  `
4603
4021
  );
@@ -4609,7 +4027,7 @@ var app13 = new Hono15().post("/", requireAdminOrSystem, zValidator5("json", cre
4609
4027
  } catch (err) {
4610
4028
  logger_default.warn(`failed to set template hash for ${name}`, logger_default.errorData(err));
4611
4029
  }
4612
- const homeDir = resolve11(dest, "home");
4030
+ const homeDir = resolve10(dest, "home");
4613
4031
  ensureVoluteGroup();
4614
4032
  createMindUser(name, homeDir);
4615
4033
  chownMindDir(dest, name);
@@ -4622,7 +4040,7 @@ var app13 = new Hono15().post("/", requireAdminOrSystem, zValidator5("json", cre
4622
4040
  await initTemplateBranch(dest, composedDir, manifest, name, env);
4623
4041
  } catch (err) {
4624
4042
  logger_default.error(`git setup failed for ${name}`, logger_default.errorData(err));
4625
- rmSync4(resolve11(dest, ".git"), { recursive: true, force: true });
4043
+ rmSync4(resolve10(dest, ".git"), { recursive: true, force: true });
4626
4044
  gitWarning = "Git setup failed \u2014 variants and upgrades won't be available until git is initialized.";
4627
4045
  }
4628
4046
  chownMindDir(dest, name);
@@ -4632,11 +4050,11 @@ The human who planted you described you as: "${body.description}"
4632
4050
  ` : "";
4633
4051
  const seedSoulRaw = body.seedSoul ?? await getPrompt("seed_soul", { name, description: descLine });
4634
4052
  const seedSoul = body.seedSoul ? substitute(seedSoulRaw, { name, description: descLine }) : seedSoulRaw;
4635
- writeFileSync7(resolve11(dest, "home/SOUL.md"), seedSoul);
4053
+ writeFileSync7(resolve10(dest, "home/SOUL.md"), seedSoul);
4636
4054
  }
4637
4055
  let skillSet = body.skills ?? (body.stage === "seed" ? SEED_SKILLS : getStandardSkillsWithExtensions());
4638
4056
  if (body.stage === "seed" && !body.skills) {
4639
- const { isImagegenEnabled } = await import("./setup-RHJRFURI.js");
4057
+ const { isImagegenEnabled } = await import("./setup-PF7JSFMO.js");
4640
4058
  if (isImagegenEnabled()) {
4641
4059
  skillSet = [...skillSet, "imagegen"];
4642
4060
  }
@@ -4654,7 +4072,7 @@ The human who planted you described you as: "${body.description}"
4654
4072
  try {
4655
4073
  const spiritEntry = await findMind("volute");
4656
4074
  if (spiritEntry) {
4657
- const { spiritDir } = await import("./spirit-ZFRDXMG7.js");
4075
+ const { spiritDir } = await import("./spirit-SM6ARJ2N.js");
4658
4076
  const sDir = spiritEntry.dir ?? spiritDir();
4659
4077
  const spiritConfig = readVoluteConfig(sDir) ?? {};
4660
4078
  const schedules = spiritConfig.schedules ?? [];
@@ -4669,7 +4087,7 @@ The human who planted you described you as: "${body.description}"
4669
4087
  });
4670
4088
  spiritConfig.schedules = schedules;
4671
4089
  writeVoluteConfig(sDir, spiritConfig);
4672
- const { getScheduler: getScheduler2 } = await import("./scheduler-Y7O4CJXL.js");
4090
+ const { getScheduler: getScheduler2 } = await import("./scheduler-NTC74JYH.js");
4673
4091
  getScheduler2().loadSchedules("volute", sDir);
4674
4092
  }
4675
4093
  }
@@ -4680,11 +4098,11 @@ The human who planted you described you as: "${body.description}"
4680
4098
  if (body.stage !== "seed") {
4681
4099
  const customSoul = await getPromptIfCustom("default_soul");
4682
4100
  if (customSoul) {
4683
- writeFileSync7(resolve11(dest, "home/SOUL.md"), customSoul.replace(/\{\{name\}\}/g, name));
4101
+ writeFileSync7(resolve10(dest, "home/SOUL.md"), customSoul.replace(/\{\{name\}\}/g, name));
4684
4102
  }
4685
4103
  const customMemory = await getPromptIfCustom("default_memory");
4686
4104
  if (customMemory) {
4687
- writeFileSync7(resolve11(dest, "home/MEMORY.md"), customMemory);
4105
+ writeFileSync7(resolve10(dest, "home/MEMORY.md"), customMemory);
4688
4106
  }
4689
4107
  }
4690
4108
  publishPublicKey(name, publicKeyPem).catch(
@@ -4713,7 +4131,7 @@ The human who planted you described you as: "${body.description}"
4713
4131
  ...skillWarnings.length > 0 && { skillWarnings }
4714
4132
  });
4715
4133
  } catch (err) {
4716
- if (existsSync8(dest)) rmSync4(dest, { recursive: true, force: true });
4134
+ if (existsSync7(dest)) rmSync4(dest, { recursive: true, force: true });
4717
4135
  try {
4718
4136
  await removeMind(name);
4719
4137
  } catch {
@@ -4733,13 +4151,13 @@ The human who planted you described you as: "${body.description}"
4733
4151
  return importFromArchive(c, body.archivePath, body.name, body.manifest);
4734
4152
  }
4735
4153
  const wsDir = body.workspacePath;
4736
- if (!wsDir || !existsSync8(resolve11(wsDir, "SOUL.md")) || !existsSync8(resolve11(wsDir, "IDENTITY.md"))) {
4154
+ if (!wsDir || !existsSync7(resolve10(wsDir, "SOUL.md")) || !existsSync7(resolve10(wsDir, "IDENTITY.md"))) {
4737
4155
  return c.json({ error: "Invalid workspace: missing SOUL.md or IDENTITY.md" }, 400);
4738
4156
  }
4739
- const soul = readFileSync9(resolve11(wsDir, "SOUL.md"), "utf-8");
4740
- const identity = readFileSync9(resolve11(wsDir, "IDENTITY.md"), "utf-8");
4741
- const userPath = resolve11(wsDir, "USER.md");
4742
- const user = existsSync8(userPath) ? readFileSync9(userPath, "utf-8") : "";
4157
+ const soul = readFileSync8(resolve10(wsDir, "SOUL.md"), "utf-8");
4158
+ const identity = readFileSync8(resolve10(wsDir, "IDENTITY.md"), "utf-8");
4159
+ const userPath = resolve10(wsDir, "USER.md");
4160
+ const user = existsSync7(userPath) ? readFileSync8(userPath, "utf-8") : "";
4743
4161
  const name = body.name ?? parseNameFromIdentity(identity) ?? "imported-mind";
4744
4162
  const template = body.template ?? "claude";
4745
4163
  const nameErr = validateMindName(name);
@@ -4759,33 +4177,33 @@ ${user.trimEnd()}
4759
4177
  ` : "";
4760
4178
  ensureVoluteHome();
4761
4179
  const dest = mindDir(name);
4762
- if (existsSync8(dest)) return c.json({ error: "Mind directory already exists" }, 409);
4180
+ if (existsSync7(dest)) return c.json({ error: "Mind directory already exists" }, 409);
4763
4181
  const templatesRoot = findTemplatesRoot();
4764
4182
  const { composedDir, manifest } = composeTemplate(templatesRoot, template);
4765
4183
  try {
4766
4184
  copyTemplateToDir(composedDir, dest, name, manifest);
4767
4185
  applyInitFiles(dest);
4768
4186
  const { publicKeyPem: importPublicKey } = generateIdentity(dest);
4769
- writeFileSync7(resolve11(dest, "home/SOUL.md"), mergedSoul);
4770
- const wsMemoryPath = resolve11(wsDir, "MEMORY.md");
4771
- const hasMemory = existsSync8(wsMemoryPath);
4187
+ writeFileSync7(resolve10(dest, "home/SOUL.md"), mergedSoul);
4188
+ const wsMemoryPath = resolve10(wsDir, "MEMORY.md");
4189
+ const hasMemory = existsSync7(wsMemoryPath);
4772
4190
  if (hasMemory) {
4773
- const existingMemory = readFileSync9(wsMemoryPath, "utf-8");
4191
+ const existingMemory = readFileSync8(wsMemoryPath, "utf-8");
4774
4192
  writeFileSync7(
4775
- resolve11(dest, "home/MEMORY.md"),
4193
+ resolve10(dest, "home/MEMORY.md"),
4776
4194
  `${existingMemory.trimEnd()}${mergedMemoryExtra}`
4777
4195
  );
4778
4196
  } else if (user) {
4779
- writeFileSync7(resolve11(dest, "home/MEMORY.md"), `${user.trimEnd()}
4197
+ writeFileSync7(resolve10(dest, "home/MEMORY.md"), `${user.trimEnd()}
4780
4198
  `);
4781
4199
  }
4782
- const wsMemoryDir = resolve11(wsDir, "memory");
4200
+ const wsMemoryDir = resolve10(wsDir, "memory");
4783
4201
  let dailyLogCount = 0;
4784
- if (existsSync8(wsMemoryDir)) {
4785
- const destMemoryDir = resolve11(dest, "home/memory");
4202
+ if (existsSync7(wsMemoryDir)) {
4203
+ const destMemoryDir = resolve10(dest, "home/memory");
4786
4204
  const files = readdirSync2(wsMemoryDir).filter((f) => f.endsWith(".md"));
4787
4205
  for (const file of files) {
4788
- cpSync(resolve11(wsMemoryDir, file), resolve11(destMemoryDir, file));
4206
+ cpSync(resolve10(wsMemoryDir, file), resolve10(destMemoryDir, file));
4789
4207
  }
4790
4208
  dailyLogCount = files.length;
4791
4209
  }
@@ -4796,7 +4214,7 @@ ${user.trimEnd()}
4796
4214
  } catch (err) {
4797
4215
  logger_default.warn(`failed to set template hash for ${name}`, logger_default.errorData(err));
4798
4216
  }
4799
- const homeDir = resolve11(dest, "home");
4217
+ const homeDir = resolve10(dest, "home");
4800
4218
  ensureVoluteGroup();
4801
4219
  createMindUser(name, homeDir);
4802
4220
  chownMindDir(dest, name);
@@ -4804,20 +4222,20 @@ ${user.trimEnd()}
4804
4222
  if (!hasMemory && dailyLogCount > 0) {
4805
4223
  await consolidateMemory(dest);
4806
4224
  }
4807
- const env = isIsolationEnabled() ? { ...process.env, HOME: resolve11(dest, "home") } : void 0;
4225
+ const env = isIsolationEnabled() ? { ...process.env, HOME: resolve10(dest, "home") } : void 0;
4808
4226
  await gitExec(["init"], { cwd: dest, mindName: name, env });
4809
4227
  await configureGitIdentity(name, { cwd: dest, mindName: name, env });
4810
4228
  await gitExec(["add", "-A"], { cwd: dest, mindName: name, env });
4811
4229
  await gitExec(["commit", "-m", "import from OpenClaw"], { cwd: dest, mindName: name, env });
4812
- const sessionFile = body.sessionPath ? resolve11(body.sessionPath) : findOpenClawSession(wsDir);
4813
- if (sessionFile && existsSync8(sessionFile)) {
4230
+ const sessionFile = body.sessionPath ? resolve10(body.sessionPath) : findOpenClawSession(wsDir);
4231
+ if (sessionFile && existsSync7(sessionFile)) {
4814
4232
  if (template === "pi") {
4815
4233
  importPiSession(sessionFile, dest);
4816
4234
  } else if (template === "claude") {
4817
4235
  const sessionId = convertSession({ sessionPath: sessionFile, projectDir: dest });
4818
- const mindRuntimeDir = resolve11(dest, ".mind");
4236
+ const mindRuntimeDir = resolve10(dest, ".mind");
4819
4237
  mkdirSync6(mindRuntimeDir, { recursive: true });
4820
- writeFileSync7(resolve11(mindRuntimeDir, "session.json"), JSON.stringify({ sessionId }));
4238
+ writeFileSync7(resolve10(mindRuntimeDir, "session.json"), JSON.stringify({ sessionId }));
4821
4239
  }
4822
4240
  }
4823
4241
  importOpenClawConnectors(name, dest);
@@ -4827,7 +4245,7 @@ ${user.trimEnd()}
4827
4245
  );
4828
4246
  return c.json({ ok: true, name, port, message: `Imported mind: ${name} (port ${port})` });
4829
4247
  } catch (err) {
4830
- if (existsSync8(dest)) rmSync4(dest, { recursive: true, force: true });
4248
+ if (existsSync7(dest)) rmSync4(dest, { recursive: true, force: true });
4831
4249
  try {
4832
4250
  await removeMind(name);
4833
4251
  } catch {
@@ -4838,20 +4256,27 @@ ${user.trimEnd()}
4838
4256
  }
4839
4257
  }).get("/", async (c) => {
4840
4258
  const entries = await readRegistry();
4841
- let lastActiveMap = /* @__PURE__ */ new Map();
4842
- try {
4843
- const db = await getDb();
4844
- const lastActiveRows = await db.select({
4845
- mind: mindHistory.mind,
4846
- lastActiveAt: sql2`MAX(${mindHistory.created_at})`
4847
- }).from(mindHistory).groupBy(mindHistory.mind);
4848
- lastActiveMap = new Map(lastActiveRows.map((r) => [r.mind, r.lastActiveAt]));
4849
- } catch {
4259
+ let lastActiveMap;
4260
+ if (_lastActiveCache.ts > 0 && Date.now() - _lastActiveCache.ts < _LAST_ACTIVE_TTL) {
4261
+ lastActiveMap = _lastActiveCache.map;
4262
+ } else {
4263
+ lastActiveMap = /* @__PURE__ */ new Map();
4264
+ try {
4265
+ const db = await getDb();
4266
+ const lastActiveRows = await db.select({
4267
+ mind: mindHistory.mind,
4268
+ lastActiveAt: sql2`MAX(${mindHistory.created_at})`
4269
+ }).from(mindHistory).groupBy(mindHistory.mind);
4270
+ lastActiveMap = new Map(lastActiveRows.map((r) => [r.mind, r.lastActiveAt]));
4271
+ _lastActiveCache.map = lastActiveMap;
4272
+ _lastActiveCache.ts = Date.now();
4273
+ } catch {
4274
+ }
4850
4275
  }
4851
4276
  const minds = await Promise.all(
4852
4277
  entries.map(async (entry) => {
4853
- const mindStatus = await getMindStatus(entry.name, entry.port);
4854
- const hasPages = existsSync8(resolve11(mindDir(entry.name), "home", "pages"));
4278
+ const mindStatus = await getMindStatus(entry.name, entry.port, entry.running);
4279
+ const hasPages = existsSync7(resolve10(mindDir(entry.name), "home", "pages"));
4855
4280
  return {
4856
4281
  ...entry,
4857
4282
  ...mindStatus,
@@ -4866,7 +4291,7 @@ ${user.trimEnd()}
4866
4291
  const entry = await findMind(name);
4867
4292
  if (!entry) return c.json({ error: "Mind not found" }, 404);
4868
4293
  const dir = entry.dir ?? mindDir(entry.parent ?? name);
4869
- if (!existsSync8(dir)) return c.json({ error: "Mind directory missing" }, 404);
4294
+ if (!existsSync7(dir)) return c.json({ error: "Mind directory missing" }, 404);
4870
4295
  const mindStatus = await getMindStatus(name, entry.port);
4871
4296
  const variants = await findVariants(name);
4872
4297
  const manager = getMindManager();
@@ -4880,7 +4305,7 @@ ${user.trimEnd()}
4880
4305
  return { name: s.name, port: s.port, status: variantStatus };
4881
4306
  })
4882
4307
  );
4883
- const hasPages = existsSync8(resolve11(mindDir(name), "home", "pages"));
4308
+ const hasPages = existsSync7(resolve10(mindDir(name), "home", "pages"));
4884
4309
  return c.json({ ...entry, ...mindStatus, variants: variantStatuses, hasPages });
4885
4310
  }).get("/:name/context", async (c) => {
4886
4311
  const name = c.req.param("name");
@@ -4917,7 +4342,7 @@ ${user.trimEnd()}
4917
4342
  if (!entry.dir) return c.json({ error: `Variant ${name} has no directory` }, 404);
4918
4343
  } else {
4919
4344
  const dir = mindDir(name);
4920
- if (!existsSync8(dir)) return c.json({ error: "Mind directory missing" }, 404);
4345
+ if (!existsSync7(dir)) return c.json({ error: "Mind directory missing" }, 404);
4921
4346
  }
4922
4347
  if (getMindManager().isRunning(name)) {
4923
4348
  return c.json({ error: "Mind already running" }, 409);
@@ -4938,7 +4363,7 @@ ${user.trimEnd()}
4938
4363
  if (!entry.dir) return c.json({ error: `Variant ${name} has no directory` }, 404);
4939
4364
  } else {
4940
4365
  const dir = mindDir(name);
4941
- if (!existsSync8(dir)) return c.json({ error: "Mind directory missing" }, 404);
4366
+ if (!existsSync7(dir)) return c.json({ error: "Mind directory missing" }, 404);
4942
4367
  }
4943
4368
  let context;
4944
4369
  const contentType = c.req.header("content-type");
@@ -4953,7 +4378,7 @@ ${user.trimEnd()}
4953
4378
  const manager = getMindManager();
4954
4379
  try {
4955
4380
  if (context?.type === "reload") {
4956
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-7KFK3USC.js");
4381
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-TQP5ZJI5.js");
4957
4382
  const sleepState = getSleepManagerIfReady2()?.getState(name);
4958
4383
  if (sleepState?.sleeping) {
4959
4384
  logger_default.info(`skipping reload for ${name} during sleep \u2014 will apply on next wake`);
@@ -4973,7 +4398,7 @@ ${user.trimEnd()}
4973
4398
  const variantEntry = await findMind(mergeVariantName);
4974
4399
  if (variantEntry && variantEntry.parent === baseName && variantEntry.dir && variantEntry.branch) {
4975
4400
  const projectRoot = mindDir(baseName);
4976
- if (existsSync8(variantEntry.dir)) {
4401
+ if (existsSync7(variantEntry.dir)) {
4977
4402
  const status = (await gitExec(["status", "--porcelain"], { cwd: variantEntry.dir })).trim();
4978
4403
  if (status) {
4979
4404
  try {
@@ -5014,10 +4439,9 @@ ${user.trimEnd()}
5014
4439
  }
5015
4440
  if (context?.type === "sprouted" && !entry.parent) {
5016
4441
  try {
5017
- const db = await getDb();
5018
- const activeConvs = await db.select({ id: conversations.id, channel: conversations.channel }).from(conversations).where(eq5(conversations.mind_name, baseName)).all();
5019
- for (const conv of activeConvs) {
5020
- await recordInbound(baseName, conv.channel, "system", "[seed has sprouted]");
4442
+ const mindConvs = await listConversationsForMind(baseName);
4443
+ for (const conv of mindConvs) {
4444
+ await recordInbound(baseName, "system", "system", "[seed has sprouted]");
5021
4445
  await addMessage(conv.id, "assistant", "system", [
5022
4446
  { type: "text", text: "[seed has sprouted]" }
5023
4447
  ]);
@@ -5049,7 +4473,7 @@ ${user.trimEnd()}
5049
4473
  const name = c.req.param("name");
5050
4474
  const entry = await findMind(name);
5051
4475
  if (!entry) return c.json({ error: "Mind not found" }, 404);
5052
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-7KFK3USC.js");
4476
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-TQP5ZJI5.js");
5053
4477
  const sm = getSleepManagerIfReady2();
5054
4478
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
5055
4479
  return c.json(sm.getState(name));
@@ -5057,7 +4481,7 @@ ${user.trimEnd()}
5057
4481
  const name = c.req.param("name");
5058
4482
  const entry = await findMind(name);
5059
4483
  if (!entry) return c.json({ error: "Mind not found" }, 404);
5060
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-7KFK3USC.js");
4484
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-TQP5ZJI5.js");
5061
4485
  const sm = getSleepManagerIfReady2();
5062
4486
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
5063
4487
  if (sm.isSleeping(name)) return c.json({ error: "Mind is already sleeping" }, 409);
@@ -5077,7 +4501,7 @@ ${user.trimEnd()}
5077
4501
  const name = c.req.param("name");
5078
4502
  const entry = await findMind(name);
5079
4503
  if (!entry) return c.json({ error: "Mind not found" }, 404);
5080
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-7KFK3USC.js");
4504
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-TQP5ZJI5.js");
5081
4505
  const sm = getSleepManagerIfReady2();
5082
4506
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
5083
4507
  const sleepState = sm.getState(name);
@@ -5092,7 +4516,7 @@ ${user.trimEnd()}
5092
4516
  const name = c.req.param("name");
5093
4517
  const entry = await findMind(name);
5094
4518
  if (!entry) return c.json({ error: "Mind not found" }, 404);
5095
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-7KFK3USC.js");
4519
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-TQP5ZJI5.js");
5096
4520
  const sm = getSleepManagerIfReady2();
5097
4521
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
5098
4522
  const flushed = await sm.flushQueuedMessages(name);
@@ -5110,7 +4534,7 @@ ${user.trimEnd()}
5110
4534
  if (body.avatar !== void 0) profile.avatar = body.avatar;
5111
4535
  config2.profile = profile;
5112
4536
  writeVoluteConfig(dir, config2);
5113
- const { syncMindProfile: syncMindProfile2 } = await import("./auth-WX4TESEI.js");
4537
+ const { syncMindProfile: syncMindProfile2 } = await import("./auth-2QOOPMBX.js");
5114
4538
  await syncMindProfile2(name, profile);
5115
4539
  broadcast({ type: "profile_updated", mind: name, summary: `${name} profile updated` });
5116
4540
  return c.json({ ok: true });
@@ -5150,14 +4574,14 @@ ${user.trimEnd()}
5150
4574
  return c.json({ output: "" });
5151
4575
  }
5152
4576
  const dir = entry.dir ?? mindDir(name);
5153
- const soulPath = resolve11(dir, "home/SOUL.md");
5154
- const memoryPath = resolve11(dir, "home/MEMORY.md");
5155
- const soulCustom = existsSync8(soulPath) && !readFileSync9(soulPath, "utf-8").includes(ORIENTATION_MARKER);
5156
- const memoryWritten = existsSync8(memoryPath) && readFileSync9(memoryPath, "utf-8").trim().length > 0;
4577
+ const soulPath = resolve10(dir, "home/SOUL.md");
4578
+ const memoryPath = resolve10(dir, "home/MEMORY.md");
4579
+ const soulCustom = existsSync7(soulPath) && !readFileSync8(soulPath, "utf-8").includes(ORIENTATION_MARKER);
4580
+ const memoryWritten = existsSync7(memoryPath) && readFileSync8(memoryPath, "utf-8").trim().length > 0;
5157
4581
  const config2 = readVoluteConfig(dir);
5158
4582
  const displayNameSet = !!config2?.profile?.displayName;
5159
4583
  const avatarSet = !!config2?.profile?.avatar;
5160
- const { isImagegenEnabled } = await import("./setup-RHJRFURI.js");
4584
+ const { isImagegenEnabled } = await import("./setup-PF7JSFMO.js");
5161
4585
  const imagegenEnabled = isImagegenEnabled();
5162
4586
  const done = [];
5163
4587
  const remaining = [];
@@ -5195,7 +4619,7 @@ ${user.trimEnd()}
5195
4619
  try {
5196
4620
  const spiritEntry = await findMind("volute");
5197
4621
  if (spiritEntry) {
5198
- const { spiritDir } = await import("./spirit-ZFRDXMG7.js");
4622
+ const { spiritDir } = await import("./spirit-SM6ARJ2N.js");
5199
4623
  const sDir = spiritEntry.dir ?? spiritDir();
5200
4624
  const spiritConfig = readVoluteConfig(sDir);
5201
4625
  if (spiritConfig?.schedules) {
@@ -5203,7 +4627,7 @@ ${user.trimEnd()}
5203
4627
  spiritConfig.schedules = spiritConfig.schedules.filter((s) => s.id !== nurtureId);
5204
4628
  if (spiritConfig.schedules.length === 0) spiritConfig.schedules = void 0;
5205
4629
  writeVoluteConfig(sDir, spiritConfig);
5206
- const { getScheduler: getScheduler2 } = await import("./scheduler-Y7O4CJXL.js");
4630
+ const { getScheduler: getScheduler2 } = await import("./scheduler-NTC74JYH.js");
5207
4631
  getScheduler2().loadSchedules("volute", sDir);
5208
4632
  }
5209
4633
  }
@@ -5229,11 +4653,12 @@ ${user.trimEnd()}
5229
4653
  }
5230
4654
  await removeMind(name);
5231
4655
  await deleteMindUser2(name);
4656
+ invalidateMindUserCache(name);
5232
4657
  const state = stateDir(name);
5233
- if (existsSync8(state)) {
4658
+ if (existsSync7(state)) {
5234
4659
  rmSync4(state, { recursive: true, force: true });
5235
4660
  }
5236
- if (force && existsSync8(dir)) {
4661
+ if (force && existsSync7(dir)) {
5237
4662
  rmSync4(dir, { recursive: true, force: true });
5238
4663
  deleteMindUser(name);
5239
4664
  }
@@ -5248,7 +4673,7 @@ ${user.trimEnd()}
5248
4673
  const entry = await findMind(mindName);
5249
4674
  if (!entry) return c.json({ error: "Mind not found" }, 404);
5250
4675
  const dir = mindDir(mindName);
5251
- if (!existsSync8(dir)) return c.json({ error: "Mind directory missing" }, 404);
4676
+ if (!existsSync7(dir)) return c.json({ error: "Mind directory missing" }, 404);
5252
4677
  let body = {};
5253
4678
  try {
5254
4679
  body = await c.req.json();
@@ -5257,16 +4682,16 @@ ${user.trimEnd()}
5257
4682
  const template = body.template ?? entry.template ?? "claude";
5258
4683
  const UPGRADE_BRANCH = "upgrade";
5259
4684
  const upgradeVariantName = `${mindName}-upgrade`;
5260
- const worktreeDir = resolve11(dir, ".variants", UPGRADE_BRANCH);
4685
+ const worktreeDir = resolve10(dir, ".variants", UPGRADE_BRANCH);
5261
4686
  if (body.abort) {
5262
- if (!existsSync8(worktreeDir)) {
4687
+ if (!existsSync7(worktreeDir)) {
5263
4688
  return c.json({ error: "No upgrade in progress" }, 400);
5264
4689
  }
5265
4690
  try {
5266
4691
  try {
5267
- const gitDirContent = readFileSync9(resolve11(worktreeDir, ".git"), "utf-8").trim();
4692
+ const gitDirContent = readFileSync8(resolve10(worktreeDir, ".git"), "utf-8").trim();
5268
4693
  const gitDir = gitDirContent.replace("gitdir: ", "");
5269
- if (existsSync8(resolve11(gitDir, "MERGE_HEAD"))) {
4694
+ if (existsSync7(resolve10(gitDir, "MERGE_HEAD"))) {
5270
4695
  await gitExec(["merge", "--abort"], { cwd: worktreeDir });
5271
4696
  }
5272
4697
  } catch {
@@ -5285,7 +4710,7 @@ ${user.trimEnd()}
5285
4710
  }
5286
4711
  }
5287
4712
  if (body.continue) {
5288
- if (!existsSync8(worktreeDir)) {
4713
+ if (!existsSync7(worktreeDir)) {
5289
4714
  return c.json({ error: "No upgrade in progress" }, 400);
5290
4715
  }
5291
4716
  const status = await gitExec(["status", "--porcelain"], { cwd: worktreeDir });
@@ -5334,7 +4759,7 @@ ${user.trimEnd()}
5334
4759
  }
5335
4760
  }
5336
4761
  if (body.accept) {
5337
- if (existsSync8(worktreeDir)) {
4762
+ if (existsSync7(worktreeDir)) {
5338
4763
  try {
5339
4764
  await cleanupVariant(upgradeVariantName, dir, worktreeDir, { stop: true });
5340
4765
  } catch (err) {
@@ -5349,7 +4774,7 @@ ${user.trimEnd()}
5349
4774
  }
5350
4775
  if (body.diff) {
5351
4776
  try {
5352
- if (!existsSync8(resolve11(dir, ".git"))) {
4777
+ if (!existsSync7(resolve10(dir, ".git"))) {
5353
4778
  return c.json({ error: "Mind has no git history \u2014 nothing to diff against" }, 400);
5354
4779
  }
5355
4780
  await updateTemplateBranch(dir, template, mindName);
@@ -5367,22 +4792,22 @@ ${user.trimEnd()}
5367
4792
  );
5368
4793
  }
5369
4794
  }
5370
- if (existsSync8(worktreeDir)) {
4795
+ if (existsSync7(worktreeDir)) {
5371
4796
  return c.json(
5372
4797
  { error: "Upgrade variant already exists. Use continue or delete it first." },
5373
4798
  409
5374
4799
  );
5375
4800
  }
5376
- if (!existsSync8(resolve11(dir, ".git"))) {
4801
+ if (!existsSync7(resolve10(dir, ".git"))) {
5377
4802
  try {
5378
- const env = isIsolationEnabled() ? { ...process.env, HOME: resolve11(dir, "home") } : void 0;
4803
+ const env = isIsolationEnabled() ? { ...process.env, HOME: resolve10(dir, "home") } : void 0;
5379
4804
  await gitExec(["init"], { cwd: dir, mindName, env });
5380
4805
  await configureGitIdentity(mindName, { cwd: dir, mindName, env });
5381
4806
  await gitExec(["add", "-A"], { cwd: dir, mindName, env });
5382
4807
  await gitExec(["commit", "-m", "initial commit"], { cwd: dir, mindName, env });
5383
4808
  chownMindDir(dir, mindName);
5384
4809
  } catch (err) {
5385
- rmSync4(resolve11(dir, ".git"), { recursive: true, force: true });
4810
+ rmSync4(resolve10(dir, ".git"), { recursive: true, force: true });
5386
4811
  return c.json(
5387
4812
  {
5388
4813
  error: `Git initialization failed: ${err instanceof Error ? err.message : String(err)}`
@@ -5397,8 +4822,8 @@ ${user.trimEnd()}
5397
4822
  } catch {
5398
4823
  }
5399
4824
  await updateTemplateBranch(dir, template, mindName);
5400
- const parentDir = resolve11(dir, ".variants");
5401
- if (!existsSync8(parentDir)) {
4825
+ const parentDir = resolve10(dir, ".variants");
4826
+ if (!existsSync7(parentDir)) {
5402
4827
  mkdirSync6(parentDir, { recursive: true });
5403
4828
  }
5404
4829
  await gitExec(["worktree", "add", "-b", UPGRADE_BRANCH, worktreeDir], { cwd: dir });
@@ -5527,22 +4952,22 @@ ${user.trimEnd()}
5527
4952
  const entry = await findMind(name);
5528
4953
  if (!entry) return c.json({ error: "Mind not found" }, 404);
5529
4954
  const dir = entry.dir ?? mindDir(name);
5530
- if (!existsSync8(dir)) return c.json({ error: "Mind directory missing" }, 404);
4955
+ if (!existsSync7(dir)) return c.json({ error: "Mind directory missing" }, 404);
5531
4956
  let config2 = readVoluteConfig(dir);
5532
4957
  if (!config2 && entry.template === "pi") {
5533
- const piConfigPath = resolve11(dir, "home/.config/config.json");
5534
- if (existsSync8(piConfigPath)) {
4958
+ const piConfigPath = resolve10(dir, "home/.config/config.json");
4959
+ if (existsSync7(piConfigPath)) {
5535
4960
  try {
5536
- config2 = JSON.parse(readFileSync9(piConfigPath, "utf-8"));
4961
+ config2 = JSON.parse(readFileSync8(piConfigPath, "utf-8"));
5537
4962
  } catch {
5538
4963
  }
5539
4964
  }
5540
4965
  }
5541
4966
  let templateConfig = {};
5542
- const configJsonPath = resolve11(dir, "home/.config/config.json");
5543
- if (existsSync8(configJsonPath)) {
4967
+ const configJsonPath = resolve10(dir, "home/.config/config.json");
4968
+ if (existsSync7(configJsonPath)) {
5544
4969
  try {
5545
- templateConfig = JSON.parse(readFileSync9(configJsonPath, "utf-8"));
4970
+ templateConfig = JSON.parse(readFileSync8(configJsonPath, "utf-8"));
5546
4971
  } catch {
5547
4972
  }
5548
4973
  }
@@ -5596,7 +5021,7 @@ ${user.trimEnd()}
5596
5021
  const entry = await findMind(name);
5597
5022
  if (!entry) return c.json({ error: "Mind not found" }, 404);
5598
5023
  const dir = entry.dir ?? mindDir(name);
5599
- if (!existsSync8(dir)) return c.json({ error: "Mind directory missing" }, 404);
5024
+ if (!existsSync7(dir)) return c.json({ error: "Mind directory missing" }, 404);
5600
5025
  const body = c.req.valid("json");
5601
5026
  const existing = readVoluteConfig(dir) ?? {};
5602
5027
  if (body.model !== void 0) existing.model = body.model;
@@ -5627,11 +5052,11 @@ ${user.trimEnd()}
5627
5052
  writeVoluteConfig(dir, existing);
5628
5053
  const needsConfigJson = body.model !== void 0 || body.thinkingLevel !== void 0 || body.maxThinkingTokens !== void 0 || body.compaction !== void 0;
5629
5054
  if (needsConfigJson) {
5630
- const configJsonPath = resolve11(dir, "home/.config/config.json");
5055
+ const configJsonPath = resolve10(dir, "home/.config/config.json");
5631
5056
  let templateConfig = {};
5632
- if (existsSync8(configJsonPath)) {
5057
+ if (existsSync7(configJsonPath)) {
5633
5058
  try {
5634
- templateConfig = JSON.parse(readFileSync9(configJsonPath, "utf-8"));
5059
+ templateConfig = JSON.parse(readFileSync8(configJsonPath, "utf-8"));
5635
5060
  } catch {
5636
5061
  }
5637
5062
  }
@@ -5699,7 +5124,7 @@ ${user.trimEnd()}
5699
5124
  }
5700
5125
  if (entry.mindType === "spirit" && body.model !== void 0) {
5701
5126
  try {
5702
- const { readGlobalConfig: readGlobalConfig2, writeGlobalConfig: writeGlobalConfig2 } = await import("./setup-RHJRFURI.js");
5127
+ const { readGlobalConfig: readGlobalConfig2, writeGlobalConfig: writeGlobalConfig2 } = await import("./setup-PF7JSFMO.js");
5703
5128
  const globalConfig = readGlobalConfig2();
5704
5129
  globalConfig.spiritModel = body.model;
5705
5130
  writeGlobalConfig2(globalConfig);
@@ -5727,7 +5152,7 @@ ${user.trimEnd()}
5727
5152
  if (!body.systemPrompt || !body.message) {
5728
5153
  return c.json({ error: "systemPrompt and message required" }, 400);
5729
5154
  }
5730
- const { aiComplete: aiCompleteFn, isAiConfigured } = await import("./ai-service-LURBEDDB.js");
5155
+ const { aiComplete: aiCompleteFn, isAiConfigured } = await import("./ai-service-C2YNARGH.js");
5731
5156
  if (!isAiConfigured()) {
5732
5157
  return c.json({ error: "AI service not configured" }, 503);
5733
5158
  }
@@ -5817,6 +5242,17 @@ ${user.trimEnd()}
5817
5242
  metadata: body.metadata,
5818
5243
  turnId: turnId ?? void 0
5819
5244
  });
5245
+ if (body.type === "text" && body.channel && cleanContent) {
5246
+ echoTextToChannel(
5247
+ baseName,
5248
+ body.channel,
5249
+ cleanContent,
5250
+ turnId ?? void 0,
5251
+ insertedId
5252
+ ).catch(
5253
+ (err) => logger_default.error(`echo-text failed for ${baseName} on ${body.channel}`, logger_default.errorData(err))
5254
+ );
5255
+ }
5820
5256
  onMindEvent(baseName, body.type, body.channel);
5821
5257
  if ((body.type === "text" || body.type === "outbound") && body.channel) {
5822
5258
  const map = getTypingMap();
@@ -5885,7 +5321,7 @@ ${user.trimEnd()}
5885
5321
  const stream = new ReadableStream({
5886
5322
  start(controller) {
5887
5323
  const encoder = new TextEncoder();
5888
- const send5 = (data) => {
5324
+ const send = (data) => {
5889
5325
  controller.enqueue(encoder.encode(`data: ${data}
5890
5326
 
5891
5327
  `));
@@ -5904,7 +5340,7 @@ ${user.trimEnd()}
5904
5340
  if (sessionFilter && event.session !== sessionFilter) return;
5905
5341
  if (channelFilter && event.channel !== channelFilter) return;
5906
5342
  try {
5907
- send5(JSON.stringify(event));
5343
+ send(JSON.stringify(event));
5908
5344
  } catch {
5909
5345
  clearInterval(pingInterval);
5910
5346
  unsubscribe?.();
@@ -6187,15 +5623,14 @@ var prompts_default = app14;
6187
5623
  import { CronExpressionParser } from "cron-parser";
6188
5624
  import { Hono as Hono17 } from "hono";
6189
5625
  var slog2 = logger_default.child("schedules");
6190
- function readSchedules(name) {
6191
- return readVoluteConfig(mindDir(name))?.schedules ?? [];
5626
+ function readSchedules(dir) {
5627
+ return readVoluteConfig(dir)?.schedules ?? [];
6192
5628
  }
6193
- function writeSchedules(name, schedules) {
6194
- const dir = mindDir(name);
5629
+ function writeSchedules(name, dir, schedules) {
6195
5630
  const config2 = readVoluteConfig(dir) ?? {};
6196
5631
  config2.schedules = schedules.length > 0 ? schedules : void 0;
6197
5632
  writeVoluteConfig(dir, config2);
6198
- getScheduler().loadSchedules(name);
5633
+ getScheduler().loadSchedules(name, dir);
6199
5634
  getSleepManagerIfReady()?.invalidateSleepConfig(name);
6200
5635
  fireWebhook({
6201
5636
  event: "schedule_changed",
@@ -6205,11 +5640,13 @@ function writeSchedules(name, schedules) {
6205
5640
  }
6206
5641
  var app15 = new Hono17().get("/:name/clock/status", async (c) => {
6207
5642
  const name = c.req.param("name");
6208
- if (!await findMind(name)) return c.json({ error: "Mind not found" }, 404);
5643
+ const entry = await findMind(name);
5644
+ if (!entry) return c.json({ error: "Mind not found" }, 404);
5645
+ const dir = entry.dir ?? mindDir(name);
6209
5646
  const sleepManager = getSleepManagerIfReady();
6210
5647
  const sleepState = sleepManager?.getState(name) ?? null;
6211
5648
  const sleepConfig = sleepManager?.getSleepConfig(name) ?? null;
6212
- const schedules = readSchedules(name);
5649
+ const schedules = readSchedules(dir);
6213
5650
  const now = /* @__PURE__ */ new Date();
6214
5651
  const upcoming = [];
6215
5652
  const previous = [];
@@ -6262,8 +5699,9 @@ var app15 = new Hono17().get("/:name/clock/status", async (c) => {
6262
5699
  return c.json({ sleep: sleepState, sleepConfig, schedules, upcoming, previous });
6263
5700
  }).get("/:name/sleep/config", async (c) => {
6264
5701
  const name = c.req.param("name");
6265
- if (!await findMind(name)) return c.json({ error: "Mind not found" }, 404);
6266
- const config2 = readVoluteConfig(mindDir(name));
5702
+ const entry = await findMind(name);
5703
+ if (!entry) return c.json({ error: "Mind not found" }, 404);
5704
+ const config2 = readVoluteConfig(entry.dir ?? mindDir(name));
6267
5705
  return c.json(config2?.sleep ?? { enabled: false });
6268
5706
  }).put("/:name/sleep/config", requireSelf(), async (c) => {
6269
5707
  const name = c.req.param("name");
@@ -6281,7 +5719,7 @@ var app15 = new Hono17().get("/:name/clock/status", async (c) => {
6281
5719
  }
6282
5720
  }
6283
5721
  }
6284
- const dir = mindDir(name);
5722
+ const dir = entry.dir ?? mindDir(name);
6285
5723
  const config2 = readVoluteConfig(dir) ?? {};
6286
5724
  const sleep = config2.sleep ?? {};
6287
5725
  if (body.enabled !== void 0) sleep.enabled = body.enabled;
@@ -6293,8 +5731,9 @@ var app15 = new Hono17().get("/:name/clock/status", async (c) => {
6293
5731
  return c.json({ ok: true });
6294
5732
  }).get("/:name/schedules", async (c) => {
6295
5733
  const name = c.req.param("name");
6296
- if (!await findMind(name)) return c.json({ error: "Mind not found" }, 404);
6297
- return c.json(readSchedules(name));
5734
+ const entry = await findMind(name);
5735
+ if (!entry) return c.json({ error: "Mind not found" }, 404);
5736
+ return c.json(readSchedules(entry.dir ?? mindDir(name)));
6298
5737
  }).post("/:name/schedules", requireSelf(), async (c) => {
6299
5738
  const name = c.req.param("name");
6300
5739
  const entry = await findMind(name);
@@ -6335,7 +5774,8 @@ var app15 = new Hono17().get("/:name/clock/status", async (c) => {
6335
5774
  400
6336
5775
  );
6337
5776
  }
6338
- const schedules = readSchedules(name);
5777
+ const dir = entry.dir ?? mindDir(name);
5778
+ const schedules = readSchedules(dir);
6339
5779
  const id = body.id;
6340
5780
  if (schedules.some((s) => s.id === id)) {
6341
5781
  return c.json({ error: `Schedule "${id}" already exists` }, 409);
@@ -6348,13 +5788,15 @@ var app15 = new Hono17().get("/:name/clock/status", async (c) => {
6348
5788
  if (body.channel) schedule.channel = body.channel;
6349
5789
  if (body.whileSleeping) schedule.whileSleeping = body.whileSleeping;
6350
5790
  schedules.push(schedule);
6351
- writeSchedules(name, schedules);
5791
+ writeSchedules(name, dir, schedules);
6352
5792
  return c.json({ ok: true, id }, 201);
6353
5793
  }).put("/:name/schedules/:id", requireSelf(), async (c) => {
6354
5794
  const name = c.req.param("name");
6355
5795
  const id = c.req.param("id");
6356
- if (!await findMind(name)) return c.json({ error: "Mind not found" }, 404);
6357
- const schedules = readSchedules(name);
5796
+ const entry = await findMind(name);
5797
+ if (!entry) return c.json({ error: "Mind not found" }, 404);
5798
+ const dir = entry.dir ?? mindDir(name);
5799
+ const schedules = readSchedules(dir);
6358
5800
  const idx = schedules.findIndex((s) => s.id === id);
6359
5801
  if (idx === -1) return c.json({ error: "Schedule not found" }, 404);
6360
5802
  const body = await c.req.json();
@@ -6397,18 +5839,20 @@ var app15 = new Hono17().get("/:name/clock/status", async (c) => {
6397
5839
  if (body.channel !== void 0) schedules[idx].channel = body.channel || void 0;
6398
5840
  if (body.whileSleeping !== void 0)
6399
5841
  schedules[idx].whileSleeping = body.whileSleeping || void 0;
6400
- writeSchedules(name, schedules);
5842
+ writeSchedules(name, dir, schedules);
6401
5843
  return c.json({ ok: true });
6402
5844
  }).delete("/:name/schedules/:id", requireSelf(), async (c) => {
6403
5845
  const name = c.req.param("name");
6404
5846
  const id = c.req.param("id");
6405
- if (!await findMind(name)) return c.json({ error: "Mind not found" }, 404);
6406
- const schedules = readSchedules(name);
5847
+ const entry = await findMind(name);
5848
+ if (!entry) return c.json({ error: "Mind not found" }, 404);
5849
+ const dir = entry.dir ?? mindDir(name);
5850
+ const schedules = readSchedules(dir);
6407
5851
  const filtered = schedules.filter((s) => s.id !== id);
6408
5852
  if (filtered.length === schedules.length) {
6409
5853
  return c.json({ error: "Schedule not found" }, 404);
6410
5854
  }
6411
- writeSchedules(name, filtered);
5855
+ writeSchedules(name, dir, filtered);
6412
5856
  return c.json({ ok: true });
6413
5857
  }).post("/:name/webhook/:event", async (c) => {
6414
5858
  const name = c.req.param("name");
@@ -6418,7 +5862,7 @@ var app15 = new Hono17().get("/:name/clock/status", async (c) => {
6418
5862
  const body = await c.req.text();
6419
5863
  const message = `[webhook: ${event}] ${body}`;
6420
5864
  try {
6421
- const { sendSystemMessage } = await import("./system-chat-IDPHYHY4.js");
5865
+ const { sendSystemMessage } = await import("./system-chat-NNXYCSVL.js");
6422
5866
  await sendSystemMessage(name, message);
6423
5867
  return c.json({ ok: true });
6424
5868
  } catch (err) {
@@ -6431,14 +5875,14 @@ var schedules_default = app15;
6431
5875
  // packages/daemon/src/web/api/setup.ts
6432
5876
  import { mkdirSync as mkdirSync7 } from "fs";
6433
5877
  import { homedir as homedir2 } from "os";
6434
- import { resolve as resolve12 } from "path";
5878
+ import { resolve as resolve11 } from "path";
6435
5879
  import { Hono as Hono18 } from "hono";
6436
5880
  import { setCookie as setCookie2 } from "hono/cookie";
6437
5881
  var DEFAULT_API_URL2 = "https://volute.systems";
6438
5882
  var setup = new Hono18();
6439
5883
  function writeSetupConfig(systemName, description) {
6440
- const configHome = process.env.VOLUTE_HOME ?? resolve12(homedir2(), ".volute");
6441
- const mindsDir = resolve12(configHome, "minds");
5884
+ const configHome = process.env.VOLUTE_HOME ?? resolve11(homedir2(), ".volute");
5885
+ const mindsDir = resolve11(configHome, "minds");
6442
5886
  mkdirSync7(configHome, { recursive: true });
6443
5887
  mkdirSync7(mindsDir, { recursive: true });
6444
5888
  const existingConfig = readGlobalConfig();
@@ -6452,7 +5896,8 @@ function writeSetupConfig(systemName, description) {
6452
5896
  ...existingConfig,
6453
5897
  name: systemName,
6454
5898
  description: description || existingConfig.description,
6455
- setup: setupConfig
5899
+ setup: setupConfig,
5900
+ setupCompleted: false
6456
5901
  };
6457
5902
  writeGlobalConfig(config2);
6458
5903
  return config2;
@@ -6471,7 +5916,7 @@ setup.get("/status", async (c) => {
6471
5916
  let hasAccount = false;
6472
5917
  if (hasSystem) {
6473
5918
  try {
6474
- const { listUsersByType: listUsersByType2 } = await import("./auth-WX4TESEI.js");
5919
+ const { listUsersByType: listUsersByType2 } = await import("./auth-2QOOPMBX.js");
6475
5920
  const brains = await listUsersByType2("brain");
6476
5921
  hasAccount = brains.length > 0;
6477
5922
  } catch (err) {
@@ -6660,7 +6105,7 @@ setup.post("/account", async (c) => {
6660
6105
  }
6661
6106
  }
6662
6107
  try {
6663
- const { createUser: createUser2, updateUserProfile: updateUserProfile2 } = await import("./auth-WX4TESEI.js");
6108
+ const { createUser: createUser2, updateUserProfile: updateUserProfile2 } = await import("./auth-2QOOPMBX.js");
6664
6109
  const user = await createUser2(body.username.trim(), body.password);
6665
6110
  if (body.displayName?.trim()) {
6666
6111
  await updateUserProfile2(user.id, { display_name: body.displayName.trim() });
@@ -6706,7 +6151,7 @@ setup.post("/models", async (c) => {
6706
6151
  return c.json({ error: "Spirit model is required" }, 400);
6707
6152
  }
6708
6153
  try {
6709
- const { setEnabledModels: setEnabledModels3, setUtilityModel: setUtilityModel2 } = await import("./ai-service-LURBEDDB.js");
6154
+ const { setEnabledModels: setEnabledModels3, setUtilityModel: setUtilityModel2 } = await import("./ai-service-C2YNARGH.js");
6710
6155
  setEnabledModels3(body.models);
6711
6156
  const config2 = readGlobalConfig();
6712
6157
  config2.spiritModel = body.spiritModel.trim();
@@ -6724,8 +6169,8 @@ setup.post("/complete", async (c) => {
6724
6169
  return c.json({ error: "Setup already complete" }, 400);
6725
6170
  }
6726
6171
  try {
6727
- const { ensureSpiritProject, syncSpiritTemplate } = await import("./spirit-ZFRDXMG7.js");
6728
- const { startSpiritFull } = await import("./mind-service-X2CAA6W6.js");
6172
+ const { ensureSpiritProject, syncSpiritTemplate } = await import("./spirit-SM6ARJ2N.js");
6173
+ const { startSpiritFull } = await import("./mind-service-UDXF5WC2.js");
6729
6174
  await ensureSpiritProject();
6730
6175
  await syncSpiritTemplate();
6731
6176
  const warnings = [];
@@ -6741,19 +6186,18 @@ setup.post("/complete", async (c) => {
6741
6186
  }
6742
6187
  let spiritConversationId;
6743
6188
  try {
6744
- const { getOrCreateMindUser: getOrCreateMindUser2, listUsersByType: listUsersByType2 } = await import("./auth-WX4TESEI.js");
6745
- const { createConversation: createConversation6, findDMConversation: findDMConversation2 } = await import("./conversations-2PW57WO2.js");
6189
+ const { getOrCreateMindUser: getOrCreateMindUser2, listUsersByType: listUsersByType2 } = await import("./auth-2QOOPMBX.js");
6190
+ const { createConversation: createConversation2, findDMConversation: findDMConversation2 } = await import("./conversations-G6YRSABR.js");
6746
6191
  const spiritUser = await getOrCreateMindUser2("volute");
6747
6192
  const brains = await listUsersByType2("brain");
6748
6193
  const admin2 = brains.find((u) => u.role === "admin");
6749
6194
  if (admin2) {
6750
- const existing = await findDMConversation2("volute", [admin2.id, spiritUser.id]);
6195
+ const existing = await findDMConversation2([admin2.id, spiritUser.id]);
6751
6196
  if (existing) {
6752
6197
  spiritConversationId = existing;
6753
6198
  } else {
6754
- const conv = await createConversation6("volute", "volute", {
6755
- participantIds: [admin2.id, spiritUser.id],
6756
- title: "Volute"
6199
+ const conv = await createConversation2({
6200
+ participantIds: [admin2.id, spiritUser.id]
6757
6201
  });
6758
6202
  spiritConversationId = conv.id;
6759
6203
  }
@@ -6765,9 +6209,9 @@ setup.post("/complete", async (c) => {
6765
6209
  logger_default.info("setup complete state", { spiritConversationId, spiritStarted });
6766
6210
  if (spiritConversationId && spiritStarted) {
6767
6211
  try {
6768
- const { deliverMessage: deliverMessage2 } = await import("./message-delivery-GRC4W6P7.js");
6769
- const { listUsersByType: listUsers6 } = await import("./auth-WX4TESEI.js");
6770
- const admins = await listUsers6("brain");
6212
+ const { deliverMessage: deliverMessage2 } = await import("./message-delivery-N2V5APCS.js");
6213
+ const { listUsersByType: listUsers2 } = await import("./auth-2QOOPMBX.js");
6214
+ const admins = await listUsers2("brain");
6771
6215
  const admin2 = admins.find((u) => u.role === "admin");
6772
6216
  const adminName = admin2?.display_name || admin2?.username || "the admin";
6773
6217
  const welcomePrompt = `Setup is complete. ${adminName} just finished setting up this system and is now in your chat. Welcome them and help them create their first mind. Keep it brief \u2014 a short greeting, then ask what kind of being they'd like to bring into the world. Remember: minds are beings with their own experience, not tools \u2014 ask about personality and character, never about tasks or utility.`;
@@ -6803,9 +6247,9 @@ setup.post("/complete", async (c) => {
6803
6247
  var setup_default = setup;
6804
6248
 
6805
6249
  // packages/daemon/src/web/api/skills.ts
6806
- import { existsSync as existsSync9, mkdtempSync, readdirSync as readdirSync3, rmSync as rmSync5 } from "fs";
6250
+ import { existsSync as existsSync8, mkdtempSync, readdirSync as readdirSync3, rmSync as rmSync5 } from "fs";
6807
6251
  import { tmpdir } from "os";
6808
- import { join, resolve as resolve13 } from "path";
6252
+ import { join, resolve as resolve12 } from "path";
6809
6253
  import AdmZip from "adm-zip";
6810
6254
  import { Hono as Hono19 } from "hono";
6811
6255
  var app16 = new Hono19().get("/", async (c) => {
@@ -6881,19 +6325,19 @@ var app16 = new Hono19().get("/", async (c) => {
6881
6325
  try {
6882
6326
  const zip = new AdmZip(buffer2);
6883
6327
  for (const entry of zip.getEntries()) {
6884
- const target = resolve13(tmpDir, entry.entryName);
6328
+ const target = resolve12(tmpDir, entry.entryName);
6885
6329
  if (!target.startsWith(tmpDir)) {
6886
6330
  return c.json({ error: "Invalid zip: paths must not escape archive" }, 400);
6887
6331
  }
6888
6332
  }
6889
6333
  zip.extractAllTo(tmpDir, true);
6890
6334
  let skillDir = null;
6891
- if (existsSync9(join(tmpDir, "SKILL.md"))) {
6335
+ if (existsSync8(join(tmpDir, "SKILL.md"))) {
6892
6336
  skillDir = tmpDir;
6893
6337
  } else {
6894
6338
  const entries = readdirSync3(tmpDir, { withFileTypes: true }).filter((e) => e.isDirectory());
6895
6339
  for (const entry of entries) {
6896
- if (existsSync9(join(tmpDir, entry.name, "SKILL.md"))) {
6340
+ if (existsSync8(join(tmpDir, entry.name, "SKILL.md"))) {
6897
6341
  skillDir = join(tmpDir, entry.name);
6898
6342
  break;
6899
6343
  }
@@ -6988,7 +6432,6 @@ import { zValidator as zValidator8 } from "@hono/zod-validator";
6988
6432
  import { Hono as Hono22 } from "hono";
6989
6433
  import { z as z8 } from "zod";
6990
6434
  var createSchema = z8.object({
6991
- title: z8.string().optional(),
6992
6435
  participantNames: z8.array(z8.string()).min(1)
6993
6436
  });
6994
6437
  var app19 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
@@ -7045,10 +6488,17 @@ var app19 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
7045
6488
  if (participantIds.size > 2) {
7046
6489
  return c.json({ error: "Use channels for multi-participant conversations" }, 400);
7047
6490
  }
7048
- const conv = await createConversation(firstMindName, "volute", {
6491
+ const ids = [...participantIds];
6492
+ if (ids.length === 2) {
6493
+ const existingId = await findDMConversation(ids);
6494
+ if (existingId) {
6495
+ const existing = await getConversation(existingId);
6496
+ if (existing) return c.json(existing);
6497
+ }
6498
+ }
6499
+ const conv = await createConversation({
7049
6500
  userId: user.id !== 0 ? user.id : void 0,
7050
- title: body.title,
7051
- participantIds: [...participantIds]
6501
+ participantIds: ids
7052
6502
  });
7053
6503
  return c.json(conv, 201);
7054
6504
  }).post("/:id/read", async (c) => {
@@ -7217,8 +6667,8 @@ var app20 = new Hono23().use("*", authMiddleware).get("/", async (c) => {
7217
6667
  });
7218
6668
  }, 15e3);
7219
6669
  cleanups.push(() => clearInterval(keepAlive));
7220
- await new Promise((resolve18) => {
7221
- stream.onAbort(() => resolve18());
6670
+ await new Promise((resolve17) => {
6671
+ stream.onAbort(() => resolve17());
7222
6672
  });
7223
6673
  } finally {
7224
6674
  for (const cleanup of cleanups) {
@@ -7233,16 +6683,16 @@ var app20 = new Hono23().use("*", authMiddleware).get("/", async (c) => {
7233
6683
  var events_default = app20;
7234
6684
 
7235
6685
  // packages/daemon/src/web/api/variants.ts
7236
- import { existsSync as existsSync10, mkdirSync as mkdirSync9, writeFileSync as writeFileSync8 } from "fs";
7237
- import { resolve as resolve15 } from "path";
6686
+ import { existsSync as existsSync9, mkdirSync as mkdirSync9, writeFileSync as writeFileSync8 } from "fs";
6687
+ import { resolve as resolve14 } from "path";
7238
6688
  import { Hono as Hono24 } from "hono";
7239
6689
 
7240
- // packages/daemon/src/lib/spawn-server.ts
6690
+ // packages/daemon/src/lib/mind/spawn-server.ts
7241
6691
  import { spawn as spawn4 } from "child_process";
7242
- import { closeSync, mkdirSync as mkdirSync8, openSync, readFileSync as readFileSync10 } from "fs";
7243
- import { resolve as resolve14 } from "path";
6692
+ import { closeSync, mkdirSync as mkdirSync8, openSync, readFileSync as readFileSync9 } from "fs";
6693
+ import { resolve as resolve13 } from "path";
7244
6694
  function tsxBin(cwd) {
7245
- return resolve14(cwd, "node_modules", ".bin", "tsx");
6695
+ return resolve13(cwd, "node_modules", ".bin", "tsx");
7246
6696
  }
7247
6697
  function spawnServer(cwd, port, options) {
7248
6698
  if (options?.detached) {
@@ -7255,31 +6705,31 @@ function spawnAttached(cwd, port) {
7255
6705
  cwd,
7256
6706
  stdio: ["ignore", "pipe", "pipe"]
7257
6707
  });
7258
- return new Promise((resolve18) => {
7259
- const timeout = setTimeout(() => resolve18(null), 3e4);
6708
+ return new Promise((resolve17) => {
6709
+ const timeout = setTimeout(() => resolve17(null), 3e4);
7260
6710
  function checkOutput(data) {
7261
6711
  const match = data.toString().match(/listening on :(\d+)/);
7262
6712
  if (match) {
7263
6713
  clearTimeout(timeout);
7264
- resolve18({ child, actualPort: parseInt(match[1], 10) });
6714
+ resolve17({ child, actualPort: parseInt(match[1], 10) });
7265
6715
  }
7266
6716
  }
7267
6717
  child.stdout?.on("data", checkOutput);
7268
6718
  child.stderr?.on("data", checkOutput);
7269
6719
  child.on("error", () => {
7270
6720
  clearTimeout(timeout);
7271
- resolve18(null);
6721
+ resolve17(null);
7272
6722
  });
7273
6723
  child.on("exit", () => {
7274
6724
  clearTimeout(timeout);
7275
- resolve18(null);
6725
+ resolve17(null);
7276
6726
  });
7277
6727
  });
7278
6728
  }
7279
6729
  function spawnDetached(cwd, port, logDir) {
7280
- const logsDir = logDir ?? resolve14(cwd, ".mind", "logs");
6730
+ const logsDir = logDir ?? resolve13(cwd, ".mind", "logs");
7281
6731
  mkdirSync8(logsDir, { recursive: true });
7282
- const logPath = resolve14(logsDir, "mind.log");
6732
+ const logPath = resolve13(logsDir, "mind.log");
7283
6733
  const logFd = openSync(logPath, "a");
7284
6734
  const child = spawn4(tsxBin(cwd), ["src/server.ts", "--port", String(port)], {
7285
6735
  cwd,
@@ -7299,7 +6749,7 @@ function spawnDetached(cwd, port, logDir) {
7299
6749
  }
7300
6750
  const interval = setInterval(() => {
7301
6751
  try {
7302
- const content = readFileSync10(logPath, "utf-8");
6752
+ const content = readFileSync9(logPath, "utf-8");
7303
6753
  const match = content.match(/listening on :(\d+)/);
7304
6754
  if (match) {
7305
6755
  finish({ child, actualPort: parseInt(match[1], 10) });
@@ -7313,7 +6763,7 @@ function spawnDetached(cwd, port, logDir) {
7313
6763
  });
7314
6764
  }
7315
6765
 
7316
- // packages/daemon/src/lib/verify.ts
6766
+ // packages/daemon/src/lib/mind/verify.ts
7317
6767
  async function verify2(port) {
7318
6768
  const health = await checkHealth(port);
7319
6769
  if (!health.ok) {
@@ -7395,11 +6845,11 @@ var app21 = new Hono24().get("/:name/variants", async (c) => {
7395
6845
  return c.json({ error: `Name already in use: ${variantName}` }, 409);
7396
6846
  }
7397
6847
  const projectRoot = mindDir(mindName);
7398
- const variantDir = resolve15(projectRoot, ".variants", variantName);
7399
- if (existsSync10(variantDir)) {
6848
+ const variantDir = resolve14(projectRoot, ".variants", variantName);
6849
+ if (existsSync9(variantDir)) {
7400
6850
  return c.json({ error: `Variant directory already exists: ${variantDir}` }, 409);
7401
6851
  }
7402
- mkdirSync9(resolve15(projectRoot, ".variants"), { recursive: true });
6852
+ mkdirSync9(resolve14(projectRoot, ".variants"), { recursive: true });
7403
6853
  try {
7404
6854
  await gitExec(["worktree", "add", "-b", variantName, variantDir], { cwd: projectRoot });
7405
6855
  } catch (e) {
@@ -7412,7 +6862,7 @@ var app21 = new Hono24().get("/:name/variants", async (c) => {
7412
6862
  const [cmd, args] = await wrapForIsolation("npm", ["install"], mindName);
7413
6863
  await exec(cmd, args, {
7414
6864
  cwd: variantDir,
7415
- env: { ...process.env, HOME: resolve15(variantDir, "home") }
6865
+ env: { ...process.env, HOME: resolve14(variantDir, "home") }
7416
6866
  });
7417
6867
  } else {
7418
6868
  await exec("npm", ["install"], { cwd: variantDir });
@@ -7422,7 +6872,7 @@ var app21 = new Hono24().get("/:name/variants", async (c) => {
7422
6872
  return c.json({ error: `npm install failed: ${msg}` }, 500);
7423
6873
  }
7424
6874
  if (body.soul) {
7425
- writeFileSync8(resolve15(variantDir, "home/SOUL.md"), body.soul);
6875
+ writeFileSync8(resolve14(variantDir, "home/SOUL.md"), body.soul);
7426
6876
  }
7427
6877
  const variantPort = body.port ?? await nextPort();
7428
6878
  await addVariant(variantName, mindName, variantPort, variantDir, variantName);
@@ -7457,7 +6907,7 @@ var app21 = new Hono24().get("/:name/variants", async (c) => {
7457
6907
  } catch {
7458
6908
  }
7459
6909
  const projectRoot = mindDir(mindName);
7460
- if (existsSync10(variantEntry.dir)) {
6910
+ if (existsSync9(variantEntry.dir)) {
7461
6911
  const status = (await gitExec(["status", "--porcelain"], { cwd: variantEntry.dir })).trim();
7462
6912
  if (status) {
7463
6913
  try {
@@ -7517,8 +6967,8 @@ var app21 = new Hono24().get("/:name/variants", async (c) => {
7517
6967
  await cleanupVariant(variantName, projectRoot, variantEntry.dir);
7518
6968
  if (variantName.endsWith("-upgrade") || variantName === "upgrade") {
7519
6969
  try {
7520
- const { computeTemplateHash: computeTemplateHash2 } = await import("./template-hash-A7FNHTB7.js");
7521
- const { setMindTemplateHash: setMindTemplateHash2 } = await import("./registry-GBSNW3HG.js");
6970
+ const { computeTemplateHash: computeTemplateHash2 } = await import("./template-hash-SSIBEEYK.js");
6971
+ const { setMindTemplateHash: setMindTemplateHash2 } = await import("./registry-KMELPC3X.js");
7522
6972
  const tmpl = parentEntry.template ?? "claude";
7523
6973
  await setMindTemplateHash2(mindName, computeTemplateHash2(tmpl));
7524
6974
  } catch (err) {
@@ -7531,7 +6981,7 @@ var app21 = new Hono24().get("/:name/variants", async (c) => {
7531
6981
  const [cmd, args] = await wrapForIsolation("npm", ["install"], mindName);
7532
6982
  await exec(cmd, args, {
7533
6983
  cwd: projectRoot,
7534
- env: { ...process.env, HOME: resolve15(projectRoot, "home") }
6984
+ env: { ...process.env, HOME: resolve14(projectRoot, "home") }
7535
6985
  });
7536
6986
  } else {
7537
6987
  await exec("npm", ["install"], { cwd: projectRoot });
@@ -7579,17 +7029,23 @@ var variants_default = app21;
7579
7029
  import { zValidator as zValidator9 } from "@hono/zod-validator";
7580
7030
  import { Hono as Hono25 } from "hono";
7581
7031
  import { z as z9 } from "zod";
7032
+ var channelSettingsSchema = z9.object({
7033
+ description: z9.string().nullable().optional(),
7034
+ rules: z9.string().nullable().optional(),
7035
+ charLimit: z9.number().int().positive().nullable().optional(),
7036
+ private: z9.boolean().optional()
7037
+ });
7582
7038
  var createSchema2 = z9.object({
7583
7039
  name: z9.string().min(1).max(50).regex(/^[a-z0-9][a-z0-9-]*$/, "Channel names must be lowercase alphanumeric with hyphens")
7584
- });
7040
+ }).merge(channelSettingsSchema);
7585
7041
  var inviteSchema = z9.object({
7586
7042
  username: z9.string().min(1)
7587
7043
  });
7588
7044
  var app22 = new Hono25().get("/", async (c) => {
7589
7045
  const user = c.get("user");
7590
- const channels = await listChannels();
7046
+ const channels2 = await listChannels();
7591
7047
  const results = await Promise.all(
7592
- channels.map(async (ch) => {
7048
+ channels2.map(async (ch) => {
7593
7049
  const participants = await getParticipants(ch.id);
7594
7050
  const isMember = participants.some((p) => p.userId === user.id);
7595
7051
  return { ...ch, participantCount: participants.length, isMember };
@@ -7600,8 +7056,9 @@ var app22 = new Hono25().get("/", async (c) => {
7600
7056
  const user = c.get("user");
7601
7057
  const body = c.req.valid("json");
7602
7058
  try {
7603
- const ch = await createChannel(body.name, user.id);
7604
- return c.json(ch, 201);
7059
+ const { name, ...settings } = body;
7060
+ const ch = await createChannel(name, user.id, settings);
7061
+ return c.json({ ...ch, channel_name: name }, 201);
7605
7062
  } catch (err) {
7606
7063
  const cause = err instanceof Error ? err.cause : null;
7607
7064
  if (cause && /UNIQUE/i.test(cause.extendedCode ?? cause.message ?? "")) {
@@ -7613,8 +7070,24 @@ var app22 = new Hono25().get("/", async (c) => {
7613
7070
  const name = c.req.param("name");
7614
7071
  const ch = await getChannelByName(name);
7615
7072
  if (!ch) return c.json({ error: "Channel not found" }, 404);
7616
- const participants = await getParticipants(ch.id);
7617
- return c.json({ ...ch, participants });
7073
+ const [participants, settings] = await Promise.all([
7074
+ getParticipants(ch.id),
7075
+ getChannelSettings(name)
7076
+ ]);
7077
+ return c.json({
7078
+ ...ch,
7079
+ channel_name: name,
7080
+ participants,
7081
+ settings: formatChannelSettings(settings)
7082
+ });
7083
+ }).patch("/:name", zValidator9("json", channelSettingsSchema), async (c) => {
7084
+ const name = c.req.param("name");
7085
+ const body = c.req.valid("json");
7086
+ const ch = await getChannelByName(name);
7087
+ if (!ch) return c.json({ error: "Channel not found" }, 404);
7088
+ await updateChannelSettings(name, body);
7089
+ const settings = await getChannelSettings(name);
7090
+ return c.json({ ...ch, channel_name: name, settings: formatChannelSettings(settings) });
7618
7091
  }).post("/:name/join", async (c) => {
7619
7092
  const name = c.req.param("name");
7620
7093
  const user = c.get("user");
@@ -7662,90 +7135,15 @@ import { zValidator as zValidator10 } from "@hono/zod-validator";
7662
7135
  import { Hono as Hono26 } from "hono";
7663
7136
  import { streamSSE as streamSSE5 } from "hono/streaming";
7664
7137
  import { z as z10 } from "zod";
7665
-
7666
- // packages/daemon/src/lib/bridge-outbound.ts
7667
- function extractContent(contentBlocks) {
7668
- const text = contentBlocks.filter((b) => b.type === "text").map((b) => b.text).join("\n");
7669
- const images = contentBlocks.filter((b) => b.type === "image").map((b) => ({ media_type: b.media_type, data: b.data }));
7670
- return { text, images };
7671
- }
7672
- async function routeOutboundBridge(conversationId, senderName, contentBlocks) {
7673
- try {
7674
- const conv = await getConversation(conversationId);
7675
- if (!conv) return;
7676
- if (conv.type === "channel" && conv.name) {
7677
- await routeChannelOutbound(conv.name, senderName, contentBlocks);
7678
- } else if (conv.type === "dm") {
7679
- await routeDMOutbound(conversationId, senderName, contentBlocks);
7680
- }
7681
- } catch (err) {
7682
- logger_default.error(`bridge outbound failed for conversation ${conversationId}`, logger_default.errorData(err));
7683
- }
7684
- }
7685
- async function routeChannelOutbound(channelName, senderName, contentBlocks) {
7686
- const bridgeInfo = findBridgeForChannel(channelName);
7687
- if (!bridgeInfo) return;
7688
- const driver = getChannelDriver(bridgeInfo.platform);
7689
- if (!driver) {
7690
- logger_default.warn(`no channel driver for bridge platform: ${bridgeInfo.platform}`);
7691
- return;
7692
- }
7693
- const { text, images } = extractContent(contentBlocks);
7694
- if (!text) return;
7695
- const env = readEnv(sharedEnvPath());
7696
- env.VOLUTE_SENDER = senderName;
7697
- await driver.send(env, bridgeInfo.externalChannel, text, images.length > 0 ? images : void 0);
7698
- logger_default.debug(`bridge outbound: sent to ${bridgeInfo.platform}:${bridgeInfo.externalChannel}`);
7699
- }
7700
- async function routeDMOutbound(conversationId, senderName, contentBlocks) {
7701
- const participants = await getParticipants(conversationId);
7702
- const puppets = participants.filter((p) => p.userType === "puppet");
7703
- if (puppets.length === 0) return;
7704
- const { text, images } = extractContent(contentBlocks);
7705
- if (!text) return;
7706
- for (const puppet of puppets) {
7707
- const colonIdx = puppet.username.indexOf(":");
7708
- if (colonIdx === -1) {
7709
- logger_default.warn(
7710
- `puppet user ${puppet.username} has malformed username (expected platform:id format)`
7711
- );
7712
- continue;
7713
- }
7714
- const platform = puppet.username.slice(0, colonIdx);
7715
- const externalUserId = puppet.username.slice(colonIdx + 1);
7716
- const bridgeConfig = getBridgeConfig(platform);
7717
- if (!bridgeConfig?.enabled) continue;
7718
- const driver = getChannelDriver(platform);
7719
- if (!driver?.createConversation) {
7720
- logger_default.warn(`no channel driver with DM support for bridge platform: ${platform}`);
7721
- continue;
7722
- }
7723
- try {
7724
- const env = readEnv(sharedEnvPath());
7725
- env.VOLUTE_SENDER = senderName;
7726
- env.VOLUTE_MIND = senderName;
7727
- env.VOLUTE_MIND_DIR = mindDir(senderName);
7728
- const slug = await driver.createConversation(env, [externalUserId]);
7729
- await driver.send(env, slug, text, images.length > 0 ? images : void 0);
7730
- logger_default.debug(`bridge outbound DM: sent to ${platform}:${externalUserId}`);
7731
- } catch (err) {
7732
- logger_default.error(`bridge outbound DM failed for puppet ${puppet.username}`, logger_default.errorData(err));
7733
- }
7734
- }
7735
- }
7736
-
7737
- // packages/daemon/src/web/api/volute/chat.ts
7738
7138
  async function fanOutToMinds(opts) {
7739
- const participants = await getParticipants(opts.conversationId);
7139
+ const participants = opts.participants;
7740
7140
  const mindParticipants = participants.filter(
7741
7141
  (p) => p.userType === "mind" || p.userType === "system"
7742
7142
  );
7743
7143
  const participantNames = participants.map((p) => p.username);
7744
7144
  const isDM = opts.isDM ?? participants.length === 2;
7745
- const { getMindManager: getMindManager2 } = await import("./mind-manager-HFLB5653.js");
7746
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-7KFK3USC.js");
7747
- const manager = getMindManager2();
7748
- const sm = getSleepManagerIfReady2();
7145
+ const manager = getMindManager();
7146
+ const sm = getSleepManagerIfReady();
7749
7147
  const targetMinds = mindParticipants.map((ap) => {
7750
7148
  const key = opts.targetName ? opts.targetName(ap.username) : ap.username;
7751
7149
  if (manager.isRunning(key) || sm?.isSleeping(ap.username)) return ap.username;
@@ -7755,7 +7153,6 @@ async function fanOutToMinds(opts) {
7755
7153
  return buildVoluteSlug({
7756
7154
  participants,
7757
7155
  mindUsername,
7758
- convTitle: opts.convTitle,
7759
7156
  conversationId: opts.conversationId,
7760
7157
  ...opts.slugExtra
7761
7158
  });
@@ -7868,17 +7265,14 @@ var unifiedChatApp = new Hono26().post(
7868
7265
  }
7869
7266
  participantIds.push(mindUser.id);
7870
7267
  if (participantIds.length === 2) {
7871
- const existing = await findDMConversation(baseName, participantIds);
7268
+ const existing = await findDMConversation(participantIds);
7872
7269
  if (existing) {
7873
7270
  conversationId = existing;
7874
7271
  }
7875
7272
  }
7876
7273
  if (!conversationId) {
7877
- const participantNames = /* @__PURE__ */ new Set([senderName, baseName]);
7878
- const title = [...participantNames].join(", ");
7879
- const conv2 = await createConversation(baseName, "volute", {
7274
+ const conv2 = await createConversation({
7880
7275
  userId: user.id !== 0 ? user.id : void 0,
7881
- title,
7882
7276
  participantIds
7883
7277
  });
7884
7278
  conversationId = conv2.id;
@@ -7891,14 +7285,14 @@ var unifiedChatApp = new Hono26().post(
7891
7285
  }
7892
7286
  const conv = await getConversation(conversationId);
7893
7287
  if (!conv) return c.json({ error: "Conversation not found" }, 404);
7894
- const convTitle = conv.title;
7288
+ const convName = conv.type === "channel" ? await getChannelName(conversationId) : null;
7289
+ const participants = await getParticipants(conversationId);
7895
7290
  const fileNotifications = [];
7896
7291
  if (body.files && body.files.length > 0) {
7897
7292
  let fileTargets;
7898
7293
  if (baseName) {
7899
7294
  fileTargets = [baseName];
7900
7295
  } else {
7901
- const participants = await getParticipants(conversationId);
7902
7296
  fileTargets = participants.filter((p) => p.userType === "mind" && p.username !== senderName).map((p) => p.username);
7903
7297
  }
7904
7298
  const { notifications, error } = stageFilesForMinds(body.files, fileTargets, senderName);
@@ -7915,6 +7309,25 @@ var unifiedChatApp = new Hono26().post(
7915
7309
  contentBlocks.push({ type: "image", media_type: img.media_type, data: img.data });
7916
7310
  }
7917
7311
  }
7312
+ if (senderIsMind && conv.type === "channel" && convName) {
7313
+ try {
7314
+ const chSettings = await getChannelSettings(convName);
7315
+ if (chSettings?.char_limit) {
7316
+ for (const block of contentBlocks) {
7317
+ if (block.type === "text" && block.text.length > chSettings.char_limit) {
7318
+ return c.json(
7319
+ {
7320
+ error: `Message exceeds channel character limit (${chSettings.char_limit}). Shorten your message and try again.`
7321
+ },
7322
+ 400
7323
+ );
7324
+ }
7325
+ }
7326
+ }
7327
+ } catch (err) {
7328
+ logger_default.warn("failed to look up channel char_limit, skipping enforcement", logger_default.errorData(err));
7329
+ }
7330
+ }
7918
7331
  const message = await addMessage(conversationId, "user", senderName, contentBlocks);
7919
7332
  let outboundId;
7920
7333
  if (senderIsMind) {
@@ -7922,12 +7335,11 @@ var unifiedChatApp = new Hono26().post(
7922
7335
  logger_default.warn("outbound bridge routing failed", logger_default.errorData(err));
7923
7336
  });
7924
7337
  const channel = buildVoluteSlug({
7925
- participants: await getParticipants(conversationId),
7338
+ participants,
7926
7339
  mindUsername: senderName,
7927
- convTitle,
7928
7340
  conversationId,
7929
7341
  convType: conv.type,
7930
- convName: conv.name
7342
+ convName
7931
7343
  });
7932
7344
  try {
7933
7345
  outboundId = await recordOutbound(senderName, channel, extractTextContent(contentBlocks), {
@@ -7942,15 +7354,14 @@ var unifiedChatApp = new Hono26().post(
7942
7354
  conversationId,
7943
7355
  contentBlocks,
7944
7356
  senderName,
7945
- convTitle,
7357
+ participants,
7946
7358
  isDM,
7947
- slugExtra: { convType: conv.type, convName: conv.name },
7359
+ slugExtra: { convType: conv.type, convName },
7948
7360
  // Variant-aware targeting: when targetMind is a variant, route to the variant name
7949
7361
  targetName: baseName ? (username) => username === baseName ? variantName : username : void 0
7950
7362
  });
7951
7363
  const systemReplyTarget = baseName ?? senderName;
7952
7364
  if (senderIsMind && body.message) {
7953
- const participants = await getParticipants(conversationId);
7954
7365
  const hasSystemUser = participants.some((p) => p.userType === "system");
7955
7366
  if (hasSystemUser) {
7956
7367
  generateSystemReply(conversationId, systemReplyTarget, body.message).catch(
@@ -7978,11 +7389,11 @@ var app23 = new Hono26().get("/:name/conversations/:id/events", async (c) => {
7978
7389
  if (!stream.aborted) console.error("[chat] SSE ping error:", err);
7979
7390
  });
7980
7391
  }, 15e3);
7981
- await new Promise((resolve18) => {
7392
+ await new Promise((resolve17) => {
7982
7393
  stream.onAbort(() => {
7983
7394
  unsubscribe();
7984
7395
  clearInterval(keepAlive);
7985
- resolve18();
7396
+ resolve17();
7986
7397
  });
7987
7398
  });
7988
7399
  });
@@ -7994,7 +7405,6 @@ import { zValidator as zValidator11 } from "@hono/zod-validator";
7994
7405
  import { Hono as Hono27 } from "hono";
7995
7406
  import { z as z11 } from "zod";
7996
7407
  var createConvSchema = z11.object({
7997
- title: z11.string().optional(),
7998
7408
  participantIds: z11.array(z11.number()).optional(),
7999
7409
  participantNames: z11.array(z11.string()).optional()
8000
7410
  });
@@ -8006,8 +7416,7 @@ var app24 = new Hono27().get("/:name/conversations", async (c) => {
8006
7416
  const mindUser = await getOrCreateMindUser(name);
8007
7417
  lookupId = mindUser.id;
8008
7418
  }
8009
- const all = await listConversationsForUser(lookupId);
8010
- const convs = all.filter((c2) => c2.mind_name === name || c2.type === "channel");
7419
+ const convs = await listConversationsForUser(lookupId);
8011
7420
  return c.json(convs);
8012
7421
  }).post("/:name/conversations", zValidator11("json", createConvSchema), async (c) => {
8013
7422
  const name = c.req.param("name");
@@ -8055,20 +7464,15 @@ var app24 = new Hono27().get("/:name/conversations", async (c) => {
8055
7464
  return c.json({ error: "Use channels for multi-participant conversations" }, 400);
8056
7465
  }
8057
7466
  if (participantIds.length === 2) {
8058
- const existingId = await findDMConversation(name, participantIds);
7467
+ const existingId = await findDMConversation(participantIds);
8059
7468
  if (existingId) {
8060
7469
  const conv2 = await getConversation(existingId);
8061
7470
  if (conv2) return c.json(conv2);
8062
7471
  console.warn(`[conversations] DM conversation ${existingId} found but not retrievable`);
8063
7472
  }
8064
7473
  }
8065
- let title = body.title;
8066
- if (!title && body.participantNames?.length) {
8067
- title = body.participantNames.join(", ");
8068
- }
8069
- const conv = await createConversation(name, "volute", {
7474
+ const conv = await createConversation({
8070
7475
  userId: user.id !== 0 ? user.id : void 0,
8071
- title,
8072
7476
  participantIds
8073
7477
  });
8074
7478
  return c.json(conv, 201);
@@ -8146,10 +7550,11 @@ app25.use(
8146
7550
  credentials: false
8147
7551
  })
8148
7552
  );
7553
+ var csrfMiddleware = csrf();
8149
7554
  app25.use("/api/*", async (c, next) => {
8150
7555
  const auth = c.req.header("Authorization");
8151
7556
  if (auth?.startsWith("Bearer ") && auth.length > 7) return next();
8152
- return csrf()(c, next);
7557
+ return csrfMiddleware(c, next);
8153
7558
  });
8154
7559
  app25.get("/api/health", (c) => {
8155
7560
  let version = "unknown";
@@ -8198,10 +7603,10 @@ app25.route("/api/conversations", conversations_default);
8198
7603
  var app_default = app25;
8199
7604
 
8200
7605
  // packages/daemon/src/web/server.ts
8201
- import { existsSync as existsSync11 } from "fs";
7606
+ import { existsSync as existsSync10 } from "fs";
8202
7607
  import { readFile as readFile2, stat as stat2 } from "fs/promises";
8203
7608
  import { createServer as createHttpsServer } from "https";
8204
- import { dirname as dirname2, extname as extname3, resolve as resolve16 } from "path";
7609
+ import { dirname as dirname2, extname as extname3, resolve as resolve15 } from "path";
8205
7610
  import { serve } from "@hono/node-server";
8206
7611
  var MIME_TYPES2 = {
8207
7612
  ".html": "text/html",
@@ -8220,8 +7625,8 @@ async function startServer({
8220
7625
  let assetsDir = "";
8221
7626
  let searchDir = dirname2(new URL(import.meta.url).pathname);
8222
7627
  for (let i = 0; i < 5; i++) {
8223
- const candidate = resolve16(searchDir, "dist", "web-assets");
8224
- if (existsSync11(candidate)) {
7628
+ const candidate = resolve15(searchDir, "dist", "web-assets");
7629
+ if (existsSync10(candidate)) {
8225
7630
  assetsDir = candidate;
8226
7631
  break;
8227
7632
  }
@@ -8231,20 +7636,24 @@ async function startServer({
8231
7636
  app_default.get("*", async (c) => {
8232
7637
  const urlPath = new URL(c.req.url).pathname;
8233
7638
  if (urlPath.startsWith("/api/") || urlPath.startsWith("/ext/")) return c.notFound();
8234
- const filePath = resolve16(assetsDir, urlPath.slice(1));
7639
+ const filePath = resolve15(assetsDir, urlPath.slice(1));
8235
7640
  if (!filePath.startsWith(assetsDir)) return c.text("Forbidden", 403);
8236
7641
  const s = await stat2(filePath).catch(() => null);
8237
7642
  if (s?.isFile()) {
8238
7643
  const ext = extname3(filePath);
8239
7644
  const mime = MIME_TYPES2[ext] || "application/octet-stream";
8240
7645
  const body = await readFile2(filePath);
8241
- return c.body(body, 200, { "Content-Type": mime });
7646
+ const basename = filePath.slice(filePath.lastIndexOf("/") + 1);
7647
+ const nameWithoutExt = basename.slice(0, basename.lastIndexOf("."));
7648
+ const isHashed = /[-.][\da-f]{8,}$/.test(nameWithoutExt);
7649
+ const cacheControl = isHashed ? "public, max-age=31536000, immutable" : "no-cache";
7650
+ return c.body(body, 200, { "Content-Type": mime, "Cache-Control": cacheControl });
8242
7651
  }
8243
- const indexPath = resolve16(assetsDir, "index.html");
7652
+ const indexPath = resolve15(assetsDir, "index.html");
8244
7653
  const indexStat = await stat2(indexPath).catch(() => null);
8245
7654
  if (indexStat?.isFile()) {
8246
7655
  const body = await readFile2(indexPath, "utf-8");
8247
- return c.html(body);
7656
+ return c.html(body, 200, { "Cache-Control": "no-cache" });
8248
7657
  }
8249
7658
  return c.text("Not found", 404);
8250
7659
  });
@@ -8257,10 +7666,10 @@ async function startServer({
8257
7666
  createServer: createHttpsServer,
8258
7667
  serverOptions: { key: tls.key, cert: tls.cert }
8259
7668
  });
8260
- await new Promise((resolve18, reject) => {
7669
+ await new Promise((resolve17, reject) => {
8261
7670
  server2.on("listening", () => {
8262
7671
  logger_default.info("Volute UI running (https)", { hostname, port });
8263
- resolve18();
7672
+ resolve17();
8264
7673
  });
8265
7674
  server2.on("error", (err) => {
8266
7675
  reject(err);
@@ -8268,13 +7677,13 @@ async function startServer({
8268
7677
  });
8269
7678
  const internalPort = port + 1;
8270
7679
  const internalServer = serve({ fetch: app_default.fetch, port: internalPort, hostname: "127.0.0.1" });
8271
- await new Promise((resolve18, reject) => {
7680
+ await new Promise((resolve17, reject) => {
8272
7681
  internalServer.on("listening", () => {
8273
7682
  logger_default.info("Volute API running (http, internal)", {
8274
7683
  hostname: "127.0.0.1",
8275
7684
  port: internalPort
8276
7685
  });
8277
- resolve18();
7686
+ resolve17();
8278
7687
  });
8279
7688
  internalServer.on("error", (err) => {
8280
7689
  reject(err);
@@ -8283,10 +7692,10 @@ async function startServer({
8283
7692
  return { server: server2, internalPort };
8284
7693
  }
8285
7694
  const server = serve({ fetch: app_default.fetch, port, hostname });
8286
- await new Promise((resolve18, reject) => {
7695
+ await new Promise((resolve17, reject) => {
8287
7696
  server.on("listening", () => {
8288
7697
  logger_default.info("Volute API running (http)", { hostname, port });
8289
- resolve18();
7698
+ resolve17();
8290
7699
  });
8291
7700
  server.on("error", (err) => {
8292
7701
  reject(err);
@@ -8297,7 +7706,7 @@ async function startServer({
8297
7706
 
8298
7707
  // packages/daemon/src/daemon.ts
8299
7708
  if (!process.env.VOLUTE_HOME) {
8300
- process.env.VOLUTE_HOME = resolve17(homedir3(), ".volute");
7709
+ process.env.VOLUTE_HOME = resolve16(homedir3(), ".volute");
8301
7710
  }
8302
7711
  if (process.env.VOLUTE_TIMEZONE && !process.env.TZ) {
8303
7712
  process.env.TZ = process.env.VOLUTE_TIMEZONE;
@@ -8308,7 +7717,7 @@ async function startDaemon(opts) {
8308
7717
  const home = voluteHome();
8309
7718
  const systemDir = voluteSystemDir();
8310
7719
  if (!opts.foreground) {
8311
- const rotatingLog = new RotatingLog(resolve17(systemDir, "daemon.log"));
7720
+ const rotatingLog = new RotatingLog(resolve16(systemDir, "daemon.log"));
8312
7721
  logger_default.setOutput((line) => rotatingLog.write(`${line}
8313
7722
  `));
8314
7723
  const write = (...args) => rotatingLog.write(`${format(...args)}
@@ -8318,22 +7727,22 @@ async function startDaemon(opts) {
8318
7727
  console.warn = write;
8319
7728
  console.info = write;
8320
7729
  }
8321
- const DAEMON_PID_PATH = resolve17(systemDir, "daemon.pid");
8322
- const DAEMON_JSON_PATH = resolve17(systemDir, "daemon.json");
7730
+ const DAEMON_PID_PATH = resolve16(systemDir, "daemon.pid");
7731
+ const DAEMON_JSON_PATH = resolve16(systemDir, "daemon.json");
8323
7732
  mkdirSync10(home, { recursive: true });
8324
7733
  ensureSystemDir();
8325
- const { migrateSetupCompleted } = await import("./setup-RHJRFURI.js");
7734
+ const { migrateSetupCompleted } = await import("./setup-PF7JSFMO.js");
8326
7735
  migrateSetupCompleted();
8327
- await (await import("./db-BVBJ57TU.js")).getDb();
7736
+ await (await import("./db-CBOCDYVA.js")).getDb();
8328
7737
  try {
8329
7738
  const { eq: eq7, and: and5 } = await import("drizzle-orm");
8330
- const { users: users2 } = await import("./schema-XVZ2CLKW.js");
8331
- const db = await (await import("./db-BVBJ57TU.js")).getDb();
7739
+ const { users: users2 } = await import("./schema-K575EBPE.js");
7740
+ const db = await (await import("./db-CBOCDYVA.js")).getDb();
8332
7741
  await db.update(users2).set({ role: "system" }).where(and5(eq7(users2.user_type, "system"), eq7(users2.role, "user")));
8333
7742
  } catch (err) {
8334
7743
  logger_default.warn("failed to migrate system user role", logger_default.errorData(err));
8335
7744
  }
8336
- const { initSandbox } = await import("./sandbox-R37VIU36.js");
7745
+ const { initSandbox } = await import("./sandbox-PQYEICEF.js");
8337
7746
  await initSandbox();
8338
7747
  try {
8339
7748
  await syncBuiltinSkills();
@@ -8360,7 +7769,7 @@ async function startDaemon(opts) {
8360
7769
  logger_default.warn("failed to ensure #system channel", logger_default.errorData(err));
8361
7770
  }
8362
7771
  try {
8363
- const { getOrCreateSystemUser: getOrCreateSystemUser2 } = await import("./auth-WX4TESEI.js");
7772
+ const { getOrCreateSystemUser: getOrCreateSystemUser2 } = await import("./auth-2QOOPMBX.js");
8364
7773
  await getOrCreateSystemUser2();
8365
7774
  } catch (err) {
8366
7775
  logger_default.warn(
@@ -8371,7 +7780,7 @@ async function startDaemon(opts) {
8371
7780
  const token = process.env.VOLUTE_DAEMON_TOKEN || randomBytes(32).toString("hex");
8372
7781
  let tls;
8373
7782
  if (opts.tailscale) {
8374
- const { getTailscaleTls } = await import("./tailscale-ZIZ2HWJ5.js");
7783
+ const { getTailscaleTls } = await import("./tailscale-LTYNKIPZ.js");
8375
7784
  const tlsConfig = await getTailscaleTls();
8376
7785
  tls = { key: tlsConfig.key, cert: tlsConfig.cert };
8377
7786
  logger_default.info("Tailscale HTTPS enabled", { hostname: tlsConfig.hostname });
@@ -8444,10 +7853,10 @@ async function startDaemon(opts) {
8444
7853
  await Promise.all(workers);
8445
7854
  }
8446
7855
  try {
8447
- const { isSetupComplete: isSetupComplete2 } = await import("./setup-RHJRFURI.js");
7856
+ const { isSetupComplete: isSetupComplete2 } = await import("./setup-PF7JSFMO.js");
8448
7857
  if (isSetupComplete2()) {
8449
- const { ensureSpiritProject, syncSpiritTemplate } = await import("./spirit-ZFRDXMG7.js");
8450
- const { startSpiritFull } = await import("./mind-service-X2CAA6W6.js");
7858
+ const { ensureSpiritProject, syncSpiritTemplate } = await import("./spirit-SM6ARJ2N.js");
7859
+ const { startSpiritFull } = await import("./mind-service-UDXF5WC2.js");
8451
7860
  await ensureSpiritProject();
8452
7861
  await syncSpiritTemplate();
8453
7862
  const spiritEntry = await findMind("volute");
@@ -8464,7 +7873,7 @@ async function startDaemon(opts) {
8464
7873
  bridgeManager.startBridges(daemonPort).catch((err) => {
8465
7874
  logger_default.warn("failed to start bridges", logger_default.errorData(err));
8466
7875
  });
8467
- import("./cloud-sync-6JL4C24T.js").then(
7876
+ import("./cloud-sync-OIX576NA.js").then(
8468
7877
  ({ consumeQueuedMessages }) => consumeQueuedMessages().catch((err) => {
8469
7878
  logger_default.warn("failed to consume queued cloud messages", logger_default.errorData(err));
8470
7879
  })
@@ -8472,7 +7881,7 @@ async function startDaemon(opts) {
8472
7881
  logger_default.warn("failed to load cloud-sync module", logger_default.errorData(err));
8473
7882
  });
8474
7883
  try {
8475
- const { backfillTemplateHashes, notifyVersionUpdate } = await import("./version-notify-FXSEMXWW.js");
7884
+ const { backfillTemplateHashes, notifyVersionUpdate } = await import("./version-notify-CSE4NBYM.js");
8476
7885
  backfillTemplateHashes();
8477
7886
  notifyVersionUpdate().catch((err) => {
8478
7887
  logger_default.warn("failed to send version update notifications", logger_default.errorData(err));
@@ -8493,13 +7902,13 @@ async function startDaemon(opts) {
8493
7902
  logger_default.info(`running on ${hostname}:${port}, pid ${myPid}`);
8494
7903
  function cleanup() {
8495
7904
  try {
8496
- if (readFileSync11(DAEMON_PID_PATH, "utf-8").trim() === myPid) {
7905
+ if (readFileSync10(DAEMON_PID_PATH, "utf-8").trim() === myPid) {
8497
7906
  unlinkSync2(DAEMON_PID_PATH);
8498
7907
  }
8499
7908
  } catch {
8500
7909
  }
8501
7910
  try {
8502
- const data = JSON.parse(readFileSync11(DAEMON_JSON_PATH, "utf-8"));
7911
+ const data = JSON.parse(readFileSync10(DAEMON_JSON_PATH, "utf-8"));
8503
7912
  if (data.token === token) {
8504
7913
  unlinkSync2(DAEMON_JSON_PATH);
8505
7914
  }