volute 0.6.0 → 0.7.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.
package/dist/daemon.js CHANGED
@@ -6,18 +6,37 @@ import {
6
6
  initAgentManager,
7
7
  loadJsonMap,
8
8
  saveJsonMap
9
- } from "./chunk-G6ZNGLUX.js";
9
+ } from "./chunk-62X577Y7.js";
10
10
  import {
11
11
  checkForUpdate,
12
12
  checkForUpdateCached,
13
13
  getCurrentVersion
14
- } from "./chunk-AOKAQGO4.js";
14
+ } from "./chunk-NKXULRSW.js";
15
15
  import {
16
16
  collectPart
17
17
  } from "./chunk-B3R6L2GW.js";
18
18
  import {
19
19
  CHANNELS
20
20
  } from "./chunk-ZZOOTYXK.js";
21
+ import {
22
+ agentMessages,
23
+ approveUser,
24
+ conversationParticipants,
25
+ conversations,
26
+ createUser,
27
+ deleteAgentUser,
28
+ getDb,
29
+ getOrCreateAgentUser,
30
+ getUser,
31
+ getUserByUsername,
32
+ listPendingUsers,
33
+ listUsers,
34
+ listUsersByType,
35
+ messages,
36
+ sessions,
37
+ users,
38
+ verifyUser
39
+ } from "./chunk-7ACDT3P2.js";
21
40
  import {
22
41
  readVoluteConfig,
23
42
  writeVoluteConfig
@@ -25,7 +44,10 @@ import {
25
44
  import {
26
45
  loadMergedEnv
27
46
  } from "./chunk-H7AMDUIA.js";
28
- import "./chunk-BX7KI4S3.js";
47
+ import {
48
+ slugify,
49
+ writeChannelEntry
50
+ } from "./chunk-BX7KI4S3.js";
29
51
  import {
30
52
  applyIsolation
31
53
  } from "./chunk-W76KWE23.js";
@@ -47,15 +69,13 @@ import {
47
69
  setVariantRunning,
48
70
  voluteHome
49
71
  } from "./chunk-UWHWAPGO.js";
50
- import {
51
- __export
52
- } from "./chunk-K3NQKI34.js";
72
+ import "./chunk-K3NQKI34.js";
53
73
 
54
74
  // src/daemon.ts
55
75
  import { randomBytes } from "crypto";
56
76
  import { mkdirSync as mkdirSync2, readFileSync as readFileSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
57
77
  import { homedir } from "os";
58
- import { resolve as resolve10 } from "path";
78
+ import { resolve as resolve9 } from "path";
59
79
  import { format } from "util";
60
80
 
61
81
  // src/lib/connector-manager.ts
@@ -295,19 +315,19 @@ var ConnectorManager = class {
295
315
  const stopKey = `${agentName}:${type}`;
296
316
  this.stopping.add(stopKey);
297
317
  agentMap.delete(type);
298
- await new Promise((resolve11) => {
299
- tracked.child.on("exit", () => resolve11());
318
+ await new Promise((resolve10) => {
319
+ tracked.child.on("exit", () => resolve10());
300
320
  try {
301
321
  tracked.child.kill("SIGTERM");
302
322
  } catch {
303
- resolve11();
323
+ resolve10();
304
324
  }
305
325
  setTimeout(() => {
306
326
  try {
307
327
  tracked.child.kill("SIGKILL");
308
328
  } catch {
309
329
  }
310
- resolve11();
330
+ resolve10();
311
331
  }, 5e3);
312
332
  });
313
333
  this.stopping.delete(stopKey);
@@ -705,245 +725,9 @@ function getTokenBudget() {
705
725
 
706
726
  // src/web/middleware/auth.ts
707
727
  import { timingSafeEqual } from "crypto";
708
- import { eq as eq2, lt } from "drizzle-orm";
728
+ import { eq, lt } from "drizzle-orm";
709
729
  import { getCookie } from "hono/cookie";
710
730
  import { createMiddleware } from "hono/factory";
711
-
712
- // src/lib/auth.ts
713
- import { compareSync, hashSync } from "bcryptjs";
714
- import { and, count, eq } from "drizzle-orm";
715
-
716
- // src/lib/db.ts
717
- import { chmodSync, existsSync as existsSync3 } from "fs";
718
- import { dirname as dirname2, resolve as resolve4 } from "path";
719
- import { fileURLToPath } from "url";
720
- import { drizzle } from "drizzle-orm/libsql";
721
- import { migrate } from "drizzle-orm/libsql/migrator";
722
-
723
- // src/lib/schema.ts
724
- var schema_exports = {};
725
- __export(schema_exports, {
726
- agentMessages: () => agentMessages,
727
- conversationParticipants: () => conversationParticipants,
728
- conversations: () => conversations,
729
- messages: () => messages,
730
- sessions: () => sessions,
731
- users: () => users
732
- });
733
- import { sql } from "drizzle-orm";
734
- import { index, integer, sqliteTable, text, uniqueIndex } from "drizzle-orm/sqlite-core";
735
- var users = sqliteTable("users", {
736
- id: integer("id").primaryKey({ autoIncrement: true }),
737
- username: text("username").unique().notNull(),
738
- password_hash: text("password_hash").notNull(),
739
- role: text("role").notNull().default("pending"),
740
- user_type: text("user_type").notNull().default("human"),
741
- created_at: text("created_at").notNull().default(sql`(datetime('now'))`)
742
- });
743
- var conversations = sqliteTable(
744
- "conversations",
745
- {
746
- id: text("id").primaryKey(),
747
- agent_name: text("agent_name").notNull(),
748
- channel: text("channel").notNull(),
749
- user_id: integer("user_id").references(() => users.id),
750
- title: text("title"),
751
- created_at: text("created_at").notNull().default(sql`(datetime('now'))`),
752
- updated_at: text("updated_at").notNull().default(sql`(datetime('now'))`)
753
- },
754
- (table) => [
755
- index("idx_conversations_agent_name").on(table.agent_name),
756
- index("idx_conversations_user_id").on(table.user_id),
757
- index("idx_conversations_updated_at").on(table.updated_at)
758
- ]
759
- );
760
- var agentMessages = sqliteTable(
761
- "agent_messages",
762
- {
763
- id: integer("id").primaryKey({ autoIncrement: true }),
764
- agent: text("agent").notNull(),
765
- channel: text("channel").notNull(),
766
- role: text("role").notNull(),
767
- sender: text("sender"),
768
- content: text("content").notNull(),
769
- created_at: text("created_at").notNull().default(sql`(datetime('now'))`)
770
- },
771
- (table) => [
772
- index("idx_agent_messages_agent").on(table.agent),
773
- index("idx_agent_messages_channel").on(table.agent, table.channel)
774
- ]
775
- );
776
- var conversationParticipants = sqliteTable(
777
- "conversation_participants",
778
- {
779
- conversation_id: text("conversation_id").notNull().references(() => conversations.id, { onDelete: "cascade" }),
780
- user_id: integer("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
781
- role: text("role").notNull().default("member"),
782
- joined_at: text("joined_at").notNull().default(sql`(datetime('now'))`)
783
- },
784
- (table) => [
785
- uniqueIndex("idx_cp_unique").on(table.conversation_id, table.user_id),
786
- index("idx_cp_user_id").on(table.user_id)
787
- ]
788
- );
789
- var sessions = sqliteTable("sessions", {
790
- id: text("id").primaryKey(),
791
- userId: integer("user_id").references(() => users.id, { onDelete: "cascade" }).notNull(),
792
- createdAt: integer("created_at").notNull()
793
- });
794
- var messages = sqliteTable(
795
- "messages",
796
- {
797
- id: integer("id").primaryKey({ autoIncrement: true }),
798
- conversation_id: text("conversation_id").notNull().references(() => conversations.id, { onDelete: "cascade" }),
799
- role: text("role").notNull(),
800
- sender_name: text("sender_name"),
801
- content: text("content").notNull(),
802
- created_at: text("created_at").notNull().default(sql`(datetime('now'))`)
803
- },
804
- (table) => [index("idx_messages_conversation_id").on(table.conversation_id)]
805
- );
806
-
807
- // src/lib/db.ts
808
- var __dirname = dirname2(fileURLToPath(import.meta.url));
809
- var migrationsFolder = existsSync3(resolve4(__dirname, "../drizzle")) ? resolve4(__dirname, "../drizzle") : resolve4(__dirname, "../../drizzle");
810
- var db = null;
811
- async function getDb() {
812
- if (db) return db;
813
- const dbPath = process.env.VOLUTE_DB_PATH || resolve4(voluteHome(), "volute.db");
814
- db = drizzle({ connection: { url: `file:${dbPath}` }, schema: schema_exports });
815
- await migrate(db, { migrationsFolder });
816
- try {
817
- chmodSync(dbPath, 384);
818
- } catch (err) {
819
- console.error(
820
- `[volute] WARNING: Failed to restrict database file permissions on ${dbPath}:`,
821
- err
822
- );
823
- }
824
- return db;
825
- }
826
-
827
- // src/lib/auth.ts
828
- async function createUser(username, password) {
829
- const db2 = await getDb();
830
- const hash = hashSync(password, 10);
831
- const [{ value }] = await db2.select({ value: count() }).from(users).where(eq(users.user_type, "human"));
832
- const role = value === 0 ? "admin" : "pending";
833
- const [result] = await db2.insert(users).values({ username, password_hash: hash, role }).returning({
834
- id: users.id,
835
- username: users.username,
836
- role: users.role,
837
- user_type: users.user_type,
838
- created_at: users.created_at
839
- });
840
- return result;
841
- }
842
- async function verifyUser(username, password) {
843
- const db2 = await getDb();
844
- const row = await db2.select().from(users).where(eq(users.username, username)).get();
845
- if (!row) return null;
846
- if (row.user_type === "agent") return null;
847
- if (!compareSync(password, row.password_hash)) return null;
848
- const { password_hash: _, ...user } = row;
849
- return user;
850
- }
851
- async function getUser(id) {
852
- const db2 = await getDb();
853
- const row = await db2.select({
854
- id: users.id,
855
- username: users.username,
856
- role: users.role,
857
- user_type: users.user_type,
858
- created_at: users.created_at
859
- }).from(users).where(eq(users.id, id)).get();
860
- return row ?? null;
861
- }
862
- async function getUserByUsername(username) {
863
- const db2 = await getDb();
864
- const row = await db2.select({
865
- id: users.id,
866
- username: users.username,
867
- role: users.role,
868
- user_type: users.user_type,
869
- created_at: users.created_at
870
- }).from(users).where(eq(users.username, username)).get();
871
- return row ?? null;
872
- }
873
- async function listUsers() {
874
- const db2 = await getDb();
875
- return db2.select({
876
- id: users.id,
877
- username: users.username,
878
- role: users.role,
879
- user_type: users.user_type,
880
- created_at: users.created_at
881
- }).from(users).orderBy(users.created_at).all();
882
- }
883
- async function listPendingUsers() {
884
- const db2 = await getDb();
885
- return db2.select({
886
- id: users.id,
887
- username: users.username,
888
- role: users.role,
889
- user_type: users.user_type,
890
- created_at: users.created_at
891
- }).from(users).where(eq(users.role, "pending")).orderBy(users.created_at).all();
892
- }
893
- async function listUsersByType(userType) {
894
- const db2 = await getDb();
895
- return db2.select({
896
- id: users.id,
897
- username: users.username,
898
- role: users.role,
899
- user_type: users.user_type,
900
- created_at: users.created_at
901
- }).from(users).where(eq(users.user_type, userType)).orderBy(users.created_at).all();
902
- }
903
- async function getOrCreateAgentUser(agentName) {
904
- const db2 = await getDb();
905
- const existing = await db2.select({
906
- id: users.id,
907
- username: users.username,
908
- role: users.role,
909
- user_type: users.user_type,
910
- created_at: users.created_at
911
- }).from(users).where(and(eq(users.username, agentName), eq(users.user_type, "agent"))).get();
912
- if (existing) return existing;
913
- try {
914
- const [result] = await db2.insert(users).values({
915
- username: agentName,
916
- password_hash: "!agent",
917
- role: "agent",
918
- user_type: "agent"
919
- }).returning({
920
- id: users.id,
921
- username: users.username,
922
- role: users.role,
923
- user_type: users.user_type,
924
- created_at: users.created_at
925
- });
926
- return result;
927
- } catch (err) {
928
- if (err instanceof Error && err.message.includes("UNIQUE constraint")) {
929
- const retried = await db2.select({
930
- id: users.id,
931
- username: users.username,
932
- role: users.role,
933
- user_type: users.user_type,
934
- created_at: users.created_at
935
- }).from(users).where(and(eq(users.username, agentName), eq(users.user_type, "agent"))).get();
936
- if (retried) return retried;
937
- }
938
- throw err;
939
- }
940
- }
941
- async function approveUser(id) {
942
- const db2 = await getDb();
943
- await db2.update(users).set({ role: "user" }).where(and(eq(users.id, id), eq(users.role, "pending")));
944
- }
945
-
946
- // src/web/middleware/auth.ts
947
731
  function isValidDaemonToken(token) {
948
732
  const expected = process.env.VOLUTE_DAEMON_TOKEN;
949
733
  if (!expected || token.length !== expected.length) return false;
@@ -951,29 +735,29 @@ function isValidDaemonToken(token) {
951
735
  }
952
736
  var SESSION_MAX_AGE = 864e5;
953
737
  async function createSession(userId) {
954
- const db2 = await getDb();
738
+ const db = await getDb();
955
739
  const sessionId = crypto.randomUUID();
956
- await db2.insert(sessions).values({ id: sessionId, userId, createdAt: Date.now() });
740
+ await db.insert(sessions).values({ id: sessionId, userId, createdAt: Date.now() });
957
741
  return sessionId;
958
742
  }
959
743
  async function deleteSession(sessionId) {
960
- const db2 = await getDb();
961
- await db2.delete(sessions).where(eq2(sessions.id, sessionId));
744
+ const db = await getDb();
745
+ await db.delete(sessions).where(eq(sessions.id, sessionId));
962
746
  }
963
747
  async function getSessionUserId(sessionId) {
964
- const db2 = await getDb();
965
- const row = await db2.select().from(sessions).where(eq2(sessions.id, sessionId)).get();
748
+ const db = await getDb();
749
+ const row = await db.select().from(sessions).where(eq(sessions.id, sessionId)).get();
966
750
  if (!row) return void 0;
967
751
  if (Date.now() - row.createdAt > SESSION_MAX_AGE) {
968
- await db2.delete(sessions).where(eq2(sessions.id, sessionId));
752
+ await db.delete(sessions).where(eq(sessions.id, sessionId));
969
753
  return void 0;
970
754
  }
971
755
  return row.userId;
972
756
  }
973
757
  async function cleanExpiredSessions() {
974
- const db2 = await getDb();
758
+ const db = await getDb();
975
759
  const cutoff = Date.now() - SESSION_MAX_AGE;
976
- await db2.delete(sessions).where(lt(sessions.createdAt, cutoff));
760
+ await db.delete(sessions).where(lt(sessions.createdAt, cutoff));
977
761
  }
978
762
  var requireAdmin = createMiddleware(async (c, next) => {
979
763
  const user = c.get("user");
@@ -1004,9 +788,9 @@ var authMiddleware = createMiddleware(async (c, next) => {
1004
788
  });
1005
789
 
1006
790
  // src/web/server.ts
1007
- import { existsSync as existsSync7 } from "fs";
791
+ import { existsSync as existsSync6 } from "fs";
1008
792
  import { readFile as readFile2, stat } from "fs/promises";
1009
- import { dirname as dirname3, extname, resolve as resolve9 } from "path";
793
+ import { dirname as dirname2, extname, resolve as resolve8 } from "path";
1010
794
  import { serve } from "@hono/node-server";
1011
795
 
1012
796
  // src/lib/log-buffer.ts
@@ -1060,9 +844,9 @@ import { csrf } from "hono/csrf";
1060
844
  import { HTTPException } from "hono/http-exception";
1061
845
 
1062
846
  // src/web/routes/agents.ts
1063
- import { existsSync as existsSync4, readFileSync as readFileSync3, rmSync } from "fs";
1064
- import { resolve as resolve5 } from "path";
1065
- import { and as and2, desc, eq as eq3 } from "drizzle-orm";
847
+ import { existsSync as existsSync3, readFileSync as readFileSync3, rmSync } from "fs";
848
+ import { resolve as resolve4 } from "path";
849
+ import { and, desc, eq as eq2 } from "drizzle-orm";
1066
850
  import { Hono } from "hono";
1067
851
  import { stream } from "hono/streaming";
1068
852
 
@@ -1175,7 +959,7 @@ function getTypingMap() {
1175
959
  // src/web/routes/agents.ts
1176
960
  function getDaemonPort() {
1177
961
  try {
1178
- const data = JSON.parse(readFileSync3(resolve5(voluteHome(), "daemon.json"), "utf-8"));
962
+ const data = JSON.parse(readFileSync3(resolve4(voluteHome(), "daemon.json"), "utf-8"));
1179
963
  return data.port;
1180
964
  } catch {
1181
965
  return void 0;
@@ -1224,7 +1008,7 @@ var app = new Hono().get("/", async (c) => {
1224
1008
  const name = c.req.param("name");
1225
1009
  const entry = findAgent(name);
1226
1010
  if (!entry) return c.json({ error: "Agent not found" }, 404);
1227
- if (!existsSync4(agentDir(name))) return c.json({ error: "Agent directory missing" }, 404);
1011
+ if (!existsSync3(agentDir(name))) return c.json({ error: "Agent directory missing" }, 404);
1228
1012
  const { status, channels } = await getAgentStatus(name, entry.port);
1229
1013
  const variants = readVariants(name);
1230
1014
  const manager = getAgentManager();
@@ -1250,7 +1034,7 @@ var app = new Hono().get("/", async (c) => {
1250
1034
  if (!variant) return c.json({ error: `Unknown variant: ${variantName}` }, 404);
1251
1035
  } else {
1252
1036
  const dir = agentDir(baseName);
1253
- if (!existsSync4(dir)) return c.json({ error: "Agent directory missing" }, 404);
1037
+ if (!existsSync3(dir)) return c.json({ error: "Agent directory missing" }, 404);
1254
1038
  }
1255
1039
  const manager = getAgentManager();
1256
1040
  if (manager.isRunning(name)) {
@@ -1285,7 +1069,7 @@ var app = new Hono().get("/", async (c) => {
1285
1069
  if (!variant) return c.json({ error: `Unknown variant: ${variantName}` }, 404);
1286
1070
  } else {
1287
1071
  const dir = agentDir(baseName);
1288
- if (!existsSync4(dir)) return c.json({ error: "Agent directory missing" }, 404);
1072
+ if (!existsSync3(dir)) return c.json({ error: "Agent directory missing" }, 404);
1289
1073
  }
1290
1074
  const manager = getAgentManager();
1291
1075
  const connectorManager = getConnectorManager();
@@ -1353,7 +1137,8 @@ var app = new Hono().get("/", async (c) => {
1353
1137
  }
1354
1138
  removeAllVariants(name);
1355
1139
  removeAgent(name);
1356
- if (force && existsSync4(dir)) {
1140
+ await deleteAgentUser(name);
1141
+ if (force && existsSync3(dir)) {
1357
1142
  rmSync(dir, { recursive: true, force: true });
1358
1143
  }
1359
1144
  return c.json({ ok: true });
@@ -1379,10 +1164,10 @@ var app = new Hono().get("/", async (c) => {
1379
1164
  console.error(`[daemon] failed to parse message body for ${baseName}:`, err);
1380
1165
  }
1381
1166
  const channel = parsed?.channel ?? "unknown";
1382
- const db2 = await getDb();
1167
+ const db = await getDb();
1383
1168
  if (parsed) {
1384
1169
  try {
1385
- const sender = parsed.sender ?? null;
1170
+ const sender2 = parsed.sender ?? null;
1386
1171
  let content;
1387
1172
  if (typeof parsed.content === "string") {
1388
1173
  content = parsed.content;
@@ -1391,11 +1176,11 @@ var app = new Hono().get("/", async (c) => {
1391
1176
  } else {
1392
1177
  content = JSON.stringify(parsed.content);
1393
1178
  }
1394
- await db2.insert(agentMessages).values({
1179
+ await db.insert(agentMessages).values({
1395
1180
  agent: baseName,
1396
1181
  channel,
1397
1182
  role: "user",
1398
- sender,
1183
+ sender: sender2,
1399
1184
  content
1400
1185
  });
1401
1186
  } catch (err) {
@@ -1432,6 +1217,8 @@ var app = new Hono().get("/", async (c) => {
1432
1217
  });
1433
1218
  }
1434
1219
  const typingMap = getTypingMap();
1220
+ const sender = parsed?.sender ?? "";
1221
+ if (sender) typingMap.delete(channel, sender);
1435
1222
  const currentlyTyping = typingMap.get(channel).filter((s) => s !== baseName);
1436
1223
  let forwardBody = body;
1437
1224
  if (parsed && currentlyTyping.length > 0) {
@@ -1493,7 +1280,7 @@ var app = new Hono().get("/", async (c) => {
1493
1280
  const content = [textParts.join(""), ...toolParts].filter(Boolean).join("\n");
1494
1281
  if (content) {
1495
1282
  try {
1496
- await db2.insert(agentMessages).values({
1283
+ await db.insert(agentMessages).values({
1497
1284
  agent: baseName,
1498
1285
  channel,
1499
1286
  role: "assistant",
@@ -1526,9 +1313,9 @@ var app = new Hono().get("/", async (c) => {
1526
1313
  if (!body.channel || !body.content) {
1527
1314
  return c.json({ error: "channel and content required" }, 400);
1528
1315
  }
1529
- const db2 = await getDb();
1316
+ const db = await getDb();
1530
1317
  try {
1531
- await db2.insert(agentMessages).values({
1318
+ await db.insert(agentMessages).values({
1532
1319
  agent: baseName,
1533
1320
  channel: body.channel,
1534
1321
  role: "assistant",
@@ -1542,20 +1329,20 @@ var app = new Hono().get("/", async (c) => {
1542
1329
  return c.json({ ok: true });
1543
1330
  }).get("/:name/history/channels", async (c) => {
1544
1331
  const name = c.req.param("name");
1545
- const db2 = await getDb();
1546
- const rows = await db2.selectDistinct({ channel: agentMessages.channel }).from(agentMessages).where(eq3(agentMessages.agent, name));
1332
+ const db = await getDb();
1333
+ const rows = await db.selectDistinct({ channel: agentMessages.channel }).from(agentMessages).where(eq2(agentMessages.agent, name));
1547
1334
  return c.json(rows.map((r) => r.channel));
1548
1335
  }).get("/:name/history", async (c) => {
1549
1336
  const name = c.req.param("name");
1550
1337
  const channel = c.req.query("channel");
1551
1338
  const limit = Math.min(Math.max(parseInt(c.req.query("limit") ?? "50", 10) || 50, 1), 200);
1552
1339
  const offset = Math.max(parseInt(c.req.query("offset") ?? "0", 10) || 0, 0);
1553
- const db2 = await getDb();
1554
- const conditions = [eq3(agentMessages.agent, name)];
1340
+ const db = await getDb();
1341
+ const conditions = [eq2(agentMessages.agent, name)];
1555
1342
  if (channel) {
1556
- conditions.push(eq3(agentMessages.channel, channel));
1343
+ conditions.push(eq2(agentMessages.channel, channel));
1557
1344
  }
1558
- const rows = await db2.select().from(agentMessages).where(and2(...conditions)).orderBy(desc(agentMessages.created_at)).limit(limit).offset(offset);
1345
+ const rows = await db.select().from(agentMessages).where(and(...conditions)).orderBy(desc(agentMessages.created_at)).limit(limit).offset(offset);
1559
1346
  return c.json(rows);
1560
1347
  });
1561
1348
  var agents_default = app;
@@ -1703,9 +1490,9 @@ var app3 = new Hono3().get("/:name/connectors", (c) => {
1703
1490
  var connectors_default = app3;
1704
1491
 
1705
1492
  // src/web/routes/files.ts
1706
- import { existsSync as existsSync5 } from "fs";
1493
+ import { existsSync as existsSync4 } from "fs";
1707
1494
  import { readdir, readFile, writeFile } from "fs/promises";
1708
- import { resolve as resolve6 } from "path";
1495
+ import { resolve as resolve5 } from "path";
1709
1496
  import { zValidator as zValidator2 } from "@hono/zod-validator";
1710
1497
  import { Hono as Hono4 } from "hono";
1711
1498
  import { z as z2 } from "zod";
@@ -1716,8 +1503,8 @@ var app4 = new Hono4().get("/:name/files", async (c) => {
1716
1503
  const entry = findAgent(name);
1717
1504
  if (!entry) return c.json({ error: "Agent not found" }, 404);
1718
1505
  const dir = agentDir(name);
1719
- const homeDir = resolve6(dir, "home");
1720
- if (!existsSync5(homeDir)) return c.json({ error: "Home directory missing" }, 404);
1506
+ const homeDir = resolve5(dir, "home");
1507
+ if (!existsSync4(homeDir)) return c.json({ error: "Home directory missing" }, 404);
1721
1508
  const allFiles = await readdir(homeDir);
1722
1509
  const files = allFiles.filter((f) => f.endsWith(".md") && ALLOWED_FILES.has(f));
1723
1510
  return c.json(files);
@@ -1730,8 +1517,8 @@ var app4 = new Hono4().get("/:name/files", async (c) => {
1730
1517
  const entry = findAgent(name);
1731
1518
  if (!entry) return c.json({ error: "Agent not found" }, 404);
1732
1519
  const dir = agentDir(name);
1733
- const filePath = resolve6(dir, "home", filename);
1734
- if (!existsSync5(filePath)) {
1520
+ const filePath = resolve5(dir, "home", filename);
1521
+ if (!existsSync4(filePath)) {
1735
1522
  return c.json({ error: "File not found" }, 404);
1736
1523
  }
1737
1524
  const content = await readFile(filePath, "utf-8");
@@ -1745,7 +1532,7 @@ var app4 = new Hono4().get("/:name/files", async (c) => {
1745
1532
  const entry = findAgent(name);
1746
1533
  if (!entry) return c.json({ error: "Agent not found" }, 404);
1747
1534
  const dir = agentDir(name);
1748
- const filePath = resolve6(dir, "home", filename);
1535
+ const filePath = resolve5(dir, "home", filename);
1749
1536
  const { content } = c.req.valid("json");
1750
1537
  await writeFile(filePath, content);
1751
1538
  return c.json({ ok: true });
@@ -1754,8 +1541,8 @@ var files_default = app4;
1754
1541
 
1755
1542
  // src/web/routes/logs.ts
1756
1543
  import { spawn as spawn2 } from "child_process";
1757
- import { existsSync as existsSync6 } from "fs";
1758
- import { resolve as resolve7 } from "path";
1544
+ import { existsSync as existsSync5 } from "fs";
1545
+ import { resolve as resolve6 } from "path";
1759
1546
  import { Hono as Hono5 } from "hono";
1760
1547
  import { streamSSE } from "hono/streaming";
1761
1548
  var app5 = new Hono5().get("/:name/logs", async (c) => {
@@ -1763,8 +1550,8 @@ var app5 = new Hono5().get("/:name/logs", async (c) => {
1763
1550
  const entry = findAgent(name);
1764
1551
  if (!entry) return c.json({ error: "Agent not found" }, 404);
1765
1552
  const dir = agentDir(name);
1766
- const logFile = resolve7(dir, ".volute", "logs", "agent.log");
1767
- if (!existsSync6(logFile)) {
1553
+ const logFile = resolve6(dir, ".volute", "logs", "agent.log");
1554
+ if (!existsSync5(logFile)) {
1768
1555
  return c.json({ error: "No log file found" }, 404);
1769
1556
  }
1770
1557
  return streamSSE(c, async (stream2) => {
@@ -1782,9 +1569,9 @@ var app5 = new Hono5().get("/:name/logs", async (c) => {
1782
1569
  stream2.onAbort(() => {
1783
1570
  tail.kill();
1784
1571
  });
1785
- await new Promise((resolve11) => {
1786
- tail.on("exit", resolve11);
1787
- stream2.onAbort(resolve11);
1572
+ await new Promise((resolve10) => {
1573
+ tail.on("exit", resolve10);
1574
+ stream2.onAbort(resolve10);
1788
1575
  });
1789
1576
  });
1790
1577
  });
@@ -1886,10 +1673,10 @@ var app7 = new Hono7().get("/logs", async (c) => {
1886
1673
  stream2.writeSSE({ data: JSON.stringify(entry) }).catch(() => {
1887
1674
  });
1888
1675
  });
1889
- await new Promise((resolve11) => {
1676
+ await new Promise((resolve10) => {
1890
1677
  stream2.onAbort(() => {
1891
1678
  unsubscribe();
1892
- resolve11();
1679
+ resolve10();
1893
1680
  });
1894
1681
  });
1895
1682
  });
@@ -1965,7 +1752,7 @@ var variants_default = app10;
1965
1752
 
1966
1753
  // src/web/routes/volute/chat.ts
1967
1754
  import { readFileSync as readFileSync4 } from "fs";
1968
- import { resolve as resolve8 } from "path";
1755
+ import { resolve as resolve7 } from "path";
1969
1756
  import { zValidator as zValidator4 } from "@hono/zod-validator";
1970
1757
  import { Hono as Hono11 } from "hono";
1971
1758
  import { streamSSE as streamSSE3 } from "hono/streaming";
@@ -1973,11 +1760,11 @@ import { z as z4 } from "zod";
1973
1760
 
1974
1761
  // src/lib/conversations.ts
1975
1762
  import { randomUUID } from "crypto";
1976
- import { and as and3, desc as desc2, eq as eq4, inArray, isNull, sql as sql2 } from "drizzle-orm";
1763
+ import { and as and2, desc as desc2, eq as eq3, inArray, isNull, sql } from "drizzle-orm";
1977
1764
  async function createConversation(agentName, channel, opts) {
1978
- const db2 = await getDb();
1765
+ const db = await getDb();
1979
1766
  const id = randomUUID();
1980
- await db2.insert(conversations).values({
1767
+ await db.insert(conversations).values({
1981
1768
  id,
1982
1769
  agent_name: agentName,
1983
1770
  channel,
@@ -1985,7 +1772,7 @@ async function createConversation(agentName, channel, opts) {
1985
1772
  title: opts?.title ?? null
1986
1773
  });
1987
1774
  if (opts?.participantIds && opts.participantIds.length > 0) {
1988
- await db2.insert(conversationParticipants).values(
1775
+ await db.insert(conversationParticipants).values(
1989
1776
  opts.participantIds.map((uid, i) => ({
1990
1777
  conversation_id: id,
1991
1778
  user_id: uid,
@@ -2004,41 +1791,41 @@ async function createConversation(agentName, channel, opts) {
2004
1791
  };
2005
1792
  }
2006
1793
  async function getConversation(id) {
2007
- const db2 = await getDb();
2008
- const row = await db2.select().from(conversations).where(eq4(conversations.id, id)).get();
1794
+ const db = await getDb();
1795
+ const row = await db.select().from(conversations).where(eq3(conversations.id, id)).get();
2009
1796
  return row ?? null;
2010
1797
  }
2011
1798
  async function getParticipants(conversationId) {
2012
- const db2 = await getDb();
2013
- const rows = await db2.select({
1799
+ const db = await getDb();
1800
+ const rows = await db.select({
2014
1801
  userId: conversationParticipants.user_id,
2015
1802
  username: users.username,
2016
1803
  userType: users.user_type,
2017
1804
  role: conversationParticipants.role
2018
- }).from(conversationParticipants).innerJoin(users, eq4(conversationParticipants.user_id, users.id)).where(eq4(conversationParticipants.conversation_id, conversationId)).all();
1805
+ }).from(conversationParticipants).innerJoin(users, eq3(conversationParticipants.user_id, users.id)).where(eq3(conversationParticipants.conversation_id, conversationId)).all();
2019
1806
  return rows;
2020
1807
  }
2021
1808
  async function isParticipant(conversationId, userId) {
2022
- const db2 = await getDb();
2023
- const row = await db2.select({ user_id: conversationParticipants.user_id }).from(conversationParticipants).where(
2024
- and3(
2025
- eq4(conversationParticipants.conversation_id, conversationId),
2026
- eq4(conversationParticipants.user_id, userId)
1809
+ const db = await getDb();
1810
+ const row = await db.select({ user_id: conversationParticipants.user_id }).from(conversationParticipants).where(
1811
+ and2(
1812
+ eq3(conversationParticipants.conversation_id, conversationId),
1813
+ eq3(conversationParticipants.user_id, userId)
2027
1814
  )
2028
1815
  ).get();
2029
1816
  return row != null;
2030
1817
  }
2031
1818
  async function listConversationsForUser(userId) {
2032
- const db2 = await getDb();
2033
- const participantRows = await db2.select({ conversation_id: conversationParticipants.conversation_id }).from(conversationParticipants).where(eq4(conversationParticipants.user_id, userId)).all();
1819
+ const db = await getDb();
1820
+ const participantRows = await db.select({ conversation_id: conversationParticipants.conversation_id }).from(conversationParticipants).where(eq3(conversationParticipants.user_id, userId)).all();
2034
1821
  if (participantRows.length === 0) return [];
2035
1822
  const convIds = participantRows.map((r) => r.conversation_id);
2036
- return db2.select().from(conversations).where(inArray(conversations.id, convIds)).orderBy(desc2(conversations.updated_at)).all();
1823
+ return db.select().from(conversations).where(inArray(conversations.id, convIds)).orderBy(desc2(conversations.updated_at)).all();
2037
1824
  }
2038
1825
  async function isParticipantOrOwner(conversationId, userId) {
2039
1826
  if (await isParticipant(conversationId, userId)) return true;
2040
- const db2 = await getDb();
2041
- const row = await db2.select().from(conversations).where(and3(eq4(conversations.id, conversationId), eq4(conversations.user_id, userId))).get();
1827
+ const db = await getDb();
1828
+ const row = await db.select().from(conversations).where(and2(eq3(conversations.id, conversationId), eq3(conversations.user_id, userId))).get();
2042
1829
  return row != null;
2043
1830
  }
2044
1831
  async function deleteConversationForUser(id, userId) {
@@ -2047,15 +1834,15 @@ async function deleteConversationForUser(id, userId) {
2047
1834
  return true;
2048
1835
  }
2049
1836
  async function addMessage(conversationId, role, senderName, content) {
2050
- const db2 = await getDb();
1837
+ const db = await getDb();
2051
1838
  const serialized = JSON.stringify(content);
2052
- const [result] = await db2.insert(messages).values({ conversation_id: conversationId, role, sender_name: senderName, content: serialized }).returning({ id: messages.id, created_at: messages.created_at });
2053
- await db2.update(conversations).set({ updated_at: sql2`datetime('now')` }).where(eq4(conversations.id, conversationId));
1839
+ 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 });
1840
+ await db.update(conversations).set({ updated_at: sql`datetime('now')` }).where(eq3(conversations.id, conversationId));
2054
1841
  if (role === "user") {
2055
1842
  const firstText = content.find((b) => b.type === "text");
2056
1843
  const title = firstText ? firstText.text.slice(0, 80) : "";
2057
1844
  if (title) {
2058
- await db2.update(conversations).set({ title }).where(and3(eq4(conversations.id, conversationId), isNull(conversations.title)));
1845
+ await db.update(conversations).set({ title }).where(and2(eq3(conversations.id, conversationId), isNull(conversations.title)));
2059
1846
  }
2060
1847
  }
2061
1848
  return {
@@ -2068,8 +1855,8 @@ async function addMessage(conversationId, role, senderName, content) {
2068
1855
  };
2069
1856
  }
2070
1857
  async function getMessages(conversationId) {
2071
- const db2 = await getDb();
2072
- const rows = await db2.select().from(messages).where(eq4(messages.conversation_id, conversationId)).orderBy(messages.created_at).all();
1858
+ const db = await getDb();
1859
+ const rows = await db.select().from(messages).where(eq3(messages.conversation_id, conversationId)).orderBy(messages.created_at).all();
2073
1860
  return rows.map((row) => {
2074
1861
  let content;
2075
1862
  try {
@@ -2084,15 +1871,15 @@ async function getMessages(conversationId) {
2084
1871
  async function listConversationsWithParticipants(userId) {
2085
1872
  const convs = await listConversationsForUser(userId);
2086
1873
  if (convs.length === 0) return [];
2087
- const db2 = await getDb();
1874
+ const db = await getDb();
2088
1875
  const convIds = convs.map((c) => c.id);
2089
- const rows = await db2.select({
1876
+ const rows = await db.select({
2090
1877
  conversationId: conversationParticipants.conversation_id,
2091
1878
  userId: users.id,
2092
1879
  username: users.username,
2093
1880
  userType: users.user_type,
2094
1881
  role: conversationParticipants.role
2095
- }).from(conversationParticipants).innerJoin(users, eq4(conversationParticipants.user_id, users.id)).where(inArray(conversationParticipants.conversation_id, convIds));
1882
+ }).from(conversationParticipants).innerJoin(users, eq3(conversationParticipants.user_id, users.id)).where(inArray(conversationParticipants.conversation_id, convIds));
2096
1883
  const byConv = /* @__PURE__ */ new Map();
2097
1884
  for (const r of rows) {
2098
1885
  let arr = byConv.get(r.conversationId);
@@ -2110,10 +1897,10 @@ async function listConversationsWithParticipants(userId) {
2110
1897
  return convs.map((c) => ({ ...c, participants: byConv.get(c.id) ?? [] }));
2111
1898
  }
2112
1899
  async function findDMConversation(agentName, participantIds) {
2113
- const db2 = await getDb();
2114
- const agentConvs = await db2.select({ id: conversations.id }).from(conversations).where(eq4(conversations.agent_name, agentName)).all();
1900
+ const db = await getDb();
1901
+ const agentConvs = await db.select({ id: conversations.id }).from(conversations).where(eq3(conversations.agent_name, agentName)).all();
2115
1902
  for (const conv of agentConvs) {
2116
- const rows = await db2.select({ user_id: conversationParticipants.user_id }).from(conversationParticipants).where(eq4(conversationParticipants.conversation_id, conv.id)).all();
1903
+ const rows = await db.select({ user_id: conversationParticipants.user_id }).from(conversationParticipants).where(eq3(conversationParticipants.conversation_id, conv.id)).all();
2117
1904
  if (rows.length !== 2) continue;
2118
1905
  const ids = new Set(rows.map((r) => r.user_id));
2119
1906
  if (ids.has(participantIds[0]) && ids.has(participantIds[1])) {
@@ -2123,8 +1910,8 @@ async function findDMConversation(agentName, participantIds) {
2123
1910
  return null;
2124
1911
  }
2125
1912
  async function deleteConversation(id) {
2126
- const db2 = await getDb();
2127
- await db2.delete(conversations).where(eq4(conversations.id, id));
1913
+ const db = await getDb();
1914
+ await db.delete(conversations).where(eq3(conversations.id, id));
2128
1915
  }
2129
1916
 
2130
1917
  // src/web/routes/volute/chat.ts
@@ -2140,7 +1927,7 @@ var chatSchema = z4.object({
2140
1927
  ).optional()
2141
1928
  });
2142
1929
  function getDaemonUrl() {
2143
- const data = JSON.parse(readFileSync4(resolve8(voluteHome(), "daemon.json"), "utf-8"));
1930
+ const data = JSON.parse(readFileSync4(resolve7(voluteHome(), "daemon.json"), "utf-8"));
2144
1931
  return `http://${daemonLoopback()}:${data.port}`;
2145
1932
  }
2146
1933
  function daemonFetchInternal(path, body) {
@@ -2223,15 +2010,17 @@ var app11 = new Hono11().post("/:name/chat", zValidator4("json", chatSchema), as
2223
2010
  }
2224
2011
  }
2225
2012
  if (!conversationId) {
2226
- const conv = await createConversation(baseName, "volute", {
2013
+ const conv2 = await createConversation(baseName, "volute", {
2227
2014
  userId: user.id !== 0 ? user.id : void 0,
2228
2015
  title,
2229
2016
  participantIds
2230
2017
  });
2231
- conversationId = conv.id;
2018
+ conversationId = conv2.id;
2232
2019
  }
2233
2020
  }
2234
- const channel = `volute:${conversationId}`;
2021
+ const conv = await getConversation(conversationId);
2022
+ const convTitle = conv?.title;
2023
+ const channel = convTitle ? `volute:${slugify(convTitle)}` : `volute:${conversationId}`;
2235
2024
  const contentBlocks = [];
2236
2025
  if (body.message) {
2237
2026
  contentBlocks.push({ type: "text", text: body.message });
@@ -2245,13 +2034,20 @@ var app11 = new Hono11().post("/:name/chat", zValidator4("json", chatSchema), as
2245
2034
  const participants = await getParticipants(conversationId);
2246
2035
  const agentParticipants = participants.filter((p) => p.userType === "agent");
2247
2036
  const participantNames = participants.map((p) => p.username);
2248
- const { getAgentManager: getAgentManager2 } = await import("./agent-manager-JDVXU3ON.js");
2037
+ const { getAgentManager: getAgentManager2 } = await import("./agent-manager-IMZ7ZMBF.js");
2249
2038
  const manager = getAgentManager2();
2250
2039
  const runningAgents = agentParticipants.map((ap) => {
2251
2040
  const agentKey = ap.username === baseName ? name : ap.username;
2252
2041
  return manager.isRunning(agentKey) ? ap.username : null;
2253
2042
  }).filter((n) => n !== null && n !== senderName);
2254
2043
  const isDM = participants.length === 2;
2044
+ const dir = agentDir(baseName);
2045
+ writeChannelEntry(dir, channel, {
2046
+ platformId: conversationId,
2047
+ platform: "volute",
2048
+ name: convTitle ?? void 0,
2049
+ type: isDM ? "dm" : "group"
2050
+ });
2255
2051
  const typingMap = getTypingMap();
2256
2052
  const currentlyTyping = typingMap.get(channel);
2257
2053
  const payload = JSON.stringify({
@@ -2548,20 +2344,20 @@ async function startServer({
2548
2344
  hostname = "127.0.0.1"
2549
2345
  }) {
2550
2346
  let assetsDir = "";
2551
- let searchDir = dirname3(new URL(import.meta.url).pathname);
2347
+ let searchDir = dirname2(new URL(import.meta.url).pathname);
2552
2348
  for (let i = 0; i < 5; i++) {
2553
- const candidate = resolve9(searchDir, "dist", "web-assets");
2554
- if (existsSync7(candidate)) {
2349
+ const candidate = resolve8(searchDir, "dist", "web-assets");
2350
+ if (existsSync6(candidate)) {
2555
2351
  assetsDir = candidate;
2556
2352
  break;
2557
2353
  }
2558
- searchDir = dirname3(searchDir);
2354
+ searchDir = dirname2(searchDir);
2559
2355
  }
2560
2356
  if (assetsDir) {
2561
2357
  app_default.get("*", async (c) => {
2562
2358
  const urlPath = new URL(c.req.url).pathname;
2563
2359
  if (urlPath.startsWith("/api/")) return c.notFound();
2564
- const filePath = resolve9(assetsDir, urlPath.slice(1));
2360
+ const filePath = resolve8(assetsDir, urlPath.slice(1));
2565
2361
  if (!filePath.startsWith(assetsDir)) return c.text("Forbidden", 403);
2566
2362
  const s = await stat(filePath).catch(() => null);
2567
2363
  if (s?.isFile()) {
@@ -2570,7 +2366,7 @@ async function startServer({
2570
2366
  const body = await readFile2(filePath);
2571
2367
  return c.body(body, 200, { "Content-Type": mime });
2572
2368
  }
2573
- const indexPath = resolve9(assetsDir, "index.html");
2369
+ const indexPath = resolve8(assetsDir, "index.html");
2574
2370
  const indexStat = await stat(indexPath).catch(() => null);
2575
2371
  if (indexStat?.isFile()) {
2576
2372
  const body = await readFile2(indexPath, "utf-8");
@@ -2580,10 +2376,10 @@ async function startServer({
2580
2376
  });
2581
2377
  }
2582
2378
  const server = serve({ fetch: app_default.fetch, port, hostname });
2583
- await new Promise((resolve11, reject) => {
2379
+ await new Promise((resolve10, reject) => {
2584
2380
  server.on("listening", () => {
2585
2381
  logger_default.info("Volute UI running", { hostname, port });
2586
- resolve11();
2382
+ resolve10();
2587
2383
  });
2588
2384
  server.on("error", (err) => {
2589
2385
  reject(err);
@@ -2594,14 +2390,14 @@ async function startServer({
2594
2390
 
2595
2391
  // src/daemon.ts
2596
2392
  if (!process.env.VOLUTE_HOME) {
2597
- process.env.VOLUTE_HOME = resolve10(homedir(), ".volute");
2393
+ process.env.VOLUTE_HOME = resolve9(homedir(), ".volute");
2598
2394
  }
2599
2395
  async function startDaemon(opts) {
2600
2396
  const { port, hostname } = opts;
2601
2397
  const myPid = String(process.pid);
2602
2398
  const home = voluteHome();
2603
2399
  if (!opts.foreground) {
2604
- const log2 = new RotatingLog(resolve10(home, "daemon.log"));
2400
+ const log2 = new RotatingLog(resolve9(home, "daemon.log"));
2605
2401
  const write2 = (...args) => log2.write(`${format(...args)}
2606
2402
  `);
2607
2403
  console.log = write2;
@@ -2609,8 +2405,8 @@ async function startDaemon(opts) {
2609
2405
  console.warn = write2;
2610
2406
  console.info = write2;
2611
2407
  }
2612
- const DAEMON_PID_PATH = resolve10(home, "daemon.pid");
2613
- const DAEMON_JSON_PATH = resolve10(home, "daemon.json");
2408
+ const DAEMON_PID_PATH = resolve9(home, "daemon.pid");
2409
+ const DAEMON_JSON_PATH = resolve9(home, "daemon.json");
2614
2410
  mkdirSync2(home, { recursive: true });
2615
2411
  const token = process.env.VOLUTE_DAEMON_TOKEN || randomBytes(32).toString("hex");
2616
2412
  process.env.VOLUTE_DAEMON_TOKEN = token;