volute 0.9.0 → 0.10.1

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 (37) hide show
  1. package/dist/{agent-MB3OTRRK.js → agent-ECRX44DB.js} +5 -5
  2. package/dist/{agent-manager-CMMH5KQQ.js → agent-manager-4OCID725.js} +2 -2
  3. package/dist/{chunk-IQXBMFZG.js → chunk-46S7YHUB.js} +18 -6
  4. package/dist/{chunk-YNNK4QN2.js → chunk-FYQGANL6.js} +40 -2
  5. package/dist/{chunk-W6TMWYU3.js → chunk-KR6WRAJ4.js} +3 -3
  6. package/dist/chunk-R3VB7NF5.js +205 -0
  7. package/dist/cli.js +14 -14
  8. package/dist/create-VBZZNJOG.js +38 -0
  9. package/dist/daemon-restart-7X72OXOW.js +61 -0
  10. package/dist/daemon.js +1492 -170
  11. package/dist/delete-BOTVU4YO.js +35 -0
  12. package/dist/{down-4DGRZRJU.js → down-4LIQG3CE.js} +3 -1
  13. package/dist/import-2BZUWT23.js +21 -0
  14. package/dist/{package-WPX6LCYE.js → package-TNE337RE.js} +1 -1
  15. package/dist/{setup-7SPMWF2O.js → setup-6QFIHXSH.js} +5 -5
  16. package/dist/{up-J7AHQHIM.js → up-FCYL2IPZ.js} +1 -1
  17. package/dist/upgrade-RSE4CZNE.js +55 -0
  18. package/dist/variant-7IZF6OWO.js +215 -0
  19. package/package.json +1 -1
  20. package/dist/chunk-ECPQXRLB.js +0 -264
  21. package/dist/chunk-NETNFBA5.js +0 -28
  22. package/dist/chunk-XUA3JUFK.js +0 -121
  23. package/dist/create-HGJHLABX.js +0 -96
  24. package/dist/daemon-restart-EKDXXHKH.js +0 -28
  25. package/dist/delete-WKQKE3FT.js +0 -70
  26. package/dist/import-CNEDF3TD.js +0 -532
  27. package/dist/upgrade-BRNMSQBX.js +0 -233
  28. package/dist/variant-AQRAN6FR.js +0 -493
  29. package/dist/{channel-G5D4VBXY.js → channel-2WHBRDTD.js} +3 -3
  30. package/dist/{chunk-AWHQZDB4.js → chunk-M5AEQLB3.js} +0 -0
  31. package/dist/{connector-PK7D5GTN.js → connector-L2HBLZBW.js} +3 -3
  32. package/dist/{env-HZMZSWWD.js → env-CGORIKVF.js} +3 -3
  33. package/dist/{history-SH25BAA5.js → history-NI5QP27M.js} +3 -3
  34. package/dist/{logs-V54B6QSG.js → logs-APWVWGNX.js} +3 -3
  35. package/dist/{schedule-XGBUF7NU.js → schedule-E4MFGYSA.js} +3 -3
  36. package/dist/{send-TFZ62XPZ.js → send-X6OQGSD6.js} +4 -4
  37. package/dist/{service-56CY4S6Z.js → service-OW35VZ5G.js} +3 -3
package/dist/daemon.js CHANGED
@@ -6,16 +6,33 @@ import {
6
6
  initAgentManager,
7
7
  loadJsonMap,
8
8
  saveJsonMap
9
- } from "./chunk-W6TMWYU3.js";
9
+ } from "./chunk-KR6WRAJ4.js";
10
10
  import {
11
11
  checkForUpdate,
12
12
  checkForUpdateCached,
13
13
  getCurrentVersion
14
14
  } from "./chunk-RT6Y7AR3.js";
15
15
  import {
16
+ applyIsolation,
17
+ chownAgentDir,
18
+ createAgentUser,
19
+ deleteAgentUser,
20
+ ensureVoluteGroup,
21
+ getAgentUserIds,
22
+ isIsolationEnabled
23
+ } from "./chunk-46S7YHUB.js";
24
+ import {
25
+ exec,
26
+ resolveVoluteBin
27
+ } from "./chunk-5C5JWR2L.js";
28
+ import {
29
+ findOpenClawSession,
30
+ importOpenClawConnectors,
31
+ importPiSession,
32
+ parseNameFromIdentity,
16
33
  readVoluteConfig,
17
34
  writeVoluteConfig
18
- } from "./chunk-NETNFBA5.js";
35
+ } from "./chunk-R3VB7NF5.js";
19
36
  import {
20
37
  agentEnvPath,
21
38
  loadMergedEnv,
@@ -27,59 +44,43 @@ import {
27
44
  CHANNELS,
28
45
  getChannelDriver
29
46
  } from "./chunk-LIPPXNIE.js";
30
- import {
31
- agentMessages,
32
- approveUser,
33
- conversationParticipants,
34
- conversations,
35
- createUser,
36
- deleteAgentUser,
37
- getDb,
38
- getOrCreateAgentUser,
39
- getUser,
40
- getUserByUsername,
41
- listPendingUsers,
42
- listUsers,
43
- listUsersByType,
44
- messages,
45
- sessions,
46
- users,
47
- verifyUser
48
- } from "./chunk-ECPQXRLB.js";
47
+ import "./chunk-D424ZQGI.js";
49
48
  import {
50
49
  slugify,
51
50
  writeChannelEntry
52
51
  } from "./chunk-N6MLQ26B.js";
53
52
  import {
54
- applyIsolation
55
- } from "./chunk-IQXBMFZG.js";
56
- import {
57
- resolveVoluteBin
58
- } from "./chunk-5C5JWR2L.js";
59
- import {
53
+ addAgent,
54
+ addVariant,
60
55
  agentDir,
61
56
  checkHealth,
62
57
  daemonLoopback,
58
+ ensureVoluteHome,
63
59
  findAgent,
64
60
  findVariant,
65
61
  getAllRunningVariants,
62
+ nextPort,
66
63
  readRegistry,
67
64
  readVariants,
68
65
  removeAgent,
69
66
  removeAllVariants,
67
+ removeVariant,
70
68
  setAgentRunning,
71
69
  setVariantRunning,
72
70
  stateDir,
71
+ validateAgentName,
73
72
  validateBranchName,
74
73
  voluteHome
75
74
  } from "./chunk-DP2DX4WV.js";
76
- import "./chunk-K3NQKI34.js";
75
+ import {
76
+ __export
77
+ } from "./chunk-K3NQKI34.js";
77
78
 
78
79
  // src/daemon.ts
79
80
  import { randomBytes } from "crypto";
80
- import { mkdirSync as mkdirSync3, readFileSync as readFileSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
81
- import { homedir } from "os";
82
- 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";
83
84
  import { format } from "util";
84
85
 
85
86
  // src/lib/connector-manager.ts
@@ -319,19 +320,19 @@ var ConnectorManager = class {
319
320
  const stopKey = `${agentName}:${type}`;
320
321
  this.stopping.add(stopKey);
321
322
  agentMap.delete(type);
322
- await new Promise((resolve11) => {
323
- tracked.child.on("exit", () => resolve11());
323
+ await new Promise((resolve17) => {
324
+ tracked.child.on("exit", () => resolve17());
324
325
  try {
325
326
  tracked.child.kill("SIGTERM");
326
327
  } catch {
327
- resolve11();
328
+ resolve17();
328
329
  }
329
330
  setTimeout(() => {
330
331
  try {
331
332
  tracked.child.kill("SIGKILL");
332
333
  } catch {
333
334
  }
334
- resolve11();
335
+ resolve17();
335
336
  }, 5e3);
336
337
  });
337
338
  this.stopping.delete(stopKey);
@@ -739,9 +740,248 @@ function getTokenBudget() {
739
740
 
740
741
  // src/web/middleware/auth.ts
741
742
  import { timingSafeEqual } from "crypto";
742
- import { eq, lt } from "drizzle-orm";
743
+ import { eq as eq2, lt } from "drizzle-orm";
743
744
  import { getCookie } from "hono/cookie";
744
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
745
985
  function isValidDaemonToken(token) {
746
986
  const expected = process.env.VOLUTE_DAEMON_TOKEN;
747
987
  if (!expected || token.length !== expected.length) return false;
@@ -749,29 +989,29 @@ function isValidDaemonToken(token) {
749
989
  }
750
990
  var SESSION_MAX_AGE = 864e5;
751
991
  async function createSession(userId) {
752
- const db = await getDb();
992
+ const db2 = await getDb();
753
993
  const sessionId = crypto.randomUUID();
754
- await db.insert(sessions).values({ id: sessionId, userId, createdAt: Date.now() });
994
+ await db2.insert(sessions).values({ id: sessionId, userId, createdAt: Date.now() });
755
995
  return sessionId;
756
996
  }
757
997
  async function deleteSession(sessionId) {
758
- const db = await getDb();
759
- await db.delete(sessions).where(eq(sessions.id, sessionId));
998
+ const db2 = await getDb();
999
+ await db2.delete(sessions).where(eq2(sessions.id, sessionId));
760
1000
  }
761
1001
  async function getSessionUserId(sessionId) {
762
- const db = await getDb();
763
- 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();
764
1004
  if (!row) return void 0;
765
1005
  if (Date.now() - row.createdAt > SESSION_MAX_AGE) {
766
- await db.delete(sessions).where(eq(sessions.id, sessionId));
1006
+ await db2.delete(sessions).where(eq2(sessions.id, sessionId));
767
1007
  return void 0;
768
1008
  }
769
1009
  return row.userId;
770
1010
  }
771
1011
  async function cleanExpiredSessions() {
772
- const db = await getDb();
1012
+ const db2 = await getDb();
773
1013
  const cutoff = Date.now() - SESSION_MAX_AGE;
774
- await db.delete(sessions).where(lt(sessions.createdAt, cutoff));
1014
+ await db2.delete(sessions).where(lt(sessions.createdAt, cutoff));
775
1015
  }
776
1016
  var requireAdmin = createMiddleware(async (c, next) => {
777
1017
  const user = c.get("user");
@@ -802,9 +1042,9 @@ var authMiddleware = createMiddleware(async (c, next) => {
802
1042
  });
803
1043
 
804
1044
  // src/web/server.ts
805
- import { existsSync as existsSync7 } from "fs";
1045
+ import { existsSync as existsSync10 } from "fs";
806
1046
  import { readFile as readFile2, stat } from "fs/promises";
807
- import { dirname as dirname2, extname, resolve as resolve9 } from "path";
1047
+ import { dirname as dirname4, extname, resolve as resolve15 } from "path";
808
1048
  import { serve } from "@hono/node-server";
809
1049
 
810
1050
  // src/lib/log-buffer.ts
@@ -858,13 +1098,376 @@ import { csrf } from "hono/csrf";
858
1098
  import { HTTPException } from "hono/http-exception";
859
1099
 
860
1100
  // src/web/routes/agents.ts
861
- import { execFile } from "child_process";
862
- import { existsSync as existsSync4, readFileSync as readFileSync3, rmSync } from "fs";
863
- import { resolve as resolve5 } from "path";
864
- import { promisify } from "util";
865
- 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";
866
1112
  import { Hono } from "hono";
867
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
+
868
1471
  // src/lib/typing.ts
869
1472
  var DEFAULT_TTL_MS = 1e4;
870
1473
  var SWEEP_INTERVAL_MS = 5e3;
@@ -933,7 +1536,6 @@ function getTypingMap() {
933
1536
  }
934
1537
 
935
1538
  // src/web/routes/agents.ts
936
- var execFileAsync = promisify(execFile);
937
1539
  async function startAgentFull(name, baseName, variantName) {
938
1540
  await getAgentManager().startAgent(name);
939
1541
  if (variantName) return;
@@ -959,7 +1561,7 @@ function extractTextContent(content) {
959
1561
  }
960
1562
  function getDaemonPort() {
961
1563
  try {
962
- const data = JSON.parse(readFileSync3(resolve5(voluteHome(), "daemon.json"), "utf-8"));
1564
+ const data = JSON.parse(readFileSync6(resolve9(voluteHome(), "daemon.json"), "utf-8"));
963
1565
  return data.port;
964
1566
  } catch (err) {
965
1567
  if (err?.code !== "ENOENT") {
@@ -998,7 +1600,247 @@ async function getAgentStatus(name, port) {
998
1600
  }
999
1601
  return { status, channels };
1000
1602
  }
1001
- 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) => {
1002
1844
  const entries = readRegistry();
1003
1845
  const agents = await Promise.all(
1004
1846
  entries.map(async (entry) => {
@@ -1011,7 +1853,7 @@ var app = new Hono().get("/", async (c) => {
1011
1853
  const name = c.req.param("name");
1012
1854
  const entry = findAgent(name);
1013
1855
  if (!entry) return c.json({ error: "Agent not found" }, 404);
1014
- 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);
1015
1857
  const { status, channels } = await getAgentStatus(name, entry.port);
1016
1858
  const variants = readVariants(name);
1017
1859
  const manager = getAgentManager();
@@ -1037,7 +1879,7 @@ var app = new Hono().get("/", async (c) => {
1037
1879
  if (!variant) return c.json({ error: `Unknown variant: ${variantName}` }, 404);
1038
1880
  } else {
1039
1881
  const dir = agentDir(baseName);
1040
- if (!existsSync4(dir)) return c.json({ error: "Agent directory missing" }, 404);
1882
+ if (!existsSync6(dir)) return c.json({ error: "Agent directory missing" }, 404);
1041
1883
  }
1042
1884
  if (getAgentManager().isRunning(name)) {
1043
1885
  return c.json({ error: "Agent already running" }, 409);
@@ -1058,7 +1900,7 @@ var app = new Hono().get("/", async (c) => {
1058
1900
  if (!variant) return c.json({ error: `Unknown variant: ${variantName}` }, 404);
1059
1901
  } else {
1060
1902
  const dir = agentDir(baseName);
1061
- if (!existsSync4(dir)) return c.json({ error: "Agent directory missing" }, 404);
1903
+ if (!existsSync6(dir)) return c.json({ error: "Agent directory missing" }, 404);
1062
1904
  }
1063
1905
  let context;
1064
1906
  const contentType = c.req.header("content-type");
@@ -1086,21 +1928,58 @@ var app = new Hono().get("/", async (c) => {
1086
1928
  return c.json({ error: `Invalid variant name: ${branchErr}` }, 400);
1087
1929
  }
1088
1930
  console.error(`[daemon] merging variant for ${baseName}: ${mergeVariantName}`);
1089
- const mergeArgs = [
1090
- "variant",
1091
- "merge",
1092
- mergeVariantName,
1093
- "--agent",
1094
- baseName,
1095
- "--skip-verify"
1096
- ];
1097
- if (context.summary) mergeArgs.push("--summary", String(context.summary));
1098
- if (context.justification) mergeArgs.push("--justification", String(context.justification));
1099
- if (context.memory) mergeArgs.push("--memory", String(context.memory));
1100
- await execFileAsync("volute", mergeArgs, {
1101
- cwd: agentDir(baseName),
1102
- env: { ...process.env, VOLUTE_SUPERVISOR: "1" }
1103
- });
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
+ }
1104
1983
  }
1105
1984
  if (context) {
1106
1985
  manager.setPendingContext(name, context);
@@ -1148,15 +2027,144 @@ var app = new Hono().get("/", async (c) => {
1148
2027
  }
1149
2028
  removeAllVariants(name);
1150
2029
  removeAgent(name);
1151
- await deleteAgentUser(name);
2030
+ await deleteAgentUser2(name);
1152
2031
  const state = stateDir(name);
1153
- if (existsSync4(state)) {
1154
- rmSync(state, { recursive: true, force: true });
2032
+ if (existsSync6(state)) {
2033
+ rmSync2(state, { recursive: true, force: true });
1155
2034
  }
1156
- if (force && existsSync4(dir)) {
1157
- rmSync(dir, { recursive: true, force: true });
2035
+ if (force && existsSync6(dir)) {
2036
+ rmSync2(dir, { recursive: true, force: true });
2037
+ deleteAgentUser(name);
1158
2038
  }
1159
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
+ }
1160
2168
  }).post("/:name/message", async (c) => {
1161
2169
  const name = c.req.param("name");
1162
2170
  const [baseName, variantName] = name.split("@", 2);
@@ -1179,12 +2187,12 @@ var app = new Hono().get("/", async (c) => {
1179
2187
  console.error(`[daemon] failed to parse message body for ${baseName}:`, err);
1180
2188
  }
1181
2189
  const channel = parsed?.channel ?? "unknown";
1182
- const db = await getDb();
2190
+ const db2 = await getDb();
1183
2191
  if (parsed) {
1184
2192
  try {
1185
2193
  const sender2 = parsed.sender ?? null;
1186
2194
  const content = extractTextContent(parsed.content);
1187
- await db.insert(agentMessages).values({
2195
+ await db2.insert(agentMessages).values({
1188
2196
  agent: baseName,
1189
2197
  channel,
1190
2198
  sender: sender2,
@@ -1237,8 +2245,8 @@ var app = new Hono().get("/", async (c) => {
1237
2245
  body: forwardBody
1238
2246
  });
1239
2247
  if (!res.ok) {
1240
- const text = await res.text().catch(() => "");
1241
- 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}`);
1242
2250
  return c.json({ error: `Agent responded with ${res.status}` }, res.status);
1243
2251
  }
1244
2252
  let result;
@@ -1277,9 +2285,9 @@ var app = new Hono().get("/", async (c) => {
1277
2285
  if (!body.channel || !body.content) {
1278
2286
  return c.json({ error: "channel and content required" }, 400);
1279
2287
  }
1280
- const db = await getDb();
2288
+ const db2 = await getDb();
1281
2289
  try {
1282
- await db.insert(agentMessages).values({
2290
+ await db2.insert(agentMessages).values({
1283
2291
  agent: baseName,
1284
2292
  channel: body.channel,
1285
2293
  sender: body.sender ?? baseName,
@@ -1292,20 +2300,20 @@ var app = new Hono().get("/", async (c) => {
1292
2300
  return c.json({ ok: true });
1293
2301
  }).get("/:name/history/channels", async (c) => {
1294
2302
  const name = c.req.param("name");
1295
- const db = await getDb();
1296
- 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));
1297
2305
  return c.json(rows.map((r) => r.channel));
1298
2306
  }).get("/:name/history", async (c) => {
1299
2307
  const name = c.req.param("name");
1300
2308
  const channel = c.req.query("channel");
1301
2309
  const limit = Math.min(Math.max(parseInt(c.req.query("limit") ?? "50", 10) || 50, 1), 200);
1302
2310
  const offset = Math.max(parseInt(c.req.query("offset") ?? "0", 10) || 0, 0);
1303
- const db = await getDb();
1304
- const conditions = [eq2(agentMessages.agent, name)];
2311
+ const db2 = await getDb();
2312
+ const conditions = [eq3(agentMessages.agent, name)];
1305
2313
  if (channel) {
1306
- conditions.push(eq2(agentMessages.channel, channel));
2314
+ conditions.push(eq3(agentMessages.channel, channel));
1307
2315
  }
1308
- 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);
1309
2317
  return c.json(rows);
1310
2318
  });
1311
2319
  var agents_default = app;
@@ -1624,9 +2632,9 @@ var sharedEnvApp = new Hono5().get("/", (c) => {
1624
2632
  var env_default = app5;
1625
2633
 
1626
2634
  // src/web/routes/files.ts
1627
- import { existsSync as existsSync5 } from "fs";
2635
+ import { existsSync as existsSync7 } from "fs";
1628
2636
  import { readdir, readFile } from "fs/promises";
1629
- import { resolve as resolve6 } from "path";
2637
+ import { resolve as resolve10 } from "path";
1630
2638
  import { Hono as Hono6 } from "hono";
1631
2639
  var ALLOWED_FILES = /* @__PURE__ */ new Set(["SOUL.md", "MEMORY.md", "CLAUDE.md", "VOLUTE.md"]);
1632
2640
  var app6 = new Hono6().get("/:name/files", async (c) => {
@@ -1634,8 +2642,8 @@ var app6 = new Hono6().get("/:name/files", async (c) => {
1634
2642
  const entry = findAgent(name);
1635
2643
  if (!entry) return c.json({ error: "Agent not found" }, 404);
1636
2644
  const dir = agentDir(name);
1637
- const homeDir = resolve6(dir, "home");
1638
- 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);
1639
2647
  const allFiles = await readdir(homeDir);
1640
2648
  const files = allFiles.filter((f) => f.endsWith(".md") && ALLOWED_FILES.has(f));
1641
2649
  return c.json(files);
@@ -1648,8 +2656,8 @@ var app6 = new Hono6().get("/:name/files", async (c) => {
1648
2656
  const entry = findAgent(name);
1649
2657
  if (!entry) return c.json({ error: "Agent not found" }, 404);
1650
2658
  const dir = agentDir(name);
1651
- const filePath = resolve6(dir, "home", filename);
1652
- if (!existsSync5(filePath)) {
2659
+ const filePath = resolve10(dir, "home", filename);
2660
+ if (!existsSync7(filePath)) {
1653
2661
  return c.json({ error: "File not found" }, 404);
1654
2662
  }
1655
2663
  const content = await readFile(filePath, "utf-8");
@@ -1659,16 +2667,16 @@ var files_default = app6;
1659
2667
 
1660
2668
  // src/web/routes/logs.ts
1661
2669
  import { spawn as spawn2 } from "child_process";
1662
- import { existsSync as existsSync6 } from "fs";
1663
- import { resolve as resolve7 } from "path";
2670
+ import { existsSync as existsSync8 } from "fs";
2671
+ import { resolve as resolve11 } from "path";
1664
2672
  import { Hono as Hono7 } from "hono";
1665
2673
  import { streamSSE } from "hono/streaming";
1666
2674
  var app7 = new Hono7().get("/:name/logs", async (c) => {
1667
2675
  const name = c.req.param("name");
1668
2676
  const entry = findAgent(name);
1669
2677
  if (!entry) return c.json({ error: "Agent not found" }, 404);
1670
- const logFile = resolve7(stateDir(name), "logs", "agent.log");
1671
- if (!existsSync6(logFile)) {
2678
+ const logFile = resolve11(stateDir(name), "logs", "agent.log");
2679
+ if (!existsSync8(logFile)) {
1672
2680
  return c.json({ error: "No log file found" }, 404);
1673
2681
  }
1674
2682
  return streamSSE(c, async (stream) => {
@@ -1686,17 +2694,17 @@ var app7 = new Hono7().get("/:name/logs", async (c) => {
1686
2694
  stream.onAbort(() => {
1687
2695
  tail.kill();
1688
2696
  });
1689
- await new Promise((resolve11) => {
1690
- tail.on("exit", resolve11);
1691
- stream.onAbort(resolve11);
2697
+ await new Promise((resolve17) => {
2698
+ tail.on("exit", resolve17);
2699
+ stream.onAbort(resolve17);
1692
2700
  });
1693
2701
  });
1694
2702
  }).get("/:name/logs/tail", async (c) => {
1695
2703
  const name = c.req.param("name");
1696
2704
  const entry = findAgent(name);
1697
2705
  if (!entry) return c.json({ error: "Agent not found" }, 404);
1698
- const logFile = resolve7(stateDir(name), "logs", "agent.log");
1699
- if (!existsSync6(logFile)) {
2706
+ const logFile = resolve11(stateDir(name), "logs", "agent.log");
2707
+ if (!existsSync8(logFile)) {
1700
2708
  return c.json({ error: "No log file found" }, 404);
1701
2709
  }
1702
2710
  const nParam = parseInt(c.req.query("n") ?? "50", 10);
@@ -1706,8 +2714,8 @@ var app7 = new Hono7().get("/:name/logs", async (c) => {
1706
2714
  tail.stdout.on("data", (data) => {
1707
2715
  output += data.toString();
1708
2716
  });
1709
- await new Promise((resolve11) => {
1710
- tail.on("exit", resolve11);
2717
+ await new Promise((resolve17) => {
2718
+ tail.on("exit", resolve17);
1711
2719
  });
1712
2720
  return c.text(output);
1713
2721
  });
@@ -1798,7 +2806,13 @@ var schedules_default = app8;
1798
2806
  // src/web/routes/system.ts
1799
2807
  import { Hono as Hono9 } from "hono";
1800
2808
  import { streamSSE as streamSSE2 } from "hono/streaming";
1801
- var app9 = new Hono9().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) => {
1802
2816
  const user = c.get("user");
1803
2817
  if (user.role !== "admin") return c.json({ error: "Forbidden" }, 403);
1804
2818
  return streamSSE2(c, async (stream) => {
@@ -1809,10 +2823,10 @@ var app9 = new Hono9().get("/logs", async (c) => {
1809
2823
  stream.writeSSE({ data: JSON.stringify(entry) }).catch(() => {
1810
2824
  });
1811
2825
  });
1812
- await new Promise((resolve11) => {
2826
+ await new Promise((resolve17) => {
1813
2827
  stream.onAbort(() => {
1814
2828
  unsubscribe();
1815
- resolve11();
2829
+ resolve17();
1816
2830
  });
1817
2831
  });
1818
2832
  });
@@ -1869,7 +2883,124 @@ var app11 = new Hono11().get("/update", async (c) => {
1869
2883
  var update_default = app11;
1870
2884
 
1871
2885
  // src/web/routes/variants.ts
2886
+ import { existsSync as existsSync9, mkdirSync as mkdirSync7, writeFileSync as writeFileSync6 } from "fs";
2887
+ import { resolve as resolve13 } from "path";
1872
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
1873
3004
  var app12 = new Hono12().get("/:name/variants", async (c) => {
1874
3005
  const name = c.req.param("name");
1875
3006
  const entry = findAgent(name);
@@ -1883,12 +3014,203 @@ var app12 = new Hono12().get("/:name/variants", async (c) => {
1883
3014
  })
1884
3015
  );
1885
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 });
1886
3208
  });
1887
3209
  var variants_default = app12;
1888
3210
 
1889
3211
  // src/web/routes/volute/chat.ts
1890
- import { readFileSync as readFileSync4 } from "fs";
1891
- import { resolve as resolve8 } from "path";
3212
+ import { readFileSync as readFileSync8 } from "fs";
3213
+ import { resolve as resolve14 } from "path";
1892
3214
  import { zValidator as zValidator3 } from "@hono/zod-validator";
1893
3215
  import { Hono as Hono13 } from "hono";
1894
3216
  import { streamSSE as streamSSE3 } from "hono/streaming";
@@ -1923,12 +3245,12 @@ function publish(conversationId, event) {
1923
3245
  }
1924
3246
 
1925
3247
  // src/lib/conversations.ts
1926
- import { randomUUID } from "crypto";
1927
- 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";
1928
3250
  async function createConversation(agentName, channel, opts) {
1929
- const db = await getDb();
1930
- const id = randomUUID();
1931
- await db.insert(conversations).values({
3251
+ const db2 = await getDb();
3252
+ const id = randomUUID2();
3253
+ await db2.insert(conversations).values({
1932
3254
  id,
1933
3255
  agent_name: agentName,
1934
3256
  channel,
@@ -1936,7 +3258,7 @@ async function createConversation(agentName, channel, opts) {
1936
3258
  title: opts?.title ?? null
1937
3259
  });
1938
3260
  if (opts?.participantIds && opts.participantIds.length > 0) {
1939
- await db.insert(conversationParticipants).values(
3261
+ await db2.insert(conversationParticipants).values(
1940
3262
  opts.participantIds.map((uid, i) => ({
1941
3263
  conversation_id: id,
1942
3264
  user_id: uid,
@@ -1955,41 +3277,41 @@ async function createConversation(agentName, channel, opts) {
1955
3277
  };
1956
3278
  }
1957
3279
  async function getConversation(id) {
1958
- const db = await getDb();
1959
- 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();
1960
3282
  return row ?? null;
1961
3283
  }
1962
3284
  async function getParticipants(conversationId) {
1963
- const db = await getDb();
1964
- const rows = await db.select({
3285
+ const db2 = await getDb();
3286
+ const rows = await db2.select({
1965
3287
  userId: conversationParticipants.user_id,
1966
3288
  username: users.username,
1967
3289
  userType: users.user_type,
1968
3290
  role: conversationParticipants.role
1969
- }).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();
1970
3292
  return rows;
1971
3293
  }
1972
3294
  async function isParticipant(conversationId, userId) {
1973
- const db = await getDb();
1974
- const row = await db.select({ user_id: conversationParticipants.user_id }).from(conversationParticipants).where(
1975
- and2(
1976
- eq3(conversationParticipants.conversation_id, conversationId),
1977
- 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)
1978
3300
  )
1979
3301
  ).get();
1980
3302
  return row != null;
1981
3303
  }
1982
3304
  async function listConversationsForUser(userId) {
1983
- const db = await getDb();
1984
- 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();
1985
3307
  if (participantRows.length === 0) return [];
1986
3308
  const convIds = participantRows.map((r) => r.conversation_id);
1987
- 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();
1988
3310
  }
1989
3311
  async function isParticipantOrOwner(conversationId, userId) {
1990
3312
  if (await isParticipant(conversationId, userId)) return true;
1991
- const db = await getDb();
1992
- 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();
1993
3315
  return row != null;
1994
3316
  }
1995
3317
  async function deleteConversationForUser(id, userId) {
@@ -1998,15 +3320,15 @@ async function deleteConversationForUser(id, userId) {
1998
3320
  return true;
1999
3321
  }
2000
3322
  async function addMessage(conversationId, role, senderName, content) {
2001
- const db = await getDb();
3323
+ const db2 = await getDb();
2002
3324
  const serialized = JSON.stringify(content);
2003
- 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 });
2004
- 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));
2005
3327
  if (role === "user") {
2006
3328
  const firstText = content.find((b) => b.type === "text");
2007
3329
  const title = firstText ? firstText.text.slice(0, 80) : "";
2008
3330
  if (title) {
2009
- 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)));
2010
3332
  }
2011
3333
  }
2012
3334
  const msg = {
@@ -2028,8 +3350,8 @@ async function addMessage(conversationId, role, senderName, content) {
2028
3350
  return msg;
2029
3351
  }
2030
3352
  async function getMessages(conversationId) {
2031
- const db = await getDb();
2032
- 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();
2033
3355
  return rows.map((row) => {
2034
3356
  let content;
2035
3357
  try {
@@ -2044,15 +3366,15 @@ async function getMessages(conversationId) {
2044
3366
  async function listConversationsWithParticipants(userId) {
2045
3367
  const convs = await listConversationsForUser(userId);
2046
3368
  if (convs.length === 0) return [];
2047
- const db = await getDb();
3369
+ const db2 = await getDb();
2048
3370
  const convIds = convs.map((c) => c.id);
2049
- const rows = await db.select({
3371
+ const rows = await db2.select({
2050
3372
  conversationId: conversationParticipants.conversation_id,
2051
3373
  userId: users.id,
2052
3374
  username: users.username,
2053
3375
  userType: users.user_type,
2054
3376
  role: conversationParticipants.role
2055
- }).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));
2056
3378
  const byConv = /* @__PURE__ */ new Map();
2057
3379
  for (const r of rows) {
2058
3380
  let arr = byConv.get(r.conversationId);
@@ -2070,10 +3392,10 @@ async function listConversationsWithParticipants(userId) {
2070
3392
  return convs.map((c) => ({ ...c, participants: byConv.get(c.id) ?? [] }));
2071
3393
  }
2072
3394
  async function findDMConversation(agentName, participantIds) {
2073
- const db = await getDb();
2074
- 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();
2075
3397
  for (const conv of agentConvs) {
2076
- 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();
2077
3399
  if (rows.length !== 2) continue;
2078
3400
  const ids = new Set(rows.map((r) => r.user_id));
2079
3401
  if (ids.has(participantIds[0]) && ids.has(participantIds[1])) {
@@ -2083,8 +3405,8 @@ async function findDMConversation(agentName, participantIds) {
2083
3405
  return null;
2084
3406
  }
2085
3407
  async function deleteConversation(id) {
2086
- const db = await getDb();
2087
- await db.delete(conversations).where(eq3(conversations.id, id));
3408
+ const db2 = await getDb();
3409
+ await db2.delete(conversations).where(eq4(conversations.id, id));
2088
3410
  }
2089
3411
 
2090
3412
  // src/web/routes/volute/chat.ts
@@ -2101,7 +3423,7 @@ var chatSchema = z3.object({
2101
3423
  });
2102
3424
  function getDaemonUrl() {
2103
3425
  try {
2104
- const data = JSON.parse(readFileSync4(resolve8(voluteHome(), "daemon.json"), "utf-8"));
3426
+ const data = JSON.parse(readFileSync8(resolve14(voluteHome(), "daemon.json"), "utf-8"));
2105
3427
  return `http://${daemonLoopback()}:${data.port}`;
2106
3428
  } catch (err) {
2107
3429
  throw new Error(`Failed to read daemon config: ${err instanceof Error ? err.message : err}`);
@@ -2179,7 +3501,7 @@ var app13 = new Hono13().post("/:name/chat", zValidator3("json", chatSchema), as
2179
3501
  const participants = await getParticipants(conversationId);
2180
3502
  const agentParticipants = participants.filter((p) => p.userType === "agent");
2181
3503
  const participantNames = participants.map((p) => p.username);
2182
- const { getAgentManager: getAgentManager2 } = await import("./agent-manager-CMMH5KQQ.js");
3504
+ const { getAgentManager: getAgentManager2 } = await import("./agent-manager-4OCID725.js");
2183
3505
  const manager = getAgentManager2();
2184
3506
  const runningAgents = agentParticipants.map((ap) => {
2185
3507
  const agentKey = ap.username === baseName ? name : ap.username;
@@ -2215,8 +3537,8 @@ var app13 = new Hono13().post("/:name/chat", zValidator3("json", chatSchema), as
2215
3537
  const targetName = agentName === baseName ? name : agentName;
2216
3538
  daemonFetchInternal(`/api/agents/${encodeURIComponent(targetName)}/message`, payload).then(async (res) => {
2217
3539
  if (!res.ok) {
2218
- const text = await res.text().catch(() => "");
2219
- 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}`);
2220
3542
  }
2221
3543
  }).catch((err) => {
2222
3544
  console.error(`[chat] agent ${agentName} unreachable via daemon:`, err);
@@ -2240,11 +3562,11 @@ var app13 = new Hono13().post("/:name/chat", zValidator3("json", chatSchema), as
2240
3562
  if (!stream.aborted) console.error("[chat] SSE ping error:", err);
2241
3563
  });
2242
3564
  }, 15e3);
2243
- await new Promise((resolve11) => {
3565
+ await new Promise((resolve17) => {
2244
3566
  stream.onAbort(() => {
2245
3567
  unsubscribe();
2246
3568
  clearInterval(keepAlive);
2247
- resolve11();
3569
+ resolve17();
2248
3570
  });
2249
3571
  });
2250
3572
  });
@@ -2472,20 +3794,20 @@ async function startServer({
2472
3794
  hostname = "127.0.0.1"
2473
3795
  }) {
2474
3796
  let assetsDir = "";
2475
- let searchDir = dirname2(new URL(import.meta.url).pathname);
3797
+ let searchDir = dirname4(new URL(import.meta.url).pathname);
2476
3798
  for (let i = 0; i < 5; i++) {
2477
- const candidate = resolve9(searchDir, "dist", "web-assets");
2478
- if (existsSync7(candidate)) {
3799
+ const candidate = resolve15(searchDir, "dist", "web-assets");
3800
+ if (existsSync10(candidate)) {
2479
3801
  assetsDir = candidate;
2480
3802
  break;
2481
3803
  }
2482
- searchDir = dirname2(searchDir);
3804
+ searchDir = dirname4(searchDir);
2483
3805
  }
2484
3806
  if (assetsDir) {
2485
3807
  app_default.get("*", async (c) => {
2486
3808
  const urlPath = new URL(c.req.url).pathname;
2487
3809
  if (urlPath.startsWith("/api/")) return c.notFound();
2488
- const filePath = resolve9(assetsDir, urlPath.slice(1));
3810
+ const filePath = resolve15(assetsDir, urlPath.slice(1));
2489
3811
  if (!filePath.startsWith(assetsDir)) return c.text("Forbidden", 403);
2490
3812
  const s = await stat(filePath).catch(() => null);
2491
3813
  if (s?.isFile()) {
@@ -2494,7 +3816,7 @@ async function startServer({
2494
3816
  const body = await readFile2(filePath);
2495
3817
  return c.body(body, 200, { "Content-Type": mime });
2496
3818
  }
2497
- const indexPath = resolve9(assetsDir, "index.html");
3819
+ const indexPath = resolve15(assetsDir, "index.html");
2498
3820
  const indexStat = await stat(indexPath).catch(() => null);
2499
3821
  if (indexStat?.isFile()) {
2500
3822
  const body = await readFile2(indexPath, "utf-8");
@@ -2504,10 +3826,10 @@ async function startServer({
2504
3826
  });
2505
3827
  }
2506
3828
  const server = serve({ fetch: app_default.fetch, port, hostname });
2507
- await new Promise((resolve11, reject) => {
3829
+ await new Promise((resolve17, reject) => {
2508
3830
  server.on("listening", () => {
2509
3831
  logger_default.info("Volute UI running", { hostname, port });
2510
- resolve11();
3832
+ resolve17();
2511
3833
  });
2512
3834
  server.on("error", (err) => {
2513
3835
  reject(err);
@@ -2518,14 +3840,14 @@ async function startServer({
2518
3840
 
2519
3841
  // src/daemon.ts
2520
3842
  if (!process.env.VOLUTE_HOME) {
2521
- process.env.VOLUTE_HOME = resolve10(homedir(), ".volute");
3843
+ process.env.VOLUTE_HOME = resolve16(homedir2(), ".volute");
2522
3844
  }
2523
3845
  async function startDaemon(opts) {
2524
3846
  const { port, hostname } = opts;
2525
3847
  const myPid = String(process.pid);
2526
3848
  const home = voluteHome();
2527
3849
  if (!opts.foreground) {
2528
- const log2 = new RotatingLog(resolve10(home, "daemon.log"));
3850
+ const log2 = new RotatingLog(resolve16(home, "daemon.log"));
2529
3851
  const write2 = (...args) => log2.write(`${format(...args)}
2530
3852
  `);
2531
3853
  console.log = write2;
@@ -2533,9 +3855,9 @@ async function startDaemon(opts) {
2533
3855
  console.warn = write2;
2534
3856
  console.info = write2;
2535
3857
  }
2536
- const DAEMON_PID_PATH = resolve10(home, "daemon.pid");
2537
- const DAEMON_JSON_PATH = resolve10(home, "daemon.json");
2538
- 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 });
2539
3861
  const token = process.env.VOLUTE_DAEMON_TOKEN || randomBytes(32).toString("hex");
2540
3862
  process.env.VOLUTE_DAEMON_TOKEN = token;
2541
3863
  process.env.VOLUTE_DAEMON_PORT = String(port);
@@ -2551,8 +3873,8 @@ async function startDaemon(opts) {
2551
3873
  }
2552
3874
  throw err;
2553
3875
  }
2554
- writeFileSync2(DAEMON_PID_PATH, myPid, { mode: 420 });
2555
- 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)}
2556
3878
  `, {
2557
3879
  mode: 420
2558
3880
  });
@@ -2606,13 +3928,13 @@ async function startDaemon(opts) {
2606
3928
  console.error(`[daemon] running on ${hostname}:${port}, pid ${myPid}`);
2607
3929
  function cleanup() {
2608
3930
  try {
2609
- if (readFileSync5(DAEMON_PID_PATH, "utf-8").trim() === myPid) {
3931
+ if (readFileSync9(DAEMON_PID_PATH, "utf-8").trim() === myPid) {
2610
3932
  unlinkSync2(DAEMON_PID_PATH);
2611
3933
  }
2612
3934
  } catch {
2613
3935
  }
2614
3936
  try {
2615
- const data = JSON.parse(readFileSync5(DAEMON_JSON_PATH, "utf-8"));
3937
+ const data = JSON.parse(readFileSync9(DAEMON_JSON_PATH, "utf-8"));
2616
3938
  if (data.token === token) {
2617
3939
  unlinkSync2(DAEMON_JSON_PATH);
2618
3940
  }