volute 0.23.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README.md +5 -5
  2. package/dist/{activity-events-3WHHCOBB.js → activity-events-4O37J7PD.js} +2 -2
  3. package/dist/api.d.ts +306 -15
  4. package/dist/{channel-BOOMFULW.js → channel-HZOSHGNF.js} +1 -1
  5. package/dist/{chunk-QIXPN3OO.js → chunk-2767L2RZ.js} +5 -5
  6. package/dist/{chunk-SGPEZ32F.js → chunk-33XAVCS4.js} +16 -0
  7. package/dist/{chunk-VT5QODNE.js → chunk-3AIBT4TW.js} +4 -3
  8. package/dist/{chunk-RK627D57.js → chunk-4TJ72QQ3.js} +2 -2
  9. package/dist/{chunk-A4S7H6G6.js → chunk-BFK6SOEJ.js} +1 -1
  10. package/dist/{chunk-HGCDWKSP.js → chunk-E7GOKNOT.js} +1 -1
  11. package/dist/{chunk-M5CNKH4J.js → chunk-NOBRGACV.js} +7 -7
  12. package/dist/{chunk-ISWZ6QUK.js → chunk-OOW675I3.js} +778 -108
  13. package/dist/{chunk-TFS25FIM.js → chunk-P3W36ZGD.js} +1 -1
  14. package/dist/{chunk-JG4CCJOA.js → chunk-TQDITGES.js} +33 -15
  15. package/dist/{chunk-KFI7TQJ6.js → chunk-TRQEV3CD.js} +9 -5
  16. package/dist/cli.js +18 -18
  17. package/dist/{cloud-sync-PI47U2LT.js → cloud-sync-DIU3OCPV.js} +6 -8
  18. package/dist/{connector-PYT5UOTZ.js → connector-M6XFI6GM.js} +1 -1
  19. package/dist/{create-WIDA3M4C.js → create-VDQJER52.js} +1 -1
  20. package/dist/{daemon-client-ZHCDL4RS.js → daemon-client-JOVQZ52X.js} +1 -1
  21. package/dist/{daemon-restart-RMGOOGPE.js → daemon-restart-YMPEATQH.js} +5 -5
  22. package/dist/daemon.js +665 -813
  23. package/dist/{delete-LOIANQGD.js → delete-2MRR4JX5.js} +1 -1
  24. package/dist/{down-WSUASL5E.js → down-674SX2IZ.js} +2 -2
  25. package/dist/{env-4PHIHTF4.js → env-2FPOZK37.js} +1 -1
  26. package/dist/{export-XD6PJBQP.js → export-IKFAPRAO.js} +1 -1
  27. package/dist/{file-X4L5TTOL.js → file-KT3UIQM3.js} +1 -1
  28. package/dist/{history-HTEKRNID.js → history-46WZN5CN.js} +1 -1
  29. package/dist/{import-EAXTHHXL.js → import-FRDPQPJ2.js} +1 -1
  30. package/dist/{log-SRO5Q6AD.js → log-6SGSSR3D.js} +1 -1
  31. package/dist/{logs-HNTNNBDW.js → logs-HRBONI5I.js} +1 -1
  32. package/dist/{merge-B6SYTGI7.js → merge-KSFJKX6T.js} +1 -1
  33. package/dist/{message-delivery-FHV4NO2F.js → message-delivery-S7BCNV6Y.js} +5 -5
  34. package/dist/{mind-BTXR5B3C.js → mind-KPLCRKQA.js} +17 -17
  35. package/dist/{mind-activity-tracker-PGC3DBJ7.js → mind-activity-tracker-NMDDEV3K.js} +3 -3
  36. package/dist/{mind-manager-KMY4GA2J.js → mind-manager-ZNRIYEK3.js} +2 -2
  37. package/dist/{mind-sleep-FWRBIFBS.js → mind-sleep-GHPTSAYN.js} +1 -1
  38. package/dist/{mind-wake-LJK2YU5X.js → mind-wake-BJDJFMDF.js} +1 -1
  39. package/dist/{package-CUBJ4PKS.js → package-S5YF25XV.js} +1 -1
  40. package/dist/{pull-GRQAXM2E.js → pull-D32SPFVU.js} +1 -1
  41. package/dist/{restart-CIDAKGG2.js → restart-5BMNV7KU.js} +1 -1
  42. package/dist/{schedule-NLR3LZLY.js → schedule-YEFDLVMJ.js} +1 -1
  43. package/dist/{seed-3H2MRREW.js → seed-6FEKB3YC.js} +1 -1
  44. package/dist/{send-RP2TA7SG.js → send-IISDYFCL.js} +1 -1
  45. package/dist/{service-7BFXDI6J.js → service-FASYWLTC.js} +3 -3
  46. package/dist/{setup-SSIIXQMI.js → setup-BMLM2UTK.js} +1 -1
  47. package/dist/{shared-2OGT3NSL.js → shared-LWMNTTZN.js} +4 -4
  48. package/dist/{skill-Q2Y6PQ3L.js → skill-BQOFACEI.js} +1 -1
  49. package/dist/skills/volute-mind/SKILL.md +71 -1
  50. package/dist/{sleep-manager-2TMQ65E4.js → sleep-manager-XXSWQQLE.js} +5 -5
  51. package/dist/{sprout-UKCYBGHK.js → sprout-CGSW4CF5.js} +3 -3
  52. package/dist/{start-JR6CUUWF.js → start-C7XITZ5O.js} +1 -1
  53. package/dist/{status-5XDGYHKP.js → status-LYS4NUOZ.js} +1 -1
  54. package/dist/{status-H2MKDN6L.js → status-SIRPLEZC.js} +4 -3
  55. package/dist/{stop-VKPGK25U.js → stop-CVKBSLXY.js} +1 -1
  56. package/dist/tailscale-AJ4VL5XK.js +49 -0
  57. package/dist/{up-Z5JRG2M2.js → up-OMHACRJL.js} +2 -2
  58. package/dist/{update-ELC6MEUT.js → update-7XCZMYBT.js} +7 -7
  59. package/dist/{upgrade-GXW2EQY3.js → upgrade-7RUIXGOO.js} +1 -1
  60. package/dist/{variant-A4I7PHXS.js → variant-UGREB4G5.js} +4 -4
  61. package/dist/{version-notify-LKABEJSA.js → version-notify-SZ75QRGO.js} +5 -5
  62. package/dist/web-assets/assets/index-Bx9WDoaQ.js +69 -0
  63. package/dist/web-assets/assets/index-Clz8OhmJ.css +1 -0
  64. package/dist/web-assets/index.html +2 -2
  65. package/drizzle/0013_user_profiles.sql +3 -0
  66. package/drizzle/0014_conversation_reads.sql +7 -0
  67. package/drizzle/meta/0013_snapshot.json +7 -0
  68. package/drizzle/meta/_journal.json +14 -0
  69. package/package.json +1 -1
  70. package/templates/_base/src/lib/format-prefix.ts +18 -2
  71. package/templates/_base/src/lib/routing.ts +2 -1
  72. package/templates/_base/src/lib/types.ts +8 -0
  73. package/dist/chunk-G5KRTU2F.js +0 -76
  74. package/dist/web-assets/assets/index-CZ26vsyY.js +0 -69
  75. package/dist/web-assets/assets/index-DyyAvJwW.css +0 -1
  76. /package/dist/{pages-YSTRWJR4.js → pages-TWR6U7DS.js} +0 -0
@@ -4,11 +4,11 @@ import {
4
4
  } from "./chunk-HFCBO2GL.js";
5
5
  import {
6
6
  markIdle
7
- } from "./chunk-HGCDWKSP.js";
7
+ } from "./chunk-E7GOKNOT.js";
8
8
  import {
9
9
  publish,
10
10
  subscribe
11
- } from "./chunk-A4S7H6G6.js";
11
+ } from "./chunk-BFK6SOEJ.js";
12
12
  import {
13
13
  RestartTracker,
14
14
  RotatingLog,
@@ -17,7 +17,7 @@ import {
17
17
  getPrompt,
18
18
  loadJsonMap,
19
19
  saveJsonMap
20
- } from "./chunk-M5CNKH4J.js";
20
+ } from "./chunk-NOBRGACV.js";
21
21
  import {
22
22
  readVoluteConfig
23
23
  } from "./chunk-XLC342FO.js";
@@ -25,10 +25,15 @@ import {
25
25
  loadMergedEnv
26
26
  } from "./chunk-PHU4DEAJ.js";
27
27
  import {
28
+ conversationParticipants,
29
+ conversationReads,
30
+ conversations,
28
31
  deliveryQueue,
29
32
  getDb,
30
- mindHistory
31
- } from "./chunk-SGPEZ32F.js";
33
+ messages,
34
+ mindHistory,
35
+ users
36
+ } from "./chunk-33XAVCS4.js";
32
37
  import {
33
38
  logger_default
34
39
  } from "./chunk-YUIHSKR6.js";
@@ -61,10 +66,132 @@ import {
61
66
  renameSync,
62
67
  writeFileSync as writeFileSync3
63
68
  } from "fs";
64
- import { resolve as resolve7 } from "path";
69
+ import { resolve as resolve8 } from "path";
65
70
  import { promisify } from "util";
66
71
  import { CronExpressionParser as CronExpressionParser2 } from "cron-parser";
67
- import { and as and2, eq as eq2, inArray } from "drizzle-orm";
72
+ import { and as and4, eq as eq4, inArray as inArray2 } from "drizzle-orm";
73
+
74
+ // src/lib/auth.ts
75
+ import { compareSync, hashSync } from "bcryptjs";
76
+ import { and, count, eq } from "drizzle-orm";
77
+ var userSelectFields = {
78
+ id: users.id,
79
+ username: users.username,
80
+ role: users.role,
81
+ user_type: users.user_type,
82
+ display_name: users.display_name,
83
+ description: users.description,
84
+ avatar: users.avatar,
85
+ created_at: users.created_at
86
+ };
87
+ async function createUser(username, password) {
88
+ const db = await getDb();
89
+ const hash = hashSync(password, 10);
90
+ const [{ value }] = await db.select({ value: count() }).from(users).where(eq(users.user_type, "brain"));
91
+ const role = value === 0 ? "admin" : "pending";
92
+ const [result] = await db.insert(users).values({ username, password_hash: hash, role }).returning(userSelectFields);
93
+ return result;
94
+ }
95
+ async function verifyUser(username, password) {
96
+ const db = await getDb();
97
+ const row = await db.select().from(users).where(eq(users.username, username)).get();
98
+ if (!row) return null;
99
+ if (row.user_type === "mind") return null;
100
+ if (!compareSync(password, row.password_hash)) return null;
101
+ const { password_hash: _, ...user } = row;
102
+ return user;
103
+ }
104
+ async function getUser(id) {
105
+ const db = await getDb();
106
+ const row = await db.select(userSelectFields).from(users).where(eq(users.id, id)).get();
107
+ return row ?? null;
108
+ }
109
+ async function getUserByUsername(username) {
110
+ const db = await getDb();
111
+ const row = await db.select(userSelectFields).from(users).where(eq(users.username, username)).get();
112
+ return row ?? null;
113
+ }
114
+ async function listUsers() {
115
+ const db = await getDb();
116
+ return db.select(userSelectFields).from(users).orderBy(users.created_at).all();
117
+ }
118
+ async function listPendingUsers() {
119
+ const db = await getDb();
120
+ return db.select(userSelectFields).from(users).where(eq(users.role, "pending")).orderBy(users.created_at).all();
121
+ }
122
+ async function listUsersByType(userType) {
123
+ const db = await getDb();
124
+ return db.select(userSelectFields).from(users).where(eq(users.user_type, userType)).orderBy(users.created_at).all();
125
+ }
126
+ async function getOrCreateMindUser(mindName) {
127
+ const db = await getDb();
128
+ const existing = await db.select(userSelectFields).from(users).where(and(eq(users.username, mindName), eq(users.user_type, "mind"))).get();
129
+ if (existing) return existing;
130
+ try {
131
+ const [result] = await db.insert(users).values({
132
+ username: mindName,
133
+ password_hash: "!mind",
134
+ role: "mind",
135
+ user_type: "mind"
136
+ }).returning(userSelectFields);
137
+ return result;
138
+ } catch (err) {
139
+ if (err instanceof Error && err.message.includes("UNIQUE constraint")) {
140
+ const retried = await db.select(userSelectFields).from(users).where(and(eq(users.username, mindName), eq(users.user_type, "mind"))).get();
141
+ if (retried) return retried;
142
+ }
143
+ throw err;
144
+ }
145
+ }
146
+ async function deleteMindUser(mindName) {
147
+ const db = await getDb();
148
+ await db.delete(users).where(and(eq(users.username, mindName), eq(users.user_type, "mind")));
149
+ }
150
+ async function changePassword(userId, currentPassword, newPassword) {
151
+ const db = await getDb();
152
+ const row = await db.select().from(users).where(eq(users.id, userId)).get();
153
+ if (!row) return false;
154
+ if (!compareSync(currentPassword, row.password_hash)) return false;
155
+ const hash = hashSync(newPassword, 10);
156
+ await db.update(users).set({ password_hash: hash }).where(eq(users.id, userId));
157
+ return true;
158
+ }
159
+ async function approveUser(id) {
160
+ const db = await getDb();
161
+ await db.update(users).set({ role: "user" }).where(and(eq(users.id, id), eq(users.role, "pending")));
162
+ }
163
+ async function countAdmins() {
164
+ const db = await getDb();
165
+ const [{ value }] = await db.select({ value: count() }).from(users).where(eq(users.role, "admin"));
166
+ return value;
167
+ }
168
+ async function setUserRole(id, role) {
169
+ const db = await getDb();
170
+ const target = await db.select({ id: users.id }).from(users).where(eq(users.id, id)).get();
171
+ if (!target) throw new Error("User not found");
172
+ await db.update(users).set({ role }).where(eq(users.id, id));
173
+ }
174
+ async function deleteUser(id) {
175
+ const db = await getDb();
176
+ const target = await db.select({ id: users.id }).from(users).where(and(eq(users.id, id), eq(users.user_type, "brain"))).get();
177
+ if (!target) throw new Error("User not found");
178
+ await db.delete(users).where(and(eq(users.id, id), eq(users.user_type, "brain")));
179
+ }
180
+ async function updateUserProfile(userId, profile) {
181
+ const db = await getDb();
182
+ const target = await db.select({ id: users.id }).from(users).where(eq(users.id, userId)).get();
183
+ if (!target) throw new Error("User not found");
184
+ await db.update(users).set(profile).where(eq(users.id, userId));
185
+ }
186
+ async function syncMindProfile(mindName, config) {
187
+ const user = await getOrCreateMindUser(mindName);
188
+ const db = await getDb();
189
+ await db.update(users).set({
190
+ display_name: config.displayName ?? null,
191
+ description: config.description ?? null,
192
+ avatar: config.avatar ?? null
193
+ }).where(eq(users.id, user.id));
194
+ }
68
195
 
69
196
  // src/lib/pages-watcher.ts
70
197
  import { existsSync, readdirSync, statSync, watch } from "fs";
@@ -521,19 +648,19 @@ var ConnectorManager = class {
521
648
  const stopKey = `${mindName}:${type}`;
522
649
  this.stopping.add(stopKey);
523
650
  mindMap.delete(type);
524
- await new Promise((resolve8) => {
525
- tracked.child.on("exit", () => resolve8());
651
+ await new Promise((resolve9) => {
652
+ tracked.child.on("exit", () => resolve9());
526
653
  try {
527
654
  process.kill(-tracked.child.pid, "SIGTERM");
528
655
  } catch {
529
- resolve8();
656
+ resolve9();
530
657
  }
531
658
  setTimeout(() => {
532
659
  try {
533
660
  process.kill(-tracked.child.pid, "SIGKILL");
534
661
  } catch {
535
662
  }
536
- resolve8();
663
+ resolve9();
537
664
  }, 5e3);
538
665
  });
539
666
  this.stopping.delete(stopKey);
@@ -647,7 +774,75 @@ function publish2(mind, event) {
647
774
  }
648
775
 
649
776
  // src/lib/delivery/delivery-manager.ts
650
- import { and, eq, sql } from "drizzle-orm";
777
+ import { readFile } from "fs/promises";
778
+ import { extname, resolve as resolve5 } from "path";
779
+ import { and as and3, eq as eq3, sql as sql2 } from "drizzle-orm";
780
+
781
+ // src/lib/events/conversations.ts
782
+ import { randomUUID } from "crypto";
783
+ import { and as and2, desc, eq as eq2, inArray, isNull, lt, sql } from "drizzle-orm";
784
+
785
+ // src/lib/webhook.ts
786
+ var slog = logger_default.child("webhook");
787
+ function getWebhookUrl() {
788
+ return process.env.VOLUTE_WEBHOOK_URL;
789
+ }
790
+ function getAuthHeaders() {
791
+ const headers = { "Content-Type": "application/json" };
792
+ const secret = process.env.VOLUTE_WEBHOOK_SECRET;
793
+ if (secret) headers.Authorization = `Bearer ${secret}`;
794
+ return headers;
795
+ }
796
+ function fireWebhook(event) {
797
+ try {
798
+ const url = getWebhookUrl();
799
+ if (!url) return;
800
+ const payload = { ...event, timestamp: event.timestamp ?? (/* @__PURE__ */ new Date()).toISOString() };
801
+ fetch(url, {
802
+ method: "POST",
803
+ headers: getAuthHeaders(),
804
+ body: JSON.stringify(payload)
805
+ }).then((res) => {
806
+ if (!res.ok) {
807
+ slog.warn(`webhook ${event.event} returned HTTP ${res.status}`);
808
+ }
809
+ }).catch((err) => {
810
+ slog.warn(`webhook delivery failed for ${event.event}`, logger_default.errorData(err));
811
+ });
812
+ } catch (err) {
813
+ slog.error(`webhook ${event.event} failed to serialize`, logger_default.errorData(err));
814
+ }
815
+ }
816
+ function initWebhook() {
817
+ const url = getWebhookUrl();
818
+ if (!url) return () => {
819
+ };
820
+ try {
821
+ const parsed = new URL(url);
822
+ if (!["http:", "https:"].includes(parsed.protocol)) {
823
+ slog.error(`VOLUTE_WEBHOOK_URL has unsupported protocol: ${parsed.protocol}`);
824
+ return () => {
825
+ };
826
+ }
827
+ } catch {
828
+ slog.error(`VOLUTE_WEBHOOK_URL is not a valid URL`);
829
+ return () => {
830
+ };
831
+ }
832
+ slog.info("webhook enabled");
833
+ return subscribe((event) => {
834
+ try {
835
+ fireWebhook({
836
+ event: event.type,
837
+ mind: event.mind,
838
+ data: { summary: event.summary, ...event.metadata },
839
+ timestamp: event.created_at
840
+ });
841
+ } catch (err) {
842
+ slog.error(`failed to fire webhook for ${event.type}`, logger_default.errorData(err));
843
+ }
844
+ });
845
+ }
651
846
 
652
847
  // src/lib/events/conversation-events.ts
653
848
  var subscribers2 = /* @__PURE__ */ new Map();
@@ -677,6 +872,330 @@ function publish3(conversationId, event) {
677
872
  }
678
873
  }
679
874
 
875
+ // src/lib/events/conversations.ts
876
+ async function createConversation(mindName, channel, opts) {
877
+ const db = await getDb();
878
+ const id = randomUUID();
879
+ const type = opts?.type ?? "dm";
880
+ const name = opts?.name ?? null;
881
+ await db.transaction(async (tx) => {
882
+ await tx.insert(conversations).values({
883
+ id,
884
+ mind_name: mindName,
885
+ channel,
886
+ type,
887
+ name,
888
+ user_id: opts?.userId ?? null,
889
+ title: opts?.title ?? null
890
+ });
891
+ if (opts?.participantIds && opts.participantIds.length > 0) {
892
+ await tx.insert(conversationParticipants).values(
893
+ opts.participantIds.map((uid, i) => ({
894
+ conversation_id: id,
895
+ user_id: uid,
896
+ role: i === 0 ? "owner" : "member"
897
+ }))
898
+ );
899
+ }
900
+ });
901
+ fireWebhook({
902
+ event: "conversation_created",
903
+ mind: mindName ?? "",
904
+ data: { id, mindName, channel, type, name, title: opts?.title ?? null }
905
+ });
906
+ return {
907
+ id,
908
+ mind_name: mindName,
909
+ channel,
910
+ type,
911
+ name,
912
+ user_id: opts?.userId ?? null,
913
+ title: opts?.title ?? null,
914
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
915
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
916
+ };
917
+ }
918
+ async function getConversation(id) {
919
+ const db = await getDb();
920
+ const row = await db.select().from(conversations).where(eq2(conversations.id, id)).get();
921
+ return row ?? null;
922
+ }
923
+ async function addParticipant(conversationId, userId, role = "member") {
924
+ const db = await getDb();
925
+ await db.insert(conversationParticipants).values({
926
+ conversation_id: conversationId,
927
+ user_id: userId,
928
+ role
929
+ });
930
+ }
931
+ async function removeParticipant(conversationId, userId) {
932
+ const db = await getDb();
933
+ await db.delete(conversationParticipants).where(
934
+ and2(
935
+ eq2(conversationParticipants.conversation_id, conversationId),
936
+ eq2(conversationParticipants.user_id, userId)
937
+ )
938
+ );
939
+ }
940
+ async function getParticipants(conversationId) {
941
+ const db = await getDb();
942
+ const rows = await db.select({
943
+ userId: conversationParticipants.user_id,
944
+ username: users.username,
945
+ userType: users.user_type,
946
+ role: conversationParticipants.role,
947
+ displayName: users.display_name,
948
+ description: users.description,
949
+ avatar: users.avatar
950
+ }).from(conversationParticipants).innerJoin(users, eq2(conversationParticipants.user_id, users.id)).where(eq2(conversationParticipants.conversation_id, conversationId)).all();
951
+ return rows;
952
+ }
953
+ async function isParticipant(conversationId, userId) {
954
+ const db = await getDb();
955
+ const row = await db.select({ user_id: conversationParticipants.user_id }).from(conversationParticipants).where(
956
+ and2(
957
+ eq2(conversationParticipants.conversation_id, conversationId),
958
+ eq2(conversationParticipants.user_id, userId)
959
+ )
960
+ ).get();
961
+ return row != null;
962
+ }
963
+ async function listConversationsForUser(userId) {
964
+ const db = await getDb();
965
+ const participantRows = await db.select({ conversation_id: conversationParticipants.conversation_id }).from(conversationParticipants).where(eq2(conversationParticipants.user_id, userId)).all();
966
+ if (participantRows.length === 0) return [];
967
+ const convIds = participantRows.map((r) => r.conversation_id);
968
+ return await db.select().from(conversations).where(inArray(conversations.id, convIds)).orderBy(desc(conversations.updated_at)).all();
969
+ }
970
+ async function isParticipantOrOwner(conversationId, userId) {
971
+ if (await isParticipant(conversationId, userId)) return true;
972
+ const db = await getDb();
973
+ const row = await db.select().from(conversations).where(and2(eq2(conversations.id, conversationId), eq2(conversations.user_id, userId))).get();
974
+ return row != null;
975
+ }
976
+ async function deleteConversationForUser(id, userId) {
977
+ if (!await isParticipantOrOwner(id, userId)) return false;
978
+ await deleteConversation(id);
979
+ return true;
980
+ }
981
+ async function addMessage(conversationId, role, senderName, content) {
982
+ const db = await getDb();
983
+ const serialized = JSON.stringify(content);
984
+ 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 });
985
+ await db.update(conversations).set({ updated_at: sql`datetime('now')` }).where(eq2(conversations.id, conversationId));
986
+ if (role === "user") {
987
+ const firstText = content.find((b) => b.type === "text");
988
+ const title = firstText ? firstText.text.slice(0, 80) : "";
989
+ if (title) {
990
+ await db.update(conversations).set({ title }).where(and2(eq2(conversations.id, conversationId), isNull(conversations.title)));
991
+ }
992
+ }
993
+ const msg = {
994
+ id: result.id,
995
+ conversation_id: conversationId,
996
+ role,
997
+ sender_name: senderName,
998
+ content,
999
+ created_at: result.created_at
1000
+ };
1001
+ publish3(conversationId, {
1002
+ type: "message",
1003
+ id: msg.id,
1004
+ role: msg.role,
1005
+ senderName: msg.sender_name,
1006
+ content: msg.content,
1007
+ createdAt: msg.created_at
1008
+ });
1009
+ const conv = await db.select({ mind_name: conversations.mind_name }).from(conversations).where(eq2(conversations.id, conversationId)).get();
1010
+ fireWebhook({
1011
+ event: "message_created",
1012
+ mind: conv?.mind_name ?? "",
1013
+ data: {
1014
+ conversationId,
1015
+ messageId: result.id,
1016
+ role,
1017
+ senderName,
1018
+ content: content.filter((b) => b.type !== "image"),
1019
+ createdAt: result.created_at
1020
+ }
1021
+ });
1022
+ return msg;
1023
+ }
1024
+ async function getMessages(conversationId) {
1025
+ const db = await getDb();
1026
+ const rows = await db.select().from(messages).where(eq2(messages.conversation_id, conversationId)).orderBy(messages.created_at).all();
1027
+ return rows.map(parseMessageRow);
1028
+ }
1029
+ async function getMessagesPaginated(conversationId, opts) {
1030
+ const db = await getDb();
1031
+ const limit = Math.min(Math.max(opts?.limit ?? 50, 1), 100);
1032
+ const conditions = [eq2(messages.conversation_id, conversationId)];
1033
+ if (opts?.before != null) {
1034
+ conditions.push(lt(messages.id, opts.before));
1035
+ }
1036
+ const rows = await db.select().from(messages).where(and2(...conditions)).orderBy(desc(messages.id)).limit(limit + 1).all();
1037
+ const hasMore = rows.length > limit;
1038
+ const page = rows.slice(0, limit).reverse();
1039
+ return {
1040
+ messages: page.map(parseMessageRow),
1041
+ hasMore
1042
+ };
1043
+ }
1044
+ function parseMessageRow(row) {
1045
+ let content;
1046
+ try {
1047
+ const parsed = JSON.parse(row.content);
1048
+ content = Array.isArray(parsed) ? parsed : [{ type: "text", text: row.content }];
1049
+ } catch {
1050
+ content = [{ type: "text", text: row.content }];
1051
+ }
1052
+ return { ...row, role: row.role, content };
1053
+ }
1054
+ async function listConversationsWithParticipants(userId) {
1055
+ const convs = await listConversationsForUser(userId);
1056
+ if (convs.length === 0) return [];
1057
+ const db = await getDb();
1058
+ const convIds = convs.map((c) => c.id);
1059
+ const rows = await db.select({
1060
+ conversationId: conversationParticipants.conversation_id,
1061
+ userId: users.id,
1062
+ username: users.username,
1063
+ userType: users.user_type,
1064
+ role: conversationParticipants.role,
1065
+ displayName: users.display_name,
1066
+ description: users.description,
1067
+ avatar: users.avatar
1068
+ }).from(conversationParticipants).innerJoin(users, eq2(conversationParticipants.user_id, users.id)).where(inArray(conversationParticipants.conversation_id, convIds));
1069
+ const byConv = /* @__PURE__ */ new Map();
1070
+ for (const r of rows) {
1071
+ let arr = byConv.get(r.conversationId);
1072
+ if (!arr) {
1073
+ arr = [];
1074
+ byConv.set(r.conversationId, arr);
1075
+ }
1076
+ arr.push({
1077
+ userId: r.userId,
1078
+ username: r.username,
1079
+ userType: r.userType,
1080
+ role: r.role,
1081
+ displayName: r.displayName,
1082
+ description: r.description,
1083
+ avatar: r.avatar
1084
+ });
1085
+ }
1086
+ const lastMsgIds = await db.select({
1087
+ conversationId: messages.conversation_id,
1088
+ maxId: sql`MAX(${messages.id})`
1089
+ }).from(messages).where(inArray(messages.conversation_id, convIds)).groupBy(messages.conversation_id);
1090
+ const byLastMsg = /* @__PURE__ */ new Map();
1091
+ if (lastMsgIds.length > 0) {
1092
+ const msgRows = await db.select().from(messages).where(
1093
+ inArray(
1094
+ messages.id,
1095
+ lastMsgIds.map((r) => r.maxId)
1096
+ )
1097
+ );
1098
+ for (const m of msgRows) {
1099
+ let text = "";
1100
+ try {
1101
+ const parsed = JSON.parse(m.content);
1102
+ const blocks = Array.isArray(parsed) ? parsed : [];
1103
+ const textBlock = blocks.find((b) => b.type === "text");
1104
+ if (textBlock && "text" in textBlock) text = textBlock.text;
1105
+ } catch {
1106
+ text = m.content;
1107
+ }
1108
+ byLastMsg.set(m.conversation_id, {
1109
+ role: m.role,
1110
+ senderName: m.sender_name,
1111
+ text,
1112
+ createdAt: m.created_at
1113
+ });
1114
+ }
1115
+ }
1116
+ return convs.map((c) => ({
1117
+ ...c,
1118
+ participants: byConv.get(c.id) ?? [],
1119
+ lastMessage: byLastMsg.get(c.id)
1120
+ }));
1121
+ }
1122
+ async function findDMConversation(mindName, participantIds) {
1123
+ const db = await getDb();
1124
+ const mindConvs = await db.select({ id: conversations.id }).from(conversations).where(and2(eq2(conversations.mind_name, mindName), eq2(conversations.type, "dm"))).all();
1125
+ for (const conv of mindConvs) {
1126
+ const rows = await db.select({ user_id: conversationParticipants.user_id }).from(conversationParticipants).where(eq2(conversationParticipants.conversation_id, conv.id)).all();
1127
+ if (rows.length !== 2) continue;
1128
+ const ids = new Set(rows.map((r) => r.user_id));
1129
+ if (ids.has(participantIds[0]) && ids.has(participantIds[1])) {
1130
+ return conv.id;
1131
+ }
1132
+ }
1133
+ return null;
1134
+ }
1135
+ async function deleteConversation(id) {
1136
+ const db = await getDb();
1137
+ await db.delete(conversations).where(eq2(conversations.id, id));
1138
+ }
1139
+ async function createChannel(name, creatorId) {
1140
+ const participantIds = creatorId ? [creatorId] : [];
1141
+ return createConversation(null, "volute", {
1142
+ type: "channel",
1143
+ name,
1144
+ title: name,
1145
+ participantIds
1146
+ });
1147
+ }
1148
+ async function getChannelByName(name) {
1149
+ const db = await getDb();
1150
+ const row = await db.select().from(conversations).where(and2(eq2(conversations.name, name), eq2(conversations.type, "channel"))).get();
1151
+ return row ?? null;
1152
+ }
1153
+ async function listChannels() {
1154
+ const db = await getDb();
1155
+ return await db.select().from(conversations).where(eq2(conversations.type, "channel")).orderBy(conversations.name).all();
1156
+ }
1157
+ async function joinChannel(conversationId, userId) {
1158
+ if (await isParticipant(conversationId, userId)) return;
1159
+ await addParticipant(conversationId, userId);
1160
+ }
1161
+ async function leaveChannel(conversationId, userId) {
1162
+ await removeParticipant(conversationId, userId);
1163
+ }
1164
+ async function getUnreadCounts(userId, conversationIds) {
1165
+ if (conversationIds.length === 0) return {};
1166
+ const db = await getDb();
1167
+ const rows = await db.select({
1168
+ conversationId: messages.conversation_id,
1169
+ count: sql`COUNT(*)`
1170
+ }).from(messages).leftJoin(
1171
+ conversationReads,
1172
+ and2(
1173
+ eq2(conversationReads.conversation_id, messages.conversation_id),
1174
+ eq2(conversationReads.user_id, userId)
1175
+ )
1176
+ ).where(
1177
+ and2(
1178
+ inArray(messages.conversation_id, conversationIds),
1179
+ sql`${messages.id} > COALESCE(${conversationReads.last_read_message_id}, 0)`
1180
+ )
1181
+ ).groupBy(messages.conversation_id);
1182
+ const result = {};
1183
+ for (const row of rows) {
1184
+ result[row.conversationId] = row.count;
1185
+ }
1186
+ return result;
1187
+ }
1188
+ async function markConversationRead(userId, conversationId) {
1189
+ const db = await getDb();
1190
+ const maxRow = await db.select({ maxId: sql`MAX(${messages.id})` }).from(messages).where(eq2(messages.conversation_id, conversationId)).get();
1191
+ const maxId = maxRow?.maxId ?? 0;
1192
+ if (maxId === 0) return;
1193
+ await db.insert(conversationReads).values({ user_id: userId, conversation_id: conversationId, last_read_message_id: maxId }).onConflictDoUpdate({
1194
+ target: [conversationReads.user_id, conversationReads.conversation_id],
1195
+ set: { last_read_message_id: maxId }
1196
+ });
1197
+ }
1198
+
680
1199
  // src/lib/typing.ts
681
1200
  var DEFAULT_TTL_MS = 1e4;
682
1201
  var SWEEP_INTERVAL_MS = 5e3;
@@ -826,7 +1345,7 @@ function globMatch(pattern, value) {
826
1345
  return regex.test(value);
827
1346
  }
828
1347
  var GLOB_MATCH_KEYS = /* @__PURE__ */ new Set(["channel", "sender"]);
829
- var NON_MATCH_KEYS = /* @__PURE__ */ new Set(["session", "destination", "path", "mode"]);
1348
+ var NON_MATCH_KEYS = /* @__PURE__ */ new Set(["session", "destination", "path", "mode", "batch"]);
830
1349
  function ruleMatches(rule, meta) {
831
1350
  for (const [key, pattern] of Object.entries(rule)) {
832
1351
  if (NON_MATCH_KEYS.has(key)) continue;
@@ -871,7 +1390,8 @@ function resolveRoute(config, meta) {
871
1390
  destination: "mind",
872
1391
  session: sanitizeSessionName(expandTemplate(rule.session ?? fallback, meta)),
873
1392
  matched: true,
874
- mode: rule.mode
1393
+ mode: rule.mode,
1394
+ rule
875
1395
  };
876
1396
  }
877
1397
  }
@@ -883,12 +1403,27 @@ function normalizeBatchConfig(batch) {
883
1403
  if (typeof batch === "number") return { maxWait: batch * 60 };
884
1404
  return batch;
885
1405
  }
886
- function resolveDeliveryMode(config, sessionName) {
1406
+ function resolveDeliveryMode(config, sessionName, rule) {
1407
+ const ruleBatch = rule?.batch;
887
1408
  const defaults = {
888
1409
  delivery: { mode: "immediate" },
889
1410
  interrupt: true
890
1411
  };
891
- if (!config.sessions) return defaults;
1412
+ if (!config.sessions) {
1413
+ if (ruleBatch != null) {
1414
+ const batch = normalizeBatchConfig(ruleBatch);
1415
+ return {
1416
+ delivery: {
1417
+ mode: "batch",
1418
+ debounce: batch.debounce ?? DEFAULT_BATCH_DEBOUNCE,
1419
+ maxWait: batch.maxWait ?? DEFAULT_BATCH_MAX_WAIT,
1420
+ triggers: batch.triggers
1421
+ },
1422
+ interrupt: true
1423
+ };
1424
+ }
1425
+ return defaults;
1426
+ }
892
1427
  for (const [pattern, sessionConfig] of Object.entries(config.sessions)) {
893
1428
  if (globMatch(pattern, sessionName)) {
894
1429
  let delivery;
@@ -932,6 +1467,18 @@ function resolveDeliveryMode(config, sessionName) {
932
1467
  };
933
1468
  }
934
1469
  }
1470
+ if (ruleBatch != null) {
1471
+ const batch = normalizeBatchConfig(ruleBatch);
1472
+ return {
1473
+ delivery: {
1474
+ mode: "batch",
1475
+ debounce: batch.debounce ?? DEFAULT_BATCH_DEBOUNCE,
1476
+ maxWait: batch.maxWait ?? DEFAULT_BATCH_MAX_WAIT,
1477
+ triggers: batch.triggers
1478
+ },
1479
+ interrupt: true
1480
+ };
1481
+ }
935
1482
  return defaults;
936
1483
  }
937
1484
 
@@ -981,7 +1528,7 @@ var DeliveryManager = class {
981
1528
  if (sessionName === "$new") {
982
1529
  sessionName = `new-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
983
1530
  }
984
- const sessionConfig = resolveDeliveryMode(config, sessionName);
1531
+ const sessionConfig = resolveDeliveryMode(config, sessionName, route.rule);
985
1532
  if (sessionConfig.delivery.mode === "batch") {
986
1533
  dlog2.debug(`enqueueing batch message for ${mindName}/${sessionName}`);
987
1534
  this.enqueueBatch(mindName, sessionName, payload, sessionConfig);
@@ -1013,7 +1560,7 @@ var DeliveryManager = class {
1013
1560
  async restoreFromDb() {
1014
1561
  try {
1015
1562
  const db = await getDb();
1016
- const rows = await db.select().from(deliveryQueue).where(eq(deliveryQueue.status, "pending"));
1563
+ const rows = await db.select().from(deliveryQueue).where(eq3(deliveryQueue.status, "pending"));
1017
1564
  for (const row of rows) {
1018
1565
  let payload;
1019
1566
  try {
@@ -1031,7 +1578,7 @@ var DeliveryManager = class {
1031
1578
  this.addToBatchBuffer(row.mind, row.session, payload, sessionConfig);
1032
1579
  } else {
1033
1580
  try {
1034
- await db.delete(deliveryQueue).where(eq(deliveryQueue.id, row.id));
1581
+ await db.delete(deliveryQueue).where(eq3(deliveryQueue.id, row.id));
1035
1582
  } catch (err) {
1036
1583
  dlog2.warn(`failed to delete queue row ${row.id} for ${row.mind}`, logger_default.errorData(err));
1037
1584
  }
@@ -1052,7 +1599,7 @@ var DeliveryManager = class {
1052
1599
  */
1053
1600
  async getPending(mindName) {
1054
1601
  const db = await getDb();
1055
- const rows = await db.select().from(deliveryQueue).where(and(eq(deliveryQueue.mind, mindName), eq(deliveryQueue.status, "gated")));
1602
+ const rows = await db.select().from(deliveryQueue).where(and3(eq3(deliveryQueue.mind, mindName), eq3(deliveryQueue.status, "gated")));
1056
1603
  const byChannel = /* @__PURE__ */ new Map();
1057
1604
  for (const row of rows) {
1058
1605
  const ch = row.channel ?? "unknown";
@@ -1145,8 +1692,9 @@ var DeliveryManager = class {
1145
1692
  if (payload.conversationId) {
1146
1693
  typingMap.set(`volute:${payload.conversationId}`, baseName, { persistent: true });
1147
1694
  }
1695
+ const enrichedPayload = await this.enrichWithProfiles(baseName, session, payload);
1148
1696
  const body = JSON.stringify({
1149
- ...payload,
1697
+ ...enrichedPayload,
1150
1698
  session,
1151
1699
  interrupt: sessionConfig.interrupt,
1152
1700
  instructions: sessionConfig.instructions
@@ -1163,22 +1711,30 @@ var DeliveryManager = class {
1163
1711
  publishTypingForChannels(typingMap.deleteSender(baseName), typingMap);
1164
1712
  }
1165
1713
  }
1166
- async deliverBatchToMind(mindName, session, messages, sessionConfig, interruptOverride) {
1714
+ async deliverBatchToMind(mindName, session, messages2, sessionConfig, interruptOverride) {
1167
1715
  const resolved = this.resolvePort(mindName);
1168
1716
  if (!resolved) {
1169
1717
  dlog2.warn(`cannot deliver batch to ${mindName}: mind not found`);
1170
1718
  return;
1171
1719
  }
1172
1720
  const { baseName, port } = resolved;
1721
+ const enrichedMessages = await Promise.all(
1722
+ messages2.map(async (msg, i) => {
1723
+ const isFirst = messages2.findIndex((m) => m.channel === msg.channel) === i;
1724
+ if (!isFirst) return msg;
1725
+ const enrichedPayload = await this.enrichWithProfiles(baseName, session, msg.payload);
1726
+ return { ...msg, payload: enrichedPayload };
1727
+ })
1728
+ );
1173
1729
  const channels = {};
1174
- for (const msg of messages) {
1730
+ for (const msg of enrichedMessages) {
1175
1731
  const ch = msg.channel ?? "unknown";
1176
1732
  if (!channels[ch]) channels[ch] = [];
1177
1733
  channels[ch].push(msg.payload);
1178
1734
  }
1179
1735
  const senders = /* @__PURE__ */ new Set();
1180
1736
  const channelSet = /* @__PURE__ */ new Set();
1181
- for (const msg of messages) {
1737
+ for (const msg of messages2) {
1182
1738
  if (msg.sender) senders.add(msg.sender);
1183
1739
  if (msg.channel) channelSet.add(msg.channel);
1184
1740
  }
@@ -1188,7 +1744,7 @@ var DeliveryManager = class {
1188
1744
  if (ch !== "unknown") typingMap.set(ch, baseName, { persistent: true });
1189
1745
  }
1190
1746
  const seenConvIds = /* @__PURE__ */ new Set();
1191
- for (const msg of messages) {
1747
+ for (const msg of messages2) {
1192
1748
  if (msg.payload.conversationId && !seenConvIds.has(msg.payload.conversationId)) {
1193
1749
  seenConvIds.add(msg.payload.conversationId);
1194
1750
  typingMap.set(`volute:${msg.payload.conversationId}`, baseName, { persistent: true });
@@ -1209,10 +1765,10 @@ var DeliveryManager = class {
1209
1765
  try {
1210
1766
  const db = await getDb();
1211
1767
  await db.delete(deliveryQueue).where(
1212
- and(
1213
- eq(deliveryQueue.mind, baseName),
1214
- eq(deliveryQueue.session, session),
1215
- eq(deliveryQueue.status, "pending")
1768
+ and3(
1769
+ eq3(deliveryQueue.mind, baseName),
1770
+ eq3(deliveryQueue.session, session),
1771
+ eq3(deliveryQueue.status, "pending")
1216
1772
  )
1217
1773
  );
1218
1774
  } catch (err) {
@@ -1310,24 +1866,24 @@ var DeliveryManager = class {
1310
1866
  flushBatch(mindName, session, extra, interruptOverride) {
1311
1867
  const bufferKey = `${mindName}:${session}`;
1312
1868
  const buffer = this.batchBuffers.get(bufferKey);
1313
- const messages = [];
1869
+ const messages2 = [];
1314
1870
  if (buffer) {
1315
1871
  if (buffer.debounceTimer) clearTimeout(buffer.debounceTimer);
1316
1872
  if (buffer.maxWaitTimer) clearTimeout(buffer.maxWaitTimer);
1317
1873
  buffer.debounceTimer = null;
1318
1874
  buffer.maxWaitTimer = null;
1319
- messages.push(...buffer.messages.splice(0));
1875
+ messages2.push(...buffer.messages.splice(0));
1320
1876
  this.batchBuffers.delete(bufferKey);
1321
1877
  }
1322
- if (extra) messages.push(...extra);
1323
- if (messages.length === 0) return;
1878
+ if (extra) messages2.push(...extra);
1879
+ if (messages2.length === 0) return;
1324
1880
  const [baseName] = mindName.split("@", 2);
1325
1881
  const config = getRoutingConfig(baseName);
1326
1882
  const sessionConfig = resolveDeliveryMode(config, session);
1327
1883
  dlog2.info(
1328
- `flushing batch for ${mindName}/${session}: ${messages.length} messages${interruptOverride ? " (new-speaker interrupt)" : ""}`
1884
+ `flushing batch for ${mindName}/${session}: ${messages2.length} messages${interruptOverride ? " (new-speaker interrupt)" : ""}`
1329
1885
  );
1330
- this.deliverBatchToMind(mindName, session, messages, sessionConfig, interruptOverride).catch(
1886
+ this.deliverBatchToMind(mindName, session, messages2, sessionConfig, interruptOverride).catch(
1331
1887
  (err) => {
1332
1888
  dlog2.warn(`failed to flush batch for ${mindName}/${session}`, logger_default.errorData(err));
1333
1889
  }
@@ -1338,14 +1894,14 @@ var DeliveryManager = class {
1338
1894
  await this.persistToQueue(baseName, session, payload, "gated");
1339
1895
  try {
1340
1896
  const db = await getDb();
1341
- const count = await db.select({ count: sql`count(*)` }).from(deliveryQueue).where(
1342
- and(
1343
- eq(deliveryQueue.mind, baseName),
1344
- eq(deliveryQueue.channel, payload.channel),
1345
- eq(deliveryQueue.status, "gated")
1897
+ const count2 = await db.select({ count: sql2`count(*)` }).from(deliveryQueue).where(
1898
+ and3(
1899
+ eq3(deliveryQueue.mind, baseName),
1900
+ eq3(deliveryQueue.channel, payload.channel),
1901
+ eq3(deliveryQueue.status, "gated")
1346
1902
  )
1347
1903
  );
1348
- if ((count[0]?.count ?? 0) <= 1) {
1904
+ if ((count2[0]?.count ?? 0) <= 1) {
1349
1905
  await this.sendInviteNotification(mindName, payload);
1350
1906
  }
1351
1907
  } catch (err) {
@@ -1397,6 +1953,72 @@ var DeliveryManager = class {
1397
1953
  );
1398
1954
  }
1399
1955
  }
1956
+ async enrichWithProfiles(mindName, session, payload) {
1957
+ if (!payload.conversationId || !payload.channel) return payload;
1958
+ const mindSessions = this.sessionStates.get(mindName);
1959
+ const state = mindSessions?.get(session);
1960
+ if (!state) return payload;
1961
+ const channelKey = payload.channel;
1962
+ if (state.seenChannelProfiles.has(channelKey)) return payload;
1963
+ try {
1964
+ const participants = await getParticipants(payload.conversationId);
1965
+ const profiles = participants.map((p) => ({
1966
+ username: p.username,
1967
+ userType: p.userType,
1968
+ displayName: p.displayName,
1969
+ description: p.description
1970
+ }));
1971
+ const avatarBlocks = await this.loadAvatarBlocks(participants);
1972
+ state.seenChannelProfiles.add(channelKey);
1973
+ const enriched = { ...payload, participantProfiles: profiles };
1974
+ if (avatarBlocks.length > 0) {
1975
+ const existing = Array.isArray(payload.content) ? payload.content : typeof payload.content === "string" ? [{ type: "text", text: payload.content }] : [];
1976
+ enriched.content = [...avatarBlocks, ...existing];
1977
+ }
1978
+ return enriched;
1979
+ } catch (err) {
1980
+ dlog2.warn(`failed to fetch participant profiles for ${mindName}`, logger_default.errorData(err));
1981
+ return payload;
1982
+ }
1983
+ }
1984
+ async loadAvatarBlocks(participants) {
1985
+ const blocks = [];
1986
+ for (const p of participants) {
1987
+ if (!p.avatar) continue;
1988
+ try {
1989
+ let filePath;
1990
+ if (p.userType === "mind") {
1991
+ const dir = mindDir(p.username);
1992
+ const config = readVoluteConfig(dir);
1993
+ if (!config?.avatar) continue;
1994
+ filePath = resolve5(dir, "home", config.avatar);
1995
+ } else {
1996
+ filePath = resolve5(voluteHome(), "avatars", p.avatar);
1997
+ }
1998
+ const ext = extname(filePath).toLowerCase();
1999
+ const mimeMap = {
2000
+ ".png": "image/png",
2001
+ ".jpg": "image/jpeg",
2002
+ ".jpeg": "image/jpeg",
2003
+ ".gif": "image/gif",
2004
+ ".webp": "image/webp"
2005
+ };
2006
+ const mediaType = mimeMap[ext];
2007
+ if (!mediaType) continue;
2008
+ const data = await readFile(filePath);
2009
+ blocks.push(
2010
+ { type: "text", text: `[Avatar for ${p.username}]` },
2011
+ { type: "image", media_type: mediaType, data: data.toString("base64") }
2012
+ );
2013
+ } catch (err) {
2014
+ const code = err.code;
2015
+ if (code !== "ENOENT") {
2016
+ dlog2.warn(`failed to load avatar for ${p.username}`, logger_default.errorData(err));
2017
+ }
2018
+ }
2019
+ }
2020
+ return blocks;
2021
+ }
1400
2022
  incrementActive(mind, session, senders, channels) {
1401
2023
  let mindSessions = this.sessionStates.get(mind);
1402
2024
  if (!mindSessions) {
@@ -1408,7 +2030,8 @@ var DeliveryManager = class {
1408
2030
  lastDeliveredAt: 0,
1409
2031
  lastDeliverySenders: /* @__PURE__ */ new Set(),
1410
2032
  lastDeliveryChannels: /* @__PURE__ */ new Set(),
1411
- lastInterruptAt: 0
2033
+ lastInterruptAt: 0,
2034
+ seenChannelProfiles: /* @__PURE__ */ new Set()
1412
2035
  };
1413
2036
  state.activeCount++;
1414
2037
  state.lastDeliveredAt = Date.now();
@@ -1731,16 +2354,16 @@ async function ensureMailAddress(mindName) {
1731
2354
  }
1732
2355
 
1733
2356
  // src/lib/daemon/scheduler.ts
1734
- import { resolve as resolve5 } from "path";
2357
+ import { resolve as resolve6 } from "path";
1735
2358
  import { CronExpressionParser } from "cron-parser";
1736
- var slog = logger_default.child("scheduler");
2359
+ var slog2 = logger_default.child("scheduler");
1737
2360
  var Scheduler = class {
1738
2361
  schedules = /* @__PURE__ */ new Map();
1739
2362
  interval = null;
1740
2363
  lastFired = /* @__PURE__ */ new Map();
1741
2364
  // "mind:scheduleId" → epoch minute
1742
2365
  get statePath() {
1743
- return resolve5(voluteHome(), "scheduler-state.json");
2366
+ return resolve6(voluteHome(), "scheduler-state.json");
1744
2367
  }
1745
2368
  start() {
1746
2369
  this.loadState();
@@ -1799,7 +2422,7 @@ var Scheduler = class {
1799
2422
  prevMinute = Math.floor(prev.getTime() / 6e4);
1800
2423
  cronCache.set(schedule.cron, prevMinute);
1801
2424
  } catch (err) {
1802
- slog.warn(`invalid cron "${schedule.cron}" for ${mind}:${schedule.id}`, logger_default.errorData(err));
2425
+ slog2.warn(`invalid cron "${schedule.cron}" for ${mind}:${schedule.id}`, logger_default.errorData(err));
1803
2426
  return false;
1804
2427
  }
1805
2428
  }
@@ -1813,11 +2436,11 @@ var Scheduler = class {
1813
2436
  try {
1814
2437
  let text;
1815
2438
  if (schedule.script) {
1816
- const homeDir = resolve5(mindDir(mindName), "home");
2439
+ const homeDir = resolve6(mindDir(mindName), "home");
1817
2440
  try {
1818
2441
  const output = await this.runScript(schedule.script, homeDir, mindName);
1819
2442
  if (!output.trim()) {
1820
- slog.info(`fired script "${schedule.id}" for ${mindName} (no output)`);
2443
+ slog2.info(`fired script "${schedule.id}" for ${mindName} (no output)`);
1821
2444
  return;
1822
2445
  }
1823
2446
  text = output;
@@ -1825,12 +2448,12 @@ var Scheduler = class {
1825
2448
  const stderr = err.stderr ?? "";
1826
2449
  text = `[script error] ${err.message}${stderr ? `
1827
2450
  ${stderr}` : ""}`;
1828
- slog.warn(`script "${schedule.id}" failed for ${mindName}`, logger_default.errorData(err));
2451
+ slog2.warn(`script "${schedule.id}" failed for ${mindName}`, logger_default.errorData(err));
1829
2452
  }
1830
2453
  } else if (schedule.message) {
1831
2454
  text = schedule.message;
1832
2455
  } else {
1833
- slog.warn(`schedule "${schedule.id}" for ${mindName} has no message or script`);
2456
+ slog2.warn(`schedule "${schedule.id}" for ${mindName} has no message or script`);
1834
2457
  return;
1835
2458
  }
1836
2459
  await this.deliver(mindName, {
@@ -1838,9 +2461,9 @@ ${stderr}` : ""}`;
1838
2461
  channel: "system:scheduler",
1839
2462
  sender: schedule.id
1840
2463
  });
1841
- slog.info(`fired "${schedule.id}" for ${mindName}`);
2464
+ slog2.info(`fired "${schedule.id}" for ${mindName}`);
1842
2465
  } catch (err) {
1843
- slog.warn(`failed to fire "${schedule.id}" for ${mindName}`, logger_default.errorData(err));
2466
+ slog2.warn(`failed to fire "${schedule.id}" for ${mindName}`, logger_default.errorData(err));
1844
2467
  }
1845
2468
  }
1846
2469
  runScript(script, cwd, mindName) {
@@ -1863,7 +2486,7 @@ function getScheduler() {
1863
2486
 
1864
2487
  // src/lib/daemon/token-budget.ts
1865
2488
  import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
1866
- import { resolve as resolve6 } from "path";
2489
+ import { resolve as resolve7 } from "path";
1867
2490
  var tlog = logger_default.child("token-budget");
1868
2491
  var DEFAULT_BUDGET_PERIOD_MINUTES = 60;
1869
2492
  var MAX_QUEUE_SIZE = 100;
@@ -1937,9 +2560,9 @@ var TokenBudget = class {
1937
2560
  drain(mind) {
1938
2561
  const state = this.budgets.get(mind);
1939
2562
  if (!state) return [];
1940
- const messages = state.queue;
2563
+ const messages2 = state.queue;
1941
2564
  state.queue = [];
1942
- return messages;
2565
+ return messages2;
1943
2566
  }
1944
2567
  getUsage(mind) {
1945
2568
  const state = this.budgets.get(mind);
@@ -1981,7 +2604,7 @@ var TokenBudget = class {
1981
2604
  this.dirty.clear();
1982
2605
  }
1983
2606
  budgetStatePath(mind) {
1984
- return resolve6(stateDir(mind), "budget.json");
2607
+ return resolve7(stateDir(mind), "budget.json");
1985
2608
  }
1986
2609
  saveBudgetState(mind, state) {
1987
2610
  try {
@@ -2020,8 +2643,8 @@ var TokenBudget = class {
2020
2643
  return null;
2021
2644
  }
2022
2645
  }
2023
- async replay(mindName, messages) {
2024
- const summary = messages.map((m) => {
2646
+ async replay(mindName, messages2) {
2647
+ const summary = messages2.map((m) => {
2025
2648
  const from = m.sender ? `[${m.sender}]` : "";
2026
2649
  const ch = m.channel ? `(${m.channel})` : "";
2027
2650
  return `${from}${ch} ${m.textContent}`;
@@ -2031,7 +2654,7 @@ var TokenBudget = class {
2031
2654
  content: [
2032
2655
  {
2033
2656
  type: "text",
2034
- text: `[Budget replay] ${messages.length} queued message(s) from the previous budget period:
2657
+ text: `[Budget replay] ${messages2.length} queued message(s) from the previous budget period:
2035
2658
 
2036
2659
  ${summary}`
2037
2660
  }
@@ -2039,11 +2662,11 @@ ${summary}`
2039
2662
  channel: "system:budget-replay",
2040
2663
  sender: "system"
2041
2664
  });
2042
- tlog.info(`replayed ${messages.length} queued message(s) for ${mindName}`);
2665
+ tlog.info(`replayed ${messages2.length} queued message(s) for ${mindName}`);
2043
2666
  } catch (err) {
2044
2667
  tlog.warn(`failed to replay for ${mindName}`, logger_default.errorData(err));
2045
2668
  const state = this.budgets.get(mindName);
2046
- if (state) state.queue.push(...messages);
2669
+ if (state) state.queue.push(...messages2);
2047
2670
  }
2048
2671
  }
2049
2672
  };
@@ -2078,6 +2701,15 @@ async function startMindFull(name) {
2078
2701
  (err) => logger_default.error(`failed to ensure mail address for ${baseName}`, logger_default.errorData(err))
2079
2702
  );
2080
2703
  const config = readVoluteConfig(dir);
2704
+ if (config) {
2705
+ syncMindProfile(baseName, {
2706
+ displayName: config.displayName,
2707
+ description: config.description,
2708
+ avatar: config.avatar
2709
+ }).catch(
2710
+ (err) => logger_default.error(`failed to sync profile for ${baseName}`, logger_default.errorData(err))
2711
+ );
2712
+ }
2081
2713
  if (config?.tokenBudget) {
2082
2714
  getTokenBudget().setBudget(
2083
2715
  baseName,
@@ -2122,7 +2754,7 @@ async function stopMindFull(name) {
2122
2754
  }
2123
2755
 
2124
2756
  // src/lib/daemon/sleep-manager.ts
2125
- var slog2 = logger_default.child("sleep");
2757
+ var slog3 = logger_default.child("sleep");
2126
2758
  function defaultState() {
2127
2759
  return {
2128
2760
  sleeping: false,
@@ -2158,7 +2790,7 @@ var SleepManager = class {
2158
2790
  unsubActivity = null;
2159
2791
  transitioning = /* @__PURE__ */ new Set();
2160
2792
  get statePath() {
2161
- return resolve7(voluteHome(), "sleep-state.json");
2793
+ return resolve8(voluteHome(), "sleep-state.json");
2162
2794
  }
2163
2795
  start() {
2164
2796
  this.loadState();
@@ -2181,7 +2813,7 @@ var SleepManager = class {
2181
2813
  }
2182
2814
  }
2183
2815
  } catch (err) {
2184
- slog2.warn("failed to load sleep state", logger_default.errorData(err));
2816
+ slog3.warn("failed to load sleep state", logger_default.errorData(err));
2185
2817
  }
2186
2818
  }
2187
2819
  saveState() {
@@ -2193,7 +2825,7 @@ var SleepManager = class {
2193
2825
  writeFileSync3(this.statePath, `${JSON.stringify(data, null, 2)}
2194
2826
  `);
2195
2827
  } catch (err) {
2196
- slog2.error("failed to save sleep state", logger_default.errorData(err));
2828
+ slog3.error("failed to save sleep state", logger_default.errorData(err));
2197
2829
  }
2198
2830
  }
2199
2831
  // --- Public API ---
@@ -2240,7 +2872,7 @@ var SleepManager = class {
2240
2872
  content: preSleepMsg
2241
2873
  });
2242
2874
  } catch (err) {
2243
- slog2.error(`failed to persist pre-sleep message for ${name}`, logger_default.errorData(err));
2875
+ slog3.error(`failed to persist pre-sleep message for ${name}`, logger_default.errorData(err));
2244
2876
  }
2245
2877
  try {
2246
2878
  await fetch(`http://127.0.0.1:${entry.port}/message`, {
@@ -2252,7 +2884,7 @@ var SleepManager = class {
2252
2884
  })
2253
2885
  });
2254
2886
  } catch (err) {
2255
- slog2.warn(`failed to send pre-sleep message to ${name}`, logger_default.errorData(err));
2887
+ slog3.warn(`failed to send pre-sleep message to ${name}`, logger_default.errorData(err));
2256
2888
  }
2257
2889
  await this.waitForIdle(name, 12e4);
2258
2890
  await new Promise((r) => setTimeout(r, 3e3));
@@ -2260,7 +2892,7 @@ var SleepManager = class {
2260
2892
  await this.killOrphanOnPort(entry.port);
2261
2893
  await this.archiveSessions(name);
2262
2894
  this.markSleeping(name, opts);
2263
- slog2.info(`${name} is now sleeping`);
2895
+ slog3.info(`${name} is now sleeping`);
2264
2896
  } finally {
2265
2897
  this.transitioning.delete(name);
2266
2898
  }
@@ -2286,7 +2918,7 @@ var SleepManager = class {
2286
2918
  try {
2287
2919
  await wakeMind(name);
2288
2920
  } catch (err) {
2289
- slog2.error(`failed to wake ${name}`, logger_default.errorData(err));
2921
+ slog3.error(`failed to wake ${name}`, logger_default.errorData(err));
2290
2922
  return;
2291
2923
  }
2292
2924
  const entry = findMind(name);
@@ -2318,7 +2950,7 @@ var SleepManager = class {
2318
2950
  content: summaryText
2319
2951
  });
2320
2952
  } catch (err) {
2321
- slog2.error(`failed to persist wake summary for ${name}`, logger_default.errorData(err));
2953
+ slog3.error(`failed to persist wake summary for ${name}`, logger_default.errorData(err));
2322
2954
  }
2323
2955
  try {
2324
2956
  await fetch(`http://127.0.0.1:${entry.port}/message`, {
@@ -2330,16 +2962,16 @@ var SleepManager = class {
2330
2962
  })
2331
2963
  });
2332
2964
  } catch (err) {
2333
- slog2.warn(`failed to deliver wake summary to ${name}`, logger_default.errorData(err));
2965
+ slog3.warn(`failed to deliver wake summary to ${name}`, logger_default.errorData(err));
2334
2966
  }
2335
2967
  const flushed = await this.flushQueuedMessages(name);
2336
2968
  if (flushed > 0) {
2337
- slog2.info(`flushed ${flushed} queued message(s) for ${name}`);
2969
+ slog3.info(`flushed ${flushed} queued message(s) for ${name}`);
2338
2970
  }
2339
2971
  if (!opts?.trigger) {
2340
2972
  this.markAwake(name);
2341
2973
  }
2342
- slog2.info(`${name} is now awake${opts?.trigger ? " (trigger wake)" : ""}`);
2974
+ slog3.info(`${name} is now awake${opts?.trigger ? " (trigger wake)" : ""}`);
2343
2975
  } finally {
2344
2976
  this.transitioning.delete(name);
2345
2977
  }
@@ -2394,20 +3026,20 @@ var SleepManager = class {
2394
3026
  async flushQueuedMessages(name) {
2395
3027
  try {
2396
3028
  const db = await getDb();
2397
- const rows = await db.select().from(deliveryQueue).where(and2(eq2(deliveryQueue.mind, name), eq2(deliveryQueue.status, "sleep-queued"))).all();
3029
+ const rows = await db.select().from(deliveryQueue).where(and4(eq4(deliveryQueue.mind, name), eq4(deliveryQueue.status, "sleep-queued"))).all();
2398
3030
  if (rows.length === 0) return 0;
2399
- const { deliverMessage: deliverMessage2 } = await import("./message-delivery-FHV4NO2F.js");
3031
+ const { deliverMessage: deliverMessage2 } = await import("./message-delivery-S7BCNV6Y.js");
2400
3032
  const delivered = [];
2401
3033
  for (const row of rows) {
2402
3034
  try {
2403
3035
  await deliverMessage2(name, JSON.parse(row.payload));
2404
3036
  delivered.push(row.id);
2405
3037
  } catch (err) {
2406
- slog2.warn(`failed to flush queued message ${row.id} for ${name}`, logger_default.errorData(err));
3038
+ slog3.warn(`failed to flush queued message ${row.id} for ${name}`, logger_default.errorData(err));
2407
3039
  }
2408
3040
  }
2409
3041
  if (delivered.length > 0) {
2410
- await db.delete(deliveryQueue).where(inArray(deliveryQueue.id, delivered));
3042
+ await db.delete(deliveryQueue).where(inArray2(deliveryQueue.id, delivered));
2411
3043
  }
2412
3044
  const state = this.states.get(name);
2413
3045
  if (state) {
@@ -2415,7 +3047,7 @@ var SleepManager = class {
2415
3047
  }
2416
3048
  return delivered.length;
2417
3049
  } catch (err) {
2418
- slog2.warn(`failed to flush queued messages for ${name}`, logger_default.errorData(err));
3050
+ slog3.warn(`failed to flush queued messages for ${name}`, logger_default.errorData(err));
2419
3051
  return 0;
2420
3052
  }
2421
3053
  }
@@ -2443,7 +3075,7 @@ var SleepManager = class {
2443
3075
  const interval = CronExpressionParser2.parse(config.schedule.wake);
2444
3076
  return interval.next().toDate().toISOString();
2445
3077
  } catch (err) {
2446
- slog2.warn(`invalid wake cron "${config.schedule.wake}"`, logger_default.errorData(err));
3078
+ slog3.warn(`invalid wake cron "${config.schedule.wake}"`, logger_default.errorData(err));
2447
3079
  return null;
2448
3080
  }
2449
3081
  }
@@ -2460,7 +3092,7 @@ var SleepManager = class {
2460
3092
  const wakeAt = new Date(state.voluntaryWakeAt);
2461
3093
  if (now >= wakeAt) {
2462
3094
  this.initiateWake(entry.name).catch(
2463
- (err) => slog2.error(`failed voluntary wake for ${entry.name}`, logger_default.errorData(err))
3095
+ (err) => slog3.error(`failed voluntary wake for ${entry.name}`, logger_default.errorData(err))
2464
3096
  );
2465
3097
  continue;
2466
3098
  }
@@ -2469,7 +3101,7 @@ var SleepManager = class {
2469
3101
  const wakeAt = new Date(state.scheduledWakeAt);
2470
3102
  if (now >= wakeAt) {
2471
3103
  this.initiateWake(entry.name).catch(
2472
- (err) => slog2.error(`failed scheduled wake for ${entry.name}`, logger_default.errorData(err))
3104
+ (err) => slog3.error(`failed scheduled wake for ${entry.name}`, logger_default.errorData(err))
2473
3105
  );
2474
3106
  continue;
2475
3107
  }
@@ -2477,7 +3109,7 @@ var SleepManager = class {
2477
3109
  if (!state?.sleeping && entry.running) {
2478
3110
  if (this.shouldSleep(config.schedule.sleep, epochMinute)) {
2479
3111
  this.initiateSleep(entry.name).catch(
2480
- (err) => slog2.error(`failed to initiate sleep for ${entry.name}`, logger_default.errorData(err))
3112
+ (err) => slog3.error(`failed to initiate sleep for ${entry.name}`, logger_default.errorData(err))
2481
3113
  );
2482
3114
  }
2483
3115
  }
@@ -2490,22 +3122,22 @@ var SleepManager = class {
2490
3122
  const prevMinute = Math.floor(prev.getTime() / 6e4);
2491
3123
  return prevMinute === epochMinute;
2492
3124
  } catch (err) {
2493
- slog2.warn(`invalid sleep cron "${cronExpr}"`, logger_default.errorData(err));
3125
+ slog3.warn(`invalid sleep cron "${cronExpr}"`, logger_default.errorData(err));
2494
3126
  return false;
2495
3127
  }
2496
3128
  }
2497
3129
  async waitForIdle(name, timeoutMs) {
2498
- return new Promise((resolve8) => {
3130
+ return new Promise((resolve9) => {
2499
3131
  const timeout = setTimeout(() => {
2500
3132
  unsub();
2501
- resolve8();
3133
+ resolve9();
2502
3134
  }, timeoutMs);
2503
3135
  const unsub = subscribe((event) => {
2504
3136
  if (event.mind !== name) return;
2505
3137
  if (event.type === "mind_done" || event.type === "mind_idle") {
2506
3138
  clearTimeout(timeout);
2507
3139
  unsub();
2508
- resolve8();
3140
+ resolve9();
2509
3141
  }
2510
3142
  });
2511
3143
  });
@@ -2513,34 +3145,34 @@ var SleepManager = class {
2513
3145
  async archiveSessions(name) {
2514
3146
  const dir = mindDir(name);
2515
3147
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 16);
2516
- const sessionsDir = resolve7(dir, ".mind", "sessions");
3148
+ const sessionsDir = resolve8(dir, ".mind", "sessions");
2517
3149
  if (existsSync5(sessionsDir)) {
2518
- const archiveDir = resolve7(sessionsDir, "archive");
3150
+ const archiveDir = resolve8(sessionsDir, "archive");
2519
3151
  mkdirSync3(archiveDir, { recursive: true });
2520
3152
  for (const file of readdirSync2(sessionsDir)) {
2521
3153
  if (file === "archive" || !file.endsWith(".json")) continue;
2522
- const src = resolve7(sessionsDir, file);
3154
+ const src = resolve8(sessionsDir, file);
2523
3155
  const base = file.replace(/\.json$/, "");
2524
- const dest = resolve7(archiveDir, `${base}-${timestamp}.json`);
3156
+ const dest = resolve8(archiveDir, `${base}-${timestamp}.json`);
2525
3157
  try {
2526
3158
  renameSync(src, dest);
2527
3159
  } catch (err) {
2528
- slog2.warn(`failed to archive session ${file} for ${name}`, logger_default.errorData(err));
3160
+ slog3.warn(`failed to archive session ${file} for ${name}`, logger_default.errorData(err));
2529
3161
  }
2530
3162
  }
2531
3163
  }
2532
- const piSessionsDir = resolve7(dir, ".mind", "pi-sessions");
3164
+ const piSessionsDir = resolve8(dir, ".mind", "pi-sessions");
2533
3165
  if (existsSync5(piSessionsDir)) {
2534
- const archiveDir = resolve7(piSessionsDir, "archive");
3166
+ const archiveDir = resolve8(piSessionsDir, "archive");
2535
3167
  mkdirSync3(archiveDir, { recursive: true });
2536
3168
  for (const entry of readdirSync2(piSessionsDir, { withFileTypes: true })) {
2537
3169
  if (entry.name === "archive" || !entry.isDirectory()) continue;
2538
- const src = resolve7(piSessionsDir, entry.name);
2539
- const dest = resolve7(archiveDir, `${entry.name}-${timestamp}`);
3170
+ const src = resolve8(piSessionsDir, entry.name);
3171
+ const dest = resolve8(archiveDir, `${entry.name}-${timestamp}`);
2540
3172
  try {
2541
3173
  renameSync(src, dest);
2542
3174
  } catch (err) {
2543
- slog2.warn(`failed to archive pi-session ${entry.name} for ${name}`, logger_default.errorData(err));
3175
+ slog3.warn(`failed to archive pi-session ${entry.name} for ${name}`, logger_default.errorData(err));
2544
3176
  }
2545
3177
  }
2546
3178
  }
@@ -2548,18 +3180,18 @@ var SleepManager = class {
2548
3180
  async buildQueuedSummary(name) {
2549
3181
  try {
2550
3182
  const db = await getDb();
2551
- const rows = await db.select({ channel: deliveryQueue.channel }).from(deliveryQueue).where(and2(eq2(deliveryQueue.mind, name), eq2(deliveryQueue.status, "sleep-queued"))).all();
2552
- if (rows.length === 0) return "No messages while you slept.";
3183
+ const rows = await db.select({ channel: deliveryQueue.channel }).from(deliveryQueue).where(and4(eq4(deliveryQueue.mind, name), eq4(deliveryQueue.status, "sleep-queued"))).all();
3184
+ if (rows.length === 0) return "No messages arrived while you slept.";
2553
3185
  const channelCounts = /* @__PURE__ */ new Map();
2554
3186
  for (const row of rows) {
2555
3187
  const ch = row.channel ?? "unknown";
2556
3188
  channelCounts.set(ch, (channelCounts.get(ch) ?? 0) + 1);
2557
3189
  }
2558
- const parts = [...channelCounts.entries()].map(([ch, count]) => `${count} on ${ch}`);
2559
- return `${rows.length} message${rows.length === 1 ? "" : "s"} while you slept (${parts.join(", ")}). Ask if you want them delivered.`;
3190
+ const parts = [...channelCounts.entries()].map(([ch, count2]) => `${count2} on ${ch}`);
3191
+ return `${rows.length} message${rows.length === 1 ? "" : "s"} arrived while you slept (${parts.join(", ")}). They'll be delivered to your normal channels now.`;
2560
3192
  } catch (err) {
2561
- slog2.warn(`failed to build queued summary for ${name}`, logger_default.errorData(err));
2562
- return "No messages while you slept.";
3193
+ slog3.error(`failed to build queued summary for ${name}`, logger_default.errorData(err));
3194
+ return "Unable to check for queued messages \u2014 there may be messages waiting.";
2563
3195
  }
2564
3196
  }
2565
3197
  /**
@@ -2573,7 +3205,7 @@ var SleepManager = class {
2573
3205
  } catch {
2574
3206
  return;
2575
3207
  }
2576
- slog2.warn(`orphan process found on port ${port} after sleep, killing`);
3208
+ slog3.warn(`orphan process found on port ${port} after sleep, killing`);
2577
3209
  const execFileAsync = promisify(execFile);
2578
3210
  try {
2579
3211
  const { stdout } = await execFileAsync("lsof", ["-ti", `:${port}`, "-sTCP:LISTEN"]);
@@ -2584,7 +3216,7 @@ var SleepManager = class {
2584
3216
  process.kill(pid, "SIGTERM");
2585
3217
  } catch (err) {
2586
3218
  if (err.code !== "ESRCH") {
2587
- slog2.warn(`failed to kill orphan pid ${pid}`, logger_default.errorData(err));
3219
+ slog3.warn(`failed to kill orphan pid ${pid}`, logger_default.errorData(err));
2588
3220
  }
2589
3221
  }
2590
3222
  }
@@ -2616,7 +3248,7 @@ var SleepManager = class {
2616
3248
  }
2617
3249
  }
2618
3250
  } catch (err) {
2619
- slog2.warn(`failed to kill orphan on port ${port} via /proc`, logger_default.errorData(err));
3251
+ slog3.warn(`failed to kill orphan on port ${port} via /proc`, logger_default.errorData(err));
2620
3252
  }
2621
3253
  }
2622
3254
  await new Promise((r) => setTimeout(r, 1e3));
@@ -2626,7 +3258,7 @@ var SleepManager = class {
2626
3258
  if (!state?.sleeping || !state.wokenByTrigger) return;
2627
3259
  if (this.transitioning.has(event.mind)) return;
2628
3260
  if (event.type === "mind_idle") {
2629
- slog2.info(`${event.mind} going back to sleep after trigger wake`);
3261
+ slog3.info(`${event.mind} going back to sleep after trigger wake`);
2630
3262
  state.wokenByTrigger = false;
2631
3263
  this.transitioning.add(event.mind);
2632
3264
  sleepMind(event.mind).then(() => this.archiveSessions(event.mind)).then(() => {
@@ -2635,9 +3267,9 @@ var SleepManager = class {
2635
3267
  const sleepConfig = this.getSleepConfig(event.mind);
2636
3268
  state.scheduledWakeAt = this.getNextWakeTime(sleepConfig);
2637
3269
  this.saveState();
2638
- slog2.info(`${event.mind} returned to sleep`);
3270
+ slog3.info(`${event.mind} returned to sleep`);
2639
3271
  }).catch((err) => {
2640
- slog2.error(`failed to return ${event.mind} to sleep`, logger_default.errorData(err));
3272
+ slog3.error(`failed to return ${event.mind} to sleep`, logger_default.errorData(err));
2641
3273
  }).finally(() => {
2642
3274
  this.transitioning.delete(event.mind);
2643
3275
  });
@@ -2661,6 +3293,21 @@ function getSleepManagerIfReady() {
2661
3293
  export {
2662
3294
  initConnectorManager,
2663
3295
  getConnectorManager,
3296
+ createUser,
3297
+ verifyUser,
3298
+ getUser,
3299
+ getUserByUsername,
3300
+ listUsers,
3301
+ listPendingUsers,
3302
+ listUsersByType,
3303
+ getOrCreateMindUser,
3304
+ deleteMindUser,
3305
+ changePassword,
3306
+ approveUser,
3307
+ countAdmins,
3308
+ setUserRole,
3309
+ deleteUser,
3310
+ updateUserProfile,
2664
3311
  stopAllWatchers,
2665
3312
  getCachedSites,
2666
3313
  getCachedRecentPages,
@@ -2677,8 +3324,31 @@ export {
2677
3324
  getSleepManagerIfReady,
2678
3325
  subscribe2 as subscribe,
2679
3326
  publish2 as publish,
3327
+ getWebhookUrl,
3328
+ getAuthHeaders,
3329
+ fireWebhook,
3330
+ initWebhook,
2680
3331
  subscribe3 as subscribe2,
2681
3332
  publish3 as publish2,
3333
+ createConversation,
3334
+ getConversation,
3335
+ getParticipants,
3336
+ isParticipant,
3337
+ listConversationsForUser,
3338
+ isParticipantOrOwner,
3339
+ deleteConversationForUser,
3340
+ addMessage,
3341
+ getMessages,
3342
+ getMessagesPaginated,
3343
+ listConversationsWithParticipants,
3344
+ findDMConversation,
3345
+ createChannel,
3346
+ getChannelByName,
3347
+ listChannels,
3348
+ joinChannel,
3349
+ leaveChannel,
3350
+ getUnreadCounts,
3351
+ markConversationRead,
2682
3352
  getTypingMap,
2683
3353
  publishTypingForChannels,
2684
3354
  extractTextContent,