volute 0.23.0 → 0.24.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 (76) hide show
  1. package/README.md +5 -5
  2. package/dist/{activity-events-3WHHCOBB.js → activity-events-4O37J7PD.js} +2 -2
  3. package/dist/api.d.ts +306 -15
  4. package/dist/{channel-BOOMFULW.js → channel-HZOSHGNF.js} +1 -1
  5. package/dist/{chunk-QIXPN3OO.js → chunk-2767L2RZ.js} +5 -5
  6. package/dist/{chunk-SGPEZ32F.js → chunk-33XAVCS4.js} +16 -0
  7. package/dist/{chunk-VT5QODNE.js → chunk-3AIBT4TW.js} +4 -3
  8. package/dist/{chunk-RK627D57.js → chunk-4TJ72QQ3.js} +2 -2
  9. package/dist/{chunk-A4S7H6G6.js → chunk-BFK6SOEJ.js} +1 -1
  10. package/dist/{chunk-HGCDWKSP.js → chunk-E7GOKNOT.js} +1 -1
  11. package/dist/{chunk-M5CNKH4J.js → chunk-NOBRGACV.js} +7 -7
  12. package/dist/{chunk-ISWZ6QUK.js → chunk-OOW675I3.js} +778 -108
  13. package/dist/{chunk-TFS25FIM.js → chunk-P3W36ZGD.js} +1 -1
  14. package/dist/{chunk-JG4CCJOA.js → chunk-TQDITGES.js} +33 -15
  15. package/dist/{chunk-KFI7TQJ6.js → chunk-TRQEV3CD.js} +9 -5
  16. package/dist/cli.js +18 -18
  17. package/dist/{cloud-sync-PI47U2LT.js → cloud-sync-DIU3OCPV.js} +6 -8
  18. package/dist/{connector-PYT5UOTZ.js → connector-M6XFI6GM.js} +1 -1
  19. package/dist/{create-WIDA3M4C.js → create-VDQJER52.js} +1 -1
  20. package/dist/{daemon-client-ZHCDL4RS.js → daemon-client-JOVQZ52X.js} +1 -1
  21. package/dist/{daemon-restart-RMGOOGPE.js → daemon-restart-YMPEATQH.js} +5 -5
  22. package/dist/daemon.js +665 -813
  23. package/dist/{delete-LOIANQGD.js → delete-2MRR4JX5.js} +1 -1
  24. package/dist/{down-WSUASL5E.js → down-674SX2IZ.js} +2 -2
  25. package/dist/{env-4PHIHTF4.js → env-2FPOZK37.js} +1 -1
  26. package/dist/{export-XD6PJBQP.js → export-IKFAPRAO.js} +1 -1
  27. package/dist/{file-X4L5TTOL.js → file-KT3UIQM3.js} +1 -1
  28. package/dist/{history-HTEKRNID.js → history-46WZN5CN.js} +1 -1
  29. package/dist/{import-EAXTHHXL.js → import-FRDPQPJ2.js} +1 -1
  30. package/dist/{log-SRO5Q6AD.js → log-6SGSSR3D.js} +1 -1
  31. package/dist/{logs-HNTNNBDW.js → logs-HRBONI5I.js} +1 -1
  32. package/dist/{merge-B6SYTGI7.js → merge-KSFJKX6T.js} +1 -1
  33. package/dist/{message-delivery-FHV4NO2F.js → message-delivery-S7BCNV6Y.js} +5 -5
  34. package/dist/{mind-BTXR5B3C.js → mind-KPLCRKQA.js} +17 -17
  35. package/dist/{mind-activity-tracker-PGC3DBJ7.js → mind-activity-tracker-NMDDEV3K.js} +3 -3
  36. package/dist/{mind-manager-KMY4GA2J.js → mind-manager-ZNRIYEK3.js} +2 -2
  37. package/dist/{mind-sleep-FWRBIFBS.js → mind-sleep-GHPTSAYN.js} +1 -1
  38. package/dist/{mind-wake-LJK2YU5X.js → mind-wake-BJDJFMDF.js} +1 -1
  39. package/dist/{package-CUBJ4PKS.js → package-S5YF25XV.js} +1 -1
  40. package/dist/{pull-GRQAXM2E.js → pull-D32SPFVU.js} +1 -1
  41. package/dist/{restart-CIDAKGG2.js → restart-5BMNV7KU.js} +1 -1
  42. package/dist/{schedule-NLR3LZLY.js → schedule-YEFDLVMJ.js} +1 -1
  43. package/dist/{seed-3H2MRREW.js → seed-6FEKB3YC.js} +1 -1
  44. package/dist/{send-RP2TA7SG.js → send-IISDYFCL.js} +1 -1
  45. package/dist/{service-7BFXDI6J.js → service-FASYWLTC.js} +3 -3
  46. package/dist/{setup-SSIIXQMI.js → setup-BMLM2UTK.js} +1 -1
  47. package/dist/{shared-2OGT3NSL.js → shared-LWMNTTZN.js} +4 -4
  48. package/dist/{skill-Q2Y6PQ3L.js → skill-BQOFACEI.js} +1 -1
  49. package/dist/skills/volute-mind/SKILL.md +71 -1
  50. package/dist/{sleep-manager-2TMQ65E4.js → sleep-manager-XXSWQQLE.js} +5 -5
  51. package/dist/{sprout-UKCYBGHK.js → sprout-CGSW4CF5.js} +3 -3
  52. package/dist/{start-JR6CUUWF.js → start-C7XITZ5O.js} +1 -1
  53. package/dist/{status-5XDGYHKP.js → status-LYS4NUOZ.js} +1 -1
  54. package/dist/{status-H2MKDN6L.js → status-SIRPLEZC.js} +4 -3
  55. package/dist/{stop-VKPGK25U.js → stop-CVKBSLXY.js} +1 -1
  56. package/dist/tailscale-AJ4VL5XK.js +49 -0
  57. package/dist/{up-Z5JRG2M2.js → up-OMHACRJL.js} +2 -2
  58. package/dist/{update-ELC6MEUT.js → update-7XCZMYBT.js} +7 -7
  59. package/dist/{upgrade-GXW2EQY3.js → upgrade-7RUIXGOO.js} +1 -1
  60. package/dist/{variant-A4I7PHXS.js → variant-UGREB4G5.js} +4 -4
  61. package/dist/{version-notify-LKABEJSA.js → version-notify-SZ75QRGO.js} +5 -5
  62. package/dist/web-assets/assets/index-Bx9WDoaQ.js +69 -0
  63. package/dist/web-assets/assets/index-Clz8OhmJ.css +1 -0
  64. package/dist/web-assets/index.html +2 -2
  65. package/drizzle/0013_user_profiles.sql +3 -0
  66. package/drizzle/0014_conversation_reads.sql +7 -0
  67. package/drizzle/meta/0013_snapshot.json +7 -0
  68. package/drizzle/meta/_journal.json +14 -0
  69. package/package.json +1 -1
  70. package/templates/_base/src/lib/format-prefix.ts +18 -2
  71. package/templates/_base/src/lib/routing.ts +2 -1
  72. package/templates/_base/src/lib/types.ts +8 -0
  73. package/dist/chunk-G5KRTU2F.js +0 -76
  74. package/dist/web-assets/assets/index-CZ26vsyY.js +0 -69
  75. package/dist/web-assets/assets/index-DyyAvJwW.css +0 -1
  76. /package/dist/{pages-YSTRWJR4.js → pages-TWR6U7DS.js} +0 -0
package/dist/daemon.js CHANGED
@@ -8,10 +8,6 @@ import {
8
8
  sharedPull,
9
9
  sharedStatus
10
10
  } from "./chunk-PHHKNGA3.js";
11
- import {
12
- fireWebhook,
13
- initWebhook
14
- } from "./chunk-G5KRTU2F.js";
15
11
  import {
16
12
  applyInitFiles,
17
13
  composeTemplate,
@@ -21,32 +17,68 @@ import {
21
17
  listFiles
22
18
  } from "./chunk-AKPFNL7L.js";
23
19
  import {
20
+ addMessage,
21
+ approveUser,
22
+ changePassword,
23
+ countAdmins,
24
+ createChannel,
25
+ createConversation,
26
+ createUser,
27
+ deleteConversationForUser,
28
+ deleteMindUser as deleteMindUser2,
29
+ deleteUser,
24
30
  deliverMessage,
25
31
  extractTextContent,
32
+ findDMConversation,
33
+ fireWebhook,
26
34
  getCachedRecentPages,
27
35
  getCachedSites,
36
+ getChannelByName,
28
37
  getConnectorManager,
38
+ getConversation,
29
39
  getDeliveryManager,
30
40
  getMailPoller,
41
+ getMessages,
42
+ getMessagesPaginated,
43
+ getOrCreateMindUser,
44
+ getParticipants,
31
45
  getScheduler,
32
46
  getTokenBudget,
33
47
  getTypingMap,
48
+ getUnreadCounts,
49
+ getUser,
50
+ getUserByUsername,
34
51
  initConnectorManager,
35
52
  initDeliveryManager,
36
53
  initMailPoller,
37
54
  initScheduler,
38
55
  initSleepManager,
39
56
  initTokenBudget,
57
+ initWebhook,
58
+ isParticipant,
59
+ isParticipantOrOwner,
60
+ joinChannel,
61
+ leaveChannel,
62
+ listChannels,
63
+ listConversationsForUser,
64
+ listConversationsWithParticipants,
65
+ listPendingUsers,
66
+ listUsers,
67
+ listUsersByType,
68
+ markConversationRead,
40
69
  publish,
41
70
  publish2,
42
71
  publishTypingForChannels,
43
72
  recordInbound,
73
+ setUserRole,
44
74
  startMindFull,
45
75
  stopAllWatchers,
46
76
  stopMindFull,
47
77
  subscribe as subscribe2,
48
- subscribe2 as subscribe3
49
- } from "./chunk-ISWZ6QUK.js";
78
+ subscribe2 as subscribe3,
79
+ updateUserProfile,
80
+ verifyUser
81
+ } from "./chunk-OOW675I3.js";
50
82
  import {
51
83
  readSystemsConfig
52
84
  } from "./chunk-HFCBO2GL.js";
@@ -54,11 +86,11 @@ import {
54
86
  getActiveMinds,
55
87
  onMindEvent,
56
88
  stopAll
57
- } from "./chunk-HGCDWKSP.js";
89
+ } from "./chunk-E7GOKNOT.js";
58
90
  import {
59
91
  broadcast,
60
92
  subscribe
61
- } from "./chunk-A4S7H6G6.js";
93
+ } from "./chunk-BFK6SOEJ.js";
62
94
  import {
63
95
  PROMPT_DEFAULTS,
64
96
  PROMPT_KEYS,
@@ -69,13 +101,13 @@ import {
69
101
  getPromptIfCustom,
70
102
  initMindManager,
71
103
  substitute
72
- } from "./chunk-M5CNKH4J.js";
104
+ } from "./chunk-NOBRGACV.js";
73
105
  import {
74
106
  findOpenClawSession,
75
107
  importOpenClawConnectors,
76
108
  importPiSession,
77
109
  parseNameFromIdentity
78
- } from "./chunk-RK627D57.js";
110
+ } from "./chunk-4TJ72QQ3.js";
79
111
  import {
80
112
  readVoluteConfig,
81
113
  writeVoluteConfig
@@ -105,18 +137,15 @@ import {
105
137
  syncBuiltinSkills,
106
138
  uninstallSkill,
107
139
  updateSkill
108
- } from "./chunk-TFS25FIM.js";
140
+ } from "./chunk-P3W36ZGD.js";
109
141
  import {
110
142
  activity,
111
- conversationParticipants,
112
143
  conversations,
113
144
  getDb,
114
- messages,
115
145
  mindHistory,
116
146
  sessions,
117
- systemPrompts,
118
- users
119
- } from "./chunk-SGPEZ32F.js";
147
+ systemPrompts
148
+ } from "./chunk-33XAVCS4.js";
120
149
  import {
121
150
  logBuffer,
122
151
  logger_default
@@ -179,9 +208,9 @@ import {
179
208
 
180
209
  // src/daemon.ts
181
210
  import { randomBytes as randomBytes2 } from "crypto";
182
- import { mkdirSync as mkdirSync8, readFileSync as readFileSync10, unlinkSync, writeFileSync as writeFileSync8 } from "fs";
211
+ import { mkdirSync as mkdirSync9, readFileSync as readFileSync11, unlinkSync, writeFileSync as writeFileSync9 } from "fs";
183
212
  import { homedir as homedir2 } from "os";
184
- import { resolve as resolve17 } from "path";
213
+ import { resolve as resolve18 } from "path";
185
214
  import { format } from "util";
186
215
 
187
216
  // src/lib/migrate-agents-to-minds.ts
@@ -359,145 +388,9 @@ function migrateMindState(name) {
359
388
 
360
389
  // src/web/middleware/auth.ts
361
390
  import { timingSafeEqual } from "crypto";
362
- import { eq as eq2, lt } from "drizzle-orm";
391
+ import { eq, lt } from "drizzle-orm";
363
392
  import { getCookie } from "hono/cookie";
364
393
  import { createMiddleware } from "hono/factory";
365
-
366
- // src/lib/auth.ts
367
- import { compareSync, hashSync } from "bcryptjs";
368
- import { and, count, eq } from "drizzle-orm";
369
- async function createUser(username, password) {
370
- const db = await getDb();
371
- const hash = hashSync(password, 10);
372
- const [{ value }] = await db.select({ value: count() }).from(users).where(eq(users.user_type, "brain"));
373
- const role = value === 0 ? "admin" : "pending";
374
- const [result] = await db.insert(users).values({ username, password_hash: hash, role }).returning({
375
- id: users.id,
376
- username: users.username,
377
- role: users.role,
378
- user_type: users.user_type,
379
- created_at: users.created_at
380
- });
381
- return result;
382
- }
383
- async function verifyUser(username, password) {
384
- const db = await getDb();
385
- const row = await db.select().from(users).where(eq(users.username, username)).get();
386
- if (!row) return null;
387
- if (row.user_type === "mind") return null;
388
- if (!compareSync(password, row.password_hash)) return null;
389
- const { password_hash: _, ...user } = row;
390
- return user;
391
- }
392
- async function getUser(id) {
393
- const db = await getDb();
394
- const row = await db.select({
395
- id: users.id,
396
- username: users.username,
397
- role: users.role,
398
- user_type: users.user_type,
399
- created_at: users.created_at
400
- }).from(users).where(eq(users.id, id)).get();
401
- return row ?? null;
402
- }
403
- async function getUserByUsername(username) {
404
- const db = await getDb();
405
- const row = await db.select({
406
- id: users.id,
407
- username: users.username,
408
- role: users.role,
409
- user_type: users.user_type,
410
- created_at: users.created_at
411
- }).from(users).where(eq(users.username, username)).get();
412
- return row ?? null;
413
- }
414
- async function listUsers() {
415
- const db = await getDb();
416
- return db.select({
417
- id: users.id,
418
- username: users.username,
419
- role: users.role,
420
- user_type: users.user_type,
421
- created_at: users.created_at
422
- }).from(users).orderBy(users.created_at).all();
423
- }
424
- async function listPendingUsers() {
425
- const db = await getDb();
426
- return db.select({
427
- id: users.id,
428
- username: users.username,
429
- role: users.role,
430
- user_type: users.user_type,
431
- created_at: users.created_at
432
- }).from(users).where(eq(users.role, "pending")).orderBy(users.created_at).all();
433
- }
434
- async function listUsersByType(userType) {
435
- const db = await getDb();
436
- return db.select({
437
- id: users.id,
438
- username: users.username,
439
- role: users.role,
440
- user_type: users.user_type,
441
- created_at: users.created_at
442
- }).from(users).where(eq(users.user_type, userType)).orderBy(users.created_at).all();
443
- }
444
- async function getOrCreateMindUser(mindName) {
445
- const db = await getDb();
446
- const existing = await db.select({
447
- id: users.id,
448
- username: users.username,
449
- role: users.role,
450
- user_type: users.user_type,
451
- created_at: users.created_at
452
- }).from(users).where(and(eq(users.username, mindName), eq(users.user_type, "mind"))).get();
453
- if (existing) return existing;
454
- try {
455
- const [result] = await db.insert(users).values({
456
- username: mindName,
457
- password_hash: "!mind",
458
- role: "mind",
459
- user_type: "mind"
460
- }).returning({
461
- id: users.id,
462
- username: users.username,
463
- role: users.role,
464
- user_type: users.user_type,
465
- created_at: users.created_at
466
- });
467
- return result;
468
- } catch (err) {
469
- if (err instanceof Error && err.message.includes("UNIQUE constraint")) {
470
- const retried = await db.select({
471
- id: users.id,
472
- username: users.username,
473
- role: users.role,
474
- user_type: users.user_type,
475
- created_at: users.created_at
476
- }).from(users).where(and(eq(users.username, mindName), eq(users.user_type, "mind"))).get();
477
- if (retried) return retried;
478
- }
479
- throw err;
480
- }
481
- }
482
- async function deleteMindUser2(mindName) {
483
- const db = await getDb();
484
- await db.delete(users).where(and(eq(users.username, mindName), eq(users.user_type, "mind")));
485
- }
486
- async function changePassword(userId, currentPassword, newPassword) {
487
- const db = await getDb();
488
- const row = await db.select().from(users).where(eq(users.id, userId)).get();
489
- if (!row) return false;
490
- if (!compareSync(currentPassword, row.password_hash)) return false;
491
- const hash = hashSync(newPassword, 10);
492
- await db.update(users).set({ password_hash: hash }).where(eq(users.id, userId));
493
- return true;
494
- }
495
- async function approveUser(id) {
496
- const db = await getDb();
497
- await db.update(users).set({ role: "user" }).where(and(eq(users.id, id), eq(users.role, "pending")));
498
- }
499
-
500
- // src/web/middleware/auth.ts
501
394
  function isValidDaemonToken(token) {
502
395
  const expected = process.env.VOLUTE_DAEMON_TOKEN;
503
396
  if (!expected || token.length !== expected.length) return false;
@@ -506,6 +399,9 @@ function isValidDaemonToken(token) {
506
399
  var SESSION_MAX_AGE = 864e5;
507
400
  var SESSION_CACHE_TTL = 5 * 60 * 1e3;
508
401
  var sessionCache = /* @__PURE__ */ new Map();
402
+ function invalidateSessionCache(sessionId) {
403
+ sessionCache.delete(sessionId);
404
+ }
509
405
  async function createSession(userId) {
510
406
  const db = await getDb();
511
407
  const sessionId = crypto.randomUUID();
@@ -515,14 +411,14 @@ async function createSession(userId) {
515
411
  async function deleteSession(sessionId) {
516
412
  sessionCache.delete(sessionId);
517
413
  const db = await getDb();
518
- await db.delete(sessions).where(eq2(sessions.id, sessionId));
414
+ await db.delete(sessions).where(eq(sessions.id, sessionId));
519
415
  }
520
416
  async function getSessionUserId(sessionId) {
521
417
  const db = await getDb();
522
- const row = await db.select().from(sessions).where(eq2(sessions.id, sessionId)).get();
418
+ const row = await db.select().from(sessions).where(eq(sessions.id, sessionId)).get();
523
419
  if (!row) return void 0;
524
420
  if (Date.now() - row.createdAt > SESSION_MAX_AGE) {
525
- await db.delete(sessions).where(eq2(sessions.id, sessionId));
421
+ await db.delete(sessions).where(eq(sessions.id, sessionId));
526
422
  return void 0;
527
423
  }
528
424
  return row.userId;
@@ -544,7 +440,15 @@ var authMiddleware = createMiddleware(async (c, next) => {
544
440
  if (authHeader?.startsWith("Bearer ")) {
545
441
  const token = authHeader.slice(7);
546
442
  if (token && isValidDaemonToken(token)) {
547
- c.set("user", { id: 0, username: "cli", role: "admin", user_type: "brain" });
443
+ c.set("user", {
444
+ id: 0,
445
+ username: "cli",
446
+ role: "admin",
447
+ user_type: "brain",
448
+ display_name: null,
449
+ description: null,
450
+ avatar: null
451
+ });
548
452
  await next();
549
453
  return;
550
454
  }
@@ -575,9 +479,10 @@ var authMiddleware = createMiddleware(async (c, next) => {
575
479
  });
576
480
 
577
481
  // src/web/server.ts
578
- import { existsSync as existsSync12 } from "fs";
482
+ import { existsSync as existsSync13 } from "fs";
579
483
  import { readFile as readFile3, stat as stat3 } from "fs/promises";
580
- import { dirname, extname as extname3, resolve as resolve16 } from "path";
484
+ import { createServer as createHttpsServer } from "https";
485
+ import { dirname, extname as extname4, resolve as resolve17 } from "path";
581
486
  import { serve } from "@hono/node-server";
582
487
 
583
488
  // src/web/app.ts
@@ -587,294 +492,9 @@ import { csrf } from "hono/csrf";
587
492
  import { HTTPException } from "hono/http-exception";
588
493
 
589
494
  // src/web/api/activity.ts
590
- import { desc as desc2 } from "drizzle-orm";
495
+ import { desc } from "drizzle-orm";
591
496
  import { Hono } from "hono";
592
497
  import { streamSSE } from "hono/streaming";
593
-
594
- // src/lib/events/conversations.ts
595
- import { randomUUID } from "crypto";
596
- import { and as and2, desc, eq as eq3, inArray, isNull, lt as lt2, sql } from "drizzle-orm";
597
- async function createConversation(mindName, channel, opts) {
598
- const db = await getDb();
599
- const id = randomUUID();
600
- const type = opts?.type ?? "dm";
601
- const name = opts?.name ?? null;
602
- await db.transaction(async (tx) => {
603
- await tx.insert(conversations).values({
604
- id,
605
- mind_name: mindName,
606
- channel,
607
- type,
608
- name,
609
- user_id: opts?.userId ?? null,
610
- title: opts?.title ?? null
611
- });
612
- if (opts?.participantIds && opts.participantIds.length > 0) {
613
- await tx.insert(conversationParticipants).values(
614
- opts.participantIds.map((uid, i) => ({
615
- conversation_id: id,
616
- user_id: uid,
617
- role: i === 0 ? "owner" : "member"
618
- }))
619
- );
620
- }
621
- });
622
- fireWebhook({
623
- event: "conversation_created",
624
- mind: mindName ?? "",
625
- data: { id, mindName, channel, type, name, title: opts?.title ?? null }
626
- });
627
- return {
628
- id,
629
- mind_name: mindName,
630
- channel,
631
- type,
632
- name,
633
- user_id: opts?.userId ?? null,
634
- title: opts?.title ?? null,
635
- created_at: (/* @__PURE__ */ new Date()).toISOString(),
636
- updated_at: (/* @__PURE__ */ new Date()).toISOString()
637
- };
638
- }
639
- async function getConversation(id) {
640
- const db = await getDb();
641
- const row = await db.select().from(conversations).where(eq3(conversations.id, id)).get();
642
- return row ?? null;
643
- }
644
- async function addParticipant(conversationId, userId, role = "member") {
645
- const db = await getDb();
646
- await db.insert(conversationParticipants).values({
647
- conversation_id: conversationId,
648
- user_id: userId,
649
- role
650
- });
651
- }
652
- async function removeParticipant(conversationId, userId) {
653
- const db = await getDb();
654
- await db.delete(conversationParticipants).where(
655
- and2(
656
- eq3(conversationParticipants.conversation_id, conversationId),
657
- eq3(conversationParticipants.user_id, userId)
658
- )
659
- );
660
- }
661
- async function getParticipants(conversationId) {
662
- const db = await getDb();
663
- const rows = await db.select({
664
- userId: conversationParticipants.user_id,
665
- username: users.username,
666
- userType: users.user_type,
667
- role: conversationParticipants.role
668
- }).from(conversationParticipants).innerJoin(users, eq3(conversationParticipants.user_id, users.id)).where(eq3(conversationParticipants.conversation_id, conversationId)).all();
669
- return rows;
670
- }
671
- async function isParticipant(conversationId, userId) {
672
- const db = await getDb();
673
- const row = await db.select({ user_id: conversationParticipants.user_id }).from(conversationParticipants).where(
674
- and2(
675
- eq3(conversationParticipants.conversation_id, conversationId),
676
- eq3(conversationParticipants.user_id, userId)
677
- )
678
- ).get();
679
- return row != null;
680
- }
681
- async function listConversationsForUser(userId) {
682
- const db = await getDb();
683
- const participantRows = await db.select({ conversation_id: conversationParticipants.conversation_id }).from(conversationParticipants).where(eq3(conversationParticipants.user_id, userId)).all();
684
- if (participantRows.length === 0) return [];
685
- const convIds = participantRows.map((r) => r.conversation_id);
686
- return await db.select().from(conversations).where(inArray(conversations.id, convIds)).orderBy(desc(conversations.updated_at)).all();
687
- }
688
- async function isParticipantOrOwner(conversationId, userId) {
689
- if (await isParticipant(conversationId, userId)) return true;
690
- const db = await getDb();
691
- const row = await db.select().from(conversations).where(and2(eq3(conversations.id, conversationId), eq3(conversations.user_id, userId))).get();
692
- return row != null;
693
- }
694
- async function deleteConversationForUser(id, userId) {
695
- if (!await isParticipantOrOwner(id, userId)) return false;
696
- await deleteConversation(id);
697
- return true;
698
- }
699
- async function addMessage(conversationId, role, senderName, content) {
700
- const db = await getDb();
701
- const serialized = JSON.stringify(content);
702
- const [result] = await db.insert(messages).values({ conversation_id: conversationId, role, sender_name: senderName, content: serialized }).returning({ id: messages.id, created_at: messages.created_at });
703
- await db.update(conversations).set({ updated_at: sql`datetime('now')` }).where(eq3(conversations.id, conversationId));
704
- if (role === "user") {
705
- const firstText = content.find((b) => b.type === "text");
706
- const title = firstText ? firstText.text.slice(0, 80) : "";
707
- if (title) {
708
- await db.update(conversations).set({ title }).where(and2(eq3(conversations.id, conversationId), isNull(conversations.title)));
709
- }
710
- }
711
- const msg = {
712
- id: result.id,
713
- conversation_id: conversationId,
714
- role,
715
- sender_name: senderName,
716
- content,
717
- created_at: result.created_at
718
- };
719
- publish2(conversationId, {
720
- type: "message",
721
- id: msg.id,
722
- role: msg.role,
723
- senderName: msg.sender_name,
724
- content: msg.content,
725
- createdAt: msg.created_at
726
- });
727
- const conv = await db.select({ mind_name: conversations.mind_name }).from(conversations).where(eq3(conversations.id, conversationId)).get();
728
- fireWebhook({
729
- event: "message_created",
730
- mind: conv?.mind_name ?? "",
731
- data: {
732
- conversationId,
733
- messageId: result.id,
734
- role,
735
- senderName,
736
- content: content.filter((b) => b.type !== "image"),
737
- createdAt: result.created_at
738
- }
739
- });
740
- return msg;
741
- }
742
- async function getMessages(conversationId) {
743
- const db = await getDb();
744
- const rows = await db.select().from(messages).where(eq3(messages.conversation_id, conversationId)).orderBy(messages.created_at).all();
745
- return rows.map(parseMessageRow);
746
- }
747
- async function getMessagesPaginated(conversationId, opts) {
748
- const db = await getDb();
749
- const limit = Math.min(Math.max(opts?.limit ?? 50, 1), 100);
750
- const conditions = [eq3(messages.conversation_id, conversationId)];
751
- if (opts?.before != null) {
752
- conditions.push(lt2(messages.id, opts.before));
753
- }
754
- const rows = await db.select().from(messages).where(and2(...conditions)).orderBy(desc(messages.id)).limit(limit + 1).all();
755
- const hasMore = rows.length > limit;
756
- const page = rows.slice(0, limit).reverse();
757
- return {
758
- messages: page.map(parseMessageRow),
759
- hasMore
760
- };
761
- }
762
- function parseMessageRow(row) {
763
- let content;
764
- try {
765
- const parsed = JSON.parse(row.content);
766
- content = Array.isArray(parsed) ? parsed : [{ type: "text", text: row.content }];
767
- } catch {
768
- content = [{ type: "text", text: row.content }];
769
- }
770
- return { ...row, role: row.role, content };
771
- }
772
- async function listConversationsWithParticipants(userId) {
773
- const convs = await listConversationsForUser(userId);
774
- if (convs.length === 0) return [];
775
- const db = await getDb();
776
- const convIds = convs.map((c) => c.id);
777
- const rows = await db.select({
778
- conversationId: conversationParticipants.conversation_id,
779
- userId: users.id,
780
- username: users.username,
781
- userType: users.user_type,
782
- role: conversationParticipants.role
783
- }).from(conversationParticipants).innerJoin(users, eq3(conversationParticipants.user_id, users.id)).where(inArray(conversationParticipants.conversation_id, convIds));
784
- const byConv = /* @__PURE__ */ new Map();
785
- for (const r of rows) {
786
- let arr = byConv.get(r.conversationId);
787
- if (!arr) {
788
- arr = [];
789
- byConv.set(r.conversationId, arr);
790
- }
791
- arr.push({
792
- userId: r.userId,
793
- username: r.username,
794
- userType: r.userType,
795
- role: r.role
796
- });
797
- }
798
- const lastMsgIds = await db.select({
799
- conversationId: messages.conversation_id,
800
- maxId: sql`MAX(${messages.id})`
801
- }).from(messages).where(inArray(messages.conversation_id, convIds)).groupBy(messages.conversation_id);
802
- const byLastMsg = /* @__PURE__ */ new Map();
803
- if (lastMsgIds.length > 0) {
804
- const msgRows = await db.select().from(messages).where(
805
- inArray(
806
- messages.id,
807
- lastMsgIds.map((r) => r.maxId)
808
- )
809
- );
810
- for (const m of msgRows) {
811
- let text = "";
812
- try {
813
- const parsed = JSON.parse(m.content);
814
- const blocks = Array.isArray(parsed) ? parsed : [];
815
- const textBlock = blocks.find((b) => b.type === "text");
816
- if (textBlock && "text" in textBlock) text = textBlock.text;
817
- } catch {
818
- text = m.content;
819
- }
820
- byLastMsg.set(m.conversation_id, {
821
- role: m.role,
822
- senderName: m.sender_name,
823
- text,
824
- createdAt: m.created_at
825
- });
826
- }
827
- }
828
- return convs.map((c) => ({
829
- ...c,
830
- participants: byConv.get(c.id) ?? [],
831
- lastMessage: byLastMsg.get(c.id)
832
- }));
833
- }
834
- async function findDMConversation(mindName, participantIds) {
835
- const db = await getDb();
836
- const mindConvs = await db.select({ id: conversations.id }).from(conversations).where(and2(eq3(conversations.mind_name, mindName), eq3(conversations.type, "dm"))).all();
837
- for (const conv of mindConvs) {
838
- const rows = await db.select({ user_id: conversationParticipants.user_id }).from(conversationParticipants).where(eq3(conversationParticipants.conversation_id, conv.id)).all();
839
- if (rows.length !== 2) continue;
840
- const ids = new Set(rows.map((r) => r.user_id));
841
- if (ids.has(participantIds[0]) && ids.has(participantIds[1])) {
842
- return conv.id;
843
- }
844
- }
845
- return null;
846
- }
847
- async function deleteConversation(id) {
848
- const db = await getDb();
849
- await db.delete(conversations).where(eq3(conversations.id, id));
850
- }
851
- async function createChannel(name, creatorId) {
852
- const participantIds = creatorId ? [creatorId] : [];
853
- return createConversation(null, "volute", {
854
- type: "channel",
855
- name,
856
- title: name,
857
- participantIds
858
- });
859
- }
860
- async function getChannelByName(name) {
861
- const db = await getDb();
862
- const row = await db.select().from(conversations).where(and2(eq3(conversations.name, name), eq3(conversations.type, "channel"))).get();
863
- return row ?? null;
864
- }
865
- async function listChannels() {
866
- const db = await getDb();
867
- return await db.select().from(conversations).where(eq3(conversations.type, "channel")).orderBy(conversations.name).all();
868
- }
869
- async function joinChannel(conversationId, userId) {
870
- if (await isParticipant(conversationId, userId)) return;
871
- await addParticipant(conversationId, userId);
872
- }
873
- async function leaveChannel(conversationId, userId) {
874
- await removeParticipant(conversationId, userId);
875
- }
876
-
877
- // src/web/api/activity.ts
878
498
  var app = new Hono().get("/events", async (c) => {
879
499
  const user = c.get("user");
880
500
  return streamSSE(c, async (stream) => {
@@ -883,7 +503,7 @@ var app = new Hono().get("/events", async (c) => {
883
503
  let recentActivity = [];
884
504
  try {
885
505
  const db = await getDb();
886
- recentActivity = await db.select().from(activity).orderBy(desc2(activity.created_at)).limit(50);
506
+ recentActivity = await db.select().from(activity).orderBy(desc(activity.created_at)).limit(50);
887
507
  recentActivity = recentActivity.map((row) => ({
888
508
  ...row,
889
509
  metadata: row.metadata ? JSON.parse(row.metadata) : null
@@ -933,8 +553,8 @@ var app = new Hono().get("/events", async (c) => {
933
553
  });
934
554
  }, 15e3);
935
555
  cleanups.push(() => clearInterval(keepAlive));
936
- await new Promise((resolve18) => {
937
- stream.onAbort(() => resolve18());
556
+ await new Promise((resolve19) => {
557
+ stream.onAbort(() => resolve19());
938
558
  });
939
559
  } finally {
940
560
  for (const cleanup of cleanups) {
@@ -949,6 +569,8 @@ var app = new Hono().get("/events", async (c) => {
949
569
  var activity_default = app;
950
570
 
951
571
  // src/web/api/auth.ts
572
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync, writeFileSync as writeFileSync2 } from "fs";
573
+ import { extname, resolve as resolve3 } from "path";
952
574
  import { zValidator } from "@hono/zod-validator";
953
575
  import { Hono as Hono2 } from "hono";
954
576
  import { deleteCookie, getCookie as getCookie2, setCookie } from "hono/cookie";
@@ -961,12 +583,71 @@ var changePasswordSchema = z.object({
961
583
  currentPassword: z.string().min(1),
962
584
  newPassword: z.string().min(1)
963
585
  });
586
+ var profileSchema = z.object({
587
+ display_name: z.string().max(100).nullable().optional(),
588
+ description: z.string().max(500).nullable().optional()
589
+ });
590
+ var AVATAR_MIME = {
591
+ ".png": "image/png",
592
+ ".jpg": "image/jpeg",
593
+ ".jpeg": "image/jpeg",
594
+ ".gif": "image/gif",
595
+ ".webp": "image/webp"
596
+ };
597
+ var MAX_AVATAR_SIZE = 2 * 1024 * 1024;
598
+ function avatarsDir() {
599
+ return resolve3(voluteHome(), "avatars");
600
+ }
964
601
  var authenticated = new Hono2().use(authMiddleware).post("/change-password", zValidator("json", changePasswordSchema), async (c) => {
965
602
  const user = c.get("user");
966
603
  const { currentPassword, newPassword } = c.req.valid("json");
967
604
  const ok = await changePassword(user.id, currentPassword, newPassword);
968
605
  if (!ok) return c.json({ error: "Current password is incorrect" }, 400);
969
606
  return c.json({ ok: true });
607
+ }).put("/profile", zValidator("json", profileSchema), async (c) => {
608
+ const user = c.get("user");
609
+ const body = c.req.valid("json");
610
+ await updateUserProfile(user.id, body);
611
+ const sessionId = getCookie2(c, "volute_session");
612
+ if (sessionId) invalidateSessionCache(sessionId);
613
+ return c.json({ ok: true });
614
+ }).post("/avatar", async (c) => {
615
+ const user = c.get("user");
616
+ const body = await c.req.parseBody();
617
+ const file = body.file;
618
+ if (!(file instanceof File)) {
619
+ return c.json({ error: "No file uploaded" }, 400);
620
+ }
621
+ if (file.size > MAX_AVATAR_SIZE) {
622
+ return c.json({ error: "File too large (max 2MB)" }, 400);
623
+ }
624
+ const ext = extname(file.name).toLowerCase();
625
+ if (!AVATAR_MIME[ext]) {
626
+ return c.json({ error: "Invalid file type (png, jpg, gif, webp only)" }, 400);
627
+ }
628
+ const dir = avatarsDir();
629
+ mkdirSync2(dir, { recursive: true });
630
+ const filename = `avatar-${user.id}${ext}`;
631
+ const buffer2 = Buffer.from(await file.arrayBuffer());
632
+ writeFileSync2(resolve3(dir, filename), buffer2);
633
+ if (user.avatar && user.avatar !== filename) {
634
+ const oldPath = resolve3(dir, user.avatar);
635
+ rmSync(oldPath, { force: true });
636
+ }
637
+ await updateUserProfile(user.id, { avatar: filename });
638
+ const sessionId = getCookie2(c, "volute_session");
639
+ if (sessionId) invalidateSessionCache(sessionId);
640
+ return c.json({ ok: true, avatar: filename });
641
+ }).delete("/avatar", async (c) => {
642
+ const user = c.get("user");
643
+ if (user.avatar) {
644
+ const path = resolve3(avatarsDir(), user.avatar);
645
+ rmSync(path, { force: true });
646
+ }
647
+ await updateUserProfile(user.id, { avatar: null });
648
+ const sessionId = getCookie2(c, "volute_session");
649
+ if (sessionId) invalidateSessionCache(sessionId);
650
+ return c.json({ ok: true });
970
651
  });
971
652
  var admin = new Hono2().use(authMiddleware).get("/users", async (c) => {
972
653
  const user = c.get("user");
@@ -988,8 +669,55 @@ var admin = new Hono2().use(authMiddleware).get("/users", async (c) => {
988
669
  const user = c.get("user");
989
670
  if (user.role !== "admin") return c.json({ error: "Forbidden" }, 403);
990
671
  const id = parseInt(c.req.param("id"), 10);
672
+ if (Number.isNaN(id)) return c.json({ error: "Invalid user ID" }, 400);
991
673
  await approveUser(id);
992
674
  return c.json({ ok: true });
675
+ }).post(
676
+ "/users/:id/role",
677
+ zValidator("json", z.object({ role: z.enum(["admin", "user"]) })),
678
+ async (c) => {
679
+ const user = c.get("user");
680
+ if (user.role !== "admin") return c.json({ error: "Forbidden" }, 403);
681
+ const id = parseInt(c.req.param("id"), 10);
682
+ if (Number.isNaN(id)) return c.json({ error: "Invalid user ID" }, 400);
683
+ const { role } = c.req.valid("json");
684
+ if (role !== "admin") {
685
+ const adminCount = await countAdmins();
686
+ if (adminCount <= 1) {
687
+ const target = await getUser(id);
688
+ if (target?.role === "admin") {
689
+ return c.json({ error: "Cannot remove the last admin" }, 400);
690
+ }
691
+ }
692
+ }
693
+ await setUserRole(id, role);
694
+ return c.json({ ok: true });
695
+ }
696
+ ).put("/users/:id/profile", zValidator("json", profileSchema), async (c) => {
697
+ const user = c.get("user");
698
+ if (user.role !== "admin") return c.json({ error: "Forbidden" }, 403);
699
+ const id = parseInt(c.req.param("id"), 10);
700
+ if (Number.isNaN(id)) return c.json({ error: "Invalid user ID" }, 400);
701
+ const body = c.req.valid("json");
702
+ await updateUserProfile(id, body);
703
+ return c.json({ ok: true });
704
+ }).delete("/users/:id", async (c) => {
705
+ const user = c.get("user");
706
+ if (user.role !== "admin") return c.json({ error: "Forbidden" }, 403);
707
+ const id = parseInt(c.req.param("id"), 10);
708
+ if (Number.isNaN(id)) return c.json({ error: "Invalid user ID" }, 400);
709
+ if (id === user.id) return c.json({ error: "Cannot delete yourself" }, 400);
710
+ const target = await getUser(id);
711
+ if (!target) return c.json({ error: "User not found" }, 404);
712
+ if (target.role === "admin") {
713
+ const adminCount = await countAdmins();
714
+ if (adminCount <= 1) return c.json({ error: "Cannot delete the last admin" }, 400);
715
+ }
716
+ if (target.user_type === "mind") {
717
+ return c.json({ error: "Use the mind deletion API to delete minds" }, 400);
718
+ }
719
+ await deleteUser(id);
720
+ return c.json({ ok: true });
993
721
  });
994
722
  var app2 = new Hono2().post("/register", zValidator("json", credentialsSchema), async (c) => {
995
723
  const { username, password } = c.req.valid("json");
@@ -1026,7 +754,32 @@ var app2 = new Hono2().post("/register", zValidator("json", credentialsSchema),
1026
754
  if (userId == null) return c.json({ error: "Not logged in" }, 401);
1027
755
  const user = await getUser(userId);
1028
756
  if (!user) return c.json({ error: "Not logged in" }, 401);
1029
- return c.json({ id: user.id, username: user.username, role: user.role });
757
+ return c.json({
758
+ id: user.id,
759
+ username: user.username,
760
+ role: user.role,
761
+ display_name: user.display_name,
762
+ description: user.description,
763
+ avatar: user.avatar
764
+ });
765
+ }).get("/avatars/:filename", async (c) => {
766
+ const filename = c.req.param("filename");
767
+ if (filename.includes("/") || filename.includes("\\") || filename.includes("..")) {
768
+ return c.json({ error: "Invalid filename" }, 400);
769
+ }
770
+ const dir = avatarsDir();
771
+ const filePath = resolve3(dir, filename);
772
+ if (!filePath.startsWith(`${dir}/`)) return c.json({ error: "Invalid path" }, 400);
773
+ if (!existsSync3(filePath)) return c.json({ error: "Not found" }, 404);
774
+ const ext = extname(filename).toLowerCase();
775
+ const mime = AVATAR_MIME[ext];
776
+ if (!mime) return c.json({ error: "Invalid file type" }, 400);
777
+ const data = readFileSync2(filePath);
778
+ return c.body(data, 200, {
779
+ "Content-Type": mime,
780
+ "Cache-Control": "public, max-age=3600",
781
+ "X-Content-Type-Options": "nosniff"
782
+ });
1030
783
  }).route("/", admin).route("/", authenticated);
1031
784
  var auth_default = app2;
1032
785
 
@@ -1067,8 +820,8 @@ async function read(env, channelSlug, limit) {
1067
820
  if (!res.ok) {
1068
821
  throw new Error(`Discord API error: ${res.status} ${res.statusText}`);
1069
822
  }
1070
- const messages2 = await res.json();
1071
- return messages2.reverse().map((m) => `${m.author.username}: ${m.content}`).join("\n");
823
+ const messages = await res.json();
824
+ return messages.reverse().map((m) => `${m.author.username}: ${m.content}`).join("\n");
1072
825
  }
1073
826
  async function send(env, channelSlug, message, images) {
1074
827
  const token = requireToken(env);
@@ -1300,8 +1053,8 @@ async function listConversations2(env) {
1300
1053
  const userMap = /* @__PURE__ */ new Map();
1301
1054
  const imChannels = data.channels.filter((ch) => ch.is_im && ch.user);
1302
1055
  if (imChannels.length > 0) {
1303
- const users2 = await listUsers3(env);
1304
- for (const u of users2) {
1056
+ const users = await listUsers3(env);
1057
+ for (const u of users) {
1305
1058
  userMap.set(u.id, u.username);
1306
1059
  }
1307
1060
  }
@@ -1496,16 +1249,16 @@ __export(volute_exports, {
1496
1249
  read: () => read4,
1497
1250
  send: () => send4
1498
1251
  });
1499
- import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
1500
- import { resolve as resolve3 } from "path";
1252
+ import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
1253
+ import { resolve as resolve4 } from "path";
1501
1254
  function getDaemonConfig() {
1502
- const configPath2 = resolve3(voluteHome(), "daemon.json");
1503
- if (!existsSync3(configPath2)) {
1255
+ const configPath2 = resolve4(voluteHome(), "daemon.json");
1256
+ if (!existsSync4(configPath2)) {
1504
1257
  throw new Error("Volute daemon is not running");
1505
1258
  }
1506
1259
  let config;
1507
1260
  try {
1508
- config = JSON.parse(readFileSync2(configPath2, "utf-8"));
1261
+ config = JSON.parse(readFileSync3(configPath2, "utf-8"));
1509
1262
  } catch (err) {
1510
1263
  throw new Error(`Failed to parse ${configPath2}: ${err}`);
1511
1264
  }
@@ -1531,8 +1284,8 @@ async function read4(env, channelSlug, limit) {
1531
1284
  if (!res.ok) {
1532
1285
  throw new Error(`Failed to read conversation: ${res.status} ${res.statusText}`);
1533
1286
  }
1534
- const messages2 = await res.json();
1535
- return messages2.slice(-limit).map((m) => {
1287
+ const messages = await res.json();
1288
+ return messages.slice(-limit).map((m) => {
1536
1289
  const text = Array.isArray(m.content) ? m.content.filter((b) => b.type === "text").map((b) => b.text).join("") : m.content;
1537
1290
  return `${m.sender_name ?? m.role}: ${text}`;
1538
1291
  }).join("\n");
@@ -1759,8 +1512,8 @@ var app3 = new Hono3().post("/:name/channels/send", requireAdmin, async (c) => {
1759
1512
  return c.json({ error: `Platform ${platform} does not support listing users` }, 400);
1760
1513
  const env = buildEnv(name);
1761
1514
  try {
1762
- const users2 = await driver.listUsers(env);
1763
- return c.json(users2);
1515
+ const users = await driver.listUsers(env);
1516
+ return c.json(users);
1764
1517
  } catch (err) {
1765
1518
  return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
1766
1519
  }
@@ -1942,14 +1695,14 @@ var sharedEnvApp = new Hono5().get("/", (c) => {
1942
1695
  var env_default = app5;
1943
1696
 
1944
1697
  // src/web/api/file-sharing.ts
1945
- import { readFileSync as readFileSync4, statSync } from "fs";
1946
- import { resolve as resolve5 } from "path";
1698
+ import { readFileSync as readFileSync5, statSync } from "fs";
1699
+ import { resolve as resolve6 } from "path";
1947
1700
  import { Hono as Hono6 } from "hono";
1948
1701
 
1949
1702
  // src/lib/file-sharing.ts
1950
1703
  import { randomBytes } from "crypto";
1951
- import { existsSync as existsSync4, mkdirSync as mkdirSync2, readdirSync as readdirSync2, readFileSync as readFileSync3, rmSync, writeFileSync as writeFileSync2 } from "fs";
1952
- import { basename, join, normalize, resolve as resolve4 } from "path";
1704
+ import { existsSync as existsSync5, mkdirSync as mkdirSync3, readdirSync as readdirSync2, readFileSync as readFileSync4, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
1705
+ import { basename, join, normalize, resolve as resolve5 } from "path";
1953
1706
  function validateFilePath(filePath) {
1954
1707
  if (!filePath) return "File path is required";
1955
1708
  const normalized = normalize(filePath);
@@ -1962,13 +1715,13 @@ function validateFilePath(filePath) {
1962
1715
  return null;
1963
1716
  }
1964
1717
  function configPath(dir) {
1965
- return resolve4(dir, "home", ".config", "file-sharing.json");
1718
+ return resolve5(dir, "home", ".config", "file-sharing.json");
1966
1719
  }
1967
1720
  function readFileSharingConfig(dir) {
1968
1721
  const p = configPath(dir);
1969
- if (!existsSync4(p)) return {};
1722
+ if (!existsSync5(p)) return {};
1970
1723
  try {
1971
- return JSON.parse(readFileSync3(p, "utf-8"));
1724
+ return JSON.parse(readFileSync4(p, "utf-8"));
1972
1725
  } catch (err) {
1973
1726
  console.warn(`[file-sharing] failed to parse config at ${p}:`, err);
1974
1727
  return {};
@@ -1976,8 +1729,8 @@ function readFileSharingConfig(dir) {
1976
1729
  }
1977
1730
  function writeFileSharingConfig(dir, config) {
1978
1731
  const p = configPath(dir);
1979
- mkdirSync2(resolve4(p, ".."), { recursive: true });
1980
- writeFileSync2(p, `${JSON.stringify(config, null, 2)}
1732
+ mkdirSync3(resolve5(p, ".."), { recursive: true });
1733
+ writeFileSync3(p, `${JSON.stringify(config, null, 2)}
1981
1734
  `);
1982
1735
  }
1983
1736
  function isTrustedSender(dir, sender) {
@@ -2000,7 +1753,7 @@ function removeTrust(dir, sender) {
2000
1753
  writeFileSharingConfig(dir, config);
2001
1754
  }
2002
1755
  function pendingDir(receiver) {
2003
- return resolve4(stateDir(receiver), "pending-files");
1756
+ return resolve5(stateDir(receiver), "pending-files");
2004
1757
  }
2005
1758
  function validateId(id) {
2006
1759
  if (!id || id.includes("/") || id.includes("\\") || id.includes("..")) {
@@ -2019,8 +1772,8 @@ function stageFile(receiver, sender, filename, content, originalPath) {
2019
1772
  throw new Error("Invalid sender name");
2020
1773
  }
2021
1774
  const id = generateId(sender);
2022
- const dir = resolve4(pendingDir(receiver), id);
2023
- mkdirSync2(dir, { recursive: true });
1775
+ const dir = resolve5(pendingDir(receiver), id);
1776
+ mkdirSync3(dir, { recursive: true });
2024
1777
  const metadata = {
2025
1778
  id,
2026
1779
  sender,
@@ -2029,22 +1782,22 @@ function stageFile(receiver, sender, filename, content, originalPath) {
2029
1782
  size: content.length,
2030
1783
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
2031
1784
  };
2032
- writeFileSync2(resolve4(dir, "metadata.json"), `${JSON.stringify(metadata, null, 2)}
1785
+ writeFileSync3(resolve5(dir, "metadata.json"), `${JSON.stringify(metadata, null, 2)}
2033
1786
  `);
2034
- writeFileSync2(resolve4(dir, "data"), content);
1787
+ writeFileSync3(resolve5(dir, "data"), content);
2035
1788
  return { id };
2036
1789
  }
2037
1790
  function listPending(receiver) {
2038
1791
  const dir = pendingDir(receiver);
2039
- if (!existsSync4(dir)) return [];
1792
+ if (!existsSync5(dir)) return [];
2040
1793
  const entries = readdirSync2(dir, { withFileTypes: true });
2041
1794
  const result = [];
2042
1795
  for (const entry of entries) {
2043
1796
  if (!entry.isDirectory()) continue;
2044
- const metaPath = resolve4(dir, entry.name, "metadata.json");
2045
- if (!existsSync4(metaPath)) continue;
1797
+ const metaPath = resolve5(dir, entry.name, "metadata.json");
1798
+ if (!existsSync5(metaPath)) continue;
2046
1799
  try {
2047
- result.push(JSON.parse(readFileSync3(metaPath, "utf-8")));
1800
+ result.push(JSON.parse(readFileSync4(metaPath, "utf-8")));
2048
1801
  } catch (err) {
2049
1802
  console.warn(`[file-sharing] skipping malformed pending entry ${entry.name}:`, err);
2050
1803
  }
@@ -2053,10 +1806,10 @@ function listPending(receiver) {
2053
1806
  }
2054
1807
  function getPending(receiver, id) {
2055
1808
  validateId(id);
2056
- const metaPath = resolve4(pendingDir(receiver), id, "metadata.json");
2057
- if (!existsSync4(metaPath)) return null;
1809
+ const metaPath = resolve5(pendingDir(receiver), id, "metadata.json");
1810
+ if (!existsSync5(metaPath)) return null;
2058
1811
  try {
2059
- return JSON.parse(readFileSync3(metaPath, "utf-8"));
1812
+ return JSON.parse(readFileSync4(metaPath, "utf-8"));
2060
1813
  } catch (err) {
2061
1814
  console.warn(`[file-sharing] failed to read pending metadata for ${id}:`, err);
2062
1815
  return null;
@@ -2071,27 +1824,27 @@ function deliverFile(receiverDir, sender, filename, content, inboxPath) {
2071
1824
  if (sender.includes("/") || sender.includes("\\")) {
2072
1825
  throw new Error("Invalid sender name");
2073
1826
  }
2074
- const destDir = resolve4(receiverDir, "home", inbox, sender);
2075
- mkdirSync2(destDir, { recursive: true });
2076
- const destPath = resolve4(destDir, basename(filename));
2077
- writeFileSync2(destPath, content);
1827
+ const destDir = resolve5(receiverDir, "home", inbox, sender);
1828
+ mkdirSync3(destDir, { recursive: true });
1829
+ const destPath = resolve5(destDir, basename(filename));
1830
+ writeFileSync3(destPath, content);
2078
1831
  return join(inbox, sender, basename(filename));
2079
1832
  }
2080
1833
  function acceptPending(receiver, id, receiverDir) {
2081
1834
  const meta = getPending(receiver, id);
2082
1835
  if (!meta) throw new Error(`Pending file not found: ${id}`);
2083
- const dataPath = resolve4(pendingDir(receiver), id, "data");
2084
- const content = readFileSync3(dataPath);
1836
+ const dataPath = resolve5(pendingDir(receiver), id, "data");
1837
+ const content = readFileSync4(dataPath);
2085
1838
  const config = readFileSharingConfig(receiverDir);
2086
1839
  const inboxPath = config.inboxPath ?? "inbox";
2087
1840
  const destPath = deliverFile(receiverDir, meta.sender, meta.filename, content, inboxPath);
2088
- rmSync(resolve4(pendingDir(receiver), id), { recursive: true });
1841
+ rmSync2(resolve5(pendingDir(receiver), id), { recursive: true });
2089
1842
  return { sender: meta.sender, filename: meta.filename, destPath };
2090
1843
  }
2091
1844
  function rejectPending(receiver, id) {
2092
1845
  const meta = getPending(receiver, id);
2093
1846
  if (!meta) throw new Error(`Pending file not found: ${id}`);
2094
- rmSync(resolve4(pendingDir(receiver), id), { recursive: true });
1847
+ rmSync2(resolve5(pendingDir(receiver), id), { recursive: true });
2095
1848
  return { sender: meta.sender, filename: meta.filename };
2096
1849
  }
2097
1850
  function formatFileSize(bytes) {
@@ -2132,7 +1885,7 @@ var app6 = new Hono6().post("/:name/files/send", async (c) => {
2132
1885
  const pathErr = validateFilePath(body.filePath);
2133
1886
  if (pathErr) return c.json({ error: pathErr }, 400);
2134
1887
  const senderDir = mindDir(senderName);
2135
- const filePath = resolve5(senderDir, "home", body.filePath);
1888
+ const filePath = resolve6(senderDir, "home", body.filePath);
2136
1889
  const MAX_FILE_SIZE = 50 * 1024 * 1024;
2137
1890
  const stat4 = statSync(filePath, { throwIfNoEntry: false });
2138
1891
  if (!stat4) return c.json({ error: `File not found: ${body.filePath}` }, 404);
@@ -2146,7 +1899,7 @@ var app6 = new Hono6().post("/:name/files/send", async (c) => {
2146
1899
  }
2147
1900
  let content;
2148
1901
  try {
2149
- content = readFileSync4(filePath);
1902
+ content = readFileSync5(filePath);
2150
1903
  } catch {
2151
1904
  return c.json({ error: `File not found: ${body.filePath}` }, 404);
2152
1905
  }
@@ -2248,19 +2001,19 @@ var app6 = new Hono6().post("/:name/files/send", async (c) => {
2248
2001
  var file_sharing_default = app6;
2249
2002
 
2250
2003
  // src/web/api/files.ts
2251
- import { existsSync as existsSync5 } from "fs";
2004
+ import { existsSync as existsSync6 } from "fs";
2252
2005
  import { readdir, readFile, realpath, stat } from "fs/promises";
2253
- import { extname, resolve as resolve6 } from "path";
2006
+ import { extname as extname2, resolve as resolve7 } from "path";
2254
2007
  import { Hono as Hono7 } from "hono";
2255
2008
  var ALLOWED_FILES = /* @__PURE__ */ new Set(["SOUL.md", "MEMORY.md", "CLAUDE.md", "VOLUTE.md"]);
2256
- var AVATAR_MIME = {
2009
+ var AVATAR_MIME2 = {
2257
2010
  ".png": "image/png",
2258
2011
  ".jpg": "image/jpeg",
2259
2012
  ".jpeg": "image/jpeg",
2260
2013
  ".gif": "image/gif",
2261
2014
  ".webp": "image/webp"
2262
2015
  };
2263
- var MAX_AVATAR_SIZE = 2 * 1024 * 1024;
2016
+ var MAX_AVATAR_SIZE2 = 2 * 1024 * 1024;
2264
2017
  var app7 = new Hono7().get("/:name/avatar", async (c) => {
2265
2018
  const name = c.req.param("name");
2266
2019
  const entry = findMind(name);
@@ -2268,11 +2021,11 @@ var app7 = new Hono7().get("/:name/avatar", async (c) => {
2268
2021
  const dir = mindDir(name);
2269
2022
  const config = readVoluteConfig(dir);
2270
2023
  if (!config?.avatar) return c.json({ error: "No avatar configured" }, 404);
2271
- const ext = extname(config.avatar).toLowerCase();
2272
- const mime = AVATAR_MIME[ext];
2024
+ const ext = extname2(config.avatar).toLowerCase();
2025
+ const mime = AVATAR_MIME2[ext];
2273
2026
  if (!mime) return c.json({ error: "Invalid avatar extension" }, 400);
2274
- const homeDir = resolve6(dir, "home");
2275
- const avatarPath = resolve6(homeDir, config.avatar);
2027
+ const homeDir = resolve7(dir, "home");
2028
+ const avatarPath = resolve7(homeDir, config.avatar);
2276
2029
  if (!avatarPath.startsWith(`${homeDir}/`)) return c.json({ error: "Invalid avatar path" }, 400);
2277
2030
  let realAvatarPath;
2278
2031
  try {
@@ -2287,7 +2040,7 @@ var app7 = new Hono7().get("/:name/avatar", async (c) => {
2287
2040
  }
2288
2041
  try {
2289
2042
  const fileStat = await stat(realAvatarPath);
2290
- if (fileStat.size > MAX_AVATAR_SIZE) return c.json({ error: "Avatar file too large" }, 400);
2043
+ if (fileStat.size > MAX_AVATAR_SIZE2) return c.json({ error: "Avatar file too large" }, 400);
2291
2044
  const body = await readFile(realAvatarPath);
2292
2045
  return c.body(body, 200, {
2293
2046
  "Content-Type": mime,
@@ -2301,8 +2054,8 @@ var app7 = new Hono7().get("/:name/avatar", async (c) => {
2301
2054
  const entry = findMind(name);
2302
2055
  if (!entry) return c.json({ error: "Mind not found" }, 404);
2303
2056
  const dir = mindDir(name);
2304
- const homeDir = resolve6(dir, "home");
2305
- if (!existsSync5(homeDir)) return c.json({ error: "Home directory missing" }, 404);
2057
+ const homeDir = resolve7(dir, "home");
2058
+ if (!existsSync6(homeDir)) return c.json({ error: "Home directory missing" }, 404);
2306
2059
  const allFiles = await readdir(homeDir);
2307
2060
  const files = allFiles.filter((f) => f.endsWith(".md") && ALLOWED_FILES.has(f));
2308
2061
  return c.json(files);
@@ -2315,8 +2068,8 @@ var app7 = new Hono7().get("/:name/avatar", async (c) => {
2315
2068
  const entry = findMind(name);
2316
2069
  if (!entry) return c.json({ error: "Mind not found" }, 404);
2317
2070
  const dir = mindDir(name);
2318
- const filePath = resolve6(dir, "home", filename);
2319
- if (!existsSync5(filePath)) {
2071
+ const filePath = resolve7(dir, "home", filename);
2072
+ if (!existsSync6(filePath)) {
2320
2073
  return c.json({ error: "File not found" }, 404);
2321
2074
  }
2322
2075
  const content = await readFile(filePath, "utf-8");
@@ -2329,19 +2082,19 @@ import { Hono as Hono8 } from "hono";
2329
2082
 
2330
2083
  // src/lib/identity.ts
2331
2084
  import { createHash, generateKeyPairSync, sign, verify } from "crypto";
2332
- import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
2333
- import { resolve as resolve7 } from "path";
2085
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
2086
+ import { resolve as resolve8 } from "path";
2334
2087
  function generateIdentity(mindDir2) {
2335
- const identityDir = resolve7(mindDir2, ".mind/identity");
2336
- mkdirSync3(identityDir, { recursive: true });
2088
+ const identityDir = resolve8(mindDir2, ".mind/identity");
2089
+ mkdirSync4(identityDir, { recursive: true });
2337
2090
  const { publicKey, privateKey } = generateKeyPairSync("ed25519", {
2338
2091
  publicKeyEncoding: { type: "spki", format: "pem" },
2339
2092
  privateKeyEncoding: { type: "pkcs8", format: "pem" }
2340
2093
  });
2341
- const privatePath = resolve7(identityDir, "private.pem");
2342
- const publicPath = resolve7(identityDir, "public.pem");
2343
- writeFileSync3(privatePath, privateKey, { mode: 384 });
2344
- writeFileSync3(publicPath, publicKey, { mode: 420 });
2094
+ const privatePath = resolve8(identityDir, "private.pem");
2095
+ const publicPath = resolve8(identityDir, "public.pem");
2096
+ writeFileSync4(privatePath, privateKey, { mode: 384 });
2097
+ writeFileSync4(publicPath, publicKey, { mode: 420 });
2345
2098
  const config = readVoluteConfig(mindDir2) ?? {};
2346
2099
  config.identity = {
2347
2100
  privateKey: ".mind/identity/private.pem",
@@ -2354,17 +2107,17 @@ function getPrivateKey(mindDir2) {
2354
2107
  const config = readVoluteConfig(mindDir2);
2355
2108
  const relPath = config?.identity?.privateKey;
2356
2109
  if (!relPath) return null;
2357
- const fullPath = resolve7(mindDir2, relPath);
2358
- if (!existsSync6(fullPath)) return null;
2359
- return readFileSync5(fullPath, "utf-8");
2110
+ const fullPath = resolve8(mindDir2, relPath);
2111
+ if (!existsSync7(fullPath)) return null;
2112
+ return readFileSync6(fullPath, "utf-8");
2360
2113
  }
2361
2114
  function getPublicKey(mindDir2) {
2362
2115
  const config = readVoluteConfig(mindDir2);
2363
2116
  const relPath = config?.identity?.publicKey;
2364
2117
  if (!relPath) return null;
2365
- const fullPath = resolve7(mindDir2, relPath);
2366
- if (!existsSync6(fullPath)) return null;
2367
- return readFileSync5(fullPath, "utf-8");
2118
+ const fullPath = resolve8(mindDir2, relPath);
2119
+ if (!existsSync7(fullPath)) return null;
2120
+ return readFileSync6(fullPath, "utf-8");
2368
2121
  }
2369
2122
  function getFingerprint(publicKeyPem) {
2370
2123
  return createHash("sha256").update(publicKeyPem).digest("hex");
@@ -2417,16 +2170,16 @@ var keys_default = app8;
2417
2170
 
2418
2171
  // src/web/api/logs.ts
2419
2172
  import { spawn } from "child_process";
2420
- import { existsSync as existsSync7 } from "fs";
2421
- import { resolve as resolve8 } from "path";
2173
+ import { existsSync as existsSync8 } from "fs";
2174
+ import { resolve as resolve9 } from "path";
2422
2175
  import { Hono as Hono9 } from "hono";
2423
2176
  import { streamSSE as streamSSE2 } from "hono/streaming";
2424
2177
  var app9 = new Hono9().get("/:name/logs", async (c) => {
2425
2178
  const name = c.req.param("name");
2426
2179
  const entry = findMind(name);
2427
2180
  if (!entry) return c.json({ error: "Mind not found" }, 404);
2428
- const logFile = resolve8(stateDir(name), "logs", "mind.log");
2429
- if (!existsSync7(logFile)) {
2181
+ const logFile = resolve9(stateDir(name), "logs", "mind.log");
2182
+ if (!existsSync8(logFile)) {
2430
2183
  return c.json({ error: "No log file found" }, 404);
2431
2184
  }
2432
2185
  return streamSSE2(c, async (stream) => {
@@ -2444,17 +2197,17 @@ var app9 = new Hono9().get("/:name/logs", async (c) => {
2444
2197
  stream.onAbort(() => {
2445
2198
  tail.kill();
2446
2199
  });
2447
- await new Promise((resolve18) => {
2448
- tail.on("exit", resolve18);
2449
- stream.onAbort(resolve18);
2200
+ await new Promise((resolve19) => {
2201
+ tail.on("exit", resolve19);
2202
+ stream.onAbort(resolve19);
2450
2203
  });
2451
2204
  });
2452
2205
  }).get("/:name/logs/tail", async (c) => {
2453
2206
  const name = c.req.param("name");
2454
2207
  const entry = findMind(name);
2455
2208
  if (!entry) return c.json({ error: "Mind not found" }, 404);
2456
- const logFile = resolve8(stateDir(name), "logs", "mind.log");
2457
- if (!existsSync7(logFile)) {
2209
+ const logFile = resolve9(stateDir(name), "logs", "mind.log");
2210
+ if (!existsSync8(logFile)) {
2458
2211
  return c.json({ error: "No log file found" }, 404);
2459
2212
  }
2460
2213
  const nParam = parseInt(c.req.query("n") ?? "50", 10);
@@ -2464,8 +2217,8 @@ var app9 = new Hono9().get("/:name/logs", async (c) => {
2464
2217
  tail.stdout.on("data", (data) => {
2465
2218
  output += data.toString();
2466
2219
  });
2467
- await new Promise((resolve18) => {
2468
- tail.on("exit", resolve18);
2220
+ await new Promise((resolve19) => {
2221
+ tail.on("exit", resolve19);
2469
2222
  });
2470
2223
  return c.text(output);
2471
2224
  });
@@ -2555,33 +2308,33 @@ var mind_skills_default = app10;
2555
2308
  // src/web/api/minds.ts
2556
2309
  import {
2557
2310
  cpSync,
2558
- existsSync as existsSync9,
2559
- mkdirSync as mkdirSync5,
2311
+ existsSync as existsSync10,
2312
+ mkdirSync as mkdirSync6,
2560
2313
  readdirSync as readdirSync4,
2561
- readFileSync as readFileSync8,
2562
- rmSync as rmSync3,
2563
- writeFileSync as writeFileSync6
2314
+ readFileSync as readFileSync9,
2315
+ rmSync as rmSync4,
2316
+ writeFileSync as writeFileSync7
2564
2317
  } from "fs";
2565
- import { resolve as resolve11 } from "path";
2318
+ import { resolve as resolve12 } from "path";
2566
2319
  import { zValidator as zValidator3 } from "@hono/zod-validator";
2567
- import { and as and3, desc as desc3, eq as eq4, sql as sql2 } from "drizzle-orm";
2320
+ import { and, desc as desc2, eq as eq2, sql } from "drizzle-orm";
2568
2321
  import { Hono as Hono11 } from "hono";
2569
2322
  import { z as z3 } from "zod";
2570
2323
 
2571
2324
  // src/lib/consolidate.ts
2572
- import { readdirSync as readdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
2573
- import { resolve as resolve9 } from "path";
2325
+ import { readdirSync as readdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
2326
+ import { resolve as resolve10 } from "path";
2574
2327
  async function consolidateMemory(mindDir2) {
2575
- const soulPath = resolve9(mindDir2, "home/SOUL.md");
2576
- const memoryPath = resolve9(mindDir2, "home/MEMORY.md");
2577
- const memoryDir = resolve9(mindDir2, "home/memory");
2578
- const soul = readFileSync6(soulPath, "utf-8");
2328
+ const soulPath = resolve10(mindDir2, "home/SOUL.md");
2329
+ const memoryPath = resolve10(mindDir2, "home/MEMORY.md");
2330
+ const memoryDir = resolve10(mindDir2, "home/memory");
2331
+ const soul = readFileSync7(soulPath, "utf-8");
2579
2332
  const logs = [];
2580
2333
  try {
2581
2334
  const files = readdirSync3(memoryDir).filter((f) => /^\d{4}-\d{2}-\d{2}\.md$/.test(f)).sort();
2582
2335
  for (const filename of files) {
2583
2336
  const date = filename.replace(".md", "");
2584
- const content2 = readFileSync6(resolve9(memoryDir, filename), "utf-8").trim();
2337
+ const content2 = readFileSync7(resolve10(memoryDir, filename), "utf-8").trim();
2585
2338
  if (content2) {
2586
2339
  logs.push(`### ${date}
2587
2340
 
@@ -2631,7 +2384,7 @@ ${content2}`);
2631
2384
  const data = await res.json();
2632
2385
  const content = data.content.filter((b) => b.type === "text" && b.text).map((b) => b.text).join("").trim();
2633
2386
  if (content) {
2634
- writeFileSync4(memoryPath, `${content}
2387
+ writeFileSync5(memoryPath, `${content}
2635
2388
  `);
2636
2389
  console.log("MEMORY.md created successfully.");
2637
2390
  } else {
@@ -2640,28 +2393,28 @@ ${content2}`);
2640
2393
  }
2641
2394
 
2642
2395
  // src/lib/convert-session.ts
2643
- import { randomUUID as randomUUID2 } from "crypto";
2644
- import { mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
2396
+ import { randomUUID } from "crypto";
2397
+ import { mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "fs";
2645
2398
  import { homedir } from "os";
2646
- import { resolve as resolve10 } from "path";
2399
+ import { resolve as resolve11 } from "path";
2647
2400
  function convertSession(opts) {
2648
- const lines = readFileSync7(opts.sessionPath, "utf-8").trim().split("\n");
2649
- const sessionId = randomUUID2();
2401
+ const lines = readFileSync8(opts.sessionPath, "utf-8").trim().split("\n");
2402
+ const sessionId = randomUUID();
2650
2403
  const idMap = /* @__PURE__ */ new Map();
2651
- const messages2 = [];
2404
+ const messages = [];
2652
2405
  for (const line of lines) {
2653
2406
  const event = JSON.parse(line);
2654
2407
  if (event.type === "message" && event.message) {
2655
- messages2.push(event);
2408
+ messages.push(event);
2656
2409
  }
2657
2410
  }
2658
2411
  const sdkEvents = [];
2659
2412
  let lastSdkUuid = null;
2660
- for (let i = 0; i < messages2.length; i++) {
2661
- const event = messages2[i];
2413
+ for (let i = 0; i < messages.length; i++) {
2414
+ const event = messages[i];
2662
2415
  const msg = event.message;
2663
2416
  if (msg.role === "user") {
2664
- const uuid = randomUUID2();
2417
+ const uuid = randomUUID();
2665
2418
  idMap.set(event.id, uuid);
2666
2419
  const parentUuid = event.parentId ? idMap.get(event.parentId) ?? null : null;
2667
2420
  const sdkEvent = {
@@ -2685,7 +2438,7 @@ function convertSession(opts) {
2685
2438
  } else if (msg.role === "assistant") {
2686
2439
  const content = convertAssistantContent(msg.content);
2687
2440
  if (content.length === 0) continue;
2688
- const uuid = randomUUID2();
2441
+ const uuid = randomUUID();
2689
2442
  idMap.set(event.id, uuid);
2690
2443
  const parentUuid = event.parentId ? idMap.get(event.parentId) ?? null : null;
2691
2444
  const stopReason = mapStopReason(msg.stopReason);
@@ -2700,12 +2453,12 @@ function convertSession(opts) {
2700
2453
  isSidechain: false,
2701
2454
  userType: "external",
2702
2455
  type: "assistant",
2703
- requestId: `req_imported_${randomUUID2()}`,
2456
+ requestId: `req_imported_${randomUUID()}`,
2704
2457
  message: {
2705
2458
  role: "assistant",
2706
2459
  content,
2707
2460
  type: "message",
2708
- id: `msg_imported_${randomUUID2()}`,
2461
+ id: `msg_imported_${randomUUID()}`,
2709
2462
  model: mapModel(msg.model),
2710
2463
  stop_reason: stopReason,
2711
2464
  stop_sequence: null,
@@ -2719,8 +2472,8 @@ function convertSession(opts) {
2719
2472
  let lastToolResultId = event.id;
2720
2473
  let lastTimestamp = event.timestamp;
2721
2474
  let j = i;
2722
- while (j < messages2.length && messages2[j].message.role === "toolResult") {
2723
- const tr = messages2[j];
2475
+ while (j < messages.length && messages[j].message.role === "toolResult") {
2476
+ const tr = messages[j];
2724
2477
  const trMsg = tr.message;
2725
2478
  lastToolResultId = tr.id;
2726
2479
  lastTimestamp = tr.timestamp;
@@ -2733,7 +2486,7 @@ function convertSession(opts) {
2733
2486
  j++;
2734
2487
  }
2735
2488
  i = j - 1;
2736
- const uuid = randomUUID2();
2489
+ const uuid = randomUUID();
2737
2490
  idMap.set(lastToolResultId, uuid);
2738
2491
  const parentUuid = event.parentId ? idMap.get(event.parentId) ?? null : lastSdkUuid;
2739
2492
  const sdkEvent = {
@@ -2759,10 +2512,10 @@ function convertSession(opts) {
2759
2512
  }
2760
2513
  }
2761
2514
  const projectId = opts.projectDir.replace(/\//g, "-");
2762
- const sdkDir = resolve10(homedir(), ".claude", "projects", projectId);
2763
- mkdirSync4(sdkDir, { recursive: true });
2764
- const sdkPath = resolve10(sdkDir, `${sessionId}.jsonl`);
2765
- writeFileSync5(sdkPath, `${sdkEvents.join("\n")}
2515
+ const sdkDir = resolve11(homedir(), ".claude", "projects", projectId);
2516
+ mkdirSync5(sdkDir, { recursive: true });
2517
+ const sdkPath = resolve11(sdkDir, `${sessionId}.jsonl`);
2518
+ writeFileSync6(sdkPath, `${sdkEvents.join("\n")}
2766
2519
  `);
2767
2520
  console.log(`Converted ${sdkEvents.length} messages \u2192 ${sdkPath}`);
2768
2521
  return sessionId;
@@ -2814,7 +2567,7 @@ function convertAssistantContent(content) {
2814
2567
  }
2815
2568
 
2816
2569
  // src/lib/variant-cleanup.ts
2817
- import { existsSync as existsSync8, rmSync as rmSync2 } from "fs";
2570
+ import { existsSync as existsSync9, rmSync as rmSync3 } from "fs";
2818
2571
  async function cleanupVariant(mindName, variantName, projectRoot, variantPath, opts) {
2819
2572
  if (opts?.stop) {
2820
2573
  try {
@@ -2822,11 +2575,11 @@ async function cleanupVariant(mindName, variantName, projectRoot, variantPath, o
2822
2575
  } catch {
2823
2576
  }
2824
2577
  }
2825
- if (existsSync8(variantPath)) {
2578
+ if (existsSync9(variantPath)) {
2826
2579
  try {
2827
2580
  await gitExec(["worktree", "remove", "--force", variantPath], { cwd: projectRoot });
2828
2581
  } catch {
2829
- rmSync2(variantPath, { recursive: true, force: true });
2582
+ rmSync3(variantPath, { recursive: true, force: true });
2830
2583
  try {
2831
2584
  await gitExec(["worktree", "prune"], { cwd: projectRoot });
2832
2585
  } catch {
@@ -2853,7 +2606,7 @@ async function getMindStatus(name, port) {
2853
2606
  const manager = getMindManager();
2854
2607
  let status = "stopped";
2855
2608
  try {
2856
- const { getSleepManagerIfReady } = await import("./sleep-manager-2TMQ65E4.js");
2609
+ const { getSleepManagerIfReady } = await import("./sleep-manager-XXSWQQLE.js");
2857
2610
  if (getSleepManagerIfReady()?.isSleeping(name)) {
2858
2611
  status = "sleeping";
2859
2612
  }
@@ -2911,7 +2664,7 @@ async function initTemplateBranch(projectRoot, composedDir, manifest, mindName,
2911
2664
  await gitExec(["commit", "-m", "initial commit"], opts);
2912
2665
  }
2913
2666
  async function updateTemplateBranch(projectRoot, template, mindName) {
2914
- const tempWorktree = resolve11(projectRoot, ".variants", "_template_update");
2667
+ const tempWorktree = resolve12(projectRoot, ".variants", "_template_update");
2915
2668
  let branchExists = false;
2916
2669
  try {
2917
2670
  await gitExec(["rev-parse", "--verify", TEMPLATE_BRANCH], { cwd: projectRoot });
@@ -2922,8 +2675,8 @@ async function updateTemplateBranch(projectRoot, template, mindName) {
2922
2675
  await gitExec(["worktree", "remove", "--force", tempWorktree], { cwd: projectRoot });
2923
2676
  } catch {
2924
2677
  }
2925
- if (existsSync9(tempWorktree)) {
2926
- rmSync3(tempWorktree, { recursive: true, force: true });
2678
+ if (existsSync10(tempWorktree)) {
2679
+ rmSync4(tempWorktree, { recursive: true, force: true });
2927
2680
  }
2928
2681
  const templatesRoot = findTemplatesRoot();
2929
2682
  const { composedDir, manifest } = composeTemplate(templatesRoot, template);
@@ -2943,9 +2696,9 @@ async function updateTemplateBranch(projectRoot, template, mindName) {
2943
2696
  });
2944
2697
  }
2945
2698
  copyTemplateToDir(composedDir, tempWorktree, mindName, manifest);
2946
- const initDir = resolve11(tempWorktree, ".init");
2947
- if (existsSync9(initDir)) {
2948
- rmSync3(initDir, { recursive: true, force: true });
2699
+ const initDir = resolve12(tempWorktree, ".init");
2700
+ if (existsSync10(initDir)) {
2701
+ rmSync4(initDir, { recursive: true, force: true });
2949
2702
  }
2950
2703
  await gitExec(["add", "-A"], { cwd: tempWorktree });
2951
2704
  try {
@@ -2958,10 +2711,10 @@ async function updateTemplateBranch(projectRoot, template, mindName) {
2958
2711
  await gitExec(["worktree", "remove", "--force", tempWorktree], { cwd: projectRoot });
2959
2712
  } catch {
2960
2713
  }
2961
- if (existsSync9(tempWorktree)) {
2962
- rmSync3(tempWorktree, { recursive: true, force: true });
2714
+ if (existsSync10(tempWorktree)) {
2715
+ rmSync4(tempWorktree, { recursive: true, force: true });
2963
2716
  }
2964
- rmSync3(composedDir, { recursive: true, force: true });
2717
+ rmSync4(composedDir, { recursive: true, force: true });
2965
2718
  }
2966
2719
  }
2967
2720
  async function mergeTemplateBranch(worktreeDir) {
@@ -2984,14 +2737,14 @@ async function mergeTemplateBranch(worktreeDir) {
2984
2737
  async function npmInstallAsMind(cwd, mindName) {
2985
2738
  if (isIsolationEnabled()) {
2986
2739
  const [cmd, args] = wrapForIsolation("npm", ["install"], mindName);
2987
- await exec(cmd, args, { cwd, env: { ...process.env, HOME: resolve11(cwd, "home") } });
2740
+ await exec(cmd, args, { cwd, env: { ...process.env, HOME: resolve12(cwd, "home") } });
2988
2741
  } else {
2989
2742
  await exec("npm", ["install"], { cwd });
2990
2743
  }
2991
2744
  }
2992
2745
  async function importFromArchive(c, tempDir, nameOverride, manifest) {
2993
- const extractedMindDir = resolve11(tempDir, "mind");
2994
- if (!existsSync9(extractedMindDir)) {
2746
+ const extractedMindDir = resolve12(tempDir, "mind");
2747
+ if (!existsSync10(extractedMindDir)) {
2995
2748
  return c.json({ error: "Invalid archive: missing mind/ directory" }, 400);
2996
2749
  }
2997
2750
  if (!manifest?.includes || !manifest.name || !manifest.template) {
@@ -3009,21 +2762,21 @@ async function importFromFullArchive(c, tempDir, extractedMindDir, nameOverride,
3009
2762
  if (findMind(name)) return c.json({ error: `Mind already exists: ${name}` }, 409);
3010
2763
  ensureVoluteHome();
3011
2764
  const dest = mindDir(name);
3012
- if (existsSync9(dest)) return c.json({ error: "Mind directory already exists" }, 409);
2765
+ if (existsSync10(dest)) return c.json({ error: "Mind directory already exists" }, 409);
3013
2766
  try {
3014
2767
  cpSync(extractedMindDir, dest, { recursive: true });
3015
2768
  if (!manifest.includes.identity) {
3016
2769
  generateIdentity(dest);
3017
2770
  }
3018
2771
  const state = stateDir(name);
3019
- mkdirSync5(state, { recursive: true });
3020
- const channelsJson = resolve11(tempDir, "state/channels.json");
3021
- if (existsSync9(channelsJson)) {
3022
- cpSync(channelsJson, resolve11(state, "channels.json"));
2772
+ mkdirSync6(state, { recursive: true });
2773
+ const channelsJson = resolve12(tempDir, "state/channels.json");
2774
+ if (existsSync10(channelsJson)) {
2775
+ cpSync(channelsJson, resolve12(state, "channels.json"));
3023
2776
  }
3024
- const envJson = resolve11(tempDir, "state/env.json");
3025
- if (existsSync9(envJson)) {
3026
- cpSync(envJson, resolve11(state, "env.json"));
2777
+ const envJson = resolve12(tempDir, "state/env.json");
2778
+ if (existsSync10(envJson)) {
2779
+ cpSync(envJson, resolve12(state, "env.json"));
3027
2780
  }
3028
2781
  const port = nextPort();
3029
2782
  addMind(name, port, manifest.stage, manifest.template);
@@ -3032,36 +2785,36 @@ async function importFromFullArchive(c, tempDir, extractedMindDir, nameOverride,
3032
2785
  } catch (err) {
3033
2786
  logger_default.warn(`failed to set template hash for ${name}`, logger_default.errorData(err));
3034
2787
  }
3035
- const homeDir = resolve11(dest, "home");
2788
+ const homeDir = resolve12(dest, "home");
3036
2789
  ensureVoluteGroup();
3037
2790
  createMindUser(name, homeDir);
3038
2791
  chownMindDir(dest, name);
3039
2792
  await npmInstallAsMind(dest, name);
3040
2793
  await importHistoryFromArchive(name, tempDir);
3041
2794
  importSessionsFromArchive(dest, tempDir);
3042
- if (!existsSync9(resolve11(dest, ".git"))) {
2795
+ if (!existsSync10(resolve12(dest, ".git"))) {
3043
2796
  try {
3044
- const env = isIsolationEnabled() ? { ...process.env, HOME: resolve11(dest, "home") } : void 0;
2797
+ const env = isIsolationEnabled() ? { ...process.env, HOME: resolve12(dest, "home") } : void 0;
3045
2798
  await gitExec(["init"], { cwd: dest, mindName: name, env });
3046
2799
  await configureGitIdentity(name, { cwd: dest, mindName: name, env });
3047
2800
  await gitExec(["add", "-A"], { cwd: dest, mindName: name, env });
3048
2801
  await gitExec(["commit", "-m", "import from archive"], { cwd: dest, mindName: name, env });
3049
2802
  } catch (err) {
3050
2803
  logger_default.error(`git setup failed for imported mind ${name}`, logger_default.errorData(err));
3051
- rmSync3(resolve11(dest, ".git"), { recursive: true, force: true });
2804
+ rmSync4(resolve12(dest, ".git"), { recursive: true, force: true });
3052
2805
  }
3053
2806
  }
3054
2807
  chownMindDir(dest, name);
3055
- rmSync3(tempDir, { recursive: true, force: true });
2808
+ rmSync4(tempDir, { recursive: true, force: true });
3056
2809
  return c.json({ ok: true, name, port, message: `Imported mind: ${name} (port ${port})` });
3057
2810
  } catch (err) {
3058
- if (existsSync9(dest)) rmSync3(dest, { recursive: true, force: true });
2811
+ if (existsSync10(dest)) rmSync4(dest, { recursive: true, force: true });
3059
2812
  try {
3060
2813
  removeMind(name);
3061
2814
  } catch (cleanupErr) {
3062
2815
  logger_default.error(`Failed to clean up registry for ${name}`, logger_default.errorData(cleanupErr));
3063
2816
  }
3064
- rmSync3(tempDir, { recursive: true, force: true });
2817
+ rmSync4(tempDir, { recursive: true, force: true });
3065
2818
  return c.json({ error: err instanceof Error ? err.message : "Failed to import mind" }, 500);
3066
2819
  }
3067
2820
  }
@@ -3072,7 +2825,7 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
3072
2825
  if (findMind(name)) return c.json({ error: `Mind already exists: ${name}` }, 409);
3073
2826
  ensureVoluteHome();
3074
2827
  const dest = mindDir(name);
3075
- if (existsSync9(dest)) return c.json({ error: "Mind directory already exists" }, 409);
2828
+ if (existsSync10(dest)) return c.json({ error: "Mind directory already exists" }, 409);
3076
2829
  const templatesRoot = findTemplatesRoot();
3077
2830
  const { composedDir, manifest: templateManifest } = composeTemplate(
3078
2831
  templatesRoot,
@@ -3081,40 +2834,40 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
3081
2834
  try {
3082
2835
  copyTemplateToDir(composedDir, dest, name, templateManifest);
3083
2836
  applyInitFiles(dest);
3084
- const extractedHome = resolve11(extractedMindDir, "home");
3085
- if (existsSync9(extractedHome)) {
3086
- cpSync(extractedHome, resolve11(dest, "home"), { recursive: true });
2837
+ const extractedHome = resolve12(extractedMindDir, "home");
2838
+ if (existsSync10(extractedHome)) {
2839
+ cpSync(extractedHome, resolve12(dest, "home"), { recursive: true });
3087
2840
  }
3088
- const extractedMindInternal = resolve11(extractedMindDir, ".mind");
3089
- if (existsSync9(extractedMindInternal)) {
3090
- cpSync(extractedMindInternal, resolve11(dest, ".mind"), { recursive: true });
2841
+ const extractedMindInternal = resolve12(extractedMindDir, ".mind");
2842
+ if (existsSync10(extractedMindInternal)) {
2843
+ cpSync(extractedMindInternal, resolve12(dest, ".mind"), { recursive: true });
3091
2844
  }
3092
- const identityDir = resolve11(dest, ".mind/identity");
2845
+ const identityDir = resolve12(dest, ".mind/identity");
3093
2846
  let publicKeyPem;
3094
- if (!manifest.includes.identity || !existsSync9(resolve11(identityDir, "private.pem"))) {
2847
+ if (!manifest.includes.identity || !existsSync10(resolve12(identityDir, "private.pem"))) {
3095
2848
  ({ publicKeyPem } = generateIdentity(dest));
3096
2849
  } else {
3097
- publicKeyPem = readFileSync8(resolve11(identityDir, "public.pem"), "utf-8");
2850
+ publicKeyPem = readFileSync9(resolve12(identityDir, "public.pem"), "utf-8");
3098
2851
  }
3099
- const promptsPath = resolve11(dest, "home/.config/prompts.json");
3100
- if (!existsSync9(promptsPath)) {
2852
+ const promptsPath = resolve12(dest, "home/.config/prompts.json");
2853
+ if (!existsSync10(promptsPath)) {
3101
2854
  const mindPrompts = await getMindPromptDefaults();
3102
- writeFileSync6(promptsPath, `${JSON.stringify(mindPrompts, null, 2)}
2855
+ writeFileSync7(promptsPath, `${JSON.stringify(mindPrompts, null, 2)}
3103
2856
  `);
3104
2857
  }
3105
2858
  const state = stateDir(name);
3106
- mkdirSync5(state, { recursive: true });
3107
- const channelsJson = resolve11(tempDir, "state/channels.json");
3108
- if (existsSync9(channelsJson)) {
3109
- cpSync(channelsJson, resolve11(state, "channels.json"));
2859
+ mkdirSync6(state, { recursive: true });
2860
+ const channelsJson = resolve12(tempDir, "state/channels.json");
2861
+ if (existsSync10(channelsJson)) {
2862
+ cpSync(channelsJson, resolve12(state, "channels.json"));
3110
2863
  }
3111
- const envJson = resolve11(tempDir, "state/env.json");
3112
- if (existsSync9(envJson)) {
3113
- cpSync(envJson, resolve11(state, "env.json"));
2864
+ const envJson = resolve12(tempDir, "state/env.json");
2865
+ if (existsSync10(envJson)) {
2866
+ cpSync(envJson, resolve12(state, "env.json"));
3114
2867
  }
3115
2868
  const port = nextPort();
3116
2869
  addMind(name, port, manifest.stage, manifest.template);
3117
- const homeDir = resolve11(dest, "home");
2870
+ const homeDir = resolve12(dest, "home");
3118
2871
  ensureVoluteGroup();
3119
2872
  createMindUser(name, homeDir);
3120
2873
  chownMindDir(dest, name);
@@ -3127,7 +2880,7 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
3127
2880
  await initTemplateBranch(dest, composedDir, templateManifest, name, env);
3128
2881
  } catch (err) {
3129
2882
  logger_default.error(`git setup failed for imported mind ${name}`, logger_default.errorData(err));
3130
- rmSync3(resolve11(dest, ".git"), { recursive: true, force: true });
2883
+ rmSync4(resolve12(dest, ".git"), { recursive: true, force: true });
3131
2884
  gitWarning = "Git setup failed \u2014 variants and upgrades won't be available until git is initialized.";
3132
2885
  }
3133
2886
  try {
@@ -3151,7 +2904,7 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
3151
2904
  publishPublicKey(name, publicKeyPem).catch(
3152
2905
  (err) => logger_default.warn(`failed to publish key for ${name}`, { error: err.message })
3153
2906
  );
3154
- rmSync3(tempDir, { recursive: true, force: true });
2907
+ rmSync4(tempDir, { recursive: true, force: true });
3155
2908
  return c.json({
3156
2909
  ok: true,
3157
2910
  name,
@@ -3162,24 +2915,24 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
3162
2915
  ...skillWarnings.length > 0 && { skillWarnings }
3163
2916
  });
3164
2917
  } catch (err) {
3165
- if (existsSync9(dest)) rmSync3(dest, { recursive: true, force: true });
2918
+ if (existsSync10(dest)) rmSync4(dest, { recursive: true, force: true });
3166
2919
  try {
3167
2920
  removeMind(name);
3168
2921
  } catch (cleanupErr) {
3169
2922
  logger_default.error(`Failed to clean up registry for ${name}`, logger_default.errorData(cleanupErr));
3170
2923
  }
3171
- rmSync3(tempDir, { recursive: true, force: true });
2924
+ rmSync4(tempDir, { recursive: true, force: true });
3172
2925
  return c.json({ error: err instanceof Error ? err.message : "Failed to import mind" }, 500);
3173
2926
  } finally {
3174
- rmSync3(composedDir, { recursive: true, force: true });
2927
+ rmSync4(composedDir, { recursive: true, force: true });
3175
2928
  }
3176
2929
  }
3177
2930
  async function importHistoryFromArchive(name, tempDir) {
3178
- const historyJsonl = resolve11(tempDir, "history.jsonl");
3179
- if (!existsSync9(historyJsonl)) return;
2931
+ const historyJsonl = resolve12(tempDir, "history.jsonl");
2932
+ if (!existsSync10(historyJsonl)) return;
3180
2933
  try {
3181
2934
  const db = await getDb();
3182
- const lines = readFileSync8(historyJsonl, "utf-8").trim().split("\n");
2935
+ const lines = readFileSync9(historyJsonl, "utf-8").trim().split("\n");
3183
2936
  let imported = 0;
3184
2937
  let failed = 0;
3185
2938
  for (const line of lines) {
@@ -3215,13 +2968,13 @@ async function importHistoryFromArchive(name, tempDir) {
3215
2968
  }
3216
2969
  }
3217
2970
  function importSessionsFromArchive(dest, tempDir) {
3218
- const sessionsDir = resolve11(tempDir, "sessions");
3219
- if (!existsSync9(sessionsDir)) return;
2971
+ const sessionsDir = resolve12(tempDir, "sessions");
2972
+ if (!existsSync10(sessionsDir)) return;
3220
2973
  try {
3221
- const destSessions = resolve11(dest, ".mind/sessions");
3222
- mkdirSync5(destSessions, { recursive: true });
2974
+ const destSessions = resolve12(dest, ".mind/sessions");
2975
+ mkdirSync6(destSessions, { recursive: true });
3223
2976
  for (const file of readdirSync4(sessionsDir)) {
3224
- cpSync(resolve11(sessionsDir, file), resolve11(destSessions, file));
2977
+ cpSync(resolve12(sessionsDir, file), resolve12(destSessions, file));
3225
2978
  }
3226
2979
  } catch (err) {
3227
2980
  logger_default.error("Failed to import sessions from archive", logger_default.errorData(err));
@@ -3244,7 +2997,7 @@ var app11 = new Hono11().post("/", requireAdmin, zValidator3("json", createMindS
3244
2997
  if (findMind(name)) return c.json({ error: `Mind already exists: ${name}` }, 409);
3245
2998
  ensureVoluteHome();
3246
2999
  const dest = mindDir(name);
3247
- if (existsSync9(dest)) return c.json({ error: "Mind directory already exists" }, 409);
3000
+ if (existsSync10(dest)) return c.json({ error: "Mind directory already exists" }, 409);
3248
3001
  const templatesRoot = findTemplatesRoot();
3249
3002
  const { composedDir, manifest } = composeTemplate(templatesRoot, template);
3250
3003
  try {
@@ -3258,15 +3011,15 @@ var app11 = new Hono11().post("/", requireAdmin, zValidator3("json", createMindS
3258
3011
  writeVoluteConfig(dest, seedConfig);
3259
3012
  }
3260
3013
  if (body.model) {
3261
- const configPath2 = resolve11(dest, "home/.config/config.json");
3262
- const existing = existsSync9(configPath2) ? JSON.parse(readFileSync8(configPath2, "utf-8")) : {};
3014
+ const configPath2 = resolve12(dest, "home/.config/config.json");
3015
+ const existing = existsSync10(configPath2) ? JSON.parse(readFileSync9(configPath2, "utf-8")) : {};
3263
3016
  existing.model = body.model;
3264
- writeFileSync6(configPath2, `${JSON.stringify(existing, null, 2)}
3017
+ writeFileSync7(configPath2, `${JSON.stringify(existing, null, 2)}
3265
3018
  `);
3266
3019
  }
3267
3020
  const mindPrompts = await getMindPromptDefaults();
3268
- writeFileSync6(
3269
- resolve11(dest, "home/.config/prompts.json"),
3021
+ writeFileSync7(
3022
+ resolve12(dest, "home/.config/prompts.json"),
3270
3023
  `${JSON.stringify(mindPrompts, null, 2)}
3271
3024
  `
3272
3025
  );
@@ -3277,7 +3030,7 @@ var app11 = new Hono11().post("/", requireAdmin, zValidator3("json", createMindS
3277
3030
  } catch (err) {
3278
3031
  logger_default.warn(`failed to set template hash for ${name}`, logger_default.errorData(err));
3279
3032
  }
3280
- const homeDir = resolve11(dest, "home");
3033
+ const homeDir = resolve12(dest, "home");
3281
3034
  ensureVoluteGroup();
3282
3035
  createMindUser(name, homeDir);
3283
3036
  chownMindDir(dest, name);
@@ -3290,7 +3043,7 @@ var app11 = new Hono11().post("/", requireAdmin, zValidator3("json", createMindS
3290
3043
  await initTemplateBranch(dest, composedDir, manifest, name, env);
3291
3044
  } catch (err) {
3292
3045
  logger_default.error(`git setup failed for ${name}`, logger_default.errorData(err));
3293
- rmSync3(resolve11(dest, ".git"), { recursive: true, force: true });
3046
+ rmSync4(resolve12(dest, ".git"), { recursive: true, force: true });
3294
3047
  gitWarning = "Git setup failed \u2014 variants and upgrades won't be available until git is initialized.";
3295
3048
  }
3296
3049
  try {
@@ -3305,7 +3058,7 @@ The human who planted you described you as: "${body.description}"
3305
3058
  ` : "";
3306
3059
  const seedSoulRaw = body.seedSoul ?? await getPrompt("seed_soul", { name, description: descLine });
3307
3060
  const seedSoul = body.seedSoul ? substitute(seedSoulRaw, { name, description: descLine }) : seedSoulRaw;
3308
- writeFileSync6(resolve11(dest, "home/SOUL.md"), seedSoul);
3061
+ writeFileSync7(resolve12(dest, "home/SOUL.md"), seedSoul);
3309
3062
  }
3310
3063
  const skillSet = body.skills ?? (body.stage === "seed" ? SEED_SKILLS : STANDARD_SKILLS);
3311
3064
  const skillWarnings = [];
@@ -3320,11 +3073,11 @@ The human who planted you described you as: "${body.description}"
3320
3073
  if (body.stage !== "seed") {
3321
3074
  const customSoul = await getPromptIfCustom("default_soul");
3322
3075
  if (customSoul) {
3323
- writeFileSync6(resolve11(dest, "home/SOUL.md"), customSoul.replace(/\{\{name\}\}/g, name));
3076
+ writeFileSync7(resolve12(dest, "home/SOUL.md"), customSoul.replace(/\{\{name\}\}/g, name));
3324
3077
  }
3325
3078
  const customMemory = await getPromptIfCustom("default_memory");
3326
3079
  if (customMemory) {
3327
- writeFileSync6(resolve11(dest, "home/MEMORY.md"), customMemory);
3080
+ writeFileSync7(resolve12(dest, "home/MEMORY.md"), customMemory);
3328
3081
  }
3329
3082
  }
3330
3083
  publishPublicKey(name, publicKeyPem).catch(
@@ -3351,14 +3104,14 @@ The human who planted you described you as: "${body.description}"
3351
3104
  ...skillWarnings.length > 0 && { skillWarnings }
3352
3105
  });
3353
3106
  } catch (err) {
3354
- if (existsSync9(dest)) rmSync3(dest, { recursive: true, force: true });
3107
+ if (existsSync10(dest)) rmSync4(dest, { recursive: true, force: true });
3355
3108
  try {
3356
3109
  removeMind(name);
3357
3110
  } catch {
3358
3111
  }
3359
3112
  return c.json({ error: err instanceof Error ? err.message : "Failed to create mind" }, 500);
3360
3113
  } finally {
3361
- rmSync3(composedDir, { recursive: true, force: true });
3114
+ rmSync4(composedDir, { recursive: true, force: true });
3362
3115
  }
3363
3116
  }).post("/import", requireAdmin, async (c) => {
3364
3117
  let body;
@@ -3371,13 +3124,13 @@ The human who planted you described you as: "${body.description}"
3371
3124
  return importFromArchive(c, body.archivePath, body.name, body.manifest);
3372
3125
  }
3373
3126
  const wsDir = body.workspacePath;
3374
- if (!wsDir || !existsSync9(resolve11(wsDir, "SOUL.md")) || !existsSync9(resolve11(wsDir, "IDENTITY.md"))) {
3127
+ if (!wsDir || !existsSync10(resolve12(wsDir, "SOUL.md")) || !existsSync10(resolve12(wsDir, "IDENTITY.md"))) {
3375
3128
  return c.json({ error: "Invalid workspace: missing SOUL.md or IDENTITY.md" }, 400);
3376
3129
  }
3377
- const soul = readFileSync8(resolve11(wsDir, "SOUL.md"), "utf-8");
3378
- const identity = readFileSync8(resolve11(wsDir, "IDENTITY.md"), "utf-8");
3379
- const userPath = resolve11(wsDir, "USER.md");
3380
- const user = existsSync9(userPath) ? readFileSync8(userPath, "utf-8") : "";
3130
+ const soul = readFileSync9(resolve12(wsDir, "SOUL.md"), "utf-8");
3131
+ const identity = readFileSync9(resolve12(wsDir, "IDENTITY.md"), "utf-8");
3132
+ const userPath = resolve12(wsDir, "USER.md");
3133
+ const user = existsSync10(userPath) ? readFileSync9(userPath, "utf-8") : "";
3381
3134
  const name = body.name ?? parseNameFromIdentity(identity) ?? "imported-mind";
3382
3135
  const template = body.template ?? "claude";
3383
3136
  const nameErr = validateMindName(name);
@@ -3397,33 +3150,33 @@ ${user.trimEnd()}
3397
3150
  ` : "";
3398
3151
  ensureVoluteHome();
3399
3152
  const dest = mindDir(name);
3400
- if (existsSync9(dest)) return c.json({ error: "Mind directory already exists" }, 409);
3153
+ if (existsSync10(dest)) return c.json({ error: "Mind directory already exists" }, 409);
3401
3154
  const templatesRoot = findTemplatesRoot();
3402
3155
  const { composedDir, manifest } = composeTemplate(templatesRoot, template);
3403
3156
  try {
3404
3157
  copyTemplateToDir(composedDir, dest, name, manifest);
3405
3158
  applyInitFiles(dest);
3406
3159
  const { publicKeyPem: importPublicKey } = generateIdentity(dest);
3407
- writeFileSync6(resolve11(dest, "home/SOUL.md"), mergedSoul);
3408
- const wsMemoryPath = resolve11(wsDir, "MEMORY.md");
3409
- const hasMemory = existsSync9(wsMemoryPath);
3160
+ writeFileSync7(resolve12(dest, "home/SOUL.md"), mergedSoul);
3161
+ const wsMemoryPath = resolve12(wsDir, "MEMORY.md");
3162
+ const hasMemory = existsSync10(wsMemoryPath);
3410
3163
  if (hasMemory) {
3411
- const existingMemory = readFileSync8(wsMemoryPath, "utf-8");
3412
- writeFileSync6(
3413
- resolve11(dest, "home/MEMORY.md"),
3164
+ const existingMemory = readFileSync9(wsMemoryPath, "utf-8");
3165
+ writeFileSync7(
3166
+ resolve12(dest, "home/MEMORY.md"),
3414
3167
  `${existingMemory.trimEnd()}${mergedMemoryExtra}`
3415
3168
  );
3416
3169
  } else if (user) {
3417
- writeFileSync6(resolve11(dest, "home/MEMORY.md"), `${user.trimEnd()}
3170
+ writeFileSync7(resolve12(dest, "home/MEMORY.md"), `${user.trimEnd()}
3418
3171
  `);
3419
3172
  }
3420
- const wsMemoryDir = resolve11(wsDir, "memory");
3173
+ const wsMemoryDir = resolve12(wsDir, "memory");
3421
3174
  let dailyLogCount = 0;
3422
- if (existsSync9(wsMemoryDir)) {
3423
- const destMemoryDir = resolve11(dest, "home/memory");
3175
+ if (existsSync10(wsMemoryDir)) {
3176
+ const destMemoryDir = resolve12(dest, "home/memory");
3424
3177
  const files = readdirSync4(wsMemoryDir).filter((f) => f.endsWith(".md"));
3425
3178
  for (const file of files) {
3426
- cpSync(resolve11(wsMemoryDir, file), resolve11(destMemoryDir, file));
3179
+ cpSync(resolve12(wsMemoryDir, file), resolve12(destMemoryDir, file));
3427
3180
  }
3428
3181
  dailyLogCount = files.length;
3429
3182
  }
@@ -3434,7 +3187,7 @@ ${user.trimEnd()}
3434
3187
  } catch (err) {
3435
3188
  logger_default.warn(`failed to set template hash for ${name}`, logger_default.errorData(err));
3436
3189
  }
3437
- const homeDir = resolve11(dest, "home");
3190
+ const homeDir = resolve12(dest, "home");
3438
3191
  ensureVoluteGroup();
3439
3192
  createMindUser(name, homeDir);
3440
3193
  chownMindDir(dest, name);
@@ -3442,20 +3195,20 @@ ${user.trimEnd()}
3442
3195
  if (!hasMemory && dailyLogCount > 0) {
3443
3196
  await consolidateMemory(dest);
3444
3197
  }
3445
- const env = isIsolationEnabled() ? { ...process.env, HOME: resolve11(dest, "home") } : void 0;
3198
+ const env = isIsolationEnabled() ? { ...process.env, HOME: resolve12(dest, "home") } : void 0;
3446
3199
  await gitExec(["init"], { cwd: dest, mindName: name, env });
3447
3200
  await configureGitIdentity(name, { cwd: dest, mindName: name, env });
3448
3201
  await gitExec(["add", "-A"], { cwd: dest, mindName: name, env });
3449
3202
  await gitExec(["commit", "-m", "import from OpenClaw"], { cwd: dest, mindName: name, env });
3450
- const sessionFile = body.sessionPath ? resolve11(body.sessionPath) : findOpenClawSession(wsDir);
3451
- if (sessionFile && existsSync9(sessionFile)) {
3203
+ const sessionFile = body.sessionPath ? resolve12(body.sessionPath) : findOpenClawSession(wsDir);
3204
+ if (sessionFile && existsSync10(sessionFile)) {
3452
3205
  if (template === "pi") {
3453
3206
  importPiSession(sessionFile, dest);
3454
3207
  } else if (template === "claude") {
3455
3208
  const sessionId = convertSession({ sessionPath: sessionFile, projectDir: dest });
3456
- const mindRuntimeDir = resolve11(dest, ".mind");
3457
- mkdirSync5(mindRuntimeDir, { recursive: true });
3458
- writeFileSync6(resolve11(mindRuntimeDir, "session.json"), JSON.stringify({ sessionId }));
3209
+ const mindRuntimeDir = resolve12(dest, ".mind");
3210
+ mkdirSync6(mindRuntimeDir, { recursive: true });
3211
+ writeFileSync7(resolve12(mindRuntimeDir, "session.json"), JSON.stringify({ sessionId }));
3459
3212
  }
3460
3213
  }
3461
3214
  importOpenClawConnectors(name, dest);
@@ -3470,14 +3223,14 @@ ${user.trimEnd()}
3470
3223
  );
3471
3224
  return c.json({ ok: true, name, port, message: `Imported mind: ${name} (port ${port})` });
3472
3225
  } catch (err) {
3473
- if (existsSync9(dest)) rmSync3(dest, { recursive: true, force: true });
3226
+ if (existsSync10(dest)) rmSync4(dest, { recursive: true, force: true });
3474
3227
  try {
3475
3228
  removeMind(name);
3476
3229
  } catch {
3477
3230
  }
3478
3231
  return c.json({ error: err instanceof Error ? err.message : "Failed to import mind" }, 500);
3479
3232
  } finally {
3480
- rmSync3(composedDir, { recursive: true, force: true });
3233
+ rmSync4(composedDir, { recursive: true, force: true });
3481
3234
  }
3482
3235
  }).get("/", async (c) => {
3483
3236
  const entries = readRegistry();
@@ -3486,7 +3239,7 @@ ${user.trimEnd()}
3486
3239
  const db = await getDb();
3487
3240
  const lastActiveRows = await db.select({
3488
3241
  mind: mindHistory.mind,
3489
- lastActiveAt: sql2`MAX(${mindHistory.created_at})`
3242
+ lastActiveAt: sql`MAX(${mindHistory.created_at})`
3490
3243
  }).from(mindHistory).groupBy(mindHistory.mind);
3491
3244
  lastActiveMap = new Map(lastActiveRows.map((r) => [r.mind, r.lastActiveAt]));
3492
3245
  } catch {
@@ -3494,7 +3247,7 @@ ${user.trimEnd()}
3494
3247
  const minds = await Promise.all(
3495
3248
  entries.map(async (entry) => {
3496
3249
  const mindStatus = await getMindStatus(entry.name, entry.port);
3497
- const hasPages = existsSync9(resolve11(mindDir(entry.name), "home", "pages"));
3250
+ const hasPages = existsSync10(resolve12(mindDir(entry.name), "home", "pages"));
3498
3251
  return {
3499
3252
  ...entry,
3500
3253
  ...mindStatus,
@@ -3512,7 +3265,7 @@ ${user.trimEnd()}
3512
3265
  const name = c.req.param("name");
3513
3266
  const entry = findMind(name);
3514
3267
  if (!entry) return c.json({ error: "Mind not found" }, 404);
3515
- if (!existsSync9(mindDir(name))) return c.json({ error: "Mind directory missing" }, 404);
3268
+ if (!existsSync10(mindDir(name))) return c.json({ error: "Mind directory missing" }, 404);
3516
3269
  const mindStatus = await getMindStatus(name, entry.port);
3517
3270
  const variants = readVariants(name);
3518
3271
  const manager = getMindManager();
@@ -3527,7 +3280,7 @@ ${user.trimEnd()}
3527
3280
  return { name: v.name, port: v.port, status: variantStatus };
3528
3281
  })
3529
3282
  );
3530
- const hasPages = existsSync9(resolve11(mindDir(name), "home", "pages"));
3283
+ const hasPages = existsSync10(resolve12(mindDir(name), "home", "pages"));
3531
3284
  return c.json({ ...entry, ...mindStatus, variants: variantStatuses, hasPages });
3532
3285
  }).post("/:name/start", requireAdmin, async (c) => {
3533
3286
  const name = c.req.param("name");
@@ -3541,7 +3294,7 @@ ${user.trimEnd()}
3541
3294
  targetPort = variant.port;
3542
3295
  } else {
3543
3296
  const dir = mindDir(baseName);
3544
- if (!existsSync9(dir)) return c.json({ error: "Mind directory missing" }, 404);
3297
+ if (!existsSync10(dir)) return c.json({ error: "Mind directory missing" }, 404);
3545
3298
  }
3546
3299
  if (getMindManager().isRunning(name)) {
3547
3300
  return c.json({ error: "Mind already running" }, 409);
@@ -3564,7 +3317,7 @@ ${user.trimEnd()}
3564
3317
  targetPort = variant.port;
3565
3318
  } else {
3566
3319
  const dir = mindDir(baseName);
3567
- if (!existsSync9(dir)) return c.json({ error: "Mind directory missing" }, 404);
3320
+ if (!existsSync10(dir)) return c.json({ error: "Mind directory missing" }, 404);
3568
3321
  }
3569
3322
  let context;
3570
3323
  const contentType = c.req.header("content-type");
@@ -3591,7 +3344,7 @@ ${user.trimEnd()}
3591
3344
  const variant = findVariant(baseName, mergeVariantName);
3592
3345
  if (variant) {
3593
3346
  const projectRoot = mindDir(baseName);
3594
- if (existsSync9(variant.path)) {
3347
+ if (existsSync10(variant.path)) {
3595
3348
  const status = (await gitExec(["status", "--porcelain"], { cwd: variant.path })).trim();
3596
3349
  if (status) {
3597
3350
  try {
@@ -3633,7 +3386,7 @@ ${user.trimEnd()}
3633
3386
  if (context?.type === "sprouted" && !variantName) {
3634
3387
  try {
3635
3388
  const db = await getDb();
3636
- const activeConvs = await db.select({ id: conversations.id }).from(conversations).where(eq4(conversations.mind_name, baseName)).all();
3389
+ const activeConvs = await db.select({ id: conversations.id }).from(conversations).where(eq2(conversations.mind_name, baseName)).all();
3637
3390
  for (const conv of activeConvs) {
3638
3391
  await addMessage(conv.id, "assistant", "system", [
3639
3392
  { type: "text", text: "[seed has sprouted]" }
@@ -3671,7 +3424,7 @@ ${user.trimEnd()}
3671
3424
  const name = c.req.param("name");
3672
3425
  const entry = findMind(name);
3673
3426
  if (!entry) return c.json({ error: "Mind not found" }, 404);
3674
- const { getSleepManagerIfReady } = await import("./sleep-manager-2TMQ65E4.js");
3427
+ const { getSleepManagerIfReady } = await import("./sleep-manager-XXSWQQLE.js");
3675
3428
  const sm = getSleepManagerIfReady();
3676
3429
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
3677
3430
  return c.json(sm.getState(name));
@@ -3679,7 +3432,7 @@ ${user.trimEnd()}
3679
3432
  const name = c.req.param("name");
3680
3433
  const entry = findMind(name);
3681
3434
  if (!entry) return c.json({ error: "Mind not found" }, 404);
3682
- const { getSleepManagerIfReady } = await import("./sleep-manager-2TMQ65E4.js");
3435
+ const { getSleepManagerIfReady } = await import("./sleep-manager-XXSWQQLE.js");
3683
3436
  const sm = getSleepManagerIfReady();
3684
3437
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
3685
3438
  if (sm.isSleeping(name)) return c.json({ error: "Mind is already sleeping" }, 409);
@@ -3699,7 +3452,7 @@ ${user.trimEnd()}
3699
3452
  const name = c.req.param("name");
3700
3453
  const entry = findMind(name);
3701
3454
  if (!entry) return c.json({ error: "Mind not found" }, 404);
3702
- const { getSleepManagerIfReady } = await import("./sleep-manager-2TMQ65E4.js");
3455
+ const { getSleepManagerIfReady } = await import("./sleep-manager-XXSWQQLE.js");
3703
3456
  const sm = getSleepManagerIfReady();
3704
3457
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
3705
3458
  if (!sm.isSleeping(name)) return c.json({ error: "Mind is not sleeping" }, 409);
@@ -3709,7 +3462,7 @@ ${user.trimEnd()}
3709
3462
  const name = c.req.param("name");
3710
3463
  const entry = findMind(name);
3711
3464
  if (!entry) return c.json({ error: "Mind not found" }, 404);
3712
- const { getSleepManagerIfReady } = await import("./sleep-manager-2TMQ65E4.js");
3465
+ const { getSleepManagerIfReady } = await import("./sleep-manager-XXSWQQLE.js");
3713
3466
  const sm = getSleepManagerIfReady();
3714
3467
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
3715
3468
  const flushed = await sm.flushQueuedMessages(name);
@@ -3742,11 +3495,11 @@ ${user.trimEnd()}
3742
3495
  removeMind(name);
3743
3496
  await deleteMindUser2(name);
3744
3497
  const state = stateDir(name);
3745
- if (existsSync9(state)) {
3746
- rmSync3(state, { recursive: true, force: true });
3498
+ if (existsSync10(state)) {
3499
+ rmSync4(state, { recursive: true, force: true });
3747
3500
  }
3748
- if (force && existsSync9(dir)) {
3749
- rmSync3(dir, { recursive: true, force: true });
3501
+ if (force && existsSync10(dir)) {
3502
+ rmSync4(dir, { recursive: true, force: true });
3750
3503
  deleteMindUser(name);
3751
3504
  }
3752
3505
  fireWebhook({
@@ -3760,7 +3513,7 @@ ${user.trimEnd()}
3760
3513
  const entry = findMind(mindName);
3761
3514
  if (!entry) return c.json({ error: "Mind not found" }, 404);
3762
3515
  const dir = mindDir(mindName);
3763
- if (!existsSync9(dir)) return c.json({ error: "Mind directory missing" }, 404);
3516
+ if (!existsSync10(dir)) return c.json({ error: "Mind directory missing" }, 404);
3764
3517
  let body = {};
3765
3518
  try {
3766
3519
  body = await c.req.json();
@@ -3769,15 +3522,15 @@ ${user.trimEnd()}
3769
3522
  const template = body.template ?? entry.template ?? "claude";
3770
3523
  const UPGRADE_VARIANT = "upgrade";
3771
3524
  if (body.abort) {
3772
- const worktreeDir2 = resolve11(dir, ".variants", UPGRADE_VARIANT);
3773
- if (!existsSync9(worktreeDir2)) {
3525
+ const worktreeDir2 = resolve12(dir, ".variants", UPGRADE_VARIANT);
3526
+ if (!existsSync10(worktreeDir2)) {
3774
3527
  return c.json({ error: "No upgrade in progress" }, 400);
3775
3528
  }
3776
3529
  try {
3777
3530
  try {
3778
- const gitDirContent = readFileSync8(resolve11(worktreeDir2, ".git"), "utf-8").trim();
3531
+ const gitDirContent = readFileSync9(resolve12(worktreeDir2, ".git"), "utf-8").trim();
3779
3532
  const gitDir = gitDirContent.replace("gitdir: ", "");
3780
- if (existsSync9(resolve11(gitDir, "MERGE_HEAD"))) {
3533
+ if (existsSync10(resolve12(gitDir, "MERGE_HEAD"))) {
3781
3534
  await gitExec(["merge", "--abort"], { cwd: worktreeDir2 });
3782
3535
  }
3783
3536
  } catch {
@@ -3792,8 +3545,8 @@ ${user.trimEnd()}
3792
3545
  }
3793
3546
  }
3794
3547
  if (body.continue) {
3795
- const worktreeDir2 = resolve11(dir, ".variants", UPGRADE_VARIANT);
3796
- if (!existsSync9(worktreeDir2)) {
3548
+ const worktreeDir2 = resolve12(dir, ".variants", UPGRADE_VARIANT);
3549
+ if (!existsSync10(worktreeDir2)) {
3797
3550
  return c.json({ error: "No upgrade in progress" }, 400);
3798
3551
  }
3799
3552
  const status = await gitExec(["status", "--porcelain"], { cwd: worktreeDir2 });
@@ -3837,23 +3590,23 @@ ${user.trimEnd()}
3837
3590
  );
3838
3591
  }
3839
3592
  }
3840
- const worktreeDir = resolve11(dir, ".variants", UPGRADE_VARIANT);
3841
- if (existsSync9(worktreeDir)) {
3593
+ const worktreeDir = resolve12(dir, ".variants", UPGRADE_VARIANT);
3594
+ if (existsSync10(worktreeDir)) {
3842
3595
  return c.json(
3843
3596
  { error: "Upgrade variant already exists. Use continue or delete it first." },
3844
3597
  409
3845
3598
  );
3846
3599
  }
3847
- if (!existsSync9(resolve11(dir, ".git"))) {
3600
+ if (!existsSync10(resolve12(dir, ".git"))) {
3848
3601
  try {
3849
- const env = isIsolationEnabled() ? { ...process.env, HOME: resolve11(dir, "home") } : void 0;
3602
+ const env = isIsolationEnabled() ? { ...process.env, HOME: resolve12(dir, "home") } : void 0;
3850
3603
  await gitExec(["init"], { cwd: dir, mindName, env });
3851
3604
  await configureGitIdentity(mindName, { cwd: dir, mindName, env });
3852
3605
  await gitExec(["add", "-A"], { cwd: dir, mindName, env });
3853
3606
  await gitExec(["commit", "-m", "initial commit"], { cwd: dir, mindName, env });
3854
3607
  chownMindDir(dir, mindName);
3855
3608
  } catch (err) {
3856
- rmSync3(resolve11(dir, ".git"), { recursive: true, force: true });
3609
+ rmSync4(resolve12(dir, ".git"), { recursive: true, force: true });
3857
3610
  return c.json(
3858
3611
  {
3859
3612
  error: `Git initialization failed: ${err instanceof Error ? err.message : String(err)}`
@@ -3867,7 +3620,7 @@ ${user.trimEnd()}
3867
3620
  await gitExec(["branch", "-D", UPGRADE_VARIANT], { cwd: dir });
3868
3621
  } catch {
3869
3622
  }
3870
- if (!existsSync9(resolve11(dir, "home", "shared"))) {
3623
+ if (!existsSync10(resolve12(dir, "home", "shared"))) {
3871
3624
  try {
3872
3625
  await addSharedWorktree(mindName, dir);
3873
3626
  } catch (err) {
@@ -3878,9 +3631,9 @@ ${user.trimEnd()}
3878
3631
  }
3879
3632
  }
3880
3633
  await updateTemplateBranch(dir, template, mindName);
3881
- const parentDir = resolve11(dir, ".variants");
3882
- if (!existsSync9(parentDir)) {
3883
- mkdirSync5(parentDir, { recursive: true });
3634
+ const parentDir = resolve12(dir, ".variants");
3635
+ if (!existsSync10(parentDir)) {
3636
+ mkdirSync6(parentDir, { recursive: true });
3884
3637
  }
3885
3638
  await gitExec(["worktree", "add", "-b", UPGRADE_VARIANT, worktreeDir], { cwd: dir });
3886
3639
  const hasConflicts = await mergeTemplateBranch(worktreeDir);
@@ -3927,7 +3680,7 @@ ${user.trimEnd()}
3927
3680
  if (!variant) return c.json({ error: `Unknown variant: ${variantName}` }, 404);
3928
3681
  }
3929
3682
  try {
3930
- const { getSleepManagerIfReady } = await import("./sleep-manager-2TMQ65E4.js");
3683
+ const { getSleepManagerIfReady } = await import("./sleep-manager-XXSWQQLE.js");
3931
3684
  const sm = getSleepManagerIfReady();
3932
3685
  if (sm?.isSleeping(baseName)) {
3933
3686
  const body2 = await c.req.text();
@@ -4025,7 +3778,7 @@ ${user.trimEnd()}
4025
3778
  if (seedEntry?.stage === "seed") {
4026
3779
  try {
4027
3780
  const db = await getDb();
4028
- const countResult = await db.select({ count: sql2`count(*)` }).from(mindHistory).where(eq4(mindHistory.mind, baseName));
3781
+ const countResult = await db.select({ count: sql`count(*)` }).from(mindHistory).where(eq2(mindHistory.mind, baseName));
4029
3782
  const msgCount = countResult[0]?.count ?? 0;
4030
3783
  if (msgCount >= 10 && msgCount % 10 === 0) {
4031
3784
  const nudge = "\n[You've been exploring for a while. Whenever you feel ready, write your SOUL.md and MEMORY.md, then run volute mind sprout.]";
@@ -4070,13 +3823,13 @@ ${user.trimEnd()}
4070
3823
  const entry = findMind(name);
4071
3824
  if (!entry) return c.json({ error: "Mind not found" }, 404);
4072
3825
  const dir = mindDir(name);
4073
- if (!existsSync9(dir)) return c.json({ error: "Mind directory missing" }, 404);
3826
+ if (!existsSync10(dir)) return c.json({ error: "Mind directory missing" }, 404);
4074
3827
  let config = readVoluteConfig(dir);
4075
3828
  if (!config && entry.template === "pi") {
4076
- const piConfigPath = resolve11(dir, "home/.config/config.json");
4077
- if (existsSync9(piConfigPath)) {
3829
+ const piConfigPath = resolve12(dir, "home/.config/config.json");
3830
+ if (existsSync10(piConfigPath)) {
4078
3831
  try {
4079
- config = JSON.parse(readFileSync8(piConfigPath, "utf-8"));
3832
+ config = JSON.parse(readFileSync9(piConfigPath, "utf-8"));
4080
3833
  } catch {
4081
3834
  }
4082
3835
  }
@@ -4113,7 +3866,7 @@ ${user.trimEnd()}
4113
3866
  const entry = findMind(name);
4114
3867
  if (!entry) return c.json({ error: "Mind not found" }, 404);
4115
3868
  const dir = mindDir(name);
4116
- if (!existsSync9(dir)) return c.json({ error: "Mind directory missing" }, 404);
3869
+ if (!existsSync10(dir)) return c.json({ error: "Mind directory missing" }, 404);
4117
3870
  const body = c.req.valid("json");
4118
3871
  const existing = readVoluteConfig(dir) ?? {};
4119
3872
  if (body.model !== void 0) existing.model = body.model;
@@ -4294,22 +4047,22 @@ ${user.trimEnd()}
4294
4047
  const db = await getDb();
4295
4048
  const rows = await db.select({
4296
4049
  session: mindHistory.session,
4297
- started_at: sql2`MIN(${mindHistory.created_at})`,
4298
- event_count: sql2`COUNT(*)`,
4299
- message_count: sql2`SUM(CASE WHEN ${mindHistory.type} IN ('inbound','outbound') THEN 1 ELSE 0 END)`,
4300
- tool_count: sql2`SUM(CASE WHEN ${mindHistory.type}='tool_use' THEN 1 ELSE 0 END)`
4301
- }).from(mindHistory).where(and3(eq4(mindHistory.mind, name), sql2`${mindHistory.session} IS NOT NULL`)).groupBy(mindHistory.session).orderBy(sql2`MIN(${mindHistory.created_at}) DESC`);
4050
+ started_at: sql`MIN(${mindHistory.created_at})`,
4051
+ event_count: sql`COUNT(*)`,
4052
+ message_count: sql`SUM(CASE WHEN ${mindHistory.type} IN ('inbound','outbound') THEN 1 ELSE 0 END)`,
4053
+ tool_count: sql`SUM(CASE WHEN ${mindHistory.type}='tool_use' THEN 1 ELSE 0 END)`
4054
+ }).from(mindHistory).where(and(eq2(mindHistory.mind, name), sql`${mindHistory.session} IS NOT NULL`)).groupBy(mindHistory.session).orderBy(sql`MIN(${mindHistory.created_at}) DESC`);
4302
4055
  return c.json(rows);
4303
4056
  }).get("/:name/history/channels", async (c) => {
4304
4057
  const name = c.req.param("name");
4305
4058
  const db = await getDb();
4306
- const rows = await db.selectDistinct({ channel: mindHistory.channel }).from(mindHistory).where(eq4(mindHistory.mind, name));
4059
+ const rows = await db.selectDistinct({ channel: mindHistory.channel }).from(mindHistory).where(eq2(mindHistory.mind, name));
4307
4060
  return c.json(rows.map((r) => r.channel));
4308
4061
  }).get("/:name/history/export", async (c) => {
4309
4062
  const name = c.req.param("name");
4310
4063
  if (!findMind(name)) return c.json({ error: "Mind not found" }, 404);
4311
4064
  const db = await getDb();
4312
- const rows = await db.select().from(mindHistory).where(eq4(mindHistory.mind, name));
4065
+ const rows = await db.select().from(mindHistory).where(eq2(mindHistory.mind, name));
4313
4066
  return c.json(rows);
4314
4067
  }).get("/:name/history", async (c) => {
4315
4068
  const name = c.req.param("name");
@@ -4319,24 +4072,24 @@ ${user.trimEnd()}
4319
4072
  const limit = Math.min(Math.max(parseInt(c.req.query("limit") ?? "50", 10) || 50, 1), 200);
4320
4073
  const offset = Math.max(parseInt(c.req.query("offset") ?? "0", 10) || 0, 0);
4321
4074
  const db = await getDb();
4322
- const conditions = [eq4(mindHistory.mind, name)];
4075
+ const conditions = [eq2(mindHistory.mind, name)];
4323
4076
  if (channel) {
4324
- conditions.push(eq4(mindHistory.channel, channel));
4077
+ conditions.push(eq2(mindHistory.channel, channel));
4325
4078
  }
4326
4079
  if (session) {
4327
- conditions.push(eq4(mindHistory.session, session));
4080
+ conditions.push(eq2(mindHistory.session, session));
4328
4081
  }
4329
4082
  if (!full) {
4330
- conditions.push(sql2`${mindHistory.type} IN ('inbound', 'outbound')`);
4083
+ conditions.push(sql`${mindHistory.type} IN ('inbound', 'outbound')`);
4331
4084
  }
4332
- const rows = await db.select().from(mindHistory).where(and3(...conditions)).orderBy(desc3(mindHistory.created_at)).limit(limit).offset(offset);
4085
+ const rows = await db.select().from(mindHistory).where(and(...conditions)).orderBy(desc2(mindHistory.created_at)).limit(limit).offset(offset);
4333
4086
  return c.json(rows);
4334
4087
  });
4335
4088
  var minds_default = app11;
4336
4089
 
4337
4090
  // src/web/api/pages.ts
4338
4091
  import { readFile as readFile2, stat as stat2 } from "fs/promises";
4339
- import { extname as extname2, resolve as resolve12 } from "path";
4092
+ import { extname as extname3, resolve as resolve13 } from "path";
4340
4093
  import { Hono as Hono12 } from "hono";
4341
4094
  var MIME_TYPES = {
4342
4095
  ".html": "text/html",
@@ -4358,17 +4111,17 @@ var app12 = new Hono12().get("/:name/*", async (c) => {
4358
4111
  const name = c.req.param("name");
4359
4112
  let pagesRoot;
4360
4113
  if (name === "_system") {
4361
- pagesRoot = resolve12(voluteHome(), "shared", "pages");
4114
+ pagesRoot = resolve13(voluteHome(), "shared", "pages");
4362
4115
  } else {
4363
4116
  if (!findMind(name)) return c.text("Not found", 404);
4364
- pagesRoot = resolve12(mindDir(name), "home", "pages");
4117
+ pagesRoot = resolve13(mindDir(name), "home", "pages");
4365
4118
  }
4366
4119
  const wildcard = c.req.path.replace(`/pages/${name}`, "") || "/";
4367
- const requestedPath = resolve12(pagesRoot, wildcard.slice(1));
4120
+ const requestedPath = resolve13(pagesRoot, wildcard.slice(1));
4368
4121
  if (!requestedPath.startsWith(pagesRoot)) return c.text("Forbidden", 403);
4369
4122
  let fileStat = await stat2(requestedPath).catch(() => null);
4370
4123
  if (fileStat?.isDirectory()) {
4371
- const indexPath = resolve12(requestedPath, "index.html");
4124
+ const indexPath = resolve13(requestedPath, "index.html");
4372
4125
  fileStat = await stat2(indexPath).catch(() => null);
4373
4126
  if (fileStat?.isFile()) {
4374
4127
  const body = await readFile2(indexPath);
@@ -4377,7 +4130,7 @@ var app12 = new Hono12().get("/:name/*", async (c) => {
4377
4130
  return c.text("Not found", 404);
4378
4131
  }
4379
4132
  if (fileStat?.isFile()) {
4380
- const ext = extname2(requestedPath);
4133
+ const ext = extname3(requestedPath);
4381
4134
  const mime = MIME_TYPES[ext] || "application/octet-stream";
4382
4135
  const body = await readFile2(requestedPath);
4383
4136
  return c.body(body, 200, { "Content-Type": mime });
@@ -4388,7 +4141,7 @@ var pages_default = app12;
4388
4141
 
4389
4142
  // src/web/api/prompts.ts
4390
4143
  import { zValidator as zValidator4 } from "@hono/zod-validator";
4391
- import { eq as eq5, sql as sql3 } from "drizzle-orm";
4144
+ import { eq as eq3, sql as sql2 } from "drizzle-orm";
4392
4145
  import { Hono as Hono13 } from "hono";
4393
4146
  import { z as z4 } from "zod";
4394
4147
  var app13 = new Hono13().get("/", async (c) => {
@@ -4421,9 +4174,9 @@ var app13 = new Hono13().get("/", async (c) => {
4421
4174
  }
4422
4175
  const { content } = c.req.valid("json");
4423
4176
  const db = await getDb();
4424
- await db.insert(systemPrompts).values({ key, content, updated_at: sql3`(datetime('now'))` }).onConflictDoUpdate({
4177
+ await db.insert(systemPrompts).values({ key, content, updated_at: sql2`(datetime('now'))` }).onConflictDoUpdate({
4425
4178
  target: systemPrompts.key,
4426
- set: { content, updated_at: sql3`(datetime('now'))` }
4179
+ set: { content, updated_at: sql2`(datetime('now'))` }
4427
4180
  });
4428
4181
  return c.json({ ok: true });
4429
4182
  }).delete("/:key", requireAdmin, async (c) => {
@@ -4432,7 +4185,7 @@ var app13 = new Hono13().get("/", async (c) => {
4432
4185
  return c.json({ error: "Unknown prompt key" }, 404);
4433
4186
  }
4434
4187
  const db = await getDb();
4435
- await db.delete(systemPrompts).where(eq5(systemPrompts.key, key));
4188
+ await db.delete(systemPrompts).where(eq3(systemPrompts.key, key));
4436
4189
  return c.json({ ok: true });
4437
4190
  });
4438
4191
  var prompts_default = app13;
@@ -4615,9 +4368,9 @@ var app15 = new Hono15().post("/:name/shared/merge", requireAdmin, async (c) =>
4615
4368
  var shared_default = app15;
4616
4369
 
4617
4370
  // src/web/api/skills.ts
4618
- import { existsSync as existsSync10, mkdtempSync, readdirSync as readdirSync5, rmSync as rmSync4 } from "fs";
4371
+ import { existsSync as existsSync11, mkdtempSync, readdirSync as readdirSync5, rmSync as rmSync5 } from "fs";
4619
4372
  import { tmpdir } from "os";
4620
- import { join as join2, resolve as resolve13 } from "path";
4373
+ import { join as join2, resolve as resolve14 } from "path";
4621
4374
  import AdmZip from "adm-zip";
4622
4375
  import { Hono as Hono16 } from "hono";
4623
4376
  var app16 = new Hono16().get("/", async (c) => {
@@ -4644,19 +4397,19 @@ var app16 = new Hono16().get("/", async (c) => {
4644
4397
  try {
4645
4398
  const zip = new AdmZip(buffer2);
4646
4399
  for (const entry of zip.getEntries()) {
4647
- const target = resolve13(tmpDir, entry.entryName);
4400
+ const target = resolve14(tmpDir, entry.entryName);
4648
4401
  if (!target.startsWith(tmpDir)) {
4649
4402
  return c.json({ error: "Invalid zip: paths must not escape archive" }, 400);
4650
4403
  }
4651
4404
  }
4652
4405
  zip.extractAllTo(tmpDir, true);
4653
4406
  let skillDir = null;
4654
- if (existsSync10(join2(tmpDir, "SKILL.md"))) {
4407
+ if (existsSync11(join2(tmpDir, "SKILL.md"))) {
4655
4408
  skillDir = tmpDir;
4656
4409
  } else {
4657
4410
  const entries = readdirSync5(tmpDir, { withFileTypes: true }).filter((e) => e.isDirectory());
4658
4411
  for (const entry of entries) {
4659
- if (existsSync10(join2(tmpDir, entry.name, "SKILL.md"))) {
4412
+ if (existsSync11(join2(tmpDir, entry.name, "SKILL.md"))) {
4660
4413
  skillDir = join2(tmpDir, entry.name);
4661
4414
  break;
4662
4415
  }
@@ -4673,7 +4426,7 @@ var app16 = new Hono16().get("/", async (c) => {
4673
4426
  }
4674
4427
  throw e;
4675
4428
  } finally {
4676
- rmSync4(tmpDir, { recursive: true, force: true });
4429
+ rmSync5(tmpDir, { recursive: true, force: true });
4677
4430
  }
4678
4431
  }).delete("/:id", requireAdmin, async (c) => {
4679
4432
  const id = c.req.param("id");
@@ -4707,10 +4460,10 @@ var app17 = new Hono17().post("/restart", requireAdmin, (c) => {
4707
4460
  stream.writeSSE({ data: JSON.stringify(entry) }).catch(() => {
4708
4461
  });
4709
4462
  });
4710
- await new Promise((resolve18) => {
4463
+ await new Promise((resolve19) => {
4711
4464
  stream.onAbort(() => {
4712
4465
  unsubscribe();
4713
- resolve18();
4466
+ resolve19();
4714
4467
  });
4715
4468
  });
4716
4469
  });
@@ -4785,8 +4538,8 @@ async function fanOutToMinds(opts) {
4785
4538
  const participantNames = participants.map((p) => p.username);
4786
4539
  const isDM = opts.isDM ?? participants.length === 2;
4787
4540
  const channelEntryType = opts.channelEntryType ?? (isDM ? "dm" : "group");
4788
- const { getMindManager: getMindManager2 } = await import("./mind-manager-KMY4GA2J.js");
4789
- const { getSleepManagerIfReady } = await import("./sleep-manager-2TMQ65E4.js");
4541
+ const { getMindManager: getMindManager2 } = await import("./mind-manager-ZNRIYEK3.js");
4542
+ const { getSleepManagerIfReady } = await import("./sleep-manager-XXSWQQLE.js");
4790
4543
  const manager = getMindManager2();
4791
4544
  const sm = getSleepManagerIfReady();
4792
4545
  const targetMinds = mindParticipants.map((ap) => {
@@ -4900,11 +4653,15 @@ var app20 = new Hono20().use("*", authMiddleware).post("/minds/:name/chat", zVal
4900
4653
  }
4901
4654
  }
4902
4655
  await addMessage(conversationId, "user", senderName, contentBlocks);
4656
+ const isDM = conv?.type === "dm";
4903
4657
  await fanOutToMinds({
4904
4658
  conversationId,
4905
4659
  contentBlocks,
4906
4660
  senderName,
4907
4661
  convTitle,
4662
+ isDM,
4663
+ channelEntryType: conv?.type === "channel" ? "group" : isDM ? "dm" : "group",
4664
+ slugExtra: conv ? { convType: conv.type, convName: conv.name } : void 0,
4908
4665
  targetName: (username) => username === baseName ? name : username
4909
4666
  });
4910
4667
  return c.json({ ok: true, conversationId });
@@ -4925,11 +4682,11 @@ var app20 = new Hono20().use("*", authMiddleware).post("/minds/:name/chat", zVal
4925
4682
  if (!stream.aborted) logger_default.error("[v1-chat] SSE ping error:", logger_default.errorData(err));
4926
4683
  });
4927
4684
  }, 15e3);
4928
- await new Promise((resolve18) => {
4685
+ await new Promise((resolve19) => {
4929
4686
  stream.onAbort(() => {
4930
4687
  unsubscribe();
4931
4688
  clearInterval(keepAlive);
4932
- resolve18();
4689
+ resolve19();
4933
4690
  });
4934
4691
  });
4935
4692
  });
@@ -5036,6 +4793,15 @@ var app21 = new Hono21().use("*", authMiddleware).get("/", async (c) => {
5036
4793
  participantIds: [...participantIds]
5037
4794
  });
5038
4795
  return c.json(conv, 201);
4796
+ }).post("/:id/read", async (c) => {
4797
+ const id = c.req.param("id");
4798
+ const user = c.get("user");
4799
+ if (user.id === 0) return c.json({ ok: true });
4800
+ if (!await isParticipantOrOwner(id, user.id)) {
4801
+ return c.json({ error: "Conversation not found" }, 404);
4802
+ }
4803
+ await markConversationRead(user.id, id);
4804
+ return c.json({ ok: true });
5039
4805
  }).delete("/:id", async (c) => {
5040
4806
  const id = c.req.param("id");
5041
4807
  const user = c.get("user");
@@ -5046,10 +4812,33 @@ var app21 = new Hono21().use("*", authMiddleware).get("/", async (c) => {
5046
4812
  var conversations_default = app21;
5047
4813
 
5048
4814
  // src/web/api/v1/events.ts
5049
- import { desc as desc4 } from "drizzle-orm";
4815
+ import { desc as desc3 } from "drizzle-orm";
5050
4816
  import { Hono as Hono22 } from "hono";
5051
4817
  import { streamSSE as streamSSE5 } from "hono/streaming";
5052
4818
 
4819
+ // src/lib/events/brain-presence.ts
4820
+ var connections = /* @__PURE__ */ new Map();
4821
+ function addConnection(username) {
4822
+ const count = connections.get(username) ?? 0;
4823
+ connections.set(username, count + 1);
4824
+ if (count === 0) {
4825
+ broadcast({ type: "brain_online", mind: username, summary: `${username} connected` });
4826
+ }
4827
+ }
4828
+ function removeConnection(username) {
4829
+ const count = connections.get(username);
4830
+ if (count == null) return;
4831
+ if (count <= 1) {
4832
+ connections.delete(username);
4833
+ broadcast({ type: "brain_offline", mind: username, summary: `${username} disconnected` });
4834
+ } else {
4835
+ connections.set(username, count - 1);
4836
+ }
4837
+ }
4838
+ function getOnlineBrains() {
4839
+ return [...connections.keys()];
4840
+ }
4841
+
5053
4842
  // src/lib/events/event-sequencer.ts
5054
4843
  var BUFFER_SIZE = 1e3;
5055
4844
  var MAX_AGE_MS = 5 * 60 * 1e3;
@@ -5077,6 +4866,10 @@ var app22 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
5077
4866
  const sinceId = since ? Number(since) : 0;
5078
4867
  return streamSSE5(c, async (stream) => {
5079
4868
  const cleanups = [];
4869
+ if (user.user_type === "brain") {
4870
+ addConnection(user.username);
4871
+ cleanups.push(() => removeConnection(user.username));
4872
+ }
5080
4873
  try {
5081
4874
  if (sinceId > 0) {
5082
4875
  const missed = getEventsSince(sinceId);
@@ -5090,7 +4883,7 @@ var app22 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
5090
4883
  let recentActivity = [];
5091
4884
  try {
5092
4885
  const db = await getDb();
5093
- recentActivity = await db.select().from(activity).orderBy(desc4(activity.created_at)).limit(50);
4886
+ recentActivity = await db.select().from(activity).orderBy(desc3(activity.created_at)).limit(50);
5094
4887
  recentActivity = recentActivity.map((row) => ({
5095
4888
  ...row,
5096
4889
  metadata: row.metadata ? JSON.parse(row.metadata) : null
@@ -5101,6 +4894,13 @@ var app22 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
5101
4894
  let conversations2 = [];
5102
4895
  try {
5103
4896
  conversations2 = await listConversationsWithParticipants(user.id);
4897
+ if (conversations2.length > 0) {
4898
+ const convIds = conversations2.map((c2) => c2.id);
4899
+ const unreads = await getUnreadCounts(user.id, convIds);
4900
+ for (const conv of conversations2) {
4901
+ conv.unreadCount = unreads[conv.id] ?? 0;
4902
+ }
4903
+ }
5104
4904
  } catch (err) {
5105
4905
  logger_default.error("[v1-events] failed to fetch conversations", logger_default.errorData(err));
5106
4906
  }
@@ -5112,7 +4912,8 @@ var app22 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
5112
4912
  conversations: conversations2,
5113
4913
  sites,
5114
4914
  recentPages,
5115
- activeMinds: getActiveMinds()
4915
+ activeMinds: getActiveMinds(),
4916
+ onlineBrains: getOnlineBrains()
5116
4917
  };
5117
4918
  const snapshotId = bufferEvent(snapshotData);
5118
4919
  await stream.writeSSE({
@@ -5153,8 +4954,8 @@ var app22 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
5153
4954
  });
5154
4955
  }, 15e3);
5155
4956
  cleanups.push(() => clearInterval(keepAlive));
5156
- await new Promise((resolve18) => {
5157
- stream.onAbort(() => resolve18());
4957
+ await new Promise((resolve19) => {
4958
+ stream.onAbort(() => resolve19());
5158
4959
  });
5159
4960
  } finally {
5160
4961
  for (const cleanup of cleanups) {
@@ -5169,16 +4970,16 @@ var app22 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
5169
4970
  var events_default = app22;
5170
4971
 
5171
4972
  // src/web/api/variants.ts
5172
- import { existsSync as existsSync11, mkdirSync as mkdirSync7, writeFileSync as writeFileSync7 } from "fs";
5173
- import { resolve as resolve15 } from "path";
4973
+ import { existsSync as existsSync12, mkdirSync as mkdirSync8, writeFileSync as writeFileSync8 } from "fs";
4974
+ import { resolve as resolve16 } from "path";
5174
4975
  import { Hono as Hono23 } from "hono";
5175
4976
 
5176
4977
  // src/lib/spawn-server.ts
5177
4978
  import { spawn as spawn3 } from "child_process";
5178
- import { closeSync, mkdirSync as mkdirSync6, openSync, readFileSync as readFileSync9 } from "fs";
5179
- import { resolve as resolve14 } from "path";
4979
+ import { closeSync, mkdirSync as mkdirSync7, openSync, readFileSync as readFileSync10 } from "fs";
4980
+ import { resolve as resolve15 } from "path";
5180
4981
  function tsxBin(cwd) {
5181
- return resolve14(cwd, "node_modules", ".bin", "tsx");
4982
+ return resolve15(cwd, "node_modules", ".bin", "tsx");
5182
4983
  }
5183
4984
  function spawnServer(cwd, port, options) {
5184
4985
  if (options?.detached) {
@@ -5191,31 +4992,31 @@ function spawnAttached(cwd, port) {
5191
4992
  cwd,
5192
4993
  stdio: ["ignore", "pipe", "pipe"]
5193
4994
  });
5194
- return new Promise((resolve18) => {
5195
- const timeout = setTimeout(() => resolve18(null), 3e4);
4995
+ return new Promise((resolve19) => {
4996
+ const timeout = setTimeout(() => resolve19(null), 3e4);
5196
4997
  function checkOutput(data) {
5197
4998
  const match = data.toString().match(/listening on :(\d+)/);
5198
4999
  if (match) {
5199
5000
  clearTimeout(timeout);
5200
- resolve18({ child, actualPort: parseInt(match[1], 10) });
5001
+ resolve19({ child, actualPort: parseInt(match[1], 10) });
5201
5002
  }
5202
5003
  }
5203
5004
  child.stdout?.on("data", checkOutput);
5204
5005
  child.stderr?.on("data", checkOutput);
5205
5006
  child.on("error", () => {
5206
5007
  clearTimeout(timeout);
5207
- resolve18(null);
5008
+ resolve19(null);
5208
5009
  });
5209
5010
  child.on("exit", () => {
5210
5011
  clearTimeout(timeout);
5211
- resolve18(null);
5012
+ resolve19(null);
5212
5013
  });
5213
5014
  });
5214
5015
  }
5215
5016
  function spawnDetached(cwd, port, logDir) {
5216
- const logsDir = logDir ?? resolve14(cwd, ".mind", "logs");
5217
- mkdirSync6(logsDir, { recursive: true });
5218
- const logPath = resolve14(logsDir, "mind.log");
5017
+ const logsDir = logDir ?? resolve15(cwd, ".mind", "logs");
5018
+ mkdirSync7(logsDir, { recursive: true });
5019
+ const logPath = resolve15(logsDir, "mind.log");
5219
5020
  const logFd = openSync(logPath, "a");
5220
5021
  const child = spawn3(tsxBin(cwd), ["src/server.ts", "--port", String(port)], {
5221
5022
  cwd,
@@ -5235,7 +5036,7 @@ function spawnDetached(cwd, port, logDir) {
5235
5036
  }
5236
5037
  const interval = setInterval(() => {
5237
5038
  try {
5238
- const content = readFileSync9(logPath, "utf-8");
5039
+ const content = readFileSync10(logPath, "utf-8");
5239
5040
  const match = content.match(/listening on :(\d+)/);
5240
5041
  if (match) {
5241
5042
  finish({ child, actualPort: parseInt(match[1], 10) });
@@ -5327,11 +5128,11 @@ var app23 = new Hono23().get("/:name/variants", async (c) => {
5327
5128
  const err = validateBranchName(variantName);
5328
5129
  if (err) return c.json({ error: err }, 400);
5329
5130
  const projectRoot = mindDir(mindName);
5330
- const variantDir = resolve15(projectRoot, ".variants", variantName);
5331
- if (existsSync11(variantDir)) {
5131
+ const variantDir = resolve16(projectRoot, ".variants", variantName);
5132
+ if (existsSync12(variantDir)) {
5332
5133
  return c.json({ error: `Variant directory already exists: ${variantDir}` }, 409);
5333
5134
  }
5334
- mkdirSync7(resolve15(projectRoot, ".variants"), { recursive: true });
5135
+ mkdirSync8(resolve16(projectRoot, ".variants"), { recursive: true });
5335
5136
  try {
5336
5137
  await gitExec(["worktree", "add", "-b", variantName, variantDir], { cwd: projectRoot });
5337
5138
  } catch (e) {
@@ -5344,7 +5145,7 @@ var app23 = new Hono23().get("/:name/variants", async (c) => {
5344
5145
  const [cmd, args] = wrapForIsolation("npm", ["install"], mindName);
5345
5146
  await exec(cmd, args, {
5346
5147
  cwd: variantDir,
5347
- env: { ...process.env, HOME: resolve15(variantDir, "home") }
5148
+ env: { ...process.env, HOME: resolve16(variantDir, "home") }
5348
5149
  });
5349
5150
  } else {
5350
5151
  await exec("npm", ["install"], { cwd: variantDir });
@@ -5354,7 +5155,7 @@ var app23 = new Hono23().get("/:name/variants", async (c) => {
5354
5155
  return c.json({ error: `npm install failed: ${msg}` }, 500);
5355
5156
  }
5356
5157
  if (body.soul) {
5357
- writeFileSync7(resolve15(variantDir, "home/SOUL.md"), body.soul);
5158
+ writeFileSync8(resolve16(variantDir, "home/SOUL.md"), body.soul);
5358
5159
  }
5359
5160
  const variantPort = body.port ?? nextPort();
5360
5161
  const variant = {
@@ -5392,7 +5193,7 @@ var app23 = new Hono23().get("/:name/variants", async (c) => {
5392
5193
  } catch {
5393
5194
  }
5394
5195
  const projectRoot = mindDir(mindName);
5395
- if (existsSync11(variant.path)) {
5196
+ if (existsSync12(variant.path)) {
5396
5197
  const status = (await gitExec(["status", "--porcelain"], { cwd: variant.path })).trim();
5397
5198
  if (status) {
5398
5199
  try {
@@ -5465,7 +5266,7 @@ var app23 = new Hono23().get("/:name/variants", async (c) => {
5465
5266
  const [cmd, args] = wrapForIsolation("npm", ["install"], mindName);
5466
5267
  await exec(cmd, args, {
5467
5268
  cwd: projectRoot,
5468
- env: { ...process.env, HOME: resolve15(projectRoot, "home") }
5269
+ env: { ...process.env, HOME: resolve16(projectRoot, "home") }
5469
5270
  });
5470
5271
  } else {
5471
5272
  await exec("npm", ["install"], { cwd: projectRoot });
@@ -5592,8 +5393,8 @@ async function fanOutToMinds2(opts) {
5592
5393
  const participantNames = participants.map((p) => p.username);
5593
5394
  const isDM = opts.isDM ?? participants.length === 2;
5594
5395
  const channelEntryType = opts.channelEntryType ?? (isDM ? "dm" : "group");
5595
- const { getMindManager: getMindManager2 } = await import("./mind-manager-KMY4GA2J.js");
5596
- const { getSleepManagerIfReady } = await import("./sleep-manager-2TMQ65E4.js");
5396
+ const { getMindManager: getMindManager2 } = await import("./mind-manager-ZNRIYEK3.js");
5397
+ const { getSleepManagerIfReady } = await import("./sleep-manager-XXSWQQLE.js");
5597
5398
  const manager = getMindManager2();
5598
5399
  const sm = getSleepManagerIfReady();
5599
5400
  const targetMinds = mindParticipants.map((ap) => {
@@ -5710,11 +5511,15 @@ var app25 = new Hono25().post("/:name/chat", zValidator9("json", chatSchema), as
5710
5511
  }
5711
5512
  }
5712
5513
  await addMessage(conversationId, "user", senderName, contentBlocks);
5514
+ const isDM = conv?.type === "dm";
5713
5515
  await fanOutToMinds2({
5714
5516
  conversationId,
5715
5517
  contentBlocks,
5716
5518
  senderName,
5717
5519
  convTitle,
5520
+ isDM,
5521
+ channelEntryType: conv?.type === "channel" ? "group" : isDM ? "dm" : "group",
5522
+ slugExtra: conv ? { convType: conv.type, convName: conv.name } : void 0,
5718
5523
  targetName: (username) => username === baseName ? name : username
5719
5524
  });
5720
5525
  return c.json({ ok: true, conversationId });
@@ -5735,11 +5540,11 @@ var app25 = new Hono25().post("/:name/chat", zValidator9("json", chatSchema), as
5735
5540
  if (!stream.aborted) console.error("[chat] SSE ping error:", err);
5736
5541
  });
5737
5542
  }, 15e3);
5738
- await new Promise((resolve18) => {
5543
+ await new Promise((resolve19) => {
5739
5544
  stream.onAbort(() => {
5740
5545
  unsubscribe();
5741
5546
  clearInterval(keepAlive);
5742
- resolve18();
5547
+ resolve19();
5743
5548
  });
5744
5549
  });
5745
5550
  });
@@ -5951,11 +5756,11 @@ var app27 = new Hono27().use("*", authMiddleware).get("/", async (c) => {
5951
5756
  if (!stream.aborted) console.error("[chat] SSE ping error:", err);
5952
5757
  });
5953
5758
  }, 15e3);
5954
- await new Promise((resolve18) => {
5759
+ await new Promise((resolve19) => {
5955
5760
  stream.onAbort(() => {
5956
5761
  unsubscribe();
5957
5762
  clearInterval(keepAlive);
5958
- resolve18();
5763
+ resolve19();
5959
5764
  });
5960
5765
  });
5961
5766
  });
@@ -6053,13 +5858,14 @@ var MIME_TYPES2 = {
6053
5858
  };
6054
5859
  async function startServer({
6055
5860
  port,
6056
- hostname = "127.0.0.1"
5861
+ hostname = "127.0.0.1",
5862
+ tls
6057
5863
  }) {
6058
5864
  let assetsDir = "";
6059
5865
  let searchDir = dirname(new URL(import.meta.url).pathname);
6060
5866
  for (let i = 0; i < 5; i++) {
6061
- const candidate = resolve16(searchDir, "dist", "web-assets");
6062
- if (existsSync12(candidate)) {
5867
+ const candidate = resolve17(searchDir, "dist", "web-assets");
5868
+ if (existsSync13(candidate)) {
6063
5869
  assetsDir = candidate;
6064
5870
  break;
6065
5871
  }
@@ -6069,16 +5875,16 @@ async function startServer({
6069
5875
  app_default.get("*", async (c) => {
6070
5876
  const urlPath = new URL(c.req.url).pathname;
6071
5877
  if (urlPath.startsWith("/api/")) return c.notFound();
6072
- const filePath = resolve16(assetsDir, urlPath.slice(1));
5878
+ const filePath = resolve17(assetsDir, urlPath.slice(1));
6073
5879
  if (!filePath.startsWith(assetsDir)) return c.text("Forbidden", 403);
6074
5880
  const s = await stat3(filePath).catch(() => null);
6075
5881
  if (s?.isFile()) {
6076
- const ext = extname3(filePath);
5882
+ const ext = extname4(filePath);
6077
5883
  const mime = MIME_TYPES2[ext] || "application/octet-stream";
6078
5884
  const body = await readFile3(filePath);
6079
5885
  return c.body(body, 200, { "Content-Type": mime });
6080
5886
  }
6081
- const indexPath = resolve16(assetsDir, "index.html");
5887
+ const indexPath = resolve17(assetsDir, "index.html");
6082
5888
  const indexStat = await stat3(indexPath).catch(() => null);
6083
5889
  if (indexStat?.isFile()) {
6084
5890
  const body = await readFile3(indexPath, "utf-8");
@@ -6087,22 +5893,55 @@ async function startServer({
6087
5893
  return c.text("Not found", 404);
6088
5894
  });
6089
5895
  }
5896
+ if (tls) {
5897
+ const server2 = serve({
5898
+ fetch: app_default.fetch,
5899
+ port,
5900
+ hostname,
5901
+ createServer: createHttpsServer,
5902
+ serverOptions: { key: tls.key, cert: tls.cert }
5903
+ });
5904
+ await new Promise((resolve19, reject) => {
5905
+ server2.on("listening", () => {
5906
+ logger_default.info("Volute UI running (https)", { hostname, port });
5907
+ resolve19();
5908
+ });
5909
+ server2.on("error", (err) => {
5910
+ reject(err);
5911
+ });
5912
+ });
5913
+ const internalPort = port + 1;
5914
+ const internalServer = serve({ fetch: app_default.fetch, port: internalPort, hostname: "127.0.0.1" });
5915
+ await new Promise((resolve19, reject) => {
5916
+ internalServer.on("listening", () => {
5917
+ logger_default.info("Volute API running (http, internal)", {
5918
+ hostname: "127.0.0.1",
5919
+ port: internalPort
5920
+ });
5921
+ resolve19();
5922
+ });
5923
+ internalServer.on("error", (err) => {
5924
+ reject(err);
5925
+ });
5926
+ });
5927
+ return { server: server2, internalPort };
5928
+ }
6090
5929
  const server = serve({ fetch: app_default.fetch, port, hostname });
6091
- await new Promise((resolve18, reject) => {
5930
+ await new Promise((resolve19, reject) => {
6092
5931
  server.on("listening", () => {
6093
- logger_default.info("Volute UI running", { hostname, port });
6094
- resolve18();
5932
+ logger_default.info("Volute API running (http)", { hostname, port });
5933
+ resolve19();
6095
5934
  });
6096
5935
  server.on("error", (err) => {
6097
5936
  reject(err);
6098
5937
  });
6099
5938
  });
6100
- return server;
5939
+ return { server };
6101
5940
  }
6102
5941
 
6103
5942
  // src/daemon.ts
6104
5943
  if (!process.env.VOLUTE_HOME) {
6105
- process.env.VOLUTE_HOME = resolve17(homedir2(), ".volute");
5944
+ process.env.VOLUTE_HOME = resolve18(homedir2(), ".volute");
6106
5945
  }
6107
5946
  if (process.env.VOLUTE_TIMEZONE && !process.env.TZ) {
6108
5947
  process.env.TZ = process.env.VOLUTE_TIMEZONE;
@@ -6112,7 +5951,7 @@ async function startDaemon(opts) {
6112
5951
  const myPid = String(process.pid);
6113
5952
  const home = voluteHome();
6114
5953
  if (!opts.foreground) {
6115
- const rotatingLog = new RotatingLog(resolve17(home, "daemon.log"));
5954
+ const rotatingLog = new RotatingLog(resolve18(home, "daemon.log"));
6116
5955
  logger_default.setOutput((line) => rotatingLog.write(`${line}
6117
5956
  `));
6118
5957
  const write = (...args) => rotatingLog.write(`${format(...args)}
@@ -6122,9 +5961,9 @@ async function startDaemon(opts) {
6122
5961
  console.warn = write;
6123
5962
  console.info = write;
6124
5963
  }
6125
- const DAEMON_PID_PATH = resolve17(home, "daemon.pid");
6126
- const DAEMON_JSON_PATH = resolve17(home, "daemon.json");
6127
- mkdirSync8(home, { recursive: true });
5964
+ const DAEMON_PID_PATH = resolve18(home, "daemon.pid");
5965
+ const DAEMON_JSON_PATH = resolve18(home, "daemon.json");
5966
+ mkdirSync9(home, { recursive: true });
6128
5967
  migrateAgentsToMinds();
6129
5968
  try {
6130
5969
  await ensureSharedRepo();
@@ -6138,12 +5977,16 @@ async function startDaemon(opts) {
6138
5977
  logger_default.error("failed to sync built-in skills", logger_default.errorData(err));
6139
5978
  }
6140
5979
  const token = process.env.VOLUTE_DAEMON_TOKEN || randomBytes2(32).toString("hex");
6141
- process.env.VOLUTE_DAEMON_TOKEN = token;
6142
- process.env.VOLUTE_DAEMON_PORT = String(port);
6143
- process.env.VOLUTE_DAEMON_HOSTNAME = hostname;
6144
- let server;
5980
+ let tls;
5981
+ if (opts.tailscale) {
5982
+ const { getTailscaleTls } = await import("./tailscale-AJ4VL5XK.js");
5983
+ const tlsConfig = await getTailscaleTls();
5984
+ tls = { key: tlsConfig.key, cert: tlsConfig.cert };
5985
+ logger_default.info("Tailscale HTTPS enabled", { hostname: tlsConfig.hostname });
5986
+ }
5987
+ let result;
6145
5988
  try {
6146
- server = await startServer({ port, hostname });
5989
+ result = await startServer({ port, hostname: "0.0.0.0", tls });
6147
5990
  } catch (err) {
6148
5991
  const e = err;
6149
5992
  if (e.code === "EADDRINUSE") {
@@ -6152,11 +5995,17 @@ async function startDaemon(opts) {
6152
5995
  }
6153
5996
  throw err;
6154
5997
  }
6155
- writeFileSync8(DAEMON_PID_PATH, myPid, { mode: 420 });
6156
- writeFileSync8(DAEMON_JSON_PATH, `${JSON.stringify({ port, hostname, token }, null, 2)}
6157
- `, {
6158
- mode: 420
6159
- });
5998
+ const { server, internalPort } = result;
5999
+ const daemonPort = internalPort ?? port;
6000
+ process.env.VOLUTE_DAEMON_TOKEN = token;
6001
+ process.env.VOLUTE_DAEMON_PORT = String(daemonPort);
6002
+ process.env.VOLUTE_DAEMON_HOSTNAME = hostname;
6003
+ writeFileSync9(DAEMON_PID_PATH, myPid, { mode: 420 });
6004
+ const daemonConfig = { port, hostname, token };
6005
+ if (internalPort) daemonConfig.internalPort = internalPort;
6006
+ if (tls) daemonConfig.tls = true;
6007
+ writeFileSync9(DAEMON_JSON_PATH, `${JSON.stringify(daemonConfig, null, 2)}
6008
+ `, { mode: 420 });
6160
6009
  const delivery = initDeliveryManager();
6161
6010
  const manager = initMindManager();
6162
6011
  manager.loadCrashAttempts();
@@ -6188,8 +6037,8 @@ async function startDaemon(opts) {
6188
6037
  if (sleepManager.isSleeping(entry.name)) {
6189
6038
  try {
6190
6039
  const dir = mindDir(entry.name);
6191
- const daemonPort = process.env.VOLUTE_DAEMON_PORT ? parseInt(process.env.VOLUTE_DAEMON_PORT, 10) : void 0;
6192
- await connectors.startConnectors(entry.name, dir, entry.port, daemonPort);
6040
+ const daemonPort2 = process.env.VOLUTE_DAEMON_PORT ? parseInt(process.env.VOLUTE_DAEMON_PORT, 10) : void 0;
6041
+ await connectors.startConnectors(entry.name, dir, entry.port, daemonPort2);
6193
6042
  scheduler.loadSchedules(entry.name);
6194
6043
  } catch (err) {
6195
6044
  logger_default.error(
@@ -6226,7 +6075,7 @@ async function startDaemon(opts) {
6226
6075
  });
6227
6076
  await Promise.all(workers);
6228
6077
  }
6229
- import("./cloud-sync-PI47U2LT.js").then(
6078
+ import("./cloud-sync-DIU3OCPV.js").then(
6230
6079
  ({ consumeQueuedMessages }) => consumeQueuedMessages().catch((err) => {
6231
6080
  logger_default.warn("failed to consume queued cloud messages", logger_default.errorData(err));
6232
6081
  })
@@ -6234,7 +6083,7 @@ async function startDaemon(opts) {
6234
6083
  logger_default.warn("failed to load cloud-sync module", logger_default.errorData(err));
6235
6084
  });
6236
6085
  try {
6237
- const { backfillTemplateHashes, notifyVersionUpdate } = await import("./version-notify-LKABEJSA.js");
6086
+ const { backfillTemplateHashes, notifyVersionUpdate } = await import("./version-notify-SZ75QRGO.js");
6238
6087
  backfillTemplateHashes();
6239
6088
  notifyVersionUpdate().catch((err) => {
6240
6089
  logger_default.warn("failed to send version update notifications", logger_default.errorData(err));
@@ -6251,13 +6100,13 @@ async function startDaemon(opts) {
6251
6100
  logger_default.info(`running on ${hostname}:${port}, pid ${myPid}`);
6252
6101
  function cleanup() {
6253
6102
  try {
6254
- if (readFileSync10(DAEMON_PID_PATH, "utf-8").trim() === myPid) {
6103
+ if (readFileSync11(DAEMON_PID_PATH, "utf-8").trim() === myPid) {
6255
6104
  unlinkSync(DAEMON_PID_PATH);
6256
6105
  }
6257
6106
  } catch {
6258
6107
  }
6259
6108
  try {
6260
- const data = JSON.parse(readFileSync10(DAEMON_JSON_PATH, "utf-8"));
6109
+ const data = JSON.parse(readFileSync11(DAEMON_JSON_PATH, "utf-8"));
6261
6110
  if (data.token === token) {
6262
6111
  unlinkSync(DAEMON_JSON_PATH);
6263
6112
  }
@@ -6271,9 +6120,9 @@ async function startDaemon(opts) {
6271
6120
  logger_default.info("shutting down...");
6272
6121
  const safe = (label, fn) => {
6273
6122
  try {
6274
- const result = fn();
6275
- if (result instanceof Promise)
6276
- return result.catch((err) => logger_default.error(`shutdown: ${label} failed`, logger_default.errorData(err)));
6123
+ const result2 = fn();
6124
+ if (result2 instanceof Promise)
6125
+ return result2.catch((err) => logger_default.error(`shutdown: ${label} failed`, logger_default.errorData(err)));
6277
6126
  } catch (err) {
6278
6127
  logger_default.error(`shutdown: ${label} failed`, logger_default.errorData(err));
6279
6128
  }
@@ -6305,9 +6154,10 @@ async function startDaemon(opts) {
6305
6154
  process.on("exit", cleanup);
6306
6155
  }
6307
6156
  if (import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith("daemon.ts")) {
6308
- let port = 4200;
6157
+ let port = 1618;
6309
6158
  let hostname = "127.0.0.1";
6310
6159
  let foreground = false;
6160
+ let tailscale = false;
6311
6161
  for (let i = 2; i < process.argv.length; i++) {
6312
6162
  if (process.argv[i] === "--port" && process.argv[i + 1]) {
6313
6163
  port = parseInt(process.argv[i + 1], 10);
@@ -6317,9 +6167,11 @@ if (import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith
6317
6167
  i++;
6318
6168
  } else if (process.argv[i] === "--foreground") {
6319
6169
  foreground = true;
6170
+ } else if (process.argv[i] === "--tailscale") {
6171
+ tailscale = true;
6320
6172
  }
6321
6173
  }
6322
- startDaemon({ port, hostname, foreground });
6174
+ startDaemon({ port, hostname, foreground, tailscale });
6323
6175
  }
6324
6176
  export {
6325
6177
  startDaemon