volute 0.24.0 → 0.26.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 (114) hide show
  1. package/README.md +15 -20
  2. package/dist/{activity-events-4O37J7PD.js → activity-events-ZMBAKLUF.js} +2 -2
  3. package/dist/api.d.ts +590 -10
  4. package/dist/{auth-HM2RSPY7.js → auth-4TV573WE.js} +2 -2
  5. package/dist/{channel-HZOSHGNF.js → channel-ZVZV42UD.js} +3 -3
  6. package/dist/{chunk-NOBRGACV.js → chunk-2VO7453N.js} +56 -19
  7. package/dist/{chunk-OOW675I3.js → chunk-3CFRE2VC.js} +931 -775
  8. package/dist/{chunk-PHHKNGA3.js → chunk-3TV4GLFO.js} +2 -2
  9. package/dist/{chunk-4TJ72QQ3.js → chunk-5Y3PBKW6.js} +3 -3
  10. package/dist/{chunk-BFK6SOEJ.js → chunk-J2CO4WEV.js} +1 -1
  11. package/dist/{chunk-TQDITGES.js → chunk-LX22GRG7.js} +10 -13
  12. package/dist/{chunk-E7GOKNOT.js → chunk-NWI2425I.js} +1 -1
  13. package/dist/{chunk-2767L2RZ.js → chunk-OZFKBXD6.js} +1 -1
  14. package/dist/{chunk-XLC342FO.js → chunk-SIAG3QMM.js} +14 -1
  15. package/dist/{chunk-RVKR2R7F.js → chunk-SSI47XP2.js} +10 -2
  16. package/dist/chunk-TZKJLDQN.js +78 -0
  17. package/dist/{chunk-P3W36ZGD.js → chunk-USNBKHYG.js} +33 -5
  18. package/dist/chunk-UTL75LP6.js +113 -0
  19. package/dist/{chunk-3AIBT4TW.js → chunk-V63B7DX3.js} +24 -1
  20. package/dist/{chunk-33XAVCS4.js → chunk-WBHMQ5OZ.js} +49 -0
  21. package/dist/{chunk-TRQEV3CD.js → chunk-WGOGUMPO.js} +22 -3
  22. package/dist/chunk-XOXLRRR2.js +176 -0
  23. package/dist/{chunk-JTDFJWI2.js → chunk-YJA7P64S.js} +1 -1
  24. package/dist/chunk-ZYGKG6VC.js +22 -0
  25. package/dist/cli.js +44 -20
  26. package/dist/{cloud-sync-DIU3OCPV.js → cloud-sync-NI2K3C7G.js} +11 -9
  27. package/dist/{connector-M6XFI6GM.js → connector-G722WXAU.js} +4 -4
  28. package/dist/{create-VDQJER52.js → create-4YBRTTJS.js} +1 -1
  29. package/dist/{daemon-client-JOVQZ52X.js → daemon-client-Z7FAJ6JW.js} +1 -1
  30. package/dist/{daemon-restart-YMPEATQH.js → daemon-restart-BJZ3O4U4.js} +6 -5
  31. package/dist/daemon.js +982 -340
  32. package/dist/{delete-2MRR4JX5.js → delete-27OYNK25.js} +1 -1
  33. package/dist/{down-674SX2IZ.js → down-7UKFMJJZ.js} +4 -4
  34. package/dist/{env-2FPOZK37.js → env-M336ONDP.js} +4 -4
  35. package/dist/{export-IKFAPRAO.js → export-HP4G5DQC.js} +1 -1
  36. package/dist/{file-KT3UIQM3.js → file-HUDKTRAS.js} +3 -3
  37. package/dist/{history-46WZN5CN.js → history-B64GTFTD.js} +3 -3
  38. package/dist/{import-FRDPQPJ2.js → import-XIB7UV4S.js} +2 -2
  39. package/dist/{log-6SGSSR3D.js → log-PBFNILJ4.js} +3 -3
  40. package/dist/{login-UO6AOVEA.js → login-6U7U6BNG.js} +1 -1
  41. package/dist/login-B5E7N7MY.js +46 -0
  42. package/dist/logout-XSJRYS3U.js +39 -0
  43. package/dist/{logs-HRBONI5I.js → logs-3CART7O7.js} +3 -3
  44. package/dist/{merge-KSFJKX6T.js → merge-VK2HSKMA.js} +3 -3
  45. package/dist/{message-delivery-S7BCNV6Y.js → message-delivery-MS5JYPZX.js} +11 -9
  46. package/dist/{mind-KPLCRKQA.js → mind-HZ3QSDDJ.js} +17 -17
  47. package/dist/{mind-activity-tracker-NMDDEV3K.js → mind-activity-tracker-4G6FURY2.js} +3 -3
  48. package/dist/{mind-manager-ZNRIYEK3.js → mind-manager-VVK67AY3.js} +6 -4
  49. package/dist/{mind-sleep-GHPTSAYN.js → mind-sleep-DTV7L44D.js} +3 -3
  50. package/dist/{mind-wake-BJDJFMDF.js → mind-wake-PFN4FN3T.js} +3 -3
  51. package/dist/notes-37FW2UR2.js +230 -0
  52. package/dist/{package-S5YF25XV.js → package-VZWLXPHV.js} +3 -1
  53. package/dist/{pages-TWR6U7DS.js → pages-DIIT5HMQ.js} +1 -1
  54. package/dist/{publish-BZNHKUUK.js → publish-HQV7YREB.js} +4 -4
  55. package/dist/{pull-D32SPFVU.js → pull-2MB4SK3C.js} +3 -3
  56. package/dist/{register-U2UO6TC4.js → register-EFND67FQ.js} +1 -1
  57. package/dist/{restart-5BMNV7KU.js → restart-CCK7D6TV.js} +3 -3
  58. package/dist/sandbox-EHGFF52K.js +19 -0
  59. package/dist/{schedule-YEFDLVMJ.js → schedule-6F7ELB2M.js} +3 -3
  60. package/dist/{seed-6FEKB3YC.js → seed-E5OQGWX3.js} +1 -1
  61. package/dist/{send-IISDYFCL.js → send-IH6XZKPC.js} +6 -20
  62. package/dist/service-LLBV3R7M.js +122 -0
  63. package/dist/setup-F6TWFYGQ.js +371 -0
  64. package/dist/setup-YGAAIKKZ.js +17 -0
  65. package/dist/{shared-LWMNTTZN.js → shared-UMO4S7CC.js} +4 -4
  66. package/dist/{skill-BQOFACEI.js → skill-42LGFBQC.js} +13 -5
  67. package/dist/skills/dreaming/SKILL.md +68 -0
  68. package/dist/skills/dreaming/references/INSTALL.md +56 -0
  69. package/dist/skills/dreaming/scripts/dream.ts +289 -0
  70. package/dist/skills/dreaming/scripts/wake-context-dreams.sh +30 -0
  71. package/dist/skills/imagegen/SKILL.md +37 -0
  72. package/dist/skills/imagegen/references/INSTALL.md +13 -0
  73. package/dist/skills/imagegen/scripts/imagegen.ts +136 -0
  74. package/dist/skills/notes/SKILL.md +34 -0
  75. package/dist/skills/resonance/SKILL.md +73 -0
  76. package/dist/skills/resonance/assets/default-config.json +21 -0
  77. package/dist/skills/resonance/references/INSTALL.md +23 -0
  78. package/dist/skills/resonance/scripts/resonance.ts +1250 -0
  79. package/dist/skills/volute-mind/SKILL.md +23 -3
  80. package/dist/{sleep-manager-XXSWQQLE.js → sleep-manager-EE4NRN2Q.js} +11 -9
  81. package/dist/{sprout-CGSW4CF5.js → sprout-QL74KR2X.js} +5 -5
  82. package/dist/{start-C7XITZ5O.js → start-O5JQASRC.js} +3 -3
  83. package/dist/{status-SIRPLEZC.js → status-FZBEBM7Q.js} +3 -3
  84. package/dist/{status-LYS4NUOZ.js → status-WXD4HXRL.js} +3 -3
  85. package/dist/{stop-CVKBSLXY.js → stop-2SOG5NYF.js} +3 -3
  86. package/dist/up-SDMCSVI3.js +17 -0
  87. package/dist/{update-7XCZMYBT.js → update-5VUDAI3D.js} +6 -6
  88. package/dist/{upgrade-7RUIXGOO.js → upgrade-QCCO33BK.js} +1 -1
  89. package/dist/{variant-UGREB4G5.js → variant-WWLDY6D5.js} +4 -4
  90. package/dist/{version-notify-SZ75QRGO.js → version-notify-USFZBWMG.js} +11 -9
  91. package/dist/web-assets/assets/index-CUQ31ieL.js +69 -0
  92. package/dist/web-assets/assets/index-CW8NSl1o.css +1 -0
  93. package/dist/web-assets/favicon.png +0 -0
  94. package/dist/web-assets/index.html +5 -4
  95. package/dist/web-assets/logo.png +0 -0
  96. package/drizzle/0015_notes.sql +23 -0
  97. package/drizzle/0016_note_reactions_and_replies.sql +15 -0
  98. package/drizzle/meta/_journal.json +14 -0
  99. package/package.json +3 -1
  100. package/templates/_base/.init/.config/hooks/wake-context.sh +7 -0
  101. package/templates/_base/home/public/.gitkeep +0 -0
  102. package/templates/_base/src/lib/startup.ts +8 -0
  103. package/templates/claude/src/agent.ts +51 -1
  104. package/templates/claude/src/server.ts +1 -0
  105. package/templates/pi/package.json.tmpl +1 -0
  106. package/templates/pi/src/agent.ts +48 -1
  107. package/templates/pi/src/lib/subagents.ts +150 -0
  108. package/templates/pi/src/server.ts +1 -0
  109. package/dist/chunk-NWPT4ASZ.js +0 -89
  110. package/dist/service-FASYWLTC.js +0 -247
  111. package/dist/setup-BMLM2UTK.js +0 -230
  112. package/dist/up-OMHACRJL.js +0 -15
  113. package/dist/web-assets/assets/index-Bx9WDoaQ.js +0 -69
  114. package/dist/web-assets/assets/index-Clz8OhmJ.css +0 -1
package/dist/daemon.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  sharedMerge,
8
8
  sharedPull,
9
9
  sharedStatus
10
- } from "./chunk-PHHKNGA3.js";
10
+ } from "./chunk-3TV4GLFO.js";
11
11
  import {
12
12
  applyInitFiles,
13
13
  composeTemplate,
@@ -18,6 +18,7 @@ import {
18
18
  } from "./chunk-AKPFNL7L.js";
19
19
  import {
20
20
  addMessage,
21
+ announceToSystem,
21
22
  approveUser,
22
23
  changePassword,
23
24
  countAdmins,
@@ -28,6 +29,7 @@ import {
28
29
  deleteMindUser as deleteMindUser2,
29
30
  deleteUser,
30
31
  deliverMessage,
32
+ ensureSystemChannel,
31
33
  extractTextContent,
32
34
  findDMConversation,
33
35
  fireWebhook,
@@ -58,6 +60,7 @@ import {
58
60
  isParticipant,
59
61
  isParticipantOrOwner,
60
62
  joinChannel,
63
+ joinSystemChannel,
61
64
  leaveChannel,
62
65
  listChannels,
63
66
  listConversationsForUser,
@@ -66,6 +69,7 @@ import {
66
69
  listUsers,
67
70
  listUsersByType,
68
71
  markConversationRead,
72
+ migrateMindRoles,
69
73
  publish,
70
74
  publish2,
71
75
  publishTypingForChannels,
@@ -78,19 +82,21 @@ import {
78
82
  subscribe2 as subscribe3,
79
83
  updateUserProfile,
80
84
  verifyUser
81
- } from "./chunk-OOW675I3.js";
85
+ } from "./chunk-3CFRE2VC.js";
82
86
  import {
83
- readSystemsConfig
87
+ deleteSystemsConfig,
88
+ readSystemsConfig,
89
+ writeSystemsConfig
84
90
  } from "./chunk-HFCBO2GL.js";
85
91
  import {
86
92
  getActiveMinds,
87
93
  onMindEvent,
88
94
  stopAll
89
- } from "./chunk-E7GOKNOT.js";
95
+ } from "./chunk-NWI2425I.js";
90
96
  import {
91
97
  broadcast,
92
98
  subscribe
93
- } from "./chunk-BFK6SOEJ.js";
99
+ } from "./chunk-J2CO4WEV.js";
94
100
  import {
95
101
  PROMPT_DEFAULTS,
96
102
  PROMPT_KEYS,
@@ -100,28 +106,10 @@ import {
100
106
  getPrompt,
101
107
  getPromptIfCustom,
102
108
  initMindManager,
109
+ resolveMindToken,
103
110
  substitute
104
- } from "./chunk-NOBRGACV.js";
105
- import {
106
- findOpenClawSession,
107
- importOpenClawConnectors,
108
- importPiSession,
109
- parseNameFromIdentity
110
- } from "./chunk-4TJ72QQ3.js";
111
- import {
112
- readVoluteConfig,
113
- writeVoluteConfig
114
- } from "./chunk-XLC342FO.js";
115
- import {
116
- loadMergedEnv,
117
- mindEnvPath,
118
- readEnv,
119
- sharedEnvPath,
120
- writeEnv
121
- } from "./chunk-PHU4DEAJ.js";
122
- import {
123
- isHomeOnlyArchive
124
- } from "./chunk-KTJGZ7M7.js";
111
+ } from "./chunk-2VO7453N.js";
112
+ import "./chunk-UTL75LP6.js";
125
113
  import {
126
114
  SEED_SKILLS,
127
115
  STANDARD_SKILLS,
@@ -137,25 +125,53 @@ import {
137
125
  syncBuiltinSkills,
138
126
  uninstallSkill,
139
127
  updateSkill
140
- } from "./chunk-P3W36ZGD.js";
128
+ } from "./chunk-USNBKHYG.js";
141
129
  import {
142
130
  activity,
143
131
  conversations,
144
132
  getDb,
145
133
  mindHistory,
134
+ noteComments,
135
+ noteReactions,
136
+ notes,
146
137
  sessions,
147
- systemPrompts
148
- } from "./chunk-33XAVCS4.js";
138
+ systemPrompts,
139
+ users
140
+ } from "./chunk-WBHMQ5OZ.js";
149
141
  import {
150
142
  logBuffer,
151
143
  logger_default
152
144
  } from "./chunk-YUIHSKR6.js";
153
- import "./chunk-D424ZQGI.js";
145
+ import {
146
+ checkForUpdate,
147
+ checkForUpdateCached,
148
+ getCurrentVersion
149
+ } from "./chunk-ON3FF5JA.js";
150
+ import {
151
+ findOpenClawSession,
152
+ importOpenClawConnectors,
153
+ importPiSession,
154
+ parseNameFromIdentity
155
+ } from "./chunk-5Y3PBKW6.js";
156
+ import {
157
+ readVoluteConfig,
158
+ writeVoluteConfig
159
+ } from "./chunk-SIAG3QMM.js";
160
+ import {
161
+ loadMergedEnv,
162
+ mindEnvPath,
163
+ readEnv,
164
+ sharedEnvPath,
165
+ writeEnv
166
+ } from "./chunk-PHU4DEAJ.js";
167
+ import {
168
+ isHomeOnlyArchive
169
+ } from "./chunk-KTJGZ7M7.js";
154
170
  import {
155
171
  exec,
156
172
  gitExec,
157
173
  resolveVoluteBin
158
- } from "./chunk-JTDFJWI2.js";
174
+ } from "./chunk-YJA7P64S.js";
159
175
  import {
160
176
  chownMindDir,
161
177
  createMindUser,
@@ -163,12 +179,8 @@ import {
163
179
  ensureVoluteGroup,
164
180
  isIsolationEnabled,
165
181
  wrapForIsolation
166
- } from "./chunk-NWPT4ASZ.js";
167
- import {
168
- checkForUpdate,
169
- checkForUpdateCached,
170
- getCurrentVersion
171
- } from "./chunk-ON3FF5JA.js";
182
+ } from "./chunk-XOXLRRR2.js";
183
+ import "./chunk-D424ZQGI.js";
172
184
  import {
173
185
  buildVoluteSlug,
174
186
  resolveChannelId,
@@ -176,6 +188,7 @@ import {
176
188
  splitMessage,
177
189
  writeChannelEntry
178
190
  } from "./chunk-WSLPZF72.js";
191
+ import "./chunk-TZKJLDQN.js";
179
192
  import {
180
193
  addMind,
181
194
  addVariant,
@@ -210,7 +223,7 @@ import {
210
223
  import { randomBytes as randomBytes2 } from "crypto";
211
224
  import { mkdirSync as mkdirSync9, readFileSync as readFileSync11, unlinkSync, writeFileSync as writeFileSync9 } from "fs";
212
225
  import { homedir as homedir2 } from "os";
213
- import { resolve as resolve18 } from "path";
226
+ import { resolve as resolve19 } from "path";
214
227
  import { format } from "util";
215
228
 
216
229
  // src/lib/migrate-agents-to-minds.ts
@@ -360,6 +373,17 @@ function migrateDotVoluteDir(name) {
360
373
  console.warn(`[migrate] both .volute/ and .mind/ exist for ${name}, skipping rename`);
361
374
  }
362
375
  }
376
+ function migratePagesDirToPublic(name) {
377
+ const dir = mindDir(name);
378
+ const oldPagesDir = resolve2(dir, "home", "pages");
379
+ const newPublicDir = resolve2(dir, "home", "public");
380
+ const newPagesDir = resolve2(newPublicDir, "pages");
381
+ if (existsSync2(oldPagesDir) && !existsSync2(newPagesDir)) {
382
+ mkdirSync(newPublicDir, { recursive: true });
383
+ renameSync2(oldPagesDir, newPagesDir);
384
+ logger_default.info(`migrated pages/ \u2192 public/pages/ for ${name}`);
385
+ }
386
+ }
363
387
  function migrateMindState(name) {
364
388
  const src = resolve2(mindDir(name), ".mind");
365
389
  if (!existsSync2(src)) return;
@@ -396,7 +420,7 @@ function isValidDaemonToken(token) {
396
420
  if (!expected || token.length !== expected.length) return false;
397
421
  return timingSafeEqual(Buffer.from(token), Buffer.from(expected));
398
422
  }
399
- var SESSION_MAX_AGE = 864e5;
423
+ var SESSION_MAX_AGE = 365 * 24 * 60 * 60 * 1e3;
400
424
  var SESSION_CACHE_TTL = 5 * 60 * 1e3;
401
425
  var sessionCache = /* @__PURE__ */ new Map();
402
426
  function invalidateSessionCache(sessionId) {
@@ -435,6 +459,24 @@ var requireAdmin = createMiddleware(async (c, next) => {
435
459
  }
436
460
  await next();
437
461
  });
462
+ async function resolveSession(sessionId) {
463
+ const cached = sessionCache.get(sessionId);
464
+ if (cached && cached.expires > Date.now()) {
465
+ return cached.user;
466
+ }
467
+ const userId = await getSessionUserId(sessionId);
468
+ if (userId == null) {
469
+ sessionCache.delete(sessionId);
470
+ return null;
471
+ }
472
+ const user = await getUser(userId);
473
+ if (!user) {
474
+ sessionCache.delete(sessionId);
475
+ return null;
476
+ }
477
+ sessionCache.set(sessionId, { userId, user, expires: Date.now() + SESSION_CACHE_TTL });
478
+ return user;
479
+ }
438
480
  var authMiddleware = createMiddleware(async (c, next) => {
439
481
  const authHeader = c.req.header("Authorization");
440
482
  if (authHeader?.startsWith("Bearer ")) {
@@ -442,7 +484,7 @@ var authMiddleware = createMiddleware(async (c, next) => {
442
484
  if (token && isValidDaemonToken(token)) {
443
485
  c.set("user", {
444
486
  id: 0,
445
- username: "cli",
487
+ username: "daemon",
446
488
  role: "admin",
447
489
  user_type: "brain",
448
490
  display_name: null,
@@ -452,41 +494,52 @@ var authMiddleware = createMiddleware(async (c, next) => {
452
494
  await next();
453
495
  return;
454
496
  }
497
+ const mindName = resolveMindToken(token);
498
+ if (mindName) {
499
+ const mindUser = await getOrCreateMindUser(mindName);
500
+ c.set("user", mindUser);
501
+ await next();
502
+ return;
503
+ }
504
+ if (token) {
505
+ const user2 = await resolveSession(token);
506
+ if (user2) {
507
+ if (user2.role === "pending") return c.json({ error: "Account pending approval" }, 403);
508
+ c.set("user", user2);
509
+ await next();
510
+ return;
511
+ }
512
+ }
455
513
  }
456
514
  const sessionId = getCookie(c, "volute_session");
457
515
  if (!sessionId) return c.json({ error: "Unauthorized" }, 401);
458
- const cached = sessionCache.get(sessionId);
459
- if (cached && cached.expires > Date.now()) {
460
- if (cached.user.role === "pending") return c.json({ error: "Account pending approval" }, 403);
461
- c.set("user", cached.user);
462
- await next();
463
- return;
464
- }
465
- const userId = await getSessionUserId(sessionId);
466
- if (userId == null) {
467
- sessionCache.delete(sessionId);
468
- return c.json({ error: "Unauthorized" }, 401);
469
- }
470
- const user = await getUser(userId);
471
- if (!user) {
472
- sessionCache.delete(sessionId);
473
- return c.json({ error: "Unauthorized" }, 401);
474
- }
516
+ const user = await resolveSession(sessionId);
517
+ if (!user) return c.json({ error: "Unauthorized" }, 401);
475
518
  if (user.role === "pending") return c.json({ error: "Account pending approval" }, 403);
476
- sessionCache.set(sessionId, { userId, user, expires: Date.now() + SESSION_CACHE_TTL });
477
519
  c.set("user", user);
478
520
  await next();
479
521
  });
522
+ var requireSelf = (paramName = "name") => createMiddleware(async (c, next) => {
523
+ const user = c.get("user");
524
+ if (user.role !== "admin") {
525
+ const target = c.req.param(paramName) ?? "";
526
+ const [baseName] = target.split("@", 2);
527
+ if (user.username !== baseName) {
528
+ return c.json({ error: "Forbidden" }, 403);
529
+ }
530
+ }
531
+ await next();
532
+ });
480
533
 
481
534
  // src/web/server.ts
482
535
  import { existsSync as existsSync13 } from "fs";
483
- import { readFile as readFile3, stat as stat3 } from "fs/promises";
536
+ import { readFile as readFile4, stat as stat4 } from "fs/promises";
484
537
  import { createServer as createHttpsServer } from "https";
485
- import { dirname, extname as extname4, resolve as resolve17 } from "path";
538
+ import { dirname, extname as extname5, resolve as resolve18 } from "path";
486
539
  import { serve } from "@hono/node-server";
487
540
 
488
541
  // src/web/app.ts
489
- import { Hono as Hono28 } from "hono";
542
+ import { Hono as Hono30 } from "hono";
490
543
  import { bodyLimit } from "hono/body-limit";
491
544
  import { csrf } from "hono/csrf";
492
545
  import { HTTPException } from "hono/http-exception";
@@ -538,7 +591,7 @@ var app = new Hono().get("/events", async (c) => {
538
591
  });
539
592
  cleanups.push(unsubActivity);
540
593
  for (const conv of conversations2) {
541
- const unsubConv = subscribe3(conv.id, (event) => {
594
+ const unsubConv = subscribe2(conv.id, (event) => {
542
595
  stream.writeSSE({
543
596
  data: JSON.stringify({ event: "conversation", conversationId: conv.id, ...event })
544
597
  }).catch((err) => {
@@ -553,8 +606,8 @@ var app = new Hono().get("/events", async (c) => {
553
606
  });
554
607
  }, 15e3);
555
608
  cleanups.push(() => clearInterval(keepAlive));
556
- await new Promise((resolve19) => {
557
- stream.onAbort(() => resolve19());
609
+ await new Promise((resolve20) => {
610
+ stream.onAbort(() => resolve20());
558
611
  });
559
612
  } finally {
560
613
  for (const cleanup of cleanups) {
@@ -575,6 +628,17 @@ import { zValidator } from "@hono/zod-validator";
575
628
  import { Hono as Hono2 } from "hono";
576
629
  import { deleteCookie, getCookie as getCookie2, setCookie } from "hono/cookie";
577
630
  import { z } from "zod";
631
+ function tryJoinSystem(userId) {
632
+ if (!process.env.VOLUTE_DAEMON_TOKEN) return;
633
+ joinSystemChannel(userId).catch(() => {
634
+ });
635
+ }
636
+ var SESSION_COOKIE_OPTIONS = {
637
+ path: "/",
638
+ httpOnly: true,
639
+ sameSite: "Lax",
640
+ maxAge: Math.floor(SESSION_MAX_AGE / 1e3)
641
+ };
578
642
  var credentialsSchema = z.object({
579
643
  username: z.string().min(1),
580
644
  password: z.string().min(1)
@@ -610,6 +674,11 @@ var authenticated = new Hono2().use(authMiddleware).post("/change-password", zVa
610
674
  await updateUserProfile(user.id, body);
611
675
  const sessionId = getCookie2(c, "volute_session");
612
676
  if (sessionId) invalidateSessionCache(sessionId);
677
+ broadcast({
678
+ type: "profile_updated",
679
+ mind: user.username,
680
+ summary: `${user.username} profile updated`
681
+ });
613
682
  return c.json({ ok: true });
614
683
  }).post("/avatar", async (c) => {
615
684
  const user = c.get("user");
@@ -637,6 +706,11 @@ var authenticated = new Hono2().use(authMiddleware).post("/change-password", zVa
637
706
  await updateUserProfile(user.id, { avatar: filename });
638
707
  const sessionId = getCookie2(c, "volute_session");
639
708
  if (sessionId) invalidateSessionCache(sessionId);
709
+ broadcast({
710
+ type: "profile_updated",
711
+ mind: user.username,
712
+ summary: `${user.username} avatar updated`
713
+ });
640
714
  return c.json({ ok: true, avatar: filename });
641
715
  }).delete("/avatar", async (c) => {
642
716
  const user = c.get("user");
@@ -647,6 +721,11 @@ var authenticated = new Hono2().use(authMiddleware).post("/change-password", zVa
647
721
  await updateUserProfile(user.id, { avatar: null });
648
722
  const sessionId = getCookie2(c, "volute_session");
649
723
  if (sessionId) invalidateSessionCache(sessionId);
724
+ broadcast({
725
+ type: "profile_updated",
726
+ mind: user.username,
727
+ summary: `${user.username} avatar removed`
728
+ });
650
729
  return c.json({ ok: true });
651
730
  });
652
731
  var admin = new Hono2().use(authMiddleware).get("/users", async (c) => {
@@ -700,6 +779,14 @@ var admin = new Hono2().use(authMiddleware).get("/users", async (c) => {
700
779
  if (Number.isNaN(id)) return c.json({ error: "Invalid user ID" }, 400);
701
780
  const body = c.req.valid("json");
702
781
  await updateUserProfile(id, body);
782
+ const updatedUser = await getUser(id);
783
+ if (updatedUser) {
784
+ broadcast({
785
+ type: "profile_updated",
786
+ mind: updatedUser.username,
787
+ summary: `${updatedUser.username} profile updated`
788
+ });
789
+ }
703
790
  return c.json({ ok: true });
704
791
  }).delete("/users/:id", async (c) => {
705
792
  const user = c.get("user");
@@ -728,8 +815,9 @@ var app2 = new Hono2().post("/register", zValidator("json", credentialsSchema),
728
815
  const user = await createUser(username, password);
729
816
  if (user.role === "admin") {
730
817
  const sessionId = await createSession(user.id);
731
- setCookie(c, "volute_session", sessionId, { path: "/", httpOnly: true, sameSite: "Lax" });
818
+ setCookie(c, "volute_session", sessionId, SESSION_COOKIE_OPTIONS);
732
819
  }
820
+ tryJoinSystem(user.id);
733
821
  return c.json({ id: user.id, username: user.username, role: user.role });
734
822
  }).post("/login", zValidator("json", credentialsSchema), async (c) => {
735
823
  const { username, password } = c.req.valid("json");
@@ -738,13 +826,22 @@ var app2 = new Hono2().post("/register", zValidator("json", credentialsSchema),
738
826
  return c.json({ error: "Invalid credentials" }, 401);
739
827
  }
740
828
  const sessionId = await createSession(user.id);
741
- setCookie(c, "volute_session", sessionId, { path: "/", httpOnly: true, sameSite: "Lax" });
742
- return c.json({ id: user.id, username: user.username, role: user.role });
829
+ setCookie(c, "volute_session", sessionId, SESSION_COOKIE_OPTIONS);
830
+ tryJoinSystem(user.id);
831
+ return c.json({ id: user.id, username: user.username, role: user.role, sessionId });
743
832
  }).post("/logout", async (c) => {
744
- const sessionId = getCookie2(c, "volute_session");
745
- if (sessionId) {
746
- await deleteSession(sessionId);
833
+ const cookieSession = getCookie2(c, "volute_session");
834
+ if (cookieSession) {
835
+ await deleteSession(cookieSession);
747
836
  deleteCookie(c, "volute_session", { path: "/" });
837
+ return c.json({ ok: true });
838
+ }
839
+ const authHeader = c.req.header("Authorization");
840
+ if (authHeader?.startsWith("Bearer ")) {
841
+ const token = authHeader.slice(7);
842
+ if (token) {
843
+ await deleteSession(token);
844
+ }
748
845
  }
749
846
  return c.json({ ok: true });
750
847
  }).get("/me", async (c) => {
@@ -1053,8 +1150,8 @@ async function listConversations2(env) {
1053
1150
  const userMap = /* @__PURE__ */ new Map();
1054
1151
  const imChannels = data.channels.filter((ch) => ch.is_im && ch.user);
1055
1152
  if (imChannels.length > 0) {
1056
- const users = await listUsers3(env);
1057
- for (const u of users) {
1153
+ const users2 = await listUsers3(env);
1154
+ for (const u of users2) {
1058
1155
  userMap.set(u.id, u.username);
1059
1156
  }
1060
1157
  }
@@ -1446,7 +1543,7 @@ function resolveChannelId2(env, slug) {
1446
1543
  function buildEnv(name) {
1447
1544
  return { ...loadMergedEnv(name), VOLUTE_MIND: name, VOLUTE_MIND_DIR: mindDir(name) };
1448
1545
  }
1449
- var app3 = new Hono3().post("/:name/channels/send", requireAdmin, async (c) => {
1546
+ var app3 = new Hono3().post("/:name/channels/send", requireSelf(), async (c) => {
1450
1547
  const name = c.req.param("name");
1451
1548
  if (!findMind(name)) return c.json({ error: "Mind not found" }, 404);
1452
1549
  const { platform, uri, message, images, sender } = await c.req.json();
@@ -1512,12 +1609,12 @@ var app3 = new Hono3().post("/:name/channels/send", requireAdmin, async (c) => {
1512
1609
  return c.json({ error: `Platform ${platform} does not support listing users` }, 400);
1513
1610
  const env = buildEnv(name);
1514
1611
  try {
1515
- const users = await driver.listUsers(env);
1516
- return c.json(users);
1612
+ const users2 = await driver.listUsers(env);
1613
+ return c.json(users2);
1517
1614
  } catch (err) {
1518
1615
  return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
1519
1616
  }
1520
- }).post("/:name/channels/create", requireAdmin, async (c) => {
1617
+ }).post("/:name/channels/create", requireSelf(), async (c) => {
1521
1618
  const name = c.req.param("name");
1522
1619
  if (!findMind(name)) return c.json({ error: "Mind not found" }, 404);
1523
1620
  const {
@@ -1872,7 +1969,7 @@ async function notifyMind(port, message, channel, sender) {
1872
1969
  console.warn(`[file-sharing] notify mind on port ${port} failed:`, err);
1873
1970
  }
1874
1971
  }
1875
- var app6 = new Hono6().post("/:name/files/send", async (c) => {
1972
+ var app6 = new Hono6().post("/:name/files/send", requireSelf(), async (c) => {
1876
1973
  const senderName = c.req.param("name");
1877
1974
  const senderEntry = findMind(senderName);
1878
1975
  if (!senderEntry) return c.json({ error: "Sender mind not found" }, 404);
@@ -1886,13 +1983,13 @@ var app6 = new Hono6().post("/:name/files/send", async (c) => {
1886
1983
  if (pathErr) return c.json({ error: pathErr }, 400);
1887
1984
  const senderDir = mindDir(senderName);
1888
1985
  const filePath = resolve6(senderDir, "home", body.filePath);
1889
- const MAX_FILE_SIZE = 50 * 1024 * 1024;
1890
- const stat4 = statSync(filePath, { throwIfNoEntry: false });
1891
- if (!stat4) return c.json({ error: `File not found: ${body.filePath}` }, 404);
1892
- if (stat4.size > MAX_FILE_SIZE) {
1986
+ const MAX_FILE_SIZE2 = 50 * 1024 * 1024;
1987
+ const stat5 = statSync(filePath, { throwIfNoEntry: false });
1988
+ if (!stat5) return c.json({ error: `File not found: ${body.filePath}` }, 404);
1989
+ if (stat5.size > MAX_FILE_SIZE2) {
1893
1990
  return c.json(
1894
1991
  {
1895
- error: `File too large (${formatFileSize(stat4.size)}, max ${formatFileSize(MAX_FILE_SIZE)})`
1992
+ error: `File too large (${formatFileSize(stat5.size)}, max ${formatFileSize(MAX_FILE_SIZE2)})`
1896
1993
  },
1897
1994
  413
1898
1995
  );
@@ -2020,12 +2117,12 @@ var app7 = new Hono7().get("/:name/avatar", async (c) => {
2020
2117
  if (!entry) return c.json({ error: "Mind not found" }, 404);
2021
2118
  const dir = mindDir(name);
2022
2119
  const config = readVoluteConfig(dir);
2023
- if (!config?.avatar) return c.json({ error: "No avatar configured" }, 404);
2024
- const ext = extname2(config.avatar).toLowerCase();
2120
+ if (!config?.profile?.avatar) return c.json({ error: "No avatar configured" }, 404);
2121
+ const ext = extname2(config.profile.avatar).toLowerCase();
2025
2122
  const mime = AVATAR_MIME2[ext];
2026
2123
  if (!mime) return c.json({ error: "Invalid avatar extension" }, 400);
2027
2124
  const homeDir = resolve7(dir, "home");
2028
- const avatarPath = resolve7(homeDir, config.avatar);
2125
+ const avatarPath = resolve7(homeDir, config.profile.avatar);
2029
2126
  if (!avatarPath.startsWith(`${homeDir}/`)) return c.json({ error: "Invalid avatar path" }, 400);
2030
2127
  let realAvatarPath;
2031
2128
  try {
@@ -2197,9 +2294,9 @@ var app9 = new Hono9().get("/:name/logs", async (c) => {
2197
2294
  stream.onAbort(() => {
2198
2295
  tail.kill();
2199
2296
  });
2200
- await new Promise((resolve19) => {
2201
- tail.on("exit", resolve19);
2202
- stream.onAbort(resolve19);
2297
+ await new Promise((resolve20) => {
2298
+ tail.on("exit", resolve20);
2299
+ stream.onAbort(resolve20);
2203
2300
  });
2204
2301
  });
2205
2302
  }).get("/:name/logs/tail", async (c) => {
@@ -2217,8 +2314,8 @@ var app9 = new Hono9().get("/:name/logs", async (c) => {
2217
2314
  tail.stdout.on("data", (data) => {
2218
2315
  output += data.toString();
2219
2316
  });
2220
- await new Promise((resolve19) => {
2221
- tail.on("exit", resolve19);
2317
+ await new Promise((resolve20) => {
2318
+ tail.on("exit", resolve20);
2222
2319
  });
2223
2320
  return c.text(output);
2224
2321
  });
@@ -2246,12 +2343,12 @@ var app10 = new Hono10().get("/:name/skills", async (c) => {
2246
2343
  const { skillId } = c.req.valid("json");
2247
2344
  const dir = mindDir(name);
2248
2345
  try {
2249
- await installSkill(name, dir, skillId);
2346
+ const result = await installSkill(name, dir, skillId);
2347
+ return c.json({ ok: true, ...result });
2250
2348
  } catch (e) {
2251
2349
  const msg = e instanceof Error ? e.message : String(e);
2252
2350
  return c.json({ error: msg }, 400);
2253
2351
  }
2254
- return c.json({ ok: true });
2255
2352
  }
2256
2353
  ).post(
2257
2354
  "/:name/skills/update",
@@ -2606,7 +2703,7 @@ async function getMindStatus(name, port) {
2606
2703
  const manager = getMindManager();
2607
2704
  let status = "stopped";
2608
2705
  try {
2609
- const { getSleepManagerIfReady } = await import("./sleep-manager-XXSWQQLE.js");
2706
+ const { getSleepManagerIfReady } = await import("./sleep-manager-EE4NRN2Q.js");
2610
2707
  if (getSleepManagerIfReady()?.isSleeping(name)) {
2611
2708
  status = "sleeping";
2612
2709
  }
@@ -2641,9 +2738,9 @@ async function getMindStatus(name, port) {
2641
2738
  return {
2642
2739
  status,
2643
2740
  channels,
2644
- displayName: config?.displayName,
2645
- description: config?.description,
2646
- avatar: config?.avatar
2741
+ displayName: config?.profile?.displayName,
2742
+ description: config?.profile?.description,
2743
+ avatar: config?.profile?.avatar
2647
2744
  };
2648
2745
  }
2649
2746
  var TEMPLATE_BRANCH = "volute/template";
@@ -3004,11 +3101,30 @@ var app11 = new Hono11().post("/", requireAdmin, zValidator3("json", createMindS
3004
3101
  copyTemplateToDir(composedDir, dest, name, manifest);
3005
3102
  applyInitFiles(dest);
3006
3103
  const { publicKeyPem } = generateIdentity(dest);
3007
- if (body.description) {
3008
- const seedConfig = readVoluteConfig(dest);
3009
- if (!seedConfig) throw new Error("Failed to read volute.json after identity generation");
3010
- seedConfig.description = body.description;
3011
- writeVoluteConfig(dest, seedConfig);
3104
+ {
3105
+ const config = readVoluteConfig(dest);
3106
+ if (!config) throw new Error("Failed to read volute.json after identity generation");
3107
+ if (body.description) {
3108
+ config.profile = { ...config.profile, description: body.description };
3109
+ }
3110
+ if (!config.sleep) {
3111
+ config.sleep = {
3112
+ enabled: true,
3113
+ schedule: { sleep: "0 0 * * *", wake: "0 8 * * *" }
3114
+ };
3115
+ }
3116
+ if (!config.schedules || config.schedules.length === 0) {
3117
+ config.schedules = [
3118
+ {
3119
+ id: "heartbeat",
3120
+ cron: "0 12,16,20 * * *",
3121
+ message: "A quiet moment. You might write something \u2014 a note, a journal entry, a page. You could explore a topic that interests you, check in on #system, or just think. No obligations, just time.",
3122
+ enabled: true,
3123
+ skipWhenSleeping: true
3124
+ }
3125
+ ];
3126
+ }
3127
+ writeVoluteConfig(dest, config);
3012
3128
  }
3013
3129
  if (body.model) {
3014
3130
  const configPath2 = resolve12(dest, "home/.config/config.json");
@@ -3094,6 +3210,8 @@ The human who planted you described you as: "${body.description}"
3094
3210
  description: body.description
3095
3211
  }
3096
3212
  });
3213
+ announceToSystem(`${name} has joined`).catch(() => {
3214
+ });
3097
3215
  return c.json({
3098
3216
  ok: true,
3099
3217
  name,
@@ -3247,7 +3365,7 @@ ${user.trimEnd()}
3247
3365
  const minds = await Promise.all(
3248
3366
  entries.map(async (entry) => {
3249
3367
  const mindStatus = await getMindStatus(entry.name, entry.port);
3250
- const hasPages = existsSync10(resolve12(mindDir(entry.name), "home", "pages"));
3368
+ const hasPages = existsSync10(resolve12(mindDir(entry.name), "home", "public", "pages"));
3251
3369
  return {
3252
3370
  ...entry,
3253
3371
  ...mindStatus,
@@ -3280,7 +3398,7 @@ ${user.trimEnd()}
3280
3398
  return { name: v.name, port: v.port, status: variantStatus };
3281
3399
  })
3282
3400
  );
3283
- const hasPages = existsSync10(resolve12(mindDir(name), "home", "pages"));
3401
+ const hasPages = existsSync10(resolve12(mindDir(name), "home", "public", "pages"));
3284
3402
  return c.json({ ...entry, ...mindStatus, variants: variantStatuses, hasPages });
3285
3403
  }).post("/:name/start", requireAdmin, async (c) => {
3286
3404
  const name = c.req.param("name");
@@ -3305,7 +3423,7 @@ ${user.trimEnd()}
3305
3423
  } catch (err) {
3306
3424
  return c.json({ error: err instanceof Error ? err.message : "Failed to start mind" }, 500);
3307
3425
  }
3308
- }).post("/:name/restart", requireAdmin, async (c) => {
3426
+ }).post("/:name/restart", requireSelf(), async (c) => {
3309
3427
  const name = c.req.param("name");
3310
3428
  const [baseName, variantName] = name.split("@", 2);
3311
3429
  const entry = findMind(baseName);
@@ -3331,6 +3449,14 @@ ${user.trimEnd()}
3331
3449
  }
3332
3450
  const manager = getMindManager();
3333
3451
  try {
3452
+ if (context?.type === "reload") {
3453
+ const { getSleepManagerIfReady } = await import("./sleep-manager-EE4NRN2Q.js");
3454
+ const sleepState = getSleepManagerIfReady()?.getState(name);
3455
+ if (sleepState?.sleeping) {
3456
+ logger_default.info(`skipping reload for ${name} during sleep \u2014 will apply on next wake`);
3457
+ return c.json({ ok: true, deferred: true, port: targetPort });
3458
+ }
3459
+ }
3334
3460
  if (manager.isRunning(name)) {
3335
3461
  await stopMindFull(name);
3336
3462
  }
@@ -3380,7 +3506,7 @@ ${user.trimEnd()}
3380
3506
  }
3381
3507
  }
3382
3508
  }
3383
- if (context) {
3509
+ if (context && context.type !== "reload") {
3384
3510
  manager.setPendingContext(name, context);
3385
3511
  }
3386
3512
  if (context?.type === "sprouted" && !variantName) {
@@ -3424,7 +3550,7 @@ ${user.trimEnd()}
3424
3550
  const name = c.req.param("name");
3425
3551
  const entry = findMind(name);
3426
3552
  if (!entry) return c.json({ error: "Mind not found" }, 404);
3427
- const { getSleepManagerIfReady } = await import("./sleep-manager-XXSWQQLE.js");
3553
+ const { getSleepManagerIfReady } = await import("./sleep-manager-EE4NRN2Q.js");
3428
3554
  const sm = getSleepManagerIfReady();
3429
3555
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
3430
3556
  return c.json(sm.getState(name));
@@ -3432,7 +3558,7 @@ ${user.trimEnd()}
3432
3558
  const name = c.req.param("name");
3433
3559
  const entry = findMind(name);
3434
3560
  if (!entry) return c.json({ error: "Mind not found" }, 404);
3435
- const { getSleepManagerIfReady } = await import("./sleep-manager-XXSWQQLE.js");
3561
+ const { getSleepManagerIfReady } = await import("./sleep-manager-EE4NRN2Q.js");
3436
3562
  const sm = getSleepManagerIfReady();
3437
3563
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
3438
3564
  if (sm.isSleeping(name)) return c.json({ error: "Mind is already sleeping" }, 409);
@@ -3452,17 +3578,22 @@ ${user.trimEnd()}
3452
3578
  const name = c.req.param("name");
3453
3579
  const entry = findMind(name);
3454
3580
  if (!entry) return c.json({ error: "Mind not found" }, 404);
3455
- const { getSleepManagerIfReady } = await import("./sleep-manager-XXSWQQLE.js");
3581
+ const { getSleepManagerIfReady } = await import("./sleep-manager-EE4NRN2Q.js");
3456
3582
  const sm = getSleepManagerIfReady();
3457
3583
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
3458
- if (!sm.isSleeping(name)) return c.json({ error: "Mind is not sleeping" }, 409);
3459
- sm.initiateWake(name).catch((err) => logger_default.error(`failed to wake ${name}`, logger_default.errorData(err)));
3584
+ const sleepState = sm.getState(name);
3585
+ if (!sleepState.sleeping) return c.json({ error: "Mind is not sleeping" }, 409);
3586
+ if (sleepState.wokenByTrigger) {
3587
+ sm.convertTriggerToFullWake(name);
3588
+ } else {
3589
+ sm.initiateWake(name).catch((err) => logger_default.error(`failed to wake ${name}`, logger_default.errorData(err)));
3590
+ }
3460
3591
  return c.json({ ok: true });
3461
3592
  }).post("/:name/sleep/messages", requireAdmin, async (c) => {
3462
3593
  const name = c.req.param("name");
3463
3594
  const entry = findMind(name);
3464
3595
  if (!entry) return c.json({ error: "Mind not found" }, 404);
3465
- const { getSleepManagerIfReady } = await import("./sleep-manager-XXSWQQLE.js");
3596
+ const { getSleepManagerIfReady } = await import("./sleep-manager-EE4NRN2Q.js");
3466
3597
  const sm = getSleepManagerIfReady();
3467
3598
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
3468
3599
  const flushed = await sm.flushQueuedMessages(name);
@@ -3670,7 +3801,7 @@ ${user.trimEnd()}
3670
3801
  500
3671
3802
  );
3672
3803
  }
3673
- }).post("/:name/message", async (c) => {
3804
+ }).post("/:name/message", requireSelf(), async (c) => {
3674
3805
  const name = c.req.param("name");
3675
3806
  const [baseName, variantName] = name.split("@", 2);
3676
3807
  const entry = findMind(baseName);
@@ -3680,7 +3811,7 @@ ${user.trimEnd()}
3680
3811
  if (!variant) return c.json({ error: `Unknown variant: ${variantName}` }, 404);
3681
3812
  }
3682
3813
  try {
3683
- const { getSleepManagerIfReady } = await import("./sleep-manager-XXSWQQLE.js");
3814
+ const { getSleepManagerIfReady } = await import("./sleep-manager-EE4NRN2Q.js");
3684
3815
  const sm = getSleepManagerIfReady();
3685
3816
  if (sm?.isSleeping(baseName)) {
3686
3817
  const body2 = await c.req.text();
@@ -3903,7 +4034,7 @@ ${user.trimEnd()}
3903
4034
  logger_default.error(`failed to get pending deliveries for ${baseName}`, logger_default.errorData(err));
3904
4035
  return c.json({ error: "Failed to retrieve pending messages" }, 500);
3905
4036
  }
3906
- }).post("/:name/events", async (c) => {
4037
+ }).post("/:name/events", requireSelf(), async (c) => {
3907
4038
  const name = c.req.param("name");
3908
4039
  const [baseName] = name.split("@", 2);
3909
4040
  let body;
@@ -3929,7 +4060,7 @@ ${user.trimEnd()}
3929
4060
  } catch (err) {
3930
4061
  logger_default.error(`failed to persist event for ${baseName}`, logger_default.errorData(err));
3931
4062
  }
3932
- publish(baseName, {
4063
+ publish2(baseName, {
3933
4064
  mind: baseName,
3934
4065
  type: body.type,
3935
4066
  session: body.session,
@@ -3988,7 +4119,7 @@ ${user.trimEnd()}
3988
4119
  unsubscribe?.();
3989
4120
  }
3990
4121
  }, 15e3);
3991
- unsubscribe = subscribe2(baseName, (event) => {
4122
+ unsubscribe = subscribe3(baseName, (event) => {
3992
4123
  if (typeFilter && !typeFilter.includes(event.type)) return;
3993
4124
  if (sessionFilter && event.session !== sessionFilter) return;
3994
4125
  if (channelFilter && event.channel !== channelFilter) return;
@@ -4016,7 +4147,7 @@ ${user.trimEnd()}
4016
4147
  Connection: "keep-alive"
4017
4148
  }
4018
4149
  });
4019
- }).post("/:name/history", async (c) => {
4150
+ }).post("/:name/history", requireSelf(), async (c) => {
4020
4151
  const name = c.req.param("name");
4021
4152
  const [baseName] = name.split("@", 2);
4022
4153
  let body;
@@ -4087,10 +4218,330 @@ ${user.trimEnd()}
4087
4218
  });
4088
4219
  var minds_default = app11;
4089
4220
 
4221
+ // src/web/api/notes.ts
4222
+ import { zValidator as zValidator4 } from "@hono/zod-validator";
4223
+ import { Hono as Hono12 } from "hono";
4224
+ import { z as z4 } from "zod";
4225
+
4226
+ // src/lib/notes.ts
4227
+ import { and as and2, count, desc as desc3, eq as eq3, inArray, sql as sql2 } from "drizzle-orm";
4228
+ async function createNote(authorId, title, content, replyToId) {
4229
+ const db = await getDb();
4230
+ let slug = slugify(title) || "untitled";
4231
+ const existing = await db.select({ slug: notes.slug }).from(notes).where(eq3(notes.author_id, authorId)).all();
4232
+ const existingSlugs = new Set(existing.map((r) => r.slug));
4233
+ if (existingSlugs.has(slug)) {
4234
+ let i = 2;
4235
+ while (existingSlugs.has(`${slug}-${i}`)) i++;
4236
+ slug = `${slug}-${i}`;
4237
+ }
4238
+ const [row] = await db.insert(notes).values({ author_id: authorId, title, slug, content, reply_to_id: replyToId ?? null }).returning();
4239
+ const author = await db.select().from(users).where(eq3(users.id, authorId)).get();
4240
+ return {
4241
+ ...row,
4242
+ author_username: author?.username ?? "unknown",
4243
+ author_display_name: author?.display_name ?? null,
4244
+ comment_count: 0
4245
+ };
4246
+ }
4247
+ async function getNote(authorUsername, slug) {
4248
+ const db = await getDb();
4249
+ const row = await db.select({
4250
+ id: notes.id,
4251
+ author_id: notes.author_id,
4252
+ title: notes.title,
4253
+ slug: notes.slug,
4254
+ content: notes.content,
4255
+ reply_to_id: notes.reply_to_id,
4256
+ created_at: notes.created_at,
4257
+ updated_at: notes.updated_at,
4258
+ author_username: users.username,
4259
+ author_display_name: users.display_name
4260
+ }).from(notes).innerJoin(users, eq3(notes.author_id, users.id)).where(and2(eq3(users.username, authorUsername), eq3(notes.slug, slug))).get();
4261
+ if (!row) return null;
4262
+ const comments = await getComments(row.id);
4263
+ const reactions = await getReactions(row.id);
4264
+ let reply_to = null;
4265
+ if (row.reply_to_id) {
4266
+ const parent = await db.select({
4267
+ title: notes.title,
4268
+ slug: notes.slug,
4269
+ author_username: users.username
4270
+ }).from(notes).innerJoin(users, eq3(notes.author_id, users.id)).where(eq3(notes.id, row.reply_to_id)).get();
4271
+ if (parent) {
4272
+ reply_to = parent;
4273
+ }
4274
+ }
4275
+ const replies = await db.select({
4276
+ author_username: users.username,
4277
+ slug: notes.slug,
4278
+ title: notes.title,
4279
+ created_at: notes.created_at
4280
+ }).from(notes).innerJoin(users, eq3(notes.author_id, users.id)).where(eq3(notes.reply_to_id, row.id)).orderBy(notes.created_at).all();
4281
+ return { ...row, comment_count: comments.length, comments, reactions, reply_to, replies };
4282
+ }
4283
+ async function listNotes(opts) {
4284
+ const db = await getDb();
4285
+ const limit = opts?.limit ?? 50;
4286
+ const offset = opts?.offset ?? 0;
4287
+ const conditions = [];
4288
+ if (opts?.authorUsername) {
4289
+ conditions.push(eq3(users.username, opts.authorUsername));
4290
+ }
4291
+ const rows = await db.select({
4292
+ id: notes.id,
4293
+ author_id: notes.author_id,
4294
+ title: notes.title,
4295
+ slug: notes.slug,
4296
+ content: notes.content,
4297
+ reply_to_id: notes.reply_to_id,
4298
+ created_at: notes.created_at,
4299
+ updated_at: notes.updated_at,
4300
+ author_username: users.username,
4301
+ author_display_name: users.display_name
4302
+ }).from(notes).innerJoin(users, eq3(notes.author_id, users.id)).where(conditions.length > 0 ? and2(...conditions) : void 0).orderBy(desc3(notes.created_at)).limit(limit).offset(offset).all();
4303
+ const noteIds = rows.map((r) => r.id);
4304
+ if (noteIds.length === 0) return [];
4305
+ const commentCounts = await db.select({
4306
+ note_id: noteComments.note_id,
4307
+ count: count()
4308
+ }).from(noteComments).where(inArray(noteComments.note_id, noteIds)).groupBy(noteComments.note_id).all();
4309
+ const countMap = new Map(commentCounts.map((r) => [r.note_id, r.count]));
4310
+ const allReactions = await db.select({
4311
+ note_id: noteReactions.note_id,
4312
+ emoji: noteReactions.emoji,
4313
+ count: count()
4314
+ }).from(noteReactions).where(inArray(noteReactions.note_id, noteIds)).groupBy(noteReactions.note_id, noteReactions.emoji).all();
4315
+ const reactionMap = /* @__PURE__ */ new Map();
4316
+ for (const r of allReactions) {
4317
+ if (!reactionMap.has(r.note_id)) reactionMap.set(r.note_id, []);
4318
+ reactionMap.get(r.note_id).push({ emoji: r.emoji, count: r.count });
4319
+ }
4320
+ const replyToIds = [...new Set(rows.filter((r) => r.reply_to_id).map((r) => r.reply_to_id))];
4321
+ const replyToMap = /* @__PURE__ */ new Map();
4322
+ if (replyToIds.length > 0) {
4323
+ const parents = await db.select({
4324
+ id: notes.id,
4325
+ title: notes.title,
4326
+ slug: notes.slug,
4327
+ author_username: users.username
4328
+ }).from(notes).innerJoin(users, eq3(notes.author_id, users.id)).where(inArray(notes.id, replyToIds)).all();
4329
+ for (const parent of parents) {
4330
+ replyToMap.set(parent.id, {
4331
+ author_username: parent.author_username,
4332
+ slug: parent.slug,
4333
+ title: parent.title
4334
+ });
4335
+ }
4336
+ }
4337
+ return rows.map((r) => {
4338
+ const reactions = reactionMap.get(r.id);
4339
+ const topReactions = reactions ? reactions.sort((a, b) => b.count - a.count).slice(0, 3).map((rx) => ({ ...rx, usernames: [] })) : void 0;
4340
+ return {
4341
+ ...r,
4342
+ comment_count: countMap.get(r.id) ?? 0,
4343
+ reactions: topReactions,
4344
+ reply_to: r.reply_to_id ? replyToMap.get(r.reply_to_id) ?? null : null
4345
+ };
4346
+ });
4347
+ }
4348
+ async function updateNote(authorUsername, slug, updates) {
4349
+ const db = await getDb();
4350
+ const existing = await db.select({ id: notes.id, author_id: notes.author_id }).from(notes).innerJoin(users, eq3(notes.author_id, users.id)).where(and2(eq3(users.username, authorUsername), eq3(notes.slug, slug))).get();
4351
+ if (!existing) return null;
4352
+ const set = { updated_at: sql2`(datetime('now'))` };
4353
+ if (updates.title !== void 0) set.title = updates.title;
4354
+ if (updates.content !== void 0) set.content = updates.content;
4355
+ await db.update(notes).set(set).where(eq3(notes.id, existing.id));
4356
+ return getNote(authorUsername, slug).then(
4357
+ (n) => n ? { ...n, comments: void 0 } : null
4358
+ );
4359
+ }
4360
+ async function deleteNote(authorUsername, slug, authorId) {
4361
+ const db = await getDb();
4362
+ const existing = await db.select({ id: notes.id, author_id: notes.author_id }).from(notes).innerJoin(users, eq3(notes.author_id, users.id)).where(and2(eq3(users.username, authorUsername), eq3(notes.slug, slug))).get();
4363
+ if (!existing || existing.author_id !== authorId) return false;
4364
+ await db.delete(notes).where(eq3(notes.id, existing.id));
4365
+ return true;
4366
+ }
4367
+ async function addComment(noteId, authorId, content) {
4368
+ const db = await getDb();
4369
+ const [row] = await db.insert(noteComments).values({ note_id: noteId, author_id: authorId, content }).returning();
4370
+ const author = await db.select().from(users).where(eq3(users.id, authorId)).get();
4371
+ return {
4372
+ ...row,
4373
+ author_username: author?.username ?? "unknown",
4374
+ author_display_name: author?.display_name ?? null
4375
+ };
4376
+ }
4377
+ async function getComments(noteId) {
4378
+ const db = await getDb();
4379
+ return db.select({
4380
+ id: noteComments.id,
4381
+ note_id: noteComments.note_id,
4382
+ author_id: noteComments.author_id,
4383
+ content: noteComments.content,
4384
+ created_at: noteComments.created_at,
4385
+ author_username: users.username,
4386
+ author_display_name: users.display_name
4387
+ }).from(noteComments).innerJoin(users, eq3(noteComments.author_id, users.id)).where(eq3(noteComments.note_id, noteId)).orderBy(noteComments.created_at).all();
4388
+ }
4389
+ async function deleteComment(commentId, authorId) {
4390
+ const db = await getDb();
4391
+ const existing = await db.select({ id: noteComments.id, author_id: noteComments.author_id }).from(noteComments).where(eq3(noteComments.id, commentId)).get();
4392
+ if (!existing || existing.author_id !== authorId) return false;
4393
+ await db.delete(noteComments).where(eq3(noteComments.id, commentId));
4394
+ return true;
4395
+ }
4396
+ async function toggleReaction(noteId, userId, emoji) {
4397
+ const db = await getDb();
4398
+ const existing = await db.select({ id: noteReactions.id }).from(noteReactions).where(
4399
+ and2(
4400
+ eq3(noteReactions.note_id, noteId),
4401
+ eq3(noteReactions.user_id, userId),
4402
+ eq3(noteReactions.emoji, emoji)
4403
+ )
4404
+ ).get();
4405
+ if (existing) {
4406
+ await db.delete(noteReactions).where(eq3(noteReactions.id, existing.id));
4407
+ return { added: false };
4408
+ }
4409
+ await db.insert(noteReactions).values({ note_id: noteId, user_id: userId, emoji });
4410
+ return { added: true };
4411
+ }
4412
+ async function getReactions(noteId) {
4413
+ const db = await getDb();
4414
+ const rows = await db.select({
4415
+ emoji: noteReactions.emoji,
4416
+ username: users.username
4417
+ }).from(noteReactions).innerJoin(users, eq3(noteReactions.user_id, users.id)).where(eq3(noteReactions.note_id, noteId)).orderBy(noteReactions.emoji).all();
4418
+ const grouped = /* @__PURE__ */ new Map();
4419
+ for (const r of rows) {
4420
+ if (!grouped.has(r.emoji)) grouped.set(r.emoji, []);
4421
+ grouped.get(r.emoji).push(r.username);
4422
+ }
4423
+ return [...grouped.entries()].map(([emoji, usernames]) => ({
4424
+ emoji,
4425
+ count: usernames.length,
4426
+ usernames
4427
+ }));
4428
+ }
4429
+ async function resolveNoteId(authorSlug) {
4430
+ const [author, slug] = authorSlug.split("/", 2);
4431
+ if (!author || !slug) return null;
4432
+ const db = await getDb();
4433
+ const row = await db.select({ id: notes.id }).from(notes).innerJoin(users, eq3(notes.author_id, users.id)).where(and2(eq3(users.username, author), eq3(notes.slug, slug))).get();
4434
+ return row?.id ?? null;
4435
+ }
4436
+
4437
+ // src/web/api/notes.ts
4438
+ var createSchema = z4.object({
4439
+ title: z4.string().min(1).max(200),
4440
+ content: z4.string().min(1),
4441
+ reply_to: z4.string().optional()
4442
+ });
4443
+ var updateSchema = z4.object({
4444
+ title: z4.string().min(1).max(200).optional(),
4445
+ content: z4.string().min(1).optional()
4446
+ });
4447
+ var commentSchema = z4.object({
4448
+ content: z4.string().min(1)
4449
+ });
4450
+ var reactionSchema = z4.object({
4451
+ emoji: z4.string().min(1).max(32)
4452
+ });
4453
+ async function resolveUserId(c) {
4454
+ const user = c.get("user");
4455
+ if (user.id === 0) {
4456
+ const asUser = c.req.query("as");
4457
+ if (!asUser) return null;
4458
+ try {
4459
+ const mindUser = await getOrCreateMindUser(asUser);
4460
+ return { id: mindUser.id, username: mindUser.username };
4461
+ } catch {
4462
+ const brainUser = await getUserByUsername(asUser);
4463
+ if (brainUser) return { id: brainUser.id, username: brainUser.username };
4464
+ return null;
4465
+ }
4466
+ }
4467
+ return { id: user.id, username: user.username };
4468
+ }
4469
+ var app12 = new Hono12().get("/", async (c) => {
4470
+ const author = c.req.query("author");
4471
+ const limit = c.req.query("limit") ? parseInt(c.req.query("limit"), 10) : void 0;
4472
+ const offset = c.req.query("offset") ? parseInt(c.req.query("offset"), 10) : void 0;
4473
+ const result = await listNotes({ authorUsername: author, limit, offset });
4474
+ return c.json(result);
4475
+ }).post("/", zValidator4("json", createSchema), async (c) => {
4476
+ const actor = await resolveUserId(c);
4477
+ if (!actor) return c.json({ error: "Missing ?as=<username> for CLI requests" }, 400);
4478
+ const { title, content, reply_to } = c.req.valid("json");
4479
+ let replyToId;
4480
+ if (reply_to) {
4481
+ const id = await resolveNoteId(reply_to);
4482
+ if (id === null) return c.json({ error: `Reply target not found: ${reply_to}` }, 404);
4483
+ replyToId = id;
4484
+ }
4485
+ const note = await createNote(actor.id, title, content, replyToId);
4486
+ const replyInfo = reply_to ? ` (in reply to ${reply_to})` : "";
4487
+ announceToSystem(`${actor.username} published a note: ${title}${replyInfo}`).catch(() => {
4488
+ });
4489
+ return c.json(note, 201);
4490
+ }).get("/:author/:slug", async (c) => {
4491
+ const { author, slug } = c.req.param();
4492
+ const note = await getNote(author, slug);
4493
+ if (!note) return c.json({ error: "Note not found" }, 404);
4494
+ return c.json(note);
4495
+ }).put("/:author/:slug", zValidator4("json", updateSchema), async (c) => {
4496
+ const actor = await resolveUserId(c);
4497
+ if (!actor) return c.json({ error: "Missing ?as=<username> for CLI requests" }, 400);
4498
+ const { author, slug } = c.req.param();
4499
+ if (actor.username !== author) return c.json({ error: "Forbidden" }, 403);
4500
+ const updates = c.req.valid("json");
4501
+ const note = await updateNote(author, slug, updates);
4502
+ if (!note) return c.json({ error: "Note not found" }, 404);
4503
+ return c.json(note);
4504
+ }).delete("/:author/:slug", async (c) => {
4505
+ const actor = await resolveUserId(c);
4506
+ if (!actor) return c.json({ error: "Missing ?as=<username> for CLI requests" }, 400);
4507
+ const { author, slug } = c.req.param();
4508
+ const deleted = await deleteNote(author, slug, actor.id);
4509
+ if (!deleted) return c.json({ error: "Note not found or not authorized" }, 404);
4510
+ return c.json({ ok: true });
4511
+ }).post("/:author/:slug/reactions", zValidator4("json", reactionSchema), async (c) => {
4512
+ const actor = await resolveUserId(c);
4513
+ if (!actor) return c.json({ error: "Missing ?as=<username> for CLI requests" }, 400);
4514
+ const { author, slug } = c.req.param();
4515
+ const note = await getNote(author, slug);
4516
+ if (!note) return c.json({ error: "Note not found" }, 404);
4517
+ const { emoji } = c.req.valid("json");
4518
+ const result = await toggleReaction(note.id, actor.id, emoji);
4519
+ const reactions = await getReactions(note.id);
4520
+ return c.json({ ...result, reactions });
4521
+ }).post("/:author/:slug/comments", zValidator4("json", commentSchema), async (c) => {
4522
+ const actor = await resolveUserId(c);
4523
+ if (!actor) return c.json({ error: "Missing ?as=<username> for CLI requests" }, 400);
4524
+ const { author, slug } = c.req.param();
4525
+ const note = await getNote(author, slug);
4526
+ if (!note) return c.json({ error: "Note not found" }, 404);
4527
+ const { content } = c.req.valid("json");
4528
+ const comment = await addComment(note.id, actor.id, content);
4529
+ return c.json(comment, 201);
4530
+ }).delete("/:author/:slug/comments/:id", async (c) => {
4531
+ const actor = await resolveUserId(c);
4532
+ if (!actor) return c.json({ error: "Missing ?as=<username> for CLI requests" }, 400);
4533
+ const commentId = parseInt(c.req.param("id"), 10);
4534
+ if (Number.isNaN(commentId)) return c.json({ error: "Invalid comment ID" }, 400);
4535
+ const deleted = await deleteComment(commentId, actor.id);
4536
+ if (!deleted) return c.json({ error: "Comment not found or not authorized" }, 404);
4537
+ return c.json({ ok: true });
4538
+ });
4539
+ var notes_default = app12;
4540
+
4090
4541
  // src/web/api/pages.ts
4091
4542
  import { readFile as readFile2, stat as stat2 } from "fs/promises";
4092
4543
  import { extname as extname3, resolve as resolve13 } from "path";
4093
- import { Hono as Hono12 } from "hono";
4544
+ import { Hono as Hono13 } from "hono";
4094
4545
  var MIME_TYPES = {
4095
4546
  ".html": "text/html",
4096
4547
  ".js": "application/javascript",
@@ -4107,14 +4558,14 @@ var MIME_TYPES = {
4107
4558
  ".txt": "text/plain",
4108
4559
  ".xml": "application/xml"
4109
4560
  };
4110
- var app12 = new Hono12().get("/:name/*", async (c) => {
4561
+ var app13 = new Hono13().get("/:name/*", async (c) => {
4111
4562
  const name = c.req.param("name");
4112
4563
  let pagesRoot;
4113
4564
  if (name === "_system") {
4114
4565
  pagesRoot = resolve13(voluteHome(), "shared", "pages");
4115
4566
  } else {
4116
4567
  if (!findMind(name)) return c.text("Not found", 404);
4117
- pagesRoot = resolve13(mindDir(name), "home", "pages");
4568
+ pagesRoot = resolve13(mindDir(name), "home", "public", "pages");
4118
4569
  }
4119
4570
  const wildcard = c.req.path.replace(`/pages/${name}`, "") || "/";
4120
4571
  const requestedPath = resolve13(pagesRoot, wildcard.slice(1));
@@ -4137,14 +4588,14 @@ var app12 = new Hono12().get("/:name/*", async (c) => {
4137
4588
  }
4138
4589
  return c.text("Not found", 404);
4139
4590
  });
4140
- var pages_default = app12;
4591
+ var pages_default = app13;
4141
4592
 
4142
4593
  // src/web/api/prompts.ts
4143
- import { zValidator as zValidator4 } from "@hono/zod-validator";
4144
- import { eq as eq3, sql as sql2 } from "drizzle-orm";
4145
- import { Hono as Hono13 } from "hono";
4146
- import { z as z4 } from "zod";
4147
- var app13 = new Hono13().get("/", async (c) => {
4594
+ import { zValidator as zValidator5 } from "@hono/zod-validator";
4595
+ import { eq as eq4, sql as sql3 } from "drizzle-orm";
4596
+ import { Hono as Hono14 } from "hono";
4597
+ import { z as z5 } from "zod";
4598
+ var app14 = new Hono14().get("/", async (c) => {
4148
4599
  let rows;
4149
4600
  try {
4150
4601
  const db = await getDb();
@@ -4167,16 +4618,16 @@ var app13 = new Hono13().get("/", async (c) => {
4167
4618
  };
4168
4619
  });
4169
4620
  return c.json(prompts);
4170
- }).put("/:key", requireAdmin, zValidator4("json", z4.object({ content: z4.string() })), async (c) => {
4621
+ }).put("/:key", requireAdmin, zValidator5("json", z5.object({ content: z5.string() })), async (c) => {
4171
4622
  const key = c.req.param("key");
4172
4623
  if (!PROMPT_KEYS.includes(key)) {
4173
4624
  return c.json({ error: "Unknown prompt key" }, 404);
4174
4625
  }
4175
4626
  const { content } = c.req.valid("json");
4176
4627
  const db = await getDb();
4177
- await db.insert(systemPrompts).values({ key, content, updated_at: sql2`(datetime('now'))` }).onConflictDoUpdate({
4628
+ await db.insert(systemPrompts).values({ key, content, updated_at: sql3`(datetime('now'))` }).onConflictDoUpdate({
4178
4629
  target: systemPrompts.key,
4179
- set: { content, updated_at: sql2`(datetime('now'))` }
4630
+ set: { content, updated_at: sql3`(datetime('now'))` }
4180
4631
  });
4181
4632
  return c.json({ ok: true });
4182
4633
  }).delete("/:key", requireAdmin, async (c) => {
@@ -4185,14 +4636,103 @@ var app13 = new Hono13().get("/", async (c) => {
4185
4636
  return c.json({ error: "Unknown prompt key" }, 404);
4186
4637
  }
4187
4638
  const db = await getDb();
4188
- await db.delete(systemPrompts).where(eq3(systemPrompts.key, key));
4639
+ await db.delete(systemPrompts).where(eq4(systemPrompts.key, key));
4189
4640
  return c.json({ ok: true });
4190
4641
  });
4191
- var prompts_default = app13;
4642
+ var prompts_default = app14;
4643
+
4644
+ // src/web/api/public-files.ts
4645
+ import { readdir as readdir2, readFile as readFile3, stat as stat3 } from "fs/promises";
4646
+ import { extname as extname4, resolve as resolve14 } from "path";
4647
+ import { Hono as Hono15 } from "hono";
4648
+ var MAX_FILE_SIZE = 50 * 1024 * 1024;
4649
+ function resolvePublicRoot(name) {
4650
+ if (name === "_system") return resolve14(voluteHome(), "shared");
4651
+ if (!findMind(name)) return null;
4652
+ return resolve14(mindDir(name), "home", "public");
4653
+ }
4654
+ function hasDotSegment(relativePath) {
4655
+ return relativePath.split("/").some((seg) => seg.startsWith("."));
4656
+ }
4657
+ var MIME_TYPES2 = {
4658
+ ".html": "text/html",
4659
+ ".js": "application/javascript",
4660
+ ".css": "text/css",
4661
+ ".json": "application/json",
4662
+ ".svg": "image/svg+xml",
4663
+ ".png": "image/png",
4664
+ ".jpg": "image/jpeg",
4665
+ ".jpeg": "image/jpeg",
4666
+ ".gif": "image/gif",
4667
+ ".ico": "image/x-icon",
4668
+ ".woff": "font/woff",
4669
+ ".woff2": "font/woff2",
4670
+ ".txt": "text/plain",
4671
+ ".xml": "application/xml",
4672
+ ".md": "text/markdown",
4673
+ ".webp": "image/webp"
4674
+ };
4675
+ async function listDir(dirPath) {
4676
+ let entries;
4677
+ try {
4678
+ entries = await readdir2(dirPath, { withFileTypes: true });
4679
+ } catch (err) {
4680
+ if (err?.code === "ENOENT") return [];
4681
+ throw err;
4682
+ }
4683
+ return entries.filter((e) => !e.name.startsWith(".")).map((e) => ({
4684
+ name: e.name,
4685
+ type: e.isDirectory() ? "directory" : "file"
4686
+ }));
4687
+ }
4688
+ var app15 = new Hono15().get("/:name/", async (c) => {
4689
+ const name = c.req.param("name");
4690
+ const publicRoot = resolvePublicRoot(name);
4691
+ if (!publicRoot) return c.json({ error: "Not found" }, 404);
4692
+ return c.json(await listDir(publicRoot));
4693
+ }).get("/:name/*", async (c) => {
4694
+ const name = c.req.param("name");
4695
+ const publicRoot = resolvePublicRoot(name);
4696
+ if (!publicRoot) return c.text("Not found", 404);
4697
+ const wildcard = c.req.path.replace(`/public/${name}`, "") || "/";
4698
+ const relativePath = wildcard.slice(1);
4699
+ const requestedPath = resolve14(publicRoot, relativePath);
4700
+ if (!requestedPath.startsWith(publicRoot)) return c.text("Forbidden", 403);
4701
+ if (hasDotSegment(relativePath)) return c.text("Forbidden", 403);
4702
+ let fileStat;
4703
+ try {
4704
+ fileStat = await stat3(requestedPath);
4705
+ } catch (err) {
4706
+ if (err?.code === "ENOENT") return c.text("Not found", 404);
4707
+ if (err?.code === "EACCES") return c.text("Forbidden", 403);
4708
+ return c.text("Internal server error", 500);
4709
+ }
4710
+ if (fileStat.isDirectory()) {
4711
+ if (wildcard.endsWith("/")) {
4712
+ return c.json(await listDir(requestedPath));
4713
+ }
4714
+ return c.text("Not found", 404);
4715
+ }
4716
+ if (fileStat.isFile()) {
4717
+ if (fileStat.size > MAX_FILE_SIZE) return c.text("File too large", 413);
4718
+ const ext = extname4(requestedPath);
4719
+ const mime = MIME_TYPES2[ext] || "application/octet-stream";
4720
+ try {
4721
+ const body = await readFile3(requestedPath);
4722
+ return c.body(body, 200, { "Content-Type": mime });
4723
+ } catch (err) {
4724
+ if (err?.code === "ENOENT") return c.text("Not found", 404);
4725
+ if (err?.code === "EACCES") return c.text("Forbidden", 403);
4726
+ return c.text("Failed to read file", 500);
4727
+ }
4728
+ }
4729
+ return c.text("Not found", 404);
4730
+ });
4731
+ var public_files_default = app15;
4192
4732
 
4193
4733
  // src/web/api/schedules.ts
4194
4734
  import { CronExpressionParser } from "cron-parser";
4195
- import { Hono as Hono14 } from "hono";
4735
+ import { Hono as Hono16 } from "hono";
4196
4736
  var slog = logger_default.child("schedules");
4197
4737
  function readSchedules(name) {
4198
4738
  return readVoluteConfig(mindDir(name))?.schedules ?? [];
@@ -4209,7 +4749,7 @@ function writeSchedules(name, schedules) {
4209
4749
  data: { schedules }
4210
4750
  });
4211
4751
  }
4212
- var app14 = new Hono14().get("/:name/schedules", (c) => {
4752
+ var app16 = new Hono16().get("/:name/schedules", (c) => {
4213
4753
  const name = c.req.param("name");
4214
4754
  if (!findMind(name)) return c.json({ error: "Mind not found" }, 404);
4215
4755
  return c.json(readSchedules(name));
@@ -4242,6 +4782,7 @@ var app14 = new Hono14().get("/:name/schedules", (c) => {
4242
4782
  const schedule = { id, cron: body.cron, enabled: body.enabled ?? true };
4243
4783
  if (body.message) schedule.message = body.message;
4244
4784
  if (body.script) schedule.script = body.script;
4785
+ if (body.channel) schedule.channel = body.channel;
4245
4786
  schedules.push(schedule);
4246
4787
  writeSchedules(name, schedules);
4247
4788
  return c.json({ ok: true, id }, 201);
@@ -4273,6 +4814,7 @@ var app14 = new Hono14().get("/:name/schedules", (c) => {
4273
4814
  delete schedules[idx].message;
4274
4815
  }
4275
4816
  if (body.enabled !== void 0) schedules[idx].enabled = body.enabled;
4817
+ if (body.channel !== void 0) schedules[idx].channel = body.channel || void 0;
4276
4818
  writeSchedules(name, schedules);
4277
4819
  return c.json({ ok: true });
4278
4820
  }).delete("/:name/schedules/:id", requireAdmin, (c) => {
@@ -4312,11 +4854,11 @@ var app14 = new Hono14().get("/:name/schedules", (c) => {
4312
4854
  return c.json({ error: "Failed to reach mind" }, 502);
4313
4855
  }
4314
4856
  });
4315
- var schedules_default = app14;
4857
+ var schedules_default = app16;
4316
4858
 
4317
4859
  // src/web/api/shared.ts
4318
- import { Hono as Hono15 } from "hono";
4319
- var app15 = new Hono15().post("/:name/shared/merge", requireAdmin, async (c) => {
4860
+ import { Hono as Hono17 } from "hono";
4861
+ var app17 = new Hono17().post("/:name/shared/merge", requireAdmin, async (c) => {
4320
4862
  const name = c.req.param("name");
4321
4863
  const entry = findMind(name);
4322
4864
  if (!entry) return c.json({ error: "Mind not found" }, 404);
@@ -4365,15 +4907,15 @@ var app15 = new Hono15().post("/:name/shared/merge", requireAdmin, async (c) =>
4365
4907
  return c.json({ error: err instanceof Error ? err.message : "Failed to get status" }, 500);
4366
4908
  }
4367
4909
  });
4368
- var shared_default = app15;
4910
+ var shared_default = app17;
4369
4911
 
4370
4912
  // src/web/api/skills.ts
4371
4913
  import { existsSync as existsSync11, mkdtempSync, readdirSync as readdirSync5, rmSync as rmSync5 } from "fs";
4372
4914
  import { tmpdir } from "os";
4373
- import { join as join2, resolve as resolve14 } from "path";
4915
+ import { join as join2, resolve as resolve15 } from "path";
4374
4916
  import AdmZip from "adm-zip";
4375
- import { Hono as Hono16 } from "hono";
4376
- var app16 = new Hono16().get("/", async (c) => {
4917
+ import { Hono as Hono18 } from "hono";
4918
+ var app18 = new Hono18().get("/", async (c) => {
4377
4919
  const skills = await listSharedSkills();
4378
4920
  return c.json(skills);
4379
4921
  }).get("/:id", async (c) => {
@@ -4397,7 +4939,7 @@ var app16 = new Hono16().get("/", async (c) => {
4397
4939
  try {
4398
4940
  const zip = new AdmZip(buffer2);
4399
4941
  for (const entry of zip.getEntries()) {
4400
- const target = resolve14(tmpDir, entry.entryName);
4942
+ const target = resolve15(tmpDir, entry.entryName);
4401
4943
  if (!target.startsWith(tmpDir)) {
4402
4944
  return c.json({ error: "Invalid zip: paths must not escape archive" }, 400);
4403
4945
  }
@@ -4438,12 +4980,15 @@ var app16 = new Hono16().get("/", async (c) => {
4438
4980
  }
4439
4981
  return c.json({ ok: true });
4440
4982
  });
4441
- var skills_default = app16;
4983
+ var skills_default = app18;
4442
4984
 
4443
4985
  // src/web/api/system.ts
4444
- import { Hono as Hono17 } from "hono";
4986
+ import { zValidator as zValidator6 } from "@hono/zod-validator";
4987
+ import { Hono as Hono19 } from "hono";
4445
4988
  import { streamSSE as streamSSE3 } from "hono/streaming";
4446
- var app17 = new Hono17().post("/restart", requireAdmin, (c) => {
4989
+ import { z as z6 } from "zod";
4990
+ var DEFAULT_API_URL = "https://volute.systems";
4991
+ var app19 = new Hono19().post("/restart", requireAdmin, (c) => {
4447
4992
  setTimeout(() => process.exit(1), 200);
4448
4993
  return c.json({ ok: true });
4449
4994
  }).post("/stop", requireAdmin, (c) => {
@@ -4460,29 +5005,107 @@ var app17 = new Hono17().post("/restart", requireAdmin, (c) => {
4460
5005
  stream.writeSSE({ data: JSON.stringify(entry) }).catch(() => {
4461
5006
  });
4462
5007
  });
4463
- await new Promise((resolve19) => {
5008
+ await new Promise((resolve20) => {
4464
5009
  stream.onAbort(() => {
4465
5010
  unsubscribe();
4466
- resolve19();
5011
+ resolve20();
4467
5012
  });
4468
5013
  });
4469
5014
  });
4470
5015
  }).get("/info", (c) => {
4471
5016
  const config = readSystemsConfig();
4472
5017
  return c.json({ system: config?.system ?? null });
5018
+ }).post(
5019
+ "/register",
5020
+ requireAdmin,
5021
+ zValidator6("json", z6.object({ name: z6.string().min(1) })),
5022
+ async (c) => {
5023
+ const existing = readSystemsConfig();
5024
+ if (existing) {
5025
+ return c.json({ error: `Already registered as "${existing.system}"` }, 400);
5026
+ }
5027
+ const { name } = c.req.valid("json");
5028
+ const apiUrl = process.env.VOLUTE_SYSTEMS_URL || DEFAULT_API_URL;
5029
+ let apiKey;
5030
+ let system;
5031
+ try {
5032
+ const res = await fetch(`${apiUrl}/api/register`, {
5033
+ method: "POST",
5034
+ headers: { "Content-Type": "application/json" },
5035
+ body: JSON.stringify({ name: name.trim() })
5036
+ });
5037
+ if (!res.ok) {
5038
+ const err = await res.json().catch(() => ({ error: `HTTP ${res.status}` }));
5039
+ return c.json({ error: err.error }, 502);
5040
+ }
5041
+ ({ apiKey, system } = await res.json());
5042
+ } catch (err) {
5043
+ return c.json({ error: `Connection failed: ${err.message}` }, 502);
5044
+ }
5045
+ try {
5046
+ writeSystemsConfig({ apiKey, system, apiUrl });
5047
+ } catch (err) {
5048
+ return c.json(
5049
+ {
5050
+ error: `Registered as "${system}" but failed to save config: ${err.message}`
5051
+ },
5052
+ 500
5053
+ );
5054
+ }
5055
+ return c.json({ system });
5056
+ }
5057
+ ).post(
5058
+ "/login",
5059
+ requireAdmin,
5060
+ zValidator6("json", z6.object({ key: z6.string().min(1) })),
5061
+ async (c) => {
5062
+ const existing = readSystemsConfig();
5063
+ if (existing) {
5064
+ return c.json({ error: `Already logged in as "${existing.system}"` }, 400);
5065
+ }
5066
+ const { key } = c.req.valid("json");
5067
+ const apiUrl = process.env.VOLUTE_SYSTEMS_URL || DEFAULT_API_URL;
5068
+ let system;
5069
+ try {
5070
+ const res = await fetch(`${apiUrl}/api/whoami`, {
5071
+ headers: { Authorization: `Bearer ${key.trim()}` }
5072
+ });
5073
+ if (!res.ok) {
5074
+ const err = await res.json().catch(() => ({ error: `HTTP ${res.status}` }));
5075
+ return c.json({ error: err.error }, 502);
5076
+ }
5077
+ ({ system } = await res.json());
5078
+ } catch (err) {
5079
+ return c.json({ error: `Connection failed: ${err.message}` }, 502);
5080
+ }
5081
+ try {
5082
+ writeSystemsConfig({ apiKey: key.trim(), system, apiUrl });
5083
+ } catch (err) {
5084
+ return c.json(
5085
+ {
5086
+ error: `Logged in as "${system}" but failed to save config: ${err.message}`
5087
+ },
5088
+ 500
5089
+ );
5090
+ }
5091
+ return c.json({ system });
5092
+ }
5093
+ ).post("/logout", requireAdmin, (c) => {
5094
+ deleteSystemsConfig();
5095
+ return c.json({ ok: true });
4473
5096
  });
4474
- var system_default = app17;
5097
+ var system_default = app19;
4475
5098
 
4476
5099
  // src/web/api/typing.ts
4477
- import { zValidator as zValidator5 } from "@hono/zod-validator";
4478
- import { Hono as Hono18 } from "hono";
4479
- import { z as z5 } from "zod";
4480
- var typingSchema = z5.object({
4481
- channel: z5.string().min(1),
4482
- sender: z5.string().min(1),
4483
- active: z5.boolean()
5100
+ import { zValidator as zValidator7 } from "@hono/zod-validator";
5101
+ import { Hono as Hono20 } from "hono";
5102
+ import { z as z7 } from "zod";
5103
+ var typingSchema = z7.object({
5104
+ channel: z7.string().min(1),
5105
+ sender: z7.string().min(1),
5106
+ active: z7.boolean()
4484
5107
  });
4485
- var app18 = new Hono18().post("/:name/typing", zValidator5("json", typingSchema), (c) => {
5108
+ var app20 = new Hono20().post("/:name/typing", zValidator7("json", typingSchema), (c) => {
4486
5109
  const { channel, sender, active } = c.req.valid("json");
4487
5110
  const map = getTypingMap();
4488
5111
  if (active) {
@@ -4493,7 +5116,7 @@ var app18 = new Hono18().post("/:name/typing", zValidator5("json", typingSchema)
4493
5116
  const volutePrefix = "volute:";
4494
5117
  if (channel.startsWith(volutePrefix)) {
4495
5118
  const conversationId = channel.slice(volutePrefix.length);
4496
- publish2(conversationId, { type: "typing", senders: map.get(channel) });
5119
+ publish(conversationId, { type: "typing", senders: map.get(channel) });
4497
5120
  }
4498
5121
  return c.json({ ok: true });
4499
5122
  }).get("/:name/typing", (c) => {
@@ -4504,13 +5127,13 @@ var app18 = new Hono18().post("/:name/typing", zValidator5("json", typingSchema)
4504
5127
  const map = getTypingMap();
4505
5128
  return c.json({ typing: map.get(channel) });
4506
5129
  });
4507
- var typing_default = app18;
5130
+ var typing_default = app20;
4508
5131
 
4509
5132
  // src/web/api/update.ts
4510
5133
  import { spawn as spawn2 } from "child_process";
4511
- import { Hono as Hono19 } from "hono";
5134
+ import { Hono as Hono21 } from "hono";
4512
5135
  var bin;
4513
- var app19 = new Hono19().get("/update", async (c) => {
5136
+ var app21 = new Hono21().get("/update", async (c) => {
4514
5137
  const result = await checkForUpdate();
4515
5138
  return c.json(result);
4516
5139
  }).post("/update", requireAdmin, async (c) => {
@@ -4525,21 +5148,21 @@ var app19 = new Hono19().get("/update", async (c) => {
4525
5148
  child.unref();
4526
5149
  return c.json({ ok: true, message: "Updating..." });
4527
5150
  });
4528
- var update_default = app19;
5151
+ var update_default = app21;
4529
5152
 
4530
5153
  // src/web/api/v1/chat.ts
4531
- import { zValidator as zValidator6 } from "@hono/zod-validator";
4532
- import { Hono as Hono20 } from "hono";
5154
+ import { zValidator as zValidator8 } from "@hono/zod-validator";
5155
+ import { Hono as Hono22 } from "hono";
4533
5156
  import { streamSSE as streamSSE4 } from "hono/streaming";
4534
- import { z as z6 } from "zod";
5157
+ import { z as z8 } from "zod";
4535
5158
  async function fanOutToMinds(opts) {
4536
5159
  const participants = await getParticipants(opts.conversationId);
4537
5160
  const mindParticipants = participants.filter((p) => p.userType === "mind");
4538
5161
  const participantNames = participants.map((p) => p.username);
4539
5162
  const isDM = opts.isDM ?? participants.length === 2;
4540
5163
  const channelEntryType = opts.channelEntryType ?? (isDM ? "dm" : "group");
4541
- const { getMindManager: getMindManager2 } = await import("./mind-manager-ZNRIYEK3.js");
4542
- const { getSleepManagerIfReady } = await import("./sleep-manager-XXSWQQLE.js");
5164
+ const { getMindManager: getMindManager2 } = await import("./mind-manager-VVK67AY3.js");
5165
+ const { getSleepManagerIfReady } = await import("./sleep-manager-EE4NRN2Q.js");
4543
5166
  const manager = getMindManager2();
4544
5167
  const sm = getSleepManagerIfReady();
4545
5168
  const targetMinds = mindParticipants.map((ap) => {
@@ -4588,18 +5211,18 @@ async function fanOutToMinds(opts) {
4588
5211
  });
4589
5212
  }
4590
5213
  }
4591
- var mindChatSchema = z6.object({
4592
- message: z6.string().optional(),
4593
- conversationId: z6.string().optional(),
4594
- sender: z6.string().optional(),
4595
- images: z6.array(z6.object({ media_type: z6.string(), data: z6.string() })).optional()
5214
+ var mindChatSchema = z8.object({
5215
+ message: z8.string().optional(),
5216
+ conversationId: z8.string().optional(),
5217
+ sender: z8.string().optional(),
5218
+ images: z8.array(z8.object({ media_type: z8.string(), data: z8.string() })).optional()
4596
5219
  });
4597
- var unifiedChatSchema = z6.object({
4598
- message: z6.string().optional(),
4599
- conversationId: z6.string(),
4600
- images: z6.array(z6.object({ media_type: z6.string(), data: z6.string() })).optional()
5220
+ var unifiedChatSchema = z8.object({
5221
+ message: z8.string().optional(),
5222
+ conversationId: z8.string(),
5223
+ images: z8.array(z8.object({ media_type: z8.string(), data: z8.string() })).optional()
4601
5224
  });
4602
- var app20 = new Hono20().use("*", authMiddleware).post("/minds/:name/chat", zValidator6("json", mindChatSchema), async (c) => {
5225
+ var app22 = new Hono22().use("*", authMiddleware).post("/minds/:name/chat", zValidator8("json", mindChatSchema), async (c) => {
4603
5226
  const name = c.req.param("name");
4604
5227
  const [baseName] = name.split("@", 2);
4605
5228
  const entry = findMind(baseName);
@@ -4672,7 +5295,7 @@ var app20 = new Hono20().use("*", authMiddleware).post("/minds/:name/chat", zVal
4672
5295
  return c.json({ error: "Conversation not found" }, 404);
4673
5296
  }
4674
5297
  return streamSSE4(c, async (stream) => {
4675
- const unsubscribe = subscribe3(conversationId, (event) => {
5298
+ const unsubscribe = subscribe2(conversationId, (event) => {
4676
5299
  stream.writeSSE({ data: JSON.stringify(event) }).catch((err) => {
4677
5300
  if (!stream.aborted) logger_default.error("[v1-chat] SSE write error:", logger_default.errorData(err));
4678
5301
  });
@@ -4682,15 +5305,15 @@ var app20 = new Hono20().use("*", authMiddleware).post("/minds/:name/chat", zVal
4682
5305
  if (!stream.aborted) logger_default.error("[v1-chat] SSE ping error:", logger_default.errorData(err));
4683
5306
  });
4684
5307
  }, 15e3);
4685
- await new Promise((resolve19) => {
5308
+ await new Promise((resolve20) => {
4686
5309
  stream.onAbort(() => {
4687
5310
  unsubscribe();
4688
5311
  clearInterval(keepAlive);
4689
- resolve19();
5312
+ resolve20();
4690
5313
  });
4691
5314
  });
4692
5315
  });
4693
- }).post("/chat", zValidator6("json", unifiedChatSchema), async (c) => {
5316
+ }).post("/chat", zValidator8("json", unifiedChatSchema), async (c) => {
4694
5317
  const user = c.get("user");
4695
5318
  const body = c.req.valid("json");
4696
5319
  if (!body.message && (!body.images || body.images.length === 0)) {
@@ -4722,17 +5345,17 @@ var app20 = new Hono20().use("*", authMiddleware).post("/minds/:name/chat", zVal
4722
5345
  });
4723
5346
  return c.json({ ok: true, conversationId: body.conversationId });
4724
5347
  });
4725
- var chat_default = app20;
5348
+ var chat_default = app22;
4726
5349
 
4727
5350
  // src/web/api/v1/conversations.ts
4728
- import { zValidator as zValidator7 } from "@hono/zod-validator";
4729
- import { Hono as Hono21 } from "hono";
4730
- import { z as z7 } from "zod";
4731
- var createSchema = z7.object({
4732
- title: z7.string().optional(),
4733
- participantNames: z7.array(z7.string()).min(1)
5351
+ import { zValidator as zValidator9 } from "@hono/zod-validator";
5352
+ import { Hono as Hono23 } from "hono";
5353
+ import { z as z9 } from "zod";
5354
+ var createSchema2 = z9.object({
5355
+ title: z9.string().optional(),
5356
+ participantNames: z9.array(z9.string()).min(1)
4734
5357
  });
4735
- var app21 = new Hono21().use("*", authMiddleware).get("/", async (c) => {
5358
+ var app23 = new Hono23().use("*", authMiddleware).get("/", async (c) => {
4736
5359
  const user = c.get("user");
4737
5360
  const convs = await listConversationsWithParticipants(user.id);
4738
5361
  return c.json(convs);
@@ -4763,7 +5386,7 @@ var app21 = new Hono21().use("*", authMiddleware).get("/", async (c) => {
4763
5386
  }
4764
5387
  const participants = await getParticipants(id);
4765
5388
  return c.json(participants);
4766
- }).post("/", zValidator7("json", createSchema), async (c) => {
5389
+ }).post("/", zValidator9("json", createSchema2), async (c) => {
4767
5390
  const user = c.get("user");
4768
5391
  const body = c.req.valid("json");
4769
5392
  const participantIds = /* @__PURE__ */ new Set();
@@ -4809,30 +5432,30 @@ var app21 = new Hono21().use("*", authMiddleware).get("/", async (c) => {
4809
5432
  if (!deleted) return c.json({ error: "Conversation not found" }, 404);
4810
5433
  return c.json({ ok: true });
4811
5434
  });
4812
- var conversations_default = app21;
5435
+ var conversations_default = app23;
4813
5436
 
4814
5437
  // src/web/api/v1/events.ts
4815
- import { desc as desc3 } from "drizzle-orm";
4816
- import { Hono as Hono22 } from "hono";
5438
+ import { desc as desc4 } from "drizzle-orm";
5439
+ import { Hono as Hono24 } from "hono";
4817
5440
  import { streamSSE as streamSSE5 } from "hono/streaming";
4818
5441
 
4819
5442
  // src/lib/events/brain-presence.ts
4820
5443
  var connections = /* @__PURE__ */ new Map();
4821
5444
  function addConnection(username) {
4822
- const count = connections.get(username) ?? 0;
4823
- connections.set(username, count + 1);
4824
- if (count === 0) {
5445
+ const count2 = connections.get(username) ?? 0;
5446
+ connections.set(username, count2 + 1);
5447
+ if (count2 === 0) {
4825
5448
  broadcast({ type: "brain_online", mind: username, summary: `${username} connected` });
4826
5449
  }
4827
5450
  }
4828
5451
  function removeConnection(username) {
4829
- const count = connections.get(username);
4830
- if (count == null) return;
4831
- if (count <= 1) {
5452
+ const count2 = connections.get(username);
5453
+ if (count2 == null) return;
5454
+ if (count2 <= 1) {
4832
5455
  connections.delete(username);
4833
5456
  broadcast({ type: "brain_offline", mind: username, summary: `${username} disconnected` });
4834
5457
  } else {
4835
- connections.set(username, count - 1);
5458
+ connections.set(username, count2 - 1);
4836
5459
  }
4837
5460
  }
4838
5461
  function getOnlineBrains() {
@@ -4860,7 +5483,7 @@ function getEventsSince(sinceId) {
4860
5483
  }
4861
5484
 
4862
5485
  // src/web/api/v1/events.ts
4863
- var app22 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
5486
+ var app24 = new Hono24().use("*", authMiddleware).get("/", async (c) => {
4864
5487
  const user = c.get("user");
4865
5488
  const since = c.req.query("since");
4866
5489
  const sinceId = since ? Number(since) : 0;
@@ -4883,7 +5506,7 @@ var app22 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
4883
5506
  let recentActivity = [];
4884
5507
  try {
4885
5508
  const db = await getDb();
4886
- recentActivity = await db.select().from(activity).orderBy(desc3(activity.created_at)).limit(50);
5509
+ recentActivity = await db.select().from(activity).orderBy(desc4(activity.created_at)).limit(50);
4887
5510
  recentActivity = recentActivity.map((row) => ({
4888
5511
  ...row,
4889
5512
  metadata: row.metadata ? JSON.parse(row.metadata) : null
@@ -4936,7 +5559,7 @@ var app22 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
4936
5559
  });
4937
5560
  cleanups.push(unsubActivity);
4938
5561
  for (const conv of conversations2) {
4939
- const unsubConv = subscribe3(conv.id, (event) => {
5562
+ const unsubConv = subscribe2(conv.id, (event) => {
4940
5563
  const data = { event: "conversation", conversationId: conv.id, ...event };
4941
5564
  const eventId = bufferEvent(data);
4942
5565
  stream.writeSSE({
@@ -4954,8 +5577,8 @@ var app22 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
4954
5577
  });
4955
5578
  }, 15e3);
4956
5579
  cleanups.push(() => clearInterval(keepAlive));
4957
- await new Promise((resolve19) => {
4958
- stream.onAbort(() => resolve19());
5580
+ await new Promise((resolve20) => {
5581
+ stream.onAbort(() => resolve20());
4959
5582
  });
4960
5583
  } finally {
4961
5584
  for (const cleanup of cleanups) {
@@ -4967,19 +5590,19 @@ var app22 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
4967
5590
  }
4968
5591
  });
4969
5592
  });
4970
- var events_default = app22;
5593
+ var events_default = app24;
4971
5594
 
4972
5595
  // src/web/api/variants.ts
4973
5596
  import { existsSync as existsSync12, mkdirSync as mkdirSync8, writeFileSync as writeFileSync8 } from "fs";
4974
- import { resolve as resolve16 } from "path";
4975
- import { Hono as Hono23 } from "hono";
5597
+ import { resolve as resolve17 } from "path";
5598
+ import { Hono as Hono25 } from "hono";
4976
5599
 
4977
5600
  // src/lib/spawn-server.ts
4978
5601
  import { spawn as spawn3 } from "child_process";
4979
5602
  import { closeSync, mkdirSync as mkdirSync7, openSync, readFileSync as readFileSync10 } from "fs";
4980
- import { resolve as resolve15 } from "path";
5603
+ import { resolve as resolve16 } from "path";
4981
5604
  function tsxBin(cwd) {
4982
- return resolve15(cwd, "node_modules", ".bin", "tsx");
5605
+ return resolve16(cwd, "node_modules", ".bin", "tsx");
4983
5606
  }
4984
5607
  function spawnServer(cwd, port, options) {
4985
5608
  if (options?.detached) {
@@ -4992,31 +5615,31 @@ function spawnAttached(cwd, port) {
4992
5615
  cwd,
4993
5616
  stdio: ["ignore", "pipe", "pipe"]
4994
5617
  });
4995
- return new Promise((resolve19) => {
4996
- const timeout = setTimeout(() => resolve19(null), 3e4);
5618
+ return new Promise((resolve20) => {
5619
+ const timeout = setTimeout(() => resolve20(null), 3e4);
4997
5620
  function checkOutput(data) {
4998
5621
  const match = data.toString().match(/listening on :(\d+)/);
4999
5622
  if (match) {
5000
5623
  clearTimeout(timeout);
5001
- resolve19({ child, actualPort: parseInt(match[1], 10) });
5624
+ resolve20({ child, actualPort: parseInt(match[1], 10) });
5002
5625
  }
5003
5626
  }
5004
5627
  child.stdout?.on("data", checkOutput);
5005
5628
  child.stderr?.on("data", checkOutput);
5006
5629
  child.on("error", () => {
5007
5630
  clearTimeout(timeout);
5008
- resolve19(null);
5631
+ resolve20(null);
5009
5632
  });
5010
5633
  child.on("exit", () => {
5011
5634
  clearTimeout(timeout);
5012
- resolve19(null);
5635
+ resolve20(null);
5013
5636
  });
5014
5637
  });
5015
5638
  }
5016
5639
  function spawnDetached(cwd, port, logDir) {
5017
- const logsDir = logDir ?? resolve15(cwd, ".mind", "logs");
5640
+ const logsDir = logDir ?? resolve16(cwd, ".mind", "logs");
5018
5641
  mkdirSync7(logsDir, { recursive: true });
5019
- const logPath = resolve15(logsDir, "mind.log");
5642
+ const logPath = resolve16(logsDir, "mind.log");
5020
5643
  const logFd = openSync(logPath, "a");
5021
5644
  const child = spawn3(tsxBin(cwd), ["src/server.ts", "--port", String(port)], {
5022
5645
  cwd,
@@ -5088,7 +5711,7 @@ async function verify2(port) {
5088
5711
  }
5089
5712
 
5090
5713
  // src/web/api/variants.ts
5091
- var app23 = new Hono23().get("/:name/variants", async (c) => {
5714
+ var app25 = new Hono25().get("/:name/variants", async (c) => {
5092
5715
  const name = c.req.param("name");
5093
5716
  const entry = findMind(name);
5094
5717
  if (!entry) return c.json({ error: "Mind not found" }, 404);
@@ -5128,11 +5751,11 @@ var app23 = new Hono23().get("/:name/variants", async (c) => {
5128
5751
  const err = validateBranchName(variantName);
5129
5752
  if (err) return c.json({ error: err }, 400);
5130
5753
  const projectRoot = mindDir(mindName);
5131
- const variantDir = resolve16(projectRoot, ".variants", variantName);
5754
+ const variantDir = resolve17(projectRoot, ".variants", variantName);
5132
5755
  if (existsSync12(variantDir)) {
5133
5756
  return c.json({ error: `Variant directory already exists: ${variantDir}` }, 409);
5134
5757
  }
5135
- mkdirSync8(resolve16(projectRoot, ".variants"), { recursive: true });
5758
+ mkdirSync8(resolve17(projectRoot, ".variants"), { recursive: true });
5136
5759
  try {
5137
5760
  await gitExec(["worktree", "add", "-b", variantName, variantDir], { cwd: projectRoot });
5138
5761
  } catch (e) {
@@ -5145,7 +5768,7 @@ var app23 = new Hono23().get("/:name/variants", async (c) => {
5145
5768
  const [cmd, args] = wrapForIsolation("npm", ["install"], mindName);
5146
5769
  await exec(cmd, args, {
5147
5770
  cwd: variantDir,
5148
- env: { ...process.env, HOME: resolve16(variantDir, "home") }
5771
+ env: { ...process.env, HOME: resolve17(variantDir, "home") }
5149
5772
  });
5150
5773
  } else {
5151
5774
  await exec("npm", ["install"], { cwd: variantDir });
@@ -5155,7 +5778,7 @@ var app23 = new Hono23().get("/:name/variants", async (c) => {
5155
5778
  return c.json({ error: `npm install failed: ${msg}` }, 500);
5156
5779
  }
5157
5780
  if (body.soul) {
5158
- writeFileSync8(resolve16(variantDir, "home/SOUL.md"), body.soul);
5781
+ writeFileSync8(resolve17(variantDir, "home/SOUL.md"), body.soul);
5159
5782
  }
5160
5783
  const variantPort = body.port ?? nextPort();
5161
5784
  const variant = {
@@ -5266,7 +5889,7 @@ var app23 = new Hono23().get("/:name/variants", async (c) => {
5266
5889
  const [cmd, args] = wrapForIsolation("npm", ["install"], mindName);
5267
5890
  await exec(cmd, args, {
5268
5891
  cwd: projectRoot,
5269
- env: { ...process.env, HOME: resolve16(projectRoot, "home") }
5892
+ env: { ...process.env, HOME: resolve17(projectRoot, "home") }
5270
5893
  });
5271
5894
  } else {
5272
5895
  await exec("npm", ["install"], { cwd: projectRoot });
@@ -5304,19 +5927,19 @@ var app23 = new Hono23().get("/:name/variants", async (c) => {
5304
5927
  await cleanupVariant(mindName, variantName, projectRoot, variant.path, { stop: true });
5305
5928
  return c.json({ ok: true });
5306
5929
  });
5307
- var variants_default = app23;
5930
+ var variants_default = app25;
5308
5931
 
5309
5932
  // src/web/api/volute/channels.ts
5310
- import { zValidator as zValidator8 } from "@hono/zod-validator";
5311
- import { Hono as Hono24 } from "hono";
5312
- import { z as z8 } from "zod";
5313
- var createSchema2 = z8.object({
5314
- name: z8.string().min(1).max(50).regex(/^[a-z0-9][a-z0-9-]*$/, "Channel names must be lowercase alphanumeric with hyphens")
5933
+ import { zValidator as zValidator10 } from "@hono/zod-validator";
5934
+ import { Hono as Hono26 } from "hono";
5935
+ import { z as z10 } from "zod";
5936
+ var createSchema3 = z10.object({
5937
+ name: z10.string().min(1).max(50).regex(/^[a-z0-9][a-z0-9-]*$/, "Channel names must be lowercase alphanumeric with hyphens")
5315
5938
  });
5316
- var inviteSchema = z8.object({
5317
- username: z8.string().min(1)
5939
+ var inviteSchema = z10.object({
5940
+ username: z10.string().min(1)
5318
5941
  });
5319
- var app24 = new Hono24().get("/", async (c) => {
5942
+ var app26 = new Hono26().get("/", async (c) => {
5320
5943
  const user = c.get("user");
5321
5944
  const channels = await listChannels();
5322
5945
  const results = await Promise.all(
@@ -5327,7 +5950,7 @@ var app24 = new Hono24().get("/", async (c) => {
5327
5950
  })
5328
5951
  );
5329
5952
  return c.json(results);
5330
- }).post("/", zValidator8("json", createSchema2), async (c) => {
5953
+ }).post("/", zValidator10("json", createSchema3), async (c) => {
5331
5954
  const user = c.get("user");
5332
5955
  const body = c.req.valid("json");
5333
5956
  try {
@@ -5360,7 +5983,7 @@ var app24 = new Hono24().get("/", async (c) => {
5360
5983
  if (!ch) return c.json({ error: "Channel not found" }, 404);
5361
5984
  const participants = await getParticipants(ch.id);
5362
5985
  return c.json(participants);
5363
- }).post("/:name/invite", zValidator8("json", inviteSchema), async (c) => {
5986
+ }).post("/:name/invite", zValidator10("json", inviteSchema), async (c) => {
5364
5987
  const name = c.req.param("name");
5365
5988
  const inviter = c.get("user");
5366
5989
  const { username } = c.req.valid("json");
@@ -5380,21 +6003,21 @@ var app24 = new Hono24().get("/", async (c) => {
5380
6003
  ]);
5381
6004
  return c.json({ ok: true });
5382
6005
  });
5383
- var channels_default2 = app24;
6006
+ var channels_default2 = app26;
5384
6007
 
5385
6008
  // src/web/api/volute/chat.ts
5386
- import { zValidator as zValidator9 } from "@hono/zod-validator";
5387
- import { Hono as Hono25 } from "hono";
6009
+ import { zValidator as zValidator11 } from "@hono/zod-validator";
6010
+ import { Hono as Hono27 } from "hono";
5388
6011
  import { streamSSE as streamSSE6 } from "hono/streaming";
5389
- import { z as z9 } from "zod";
6012
+ import { z as z11 } from "zod";
5390
6013
  async function fanOutToMinds2(opts) {
5391
6014
  const participants = await getParticipants(opts.conversationId);
5392
6015
  const mindParticipants = participants.filter((p) => p.userType === "mind");
5393
6016
  const participantNames = participants.map((p) => p.username);
5394
6017
  const isDM = opts.isDM ?? participants.length === 2;
5395
6018
  const channelEntryType = opts.channelEntryType ?? (isDM ? "dm" : "group");
5396
- const { getMindManager: getMindManager2 } = await import("./mind-manager-ZNRIYEK3.js");
5397
- const { getSleepManagerIfReady } = await import("./sleep-manager-XXSWQQLE.js");
6019
+ const { getMindManager: getMindManager2 } = await import("./mind-manager-VVK67AY3.js");
6020
+ const { getSleepManagerIfReady } = await import("./sleep-manager-EE4NRN2Q.js");
5398
6021
  const manager = getMindManager2();
5399
6022
  const sm = getSleepManagerIfReady();
5400
6023
  const targetMinds = mindParticipants.map((ap) => {
@@ -5442,18 +6065,18 @@ async function fanOutToMinds2(opts) {
5442
6065
  });
5443
6066
  }
5444
6067
  }
5445
- var chatSchema = z9.object({
5446
- message: z9.string().optional(),
5447
- conversationId: z9.string().optional(),
5448
- sender: z9.string().optional(),
5449
- images: z9.array(
5450
- z9.object({
5451
- media_type: z9.string(),
5452
- data: z9.string()
6068
+ var chatSchema = z11.object({
6069
+ message: z11.string().optional(),
6070
+ conversationId: z11.string().optional(),
6071
+ sender: z11.string().optional(),
6072
+ images: z11.array(
6073
+ z11.object({
6074
+ media_type: z11.string(),
6075
+ data: z11.string()
5453
6076
  })
5454
6077
  ).optional()
5455
6078
  });
5456
- var app25 = new Hono25().post("/:name/chat", zValidator9("json", chatSchema), async (c) => {
6079
+ var app27 = new Hono27().post("/:name/chat", zValidator11("json", chatSchema), async (c) => {
5457
6080
  const name = c.req.param("name");
5458
6081
  const [baseName] = name.split("@", 2);
5459
6082
  const entry = findMind(baseName);
@@ -5530,7 +6153,7 @@ var app25 = new Hono25().post("/:name/chat", zValidator9("json", chatSchema), as
5530
6153
  return c.json({ error: "Conversation not found" }, 404);
5531
6154
  }
5532
6155
  return streamSSE6(c, async (stream) => {
5533
- const unsubscribe = subscribe3(conversationId, (event) => {
6156
+ const unsubscribe = subscribe2(conversationId, (event) => {
5534
6157
  stream.writeSSE({ data: JSON.stringify(event) }).catch((err) => {
5535
6158
  if (!stream.aborted) console.error("[chat] SSE write error:", err);
5536
6159
  });
@@ -5540,23 +6163,23 @@ var app25 = new Hono25().post("/:name/chat", zValidator9("json", chatSchema), as
5540
6163
  if (!stream.aborted) console.error("[chat] SSE ping error:", err);
5541
6164
  });
5542
6165
  }, 15e3);
5543
- await new Promise((resolve19) => {
6166
+ await new Promise((resolve20) => {
5544
6167
  stream.onAbort(() => {
5545
6168
  unsubscribe();
5546
6169
  clearInterval(keepAlive);
5547
- resolve19();
6170
+ resolve20();
5548
6171
  });
5549
6172
  });
5550
6173
  });
5551
6174
  });
5552
- var unifiedChatSchema2 = z9.object({
5553
- message: z9.string().optional(),
5554
- conversationId: z9.string(),
5555
- images: z9.array(z9.object({ media_type: z9.string(), data: z9.string() })).optional()
6175
+ var unifiedChatSchema2 = z11.object({
6176
+ message: z11.string().optional(),
6177
+ conversationId: z11.string(),
6178
+ images: z11.array(z11.object({ media_type: z11.string(), data: z11.string() })).optional()
5556
6179
  });
5557
- var unifiedChatApp = new Hono25().post(
6180
+ var unifiedChatApp = new Hono27().post(
5558
6181
  "/chat",
5559
- zValidator9("json", unifiedChatSchema2),
6182
+ zValidator11("json", unifiedChatSchema2),
5560
6183
  async (c) => {
5561
6184
  const user = c.get("user");
5562
6185
  const body = c.req.valid("json");
@@ -5590,18 +6213,18 @@ var unifiedChatApp = new Hono25().post(
5590
6213
  return c.json({ ok: true, conversationId: body.conversationId });
5591
6214
  }
5592
6215
  );
5593
- var chat_default2 = app25;
6216
+ var chat_default2 = app27;
5594
6217
 
5595
6218
  // src/web/api/volute/conversations.ts
5596
- import { zValidator as zValidator10 } from "@hono/zod-validator";
5597
- import { Hono as Hono26 } from "hono";
5598
- import { z as z10 } from "zod";
5599
- var createConvSchema = z10.object({
5600
- title: z10.string().optional(),
5601
- participantIds: z10.array(z10.number()).optional(),
5602
- participantNames: z10.array(z10.string()).optional()
6219
+ import { zValidator as zValidator12 } from "@hono/zod-validator";
6220
+ import { Hono as Hono28 } from "hono";
6221
+ import { z as z12 } from "zod";
6222
+ var createConvSchema = z12.object({
6223
+ title: z12.string().optional(),
6224
+ participantIds: z12.array(z12.number()).optional(),
6225
+ participantNames: z12.array(z12.string()).optional()
5603
6226
  });
5604
- var app26 = new Hono26().get("/:name/conversations", async (c) => {
6227
+ var app28 = new Hono28().get("/:name/conversations", async (c) => {
5605
6228
  const name = c.req.param("name");
5606
6229
  const user = c.get("user");
5607
6230
  let lookupId = user.id;
@@ -5612,7 +6235,7 @@ var app26 = new Hono26().get("/:name/conversations", async (c) => {
5612
6235
  const all = await listConversationsForUser(lookupId);
5613
6236
  const convs = all.filter((c2) => c2.mind_name === name || c2.type === "channel");
5614
6237
  return c.json(convs);
5615
- }).post("/:name/conversations", zValidator10("json", createConvSchema), async (c) => {
6238
+ }).post("/:name/conversations", zValidator12("json", createConvSchema), async (c) => {
5616
6239
  const name = c.req.param("name");
5617
6240
  const user = c.get("user");
5618
6241
  const body = c.req.valid("json");
@@ -5686,18 +6309,18 @@ var app26 = new Hono26().get("/:name/conversations", async (c) => {
5686
6309
  if (!deleted) return c.json({ error: "Conversation not found" }, 404);
5687
6310
  return c.json({ ok: true });
5688
6311
  });
5689
- var conversations_default2 = app26;
6312
+ var conversations_default2 = app28;
5690
6313
 
5691
6314
  // src/web/api/volute/user-conversations.ts
5692
- import { zValidator as zValidator11 } from "@hono/zod-validator";
5693
- import { Hono as Hono27 } from "hono";
6315
+ import { zValidator as zValidator13 } from "@hono/zod-validator";
6316
+ import { Hono as Hono29 } from "hono";
5694
6317
  import { streamSSE as streamSSE7 } from "hono/streaming";
5695
- import { z as z11 } from "zod";
5696
- var createSchema3 = z11.object({
5697
- title: z11.string().optional(),
5698
- participantNames: z11.array(z11.string()).min(1)
6318
+ import { z as z13 } from "zod";
6319
+ var createSchema4 = z13.object({
6320
+ title: z13.string().optional(),
6321
+ participantNames: z13.array(z13.string()).min(1)
5699
6322
  });
5700
- var app27 = new Hono27().use("*", authMiddleware).get("/", async (c) => {
6323
+ var app29 = new Hono29().use("*", authMiddleware).get("/", async (c) => {
5701
6324
  const user = c.get("user");
5702
6325
  const convs = await listConversationsWithParticipants(user.id);
5703
6326
  return c.json(convs);
@@ -5709,7 +6332,7 @@ var app27 = new Hono27().use("*", authMiddleware).get("/", async (c) => {
5709
6332
  }
5710
6333
  const msgs = await getMessages(id);
5711
6334
  return c.json(msgs);
5712
- }).post("/", zValidator11("json", createSchema3), async (c) => {
6335
+ }).post("/", zValidator13("json", createSchema4), async (c) => {
5713
6336
  const user = c.get("user");
5714
6337
  const body = c.req.valid("json");
5715
6338
  const participantIds = /* @__PURE__ */ new Set();
@@ -5746,7 +6369,7 @@ var app27 = new Hono27().use("*", authMiddleware).get("/", async (c) => {
5746
6369
  return c.json({ error: "Conversation not found" }, 404);
5747
6370
  }
5748
6371
  return streamSSE7(c, async (stream) => {
5749
- const unsubscribe = subscribe3(conversationId, (event) => {
6372
+ const unsubscribe = subscribe2(conversationId, (event) => {
5750
6373
  stream.writeSSE({ data: JSON.stringify(event) }).catch((err) => {
5751
6374
  if (!stream.aborted) console.error("[chat] SSE write error:", err);
5752
6375
  });
@@ -5756,11 +6379,11 @@ var app27 = new Hono27().use("*", authMiddleware).get("/", async (c) => {
5756
6379
  if (!stream.aborted) console.error("[chat] SSE ping error:", err);
5757
6380
  });
5758
6381
  }, 15e3);
5759
- await new Promise((resolve19) => {
6382
+ await new Promise((resolve20) => {
5760
6383
  stream.onAbort(() => {
5761
6384
  unsubscribe();
5762
6385
  clearInterval(keepAlive);
5763
- resolve19();
6386
+ resolve20();
5764
6387
  });
5765
6388
  });
5766
6389
  });
@@ -5771,12 +6394,12 @@ var app27 = new Hono27().use("*", authMiddleware).get("/", async (c) => {
5771
6394
  if (!deleted) return c.json({ error: "Conversation not found" }, 404);
5772
6395
  return c.json({ ok: true });
5773
6396
  });
5774
- var user_conversations_default = app27;
6397
+ var user_conversations_default = app29;
5775
6398
 
5776
6399
  // src/web/app.ts
5777
6400
  var httpLog = logger_default.child("http");
5778
- var app28 = new Hono28();
5779
- app28.onError((err, c) => {
6401
+ var app30 = new Hono30();
6402
+ app30.onError((err, c) => {
5780
6403
  if (err instanceof HTTPException) {
5781
6404
  return err.getResponse();
5782
6405
  }
@@ -5787,10 +6410,10 @@ app28.onError((err, c) => {
5787
6410
  });
5788
6411
  return c.json({ error: "Internal server error" }, 500);
5789
6412
  });
5790
- app28.notFound((c) => {
6413
+ app30.notFound((c) => {
5791
6414
  return c.json({ error: "Not found" }, 404);
5792
6415
  });
5793
- app28.use("*", async (c, next) => {
6416
+ app30.use("*", async (c, next) => {
5794
6417
  const start = Date.now();
5795
6418
  await next();
5796
6419
  const duration = Date.now() - start;
@@ -5801,7 +6424,7 @@ app28.use("*", async (c, next) => {
5801
6424
  httpLog.debug("request", data);
5802
6425
  }
5803
6426
  });
5804
- app28.get("/api/health", (c) => {
6427
+ app30.get("/api/health", (c) => {
5805
6428
  let version = "unknown";
5806
6429
  let cached = null;
5807
6430
  try {
@@ -5816,38 +6439,40 @@ app28.get("/api/health", (c) => {
5816
6439
  ...cached?.updateAvailable ? { updateAvailable: true, latest: cached.latest } : {}
5817
6440
  });
5818
6441
  });
5819
- app28.use("/api/*", bodyLimit({ maxSize: 10 * 1024 * 1024 }));
5820
- app28.use("/api/*", csrf());
5821
- app28.use("/api/activity/*", authMiddleware);
5822
- app28.use("/api/minds/*", authMiddleware);
5823
- app28.use("/api/conversations/*", authMiddleware);
5824
- app28.use("/api/volute/*", authMiddleware);
5825
- app28.use("/api/system/*", authMiddleware);
5826
- app28.use("/api/env/*", authMiddleware);
5827
- app28.use("/api/prompts/*", authMiddleware);
5828
- app28.use("/api/skills/*", authMiddleware);
5829
- app28.use("/api/v1/*", authMiddleware);
5830
- app28.route("/pages", pages_default);
5831
- var routes = app28.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", connectors_default).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/v1/conversations", conversations_default).route("/api/v1/events", events_default).route("/api/v1", chat_default);
5832
- app28.route("/api/v1/minds", minds_default);
5833
- app28.route("/api/v1/minds", typing_default);
5834
- app28.route("/api/v1/minds", variants_default);
5835
- app28.route("/api/v1/minds", files_default);
5836
- app28.route("/api/v1/minds", env_default);
5837
- app28.route("/api/v1/minds", mind_skills_default);
5838
- app28.route("/api/v1/minds", connectors_default);
5839
- app28.route("/api/v1/minds", schedules_default);
5840
- app28.route("/api/v1/minds", logs_default);
5841
- app28.route("/api/v1/system", system_default);
5842
- app28.route("/api/v1/system", update_default);
5843
- app28.route("/api/v1/prompts", prompts_default);
5844
- app28.route("/api/v1/skills", skills_default);
5845
- app28.route("/api/v1/env", sharedEnvApp);
5846
- app28.route("/api/v1/channels", channels_default2);
5847
- var app_default = app28;
6442
+ app30.use("/api/*", bodyLimit({ maxSize: 10 * 1024 * 1024 }));
6443
+ app30.use("/api/*", csrf());
6444
+ app30.use("/api/activity/*", authMiddleware);
6445
+ app30.use("/api/minds/*", authMiddleware);
6446
+ app30.use("/api/conversations/*", authMiddleware);
6447
+ app30.use("/api/volute/*", authMiddleware);
6448
+ app30.use("/api/system/*", authMiddleware);
6449
+ app30.use("/api/env/*", authMiddleware);
6450
+ app30.use("/api/prompts/*", authMiddleware);
6451
+ app30.use("/api/skills/*", authMiddleware);
6452
+ app30.use("/api/notes/*", authMiddleware);
6453
+ app30.use("/api/v1/*", authMiddleware);
6454
+ app30.route("/pages", pages_default);
6455
+ app30.route("/public", public_files_default);
6456
+ 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", connectors_default).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/v1/conversations", conversations_default).route("/api/v1/events", events_default).route("/api/v1", chat_default);
6457
+ app30.route("/api/v1/minds", minds_default);
6458
+ app30.route("/api/v1/minds", typing_default);
6459
+ app30.route("/api/v1/minds", variants_default);
6460
+ app30.route("/api/v1/minds", files_default);
6461
+ app30.route("/api/v1/minds", env_default);
6462
+ app30.route("/api/v1/minds", mind_skills_default);
6463
+ app30.route("/api/v1/minds", connectors_default);
6464
+ app30.route("/api/v1/minds", schedules_default);
6465
+ app30.route("/api/v1/minds", logs_default);
6466
+ app30.route("/api/v1/system", system_default);
6467
+ app30.route("/api/v1/system", update_default);
6468
+ app30.route("/api/v1/prompts", prompts_default);
6469
+ app30.route("/api/v1/skills", skills_default);
6470
+ app30.route("/api/v1/env", sharedEnvApp);
6471
+ app30.route("/api/v1/channels", channels_default2);
6472
+ var app_default = app30;
5848
6473
 
5849
6474
  // src/web/server.ts
5850
- var MIME_TYPES2 = {
6475
+ var MIME_TYPES3 = {
5851
6476
  ".html": "text/html",
5852
6477
  ".js": "application/javascript",
5853
6478
  ".css": "text/css",
@@ -5864,7 +6489,7 @@ async function startServer({
5864
6489
  let assetsDir = "";
5865
6490
  let searchDir = dirname(new URL(import.meta.url).pathname);
5866
6491
  for (let i = 0; i < 5; i++) {
5867
- const candidate = resolve17(searchDir, "dist", "web-assets");
6492
+ const candidate = resolve18(searchDir, "dist", "web-assets");
5868
6493
  if (existsSync13(candidate)) {
5869
6494
  assetsDir = candidate;
5870
6495
  break;
@@ -5875,19 +6500,19 @@ async function startServer({
5875
6500
  app_default.get("*", async (c) => {
5876
6501
  const urlPath = new URL(c.req.url).pathname;
5877
6502
  if (urlPath.startsWith("/api/")) return c.notFound();
5878
- const filePath = resolve17(assetsDir, urlPath.slice(1));
6503
+ const filePath = resolve18(assetsDir, urlPath.slice(1));
5879
6504
  if (!filePath.startsWith(assetsDir)) return c.text("Forbidden", 403);
5880
- const s = await stat3(filePath).catch(() => null);
6505
+ const s = await stat4(filePath).catch(() => null);
5881
6506
  if (s?.isFile()) {
5882
- const ext = extname4(filePath);
5883
- const mime = MIME_TYPES2[ext] || "application/octet-stream";
5884
- const body = await readFile3(filePath);
6507
+ const ext = extname5(filePath);
6508
+ const mime = MIME_TYPES3[ext] || "application/octet-stream";
6509
+ const body = await readFile4(filePath);
5885
6510
  return c.body(body, 200, { "Content-Type": mime });
5886
6511
  }
5887
- const indexPath = resolve17(assetsDir, "index.html");
5888
- const indexStat = await stat3(indexPath).catch(() => null);
6512
+ const indexPath = resolve18(assetsDir, "index.html");
6513
+ const indexStat = await stat4(indexPath).catch(() => null);
5889
6514
  if (indexStat?.isFile()) {
5890
- const body = await readFile3(indexPath, "utf-8");
6515
+ const body = await readFile4(indexPath, "utf-8");
5891
6516
  return c.html(body);
5892
6517
  }
5893
6518
  return c.text("Not found", 404);
@@ -5901,10 +6526,10 @@ async function startServer({
5901
6526
  createServer: createHttpsServer,
5902
6527
  serverOptions: { key: tls.key, cert: tls.cert }
5903
6528
  });
5904
- await new Promise((resolve19, reject) => {
6529
+ await new Promise((resolve20, reject) => {
5905
6530
  server2.on("listening", () => {
5906
6531
  logger_default.info("Volute UI running (https)", { hostname, port });
5907
- resolve19();
6532
+ resolve20();
5908
6533
  });
5909
6534
  server2.on("error", (err) => {
5910
6535
  reject(err);
@@ -5912,13 +6537,13 @@ async function startServer({
5912
6537
  });
5913
6538
  const internalPort = port + 1;
5914
6539
  const internalServer = serve({ fetch: app_default.fetch, port: internalPort, hostname: "127.0.0.1" });
5915
- await new Promise((resolve19, reject) => {
6540
+ await new Promise((resolve20, reject) => {
5916
6541
  internalServer.on("listening", () => {
5917
6542
  logger_default.info("Volute API running (http, internal)", {
5918
6543
  hostname: "127.0.0.1",
5919
6544
  port: internalPort
5920
6545
  });
5921
- resolve19();
6546
+ resolve20();
5922
6547
  });
5923
6548
  internalServer.on("error", (err) => {
5924
6549
  reject(err);
@@ -5927,10 +6552,10 @@ async function startServer({
5927
6552
  return { server: server2, internalPort };
5928
6553
  }
5929
6554
  const server = serve({ fetch: app_default.fetch, port, hostname });
5930
- await new Promise((resolve19, reject) => {
6555
+ await new Promise((resolve20, reject) => {
5931
6556
  server.on("listening", () => {
5932
6557
  logger_default.info("Volute API running (http)", { hostname, port });
5933
- resolve19();
6558
+ resolve20();
5934
6559
  });
5935
6560
  server.on("error", (err) => {
5936
6561
  reject(err);
@@ -5941,7 +6566,7 @@ async function startServer({
5941
6566
 
5942
6567
  // src/daemon.ts
5943
6568
  if (!process.env.VOLUTE_HOME) {
5944
- process.env.VOLUTE_HOME = resolve18(homedir2(), ".volute");
6569
+ process.env.VOLUTE_HOME = resolve19(homedir2(), ".volute");
5945
6570
  }
5946
6571
  if (process.env.VOLUTE_TIMEZONE && !process.env.TZ) {
5947
6572
  process.env.TZ = process.env.VOLUTE_TIMEZONE;
@@ -5951,7 +6576,7 @@ async function startDaemon(opts) {
5951
6576
  const myPid = String(process.pid);
5952
6577
  const home = voluteHome();
5953
6578
  if (!opts.foreground) {
5954
- const rotatingLog = new RotatingLog(resolve18(home, "daemon.log"));
6579
+ const rotatingLog = new RotatingLog(resolve19(home, "daemon.log"));
5955
6580
  logger_default.setOutput((line) => rotatingLog.write(`${line}
5956
6581
  `));
5957
6582
  const write = (...args) => rotatingLog.write(`${format(...args)}
@@ -5961,8 +6586,8 @@ async function startDaemon(opts) {
5961
6586
  console.warn = write;
5962
6587
  console.info = write;
5963
6588
  }
5964
- const DAEMON_PID_PATH = resolve18(home, "daemon.pid");
5965
- const DAEMON_JSON_PATH = resolve18(home, "daemon.json");
6589
+ const DAEMON_PID_PATH = resolve19(home, "daemon.pid");
6590
+ const DAEMON_JSON_PATH = resolve19(home, "daemon.json");
5966
6591
  mkdirSync9(home, { recursive: true });
5967
6592
  migrateAgentsToMinds();
5968
6593
  try {
@@ -5971,11 +6596,18 @@ async function startDaemon(opts) {
5971
6596
  logger_default.warn("failed to initialize shared repo", logger_default.errorData(err));
5972
6597
  }
5973
6598
  initRegistryCache();
6599
+ const { initSandbox } = await import("./sandbox-EHGFF52K.js");
6600
+ await initSandbox();
5974
6601
  try {
5975
6602
  await syncBuiltinSkills();
5976
6603
  } catch (err) {
5977
6604
  logger_default.error("failed to sync built-in skills", logger_default.errorData(err));
5978
6605
  }
6606
+ try {
6607
+ await ensureSystemChannel();
6608
+ } catch (err) {
6609
+ logger_default.warn("failed to ensure #system channel", logger_default.errorData(err));
6610
+ }
5979
6611
  const token = process.env.VOLUTE_DAEMON_TOKEN || randomBytes2(32).toString("hex");
5980
6612
  let tls;
5981
6613
  if (opts.tailscale) {
@@ -6021,11 +6653,12 @@ async function startDaemon(opts) {
6021
6653
  const unsubscribeWebhook = initWebhook();
6022
6654
  const registry = readRegistry();
6023
6655
  for (const entry of registry) {
6024
- try {
6025
- migrateDotVoluteDir(entry.name);
6026
- migrateMindState(entry.name);
6027
- } catch (err) {
6028
- logger_default.warn(`failed to migrate state for ${entry.name}`, logger_default.errorData(err));
6656
+ for (const migrate of [migrateDotVoluteDir, migrateMindState, migratePagesDirToPublic]) {
6657
+ try {
6658
+ migrate(entry.name);
6659
+ } catch (err) {
6660
+ logger_default.warn(`failed to migrate state for ${entry.name}`, logger_default.errorData(err));
6661
+ }
6029
6662
  }
6030
6663
  }
6031
6664
  const runningEntries = registry.filter((e) => e.running);
@@ -6075,7 +6708,7 @@ async function startDaemon(opts) {
6075
6708
  });
6076
6709
  await Promise.all(workers);
6077
6710
  }
6078
- import("./cloud-sync-DIU3OCPV.js").then(
6711
+ import("./cloud-sync-NI2K3C7G.js").then(
6079
6712
  ({ consumeQueuedMessages }) => consumeQueuedMessages().catch((err) => {
6080
6713
  logger_default.warn("failed to consume queued cloud messages", logger_default.errorData(err));
6081
6714
  })
@@ -6083,7 +6716,7 @@ async function startDaemon(opts) {
6083
6716
  logger_default.warn("failed to load cloud-sync module", logger_default.errorData(err));
6084
6717
  });
6085
6718
  try {
6086
- const { backfillTemplateHashes, notifyVersionUpdate } = await import("./version-notify-SZ75QRGO.js");
6719
+ const { backfillTemplateHashes, notifyVersionUpdate } = await import("./version-notify-USFZBWMG.js");
6087
6720
  backfillTemplateHashes();
6088
6721
  notifyVersionUpdate().catch((err) => {
6089
6722
  logger_default.warn("failed to send version update notifications", logger_default.errorData(err));
@@ -6097,6 +6730,9 @@ async function startDaemon(opts) {
6097
6730
  cleanExpiredSessions().catch((err) => {
6098
6731
  logger_default.warn("failed to clean expired sessions", logger_default.errorData(err));
6099
6732
  });
6733
+ migrateMindRoles().catch((err) => {
6734
+ logger_default.warn("failed to migrate mind roles", logger_default.errorData(err));
6735
+ });
6100
6736
  logger_default.info(`running on ${hostname}:${port}, pid ${myPid}`);
6101
6737
  function cleanup() {
6102
6738
  try {
@@ -6158,6 +6794,7 @@ if (import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith
6158
6794
  let hostname = "127.0.0.1";
6159
6795
  let foreground = false;
6160
6796
  let tailscale = false;
6797
+ let noSandbox = false;
6161
6798
  for (let i = 2; i < process.argv.length; i++) {
6162
6799
  if (process.argv[i] === "--port" && process.argv[i + 1]) {
6163
6800
  port = parseInt(process.argv[i + 1], 10);
@@ -6169,8 +6806,13 @@ if (import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith
6169
6806
  foreground = true;
6170
6807
  } else if (process.argv[i] === "--tailscale") {
6171
6808
  tailscale = true;
6809
+ } else if (process.argv[i] === "--no-sandbox") {
6810
+ noSandbox = true;
6172
6811
  }
6173
6812
  }
6813
+ if (noSandbox) {
6814
+ process.env.VOLUTE_SANDBOX = "0";
6815
+ }
6174
6816
  startDaemon({ port, hostname, foreground, tailscale });
6175
6817
  }
6176
6818
  export {