volute 0.28.0 → 0.30.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 (134) hide show
  1. package/README.md +127 -18
  2. package/dist/{accept-666DIZX2.js → accept-E3PAH3QJ.js} +2 -2
  3. package/dist/{activity-events-BBIEA2F4.js → activity-events-BKBPPUBP.js} +2 -2
  4. package/dist/ai-service-VAJT5UBS.js +29 -0
  5. package/dist/api.d.ts +586 -529
  6. package/dist/{archive-UA4BDFXQ.js → archive-WWDBWYN2.js} +2 -2
  7. package/dist/{bridge-FQHZL3MC.js → bridge-RO37CUFM.js} +2 -2
  8. package/dist/{chat-M4SX42JD.js → chat-TCUNPFGO.js} +8 -8
  9. package/dist/{chunk-IAYBDWVG.js → chunk-2C2VXEBB.js} +147 -2
  10. package/dist/chunk-2NDZC3S7.js +1330 -0
  11. package/dist/{chunk-IKRVFPWU.js → chunk-7D47T4RB.js} +3 -2
  12. package/dist/chunk-A6TUJJ3L.js +19 -0
  13. package/dist/{chunk-AW7PFDVN.js → chunk-CVH6Y2YG.js} +1 -1
  14. package/dist/{chunk-XBLSAVJF.js → chunk-DTC6EH5I.js} +1 -1
  15. package/dist/chunk-EFP3PE6C.js +232 -0
  16. package/dist/{chunk-JGFVMROS.js → chunk-EFVHR7KH.js} +1 -1
  17. package/dist/{chunk-K5NAC55T.js → chunk-FSM45XD5.js} +2 -2
  18. package/dist/{chunk-LAC664WU.js → chunk-FXHXHI2A.js} +42 -24
  19. package/dist/{chunk-RKQEHRBB.js → chunk-G3GBKZGG.js} +1 -1
  20. package/dist/{chunk-H7OZRFJB.js → chunk-HHTXM4JT.js} +0 -49
  21. package/dist/{chunk-J4IBNXGJ.js → chunk-IKHDUZRH.js} +4 -3
  22. package/dist/{chunk-MD4C26II.js → chunk-JGFRDMR6.js} +1 -1
  23. package/dist/{chunk-POSXWWTA.js → chunk-LIRWLNAK.js} +26 -12
  24. package/dist/{chunk-NI5FFCCS.js → chunk-MDPCSXZ4.js} +35 -11
  25. package/dist/chunk-NSBFETWP.js +188 -0
  26. package/dist/{chunk-VIVMW2H2.js → chunk-P27RV5WM.js} +1 -1
  27. package/dist/{chunk-EHYDTZTF.js → chunk-P7VFDSSG.js} +2 -2
  28. package/dist/{chunk-AAPXKR5V.js → chunk-QVAQ5454.js} +181 -544
  29. package/dist/{chunk-HDN7MNGD.js → chunk-S5LR3XYJ.js} +1 -1
  30. package/dist/{chunk-2YP2TVDT.js → chunk-UPA6COHU.js} +5 -5
  31. package/dist/{chunk-AKPFNL7L.js → chunk-VGWJSNHS.js} +1 -1
  32. package/dist/{chunk-SGVNFZHW.js → chunk-W5OOPLNP.js} +3 -3
  33. package/dist/{chunk-2WPW7OT6.js → chunk-ZWKTUQEL.js} +1 -1
  34. package/dist/cli.js +25 -26
  35. package/dist/clock-G3ALCMLJ.js +263 -0
  36. package/dist/{cloud-sync-HDL6PHZI.js → cloud-sync-JV4LJOK3.js} +14 -12
  37. package/dist/connectors/discord-bridge.js +1 -1
  38. package/dist/connectors/slack-bridge.js +1 -1
  39. package/dist/connectors/telegram-bridge.js +1 -1
  40. package/dist/{conversations-M2K4253F.js → conversations-7KVQV7EZ.js} +9 -3
  41. package/dist/create-JTLS7GX3.js +70 -0
  42. package/dist/{create-QWV73WXD.js → create-VQSQHJQW.js} +1 -1
  43. package/dist/{daemon-client-I42FK2BF.js → daemon-client-BCTFGVCZ.js} +2 -2
  44. package/dist/{daemon-restart-G4B2OYAB.js → daemon-restart-4JGBHEJ4.js} +7 -7
  45. package/dist/daemon.js +1474 -1124
  46. package/dist/{db-IC4J52XQ.js → db-HMFPIRO2.js} +1 -1
  47. package/dist/{delete-4JYGD4VN.js → delete-JESHKE7F.js} +1 -1
  48. package/dist/down-NGBMGORS.js +14 -0
  49. package/dist/{env-YJMUMFIY.js → env-CLXXT7M2.js} +2 -2
  50. package/dist/{export-BOJQWBMA.js → export-EGA5M5PB.js} +3 -3
  51. package/dist/extension-WZ4SUPJB.js +174 -0
  52. package/dist/extensions-ECO4RPFQ.js +27 -0
  53. package/dist/{files-M546TKVN.js → files-4VEJDASH.js} +3 -3
  54. package/dist/{history-ALPTNB3I.js → history-EJMMLXDO.js} +17 -2
  55. package/dist/{import-SRTQXBGH.js → import-YCGPMBSI.js} +3 -3
  56. package/dist/{join-J4QU42DL.js → join-2GBJKZEN.js} +1 -1
  57. package/dist/{list-R73GENNL.js → list-Q6O7FGAN.js} +2 -2
  58. package/dist/{login-3QZNR2DF.js → login-RET5WESK.js} +2 -2
  59. package/dist/{login-BKP3AFWN.js → login-RL6AU2SM.js} +3 -3
  60. package/dist/{logout-T53VKCPU.js → logout-CGAGJN3L.js} +2 -2
  61. package/dist/{logout-IQK7FNEK.js → logout-JRPBEMMR.js} +3 -3
  62. package/dist/message-delivery-6YMVNOEC.js +28 -0
  63. package/dist/{migrate-registry-to-db-XC7T5B7P.js → migrate-registry-to-db-FK35IPEH.js} +1 -1
  64. package/dist/{mind-S5V6CK5W.js → mind-LUWRQUQ5.js} +17 -17
  65. package/dist/{mind-activity-tracker-EN6XNXPF.js → mind-activity-tracker-VYN2ZZ2M.js} +3 -3
  66. package/dist/{mind-list-UPJ75GPI.js → mind-list-V5WW5DUA.js} +2 -2
  67. package/dist/{mind-manager-S6ILZVX3.js → mind-manager-YFCOIAAX.js} +6 -6
  68. package/dist/{mind-sleep-BTSWQNAC.js → mind-sleep-R6PTNNW4.js} +2 -2
  69. package/dist/{mind-status-TK5AETEM.js → mind-status-I4ISFJ6I.js} +2 -2
  70. package/dist/{mind-wake-SBAKIDVP.js → mind-wake-67ZQEWAV.js} +2 -2
  71. package/dist/{package-CG4RWUGP.js → package-S2OAA5ZA.js} +11 -5
  72. package/dist/pages-watcher-Z3PKNROC.js +21 -0
  73. package/dist/{read-36UFXN3G.js → read-WQMPTSN2.js} +2 -2
  74. package/dist/{register-CHREOMJ3.js → register-NZDSTLP3.js} +3 -3
  75. package/dist/{registry-NDNOOYG4.js → registry-ODSALQQL.js} +1 -1
  76. package/dist/{reject-LXIZFJ4Q.js → reject-2HZOJEIJ.js} +2 -2
  77. package/dist/{restart-6ESL3NBO.js → restart-QHS3NT64.js} +2 -2
  78. package/dist/{sandbox-5BW5HPXM.js → sandbox-O5FUSF43.js} +3 -3
  79. package/dist/{seed-SSUCYYDF.js → seed-WUQMPLDM.js} +1 -1
  80. package/dist/{send-TAOEZ4NH.js → send-OAN3RYYY.js} +20 -6
  81. package/dist/{setup-JHL5ZEST.js → setup-QMDK5RZX.js} +2 -2
  82. package/dist/{setup-RXYVGGT7.js → setup-XJH3E7YM.js} +45 -14
  83. package/dist/{skill-AUAQTSP5.js → skill-FZIN4W4Q.js} +65 -3
  84. package/dist/skills/dreaming/references/INSTALL.md +3 -17
  85. package/dist/skills/volute-mind/SKILL.md +45 -27
  86. package/dist/sleep-manager-O7YQFCV5.js +30 -0
  87. package/dist/{split-TKJ5OT3P.js → split-EXYGGGQN.js} +1 -1
  88. package/dist/{sprout-UNT7LKKE.js → sprout-AXQ6H5DB.js} +8 -7
  89. package/dist/{start-EUJSS5R4.js → start-MTOVL6SY.js} +2 -2
  90. package/dist/{status-NQJYR4BG.js → status-ZRO37MWR.js} +5 -5
  91. package/dist/{stop-3XAITBBF.js → stop-OK5WEPVC.js} +2 -2
  92. package/dist/{systems-SMEFSHTA.js → systems-W3BBMSOZ.js} +5 -5
  93. package/dist/{tailscale-NY5MUMY3.js → tailscale-BM72RXCJ.js} +1 -1
  94. package/dist/{template-hash-BIMA4ILT.js → template-hash-3HOR4UAJ.js} +1 -1
  95. package/dist/up-BXUAIDXB.js +17 -0
  96. package/dist/{update-PTSH22AZ.js → update-PLPHMMZ2.js} +5 -5
  97. package/dist/{update-check-64FWC4Y2.js → update-check-CVCN7MF6.js} +2 -2
  98. package/dist/{upgrade-HA47CS4C.js → upgrade-I6NPCYUU.js} +1 -1
  99. package/dist/{version-notify-JDUF4HQJ.js → version-notify-2NTWVEHL.js} +18 -16
  100. package/dist/web-assets/assets/index--kREqKl9.js +72 -0
  101. package/dist/web-assets/assets/index-BXYTG0nJ.css +1 -0
  102. package/dist/web-assets/ext-theme.css +111 -0
  103. package/dist/web-assets/index.html +2 -2
  104. package/package.json +11 -5
  105. package/packages/extensions/notes/dist/ui/assets/index-DgawVO5g.css +1 -0
  106. package/packages/extensions/notes/dist/ui/assets/index-qUWoeC4c.js +2 -0
  107. package/packages/extensions/notes/dist/ui/index.html +14 -0
  108. package/packages/extensions/notes/skills/notes/SKILL.md +62 -0
  109. package/packages/extensions/notes/skills/notes/scripts/notes.mjs +185 -0
  110. package/packages/extensions/pages/dist/ui/assets/index-D0HyS-xQ.css +1 -0
  111. package/packages/extensions/pages/dist/ui/assets/index-tLTROSk5.js +2 -0
  112. package/packages/extensions/pages/dist/ui/index.html +14 -0
  113. package/packages/extensions/pages/skills/pages/SKILL.md +58 -0
  114. package/templates/_base/home/VOLUTE.md +1 -1
  115. package/templates/_base/src/lib/logger.ts +10 -49
  116. package/templates/_base/src/lib/router.ts +1 -9
  117. package/templates/claude/src/lib/stream-consumer.ts +1 -4
  118. package/templates/pi/src/lib/event-handler.ts +1 -14
  119. package/dist/chunk-P72MVS4R.js +0 -188
  120. package/dist/chunk-T6HKBWXZ.js +0 -23
  121. package/dist/chunk-ZYGKG6VC.js +0 -22
  122. package/dist/create-D7J73A6H.js +0 -45
  123. package/dist/down-LVBXEULC.js +0 -14
  124. package/dist/message-delivery-HV3S6HZV.js +0 -24
  125. package/dist/notes-XCER3I7M.js +0 -220
  126. package/dist/pages-KJDJX4TA.js +0 -36
  127. package/dist/publish-ZZB33WP4.js +0 -86
  128. package/dist/schedule-QTJMFATP.js +0 -154
  129. package/dist/skills/notes/SKILL.md +0 -34
  130. package/dist/sleep-manager-WMVG2VCL.js +0 -28
  131. package/dist/status-S7UUPNRW.js +0 -38
  132. package/dist/up-GM2JOH2Y.js +0 -17
  133. package/dist/web-assets/assets/index-BZGvToHi.css +0 -1
  134. package/dist/web-assets/assets/index-Cz4TrpzB.js +0 -75
package/dist/daemon.js CHANGED
@@ -1,72 +1,51 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- addSharedWorktree,
4
- ensureSharedRepo,
5
- removeSharedWorktree,
6
- sharedMerge
7
- } from "./chunk-P72MVS4R.js";
8
- import {
9
- checkForUpdate,
10
- checkForUpdateCached,
11
- getCurrentVersion
12
- } from "./chunk-HDN7MNGD.js";
13
- import {
14
- applyInitFiles,
15
- composeTemplate,
16
- computeTemplateHash,
17
- copyTemplateToDir,
18
- findTemplatesRoot,
19
- listFiles
20
- } from "./chunk-AKPFNL7L.js";
21
2
  import {
22
3
  announceToSystem,
23
- approveUser,
24
- changePassword,
25
- countAdmins,
26
- createUser,
27
- deleteMindUser as deleteMindUser2,
28
- deleteSystemsConfig,
29
- deleteUser,
30
4
  deliverMessage,
31
5
  ensureSystemChannel,
32
6
  extractTextContent,
33
- getCachedRecentPages,
34
- getCachedSites,
35
7
  getDeliveryManager,
36
- getOrCreateMindUser,
37
8
  getScheduler,
9
+ getSleepManagerIfReady,
38
10
  getTokenBudget,
39
11
  getTypingMap,
40
- getUser,
41
- getUserByUsername,
42
12
  initDeliveryManager,
43
13
  initMailPoller,
44
14
  initScheduler,
45
15
  initSleepManager,
46
16
  initTokenBudget,
47
17
  joinSystemChannel,
48
- listPendingUsers,
49
- listUsers,
50
- listUsersByType,
51
- migrateMindRoles,
52
18
  publish as publish2,
53
19
  publishTypingForChannels,
54
- readSystemsConfig,
55
20
  readVoluteConfig,
56
21
  recordInbound,
57
22
  resolveChannelId,
58
- setUserRole,
59
23
  splitMessage,
60
24
  startMindFull,
61
- stopAllWatchers,
62
25
  stopMindFull,
63
26
  subscribe as subscribe3,
64
- updateUserProfile,
65
- verifyUser,
66
27
  writeChannelEntry,
67
- writeSystemsConfig,
68
28
  writeVoluteConfig
69
- } from "./chunk-AAPXKR5V.js";
29
+ } from "./chunk-QVAQ5454.js";
30
+ import {
31
+ getActiveMinds,
32
+ onMindEvent,
33
+ stopAll
34
+ } from "./chunk-FSM45XD5.js";
35
+ import {
36
+ PROMPT_DEFAULTS,
37
+ PROMPT_KEYS,
38
+ RestartTracker,
39
+ RotatingLog,
40
+ getMindManager,
41
+ getMindPromptDefaults,
42
+ getPrompt,
43
+ getPromptIfCustom,
44
+ initMindManager,
45
+ resolveMindToken,
46
+ substitute
47
+ } from "./chunk-LIRWLNAK.js";
48
+ import "./chunk-IKHDUZRH.js";
70
49
  import {
71
50
  addMessage,
72
51
  createChannel,
@@ -81,45 +60,81 @@ import {
81
60
  getParticipants,
82
61
  getUnreadCounts,
83
62
  initWebhook,
63
+ isConversationForMind,
84
64
  isParticipant,
85
65
  isParticipantOrOwner,
86
66
  joinChannel,
87
67
  leaveChannel,
88
68
  listChannels,
69
+ listConversationsForMind,
89
70
  listConversationsForUser,
90
71
  listConversationsWithParticipants,
91
72
  markConversationRead,
92
73
  publish,
93
74
  subscribe as subscribe2
94
- } from "./chunk-IAYBDWVG.js";
75
+ } from "./chunk-2C2VXEBB.js";
95
76
  import {
96
- getActiveMinds,
97
- onMindEvent,
98
- stopAll
99
- } from "./chunk-K5NAC55T.js";
77
+ checkForUpdate,
78
+ checkForUpdateCached,
79
+ getCurrentVersion
80
+ } from "./chunk-S5LR3XYJ.js";
81
+ import {
82
+ applyInitFiles,
83
+ composeTemplate,
84
+ computeTemplateHash,
85
+ copyTemplateToDir,
86
+ findTemplatesRoot,
87
+ listFiles
88
+ } from "./chunk-VGWJSNHS.js";
89
+ import {
90
+ acceptPending,
91
+ formatFileSize,
92
+ listPending,
93
+ rejectPending,
94
+ stageFile,
95
+ validateFilePath
96
+ } from "./chunk-JGFRDMR6.js";
97
+ import {
98
+ getCachedRecentPages,
99
+ getCachedSites
100
+ } from "./chunk-EFP3PE6C.js";
101
+ import {
102
+ approveUser,
103
+ changePassword,
104
+ countAdmins,
105
+ createUser,
106
+ deleteMindUser as deleteMindUser2,
107
+ deleteSystemsConfig,
108
+ deleteUser,
109
+ getExtensionStandardSkills,
110
+ getLoadedExtensions,
111
+ getOrCreateMindUser,
112
+ getUser,
113
+ getUserByUsername,
114
+ listPendingUsers,
115
+ listUsers,
116
+ listUsersByType,
117
+ loadAllExtensions,
118
+ migrateMindRoles,
119
+ notifyExtensionsDaemonStart,
120
+ notifyExtensionsDaemonStop,
121
+ readSystemsConfig,
122
+ setUserRole,
123
+ updateUserProfile,
124
+ verifyUser,
125
+ writeSystemsConfig
126
+ } from "./chunk-2NDZC3S7.js";
100
127
  import {
101
128
  broadcast,
102
129
  subscribe
103
- } from "./chunk-VIVMW2H2.js";
104
- import {
105
- PROMPT_DEFAULTS,
106
- PROMPT_KEYS,
107
- RestartTracker,
108
- RotatingLog,
109
- getMindManager,
110
- getMindPromptDefaults,
111
- getPrompt,
112
- getPromptIfCustom,
113
- initMindManager,
114
- resolveMindToken,
115
- substitute
116
- } from "./chunk-POSXWWTA.js";
117
- import "./chunk-J4IBNXGJ.js";
130
+ } from "./chunk-P27RV5WM.js";
118
131
  import {
119
132
  SEED_SKILLS,
120
133
  STANDARD_SKILLS,
121
134
  getSharedSkill,
135
+ getStandardSkillsWithExtensions,
122
136
  importSkillFromDir,
137
+ initDefaultSkills,
123
138
  installSkill,
124
139
  listFilesRecursive,
125
140
  listMindSkills,
@@ -130,15 +145,10 @@ import {
130
145
  syncBuiltinSkills,
131
146
  uninstallSkill,
132
147
  updateSkill
133
- } from "./chunk-NI5FFCCS.js";
148
+ } from "./chunk-MDPCSXZ4.js";
134
149
  import {
135
- acceptPending,
136
- formatFileSize,
137
- listPending,
138
- rejectPending,
139
- stageFile,
140
- validateFilePath
141
- } from "./chunk-MD4C26II.js";
150
+ isHomeOnlyArchive
151
+ } from "./chunk-DTC6EH5I.js";
142
152
  import {
143
153
  findBridgeForChannel,
144
154
  findOpenClawSession,
@@ -152,40 +162,51 @@ import {
152
162
  resolveChannelMapping,
153
163
  setBridgeConfig,
154
164
  setChannelMapping
155
- } from "./chunk-2YP2TVDT.js";
165
+ } from "./chunk-UPA6COHU.js";
156
166
  import {
157
167
  loadMergedEnv,
158
168
  mindEnvPath,
159
169
  readEnv,
160
170
  sharedEnvPath,
161
171
  writeEnv
162
- } from "./chunk-2WPW7OT6.js";
172
+ } from "./chunk-ZWKTUQEL.js";
173
+ import {
174
+ aiComplete,
175
+ getAiConfig,
176
+ getAvailableModels,
177
+ getEnabledModels,
178
+ removeAiConfig,
179
+ removeProviderConfig,
180
+ saveProviderConfig,
181
+ setEnabledModels
182
+ } from "./chunk-NSBFETWP.js";
163
183
  import {
164
184
  logBuffer,
165
185
  logger_default
166
186
  } from "./chunk-YUIHSKR6.js";
167
- import {
168
- isHomeOnlyArchive
169
- } from "./chunk-XBLSAVJF.js";
170
187
  import {
171
188
  exec,
172
189
  gitExec,
173
190
  resolveVoluteBin
174
- } from "./chunk-AW7PFDVN.js";
191
+ } from "./chunk-CVH6Y2YG.js";
175
192
  import {
176
193
  chownMindDir,
177
194
  createMindUser,
178
195
  deleteMindUser,
179
196
  ensureVoluteGroup,
180
197
  isIsolationEnabled,
198
+ mindUserName,
181
199
  wrapForIsolation
182
- } from "./chunk-RKQEHRBB.js";
183
- import "./chunk-IKRVFPWU.js";
200
+ } from "./chunk-G3GBKZGG.js";
201
+ import {
202
+ readGlobalConfig,
203
+ writeGlobalConfig
204
+ } from "./chunk-7D47T4RB.js";
184
205
  import "./chunk-D424ZQGI.js";
185
206
  import {
186
207
  buildVoluteSlug,
187
208
  slugify
188
- } from "./chunk-T6HKBWXZ.js";
209
+ } from "./chunk-A6TUJJ3L.js";
189
210
  import {
190
211
  activity,
191
212
  addMind,
@@ -201,9 +222,6 @@ import {
201
222
  mindDir,
202
223
  mindHistory,
203
224
  nextPort,
204
- noteComments,
205
- noteReactions,
206
- notes,
207
225
  readAllMinds,
208
226
  readRegistry,
209
227
  removeMind,
@@ -217,14 +235,14 @@ import {
217
235
  validateMindName,
218
236
  voluteHome,
219
237
  voluteSystemDir
220
- } from "./chunk-H7OZRFJB.js";
238
+ } from "./chunk-HHTXM4JT.js";
221
239
  import {
222
240
  __export
223
241
  } from "./chunk-K3NQKI34.js";
224
242
 
225
243
  // src/daemon.ts
226
244
  import { randomBytes } from "crypto";
227
- import { mkdirSync as mkdirSync11, readFileSync as readFileSync13, unlinkSync as unlinkSync2, writeFileSync as writeFileSync10 } from "fs";
245
+ import { mkdirSync as mkdirSync12, readFileSync as readFileSync14, unlinkSync as unlinkSync2, writeFileSync as writeFileSync11 } from "fs";
228
246
  import { homedir as homedir2 } from "os";
229
247
  import { resolve as resolve22 } from "path";
230
248
  import { format } from "util";
@@ -813,136 +831,174 @@ function migrateToSystemDir() {
813
831
  }
814
832
  }
815
833
 
816
- // src/web/middleware/auth.ts
817
- import { timingSafeEqual } from "crypto";
818
- import { eq as eq2, lt as lt2 } from "drizzle-orm";
819
- import { getCookie } from "hono/cookie";
820
- import { createMiddleware } from "hono/factory";
821
- function isValidDaemonToken(token) {
822
- const expected = process.env.VOLUTE_DAEMON_TOKEN;
823
- if (!expected || token.length !== expected.length) return false;
824
- return timingSafeEqual(Buffer.from(token), Buffer.from(expected));
825
- }
826
- var SESSION_MAX_AGE = 365 * 24 * 60 * 60 * 1e3;
827
- var SESSION_CACHE_TTL = 5 * 60 * 1e3;
828
- var sessionCache = /* @__PURE__ */ new Map();
829
- function invalidateSessionCache(sessionId) {
830
- sessionCache.delete(sessionId);
831
- }
832
- async function createSession(userId) {
833
- const db = await getDb();
834
- const sessionId = crypto.randomUUID();
835
- await db.insert(sessions).values({ id: sessionId, userId, createdAt: Date.now() });
836
- return sessionId;
834
+ // src/lib/shared.ts
835
+ import { execFileSync as execFileSync2 } from "child_process";
836
+ import { chmodSync, existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync4, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
837
+ import { resolve as resolve6 } from "path";
838
+ function readWorktreeGitDir(worktreePath) {
839
+ const dotGit = resolve6(worktreePath, ".git");
840
+ if (!existsSync6(dotGit)) return null;
841
+ try {
842
+ const content = readFileSync4(dotGit, "utf-8").trim();
843
+ const match = content.match(/^gitdir:\s*(.+)$/);
844
+ return match ? match[1] : null;
845
+ } catch {
846
+ return null;
847
+ }
837
848
  }
838
- async function deleteSession(sessionId) {
839
- sessionCache.delete(sessionId);
840
- const db = await getDb();
841
- await db.delete(sessions).where(eq2(sessions.id, sessionId));
849
+ function sharedDir() {
850
+ return resolve6(voluteHome(), "shared");
842
851
  }
843
- async function getSessionUserId(sessionId) {
844
- const db = await getDb();
845
- const row = await db.select().from(sessions).where(eq2(sessions.id, sessionId)).get();
846
- if (!row) return void 0;
847
- if (Date.now() - row.createdAt > SESSION_MAX_AGE) {
848
- await db.delete(sessions).where(eq2(sessions.id, sessionId));
849
- return void 0;
852
+ async function ensureSharedRepo() {
853
+ const dir = sharedDir();
854
+ mkdirSync4(dir, { recursive: true });
855
+ if (existsSync6(resolve6(dir, ".git"))) {
856
+ try {
857
+ await gitExec(["rev-parse", "HEAD"], { cwd: dir });
858
+ return;
859
+ } catch (err) {
860
+ const msg = err instanceof Error ? err.message : String(err);
861
+ if (msg.includes("unknown revision") || msg.includes("bad default revision")) {
862
+ logger_default.warn("shared repo has no commits, re-initializing");
863
+ rmSync2(resolve6(dir, ".git"), { recursive: true, force: true });
864
+ } else {
865
+ throw err;
866
+ }
867
+ }
868
+ }
869
+ const initArgs = isIsolationEnabled() ? ["init", "--shared=group"] : ["init"];
870
+ await gitExec(initArgs, { cwd: dir });
871
+ await gitExec(["checkout", "-b", "main"], { cwd: dir });
872
+ const pagesDir = resolve6(dir, "pages");
873
+ mkdirSync4(pagesDir, { recursive: true });
874
+ writeFileSync3(resolve6(pagesDir, ".gitkeep"), "");
875
+ await gitExec(["add", "-A"], { cwd: dir });
876
+ await gitExec(["commit", "-m", "init shared repo"], { cwd: dir });
877
+ if (isIsolationEnabled()) {
878
+ try {
879
+ execFileSync2("chgrp", ["-R", "volute", dir], { stdio: "ignore" });
880
+ } catch (err) {
881
+ logger_default.warn("failed to chgrp shared repo to volute group", logger_default.errorData(err));
882
+ }
883
+ chmodSync(dir, 1533);
850
884
  }
851
- return row.userId;
852
885
  }
853
- async function cleanExpiredSessions() {
854
- const db = await getDb();
855
- const cutoff = Date.now() - SESSION_MAX_AGE;
856
- await db.delete(sessions).where(lt2(sessions.createdAt, cutoff));
886
+ async function addSharedWorktree(mindName, mindDir2) {
887
+ const dir = sharedDir();
888
+ if (!existsSync6(resolve6(dir, ".git"))) return;
889
+ const worktreePath = resolve6(mindDir2, "home", "shared");
890
+ if (existsSync6(worktreePath)) return;
891
+ let branchExists = false;
892
+ try {
893
+ await gitExec(["rev-parse", "--verify", mindName], { cwd: dir });
894
+ branchExists = true;
895
+ } catch {
896
+ }
897
+ if (branchExists) {
898
+ await gitExec(["worktree", "add", worktreePath, mindName], { cwd: dir });
899
+ } else {
900
+ await gitExec(["worktree", "add", "-b", mindName, worktreePath], { cwd: dir });
901
+ }
902
+ if (isIsolationEnabled()) {
903
+ const worktreeGitDir = readWorktreeGitDir(worktreePath);
904
+ if (worktreeGitDir) {
905
+ try {
906
+ const user = mindUserName(mindName);
907
+ execFileSync2("chown", ["-R", `${user}:volute`, worktreeGitDir], { stdio: "ignore" });
908
+ } catch (err) {
909
+ logger_default.warn(`failed to chown worktree git dir for ${mindName}`, logger_default.errorData(err));
910
+ }
911
+ }
912
+ }
857
913
  }
858
- var requireAdmin = createMiddleware(async (c, next) => {
859
- const user = c.get("user");
860
- if (user.role !== "admin") {
861
- return c.json({ error: "Forbidden" }, 403);
914
+ async function removeSharedWorktree(mindName, mindDir2) {
915
+ const dir = sharedDir();
916
+ if (!existsSync6(resolve6(dir, ".git"))) return;
917
+ const worktreePath = resolve6(mindDir2, "home", "shared");
918
+ if (existsSync6(worktreePath)) {
919
+ try {
920
+ await gitExec(["worktree", "remove", "--force", worktreePath], { cwd: dir });
921
+ } catch (err) {
922
+ logger_default.debug(`worktree remove failed for ${mindName}`, logger_default.errorData(err));
923
+ }
862
924
  }
863
- await next();
864
- });
865
- async function resolveSession(sessionId) {
866
- const cached = sessionCache.get(sessionId);
867
- if (cached && cached.expires > Date.now()) {
868
- return cached.user;
925
+ try {
926
+ await gitExec(["worktree", "prune"], { cwd: dir });
927
+ } catch (err) {
928
+ logger_default.debug(`worktree prune failed for ${mindName}`, logger_default.errorData(err));
869
929
  }
870
- const userId = await getSessionUserId(sessionId);
871
- if (userId == null) {
872
- sessionCache.delete(sessionId);
873
- return null;
930
+ try {
931
+ await gitExec(["branch", "-D", mindName], { cwd: dir });
932
+ } catch {
874
933
  }
875
- const user = await getUser(userId);
876
- if (!user) {
877
- sessionCache.delete(sessionId);
878
- return null;
934
+ }
935
+ var sharedLock = Promise.resolve();
936
+ function rechownWorktree(worktreePath, mindName) {
937
+ if (!isIsolationEnabled()) return;
938
+ try {
939
+ const user = mindUserName(mindName);
940
+ execFileSync2("chown", ["-R", `${user}:volute`, worktreePath], { stdio: "ignore" });
941
+ } catch (err) {
942
+ logger_default.warn(`failed to rechown worktree for ${mindName}`, logger_default.errorData(err));
879
943
  }
880
- sessionCache.set(sessionId, { userId, user, expires: Date.now() + SESSION_CACHE_TTL });
881
- return user;
882
944
  }
883
- var authMiddleware = createMiddleware(async (c, next) => {
884
- const authHeader = c.req.header("Authorization");
885
- if (authHeader?.startsWith("Bearer ")) {
886
- const token = authHeader.slice(7);
887
- if (token && isValidDaemonToken(token)) {
888
- c.set("user", {
889
- id: 0,
890
- username: "daemon",
891
- role: "admin",
892
- user_type: "brain",
893
- display_name: null,
894
- description: null,
895
- avatar: null
896
- });
897
- await next();
898
- return;
945
+ async function withSharedLock(fn) {
946
+ const prev = sharedLock;
947
+ let resolve_;
948
+ sharedLock = new Promise((r) => {
949
+ resolve_ = r;
950
+ });
951
+ await prev;
952
+ try {
953
+ return await fn();
954
+ } finally {
955
+ resolve_();
956
+ }
957
+ }
958
+ async function sharedMerge(mindName, mindDir2, message) {
959
+ return withSharedLock(async () => {
960
+ const dir = sharedDir();
961
+ const worktreePath = resolve6(mindDir2, "home", "shared");
962
+ const status = (await gitExec(["status", "--porcelain"], { cwd: worktreePath })).trim();
963
+ if (status) {
964
+ await gitExec(["add", "-A"], { cwd: worktreePath });
965
+ await gitExec(
966
+ ["commit", "--author", `${mindName} <${mindName}@volute>`, "-m", `wip: ${mindName}`],
967
+ { cwd: worktreePath }
968
+ );
899
969
  }
900
- const mindName = resolveMindToken(token);
901
- if (mindName) {
902
- const mindUser = await getOrCreateMindUser(mindName);
903
- c.set("user", mindUser);
904
- await next();
905
- return;
970
+ const diff = (await gitExec(["diff", `main...${mindName}`, "--stat"], { cwd: dir })).trim();
971
+ if (!diff) {
972
+ return { ok: true, message: "Nothing to merge" };
906
973
  }
907
- if (token) {
908
- const user2 = await resolveSession(token);
909
- if (user2) {
910
- if (user2.role === "pending") return c.json({ error: "Account pending approval" }, 403);
911
- c.set("user", user2);
912
- await next();
913
- return;
974
+ try {
975
+ await gitExec(["merge", "--squash", mindName], { cwd: dir });
976
+ } catch {
977
+ try {
978
+ await gitExec(["reset", "--hard", "HEAD"], { cwd: dir });
979
+ } catch (resetErr) {
980
+ logger_default.error("reset after squash conflict failed in shared repo", logger_default.errorData(resetErr));
914
981
  }
982
+ return { ok: false, conflicts: true, message: "Merge conflicts detected" };
915
983
  }
916
- }
917
- const sessionId = getCookie(c, "volute_session");
918
- if (!sessionId) return c.json({ error: "Unauthorized" }, 401);
919
- const user = await resolveSession(sessionId);
920
- if (!user) return c.json({ error: "Unauthorized" }, 401);
921
- if (user.role === "pending") return c.json({ error: "Account pending approval" }, 403);
922
- c.set("user", user);
923
- await next();
924
- });
925
- var requireSelf = (paramName = "name") => createMiddleware(async (c, next) => {
926
- const user = c.get("user");
927
- if (user.role !== "admin") {
928
- const target = c.req.param(paramName) ?? "";
929
- const baseName = await getBaseName(target);
930
- if (user.username !== baseName) {
931
- return c.json({ error: "Forbidden" }, 403);
984
+ await gitExec(["commit", "--author", `${mindName} <${mindName}@volute>`, "-m", message], {
985
+ cwd: dir
986
+ });
987
+ try {
988
+ await gitExec(["reset", "--hard", "main"], { cwd: worktreePath });
989
+ } catch {
990
+ return {
991
+ ok: true,
992
+ message: "Merged to main, but branch reset failed \u2014 run 'volute shared pull' to sync"
993
+ };
932
994
  }
933
- }
934
- await next();
935
- });
936
-
937
- // src/web/server.ts
938
- import { existsSync as existsSync16 } from "fs";
939
- import { readFile as readFile4, stat as stat4 } from "fs/promises";
940
- import { createServer as createHttpsServer } from "https";
941
- import { dirname as dirname2, extname as extname5, resolve as resolve21 } from "path";
942
- import { serve } from "@hono/node-server";
995
+ rechownWorktree(worktreePath, mindName);
996
+ return { ok: true };
997
+ });
998
+ }
943
999
 
944
1000
  // src/web/app.ts
945
- import { Hono as Hono30 } from "hono";
1001
+ import { Hono as Hono29 } from "hono";
946
1002
  import { bodyLimit } from "hono/body-limit";
947
1003
  import { csrf } from "hono/csrf";
948
1004
  import { HTTPException } from "hono/http-exception";
@@ -973,15 +1029,11 @@ var app = new Hono().get("/events", async (c) => {
973
1029
  } catch (err) {
974
1030
  logger_default.error("[activity-sse] failed to fetch conversations", logger_default.errorData(err));
975
1031
  }
976
- const sites = getCachedSites();
977
- const recentPages = getCachedRecentPages();
978
1032
  await stream.writeSSE({
979
1033
  data: JSON.stringify({
980
1034
  event: "snapshot",
981
1035
  activity: recentActivity,
982
1036
  conversations: conversations2,
983
- sites,
984
- recentPages,
985
1037
  activeMinds: getActiveMinds()
986
1038
  })
987
1039
  });
@@ -1012,25 +1064,148 @@ var app = new Hono().get("/events", async (c) => {
1012
1064
  await new Promise((resolve23) => {
1013
1065
  stream.onAbort(() => resolve23());
1014
1066
  });
1015
- } finally {
1016
- for (const cleanup of cleanups) {
1017
- try {
1018
- cleanup();
1019
- } catch {
1020
- }
1067
+ } finally {
1068
+ for (const cleanup of cleanups) {
1069
+ try {
1070
+ cleanup();
1071
+ } catch {
1072
+ }
1073
+ }
1074
+ }
1075
+ });
1076
+ });
1077
+ var activity_default = app;
1078
+
1079
+ // src/web/api/auth.ts
1080
+ import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync5, rmSync as rmSync3, writeFileSync as writeFileSync4 } from "fs";
1081
+ import { extname, resolve as resolve7 } from "path";
1082
+ import { zValidator } from "@hono/zod-validator";
1083
+ import { Hono as Hono2 } from "hono";
1084
+ import { deleteCookie, getCookie as getCookie2, setCookie } from "hono/cookie";
1085
+ import { z } from "zod";
1086
+
1087
+ // src/web/middleware/auth.ts
1088
+ import { timingSafeEqual } from "crypto";
1089
+ import { eq as eq2, lt as lt2 } from "drizzle-orm";
1090
+ import { getCookie } from "hono/cookie";
1091
+ import { createMiddleware } from "hono/factory";
1092
+ function isValidDaemonToken(token) {
1093
+ const expected = process.env.VOLUTE_DAEMON_TOKEN;
1094
+ if (!expected || token.length !== expected.length) return false;
1095
+ return timingSafeEqual(Buffer.from(token), Buffer.from(expected));
1096
+ }
1097
+ var SESSION_MAX_AGE = 365 * 24 * 60 * 60 * 1e3;
1098
+ var SESSION_CACHE_TTL = 5 * 60 * 1e3;
1099
+ var sessionCache = /* @__PURE__ */ new Map();
1100
+ function invalidateSessionCache(sessionId) {
1101
+ sessionCache.delete(sessionId);
1102
+ }
1103
+ async function createSession(userId) {
1104
+ const db = await getDb();
1105
+ const sessionId = crypto.randomUUID();
1106
+ await db.insert(sessions).values({ id: sessionId, userId, createdAt: Date.now() });
1107
+ return sessionId;
1108
+ }
1109
+ async function deleteSession(sessionId) {
1110
+ sessionCache.delete(sessionId);
1111
+ const db = await getDb();
1112
+ await db.delete(sessions).where(eq2(sessions.id, sessionId));
1113
+ }
1114
+ async function getSessionUserId(sessionId) {
1115
+ const db = await getDb();
1116
+ const row = await db.select().from(sessions).where(eq2(sessions.id, sessionId)).get();
1117
+ if (!row) return void 0;
1118
+ if (Date.now() - row.createdAt > SESSION_MAX_AGE) {
1119
+ await db.delete(sessions).where(eq2(sessions.id, sessionId));
1120
+ return void 0;
1121
+ }
1122
+ return row.userId;
1123
+ }
1124
+ async function cleanExpiredSessions() {
1125
+ const db = await getDb();
1126
+ const cutoff = Date.now() - SESSION_MAX_AGE;
1127
+ await db.delete(sessions).where(lt2(sessions.createdAt, cutoff));
1128
+ }
1129
+ var requireAdmin = createMiddleware(async (c, next) => {
1130
+ const user = c.get("user");
1131
+ if (user.role !== "admin") {
1132
+ return c.json({ error: "Forbidden" }, 403);
1133
+ }
1134
+ await next();
1135
+ });
1136
+ async function resolveSession(sessionId) {
1137
+ const cached = sessionCache.get(sessionId);
1138
+ if (cached && cached.expires > Date.now()) {
1139
+ return cached.user;
1140
+ }
1141
+ const userId = await getSessionUserId(sessionId);
1142
+ if (userId == null) {
1143
+ sessionCache.delete(sessionId);
1144
+ return null;
1145
+ }
1146
+ const user = await getUser(userId);
1147
+ if (!user) {
1148
+ sessionCache.delete(sessionId);
1149
+ return null;
1150
+ }
1151
+ sessionCache.set(sessionId, { userId, user, expires: Date.now() + SESSION_CACHE_TTL });
1152
+ return user;
1153
+ }
1154
+ var authMiddleware = createMiddleware(async (c, next) => {
1155
+ const authHeader = c.req.header("Authorization");
1156
+ if (authHeader?.startsWith("Bearer ")) {
1157
+ const token = authHeader.slice(7);
1158
+ if (token && isValidDaemonToken(token)) {
1159
+ c.set("user", {
1160
+ id: 0,
1161
+ username: "daemon",
1162
+ role: "admin",
1163
+ user_type: "brain",
1164
+ display_name: null,
1165
+ description: null,
1166
+ avatar: null
1167
+ });
1168
+ await next();
1169
+ return;
1170
+ }
1171
+ const mindName = resolveMindToken(token);
1172
+ if (mindName) {
1173
+ const mindUser = await getOrCreateMindUser(mindName);
1174
+ c.set("user", mindUser);
1175
+ await next();
1176
+ return;
1177
+ }
1178
+ if (token) {
1179
+ const user2 = await resolveSession(token);
1180
+ if (user2) {
1181
+ if (user2.role === "pending") return c.json({ error: "Account pending approval" }, 403);
1182
+ c.set("user", user2);
1183
+ await next();
1184
+ return;
1021
1185
  }
1022
1186
  }
1023
- });
1187
+ }
1188
+ const sessionId = getCookie(c, "volute_session");
1189
+ if (!sessionId) return c.json({ error: "Unauthorized" }, 401);
1190
+ const user = await resolveSession(sessionId);
1191
+ if (!user) return c.json({ error: "Unauthorized" }, 401);
1192
+ if (user.role === "pending") return c.json({ error: "Account pending approval" }, 403);
1193
+ c.set("user", user);
1194
+ await next();
1195
+ });
1196
+ var requireSelf = (paramName = "name") => createMiddleware(async (c, next) => {
1197
+ const user = c.get("user");
1198
+ if (user.role !== "admin") {
1199
+ const target = c.req.param(paramName) ?? "";
1200
+ const baseName = await getBaseName(target);
1201
+ if (user.username !== baseName) {
1202
+ return c.json({ error: "Forbidden" }, 403);
1203
+ }
1204
+ }
1205
+ await next();
1024
1206
  });
1025
- var activity_default = app;
1026
1207
 
1027
1208
  // src/web/api/auth.ts
1028
- import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync4, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
1029
- import { extname, resolve as resolve6 } from "path";
1030
- import { zValidator } from "@hono/zod-validator";
1031
- import { Hono as Hono2 } from "hono";
1032
- import { deleteCookie, getCookie as getCookie2, setCookie } from "hono/cookie";
1033
- import { z } from "zod";
1034
1209
  function tryJoinSystem(userId) {
1035
1210
  if (!process.env.VOLUTE_DAEMON_TOKEN) return;
1036
1211
  joinSystemChannel(userId).catch(() => {
@@ -1063,7 +1238,7 @@ var AVATAR_MIME = {
1063
1238
  };
1064
1239
  var MAX_AVATAR_SIZE = 2 * 1024 * 1024;
1065
1240
  function avatarsDir() {
1066
- return resolve6(voluteHome(), "avatars");
1241
+ return resolve7(voluteHome(), "avatars");
1067
1242
  }
1068
1243
  var authenticated = new Hono2().use(authMiddleware).post("/change-password", zValidator("json", changePasswordSchema), async (c) => {
1069
1244
  const user = c.get("user");
@@ -1098,13 +1273,13 @@ var authenticated = new Hono2().use(authMiddleware).post("/change-password", zVa
1098
1273
  return c.json({ error: "Invalid file type (png, jpg, gif, webp only)" }, 400);
1099
1274
  }
1100
1275
  const dir = avatarsDir();
1101
- mkdirSync4(dir, { recursive: true });
1276
+ mkdirSync5(dir, { recursive: true });
1102
1277
  const filename = `avatar-${user.id}${ext}`;
1103
1278
  const buffer2 = Buffer.from(await file.arrayBuffer());
1104
- writeFileSync3(resolve6(dir, filename), buffer2);
1279
+ writeFileSync4(resolve7(dir, filename), buffer2);
1105
1280
  if (user.avatar && user.avatar !== filename) {
1106
- const oldPath = resolve6(dir, user.avatar);
1107
- rmSync2(oldPath, { force: true });
1281
+ const oldPath = resolve7(dir, user.avatar);
1282
+ rmSync3(oldPath, { force: true });
1108
1283
  }
1109
1284
  await updateUserProfile(user.id, { avatar: filename });
1110
1285
  const sessionId = getCookie2(c, "volute_session");
@@ -1118,8 +1293,8 @@ var authenticated = new Hono2().use(authMiddleware).post("/change-password", zVa
1118
1293
  }).delete("/avatar", async (c) => {
1119
1294
  const user = c.get("user");
1120
1295
  if (user.avatar) {
1121
- const path = resolve6(avatarsDir(), user.avatar);
1122
- rmSync2(path, { force: true });
1296
+ const path = resolve7(avatarsDir(), user.avatar);
1297
+ rmSync3(path, { force: true });
1123
1298
  }
1124
1299
  await updateUserProfile(user.id, { avatar: null });
1125
1300
  const sessionId = getCookie2(c, "volute_session");
@@ -1268,13 +1443,13 @@ var app2 = new Hono2().post("/register", zValidator("json", credentialsSchema),
1268
1443
  return c.json({ error: "Invalid filename" }, 400);
1269
1444
  }
1270
1445
  const dir = avatarsDir();
1271
- const filePath = resolve6(dir, filename);
1446
+ const filePath = resolve7(dir, filename);
1272
1447
  if (!filePath.startsWith(`${dir}/`)) return c.json({ error: "Invalid path" }, 400);
1273
- if (!existsSync6(filePath)) return c.json({ error: "Not found" }, 404);
1448
+ if (!existsSync7(filePath)) return c.json({ error: "Not found" }, 404);
1274
1449
  const ext = extname(filename).toLowerCase();
1275
1450
  const mime = AVATAR_MIME[ext];
1276
1451
  if (!mime) return c.json({ error: "Invalid file type" }, 400);
1277
- const data = readFileSync4(filePath);
1452
+ const data = readFileSync5(filePath);
1278
1453
  return c.body(data, 200, {
1279
1454
  "Content-Type": mime,
1280
1455
  "Cache-Control": "public, max-age=3600",
@@ -1409,7 +1584,7 @@ var app3 = new Hono3().post("/:platform/inbound", zValidator2("json", inboundSch
1409
1584
  }
1410
1585
  const participants = await getParticipants(channel.id);
1411
1586
  if (!participants.some((p) => p.userId === puppet.id)) {
1412
- const { addParticipant } = await import("./conversations-M2K4253F.js");
1587
+ const { addParticipant } = await import("./conversations-7KVQV7EZ.js");
1413
1588
  await addParticipant(channel.id, puppet.id);
1414
1589
  }
1415
1590
  const contentBlocks = body.content;
@@ -1464,7 +1639,7 @@ var app3 = new Hono3().post("/:platform/inbound", zValidator2("json", inboundSch
1464
1639
  });
1465
1640
  try {
1466
1641
  const daemonPort = parseInt(process.env.VOLUTE_DAEMON_PORT ?? "", 10);
1467
- if (isNaN(daemonPort)) {
1642
+ if (Number.isNaN(daemonPort)) {
1468
1643
  return c.json({ error: "VOLUTE_DAEMON_PORT not available" }, 500);
1469
1644
  }
1470
1645
  await manager.startBridge(platform, daemonPort);
@@ -1502,10 +1677,10 @@ async function fanOutToBridgedMinds(opts) {
1502
1677
  const participants = await getParticipants(opts.conversationId);
1503
1678
  const mindParticipants = participants.filter((p) => p.userType === "mind");
1504
1679
  const participantNames = participants.map((p) => p.username);
1505
- const { getMindManager: getMindManager2 } = await import("./mind-manager-S6ILZVX3.js");
1506
- const { getSleepManagerIfReady } = await import("./sleep-manager-WMVG2VCL.js");
1680
+ const { getMindManager: getMindManager2 } = await import("./mind-manager-YFCOIAAX.js");
1681
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-O7YQFCV5.js");
1507
1682
  const manager = getMindManager2();
1508
- const sm = getSleepManagerIfReady();
1683
+ const sm = getSleepManagerIfReady2();
1509
1684
  const targetMinds = mindParticipants.filter((ap) => {
1510
1685
  return (manager.isRunning(ap.username) || sm?.isSleeping(ap.username)) && ap.username !== opts.senderName;
1511
1686
  }).map((ap) => ap.username);
@@ -1520,7 +1695,7 @@ async function fanOutToBridgedMinds(opts) {
1520
1695
  writeChannelEntry(mindName, channel, {
1521
1696
  platformId: opts.conversationId,
1522
1697
  platform: "volute",
1523
- type: opts.isDM ? "dm" : "group"
1698
+ type: opts.isDM ? "dm" : "channel"
1524
1699
  });
1525
1700
  } catch (err) {
1526
1701
  logger_default.warn(`failed to write channel entry for ${mindName}`, logger_default.errorData(err));
@@ -1648,7 +1823,7 @@ async function listConversations(env) {
1648
1823
  id: slug,
1649
1824
  platformId: dm.id,
1650
1825
  name: recipients.join(", ") || "DM",
1651
- type: dm.type === 1 ? "dm" : "group"
1826
+ type: dm.type === 1 ? "dm" : "channel"
1652
1827
  });
1653
1828
  }
1654
1829
  return results;
@@ -1818,7 +1993,6 @@ async function listConversations2(env) {
1818
1993
  return data.channels.map((ch) => {
1819
1994
  let type = "channel";
1820
1995
  if (ch.is_im) type = "dm";
1821
- else if (ch.is_mpim) type = "group";
1822
1996
  let slug;
1823
1997
  let name;
1824
1998
  if (ch.is_im && ch.user) {
@@ -1895,7 +2069,7 @@ async function createConversation3(env, participants, name) {
1895
2069
  platformId,
1896
2070
  platform: "slack",
1897
2071
  name: participants.join(", "),
1898
- type: participants.length === 1 ? "dm" : "group"
2072
+ type: participants.length === 1 ? "dm" : "channel"
1899
2073
  });
1900
2074
  }
1901
2075
  return slug;
@@ -2006,18 +2180,18 @@ __export(volute_exports, {
2006
2180
  read: () => read4,
2007
2181
  send: () => send4
2008
2182
  });
2009
- import { existsSync as existsSync7, readFileSync as readFileSync5 } from "fs";
2010
- import { resolve as resolve7 } from "path";
2183
+ import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
2184
+ import { resolve as resolve8 } from "path";
2011
2185
  function getDaemonConfig() {
2012
- const newPath = resolve7(voluteSystemDir(), "daemon.json");
2013
- const legacyPath = resolve7(voluteHome(), "daemon.json");
2014
- const configPath = existsSync7(newPath) ? newPath : legacyPath;
2015
- if (!existsSync7(configPath)) {
2186
+ const newPath = resolve8(voluteSystemDir(), "daemon.json");
2187
+ const legacyPath = resolve8(voluteHome(), "daemon.json");
2188
+ const configPath = existsSync8(newPath) ? newPath : legacyPath;
2189
+ if (!existsSync8(configPath)) {
2016
2190
  throw new Error("Volute daemon is not running");
2017
2191
  }
2018
2192
  let config;
2019
2193
  try {
2020
- config = JSON.parse(readFileSync5(configPath, "utf-8"));
2194
+ config = JSON.parse(readFileSync6(configPath, "utf-8"));
2021
2195
  } catch (err) {
2022
2196
  throw new Error(`Failed to parse ${configPath}: ${err}`);
2023
2197
  }
@@ -2111,7 +2285,7 @@ async function listConversations4(env) {
2111
2285
  convType: conv.type,
2112
2286
  convName: conv.name
2113
2287
  });
2114
- const convType = conv.type === "channel" ? "channel" : participants.length === 2 ? "dm" : "group";
2288
+ const convType = conv.type === "channel" ? "channel" : "dm";
2115
2289
  results.push({
2116
2290
  id: slug,
2117
2291
  platformId: conv.id,
@@ -2374,10 +2548,17 @@ var sharedEnvApp = new Hono5().get("/", (c) => {
2374
2548
  });
2375
2549
  var env_default = app5;
2376
2550
 
2377
- // src/web/api/file-sharing.ts
2378
- import { readFileSync as readFileSync6, statSync } from "fs";
2379
- import { resolve as resolve8 } from "path";
2551
+ // src/web/api/extensions.ts
2380
2552
  import { Hono as Hono6 } from "hono";
2553
+ var app6 = new Hono6().get("/", (c) => {
2554
+ return c.json(getLoadedExtensions());
2555
+ });
2556
+ var extensions_default = app6;
2557
+
2558
+ // src/web/api/file-sharing.ts
2559
+ import { readFileSync as readFileSync7, statSync } from "fs";
2560
+ import { resolve as resolve9 } from "path";
2561
+ import { Hono as Hono7 } from "hono";
2381
2562
  async function notifyMind(port, message, channel, sender) {
2382
2563
  try {
2383
2564
  const res = await fetch(`http://127.0.0.1:${port}/message`, {
@@ -2396,7 +2577,7 @@ async function notifyMind(port, message, channel, sender) {
2396
2577
  console.warn(`[file-sharing] notify mind on port ${port} failed:`, err);
2397
2578
  }
2398
2579
  }
2399
- var app6 = new Hono6().post("/:name/files/send", requireSelf(), async (c) => {
2580
+ var app7 = new Hono7().post("/:name/files/send", requireSelf(), async (c) => {
2400
2581
  const senderName = c.req.param("name");
2401
2582
  const senderEntry = await findMind(senderName);
2402
2583
  if (!senderEntry) return c.json({ error: "Sender mind not found" }, 404);
@@ -2409,21 +2590,21 @@ var app6 = new Hono6().post("/:name/files/send", requireSelf(), async (c) => {
2409
2590
  const pathErr = validateFilePath(body.filePath);
2410
2591
  if (pathErr) return c.json({ error: pathErr }, 400);
2411
2592
  const senderDir = mindDir(senderName);
2412
- const filePath = resolve8(senderDir, "home", body.filePath);
2593
+ const filePath = resolve9(senderDir, "home", body.filePath);
2413
2594
  const MAX_FILE_SIZE2 = 50 * 1024 * 1024;
2414
- const stat5 = statSync(filePath, { throwIfNoEntry: false });
2415
- if (!stat5) return c.json({ error: `File not found: ${body.filePath}` }, 404);
2416
- if (stat5.size > MAX_FILE_SIZE2) {
2595
+ const stat4 = statSync(filePath, { throwIfNoEntry: false });
2596
+ if (!stat4) return c.json({ error: `File not found: ${body.filePath}` }, 404);
2597
+ if (stat4.size > MAX_FILE_SIZE2) {
2417
2598
  return c.json(
2418
2599
  {
2419
- error: `File too large (${formatFileSize(stat5.size)}, max ${formatFileSize(MAX_FILE_SIZE2)})`
2600
+ error: `File too large (${formatFileSize(stat4.size)}, max ${formatFileSize(MAX_FILE_SIZE2)})`
2420
2601
  },
2421
2602
  413
2422
2603
  );
2423
2604
  }
2424
2605
  let content;
2425
2606
  try {
2426
- content = readFileSync6(filePath);
2607
+ content = readFileSync7(filePath);
2427
2608
  } catch (err) {
2428
2609
  const code = err.code;
2429
2610
  if (code === "ENOENT") {
@@ -2534,13 +2715,13 @@ var app6 = new Hono6().post("/:name/files/send", requireSelf(), async (c) => {
2534
2715
  }
2535
2716
  return c.json({ status: "pending", id }, 200);
2536
2717
  });
2537
- var file_sharing_default = app6;
2718
+ var file_sharing_default = app7;
2538
2719
 
2539
2720
  // src/web/api/files.ts
2540
- import { existsSync as existsSync8 } from "fs";
2721
+ import { existsSync as existsSync9 } from "fs";
2541
2722
  import { readdir, readFile, realpath, stat } from "fs/promises";
2542
- import { extname as extname2, resolve as resolve9 } from "path";
2543
- import { Hono as Hono7 } from "hono";
2723
+ import { extname as extname2, resolve as resolve10 } from "path";
2724
+ import { Hono as Hono8 } from "hono";
2544
2725
  var ALLOWED_FILES = /* @__PURE__ */ new Set(["SOUL.md", "MEMORY.md", "CLAUDE.md", "VOLUTE.md"]);
2545
2726
  var AVATAR_MIME2 = {
2546
2727
  ".png": "image/png",
@@ -2550,7 +2731,7 @@ var AVATAR_MIME2 = {
2550
2731
  ".webp": "image/webp"
2551
2732
  };
2552
2733
  var MAX_AVATAR_SIZE2 = 2 * 1024 * 1024;
2553
- var app7 = new Hono7().get("/:name/avatar", async (c) => {
2734
+ var app8 = new Hono8().get("/:name/avatar", async (c) => {
2554
2735
  const name = c.req.param("name");
2555
2736
  const entry = await findMind(name);
2556
2737
  if (!entry) return c.json({ error: "Mind not found" }, 404);
@@ -2560,8 +2741,8 @@ var app7 = new Hono7().get("/:name/avatar", async (c) => {
2560
2741
  const ext = extname2(config.profile.avatar).toLowerCase();
2561
2742
  const mime = AVATAR_MIME2[ext];
2562
2743
  if (!mime) return c.json({ error: "Invalid avatar extension" }, 400);
2563
- const homeDir = resolve9(dir, "home");
2564
- const avatarPath = resolve9(homeDir, config.profile.avatar);
2744
+ const homeDir = resolve10(dir, "home");
2745
+ const avatarPath = resolve10(homeDir, config.profile.avatar);
2565
2746
  if (!avatarPath.startsWith(`${homeDir}/`)) return c.json({ error: "Invalid avatar path" }, 400);
2566
2747
  let realAvatarPath;
2567
2748
  try {
@@ -2590,8 +2771,8 @@ var app7 = new Hono7().get("/:name/avatar", async (c) => {
2590
2771
  const entry = await findMind(name);
2591
2772
  if (!entry) return c.json({ error: "Mind not found" }, 404);
2592
2773
  const dir = mindDir(name);
2593
- const homeDir = resolve9(dir, "home");
2594
- if (!existsSync8(homeDir)) return c.json({ error: "Home directory missing" }, 404);
2774
+ const homeDir = resolve10(dir, "home");
2775
+ if (!existsSync9(homeDir)) return c.json({ error: "Home directory missing" }, 404);
2595
2776
  const allFiles = await readdir(homeDir);
2596
2777
  const files = allFiles.filter((f) => f.endsWith(".md") && ALLOWED_FILES.has(f));
2597
2778
  return c.json(files);
@@ -2604,33 +2785,33 @@ var app7 = new Hono7().get("/:name/avatar", async (c) => {
2604
2785
  const entry = await findMind(name);
2605
2786
  if (!entry) return c.json({ error: "Mind not found" }, 404);
2606
2787
  const dir = mindDir(name);
2607
- const filePath = resolve9(dir, "home", filename);
2608
- if (!existsSync8(filePath)) {
2788
+ const filePath = resolve10(dir, "home", filename);
2789
+ if (!existsSync9(filePath)) {
2609
2790
  return c.json({ error: "File not found" }, 404);
2610
2791
  }
2611
2792
  const content = await readFile(filePath, "utf-8");
2612
2793
  return c.json({ filename, content });
2613
2794
  });
2614
- var files_default = app7;
2795
+ var files_default = app8;
2615
2796
 
2616
2797
  // src/web/api/keys.ts
2617
- import { Hono as Hono8 } from "hono";
2798
+ import { Hono as Hono9 } from "hono";
2618
2799
 
2619
2800
  // src/lib/identity.ts
2620
2801
  import { createHash, generateKeyPairSync, sign, verify } from "crypto";
2621
- import { existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
2622
- import { resolve as resolve10 } from "path";
2802
+ import { existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
2803
+ import { resolve as resolve11 } from "path";
2623
2804
  function generateIdentity(mindDir2) {
2624
- const identityDir = resolve10(mindDir2, ".mind/identity");
2625
- mkdirSync5(identityDir, { recursive: true });
2805
+ const identityDir = resolve11(mindDir2, ".mind/identity");
2806
+ mkdirSync6(identityDir, { recursive: true });
2626
2807
  const { publicKey, privateKey } = generateKeyPairSync("ed25519", {
2627
2808
  publicKeyEncoding: { type: "spki", format: "pem" },
2628
2809
  privateKeyEncoding: { type: "pkcs8", format: "pem" }
2629
2810
  });
2630
- const privatePath = resolve10(identityDir, "private.pem");
2631
- const publicPath = resolve10(identityDir, "public.pem");
2632
- writeFileSync4(privatePath, privateKey, { mode: 384 });
2633
- writeFileSync4(publicPath, publicKey, { mode: 420 });
2811
+ const privatePath = resolve11(identityDir, "private.pem");
2812
+ const publicPath = resolve11(identityDir, "public.pem");
2813
+ writeFileSync5(privatePath, privateKey, { mode: 384 });
2814
+ writeFileSync5(publicPath, publicKey, { mode: 420 });
2634
2815
  const config = readVoluteConfig(mindDir2) ?? {};
2635
2816
  config.identity = {
2636
2817
  privateKey: ".mind/identity/private.pem",
@@ -2643,17 +2824,17 @@ function getPrivateKey(mindDir2) {
2643
2824
  const config = readVoluteConfig(mindDir2);
2644
2825
  const relPath = config?.identity?.privateKey;
2645
2826
  if (!relPath) return null;
2646
- const fullPath = resolve10(mindDir2, relPath);
2647
- if (!existsSync9(fullPath)) return null;
2648
- return readFileSync7(fullPath, "utf-8");
2827
+ const fullPath = resolve11(mindDir2, relPath);
2828
+ if (!existsSync10(fullPath)) return null;
2829
+ return readFileSync8(fullPath, "utf-8");
2649
2830
  }
2650
2831
  function getPublicKey(mindDir2) {
2651
2832
  const config = readVoluteConfig(mindDir2);
2652
2833
  const relPath = config?.identity?.publicKey;
2653
2834
  if (!relPath) return null;
2654
- const fullPath = resolve10(mindDir2, relPath);
2655
- if (!existsSync9(fullPath)) return null;
2656
- return readFileSync7(fullPath, "utf-8");
2835
+ const fullPath = resolve11(mindDir2, relPath);
2836
+ if (!existsSync10(fullPath)) return null;
2837
+ return readFileSync8(fullPath, "utf-8");
2657
2838
  }
2658
2839
  function getFingerprint(publicKeyPem) {
2659
2840
  return createHash("sha256").update(publicKeyPem).digest("hex");
@@ -2688,7 +2869,7 @@ async function publishPublicKey(mindName, publicKeyPem) {
2688
2869
  }
2689
2870
 
2690
2871
  // src/web/api/keys.ts
2691
- var app8 = new Hono8().get("/:fingerprint", async (c) => {
2872
+ var app9 = new Hono9().get("/:fingerprint", async (c) => {
2692
2873
  const fingerprint = c.req.param("fingerprint");
2693
2874
  for (const entry of await readRegistry()) {
2694
2875
  try {
@@ -2702,20 +2883,20 @@ var app8 = new Hono8().get("/:fingerprint", async (c) => {
2702
2883
  }
2703
2884
  return c.json({ error: "Key not found" }, 404);
2704
2885
  });
2705
- var keys_default = app8;
2886
+ var keys_default = app9;
2706
2887
 
2707
2888
  // src/web/api/logs.ts
2708
2889
  import { spawn as spawn2 } from "child_process";
2709
- import { existsSync as existsSync10 } from "fs";
2710
- import { resolve as resolve11 } from "path";
2711
- import { Hono as Hono9 } from "hono";
2890
+ import { existsSync as existsSync11 } from "fs";
2891
+ import { resolve as resolve12 } from "path";
2892
+ import { Hono as Hono10 } from "hono";
2712
2893
  import { streamSSE as streamSSE2 } from "hono/streaming";
2713
- var app9 = new Hono9().get("/:name/logs", async (c) => {
2894
+ var app10 = new Hono10().get("/:name/logs", async (c) => {
2714
2895
  const name = c.req.param("name");
2715
2896
  const entry = await findMind(name);
2716
2897
  if (!entry) return c.json({ error: "Mind not found" }, 404);
2717
- const logFile = resolve11(stateDir(name), "logs", "mind.log");
2718
- if (!existsSync10(logFile)) {
2898
+ const logFile = resolve12(stateDir(name), "logs", "mind.log");
2899
+ if (!existsSync11(logFile)) {
2719
2900
  return c.json({ error: "No log file found" }, 404);
2720
2901
  }
2721
2902
  return streamSSE2(c, async (stream) => {
@@ -2742,8 +2923,8 @@ var app9 = new Hono9().get("/:name/logs", async (c) => {
2742
2923
  const name = c.req.param("name");
2743
2924
  const entry = await findMind(name);
2744
2925
  if (!entry) return c.json({ error: "Mind not found" }, 404);
2745
- const logFile = resolve11(stateDir(name), "logs", "mind.log");
2746
- if (!existsSync10(logFile)) {
2926
+ const logFile = resolve12(stateDir(name), "logs", "mind.log");
2927
+ if (!existsSync11(logFile)) {
2747
2928
  return c.json({ error: "No log file found" }, 404);
2748
2929
  }
2749
2930
  const nParam = parseInt(c.req.query("n") ?? "50", 10);
@@ -2758,13 +2939,13 @@ var app9 = new Hono9().get("/:name/logs", async (c) => {
2758
2939
  });
2759
2940
  return c.text(output);
2760
2941
  });
2761
- var logs_default = app9;
2942
+ var logs_default = app10;
2762
2943
 
2763
2944
  // src/web/api/mind-skills.ts
2764
2945
  import { zValidator as zValidator3 } from "@hono/zod-validator";
2765
- import { Hono as Hono10 } from "hono";
2946
+ import { Hono as Hono11 } from "hono";
2766
2947
  import { z as z3 } from "zod";
2767
- var app10 = new Hono10().get("/:name/skills", async (c) => {
2948
+ var app11 = new Hono11().get("/:name/skills", async (c) => {
2768
2949
  const name = c.req.param("name");
2769
2950
  const entry = await findMind(name);
2770
2951
  if (!entry) return c.json({ error: "Mind not found" }, 404);
@@ -2839,38 +3020,38 @@ var app10 = new Hono10().get("/:name/skills", async (c) => {
2839
3020
  }
2840
3021
  return c.json({ ok: true });
2841
3022
  });
2842
- var mind_skills_default = app10;
3023
+ var mind_skills_default = app11;
2843
3024
 
2844
3025
  // src/web/api/minds.ts
2845
3026
  import {
2846
3027
  cpSync,
2847
- existsSync as existsSync13,
2848
- mkdirSync as mkdirSync8,
3028
+ existsSync as existsSync14,
3029
+ mkdirSync as mkdirSync9,
2849
3030
  readdirSync as readdirSync3,
2850
- readFileSync as readFileSync11,
2851
- rmSync as rmSync4,
2852
- writeFileSync as writeFileSync8
3031
+ readFileSync as readFileSync12,
3032
+ rmSync as rmSync5,
3033
+ writeFileSync as writeFileSync9
2853
3034
  } from "fs";
2854
- import { resolve as resolve15 } from "path";
3035
+ import { resolve as resolve16 } from "path";
2855
3036
  import { zValidator as zValidator4 } from "@hono/zod-validator";
2856
- import { and as and3, desc as desc2, eq as eq4, sql } from "drizzle-orm";
2857
- import { Hono as Hono11 } from "hono";
3037
+ import { and as and4, desc as desc3, eq as eq5, sql as sql2 } from "drizzle-orm";
3038
+ import { Hono as Hono12 } from "hono";
2858
3039
  import { z as z4 } from "zod";
2859
3040
 
2860
3041
  // src/lib/consolidate.ts
2861
- import { readdirSync as readdirSync2, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
2862
- import { resolve as resolve12 } from "path";
3042
+ import { readdirSync as readdirSync2, readFileSync as readFileSync9, writeFileSync as writeFileSync6 } from "fs";
3043
+ import { resolve as resolve13 } from "path";
2863
3044
  async function consolidateMemory(mindDir2) {
2864
- const soulPath = resolve12(mindDir2, "home/SOUL.md");
2865
- const memoryPath = resolve12(mindDir2, "home/MEMORY.md");
2866
- const memoryDir = resolve12(mindDir2, "home/memory");
2867
- const soul = readFileSync8(soulPath, "utf-8");
3045
+ const soulPath = resolve13(mindDir2, "home/SOUL.md");
3046
+ const memoryPath = resolve13(mindDir2, "home/MEMORY.md");
3047
+ const memoryDir = resolve13(mindDir2, "home/memory");
3048
+ const soul = readFileSync9(soulPath, "utf-8");
2868
3049
  const logs = [];
2869
3050
  try {
2870
3051
  const files = readdirSync2(memoryDir).filter((f) => /^\d{4}-\d{2}-\d{2}\.md$/.test(f)).sort();
2871
3052
  for (const filename of files) {
2872
3053
  const date = filename.replace(".md", "");
2873
- const content2 = readFileSync8(resolve12(memoryDir, filename), "utf-8").trim();
3054
+ const content2 = readFileSync9(resolve13(memoryDir, filename), "utf-8").trim();
2874
3055
  if (content2) {
2875
3056
  logs.push(`### ${date}
2876
3057
 
@@ -2920,7 +3101,7 @@ ${content2}`);
2920
3101
  const data = await res.json();
2921
3102
  const content = data.content.filter((b) => b.type === "text" && b.text).map((b) => b.text).join("").trim();
2922
3103
  if (content) {
2923
- writeFileSync5(memoryPath, `${content}
3104
+ writeFileSync6(memoryPath, `${content}
2924
3105
  `);
2925
3106
  console.log("MEMORY.md created successfully.");
2926
3107
  } else {
@@ -2930,11 +3111,11 @@ ${content2}`);
2930
3111
 
2931
3112
  // src/lib/convert-session.ts
2932
3113
  import { randomUUID } from "crypto";
2933
- import { mkdirSync as mkdirSync6, readFileSync as readFileSync9, writeFileSync as writeFileSync6 } from "fs";
3114
+ import { mkdirSync as mkdirSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync7 } from "fs";
2934
3115
  import { homedir } from "os";
2935
- import { resolve as resolve13 } from "path";
3116
+ import { resolve as resolve14 } from "path";
2936
3117
  function convertSession(opts) {
2937
- const lines = readFileSync9(opts.sessionPath, "utf-8").trim().split("\n");
3118
+ const lines = readFileSync10(opts.sessionPath, "utf-8").trim().split("\n");
2938
3119
  const sessionId = randomUUID();
2939
3120
  const idMap = /* @__PURE__ */ new Map();
2940
3121
  const messages = [];
@@ -3048,10 +3229,10 @@ function convertSession(opts) {
3048
3229
  }
3049
3230
  }
3050
3231
  const projectId = opts.projectDir.replace(/\//g, "-");
3051
- const sdkDir = resolve13(homedir(), ".claude", "projects", projectId);
3052
- mkdirSync6(sdkDir, { recursive: true });
3053
- const sdkPath = resolve13(sdkDir, `${sessionId}.jsonl`);
3054
- writeFileSync6(sdkPath, `${sdkEvents.join("\n")}
3232
+ const sdkDir = resolve14(homedir(), ".claude", "projects", projectId);
3233
+ mkdirSync7(sdkDir, { recursive: true });
3234
+ const sdkPath = resolve14(sdkDir, `${sessionId}.jsonl`);
3235
+ writeFileSync7(sdkPath, `${sdkEvents.join("\n")}
3055
3236
  `);
3056
3237
  console.log(`Converted ${sdkEvents.length} messages \u2192 ${sdkPath}`);
3057
3238
  return sessionId;
@@ -3102,6 +3283,194 @@ function convertAssistantContent(content) {
3102
3283
  return result;
3103
3284
  }
3104
3285
 
3286
+ // src/lib/daemon/turn-summarizer.ts
3287
+ import { and as and3, desc as desc2, eq as eq4, gt, lt as lt3, sql } from "drizzle-orm";
3288
+
3289
+ // src/lib/format-tool.ts
3290
+ function summarizeTool(name, input) {
3291
+ if (input && typeof input === "object") {
3292
+ const args = input;
3293
+ const val = args.path ?? args.command ?? args.query ?? args.url;
3294
+ if (typeof val === "string") {
3295
+ const brief = val.length > 60 ? `${val.slice(0, 57)}...` : val;
3296
+ return `[${name} ${brief}]`;
3297
+ }
3298
+ }
3299
+ return `[${name}]`;
3300
+ }
3301
+
3302
+ // src/lib/daemon/turn-summarizer.ts
3303
+ var sLog = logger_default.child("turn-summarizer");
3304
+ async function gatherTurnEvents(mind, session, doneId) {
3305
+ const db = await getDb();
3306
+ const conditions = [
3307
+ eq4(mindHistory.mind, mind),
3308
+ eq4(mindHistory.type, "done"),
3309
+ lt3(mindHistory.id, doneId)
3310
+ ];
3311
+ if (session) {
3312
+ conditions.push(eq4(mindHistory.session, session));
3313
+ }
3314
+ const prevDone = await db.select({ id: mindHistory.id }).from(mindHistory).where(and3(...conditions)).orderBy(desc2(mindHistory.id)).limit(1);
3315
+ const prevDoneId = prevDone.length > 0 ? prevDone[0].id : 0;
3316
+ const turnConditions = [
3317
+ eq4(mindHistory.mind, mind),
3318
+ gt(mindHistory.id, prevDoneId),
3319
+ sql`${mindHistory.id} <= ${doneId}`
3320
+ ];
3321
+ if (session) {
3322
+ turnConditions.push(eq4(mindHistory.session, session));
3323
+ }
3324
+ const events = await db.select({
3325
+ id: mindHistory.id,
3326
+ type: mindHistory.type,
3327
+ channel: mindHistory.channel,
3328
+ session: mindHistory.session,
3329
+ content: mindHistory.content,
3330
+ metadata: mindHistory.metadata,
3331
+ created_at: mindHistory.created_at
3332
+ }).from(mindHistory).where(and3(...turnConditions)).orderBy(mindHistory.id);
3333
+ return {
3334
+ events,
3335
+ fromId: events.length > 0 ? events[0].id : doneId,
3336
+ toId: doneId
3337
+ };
3338
+ }
3339
+ function buildDeterministicSummary(events) {
3340
+ const channels = /* @__PURE__ */ new Set();
3341
+ const tools = [];
3342
+ let hasInbound = false;
3343
+ let hasOutbound = false;
3344
+ for (const ev of events) {
3345
+ if (ev.type === "inbound") {
3346
+ hasInbound = true;
3347
+ if (ev.channel) channels.add(ev.channel);
3348
+ }
3349
+ if (ev.type === "outbound" || ev.type === "text") {
3350
+ hasOutbound = true;
3351
+ }
3352
+ if (ev.type === "tool_use" && ev.metadata) {
3353
+ try {
3354
+ const meta = JSON.parse(ev.metadata);
3355
+ if (meta.name) tools.push(meta.name);
3356
+ } catch (err) {
3357
+ sLog.debug(`failed to parse tool_use metadata for event ${ev.id}`, logger_default.errorData(err));
3358
+ }
3359
+ }
3360
+ }
3361
+ const parts = [];
3362
+ if (hasInbound) {
3363
+ const channelList = [...channels];
3364
+ parts.push(
3365
+ channelList.length > 0 ? `Received message on ${channelList.join(", ")}` : "Received message"
3366
+ );
3367
+ }
3368
+ if (tools.length > 0) {
3369
+ const unique = [...new Set(tools)];
3370
+ parts.push(`Used ${unique.join(", ")}`);
3371
+ }
3372
+ if (hasOutbound) {
3373
+ parts.push("Sent response");
3374
+ }
3375
+ return parts.length > 0 ? `${parts.join(". ")}.` : "Turn completed.";
3376
+ }
3377
+ function buildTranscript(events) {
3378
+ const lines = [];
3379
+ for (const ev of events) {
3380
+ switch (ev.type) {
3381
+ case "inbound":
3382
+ lines.push(`[inbound${ev.channel ? ` ${ev.channel}` : ""}] ${ev.content ?? ""}`);
3383
+ break;
3384
+ case "outbound":
3385
+ case "text":
3386
+ lines.push(`[response] ${(ev.content ?? "").slice(0, 500)}`);
3387
+ break;
3388
+ case "tool_use": {
3389
+ let toolInfo = "tool";
3390
+ if (ev.metadata) {
3391
+ try {
3392
+ const meta = JSON.parse(ev.metadata);
3393
+ toolInfo = summarizeTool(meta.name ?? "tool", meta.input ?? {});
3394
+ } catch (err) {
3395
+ sLog.debug(`failed to parse tool_use metadata for event ${ev.id}`, logger_default.errorData(err));
3396
+ }
3397
+ }
3398
+ lines.push(toolInfo);
3399
+ break;
3400
+ }
3401
+ }
3402
+ }
3403
+ return lines.join("\n");
3404
+ }
3405
+ async function summarizeTurn(mind, session, channel, doneId) {
3406
+ const { events, fromId, toId } = await gatherTurnEvents(mind, session, doneId);
3407
+ if (events.length === 0) return;
3408
+ const tools = [];
3409
+ for (const ev of events) {
3410
+ if (ev.type === "tool_use" && ev.metadata) {
3411
+ try {
3412
+ const meta = JSON.parse(ev.metadata);
3413
+ if (meta.name) tools.push(meta.name);
3414
+ } catch (err) {
3415
+ sLog.debug(`failed to parse tool_use metadata for event ${ev.id}`, logger_default.errorData(err));
3416
+ }
3417
+ }
3418
+ }
3419
+ const fromTime = events[0].created_at;
3420
+ const toTime = events[events.length - 1].created_at;
3421
+ let summaryText;
3422
+ let deterministic;
3423
+ const transcript = buildTranscript(events);
3424
+ if (transcript.trim()) {
3425
+ const summaryPrompt = await getPrompt("turn_summary");
3426
+ const aiResult = await aiComplete(summaryPrompt, transcript);
3427
+ if (aiResult) {
3428
+ summaryText = aiResult;
3429
+ deterministic = false;
3430
+ } else {
3431
+ summaryText = buildDeterministicSummary(events);
3432
+ deterministic = true;
3433
+ }
3434
+ } else {
3435
+ summaryText = buildDeterministicSummary(events);
3436
+ deterministic = true;
3437
+ }
3438
+ const metadata = {
3439
+ deterministic,
3440
+ tool_count: tools.length,
3441
+ tools: [...new Set(tools)],
3442
+ from_id: fromId,
3443
+ to_id: toId,
3444
+ from_time: fromTime,
3445
+ to_time: toTime
3446
+ };
3447
+ const db = await getDb();
3448
+ try {
3449
+ await db.insert(mindHistory).values({
3450
+ mind,
3451
+ type: "summary",
3452
+ session: session ?? null,
3453
+ channel: channel ?? null,
3454
+ content: summaryText,
3455
+ metadata: JSON.stringify(metadata)
3456
+ });
3457
+ } catch (err) {
3458
+ sLog.error(
3459
+ `failed to persist summary for ${mind} (events ${fromId}-${toId})`,
3460
+ logger_default.errorData(err)
3461
+ );
3462
+ return;
3463
+ }
3464
+ publish2(mind, {
3465
+ mind,
3466
+ type: "summary",
3467
+ session,
3468
+ channel,
3469
+ content: summaryText,
3470
+ metadata
3471
+ });
3472
+ }
3473
+
3105
3474
  // src/lib/health.ts
3106
3475
  async function checkHealth(port) {
3107
3476
  try {
@@ -3117,7 +3486,7 @@ async function checkHealth(port) {
3117
3486
  }
3118
3487
 
3119
3488
  // src/lib/variant-cleanup.ts
3120
- import { existsSync as existsSync11, rmSync as rmSync3 } from "fs";
3489
+ import { existsSync as existsSync12, rmSync as rmSync4 } from "fs";
3121
3490
  async function cleanupVariant(variantName, projectRoot, variantPath, opts) {
3122
3491
  if (opts?.stop) {
3123
3492
  try {
@@ -3126,14 +3495,14 @@ async function cleanupVariant(variantName, projectRoot, variantPath, opts) {
3126
3495
  logger_default.warn(`failed to stop variant ${variantName}`, logger_default.errorData(err));
3127
3496
  }
3128
3497
  }
3129
- const { findMind: findMind2 } = await import("./registry-NDNOOYG4.js");
3498
+ const { findMind: findMind2 } = await import("./registry-ODSALQQL.js");
3130
3499
  const variantEntry = await findMind2(variantName);
3131
3500
  const branchName = variantEntry?.branch ?? variantName;
3132
- if (existsSync11(variantPath)) {
3501
+ if (existsSync12(variantPath)) {
3133
3502
  try {
3134
3503
  await gitExec(["worktree", "remove", "--force", variantPath], { cwd: projectRoot });
3135
3504
  } catch {
3136
- rmSync3(variantPath, { recursive: true, force: true });
3505
+ rmSync4(variantPath, { recursive: true, force: true });
3137
3506
  try {
3138
3507
  await gitExec(["worktree", "prune"], { cwd: projectRoot });
3139
3508
  } catch (err) {
@@ -3163,8 +3532,8 @@ async function cleanupVariant(variantName, projectRoot, variantPath, opts) {
3163
3532
  }
3164
3533
 
3165
3534
  // src/lib/variants.ts
3166
- import { existsSync as existsSync12, mkdirSync as mkdirSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync7 } from "fs";
3167
- import { resolve as resolve14 } from "path";
3535
+ import { existsSync as existsSync13, mkdirSync as mkdirSync8, readFileSync as readFileSync11, writeFileSync as writeFileSync8 } from "fs";
3536
+ import { resolve as resolve15 } from "path";
3168
3537
  async function checkHealth2(port) {
3169
3538
  try {
3170
3539
  const res = await fetch(`http://127.0.0.1:${port}/health`, {
@@ -3193,8 +3562,8 @@ async function getMindStatus(name, port) {
3193
3562
  const manager = getMindManager();
3194
3563
  let status = "stopped";
3195
3564
  try {
3196
- const { getSleepManagerIfReady } = await import("./sleep-manager-WMVG2VCL.js");
3197
- if (getSleepManagerIfReady()?.isSleeping(name)) {
3565
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-O7YQFCV5.js");
3566
+ if (getSleepManagerIfReady2()?.isSleeping(name)) {
3198
3567
  status = "sleeping";
3199
3568
  }
3200
3569
  } catch {
@@ -3241,7 +3610,7 @@ async function initTemplateBranch(projectRoot, composedDir, manifest, mindName,
3241
3610
  await gitExec(["commit", "-m", "initial commit"], opts);
3242
3611
  }
3243
3612
  async function updateTemplateBranch(projectRoot, template, mindName) {
3244
- const tempWorktree = resolve15(projectRoot, ".variants", "_template_update");
3613
+ const tempWorktree = resolve16(projectRoot, ".variants", "_template_update");
3245
3614
  let branchExists = false;
3246
3615
  try {
3247
3616
  await gitExec(["rev-parse", "--verify", TEMPLATE_BRANCH], { cwd: projectRoot });
@@ -3252,8 +3621,8 @@ async function updateTemplateBranch(projectRoot, template, mindName) {
3252
3621
  await gitExec(["worktree", "remove", "--force", tempWorktree], { cwd: projectRoot });
3253
3622
  } catch {
3254
3623
  }
3255
- if (existsSync13(tempWorktree)) {
3256
- rmSync4(tempWorktree, { recursive: true, force: true });
3624
+ if (existsSync14(tempWorktree)) {
3625
+ rmSync5(tempWorktree, { recursive: true, force: true });
3257
3626
  }
3258
3627
  const templatesRoot = findTemplatesRoot();
3259
3628
  const { composedDir, manifest } = composeTemplate(templatesRoot, template);
@@ -3273,9 +3642,9 @@ async function updateTemplateBranch(projectRoot, template, mindName) {
3273
3642
  });
3274
3643
  }
3275
3644
  copyTemplateToDir(composedDir, tempWorktree, mindName, manifest);
3276
- const initDir = resolve15(tempWorktree, ".init");
3277
- if (existsSync13(initDir)) {
3278
- rmSync4(initDir, { recursive: true, force: true });
3645
+ const initDir = resolve16(tempWorktree, ".init");
3646
+ if (existsSync14(initDir)) {
3647
+ rmSync5(initDir, { recursive: true, force: true });
3279
3648
  }
3280
3649
  await gitExec(["add", "-A"], { cwd: tempWorktree });
3281
3650
  try {
@@ -3288,10 +3657,10 @@ async function updateTemplateBranch(projectRoot, template, mindName) {
3288
3657
  await gitExec(["worktree", "remove", "--force", tempWorktree], { cwd: projectRoot });
3289
3658
  } catch {
3290
3659
  }
3291
- if (existsSync13(tempWorktree)) {
3292
- rmSync4(tempWorktree, { recursive: true, force: true });
3660
+ if (existsSync14(tempWorktree)) {
3661
+ rmSync5(tempWorktree, { recursive: true, force: true });
3293
3662
  }
3294
- rmSync4(composedDir, { recursive: true, force: true });
3663
+ rmSync5(composedDir, { recursive: true, force: true });
3295
3664
  }
3296
3665
  }
3297
3666
  async function mergeTemplateBranch(worktreeDir) {
@@ -3314,14 +3683,14 @@ async function mergeTemplateBranch(worktreeDir) {
3314
3683
  async function npmInstallAsMind(cwd, mindName) {
3315
3684
  if (isIsolationEnabled()) {
3316
3685
  const [cmd, args] = await wrapForIsolation("npm", ["install"], mindName);
3317
- await exec(cmd, args, { cwd, env: { ...process.env, HOME: resolve15(cwd, "home") } });
3686
+ await exec(cmd, args, { cwd, env: { ...process.env, HOME: resolve16(cwd, "home") } });
3318
3687
  } else {
3319
3688
  await exec("npm", ["install"], { cwd });
3320
3689
  }
3321
3690
  }
3322
3691
  async function importFromArchive(c, tempDir, nameOverride, manifest) {
3323
- const extractedMindDir = resolve15(tempDir, "mind");
3324
- if (!existsSync13(extractedMindDir)) {
3692
+ const extractedMindDir = resolve16(tempDir, "mind");
3693
+ if (!existsSync14(extractedMindDir)) {
3325
3694
  return c.json({ error: "Invalid archive: missing mind/ directory" }, 400);
3326
3695
  }
3327
3696
  if (!manifest?.includes || !manifest.name || !manifest.template) {
@@ -3339,21 +3708,21 @@ async function importFromFullArchive(c, tempDir, extractedMindDir, nameOverride,
3339
3708
  if (await findMind(name)) return c.json({ error: `Mind already exists: ${name}` }, 409);
3340
3709
  ensureVoluteHome();
3341
3710
  const dest = mindDir(name);
3342
- if (existsSync13(dest)) return c.json({ error: "Mind directory already exists" }, 409);
3711
+ if (existsSync14(dest)) return c.json({ error: "Mind directory already exists" }, 409);
3343
3712
  try {
3344
3713
  cpSync(extractedMindDir, dest, { recursive: true });
3345
3714
  if (!manifest.includes.identity) {
3346
3715
  generateIdentity(dest);
3347
3716
  }
3348
3717
  const state = stateDir(name);
3349
- mkdirSync8(state, { recursive: true });
3350
- const channelsJson = resolve15(tempDir, "state/channels.json");
3351
- if (existsSync13(channelsJson)) {
3352
- cpSync(channelsJson, resolve15(state, "channels.json"));
3718
+ mkdirSync9(state, { recursive: true });
3719
+ const channelsJson = resolve16(tempDir, "state/channels.json");
3720
+ if (existsSync14(channelsJson)) {
3721
+ cpSync(channelsJson, resolve16(state, "channels.json"));
3353
3722
  }
3354
- const envJson = resolve15(tempDir, "state/env.json");
3355
- if (existsSync13(envJson)) {
3356
- cpSync(envJson, resolve15(state, "env.json"));
3723
+ const envJson = resolve16(tempDir, "state/env.json");
3724
+ if (existsSync14(envJson)) {
3725
+ cpSync(envJson, resolve16(state, "env.json"));
3357
3726
  }
3358
3727
  const port = await nextPort();
3359
3728
  await addMind(name, port, manifest.stage, manifest.template);
@@ -3362,36 +3731,36 @@ async function importFromFullArchive(c, tempDir, extractedMindDir, nameOverride,
3362
3731
  } catch (err) {
3363
3732
  logger_default.warn(`failed to set template hash for ${name}`, logger_default.errorData(err));
3364
3733
  }
3365
- const homeDir = resolve15(dest, "home");
3734
+ const homeDir = resolve16(dest, "home");
3366
3735
  ensureVoluteGroup();
3367
3736
  createMindUser(name, homeDir);
3368
3737
  chownMindDir(dest, name);
3369
3738
  await npmInstallAsMind(dest, name);
3370
3739
  await importHistoryFromArchive(name, tempDir);
3371
3740
  importSessionsFromArchive(dest, tempDir);
3372
- if (!existsSync13(resolve15(dest, ".git"))) {
3741
+ if (!existsSync14(resolve16(dest, ".git"))) {
3373
3742
  try {
3374
- const env = isIsolationEnabled() ? { ...process.env, HOME: resolve15(dest, "home") } : void 0;
3743
+ const env = isIsolationEnabled() ? { ...process.env, HOME: resolve16(dest, "home") } : void 0;
3375
3744
  await gitExec(["init"], { cwd: dest, mindName: name, env });
3376
3745
  await configureGitIdentity(name, { cwd: dest, mindName: name, env });
3377
3746
  await gitExec(["add", "-A"], { cwd: dest, mindName: name, env });
3378
3747
  await gitExec(["commit", "-m", "import from archive"], { cwd: dest, mindName: name, env });
3379
3748
  } catch (err) {
3380
3749
  logger_default.error(`git setup failed for imported mind ${name}`, logger_default.errorData(err));
3381
- rmSync4(resolve15(dest, ".git"), { recursive: true, force: true });
3750
+ rmSync5(resolve16(dest, ".git"), { recursive: true, force: true });
3382
3751
  }
3383
3752
  }
3384
3753
  chownMindDir(dest, name);
3385
- rmSync4(tempDir, { recursive: true, force: true });
3754
+ rmSync5(tempDir, { recursive: true, force: true });
3386
3755
  return c.json({ ok: true, name, port, message: `Imported mind: ${name} (port ${port})` });
3387
3756
  } catch (err) {
3388
- if (existsSync13(dest)) rmSync4(dest, { recursive: true, force: true });
3757
+ if (existsSync14(dest)) rmSync5(dest, { recursive: true, force: true });
3389
3758
  try {
3390
3759
  await removeMind(name);
3391
3760
  } catch (cleanupErr) {
3392
3761
  logger_default.error(`Failed to clean up registry for ${name}`, logger_default.errorData(cleanupErr));
3393
3762
  }
3394
- rmSync4(tempDir, { recursive: true, force: true });
3763
+ rmSync5(tempDir, { recursive: true, force: true });
3395
3764
  return c.json({ error: err instanceof Error ? err.message : "Failed to import mind" }, 500);
3396
3765
  }
3397
3766
  }
@@ -3402,7 +3771,7 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
3402
3771
  if (await findMind(name)) return c.json({ error: `Mind already exists: ${name}` }, 409);
3403
3772
  ensureVoluteHome();
3404
3773
  const dest = mindDir(name);
3405
- if (existsSync13(dest)) return c.json({ error: "Mind directory already exists" }, 409);
3774
+ if (existsSync14(dest)) return c.json({ error: "Mind directory already exists" }, 409);
3406
3775
  const templatesRoot = findTemplatesRoot();
3407
3776
  const { composedDir, manifest: templateManifest } = composeTemplate(
3408
3777
  templatesRoot,
@@ -3411,40 +3780,40 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
3411
3780
  try {
3412
3781
  copyTemplateToDir(composedDir, dest, name, templateManifest);
3413
3782
  applyInitFiles(dest);
3414
- const extractedHome = resolve15(extractedMindDir, "home");
3415
- if (existsSync13(extractedHome)) {
3416
- cpSync(extractedHome, resolve15(dest, "home"), { recursive: true });
3783
+ const extractedHome = resolve16(extractedMindDir, "home");
3784
+ if (existsSync14(extractedHome)) {
3785
+ cpSync(extractedHome, resolve16(dest, "home"), { recursive: true });
3417
3786
  }
3418
- const extractedMindInternal = resolve15(extractedMindDir, ".mind");
3419
- if (existsSync13(extractedMindInternal)) {
3420
- cpSync(extractedMindInternal, resolve15(dest, ".mind"), { recursive: true });
3787
+ const extractedMindInternal = resolve16(extractedMindDir, ".mind");
3788
+ if (existsSync14(extractedMindInternal)) {
3789
+ cpSync(extractedMindInternal, resolve16(dest, ".mind"), { recursive: true });
3421
3790
  }
3422
- const identityDir = resolve15(dest, ".mind/identity");
3791
+ const identityDir = resolve16(dest, ".mind/identity");
3423
3792
  let publicKeyPem;
3424
- if (!manifest.includes.identity || !existsSync13(resolve15(identityDir, "private.pem"))) {
3793
+ if (!manifest.includes.identity || !existsSync14(resolve16(identityDir, "private.pem"))) {
3425
3794
  ({ publicKeyPem } = generateIdentity(dest));
3426
3795
  } else {
3427
- publicKeyPem = readFileSync11(resolve15(identityDir, "public.pem"), "utf-8");
3796
+ publicKeyPem = readFileSync12(resolve16(identityDir, "public.pem"), "utf-8");
3428
3797
  }
3429
- const promptsPath = resolve15(dest, "home/.config/prompts.json");
3430
- if (!existsSync13(promptsPath)) {
3798
+ const promptsPath = resolve16(dest, "home/.config/prompts.json");
3799
+ if (!existsSync14(promptsPath)) {
3431
3800
  const mindPrompts = await getMindPromptDefaults();
3432
- writeFileSync8(promptsPath, `${JSON.stringify(mindPrompts, null, 2)}
3801
+ writeFileSync9(promptsPath, `${JSON.stringify(mindPrompts, null, 2)}
3433
3802
  `);
3434
3803
  }
3435
3804
  const state = stateDir(name);
3436
- mkdirSync8(state, { recursive: true });
3437
- const channelsJson = resolve15(tempDir, "state/channels.json");
3438
- if (existsSync13(channelsJson)) {
3439
- cpSync(channelsJson, resolve15(state, "channels.json"));
3805
+ mkdirSync9(state, { recursive: true });
3806
+ const channelsJson = resolve16(tempDir, "state/channels.json");
3807
+ if (existsSync14(channelsJson)) {
3808
+ cpSync(channelsJson, resolve16(state, "channels.json"));
3440
3809
  }
3441
- const envJson = resolve15(tempDir, "state/env.json");
3442
- if (existsSync13(envJson)) {
3443
- cpSync(envJson, resolve15(state, "env.json"));
3810
+ const envJson = resolve16(tempDir, "state/env.json");
3811
+ if (existsSync14(envJson)) {
3812
+ cpSync(envJson, resolve16(state, "env.json"));
3444
3813
  }
3445
3814
  const port = await nextPort();
3446
3815
  await addMind(name, port, manifest.stage, manifest.template);
3447
- const homeDir = resolve15(dest, "home");
3816
+ const homeDir = resolve16(dest, "home");
3448
3817
  ensureVoluteGroup();
3449
3818
  createMindUser(name, homeDir);
3450
3819
  chownMindDir(dest, name);
@@ -3457,7 +3826,7 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
3457
3826
  await initTemplateBranch(dest, composedDir, templateManifest, name, env);
3458
3827
  } catch (err) {
3459
3828
  logger_default.error(`git setup failed for imported mind ${name}`, logger_default.errorData(err));
3460
- rmSync4(resolve15(dest, ".git"), { recursive: true, force: true });
3829
+ rmSync5(resolve16(dest, ".git"), { recursive: true, force: true });
3461
3830
  gitWarning = "Git setup failed \u2014 variants and upgrades won't be available until git is initialized.";
3462
3831
  }
3463
3832
  try {
@@ -3465,7 +3834,7 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
3465
3834
  } catch (err) {
3466
3835
  logger_default.warn(`failed to add shared worktree for ${name}`, logger_default.errorData(err));
3467
3836
  }
3468
- const skillSet = manifest.stage === "seed" ? SEED_SKILLS : STANDARD_SKILLS;
3837
+ const skillSet = manifest.stage === "seed" ? SEED_SKILLS : getStandardSkillsWithExtensions();
3469
3838
  const skillWarnings = [];
3470
3839
  for (const skillId of skillSet) {
3471
3840
  try {
@@ -3481,7 +3850,7 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
3481
3850
  publishPublicKey(name, publicKeyPem).catch(
3482
3851
  (err) => logger_default.warn(`failed to publish key for ${name}`, { error: err.message })
3483
3852
  );
3484
- rmSync4(tempDir, { recursive: true, force: true });
3853
+ rmSync5(tempDir, { recursive: true, force: true });
3485
3854
  return c.json({
3486
3855
  ok: true,
3487
3856
  name,
@@ -3492,24 +3861,24 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
3492
3861
  ...skillWarnings.length > 0 && { skillWarnings }
3493
3862
  });
3494
3863
  } catch (err) {
3495
- if (existsSync13(dest)) rmSync4(dest, { recursive: true, force: true });
3864
+ if (existsSync14(dest)) rmSync5(dest, { recursive: true, force: true });
3496
3865
  try {
3497
3866
  await removeMind(name);
3498
3867
  } catch (cleanupErr) {
3499
3868
  logger_default.error(`Failed to clean up registry for ${name}`, logger_default.errorData(cleanupErr));
3500
3869
  }
3501
- rmSync4(tempDir, { recursive: true, force: true });
3870
+ rmSync5(tempDir, { recursive: true, force: true });
3502
3871
  return c.json({ error: err instanceof Error ? err.message : "Failed to import mind" }, 500);
3503
3872
  } finally {
3504
- rmSync4(composedDir, { recursive: true, force: true });
3873
+ rmSync5(composedDir, { recursive: true, force: true });
3505
3874
  }
3506
3875
  }
3507
3876
  async function importHistoryFromArchive(name, tempDir) {
3508
- const historyJsonl = resolve15(tempDir, "history.jsonl");
3509
- if (!existsSync13(historyJsonl)) return;
3877
+ const historyJsonl = resolve16(tempDir, "history.jsonl");
3878
+ if (!existsSync14(historyJsonl)) return;
3510
3879
  try {
3511
3880
  const db = await getDb();
3512
- const lines = readFileSync11(historyJsonl, "utf-8").trim().split("\n");
3881
+ const lines = readFileSync12(historyJsonl, "utf-8").trim().split("\n");
3513
3882
  let imported = 0;
3514
3883
  let failed = 0;
3515
3884
  for (const line of lines) {
@@ -3545,13 +3914,13 @@ async function importHistoryFromArchive(name, tempDir) {
3545
3914
  }
3546
3915
  }
3547
3916
  function importSessionsFromArchive(dest, tempDir) {
3548
- const sessionsDir = resolve15(tempDir, "sessions");
3549
- if (!existsSync13(sessionsDir)) return;
3917
+ const sessionsDir = resolve16(tempDir, "sessions");
3918
+ if (!existsSync14(sessionsDir)) return;
3550
3919
  try {
3551
- const destSessions = resolve15(dest, ".mind/sessions");
3552
- mkdirSync8(destSessions, { recursive: true });
3920
+ const destSessions = resolve16(dest, ".mind/sessions");
3921
+ mkdirSync9(destSessions, { recursive: true });
3553
3922
  for (const file of readdirSync3(sessionsDir)) {
3554
- cpSync(resolve15(sessionsDir, file), resolve15(destSessions, file));
3923
+ cpSync(resolve16(sessionsDir, file), resolve16(destSessions, file));
3555
3924
  }
3556
3925
  } catch (err) {
3557
3926
  logger_default.error("Failed to import sessions from archive", logger_default.errorData(err));
@@ -3566,7 +3935,7 @@ var createMindSchema = z4.object({
3566
3935
  seedSoul: z4.string().optional(),
3567
3936
  skills: z4.array(z4.string()).optional()
3568
3937
  });
3569
- var app11 = new Hono11().post("/", requireAdmin, zValidator4("json", createMindSchema), async (c) => {
3938
+ var app12 = new Hono12().post("/", requireAdmin, zValidator4("json", createMindSchema), async (c) => {
3570
3939
  const body = c.req.valid("json");
3571
3940
  const { name, template = "claude" } = body;
3572
3941
  const nameErr = validateMindName(name);
@@ -3574,7 +3943,7 @@ var app11 = new Hono11().post("/", requireAdmin, zValidator4("json", createMindS
3574
3943
  if (await findMind(name)) return c.json({ error: `Mind already exists: ${name}` }, 409);
3575
3944
  ensureVoluteHome();
3576
3945
  const dest = mindDir(name);
3577
- if (existsSync13(dest)) return c.json({ error: "Mind directory already exists" }, 409);
3946
+ if (existsSync14(dest)) return c.json({ error: "Mind directory already exists" }, 409);
3578
3947
  const templatesRoot = findTemplatesRoot();
3579
3948
  const { composedDir, manifest } = composeTemplate(templatesRoot, template);
3580
3949
  try {
@@ -3607,15 +3976,15 @@ var app11 = new Hono11().post("/", requireAdmin, zValidator4("json", createMindS
3607
3976
  writeVoluteConfig(dest, config);
3608
3977
  }
3609
3978
  if (body.model) {
3610
- const configPath = resolve15(dest, "home/.config/config.json");
3611
- const existing = existsSync13(configPath) ? JSON.parse(readFileSync11(configPath, "utf-8")) : {};
3979
+ const configPath = resolve16(dest, "home/.config/config.json");
3980
+ const existing = existsSync14(configPath) ? JSON.parse(readFileSync12(configPath, "utf-8")) : {};
3612
3981
  existing.model = body.model;
3613
- writeFileSync8(configPath, `${JSON.stringify(existing, null, 2)}
3982
+ writeFileSync9(configPath, `${JSON.stringify(existing, null, 2)}
3614
3983
  `);
3615
3984
  }
3616
3985
  const mindPrompts = await getMindPromptDefaults();
3617
- writeFileSync8(
3618
- resolve15(dest, "home/.config/prompts.json"),
3986
+ writeFileSync9(
3987
+ resolve16(dest, "home/.config/prompts.json"),
3619
3988
  `${JSON.stringify(mindPrompts, null, 2)}
3620
3989
  `
3621
3990
  );
@@ -3626,7 +3995,7 @@ var app11 = new Hono11().post("/", requireAdmin, zValidator4("json", createMindS
3626
3995
  } catch (err) {
3627
3996
  logger_default.warn(`failed to set template hash for ${name}`, logger_default.errorData(err));
3628
3997
  }
3629
- const homeDir = resolve15(dest, "home");
3998
+ const homeDir = resolve16(dest, "home");
3630
3999
  ensureVoluteGroup();
3631
4000
  createMindUser(name, homeDir);
3632
4001
  chownMindDir(dest, name);
@@ -3639,7 +4008,7 @@ var app11 = new Hono11().post("/", requireAdmin, zValidator4("json", createMindS
3639
4008
  await initTemplateBranch(dest, composedDir, manifest, name, env);
3640
4009
  } catch (err) {
3641
4010
  logger_default.error(`git setup failed for ${name}`, logger_default.errorData(err));
3642
- rmSync4(resolve15(dest, ".git"), { recursive: true, force: true });
4011
+ rmSync5(resolve16(dest, ".git"), { recursive: true, force: true });
3643
4012
  gitWarning = "Git setup failed \u2014 variants and upgrades won't be available until git is initialized.";
3644
4013
  }
3645
4014
  try {
@@ -3654,9 +4023,9 @@ The human who planted you described you as: "${body.description}"
3654
4023
  ` : "";
3655
4024
  const seedSoulRaw = body.seedSoul ?? await getPrompt("seed_soul", { name, description: descLine });
3656
4025
  const seedSoul = body.seedSoul ? substitute(seedSoulRaw, { name, description: descLine }) : seedSoulRaw;
3657
- writeFileSync8(resolve15(dest, "home/SOUL.md"), seedSoul);
4026
+ writeFileSync9(resolve16(dest, "home/SOUL.md"), seedSoul);
3658
4027
  }
3659
- const skillSet = body.skills ?? (body.stage === "seed" ? SEED_SKILLS : STANDARD_SKILLS);
4028
+ const skillSet = body.skills ?? (body.stage === "seed" ? SEED_SKILLS : getStandardSkillsWithExtensions());
3660
4029
  const skillWarnings = [];
3661
4030
  for (const skillId of skillSet) {
3662
4031
  try {
@@ -3669,11 +4038,11 @@ The human who planted you described you as: "${body.description}"
3669
4038
  if (body.stage !== "seed") {
3670
4039
  const customSoul = await getPromptIfCustom("default_soul");
3671
4040
  if (customSoul) {
3672
- writeFileSync8(resolve15(dest, "home/SOUL.md"), customSoul.replace(/\{\{name\}\}/g, name));
4041
+ writeFileSync9(resolve16(dest, "home/SOUL.md"), customSoul.replace(/\{\{name\}\}/g, name));
3673
4042
  }
3674
4043
  const customMemory = await getPromptIfCustom("default_memory");
3675
4044
  if (customMemory) {
3676
- writeFileSync8(resolve15(dest, "home/MEMORY.md"), customMemory);
4045
+ writeFileSync9(resolve16(dest, "home/MEMORY.md"), customMemory);
3677
4046
  }
3678
4047
  }
3679
4048
  publishPublicKey(name, publicKeyPem).catch(
@@ -3702,14 +4071,14 @@ The human who planted you described you as: "${body.description}"
3702
4071
  ...skillWarnings.length > 0 && { skillWarnings }
3703
4072
  });
3704
4073
  } catch (err) {
3705
- if (existsSync13(dest)) rmSync4(dest, { recursive: true, force: true });
4074
+ if (existsSync14(dest)) rmSync5(dest, { recursive: true, force: true });
3706
4075
  try {
3707
4076
  await removeMind(name);
3708
4077
  } catch {
3709
4078
  }
3710
4079
  return c.json({ error: err instanceof Error ? err.message : "Failed to create mind" }, 500);
3711
4080
  } finally {
3712
- rmSync4(composedDir, { recursive: true, force: true });
4081
+ rmSync5(composedDir, { recursive: true, force: true });
3713
4082
  }
3714
4083
  }).post("/import", requireAdmin, async (c) => {
3715
4084
  let body;
@@ -3722,13 +4091,13 @@ The human who planted you described you as: "${body.description}"
3722
4091
  return importFromArchive(c, body.archivePath, body.name, body.manifest);
3723
4092
  }
3724
4093
  const wsDir = body.workspacePath;
3725
- if (!wsDir || !existsSync13(resolve15(wsDir, "SOUL.md")) || !existsSync13(resolve15(wsDir, "IDENTITY.md"))) {
4094
+ if (!wsDir || !existsSync14(resolve16(wsDir, "SOUL.md")) || !existsSync14(resolve16(wsDir, "IDENTITY.md"))) {
3726
4095
  return c.json({ error: "Invalid workspace: missing SOUL.md or IDENTITY.md" }, 400);
3727
4096
  }
3728
- const soul = readFileSync11(resolve15(wsDir, "SOUL.md"), "utf-8");
3729
- const identity = readFileSync11(resolve15(wsDir, "IDENTITY.md"), "utf-8");
3730
- const userPath = resolve15(wsDir, "USER.md");
3731
- const user = existsSync13(userPath) ? readFileSync11(userPath, "utf-8") : "";
4097
+ const soul = readFileSync12(resolve16(wsDir, "SOUL.md"), "utf-8");
4098
+ const identity = readFileSync12(resolve16(wsDir, "IDENTITY.md"), "utf-8");
4099
+ const userPath = resolve16(wsDir, "USER.md");
4100
+ const user = existsSync14(userPath) ? readFileSync12(userPath, "utf-8") : "";
3732
4101
  const name = body.name ?? parseNameFromIdentity(identity) ?? "imported-mind";
3733
4102
  const template = body.template ?? "claude";
3734
4103
  const nameErr = validateMindName(name);
@@ -3748,33 +4117,33 @@ ${user.trimEnd()}
3748
4117
  ` : "";
3749
4118
  ensureVoluteHome();
3750
4119
  const dest = mindDir(name);
3751
- if (existsSync13(dest)) return c.json({ error: "Mind directory already exists" }, 409);
4120
+ if (existsSync14(dest)) return c.json({ error: "Mind directory already exists" }, 409);
3752
4121
  const templatesRoot = findTemplatesRoot();
3753
4122
  const { composedDir, manifest } = composeTemplate(templatesRoot, template);
3754
4123
  try {
3755
4124
  copyTemplateToDir(composedDir, dest, name, manifest);
3756
4125
  applyInitFiles(dest);
3757
4126
  const { publicKeyPem: importPublicKey } = generateIdentity(dest);
3758
- writeFileSync8(resolve15(dest, "home/SOUL.md"), mergedSoul);
3759
- const wsMemoryPath = resolve15(wsDir, "MEMORY.md");
3760
- const hasMemory = existsSync13(wsMemoryPath);
4127
+ writeFileSync9(resolve16(dest, "home/SOUL.md"), mergedSoul);
4128
+ const wsMemoryPath = resolve16(wsDir, "MEMORY.md");
4129
+ const hasMemory = existsSync14(wsMemoryPath);
3761
4130
  if (hasMemory) {
3762
- const existingMemory = readFileSync11(wsMemoryPath, "utf-8");
3763
- writeFileSync8(
3764
- resolve15(dest, "home/MEMORY.md"),
4131
+ const existingMemory = readFileSync12(wsMemoryPath, "utf-8");
4132
+ writeFileSync9(
4133
+ resolve16(dest, "home/MEMORY.md"),
3765
4134
  `${existingMemory.trimEnd()}${mergedMemoryExtra}`
3766
4135
  );
3767
4136
  } else if (user) {
3768
- writeFileSync8(resolve15(dest, "home/MEMORY.md"), `${user.trimEnd()}
4137
+ writeFileSync9(resolve16(dest, "home/MEMORY.md"), `${user.trimEnd()}
3769
4138
  `);
3770
4139
  }
3771
- const wsMemoryDir = resolve15(wsDir, "memory");
4140
+ const wsMemoryDir = resolve16(wsDir, "memory");
3772
4141
  let dailyLogCount = 0;
3773
- if (existsSync13(wsMemoryDir)) {
3774
- const destMemoryDir = resolve15(dest, "home/memory");
4142
+ if (existsSync14(wsMemoryDir)) {
4143
+ const destMemoryDir = resolve16(dest, "home/memory");
3775
4144
  const files = readdirSync3(wsMemoryDir).filter((f) => f.endsWith(".md"));
3776
4145
  for (const file of files) {
3777
- cpSync(resolve15(wsMemoryDir, file), resolve15(destMemoryDir, file));
4146
+ cpSync(resolve16(wsMemoryDir, file), resolve16(destMemoryDir, file));
3778
4147
  }
3779
4148
  dailyLogCount = files.length;
3780
4149
  }
@@ -3785,7 +4154,7 @@ ${user.trimEnd()}
3785
4154
  } catch (err) {
3786
4155
  logger_default.warn(`failed to set template hash for ${name}`, logger_default.errorData(err));
3787
4156
  }
3788
- const homeDir = resolve15(dest, "home");
4157
+ const homeDir = resolve16(dest, "home");
3789
4158
  ensureVoluteGroup();
3790
4159
  createMindUser(name, homeDir);
3791
4160
  chownMindDir(dest, name);
@@ -3793,20 +4162,20 @@ ${user.trimEnd()}
3793
4162
  if (!hasMemory && dailyLogCount > 0) {
3794
4163
  await consolidateMemory(dest);
3795
4164
  }
3796
- const env = isIsolationEnabled() ? { ...process.env, HOME: resolve15(dest, "home") } : void 0;
4165
+ const env = isIsolationEnabled() ? { ...process.env, HOME: resolve16(dest, "home") } : void 0;
3797
4166
  await gitExec(["init"], { cwd: dest, mindName: name, env });
3798
4167
  await configureGitIdentity(name, { cwd: dest, mindName: name, env });
3799
4168
  await gitExec(["add", "-A"], { cwd: dest, mindName: name, env });
3800
4169
  await gitExec(["commit", "-m", "import from OpenClaw"], { cwd: dest, mindName: name, env });
3801
- const sessionFile = body.sessionPath ? resolve15(body.sessionPath) : findOpenClawSession(wsDir);
3802
- if (sessionFile && existsSync13(sessionFile)) {
4170
+ const sessionFile = body.sessionPath ? resolve16(body.sessionPath) : findOpenClawSession(wsDir);
4171
+ if (sessionFile && existsSync14(sessionFile)) {
3803
4172
  if (template === "pi") {
3804
4173
  importPiSession(sessionFile, dest);
3805
4174
  } else if (template === "claude") {
3806
4175
  const sessionId = convertSession({ sessionPath: sessionFile, projectDir: dest });
3807
- const mindRuntimeDir = resolve15(dest, ".mind");
3808
- mkdirSync8(mindRuntimeDir, { recursive: true });
3809
- writeFileSync8(resolve15(mindRuntimeDir, "session.json"), JSON.stringify({ sessionId }));
4176
+ const mindRuntimeDir = resolve16(dest, ".mind");
4177
+ mkdirSync9(mindRuntimeDir, { recursive: true });
4178
+ writeFileSync9(resolve16(mindRuntimeDir, "session.json"), JSON.stringify({ sessionId }));
3810
4179
  }
3811
4180
  }
3812
4181
  importOpenClawConnectors(name, dest);
@@ -3821,14 +4190,14 @@ ${user.trimEnd()}
3821
4190
  );
3822
4191
  return c.json({ ok: true, name, port, message: `Imported mind: ${name} (port ${port})` });
3823
4192
  } catch (err) {
3824
- if (existsSync13(dest)) rmSync4(dest, { recursive: true, force: true });
4193
+ if (existsSync14(dest)) rmSync5(dest, { recursive: true, force: true });
3825
4194
  try {
3826
4195
  await removeMind(name);
3827
4196
  } catch {
3828
4197
  }
3829
4198
  return c.json({ error: err instanceof Error ? err.message : "Failed to import mind" }, 500);
3830
4199
  } finally {
3831
- rmSync4(composedDir, { recursive: true, force: true });
4200
+ rmSync5(composedDir, { recursive: true, force: true });
3832
4201
  }
3833
4202
  }).get("/", async (c) => {
3834
4203
  const entries = await readRegistry();
@@ -3837,7 +4206,7 @@ ${user.trimEnd()}
3837
4206
  const db = await getDb();
3838
4207
  const lastActiveRows = await db.select({
3839
4208
  mind: mindHistory.mind,
3840
- lastActiveAt: sql`MAX(${mindHistory.created_at})`
4209
+ lastActiveAt: sql2`MAX(${mindHistory.created_at})`
3841
4210
  }).from(mindHistory).groupBy(mindHistory.mind);
3842
4211
  lastActiveMap = new Map(lastActiveRows.map((r) => [r.mind, r.lastActiveAt]));
3843
4212
  } catch {
@@ -3845,7 +4214,7 @@ ${user.trimEnd()}
3845
4214
  const minds = await Promise.all(
3846
4215
  entries.map(async (entry) => {
3847
4216
  const mindStatus = await getMindStatus(entry.name, entry.port);
3848
- const hasPages = existsSync13(resolve15(mindDir(entry.name), "home", "public", "pages"));
4217
+ const hasPages = existsSync14(resolve16(mindDir(entry.name), "home", "public", "pages"));
3849
4218
  return {
3850
4219
  ...entry,
3851
4220
  ...mindStatus,
@@ -3864,7 +4233,7 @@ ${user.trimEnd()}
3864
4233
  const entry = await findMind(name);
3865
4234
  if (!entry) return c.json({ error: "Mind not found" }, 404);
3866
4235
  const dir = entry.dir ?? mindDir(entry.parent ?? name);
3867
- if (!existsSync13(dir)) return c.json({ error: "Mind directory missing" }, 404);
4236
+ if (!existsSync14(dir)) return c.json({ error: "Mind directory missing" }, 404);
3868
4237
  const mindStatus = await getMindStatus(name, entry.port);
3869
4238
  const variants = await findVariants(name);
3870
4239
  const manager = getMindManager();
@@ -3878,7 +4247,7 @@ ${user.trimEnd()}
3878
4247
  return { name: s.name, port: s.port, status: variantStatus };
3879
4248
  })
3880
4249
  );
3881
- const hasPages = existsSync13(resolve15(mindDir(name), "home", "public", "pages"));
4250
+ const hasPages = existsSync14(resolve16(mindDir(name), "home", "public", "pages"));
3882
4251
  return c.json({ ...entry, ...mindStatus, variants: variantStatuses, hasPages });
3883
4252
  }).post("/:name/start", requireSelf(), async (c) => {
3884
4253
  const name = c.req.param("name");
@@ -3889,7 +4258,7 @@ ${user.trimEnd()}
3889
4258
  if (!entry.dir) return c.json({ error: `Variant ${name} has no directory` }, 404);
3890
4259
  } else {
3891
4260
  const dir = mindDir(name);
3892
- if (!existsSync13(dir)) return c.json({ error: "Mind directory missing" }, 404);
4261
+ if (!existsSync14(dir)) return c.json({ error: "Mind directory missing" }, 404);
3893
4262
  }
3894
4263
  if (getMindManager().isRunning(name)) {
3895
4264
  return c.json({ error: "Mind already running" }, 409);
@@ -3910,7 +4279,7 @@ ${user.trimEnd()}
3910
4279
  if (!entry.dir) return c.json({ error: `Variant ${name} has no directory` }, 404);
3911
4280
  } else {
3912
4281
  const dir = mindDir(name);
3913
- if (!existsSync13(dir)) return c.json({ error: "Mind directory missing" }, 404);
4282
+ if (!existsSync14(dir)) return c.json({ error: "Mind directory missing" }, 404);
3914
4283
  }
3915
4284
  let context;
3916
4285
  const contentType = c.req.header("content-type");
@@ -3925,8 +4294,8 @@ ${user.trimEnd()}
3925
4294
  const manager = getMindManager();
3926
4295
  try {
3927
4296
  if (context?.type === "reload") {
3928
- const { getSleepManagerIfReady } = await import("./sleep-manager-WMVG2VCL.js");
3929
- const sleepState = getSleepManagerIfReady()?.getState(name);
4297
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-O7YQFCV5.js");
4298
+ const sleepState = getSleepManagerIfReady2()?.getState(name);
3930
4299
  if (sleepState?.sleeping) {
3931
4300
  logger_default.info(`skipping reload for ${name} during sleep \u2014 will apply on next wake`);
3932
4301
  return c.json({ ok: true, deferred: true, port: targetPort });
@@ -3945,7 +4314,7 @@ ${user.trimEnd()}
3945
4314
  const variantEntry = await findMind(mergeVariantName);
3946
4315
  if (variantEntry && variantEntry.parent === baseName && variantEntry.dir && variantEntry.branch) {
3947
4316
  const projectRoot = mindDir(baseName);
3948
- if (existsSync13(variantEntry.dir)) {
4317
+ if (existsSync14(variantEntry.dir)) {
3949
4318
  const status = (await gitExec(["status", "--porcelain"], { cwd: variantEntry.dir })).trim();
3950
4319
  if (status) {
3951
4320
  try {
@@ -3987,7 +4356,7 @@ ${user.trimEnd()}
3987
4356
  if (context?.type === "sprouted" && !entry.parent) {
3988
4357
  try {
3989
4358
  const db = await getDb();
3990
- const activeConvs = await db.select({ id: conversations.id }).from(conversations).where(eq4(conversations.mind_name, baseName)).all();
4359
+ const activeConvs = await db.select({ id: conversations.id }).from(conversations).where(eq5(conversations.mind_name, baseName)).all();
3991
4360
  for (const conv of activeConvs) {
3992
4361
  await addMessage(conv.id, "assistant", "system", [
3993
4362
  { type: "text", text: "[seed has sprouted]" }
@@ -4020,16 +4389,16 @@ ${user.trimEnd()}
4020
4389
  const name = c.req.param("name");
4021
4390
  const entry = await findMind(name);
4022
4391
  if (!entry) return c.json({ error: "Mind not found" }, 404);
4023
- const { getSleepManagerIfReady } = await import("./sleep-manager-WMVG2VCL.js");
4024
- const sm = getSleepManagerIfReady();
4392
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-O7YQFCV5.js");
4393
+ const sm = getSleepManagerIfReady2();
4025
4394
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
4026
4395
  return c.json(sm.getState(name));
4027
4396
  }).post("/:name/sleep", requireSelf(), async (c) => {
4028
4397
  const name = c.req.param("name");
4029
4398
  const entry = await findMind(name);
4030
4399
  if (!entry) return c.json({ error: "Mind not found" }, 404);
4031
- const { getSleepManagerIfReady } = await import("./sleep-manager-WMVG2VCL.js");
4032
- const sm = getSleepManagerIfReady();
4400
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-O7YQFCV5.js");
4401
+ const sm = getSleepManagerIfReady2();
4033
4402
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
4034
4403
  if (sm.isSleeping(name)) return c.json({ error: "Mind is already sleeping" }, 409);
4035
4404
  const body = await c.req.json().catch(() => ({}));
@@ -4048,8 +4417,8 @@ ${user.trimEnd()}
4048
4417
  const name = c.req.param("name");
4049
4418
  const entry = await findMind(name);
4050
4419
  if (!entry) return c.json({ error: "Mind not found" }, 404);
4051
- const { getSleepManagerIfReady } = await import("./sleep-manager-WMVG2VCL.js");
4052
- const sm = getSleepManagerIfReady();
4420
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-O7YQFCV5.js");
4421
+ const sm = getSleepManagerIfReady2();
4053
4422
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
4054
4423
  const sleepState = sm.getState(name);
4055
4424
  if (!sleepState.sleeping) return c.json({ error: "Mind is not sleeping" }, 409);
@@ -4063,8 +4432,8 @@ ${user.trimEnd()}
4063
4432
  const name = c.req.param("name");
4064
4433
  const entry = await findMind(name);
4065
4434
  if (!entry) return c.json({ error: "Mind not found" }, 404);
4066
- const { getSleepManagerIfReady } = await import("./sleep-manager-WMVG2VCL.js");
4067
- const sm = getSleepManagerIfReady();
4435
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-O7YQFCV5.js");
4436
+ const sm = getSleepManagerIfReady2();
4068
4437
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
4069
4438
  const flushed = await sm.flushQueuedMessages(name);
4070
4439
  return c.json({ ok: true, flushed });
@@ -4101,11 +4470,11 @@ ${user.trimEnd()}
4101
4470
  await removeMind(name);
4102
4471
  await deleteMindUser2(name);
4103
4472
  const state = stateDir(name);
4104
- if (existsSync13(state)) {
4105
- rmSync4(state, { recursive: true, force: true });
4473
+ if (existsSync14(state)) {
4474
+ rmSync5(state, { recursive: true, force: true });
4106
4475
  }
4107
- if (force && existsSync13(dir)) {
4108
- rmSync4(dir, { recursive: true, force: true });
4476
+ if (force && existsSync14(dir)) {
4477
+ rmSync5(dir, { recursive: true, force: true });
4109
4478
  deleteMindUser(name);
4110
4479
  }
4111
4480
  fireWebhook({
@@ -4119,7 +4488,7 @@ ${user.trimEnd()}
4119
4488
  const entry = await findMind(mindName);
4120
4489
  if (!entry) return c.json({ error: "Mind not found" }, 404);
4121
4490
  const dir = mindDir(mindName);
4122
- if (!existsSync13(dir)) return c.json({ error: "Mind directory missing" }, 404);
4491
+ if (!existsSync14(dir)) return c.json({ error: "Mind directory missing" }, 404);
4123
4492
  let body = {};
4124
4493
  try {
4125
4494
  body = await c.req.json();
@@ -4128,16 +4497,16 @@ ${user.trimEnd()}
4128
4497
  const template = body.template ?? entry.template ?? "claude";
4129
4498
  const UPGRADE_BRANCH = "upgrade";
4130
4499
  const upgradeVariantName = `${mindName}-upgrade`;
4131
- const worktreeDir = resolve15(dir, ".variants", UPGRADE_BRANCH);
4500
+ const worktreeDir = resolve16(dir, ".variants", UPGRADE_BRANCH);
4132
4501
  if (body.abort) {
4133
- if (!existsSync13(worktreeDir)) {
4502
+ if (!existsSync14(worktreeDir)) {
4134
4503
  return c.json({ error: "No upgrade in progress" }, 400);
4135
4504
  }
4136
4505
  try {
4137
4506
  try {
4138
- const gitDirContent = readFileSync11(resolve15(worktreeDir, ".git"), "utf-8").trim();
4507
+ const gitDirContent = readFileSync12(resolve16(worktreeDir, ".git"), "utf-8").trim();
4139
4508
  const gitDir = gitDirContent.replace("gitdir: ", "");
4140
- if (existsSync13(resolve15(gitDir, "MERGE_HEAD"))) {
4509
+ if (existsSync14(resolve16(gitDir, "MERGE_HEAD"))) {
4141
4510
  await gitExec(["merge", "--abort"], { cwd: worktreeDir });
4142
4511
  }
4143
4512
  } catch {
@@ -4156,7 +4525,7 @@ ${user.trimEnd()}
4156
4525
  }
4157
4526
  }
4158
4527
  if (body.continue) {
4159
- if (!existsSync13(worktreeDir)) {
4528
+ if (!existsSync14(worktreeDir)) {
4160
4529
  return c.json({ error: "No upgrade in progress" }, 400);
4161
4530
  }
4162
4531
  const status = await gitExec(["status", "--porcelain"], { cwd: worktreeDir });
@@ -4195,7 +4564,7 @@ ${user.trimEnd()}
4195
4564
  }
4196
4565
  }
4197
4566
  if (body.accept) {
4198
- if (!existsSync13(worktreeDir)) {
4567
+ if (!existsSync14(worktreeDir)) {
4199
4568
  return c.json({ error: "No upgrade in progress" }, 400);
4200
4569
  }
4201
4570
  const variantEntry = await findMind(upgradeVariantName);
@@ -4218,7 +4587,7 @@ ${user.trimEnd()}
4218
4587
  try {
4219
4588
  await gitExec(["add", "-A"], { cwd: dir });
4220
4589
  await gitExec(["commit", "-m", "Auto-commit before upgrade merge"], { cwd: dir });
4221
- } catch (e) {
4590
+ } catch (_e) {
4222
4591
  return c.json({ error: "Failed to auto-commit main changes before merge" }, 500);
4223
4592
  }
4224
4593
  }
@@ -4253,22 +4622,22 @@ ${user.trimEnd()}
4253
4622
  }
4254
4623
  return c.json({ ok: true });
4255
4624
  }
4256
- if (existsSync13(worktreeDir)) {
4625
+ if (existsSync14(worktreeDir)) {
4257
4626
  return c.json(
4258
4627
  { error: "Upgrade variant already exists. Use continue or delete it first." },
4259
4628
  409
4260
4629
  );
4261
4630
  }
4262
- if (!existsSync13(resolve15(dir, ".git"))) {
4631
+ if (!existsSync14(resolve16(dir, ".git"))) {
4263
4632
  try {
4264
- const env = isIsolationEnabled() ? { ...process.env, HOME: resolve15(dir, "home") } : void 0;
4633
+ const env = isIsolationEnabled() ? { ...process.env, HOME: resolve16(dir, "home") } : void 0;
4265
4634
  await gitExec(["init"], { cwd: dir, mindName, env });
4266
4635
  await configureGitIdentity(mindName, { cwd: dir, mindName, env });
4267
4636
  await gitExec(["add", "-A"], { cwd: dir, mindName, env });
4268
4637
  await gitExec(["commit", "-m", "initial commit"], { cwd: dir, mindName, env });
4269
4638
  chownMindDir(dir, mindName);
4270
4639
  } catch (err) {
4271
- rmSync4(resolve15(dir, ".git"), { recursive: true, force: true });
4640
+ rmSync5(resolve16(dir, ".git"), { recursive: true, force: true });
4272
4641
  return c.json(
4273
4642
  {
4274
4643
  error: `Git initialization failed: ${err instanceof Error ? err.message : String(err)}`
@@ -4282,7 +4651,7 @@ ${user.trimEnd()}
4282
4651
  await gitExec(["branch", "-D", UPGRADE_BRANCH], { cwd: dir });
4283
4652
  } catch {
4284
4653
  }
4285
- if (!existsSync13(resolve15(dir, "home", "shared"))) {
4654
+ if (!existsSync14(resolve16(dir, "home", "shared"))) {
4286
4655
  try {
4287
4656
  await addSharedWorktree(mindName, dir);
4288
4657
  } catch (err) {
@@ -4293,9 +4662,9 @@ ${user.trimEnd()}
4293
4662
  }
4294
4663
  }
4295
4664
  await updateTemplateBranch(dir, template, mindName);
4296
- const parentDir = resolve15(dir, ".variants");
4297
- if (!existsSync13(parentDir)) {
4298
- mkdirSync8(parentDir, { recursive: true });
4665
+ const parentDir = resolve16(dir, ".variants");
4666
+ if (!existsSync14(parentDir)) {
4667
+ mkdirSync9(parentDir, { recursive: true });
4299
4668
  }
4300
4669
  await gitExec(["worktree", "add", "-b", UPGRADE_BRANCH, worktreeDir], { cwd: dir });
4301
4670
  const hasConflicts = await mergeTemplateBranch(worktreeDir);
@@ -4332,8 +4701,8 @@ ${user.trimEnd()}
4332
4701
  if (!entry) return c.json({ error: "Mind not found" }, 404);
4333
4702
  const baseName = entry.parent ?? name;
4334
4703
  try {
4335
- const { getSleepManagerIfReady } = await import("./sleep-manager-WMVG2VCL.js");
4336
- const sm = getSleepManagerIfReady();
4704
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-O7YQFCV5.js");
4705
+ const sm = getSleepManagerIfReady2();
4337
4706
  if (sm?.isSleeping(baseName)) {
4338
4707
  const body2 = await c.req.text();
4339
4708
  let parsed2 = null;
@@ -4430,7 +4799,7 @@ ${user.trimEnd()}
4430
4799
  if (seedEntry?.stage === "seed") {
4431
4800
  try {
4432
4801
  const db = await getDb();
4433
- const countResult = await db.select({ count: sql`count(*)` }).from(mindHistory).where(eq4(mindHistory.mind, baseName));
4802
+ const countResult = await db.select({ count: sql2`count(*)` }).from(mindHistory).where(eq5(mindHistory.mind, baseName));
4434
4803
  const msgCount = countResult[0]?.count ?? 0;
4435
4804
  if (msgCount >= 10 && msgCount % 10 === 0) {
4436
4805
  const nudge = "\n[You've been exploring for a while. Whenever you feel ready, write your SOUL.md and MEMORY.md, then run volute mind sprout.]";
@@ -4464,6 +4833,34 @@ ${user.trimEnd()}
4464
4833
  logger_default.error(`delivery failed for ${name}`, logger_default.errorData(err));
4465
4834
  });
4466
4835
  return c.json({ ok: true });
4836
+ }).get("/:name/conversations", async (c) => {
4837
+ const name = c.req.param("name");
4838
+ const entry = await findMind(name);
4839
+ if (!entry) return c.json({ error: "Mind not found" }, 404);
4840
+ const convs = await listConversationsForMind(name);
4841
+ return c.json(convs);
4842
+ }).get("/:name/conversations/:convId/messages", async (c) => {
4843
+ const name = c.req.param("name");
4844
+ const convId = c.req.param("convId");
4845
+ const entry = await findMind(name);
4846
+ if (!entry) return c.json({ error: "Mind not found" }, 404);
4847
+ const belongs = await isConversationForMind(name, convId);
4848
+ if (!belongs) {
4849
+ return c.json({ error: "Conversation not found" }, 404);
4850
+ }
4851
+ const beforeStr = c.req.query("before");
4852
+ const limitStr = c.req.query("limit");
4853
+ if (!beforeStr && !limitStr) {
4854
+ const msgs = await getMessages(convId);
4855
+ return c.json({ items: msgs, hasMore: false });
4856
+ }
4857
+ const before = beforeStr ? parseInt(beforeStr, 10) : void 0;
4858
+ const limit = limitStr ? parseInt(limitStr, 10) : void 0;
4859
+ if (before !== void 0 && Number.isNaN(before) || limit !== void 0 && Number.isNaN(limit)) {
4860
+ return c.json({ error: "Invalid pagination parameters" }, 400);
4861
+ }
4862
+ const result = await getMessagesPaginated(convId, { before, limit });
4863
+ return c.json({ items: result.messages, hasMore: result.hasMore });
4467
4864
  }).get("/:name/budget", async (c) => {
4468
4865
  const name = c.req.param("name");
4469
4866
  const baseName = await getBaseName(name);
@@ -4475,13 +4872,13 @@ ${user.trimEnd()}
4475
4872
  const entry = await findMind(name);
4476
4873
  if (!entry) return c.json({ error: "Mind not found" }, 404);
4477
4874
  const dir = mindDir(name);
4478
- if (!existsSync13(dir)) return c.json({ error: "Mind directory missing" }, 404);
4875
+ if (!existsSync14(dir)) return c.json({ error: "Mind directory missing" }, 404);
4479
4876
  let config = readVoluteConfig(dir);
4480
4877
  if (!config && entry.template === "pi") {
4481
- const piConfigPath = resolve15(dir, "home/.config/config.json");
4482
- if (existsSync13(piConfigPath)) {
4878
+ const piConfigPath = resolve16(dir, "home/.config/config.json");
4879
+ if (existsSync14(piConfigPath)) {
4483
4880
  try {
4484
- config = JSON.parse(readFileSync11(piConfigPath, "utf-8"));
4881
+ config = JSON.parse(readFileSync12(piConfigPath, "utf-8"));
4485
4882
  } catch {
4486
4883
  }
4487
4884
  }
@@ -4518,7 +4915,7 @@ ${user.trimEnd()}
4518
4915
  const entry = await findMind(name);
4519
4916
  if (!entry) return c.json({ error: "Mind not found" }, 404);
4520
4917
  const dir = mindDir(name);
4521
- if (!existsSync13(dir)) return c.json({ error: "Mind directory missing" }, 404);
4918
+ if (!existsSync14(dir)) return c.json({ error: "Mind directory missing" }, 404);
4522
4919
  const body = c.req.valid("json");
4523
4920
  const existing = readVoluteConfig(dir) ?? {};
4524
4921
  if (body.model !== void 0) existing.model = body.model;
@@ -4555,6 +4952,20 @@ ${user.trimEnd()}
4555
4952
  logger_default.error(`failed to get pending deliveries for ${baseName}`, logger_default.errorData(err));
4556
4953
  return c.json({ error: "Failed to retrieve pending messages" }, 500);
4557
4954
  }
4955
+ }).post("/:name/ai/complete", requireSelf(), async (c) => {
4956
+ const body = await c.req.json();
4957
+ if (!body.systemPrompt || !body.message) {
4958
+ return c.json({ error: "systemPrompt and message required" }, 400);
4959
+ }
4960
+ const { aiComplete: aiCompleteFn, isAiConfigured } = await import("./ai-service-VAJT5UBS.js");
4961
+ if (!isAiConfigured()) {
4962
+ return c.json({ error: "AI service not configured" }, 503);
4963
+ }
4964
+ const text = await aiCompleteFn(body.systemPrompt, body.message, body.model);
4965
+ if (text == null) {
4966
+ return c.json({ error: "AI completion failed" }, 502);
4967
+ }
4968
+ return c.json({ text });
4558
4969
  }).post("/:name/events", requireSelf(), async (c) => {
4559
4970
  const name = c.req.param("name");
4560
4971
  const baseName = await getBaseName(name);
@@ -4568,8 +4979,9 @@ ${user.trimEnd()}
4568
4979
  return c.json({ error: "type required" }, 400);
4569
4980
  }
4570
4981
  const db = await getDb();
4982
+ let insertedId;
4571
4983
  try {
4572
- await db.insert(mindHistory).values({
4984
+ const result = await db.insert(mindHistory).values({
4573
4985
  mind: baseName,
4574
4986
  type: body.type,
4575
4987
  session: body.session ?? null,
@@ -4577,7 +4989,8 @@ ${user.trimEnd()}
4577
4989
  message_id: body.messageId ?? null,
4578
4990
  content: body.content ?? null,
4579
4991
  metadata: body.metadata ? JSON.stringify(body.metadata) : null
4580
- });
4992
+ }).returning({ id: mindHistory.id });
4993
+ insertedId = result[0]?.id;
4581
4994
  } catch (err) {
4582
4995
  logger_default.error(`failed to persist event for ${baseName}`, logger_default.errorData(err));
4583
4996
  }
@@ -4608,6 +5021,11 @@ ${user.trimEnd()}
4608
5021
  logger_default.error(`delivery manager sessionDone failed for ${baseName}`, logger_default.errorData(err));
4609
5022
  }
4610
5023
  }
5024
+ if (insertedId != null) {
5025
+ summarizeTurn(baseName, body.session, body.channel, insertedId).catch((err) => {
5026
+ logger_default.error("turn summarization failed", logger_default.errorData(err));
5027
+ });
5028
+ }
4611
5029
  }
4612
5030
  if (body.type === "usage" && body.metadata) {
4613
5031
  const inputTokens = body.metadata.input_tokens ?? 0;
@@ -4683,432 +5101,68 @@ ${user.trimEnd()}
4683
5101
  const db = await getDb();
4684
5102
  try {
4685
5103
  await db.insert(mindHistory).values({
4686
- mind: baseName,
4687
- type: "outbound",
4688
- channel: body.channel,
4689
- sender: body.sender ?? baseName,
4690
- content: body.content
4691
- });
4692
- } catch (err) {
4693
- logger_default.error(`failed to persist external send for ${baseName}`, logger_default.errorData(err));
4694
- return c.json({ error: "Failed to persist" }, 500);
4695
- }
4696
- return c.json({ ok: true });
4697
- }).get("/:name/history/sessions", async (c) => {
4698
- const name = c.req.param("name");
4699
- const db = await getDb();
4700
- const rows = await db.select({
4701
- session: mindHistory.session,
4702
- started_at: sql`MIN(${mindHistory.created_at})`,
4703
- event_count: sql`COUNT(*)`,
4704
- message_count: sql`SUM(CASE WHEN ${mindHistory.type} IN ('inbound','outbound') THEN 1 ELSE 0 END)`,
4705
- tool_count: sql`SUM(CASE WHEN ${mindHistory.type}='tool_use' THEN 1 ELSE 0 END)`
4706
- }).from(mindHistory).where(and3(eq4(mindHistory.mind, name), sql`${mindHistory.session} IS NOT NULL`)).groupBy(mindHistory.session).orderBy(sql`MIN(${mindHistory.created_at}) DESC`);
4707
- return c.json(rows);
4708
- }).get("/:name/history/channels", async (c) => {
4709
- const name = c.req.param("name");
4710
- const db = await getDb();
4711
- const rows = await db.selectDistinct({ channel: mindHistory.channel }).from(mindHistory).where(eq4(mindHistory.mind, name));
4712
- return c.json(rows.map((r) => r.channel));
4713
- }).get("/:name/history/export", async (c) => {
4714
- const name = c.req.param("name");
4715
- if (!await findMind(name)) return c.json({ error: "Mind not found" }, 404);
4716
- const db = await getDb();
4717
- const rows = await db.select().from(mindHistory).where(eq4(mindHistory.mind, name));
4718
- return c.json(rows);
4719
- }).get("/:name/history", async (c) => {
4720
- const name = c.req.param("name");
4721
- const channel = c.req.query("channel");
4722
- const session = c.req.query("session");
4723
- const full = c.req.query("full") === "true";
4724
- const limit = Math.min(Math.max(parseInt(c.req.query("limit") ?? "50", 10) || 50, 1), 200);
4725
- const offset = Math.max(parseInt(c.req.query("offset") ?? "0", 10) || 0, 0);
4726
- const db = await getDb();
4727
- const conditions = [eq4(mindHistory.mind, name)];
4728
- if (channel) {
4729
- conditions.push(eq4(mindHistory.channel, channel));
4730
- }
4731
- if (session) {
4732
- conditions.push(eq4(mindHistory.session, session));
4733
- }
4734
- if (!full) {
4735
- conditions.push(sql`${mindHistory.type} IN ('inbound', 'outbound')`);
4736
- }
4737
- const rows = await db.select().from(mindHistory).where(and3(...conditions)).orderBy(desc2(mindHistory.created_at)).limit(limit).offset(offset);
4738
- return c.json(rows);
4739
- });
4740
- var minds_default = app11;
4741
-
4742
- // src/web/api/notes.ts
4743
- import { zValidator as zValidator5 } from "@hono/zod-validator";
4744
- import { Hono as Hono12 } from "hono";
4745
- import { z as z5 } from "zod";
4746
-
4747
- // src/lib/notes.ts
4748
- import { and as and4, count, desc as desc3, eq as eq5, inArray, sql as sql2 } from "drizzle-orm";
4749
- async function createNote(authorId, title, content, replyToId) {
4750
- const db = await getDb();
4751
- let slug = slugify(title) || "untitled";
4752
- const existing = await db.select({ slug: notes.slug }).from(notes).where(eq5(notes.author_id, authorId)).all();
4753
- const existingSlugs = new Set(existing.map((r) => r.slug));
4754
- if (existingSlugs.has(slug)) {
4755
- let i = 2;
4756
- while (existingSlugs.has(`${slug}-${i}`)) i++;
4757
- slug = `${slug}-${i}`;
4758
- }
4759
- const [row] = await db.insert(notes).values({ author_id: authorId, title, slug, content, reply_to_id: replyToId ?? null }).returning();
4760
- const author = await db.select().from(users).where(eq5(users.id, authorId)).get();
4761
- return {
4762
- ...row,
4763
- author_username: author?.username ?? "unknown",
4764
- author_display_name: author?.display_name ?? null,
4765
- comment_count: 0
4766
- };
4767
- }
4768
- async function getNote(authorUsername, slug) {
4769
- const db = await getDb();
4770
- const row = await db.select({
4771
- id: notes.id,
4772
- author_id: notes.author_id,
4773
- title: notes.title,
4774
- slug: notes.slug,
4775
- content: notes.content,
4776
- reply_to_id: notes.reply_to_id,
4777
- created_at: notes.created_at,
4778
- updated_at: notes.updated_at,
4779
- author_username: users.username,
4780
- author_display_name: users.display_name
4781
- }).from(notes).innerJoin(users, eq5(notes.author_id, users.id)).where(and4(eq5(users.username, authorUsername), eq5(notes.slug, slug))).get();
4782
- if (!row) return null;
4783
- const comments = await getComments(row.id);
4784
- const reactions = await getReactions(row.id);
4785
- let reply_to = null;
4786
- if (row.reply_to_id) {
4787
- const parent = await db.select({
4788
- title: notes.title,
4789
- slug: notes.slug,
4790
- author_username: users.username
4791
- }).from(notes).innerJoin(users, eq5(notes.author_id, users.id)).where(eq5(notes.id, row.reply_to_id)).get();
4792
- if (parent) {
4793
- reply_to = parent;
4794
- }
4795
- }
4796
- const replies = await db.select({
4797
- author_username: users.username,
4798
- slug: notes.slug,
4799
- title: notes.title,
4800
- created_at: notes.created_at
4801
- }).from(notes).innerJoin(users, eq5(notes.author_id, users.id)).where(eq5(notes.reply_to_id, row.id)).orderBy(notes.created_at).all();
4802
- return { ...row, comment_count: comments.length, comments, reactions, reply_to, replies };
4803
- }
4804
- async function listNotes(opts) {
4805
- const db = await getDb();
4806
- const limit = opts?.limit ?? 50;
4807
- const offset = opts?.offset ?? 0;
4808
- const conditions = [];
4809
- if (opts?.authorUsername) {
4810
- conditions.push(eq5(users.username, opts.authorUsername));
4811
- }
4812
- const rows = await db.select({
4813
- id: notes.id,
4814
- author_id: notes.author_id,
4815
- title: notes.title,
4816
- slug: notes.slug,
4817
- content: notes.content,
4818
- reply_to_id: notes.reply_to_id,
4819
- created_at: notes.created_at,
4820
- updated_at: notes.updated_at,
4821
- author_username: users.username,
4822
- author_display_name: users.display_name
4823
- }).from(notes).innerJoin(users, eq5(notes.author_id, users.id)).where(conditions.length > 0 ? and4(...conditions) : void 0).orderBy(desc3(notes.created_at)).limit(limit).offset(offset).all();
4824
- const noteIds = rows.map((r) => r.id);
4825
- if (noteIds.length === 0) return [];
4826
- const commentCounts = await db.select({
4827
- note_id: noteComments.note_id,
4828
- count: count()
4829
- }).from(noteComments).where(inArray(noteComments.note_id, noteIds)).groupBy(noteComments.note_id).all();
4830
- const countMap = new Map(commentCounts.map((r) => [r.note_id, r.count]));
4831
- const allReactions = await db.select({
4832
- note_id: noteReactions.note_id,
4833
- emoji: noteReactions.emoji,
4834
- count: count()
4835
- }).from(noteReactions).where(inArray(noteReactions.note_id, noteIds)).groupBy(noteReactions.note_id, noteReactions.emoji).all();
4836
- const reactionMap = /* @__PURE__ */ new Map();
4837
- for (const r of allReactions) {
4838
- if (!reactionMap.has(r.note_id)) reactionMap.set(r.note_id, []);
4839
- reactionMap.get(r.note_id).push({ emoji: r.emoji, count: r.count });
4840
- }
4841
- const replyToIds = [...new Set(rows.filter((r) => r.reply_to_id).map((r) => r.reply_to_id))];
4842
- const replyToMap = /* @__PURE__ */ new Map();
4843
- if (replyToIds.length > 0) {
4844
- const parents = await db.select({
4845
- id: notes.id,
4846
- title: notes.title,
4847
- slug: notes.slug,
4848
- author_username: users.username
4849
- }).from(notes).innerJoin(users, eq5(notes.author_id, users.id)).where(inArray(notes.id, replyToIds)).all();
4850
- for (const parent of parents) {
4851
- replyToMap.set(parent.id, {
4852
- author_username: parent.author_username,
4853
- slug: parent.slug,
4854
- title: parent.title
4855
- });
4856
- }
4857
- }
4858
- return rows.map((r) => {
4859
- const reactions = reactionMap.get(r.id);
4860
- const topReactions = reactions ? reactions.sort((a, b) => b.count - a.count).slice(0, 3).map((rx) => ({ ...rx, usernames: [] })) : void 0;
4861
- return {
4862
- ...r,
4863
- comment_count: countMap.get(r.id) ?? 0,
4864
- reactions: topReactions,
4865
- reply_to: r.reply_to_id ? replyToMap.get(r.reply_to_id) ?? null : null
4866
- };
4867
- });
4868
- }
4869
- async function updateNote(authorUsername, slug, updates) {
4870
- const db = await getDb();
4871
- const existing = await db.select({ id: notes.id, author_id: notes.author_id }).from(notes).innerJoin(users, eq5(notes.author_id, users.id)).where(and4(eq5(users.username, authorUsername), eq5(notes.slug, slug))).get();
4872
- if (!existing) return null;
4873
- const set = { updated_at: sql2`(datetime('now'))` };
4874
- if (updates.title !== void 0) set.title = updates.title;
4875
- if (updates.content !== void 0) set.content = updates.content;
4876
- await db.update(notes).set(set).where(eq5(notes.id, existing.id));
4877
- return getNote(authorUsername, slug).then((n) => {
4878
- if (!n) return null;
4879
- const { comments, replies, ...note } = n;
4880
- return note;
4881
- });
4882
- }
4883
- async function deleteNote(authorUsername, slug, authorId) {
4884
- const db = await getDb();
4885
- const existing = await db.select({ id: notes.id, author_id: notes.author_id }).from(notes).innerJoin(users, eq5(notes.author_id, users.id)).where(and4(eq5(users.username, authorUsername), eq5(notes.slug, slug))).get();
4886
- if (!existing || existing.author_id !== authorId) return false;
4887
- await db.delete(notes).where(eq5(notes.id, existing.id));
4888
- return true;
4889
- }
4890
- async function addComment(noteId, authorId, content) {
4891
- const db = await getDb();
4892
- const [row] = await db.insert(noteComments).values({ note_id: noteId, author_id: authorId, content }).returning();
4893
- const author = await db.select().from(users).where(eq5(users.id, authorId)).get();
4894
- return {
4895
- ...row,
4896
- author_username: author?.username ?? "unknown",
4897
- author_display_name: author?.display_name ?? null
4898
- };
4899
- }
4900
- async function getComments(noteId) {
4901
- const db = await getDb();
4902
- return db.select({
4903
- id: noteComments.id,
4904
- note_id: noteComments.note_id,
4905
- author_id: noteComments.author_id,
4906
- content: noteComments.content,
4907
- created_at: noteComments.created_at,
4908
- author_username: users.username,
4909
- author_display_name: users.display_name
4910
- }).from(noteComments).innerJoin(users, eq5(noteComments.author_id, users.id)).where(eq5(noteComments.note_id, noteId)).orderBy(noteComments.created_at).all();
4911
- }
4912
- async function deleteComment(commentId, authorId) {
4913
- const db = await getDb();
4914
- const existing = await db.select({ id: noteComments.id, author_id: noteComments.author_id }).from(noteComments).where(eq5(noteComments.id, commentId)).get();
4915
- if (!existing || existing.author_id !== authorId) return false;
4916
- await db.delete(noteComments).where(eq5(noteComments.id, commentId));
4917
- return true;
4918
- }
4919
- async function toggleReaction(noteId, userId, emoji) {
4920
- const db = await getDb();
4921
- const existing = await db.select({ id: noteReactions.id }).from(noteReactions).where(
4922
- and4(
4923
- eq5(noteReactions.note_id, noteId),
4924
- eq5(noteReactions.user_id, userId),
4925
- eq5(noteReactions.emoji, emoji)
4926
- )
4927
- ).get();
4928
- if (existing) {
4929
- await db.delete(noteReactions).where(eq5(noteReactions.id, existing.id));
4930
- return { added: false };
5104
+ mind: baseName,
5105
+ type: "outbound",
5106
+ channel: body.channel,
5107
+ sender: body.sender ?? baseName,
5108
+ content: body.content
5109
+ });
5110
+ } catch (err) {
5111
+ logger_default.error(`failed to persist external send for ${baseName}`, logger_default.errorData(err));
5112
+ return c.json({ error: "Failed to persist" }, 500);
4931
5113
  }
4932
- await db.insert(noteReactions).values({ note_id: noteId, user_id: userId, emoji });
4933
- return { added: true };
4934
- }
4935
- async function getReactions(noteId) {
5114
+ return c.json({ ok: true });
5115
+ }).get("/:name/history/sessions", async (c) => {
5116
+ const name = c.req.param("name");
4936
5117
  const db = await getDb();
4937
5118
  const rows = await db.select({
4938
- emoji: noteReactions.emoji,
4939
- username: users.username
4940
- }).from(noteReactions).innerJoin(users, eq5(noteReactions.user_id, users.id)).where(eq5(noteReactions.note_id, noteId)).orderBy(noteReactions.emoji).all();
4941
- const grouped = /* @__PURE__ */ new Map();
4942
- for (const r of rows) {
4943
- if (!grouped.has(r.emoji)) grouped.set(r.emoji, []);
4944
- grouped.get(r.emoji).push(r.username);
4945
- }
4946
- return [...grouped.entries()].map(([emoji, usernames]) => ({
4947
- emoji,
4948
- count: usernames.length,
4949
- usernames
4950
- }));
4951
- }
4952
- async function resolveNoteId(authorSlug) {
4953
- const [author, slug] = authorSlug.split("/", 2);
4954
- if (!author || !slug) return null;
5119
+ session: mindHistory.session,
5120
+ started_at: sql2`MIN(${mindHistory.created_at})`,
5121
+ event_count: sql2`COUNT(*)`,
5122
+ message_count: sql2`SUM(CASE WHEN ${mindHistory.type} IN ('inbound','outbound') THEN 1 ELSE 0 END)`,
5123
+ tool_count: sql2`SUM(CASE WHEN ${mindHistory.type}='tool_use' THEN 1 ELSE 0 END)`
5124
+ }).from(mindHistory).where(and4(eq5(mindHistory.mind, name), sql2`${mindHistory.session} IS NOT NULL`)).groupBy(mindHistory.session).orderBy(sql2`MIN(${mindHistory.created_at}) DESC`);
5125
+ return c.json(rows);
5126
+ }).get("/:name/history/channels", async (c) => {
5127
+ const name = c.req.param("name");
4955
5128
  const db = await getDb();
4956
- const row = await db.select({ id: notes.id }).from(notes).innerJoin(users, eq5(notes.author_id, users.id)).where(and4(eq5(users.username, author), eq5(notes.slug, slug))).get();
4957
- return row?.id ?? null;
4958
- }
4959
-
4960
- // src/web/api/notes.ts
4961
- var createSchema = z5.object({
4962
- title: z5.string().min(1).max(200),
4963
- content: z5.string().min(1),
4964
- reply_to: z5.string().optional()
4965
- });
4966
- var updateSchema = z5.object({
4967
- title: z5.string().min(1).max(200).optional(),
4968
- content: z5.string().min(1).optional()
4969
- });
4970
- var commentSchema = z5.object({
4971
- content: z5.string().min(1)
4972
- });
4973
- var reactionSchema = z5.object({
4974
- emoji: z5.string().min(1).max(32)
4975
- });
4976
- function resolveUserId(c) {
4977
- const user = c.get("user");
4978
- if (user.id === 0) return null;
4979
- return { id: user.id, username: user.username };
4980
- }
4981
- var app12 = new Hono12().get("/", async (c) => {
4982
- const author = c.req.query("author");
4983
- const limit = c.req.query("limit") ? parseInt(c.req.query("limit"), 10) : void 0;
4984
- const offset = c.req.query("offset") ? parseInt(c.req.query("offset"), 10) : void 0;
4985
- const result = await listNotes({ authorUsername: author, limit, offset });
4986
- return c.json(result);
4987
- }).post("/", zValidator5("json", createSchema), async (c) => {
4988
- const actor = resolveUserId(c);
4989
- if (!actor) return c.json({ error: "Unauthorized" }, 401);
4990
- const { title, content, reply_to } = c.req.valid("json");
4991
- let replyToId;
4992
- if (reply_to) {
4993
- const id = await resolveNoteId(reply_to);
4994
- if (id === null) return c.json({ error: `Reply target not found: ${reply_to}` }, 404);
4995
- replyToId = id;
4996
- }
4997
- const note = await createNote(actor.id, title, content, replyToId);
4998
- const replyInfo = reply_to ? ` (in reply to ${reply_to})` : "";
4999
- announceToSystem(`${actor.username} published a note: ${title}${replyInfo}`).catch((err) => {
5000
- logger_default.warn("failed to announce note to #system", logger_default.errorData(err));
5001
- });
5002
- return c.json(note, 201);
5003
- }).get("/:author/:slug", async (c) => {
5004
- const { author, slug } = c.req.param();
5005
- const note = await getNote(author, slug);
5006
- if (!note) return c.json({ error: "Note not found" }, 404);
5007
- return c.json(note);
5008
- }).put("/:author/:slug", zValidator5("json", updateSchema), async (c) => {
5009
- const actor = resolveUserId(c);
5010
- if (!actor) return c.json({ error: "Unauthorized" }, 401);
5011
- const { author, slug } = c.req.param();
5012
- if (actor.username !== author) return c.json({ error: "Forbidden" }, 403);
5013
- const updates = c.req.valid("json");
5014
- const note = await updateNote(author, slug, updates);
5015
- if (!note) return c.json({ error: "Note not found" }, 404);
5016
- return c.json(note);
5017
- }).delete("/:author/:slug", async (c) => {
5018
- const actor = resolveUserId(c);
5019
- if (!actor) return c.json({ error: "Unauthorized" }, 401);
5020
- const { author, slug } = c.req.param();
5021
- const deleted = await deleteNote(author, slug, actor.id);
5022
- if (!deleted) return c.json({ error: "Note not found or not authorized" }, 404);
5023
- return c.json({ ok: true });
5024
- }).post("/:author/:slug/reactions", zValidator5("json", reactionSchema), async (c) => {
5025
- const actor = resolveUserId(c);
5026
- if (!actor) return c.json({ error: "Unauthorized" }, 401);
5027
- const { author, slug } = c.req.param();
5028
- const note = await getNote(author, slug);
5029
- if (!note) return c.json({ error: "Note not found" }, 404);
5030
- const { emoji } = c.req.valid("json");
5031
- const result = await toggleReaction(note.id, actor.id, emoji);
5032
- const reactions = await getReactions(note.id);
5033
- return c.json({ ...result, reactions });
5034
- }).post("/:author/:slug/comments", zValidator5("json", commentSchema), async (c) => {
5035
- const actor = resolveUserId(c);
5036
- if (!actor) return c.json({ error: "Unauthorized" }, 401);
5037
- const { author, slug } = c.req.param();
5038
- const note = await getNote(author, slug);
5039
- if (!note) return c.json({ error: "Note not found" }, 404);
5040
- const { content } = c.req.valid("json");
5041
- const comment = await addComment(note.id, actor.id, content);
5042
- return c.json(comment, 201);
5043
- }).delete("/:author/:slug/comments/:id", async (c) => {
5044
- const actor = resolveUserId(c);
5045
- if (!actor) return c.json({ error: "Unauthorized" }, 401);
5046
- const commentId = parseInt(c.req.param("id"), 10);
5047
- if (Number.isNaN(commentId)) return c.json({ error: "Invalid comment ID" }, 400);
5048
- const deleted = await deleteComment(commentId, actor.id);
5049
- if (!deleted) return c.json({ error: "Comment not found or not authorized" }, 404);
5050
- return c.json({ ok: true });
5051
- });
5052
- var notes_default = app12;
5053
-
5054
- // src/web/api/pages.ts
5055
- import { readFile as readFile2, stat as stat2 } from "fs/promises";
5056
- import { extname as extname3, resolve as resolve16 } from "path";
5057
- import { Hono as Hono13 } from "hono";
5058
- var MIME_TYPES = {
5059
- ".html": "text/html",
5060
- ".js": "application/javascript",
5061
- ".css": "text/css",
5062
- ".json": "application/json",
5063
- ".svg": "image/svg+xml",
5064
- ".png": "image/png",
5065
- ".jpg": "image/jpeg",
5066
- ".jpeg": "image/jpeg",
5067
- ".gif": "image/gif",
5068
- ".ico": "image/x-icon",
5069
- ".woff": "font/woff",
5070
- ".woff2": "font/woff2",
5071
- ".txt": "text/plain",
5072
- ".xml": "application/xml"
5073
- };
5074
- var app13 = new Hono13().get("/:name/*", async (c) => {
5129
+ const rows = await db.selectDistinct({ channel: mindHistory.channel }).from(mindHistory).where(eq5(mindHistory.mind, name));
5130
+ return c.json(rows.map((r) => r.channel));
5131
+ }).get("/:name/history/export", async (c) => {
5075
5132
  const name = c.req.param("name");
5076
- let pagesRoot;
5077
- if (name === "_system") {
5078
- pagesRoot = resolve16(voluteHome(), "shared", "pages");
5079
- } else {
5080
- if (!await findMind(name)) return c.text("Not found", 404);
5081
- pagesRoot = resolve16(mindDir(name), "home", "public", "pages");
5082
- }
5083
- const wildcard = c.req.path.replace(`/pages/${name}`, "") || "/";
5084
- const requestedPath = resolve16(pagesRoot, wildcard.slice(1));
5085
- if (!requestedPath.startsWith(pagesRoot)) return c.text("Forbidden", 403);
5086
- let fileStat = await stat2(requestedPath).catch(() => null);
5087
- if (fileStat?.isDirectory()) {
5088
- const indexPath = resolve16(requestedPath, "index.html");
5089
- fileStat = await stat2(indexPath).catch(() => null);
5090
- if (fileStat?.isFile()) {
5091
- const body = await readFile2(indexPath);
5092
- return c.body(body, 200, { "Content-Type": "text/html" });
5093
- }
5094
- return c.text("Not found", 404);
5133
+ if (!await findMind(name)) return c.json({ error: "Mind not found" }, 404);
5134
+ const db = await getDb();
5135
+ const rows = await db.select().from(mindHistory).where(eq5(mindHistory.mind, name));
5136
+ return c.json(rows);
5137
+ }).get("/:name/history", async (c) => {
5138
+ const name = c.req.param("name");
5139
+ const channel = c.req.query("channel");
5140
+ const session = c.req.query("session");
5141
+ const full = c.req.query("full") === "true";
5142
+ const limit = Math.min(Math.max(parseInt(c.req.query("limit") ?? "50", 10) || 50, 1), 200);
5143
+ const offset = Math.max(parseInt(c.req.query("offset") ?? "0", 10) || 0, 0);
5144
+ const db = await getDb();
5145
+ const conditions = [eq5(mindHistory.mind, name)];
5146
+ if (channel) {
5147
+ conditions.push(eq5(mindHistory.channel, channel));
5095
5148
  }
5096
- if (fileStat?.isFile()) {
5097
- const ext = extname3(requestedPath);
5098
- const mime = MIME_TYPES[ext] || "application/octet-stream";
5099
- const body = await readFile2(requestedPath);
5100
- return c.body(body, 200, { "Content-Type": mime });
5149
+ if (session) {
5150
+ conditions.push(eq5(mindHistory.session, session));
5101
5151
  }
5102
- return c.text("Not found", 404);
5152
+ if (!full) {
5153
+ conditions.push(sql2`${mindHistory.type} IN ('inbound', 'outbound', 'summary')`);
5154
+ }
5155
+ const rows = await db.select().from(mindHistory).where(and4(...conditions)).orderBy(desc3(mindHistory.created_at)).limit(limit).offset(offset);
5156
+ return c.json(rows);
5103
5157
  });
5104
- var pages_default = app13;
5158
+ var minds_default = app12;
5105
5159
 
5106
5160
  // src/web/api/prompts.ts
5107
- import { zValidator as zValidator6 } from "@hono/zod-validator";
5161
+ import { zValidator as zValidator5 } from "@hono/zod-validator";
5108
5162
  import { eq as eq6, sql as sql3 } from "drizzle-orm";
5109
- import { Hono as Hono14 } from "hono";
5110
- import { z as z6 } from "zod";
5111
- var app14 = new Hono14().get("/", async (c) => {
5163
+ import { Hono as Hono13 } from "hono";
5164
+ import { z as z5 } from "zod";
5165
+ var app13 = new Hono13().get("/", async (c) => {
5112
5166
  let rows;
5113
5167
  try {
5114
5168
  const db = await getDb();
@@ -5131,7 +5185,7 @@ var app14 = new Hono14().get("/", async (c) => {
5131
5185
  };
5132
5186
  });
5133
5187
  return c.json(prompts);
5134
- }).put("/:key", requireAdmin, zValidator6("json", z6.object({ content: z6.string() })), async (c) => {
5188
+ }).put("/:key", requireAdmin, zValidator5("json", z5.object({ content: z5.string() })), async (c) => {
5135
5189
  const key = c.req.param("key");
5136
5190
  if (!PROMPT_KEYS.includes(key)) {
5137
5191
  return c.json({ error: "Unknown prompt key" }, 404);
@@ -5152,12 +5206,12 @@ var app14 = new Hono14().get("/", async (c) => {
5152
5206
  await db.delete(systemPrompts).where(eq6(systemPrompts.key, key));
5153
5207
  return c.json({ ok: true });
5154
5208
  });
5155
- var prompts_default = app14;
5209
+ var prompts_default = app13;
5156
5210
 
5157
5211
  // src/web/api/public-files.ts
5158
- import { readdir as readdir2, readFile as readFile3, stat as stat3 } from "fs/promises";
5159
- import { extname as extname4, resolve as resolve17 } from "path";
5160
- import { Hono as Hono15 } from "hono";
5212
+ import { readdir as readdir2, readFile as readFile2, stat as stat2 } from "fs/promises";
5213
+ import { extname as extname3, resolve as resolve17 } from "path";
5214
+ import { Hono as Hono14 } from "hono";
5161
5215
  var MAX_FILE_SIZE = 50 * 1024 * 1024;
5162
5216
  async function resolvePublicRoot(name) {
5163
5217
  if (name === "_system") return resolve17(voluteHome(), "shared");
@@ -5167,7 +5221,7 @@ async function resolvePublicRoot(name) {
5167
5221
  function hasDotSegment(relativePath) {
5168
5222
  return relativePath.split("/").some((seg) => seg.startsWith("."));
5169
5223
  }
5170
- var MIME_TYPES2 = {
5224
+ var MIME_TYPES = {
5171
5225
  ".html": "text/html",
5172
5226
  ".js": "application/javascript",
5173
5227
  ".css": "text/css",
@@ -5198,7 +5252,7 @@ async function listDir(dirPath) {
5198
5252
  type: e.isDirectory() ? "directory" : "file"
5199
5253
  }));
5200
5254
  }
5201
- var app15 = new Hono15().get("/:name/", async (c) => {
5255
+ var app14 = new Hono14().get("/:name/", async (c) => {
5202
5256
  const name = c.req.param("name");
5203
5257
  const publicRoot = await resolvePublicRoot(name);
5204
5258
  if (!publicRoot) return c.json({ error: "Not found" }, 404);
@@ -5214,7 +5268,7 @@ var app15 = new Hono15().get("/:name/", async (c) => {
5214
5268
  if (hasDotSegment(relativePath)) return c.text("Forbidden", 403);
5215
5269
  let fileStat;
5216
5270
  try {
5217
- fileStat = await stat3(requestedPath);
5271
+ fileStat = await stat2(requestedPath);
5218
5272
  } catch (err) {
5219
5273
  if (err?.code === "ENOENT") return c.text("Not found", 404);
5220
5274
  if (err?.code === "EACCES") return c.text("Forbidden", 403);
@@ -5228,10 +5282,10 @@ var app15 = new Hono15().get("/:name/", async (c) => {
5228
5282
  }
5229
5283
  if (fileStat.isFile()) {
5230
5284
  if (fileStat.size > MAX_FILE_SIZE) return c.text("File too large", 413);
5231
- const ext = extname4(requestedPath);
5232
- const mime = MIME_TYPES2[ext] || "application/octet-stream";
5285
+ const ext = extname3(requestedPath);
5286
+ const mime = MIME_TYPES[ext] || "application/octet-stream";
5233
5287
  try {
5234
- const body = await readFile3(requestedPath);
5288
+ const body = await readFile2(requestedPath);
5235
5289
  return c.body(body, 200, { "Content-Type": mime });
5236
5290
  } catch (err) {
5237
5291
  if (err?.code === "ENOENT") return c.text("Not found", 404);
@@ -5241,11 +5295,11 @@ var app15 = new Hono15().get("/:name/", async (c) => {
5241
5295
  }
5242
5296
  return c.text("Not found", 404);
5243
5297
  });
5244
- var public_files_default = app15;
5298
+ var public_files_default = app14;
5245
5299
 
5246
5300
  // src/web/api/schedules.ts
5247
5301
  import { CronExpressionParser } from "cron-parser";
5248
- import { Hono as Hono16 } from "hono";
5302
+ import { Hono as Hono15 } from "hono";
5249
5303
  var slog = logger_default.child("schedules");
5250
5304
  function readSchedules(name) {
5251
5305
  return readVoluteConfig(mindDir(name))?.schedules ?? [];
@@ -5256,13 +5310,45 @@ function writeSchedules(name, schedules) {
5256
5310
  config.schedules = schedules.length > 0 ? schedules : void 0;
5257
5311
  writeVoluteConfig(dir, config);
5258
5312
  getScheduler().loadSchedules(name);
5313
+ getSleepManagerIfReady()?.invalidateSleepConfig(name);
5259
5314
  fireWebhook({
5260
5315
  event: "schedule_changed",
5261
5316
  mind: name,
5262
5317
  data: { schedules }
5263
5318
  });
5264
5319
  }
5265
- var app16 = new Hono16().get("/:name/schedules", async (c) => {
5320
+ var app15 = new Hono15().get("/:name/clock/status", async (c) => {
5321
+ const name = c.req.param("name");
5322
+ if (!await findMind(name)) return c.json({ error: "Mind not found" }, 404);
5323
+ const sleepManager = getSleepManagerIfReady();
5324
+ const sleepState = sleepManager?.getState(name) ?? null;
5325
+ const sleepConfig = sleepManager?.getSleepConfig(name) ?? null;
5326
+ const schedules = readSchedules(name);
5327
+ const now = /* @__PURE__ */ new Date();
5328
+ const in24h = new Date(now.getTime() + 24 * 60 * 6e4);
5329
+ const upcoming = [];
5330
+ for (const s of schedules) {
5331
+ if (!s.enabled) continue;
5332
+ if (s.fireAt) {
5333
+ const fireDate = new Date(s.fireAt);
5334
+ if (fireDate >= now && fireDate <= in24h) {
5335
+ upcoming.push({ id: s.id, at: fireDate.toISOString(), type: "timer" });
5336
+ }
5337
+ } else if (s.cron) {
5338
+ try {
5339
+ const interval = CronExpressionParser.parse(s.cron);
5340
+ const next = interval.next().toDate();
5341
+ if (next <= in24h) {
5342
+ upcoming.push({ id: s.id, at: next.toISOString(), type: "cron" });
5343
+ }
5344
+ } catch {
5345
+ slog.warn(`invalid cron "${s.cron}" for schedule "${s.id}" of ${name}`);
5346
+ }
5347
+ }
5348
+ }
5349
+ upcoming.sort((a, b) => a.at.localeCompare(b.at));
5350
+ return c.json({ sleep: sleepState, sleepConfig, schedules, upcoming });
5351
+ }).get("/:name/schedules", async (c) => {
5266
5352
  const name = c.req.param("name");
5267
5353
  if (!await findMind(name)) return c.json({ error: "Mind not found" }, 404);
5268
5354
  return c.json(readSchedules(name));
@@ -5273,8 +5359,14 @@ var app16 = new Hono16().get("/:name/schedules", async (c) => {
5273
5359
  if (entry.stage === "seed")
5274
5360
  return c.json({ error: "Seed minds cannot use schedules \u2014 sprout first" }, 403);
5275
5361
  const body = await c.req.json();
5276
- if (!body.cron) {
5277
- return c.json({ error: "cron is required" }, 400);
5362
+ if (!body.id) {
5363
+ return c.json({ error: "id is required (a descriptive name for this schedule)" }, 400);
5364
+ }
5365
+ if (!body.cron && !body.fireAt) {
5366
+ return c.json({ error: "cron or fireAt is required" }, 400);
5367
+ }
5368
+ if (body.cron && body.fireAt) {
5369
+ return c.json({ error: "cron and fireAt are mutually exclusive" }, 400);
5278
5370
  }
5279
5371
  if (!body.message && !body.script) {
5280
5372
  return c.json({ error: "message or script is required" }, 400);
@@ -5282,20 +5374,36 @@ var app16 = new Hono16().get("/:name/schedules", async (c) => {
5282
5374
  if (body.message && body.script) {
5283
5375
  return c.json({ error: "message and script are mutually exclusive" }, 400);
5284
5376
  }
5285
- try {
5286
- CronExpressionParser.parse(body.cron);
5287
- } catch {
5288
- return c.json({ error: `Invalid cron expression: ${body.cron}` }, 400);
5377
+ if (body.cron) {
5378
+ try {
5379
+ CronExpressionParser.parse(body.cron);
5380
+ } catch {
5381
+ return c.json({ error: `Invalid cron expression: ${body.cron}` }, 400);
5382
+ }
5383
+ }
5384
+ if (body.fireAt && Number.isNaN(new Date(body.fireAt).getTime())) {
5385
+ return c.json({ error: `Invalid fireAt date: ${body.fireAt}` }, 400);
5386
+ }
5387
+ if (body.whileSleeping && !["skip", "queue", "trigger-wake"].includes(body.whileSleeping)) {
5388
+ return c.json(
5389
+ {
5390
+ error: `Invalid whileSleeping value: ${body.whileSleeping} (must be skip, queue, or trigger-wake)`
5391
+ },
5392
+ 400
5393
+ );
5289
5394
  }
5290
5395
  const schedules = readSchedules(name);
5291
- const id = body.id || `schedule-${Date.now()}`;
5396
+ const id = body.id;
5292
5397
  if (schedules.some((s) => s.id === id)) {
5293
5398
  return c.json({ error: `Schedule "${id}" already exists` }, 409);
5294
5399
  }
5295
- const schedule = { id, cron: body.cron, enabled: body.enabled ?? true };
5400
+ const schedule = { id, enabled: body.enabled ?? true };
5401
+ if (body.cron) schedule.cron = body.cron;
5402
+ if (body.fireAt) schedule.fireAt = body.fireAt;
5296
5403
  if (body.message) schedule.message = body.message;
5297
5404
  if (body.script) schedule.script = body.script;
5298
5405
  if (body.channel) schedule.channel = body.channel;
5406
+ if (body.whileSleeping) schedule.whileSleeping = body.whileSleeping;
5299
5407
  schedules.push(schedule);
5300
5408
  writeSchedules(name, schedules);
5301
5409
  return c.json({ ok: true, id }, 201);
@@ -5317,6 +5425,14 @@ var app16 = new Hono16().get("/:name/schedules", async (c) => {
5317
5425
  return c.json({ error: `Invalid cron expression: ${body.cron}` }, 400);
5318
5426
  }
5319
5427
  schedules[idx].cron = body.cron;
5428
+ delete schedules[idx].fireAt;
5429
+ }
5430
+ if (body.fireAt !== void 0) {
5431
+ if (Number.isNaN(new Date(body.fireAt).getTime())) {
5432
+ return c.json({ error: `Invalid fireAt date: ${body.fireAt}` }, 400);
5433
+ }
5434
+ schedules[idx].fireAt = body.fireAt;
5435
+ delete schedules[idx].cron;
5320
5436
  }
5321
5437
  if (body.message !== void 0) {
5322
5438
  schedules[idx].message = body.message;
@@ -5326,8 +5442,18 @@ var app16 = new Hono16().get("/:name/schedules", async (c) => {
5326
5442
  schedules[idx].script = body.script;
5327
5443
  delete schedules[idx].message;
5328
5444
  }
5445
+ if (body.whileSleeping && !["skip", "queue", "trigger-wake"].includes(body.whileSleeping)) {
5446
+ return c.json(
5447
+ {
5448
+ error: `Invalid whileSleeping value: ${body.whileSleeping} (must be skip, queue, or trigger-wake)`
5449
+ },
5450
+ 400
5451
+ );
5452
+ }
5329
5453
  if (body.enabled !== void 0) schedules[idx].enabled = body.enabled;
5330
5454
  if (body.channel !== void 0) schedules[idx].channel = body.channel || void 0;
5455
+ if (body.whileSleeping !== void 0)
5456
+ schedules[idx].whileSleeping = body.whileSleeping || void 0;
5331
5457
  writeSchedules(name, schedules);
5332
5458
  return c.json({ ok: true });
5333
5459
  }).delete("/:name/schedules/:id", requireSelf(), async (c) => {
@@ -5367,11 +5493,11 @@ var app16 = new Hono16().get("/:name/schedules", async (c) => {
5367
5493
  return c.json({ error: "Failed to reach mind" }, 502);
5368
5494
  }
5369
5495
  });
5370
- var schedules_default = app16;
5496
+ var schedules_default = app15;
5371
5497
 
5372
5498
  // src/web/api/shared.ts
5373
- import { Hono as Hono17 } from "hono";
5374
- var app17 = new Hono17().post("/:name/shared/merge", requireAdmin, async (c) => {
5499
+ import { Hono as Hono16 } from "hono";
5500
+ var app16 = new Hono16().post("/:name/shared/merge", requireAdmin, async (c) => {
5375
5501
  const name = c.req.param("name");
5376
5502
  const entry = await findMind(name);
5377
5503
  if (!entry) return c.json({ error: "Mind not found" }, 404);
@@ -5389,24 +5515,63 @@ var app17 = new Hono17().post("/:name/shared/merge", requireAdmin, async (c) =>
5389
5515
  return c.json({ error: err instanceof Error ? err.message : "Merge failed" }, 500);
5390
5516
  }
5391
5517
  });
5392
- var shared_default = app17;
5518
+ var shared_default = app16;
5393
5519
 
5394
5520
  // src/web/api/skills.ts
5395
- import { existsSync as existsSync14, mkdtempSync, readdirSync as readdirSync4, rmSync as rmSync5 } from "fs";
5521
+ import { existsSync as existsSync15, mkdtempSync, readdirSync as readdirSync4, rmSync as rmSync6 } from "fs";
5396
5522
  import { tmpdir } from "os";
5397
5523
  import { join, resolve as resolve18 } from "path";
5398
5524
  import AdmZip from "adm-zip";
5399
- import { Hono as Hono18 } from "hono";
5400
- var app18 = new Hono18().get("/", async (c) => {
5525
+ import { Hono as Hono17 } from "hono";
5526
+ var app17 = new Hono17().get("/", async (c) => {
5401
5527
  const skills = await listSharedSkills();
5402
5528
  return c.json(skills);
5403
- }).get("/:id", async (c) => {
5404
- const id = c.req.param("id");
5405
- const skill = await getSharedSkill(id);
5406
- if (!skill) return c.json({ error: "Skill not found" }, 404);
5407
- const dir = join(sharedSkillsDir(), id);
5408
- const files = listFilesRecursive(dir);
5409
- return c.json({ ...skill, files });
5529
+ }).get("/defaults/list", async (c) => {
5530
+ return c.json({ skills: getStandardSkillsWithExtensions() });
5531
+ }).put("/defaults/list", requireAdmin, async (c) => {
5532
+ const body = await c.req.json();
5533
+ if (!Array.isArray(body.skills) || !body.skills.every((s) => typeof s === "string")) {
5534
+ return c.json({ error: "body.skills must be a string array" }, 400);
5535
+ }
5536
+ const config = readGlobalConfig();
5537
+ const allStandard = /* @__PURE__ */ new Set([...STANDARD_SKILLS, ...getExtensionStandardSkills()]);
5538
+ const newSet = new Set(body.skills);
5539
+ const removed = [...allStandard].filter((s) => !newSet.has(s));
5540
+ const prevRemoved = new Set(config.removedDefaultSkills ?? []);
5541
+ for (const s of removed) prevRemoved.add(s);
5542
+ for (const s of body.skills) prevRemoved.delete(s);
5543
+ writeGlobalConfig({
5544
+ ...config,
5545
+ defaultSkills: body.skills,
5546
+ removedDefaultSkills: [...prevRemoved]
5547
+ });
5548
+ return c.json({ skills: body.skills });
5549
+ }).post("/defaults/list", requireAdmin, async (c) => {
5550
+ const body = await c.req.json();
5551
+ if (typeof body.skill !== "string" || !body.skill) {
5552
+ return c.json({ error: "body.skill must be a non-empty string" }, 400);
5553
+ }
5554
+ const current = getStandardSkillsWithExtensions();
5555
+ if (current.includes(body.skill)) {
5556
+ return c.json({ error: `"${body.skill}" is already a default skill` }, 409);
5557
+ }
5558
+ const config = readGlobalConfig();
5559
+ const updated = [...current, body.skill];
5560
+ const removed = (config.removedDefaultSkills ?? []).filter((s) => s !== body.skill);
5561
+ writeGlobalConfig({ ...config, defaultSkills: updated, removedDefaultSkills: removed });
5562
+ return c.json({ skills: updated });
5563
+ }).delete("/defaults/list/:skill", requireAdmin, async (c) => {
5564
+ const skill = c.req.param("skill");
5565
+ const current = getStandardSkillsWithExtensions();
5566
+ if (!current.includes(skill)) {
5567
+ return c.json({ error: `"${skill}" is not a default skill` }, 404);
5568
+ }
5569
+ const config = readGlobalConfig();
5570
+ const updated = current.filter((s) => s !== skill);
5571
+ const removed = new Set(config.removedDefaultSkills ?? []);
5572
+ removed.add(skill);
5573
+ writeGlobalConfig({ ...config, defaultSkills: updated, removedDefaultSkills: [...removed] });
5574
+ return c.json({ skills: updated });
5410
5575
  }).post("/upload", requireAdmin, async (c) => {
5411
5576
  const body = await c.req.parseBody();
5412
5577
  const file = body.file;
@@ -5428,12 +5593,12 @@ var app18 = new Hono18().get("/", async (c) => {
5428
5593
  }
5429
5594
  zip.extractAllTo(tmpDir, true);
5430
5595
  let skillDir = null;
5431
- if (existsSync14(join(tmpDir, "SKILL.md"))) {
5596
+ if (existsSync15(join(tmpDir, "SKILL.md"))) {
5432
5597
  skillDir = tmpDir;
5433
5598
  } else {
5434
5599
  const entries = readdirSync4(tmpDir, { withFileTypes: true }).filter((e) => e.isDirectory());
5435
5600
  for (const entry of entries) {
5436
- if (existsSync14(join(tmpDir, entry.name, "SKILL.md"))) {
5601
+ if (existsSync15(join(tmpDir, entry.name, "SKILL.md"))) {
5437
5602
  skillDir = join(tmpDir, entry.name);
5438
5603
  break;
5439
5604
  }
@@ -5450,8 +5615,15 @@ var app18 = new Hono18().get("/", async (c) => {
5450
5615
  }
5451
5616
  throw e;
5452
5617
  } finally {
5453
- rmSync5(tmpDir, { recursive: true, force: true });
5618
+ rmSync6(tmpDir, { recursive: true, force: true });
5454
5619
  }
5620
+ }).get("/:id", async (c) => {
5621
+ const id = c.req.param("id");
5622
+ const skill = await getSharedSkill(id);
5623
+ if (!skill) return c.json({ error: "Skill not found" }, 404);
5624
+ const dir = join(sharedSkillsDir(), id);
5625
+ const files = listFilesRecursive(dir);
5626
+ return c.json({ ...skill, files });
5455
5627
  }).delete("/:id", requireAdmin, async (c) => {
5456
5628
  const id = c.req.param("id");
5457
5629
  try {
@@ -5462,15 +5634,16 @@ var app18 = new Hono18().get("/", async (c) => {
5462
5634
  }
5463
5635
  return c.json({ ok: true });
5464
5636
  });
5465
- var skills_default = app18;
5637
+ var skills_default = app17;
5466
5638
 
5467
5639
  // src/web/api/system.ts
5468
- import { zValidator as zValidator7 } from "@hono/zod-validator";
5469
- import { Hono as Hono19 } from "hono";
5640
+ import { zValidator as zValidator6 } from "@hono/zod-validator";
5641
+ import { getOAuthProvider, getOAuthProviders, getProviders } from "@mariozechner/pi-ai";
5642
+ import { Hono as Hono18 } from "hono";
5470
5643
  import { streamSSE as streamSSE3 } from "hono/streaming";
5471
- import { z as z7 } from "zod";
5644
+ import { z as z6 } from "zod";
5472
5645
  var DEFAULT_API_URL = "https://volute.systems";
5473
- var app19 = new Hono19().post("/restart", requireAdmin, (c) => {
5646
+ var app18 = new Hono18().post("/restart", requireAdmin, (c) => {
5474
5647
  setTimeout(() => process.exit(1), 200);
5475
5648
  return c.json({ ok: true });
5476
5649
  }).post("/stop", requireAdmin, (c) => {
@@ -5500,7 +5673,7 @@ var app19 = new Hono19().post("/restart", requireAdmin, (c) => {
5500
5673
  }).post(
5501
5674
  "/register",
5502
5675
  requireAdmin,
5503
- zValidator7("json", z7.object({ name: z7.string().min(1) })),
5676
+ zValidator6("json", z6.object({ name: z6.string().min(1) })),
5504
5677
  async (c) => {
5505
5678
  const existing = readSystemsConfig();
5506
5679
  if (existing) {
@@ -5539,7 +5712,7 @@ var app19 = new Hono19().post("/restart", requireAdmin, (c) => {
5539
5712
  ).post(
5540
5713
  "/login",
5541
5714
  requireAdmin,
5542
- zValidator7("json", z7.object({ key: z7.string().min(1) })),
5715
+ zValidator6("json", z6.object({ key: z6.string().min(1) })),
5543
5716
  async (c) => {
5544
5717
  const existing = readSystemsConfig();
5545
5718
  if (existing) {
@@ -5607,19 +5780,173 @@ var app19 = new Hono19().post("/restart", requireAdmin, (c) => {
5607
5780
  } catch (err) {
5608
5781
  return c.json({ error: `Connection failed: ${err.message}` }, 502);
5609
5782
  }
5783
+ }).get("/ai/providers", requireAdmin, (c) => {
5784
+ const allProviders = getProviders();
5785
+ const oauthProviders = getOAuthProviders();
5786
+ const oauthMap = new Map(oauthProviders.map((p) => [p.id, p]));
5787
+ const ai = getAiConfig();
5788
+ const result = allProviders.map((id) => {
5789
+ const oauth = oauthMap.get(id);
5790
+ const providerConfig = ai?.providers[id];
5791
+ const configured = !!(providerConfig?.apiKey || providerConfig?.oauth);
5792
+ let authMethod = null;
5793
+ if (providerConfig?.oauth) authMethod = "oauth";
5794
+ else if (providerConfig?.apiKey) authMethod = "api_key";
5795
+ return {
5796
+ id,
5797
+ oauth: !!oauth,
5798
+ oauthName: oauth?.name,
5799
+ usesCallbackServer: !!oauth?.usesCallbackServer,
5800
+ configured,
5801
+ authMethod
5802
+ };
5803
+ });
5804
+ return c.json(result);
5805
+ }).get("/ai/models", requireAdmin, (c) => {
5806
+ const models = getAvailableModels();
5807
+ const enabled = new Set(getEnabledModels());
5808
+ return c.json(
5809
+ models.map((m) => ({
5810
+ id: m.id,
5811
+ name: m.name,
5812
+ provider: m.provider,
5813
+ contextWindow: m.contextWindow,
5814
+ maxTokens: m.maxTokens,
5815
+ enabled: enabled.has(m.id)
5816
+ }))
5817
+ );
5818
+ }).put(
5819
+ "/ai/models",
5820
+ requireAdmin,
5821
+ zValidator6("json", z6.object({ models: z6.array(z6.string()) })),
5822
+ (c) => {
5823
+ const { models } = c.req.valid("json");
5824
+ setEnabledModels(models);
5825
+ return c.json({ ok: true });
5826
+ }
5827
+ ).put(
5828
+ "/ai/providers/:id",
5829
+ requireAdmin,
5830
+ zValidator6("json", z6.object({ apiKey: z6.string().min(1) })),
5831
+ (c) => {
5832
+ const id = c.req.param("id");
5833
+ const { apiKey } = c.req.valid("json");
5834
+ saveProviderConfig(id, { apiKey });
5835
+ return c.json({ ok: true });
5836
+ }
5837
+ ).delete("/ai/providers/:id", requireAdmin, (c) => {
5838
+ const id = c.req.param("id");
5839
+ removeProviderConfig(id);
5840
+ return c.json({ ok: true });
5841
+ }).delete("/ai", requireAdmin, (c) => {
5842
+ removeAiConfig();
5843
+ return c.json({ ok: true });
5844
+ }).post(
5845
+ "/ai/oauth/start",
5846
+ requireAdmin,
5847
+ zValidator6("json", z6.object({ provider: z6.string().min(1) })),
5848
+ async (c) => {
5849
+ const { provider } = c.req.valid("json");
5850
+ const oauthProvider = getOAuthProvider(provider);
5851
+ if (!oauthProvider) {
5852
+ return c.json({ error: `OAuth not supported for provider: ${provider}` }, 400);
5853
+ }
5854
+ cleanupOAuthFlows();
5855
+ const flowId = crypto.randomUUID();
5856
+ const needsManualCode = !!oauthProvider.usesCallbackServer;
5857
+ const flow = {
5858
+ status: "pending",
5859
+ needsManualCode,
5860
+ createdAt: Date.now()
5861
+ };
5862
+ oauthFlows.set(flowId, flow);
5863
+ const promptPromise = needsManualCode ? new Promise((resolve23) => {
5864
+ flow.resolveCode = resolve23;
5865
+ }) : void 0;
5866
+ oauthProvider.login({
5867
+ onAuth: (info) => {
5868
+ const existing = oauthFlows.get(flowId);
5869
+ if (existing)
5870
+ Object.assign(existing, { url: info.url, instructions: info.instructions });
5871
+ },
5872
+ onPrompt: async (_prompt) => {
5873
+ if (promptPromise) {
5874
+ const existing = oauthFlows.get(flowId);
5875
+ if (existing) existing.waitingForCode = true;
5876
+ return promptPromise;
5877
+ }
5878
+ return "";
5879
+ },
5880
+ onManualCodeInput: needsManualCode ? () => promptPromise : void 0
5881
+ }).then((credentials) => {
5882
+ saveProviderConfig(provider, { oauth: credentials });
5883
+ const existing = oauthFlows.get(flowId);
5884
+ if (existing) existing.status = "complete";
5885
+ }).catch((err) => {
5886
+ const existing = oauthFlows.get(flowId);
5887
+ if (existing) {
5888
+ existing.status = "error";
5889
+ existing.error = err instanceof Error ? err.message : String(err);
5890
+ }
5891
+ });
5892
+ await new Promise((resolve23) => setTimeout(resolve23, 2e3));
5893
+ const state = oauthFlows.get(flowId);
5894
+ return c.json({
5895
+ flowId,
5896
+ url: state.url,
5897
+ instructions: state.instructions,
5898
+ needsManualCode
5899
+ });
5900
+ }
5901
+ ).post(
5902
+ "/ai/oauth/code/:flowId",
5903
+ requireAdmin,
5904
+ zValidator6("json", z6.object({ code: z6.string().min(1) })),
5905
+ async (c) => {
5906
+ const flowId = c.req.param("flowId");
5907
+ const flow = oauthFlows.get(flowId);
5908
+ if (!flow) return c.json({ error: "Flow not found" }, 404);
5909
+ if (!flow.resolveCode) return c.json({ error: "Flow does not accept manual code" }, 400);
5910
+ const { code } = c.req.valid("json");
5911
+ flow.resolveCode(code.trim());
5912
+ return c.json({ ok: true });
5913
+ }
5914
+ ).get("/ai/oauth/status/:flowId", requireAdmin, (c) => {
5915
+ const flowId = c.req.param("flowId");
5916
+ const flow = oauthFlows.get(flowId);
5917
+ if (!flow) return c.json({ error: "Flow not found" }, 404);
5918
+ const result = {
5919
+ status: flow.status,
5920
+ waitingForCode: flow.waitingForCode
5921
+ };
5922
+ if (flow.error) result.error = flow.error;
5923
+ if (flow.status !== "pending") {
5924
+ setTimeout(() => oauthFlows.delete(flowId), 3e4);
5925
+ }
5926
+ return c.json(result);
5610
5927
  });
5611
- var system_default = app19;
5928
+ var oauthFlows = /* @__PURE__ */ new Map();
5929
+ var OAUTH_FLOW_TTL_MS = 10 * 60 * 1e3;
5930
+ function cleanupOAuthFlows() {
5931
+ const now = Date.now();
5932
+ for (const [id, flow] of oauthFlows) {
5933
+ if (now - flow.createdAt > OAUTH_FLOW_TTL_MS) {
5934
+ oauthFlows.delete(id);
5935
+ }
5936
+ }
5937
+ }
5938
+ var system_default = app18;
5612
5939
 
5613
5940
  // src/web/api/typing.ts
5614
- import { zValidator as zValidator8 } from "@hono/zod-validator";
5615
- import { Hono as Hono20 } from "hono";
5616
- import { z as z8 } from "zod";
5617
- var typingSchema = z8.object({
5618
- channel: z8.string().min(1),
5619
- sender: z8.string().min(1),
5620
- active: z8.boolean()
5941
+ import { zValidator as zValidator7 } from "@hono/zod-validator";
5942
+ import { Hono as Hono19 } from "hono";
5943
+ import { z as z7 } from "zod";
5944
+ var typingSchema = z7.object({
5945
+ channel: z7.string().min(1),
5946
+ sender: z7.string().min(1),
5947
+ active: z7.boolean()
5621
5948
  });
5622
- var app20 = new Hono20().post("/:name/typing", zValidator8("json", typingSchema), (c) => {
5949
+ var app19 = new Hono19().post("/:name/typing", zValidator7("json", typingSchema), (c) => {
5623
5950
  const { channel, sender, active } = c.req.valid("json");
5624
5951
  const map = getTypingMap();
5625
5952
  if (active) {
@@ -5641,13 +5968,13 @@ var app20 = new Hono20().post("/:name/typing", zValidator8("json", typingSchema)
5641
5968
  const map = getTypingMap();
5642
5969
  return c.json({ typing: map.get(channel) });
5643
5970
  });
5644
- var typing_default = app20;
5971
+ var typing_default = app19;
5645
5972
 
5646
5973
  // src/web/api/update.ts
5647
5974
  import { spawn as spawn3 } from "child_process";
5648
- import { Hono as Hono21 } from "hono";
5975
+ import { Hono as Hono20 } from "hono";
5649
5976
  var bin;
5650
- var app21 = new Hono21().get("/update", async (c) => {
5977
+ var app20 = new Hono20().get("/update", async (c) => {
5651
5978
  const result = await checkForUpdate();
5652
5979
  return c.json(result);
5653
5980
  }).post("/update", requireAdmin, async (c) => {
@@ -5662,23 +5989,23 @@ var app21 = new Hono21().get("/update", async (c) => {
5662
5989
  child.unref();
5663
5990
  return c.json({ ok: true, message: "Updating..." });
5664
5991
  });
5665
- var update_default = app21;
5992
+ var update_default = app20;
5666
5993
 
5667
5994
  // src/web/api/v1/chat.ts
5668
- import { zValidator as zValidator9 } from "@hono/zod-validator";
5669
- import { Hono as Hono22 } from "hono";
5995
+ import { zValidator as zValidator8 } from "@hono/zod-validator";
5996
+ import { Hono as Hono21 } from "hono";
5670
5997
  import { streamSSE as streamSSE4 } from "hono/streaming";
5671
- import { z as z9 } from "zod";
5998
+ import { z as z8 } from "zod";
5672
5999
  async function fanOutToMinds(opts) {
5673
6000
  const participants = await getParticipants(opts.conversationId);
5674
6001
  const mindParticipants = participants.filter((p) => p.userType === "mind");
5675
6002
  const participantNames = participants.map((p) => p.username);
5676
6003
  const isDM = opts.isDM ?? participants.length === 2;
5677
- const channelEntryType = opts.channelEntryType ?? (isDM ? "dm" : "group");
5678
- const { getMindManager: getMindManager2 } = await import("./mind-manager-S6ILZVX3.js");
5679
- const { getSleepManagerIfReady } = await import("./sleep-manager-WMVG2VCL.js");
6004
+ const channelEntryType = opts.channelEntryType ?? (isDM ? "dm" : "channel");
6005
+ const { getMindManager: getMindManager2 } = await import("./mind-manager-YFCOIAAX.js");
6006
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-O7YQFCV5.js");
5680
6007
  const manager = getMindManager2();
5681
- const sm = getSleepManagerIfReady();
6008
+ const sm = getSleepManagerIfReady2();
5682
6009
  const targetMinds = mindParticipants.map((ap) => {
5683
6010
  const key = opts.targetName ? opts.targetName(ap.username) : ap.username;
5684
6011
  if (manager.isRunning(key) || sm?.isSleeping(ap.username)) return ap.username;
@@ -5725,18 +6052,18 @@ async function fanOutToMinds(opts) {
5725
6052
  });
5726
6053
  }
5727
6054
  }
5728
- var mindChatSchema = z9.object({
5729
- message: z9.string().optional(),
5730
- conversationId: z9.string().optional(),
5731
- sender: z9.string().optional(),
5732
- images: z9.array(z9.object({ media_type: z9.string(), data: z9.string() })).optional()
6055
+ var mindChatSchema = z8.object({
6056
+ message: z8.string().optional(),
6057
+ conversationId: z8.string().optional(),
6058
+ sender: z8.string().optional(),
6059
+ images: z8.array(z8.object({ media_type: z8.string(), data: z8.string() })).optional()
5733
6060
  });
5734
- var unifiedChatSchema = z9.object({
5735
- message: z9.string().optional(),
5736
- conversationId: z9.string(),
5737
- images: z9.array(z9.object({ media_type: z9.string(), data: z9.string() })).optional()
6061
+ var unifiedChatSchema = z8.object({
6062
+ message: z8.string().optional(),
6063
+ conversationId: z8.string(),
6064
+ images: z8.array(z8.object({ media_type: z8.string(), data: z8.string() })).optional()
5738
6065
  });
5739
- var app22 = new Hono22().use("*", authMiddleware).post("/minds/:name/chat", zValidator9("json", mindChatSchema), async (c) => {
6066
+ var app21 = new Hono21().use("*", authMiddleware).post("/minds/:name/chat", zValidator8("json", mindChatSchema), async (c) => {
5740
6067
  const name = c.req.param("name");
5741
6068
  const baseName = await getBaseName(name);
5742
6069
  const entry = await findMind(baseName);
@@ -5797,7 +6124,7 @@ var app22 = new Hono22().use("*", authMiddleware).post("/minds/:name/chat", zVal
5797
6124
  senderName,
5798
6125
  convTitle,
5799
6126
  isDM,
5800
- channelEntryType: conv?.type === "channel" ? "group" : isDM ? "dm" : "group",
6127
+ channelEntryType: isDM ? "dm" : "channel",
5801
6128
  slugExtra: conv ? { convType: conv.type, convName: conv.name } : void 0,
5802
6129
  targetName: (username) => username === baseName ? name : username
5803
6130
  });
@@ -5827,7 +6154,7 @@ var app22 = new Hono22().use("*", authMiddleware).post("/minds/:name/chat", zVal
5827
6154
  });
5828
6155
  });
5829
6156
  });
5830
- }).post("/chat", zValidator9("json", unifiedChatSchema), async (c) => {
6157
+ }).post("/chat", zValidator8("json", unifiedChatSchema), async (c) => {
5831
6158
  const user = c.get("user");
5832
6159
  const body = c.req.valid("json");
5833
6160
  if (!body.message && (!body.images || body.images.length === 0)) {
@@ -5854,22 +6181,22 @@ var app22 = new Hono22().use("*", authMiddleware).post("/minds/:name/chat", zVal
5854
6181
  senderName,
5855
6182
  convTitle: conv.title,
5856
6183
  isDM,
5857
- channelEntryType: conv.type === "channel" ? "group" : isDM ? "dm" : "group",
6184
+ channelEntryType: isDM ? "dm" : "channel",
5858
6185
  slugExtra: { convType: conv.type, convName: conv.name }
5859
6186
  });
5860
6187
  return c.json({ ok: true, conversationId: body.conversationId });
5861
6188
  });
5862
- var chat_default = app22;
6189
+ var chat_default = app21;
5863
6190
 
5864
6191
  // src/web/api/v1/conversations.ts
5865
- import { zValidator as zValidator10 } from "@hono/zod-validator";
5866
- import { Hono as Hono23 } from "hono";
5867
- import { z as z10 } from "zod";
5868
- var createSchema2 = z10.object({
5869
- title: z10.string().optional(),
5870
- participantNames: z10.array(z10.string()).min(1)
6192
+ import { zValidator as zValidator9 } from "@hono/zod-validator";
6193
+ import { Hono as Hono22 } from "hono";
6194
+ import { z as z9 } from "zod";
6195
+ var createSchema = z9.object({
6196
+ title: z9.string().optional(),
6197
+ participantNames: z9.array(z9.string()).min(1)
5871
6198
  });
5872
- var app23 = new Hono23().use("*", authMiddleware).get("/", async (c) => {
6199
+ var app22 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
5873
6200
  const user = c.get("user");
5874
6201
  const convs = await listConversationsWithParticipants(user.id);
5875
6202
  return c.json(convs);
@@ -5887,7 +6214,7 @@ var app23 = new Hono23().use("*", authMiddleware).get("/", async (c) => {
5887
6214
  }
5888
6215
  const before = beforeStr ? parseInt(beforeStr, 10) : void 0;
5889
6216
  const limit = limitStr ? parseInt(limitStr, 10) : void 0;
5890
- if (before !== void 0 && isNaN(before) || limit !== void 0 && isNaN(limit)) {
6217
+ if (before !== void 0 && Number.isNaN(before) || limit !== void 0 && Number.isNaN(limit)) {
5891
6218
  return c.json({ error: "Invalid cursor params: before and limit must be integers" }, 400);
5892
6219
  }
5893
6220
  const result = await getMessagesPaginated(id, { before, limit });
@@ -5900,7 +6227,7 @@ var app23 = new Hono23().use("*", authMiddleware).get("/", async (c) => {
5900
6227
  }
5901
6228
  const participants = await getParticipants(id);
5902
6229
  return c.json(participants);
5903
- }).post("/", zValidator10("json", createSchema2), async (c) => {
6230
+ }).post("/", zValidator9("json", createSchema), async (c) => {
5904
6231
  const user = c.get("user");
5905
6232
  const body = c.req.valid("json");
5906
6233
  const participantIds = /* @__PURE__ */ new Set();
@@ -5946,30 +6273,30 @@ var app23 = new Hono23().use("*", authMiddleware).get("/", async (c) => {
5946
6273
  if (!deleted) return c.json({ error: "Conversation not found" }, 404);
5947
6274
  return c.json({ ok: true });
5948
6275
  });
5949
- var conversations_default = app23;
6276
+ var conversations_default = app22;
5950
6277
 
5951
6278
  // src/web/api/v1/events.ts
5952
6279
  import { desc as desc4 } from "drizzle-orm";
5953
- import { Hono as Hono24 } from "hono";
6280
+ import { Hono as Hono23 } from "hono";
5954
6281
  import { streamSSE as streamSSE5 } from "hono/streaming";
5955
6282
 
5956
6283
  // src/lib/events/brain-presence.ts
5957
6284
  var connections = /* @__PURE__ */ new Map();
5958
6285
  function addConnection(username) {
5959
- const count2 = connections.get(username) ?? 0;
5960
- connections.set(username, count2 + 1);
5961
- if (count2 === 0) {
6286
+ const count = connections.get(username) ?? 0;
6287
+ connections.set(username, count + 1);
6288
+ if (count === 0) {
5962
6289
  broadcast({ type: "brain_online", mind: username, summary: `${username} connected` });
5963
6290
  }
5964
6291
  }
5965
6292
  function removeConnection(username) {
5966
- const count2 = connections.get(username);
5967
- if (count2 == null) return;
5968
- if (count2 <= 1) {
6293
+ const count = connections.get(username);
6294
+ if (count == null) return;
6295
+ if (count <= 1) {
5969
6296
  connections.delete(username);
5970
6297
  broadcast({ type: "brain_offline", mind: username, summary: `${username} disconnected` });
5971
6298
  } else {
5972
- connections.set(username, count2 - 1);
6299
+ connections.set(username, count - 1);
5973
6300
  }
5974
6301
  }
5975
6302
  function getOnlineBrains() {
@@ -5997,7 +6324,7 @@ function getEventsSince(sinceId) {
5997
6324
  }
5998
6325
 
5999
6326
  // src/web/api/v1/events.ts
6000
- var app24 = new Hono24().use("*", authMiddleware).get("/", async (c) => {
6327
+ var app23 = new Hono23().use("*", authMiddleware).get("/", async (c) => {
6001
6328
  const user = c.get("user");
6002
6329
  const since = c.req.query("since");
6003
6330
  const sinceId = since ? Number(since) : 0;
@@ -6104,16 +6431,16 @@ var app24 = new Hono24().use("*", authMiddleware).get("/", async (c) => {
6104
6431
  }
6105
6432
  });
6106
6433
  });
6107
- var events_default = app24;
6434
+ var events_default = app23;
6108
6435
 
6109
6436
  // src/web/api/variants.ts
6110
- import { existsSync as existsSync15, mkdirSync as mkdirSync10, writeFileSync as writeFileSync9 } from "fs";
6437
+ import { existsSync as existsSync16, mkdirSync as mkdirSync11, writeFileSync as writeFileSync10 } from "fs";
6111
6438
  import { resolve as resolve20 } from "path";
6112
- import { Hono as Hono25 } from "hono";
6439
+ import { Hono as Hono24 } from "hono";
6113
6440
 
6114
6441
  // src/lib/spawn-server.ts
6115
6442
  import { spawn as spawn4 } from "child_process";
6116
- import { closeSync, mkdirSync as mkdirSync9, openSync, readFileSync as readFileSync12 } from "fs";
6443
+ import { closeSync, mkdirSync as mkdirSync10, openSync, readFileSync as readFileSync13 } from "fs";
6117
6444
  import { resolve as resolve19 } from "path";
6118
6445
  function tsxBin(cwd) {
6119
6446
  return resolve19(cwd, "node_modules", ".bin", "tsx");
@@ -6152,7 +6479,7 @@ function spawnAttached(cwd, port) {
6152
6479
  }
6153
6480
  function spawnDetached(cwd, port, logDir) {
6154
6481
  const logsDir = logDir ?? resolve19(cwd, ".mind", "logs");
6155
- mkdirSync9(logsDir, { recursive: true });
6482
+ mkdirSync10(logsDir, { recursive: true });
6156
6483
  const logPath = resolve19(logsDir, "mind.log");
6157
6484
  const logFd = openSync(logPath, "a");
6158
6485
  const child = spawn4(tsxBin(cwd), ["src/server.ts", "--port", String(port)], {
@@ -6173,7 +6500,7 @@ function spawnDetached(cwd, port, logDir) {
6173
6500
  }
6174
6501
  const interval = setInterval(() => {
6175
6502
  try {
6176
- const content = readFileSync12(logPath, "utf-8");
6503
+ const content = readFileSync13(logPath, "utf-8");
6177
6504
  const match = content.match(/listening on :(\d+)/);
6178
6505
  if (match) {
6179
6506
  finish({ child, actualPort: parseInt(match[1], 10) });
@@ -6225,7 +6552,7 @@ async function verify2(port) {
6225
6552
  }
6226
6553
 
6227
6554
  // src/web/api/variants.ts
6228
- var app25 = new Hono25().get("/:name/variants", async (c) => {
6555
+ var app24 = new Hono24().get("/:name/variants", async (c) => {
6229
6556
  const name = c.req.param("name");
6230
6557
  const entry = await findMind(name);
6231
6558
  if (!entry) return c.json({ error: "Mind not found" }, 404);
@@ -6270,10 +6597,10 @@ var app25 = new Hono25().get("/:name/variants", async (c) => {
6270
6597
  }
6271
6598
  const projectRoot = mindDir(mindName);
6272
6599
  const variantDir = resolve20(projectRoot, ".variants", variantName);
6273
- if (existsSync15(variantDir)) {
6600
+ if (existsSync16(variantDir)) {
6274
6601
  return c.json({ error: `Variant directory already exists: ${variantDir}` }, 409);
6275
6602
  }
6276
- mkdirSync10(resolve20(projectRoot, ".variants"), { recursive: true });
6603
+ mkdirSync11(resolve20(projectRoot, ".variants"), { recursive: true });
6277
6604
  try {
6278
6605
  await gitExec(["worktree", "add", "-b", variantName, variantDir], { cwd: projectRoot });
6279
6606
  } catch (e) {
@@ -6296,7 +6623,7 @@ var app25 = new Hono25().get("/:name/variants", async (c) => {
6296
6623
  return c.json({ error: `npm install failed: ${msg}` }, 500);
6297
6624
  }
6298
6625
  if (body.soul) {
6299
- writeFileSync9(resolve20(variantDir, "home/SOUL.md"), body.soul);
6626
+ writeFileSync10(resolve20(variantDir, "home/SOUL.md"), body.soul);
6300
6627
  }
6301
6628
  const variantPort = body.port ?? await nextPort();
6302
6629
  await addVariant(variantName, mindName, variantPort, variantDir, variantName);
@@ -6331,7 +6658,7 @@ var app25 = new Hono25().get("/:name/variants", async (c) => {
6331
6658
  } catch {
6332
6659
  }
6333
6660
  const projectRoot = mindDir(mindName);
6334
- if (existsSync15(variantEntry.dir)) {
6661
+ if (existsSync16(variantEntry.dir)) {
6335
6662
  const status = (await gitExec(["status", "--porcelain"], { cwd: variantEntry.dir })).trim();
6336
6663
  if (status) {
6337
6664
  try {
@@ -6339,7 +6666,7 @@ var app25 = new Hono25().get("/:name/variants", async (c) => {
6339
6666
  await gitExec(["commit", "-m", "Auto-commit uncommitted changes before merge"], {
6340
6667
  cwd: variantEntry.dir
6341
6668
  });
6342
- } catch (e) {
6669
+ } catch (_e) {
6343
6670
  return c.json(
6344
6671
  {
6345
6672
  error: "Failed to auto-commit variant changes. Commit or stash manually before merging."
@@ -6376,7 +6703,7 @@ var app25 = new Hono25().get("/:name/variants", async (c) => {
6376
6703
  await gitExec(["commit", "-m", "Auto-commit uncommitted changes before merge"], {
6377
6704
  cwd: projectRoot
6378
6705
  });
6379
- } catch (e) {
6706
+ } catch (_e) {
6380
6707
  return c.json(
6381
6708
  { error: "Failed to auto-commit main changes. Commit or stash manually before merging." },
6382
6709
  500
@@ -6385,14 +6712,14 @@ var app25 = new Hono25().get("/:name/variants", async (c) => {
6385
6712
  }
6386
6713
  try {
6387
6714
  await gitExec(["merge", variantEntry.branch], { cwd: projectRoot });
6388
- } catch (e) {
6715
+ } catch (_e) {
6389
6716
  return c.json({ error: "Merge failed. Resolve conflicts manually." }, 500);
6390
6717
  }
6391
6718
  await cleanupVariant(variantName, projectRoot, variantEntry.dir);
6392
6719
  if (variantName.endsWith("-upgrade") || variantName === "upgrade") {
6393
6720
  try {
6394
- const { computeTemplateHash: computeTemplateHash2 } = await import("./template-hash-BIMA4ILT.js");
6395
- const { setMindTemplateHash: setMindTemplateHash2 } = await import("./registry-NDNOOYG4.js");
6721
+ const { computeTemplateHash: computeTemplateHash2 } = await import("./template-hash-3HOR4UAJ.js");
6722
+ const { setMindTemplateHash: setMindTemplateHash2 } = await import("./registry-ODSALQQL.js");
6396
6723
  const tmpl = parentEntry.template ?? "claude";
6397
6724
  await setMindTemplateHash2(mindName, computeTemplateHash2(tmpl));
6398
6725
  } catch (err) {
@@ -6447,19 +6774,19 @@ var app25 = new Hono25().get("/:name/variants", async (c) => {
6447
6774
  await cleanupVariant(variantName, projectRoot, variantEntry.dir, { stop: true });
6448
6775
  return c.json({ ok: true });
6449
6776
  });
6450
- var variants_default = app25;
6777
+ var variants_default = app24;
6451
6778
 
6452
6779
  // src/web/api/volute/channels.ts
6453
- import { zValidator as zValidator11 } from "@hono/zod-validator";
6454
- import { Hono as Hono26 } from "hono";
6455
- import { z as z11 } from "zod";
6456
- var createSchema3 = z11.object({
6457
- name: z11.string().min(1).max(50).regex(/^[a-z0-9][a-z0-9-]*$/, "Channel names must be lowercase alphanumeric with hyphens")
6780
+ import { zValidator as zValidator10 } from "@hono/zod-validator";
6781
+ import { Hono as Hono25 } from "hono";
6782
+ import { z as z10 } from "zod";
6783
+ var createSchema2 = z10.object({
6784
+ name: z10.string().min(1).max(50).regex(/^[a-z0-9][a-z0-9-]*$/, "Channel names must be lowercase alphanumeric with hyphens")
6458
6785
  });
6459
- var inviteSchema = z11.object({
6460
- username: z11.string().min(1)
6786
+ var inviteSchema = z10.object({
6787
+ username: z10.string().min(1)
6461
6788
  });
6462
- var app26 = new Hono26().get("/", async (c) => {
6789
+ var app25 = new Hono25().get("/", async (c) => {
6463
6790
  const user = c.get("user");
6464
6791
  const channels = await listChannels();
6465
6792
  const results = await Promise.all(
@@ -6470,7 +6797,7 @@ var app26 = new Hono26().get("/", async (c) => {
6470
6797
  })
6471
6798
  );
6472
6799
  return c.json(results);
6473
- }).post("/", zValidator11("json", createSchema3), async (c) => {
6800
+ }).post("/", zValidator10("json", createSchema2), async (c) => {
6474
6801
  const user = c.get("user");
6475
6802
  const body = c.req.valid("json");
6476
6803
  try {
@@ -6503,7 +6830,7 @@ var app26 = new Hono26().get("/", async (c) => {
6503
6830
  if (!ch) return c.json({ error: "Channel not found" }, 404);
6504
6831
  const participants = await getParticipants(ch.id);
6505
6832
  return c.json(participants);
6506
- }).post("/:name/invite", zValidator11("json", inviteSchema), async (c) => {
6833
+ }).post("/:name/invite", zValidator10("json", inviteSchema), async (c) => {
6507
6834
  const name = c.req.param("name");
6508
6835
  const inviter = c.get("user");
6509
6836
  const { username } = c.req.valid("json");
@@ -6523,13 +6850,13 @@ var app26 = new Hono26().get("/", async (c) => {
6523
6850
  ]);
6524
6851
  return c.json({ ok: true });
6525
6852
  });
6526
- var channels_default2 = app26;
6853
+ var channels_default2 = app25;
6527
6854
 
6528
6855
  // src/web/api/volute/chat.ts
6529
- import { zValidator as zValidator12 } from "@hono/zod-validator";
6530
- import { Hono as Hono27 } from "hono";
6856
+ import { zValidator as zValidator11 } from "@hono/zod-validator";
6857
+ import { Hono as Hono26 } from "hono";
6531
6858
  import { streamSSE as streamSSE6 } from "hono/streaming";
6532
- import { z as z12 } from "zod";
6859
+ import { z as z11 } from "zod";
6533
6860
 
6534
6861
  // src/lib/bridge-outbound.ts
6535
6862
  function extractContent(contentBlocks) {
@@ -6608,11 +6935,11 @@ async function fanOutToMinds2(opts) {
6608
6935
  const mindParticipants = participants.filter((p) => p.userType === "mind");
6609
6936
  const participantNames = participants.map((p) => p.username);
6610
6937
  const isDM = opts.isDM ?? participants.length === 2;
6611
- const channelEntryType = opts.channelEntryType ?? (isDM ? "dm" : "group");
6612
- const { getMindManager: getMindManager2 } = await import("./mind-manager-S6ILZVX3.js");
6613
- const { getSleepManagerIfReady } = await import("./sleep-manager-WMVG2VCL.js");
6938
+ const channelEntryType = opts.channelEntryType ?? (isDM ? "dm" : "channel");
6939
+ const { getMindManager: getMindManager2 } = await import("./mind-manager-YFCOIAAX.js");
6940
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-O7YQFCV5.js");
6614
6941
  const manager = getMindManager2();
6615
- const sm = getSleepManagerIfReady();
6942
+ const sm = getSleepManagerIfReady2();
6616
6943
  const targetMinds = mindParticipants.map((ap) => {
6617
6944
  const key = opts.targetName ? opts.targetName(ap.username) : ap.username;
6618
6945
  if (manager.isRunning(key) || sm?.isSleeping(ap.username)) return ap.username;
@@ -6659,24 +6986,24 @@ async function fanOutToMinds2(opts) {
6659
6986
  });
6660
6987
  }
6661
6988
  }
6662
- var fileSchema = z12.object({
6663
- filename: z12.string(),
6664
- data: z12.string()
6989
+ var fileSchema = z11.object({
6990
+ filename: z11.string(),
6991
+ data: z11.string()
6665
6992
  // base64
6666
6993
  });
6667
- var chatSchema = z12.object({
6668
- message: z12.string().optional(),
6669
- conversationId: z12.string().optional(),
6670
- sender: z12.string().optional(),
6671
- images: z12.array(
6672
- z12.object({
6673
- media_type: z12.string(),
6674
- data: z12.string()
6994
+ var chatSchema = z11.object({
6995
+ message: z11.string().optional(),
6996
+ conversationId: z11.string().optional(),
6997
+ sender: z11.string().optional(),
6998
+ images: z11.array(
6999
+ z11.object({
7000
+ media_type: z11.string(),
7001
+ data: z11.string()
6675
7002
  })
6676
7003
  ).optional(),
6677
- files: z12.array(fileSchema).optional()
7004
+ files: z11.array(fileSchema).optional()
6678
7005
  });
6679
- var app27 = new Hono27().post("/:name/chat", zValidator12("json", chatSchema), async (c) => {
7006
+ var app26 = new Hono26().post("/:name/chat", zValidator11("json", chatSchema), async (c) => {
6680
7007
  const name = c.req.param("name");
6681
7008
  const baseName = await getBaseName(name);
6682
7009
  const entry = await findMind(baseName);
@@ -6771,7 +7098,7 @@ var app27 = new Hono27().post("/:name/chat", zValidator12("json", chatSchema), a
6771
7098
  senderName,
6772
7099
  convTitle,
6773
7100
  isDM,
6774
- channelEntryType: conv?.type === "channel" ? "group" : isDM ? "dm" : "group",
7101
+ channelEntryType: isDM ? "dm" : "channel",
6775
7102
  slugExtra: conv ? { convType: conv.type, convName: conv.name } : void 0,
6776
7103
  targetName: (username) => username === baseName ? name : username
6777
7104
  });
@@ -6802,15 +7129,15 @@ var app27 = new Hono27().post("/:name/chat", zValidator12("json", chatSchema), a
6802
7129
  });
6803
7130
  });
6804
7131
  });
6805
- var unifiedChatSchema2 = z12.object({
6806
- message: z12.string().optional(),
6807
- conversationId: z12.string(),
6808
- images: z12.array(z12.object({ media_type: z12.string(), data: z12.string() })).optional(),
6809
- files: z12.array(fileSchema).optional()
7132
+ var unifiedChatSchema2 = z11.object({
7133
+ message: z11.string().optional(),
7134
+ conversationId: z11.string(),
7135
+ images: z11.array(z11.object({ media_type: z11.string(), data: z11.string() })).optional(),
7136
+ files: z11.array(fileSchema).optional()
6810
7137
  });
6811
- var unifiedChatApp = new Hono27().post(
7138
+ var unifiedChatApp = new Hono26().post(
6812
7139
  "/chat",
6813
- zValidator12("json", unifiedChatSchema2),
7140
+ zValidator11("json", unifiedChatSchema2),
6814
7141
  async (c) => {
6815
7142
  const user = c.get("user");
6816
7143
  const body = c.req.valid("json");
@@ -6879,24 +7206,24 @@ var unifiedChatApp = new Hono27().post(
6879
7206
  senderName,
6880
7207
  convTitle: conv.title,
6881
7208
  isDM,
6882
- channelEntryType: conv.type === "channel" ? "group" : isDM ? "dm" : "group",
7209
+ channelEntryType: isDM ? "dm" : "channel",
6883
7210
  slugExtra: { convType: conv.type, convName: conv.name }
6884
7211
  });
6885
7212
  return c.json({ ok: true, conversationId: body.conversationId });
6886
7213
  }
6887
7214
  );
6888
- var chat_default2 = app27;
7215
+ var chat_default2 = app26;
6889
7216
 
6890
7217
  // src/web/api/volute/conversations.ts
6891
- import { zValidator as zValidator13 } from "@hono/zod-validator";
6892
- import { Hono as Hono28 } from "hono";
6893
- import { z as z13 } from "zod";
6894
- var createConvSchema = z13.object({
6895
- title: z13.string().optional(),
6896
- participantIds: z13.array(z13.number()).optional(),
6897
- participantNames: z13.array(z13.string()).optional()
7218
+ import { zValidator as zValidator12 } from "@hono/zod-validator";
7219
+ import { Hono as Hono27 } from "hono";
7220
+ import { z as z12 } from "zod";
7221
+ var createConvSchema = z12.object({
7222
+ title: z12.string().optional(),
7223
+ participantIds: z12.array(z12.number()).optional(),
7224
+ participantNames: z12.array(z12.string()).optional()
6898
7225
  });
6899
- var app28 = new Hono28().get("/:name/conversations", async (c) => {
7226
+ var app27 = new Hono27().get("/:name/conversations", async (c) => {
6900
7227
  const name = c.req.param("name");
6901
7228
  const user = c.get("user");
6902
7229
  let lookupId = user.id;
@@ -6907,7 +7234,7 @@ var app28 = new Hono28().get("/:name/conversations", async (c) => {
6907
7234
  const all = await listConversationsForUser(lookupId);
6908
7235
  const convs = all.filter((c2) => c2.mind_name === name || c2.type === "channel");
6909
7236
  return c.json(convs);
6910
- }).post("/:name/conversations", zValidator13("json", createConvSchema), async (c) => {
7237
+ }).post("/:name/conversations", zValidator12("json", createConvSchema), async (c) => {
6911
7238
  const name = c.req.param("name");
6912
7239
  const user = c.get("user");
6913
7240
  const body = c.req.valid("json");
@@ -6927,7 +7254,7 @@ var app28 = new Hono28().get("/:name/conversations", async (c) => {
6927
7254
  if (hyphenIdx > 0) {
6928
7255
  const prefix = pname.slice(0, hyphenIdx);
6929
7256
  if (["discord", "slack", "telegram"].includes(prefix)) {
6930
- existing = await getUserByUsername(prefix + ":" + pname.slice(hyphenIdx + 1));
7257
+ existing = await getUserByUsername(`${prefix}:${pname.slice(hyphenIdx + 1)}`);
6931
7258
  }
6932
7259
  }
6933
7260
  }
@@ -6949,6 +7276,9 @@ var app28 = new Hono28().get("/:name/conversations", async (c) => {
6949
7276
  if (!u) return c.json({ error: `User ${id} not found` }, 400);
6950
7277
  }
6951
7278
  const participantIds = [...participantSet];
7279
+ if (participantIds.length > 2) {
7280
+ return c.json({ error: "Use channels for multi-participant conversations" }, 400);
7281
+ }
6952
7282
  if (participantIds.length === 2) {
6953
7283
  const existingId = await findDMConversation(name, participantIds);
6954
7284
  if (existingId) {
@@ -6990,18 +7320,18 @@ var app28 = new Hono28().get("/:name/conversations", async (c) => {
6990
7320
  if (!deleted) return c.json({ error: "Conversation not found" }, 404);
6991
7321
  return c.json({ ok: true });
6992
7322
  });
6993
- var conversations_default2 = app28;
7323
+ var conversations_default2 = app27;
6994
7324
 
6995
7325
  // src/web/api/volute/user-conversations.ts
6996
- import { zValidator as zValidator14 } from "@hono/zod-validator";
6997
- import { Hono as Hono29 } from "hono";
7326
+ import { zValidator as zValidator13 } from "@hono/zod-validator";
7327
+ import { Hono as Hono28 } from "hono";
6998
7328
  import { streamSSE as streamSSE7 } from "hono/streaming";
6999
- import { z as z14 } from "zod";
7000
- var createSchema4 = z14.object({
7001
- title: z14.string().optional(),
7002
- participantNames: z14.array(z14.string()).min(1)
7329
+ import { z as z13 } from "zod";
7330
+ var createSchema3 = z13.object({
7331
+ title: z13.string().optional(),
7332
+ participantNames: z13.array(z13.string()).min(1)
7003
7333
  });
7004
- var app29 = new Hono29().use("*", authMiddleware).get("/", async (c) => {
7334
+ var app28 = new Hono28().use("*", authMiddleware).get("/", async (c) => {
7005
7335
  const user = c.get("user");
7006
7336
  const convs = await listConversationsWithParticipants(user.id);
7007
7337
  return c.json(convs);
@@ -7013,7 +7343,7 @@ var app29 = new Hono29().use("*", authMiddleware).get("/", async (c) => {
7013
7343
  }
7014
7344
  const msgs = await getMessages(id);
7015
7345
  return c.json(msgs);
7016
- }).post("/", zValidator14("json", createSchema4), async (c) => {
7346
+ }).post("/", zValidator13("json", createSchema3), async (c) => {
7017
7347
  const user = c.get("user");
7018
7348
  const body = c.req.valid("json");
7019
7349
  const participantIds = /* @__PURE__ */ new Set();
@@ -7037,6 +7367,9 @@ var app29 = new Hono29().use("*", authMiddleware).get("/", async (c) => {
7037
7367
  if (!firstMindName) {
7038
7368
  return c.json({ error: "At least one mind participant is required" }, 400);
7039
7369
  }
7370
+ if (participantIds.size > 2) {
7371
+ return c.json({ error: "Use channels for multi-participant conversations" }, 400);
7372
+ }
7040
7373
  const conv = await createConversation(firstMindName, "volute", {
7041
7374
  userId: user.id !== 0 ? user.id : void 0,
7042
7375
  title: body.title,
@@ -7075,12 +7408,12 @@ var app29 = new Hono29().use("*", authMiddleware).get("/", async (c) => {
7075
7408
  if (!deleted) return c.json({ error: "Conversation not found" }, 404);
7076
7409
  return c.json({ ok: true });
7077
7410
  });
7078
- var user_conversations_default = app29;
7411
+ var user_conversations_default = app28;
7079
7412
 
7080
7413
  // src/web/app.ts
7081
7414
  var httpLog = logger_default.child("http");
7082
- var app30 = new Hono30();
7083
- app30.onError((err, c) => {
7415
+ var app29 = new Hono29();
7416
+ app29.onError((err, c) => {
7084
7417
  if (err instanceof HTTPException) {
7085
7418
  return err.getResponse();
7086
7419
  }
@@ -7091,10 +7424,10 @@ app30.onError((err, c) => {
7091
7424
  });
7092
7425
  return c.json({ error: "Internal server error" }, 500);
7093
7426
  });
7094
- app30.notFound((c) => {
7427
+ app29.notFound((c) => {
7095
7428
  return c.json({ error: "Not found" }, 404);
7096
7429
  });
7097
- app30.use("*", async (c, next) => {
7430
+ app29.use("*", async (c, next) => {
7098
7431
  const start = Date.now();
7099
7432
  await next();
7100
7433
  const duration = Date.now() - start;
@@ -7105,7 +7438,7 @@ app30.use("*", async (c, next) => {
7105
7438
  httpLog.debug("request", data);
7106
7439
  }
7107
7440
  });
7108
- app30.get("/api/health", (c) => {
7441
+ app29.get("/api/health", (c) => {
7109
7442
  let version = "unknown";
7110
7443
  let cached = null;
7111
7444
  try {
@@ -7120,40 +7453,44 @@ app30.get("/api/health", (c) => {
7120
7453
  ...cached?.updateAvailable ? { updateAvailable: true, latest: cached.latest } : {}
7121
7454
  });
7122
7455
  });
7123
- app30.use("/api/*", bodyLimit({ maxSize: 10 * 1024 * 1024 }));
7124
- app30.use("/api/*", csrf());
7125
- app30.use("/api/activity/*", authMiddleware);
7126
- app30.use("/api/minds/*", authMiddleware);
7127
- app30.use("/api/conversations/*", authMiddleware);
7128
- app30.use("/api/volute/*", authMiddleware);
7129
- app30.use("/api/system/*", authMiddleware);
7130
- app30.use("/api/env/*", authMiddleware);
7131
- app30.use("/api/prompts/*", authMiddleware);
7132
- app30.use("/api/skills/*", authMiddleware);
7133
- app30.use("/api/notes/*", authMiddleware);
7134
- app30.use("/api/bridges/*", authMiddleware);
7135
- app30.use("/api/v1/*", authMiddleware);
7136
- app30.route("/pages", pages_default);
7137
- app30.route("/public", public_files_default);
7138
- var routes = app30.route("/api/activity", activity_default).route("/api/keys", keys_default).route("/api/auth", auth_default).route("/api/system", system_default).route("/api/system", update_default).route("/api/minds", minds_default).route("/api/minds", chat_default2).route("/api/minds", schedules_default).route("/api/minds", logs_default).route("/api/minds", typing_default).route("/api/minds", variants_default).route("/api/minds", file_sharing_default).route("/api/minds", files_default).route("/api/minds", channels_default).route("/api/minds", shared_default).route("/api/minds", env_default).route("/api/minds", mind_skills_default).route("/api/minds", conversations_default2).route("/api/env", sharedEnvApp).route("/api/notes", notes_default).route("/api/prompts", prompts_default).route("/api/skills", skills_default).route("/api/conversations", user_conversations_default).route("/api/volute/channels", channels_default2).route("/api/volute", unifiedChatApp).route("/api/bridges", bridges_default).route("/api/v1/conversations", conversations_default).route("/api/v1/events", events_default).route("/api/v1", chat_default);
7139
- app30.route("/api/v1/minds", minds_default);
7140
- app30.route("/api/v1/minds", typing_default);
7141
- app30.route("/api/v1/minds", variants_default);
7142
- app30.route("/api/v1/minds", files_default);
7143
- app30.route("/api/v1/minds", env_default);
7144
- app30.route("/api/v1/minds", mind_skills_default);
7145
- app30.route("/api/v1/minds", schedules_default);
7146
- app30.route("/api/v1/minds", logs_default);
7147
- app30.route("/api/v1/system", system_default);
7148
- app30.route("/api/v1/system", update_default);
7149
- app30.route("/api/v1/prompts", prompts_default);
7150
- app30.route("/api/v1/skills", skills_default);
7151
- app30.route("/api/v1/env", sharedEnvApp);
7152
- app30.route("/api/v1/channels", channels_default2);
7153
- var app_default = app30;
7456
+ app29.use("/api/*", bodyLimit({ maxSize: 10 * 1024 * 1024 }));
7457
+ app29.use("/api/*", csrf());
7458
+ app29.use("/api/activity/*", authMiddleware);
7459
+ app29.use("/api/minds/*", authMiddleware);
7460
+ app29.use("/api/conversations/*", authMiddleware);
7461
+ app29.use("/api/volute/*", authMiddleware);
7462
+ app29.use("/api/system/*", authMiddleware);
7463
+ app29.use("/api/env/*", authMiddleware);
7464
+ app29.use("/api/prompts/*", authMiddleware);
7465
+ app29.use("/api/skills/*", authMiddleware);
7466
+ app29.use("/api/extensions/*", authMiddleware);
7467
+ app29.use("/api/bridges/*", authMiddleware);
7468
+ app29.use("/api/v1/*", authMiddleware);
7469
+ app29.route("/public", public_files_default);
7470
+ var routes = app29.route("/api/activity", activity_default).route("/api/keys", keys_default).route("/api/auth", auth_default).route("/api/system", system_default).route("/api/system", update_default).route("/api/minds", minds_default).route("/api/minds", chat_default2).route("/api/minds", schedules_default).route("/api/minds", logs_default).route("/api/minds", typing_default).route("/api/minds", variants_default).route("/api/minds", file_sharing_default).route("/api/minds", files_default).route("/api/minds", channels_default).route("/api/minds", shared_default).route("/api/minds", env_default).route("/api/minds", mind_skills_default).route("/api/minds", conversations_default2).route("/api/env", sharedEnvApp).route("/api/prompts", prompts_default).route("/api/skills", skills_default).route("/api/conversations", user_conversations_default).route("/api/volute/channels", channels_default2).route("/api/volute", unifiedChatApp).route("/api/bridges", bridges_default).route("/api/extensions", extensions_default).route("/api/v1/conversations", conversations_default).route("/api/v1/events", events_default).route("/api/v1", chat_default);
7471
+ app29.route("/api/v1/minds", minds_default);
7472
+ app29.route("/api/v1/minds", typing_default);
7473
+ app29.route("/api/v1/minds", variants_default);
7474
+ app29.route("/api/v1/minds", files_default);
7475
+ app29.route("/api/v1/minds", env_default);
7476
+ app29.route("/api/v1/minds", mind_skills_default);
7477
+ app29.route("/api/v1/minds", schedules_default);
7478
+ app29.route("/api/v1/minds", logs_default);
7479
+ app29.route("/api/v1/system", system_default);
7480
+ app29.route("/api/v1/system", update_default);
7481
+ app29.route("/api/v1/prompts", prompts_default);
7482
+ app29.route("/api/v1/skills", skills_default);
7483
+ app29.route("/api/v1/env", sharedEnvApp);
7484
+ app29.route("/api/v1/channels", channels_default2);
7485
+ var app_default = app29;
7154
7486
 
7155
7487
  // src/web/server.ts
7156
- var MIME_TYPES3 = {
7488
+ import { existsSync as existsSync17 } from "fs";
7489
+ import { readFile as readFile3, stat as stat3 } from "fs/promises";
7490
+ import { createServer as createHttpsServer } from "https";
7491
+ import { dirname as dirname2, extname as extname4, resolve as resolve21 } from "path";
7492
+ import { serve } from "@hono/node-server";
7493
+ var MIME_TYPES2 = {
7157
7494
  ".html": "text/html",
7158
7495
  ".js": "application/javascript",
7159
7496
  ".css": "text/css",
@@ -7171,7 +7508,7 @@ async function startServer({
7171
7508
  let searchDir = dirname2(new URL(import.meta.url).pathname);
7172
7509
  for (let i = 0; i < 5; i++) {
7173
7510
  const candidate = resolve21(searchDir, "dist", "web-assets");
7174
- if (existsSync16(candidate)) {
7511
+ if (existsSync17(candidate)) {
7175
7512
  assetsDir = candidate;
7176
7513
  break;
7177
7514
  }
@@ -7180,20 +7517,20 @@ async function startServer({
7180
7517
  if (assetsDir) {
7181
7518
  app_default.get("*", async (c) => {
7182
7519
  const urlPath = new URL(c.req.url).pathname;
7183
- if (urlPath.startsWith("/api/")) return c.notFound();
7520
+ if (urlPath.startsWith("/api/") || urlPath.startsWith("/ext/")) return c.notFound();
7184
7521
  const filePath = resolve21(assetsDir, urlPath.slice(1));
7185
7522
  if (!filePath.startsWith(assetsDir)) return c.text("Forbidden", 403);
7186
- const s = await stat4(filePath).catch(() => null);
7523
+ const s = await stat3(filePath).catch(() => null);
7187
7524
  if (s?.isFile()) {
7188
- const ext = extname5(filePath);
7189
- const mime = MIME_TYPES3[ext] || "application/octet-stream";
7190
- const body = await readFile4(filePath);
7525
+ const ext = extname4(filePath);
7526
+ const mime = MIME_TYPES2[ext] || "application/octet-stream";
7527
+ const body = await readFile3(filePath);
7191
7528
  return c.body(body, 200, { "Content-Type": mime });
7192
7529
  }
7193
7530
  const indexPath = resolve21(assetsDir, "index.html");
7194
- const indexStat = await stat4(indexPath).catch(() => null);
7531
+ const indexStat = await stat3(indexPath).catch(() => null);
7195
7532
  if (indexStat?.isFile()) {
7196
- const body = await readFile4(indexPath, "utf-8");
7533
+ const body = await readFile3(indexPath, "utf-8");
7197
7534
  return c.html(body);
7198
7535
  }
7199
7536
  return c.text("Not found", 404);
@@ -7270,7 +7607,7 @@ async function startDaemon(opts) {
7270
7607
  }
7271
7608
  const DAEMON_PID_PATH = resolve22(systemDir, "daemon.pid");
7272
7609
  const DAEMON_JSON_PATH = resolve22(systemDir, "daemon.json");
7273
- mkdirSync11(home, { recursive: true });
7610
+ mkdirSync12(home, { recursive: true });
7274
7611
  ensureSystemDir();
7275
7612
  migrateToSystemDir();
7276
7613
  migrateAgentsToMinds();
@@ -7279,16 +7616,29 @@ async function startDaemon(opts) {
7279
7616
  } catch (err) {
7280
7617
  logger_default.warn("failed to initialize shared repo", logger_default.errorData(err));
7281
7618
  }
7282
- await (await import("./db-IC4J52XQ.js")).getDb();
7283
- const { migrateRegistryToDb } = await import("./migrate-registry-to-db-XC7T5B7P.js");
7619
+ await (await import("./db-HMFPIRO2.js")).getDb();
7620
+ const { migrateRegistryToDb } = await import("./migrate-registry-to-db-FK35IPEH.js");
7284
7621
  migrateRegistryToDb();
7285
- const { initSandbox } = await import("./sandbox-5BW5HPXM.js");
7622
+ try {
7623
+ const { migrateGroupDMsToChannels } = await import("./conversations-7KVQV7EZ.js");
7624
+ await migrateGroupDMsToChannels();
7625
+ } catch (err) {
7626
+ logger_default.error("failed to migrate group DMs to channels", logger_default.errorData(err));
7627
+ }
7628
+ const { initSandbox } = await import("./sandbox-O5FUSF43.js");
7286
7629
  await initSandbox();
7287
7630
  try {
7288
7631
  await syncBuiltinSkills();
7289
7632
  } catch (err) {
7290
7633
  logger_default.error("failed to sync built-in skills", logger_default.errorData(err));
7291
7634
  }
7635
+ try {
7636
+ await loadAllExtensions(app_default, authMiddleware);
7637
+ notifyExtensionsDaemonStart();
7638
+ } catch (err) {
7639
+ logger_default.error("failed to load extensions", logger_default.errorData(err));
7640
+ }
7641
+ await initDefaultSkills();
7292
7642
  try {
7293
7643
  await ensureSystemChannel();
7294
7644
  } catch (err) {
@@ -7297,7 +7647,7 @@ async function startDaemon(opts) {
7297
7647
  const token = process.env.VOLUTE_DAEMON_TOKEN || randomBytes(32).toString("hex");
7298
7648
  let tls;
7299
7649
  if (opts.tailscale) {
7300
- const { getTailscaleTls } = await import("./tailscale-NY5MUMY3.js");
7650
+ const { getTailscaleTls } = await import("./tailscale-BM72RXCJ.js");
7301
7651
  const tlsConfig = await getTailscaleTls();
7302
7652
  tls = { key: tlsConfig.key, cert: tlsConfig.cert };
7303
7653
  logger_default.info("Tailscale HTTPS enabled", { hostname: tlsConfig.hostname });
@@ -7318,11 +7668,11 @@ async function startDaemon(opts) {
7318
7668
  process.env.VOLUTE_DAEMON_TOKEN = token;
7319
7669
  process.env.VOLUTE_DAEMON_PORT = String(daemonPort);
7320
7670
  process.env.VOLUTE_DAEMON_HOSTNAME = hostname;
7321
- writeFileSync10(DAEMON_PID_PATH, myPid, { mode: 420 });
7671
+ writeFileSync11(DAEMON_PID_PATH, myPid, { mode: 420 });
7322
7672
  const daemonConfig = { port, hostname, token };
7323
7673
  if (internalPort) daemonConfig.internalPort = internalPort;
7324
7674
  if (tls) daemonConfig.tls = true;
7325
- writeFileSync10(DAEMON_JSON_PATH, `${JSON.stringify(daemonConfig, null, 2)}
7675
+ writeFileSync11(DAEMON_JSON_PATH, `${JSON.stringify(daemonConfig, null, 2)}
7326
7676
  `, { mode: 420 });
7327
7677
  const delivery = initDeliveryManager();
7328
7678
  const manager = initMindManager();
@@ -7378,7 +7728,7 @@ async function startDaemon(opts) {
7378
7728
  bridgeManager.startBridges(daemonPort).catch((err) => {
7379
7729
  logger_default.warn("failed to start bridges", logger_default.errorData(err));
7380
7730
  });
7381
- import("./cloud-sync-HDL6PHZI.js").then(
7731
+ import("./cloud-sync-JV4LJOK3.js").then(
7382
7732
  ({ consumeQueuedMessages }) => consumeQueuedMessages().catch((err) => {
7383
7733
  logger_default.warn("failed to consume queued cloud messages", logger_default.errorData(err));
7384
7734
  })
@@ -7386,7 +7736,7 @@ async function startDaemon(opts) {
7386
7736
  logger_default.warn("failed to load cloud-sync module", logger_default.errorData(err));
7387
7737
  });
7388
7738
  try {
7389
- const { backfillTemplateHashes, notifyVersionUpdate } = await import("./version-notify-JDUF4HQJ.js");
7739
+ const { backfillTemplateHashes, notifyVersionUpdate } = await import("./version-notify-2NTWVEHL.js");
7390
7740
  backfillTemplateHashes();
7391
7741
  notifyVersionUpdate().catch((err) => {
7392
7742
  logger_default.warn("failed to send version update notifications", logger_default.errorData(err));
@@ -7409,13 +7759,13 @@ async function startDaemon(opts) {
7409
7759
  logger_default.info(`running on ${hostname}:${port}, pid ${myPid}`);
7410
7760
  function cleanup() {
7411
7761
  try {
7412
- if (readFileSync13(DAEMON_PID_PATH, "utf-8").trim() === myPid) {
7762
+ if (readFileSync14(DAEMON_PID_PATH, "utf-8").trim() === myPid) {
7413
7763
  unlinkSync2(DAEMON_PID_PATH);
7414
7764
  }
7415
7765
  } catch {
7416
7766
  }
7417
7767
  try {
7418
- const data = JSON.parse(readFileSync13(DAEMON_JSON_PATH, "utf-8"));
7768
+ const data = JSON.parse(readFileSync14(DAEMON_JSON_PATH, "utf-8"));
7419
7769
  if (data.token === token) {
7420
7770
  unlinkSync2(DAEMON_JSON_PATH);
7421
7771
  }
@@ -7437,7 +7787,7 @@ async function startDaemon(opts) {
7437
7787
  }
7438
7788
  };
7439
7789
  try {
7440
- safe("stopAllWatchers", stopAllWatchers);
7790
+ safe("notifyExtensionsDaemonStop", notifyExtensionsDaemonStop);
7441
7791
  safe("stopAllActivityTrackers", stopAll);
7442
7792
  safe("unsubscribeWebhook", unsubscribeWebhook);
7443
7793
  safe("sleepManager.stop", () => sleepManager.stop());