volute 0.8.3 → 0.10.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 (45) hide show
  1. package/dist/{agent-YORVRB6I.js → agent-ECRX44DB.js} +10 -10
  2. package/dist/{agent-manager-CMMH5KQQ.js → agent-manager-MRHHKAB6.js} +2 -2
  3. package/dist/api-client-YPKOZP2O.js +10 -0
  4. package/dist/{channel-RDGHBFSI.js → channel-2WHBRDTD.js} +66 -81
  5. package/dist/chunk-4RQBJWQX.js +17 -0
  6. package/dist/{chunk-YNNK4QN2.js → chunk-FYQGANL6.js} +40 -2
  7. package/dist/chunk-R3VB7NF5.js +205 -0
  8. package/dist/{chunk-23L3MKEV.js → chunk-STOEJOJO.js} +18 -4
  9. package/dist/cli.js +14 -14
  10. package/dist/{connector-ZP6MEFF4.js → connector-L2HBLZBW.js} +37 -20
  11. package/dist/create-VBZZNJOG.js +38 -0
  12. package/dist/{daemon-client-54J3EIZD.js → daemon-client-P44NU3KU.js} +1 -1
  13. package/dist/daemon-restart-QCLR6ZZV.js +61 -0
  14. package/dist/daemon.js +1732 -214
  15. package/dist/delete-BOTVU4YO.js +35 -0
  16. package/dist/{down-4DGRZRJU.js → down-4LIQG3CE.js} +3 -1
  17. package/dist/{env-KMNYGVZ2.js → env-CGORIKVF.js} +86 -37
  18. package/dist/{history-PXJVYLVY.js → history-NI5QP27M.js} +11 -8
  19. package/dist/import-2BZUWT23.js +21 -0
  20. package/dist/logs-APWVWGNX.js +77 -0
  21. package/dist/{package-2S7APQBC.js → package-ERGXEDAF.js} +1 -1
  22. package/dist/{restart-KVH3TK5N.js → restart-CCYM3MEC.js} +10 -4
  23. package/dist/{schedule-HCUCBNQI.js → schedule-E4MFGYSA.js} +24 -8
  24. package/dist/{send-BNC2S5BY.js → send-X6OQGSD6.js} +36 -28
  25. package/dist/{start-QU73YTJW.js → start-6YRS6FF6.js} +7 -2
  26. package/dist/{status-Q6ZQJXNI.js → status-SIMKH3ZE.js} +8 -3
  27. package/dist/{stop-N7U5N6A7.js → stop-UQSNF4CG.js} +7 -2
  28. package/dist/{up-RZJMSVQS.js → up-MNNPCMFF.js} +1 -1
  29. package/dist/upgrade-RSE4CZNE.js +55 -0
  30. package/dist/variant-7IZF6OWO.js +215 -0
  31. package/package.json +1 -1
  32. package/dist/chunk-ECPQXRLB.js +0 -264
  33. package/dist/chunk-NETNFBA5.js +0 -28
  34. package/dist/chunk-XUA3JUFK.js +0 -121
  35. package/dist/create-HGJHLABX.js +0 -96
  36. package/dist/daemon-restart-IMNCBWFV.js +0 -28
  37. package/dist/delete-45TGQC4N.js +0 -67
  38. package/dist/import-CNEDF3TD.js +0 -532
  39. package/dist/logs-TZB3MTLZ.js +0 -37
  40. package/dist/upgrade-CZF6PN7Y.js +0 -224
  41. package/dist/variant-RKXPN5DH.js +0 -476
  42. package/dist/{chunk-6RDCTVQK.js → chunk-4NAGJV3I.js} +0 -0
  43. package/dist/{chunk-W6TMWYU3.js → chunk-WV4W7BAT.js} +3 -3
  44. package/dist/{service-56CY4S6Z.js → service-OW35VZ5G.js} +3 -3
  45. package/dist/{setup-7SPMWF2O.js → setup-ABMZK6LS.js} +3 -3
package/dist/daemon.js CHANGED
@@ -6,75 +6,81 @@ import {
6
6
  initAgentManager,
7
7
  loadJsonMap,
8
8
  saveJsonMap
9
- } from "./chunk-W6TMWYU3.js";
9
+ } from "./chunk-WV4W7BAT.js";
10
10
  import {
11
11
  checkForUpdate,
12
12
  checkForUpdateCached,
13
13
  getCurrentVersion
14
14
  } from "./chunk-RT6Y7AR3.js";
15
15
  import {
16
- CHANNELS
17
- } from "./chunk-LIPPXNIE.js";
18
- import {
19
- agentMessages,
20
- approveUser,
21
- conversationParticipants,
22
- conversations,
23
- createUser,
16
+ applyIsolation,
17
+ chownAgentDir,
18
+ createAgentUser,
24
19
  deleteAgentUser,
25
- getDb,
26
- getOrCreateAgentUser,
27
- getUser,
28
- getUserByUsername,
29
- listPendingUsers,
30
- listUsers,
31
- listUsersByType,
32
- messages,
33
- sessions,
34
- users,
35
- verifyUser
36
- } from "./chunk-ECPQXRLB.js";
20
+ ensureVoluteGroup,
21
+ getAgentUserIds,
22
+ isIsolationEnabled
23
+ } from "./chunk-IQXBMFZG.js";
24
+ import {
25
+ exec,
26
+ resolveVoluteBin
27
+ } from "./chunk-5C5JWR2L.js";
37
28
  import {
29
+ findOpenClawSession,
30
+ importOpenClawConnectors,
31
+ importPiSession,
32
+ parseNameFromIdentity,
38
33
  readVoluteConfig,
39
34
  writeVoluteConfig
40
- } from "./chunk-NETNFBA5.js";
35
+ } from "./chunk-R3VB7NF5.js";
41
36
  import {
42
- loadMergedEnv
37
+ agentEnvPath,
38
+ loadMergedEnv,
39
+ readEnv,
40
+ sharedEnvPath,
41
+ writeEnv
43
42
  } from "./chunk-QF22MYDJ.js";
43
+ import {
44
+ CHANNELS,
45
+ getChannelDriver
46
+ } from "./chunk-LIPPXNIE.js";
47
+ import "./chunk-D424ZQGI.js";
44
48
  import {
45
49
  slugify,
46
50
  writeChannelEntry
47
51
  } from "./chunk-N6MLQ26B.js";
48
52
  import {
49
- applyIsolation
50
- } from "./chunk-IQXBMFZG.js";
51
- import {
52
- resolveVoluteBin
53
- } from "./chunk-5C5JWR2L.js";
54
- import {
53
+ addAgent,
54
+ addVariant,
55
55
  agentDir,
56
56
  checkHealth,
57
57
  daemonLoopback,
58
+ ensureVoluteHome,
58
59
  findAgent,
59
60
  findVariant,
60
61
  getAllRunningVariants,
62
+ nextPort,
61
63
  readRegistry,
62
64
  readVariants,
63
65
  removeAgent,
64
66
  removeAllVariants,
67
+ removeVariant,
65
68
  setAgentRunning,
66
69
  setVariantRunning,
67
70
  stateDir,
71
+ validateAgentName,
68
72
  validateBranchName,
69
73
  voluteHome
70
74
  } from "./chunk-DP2DX4WV.js";
71
- import "./chunk-K3NQKI34.js";
75
+ import {
76
+ __export
77
+ } from "./chunk-K3NQKI34.js";
72
78
 
73
79
  // src/daemon.ts
74
80
  import { randomBytes } from "crypto";
75
- import { mkdirSync as mkdirSync3, readFileSync as readFileSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
76
- import { homedir } from "os";
77
- import { resolve as resolve10 } from "path";
81
+ import { mkdirSync as mkdirSync8, readFileSync as readFileSync9, unlinkSync as unlinkSync2, writeFileSync as writeFileSync7 } from "fs";
82
+ import { homedir as homedir2 } from "os";
83
+ import { resolve as resolve16 } from "path";
78
84
  import { format } from "util";
79
85
 
80
86
  // src/lib/connector-manager.ts
@@ -314,19 +320,19 @@ var ConnectorManager = class {
314
320
  const stopKey = `${agentName}:${type}`;
315
321
  this.stopping.add(stopKey);
316
322
  agentMap.delete(type);
317
- await new Promise((resolve11) => {
318
- tracked.child.on("exit", () => resolve11());
323
+ await new Promise((resolve17) => {
324
+ tracked.child.on("exit", () => resolve17());
319
325
  try {
320
326
  tracked.child.kill("SIGTERM");
321
327
  } catch {
322
- resolve11();
328
+ resolve17();
323
329
  }
324
330
  setTimeout(() => {
325
331
  try {
326
332
  tracked.child.kill("SIGKILL");
327
333
  } catch {
328
334
  }
329
- resolve11();
335
+ resolve17();
330
336
  }, 5e3);
331
337
  });
332
338
  this.stopping.delete(stopKey);
@@ -734,9 +740,248 @@ function getTokenBudget() {
734
740
 
735
741
  // src/web/middleware/auth.ts
736
742
  import { timingSafeEqual } from "crypto";
737
- import { eq, lt } from "drizzle-orm";
743
+ import { eq as eq2, lt } from "drizzle-orm";
738
744
  import { getCookie } from "hono/cookie";
739
745
  import { createMiddleware } from "hono/factory";
746
+
747
+ // src/lib/auth.ts
748
+ import { compareSync, hashSync } from "bcryptjs";
749
+ import { and, count, eq } from "drizzle-orm";
750
+
751
+ // src/lib/db.ts
752
+ import { chmodSync, existsSync as existsSync4 } from "fs";
753
+ import { dirname as dirname2, resolve as resolve5 } from "path";
754
+ import { fileURLToPath } from "url";
755
+ import { drizzle } from "drizzle-orm/libsql";
756
+ import { migrate } from "drizzle-orm/libsql/migrator";
757
+
758
+ // src/lib/schema.ts
759
+ var schema_exports = {};
760
+ __export(schema_exports, {
761
+ agentMessages: () => agentMessages,
762
+ conversationParticipants: () => conversationParticipants,
763
+ conversations: () => conversations,
764
+ messages: () => messages,
765
+ sessions: () => sessions,
766
+ users: () => users
767
+ });
768
+ import { sql } from "drizzle-orm";
769
+ import { index, integer, sqliteTable, text, uniqueIndex } from "drizzle-orm/sqlite-core";
770
+ var users = sqliteTable("users", {
771
+ id: integer("id").primaryKey({ autoIncrement: true }),
772
+ username: text("username").unique().notNull(),
773
+ password_hash: text("password_hash").notNull(),
774
+ role: text("role").notNull().default("pending"),
775
+ user_type: text("user_type").notNull().default("human"),
776
+ created_at: text("created_at").notNull().default(sql`(datetime('now'))`)
777
+ });
778
+ var conversations = sqliteTable(
779
+ "conversations",
780
+ {
781
+ id: text("id").primaryKey(),
782
+ agent_name: text("agent_name").notNull(),
783
+ channel: text("channel").notNull(),
784
+ user_id: integer("user_id").references(() => users.id),
785
+ title: text("title"),
786
+ created_at: text("created_at").notNull().default(sql`(datetime('now'))`),
787
+ updated_at: text("updated_at").notNull().default(sql`(datetime('now'))`)
788
+ },
789
+ (table) => [
790
+ index("idx_conversations_agent_name").on(table.agent_name),
791
+ index("idx_conversations_user_id").on(table.user_id),
792
+ index("idx_conversations_updated_at").on(table.updated_at)
793
+ ]
794
+ );
795
+ var agentMessages = sqliteTable(
796
+ "agent_messages",
797
+ {
798
+ id: integer("id").primaryKey({ autoIncrement: true }),
799
+ agent: text("agent").notNull(),
800
+ channel: text("channel").notNull(),
801
+ sender: text("sender"),
802
+ content: text("content").notNull(),
803
+ created_at: text("created_at").notNull().default(sql`(datetime('now'))`)
804
+ },
805
+ (table) => [
806
+ index("idx_agent_messages_agent").on(table.agent),
807
+ index("idx_agent_messages_channel").on(table.agent, table.channel)
808
+ ]
809
+ );
810
+ var conversationParticipants = sqliteTable(
811
+ "conversation_participants",
812
+ {
813
+ conversation_id: text("conversation_id").notNull().references(() => conversations.id, { onDelete: "cascade" }),
814
+ user_id: integer("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
815
+ role: text("role").notNull().default("member"),
816
+ joined_at: text("joined_at").notNull().default(sql`(datetime('now'))`)
817
+ },
818
+ (table) => [
819
+ uniqueIndex("idx_cp_unique").on(table.conversation_id, table.user_id),
820
+ index("idx_cp_user_id").on(table.user_id)
821
+ ]
822
+ );
823
+ var sessions = sqliteTable("sessions", {
824
+ id: text("id").primaryKey(),
825
+ userId: integer("user_id").references(() => users.id, { onDelete: "cascade" }).notNull(),
826
+ createdAt: integer("created_at").notNull()
827
+ });
828
+ var messages = sqliteTable(
829
+ "messages",
830
+ {
831
+ id: integer("id").primaryKey({ autoIncrement: true }),
832
+ conversation_id: text("conversation_id").notNull().references(() => conversations.id, { onDelete: "cascade" }),
833
+ role: text("role").notNull(),
834
+ sender_name: text("sender_name"),
835
+ content: text("content").notNull(),
836
+ created_at: text("created_at").notNull().default(sql`(datetime('now'))`)
837
+ },
838
+ (table) => [index("idx_messages_conversation_id").on(table.conversation_id)]
839
+ );
840
+
841
+ // src/lib/db.ts
842
+ var __dirname = dirname2(fileURLToPath(import.meta.url));
843
+ var migrationsFolder = existsSync4(resolve5(__dirname, "../drizzle")) ? resolve5(__dirname, "../drizzle") : resolve5(__dirname, "../../drizzle");
844
+ var db = null;
845
+ async function getDb() {
846
+ if (db) return db;
847
+ const dbPath = process.env.VOLUTE_DB_PATH || resolve5(voluteHome(), "volute.db");
848
+ db = drizzle({ connection: { url: `file:${dbPath}` }, schema: schema_exports });
849
+ await migrate(db, { migrationsFolder });
850
+ try {
851
+ chmodSync(dbPath, 384);
852
+ } catch (err) {
853
+ console.error(
854
+ `[volute] WARNING: Failed to restrict database file permissions on ${dbPath}:`,
855
+ err
856
+ );
857
+ }
858
+ return db;
859
+ }
860
+
861
+ // src/lib/auth.ts
862
+ async function createUser(username, password) {
863
+ const db2 = await getDb();
864
+ const hash = hashSync(password, 10);
865
+ const [{ value }] = await db2.select({ value: count() }).from(users).where(eq(users.user_type, "human"));
866
+ const role = value === 0 ? "admin" : "pending";
867
+ const [result] = await db2.insert(users).values({ username, password_hash: hash, role }).returning({
868
+ id: users.id,
869
+ username: users.username,
870
+ role: users.role,
871
+ user_type: users.user_type,
872
+ created_at: users.created_at
873
+ });
874
+ return result;
875
+ }
876
+ async function verifyUser(username, password) {
877
+ const db2 = await getDb();
878
+ const row = await db2.select().from(users).where(eq(users.username, username)).get();
879
+ if (!row) return null;
880
+ if (row.user_type === "agent") return null;
881
+ if (!compareSync(password, row.password_hash)) return null;
882
+ const { password_hash: _, ...user } = row;
883
+ return user;
884
+ }
885
+ async function getUser(id) {
886
+ const db2 = await getDb();
887
+ const row = await db2.select({
888
+ id: users.id,
889
+ username: users.username,
890
+ role: users.role,
891
+ user_type: users.user_type,
892
+ created_at: users.created_at
893
+ }).from(users).where(eq(users.id, id)).get();
894
+ return row ?? null;
895
+ }
896
+ async function getUserByUsername(username) {
897
+ const db2 = await getDb();
898
+ const row = await db2.select({
899
+ id: users.id,
900
+ username: users.username,
901
+ role: users.role,
902
+ user_type: users.user_type,
903
+ created_at: users.created_at
904
+ }).from(users).where(eq(users.username, username)).get();
905
+ return row ?? null;
906
+ }
907
+ async function listUsers() {
908
+ const db2 = await getDb();
909
+ return db2.select({
910
+ id: users.id,
911
+ username: users.username,
912
+ role: users.role,
913
+ user_type: users.user_type,
914
+ created_at: users.created_at
915
+ }).from(users).orderBy(users.created_at).all();
916
+ }
917
+ async function listPendingUsers() {
918
+ const db2 = await getDb();
919
+ return db2.select({
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
+ }).from(users).where(eq(users.role, "pending")).orderBy(users.created_at).all();
926
+ }
927
+ async function listUsersByType(userType) {
928
+ const db2 = await getDb();
929
+ return 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(eq(users.user_type, userType)).orderBy(users.created_at).all();
936
+ }
937
+ async function getOrCreateAgentUser(agentName) {
938
+ const db2 = await getDb();
939
+ const existing = await db2.select({
940
+ id: users.id,
941
+ username: users.username,
942
+ role: users.role,
943
+ user_type: users.user_type,
944
+ created_at: users.created_at
945
+ }).from(users).where(and(eq(users.username, agentName), eq(users.user_type, "agent"))).get();
946
+ if (existing) return existing;
947
+ try {
948
+ const [result] = await db2.insert(users).values({
949
+ username: agentName,
950
+ password_hash: "!agent",
951
+ role: "agent",
952
+ user_type: "agent"
953
+ }).returning({
954
+ id: users.id,
955
+ username: users.username,
956
+ role: users.role,
957
+ user_type: users.user_type,
958
+ created_at: users.created_at
959
+ });
960
+ return result;
961
+ } catch (err) {
962
+ if (err instanceof Error && err.message.includes("UNIQUE constraint")) {
963
+ const retried = await db2.select({
964
+ id: users.id,
965
+ username: users.username,
966
+ role: users.role,
967
+ user_type: users.user_type,
968
+ created_at: users.created_at
969
+ }).from(users).where(and(eq(users.username, agentName), eq(users.user_type, "agent"))).get();
970
+ if (retried) return retried;
971
+ }
972
+ throw err;
973
+ }
974
+ }
975
+ async function deleteAgentUser2(agentName) {
976
+ const db2 = await getDb();
977
+ await db2.delete(users).where(and(eq(users.username, agentName), eq(users.user_type, "agent")));
978
+ }
979
+ async function approveUser(id) {
980
+ const db2 = await getDb();
981
+ await db2.update(users).set({ role: "user" }).where(and(eq(users.id, id), eq(users.role, "pending")));
982
+ }
983
+
984
+ // src/web/middleware/auth.ts
740
985
  function isValidDaemonToken(token) {
741
986
  const expected = process.env.VOLUTE_DAEMON_TOKEN;
742
987
  if (!expected || token.length !== expected.length) return false;
@@ -744,29 +989,29 @@ function isValidDaemonToken(token) {
744
989
  }
745
990
  var SESSION_MAX_AGE = 864e5;
746
991
  async function createSession(userId) {
747
- const db = await getDb();
992
+ const db2 = await getDb();
748
993
  const sessionId = crypto.randomUUID();
749
- await db.insert(sessions).values({ id: sessionId, userId, createdAt: Date.now() });
994
+ await db2.insert(sessions).values({ id: sessionId, userId, createdAt: Date.now() });
750
995
  return sessionId;
751
996
  }
752
997
  async function deleteSession(sessionId) {
753
- const db = await getDb();
754
- await db.delete(sessions).where(eq(sessions.id, sessionId));
998
+ const db2 = await getDb();
999
+ await db2.delete(sessions).where(eq2(sessions.id, sessionId));
755
1000
  }
756
1001
  async function getSessionUserId(sessionId) {
757
- const db = await getDb();
758
- const row = await db.select().from(sessions).where(eq(sessions.id, sessionId)).get();
1002
+ const db2 = await getDb();
1003
+ const row = await db2.select().from(sessions).where(eq2(sessions.id, sessionId)).get();
759
1004
  if (!row) return void 0;
760
1005
  if (Date.now() - row.createdAt > SESSION_MAX_AGE) {
761
- await db.delete(sessions).where(eq(sessions.id, sessionId));
1006
+ await db2.delete(sessions).where(eq2(sessions.id, sessionId));
762
1007
  return void 0;
763
1008
  }
764
1009
  return row.userId;
765
1010
  }
766
1011
  async function cleanExpiredSessions() {
767
- const db = await getDb();
1012
+ const db2 = await getDb();
768
1013
  const cutoff = Date.now() - SESSION_MAX_AGE;
769
- await db.delete(sessions).where(lt(sessions.createdAt, cutoff));
1014
+ await db2.delete(sessions).where(lt(sessions.createdAt, cutoff));
770
1015
  }
771
1016
  var requireAdmin = createMiddleware(async (c, next) => {
772
1017
  const user = c.get("user");
@@ -797,9 +1042,9 @@ var authMiddleware = createMiddleware(async (c, next) => {
797
1042
  });
798
1043
 
799
1044
  // src/web/server.ts
800
- import { existsSync as existsSync7 } from "fs";
1045
+ import { existsSync as existsSync10 } from "fs";
801
1046
  import { readFile as readFile2, stat } from "fs/promises";
802
- import { dirname as dirname2, extname, resolve as resolve9 } from "path";
1047
+ import { dirname as dirname4, extname, resolve as resolve15 } from "path";
803
1048
  import { serve } from "@hono/node-server";
804
1049
 
805
1050
  // src/lib/log-buffer.ts
@@ -847,19 +1092,382 @@ var log = {
847
1092
  var logger_default = log;
848
1093
 
849
1094
  // src/web/app.ts
850
- import { Hono as Hono14 } from "hono";
1095
+ import { Hono as Hono16 } from "hono";
851
1096
  import { bodyLimit } from "hono/body-limit";
852
1097
  import { csrf } from "hono/csrf";
853
1098
  import { HTTPException } from "hono/http-exception";
854
1099
 
855
1100
  // src/web/routes/agents.ts
856
- import { execFile } from "child_process";
857
- import { existsSync as existsSync4, readFileSync as readFileSync3, rmSync } from "fs";
858
- import { resolve as resolve5 } from "path";
859
- import { promisify } from "util";
860
- import { and, desc, eq as eq2 } from "drizzle-orm";
1101
+ import {
1102
+ cpSync as cpSync2,
1103
+ existsSync as existsSync6,
1104
+ mkdirSync as mkdirSync5,
1105
+ readdirSync as readdirSync4,
1106
+ readFileSync as readFileSync6,
1107
+ rmSync as rmSync2,
1108
+ writeFileSync as writeFileSync5
1109
+ } from "fs";
1110
+ import { resolve as resolve9 } from "path";
1111
+ import { and as and2, desc, eq as eq3 } from "drizzle-orm";
861
1112
  import { Hono } from "hono";
862
1113
 
1114
+ // src/lib/consolidate.ts
1115
+ import { readdirSync as readdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
1116
+ import { resolve as resolve6 } from "path";
1117
+ async function consolidateMemory(agentDir2) {
1118
+ const soulPath = resolve6(agentDir2, "home/SOUL.md");
1119
+ const memoryPath = resolve6(agentDir2, "home/MEMORY.md");
1120
+ const memoryDir = resolve6(agentDir2, "home/memory");
1121
+ const soul = readFileSync3(soulPath, "utf-8");
1122
+ const logs = [];
1123
+ try {
1124
+ const files = readdirSync2(memoryDir).filter((f) => /^\d{4}-\d{2}-\d{2}\.md$/.test(f)).sort();
1125
+ for (const filename of files) {
1126
+ const date = filename.replace(".md", "");
1127
+ const content2 = readFileSync3(resolve6(memoryDir, filename), "utf-8").trim();
1128
+ if (content2) {
1129
+ logs.push(`### ${date}
1130
+
1131
+ ${content2}`);
1132
+ }
1133
+ }
1134
+ } catch {
1135
+ }
1136
+ if (logs.length === 0) {
1137
+ console.log("No daily logs found.");
1138
+ return;
1139
+ }
1140
+ const apiKey = process.env.ANTHROPIC_API_KEY;
1141
+ if (!apiKey) {
1142
+ console.error("ANTHROPIC_API_KEY not set, skipping memory consolidation.");
1143
+ return;
1144
+ }
1145
+ console.log("Consolidating memory from daily logs...");
1146
+ const userMessage = [
1147
+ "You have daily logs from a previous environment but no long-term memory file yet.",
1148
+ "Please review the daily logs below and produce consolidated MEMORY.md content.",
1149
+ "Keep it concise and organized by topic. Output ONLY the markdown content for MEMORY.md, nothing else.",
1150
+ "",
1151
+ "## Daily logs",
1152
+ "",
1153
+ logs.join("\n\n")
1154
+ ].join("\n");
1155
+ const res = await fetch("https://api.anthropic.com/v1/messages", {
1156
+ method: "POST",
1157
+ headers: {
1158
+ "Content-Type": "application/json",
1159
+ "x-api-key": apiKey,
1160
+ "anthropic-version": "2023-06-01"
1161
+ },
1162
+ body: JSON.stringify({
1163
+ model: "claude-sonnet-4-20250514",
1164
+ max_tokens: 4096,
1165
+ system: soul,
1166
+ messages: [{ role: "user", content: userMessage }]
1167
+ })
1168
+ });
1169
+ if (!res.ok) {
1170
+ const body = await res.text();
1171
+ console.error(`Anthropic API error (${res.status}): ${body}`);
1172
+ return;
1173
+ }
1174
+ const data = await res.json();
1175
+ const content = data.content.filter((b) => b.type === "text" && b.text).map((b) => b.text).join("").trim();
1176
+ if (content) {
1177
+ writeFileSync2(memoryPath, `${content}
1178
+ `);
1179
+ console.log("MEMORY.md created successfully.");
1180
+ } else {
1181
+ console.warn("Warning: No content produced.");
1182
+ }
1183
+ }
1184
+
1185
+ // src/lib/convert-session.ts
1186
+ import { randomUUID } from "crypto";
1187
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
1188
+ import { homedir } from "os";
1189
+ import { resolve as resolve7 } from "path";
1190
+ function convertSession(opts) {
1191
+ const lines = readFileSync4(opts.sessionPath, "utf-8").trim().split("\n");
1192
+ const sessionId = randomUUID();
1193
+ const idMap = /* @__PURE__ */ new Map();
1194
+ const messages2 = [];
1195
+ for (const line of lines) {
1196
+ const event = JSON.parse(line);
1197
+ if (event.type === "message" && event.message) {
1198
+ messages2.push(event);
1199
+ }
1200
+ }
1201
+ const sdkEvents = [];
1202
+ let lastSdkUuid = null;
1203
+ for (let i = 0; i < messages2.length; i++) {
1204
+ const event = messages2[i];
1205
+ const msg = event.message;
1206
+ if (msg.role === "user") {
1207
+ const uuid = randomUUID();
1208
+ idMap.set(event.id, uuid);
1209
+ const parentUuid = event.parentId ? idMap.get(event.parentId) ?? null : null;
1210
+ const sdkEvent = {
1211
+ uuid,
1212
+ parentUuid,
1213
+ sessionId,
1214
+ timestamp: event.timestamp,
1215
+ cwd: opts.projectDir,
1216
+ version: "0.1.0",
1217
+ gitBranch: "main",
1218
+ isSidechain: false,
1219
+ userType: "external",
1220
+ type: "user",
1221
+ message: {
1222
+ role: "user",
1223
+ content: msg.content
1224
+ }
1225
+ };
1226
+ sdkEvents.push(JSON.stringify(sdkEvent));
1227
+ lastSdkUuid = uuid;
1228
+ } else if (msg.role === "assistant") {
1229
+ const content = convertAssistantContent(msg.content);
1230
+ if (content.length === 0) continue;
1231
+ const uuid = randomUUID();
1232
+ idMap.set(event.id, uuid);
1233
+ const parentUuid = event.parentId ? idMap.get(event.parentId) ?? null : null;
1234
+ const stopReason = mapStopReason(msg.stopReason);
1235
+ const sdkEvent = {
1236
+ uuid,
1237
+ parentUuid,
1238
+ sessionId,
1239
+ timestamp: event.timestamp,
1240
+ cwd: opts.projectDir,
1241
+ version: "0.1.0",
1242
+ gitBranch: "main",
1243
+ isSidechain: false,
1244
+ userType: "external",
1245
+ type: "assistant",
1246
+ requestId: `req_imported_${randomUUID()}`,
1247
+ message: {
1248
+ role: "assistant",
1249
+ content,
1250
+ type: "message",
1251
+ id: `msg_imported_${randomUUID()}`,
1252
+ model: mapModel(msg.model),
1253
+ stop_reason: stopReason,
1254
+ stop_sequence: null,
1255
+ usage: mapUsage(msg.usage)
1256
+ }
1257
+ };
1258
+ sdkEvents.push(JSON.stringify(sdkEvent));
1259
+ lastSdkUuid = uuid;
1260
+ } else if (msg.role === "toolResult") {
1261
+ const toolResults = [];
1262
+ let lastToolResultId = event.id;
1263
+ let lastTimestamp = event.timestamp;
1264
+ let j = i;
1265
+ while (j < messages2.length && messages2[j].message.role === "toolResult") {
1266
+ const tr = messages2[j];
1267
+ const trMsg = tr.message;
1268
+ lastToolResultId = tr.id;
1269
+ lastTimestamp = tr.timestamp;
1270
+ toolResults.push({
1271
+ type: "tool_result",
1272
+ tool_use_id: trMsg.toolCallId,
1273
+ content: trMsg.content,
1274
+ ...trMsg.isError ? { is_error: true } : {}
1275
+ });
1276
+ j++;
1277
+ }
1278
+ i = j - 1;
1279
+ const uuid = randomUUID();
1280
+ idMap.set(lastToolResultId, uuid);
1281
+ const parentUuid = event.parentId ? idMap.get(event.parentId) ?? null : lastSdkUuid;
1282
+ const sdkEvent = {
1283
+ uuid,
1284
+ parentUuid,
1285
+ sessionId,
1286
+ timestamp: lastTimestamp,
1287
+ cwd: opts.projectDir,
1288
+ version: "0.1.0",
1289
+ gitBranch: "main",
1290
+ isSidechain: false,
1291
+ userType: "external",
1292
+ type: "user",
1293
+ sourceToolAssistantUUID: lastSdkUuid ?? void 0,
1294
+ toolUseResult: "imported",
1295
+ message: {
1296
+ role: "user",
1297
+ content: toolResults
1298
+ }
1299
+ };
1300
+ sdkEvents.push(JSON.stringify(sdkEvent));
1301
+ lastSdkUuid = uuid;
1302
+ }
1303
+ }
1304
+ const projectId = opts.projectDir.replace(/\//g, "-");
1305
+ const sdkDir = resolve7(homedir(), ".claude", "projects", projectId);
1306
+ mkdirSync3(sdkDir, { recursive: true });
1307
+ const sdkPath = resolve7(sdkDir, `${sessionId}.jsonl`);
1308
+ writeFileSync3(sdkPath, `${sdkEvents.join("\n")}
1309
+ `);
1310
+ console.log(`Converted ${sdkEvents.length} messages \u2192 ${sdkPath}`);
1311
+ return sessionId;
1312
+ }
1313
+ var MODEL_MAP = {
1314
+ "claude-opus-4-5": "claude-opus-4-5-20251101",
1315
+ "claude-sonnet-4": "claude-sonnet-4-20250514"
1316
+ };
1317
+ function mapModel(model) {
1318
+ if (!model) return "claude-opus-4-5-20251101";
1319
+ return MODEL_MAP[model] ?? model;
1320
+ }
1321
+ function mapStopReason(stopReason) {
1322
+ if (!stopReason) return "end_turn";
1323
+ const map = {
1324
+ toolUse: "tool_use",
1325
+ endTurn: "end_turn",
1326
+ stop: "end_turn",
1327
+ maxTokens: "max_tokens"
1328
+ };
1329
+ return map[stopReason] ?? stopReason;
1330
+ }
1331
+ function mapUsage(usage) {
1332
+ if (!usage) return { input_tokens: 0, output_tokens: 0 };
1333
+ return {
1334
+ input_tokens: usage.input ?? usage.input_tokens ?? 0,
1335
+ output_tokens: usage.output ?? usage.output_tokens ?? 0,
1336
+ cache_read_input_tokens: usage.cacheRead ?? usage.cache_read_input_tokens ?? 0,
1337
+ cache_creation_input_tokens: usage.cacheWrite ?? usage.cache_creation_input_tokens ?? 0
1338
+ };
1339
+ }
1340
+ function convertAssistantContent(content) {
1341
+ const result = [];
1342
+ for (const block of content) {
1343
+ if (block.type === "thinking") {
1344
+ } else if (block.type === "toolCall") {
1345
+ result.push({
1346
+ type: "tool_use",
1347
+ id: block.id,
1348
+ name: block.name,
1349
+ input: block.arguments ?? block.input ?? {},
1350
+ caller: { type: "direct" }
1351
+ });
1352
+ } else {
1353
+ result.push(block);
1354
+ }
1355
+ }
1356
+ return result;
1357
+ }
1358
+
1359
+ // src/lib/template.ts
1360
+ import {
1361
+ cpSync,
1362
+ existsSync as existsSync5,
1363
+ mkdirSync as mkdirSync4,
1364
+ readdirSync as readdirSync3,
1365
+ readFileSync as readFileSync5,
1366
+ renameSync,
1367
+ rmSync,
1368
+ statSync,
1369
+ writeFileSync as writeFileSync4
1370
+ } from "fs";
1371
+ import { tmpdir } from "os";
1372
+ import { dirname as dirname3, join, relative, resolve as resolve8 } from "path";
1373
+ function findTemplatesRoot() {
1374
+ let dir = dirname3(new URL(import.meta.url).pathname);
1375
+ for (let i = 0; i < 5; i++) {
1376
+ const candidate = resolve8(dir, "templates");
1377
+ if (existsSync5(resolve8(candidate, "_base"))) return candidate;
1378
+ dir = dirname3(dir);
1379
+ }
1380
+ console.error(
1381
+ "Templates directory not found. Searched up from:",
1382
+ dirname3(new URL(import.meta.url).pathname)
1383
+ );
1384
+ process.exit(1);
1385
+ }
1386
+ function composeTemplate(templatesRoot, templateName) {
1387
+ const baseDir = resolve8(templatesRoot, "_base");
1388
+ const templateDir = resolve8(templatesRoot, templateName);
1389
+ if (!existsSync5(baseDir)) {
1390
+ console.error("Base template not found:", baseDir);
1391
+ process.exit(1);
1392
+ }
1393
+ if (!existsSync5(templateDir)) {
1394
+ console.error(`Template not found: ${templateName}`);
1395
+ process.exit(1);
1396
+ }
1397
+ const composedDir = resolve8(tmpdir(), `volute-template-${Date.now()}`);
1398
+ mkdirSync4(composedDir, { recursive: true });
1399
+ cpSync(baseDir, composedDir, { recursive: true });
1400
+ for (const file of listFiles(templateDir)) {
1401
+ const src = resolve8(templateDir, file);
1402
+ const dest = resolve8(composedDir, file);
1403
+ mkdirSync4(dirname3(dest), { recursive: true });
1404
+ cpSync(src, dest);
1405
+ }
1406
+ const manifestPath = resolve8(composedDir, "volute-template.json");
1407
+ if (!existsSync5(manifestPath)) {
1408
+ rmSync(composedDir, { recursive: true, force: true });
1409
+ console.error(`Template manifest not found: ${templateName}/volute-template.json`);
1410
+ process.exit(1);
1411
+ }
1412
+ const manifest = JSON.parse(readFileSync5(manifestPath, "utf-8"));
1413
+ const skillsSrc = resolve8(composedDir, "_skills");
1414
+ if (existsSync5(skillsSrc)) {
1415
+ const skillsDest = resolve8(composedDir, manifest.skillsDir);
1416
+ mkdirSync4(skillsDest, { recursive: true });
1417
+ cpSync(skillsSrc, skillsDest, { recursive: true });
1418
+ rmSync(skillsSrc, { recursive: true, force: true });
1419
+ }
1420
+ rmSync(manifestPath);
1421
+ return { composedDir, manifest };
1422
+ }
1423
+ function copyTemplateToDir(composedDir, destDir, agentName, manifest) {
1424
+ cpSync(composedDir, destDir, { recursive: true });
1425
+ for (const [from, to] of Object.entries(manifest.rename)) {
1426
+ const fromPath = resolve8(destDir, from);
1427
+ if (existsSync5(fromPath)) {
1428
+ renameSync(fromPath, resolve8(destDir, to));
1429
+ }
1430
+ }
1431
+ for (const file of manifest.substitute) {
1432
+ const path = resolve8(destDir, file);
1433
+ if (existsSync5(path)) {
1434
+ const content = readFileSync5(path, "utf-8");
1435
+ writeFileSync4(path, content.replaceAll("{{name}}", agentName));
1436
+ }
1437
+ }
1438
+ }
1439
+ function applyInitFiles(destDir) {
1440
+ const initDir = resolve8(destDir, ".init");
1441
+ if (!existsSync5(initDir)) return;
1442
+ const homeDir = resolve8(destDir, "home");
1443
+ for (const file of listFiles(initDir)) {
1444
+ const src = resolve8(initDir, file);
1445
+ const dest = resolve8(homeDir, file);
1446
+ const parent = dirname3(dest);
1447
+ if (!existsSync5(parent)) {
1448
+ mkdirSync4(parent, { recursive: true });
1449
+ }
1450
+ cpSync(src, dest);
1451
+ }
1452
+ rmSync(initDir, { recursive: true, force: true });
1453
+ }
1454
+ function listFiles(dir) {
1455
+ const results = [];
1456
+ function walk(current) {
1457
+ for (const entry of readdirSync3(current)) {
1458
+ const full = join(current, entry);
1459
+ if (statSync(full).isDirectory()) {
1460
+ if (entry === ".git") continue;
1461
+ walk(full);
1462
+ } else {
1463
+ results.push(relative(dir, full));
1464
+ }
1465
+ }
1466
+ }
1467
+ walk(dir);
1468
+ return results;
1469
+ }
1470
+
863
1471
  // src/lib/typing.ts
864
1472
  var DEFAULT_TTL_MS = 1e4;
865
1473
  var SWEEP_INTERVAL_MS = 5e3;
@@ -928,7 +1536,6 @@ function getTypingMap() {
928
1536
  }
929
1537
 
930
1538
  // src/web/routes/agents.ts
931
- var execFileAsync = promisify(execFile);
932
1539
  async function startAgentFull(name, baseName, variantName) {
933
1540
  await getAgentManager().startAgent(name);
934
1541
  if (variantName) return;
@@ -954,7 +1561,7 @@ function extractTextContent(content) {
954
1561
  }
955
1562
  function getDaemonPort() {
956
1563
  try {
957
- const data = JSON.parse(readFileSync3(resolve5(voluteHome(), "daemon.json"), "utf-8"));
1564
+ const data = JSON.parse(readFileSync6(resolve9(voluteHome(), "daemon.json"), "utf-8"));
958
1565
  return data.port;
959
1566
  } catch (err) {
960
1567
  if (err?.code !== "ENOENT") {
@@ -993,7 +1600,247 @@ async function getAgentStatus(name, port) {
993
1600
  }
994
1601
  return { status, channels };
995
1602
  }
996
- var app = new Hono().get("/", async (c) => {
1603
+ var TEMPLATE_BRANCH = "volute/template";
1604
+ async function initTemplateBranch(projectRoot, composedDir, manifest, ids, env) {
1605
+ const templateFiles = listFiles(composedDir).filter((f) => !f.startsWith(".init/") && !f.startsWith(".init\\")).map((f) => manifest.rename[f] ?? f);
1606
+ const opts = { cwd: projectRoot, uid: ids?.uid, gid: ids?.gid, env };
1607
+ await exec("git", ["checkout", "--orphan", TEMPLATE_BRANCH], opts);
1608
+ await exec("git", ["add", "--", ...templateFiles], opts);
1609
+ await exec("git", ["commit", "-m", "template update"], opts);
1610
+ await exec("git", ["checkout", "-b", "main"], opts);
1611
+ await exec("git", ["add", "-A"], opts);
1612
+ await exec("git", ["commit", "-m", "initial commit"], opts);
1613
+ }
1614
+ async function updateTemplateBranch(projectRoot, template, agentName) {
1615
+ const tempWorktree = resolve9(projectRoot, ".variants", "_template_update");
1616
+ let branchExists = false;
1617
+ try {
1618
+ await exec("git", ["rev-parse", "--verify", TEMPLATE_BRANCH], { cwd: projectRoot });
1619
+ branchExists = true;
1620
+ } catch {
1621
+ }
1622
+ try {
1623
+ await exec("git", ["worktree", "remove", "--force", tempWorktree], { cwd: projectRoot });
1624
+ } catch {
1625
+ }
1626
+ if (existsSync6(tempWorktree)) {
1627
+ rmSync2(tempWorktree, { recursive: true, force: true });
1628
+ }
1629
+ const templatesRoot = findTemplatesRoot();
1630
+ const { composedDir, manifest } = composeTemplate(templatesRoot, template);
1631
+ try {
1632
+ if (branchExists) {
1633
+ await exec("git", ["worktree", "add", tempWorktree, TEMPLATE_BRANCH], {
1634
+ cwd: projectRoot
1635
+ });
1636
+ } else {
1637
+ await exec("git", ["worktree", "add", "--detach", tempWorktree], { cwd: projectRoot });
1638
+ await exec("git", ["checkout", "--orphan", TEMPLATE_BRANCH], { cwd: tempWorktree });
1639
+ await exec("git", ["rm", "-rf", "--cached", "."], { cwd: tempWorktree });
1640
+ await exec("git", ["clean", "-fd"], { cwd: tempWorktree });
1641
+ }
1642
+ if (branchExists) {
1643
+ await exec("git", ["rm", "-rf", "."], { cwd: tempWorktree }).catch(() => {
1644
+ });
1645
+ }
1646
+ copyTemplateToDir(composedDir, tempWorktree, agentName, manifest);
1647
+ const initDir = resolve9(tempWorktree, ".init");
1648
+ if (existsSync6(initDir)) {
1649
+ rmSync2(initDir, { recursive: true, force: true });
1650
+ }
1651
+ await exec("git", ["add", "-A"], { cwd: tempWorktree });
1652
+ try {
1653
+ await exec("git", ["diff", "--cached", "--quiet"], { cwd: tempWorktree });
1654
+ } catch {
1655
+ await exec("git", ["commit", "-m", "template update"], { cwd: tempWorktree });
1656
+ }
1657
+ } finally {
1658
+ try {
1659
+ await exec("git", ["worktree", "remove", "--force", tempWorktree], { cwd: projectRoot });
1660
+ } catch {
1661
+ }
1662
+ if (existsSync6(tempWorktree)) {
1663
+ rmSync2(tempWorktree, { recursive: true, force: true });
1664
+ }
1665
+ rmSync2(composedDir, { recursive: true, force: true });
1666
+ }
1667
+ }
1668
+ async function mergeTemplateBranch(worktreeDir) {
1669
+ try {
1670
+ await exec(
1671
+ "git",
1672
+ ["merge", TEMPLATE_BRANCH, "--allow-unrelated-histories", "-m", "merge template update"],
1673
+ { cwd: worktreeDir }
1674
+ );
1675
+ return false;
1676
+ } catch (e) {
1677
+ try {
1678
+ const status = await exec("git", ["status", "--porcelain"], { cwd: worktreeDir });
1679
+ const hasConflictMarkers = status.split("\n").some((line) => line.startsWith("UU") || line.startsWith("AA"));
1680
+ if (hasConflictMarkers) return true;
1681
+ } catch {
1682
+ }
1683
+ throw e;
1684
+ }
1685
+ }
1686
+ var app = new Hono().post("/", requireAdmin, async (c) => {
1687
+ let body;
1688
+ try {
1689
+ body = await c.req.json();
1690
+ } catch {
1691
+ return c.json({ error: "Invalid JSON" }, 400);
1692
+ }
1693
+ const { name, template = "agent-sdk" } = body;
1694
+ const nameErr = validateAgentName(name);
1695
+ if (nameErr) return c.json({ error: nameErr }, 400);
1696
+ if (findAgent(name)) return c.json({ error: `Agent already exists: ${name}` }, 409);
1697
+ ensureVoluteHome();
1698
+ const dest = agentDir(name);
1699
+ if (existsSync6(dest)) return c.json({ error: "Agent directory already exists" }, 409);
1700
+ const templatesRoot = findTemplatesRoot();
1701
+ const { composedDir, manifest } = composeTemplate(templatesRoot, template);
1702
+ try {
1703
+ copyTemplateToDir(composedDir, dest, name, manifest);
1704
+ applyInitFiles(dest);
1705
+ const port = nextPort();
1706
+ addAgent(name, port);
1707
+ ensureVoluteGroup();
1708
+ createAgentUser(name);
1709
+ chownAgentDir(dest, name);
1710
+ const ids = isIsolationEnabled() ? await getAgentUserIds(name) : void 0;
1711
+ const env = ids ? { ...process.env, HOME: dest } : void 0;
1712
+ await exec("npm", ["install"], { cwd: dest, uid: ids?.uid, gid: ids?.gid, env });
1713
+ let gitWarning;
1714
+ try {
1715
+ await exec("git", ["init"], { cwd: dest, uid: ids?.uid, gid: ids?.gid, env });
1716
+ await initTemplateBranch(dest, composedDir, manifest, ids, env);
1717
+ } catch {
1718
+ rmSync2(resolve9(dest, ".git"), { recursive: true, force: true });
1719
+ gitWarning = "Git setup failed \u2014 variants and upgrades won't be available until git is initialized.";
1720
+ }
1721
+ return c.json({
1722
+ ok: true,
1723
+ name,
1724
+ port,
1725
+ message: `Created agent: ${name} (port ${port})`,
1726
+ ...gitWarning && { warning: gitWarning }
1727
+ });
1728
+ } catch (err) {
1729
+ if (existsSync6(dest)) rmSync2(dest, { recursive: true, force: true });
1730
+ try {
1731
+ removeAgent(name);
1732
+ } catch {
1733
+ }
1734
+ return c.json({ error: err instanceof Error ? err.message : "Failed to create agent" }, 500);
1735
+ } finally {
1736
+ rmSync2(composedDir, { recursive: true, force: true });
1737
+ }
1738
+ }).post("/import", requireAdmin, async (c) => {
1739
+ let body;
1740
+ try {
1741
+ body = await c.req.json();
1742
+ } catch {
1743
+ return c.json({ error: "Invalid JSON" }, 400);
1744
+ }
1745
+ const wsDir = body.workspacePath;
1746
+ if (!wsDir || !existsSync6(resolve9(wsDir, "SOUL.md")) || !existsSync6(resolve9(wsDir, "IDENTITY.md"))) {
1747
+ return c.json({ error: "Invalid workspace: missing SOUL.md or IDENTITY.md" }, 400);
1748
+ }
1749
+ const soul = readFileSync6(resolve9(wsDir, "SOUL.md"), "utf-8");
1750
+ const identity = readFileSync6(resolve9(wsDir, "IDENTITY.md"), "utf-8");
1751
+ const userPath = resolve9(wsDir, "USER.md");
1752
+ const user = existsSync6(userPath) ? readFileSync6(userPath, "utf-8") : "";
1753
+ const name = body.name ?? parseNameFromIdentity(identity) ?? "imported-agent";
1754
+ const template = body.template ?? "agent-sdk";
1755
+ const nameErr = validateAgentName(name);
1756
+ if (nameErr) return c.json({ error: nameErr }, 400);
1757
+ if (findAgent(name)) return c.json({ error: `Agent already exists: ${name}` }, 409);
1758
+ const mergedSoul = `${soul.trimEnd()}
1759
+
1760
+ ---
1761
+
1762
+ ${identity.trimEnd()}
1763
+ `;
1764
+ const mergedMemoryExtra = user ? `
1765
+
1766
+ ---
1767
+
1768
+ ${user.trimEnd()}
1769
+ ` : "";
1770
+ ensureVoluteHome();
1771
+ const dest = agentDir(name);
1772
+ if (existsSync6(dest)) return c.json({ error: "Agent directory already exists" }, 409);
1773
+ const templatesRoot = findTemplatesRoot();
1774
+ const { composedDir, manifest } = composeTemplate(templatesRoot, template);
1775
+ try {
1776
+ copyTemplateToDir(composedDir, dest, name, manifest);
1777
+ applyInitFiles(dest);
1778
+ writeFileSync5(resolve9(dest, "home/SOUL.md"), mergedSoul);
1779
+ const wsMemoryPath = resolve9(wsDir, "MEMORY.md");
1780
+ const hasMemory = existsSync6(wsMemoryPath);
1781
+ if (hasMemory) {
1782
+ const existingMemory = readFileSync6(wsMemoryPath, "utf-8");
1783
+ writeFileSync5(
1784
+ resolve9(dest, "home/MEMORY.md"),
1785
+ `${existingMemory.trimEnd()}${mergedMemoryExtra}`
1786
+ );
1787
+ } else if (user) {
1788
+ writeFileSync5(resolve9(dest, "home/MEMORY.md"), `${user.trimEnd()}
1789
+ `);
1790
+ }
1791
+ const wsMemoryDir = resolve9(wsDir, "memory");
1792
+ let dailyLogCount = 0;
1793
+ if (existsSync6(wsMemoryDir)) {
1794
+ const destMemoryDir = resolve9(dest, "home/memory");
1795
+ const files = readdirSync4(wsMemoryDir).filter((f) => f.endsWith(".md"));
1796
+ for (const file of files) {
1797
+ cpSync2(resolve9(wsMemoryDir, file), resolve9(destMemoryDir, file));
1798
+ }
1799
+ dailyLogCount = files.length;
1800
+ }
1801
+ const port = nextPort();
1802
+ addAgent(name, port);
1803
+ ensureVoluteGroup();
1804
+ createAgentUser(name);
1805
+ chownAgentDir(dest, name);
1806
+ const ids = isIsolationEnabled() ? await getAgentUserIds(name) : void 0;
1807
+ const env = ids ? { ...process.env, HOME: dest } : void 0;
1808
+ await exec("npm", ["install"], { cwd: dest, uid: ids?.uid, gid: ids?.gid, env });
1809
+ if (!hasMemory && dailyLogCount > 0) {
1810
+ await consolidateMemory(dest);
1811
+ }
1812
+ await exec("git", ["init"], { cwd: dest, uid: ids?.uid, gid: ids?.gid, env });
1813
+ await exec("git", ["add", "-A"], { cwd: dest, uid: ids?.uid, gid: ids?.gid, env });
1814
+ await exec("git", ["commit", "-m", "import from OpenClaw"], {
1815
+ cwd: dest,
1816
+ uid: ids?.uid,
1817
+ gid: ids?.gid,
1818
+ env
1819
+ });
1820
+ const sessionFile = body.sessionPath ? resolve9(body.sessionPath) : findOpenClawSession(wsDir);
1821
+ if (sessionFile && existsSync6(sessionFile)) {
1822
+ if (template === "pi") {
1823
+ importPiSession(sessionFile, dest);
1824
+ } else if (template === "agent-sdk") {
1825
+ const sessionId = convertSession({ sessionPath: sessionFile, projectDir: dest });
1826
+ const voluteDir = resolve9(dest, ".volute");
1827
+ mkdirSync5(voluteDir, { recursive: true });
1828
+ writeFileSync5(resolve9(voluteDir, "session.json"), JSON.stringify({ sessionId }));
1829
+ }
1830
+ }
1831
+ importOpenClawConnectors(name, dest);
1832
+ return c.json({ ok: true, name, port, message: `Imported agent: ${name} (port ${port})` });
1833
+ } catch (err) {
1834
+ if (existsSync6(dest)) rmSync2(dest, { recursive: true, force: true });
1835
+ try {
1836
+ removeAgent(name);
1837
+ } catch {
1838
+ }
1839
+ return c.json({ error: err instanceof Error ? err.message : "Failed to import agent" }, 500);
1840
+ } finally {
1841
+ rmSync2(composedDir, { recursive: true, force: true });
1842
+ }
1843
+ }).get("/", async (c) => {
997
1844
  const entries = readRegistry();
998
1845
  const agents = await Promise.all(
999
1846
  entries.map(async (entry) => {
@@ -1006,7 +1853,7 @@ var app = new Hono().get("/", async (c) => {
1006
1853
  const name = c.req.param("name");
1007
1854
  const entry = findAgent(name);
1008
1855
  if (!entry) return c.json({ error: "Agent not found" }, 404);
1009
- if (!existsSync4(agentDir(name))) return c.json({ error: "Agent directory missing" }, 404);
1856
+ if (!existsSync6(agentDir(name))) return c.json({ error: "Agent directory missing" }, 404);
1010
1857
  const { status, channels } = await getAgentStatus(name, entry.port);
1011
1858
  const variants = readVariants(name);
1012
1859
  const manager = getAgentManager();
@@ -1032,7 +1879,7 @@ var app = new Hono().get("/", async (c) => {
1032
1879
  if (!variant) return c.json({ error: `Unknown variant: ${variantName}` }, 404);
1033
1880
  } else {
1034
1881
  const dir = agentDir(baseName);
1035
- if (!existsSync4(dir)) return c.json({ error: "Agent directory missing" }, 404);
1882
+ if (!existsSync6(dir)) return c.json({ error: "Agent directory missing" }, 404);
1036
1883
  }
1037
1884
  if (getAgentManager().isRunning(name)) {
1038
1885
  return c.json({ error: "Agent already running" }, 409);
@@ -1053,7 +1900,7 @@ var app = new Hono().get("/", async (c) => {
1053
1900
  if (!variant) return c.json({ error: `Unknown variant: ${variantName}` }, 404);
1054
1901
  } else {
1055
1902
  const dir = agentDir(baseName);
1056
- if (!existsSync4(dir)) return c.json({ error: "Agent directory missing" }, 404);
1903
+ if (!existsSync6(dir)) return c.json({ error: "Agent directory missing" }, 404);
1057
1904
  }
1058
1905
  let context;
1059
1906
  const contentType = c.req.header("content-type");
@@ -1081,21 +1928,58 @@ var app = new Hono().get("/", async (c) => {
1081
1928
  return c.json({ error: `Invalid variant name: ${branchErr}` }, 400);
1082
1929
  }
1083
1930
  console.error(`[daemon] merging variant for ${baseName}: ${mergeVariantName}`);
1084
- const mergeArgs = [
1085
- "variant",
1086
- "merge",
1087
- mergeVariantName,
1088
- "--agent",
1089
- baseName,
1090
- "--skip-verify"
1091
- ];
1092
- if (context.summary) mergeArgs.push("--summary", String(context.summary));
1093
- if (context.justification) mergeArgs.push("--justification", String(context.justification));
1094
- if (context.memory) mergeArgs.push("--memory", String(context.memory));
1095
- await execFileAsync("volute", mergeArgs, {
1096
- cwd: agentDir(baseName),
1097
- env: { ...process.env, VOLUTE_SUPERVISOR: "1" }
1098
- });
1931
+ const variant = findVariant(baseName, mergeVariantName);
1932
+ if (variant) {
1933
+ const projectRoot = agentDir(baseName);
1934
+ if (existsSync6(variant.path)) {
1935
+ const status = (await exec("git", ["status", "--porcelain"], { cwd: variant.path })).trim();
1936
+ if (status) {
1937
+ try {
1938
+ await exec("git", ["add", "-A"], { cwd: variant.path });
1939
+ await exec(
1940
+ "git",
1941
+ ["commit", "-m", "Auto-commit uncommitted changes before merge"],
1942
+ { cwd: variant.path }
1943
+ );
1944
+ } catch (e) {
1945
+ console.error(
1946
+ `[daemon] failed to auto-commit variant worktree for ${baseName}:`,
1947
+ e
1948
+ );
1949
+ }
1950
+ }
1951
+ }
1952
+ const mainStatus = (await exec("git", ["status", "--porcelain"], { cwd: projectRoot })).trim();
1953
+ if (mainStatus) {
1954
+ try {
1955
+ await exec("git", ["add", "-A"], { cwd: projectRoot });
1956
+ await exec("git", ["commit", "-m", "Auto-commit uncommitted changes before merge"], {
1957
+ cwd: projectRoot
1958
+ });
1959
+ } catch (e) {
1960
+ console.error(`[daemon] failed to auto-commit main worktree for ${baseName}:`, e);
1961
+ }
1962
+ }
1963
+ await exec("git", ["merge", variant.branch], { cwd: projectRoot });
1964
+ if (existsSync6(variant.path)) {
1965
+ try {
1966
+ await exec("git", ["worktree", "remove", "--force", variant.path], {
1967
+ cwd: projectRoot
1968
+ });
1969
+ } catch {
1970
+ }
1971
+ }
1972
+ try {
1973
+ await exec("git", ["branch", "-D", variant.branch], { cwd: projectRoot });
1974
+ } catch {
1975
+ }
1976
+ removeVariant(baseName, mergeVariantName);
1977
+ try {
1978
+ await exec("npm", ["install"], { cwd: projectRoot });
1979
+ } catch (e) {
1980
+ console.error(`[daemon] npm install failed after merge for ${baseName}:`, e);
1981
+ }
1982
+ }
1099
1983
  }
1100
1984
  if (context) {
1101
1985
  manager.setPendingContext(name, context);
@@ -1143,15 +2027,144 @@ var app = new Hono().get("/", async (c) => {
1143
2027
  }
1144
2028
  removeAllVariants(name);
1145
2029
  removeAgent(name);
1146
- await deleteAgentUser(name);
2030
+ await deleteAgentUser2(name);
1147
2031
  const state = stateDir(name);
1148
- if (existsSync4(state)) {
1149
- rmSync(state, { recursive: true, force: true });
2032
+ if (existsSync6(state)) {
2033
+ rmSync2(state, { recursive: true, force: true });
1150
2034
  }
1151
- if (force && existsSync4(dir)) {
1152
- rmSync(dir, { recursive: true, force: true });
2035
+ if (force && existsSync6(dir)) {
2036
+ rmSync2(dir, { recursive: true, force: true });
2037
+ deleteAgentUser(name);
1153
2038
  }
1154
2039
  return c.json({ ok: true });
2040
+ }).post("/:name/upgrade", requireAdmin, async (c) => {
2041
+ const agentName = c.req.param("name");
2042
+ const entry = findAgent(agentName);
2043
+ if (!entry) return c.json({ error: "Agent not found" }, 404);
2044
+ const dir = agentDir(agentName);
2045
+ if (!existsSync6(dir)) return c.json({ error: "Agent directory missing" }, 404);
2046
+ let body = {};
2047
+ try {
2048
+ body = await c.req.json();
2049
+ } catch {
2050
+ }
2051
+ const template = body.template ?? "agent-sdk";
2052
+ const UPGRADE_VARIANT = "upgrade";
2053
+ if (body.continue) {
2054
+ const worktreeDir2 = resolve9(dir, ".variants", UPGRADE_VARIANT);
2055
+ if (!existsSync6(worktreeDir2)) {
2056
+ return c.json({ error: "No upgrade in progress" }, 400);
2057
+ }
2058
+ const status = await exec("git", ["status", "--porcelain"], { cwd: worktreeDir2 });
2059
+ const hasConflicts2 = status.split("\n").some((line) => line.startsWith("UU") || line.startsWith("AA"));
2060
+ if (hasConflicts2) {
2061
+ return c.json({ error: "Unresolved conflicts remain" }, 409);
2062
+ }
2063
+ try {
2064
+ await exec("git", ["add", "-A"], { cwd: worktreeDir2 });
2065
+ await exec("git", ["commit", "--no-edit"], { cwd: worktreeDir2 });
2066
+ } catch (e) {
2067
+ const msg = e instanceof Error ? e.message : String(e);
2068
+ if (!msg.includes("nothing to commit")) throw e;
2069
+ }
2070
+ try {
2071
+ await exec("npm", ["install"], { cwd: worktreeDir2 });
2072
+ const variantPort = nextPort();
2073
+ addVariant(agentName, {
2074
+ name: UPGRADE_VARIANT,
2075
+ branch: UPGRADE_VARIANT,
2076
+ path: worktreeDir2,
2077
+ port: variantPort,
2078
+ created: (/* @__PURE__ */ new Date()).toISOString()
2079
+ });
2080
+ await getAgentManager().startAgent(`${agentName}@${UPGRADE_VARIANT}`);
2081
+ return c.json({
2082
+ ok: true,
2083
+ name: agentName,
2084
+ variant: UPGRADE_VARIANT,
2085
+ port: variantPort
2086
+ });
2087
+ } catch (err) {
2088
+ try {
2089
+ removeVariant(agentName, UPGRADE_VARIANT);
2090
+ } catch {
2091
+ }
2092
+ try {
2093
+ await exec("git", ["worktree", "remove", "--force", worktreeDir2], { cwd: dir });
2094
+ } catch {
2095
+ }
2096
+ try {
2097
+ await exec("git", ["branch", "-D", UPGRADE_VARIANT], { cwd: dir });
2098
+ } catch {
2099
+ }
2100
+ return c.json(
2101
+ { error: err instanceof Error ? err.message : "Failed to continue upgrade" },
2102
+ 500
2103
+ );
2104
+ }
2105
+ }
2106
+ const worktreeDir = resolve9(dir, ".variants", UPGRADE_VARIANT);
2107
+ if (existsSync6(worktreeDir)) {
2108
+ return c.json(
2109
+ { error: "Upgrade variant already exists. Use continue or delete it first." },
2110
+ 409
2111
+ );
2112
+ }
2113
+ await exec("git", ["worktree", "prune"], { cwd: dir });
2114
+ try {
2115
+ await exec("git", ["branch", "-D", UPGRADE_VARIANT], { cwd: dir });
2116
+ } catch {
2117
+ }
2118
+ await updateTemplateBranch(dir, template, agentName);
2119
+ const parentDir = resolve9(dir, ".variants");
2120
+ if (!existsSync6(parentDir)) {
2121
+ mkdirSync5(parentDir, { recursive: true });
2122
+ }
2123
+ await exec("git", ["worktree", "add", "-b", UPGRADE_VARIANT, worktreeDir], { cwd: dir });
2124
+ const hasConflicts = await mergeTemplateBranch(worktreeDir);
2125
+ if (hasConflicts) {
2126
+ return c.json({
2127
+ ok: false,
2128
+ conflicts: true,
2129
+ worktreeDir,
2130
+ message: "Merge conflicts detected. Resolve them, then run with continue."
2131
+ });
2132
+ }
2133
+ try {
2134
+ await exec("npm", ["install"], { cwd: worktreeDir });
2135
+ const variantPort = nextPort();
2136
+ addVariant(agentName, {
2137
+ name: UPGRADE_VARIANT,
2138
+ branch: UPGRADE_VARIANT,
2139
+ path: worktreeDir,
2140
+ port: variantPort,
2141
+ created: (/* @__PURE__ */ new Date()).toISOString()
2142
+ });
2143
+ await getAgentManager().startAgent(`${agentName}@${UPGRADE_VARIANT}`);
2144
+ return c.json({
2145
+ ok: true,
2146
+ name: agentName,
2147
+ variant: UPGRADE_VARIANT,
2148
+ port: variantPort
2149
+ });
2150
+ } catch (err) {
2151
+ try {
2152
+ removeVariant(agentName, UPGRADE_VARIANT);
2153
+ } catch {
2154
+ }
2155
+ try {
2156
+ await exec("git", ["worktree", "remove", "--force", worktreeDir], { cwd: dir });
2157
+ } catch {
2158
+ }
2159
+ try {
2160
+ await exec("git", ["branch", "-D", UPGRADE_VARIANT], { cwd: dir });
2161
+ } catch {
2162
+ }
2163
+ return c.json(
2164
+ { error: err instanceof Error ? err.message : "Failed to complete upgrade" },
2165
+ 500
2166
+ );
2167
+ }
1155
2168
  }).post("/:name/message", async (c) => {
1156
2169
  const name = c.req.param("name");
1157
2170
  const [baseName, variantName] = name.split("@", 2);
@@ -1174,12 +2187,12 @@ var app = new Hono().get("/", async (c) => {
1174
2187
  console.error(`[daemon] failed to parse message body for ${baseName}:`, err);
1175
2188
  }
1176
2189
  const channel = parsed?.channel ?? "unknown";
1177
- const db = await getDb();
2190
+ const db2 = await getDb();
1178
2191
  if (parsed) {
1179
2192
  try {
1180
2193
  const sender2 = parsed.sender ?? null;
1181
2194
  const content = extractTextContent(parsed.content);
1182
- await db.insert(agentMessages).values({
2195
+ await db2.insert(agentMessages).values({
1183
2196
  agent: baseName,
1184
2197
  channel,
1185
2198
  sender: sender2,
@@ -1232,8 +2245,8 @@ var app = new Hono().get("/", async (c) => {
1232
2245
  body: forwardBody
1233
2246
  });
1234
2247
  if (!res.ok) {
1235
- const text = await res.text().catch(() => "");
1236
- console.error(`[daemon] agent ${name} responded with ${res.status}: ${text}`);
2248
+ const text2 = await res.text().catch(() => "");
2249
+ console.error(`[daemon] agent ${name} responded with ${res.status}: ${text2}`);
1237
2250
  return c.json({ error: `Agent responded with ${res.status}` }, res.status);
1238
2251
  }
1239
2252
  let result;
@@ -1272,9 +2285,9 @@ var app = new Hono().get("/", async (c) => {
1272
2285
  if (!body.channel || !body.content) {
1273
2286
  return c.json({ error: "channel and content required" }, 400);
1274
2287
  }
1275
- const db = await getDb();
2288
+ const db2 = await getDb();
1276
2289
  try {
1277
- await db.insert(agentMessages).values({
2290
+ await db2.insert(agentMessages).values({
1278
2291
  agent: baseName,
1279
2292
  channel: body.channel,
1280
2293
  sender: body.sender ?? baseName,
@@ -1287,20 +2300,20 @@ var app = new Hono().get("/", async (c) => {
1287
2300
  return c.json({ ok: true });
1288
2301
  }).get("/:name/history/channels", async (c) => {
1289
2302
  const name = c.req.param("name");
1290
- const db = await getDb();
1291
- const rows = await db.selectDistinct({ channel: agentMessages.channel }).from(agentMessages).where(eq2(agentMessages.agent, name));
2303
+ const db2 = await getDb();
2304
+ const rows = await db2.selectDistinct({ channel: agentMessages.channel }).from(agentMessages).where(eq3(agentMessages.agent, name));
1292
2305
  return c.json(rows.map((r) => r.channel));
1293
2306
  }).get("/:name/history", async (c) => {
1294
2307
  const name = c.req.param("name");
1295
2308
  const channel = c.req.query("channel");
1296
2309
  const limit = Math.min(Math.max(parseInt(c.req.query("limit") ?? "50", 10) || 50, 1), 200);
1297
2310
  const offset = Math.max(parseInt(c.req.query("offset") ?? "0", 10) || 0, 0);
1298
- const db = await getDb();
1299
- const conditions = [eq2(agentMessages.agent, name)];
2311
+ const db2 = await getDb();
2312
+ const conditions = [eq3(agentMessages.agent, name)];
1300
2313
  if (channel) {
1301
- conditions.push(eq2(agentMessages.channel, channel));
2314
+ conditions.push(eq3(agentMessages.channel, channel));
1302
2315
  }
1303
- const rows = await db.select().from(agentMessages).where(and(...conditions)).orderBy(desc(agentMessages.created_at)).limit(limit).offset(offset);
2316
+ const rows = await db2.select().from(agentMessages).where(and2(...conditions)).orderBy(desc(agentMessages.created_at)).limit(limit).offset(offset);
1304
2317
  return c.json(rows);
1305
2318
  });
1306
2319
  var agents_default = app;
@@ -1376,10 +2389,107 @@ var app2 = new Hono2().post("/register", zValidator("json", credentialsSchema),
1376
2389
  }).route("/", admin);
1377
2390
  var auth_default = app2;
1378
2391
 
1379
- // src/web/routes/connectors.ts
2392
+ // src/web/routes/channels.ts
1380
2393
  import { Hono as Hono3 } from "hono";
2394
+ function buildEnv(name) {
2395
+ return { ...loadMergedEnv(name), VOLUTE_AGENT: name, VOLUTE_AGENT_DIR: agentDir(name) };
2396
+ }
2397
+ var app3 = new Hono3().post("/:name/channels/send", requireAdmin, async (c) => {
2398
+ const name = c.req.param("name");
2399
+ if (!findAgent(name)) return c.json({ error: "Agent not found" }, 404);
2400
+ const { platform, uri, message } = await c.req.json();
2401
+ const driver = getChannelDriver(platform);
2402
+ if (!driver) return c.json({ error: `No driver for platform: ${platform}` }, 400);
2403
+ const env = buildEnv(name);
2404
+ try {
2405
+ await driver.send(env, uri, message);
2406
+ return c.json({ ok: true });
2407
+ } catch (err) {
2408
+ return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
2409
+ }
2410
+ }).get("/:name/channels/read", async (c) => {
2411
+ const name = c.req.param("name");
2412
+ if (!findAgent(name)) return c.json({ error: "Agent not found" }, 404);
2413
+ const platform = c.req.query("platform");
2414
+ const uri = c.req.query("uri");
2415
+ const limit = parseInt(c.req.query("limit") ?? "20", 10) || 20;
2416
+ if (!platform || !uri) return c.json({ error: "platform and uri required" }, 400);
2417
+ const driver = getChannelDriver(platform);
2418
+ if (!driver) return c.json({ error: `No driver for platform: ${platform}` }, 400);
2419
+ const env = buildEnv(name);
2420
+ try {
2421
+ const output = await driver.read(env, uri, limit);
2422
+ return c.text(output);
2423
+ } catch (err) {
2424
+ return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
2425
+ }
2426
+ }).get("/:name/channels/list", async (c) => {
2427
+ const name = c.req.param("name");
2428
+ if (!findAgent(name)) return c.json({ error: "Agent not found" }, 404);
2429
+ const platform = c.req.query("platform");
2430
+ const platforms = platform ? [platform] : Object.keys(CHANNELS);
2431
+ const env = buildEnv(name);
2432
+ const results = {};
2433
+ for (const p of platforms) {
2434
+ const driver = getChannelDriver(p);
2435
+ if (!driver?.listConversations) continue;
2436
+ try {
2437
+ const convs = await driver.listConversations(env);
2438
+ for (const conv of convs) {
2439
+ writeChannelEntry(name, conv.id, {
2440
+ platformId: conv.platformId,
2441
+ platform: p,
2442
+ name: conv.name,
2443
+ type: conv.type
2444
+ });
2445
+ }
2446
+ results[p] = convs;
2447
+ } catch (err) {
2448
+ results[p] = [{ error: err instanceof Error ? err.message : String(err) }];
2449
+ }
2450
+ }
2451
+ return c.json(results);
2452
+ }).get("/:name/channels/users", async (c) => {
2453
+ const name = c.req.param("name");
2454
+ if (!findAgent(name)) return c.json({ error: "Agent not found" }, 404);
2455
+ const platform = c.req.query("platform");
2456
+ if (!platform) return c.json({ error: "platform required" }, 400);
2457
+ const driver = getChannelDriver(platform);
2458
+ if (!driver?.listUsers)
2459
+ return c.json({ error: `Platform ${platform} does not support listing users` }, 400);
2460
+ const env = buildEnv(name);
2461
+ try {
2462
+ const users2 = await driver.listUsers(env);
2463
+ return c.json(users2);
2464
+ } catch (err) {
2465
+ return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
2466
+ }
2467
+ }).post("/:name/channels/create", requireAdmin, async (c) => {
2468
+ const name = c.req.param("name");
2469
+ if (!findAgent(name)) return c.json({ error: "Agent not found" }, 404);
2470
+ const {
2471
+ platform,
2472
+ participants,
2473
+ name: convName
2474
+ } = await c.req.json();
2475
+ const driver = getChannelDriver(platform);
2476
+ if (!driver?.createConversation) {
2477
+ return c.json({ error: `Platform ${platform} does not support creating conversations` }, 400);
2478
+ }
2479
+ const env = buildEnv(name);
2480
+ try {
2481
+ const slug = await driver.createConversation(env, participants, convName);
2482
+ return c.json({ slug });
2483
+ } catch (err) {
2484
+ return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
2485
+ }
2486
+ });
2487
+ var channels_default = app3;
2488
+
2489
+ // src/web/routes/connectors.ts
2490
+ import { Hono as Hono4 } from "hono";
1381
2491
  var CONNECTOR_TYPE_RE = /^[a-z][a-z0-9-]*$/;
1382
- var app3 = new Hono3().get("/:name/connectors", (c) => {
2492
+ var app4 = new Hono4().get("/:name/connectors", (c) => {
1383
2493
  const name = c.req.param("name");
1384
2494
  const entry = findAgent(name);
1385
2495
  if (!entry) return c.json({ error: "Agent not found" }, 404);
@@ -1445,21 +2555,95 @@ var app3 = new Hono3().get("/:name/connectors", (c) => {
1445
2555
  writeVoluteConfig(dir, config);
1446
2556
  return c.json({ ok: true });
1447
2557
  });
1448
- var connectors_default = app3;
2558
+ var connectors_default = app4;
2559
+
2560
+ // src/web/routes/env.ts
2561
+ import { Hono as Hono5 } from "hono";
2562
+ var app5 = new Hono5().get("/:name/env", (c) => {
2563
+ const name = c.req.param("name");
2564
+ if (!findAgent(name)) return c.json({ error: "Agent not found" }, 404);
2565
+ const shared = readEnv(sharedEnvPath());
2566
+ const agent = readEnv(agentEnvPath(name));
2567
+ return c.json({ shared, agent });
2568
+ }).get("/:name/env/:key", (c) => {
2569
+ const name = c.req.param("name");
2570
+ if (!findAgent(name)) return c.json({ error: "Agent not found" }, 404);
2571
+ const key = c.req.param("key");
2572
+ const merged = loadMergedEnv(name);
2573
+ const value = merged[key];
2574
+ if (value === void 0) return c.json({ error: "Key not found" }, 404);
2575
+ return c.json({ value });
2576
+ }).put("/:name/env/:key", requireAdmin, async (c) => {
2577
+ const name = c.req.param("name");
2578
+ if (!findAgent(name)) return c.json({ error: "Agent not found" }, 404);
2579
+ const key = c.req.param("key");
2580
+ let body;
2581
+ try {
2582
+ body = await c.req.json();
2583
+ } catch {
2584
+ return c.json({ error: "Invalid JSON body" }, 400);
2585
+ }
2586
+ if (typeof body.value !== "string") {
2587
+ return c.json({ error: "Missing required field: value" }, 400);
2588
+ }
2589
+ const path = agentEnvPath(name);
2590
+ const env = readEnv(path);
2591
+ env[key] = body.value;
2592
+ writeEnv(path, env);
2593
+ return c.json({ ok: true });
2594
+ }).delete("/:name/env/:key", requireAdmin, (c) => {
2595
+ const name = c.req.param("name");
2596
+ if (!findAgent(name)) return c.json({ error: "Agent not found" }, 404);
2597
+ const key = c.req.param("key");
2598
+ const path = agentEnvPath(name);
2599
+ const env = readEnv(path);
2600
+ if (!(key in env)) return c.json({ error: "Key not found" }, 404);
2601
+ delete env[key];
2602
+ writeEnv(path, env);
2603
+ return c.json({ ok: true });
2604
+ });
2605
+ var sharedEnvApp = new Hono5().get("/", (c) => {
2606
+ return c.json(readEnv(sharedEnvPath()));
2607
+ }).put("/:key", requireAdmin, async (c) => {
2608
+ const key = c.req.param("key");
2609
+ let body;
2610
+ try {
2611
+ body = await c.req.json();
2612
+ } catch {
2613
+ return c.json({ error: "Invalid JSON body" }, 400);
2614
+ }
2615
+ if (typeof body.value !== "string") {
2616
+ return c.json({ error: "Missing required field: value" }, 400);
2617
+ }
2618
+ const path = sharedEnvPath();
2619
+ const env = readEnv(path);
2620
+ env[key] = body.value;
2621
+ writeEnv(path, env);
2622
+ return c.json({ ok: true });
2623
+ }).delete("/:key", requireAdmin, (c) => {
2624
+ const key = c.req.param("key");
2625
+ const path = sharedEnvPath();
2626
+ const env = readEnv(path);
2627
+ if (!(key in env)) return c.json({ error: "Key not found" }, 404);
2628
+ delete env[key];
2629
+ writeEnv(path, env);
2630
+ return c.json({ ok: true });
2631
+ });
2632
+ var env_default = app5;
1449
2633
 
1450
2634
  // src/web/routes/files.ts
1451
- import { existsSync as existsSync5 } from "fs";
2635
+ import { existsSync as existsSync7 } from "fs";
1452
2636
  import { readdir, readFile } from "fs/promises";
1453
- import { resolve as resolve6 } from "path";
1454
- import { Hono as Hono4 } from "hono";
2637
+ import { resolve as resolve10 } from "path";
2638
+ import { Hono as Hono6 } from "hono";
1455
2639
  var ALLOWED_FILES = /* @__PURE__ */ new Set(["SOUL.md", "MEMORY.md", "CLAUDE.md", "VOLUTE.md"]);
1456
- var app4 = new Hono4().get("/:name/files", async (c) => {
2640
+ var app6 = new Hono6().get("/:name/files", async (c) => {
1457
2641
  const name = c.req.param("name");
1458
2642
  const entry = findAgent(name);
1459
2643
  if (!entry) return c.json({ error: "Agent not found" }, 404);
1460
2644
  const dir = agentDir(name);
1461
- const homeDir = resolve6(dir, "home");
1462
- if (!existsSync5(homeDir)) return c.json({ error: "Home directory missing" }, 404);
2645
+ const homeDir = resolve10(dir, "home");
2646
+ if (!existsSync7(homeDir)) return c.json({ error: "Home directory missing" }, 404);
1463
2647
  const allFiles = await readdir(homeDir);
1464
2648
  const files = allFiles.filter((f) => f.endsWith(".md") && ALLOWED_FILES.has(f));
1465
2649
  return c.json(files);
@@ -1472,27 +2656,27 @@ var app4 = new Hono4().get("/:name/files", async (c) => {
1472
2656
  const entry = findAgent(name);
1473
2657
  if (!entry) return c.json({ error: "Agent not found" }, 404);
1474
2658
  const dir = agentDir(name);
1475
- const filePath = resolve6(dir, "home", filename);
1476
- if (!existsSync5(filePath)) {
2659
+ const filePath = resolve10(dir, "home", filename);
2660
+ if (!existsSync7(filePath)) {
1477
2661
  return c.json({ error: "File not found" }, 404);
1478
2662
  }
1479
2663
  const content = await readFile(filePath, "utf-8");
1480
2664
  return c.json({ filename, content });
1481
2665
  });
1482
- var files_default = app4;
2666
+ var files_default = app6;
1483
2667
 
1484
2668
  // src/web/routes/logs.ts
1485
2669
  import { spawn as spawn2 } from "child_process";
1486
- import { existsSync as existsSync6 } from "fs";
1487
- import { resolve as resolve7 } from "path";
1488
- import { Hono as Hono5 } from "hono";
2670
+ import { existsSync as existsSync8 } from "fs";
2671
+ import { resolve as resolve11 } from "path";
2672
+ import { Hono as Hono7 } from "hono";
1489
2673
  import { streamSSE } from "hono/streaming";
1490
- var app5 = new Hono5().get("/:name/logs", async (c) => {
2674
+ var app7 = new Hono7().get("/:name/logs", async (c) => {
1491
2675
  const name = c.req.param("name");
1492
2676
  const entry = findAgent(name);
1493
2677
  if (!entry) return c.json({ error: "Agent not found" }, 404);
1494
- const logFile = resolve7(stateDir(name), "logs", "agent.log");
1495
- if (!existsSync6(logFile)) {
2678
+ const logFile = resolve11(stateDir(name), "logs", "agent.log");
2679
+ if (!existsSync8(logFile)) {
1496
2680
  return c.json({ error: "No log file found" }, 404);
1497
2681
  }
1498
2682
  return streamSSE(c, async (stream) => {
@@ -1510,16 +2694,35 @@ var app5 = new Hono5().get("/:name/logs", async (c) => {
1510
2694
  stream.onAbort(() => {
1511
2695
  tail.kill();
1512
2696
  });
1513
- await new Promise((resolve11) => {
1514
- tail.on("exit", resolve11);
1515
- stream.onAbort(resolve11);
2697
+ await new Promise((resolve17) => {
2698
+ tail.on("exit", resolve17);
2699
+ stream.onAbort(resolve17);
1516
2700
  });
1517
2701
  });
2702
+ }).get("/:name/logs/tail", async (c) => {
2703
+ const name = c.req.param("name");
2704
+ const entry = findAgent(name);
2705
+ if (!entry) return c.json({ error: "Agent not found" }, 404);
2706
+ const logFile = resolve11(stateDir(name), "logs", "agent.log");
2707
+ if (!existsSync8(logFile)) {
2708
+ return c.json({ error: "No log file found" }, 404);
2709
+ }
2710
+ const nParam = parseInt(c.req.query("n") ?? "50", 10);
2711
+ const n = Number.isFinite(nParam) && nParam > 0 ? Math.min(nParam, 1e4) : 50;
2712
+ const tail = spawn2("tail", ["-n", String(n), logFile]);
2713
+ let output = "";
2714
+ tail.stdout.on("data", (data) => {
2715
+ output += data.toString();
2716
+ });
2717
+ await new Promise((resolve17) => {
2718
+ tail.on("exit", resolve17);
2719
+ });
2720
+ return c.text(output);
1518
2721
  });
1519
- var logs_default = app5;
2722
+ var logs_default = app7;
1520
2723
 
1521
2724
  // src/web/routes/schedules.ts
1522
- import { Hono as Hono6 } from "hono";
2725
+ import { Hono as Hono8 } from "hono";
1523
2726
  function readSchedules(name) {
1524
2727
  return readVoluteConfig(agentDir(name))?.schedules ?? [];
1525
2728
  }
@@ -1530,7 +2733,7 @@ function writeSchedules(name, schedules) {
1530
2733
  writeVoluteConfig(dir, config);
1531
2734
  getScheduler().loadSchedules(name);
1532
2735
  }
1533
- var app6 = new Hono6().get("/:name/schedules", (c) => {
2736
+ var app8 = new Hono8().get("/:name/schedules", (c) => {
1534
2737
  const name = c.req.param("name");
1535
2738
  if (!findAgent(name)) return c.json({ error: "Agent not found" }, 404);
1536
2739
  return c.json(readSchedules(name));
@@ -1598,12 +2801,18 @@ var app6 = new Hono6().get("/:name/schedules", (c) => {
1598
2801
  return c.json({ error: "Failed to reach agent" }, 502);
1599
2802
  }
1600
2803
  });
1601
- var schedules_default = app6;
2804
+ var schedules_default = app8;
1602
2805
 
1603
2806
  // src/web/routes/system.ts
1604
- import { Hono as Hono7 } from "hono";
2807
+ import { Hono as Hono9 } from "hono";
1605
2808
  import { streamSSE as streamSSE2 } from "hono/streaming";
1606
- var app7 = new Hono7().get("/logs", async (c) => {
2809
+ var app9 = new Hono9().post("/restart", requireAdmin, (c) => {
2810
+ setTimeout(() => process.exit(1), 200);
2811
+ return c.json({ ok: true });
2812
+ }).post("/stop", requireAdmin, (c) => {
2813
+ setTimeout(() => process.exit(0), 200);
2814
+ return c.json({ ok: true });
2815
+ }).get("/logs", async (c) => {
1607
2816
  const user = c.get("user");
1608
2817
  if (user.role !== "admin") return c.json({ error: "Forbidden" }, 403);
1609
2818
  return streamSSE2(c, async (stream) => {
@@ -1614,26 +2823,26 @@ var app7 = new Hono7().get("/logs", async (c) => {
1614
2823
  stream.writeSSE({ data: JSON.stringify(entry) }).catch(() => {
1615
2824
  });
1616
2825
  });
1617
- await new Promise((resolve11) => {
2826
+ await new Promise((resolve17) => {
1618
2827
  stream.onAbort(() => {
1619
2828
  unsubscribe();
1620
- resolve11();
2829
+ resolve17();
1621
2830
  });
1622
2831
  });
1623
2832
  });
1624
2833
  });
1625
- var system_default = app7;
2834
+ var system_default = app9;
1626
2835
 
1627
2836
  // src/web/routes/typing.ts
1628
2837
  import { zValidator as zValidator2 } from "@hono/zod-validator";
1629
- import { Hono as Hono8 } from "hono";
2838
+ import { Hono as Hono10 } from "hono";
1630
2839
  import { z as z2 } from "zod";
1631
2840
  var typingSchema = z2.object({
1632
2841
  channel: z2.string().min(1),
1633
2842
  sender: z2.string().min(1),
1634
2843
  active: z2.boolean()
1635
2844
  });
1636
- var app8 = new Hono8().post("/:name/typing", zValidator2("json", typingSchema), (c) => {
2845
+ var app10 = new Hono10().post("/:name/typing", zValidator2("json", typingSchema), (c) => {
1637
2846
  const { channel, sender, active } = c.req.valid("json");
1638
2847
  const map = getTypingMap();
1639
2848
  if (active) {
@@ -1650,13 +2859,13 @@ var app8 = new Hono8().post("/:name/typing", zValidator2("json", typingSchema),
1650
2859
  const map = getTypingMap();
1651
2860
  return c.json({ typing: map.get(channel) });
1652
2861
  });
1653
- var typing_default = app8;
2862
+ var typing_default = app10;
1654
2863
 
1655
2864
  // src/web/routes/update.ts
1656
2865
  import { spawn as spawn3 } from "child_process";
1657
- import { Hono as Hono9 } from "hono";
2866
+ import { Hono as Hono11 } from "hono";
1658
2867
  var bin;
1659
- var app9 = new Hono9().get("/update", async (c) => {
2868
+ var app11 = new Hono11().get("/update", async (c) => {
1660
2869
  const result = await checkForUpdate();
1661
2870
  return c.json(result);
1662
2871
  }).post("/update", requireAdmin, async (c) => {
@@ -1671,11 +2880,128 @@ var app9 = new Hono9().get("/update", async (c) => {
1671
2880
  child.unref();
1672
2881
  return c.json({ ok: true, message: "Updating..." });
1673
2882
  });
1674
- var update_default = app9;
2883
+ var update_default = app11;
1675
2884
 
1676
2885
  // src/web/routes/variants.ts
1677
- import { Hono as Hono10 } from "hono";
1678
- var app10 = new Hono10().get("/:name/variants", async (c) => {
2886
+ import { existsSync as existsSync9, mkdirSync as mkdirSync7, writeFileSync as writeFileSync6 } from "fs";
2887
+ import { resolve as resolve13 } from "path";
2888
+ import { Hono as Hono12 } from "hono";
2889
+
2890
+ // src/lib/spawn-server.ts
2891
+ import { spawn as spawn4 } from "child_process";
2892
+ import { closeSync, mkdirSync as mkdirSync6, openSync, readFileSync as readFileSync7 } from "fs";
2893
+ import { resolve as resolve12 } from "path";
2894
+ function tsxBin(cwd) {
2895
+ return resolve12(cwd, "node_modules", ".bin", "tsx");
2896
+ }
2897
+ function spawnServer(cwd, port, options) {
2898
+ if (options?.detached) {
2899
+ return spawnDetached(cwd, port, options.logDir);
2900
+ }
2901
+ return spawnAttached(cwd, port);
2902
+ }
2903
+ function spawnAttached(cwd, port) {
2904
+ const child = spawn4(tsxBin(cwd), ["src/server.ts", "--port", String(port)], {
2905
+ cwd,
2906
+ stdio: ["ignore", "pipe", "pipe"]
2907
+ });
2908
+ return new Promise((resolve17) => {
2909
+ const timeout = setTimeout(() => resolve17(null), 3e4);
2910
+ function checkOutput(data) {
2911
+ const match = data.toString().match(/listening on :(\d+)/);
2912
+ if (match) {
2913
+ clearTimeout(timeout);
2914
+ resolve17({ child, actualPort: parseInt(match[1], 10) });
2915
+ }
2916
+ }
2917
+ child.stdout?.on("data", checkOutput);
2918
+ child.stderr?.on("data", checkOutput);
2919
+ child.on("error", () => {
2920
+ clearTimeout(timeout);
2921
+ resolve17(null);
2922
+ });
2923
+ child.on("exit", () => {
2924
+ clearTimeout(timeout);
2925
+ resolve17(null);
2926
+ });
2927
+ });
2928
+ }
2929
+ function spawnDetached(cwd, port, logDir) {
2930
+ const logsDir = logDir ?? resolve12(cwd, ".volute", "logs");
2931
+ mkdirSync6(logsDir, { recursive: true });
2932
+ const logPath = resolve12(logsDir, "agent.log");
2933
+ const logFd = openSync(logPath, "a");
2934
+ const child = spawn4(tsxBin(cwd), ["src/server.ts", "--port", String(port)], {
2935
+ cwd,
2936
+ stdio: ["ignore", logFd, logFd],
2937
+ detached: true
2938
+ });
2939
+ child.unref();
2940
+ closeSync(logFd);
2941
+ return new Promise((res) => {
2942
+ let done = false;
2943
+ function finish(result) {
2944
+ if (done) return;
2945
+ done = true;
2946
+ clearInterval(interval);
2947
+ clearTimeout(timeout);
2948
+ res(result);
2949
+ }
2950
+ const interval = setInterval(() => {
2951
+ try {
2952
+ const content = readFileSync7(logPath, "utf-8");
2953
+ const match = content.match(/listening on :(\d+)/);
2954
+ if (match) {
2955
+ finish({ child, actualPort: parseInt(match[1], 10) });
2956
+ }
2957
+ } catch {
2958
+ }
2959
+ }, 100);
2960
+ const timeout = setTimeout(() => finish(null), 3e4);
2961
+ child.on("error", () => finish(null));
2962
+ child.on("exit", () => finish(null));
2963
+ });
2964
+ }
2965
+
2966
+ // src/lib/verify.ts
2967
+ async function verify(port) {
2968
+ const health = await checkHealth(port);
2969
+ if (!health.ok) {
2970
+ console.error(" Health check: failed");
2971
+ return false;
2972
+ }
2973
+ console.log(" Health check: OK");
2974
+ try {
2975
+ const res = await fetch(`http://127.0.0.1:${port}/message`, {
2976
+ method: "POST",
2977
+ headers: { "Content-Type": "application/json" },
2978
+ body: JSON.stringify({
2979
+ content: [{ type: "text", text: "ping" }],
2980
+ channel: "system"
2981
+ }),
2982
+ signal: AbortSignal.timeout(6e4)
2983
+ });
2984
+ if (!res.ok) {
2985
+ console.error(" Test message: failed to send");
2986
+ return false;
2987
+ }
2988
+ const result = await res.json();
2989
+ if (result.ok) {
2990
+ console.log(" Test message: OK");
2991
+ return true;
2992
+ } else {
2993
+ console.error(" Test message: unexpected response");
2994
+ return false;
2995
+ }
2996
+ } catch (e) {
2997
+ const msg = e instanceof Error ? e.message : String(e);
2998
+ console.error(` Test message: ${msg}`);
2999
+ return false;
3000
+ }
3001
+ }
3002
+
3003
+ // src/web/routes/variants.ts
3004
+ var app12 = new Hono12().get("/:name/variants", async (c) => {
1679
3005
  const name = c.req.param("name");
1680
3006
  const entry = findAgent(name);
1681
3007
  if (!entry) return c.json({ error: "Agent not found" }, 404);
@@ -1688,14 +3014,205 @@ var app10 = new Hono10().get("/:name/variants", async (c) => {
1688
3014
  })
1689
3015
  );
1690
3016
  return c.json(results);
3017
+ }).post("/:name/variants", requireAdmin, async (c) => {
3018
+ const agentName = c.req.param("name");
3019
+ const entry = findAgent(agentName);
3020
+ if (!entry) return c.json({ error: "Agent not found" }, 404);
3021
+ let body;
3022
+ try {
3023
+ body = await c.req.json();
3024
+ } catch {
3025
+ return c.json({ error: "Invalid JSON" }, 400);
3026
+ }
3027
+ const variantName = body.name;
3028
+ if (!variantName) return c.json({ error: "Variant name required" }, 400);
3029
+ const err = validateBranchName(variantName);
3030
+ if (err) return c.json({ error: err }, 400);
3031
+ const projectRoot = agentDir(agentName);
3032
+ const variantDir = resolve13(projectRoot, ".variants", variantName);
3033
+ if (existsSync9(variantDir)) {
3034
+ return c.json({ error: `Variant directory already exists: ${variantDir}` }, 409);
3035
+ }
3036
+ mkdirSync7(resolve13(projectRoot, ".variants"), { recursive: true });
3037
+ try {
3038
+ await exec("git", ["worktree", "add", "-b", variantName, variantDir], { cwd: projectRoot });
3039
+ } catch (e) {
3040
+ const msg = e instanceof Error ? e.message : String(e);
3041
+ return c.json({ error: `Failed to create worktree: ${msg}` }, 500);
3042
+ }
3043
+ try {
3044
+ await exec("npm", ["install"], { cwd: variantDir });
3045
+ } catch (e) {
3046
+ const msg = e instanceof Error ? e.message : String(e);
3047
+ return c.json({ error: `npm install failed: ${msg}` }, 500);
3048
+ }
3049
+ if (body.soul) {
3050
+ writeFileSync6(resolve13(variantDir, "home/SOUL.md"), body.soul);
3051
+ }
3052
+ const variantPort = body.port ?? nextPort();
3053
+ const variant = {
3054
+ name: variantName,
3055
+ branch: variantName,
3056
+ path: variantDir,
3057
+ port: variantPort,
3058
+ created: (/* @__PURE__ */ new Date()).toISOString()
3059
+ };
3060
+ addVariant(agentName, variant);
3061
+ if (!body.noStart) {
3062
+ try {
3063
+ await getAgentManager().startAgent(`${agentName}@${variantName}`);
3064
+ } catch (e) {
3065
+ const msg = e instanceof Error ? e.message : String(e);
3066
+ return c.json({ error: `Variant created but failed to start: ${msg}` }, 500);
3067
+ }
3068
+ }
3069
+ return c.json({
3070
+ ok: true,
3071
+ variant: { name: variantName, branch: variantName, path: variantDir, port: variantPort }
3072
+ });
3073
+ }).post("/:name/variants/:variant/merge", requireAdmin, async (c) => {
3074
+ const agentName = c.req.param("name");
3075
+ const variantName = c.req.param("variant");
3076
+ const entry = findAgent(agentName);
3077
+ if (!entry) return c.json({ error: "Agent not found" }, 404);
3078
+ const variant = findVariant(agentName, variantName);
3079
+ if (!variant) return c.json({ error: `Unknown variant: ${variantName}` }, 404);
3080
+ const branchErr = validateBranchName(variant.branch);
3081
+ if (branchErr) return c.json({ error: branchErr }, 400);
3082
+ let body = {};
3083
+ try {
3084
+ body = await c.req.json();
3085
+ } catch {
3086
+ }
3087
+ const projectRoot = agentDir(agentName);
3088
+ if (existsSync9(variant.path)) {
3089
+ const status = (await exec("git", ["status", "--porcelain"], { cwd: variant.path })).trim();
3090
+ if (status) {
3091
+ try {
3092
+ await exec("git", ["add", "-A"], { cwd: variant.path });
3093
+ await exec("git", ["commit", "-m", "Auto-commit uncommitted changes before merge"], {
3094
+ cwd: variant.path
3095
+ });
3096
+ } catch (e) {
3097
+ return c.json(
3098
+ {
3099
+ error: "Failed to auto-commit variant changes. Commit or stash manually before merging."
3100
+ },
3101
+ 500
3102
+ );
3103
+ }
3104
+ }
3105
+ }
3106
+ if (!body.skipVerify) {
3107
+ const result = await spawnServer(variant.path, 0, { detached: true });
3108
+ if (!result) {
3109
+ return c.json(
3110
+ { error: "Failed to start server for verification. Use skipVerify to skip." },
3111
+ 500
3112
+ );
3113
+ }
3114
+ const verified = await verify(result.actualPort);
3115
+ try {
3116
+ process.kill(result.child.pid);
3117
+ } catch {
3118
+ }
3119
+ if (!verified) {
3120
+ return c.json(
3121
+ { error: "Verification failed. Fix issues or use skipVerify to proceed." },
3122
+ 500
3123
+ );
3124
+ }
3125
+ }
3126
+ const mainStatus = (await exec("git", ["status", "--porcelain"], { cwd: projectRoot })).trim();
3127
+ if (mainStatus) {
3128
+ try {
3129
+ await exec("git", ["add", "-A"], { cwd: projectRoot });
3130
+ await exec("git", ["commit", "-m", "Auto-commit uncommitted changes before merge"], {
3131
+ cwd: projectRoot
3132
+ });
3133
+ } catch (e) {
3134
+ return c.json(
3135
+ { error: "Failed to auto-commit main changes. Commit or stash manually before merging." },
3136
+ 500
3137
+ );
3138
+ }
3139
+ }
3140
+ try {
3141
+ await exec("git", ["merge", variant.branch], { cwd: projectRoot });
3142
+ } catch (e) {
3143
+ return c.json({ error: "Merge failed. Resolve conflicts manually." }, 500);
3144
+ }
3145
+ if (existsSync9(variant.path)) {
3146
+ try {
3147
+ await exec("git", ["worktree", "remove", "--force", variant.path], { cwd: projectRoot });
3148
+ } catch {
3149
+ }
3150
+ }
3151
+ try {
3152
+ await exec("git", ["branch", "-D", variant.branch], { cwd: projectRoot });
3153
+ } catch {
3154
+ }
3155
+ removeVariant(agentName, variantName);
3156
+ try {
3157
+ await exec("npm", ["install"], { cwd: projectRoot });
3158
+ } catch {
3159
+ }
3160
+ const manager = getAgentManager();
3161
+ const context = {
3162
+ type: "merged",
3163
+ name: variantName,
3164
+ ...body.summary && { summary: body.summary },
3165
+ ...body.justification && { justification: body.justification },
3166
+ ...body.memory && { memory: body.memory }
3167
+ };
3168
+ let restartWarning;
3169
+ try {
3170
+ if (manager.isRunning(agentName)) {
3171
+ await manager.stopAgent(agentName);
3172
+ }
3173
+ manager.setPendingContext(agentName, context);
3174
+ await manager.startAgent(agentName);
3175
+ } catch (e) {
3176
+ restartWarning = `Merge succeeded but agent restart failed: ${e instanceof Error ? e.message : String(e)}`;
3177
+ console.error(`[daemon] ${restartWarning}`);
3178
+ }
3179
+ return c.json({ ok: true, ...restartWarning && { warning: restartWarning } });
3180
+ }).delete("/:name/variants/:variant", requireAdmin, async (c) => {
3181
+ const agentName = c.req.param("name");
3182
+ const variantName = c.req.param("variant");
3183
+ const entry = findAgent(agentName);
3184
+ if (!entry) return c.json({ error: "Agent not found" }, 404);
3185
+ const variant = findVariant(agentName, variantName);
3186
+ if (!variant) return c.json({ error: `Unknown variant: ${variantName}` }, 404);
3187
+ const projectRoot = agentDir(agentName);
3188
+ const manager = getAgentManager();
3189
+ const compositeKey = `${agentName}@${variantName}`;
3190
+ if (manager.isRunning(compositeKey)) {
3191
+ try {
3192
+ await manager.stopAgent(compositeKey);
3193
+ } catch {
3194
+ }
3195
+ }
3196
+ if (existsSync9(variant.path)) {
3197
+ try {
3198
+ await exec("git", ["worktree", "remove", "--force", variant.path], { cwd: projectRoot });
3199
+ } catch {
3200
+ }
3201
+ }
3202
+ try {
3203
+ await exec("git", ["branch", "-D", variant.branch], { cwd: projectRoot });
3204
+ } catch {
3205
+ }
3206
+ removeVariant(agentName, variantName);
3207
+ return c.json({ ok: true });
1691
3208
  });
1692
- var variants_default = app10;
3209
+ var variants_default = app12;
1693
3210
 
1694
3211
  // src/web/routes/volute/chat.ts
1695
- import { readFileSync as readFileSync4 } from "fs";
1696
- import { resolve as resolve8 } from "path";
3212
+ import { readFileSync as readFileSync8 } from "fs";
3213
+ import { resolve as resolve14 } from "path";
1697
3214
  import { zValidator as zValidator3 } from "@hono/zod-validator";
1698
- import { Hono as Hono11 } from "hono";
3215
+ import { Hono as Hono13 } from "hono";
1699
3216
  import { streamSSE as streamSSE3 } from "hono/streaming";
1700
3217
  import { z as z3 } from "zod";
1701
3218
 
@@ -1728,12 +3245,12 @@ function publish(conversationId, event) {
1728
3245
  }
1729
3246
 
1730
3247
  // src/lib/conversations.ts
1731
- import { randomUUID } from "crypto";
1732
- import { and as and2, desc as desc2, eq as eq3, inArray, isNull, sql } from "drizzle-orm";
3248
+ import { randomUUID as randomUUID2 } from "crypto";
3249
+ import { and as and3, desc as desc2, eq as eq4, inArray, isNull, sql as sql2 } from "drizzle-orm";
1733
3250
  async function createConversation(agentName, channel, opts) {
1734
- const db = await getDb();
1735
- const id = randomUUID();
1736
- await db.insert(conversations).values({
3251
+ const db2 = await getDb();
3252
+ const id = randomUUID2();
3253
+ await db2.insert(conversations).values({
1737
3254
  id,
1738
3255
  agent_name: agentName,
1739
3256
  channel,
@@ -1741,7 +3258,7 @@ async function createConversation(agentName, channel, opts) {
1741
3258
  title: opts?.title ?? null
1742
3259
  });
1743
3260
  if (opts?.participantIds && opts.participantIds.length > 0) {
1744
- await db.insert(conversationParticipants).values(
3261
+ await db2.insert(conversationParticipants).values(
1745
3262
  opts.participantIds.map((uid, i) => ({
1746
3263
  conversation_id: id,
1747
3264
  user_id: uid,
@@ -1760,41 +3277,41 @@ async function createConversation(agentName, channel, opts) {
1760
3277
  };
1761
3278
  }
1762
3279
  async function getConversation(id) {
1763
- const db = await getDb();
1764
- const row = await db.select().from(conversations).where(eq3(conversations.id, id)).get();
3280
+ const db2 = await getDb();
3281
+ const row = await db2.select().from(conversations).where(eq4(conversations.id, id)).get();
1765
3282
  return row ?? null;
1766
3283
  }
1767
3284
  async function getParticipants(conversationId) {
1768
- const db = await getDb();
1769
- const rows = await db.select({
3285
+ const db2 = await getDb();
3286
+ const rows = await db2.select({
1770
3287
  userId: conversationParticipants.user_id,
1771
3288
  username: users.username,
1772
3289
  userType: users.user_type,
1773
3290
  role: conversationParticipants.role
1774
- }).from(conversationParticipants).innerJoin(users, eq3(conversationParticipants.user_id, users.id)).where(eq3(conversationParticipants.conversation_id, conversationId)).all();
3291
+ }).from(conversationParticipants).innerJoin(users, eq4(conversationParticipants.user_id, users.id)).where(eq4(conversationParticipants.conversation_id, conversationId)).all();
1775
3292
  return rows;
1776
3293
  }
1777
3294
  async function isParticipant(conversationId, userId) {
1778
- const db = await getDb();
1779
- const row = await db.select({ user_id: conversationParticipants.user_id }).from(conversationParticipants).where(
1780
- and2(
1781
- eq3(conversationParticipants.conversation_id, conversationId),
1782
- eq3(conversationParticipants.user_id, userId)
3295
+ const db2 = await getDb();
3296
+ const row = await db2.select({ user_id: conversationParticipants.user_id }).from(conversationParticipants).where(
3297
+ and3(
3298
+ eq4(conversationParticipants.conversation_id, conversationId),
3299
+ eq4(conversationParticipants.user_id, userId)
1783
3300
  )
1784
3301
  ).get();
1785
3302
  return row != null;
1786
3303
  }
1787
3304
  async function listConversationsForUser(userId) {
1788
- const db = await getDb();
1789
- const participantRows = await db.select({ conversation_id: conversationParticipants.conversation_id }).from(conversationParticipants).where(eq3(conversationParticipants.user_id, userId)).all();
3305
+ const db2 = await getDb();
3306
+ const participantRows = await db2.select({ conversation_id: conversationParticipants.conversation_id }).from(conversationParticipants).where(eq4(conversationParticipants.user_id, userId)).all();
1790
3307
  if (participantRows.length === 0) return [];
1791
3308
  const convIds = participantRows.map((r) => r.conversation_id);
1792
- return db.select().from(conversations).where(inArray(conversations.id, convIds)).orderBy(desc2(conversations.updated_at)).all();
3309
+ return db2.select().from(conversations).where(inArray(conversations.id, convIds)).orderBy(desc2(conversations.updated_at)).all();
1793
3310
  }
1794
3311
  async function isParticipantOrOwner(conversationId, userId) {
1795
3312
  if (await isParticipant(conversationId, userId)) return true;
1796
- const db = await getDb();
1797
- const row = await db.select().from(conversations).where(and2(eq3(conversations.id, conversationId), eq3(conversations.user_id, userId))).get();
3313
+ const db2 = await getDb();
3314
+ const row = await db2.select().from(conversations).where(and3(eq4(conversations.id, conversationId), eq4(conversations.user_id, userId))).get();
1798
3315
  return row != null;
1799
3316
  }
1800
3317
  async function deleteConversationForUser(id, userId) {
@@ -1803,15 +3320,15 @@ async function deleteConversationForUser(id, userId) {
1803
3320
  return true;
1804
3321
  }
1805
3322
  async function addMessage(conversationId, role, senderName, content) {
1806
- const db = await getDb();
3323
+ const db2 = await getDb();
1807
3324
  const serialized = JSON.stringify(content);
1808
- 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 });
1809
- await db.update(conversations).set({ updated_at: sql`datetime('now')` }).where(eq3(conversations.id, conversationId));
3325
+ 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 });
3326
+ await db2.update(conversations).set({ updated_at: sql2`datetime('now')` }).where(eq4(conversations.id, conversationId));
1810
3327
  if (role === "user") {
1811
3328
  const firstText = content.find((b) => b.type === "text");
1812
3329
  const title = firstText ? firstText.text.slice(0, 80) : "";
1813
3330
  if (title) {
1814
- await db.update(conversations).set({ title }).where(and2(eq3(conversations.id, conversationId), isNull(conversations.title)));
3331
+ await db2.update(conversations).set({ title }).where(and3(eq4(conversations.id, conversationId), isNull(conversations.title)));
1815
3332
  }
1816
3333
  }
1817
3334
  const msg = {
@@ -1833,8 +3350,8 @@ async function addMessage(conversationId, role, senderName, content) {
1833
3350
  return msg;
1834
3351
  }
1835
3352
  async function getMessages(conversationId) {
1836
- const db = await getDb();
1837
- const rows = await db.select().from(messages).where(eq3(messages.conversation_id, conversationId)).orderBy(messages.created_at).all();
3353
+ const db2 = await getDb();
3354
+ const rows = await db2.select().from(messages).where(eq4(messages.conversation_id, conversationId)).orderBy(messages.created_at).all();
1838
3355
  return rows.map((row) => {
1839
3356
  let content;
1840
3357
  try {
@@ -1849,15 +3366,15 @@ async function getMessages(conversationId) {
1849
3366
  async function listConversationsWithParticipants(userId) {
1850
3367
  const convs = await listConversationsForUser(userId);
1851
3368
  if (convs.length === 0) return [];
1852
- const db = await getDb();
3369
+ const db2 = await getDb();
1853
3370
  const convIds = convs.map((c) => c.id);
1854
- const rows = await db.select({
3371
+ const rows = await db2.select({
1855
3372
  conversationId: conversationParticipants.conversation_id,
1856
3373
  userId: users.id,
1857
3374
  username: users.username,
1858
3375
  userType: users.user_type,
1859
3376
  role: conversationParticipants.role
1860
- }).from(conversationParticipants).innerJoin(users, eq3(conversationParticipants.user_id, users.id)).where(inArray(conversationParticipants.conversation_id, convIds));
3377
+ }).from(conversationParticipants).innerJoin(users, eq4(conversationParticipants.user_id, users.id)).where(inArray(conversationParticipants.conversation_id, convIds));
1861
3378
  const byConv = /* @__PURE__ */ new Map();
1862
3379
  for (const r of rows) {
1863
3380
  let arr = byConv.get(r.conversationId);
@@ -1875,10 +3392,10 @@ async function listConversationsWithParticipants(userId) {
1875
3392
  return convs.map((c) => ({ ...c, participants: byConv.get(c.id) ?? [] }));
1876
3393
  }
1877
3394
  async function findDMConversation(agentName, participantIds) {
1878
- const db = await getDb();
1879
- const agentConvs = await db.select({ id: conversations.id }).from(conversations).where(eq3(conversations.agent_name, agentName)).all();
3395
+ const db2 = await getDb();
3396
+ const agentConvs = await db2.select({ id: conversations.id }).from(conversations).where(eq4(conversations.agent_name, agentName)).all();
1880
3397
  for (const conv of agentConvs) {
1881
- const rows = await db.select({ user_id: conversationParticipants.user_id }).from(conversationParticipants).where(eq3(conversationParticipants.conversation_id, conv.id)).all();
3398
+ const rows = await db2.select({ user_id: conversationParticipants.user_id }).from(conversationParticipants).where(eq4(conversationParticipants.conversation_id, conv.id)).all();
1882
3399
  if (rows.length !== 2) continue;
1883
3400
  const ids = new Set(rows.map((r) => r.user_id));
1884
3401
  if (ids.has(participantIds[0]) && ids.has(participantIds[1])) {
@@ -1888,8 +3405,8 @@ async function findDMConversation(agentName, participantIds) {
1888
3405
  return null;
1889
3406
  }
1890
3407
  async function deleteConversation(id) {
1891
- const db = await getDb();
1892
- await db.delete(conversations).where(eq3(conversations.id, id));
3408
+ const db2 = await getDb();
3409
+ await db2.delete(conversations).where(eq4(conversations.id, id));
1893
3410
  }
1894
3411
 
1895
3412
  // src/web/routes/volute/chat.ts
@@ -1906,7 +3423,7 @@ var chatSchema = z3.object({
1906
3423
  });
1907
3424
  function getDaemonUrl() {
1908
3425
  try {
1909
- const data = JSON.parse(readFileSync4(resolve8(voluteHome(), "daemon.json"), "utf-8"));
3426
+ const data = JSON.parse(readFileSync8(resolve14(voluteHome(), "daemon.json"), "utf-8"));
1910
3427
  return `http://${daemonLoopback()}:${data.port}`;
1911
3428
  } catch (err) {
1912
3429
  throw new Error(`Failed to read daemon config: ${err instanceof Error ? err.message : err}`);
@@ -1922,7 +3439,7 @@ function daemonFetchInternal(path, body) {
1922
3439
  if (token) headers.Authorization = `Bearer ${token}`;
1923
3440
  return fetch(`${daemonUrl}${path}`, { method: "POST", headers, body });
1924
3441
  }
1925
- var app11 = new Hono11().post("/:name/chat", zValidator3("json", chatSchema), async (c) => {
3442
+ var app13 = new Hono13().post("/:name/chat", zValidator3("json", chatSchema), async (c) => {
1926
3443
  const name = c.req.param("name");
1927
3444
  const [baseName] = name.split("@", 2);
1928
3445
  const entry = findAgent(baseName);
@@ -1984,7 +3501,7 @@ var app11 = new Hono11().post("/:name/chat", zValidator3("json", chatSchema), as
1984
3501
  const participants = await getParticipants(conversationId);
1985
3502
  const agentParticipants = participants.filter((p) => p.userType === "agent");
1986
3503
  const participantNames = participants.map((p) => p.username);
1987
- const { getAgentManager: getAgentManager2 } = await import("./agent-manager-CMMH5KQQ.js");
3504
+ const { getAgentManager: getAgentManager2 } = await import("./agent-manager-MRHHKAB6.js");
1988
3505
  const manager = getAgentManager2();
1989
3506
  const runningAgents = agentParticipants.map((ap) => {
1990
3507
  const agentKey = ap.username === baseName ? name : ap.username;
@@ -2020,8 +3537,8 @@ var app11 = new Hono11().post("/:name/chat", zValidator3("json", chatSchema), as
2020
3537
  const targetName = agentName === baseName ? name : agentName;
2021
3538
  daemonFetchInternal(`/api/agents/${encodeURIComponent(targetName)}/message`, payload).then(async (res) => {
2022
3539
  if (!res.ok) {
2023
- const text = await res.text().catch(() => "");
2024
- console.error(`[chat] agent ${agentName} responded ${res.status}: ${text}`);
3540
+ const text2 = await res.text().catch(() => "");
3541
+ console.error(`[chat] agent ${agentName} responded ${res.status}: ${text2}`);
2025
3542
  }
2026
3543
  }).catch((err) => {
2027
3544
  console.error(`[chat] agent ${agentName} unreachable via daemon:`, err);
@@ -2045,27 +3562,27 @@ var app11 = new Hono11().post("/:name/chat", zValidator3("json", chatSchema), as
2045
3562
  if (!stream.aborted) console.error("[chat] SSE ping error:", err);
2046
3563
  });
2047
3564
  }, 15e3);
2048
- await new Promise((resolve11) => {
3565
+ await new Promise((resolve17) => {
2049
3566
  stream.onAbort(() => {
2050
3567
  unsubscribe();
2051
3568
  clearInterval(keepAlive);
2052
- resolve11();
3569
+ resolve17();
2053
3570
  });
2054
3571
  });
2055
3572
  });
2056
3573
  });
2057
- var chat_default = app11;
3574
+ var chat_default = app13;
2058
3575
 
2059
3576
  // src/web/routes/volute/conversations.ts
2060
3577
  import { zValidator as zValidator4 } from "@hono/zod-validator";
2061
- import { Hono as Hono12 } from "hono";
3578
+ import { Hono as Hono14 } from "hono";
2062
3579
  import { z as z4 } from "zod";
2063
3580
  var createConvSchema = z4.object({
2064
3581
  title: z4.string().optional(),
2065
3582
  participantIds: z4.array(z4.number()).optional(),
2066
3583
  participantNames: z4.array(z4.string()).optional()
2067
3584
  });
2068
- var app12 = new Hono12().get("/:name/conversations", async (c) => {
3585
+ var app14 = new Hono14().get("/:name/conversations", async (c) => {
2069
3586
  const name = c.req.param("name");
2070
3587
  const user = c.get("user");
2071
3588
  let lookupId = user.id;
@@ -2150,17 +3667,17 @@ var app12 = new Hono12().get("/:name/conversations", async (c) => {
2150
3667
  if (!deleted) return c.json({ error: "Conversation not found" }, 404);
2151
3668
  return c.json({ ok: true });
2152
3669
  });
2153
- var conversations_default = app12;
3670
+ var conversations_default = app14;
2154
3671
 
2155
3672
  // src/web/routes/volute/user-conversations.ts
2156
3673
  import { zValidator as zValidator5 } from "@hono/zod-validator";
2157
- import { Hono as Hono13 } from "hono";
3674
+ import { Hono as Hono15 } from "hono";
2158
3675
  import { z as z5 } from "zod";
2159
3676
  var createSchema = z5.object({
2160
3677
  title: z5.string().optional(),
2161
3678
  participantNames: z5.array(z5.string()).min(1)
2162
3679
  });
2163
- var app13 = new Hono13().use("*", authMiddleware).get("/", async (c) => {
3680
+ var app15 = new Hono15().use("*", authMiddleware).get("/", async (c) => {
2164
3681
  const user = c.get("user");
2165
3682
  const convs = await listConversationsWithParticipants(user.id);
2166
3683
  return c.json(convs);
@@ -2209,11 +3726,11 @@ var app13 = new Hono13().use("*", authMiddleware).get("/", async (c) => {
2209
3726
  if (!deleted) return c.json({ error: "Conversation not found" }, 404);
2210
3727
  return c.json({ ok: true });
2211
3728
  });
2212
- var user_conversations_default = app13;
3729
+ var user_conversations_default = app15;
2213
3730
 
2214
3731
  // src/web/app.ts
2215
- var app14 = new Hono14();
2216
- app14.onError((err, c) => {
3732
+ var app16 = new Hono16();
3733
+ app16.onError((err, c) => {
2217
3734
  if (err instanceof HTTPException) {
2218
3735
  return err.getResponse();
2219
3736
  }
@@ -2224,10 +3741,10 @@ app14.onError((err, c) => {
2224
3741
  });
2225
3742
  return c.json({ error: "Internal server error" }, 500);
2226
3743
  });
2227
- app14.notFound((c) => {
3744
+ app16.notFound((c) => {
2228
3745
  return c.json({ error: "Not found" }, 404);
2229
3746
  });
2230
- app14.use("*", async (c, next) => {
3747
+ app16.use("*", async (c, next) => {
2231
3748
  const start = Date.now();
2232
3749
  await next();
2233
3750
  const duration = Date.now() - start;
@@ -2238,7 +3755,7 @@ app14.use("*", async (c, next) => {
2238
3755
  duration
2239
3756
  });
2240
3757
  });
2241
- app14.get("/api/health", (c) => {
3758
+ app16.get("/api/health", (c) => {
2242
3759
  let version = "unknown";
2243
3760
  let cached = null;
2244
3761
  try {
@@ -2253,13 +3770,14 @@ app14.get("/api/health", (c) => {
2253
3770
  ...cached?.updateAvailable ? { updateAvailable: true, latest: cached.latest } : {}
2254
3771
  });
2255
3772
  });
2256
- app14.use("/api/*", bodyLimit({ maxSize: 10 * 1024 * 1024 }));
2257
- app14.use("/api/*", csrf());
2258
- app14.use("/api/agents/*", authMiddleware);
2259
- app14.use("/api/conversations/*", authMiddleware);
2260
- app14.use("/api/system/*", authMiddleware);
2261
- var routes = app14.route("/api/auth", auth_default).route("/api/system", system_default).route("/api/system", update_default).route("/api/agents", agents_default).route("/api/agents", chat_default).route("/api/agents", connectors_default).route("/api/agents", schedules_default).route("/api/agents", logs_default).route("/api/agents", typing_default).route("/api/agents", variants_default).route("/api/agents", files_default).route("/api/agents", conversations_default).route("/api/conversations", user_conversations_default);
2262
- var app_default = app14;
3773
+ app16.use("/api/*", bodyLimit({ maxSize: 10 * 1024 * 1024 }));
3774
+ app16.use("/api/*", csrf());
3775
+ app16.use("/api/agents/*", authMiddleware);
3776
+ app16.use("/api/conversations/*", authMiddleware);
3777
+ app16.use("/api/system/*", authMiddleware);
3778
+ app16.use("/api/env/*", authMiddleware);
3779
+ var routes = app16.route("/api/auth", auth_default).route("/api/system", system_default).route("/api/system", update_default).route("/api/agents", agents_default).route("/api/agents", chat_default).route("/api/agents", connectors_default).route("/api/agents", schedules_default).route("/api/agents", logs_default).route("/api/agents", typing_default).route("/api/agents", variants_default).route("/api/agents", files_default).route("/api/agents", channels_default).route("/api/agents", env_default).route("/api/agents", conversations_default).route("/api/env", sharedEnvApp).route("/api/conversations", user_conversations_default);
3780
+ var app_default = app16;
2263
3781
 
2264
3782
  // src/web/server.ts
2265
3783
  var MIME_TYPES = {
@@ -2276,20 +3794,20 @@ async function startServer({
2276
3794
  hostname = "127.0.0.1"
2277
3795
  }) {
2278
3796
  let assetsDir = "";
2279
- let searchDir = dirname2(new URL(import.meta.url).pathname);
3797
+ let searchDir = dirname4(new URL(import.meta.url).pathname);
2280
3798
  for (let i = 0; i < 5; i++) {
2281
- const candidate = resolve9(searchDir, "dist", "web-assets");
2282
- if (existsSync7(candidate)) {
3799
+ const candidate = resolve15(searchDir, "dist", "web-assets");
3800
+ if (existsSync10(candidate)) {
2283
3801
  assetsDir = candidate;
2284
3802
  break;
2285
3803
  }
2286
- searchDir = dirname2(searchDir);
3804
+ searchDir = dirname4(searchDir);
2287
3805
  }
2288
3806
  if (assetsDir) {
2289
3807
  app_default.get("*", async (c) => {
2290
3808
  const urlPath = new URL(c.req.url).pathname;
2291
3809
  if (urlPath.startsWith("/api/")) return c.notFound();
2292
- const filePath = resolve9(assetsDir, urlPath.slice(1));
3810
+ const filePath = resolve15(assetsDir, urlPath.slice(1));
2293
3811
  if (!filePath.startsWith(assetsDir)) return c.text("Forbidden", 403);
2294
3812
  const s = await stat(filePath).catch(() => null);
2295
3813
  if (s?.isFile()) {
@@ -2298,7 +3816,7 @@ async function startServer({
2298
3816
  const body = await readFile2(filePath);
2299
3817
  return c.body(body, 200, { "Content-Type": mime });
2300
3818
  }
2301
- const indexPath = resolve9(assetsDir, "index.html");
3819
+ const indexPath = resolve15(assetsDir, "index.html");
2302
3820
  const indexStat = await stat(indexPath).catch(() => null);
2303
3821
  if (indexStat?.isFile()) {
2304
3822
  const body = await readFile2(indexPath, "utf-8");
@@ -2308,10 +3826,10 @@ async function startServer({
2308
3826
  });
2309
3827
  }
2310
3828
  const server = serve({ fetch: app_default.fetch, port, hostname });
2311
- await new Promise((resolve11, reject) => {
3829
+ await new Promise((resolve17, reject) => {
2312
3830
  server.on("listening", () => {
2313
3831
  logger_default.info("Volute UI running", { hostname, port });
2314
- resolve11();
3832
+ resolve17();
2315
3833
  });
2316
3834
  server.on("error", (err) => {
2317
3835
  reject(err);
@@ -2322,14 +3840,14 @@ async function startServer({
2322
3840
 
2323
3841
  // src/daemon.ts
2324
3842
  if (!process.env.VOLUTE_HOME) {
2325
- process.env.VOLUTE_HOME = resolve10(homedir(), ".volute");
3843
+ process.env.VOLUTE_HOME = resolve16(homedir2(), ".volute");
2326
3844
  }
2327
3845
  async function startDaemon(opts) {
2328
3846
  const { port, hostname } = opts;
2329
3847
  const myPid = String(process.pid);
2330
3848
  const home = voluteHome();
2331
3849
  if (!opts.foreground) {
2332
- const log2 = new RotatingLog(resolve10(home, "daemon.log"));
3850
+ const log2 = new RotatingLog(resolve16(home, "daemon.log"));
2333
3851
  const write2 = (...args) => log2.write(`${format(...args)}
2334
3852
  `);
2335
3853
  console.log = write2;
@@ -2337,9 +3855,9 @@ async function startDaemon(opts) {
2337
3855
  console.warn = write2;
2338
3856
  console.info = write2;
2339
3857
  }
2340
- const DAEMON_PID_PATH = resolve10(home, "daemon.pid");
2341
- const DAEMON_JSON_PATH = resolve10(home, "daemon.json");
2342
- mkdirSync3(home, { recursive: true });
3858
+ const DAEMON_PID_PATH = resolve16(home, "daemon.pid");
3859
+ const DAEMON_JSON_PATH = resolve16(home, "daemon.json");
3860
+ mkdirSync8(home, { recursive: true });
2343
3861
  const token = process.env.VOLUTE_DAEMON_TOKEN || randomBytes(32).toString("hex");
2344
3862
  process.env.VOLUTE_DAEMON_TOKEN = token;
2345
3863
  process.env.VOLUTE_DAEMON_PORT = String(port);
@@ -2355,10 +3873,10 @@ async function startDaemon(opts) {
2355
3873
  }
2356
3874
  throw err;
2357
3875
  }
2358
- writeFileSync2(DAEMON_PID_PATH, myPid, { mode: 384 });
2359
- writeFileSync2(DAEMON_JSON_PATH, `${JSON.stringify({ port, hostname, token }, null, 2)}
3876
+ writeFileSync7(DAEMON_PID_PATH, myPid, { mode: 420 });
3877
+ writeFileSync7(DAEMON_JSON_PATH, `${JSON.stringify({ port, hostname, token }, null, 2)}
2360
3878
  `, {
2361
- mode: 384
3879
+ mode: 420
2362
3880
  });
2363
3881
  const manager = initAgentManager();
2364
3882
  manager.loadCrashAttempts();
@@ -2410,13 +3928,13 @@ async function startDaemon(opts) {
2410
3928
  console.error(`[daemon] running on ${hostname}:${port}, pid ${myPid}`);
2411
3929
  function cleanup() {
2412
3930
  try {
2413
- if (readFileSync5(DAEMON_PID_PATH, "utf-8").trim() === myPid) {
3931
+ if (readFileSync9(DAEMON_PID_PATH, "utf-8").trim() === myPid) {
2414
3932
  unlinkSync2(DAEMON_PID_PATH);
2415
3933
  }
2416
3934
  } catch {
2417
3935
  }
2418
3936
  try {
2419
- const data = JSON.parse(readFileSync5(DAEMON_JSON_PATH, "utf-8"));
3937
+ const data = JSON.parse(readFileSync9(DAEMON_JSON_PATH, "utf-8"));
2420
3938
  if (data.token === token) {
2421
3939
  unlinkSync2(DAEMON_JSON_PATH);
2422
3940
  }