volute 0.35.0 → 0.37.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 (167) hide show
  1. package/dist/{accept-ZBDVVCEU.js → accept-AHAOUFBK.js} +4 -4
  2. package/dist/activity-events-N6HCHU4P.js +15 -0
  3. package/dist/{ai-service-LURBEDDB.js → ai-service-C2YNARGH.js} +6 -6
  4. package/dist/{api-client-3A77HMH7.js → api-client-LC5YRA32.js} +1 -1
  5. package/dist/{archive-ESU2FUN4.js → archive-AWIJTVQV.js} +4 -4
  6. package/dist/{auth-WX4TESEI.js → auth-2QOOPMBX.js} +6 -6
  7. package/dist/{bridge-PXIO6PS2.js → bridge-F3ZJEKDN.js} +4 -4
  8. package/dist/{chat-QXAJF3FU.js → chat-5Y4FD77E.js} +9 -9
  9. package/dist/{chunk-BDYXIWA5.js → chunk-2NHRJ3YO.js} +13 -2
  10. package/dist/{chunk-AN2W47GW.js → chunk-3F7XK5Q7.js} +2 -2
  11. package/dist/{chunk-AOB6GVRM.js → chunk-46DYYHN6.js} +8 -3
  12. package/dist/{chunk-WZRZFFCL.js → chunk-5DPRTREW.js} +146 -186
  13. package/dist/{chunk-QWTR6AWZ.js → chunk-7AZQFSOV.js} +2 -2
  14. package/dist/{chunk-BMZQYACC.js → chunk-A6FLW5XD.js} +19 -4
  15. package/dist/{chunk-N5LMGYXX.js → chunk-BIEWHAAM.js} +2 -2
  16. package/dist/{chunk-5N7Y5WAM.js → chunk-CJ26DXZL.js} +29 -13
  17. package/dist/{chunk-ZSR72JB3.js → chunk-CU6OFXMM.js} +1 -1
  18. package/dist/{chunk-N446KRP7.js → chunk-GVVVMZ4J.js} +2 -2
  19. package/dist/chunk-K3NQKI34.js +10 -0
  20. package/dist/{chunk-F7ZNLYKZ.js → chunk-KAB6UGOL.js} +2 -2
  21. package/dist/{chunk-WJPROOU5.js → chunk-KBRGHKVU.js} +681 -3726
  22. package/dist/{chunk-PWQ2ITYG.js → chunk-KXXJYY62.js} +6 -6
  23. package/dist/{chunk-NJK5SDGR.js → chunk-LGNUFVMR.js} +1 -1
  24. package/dist/chunk-LQ6Z4FXN.js +87 -0
  25. package/dist/{chunk-J6CJQDWI.js → chunk-MQRS4J24.js} +2 -2
  26. package/dist/{chunk-CORXD635.js → chunk-N42QMDID.js} +3 -3
  27. package/dist/{chunk-IJHIXLVN.js → chunk-NYP3LBIV.js} +15 -13
  28. package/dist/{chunk-FT5KETXZ.js → chunk-ORNY3MZR.js} +4 -4
  29. package/dist/{chunk-A2ZLHBHG.js → chunk-PMMHVSCR.js} +2 -2
  30. package/dist/chunk-QQQI6ISK.js +853 -0
  31. package/dist/chunk-RG5TOL4O.js +18 -0
  32. package/dist/{chunk-VHJRZM2S.js → chunk-SNW2NPP4.js} +2 -2
  33. package/dist/chunk-SZJWC2GA.js +125 -0
  34. package/dist/{chunk-BKF4WQCY.js → chunk-T2TP6ZC6.js} +20 -8
  35. package/dist/{chunk-2TGZJFAT.js → chunk-TNZ5XQA4.js} +3 -3
  36. package/dist/{chunk-QCH6K235.js → chunk-UI7RPV2B.js} +1 -1
  37. package/dist/{chunk-XRQSAMX2.js → chunk-UIM5NHPP.js} +3 -3
  38. package/dist/{chunk-5XJYUFZH.js → chunk-UQFYNZKT.js} +76 -30
  39. package/dist/{chunk-OTC67N2Z.js → chunk-WC635OPK.js} +1 -1
  40. package/dist/{chunk-BV65KRHM.js → chunk-XLBQYIHH.js} +2 -2
  41. package/dist/{chunk-VY3RB2V7.js → chunk-Z6TIXE77.js} +3 -3
  42. package/dist/cli.js +24 -24
  43. package/dist/{clock-HSEKS5AR.js → clock-BMLJ2TR6.js} +8 -8
  44. package/dist/{cloud-sync-6JL4C24T.js → cloud-sync-OIX576NA.js} +20 -21
  45. package/dist/{config-UTS7QULS.js → config-QB7W3Z7P.js} +4 -4
  46. package/dist/connectors/discord-bridge.js +4 -4
  47. package/dist/connectors/slack-bridge.js +4 -4
  48. package/dist/connectors/telegram-bridge.js +4 -4
  49. package/dist/{conversations-2PW57WO2.js → conversations-G6YRSABR.js} +16 -10
  50. package/dist/{create-5BPOOJAN.js → create-C3BBFYV7.js} +4 -4
  51. package/dist/{create-UVCK2CS6.js → create-PN73742N.js} +4 -4
  52. package/dist/{daemon-client-RVIKXGFQ.js → daemon-client-2MIPKY3E.js} +1 -1
  53. package/dist/{daemon-restart-HSZ3BCX5.js → daemon-restart-L2O6L7NR.js} +9 -9
  54. package/dist/daemon.js +690 -1281
  55. package/dist/db-CBOCDYVA.js +9 -0
  56. package/dist/{db-BDMH4SZ2.js → db-IJL6B26S.js} +1 -1
  57. package/dist/{delete-L5PAVDGQ.js → delete-NLXES2C7.js} +3 -3
  58. package/dist/delivery-manager-4PVBUZJB.js +30 -0
  59. package/dist/{delivery-router-HEJSJAHQ.js → delivery-router-QTFEZ26O.js} +5 -5
  60. package/dist/down-25L2RKCQ.js +17 -0
  61. package/dist/echo-text-T5ZLGMA7.js +31 -0
  62. package/dist/{env-E4XHO2BI.js → env-IQ6Q2333.js} +6 -6
  63. package/dist/exec-ONYZEA5B.js +17 -0
  64. package/dist/{export-OAS6QVBN.js → export-JPDBQESV.js} +6 -6
  65. package/dist/{extension-D74CNM7G.js → extension-LZYHBNLV.js} +26 -7
  66. package/dist/extensions-CLYXNGYB.js +50 -0
  67. package/dist/{files-CWTK6V3H.js → files-LAQ3NXQK.js} +7 -7
  68. package/dist/{import-5A3T7QV4.js → import-EROF27RH.js} +12 -11
  69. package/dist/{isolation-TK5RX2WM.js → isolation-G5J3MTKU.js} +4 -4
  70. package/dist/{join-DF5XSJAC.js → join-6SZCA5FX.js} +3 -3
  71. package/dist/{list-PDMQM7ZV.js → list-KHJZJPEJ.js} +11 -5
  72. package/dist/{login-7TE6CIZF.js → login-F6YMAVLE.js} +6 -6
  73. package/dist/{login-GOTAYLXP.js → login-GYTH67ES.js} +4 -4
  74. package/dist/{logout-T4XS6LRU.js → logout-HHPH52KZ.js} +6 -6
  75. package/dist/{logout-6KIA74EV.js → logout-YHQLOFLR.js} +4 -4
  76. package/dist/message-delivery-N2V5APCS.js +40 -0
  77. package/dist/{mind-5IEYKV7I.js → mind-M57ET546.js} +19 -19
  78. package/dist/mind-activity-tracker-42ENM32S.js +18 -0
  79. package/dist/{mind-history-IE2QH7U5.js → mind-history-WHCNZ6I5.js} +86 -19
  80. package/dist/{mind-list-GEWHWAL4.js → mind-list-H3HC2ZRG.js} +4 -4
  81. package/dist/mind-manager-LS2AIXHQ.js +30 -0
  82. package/dist/{mind-profile-DCBDVF5B.js → mind-profile-7VYRJGFZ.js} +2 -2
  83. package/dist/mind-service-UDXF5WC2.js +36 -0
  84. package/dist/{mind-sleep-ITCF6OQA.js → mind-sleep-ZL5ZXFTM.js} +4 -4
  85. package/dist/{mind-status-X4SX3YUG.js → mind-status-ZWULKOUO.js} +4 -4
  86. package/dist/{mind-wake-KXMKMGWX.js → mind-wake-HK5ORGUK.js} +4 -4
  87. package/dist/{package-D2FSVFAX.js → package-5FGU5QNP.js} +6 -6
  88. package/dist/{read-67VRP2DO.js → read-CP7MYMJQ.js} +8 -8
  89. package/dist/{read-stdin-3X5VYKNS.js → read-stdin-4B5UYPPM.js} +1 -1
  90. package/dist/{register-SB7NXCOE.js → register-XOBFEMI4.js} +4 -4
  91. package/dist/{registry-GBSNW3HG.js → registry-KMELPC3X.js} +3 -3
  92. package/dist/{reject-MUR2KWJ4.js → reject-43AGXB6B.js} +4 -4
  93. package/dist/{restart-5EGG4JXU.js → restart-O5QIYQJT.js} +5 -5
  94. package/dist/{sandbox-R37VIU36.js → sandbox-PQYEICEF.js} +6 -6
  95. package/dist/scheduler-NTC74JYH.js +30 -0
  96. package/dist/{schema-XVZ2CLKW.js → schema-K575EBPE.js} +4 -2
  97. package/dist/{seed-EQORWX77.js → seed-55VC3A57.js} +2 -2
  98. package/dist/{seed-check-KJNTL72M.js → seed-check-HZPVFJKZ.js} +2 -2
  99. package/dist/{seed-cmd-ZM2XGVU2.js → seed-cmd-2KOEQZK6.js} +4 -4
  100. package/dist/{seed-create-DRWGGHEI.js → seed-create-Y2Z5JWBB.js} +6 -6
  101. package/dist/{seed-sprout-JYXGXOP3.js → seed-sprout-OLSIWXZN.js} +15 -15
  102. package/dist/{send-JBJJQ7CA.js → send-7CIP5GLS.js} +8 -8
  103. package/dist/{service-WNPCNHOX.js → service-YMHWPDXW.js} +6 -6
  104. package/dist/{setup-BJ4YAY26.js → setup-6Z34JJEB.js} +32 -37
  105. package/dist/{setup-RHJRFURI.js → setup-PF7JSFMO.js} +6 -4
  106. package/dist/{skill-TAAKEYBV.js → skill-ICN6Y2ZF.js} +6 -6
  107. package/dist/skills/tending/SKILL.md +52 -0
  108. package/dist/{skills-EKMCQ46K.js → skills-FDMLJGZ3.js} +8 -8
  109. package/dist/sleep-manager-TQP5ZJI5.js +34 -0
  110. package/dist/spirit-SM6ARJ2N.js +24 -0
  111. package/dist/{split-AWVOYOPZ.js → split-5YBEQTBF.js} +3 -3
  112. package/dist/{sprout-HE4TITMK.js → sprout-G6G57IOY.js} +2 -2
  113. package/dist/src-LT6ZBYYX.js +2133 -0
  114. package/dist/src-O4PRLMKM.js +425 -0
  115. package/dist/src-OYWRPLC6.js +617 -0
  116. package/dist/{start-3UXOPXQG.js → start-LMXXRR3X.js} +5 -5
  117. package/dist/{status-ZK34WYIM.js → status-MC2P7DBG.js} +7 -7
  118. package/dist/{stop-3XYIBGFM.js → stop-TWDKVEUX.js} +5 -5
  119. package/dist/system-chat-NNXYCSVL.js +34 -0
  120. package/dist/{systems-O43WGQY6.js → systems-Y2WZV2K4.js} +7 -7
  121. package/dist/{tailscale-ZIZ2HWJ5.js → tailscale-LTYNKIPZ.js} +4 -4
  122. package/dist/template-hash-SSIBEEYK.js +9 -0
  123. package/dist/up-5JXV6BZS.js +19 -0
  124. package/dist/{update-ANE5ZM7F.js → update-UOP2INF2.js} +7 -7
  125. package/dist/{update-check-UV55CBEP.js → update-check-IKS7SGK5.js} +4 -4
  126. package/dist/{upgrade-ZMDGC7M2.js → upgrade-RXFZR5FI.js} +3 -3
  127. package/dist/{variant-QWL2WSRI.js → variant-HHDTW74J.js} +1 -1
  128. package/dist/{version-notify-FXSEMXWW.js → version-notify-CSE4NBYM.js} +22 -23
  129. package/dist/{volute-config-D2XVS2YI.js → volute-config-TS62GS6A.js} +2 -2
  130. package/dist/web-assets/assets/index-B3xLeex8.js +75 -0
  131. package/dist/web-assets/assets/index-Dr4A90Lo.css +1 -0
  132. package/dist/web-assets/index.html +2 -2
  133. package/drizzle/0006_channels.sql +17 -0
  134. package/drizzle/0007_drop_conversation_name_title.sql +11 -0
  135. package/drizzle/0008_performance_indexes.sql +6 -0
  136. package/drizzle/meta/0006_snapshot.json +7 -0
  137. package/drizzle/meta/0007_snapshot.json +7 -0
  138. package/drizzle/meta/_journal.json +21 -0
  139. package/package.json +5 -5
  140. package/templates/_base/home/.config/routes.json +2 -2
  141. package/templates/_base/home/VOLUTE.md +1 -2
  142. package/templates/_base/src/lib/context-breakdown.ts +22 -16
  143. package/templates/_base/src/lib/format-prefix.ts +1 -7
  144. package/templates/claude/.init/.config/routes.json +2 -2
  145. package/templates/codex/.init/.config/routes.json +2 -2
  146. package/templates/pi/.init/.config/routes.json +2 -2
  147. package/dist/activity-events-ZW4SDL2C.js +0 -15
  148. package/dist/chunk-7KJOFUNN.js +0 -22
  149. package/dist/chunk-MDJGMOSD.js +0 -207
  150. package/dist/db-BVBJ57TU.js +0 -9
  151. package/dist/delivery-manager-H5ZVBMCQ.js +0 -31
  152. package/dist/down-74VXM45A.js +0 -17
  153. package/dist/exec-PY7THYH4.js +0 -17
  154. package/dist/extensions-XDDFY72A.js +0 -49
  155. package/dist/lib-DYEZMGW7.js +0 -6588
  156. package/dist/message-delivery-GRC4W6P7.js +0 -41
  157. package/dist/mind-activity-tracker-QBLIV7ZJ.js +0 -18
  158. package/dist/mind-manager-HFLB5653.js +0 -31
  159. package/dist/mind-service-X2CAA6W6.js +0 -37
  160. package/dist/scheduler-Y7O4CJXL.js +0 -31
  161. package/dist/sleep-manager-7KFK3USC.js +0 -35
  162. package/dist/spirit-ZFRDXMG7.js +0 -23
  163. package/dist/system-chat-IDPHYHY4.js +0 -35
  164. package/dist/template-hash-A7FNHTB7.js +0 -9
  165. package/dist/up-77ICEDEW.js +0 -19
  166. package/dist/web-assets/assets/index-BhxWKvbB.css +0 -1
  167. package/dist/web-assets/assets/index-CHVKJ9II.js +0 -75
@@ -1,24 +1,25 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  subscribe
4
- } from "./chunk-CORXD635.js";
4
+ } from "./chunk-N42QMDID.js";
5
5
  import {
6
6
  logger_default
7
- } from "./chunk-BKF4WQCY.js";
7
+ } from "./chunk-T2TP6ZC6.js";
8
8
  import {
9
9
  getDb
10
- } from "./chunk-BDYXIWA5.js";
10
+ } from "./chunk-2NHRJ3YO.js";
11
11
  import {
12
+ channels,
12
13
  conversationParticipants,
13
14
  conversationReads,
14
15
  conversations,
15
16
  messages,
16
17
  users
17
- } from "./chunk-5N7Y5WAM.js";
18
+ } from "./chunk-CJ26DXZL.js";
18
19
 
19
20
  // packages/daemon/src/lib/events/conversations.ts
20
21
  import { randomUUID } from "crypto";
21
- import { and, desc, eq, inArray, isNull, lt, sql } from "drizzle-orm";
22
+ import { aliasedTable, and, desc, eq, inArray, lt, sql } from "drizzle-orm";
22
23
 
23
24
  // packages/daemon/src/lib/webhook.ts
24
25
  var slog = logger_default.child("webhook");
@@ -111,19 +112,14 @@ function publish(conversationId, event) {
111
112
  }
112
113
 
113
114
  // packages/daemon/src/lib/events/conversations.ts
114
- async function createConversation(mindName, channel, opts) {
115
+ async function createConversation(opts) {
115
116
  const db = await getDb();
116
117
  const id = randomUUID();
117
118
  const type = opts?.type ?? "dm";
118
- const name = opts?.name ?? null;
119
119
  await db.insert(conversations).values({
120
120
  id,
121
- mind_name: mindName,
122
- channel,
123
121
  type,
124
- name,
125
- user_id: opts?.userId ?? null,
126
- title: opts?.title ?? null
122
+ user_id: opts?.userId ?? null
127
123
  });
128
124
  if (opts?.participantIds && opts.participantIds.length > 0) {
129
125
  await db.insert(conversationParticipants).values(
@@ -136,34 +132,18 @@ async function createConversation(mindName, channel, opts) {
136
132
  }
137
133
  fireWebhook({
138
134
  event: "conversation_created",
139
- mind: mindName ?? "",
140
- data: { id, mindName, channel, type, name, title: opts?.title ?? null }
135
+ mind: "",
136
+ data: { id, type }
141
137
  });
142
138
  return {
143
139
  id,
144
- mind_name: mindName,
145
- channel,
146
140
  type,
147
- name,
148
141
  user_id: opts?.userId ?? null,
149
- title: opts?.title ?? null,
150
142
  private: 0,
151
143
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
152
144
  updated_at: (/* @__PURE__ */ new Date()).toISOString()
153
145
  };
154
146
  }
155
- async function getOrCreateConversation(mindName, channel, opts) {
156
- const db = await getDb();
157
- const existing = await db.select().from(conversations).where(
158
- and(
159
- eq(conversations.mind_name, mindName),
160
- eq(conversations.channel, channel),
161
- eq(conversations.type, "dm")
162
- )
163
- ).orderBy(desc(conversations.updated_at)).limit(1).get();
164
- if (existing) return existing;
165
- return createConversation(mindName, channel, opts);
166
- }
167
147
  async function getConversation(id) {
168
148
  const db = await getDb();
169
149
  const row = await db.select().from(conversations).where(eq(conversations.id, id)).get();
@@ -236,13 +216,6 @@ async function addMessage(conversationId, role, senderName, content, opts) {
236
216
  turn_id: opts?.turnId ?? null
237
217
  }).returning({ id: messages.id, created_at: messages.created_at });
238
218
  await db.update(conversations).set({ updated_at: sql`datetime('now')` }).where(eq(conversations.id, conversationId));
239
- if (role === "user") {
240
- const firstText = content.find((b) => b.type === "text");
241
- const title = firstText ? firstText.text.slice(0, 80) : "";
242
- if (title) {
243
- await db.update(conversations).set({ title }).where(and(eq(conversations.id, conversationId), isNull(conversations.title)));
244
- }
245
- }
246
219
  const msg = {
247
220
  id: result.id,
248
221
  conversation_id: conversationId,
@@ -259,10 +232,9 @@ async function addMessage(conversationId, role, senderName, content, opts) {
259
232
  content: msg.content,
260
233
  createdAt: msg.created_at
261
234
  });
262
- const conv = await db.select({ mind_name: conversations.mind_name }).from(conversations).where(eq(conversations.id, conversationId)).get();
263
235
  fireWebhook({
264
236
  event: "message_created",
265
- mind: conv?.mind_name ?? "",
237
+ mind: "",
266
238
  data: {
267
239
  conversationId,
268
240
  messageId: result.id,
@@ -274,10 +246,10 @@ async function addMessage(conversationId, role, senderName, content, opts) {
274
246
  });
275
247
  return msg;
276
248
  }
277
- async function getMessages(conversationId) {
249
+ async function getMessages(conversationId, limit = 200) {
278
250
  const db = await getDb();
279
- const rows = await db.select().from(messages).where(eq(messages.conversation_id, conversationId)).orderBy(messages.created_at).all();
280
- return rows.map(parseMessageRow);
251
+ const rows = await db.select().from(messages).where(eq(messages.conversation_id, conversationId)).orderBy(desc(messages.id)).limit(limit).all();
252
+ return rows.reverse().map(parseMessageRow);
281
253
  }
282
254
  async function getMessagesPaginated(conversationId, opts) {
283
255
  const db = await getDb();
@@ -304,8 +276,7 @@ function parseMessageRow(row) {
304
276
  }
305
277
  return { ...row, role: row.role, content };
306
278
  }
307
- async function listConversationsWithParticipants(userId) {
308
- const convs = await listConversationsForUser(userId);
279
+ async function enrichConversations(convs) {
309
280
  if (convs.length === 0) return [];
310
281
  const db = await getDb();
311
282
  const convIds = convs.map((c) => c.id);
@@ -317,168 +288,93 @@ async function listConversationsWithParticipants(userId) {
317
288
  role: conversationParticipants.role,
318
289
  displayName: users.display_name,
319
290
  description: users.description,
320
- avatar: users.avatar
321
- }).from(conversationParticipants).innerJoin(users, eq(conversationParticipants.user_id, users.id)).where(inArray(conversationParticipants.conversation_id, convIds));
291
+ avatar: users.avatar,
292
+ channelName: channels.name
293
+ }).from(conversationParticipants).innerJoin(users, eq(conversationParticipants.user_id, users.id)).leftJoin(channels, eq(channels.conversation_id, conversationParticipants.conversation_id)).where(inArray(conversationParticipants.conversation_id, convIds));
322
294
  const byConv = /* @__PURE__ */ new Map();
295
+ const channelNames = /* @__PURE__ */ new Map();
323
296
  for (const r of rows) {
324
297
  let arr = byConv.get(r.conversationId);
325
298
  if (!arr) {
326
299
  arr = [];
327
300
  byConv.set(r.conversationId, arr);
328
301
  }
329
- arr.push({
330
- userId: r.userId,
331
- username: r.username,
332
- userType: r.userType,
333
- role: r.role,
334
- displayName: r.displayName,
335
- description: r.description,
336
- avatar: r.avatar
337
- });
302
+ if (!arr.some((p) => p.userId === r.userId)) {
303
+ arr.push({
304
+ userId: r.userId,
305
+ username: r.username,
306
+ userType: r.userType,
307
+ role: r.role,
308
+ displayName: r.displayName,
309
+ description: r.description,
310
+ avatar: r.avatar
311
+ });
312
+ }
313
+ if (r.channelName && !channelNames.has(r.conversationId)) {
314
+ channelNames.set(r.conversationId, r.channelName);
315
+ }
338
316
  }
339
- const lastMsgIds = await db.select({
340
- conversationId: messages.conversation_id,
341
- maxId: sql`MAX(${messages.id})`
342
- }).from(messages).where(inArray(messages.conversation_id, convIds)).groupBy(messages.conversation_id);
317
+ const msgRows = await db.select().from(messages).where(
318
+ sql`${messages.id} IN (SELECT MAX(id) FROM ${messages} WHERE ${messages.conversation_id} IN ${convIds} GROUP BY ${messages.conversation_id})`
319
+ );
343
320
  const byLastMsg = /* @__PURE__ */ new Map();
344
- if (lastMsgIds.length > 0) {
345
- const msgRows = await db.select().from(messages).where(
346
- inArray(
347
- messages.id,
348
- lastMsgIds.map((r) => r.maxId)
349
- )
350
- );
351
- for (const m of msgRows) {
352
- let text = "";
353
- try {
354
- const parsed = JSON.parse(m.content);
355
- const blocks = Array.isArray(parsed) ? parsed : [];
356
- const textBlock = blocks.find((b) => b.type === "text");
357
- if (textBlock && "text" in textBlock) text = textBlock.text;
358
- } catch {
359
- text = m.content;
360
- }
361
- byLastMsg.set(m.conversation_id, {
362
- role: m.role,
363
- senderName: m.sender_name,
364
- text,
365
- createdAt: m.created_at
366
- });
321
+ for (const m of msgRows) {
322
+ let text = "";
323
+ try {
324
+ const parsed = JSON.parse(m.content);
325
+ const blocks = Array.isArray(parsed) ? parsed : [];
326
+ const textBlock = blocks.find((b) => b.type === "text");
327
+ if (textBlock && "text" in textBlock) text = textBlock.text;
328
+ } catch {
329
+ text = m.content;
367
330
  }
331
+ byLastMsg.set(m.conversation_id, {
332
+ role: m.role,
333
+ senderName: m.sender_name,
334
+ text,
335
+ createdAt: m.created_at
336
+ });
368
337
  }
369
338
  return convs.map((c) => ({
370
339
  ...c,
340
+ channel_name: channelNames.get(c.id) ?? null,
371
341
  participants: byConv.get(c.id) ?? [],
372
342
  lastMessage: byLastMsg.get(c.id)
373
343
  }));
374
344
  }
375
- async function findDMConversation(mindName, participantIds) {
345
+ async function listConversationsWithParticipants(userId) {
346
+ const convs = await listConversationsForUser(userId);
347
+ return enrichConversations(convs);
348
+ }
349
+ async function findDMConversation(participantIds) {
376
350
  const db = await getDb();
377
- const mindConvs = await db.select({ id: conversations.id }).from(conversations).where(and(eq(conversations.mind_name, mindName), eq(conversations.type, "dm"))).all();
378
- for (const conv of mindConvs) {
379
- const rows = await db.select({ user_id: conversationParticipants.user_id }).from(conversationParticipants).where(eq(conversationParticipants.conversation_id, conv.id)).all();
380
- if (rows.length !== 2) continue;
381
- const ids = new Set(rows.map((r) => r.user_id));
382
- if (ids.has(participantIds[0]) && ids.has(participantIds[1])) {
383
- return conv.id;
384
- }
385
- }
386
- return null;
351
+ const [id1, id2] = participantIds;
352
+ const cp2 = aliasedTable(conversationParticipants, "cp2");
353
+ const row = await db.select({ conversation_id: conversationParticipants.conversation_id }).from(conversationParticipants).innerJoin(cp2, eq(conversationParticipants.conversation_id, cp2.conversation_id)).innerJoin(conversations, eq(conversations.id, conversationParticipants.conversation_id)).where(
354
+ and(
355
+ eq(conversationParticipants.user_id, id1),
356
+ eq(cp2.user_id, id2),
357
+ eq(conversations.type, "dm"),
358
+ sql`(SELECT COUNT(*) FROM conversation_participants cp3 WHERE cp3.conversation_id = ${conversations.id}) = 2`
359
+ )
360
+ ).get();
361
+ return row?.conversation_id ?? null;
387
362
  }
388
363
  async function listConversationsForMind(mindName) {
389
364
  const db = await getDb();
390
- const byName = await db.select().from(conversations).where(eq(conversations.mind_name, mindName)).orderBy(desc(conversations.updated_at)).all();
391
365
  const mindUser = await db.select({ id: users.id }).from(users).where(and(eq(users.username, mindName), eq(users.user_type, "mind"))).get();
392
- const byNameIds = new Set(byName.map((c) => c.id));
393
- let byParticipation = [];
394
- if (mindUser) {
395
- const participantRows = await db.select({ conversation_id: conversationParticipants.conversation_id }).from(conversationParticipants).where(eq(conversationParticipants.user_id, mindUser.id)).all();
396
- const extraIds = participantRows.map((r) => r.conversation_id).filter((id) => !byNameIds.has(id));
397
- if (extraIds.length > 0) {
398
- byParticipation = await db.select().from(conversations).where(inArray(conversations.id, extraIds)).orderBy(desc(conversations.updated_at)).all();
399
- }
400
- }
401
- const convs = [...byName, ...byParticipation].sort(
402
- (a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime()
403
- );
404
- if (convs.length === 0) return [];
405
- const convIds = convs.map((c) => c.id);
406
- const rows = await db.select({
407
- conversationId: conversationParticipants.conversation_id,
408
- userId: users.id,
409
- username: users.username,
410
- userType: users.user_type,
411
- role: conversationParticipants.role,
412
- displayName: users.display_name,
413
- description: users.description,
414
- avatar: users.avatar
415
- }).from(conversationParticipants).innerJoin(users, eq(conversationParticipants.user_id, users.id)).where(inArray(conversationParticipants.conversation_id, convIds));
416
- const byConv = /* @__PURE__ */ new Map();
417
- for (const r of rows) {
418
- let arr = byConv.get(r.conversationId);
419
- if (!arr) {
420
- arr = [];
421
- byConv.set(r.conversationId, arr);
422
- }
423
- arr.push({
424
- userId: r.userId,
425
- username: r.username,
426
- userType: r.userType,
427
- role: r.role,
428
- displayName: r.displayName,
429
- description: r.description,
430
- avatar: r.avatar
431
- });
432
- }
433
- const lastMsgIds = await db.select({
434
- conversationId: messages.conversation_id,
435
- maxId: sql`MAX(${messages.id})`
436
- }).from(messages).where(inArray(messages.conversation_id, convIds)).groupBy(messages.conversation_id);
437
- const byLastMsg = /* @__PURE__ */ new Map();
438
- if (lastMsgIds.length > 0) {
439
- const msgRows = await db.select().from(messages).where(
440
- inArray(
441
- messages.id,
442
- lastMsgIds.map((r) => r.maxId)
443
- )
444
- );
445
- for (const m of msgRows) {
446
- let text = "";
447
- try {
448
- const parsed = JSON.parse(m.content);
449
- const blocks = Array.isArray(parsed) ? parsed : [];
450
- const textBlock = blocks.find((b) => b.type === "text");
451
- if (textBlock && "text" in textBlock) text = textBlock.text;
452
- } catch {
453
- text = m.content;
454
- }
455
- byLastMsg.set(m.conversation_id, {
456
- role: m.role,
457
- senderName: m.sender_name,
458
- text,
459
- createdAt: m.created_at
460
- });
461
- }
462
- }
463
- return convs.map((c) => ({
464
- ...c,
465
- participants: byConv.get(c.id) ?? [],
466
- lastMessage: byLastMsg.get(c.id)
467
- }));
366
+ if (!mindUser) return [];
367
+ const participantRows = await db.select({ conversation_id: conversationParticipants.conversation_id }).from(conversationParticipants).where(eq(conversationParticipants.user_id, mindUser.id)).all();
368
+ if (participantRows.length === 0) return [];
369
+ const convIds = participantRows.map((r) => r.conversation_id);
370
+ const convs = await db.select().from(conversations).where(inArray(conversations.id, convIds)).orderBy(desc(conversations.updated_at)).all();
371
+ return enrichConversations(convs);
468
372
  }
469
373
  async function isConversationForMind(mindName, conversationId) {
470
374
  const db = await getDb();
471
- const byName = await db.select({ id: conversations.id }).from(conversations).where(and(eq(conversations.id, conversationId), eq(conversations.mind_name, mindName))).get();
472
- if (byName) return true;
473
375
  const mindUser = await db.select({ id: users.id }).from(users).where(and(eq(users.username, mindName), eq(users.user_type, "mind"))).get();
474
376
  if (!mindUser) return false;
475
- const participant = await db.select({ conversation_id: conversationParticipants.conversation_id }).from(conversationParticipants).where(
476
- and(
477
- eq(conversationParticipants.conversation_id, conversationId),
478
- eq(conversationParticipants.user_id, mindUser.id)
479
- )
480
- ).get();
481
- return !!participant;
377
+ return isParticipant(conversationId, mindUser.id);
482
378
  }
483
379
  async function setConversationPrivate(id, isPrivate) {
484
380
  const db = await getDb();
@@ -488,23 +384,84 @@ async function deleteConversation(id) {
488
384
  const db = await getDb();
489
385
  await db.delete(conversations).where(eq(conversations.id, id));
490
386
  }
491
- async function createChannel(name, creatorId) {
387
+ async function createChannel(name, creatorId, settings) {
492
388
  const participantIds = creatorId ? [creatorId] : [];
493
- return createConversation(null, "volute", {
389
+ const conv = await createConversation({
494
390
  type: "channel",
495
- name,
496
- title: name,
497
391
  participantIds
498
392
  });
393
+ const db = await getDb();
394
+ try {
395
+ const isPrivate = settings?.private ? 1 : 0;
396
+ await db.insert(channels).values({
397
+ conversation_id: conv.id,
398
+ name,
399
+ description: settings?.description ?? null,
400
+ rules: settings?.rules ?? null,
401
+ char_limit: settings?.charLimit ?? null,
402
+ private: isPrivate
403
+ });
404
+ if (isPrivate) {
405
+ await db.update(conversations).set({ private: 1 }).where(eq(conversations.id, conv.id));
406
+ }
407
+ } catch (err) {
408
+ try {
409
+ await db.delete(conversations).where(eq(conversations.id, conv.id));
410
+ } catch {
411
+ }
412
+ throw err;
413
+ }
414
+ return conv;
415
+ }
416
+ async function getChannelName(conversationId) {
417
+ const db = await getDb();
418
+ const row = await db.select({ name: channels.name }).from(channels).where(eq(channels.conversation_id, conversationId)).get();
419
+ return row?.name ?? null;
499
420
  }
500
421
  async function getChannelByName(name) {
501
422
  const db = await getDb();
502
- const row = await db.select().from(conversations).where(and(eq(conversations.name, name), eq(conversations.type, "channel"))).get();
423
+ const ch = await db.select().from(channels).where(eq(channels.name, name)).get();
424
+ if (!ch) return null;
425
+ return getConversation(ch.conversation_id);
426
+ }
427
+ async function getChannelSettings(name) {
428
+ const db = await getDb();
429
+ const row = await db.select().from(channels).where(eq(channels.name, name)).get();
503
430
  return row ?? null;
504
431
  }
432
+ function formatChannelSettings(row) {
433
+ if (!row) return null;
434
+ return {
435
+ description: row.description,
436
+ rules: row.rules,
437
+ charLimit: row.char_limit,
438
+ private: !!row.private
439
+ };
440
+ }
505
441
  async function listChannels() {
506
442
  const db = await getDb();
507
- return await db.select().from(conversations).where(eq(conversations.type, "channel")).orderBy(conversations.name).all();
443
+ const rows = await db.select().from(conversations).innerJoin(channels, eq(channels.conversation_id, conversations.id)).where(eq(conversations.type, "channel")).orderBy(channels.name).all();
444
+ return rows.map((r) => ({
445
+ ...r.conversations,
446
+ channel_name: r.channels.name
447
+ }));
448
+ }
449
+ async function updateChannelSettings(name, settings) {
450
+ const db = await getDb();
451
+ const updates = {
452
+ updated_at: sql`(datetime('now'))`
453
+ };
454
+ if (settings.description !== void 0) updates.description = settings.description;
455
+ if (settings.rules !== void 0) updates.rules = settings.rules;
456
+ if (settings.charLimit !== void 0) updates.char_limit = settings.charLimit;
457
+ if (settings.private !== void 0) updates.private = settings.private ? 1 : 0;
458
+ await db.update(channels).set(updates).where(eq(channels.name, name));
459
+ if (settings.private !== void 0) {
460
+ const ch = await db.select().from(channels).where(eq(channels.name, name)).get();
461
+ if (ch) {
462
+ await db.update(conversations).set({ private: settings.private ? 1 : 0 }).where(eq(conversations.id, ch.conversation_id));
463
+ }
464
+ }
508
465
  }
509
466
  async function joinChannel(conversationId, userId) {
510
467
  if (await isParticipant(conversationId, userId)) return;
@@ -556,7 +513,6 @@ export {
556
513
  subscribe2 as subscribe,
557
514
  publish,
558
515
  createConversation,
559
- getOrCreateConversation,
560
516
  getConversation,
561
517
  addParticipant,
562
518
  removeParticipant,
@@ -575,8 +531,12 @@ export {
575
531
  setConversationPrivate,
576
532
  deleteConversation,
577
533
  createChannel,
534
+ getChannelName,
578
535
  getChannelByName,
536
+ getChannelSettings,
537
+ formatChannelSettings,
579
538
  listChannels,
539
+ updateChannelSettings,
580
540
  joinChannel,
581
541
  leaveChannel,
582
542
  getUnreadCounts,
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  logger_default
4
- } from "./chunk-BKF4WQCY.js";
4
+ } from "./chunk-T2TP6ZC6.js";
5
5
  import {
6
6
  mindDir
7
- } from "./chunk-BDYXIWA5.js";
7
+ } from "./chunk-2NHRJ3YO.js";
8
8
 
9
9
  // packages/daemon/src/lib/delivery/delivery-router.ts
10
10
  import { readFileSync, statSync } from "fs";
@@ -1,19 +1,32 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  voluteSystemDir
4
- } from "./chunk-BDYXIWA5.js";
4
+ } from "./chunk-2NHRJ3YO.js";
5
5
 
6
- // packages/daemon/src/lib/setup.ts
6
+ // packages/daemon/src/lib/config/setup.ts
7
7
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
8
8
  import { resolve } from "path";
9
9
  function configPath() {
10
10
  return resolve(voluteSystemDir(), "config.json");
11
11
  }
12
+ var _cachedConfig = null;
13
+ var CONFIG_CACHE_TTL = 2e3;
14
+ function _resetConfigCache() {
15
+ _cachedConfig = null;
16
+ }
12
17
  function readGlobalConfig() {
18
+ if (_cachedConfig && Date.now() - _cachedConfig.ts < CONFIG_CACHE_TTL) {
19
+ return { ..._cachedConfig.config };
20
+ }
13
21
  const path = configPath();
14
- if (!existsSync(path)) return {};
22
+ if (!existsSync(path)) {
23
+ _cachedConfig = null;
24
+ return {};
25
+ }
15
26
  try {
16
- return JSON.parse(readFileSync(path, "utf-8"));
27
+ const config = JSON.parse(readFileSync(path, "utf-8"));
28
+ _cachedConfig = { config, ts: Date.now() };
29
+ return config;
17
30
  } catch (err) {
18
31
  console.error(`Failed to parse ${path}: ${err instanceof Error ? err.message : err}`);
19
32
  return {};
@@ -24,6 +37,7 @@ function writeGlobalConfig(config) {
24
37
  mkdirSync(voluteSystemDir(), { recursive: true });
25
38
  writeFileSync(path, `${JSON.stringify(config, null, 2)}
26
39
  `);
40
+ _cachedConfig = { config, ts: Date.now() };
27
41
  }
28
42
  function isSetupComplete() {
29
43
  const config = readGlobalConfig();
@@ -47,6 +61,7 @@ function migrateSetupCompleted() {
47
61
 
48
62
  export {
49
63
  configPath,
64
+ _resetConfigCache,
50
65
  readGlobalConfig,
51
66
  writeGlobalConfig,
52
67
  isSetupComplete,
@@ -2,9 +2,9 @@
2
2
  import {
3
3
  mindDir,
4
4
  stateDir
5
- } from "./chunk-BDYXIWA5.js";
5
+ } from "./chunk-2NHRJ3YO.js";
6
6
 
7
- // packages/daemon/src/lib/archive.ts
7
+ // packages/daemon/src/lib/mind/archive.ts
8
8
  import { execFileSync } from "child_process";
9
9
  import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "fs";
10
10
  import { join, relative, resolve } from "path";
@@ -1,12 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  __export
4
- } from "./chunk-7KJOFUNN.js";
4
+ } from "./chunk-K3NQKI34.js";
5
5
 
6
6
  // packages/daemon/src/lib/schema.ts
7
7
  var schema_exports = {};
8
8
  __export(schema_exports, {
9
9
  activity: () => activity,
10
+ channels: () => channels,
10
11
  conversationParticipants: () => conversationParticipants,
11
12
  conversationReads: () => conversationReads,
12
13
  conversations: () => conversations,
@@ -59,21 +60,15 @@ var conversations = sqliteTable(
59
60
  "conversations",
60
61
  {
61
62
  id: text("id").primaryKey(),
62
- mind_name: text("mind_name"),
63
- channel: text("channel").notNull(),
64
63
  type: text("type").notNull().default("dm"),
65
- name: text("name"),
66
64
  user_id: integer("user_id").references(() => users.id),
67
- title: text("title"),
68
65
  private: integer("private").notNull().default(0),
69
66
  created_at: text("created_at").notNull().default(sql`(datetime('now'))`),
70
67
  updated_at: text("updated_at").notNull().default(sql`(datetime('now'))`)
71
68
  },
72
69
  (table) => [
73
- index("idx_conversations_mind_name").on(table.mind_name),
74
70
  index("idx_conversations_user_id").on(table.user_id),
75
- index("idx_conversations_updated_at").on(table.updated_at),
76
- uniqueIndex("idx_conversations_name").on(table.name)
71
+ index("idx_conversations_updated_at").on(table.updated_at)
77
72
  ]
78
73
  );
79
74
  var turns = sqliteTable(
@@ -90,7 +85,8 @@ var turns = sqliteTable(
90
85
  },
91
86
  (table) => [
92
87
  index("idx_turns_mind").on(table.mind),
93
- index("idx_turns_mind_status").on(table.mind, table.status)
88
+ index("idx_turns_mind_status").on(table.mind, table.status),
89
+ index("idx_turns_mind_created_at").on(table.mind, table.created_at)
94
90
  ]
95
91
  );
96
92
  var mindHistory = sqliteTable(
@@ -112,7 +108,9 @@ var mindHistory = sqliteTable(
112
108
  index("idx_mind_history_mind").on(table.mind),
113
109
  index("idx_mind_history_mind_channel").on(table.mind, table.channel),
114
110
  index("idx_mind_history_mind_type").on(table.mind, table.type),
115
- index("idx_mind_history_turn_id").on(table.turn_id)
111
+ index("idx_mind_history_turn_id").on(table.turn_id),
112
+ index("idx_mind_history_session").on(table.session),
113
+ index("idx_mind_history_mind_created_at").on(table.mind, table.created_at)
116
114
  ]
117
115
  );
118
116
  var conversationParticipants = sqliteTable(
@@ -161,7 +159,8 @@ var deliveryQueue = sqliteTable(
161
159
  },
162
160
  (table) => [
163
161
  index("idx_delivery_queue_mind_session").on(table.mind, table.session),
164
- index("idx_delivery_queue_mind_status").on(table.mind, table.status)
162
+ index("idx_delivery_queue_mind_status").on(table.mind, table.status),
163
+ index("idx_delivery_queue_status").on(table.status)
165
164
  ]
166
165
  );
167
166
  var activity = sqliteTable(
@@ -179,7 +178,8 @@ var activity = sqliteTable(
179
178
  (table) => [
180
179
  index("idx_activity_created_at").on(table.created_at),
181
180
  index("idx_activity_mind").on(table.mind),
182
- index("idx_activity_turn_id").on(table.turn_id)
181
+ index("idx_activity_turn_id").on(table.turn_id),
182
+ index("idx_activity_type").on(table.type)
183
183
  ]
184
184
  );
185
185
  var summaries = sqliteTable(
@@ -195,7 +195,8 @@ var summaries = sqliteTable(
195
195
  },
196
196
  (table) => [
197
197
  uniqueIndex("idx_summaries_unique").on(table.mind, table.period, table.period_key),
198
- index("idx_summaries_mind_period").on(table.mind, table.period)
198
+ index("idx_summaries_mind_period").on(table.mind, table.period),
199
+ index("idx_summaries_mind_period_key").on(table.mind, table.period_key)
199
200
  ]
200
201
  );
201
202
  var conversationReads = sqliteTable(
@@ -209,6 +210,20 @@ var conversationReads = sqliteTable(
209
210
  uniqueIndex("idx_conversation_reads_unique").on(table.user_id, table.conversation_id)
210
211
  ]
211
212
  );
213
+ var channels = sqliteTable(
214
+ "channels",
215
+ {
216
+ conversation_id: text("conversation_id").primaryKey().references(() => conversations.id, { onDelete: "cascade" }),
217
+ name: text("name").notNull(),
218
+ description: text("description"),
219
+ rules: text("rules"),
220
+ char_limit: integer("char_limit"),
221
+ private: integer("private").notNull().default(0),
222
+ created_at: text("created_at").notNull().default(sql`(datetime('now'))`),
223
+ updated_at: text("updated_at").notNull().default(sql`(datetime('now'))`)
224
+ },
225
+ (table) => [uniqueIndex("idx_channels_name").on(table.name)]
226
+ );
212
227
  var messages = sqliteTable(
213
228
  "messages",
214
229
  {
@@ -241,6 +256,7 @@ export {
241
256
  activity,
242
257
  summaries,
243
258
  conversationReads,
259
+ channels,
244
260
  messages,
245
261
  schema_exports
246
262
  };
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // packages/daemon/src/lib/volute-config.ts
3
+ // packages/daemon/src/lib/mind/volute-config.ts
4
4
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
5
5
  import { dirname, resolve } from "path";
6
6
  function readJson(path) {