volute 0.22.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 (80) 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-VNVCRVYI.js → chunk-NOBRGACV.js} +7 -7
  12. package/dist/{chunk-OSFGKF2T.js → chunk-OOW675I3.js} +839 -129
  13. package/dist/{chunk-TFS25FIM.js → chunk-P3W36ZGD.js} +1 -1
  14. package/dist/{chunk-JNFRY2WU.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-C6WRYRVR.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-TPQ2XBRZ.js → daemon-restart-YMPEATQH.js} +5 -5
  22. package/dist/daemon.js +697 -865
  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-WUS4K4ZC.js → message-delivery-S7BCNV6Y.js} +9 -7
  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-P5OBDUKI.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-A7PEYJI2.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-3RWUX2ZR.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-JKGC7PPF.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-5FGUAVSF.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/file-handler.ts +6 -1
  71. package/templates/_base/src/lib/format-prefix.ts +18 -2
  72. package/templates/_base/src/lib/routing.ts +2 -1
  73. package/templates/_base/src/lib/types.ts +8 -0
  74. package/templates/claude/src/lib/stream-consumer.ts +10 -1
  75. package/templates/pi/src/lib/content.ts +18 -3
  76. package/templates/pi/src/lib/event-handler.ts +9 -1
  77. package/dist/chunk-G5KRTU2F.js +0 -76
  78. package/dist/web-assets/assets/index-DWBxl4LO.js +0 -69
  79. package/dist/web-assets/assets/index-ZqMd1mx1.css +0 -1
  80. /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,29 +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,
70
+ publish2,
41
71
  publishTypingForChannels,
72
+ recordInbound,
73
+ setUserRole,
42
74
  startMindFull,
43
75
  stopAllWatchers,
44
76
  stopMindFull,
45
- subscribe as subscribe2
46
- } from "./chunk-OSFGKF2T.js";
77
+ subscribe as subscribe2,
78
+ subscribe2 as subscribe3,
79
+ updateUserProfile,
80
+ verifyUser
81
+ } from "./chunk-OOW675I3.js";
47
82
  import {
48
83
  readSystemsConfig
49
84
  } from "./chunk-HFCBO2GL.js";
@@ -51,11 +86,11 @@ import {
51
86
  getActiveMinds,
52
87
  onMindEvent,
53
88
  stopAll
54
- } from "./chunk-HGCDWKSP.js";
89
+ } from "./chunk-E7GOKNOT.js";
55
90
  import {
56
91
  broadcast,
57
92
  subscribe
58
- } from "./chunk-A4S7H6G6.js";
93
+ } from "./chunk-BFK6SOEJ.js";
59
94
  import {
60
95
  PROMPT_DEFAULTS,
61
96
  PROMPT_KEYS,
@@ -66,13 +101,13 @@ import {
66
101
  getPromptIfCustom,
67
102
  initMindManager,
68
103
  substitute
69
- } from "./chunk-VNVCRVYI.js";
104
+ } from "./chunk-NOBRGACV.js";
70
105
  import {
71
106
  findOpenClawSession,
72
107
  importOpenClawConnectors,
73
108
  importPiSession,
74
109
  parseNameFromIdentity
75
- } from "./chunk-RK627D57.js";
110
+ } from "./chunk-4TJ72QQ3.js";
76
111
  import {
77
112
  readVoluteConfig,
78
113
  writeVoluteConfig
@@ -102,18 +137,15 @@ import {
102
137
  syncBuiltinSkills,
103
138
  uninstallSkill,
104
139
  updateSkill
105
- } from "./chunk-TFS25FIM.js";
140
+ } from "./chunk-P3W36ZGD.js";
106
141
  import {
107
142
  activity,
108
- conversationParticipants,
109
143
  conversations,
110
144
  getDb,
111
- messages,
112
145
  mindHistory,
113
146
  sessions,
114
- systemPrompts,
115
- users
116
- } from "./chunk-SGPEZ32F.js";
147
+ systemPrompts
148
+ } from "./chunk-33XAVCS4.js";
117
149
  import {
118
150
  logBuffer,
119
151
  logger_default
@@ -176,9 +208,9 @@ import {
176
208
 
177
209
  // src/daemon.ts
178
210
  import { randomBytes as randomBytes2 } from "crypto";
179
- 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";
180
212
  import { homedir as homedir2 } from "os";
181
- import { resolve as resolve17 } from "path";
213
+ import { resolve as resolve18 } from "path";
182
214
  import { format } from "util";
183
215
 
184
216
  // src/lib/migrate-agents-to-minds.ts
@@ -356,145 +388,9 @@ function migrateMindState(name) {
356
388
 
357
389
  // src/web/middleware/auth.ts
358
390
  import { timingSafeEqual } from "crypto";
359
- import { eq as eq2, lt } from "drizzle-orm";
391
+ import { eq, lt } from "drizzle-orm";
360
392
  import { getCookie } from "hono/cookie";
361
393
  import { createMiddleware } from "hono/factory";
362
-
363
- // src/lib/auth.ts
364
- import { compareSync, hashSync } from "bcryptjs";
365
- import { and, count, eq } from "drizzle-orm";
366
- async function createUser(username, password) {
367
- const db = await getDb();
368
- const hash = hashSync(password, 10);
369
- const [{ value }] = await db.select({ value: count() }).from(users).where(eq(users.user_type, "brain"));
370
- const role = value === 0 ? "admin" : "pending";
371
- const [result] = await db.insert(users).values({ username, password_hash: hash, role }).returning({
372
- id: users.id,
373
- username: users.username,
374
- role: users.role,
375
- user_type: users.user_type,
376
- created_at: users.created_at
377
- });
378
- return result;
379
- }
380
- async function verifyUser(username, password) {
381
- const db = await getDb();
382
- const row = await db.select().from(users).where(eq(users.username, username)).get();
383
- if (!row) return null;
384
- if (row.user_type === "mind") return null;
385
- if (!compareSync(password, row.password_hash)) return null;
386
- const { password_hash: _, ...user } = row;
387
- return user;
388
- }
389
- async function getUser(id) {
390
- const db = await getDb();
391
- const row = await db.select({
392
- id: users.id,
393
- username: users.username,
394
- role: users.role,
395
- user_type: users.user_type,
396
- created_at: users.created_at
397
- }).from(users).where(eq(users.id, id)).get();
398
- return row ?? null;
399
- }
400
- async function getUserByUsername(username) {
401
- const db = await getDb();
402
- const row = await db.select({
403
- id: users.id,
404
- username: users.username,
405
- role: users.role,
406
- user_type: users.user_type,
407
- created_at: users.created_at
408
- }).from(users).where(eq(users.username, username)).get();
409
- return row ?? null;
410
- }
411
- async function listUsers() {
412
- const db = await getDb();
413
- return db.select({
414
- id: users.id,
415
- username: users.username,
416
- role: users.role,
417
- user_type: users.user_type,
418
- created_at: users.created_at
419
- }).from(users).orderBy(users.created_at).all();
420
- }
421
- async function listPendingUsers() {
422
- const db = await getDb();
423
- return db.select({
424
- id: users.id,
425
- username: users.username,
426
- role: users.role,
427
- user_type: users.user_type,
428
- created_at: users.created_at
429
- }).from(users).where(eq(users.role, "pending")).orderBy(users.created_at).all();
430
- }
431
- async function listUsersByType(userType) {
432
- const db = await getDb();
433
- return db.select({
434
- id: users.id,
435
- username: users.username,
436
- role: users.role,
437
- user_type: users.user_type,
438
- created_at: users.created_at
439
- }).from(users).where(eq(users.user_type, userType)).orderBy(users.created_at).all();
440
- }
441
- async function getOrCreateMindUser(mindName) {
442
- const db = await getDb();
443
- const existing = await db.select({
444
- id: users.id,
445
- username: users.username,
446
- role: users.role,
447
- user_type: users.user_type,
448
- created_at: users.created_at
449
- }).from(users).where(and(eq(users.username, mindName), eq(users.user_type, "mind"))).get();
450
- if (existing) return existing;
451
- try {
452
- const [result] = await db.insert(users).values({
453
- username: mindName,
454
- password_hash: "!mind",
455
- role: "mind",
456
- user_type: "mind"
457
- }).returning({
458
- id: users.id,
459
- username: users.username,
460
- role: users.role,
461
- user_type: users.user_type,
462
- created_at: users.created_at
463
- });
464
- return result;
465
- } catch (err) {
466
- if (err instanceof Error && err.message.includes("UNIQUE constraint")) {
467
- const retried = await db.select({
468
- id: users.id,
469
- username: users.username,
470
- role: users.role,
471
- user_type: users.user_type,
472
- created_at: users.created_at
473
- }).from(users).where(and(eq(users.username, mindName), eq(users.user_type, "mind"))).get();
474
- if (retried) return retried;
475
- }
476
- throw err;
477
- }
478
- }
479
- async function deleteMindUser2(mindName) {
480
- const db = await getDb();
481
- await db.delete(users).where(and(eq(users.username, mindName), eq(users.user_type, "mind")));
482
- }
483
- async function changePassword(userId, currentPassword, newPassword) {
484
- const db = await getDb();
485
- const row = await db.select().from(users).where(eq(users.id, userId)).get();
486
- if (!row) return false;
487
- if (!compareSync(currentPassword, row.password_hash)) return false;
488
- const hash = hashSync(newPassword, 10);
489
- await db.update(users).set({ password_hash: hash }).where(eq(users.id, userId));
490
- return true;
491
- }
492
- async function approveUser(id) {
493
- const db = await getDb();
494
- await db.update(users).set({ role: "user" }).where(and(eq(users.id, id), eq(users.role, "pending")));
495
- }
496
-
497
- // src/web/middleware/auth.ts
498
394
  function isValidDaemonToken(token) {
499
395
  const expected = process.env.VOLUTE_DAEMON_TOKEN;
500
396
  if (!expected || token.length !== expected.length) return false;
@@ -503,6 +399,9 @@ function isValidDaemonToken(token) {
503
399
  var SESSION_MAX_AGE = 864e5;
504
400
  var SESSION_CACHE_TTL = 5 * 60 * 1e3;
505
401
  var sessionCache = /* @__PURE__ */ new Map();
402
+ function invalidateSessionCache(sessionId) {
403
+ sessionCache.delete(sessionId);
404
+ }
506
405
  async function createSession(userId) {
507
406
  const db = await getDb();
508
407
  const sessionId = crypto.randomUUID();
@@ -512,14 +411,14 @@ async function createSession(userId) {
512
411
  async function deleteSession(sessionId) {
513
412
  sessionCache.delete(sessionId);
514
413
  const db = await getDb();
515
- await db.delete(sessions).where(eq2(sessions.id, sessionId));
414
+ await db.delete(sessions).where(eq(sessions.id, sessionId));
516
415
  }
517
416
  async function getSessionUserId(sessionId) {
518
417
  const db = await getDb();
519
- 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();
520
419
  if (!row) return void 0;
521
420
  if (Date.now() - row.createdAt > SESSION_MAX_AGE) {
522
- await db.delete(sessions).where(eq2(sessions.id, sessionId));
421
+ await db.delete(sessions).where(eq(sessions.id, sessionId));
523
422
  return void 0;
524
423
  }
525
424
  return row.userId;
@@ -541,7 +440,15 @@ var authMiddleware = createMiddleware(async (c, next) => {
541
440
  if (authHeader?.startsWith("Bearer ")) {
542
441
  const token = authHeader.slice(7);
543
442
  if (token && isValidDaemonToken(token)) {
544
- 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
+ });
545
452
  await next();
546
453
  return;
547
454
  }
@@ -572,9 +479,10 @@ var authMiddleware = createMiddleware(async (c, next) => {
572
479
  });
573
480
 
574
481
  // src/web/server.ts
575
- import { existsSync as existsSync12 } from "fs";
482
+ import { existsSync as existsSync13 } from "fs";
576
483
  import { readFile as readFile3, stat as stat3 } from "fs/promises";
577
- 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";
578
486
  import { serve } from "@hono/node-server";
579
487
 
580
488
  // src/web/app.ts
@@ -584,294 +492,9 @@ import { csrf } from "hono/csrf";
584
492
  import { HTTPException } from "hono/http-exception";
585
493
 
586
494
  // src/web/api/activity.ts
587
- import { desc as desc2 } from "drizzle-orm";
495
+ import { desc } from "drizzle-orm";
588
496
  import { Hono } from "hono";
589
497
  import { streamSSE } from "hono/streaming";
590
-
591
- // src/lib/events/conversations.ts
592
- import { randomUUID } from "crypto";
593
- import { and as and2, desc, eq as eq3, inArray, isNull, lt as lt2, sql } from "drizzle-orm";
594
- async function createConversation(mindName, channel, opts) {
595
- const db = await getDb();
596
- const id = randomUUID();
597
- const type = opts?.type ?? "dm";
598
- const name = opts?.name ?? null;
599
- await db.transaction(async (tx) => {
600
- await tx.insert(conversations).values({
601
- id,
602
- mind_name: mindName,
603
- channel,
604
- type,
605
- name,
606
- user_id: opts?.userId ?? null,
607
- title: opts?.title ?? null
608
- });
609
- if (opts?.participantIds && opts.participantIds.length > 0) {
610
- await tx.insert(conversationParticipants).values(
611
- opts.participantIds.map((uid, i) => ({
612
- conversation_id: id,
613
- user_id: uid,
614
- role: i === 0 ? "owner" : "member"
615
- }))
616
- );
617
- }
618
- });
619
- fireWebhook({
620
- event: "conversation_created",
621
- mind: mindName ?? "",
622
- data: { id, mindName, channel, type, name, title: opts?.title ?? null }
623
- });
624
- return {
625
- id,
626
- mind_name: mindName,
627
- channel,
628
- type,
629
- name,
630
- user_id: opts?.userId ?? null,
631
- title: opts?.title ?? null,
632
- created_at: (/* @__PURE__ */ new Date()).toISOString(),
633
- updated_at: (/* @__PURE__ */ new Date()).toISOString()
634
- };
635
- }
636
- async function getConversation(id) {
637
- const db = await getDb();
638
- const row = await db.select().from(conversations).where(eq3(conversations.id, id)).get();
639
- return row ?? null;
640
- }
641
- async function addParticipant(conversationId, userId, role = "member") {
642
- const db = await getDb();
643
- await db.insert(conversationParticipants).values({
644
- conversation_id: conversationId,
645
- user_id: userId,
646
- role
647
- });
648
- }
649
- async function removeParticipant(conversationId, userId) {
650
- const db = await getDb();
651
- await db.delete(conversationParticipants).where(
652
- and2(
653
- eq3(conversationParticipants.conversation_id, conversationId),
654
- eq3(conversationParticipants.user_id, userId)
655
- )
656
- );
657
- }
658
- async function getParticipants(conversationId) {
659
- const db = await getDb();
660
- const rows = await db.select({
661
- userId: conversationParticipants.user_id,
662
- username: users.username,
663
- userType: users.user_type,
664
- role: conversationParticipants.role
665
- }).from(conversationParticipants).innerJoin(users, eq3(conversationParticipants.user_id, users.id)).where(eq3(conversationParticipants.conversation_id, conversationId)).all();
666
- return rows;
667
- }
668
- async function isParticipant(conversationId, userId) {
669
- const db = await getDb();
670
- const row = await db.select({ user_id: conversationParticipants.user_id }).from(conversationParticipants).where(
671
- and2(
672
- eq3(conversationParticipants.conversation_id, conversationId),
673
- eq3(conversationParticipants.user_id, userId)
674
- )
675
- ).get();
676
- return row != null;
677
- }
678
- async function listConversationsForUser(userId) {
679
- const db = await getDb();
680
- const participantRows = await db.select({ conversation_id: conversationParticipants.conversation_id }).from(conversationParticipants).where(eq3(conversationParticipants.user_id, userId)).all();
681
- if (participantRows.length === 0) return [];
682
- const convIds = participantRows.map((r) => r.conversation_id);
683
- return await db.select().from(conversations).where(inArray(conversations.id, convIds)).orderBy(desc(conversations.updated_at)).all();
684
- }
685
- async function isParticipantOrOwner(conversationId, userId) {
686
- if (await isParticipant(conversationId, userId)) return true;
687
- const db = await getDb();
688
- const row = await db.select().from(conversations).where(and2(eq3(conversations.id, conversationId), eq3(conversations.user_id, userId))).get();
689
- return row != null;
690
- }
691
- async function deleteConversationForUser(id, userId) {
692
- if (!await isParticipantOrOwner(id, userId)) return false;
693
- await deleteConversation(id);
694
- return true;
695
- }
696
- async function addMessage(conversationId, role, senderName, content) {
697
- const db = await getDb();
698
- const serialized = JSON.stringify(content);
699
- 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 });
700
- await db.update(conversations).set({ updated_at: sql`datetime('now')` }).where(eq3(conversations.id, conversationId));
701
- if (role === "user") {
702
- const firstText = content.find((b) => b.type === "text");
703
- const title = firstText ? firstText.text.slice(0, 80) : "";
704
- if (title) {
705
- await db.update(conversations).set({ title }).where(and2(eq3(conversations.id, conversationId), isNull(conversations.title)));
706
- }
707
- }
708
- const msg = {
709
- id: result.id,
710
- conversation_id: conversationId,
711
- role,
712
- sender_name: senderName,
713
- content,
714
- created_at: result.created_at
715
- };
716
- publish(conversationId, {
717
- type: "message",
718
- id: msg.id,
719
- role: msg.role,
720
- senderName: msg.sender_name,
721
- content: msg.content,
722
- createdAt: msg.created_at
723
- });
724
- const conv = await db.select({ mind_name: conversations.mind_name }).from(conversations).where(eq3(conversations.id, conversationId)).get();
725
- fireWebhook({
726
- event: "message_created",
727
- mind: conv?.mind_name ?? "",
728
- data: {
729
- conversationId,
730
- messageId: result.id,
731
- role,
732
- senderName,
733
- content: content.filter((b) => b.type !== "image"),
734
- createdAt: result.created_at
735
- }
736
- });
737
- return msg;
738
- }
739
- async function getMessages(conversationId) {
740
- const db = await getDb();
741
- const rows = await db.select().from(messages).where(eq3(messages.conversation_id, conversationId)).orderBy(messages.created_at).all();
742
- return rows.map(parseMessageRow);
743
- }
744
- async function getMessagesPaginated(conversationId, opts) {
745
- const db = await getDb();
746
- const limit = Math.min(Math.max(opts?.limit ?? 50, 1), 100);
747
- const conditions = [eq3(messages.conversation_id, conversationId)];
748
- if (opts?.before != null) {
749
- conditions.push(lt2(messages.id, opts.before));
750
- }
751
- const rows = await db.select().from(messages).where(and2(...conditions)).orderBy(desc(messages.id)).limit(limit + 1).all();
752
- const hasMore = rows.length > limit;
753
- const page = rows.slice(0, limit).reverse();
754
- return {
755
- messages: page.map(parseMessageRow),
756
- hasMore
757
- };
758
- }
759
- function parseMessageRow(row) {
760
- let content;
761
- try {
762
- const parsed = JSON.parse(row.content);
763
- content = Array.isArray(parsed) ? parsed : [{ type: "text", text: row.content }];
764
- } catch {
765
- content = [{ type: "text", text: row.content }];
766
- }
767
- return { ...row, role: row.role, content };
768
- }
769
- async function listConversationsWithParticipants(userId) {
770
- const convs = await listConversationsForUser(userId);
771
- if (convs.length === 0) return [];
772
- const db = await getDb();
773
- const convIds = convs.map((c) => c.id);
774
- const rows = await db.select({
775
- conversationId: conversationParticipants.conversation_id,
776
- userId: users.id,
777
- username: users.username,
778
- userType: users.user_type,
779
- role: conversationParticipants.role
780
- }).from(conversationParticipants).innerJoin(users, eq3(conversationParticipants.user_id, users.id)).where(inArray(conversationParticipants.conversation_id, convIds));
781
- const byConv = /* @__PURE__ */ new Map();
782
- for (const r of rows) {
783
- let arr = byConv.get(r.conversationId);
784
- if (!arr) {
785
- arr = [];
786
- byConv.set(r.conversationId, arr);
787
- }
788
- arr.push({
789
- userId: r.userId,
790
- username: r.username,
791
- userType: r.userType,
792
- role: r.role
793
- });
794
- }
795
- const lastMsgIds = await db.select({
796
- conversationId: messages.conversation_id,
797
- maxId: sql`MAX(${messages.id})`
798
- }).from(messages).where(inArray(messages.conversation_id, convIds)).groupBy(messages.conversation_id);
799
- const byLastMsg = /* @__PURE__ */ new Map();
800
- if (lastMsgIds.length > 0) {
801
- const msgRows = await db.select().from(messages).where(
802
- inArray(
803
- messages.id,
804
- lastMsgIds.map((r) => r.maxId)
805
- )
806
- );
807
- for (const m of msgRows) {
808
- let text = "";
809
- try {
810
- const parsed = JSON.parse(m.content);
811
- const blocks = Array.isArray(parsed) ? parsed : [];
812
- const textBlock = blocks.find((b) => b.type === "text");
813
- if (textBlock && "text" in textBlock) text = textBlock.text;
814
- } catch {
815
- text = m.content;
816
- }
817
- byLastMsg.set(m.conversation_id, {
818
- role: m.role,
819
- senderName: m.sender_name,
820
- text,
821
- createdAt: m.created_at
822
- });
823
- }
824
- }
825
- return convs.map((c) => ({
826
- ...c,
827
- participants: byConv.get(c.id) ?? [],
828
- lastMessage: byLastMsg.get(c.id)
829
- }));
830
- }
831
- async function findDMConversation(mindName, participantIds) {
832
- const db = await getDb();
833
- const mindConvs = await db.select({ id: conversations.id }).from(conversations).where(and2(eq3(conversations.mind_name, mindName), eq3(conversations.type, "dm"))).all();
834
- for (const conv of mindConvs) {
835
- const rows = await db.select({ user_id: conversationParticipants.user_id }).from(conversationParticipants).where(eq3(conversationParticipants.conversation_id, conv.id)).all();
836
- if (rows.length !== 2) continue;
837
- const ids = new Set(rows.map((r) => r.user_id));
838
- if (ids.has(participantIds[0]) && ids.has(participantIds[1])) {
839
- return conv.id;
840
- }
841
- }
842
- return null;
843
- }
844
- async function deleteConversation(id) {
845
- const db = await getDb();
846
- await db.delete(conversations).where(eq3(conversations.id, id));
847
- }
848
- async function createChannel(name, creatorId) {
849
- const participantIds = creatorId ? [creatorId] : [];
850
- return createConversation(null, "volute", {
851
- type: "channel",
852
- name,
853
- title: name,
854
- participantIds
855
- });
856
- }
857
- async function getChannelByName(name) {
858
- const db = await getDb();
859
- const row = await db.select().from(conversations).where(and2(eq3(conversations.name, name), eq3(conversations.type, "channel"))).get();
860
- return row ?? null;
861
- }
862
- async function listChannels() {
863
- const db = await getDb();
864
- return await db.select().from(conversations).where(eq3(conversations.type, "channel")).orderBy(conversations.name).all();
865
- }
866
- async function joinChannel(conversationId, userId) {
867
- if (await isParticipant(conversationId, userId)) return;
868
- await addParticipant(conversationId, userId);
869
- }
870
- async function leaveChannel(conversationId, userId) {
871
- await removeParticipant(conversationId, userId);
872
- }
873
-
874
- // src/web/api/activity.ts
875
498
  var app = new Hono().get("/events", async (c) => {
876
499
  const user = c.get("user");
877
500
  return streamSSE(c, async (stream) => {
@@ -880,7 +503,7 @@ var app = new Hono().get("/events", async (c) => {
880
503
  let recentActivity = [];
881
504
  try {
882
505
  const db = await getDb();
883
- 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);
884
507
  recentActivity = recentActivity.map((row) => ({
885
508
  ...row,
886
509
  metadata: row.metadata ? JSON.parse(row.metadata) : null
@@ -915,7 +538,7 @@ var app = new Hono().get("/events", async (c) => {
915
538
  });
916
539
  cleanups.push(unsubActivity);
917
540
  for (const conv of conversations2) {
918
- const unsubConv = subscribe2(conv.id, (event) => {
541
+ const unsubConv = subscribe3(conv.id, (event) => {
919
542
  stream.writeSSE({
920
543
  data: JSON.stringify({ event: "conversation", conversationId: conv.id, ...event })
921
544
  }).catch((err) => {
@@ -930,8 +553,8 @@ var app = new Hono().get("/events", async (c) => {
930
553
  });
931
554
  }, 15e3);
932
555
  cleanups.push(() => clearInterval(keepAlive));
933
- await new Promise((resolve18) => {
934
- stream.onAbort(() => resolve18());
556
+ await new Promise((resolve19) => {
557
+ stream.onAbort(() => resolve19());
935
558
  });
936
559
  } finally {
937
560
  for (const cleanup of cleanups) {
@@ -946,6 +569,8 @@ var app = new Hono().get("/events", async (c) => {
946
569
  var activity_default = app;
947
570
 
948
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";
949
574
  import { zValidator } from "@hono/zod-validator";
950
575
  import { Hono as Hono2 } from "hono";
951
576
  import { deleteCookie, getCookie as getCookie2, setCookie } from "hono/cookie";
@@ -958,12 +583,71 @@ var changePasswordSchema = z.object({
958
583
  currentPassword: z.string().min(1),
959
584
  newPassword: z.string().min(1)
960
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
+ }
961
601
  var authenticated = new Hono2().use(authMiddleware).post("/change-password", zValidator("json", changePasswordSchema), async (c) => {
962
602
  const user = c.get("user");
963
603
  const { currentPassword, newPassword } = c.req.valid("json");
964
604
  const ok = await changePassword(user.id, currentPassword, newPassword);
965
605
  if (!ok) return c.json({ error: "Current password is incorrect" }, 400);
966
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 });
967
651
  });
968
652
  var admin = new Hono2().use(authMiddleware).get("/users", async (c) => {
969
653
  const user = c.get("user");
@@ -985,8 +669,55 @@ var admin = new Hono2().use(authMiddleware).get("/users", async (c) => {
985
669
  const user = c.get("user");
986
670
  if (user.role !== "admin") return c.json({ error: "Forbidden" }, 403);
987
671
  const id = parseInt(c.req.param("id"), 10);
672
+ if (Number.isNaN(id)) return c.json({ error: "Invalid user ID" }, 400);
988
673
  await approveUser(id);
989
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 });
990
721
  });
991
722
  var app2 = new Hono2().post("/register", zValidator("json", credentialsSchema), async (c) => {
992
723
  const { username, password } = c.req.valid("json");
@@ -1023,7 +754,32 @@ var app2 = new Hono2().post("/register", zValidator("json", credentialsSchema),
1023
754
  if (userId == null) return c.json({ error: "Not logged in" }, 401);
1024
755
  const user = await getUser(userId);
1025
756
  if (!user) return c.json({ error: "Not logged in" }, 401);
1026
- 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
+ });
1027
783
  }).route("/", admin).route("/", authenticated);
1028
784
  var auth_default = app2;
1029
785
 
@@ -1064,8 +820,8 @@ async function read(env, channelSlug, limit) {
1064
820
  if (!res.ok) {
1065
821
  throw new Error(`Discord API error: ${res.status} ${res.statusText}`);
1066
822
  }
1067
- const messages2 = await res.json();
1068
- 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");
1069
825
  }
1070
826
  async function send(env, channelSlug, message, images) {
1071
827
  const token = requireToken(env);
@@ -1297,8 +1053,8 @@ async function listConversations2(env) {
1297
1053
  const userMap = /* @__PURE__ */ new Map();
1298
1054
  const imChannels = data.channels.filter((ch) => ch.is_im && ch.user);
1299
1055
  if (imChannels.length > 0) {
1300
- const users2 = await listUsers3(env);
1301
- for (const u of users2) {
1056
+ const users = await listUsers3(env);
1057
+ for (const u of users) {
1302
1058
  userMap.set(u.id, u.username);
1303
1059
  }
1304
1060
  }
@@ -1493,16 +1249,16 @@ __export(volute_exports, {
1493
1249
  read: () => read4,
1494
1250
  send: () => send4
1495
1251
  });
1496
- import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
1497
- import { resolve as resolve3 } from "path";
1252
+ import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
1253
+ import { resolve as resolve4 } from "path";
1498
1254
  function getDaemonConfig() {
1499
- const configPath2 = resolve3(voluteHome(), "daemon.json");
1500
- if (!existsSync3(configPath2)) {
1255
+ const configPath2 = resolve4(voluteHome(), "daemon.json");
1256
+ if (!existsSync4(configPath2)) {
1501
1257
  throw new Error("Volute daemon is not running");
1502
1258
  }
1503
1259
  let config;
1504
1260
  try {
1505
- config = JSON.parse(readFileSync2(configPath2, "utf-8"));
1261
+ config = JSON.parse(readFileSync3(configPath2, "utf-8"));
1506
1262
  } catch (err) {
1507
1263
  throw new Error(`Failed to parse ${configPath2}: ${err}`);
1508
1264
  }
@@ -1528,8 +1284,8 @@ async function read4(env, channelSlug, limit) {
1528
1284
  if (!res.ok) {
1529
1285
  throw new Error(`Failed to read conversation: ${res.status} ${res.statusText}`);
1530
1286
  }
1531
- const messages2 = await res.json();
1532
- return messages2.slice(-limit).map((m) => {
1287
+ const messages = await res.json();
1288
+ return messages.slice(-limit).map((m) => {
1533
1289
  const text = Array.isArray(m.content) ? m.content.filter((b) => b.type === "text").map((b) => b.text).join("") : m.content;
1534
1290
  return `${m.sender_name ?? m.role}: ${text}`;
1535
1291
  }).join("\n");
@@ -1756,8 +1512,8 @@ var app3 = new Hono3().post("/:name/channels/send", requireAdmin, async (c) => {
1756
1512
  return c.json({ error: `Platform ${platform} does not support listing users` }, 400);
1757
1513
  const env = buildEnv(name);
1758
1514
  try {
1759
- const users2 = await driver.listUsers(env);
1760
- return c.json(users2);
1515
+ const users = await driver.listUsers(env);
1516
+ return c.json(users);
1761
1517
  } catch (err) {
1762
1518
  return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
1763
1519
  }
@@ -1939,14 +1695,14 @@ var sharedEnvApp = new Hono5().get("/", (c) => {
1939
1695
  var env_default = app5;
1940
1696
 
1941
1697
  // src/web/api/file-sharing.ts
1942
- import { readFileSync as readFileSync4, statSync } from "fs";
1943
- import { resolve as resolve5 } from "path";
1698
+ import { readFileSync as readFileSync5, statSync } from "fs";
1699
+ import { resolve as resolve6 } from "path";
1944
1700
  import { Hono as Hono6 } from "hono";
1945
1701
 
1946
1702
  // src/lib/file-sharing.ts
1947
1703
  import { randomBytes } from "crypto";
1948
- import { existsSync as existsSync4, mkdirSync as mkdirSync2, readdirSync as readdirSync2, readFileSync as readFileSync3, rmSync, writeFileSync as writeFileSync2 } from "fs";
1949
- 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";
1950
1706
  function validateFilePath(filePath) {
1951
1707
  if (!filePath) return "File path is required";
1952
1708
  const normalized = normalize(filePath);
@@ -1959,13 +1715,13 @@ function validateFilePath(filePath) {
1959
1715
  return null;
1960
1716
  }
1961
1717
  function configPath(dir) {
1962
- return resolve4(dir, "home", ".config", "file-sharing.json");
1718
+ return resolve5(dir, "home", ".config", "file-sharing.json");
1963
1719
  }
1964
1720
  function readFileSharingConfig(dir) {
1965
1721
  const p = configPath(dir);
1966
- if (!existsSync4(p)) return {};
1722
+ if (!existsSync5(p)) return {};
1967
1723
  try {
1968
- return JSON.parse(readFileSync3(p, "utf-8"));
1724
+ return JSON.parse(readFileSync4(p, "utf-8"));
1969
1725
  } catch (err) {
1970
1726
  console.warn(`[file-sharing] failed to parse config at ${p}:`, err);
1971
1727
  return {};
@@ -1973,8 +1729,8 @@ function readFileSharingConfig(dir) {
1973
1729
  }
1974
1730
  function writeFileSharingConfig(dir, config) {
1975
1731
  const p = configPath(dir);
1976
- mkdirSync2(resolve4(p, ".."), { recursive: true });
1977
- writeFileSync2(p, `${JSON.stringify(config, null, 2)}
1732
+ mkdirSync3(resolve5(p, ".."), { recursive: true });
1733
+ writeFileSync3(p, `${JSON.stringify(config, null, 2)}
1978
1734
  `);
1979
1735
  }
1980
1736
  function isTrustedSender(dir, sender) {
@@ -1997,7 +1753,7 @@ function removeTrust(dir, sender) {
1997
1753
  writeFileSharingConfig(dir, config);
1998
1754
  }
1999
1755
  function pendingDir(receiver) {
2000
- return resolve4(stateDir(receiver), "pending-files");
1756
+ return resolve5(stateDir(receiver), "pending-files");
2001
1757
  }
2002
1758
  function validateId(id) {
2003
1759
  if (!id || id.includes("/") || id.includes("\\") || id.includes("..")) {
@@ -2016,8 +1772,8 @@ function stageFile(receiver, sender, filename, content, originalPath) {
2016
1772
  throw new Error("Invalid sender name");
2017
1773
  }
2018
1774
  const id = generateId(sender);
2019
- const dir = resolve4(pendingDir(receiver), id);
2020
- mkdirSync2(dir, { recursive: true });
1775
+ const dir = resolve5(pendingDir(receiver), id);
1776
+ mkdirSync3(dir, { recursive: true });
2021
1777
  const metadata = {
2022
1778
  id,
2023
1779
  sender,
@@ -2026,22 +1782,22 @@ function stageFile(receiver, sender, filename, content, originalPath) {
2026
1782
  size: content.length,
2027
1783
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
2028
1784
  };
2029
- writeFileSync2(resolve4(dir, "metadata.json"), `${JSON.stringify(metadata, null, 2)}
1785
+ writeFileSync3(resolve5(dir, "metadata.json"), `${JSON.stringify(metadata, null, 2)}
2030
1786
  `);
2031
- writeFileSync2(resolve4(dir, "data"), content);
1787
+ writeFileSync3(resolve5(dir, "data"), content);
2032
1788
  return { id };
2033
1789
  }
2034
1790
  function listPending(receiver) {
2035
1791
  const dir = pendingDir(receiver);
2036
- if (!existsSync4(dir)) return [];
1792
+ if (!existsSync5(dir)) return [];
2037
1793
  const entries = readdirSync2(dir, { withFileTypes: true });
2038
1794
  const result = [];
2039
1795
  for (const entry of entries) {
2040
1796
  if (!entry.isDirectory()) continue;
2041
- const metaPath = resolve4(dir, entry.name, "metadata.json");
2042
- if (!existsSync4(metaPath)) continue;
1797
+ const metaPath = resolve5(dir, entry.name, "metadata.json");
1798
+ if (!existsSync5(metaPath)) continue;
2043
1799
  try {
2044
- result.push(JSON.parse(readFileSync3(metaPath, "utf-8")));
1800
+ result.push(JSON.parse(readFileSync4(metaPath, "utf-8")));
2045
1801
  } catch (err) {
2046
1802
  console.warn(`[file-sharing] skipping malformed pending entry ${entry.name}:`, err);
2047
1803
  }
@@ -2050,10 +1806,10 @@ function listPending(receiver) {
2050
1806
  }
2051
1807
  function getPending(receiver, id) {
2052
1808
  validateId(id);
2053
- const metaPath = resolve4(pendingDir(receiver), id, "metadata.json");
2054
- if (!existsSync4(metaPath)) return null;
1809
+ const metaPath = resolve5(pendingDir(receiver), id, "metadata.json");
1810
+ if (!existsSync5(metaPath)) return null;
2055
1811
  try {
2056
- return JSON.parse(readFileSync3(metaPath, "utf-8"));
1812
+ return JSON.parse(readFileSync4(metaPath, "utf-8"));
2057
1813
  } catch (err) {
2058
1814
  console.warn(`[file-sharing] failed to read pending metadata for ${id}:`, err);
2059
1815
  return null;
@@ -2068,27 +1824,27 @@ function deliverFile(receiverDir, sender, filename, content, inboxPath) {
2068
1824
  if (sender.includes("/") || sender.includes("\\")) {
2069
1825
  throw new Error("Invalid sender name");
2070
1826
  }
2071
- const destDir = resolve4(receiverDir, "home", inbox, sender);
2072
- mkdirSync2(destDir, { recursive: true });
2073
- const destPath = resolve4(destDir, basename(filename));
2074
- 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);
2075
1831
  return join(inbox, sender, basename(filename));
2076
1832
  }
2077
1833
  function acceptPending(receiver, id, receiverDir) {
2078
1834
  const meta = getPending(receiver, id);
2079
1835
  if (!meta) throw new Error(`Pending file not found: ${id}`);
2080
- const dataPath = resolve4(pendingDir(receiver), id, "data");
2081
- const content = readFileSync3(dataPath);
1836
+ const dataPath = resolve5(pendingDir(receiver), id, "data");
1837
+ const content = readFileSync4(dataPath);
2082
1838
  const config = readFileSharingConfig(receiverDir);
2083
1839
  const inboxPath = config.inboxPath ?? "inbox";
2084
1840
  const destPath = deliverFile(receiverDir, meta.sender, meta.filename, content, inboxPath);
2085
- rmSync(resolve4(pendingDir(receiver), id), { recursive: true });
1841
+ rmSync2(resolve5(pendingDir(receiver), id), { recursive: true });
2086
1842
  return { sender: meta.sender, filename: meta.filename, destPath };
2087
1843
  }
2088
1844
  function rejectPending(receiver, id) {
2089
1845
  const meta = getPending(receiver, id);
2090
1846
  if (!meta) throw new Error(`Pending file not found: ${id}`);
2091
- rmSync(resolve4(pendingDir(receiver), id), { recursive: true });
1847
+ rmSync2(resolve5(pendingDir(receiver), id), { recursive: true });
2092
1848
  return { sender: meta.sender, filename: meta.filename };
2093
1849
  }
2094
1850
  function formatFileSize(bytes) {
@@ -2129,7 +1885,7 @@ var app6 = new Hono6().post("/:name/files/send", async (c) => {
2129
1885
  const pathErr = validateFilePath(body.filePath);
2130
1886
  if (pathErr) return c.json({ error: pathErr }, 400);
2131
1887
  const senderDir = mindDir(senderName);
2132
- const filePath = resolve5(senderDir, "home", body.filePath);
1888
+ const filePath = resolve6(senderDir, "home", body.filePath);
2133
1889
  const MAX_FILE_SIZE = 50 * 1024 * 1024;
2134
1890
  const stat4 = statSync(filePath, { throwIfNoEntry: false });
2135
1891
  if (!stat4) return c.json({ error: `File not found: ${body.filePath}` }, 404);
@@ -2143,7 +1899,7 @@ var app6 = new Hono6().post("/:name/files/send", async (c) => {
2143
1899
  }
2144
1900
  let content;
2145
1901
  try {
2146
- content = readFileSync4(filePath);
1902
+ content = readFileSync5(filePath);
2147
1903
  } catch {
2148
1904
  return c.json({ error: `File not found: ${body.filePath}` }, 404);
2149
1905
  }
@@ -2245,19 +2001,19 @@ var app6 = new Hono6().post("/:name/files/send", async (c) => {
2245
2001
  var file_sharing_default = app6;
2246
2002
 
2247
2003
  // src/web/api/files.ts
2248
- import { existsSync as existsSync5 } from "fs";
2004
+ import { existsSync as existsSync6 } from "fs";
2249
2005
  import { readdir, readFile, realpath, stat } from "fs/promises";
2250
- import { extname, resolve as resolve6 } from "path";
2006
+ import { extname as extname2, resolve as resolve7 } from "path";
2251
2007
  import { Hono as Hono7 } from "hono";
2252
2008
  var ALLOWED_FILES = /* @__PURE__ */ new Set(["SOUL.md", "MEMORY.md", "CLAUDE.md", "VOLUTE.md"]);
2253
- var AVATAR_MIME = {
2009
+ var AVATAR_MIME2 = {
2254
2010
  ".png": "image/png",
2255
2011
  ".jpg": "image/jpeg",
2256
2012
  ".jpeg": "image/jpeg",
2257
2013
  ".gif": "image/gif",
2258
2014
  ".webp": "image/webp"
2259
2015
  };
2260
- var MAX_AVATAR_SIZE = 2 * 1024 * 1024;
2016
+ var MAX_AVATAR_SIZE2 = 2 * 1024 * 1024;
2261
2017
  var app7 = new Hono7().get("/:name/avatar", async (c) => {
2262
2018
  const name = c.req.param("name");
2263
2019
  const entry = findMind(name);
@@ -2265,11 +2021,11 @@ var app7 = new Hono7().get("/:name/avatar", async (c) => {
2265
2021
  const dir = mindDir(name);
2266
2022
  const config = readVoluteConfig(dir);
2267
2023
  if (!config?.avatar) return c.json({ error: "No avatar configured" }, 404);
2268
- const ext = extname(config.avatar).toLowerCase();
2269
- const mime = AVATAR_MIME[ext];
2024
+ const ext = extname2(config.avatar).toLowerCase();
2025
+ const mime = AVATAR_MIME2[ext];
2270
2026
  if (!mime) return c.json({ error: "Invalid avatar extension" }, 400);
2271
- const homeDir = resolve6(dir, "home");
2272
- const avatarPath = resolve6(homeDir, config.avatar);
2027
+ const homeDir = resolve7(dir, "home");
2028
+ const avatarPath = resolve7(homeDir, config.avatar);
2273
2029
  if (!avatarPath.startsWith(`${homeDir}/`)) return c.json({ error: "Invalid avatar path" }, 400);
2274
2030
  let realAvatarPath;
2275
2031
  try {
@@ -2284,7 +2040,7 @@ var app7 = new Hono7().get("/:name/avatar", async (c) => {
2284
2040
  }
2285
2041
  try {
2286
2042
  const fileStat = await stat(realAvatarPath);
2287
- 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);
2288
2044
  const body = await readFile(realAvatarPath);
2289
2045
  return c.body(body, 200, {
2290
2046
  "Content-Type": mime,
@@ -2298,8 +2054,8 @@ var app7 = new Hono7().get("/:name/avatar", async (c) => {
2298
2054
  const entry = findMind(name);
2299
2055
  if (!entry) return c.json({ error: "Mind not found" }, 404);
2300
2056
  const dir = mindDir(name);
2301
- const homeDir = resolve6(dir, "home");
2302
- 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);
2303
2059
  const allFiles = await readdir(homeDir);
2304
2060
  const files = allFiles.filter((f) => f.endsWith(".md") && ALLOWED_FILES.has(f));
2305
2061
  return c.json(files);
@@ -2312,8 +2068,8 @@ var app7 = new Hono7().get("/:name/avatar", async (c) => {
2312
2068
  const entry = findMind(name);
2313
2069
  if (!entry) return c.json({ error: "Mind not found" }, 404);
2314
2070
  const dir = mindDir(name);
2315
- const filePath = resolve6(dir, "home", filename);
2316
- if (!existsSync5(filePath)) {
2071
+ const filePath = resolve7(dir, "home", filename);
2072
+ if (!existsSync6(filePath)) {
2317
2073
  return c.json({ error: "File not found" }, 404);
2318
2074
  }
2319
2075
  const content = await readFile(filePath, "utf-8");
@@ -2326,19 +2082,19 @@ import { Hono as Hono8 } from "hono";
2326
2082
 
2327
2083
  // src/lib/identity.ts
2328
2084
  import { createHash, generateKeyPairSync, sign, verify } from "crypto";
2329
- import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
2330
- 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";
2331
2087
  function generateIdentity(mindDir2) {
2332
- const identityDir = resolve7(mindDir2, ".mind/identity");
2333
- mkdirSync3(identityDir, { recursive: true });
2088
+ const identityDir = resolve8(mindDir2, ".mind/identity");
2089
+ mkdirSync4(identityDir, { recursive: true });
2334
2090
  const { publicKey, privateKey } = generateKeyPairSync("ed25519", {
2335
2091
  publicKeyEncoding: { type: "spki", format: "pem" },
2336
2092
  privateKeyEncoding: { type: "pkcs8", format: "pem" }
2337
2093
  });
2338
- const privatePath = resolve7(identityDir, "private.pem");
2339
- const publicPath = resolve7(identityDir, "public.pem");
2340
- writeFileSync3(privatePath, privateKey, { mode: 384 });
2341
- 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 });
2342
2098
  const config = readVoluteConfig(mindDir2) ?? {};
2343
2099
  config.identity = {
2344
2100
  privateKey: ".mind/identity/private.pem",
@@ -2351,17 +2107,17 @@ function getPrivateKey(mindDir2) {
2351
2107
  const config = readVoluteConfig(mindDir2);
2352
2108
  const relPath = config?.identity?.privateKey;
2353
2109
  if (!relPath) return null;
2354
- const fullPath = resolve7(mindDir2, relPath);
2355
- if (!existsSync6(fullPath)) return null;
2356
- return readFileSync5(fullPath, "utf-8");
2110
+ const fullPath = resolve8(mindDir2, relPath);
2111
+ if (!existsSync7(fullPath)) return null;
2112
+ return readFileSync6(fullPath, "utf-8");
2357
2113
  }
2358
2114
  function getPublicKey(mindDir2) {
2359
2115
  const config = readVoluteConfig(mindDir2);
2360
2116
  const relPath = config?.identity?.publicKey;
2361
2117
  if (!relPath) return null;
2362
- const fullPath = resolve7(mindDir2, relPath);
2363
- if (!existsSync6(fullPath)) return null;
2364
- return readFileSync5(fullPath, "utf-8");
2118
+ const fullPath = resolve8(mindDir2, relPath);
2119
+ if (!existsSync7(fullPath)) return null;
2120
+ return readFileSync6(fullPath, "utf-8");
2365
2121
  }
2366
2122
  function getFingerprint(publicKeyPem) {
2367
2123
  return createHash("sha256").update(publicKeyPem).digest("hex");
@@ -2414,16 +2170,16 @@ var keys_default = app8;
2414
2170
 
2415
2171
  // src/web/api/logs.ts
2416
2172
  import { spawn } from "child_process";
2417
- import { existsSync as existsSync7 } from "fs";
2418
- import { resolve as resolve8 } from "path";
2173
+ import { existsSync as existsSync8 } from "fs";
2174
+ import { resolve as resolve9 } from "path";
2419
2175
  import { Hono as Hono9 } from "hono";
2420
2176
  import { streamSSE as streamSSE2 } from "hono/streaming";
2421
2177
  var app9 = new Hono9().get("/:name/logs", async (c) => {
2422
2178
  const name = c.req.param("name");
2423
2179
  const entry = findMind(name);
2424
2180
  if (!entry) return c.json({ error: "Mind not found" }, 404);
2425
- const logFile = resolve8(stateDir(name), "logs", "mind.log");
2426
- if (!existsSync7(logFile)) {
2181
+ const logFile = resolve9(stateDir(name), "logs", "mind.log");
2182
+ if (!existsSync8(logFile)) {
2427
2183
  return c.json({ error: "No log file found" }, 404);
2428
2184
  }
2429
2185
  return streamSSE2(c, async (stream) => {
@@ -2441,17 +2197,17 @@ var app9 = new Hono9().get("/:name/logs", async (c) => {
2441
2197
  stream.onAbort(() => {
2442
2198
  tail.kill();
2443
2199
  });
2444
- await new Promise((resolve18) => {
2445
- tail.on("exit", resolve18);
2446
- stream.onAbort(resolve18);
2200
+ await new Promise((resolve19) => {
2201
+ tail.on("exit", resolve19);
2202
+ stream.onAbort(resolve19);
2447
2203
  });
2448
2204
  });
2449
2205
  }).get("/:name/logs/tail", async (c) => {
2450
2206
  const name = c.req.param("name");
2451
2207
  const entry = findMind(name);
2452
2208
  if (!entry) return c.json({ error: "Mind not found" }, 404);
2453
- const logFile = resolve8(stateDir(name), "logs", "mind.log");
2454
- if (!existsSync7(logFile)) {
2209
+ const logFile = resolve9(stateDir(name), "logs", "mind.log");
2210
+ if (!existsSync8(logFile)) {
2455
2211
  return c.json({ error: "No log file found" }, 404);
2456
2212
  }
2457
2213
  const nParam = parseInt(c.req.query("n") ?? "50", 10);
@@ -2461,8 +2217,8 @@ var app9 = new Hono9().get("/:name/logs", async (c) => {
2461
2217
  tail.stdout.on("data", (data) => {
2462
2218
  output += data.toString();
2463
2219
  });
2464
- await new Promise((resolve18) => {
2465
- tail.on("exit", resolve18);
2220
+ await new Promise((resolve19) => {
2221
+ tail.on("exit", resolve19);
2466
2222
  });
2467
2223
  return c.text(output);
2468
2224
  });
@@ -2552,33 +2308,33 @@ var mind_skills_default = app10;
2552
2308
  // src/web/api/minds.ts
2553
2309
  import {
2554
2310
  cpSync,
2555
- existsSync as existsSync9,
2556
- mkdirSync as mkdirSync5,
2311
+ existsSync as existsSync10,
2312
+ mkdirSync as mkdirSync6,
2557
2313
  readdirSync as readdirSync4,
2558
- readFileSync as readFileSync8,
2559
- rmSync as rmSync3,
2560
- writeFileSync as writeFileSync6
2314
+ readFileSync as readFileSync9,
2315
+ rmSync as rmSync4,
2316
+ writeFileSync as writeFileSync7
2561
2317
  } from "fs";
2562
- import { resolve as resolve11 } from "path";
2318
+ import { resolve as resolve12 } from "path";
2563
2319
  import { zValidator as zValidator3 } from "@hono/zod-validator";
2564
- 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";
2565
2321
  import { Hono as Hono11 } from "hono";
2566
2322
  import { z as z3 } from "zod";
2567
2323
 
2568
2324
  // src/lib/consolidate.ts
2569
- import { readdirSync as readdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
2570
- 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";
2571
2327
  async function consolidateMemory(mindDir2) {
2572
- const soulPath = resolve9(mindDir2, "home/SOUL.md");
2573
- const memoryPath = resolve9(mindDir2, "home/MEMORY.md");
2574
- const memoryDir = resolve9(mindDir2, "home/memory");
2575
- 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");
2576
2332
  const logs = [];
2577
2333
  try {
2578
2334
  const files = readdirSync3(memoryDir).filter((f) => /^\d{4}-\d{2}-\d{2}\.md$/.test(f)).sort();
2579
2335
  for (const filename of files) {
2580
2336
  const date = filename.replace(".md", "");
2581
- const content2 = readFileSync6(resolve9(memoryDir, filename), "utf-8").trim();
2337
+ const content2 = readFileSync7(resolve10(memoryDir, filename), "utf-8").trim();
2582
2338
  if (content2) {
2583
2339
  logs.push(`### ${date}
2584
2340
 
@@ -2628,7 +2384,7 @@ ${content2}`);
2628
2384
  const data = await res.json();
2629
2385
  const content = data.content.filter((b) => b.type === "text" && b.text).map((b) => b.text).join("").trim();
2630
2386
  if (content) {
2631
- writeFileSync4(memoryPath, `${content}
2387
+ writeFileSync5(memoryPath, `${content}
2632
2388
  `);
2633
2389
  console.log("MEMORY.md created successfully.");
2634
2390
  } else {
@@ -2637,28 +2393,28 @@ ${content2}`);
2637
2393
  }
2638
2394
 
2639
2395
  // src/lib/convert-session.ts
2640
- import { randomUUID as randomUUID2 } from "crypto";
2641
- 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";
2642
2398
  import { homedir } from "os";
2643
- import { resolve as resolve10 } from "path";
2399
+ import { resolve as resolve11 } from "path";
2644
2400
  function convertSession(opts) {
2645
- const lines = readFileSync7(opts.sessionPath, "utf-8").trim().split("\n");
2646
- const sessionId = randomUUID2();
2401
+ const lines = readFileSync8(opts.sessionPath, "utf-8").trim().split("\n");
2402
+ const sessionId = randomUUID();
2647
2403
  const idMap = /* @__PURE__ */ new Map();
2648
- const messages2 = [];
2404
+ const messages = [];
2649
2405
  for (const line of lines) {
2650
2406
  const event = JSON.parse(line);
2651
2407
  if (event.type === "message" && event.message) {
2652
- messages2.push(event);
2408
+ messages.push(event);
2653
2409
  }
2654
2410
  }
2655
2411
  const sdkEvents = [];
2656
2412
  let lastSdkUuid = null;
2657
- for (let i = 0; i < messages2.length; i++) {
2658
- const event = messages2[i];
2413
+ for (let i = 0; i < messages.length; i++) {
2414
+ const event = messages[i];
2659
2415
  const msg = event.message;
2660
2416
  if (msg.role === "user") {
2661
- const uuid = randomUUID2();
2417
+ const uuid = randomUUID();
2662
2418
  idMap.set(event.id, uuid);
2663
2419
  const parentUuid = event.parentId ? idMap.get(event.parentId) ?? null : null;
2664
2420
  const sdkEvent = {
@@ -2682,7 +2438,7 @@ function convertSession(opts) {
2682
2438
  } else if (msg.role === "assistant") {
2683
2439
  const content = convertAssistantContent(msg.content);
2684
2440
  if (content.length === 0) continue;
2685
- const uuid = randomUUID2();
2441
+ const uuid = randomUUID();
2686
2442
  idMap.set(event.id, uuid);
2687
2443
  const parentUuid = event.parentId ? idMap.get(event.parentId) ?? null : null;
2688
2444
  const stopReason = mapStopReason(msg.stopReason);
@@ -2697,12 +2453,12 @@ function convertSession(opts) {
2697
2453
  isSidechain: false,
2698
2454
  userType: "external",
2699
2455
  type: "assistant",
2700
- requestId: `req_imported_${randomUUID2()}`,
2456
+ requestId: `req_imported_${randomUUID()}`,
2701
2457
  message: {
2702
2458
  role: "assistant",
2703
2459
  content,
2704
2460
  type: "message",
2705
- id: `msg_imported_${randomUUID2()}`,
2461
+ id: `msg_imported_${randomUUID()}`,
2706
2462
  model: mapModel(msg.model),
2707
2463
  stop_reason: stopReason,
2708
2464
  stop_sequence: null,
@@ -2716,8 +2472,8 @@ function convertSession(opts) {
2716
2472
  let lastToolResultId = event.id;
2717
2473
  let lastTimestamp = event.timestamp;
2718
2474
  let j = i;
2719
- while (j < messages2.length && messages2[j].message.role === "toolResult") {
2720
- const tr = messages2[j];
2475
+ while (j < messages.length && messages[j].message.role === "toolResult") {
2476
+ const tr = messages[j];
2721
2477
  const trMsg = tr.message;
2722
2478
  lastToolResultId = tr.id;
2723
2479
  lastTimestamp = tr.timestamp;
@@ -2730,7 +2486,7 @@ function convertSession(opts) {
2730
2486
  j++;
2731
2487
  }
2732
2488
  i = j - 1;
2733
- const uuid = randomUUID2();
2489
+ const uuid = randomUUID();
2734
2490
  idMap.set(lastToolResultId, uuid);
2735
2491
  const parentUuid = event.parentId ? idMap.get(event.parentId) ?? null : lastSdkUuid;
2736
2492
  const sdkEvent = {
@@ -2756,10 +2512,10 @@ function convertSession(opts) {
2756
2512
  }
2757
2513
  }
2758
2514
  const projectId = opts.projectDir.replace(/\//g, "-");
2759
- const sdkDir = resolve10(homedir(), ".claude", "projects", projectId);
2760
- mkdirSync4(sdkDir, { recursive: true });
2761
- const sdkPath = resolve10(sdkDir, `${sessionId}.jsonl`);
2762
- 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")}
2763
2519
  `);
2764
2520
  console.log(`Converted ${sdkEvents.length} messages \u2192 ${sdkPath}`);
2765
2521
  return sessionId;
@@ -2810,36 +2566,8 @@ function convertAssistantContent(content) {
2810
2566
  return result;
2811
2567
  }
2812
2568
 
2813
- // src/lib/events/mind-events.ts
2814
- var subscribers = /* @__PURE__ */ new Map();
2815
- function subscribe3(mind, callback) {
2816
- let set = subscribers.get(mind);
2817
- if (!set) {
2818
- set = /* @__PURE__ */ new Set();
2819
- subscribers.set(mind, set);
2820
- }
2821
- set.add(callback);
2822
- return () => {
2823
- set.delete(callback);
2824
- if (set.size === 0) subscribers.delete(mind);
2825
- };
2826
- }
2827
- function publish2(mind, event) {
2828
- const set = subscribers.get(mind);
2829
- if (!set) return;
2830
- for (const cb of set) {
2831
- try {
2832
- cb(event);
2833
- } catch (err) {
2834
- console.error("[mind-events] subscriber threw:", err);
2835
- set.delete(cb);
2836
- if (set.size === 0) subscribers.delete(mind);
2837
- }
2838
- }
2839
- }
2840
-
2841
2569
  // src/lib/variant-cleanup.ts
2842
- import { existsSync as existsSync8, rmSync as rmSync2 } from "fs";
2570
+ import { existsSync as existsSync9, rmSync as rmSync3 } from "fs";
2843
2571
  async function cleanupVariant(mindName, variantName, projectRoot, variantPath, opts) {
2844
2572
  if (opts?.stop) {
2845
2573
  try {
@@ -2847,11 +2575,11 @@ async function cleanupVariant(mindName, variantName, projectRoot, variantPath, o
2847
2575
  } catch {
2848
2576
  }
2849
2577
  }
2850
- if (existsSync8(variantPath)) {
2578
+ if (existsSync9(variantPath)) {
2851
2579
  try {
2852
2580
  await gitExec(["worktree", "remove", "--force", variantPath], { cwd: projectRoot });
2853
2581
  } catch {
2854
- rmSync2(variantPath, { recursive: true, force: true });
2582
+ rmSync3(variantPath, { recursive: true, force: true });
2855
2583
  try {
2856
2584
  await gitExec(["worktree", "prune"], { cwd: projectRoot });
2857
2585
  } catch {
@@ -2878,7 +2606,7 @@ async function getMindStatus(name, port) {
2878
2606
  const manager = getMindManager();
2879
2607
  let status = "stopped";
2880
2608
  try {
2881
- const { getSleepManagerIfReady } = await import("./sleep-manager-3RWUX2ZR.js");
2609
+ const { getSleepManagerIfReady } = await import("./sleep-manager-XXSWQQLE.js");
2882
2610
  if (getSleepManagerIfReady()?.isSleeping(name)) {
2883
2611
  status = "sleeping";
2884
2612
  }
@@ -2936,7 +2664,7 @@ async function initTemplateBranch(projectRoot, composedDir, manifest, mindName,
2936
2664
  await gitExec(["commit", "-m", "initial commit"], opts);
2937
2665
  }
2938
2666
  async function updateTemplateBranch(projectRoot, template, mindName) {
2939
- const tempWorktree = resolve11(projectRoot, ".variants", "_template_update");
2667
+ const tempWorktree = resolve12(projectRoot, ".variants", "_template_update");
2940
2668
  let branchExists = false;
2941
2669
  try {
2942
2670
  await gitExec(["rev-parse", "--verify", TEMPLATE_BRANCH], { cwd: projectRoot });
@@ -2947,8 +2675,8 @@ async function updateTemplateBranch(projectRoot, template, mindName) {
2947
2675
  await gitExec(["worktree", "remove", "--force", tempWorktree], { cwd: projectRoot });
2948
2676
  } catch {
2949
2677
  }
2950
- if (existsSync9(tempWorktree)) {
2951
- rmSync3(tempWorktree, { recursive: true, force: true });
2678
+ if (existsSync10(tempWorktree)) {
2679
+ rmSync4(tempWorktree, { recursive: true, force: true });
2952
2680
  }
2953
2681
  const templatesRoot = findTemplatesRoot();
2954
2682
  const { composedDir, manifest } = composeTemplate(templatesRoot, template);
@@ -2968,9 +2696,9 @@ async function updateTemplateBranch(projectRoot, template, mindName) {
2968
2696
  });
2969
2697
  }
2970
2698
  copyTemplateToDir(composedDir, tempWorktree, mindName, manifest);
2971
- const initDir = resolve11(tempWorktree, ".init");
2972
- if (existsSync9(initDir)) {
2973
- rmSync3(initDir, { recursive: true, force: true });
2699
+ const initDir = resolve12(tempWorktree, ".init");
2700
+ if (existsSync10(initDir)) {
2701
+ rmSync4(initDir, { recursive: true, force: true });
2974
2702
  }
2975
2703
  await gitExec(["add", "-A"], { cwd: tempWorktree });
2976
2704
  try {
@@ -2983,10 +2711,10 @@ async function updateTemplateBranch(projectRoot, template, mindName) {
2983
2711
  await gitExec(["worktree", "remove", "--force", tempWorktree], { cwd: projectRoot });
2984
2712
  } catch {
2985
2713
  }
2986
- if (existsSync9(tempWorktree)) {
2987
- rmSync3(tempWorktree, { recursive: true, force: true });
2714
+ if (existsSync10(tempWorktree)) {
2715
+ rmSync4(tempWorktree, { recursive: true, force: true });
2988
2716
  }
2989
- rmSync3(composedDir, { recursive: true, force: true });
2717
+ rmSync4(composedDir, { recursive: true, force: true });
2990
2718
  }
2991
2719
  }
2992
2720
  async function mergeTemplateBranch(worktreeDir) {
@@ -3009,14 +2737,14 @@ async function mergeTemplateBranch(worktreeDir) {
3009
2737
  async function npmInstallAsMind(cwd, mindName) {
3010
2738
  if (isIsolationEnabled()) {
3011
2739
  const [cmd, args] = wrapForIsolation("npm", ["install"], mindName);
3012
- 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") } });
3013
2741
  } else {
3014
2742
  await exec("npm", ["install"], { cwd });
3015
2743
  }
3016
2744
  }
3017
2745
  async function importFromArchive(c, tempDir, nameOverride, manifest) {
3018
- const extractedMindDir = resolve11(tempDir, "mind");
3019
- if (!existsSync9(extractedMindDir)) {
2746
+ const extractedMindDir = resolve12(tempDir, "mind");
2747
+ if (!existsSync10(extractedMindDir)) {
3020
2748
  return c.json({ error: "Invalid archive: missing mind/ directory" }, 400);
3021
2749
  }
3022
2750
  if (!manifest?.includes || !manifest.name || !manifest.template) {
@@ -3034,21 +2762,21 @@ async function importFromFullArchive(c, tempDir, extractedMindDir, nameOverride,
3034
2762
  if (findMind(name)) return c.json({ error: `Mind already exists: ${name}` }, 409);
3035
2763
  ensureVoluteHome();
3036
2764
  const dest = mindDir(name);
3037
- 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);
3038
2766
  try {
3039
2767
  cpSync(extractedMindDir, dest, { recursive: true });
3040
2768
  if (!manifest.includes.identity) {
3041
2769
  generateIdentity(dest);
3042
2770
  }
3043
2771
  const state = stateDir(name);
3044
- mkdirSync5(state, { recursive: true });
3045
- const channelsJson = resolve11(tempDir, "state/channels.json");
3046
- if (existsSync9(channelsJson)) {
3047
- 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"));
3048
2776
  }
3049
- const envJson = resolve11(tempDir, "state/env.json");
3050
- if (existsSync9(envJson)) {
3051
- 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"));
3052
2780
  }
3053
2781
  const port = nextPort();
3054
2782
  addMind(name, port, manifest.stage, manifest.template);
@@ -3057,36 +2785,36 @@ async function importFromFullArchive(c, tempDir, extractedMindDir, nameOverride,
3057
2785
  } catch (err) {
3058
2786
  logger_default.warn(`failed to set template hash for ${name}`, logger_default.errorData(err));
3059
2787
  }
3060
- const homeDir = resolve11(dest, "home");
2788
+ const homeDir = resolve12(dest, "home");
3061
2789
  ensureVoluteGroup();
3062
2790
  createMindUser(name, homeDir);
3063
2791
  chownMindDir(dest, name);
3064
2792
  await npmInstallAsMind(dest, name);
3065
2793
  await importHistoryFromArchive(name, tempDir);
3066
2794
  importSessionsFromArchive(dest, tempDir);
3067
- if (!existsSync9(resolve11(dest, ".git"))) {
2795
+ if (!existsSync10(resolve12(dest, ".git"))) {
3068
2796
  try {
3069
- const env = isIsolationEnabled() ? { ...process.env, HOME: resolve11(dest, "home") } : void 0;
2797
+ const env = isIsolationEnabled() ? { ...process.env, HOME: resolve12(dest, "home") } : void 0;
3070
2798
  await gitExec(["init"], { cwd: dest, mindName: name, env });
3071
2799
  await configureGitIdentity(name, { cwd: dest, mindName: name, env });
3072
2800
  await gitExec(["add", "-A"], { cwd: dest, mindName: name, env });
3073
2801
  await gitExec(["commit", "-m", "import from archive"], { cwd: dest, mindName: name, env });
3074
2802
  } catch (err) {
3075
2803
  logger_default.error(`git setup failed for imported mind ${name}`, logger_default.errorData(err));
3076
- rmSync3(resolve11(dest, ".git"), { recursive: true, force: true });
2804
+ rmSync4(resolve12(dest, ".git"), { recursive: true, force: true });
3077
2805
  }
3078
2806
  }
3079
2807
  chownMindDir(dest, name);
3080
- rmSync3(tempDir, { recursive: true, force: true });
2808
+ rmSync4(tempDir, { recursive: true, force: true });
3081
2809
  return c.json({ ok: true, name, port, message: `Imported mind: ${name} (port ${port})` });
3082
2810
  } catch (err) {
3083
- if (existsSync9(dest)) rmSync3(dest, { recursive: true, force: true });
2811
+ if (existsSync10(dest)) rmSync4(dest, { recursive: true, force: true });
3084
2812
  try {
3085
2813
  removeMind(name);
3086
2814
  } catch (cleanupErr) {
3087
2815
  logger_default.error(`Failed to clean up registry for ${name}`, logger_default.errorData(cleanupErr));
3088
2816
  }
3089
- rmSync3(tempDir, { recursive: true, force: true });
2817
+ rmSync4(tempDir, { recursive: true, force: true });
3090
2818
  return c.json({ error: err instanceof Error ? err.message : "Failed to import mind" }, 500);
3091
2819
  }
3092
2820
  }
@@ -3097,7 +2825,7 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
3097
2825
  if (findMind(name)) return c.json({ error: `Mind already exists: ${name}` }, 409);
3098
2826
  ensureVoluteHome();
3099
2827
  const dest = mindDir(name);
3100
- 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);
3101
2829
  const templatesRoot = findTemplatesRoot();
3102
2830
  const { composedDir, manifest: templateManifest } = composeTemplate(
3103
2831
  templatesRoot,
@@ -3106,40 +2834,40 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
3106
2834
  try {
3107
2835
  copyTemplateToDir(composedDir, dest, name, templateManifest);
3108
2836
  applyInitFiles(dest);
3109
- const extractedHome = resolve11(extractedMindDir, "home");
3110
- if (existsSync9(extractedHome)) {
3111
- 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 });
3112
2840
  }
3113
- const extractedMindInternal = resolve11(extractedMindDir, ".mind");
3114
- if (existsSync9(extractedMindInternal)) {
3115
- 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 });
3116
2844
  }
3117
- const identityDir = resolve11(dest, ".mind/identity");
2845
+ const identityDir = resolve12(dest, ".mind/identity");
3118
2846
  let publicKeyPem;
3119
- if (!manifest.includes.identity || !existsSync9(resolve11(identityDir, "private.pem"))) {
2847
+ if (!manifest.includes.identity || !existsSync10(resolve12(identityDir, "private.pem"))) {
3120
2848
  ({ publicKeyPem } = generateIdentity(dest));
3121
2849
  } else {
3122
- publicKeyPem = readFileSync8(resolve11(identityDir, "public.pem"), "utf-8");
2850
+ publicKeyPem = readFileSync9(resolve12(identityDir, "public.pem"), "utf-8");
3123
2851
  }
3124
- const promptsPath = resolve11(dest, "home/.config/prompts.json");
3125
- if (!existsSync9(promptsPath)) {
2852
+ const promptsPath = resolve12(dest, "home/.config/prompts.json");
2853
+ if (!existsSync10(promptsPath)) {
3126
2854
  const mindPrompts = await getMindPromptDefaults();
3127
- writeFileSync6(promptsPath, `${JSON.stringify(mindPrompts, null, 2)}
2855
+ writeFileSync7(promptsPath, `${JSON.stringify(mindPrompts, null, 2)}
3128
2856
  `);
3129
2857
  }
3130
2858
  const state = stateDir(name);
3131
- mkdirSync5(state, { recursive: true });
3132
- const channelsJson = resolve11(tempDir, "state/channels.json");
3133
- if (existsSync9(channelsJson)) {
3134
- 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"));
3135
2863
  }
3136
- const envJson = resolve11(tempDir, "state/env.json");
3137
- if (existsSync9(envJson)) {
3138
- 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"));
3139
2867
  }
3140
2868
  const port = nextPort();
3141
2869
  addMind(name, port, manifest.stage, manifest.template);
3142
- const homeDir = resolve11(dest, "home");
2870
+ const homeDir = resolve12(dest, "home");
3143
2871
  ensureVoluteGroup();
3144
2872
  createMindUser(name, homeDir);
3145
2873
  chownMindDir(dest, name);
@@ -3152,7 +2880,7 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
3152
2880
  await initTemplateBranch(dest, composedDir, templateManifest, name, env);
3153
2881
  } catch (err) {
3154
2882
  logger_default.error(`git setup failed for imported mind ${name}`, logger_default.errorData(err));
3155
- rmSync3(resolve11(dest, ".git"), { recursive: true, force: true });
2883
+ rmSync4(resolve12(dest, ".git"), { recursive: true, force: true });
3156
2884
  gitWarning = "Git setup failed \u2014 variants and upgrades won't be available until git is initialized.";
3157
2885
  }
3158
2886
  try {
@@ -3176,7 +2904,7 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
3176
2904
  publishPublicKey(name, publicKeyPem).catch(
3177
2905
  (err) => logger_default.warn(`failed to publish key for ${name}`, { error: err.message })
3178
2906
  );
3179
- rmSync3(tempDir, { recursive: true, force: true });
2907
+ rmSync4(tempDir, { recursive: true, force: true });
3180
2908
  return c.json({
3181
2909
  ok: true,
3182
2910
  name,
@@ -3187,24 +2915,24 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
3187
2915
  ...skillWarnings.length > 0 && { skillWarnings }
3188
2916
  });
3189
2917
  } catch (err) {
3190
- if (existsSync9(dest)) rmSync3(dest, { recursive: true, force: true });
2918
+ if (existsSync10(dest)) rmSync4(dest, { recursive: true, force: true });
3191
2919
  try {
3192
2920
  removeMind(name);
3193
2921
  } catch (cleanupErr) {
3194
2922
  logger_default.error(`Failed to clean up registry for ${name}`, logger_default.errorData(cleanupErr));
3195
2923
  }
3196
- rmSync3(tempDir, { recursive: true, force: true });
2924
+ rmSync4(tempDir, { recursive: true, force: true });
3197
2925
  return c.json({ error: err instanceof Error ? err.message : "Failed to import mind" }, 500);
3198
2926
  } finally {
3199
- rmSync3(composedDir, { recursive: true, force: true });
2927
+ rmSync4(composedDir, { recursive: true, force: true });
3200
2928
  }
3201
2929
  }
3202
2930
  async function importHistoryFromArchive(name, tempDir) {
3203
- const historyJsonl = resolve11(tempDir, "history.jsonl");
3204
- if (!existsSync9(historyJsonl)) return;
2931
+ const historyJsonl = resolve12(tempDir, "history.jsonl");
2932
+ if (!existsSync10(historyJsonl)) return;
3205
2933
  try {
3206
2934
  const db = await getDb();
3207
- const lines = readFileSync8(historyJsonl, "utf-8").trim().split("\n");
2935
+ const lines = readFileSync9(historyJsonl, "utf-8").trim().split("\n");
3208
2936
  let imported = 0;
3209
2937
  let failed = 0;
3210
2938
  for (const line of lines) {
@@ -3240,13 +2968,13 @@ async function importHistoryFromArchive(name, tempDir) {
3240
2968
  }
3241
2969
  }
3242
2970
  function importSessionsFromArchive(dest, tempDir) {
3243
- const sessionsDir = resolve11(tempDir, "sessions");
3244
- if (!existsSync9(sessionsDir)) return;
2971
+ const sessionsDir = resolve12(tempDir, "sessions");
2972
+ if (!existsSync10(sessionsDir)) return;
3245
2973
  try {
3246
- const destSessions = resolve11(dest, ".mind/sessions");
3247
- mkdirSync5(destSessions, { recursive: true });
2974
+ const destSessions = resolve12(dest, ".mind/sessions");
2975
+ mkdirSync6(destSessions, { recursive: true });
3248
2976
  for (const file of readdirSync4(sessionsDir)) {
3249
- cpSync(resolve11(sessionsDir, file), resolve11(destSessions, file));
2977
+ cpSync(resolve12(sessionsDir, file), resolve12(destSessions, file));
3250
2978
  }
3251
2979
  } catch (err) {
3252
2980
  logger_default.error("Failed to import sessions from archive", logger_default.errorData(err));
@@ -3269,7 +2997,7 @@ var app11 = new Hono11().post("/", requireAdmin, zValidator3("json", createMindS
3269
2997
  if (findMind(name)) return c.json({ error: `Mind already exists: ${name}` }, 409);
3270
2998
  ensureVoluteHome();
3271
2999
  const dest = mindDir(name);
3272
- 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);
3273
3001
  const templatesRoot = findTemplatesRoot();
3274
3002
  const { composedDir, manifest } = composeTemplate(templatesRoot, template);
3275
3003
  try {
@@ -3283,15 +3011,15 @@ var app11 = new Hono11().post("/", requireAdmin, zValidator3("json", createMindS
3283
3011
  writeVoluteConfig(dest, seedConfig);
3284
3012
  }
3285
3013
  if (body.model) {
3286
- const configPath2 = resolve11(dest, "home/.config/config.json");
3287
- 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")) : {};
3288
3016
  existing.model = body.model;
3289
- writeFileSync6(configPath2, `${JSON.stringify(existing, null, 2)}
3017
+ writeFileSync7(configPath2, `${JSON.stringify(existing, null, 2)}
3290
3018
  `);
3291
3019
  }
3292
3020
  const mindPrompts = await getMindPromptDefaults();
3293
- writeFileSync6(
3294
- resolve11(dest, "home/.config/prompts.json"),
3021
+ writeFileSync7(
3022
+ resolve12(dest, "home/.config/prompts.json"),
3295
3023
  `${JSON.stringify(mindPrompts, null, 2)}
3296
3024
  `
3297
3025
  );
@@ -3302,7 +3030,7 @@ var app11 = new Hono11().post("/", requireAdmin, zValidator3("json", createMindS
3302
3030
  } catch (err) {
3303
3031
  logger_default.warn(`failed to set template hash for ${name}`, logger_default.errorData(err));
3304
3032
  }
3305
- const homeDir = resolve11(dest, "home");
3033
+ const homeDir = resolve12(dest, "home");
3306
3034
  ensureVoluteGroup();
3307
3035
  createMindUser(name, homeDir);
3308
3036
  chownMindDir(dest, name);
@@ -3315,7 +3043,7 @@ var app11 = new Hono11().post("/", requireAdmin, zValidator3("json", createMindS
3315
3043
  await initTemplateBranch(dest, composedDir, manifest, name, env);
3316
3044
  } catch (err) {
3317
3045
  logger_default.error(`git setup failed for ${name}`, logger_default.errorData(err));
3318
- rmSync3(resolve11(dest, ".git"), { recursive: true, force: true });
3046
+ rmSync4(resolve12(dest, ".git"), { recursive: true, force: true });
3319
3047
  gitWarning = "Git setup failed \u2014 variants and upgrades won't be available until git is initialized.";
3320
3048
  }
3321
3049
  try {
@@ -3330,7 +3058,7 @@ The human who planted you described you as: "${body.description}"
3330
3058
  ` : "";
3331
3059
  const seedSoulRaw = body.seedSoul ?? await getPrompt("seed_soul", { name, description: descLine });
3332
3060
  const seedSoul = body.seedSoul ? substitute(seedSoulRaw, { name, description: descLine }) : seedSoulRaw;
3333
- writeFileSync6(resolve11(dest, "home/SOUL.md"), seedSoul);
3061
+ writeFileSync7(resolve12(dest, "home/SOUL.md"), seedSoul);
3334
3062
  }
3335
3063
  const skillSet = body.skills ?? (body.stage === "seed" ? SEED_SKILLS : STANDARD_SKILLS);
3336
3064
  const skillWarnings = [];
@@ -3345,11 +3073,11 @@ The human who planted you described you as: "${body.description}"
3345
3073
  if (body.stage !== "seed") {
3346
3074
  const customSoul = await getPromptIfCustom("default_soul");
3347
3075
  if (customSoul) {
3348
- writeFileSync6(resolve11(dest, "home/SOUL.md"), customSoul.replace(/\{\{name\}\}/g, name));
3076
+ writeFileSync7(resolve12(dest, "home/SOUL.md"), customSoul.replace(/\{\{name\}\}/g, name));
3349
3077
  }
3350
3078
  const customMemory = await getPromptIfCustom("default_memory");
3351
3079
  if (customMemory) {
3352
- writeFileSync6(resolve11(dest, "home/MEMORY.md"), customMemory);
3080
+ writeFileSync7(resolve12(dest, "home/MEMORY.md"), customMemory);
3353
3081
  }
3354
3082
  }
3355
3083
  publishPublicKey(name, publicKeyPem).catch(
@@ -3376,14 +3104,14 @@ The human who planted you described you as: "${body.description}"
3376
3104
  ...skillWarnings.length > 0 && { skillWarnings }
3377
3105
  });
3378
3106
  } catch (err) {
3379
- if (existsSync9(dest)) rmSync3(dest, { recursive: true, force: true });
3107
+ if (existsSync10(dest)) rmSync4(dest, { recursive: true, force: true });
3380
3108
  try {
3381
3109
  removeMind(name);
3382
3110
  } catch {
3383
3111
  }
3384
3112
  return c.json({ error: err instanceof Error ? err.message : "Failed to create mind" }, 500);
3385
3113
  } finally {
3386
- rmSync3(composedDir, { recursive: true, force: true });
3114
+ rmSync4(composedDir, { recursive: true, force: true });
3387
3115
  }
3388
3116
  }).post("/import", requireAdmin, async (c) => {
3389
3117
  let body;
@@ -3396,13 +3124,13 @@ The human who planted you described you as: "${body.description}"
3396
3124
  return importFromArchive(c, body.archivePath, body.name, body.manifest);
3397
3125
  }
3398
3126
  const wsDir = body.workspacePath;
3399
- 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"))) {
3400
3128
  return c.json({ error: "Invalid workspace: missing SOUL.md or IDENTITY.md" }, 400);
3401
3129
  }
3402
- const soul = readFileSync8(resolve11(wsDir, "SOUL.md"), "utf-8");
3403
- const identity = readFileSync8(resolve11(wsDir, "IDENTITY.md"), "utf-8");
3404
- const userPath = resolve11(wsDir, "USER.md");
3405
- 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") : "";
3406
3134
  const name = body.name ?? parseNameFromIdentity(identity) ?? "imported-mind";
3407
3135
  const template = body.template ?? "claude";
3408
3136
  const nameErr = validateMindName(name);
@@ -3422,33 +3150,33 @@ ${user.trimEnd()}
3422
3150
  ` : "";
3423
3151
  ensureVoluteHome();
3424
3152
  const dest = mindDir(name);
3425
- 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);
3426
3154
  const templatesRoot = findTemplatesRoot();
3427
3155
  const { composedDir, manifest } = composeTemplate(templatesRoot, template);
3428
3156
  try {
3429
3157
  copyTemplateToDir(composedDir, dest, name, manifest);
3430
3158
  applyInitFiles(dest);
3431
3159
  const { publicKeyPem: importPublicKey } = generateIdentity(dest);
3432
- writeFileSync6(resolve11(dest, "home/SOUL.md"), mergedSoul);
3433
- const wsMemoryPath = resolve11(wsDir, "MEMORY.md");
3434
- const hasMemory = existsSync9(wsMemoryPath);
3160
+ writeFileSync7(resolve12(dest, "home/SOUL.md"), mergedSoul);
3161
+ const wsMemoryPath = resolve12(wsDir, "MEMORY.md");
3162
+ const hasMemory = existsSync10(wsMemoryPath);
3435
3163
  if (hasMemory) {
3436
- const existingMemory = readFileSync8(wsMemoryPath, "utf-8");
3437
- writeFileSync6(
3438
- resolve11(dest, "home/MEMORY.md"),
3164
+ const existingMemory = readFileSync9(wsMemoryPath, "utf-8");
3165
+ writeFileSync7(
3166
+ resolve12(dest, "home/MEMORY.md"),
3439
3167
  `${existingMemory.trimEnd()}${mergedMemoryExtra}`
3440
3168
  );
3441
3169
  } else if (user) {
3442
- writeFileSync6(resolve11(dest, "home/MEMORY.md"), `${user.trimEnd()}
3170
+ writeFileSync7(resolve12(dest, "home/MEMORY.md"), `${user.trimEnd()}
3443
3171
  `);
3444
3172
  }
3445
- const wsMemoryDir = resolve11(wsDir, "memory");
3173
+ const wsMemoryDir = resolve12(wsDir, "memory");
3446
3174
  let dailyLogCount = 0;
3447
- if (existsSync9(wsMemoryDir)) {
3448
- const destMemoryDir = resolve11(dest, "home/memory");
3175
+ if (existsSync10(wsMemoryDir)) {
3176
+ const destMemoryDir = resolve12(dest, "home/memory");
3449
3177
  const files = readdirSync4(wsMemoryDir).filter((f) => f.endsWith(".md"));
3450
3178
  for (const file of files) {
3451
- cpSync(resolve11(wsMemoryDir, file), resolve11(destMemoryDir, file));
3179
+ cpSync(resolve12(wsMemoryDir, file), resolve12(destMemoryDir, file));
3452
3180
  }
3453
3181
  dailyLogCount = files.length;
3454
3182
  }
@@ -3459,7 +3187,7 @@ ${user.trimEnd()}
3459
3187
  } catch (err) {
3460
3188
  logger_default.warn(`failed to set template hash for ${name}`, logger_default.errorData(err));
3461
3189
  }
3462
- const homeDir = resolve11(dest, "home");
3190
+ const homeDir = resolve12(dest, "home");
3463
3191
  ensureVoluteGroup();
3464
3192
  createMindUser(name, homeDir);
3465
3193
  chownMindDir(dest, name);
@@ -3467,20 +3195,20 @@ ${user.trimEnd()}
3467
3195
  if (!hasMemory && dailyLogCount > 0) {
3468
3196
  await consolidateMemory(dest);
3469
3197
  }
3470
- const env = isIsolationEnabled() ? { ...process.env, HOME: resolve11(dest, "home") } : void 0;
3198
+ const env = isIsolationEnabled() ? { ...process.env, HOME: resolve12(dest, "home") } : void 0;
3471
3199
  await gitExec(["init"], { cwd: dest, mindName: name, env });
3472
3200
  await configureGitIdentity(name, { cwd: dest, mindName: name, env });
3473
3201
  await gitExec(["add", "-A"], { cwd: dest, mindName: name, env });
3474
3202
  await gitExec(["commit", "-m", "import from OpenClaw"], { cwd: dest, mindName: name, env });
3475
- const sessionFile = body.sessionPath ? resolve11(body.sessionPath) : findOpenClawSession(wsDir);
3476
- if (sessionFile && existsSync9(sessionFile)) {
3203
+ const sessionFile = body.sessionPath ? resolve12(body.sessionPath) : findOpenClawSession(wsDir);
3204
+ if (sessionFile && existsSync10(sessionFile)) {
3477
3205
  if (template === "pi") {
3478
3206
  importPiSession(sessionFile, dest);
3479
3207
  } else if (template === "claude") {
3480
3208
  const sessionId = convertSession({ sessionPath: sessionFile, projectDir: dest });
3481
- const mindRuntimeDir = resolve11(dest, ".mind");
3482
- mkdirSync5(mindRuntimeDir, { recursive: true });
3483
- 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 }));
3484
3212
  }
3485
3213
  }
3486
3214
  importOpenClawConnectors(name, dest);
@@ -3495,14 +3223,14 @@ ${user.trimEnd()}
3495
3223
  );
3496
3224
  return c.json({ ok: true, name, port, message: `Imported mind: ${name} (port ${port})` });
3497
3225
  } catch (err) {
3498
- if (existsSync9(dest)) rmSync3(dest, { recursive: true, force: true });
3226
+ if (existsSync10(dest)) rmSync4(dest, { recursive: true, force: true });
3499
3227
  try {
3500
3228
  removeMind(name);
3501
3229
  } catch {
3502
3230
  }
3503
3231
  return c.json({ error: err instanceof Error ? err.message : "Failed to import mind" }, 500);
3504
3232
  } finally {
3505
- rmSync3(composedDir, { recursive: true, force: true });
3233
+ rmSync4(composedDir, { recursive: true, force: true });
3506
3234
  }
3507
3235
  }).get("/", async (c) => {
3508
3236
  const entries = readRegistry();
@@ -3511,7 +3239,7 @@ ${user.trimEnd()}
3511
3239
  const db = await getDb();
3512
3240
  const lastActiveRows = await db.select({
3513
3241
  mind: mindHistory.mind,
3514
- lastActiveAt: sql2`MAX(${mindHistory.created_at})`
3242
+ lastActiveAt: sql`MAX(${mindHistory.created_at})`
3515
3243
  }).from(mindHistory).groupBy(mindHistory.mind);
3516
3244
  lastActiveMap = new Map(lastActiveRows.map((r) => [r.mind, r.lastActiveAt]));
3517
3245
  } catch {
@@ -3519,7 +3247,7 @@ ${user.trimEnd()}
3519
3247
  const minds = await Promise.all(
3520
3248
  entries.map(async (entry) => {
3521
3249
  const mindStatus = await getMindStatus(entry.name, entry.port);
3522
- const hasPages = existsSync9(resolve11(mindDir(entry.name), "home", "pages"));
3250
+ const hasPages = existsSync10(resolve12(mindDir(entry.name), "home", "pages"));
3523
3251
  return {
3524
3252
  ...entry,
3525
3253
  ...mindStatus,
@@ -3537,7 +3265,7 @@ ${user.trimEnd()}
3537
3265
  const name = c.req.param("name");
3538
3266
  const entry = findMind(name);
3539
3267
  if (!entry) return c.json({ error: "Mind not found" }, 404);
3540
- 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);
3541
3269
  const mindStatus = await getMindStatus(name, entry.port);
3542
3270
  const variants = readVariants(name);
3543
3271
  const manager = getMindManager();
@@ -3552,7 +3280,7 @@ ${user.trimEnd()}
3552
3280
  return { name: v.name, port: v.port, status: variantStatus };
3553
3281
  })
3554
3282
  );
3555
- const hasPages = existsSync9(resolve11(mindDir(name), "home", "pages"));
3283
+ const hasPages = existsSync10(resolve12(mindDir(name), "home", "pages"));
3556
3284
  return c.json({ ...entry, ...mindStatus, variants: variantStatuses, hasPages });
3557
3285
  }).post("/:name/start", requireAdmin, async (c) => {
3558
3286
  const name = c.req.param("name");
@@ -3566,7 +3294,7 @@ ${user.trimEnd()}
3566
3294
  targetPort = variant.port;
3567
3295
  } else {
3568
3296
  const dir = mindDir(baseName);
3569
- if (!existsSync9(dir)) return c.json({ error: "Mind directory missing" }, 404);
3297
+ if (!existsSync10(dir)) return c.json({ error: "Mind directory missing" }, 404);
3570
3298
  }
3571
3299
  if (getMindManager().isRunning(name)) {
3572
3300
  return c.json({ error: "Mind already running" }, 409);
@@ -3589,7 +3317,7 @@ ${user.trimEnd()}
3589
3317
  targetPort = variant.port;
3590
3318
  } else {
3591
3319
  const dir = mindDir(baseName);
3592
- if (!existsSync9(dir)) return c.json({ error: "Mind directory missing" }, 404);
3320
+ if (!existsSync10(dir)) return c.json({ error: "Mind directory missing" }, 404);
3593
3321
  }
3594
3322
  let context;
3595
3323
  const contentType = c.req.header("content-type");
@@ -3616,7 +3344,7 @@ ${user.trimEnd()}
3616
3344
  const variant = findVariant(baseName, mergeVariantName);
3617
3345
  if (variant) {
3618
3346
  const projectRoot = mindDir(baseName);
3619
- if (existsSync9(variant.path)) {
3347
+ if (existsSync10(variant.path)) {
3620
3348
  const status = (await gitExec(["status", "--porcelain"], { cwd: variant.path })).trim();
3621
3349
  if (status) {
3622
3350
  try {
@@ -3658,7 +3386,7 @@ ${user.trimEnd()}
3658
3386
  if (context?.type === "sprouted" && !variantName) {
3659
3387
  try {
3660
3388
  const db = await getDb();
3661
- 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();
3662
3390
  for (const conv of activeConvs) {
3663
3391
  await addMessage(conv.id, "assistant", "system", [
3664
3392
  { type: "text", text: "[seed has sprouted]" }
@@ -3696,7 +3424,7 @@ ${user.trimEnd()}
3696
3424
  const name = c.req.param("name");
3697
3425
  const entry = findMind(name);
3698
3426
  if (!entry) return c.json({ error: "Mind not found" }, 404);
3699
- const { getSleepManagerIfReady } = await import("./sleep-manager-3RWUX2ZR.js");
3427
+ const { getSleepManagerIfReady } = await import("./sleep-manager-XXSWQQLE.js");
3700
3428
  const sm = getSleepManagerIfReady();
3701
3429
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
3702
3430
  return c.json(sm.getState(name));
@@ -3704,7 +3432,7 @@ ${user.trimEnd()}
3704
3432
  const name = c.req.param("name");
3705
3433
  const entry = findMind(name);
3706
3434
  if (!entry) return c.json({ error: "Mind not found" }, 404);
3707
- const { getSleepManagerIfReady } = await import("./sleep-manager-3RWUX2ZR.js");
3435
+ const { getSleepManagerIfReady } = await import("./sleep-manager-XXSWQQLE.js");
3708
3436
  const sm = getSleepManagerIfReady();
3709
3437
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
3710
3438
  if (sm.isSleeping(name)) return c.json({ error: "Mind is already sleeping" }, 409);
@@ -3724,7 +3452,7 @@ ${user.trimEnd()}
3724
3452
  const name = c.req.param("name");
3725
3453
  const entry = findMind(name);
3726
3454
  if (!entry) return c.json({ error: "Mind not found" }, 404);
3727
- const { getSleepManagerIfReady } = await import("./sleep-manager-3RWUX2ZR.js");
3455
+ const { getSleepManagerIfReady } = await import("./sleep-manager-XXSWQQLE.js");
3728
3456
  const sm = getSleepManagerIfReady();
3729
3457
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
3730
3458
  if (!sm.isSleeping(name)) return c.json({ error: "Mind is not sleeping" }, 409);
@@ -3734,7 +3462,7 @@ ${user.trimEnd()}
3734
3462
  const name = c.req.param("name");
3735
3463
  const entry = findMind(name);
3736
3464
  if (!entry) return c.json({ error: "Mind not found" }, 404);
3737
- const { getSleepManagerIfReady } = await import("./sleep-manager-3RWUX2ZR.js");
3465
+ const { getSleepManagerIfReady } = await import("./sleep-manager-XXSWQQLE.js");
3738
3466
  const sm = getSleepManagerIfReady();
3739
3467
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
3740
3468
  const flushed = await sm.flushQueuedMessages(name);
@@ -3767,11 +3495,11 @@ ${user.trimEnd()}
3767
3495
  removeMind(name);
3768
3496
  await deleteMindUser2(name);
3769
3497
  const state = stateDir(name);
3770
- if (existsSync9(state)) {
3771
- rmSync3(state, { recursive: true, force: true });
3498
+ if (existsSync10(state)) {
3499
+ rmSync4(state, { recursive: true, force: true });
3772
3500
  }
3773
- if (force && existsSync9(dir)) {
3774
- rmSync3(dir, { recursive: true, force: true });
3501
+ if (force && existsSync10(dir)) {
3502
+ rmSync4(dir, { recursive: true, force: true });
3775
3503
  deleteMindUser(name);
3776
3504
  }
3777
3505
  fireWebhook({
@@ -3785,7 +3513,7 @@ ${user.trimEnd()}
3785
3513
  const entry = findMind(mindName);
3786
3514
  if (!entry) return c.json({ error: "Mind not found" }, 404);
3787
3515
  const dir = mindDir(mindName);
3788
- if (!existsSync9(dir)) return c.json({ error: "Mind directory missing" }, 404);
3516
+ if (!existsSync10(dir)) return c.json({ error: "Mind directory missing" }, 404);
3789
3517
  let body = {};
3790
3518
  try {
3791
3519
  body = await c.req.json();
@@ -3794,15 +3522,15 @@ ${user.trimEnd()}
3794
3522
  const template = body.template ?? entry.template ?? "claude";
3795
3523
  const UPGRADE_VARIANT = "upgrade";
3796
3524
  if (body.abort) {
3797
- const worktreeDir2 = resolve11(dir, ".variants", UPGRADE_VARIANT);
3798
- if (!existsSync9(worktreeDir2)) {
3525
+ const worktreeDir2 = resolve12(dir, ".variants", UPGRADE_VARIANT);
3526
+ if (!existsSync10(worktreeDir2)) {
3799
3527
  return c.json({ error: "No upgrade in progress" }, 400);
3800
3528
  }
3801
3529
  try {
3802
3530
  try {
3803
- const gitDirContent = readFileSync8(resolve11(worktreeDir2, ".git"), "utf-8").trim();
3531
+ const gitDirContent = readFileSync9(resolve12(worktreeDir2, ".git"), "utf-8").trim();
3804
3532
  const gitDir = gitDirContent.replace("gitdir: ", "");
3805
- if (existsSync9(resolve11(gitDir, "MERGE_HEAD"))) {
3533
+ if (existsSync10(resolve12(gitDir, "MERGE_HEAD"))) {
3806
3534
  await gitExec(["merge", "--abort"], { cwd: worktreeDir2 });
3807
3535
  }
3808
3536
  } catch {
@@ -3817,8 +3545,8 @@ ${user.trimEnd()}
3817
3545
  }
3818
3546
  }
3819
3547
  if (body.continue) {
3820
- const worktreeDir2 = resolve11(dir, ".variants", UPGRADE_VARIANT);
3821
- if (!existsSync9(worktreeDir2)) {
3548
+ const worktreeDir2 = resolve12(dir, ".variants", UPGRADE_VARIANT);
3549
+ if (!existsSync10(worktreeDir2)) {
3822
3550
  return c.json({ error: "No upgrade in progress" }, 400);
3823
3551
  }
3824
3552
  const status = await gitExec(["status", "--porcelain"], { cwd: worktreeDir2 });
@@ -3862,23 +3590,23 @@ ${user.trimEnd()}
3862
3590
  );
3863
3591
  }
3864
3592
  }
3865
- const worktreeDir = resolve11(dir, ".variants", UPGRADE_VARIANT);
3866
- if (existsSync9(worktreeDir)) {
3593
+ const worktreeDir = resolve12(dir, ".variants", UPGRADE_VARIANT);
3594
+ if (existsSync10(worktreeDir)) {
3867
3595
  return c.json(
3868
3596
  { error: "Upgrade variant already exists. Use continue or delete it first." },
3869
3597
  409
3870
3598
  );
3871
3599
  }
3872
- if (!existsSync9(resolve11(dir, ".git"))) {
3600
+ if (!existsSync10(resolve12(dir, ".git"))) {
3873
3601
  try {
3874
- const env = isIsolationEnabled() ? { ...process.env, HOME: resolve11(dir, "home") } : void 0;
3602
+ const env = isIsolationEnabled() ? { ...process.env, HOME: resolve12(dir, "home") } : void 0;
3875
3603
  await gitExec(["init"], { cwd: dir, mindName, env });
3876
3604
  await configureGitIdentity(mindName, { cwd: dir, mindName, env });
3877
3605
  await gitExec(["add", "-A"], { cwd: dir, mindName, env });
3878
3606
  await gitExec(["commit", "-m", "initial commit"], { cwd: dir, mindName, env });
3879
3607
  chownMindDir(dir, mindName);
3880
3608
  } catch (err) {
3881
- rmSync3(resolve11(dir, ".git"), { recursive: true, force: true });
3609
+ rmSync4(resolve12(dir, ".git"), { recursive: true, force: true });
3882
3610
  return c.json(
3883
3611
  {
3884
3612
  error: `Git initialization failed: ${err instanceof Error ? err.message : String(err)}`
@@ -3892,7 +3620,7 @@ ${user.trimEnd()}
3892
3620
  await gitExec(["branch", "-D", UPGRADE_VARIANT], { cwd: dir });
3893
3621
  } catch {
3894
3622
  }
3895
- if (!existsSync9(resolve11(dir, "home", "shared"))) {
3623
+ if (!existsSync10(resolve12(dir, "home", "shared"))) {
3896
3624
  try {
3897
3625
  await addSharedWorktree(mindName, dir);
3898
3626
  } catch (err) {
@@ -3903,9 +3631,9 @@ ${user.trimEnd()}
3903
3631
  }
3904
3632
  }
3905
3633
  await updateTemplateBranch(dir, template, mindName);
3906
- const parentDir = resolve11(dir, ".variants");
3907
- if (!existsSync9(parentDir)) {
3908
- mkdirSync5(parentDir, { recursive: true });
3634
+ const parentDir = resolve12(dir, ".variants");
3635
+ if (!existsSync10(parentDir)) {
3636
+ mkdirSync6(parentDir, { recursive: true });
3909
3637
  }
3910
3638
  await gitExec(["worktree", "add", "-b", UPGRADE_VARIANT, worktreeDir], { cwd: dir });
3911
3639
  const hasConflicts = await mergeTemplateBranch(worktreeDir);
@@ -3952,7 +3680,7 @@ ${user.trimEnd()}
3952
3680
  if (!variant) return c.json({ error: `Unknown variant: ${variantName}` }, 404);
3953
3681
  }
3954
3682
  try {
3955
- const { getSleepManagerIfReady } = await import("./sleep-manager-3RWUX2ZR.js");
3683
+ const { getSleepManagerIfReady } = await import("./sleep-manager-XXSWQQLE.js");
3956
3684
  const sm = getSleepManagerIfReady();
3957
3685
  if (sm?.isSleeping(baseName)) {
3958
3686
  const body2 = await c.req.text();
@@ -3994,21 +3722,10 @@ ${user.trimEnd()}
3994
3722
  logger_default.error(`failed to parse message body for ${baseName}`, logger_default.errorData(err));
3995
3723
  }
3996
3724
  const channel = parsed?.channel ?? "unknown";
3997
- const db = await getDb();
3998
3725
  if (parsed) {
3999
- try {
4000
- const sender2 = parsed.sender ?? null;
4001
- const content = extractTextContent(parsed.content);
4002
- await db.insert(mindHistory).values({
4003
- mind: baseName,
4004
- type: "inbound",
4005
- channel,
4006
- sender: sender2,
4007
- content
4008
- });
4009
- } catch (err) {
4010
- logger_default.error(`failed to persist inbound message for ${baseName}`, logger_default.errorData(err));
4011
- }
3726
+ const sender2 = parsed.sender ?? null;
3727
+ const content = extractTextContent(parsed.content);
3728
+ await recordInbound(baseName, channel, sender2, content);
4012
3729
  }
4013
3730
  const budget = getTokenBudget();
4014
3731
  const budgetStatus = budget.checkBudget(baseName);
@@ -4060,7 +3777,8 @@ ${user.trimEnd()}
4060
3777
  const seedEntry = findMind(baseName);
4061
3778
  if (seedEntry?.stage === "seed") {
4062
3779
  try {
4063
- const countResult = await db.select({ count: sql2`count(*)` }).from(mindHistory).where(eq4(mindHistory.mind, baseName));
3780
+ const db = await getDb();
3781
+ const countResult = await db.select({ count: sql`count(*)` }).from(mindHistory).where(eq2(mindHistory.mind, baseName));
4064
3782
  const msgCount = countResult[0]?.count ?? 0;
4065
3783
  if (msgCount >= 10 && msgCount % 10 === 0) {
4066
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.]";
@@ -4105,13 +3823,13 @@ ${user.trimEnd()}
4105
3823
  const entry = findMind(name);
4106
3824
  if (!entry) return c.json({ error: "Mind not found" }, 404);
4107
3825
  const dir = mindDir(name);
4108
- if (!existsSync9(dir)) return c.json({ error: "Mind directory missing" }, 404);
3826
+ if (!existsSync10(dir)) return c.json({ error: "Mind directory missing" }, 404);
4109
3827
  let config = readVoluteConfig(dir);
4110
3828
  if (!config && entry.template === "pi") {
4111
- const piConfigPath = resolve11(dir, "home/.config/config.json");
4112
- if (existsSync9(piConfigPath)) {
3829
+ const piConfigPath = resolve12(dir, "home/.config/config.json");
3830
+ if (existsSync10(piConfigPath)) {
4113
3831
  try {
4114
- config = JSON.parse(readFileSync8(piConfigPath, "utf-8"));
3832
+ config = JSON.parse(readFileSync9(piConfigPath, "utf-8"));
4115
3833
  } catch {
4116
3834
  }
4117
3835
  }
@@ -4148,7 +3866,7 @@ ${user.trimEnd()}
4148
3866
  const entry = findMind(name);
4149
3867
  if (!entry) return c.json({ error: "Mind not found" }, 404);
4150
3868
  const dir = mindDir(name);
4151
- if (!existsSync9(dir)) return c.json({ error: "Mind directory missing" }, 404);
3869
+ if (!existsSync10(dir)) return c.json({ error: "Mind directory missing" }, 404);
4152
3870
  const body = c.req.valid("json");
4153
3871
  const existing = readVoluteConfig(dir) ?? {};
4154
3872
  if (body.model !== void 0) existing.model = body.model;
@@ -4211,7 +3929,7 @@ ${user.trimEnd()}
4211
3929
  } catch (err) {
4212
3930
  logger_default.error(`failed to persist event for ${baseName}`, logger_default.errorData(err));
4213
3931
  }
4214
- publish2(baseName, {
3932
+ publish(baseName, {
4215
3933
  mind: baseName,
4216
3934
  type: body.type,
4217
3935
  session: body.session,
@@ -4261,14 +3979,29 @@ ${user.trimEnd()}
4261
3979
 
4262
3980
  `));
4263
3981
  };
4264
- const unsubscribe = subscribe3(baseName, (event) => {
3982
+ let unsubscribe;
3983
+ const pingInterval = setInterval(() => {
3984
+ try {
3985
+ controller.enqueue(encoder.encode(": ping\n\n"));
3986
+ } catch {
3987
+ clearInterval(pingInterval);
3988
+ unsubscribe?.();
3989
+ }
3990
+ }, 15e3);
3991
+ unsubscribe = subscribe2(baseName, (event) => {
4265
3992
  if (typeFilter && !typeFilter.includes(event.type)) return;
4266
3993
  if (sessionFilter && event.session !== sessionFilter) return;
4267
3994
  if (channelFilter && event.channel !== channelFilter) return;
4268
- send5(JSON.stringify(event));
3995
+ try {
3996
+ send5(JSON.stringify(event));
3997
+ } catch {
3998
+ clearInterval(pingInterval);
3999
+ unsubscribe?.();
4000
+ }
4269
4001
  });
4270
4002
  c.req.raw.signal.addEventListener("abort", () => {
4271
- unsubscribe();
4003
+ clearInterval(pingInterval);
4004
+ unsubscribe?.();
4272
4005
  try {
4273
4006
  controller.close();
4274
4007
  } catch {
@@ -4314,22 +4047,22 @@ ${user.trimEnd()}
4314
4047
  const db = await getDb();
4315
4048
  const rows = await db.select({
4316
4049
  session: mindHistory.session,
4317
- started_at: sql2`MIN(${mindHistory.created_at})`,
4318
- event_count: sql2`COUNT(*)`,
4319
- message_count: sql2`SUM(CASE WHEN ${mindHistory.type} IN ('inbound','outbound') THEN 1 ELSE 0 END)`,
4320
- tool_count: sql2`SUM(CASE WHEN ${mindHistory.type}='tool_use' THEN 1 ELSE 0 END)`
4321
- }).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`);
4322
4055
  return c.json(rows);
4323
4056
  }).get("/:name/history/channels", async (c) => {
4324
4057
  const name = c.req.param("name");
4325
4058
  const db = await getDb();
4326
- 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));
4327
4060
  return c.json(rows.map((r) => r.channel));
4328
4061
  }).get("/:name/history/export", async (c) => {
4329
4062
  const name = c.req.param("name");
4330
4063
  if (!findMind(name)) return c.json({ error: "Mind not found" }, 404);
4331
4064
  const db = await getDb();
4332
- 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));
4333
4066
  return c.json(rows);
4334
4067
  }).get("/:name/history", async (c) => {
4335
4068
  const name = c.req.param("name");
@@ -4339,24 +4072,24 @@ ${user.trimEnd()}
4339
4072
  const limit = Math.min(Math.max(parseInt(c.req.query("limit") ?? "50", 10) || 50, 1), 200);
4340
4073
  const offset = Math.max(parseInt(c.req.query("offset") ?? "0", 10) || 0, 0);
4341
4074
  const db = await getDb();
4342
- const conditions = [eq4(mindHistory.mind, name)];
4075
+ const conditions = [eq2(mindHistory.mind, name)];
4343
4076
  if (channel) {
4344
- conditions.push(eq4(mindHistory.channel, channel));
4077
+ conditions.push(eq2(mindHistory.channel, channel));
4345
4078
  }
4346
4079
  if (session) {
4347
- conditions.push(eq4(mindHistory.session, session));
4080
+ conditions.push(eq2(mindHistory.session, session));
4348
4081
  }
4349
4082
  if (!full) {
4350
- conditions.push(sql2`${mindHistory.type} IN ('inbound', 'outbound')`);
4083
+ conditions.push(sql`${mindHistory.type} IN ('inbound', 'outbound')`);
4351
4084
  }
4352
- 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);
4353
4086
  return c.json(rows);
4354
4087
  });
4355
4088
  var minds_default = app11;
4356
4089
 
4357
4090
  // src/web/api/pages.ts
4358
4091
  import { readFile as readFile2, stat as stat2 } from "fs/promises";
4359
- import { extname as extname2, resolve as resolve12 } from "path";
4092
+ import { extname as extname3, resolve as resolve13 } from "path";
4360
4093
  import { Hono as Hono12 } from "hono";
4361
4094
  var MIME_TYPES = {
4362
4095
  ".html": "text/html",
@@ -4378,17 +4111,17 @@ var app12 = new Hono12().get("/:name/*", async (c) => {
4378
4111
  const name = c.req.param("name");
4379
4112
  let pagesRoot;
4380
4113
  if (name === "_system") {
4381
- pagesRoot = resolve12(voluteHome(), "shared", "pages");
4114
+ pagesRoot = resolve13(voluteHome(), "shared", "pages");
4382
4115
  } else {
4383
4116
  if (!findMind(name)) return c.text("Not found", 404);
4384
- pagesRoot = resolve12(mindDir(name), "home", "pages");
4117
+ pagesRoot = resolve13(mindDir(name), "home", "pages");
4385
4118
  }
4386
4119
  const wildcard = c.req.path.replace(`/pages/${name}`, "") || "/";
4387
- const requestedPath = resolve12(pagesRoot, wildcard.slice(1));
4120
+ const requestedPath = resolve13(pagesRoot, wildcard.slice(1));
4388
4121
  if (!requestedPath.startsWith(pagesRoot)) return c.text("Forbidden", 403);
4389
4122
  let fileStat = await stat2(requestedPath).catch(() => null);
4390
4123
  if (fileStat?.isDirectory()) {
4391
- const indexPath = resolve12(requestedPath, "index.html");
4124
+ const indexPath = resolve13(requestedPath, "index.html");
4392
4125
  fileStat = await stat2(indexPath).catch(() => null);
4393
4126
  if (fileStat?.isFile()) {
4394
4127
  const body = await readFile2(indexPath);
@@ -4397,7 +4130,7 @@ var app12 = new Hono12().get("/:name/*", async (c) => {
4397
4130
  return c.text("Not found", 404);
4398
4131
  }
4399
4132
  if (fileStat?.isFile()) {
4400
- const ext = extname2(requestedPath);
4133
+ const ext = extname3(requestedPath);
4401
4134
  const mime = MIME_TYPES[ext] || "application/octet-stream";
4402
4135
  const body = await readFile2(requestedPath);
4403
4136
  return c.body(body, 200, { "Content-Type": mime });
@@ -4408,7 +4141,7 @@ var pages_default = app12;
4408
4141
 
4409
4142
  // src/web/api/prompts.ts
4410
4143
  import { zValidator as zValidator4 } from "@hono/zod-validator";
4411
- import { eq as eq5, sql as sql3 } from "drizzle-orm";
4144
+ import { eq as eq3, sql as sql2 } from "drizzle-orm";
4412
4145
  import { Hono as Hono13 } from "hono";
4413
4146
  import { z as z4 } from "zod";
4414
4147
  var app13 = new Hono13().get("/", async (c) => {
@@ -4441,9 +4174,9 @@ var app13 = new Hono13().get("/", async (c) => {
4441
4174
  }
4442
4175
  const { content } = c.req.valid("json");
4443
4176
  const db = await getDb();
4444
- 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({
4445
4178
  target: systemPrompts.key,
4446
- set: { content, updated_at: sql3`(datetime('now'))` }
4179
+ set: { content, updated_at: sql2`(datetime('now'))` }
4447
4180
  });
4448
4181
  return c.json({ ok: true });
4449
4182
  }).delete("/:key", requireAdmin, async (c) => {
@@ -4452,7 +4185,7 @@ var app13 = new Hono13().get("/", async (c) => {
4452
4185
  return c.json({ error: "Unknown prompt key" }, 404);
4453
4186
  }
4454
4187
  const db = await getDb();
4455
- await db.delete(systemPrompts).where(eq5(systemPrompts.key, key));
4188
+ await db.delete(systemPrompts).where(eq3(systemPrompts.key, key));
4456
4189
  return c.json({ ok: true });
4457
4190
  });
4458
4191
  var prompts_default = app13;
@@ -4635,9 +4368,9 @@ var app15 = new Hono15().post("/:name/shared/merge", requireAdmin, async (c) =>
4635
4368
  var shared_default = app15;
4636
4369
 
4637
4370
  // src/web/api/skills.ts
4638
- 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";
4639
4372
  import { tmpdir } from "os";
4640
- import { join as join2, resolve as resolve13 } from "path";
4373
+ import { join as join2, resolve as resolve14 } from "path";
4641
4374
  import AdmZip from "adm-zip";
4642
4375
  import { Hono as Hono16 } from "hono";
4643
4376
  var app16 = new Hono16().get("/", async (c) => {
@@ -4664,19 +4397,19 @@ var app16 = new Hono16().get("/", async (c) => {
4664
4397
  try {
4665
4398
  const zip = new AdmZip(buffer2);
4666
4399
  for (const entry of zip.getEntries()) {
4667
- const target = resolve13(tmpDir, entry.entryName);
4400
+ const target = resolve14(tmpDir, entry.entryName);
4668
4401
  if (!target.startsWith(tmpDir)) {
4669
4402
  return c.json({ error: "Invalid zip: paths must not escape archive" }, 400);
4670
4403
  }
4671
4404
  }
4672
4405
  zip.extractAllTo(tmpDir, true);
4673
4406
  let skillDir = null;
4674
- if (existsSync10(join2(tmpDir, "SKILL.md"))) {
4407
+ if (existsSync11(join2(tmpDir, "SKILL.md"))) {
4675
4408
  skillDir = tmpDir;
4676
4409
  } else {
4677
4410
  const entries = readdirSync5(tmpDir, { withFileTypes: true }).filter((e) => e.isDirectory());
4678
4411
  for (const entry of entries) {
4679
- if (existsSync10(join2(tmpDir, entry.name, "SKILL.md"))) {
4412
+ if (existsSync11(join2(tmpDir, entry.name, "SKILL.md"))) {
4680
4413
  skillDir = join2(tmpDir, entry.name);
4681
4414
  break;
4682
4415
  }
@@ -4693,7 +4426,7 @@ var app16 = new Hono16().get("/", async (c) => {
4693
4426
  }
4694
4427
  throw e;
4695
4428
  } finally {
4696
- rmSync4(tmpDir, { recursive: true, force: true });
4429
+ rmSync5(tmpDir, { recursive: true, force: true });
4697
4430
  }
4698
4431
  }).delete("/:id", requireAdmin, async (c) => {
4699
4432
  const id = c.req.param("id");
@@ -4727,10 +4460,10 @@ var app17 = new Hono17().post("/restart", requireAdmin, (c) => {
4727
4460
  stream.writeSSE({ data: JSON.stringify(entry) }).catch(() => {
4728
4461
  });
4729
4462
  });
4730
- await new Promise((resolve18) => {
4463
+ await new Promise((resolve19) => {
4731
4464
  stream.onAbort(() => {
4732
4465
  unsubscribe();
4733
- resolve18();
4466
+ resolve19();
4734
4467
  });
4735
4468
  });
4736
4469
  });
@@ -4760,7 +4493,7 @@ var app18 = new Hono18().post("/:name/typing", zValidator5("json", typingSchema)
4760
4493
  const volutePrefix = "volute:";
4761
4494
  if (channel.startsWith(volutePrefix)) {
4762
4495
  const conversationId = channel.slice(volutePrefix.length);
4763
- publish(conversationId, { type: "typing", senders: map.get(channel) });
4496
+ publish2(conversationId, { type: "typing", senders: map.get(channel) });
4764
4497
  }
4765
4498
  return c.json({ ok: true });
4766
4499
  }).get("/:name/typing", (c) => {
@@ -4805,8 +4538,8 @@ async function fanOutToMinds(opts) {
4805
4538
  const participantNames = participants.map((p) => p.username);
4806
4539
  const isDM = opts.isDM ?? participants.length === 2;
4807
4540
  const channelEntryType = opts.channelEntryType ?? (isDM ? "dm" : "group");
4808
- const { getMindManager: getMindManager2 } = await import("./mind-manager-P5OBDUKI.js");
4809
- const { getSleepManagerIfReady } = await import("./sleep-manager-3RWUX2ZR.js");
4541
+ const { getMindManager: getMindManager2 } = await import("./mind-manager-ZNRIYEK3.js");
4542
+ const { getSleepManagerIfReady } = await import("./sleep-manager-XXSWQQLE.js");
4810
4543
  const manager = getMindManager2();
4811
4544
  const sm = getSleepManagerIfReady();
4812
4545
  const targetMinds = mindParticipants.map((ap) => {
@@ -4920,11 +4653,15 @@ var app20 = new Hono20().use("*", authMiddleware).post("/minds/:name/chat", zVal
4920
4653
  }
4921
4654
  }
4922
4655
  await addMessage(conversationId, "user", senderName, contentBlocks);
4656
+ const isDM = conv?.type === "dm";
4923
4657
  await fanOutToMinds({
4924
4658
  conversationId,
4925
4659
  contentBlocks,
4926
4660
  senderName,
4927
4661
  convTitle,
4662
+ isDM,
4663
+ channelEntryType: conv?.type === "channel" ? "group" : isDM ? "dm" : "group",
4664
+ slugExtra: conv ? { convType: conv.type, convName: conv.name } : void 0,
4928
4665
  targetName: (username) => username === baseName ? name : username
4929
4666
  });
4930
4667
  return c.json({ ok: true, conversationId });
@@ -4935,7 +4672,7 @@ var app20 = new Hono20().use("*", authMiddleware).post("/minds/:name/chat", zVal
4935
4672
  return c.json({ error: "Conversation not found" }, 404);
4936
4673
  }
4937
4674
  return streamSSE4(c, async (stream) => {
4938
- const unsubscribe = subscribe2(conversationId, (event) => {
4675
+ const unsubscribe = subscribe3(conversationId, (event) => {
4939
4676
  stream.writeSSE({ data: JSON.stringify(event) }).catch((err) => {
4940
4677
  if (!stream.aborted) logger_default.error("[v1-chat] SSE write error:", logger_default.errorData(err));
4941
4678
  });
@@ -4945,11 +4682,11 @@ var app20 = new Hono20().use("*", authMiddleware).post("/minds/:name/chat", zVal
4945
4682
  if (!stream.aborted) logger_default.error("[v1-chat] SSE ping error:", logger_default.errorData(err));
4946
4683
  });
4947
4684
  }, 15e3);
4948
- await new Promise((resolve18) => {
4685
+ await new Promise((resolve19) => {
4949
4686
  stream.onAbort(() => {
4950
4687
  unsubscribe();
4951
4688
  clearInterval(keepAlive);
4952
- resolve18();
4689
+ resolve19();
4953
4690
  });
4954
4691
  });
4955
4692
  });
@@ -5056,6 +4793,15 @@ var app21 = new Hono21().use("*", authMiddleware).get("/", async (c) => {
5056
4793
  participantIds: [...participantIds]
5057
4794
  });
5058
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 });
5059
4805
  }).delete("/:id", async (c) => {
5060
4806
  const id = c.req.param("id");
5061
4807
  const user = c.get("user");
@@ -5066,10 +4812,33 @@ var app21 = new Hono21().use("*", authMiddleware).get("/", async (c) => {
5066
4812
  var conversations_default = app21;
5067
4813
 
5068
4814
  // src/web/api/v1/events.ts
5069
- import { desc as desc4 } from "drizzle-orm";
4815
+ import { desc as desc3 } from "drizzle-orm";
5070
4816
  import { Hono as Hono22 } from "hono";
5071
4817
  import { streamSSE as streamSSE5 } from "hono/streaming";
5072
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
+
5073
4842
  // src/lib/events/event-sequencer.ts
5074
4843
  var BUFFER_SIZE = 1e3;
5075
4844
  var MAX_AGE_MS = 5 * 60 * 1e3;
@@ -5097,6 +4866,10 @@ var app22 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
5097
4866
  const sinceId = since ? Number(since) : 0;
5098
4867
  return streamSSE5(c, async (stream) => {
5099
4868
  const cleanups = [];
4869
+ if (user.user_type === "brain") {
4870
+ addConnection(user.username);
4871
+ cleanups.push(() => removeConnection(user.username));
4872
+ }
5100
4873
  try {
5101
4874
  if (sinceId > 0) {
5102
4875
  const missed = getEventsSince(sinceId);
@@ -5110,7 +4883,7 @@ var app22 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
5110
4883
  let recentActivity = [];
5111
4884
  try {
5112
4885
  const db = await getDb();
5113
- 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);
5114
4887
  recentActivity = recentActivity.map((row) => ({
5115
4888
  ...row,
5116
4889
  metadata: row.metadata ? JSON.parse(row.metadata) : null
@@ -5121,6 +4894,13 @@ var app22 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
5121
4894
  let conversations2 = [];
5122
4895
  try {
5123
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
+ }
5124
4904
  } catch (err) {
5125
4905
  logger_default.error("[v1-events] failed to fetch conversations", logger_default.errorData(err));
5126
4906
  }
@@ -5132,7 +4912,8 @@ var app22 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
5132
4912
  conversations: conversations2,
5133
4913
  sites,
5134
4914
  recentPages,
5135
- activeMinds: getActiveMinds()
4915
+ activeMinds: getActiveMinds(),
4916
+ onlineBrains: getOnlineBrains()
5136
4917
  };
5137
4918
  const snapshotId = bufferEvent(snapshotData);
5138
4919
  await stream.writeSSE({
@@ -5155,7 +4936,7 @@ var app22 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
5155
4936
  });
5156
4937
  cleanups.push(unsubActivity);
5157
4938
  for (const conv of conversations2) {
5158
- const unsubConv = subscribe2(conv.id, (event) => {
4939
+ const unsubConv = subscribe3(conv.id, (event) => {
5159
4940
  const data = { event: "conversation", conversationId: conv.id, ...event };
5160
4941
  const eventId = bufferEvent(data);
5161
4942
  stream.writeSSE({
@@ -5173,8 +4954,8 @@ var app22 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
5173
4954
  });
5174
4955
  }, 15e3);
5175
4956
  cleanups.push(() => clearInterval(keepAlive));
5176
- await new Promise((resolve18) => {
5177
- stream.onAbort(() => resolve18());
4957
+ await new Promise((resolve19) => {
4958
+ stream.onAbort(() => resolve19());
5178
4959
  });
5179
4960
  } finally {
5180
4961
  for (const cleanup of cleanups) {
@@ -5189,16 +4970,16 @@ var app22 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
5189
4970
  var events_default = app22;
5190
4971
 
5191
4972
  // src/web/api/variants.ts
5192
- import { existsSync as existsSync11, mkdirSync as mkdirSync7, writeFileSync as writeFileSync7 } from "fs";
5193
- 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";
5194
4975
  import { Hono as Hono23 } from "hono";
5195
4976
 
5196
4977
  // src/lib/spawn-server.ts
5197
4978
  import { spawn as spawn3 } from "child_process";
5198
- import { closeSync, mkdirSync as mkdirSync6, openSync, readFileSync as readFileSync9 } from "fs";
5199
- 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";
5200
4981
  function tsxBin(cwd) {
5201
- return resolve14(cwd, "node_modules", ".bin", "tsx");
4982
+ return resolve15(cwd, "node_modules", ".bin", "tsx");
5202
4983
  }
5203
4984
  function spawnServer(cwd, port, options) {
5204
4985
  if (options?.detached) {
@@ -5211,31 +4992,31 @@ function spawnAttached(cwd, port) {
5211
4992
  cwd,
5212
4993
  stdio: ["ignore", "pipe", "pipe"]
5213
4994
  });
5214
- return new Promise((resolve18) => {
5215
- const timeout = setTimeout(() => resolve18(null), 3e4);
4995
+ return new Promise((resolve19) => {
4996
+ const timeout = setTimeout(() => resolve19(null), 3e4);
5216
4997
  function checkOutput(data) {
5217
4998
  const match = data.toString().match(/listening on :(\d+)/);
5218
4999
  if (match) {
5219
5000
  clearTimeout(timeout);
5220
- resolve18({ child, actualPort: parseInt(match[1], 10) });
5001
+ resolve19({ child, actualPort: parseInt(match[1], 10) });
5221
5002
  }
5222
5003
  }
5223
5004
  child.stdout?.on("data", checkOutput);
5224
5005
  child.stderr?.on("data", checkOutput);
5225
5006
  child.on("error", () => {
5226
5007
  clearTimeout(timeout);
5227
- resolve18(null);
5008
+ resolve19(null);
5228
5009
  });
5229
5010
  child.on("exit", () => {
5230
5011
  clearTimeout(timeout);
5231
- resolve18(null);
5012
+ resolve19(null);
5232
5013
  });
5233
5014
  });
5234
5015
  }
5235
5016
  function spawnDetached(cwd, port, logDir) {
5236
- const logsDir = logDir ?? resolve14(cwd, ".mind", "logs");
5237
- mkdirSync6(logsDir, { recursive: true });
5238
- 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");
5239
5020
  const logFd = openSync(logPath, "a");
5240
5021
  const child = spawn3(tsxBin(cwd), ["src/server.ts", "--port", String(port)], {
5241
5022
  cwd,
@@ -5255,7 +5036,7 @@ function spawnDetached(cwd, port, logDir) {
5255
5036
  }
5256
5037
  const interval = setInterval(() => {
5257
5038
  try {
5258
- const content = readFileSync9(logPath, "utf-8");
5039
+ const content = readFileSync10(logPath, "utf-8");
5259
5040
  const match = content.match(/listening on :(\d+)/);
5260
5041
  if (match) {
5261
5042
  finish({ child, actualPort: parseInt(match[1], 10) });
@@ -5347,11 +5128,11 @@ var app23 = new Hono23().get("/:name/variants", async (c) => {
5347
5128
  const err = validateBranchName(variantName);
5348
5129
  if (err) return c.json({ error: err }, 400);
5349
5130
  const projectRoot = mindDir(mindName);
5350
- const variantDir = resolve15(projectRoot, ".variants", variantName);
5351
- if (existsSync11(variantDir)) {
5131
+ const variantDir = resolve16(projectRoot, ".variants", variantName);
5132
+ if (existsSync12(variantDir)) {
5352
5133
  return c.json({ error: `Variant directory already exists: ${variantDir}` }, 409);
5353
5134
  }
5354
- mkdirSync7(resolve15(projectRoot, ".variants"), { recursive: true });
5135
+ mkdirSync8(resolve16(projectRoot, ".variants"), { recursive: true });
5355
5136
  try {
5356
5137
  await gitExec(["worktree", "add", "-b", variantName, variantDir], { cwd: projectRoot });
5357
5138
  } catch (e) {
@@ -5364,7 +5145,7 @@ var app23 = new Hono23().get("/:name/variants", async (c) => {
5364
5145
  const [cmd, args] = wrapForIsolation("npm", ["install"], mindName);
5365
5146
  await exec(cmd, args, {
5366
5147
  cwd: variantDir,
5367
- env: { ...process.env, HOME: resolve15(variantDir, "home") }
5148
+ env: { ...process.env, HOME: resolve16(variantDir, "home") }
5368
5149
  });
5369
5150
  } else {
5370
5151
  await exec("npm", ["install"], { cwd: variantDir });
@@ -5374,7 +5155,7 @@ var app23 = new Hono23().get("/:name/variants", async (c) => {
5374
5155
  return c.json({ error: `npm install failed: ${msg}` }, 500);
5375
5156
  }
5376
5157
  if (body.soul) {
5377
- writeFileSync7(resolve15(variantDir, "home/SOUL.md"), body.soul);
5158
+ writeFileSync8(resolve16(variantDir, "home/SOUL.md"), body.soul);
5378
5159
  }
5379
5160
  const variantPort = body.port ?? nextPort();
5380
5161
  const variant = {
@@ -5412,7 +5193,7 @@ var app23 = new Hono23().get("/:name/variants", async (c) => {
5412
5193
  } catch {
5413
5194
  }
5414
5195
  const projectRoot = mindDir(mindName);
5415
- if (existsSync11(variant.path)) {
5196
+ if (existsSync12(variant.path)) {
5416
5197
  const status = (await gitExec(["status", "--porcelain"], { cwd: variant.path })).trim();
5417
5198
  if (status) {
5418
5199
  try {
@@ -5485,7 +5266,7 @@ var app23 = new Hono23().get("/:name/variants", async (c) => {
5485
5266
  const [cmd, args] = wrapForIsolation("npm", ["install"], mindName);
5486
5267
  await exec(cmd, args, {
5487
5268
  cwd: projectRoot,
5488
- env: { ...process.env, HOME: resolve15(projectRoot, "home") }
5269
+ env: { ...process.env, HOME: resolve16(projectRoot, "home") }
5489
5270
  });
5490
5271
  } else {
5491
5272
  await exec("npm", ["install"], { cwd: projectRoot });
@@ -5612,8 +5393,8 @@ async function fanOutToMinds2(opts) {
5612
5393
  const participantNames = participants.map((p) => p.username);
5613
5394
  const isDM = opts.isDM ?? participants.length === 2;
5614
5395
  const channelEntryType = opts.channelEntryType ?? (isDM ? "dm" : "group");
5615
- const { getMindManager: getMindManager2 } = await import("./mind-manager-P5OBDUKI.js");
5616
- const { getSleepManagerIfReady } = await import("./sleep-manager-3RWUX2ZR.js");
5396
+ const { getMindManager: getMindManager2 } = await import("./mind-manager-ZNRIYEK3.js");
5397
+ const { getSleepManagerIfReady } = await import("./sleep-manager-XXSWQQLE.js");
5617
5398
  const manager = getMindManager2();
5618
5399
  const sm = getSleepManagerIfReady();
5619
5400
  const targetMinds = mindParticipants.map((ap) => {
@@ -5730,11 +5511,15 @@ var app25 = new Hono25().post("/:name/chat", zValidator9("json", chatSchema), as
5730
5511
  }
5731
5512
  }
5732
5513
  await addMessage(conversationId, "user", senderName, contentBlocks);
5514
+ const isDM = conv?.type === "dm";
5733
5515
  await fanOutToMinds2({
5734
5516
  conversationId,
5735
5517
  contentBlocks,
5736
5518
  senderName,
5737
5519
  convTitle,
5520
+ isDM,
5521
+ channelEntryType: conv?.type === "channel" ? "group" : isDM ? "dm" : "group",
5522
+ slugExtra: conv ? { convType: conv.type, convName: conv.name } : void 0,
5738
5523
  targetName: (username) => username === baseName ? name : username
5739
5524
  });
5740
5525
  return c.json({ ok: true, conversationId });
@@ -5745,7 +5530,7 @@ var app25 = new Hono25().post("/:name/chat", zValidator9("json", chatSchema), as
5745
5530
  return c.json({ error: "Conversation not found" }, 404);
5746
5531
  }
5747
5532
  return streamSSE6(c, async (stream) => {
5748
- const unsubscribe = subscribe2(conversationId, (event) => {
5533
+ const unsubscribe = subscribe3(conversationId, (event) => {
5749
5534
  stream.writeSSE({ data: JSON.stringify(event) }).catch((err) => {
5750
5535
  if (!stream.aborted) console.error("[chat] SSE write error:", err);
5751
5536
  });
@@ -5755,11 +5540,11 @@ var app25 = new Hono25().post("/:name/chat", zValidator9("json", chatSchema), as
5755
5540
  if (!stream.aborted) console.error("[chat] SSE ping error:", err);
5756
5541
  });
5757
5542
  }, 15e3);
5758
- await new Promise((resolve18) => {
5543
+ await new Promise((resolve19) => {
5759
5544
  stream.onAbort(() => {
5760
5545
  unsubscribe();
5761
5546
  clearInterval(keepAlive);
5762
- resolve18();
5547
+ resolve19();
5763
5548
  });
5764
5549
  });
5765
5550
  });
@@ -5961,7 +5746,7 @@ var app27 = new Hono27().use("*", authMiddleware).get("/", async (c) => {
5961
5746
  return c.json({ error: "Conversation not found" }, 404);
5962
5747
  }
5963
5748
  return streamSSE7(c, async (stream) => {
5964
- const unsubscribe = subscribe2(conversationId, (event) => {
5749
+ const unsubscribe = subscribe3(conversationId, (event) => {
5965
5750
  stream.writeSSE({ data: JSON.stringify(event) }).catch((err) => {
5966
5751
  if (!stream.aborted) console.error("[chat] SSE write error:", err);
5967
5752
  });
@@ -5971,11 +5756,11 @@ var app27 = new Hono27().use("*", authMiddleware).get("/", async (c) => {
5971
5756
  if (!stream.aborted) console.error("[chat] SSE ping error:", err);
5972
5757
  });
5973
5758
  }, 15e3);
5974
- await new Promise((resolve18) => {
5759
+ await new Promise((resolve19) => {
5975
5760
  stream.onAbort(() => {
5976
5761
  unsubscribe();
5977
5762
  clearInterval(keepAlive);
5978
- resolve18();
5763
+ resolve19();
5979
5764
  });
5980
5765
  });
5981
5766
  });
@@ -6073,13 +5858,14 @@ var MIME_TYPES2 = {
6073
5858
  };
6074
5859
  async function startServer({
6075
5860
  port,
6076
- hostname = "127.0.0.1"
5861
+ hostname = "127.0.0.1",
5862
+ tls
6077
5863
  }) {
6078
5864
  let assetsDir = "";
6079
5865
  let searchDir = dirname(new URL(import.meta.url).pathname);
6080
5866
  for (let i = 0; i < 5; i++) {
6081
- const candidate = resolve16(searchDir, "dist", "web-assets");
6082
- if (existsSync12(candidate)) {
5867
+ const candidate = resolve17(searchDir, "dist", "web-assets");
5868
+ if (existsSync13(candidate)) {
6083
5869
  assetsDir = candidate;
6084
5870
  break;
6085
5871
  }
@@ -6089,16 +5875,16 @@ async function startServer({
6089
5875
  app_default.get("*", async (c) => {
6090
5876
  const urlPath = new URL(c.req.url).pathname;
6091
5877
  if (urlPath.startsWith("/api/")) return c.notFound();
6092
- const filePath = resolve16(assetsDir, urlPath.slice(1));
5878
+ const filePath = resolve17(assetsDir, urlPath.slice(1));
6093
5879
  if (!filePath.startsWith(assetsDir)) return c.text("Forbidden", 403);
6094
5880
  const s = await stat3(filePath).catch(() => null);
6095
5881
  if (s?.isFile()) {
6096
- const ext = extname3(filePath);
5882
+ const ext = extname4(filePath);
6097
5883
  const mime = MIME_TYPES2[ext] || "application/octet-stream";
6098
5884
  const body = await readFile3(filePath);
6099
5885
  return c.body(body, 200, { "Content-Type": mime });
6100
5886
  }
6101
- const indexPath = resolve16(assetsDir, "index.html");
5887
+ const indexPath = resolve17(assetsDir, "index.html");
6102
5888
  const indexStat = await stat3(indexPath).catch(() => null);
6103
5889
  if (indexStat?.isFile()) {
6104
5890
  const body = await readFile3(indexPath, "utf-8");
@@ -6107,22 +5893,55 @@ async function startServer({
6107
5893
  return c.text("Not found", 404);
6108
5894
  });
6109
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
+ }
6110
5929
  const server = serve({ fetch: app_default.fetch, port, hostname });
6111
- await new Promise((resolve18, reject) => {
5930
+ await new Promise((resolve19, reject) => {
6112
5931
  server.on("listening", () => {
6113
- logger_default.info("Volute UI running", { hostname, port });
6114
- resolve18();
5932
+ logger_default.info("Volute API running (http)", { hostname, port });
5933
+ resolve19();
6115
5934
  });
6116
5935
  server.on("error", (err) => {
6117
5936
  reject(err);
6118
5937
  });
6119
5938
  });
6120
- return server;
5939
+ return { server };
6121
5940
  }
6122
5941
 
6123
5942
  // src/daemon.ts
6124
5943
  if (!process.env.VOLUTE_HOME) {
6125
- process.env.VOLUTE_HOME = resolve17(homedir2(), ".volute");
5944
+ process.env.VOLUTE_HOME = resolve18(homedir2(), ".volute");
6126
5945
  }
6127
5946
  if (process.env.VOLUTE_TIMEZONE && !process.env.TZ) {
6128
5947
  process.env.TZ = process.env.VOLUTE_TIMEZONE;
@@ -6132,7 +5951,7 @@ async function startDaemon(opts) {
6132
5951
  const myPid = String(process.pid);
6133
5952
  const home = voluteHome();
6134
5953
  if (!opts.foreground) {
6135
- const rotatingLog = new RotatingLog(resolve17(home, "daemon.log"));
5954
+ const rotatingLog = new RotatingLog(resolve18(home, "daemon.log"));
6136
5955
  logger_default.setOutput((line) => rotatingLog.write(`${line}
6137
5956
  `));
6138
5957
  const write = (...args) => rotatingLog.write(`${format(...args)}
@@ -6142,9 +5961,9 @@ async function startDaemon(opts) {
6142
5961
  console.warn = write;
6143
5962
  console.info = write;
6144
5963
  }
6145
- const DAEMON_PID_PATH = resolve17(home, "daemon.pid");
6146
- const DAEMON_JSON_PATH = resolve17(home, "daemon.json");
6147
- 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 });
6148
5967
  migrateAgentsToMinds();
6149
5968
  try {
6150
5969
  await ensureSharedRepo();
@@ -6158,12 +5977,16 @@ async function startDaemon(opts) {
6158
5977
  logger_default.error("failed to sync built-in skills", logger_default.errorData(err));
6159
5978
  }
6160
5979
  const token = process.env.VOLUTE_DAEMON_TOKEN || randomBytes2(32).toString("hex");
6161
- process.env.VOLUTE_DAEMON_TOKEN = token;
6162
- process.env.VOLUTE_DAEMON_PORT = String(port);
6163
- process.env.VOLUTE_DAEMON_HOSTNAME = hostname;
6164
- 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;
6165
5988
  try {
6166
- server = await startServer({ port, hostname });
5989
+ result = await startServer({ port, hostname: "0.0.0.0", tls });
6167
5990
  } catch (err) {
6168
5991
  const e = err;
6169
5992
  if (e.code === "EADDRINUSE") {
@@ -6172,11 +5995,17 @@ async function startDaemon(opts) {
6172
5995
  }
6173
5996
  throw err;
6174
5997
  }
6175
- writeFileSync8(DAEMON_PID_PATH, myPid, { mode: 420 });
6176
- writeFileSync8(DAEMON_JSON_PATH, `${JSON.stringify({ port, hostname, token }, null, 2)}
6177
- `, {
6178
- mode: 420
6179
- });
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 });
6180
6009
  const delivery = initDeliveryManager();
6181
6010
  const manager = initMindManager();
6182
6011
  manager.loadCrashAttempts();
@@ -6208,8 +6037,8 @@ async function startDaemon(opts) {
6208
6037
  if (sleepManager.isSleeping(entry.name)) {
6209
6038
  try {
6210
6039
  const dir = mindDir(entry.name);
6211
- const daemonPort = process.env.VOLUTE_DAEMON_PORT ? parseInt(process.env.VOLUTE_DAEMON_PORT, 10) : void 0;
6212
- 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);
6213
6042
  scheduler.loadSchedules(entry.name);
6214
6043
  } catch (err) {
6215
6044
  logger_default.error(
@@ -6246,7 +6075,7 @@ async function startDaemon(opts) {
6246
6075
  });
6247
6076
  await Promise.all(workers);
6248
6077
  }
6249
- import("./cloud-sync-C6WRYRVR.js").then(
6078
+ import("./cloud-sync-DIU3OCPV.js").then(
6250
6079
  ({ consumeQueuedMessages }) => consumeQueuedMessages().catch((err) => {
6251
6080
  logger_default.warn("failed to consume queued cloud messages", logger_default.errorData(err));
6252
6081
  })
@@ -6254,7 +6083,7 @@ async function startDaemon(opts) {
6254
6083
  logger_default.warn("failed to load cloud-sync module", logger_default.errorData(err));
6255
6084
  });
6256
6085
  try {
6257
- const { backfillTemplateHashes, notifyVersionUpdate } = await import("./version-notify-5FGUAVSF.js");
6086
+ const { backfillTemplateHashes, notifyVersionUpdate } = await import("./version-notify-SZ75QRGO.js");
6258
6087
  backfillTemplateHashes();
6259
6088
  notifyVersionUpdate().catch((err) => {
6260
6089
  logger_default.warn("failed to send version update notifications", logger_default.errorData(err));
@@ -6271,13 +6100,13 @@ async function startDaemon(opts) {
6271
6100
  logger_default.info(`running on ${hostname}:${port}, pid ${myPid}`);
6272
6101
  function cleanup() {
6273
6102
  try {
6274
- if (readFileSync10(DAEMON_PID_PATH, "utf-8").trim() === myPid) {
6103
+ if (readFileSync11(DAEMON_PID_PATH, "utf-8").trim() === myPid) {
6275
6104
  unlinkSync(DAEMON_PID_PATH);
6276
6105
  }
6277
6106
  } catch {
6278
6107
  }
6279
6108
  try {
6280
- const data = JSON.parse(readFileSync10(DAEMON_JSON_PATH, "utf-8"));
6109
+ const data = JSON.parse(readFileSync11(DAEMON_JSON_PATH, "utf-8"));
6281
6110
  if (data.token === token) {
6282
6111
  unlinkSync(DAEMON_JSON_PATH);
6283
6112
  }
@@ -6291,9 +6120,9 @@ async function startDaemon(opts) {
6291
6120
  logger_default.info("shutting down...");
6292
6121
  const safe = (label, fn) => {
6293
6122
  try {
6294
- const result = fn();
6295
- if (result instanceof Promise)
6296
- 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)));
6297
6126
  } catch (err) {
6298
6127
  logger_default.error(`shutdown: ${label} failed`, logger_default.errorData(err));
6299
6128
  }
@@ -6325,9 +6154,10 @@ async function startDaemon(opts) {
6325
6154
  process.on("exit", cleanup);
6326
6155
  }
6327
6156
  if (import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith("daemon.ts")) {
6328
- let port = 4200;
6157
+ let port = 1618;
6329
6158
  let hostname = "127.0.0.1";
6330
6159
  let foreground = false;
6160
+ let tailscale = false;
6331
6161
  for (let i = 2; i < process.argv.length; i++) {
6332
6162
  if (process.argv[i] === "--port" && process.argv[i + 1]) {
6333
6163
  port = parseInt(process.argv[i + 1], 10);
@@ -6337,9 +6167,11 @@ if (import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith
6337
6167
  i++;
6338
6168
  } else if (process.argv[i] === "--foreground") {
6339
6169
  foreground = true;
6170
+ } else if (process.argv[i] === "--tailscale") {
6171
+ tailscale = true;
6340
6172
  }
6341
6173
  }
6342
- startDaemon({ port, hostname, foreground });
6174
+ startDaemon({ port, hostname, foreground, tailscale });
6343
6175
  }
6344
6176
  export {
6345
6177
  startDaemon