volute 0.35.0 → 0.36.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 (123) hide show
  1. package/dist/{activity-events-ZW4SDL2C.js → activity-events-PWOGSMRL.js} +4 -4
  2. package/dist/{ai-service-LURBEDDB.js → ai-service-GSZWIETO.js} +5 -5
  3. package/dist/{archive-ESU2FUN4.js → archive-Y2YEOCGB.js} +3 -3
  4. package/dist/{auth-WX4TESEI.js → auth-YTQME4EV.js} +5 -5
  5. package/dist/{chat-QXAJF3FU.js → chat-ED7YOGKO.js} +4 -4
  6. package/dist/{chunk-AOB6GVRM.js → chunk-46DYYHN6.js} +8 -3
  7. package/dist/{chunk-5XJYUFZH.js → chunk-6F3YNULE.js} +68 -22
  8. package/dist/{chunk-BDYXIWA5.js → chunk-75AJ54GM.js} +13 -2
  9. package/dist/{chunk-5N7Y5WAM.js → chunk-7PTQGPJY.js} +28 -12
  10. package/dist/{chunk-CORXD635.js → chunk-B35VNNSS.js} +3 -3
  11. package/dist/{chunk-PWQ2ITYG.js → chunk-BOLJUV77.js} +4 -4
  12. package/dist/{chunk-ZSR72JB3.js → chunk-CU6OFXMM.js} +1 -1
  13. package/dist/{chunk-2TGZJFAT.js → chunk-DJT5Y4UF.js} +3 -3
  14. package/dist/{chunk-XRQSAMX2.js → chunk-DMV5P2LU.js} +3 -3
  15. package/dist/{chunk-WJPROOU5.js → chunk-DQ7VBXAP.js} +635 -3692
  16. package/dist/{chunk-IJHIXLVN.js → chunk-GBDVNPN2.js} +13 -11
  17. package/dist/{chunk-WZRZFFCL.js → chunk-IIWF2IPD.js} +146 -186
  18. package/dist/{chunk-F7ZNLYKZ.js → chunk-KAB6UGOL.js} +2 -2
  19. package/dist/{chunk-VHJRZM2S.js → chunk-L72WYMF7.js} +2 -2
  20. package/dist/{chunk-NJK5SDGR.js → chunk-LGNUFVMR.js} +1 -1
  21. package/dist/{chunk-FT5KETXZ.js → chunk-M5RYAA5I.js} +2 -2
  22. package/dist/{chunk-J6CJQDWI.js → chunk-N2AUHW4C.js} +2 -2
  23. package/dist/{chunk-BMZQYACC.js → chunk-NUX47Y2V.js} +19 -4
  24. package/dist/{chunk-AN2W47GW.js → chunk-PJ4IPTIN.js} +2 -2
  25. package/dist/{chunk-N446KRP7.js → chunk-PY557GDR.js} +2 -2
  26. package/dist/{chunk-MDJGMOSD.js → chunk-PZYJBOQP.js} +6 -6
  27. package/dist/chunk-RG5TOL4O.js +18 -0
  28. package/dist/{chunk-N5LMGYXX.js → chunk-SWW6AUVW.js} +2 -2
  29. package/dist/{chunk-BKF4WQCY.js → chunk-T2TP6ZC6.js} +20 -8
  30. package/dist/{chunk-VY3RB2V7.js → chunk-TWAN7ZNO.js} +3 -3
  31. package/dist/{chunk-QCH6K235.js → chunk-UI7RPV2B.js} +1 -1
  32. package/dist/{chunk-QWTR6AWZ.js → chunk-X2J7QUFH.js} +2 -2
  33. package/dist/{chunk-A2ZLHBHG.js → chunk-YDBAY3NA.js} +2 -2
  34. package/dist/{chunk-BV65KRHM.js → chunk-YTWZORJN.js} +2 -2
  35. package/dist/{chunk-OTC67N2Z.js → chunk-ZTVKQOU7.js} +1 -1
  36. package/dist/cli.js +16 -16
  37. package/dist/{cloud-sync-6JL4C24T.js → cloud-sync-BOCZSDIA.js} +19 -20
  38. package/dist/connectors/discord-bridge.js +3 -3
  39. package/dist/connectors/slack-bridge.js +3 -3
  40. package/dist/connectors/telegram-bridge.js +3 -3
  41. package/dist/{conversations-2PW57WO2.js → conversations-HH3CJD4E.js} +15 -9
  42. package/dist/{create-UVCK2CS6.js → create-QBEPSD2Z.js} +1 -1
  43. package/dist/{daemon-restart-HSZ3BCX5.js → daemon-restart-SIR3UR4B.js} +10 -10
  44. package/dist/daemon.js +446 -328
  45. package/dist/{db-BVBJ57TU.js → db-URORGSXQ.js} +2 -2
  46. package/dist/delivery-manager-WTGIPBGY.js +30 -0
  47. package/dist/{delivery-router-HEJSJAHQ.js → delivery-router-VSULHXNH.js} +4 -4
  48. package/dist/down-DGGLZ5TA.js +17 -0
  49. package/dist/{exec-PY7THYH4.js → exec-X3C6ZZTQ.js} +4 -4
  50. package/dist/{export-OAS6QVBN.js → export-HTFOHOKL.js} +3 -3
  51. package/dist/{extension-D74CNM7G.js → extension-AKZ46YSL.js} +22 -3
  52. package/dist/{extensions-XDDFY72A.js → extensions-OOSFVH7U.js} +21 -20
  53. package/dist/{files-CWTK6V3H.js → files-H2YLRD37.js} +3 -3
  54. package/dist/{import-5A3T7QV4.js → import-OL5BZX7S.js} +6 -6
  55. package/dist/{isolation-TK5RX2WM.js → isolation-N74RWOUX.js} +3 -3
  56. package/dist/{list-PDMQM7ZV.js → list-GJ4RUQQT.js} +7 -1
  57. package/dist/{login-7TE6CIZF.js → login-JXRVMBRB.js} +2 -2
  58. package/dist/{logout-T4XS6LRU.js → logout-FW243JBU.js} +2 -2
  59. package/dist/message-delivery-YORUXKDQ.js +40 -0
  60. package/dist/{mind-5IEYKV7I.js → mind-6VJJHF65.js} +6 -6
  61. package/dist/{mind-activity-tracker-QBLIV7ZJ.js → mind-activity-tracker-66UVYIFW.js} +5 -5
  62. package/dist/{mind-history-IE2QH7U5.js → mind-history-MII2SK7F.js} +81 -14
  63. package/dist/mind-manager-TJ2SUPRX.js +30 -0
  64. package/dist/mind-service-E7FM2WZF.js +36 -0
  65. package/dist/{package-D2FSVFAX.js → package-3W2MEXHB.js} +5 -5
  66. package/dist/{read-67VRP2DO.js → read-ZUDG4JWU.js} +4 -4
  67. package/dist/{registry-GBSNW3HG.js → registry-YPHK534W.js} +2 -2
  68. package/dist/{sandbox-R37VIU36.js → sandbox-LP6YRAXS.js} +5 -5
  69. package/dist/scheduler-FRJ5DK24.js +30 -0
  70. package/dist/{schema-XVZ2CLKW.js → schema-MISD3JFG.js} +3 -1
  71. package/dist/{seed-EQORWX77.js → seed-CEC4RC23.js} +1 -1
  72. package/dist/{seed-cmd-ZM2XGVU2.js → seed-cmd-WTTG7SRQ.js} +2 -2
  73. package/dist/{seed-create-DRWGGHEI.js → seed-create-M6RCC6RP.js} +3 -3
  74. package/dist/{seed-sprout-JYXGXOP3.js → seed-sprout-ZKCHFJKH.js} +10 -10
  75. package/dist/{send-JBJJQ7CA.js → send-LXUT2GGR.js} +3 -3
  76. package/dist/{service-WNPCNHOX.js → service-M6N3RUYU.js} +5 -5
  77. package/dist/{setup-BJ4YAY26.js → setup-PJOF5UV5.js} +7 -7
  78. package/dist/{setup-RHJRFURI.js → setup-PMJHCZQX.js} +5 -3
  79. package/dist/skills/tending/SKILL.md +52 -0
  80. package/dist/{skills-EKMCQ46K.js → skills-2PTRTBQP.js} +7 -7
  81. package/dist/sleep-manager-WAZWMFJT.js +34 -0
  82. package/dist/spirit-6KVDIROQ.js +24 -0
  83. package/dist/{sprout-HE4TITMK.js → sprout-WX2FFYLP.js} +1 -1
  84. package/dist/src-FQE4BHRG.js +617 -0
  85. package/dist/src-GW6FP6VL.js +425 -0
  86. package/dist/src-QEOLMAYC.js +2133 -0
  87. package/dist/{status-ZK34WYIM.js → status-3IVSLJDN.js} +6 -6
  88. package/dist/system-chat-2IFS5HCX.js +34 -0
  89. package/dist/{tailscale-ZIZ2HWJ5.js → tailscale-DZU4WM3E.js} +3 -3
  90. package/dist/{template-hash-A7FNHTB7.js → template-hash-6ITI3WC4.js} +2 -2
  91. package/dist/up-4SCIUIMG.js +19 -0
  92. package/dist/{update-ANE5ZM7F.js → update-RIQYUPVN.js} +6 -6
  93. package/dist/{update-check-UV55CBEP.js → update-check-4TIJKVGD.js} +3 -3
  94. package/dist/{version-notify-FXSEMXWW.js → version-notify-UXSHBZ35.js} +21 -22
  95. package/dist/{volute-config-D2XVS2YI.js → volute-config-V7UFFBG3.js} +1 -1
  96. package/dist/web-assets/assets/index-C-eYso8Y.js +75 -0
  97. package/dist/web-assets/assets/index-CCv_fSte.css +1 -0
  98. package/dist/web-assets/index.html +2 -2
  99. package/drizzle/0006_channels.sql +17 -0
  100. package/drizzle/0007_drop_conversation_name_title.sql +11 -0
  101. package/drizzle/0008_performance_indexes.sql +6 -0
  102. package/drizzle/meta/0006_snapshot.json +7 -0
  103. package/drizzle/meta/0007_snapshot.json +7 -0
  104. package/drizzle/meta/_journal.json +21 -0
  105. package/package.json +5 -5
  106. package/templates/_base/home/.config/routes.json +2 -2
  107. package/templates/_base/home/VOLUTE.md +1 -2
  108. package/templates/_base/src/lib/format-prefix.ts +1 -7
  109. package/templates/claude/.init/.config/routes.json +2 -2
  110. package/templates/codex/.init/.config/routes.json +2 -2
  111. package/templates/pi/.init/.config/routes.json +2 -2
  112. package/dist/delivery-manager-H5ZVBMCQ.js +0 -31
  113. package/dist/down-74VXM45A.js +0 -17
  114. package/dist/message-delivery-GRC4W6P7.js +0 -41
  115. package/dist/mind-manager-HFLB5653.js +0 -31
  116. package/dist/mind-service-X2CAA6W6.js +0 -37
  117. package/dist/scheduler-Y7O4CJXL.js +0 -31
  118. package/dist/sleep-manager-7KFK3USC.js +0 -35
  119. package/dist/spirit-ZFRDXMG7.js +0 -23
  120. package/dist/system-chat-IDPHYHY4.js +0 -35
  121. package/dist/up-77ICEDEW.js +0 -19
  122. package/dist/web-assets/assets/index-BhxWKvbB.css +0 -1
  123. package/dist/web-assets/assets/index-CHVKJ9II.js +0 -75
package/dist/daemon.js CHANGED
@@ -3,10 +3,10 @@ import {
3
3
  checkForUpdate,
4
4
  checkForUpdateCached,
5
5
  getCurrentVersion
6
- } from "./chunk-OTC67N2Z.js";
6
+ } from "./chunk-ZTVKQOU7.js";
7
7
  import {
8
8
  computeTemplateHash
9
- } from "./chunk-F7ZNLYKZ.js";
9
+ } from "./chunk-KAB6UGOL.js";
10
10
  import {
11
11
  acceptPending,
12
12
  formatFileSize,
@@ -14,7 +14,7 @@ import {
14
14
  rejectPending,
15
15
  stageFile,
16
16
  validateFilePath
17
- } from "./chunk-BV65KRHM.js";
17
+ } from "./chunk-YTWZORJN.js";
18
18
  import {
19
19
  PROMPT_DEFAULTS,
20
20
  PROMPT_KEYS,
@@ -31,6 +31,7 @@ import {
31
31
  generateSystemReply,
32
32
  getActiveTurnId,
33
33
  getAllDiscoveredExtensions,
34
+ getAllDiscoveredExtensionsDetailed,
34
35
  getDeliveryManager,
35
36
  getExtensionStandardSkills,
36
37
  getLastToolUseEventId,
@@ -76,15 +77,15 @@ import {
76
77
  trackToolUse,
77
78
  uninstallNpmExtension,
78
79
  writeSystemsConfig
79
- } from "./chunk-WJPROOU5.js";
80
- import "./chunk-5XJYUFZH.js";
80
+ } from "./chunk-DQ7VBXAP.js";
81
+ import "./chunk-6F3YNULE.js";
81
82
  import {
82
83
  applyInitFiles,
83
84
  composeTemplate,
84
85
  copyTemplateToDir,
85
86
  findTemplatesRoot,
86
87
  listFiles
87
- } from "./chunk-AOB6GVRM.js";
88
+ } from "./chunk-46DYYHN6.js";
88
89
  import {
89
90
  SEED_SKILLS,
90
91
  STANDARD_SKILLS,
@@ -104,16 +105,12 @@ import {
104
105
  syncBuiltinSkills,
105
106
  uninstallSkill,
106
107
  updateSkill
107
- } from "./chunk-IJHIXLVN.js";
108
- import {
109
- extractTextContent
110
- } from "./chunk-QWTR6AWZ.js";
108
+ } from "./chunk-GBDVNPN2.js";
111
109
  import {
112
110
  getActiveMinds,
113
111
  onMindEvent,
114
112
  stopAll
115
- } from "./chunk-N446KRP7.js";
116
- import "./chunk-QHG4OMZL.js";
113
+ } from "./chunk-PY557GDR.js";
117
114
  import {
118
115
  approveUser,
119
116
  changePassword,
@@ -132,7 +129,7 @@ import {
132
129
  syncMindProfile,
133
130
  updateUserProfile,
134
131
  verifyUser
135
- } from "./chunk-XRQSAMX2.js";
132
+ } from "./chunk-DMV5P2LU.js";
136
133
  import {
137
134
  addMessage,
138
135
  createChannel,
@@ -140,7 +137,10 @@ import {
140
137
  deleteConversationForUser,
141
138
  findDMConversation,
142
139
  fireWebhook,
140
+ formatChannelSettings,
143
141
  getChannelByName,
142
+ getChannelName,
143
+ getChannelSettings,
144
144
  getConversation,
145
145
  getMessages,
146
146
  getMessagesPaginated,
@@ -159,17 +159,21 @@ import {
159
159
  markConversationRead,
160
160
  publish,
161
161
  setConversationPrivate,
162
- subscribe as subscribe2
163
- } from "./chunk-WZRZFFCL.js";
162
+ subscribe as subscribe2,
163
+ updateChannelSettings
164
+ } from "./chunk-IIWF2IPD.js";
164
165
  import {
165
166
  broadcast,
166
167
  subscribe
167
- } from "./chunk-CORXD635.js";
168
+ } from "./chunk-B35VNNSS.js";
168
169
  import {
169
170
  readVoluteConfig,
170
171
  writeVoluteConfig
171
- } from "./chunk-ZSR72JB3.js";
172
- import "./chunk-PWQ2ITYG.js";
172
+ } from "./chunk-CU6OFXMM.js";
173
+ import "./chunk-BOLJUV77.js";
174
+ import {
175
+ extractTextContent
176
+ } from "./chunk-X2J7QUFH.js";
173
177
  import {
174
178
  findBridgeForChannel,
175
179
  findOpenClawSession,
@@ -183,17 +187,17 @@ import {
183
187
  resolveChannelMapping,
184
188
  setBridgeConfig,
185
189
  setChannelMapping
186
- } from "./chunk-MDJGMOSD.js";
190
+ } from "./chunk-PZYJBOQP.js";
187
191
  import {
188
192
  loadMergedEnv,
189
193
  mindEnvPath,
190
194
  readEnv,
191
195
  sharedEnvPath,
192
196
  writeEnv
193
- } from "./chunk-A2ZLHBHG.js";
197
+ } from "./chunk-YDBAY3NA.js";
194
198
  import {
195
199
  isHomeOnlyArchive
196
- } from "./chunk-N5LMGYXX.js";
200
+ } from "./chunk-SWW6AUVW.js";
197
201
  import {
198
202
  getAiConfig,
199
203
  getAvailableModels,
@@ -209,16 +213,16 @@ import {
209
213
  setEnabledModels,
210
214
  setUtilityModel,
211
215
  unqualifyModelId
212
- } from "./chunk-FT5KETXZ.js";
216
+ } from "./chunk-M5RYAA5I.js";
213
217
  import {
214
218
  logBuffer,
215
219
  logger_default
216
- } from "./chunk-BKF4WQCY.js";
220
+ } from "./chunk-T2TP6ZC6.js";
217
221
  import {
218
222
  exec,
219
223
  gitExec,
220
224
  resolveVoluteBin
221
- } from "./chunk-AN2W47GW.js";
225
+ } from "./chunk-PJ4IPTIN.js";
222
226
  import {
223
227
  chownMindDir,
224
228
  createMindUser,
@@ -226,16 +230,15 @@ import {
226
230
  ensureVoluteGroup,
227
231
  isIsolationEnabled,
228
232
  wrapForIsolation
229
- } from "./chunk-VHJRZM2S.js";
233
+ } from "./chunk-L72WYMF7.js";
230
234
  import {
231
235
  isSetupComplete,
232
236
  readGlobalConfig,
233
237
  writeGlobalConfig
234
- } from "./chunk-BMZQYACC.js";
238
+ } from "./chunk-NUX47Y2V.js";
235
239
  import {
236
- buildVoluteSlug,
237
- slugify
238
- } from "./chunk-NJK5SDGR.js";
240
+ buildVoluteSlug
241
+ } from "./chunk-LGNUFVMR.js";
239
242
  import {
240
243
  addMind,
241
244
  addVariant,
@@ -258,9 +261,10 @@ import {
258
261
  validateMindName,
259
262
  voluteHome,
260
263
  voluteSystemDir
261
- } from "./chunk-BDYXIWA5.js";
264
+ } from "./chunk-75AJ54GM.js";
262
265
  import {
263
266
  activity,
267
+ channels,
264
268
  conversationParticipants,
265
269
  conversations,
266
270
  mindHistory,
@@ -269,7 +273,7 @@ import {
269
273
  systemPrompts,
270
274
  turns,
271
275
  users
272
- } from "./chunk-5N7Y5WAM.js";
276
+ } from "./chunk-7PTQGPJY.js";
273
277
  import {
274
278
  __export
275
279
  } from "./chunk-7KJOFUNN.js";
@@ -286,7 +290,7 @@ import { spawn } from "child_process";
286
290
  import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, unlinkSync, writeFileSync } from "fs";
287
291
  import { dirname, resolve as resolve2 } from "path";
288
292
 
289
- // packages/daemon/src/lib/bridge-defs.ts
293
+ // packages/daemon/src/lib/bridges/bridge-defs.ts
290
294
  import { existsSync, readFileSync } from "fs";
291
295
  import { resolve } from "path";
292
296
  var BUILTIN_DEFS = {
@@ -599,7 +603,7 @@ function getBridgeManager() {
599
603
  return instance;
600
604
  }
601
605
 
602
- // packages/daemon/src/lib/history-cleanup.ts
606
+ // packages/daemon/src/lib/util/history-cleanup.ts
603
607
  import { and, eq, lt } from "drizzle-orm";
604
608
  var LOG_RETENTION_MS = 24 * 60 * 60 * 1e3;
605
609
  async function cleanExpiredLogs() {
@@ -816,6 +820,11 @@ import { timingSafeEqual } from "crypto";
816
820
  import { eq as eq2, lt as lt2 } from "drizzle-orm";
817
821
  import { getCookie } from "hono/cookie";
818
822
  import { createMiddleware } from "hono/factory";
823
+ var MIND_USER_CACHE_TTL = 5 * 60 * 1e3;
824
+ var mindUserCache = /* @__PURE__ */ new Map();
825
+ function invalidateMindUserCache(mindName) {
826
+ mindUserCache.delete(mindName);
827
+ }
819
828
  function isValidDaemonToken(token) {
820
829
  const expected = process.env.VOLUTE_DAEMON_TOKEN;
821
830
  if (!expected || token.length !== expected.length) return false;
@@ -906,7 +915,14 @@ var authMiddleware = createMiddleware(async (c, next) => {
906
915
  }
907
916
  const mindName = resolveMindToken(token);
908
917
  if (mindName) {
909
- const mindUser = await getOrCreateMindUser(mindName);
918
+ const cached = mindUserCache.get(mindName);
919
+ let mindUser;
920
+ if (cached && Date.now() - cached.ts < MIND_USER_CACHE_TTL) {
921
+ mindUser = cached.user;
922
+ } else {
923
+ mindUser = await getOrCreateMindUser(mindName);
924
+ mindUserCache.set(mindName, { user: mindUser, ts: Date.now() });
925
+ }
910
926
  c.set("user", mindUser);
911
927
  const mindSessionHeader = c.req.header("X-Volute-Session");
912
928
  if (mindSessionHeader) c.set("mindSession", mindSessionHeader);
@@ -1848,7 +1864,7 @@ import { zValidator as zValidator3 } from "@hono/zod-validator";
1848
1864
  import { Hono as Hono4 } from "hono";
1849
1865
  import { z as z3 } from "zod";
1850
1866
 
1851
- // packages/daemon/src/lib/puppets.ts
1867
+ // packages/daemon/src/lib/chat/puppets.ts
1852
1868
  import { and as and2, eq as eq3 } from "drizzle-orm";
1853
1869
  async function findOrCreatePuppet(platform, platformId, displayName) {
1854
1870
  const username = `${platform}:${platformId}`;
@@ -1936,11 +1952,9 @@ var app4 = new Hono4().post("/:platform/inbound", zValidator3("json", inboundSch
1936
1952
  }
1937
1953
  const mindUser = await getOrCreateMindUser(mindName);
1938
1954
  const participantIds = [puppet.id, mindUser.id];
1939
- let conversationId = await findDMConversation(mindName, participantIds);
1955
+ let conversationId = await findDMConversation(participantIds);
1940
1956
  if (!conversationId) {
1941
- const title = `${body.displayName}, ${mindName}`;
1942
- const conv = await createConversation(mindName, "volute", {
1943
- title,
1957
+ const conv = await createConversation({
1944
1958
  participantIds: [puppet.id, mindUser.id],
1945
1959
  type: "dm"
1946
1960
  });
@@ -1969,7 +1983,7 @@ var app4 = new Hono4().post("/:platform/inbound", zValidator3("json", inboundSch
1969
1983
  }
1970
1984
  const participants = await getParticipants(channel.id);
1971
1985
  if (!participants.some((p) => p.userId === puppet.id)) {
1972
- const { addParticipant } = await import("./conversations-2PW57WO2.js");
1986
+ const { addParticipant } = await import("./conversations-HH3CJD4E.js");
1973
1987
  await addParticipant(channel.id, puppet.id);
1974
1988
  }
1975
1989
  const contentBlocks = body.content;
@@ -2062,8 +2076,8 @@ async function fanOutToBridgedMinds(opts) {
2062
2076
  const participants = await getParticipants(opts.conversationId);
2063
2077
  const mindParticipants = participants.filter((p) => p.userType === "mind");
2064
2078
  const participantNames = participants.map((p) => p.username);
2065
- const { getMindManager: getMindManager2 } = await import("./mind-manager-HFLB5653.js");
2066
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-7KFK3USC.js");
2079
+ const { getMindManager: getMindManager2 } = await import("./mind-manager-TJ2SUPRX.js");
2080
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-WAZWMFJT.js");
2067
2081
  const manager = getMindManager2();
2068
2082
  const sm = getSleepManagerIfReady2();
2069
2083
  const targetMinds = mindParticipants.filter((ap) => {
@@ -2073,7 +2087,6 @@ async function fanOutToBridgedMinds(opts) {
2073
2087
  const channel = buildVoluteSlug({
2074
2088
  participants,
2075
2089
  mindUsername: mindName,
2076
- convTitle: null,
2077
2090
  conversationId: opts.conversationId
2078
2091
  });
2079
2092
  deliverMessage(mindName, {
@@ -2094,7 +2107,7 @@ var bridges_default = app4;
2094
2107
  // packages/daemon/src/web/api/channels.ts
2095
2108
  import { Hono as Hono5 } from "hono";
2096
2109
 
2097
- // packages/daemon/src/lib/channels/discord.ts
2110
+ // packages/platforms/src/drivers/discord.ts
2098
2111
  var discord_exports = {};
2099
2112
  __export(discord_exports, {
2100
2113
  createConversation: () => createConversation2,
@@ -2104,7 +2117,12 @@ __export(discord_exports, {
2104
2117
  send: () => send
2105
2118
  });
2106
2119
 
2107
- // packages/daemon/src/connectors/sdk.ts
2120
+ // packages/platforms/src/slugify.ts
2121
+ function slugify(text) {
2122
+ return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
2123
+ }
2124
+
2125
+ // packages/platforms/src/split-message.ts
2108
2126
  function splitMessage(text, maxLength) {
2109
2127
  const chunks = [];
2110
2128
  while (text.length > maxLength) {
@@ -2117,7 +2135,7 @@ function splitMessage(text, maxLength) {
2117
2135
  return chunks;
2118
2136
  }
2119
2137
 
2120
- // packages/daemon/src/lib/channels/discord.ts
2138
+ // packages/platforms/src/drivers/discord.ts
2121
2139
  var DISCORD_MAX_LENGTH = 2e3;
2122
2140
  var API_BASE = "https://discord.com/api/v10";
2123
2141
  function requireToken(env) {
@@ -2136,7 +2154,7 @@ async function discordGet(token, path) {
2136
2154
  }
2137
2155
  async function read(env, channelSlug, limit) {
2138
2156
  const token = requireToken(env);
2139
- const channelId = resolveChannelId(channelSlug);
2157
+ const channelId = resolvePlatformId(channelSlug);
2140
2158
  const res = await fetch(`${API_BASE}/channels/${channelId}/messages?limit=${limit}`, {
2141
2159
  headers: { Authorization: `Bot ${token}` }
2142
2160
  });
@@ -2148,7 +2166,7 @@ async function read(env, channelSlug, limit) {
2148
2166
  }
2149
2167
  async function send(env, channelSlug, message, images) {
2150
2168
  const token = requireToken(env);
2151
- const channelId = resolveChannelId(channelSlug);
2169
+ const channelId = resolvePlatformId(channelSlug);
2152
2170
  if (images?.length) {
2153
2171
  for (let i = 0; i < images.length; i++) {
2154
2172
  const img = images[i];
@@ -2195,8 +2213,8 @@ async function listConversations(env) {
2195
2213
  const results = [];
2196
2214
  const guilds = await discordGet(token, "/users/@me/guilds");
2197
2215
  for (const guild of guilds) {
2198
- const channels = await discordGet(token, `/guilds/${guild.id}/channels`);
2199
- for (const ch of channels) {
2216
+ const channels2 = await discordGet(token, `/guilds/${guild.id}/channels`);
2217
+ for (const ch of channels2) {
2200
2218
  if (ch.type !== 0) continue;
2201
2219
  results.push({
2202
2220
  id: `discord:${slugify(guild.name)}/${slugify(ch.name)}`,
@@ -2264,7 +2282,7 @@ async function createConversation2(env, participants, _name) {
2264
2282
  return `discord:${dmChannel.id}`;
2265
2283
  }
2266
2284
 
2267
- // packages/daemon/src/lib/channels/slack.ts
2285
+ // packages/platforms/src/drivers/slack.ts
2268
2286
  var slack_exports = {};
2269
2287
  __export(slack_exports, {
2270
2288
  createConversation: () => createConversation3,
@@ -2300,7 +2318,7 @@ async function slackApi(token, method, body) {
2300
2318
  }
2301
2319
  async function read2(env, channelSlug, limit) {
2302
2320
  const token = requireToken2(env);
2303
- const channelId = resolveChannelId(channelSlug);
2321
+ const channelId = resolvePlatformId(channelSlug);
2304
2322
  const data = await slackApi(token, "conversations.history", {
2305
2323
  channel: channelId,
2306
2324
  limit
@@ -2309,7 +2327,7 @@ async function read2(env, channelSlug, limit) {
2309
2327
  }
2310
2328
  async function send2(env, channelSlug, message, images) {
2311
2329
  const token = requireToken2(env);
2312
- const channelId = resolveChannelId(channelSlug);
2330
+ const channelId = resolvePlatformId(channelSlug);
2313
2331
  if (images?.length) {
2314
2332
  for (const img of images) {
2315
2333
  const ext = img.media_type.split("/")[1] || "png";
@@ -2435,7 +2453,7 @@ async function createConversation3(env, participants, name) {
2435
2453
  return `slack:${openData.channel.id}`;
2436
2454
  }
2437
2455
 
2438
- // packages/daemon/src/lib/channels/telegram.ts
2456
+ // packages/platforms/src/drivers/telegram.ts
2439
2457
  var telegram_exports = {};
2440
2458
  __export(telegram_exports, {
2441
2459
  createConversation: () => createConversation4,
@@ -2458,7 +2476,7 @@ async function read3(_env, _channelSlug, _limit) {
2458
2476
  }
2459
2477
  async function send3(env, channelSlug, message, images) {
2460
2478
  const token = requireToken3(env);
2461
- const chatId = resolveChannelId(channelSlug);
2479
+ const chatId = resolvePlatformId(channelSlug);
2462
2480
  if (images?.length) {
2463
2481
  const CAPTION_MAX = 1024;
2464
2482
  for (let i = 0; i < images.length; i++) {
@@ -2531,7 +2549,39 @@ async function createConversation4() {
2531
2549
  );
2532
2550
  }
2533
2551
 
2534
- // packages/daemon/src/lib/channels/volute.ts
2552
+ // packages/platforms/src/index.ts
2553
+ var PLATFORMS = {
2554
+ discord: {
2555
+ name: "discord",
2556
+ displayName: "Discord",
2557
+ driver: discord_exports
2558
+ },
2559
+ slack: {
2560
+ name: "slack",
2561
+ displayName: "Slack",
2562
+ driver: slack_exports
2563
+ },
2564
+ telegram: {
2565
+ name: "telegram",
2566
+ displayName: "Telegram",
2567
+ driver: telegram_exports
2568
+ },
2569
+ volute: { name: "volute", displayName: "Volute" },
2570
+ mail: { name: "mail", displayName: "Email" },
2571
+ system: { name: "system", displayName: "System" }
2572
+ };
2573
+ function registerPlatform(name, platform) {
2574
+ PLATFORMS[name] = platform;
2575
+ }
2576
+ function getPlatformDriver(platform) {
2577
+ return PLATFORMS[platform]?.driver ?? null;
2578
+ }
2579
+ function resolvePlatformId(slug) {
2580
+ const colonIdx = slug.indexOf(":");
2581
+ return colonIdx !== -1 ? slug.slice(colonIdx + 1) : slug;
2582
+ }
2583
+
2584
+ // packages/daemon/src/lib/platforms/volute.ts
2535
2585
  var volute_exports = {};
2536
2586
  __export(volute_exports, {
2537
2587
  createConversation: () => createConversation5,
@@ -2576,7 +2626,7 @@ function getDaemonConfig() {
2576
2626
  async function read4(env, channelSlug, limit) {
2577
2627
  const mindName = env.VOLUTE_MIND;
2578
2628
  if (!mindName) throw new Error("VOLUTE_MIND not set");
2579
- const conversationId = resolveChannelId(channelSlug);
2629
+ const conversationId = resolvePlatformId(channelSlug);
2580
2630
  const { url, token } = getDaemonConfig();
2581
2631
  const headers = { Origin: url };
2582
2632
  if (token) headers.Authorization = `Bearer ${token}`;
@@ -2599,7 +2649,7 @@ async function read4(env, channelSlug, limit) {
2599
2649
  async function send4(env, channelSlug, message, images) {
2600
2650
  const mindName = env.VOLUTE_MIND;
2601
2651
  if (!mindName) throw new Error("VOLUTE_MIND not set");
2602
- const conversationId = resolveChannelId(channelSlug);
2652
+ const conversationId = resolvePlatformId(channelSlug);
2603
2653
  const { url, token } = getDaemonConfig();
2604
2654
  const headers = {
2605
2655
  "Content-Type": "application/json",
@@ -2639,33 +2689,21 @@ async function listConversations4(env) {
2639
2689
  const convs = await res.json();
2640
2690
  const results = [];
2641
2691
  for (const conv of convs) {
2642
- let participants = [];
2643
- try {
2644
- const pRes = await fetch(
2645
- `${url}/api/minds/${encodeURIComponent(mindName)}/conversations/${encodeURIComponent(conv.id)}/participants`,
2646
- { headers }
2647
- );
2648
- if (pRes.ok) {
2649
- participants = await pRes.json();
2650
- } else {
2651
- console.error(`[volute] failed to fetch participants for ${conv.id}: HTTP ${pRes.status}`);
2652
- }
2653
- } catch (err) {
2654
- console.error(`[volute] failed to fetch participants for ${conv.id}:`, err);
2655
- }
2692
+ const participants = conv.participants ?? [];
2656
2693
  const slug = buildVoluteSlug({
2657
2694
  participants,
2658
2695
  mindUsername: mindName,
2659
- convTitle: conv.title,
2660
2696
  conversationId: conv.id,
2661
2697
  convType: conv.type,
2662
- convName: conv.name
2698
+ convName: conv.channel_name
2663
2699
  });
2664
2700
  const convType = conv.type === "channel" ? "channel" : "dm";
2701
+ const other = participants.find((p) => p.username !== mindName);
2702
+ const displayName = conv.type === "channel" && conv.channel_name ? `#${conv.channel_name}` : other ? `@${other.username}` : "(untitled)";
2665
2703
  results.push({
2666
2704
  id: slug,
2667
2705
  platformId: conv.id,
2668
- name: conv.type === "channel" ? `#${conv.name}` : conv.title ?? "(untitled)",
2706
+ name: displayName,
2669
2707
  type: convType,
2670
2708
  participantCount: participants.length
2671
2709
  });
@@ -2699,7 +2737,7 @@ async function createConversation5(env, participants, name) {
2699
2737
  const res = await fetch(`${url}/api/minds/${encodeURIComponent(mindName)}/conversations`, {
2700
2738
  method: "POST",
2701
2739
  headers,
2702
- body: JSON.stringify({ participantNames: participants, title: name })
2740
+ body: JSON.stringify({ participantNames: participants })
2703
2741
  });
2704
2742
  if (!res.ok) {
2705
2743
  const data = await res.json().catch(() => ({}));
@@ -2709,39 +2747,13 @@ async function createConversation5(env, participants, name) {
2709
2747
  return conv.id;
2710
2748
  }
2711
2749
 
2712
- // packages/daemon/src/lib/channels.ts
2713
- var CHANNELS = {
2714
- volute: {
2715
- name: "volute",
2716
- displayName: "Volute",
2717
- builtIn: true,
2718
- driver: volute_exports
2719
- },
2720
- discord: {
2721
- name: "discord",
2722
- displayName: "Discord",
2723
- driver: discord_exports
2724
- },
2725
- slack: {
2726
- name: "slack",
2727
- displayName: "Slack",
2728
- driver: slack_exports
2729
- },
2730
- telegram: {
2731
- name: "telegram",
2732
- displayName: "Telegram",
2733
- driver: telegram_exports
2734
- },
2735
- mail: { name: "mail", displayName: "Email" },
2736
- system: { name: "system", displayName: "System" }
2737
- };
2738
- function getChannelDriver(platform) {
2739
- return CHANNELS[platform]?.driver ?? null;
2740
- }
2741
- function resolveChannelId(slug) {
2742
- const colonIdx = slug.indexOf(":");
2743
- return colonIdx !== -1 ? slug.slice(colonIdx + 1) : slug;
2744
- }
2750
+ // packages/daemon/src/lib/platforms.ts
2751
+ registerPlatform("volute", {
2752
+ name: "volute",
2753
+ displayName: "Volute",
2754
+ builtIn: true,
2755
+ driver: volute_exports
2756
+ });
2745
2757
 
2746
2758
  // packages/daemon/src/web/api/channels.ts
2747
2759
  function buildEnv(name) {
@@ -2756,7 +2768,7 @@ var app5 = new Hono5().post("/:name/channels/create", requireSelf(), async (c) =
2756
2768
  name: convName,
2757
2769
  sender
2758
2770
  } = await c.req.json();
2759
- const driver = getChannelDriver(platform);
2771
+ const driver = getPlatformDriver(platform);
2760
2772
  if (!driver?.createConversation) {
2761
2773
  return c.json({ error: `Platform ${platform} does not support creating conversations` }, 400);
2762
2774
  }
@@ -2878,7 +2890,8 @@ import { Hono as Hono8 } from "hono";
2878
2890
  var app7 = new Hono8().get("/", (c) => {
2879
2891
  return c.json(getLoadedExtensions());
2880
2892
  }).get("/all", (c) => {
2881
- return c.json(getAllDiscoveredExtensions());
2893
+ const detail = c.req.query("detail") === "true";
2894
+ return c.json(detail ? getAllDiscoveredExtensionsDetailed() : getAllDiscoveredExtensions());
2882
2895
  }).put("/:id/enabled", async (c) => {
2883
2896
  const { id } = c.req.param();
2884
2897
  const body = await c.req.json().catch(() => null);
@@ -2926,7 +2939,7 @@ async function notifyMind(mindName, message) {
2926
2939
  const entry = await findMind(mindName);
2927
2940
  if (!entry) return;
2928
2941
  try {
2929
- const { sendSystemMessage } = await import("./system-chat-IDPHYHY4.js");
2942
+ const { sendSystemMessage } = await import("./system-chat-2IFS5HCX.js");
2930
2943
  await sendSystemMessage(mindName, message);
2931
2944
  } catch (err) {
2932
2945
  logger_default.warn(`[file-sharing] notify mind ${mindName} failed`, logger_default.errorData(err));
@@ -3305,14 +3318,14 @@ var history = new Hono11().get("/turns", async (c) => {
3305
3318
  for (const r of triggerRows) triggerMap.set(r.id, r);
3306
3319
  }
3307
3320
  const allChannelSlugs = /* @__PURE__ */ new Set();
3308
- for (const [, channels] of msgsByTurnChannel) {
3309
- for (const ch of channels.keys()) allChannelSlugs.add(ch);
3321
+ for (const [, channels2] of msgsByTurnChannel) {
3322
+ for (const ch of channels2.keys()) allChannelSlugs.add(ch);
3310
3323
  }
3311
3324
  const dmSlugMinds = /* @__PURE__ */ new Map();
3312
- for (const [turnId, channels] of msgsByTurnChannel) {
3325
+ for (const [turnId, channels2] of msgsByTurnChannel) {
3313
3326
  const mindName = turnMindMap.get(turnId);
3314
3327
  if (!mindName) continue;
3315
- for (const ch of channels.keys()) {
3328
+ for (const ch of channels2.keys()) {
3316
3329
  if (ch.startsWith("@")) {
3317
3330
  let minds = dmSlugMinds.get(ch);
3318
3331
  if (!minds) {
@@ -3334,16 +3347,13 @@ var history = new Hono11().get("/turns", async (c) => {
3334
3347
  return { slug: s, name };
3335
3348
  });
3336
3349
  if (channelNames.length > 0) {
3337
- const channelRows = await db.select({ id: conversations.id, name: conversations.name }).from(conversations).where(
3338
- and3(
3339
- eq4(conversations.type, "channel"),
3340
- inArray(
3341
- conversations.name,
3342
- channelNames.map((c2) => c2.name)
3343
- )
3350
+ const channelRows = await db.select({ conversationId: channels.conversation_id, name: channels.name }).from(channels).where(
3351
+ inArray(
3352
+ channels.name,
3353
+ channelNames.map((c2) => c2.name)
3344
3354
  )
3345
3355
  );
3346
- const nameToId = new Map(channelRows.map((r) => [r.name, r.id]));
3356
+ const nameToId = new Map(channelRows.map((r) => [r.name, r.conversationId]));
3347
3357
  for (const { slug, name } of channelNames) {
3348
3358
  const id = nameToId.get(name);
3349
3359
  if (id) channelIdMap.set(slug, id);
@@ -3365,17 +3375,31 @@ var history = new Hono11().get("/turns", async (c) => {
3365
3375
  conversationParticipants,
3366
3376
  eq4(conversations.id, conversationParticipants.conversation_id)
3367
3377
  ).innerJoin(users, eq4(conversationParticipants.user_id, users.id)).where(and3(eq4(conversations.type, "dm"), inArray(users.username, targetNames)));
3378
+ const dmCandidates = [];
3368
3379
  for (const row of dmRows) {
3369
3380
  const slug = `@${row.targetUsername}`;
3370
3381
  const mindNames = dmSlugMinds.get(slug);
3371
3382
  if (mindNames && !channelIdMap.has(slug)) {
3372
- const mindCheck = await db.select({ id: conversationParticipants.user_id }).from(conversationParticipants).innerJoin(users, eq4(conversationParticipants.user_id, users.id)).where(
3373
- and3(
3374
- eq4(conversationParticipants.conversation_id, row.id),
3375
- inArray(users.username, [...mindNames])
3376
- )
3377
- ).get();
3378
- if (mindCheck) channelIdMap.set(slug, row.id);
3383
+ dmCandidates.push({ slug, conversationId: row.id, mindNames: [...mindNames] });
3384
+ }
3385
+ }
3386
+ if (dmCandidates.length > 0) {
3387
+ const candidateConvIds = dmCandidates.map((d) => d.conversationId);
3388
+ const allMindNames = [...new Set(dmCandidates.flatMap((d) => d.mindNames))];
3389
+ const verifyRows = await db.select({
3390
+ conversation_id: conversationParticipants.conversation_id,
3391
+ username: users.username
3392
+ }).from(conversationParticipants).innerJoin(users, eq4(conversationParticipants.user_id, users.id)).where(
3393
+ and3(
3394
+ inArray(conversationParticipants.conversation_id, candidateConvIds),
3395
+ inArray(users.username, allMindNames)
3396
+ )
3397
+ );
3398
+ const verifiedConvs = new Set(verifyRows.map((r) => r.conversation_id));
3399
+ for (const candidate of dmCandidates) {
3400
+ if (verifiedConvs.has(candidate.conversationId)) {
3401
+ channelIdMap.set(candidate.slug, candidate.conversationId);
3402
+ }
3379
3403
  }
3380
3404
  }
3381
3405
  }
@@ -3506,7 +3530,7 @@ var history = new Hono11().get("/turns", async (c) => {
3506
3530
  const limit = Math.min(Math.max(parseInt(c.req.query("limit") ?? "50", 10) || 50, 1), 200);
3507
3531
  if (ids) {
3508
3532
  const db2 = await getDb();
3509
- const idList = ids.split(",").map((s) => parseInt(s, 10)).filter((n) => !isNaN(n));
3533
+ const idList = ids.split(",").map((s) => parseInt(s, 10)).filter((n) => !Number.isNaN(n));
3510
3534
  if (idList.length === 0) return c.json([]);
3511
3535
  const rows2 = await db2.select().from(summaries).where(inArray(summaries.id, idList));
3512
3536
  const result2 = rows2.map((r) => {
@@ -3550,13 +3574,46 @@ var history = new Hono11().get("/turns", async (c) => {
3550
3574
  return { ...r, metadata };
3551
3575
  });
3552
3576
  return c.json(result);
3577
+ }).get("/activity", async (c) => {
3578
+ const mind = c.req.query("mind");
3579
+ const from = c.req.query("from");
3580
+ const to = c.req.query("to");
3581
+ const limit = Math.min(Math.max(parseInt(c.req.query("limit") ?? "100", 10) || 100, 1), 500);
3582
+ const db = await getDb();
3583
+ const conditions = [];
3584
+ if (mind) conditions.push(eq4(activity.mind, mind));
3585
+ if (from) conditions.push(gte(activity.created_at, from));
3586
+ if (to) conditions.push(sql`${activity.created_at} <= ${to}`);
3587
+ conditions.push(
3588
+ sql`${activity.type} NOT IN ('mind_started', 'mind_stopped', 'mind_active', 'mind_idle', 'mind_done', 'mind_sleeping', 'mind_waking', 'brain_online', 'brain_offline')`
3589
+ );
3590
+ const rows = await db.select({
3591
+ id: activity.id,
3592
+ type: activity.type,
3593
+ mind: activity.mind,
3594
+ summary: activity.summary,
3595
+ metadata: activity.metadata,
3596
+ created_at: activity.created_at
3597
+ }).from(activity).where(conditions.length > 0 ? and3(...conditions) : void 0).orderBy(desc2(activity.created_at)).limit(limit);
3598
+ const result = rows.map((r) => {
3599
+ let metadata = null;
3600
+ if (r.metadata) {
3601
+ try {
3602
+ metadata = JSON.parse(r.metadata);
3603
+ } catch (err) {
3604
+ logger_default.debug(`malformed activity metadata for id ${r.id}`, logger_default.errorData(err));
3605
+ }
3606
+ }
3607
+ return { ...r, metadata };
3608
+ });
3609
+ return c.json(result);
3553
3610
  });
3554
3611
  var history_default = history;
3555
3612
 
3556
3613
  // packages/daemon/src/web/api/keys.ts
3557
3614
  import { Hono as Hono12 } from "hono";
3558
3615
 
3559
- // packages/daemon/src/lib/identity.ts
3616
+ // packages/daemon/src/lib/mind/identity.ts
3560
3617
  import { createHash, generateKeyPairSync, sign, verify } from "crypto";
3561
3618
  import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
3562
3619
  import { resolve as resolve7 } from "path";
@@ -3783,7 +3840,7 @@ import { and as and4, desc as desc3, eq as eq5, sql as sql2 } from "drizzle-orm"
3783
3840
  import { Hono as Hono15 } from "hono";
3784
3841
  import { z as z5 } from "zod";
3785
3842
 
3786
- // packages/daemon/src/lib/consolidate.ts
3843
+ // packages/daemon/src/lib/mind/consolidate.ts
3787
3844
  import { readdirSync, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
3788
3845
  import { resolve as resolve9 } from "path";
3789
3846
  async function consolidateMemory(mindDir2) {
@@ -3854,7 +3911,65 @@ ${content2}`);
3854
3911
  }
3855
3912
  }
3856
3913
 
3857
- // packages/daemon/src/lib/convert-session.ts
3914
+ // packages/daemon/src/lib/mind/variant-cleanup.ts
3915
+ import { existsSync as existsSync7, rmSync as rmSync3 } from "fs";
3916
+ async function cleanupVariant(variantName, projectRoot, variantPath, opts) {
3917
+ if (opts?.stop) {
3918
+ try {
3919
+ await getMindManager().stopMind(variantName);
3920
+ } catch (err) {
3921
+ logger_default.warn(`failed to stop variant ${variantName}`, logger_default.errorData(err));
3922
+ }
3923
+ }
3924
+ const { findMind: findMind2 } = await import("./registry-YPHK534W.js");
3925
+ const variantEntry = await findMind2(variantName);
3926
+ const branchName = variantEntry?.branch ?? variantName;
3927
+ if (existsSync7(variantPath)) {
3928
+ try {
3929
+ await gitExec(["worktree", "remove", "--force", variantPath], { cwd: projectRoot });
3930
+ } catch {
3931
+ rmSync3(variantPath, { recursive: true, force: true });
3932
+ try {
3933
+ await gitExec(["worktree", "prune"], { cwd: projectRoot });
3934
+ } catch (err) {
3935
+ logger_default.warn(`failed to prune worktrees for ${variantName}`, logger_default.errorData(err));
3936
+ }
3937
+ }
3938
+ }
3939
+ try {
3940
+ await gitExec(["branch", "-D", branchName], { cwd: projectRoot });
3941
+ } catch (err) {
3942
+ logger_default.warn(`failed to delete branch ${branchName} for ${variantName}`, logger_default.errorData(err));
3943
+ }
3944
+ const baseName = variantEntry?.parent ?? variantName;
3945
+ try {
3946
+ await removeMind(variantName);
3947
+ } catch (err) {
3948
+ logger_default.warn(`failed to remove variant ${variantName} from DB`, logger_default.errorData(err));
3949
+ }
3950
+ try {
3951
+ chownMindDir(projectRoot, baseName);
3952
+ } catch (err) {
3953
+ logger_default.error(
3954
+ `failed to fix ownership during variant cleanup for ${variantName}`,
3955
+ logger_default.errorData(err)
3956
+ );
3957
+ }
3958
+ }
3959
+
3960
+ // packages/daemon/src/lib/mind/variants.ts
3961
+ var SAFE_BRANCH_RE = /^[a-zA-Z0-9._\-/]+$/;
3962
+ function validateBranchName(branch) {
3963
+ if (!SAFE_BRANCH_RE.test(branch)) {
3964
+ return `Invalid branch name: ${branch}. Only alphanumeric, '.', '_', '-', '/' allowed.`;
3965
+ }
3966
+ if (branch.includes("..")) {
3967
+ return `Invalid branch name: ${branch}. '..' not allowed.`;
3968
+ }
3969
+ return null;
3970
+ }
3971
+
3972
+ // packages/daemon/src/lib/template/convert-session.ts
3858
3973
  import { randomUUID } from "crypto";
3859
3974
  import { mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "fs";
3860
3975
  import { homedir } from "os";
@@ -4028,7 +4143,7 @@ function convertAssistantContent(content) {
4028
4143
  return result;
4029
4144
  }
4030
4145
 
4031
- // packages/daemon/src/lib/health.ts
4146
+ // packages/daemon/src/lib/util/health.ts
4032
4147
  async function checkHealth(port) {
4033
4148
  try {
4034
4149
  const res = await fetch(`http://127.0.0.1:${port}/health`, {
@@ -4042,85 +4157,29 @@ async function checkHealth(port) {
4042
4157
  }
4043
4158
  }
4044
4159
 
4045
- // packages/daemon/src/lib/variant-cleanup.ts
4046
- import { existsSync as existsSync7, rmSync as rmSync3 } from "fs";
4047
- async function cleanupVariant(variantName, projectRoot, variantPath, opts) {
4048
- if (opts?.stop) {
4049
- try {
4050
- await getMindManager().stopMind(variantName);
4051
- } catch (err) {
4052
- logger_default.warn(`failed to stop variant ${variantName}`, logger_default.errorData(err));
4053
- }
4054
- }
4055
- const { findMind: findMind2 } = await import("./registry-GBSNW3HG.js");
4056
- const variantEntry = await findMind2(variantName);
4057
- const branchName = variantEntry?.branch ?? variantName;
4058
- if (existsSync7(variantPath)) {
4059
- try {
4060
- await gitExec(["worktree", "remove", "--force", variantPath], { cwd: projectRoot });
4061
- } catch {
4062
- rmSync3(variantPath, { recursive: true, force: true });
4063
- try {
4064
- await gitExec(["worktree", "prune"], { cwd: projectRoot });
4065
- } catch (err) {
4066
- logger_default.warn(`failed to prune worktrees for ${variantName}`, logger_default.errorData(err));
4067
- }
4068
- }
4069
- }
4070
- try {
4071
- await gitExec(["branch", "-D", branchName], { cwd: projectRoot });
4072
- } catch (err) {
4073
- logger_default.warn(`failed to delete branch ${branchName} for ${variantName}`, logger_default.errorData(err));
4074
- }
4075
- const baseName = variantEntry?.parent ?? variantName;
4076
- try {
4077
- await removeMind(variantName);
4078
- } catch (err) {
4079
- logger_default.warn(`failed to remove variant ${variantName} from DB`, logger_default.errorData(err));
4080
- }
4081
- try {
4082
- chownMindDir(projectRoot, baseName);
4083
- } catch (err) {
4084
- logger_default.error(
4085
- `failed to fix ownership during variant cleanup for ${variantName}`,
4086
- logger_default.errorData(err)
4087
- );
4088
- }
4089
- }
4090
-
4091
- // packages/daemon/src/lib/variants.ts
4092
- var SAFE_BRANCH_RE = /^[a-zA-Z0-9._\-/]+$/;
4093
- function validateBranchName(branch) {
4094
- if (!SAFE_BRANCH_RE.test(branch)) {
4095
- return `Invalid branch name: ${branch}. Only alphanumeric, '.', '_', '-', '/' allowed.`;
4096
- }
4097
- if (branch.includes("..")) {
4098
- return `Invalid branch name: ${branch}. '..' not allowed.`;
4099
- }
4100
- return null;
4101
- }
4102
-
4103
4160
  // packages/daemon/src/web/api/minds.ts
4104
4161
  var SUBSTANTIVE_TYPES = /* @__PURE__ */ new Set(["thinking", "text", "tool_use", "tool_result", "outbound"]);
4105
- async function getMindStatus(name, port) {
4162
+ var _lastActiveCache = { map: /* @__PURE__ */ new Map(), ts: 0 };
4163
+ var _LAST_ACTIVE_TTL = 6e4;
4164
+ async function getMindStatus(name, port, registryRunning) {
4106
4165
  const manager = getMindManager();
4107
4166
  let status = "stopped";
4108
4167
  try {
4109
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-7KFK3USC.js");
4168
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-WAZWMFJT.js");
4110
4169
  if (getSleepManagerIfReady2()?.isSleeping(name)) {
4111
4170
  status = "sleeping";
4112
4171
  }
4113
4172
  } catch {
4114
4173
  }
4115
- if (status !== "sleeping" && manager.isRunning(name)) {
4174
+ if (status !== "sleeping" && registryRunning !== false && manager.isRunning(name)) {
4116
4175
  const health = await checkHealth(port);
4117
4176
  status = health.ok ? "running" : "starting";
4118
4177
  }
4119
4178
  const config2 = readVoluteConfig(mindDir(name));
4120
- const channels = [];
4121
- for (const [, provider] of Object.entries(CHANNELS)) {
4179
+ const channels2 = [];
4180
+ for (const [, provider] of Object.entries(PLATFORMS)) {
4122
4181
  if (!provider.builtIn) continue;
4123
- channels.push({
4182
+ channels2.push({
4124
4183
  name: provider.name,
4125
4184
  displayName: provider.displayName,
4126
4185
  status: status === "running" ? "connected" : "disconnected"
@@ -4128,7 +4187,7 @@ async function getMindStatus(name, port) {
4128
4187
  }
4129
4188
  return {
4130
4189
  status,
4131
- channels,
4190
+ channels: channels2,
4132
4191
  displayName: config2?.profile?.displayName,
4133
4192
  description: config2?.profile?.description,
4134
4193
  avatar: config2?.profile?.avatar
@@ -4545,7 +4604,7 @@ var app13 = new Hono15().post("/", requireAdminOrSystem, zValidator5("json", cre
4545
4604
  applyInitFiles(dest);
4546
4605
  const { publicKeyPem } = generateIdentity(dest);
4547
4606
  {
4548
- const { readGlobalConfig: readGlobal } = await import("./setup-RHJRFURI.js");
4607
+ const { readGlobalConfig: readGlobal } = await import("./setup-PMJHCZQX.js");
4549
4608
  const mindDefaults = readGlobal().mindDefaults;
4550
4609
  const config2 = readVoluteConfig(dest);
4551
4610
  if (!config2) throw new Error("Failed to read volute.json after identity generation");
@@ -4636,7 +4695,7 @@ The human who planted you described you as: "${body.description}"
4636
4695
  }
4637
4696
  let skillSet = body.skills ?? (body.stage === "seed" ? SEED_SKILLS : getStandardSkillsWithExtensions());
4638
4697
  if (body.stage === "seed" && !body.skills) {
4639
- const { isImagegenEnabled } = await import("./setup-RHJRFURI.js");
4698
+ const { isImagegenEnabled } = await import("./setup-PMJHCZQX.js");
4640
4699
  if (isImagegenEnabled()) {
4641
4700
  skillSet = [...skillSet, "imagegen"];
4642
4701
  }
@@ -4654,7 +4713,7 @@ The human who planted you described you as: "${body.description}"
4654
4713
  try {
4655
4714
  const spiritEntry = await findMind("volute");
4656
4715
  if (spiritEntry) {
4657
- const { spiritDir } = await import("./spirit-ZFRDXMG7.js");
4716
+ const { spiritDir } = await import("./spirit-6KVDIROQ.js");
4658
4717
  const sDir = spiritEntry.dir ?? spiritDir();
4659
4718
  const spiritConfig = readVoluteConfig(sDir) ?? {};
4660
4719
  const schedules = spiritConfig.schedules ?? [];
@@ -4669,7 +4728,7 @@ The human who planted you described you as: "${body.description}"
4669
4728
  });
4670
4729
  spiritConfig.schedules = schedules;
4671
4730
  writeVoluteConfig(sDir, spiritConfig);
4672
- const { getScheduler: getScheduler2 } = await import("./scheduler-Y7O4CJXL.js");
4731
+ const { getScheduler: getScheduler2 } = await import("./scheduler-FRJ5DK24.js");
4673
4732
  getScheduler2().loadSchedules("volute", sDir);
4674
4733
  }
4675
4734
  }
@@ -4838,19 +4897,26 @@ ${user.trimEnd()}
4838
4897
  }
4839
4898
  }).get("/", async (c) => {
4840
4899
  const entries = await readRegistry();
4841
- let lastActiveMap = /* @__PURE__ */ new Map();
4842
- try {
4843
- const db = await getDb();
4844
- const lastActiveRows = await db.select({
4845
- mind: mindHistory.mind,
4846
- lastActiveAt: sql2`MAX(${mindHistory.created_at})`
4847
- }).from(mindHistory).groupBy(mindHistory.mind);
4848
- lastActiveMap = new Map(lastActiveRows.map((r) => [r.mind, r.lastActiveAt]));
4849
- } catch {
4900
+ let lastActiveMap;
4901
+ if (_lastActiveCache.ts > 0 && Date.now() - _lastActiveCache.ts < _LAST_ACTIVE_TTL) {
4902
+ lastActiveMap = _lastActiveCache.map;
4903
+ } else {
4904
+ lastActiveMap = /* @__PURE__ */ new Map();
4905
+ try {
4906
+ const db = await getDb();
4907
+ const lastActiveRows = await db.select({
4908
+ mind: mindHistory.mind,
4909
+ lastActiveAt: sql2`MAX(${mindHistory.created_at})`
4910
+ }).from(mindHistory).groupBy(mindHistory.mind);
4911
+ lastActiveMap = new Map(lastActiveRows.map((r) => [r.mind, r.lastActiveAt]));
4912
+ _lastActiveCache.map = lastActiveMap;
4913
+ _lastActiveCache.ts = Date.now();
4914
+ } catch {
4915
+ }
4850
4916
  }
4851
4917
  const minds = await Promise.all(
4852
4918
  entries.map(async (entry) => {
4853
- const mindStatus = await getMindStatus(entry.name, entry.port);
4919
+ const mindStatus = await getMindStatus(entry.name, entry.port, entry.running);
4854
4920
  const hasPages = existsSync8(resolve11(mindDir(entry.name), "home", "pages"));
4855
4921
  return {
4856
4922
  ...entry,
@@ -4953,7 +5019,7 @@ ${user.trimEnd()}
4953
5019
  const manager = getMindManager();
4954
5020
  try {
4955
5021
  if (context?.type === "reload") {
4956
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-7KFK3USC.js");
5022
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-WAZWMFJT.js");
4957
5023
  const sleepState = getSleepManagerIfReady2()?.getState(name);
4958
5024
  if (sleepState?.sleeping) {
4959
5025
  logger_default.info(`skipping reload for ${name} during sleep \u2014 will apply on next wake`);
@@ -5014,10 +5080,9 @@ ${user.trimEnd()}
5014
5080
  }
5015
5081
  if (context?.type === "sprouted" && !entry.parent) {
5016
5082
  try {
5017
- const db = await getDb();
5018
- const activeConvs = await db.select({ id: conversations.id, channel: conversations.channel }).from(conversations).where(eq5(conversations.mind_name, baseName)).all();
5019
- for (const conv of activeConvs) {
5020
- await recordInbound(baseName, conv.channel, "system", "[seed has sprouted]");
5083
+ const mindConvs = await listConversationsForMind(baseName);
5084
+ for (const conv of mindConvs) {
5085
+ await recordInbound(baseName, "system", "system", "[seed has sprouted]");
5021
5086
  await addMessage(conv.id, "assistant", "system", [
5022
5087
  { type: "text", text: "[seed has sprouted]" }
5023
5088
  ]);
@@ -5049,7 +5114,7 @@ ${user.trimEnd()}
5049
5114
  const name = c.req.param("name");
5050
5115
  const entry = await findMind(name);
5051
5116
  if (!entry) return c.json({ error: "Mind not found" }, 404);
5052
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-7KFK3USC.js");
5117
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-WAZWMFJT.js");
5053
5118
  const sm = getSleepManagerIfReady2();
5054
5119
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
5055
5120
  return c.json(sm.getState(name));
@@ -5057,7 +5122,7 @@ ${user.trimEnd()}
5057
5122
  const name = c.req.param("name");
5058
5123
  const entry = await findMind(name);
5059
5124
  if (!entry) return c.json({ error: "Mind not found" }, 404);
5060
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-7KFK3USC.js");
5125
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-WAZWMFJT.js");
5061
5126
  const sm = getSleepManagerIfReady2();
5062
5127
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
5063
5128
  if (sm.isSleeping(name)) return c.json({ error: "Mind is already sleeping" }, 409);
@@ -5077,7 +5142,7 @@ ${user.trimEnd()}
5077
5142
  const name = c.req.param("name");
5078
5143
  const entry = await findMind(name);
5079
5144
  if (!entry) return c.json({ error: "Mind not found" }, 404);
5080
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-7KFK3USC.js");
5145
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-WAZWMFJT.js");
5081
5146
  const sm = getSleepManagerIfReady2();
5082
5147
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
5083
5148
  const sleepState = sm.getState(name);
@@ -5092,7 +5157,7 @@ ${user.trimEnd()}
5092
5157
  const name = c.req.param("name");
5093
5158
  const entry = await findMind(name);
5094
5159
  if (!entry) return c.json({ error: "Mind not found" }, 404);
5095
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-7KFK3USC.js");
5160
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-WAZWMFJT.js");
5096
5161
  const sm = getSleepManagerIfReady2();
5097
5162
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
5098
5163
  const flushed = await sm.flushQueuedMessages(name);
@@ -5110,7 +5175,7 @@ ${user.trimEnd()}
5110
5175
  if (body.avatar !== void 0) profile.avatar = body.avatar;
5111
5176
  config2.profile = profile;
5112
5177
  writeVoluteConfig(dir, config2);
5113
- const { syncMindProfile: syncMindProfile2 } = await import("./auth-WX4TESEI.js");
5178
+ const { syncMindProfile: syncMindProfile2 } = await import("./auth-YTQME4EV.js");
5114
5179
  await syncMindProfile2(name, profile);
5115
5180
  broadcast({ type: "profile_updated", mind: name, summary: `${name} profile updated` });
5116
5181
  return c.json({ ok: true });
@@ -5157,7 +5222,7 @@ ${user.trimEnd()}
5157
5222
  const config2 = readVoluteConfig(dir);
5158
5223
  const displayNameSet = !!config2?.profile?.displayName;
5159
5224
  const avatarSet = !!config2?.profile?.avatar;
5160
- const { isImagegenEnabled } = await import("./setup-RHJRFURI.js");
5225
+ const { isImagegenEnabled } = await import("./setup-PMJHCZQX.js");
5161
5226
  const imagegenEnabled = isImagegenEnabled();
5162
5227
  const done = [];
5163
5228
  const remaining = [];
@@ -5195,7 +5260,7 @@ ${user.trimEnd()}
5195
5260
  try {
5196
5261
  const spiritEntry = await findMind("volute");
5197
5262
  if (spiritEntry) {
5198
- const { spiritDir } = await import("./spirit-ZFRDXMG7.js");
5263
+ const { spiritDir } = await import("./spirit-6KVDIROQ.js");
5199
5264
  const sDir = spiritEntry.dir ?? spiritDir();
5200
5265
  const spiritConfig = readVoluteConfig(sDir);
5201
5266
  if (spiritConfig?.schedules) {
@@ -5203,7 +5268,7 @@ ${user.trimEnd()}
5203
5268
  spiritConfig.schedules = spiritConfig.schedules.filter((s) => s.id !== nurtureId);
5204
5269
  if (spiritConfig.schedules.length === 0) spiritConfig.schedules = void 0;
5205
5270
  writeVoluteConfig(sDir, spiritConfig);
5206
- const { getScheduler: getScheduler2 } = await import("./scheduler-Y7O4CJXL.js");
5271
+ const { getScheduler: getScheduler2 } = await import("./scheduler-FRJ5DK24.js");
5207
5272
  getScheduler2().loadSchedules("volute", sDir);
5208
5273
  }
5209
5274
  }
@@ -5229,6 +5294,7 @@ ${user.trimEnd()}
5229
5294
  }
5230
5295
  await removeMind(name);
5231
5296
  await deleteMindUser2(name);
5297
+ invalidateMindUserCache(name);
5232
5298
  const state = stateDir(name);
5233
5299
  if (existsSync8(state)) {
5234
5300
  rmSync4(state, { recursive: true, force: true });
@@ -5699,7 +5765,7 @@ ${user.trimEnd()}
5699
5765
  }
5700
5766
  if (entry.mindType === "spirit" && body.model !== void 0) {
5701
5767
  try {
5702
- const { readGlobalConfig: readGlobalConfig2, writeGlobalConfig: writeGlobalConfig2 } = await import("./setup-RHJRFURI.js");
5768
+ const { readGlobalConfig: readGlobalConfig2, writeGlobalConfig: writeGlobalConfig2 } = await import("./setup-PMJHCZQX.js");
5703
5769
  const globalConfig = readGlobalConfig2();
5704
5770
  globalConfig.spiritModel = body.model;
5705
5771
  writeGlobalConfig2(globalConfig);
@@ -5727,7 +5793,7 @@ ${user.trimEnd()}
5727
5793
  if (!body.systemPrompt || !body.message) {
5728
5794
  return c.json({ error: "systemPrompt and message required" }, 400);
5729
5795
  }
5730
- const { aiComplete: aiCompleteFn, isAiConfigured } = await import("./ai-service-LURBEDDB.js");
5796
+ const { aiComplete: aiCompleteFn, isAiConfigured } = await import("./ai-service-GSZWIETO.js");
5731
5797
  if (!isAiConfigured()) {
5732
5798
  return c.json({ error: "AI service not configured" }, 503);
5733
5799
  }
@@ -6187,15 +6253,14 @@ var prompts_default = app14;
6187
6253
  import { CronExpressionParser } from "cron-parser";
6188
6254
  import { Hono as Hono17 } from "hono";
6189
6255
  var slog2 = logger_default.child("schedules");
6190
- function readSchedules(name) {
6191
- return readVoluteConfig(mindDir(name))?.schedules ?? [];
6256
+ function readSchedules(dir) {
6257
+ return readVoluteConfig(dir)?.schedules ?? [];
6192
6258
  }
6193
- function writeSchedules(name, schedules) {
6194
- const dir = mindDir(name);
6259
+ function writeSchedules(name, dir, schedules) {
6195
6260
  const config2 = readVoluteConfig(dir) ?? {};
6196
6261
  config2.schedules = schedules.length > 0 ? schedules : void 0;
6197
6262
  writeVoluteConfig(dir, config2);
6198
- getScheduler().loadSchedules(name);
6263
+ getScheduler().loadSchedules(name, dir);
6199
6264
  getSleepManagerIfReady()?.invalidateSleepConfig(name);
6200
6265
  fireWebhook({
6201
6266
  event: "schedule_changed",
@@ -6205,11 +6270,13 @@ function writeSchedules(name, schedules) {
6205
6270
  }
6206
6271
  var app15 = new Hono17().get("/:name/clock/status", async (c) => {
6207
6272
  const name = c.req.param("name");
6208
- if (!await findMind(name)) return c.json({ error: "Mind not found" }, 404);
6273
+ const entry = await findMind(name);
6274
+ if (!entry) return c.json({ error: "Mind not found" }, 404);
6275
+ const dir = entry.dir ?? mindDir(name);
6209
6276
  const sleepManager = getSleepManagerIfReady();
6210
6277
  const sleepState = sleepManager?.getState(name) ?? null;
6211
6278
  const sleepConfig = sleepManager?.getSleepConfig(name) ?? null;
6212
- const schedules = readSchedules(name);
6279
+ const schedules = readSchedules(dir);
6213
6280
  const now = /* @__PURE__ */ new Date();
6214
6281
  const upcoming = [];
6215
6282
  const previous = [];
@@ -6262,8 +6329,9 @@ var app15 = new Hono17().get("/:name/clock/status", async (c) => {
6262
6329
  return c.json({ sleep: sleepState, sleepConfig, schedules, upcoming, previous });
6263
6330
  }).get("/:name/sleep/config", async (c) => {
6264
6331
  const name = c.req.param("name");
6265
- if (!await findMind(name)) return c.json({ error: "Mind not found" }, 404);
6266
- const config2 = readVoluteConfig(mindDir(name));
6332
+ const entry = await findMind(name);
6333
+ if (!entry) return c.json({ error: "Mind not found" }, 404);
6334
+ const config2 = readVoluteConfig(entry.dir ?? mindDir(name));
6267
6335
  return c.json(config2?.sleep ?? { enabled: false });
6268
6336
  }).put("/:name/sleep/config", requireSelf(), async (c) => {
6269
6337
  const name = c.req.param("name");
@@ -6281,7 +6349,7 @@ var app15 = new Hono17().get("/:name/clock/status", async (c) => {
6281
6349
  }
6282
6350
  }
6283
6351
  }
6284
- const dir = mindDir(name);
6352
+ const dir = entry.dir ?? mindDir(name);
6285
6353
  const config2 = readVoluteConfig(dir) ?? {};
6286
6354
  const sleep = config2.sleep ?? {};
6287
6355
  if (body.enabled !== void 0) sleep.enabled = body.enabled;
@@ -6293,8 +6361,9 @@ var app15 = new Hono17().get("/:name/clock/status", async (c) => {
6293
6361
  return c.json({ ok: true });
6294
6362
  }).get("/:name/schedules", async (c) => {
6295
6363
  const name = c.req.param("name");
6296
- if (!await findMind(name)) return c.json({ error: "Mind not found" }, 404);
6297
- return c.json(readSchedules(name));
6364
+ const entry = await findMind(name);
6365
+ if (!entry) return c.json({ error: "Mind not found" }, 404);
6366
+ return c.json(readSchedules(entry.dir ?? mindDir(name)));
6298
6367
  }).post("/:name/schedules", requireSelf(), async (c) => {
6299
6368
  const name = c.req.param("name");
6300
6369
  const entry = await findMind(name);
@@ -6335,7 +6404,8 @@ var app15 = new Hono17().get("/:name/clock/status", async (c) => {
6335
6404
  400
6336
6405
  );
6337
6406
  }
6338
- const schedules = readSchedules(name);
6407
+ const dir = entry.dir ?? mindDir(name);
6408
+ const schedules = readSchedules(dir);
6339
6409
  const id = body.id;
6340
6410
  if (schedules.some((s) => s.id === id)) {
6341
6411
  return c.json({ error: `Schedule "${id}" already exists` }, 409);
@@ -6348,13 +6418,15 @@ var app15 = new Hono17().get("/:name/clock/status", async (c) => {
6348
6418
  if (body.channel) schedule.channel = body.channel;
6349
6419
  if (body.whileSleeping) schedule.whileSleeping = body.whileSleeping;
6350
6420
  schedules.push(schedule);
6351
- writeSchedules(name, schedules);
6421
+ writeSchedules(name, dir, schedules);
6352
6422
  return c.json({ ok: true, id }, 201);
6353
6423
  }).put("/:name/schedules/:id", requireSelf(), async (c) => {
6354
6424
  const name = c.req.param("name");
6355
6425
  const id = c.req.param("id");
6356
- if (!await findMind(name)) return c.json({ error: "Mind not found" }, 404);
6357
- const schedules = readSchedules(name);
6426
+ const entry = await findMind(name);
6427
+ if (!entry) return c.json({ error: "Mind not found" }, 404);
6428
+ const dir = entry.dir ?? mindDir(name);
6429
+ const schedules = readSchedules(dir);
6358
6430
  const idx = schedules.findIndex((s) => s.id === id);
6359
6431
  if (idx === -1) return c.json({ error: "Schedule not found" }, 404);
6360
6432
  const body = await c.req.json();
@@ -6397,18 +6469,20 @@ var app15 = new Hono17().get("/:name/clock/status", async (c) => {
6397
6469
  if (body.channel !== void 0) schedules[idx].channel = body.channel || void 0;
6398
6470
  if (body.whileSleeping !== void 0)
6399
6471
  schedules[idx].whileSleeping = body.whileSleeping || void 0;
6400
- writeSchedules(name, schedules);
6472
+ writeSchedules(name, dir, schedules);
6401
6473
  return c.json({ ok: true });
6402
6474
  }).delete("/:name/schedules/:id", requireSelf(), async (c) => {
6403
6475
  const name = c.req.param("name");
6404
6476
  const id = c.req.param("id");
6405
- if (!await findMind(name)) return c.json({ error: "Mind not found" }, 404);
6406
- const schedules = readSchedules(name);
6477
+ const entry = await findMind(name);
6478
+ if (!entry) return c.json({ error: "Mind not found" }, 404);
6479
+ const dir = entry.dir ?? mindDir(name);
6480
+ const schedules = readSchedules(dir);
6407
6481
  const filtered = schedules.filter((s) => s.id !== id);
6408
6482
  if (filtered.length === schedules.length) {
6409
6483
  return c.json({ error: "Schedule not found" }, 404);
6410
6484
  }
6411
- writeSchedules(name, filtered);
6485
+ writeSchedules(name, dir, filtered);
6412
6486
  return c.json({ ok: true });
6413
6487
  }).post("/:name/webhook/:event", async (c) => {
6414
6488
  const name = c.req.param("name");
@@ -6418,7 +6492,7 @@ var app15 = new Hono17().get("/:name/clock/status", async (c) => {
6418
6492
  const body = await c.req.text();
6419
6493
  const message = `[webhook: ${event}] ${body}`;
6420
6494
  try {
6421
- const { sendSystemMessage } = await import("./system-chat-IDPHYHY4.js");
6495
+ const { sendSystemMessage } = await import("./system-chat-2IFS5HCX.js");
6422
6496
  await sendSystemMessage(name, message);
6423
6497
  return c.json({ ok: true });
6424
6498
  } catch (err) {
@@ -6471,7 +6545,7 @@ setup.get("/status", async (c) => {
6471
6545
  let hasAccount = false;
6472
6546
  if (hasSystem) {
6473
6547
  try {
6474
- const { listUsersByType: listUsersByType2 } = await import("./auth-WX4TESEI.js");
6548
+ const { listUsersByType: listUsersByType2 } = await import("./auth-YTQME4EV.js");
6475
6549
  const brains = await listUsersByType2("brain");
6476
6550
  hasAccount = brains.length > 0;
6477
6551
  } catch (err) {
@@ -6660,7 +6734,7 @@ setup.post("/account", async (c) => {
6660
6734
  }
6661
6735
  }
6662
6736
  try {
6663
- const { createUser: createUser2, updateUserProfile: updateUserProfile2 } = await import("./auth-WX4TESEI.js");
6737
+ const { createUser: createUser2, updateUserProfile: updateUserProfile2 } = await import("./auth-YTQME4EV.js");
6664
6738
  const user = await createUser2(body.username.trim(), body.password);
6665
6739
  if (body.displayName?.trim()) {
6666
6740
  await updateUserProfile2(user.id, { display_name: body.displayName.trim() });
@@ -6706,7 +6780,7 @@ setup.post("/models", async (c) => {
6706
6780
  return c.json({ error: "Spirit model is required" }, 400);
6707
6781
  }
6708
6782
  try {
6709
- const { setEnabledModels: setEnabledModels3, setUtilityModel: setUtilityModel2 } = await import("./ai-service-LURBEDDB.js");
6783
+ const { setEnabledModels: setEnabledModels3, setUtilityModel: setUtilityModel2 } = await import("./ai-service-GSZWIETO.js");
6710
6784
  setEnabledModels3(body.models);
6711
6785
  const config2 = readGlobalConfig();
6712
6786
  config2.spiritModel = body.spiritModel.trim();
@@ -6724,8 +6798,8 @@ setup.post("/complete", async (c) => {
6724
6798
  return c.json({ error: "Setup already complete" }, 400);
6725
6799
  }
6726
6800
  try {
6727
- const { ensureSpiritProject, syncSpiritTemplate } = await import("./spirit-ZFRDXMG7.js");
6728
- const { startSpiritFull } = await import("./mind-service-X2CAA6W6.js");
6801
+ const { ensureSpiritProject, syncSpiritTemplate } = await import("./spirit-6KVDIROQ.js");
6802
+ const { startSpiritFull } = await import("./mind-service-E7FM2WZF.js");
6729
6803
  await ensureSpiritProject();
6730
6804
  await syncSpiritTemplate();
6731
6805
  const warnings = [];
@@ -6741,19 +6815,18 @@ setup.post("/complete", async (c) => {
6741
6815
  }
6742
6816
  let spiritConversationId;
6743
6817
  try {
6744
- const { getOrCreateMindUser: getOrCreateMindUser2, listUsersByType: listUsersByType2 } = await import("./auth-WX4TESEI.js");
6745
- const { createConversation: createConversation6, findDMConversation: findDMConversation2 } = await import("./conversations-2PW57WO2.js");
6818
+ const { getOrCreateMindUser: getOrCreateMindUser2, listUsersByType: listUsersByType2 } = await import("./auth-YTQME4EV.js");
6819
+ const { createConversation: createConversation6, findDMConversation: findDMConversation2 } = await import("./conversations-HH3CJD4E.js");
6746
6820
  const spiritUser = await getOrCreateMindUser2("volute");
6747
6821
  const brains = await listUsersByType2("brain");
6748
6822
  const admin2 = brains.find((u) => u.role === "admin");
6749
6823
  if (admin2) {
6750
- const existing = await findDMConversation2("volute", [admin2.id, spiritUser.id]);
6824
+ const existing = await findDMConversation2([admin2.id, spiritUser.id]);
6751
6825
  if (existing) {
6752
6826
  spiritConversationId = existing;
6753
6827
  } else {
6754
- const conv = await createConversation6("volute", "volute", {
6755
- participantIds: [admin2.id, spiritUser.id],
6756
- title: "Volute"
6828
+ const conv = await createConversation6({
6829
+ participantIds: [admin2.id, spiritUser.id]
6757
6830
  });
6758
6831
  spiritConversationId = conv.id;
6759
6832
  }
@@ -6765,8 +6838,8 @@ setup.post("/complete", async (c) => {
6765
6838
  logger_default.info("setup complete state", { spiritConversationId, spiritStarted });
6766
6839
  if (spiritConversationId && spiritStarted) {
6767
6840
  try {
6768
- const { deliverMessage: deliverMessage2 } = await import("./message-delivery-GRC4W6P7.js");
6769
- const { listUsersByType: listUsers6 } = await import("./auth-WX4TESEI.js");
6841
+ const { deliverMessage: deliverMessage2 } = await import("./message-delivery-YORUXKDQ.js");
6842
+ const { listUsersByType: listUsers6 } = await import("./auth-YTQME4EV.js");
6770
6843
  const admins = await listUsers6("brain");
6771
6844
  const admin2 = admins.find((u) => u.role === "admin");
6772
6845
  const adminName = admin2?.display_name || admin2?.username || "the admin";
@@ -6988,7 +7061,6 @@ import { zValidator as zValidator8 } from "@hono/zod-validator";
6988
7061
  import { Hono as Hono22 } from "hono";
6989
7062
  import { z as z8 } from "zod";
6990
7063
  var createSchema = z8.object({
6991
- title: z8.string().optional(),
6992
7064
  participantNames: z8.array(z8.string()).min(1)
6993
7065
  });
6994
7066
  var app19 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
@@ -7045,10 +7117,17 @@ var app19 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
7045
7117
  if (participantIds.size > 2) {
7046
7118
  return c.json({ error: "Use channels for multi-participant conversations" }, 400);
7047
7119
  }
7048
- const conv = await createConversation(firstMindName, "volute", {
7120
+ const ids = [...participantIds];
7121
+ if (ids.length === 2) {
7122
+ const existingId = await findDMConversation(ids);
7123
+ if (existingId) {
7124
+ const existing = await getConversation(existingId);
7125
+ if (existing) return c.json(existing);
7126
+ }
7127
+ }
7128
+ const conv = await createConversation({
7049
7129
  userId: user.id !== 0 ? user.id : void 0,
7050
- title: body.title,
7051
- participantIds: [...participantIds]
7130
+ participantIds: ids
7052
7131
  });
7053
7132
  return c.json(conv, 201);
7054
7133
  }).post("/:id/read", async (c) => {
@@ -7237,7 +7316,7 @@ import { existsSync as existsSync10, mkdirSync as mkdirSync9, writeFileSync as w
7237
7316
  import { resolve as resolve15 } from "path";
7238
7317
  import { Hono as Hono24 } from "hono";
7239
7318
 
7240
- // packages/daemon/src/lib/spawn-server.ts
7319
+ // packages/daemon/src/lib/mind/spawn-server.ts
7241
7320
  import { spawn as spawn4 } from "child_process";
7242
7321
  import { closeSync, mkdirSync as mkdirSync8, openSync, readFileSync as readFileSync10 } from "fs";
7243
7322
  import { resolve as resolve14 } from "path";
@@ -7313,7 +7392,7 @@ function spawnDetached(cwd, port, logDir) {
7313
7392
  });
7314
7393
  }
7315
7394
 
7316
- // packages/daemon/src/lib/verify.ts
7395
+ // packages/daemon/src/lib/mind/verify.ts
7317
7396
  async function verify2(port) {
7318
7397
  const health = await checkHealth(port);
7319
7398
  if (!health.ok) {
@@ -7517,8 +7596,8 @@ var app21 = new Hono24().get("/:name/variants", async (c) => {
7517
7596
  await cleanupVariant(variantName, projectRoot, variantEntry.dir);
7518
7597
  if (variantName.endsWith("-upgrade") || variantName === "upgrade") {
7519
7598
  try {
7520
- const { computeTemplateHash: computeTemplateHash2 } = await import("./template-hash-A7FNHTB7.js");
7521
- const { setMindTemplateHash: setMindTemplateHash2 } = await import("./registry-GBSNW3HG.js");
7599
+ const { computeTemplateHash: computeTemplateHash2 } = await import("./template-hash-6ITI3WC4.js");
7600
+ const { setMindTemplateHash: setMindTemplateHash2 } = await import("./registry-YPHK534W.js");
7522
7601
  const tmpl = parentEntry.template ?? "claude";
7523
7602
  await setMindTemplateHash2(mindName, computeTemplateHash2(tmpl));
7524
7603
  } catch (err) {
@@ -7579,17 +7658,23 @@ var variants_default = app21;
7579
7658
  import { zValidator as zValidator9 } from "@hono/zod-validator";
7580
7659
  import { Hono as Hono25 } from "hono";
7581
7660
  import { z as z9 } from "zod";
7661
+ var channelSettingsSchema = z9.object({
7662
+ description: z9.string().nullable().optional(),
7663
+ rules: z9.string().nullable().optional(),
7664
+ charLimit: z9.number().int().positive().nullable().optional(),
7665
+ private: z9.boolean().optional()
7666
+ });
7582
7667
  var createSchema2 = z9.object({
7583
7668
  name: z9.string().min(1).max(50).regex(/^[a-z0-9][a-z0-9-]*$/, "Channel names must be lowercase alphanumeric with hyphens")
7584
- });
7669
+ }).merge(channelSettingsSchema);
7585
7670
  var inviteSchema = z9.object({
7586
7671
  username: z9.string().min(1)
7587
7672
  });
7588
7673
  var app22 = new Hono25().get("/", async (c) => {
7589
7674
  const user = c.get("user");
7590
- const channels = await listChannels();
7675
+ const channels2 = await listChannels();
7591
7676
  const results = await Promise.all(
7592
- channels.map(async (ch) => {
7677
+ channels2.map(async (ch) => {
7593
7678
  const participants = await getParticipants(ch.id);
7594
7679
  const isMember = participants.some((p) => p.userId === user.id);
7595
7680
  return { ...ch, participantCount: participants.length, isMember };
@@ -7600,8 +7685,9 @@ var app22 = new Hono25().get("/", async (c) => {
7600
7685
  const user = c.get("user");
7601
7686
  const body = c.req.valid("json");
7602
7687
  try {
7603
- const ch = await createChannel(body.name, user.id);
7604
- return c.json(ch, 201);
7688
+ const { name, ...settings } = body;
7689
+ const ch = await createChannel(name, user.id, settings);
7690
+ return c.json({ ...ch, channel_name: name }, 201);
7605
7691
  } catch (err) {
7606
7692
  const cause = err instanceof Error ? err.cause : null;
7607
7693
  if (cause && /UNIQUE/i.test(cause.extendedCode ?? cause.message ?? "")) {
@@ -7613,8 +7699,24 @@ var app22 = new Hono25().get("/", async (c) => {
7613
7699
  const name = c.req.param("name");
7614
7700
  const ch = await getChannelByName(name);
7615
7701
  if (!ch) return c.json({ error: "Channel not found" }, 404);
7616
- const participants = await getParticipants(ch.id);
7617
- return c.json({ ...ch, participants });
7702
+ const [participants, settings] = await Promise.all([
7703
+ getParticipants(ch.id),
7704
+ getChannelSettings(name)
7705
+ ]);
7706
+ return c.json({
7707
+ ...ch,
7708
+ channel_name: name,
7709
+ participants,
7710
+ settings: formatChannelSettings(settings)
7711
+ });
7712
+ }).patch("/:name", zValidator9("json", channelSettingsSchema), async (c) => {
7713
+ const name = c.req.param("name");
7714
+ const body = c.req.valid("json");
7715
+ const ch = await getChannelByName(name);
7716
+ if (!ch) return c.json({ error: "Channel not found" }, 404);
7717
+ await updateChannelSettings(name, body);
7718
+ const settings = await getChannelSettings(name);
7719
+ return c.json({ ...ch, channel_name: name, settings: formatChannelSettings(settings) });
7618
7720
  }).post("/:name/join", async (c) => {
7619
7721
  const name = c.req.param("name");
7620
7722
  const user = c.get("user");
@@ -7663,7 +7765,7 @@ import { Hono as Hono26 } from "hono";
7663
7765
  import { streamSSE as streamSSE5 } from "hono/streaming";
7664
7766
  import { z as z10 } from "zod";
7665
7767
 
7666
- // packages/daemon/src/lib/bridge-outbound.ts
7768
+ // packages/daemon/src/lib/bridges/bridge-outbound.ts
7667
7769
  function extractContent(contentBlocks) {
7668
7770
  const text = contentBlocks.filter((b) => b.type === "text").map((b) => b.text).join("\n");
7669
7771
  const images = contentBlocks.filter((b) => b.type === "image").map((b) => ({ media_type: b.media_type, data: b.data }));
@@ -7673,8 +7775,15 @@ async function routeOutboundBridge(conversationId, senderName, contentBlocks) {
7673
7775
  try {
7674
7776
  const conv = await getConversation(conversationId);
7675
7777
  if (!conv) return;
7676
- if (conv.type === "channel" && conv.name) {
7677
- await routeChannelOutbound(conv.name, senderName, contentBlocks);
7778
+ if (conv.type === "channel") {
7779
+ const channelName = await getChannelName(conversationId);
7780
+ if (channelName) {
7781
+ await routeChannelOutbound(channelName, senderName, contentBlocks);
7782
+ } else {
7783
+ logger_default.warn(
7784
+ `channel conversation ${conversationId} has no channel name \u2014 skipping bridge outbound`
7785
+ );
7786
+ }
7678
7787
  } else if (conv.type === "dm") {
7679
7788
  await routeDMOutbound(conversationId, senderName, contentBlocks);
7680
7789
  }
@@ -7685,7 +7794,7 @@ async function routeOutboundBridge(conversationId, senderName, contentBlocks) {
7685
7794
  async function routeChannelOutbound(channelName, senderName, contentBlocks) {
7686
7795
  const bridgeInfo = findBridgeForChannel(channelName);
7687
7796
  if (!bridgeInfo) return;
7688
- const driver = getChannelDriver(bridgeInfo.platform);
7797
+ const driver = getPlatformDriver(bridgeInfo.platform);
7689
7798
  if (!driver) {
7690
7799
  logger_default.warn(`no channel driver for bridge platform: ${bridgeInfo.platform}`);
7691
7800
  return;
@@ -7715,7 +7824,7 @@ async function routeDMOutbound(conversationId, senderName, contentBlocks) {
7715
7824
  const externalUserId = puppet.username.slice(colonIdx + 1);
7716
7825
  const bridgeConfig = getBridgeConfig(platform);
7717
7826
  if (!bridgeConfig?.enabled) continue;
7718
- const driver = getChannelDriver(platform);
7827
+ const driver = getPlatformDriver(platform);
7719
7828
  if (!driver?.createConversation) {
7720
7829
  logger_default.warn(`no channel driver with DM support for bridge platform: ${platform}`);
7721
7830
  continue;
@@ -7736,16 +7845,14 @@ async function routeDMOutbound(conversationId, senderName, contentBlocks) {
7736
7845
 
7737
7846
  // packages/daemon/src/web/api/volute/chat.ts
7738
7847
  async function fanOutToMinds(opts) {
7739
- const participants = await getParticipants(opts.conversationId);
7848
+ const participants = opts.participants;
7740
7849
  const mindParticipants = participants.filter(
7741
7850
  (p) => p.userType === "mind" || p.userType === "system"
7742
7851
  );
7743
7852
  const participantNames = participants.map((p) => p.username);
7744
7853
  const isDM = opts.isDM ?? participants.length === 2;
7745
- const { getMindManager: getMindManager2 } = await import("./mind-manager-HFLB5653.js");
7746
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-7KFK3USC.js");
7747
- const manager = getMindManager2();
7748
- const sm = getSleepManagerIfReady2();
7854
+ const manager = getMindManager();
7855
+ const sm = getSleepManagerIfReady();
7749
7856
  const targetMinds = mindParticipants.map((ap) => {
7750
7857
  const key = opts.targetName ? opts.targetName(ap.username) : ap.username;
7751
7858
  if (manager.isRunning(key) || sm?.isSleeping(ap.username)) return ap.username;
@@ -7755,7 +7862,6 @@ async function fanOutToMinds(opts) {
7755
7862
  return buildVoluteSlug({
7756
7863
  participants,
7757
7864
  mindUsername,
7758
- convTitle: opts.convTitle,
7759
7865
  conversationId: opts.conversationId,
7760
7866
  ...opts.slugExtra
7761
7867
  });
@@ -7868,17 +7974,14 @@ var unifiedChatApp = new Hono26().post(
7868
7974
  }
7869
7975
  participantIds.push(mindUser.id);
7870
7976
  if (participantIds.length === 2) {
7871
- const existing = await findDMConversation(baseName, participantIds);
7977
+ const existing = await findDMConversation(participantIds);
7872
7978
  if (existing) {
7873
7979
  conversationId = existing;
7874
7980
  }
7875
7981
  }
7876
7982
  if (!conversationId) {
7877
- const participantNames = /* @__PURE__ */ new Set([senderName, baseName]);
7878
- const title = [...participantNames].join(", ");
7879
- const conv2 = await createConversation(baseName, "volute", {
7983
+ const conv2 = await createConversation({
7880
7984
  userId: user.id !== 0 ? user.id : void 0,
7881
- title,
7882
7985
  participantIds
7883
7986
  });
7884
7987
  conversationId = conv2.id;
@@ -7891,14 +7994,14 @@ var unifiedChatApp = new Hono26().post(
7891
7994
  }
7892
7995
  const conv = await getConversation(conversationId);
7893
7996
  if (!conv) return c.json({ error: "Conversation not found" }, 404);
7894
- const convTitle = conv.title;
7997
+ const convName = conv.type === "channel" ? await getChannelName(conversationId) : null;
7998
+ const participants = await getParticipants(conversationId);
7895
7999
  const fileNotifications = [];
7896
8000
  if (body.files && body.files.length > 0) {
7897
8001
  let fileTargets;
7898
8002
  if (baseName) {
7899
8003
  fileTargets = [baseName];
7900
8004
  } else {
7901
- const participants = await getParticipants(conversationId);
7902
8005
  fileTargets = participants.filter((p) => p.userType === "mind" && p.username !== senderName).map((p) => p.username);
7903
8006
  }
7904
8007
  const { notifications, error } = stageFilesForMinds(body.files, fileTargets, senderName);
@@ -7915,6 +8018,25 @@ var unifiedChatApp = new Hono26().post(
7915
8018
  contentBlocks.push({ type: "image", media_type: img.media_type, data: img.data });
7916
8019
  }
7917
8020
  }
8021
+ if (senderIsMind && conv.type === "channel" && convName) {
8022
+ try {
8023
+ const chSettings = await getChannelSettings(convName);
8024
+ if (chSettings?.char_limit) {
8025
+ for (const block of contentBlocks) {
8026
+ if (block.type === "text" && block.text.length > chSettings.char_limit) {
8027
+ return c.json(
8028
+ {
8029
+ error: `Message exceeds channel character limit (${chSettings.char_limit}). Shorten your message and try again.`
8030
+ },
8031
+ 400
8032
+ );
8033
+ }
8034
+ }
8035
+ }
8036
+ } catch (err) {
8037
+ logger_default.warn("failed to look up channel char_limit, skipping enforcement", logger_default.errorData(err));
8038
+ }
8039
+ }
7918
8040
  const message = await addMessage(conversationId, "user", senderName, contentBlocks);
7919
8041
  let outboundId;
7920
8042
  if (senderIsMind) {
@@ -7922,12 +8044,11 @@ var unifiedChatApp = new Hono26().post(
7922
8044
  logger_default.warn("outbound bridge routing failed", logger_default.errorData(err));
7923
8045
  });
7924
8046
  const channel = buildVoluteSlug({
7925
- participants: await getParticipants(conversationId),
8047
+ participants,
7926
8048
  mindUsername: senderName,
7927
- convTitle,
7928
8049
  conversationId,
7929
8050
  convType: conv.type,
7930
- convName: conv.name
8051
+ convName
7931
8052
  });
7932
8053
  try {
7933
8054
  outboundId = await recordOutbound(senderName, channel, extractTextContent(contentBlocks), {
@@ -7942,15 +8063,14 @@ var unifiedChatApp = new Hono26().post(
7942
8063
  conversationId,
7943
8064
  contentBlocks,
7944
8065
  senderName,
7945
- convTitle,
8066
+ participants,
7946
8067
  isDM,
7947
- slugExtra: { convType: conv.type, convName: conv.name },
8068
+ slugExtra: { convType: conv.type, convName },
7948
8069
  // Variant-aware targeting: when targetMind is a variant, route to the variant name
7949
8070
  targetName: baseName ? (username) => username === baseName ? variantName : username : void 0
7950
8071
  });
7951
8072
  const systemReplyTarget = baseName ?? senderName;
7952
8073
  if (senderIsMind && body.message) {
7953
- const participants = await getParticipants(conversationId);
7954
8074
  const hasSystemUser = participants.some((p) => p.userType === "system");
7955
8075
  if (hasSystemUser) {
7956
8076
  generateSystemReply(conversationId, systemReplyTarget, body.message).catch(
@@ -7994,7 +8114,6 @@ import { zValidator as zValidator11 } from "@hono/zod-validator";
7994
8114
  import { Hono as Hono27 } from "hono";
7995
8115
  import { z as z11 } from "zod";
7996
8116
  var createConvSchema = z11.object({
7997
- title: z11.string().optional(),
7998
8117
  participantIds: z11.array(z11.number()).optional(),
7999
8118
  participantNames: z11.array(z11.string()).optional()
8000
8119
  });
@@ -8006,8 +8125,7 @@ var app24 = new Hono27().get("/:name/conversations", async (c) => {
8006
8125
  const mindUser = await getOrCreateMindUser(name);
8007
8126
  lookupId = mindUser.id;
8008
8127
  }
8009
- const all = await listConversationsForUser(lookupId);
8010
- const convs = all.filter((c2) => c2.mind_name === name || c2.type === "channel");
8128
+ const convs = await listConversationsForUser(lookupId);
8011
8129
  return c.json(convs);
8012
8130
  }).post("/:name/conversations", zValidator11("json", createConvSchema), async (c) => {
8013
8131
  const name = c.req.param("name");
@@ -8055,20 +8173,15 @@ var app24 = new Hono27().get("/:name/conversations", async (c) => {
8055
8173
  return c.json({ error: "Use channels for multi-participant conversations" }, 400);
8056
8174
  }
8057
8175
  if (participantIds.length === 2) {
8058
- const existingId = await findDMConversation(name, participantIds);
8176
+ const existingId = await findDMConversation(participantIds);
8059
8177
  if (existingId) {
8060
8178
  const conv2 = await getConversation(existingId);
8061
8179
  if (conv2) return c.json(conv2);
8062
8180
  console.warn(`[conversations] DM conversation ${existingId} found but not retrievable`);
8063
8181
  }
8064
8182
  }
8065
- let title = body.title;
8066
- if (!title && body.participantNames?.length) {
8067
- title = body.participantNames.join(", ");
8068
- }
8069
- const conv = await createConversation(name, "volute", {
8183
+ const conv = await createConversation({
8070
8184
  userId: user.id !== 0 ? user.id : void 0,
8071
- title,
8072
8185
  participantIds
8073
8186
  });
8074
8187
  return c.json(conv, 201);
@@ -8146,10 +8259,11 @@ app25.use(
8146
8259
  credentials: false
8147
8260
  })
8148
8261
  );
8262
+ var csrfMiddleware = csrf();
8149
8263
  app25.use("/api/*", async (c, next) => {
8150
8264
  const auth = c.req.header("Authorization");
8151
8265
  if (auth?.startsWith("Bearer ") && auth.length > 7) return next();
8152
- return csrf()(c, next);
8266
+ return csrfMiddleware(c, next);
8153
8267
  });
8154
8268
  app25.get("/api/health", (c) => {
8155
8269
  let version = "unknown";
@@ -8238,13 +8352,17 @@ async function startServer({
8238
8352
  const ext = extname3(filePath);
8239
8353
  const mime = MIME_TYPES2[ext] || "application/octet-stream";
8240
8354
  const body = await readFile2(filePath);
8241
- return c.body(body, 200, { "Content-Type": mime });
8355
+ const basename = filePath.slice(filePath.lastIndexOf("/") + 1);
8356
+ const nameWithoutExt = basename.slice(0, basename.lastIndexOf("."));
8357
+ const isHashed = /[-.][\da-f]{8,}$/.test(nameWithoutExt);
8358
+ const cacheControl = isHashed ? "public, max-age=31536000, immutable" : "no-cache";
8359
+ return c.body(body, 200, { "Content-Type": mime, "Cache-Control": cacheControl });
8242
8360
  }
8243
8361
  const indexPath = resolve16(assetsDir, "index.html");
8244
8362
  const indexStat = await stat2(indexPath).catch(() => null);
8245
8363
  if (indexStat?.isFile()) {
8246
8364
  const body = await readFile2(indexPath, "utf-8");
8247
- return c.html(body);
8365
+ return c.html(body, 200, { "Cache-Control": "no-cache" });
8248
8366
  }
8249
8367
  return c.text("Not found", 404);
8250
8368
  });
@@ -8322,18 +8440,18 @@ async function startDaemon(opts) {
8322
8440
  const DAEMON_JSON_PATH = resolve17(systemDir, "daemon.json");
8323
8441
  mkdirSync10(home, { recursive: true });
8324
8442
  ensureSystemDir();
8325
- const { migrateSetupCompleted } = await import("./setup-RHJRFURI.js");
8443
+ const { migrateSetupCompleted } = await import("./setup-PMJHCZQX.js");
8326
8444
  migrateSetupCompleted();
8327
- await (await import("./db-BVBJ57TU.js")).getDb();
8445
+ await (await import("./db-URORGSXQ.js")).getDb();
8328
8446
  try {
8329
8447
  const { eq: eq7, and: and5 } = await import("drizzle-orm");
8330
- const { users: users2 } = await import("./schema-XVZ2CLKW.js");
8331
- const db = await (await import("./db-BVBJ57TU.js")).getDb();
8448
+ const { users: users2 } = await import("./schema-MISD3JFG.js");
8449
+ const db = await (await import("./db-URORGSXQ.js")).getDb();
8332
8450
  await db.update(users2).set({ role: "system" }).where(and5(eq7(users2.user_type, "system"), eq7(users2.role, "user")));
8333
8451
  } catch (err) {
8334
8452
  logger_default.warn("failed to migrate system user role", logger_default.errorData(err));
8335
8453
  }
8336
- const { initSandbox } = await import("./sandbox-R37VIU36.js");
8454
+ const { initSandbox } = await import("./sandbox-LP6YRAXS.js");
8337
8455
  await initSandbox();
8338
8456
  try {
8339
8457
  await syncBuiltinSkills();
@@ -8360,7 +8478,7 @@ async function startDaemon(opts) {
8360
8478
  logger_default.warn("failed to ensure #system channel", logger_default.errorData(err));
8361
8479
  }
8362
8480
  try {
8363
- const { getOrCreateSystemUser: getOrCreateSystemUser2 } = await import("./auth-WX4TESEI.js");
8481
+ const { getOrCreateSystemUser: getOrCreateSystemUser2 } = await import("./auth-YTQME4EV.js");
8364
8482
  await getOrCreateSystemUser2();
8365
8483
  } catch (err) {
8366
8484
  logger_default.warn(
@@ -8371,7 +8489,7 @@ async function startDaemon(opts) {
8371
8489
  const token = process.env.VOLUTE_DAEMON_TOKEN || randomBytes(32).toString("hex");
8372
8490
  let tls;
8373
8491
  if (opts.tailscale) {
8374
- const { getTailscaleTls } = await import("./tailscale-ZIZ2HWJ5.js");
8492
+ const { getTailscaleTls } = await import("./tailscale-DZU4WM3E.js");
8375
8493
  const tlsConfig = await getTailscaleTls();
8376
8494
  tls = { key: tlsConfig.key, cert: tlsConfig.cert };
8377
8495
  logger_default.info("Tailscale HTTPS enabled", { hostname: tlsConfig.hostname });
@@ -8444,10 +8562,10 @@ async function startDaemon(opts) {
8444
8562
  await Promise.all(workers);
8445
8563
  }
8446
8564
  try {
8447
- const { isSetupComplete: isSetupComplete2 } = await import("./setup-RHJRFURI.js");
8565
+ const { isSetupComplete: isSetupComplete2 } = await import("./setup-PMJHCZQX.js");
8448
8566
  if (isSetupComplete2()) {
8449
- const { ensureSpiritProject, syncSpiritTemplate } = await import("./spirit-ZFRDXMG7.js");
8450
- const { startSpiritFull } = await import("./mind-service-X2CAA6W6.js");
8567
+ const { ensureSpiritProject, syncSpiritTemplate } = await import("./spirit-6KVDIROQ.js");
8568
+ const { startSpiritFull } = await import("./mind-service-E7FM2WZF.js");
8451
8569
  await ensureSpiritProject();
8452
8570
  await syncSpiritTemplate();
8453
8571
  const spiritEntry = await findMind("volute");
@@ -8464,7 +8582,7 @@ async function startDaemon(opts) {
8464
8582
  bridgeManager.startBridges(daemonPort).catch((err) => {
8465
8583
  logger_default.warn("failed to start bridges", logger_default.errorData(err));
8466
8584
  });
8467
- import("./cloud-sync-6JL4C24T.js").then(
8585
+ import("./cloud-sync-BOCZSDIA.js").then(
8468
8586
  ({ consumeQueuedMessages }) => consumeQueuedMessages().catch((err) => {
8469
8587
  logger_default.warn("failed to consume queued cloud messages", logger_default.errorData(err));
8470
8588
  })
@@ -8472,7 +8590,7 @@ async function startDaemon(opts) {
8472
8590
  logger_default.warn("failed to load cloud-sync module", logger_default.errorData(err));
8473
8591
  });
8474
8592
  try {
8475
- const { backfillTemplateHashes, notifyVersionUpdate } = await import("./version-notify-FXSEMXWW.js");
8593
+ const { backfillTemplateHashes, notifyVersionUpdate } = await import("./version-notify-UXSHBZ35.js");
8476
8594
  backfillTemplateHashes();
8477
8595
  notifyVersionUpdate().catch((err) => {
8478
8596
  logger_default.warn("failed to send version update notifications", logger_default.errorData(err));