volute 0.27.0 → 0.29.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 (81) hide show
  1. package/README.md +20 -10
  2. package/dist/accept-666DIZX2.js +41 -0
  3. package/dist/api.d.ts +342 -143
  4. package/dist/{chat-MHJ3L6JQ.js → chat-KTPOR2JT.js} +18 -8
  5. package/dist/chunk-A6TUJJ3L.js +19 -0
  6. package/dist/{chunk-OQZH4PBB.js → chunk-CMILSHZD.js} +199 -277
  7. package/dist/{chunk-K5NAC55T.js → chunk-CQ7SNKNI.js} +1 -1
  8. package/dist/{chunk-PHSAT7YL.js → chunk-EHZKEMMV.js} +5 -5
  9. package/dist/{chunk-IAYBDWVG.js → chunk-FLZGS4QH.js} +145 -0
  10. package/dist/{chunk-USUXRNVD.js → chunk-J4IBNXGJ.js} +0 -2
  11. package/dist/chunk-MD4C26II.js +128 -0
  12. package/dist/{chunk-4WXYUOAK.js → chunk-NI5FFCCS.js} +8 -1
  13. package/dist/{chunk-JKOWNZ4P.js → chunk-P72MVS4R.js} +1 -40
  14. package/dist/chunk-THUUIU3E.js +232 -0
  15. package/dist/cli.js +21 -30
  16. package/dist/clock-DGCBVGYA.js +259 -0
  17. package/dist/{cloud-sync-T7M3ESC3.js → cloud-sync-KILFGV5Q.js} +7 -7
  18. package/dist/connectors/discord-bridge.js +1 -1
  19. package/dist/connectors/slack-bridge.js +1 -1
  20. package/dist/connectors/telegram-bridge.js +1 -1
  21. package/dist/{conversations-M2K4253F.js → conversations-P5BL7RMX.js} +7 -1
  22. package/dist/create-DFCAGEE5.js +70 -0
  23. package/dist/{daemon-restart-M2QTYMEG.js → daemon-restart-UHOMICXT.js} +1 -1
  24. package/dist/daemon.js +715 -661
  25. package/dist/files-M546TKVN.js +46 -0
  26. package/dist/{login-XX37I52P.js → login-BKP3AFWN.js} +7 -17
  27. package/dist/logout-IQK7FNEK.js +20 -0
  28. package/dist/{message-delivery-LDXLGERA.js → message-delivery-Q7VUMIEI.js} +11 -9
  29. package/dist/{mind-DI33C74K.js → mind-S5V6CK5W.js} +8 -13
  30. package/dist/{mind-activity-tracker-EN6XNXPF.js → mind-activity-tracker-WRHFI3YW.js} +1 -1
  31. package/dist/mind-list-UPJ75GPI.js +29 -0
  32. package/dist/{mind-manager-M6EMUW5I.js → mind-manager-P66HQDNE.js} +2 -2
  33. package/dist/mind-status-TK5AETEM.js +55 -0
  34. package/dist/{package-7WY6VKU3.js → package-OFKXNKJF.js} +1 -1
  35. package/dist/{pages-6EBS6CBR.js → pages-EUJR52AH.js} +5 -5
  36. package/dist/pages-watcher-P7QECRE2.js +21 -0
  37. package/dist/{publish-66UB2ZFY.js → publish-ZZB33WP4.js} +6 -17
  38. package/dist/{register-6B2CXTYM.js → register-CHREOMJ3.js} +5 -24
  39. package/dist/reject-LXIZFJ4Q.js +39 -0
  40. package/dist/{sandbox-TGBX22DS.js → sandbox-5BW5HPXM.js} +1 -1
  41. package/dist/{send-ZNCJDSRP.js → send-TAOEZ4NH.js} +64 -6
  42. package/dist/skills/dreaming/references/INSTALL.md +3 -17
  43. package/dist/skills/shared-files/SKILL.md +44 -0
  44. package/dist/skills/shared-files/scripts/merge.ts +72 -0
  45. package/dist/skills/shared-files/scripts/pull.ts +52 -0
  46. package/dist/skills/volute-mind/SKILL.md +48 -22
  47. package/dist/{sleep-manager-MWYHM5HV.js → sleep-manager-G4B5GW5P.js} +7 -7
  48. package/dist/{sprout-IJVVKSJ2.js → sprout-UNT7LKKE.js} +1 -1
  49. package/dist/{status-77YEPHMW.js → status-NQJYR4BG.js} +45 -1
  50. package/dist/{status-THLOBLWG.js → status-S7UUPNRW.js} +3 -13
  51. package/dist/systems-SMEFSHTA.js +60 -0
  52. package/dist/{up-NKSMXBWR.js → up-W6VAK2XE.js} +1 -1
  53. package/dist/{version-notify-5Z4MNR6M.js → version-notify-WDHRO3XD.js} +11 -11
  54. package/dist/web-assets/assets/index-BmKDnWDB.css +1 -0
  55. package/dist/web-assets/assets/index-CLJMx-GA.js +71 -0
  56. package/dist/web-assets/index.html +2 -2
  57. package/package.json +1 -1
  58. package/templates/_base/src/lib/logger.ts +10 -53
  59. package/templates/_base/src/lib/router.ts +1 -9
  60. package/templates/claude/src/lib/stream-consumer.ts +1 -4
  61. package/templates/pi/src/lib/event-handler.ts +1 -14
  62. package/dist/auth-D3OT2ARB.js +0 -37
  63. package/dist/chunk-KDGS53OS.js +0 -50
  64. package/dist/chunk-RWKVSSLY.js +0 -26
  65. package/dist/chunk-T6HKBWXZ.js +0 -23
  66. package/dist/create-D7J73A6H.js +0 -45
  67. package/dist/file-CR36YUPD.js +0 -204
  68. package/dist/log-ABYNVYJ3.js +0 -39
  69. package/dist/logout-W4KOOBIT.js +0 -18
  70. package/dist/logs-U35JR2KE.js +0 -77
  71. package/dist/merge-LNSMSAOF.js +0 -46
  72. package/dist/pull-XCHJTM5M.js +0 -39
  73. package/dist/schedule-QTJMFATP.js +0 -154
  74. package/dist/service-6LIN3F3K.js +0 -122
  75. package/dist/shared-ML5I4Q2A.js +0 -39
  76. package/dist/status-7GA4SM4Y.js +0 -35
  77. package/dist/web-assets/assets/index-CI5wgghI.css +0 -1
  78. package/dist/web-assets/assets/index-is5CvJWH.js +0 -75
  79. package/dist/{chunk-GIE6CSN5.js → chunk-DUAUMCEE.js} +0 -0
  80. package/dist/{history-XKRTAFS2.js → history-ALPTNB3I.js} +0 -0
  81. package/dist/{setup-JG4QAEBV.js → setup-RXYVGGT7.js} +3 -3
package/dist/daemon.js CHANGED
@@ -3,11 +3,8 @@ import {
3
3
  addSharedWorktree,
4
4
  ensureSharedRepo,
5
5
  removeSharedWorktree,
6
- sharedLog,
7
- sharedMerge,
8
- sharedPull,
9
- sharedStatus
10
- } from "./chunk-JKOWNZ4P.js";
6
+ sharedMerge
7
+ } from "./chunk-P72MVS4R.js";
11
8
  import {
12
9
  announceToSystem,
13
10
  approveUser,
@@ -15,15 +12,15 @@ import {
15
12
  countAdmins,
16
13
  createUser,
17
14
  deleteMindUser as deleteMindUser2,
15
+ deleteSystemsConfig,
18
16
  deleteUser,
19
17
  deliverMessage,
20
18
  ensureSystemChannel,
21
19
  extractTextContent,
22
- getCachedRecentPages,
23
- getCachedSites,
24
20
  getDeliveryManager,
25
21
  getOrCreateMindUser,
26
22
  getScheduler,
23
+ getSleepManagerIfReady,
27
24
  getTokenBudget,
28
25
  getTypingMap,
29
26
  getUser,
@@ -40,44 +37,26 @@ import {
40
37
  migrateMindRoles,
41
38
  publish as publish2,
42
39
  publishTypingForChannels,
40
+ readSystemsConfig,
43
41
  readVoluteConfig,
44
42
  recordInbound,
45
43
  resolveChannelId,
46
44
  setUserRole,
47
45
  splitMessage,
48
46
  startMindFull,
49
- stopAllWatchers,
50
47
  stopMindFull,
51
48
  subscribe as subscribe3,
52
49
  updateUserProfile,
53
50
  verifyUser,
54
51
  writeChannelEntry,
52
+ writeSystemsConfig,
55
53
  writeVoluteConfig
56
- } from "./chunk-OQZH4PBB.js";
54
+ } from "./chunk-CMILSHZD.js";
57
55
  import {
58
- deleteSystemsConfig,
59
- readSystemsConfig,
60
- writeSystemsConfig
61
- } from "./chunk-KDGS53OS.js";
62
- import {
63
- getActiveMinds,
64
- onMindEvent,
65
- stopAll
66
- } from "./chunk-K5NAC55T.js";
67
- import {
68
- PROMPT_DEFAULTS,
69
- PROMPT_KEYS,
70
- RestartTracker,
71
- RotatingLog,
72
- getMindManager,
73
- getMindPromptDefaults,
74
- getPrompt,
75
- getPromptIfCustom,
76
- initMindManager,
77
- resolveMindToken,
78
- substitute
79
- } from "./chunk-PHSAT7YL.js";
80
- import "./chunk-USUXRNVD.js";
56
+ getCachedRecentPages,
57
+ getCachedSites,
58
+ stopAllWatchers
59
+ } from "./chunk-THUUIU3E.js";
81
60
  import {
82
61
  addMessage,
83
62
  createChannel,
@@ -92,21 +71,24 @@ import {
92
71
  getParticipants,
93
72
  getUnreadCounts,
94
73
  initWebhook,
74
+ isConversationForMind,
95
75
  isParticipant,
96
76
  isParticipantOrOwner,
97
77
  joinChannel,
98
78
  leaveChannel,
99
79
  listChannels,
80
+ listConversationsForMind,
100
81
  listConversationsForUser,
101
82
  listConversationsWithParticipants,
102
83
  markConversationRead,
103
84
  publish,
104
85
  subscribe as subscribe2
105
- } from "./chunk-IAYBDWVG.js";
86
+ } from "./chunk-FLZGS4QH.js";
106
87
  import {
107
- broadcast,
108
- subscribe
109
- } from "./chunk-VIVMW2H2.js";
88
+ checkForUpdate,
89
+ checkForUpdateCached,
90
+ getCurrentVersion
91
+ } from "./chunk-HDN7MNGD.js";
110
92
  import {
111
93
  applyInitFiles,
112
94
  composeTemplate,
@@ -115,6 +97,29 @@ import {
115
97
  findTemplatesRoot,
116
98
  listFiles
117
99
  } from "./chunk-AKPFNL7L.js";
100
+ import {
101
+ getActiveMinds,
102
+ onMindEvent,
103
+ stopAll
104
+ } from "./chunk-CQ7SNKNI.js";
105
+ import {
106
+ broadcast,
107
+ subscribe
108
+ } from "./chunk-VIVMW2H2.js";
109
+ import {
110
+ PROMPT_DEFAULTS,
111
+ PROMPT_KEYS,
112
+ RestartTracker,
113
+ RotatingLog,
114
+ getMindManager,
115
+ getMindPromptDefaults,
116
+ getPrompt,
117
+ getPromptIfCustom,
118
+ initMindManager,
119
+ resolveMindToken,
120
+ substitute
121
+ } from "./chunk-EHZKEMMV.js";
122
+ import "./chunk-J4IBNXGJ.js";
118
123
  import {
119
124
  SEED_SKILLS,
120
125
  STANDARD_SKILLS,
@@ -130,12 +135,15 @@ import {
130
135
  syncBuiltinSkills,
131
136
  uninstallSkill,
132
137
  updateSkill
133
- } from "./chunk-4WXYUOAK.js";
138
+ } from "./chunk-NI5FFCCS.js";
134
139
  import {
135
- checkForUpdate,
136
- checkForUpdateCached,
137
- getCurrentVersion
138
- } from "./chunk-HDN7MNGD.js";
140
+ acceptPending,
141
+ formatFileSize,
142
+ listPending,
143
+ rejectPending,
144
+ stageFile,
145
+ validateFilePath
146
+ } from "./chunk-MD4C26II.js";
139
147
  import {
140
148
  findBridgeForChannel,
141
149
  findOpenClawSession,
@@ -182,7 +190,7 @@ import "./chunk-D424ZQGI.js";
182
190
  import {
183
191
  buildVoluteSlug,
184
192
  slugify
185
- } from "./chunk-T6HKBWXZ.js";
193
+ } from "./chunk-A6TUJJ3L.js";
186
194
  import {
187
195
  activity,
188
196
  addMind,
@@ -220,10 +228,10 @@ import {
220
228
  } from "./chunk-K3NQKI34.js";
221
229
 
222
230
  // src/daemon.ts
223
- import { randomBytes as randomBytes2 } from "crypto";
224
- import { mkdirSync as mkdirSync12, readFileSync as readFileSync14, unlinkSync as unlinkSync2, writeFileSync as writeFileSync11 } from "fs";
231
+ import { randomBytes } from "crypto";
232
+ import { mkdirSync as mkdirSync11, readFileSync as readFileSync13, unlinkSync as unlinkSync2, writeFileSync as writeFileSync10 } from "fs";
225
233
  import { homedir as homedir2 } from "os";
226
- import { resolve as resolve23 } from "path";
234
+ import { resolve as resolve22 } from "path";
227
235
  import { format } from "util";
228
236
 
229
237
  // src/lib/daemon/bridge-manager.ts
@@ -433,8 +441,8 @@ var BridgeManager = class {
433
441
  if (!tracked) return;
434
442
  this.stopping.add(platform);
435
443
  this.bridges.delete(platform);
436
- await new Promise((resolve24) => {
437
- tracked.child.on("exit", () => resolve24());
444
+ await new Promise((resolve23) => {
445
+ tracked.child.on("exit", () => resolve23());
438
446
  try {
439
447
  if (tracked.child.pid) {
440
448
  process.kill(-tracked.child.pid, "SIGTERM");
@@ -445,7 +453,7 @@ var BridgeManager = class {
445
453
  if (err instanceof Error && err.code !== "ESRCH") {
446
454
  blog.warn(`failed to stop bridge ${platform}`, logger_default.errorData(err));
447
455
  }
448
- resolve24();
456
+ resolve23();
449
457
  }
450
458
  setTimeout(() => {
451
459
  try {
@@ -456,7 +464,7 @@ var BridgeManager = class {
456
464
  }
457
465
  } catch {
458
466
  }
459
- resolve24();
467
+ resolve23();
460
468
  }, 5e3);
461
469
  });
462
470
  this.stopping.delete(platform);
@@ -544,6 +552,15 @@ function getBridgeManager() {
544
552
  return instance;
545
553
  }
546
554
 
555
+ // src/lib/history-cleanup.ts
556
+ import { and, eq, lt } from "drizzle-orm";
557
+ var LOG_RETENTION_MS = 24 * 60 * 60 * 1e3;
558
+ async function cleanExpiredLogs() {
559
+ const db = await getDb();
560
+ const cutoff = new Date(Date.now() - LOG_RETENTION_MS).toISOString().replace("T", " ").slice(0, 19);
561
+ await db.delete(mindHistory).where(and(eq(mindHistory.type, "log"), lt(mindHistory.created_at, cutoff)));
562
+ }
563
+
547
564
  // src/lib/migrate-agents-to-minds.ts
548
565
  import { execFileSync } from "child_process";
549
566
  import { existsSync as existsSync3, readFileSync as readFileSync3, renameSync, writeFileSync as writeFileSync2 } from "fs";
@@ -803,7 +820,7 @@ function migrateToSystemDir() {
803
820
 
804
821
  // src/web/middleware/auth.ts
805
822
  import { timingSafeEqual } from "crypto";
806
- import { eq, lt } from "drizzle-orm";
823
+ import { eq as eq2, lt as lt2 } from "drizzle-orm";
807
824
  import { getCookie } from "hono/cookie";
808
825
  import { createMiddleware } from "hono/factory";
809
826
  function isValidDaemonToken(token) {
@@ -826,14 +843,14 @@ async function createSession(userId) {
826
843
  async function deleteSession(sessionId) {
827
844
  sessionCache.delete(sessionId);
828
845
  const db = await getDb();
829
- await db.delete(sessions).where(eq(sessions.id, sessionId));
846
+ await db.delete(sessions).where(eq2(sessions.id, sessionId));
830
847
  }
831
848
  async function getSessionUserId(sessionId) {
832
849
  const db = await getDb();
833
- const row = await db.select().from(sessions).where(eq(sessions.id, sessionId)).get();
850
+ const row = await db.select().from(sessions).where(eq2(sessions.id, sessionId)).get();
834
851
  if (!row) return void 0;
835
852
  if (Date.now() - row.createdAt > SESSION_MAX_AGE) {
836
- await db.delete(sessions).where(eq(sessions.id, sessionId));
853
+ await db.delete(sessions).where(eq2(sessions.id, sessionId));
837
854
  return void 0;
838
855
  }
839
856
  return row.userId;
@@ -841,7 +858,7 @@ async function getSessionUserId(sessionId) {
841
858
  async function cleanExpiredSessions() {
842
859
  const db = await getDb();
843
860
  const cutoff = Date.now() - SESSION_MAX_AGE;
844
- await db.delete(sessions).where(lt(sessions.createdAt, cutoff));
861
+ await db.delete(sessions).where(lt2(sessions.createdAt, cutoff));
845
862
  }
846
863
  var requireAdmin = createMiddleware(async (c, next) => {
847
864
  const user = c.get("user");
@@ -923,10 +940,10 @@ var requireSelf = (paramName = "name") => createMiddleware(async (c, next) => {
923
940
  });
924
941
 
925
942
  // src/web/server.ts
926
- import { existsSync as existsSync17 } from "fs";
943
+ import { existsSync as existsSync16 } from "fs";
927
944
  import { readFile as readFile4, stat as stat4 } from "fs/promises";
928
945
  import { createServer as createHttpsServer } from "https";
929
- import { dirname as dirname2, extname as extname5, resolve as resolve22 } from "path";
946
+ import { dirname as dirname2, extname as extname5, resolve as resolve21 } from "path";
930
947
  import { serve } from "@hono/node-server";
931
948
 
932
949
  // src/web/app.ts
@@ -997,8 +1014,8 @@ var app = new Hono().get("/events", async (c) => {
997
1014
  });
998
1015
  }, 15e3);
999
1016
  cleanups.push(() => clearInterval(keepAlive));
1000
- await new Promise((resolve24) => {
1001
- stream.onAbort(() => resolve24());
1017
+ await new Promise((resolve23) => {
1018
+ stream.onAbort(() => resolve23());
1002
1019
  });
1003
1020
  } finally {
1004
1021
  for (const cleanup of cleanups) {
@@ -1277,7 +1294,7 @@ import { Hono as Hono3 } from "hono";
1277
1294
  import { z as z2 } from "zod";
1278
1295
 
1279
1296
  // src/lib/puppets.ts
1280
- import { and, eq as eq2 } from "drizzle-orm";
1297
+ import { and as and2, eq as eq3 } from "drizzle-orm";
1281
1298
  async function findOrCreatePuppet(platform, platformId, displayName) {
1282
1299
  const username = `${platform}:${platformId}`;
1283
1300
  const db = await getDb();
@@ -1286,10 +1303,10 @@ async function findOrCreatePuppet(platform, platformId, displayName) {
1286
1303
  username: users.username,
1287
1304
  display_name: users.display_name,
1288
1305
  avatar: users.avatar
1289
- }).from(users).where(and(eq2(users.username, username), eq2(users.user_type, "puppet"))).get();
1306
+ }).from(users).where(and2(eq3(users.username, username), eq3(users.user_type, "puppet"))).get();
1290
1307
  if (existing) {
1291
1308
  if (existing.display_name !== displayName) {
1292
- await db.update(users).set({ display_name: displayName }).where(eq2(users.id, existing.id));
1309
+ await db.update(users).set({ display_name: displayName }).where(eq3(users.id, existing.id));
1293
1310
  existing.display_name = displayName;
1294
1311
  }
1295
1312
  return existing;
@@ -1315,7 +1332,7 @@ async function findOrCreatePuppet(platform, platformId, displayName) {
1315
1332
  username: users.username,
1316
1333
  display_name: users.display_name,
1317
1334
  avatar: users.avatar
1318
- }).from(users).where(and(eq2(users.username, username), eq2(users.user_type, "puppet"))).get();
1335
+ }).from(users).where(and2(eq3(users.username, username), eq3(users.user_type, "puppet"))).get();
1319
1336
  if (retried) return retried;
1320
1337
  }
1321
1338
  throw err;
@@ -1397,7 +1414,7 @@ var app3 = new Hono3().post("/:platform/inbound", zValidator2("json", inboundSch
1397
1414
  }
1398
1415
  const participants = await getParticipants(channel.id);
1399
1416
  if (!participants.some((p) => p.userId === puppet.id)) {
1400
- const { addParticipant } = await import("./conversations-M2K4253F.js");
1417
+ const { addParticipant } = await import("./conversations-P5BL7RMX.js");
1401
1418
  await addParticipant(channel.id, puppet.id);
1402
1419
  }
1403
1420
  const contentBlocks = body.content;
@@ -1490,10 +1507,10 @@ async function fanOutToBridgedMinds(opts) {
1490
1507
  const participants = await getParticipants(opts.conversationId);
1491
1508
  const mindParticipants = participants.filter((p) => p.userType === "mind");
1492
1509
  const participantNames = participants.map((p) => p.username);
1493
- const { getMindManager: getMindManager2 } = await import("./mind-manager-M6EMUW5I.js");
1494
- const { getSleepManagerIfReady } = await import("./sleep-manager-MWYHM5HV.js");
1510
+ const { getMindManager: getMindManager2 } = await import("./mind-manager-P66HQDNE.js");
1511
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-G4B5GW5P.js");
1495
1512
  const manager = getMindManager2();
1496
- const sm = getSleepManagerIfReady();
1513
+ const sm = getSleepManagerIfReady2();
1497
1514
  const targetMinds = mindParticipants.filter((ap) => {
1498
1515
  return (manager.isRunning(ap.username) || sm?.isSleeping(ap.username)) && ap.username !== opts.senderName;
1499
1516
  }).map((ap) => ap.username);
@@ -1508,7 +1525,7 @@ async function fanOutToBridgedMinds(opts) {
1508
1525
  writeChannelEntry(mindName, channel, {
1509
1526
  platformId: opts.conversationId,
1510
1527
  platform: "volute",
1511
- type: opts.isDM ? "dm" : "group"
1528
+ type: opts.isDM ? "dm" : "channel"
1512
1529
  });
1513
1530
  } catch (err) {
1514
1531
  logger_default.warn(`failed to write channel entry for ${mindName}`, logger_default.errorData(err));
@@ -1636,7 +1653,7 @@ async function listConversations(env) {
1636
1653
  id: slug,
1637
1654
  platformId: dm.id,
1638
1655
  name: recipients.join(", ") || "DM",
1639
- type: dm.type === 1 ? "dm" : "group"
1656
+ type: dm.type === 1 ? "dm" : "channel"
1640
1657
  });
1641
1658
  }
1642
1659
  return results;
@@ -1806,7 +1823,6 @@ async function listConversations2(env) {
1806
1823
  return data.channels.map((ch) => {
1807
1824
  let type = "channel";
1808
1825
  if (ch.is_im) type = "dm";
1809
- else if (ch.is_mpim) type = "group";
1810
1826
  let slug;
1811
1827
  let name;
1812
1828
  if (ch.is_im && ch.user) {
@@ -1883,7 +1899,7 @@ async function createConversation3(env, participants, name) {
1883
1899
  platformId,
1884
1900
  platform: "slack",
1885
1901
  name: participants.join(", "),
1886
- type: participants.length === 1 ? "dm" : "group"
1902
+ type: participants.length === 1 ? "dm" : "channel"
1887
1903
  });
1888
1904
  }
1889
1905
  return slug;
@@ -1999,18 +2015,18 @@ import { resolve as resolve7 } from "path";
1999
2015
  function getDaemonConfig() {
2000
2016
  const newPath = resolve7(voluteSystemDir(), "daemon.json");
2001
2017
  const legacyPath = resolve7(voluteHome(), "daemon.json");
2002
- const configPath2 = existsSync7(newPath) ? newPath : legacyPath;
2003
- if (!existsSync7(configPath2)) {
2018
+ const configPath = existsSync7(newPath) ? newPath : legacyPath;
2019
+ if (!existsSync7(configPath)) {
2004
2020
  throw new Error("Volute daemon is not running");
2005
2021
  }
2006
2022
  let config;
2007
2023
  try {
2008
- config = JSON.parse(readFileSync5(configPath2, "utf-8"));
2024
+ config = JSON.parse(readFileSync5(configPath, "utf-8"));
2009
2025
  } catch (err) {
2010
- throw new Error(`Failed to parse ${configPath2}: ${err}`);
2026
+ throw new Error(`Failed to parse ${configPath}: ${err}`);
2011
2027
  }
2012
2028
  if (typeof config.port !== "number") {
2013
- throw new Error(`Invalid or missing port in ${configPath2}`);
2029
+ throw new Error(`Invalid or missing port in ${configPath}`);
2014
2030
  }
2015
2031
  const url = new URL("http://localhost");
2016
2032
  url.hostname = config.hostname || "localhost";
@@ -2099,7 +2115,7 @@ async function listConversations4(env) {
2099
2115
  convType: conv.type,
2100
2116
  convName: conv.name
2101
2117
  });
2102
- const convType = conv.type === "channel" ? "channel" : participants.length === 2 ? "dm" : "group";
2118
+ const convType = conv.type === "channel" ? "channel" : "dm";
2103
2119
  results.push({
2104
2120
  id: slug,
2105
2121
  platformId: conv.id,
@@ -2304,7 +2320,7 @@ var app5 = new Hono5().get("/:name/env", async (c) => {
2304
2320
  const value = merged[key];
2305
2321
  if (value === void 0) return c.json({ error: "Key not found" }, 404);
2306
2322
  return c.json({ value });
2307
- }).put("/:name/env/:key", requireAdmin, async (c) => {
2323
+ }).put("/:name/env/:key", requireSelf(), async (c) => {
2308
2324
  const name = c.req.param("name");
2309
2325
  if (!await findMind(name)) return c.json({ error: "Mind not found" }, 404);
2310
2326
  const key = c.req.param("key");
@@ -2322,7 +2338,7 @@ var app5 = new Hono5().get("/:name/env", async (c) => {
2322
2338
  env[key] = body.value;
2323
2339
  writeEnv(path, env);
2324
2340
  return c.json({ ok: true });
2325
- }).delete("/:name/env/:key", requireAdmin, async (c) => {
2341
+ }).delete("/:name/env/:key", requireSelf(), async (c) => {
2326
2342
  const name = c.req.param("name");
2327
2343
  if (!await findMind(name)) return c.json({ error: "Mind not found" }, 404);
2328
2344
  const key = c.req.param("key");
@@ -2363,165 +2379,9 @@ var sharedEnvApp = new Hono5().get("/", (c) => {
2363
2379
  var env_default = app5;
2364
2380
 
2365
2381
  // src/web/api/file-sharing.ts
2366
- import { readFileSync as readFileSync7, statSync } from "fs";
2367
- import { resolve as resolve9 } from "path";
2382
+ import { readFileSync as readFileSync6, statSync } from "fs";
2383
+ import { resolve as resolve8 } from "path";
2368
2384
  import { Hono as Hono6 } from "hono";
2369
-
2370
- // src/lib/file-sharing.ts
2371
- import { randomBytes } from "crypto";
2372
- import { existsSync as existsSync8, mkdirSync as mkdirSync5, readdirSync as readdirSync2, readFileSync as readFileSync6, rmSync as rmSync3, writeFileSync as writeFileSync4 } from "fs";
2373
- import { basename, join, normalize, resolve as resolve8 } from "path";
2374
- function validateFilePath(filePath) {
2375
- if (!filePath) return "File path is required";
2376
- const normalized = normalize(filePath);
2377
- if (normalized.startsWith("/") || normalized.startsWith("\\")) {
2378
- return "Absolute paths are not allowed";
2379
- }
2380
- if (normalized.includes("..")) {
2381
- return "Path traversal (..) is not allowed";
2382
- }
2383
- return null;
2384
- }
2385
- function configPath(dir) {
2386
- return resolve8(dir, "home", ".config", "file-sharing.json");
2387
- }
2388
- function readFileSharingConfig(dir) {
2389
- const p = configPath(dir);
2390
- if (!existsSync8(p)) return {};
2391
- try {
2392
- return JSON.parse(readFileSync6(p, "utf-8"));
2393
- } catch (err) {
2394
- console.warn(`[file-sharing] failed to parse config at ${p}:`, err);
2395
- return {};
2396
- }
2397
- }
2398
- function writeFileSharingConfig(dir, config) {
2399
- const p = configPath(dir);
2400
- mkdirSync5(resolve8(p, ".."), { recursive: true });
2401
- writeFileSync4(p, `${JSON.stringify(config, null, 2)}
2402
- `);
2403
- }
2404
- function isTrustedSender(dir, sender) {
2405
- const config = readFileSharingConfig(dir);
2406
- return config.trustedSenders?.includes(sender) ?? false;
2407
- }
2408
- function addTrust(dir, sender) {
2409
- const config = readFileSharingConfig(dir);
2410
- const trusted = config.trustedSenders ?? [];
2411
- if (!trusted.includes(sender)) {
2412
- trusted.push(sender);
2413
- }
2414
- config.trustedSenders = trusted;
2415
- writeFileSharingConfig(dir, config);
2416
- }
2417
- function removeTrust(dir, sender) {
2418
- const config = readFileSharingConfig(dir);
2419
- const trusted = config.trustedSenders ?? [];
2420
- config.trustedSenders = trusted.filter((s) => s !== sender);
2421
- writeFileSharingConfig(dir, config);
2422
- }
2423
- function pendingDir(receiver) {
2424
- return resolve8(stateDir(receiver), "pending-files");
2425
- }
2426
- function validateId(id) {
2427
- if (!id || id.includes("/") || id.includes("\\") || id.includes("..")) {
2428
- throw new Error("Invalid pending file id");
2429
- }
2430
- }
2431
- function generateId(sender) {
2432
- const ts = Date.now();
2433
- const rand = randomBytes(2).toString("hex");
2434
- return `${sender}-${ts}-${rand}`;
2435
- }
2436
- function stageFile(receiver, sender, filename, content, originalPath) {
2437
- const err = validateFilePath(filename);
2438
- if (err) throw new Error(err);
2439
- if (sender.includes("/") || sender.includes("\\")) {
2440
- throw new Error("Invalid sender name");
2441
- }
2442
- const id = generateId(sender);
2443
- const dir = resolve8(pendingDir(receiver), id);
2444
- mkdirSync5(dir, { recursive: true });
2445
- const metadata = {
2446
- id,
2447
- sender,
2448
- filename: basename(filename),
2449
- originalPath,
2450
- size: content.length,
2451
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
2452
- };
2453
- writeFileSync4(resolve8(dir, "metadata.json"), `${JSON.stringify(metadata, null, 2)}
2454
- `);
2455
- writeFileSync4(resolve8(dir, "data"), content);
2456
- return { id };
2457
- }
2458
- function listPending(receiver) {
2459
- const dir = pendingDir(receiver);
2460
- if (!existsSync8(dir)) return [];
2461
- const entries = readdirSync2(dir, { withFileTypes: true });
2462
- const result = [];
2463
- for (const entry of entries) {
2464
- if (!entry.isDirectory()) continue;
2465
- const metaPath = resolve8(dir, entry.name, "metadata.json");
2466
- if (!existsSync8(metaPath)) continue;
2467
- try {
2468
- result.push(JSON.parse(readFileSync6(metaPath, "utf-8")));
2469
- } catch (err) {
2470
- console.warn(`[file-sharing] skipping malformed pending entry ${entry.name}:`, err);
2471
- }
2472
- }
2473
- return result.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
2474
- }
2475
- function getPending(receiver, id) {
2476
- validateId(id);
2477
- const metaPath = resolve8(pendingDir(receiver), id, "metadata.json");
2478
- if (!existsSync8(metaPath)) return null;
2479
- try {
2480
- return JSON.parse(readFileSync6(metaPath, "utf-8"));
2481
- } catch (err) {
2482
- console.warn(`[file-sharing] failed to read pending metadata for ${id}:`, err);
2483
- return null;
2484
- }
2485
- }
2486
- function deliverFile(receiverDir, sender, filename, content, inboxPath) {
2487
- const err = validateFilePath(filename);
2488
- if (err) throw new Error(err);
2489
- const inbox = inboxPath ?? "inbox";
2490
- const inboxErr = validateFilePath(inbox);
2491
- if (inboxErr) throw new Error(`Invalid inboxPath: ${inboxErr}`);
2492
- if (sender.includes("/") || sender.includes("\\")) {
2493
- throw new Error("Invalid sender name");
2494
- }
2495
- const destDir = resolve8(receiverDir, "home", inbox, sender);
2496
- mkdirSync5(destDir, { recursive: true });
2497
- const destPath = resolve8(destDir, basename(filename));
2498
- writeFileSync4(destPath, content);
2499
- return join(inbox, sender, basename(filename));
2500
- }
2501
- function acceptPending(receiver, id, receiverDir) {
2502
- const meta = getPending(receiver, id);
2503
- if (!meta) throw new Error(`Pending file not found: ${id}`);
2504
- const dataPath = resolve8(pendingDir(receiver), id, "data");
2505
- const content = readFileSync6(dataPath);
2506
- const config = readFileSharingConfig(receiverDir);
2507
- const inboxPath = config.inboxPath ?? "inbox";
2508
- const destPath = deliverFile(receiverDir, meta.sender, meta.filename, content, inboxPath);
2509
- rmSync3(resolve8(pendingDir(receiver), id), { recursive: true });
2510
- return { sender: meta.sender, filename: meta.filename, destPath };
2511
- }
2512
- function rejectPending(receiver, id) {
2513
- const meta = getPending(receiver, id);
2514
- if (!meta) throw new Error(`Pending file not found: ${id}`);
2515
- rmSync3(resolve8(pendingDir(receiver), id), { recursive: true });
2516
- return { sender: meta.sender, filename: meta.filename };
2517
- }
2518
- function formatFileSize(bytes) {
2519
- if (bytes < 1024) return `${bytes} B`;
2520
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
2521
- return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
2522
- }
2523
-
2524
- // src/web/api/file-sharing.ts
2525
2385
  async function notifyMind(port, message, channel, sender) {
2526
2386
  try {
2527
2387
  const res = await fetch(`http://127.0.0.1:${port}/message`, {
@@ -2553,7 +2413,7 @@ var app6 = new Hono6().post("/:name/files/send", requireSelf(), async (c) => {
2553
2413
  const pathErr = validateFilePath(body.filePath);
2554
2414
  if (pathErr) return c.json({ error: pathErr }, 400);
2555
2415
  const senderDir = mindDir(senderName);
2556
- const filePath = resolve9(senderDir, "home", body.filePath);
2416
+ const filePath = resolve8(senderDir, "home", body.filePath);
2557
2417
  const MAX_FILE_SIZE2 = 50 * 1024 * 1024;
2558
2418
  const stat5 = statSync(filePath, { throwIfNoEntry: false });
2559
2419
  if (!stat5) return c.json({ error: `File not found: ${body.filePath}` }, 404);
@@ -2567,31 +2427,21 @@ var app6 = new Hono6().post("/:name/files/send", requireSelf(), async (c) => {
2567
2427
  }
2568
2428
  let content;
2569
2429
  try {
2570
- content = readFileSync7(filePath);
2571
- } catch {
2572
- return c.json({ error: `File not found: ${body.filePath}` }, 404);
2430
+ content = readFileSync6(filePath);
2431
+ } catch (err) {
2432
+ const code = err.code;
2433
+ if (code === "ENOENT") {
2434
+ return c.json({ error: `File not found: ${body.filePath}` }, 404);
2435
+ }
2436
+ return c.json({ error: `Failed to read file: ${code ?? err.message}` }, 500);
2573
2437
  }
2574
- const receiverDir = mindDir(body.targetMind);
2575
2438
  const filename = body.filePath;
2576
2439
  const sizeStr = formatFileSize(content.length);
2577
- if (isTrustedSender(receiverDir, senderName)) {
2578
- const config = readFileSharingConfig(receiverDir);
2579
- const destPath = deliverFile(receiverDir, senderName, filename, content, config.inboxPath);
2580
- if (receiverEntry.running) {
2581
- await notifyMind(
2582
- receiverEntry.port,
2583
- `[file] ${senderName} sent ${filename} (${sizeStr}) \u2192 ${destPath}`,
2584
- "system:file-sharing",
2585
- senderName
2586
- );
2587
- }
2588
- return c.json({ status: "delivered", destPath }, 200);
2589
- }
2590
2440
  const { id } = stageFile(body.targetMind, senderName, filename, content, body.filePath);
2591
2441
  if (receiverEntry.running) {
2592
2442
  await notifyMind(
2593
2443
  receiverEntry.port,
2594
- `[file] ${senderName} wants to send ${filename} (${sizeStr}) \u2014 run: volute file accept ${id}`,
2444
+ `[file] ${senderName} sent ${filename} (${sizeStr}) \u2014 run: volute chat accept ${id}`,
2595
2445
  "system:file-sharing",
2596
2446
  senderName
2597
2447
  );
@@ -2607,9 +2457,13 @@ var app6 = new Hono6().post("/:name/files/send", requireSelf(), async (c) => {
2607
2457
  if (!entry) return c.json({ error: "Mind not found" }, 404);
2608
2458
  const body = await c.req.json();
2609
2459
  if (!body.id) return c.json({ error: "id is required" }, 400);
2460
+ if (body.dest) {
2461
+ const destErr = validateFilePath(body.dest);
2462
+ if (destErr) return c.json({ error: `Invalid dest: ${destErr}` }, 400);
2463
+ }
2610
2464
  let result;
2611
2465
  try {
2612
- result = acceptPending(name, body.id, mindDir(name));
2466
+ result = acceptPending(name, body.id, mindDir(name), body.dest);
2613
2467
  } catch (err) {
2614
2468
  const message = err.message;
2615
2469
  if (message.includes("not found") || message.includes("Invalid pending")) {
@@ -2652,26 +2506,44 @@ var app6 = new Hono6().post("/:name/files/send", requireSelf(), async (c) => {
2652
2506
  );
2653
2507
  }
2654
2508
  return c.json({ ok: true });
2655
- }).post("/:name/files/trust", async (c) => {
2656
- const name = c.req.param("name");
2657
- if (!await findMind(name)) return c.json({ error: "Mind not found" }, 404);
2509
+ }).post("/:name/files/stage", async (c) => {
2510
+ const receiverName = c.req.param("name");
2511
+ const receiverEntry = await findMind(receiverName);
2512
+ if (!receiverEntry) return c.json({ error: "Mind not found" }, 404);
2658
2513
  const body = await c.req.json();
2659
- if (!body.sender) return c.json({ error: "sender is required" }, 400);
2660
- addTrust(mindDir(name), body.sender);
2661
- return c.json({ ok: true });
2662
- }).delete("/:name/files/trust/:sender", async (c) => {
2663
- const name = c.req.param("name");
2664
- if (!await findMind(name)) return c.json({ error: "Mind not found" }, 404);
2665
- const sender = c.req.param("sender");
2666
- removeTrust(mindDir(name), sender);
2667
- return c.json({ ok: true });
2514
+ if (!body.sender || !body.filename || !body.data) {
2515
+ return c.json({ error: "sender, filename, and data are required" }, 400);
2516
+ }
2517
+ const pathErr = validateFilePath(body.filename);
2518
+ if (pathErr) return c.json({ error: pathErr }, 400);
2519
+ const content = Buffer.from(body.data, "base64");
2520
+ const MAX_FILE_SIZE2 = 50 * 1024 * 1024;
2521
+ if (content.length > MAX_FILE_SIZE2) {
2522
+ return c.json(
2523
+ {
2524
+ error: `File too large (${formatFileSize(content.length)}, max ${formatFileSize(MAX_FILE_SIZE2)})`
2525
+ },
2526
+ 413
2527
+ );
2528
+ }
2529
+ const sizeStr = formatFileSize(content.length);
2530
+ const { id } = stageFile(receiverName, body.sender, body.filename, content, body.filename);
2531
+ if (receiverEntry.running) {
2532
+ await notifyMind(
2533
+ receiverEntry.port,
2534
+ `[file] ${body.sender} sent ${body.filename} (${sizeStr}) \u2014 run: volute chat accept ${id}`,
2535
+ "system:file-sharing",
2536
+ body.sender
2537
+ );
2538
+ }
2539
+ return c.json({ status: "pending", id }, 200);
2668
2540
  });
2669
2541
  var file_sharing_default = app6;
2670
2542
 
2671
2543
  // src/web/api/files.ts
2672
- import { existsSync as existsSync9 } from "fs";
2544
+ import { existsSync as existsSync8 } from "fs";
2673
2545
  import { readdir, readFile, realpath, stat } from "fs/promises";
2674
- import { extname as extname2, resolve as resolve10 } from "path";
2546
+ import { extname as extname2, resolve as resolve9 } from "path";
2675
2547
  import { Hono as Hono7 } from "hono";
2676
2548
  var ALLOWED_FILES = /* @__PURE__ */ new Set(["SOUL.md", "MEMORY.md", "CLAUDE.md", "VOLUTE.md"]);
2677
2549
  var AVATAR_MIME2 = {
@@ -2692,8 +2564,8 @@ var app7 = new Hono7().get("/:name/avatar", async (c) => {
2692
2564
  const ext = extname2(config.profile.avatar).toLowerCase();
2693
2565
  const mime = AVATAR_MIME2[ext];
2694
2566
  if (!mime) return c.json({ error: "Invalid avatar extension" }, 400);
2695
- const homeDir = resolve10(dir, "home");
2696
- const avatarPath = resolve10(homeDir, config.profile.avatar);
2567
+ const homeDir = resolve9(dir, "home");
2568
+ const avatarPath = resolve9(homeDir, config.profile.avatar);
2697
2569
  if (!avatarPath.startsWith(`${homeDir}/`)) return c.json({ error: "Invalid avatar path" }, 400);
2698
2570
  let realAvatarPath;
2699
2571
  try {
@@ -2722,8 +2594,8 @@ var app7 = new Hono7().get("/:name/avatar", async (c) => {
2722
2594
  const entry = await findMind(name);
2723
2595
  if (!entry) return c.json({ error: "Mind not found" }, 404);
2724
2596
  const dir = mindDir(name);
2725
- const homeDir = resolve10(dir, "home");
2726
- if (!existsSync9(homeDir)) return c.json({ error: "Home directory missing" }, 404);
2597
+ const homeDir = resolve9(dir, "home");
2598
+ if (!existsSync8(homeDir)) return c.json({ error: "Home directory missing" }, 404);
2727
2599
  const allFiles = await readdir(homeDir);
2728
2600
  const files = allFiles.filter((f) => f.endsWith(".md") && ALLOWED_FILES.has(f));
2729
2601
  return c.json(files);
@@ -2736,8 +2608,8 @@ var app7 = new Hono7().get("/:name/avatar", async (c) => {
2736
2608
  const entry = await findMind(name);
2737
2609
  if (!entry) return c.json({ error: "Mind not found" }, 404);
2738
2610
  const dir = mindDir(name);
2739
- const filePath = resolve10(dir, "home", filename);
2740
- if (!existsSync9(filePath)) {
2611
+ const filePath = resolve9(dir, "home", filename);
2612
+ if (!existsSync8(filePath)) {
2741
2613
  return c.json({ error: "File not found" }, 404);
2742
2614
  }
2743
2615
  const content = await readFile(filePath, "utf-8");
@@ -2750,19 +2622,19 @@ import { Hono as Hono8 } from "hono";
2750
2622
 
2751
2623
  // src/lib/identity.ts
2752
2624
  import { createHash, generateKeyPairSync, sign, verify } from "crypto";
2753
- import { existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
2754
- import { resolve as resolve11 } from "path";
2625
+ import { existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
2626
+ import { resolve as resolve10 } from "path";
2755
2627
  function generateIdentity(mindDir2) {
2756
- const identityDir = resolve11(mindDir2, ".mind/identity");
2757
- mkdirSync6(identityDir, { recursive: true });
2628
+ const identityDir = resolve10(mindDir2, ".mind/identity");
2629
+ mkdirSync5(identityDir, { recursive: true });
2758
2630
  const { publicKey, privateKey } = generateKeyPairSync("ed25519", {
2759
2631
  publicKeyEncoding: { type: "spki", format: "pem" },
2760
2632
  privateKeyEncoding: { type: "pkcs8", format: "pem" }
2761
2633
  });
2762
- const privatePath = resolve11(identityDir, "private.pem");
2763
- const publicPath = resolve11(identityDir, "public.pem");
2764
- writeFileSync5(privatePath, privateKey, { mode: 384 });
2765
- writeFileSync5(publicPath, publicKey, { mode: 420 });
2634
+ const privatePath = resolve10(identityDir, "private.pem");
2635
+ const publicPath = resolve10(identityDir, "public.pem");
2636
+ writeFileSync4(privatePath, privateKey, { mode: 384 });
2637
+ writeFileSync4(publicPath, publicKey, { mode: 420 });
2766
2638
  const config = readVoluteConfig(mindDir2) ?? {};
2767
2639
  config.identity = {
2768
2640
  privateKey: ".mind/identity/private.pem",
@@ -2775,17 +2647,17 @@ function getPrivateKey(mindDir2) {
2775
2647
  const config = readVoluteConfig(mindDir2);
2776
2648
  const relPath = config?.identity?.privateKey;
2777
2649
  if (!relPath) return null;
2778
- const fullPath = resolve11(mindDir2, relPath);
2779
- if (!existsSync10(fullPath)) return null;
2780
- return readFileSync8(fullPath, "utf-8");
2650
+ const fullPath = resolve10(mindDir2, relPath);
2651
+ if (!existsSync9(fullPath)) return null;
2652
+ return readFileSync7(fullPath, "utf-8");
2781
2653
  }
2782
2654
  function getPublicKey(mindDir2) {
2783
2655
  const config = readVoluteConfig(mindDir2);
2784
2656
  const relPath = config?.identity?.publicKey;
2785
2657
  if (!relPath) return null;
2786
- const fullPath = resolve11(mindDir2, relPath);
2787
- if (!existsSync10(fullPath)) return null;
2788
- return readFileSync8(fullPath, "utf-8");
2658
+ const fullPath = resolve10(mindDir2, relPath);
2659
+ if (!existsSync9(fullPath)) return null;
2660
+ return readFileSync7(fullPath, "utf-8");
2789
2661
  }
2790
2662
  function getFingerprint(publicKeyPem) {
2791
2663
  return createHash("sha256").update(publicKeyPem).digest("hex");
@@ -2838,16 +2710,16 @@ var keys_default = app8;
2838
2710
 
2839
2711
  // src/web/api/logs.ts
2840
2712
  import { spawn as spawn2 } from "child_process";
2841
- import { existsSync as existsSync11 } from "fs";
2842
- import { resolve as resolve12 } from "path";
2713
+ import { existsSync as existsSync10 } from "fs";
2714
+ import { resolve as resolve11 } from "path";
2843
2715
  import { Hono as Hono9 } from "hono";
2844
2716
  import { streamSSE as streamSSE2 } from "hono/streaming";
2845
2717
  var app9 = new Hono9().get("/:name/logs", async (c) => {
2846
2718
  const name = c.req.param("name");
2847
2719
  const entry = await findMind(name);
2848
2720
  if (!entry) return c.json({ error: "Mind not found" }, 404);
2849
- const logFile = resolve12(stateDir(name), "logs", "mind.log");
2850
- if (!existsSync11(logFile)) {
2721
+ const logFile = resolve11(stateDir(name), "logs", "mind.log");
2722
+ if (!existsSync10(logFile)) {
2851
2723
  return c.json({ error: "No log file found" }, 404);
2852
2724
  }
2853
2725
  return streamSSE2(c, async (stream) => {
@@ -2865,17 +2737,17 @@ var app9 = new Hono9().get("/:name/logs", async (c) => {
2865
2737
  stream.onAbort(() => {
2866
2738
  tail.kill();
2867
2739
  });
2868
- await new Promise((resolve24) => {
2869
- tail.on("exit", resolve24);
2870
- stream.onAbort(resolve24);
2740
+ await new Promise((resolve23) => {
2741
+ tail.on("exit", resolve23);
2742
+ stream.onAbort(resolve23);
2871
2743
  });
2872
2744
  });
2873
2745
  }).get("/:name/logs/tail", async (c) => {
2874
2746
  const name = c.req.param("name");
2875
2747
  const entry = await findMind(name);
2876
2748
  if (!entry) return c.json({ error: "Mind not found" }, 404);
2877
- const logFile = resolve12(stateDir(name), "logs", "mind.log");
2878
- if (!existsSync11(logFile)) {
2749
+ const logFile = resolve11(stateDir(name), "logs", "mind.log");
2750
+ if (!existsSync10(logFile)) {
2879
2751
  return c.json({ error: "No log file found" }, 404);
2880
2752
  }
2881
2753
  const nParam = parseInt(c.req.query("n") ?? "50", 10);
@@ -2885,8 +2757,8 @@ var app9 = new Hono9().get("/:name/logs", async (c) => {
2885
2757
  tail.stdout.on("data", (data) => {
2886
2758
  output += data.toString();
2887
2759
  });
2888
- await new Promise((resolve24) => {
2889
- tail.on("exit", resolve24);
2760
+ await new Promise((resolve23) => {
2761
+ tail.on("exit", resolve23);
2890
2762
  });
2891
2763
  return c.text(output);
2892
2764
  });
@@ -2905,7 +2777,7 @@ var app10 = new Hono10().get("/:name/skills", async (c) => {
2905
2777
  return c.json(skills);
2906
2778
  }).post(
2907
2779
  "/:name/skills/install",
2908
- requireAdmin,
2780
+ requireSelf(),
2909
2781
  zValidator3("json", z3.object({ skillId: z3.string() })),
2910
2782
  async (c) => {
2911
2783
  const name = c.req.param("name");
@@ -2923,7 +2795,7 @@ var app10 = new Hono10().get("/:name/skills", async (c) => {
2923
2795
  }
2924
2796
  ).post(
2925
2797
  "/:name/skills/update",
2926
- requireAdmin,
2798
+ requireSelf(),
2927
2799
  zValidator3("json", z3.object({ skillId: z3.string() })),
2928
2800
  async (c) => {
2929
2801
  const name = c.req.param("name");
@@ -2941,7 +2813,7 @@ var app10 = new Hono10().get("/:name/skills", async (c) => {
2941
2813
  }
2942
2814
  ).post(
2943
2815
  "/:name/skills/publish",
2944
- requireAdmin,
2816
+ requireSelf(),
2945
2817
  zValidator3("json", z3.object({ skillId: z3.string() })),
2946
2818
  async (c) => {
2947
2819
  const name = c.req.param("name");
@@ -2957,7 +2829,7 @@ var app10 = new Hono10().get("/:name/skills", async (c) => {
2957
2829
  return c.json({ error: msg }, 400);
2958
2830
  }
2959
2831
  }
2960
- ).delete("/:name/skills/:skill", requireAdmin, async (c) => {
2832
+ ).delete("/:name/skills/:skill", requireSelf(), async (c) => {
2961
2833
  const name = c.req.param("name");
2962
2834
  const skillName = c.req.param("skill");
2963
2835
  const entry = await findMind(name);
@@ -2976,33 +2848,33 @@ var mind_skills_default = app10;
2976
2848
  // src/web/api/minds.ts
2977
2849
  import {
2978
2850
  cpSync,
2979
- existsSync as existsSync14,
2980
- mkdirSync as mkdirSync9,
2981
- readdirSync as readdirSync4,
2982
- readFileSync as readFileSync12,
2983
- rmSync as rmSync5,
2984
- writeFileSync as writeFileSync9
2851
+ existsSync as existsSync13,
2852
+ mkdirSync as mkdirSync8,
2853
+ readdirSync as readdirSync3,
2854
+ readFileSync as readFileSync11,
2855
+ rmSync as rmSync4,
2856
+ writeFileSync as writeFileSync8
2985
2857
  } from "fs";
2986
- import { resolve as resolve16 } from "path";
2858
+ import { resolve as resolve15 } from "path";
2987
2859
  import { zValidator as zValidator4 } from "@hono/zod-validator";
2988
- import { and as and2, desc as desc2, eq as eq3, sql } from "drizzle-orm";
2860
+ import { and as and3, desc as desc2, eq as eq4, sql } from "drizzle-orm";
2989
2861
  import { Hono as Hono11 } from "hono";
2990
2862
  import { z as z4 } from "zod";
2991
2863
 
2992
2864
  // src/lib/consolidate.ts
2993
- import { readdirSync as readdirSync3, readFileSync as readFileSync9, writeFileSync as writeFileSync6 } from "fs";
2994
- import { resolve as resolve13 } from "path";
2865
+ import { readdirSync as readdirSync2, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
2866
+ import { resolve as resolve12 } from "path";
2995
2867
  async function consolidateMemory(mindDir2) {
2996
- const soulPath = resolve13(mindDir2, "home/SOUL.md");
2997
- const memoryPath = resolve13(mindDir2, "home/MEMORY.md");
2998
- const memoryDir = resolve13(mindDir2, "home/memory");
2999
- const soul = readFileSync9(soulPath, "utf-8");
2868
+ const soulPath = resolve12(mindDir2, "home/SOUL.md");
2869
+ const memoryPath = resolve12(mindDir2, "home/MEMORY.md");
2870
+ const memoryDir = resolve12(mindDir2, "home/memory");
2871
+ const soul = readFileSync8(soulPath, "utf-8");
3000
2872
  const logs = [];
3001
2873
  try {
3002
- const files = readdirSync3(memoryDir).filter((f) => /^\d{4}-\d{2}-\d{2}\.md$/.test(f)).sort();
2874
+ const files = readdirSync2(memoryDir).filter((f) => /^\d{4}-\d{2}-\d{2}\.md$/.test(f)).sort();
3003
2875
  for (const filename of files) {
3004
2876
  const date = filename.replace(".md", "");
3005
- const content2 = readFileSync9(resolve13(memoryDir, filename), "utf-8").trim();
2877
+ const content2 = readFileSync8(resolve12(memoryDir, filename), "utf-8").trim();
3006
2878
  if (content2) {
3007
2879
  logs.push(`### ${date}
3008
2880
 
@@ -3052,7 +2924,7 @@ ${content2}`);
3052
2924
  const data = await res.json();
3053
2925
  const content = data.content.filter((b) => b.type === "text" && b.text).map((b) => b.text).join("").trim();
3054
2926
  if (content) {
3055
- writeFileSync6(memoryPath, `${content}
2927
+ writeFileSync5(memoryPath, `${content}
3056
2928
  `);
3057
2929
  console.log("MEMORY.md created successfully.");
3058
2930
  } else {
@@ -3062,11 +2934,11 @@ ${content2}`);
3062
2934
 
3063
2935
  // src/lib/convert-session.ts
3064
2936
  import { randomUUID } from "crypto";
3065
- import { mkdirSync as mkdirSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync7 } from "fs";
2937
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync9, writeFileSync as writeFileSync6 } from "fs";
3066
2938
  import { homedir } from "os";
3067
- import { resolve as resolve14 } from "path";
2939
+ import { resolve as resolve13 } from "path";
3068
2940
  function convertSession(opts) {
3069
- const lines = readFileSync10(opts.sessionPath, "utf-8").trim().split("\n");
2941
+ const lines = readFileSync9(opts.sessionPath, "utf-8").trim().split("\n");
3070
2942
  const sessionId = randomUUID();
3071
2943
  const idMap = /* @__PURE__ */ new Map();
3072
2944
  const messages = [];
@@ -3180,10 +3052,10 @@ function convertSession(opts) {
3180
3052
  }
3181
3053
  }
3182
3054
  const projectId = opts.projectDir.replace(/\//g, "-");
3183
- const sdkDir = resolve14(homedir(), ".claude", "projects", projectId);
3184
- mkdirSync7(sdkDir, { recursive: true });
3185
- const sdkPath = resolve14(sdkDir, `${sessionId}.jsonl`);
3186
- writeFileSync7(sdkPath, `${sdkEvents.join("\n")}
3055
+ const sdkDir = resolve13(homedir(), ".claude", "projects", projectId);
3056
+ mkdirSync6(sdkDir, { recursive: true });
3057
+ const sdkPath = resolve13(sdkDir, `${sessionId}.jsonl`);
3058
+ writeFileSync6(sdkPath, `${sdkEvents.join("\n")}
3187
3059
  `);
3188
3060
  console.log(`Converted ${sdkEvents.length} messages \u2192 ${sdkPath}`);
3189
3061
  return sessionId;
@@ -3249,7 +3121,7 @@ async function checkHealth(port) {
3249
3121
  }
3250
3122
 
3251
3123
  // src/lib/variant-cleanup.ts
3252
- import { existsSync as existsSync12, rmSync as rmSync4 } from "fs";
3124
+ import { existsSync as existsSync11, rmSync as rmSync3 } from "fs";
3253
3125
  async function cleanupVariant(variantName, projectRoot, variantPath, opts) {
3254
3126
  if (opts?.stop) {
3255
3127
  try {
@@ -3261,11 +3133,11 @@ async function cleanupVariant(variantName, projectRoot, variantPath, opts) {
3261
3133
  const { findMind: findMind2 } = await import("./registry-NDNOOYG4.js");
3262
3134
  const variantEntry = await findMind2(variantName);
3263
3135
  const branchName = variantEntry?.branch ?? variantName;
3264
- if (existsSync12(variantPath)) {
3136
+ if (existsSync11(variantPath)) {
3265
3137
  try {
3266
3138
  await gitExec(["worktree", "remove", "--force", variantPath], { cwd: projectRoot });
3267
3139
  } catch {
3268
- rmSync4(variantPath, { recursive: true, force: true });
3140
+ rmSync3(variantPath, { recursive: true, force: true });
3269
3141
  try {
3270
3142
  await gitExec(["worktree", "prune"], { cwd: projectRoot });
3271
3143
  } catch (err) {
@@ -3295,8 +3167,8 @@ async function cleanupVariant(variantName, projectRoot, variantPath, opts) {
3295
3167
  }
3296
3168
 
3297
3169
  // src/lib/variants.ts
3298
- import { existsSync as existsSync13, mkdirSync as mkdirSync8, readFileSync as readFileSync11, writeFileSync as writeFileSync8 } from "fs";
3299
- import { resolve as resolve15 } from "path";
3170
+ import { existsSync as existsSync12, mkdirSync as mkdirSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync7 } from "fs";
3171
+ import { resolve as resolve14 } from "path";
3300
3172
  async function checkHealth2(port) {
3301
3173
  try {
3302
3174
  const res = await fetch(`http://127.0.0.1:${port}/health`, {
@@ -3325,8 +3197,8 @@ async function getMindStatus(name, port) {
3325
3197
  const manager = getMindManager();
3326
3198
  let status = "stopped";
3327
3199
  try {
3328
- const { getSleepManagerIfReady } = await import("./sleep-manager-MWYHM5HV.js");
3329
- if (getSleepManagerIfReady()?.isSleeping(name)) {
3200
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-G4B5GW5P.js");
3201
+ if (getSleepManagerIfReady2()?.isSleeping(name)) {
3330
3202
  status = "sleeping";
3331
3203
  }
3332
3204
  } catch {
@@ -3373,7 +3245,7 @@ async function initTemplateBranch(projectRoot, composedDir, manifest, mindName,
3373
3245
  await gitExec(["commit", "-m", "initial commit"], opts);
3374
3246
  }
3375
3247
  async function updateTemplateBranch(projectRoot, template, mindName) {
3376
- const tempWorktree = resolve16(projectRoot, ".variants", "_template_update");
3248
+ const tempWorktree = resolve15(projectRoot, ".variants", "_template_update");
3377
3249
  let branchExists = false;
3378
3250
  try {
3379
3251
  await gitExec(["rev-parse", "--verify", TEMPLATE_BRANCH], { cwd: projectRoot });
@@ -3384,8 +3256,8 @@ async function updateTemplateBranch(projectRoot, template, mindName) {
3384
3256
  await gitExec(["worktree", "remove", "--force", tempWorktree], { cwd: projectRoot });
3385
3257
  } catch {
3386
3258
  }
3387
- if (existsSync14(tempWorktree)) {
3388
- rmSync5(tempWorktree, { recursive: true, force: true });
3259
+ if (existsSync13(tempWorktree)) {
3260
+ rmSync4(tempWorktree, { recursive: true, force: true });
3389
3261
  }
3390
3262
  const templatesRoot = findTemplatesRoot();
3391
3263
  const { composedDir, manifest } = composeTemplate(templatesRoot, template);
@@ -3405,9 +3277,9 @@ async function updateTemplateBranch(projectRoot, template, mindName) {
3405
3277
  });
3406
3278
  }
3407
3279
  copyTemplateToDir(composedDir, tempWorktree, mindName, manifest);
3408
- const initDir = resolve16(tempWorktree, ".init");
3409
- if (existsSync14(initDir)) {
3410
- rmSync5(initDir, { recursive: true, force: true });
3280
+ const initDir = resolve15(tempWorktree, ".init");
3281
+ if (existsSync13(initDir)) {
3282
+ rmSync4(initDir, { recursive: true, force: true });
3411
3283
  }
3412
3284
  await gitExec(["add", "-A"], { cwd: tempWorktree });
3413
3285
  try {
@@ -3420,10 +3292,10 @@ async function updateTemplateBranch(projectRoot, template, mindName) {
3420
3292
  await gitExec(["worktree", "remove", "--force", tempWorktree], { cwd: projectRoot });
3421
3293
  } catch {
3422
3294
  }
3423
- if (existsSync14(tempWorktree)) {
3424
- rmSync5(tempWorktree, { recursive: true, force: true });
3295
+ if (existsSync13(tempWorktree)) {
3296
+ rmSync4(tempWorktree, { recursive: true, force: true });
3425
3297
  }
3426
- rmSync5(composedDir, { recursive: true, force: true });
3298
+ rmSync4(composedDir, { recursive: true, force: true });
3427
3299
  }
3428
3300
  }
3429
3301
  async function mergeTemplateBranch(worktreeDir) {
@@ -3446,14 +3318,14 @@ async function mergeTemplateBranch(worktreeDir) {
3446
3318
  async function npmInstallAsMind(cwd, mindName) {
3447
3319
  if (isIsolationEnabled()) {
3448
3320
  const [cmd, args] = await wrapForIsolation("npm", ["install"], mindName);
3449
- await exec(cmd, args, { cwd, env: { ...process.env, HOME: resolve16(cwd, "home") } });
3321
+ await exec(cmd, args, { cwd, env: { ...process.env, HOME: resolve15(cwd, "home") } });
3450
3322
  } else {
3451
3323
  await exec("npm", ["install"], { cwd });
3452
3324
  }
3453
3325
  }
3454
3326
  async function importFromArchive(c, tempDir, nameOverride, manifest) {
3455
- const extractedMindDir = resolve16(tempDir, "mind");
3456
- if (!existsSync14(extractedMindDir)) {
3327
+ const extractedMindDir = resolve15(tempDir, "mind");
3328
+ if (!existsSync13(extractedMindDir)) {
3457
3329
  return c.json({ error: "Invalid archive: missing mind/ directory" }, 400);
3458
3330
  }
3459
3331
  if (!manifest?.includes || !manifest.name || !manifest.template) {
@@ -3471,21 +3343,21 @@ async function importFromFullArchive(c, tempDir, extractedMindDir, nameOverride,
3471
3343
  if (await findMind(name)) return c.json({ error: `Mind already exists: ${name}` }, 409);
3472
3344
  ensureVoluteHome();
3473
3345
  const dest = mindDir(name);
3474
- if (existsSync14(dest)) return c.json({ error: "Mind directory already exists" }, 409);
3346
+ if (existsSync13(dest)) return c.json({ error: "Mind directory already exists" }, 409);
3475
3347
  try {
3476
3348
  cpSync(extractedMindDir, dest, { recursive: true });
3477
3349
  if (!manifest.includes.identity) {
3478
3350
  generateIdentity(dest);
3479
3351
  }
3480
3352
  const state = stateDir(name);
3481
- mkdirSync9(state, { recursive: true });
3482
- const channelsJson = resolve16(tempDir, "state/channels.json");
3483
- if (existsSync14(channelsJson)) {
3484
- cpSync(channelsJson, resolve16(state, "channels.json"));
3353
+ mkdirSync8(state, { recursive: true });
3354
+ const channelsJson = resolve15(tempDir, "state/channels.json");
3355
+ if (existsSync13(channelsJson)) {
3356
+ cpSync(channelsJson, resolve15(state, "channels.json"));
3485
3357
  }
3486
- const envJson = resolve16(tempDir, "state/env.json");
3487
- if (existsSync14(envJson)) {
3488
- cpSync(envJson, resolve16(state, "env.json"));
3358
+ const envJson = resolve15(tempDir, "state/env.json");
3359
+ if (existsSync13(envJson)) {
3360
+ cpSync(envJson, resolve15(state, "env.json"));
3489
3361
  }
3490
3362
  const port = await nextPort();
3491
3363
  await addMind(name, port, manifest.stage, manifest.template);
@@ -3494,36 +3366,36 @@ async function importFromFullArchive(c, tempDir, extractedMindDir, nameOverride,
3494
3366
  } catch (err) {
3495
3367
  logger_default.warn(`failed to set template hash for ${name}`, logger_default.errorData(err));
3496
3368
  }
3497
- const homeDir = resolve16(dest, "home");
3369
+ const homeDir = resolve15(dest, "home");
3498
3370
  ensureVoluteGroup();
3499
3371
  createMindUser(name, homeDir);
3500
3372
  chownMindDir(dest, name);
3501
3373
  await npmInstallAsMind(dest, name);
3502
3374
  await importHistoryFromArchive(name, tempDir);
3503
3375
  importSessionsFromArchive(dest, tempDir);
3504
- if (!existsSync14(resolve16(dest, ".git"))) {
3376
+ if (!existsSync13(resolve15(dest, ".git"))) {
3505
3377
  try {
3506
- const env = isIsolationEnabled() ? { ...process.env, HOME: resolve16(dest, "home") } : void 0;
3378
+ const env = isIsolationEnabled() ? { ...process.env, HOME: resolve15(dest, "home") } : void 0;
3507
3379
  await gitExec(["init"], { cwd: dest, mindName: name, env });
3508
3380
  await configureGitIdentity(name, { cwd: dest, mindName: name, env });
3509
3381
  await gitExec(["add", "-A"], { cwd: dest, mindName: name, env });
3510
3382
  await gitExec(["commit", "-m", "import from archive"], { cwd: dest, mindName: name, env });
3511
3383
  } catch (err) {
3512
3384
  logger_default.error(`git setup failed for imported mind ${name}`, logger_default.errorData(err));
3513
- rmSync5(resolve16(dest, ".git"), { recursive: true, force: true });
3385
+ rmSync4(resolve15(dest, ".git"), { recursive: true, force: true });
3514
3386
  }
3515
3387
  }
3516
3388
  chownMindDir(dest, name);
3517
- rmSync5(tempDir, { recursive: true, force: true });
3389
+ rmSync4(tempDir, { recursive: true, force: true });
3518
3390
  return c.json({ ok: true, name, port, message: `Imported mind: ${name} (port ${port})` });
3519
3391
  } catch (err) {
3520
- if (existsSync14(dest)) rmSync5(dest, { recursive: true, force: true });
3392
+ if (existsSync13(dest)) rmSync4(dest, { recursive: true, force: true });
3521
3393
  try {
3522
3394
  await removeMind(name);
3523
3395
  } catch (cleanupErr) {
3524
3396
  logger_default.error(`Failed to clean up registry for ${name}`, logger_default.errorData(cleanupErr));
3525
3397
  }
3526
- rmSync5(tempDir, { recursive: true, force: true });
3398
+ rmSync4(tempDir, { recursive: true, force: true });
3527
3399
  return c.json({ error: err instanceof Error ? err.message : "Failed to import mind" }, 500);
3528
3400
  }
3529
3401
  }
@@ -3534,7 +3406,7 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
3534
3406
  if (await findMind(name)) return c.json({ error: `Mind already exists: ${name}` }, 409);
3535
3407
  ensureVoluteHome();
3536
3408
  const dest = mindDir(name);
3537
- if (existsSync14(dest)) return c.json({ error: "Mind directory already exists" }, 409);
3409
+ if (existsSync13(dest)) return c.json({ error: "Mind directory already exists" }, 409);
3538
3410
  const templatesRoot = findTemplatesRoot();
3539
3411
  const { composedDir, manifest: templateManifest } = composeTemplate(
3540
3412
  templatesRoot,
@@ -3543,40 +3415,40 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
3543
3415
  try {
3544
3416
  copyTemplateToDir(composedDir, dest, name, templateManifest);
3545
3417
  applyInitFiles(dest);
3546
- const extractedHome = resolve16(extractedMindDir, "home");
3547
- if (existsSync14(extractedHome)) {
3548
- cpSync(extractedHome, resolve16(dest, "home"), { recursive: true });
3418
+ const extractedHome = resolve15(extractedMindDir, "home");
3419
+ if (existsSync13(extractedHome)) {
3420
+ cpSync(extractedHome, resolve15(dest, "home"), { recursive: true });
3549
3421
  }
3550
- const extractedMindInternal = resolve16(extractedMindDir, ".mind");
3551
- if (existsSync14(extractedMindInternal)) {
3552
- cpSync(extractedMindInternal, resolve16(dest, ".mind"), { recursive: true });
3422
+ const extractedMindInternal = resolve15(extractedMindDir, ".mind");
3423
+ if (existsSync13(extractedMindInternal)) {
3424
+ cpSync(extractedMindInternal, resolve15(dest, ".mind"), { recursive: true });
3553
3425
  }
3554
- const identityDir = resolve16(dest, ".mind/identity");
3426
+ const identityDir = resolve15(dest, ".mind/identity");
3555
3427
  let publicKeyPem;
3556
- if (!manifest.includes.identity || !existsSync14(resolve16(identityDir, "private.pem"))) {
3428
+ if (!manifest.includes.identity || !existsSync13(resolve15(identityDir, "private.pem"))) {
3557
3429
  ({ publicKeyPem } = generateIdentity(dest));
3558
3430
  } else {
3559
- publicKeyPem = readFileSync12(resolve16(identityDir, "public.pem"), "utf-8");
3431
+ publicKeyPem = readFileSync11(resolve15(identityDir, "public.pem"), "utf-8");
3560
3432
  }
3561
- const promptsPath = resolve16(dest, "home/.config/prompts.json");
3562
- if (!existsSync14(promptsPath)) {
3433
+ const promptsPath = resolve15(dest, "home/.config/prompts.json");
3434
+ if (!existsSync13(promptsPath)) {
3563
3435
  const mindPrompts = await getMindPromptDefaults();
3564
- writeFileSync9(promptsPath, `${JSON.stringify(mindPrompts, null, 2)}
3436
+ writeFileSync8(promptsPath, `${JSON.stringify(mindPrompts, null, 2)}
3565
3437
  `);
3566
3438
  }
3567
3439
  const state = stateDir(name);
3568
- mkdirSync9(state, { recursive: true });
3569
- const channelsJson = resolve16(tempDir, "state/channels.json");
3570
- if (existsSync14(channelsJson)) {
3571
- cpSync(channelsJson, resolve16(state, "channels.json"));
3440
+ mkdirSync8(state, { recursive: true });
3441
+ const channelsJson = resolve15(tempDir, "state/channels.json");
3442
+ if (existsSync13(channelsJson)) {
3443
+ cpSync(channelsJson, resolve15(state, "channels.json"));
3572
3444
  }
3573
- const envJson = resolve16(tempDir, "state/env.json");
3574
- if (existsSync14(envJson)) {
3575
- cpSync(envJson, resolve16(state, "env.json"));
3445
+ const envJson = resolve15(tempDir, "state/env.json");
3446
+ if (existsSync13(envJson)) {
3447
+ cpSync(envJson, resolve15(state, "env.json"));
3576
3448
  }
3577
3449
  const port = await nextPort();
3578
3450
  await addMind(name, port, manifest.stage, manifest.template);
3579
- const homeDir = resolve16(dest, "home");
3451
+ const homeDir = resolve15(dest, "home");
3580
3452
  ensureVoluteGroup();
3581
3453
  createMindUser(name, homeDir);
3582
3454
  chownMindDir(dest, name);
@@ -3589,7 +3461,7 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
3589
3461
  await initTemplateBranch(dest, composedDir, templateManifest, name, env);
3590
3462
  } catch (err) {
3591
3463
  logger_default.error(`git setup failed for imported mind ${name}`, logger_default.errorData(err));
3592
- rmSync5(resolve16(dest, ".git"), { recursive: true, force: true });
3464
+ rmSync4(resolve15(dest, ".git"), { recursive: true, force: true });
3593
3465
  gitWarning = "Git setup failed \u2014 variants and upgrades won't be available until git is initialized.";
3594
3466
  }
3595
3467
  try {
@@ -3613,7 +3485,7 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
3613
3485
  publishPublicKey(name, publicKeyPem).catch(
3614
3486
  (err) => logger_default.warn(`failed to publish key for ${name}`, { error: err.message })
3615
3487
  );
3616
- rmSync5(tempDir, { recursive: true, force: true });
3488
+ rmSync4(tempDir, { recursive: true, force: true });
3617
3489
  return c.json({
3618
3490
  ok: true,
3619
3491
  name,
@@ -3624,24 +3496,24 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
3624
3496
  ...skillWarnings.length > 0 && { skillWarnings }
3625
3497
  });
3626
3498
  } catch (err) {
3627
- if (existsSync14(dest)) rmSync5(dest, { recursive: true, force: true });
3499
+ if (existsSync13(dest)) rmSync4(dest, { recursive: true, force: true });
3628
3500
  try {
3629
3501
  await removeMind(name);
3630
3502
  } catch (cleanupErr) {
3631
3503
  logger_default.error(`Failed to clean up registry for ${name}`, logger_default.errorData(cleanupErr));
3632
3504
  }
3633
- rmSync5(tempDir, { recursive: true, force: true });
3505
+ rmSync4(tempDir, { recursive: true, force: true });
3634
3506
  return c.json({ error: err instanceof Error ? err.message : "Failed to import mind" }, 500);
3635
3507
  } finally {
3636
- rmSync5(composedDir, { recursive: true, force: true });
3508
+ rmSync4(composedDir, { recursive: true, force: true });
3637
3509
  }
3638
3510
  }
3639
3511
  async function importHistoryFromArchive(name, tempDir) {
3640
- const historyJsonl = resolve16(tempDir, "history.jsonl");
3641
- if (!existsSync14(historyJsonl)) return;
3512
+ const historyJsonl = resolve15(tempDir, "history.jsonl");
3513
+ if (!existsSync13(historyJsonl)) return;
3642
3514
  try {
3643
3515
  const db = await getDb();
3644
- const lines = readFileSync12(historyJsonl, "utf-8").trim().split("\n");
3516
+ const lines = readFileSync11(historyJsonl, "utf-8").trim().split("\n");
3645
3517
  let imported = 0;
3646
3518
  let failed = 0;
3647
3519
  for (const line of lines) {
@@ -3677,13 +3549,13 @@ async function importHistoryFromArchive(name, tempDir) {
3677
3549
  }
3678
3550
  }
3679
3551
  function importSessionsFromArchive(dest, tempDir) {
3680
- const sessionsDir = resolve16(tempDir, "sessions");
3681
- if (!existsSync14(sessionsDir)) return;
3552
+ const sessionsDir = resolve15(tempDir, "sessions");
3553
+ if (!existsSync13(sessionsDir)) return;
3682
3554
  try {
3683
- const destSessions = resolve16(dest, ".mind/sessions");
3684
- mkdirSync9(destSessions, { recursive: true });
3685
- for (const file of readdirSync4(sessionsDir)) {
3686
- cpSync(resolve16(sessionsDir, file), resolve16(destSessions, file));
3555
+ const destSessions = resolve15(dest, ".mind/sessions");
3556
+ mkdirSync8(destSessions, { recursive: true });
3557
+ for (const file of readdirSync3(sessionsDir)) {
3558
+ cpSync(resolve15(sessionsDir, file), resolve15(destSessions, file));
3687
3559
  }
3688
3560
  } catch (err) {
3689
3561
  logger_default.error("Failed to import sessions from archive", logger_default.errorData(err));
@@ -3706,7 +3578,7 @@ var app11 = new Hono11().post("/", requireAdmin, zValidator4("json", createMindS
3706
3578
  if (await findMind(name)) return c.json({ error: `Mind already exists: ${name}` }, 409);
3707
3579
  ensureVoluteHome();
3708
3580
  const dest = mindDir(name);
3709
- if (existsSync14(dest)) return c.json({ error: "Mind directory already exists" }, 409);
3581
+ if (existsSync13(dest)) return c.json({ error: "Mind directory already exists" }, 409);
3710
3582
  const templatesRoot = findTemplatesRoot();
3711
3583
  const { composedDir, manifest } = composeTemplate(templatesRoot, template);
3712
3584
  try {
@@ -3739,15 +3611,15 @@ var app11 = new Hono11().post("/", requireAdmin, zValidator4("json", createMindS
3739
3611
  writeVoluteConfig(dest, config);
3740
3612
  }
3741
3613
  if (body.model) {
3742
- const configPath2 = resolve16(dest, "home/.config/config.json");
3743
- const existing = existsSync14(configPath2) ? JSON.parse(readFileSync12(configPath2, "utf-8")) : {};
3614
+ const configPath = resolve15(dest, "home/.config/config.json");
3615
+ const existing = existsSync13(configPath) ? JSON.parse(readFileSync11(configPath, "utf-8")) : {};
3744
3616
  existing.model = body.model;
3745
- writeFileSync9(configPath2, `${JSON.stringify(existing, null, 2)}
3617
+ writeFileSync8(configPath, `${JSON.stringify(existing, null, 2)}
3746
3618
  `);
3747
3619
  }
3748
3620
  const mindPrompts = await getMindPromptDefaults();
3749
- writeFileSync9(
3750
- resolve16(dest, "home/.config/prompts.json"),
3621
+ writeFileSync8(
3622
+ resolve15(dest, "home/.config/prompts.json"),
3751
3623
  `${JSON.stringify(mindPrompts, null, 2)}
3752
3624
  `
3753
3625
  );
@@ -3758,7 +3630,7 @@ var app11 = new Hono11().post("/", requireAdmin, zValidator4("json", createMindS
3758
3630
  } catch (err) {
3759
3631
  logger_default.warn(`failed to set template hash for ${name}`, logger_default.errorData(err));
3760
3632
  }
3761
- const homeDir = resolve16(dest, "home");
3633
+ const homeDir = resolve15(dest, "home");
3762
3634
  ensureVoluteGroup();
3763
3635
  createMindUser(name, homeDir);
3764
3636
  chownMindDir(dest, name);
@@ -3771,7 +3643,7 @@ var app11 = new Hono11().post("/", requireAdmin, zValidator4("json", createMindS
3771
3643
  await initTemplateBranch(dest, composedDir, manifest, name, env);
3772
3644
  } catch (err) {
3773
3645
  logger_default.error(`git setup failed for ${name}`, logger_default.errorData(err));
3774
- rmSync5(resolve16(dest, ".git"), { recursive: true, force: true });
3646
+ rmSync4(resolve15(dest, ".git"), { recursive: true, force: true });
3775
3647
  gitWarning = "Git setup failed \u2014 variants and upgrades won't be available until git is initialized.";
3776
3648
  }
3777
3649
  try {
@@ -3786,7 +3658,7 @@ The human who planted you described you as: "${body.description}"
3786
3658
  ` : "";
3787
3659
  const seedSoulRaw = body.seedSoul ?? await getPrompt("seed_soul", { name, description: descLine });
3788
3660
  const seedSoul = body.seedSoul ? substitute(seedSoulRaw, { name, description: descLine }) : seedSoulRaw;
3789
- writeFileSync9(resolve16(dest, "home/SOUL.md"), seedSoul);
3661
+ writeFileSync8(resolve15(dest, "home/SOUL.md"), seedSoul);
3790
3662
  }
3791
3663
  const skillSet = body.skills ?? (body.stage === "seed" ? SEED_SKILLS : STANDARD_SKILLS);
3792
3664
  const skillWarnings = [];
@@ -3801,11 +3673,11 @@ The human who planted you described you as: "${body.description}"
3801
3673
  if (body.stage !== "seed") {
3802
3674
  const customSoul = await getPromptIfCustom("default_soul");
3803
3675
  if (customSoul) {
3804
- writeFileSync9(resolve16(dest, "home/SOUL.md"), customSoul.replace(/\{\{name\}\}/g, name));
3676
+ writeFileSync8(resolve15(dest, "home/SOUL.md"), customSoul.replace(/\{\{name\}\}/g, name));
3805
3677
  }
3806
3678
  const customMemory = await getPromptIfCustom("default_memory");
3807
3679
  if (customMemory) {
3808
- writeFileSync9(resolve16(dest, "home/MEMORY.md"), customMemory);
3680
+ writeFileSync8(resolve15(dest, "home/MEMORY.md"), customMemory);
3809
3681
  }
3810
3682
  }
3811
3683
  publishPublicKey(name, publicKeyPem).catch(
@@ -3834,14 +3706,14 @@ The human who planted you described you as: "${body.description}"
3834
3706
  ...skillWarnings.length > 0 && { skillWarnings }
3835
3707
  });
3836
3708
  } catch (err) {
3837
- if (existsSync14(dest)) rmSync5(dest, { recursive: true, force: true });
3709
+ if (existsSync13(dest)) rmSync4(dest, { recursive: true, force: true });
3838
3710
  try {
3839
3711
  await removeMind(name);
3840
3712
  } catch {
3841
3713
  }
3842
3714
  return c.json({ error: err instanceof Error ? err.message : "Failed to create mind" }, 500);
3843
3715
  } finally {
3844
- rmSync5(composedDir, { recursive: true, force: true });
3716
+ rmSync4(composedDir, { recursive: true, force: true });
3845
3717
  }
3846
3718
  }).post("/import", requireAdmin, async (c) => {
3847
3719
  let body;
@@ -3854,13 +3726,13 @@ The human who planted you described you as: "${body.description}"
3854
3726
  return importFromArchive(c, body.archivePath, body.name, body.manifest);
3855
3727
  }
3856
3728
  const wsDir = body.workspacePath;
3857
- if (!wsDir || !existsSync14(resolve16(wsDir, "SOUL.md")) || !existsSync14(resolve16(wsDir, "IDENTITY.md"))) {
3729
+ if (!wsDir || !existsSync13(resolve15(wsDir, "SOUL.md")) || !existsSync13(resolve15(wsDir, "IDENTITY.md"))) {
3858
3730
  return c.json({ error: "Invalid workspace: missing SOUL.md or IDENTITY.md" }, 400);
3859
3731
  }
3860
- const soul = readFileSync12(resolve16(wsDir, "SOUL.md"), "utf-8");
3861
- const identity = readFileSync12(resolve16(wsDir, "IDENTITY.md"), "utf-8");
3862
- const userPath = resolve16(wsDir, "USER.md");
3863
- const user = existsSync14(userPath) ? readFileSync12(userPath, "utf-8") : "";
3732
+ const soul = readFileSync11(resolve15(wsDir, "SOUL.md"), "utf-8");
3733
+ const identity = readFileSync11(resolve15(wsDir, "IDENTITY.md"), "utf-8");
3734
+ const userPath = resolve15(wsDir, "USER.md");
3735
+ const user = existsSync13(userPath) ? readFileSync11(userPath, "utf-8") : "";
3864
3736
  const name = body.name ?? parseNameFromIdentity(identity) ?? "imported-mind";
3865
3737
  const template = body.template ?? "claude";
3866
3738
  const nameErr = validateMindName(name);
@@ -3880,33 +3752,33 @@ ${user.trimEnd()}
3880
3752
  ` : "";
3881
3753
  ensureVoluteHome();
3882
3754
  const dest = mindDir(name);
3883
- if (existsSync14(dest)) return c.json({ error: "Mind directory already exists" }, 409);
3755
+ if (existsSync13(dest)) return c.json({ error: "Mind directory already exists" }, 409);
3884
3756
  const templatesRoot = findTemplatesRoot();
3885
3757
  const { composedDir, manifest } = composeTemplate(templatesRoot, template);
3886
3758
  try {
3887
3759
  copyTemplateToDir(composedDir, dest, name, manifest);
3888
3760
  applyInitFiles(dest);
3889
3761
  const { publicKeyPem: importPublicKey } = generateIdentity(dest);
3890
- writeFileSync9(resolve16(dest, "home/SOUL.md"), mergedSoul);
3891
- const wsMemoryPath = resolve16(wsDir, "MEMORY.md");
3892
- const hasMemory = existsSync14(wsMemoryPath);
3762
+ writeFileSync8(resolve15(dest, "home/SOUL.md"), mergedSoul);
3763
+ const wsMemoryPath = resolve15(wsDir, "MEMORY.md");
3764
+ const hasMemory = existsSync13(wsMemoryPath);
3893
3765
  if (hasMemory) {
3894
- const existingMemory = readFileSync12(wsMemoryPath, "utf-8");
3895
- writeFileSync9(
3896
- resolve16(dest, "home/MEMORY.md"),
3766
+ const existingMemory = readFileSync11(wsMemoryPath, "utf-8");
3767
+ writeFileSync8(
3768
+ resolve15(dest, "home/MEMORY.md"),
3897
3769
  `${existingMemory.trimEnd()}${mergedMemoryExtra}`
3898
3770
  );
3899
3771
  } else if (user) {
3900
- writeFileSync9(resolve16(dest, "home/MEMORY.md"), `${user.trimEnd()}
3772
+ writeFileSync8(resolve15(dest, "home/MEMORY.md"), `${user.trimEnd()}
3901
3773
  `);
3902
3774
  }
3903
- const wsMemoryDir = resolve16(wsDir, "memory");
3775
+ const wsMemoryDir = resolve15(wsDir, "memory");
3904
3776
  let dailyLogCount = 0;
3905
- if (existsSync14(wsMemoryDir)) {
3906
- const destMemoryDir = resolve16(dest, "home/memory");
3907
- const files = readdirSync4(wsMemoryDir).filter((f) => f.endsWith(".md"));
3777
+ if (existsSync13(wsMemoryDir)) {
3778
+ const destMemoryDir = resolve15(dest, "home/memory");
3779
+ const files = readdirSync3(wsMemoryDir).filter((f) => f.endsWith(".md"));
3908
3780
  for (const file of files) {
3909
- cpSync(resolve16(wsMemoryDir, file), resolve16(destMemoryDir, file));
3781
+ cpSync(resolve15(wsMemoryDir, file), resolve15(destMemoryDir, file));
3910
3782
  }
3911
3783
  dailyLogCount = files.length;
3912
3784
  }
@@ -3917,7 +3789,7 @@ ${user.trimEnd()}
3917
3789
  } catch (err) {
3918
3790
  logger_default.warn(`failed to set template hash for ${name}`, logger_default.errorData(err));
3919
3791
  }
3920
- const homeDir = resolve16(dest, "home");
3792
+ const homeDir = resolve15(dest, "home");
3921
3793
  ensureVoluteGroup();
3922
3794
  createMindUser(name, homeDir);
3923
3795
  chownMindDir(dest, name);
@@ -3925,20 +3797,20 @@ ${user.trimEnd()}
3925
3797
  if (!hasMemory && dailyLogCount > 0) {
3926
3798
  await consolidateMemory(dest);
3927
3799
  }
3928
- const env = isIsolationEnabled() ? { ...process.env, HOME: resolve16(dest, "home") } : void 0;
3800
+ const env = isIsolationEnabled() ? { ...process.env, HOME: resolve15(dest, "home") } : void 0;
3929
3801
  await gitExec(["init"], { cwd: dest, mindName: name, env });
3930
3802
  await configureGitIdentity(name, { cwd: dest, mindName: name, env });
3931
3803
  await gitExec(["add", "-A"], { cwd: dest, mindName: name, env });
3932
3804
  await gitExec(["commit", "-m", "import from OpenClaw"], { cwd: dest, mindName: name, env });
3933
- const sessionFile = body.sessionPath ? resolve16(body.sessionPath) : findOpenClawSession(wsDir);
3934
- if (sessionFile && existsSync14(sessionFile)) {
3805
+ const sessionFile = body.sessionPath ? resolve15(body.sessionPath) : findOpenClawSession(wsDir);
3806
+ if (sessionFile && existsSync13(sessionFile)) {
3935
3807
  if (template === "pi") {
3936
3808
  importPiSession(sessionFile, dest);
3937
3809
  } else if (template === "claude") {
3938
3810
  const sessionId = convertSession({ sessionPath: sessionFile, projectDir: dest });
3939
- const mindRuntimeDir = resolve16(dest, ".mind");
3940
- mkdirSync9(mindRuntimeDir, { recursive: true });
3941
- writeFileSync9(resolve16(mindRuntimeDir, "session.json"), JSON.stringify({ sessionId }));
3811
+ const mindRuntimeDir = resolve15(dest, ".mind");
3812
+ mkdirSync8(mindRuntimeDir, { recursive: true });
3813
+ writeFileSync8(resolve15(mindRuntimeDir, "session.json"), JSON.stringify({ sessionId }));
3942
3814
  }
3943
3815
  }
3944
3816
  importOpenClawConnectors(name, dest);
@@ -3953,14 +3825,14 @@ ${user.trimEnd()}
3953
3825
  );
3954
3826
  return c.json({ ok: true, name, port, message: `Imported mind: ${name} (port ${port})` });
3955
3827
  } catch (err) {
3956
- if (existsSync14(dest)) rmSync5(dest, { recursive: true, force: true });
3828
+ if (existsSync13(dest)) rmSync4(dest, { recursive: true, force: true });
3957
3829
  try {
3958
3830
  await removeMind(name);
3959
3831
  } catch {
3960
3832
  }
3961
3833
  return c.json({ error: err instanceof Error ? err.message : "Failed to import mind" }, 500);
3962
3834
  } finally {
3963
- rmSync5(composedDir, { recursive: true, force: true });
3835
+ rmSync4(composedDir, { recursive: true, force: true });
3964
3836
  }
3965
3837
  }).get("/", async (c) => {
3966
3838
  const entries = await readRegistry();
@@ -3977,7 +3849,7 @@ ${user.trimEnd()}
3977
3849
  const minds = await Promise.all(
3978
3850
  entries.map(async (entry) => {
3979
3851
  const mindStatus = await getMindStatus(entry.name, entry.port);
3980
- const hasPages = existsSync14(resolve16(mindDir(entry.name), "home", "public", "pages"));
3852
+ const hasPages = existsSync13(resolve15(mindDir(entry.name), "home", "public", "pages"));
3981
3853
  return {
3982
3854
  ...entry,
3983
3855
  ...mindStatus,
@@ -3996,7 +3868,7 @@ ${user.trimEnd()}
3996
3868
  const entry = await findMind(name);
3997
3869
  if (!entry) return c.json({ error: "Mind not found" }, 404);
3998
3870
  const dir = entry.dir ?? mindDir(entry.parent ?? name);
3999
- if (!existsSync14(dir)) return c.json({ error: "Mind directory missing" }, 404);
3871
+ if (!existsSync13(dir)) return c.json({ error: "Mind directory missing" }, 404);
4000
3872
  const mindStatus = await getMindStatus(name, entry.port);
4001
3873
  const variants = await findVariants(name);
4002
3874
  const manager = getMindManager();
@@ -4010,9 +3882,9 @@ ${user.trimEnd()}
4010
3882
  return { name: s.name, port: s.port, status: variantStatus };
4011
3883
  })
4012
3884
  );
4013
- const hasPages = existsSync14(resolve16(mindDir(name), "home", "public", "pages"));
3885
+ const hasPages = existsSync13(resolve15(mindDir(name), "home", "public", "pages"));
4014
3886
  return c.json({ ...entry, ...mindStatus, variants: variantStatuses, hasPages });
4015
- }).post("/:name/start", requireAdmin, async (c) => {
3887
+ }).post("/:name/start", requireSelf(), async (c) => {
4016
3888
  const name = c.req.param("name");
4017
3889
  const entry = await findMind(name);
4018
3890
  if (!entry) return c.json({ error: "Mind not found" }, 404);
@@ -4021,7 +3893,7 @@ ${user.trimEnd()}
4021
3893
  if (!entry.dir) return c.json({ error: `Variant ${name} has no directory` }, 404);
4022
3894
  } else {
4023
3895
  const dir = mindDir(name);
4024
- if (!existsSync14(dir)) return c.json({ error: "Mind directory missing" }, 404);
3896
+ if (!existsSync13(dir)) return c.json({ error: "Mind directory missing" }, 404);
4025
3897
  }
4026
3898
  if (getMindManager().isRunning(name)) {
4027
3899
  return c.json({ error: "Mind already running" }, 409);
@@ -4042,7 +3914,7 @@ ${user.trimEnd()}
4042
3914
  if (!entry.dir) return c.json({ error: `Variant ${name} has no directory` }, 404);
4043
3915
  } else {
4044
3916
  const dir = mindDir(name);
4045
- if (!existsSync14(dir)) return c.json({ error: "Mind directory missing" }, 404);
3917
+ if (!existsSync13(dir)) return c.json({ error: "Mind directory missing" }, 404);
4046
3918
  }
4047
3919
  let context;
4048
3920
  const contentType = c.req.header("content-type");
@@ -4057,8 +3929,8 @@ ${user.trimEnd()}
4057
3929
  const manager = getMindManager();
4058
3930
  try {
4059
3931
  if (context?.type === "reload") {
4060
- const { getSleepManagerIfReady } = await import("./sleep-manager-MWYHM5HV.js");
4061
- const sleepState = getSleepManagerIfReady()?.getState(name);
3932
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-G4B5GW5P.js");
3933
+ const sleepState = getSleepManagerIfReady2()?.getState(name);
4062
3934
  if (sleepState?.sleeping) {
4063
3935
  logger_default.info(`skipping reload for ${name} during sleep \u2014 will apply on next wake`);
4064
3936
  return c.json({ ok: true, deferred: true, port: targetPort });
@@ -4077,7 +3949,7 @@ ${user.trimEnd()}
4077
3949
  const variantEntry = await findMind(mergeVariantName);
4078
3950
  if (variantEntry && variantEntry.parent === baseName && variantEntry.dir && variantEntry.branch) {
4079
3951
  const projectRoot = mindDir(baseName);
4080
- if (existsSync14(variantEntry.dir)) {
3952
+ if (existsSync13(variantEntry.dir)) {
4081
3953
  const status = (await gitExec(["status", "--porcelain"], { cwd: variantEntry.dir })).trim();
4082
3954
  if (status) {
4083
3955
  try {
@@ -4119,7 +3991,7 @@ ${user.trimEnd()}
4119
3991
  if (context?.type === "sprouted" && !entry.parent) {
4120
3992
  try {
4121
3993
  const db = await getDb();
4122
- const activeConvs = await db.select({ id: conversations.id }).from(conversations).where(eq3(conversations.mind_name, baseName)).all();
3994
+ const activeConvs = await db.select({ id: conversations.id }).from(conversations).where(eq4(conversations.mind_name, baseName)).all();
4123
3995
  for (const conv of activeConvs) {
4124
3996
  await addMessage(conv.id, "assistant", "system", [
4125
3997
  { type: "text", text: "[seed has sprouted]" }
@@ -4134,7 +4006,7 @@ ${user.trimEnd()}
4134
4006
  } catch (err) {
4135
4007
  return c.json({ error: err instanceof Error ? err.message : "Failed to restart mind" }, 500);
4136
4008
  }
4137
- }).post("/:name/stop", requireAdmin, async (c) => {
4009
+ }).post("/:name/stop", requireSelf(), async (c) => {
4138
4010
  const name = c.req.param("name");
4139
4011
  const entry = await findMind(name);
4140
4012
  if (!entry) return c.json({ error: "Mind not found" }, 404);
@@ -4148,20 +4020,20 @@ ${user.trimEnd()}
4148
4020
  } catch (err) {
4149
4021
  return c.json({ error: err instanceof Error ? err.message : "Failed to stop mind" }, 500);
4150
4022
  }
4151
- }).get("/:name/sleep", requireAdmin, async (c) => {
4023
+ }).get("/:name/sleep", requireSelf(), async (c) => {
4152
4024
  const name = c.req.param("name");
4153
4025
  const entry = await findMind(name);
4154
4026
  if (!entry) return c.json({ error: "Mind not found" }, 404);
4155
- const { getSleepManagerIfReady } = await import("./sleep-manager-MWYHM5HV.js");
4156
- const sm = getSleepManagerIfReady();
4027
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-G4B5GW5P.js");
4028
+ const sm = getSleepManagerIfReady2();
4157
4029
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
4158
4030
  return c.json(sm.getState(name));
4159
- }).post("/:name/sleep", requireAdmin, async (c) => {
4031
+ }).post("/:name/sleep", requireSelf(), async (c) => {
4160
4032
  const name = c.req.param("name");
4161
4033
  const entry = await findMind(name);
4162
4034
  if (!entry) return c.json({ error: "Mind not found" }, 404);
4163
- const { getSleepManagerIfReady } = await import("./sleep-manager-MWYHM5HV.js");
4164
- const sm = getSleepManagerIfReady();
4035
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-G4B5GW5P.js");
4036
+ const sm = getSleepManagerIfReady2();
4165
4037
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
4166
4038
  if (sm.isSleeping(name)) return c.json({ error: "Mind is already sleeping" }, 409);
4167
4039
  const body = await c.req.json().catch(() => ({}));
@@ -4176,12 +4048,12 @@ ${user.trimEnd()}
4176
4048
  (err) => logger_default.error(`failed to initiate sleep for ${name}`, logger_default.errorData(err))
4177
4049
  );
4178
4050
  return c.json({ ok: true });
4179
- }).post("/:name/wake", requireAdmin, async (c) => {
4051
+ }).post("/:name/wake", requireSelf(), async (c) => {
4180
4052
  const name = c.req.param("name");
4181
4053
  const entry = await findMind(name);
4182
4054
  if (!entry) return c.json({ error: "Mind not found" }, 404);
4183
- const { getSleepManagerIfReady } = await import("./sleep-manager-MWYHM5HV.js");
4184
- const sm = getSleepManagerIfReady();
4055
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-G4B5GW5P.js");
4056
+ const sm = getSleepManagerIfReady2();
4185
4057
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
4186
4058
  const sleepState = sm.getState(name);
4187
4059
  if (!sleepState.sleeping) return c.json({ error: "Mind is not sleeping" }, 409);
@@ -4191,16 +4063,16 @@ ${user.trimEnd()}
4191
4063
  sm.initiateWake(name).catch((err) => logger_default.error(`failed to wake ${name}`, logger_default.errorData(err)));
4192
4064
  }
4193
4065
  return c.json({ ok: true });
4194
- }).post("/:name/sleep/messages", requireAdmin, async (c) => {
4066
+ }).post("/:name/sleep/messages", requireSelf(), async (c) => {
4195
4067
  const name = c.req.param("name");
4196
4068
  const entry = await findMind(name);
4197
4069
  if (!entry) return c.json({ error: "Mind not found" }, 404);
4198
- const { getSleepManagerIfReady } = await import("./sleep-manager-MWYHM5HV.js");
4199
- const sm = getSleepManagerIfReady();
4070
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-G4B5GW5P.js");
4071
+ const sm = getSleepManagerIfReady2();
4200
4072
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
4201
4073
  const flushed = await sm.flushQueuedMessages(name);
4202
4074
  return c.json({ ok: true, flushed });
4203
- }).post("/:name/sprout", requireAdmin, async (c) => {
4075
+ }).post("/:name/sprout", requireSelf(), async (c) => {
4204
4076
  const name = c.req.param("name");
4205
4077
  const entry = await findMind(name);
4206
4078
  if (!entry) return c.json({ error: "Mind not found" }, 404);
@@ -4233,11 +4105,11 @@ ${user.trimEnd()}
4233
4105
  await removeMind(name);
4234
4106
  await deleteMindUser2(name);
4235
4107
  const state = stateDir(name);
4236
- if (existsSync14(state)) {
4237
- rmSync5(state, { recursive: true, force: true });
4108
+ if (existsSync13(state)) {
4109
+ rmSync4(state, { recursive: true, force: true });
4238
4110
  }
4239
- if (force && existsSync14(dir)) {
4240
- rmSync5(dir, { recursive: true, force: true });
4111
+ if (force && existsSync13(dir)) {
4112
+ rmSync4(dir, { recursive: true, force: true });
4241
4113
  deleteMindUser(name);
4242
4114
  }
4243
4115
  fireWebhook({
@@ -4246,12 +4118,12 @@ ${user.trimEnd()}
4246
4118
  data: { port: entry.port, stage: entry.stage, template: entry.template }
4247
4119
  });
4248
4120
  return c.json({ ok: true });
4249
- }).post("/:name/upgrade", requireAdmin, async (c) => {
4121
+ }).post("/:name/upgrade", requireSelf(), async (c) => {
4250
4122
  const mindName = c.req.param("name");
4251
4123
  const entry = await findMind(mindName);
4252
4124
  if (!entry) return c.json({ error: "Mind not found" }, 404);
4253
4125
  const dir = mindDir(mindName);
4254
- if (!existsSync14(dir)) return c.json({ error: "Mind directory missing" }, 404);
4126
+ if (!existsSync13(dir)) return c.json({ error: "Mind directory missing" }, 404);
4255
4127
  let body = {};
4256
4128
  try {
4257
4129
  body = await c.req.json();
@@ -4260,16 +4132,16 @@ ${user.trimEnd()}
4260
4132
  const template = body.template ?? entry.template ?? "claude";
4261
4133
  const UPGRADE_BRANCH = "upgrade";
4262
4134
  const upgradeVariantName = `${mindName}-upgrade`;
4263
- const worktreeDir = resolve16(dir, ".variants", UPGRADE_BRANCH);
4135
+ const worktreeDir = resolve15(dir, ".variants", UPGRADE_BRANCH);
4264
4136
  if (body.abort) {
4265
- if (!existsSync14(worktreeDir)) {
4137
+ if (!existsSync13(worktreeDir)) {
4266
4138
  return c.json({ error: "No upgrade in progress" }, 400);
4267
4139
  }
4268
4140
  try {
4269
4141
  try {
4270
- const gitDirContent = readFileSync12(resolve16(worktreeDir, ".git"), "utf-8").trim();
4142
+ const gitDirContent = readFileSync11(resolve15(worktreeDir, ".git"), "utf-8").trim();
4271
4143
  const gitDir = gitDirContent.replace("gitdir: ", "");
4272
- if (existsSync14(resolve16(gitDir, "MERGE_HEAD"))) {
4144
+ if (existsSync13(resolve15(gitDir, "MERGE_HEAD"))) {
4273
4145
  await gitExec(["merge", "--abort"], { cwd: worktreeDir });
4274
4146
  }
4275
4147
  } catch {
@@ -4288,7 +4160,7 @@ ${user.trimEnd()}
4288
4160
  }
4289
4161
  }
4290
4162
  if (body.continue) {
4291
- if (!existsSync14(worktreeDir)) {
4163
+ if (!existsSync13(worktreeDir)) {
4292
4164
  return c.json({ error: "No upgrade in progress" }, 400);
4293
4165
  }
4294
4166
  const status = await gitExec(["status", "--porcelain"], { cwd: worktreeDir });
@@ -4327,7 +4199,7 @@ ${user.trimEnd()}
4327
4199
  }
4328
4200
  }
4329
4201
  if (body.accept) {
4330
- if (!existsSync14(worktreeDir)) {
4202
+ if (!existsSync13(worktreeDir)) {
4331
4203
  return c.json({ error: "No upgrade in progress" }, 400);
4332
4204
  }
4333
4205
  const variantEntry = await findMind(upgradeVariantName);
@@ -4341,7 +4213,7 @@ ${user.trimEnd()}
4341
4213
  await gitExec(["commit", "-m", "Auto-commit before upgrade merge"], {
4342
4214
  cwd: worktreeDir
4343
4215
  });
4344
- } catch (e) {
4216
+ } catch {
4345
4217
  return c.json({ error: "Failed to auto-commit upgrade changes before merge" }, 500);
4346
4218
  }
4347
4219
  }
@@ -4385,22 +4257,22 @@ ${user.trimEnd()}
4385
4257
  }
4386
4258
  return c.json({ ok: true });
4387
4259
  }
4388
- if (existsSync14(worktreeDir)) {
4260
+ if (existsSync13(worktreeDir)) {
4389
4261
  return c.json(
4390
4262
  { error: "Upgrade variant already exists. Use continue or delete it first." },
4391
4263
  409
4392
4264
  );
4393
4265
  }
4394
- if (!existsSync14(resolve16(dir, ".git"))) {
4266
+ if (!existsSync13(resolve15(dir, ".git"))) {
4395
4267
  try {
4396
- const env = isIsolationEnabled() ? { ...process.env, HOME: resolve16(dir, "home") } : void 0;
4268
+ const env = isIsolationEnabled() ? { ...process.env, HOME: resolve15(dir, "home") } : void 0;
4397
4269
  await gitExec(["init"], { cwd: dir, mindName, env });
4398
4270
  await configureGitIdentity(mindName, { cwd: dir, mindName, env });
4399
4271
  await gitExec(["add", "-A"], { cwd: dir, mindName, env });
4400
4272
  await gitExec(["commit", "-m", "initial commit"], { cwd: dir, mindName, env });
4401
4273
  chownMindDir(dir, mindName);
4402
4274
  } catch (err) {
4403
- rmSync5(resolve16(dir, ".git"), { recursive: true, force: true });
4275
+ rmSync4(resolve15(dir, ".git"), { recursive: true, force: true });
4404
4276
  return c.json(
4405
4277
  {
4406
4278
  error: `Git initialization failed: ${err instanceof Error ? err.message : String(err)}`
@@ -4414,7 +4286,7 @@ ${user.trimEnd()}
4414
4286
  await gitExec(["branch", "-D", UPGRADE_BRANCH], { cwd: dir });
4415
4287
  } catch {
4416
4288
  }
4417
- if (!existsSync14(resolve16(dir, "home", "shared"))) {
4289
+ if (!existsSync13(resolve15(dir, "home", "shared"))) {
4418
4290
  try {
4419
4291
  await addSharedWorktree(mindName, dir);
4420
4292
  } catch (err) {
@@ -4425,9 +4297,9 @@ ${user.trimEnd()}
4425
4297
  }
4426
4298
  }
4427
4299
  await updateTemplateBranch(dir, template, mindName);
4428
- const parentDir = resolve16(dir, ".variants");
4429
- if (!existsSync14(parentDir)) {
4430
- mkdirSync9(parentDir, { recursive: true });
4300
+ const parentDir = resolve15(dir, ".variants");
4301
+ if (!existsSync13(parentDir)) {
4302
+ mkdirSync8(parentDir, { recursive: true });
4431
4303
  }
4432
4304
  await gitExec(["worktree", "add", "-b", UPGRADE_BRANCH, worktreeDir], { cwd: dir });
4433
4305
  const hasConflicts = await mergeTemplateBranch(worktreeDir);
@@ -4464,8 +4336,8 @@ ${user.trimEnd()}
4464
4336
  if (!entry) return c.json({ error: "Mind not found" }, 404);
4465
4337
  const baseName = entry.parent ?? name;
4466
4338
  try {
4467
- const { getSleepManagerIfReady } = await import("./sleep-manager-MWYHM5HV.js");
4468
- const sm = getSleepManagerIfReady();
4339
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-G4B5GW5P.js");
4340
+ const sm = getSleepManagerIfReady2();
4469
4341
  if (sm?.isSleeping(baseName)) {
4470
4342
  const body2 = await c.req.text();
4471
4343
  let parsed2 = null;
@@ -4562,7 +4434,7 @@ ${user.trimEnd()}
4562
4434
  if (seedEntry?.stage === "seed") {
4563
4435
  try {
4564
4436
  const db = await getDb();
4565
- const countResult = await db.select({ count: sql`count(*)` }).from(mindHistory).where(eq3(mindHistory.mind, baseName));
4437
+ const countResult = await db.select({ count: sql`count(*)` }).from(mindHistory).where(eq4(mindHistory.mind, baseName));
4566
4438
  const msgCount = countResult[0]?.count ?? 0;
4567
4439
  if (msgCount >= 10 && msgCount % 10 === 0) {
4568
4440
  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.]";
@@ -4596,6 +4468,34 @@ ${user.trimEnd()}
4596
4468
  logger_default.error(`delivery failed for ${name}`, logger_default.errorData(err));
4597
4469
  });
4598
4470
  return c.json({ ok: true });
4471
+ }).get("/:name/conversations", async (c) => {
4472
+ const name = c.req.param("name");
4473
+ const entry = await findMind(name);
4474
+ if (!entry) return c.json({ error: "Mind not found" }, 404);
4475
+ const convs = await listConversationsForMind(name);
4476
+ return c.json(convs);
4477
+ }).get("/:name/conversations/:convId/messages", async (c) => {
4478
+ const name = c.req.param("name");
4479
+ const convId = c.req.param("convId");
4480
+ const entry = await findMind(name);
4481
+ if (!entry) return c.json({ error: "Mind not found" }, 404);
4482
+ const belongs = await isConversationForMind(name, convId);
4483
+ if (!belongs) {
4484
+ return c.json({ error: "Conversation not found" }, 404);
4485
+ }
4486
+ const beforeStr = c.req.query("before");
4487
+ const limitStr = c.req.query("limit");
4488
+ if (!beforeStr && !limitStr) {
4489
+ const msgs = await getMessages(convId);
4490
+ return c.json({ items: msgs, hasMore: false });
4491
+ }
4492
+ const before = beforeStr ? parseInt(beforeStr, 10) : void 0;
4493
+ const limit = limitStr ? parseInt(limitStr, 10) : void 0;
4494
+ if (before !== void 0 && isNaN(before) || limit !== void 0 && isNaN(limit)) {
4495
+ return c.json({ error: "Invalid pagination parameters" }, 400);
4496
+ }
4497
+ const result = await getMessagesPaginated(convId, { before, limit });
4498
+ return c.json({ items: result.messages, hasMore: result.hasMore });
4599
4499
  }).get("/:name/budget", async (c) => {
4600
4500
  const name = c.req.param("name");
4601
4501
  const baseName = await getBaseName(name);
@@ -4607,13 +4507,13 @@ ${user.trimEnd()}
4607
4507
  const entry = await findMind(name);
4608
4508
  if (!entry) return c.json({ error: "Mind not found" }, 404);
4609
4509
  const dir = mindDir(name);
4610
- if (!existsSync14(dir)) return c.json({ error: "Mind directory missing" }, 404);
4510
+ if (!existsSync13(dir)) return c.json({ error: "Mind directory missing" }, 404);
4611
4511
  let config = readVoluteConfig(dir);
4612
4512
  if (!config && entry.template === "pi") {
4613
- const piConfigPath = resolve16(dir, "home/.config/config.json");
4614
- if (existsSync14(piConfigPath)) {
4513
+ const piConfigPath = resolve15(dir, "home/.config/config.json");
4514
+ if (existsSync13(piConfigPath)) {
4615
4515
  try {
4616
- config = JSON.parse(readFileSync12(piConfigPath, "utf-8"));
4516
+ config = JSON.parse(readFileSync11(piConfigPath, "utf-8"));
4617
4517
  } catch {
4618
4518
  }
4619
4519
  }
@@ -4635,7 +4535,7 @@ ${user.trimEnd()}
4635
4535
  });
4636
4536
  }).put(
4637
4537
  "/:name/config",
4638
- requireAdmin,
4538
+ requireSelf(),
4639
4539
  zValidator4(
4640
4540
  "json",
4641
4541
  z4.object({
@@ -4650,7 +4550,7 @@ ${user.trimEnd()}
4650
4550
  const entry = await findMind(name);
4651
4551
  if (!entry) return c.json({ error: "Mind not found" }, 404);
4652
4552
  const dir = mindDir(name);
4653
- if (!existsSync14(dir)) return c.json({ error: "Mind directory missing" }, 404);
4553
+ if (!existsSync13(dir)) return c.json({ error: "Mind directory missing" }, 404);
4654
4554
  const body = c.req.valid("json");
4655
4555
  const existing = readVoluteConfig(dir) ?? {};
4656
4556
  if (body.model !== void 0) existing.model = body.model;
@@ -4835,18 +4735,18 @@ ${user.trimEnd()}
4835
4735
  event_count: sql`COUNT(*)`,
4836
4736
  message_count: sql`SUM(CASE WHEN ${mindHistory.type} IN ('inbound','outbound') THEN 1 ELSE 0 END)`,
4837
4737
  tool_count: sql`SUM(CASE WHEN ${mindHistory.type}='tool_use' THEN 1 ELSE 0 END)`
4838
- }).from(mindHistory).where(and2(eq3(mindHistory.mind, name), sql`${mindHistory.session} IS NOT NULL`)).groupBy(mindHistory.session).orderBy(sql`MIN(${mindHistory.created_at}) DESC`);
4738
+ }).from(mindHistory).where(and3(eq4(mindHistory.mind, name), sql`${mindHistory.session} IS NOT NULL`)).groupBy(mindHistory.session).orderBy(sql`MIN(${mindHistory.created_at}) DESC`);
4839
4739
  return c.json(rows);
4840
4740
  }).get("/:name/history/channels", async (c) => {
4841
4741
  const name = c.req.param("name");
4842
4742
  const db = await getDb();
4843
- const rows = await db.selectDistinct({ channel: mindHistory.channel }).from(mindHistory).where(eq3(mindHistory.mind, name));
4743
+ const rows = await db.selectDistinct({ channel: mindHistory.channel }).from(mindHistory).where(eq4(mindHistory.mind, name));
4844
4744
  return c.json(rows.map((r) => r.channel));
4845
4745
  }).get("/:name/history/export", async (c) => {
4846
4746
  const name = c.req.param("name");
4847
4747
  if (!await findMind(name)) return c.json({ error: "Mind not found" }, 404);
4848
4748
  const db = await getDb();
4849
- const rows = await db.select().from(mindHistory).where(eq3(mindHistory.mind, name));
4749
+ const rows = await db.select().from(mindHistory).where(eq4(mindHistory.mind, name));
4850
4750
  return c.json(rows);
4851
4751
  }).get("/:name/history", async (c) => {
4852
4752
  const name = c.req.param("name");
@@ -4856,17 +4756,17 @@ ${user.trimEnd()}
4856
4756
  const limit = Math.min(Math.max(parseInt(c.req.query("limit") ?? "50", 10) || 50, 1), 200);
4857
4757
  const offset = Math.max(parseInt(c.req.query("offset") ?? "0", 10) || 0, 0);
4858
4758
  const db = await getDb();
4859
- const conditions = [eq3(mindHistory.mind, name)];
4759
+ const conditions = [eq4(mindHistory.mind, name)];
4860
4760
  if (channel) {
4861
- conditions.push(eq3(mindHistory.channel, channel));
4761
+ conditions.push(eq4(mindHistory.channel, channel));
4862
4762
  }
4863
4763
  if (session) {
4864
- conditions.push(eq3(mindHistory.session, session));
4764
+ conditions.push(eq4(mindHistory.session, session));
4865
4765
  }
4866
4766
  if (!full) {
4867
4767
  conditions.push(sql`${mindHistory.type} IN ('inbound', 'outbound')`);
4868
4768
  }
4869
- const rows = await db.select().from(mindHistory).where(and2(...conditions)).orderBy(desc2(mindHistory.created_at)).limit(limit).offset(offset);
4769
+ const rows = await db.select().from(mindHistory).where(and3(...conditions)).orderBy(desc2(mindHistory.created_at)).limit(limit).offset(offset);
4870
4770
  return c.json(rows);
4871
4771
  });
4872
4772
  var minds_default = app11;
@@ -4877,11 +4777,11 @@ import { Hono as Hono12 } from "hono";
4877
4777
  import { z as z5 } from "zod";
4878
4778
 
4879
4779
  // src/lib/notes.ts
4880
- import { and as and3, count, desc as desc3, eq as eq4, inArray, sql as sql2 } from "drizzle-orm";
4780
+ import { and as and4, count, desc as desc3, eq as eq5, inArray, sql as sql2 } from "drizzle-orm";
4881
4781
  async function createNote(authorId, title, content, replyToId) {
4882
4782
  const db = await getDb();
4883
4783
  let slug = slugify(title) || "untitled";
4884
- const existing = await db.select({ slug: notes.slug }).from(notes).where(eq4(notes.author_id, authorId)).all();
4784
+ const existing = await db.select({ slug: notes.slug }).from(notes).where(eq5(notes.author_id, authorId)).all();
4885
4785
  const existingSlugs = new Set(existing.map((r) => r.slug));
4886
4786
  if (existingSlugs.has(slug)) {
4887
4787
  let i = 2;
@@ -4889,7 +4789,7 @@ async function createNote(authorId, title, content, replyToId) {
4889
4789
  slug = `${slug}-${i}`;
4890
4790
  }
4891
4791
  const [row] = await db.insert(notes).values({ author_id: authorId, title, slug, content, reply_to_id: replyToId ?? null }).returning();
4892
- const author = await db.select().from(users).where(eq4(users.id, authorId)).get();
4792
+ const author = await db.select().from(users).where(eq5(users.id, authorId)).get();
4893
4793
  return {
4894
4794
  ...row,
4895
4795
  author_username: author?.username ?? "unknown",
@@ -4910,7 +4810,7 @@ async function getNote(authorUsername, slug) {
4910
4810
  updated_at: notes.updated_at,
4911
4811
  author_username: users.username,
4912
4812
  author_display_name: users.display_name
4913
- }).from(notes).innerJoin(users, eq4(notes.author_id, users.id)).where(and3(eq4(users.username, authorUsername), eq4(notes.slug, slug))).get();
4813
+ }).from(notes).innerJoin(users, eq5(notes.author_id, users.id)).where(and4(eq5(users.username, authorUsername), eq5(notes.slug, slug))).get();
4914
4814
  if (!row) return null;
4915
4815
  const comments = await getComments(row.id);
4916
4816
  const reactions = await getReactions(row.id);
@@ -4920,7 +4820,7 @@ async function getNote(authorUsername, slug) {
4920
4820
  title: notes.title,
4921
4821
  slug: notes.slug,
4922
4822
  author_username: users.username
4923
- }).from(notes).innerJoin(users, eq4(notes.author_id, users.id)).where(eq4(notes.id, row.reply_to_id)).get();
4823
+ }).from(notes).innerJoin(users, eq5(notes.author_id, users.id)).where(eq5(notes.id, row.reply_to_id)).get();
4924
4824
  if (parent) {
4925
4825
  reply_to = parent;
4926
4826
  }
@@ -4930,7 +4830,7 @@ async function getNote(authorUsername, slug) {
4930
4830
  slug: notes.slug,
4931
4831
  title: notes.title,
4932
4832
  created_at: notes.created_at
4933
- }).from(notes).innerJoin(users, eq4(notes.author_id, users.id)).where(eq4(notes.reply_to_id, row.id)).orderBy(notes.created_at).all();
4833
+ }).from(notes).innerJoin(users, eq5(notes.author_id, users.id)).where(eq5(notes.reply_to_id, row.id)).orderBy(notes.created_at).all();
4934
4834
  return { ...row, comment_count: comments.length, comments, reactions, reply_to, replies };
4935
4835
  }
4936
4836
  async function listNotes(opts) {
@@ -4939,7 +4839,7 @@ async function listNotes(opts) {
4939
4839
  const offset = opts?.offset ?? 0;
4940
4840
  const conditions = [];
4941
4841
  if (opts?.authorUsername) {
4942
- conditions.push(eq4(users.username, opts.authorUsername));
4842
+ conditions.push(eq5(users.username, opts.authorUsername));
4943
4843
  }
4944
4844
  const rows = await db.select({
4945
4845
  id: notes.id,
@@ -4952,7 +4852,7 @@ async function listNotes(opts) {
4952
4852
  updated_at: notes.updated_at,
4953
4853
  author_username: users.username,
4954
4854
  author_display_name: users.display_name
4955
- }).from(notes).innerJoin(users, eq4(notes.author_id, users.id)).where(conditions.length > 0 ? and3(...conditions) : void 0).orderBy(desc3(notes.created_at)).limit(limit).offset(offset).all();
4855
+ }).from(notes).innerJoin(users, eq5(notes.author_id, users.id)).where(conditions.length > 0 ? and4(...conditions) : void 0).orderBy(desc3(notes.created_at)).limit(limit).offset(offset).all();
4956
4856
  const noteIds = rows.map((r) => r.id);
4957
4857
  if (noteIds.length === 0) return [];
4958
4858
  const commentCounts = await db.select({
@@ -4978,7 +4878,7 @@ async function listNotes(opts) {
4978
4878
  title: notes.title,
4979
4879
  slug: notes.slug,
4980
4880
  author_username: users.username
4981
- }).from(notes).innerJoin(users, eq4(notes.author_id, users.id)).where(inArray(notes.id, replyToIds)).all();
4881
+ }).from(notes).innerJoin(users, eq5(notes.author_id, users.id)).where(inArray(notes.id, replyToIds)).all();
4982
4882
  for (const parent of parents) {
4983
4883
  replyToMap.set(parent.id, {
4984
4884
  author_username: parent.author_username,
@@ -5000,12 +4900,12 @@ async function listNotes(opts) {
5000
4900
  }
5001
4901
  async function updateNote(authorUsername, slug, updates) {
5002
4902
  const db = await getDb();
5003
- const existing = await db.select({ id: notes.id, author_id: notes.author_id }).from(notes).innerJoin(users, eq4(notes.author_id, users.id)).where(and3(eq4(users.username, authorUsername), eq4(notes.slug, slug))).get();
4903
+ const existing = await db.select({ id: notes.id, author_id: notes.author_id }).from(notes).innerJoin(users, eq5(notes.author_id, users.id)).where(and4(eq5(users.username, authorUsername), eq5(notes.slug, slug))).get();
5004
4904
  if (!existing) return null;
5005
4905
  const set = { updated_at: sql2`(datetime('now'))` };
5006
4906
  if (updates.title !== void 0) set.title = updates.title;
5007
4907
  if (updates.content !== void 0) set.content = updates.content;
5008
- await db.update(notes).set(set).where(eq4(notes.id, existing.id));
4908
+ await db.update(notes).set(set).where(eq5(notes.id, existing.id));
5009
4909
  return getNote(authorUsername, slug).then((n) => {
5010
4910
  if (!n) return null;
5011
4911
  const { comments, replies, ...note } = n;
@@ -5014,15 +4914,15 @@ async function updateNote(authorUsername, slug, updates) {
5014
4914
  }
5015
4915
  async function deleteNote(authorUsername, slug, authorId) {
5016
4916
  const db = await getDb();
5017
- const existing = await db.select({ id: notes.id, author_id: notes.author_id }).from(notes).innerJoin(users, eq4(notes.author_id, users.id)).where(and3(eq4(users.username, authorUsername), eq4(notes.slug, slug))).get();
4917
+ const existing = await db.select({ id: notes.id, author_id: notes.author_id }).from(notes).innerJoin(users, eq5(notes.author_id, users.id)).where(and4(eq5(users.username, authorUsername), eq5(notes.slug, slug))).get();
5018
4918
  if (!existing || existing.author_id !== authorId) return false;
5019
- await db.delete(notes).where(eq4(notes.id, existing.id));
4919
+ await db.delete(notes).where(eq5(notes.id, existing.id));
5020
4920
  return true;
5021
4921
  }
5022
4922
  async function addComment(noteId, authorId, content) {
5023
4923
  const db = await getDb();
5024
4924
  const [row] = await db.insert(noteComments).values({ note_id: noteId, author_id: authorId, content }).returning();
5025
- const author = await db.select().from(users).where(eq4(users.id, authorId)).get();
4925
+ const author = await db.select().from(users).where(eq5(users.id, authorId)).get();
5026
4926
  return {
5027
4927
  ...row,
5028
4928
  author_username: author?.username ?? "unknown",
@@ -5039,26 +4939,26 @@ async function getComments(noteId) {
5039
4939
  created_at: noteComments.created_at,
5040
4940
  author_username: users.username,
5041
4941
  author_display_name: users.display_name
5042
- }).from(noteComments).innerJoin(users, eq4(noteComments.author_id, users.id)).where(eq4(noteComments.note_id, noteId)).orderBy(noteComments.created_at).all();
4942
+ }).from(noteComments).innerJoin(users, eq5(noteComments.author_id, users.id)).where(eq5(noteComments.note_id, noteId)).orderBy(noteComments.created_at).all();
5043
4943
  }
5044
4944
  async function deleteComment(commentId, authorId) {
5045
4945
  const db = await getDb();
5046
- const existing = await db.select({ id: noteComments.id, author_id: noteComments.author_id }).from(noteComments).where(eq4(noteComments.id, commentId)).get();
4946
+ const existing = await db.select({ id: noteComments.id, author_id: noteComments.author_id }).from(noteComments).where(eq5(noteComments.id, commentId)).get();
5047
4947
  if (!existing || existing.author_id !== authorId) return false;
5048
- await db.delete(noteComments).where(eq4(noteComments.id, commentId));
4948
+ await db.delete(noteComments).where(eq5(noteComments.id, commentId));
5049
4949
  return true;
5050
4950
  }
5051
4951
  async function toggleReaction(noteId, userId, emoji) {
5052
4952
  const db = await getDb();
5053
4953
  const existing = await db.select({ id: noteReactions.id }).from(noteReactions).where(
5054
- and3(
5055
- eq4(noteReactions.note_id, noteId),
5056
- eq4(noteReactions.user_id, userId),
5057
- eq4(noteReactions.emoji, emoji)
4954
+ and4(
4955
+ eq5(noteReactions.note_id, noteId),
4956
+ eq5(noteReactions.user_id, userId),
4957
+ eq5(noteReactions.emoji, emoji)
5058
4958
  )
5059
4959
  ).get();
5060
4960
  if (existing) {
5061
- await db.delete(noteReactions).where(eq4(noteReactions.id, existing.id));
4961
+ await db.delete(noteReactions).where(eq5(noteReactions.id, existing.id));
5062
4962
  return { added: false };
5063
4963
  }
5064
4964
  await db.insert(noteReactions).values({ note_id: noteId, user_id: userId, emoji });
@@ -5069,7 +4969,7 @@ async function getReactions(noteId) {
5069
4969
  const rows = await db.select({
5070
4970
  emoji: noteReactions.emoji,
5071
4971
  username: users.username
5072
- }).from(noteReactions).innerJoin(users, eq4(noteReactions.user_id, users.id)).where(eq4(noteReactions.note_id, noteId)).orderBy(noteReactions.emoji).all();
4972
+ }).from(noteReactions).innerJoin(users, eq5(noteReactions.user_id, users.id)).where(eq5(noteReactions.note_id, noteId)).orderBy(noteReactions.emoji).all();
5073
4973
  const grouped = /* @__PURE__ */ new Map();
5074
4974
  for (const r of rows) {
5075
4975
  if (!grouped.has(r.emoji)) grouped.set(r.emoji, []);
@@ -5085,7 +4985,7 @@ async function resolveNoteId(authorSlug) {
5085
4985
  const [author, slug] = authorSlug.split("/", 2);
5086
4986
  if (!author || !slug) return null;
5087
4987
  const db = await getDb();
5088
- const row = await db.select({ id: notes.id }).from(notes).innerJoin(users, eq4(notes.author_id, users.id)).where(and3(eq4(users.username, author), eq4(notes.slug, slug))).get();
4988
+ const row = await db.select({ id: notes.id }).from(notes).innerJoin(users, eq5(notes.author_id, users.id)).where(and4(eq5(users.username, author), eq5(notes.slug, slug))).get();
5089
4989
  return row?.id ?? null;
5090
4990
  }
5091
4991
 
@@ -5185,7 +5085,7 @@ var notes_default = app12;
5185
5085
 
5186
5086
  // src/web/api/pages.ts
5187
5087
  import { readFile as readFile2, stat as stat2 } from "fs/promises";
5188
- import { extname as extname3, resolve as resolve17 } from "path";
5088
+ import { extname as extname3, resolve as resolve16 } from "path";
5189
5089
  import { Hono as Hono13 } from "hono";
5190
5090
  var MIME_TYPES = {
5191
5091
  ".html": "text/html",
@@ -5207,17 +5107,17 @@ var app13 = new Hono13().get("/:name/*", async (c) => {
5207
5107
  const name = c.req.param("name");
5208
5108
  let pagesRoot;
5209
5109
  if (name === "_system") {
5210
- pagesRoot = resolve17(voluteHome(), "shared", "pages");
5110
+ pagesRoot = resolve16(voluteHome(), "shared", "pages");
5211
5111
  } else {
5212
5112
  if (!await findMind(name)) return c.text("Not found", 404);
5213
- pagesRoot = resolve17(mindDir(name), "home", "public", "pages");
5113
+ pagesRoot = resolve16(mindDir(name), "home", "public", "pages");
5214
5114
  }
5215
5115
  const wildcard = c.req.path.replace(`/pages/${name}`, "") || "/";
5216
- const requestedPath = resolve17(pagesRoot, wildcard.slice(1));
5116
+ const requestedPath = resolve16(pagesRoot, wildcard.slice(1));
5217
5117
  if (!requestedPath.startsWith(pagesRoot)) return c.text("Forbidden", 403);
5218
5118
  let fileStat = await stat2(requestedPath).catch(() => null);
5219
5119
  if (fileStat?.isDirectory()) {
5220
- const indexPath = resolve17(requestedPath, "index.html");
5120
+ const indexPath = resolve16(requestedPath, "index.html");
5221
5121
  fileStat = await stat2(indexPath).catch(() => null);
5222
5122
  if (fileStat?.isFile()) {
5223
5123
  const body = await readFile2(indexPath);
@@ -5237,7 +5137,7 @@ var pages_default = app13;
5237
5137
 
5238
5138
  // src/web/api/prompts.ts
5239
5139
  import { zValidator as zValidator6 } from "@hono/zod-validator";
5240
- import { eq as eq5, sql as sql3 } from "drizzle-orm";
5140
+ import { eq as eq6, sql as sql3 } from "drizzle-orm";
5241
5141
  import { Hono as Hono14 } from "hono";
5242
5142
  import { z as z6 } from "zod";
5243
5143
  var app14 = new Hono14().get("/", async (c) => {
@@ -5281,20 +5181,20 @@ var app14 = new Hono14().get("/", async (c) => {
5281
5181
  return c.json({ error: "Unknown prompt key" }, 404);
5282
5182
  }
5283
5183
  const db = await getDb();
5284
- await db.delete(systemPrompts).where(eq5(systemPrompts.key, key));
5184
+ await db.delete(systemPrompts).where(eq6(systemPrompts.key, key));
5285
5185
  return c.json({ ok: true });
5286
5186
  });
5287
5187
  var prompts_default = app14;
5288
5188
 
5289
5189
  // src/web/api/public-files.ts
5290
5190
  import { readdir as readdir2, readFile as readFile3, stat as stat3 } from "fs/promises";
5291
- import { extname as extname4, resolve as resolve18 } from "path";
5191
+ import { extname as extname4, resolve as resolve17 } from "path";
5292
5192
  import { Hono as Hono15 } from "hono";
5293
5193
  var MAX_FILE_SIZE = 50 * 1024 * 1024;
5294
5194
  async function resolvePublicRoot(name) {
5295
- if (name === "_system") return resolve18(voluteHome(), "shared");
5195
+ if (name === "_system") return resolve17(voluteHome(), "shared");
5296
5196
  if (!await findMind(name)) return null;
5297
- return resolve18(mindDir(name), "home", "public");
5197
+ return resolve17(mindDir(name), "home", "public");
5298
5198
  }
5299
5199
  function hasDotSegment(relativePath) {
5300
5200
  return relativePath.split("/").some((seg) => seg.startsWith("."));
@@ -5341,7 +5241,7 @@ var app15 = new Hono15().get("/:name/", async (c) => {
5341
5241
  if (!publicRoot) return c.text("Not found", 404);
5342
5242
  const wildcard = c.req.path.replace(`/public/${name}`, "") || "/";
5343
5243
  const relativePath = wildcard.slice(1);
5344
- const requestedPath = resolve18(publicRoot, relativePath);
5244
+ const requestedPath = resolve17(publicRoot, relativePath);
5345
5245
  if (!requestedPath.startsWith(publicRoot)) return c.text("Forbidden", 403);
5346
5246
  if (hasDotSegment(relativePath)) return c.text("Forbidden", 403);
5347
5247
  let fileStat;
@@ -5388,25 +5288,60 @@ function writeSchedules(name, schedules) {
5388
5288
  config.schedules = schedules.length > 0 ? schedules : void 0;
5389
5289
  writeVoluteConfig(dir, config);
5390
5290
  getScheduler().loadSchedules(name);
5291
+ getSleepManagerIfReady()?.invalidateSleepConfig(name);
5391
5292
  fireWebhook({
5392
5293
  event: "schedule_changed",
5393
5294
  mind: name,
5394
5295
  data: { schedules }
5395
5296
  });
5396
5297
  }
5397
- var app16 = new Hono16().get("/:name/schedules", async (c) => {
5298
+ var app16 = new Hono16().get("/:name/clock/status", async (c) => {
5299
+ const name = c.req.param("name");
5300
+ if (!await findMind(name)) return c.json({ error: "Mind not found" }, 404);
5301
+ const sleepManager = getSleepManagerIfReady();
5302
+ const sleepState = sleepManager?.getState(name) ?? null;
5303
+ const sleepConfig = sleepManager?.getSleepConfig(name) ?? null;
5304
+ const schedules = readSchedules(name);
5305
+ const now = /* @__PURE__ */ new Date();
5306
+ const in24h = new Date(now.getTime() + 24 * 60 * 6e4);
5307
+ const upcoming = [];
5308
+ for (const s of schedules) {
5309
+ if (!s.enabled) continue;
5310
+ if (s.fireAt) {
5311
+ const fireDate = new Date(s.fireAt);
5312
+ if (fireDate >= now && fireDate <= in24h) {
5313
+ upcoming.push({ id: s.id, at: fireDate.toISOString(), type: "timer" });
5314
+ }
5315
+ } else if (s.cron) {
5316
+ try {
5317
+ const interval = CronExpressionParser.parse(s.cron);
5318
+ const next = interval.next().toDate();
5319
+ if (next <= in24h) {
5320
+ upcoming.push({ id: s.id, at: next.toISOString(), type: "cron" });
5321
+ }
5322
+ } catch {
5323
+ slog.warn(`invalid cron "${s.cron}" for schedule "${s.id}" of ${name}`);
5324
+ }
5325
+ }
5326
+ }
5327
+ upcoming.sort((a, b) => a.at.localeCompare(b.at));
5328
+ return c.json({ sleep: sleepState, sleepConfig, schedules, upcoming });
5329
+ }).get("/:name/schedules", async (c) => {
5398
5330
  const name = c.req.param("name");
5399
5331
  if (!await findMind(name)) return c.json({ error: "Mind not found" }, 404);
5400
5332
  return c.json(readSchedules(name));
5401
- }).post("/:name/schedules", requireAdmin, async (c) => {
5333
+ }).post("/:name/schedules", requireSelf(), async (c) => {
5402
5334
  const name = c.req.param("name");
5403
5335
  const entry = await findMind(name);
5404
5336
  if (!entry) return c.json({ error: "Mind not found" }, 404);
5405
5337
  if (entry.stage === "seed")
5406
5338
  return c.json({ error: "Seed minds cannot use schedules \u2014 sprout first" }, 403);
5407
5339
  const body = await c.req.json();
5408
- if (!body.cron) {
5409
- return c.json({ error: "cron is required" }, 400);
5340
+ if (!body.cron && !body.fireAt) {
5341
+ return c.json({ error: "cron or fireAt is required" }, 400);
5342
+ }
5343
+ if (body.cron && body.fireAt) {
5344
+ return c.json({ error: "cron and fireAt are mutually exclusive" }, 400);
5410
5345
  }
5411
5346
  if (!body.message && !body.script) {
5412
5347
  return c.json({ error: "message or script is required" }, 400);
@@ -5414,24 +5349,40 @@ var app16 = new Hono16().get("/:name/schedules", async (c) => {
5414
5349
  if (body.message && body.script) {
5415
5350
  return c.json({ error: "message and script are mutually exclusive" }, 400);
5416
5351
  }
5417
- try {
5418
- CronExpressionParser.parse(body.cron);
5419
- } catch {
5420
- return c.json({ error: `Invalid cron expression: ${body.cron}` }, 400);
5352
+ if (body.cron) {
5353
+ try {
5354
+ CronExpressionParser.parse(body.cron);
5355
+ } catch {
5356
+ return c.json({ error: `Invalid cron expression: ${body.cron}` }, 400);
5357
+ }
5358
+ }
5359
+ if (body.fireAt && Number.isNaN(new Date(body.fireAt).getTime())) {
5360
+ return c.json({ error: `Invalid fireAt date: ${body.fireAt}` }, 400);
5361
+ }
5362
+ if (body.whileSleeping && !["skip", "queue", "trigger-wake"].includes(body.whileSleeping)) {
5363
+ return c.json(
5364
+ {
5365
+ error: `Invalid whileSleeping value: ${body.whileSleeping} (must be skip, queue, or trigger-wake)`
5366
+ },
5367
+ 400
5368
+ );
5421
5369
  }
5422
5370
  const schedules = readSchedules(name);
5423
5371
  const id = body.id || `schedule-${Date.now()}`;
5424
5372
  if (schedules.some((s) => s.id === id)) {
5425
5373
  return c.json({ error: `Schedule "${id}" already exists` }, 409);
5426
5374
  }
5427
- const schedule = { id, cron: body.cron, enabled: body.enabled ?? true };
5375
+ const schedule = { id, enabled: body.enabled ?? true };
5376
+ if (body.cron) schedule.cron = body.cron;
5377
+ if (body.fireAt) schedule.fireAt = body.fireAt;
5428
5378
  if (body.message) schedule.message = body.message;
5429
5379
  if (body.script) schedule.script = body.script;
5430
5380
  if (body.channel) schedule.channel = body.channel;
5381
+ if (body.whileSleeping) schedule.whileSleeping = body.whileSleeping;
5431
5382
  schedules.push(schedule);
5432
5383
  writeSchedules(name, schedules);
5433
5384
  return c.json({ ok: true, id }, 201);
5434
- }).put("/:name/schedules/:id", requireAdmin, async (c) => {
5385
+ }).put("/:name/schedules/:id", requireSelf(), async (c) => {
5435
5386
  const name = c.req.param("name");
5436
5387
  const id = c.req.param("id");
5437
5388
  if (!await findMind(name)) return c.json({ error: "Mind not found" }, 404);
@@ -5449,6 +5400,14 @@ var app16 = new Hono16().get("/:name/schedules", async (c) => {
5449
5400
  return c.json({ error: `Invalid cron expression: ${body.cron}` }, 400);
5450
5401
  }
5451
5402
  schedules[idx].cron = body.cron;
5403
+ delete schedules[idx].fireAt;
5404
+ }
5405
+ if (body.fireAt !== void 0) {
5406
+ if (Number.isNaN(new Date(body.fireAt).getTime())) {
5407
+ return c.json({ error: `Invalid fireAt date: ${body.fireAt}` }, 400);
5408
+ }
5409
+ schedules[idx].fireAt = body.fireAt;
5410
+ delete schedules[idx].cron;
5452
5411
  }
5453
5412
  if (body.message !== void 0) {
5454
5413
  schedules[idx].message = body.message;
@@ -5458,11 +5417,21 @@ var app16 = new Hono16().get("/:name/schedules", async (c) => {
5458
5417
  schedules[idx].script = body.script;
5459
5418
  delete schedules[idx].message;
5460
5419
  }
5420
+ if (body.whileSleeping && !["skip", "queue", "trigger-wake"].includes(body.whileSleeping)) {
5421
+ return c.json(
5422
+ {
5423
+ error: `Invalid whileSleeping value: ${body.whileSleeping} (must be skip, queue, or trigger-wake)`
5424
+ },
5425
+ 400
5426
+ );
5427
+ }
5461
5428
  if (body.enabled !== void 0) schedules[idx].enabled = body.enabled;
5462
5429
  if (body.channel !== void 0) schedules[idx].channel = body.channel || void 0;
5430
+ if (body.whileSleeping !== void 0)
5431
+ schedules[idx].whileSleeping = body.whileSleeping || void 0;
5463
5432
  writeSchedules(name, schedules);
5464
5433
  return c.json({ ok: true });
5465
- }).delete("/:name/schedules/:id", requireAdmin, async (c) => {
5434
+ }).delete("/:name/schedules/:id", requireSelf(), async (c) => {
5466
5435
  const name = c.req.param("name");
5467
5436
  const id = c.req.param("id");
5468
5437
  if (!await findMind(name)) return c.json({ error: "Mind not found" }, 404);
@@ -5520,44 +5489,13 @@ var app17 = new Hono17().post("/:name/shared/merge", requireAdmin, async (c) =>
5520
5489
  } catch (err) {
5521
5490
  return c.json({ error: err instanceof Error ? err.message : "Merge failed" }, 500);
5522
5491
  }
5523
- }).post("/:name/shared/pull", requireAdmin, async (c) => {
5524
- const name = c.req.param("name");
5525
- const entry = await findMind(name);
5526
- if (!entry) return c.json({ error: "Mind not found" }, 404);
5527
- try {
5528
- const result = await sharedPull(name, mindDir(name));
5529
- return c.json(result);
5530
- } catch (err) {
5531
- return c.json({ error: err instanceof Error ? err.message : "Pull failed" }, 500);
5532
- }
5533
- }).get("/:name/shared/log", async (c) => {
5534
- const name = c.req.param("name");
5535
- const entry = await findMind(name);
5536
- if (!entry) return c.json({ error: "Mind not found" }, 404);
5537
- const limit = parseInt(c.req.query("limit") ?? "20", 10) || 20;
5538
- try {
5539
- const log2 = await sharedLog(limit);
5540
- return c.text(log2);
5541
- } catch (err) {
5542
- return c.json({ error: err instanceof Error ? err.message : "Failed to read log" }, 500);
5543
- }
5544
- }).get("/:name/shared/status", async (c) => {
5545
- const name = c.req.param("name");
5546
- const entry = await findMind(name);
5547
- if (!entry) return c.json({ error: "Mind not found" }, 404);
5548
- try {
5549
- const status = await sharedStatus(name);
5550
- return c.text(status);
5551
- } catch (err) {
5552
- return c.json({ error: err instanceof Error ? err.message : "Failed to get status" }, 500);
5553
- }
5554
5492
  });
5555
5493
  var shared_default = app17;
5556
5494
 
5557
5495
  // src/web/api/skills.ts
5558
- import { existsSync as existsSync15, mkdtempSync, readdirSync as readdirSync5, rmSync as rmSync6 } from "fs";
5496
+ import { existsSync as existsSync14, mkdtempSync, readdirSync as readdirSync4, rmSync as rmSync5 } from "fs";
5559
5497
  import { tmpdir } from "os";
5560
- import { join as join2, resolve as resolve19 } from "path";
5498
+ import { join, resolve as resolve18 } from "path";
5561
5499
  import AdmZip from "adm-zip";
5562
5500
  import { Hono as Hono18 } from "hono";
5563
5501
  var app18 = new Hono18().get("/", async (c) => {
@@ -5567,7 +5505,7 @@ var app18 = new Hono18().get("/", async (c) => {
5567
5505
  const id = c.req.param("id");
5568
5506
  const skill = await getSharedSkill(id);
5569
5507
  if (!skill) return c.json({ error: "Skill not found" }, 404);
5570
- const dir = join2(sharedSkillsDir(), id);
5508
+ const dir = join(sharedSkillsDir(), id);
5571
5509
  const files = listFilesRecursive(dir);
5572
5510
  return c.json({ ...skill, files });
5573
5511
  }).post("/upload", requireAdmin, async (c) => {
@@ -5580,24 +5518,24 @@ var app18 = new Hono18().get("/", async (c) => {
5580
5518
  return c.json({ error: "Only .zip files are accepted" }, 400);
5581
5519
  }
5582
5520
  const buffer2 = Buffer.from(await file.arrayBuffer());
5583
- const tmpDir = mkdtempSync(join2(tmpdir(), "volute-skill-upload-"));
5521
+ const tmpDir = mkdtempSync(join(tmpdir(), "volute-skill-upload-"));
5584
5522
  try {
5585
5523
  const zip = new AdmZip(buffer2);
5586
5524
  for (const entry of zip.getEntries()) {
5587
- const target = resolve19(tmpDir, entry.entryName);
5525
+ const target = resolve18(tmpDir, entry.entryName);
5588
5526
  if (!target.startsWith(tmpDir)) {
5589
5527
  return c.json({ error: "Invalid zip: paths must not escape archive" }, 400);
5590
5528
  }
5591
5529
  }
5592
5530
  zip.extractAllTo(tmpDir, true);
5593
5531
  let skillDir = null;
5594
- if (existsSync15(join2(tmpDir, "SKILL.md"))) {
5532
+ if (existsSync14(join(tmpDir, "SKILL.md"))) {
5595
5533
  skillDir = tmpDir;
5596
5534
  } else {
5597
- const entries = readdirSync5(tmpDir, { withFileTypes: true }).filter((e) => e.isDirectory());
5535
+ const entries = readdirSync4(tmpDir, { withFileTypes: true }).filter((e) => e.isDirectory());
5598
5536
  for (const entry of entries) {
5599
- if (existsSync15(join2(tmpDir, entry.name, "SKILL.md"))) {
5600
- skillDir = join2(tmpDir, entry.name);
5537
+ if (existsSync14(join(tmpDir, entry.name, "SKILL.md"))) {
5538
+ skillDir = join(tmpDir, entry.name);
5601
5539
  break;
5602
5540
  }
5603
5541
  }
@@ -5613,7 +5551,7 @@ var app18 = new Hono18().get("/", async (c) => {
5613
5551
  }
5614
5552
  throw e;
5615
5553
  } finally {
5616
- rmSync6(tmpDir, { recursive: true, force: true });
5554
+ rmSync5(tmpDir, { recursive: true, force: true });
5617
5555
  }
5618
5556
  }).delete("/:id", requireAdmin, async (c) => {
5619
5557
  const id = c.req.param("id");
@@ -5650,10 +5588,10 @@ var app19 = new Hono19().post("/restart", requireAdmin, (c) => {
5650
5588
  stream.writeSSE({ data: JSON.stringify(entry) }).catch(() => {
5651
5589
  });
5652
5590
  });
5653
- await new Promise((resolve24) => {
5591
+ await new Promise((resolve23) => {
5654
5592
  stream.onAbort(() => {
5655
5593
  unsubscribe();
5656
- resolve24();
5594
+ resolve23();
5657
5595
  });
5658
5596
  });
5659
5597
  });
@@ -5738,6 +5676,38 @@ var app19 = new Hono19().post("/restart", requireAdmin, (c) => {
5738
5676
  ).post("/logout", requireAdmin, (c) => {
5739
5677
  deleteSystemsConfig();
5740
5678
  return c.json({ ok: true });
5679
+ }).put("/pages/publish/:name", requireAdmin, async (c) => {
5680
+ const config = readSystemsConfig();
5681
+ if (!config) return c.json({ error: "Not connected to volute.systems" }, 400);
5682
+ const name = c.req.param("name");
5683
+ const body = await c.req.text();
5684
+ try {
5685
+ const res = await fetch(`${config.apiUrl}/api/pages/publish/${name}`, {
5686
+ method: "PUT",
5687
+ headers: {
5688
+ "Content-Type": "application/json",
5689
+ Authorization: `Bearer ${config.apiKey}`
5690
+ },
5691
+ body
5692
+ });
5693
+ const data = await res.json().catch(() => ({ error: `HTTP ${res.status}` }));
5694
+ return c.json(data, res.status);
5695
+ } catch (err) {
5696
+ return c.json({ error: `Connection failed: ${err.message}` }, 502);
5697
+ }
5698
+ }).get("/pages/status/:name", requireAdmin, async (c) => {
5699
+ const config = readSystemsConfig();
5700
+ if (!config) return c.json({ error: "Not connected to volute.systems" }, 400);
5701
+ const name = c.req.param("name");
5702
+ try {
5703
+ const res = await fetch(`${config.apiUrl}/api/pages/status/${name}`, {
5704
+ headers: { Authorization: `Bearer ${config.apiKey}` }
5705
+ });
5706
+ const data = await res.json().catch(() => ({ error: `HTTP ${res.status}` }));
5707
+ return c.json(data, res.status);
5708
+ } catch (err) {
5709
+ return c.json({ error: `Connection failed: ${err.message}` }, 502);
5710
+ }
5741
5711
  });
5742
5712
  var system_default = app19;
5743
5713
 
@@ -5805,11 +5775,11 @@ async function fanOutToMinds(opts) {
5805
5775
  const mindParticipants = participants.filter((p) => p.userType === "mind");
5806
5776
  const participantNames = participants.map((p) => p.username);
5807
5777
  const isDM = opts.isDM ?? participants.length === 2;
5808
- const channelEntryType = opts.channelEntryType ?? (isDM ? "dm" : "group");
5809
- const { getMindManager: getMindManager2 } = await import("./mind-manager-M6EMUW5I.js");
5810
- const { getSleepManagerIfReady } = await import("./sleep-manager-MWYHM5HV.js");
5778
+ const channelEntryType = opts.channelEntryType ?? (isDM ? "dm" : "channel");
5779
+ const { getMindManager: getMindManager2 } = await import("./mind-manager-P66HQDNE.js");
5780
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-G4B5GW5P.js");
5811
5781
  const manager = getMindManager2();
5812
- const sm = getSleepManagerIfReady();
5782
+ const sm = getSleepManagerIfReady2();
5813
5783
  const targetMinds = mindParticipants.map((ap) => {
5814
5784
  const key = opts.targetName ? opts.targetName(ap.username) : ap.username;
5815
5785
  if (manager.isRunning(key) || sm?.isSleeping(ap.username)) return ap.username;
@@ -5928,7 +5898,7 @@ var app22 = new Hono22().use("*", authMiddleware).post("/minds/:name/chat", zVal
5928
5898
  senderName,
5929
5899
  convTitle,
5930
5900
  isDM,
5931
- channelEntryType: conv?.type === "channel" ? "group" : isDM ? "dm" : "group",
5901
+ channelEntryType: isDM ? "dm" : "channel",
5932
5902
  slugExtra: conv ? { convType: conv.type, convName: conv.name } : void 0,
5933
5903
  targetName: (username) => username === baseName ? name : username
5934
5904
  });
@@ -5950,11 +5920,11 @@ var app22 = new Hono22().use("*", authMiddleware).post("/minds/:name/chat", zVal
5950
5920
  if (!stream.aborted) logger_default.error("[v1-chat] SSE ping error:", logger_default.errorData(err));
5951
5921
  });
5952
5922
  }, 15e3);
5953
- await new Promise((resolve24) => {
5923
+ await new Promise((resolve23) => {
5954
5924
  stream.onAbort(() => {
5955
5925
  unsubscribe();
5956
5926
  clearInterval(keepAlive);
5957
- resolve24();
5927
+ resolve23();
5958
5928
  });
5959
5929
  });
5960
5930
  });
@@ -5985,7 +5955,7 @@ var app22 = new Hono22().use("*", authMiddleware).post("/minds/:name/chat", zVal
5985
5955
  senderName,
5986
5956
  convTitle: conv.title,
5987
5957
  isDM,
5988
- channelEntryType: conv.type === "channel" ? "group" : isDM ? "dm" : "group",
5958
+ channelEntryType: isDM ? "dm" : "channel",
5989
5959
  slugExtra: { convType: conv.type, convName: conv.name }
5990
5960
  });
5991
5961
  return c.json({ ok: true, conversationId: body.conversationId });
@@ -6222,8 +6192,8 @@ var app24 = new Hono24().use("*", authMiddleware).get("/", async (c) => {
6222
6192
  });
6223
6193
  }, 15e3);
6224
6194
  cleanups.push(() => clearInterval(keepAlive));
6225
- await new Promise((resolve24) => {
6226
- stream.onAbort(() => resolve24());
6195
+ await new Promise((resolve23) => {
6196
+ stream.onAbort(() => resolve23());
6227
6197
  });
6228
6198
  } finally {
6229
6199
  for (const cleanup of cleanups) {
@@ -6238,16 +6208,16 @@ var app24 = new Hono24().use("*", authMiddleware).get("/", async (c) => {
6238
6208
  var events_default = app24;
6239
6209
 
6240
6210
  // src/web/api/variants.ts
6241
- import { existsSync as existsSync16, mkdirSync as mkdirSync11, writeFileSync as writeFileSync10 } from "fs";
6242
- import { resolve as resolve21 } from "path";
6211
+ import { existsSync as existsSync15, mkdirSync as mkdirSync10, writeFileSync as writeFileSync9 } from "fs";
6212
+ import { resolve as resolve20 } from "path";
6243
6213
  import { Hono as Hono25 } from "hono";
6244
6214
 
6245
6215
  // src/lib/spawn-server.ts
6246
6216
  import { spawn as spawn4 } from "child_process";
6247
- import { closeSync, mkdirSync as mkdirSync10, openSync, readFileSync as readFileSync13 } from "fs";
6248
- import { resolve as resolve20 } from "path";
6217
+ import { closeSync, mkdirSync as mkdirSync9, openSync, readFileSync as readFileSync12 } from "fs";
6218
+ import { resolve as resolve19 } from "path";
6249
6219
  function tsxBin(cwd) {
6250
- return resolve20(cwd, "node_modules", ".bin", "tsx");
6220
+ return resolve19(cwd, "node_modules", ".bin", "tsx");
6251
6221
  }
6252
6222
  function spawnServer(cwd, port, options) {
6253
6223
  if (options?.detached) {
@@ -6260,31 +6230,31 @@ function spawnAttached(cwd, port) {
6260
6230
  cwd,
6261
6231
  stdio: ["ignore", "pipe", "pipe"]
6262
6232
  });
6263
- return new Promise((resolve24) => {
6264
- const timeout = setTimeout(() => resolve24(null), 3e4);
6233
+ return new Promise((resolve23) => {
6234
+ const timeout = setTimeout(() => resolve23(null), 3e4);
6265
6235
  function checkOutput(data) {
6266
6236
  const match = data.toString().match(/listening on :(\d+)/);
6267
6237
  if (match) {
6268
6238
  clearTimeout(timeout);
6269
- resolve24({ child, actualPort: parseInt(match[1], 10) });
6239
+ resolve23({ child, actualPort: parseInt(match[1], 10) });
6270
6240
  }
6271
6241
  }
6272
6242
  child.stdout?.on("data", checkOutput);
6273
6243
  child.stderr?.on("data", checkOutput);
6274
6244
  child.on("error", () => {
6275
6245
  clearTimeout(timeout);
6276
- resolve24(null);
6246
+ resolve23(null);
6277
6247
  });
6278
6248
  child.on("exit", () => {
6279
6249
  clearTimeout(timeout);
6280
- resolve24(null);
6250
+ resolve23(null);
6281
6251
  });
6282
6252
  });
6283
6253
  }
6284
6254
  function spawnDetached(cwd, port, logDir) {
6285
- const logsDir = logDir ?? resolve20(cwd, ".mind", "logs");
6286
- mkdirSync10(logsDir, { recursive: true });
6287
- const logPath = resolve20(logsDir, "mind.log");
6255
+ const logsDir = logDir ?? resolve19(cwd, ".mind", "logs");
6256
+ mkdirSync9(logsDir, { recursive: true });
6257
+ const logPath = resolve19(logsDir, "mind.log");
6288
6258
  const logFd = openSync(logPath, "a");
6289
6259
  const child = spawn4(tsxBin(cwd), ["src/server.ts", "--port", String(port)], {
6290
6260
  cwd,
@@ -6304,7 +6274,7 @@ function spawnDetached(cwd, port, logDir) {
6304
6274
  }
6305
6275
  const interval = setInterval(() => {
6306
6276
  try {
6307
- const content = readFileSync13(logPath, "utf-8");
6277
+ const content = readFileSync12(logPath, "utf-8");
6308
6278
  const match = content.match(/listening on :(\d+)/);
6309
6279
  if (match) {
6310
6280
  finish({ child, actualPort: parseInt(match[1], 10) });
@@ -6380,7 +6350,7 @@ var app25 = new Hono25().get("/:name/variants", async (c) => {
6380
6350
  logger_default.warn(`failed to sync variant status for ${name}`, logger_default.errorData(err));
6381
6351
  }
6382
6352
  return c.json(results);
6383
- }).post("/:name/variants", requireAdmin, async (c) => {
6353
+ }).post("/:name/variants", requireSelf(), async (c) => {
6384
6354
  const mindName = c.req.param("name");
6385
6355
  const entry = await findMind(mindName);
6386
6356
  if (!entry) return c.json({ error: "Mind not found" }, 404);
@@ -6400,11 +6370,11 @@ var app25 = new Hono25().get("/:name/variants", async (c) => {
6400
6370
  return c.json({ error: `Name already in use: ${variantName}` }, 409);
6401
6371
  }
6402
6372
  const projectRoot = mindDir(mindName);
6403
- const variantDir = resolve21(projectRoot, ".variants", variantName);
6404
- if (existsSync16(variantDir)) {
6373
+ const variantDir = resolve20(projectRoot, ".variants", variantName);
6374
+ if (existsSync15(variantDir)) {
6405
6375
  return c.json({ error: `Variant directory already exists: ${variantDir}` }, 409);
6406
6376
  }
6407
- mkdirSync11(resolve21(projectRoot, ".variants"), { recursive: true });
6377
+ mkdirSync10(resolve20(projectRoot, ".variants"), { recursive: true });
6408
6378
  try {
6409
6379
  await gitExec(["worktree", "add", "-b", variantName, variantDir], { cwd: projectRoot });
6410
6380
  } catch (e) {
@@ -6417,7 +6387,7 @@ var app25 = new Hono25().get("/:name/variants", async (c) => {
6417
6387
  const [cmd, args] = await wrapForIsolation("npm", ["install"], mindName);
6418
6388
  await exec(cmd, args, {
6419
6389
  cwd: variantDir,
6420
- env: { ...process.env, HOME: resolve21(variantDir, "home") }
6390
+ env: { ...process.env, HOME: resolve20(variantDir, "home") }
6421
6391
  });
6422
6392
  } else {
6423
6393
  await exec("npm", ["install"], { cwd: variantDir });
@@ -6427,7 +6397,7 @@ var app25 = new Hono25().get("/:name/variants", async (c) => {
6427
6397
  return c.json({ error: `npm install failed: ${msg}` }, 500);
6428
6398
  }
6429
6399
  if (body.soul) {
6430
- writeFileSync10(resolve21(variantDir, "home/SOUL.md"), body.soul);
6400
+ writeFileSync9(resolve20(variantDir, "home/SOUL.md"), body.soul);
6431
6401
  }
6432
6402
  const variantPort = body.port ?? await nextPort();
6433
6403
  await addVariant(variantName, mindName, variantPort, variantDir, variantName);
@@ -6443,7 +6413,7 @@ var app25 = new Hono25().get("/:name/variants", async (c) => {
6443
6413
  ok: true,
6444
6414
  variant: { name: variantName, branch: variantName, path: variantDir, port: variantPort }
6445
6415
  });
6446
- }).post("/:name/variants/:variant/merge", requireAdmin, async (c) => {
6416
+ }).post("/:name/variants/:variant/merge", requireSelf(), async (c) => {
6447
6417
  const mindName = c.req.param("name");
6448
6418
  const variantName = c.req.param("variant");
6449
6419
  const parentEntry = await findMind(mindName);
@@ -6462,7 +6432,7 @@ var app25 = new Hono25().get("/:name/variants", async (c) => {
6462
6432
  } catch {
6463
6433
  }
6464
6434
  const projectRoot = mindDir(mindName);
6465
- if (existsSync16(variantEntry.dir)) {
6435
+ if (existsSync15(variantEntry.dir)) {
6466
6436
  const status = (await gitExec(["status", "--porcelain"], { cwd: variantEntry.dir })).trim();
6467
6437
  if (status) {
6468
6438
  try {
@@ -6536,7 +6506,7 @@ var app25 = new Hono25().get("/:name/variants", async (c) => {
6536
6506
  const [cmd, args] = await wrapForIsolation("npm", ["install"], mindName);
6537
6507
  await exec(cmd, args, {
6538
6508
  cwd: projectRoot,
6539
- env: { ...process.env, HOME: resolve21(projectRoot, "home") }
6509
+ env: { ...process.env, HOME: resolve20(projectRoot, "home") }
6540
6510
  });
6541
6511
  } else {
6542
6512
  await exec("npm", ["install"], { cwd: projectRoot });
@@ -6564,7 +6534,7 @@ var app25 = new Hono25().get("/:name/variants", async (c) => {
6564
6534
  logger_default.warn(restartWarning);
6565
6535
  }
6566
6536
  return c.json({ ok: true, ...restartWarning && { warning: restartWarning } });
6567
- }).delete("/:name/variants/:variant", requireAdmin, async (c) => {
6537
+ }).delete("/:name/variants/:variant", requireSelf(), async (c) => {
6568
6538
  const mindName = c.req.param("name");
6569
6539
  const variantName = c.req.param("variant");
6570
6540
  const parentEntry = await findMind(mindName);
@@ -6739,11 +6709,11 @@ async function fanOutToMinds2(opts) {
6739
6709
  const mindParticipants = participants.filter((p) => p.userType === "mind");
6740
6710
  const participantNames = participants.map((p) => p.username);
6741
6711
  const isDM = opts.isDM ?? participants.length === 2;
6742
- const channelEntryType = opts.channelEntryType ?? (isDM ? "dm" : "group");
6743
- const { getMindManager: getMindManager2 } = await import("./mind-manager-M6EMUW5I.js");
6744
- const { getSleepManagerIfReady } = await import("./sleep-manager-MWYHM5HV.js");
6712
+ const channelEntryType = opts.channelEntryType ?? (isDM ? "dm" : "channel");
6713
+ const { getMindManager: getMindManager2 } = await import("./mind-manager-P66HQDNE.js");
6714
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-G4B5GW5P.js");
6745
6715
  const manager = getMindManager2();
6746
- const sm = getSleepManagerIfReady();
6716
+ const sm = getSleepManagerIfReady2();
6747
6717
  const targetMinds = mindParticipants.map((ap) => {
6748
6718
  const key = opts.targetName ? opts.targetName(ap.username) : ap.username;
6749
6719
  if (manager.isRunning(key) || sm?.isSleeping(ap.username)) return ap.username;
@@ -6790,6 +6760,11 @@ async function fanOutToMinds2(opts) {
6790
6760
  });
6791
6761
  }
6792
6762
  }
6763
+ var fileSchema = z12.object({
6764
+ filename: z12.string(),
6765
+ data: z12.string()
6766
+ // base64
6767
+ });
6793
6768
  var chatSchema = z12.object({
6794
6769
  message: z12.string().optional(),
6795
6770
  conversationId: z12.string().optional(),
@@ -6799,7 +6774,8 @@ var chatSchema = z12.object({
6799
6774
  media_type: z12.string(),
6800
6775
  data: z12.string()
6801
6776
  })
6802
- ).optional()
6777
+ ).optional(),
6778
+ files: z12.array(fileSchema).optional()
6803
6779
  });
6804
6780
  var app27 = new Hono27().post("/:name/chat", zValidator12("json", chatSchema), async (c) => {
6805
6781
  const name = c.req.param("name");
@@ -6807,8 +6783,8 @@ var app27 = new Hono27().post("/:name/chat", zValidator12("json", chatSchema), a
6807
6783
  const entry = await findMind(baseName);
6808
6784
  if (!entry) return c.json({ error: "Mind not found" }, 404);
6809
6785
  const body = c.req.valid("json");
6810
- if (!body.message && (!body.images || body.images.length === 0)) {
6811
- return c.json({ error: "message or images required" }, 400);
6786
+ if (!body.message && (!body.images || body.images.length === 0) && (!body.files || body.files.length === 0)) {
6787
+ return c.json({ error: "message, images, or files required" }, 400);
6812
6788
  }
6813
6789
  const user = c.get("user");
6814
6790
  const mindUser = await getOrCreateMindUser(baseName);
@@ -6849,10 +6825,34 @@ var app27 = new Hono27().post("/:name/chat", zValidator12("json", chatSchema), a
6849
6825
  }
6850
6826
  const conv = await getConversation(conversationId);
6851
6827
  const convTitle = conv?.title ?? null;
6828
+ const fileNotifications = [];
6829
+ if (body.files && body.files.length > 0) {
6830
+ const MAX_FILE_SIZE2 = 50 * 1024 * 1024;
6831
+ for (const file of body.files) {
6832
+ const pathErr = validateFilePath(file.filename);
6833
+ if (pathErr) return c.json({ error: `Invalid filename: ${pathErr}` }, 400);
6834
+ const content = Buffer.from(file.data, "base64");
6835
+ if (content.length > MAX_FILE_SIZE2) {
6836
+ return c.json(
6837
+ {
6838
+ error: `File too large: ${file.filename} (${formatFileSize(content.length)}, max ${formatFileSize(MAX_FILE_SIZE2)})`
6839
+ },
6840
+ 413
6841
+ );
6842
+ }
6843
+ const { id } = stageFile(baseName, senderName, file.filename, content, file.filename);
6844
+ fileNotifications.push(
6845
+ `[file] ${senderName} sent ${file.filename} (${formatFileSize(content.length)}) \u2014 run: volute chat accept ${id}`
6846
+ );
6847
+ }
6848
+ }
6852
6849
  const contentBlocks = [];
6853
6850
  if (body.message) {
6854
6851
  contentBlocks.push({ type: "text", text: body.message });
6855
6852
  }
6853
+ for (const note of fileNotifications) {
6854
+ contentBlocks.push({ type: "text", text: note });
6855
+ }
6856
6856
  if (body.images) {
6857
6857
  for (const img of body.images) {
6858
6858
  contentBlocks.push({ type: "image", media_type: img.media_type, data: img.data });
@@ -6872,7 +6872,7 @@ var app27 = new Hono27().post("/:name/chat", zValidator12("json", chatSchema), a
6872
6872
  senderName,
6873
6873
  convTitle,
6874
6874
  isDM,
6875
- channelEntryType: conv?.type === "channel" ? "group" : isDM ? "dm" : "group",
6875
+ channelEntryType: isDM ? "dm" : "channel",
6876
6876
  slugExtra: conv ? { convType: conv.type, convName: conv.name } : void 0,
6877
6877
  targetName: (username) => username === baseName ? name : username
6878
6878
  });
@@ -6894,11 +6894,11 @@ var app27 = new Hono27().post("/:name/chat", zValidator12("json", chatSchema), a
6894
6894
  if (!stream.aborted) console.error("[chat] SSE ping error:", err);
6895
6895
  });
6896
6896
  }, 15e3);
6897
- await new Promise((resolve24) => {
6897
+ await new Promise((resolve23) => {
6898
6898
  stream.onAbort(() => {
6899
6899
  unsubscribe();
6900
6900
  clearInterval(keepAlive);
6901
- resolve24();
6901
+ resolve23();
6902
6902
  });
6903
6903
  });
6904
6904
  });
@@ -6906,7 +6906,8 @@ var app27 = new Hono27().post("/:name/chat", zValidator12("json", chatSchema), a
6906
6906
  var unifiedChatSchema2 = z12.object({
6907
6907
  message: z12.string().optional(),
6908
6908
  conversationId: z12.string(),
6909
- images: z12.array(z12.object({ media_type: z12.string(), data: z12.string() })).optional()
6909
+ images: z12.array(z12.object({ media_type: z12.string(), data: z12.string() })).optional(),
6910
+ files: z12.array(fileSchema).optional()
6910
6911
  });
6911
6912
  var unifiedChatApp = new Hono27().post(
6912
6913
  "/chat",
@@ -6914,8 +6915,8 @@ var unifiedChatApp = new Hono27().post(
6914
6915
  async (c) => {
6915
6916
  const user = c.get("user");
6916
6917
  const body = c.req.valid("json");
6917
- if (!body.message && (!body.images || body.images.length === 0)) {
6918
- return c.json({ error: "message or images required" }, 400);
6918
+ if (!body.message && (!body.images || body.images.length === 0) && (!body.files || body.files.length === 0)) {
6919
+ return c.json({ error: "message, images, or files required" }, 400);
6919
6920
  }
6920
6921
  const conv = await getConversation(body.conversationId);
6921
6922
  if (!conv) return c.json({ error: "Conversation not found" }, 404);
@@ -6923,8 +6924,44 @@ var unifiedChatApp = new Hono27().post(
6923
6924
  return c.json({ error: "Conversation not found" }, 404);
6924
6925
  }
6925
6926
  const senderName = user.username;
6927
+ const fileNotifications = [];
6928
+ if (body.files && body.files.length > 0) {
6929
+ const participants = await getParticipants(body.conversationId);
6930
+ const mindParticipants = participants.filter(
6931
+ (p) => p.userType === "mind" && p.username !== senderName
6932
+ );
6933
+ const MAX_FILE_SIZE2 = 50 * 1024 * 1024;
6934
+ for (const file of body.files) {
6935
+ const pathErr = validateFilePath(file.filename);
6936
+ if (pathErr) return c.json({ error: `Invalid filename: ${pathErr}` }, 400);
6937
+ const content = Buffer.from(file.data, "base64");
6938
+ if (content.length > MAX_FILE_SIZE2) {
6939
+ return c.json(
6940
+ {
6941
+ error: `File too large: ${file.filename} (${formatFileSize(content.length)}, max ${formatFileSize(MAX_FILE_SIZE2)})`
6942
+ },
6943
+ 413
6944
+ );
6945
+ }
6946
+ for (const mind of mindParticipants) {
6947
+ const { id } = stageFile(
6948
+ mind.username,
6949
+ senderName,
6950
+ file.filename,
6951
+ content,
6952
+ file.filename
6953
+ );
6954
+ fileNotifications.push(
6955
+ `[file] ${senderName} sent ${file.filename} (${formatFileSize(content.length)}) \u2014 run: volute chat accept ${id}`
6956
+ );
6957
+ }
6958
+ }
6959
+ }
6926
6960
  const contentBlocks = [];
6927
6961
  if (body.message) contentBlocks.push({ type: "text", text: body.message });
6962
+ for (const note of fileNotifications) {
6963
+ contentBlocks.push({ type: "text", text: note });
6964
+ }
6928
6965
  if (body.images) {
6929
6966
  for (const img of body.images) {
6930
6967
  contentBlocks.push({ type: "image", media_type: img.media_type, data: img.data });
@@ -6943,7 +6980,7 @@ var unifiedChatApp = new Hono27().post(
6943
6980
  senderName,
6944
6981
  convTitle: conv.title,
6945
6982
  isDM,
6946
- channelEntryType: conv.type === "channel" ? "group" : isDM ? "dm" : "group",
6983
+ channelEntryType: isDM ? "dm" : "channel",
6947
6984
  slugExtra: { convType: conv.type, convName: conv.name }
6948
6985
  });
6949
6986
  return c.json({ ok: true, conversationId: body.conversationId });
@@ -7013,6 +7050,9 @@ var app28 = new Hono28().get("/:name/conversations", async (c) => {
7013
7050
  if (!u) return c.json({ error: `User ${id} not found` }, 400);
7014
7051
  }
7015
7052
  const participantIds = [...participantSet];
7053
+ if (participantIds.length > 2) {
7054
+ return c.json({ error: "Use channels for multi-participant conversations" }, 400);
7055
+ }
7016
7056
  if (participantIds.length === 2) {
7017
7057
  const existingId = await findDMConversation(name, participantIds);
7018
7058
  if (existingId) {
@@ -7101,6 +7141,9 @@ var app29 = new Hono29().use("*", authMiddleware).get("/", async (c) => {
7101
7141
  if (!firstMindName) {
7102
7142
  return c.json({ error: "At least one mind participant is required" }, 400);
7103
7143
  }
7144
+ if (participantIds.size > 2) {
7145
+ return c.json({ error: "Use channels for multi-participant conversations" }, 400);
7146
+ }
7104
7147
  const conv = await createConversation(firstMindName, "volute", {
7105
7148
  userId: user.id !== 0 ? user.id : void 0,
7106
7149
  title: body.title,
@@ -7124,11 +7167,11 @@ var app29 = new Hono29().use("*", authMiddleware).get("/", async (c) => {
7124
7167
  if (!stream.aborted) console.error("[chat] SSE ping error:", err);
7125
7168
  });
7126
7169
  }, 15e3);
7127
- await new Promise((resolve24) => {
7170
+ await new Promise((resolve23) => {
7128
7171
  stream.onAbort(() => {
7129
7172
  unsubscribe();
7130
7173
  clearInterval(keepAlive);
7131
- resolve24();
7174
+ resolve23();
7132
7175
  });
7133
7176
  });
7134
7177
  });
@@ -7234,8 +7277,8 @@ async function startServer({
7234
7277
  let assetsDir = "";
7235
7278
  let searchDir = dirname2(new URL(import.meta.url).pathname);
7236
7279
  for (let i = 0; i < 5; i++) {
7237
- const candidate = resolve22(searchDir, "dist", "web-assets");
7238
- if (existsSync17(candidate)) {
7280
+ const candidate = resolve21(searchDir, "dist", "web-assets");
7281
+ if (existsSync16(candidate)) {
7239
7282
  assetsDir = candidate;
7240
7283
  break;
7241
7284
  }
@@ -7245,7 +7288,7 @@ async function startServer({
7245
7288
  app_default.get("*", async (c) => {
7246
7289
  const urlPath = new URL(c.req.url).pathname;
7247
7290
  if (urlPath.startsWith("/api/")) return c.notFound();
7248
- const filePath = resolve22(assetsDir, urlPath.slice(1));
7291
+ const filePath = resolve21(assetsDir, urlPath.slice(1));
7249
7292
  if (!filePath.startsWith(assetsDir)) return c.text("Forbidden", 403);
7250
7293
  const s = await stat4(filePath).catch(() => null);
7251
7294
  if (s?.isFile()) {
@@ -7254,7 +7297,7 @@ async function startServer({
7254
7297
  const body = await readFile4(filePath);
7255
7298
  return c.body(body, 200, { "Content-Type": mime });
7256
7299
  }
7257
- const indexPath = resolve22(assetsDir, "index.html");
7300
+ const indexPath = resolve21(assetsDir, "index.html");
7258
7301
  const indexStat = await stat4(indexPath).catch(() => null);
7259
7302
  if (indexStat?.isFile()) {
7260
7303
  const body = await readFile4(indexPath, "utf-8");
@@ -7271,10 +7314,10 @@ async function startServer({
7271
7314
  createServer: createHttpsServer,
7272
7315
  serverOptions: { key: tls.key, cert: tls.cert }
7273
7316
  });
7274
- await new Promise((resolve24, reject) => {
7317
+ await new Promise((resolve23, reject) => {
7275
7318
  server2.on("listening", () => {
7276
7319
  logger_default.info("Volute UI running (https)", { hostname, port });
7277
- resolve24();
7320
+ resolve23();
7278
7321
  });
7279
7322
  server2.on("error", (err) => {
7280
7323
  reject(err);
@@ -7282,13 +7325,13 @@ async function startServer({
7282
7325
  });
7283
7326
  const internalPort = port + 1;
7284
7327
  const internalServer = serve({ fetch: app_default.fetch, port: internalPort, hostname: "127.0.0.1" });
7285
- await new Promise((resolve24, reject) => {
7328
+ await new Promise((resolve23, reject) => {
7286
7329
  internalServer.on("listening", () => {
7287
7330
  logger_default.info("Volute API running (http, internal)", {
7288
7331
  hostname: "127.0.0.1",
7289
7332
  port: internalPort
7290
7333
  });
7291
- resolve24();
7334
+ resolve23();
7292
7335
  });
7293
7336
  internalServer.on("error", (err) => {
7294
7337
  reject(err);
@@ -7297,10 +7340,10 @@ async function startServer({
7297
7340
  return { server: server2, internalPort };
7298
7341
  }
7299
7342
  const server = serve({ fetch: app_default.fetch, port, hostname });
7300
- await new Promise((resolve24, reject) => {
7343
+ await new Promise((resolve23, reject) => {
7301
7344
  server.on("listening", () => {
7302
7345
  logger_default.info("Volute API running (http)", { hostname, port });
7303
- resolve24();
7346
+ resolve23();
7304
7347
  });
7305
7348
  server.on("error", (err) => {
7306
7349
  reject(err);
@@ -7311,7 +7354,7 @@ async function startServer({
7311
7354
 
7312
7355
  // src/daemon.ts
7313
7356
  if (!process.env.VOLUTE_HOME) {
7314
- process.env.VOLUTE_HOME = resolve23(homedir2(), ".volute");
7357
+ process.env.VOLUTE_HOME = resolve22(homedir2(), ".volute");
7315
7358
  }
7316
7359
  if (process.env.VOLUTE_TIMEZONE && !process.env.TZ) {
7317
7360
  process.env.TZ = process.env.VOLUTE_TIMEZONE;
@@ -7322,7 +7365,7 @@ async function startDaemon(opts) {
7322
7365
  const home = voluteHome();
7323
7366
  const systemDir = voluteSystemDir();
7324
7367
  if (!opts.foreground) {
7325
- const rotatingLog = new RotatingLog(resolve23(systemDir, "daemon.log"));
7368
+ const rotatingLog = new RotatingLog(resolve22(systemDir, "daemon.log"));
7326
7369
  logger_default.setOutput((line) => rotatingLog.write(`${line}
7327
7370
  `));
7328
7371
  const write = (...args) => rotatingLog.write(`${format(...args)}
@@ -7332,9 +7375,9 @@ async function startDaemon(opts) {
7332
7375
  console.warn = write;
7333
7376
  console.info = write;
7334
7377
  }
7335
- const DAEMON_PID_PATH = resolve23(systemDir, "daemon.pid");
7336
- const DAEMON_JSON_PATH = resolve23(systemDir, "daemon.json");
7337
- mkdirSync12(home, { recursive: true });
7378
+ const DAEMON_PID_PATH = resolve22(systemDir, "daemon.pid");
7379
+ const DAEMON_JSON_PATH = resolve22(systemDir, "daemon.json");
7380
+ mkdirSync11(home, { recursive: true });
7338
7381
  ensureSystemDir();
7339
7382
  migrateToSystemDir();
7340
7383
  migrateAgentsToMinds();
@@ -7346,7 +7389,13 @@ async function startDaemon(opts) {
7346
7389
  await (await import("./db-IC4J52XQ.js")).getDb();
7347
7390
  const { migrateRegistryToDb } = await import("./migrate-registry-to-db-XC7T5B7P.js");
7348
7391
  migrateRegistryToDb();
7349
- const { initSandbox } = await import("./sandbox-TGBX22DS.js");
7392
+ try {
7393
+ const { migrateGroupDMsToChannels } = await import("./conversations-P5BL7RMX.js");
7394
+ await migrateGroupDMsToChannels();
7395
+ } catch (err) {
7396
+ logger_default.error("failed to migrate group DMs to channels", logger_default.errorData(err));
7397
+ }
7398
+ const { initSandbox } = await import("./sandbox-5BW5HPXM.js");
7350
7399
  await initSandbox();
7351
7400
  try {
7352
7401
  await syncBuiltinSkills();
@@ -7358,7 +7407,9 @@ async function startDaemon(opts) {
7358
7407
  } catch (err) {
7359
7408
  logger_default.warn("failed to ensure #system channel", logger_default.errorData(err));
7360
7409
  }
7361
- const token = process.env.VOLUTE_DAEMON_TOKEN || randomBytes2(32).toString("hex");
7410
+ const { startSystemWatcher } = await import("./pages-watcher-P7QECRE2.js");
7411
+ startSystemWatcher();
7412
+ const token = process.env.VOLUTE_DAEMON_TOKEN || randomBytes(32).toString("hex");
7362
7413
  let tls;
7363
7414
  if (opts.tailscale) {
7364
7415
  const { getTailscaleTls } = await import("./tailscale-NY5MUMY3.js");
@@ -7382,11 +7433,11 @@ async function startDaemon(opts) {
7382
7433
  process.env.VOLUTE_DAEMON_TOKEN = token;
7383
7434
  process.env.VOLUTE_DAEMON_PORT = String(daemonPort);
7384
7435
  process.env.VOLUTE_DAEMON_HOSTNAME = hostname;
7385
- writeFileSync11(DAEMON_PID_PATH, myPid, { mode: 420 });
7436
+ writeFileSync10(DAEMON_PID_PATH, myPid, { mode: 420 });
7386
7437
  const daemonConfig = { port, hostname, token };
7387
7438
  if (internalPort) daemonConfig.internalPort = internalPort;
7388
7439
  if (tls) daemonConfig.tls = true;
7389
- writeFileSync11(DAEMON_JSON_PATH, `${JSON.stringify(daemonConfig, null, 2)}
7440
+ writeFileSync10(DAEMON_JSON_PATH, `${JSON.stringify(daemonConfig, null, 2)}
7390
7441
  `, { mode: 420 });
7391
7442
  const delivery = initDeliveryManager();
7392
7443
  const manager = initMindManager();
@@ -7442,7 +7493,7 @@ async function startDaemon(opts) {
7442
7493
  bridgeManager.startBridges(daemonPort).catch((err) => {
7443
7494
  logger_default.warn("failed to start bridges", logger_default.errorData(err));
7444
7495
  });
7445
- import("./cloud-sync-T7M3ESC3.js").then(
7496
+ import("./cloud-sync-KILFGV5Q.js").then(
7446
7497
  ({ consumeQueuedMessages }) => consumeQueuedMessages().catch((err) => {
7447
7498
  logger_default.warn("failed to consume queued cloud messages", logger_default.errorData(err));
7448
7499
  })
@@ -7450,7 +7501,7 @@ async function startDaemon(opts) {
7450
7501
  logger_default.warn("failed to load cloud-sync module", logger_default.errorData(err));
7451
7502
  });
7452
7503
  try {
7453
- const { backfillTemplateHashes, notifyVersionUpdate } = await import("./version-notify-5Z4MNR6M.js");
7504
+ const { backfillTemplateHashes, notifyVersionUpdate } = await import("./version-notify-WDHRO3XD.js");
7454
7505
  backfillTemplateHashes();
7455
7506
  notifyVersionUpdate().catch((err) => {
7456
7507
  logger_default.warn("failed to send version update notifications", logger_default.errorData(err));
@@ -7464,19 +7515,22 @@ async function startDaemon(opts) {
7464
7515
  cleanExpiredSessions().catch((err) => {
7465
7516
  logger_default.warn("failed to clean expired sessions", logger_default.errorData(err));
7466
7517
  });
7518
+ cleanExpiredLogs().catch((err) => {
7519
+ logger_default.warn("failed to clean expired logs", logger_default.errorData(err));
7520
+ });
7467
7521
  migrateMindRoles().catch((err) => {
7468
7522
  logger_default.warn("failed to migrate mind roles", logger_default.errorData(err));
7469
7523
  });
7470
7524
  logger_default.info(`running on ${hostname}:${port}, pid ${myPid}`);
7471
7525
  function cleanup() {
7472
7526
  try {
7473
- if (readFileSync14(DAEMON_PID_PATH, "utf-8").trim() === myPid) {
7527
+ if (readFileSync13(DAEMON_PID_PATH, "utf-8").trim() === myPid) {
7474
7528
  unlinkSync2(DAEMON_PID_PATH);
7475
7529
  }
7476
7530
  } catch {
7477
7531
  }
7478
7532
  try {
7479
- const data = JSON.parse(readFileSync14(DAEMON_JSON_PATH, "utf-8"));
7533
+ const data = JSON.parse(readFileSync13(DAEMON_JSON_PATH, "utf-8"));
7480
7534
  if (data.token === token) {
7481
7535
  unlinkSync2(DAEMON_JSON_PATH);
7482
7536
  }