volute 0.23.0 → 0.25.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 (88) hide show
  1. package/README.md +5 -5
  2. package/dist/{activity-events-3WHHCOBB.js → activity-events-4O37J7PD.js} +2 -2
  3. package/dist/api.d.ts +419 -19
  4. package/dist/{channel-BOOMFULW.js → channel-HZOSHGNF.js} +1 -1
  5. package/dist/{chunk-QIXPN3OO.js → chunk-2767L2RZ.js} +5 -5
  6. package/dist/{chunk-SGPEZ32F.js → chunk-33XAVCS4.js} +16 -0
  7. package/dist/{chunk-VT5QODNE.js → chunk-3AIBT4TW.js} +4 -3
  8. package/dist/{chunk-A4S7H6G6.js → chunk-BFK6SOEJ.js} +1 -1
  9. package/dist/{chunk-RK627D57.js → chunk-BOTQ25QT.js} +3 -3
  10. package/dist/{chunk-TFS25FIM.js → chunk-DG7TO7EE.js} +31 -3
  11. package/dist/{chunk-HGCDWKSP.js → chunk-E7GOKNOT.js} +1 -1
  12. package/dist/{chunk-ISWZ6QUK.js → chunk-PMX4EIJK.js} +804 -115
  13. package/dist/{chunk-M5CNKH4J.js → chunk-SHSWYG2J.js} +7 -7
  14. package/dist/{chunk-XLC342FO.js → chunk-SIAG3QMM.js} +14 -1
  15. package/dist/{chunk-KFI7TQJ6.js → chunk-TRQEV3CD.js} +9 -5
  16. package/dist/{chunk-JG4CCJOA.js → chunk-ZSH4G2P5.js} +33 -15
  17. package/dist/cli.js +18 -18
  18. package/dist/{cloud-sync-PI47U2LT.js → cloud-sync-PPBBJDY6.js} +7 -9
  19. package/dist/{connector-PYT5UOTZ.js → connector-M6XFI6GM.js} +1 -1
  20. package/dist/{create-WIDA3M4C.js → create-VDQJER52.js} +1 -1
  21. package/dist/{daemon-client-ZHCDL4RS.js → daemon-client-JOVQZ52X.js} +1 -1
  22. package/dist/{daemon-restart-RMGOOGPE.js → daemon-restart-FDNOZEAD.js} +5 -5
  23. package/dist/daemon.js +1047 -981
  24. package/dist/{delete-LOIANQGD.js → delete-2MRR4JX5.js} +1 -1
  25. package/dist/{down-WSUASL5E.js → down-674SX2IZ.js} +2 -2
  26. package/dist/{env-4PHIHTF4.js → env-2FPOZK37.js} +1 -1
  27. package/dist/{export-XD6PJBQP.js → export-IKFAPRAO.js} +1 -1
  28. package/dist/{file-X4L5TTOL.js → file-KT3UIQM3.js} +1 -1
  29. package/dist/{history-HTEKRNID.js → history-46WZN5CN.js} +1 -1
  30. package/dist/{import-EAXTHHXL.js → import-TH26J76F.js} +2 -2
  31. package/dist/{log-SRO5Q6AD.js → log-6SGSSR3D.js} +1 -1
  32. package/dist/{logs-HNTNNBDW.js → logs-HRBONI5I.js} +1 -1
  33. package/dist/{merge-B6SYTGI7.js → merge-KSFJKX6T.js} +1 -1
  34. package/dist/{message-delivery-FHV4NO2F.js → message-delivery-XMGV3FUM.js} +6 -6
  35. package/dist/{mind-BTXR5B3C.js → mind-YVWAHL2A.js} +17 -17
  36. package/dist/{mind-activity-tracker-PGC3DBJ7.js → mind-activity-tracker-NMDDEV3K.js} +3 -3
  37. package/dist/{mind-manager-KMY4GA2J.js → mind-manager-4NDNAYAB.js} +2 -2
  38. package/dist/{mind-sleep-FWRBIFBS.js → mind-sleep-GHPTSAYN.js} +1 -1
  39. package/dist/{mind-wake-LJK2YU5X.js → mind-wake-BJDJFMDF.js} +1 -1
  40. package/dist/{package-CUBJ4PKS.js → package-3HF5MXU2.js} +2 -1
  41. package/dist/{pages-YSTRWJR4.js → pages-Y6DRWUOJ.js} +1 -1
  42. package/dist/{publish-BZNHKUUK.js → publish-EEKTZBHW.js} +1 -1
  43. package/dist/{pull-GRQAXM2E.js → pull-D32SPFVU.js} +1 -1
  44. package/dist/{restart-CIDAKGG2.js → restart-5BMNV7KU.js} +1 -1
  45. package/dist/{schedule-NLR3LZLY.js → schedule-YEFDLVMJ.js} +1 -1
  46. package/dist/{seed-3H2MRREW.js → seed-6FEKB3YC.js} +1 -1
  47. package/dist/{send-RP2TA7SG.js → send-IISDYFCL.js} +1 -1
  48. package/dist/{service-7BFXDI6J.js → service-FASYWLTC.js} +3 -3
  49. package/dist/{setup-SSIIXQMI.js → setup-BMLM2UTK.js} +1 -1
  50. package/dist/{shared-2OGT3NSL.js → shared-LWMNTTZN.js} +4 -4
  51. package/dist/{skill-Q2Y6PQ3L.js → skill-T3EMR6IR.js} +11 -3
  52. package/dist/skills/imagegen/SKILL.md +37 -0
  53. package/dist/skills/imagegen/references/INSTALL.md +13 -0
  54. package/dist/skills/imagegen/scripts/imagegen.ts +136 -0
  55. package/dist/skills/resonance/SKILL.md +73 -0
  56. package/dist/skills/resonance/assets/default-config.json +21 -0
  57. package/dist/skills/resonance/references/INSTALL.md +23 -0
  58. package/dist/skills/resonance/scripts/resonance.ts +1250 -0
  59. package/dist/skills/volute-mind/SKILL.md +94 -4
  60. package/dist/{sleep-manager-2TMQ65E4.js → sleep-manager-RKTFZPD3.js} +6 -6
  61. package/dist/{sprout-UKCYBGHK.js → sprout-QJVGJDSH.js} +3 -3
  62. package/dist/{start-JR6CUUWF.js → start-C7XITZ5O.js} +1 -1
  63. package/dist/{status-5XDGYHKP.js → status-LYS4NUOZ.js} +1 -1
  64. package/dist/{status-H2MKDN6L.js → status-SIRPLEZC.js} +4 -3
  65. package/dist/{stop-VKPGK25U.js → stop-CVKBSLXY.js} +1 -1
  66. package/dist/tailscale-AJ4VL5XK.js +49 -0
  67. package/dist/{up-Z5JRG2M2.js → up-CJ26KQLN.js} +2 -2
  68. package/dist/{update-ELC6MEUT.js → update-7XCZMYBT.js} +7 -7
  69. package/dist/{upgrade-GXW2EQY3.js → upgrade-7RUIXGOO.js} +1 -1
  70. package/dist/{variant-A4I7PHXS.js → variant-UGREB4G5.js} +4 -4
  71. package/dist/{version-notify-LKABEJSA.js → version-notify-AZQMC32A.js} +6 -6
  72. package/dist/web-assets/assets/index-CGPSVu19.js +69 -0
  73. package/dist/web-assets/assets/index-V_rNDsM8.css +1 -0
  74. package/dist/web-assets/favicon.png +0 -0
  75. package/dist/web-assets/index.html +5 -4
  76. package/dist/web-assets/logo.png +0 -0
  77. package/drizzle/0013_user_profiles.sql +3 -0
  78. package/drizzle/0014_conversation_reads.sql +7 -0
  79. package/drizzle/meta/0013_snapshot.json +7 -0
  80. package/drizzle/meta/_journal.json +14 -0
  81. package/package.json +2 -1
  82. package/templates/_base/home/public/.gitkeep +0 -0
  83. package/templates/_base/src/lib/format-prefix.ts +18 -2
  84. package/templates/_base/src/lib/routing.ts +2 -1
  85. package/templates/_base/src/lib/types.ts +8 -0
  86. package/dist/chunk-G5KRTU2F.js +0 -76
  87. package/dist/web-assets/assets/index-CZ26vsyY.js +0 -69
  88. package/dist/web-assets/assets/index-DyyAvJwW.css +0 -1
package/dist/daemon.js CHANGED
@@ -8,10 +8,6 @@ import {
8
8
  sharedPull,
9
9
  sharedStatus
10
10
  } from "./chunk-PHHKNGA3.js";
11
- import {
12
- fireWebhook,
13
- initWebhook
14
- } from "./chunk-G5KRTU2F.js";
15
11
  import {
16
12
  applyInitFiles,
17
13
  composeTemplate,
@@ -21,44 +17,82 @@ import {
21
17
  listFiles
22
18
  } from "./chunk-AKPFNL7L.js";
23
19
  import {
20
+ addMessage,
21
+ approveUser,
22
+ changePassword,
23
+ countAdmins,
24
+ createChannel,
25
+ createConversation,
26
+ createUser,
27
+ deleteConversationForUser,
28
+ deleteMindUser as deleteMindUser2,
29
+ deleteUser,
24
30
  deliverMessage,
25
31
  extractTextContent,
32
+ findDMConversation,
33
+ fireWebhook,
26
34
  getCachedRecentPages,
27
35
  getCachedSites,
36
+ getChannelByName,
28
37
  getConnectorManager,
38
+ getConversation,
29
39
  getDeliveryManager,
30
40
  getMailPoller,
41
+ getMessages,
42
+ getMessagesPaginated,
43
+ getOrCreateMindUser,
44
+ getParticipants,
31
45
  getScheduler,
32
46
  getTokenBudget,
33
47
  getTypingMap,
48
+ getUnreadCounts,
49
+ getUser,
50
+ getUserByUsername,
34
51
  initConnectorManager,
35
52
  initDeliveryManager,
36
53
  initMailPoller,
37
54
  initScheduler,
38
55
  initSleepManager,
39
56
  initTokenBudget,
57
+ initWebhook,
58
+ isParticipant,
59
+ isParticipantOrOwner,
60
+ joinChannel,
61
+ leaveChannel,
62
+ listChannels,
63
+ listConversationsForUser,
64
+ listConversationsWithParticipants,
65
+ listPendingUsers,
66
+ listUsers,
67
+ listUsersByType,
68
+ markConversationRead,
40
69
  publish,
41
70
  publish2,
42
71
  publishTypingForChannels,
43
72
  recordInbound,
73
+ setUserRole,
44
74
  startMindFull,
45
75
  stopAllWatchers,
46
76
  stopMindFull,
47
77
  subscribe as subscribe2,
48
- subscribe2 as subscribe3
49
- } from "./chunk-ISWZ6QUK.js";
78
+ subscribe2 as subscribe3,
79
+ updateUserProfile,
80
+ verifyUser
81
+ } from "./chunk-PMX4EIJK.js";
50
82
  import {
51
- readSystemsConfig
83
+ deleteSystemsConfig,
84
+ readSystemsConfig,
85
+ writeSystemsConfig
52
86
  } from "./chunk-HFCBO2GL.js";
53
87
  import {
54
88
  getActiveMinds,
55
89
  onMindEvent,
56
90
  stopAll
57
- } from "./chunk-HGCDWKSP.js";
91
+ } from "./chunk-E7GOKNOT.js";
58
92
  import {
59
93
  broadcast,
60
94
  subscribe
61
- } from "./chunk-A4S7H6G6.js";
95
+ } from "./chunk-BFK6SOEJ.js";
62
96
  import {
63
97
  PROMPT_DEFAULTS,
64
98
  PROMPT_KEYS,
@@ -69,17 +103,17 @@ import {
69
103
  getPromptIfCustom,
70
104
  initMindManager,
71
105
  substitute
72
- } from "./chunk-M5CNKH4J.js";
106
+ } from "./chunk-SHSWYG2J.js";
73
107
  import {
74
108
  findOpenClawSession,
75
109
  importOpenClawConnectors,
76
110
  importPiSession,
77
111
  parseNameFromIdentity
78
- } from "./chunk-RK627D57.js";
112
+ } from "./chunk-BOTQ25QT.js";
79
113
  import {
80
114
  readVoluteConfig,
81
115
  writeVoluteConfig
82
- } from "./chunk-XLC342FO.js";
116
+ } from "./chunk-SIAG3QMM.js";
83
117
  import {
84
118
  loadMergedEnv,
85
119
  mindEnvPath,
@@ -105,18 +139,15 @@ import {
105
139
  syncBuiltinSkills,
106
140
  uninstallSkill,
107
141
  updateSkill
108
- } from "./chunk-TFS25FIM.js";
142
+ } from "./chunk-DG7TO7EE.js";
109
143
  import {
110
144
  activity,
111
- conversationParticipants,
112
145
  conversations,
113
146
  getDb,
114
- messages,
115
147
  mindHistory,
116
148
  sessions,
117
- systemPrompts,
118
- users
119
- } from "./chunk-SGPEZ32F.js";
149
+ systemPrompts
150
+ } from "./chunk-33XAVCS4.js";
120
151
  import {
121
152
  logBuffer,
122
153
  logger_default
@@ -179,9 +210,9 @@ import {
179
210
 
180
211
  // src/daemon.ts
181
212
  import { randomBytes as randomBytes2 } from "crypto";
182
- import { mkdirSync as mkdirSync8, readFileSync as readFileSync10, unlinkSync, writeFileSync as writeFileSync8 } from "fs";
213
+ import { mkdirSync as mkdirSync9, readFileSync as readFileSync11, unlinkSync, writeFileSync as writeFileSync9 } from "fs";
183
214
  import { homedir as homedir2 } from "os";
184
- import { resolve as resolve17 } from "path";
215
+ import { resolve as resolve19 } from "path";
185
216
  import { format } from "util";
186
217
 
187
218
  // src/lib/migrate-agents-to-minds.ts
@@ -331,6 +362,17 @@ function migrateDotVoluteDir(name) {
331
362
  console.warn(`[migrate] both .volute/ and .mind/ exist for ${name}, skipping rename`);
332
363
  }
333
364
  }
365
+ function migratePagesDirToPublic(name) {
366
+ const dir = mindDir(name);
367
+ const oldPagesDir = resolve2(dir, "home", "pages");
368
+ const newPublicDir = resolve2(dir, "home", "public");
369
+ const newPagesDir = resolve2(newPublicDir, "pages");
370
+ if (existsSync2(oldPagesDir) && !existsSync2(newPagesDir)) {
371
+ mkdirSync(newPublicDir, { recursive: true });
372
+ renameSync2(oldPagesDir, newPagesDir);
373
+ logger_default.info(`migrated pages/ \u2192 public/pages/ for ${name}`);
374
+ }
375
+ }
334
376
  function migrateMindState(name) {
335
377
  const src = resolve2(mindDir(name), ".mind");
336
378
  if (!existsSync2(src)) return;
@@ -359,153 +401,20 @@ function migrateMindState(name) {
359
401
 
360
402
  // src/web/middleware/auth.ts
361
403
  import { timingSafeEqual } from "crypto";
362
- import { eq as eq2, lt } from "drizzle-orm";
404
+ import { eq, lt } from "drizzle-orm";
363
405
  import { getCookie } from "hono/cookie";
364
406
  import { createMiddleware } from "hono/factory";
365
-
366
- // src/lib/auth.ts
367
- import { compareSync, hashSync } from "bcryptjs";
368
- import { and, count, eq } from "drizzle-orm";
369
- async function createUser(username, password) {
370
- const db = await getDb();
371
- const hash = hashSync(password, 10);
372
- const [{ value }] = await db.select({ value: count() }).from(users).where(eq(users.user_type, "brain"));
373
- const role = value === 0 ? "admin" : "pending";
374
- const [result] = await db.insert(users).values({ username, password_hash: hash, role }).returning({
375
- id: users.id,
376
- username: users.username,
377
- role: users.role,
378
- user_type: users.user_type,
379
- created_at: users.created_at
380
- });
381
- return result;
382
- }
383
- async function verifyUser(username, password) {
384
- const db = await getDb();
385
- const row = await db.select().from(users).where(eq(users.username, username)).get();
386
- if (!row) return null;
387
- if (row.user_type === "mind") return null;
388
- if (!compareSync(password, row.password_hash)) return null;
389
- const { password_hash: _, ...user } = row;
390
- return user;
391
- }
392
- async function getUser(id) {
393
- const db = await getDb();
394
- const row = await db.select({
395
- id: users.id,
396
- username: users.username,
397
- role: users.role,
398
- user_type: users.user_type,
399
- created_at: users.created_at
400
- }).from(users).where(eq(users.id, id)).get();
401
- return row ?? null;
402
- }
403
- async function getUserByUsername(username) {
404
- const db = await getDb();
405
- const row = await db.select({
406
- id: users.id,
407
- username: users.username,
408
- role: users.role,
409
- user_type: users.user_type,
410
- created_at: users.created_at
411
- }).from(users).where(eq(users.username, username)).get();
412
- return row ?? null;
413
- }
414
- async function listUsers() {
415
- const db = await getDb();
416
- return db.select({
417
- id: users.id,
418
- username: users.username,
419
- role: users.role,
420
- user_type: users.user_type,
421
- created_at: users.created_at
422
- }).from(users).orderBy(users.created_at).all();
423
- }
424
- async function listPendingUsers() {
425
- const db = await getDb();
426
- return db.select({
427
- id: users.id,
428
- username: users.username,
429
- role: users.role,
430
- user_type: users.user_type,
431
- created_at: users.created_at
432
- }).from(users).where(eq(users.role, "pending")).orderBy(users.created_at).all();
433
- }
434
- async function listUsersByType(userType) {
435
- const db = await getDb();
436
- return db.select({
437
- id: users.id,
438
- username: users.username,
439
- role: users.role,
440
- user_type: users.user_type,
441
- created_at: users.created_at
442
- }).from(users).where(eq(users.user_type, userType)).orderBy(users.created_at).all();
443
- }
444
- async function getOrCreateMindUser(mindName) {
445
- const db = await getDb();
446
- const existing = await db.select({
447
- id: users.id,
448
- username: users.username,
449
- role: users.role,
450
- user_type: users.user_type,
451
- created_at: users.created_at
452
- }).from(users).where(and(eq(users.username, mindName), eq(users.user_type, "mind"))).get();
453
- if (existing) return existing;
454
- try {
455
- const [result] = await db.insert(users).values({
456
- username: mindName,
457
- password_hash: "!mind",
458
- role: "mind",
459
- user_type: "mind"
460
- }).returning({
461
- id: users.id,
462
- username: users.username,
463
- role: users.role,
464
- user_type: users.user_type,
465
- created_at: users.created_at
466
- });
467
- return result;
468
- } catch (err) {
469
- if (err instanceof Error && err.message.includes("UNIQUE constraint")) {
470
- const retried = await db.select({
471
- id: users.id,
472
- username: users.username,
473
- role: users.role,
474
- user_type: users.user_type,
475
- created_at: users.created_at
476
- }).from(users).where(and(eq(users.username, mindName), eq(users.user_type, "mind"))).get();
477
- if (retried) return retried;
478
- }
479
- throw err;
480
- }
481
- }
482
- async function deleteMindUser2(mindName) {
483
- const db = await getDb();
484
- await db.delete(users).where(and(eq(users.username, mindName), eq(users.user_type, "mind")));
485
- }
486
- async function changePassword(userId, currentPassword, newPassword) {
487
- const db = await getDb();
488
- const row = await db.select().from(users).where(eq(users.id, userId)).get();
489
- if (!row) return false;
490
- if (!compareSync(currentPassword, row.password_hash)) return false;
491
- const hash = hashSync(newPassword, 10);
492
- await db.update(users).set({ password_hash: hash }).where(eq(users.id, userId));
493
- return true;
494
- }
495
- async function approveUser(id) {
496
- const db = await getDb();
497
- await db.update(users).set({ role: "user" }).where(and(eq(users.id, id), eq(users.role, "pending")));
498
- }
499
-
500
- // src/web/middleware/auth.ts
501
407
  function isValidDaemonToken(token) {
502
408
  const expected = process.env.VOLUTE_DAEMON_TOKEN;
503
409
  if (!expected || token.length !== expected.length) return false;
504
410
  return timingSafeEqual(Buffer.from(token), Buffer.from(expected));
505
411
  }
506
- var SESSION_MAX_AGE = 864e5;
412
+ var SESSION_MAX_AGE = 365 * 24 * 60 * 60 * 1e3;
507
413
  var SESSION_CACHE_TTL = 5 * 60 * 1e3;
508
414
  var sessionCache = /* @__PURE__ */ new Map();
415
+ function invalidateSessionCache(sessionId) {
416
+ sessionCache.delete(sessionId);
417
+ }
509
418
  async function createSession(userId) {
510
419
  const db = await getDb();
511
420
  const sessionId = crypto.randomUUID();
@@ -515,14 +424,14 @@ async function createSession(userId) {
515
424
  async function deleteSession(sessionId) {
516
425
  sessionCache.delete(sessionId);
517
426
  const db = await getDb();
518
- await db.delete(sessions).where(eq2(sessions.id, sessionId));
427
+ await db.delete(sessions).where(eq(sessions.id, sessionId));
519
428
  }
520
429
  async function getSessionUserId(sessionId) {
521
430
  const db = await getDb();
522
- const row = await db.select().from(sessions).where(eq2(sessions.id, sessionId)).get();
431
+ const row = await db.select().from(sessions).where(eq(sessions.id, sessionId)).get();
523
432
  if (!row) return void 0;
524
433
  if (Date.now() - row.createdAt > SESSION_MAX_AGE) {
525
- await db.delete(sessions).where(eq2(sessions.id, sessionId));
434
+ await db.delete(sessions).where(eq(sessions.id, sessionId));
526
435
  return void 0;
527
436
  }
528
437
  return row.userId;
@@ -544,7 +453,15 @@ var authMiddleware = createMiddleware(async (c, next) => {
544
453
  if (authHeader?.startsWith("Bearer ")) {
545
454
  const token = authHeader.slice(7);
546
455
  if (token && isValidDaemonToken(token)) {
547
- c.set("user", { id: 0, username: "cli", role: "admin", user_type: "brain" });
456
+ c.set("user", {
457
+ id: 0,
458
+ username: "cli",
459
+ role: "admin",
460
+ user_type: "brain",
461
+ display_name: null,
462
+ description: null,
463
+ avatar: null
464
+ });
548
465
  await next();
549
466
  return;
550
467
  }
@@ -575,306 +492,22 @@ var authMiddleware = createMiddleware(async (c, next) => {
575
492
  });
576
493
 
577
494
  // src/web/server.ts
578
- import { existsSync as existsSync12 } from "fs";
579
- import { readFile as readFile3, stat as stat3 } from "fs/promises";
580
- import { dirname, extname as extname3, resolve as resolve16 } from "path";
495
+ import { existsSync as existsSync13 } from "fs";
496
+ import { readFile as readFile4, stat as stat4 } from "fs/promises";
497
+ import { createServer as createHttpsServer } from "https";
498
+ import { dirname, extname as extname5, resolve as resolve18 } from "path";
581
499
  import { serve } from "@hono/node-server";
582
500
 
583
501
  // src/web/app.ts
584
- import { Hono as Hono28 } from "hono";
502
+ import { Hono as Hono29 } from "hono";
585
503
  import { bodyLimit } from "hono/body-limit";
586
504
  import { csrf } from "hono/csrf";
587
505
  import { HTTPException } from "hono/http-exception";
588
506
 
589
507
  // src/web/api/activity.ts
590
- import { desc as desc2 } from "drizzle-orm";
508
+ import { desc } from "drizzle-orm";
591
509
  import { Hono } from "hono";
592
510
  import { streamSSE } from "hono/streaming";
593
-
594
- // src/lib/events/conversations.ts
595
- import { randomUUID } from "crypto";
596
- import { and as and2, desc, eq as eq3, inArray, isNull, lt as lt2, sql } from "drizzle-orm";
597
- async function createConversation(mindName, channel, opts) {
598
- const db = await getDb();
599
- const id = randomUUID();
600
- const type = opts?.type ?? "dm";
601
- const name = opts?.name ?? null;
602
- await db.transaction(async (tx) => {
603
- await tx.insert(conversations).values({
604
- id,
605
- mind_name: mindName,
606
- channel,
607
- type,
608
- name,
609
- user_id: opts?.userId ?? null,
610
- title: opts?.title ?? null
611
- });
612
- if (opts?.participantIds && opts.participantIds.length > 0) {
613
- await tx.insert(conversationParticipants).values(
614
- opts.participantIds.map((uid, i) => ({
615
- conversation_id: id,
616
- user_id: uid,
617
- role: i === 0 ? "owner" : "member"
618
- }))
619
- );
620
- }
621
- });
622
- fireWebhook({
623
- event: "conversation_created",
624
- mind: mindName ?? "",
625
- data: { id, mindName, channel, type, name, title: opts?.title ?? null }
626
- });
627
- return {
628
- id,
629
- mind_name: mindName,
630
- channel,
631
- type,
632
- name,
633
- user_id: opts?.userId ?? null,
634
- title: opts?.title ?? null,
635
- created_at: (/* @__PURE__ */ new Date()).toISOString(),
636
- updated_at: (/* @__PURE__ */ new Date()).toISOString()
637
- };
638
- }
639
- async function getConversation(id) {
640
- const db = await getDb();
641
- const row = await db.select().from(conversations).where(eq3(conversations.id, id)).get();
642
- return row ?? null;
643
- }
644
- async function addParticipant(conversationId, userId, role = "member") {
645
- const db = await getDb();
646
- await db.insert(conversationParticipants).values({
647
- conversation_id: conversationId,
648
- user_id: userId,
649
- role
650
- });
651
- }
652
- async function removeParticipant(conversationId, userId) {
653
- const db = await getDb();
654
- await db.delete(conversationParticipants).where(
655
- and2(
656
- eq3(conversationParticipants.conversation_id, conversationId),
657
- eq3(conversationParticipants.user_id, userId)
658
- )
659
- );
660
- }
661
- async function getParticipants(conversationId) {
662
- const db = await getDb();
663
- const rows = await db.select({
664
- userId: conversationParticipants.user_id,
665
- username: users.username,
666
- userType: users.user_type,
667
- role: conversationParticipants.role
668
- }).from(conversationParticipants).innerJoin(users, eq3(conversationParticipants.user_id, users.id)).where(eq3(conversationParticipants.conversation_id, conversationId)).all();
669
- return rows;
670
- }
671
- async function isParticipant(conversationId, userId) {
672
- const db = await getDb();
673
- const row = await db.select({ user_id: conversationParticipants.user_id }).from(conversationParticipants).where(
674
- and2(
675
- eq3(conversationParticipants.conversation_id, conversationId),
676
- eq3(conversationParticipants.user_id, userId)
677
- )
678
- ).get();
679
- return row != null;
680
- }
681
- async function listConversationsForUser(userId) {
682
- const db = await getDb();
683
- const participantRows = await db.select({ conversation_id: conversationParticipants.conversation_id }).from(conversationParticipants).where(eq3(conversationParticipants.user_id, userId)).all();
684
- if (participantRows.length === 0) return [];
685
- const convIds = participantRows.map((r) => r.conversation_id);
686
- return await db.select().from(conversations).where(inArray(conversations.id, convIds)).orderBy(desc(conversations.updated_at)).all();
687
- }
688
- async function isParticipantOrOwner(conversationId, userId) {
689
- if (await isParticipant(conversationId, userId)) return true;
690
- const db = await getDb();
691
- const row = await db.select().from(conversations).where(and2(eq3(conversations.id, conversationId), eq3(conversations.user_id, userId))).get();
692
- return row != null;
693
- }
694
- async function deleteConversationForUser(id, userId) {
695
- if (!await isParticipantOrOwner(id, userId)) return false;
696
- await deleteConversation(id);
697
- return true;
698
- }
699
- async function addMessage(conversationId, role, senderName, content) {
700
- const db = await getDb();
701
- const serialized = JSON.stringify(content);
702
- const [result] = await db.insert(messages).values({ conversation_id: conversationId, role, sender_name: senderName, content: serialized }).returning({ id: messages.id, created_at: messages.created_at });
703
- await db.update(conversations).set({ updated_at: sql`datetime('now')` }).where(eq3(conversations.id, conversationId));
704
- if (role === "user") {
705
- const firstText = content.find((b) => b.type === "text");
706
- const title = firstText ? firstText.text.slice(0, 80) : "";
707
- if (title) {
708
- await db.update(conversations).set({ title }).where(and2(eq3(conversations.id, conversationId), isNull(conversations.title)));
709
- }
710
- }
711
- const msg = {
712
- id: result.id,
713
- conversation_id: conversationId,
714
- role,
715
- sender_name: senderName,
716
- content,
717
- created_at: result.created_at
718
- };
719
- publish2(conversationId, {
720
- type: "message",
721
- id: msg.id,
722
- role: msg.role,
723
- senderName: msg.sender_name,
724
- content: msg.content,
725
- createdAt: msg.created_at
726
- });
727
- const conv = await db.select({ mind_name: conversations.mind_name }).from(conversations).where(eq3(conversations.id, conversationId)).get();
728
- fireWebhook({
729
- event: "message_created",
730
- mind: conv?.mind_name ?? "",
731
- data: {
732
- conversationId,
733
- messageId: result.id,
734
- role,
735
- senderName,
736
- content: content.filter((b) => b.type !== "image"),
737
- createdAt: result.created_at
738
- }
739
- });
740
- return msg;
741
- }
742
- async function getMessages(conversationId) {
743
- const db = await getDb();
744
- const rows = await db.select().from(messages).where(eq3(messages.conversation_id, conversationId)).orderBy(messages.created_at).all();
745
- return rows.map(parseMessageRow);
746
- }
747
- async function getMessagesPaginated(conversationId, opts) {
748
- const db = await getDb();
749
- const limit = Math.min(Math.max(opts?.limit ?? 50, 1), 100);
750
- const conditions = [eq3(messages.conversation_id, conversationId)];
751
- if (opts?.before != null) {
752
- conditions.push(lt2(messages.id, opts.before));
753
- }
754
- const rows = await db.select().from(messages).where(and2(...conditions)).orderBy(desc(messages.id)).limit(limit + 1).all();
755
- const hasMore = rows.length > limit;
756
- const page = rows.slice(0, limit).reverse();
757
- return {
758
- messages: page.map(parseMessageRow),
759
- hasMore
760
- };
761
- }
762
- function parseMessageRow(row) {
763
- let content;
764
- try {
765
- const parsed = JSON.parse(row.content);
766
- content = Array.isArray(parsed) ? parsed : [{ type: "text", text: row.content }];
767
- } catch {
768
- content = [{ type: "text", text: row.content }];
769
- }
770
- return { ...row, role: row.role, content };
771
- }
772
- async function listConversationsWithParticipants(userId) {
773
- const convs = await listConversationsForUser(userId);
774
- if (convs.length === 0) return [];
775
- const db = await getDb();
776
- const convIds = convs.map((c) => c.id);
777
- const rows = await db.select({
778
- conversationId: conversationParticipants.conversation_id,
779
- userId: users.id,
780
- username: users.username,
781
- userType: users.user_type,
782
- role: conversationParticipants.role
783
- }).from(conversationParticipants).innerJoin(users, eq3(conversationParticipants.user_id, users.id)).where(inArray(conversationParticipants.conversation_id, convIds));
784
- const byConv = /* @__PURE__ */ new Map();
785
- for (const r of rows) {
786
- let arr = byConv.get(r.conversationId);
787
- if (!arr) {
788
- arr = [];
789
- byConv.set(r.conversationId, arr);
790
- }
791
- arr.push({
792
- userId: r.userId,
793
- username: r.username,
794
- userType: r.userType,
795
- role: r.role
796
- });
797
- }
798
- const lastMsgIds = await db.select({
799
- conversationId: messages.conversation_id,
800
- maxId: sql`MAX(${messages.id})`
801
- }).from(messages).where(inArray(messages.conversation_id, convIds)).groupBy(messages.conversation_id);
802
- const byLastMsg = /* @__PURE__ */ new Map();
803
- if (lastMsgIds.length > 0) {
804
- const msgRows = await db.select().from(messages).where(
805
- inArray(
806
- messages.id,
807
- lastMsgIds.map((r) => r.maxId)
808
- )
809
- );
810
- for (const m of msgRows) {
811
- let text = "";
812
- try {
813
- const parsed = JSON.parse(m.content);
814
- const blocks = Array.isArray(parsed) ? parsed : [];
815
- const textBlock = blocks.find((b) => b.type === "text");
816
- if (textBlock && "text" in textBlock) text = textBlock.text;
817
- } catch {
818
- text = m.content;
819
- }
820
- byLastMsg.set(m.conversation_id, {
821
- role: m.role,
822
- senderName: m.sender_name,
823
- text,
824
- createdAt: m.created_at
825
- });
826
- }
827
- }
828
- return convs.map((c) => ({
829
- ...c,
830
- participants: byConv.get(c.id) ?? [],
831
- lastMessage: byLastMsg.get(c.id)
832
- }));
833
- }
834
- async function findDMConversation(mindName, participantIds) {
835
- const db = await getDb();
836
- const mindConvs = await db.select({ id: conversations.id }).from(conversations).where(and2(eq3(conversations.mind_name, mindName), eq3(conversations.type, "dm"))).all();
837
- for (const conv of mindConvs) {
838
- const rows = await db.select({ user_id: conversationParticipants.user_id }).from(conversationParticipants).where(eq3(conversationParticipants.conversation_id, conv.id)).all();
839
- if (rows.length !== 2) continue;
840
- const ids = new Set(rows.map((r) => r.user_id));
841
- if (ids.has(participantIds[0]) && ids.has(participantIds[1])) {
842
- return conv.id;
843
- }
844
- }
845
- return null;
846
- }
847
- async function deleteConversation(id) {
848
- const db = await getDb();
849
- await db.delete(conversations).where(eq3(conversations.id, id));
850
- }
851
- async function createChannel(name, creatorId) {
852
- const participantIds = creatorId ? [creatorId] : [];
853
- return createConversation(null, "volute", {
854
- type: "channel",
855
- name,
856
- title: name,
857
- participantIds
858
- });
859
- }
860
- async function getChannelByName(name) {
861
- const db = await getDb();
862
- const row = await db.select().from(conversations).where(and2(eq3(conversations.name, name), eq3(conversations.type, "channel"))).get();
863
- return row ?? null;
864
- }
865
- async function listChannels() {
866
- const db = await getDb();
867
- return await db.select().from(conversations).where(eq3(conversations.type, "channel")).orderBy(conversations.name).all();
868
- }
869
- async function joinChannel(conversationId, userId) {
870
- if (await isParticipant(conversationId, userId)) return;
871
- await addParticipant(conversationId, userId);
872
- }
873
- async function leaveChannel(conversationId, userId) {
874
- await removeParticipant(conversationId, userId);
875
- }
876
-
877
- // src/web/api/activity.ts
878
511
  var app = new Hono().get("/events", async (c) => {
879
512
  const user = c.get("user");
880
513
  return streamSSE(c, async (stream) => {
@@ -883,7 +516,7 @@ var app = new Hono().get("/events", async (c) => {
883
516
  let recentActivity = [];
884
517
  try {
885
518
  const db = await getDb();
886
- recentActivity = await db.select().from(activity).orderBy(desc2(activity.created_at)).limit(50);
519
+ recentActivity = await db.select().from(activity).orderBy(desc(activity.created_at)).limit(50);
887
520
  recentActivity = recentActivity.map((row) => ({
888
521
  ...row,
889
522
  metadata: row.metadata ? JSON.parse(row.metadata) : null
@@ -933,8 +566,8 @@ var app = new Hono().get("/events", async (c) => {
933
566
  });
934
567
  }, 15e3);
935
568
  cleanups.push(() => clearInterval(keepAlive));
936
- await new Promise((resolve18) => {
937
- stream.onAbort(() => resolve18());
569
+ await new Promise((resolve20) => {
570
+ stream.onAbort(() => resolve20());
938
571
  });
939
572
  } finally {
940
573
  for (const cleanup of cleanups) {
@@ -949,10 +582,18 @@ var app = new Hono().get("/events", async (c) => {
949
582
  var activity_default = app;
950
583
 
951
584
  // src/web/api/auth.ts
585
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync, writeFileSync as writeFileSync2 } from "fs";
586
+ import { extname, resolve as resolve3 } from "path";
952
587
  import { zValidator } from "@hono/zod-validator";
953
588
  import { Hono as Hono2 } from "hono";
954
589
  import { deleteCookie, getCookie as getCookie2, setCookie } from "hono/cookie";
955
590
  import { z } from "zod";
591
+ var SESSION_COOKIE_OPTIONS = {
592
+ path: "/",
593
+ httpOnly: true,
594
+ sameSite: "Lax",
595
+ maxAge: Math.floor(SESSION_MAX_AGE / 1e3)
596
+ };
956
597
  var credentialsSchema = z.object({
957
598
  username: z.string().min(1),
958
599
  password: z.string().min(1)
@@ -961,12 +602,86 @@ var changePasswordSchema = z.object({
961
602
  currentPassword: z.string().min(1),
962
603
  newPassword: z.string().min(1)
963
604
  });
605
+ var profileSchema = z.object({
606
+ display_name: z.string().max(100).nullable().optional(),
607
+ description: z.string().max(500).nullable().optional()
608
+ });
609
+ var AVATAR_MIME = {
610
+ ".png": "image/png",
611
+ ".jpg": "image/jpeg",
612
+ ".jpeg": "image/jpeg",
613
+ ".gif": "image/gif",
614
+ ".webp": "image/webp"
615
+ };
616
+ var MAX_AVATAR_SIZE = 2 * 1024 * 1024;
617
+ function avatarsDir() {
618
+ return resolve3(voluteHome(), "avatars");
619
+ }
964
620
  var authenticated = new Hono2().use(authMiddleware).post("/change-password", zValidator("json", changePasswordSchema), async (c) => {
965
621
  const user = c.get("user");
966
622
  const { currentPassword, newPassword } = c.req.valid("json");
967
623
  const ok = await changePassword(user.id, currentPassword, newPassword);
968
624
  if (!ok) return c.json({ error: "Current password is incorrect" }, 400);
969
625
  return c.json({ ok: true });
626
+ }).put("/profile", zValidator("json", profileSchema), async (c) => {
627
+ const user = c.get("user");
628
+ const body = c.req.valid("json");
629
+ await updateUserProfile(user.id, body);
630
+ const sessionId = getCookie2(c, "volute_session");
631
+ if (sessionId) invalidateSessionCache(sessionId);
632
+ broadcast({
633
+ type: "profile_updated",
634
+ mind: user.username,
635
+ summary: `${user.username} profile updated`
636
+ });
637
+ return c.json({ ok: true });
638
+ }).post("/avatar", async (c) => {
639
+ const user = c.get("user");
640
+ const body = await c.req.parseBody();
641
+ const file = body.file;
642
+ if (!(file instanceof File)) {
643
+ return c.json({ error: "No file uploaded" }, 400);
644
+ }
645
+ if (file.size > MAX_AVATAR_SIZE) {
646
+ return c.json({ error: "File too large (max 2MB)" }, 400);
647
+ }
648
+ const ext = extname(file.name).toLowerCase();
649
+ if (!AVATAR_MIME[ext]) {
650
+ return c.json({ error: "Invalid file type (png, jpg, gif, webp only)" }, 400);
651
+ }
652
+ const dir = avatarsDir();
653
+ mkdirSync2(dir, { recursive: true });
654
+ const filename = `avatar-${user.id}${ext}`;
655
+ const buffer2 = Buffer.from(await file.arrayBuffer());
656
+ writeFileSync2(resolve3(dir, filename), buffer2);
657
+ if (user.avatar && user.avatar !== filename) {
658
+ const oldPath = resolve3(dir, user.avatar);
659
+ rmSync(oldPath, { force: true });
660
+ }
661
+ await updateUserProfile(user.id, { avatar: filename });
662
+ const sessionId = getCookie2(c, "volute_session");
663
+ if (sessionId) invalidateSessionCache(sessionId);
664
+ broadcast({
665
+ type: "profile_updated",
666
+ mind: user.username,
667
+ summary: `${user.username} avatar updated`
668
+ });
669
+ return c.json({ ok: true, avatar: filename });
670
+ }).delete("/avatar", async (c) => {
671
+ const user = c.get("user");
672
+ if (user.avatar) {
673
+ const path = resolve3(avatarsDir(), user.avatar);
674
+ rmSync(path, { force: true });
675
+ }
676
+ await updateUserProfile(user.id, { avatar: null });
677
+ const sessionId = getCookie2(c, "volute_session");
678
+ if (sessionId) invalidateSessionCache(sessionId);
679
+ broadcast({
680
+ type: "profile_updated",
681
+ mind: user.username,
682
+ summary: `${user.username} avatar removed`
683
+ });
684
+ return c.json({ ok: true });
970
685
  });
971
686
  var admin = new Hono2().use(authMiddleware).get("/users", async (c) => {
972
687
  const user = c.get("user");
@@ -988,8 +703,63 @@ var admin = new Hono2().use(authMiddleware).get("/users", async (c) => {
988
703
  const user = c.get("user");
989
704
  if (user.role !== "admin") return c.json({ error: "Forbidden" }, 403);
990
705
  const id = parseInt(c.req.param("id"), 10);
706
+ if (Number.isNaN(id)) return c.json({ error: "Invalid user ID" }, 400);
991
707
  await approveUser(id);
992
708
  return c.json({ ok: true });
709
+ }).post(
710
+ "/users/:id/role",
711
+ zValidator("json", z.object({ role: z.enum(["admin", "user"]) })),
712
+ async (c) => {
713
+ const user = c.get("user");
714
+ if (user.role !== "admin") return c.json({ error: "Forbidden" }, 403);
715
+ const id = parseInt(c.req.param("id"), 10);
716
+ if (Number.isNaN(id)) return c.json({ error: "Invalid user ID" }, 400);
717
+ const { role } = c.req.valid("json");
718
+ if (role !== "admin") {
719
+ const adminCount = await countAdmins();
720
+ if (adminCount <= 1) {
721
+ const target = await getUser(id);
722
+ if (target?.role === "admin") {
723
+ return c.json({ error: "Cannot remove the last admin" }, 400);
724
+ }
725
+ }
726
+ }
727
+ await setUserRole(id, role);
728
+ return c.json({ ok: true });
729
+ }
730
+ ).put("/users/:id/profile", zValidator("json", profileSchema), async (c) => {
731
+ const user = c.get("user");
732
+ if (user.role !== "admin") return c.json({ error: "Forbidden" }, 403);
733
+ const id = parseInt(c.req.param("id"), 10);
734
+ if (Number.isNaN(id)) return c.json({ error: "Invalid user ID" }, 400);
735
+ const body = c.req.valid("json");
736
+ await updateUserProfile(id, body);
737
+ const updatedUser = await getUser(id);
738
+ if (updatedUser) {
739
+ broadcast({
740
+ type: "profile_updated",
741
+ mind: updatedUser.username,
742
+ summary: `${updatedUser.username} profile updated`
743
+ });
744
+ }
745
+ return c.json({ ok: true });
746
+ }).delete("/users/:id", async (c) => {
747
+ const user = c.get("user");
748
+ if (user.role !== "admin") return c.json({ error: "Forbidden" }, 403);
749
+ const id = parseInt(c.req.param("id"), 10);
750
+ if (Number.isNaN(id)) return c.json({ error: "Invalid user ID" }, 400);
751
+ if (id === user.id) return c.json({ error: "Cannot delete yourself" }, 400);
752
+ const target = await getUser(id);
753
+ if (!target) return c.json({ error: "User not found" }, 404);
754
+ if (target.role === "admin") {
755
+ const adminCount = await countAdmins();
756
+ if (adminCount <= 1) return c.json({ error: "Cannot delete the last admin" }, 400);
757
+ }
758
+ if (target.user_type === "mind") {
759
+ return c.json({ error: "Use the mind deletion API to delete minds" }, 400);
760
+ }
761
+ await deleteUser(id);
762
+ return c.json({ ok: true });
993
763
  });
994
764
  var app2 = new Hono2().post("/register", zValidator("json", credentialsSchema), async (c) => {
995
765
  const { username, password } = c.req.valid("json");
@@ -1000,7 +770,7 @@ var app2 = new Hono2().post("/register", zValidator("json", credentialsSchema),
1000
770
  const user = await createUser(username, password);
1001
771
  if (user.role === "admin") {
1002
772
  const sessionId = await createSession(user.id);
1003
- setCookie(c, "volute_session", sessionId, { path: "/", httpOnly: true, sameSite: "Lax" });
773
+ setCookie(c, "volute_session", sessionId, SESSION_COOKIE_OPTIONS);
1004
774
  }
1005
775
  return c.json({ id: user.id, username: user.username, role: user.role });
1006
776
  }).post("/login", zValidator("json", credentialsSchema), async (c) => {
@@ -1010,7 +780,7 @@ var app2 = new Hono2().post("/register", zValidator("json", credentialsSchema),
1010
780
  return c.json({ error: "Invalid credentials" }, 401);
1011
781
  }
1012
782
  const sessionId = await createSession(user.id);
1013
- setCookie(c, "volute_session", sessionId, { path: "/", httpOnly: true, sameSite: "Lax" });
783
+ setCookie(c, "volute_session", sessionId, SESSION_COOKIE_OPTIONS);
1014
784
  return c.json({ id: user.id, username: user.username, role: user.role });
1015
785
  }).post("/logout", async (c) => {
1016
786
  const sessionId = getCookie2(c, "volute_session");
@@ -1026,7 +796,32 @@ var app2 = new Hono2().post("/register", zValidator("json", credentialsSchema),
1026
796
  if (userId == null) return c.json({ error: "Not logged in" }, 401);
1027
797
  const user = await getUser(userId);
1028
798
  if (!user) return c.json({ error: "Not logged in" }, 401);
1029
- return c.json({ id: user.id, username: user.username, role: user.role });
799
+ return c.json({
800
+ id: user.id,
801
+ username: user.username,
802
+ role: user.role,
803
+ display_name: user.display_name,
804
+ description: user.description,
805
+ avatar: user.avatar
806
+ });
807
+ }).get("/avatars/:filename", async (c) => {
808
+ const filename = c.req.param("filename");
809
+ if (filename.includes("/") || filename.includes("\\") || filename.includes("..")) {
810
+ return c.json({ error: "Invalid filename" }, 400);
811
+ }
812
+ const dir = avatarsDir();
813
+ const filePath = resolve3(dir, filename);
814
+ if (!filePath.startsWith(`${dir}/`)) return c.json({ error: "Invalid path" }, 400);
815
+ if (!existsSync3(filePath)) return c.json({ error: "Not found" }, 404);
816
+ const ext = extname(filename).toLowerCase();
817
+ const mime = AVATAR_MIME[ext];
818
+ if (!mime) return c.json({ error: "Invalid file type" }, 400);
819
+ const data = readFileSync2(filePath);
820
+ return c.body(data, 200, {
821
+ "Content-Type": mime,
822
+ "Cache-Control": "public, max-age=3600",
823
+ "X-Content-Type-Options": "nosniff"
824
+ });
1030
825
  }).route("/", admin).route("/", authenticated);
1031
826
  var auth_default = app2;
1032
827
 
@@ -1067,8 +862,8 @@ async function read(env, channelSlug, limit) {
1067
862
  if (!res.ok) {
1068
863
  throw new Error(`Discord API error: ${res.status} ${res.statusText}`);
1069
864
  }
1070
- const messages2 = await res.json();
1071
- return messages2.reverse().map((m) => `${m.author.username}: ${m.content}`).join("\n");
865
+ const messages = await res.json();
866
+ return messages.reverse().map((m) => `${m.author.username}: ${m.content}`).join("\n");
1072
867
  }
1073
868
  async function send(env, channelSlug, message, images) {
1074
869
  const token = requireToken(env);
@@ -1300,8 +1095,8 @@ async function listConversations2(env) {
1300
1095
  const userMap = /* @__PURE__ */ new Map();
1301
1096
  const imChannels = data.channels.filter((ch) => ch.is_im && ch.user);
1302
1097
  if (imChannels.length > 0) {
1303
- const users2 = await listUsers3(env);
1304
- for (const u of users2) {
1098
+ const users = await listUsers3(env);
1099
+ for (const u of users) {
1305
1100
  userMap.set(u.id, u.username);
1306
1101
  }
1307
1102
  }
@@ -1496,16 +1291,16 @@ __export(volute_exports, {
1496
1291
  read: () => read4,
1497
1292
  send: () => send4
1498
1293
  });
1499
- import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
1500
- import { resolve as resolve3 } from "path";
1294
+ import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
1295
+ import { resolve as resolve4 } from "path";
1501
1296
  function getDaemonConfig() {
1502
- const configPath2 = resolve3(voluteHome(), "daemon.json");
1503
- if (!existsSync3(configPath2)) {
1297
+ const configPath2 = resolve4(voluteHome(), "daemon.json");
1298
+ if (!existsSync4(configPath2)) {
1504
1299
  throw new Error("Volute daemon is not running");
1505
1300
  }
1506
1301
  let config;
1507
1302
  try {
1508
- config = JSON.parse(readFileSync2(configPath2, "utf-8"));
1303
+ config = JSON.parse(readFileSync3(configPath2, "utf-8"));
1509
1304
  } catch (err) {
1510
1305
  throw new Error(`Failed to parse ${configPath2}: ${err}`);
1511
1306
  }
@@ -1531,8 +1326,8 @@ async function read4(env, channelSlug, limit) {
1531
1326
  if (!res.ok) {
1532
1327
  throw new Error(`Failed to read conversation: ${res.status} ${res.statusText}`);
1533
1328
  }
1534
- const messages2 = await res.json();
1535
- return messages2.slice(-limit).map((m) => {
1329
+ const messages = await res.json();
1330
+ return messages.slice(-limit).map((m) => {
1536
1331
  const text = Array.isArray(m.content) ? m.content.filter((b) => b.type === "text").map((b) => b.text).join("") : m.content;
1537
1332
  return `${m.sender_name ?? m.role}: ${text}`;
1538
1333
  }).join("\n");
@@ -1759,8 +1554,8 @@ var app3 = new Hono3().post("/:name/channels/send", requireAdmin, async (c) => {
1759
1554
  return c.json({ error: `Platform ${platform} does not support listing users` }, 400);
1760
1555
  const env = buildEnv(name);
1761
1556
  try {
1762
- const users2 = await driver.listUsers(env);
1763
- return c.json(users2);
1557
+ const users = await driver.listUsers(env);
1558
+ return c.json(users);
1764
1559
  } catch (err) {
1765
1560
  return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
1766
1561
  }
@@ -1942,14 +1737,14 @@ var sharedEnvApp = new Hono5().get("/", (c) => {
1942
1737
  var env_default = app5;
1943
1738
 
1944
1739
  // src/web/api/file-sharing.ts
1945
- import { readFileSync as readFileSync4, statSync } from "fs";
1946
- import { resolve as resolve5 } from "path";
1740
+ import { readFileSync as readFileSync5, statSync } from "fs";
1741
+ import { resolve as resolve6 } from "path";
1947
1742
  import { Hono as Hono6 } from "hono";
1948
1743
 
1949
1744
  // src/lib/file-sharing.ts
1950
1745
  import { randomBytes } from "crypto";
1951
- import { existsSync as existsSync4, mkdirSync as mkdirSync2, readdirSync as readdirSync2, readFileSync as readFileSync3, rmSync, writeFileSync as writeFileSync2 } from "fs";
1952
- import { basename, join, normalize, resolve as resolve4 } from "path";
1746
+ import { existsSync as existsSync5, mkdirSync as mkdirSync3, readdirSync as readdirSync2, readFileSync as readFileSync4, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
1747
+ import { basename, join, normalize, resolve as resolve5 } from "path";
1953
1748
  function validateFilePath(filePath) {
1954
1749
  if (!filePath) return "File path is required";
1955
1750
  const normalized = normalize(filePath);
@@ -1962,13 +1757,13 @@ function validateFilePath(filePath) {
1962
1757
  return null;
1963
1758
  }
1964
1759
  function configPath(dir) {
1965
- return resolve4(dir, "home", ".config", "file-sharing.json");
1760
+ return resolve5(dir, "home", ".config", "file-sharing.json");
1966
1761
  }
1967
1762
  function readFileSharingConfig(dir) {
1968
1763
  const p = configPath(dir);
1969
- if (!existsSync4(p)) return {};
1764
+ if (!existsSync5(p)) return {};
1970
1765
  try {
1971
- return JSON.parse(readFileSync3(p, "utf-8"));
1766
+ return JSON.parse(readFileSync4(p, "utf-8"));
1972
1767
  } catch (err) {
1973
1768
  console.warn(`[file-sharing] failed to parse config at ${p}:`, err);
1974
1769
  return {};
@@ -1976,8 +1771,8 @@ function readFileSharingConfig(dir) {
1976
1771
  }
1977
1772
  function writeFileSharingConfig(dir, config) {
1978
1773
  const p = configPath(dir);
1979
- mkdirSync2(resolve4(p, ".."), { recursive: true });
1980
- writeFileSync2(p, `${JSON.stringify(config, null, 2)}
1774
+ mkdirSync3(resolve5(p, ".."), { recursive: true });
1775
+ writeFileSync3(p, `${JSON.stringify(config, null, 2)}
1981
1776
  `);
1982
1777
  }
1983
1778
  function isTrustedSender(dir, sender) {
@@ -2000,7 +1795,7 @@ function removeTrust(dir, sender) {
2000
1795
  writeFileSharingConfig(dir, config);
2001
1796
  }
2002
1797
  function pendingDir(receiver) {
2003
- return resolve4(stateDir(receiver), "pending-files");
1798
+ return resolve5(stateDir(receiver), "pending-files");
2004
1799
  }
2005
1800
  function validateId(id) {
2006
1801
  if (!id || id.includes("/") || id.includes("\\") || id.includes("..")) {
@@ -2019,8 +1814,8 @@ function stageFile(receiver, sender, filename, content, originalPath) {
2019
1814
  throw new Error("Invalid sender name");
2020
1815
  }
2021
1816
  const id = generateId(sender);
2022
- const dir = resolve4(pendingDir(receiver), id);
2023
- mkdirSync2(dir, { recursive: true });
1817
+ const dir = resolve5(pendingDir(receiver), id);
1818
+ mkdirSync3(dir, { recursive: true });
2024
1819
  const metadata = {
2025
1820
  id,
2026
1821
  sender,
@@ -2029,22 +1824,22 @@ function stageFile(receiver, sender, filename, content, originalPath) {
2029
1824
  size: content.length,
2030
1825
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
2031
1826
  };
2032
- writeFileSync2(resolve4(dir, "metadata.json"), `${JSON.stringify(metadata, null, 2)}
1827
+ writeFileSync3(resolve5(dir, "metadata.json"), `${JSON.stringify(metadata, null, 2)}
2033
1828
  `);
2034
- writeFileSync2(resolve4(dir, "data"), content);
1829
+ writeFileSync3(resolve5(dir, "data"), content);
2035
1830
  return { id };
2036
1831
  }
2037
1832
  function listPending(receiver) {
2038
1833
  const dir = pendingDir(receiver);
2039
- if (!existsSync4(dir)) return [];
1834
+ if (!existsSync5(dir)) return [];
2040
1835
  const entries = readdirSync2(dir, { withFileTypes: true });
2041
1836
  const result = [];
2042
1837
  for (const entry of entries) {
2043
1838
  if (!entry.isDirectory()) continue;
2044
- const metaPath = resolve4(dir, entry.name, "metadata.json");
2045
- if (!existsSync4(metaPath)) continue;
1839
+ const metaPath = resolve5(dir, entry.name, "metadata.json");
1840
+ if (!existsSync5(metaPath)) continue;
2046
1841
  try {
2047
- result.push(JSON.parse(readFileSync3(metaPath, "utf-8")));
1842
+ result.push(JSON.parse(readFileSync4(metaPath, "utf-8")));
2048
1843
  } catch (err) {
2049
1844
  console.warn(`[file-sharing] skipping malformed pending entry ${entry.name}:`, err);
2050
1845
  }
@@ -2053,10 +1848,10 @@ function listPending(receiver) {
2053
1848
  }
2054
1849
  function getPending(receiver, id) {
2055
1850
  validateId(id);
2056
- const metaPath = resolve4(pendingDir(receiver), id, "metadata.json");
2057
- if (!existsSync4(metaPath)) return null;
1851
+ const metaPath = resolve5(pendingDir(receiver), id, "metadata.json");
1852
+ if (!existsSync5(metaPath)) return null;
2058
1853
  try {
2059
- return JSON.parse(readFileSync3(metaPath, "utf-8"));
1854
+ return JSON.parse(readFileSync4(metaPath, "utf-8"));
2060
1855
  } catch (err) {
2061
1856
  console.warn(`[file-sharing] failed to read pending metadata for ${id}:`, err);
2062
1857
  return null;
@@ -2071,27 +1866,27 @@ function deliverFile(receiverDir, sender, filename, content, inboxPath) {
2071
1866
  if (sender.includes("/") || sender.includes("\\")) {
2072
1867
  throw new Error("Invalid sender name");
2073
1868
  }
2074
- const destDir = resolve4(receiverDir, "home", inbox, sender);
2075
- mkdirSync2(destDir, { recursive: true });
2076
- const destPath = resolve4(destDir, basename(filename));
2077
- writeFileSync2(destPath, content);
1869
+ const destDir = resolve5(receiverDir, "home", inbox, sender);
1870
+ mkdirSync3(destDir, { recursive: true });
1871
+ const destPath = resolve5(destDir, basename(filename));
1872
+ writeFileSync3(destPath, content);
2078
1873
  return join(inbox, sender, basename(filename));
2079
1874
  }
2080
1875
  function acceptPending(receiver, id, receiverDir) {
2081
1876
  const meta = getPending(receiver, id);
2082
1877
  if (!meta) throw new Error(`Pending file not found: ${id}`);
2083
- const dataPath = resolve4(pendingDir(receiver), id, "data");
2084
- const content = readFileSync3(dataPath);
1878
+ const dataPath = resolve5(pendingDir(receiver), id, "data");
1879
+ const content = readFileSync4(dataPath);
2085
1880
  const config = readFileSharingConfig(receiverDir);
2086
1881
  const inboxPath = config.inboxPath ?? "inbox";
2087
1882
  const destPath = deliverFile(receiverDir, meta.sender, meta.filename, content, inboxPath);
2088
- rmSync(resolve4(pendingDir(receiver), id), { recursive: true });
1883
+ rmSync2(resolve5(pendingDir(receiver), id), { recursive: true });
2089
1884
  return { sender: meta.sender, filename: meta.filename, destPath };
2090
1885
  }
2091
1886
  function rejectPending(receiver, id) {
2092
1887
  const meta = getPending(receiver, id);
2093
1888
  if (!meta) throw new Error(`Pending file not found: ${id}`);
2094
- rmSync(resolve4(pendingDir(receiver), id), { recursive: true });
1889
+ rmSync2(resolve5(pendingDir(receiver), id), { recursive: true });
2095
1890
  return { sender: meta.sender, filename: meta.filename };
2096
1891
  }
2097
1892
  function formatFileSize(bytes) {
@@ -2132,21 +1927,21 @@ var app6 = new Hono6().post("/:name/files/send", async (c) => {
2132
1927
  const pathErr = validateFilePath(body.filePath);
2133
1928
  if (pathErr) return c.json({ error: pathErr }, 400);
2134
1929
  const senderDir = mindDir(senderName);
2135
- const filePath = resolve5(senderDir, "home", body.filePath);
2136
- const MAX_FILE_SIZE = 50 * 1024 * 1024;
2137
- const stat4 = statSync(filePath, { throwIfNoEntry: false });
2138
- if (!stat4) return c.json({ error: `File not found: ${body.filePath}` }, 404);
2139
- if (stat4.size > MAX_FILE_SIZE) {
1930
+ const filePath = resolve6(senderDir, "home", body.filePath);
1931
+ const MAX_FILE_SIZE2 = 50 * 1024 * 1024;
1932
+ const stat5 = statSync(filePath, { throwIfNoEntry: false });
1933
+ if (!stat5) return c.json({ error: `File not found: ${body.filePath}` }, 404);
1934
+ if (stat5.size > MAX_FILE_SIZE2) {
2140
1935
  return c.json(
2141
1936
  {
2142
- error: `File too large (${formatFileSize(stat4.size)}, max ${formatFileSize(MAX_FILE_SIZE)})`
1937
+ error: `File too large (${formatFileSize(stat5.size)}, max ${formatFileSize(MAX_FILE_SIZE2)})`
2143
1938
  },
2144
1939
  413
2145
1940
  );
2146
1941
  }
2147
1942
  let content;
2148
1943
  try {
2149
- content = readFileSync4(filePath);
1944
+ content = readFileSync5(filePath);
2150
1945
  } catch {
2151
1946
  return c.json({ error: `File not found: ${body.filePath}` }, 404);
2152
1947
  }
@@ -2248,31 +2043,31 @@ var app6 = new Hono6().post("/:name/files/send", async (c) => {
2248
2043
  var file_sharing_default = app6;
2249
2044
 
2250
2045
  // src/web/api/files.ts
2251
- import { existsSync as existsSync5 } from "fs";
2046
+ import { existsSync as existsSync6 } from "fs";
2252
2047
  import { readdir, readFile, realpath, stat } from "fs/promises";
2253
- import { extname, resolve as resolve6 } from "path";
2048
+ import { extname as extname2, resolve as resolve7 } from "path";
2254
2049
  import { Hono as Hono7 } from "hono";
2255
2050
  var ALLOWED_FILES = /* @__PURE__ */ new Set(["SOUL.md", "MEMORY.md", "CLAUDE.md", "VOLUTE.md"]);
2256
- var AVATAR_MIME = {
2051
+ var AVATAR_MIME2 = {
2257
2052
  ".png": "image/png",
2258
2053
  ".jpg": "image/jpeg",
2259
2054
  ".jpeg": "image/jpeg",
2260
2055
  ".gif": "image/gif",
2261
2056
  ".webp": "image/webp"
2262
2057
  };
2263
- var MAX_AVATAR_SIZE = 2 * 1024 * 1024;
2058
+ var MAX_AVATAR_SIZE2 = 2 * 1024 * 1024;
2264
2059
  var app7 = new Hono7().get("/:name/avatar", async (c) => {
2265
2060
  const name = c.req.param("name");
2266
2061
  const entry = findMind(name);
2267
2062
  if (!entry) return c.json({ error: "Mind not found" }, 404);
2268
2063
  const dir = mindDir(name);
2269
2064
  const config = readVoluteConfig(dir);
2270
- if (!config?.avatar) return c.json({ error: "No avatar configured" }, 404);
2271
- const ext = extname(config.avatar).toLowerCase();
2272
- const mime = AVATAR_MIME[ext];
2065
+ if (!config?.profile?.avatar) return c.json({ error: "No avatar configured" }, 404);
2066
+ const ext = extname2(config.profile.avatar).toLowerCase();
2067
+ const mime = AVATAR_MIME2[ext];
2273
2068
  if (!mime) return c.json({ error: "Invalid avatar extension" }, 400);
2274
- const homeDir = resolve6(dir, "home");
2275
- const avatarPath = resolve6(homeDir, config.avatar);
2069
+ const homeDir = resolve7(dir, "home");
2070
+ const avatarPath = resolve7(homeDir, config.profile.avatar);
2276
2071
  if (!avatarPath.startsWith(`${homeDir}/`)) return c.json({ error: "Invalid avatar path" }, 400);
2277
2072
  let realAvatarPath;
2278
2073
  try {
@@ -2287,7 +2082,7 @@ var app7 = new Hono7().get("/:name/avatar", async (c) => {
2287
2082
  }
2288
2083
  try {
2289
2084
  const fileStat = await stat(realAvatarPath);
2290
- if (fileStat.size > MAX_AVATAR_SIZE) return c.json({ error: "Avatar file too large" }, 400);
2085
+ if (fileStat.size > MAX_AVATAR_SIZE2) return c.json({ error: "Avatar file too large" }, 400);
2291
2086
  const body = await readFile(realAvatarPath);
2292
2087
  return c.body(body, 200, {
2293
2088
  "Content-Type": mime,
@@ -2301,8 +2096,8 @@ var app7 = new Hono7().get("/:name/avatar", async (c) => {
2301
2096
  const entry = findMind(name);
2302
2097
  if (!entry) return c.json({ error: "Mind not found" }, 404);
2303
2098
  const dir = mindDir(name);
2304
- const homeDir = resolve6(dir, "home");
2305
- if (!existsSync5(homeDir)) return c.json({ error: "Home directory missing" }, 404);
2099
+ const homeDir = resolve7(dir, "home");
2100
+ if (!existsSync6(homeDir)) return c.json({ error: "Home directory missing" }, 404);
2306
2101
  const allFiles = await readdir(homeDir);
2307
2102
  const files = allFiles.filter((f) => f.endsWith(".md") && ALLOWED_FILES.has(f));
2308
2103
  return c.json(files);
@@ -2315,8 +2110,8 @@ var app7 = new Hono7().get("/:name/avatar", async (c) => {
2315
2110
  const entry = findMind(name);
2316
2111
  if (!entry) return c.json({ error: "Mind not found" }, 404);
2317
2112
  const dir = mindDir(name);
2318
- const filePath = resolve6(dir, "home", filename);
2319
- if (!existsSync5(filePath)) {
2113
+ const filePath = resolve7(dir, "home", filename);
2114
+ if (!existsSync6(filePath)) {
2320
2115
  return c.json({ error: "File not found" }, 404);
2321
2116
  }
2322
2117
  const content = await readFile(filePath, "utf-8");
@@ -2329,19 +2124,19 @@ import { Hono as Hono8 } from "hono";
2329
2124
 
2330
2125
  // src/lib/identity.ts
2331
2126
  import { createHash, generateKeyPairSync, sign, verify } from "crypto";
2332
- import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
2333
- import { resolve as resolve7 } from "path";
2127
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
2128
+ import { resolve as resolve8 } from "path";
2334
2129
  function generateIdentity(mindDir2) {
2335
- const identityDir = resolve7(mindDir2, ".mind/identity");
2336
- mkdirSync3(identityDir, { recursive: true });
2130
+ const identityDir = resolve8(mindDir2, ".mind/identity");
2131
+ mkdirSync4(identityDir, { recursive: true });
2337
2132
  const { publicKey, privateKey } = generateKeyPairSync("ed25519", {
2338
2133
  publicKeyEncoding: { type: "spki", format: "pem" },
2339
2134
  privateKeyEncoding: { type: "pkcs8", format: "pem" }
2340
2135
  });
2341
- const privatePath = resolve7(identityDir, "private.pem");
2342
- const publicPath = resolve7(identityDir, "public.pem");
2343
- writeFileSync3(privatePath, privateKey, { mode: 384 });
2344
- writeFileSync3(publicPath, publicKey, { mode: 420 });
2136
+ const privatePath = resolve8(identityDir, "private.pem");
2137
+ const publicPath = resolve8(identityDir, "public.pem");
2138
+ writeFileSync4(privatePath, privateKey, { mode: 384 });
2139
+ writeFileSync4(publicPath, publicKey, { mode: 420 });
2345
2140
  const config = readVoluteConfig(mindDir2) ?? {};
2346
2141
  config.identity = {
2347
2142
  privateKey: ".mind/identity/private.pem",
@@ -2354,17 +2149,17 @@ function getPrivateKey(mindDir2) {
2354
2149
  const config = readVoluteConfig(mindDir2);
2355
2150
  const relPath = config?.identity?.privateKey;
2356
2151
  if (!relPath) return null;
2357
- const fullPath = resolve7(mindDir2, relPath);
2358
- if (!existsSync6(fullPath)) return null;
2359
- return readFileSync5(fullPath, "utf-8");
2152
+ const fullPath = resolve8(mindDir2, relPath);
2153
+ if (!existsSync7(fullPath)) return null;
2154
+ return readFileSync6(fullPath, "utf-8");
2360
2155
  }
2361
2156
  function getPublicKey(mindDir2) {
2362
2157
  const config = readVoluteConfig(mindDir2);
2363
2158
  const relPath = config?.identity?.publicKey;
2364
2159
  if (!relPath) return null;
2365
- const fullPath = resolve7(mindDir2, relPath);
2366
- if (!existsSync6(fullPath)) return null;
2367
- return readFileSync5(fullPath, "utf-8");
2160
+ const fullPath = resolve8(mindDir2, relPath);
2161
+ if (!existsSync7(fullPath)) return null;
2162
+ return readFileSync6(fullPath, "utf-8");
2368
2163
  }
2369
2164
  function getFingerprint(publicKeyPem) {
2370
2165
  return createHash("sha256").update(publicKeyPem).digest("hex");
@@ -2417,16 +2212,16 @@ var keys_default = app8;
2417
2212
 
2418
2213
  // src/web/api/logs.ts
2419
2214
  import { spawn } from "child_process";
2420
- import { existsSync as existsSync7 } from "fs";
2421
- import { resolve as resolve8 } from "path";
2215
+ import { existsSync as existsSync8 } from "fs";
2216
+ import { resolve as resolve9 } from "path";
2422
2217
  import { Hono as Hono9 } from "hono";
2423
2218
  import { streamSSE as streamSSE2 } from "hono/streaming";
2424
2219
  var app9 = new Hono9().get("/:name/logs", async (c) => {
2425
2220
  const name = c.req.param("name");
2426
2221
  const entry = findMind(name);
2427
2222
  if (!entry) return c.json({ error: "Mind not found" }, 404);
2428
- const logFile = resolve8(stateDir(name), "logs", "mind.log");
2429
- if (!existsSync7(logFile)) {
2223
+ const logFile = resolve9(stateDir(name), "logs", "mind.log");
2224
+ if (!existsSync8(logFile)) {
2430
2225
  return c.json({ error: "No log file found" }, 404);
2431
2226
  }
2432
2227
  return streamSSE2(c, async (stream) => {
@@ -2444,17 +2239,17 @@ var app9 = new Hono9().get("/:name/logs", async (c) => {
2444
2239
  stream.onAbort(() => {
2445
2240
  tail.kill();
2446
2241
  });
2447
- await new Promise((resolve18) => {
2448
- tail.on("exit", resolve18);
2449
- stream.onAbort(resolve18);
2242
+ await new Promise((resolve20) => {
2243
+ tail.on("exit", resolve20);
2244
+ stream.onAbort(resolve20);
2450
2245
  });
2451
2246
  });
2452
2247
  }).get("/:name/logs/tail", async (c) => {
2453
2248
  const name = c.req.param("name");
2454
2249
  const entry = findMind(name);
2455
2250
  if (!entry) return c.json({ error: "Mind not found" }, 404);
2456
- const logFile = resolve8(stateDir(name), "logs", "mind.log");
2457
- if (!existsSync7(logFile)) {
2251
+ const logFile = resolve9(stateDir(name), "logs", "mind.log");
2252
+ if (!existsSync8(logFile)) {
2458
2253
  return c.json({ error: "No log file found" }, 404);
2459
2254
  }
2460
2255
  const nParam = parseInt(c.req.query("n") ?? "50", 10);
@@ -2464,8 +2259,8 @@ var app9 = new Hono9().get("/:name/logs", async (c) => {
2464
2259
  tail.stdout.on("data", (data) => {
2465
2260
  output += data.toString();
2466
2261
  });
2467
- await new Promise((resolve18) => {
2468
- tail.on("exit", resolve18);
2262
+ await new Promise((resolve20) => {
2263
+ tail.on("exit", resolve20);
2469
2264
  });
2470
2265
  return c.text(output);
2471
2266
  });
@@ -2493,12 +2288,12 @@ var app10 = new Hono10().get("/:name/skills", async (c) => {
2493
2288
  const { skillId } = c.req.valid("json");
2494
2289
  const dir = mindDir(name);
2495
2290
  try {
2496
- await installSkill(name, dir, skillId);
2291
+ const result = await installSkill(name, dir, skillId);
2292
+ return c.json({ ok: true, ...result });
2497
2293
  } catch (e) {
2498
2294
  const msg = e instanceof Error ? e.message : String(e);
2499
2295
  return c.json({ error: msg }, 400);
2500
2296
  }
2501
- return c.json({ ok: true });
2502
2297
  }
2503
2298
  ).post(
2504
2299
  "/:name/skills/update",
@@ -2555,33 +2350,33 @@ var mind_skills_default = app10;
2555
2350
  // src/web/api/minds.ts
2556
2351
  import {
2557
2352
  cpSync,
2558
- existsSync as existsSync9,
2559
- mkdirSync as mkdirSync5,
2353
+ existsSync as existsSync10,
2354
+ mkdirSync as mkdirSync6,
2560
2355
  readdirSync as readdirSync4,
2561
- readFileSync as readFileSync8,
2562
- rmSync as rmSync3,
2563
- writeFileSync as writeFileSync6
2356
+ readFileSync as readFileSync9,
2357
+ rmSync as rmSync4,
2358
+ writeFileSync as writeFileSync7
2564
2359
  } from "fs";
2565
- import { resolve as resolve11 } from "path";
2360
+ import { resolve as resolve12 } from "path";
2566
2361
  import { zValidator as zValidator3 } from "@hono/zod-validator";
2567
- import { and as and3, desc as desc3, eq as eq4, sql as sql2 } from "drizzle-orm";
2362
+ import { and, desc as desc2, eq as eq2, sql } from "drizzle-orm";
2568
2363
  import { Hono as Hono11 } from "hono";
2569
2364
  import { z as z3 } from "zod";
2570
2365
 
2571
2366
  // src/lib/consolidate.ts
2572
- import { readdirSync as readdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
2573
- import { resolve as resolve9 } from "path";
2367
+ import { readdirSync as readdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
2368
+ import { resolve as resolve10 } from "path";
2574
2369
  async function consolidateMemory(mindDir2) {
2575
- const soulPath = resolve9(mindDir2, "home/SOUL.md");
2576
- const memoryPath = resolve9(mindDir2, "home/MEMORY.md");
2577
- const memoryDir = resolve9(mindDir2, "home/memory");
2578
- const soul = readFileSync6(soulPath, "utf-8");
2370
+ const soulPath = resolve10(mindDir2, "home/SOUL.md");
2371
+ const memoryPath = resolve10(mindDir2, "home/MEMORY.md");
2372
+ const memoryDir = resolve10(mindDir2, "home/memory");
2373
+ const soul = readFileSync7(soulPath, "utf-8");
2579
2374
  const logs = [];
2580
2375
  try {
2581
2376
  const files = readdirSync3(memoryDir).filter((f) => /^\d{4}-\d{2}-\d{2}\.md$/.test(f)).sort();
2582
2377
  for (const filename of files) {
2583
2378
  const date = filename.replace(".md", "");
2584
- const content2 = readFileSync6(resolve9(memoryDir, filename), "utf-8").trim();
2379
+ const content2 = readFileSync7(resolve10(memoryDir, filename), "utf-8").trim();
2585
2380
  if (content2) {
2586
2381
  logs.push(`### ${date}
2587
2382
 
@@ -2631,7 +2426,7 @@ ${content2}`);
2631
2426
  const data = await res.json();
2632
2427
  const content = data.content.filter((b) => b.type === "text" && b.text).map((b) => b.text).join("").trim();
2633
2428
  if (content) {
2634
- writeFileSync4(memoryPath, `${content}
2429
+ writeFileSync5(memoryPath, `${content}
2635
2430
  `);
2636
2431
  console.log("MEMORY.md created successfully.");
2637
2432
  } else {
@@ -2640,28 +2435,28 @@ ${content2}`);
2640
2435
  }
2641
2436
 
2642
2437
  // src/lib/convert-session.ts
2643
- import { randomUUID as randomUUID2 } from "crypto";
2644
- import { mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
2438
+ import { randomUUID } from "crypto";
2439
+ import { mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "fs";
2645
2440
  import { homedir } from "os";
2646
- import { resolve as resolve10 } from "path";
2441
+ import { resolve as resolve11 } from "path";
2647
2442
  function convertSession(opts) {
2648
- const lines = readFileSync7(opts.sessionPath, "utf-8").trim().split("\n");
2649
- const sessionId = randomUUID2();
2443
+ const lines = readFileSync8(opts.sessionPath, "utf-8").trim().split("\n");
2444
+ const sessionId = randomUUID();
2650
2445
  const idMap = /* @__PURE__ */ new Map();
2651
- const messages2 = [];
2446
+ const messages = [];
2652
2447
  for (const line of lines) {
2653
2448
  const event = JSON.parse(line);
2654
2449
  if (event.type === "message" && event.message) {
2655
- messages2.push(event);
2450
+ messages.push(event);
2656
2451
  }
2657
2452
  }
2658
2453
  const sdkEvents = [];
2659
2454
  let lastSdkUuid = null;
2660
- for (let i = 0; i < messages2.length; i++) {
2661
- const event = messages2[i];
2455
+ for (let i = 0; i < messages.length; i++) {
2456
+ const event = messages[i];
2662
2457
  const msg = event.message;
2663
2458
  if (msg.role === "user") {
2664
- const uuid = randomUUID2();
2459
+ const uuid = randomUUID();
2665
2460
  idMap.set(event.id, uuid);
2666
2461
  const parentUuid = event.parentId ? idMap.get(event.parentId) ?? null : null;
2667
2462
  const sdkEvent = {
@@ -2685,7 +2480,7 @@ function convertSession(opts) {
2685
2480
  } else if (msg.role === "assistant") {
2686
2481
  const content = convertAssistantContent(msg.content);
2687
2482
  if (content.length === 0) continue;
2688
- const uuid = randomUUID2();
2483
+ const uuid = randomUUID();
2689
2484
  idMap.set(event.id, uuid);
2690
2485
  const parentUuid = event.parentId ? idMap.get(event.parentId) ?? null : null;
2691
2486
  const stopReason = mapStopReason(msg.stopReason);
@@ -2700,12 +2495,12 @@ function convertSession(opts) {
2700
2495
  isSidechain: false,
2701
2496
  userType: "external",
2702
2497
  type: "assistant",
2703
- requestId: `req_imported_${randomUUID2()}`,
2498
+ requestId: `req_imported_${randomUUID()}`,
2704
2499
  message: {
2705
2500
  role: "assistant",
2706
2501
  content,
2707
2502
  type: "message",
2708
- id: `msg_imported_${randomUUID2()}`,
2503
+ id: `msg_imported_${randomUUID()}`,
2709
2504
  model: mapModel(msg.model),
2710
2505
  stop_reason: stopReason,
2711
2506
  stop_sequence: null,
@@ -2719,8 +2514,8 @@ function convertSession(opts) {
2719
2514
  let lastToolResultId = event.id;
2720
2515
  let lastTimestamp = event.timestamp;
2721
2516
  let j = i;
2722
- while (j < messages2.length && messages2[j].message.role === "toolResult") {
2723
- const tr = messages2[j];
2517
+ while (j < messages.length && messages[j].message.role === "toolResult") {
2518
+ const tr = messages[j];
2724
2519
  const trMsg = tr.message;
2725
2520
  lastToolResultId = tr.id;
2726
2521
  lastTimestamp = tr.timestamp;
@@ -2733,7 +2528,7 @@ function convertSession(opts) {
2733
2528
  j++;
2734
2529
  }
2735
2530
  i = j - 1;
2736
- const uuid = randomUUID2();
2531
+ const uuid = randomUUID();
2737
2532
  idMap.set(lastToolResultId, uuid);
2738
2533
  const parentUuid = event.parentId ? idMap.get(event.parentId) ?? null : lastSdkUuid;
2739
2534
  const sdkEvent = {
@@ -2759,10 +2554,10 @@ function convertSession(opts) {
2759
2554
  }
2760
2555
  }
2761
2556
  const projectId = opts.projectDir.replace(/\//g, "-");
2762
- const sdkDir = resolve10(homedir(), ".claude", "projects", projectId);
2763
- mkdirSync4(sdkDir, { recursive: true });
2764
- const sdkPath = resolve10(sdkDir, `${sessionId}.jsonl`);
2765
- writeFileSync5(sdkPath, `${sdkEvents.join("\n")}
2557
+ const sdkDir = resolve11(homedir(), ".claude", "projects", projectId);
2558
+ mkdirSync5(sdkDir, { recursive: true });
2559
+ const sdkPath = resolve11(sdkDir, `${sessionId}.jsonl`);
2560
+ writeFileSync6(sdkPath, `${sdkEvents.join("\n")}
2766
2561
  `);
2767
2562
  console.log(`Converted ${sdkEvents.length} messages \u2192 ${sdkPath}`);
2768
2563
  return sessionId;
@@ -2814,7 +2609,7 @@ function convertAssistantContent(content) {
2814
2609
  }
2815
2610
 
2816
2611
  // src/lib/variant-cleanup.ts
2817
- import { existsSync as existsSync8, rmSync as rmSync2 } from "fs";
2612
+ import { existsSync as existsSync9, rmSync as rmSync3 } from "fs";
2818
2613
  async function cleanupVariant(mindName, variantName, projectRoot, variantPath, opts) {
2819
2614
  if (opts?.stop) {
2820
2615
  try {
@@ -2822,11 +2617,11 @@ async function cleanupVariant(mindName, variantName, projectRoot, variantPath, o
2822
2617
  } catch {
2823
2618
  }
2824
2619
  }
2825
- if (existsSync8(variantPath)) {
2620
+ if (existsSync9(variantPath)) {
2826
2621
  try {
2827
2622
  await gitExec(["worktree", "remove", "--force", variantPath], { cwd: projectRoot });
2828
2623
  } catch {
2829
- rmSync2(variantPath, { recursive: true, force: true });
2624
+ rmSync3(variantPath, { recursive: true, force: true });
2830
2625
  try {
2831
2626
  await gitExec(["worktree", "prune"], { cwd: projectRoot });
2832
2627
  } catch {
@@ -2853,7 +2648,7 @@ async function getMindStatus(name, port) {
2853
2648
  const manager = getMindManager();
2854
2649
  let status = "stopped";
2855
2650
  try {
2856
- const { getSleepManagerIfReady } = await import("./sleep-manager-2TMQ65E4.js");
2651
+ const { getSleepManagerIfReady } = await import("./sleep-manager-RKTFZPD3.js");
2857
2652
  if (getSleepManagerIfReady()?.isSleeping(name)) {
2858
2653
  status = "sleeping";
2859
2654
  }
@@ -2888,9 +2683,9 @@ async function getMindStatus(name, port) {
2888
2683
  return {
2889
2684
  status,
2890
2685
  channels,
2891
- displayName: config?.displayName,
2892
- description: config?.description,
2893
- avatar: config?.avatar
2686
+ displayName: config?.profile?.displayName,
2687
+ description: config?.profile?.description,
2688
+ avatar: config?.profile?.avatar
2894
2689
  };
2895
2690
  }
2896
2691
  var TEMPLATE_BRANCH = "volute/template";
@@ -2911,7 +2706,7 @@ async function initTemplateBranch(projectRoot, composedDir, manifest, mindName,
2911
2706
  await gitExec(["commit", "-m", "initial commit"], opts);
2912
2707
  }
2913
2708
  async function updateTemplateBranch(projectRoot, template, mindName) {
2914
- const tempWorktree = resolve11(projectRoot, ".variants", "_template_update");
2709
+ const tempWorktree = resolve12(projectRoot, ".variants", "_template_update");
2915
2710
  let branchExists = false;
2916
2711
  try {
2917
2712
  await gitExec(["rev-parse", "--verify", TEMPLATE_BRANCH], { cwd: projectRoot });
@@ -2922,8 +2717,8 @@ async function updateTemplateBranch(projectRoot, template, mindName) {
2922
2717
  await gitExec(["worktree", "remove", "--force", tempWorktree], { cwd: projectRoot });
2923
2718
  } catch {
2924
2719
  }
2925
- if (existsSync9(tempWorktree)) {
2926
- rmSync3(tempWorktree, { recursive: true, force: true });
2720
+ if (existsSync10(tempWorktree)) {
2721
+ rmSync4(tempWorktree, { recursive: true, force: true });
2927
2722
  }
2928
2723
  const templatesRoot = findTemplatesRoot();
2929
2724
  const { composedDir, manifest } = composeTemplate(templatesRoot, template);
@@ -2943,9 +2738,9 @@ async function updateTemplateBranch(projectRoot, template, mindName) {
2943
2738
  });
2944
2739
  }
2945
2740
  copyTemplateToDir(composedDir, tempWorktree, mindName, manifest);
2946
- const initDir = resolve11(tempWorktree, ".init");
2947
- if (existsSync9(initDir)) {
2948
- rmSync3(initDir, { recursive: true, force: true });
2741
+ const initDir = resolve12(tempWorktree, ".init");
2742
+ if (existsSync10(initDir)) {
2743
+ rmSync4(initDir, { recursive: true, force: true });
2949
2744
  }
2950
2745
  await gitExec(["add", "-A"], { cwd: tempWorktree });
2951
2746
  try {
@@ -2958,10 +2753,10 @@ async function updateTemplateBranch(projectRoot, template, mindName) {
2958
2753
  await gitExec(["worktree", "remove", "--force", tempWorktree], { cwd: projectRoot });
2959
2754
  } catch {
2960
2755
  }
2961
- if (existsSync9(tempWorktree)) {
2962
- rmSync3(tempWorktree, { recursive: true, force: true });
2756
+ if (existsSync10(tempWorktree)) {
2757
+ rmSync4(tempWorktree, { recursive: true, force: true });
2963
2758
  }
2964
- rmSync3(composedDir, { recursive: true, force: true });
2759
+ rmSync4(composedDir, { recursive: true, force: true });
2965
2760
  }
2966
2761
  }
2967
2762
  async function mergeTemplateBranch(worktreeDir) {
@@ -2984,14 +2779,14 @@ async function mergeTemplateBranch(worktreeDir) {
2984
2779
  async function npmInstallAsMind(cwd, mindName) {
2985
2780
  if (isIsolationEnabled()) {
2986
2781
  const [cmd, args] = wrapForIsolation("npm", ["install"], mindName);
2987
- await exec(cmd, args, { cwd, env: { ...process.env, HOME: resolve11(cwd, "home") } });
2782
+ await exec(cmd, args, { cwd, env: { ...process.env, HOME: resolve12(cwd, "home") } });
2988
2783
  } else {
2989
2784
  await exec("npm", ["install"], { cwd });
2990
2785
  }
2991
2786
  }
2992
2787
  async function importFromArchive(c, tempDir, nameOverride, manifest) {
2993
- const extractedMindDir = resolve11(tempDir, "mind");
2994
- if (!existsSync9(extractedMindDir)) {
2788
+ const extractedMindDir = resolve12(tempDir, "mind");
2789
+ if (!existsSync10(extractedMindDir)) {
2995
2790
  return c.json({ error: "Invalid archive: missing mind/ directory" }, 400);
2996
2791
  }
2997
2792
  if (!manifest?.includes || !manifest.name || !manifest.template) {
@@ -3009,21 +2804,21 @@ async function importFromFullArchive(c, tempDir, extractedMindDir, nameOverride,
3009
2804
  if (findMind(name)) return c.json({ error: `Mind already exists: ${name}` }, 409);
3010
2805
  ensureVoluteHome();
3011
2806
  const dest = mindDir(name);
3012
- if (existsSync9(dest)) return c.json({ error: "Mind directory already exists" }, 409);
2807
+ if (existsSync10(dest)) return c.json({ error: "Mind directory already exists" }, 409);
3013
2808
  try {
3014
2809
  cpSync(extractedMindDir, dest, { recursive: true });
3015
2810
  if (!manifest.includes.identity) {
3016
2811
  generateIdentity(dest);
3017
2812
  }
3018
2813
  const state = stateDir(name);
3019
- mkdirSync5(state, { recursive: true });
3020
- const channelsJson = resolve11(tempDir, "state/channels.json");
3021
- if (existsSync9(channelsJson)) {
3022
- cpSync(channelsJson, resolve11(state, "channels.json"));
2814
+ mkdirSync6(state, { recursive: true });
2815
+ const channelsJson = resolve12(tempDir, "state/channels.json");
2816
+ if (existsSync10(channelsJson)) {
2817
+ cpSync(channelsJson, resolve12(state, "channels.json"));
3023
2818
  }
3024
- const envJson = resolve11(tempDir, "state/env.json");
3025
- if (existsSync9(envJson)) {
3026
- cpSync(envJson, resolve11(state, "env.json"));
2819
+ const envJson = resolve12(tempDir, "state/env.json");
2820
+ if (existsSync10(envJson)) {
2821
+ cpSync(envJson, resolve12(state, "env.json"));
3027
2822
  }
3028
2823
  const port = nextPort();
3029
2824
  addMind(name, port, manifest.stage, manifest.template);
@@ -3032,36 +2827,36 @@ async function importFromFullArchive(c, tempDir, extractedMindDir, nameOverride,
3032
2827
  } catch (err) {
3033
2828
  logger_default.warn(`failed to set template hash for ${name}`, logger_default.errorData(err));
3034
2829
  }
3035
- const homeDir = resolve11(dest, "home");
2830
+ const homeDir = resolve12(dest, "home");
3036
2831
  ensureVoluteGroup();
3037
2832
  createMindUser(name, homeDir);
3038
2833
  chownMindDir(dest, name);
3039
2834
  await npmInstallAsMind(dest, name);
3040
2835
  await importHistoryFromArchive(name, tempDir);
3041
2836
  importSessionsFromArchive(dest, tempDir);
3042
- if (!existsSync9(resolve11(dest, ".git"))) {
2837
+ if (!existsSync10(resolve12(dest, ".git"))) {
3043
2838
  try {
3044
- const env = isIsolationEnabled() ? { ...process.env, HOME: resolve11(dest, "home") } : void 0;
2839
+ const env = isIsolationEnabled() ? { ...process.env, HOME: resolve12(dest, "home") } : void 0;
3045
2840
  await gitExec(["init"], { cwd: dest, mindName: name, env });
3046
2841
  await configureGitIdentity(name, { cwd: dest, mindName: name, env });
3047
2842
  await gitExec(["add", "-A"], { cwd: dest, mindName: name, env });
3048
2843
  await gitExec(["commit", "-m", "import from archive"], { cwd: dest, mindName: name, env });
3049
2844
  } catch (err) {
3050
2845
  logger_default.error(`git setup failed for imported mind ${name}`, logger_default.errorData(err));
3051
- rmSync3(resolve11(dest, ".git"), { recursive: true, force: true });
2846
+ rmSync4(resolve12(dest, ".git"), { recursive: true, force: true });
3052
2847
  }
3053
2848
  }
3054
2849
  chownMindDir(dest, name);
3055
- rmSync3(tempDir, { recursive: true, force: true });
2850
+ rmSync4(tempDir, { recursive: true, force: true });
3056
2851
  return c.json({ ok: true, name, port, message: `Imported mind: ${name} (port ${port})` });
3057
2852
  } catch (err) {
3058
- if (existsSync9(dest)) rmSync3(dest, { recursive: true, force: true });
2853
+ if (existsSync10(dest)) rmSync4(dest, { recursive: true, force: true });
3059
2854
  try {
3060
2855
  removeMind(name);
3061
2856
  } catch (cleanupErr) {
3062
2857
  logger_default.error(`Failed to clean up registry for ${name}`, logger_default.errorData(cleanupErr));
3063
2858
  }
3064
- rmSync3(tempDir, { recursive: true, force: true });
2859
+ rmSync4(tempDir, { recursive: true, force: true });
3065
2860
  return c.json({ error: err instanceof Error ? err.message : "Failed to import mind" }, 500);
3066
2861
  }
3067
2862
  }
@@ -3072,7 +2867,7 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
3072
2867
  if (findMind(name)) return c.json({ error: `Mind already exists: ${name}` }, 409);
3073
2868
  ensureVoluteHome();
3074
2869
  const dest = mindDir(name);
3075
- if (existsSync9(dest)) return c.json({ error: "Mind directory already exists" }, 409);
2870
+ if (existsSync10(dest)) return c.json({ error: "Mind directory already exists" }, 409);
3076
2871
  const templatesRoot = findTemplatesRoot();
3077
2872
  const { composedDir, manifest: templateManifest } = composeTemplate(
3078
2873
  templatesRoot,
@@ -3081,40 +2876,40 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
3081
2876
  try {
3082
2877
  copyTemplateToDir(composedDir, dest, name, templateManifest);
3083
2878
  applyInitFiles(dest);
3084
- const extractedHome = resolve11(extractedMindDir, "home");
3085
- if (existsSync9(extractedHome)) {
3086
- cpSync(extractedHome, resolve11(dest, "home"), { recursive: true });
2879
+ const extractedHome = resolve12(extractedMindDir, "home");
2880
+ if (existsSync10(extractedHome)) {
2881
+ cpSync(extractedHome, resolve12(dest, "home"), { recursive: true });
3087
2882
  }
3088
- const extractedMindInternal = resolve11(extractedMindDir, ".mind");
3089
- if (existsSync9(extractedMindInternal)) {
3090
- cpSync(extractedMindInternal, resolve11(dest, ".mind"), { recursive: true });
2883
+ const extractedMindInternal = resolve12(extractedMindDir, ".mind");
2884
+ if (existsSync10(extractedMindInternal)) {
2885
+ cpSync(extractedMindInternal, resolve12(dest, ".mind"), { recursive: true });
3091
2886
  }
3092
- const identityDir = resolve11(dest, ".mind/identity");
2887
+ const identityDir = resolve12(dest, ".mind/identity");
3093
2888
  let publicKeyPem;
3094
- if (!manifest.includes.identity || !existsSync9(resolve11(identityDir, "private.pem"))) {
2889
+ if (!manifest.includes.identity || !existsSync10(resolve12(identityDir, "private.pem"))) {
3095
2890
  ({ publicKeyPem } = generateIdentity(dest));
3096
2891
  } else {
3097
- publicKeyPem = readFileSync8(resolve11(identityDir, "public.pem"), "utf-8");
2892
+ publicKeyPem = readFileSync9(resolve12(identityDir, "public.pem"), "utf-8");
3098
2893
  }
3099
- const promptsPath = resolve11(dest, "home/.config/prompts.json");
3100
- if (!existsSync9(promptsPath)) {
2894
+ const promptsPath = resolve12(dest, "home/.config/prompts.json");
2895
+ if (!existsSync10(promptsPath)) {
3101
2896
  const mindPrompts = await getMindPromptDefaults();
3102
- writeFileSync6(promptsPath, `${JSON.stringify(mindPrompts, null, 2)}
2897
+ writeFileSync7(promptsPath, `${JSON.stringify(mindPrompts, null, 2)}
3103
2898
  `);
3104
2899
  }
3105
2900
  const state = stateDir(name);
3106
- mkdirSync5(state, { recursive: true });
3107
- const channelsJson = resolve11(tempDir, "state/channels.json");
3108
- if (existsSync9(channelsJson)) {
3109
- cpSync(channelsJson, resolve11(state, "channels.json"));
2901
+ mkdirSync6(state, { recursive: true });
2902
+ const channelsJson = resolve12(tempDir, "state/channels.json");
2903
+ if (existsSync10(channelsJson)) {
2904
+ cpSync(channelsJson, resolve12(state, "channels.json"));
3110
2905
  }
3111
- const envJson = resolve11(tempDir, "state/env.json");
3112
- if (existsSync9(envJson)) {
3113
- cpSync(envJson, resolve11(state, "env.json"));
2906
+ const envJson = resolve12(tempDir, "state/env.json");
2907
+ if (existsSync10(envJson)) {
2908
+ cpSync(envJson, resolve12(state, "env.json"));
3114
2909
  }
3115
2910
  const port = nextPort();
3116
2911
  addMind(name, port, manifest.stage, manifest.template);
3117
- const homeDir = resolve11(dest, "home");
2912
+ const homeDir = resolve12(dest, "home");
3118
2913
  ensureVoluteGroup();
3119
2914
  createMindUser(name, homeDir);
3120
2915
  chownMindDir(dest, name);
@@ -3127,7 +2922,7 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
3127
2922
  await initTemplateBranch(dest, composedDir, templateManifest, name, env);
3128
2923
  } catch (err) {
3129
2924
  logger_default.error(`git setup failed for imported mind ${name}`, logger_default.errorData(err));
3130
- rmSync3(resolve11(dest, ".git"), { recursive: true, force: true });
2925
+ rmSync4(resolve12(dest, ".git"), { recursive: true, force: true });
3131
2926
  gitWarning = "Git setup failed \u2014 variants and upgrades won't be available until git is initialized.";
3132
2927
  }
3133
2928
  try {
@@ -3151,7 +2946,7 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
3151
2946
  publishPublicKey(name, publicKeyPem).catch(
3152
2947
  (err) => logger_default.warn(`failed to publish key for ${name}`, { error: err.message })
3153
2948
  );
3154
- rmSync3(tempDir, { recursive: true, force: true });
2949
+ rmSync4(tempDir, { recursive: true, force: true });
3155
2950
  return c.json({
3156
2951
  ok: true,
3157
2952
  name,
@@ -3162,24 +2957,24 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
3162
2957
  ...skillWarnings.length > 0 && { skillWarnings }
3163
2958
  });
3164
2959
  } catch (err) {
3165
- if (existsSync9(dest)) rmSync3(dest, { recursive: true, force: true });
2960
+ if (existsSync10(dest)) rmSync4(dest, { recursive: true, force: true });
3166
2961
  try {
3167
2962
  removeMind(name);
3168
2963
  } catch (cleanupErr) {
3169
2964
  logger_default.error(`Failed to clean up registry for ${name}`, logger_default.errorData(cleanupErr));
3170
2965
  }
3171
- rmSync3(tempDir, { recursive: true, force: true });
2966
+ rmSync4(tempDir, { recursive: true, force: true });
3172
2967
  return c.json({ error: err instanceof Error ? err.message : "Failed to import mind" }, 500);
3173
2968
  } finally {
3174
- rmSync3(composedDir, { recursive: true, force: true });
2969
+ rmSync4(composedDir, { recursive: true, force: true });
3175
2970
  }
3176
2971
  }
3177
2972
  async function importHistoryFromArchive(name, tempDir) {
3178
- const historyJsonl = resolve11(tempDir, "history.jsonl");
3179
- if (!existsSync9(historyJsonl)) return;
2973
+ const historyJsonl = resolve12(tempDir, "history.jsonl");
2974
+ if (!existsSync10(historyJsonl)) return;
3180
2975
  try {
3181
2976
  const db = await getDb();
3182
- const lines = readFileSync8(historyJsonl, "utf-8").trim().split("\n");
2977
+ const lines = readFileSync9(historyJsonl, "utf-8").trim().split("\n");
3183
2978
  let imported = 0;
3184
2979
  let failed = 0;
3185
2980
  for (const line of lines) {
@@ -3215,13 +3010,13 @@ async function importHistoryFromArchive(name, tempDir) {
3215
3010
  }
3216
3011
  }
3217
3012
  function importSessionsFromArchive(dest, tempDir) {
3218
- const sessionsDir = resolve11(tempDir, "sessions");
3219
- if (!existsSync9(sessionsDir)) return;
3013
+ const sessionsDir = resolve12(tempDir, "sessions");
3014
+ if (!existsSync10(sessionsDir)) return;
3220
3015
  try {
3221
- const destSessions = resolve11(dest, ".mind/sessions");
3222
- mkdirSync5(destSessions, { recursive: true });
3016
+ const destSessions = resolve12(dest, ".mind/sessions");
3017
+ mkdirSync6(destSessions, { recursive: true });
3223
3018
  for (const file of readdirSync4(sessionsDir)) {
3224
- cpSync(resolve11(sessionsDir, file), resolve11(destSessions, file));
3019
+ cpSync(resolve12(sessionsDir, file), resolve12(destSessions, file));
3225
3020
  }
3226
3021
  } catch (err) {
3227
3022
  logger_default.error("Failed to import sessions from archive", logger_default.errorData(err));
@@ -3244,7 +3039,7 @@ var app11 = new Hono11().post("/", requireAdmin, zValidator3("json", createMindS
3244
3039
  if (findMind(name)) return c.json({ error: `Mind already exists: ${name}` }, 409);
3245
3040
  ensureVoluteHome();
3246
3041
  const dest = mindDir(name);
3247
- if (existsSync9(dest)) return c.json({ error: "Mind directory already exists" }, 409);
3042
+ if (existsSync10(dest)) return c.json({ error: "Mind directory already exists" }, 409);
3248
3043
  const templatesRoot = findTemplatesRoot();
3249
3044
  const { composedDir, manifest } = composeTemplate(templatesRoot, template);
3250
3045
  try {
@@ -3254,19 +3049,19 @@ var app11 = new Hono11().post("/", requireAdmin, zValidator3("json", createMindS
3254
3049
  if (body.description) {
3255
3050
  const seedConfig = readVoluteConfig(dest);
3256
3051
  if (!seedConfig) throw new Error("Failed to read volute.json after identity generation");
3257
- seedConfig.description = body.description;
3052
+ seedConfig.profile = { ...seedConfig.profile, description: body.description };
3258
3053
  writeVoluteConfig(dest, seedConfig);
3259
3054
  }
3260
3055
  if (body.model) {
3261
- const configPath2 = resolve11(dest, "home/.config/config.json");
3262
- const existing = existsSync9(configPath2) ? JSON.parse(readFileSync8(configPath2, "utf-8")) : {};
3056
+ const configPath2 = resolve12(dest, "home/.config/config.json");
3057
+ const existing = existsSync10(configPath2) ? JSON.parse(readFileSync9(configPath2, "utf-8")) : {};
3263
3058
  existing.model = body.model;
3264
- writeFileSync6(configPath2, `${JSON.stringify(existing, null, 2)}
3059
+ writeFileSync7(configPath2, `${JSON.stringify(existing, null, 2)}
3265
3060
  `);
3266
3061
  }
3267
3062
  const mindPrompts = await getMindPromptDefaults();
3268
- writeFileSync6(
3269
- resolve11(dest, "home/.config/prompts.json"),
3063
+ writeFileSync7(
3064
+ resolve12(dest, "home/.config/prompts.json"),
3270
3065
  `${JSON.stringify(mindPrompts, null, 2)}
3271
3066
  `
3272
3067
  );
@@ -3277,7 +3072,7 @@ var app11 = new Hono11().post("/", requireAdmin, zValidator3("json", createMindS
3277
3072
  } catch (err) {
3278
3073
  logger_default.warn(`failed to set template hash for ${name}`, logger_default.errorData(err));
3279
3074
  }
3280
- const homeDir = resolve11(dest, "home");
3075
+ const homeDir = resolve12(dest, "home");
3281
3076
  ensureVoluteGroup();
3282
3077
  createMindUser(name, homeDir);
3283
3078
  chownMindDir(dest, name);
@@ -3290,7 +3085,7 @@ var app11 = new Hono11().post("/", requireAdmin, zValidator3("json", createMindS
3290
3085
  await initTemplateBranch(dest, composedDir, manifest, name, env);
3291
3086
  } catch (err) {
3292
3087
  logger_default.error(`git setup failed for ${name}`, logger_default.errorData(err));
3293
- rmSync3(resolve11(dest, ".git"), { recursive: true, force: true });
3088
+ rmSync4(resolve12(dest, ".git"), { recursive: true, force: true });
3294
3089
  gitWarning = "Git setup failed \u2014 variants and upgrades won't be available until git is initialized.";
3295
3090
  }
3296
3091
  try {
@@ -3305,7 +3100,7 @@ The human who planted you described you as: "${body.description}"
3305
3100
  ` : "";
3306
3101
  const seedSoulRaw = body.seedSoul ?? await getPrompt("seed_soul", { name, description: descLine });
3307
3102
  const seedSoul = body.seedSoul ? substitute(seedSoulRaw, { name, description: descLine }) : seedSoulRaw;
3308
- writeFileSync6(resolve11(dest, "home/SOUL.md"), seedSoul);
3103
+ writeFileSync7(resolve12(dest, "home/SOUL.md"), seedSoul);
3309
3104
  }
3310
3105
  const skillSet = body.skills ?? (body.stage === "seed" ? SEED_SKILLS : STANDARD_SKILLS);
3311
3106
  const skillWarnings = [];
@@ -3320,11 +3115,11 @@ The human who planted you described you as: "${body.description}"
3320
3115
  if (body.stage !== "seed") {
3321
3116
  const customSoul = await getPromptIfCustom("default_soul");
3322
3117
  if (customSoul) {
3323
- writeFileSync6(resolve11(dest, "home/SOUL.md"), customSoul.replace(/\{\{name\}\}/g, name));
3118
+ writeFileSync7(resolve12(dest, "home/SOUL.md"), customSoul.replace(/\{\{name\}\}/g, name));
3324
3119
  }
3325
3120
  const customMemory = await getPromptIfCustom("default_memory");
3326
3121
  if (customMemory) {
3327
- writeFileSync6(resolve11(dest, "home/MEMORY.md"), customMemory);
3122
+ writeFileSync7(resolve12(dest, "home/MEMORY.md"), customMemory);
3328
3123
  }
3329
3124
  }
3330
3125
  publishPublicKey(name, publicKeyPem).catch(
@@ -3351,14 +3146,14 @@ The human who planted you described you as: "${body.description}"
3351
3146
  ...skillWarnings.length > 0 && { skillWarnings }
3352
3147
  });
3353
3148
  } catch (err) {
3354
- if (existsSync9(dest)) rmSync3(dest, { recursive: true, force: true });
3149
+ if (existsSync10(dest)) rmSync4(dest, { recursive: true, force: true });
3355
3150
  try {
3356
3151
  removeMind(name);
3357
3152
  } catch {
3358
3153
  }
3359
3154
  return c.json({ error: err instanceof Error ? err.message : "Failed to create mind" }, 500);
3360
3155
  } finally {
3361
- rmSync3(composedDir, { recursive: true, force: true });
3156
+ rmSync4(composedDir, { recursive: true, force: true });
3362
3157
  }
3363
3158
  }).post("/import", requireAdmin, async (c) => {
3364
3159
  let body;
@@ -3371,13 +3166,13 @@ The human who planted you described you as: "${body.description}"
3371
3166
  return importFromArchive(c, body.archivePath, body.name, body.manifest);
3372
3167
  }
3373
3168
  const wsDir = body.workspacePath;
3374
- if (!wsDir || !existsSync9(resolve11(wsDir, "SOUL.md")) || !existsSync9(resolve11(wsDir, "IDENTITY.md"))) {
3169
+ if (!wsDir || !existsSync10(resolve12(wsDir, "SOUL.md")) || !existsSync10(resolve12(wsDir, "IDENTITY.md"))) {
3375
3170
  return c.json({ error: "Invalid workspace: missing SOUL.md or IDENTITY.md" }, 400);
3376
3171
  }
3377
- const soul = readFileSync8(resolve11(wsDir, "SOUL.md"), "utf-8");
3378
- const identity = readFileSync8(resolve11(wsDir, "IDENTITY.md"), "utf-8");
3379
- const userPath = resolve11(wsDir, "USER.md");
3380
- const user = existsSync9(userPath) ? readFileSync8(userPath, "utf-8") : "";
3172
+ const soul = readFileSync9(resolve12(wsDir, "SOUL.md"), "utf-8");
3173
+ const identity = readFileSync9(resolve12(wsDir, "IDENTITY.md"), "utf-8");
3174
+ const userPath = resolve12(wsDir, "USER.md");
3175
+ const user = existsSync10(userPath) ? readFileSync9(userPath, "utf-8") : "";
3381
3176
  const name = body.name ?? parseNameFromIdentity(identity) ?? "imported-mind";
3382
3177
  const template = body.template ?? "claude";
3383
3178
  const nameErr = validateMindName(name);
@@ -3397,33 +3192,33 @@ ${user.trimEnd()}
3397
3192
  ` : "";
3398
3193
  ensureVoluteHome();
3399
3194
  const dest = mindDir(name);
3400
- if (existsSync9(dest)) return c.json({ error: "Mind directory already exists" }, 409);
3195
+ if (existsSync10(dest)) return c.json({ error: "Mind directory already exists" }, 409);
3401
3196
  const templatesRoot = findTemplatesRoot();
3402
3197
  const { composedDir, manifest } = composeTemplate(templatesRoot, template);
3403
3198
  try {
3404
3199
  copyTemplateToDir(composedDir, dest, name, manifest);
3405
3200
  applyInitFiles(dest);
3406
3201
  const { publicKeyPem: importPublicKey } = generateIdentity(dest);
3407
- writeFileSync6(resolve11(dest, "home/SOUL.md"), mergedSoul);
3408
- const wsMemoryPath = resolve11(wsDir, "MEMORY.md");
3409
- const hasMemory = existsSync9(wsMemoryPath);
3202
+ writeFileSync7(resolve12(dest, "home/SOUL.md"), mergedSoul);
3203
+ const wsMemoryPath = resolve12(wsDir, "MEMORY.md");
3204
+ const hasMemory = existsSync10(wsMemoryPath);
3410
3205
  if (hasMemory) {
3411
- const existingMemory = readFileSync8(wsMemoryPath, "utf-8");
3412
- writeFileSync6(
3413
- resolve11(dest, "home/MEMORY.md"),
3206
+ const existingMemory = readFileSync9(wsMemoryPath, "utf-8");
3207
+ writeFileSync7(
3208
+ resolve12(dest, "home/MEMORY.md"),
3414
3209
  `${existingMemory.trimEnd()}${mergedMemoryExtra}`
3415
3210
  );
3416
3211
  } else if (user) {
3417
- writeFileSync6(resolve11(dest, "home/MEMORY.md"), `${user.trimEnd()}
3212
+ writeFileSync7(resolve12(dest, "home/MEMORY.md"), `${user.trimEnd()}
3418
3213
  `);
3419
3214
  }
3420
- const wsMemoryDir = resolve11(wsDir, "memory");
3215
+ const wsMemoryDir = resolve12(wsDir, "memory");
3421
3216
  let dailyLogCount = 0;
3422
- if (existsSync9(wsMemoryDir)) {
3423
- const destMemoryDir = resolve11(dest, "home/memory");
3217
+ if (existsSync10(wsMemoryDir)) {
3218
+ const destMemoryDir = resolve12(dest, "home/memory");
3424
3219
  const files = readdirSync4(wsMemoryDir).filter((f) => f.endsWith(".md"));
3425
3220
  for (const file of files) {
3426
- cpSync(resolve11(wsMemoryDir, file), resolve11(destMemoryDir, file));
3221
+ cpSync(resolve12(wsMemoryDir, file), resolve12(destMemoryDir, file));
3427
3222
  }
3428
3223
  dailyLogCount = files.length;
3429
3224
  }
@@ -3434,7 +3229,7 @@ ${user.trimEnd()}
3434
3229
  } catch (err) {
3435
3230
  logger_default.warn(`failed to set template hash for ${name}`, logger_default.errorData(err));
3436
3231
  }
3437
- const homeDir = resolve11(dest, "home");
3232
+ const homeDir = resolve12(dest, "home");
3438
3233
  ensureVoluteGroup();
3439
3234
  createMindUser(name, homeDir);
3440
3235
  chownMindDir(dest, name);
@@ -3442,20 +3237,20 @@ ${user.trimEnd()}
3442
3237
  if (!hasMemory && dailyLogCount > 0) {
3443
3238
  await consolidateMemory(dest);
3444
3239
  }
3445
- const env = isIsolationEnabled() ? { ...process.env, HOME: resolve11(dest, "home") } : void 0;
3240
+ const env = isIsolationEnabled() ? { ...process.env, HOME: resolve12(dest, "home") } : void 0;
3446
3241
  await gitExec(["init"], { cwd: dest, mindName: name, env });
3447
3242
  await configureGitIdentity(name, { cwd: dest, mindName: name, env });
3448
3243
  await gitExec(["add", "-A"], { cwd: dest, mindName: name, env });
3449
3244
  await gitExec(["commit", "-m", "import from OpenClaw"], { cwd: dest, mindName: name, env });
3450
- const sessionFile = body.sessionPath ? resolve11(body.sessionPath) : findOpenClawSession(wsDir);
3451
- if (sessionFile && existsSync9(sessionFile)) {
3245
+ const sessionFile = body.sessionPath ? resolve12(body.sessionPath) : findOpenClawSession(wsDir);
3246
+ if (sessionFile && existsSync10(sessionFile)) {
3452
3247
  if (template === "pi") {
3453
3248
  importPiSession(sessionFile, dest);
3454
3249
  } else if (template === "claude") {
3455
3250
  const sessionId = convertSession({ sessionPath: sessionFile, projectDir: dest });
3456
- const mindRuntimeDir = resolve11(dest, ".mind");
3457
- mkdirSync5(mindRuntimeDir, { recursive: true });
3458
- writeFileSync6(resolve11(mindRuntimeDir, "session.json"), JSON.stringify({ sessionId }));
3251
+ const mindRuntimeDir = resolve12(dest, ".mind");
3252
+ mkdirSync6(mindRuntimeDir, { recursive: true });
3253
+ writeFileSync7(resolve12(mindRuntimeDir, "session.json"), JSON.stringify({ sessionId }));
3459
3254
  }
3460
3255
  }
3461
3256
  importOpenClawConnectors(name, dest);
@@ -3470,14 +3265,14 @@ ${user.trimEnd()}
3470
3265
  );
3471
3266
  return c.json({ ok: true, name, port, message: `Imported mind: ${name} (port ${port})` });
3472
3267
  } catch (err) {
3473
- if (existsSync9(dest)) rmSync3(dest, { recursive: true, force: true });
3268
+ if (existsSync10(dest)) rmSync4(dest, { recursive: true, force: true });
3474
3269
  try {
3475
3270
  removeMind(name);
3476
3271
  } catch {
3477
3272
  }
3478
3273
  return c.json({ error: err instanceof Error ? err.message : "Failed to import mind" }, 500);
3479
3274
  } finally {
3480
- rmSync3(composedDir, { recursive: true, force: true });
3275
+ rmSync4(composedDir, { recursive: true, force: true });
3481
3276
  }
3482
3277
  }).get("/", async (c) => {
3483
3278
  const entries = readRegistry();
@@ -3486,7 +3281,7 @@ ${user.trimEnd()}
3486
3281
  const db = await getDb();
3487
3282
  const lastActiveRows = await db.select({
3488
3283
  mind: mindHistory.mind,
3489
- lastActiveAt: sql2`MAX(${mindHistory.created_at})`
3284
+ lastActiveAt: sql`MAX(${mindHistory.created_at})`
3490
3285
  }).from(mindHistory).groupBy(mindHistory.mind);
3491
3286
  lastActiveMap = new Map(lastActiveRows.map((r) => [r.mind, r.lastActiveAt]));
3492
3287
  } catch {
@@ -3494,7 +3289,7 @@ ${user.trimEnd()}
3494
3289
  const minds = await Promise.all(
3495
3290
  entries.map(async (entry) => {
3496
3291
  const mindStatus = await getMindStatus(entry.name, entry.port);
3497
- const hasPages = existsSync9(resolve11(mindDir(entry.name), "home", "pages"));
3292
+ const hasPages = existsSync10(resolve12(mindDir(entry.name), "home", "public", "pages"));
3498
3293
  return {
3499
3294
  ...entry,
3500
3295
  ...mindStatus,
@@ -3512,7 +3307,7 @@ ${user.trimEnd()}
3512
3307
  const name = c.req.param("name");
3513
3308
  const entry = findMind(name);
3514
3309
  if (!entry) return c.json({ error: "Mind not found" }, 404);
3515
- if (!existsSync9(mindDir(name))) return c.json({ error: "Mind directory missing" }, 404);
3310
+ if (!existsSync10(mindDir(name))) return c.json({ error: "Mind directory missing" }, 404);
3516
3311
  const mindStatus = await getMindStatus(name, entry.port);
3517
3312
  const variants = readVariants(name);
3518
3313
  const manager = getMindManager();
@@ -3527,7 +3322,7 @@ ${user.trimEnd()}
3527
3322
  return { name: v.name, port: v.port, status: variantStatus };
3528
3323
  })
3529
3324
  );
3530
- const hasPages = existsSync9(resolve11(mindDir(name), "home", "pages"));
3325
+ const hasPages = existsSync10(resolve12(mindDir(name), "home", "public", "pages"));
3531
3326
  return c.json({ ...entry, ...mindStatus, variants: variantStatuses, hasPages });
3532
3327
  }).post("/:name/start", requireAdmin, async (c) => {
3533
3328
  const name = c.req.param("name");
@@ -3541,7 +3336,7 @@ ${user.trimEnd()}
3541
3336
  targetPort = variant.port;
3542
3337
  } else {
3543
3338
  const dir = mindDir(baseName);
3544
- if (!existsSync9(dir)) return c.json({ error: "Mind directory missing" }, 404);
3339
+ if (!existsSync10(dir)) return c.json({ error: "Mind directory missing" }, 404);
3545
3340
  }
3546
3341
  if (getMindManager().isRunning(name)) {
3547
3342
  return c.json({ error: "Mind already running" }, 409);
@@ -3564,7 +3359,7 @@ ${user.trimEnd()}
3564
3359
  targetPort = variant.port;
3565
3360
  } else {
3566
3361
  const dir = mindDir(baseName);
3567
- if (!existsSync9(dir)) return c.json({ error: "Mind directory missing" }, 404);
3362
+ if (!existsSync10(dir)) return c.json({ error: "Mind directory missing" }, 404);
3568
3363
  }
3569
3364
  let context;
3570
3365
  const contentType = c.req.header("content-type");
@@ -3591,7 +3386,7 @@ ${user.trimEnd()}
3591
3386
  const variant = findVariant(baseName, mergeVariantName);
3592
3387
  if (variant) {
3593
3388
  const projectRoot = mindDir(baseName);
3594
- if (existsSync9(variant.path)) {
3389
+ if (existsSync10(variant.path)) {
3595
3390
  const status = (await gitExec(["status", "--porcelain"], { cwd: variant.path })).trim();
3596
3391
  if (status) {
3597
3392
  try {
@@ -3627,13 +3422,13 @@ ${user.trimEnd()}
3627
3422
  }
3628
3423
  }
3629
3424
  }
3630
- if (context) {
3425
+ if (context && context.type !== "reload") {
3631
3426
  manager.setPendingContext(name, context);
3632
3427
  }
3633
3428
  if (context?.type === "sprouted" && !variantName) {
3634
3429
  try {
3635
3430
  const db = await getDb();
3636
- const activeConvs = await db.select({ id: conversations.id }).from(conversations).where(eq4(conversations.mind_name, baseName)).all();
3431
+ const activeConvs = await db.select({ id: conversations.id }).from(conversations).where(eq2(conversations.mind_name, baseName)).all();
3637
3432
  for (const conv of activeConvs) {
3638
3433
  await addMessage(conv.id, "assistant", "system", [
3639
3434
  { type: "text", text: "[seed has sprouted]" }
@@ -3671,7 +3466,7 @@ ${user.trimEnd()}
3671
3466
  const name = c.req.param("name");
3672
3467
  const entry = findMind(name);
3673
3468
  if (!entry) return c.json({ error: "Mind not found" }, 404);
3674
- const { getSleepManagerIfReady } = await import("./sleep-manager-2TMQ65E4.js");
3469
+ const { getSleepManagerIfReady } = await import("./sleep-manager-RKTFZPD3.js");
3675
3470
  const sm = getSleepManagerIfReady();
3676
3471
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
3677
3472
  return c.json(sm.getState(name));
@@ -3679,7 +3474,7 @@ ${user.trimEnd()}
3679
3474
  const name = c.req.param("name");
3680
3475
  const entry = findMind(name);
3681
3476
  if (!entry) return c.json({ error: "Mind not found" }, 404);
3682
- const { getSleepManagerIfReady } = await import("./sleep-manager-2TMQ65E4.js");
3477
+ const { getSleepManagerIfReady } = await import("./sleep-manager-RKTFZPD3.js");
3683
3478
  const sm = getSleepManagerIfReady();
3684
3479
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
3685
3480
  if (sm.isSleeping(name)) return c.json({ error: "Mind is already sleeping" }, 409);
@@ -3699,7 +3494,7 @@ ${user.trimEnd()}
3699
3494
  const name = c.req.param("name");
3700
3495
  const entry = findMind(name);
3701
3496
  if (!entry) return c.json({ error: "Mind not found" }, 404);
3702
- const { getSleepManagerIfReady } = await import("./sleep-manager-2TMQ65E4.js");
3497
+ const { getSleepManagerIfReady } = await import("./sleep-manager-RKTFZPD3.js");
3703
3498
  const sm = getSleepManagerIfReady();
3704
3499
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
3705
3500
  if (!sm.isSleeping(name)) return c.json({ error: "Mind is not sleeping" }, 409);
@@ -3709,7 +3504,7 @@ ${user.trimEnd()}
3709
3504
  const name = c.req.param("name");
3710
3505
  const entry = findMind(name);
3711
3506
  if (!entry) return c.json({ error: "Mind not found" }, 404);
3712
- const { getSleepManagerIfReady } = await import("./sleep-manager-2TMQ65E4.js");
3507
+ const { getSleepManagerIfReady } = await import("./sleep-manager-RKTFZPD3.js");
3713
3508
  const sm = getSleepManagerIfReady();
3714
3509
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
3715
3510
  const flushed = await sm.flushQueuedMessages(name);
@@ -3742,11 +3537,11 @@ ${user.trimEnd()}
3742
3537
  removeMind(name);
3743
3538
  await deleteMindUser2(name);
3744
3539
  const state = stateDir(name);
3745
- if (existsSync9(state)) {
3746
- rmSync3(state, { recursive: true, force: true });
3540
+ if (existsSync10(state)) {
3541
+ rmSync4(state, { recursive: true, force: true });
3747
3542
  }
3748
- if (force && existsSync9(dir)) {
3749
- rmSync3(dir, { recursive: true, force: true });
3543
+ if (force && existsSync10(dir)) {
3544
+ rmSync4(dir, { recursive: true, force: true });
3750
3545
  deleteMindUser(name);
3751
3546
  }
3752
3547
  fireWebhook({
@@ -3760,7 +3555,7 @@ ${user.trimEnd()}
3760
3555
  const entry = findMind(mindName);
3761
3556
  if (!entry) return c.json({ error: "Mind not found" }, 404);
3762
3557
  const dir = mindDir(mindName);
3763
- if (!existsSync9(dir)) return c.json({ error: "Mind directory missing" }, 404);
3558
+ if (!existsSync10(dir)) return c.json({ error: "Mind directory missing" }, 404);
3764
3559
  let body = {};
3765
3560
  try {
3766
3561
  body = await c.req.json();
@@ -3769,15 +3564,15 @@ ${user.trimEnd()}
3769
3564
  const template = body.template ?? entry.template ?? "claude";
3770
3565
  const UPGRADE_VARIANT = "upgrade";
3771
3566
  if (body.abort) {
3772
- const worktreeDir2 = resolve11(dir, ".variants", UPGRADE_VARIANT);
3773
- if (!existsSync9(worktreeDir2)) {
3567
+ const worktreeDir2 = resolve12(dir, ".variants", UPGRADE_VARIANT);
3568
+ if (!existsSync10(worktreeDir2)) {
3774
3569
  return c.json({ error: "No upgrade in progress" }, 400);
3775
3570
  }
3776
3571
  try {
3777
3572
  try {
3778
- const gitDirContent = readFileSync8(resolve11(worktreeDir2, ".git"), "utf-8").trim();
3573
+ const gitDirContent = readFileSync9(resolve12(worktreeDir2, ".git"), "utf-8").trim();
3779
3574
  const gitDir = gitDirContent.replace("gitdir: ", "");
3780
- if (existsSync9(resolve11(gitDir, "MERGE_HEAD"))) {
3575
+ if (existsSync10(resolve12(gitDir, "MERGE_HEAD"))) {
3781
3576
  await gitExec(["merge", "--abort"], { cwd: worktreeDir2 });
3782
3577
  }
3783
3578
  } catch {
@@ -3792,8 +3587,8 @@ ${user.trimEnd()}
3792
3587
  }
3793
3588
  }
3794
3589
  if (body.continue) {
3795
- const worktreeDir2 = resolve11(dir, ".variants", UPGRADE_VARIANT);
3796
- if (!existsSync9(worktreeDir2)) {
3590
+ const worktreeDir2 = resolve12(dir, ".variants", UPGRADE_VARIANT);
3591
+ if (!existsSync10(worktreeDir2)) {
3797
3592
  return c.json({ error: "No upgrade in progress" }, 400);
3798
3593
  }
3799
3594
  const status = await gitExec(["status", "--porcelain"], { cwd: worktreeDir2 });
@@ -3837,23 +3632,23 @@ ${user.trimEnd()}
3837
3632
  );
3838
3633
  }
3839
3634
  }
3840
- const worktreeDir = resolve11(dir, ".variants", UPGRADE_VARIANT);
3841
- if (existsSync9(worktreeDir)) {
3635
+ const worktreeDir = resolve12(dir, ".variants", UPGRADE_VARIANT);
3636
+ if (existsSync10(worktreeDir)) {
3842
3637
  return c.json(
3843
3638
  { error: "Upgrade variant already exists. Use continue or delete it first." },
3844
3639
  409
3845
3640
  );
3846
3641
  }
3847
- if (!existsSync9(resolve11(dir, ".git"))) {
3642
+ if (!existsSync10(resolve12(dir, ".git"))) {
3848
3643
  try {
3849
- const env = isIsolationEnabled() ? { ...process.env, HOME: resolve11(dir, "home") } : void 0;
3644
+ const env = isIsolationEnabled() ? { ...process.env, HOME: resolve12(dir, "home") } : void 0;
3850
3645
  await gitExec(["init"], { cwd: dir, mindName, env });
3851
3646
  await configureGitIdentity(mindName, { cwd: dir, mindName, env });
3852
3647
  await gitExec(["add", "-A"], { cwd: dir, mindName, env });
3853
3648
  await gitExec(["commit", "-m", "initial commit"], { cwd: dir, mindName, env });
3854
3649
  chownMindDir(dir, mindName);
3855
3650
  } catch (err) {
3856
- rmSync3(resolve11(dir, ".git"), { recursive: true, force: true });
3651
+ rmSync4(resolve12(dir, ".git"), { recursive: true, force: true });
3857
3652
  return c.json(
3858
3653
  {
3859
3654
  error: `Git initialization failed: ${err instanceof Error ? err.message : String(err)}`
@@ -3867,7 +3662,7 @@ ${user.trimEnd()}
3867
3662
  await gitExec(["branch", "-D", UPGRADE_VARIANT], { cwd: dir });
3868
3663
  } catch {
3869
3664
  }
3870
- if (!existsSync9(resolve11(dir, "home", "shared"))) {
3665
+ if (!existsSync10(resolve12(dir, "home", "shared"))) {
3871
3666
  try {
3872
3667
  await addSharedWorktree(mindName, dir);
3873
3668
  } catch (err) {
@@ -3878,9 +3673,9 @@ ${user.trimEnd()}
3878
3673
  }
3879
3674
  }
3880
3675
  await updateTemplateBranch(dir, template, mindName);
3881
- const parentDir = resolve11(dir, ".variants");
3882
- if (!existsSync9(parentDir)) {
3883
- mkdirSync5(parentDir, { recursive: true });
3676
+ const parentDir = resolve12(dir, ".variants");
3677
+ if (!existsSync10(parentDir)) {
3678
+ mkdirSync6(parentDir, { recursive: true });
3884
3679
  }
3885
3680
  await gitExec(["worktree", "add", "-b", UPGRADE_VARIANT, worktreeDir], { cwd: dir });
3886
3681
  const hasConflicts = await mergeTemplateBranch(worktreeDir);
@@ -3927,7 +3722,7 @@ ${user.trimEnd()}
3927
3722
  if (!variant) return c.json({ error: `Unknown variant: ${variantName}` }, 404);
3928
3723
  }
3929
3724
  try {
3930
- const { getSleepManagerIfReady } = await import("./sleep-manager-2TMQ65E4.js");
3725
+ const { getSleepManagerIfReady } = await import("./sleep-manager-RKTFZPD3.js");
3931
3726
  const sm = getSleepManagerIfReady();
3932
3727
  if (sm?.isSleeping(baseName)) {
3933
3728
  const body2 = await c.req.text();
@@ -4025,7 +3820,7 @@ ${user.trimEnd()}
4025
3820
  if (seedEntry?.stage === "seed") {
4026
3821
  try {
4027
3822
  const db = await getDb();
4028
- const countResult = await db.select({ count: sql2`count(*)` }).from(mindHistory).where(eq4(mindHistory.mind, baseName));
3823
+ const countResult = await db.select({ count: sql`count(*)` }).from(mindHistory).where(eq2(mindHistory.mind, baseName));
4029
3824
  const msgCount = countResult[0]?.count ?? 0;
4030
3825
  if (msgCount >= 10 && msgCount % 10 === 0) {
4031
3826
  const nudge = "\n[You've been exploring for a while. Whenever you feel ready, write your SOUL.md and MEMORY.md, then run volute mind sprout.]";
@@ -4070,13 +3865,13 @@ ${user.trimEnd()}
4070
3865
  const entry = findMind(name);
4071
3866
  if (!entry) return c.json({ error: "Mind not found" }, 404);
4072
3867
  const dir = mindDir(name);
4073
- if (!existsSync9(dir)) return c.json({ error: "Mind directory missing" }, 404);
3868
+ if (!existsSync10(dir)) return c.json({ error: "Mind directory missing" }, 404);
4074
3869
  let config = readVoluteConfig(dir);
4075
3870
  if (!config && entry.template === "pi") {
4076
- const piConfigPath = resolve11(dir, "home/.config/config.json");
4077
- if (existsSync9(piConfigPath)) {
3871
+ const piConfigPath = resolve12(dir, "home/.config/config.json");
3872
+ if (existsSync10(piConfigPath)) {
4078
3873
  try {
4079
- config = JSON.parse(readFileSync8(piConfigPath, "utf-8"));
3874
+ config = JSON.parse(readFileSync9(piConfigPath, "utf-8"));
4080
3875
  } catch {
4081
3876
  }
4082
3877
  }
@@ -4113,7 +3908,7 @@ ${user.trimEnd()}
4113
3908
  const entry = findMind(name);
4114
3909
  if (!entry) return c.json({ error: "Mind not found" }, 404);
4115
3910
  const dir = mindDir(name);
4116
- if (!existsSync9(dir)) return c.json({ error: "Mind directory missing" }, 404);
3911
+ if (!existsSync10(dir)) return c.json({ error: "Mind directory missing" }, 404);
4117
3912
  const body = c.req.valid("json");
4118
3913
  const existing = readVoluteConfig(dir) ?? {};
4119
3914
  if (body.model !== void 0) existing.model = body.model;
@@ -4294,22 +4089,22 @@ ${user.trimEnd()}
4294
4089
  const db = await getDb();
4295
4090
  const rows = await db.select({
4296
4091
  session: mindHistory.session,
4297
- started_at: sql2`MIN(${mindHistory.created_at})`,
4298
- event_count: sql2`COUNT(*)`,
4299
- message_count: sql2`SUM(CASE WHEN ${mindHistory.type} IN ('inbound','outbound') THEN 1 ELSE 0 END)`,
4300
- tool_count: sql2`SUM(CASE WHEN ${mindHistory.type}='tool_use' THEN 1 ELSE 0 END)`
4301
- }).from(mindHistory).where(and3(eq4(mindHistory.mind, name), sql2`${mindHistory.session} IS NOT NULL`)).groupBy(mindHistory.session).orderBy(sql2`MIN(${mindHistory.created_at}) DESC`);
4092
+ started_at: sql`MIN(${mindHistory.created_at})`,
4093
+ event_count: sql`COUNT(*)`,
4094
+ message_count: sql`SUM(CASE WHEN ${mindHistory.type} IN ('inbound','outbound') THEN 1 ELSE 0 END)`,
4095
+ tool_count: sql`SUM(CASE WHEN ${mindHistory.type}='tool_use' THEN 1 ELSE 0 END)`
4096
+ }).from(mindHistory).where(and(eq2(mindHistory.mind, name), sql`${mindHistory.session} IS NOT NULL`)).groupBy(mindHistory.session).orderBy(sql`MIN(${mindHistory.created_at}) DESC`);
4302
4097
  return c.json(rows);
4303
4098
  }).get("/:name/history/channels", async (c) => {
4304
4099
  const name = c.req.param("name");
4305
4100
  const db = await getDb();
4306
- const rows = await db.selectDistinct({ channel: mindHistory.channel }).from(mindHistory).where(eq4(mindHistory.mind, name));
4101
+ const rows = await db.selectDistinct({ channel: mindHistory.channel }).from(mindHistory).where(eq2(mindHistory.mind, name));
4307
4102
  return c.json(rows.map((r) => r.channel));
4308
4103
  }).get("/:name/history/export", async (c) => {
4309
4104
  const name = c.req.param("name");
4310
4105
  if (!findMind(name)) return c.json({ error: "Mind not found" }, 404);
4311
4106
  const db = await getDb();
4312
- const rows = await db.select().from(mindHistory).where(eq4(mindHistory.mind, name));
4107
+ const rows = await db.select().from(mindHistory).where(eq2(mindHistory.mind, name));
4313
4108
  return c.json(rows);
4314
4109
  }).get("/:name/history", async (c) => {
4315
4110
  const name = c.req.param("name");
@@ -4319,24 +4114,24 @@ ${user.trimEnd()}
4319
4114
  const limit = Math.min(Math.max(parseInt(c.req.query("limit") ?? "50", 10) || 50, 1), 200);
4320
4115
  const offset = Math.max(parseInt(c.req.query("offset") ?? "0", 10) || 0, 0);
4321
4116
  const db = await getDb();
4322
- const conditions = [eq4(mindHistory.mind, name)];
4117
+ const conditions = [eq2(mindHistory.mind, name)];
4323
4118
  if (channel) {
4324
- conditions.push(eq4(mindHistory.channel, channel));
4119
+ conditions.push(eq2(mindHistory.channel, channel));
4325
4120
  }
4326
4121
  if (session) {
4327
- conditions.push(eq4(mindHistory.session, session));
4122
+ conditions.push(eq2(mindHistory.session, session));
4328
4123
  }
4329
4124
  if (!full) {
4330
- conditions.push(sql2`${mindHistory.type} IN ('inbound', 'outbound')`);
4125
+ conditions.push(sql`${mindHistory.type} IN ('inbound', 'outbound')`);
4331
4126
  }
4332
- const rows = await db.select().from(mindHistory).where(and3(...conditions)).orderBy(desc3(mindHistory.created_at)).limit(limit).offset(offset);
4127
+ const rows = await db.select().from(mindHistory).where(and(...conditions)).orderBy(desc2(mindHistory.created_at)).limit(limit).offset(offset);
4333
4128
  return c.json(rows);
4334
4129
  });
4335
4130
  var minds_default = app11;
4336
4131
 
4337
4132
  // src/web/api/pages.ts
4338
4133
  import { readFile as readFile2, stat as stat2 } from "fs/promises";
4339
- import { extname as extname2, resolve as resolve12 } from "path";
4134
+ import { extname as extname3, resolve as resolve13 } from "path";
4340
4135
  import { Hono as Hono12 } from "hono";
4341
4136
  var MIME_TYPES = {
4342
4137
  ".html": "text/html",
@@ -4358,17 +4153,17 @@ var app12 = new Hono12().get("/:name/*", async (c) => {
4358
4153
  const name = c.req.param("name");
4359
4154
  let pagesRoot;
4360
4155
  if (name === "_system") {
4361
- pagesRoot = resolve12(voluteHome(), "shared", "pages");
4156
+ pagesRoot = resolve13(voluteHome(), "shared", "pages");
4362
4157
  } else {
4363
4158
  if (!findMind(name)) return c.text("Not found", 404);
4364
- pagesRoot = resolve12(mindDir(name), "home", "pages");
4159
+ pagesRoot = resolve13(mindDir(name), "home", "public", "pages");
4365
4160
  }
4366
4161
  const wildcard = c.req.path.replace(`/pages/${name}`, "") || "/";
4367
- const requestedPath = resolve12(pagesRoot, wildcard.slice(1));
4162
+ const requestedPath = resolve13(pagesRoot, wildcard.slice(1));
4368
4163
  if (!requestedPath.startsWith(pagesRoot)) return c.text("Forbidden", 403);
4369
4164
  let fileStat = await stat2(requestedPath).catch(() => null);
4370
4165
  if (fileStat?.isDirectory()) {
4371
- const indexPath = resolve12(requestedPath, "index.html");
4166
+ const indexPath = resolve13(requestedPath, "index.html");
4372
4167
  fileStat = await stat2(indexPath).catch(() => null);
4373
4168
  if (fileStat?.isFile()) {
4374
4169
  const body = await readFile2(indexPath);
@@ -4377,7 +4172,7 @@ var app12 = new Hono12().get("/:name/*", async (c) => {
4377
4172
  return c.text("Not found", 404);
4378
4173
  }
4379
4174
  if (fileStat?.isFile()) {
4380
- const ext = extname2(requestedPath);
4175
+ const ext = extname3(requestedPath);
4381
4176
  const mime = MIME_TYPES[ext] || "application/octet-stream";
4382
4177
  const body = await readFile2(requestedPath);
4383
4178
  return c.body(body, 200, { "Content-Type": mime });
@@ -4388,7 +4183,7 @@ var pages_default = app12;
4388
4183
 
4389
4184
  // src/web/api/prompts.ts
4390
4185
  import { zValidator as zValidator4 } from "@hono/zod-validator";
4391
- import { eq as eq5, sql as sql3 } from "drizzle-orm";
4186
+ import { eq as eq3, sql as sql2 } from "drizzle-orm";
4392
4187
  import { Hono as Hono13 } from "hono";
4393
4188
  import { z as z4 } from "zod";
4394
4189
  var app13 = new Hono13().get("/", async (c) => {
@@ -4421,9 +4216,9 @@ var app13 = new Hono13().get("/", async (c) => {
4421
4216
  }
4422
4217
  const { content } = c.req.valid("json");
4423
4218
  const db = await getDb();
4424
- await db.insert(systemPrompts).values({ key, content, updated_at: sql3`(datetime('now'))` }).onConflictDoUpdate({
4219
+ await db.insert(systemPrompts).values({ key, content, updated_at: sql2`(datetime('now'))` }).onConflictDoUpdate({
4425
4220
  target: systemPrompts.key,
4426
- set: { content, updated_at: sql3`(datetime('now'))` }
4221
+ set: { content, updated_at: sql2`(datetime('now'))` }
4427
4222
  });
4428
4223
  return c.json({ ok: true });
4429
4224
  }).delete("/:key", requireAdmin, async (c) => {
@@ -4432,14 +4227,103 @@ var app13 = new Hono13().get("/", async (c) => {
4432
4227
  return c.json({ error: "Unknown prompt key" }, 404);
4433
4228
  }
4434
4229
  const db = await getDb();
4435
- await db.delete(systemPrompts).where(eq5(systemPrompts.key, key));
4230
+ await db.delete(systemPrompts).where(eq3(systemPrompts.key, key));
4436
4231
  return c.json({ ok: true });
4437
4232
  });
4438
4233
  var prompts_default = app13;
4439
4234
 
4235
+ // src/web/api/public-files.ts
4236
+ import { readdir as readdir2, readFile as readFile3, stat as stat3 } from "fs/promises";
4237
+ import { extname as extname4, resolve as resolve14 } from "path";
4238
+ import { Hono as Hono14 } from "hono";
4239
+ var MAX_FILE_SIZE = 50 * 1024 * 1024;
4240
+ function resolvePublicRoot(name) {
4241
+ if (name === "_system") return resolve14(voluteHome(), "shared");
4242
+ if (!findMind(name)) return null;
4243
+ return resolve14(mindDir(name), "home", "public");
4244
+ }
4245
+ function hasDotSegment(relativePath) {
4246
+ return relativePath.split("/").some((seg) => seg.startsWith("."));
4247
+ }
4248
+ var MIME_TYPES2 = {
4249
+ ".html": "text/html",
4250
+ ".js": "application/javascript",
4251
+ ".css": "text/css",
4252
+ ".json": "application/json",
4253
+ ".svg": "image/svg+xml",
4254
+ ".png": "image/png",
4255
+ ".jpg": "image/jpeg",
4256
+ ".jpeg": "image/jpeg",
4257
+ ".gif": "image/gif",
4258
+ ".ico": "image/x-icon",
4259
+ ".woff": "font/woff",
4260
+ ".woff2": "font/woff2",
4261
+ ".txt": "text/plain",
4262
+ ".xml": "application/xml",
4263
+ ".md": "text/markdown",
4264
+ ".webp": "image/webp"
4265
+ };
4266
+ async function listDir(dirPath) {
4267
+ let entries;
4268
+ try {
4269
+ entries = await readdir2(dirPath, { withFileTypes: true });
4270
+ } catch (err) {
4271
+ if (err?.code === "ENOENT") return [];
4272
+ throw err;
4273
+ }
4274
+ return entries.filter((e) => !e.name.startsWith(".")).map((e) => ({
4275
+ name: e.name,
4276
+ type: e.isDirectory() ? "directory" : "file"
4277
+ }));
4278
+ }
4279
+ var app14 = new Hono14().get("/:name/", async (c) => {
4280
+ const name = c.req.param("name");
4281
+ const publicRoot = resolvePublicRoot(name);
4282
+ if (!publicRoot) return c.json({ error: "Not found" }, 404);
4283
+ return c.json(await listDir(publicRoot));
4284
+ }).get("/:name/*", async (c) => {
4285
+ const name = c.req.param("name");
4286
+ const publicRoot = resolvePublicRoot(name);
4287
+ if (!publicRoot) return c.text("Not found", 404);
4288
+ const wildcard = c.req.path.replace(`/public/${name}`, "") || "/";
4289
+ const relativePath = wildcard.slice(1);
4290
+ const requestedPath = resolve14(publicRoot, relativePath);
4291
+ if (!requestedPath.startsWith(publicRoot)) return c.text("Forbidden", 403);
4292
+ if (hasDotSegment(relativePath)) return c.text("Forbidden", 403);
4293
+ let fileStat;
4294
+ try {
4295
+ fileStat = await stat3(requestedPath);
4296
+ } catch (err) {
4297
+ if (err?.code === "ENOENT") return c.text("Not found", 404);
4298
+ if (err?.code === "EACCES") return c.text("Forbidden", 403);
4299
+ return c.text("Internal server error", 500);
4300
+ }
4301
+ if (fileStat.isDirectory()) {
4302
+ if (wildcard.endsWith("/")) {
4303
+ return c.json(await listDir(requestedPath));
4304
+ }
4305
+ return c.text("Not found", 404);
4306
+ }
4307
+ if (fileStat.isFile()) {
4308
+ if (fileStat.size > MAX_FILE_SIZE) return c.text("File too large", 413);
4309
+ const ext = extname4(requestedPath);
4310
+ const mime = MIME_TYPES2[ext] || "application/octet-stream";
4311
+ try {
4312
+ const body = await readFile3(requestedPath);
4313
+ return c.body(body, 200, { "Content-Type": mime });
4314
+ } catch (err) {
4315
+ if (err?.code === "ENOENT") return c.text("Not found", 404);
4316
+ if (err?.code === "EACCES") return c.text("Forbidden", 403);
4317
+ return c.text("Failed to read file", 500);
4318
+ }
4319
+ }
4320
+ return c.text("Not found", 404);
4321
+ });
4322
+ var public_files_default = app14;
4323
+
4440
4324
  // src/web/api/schedules.ts
4441
4325
  import { CronExpressionParser } from "cron-parser";
4442
- import { Hono as Hono14 } from "hono";
4326
+ import { Hono as Hono15 } from "hono";
4443
4327
  var slog = logger_default.child("schedules");
4444
4328
  function readSchedules(name) {
4445
4329
  return readVoluteConfig(mindDir(name))?.schedules ?? [];
@@ -4456,7 +4340,7 @@ function writeSchedules(name, schedules) {
4456
4340
  data: { schedules }
4457
4341
  });
4458
4342
  }
4459
- var app14 = new Hono14().get("/:name/schedules", (c) => {
4343
+ var app15 = new Hono15().get("/:name/schedules", (c) => {
4460
4344
  const name = c.req.param("name");
4461
4345
  if (!findMind(name)) return c.json({ error: "Mind not found" }, 404);
4462
4346
  return c.json(readSchedules(name));
@@ -4559,11 +4443,11 @@ var app14 = new Hono14().get("/:name/schedules", (c) => {
4559
4443
  return c.json({ error: "Failed to reach mind" }, 502);
4560
4444
  }
4561
4445
  });
4562
- var schedules_default = app14;
4446
+ var schedules_default = app15;
4563
4447
 
4564
4448
  // src/web/api/shared.ts
4565
- import { Hono as Hono15 } from "hono";
4566
- var app15 = new Hono15().post("/:name/shared/merge", requireAdmin, async (c) => {
4449
+ import { Hono as Hono16 } from "hono";
4450
+ var app16 = new Hono16().post("/:name/shared/merge", requireAdmin, async (c) => {
4567
4451
  const name = c.req.param("name");
4568
4452
  const entry = findMind(name);
4569
4453
  if (!entry) return c.json({ error: "Mind not found" }, 404);
@@ -4612,15 +4496,15 @@ var app15 = new Hono15().post("/:name/shared/merge", requireAdmin, async (c) =>
4612
4496
  return c.json({ error: err instanceof Error ? err.message : "Failed to get status" }, 500);
4613
4497
  }
4614
4498
  });
4615
- var shared_default = app15;
4499
+ var shared_default = app16;
4616
4500
 
4617
4501
  // src/web/api/skills.ts
4618
- import { existsSync as existsSync10, mkdtempSync, readdirSync as readdirSync5, rmSync as rmSync4 } from "fs";
4502
+ import { existsSync as existsSync11, mkdtempSync, readdirSync as readdirSync5, rmSync as rmSync5 } from "fs";
4619
4503
  import { tmpdir } from "os";
4620
- import { join as join2, resolve as resolve13 } from "path";
4504
+ import { join as join2, resolve as resolve15 } from "path";
4621
4505
  import AdmZip from "adm-zip";
4622
- import { Hono as Hono16 } from "hono";
4623
- var app16 = new Hono16().get("/", async (c) => {
4506
+ import { Hono as Hono17 } from "hono";
4507
+ var app17 = new Hono17().get("/", async (c) => {
4624
4508
  const skills = await listSharedSkills();
4625
4509
  return c.json(skills);
4626
4510
  }).get("/:id", async (c) => {
@@ -4644,19 +4528,19 @@ var app16 = new Hono16().get("/", async (c) => {
4644
4528
  try {
4645
4529
  const zip = new AdmZip(buffer2);
4646
4530
  for (const entry of zip.getEntries()) {
4647
- const target = resolve13(tmpDir, entry.entryName);
4531
+ const target = resolve15(tmpDir, entry.entryName);
4648
4532
  if (!target.startsWith(tmpDir)) {
4649
4533
  return c.json({ error: "Invalid zip: paths must not escape archive" }, 400);
4650
4534
  }
4651
4535
  }
4652
4536
  zip.extractAllTo(tmpDir, true);
4653
4537
  let skillDir = null;
4654
- if (existsSync10(join2(tmpDir, "SKILL.md"))) {
4538
+ if (existsSync11(join2(tmpDir, "SKILL.md"))) {
4655
4539
  skillDir = tmpDir;
4656
4540
  } else {
4657
4541
  const entries = readdirSync5(tmpDir, { withFileTypes: true }).filter((e) => e.isDirectory());
4658
4542
  for (const entry of entries) {
4659
- if (existsSync10(join2(tmpDir, entry.name, "SKILL.md"))) {
4543
+ if (existsSync11(join2(tmpDir, entry.name, "SKILL.md"))) {
4660
4544
  skillDir = join2(tmpDir, entry.name);
4661
4545
  break;
4662
4546
  }
@@ -4673,7 +4557,7 @@ var app16 = new Hono16().get("/", async (c) => {
4673
4557
  }
4674
4558
  throw e;
4675
4559
  } finally {
4676
- rmSync4(tmpDir, { recursive: true, force: true });
4560
+ rmSync5(tmpDir, { recursive: true, force: true });
4677
4561
  }
4678
4562
  }).delete("/:id", requireAdmin, async (c) => {
4679
4563
  const id = c.req.param("id");
@@ -4685,12 +4569,15 @@ var app16 = new Hono16().get("/", async (c) => {
4685
4569
  }
4686
4570
  return c.json({ ok: true });
4687
4571
  });
4688
- var skills_default = app16;
4572
+ var skills_default = app17;
4689
4573
 
4690
4574
  // src/web/api/system.ts
4691
- import { Hono as Hono17 } from "hono";
4575
+ import { zValidator as zValidator5 } from "@hono/zod-validator";
4576
+ import { Hono as Hono18 } from "hono";
4692
4577
  import { streamSSE as streamSSE3 } from "hono/streaming";
4693
- var app17 = new Hono17().post("/restart", requireAdmin, (c) => {
4578
+ import { z as z5 } from "zod";
4579
+ var DEFAULT_API_URL = "https://volute.systems";
4580
+ var app18 = new Hono18().post("/restart", requireAdmin, (c) => {
4694
4581
  setTimeout(() => process.exit(1), 200);
4695
4582
  return c.json({ ok: true });
4696
4583
  }).post("/stop", requireAdmin, (c) => {
@@ -4707,29 +4594,107 @@ var app17 = new Hono17().post("/restart", requireAdmin, (c) => {
4707
4594
  stream.writeSSE({ data: JSON.stringify(entry) }).catch(() => {
4708
4595
  });
4709
4596
  });
4710
- await new Promise((resolve18) => {
4597
+ await new Promise((resolve20) => {
4711
4598
  stream.onAbort(() => {
4712
4599
  unsubscribe();
4713
- resolve18();
4600
+ resolve20();
4714
4601
  });
4715
4602
  });
4716
4603
  });
4717
4604
  }).get("/info", (c) => {
4718
4605
  const config = readSystemsConfig();
4719
4606
  return c.json({ system: config?.system ?? null });
4607
+ }).post(
4608
+ "/register",
4609
+ requireAdmin,
4610
+ zValidator5("json", z5.object({ name: z5.string().min(1) })),
4611
+ async (c) => {
4612
+ const existing = readSystemsConfig();
4613
+ if (existing) {
4614
+ return c.json({ error: `Already registered as "${existing.system}"` }, 400);
4615
+ }
4616
+ const { name } = c.req.valid("json");
4617
+ const apiUrl = process.env.VOLUTE_SYSTEMS_URL || DEFAULT_API_URL;
4618
+ let apiKey;
4619
+ let system;
4620
+ try {
4621
+ const res = await fetch(`${apiUrl}/api/register`, {
4622
+ method: "POST",
4623
+ headers: { "Content-Type": "application/json" },
4624
+ body: JSON.stringify({ name: name.trim() })
4625
+ });
4626
+ if (!res.ok) {
4627
+ const err = await res.json().catch(() => ({ error: `HTTP ${res.status}` }));
4628
+ return c.json({ error: err.error }, 502);
4629
+ }
4630
+ ({ apiKey, system } = await res.json());
4631
+ } catch (err) {
4632
+ return c.json({ error: `Connection failed: ${err.message}` }, 502);
4633
+ }
4634
+ try {
4635
+ writeSystemsConfig({ apiKey, system, apiUrl });
4636
+ } catch (err) {
4637
+ return c.json(
4638
+ {
4639
+ error: `Registered as "${system}" but failed to save config: ${err.message}`
4640
+ },
4641
+ 500
4642
+ );
4643
+ }
4644
+ return c.json({ system });
4645
+ }
4646
+ ).post(
4647
+ "/login",
4648
+ requireAdmin,
4649
+ zValidator5("json", z5.object({ key: z5.string().min(1) })),
4650
+ async (c) => {
4651
+ const existing = readSystemsConfig();
4652
+ if (existing) {
4653
+ return c.json({ error: `Already logged in as "${existing.system}"` }, 400);
4654
+ }
4655
+ const { key } = c.req.valid("json");
4656
+ const apiUrl = process.env.VOLUTE_SYSTEMS_URL || DEFAULT_API_URL;
4657
+ let system;
4658
+ try {
4659
+ const res = await fetch(`${apiUrl}/api/whoami`, {
4660
+ headers: { Authorization: `Bearer ${key.trim()}` }
4661
+ });
4662
+ if (!res.ok) {
4663
+ const err = await res.json().catch(() => ({ error: `HTTP ${res.status}` }));
4664
+ return c.json({ error: err.error }, 502);
4665
+ }
4666
+ ({ system } = await res.json());
4667
+ } catch (err) {
4668
+ return c.json({ error: `Connection failed: ${err.message}` }, 502);
4669
+ }
4670
+ try {
4671
+ writeSystemsConfig({ apiKey: key.trim(), system, apiUrl });
4672
+ } catch (err) {
4673
+ return c.json(
4674
+ {
4675
+ error: `Logged in as "${system}" but failed to save config: ${err.message}`
4676
+ },
4677
+ 500
4678
+ );
4679
+ }
4680
+ return c.json({ system });
4681
+ }
4682
+ ).post("/logout", requireAdmin, (c) => {
4683
+ deleteSystemsConfig();
4684
+ return c.json({ ok: true });
4720
4685
  });
4721
- var system_default = app17;
4686
+ var system_default = app18;
4722
4687
 
4723
4688
  // src/web/api/typing.ts
4724
- import { zValidator as zValidator5 } from "@hono/zod-validator";
4725
- import { Hono as Hono18 } from "hono";
4726
- import { z as z5 } from "zod";
4727
- var typingSchema = z5.object({
4728
- channel: z5.string().min(1),
4729
- sender: z5.string().min(1),
4730
- active: z5.boolean()
4689
+ import { zValidator as zValidator6 } from "@hono/zod-validator";
4690
+ import { Hono as Hono19 } from "hono";
4691
+ import { z as z6 } from "zod";
4692
+ var typingSchema = z6.object({
4693
+ channel: z6.string().min(1),
4694
+ sender: z6.string().min(1),
4695
+ active: z6.boolean()
4731
4696
  });
4732
- var app18 = new Hono18().post("/:name/typing", zValidator5("json", typingSchema), (c) => {
4697
+ var app19 = new Hono19().post("/:name/typing", zValidator6("json", typingSchema), (c) => {
4733
4698
  const { channel, sender, active } = c.req.valid("json");
4734
4699
  const map = getTypingMap();
4735
4700
  if (active) {
@@ -4751,13 +4716,13 @@ var app18 = new Hono18().post("/:name/typing", zValidator5("json", typingSchema)
4751
4716
  const map = getTypingMap();
4752
4717
  return c.json({ typing: map.get(channel) });
4753
4718
  });
4754
- var typing_default = app18;
4719
+ var typing_default = app19;
4755
4720
 
4756
4721
  // src/web/api/update.ts
4757
4722
  import { spawn as spawn2 } from "child_process";
4758
- import { Hono as Hono19 } from "hono";
4723
+ import { Hono as Hono20 } from "hono";
4759
4724
  var bin;
4760
- var app19 = new Hono19().get("/update", async (c) => {
4725
+ var app20 = new Hono20().get("/update", async (c) => {
4761
4726
  const result = await checkForUpdate();
4762
4727
  return c.json(result);
4763
4728
  }).post("/update", requireAdmin, async (c) => {
@@ -4772,21 +4737,21 @@ var app19 = new Hono19().get("/update", async (c) => {
4772
4737
  child.unref();
4773
4738
  return c.json({ ok: true, message: "Updating..." });
4774
4739
  });
4775
- var update_default = app19;
4740
+ var update_default = app20;
4776
4741
 
4777
4742
  // src/web/api/v1/chat.ts
4778
- import { zValidator as zValidator6 } from "@hono/zod-validator";
4779
- import { Hono as Hono20 } from "hono";
4743
+ import { zValidator as zValidator7 } from "@hono/zod-validator";
4744
+ import { Hono as Hono21 } from "hono";
4780
4745
  import { streamSSE as streamSSE4 } from "hono/streaming";
4781
- import { z as z6 } from "zod";
4746
+ import { z as z7 } from "zod";
4782
4747
  async function fanOutToMinds(opts) {
4783
4748
  const participants = await getParticipants(opts.conversationId);
4784
4749
  const mindParticipants = participants.filter((p) => p.userType === "mind");
4785
4750
  const participantNames = participants.map((p) => p.username);
4786
4751
  const isDM = opts.isDM ?? participants.length === 2;
4787
4752
  const channelEntryType = opts.channelEntryType ?? (isDM ? "dm" : "group");
4788
- const { getMindManager: getMindManager2 } = await import("./mind-manager-KMY4GA2J.js");
4789
- const { getSleepManagerIfReady } = await import("./sleep-manager-2TMQ65E4.js");
4753
+ const { getMindManager: getMindManager2 } = await import("./mind-manager-4NDNAYAB.js");
4754
+ const { getSleepManagerIfReady } = await import("./sleep-manager-RKTFZPD3.js");
4790
4755
  const manager = getMindManager2();
4791
4756
  const sm = getSleepManagerIfReady();
4792
4757
  const targetMinds = mindParticipants.map((ap) => {
@@ -4835,18 +4800,18 @@ async function fanOutToMinds(opts) {
4835
4800
  });
4836
4801
  }
4837
4802
  }
4838
- var mindChatSchema = z6.object({
4839
- message: z6.string().optional(),
4840
- conversationId: z6.string().optional(),
4841
- sender: z6.string().optional(),
4842
- images: z6.array(z6.object({ media_type: z6.string(), data: z6.string() })).optional()
4803
+ var mindChatSchema = z7.object({
4804
+ message: z7.string().optional(),
4805
+ conversationId: z7.string().optional(),
4806
+ sender: z7.string().optional(),
4807
+ images: z7.array(z7.object({ media_type: z7.string(), data: z7.string() })).optional()
4843
4808
  });
4844
- var unifiedChatSchema = z6.object({
4845
- message: z6.string().optional(),
4846
- conversationId: z6.string(),
4847
- images: z6.array(z6.object({ media_type: z6.string(), data: z6.string() })).optional()
4809
+ var unifiedChatSchema = z7.object({
4810
+ message: z7.string().optional(),
4811
+ conversationId: z7.string(),
4812
+ images: z7.array(z7.object({ media_type: z7.string(), data: z7.string() })).optional()
4848
4813
  });
4849
- var app20 = new Hono20().use("*", authMiddleware).post("/minds/:name/chat", zValidator6("json", mindChatSchema), async (c) => {
4814
+ var app21 = new Hono21().use("*", authMiddleware).post("/minds/:name/chat", zValidator7("json", mindChatSchema), async (c) => {
4850
4815
  const name = c.req.param("name");
4851
4816
  const [baseName] = name.split("@", 2);
4852
4817
  const entry = findMind(baseName);
@@ -4900,11 +4865,15 @@ var app20 = new Hono20().use("*", authMiddleware).post("/minds/:name/chat", zVal
4900
4865
  }
4901
4866
  }
4902
4867
  await addMessage(conversationId, "user", senderName, contentBlocks);
4868
+ const isDM = conv?.type === "dm";
4903
4869
  await fanOutToMinds({
4904
4870
  conversationId,
4905
4871
  contentBlocks,
4906
4872
  senderName,
4907
4873
  convTitle,
4874
+ isDM,
4875
+ channelEntryType: conv?.type === "channel" ? "group" : isDM ? "dm" : "group",
4876
+ slugExtra: conv ? { convType: conv.type, convName: conv.name } : void 0,
4908
4877
  targetName: (username) => username === baseName ? name : username
4909
4878
  });
4910
4879
  return c.json({ ok: true, conversationId });
@@ -4925,15 +4894,15 @@ var app20 = new Hono20().use("*", authMiddleware).post("/minds/:name/chat", zVal
4925
4894
  if (!stream.aborted) logger_default.error("[v1-chat] SSE ping error:", logger_default.errorData(err));
4926
4895
  });
4927
4896
  }, 15e3);
4928
- await new Promise((resolve18) => {
4897
+ await new Promise((resolve20) => {
4929
4898
  stream.onAbort(() => {
4930
4899
  unsubscribe();
4931
4900
  clearInterval(keepAlive);
4932
- resolve18();
4901
+ resolve20();
4933
4902
  });
4934
4903
  });
4935
4904
  });
4936
- }).post("/chat", zValidator6("json", unifiedChatSchema), async (c) => {
4905
+ }).post("/chat", zValidator7("json", unifiedChatSchema), async (c) => {
4937
4906
  const user = c.get("user");
4938
4907
  const body = c.req.valid("json");
4939
4908
  if (!body.message && (!body.images || body.images.length === 0)) {
@@ -4965,17 +4934,17 @@ var app20 = new Hono20().use("*", authMiddleware).post("/minds/:name/chat", zVal
4965
4934
  });
4966
4935
  return c.json({ ok: true, conversationId: body.conversationId });
4967
4936
  });
4968
- var chat_default = app20;
4937
+ var chat_default = app21;
4969
4938
 
4970
4939
  // src/web/api/v1/conversations.ts
4971
- import { zValidator as zValidator7 } from "@hono/zod-validator";
4972
- import { Hono as Hono21 } from "hono";
4973
- import { z as z7 } from "zod";
4974
- var createSchema = z7.object({
4975
- title: z7.string().optional(),
4976
- participantNames: z7.array(z7.string()).min(1)
4940
+ import { zValidator as zValidator8 } from "@hono/zod-validator";
4941
+ import { Hono as Hono22 } from "hono";
4942
+ import { z as z8 } from "zod";
4943
+ var createSchema = z8.object({
4944
+ title: z8.string().optional(),
4945
+ participantNames: z8.array(z8.string()).min(1)
4977
4946
  });
4978
- var app21 = new Hono21().use("*", authMiddleware).get("/", async (c) => {
4947
+ var app22 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
4979
4948
  const user = c.get("user");
4980
4949
  const convs = await listConversationsWithParticipants(user.id);
4981
4950
  return c.json(convs);
@@ -5006,7 +4975,7 @@ var app21 = new Hono21().use("*", authMiddleware).get("/", async (c) => {
5006
4975
  }
5007
4976
  const participants = await getParticipants(id);
5008
4977
  return c.json(participants);
5009
- }).post("/", zValidator7("json", createSchema), async (c) => {
4978
+ }).post("/", zValidator8("json", createSchema), async (c) => {
5010
4979
  const user = c.get("user");
5011
4980
  const body = c.req.valid("json");
5012
4981
  const participantIds = /* @__PURE__ */ new Set();
@@ -5036,6 +5005,15 @@ var app21 = new Hono21().use("*", authMiddleware).get("/", async (c) => {
5036
5005
  participantIds: [...participantIds]
5037
5006
  });
5038
5007
  return c.json(conv, 201);
5008
+ }).post("/:id/read", async (c) => {
5009
+ const id = c.req.param("id");
5010
+ const user = c.get("user");
5011
+ if (user.id === 0) return c.json({ ok: true });
5012
+ if (!await isParticipantOrOwner(id, user.id)) {
5013
+ return c.json({ error: "Conversation not found" }, 404);
5014
+ }
5015
+ await markConversationRead(user.id, id);
5016
+ return c.json({ ok: true });
5039
5017
  }).delete("/:id", async (c) => {
5040
5018
  const id = c.req.param("id");
5041
5019
  const user = c.get("user");
@@ -5043,13 +5021,36 @@ var app21 = new Hono21().use("*", authMiddleware).get("/", async (c) => {
5043
5021
  if (!deleted) return c.json({ error: "Conversation not found" }, 404);
5044
5022
  return c.json({ ok: true });
5045
5023
  });
5046
- var conversations_default = app21;
5024
+ var conversations_default = app22;
5047
5025
 
5048
5026
  // src/web/api/v1/events.ts
5049
- import { desc as desc4 } from "drizzle-orm";
5050
- import { Hono as Hono22 } from "hono";
5027
+ import { desc as desc3 } from "drizzle-orm";
5028
+ import { Hono as Hono23 } from "hono";
5051
5029
  import { streamSSE as streamSSE5 } from "hono/streaming";
5052
5030
 
5031
+ // src/lib/events/brain-presence.ts
5032
+ var connections = /* @__PURE__ */ new Map();
5033
+ function addConnection(username) {
5034
+ const count = connections.get(username) ?? 0;
5035
+ connections.set(username, count + 1);
5036
+ if (count === 0) {
5037
+ broadcast({ type: "brain_online", mind: username, summary: `${username} connected` });
5038
+ }
5039
+ }
5040
+ function removeConnection(username) {
5041
+ const count = connections.get(username);
5042
+ if (count == null) return;
5043
+ if (count <= 1) {
5044
+ connections.delete(username);
5045
+ broadcast({ type: "brain_offline", mind: username, summary: `${username} disconnected` });
5046
+ } else {
5047
+ connections.set(username, count - 1);
5048
+ }
5049
+ }
5050
+ function getOnlineBrains() {
5051
+ return [...connections.keys()];
5052
+ }
5053
+
5053
5054
  // src/lib/events/event-sequencer.ts
5054
5055
  var BUFFER_SIZE = 1e3;
5055
5056
  var MAX_AGE_MS = 5 * 60 * 1e3;
@@ -5071,12 +5072,16 @@ function getEventsSince(sinceId) {
5071
5072
  }
5072
5073
 
5073
5074
  // src/web/api/v1/events.ts
5074
- var app22 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
5075
+ var app23 = new Hono23().use("*", authMiddleware).get("/", async (c) => {
5075
5076
  const user = c.get("user");
5076
5077
  const since = c.req.query("since");
5077
5078
  const sinceId = since ? Number(since) : 0;
5078
5079
  return streamSSE5(c, async (stream) => {
5079
5080
  const cleanups = [];
5081
+ if (user.user_type === "brain") {
5082
+ addConnection(user.username);
5083
+ cleanups.push(() => removeConnection(user.username));
5084
+ }
5080
5085
  try {
5081
5086
  if (sinceId > 0) {
5082
5087
  const missed = getEventsSince(sinceId);
@@ -5090,7 +5095,7 @@ var app22 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
5090
5095
  let recentActivity = [];
5091
5096
  try {
5092
5097
  const db = await getDb();
5093
- recentActivity = await db.select().from(activity).orderBy(desc4(activity.created_at)).limit(50);
5098
+ recentActivity = await db.select().from(activity).orderBy(desc3(activity.created_at)).limit(50);
5094
5099
  recentActivity = recentActivity.map((row) => ({
5095
5100
  ...row,
5096
5101
  metadata: row.metadata ? JSON.parse(row.metadata) : null
@@ -5101,6 +5106,13 @@ var app22 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
5101
5106
  let conversations2 = [];
5102
5107
  try {
5103
5108
  conversations2 = await listConversationsWithParticipants(user.id);
5109
+ if (conversations2.length > 0) {
5110
+ const convIds = conversations2.map((c2) => c2.id);
5111
+ const unreads = await getUnreadCounts(user.id, convIds);
5112
+ for (const conv of conversations2) {
5113
+ conv.unreadCount = unreads[conv.id] ?? 0;
5114
+ }
5115
+ }
5104
5116
  } catch (err) {
5105
5117
  logger_default.error("[v1-events] failed to fetch conversations", logger_default.errorData(err));
5106
5118
  }
@@ -5112,7 +5124,8 @@ var app22 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
5112
5124
  conversations: conversations2,
5113
5125
  sites,
5114
5126
  recentPages,
5115
- activeMinds: getActiveMinds()
5127
+ activeMinds: getActiveMinds(),
5128
+ onlineBrains: getOnlineBrains()
5116
5129
  };
5117
5130
  const snapshotId = bufferEvent(snapshotData);
5118
5131
  await stream.writeSSE({
@@ -5153,8 +5166,8 @@ var app22 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
5153
5166
  });
5154
5167
  }, 15e3);
5155
5168
  cleanups.push(() => clearInterval(keepAlive));
5156
- await new Promise((resolve18) => {
5157
- stream.onAbort(() => resolve18());
5169
+ await new Promise((resolve20) => {
5170
+ stream.onAbort(() => resolve20());
5158
5171
  });
5159
5172
  } finally {
5160
5173
  for (const cleanup of cleanups) {
@@ -5166,19 +5179,19 @@ var app22 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
5166
5179
  }
5167
5180
  });
5168
5181
  });
5169
- var events_default = app22;
5182
+ var events_default = app23;
5170
5183
 
5171
5184
  // src/web/api/variants.ts
5172
- import { existsSync as existsSync11, mkdirSync as mkdirSync7, writeFileSync as writeFileSync7 } from "fs";
5173
- import { resolve as resolve15 } from "path";
5174
- import { Hono as Hono23 } from "hono";
5185
+ import { existsSync as existsSync12, mkdirSync as mkdirSync8, writeFileSync as writeFileSync8 } from "fs";
5186
+ import { resolve as resolve17 } from "path";
5187
+ import { Hono as Hono24 } from "hono";
5175
5188
 
5176
5189
  // src/lib/spawn-server.ts
5177
5190
  import { spawn as spawn3 } from "child_process";
5178
- import { closeSync, mkdirSync as mkdirSync6, openSync, readFileSync as readFileSync9 } from "fs";
5179
- import { resolve as resolve14 } from "path";
5191
+ import { closeSync, mkdirSync as mkdirSync7, openSync, readFileSync as readFileSync10 } from "fs";
5192
+ import { resolve as resolve16 } from "path";
5180
5193
  function tsxBin(cwd) {
5181
- return resolve14(cwd, "node_modules", ".bin", "tsx");
5194
+ return resolve16(cwd, "node_modules", ".bin", "tsx");
5182
5195
  }
5183
5196
  function spawnServer(cwd, port, options) {
5184
5197
  if (options?.detached) {
@@ -5191,31 +5204,31 @@ function spawnAttached(cwd, port) {
5191
5204
  cwd,
5192
5205
  stdio: ["ignore", "pipe", "pipe"]
5193
5206
  });
5194
- return new Promise((resolve18) => {
5195
- const timeout = setTimeout(() => resolve18(null), 3e4);
5207
+ return new Promise((resolve20) => {
5208
+ const timeout = setTimeout(() => resolve20(null), 3e4);
5196
5209
  function checkOutput(data) {
5197
5210
  const match = data.toString().match(/listening on :(\d+)/);
5198
5211
  if (match) {
5199
5212
  clearTimeout(timeout);
5200
- resolve18({ child, actualPort: parseInt(match[1], 10) });
5213
+ resolve20({ child, actualPort: parseInt(match[1], 10) });
5201
5214
  }
5202
5215
  }
5203
5216
  child.stdout?.on("data", checkOutput);
5204
5217
  child.stderr?.on("data", checkOutput);
5205
5218
  child.on("error", () => {
5206
5219
  clearTimeout(timeout);
5207
- resolve18(null);
5220
+ resolve20(null);
5208
5221
  });
5209
5222
  child.on("exit", () => {
5210
5223
  clearTimeout(timeout);
5211
- resolve18(null);
5224
+ resolve20(null);
5212
5225
  });
5213
5226
  });
5214
5227
  }
5215
5228
  function spawnDetached(cwd, port, logDir) {
5216
- const logsDir = logDir ?? resolve14(cwd, ".mind", "logs");
5217
- mkdirSync6(logsDir, { recursive: true });
5218
- const logPath = resolve14(logsDir, "mind.log");
5229
+ const logsDir = logDir ?? resolve16(cwd, ".mind", "logs");
5230
+ mkdirSync7(logsDir, { recursive: true });
5231
+ const logPath = resolve16(logsDir, "mind.log");
5219
5232
  const logFd = openSync(logPath, "a");
5220
5233
  const child = spawn3(tsxBin(cwd), ["src/server.ts", "--port", String(port)], {
5221
5234
  cwd,
@@ -5235,7 +5248,7 @@ function spawnDetached(cwd, port, logDir) {
5235
5248
  }
5236
5249
  const interval = setInterval(() => {
5237
5250
  try {
5238
- const content = readFileSync9(logPath, "utf-8");
5251
+ const content = readFileSync10(logPath, "utf-8");
5239
5252
  const match = content.match(/listening on :(\d+)/);
5240
5253
  if (match) {
5241
5254
  finish({ child, actualPort: parseInt(match[1], 10) });
@@ -5287,7 +5300,7 @@ async function verify2(port) {
5287
5300
  }
5288
5301
 
5289
5302
  // src/web/api/variants.ts
5290
- var app23 = new Hono23().get("/:name/variants", async (c) => {
5303
+ var app24 = new Hono24().get("/:name/variants", async (c) => {
5291
5304
  const name = c.req.param("name");
5292
5305
  const entry = findMind(name);
5293
5306
  if (!entry) return c.json({ error: "Mind not found" }, 404);
@@ -5327,11 +5340,11 @@ var app23 = new Hono23().get("/:name/variants", async (c) => {
5327
5340
  const err = validateBranchName(variantName);
5328
5341
  if (err) return c.json({ error: err }, 400);
5329
5342
  const projectRoot = mindDir(mindName);
5330
- const variantDir = resolve15(projectRoot, ".variants", variantName);
5331
- if (existsSync11(variantDir)) {
5343
+ const variantDir = resolve17(projectRoot, ".variants", variantName);
5344
+ if (existsSync12(variantDir)) {
5332
5345
  return c.json({ error: `Variant directory already exists: ${variantDir}` }, 409);
5333
5346
  }
5334
- mkdirSync7(resolve15(projectRoot, ".variants"), { recursive: true });
5347
+ mkdirSync8(resolve17(projectRoot, ".variants"), { recursive: true });
5335
5348
  try {
5336
5349
  await gitExec(["worktree", "add", "-b", variantName, variantDir], { cwd: projectRoot });
5337
5350
  } catch (e) {
@@ -5344,7 +5357,7 @@ var app23 = new Hono23().get("/:name/variants", async (c) => {
5344
5357
  const [cmd, args] = wrapForIsolation("npm", ["install"], mindName);
5345
5358
  await exec(cmd, args, {
5346
5359
  cwd: variantDir,
5347
- env: { ...process.env, HOME: resolve15(variantDir, "home") }
5360
+ env: { ...process.env, HOME: resolve17(variantDir, "home") }
5348
5361
  });
5349
5362
  } else {
5350
5363
  await exec("npm", ["install"], { cwd: variantDir });
@@ -5354,7 +5367,7 @@ var app23 = new Hono23().get("/:name/variants", async (c) => {
5354
5367
  return c.json({ error: `npm install failed: ${msg}` }, 500);
5355
5368
  }
5356
5369
  if (body.soul) {
5357
- writeFileSync7(resolve15(variantDir, "home/SOUL.md"), body.soul);
5370
+ writeFileSync8(resolve17(variantDir, "home/SOUL.md"), body.soul);
5358
5371
  }
5359
5372
  const variantPort = body.port ?? nextPort();
5360
5373
  const variant = {
@@ -5392,7 +5405,7 @@ var app23 = new Hono23().get("/:name/variants", async (c) => {
5392
5405
  } catch {
5393
5406
  }
5394
5407
  const projectRoot = mindDir(mindName);
5395
- if (existsSync11(variant.path)) {
5408
+ if (existsSync12(variant.path)) {
5396
5409
  const status = (await gitExec(["status", "--porcelain"], { cwd: variant.path })).trim();
5397
5410
  if (status) {
5398
5411
  try {
@@ -5465,7 +5478,7 @@ var app23 = new Hono23().get("/:name/variants", async (c) => {
5465
5478
  const [cmd, args] = wrapForIsolation("npm", ["install"], mindName);
5466
5479
  await exec(cmd, args, {
5467
5480
  cwd: projectRoot,
5468
- env: { ...process.env, HOME: resolve15(projectRoot, "home") }
5481
+ env: { ...process.env, HOME: resolve17(projectRoot, "home") }
5469
5482
  });
5470
5483
  } else {
5471
5484
  await exec("npm", ["install"], { cwd: projectRoot });
@@ -5503,19 +5516,19 @@ var app23 = new Hono23().get("/:name/variants", async (c) => {
5503
5516
  await cleanupVariant(mindName, variantName, projectRoot, variant.path, { stop: true });
5504
5517
  return c.json({ ok: true });
5505
5518
  });
5506
- var variants_default = app23;
5519
+ var variants_default = app24;
5507
5520
 
5508
5521
  // src/web/api/volute/channels.ts
5509
- import { zValidator as zValidator8 } from "@hono/zod-validator";
5510
- import { Hono as Hono24 } from "hono";
5511
- import { z as z8 } from "zod";
5512
- var createSchema2 = z8.object({
5513
- name: z8.string().min(1).max(50).regex(/^[a-z0-9][a-z0-9-]*$/, "Channel names must be lowercase alphanumeric with hyphens")
5522
+ import { zValidator as zValidator9 } from "@hono/zod-validator";
5523
+ import { Hono as Hono25 } from "hono";
5524
+ import { z as z9 } from "zod";
5525
+ var createSchema2 = z9.object({
5526
+ name: z9.string().min(1).max(50).regex(/^[a-z0-9][a-z0-9-]*$/, "Channel names must be lowercase alphanumeric with hyphens")
5514
5527
  });
5515
- var inviteSchema = z8.object({
5516
- username: z8.string().min(1)
5528
+ var inviteSchema = z9.object({
5529
+ username: z9.string().min(1)
5517
5530
  });
5518
- var app24 = new Hono24().get("/", async (c) => {
5531
+ var app25 = new Hono25().get("/", async (c) => {
5519
5532
  const user = c.get("user");
5520
5533
  const channels = await listChannels();
5521
5534
  const results = await Promise.all(
@@ -5526,7 +5539,7 @@ var app24 = new Hono24().get("/", async (c) => {
5526
5539
  })
5527
5540
  );
5528
5541
  return c.json(results);
5529
- }).post("/", zValidator8("json", createSchema2), async (c) => {
5542
+ }).post("/", zValidator9("json", createSchema2), async (c) => {
5530
5543
  const user = c.get("user");
5531
5544
  const body = c.req.valid("json");
5532
5545
  try {
@@ -5559,7 +5572,7 @@ var app24 = new Hono24().get("/", async (c) => {
5559
5572
  if (!ch) return c.json({ error: "Channel not found" }, 404);
5560
5573
  const participants = await getParticipants(ch.id);
5561
5574
  return c.json(participants);
5562
- }).post("/:name/invite", zValidator8("json", inviteSchema), async (c) => {
5575
+ }).post("/:name/invite", zValidator9("json", inviteSchema), async (c) => {
5563
5576
  const name = c.req.param("name");
5564
5577
  const inviter = c.get("user");
5565
5578
  const { username } = c.req.valid("json");
@@ -5579,21 +5592,21 @@ var app24 = new Hono24().get("/", async (c) => {
5579
5592
  ]);
5580
5593
  return c.json({ ok: true });
5581
5594
  });
5582
- var channels_default2 = app24;
5595
+ var channels_default2 = app25;
5583
5596
 
5584
5597
  // src/web/api/volute/chat.ts
5585
- import { zValidator as zValidator9 } from "@hono/zod-validator";
5586
- import { Hono as Hono25 } from "hono";
5598
+ import { zValidator as zValidator10 } from "@hono/zod-validator";
5599
+ import { Hono as Hono26 } from "hono";
5587
5600
  import { streamSSE as streamSSE6 } from "hono/streaming";
5588
- import { z as z9 } from "zod";
5601
+ import { z as z10 } from "zod";
5589
5602
  async function fanOutToMinds2(opts) {
5590
5603
  const participants = await getParticipants(opts.conversationId);
5591
5604
  const mindParticipants = participants.filter((p) => p.userType === "mind");
5592
5605
  const participantNames = participants.map((p) => p.username);
5593
5606
  const isDM = opts.isDM ?? participants.length === 2;
5594
5607
  const channelEntryType = opts.channelEntryType ?? (isDM ? "dm" : "group");
5595
- const { getMindManager: getMindManager2 } = await import("./mind-manager-KMY4GA2J.js");
5596
- const { getSleepManagerIfReady } = await import("./sleep-manager-2TMQ65E4.js");
5608
+ const { getMindManager: getMindManager2 } = await import("./mind-manager-4NDNAYAB.js");
5609
+ const { getSleepManagerIfReady } = await import("./sleep-manager-RKTFZPD3.js");
5597
5610
  const manager = getMindManager2();
5598
5611
  const sm = getSleepManagerIfReady();
5599
5612
  const targetMinds = mindParticipants.map((ap) => {
@@ -5641,18 +5654,18 @@ async function fanOutToMinds2(opts) {
5641
5654
  });
5642
5655
  }
5643
5656
  }
5644
- var chatSchema = z9.object({
5645
- message: z9.string().optional(),
5646
- conversationId: z9.string().optional(),
5647
- sender: z9.string().optional(),
5648
- images: z9.array(
5649
- z9.object({
5650
- media_type: z9.string(),
5651
- data: z9.string()
5657
+ var chatSchema = z10.object({
5658
+ message: z10.string().optional(),
5659
+ conversationId: z10.string().optional(),
5660
+ sender: z10.string().optional(),
5661
+ images: z10.array(
5662
+ z10.object({
5663
+ media_type: z10.string(),
5664
+ data: z10.string()
5652
5665
  })
5653
5666
  ).optional()
5654
5667
  });
5655
- var app25 = new Hono25().post("/:name/chat", zValidator9("json", chatSchema), async (c) => {
5668
+ var app26 = new Hono26().post("/:name/chat", zValidator10("json", chatSchema), async (c) => {
5656
5669
  const name = c.req.param("name");
5657
5670
  const [baseName] = name.split("@", 2);
5658
5671
  const entry = findMind(baseName);
@@ -5710,11 +5723,15 @@ var app25 = new Hono25().post("/:name/chat", zValidator9("json", chatSchema), as
5710
5723
  }
5711
5724
  }
5712
5725
  await addMessage(conversationId, "user", senderName, contentBlocks);
5726
+ const isDM = conv?.type === "dm";
5713
5727
  await fanOutToMinds2({
5714
5728
  conversationId,
5715
5729
  contentBlocks,
5716
5730
  senderName,
5717
5731
  convTitle,
5732
+ isDM,
5733
+ channelEntryType: conv?.type === "channel" ? "group" : isDM ? "dm" : "group",
5734
+ slugExtra: conv ? { convType: conv.type, convName: conv.name } : void 0,
5718
5735
  targetName: (username) => username === baseName ? name : username
5719
5736
  });
5720
5737
  return c.json({ ok: true, conversationId });
@@ -5735,23 +5752,23 @@ var app25 = new Hono25().post("/:name/chat", zValidator9("json", chatSchema), as
5735
5752
  if (!stream.aborted) console.error("[chat] SSE ping error:", err);
5736
5753
  });
5737
5754
  }, 15e3);
5738
- await new Promise((resolve18) => {
5755
+ await new Promise((resolve20) => {
5739
5756
  stream.onAbort(() => {
5740
5757
  unsubscribe();
5741
5758
  clearInterval(keepAlive);
5742
- resolve18();
5759
+ resolve20();
5743
5760
  });
5744
5761
  });
5745
5762
  });
5746
5763
  });
5747
- var unifiedChatSchema2 = z9.object({
5748
- message: z9.string().optional(),
5749
- conversationId: z9.string(),
5750
- images: z9.array(z9.object({ media_type: z9.string(), data: z9.string() })).optional()
5764
+ var unifiedChatSchema2 = z10.object({
5765
+ message: z10.string().optional(),
5766
+ conversationId: z10.string(),
5767
+ images: z10.array(z10.object({ media_type: z10.string(), data: z10.string() })).optional()
5751
5768
  });
5752
- var unifiedChatApp = new Hono25().post(
5769
+ var unifiedChatApp = new Hono26().post(
5753
5770
  "/chat",
5754
- zValidator9("json", unifiedChatSchema2),
5771
+ zValidator10("json", unifiedChatSchema2),
5755
5772
  async (c) => {
5756
5773
  const user = c.get("user");
5757
5774
  const body = c.req.valid("json");
@@ -5785,18 +5802,18 @@ var unifiedChatApp = new Hono25().post(
5785
5802
  return c.json({ ok: true, conversationId: body.conversationId });
5786
5803
  }
5787
5804
  );
5788
- var chat_default2 = app25;
5805
+ var chat_default2 = app26;
5789
5806
 
5790
5807
  // src/web/api/volute/conversations.ts
5791
- import { zValidator as zValidator10 } from "@hono/zod-validator";
5792
- import { Hono as Hono26 } from "hono";
5793
- import { z as z10 } from "zod";
5794
- var createConvSchema = z10.object({
5795
- title: z10.string().optional(),
5796
- participantIds: z10.array(z10.number()).optional(),
5797
- participantNames: z10.array(z10.string()).optional()
5808
+ import { zValidator as zValidator11 } from "@hono/zod-validator";
5809
+ import { Hono as Hono27 } from "hono";
5810
+ import { z as z11 } from "zod";
5811
+ var createConvSchema = z11.object({
5812
+ title: z11.string().optional(),
5813
+ participantIds: z11.array(z11.number()).optional(),
5814
+ participantNames: z11.array(z11.string()).optional()
5798
5815
  });
5799
- var app26 = new Hono26().get("/:name/conversations", async (c) => {
5816
+ var app27 = new Hono27().get("/:name/conversations", async (c) => {
5800
5817
  const name = c.req.param("name");
5801
5818
  const user = c.get("user");
5802
5819
  let lookupId = user.id;
@@ -5807,7 +5824,7 @@ var app26 = new Hono26().get("/:name/conversations", async (c) => {
5807
5824
  const all = await listConversationsForUser(lookupId);
5808
5825
  const convs = all.filter((c2) => c2.mind_name === name || c2.type === "channel");
5809
5826
  return c.json(convs);
5810
- }).post("/:name/conversations", zValidator10("json", createConvSchema), async (c) => {
5827
+ }).post("/:name/conversations", zValidator11("json", createConvSchema), async (c) => {
5811
5828
  const name = c.req.param("name");
5812
5829
  const user = c.get("user");
5813
5830
  const body = c.req.valid("json");
@@ -5881,18 +5898,18 @@ var app26 = new Hono26().get("/:name/conversations", async (c) => {
5881
5898
  if (!deleted) return c.json({ error: "Conversation not found" }, 404);
5882
5899
  return c.json({ ok: true });
5883
5900
  });
5884
- var conversations_default2 = app26;
5901
+ var conversations_default2 = app27;
5885
5902
 
5886
5903
  // src/web/api/volute/user-conversations.ts
5887
- import { zValidator as zValidator11 } from "@hono/zod-validator";
5888
- import { Hono as Hono27 } from "hono";
5904
+ import { zValidator as zValidator12 } from "@hono/zod-validator";
5905
+ import { Hono as Hono28 } from "hono";
5889
5906
  import { streamSSE as streamSSE7 } from "hono/streaming";
5890
- import { z as z11 } from "zod";
5891
- var createSchema3 = z11.object({
5892
- title: z11.string().optional(),
5893
- participantNames: z11.array(z11.string()).min(1)
5907
+ import { z as z12 } from "zod";
5908
+ var createSchema3 = z12.object({
5909
+ title: z12.string().optional(),
5910
+ participantNames: z12.array(z12.string()).min(1)
5894
5911
  });
5895
- var app27 = new Hono27().use("*", authMiddleware).get("/", async (c) => {
5912
+ var app28 = new Hono28().use("*", authMiddleware).get("/", async (c) => {
5896
5913
  const user = c.get("user");
5897
5914
  const convs = await listConversationsWithParticipants(user.id);
5898
5915
  return c.json(convs);
@@ -5904,7 +5921,7 @@ var app27 = new Hono27().use("*", authMiddleware).get("/", async (c) => {
5904
5921
  }
5905
5922
  const msgs = await getMessages(id);
5906
5923
  return c.json(msgs);
5907
- }).post("/", zValidator11("json", createSchema3), async (c) => {
5924
+ }).post("/", zValidator12("json", createSchema3), async (c) => {
5908
5925
  const user = c.get("user");
5909
5926
  const body = c.req.valid("json");
5910
5927
  const participantIds = /* @__PURE__ */ new Set();
@@ -5951,11 +5968,11 @@ var app27 = new Hono27().use("*", authMiddleware).get("/", async (c) => {
5951
5968
  if (!stream.aborted) console.error("[chat] SSE ping error:", err);
5952
5969
  });
5953
5970
  }, 15e3);
5954
- await new Promise((resolve18) => {
5971
+ await new Promise((resolve20) => {
5955
5972
  stream.onAbort(() => {
5956
5973
  unsubscribe();
5957
5974
  clearInterval(keepAlive);
5958
- resolve18();
5975
+ resolve20();
5959
5976
  });
5960
5977
  });
5961
5978
  });
@@ -5966,12 +5983,12 @@ var app27 = new Hono27().use("*", authMiddleware).get("/", async (c) => {
5966
5983
  if (!deleted) return c.json({ error: "Conversation not found" }, 404);
5967
5984
  return c.json({ ok: true });
5968
5985
  });
5969
- var user_conversations_default = app27;
5986
+ var user_conversations_default = app28;
5970
5987
 
5971
5988
  // src/web/app.ts
5972
5989
  var httpLog = logger_default.child("http");
5973
- var app28 = new Hono28();
5974
- app28.onError((err, c) => {
5990
+ var app29 = new Hono29();
5991
+ app29.onError((err, c) => {
5975
5992
  if (err instanceof HTTPException) {
5976
5993
  return err.getResponse();
5977
5994
  }
@@ -5982,10 +5999,10 @@ app28.onError((err, c) => {
5982
5999
  });
5983
6000
  return c.json({ error: "Internal server error" }, 500);
5984
6001
  });
5985
- app28.notFound((c) => {
6002
+ app29.notFound((c) => {
5986
6003
  return c.json({ error: "Not found" }, 404);
5987
6004
  });
5988
- app28.use("*", async (c, next) => {
6005
+ app29.use("*", async (c, next) => {
5989
6006
  const start = Date.now();
5990
6007
  await next();
5991
6008
  const duration = Date.now() - start;
@@ -5996,7 +6013,7 @@ app28.use("*", async (c, next) => {
5996
6013
  httpLog.debug("request", data);
5997
6014
  }
5998
6015
  });
5999
- app28.get("/api/health", (c) => {
6016
+ app29.get("/api/health", (c) => {
6000
6017
  let version = "unknown";
6001
6018
  let cached = null;
6002
6019
  try {
@@ -6011,38 +6028,39 @@ app28.get("/api/health", (c) => {
6011
6028
  ...cached?.updateAvailable ? { updateAvailable: true, latest: cached.latest } : {}
6012
6029
  });
6013
6030
  });
6014
- app28.use("/api/*", bodyLimit({ maxSize: 10 * 1024 * 1024 }));
6015
- app28.use("/api/*", csrf());
6016
- app28.use("/api/activity/*", authMiddleware);
6017
- app28.use("/api/minds/*", authMiddleware);
6018
- app28.use("/api/conversations/*", authMiddleware);
6019
- app28.use("/api/volute/*", authMiddleware);
6020
- app28.use("/api/system/*", authMiddleware);
6021
- app28.use("/api/env/*", authMiddleware);
6022
- app28.use("/api/prompts/*", authMiddleware);
6023
- app28.use("/api/skills/*", authMiddleware);
6024
- app28.use("/api/v1/*", authMiddleware);
6025
- app28.route("/pages", pages_default);
6026
- 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);
6027
- app28.route("/api/v1/minds", minds_default);
6028
- app28.route("/api/v1/minds", typing_default);
6029
- app28.route("/api/v1/minds", variants_default);
6030
- app28.route("/api/v1/minds", files_default);
6031
- app28.route("/api/v1/minds", env_default);
6032
- app28.route("/api/v1/minds", mind_skills_default);
6033
- app28.route("/api/v1/minds", connectors_default);
6034
- app28.route("/api/v1/minds", schedules_default);
6035
- app28.route("/api/v1/minds", logs_default);
6036
- app28.route("/api/v1/system", system_default);
6037
- app28.route("/api/v1/system", update_default);
6038
- app28.route("/api/v1/prompts", prompts_default);
6039
- app28.route("/api/v1/skills", skills_default);
6040
- app28.route("/api/v1/env", sharedEnvApp);
6041
- app28.route("/api/v1/channels", channels_default2);
6042
- var app_default = app28;
6031
+ app29.use("/api/*", bodyLimit({ maxSize: 10 * 1024 * 1024 }));
6032
+ app29.use("/api/*", csrf());
6033
+ app29.use("/api/activity/*", authMiddleware);
6034
+ app29.use("/api/minds/*", authMiddleware);
6035
+ app29.use("/api/conversations/*", authMiddleware);
6036
+ app29.use("/api/volute/*", authMiddleware);
6037
+ app29.use("/api/system/*", authMiddleware);
6038
+ app29.use("/api/env/*", authMiddleware);
6039
+ app29.use("/api/prompts/*", authMiddleware);
6040
+ app29.use("/api/skills/*", authMiddleware);
6041
+ app29.use("/api/v1/*", authMiddleware);
6042
+ app29.route("/pages", pages_default);
6043
+ app29.route("/public", public_files_default);
6044
+ var routes = app29.route("/api/activity", activity_default).route("/api/keys", keys_default).route("/api/auth", auth_default).route("/api/system", system_default).route("/api/system", update_default).route("/api/minds", minds_default).route("/api/minds", chat_default2).route("/api/minds", 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);
6045
+ app29.route("/api/v1/minds", minds_default);
6046
+ app29.route("/api/v1/minds", typing_default);
6047
+ app29.route("/api/v1/minds", variants_default);
6048
+ app29.route("/api/v1/minds", files_default);
6049
+ app29.route("/api/v1/minds", env_default);
6050
+ app29.route("/api/v1/minds", mind_skills_default);
6051
+ app29.route("/api/v1/minds", connectors_default);
6052
+ app29.route("/api/v1/minds", schedules_default);
6053
+ app29.route("/api/v1/minds", logs_default);
6054
+ app29.route("/api/v1/system", system_default);
6055
+ app29.route("/api/v1/system", update_default);
6056
+ app29.route("/api/v1/prompts", prompts_default);
6057
+ app29.route("/api/v1/skills", skills_default);
6058
+ app29.route("/api/v1/env", sharedEnvApp);
6059
+ app29.route("/api/v1/channels", channels_default2);
6060
+ var app_default = app29;
6043
6061
 
6044
6062
  // src/web/server.ts
6045
- var MIME_TYPES2 = {
6063
+ var MIME_TYPES3 = {
6046
6064
  ".html": "text/html",
6047
6065
  ".js": "application/javascript",
6048
6066
  ".css": "text/css",
@@ -6053,13 +6071,14 @@ var MIME_TYPES2 = {
6053
6071
  };
6054
6072
  async function startServer({
6055
6073
  port,
6056
- hostname = "127.0.0.1"
6074
+ hostname = "127.0.0.1",
6075
+ tls
6057
6076
  }) {
6058
6077
  let assetsDir = "";
6059
6078
  let searchDir = dirname(new URL(import.meta.url).pathname);
6060
6079
  for (let i = 0; i < 5; i++) {
6061
- const candidate = resolve16(searchDir, "dist", "web-assets");
6062
- if (existsSync12(candidate)) {
6080
+ const candidate = resolve18(searchDir, "dist", "web-assets");
6081
+ if (existsSync13(candidate)) {
6063
6082
  assetsDir = candidate;
6064
6083
  break;
6065
6084
  }
@@ -6069,40 +6088,73 @@ async function startServer({
6069
6088
  app_default.get("*", async (c) => {
6070
6089
  const urlPath = new URL(c.req.url).pathname;
6071
6090
  if (urlPath.startsWith("/api/")) return c.notFound();
6072
- const filePath = resolve16(assetsDir, urlPath.slice(1));
6091
+ const filePath = resolve18(assetsDir, urlPath.slice(1));
6073
6092
  if (!filePath.startsWith(assetsDir)) return c.text("Forbidden", 403);
6074
- const s = await stat3(filePath).catch(() => null);
6093
+ const s = await stat4(filePath).catch(() => null);
6075
6094
  if (s?.isFile()) {
6076
- const ext = extname3(filePath);
6077
- const mime = MIME_TYPES2[ext] || "application/octet-stream";
6078
- const body = await readFile3(filePath);
6095
+ const ext = extname5(filePath);
6096
+ const mime = MIME_TYPES3[ext] || "application/octet-stream";
6097
+ const body = await readFile4(filePath);
6079
6098
  return c.body(body, 200, { "Content-Type": mime });
6080
6099
  }
6081
- const indexPath = resolve16(assetsDir, "index.html");
6082
- const indexStat = await stat3(indexPath).catch(() => null);
6100
+ const indexPath = resolve18(assetsDir, "index.html");
6101
+ const indexStat = await stat4(indexPath).catch(() => null);
6083
6102
  if (indexStat?.isFile()) {
6084
- const body = await readFile3(indexPath, "utf-8");
6103
+ const body = await readFile4(indexPath, "utf-8");
6085
6104
  return c.html(body);
6086
6105
  }
6087
6106
  return c.text("Not found", 404);
6088
6107
  });
6089
6108
  }
6109
+ if (tls) {
6110
+ const server2 = serve({
6111
+ fetch: app_default.fetch,
6112
+ port,
6113
+ hostname,
6114
+ createServer: createHttpsServer,
6115
+ serverOptions: { key: tls.key, cert: tls.cert }
6116
+ });
6117
+ await new Promise((resolve20, reject) => {
6118
+ server2.on("listening", () => {
6119
+ logger_default.info("Volute UI running (https)", { hostname, port });
6120
+ resolve20();
6121
+ });
6122
+ server2.on("error", (err) => {
6123
+ reject(err);
6124
+ });
6125
+ });
6126
+ const internalPort = port + 1;
6127
+ const internalServer = serve({ fetch: app_default.fetch, port: internalPort, hostname: "127.0.0.1" });
6128
+ await new Promise((resolve20, reject) => {
6129
+ internalServer.on("listening", () => {
6130
+ logger_default.info("Volute API running (http, internal)", {
6131
+ hostname: "127.0.0.1",
6132
+ port: internalPort
6133
+ });
6134
+ resolve20();
6135
+ });
6136
+ internalServer.on("error", (err) => {
6137
+ reject(err);
6138
+ });
6139
+ });
6140
+ return { server: server2, internalPort };
6141
+ }
6090
6142
  const server = serve({ fetch: app_default.fetch, port, hostname });
6091
- await new Promise((resolve18, reject) => {
6143
+ await new Promise((resolve20, reject) => {
6092
6144
  server.on("listening", () => {
6093
- logger_default.info("Volute UI running", { hostname, port });
6094
- resolve18();
6145
+ logger_default.info("Volute API running (http)", { hostname, port });
6146
+ resolve20();
6095
6147
  });
6096
6148
  server.on("error", (err) => {
6097
6149
  reject(err);
6098
6150
  });
6099
6151
  });
6100
- return server;
6152
+ return { server };
6101
6153
  }
6102
6154
 
6103
6155
  // src/daemon.ts
6104
6156
  if (!process.env.VOLUTE_HOME) {
6105
- process.env.VOLUTE_HOME = resolve17(homedir2(), ".volute");
6157
+ process.env.VOLUTE_HOME = resolve19(homedir2(), ".volute");
6106
6158
  }
6107
6159
  if (process.env.VOLUTE_TIMEZONE && !process.env.TZ) {
6108
6160
  process.env.TZ = process.env.VOLUTE_TIMEZONE;
@@ -6112,7 +6164,7 @@ async function startDaemon(opts) {
6112
6164
  const myPid = String(process.pid);
6113
6165
  const home = voluteHome();
6114
6166
  if (!opts.foreground) {
6115
- const rotatingLog = new RotatingLog(resolve17(home, "daemon.log"));
6167
+ const rotatingLog = new RotatingLog(resolve19(home, "daemon.log"));
6116
6168
  logger_default.setOutput((line) => rotatingLog.write(`${line}
6117
6169
  `));
6118
6170
  const write = (...args) => rotatingLog.write(`${format(...args)}
@@ -6122,9 +6174,9 @@ async function startDaemon(opts) {
6122
6174
  console.warn = write;
6123
6175
  console.info = write;
6124
6176
  }
6125
- const DAEMON_PID_PATH = resolve17(home, "daemon.pid");
6126
- const DAEMON_JSON_PATH = resolve17(home, "daemon.json");
6127
- mkdirSync8(home, { recursive: true });
6177
+ const DAEMON_PID_PATH = resolve19(home, "daemon.pid");
6178
+ const DAEMON_JSON_PATH = resolve19(home, "daemon.json");
6179
+ mkdirSync9(home, { recursive: true });
6128
6180
  migrateAgentsToMinds();
6129
6181
  try {
6130
6182
  await ensureSharedRepo();
@@ -6138,12 +6190,16 @@ async function startDaemon(opts) {
6138
6190
  logger_default.error("failed to sync built-in skills", logger_default.errorData(err));
6139
6191
  }
6140
6192
  const token = process.env.VOLUTE_DAEMON_TOKEN || randomBytes2(32).toString("hex");
6141
- process.env.VOLUTE_DAEMON_TOKEN = token;
6142
- process.env.VOLUTE_DAEMON_PORT = String(port);
6143
- process.env.VOLUTE_DAEMON_HOSTNAME = hostname;
6144
- let server;
6193
+ let tls;
6194
+ if (opts.tailscale) {
6195
+ const { getTailscaleTls } = await import("./tailscale-AJ4VL5XK.js");
6196
+ const tlsConfig = await getTailscaleTls();
6197
+ tls = { key: tlsConfig.key, cert: tlsConfig.cert };
6198
+ logger_default.info("Tailscale HTTPS enabled", { hostname: tlsConfig.hostname });
6199
+ }
6200
+ let result;
6145
6201
  try {
6146
- server = await startServer({ port, hostname });
6202
+ result = await startServer({ port, hostname: "0.0.0.0", tls });
6147
6203
  } catch (err) {
6148
6204
  const e = err;
6149
6205
  if (e.code === "EADDRINUSE") {
@@ -6152,11 +6208,17 @@ async function startDaemon(opts) {
6152
6208
  }
6153
6209
  throw err;
6154
6210
  }
6155
- writeFileSync8(DAEMON_PID_PATH, myPid, { mode: 420 });
6156
- writeFileSync8(DAEMON_JSON_PATH, `${JSON.stringify({ port, hostname, token }, null, 2)}
6157
- `, {
6158
- mode: 420
6159
- });
6211
+ const { server, internalPort } = result;
6212
+ const daemonPort = internalPort ?? port;
6213
+ process.env.VOLUTE_DAEMON_TOKEN = token;
6214
+ process.env.VOLUTE_DAEMON_PORT = String(daemonPort);
6215
+ process.env.VOLUTE_DAEMON_HOSTNAME = hostname;
6216
+ writeFileSync9(DAEMON_PID_PATH, myPid, { mode: 420 });
6217
+ const daemonConfig = { port, hostname, token };
6218
+ if (internalPort) daemonConfig.internalPort = internalPort;
6219
+ if (tls) daemonConfig.tls = true;
6220
+ writeFileSync9(DAEMON_JSON_PATH, `${JSON.stringify(daemonConfig, null, 2)}
6221
+ `, { mode: 420 });
6160
6222
  const delivery = initDeliveryManager();
6161
6223
  const manager = initMindManager();
6162
6224
  manager.loadCrashAttempts();
@@ -6172,11 +6234,12 @@ async function startDaemon(opts) {
6172
6234
  const unsubscribeWebhook = initWebhook();
6173
6235
  const registry = readRegistry();
6174
6236
  for (const entry of registry) {
6175
- try {
6176
- migrateDotVoluteDir(entry.name);
6177
- migrateMindState(entry.name);
6178
- } catch (err) {
6179
- logger_default.warn(`failed to migrate state for ${entry.name}`, logger_default.errorData(err));
6237
+ for (const migrate of [migrateDotVoluteDir, migrateMindState, migratePagesDirToPublic]) {
6238
+ try {
6239
+ migrate(entry.name);
6240
+ } catch (err) {
6241
+ logger_default.warn(`failed to migrate state for ${entry.name}`, logger_default.errorData(err));
6242
+ }
6180
6243
  }
6181
6244
  }
6182
6245
  const runningEntries = registry.filter((e) => e.running);
@@ -6188,8 +6251,8 @@ async function startDaemon(opts) {
6188
6251
  if (sleepManager.isSleeping(entry.name)) {
6189
6252
  try {
6190
6253
  const dir = mindDir(entry.name);
6191
- const daemonPort = process.env.VOLUTE_DAEMON_PORT ? parseInt(process.env.VOLUTE_DAEMON_PORT, 10) : void 0;
6192
- await connectors.startConnectors(entry.name, dir, entry.port, daemonPort);
6254
+ const daemonPort2 = process.env.VOLUTE_DAEMON_PORT ? parseInt(process.env.VOLUTE_DAEMON_PORT, 10) : void 0;
6255
+ await connectors.startConnectors(entry.name, dir, entry.port, daemonPort2);
6193
6256
  scheduler.loadSchedules(entry.name);
6194
6257
  } catch (err) {
6195
6258
  logger_default.error(
@@ -6226,7 +6289,7 @@ async function startDaemon(opts) {
6226
6289
  });
6227
6290
  await Promise.all(workers);
6228
6291
  }
6229
- import("./cloud-sync-PI47U2LT.js").then(
6292
+ import("./cloud-sync-PPBBJDY6.js").then(
6230
6293
  ({ consumeQueuedMessages }) => consumeQueuedMessages().catch((err) => {
6231
6294
  logger_default.warn("failed to consume queued cloud messages", logger_default.errorData(err));
6232
6295
  })
@@ -6234,7 +6297,7 @@ async function startDaemon(opts) {
6234
6297
  logger_default.warn("failed to load cloud-sync module", logger_default.errorData(err));
6235
6298
  });
6236
6299
  try {
6237
- const { backfillTemplateHashes, notifyVersionUpdate } = await import("./version-notify-LKABEJSA.js");
6300
+ const { backfillTemplateHashes, notifyVersionUpdate } = await import("./version-notify-AZQMC32A.js");
6238
6301
  backfillTemplateHashes();
6239
6302
  notifyVersionUpdate().catch((err) => {
6240
6303
  logger_default.warn("failed to send version update notifications", logger_default.errorData(err));
@@ -6251,13 +6314,13 @@ async function startDaemon(opts) {
6251
6314
  logger_default.info(`running on ${hostname}:${port}, pid ${myPid}`);
6252
6315
  function cleanup() {
6253
6316
  try {
6254
- if (readFileSync10(DAEMON_PID_PATH, "utf-8").trim() === myPid) {
6317
+ if (readFileSync11(DAEMON_PID_PATH, "utf-8").trim() === myPid) {
6255
6318
  unlinkSync(DAEMON_PID_PATH);
6256
6319
  }
6257
6320
  } catch {
6258
6321
  }
6259
6322
  try {
6260
- const data = JSON.parse(readFileSync10(DAEMON_JSON_PATH, "utf-8"));
6323
+ const data = JSON.parse(readFileSync11(DAEMON_JSON_PATH, "utf-8"));
6261
6324
  if (data.token === token) {
6262
6325
  unlinkSync(DAEMON_JSON_PATH);
6263
6326
  }
@@ -6271,9 +6334,9 @@ async function startDaemon(opts) {
6271
6334
  logger_default.info("shutting down...");
6272
6335
  const safe = (label, fn) => {
6273
6336
  try {
6274
- const result = fn();
6275
- if (result instanceof Promise)
6276
- return result.catch((err) => logger_default.error(`shutdown: ${label} failed`, logger_default.errorData(err)));
6337
+ const result2 = fn();
6338
+ if (result2 instanceof Promise)
6339
+ return result2.catch((err) => logger_default.error(`shutdown: ${label} failed`, logger_default.errorData(err)));
6277
6340
  } catch (err) {
6278
6341
  logger_default.error(`shutdown: ${label} failed`, logger_default.errorData(err));
6279
6342
  }
@@ -6305,9 +6368,10 @@ async function startDaemon(opts) {
6305
6368
  process.on("exit", cleanup);
6306
6369
  }
6307
6370
  if (import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith("daemon.ts")) {
6308
- let port = 4200;
6371
+ let port = 1618;
6309
6372
  let hostname = "127.0.0.1";
6310
6373
  let foreground = false;
6374
+ let tailscale = false;
6311
6375
  for (let i = 2; i < process.argv.length; i++) {
6312
6376
  if (process.argv[i] === "--port" && process.argv[i + 1]) {
6313
6377
  port = parseInt(process.argv[i + 1], 10);
@@ -6317,9 +6381,11 @@ if (import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith
6317
6381
  i++;
6318
6382
  } else if (process.argv[i] === "--foreground") {
6319
6383
  foreground = true;
6384
+ } else if (process.argv[i] === "--tailscale") {
6385
+ tailscale = true;
6320
6386
  }
6321
6387
  }
6322
- startDaemon({ port, hostname, foreground });
6388
+ startDaemon({ port, hostname, foreground, tailscale });
6323
6389
  }
6324
6390
  export {
6325
6391
  startDaemon