volute 0.35.0 → 0.36.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 (123) hide show
  1. package/dist/{activity-events-ZW4SDL2C.js → activity-events-PWOGSMRL.js} +4 -4
  2. package/dist/{ai-service-LURBEDDB.js → ai-service-GSZWIETO.js} +5 -5
  3. package/dist/{archive-ESU2FUN4.js → archive-Y2YEOCGB.js} +3 -3
  4. package/dist/{auth-WX4TESEI.js → auth-YTQME4EV.js} +5 -5
  5. package/dist/{chat-QXAJF3FU.js → chat-ED7YOGKO.js} +4 -4
  6. package/dist/{chunk-AOB6GVRM.js → chunk-46DYYHN6.js} +8 -3
  7. package/dist/{chunk-5XJYUFZH.js → chunk-6F3YNULE.js} +68 -22
  8. package/dist/{chunk-BDYXIWA5.js → chunk-75AJ54GM.js} +13 -2
  9. package/dist/{chunk-5N7Y5WAM.js → chunk-7PTQGPJY.js} +28 -12
  10. package/dist/{chunk-CORXD635.js → chunk-B35VNNSS.js} +3 -3
  11. package/dist/{chunk-PWQ2ITYG.js → chunk-BOLJUV77.js} +4 -4
  12. package/dist/{chunk-ZSR72JB3.js → chunk-CU6OFXMM.js} +1 -1
  13. package/dist/{chunk-2TGZJFAT.js → chunk-DJT5Y4UF.js} +3 -3
  14. package/dist/{chunk-XRQSAMX2.js → chunk-DMV5P2LU.js} +3 -3
  15. package/dist/{chunk-WJPROOU5.js → chunk-DQ7VBXAP.js} +635 -3692
  16. package/dist/{chunk-IJHIXLVN.js → chunk-GBDVNPN2.js} +13 -11
  17. package/dist/{chunk-WZRZFFCL.js → chunk-IIWF2IPD.js} +146 -186
  18. package/dist/{chunk-F7ZNLYKZ.js → chunk-KAB6UGOL.js} +2 -2
  19. package/dist/{chunk-VHJRZM2S.js → chunk-L72WYMF7.js} +2 -2
  20. package/dist/{chunk-NJK5SDGR.js → chunk-LGNUFVMR.js} +1 -1
  21. package/dist/{chunk-FT5KETXZ.js → chunk-M5RYAA5I.js} +2 -2
  22. package/dist/{chunk-J6CJQDWI.js → chunk-N2AUHW4C.js} +2 -2
  23. package/dist/{chunk-BMZQYACC.js → chunk-NUX47Y2V.js} +19 -4
  24. package/dist/{chunk-AN2W47GW.js → chunk-PJ4IPTIN.js} +2 -2
  25. package/dist/{chunk-N446KRP7.js → chunk-PY557GDR.js} +2 -2
  26. package/dist/{chunk-MDJGMOSD.js → chunk-PZYJBOQP.js} +6 -6
  27. package/dist/chunk-RG5TOL4O.js +18 -0
  28. package/dist/{chunk-N5LMGYXX.js → chunk-SWW6AUVW.js} +2 -2
  29. package/dist/{chunk-BKF4WQCY.js → chunk-T2TP6ZC6.js} +20 -8
  30. package/dist/{chunk-VY3RB2V7.js → chunk-TWAN7ZNO.js} +3 -3
  31. package/dist/{chunk-QCH6K235.js → chunk-UI7RPV2B.js} +1 -1
  32. package/dist/{chunk-QWTR6AWZ.js → chunk-X2J7QUFH.js} +2 -2
  33. package/dist/{chunk-A2ZLHBHG.js → chunk-YDBAY3NA.js} +2 -2
  34. package/dist/{chunk-BV65KRHM.js → chunk-YTWZORJN.js} +2 -2
  35. package/dist/{chunk-OTC67N2Z.js → chunk-ZTVKQOU7.js} +1 -1
  36. package/dist/cli.js +16 -16
  37. package/dist/{cloud-sync-6JL4C24T.js → cloud-sync-BOCZSDIA.js} +19 -20
  38. package/dist/connectors/discord-bridge.js +3 -3
  39. package/dist/connectors/slack-bridge.js +3 -3
  40. package/dist/connectors/telegram-bridge.js +3 -3
  41. package/dist/{conversations-2PW57WO2.js → conversations-HH3CJD4E.js} +15 -9
  42. package/dist/{create-UVCK2CS6.js → create-QBEPSD2Z.js} +1 -1
  43. package/dist/{daemon-restart-HSZ3BCX5.js → daemon-restart-SIR3UR4B.js} +10 -10
  44. package/dist/daemon.js +446 -328
  45. package/dist/{db-BVBJ57TU.js → db-URORGSXQ.js} +2 -2
  46. package/dist/delivery-manager-WTGIPBGY.js +30 -0
  47. package/dist/{delivery-router-HEJSJAHQ.js → delivery-router-VSULHXNH.js} +4 -4
  48. package/dist/down-DGGLZ5TA.js +17 -0
  49. package/dist/{exec-PY7THYH4.js → exec-X3C6ZZTQ.js} +4 -4
  50. package/dist/{export-OAS6QVBN.js → export-HTFOHOKL.js} +3 -3
  51. package/dist/{extension-D74CNM7G.js → extension-AKZ46YSL.js} +22 -3
  52. package/dist/{extensions-XDDFY72A.js → extensions-OOSFVH7U.js} +21 -20
  53. package/dist/{files-CWTK6V3H.js → files-H2YLRD37.js} +3 -3
  54. package/dist/{import-5A3T7QV4.js → import-OL5BZX7S.js} +6 -6
  55. package/dist/{isolation-TK5RX2WM.js → isolation-N74RWOUX.js} +3 -3
  56. package/dist/{list-PDMQM7ZV.js → list-GJ4RUQQT.js} +7 -1
  57. package/dist/{login-7TE6CIZF.js → login-JXRVMBRB.js} +2 -2
  58. package/dist/{logout-T4XS6LRU.js → logout-FW243JBU.js} +2 -2
  59. package/dist/message-delivery-YORUXKDQ.js +40 -0
  60. package/dist/{mind-5IEYKV7I.js → mind-6VJJHF65.js} +6 -6
  61. package/dist/{mind-activity-tracker-QBLIV7ZJ.js → mind-activity-tracker-66UVYIFW.js} +5 -5
  62. package/dist/{mind-history-IE2QH7U5.js → mind-history-MII2SK7F.js} +81 -14
  63. package/dist/mind-manager-TJ2SUPRX.js +30 -0
  64. package/dist/mind-service-E7FM2WZF.js +36 -0
  65. package/dist/{package-D2FSVFAX.js → package-3W2MEXHB.js} +5 -5
  66. package/dist/{read-67VRP2DO.js → read-ZUDG4JWU.js} +4 -4
  67. package/dist/{registry-GBSNW3HG.js → registry-YPHK534W.js} +2 -2
  68. package/dist/{sandbox-R37VIU36.js → sandbox-LP6YRAXS.js} +5 -5
  69. package/dist/scheduler-FRJ5DK24.js +30 -0
  70. package/dist/{schema-XVZ2CLKW.js → schema-MISD3JFG.js} +3 -1
  71. package/dist/{seed-EQORWX77.js → seed-CEC4RC23.js} +1 -1
  72. package/dist/{seed-cmd-ZM2XGVU2.js → seed-cmd-WTTG7SRQ.js} +2 -2
  73. package/dist/{seed-create-DRWGGHEI.js → seed-create-M6RCC6RP.js} +3 -3
  74. package/dist/{seed-sprout-JYXGXOP3.js → seed-sprout-ZKCHFJKH.js} +10 -10
  75. package/dist/{send-JBJJQ7CA.js → send-LXUT2GGR.js} +3 -3
  76. package/dist/{service-WNPCNHOX.js → service-M6N3RUYU.js} +5 -5
  77. package/dist/{setup-BJ4YAY26.js → setup-PJOF5UV5.js} +7 -7
  78. package/dist/{setup-RHJRFURI.js → setup-PMJHCZQX.js} +5 -3
  79. package/dist/skills/tending/SKILL.md +52 -0
  80. package/dist/{skills-EKMCQ46K.js → skills-2PTRTBQP.js} +7 -7
  81. package/dist/sleep-manager-WAZWMFJT.js +34 -0
  82. package/dist/spirit-6KVDIROQ.js +24 -0
  83. package/dist/{sprout-HE4TITMK.js → sprout-WX2FFYLP.js} +1 -1
  84. package/dist/src-FQE4BHRG.js +617 -0
  85. package/dist/src-GW6FP6VL.js +425 -0
  86. package/dist/src-QEOLMAYC.js +2133 -0
  87. package/dist/{status-ZK34WYIM.js → status-3IVSLJDN.js} +6 -6
  88. package/dist/system-chat-2IFS5HCX.js +34 -0
  89. package/dist/{tailscale-ZIZ2HWJ5.js → tailscale-DZU4WM3E.js} +3 -3
  90. package/dist/{template-hash-A7FNHTB7.js → template-hash-6ITI3WC4.js} +2 -2
  91. package/dist/up-4SCIUIMG.js +19 -0
  92. package/dist/{update-ANE5ZM7F.js → update-RIQYUPVN.js} +6 -6
  93. package/dist/{update-check-UV55CBEP.js → update-check-4TIJKVGD.js} +3 -3
  94. package/dist/{version-notify-FXSEMXWW.js → version-notify-UXSHBZ35.js} +21 -22
  95. package/dist/{volute-config-D2XVS2YI.js → volute-config-V7UFFBG3.js} +1 -1
  96. package/dist/web-assets/assets/index-C-eYso8Y.js +75 -0
  97. package/dist/web-assets/assets/index-CCv_fSte.css +1 -0
  98. package/dist/web-assets/index.html +2 -2
  99. package/drizzle/0006_channels.sql +17 -0
  100. package/drizzle/0007_drop_conversation_name_title.sql +11 -0
  101. package/drizzle/0008_performance_indexes.sql +6 -0
  102. package/drizzle/meta/0006_snapshot.json +7 -0
  103. package/drizzle/meta/0007_snapshot.json +7 -0
  104. package/drizzle/meta/_journal.json +21 -0
  105. package/package.json +5 -5
  106. package/templates/_base/home/.config/routes.json +2 -2
  107. package/templates/_base/home/VOLUTE.md +1 -2
  108. package/templates/_base/src/lib/format-prefix.ts +1 -7
  109. package/templates/claude/.init/.config/routes.json +2 -2
  110. package/templates/codex/.init/.config/routes.json +2 -2
  111. package/templates/pi/.init/.config/routes.json +2 -2
  112. package/dist/delivery-manager-H5ZVBMCQ.js +0 -31
  113. package/dist/down-74VXM45A.js +0 -17
  114. package/dist/message-delivery-GRC4W6P7.js +0 -41
  115. package/dist/mind-manager-HFLB5653.js +0 -31
  116. package/dist/mind-service-X2CAA6W6.js +0 -37
  117. package/dist/scheduler-Y7O4CJXL.js +0 -31
  118. package/dist/sleep-manager-7KFK3USC.js +0 -35
  119. package/dist/spirit-ZFRDXMG7.js +0 -23
  120. package/dist/system-chat-IDPHYHY4.js +0 -35
  121. package/dist/up-77ICEDEW.js +0 -19
  122. package/dist/web-assets/assets/index-BhxWKvbB.css +0 -1
  123. package/dist/web-assets/assets/index-CHVKJ9II.js +0 -75
@@ -1,24 +1,24 @@
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
  exec,
7
7
  gitExec
8
- } from "./chunk-AN2W47GW.js";
8
+ } from "./chunk-PJ4IPTIN.js";
9
9
  import {
10
10
  readGlobalConfig,
11
11
  writeGlobalConfig
12
- } from "./chunk-BMZQYACC.js";
12
+ } from "./chunk-NUX47Y2V.js";
13
13
  import {
14
14
  getDb,
15
15
  mindDir,
16
16
  readRegistry,
17
17
  voluteHome
18
- } from "./chunk-BDYXIWA5.js";
18
+ } from "./chunk-75AJ54GM.js";
19
19
  import {
20
20
  sharedSkills
21
- } from "./chunk-5N7Y5WAM.js";
21
+ } from "./chunk-7PTQGPJY.js";
22
22
 
23
23
  // packages/daemon/src/lib/skills.ts
24
24
  import { createHash } from "crypto";
@@ -46,7 +46,7 @@ async function initDefaultSkills() {
46
46
  const config = readGlobalConfig();
47
47
  let extensionSkills = [];
48
48
  try {
49
- const { getExtensionStandardSkills } = await import("./extensions-XDDFY72A.js");
49
+ const { getExtensionStandardSkills } = await import("./extensions-OOSFVH7U.js");
50
50
  extensionSkills = getExtensionStandardSkills();
51
51
  } catch (err) {
52
52
  logger_default.warn("failed to load extension standard skills during init", logger_default.errorData(err));
@@ -206,7 +206,7 @@ async function installSkill(_mindName, dir, skillId) {
206
206
  const npmInstalled = [];
207
207
  const skillMdPath = join(sourceDir, "SKILL.md");
208
208
  if (existsSync(skillMdPath)) {
209
- const { npmDependencies } = parseSkillMd(readFileSync(skillMdPath, "utf-8"));
209
+ const { npmDependencies, hooks, bin } = parseSkillMd(readFileSync(skillMdPath, "utf-8"));
210
210
  if (npmDependencies.length > 0) {
211
211
  try {
212
212
  await exec("npm", ["install", ...npmDependencies], { cwd: dir });
@@ -219,9 +219,6 @@ async function installSkill(_mindName, dir, skillId) {
219
219
  );
220
220
  }
221
221
  }
222
- }
223
- if (existsSync(skillMdPath)) {
224
- const { hooks, bin } = parseSkillMd(readFileSync(skillMdPath, "utf-8"));
225
222
  installHookShims(dir, skillId, hooks);
226
223
  if (bin) installBinShim(dir, skillId, bin);
227
224
  }
@@ -506,11 +503,16 @@ function listFilesRecursive(dir, prefix = "") {
506
503
  }
507
504
  return results;
508
505
  }
506
+ var _skillsRoot = null;
509
507
  function findSkillsRoot() {
508
+ if (_skillsRoot) return _skillsRoot;
510
509
  let dir = dirname(new URL(import.meta.url).pathname);
511
510
  for (let i = 0; i < 5; i++) {
512
511
  const candidate = resolve(dir, "skills");
513
- if (existsSync(candidate) && hasSkillSubdir(candidate)) return candidate;
512
+ if (existsSync(candidate) && hasSkillSubdir(candidate)) {
513
+ _skillsRoot = candidate;
514
+ return _skillsRoot;
515
+ }
514
516
  dir = dirname(dir);
515
517
  }
516
518
  throw new Error("Skills directory not found");
@@ -1,24 +1,25 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  subscribe
4
- } from "./chunk-CORXD635.js";
4
+ } from "./chunk-B35VNNSS.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-75AJ54GM.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-7PTQGPJY.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,
@@ -3,9 +3,9 @@ import {
3
3
  composeTemplate,
4
4
  findTemplatesRoot,
5
5
  listFiles
6
- } from "./chunk-AOB6GVRM.js";
6
+ } from "./chunk-46DYYHN6.js";
7
7
 
8
- // packages/daemon/src/lib/template-hash.ts
8
+ // packages/daemon/src/lib/template/template-hash.ts
9
9
  import { createHash } from "crypto";
10
10
  import { existsSync, readFileSync, rmSync } from "fs";
11
11
  import { resolve } from "path";
@@ -2,9 +2,9 @@
2
2
  import {
3
3
  getBaseName,
4
4
  validateMindName
5
- } from "./chunk-BDYXIWA5.js";
5
+ } from "./chunk-75AJ54GM.js";
6
6
 
7
- // packages/daemon/src/lib/isolation.ts
7
+ // packages/daemon/src/lib/mind/isolation.ts
8
8
  import { execFileSync } from "child_process";
9
9
  function isIsolationEnabled() {
10
10
  return process.env.VOLUTE_ISOLATION === "user";
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // packages/daemon/src/lib/slugify.ts
3
+ // packages/daemon/src/lib/util/slugify.ts
4
4
  function slugify(text) {
5
5
  return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
6
6
  }
@@ -1,11 +1,11 @@
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
  readGlobalConfig,
7
7
  writeGlobalConfig
8
- } from "./chunk-BMZQYACC.js";
8
+ } from "./chunk-NUX47Y2V.js";
9
9
 
10
10
  // packages/daemon/src/lib/ai-service.ts
11
11
  import { complete, getEnvApiKey, getModel, getModels, getProviders } from "@mariozechner/pi-ai";
@@ -5,13 +5,13 @@ import {
5
5
  pollHealthDown,
6
6
  readDaemonConfig,
7
7
  stopService
8
- } from "./chunk-2TGZJFAT.js";
8
+ } from "./chunk-DJT5Y4UF.js";
9
9
  import {
10
10
  command
11
11
  } from "./chunk-TXSA4Q3V.js";
12
12
  import {
13
13
  voluteSystemDir
14
- } from "./chunk-BDYXIWA5.js";
14
+ } from "./chunk-75AJ54GM.js";
15
15
 
16
16
  // src/commands/down.ts
17
17
  import { existsSync, readFileSync, unlinkSync } 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-75AJ54GM.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,