volute 0.12.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agent-2AQPI3QV.js → agent-HYX2ZTFM.js} +10 -10
- package/dist/{agent-manager-RMWXST3T.js → agent-manager-HHBAAL2D.js} +4 -4
- package/dist/{channel-2WHBRDTD.js → channel-72YET5JC.js} +2 -2
- package/dist/{chunk-QF22MYDJ.js → chunk-BJDLYTPS.js} +1 -1
- package/dist/{chunk-46S7YHUB.js → chunk-EN3NHRQC.js} +12 -19
- package/dist/{chunk-DP2DX4WV.js → chunk-ESTOWEG2.js} +12 -3
- package/dist/{chunk-POLMWJFC.js → chunk-GPZCPGV3.js} +1 -1
- package/dist/{chunk-ZKNBD5P3.js → chunk-IELXXS7E.js} +2 -2
- package/dist/{chunk-STOEJOJO.js → chunk-J3IHIXDB.js} +1 -1
- package/dist/{chunk-R3VB7NF5.js → chunk-KYHC7LHS.js} +2 -2
- package/dist/{chunk-YY2QX2J6.js → chunk-LG4ROCHN.js} +1 -1
- package/dist/{chunk-PHJCXAWJ.js → chunk-NQKKTRET.js} +19 -37
- package/dist/{chunk-IZEQ47HW.js → chunk-PEQQ7MRI.js} +2 -2
- package/dist/{chunk-WKPMR5B2.js → chunk-PTK3GBCG.js} +2 -2
- package/dist/{chunk-RGWADNLT.js → chunk-RH3XLDY2.js} +2 -2
- package/dist/{chunk-WTJI3JVR.js → chunk-VRAOTXDF.js} +9 -6
- package/dist/chunk-XUA3JUFK.js +121 -0
- package/dist/cli.js +26 -17
- package/dist/{connector-L2HBLZBW.js → connector-Z5KYVTZ5.js} +2 -2
- package/dist/connectors/discord.js +2 -2
- package/dist/connectors/slack.js +2 -2
- package/dist/connectors/telegram.js +2 -2
- package/dist/{create-VBZZNJOG.js → create-AAI52BC2.js} +1 -1
- package/dist/{daemon-client-P44NU3KU.js → daemon-client-WXN43USO.js} +2 -2
- package/dist/{daemon-restart-IVJ7X4PF.js → daemon-restart-4OXIGWV6.js} +6 -5
- package/dist/daemon.js +502 -540
- package/dist/{delete-BOTVU4YO.js → delete-BJ3LNU2I.js} +1 -1
- package/dist/down-IMZE7V42.js +14 -0
- package/dist/{env-CGORIKVF.js → env-EOO2C7L7.js} +2 -2
- package/dist/{history-NI5QP27M.js → history-J7TURCZS.js} +2 -2
- package/dist/{import-2BZUWT23.js → import-TKF67X4R.js} +3 -3
- package/dist/{logs-APWVWGNX.js → logs-KHBOS6IZ.js} +2 -2
- package/dist/{package-5LQNWBH7.js → package-TPXKJXLG.js} +1 -1
- package/dist/{restart-CCYM3MEC.js → restart-CMP63H6A.js} +2 -2
- package/dist/{schedule-E4MFGYSA.js → schedule-YMAJZ52M.js} +2 -2
- package/dist/seed-M6QPHFTV.js +68 -0
- package/dist/{send-V5ESR5O2.js → send-EZYEIVMA.js} +18 -6
- package/dist/{service-UL3OCODG.js → service-DDFZA7Q3.js} +4 -3
- package/dist/{setup-7N4KYOYN.js → setup-DCZWBBD7.js} +7 -7
- package/dist/sprout-EAWETTWZ.js +89 -0
- package/dist/{start-6YRS6FF6.js → start-54JXJ7VV.js} +2 -2
- package/dist/{status-DFWM342I.js → status-PMMS4XUH.js} +7 -5
- package/dist/{stop-UQSNF4CG.js → stop-WXU42NWP.js} +2 -2
- package/dist/{up-A6XT6AFX.js → up-N2OWDCT6.js} +5 -4
- package/dist/{update-PV3XM6DX.js → update-FXLGIIWH.js} +5 -4
- package/dist/{update-check-YPGH5X4E.js → update-check-NBHTSTHK.js} +2 -2
- package/dist/{upgrade-RSE4CZNE.js → upgrade-TEI7N6CQ.js} +1 -1
- package/dist/{variant-7IZF6OWO.js → variant-ZCZS3JAP.js} +4 -4
- package/dist/web-assets/assets/index-TqXd1QOX.js +307 -0
- package/dist/web-assets/index.html +1 -1
- package/package.json +1 -1
- package/templates/_base/_skills/orientation/SKILL.md +58 -0
- package/templates/_base/home/.config/config.json.tmpl +3 -0
- package/templates/_base/src/lib/startup.ts +8 -4
- package/templates/agent-sdk/volute-template.json +1 -1
- package/templates/pi/home/.config/config.json.tmpl +3 -0
- package/templates/pi/volute-template.json +1 -1
- package/dist/down-O2EQJ5DO.js +0 -13
- package/dist/web-assets/assets/index-D-3zx6vs.js +0 -307
- package/templates/_base/home/.config/volute.json.tmpl +0 -3
- package/templates/pi/home/.config/volute.json.tmpl +0 -3
package/dist/daemon.js
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
applyInitFiles,
|
|
4
|
+
composeTemplate,
|
|
5
|
+
copyTemplateToDir,
|
|
6
|
+
findTemplatesRoot,
|
|
7
|
+
listFiles
|
|
8
|
+
} from "./chunk-XUA3JUFK.js";
|
|
2
9
|
import {
|
|
3
10
|
RotatingLog,
|
|
4
11
|
clearJsonMap,
|
|
@@ -6,16 +13,7 @@ import {
|
|
|
6
13
|
initAgentManager,
|
|
7
14
|
loadJsonMap,
|
|
8
15
|
saveJsonMap
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
import {
|
|
11
|
-
applyIsolation,
|
|
12
|
-
chownAgentDir,
|
|
13
|
-
createAgentUser,
|
|
14
|
-
deleteAgentUser,
|
|
15
|
-
ensureVoluteGroup,
|
|
16
|
-
getAgentUserIds,
|
|
17
|
-
isIsolationEnabled
|
|
18
|
-
} from "./chunk-46S7YHUB.js";
|
|
16
|
+
} from "./chunk-NQKKTRET.js";
|
|
19
17
|
import {
|
|
20
18
|
findOpenClawSession,
|
|
21
19
|
importOpenClawConnectors,
|
|
@@ -23,33 +21,41 @@ import {
|
|
|
23
21
|
parseNameFromIdentity,
|
|
24
22
|
readVoluteConfig,
|
|
25
23
|
writeVoluteConfig
|
|
26
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-KYHC7LHS.js";
|
|
27
25
|
import {
|
|
28
26
|
agentEnvPath,
|
|
29
27
|
loadMergedEnv,
|
|
30
28
|
readEnv,
|
|
31
29
|
sharedEnvPath,
|
|
32
30
|
writeEnv
|
|
33
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-BJDLYTPS.js";
|
|
34
32
|
import {
|
|
35
33
|
CHANNELS,
|
|
36
34
|
getChannelDriver
|
|
37
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-PTK3GBCG.js";
|
|
38
36
|
import {
|
|
39
37
|
exec,
|
|
40
38
|
gitExec,
|
|
41
39
|
resolveVoluteBin
|
|
42
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-VRAOTXDF.js";
|
|
41
|
+
import {
|
|
42
|
+
chownAgentDir,
|
|
43
|
+
createAgentUser,
|
|
44
|
+
deleteAgentUser,
|
|
45
|
+
ensureVoluteGroup,
|
|
46
|
+
isIsolationEnabled,
|
|
47
|
+
wrapForIsolation
|
|
48
|
+
} from "./chunk-EN3NHRQC.js";
|
|
43
49
|
import {
|
|
44
50
|
checkForUpdate,
|
|
45
51
|
checkForUpdateCached,
|
|
46
52
|
getCurrentVersion
|
|
47
|
-
} from "./chunk-
|
|
53
|
+
} from "./chunk-LG4ROCHN.js";
|
|
48
54
|
import "./chunk-D424ZQGI.js";
|
|
49
55
|
import {
|
|
50
56
|
buildVoluteSlug,
|
|
51
57
|
writeChannelEntry
|
|
52
|
-
} from "./chunk-
|
|
58
|
+
} from "./chunk-GPZCPGV3.js";
|
|
53
59
|
import {
|
|
54
60
|
addAgent,
|
|
55
61
|
addVariant,
|
|
@@ -72,16 +78,16 @@ import {
|
|
|
72
78
|
validateAgentName,
|
|
73
79
|
validateBranchName,
|
|
74
80
|
voluteHome
|
|
75
|
-
} from "./chunk-
|
|
81
|
+
} from "./chunk-ESTOWEG2.js";
|
|
76
82
|
import {
|
|
77
83
|
__export
|
|
78
84
|
} from "./chunk-K3NQKI34.js";
|
|
79
85
|
|
|
80
86
|
// src/daemon.ts
|
|
81
87
|
import { randomBytes } from "crypto";
|
|
82
|
-
import { mkdirSync as
|
|
88
|
+
import { mkdirSync as mkdirSync7, readFileSync as readFileSync8, unlinkSync as unlinkSync2, writeFileSync as writeFileSync6 } from "fs";
|
|
83
89
|
import { homedir as homedir2 } from "os";
|
|
84
|
-
import { resolve as
|
|
90
|
+
import { resolve as resolve15 } from "path";
|
|
85
91
|
import { format } from "util";
|
|
86
92
|
|
|
87
93
|
// src/lib/connector-manager.ts
|
|
@@ -277,8 +283,8 @@ var ConnectorManager = class {
|
|
|
277
283
|
...connectorEnv
|
|
278
284
|
}
|
|
279
285
|
};
|
|
280
|
-
|
|
281
|
-
const child = spawn(
|
|
286
|
+
const [spawnCmd, spawnArgs] = wrapForIsolation(runtime, [connectorScript], agentName);
|
|
287
|
+
const child = spawn(spawnCmd, spawnArgs, spawnOpts);
|
|
282
288
|
let lastStderr = "";
|
|
283
289
|
child.stdout?.pipe(logStream);
|
|
284
290
|
child.stderr?.on("data", (chunk) => {
|
|
@@ -332,19 +338,19 @@ var ConnectorManager = class {
|
|
|
332
338
|
const stopKey = `${agentName}:${type}`;
|
|
333
339
|
this.stopping.add(stopKey);
|
|
334
340
|
agentMap.delete(type);
|
|
335
|
-
await new Promise((
|
|
336
|
-
tracked.child.on("exit", () =>
|
|
341
|
+
await new Promise((resolve16) => {
|
|
342
|
+
tracked.child.on("exit", () => resolve16());
|
|
337
343
|
try {
|
|
338
344
|
tracked.child.kill("SIGTERM");
|
|
339
345
|
} catch {
|
|
340
|
-
|
|
346
|
+
resolve16();
|
|
341
347
|
}
|
|
342
348
|
setTimeout(() => {
|
|
343
349
|
try {
|
|
344
350
|
tracked.child.kill("SIGKILL");
|
|
345
351
|
} catch {
|
|
346
352
|
}
|
|
347
|
-
|
|
353
|
+
resolve16();
|
|
348
354
|
}, 5e3);
|
|
349
355
|
});
|
|
350
356
|
this.stopping.delete(stopKey);
|
|
@@ -1054,9 +1060,9 @@ var authMiddleware = createMiddleware(async (c, next) => {
|
|
|
1054
1060
|
});
|
|
1055
1061
|
|
|
1056
1062
|
// src/web/server.ts
|
|
1057
|
-
import { existsSync as
|
|
1063
|
+
import { existsSync as existsSync9 } from "fs";
|
|
1058
1064
|
import { readFile as readFile2, stat } from "fs/promises";
|
|
1059
|
-
import { dirname as
|
|
1065
|
+
import { dirname as dirname3, extname, resolve as resolve14 } from "path";
|
|
1060
1066
|
import { serve } from "@hono/node-server";
|
|
1061
1067
|
|
|
1062
1068
|
// src/lib/log-buffer.ts
|
|
@@ -1111,17 +1117,19 @@ import { HTTPException } from "hono/http-exception";
|
|
|
1111
1117
|
|
|
1112
1118
|
// src/web/routes/agents.ts
|
|
1113
1119
|
import {
|
|
1114
|
-
cpSync
|
|
1115
|
-
existsSync as
|
|
1116
|
-
mkdirSync as
|
|
1117
|
-
readdirSync as
|
|
1118
|
-
readFileSync as
|
|
1119
|
-
rmSync
|
|
1120
|
-
writeFileSync as
|
|
1120
|
+
cpSync,
|
|
1121
|
+
existsSync as existsSync5,
|
|
1122
|
+
mkdirSync as mkdirSync4,
|
|
1123
|
+
readdirSync as readdirSync3,
|
|
1124
|
+
readFileSync as readFileSync5,
|
|
1125
|
+
rmSync,
|
|
1126
|
+
writeFileSync as writeFileSync4
|
|
1121
1127
|
} from "fs";
|
|
1122
|
-
import { resolve as
|
|
1123
|
-
import {
|
|
1128
|
+
import { resolve as resolve8 } from "path";
|
|
1129
|
+
import { zValidator } from "@hono/zod-validator";
|
|
1130
|
+
import { and as and3, desc as desc2, eq as eq4, sql as sql3 } from "drizzle-orm";
|
|
1124
1131
|
import { Hono } from "hono";
|
|
1132
|
+
import { z } from "zod";
|
|
1125
1133
|
|
|
1126
1134
|
// src/lib/consolidate.ts
|
|
1127
1135
|
import { readdirSync as readdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
@@ -1194,14 +1202,209 @@ ${content2}`);
|
|
|
1194
1202
|
}
|
|
1195
1203
|
}
|
|
1196
1204
|
|
|
1197
|
-
// src/lib/
|
|
1205
|
+
// src/lib/conversations.ts
|
|
1198
1206
|
import { randomUUID } from "crypto";
|
|
1207
|
+
import { and as and2, desc, eq as eq3, inArray, isNull, sql as sql2 } from "drizzle-orm";
|
|
1208
|
+
|
|
1209
|
+
// src/lib/conversation-events.ts
|
|
1210
|
+
var subscribers = /* @__PURE__ */ new Map();
|
|
1211
|
+
function subscribe(conversationId, callback) {
|
|
1212
|
+
let set = subscribers.get(conversationId);
|
|
1213
|
+
if (!set) {
|
|
1214
|
+
set = /* @__PURE__ */ new Set();
|
|
1215
|
+
subscribers.set(conversationId, set);
|
|
1216
|
+
}
|
|
1217
|
+
set.add(callback);
|
|
1218
|
+
return () => {
|
|
1219
|
+
set.delete(callback);
|
|
1220
|
+
if (set.size === 0) subscribers.delete(conversationId);
|
|
1221
|
+
};
|
|
1222
|
+
}
|
|
1223
|
+
function publish(conversationId, event) {
|
|
1224
|
+
const set = subscribers.get(conversationId);
|
|
1225
|
+
if (!set) return;
|
|
1226
|
+
for (const cb of set) {
|
|
1227
|
+
try {
|
|
1228
|
+
cb(event);
|
|
1229
|
+
} catch (err) {
|
|
1230
|
+
console.error("[conversation-events] subscriber threw:", err);
|
|
1231
|
+
set.delete(cb);
|
|
1232
|
+
if (set.size === 0) subscribers.delete(conversationId);
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
// src/lib/conversations.ts
|
|
1238
|
+
async function createConversation(agentName, channel, opts) {
|
|
1239
|
+
const db2 = await getDb();
|
|
1240
|
+
const id = randomUUID();
|
|
1241
|
+
await db2.insert(conversations).values({
|
|
1242
|
+
id,
|
|
1243
|
+
agent_name: agentName,
|
|
1244
|
+
channel,
|
|
1245
|
+
user_id: opts?.userId ?? null,
|
|
1246
|
+
title: opts?.title ?? null
|
|
1247
|
+
});
|
|
1248
|
+
if (opts?.participantIds && opts.participantIds.length > 0) {
|
|
1249
|
+
await db2.insert(conversationParticipants).values(
|
|
1250
|
+
opts.participantIds.map((uid, i) => ({
|
|
1251
|
+
conversation_id: id,
|
|
1252
|
+
user_id: uid,
|
|
1253
|
+
role: i === 0 ? "owner" : "member"
|
|
1254
|
+
}))
|
|
1255
|
+
);
|
|
1256
|
+
}
|
|
1257
|
+
return {
|
|
1258
|
+
id,
|
|
1259
|
+
agent_name: agentName,
|
|
1260
|
+
channel,
|
|
1261
|
+
user_id: opts?.userId ?? null,
|
|
1262
|
+
title: opts?.title ?? null,
|
|
1263
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1264
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1265
|
+
};
|
|
1266
|
+
}
|
|
1267
|
+
async function getConversation(id) {
|
|
1268
|
+
const db2 = await getDb();
|
|
1269
|
+
const row = await db2.select().from(conversations).where(eq3(conversations.id, id)).get();
|
|
1270
|
+
return row ?? null;
|
|
1271
|
+
}
|
|
1272
|
+
async function getParticipants(conversationId) {
|
|
1273
|
+
const db2 = await getDb();
|
|
1274
|
+
const rows = await db2.select({
|
|
1275
|
+
userId: conversationParticipants.user_id,
|
|
1276
|
+
username: users.username,
|
|
1277
|
+
userType: users.user_type,
|
|
1278
|
+
role: conversationParticipants.role
|
|
1279
|
+
}).from(conversationParticipants).innerJoin(users, eq3(conversationParticipants.user_id, users.id)).where(eq3(conversationParticipants.conversation_id, conversationId)).all();
|
|
1280
|
+
return rows;
|
|
1281
|
+
}
|
|
1282
|
+
async function isParticipant(conversationId, userId) {
|
|
1283
|
+
const db2 = await getDb();
|
|
1284
|
+
const row = await db2.select({ user_id: conversationParticipants.user_id }).from(conversationParticipants).where(
|
|
1285
|
+
and2(
|
|
1286
|
+
eq3(conversationParticipants.conversation_id, conversationId),
|
|
1287
|
+
eq3(conversationParticipants.user_id, userId)
|
|
1288
|
+
)
|
|
1289
|
+
).get();
|
|
1290
|
+
return row != null;
|
|
1291
|
+
}
|
|
1292
|
+
async function listConversationsForUser(userId) {
|
|
1293
|
+
const db2 = await getDb();
|
|
1294
|
+
const participantRows = await db2.select({ conversation_id: conversationParticipants.conversation_id }).from(conversationParticipants).where(eq3(conversationParticipants.user_id, userId)).all();
|
|
1295
|
+
if (participantRows.length === 0) return [];
|
|
1296
|
+
const convIds = participantRows.map((r) => r.conversation_id);
|
|
1297
|
+
return db2.select().from(conversations).where(inArray(conversations.id, convIds)).orderBy(desc(conversations.updated_at)).all();
|
|
1298
|
+
}
|
|
1299
|
+
async function isParticipantOrOwner(conversationId, userId) {
|
|
1300
|
+
if (await isParticipant(conversationId, userId)) return true;
|
|
1301
|
+
const db2 = await getDb();
|
|
1302
|
+
const row = await db2.select().from(conversations).where(and2(eq3(conversations.id, conversationId), eq3(conversations.user_id, userId))).get();
|
|
1303
|
+
return row != null;
|
|
1304
|
+
}
|
|
1305
|
+
async function deleteConversationForUser(id, userId) {
|
|
1306
|
+
if (!await isParticipantOrOwner(id, userId)) return false;
|
|
1307
|
+
await deleteConversation(id);
|
|
1308
|
+
return true;
|
|
1309
|
+
}
|
|
1310
|
+
async function addMessage(conversationId, role, senderName, content) {
|
|
1311
|
+
const db2 = await getDb();
|
|
1312
|
+
const serialized = JSON.stringify(content);
|
|
1313
|
+
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 });
|
|
1314
|
+
await db2.update(conversations).set({ updated_at: sql2`datetime('now')` }).where(eq3(conversations.id, conversationId));
|
|
1315
|
+
if (role === "user") {
|
|
1316
|
+
const firstText = content.find((b) => b.type === "text");
|
|
1317
|
+
const title = firstText ? firstText.text.slice(0, 80) : "";
|
|
1318
|
+
if (title) {
|
|
1319
|
+
await db2.update(conversations).set({ title }).where(and2(eq3(conversations.id, conversationId), isNull(conversations.title)));
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
const msg = {
|
|
1323
|
+
id: result.id,
|
|
1324
|
+
conversation_id: conversationId,
|
|
1325
|
+
role,
|
|
1326
|
+
sender_name: senderName,
|
|
1327
|
+
content,
|
|
1328
|
+
created_at: result.created_at
|
|
1329
|
+
};
|
|
1330
|
+
publish(conversationId, {
|
|
1331
|
+
type: "message",
|
|
1332
|
+
id: msg.id,
|
|
1333
|
+
role: msg.role,
|
|
1334
|
+
senderName: msg.sender_name,
|
|
1335
|
+
content: msg.content,
|
|
1336
|
+
createdAt: msg.created_at
|
|
1337
|
+
});
|
|
1338
|
+
return msg;
|
|
1339
|
+
}
|
|
1340
|
+
async function getMessages(conversationId) {
|
|
1341
|
+
const db2 = await getDb();
|
|
1342
|
+
const rows = await db2.select().from(messages).where(eq3(messages.conversation_id, conversationId)).orderBy(messages.created_at).all();
|
|
1343
|
+
return rows.map((row) => {
|
|
1344
|
+
let content;
|
|
1345
|
+
try {
|
|
1346
|
+
const parsed = JSON.parse(row.content);
|
|
1347
|
+
content = Array.isArray(parsed) ? parsed : [{ type: "text", text: row.content }];
|
|
1348
|
+
} catch {
|
|
1349
|
+
content = [{ type: "text", text: row.content }];
|
|
1350
|
+
}
|
|
1351
|
+
return { ...row, content };
|
|
1352
|
+
});
|
|
1353
|
+
}
|
|
1354
|
+
async function listConversationsWithParticipants(userId) {
|
|
1355
|
+
const convs = await listConversationsForUser(userId);
|
|
1356
|
+
if (convs.length === 0) return [];
|
|
1357
|
+
const db2 = await getDb();
|
|
1358
|
+
const convIds = convs.map((c) => c.id);
|
|
1359
|
+
const rows = await db2.select({
|
|
1360
|
+
conversationId: conversationParticipants.conversation_id,
|
|
1361
|
+
userId: users.id,
|
|
1362
|
+
username: users.username,
|
|
1363
|
+
userType: users.user_type,
|
|
1364
|
+
role: conversationParticipants.role
|
|
1365
|
+
}).from(conversationParticipants).innerJoin(users, eq3(conversationParticipants.user_id, users.id)).where(inArray(conversationParticipants.conversation_id, convIds));
|
|
1366
|
+
const byConv = /* @__PURE__ */ new Map();
|
|
1367
|
+
for (const r of rows) {
|
|
1368
|
+
let arr = byConv.get(r.conversationId);
|
|
1369
|
+
if (!arr) {
|
|
1370
|
+
arr = [];
|
|
1371
|
+
byConv.set(r.conversationId, arr);
|
|
1372
|
+
}
|
|
1373
|
+
arr.push({
|
|
1374
|
+
userId: r.userId,
|
|
1375
|
+
username: r.username,
|
|
1376
|
+
userType: r.userType,
|
|
1377
|
+
role: r.role
|
|
1378
|
+
});
|
|
1379
|
+
}
|
|
1380
|
+
return convs.map((c) => ({ ...c, participants: byConv.get(c.id) ?? [] }));
|
|
1381
|
+
}
|
|
1382
|
+
async function findDMConversation(agentName, participantIds) {
|
|
1383
|
+
const db2 = await getDb();
|
|
1384
|
+
const agentConvs = await db2.select({ id: conversations.id }).from(conversations).where(eq3(conversations.agent_name, agentName)).all();
|
|
1385
|
+
for (const conv of agentConvs) {
|
|
1386
|
+
const rows = await db2.select({ user_id: conversationParticipants.user_id }).from(conversationParticipants).where(eq3(conversationParticipants.conversation_id, conv.id)).all();
|
|
1387
|
+
if (rows.length !== 2) continue;
|
|
1388
|
+
const ids = new Set(rows.map((r) => r.user_id));
|
|
1389
|
+
if (ids.has(participantIds[0]) && ids.has(participantIds[1])) {
|
|
1390
|
+
return conv.id;
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
return null;
|
|
1394
|
+
}
|
|
1395
|
+
async function deleteConversation(id) {
|
|
1396
|
+
const db2 = await getDb();
|
|
1397
|
+
await db2.delete(conversations).where(eq3(conversations.id, id));
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
// src/lib/convert-session.ts
|
|
1401
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
1199
1402
|
import { mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
1200
1403
|
import { homedir } from "os";
|
|
1201
1404
|
import { resolve as resolve7 } from "path";
|
|
1202
1405
|
function convertSession(opts) {
|
|
1203
1406
|
const lines = readFileSync4(opts.sessionPath, "utf-8").trim().split("\n");
|
|
1204
|
-
const sessionId =
|
|
1407
|
+
const sessionId = randomUUID2();
|
|
1205
1408
|
const idMap = /* @__PURE__ */ new Map();
|
|
1206
1409
|
const messages2 = [];
|
|
1207
1410
|
for (const line of lines) {
|
|
@@ -1216,7 +1419,7 @@ function convertSession(opts) {
|
|
|
1216
1419
|
const event = messages2[i];
|
|
1217
1420
|
const msg = event.message;
|
|
1218
1421
|
if (msg.role === "user") {
|
|
1219
|
-
const uuid =
|
|
1422
|
+
const uuid = randomUUID2();
|
|
1220
1423
|
idMap.set(event.id, uuid);
|
|
1221
1424
|
const parentUuid = event.parentId ? idMap.get(event.parentId) ?? null : null;
|
|
1222
1425
|
const sdkEvent = {
|
|
@@ -1240,7 +1443,7 @@ function convertSession(opts) {
|
|
|
1240
1443
|
} else if (msg.role === "assistant") {
|
|
1241
1444
|
const content = convertAssistantContent(msg.content);
|
|
1242
1445
|
if (content.length === 0) continue;
|
|
1243
|
-
const uuid =
|
|
1446
|
+
const uuid = randomUUID2();
|
|
1244
1447
|
idMap.set(event.id, uuid);
|
|
1245
1448
|
const parentUuid = event.parentId ? idMap.get(event.parentId) ?? null : null;
|
|
1246
1449
|
const stopReason = mapStopReason(msg.stopReason);
|
|
@@ -1255,12 +1458,12 @@ function convertSession(opts) {
|
|
|
1255
1458
|
isSidechain: false,
|
|
1256
1459
|
userType: "external",
|
|
1257
1460
|
type: "assistant",
|
|
1258
|
-
requestId: `req_imported_${
|
|
1461
|
+
requestId: `req_imported_${randomUUID2()}`,
|
|
1259
1462
|
message: {
|
|
1260
1463
|
role: "assistant",
|
|
1261
1464
|
content,
|
|
1262
1465
|
type: "message",
|
|
1263
|
-
id: `msg_imported_${
|
|
1466
|
+
id: `msg_imported_${randomUUID2()}`,
|
|
1264
1467
|
model: mapModel(msg.model),
|
|
1265
1468
|
stop_reason: stopReason,
|
|
1266
1469
|
stop_sequence: null,
|
|
@@ -1288,7 +1491,7 @@ function convertSession(opts) {
|
|
|
1288
1491
|
j++;
|
|
1289
1492
|
}
|
|
1290
1493
|
i = j - 1;
|
|
1291
|
-
const uuid =
|
|
1494
|
+
const uuid = randomUUID2();
|
|
1292
1495
|
idMap.set(lastToolResultId, uuid);
|
|
1293
1496
|
const parentUuid = event.parentId ? idMap.get(event.parentId) ?? null : lastSdkUuid;
|
|
1294
1497
|
const sdkEvent = {
|
|
@@ -1368,118 +1571,6 @@ function convertAssistantContent(content) {
|
|
|
1368
1571
|
return result;
|
|
1369
1572
|
}
|
|
1370
1573
|
|
|
1371
|
-
// src/lib/template.ts
|
|
1372
|
-
import {
|
|
1373
|
-
cpSync,
|
|
1374
|
-
existsSync as existsSync5,
|
|
1375
|
-
mkdirSync as mkdirSync4,
|
|
1376
|
-
readdirSync as readdirSync3,
|
|
1377
|
-
readFileSync as readFileSync5,
|
|
1378
|
-
renameSync,
|
|
1379
|
-
rmSync,
|
|
1380
|
-
statSync,
|
|
1381
|
-
writeFileSync as writeFileSync4
|
|
1382
|
-
} from "fs";
|
|
1383
|
-
import { tmpdir } from "os";
|
|
1384
|
-
import { dirname as dirname3, join, relative, resolve as resolve8 } from "path";
|
|
1385
|
-
function findTemplatesRoot() {
|
|
1386
|
-
let dir = dirname3(new URL(import.meta.url).pathname);
|
|
1387
|
-
for (let i = 0; i < 5; i++) {
|
|
1388
|
-
const candidate = resolve8(dir, "templates");
|
|
1389
|
-
if (existsSync5(resolve8(candidate, "_base"))) return candidate;
|
|
1390
|
-
dir = dirname3(dir);
|
|
1391
|
-
}
|
|
1392
|
-
console.error(
|
|
1393
|
-
"Templates directory not found. Searched up from:",
|
|
1394
|
-
dirname3(new URL(import.meta.url).pathname)
|
|
1395
|
-
);
|
|
1396
|
-
process.exit(1);
|
|
1397
|
-
}
|
|
1398
|
-
function composeTemplate(templatesRoot, templateName) {
|
|
1399
|
-
const baseDir = resolve8(templatesRoot, "_base");
|
|
1400
|
-
const templateDir = resolve8(templatesRoot, templateName);
|
|
1401
|
-
if (!existsSync5(baseDir)) {
|
|
1402
|
-
console.error("Base template not found:", baseDir);
|
|
1403
|
-
process.exit(1);
|
|
1404
|
-
}
|
|
1405
|
-
if (!existsSync5(templateDir)) {
|
|
1406
|
-
console.error(`Template not found: ${templateName}`);
|
|
1407
|
-
process.exit(1);
|
|
1408
|
-
}
|
|
1409
|
-
const composedDir = resolve8(tmpdir(), `volute-template-${Date.now()}`);
|
|
1410
|
-
mkdirSync4(composedDir, { recursive: true });
|
|
1411
|
-
cpSync(baseDir, composedDir, { recursive: true });
|
|
1412
|
-
for (const file of listFiles(templateDir)) {
|
|
1413
|
-
const src = resolve8(templateDir, file);
|
|
1414
|
-
const dest = resolve8(composedDir, file);
|
|
1415
|
-
mkdirSync4(dirname3(dest), { recursive: true });
|
|
1416
|
-
cpSync(src, dest);
|
|
1417
|
-
}
|
|
1418
|
-
const manifestPath = resolve8(composedDir, "volute-template.json");
|
|
1419
|
-
if (!existsSync5(manifestPath)) {
|
|
1420
|
-
rmSync(composedDir, { recursive: true, force: true });
|
|
1421
|
-
console.error(`Template manifest not found: ${templateName}/volute-template.json`);
|
|
1422
|
-
process.exit(1);
|
|
1423
|
-
}
|
|
1424
|
-
const manifest = JSON.parse(readFileSync5(manifestPath, "utf-8"));
|
|
1425
|
-
const skillsSrc = resolve8(composedDir, "_skills");
|
|
1426
|
-
if (existsSync5(skillsSrc)) {
|
|
1427
|
-
const skillsDest = resolve8(composedDir, manifest.skillsDir);
|
|
1428
|
-
mkdirSync4(skillsDest, { recursive: true });
|
|
1429
|
-
cpSync(skillsSrc, skillsDest, { recursive: true });
|
|
1430
|
-
rmSync(skillsSrc, { recursive: true, force: true });
|
|
1431
|
-
}
|
|
1432
|
-
rmSync(manifestPath);
|
|
1433
|
-
return { composedDir, manifest };
|
|
1434
|
-
}
|
|
1435
|
-
function copyTemplateToDir(composedDir, destDir, agentName, manifest) {
|
|
1436
|
-
cpSync(composedDir, destDir, { recursive: true });
|
|
1437
|
-
for (const [from, to] of Object.entries(manifest.rename)) {
|
|
1438
|
-
const fromPath = resolve8(destDir, from);
|
|
1439
|
-
if (existsSync5(fromPath)) {
|
|
1440
|
-
renameSync(fromPath, resolve8(destDir, to));
|
|
1441
|
-
}
|
|
1442
|
-
}
|
|
1443
|
-
for (const file of manifest.substitute) {
|
|
1444
|
-
const path = resolve8(destDir, file);
|
|
1445
|
-
if (existsSync5(path)) {
|
|
1446
|
-
const content = readFileSync5(path, "utf-8");
|
|
1447
|
-
writeFileSync4(path, content.replaceAll("{{name}}", agentName));
|
|
1448
|
-
}
|
|
1449
|
-
}
|
|
1450
|
-
}
|
|
1451
|
-
function applyInitFiles(destDir) {
|
|
1452
|
-
const initDir = resolve8(destDir, ".init");
|
|
1453
|
-
if (!existsSync5(initDir)) return;
|
|
1454
|
-
const homeDir = resolve8(destDir, "home");
|
|
1455
|
-
for (const file of listFiles(initDir)) {
|
|
1456
|
-
const src = resolve8(initDir, file);
|
|
1457
|
-
const dest = resolve8(homeDir, file);
|
|
1458
|
-
const parent = dirname3(dest);
|
|
1459
|
-
if (!existsSync5(parent)) {
|
|
1460
|
-
mkdirSync4(parent, { recursive: true });
|
|
1461
|
-
}
|
|
1462
|
-
cpSync(src, dest);
|
|
1463
|
-
}
|
|
1464
|
-
rmSync(initDir, { recursive: true, force: true });
|
|
1465
|
-
}
|
|
1466
|
-
function listFiles(dir) {
|
|
1467
|
-
const results = [];
|
|
1468
|
-
function walk(current) {
|
|
1469
|
-
for (const entry of readdirSync3(current)) {
|
|
1470
|
-
const full = join(current, entry);
|
|
1471
|
-
if (statSync(full).isDirectory()) {
|
|
1472
|
-
if (entry === ".git") continue;
|
|
1473
|
-
walk(full);
|
|
1474
|
-
} else {
|
|
1475
|
-
results.push(relative(dir, full));
|
|
1476
|
-
}
|
|
1477
|
-
}
|
|
1478
|
-
}
|
|
1479
|
-
walk(dir);
|
|
1480
|
-
return results;
|
|
1481
|
-
}
|
|
1482
|
-
|
|
1483
1574
|
// src/lib/typing.ts
|
|
1484
1575
|
var DEFAULT_TTL_MS = 1e4;
|
|
1485
1576
|
var SWEEP_INTERVAL_MS = 5e3;
|
|
@@ -1551,6 +1642,7 @@ function getTypingMap() {
|
|
|
1551
1642
|
async function startAgentFull(name, baseName, variantName) {
|
|
1552
1643
|
await getAgentManager().startAgent(name);
|
|
1553
1644
|
if (variantName) return;
|
|
1645
|
+
if (findAgent(baseName)?.stage === "seed") return;
|
|
1554
1646
|
const dir = agentDir(baseName);
|
|
1555
1647
|
const entry = findAgent(baseName);
|
|
1556
1648
|
await getConnectorManager().startConnectors(baseName, dir, entry.port, getDaemonPort());
|
|
@@ -1573,7 +1665,7 @@ function extractTextContent(content) {
|
|
|
1573
1665
|
}
|
|
1574
1666
|
function getDaemonPort() {
|
|
1575
1667
|
try {
|
|
1576
|
-
const data = JSON.parse(
|
|
1668
|
+
const data = JSON.parse(readFileSync5(resolve8(voluteHome(), "daemon.json"), "utf-8"));
|
|
1577
1669
|
return data.port;
|
|
1578
1670
|
} catch (err) {
|
|
1579
1671
|
if (err?.code !== "ENOENT") {
|
|
@@ -1613,9 +1705,9 @@ async function getAgentStatus(name, port) {
|
|
|
1613
1705
|
return { status, channels };
|
|
1614
1706
|
}
|
|
1615
1707
|
var TEMPLATE_BRANCH = "volute/template";
|
|
1616
|
-
async function initTemplateBranch(projectRoot, composedDir, manifest,
|
|
1708
|
+
async function initTemplateBranch(projectRoot, composedDir, manifest, agentName, env) {
|
|
1617
1709
|
const templateFiles = listFiles(composedDir).filter((f) => !f.startsWith(".init/") && !f.startsWith(".init\\")).map((f) => manifest.rename[f] ?? f);
|
|
1618
|
-
const opts = { cwd: projectRoot,
|
|
1710
|
+
const opts = { cwd: projectRoot, agentName, env };
|
|
1619
1711
|
await gitExec(["checkout", "--orphan", TEMPLATE_BRANCH], opts);
|
|
1620
1712
|
await gitExec(["add", "--", ...templateFiles], opts);
|
|
1621
1713
|
await gitExec(["commit", "-m", "template update"], opts);
|
|
@@ -1624,7 +1716,7 @@ async function initTemplateBranch(projectRoot, composedDir, manifest, ids, env)
|
|
|
1624
1716
|
await gitExec(["commit", "-m", "initial commit"], opts);
|
|
1625
1717
|
}
|
|
1626
1718
|
async function updateTemplateBranch(projectRoot, template, agentName) {
|
|
1627
|
-
const tempWorktree =
|
|
1719
|
+
const tempWorktree = resolve8(projectRoot, ".variants", "_template_update");
|
|
1628
1720
|
let branchExists = false;
|
|
1629
1721
|
try {
|
|
1630
1722
|
await gitExec(["rev-parse", "--verify", TEMPLATE_BRANCH], { cwd: projectRoot });
|
|
@@ -1635,8 +1727,8 @@ async function updateTemplateBranch(projectRoot, template, agentName) {
|
|
|
1635
1727
|
await gitExec(["worktree", "remove", "--force", tempWorktree], { cwd: projectRoot });
|
|
1636
1728
|
} catch {
|
|
1637
1729
|
}
|
|
1638
|
-
if (
|
|
1639
|
-
|
|
1730
|
+
if (existsSync5(tempWorktree)) {
|
|
1731
|
+
rmSync(tempWorktree, { recursive: true, force: true });
|
|
1640
1732
|
}
|
|
1641
1733
|
const templatesRoot = findTemplatesRoot();
|
|
1642
1734
|
const { composedDir, manifest } = composeTemplate(templatesRoot, template);
|
|
@@ -1656,9 +1748,9 @@ async function updateTemplateBranch(projectRoot, template, agentName) {
|
|
|
1656
1748
|
});
|
|
1657
1749
|
}
|
|
1658
1750
|
copyTemplateToDir(composedDir, tempWorktree, agentName, manifest);
|
|
1659
|
-
const initDir =
|
|
1660
|
-
if (
|
|
1661
|
-
|
|
1751
|
+
const initDir = resolve8(tempWorktree, ".init");
|
|
1752
|
+
if (existsSync5(initDir)) {
|
|
1753
|
+
rmSync(initDir, { recursive: true, force: true });
|
|
1662
1754
|
}
|
|
1663
1755
|
await gitExec(["add", "-A"], { cwd: tempWorktree });
|
|
1664
1756
|
try {
|
|
@@ -1671,10 +1763,10 @@ async function updateTemplateBranch(projectRoot, template, agentName) {
|
|
|
1671
1763
|
await gitExec(["worktree", "remove", "--force", tempWorktree], { cwd: projectRoot });
|
|
1672
1764
|
} catch {
|
|
1673
1765
|
}
|
|
1674
|
-
if (
|
|
1675
|
-
|
|
1766
|
+
if (existsSync5(tempWorktree)) {
|
|
1767
|
+
rmSync(tempWorktree, { recursive: true, force: true });
|
|
1676
1768
|
}
|
|
1677
|
-
|
|
1769
|
+
rmSync(composedDir, { recursive: true, force: true });
|
|
1678
1770
|
}
|
|
1679
1771
|
}
|
|
1680
1772
|
async function mergeTemplateBranch(worktreeDir) {
|
|
@@ -1694,57 +1786,87 @@ async function mergeTemplateBranch(worktreeDir) {
|
|
|
1694
1786
|
throw e;
|
|
1695
1787
|
}
|
|
1696
1788
|
}
|
|
1697
|
-
var
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1789
|
+
var createAgentSchema = z.object({
|
|
1790
|
+
name: z.string(),
|
|
1791
|
+
template: z.string().optional(),
|
|
1792
|
+
stage: z.enum(["seed", "mind"]).optional(),
|
|
1793
|
+
description: z.string().optional(),
|
|
1794
|
+
model: z.string().optional()
|
|
1795
|
+
});
|
|
1796
|
+
var app = new Hono().post("/", requireAdmin, zValidator("json", createAgentSchema), async (c) => {
|
|
1797
|
+
const body = c.req.valid("json");
|
|
1704
1798
|
const { name, template = "agent-sdk" } = body;
|
|
1705
1799
|
const nameErr = validateAgentName(name);
|
|
1706
1800
|
if (nameErr) return c.json({ error: nameErr }, 400);
|
|
1707
1801
|
if (findAgent(name)) return c.json({ error: `Agent already exists: ${name}` }, 409);
|
|
1708
1802
|
ensureVoluteHome();
|
|
1709
1803
|
const dest = agentDir(name);
|
|
1710
|
-
if (
|
|
1804
|
+
if (existsSync5(dest)) return c.json({ error: "Agent directory already exists" }, 409);
|
|
1711
1805
|
const templatesRoot = findTemplatesRoot();
|
|
1712
1806
|
const { composedDir, manifest } = composeTemplate(templatesRoot, template);
|
|
1713
1807
|
try {
|
|
1714
1808
|
copyTemplateToDir(composedDir, dest, name, manifest);
|
|
1715
1809
|
applyInitFiles(dest);
|
|
1810
|
+
if (body.model) {
|
|
1811
|
+
const configPath = resolve8(dest, "home/.config/config.json");
|
|
1812
|
+
const existing = existsSync5(configPath) ? JSON.parse(readFileSync5(configPath, "utf-8")) : {};
|
|
1813
|
+
existing.model = body.model;
|
|
1814
|
+
writeFileSync4(configPath, `${JSON.stringify(existing, null, 2)}
|
|
1815
|
+
`);
|
|
1816
|
+
}
|
|
1817
|
+
if (body.stage === "seed") {
|
|
1818
|
+
const descLine = body.description ? `
|
|
1819
|
+
The human who planted you described you as: "${body.description}"
|
|
1820
|
+
` : "";
|
|
1821
|
+
writeFileSync4(
|
|
1822
|
+
resolve8(dest, "home/SOUL.md"),
|
|
1823
|
+
`You are ${name}.
|
|
1824
|
+
|
|
1825
|
+
You don't have a soul yet. You're a seed \u2014 a new agent in the process of discovering who you are.
|
|
1826
|
+
${descLine}
|
|
1827
|
+
Have a conversation with the human. Explore what kind of agent you want to be. When you're ready, write your SOUL.md and MEMORY.md, then run \`volute sprout\` to complete the transformation.
|
|
1828
|
+
`
|
|
1829
|
+
);
|
|
1830
|
+
const skillsDir = resolve8(dest, manifest.skillsDir);
|
|
1831
|
+
for (const skill of ["volute-agent", "memory", "sessions"]) {
|
|
1832
|
+
const skillPath = resolve8(skillsDir, skill);
|
|
1833
|
+
if (existsSync5(skillPath)) rmSync(skillPath, { recursive: true, force: true });
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1716
1836
|
const port = nextPort();
|
|
1717
|
-
addAgent(name, port);
|
|
1837
|
+
addAgent(name, port, body.stage);
|
|
1838
|
+
const homeDir = resolve8(dest, "home");
|
|
1718
1839
|
ensureVoluteGroup();
|
|
1719
|
-
createAgentUser(name);
|
|
1840
|
+
createAgentUser(name, homeDir);
|
|
1720
1841
|
chownAgentDir(dest, name);
|
|
1721
|
-
const
|
|
1722
|
-
const env =
|
|
1723
|
-
await exec("npm", ["install"], { cwd: dest,
|
|
1842
|
+
const agentName = isIsolationEnabled() ? name : void 0;
|
|
1843
|
+
const env = agentName ? { ...process.env, HOME: homeDir } : void 0;
|
|
1844
|
+
await exec("npm", ["install"], { cwd: dest, agentName, env });
|
|
1724
1845
|
let gitWarning;
|
|
1725
1846
|
try {
|
|
1726
|
-
await gitExec(["init"], { cwd: dest,
|
|
1727
|
-
await initTemplateBranch(dest, composedDir, manifest,
|
|
1847
|
+
await gitExec(["init"], { cwd: dest, agentName, env });
|
|
1848
|
+
await initTemplateBranch(dest, composedDir, manifest, agentName, env);
|
|
1728
1849
|
} catch {
|
|
1729
|
-
|
|
1850
|
+
rmSync(resolve8(dest, ".git"), { recursive: true, force: true });
|
|
1730
1851
|
gitWarning = "Git setup failed \u2014 variants and upgrades won't be available until git is initialized.";
|
|
1731
1852
|
}
|
|
1732
1853
|
return c.json({
|
|
1733
1854
|
ok: true,
|
|
1734
1855
|
name,
|
|
1735
1856
|
port,
|
|
1857
|
+
stage: body.stage ?? "mind",
|
|
1736
1858
|
message: `Created agent: ${name} (port ${port})`,
|
|
1737
1859
|
...gitWarning && { warning: gitWarning }
|
|
1738
1860
|
});
|
|
1739
1861
|
} catch (err) {
|
|
1740
|
-
if (
|
|
1862
|
+
if (existsSync5(dest)) rmSync(dest, { recursive: true, force: true });
|
|
1741
1863
|
try {
|
|
1742
1864
|
removeAgent(name);
|
|
1743
1865
|
} catch {
|
|
1744
1866
|
}
|
|
1745
1867
|
return c.json({ error: err instanceof Error ? err.message : "Failed to create agent" }, 500);
|
|
1746
1868
|
} finally {
|
|
1747
|
-
|
|
1869
|
+
rmSync(composedDir, { recursive: true, force: true });
|
|
1748
1870
|
}
|
|
1749
1871
|
}).post("/import", requireAdmin, async (c) => {
|
|
1750
1872
|
let body;
|
|
@@ -1754,13 +1876,13 @@ var app = new Hono().post("/", requireAdmin, async (c) => {
|
|
|
1754
1876
|
return c.json({ error: "Invalid JSON" }, 400);
|
|
1755
1877
|
}
|
|
1756
1878
|
const wsDir = body.workspacePath;
|
|
1757
|
-
if (!wsDir || !
|
|
1879
|
+
if (!wsDir || !existsSync5(resolve8(wsDir, "SOUL.md")) || !existsSync5(resolve8(wsDir, "IDENTITY.md"))) {
|
|
1758
1880
|
return c.json({ error: "Invalid workspace: missing SOUL.md or IDENTITY.md" }, 400);
|
|
1759
1881
|
}
|
|
1760
|
-
const soul =
|
|
1761
|
-
const identity =
|
|
1762
|
-
const userPath =
|
|
1763
|
-
const user =
|
|
1882
|
+
const soul = readFileSync5(resolve8(wsDir, "SOUL.md"), "utf-8");
|
|
1883
|
+
const identity = readFileSync5(resolve8(wsDir, "IDENTITY.md"), "utf-8");
|
|
1884
|
+
const userPath = resolve8(wsDir, "USER.md");
|
|
1885
|
+
const user = existsSync5(userPath) ? readFileSync5(userPath, "utf-8") : "";
|
|
1764
1886
|
const name = body.name ?? parseNameFromIdentity(identity) ?? "imported-agent";
|
|
1765
1887
|
const template = body.template ?? "agent-sdk";
|
|
1766
1888
|
const nameErr = validateAgentName(name);
|
|
@@ -1780,76 +1902,72 @@ ${user.trimEnd()}
|
|
|
1780
1902
|
` : "";
|
|
1781
1903
|
ensureVoluteHome();
|
|
1782
1904
|
const dest = agentDir(name);
|
|
1783
|
-
if (
|
|
1905
|
+
if (existsSync5(dest)) return c.json({ error: "Agent directory already exists" }, 409);
|
|
1784
1906
|
const templatesRoot = findTemplatesRoot();
|
|
1785
1907
|
const { composedDir, manifest } = composeTemplate(templatesRoot, template);
|
|
1786
1908
|
try {
|
|
1787
1909
|
copyTemplateToDir(composedDir, dest, name, manifest);
|
|
1788
1910
|
applyInitFiles(dest);
|
|
1789
|
-
|
|
1790
|
-
const wsMemoryPath =
|
|
1791
|
-
const hasMemory =
|
|
1911
|
+
writeFileSync4(resolve8(dest, "home/SOUL.md"), mergedSoul);
|
|
1912
|
+
const wsMemoryPath = resolve8(wsDir, "MEMORY.md");
|
|
1913
|
+
const hasMemory = existsSync5(wsMemoryPath);
|
|
1792
1914
|
if (hasMemory) {
|
|
1793
|
-
const existingMemory =
|
|
1794
|
-
|
|
1795
|
-
|
|
1915
|
+
const existingMemory = readFileSync5(wsMemoryPath, "utf-8");
|
|
1916
|
+
writeFileSync4(
|
|
1917
|
+
resolve8(dest, "home/MEMORY.md"),
|
|
1796
1918
|
`${existingMemory.trimEnd()}${mergedMemoryExtra}`
|
|
1797
1919
|
);
|
|
1798
1920
|
} else if (user) {
|
|
1799
|
-
|
|
1921
|
+
writeFileSync4(resolve8(dest, "home/MEMORY.md"), `${user.trimEnd()}
|
|
1800
1922
|
`);
|
|
1801
1923
|
}
|
|
1802
|
-
const wsMemoryDir =
|
|
1924
|
+
const wsMemoryDir = resolve8(wsDir, "memory");
|
|
1803
1925
|
let dailyLogCount = 0;
|
|
1804
|
-
if (
|
|
1805
|
-
const destMemoryDir =
|
|
1806
|
-
const files =
|
|
1926
|
+
if (existsSync5(wsMemoryDir)) {
|
|
1927
|
+
const destMemoryDir = resolve8(dest, "home/memory");
|
|
1928
|
+
const files = readdirSync3(wsMemoryDir).filter((f) => f.endsWith(".md"));
|
|
1807
1929
|
for (const file of files) {
|
|
1808
|
-
|
|
1930
|
+
cpSync(resolve8(wsMemoryDir, file), resolve8(destMemoryDir, file));
|
|
1809
1931
|
}
|
|
1810
1932
|
dailyLogCount = files.length;
|
|
1811
1933
|
}
|
|
1812
1934
|
const port = nextPort();
|
|
1813
1935
|
addAgent(name, port);
|
|
1936
|
+
const homeDir = resolve8(dest, "home");
|
|
1814
1937
|
ensureVoluteGroup();
|
|
1815
|
-
createAgentUser(name);
|
|
1938
|
+
createAgentUser(name, homeDir);
|
|
1816
1939
|
chownAgentDir(dest, name);
|
|
1817
|
-
const
|
|
1818
|
-
const env =
|
|
1819
|
-
await exec("npm", ["install"], { cwd: dest,
|
|
1940
|
+
const agentName = isIsolationEnabled() ? name : void 0;
|
|
1941
|
+
const env = agentName ? { ...process.env, HOME: homeDir } : void 0;
|
|
1942
|
+
await exec("npm", ["install"], { cwd: dest, agentName, env });
|
|
1820
1943
|
if (!hasMemory && dailyLogCount > 0) {
|
|
1821
1944
|
await consolidateMemory(dest);
|
|
1822
1945
|
}
|
|
1823
|
-
await gitExec(["init"], { cwd: dest,
|
|
1824
|
-
await gitExec(["add", "-A"], { cwd: dest,
|
|
1825
|
-
await gitExec(["commit", "-m", "import from OpenClaw"], {
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
gid: ids?.gid,
|
|
1829
|
-
env
|
|
1830
|
-
});
|
|
1831
|
-
const sessionFile = body.sessionPath ? resolve9(body.sessionPath) : findOpenClawSession(wsDir);
|
|
1832
|
-
if (sessionFile && existsSync6(sessionFile)) {
|
|
1946
|
+
await gitExec(["init"], { cwd: dest, agentName, env });
|
|
1947
|
+
await gitExec(["add", "-A"], { cwd: dest, agentName, env });
|
|
1948
|
+
await gitExec(["commit", "-m", "import from OpenClaw"], { cwd: dest, agentName, env });
|
|
1949
|
+
const sessionFile = body.sessionPath ? resolve8(body.sessionPath) : findOpenClawSession(wsDir);
|
|
1950
|
+
if (sessionFile && existsSync5(sessionFile)) {
|
|
1833
1951
|
if (template === "pi") {
|
|
1834
1952
|
importPiSession(sessionFile, dest);
|
|
1835
1953
|
} else if (template === "agent-sdk") {
|
|
1836
1954
|
const sessionId = convertSession({ sessionPath: sessionFile, projectDir: dest });
|
|
1837
|
-
const voluteDir =
|
|
1838
|
-
|
|
1839
|
-
|
|
1955
|
+
const voluteDir = resolve8(dest, ".volute");
|
|
1956
|
+
mkdirSync4(voluteDir, { recursive: true });
|
|
1957
|
+
writeFileSync4(resolve8(voluteDir, "session.json"), JSON.stringify({ sessionId }));
|
|
1840
1958
|
}
|
|
1841
1959
|
}
|
|
1842
1960
|
importOpenClawConnectors(name, dest);
|
|
1843
1961
|
return c.json({ ok: true, name, port, message: `Imported agent: ${name} (port ${port})` });
|
|
1844
1962
|
} catch (err) {
|
|
1845
|
-
if (
|
|
1963
|
+
if (existsSync5(dest)) rmSync(dest, { recursive: true, force: true });
|
|
1846
1964
|
try {
|
|
1847
1965
|
removeAgent(name);
|
|
1848
1966
|
} catch {
|
|
1849
1967
|
}
|
|
1850
1968
|
return c.json({ error: err instanceof Error ? err.message : "Failed to import agent" }, 500);
|
|
1851
1969
|
} finally {
|
|
1852
|
-
|
|
1970
|
+
rmSync(composedDir, { recursive: true, force: true });
|
|
1853
1971
|
}
|
|
1854
1972
|
}).get("/", async (c) => {
|
|
1855
1973
|
const entries = readRegistry();
|
|
@@ -1864,7 +1982,7 @@ ${user.trimEnd()}
|
|
|
1864
1982
|
const name = c.req.param("name");
|
|
1865
1983
|
const entry = findAgent(name);
|
|
1866
1984
|
if (!entry) return c.json({ error: "Agent not found" }, 404);
|
|
1867
|
-
if (!
|
|
1985
|
+
if (!existsSync5(agentDir(name))) return c.json({ error: "Agent directory missing" }, 404);
|
|
1868
1986
|
const { status, channels } = await getAgentStatus(name, entry.port);
|
|
1869
1987
|
const variants = readVariants(name);
|
|
1870
1988
|
const manager = getAgentManager();
|
|
@@ -1890,7 +2008,7 @@ ${user.trimEnd()}
|
|
|
1890
2008
|
if (!variant) return c.json({ error: `Unknown variant: ${variantName}` }, 404);
|
|
1891
2009
|
} else {
|
|
1892
2010
|
const dir = agentDir(baseName);
|
|
1893
|
-
if (!
|
|
2011
|
+
if (!existsSync5(dir)) return c.json({ error: "Agent directory missing" }, 404);
|
|
1894
2012
|
}
|
|
1895
2013
|
if (getAgentManager().isRunning(name)) {
|
|
1896
2014
|
return c.json({ error: "Agent already running" }, 409);
|
|
@@ -1911,7 +2029,7 @@ ${user.trimEnd()}
|
|
|
1911
2029
|
if (!variant) return c.json({ error: `Unknown variant: ${variantName}` }, 404);
|
|
1912
2030
|
} else {
|
|
1913
2031
|
const dir = agentDir(baseName);
|
|
1914
|
-
if (!
|
|
2032
|
+
if (!existsSync5(dir)) return c.json({ error: "Agent directory missing" }, 404);
|
|
1915
2033
|
}
|
|
1916
2034
|
let context;
|
|
1917
2035
|
const contentType = c.req.header("content-type");
|
|
@@ -1942,7 +2060,7 @@ ${user.trimEnd()}
|
|
|
1942
2060
|
const variant = findVariant(baseName, mergeVariantName);
|
|
1943
2061
|
if (variant) {
|
|
1944
2062
|
const projectRoot = agentDir(baseName);
|
|
1945
|
-
if (
|
|
2063
|
+
if (existsSync5(variant.path)) {
|
|
1946
2064
|
const status = (await gitExec(["status", "--porcelain"], { cwd: variant.path })).trim();
|
|
1947
2065
|
if (status) {
|
|
1948
2066
|
try {
|
|
@@ -1970,7 +2088,7 @@ ${user.trimEnd()}
|
|
|
1970
2088
|
}
|
|
1971
2089
|
}
|
|
1972
2090
|
await gitExec(["merge", variant.branch], { cwd: projectRoot });
|
|
1973
|
-
if (
|
|
2091
|
+
if (existsSync5(variant.path)) {
|
|
1974
2092
|
try {
|
|
1975
2093
|
await gitExec(["worktree", "remove", "--force", variant.path], {
|
|
1976
2094
|
cwd: projectRoot
|
|
@@ -1994,6 +2112,19 @@ ${user.trimEnd()}
|
|
|
1994
2112
|
if (context) {
|
|
1995
2113
|
manager.setPendingContext(name, context);
|
|
1996
2114
|
}
|
|
2115
|
+
if (context?.type === "sprouted" && !variantName) {
|
|
2116
|
+
try {
|
|
2117
|
+
const db2 = await getDb();
|
|
2118
|
+
const activeConvs = await db2.select({ id: conversations.id }).from(conversations).where(eq4(conversations.agent_name, baseName)).all();
|
|
2119
|
+
for (const conv of activeConvs) {
|
|
2120
|
+
await addMessage(conv.id, "assistant", "system", [
|
|
2121
|
+
{ type: "text", text: "[seed has sprouted]" }
|
|
2122
|
+
]);
|
|
2123
|
+
}
|
|
2124
|
+
} catch (err) {
|
|
2125
|
+
console.error(`[daemon] failed to inject sprouted message for ${baseName}:`, err);
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
1997
2128
|
await startAgentFull(name, baseName, variantName);
|
|
1998
2129
|
return c.json({ ok: true });
|
|
1999
2130
|
} catch (err) {
|
|
@@ -2039,11 +2170,11 @@ ${user.trimEnd()}
|
|
|
2039
2170
|
removeAgent(name);
|
|
2040
2171
|
await deleteAgentUser2(name);
|
|
2041
2172
|
const state = stateDir(name);
|
|
2042
|
-
if (
|
|
2043
|
-
|
|
2173
|
+
if (existsSync5(state)) {
|
|
2174
|
+
rmSync(state, { recursive: true, force: true });
|
|
2044
2175
|
}
|
|
2045
|
-
if (force &&
|
|
2046
|
-
|
|
2176
|
+
if (force && existsSync5(dir)) {
|
|
2177
|
+
rmSync(dir, { recursive: true, force: true });
|
|
2047
2178
|
deleteAgentUser(name);
|
|
2048
2179
|
}
|
|
2049
2180
|
return c.json({ ok: true });
|
|
@@ -2052,7 +2183,7 @@ ${user.trimEnd()}
|
|
|
2052
2183
|
const entry = findAgent(agentName);
|
|
2053
2184
|
if (!entry) return c.json({ error: "Agent not found" }, 404);
|
|
2054
2185
|
const dir = agentDir(agentName);
|
|
2055
|
-
if (!
|
|
2186
|
+
if (!existsSync5(dir)) return c.json({ error: "Agent directory missing" }, 404);
|
|
2056
2187
|
let body = {};
|
|
2057
2188
|
try {
|
|
2058
2189
|
body = await c.req.json();
|
|
@@ -2061,8 +2192,8 @@ ${user.trimEnd()}
|
|
|
2061
2192
|
const template = body.template ?? "agent-sdk";
|
|
2062
2193
|
const UPGRADE_VARIANT = "upgrade";
|
|
2063
2194
|
if (body.continue) {
|
|
2064
|
-
const worktreeDir2 =
|
|
2065
|
-
if (!
|
|
2195
|
+
const worktreeDir2 = resolve8(dir, ".variants", UPGRADE_VARIANT);
|
|
2196
|
+
if (!existsSync5(worktreeDir2)) {
|
|
2066
2197
|
return c.json({ error: "No upgrade in progress" }, 400);
|
|
2067
2198
|
}
|
|
2068
2199
|
const status = await gitExec(["status", "--porcelain"], { cwd: worktreeDir2 });
|
|
@@ -2122,8 +2253,8 @@ ${user.trimEnd()}
|
|
|
2122
2253
|
);
|
|
2123
2254
|
}
|
|
2124
2255
|
}
|
|
2125
|
-
const worktreeDir =
|
|
2126
|
-
if (
|
|
2256
|
+
const worktreeDir = resolve8(dir, ".variants", UPGRADE_VARIANT);
|
|
2257
|
+
if (existsSync5(worktreeDir)) {
|
|
2127
2258
|
return c.json(
|
|
2128
2259
|
{ error: "Upgrade variant already exists. Use continue or delete it first." },
|
|
2129
2260
|
409
|
|
@@ -2135,9 +2266,9 @@ ${user.trimEnd()}
|
|
|
2135
2266
|
} catch {
|
|
2136
2267
|
}
|
|
2137
2268
|
await updateTemplateBranch(dir, template, agentName);
|
|
2138
|
-
const parentDir =
|
|
2139
|
-
if (!
|
|
2140
|
-
|
|
2269
|
+
const parentDir = resolve8(dir, ".variants");
|
|
2270
|
+
if (!existsSync5(parentDir)) {
|
|
2271
|
+
mkdirSync4(parentDir, { recursive: true });
|
|
2141
2272
|
}
|
|
2142
2273
|
await gitExec(["worktree", "add", "-b", UPGRADE_VARIANT, worktreeDir], { cwd: dir });
|
|
2143
2274
|
const hasConflicts = await mergeTemplateBranch(worktreeDir);
|
|
@@ -2264,6 +2395,24 @@ ${user.trimEnd()}
|
|
|
2264
2395
|
budget.acknowledgeWarning(baseName);
|
|
2265
2396
|
forwardBody = JSON.stringify(parsed);
|
|
2266
2397
|
}
|
|
2398
|
+
const seedEntry = findAgent(baseName);
|
|
2399
|
+
if (seedEntry?.stage === "seed" && parsed) {
|
|
2400
|
+
try {
|
|
2401
|
+
const countResult = await db2.select({ count: sql3`count(*)` }).from(agentMessages).where(eq4(agentMessages.agent, baseName));
|
|
2402
|
+
const msgCount = countResult[0]?.count ?? 0;
|
|
2403
|
+
if (msgCount >= 10 && msgCount % 10 === 0) {
|
|
2404
|
+
const nudge = "\n[You've been exploring for a while. Whenever you feel ready, write your SOUL.md and MEMORY.md, then run volute sprout.]";
|
|
2405
|
+
if (typeof parsed.content === "string") {
|
|
2406
|
+
parsed.content = parsed.content + nudge;
|
|
2407
|
+
} else if (Array.isArray(parsed.content)) {
|
|
2408
|
+
parsed.content = [...parsed.content, { type: "text", text: nudge }];
|
|
2409
|
+
}
|
|
2410
|
+
forwardBody = JSON.stringify(parsed);
|
|
2411
|
+
}
|
|
2412
|
+
} catch (err) {
|
|
2413
|
+
console.error(`[daemon] failed to check seed message count for ${baseName}:`, err);
|
|
2414
|
+
}
|
|
2415
|
+
}
|
|
2267
2416
|
typingMap.set(channel, baseName, { persistent: true });
|
|
2268
2417
|
const conversationId = parsed?.conversationId ?? null;
|
|
2269
2418
|
if (conversationId) typingMap.set(`volute:${conversationId}`, baseName, { persistent: true });
|
|
@@ -2330,7 +2479,7 @@ ${user.trimEnd()}
|
|
|
2330
2479
|
}).get("/:name/history/channels", async (c) => {
|
|
2331
2480
|
const name = c.req.param("name");
|
|
2332
2481
|
const db2 = await getDb();
|
|
2333
|
-
const rows = await db2.selectDistinct({ channel: agentMessages.channel }).from(agentMessages).where(
|
|
2482
|
+
const rows = await db2.selectDistinct({ channel: agentMessages.channel }).from(agentMessages).where(eq4(agentMessages.agent, name));
|
|
2334
2483
|
return c.json(rows.map((r) => r.channel));
|
|
2335
2484
|
}).get("/:name/history", async (c) => {
|
|
2336
2485
|
const name = c.req.param("name");
|
|
@@ -2338,23 +2487,23 @@ ${user.trimEnd()}
|
|
|
2338
2487
|
const limit = Math.min(Math.max(parseInt(c.req.query("limit") ?? "50", 10) || 50, 1), 200);
|
|
2339
2488
|
const offset = Math.max(parseInt(c.req.query("offset") ?? "0", 10) || 0, 0);
|
|
2340
2489
|
const db2 = await getDb();
|
|
2341
|
-
const conditions = [
|
|
2490
|
+
const conditions = [eq4(agentMessages.agent, name)];
|
|
2342
2491
|
if (channel) {
|
|
2343
|
-
conditions.push(
|
|
2492
|
+
conditions.push(eq4(agentMessages.channel, channel));
|
|
2344
2493
|
}
|
|
2345
|
-
const rows = await db2.select().from(agentMessages).where(
|
|
2494
|
+
const rows = await db2.select().from(agentMessages).where(and3(...conditions)).orderBy(desc2(agentMessages.created_at)).limit(limit).offset(offset);
|
|
2346
2495
|
return c.json(rows);
|
|
2347
2496
|
});
|
|
2348
2497
|
var agents_default = app;
|
|
2349
2498
|
|
|
2350
2499
|
// src/web/routes/auth.ts
|
|
2351
|
-
import { zValidator } from "@hono/zod-validator";
|
|
2500
|
+
import { zValidator as zValidator2 } from "@hono/zod-validator";
|
|
2352
2501
|
import { Hono as Hono2 } from "hono";
|
|
2353
2502
|
import { deleteCookie, getCookie as getCookie2, setCookie } from "hono/cookie";
|
|
2354
|
-
import { z } from "zod";
|
|
2355
|
-
var credentialsSchema =
|
|
2356
|
-
username:
|
|
2357
|
-
password:
|
|
2503
|
+
import { z as z2 } from "zod";
|
|
2504
|
+
var credentialsSchema = z2.object({
|
|
2505
|
+
username: z2.string().min(1),
|
|
2506
|
+
password: z2.string().min(1)
|
|
2358
2507
|
});
|
|
2359
2508
|
var admin = new Hono2().use(authMiddleware).get("/users", async (c) => {
|
|
2360
2509
|
const user = c.get("user");
|
|
@@ -2379,7 +2528,7 @@ var admin = new Hono2().use(authMiddleware).get("/users", async (c) => {
|
|
|
2379
2528
|
await approveUser(id);
|
|
2380
2529
|
return c.json({ ok: true });
|
|
2381
2530
|
});
|
|
2382
|
-
var app2 = new Hono2().post("/register",
|
|
2531
|
+
var app2 = new Hono2().post("/register", zValidator2("json", credentialsSchema), async (c) => {
|
|
2383
2532
|
const { username, password } = c.req.valid("json");
|
|
2384
2533
|
const existing = await getUserByUsername(username);
|
|
2385
2534
|
if (existing) {
|
|
@@ -2391,7 +2540,7 @@ var app2 = new Hono2().post("/register", zValidator("json", credentialsSchema),
|
|
|
2391
2540
|
setCookie(c, "volute_session", sessionId, { path: "/", httpOnly: true, sameSite: "Lax" });
|
|
2392
2541
|
}
|
|
2393
2542
|
return c.json({ id: user.id, username: user.username, role: user.role });
|
|
2394
|
-
}).post("/login",
|
|
2543
|
+
}).post("/login", zValidator2("json", credentialsSchema), async (c) => {
|
|
2395
2544
|
const { username, password } = c.req.valid("json");
|
|
2396
2545
|
const user = await verifyUser(username, password);
|
|
2397
2546
|
if (!user) {
|
|
@@ -2540,6 +2689,8 @@ var app4 = new Hono4().get("/:name/connectors", (c) => {
|
|
|
2540
2689
|
}
|
|
2541
2690
|
const entry = findAgent(name);
|
|
2542
2691
|
if (!entry) return c.json({ error: "Agent not found" }, 404);
|
|
2692
|
+
if (entry.stage === "seed")
|
|
2693
|
+
return c.json({ error: "Seed agents cannot use connectors \u2014 sprout first" }, 403);
|
|
2543
2694
|
const dir = agentDir(name);
|
|
2544
2695
|
const manager = getConnectorManager();
|
|
2545
2696
|
const envCheck = manager.checkConnectorEnv(type, name, dir);
|
|
@@ -2661,9 +2812,9 @@ var sharedEnvApp = new Hono5().get("/", (c) => {
|
|
|
2661
2812
|
var env_default = app5;
|
|
2662
2813
|
|
|
2663
2814
|
// src/web/routes/files.ts
|
|
2664
|
-
import { existsSync as
|
|
2815
|
+
import { existsSync as existsSync6 } from "fs";
|
|
2665
2816
|
import { readdir, readFile } from "fs/promises";
|
|
2666
|
-
import { resolve as
|
|
2817
|
+
import { resolve as resolve9 } from "path";
|
|
2667
2818
|
import { Hono as Hono6 } from "hono";
|
|
2668
2819
|
var ALLOWED_FILES = /* @__PURE__ */ new Set(["SOUL.md", "MEMORY.md", "CLAUDE.md", "VOLUTE.md"]);
|
|
2669
2820
|
var app6 = new Hono6().get("/:name/files", async (c) => {
|
|
@@ -2671,8 +2822,8 @@ var app6 = new Hono6().get("/:name/files", async (c) => {
|
|
|
2671
2822
|
const entry = findAgent(name);
|
|
2672
2823
|
if (!entry) return c.json({ error: "Agent not found" }, 404);
|
|
2673
2824
|
const dir = agentDir(name);
|
|
2674
|
-
const homeDir =
|
|
2675
|
-
if (!
|
|
2825
|
+
const homeDir = resolve9(dir, "home");
|
|
2826
|
+
if (!existsSync6(homeDir)) return c.json({ error: "Home directory missing" }, 404);
|
|
2676
2827
|
const allFiles = await readdir(homeDir);
|
|
2677
2828
|
const files = allFiles.filter((f) => f.endsWith(".md") && ALLOWED_FILES.has(f));
|
|
2678
2829
|
return c.json(files);
|
|
@@ -2685,8 +2836,8 @@ var app6 = new Hono6().get("/:name/files", async (c) => {
|
|
|
2685
2836
|
const entry = findAgent(name);
|
|
2686
2837
|
if (!entry) return c.json({ error: "Agent not found" }, 404);
|
|
2687
2838
|
const dir = agentDir(name);
|
|
2688
|
-
const filePath =
|
|
2689
|
-
if (!
|
|
2839
|
+
const filePath = resolve9(dir, "home", filename);
|
|
2840
|
+
if (!existsSync6(filePath)) {
|
|
2690
2841
|
return c.json({ error: "File not found" }, 404);
|
|
2691
2842
|
}
|
|
2692
2843
|
const content = await readFile(filePath, "utf-8");
|
|
@@ -2696,16 +2847,16 @@ var files_default = app6;
|
|
|
2696
2847
|
|
|
2697
2848
|
// src/web/routes/logs.ts
|
|
2698
2849
|
import { spawn as spawn2 } from "child_process";
|
|
2699
|
-
import { existsSync as
|
|
2700
|
-
import { resolve as
|
|
2850
|
+
import { existsSync as existsSync7 } from "fs";
|
|
2851
|
+
import { resolve as resolve10 } from "path";
|
|
2701
2852
|
import { Hono as Hono7 } from "hono";
|
|
2702
2853
|
import { streamSSE } from "hono/streaming";
|
|
2703
2854
|
var app7 = new Hono7().get("/:name/logs", async (c) => {
|
|
2704
2855
|
const name = c.req.param("name");
|
|
2705
2856
|
const entry = findAgent(name);
|
|
2706
2857
|
if (!entry) return c.json({ error: "Agent not found" }, 404);
|
|
2707
|
-
const logFile =
|
|
2708
|
-
if (!
|
|
2858
|
+
const logFile = resolve10(stateDir(name), "logs", "agent.log");
|
|
2859
|
+
if (!existsSync7(logFile)) {
|
|
2709
2860
|
return c.json({ error: "No log file found" }, 404);
|
|
2710
2861
|
}
|
|
2711
2862
|
return streamSSE(c, async (stream) => {
|
|
@@ -2723,17 +2874,17 @@ var app7 = new Hono7().get("/:name/logs", async (c) => {
|
|
|
2723
2874
|
stream.onAbort(() => {
|
|
2724
2875
|
tail.kill();
|
|
2725
2876
|
});
|
|
2726
|
-
await new Promise((
|
|
2727
|
-
tail.on("exit",
|
|
2728
|
-
stream.onAbort(
|
|
2877
|
+
await new Promise((resolve16) => {
|
|
2878
|
+
tail.on("exit", resolve16);
|
|
2879
|
+
stream.onAbort(resolve16);
|
|
2729
2880
|
});
|
|
2730
2881
|
});
|
|
2731
2882
|
}).get("/:name/logs/tail", async (c) => {
|
|
2732
2883
|
const name = c.req.param("name");
|
|
2733
2884
|
const entry = findAgent(name);
|
|
2734
2885
|
if (!entry) return c.json({ error: "Agent not found" }, 404);
|
|
2735
|
-
const logFile =
|
|
2736
|
-
if (!
|
|
2886
|
+
const logFile = resolve10(stateDir(name), "logs", "agent.log");
|
|
2887
|
+
if (!existsSync7(logFile)) {
|
|
2737
2888
|
return c.json({ error: "No log file found" }, 404);
|
|
2738
2889
|
}
|
|
2739
2890
|
const nParam = parseInt(c.req.query("n") ?? "50", 10);
|
|
@@ -2743,8 +2894,8 @@ var app7 = new Hono7().get("/:name/logs", async (c) => {
|
|
|
2743
2894
|
tail.stdout.on("data", (data) => {
|
|
2744
2895
|
output += data.toString();
|
|
2745
2896
|
});
|
|
2746
|
-
await new Promise((
|
|
2747
|
-
tail.on("exit",
|
|
2897
|
+
await new Promise((resolve16) => {
|
|
2898
|
+
tail.on("exit", resolve16);
|
|
2748
2899
|
});
|
|
2749
2900
|
return c.text(output);
|
|
2750
2901
|
});
|
|
@@ -2768,7 +2919,10 @@ var app8 = new Hono8().get("/:name/schedules", (c) => {
|
|
|
2768
2919
|
return c.json(readSchedules(name));
|
|
2769
2920
|
}).post("/:name/schedules", requireAdmin, async (c) => {
|
|
2770
2921
|
const name = c.req.param("name");
|
|
2771
|
-
|
|
2922
|
+
const entry = findAgent(name);
|
|
2923
|
+
if (!entry) return c.json({ error: "Agent not found" }, 404);
|
|
2924
|
+
if (entry.stage === "seed")
|
|
2925
|
+
return c.json({ error: "Seed agents cannot use schedules \u2014 sprout first" }, 403);
|
|
2772
2926
|
const body = await c.req.json();
|
|
2773
2927
|
if (!body.cron || !body.message) {
|
|
2774
2928
|
return c.json({ error: "cron and message are required" }, 400);
|
|
@@ -2852,10 +3006,10 @@ var app9 = new Hono9().post("/restart", requireAdmin, (c) => {
|
|
|
2852
3006
|
stream.writeSSE({ data: JSON.stringify(entry) }).catch(() => {
|
|
2853
3007
|
});
|
|
2854
3008
|
});
|
|
2855
|
-
await new Promise((
|
|
3009
|
+
await new Promise((resolve16) => {
|
|
2856
3010
|
stream.onAbort(() => {
|
|
2857
3011
|
unsubscribe();
|
|
2858
|
-
|
|
3012
|
+
resolve16();
|
|
2859
3013
|
});
|
|
2860
3014
|
});
|
|
2861
3015
|
});
|
|
@@ -2863,15 +3017,15 @@ var app9 = new Hono9().post("/restart", requireAdmin, (c) => {
|
|
|
2863
3017
|
var system_default = app9;
|
|
2864
3018
|
|
|
2865
3019
|
// src/web/routes/typing.ts
|
|
2866
|
-
import { zValidator as
|
|
3020
|
+
import { zValidator as zValidator3 } from "@hono/zod-validator";
|
|
2867
3021
|
import { Hono as Hono10 } from "hono";
|
|
2868
|
-
import { z as
|
|
2869
|
-
var typingSchema =
|
|
2870
|
-
channel:
|
|
2871
|
-
sender:
|
|
2872
|
-
active:
|
|
3022
|
+
import { z as z3 } from "zod";
|
|
3023
|
+
var typingSchema = z3.object({
|
|
3024
|
+
channel: z3.string().min(1),
|
|
3025
|
+
sender: z3.string().min(1),
|
|
3026
|
+
active: z3.boolean()
|
|
2873
3027
|
});
|
|
2874
|
-
var app10 = new Hono10().post("/:name/typing",
|
|
3028
|
+
var app10 = new Hono10().post("/:name/typing", zValidator3("json", typingSchema), (c) => {
|
|
2875
3029
|
const { channel, sender, active } = c.req.valid("json");
|
|
2876
3030
|
const map = getTypingMap();
|
|
2877
3031
|
if (active) {
|
|
@@ -2912,16 +3066,16 @@ var app11 = new Hono11().get("/update", async (c) => {
|
|
|
2912
3066
|
var update_default = app11;
|
|
2913
3067
|
|
|
2914
3068
|
// src/web/routes/variants.ts
|
|
2915
|
-
import { existsSync as
|
|
2916
|
-
import { resolve as
|
|
3069
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
3070
|
+
import { resolve as resolve12 } from "path";
|
|
2917
3071
|
import { Hono as Hono12 } from "hono";
|
|
2918
3072
|
|
|
2919
3073
|
// src/lib/spawn-server.ts
|
|
2920
3074
|
import { spawn as spawn4 } from "child_process";
|
|
2921
|
-
import { closeSync, mkdirSync as
|
|
2922
|
-
import { resolve as
|
|
3075
|
+
import { closeSync, mkdirSync as mkdirSync5, openSync, readFileSync as readFileSync6 } from "fs";
|
|
3076
|
+
import { resolve as resolve11 } from "path";
|
|
2923
3077
|
function tsxBin(cwd) {
|
|
2924
|
-
return
|
|
3078
|
+
return resolve11(cwd, "node_modules", ".bin", "tsx");
|
|
2925
3079
|
}
|
|
2926
3080
|
function spawnServer(cwd, port, options) {
|
|
2927
3081
|
if (options?.detached) {
|
|
@@ -2934,31 +3088,31 @@ function spawnAttached(cwd, port) {
|
|
|
2934
3088
|
cwd,
|
|
2935
3089
|
stdio: ["ignore", "pipe", "pipe"]
|
|
2936
3090
|
});
|
|
2937
|
-
return new Promise((
|
|
2938
|
-
const timeout = setTimeout(() =>
|
|
3091
|
+
return new Promise((resolve16) => {
|
|
3092
|
+
const timeout = setTimeout(() => resolve16(null), 3e4);
|
|
2939
3093
|
function checkOutput(data) {
|
|
2940
3094
|
const match = data.toString().match(/listening on :(\d+)/);
|
|
2941
3095
|
if (match) {
|
|
2942
3096
|
clearTimeout(timeout);
|
|
2943
|
-
|
|
3097
|
+
resolve16({ child, actualPort: parseInt(match[1], 10) });
|
|
2944
3098
|
}
|
|
2945
3099
|
}
|
|
2946
3100
|
child.stdout?.on("data", checkOutput);
|
|
2947
3101
|
child.stderr?.on("data", checkOutput);
|
|
2948
3102
|
child.on("error", () => {
|
|
2949
3103
|
clearTimeout(timeout);
|
|
2950
|
-
|
|
3104
|
+
resolve16(null);
|
|
2951
3105
|
});
|
|
2952
3106
|
child.on("exit", () => {
|
|
2953
3107
|
clearTimeout(timeout);
|
|
2954
|
-
|
|
3108
|
+
resolve16(null);
|
|
2955
3109
|
});
|
|
2956
3110
|
});
|
|
2957
3111
|
}
|
|
2958
3112
|
function spawnDetached(cwd, port, logDir) {
|
|
2959
|
-
const logsDir = logDir ??
|
|
2960
|
-
|
|
2961
|
-
const logPath =
|
|
3113
|
+
const logsDir = logDir ?? resolve11(cwd, ".volute", "logs");
|
|
3114
|
+
mkdirSync5(logsDir, { recursive: true });
|
|
3115
|
+
const logPath = resolve11(logsDir, "agent.log");
|
|
2962
3116
|
const logFd = openSync(logPath, "a");
|
|
2963
3117
|
const child = spawn4(tsxBin(cwd), ["src/server.ts", "--port", String(port)], {
|
|
2964
3118
|
cwd,
|
|
@@ -2978,7 +3132,7 @@ function spawnDetached(cwd, port, logDir) {
|
|
|
2978
3132
|
}
|
|
2979
3133
|
const interval = setInterval(() => {
|
|
2980
3134
|
try {
|
|
2981
|
-
const content =
|
|
3135
|
+
const content = readFileSync6(logPath, "utf-8");
|
|
2982
3136
|
const match = content.match(/listening on :(\d+)/);
|
|
2983
3137
|
if (match) {
|
|
2984
3138
|
finish({ child, actualPort: parseInt(match[1], 10) });
|
|
@@ -3047,6 +3201,8 @@ var app12 = new Hono12().get("/:name/variants", async (c) => {
|
|
|
3047
3201
|
const agentName = c.req.param("name");
|
|
3048
3202
|
const entry = findAgent(agentName);
|
|
3049
3203
|
if (!entry) return c.json({ error: "Agent not found" }, 404);
|
|
3204
|
+
if (entry.stage === "seed")
|
|
3205
|
+
return c.json({ error: "Seed agents cannot create variants \u2014 sprout first" }, 403);
|
|
3050
3206
|
let body;
|
|
3051
3207
|
try {
|
|
3052
3208
|
body = await c.req.json();
|
|
@@ -3058,11 +3214,11 @@ var app12 = new Hono12().get("/:name/variants", async (c) => {
|
|
|
3058
3214
|
const err = validateBranchName(variantName);
|
|
3059
3215
|
if (err) return c.json({ error: err }, 400);
|
|
3060
3216
|
const projectRoot = agentDir(agentName);
|
|
3061
|
-
const variantDir =
|
|
3062
|
-
if (
|
|
3217
|
+
const variantDir = resolve12(projectRoot, ".variants", variantName);
|
|
3218
|
+
if (existsSync8(variantDir)) {
|
|
3063
3219
|
return c.json({ error: `Variant directory already exists: ${variantDir}` }, 409);
|
|
3064
3220
|
}
|
|
3065
|
-
|
|
3221
|
+
mkdirSync6(resolve12(projectRoot, ".variants"), { recursive: true });
|
|
3066
3222
|
try {
|
|
3067
3223
|
await gitExec(["worktree", "add", "-b", variantName, variantDir], { cwd: projectRoot });
|
|
3068
3224
|
} catch (e) {
|
|
@@ -3076,7 +3232,7 @@ var app12 = new Hono12().get("/:name/variants", async (c) => {
|
|
|
3076
3232
|
return c.json({ error: `npm install failed: ${msg}` }, 500);
|
|
3077
3233
|
}
|
|
3078
3234
|
if (body.soul) {
|
|
3079
|
-
|
|
3235
|
+
writeFileSync5(resolve12(variantDir, "home/SOUL.md"), body.soul);
|
|
3080
3236
|
}
|
|
3081
3237
|
const variantPort = body.port ?? nextPort();
|
|
3082
3238
|
const variant = {
|
|
@@ -3115,7 +3271,7 @@ var app12 = new Hono12().get("/:name/variants", async (c) => {
|
|
|
3115
3271
|
} catch {
|
|
3116
3272
|
}
|
|
3117
3273
|
const projectRoot = agentDir(agentName);
|
|
3118
|
-
if (
|
|
3274
|
+
if (existsSync8(variant.path)) {
|
|
3119
3275
|
const status = (await gitExec(["status", "--porcelain"], { cwd: variant.path })).trim();
|
|
3120
3276
|
if (status) {
|
|
3121
3277
|
try {
|
|
@@ -3172,7 +3328,7 @@ var app12 = new Hono12().get("/:name/variants", async (c) => {
|
|
|
3172
3328
|
} catch (e) {
|
|
3173
3329
|
return c.json({ error: "Merge failed. Resolve conflicts manually." }, 500);
|
|
3174
3330
|
}
|
|
3175
|
-
if (
|
|
3331
|
+
if (existsSync8(variant.path)) {
|
|
3176
3332
|
try {
|
|
3177
3333
|
await gitExec(["worktree", "remove", "--force", variant.path], { cwd: projectRoot });
|
|
3178
3334
|
} catch {
|
|
@@ -3224,7 +3380,7 @@ var app12 = new Hono12().get("/:name/variants", async (c) => {
|
|
|
3224
3380
|
} catch {
|
|
3225
3381
|
}
|
|
3226
3382
|
}
|
|
3227
|
-
if (
|
|
3383
|
+
if (existsSync8(variant.path)) {
|
|
3228
3384
|
try {
|
|
3229
3385
|
await gitExec(["worktree", "remove", "--force", variant.path], { cwd: projectRoot });
|
|
3230
3386
|
} catch {
|
|
@@ -3241,221 +3397,26 @@ var app12 = new Hono12().get("/:name/variants", async (c) => {
|
|
|
3241
3397
|
var variants_default = app12;
|
|
3242
3398
|
|
|
3243
3399
|
// src/web/routes/volute/chat.ts
|
|
3244
|
-
import { readFileSync as
|
|
3245
|
-
import { resolve as
|
|
3246
|
-
import { zValidator as
|
|
3400
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
3401
|
+
import { resolve as resolve13 } from "path";
|
|
3402
|
+
import { zValidator as zValidator4 } from "@hono/zod-validator";
|
|
3247
3403
|
import { Hono as Hono13 } from "hono";
|
|
3248
3404
|
import { streamSSE as streamSSE3 } from "hono/streaming";
|
|
3249
|
-
import { z as
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
}
|
|
3259
|
-
set.add(callback);
|
|
3260
|
-
return () => {
|
|
3261
|
-
set.delete(callback);
|
|
3262
|
-
if (set.size === 0) subscribers.delete(conversationId);
|
|
3263
|
-
};
|
|
3264
|
-
}
|
|
3265
|
-
function publish(conversationId, event) {
|
|
3266
|
-
const set = subscribers.get(conversationId);
|
|
3267
|
-
if (!set) return;
|
|
3268
|
-
for (const cb of set) {
|
|
3269
|
-
try {
|
|
3270
|
-
cb(event);
|
|
3271
|
-
} catch (err) {
|
|
3272
|
-
console.error("[conversation-events] subscriber threw:", err);
|
|
3273
|
-
set.delete(cb);
|
|
3274
|
-
if (set.size === 0) subscribers.delete(conversationId);
|
|
3275
|
-
}
|
|
3276
|
-
}
|
|
3277
|
-
}
|
|
3278
|
-
|
|
3279
|
-
// src/lib/conversations.ts
|
|
3280
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
3281
|
-
import { and as and3, desc as desc2, eq as eq4, inArray, isNull, sql as sql2 } from "drizzle-orm";
|
|
3282
|
-
async function createConversation(agentName, channel, opts) {
|
|
3283
|
-
const db2 = await getDb();
|
|
3284
|
-
const id = randomUUID2();
|
|
3285
|
-
await db2.insert(conversations).values({
|
|
3286
|
-
id,
|
|
3287
|
-
agent_name: agentName,
|
|
3288
|
-
channel,
|
|
3289
|
-
user_id: opts?.userId ?? null,
|
|
3290
|
-
title: opts?.title ?? null
|
|
3291
|
-
});
|
|
3292
|
-
if (opts?.participantIds && opts.participantIds.length > 0) {
|
|
3293
|
-
await db2.insert(conversationParticipants).values(
|
|
3294
|
-
opts.participantIds.map((uid, i) => ({
|
|
3295
|
-
conversation_id: id,
|
|
3296
|
-
user_id: uid,
|
|
3297
|
-
role: i === 0 ? "owner" : "member"
|
|
3298
|
-
}))
|
|
3299
|
-
);
|
|
3300
|
-
}
|
|
3301
|
-
return {
|
|
3302
|
-
id,
|
|
3303
|
-
agent_name: agentName,
|
|
3304
|
-
channel,
|
|
3305
|
-
user_id: opts?.userId ?? null,
|
|
3306
|
-
title: opts?.title ?? null,
|
|
3307
|
-
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3308
|
-
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
3309
|
-
};
|
|
3310
|
-
}
|
|
3311
|
-
async function getConversation(id) {
|
|
3312
|
-
const db2 = await getDb();
|
|
3313
|
-
const row = await db2.select().from(conversations).where(eq4(conversations.id, id)).get();
|
|
3314
|
-
return row ?? null;
|
|
3315
|
-
}
|
|
3316
|
-
async function getParticipants(conversationId) {
|
|
3317
|
-
const db2 = await getDb();
|
|
3318
|
-
const rows = await db2.select({
|
|
3319
|
-
userId: conversationParticipants.user_id,
|
|
3320
|
-
username: users.username,
|
|
3321
|
-
userType: users.user_type,
|
|
3322
|
-
role: conversationParticipants.role
|
|
3323
|
-
}).from(conversationParticipants).innerJoin(users, eq4(conversationParticipants.user_id, users.id)).where(eq4(conversationParticipants.conversation_id, conversationId)).all();
|
|
3324
|
-
return rows;
|
|
3325
|
-
}
|
|
3326
|
-
async function isParticipant(conversationId, userId) {
|
|
3327
|
-
const db2 = await getDb();
|
|
3328
|
-
const row = await db2.select({ user_id: conversationParticipants.user_id }).from(conversationParticipants).where(
|
|
3329
|
-
and3(
|
|
3330
|
-
eq4(conversationParticipants.conversation_id, conversationId),
|
|
3331
|
-
eq4(conversationParticipants.user_id, userId)
|
|
3332
|
-
)
|
|
3333
|
-
).get();
|
|
3334
|
-
return row != null;
|
|
3335
|
-
}
|
|
3336
|
-
async function listConversationsForUser(userId) {
|
|
3337
|
-
const db2 = await getDb();
|
|
3338
|
-
const participantRows = await db2.select({ conversation_id: conversationParticipants.conversation_id }).from(conversationParticipants).where(eq4(conversationParticipants.user_id, userId)).all();
|
|
3339
|
-
if (participantRows.length === 0) return [];
|
|
3340
|
-
const convIds = participantRows.map((r) => r.conversation_id);
|
|
3341
|
-
return db2.select().from(conversations).where(inArray(conversations.id, convIds)).orderBy(desc2(conversations.updated_at)).all();
|
|
3342
|
-
}
|
|
3343
|
-
async function isParticipantOrOwner(conversationId, userId) {
|
|
3344
|
-
if (await isParticipant(conversationId, userId)) return true;
|
|
3345
|
-
const db2 = await getDb();
|
|
3346
|
-
const row = await db2.select().from(conversations).where(and3(eq4(conversations.id, conversationId), eq4(conversations.user_id, userId))).get();
|
|
3347
|
-
return row != null;
|
|
3348
|
-
}
|
|
3349
|
-
async function deleteConversationForUser(id, userId) {
|
|
3350
|
-
if (!await isParticipantOrOwner(id, userId)) return false;
|
|
3351
|
-
await deleteConversation(id);
|
|
3352
|
-
return true;
|
|
3353
|
-
}
|
|
3354
|
-
async function addMessage(conversationId, role, senderName, content) {
|
|
3355
|
-
const db2 = await getDb();
|
|
3356
|
-
const serialized = JSON.stringify(content);
|
|
3357
|
-
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 });
|
|
3358
|
-
await db2.update(conversations).set({ updated_at: sql2`datetime('now')` }).where(eq4(conversations.id, conversationId));
|
|
3359
|
-
if (role === "user") {
|
|
3360
|
-
const firstText = content.find((b) => b.type === "text");
|
|
3361
|
-
const title = firstText ? firstText.text.slice(0, 80) : "";
|
|
3362
|
-
if (title) {
|
|
3363
|
-
await db2.update(conversations).set({ title }).where(and3(eq4(conversations.id, conversationId), isNull(conversations.title)));
|
|
3364
|
-
}
|
|
3365
|
-
}
|
|
3366
|
-
const msg = {
|
|
3367
|
-
id: result.id,
|
|
3368
|
-
conversation_id: conversationId,
|
|
3369
|
-
role,
|
|
3370
|
-
sender_name: senderName,
|
|
3371
|
-
content,
|
|
3372
|
-
created_at: result.created_at
|
|
3373
|
-
};
|
|
3374
|
-
publish(conversationId, {
|
|
3375
|
-
type: "message",
|
|
3376
|
-
id: msg.id,
|
|
3377
|
-
role: msg.role,
|
|
3378
|
-
senderName: msg.sender_name,
|
|
3379
|
-
content: msg.content,
|
|
3380
|
-
createdAt: msg.created_at
|
|
3381
|
-
});
|
|
3382
|
-
return msg;
|
|
3383
|
-
}
|
|
3384
|
-
async function getMessages(conversationId) {
|
|
3385
|
-
const db2 = await getDb();
|
|
3386
|
-
const rows = await db2.select().from(messages).where(eq4(messages.conversation_id, conversationId)).orderBy(messages.created_at).all();
|
|
3387
|
-
return rows.map((row) => {
|
|
3388
|
-
let content;
|
|
3389
|
-
try {
|
|
3390
|
-
const parsed = JSON.parse(row.content);
|
|
3391
|
-
content = Array.isArray(parsed) ? parsed : [{ type: "text", text: row.content }];
|
|
3392
|
-
} catch {
|
|
3393
|
-
content = [{ type: "text", text: row.content }];
|
|
3394
|
-
}
|
|
3395
|
-
return { ...row, content };
|
|
3396
|
-
});
|
|
3397
|
-
}
|
|
3398
|
-
async function listConversationsWithParticipants(userId) {
|
|
3399
|
-
const convs = await listConversationsForUser(userId);
|
|
3400
|
-
if (convs.length === 0) return [];
|
|
3401
|
-
const db2 = await getDb();
|
|
3402
|
-
const convIds = convs.map((c) => c.id);
|
|
3403
|
-
const rows = await db2.select({
|
|
3404
|
-
conversationId: conversationParticipants.conversation_id,
|
|
3405
|
-
userId: users.id,
|
|
3406
|
-
username: users.username,
|
|
3407
|
-
userType: users.user_type,
|
|
3408
|
-
role: conversationParticipants.role
|
|
3409
|
-
}).from(conversationParticipants).innerJoin(users, eq4(conversationParticipants.user_id, users.id)).where(inArray(conversationParticipants.conversation_id, convIds));
|
|
3410
|
-
const byConv = /* @__PURE__ */ new Map();
|
|
3411
|
-
for (const r of rows) {
|
|
3412
|
-
let arr = byConv.get(r.conversationId);
|
|
3413
|
-
if (!arr) {
|
|
3414
|
-
arr = [];
|
|
3415
|
-
byConv.set(r.conversationId, arr);
|
|
3416
|
-
}
|
|
3417
|
-
arr.push({
|
|
3418
|
-
userId: r.userId,
|
|
3419
|
-
username: r.username,
|
|
3420
|
-
userType: r.userType,
|
|
3421
|
-
role: r.role
|
|
3422
|
-
});
|
|
3423
|
-
}
|
|
3424
|
-
return convs.map((c) => ({ ...c, participants: byConv.get(c.id) ?? [] }));
|
|
3425
|
-
}
|
|
3426
|
-
async function findDMConversation(agentName, participantIds) {
|
|
3427
|
-
const db2 = await getDb();
|
|
3428
|
-
const agentConvs = await db2.select({ id: conversations.id }).from(conversations).where(eq4(conversations.agent_name, agentName)).all();
|
|
3429
|
-
for (const conv of agentConvs) {
|
|
3430
|
-
const rows = await db2.select({ user_id: conversationParticipants.user_id }).from(conversationParticipants).where(eq4(conversationParticipants.conversation_id, conv.id)).all();
|
|
3431
|
-
if (rows.length !== 2) continue;
|
|
3432
|
-
const ids = new Set(rows.map((r) => r.user_id));
|
|
3433
|
-
if (ids.has(participantIds[0]) && ids.has(participantIds[1])) {
|
|
3434
|
-
return conv.id;
|
|
3435
|
-
}
|
|
3436
|
-
}
|
|
3437
|
-
return null;
|
|
3438
|
-
}
|
|
3439
|
-
async function deleteConversation(id) {
|
|
3440
|
-
const db2 = await getDb();
|
|
3441
|
-
await db2.delete(conversations).where(eq4(conversations.id, id));
|
|
3442
|
-
}
|
|
3443
|
-
|
|
3444
|
-
// src/web/routes/volute/chat.ts
|
|
3445
|
-
var chatSchema = z3.object({
|
|
3446
|
-
message: z3.string().optional(),
|
|
3447
|
-
conversationId: z3.string().optional(),
|
|
3448
|
-
sender: z3.string().optional(),
|
|
3449
|
-
images: z3.array(
|
|
3450
|
-
z3.object({
|
|
3451
|
-
media_type: z3.string(),
|
|
3452
|
-
data: z3.string()
|
|
3405
|
+
import { z as z4 } from "zod";
|
|
3406
|
+
var chatSchema = z4.object({
|
|
3407
|
+
message: z4.string().optional(),
|
|
3408
|
+
conversationId: z4.string().optional(),
|
|
3409
|
+
sender: z4.string().optional(),
|
|
3410
|
+
images: z4.array(
|
|
3411
|
+
z4.object({
|
|
3412
|
+
media_type: z4.string(),
|
|
3413
|
+
data: z4.string()
|
|
3453
3414
|
})
|
|
3454
3415
|
).optional()
|
|
3455
3416
|
});
|
|
3456
3417
|
function getDaemonUrl() {
|
|
3457
3418
|
try {
|
|
3458
|
-
const data = JSON.parse(
|
|
3419
|
+
const data = JSON.parse(readFileSync7(resolve13(voluteHome(), "daemon.json"), "utf-8"));
|
|
3459
3420
|
return `http://${daemonLoopback()}:${data.port}`;
|
|
3460
3421
|
} catch (err) {
|
|
3461
3422
|
throw new Error(`Failed to read daemon config: ${err instanceof Error ? err.message : err}`);
|
|
@@ -3471,7 +3432,7 @@ function daemonFetchInternal(path, body) {
|
|
|
3471
3432
|
if (token) headers.Authorization = `Bearer ${token}`;
|
|
3472
3433
|
return fetch(`${daemonUrl}${path}`, { method: "POST", headers, body });
|
|
3473
3434
|
}
|
|
3474
|
-
var app13 = new Hono13().post("/:name/chat",
|
|
3435
|
+
var app13 = new Hono13().post("/:name/chat", zValidator4("json", chatSchema), async (c) => {
|
|
3475
3436
|
const name = c.req.param("name");
|
|
3476
3437
|
const [baseName] = name.split("@", 2);
|
|
3477
3438
|
const entry = findAgent(baseName);
|
|
@@ -3532,7 +3493,7 @@ var app13 = new Hono13().post("/:name/chat", zValidator3("json", chatSchema), as
|
|
|
3532
3493
|
const participants = await getParticipants(conversationId);
|
|
3533
3494
|
const agentParticipants = participants.filter((p) => p.userType === "agent");
|
|
3534
3495
|
const participantNames = participants.map((p) => p.username);
|
|
3535
|
-
const { getAgentManager: getAgentManager2 } = await import("./agent-manager-
|
|
3496
|
+
const { getAgentManager: getAgentManager2 } = await import("./agent-manager-HHBAAL2D.js");
|
|
3536
3497
|
const manager = getAgentManager2();
|
|
3537
3498
|
const runningAgents = agentParticipants.map((ap) => {
|
|
3538
3499
|
const agentKey = ap.username === baseName ? name : ap.username;
|
|
@@ -3602,11 +3563,11 @@ var app13 = new Hono13().post("/:name/chat", zValidator3("json", chatSchema), as
|
|
|
3602
3563
|
if (!stream.aborted) console.error("[chat] SSE ping error:", err);
|
|
3603
3564
|
});
|
|
3604
3565
|
}, 15e3);
|
|
3605
|
-
await new Promise((
|
|
3566
|
+
await new Promise((resolve16) => {
|
|
3606
3567
|
stream.onAbort(() => {
|
|
3607
3568
|
unsubscribe();
|
|
3608
3569
|
clearInterval(keepAlive);
|
|
3609
|
-
|
|
3570
|
+
resolve16();
|
|
3610
3571
|
});
|
|
3611
3572
|
});
|
|
3612
3573
|
});
|
|
@@ -3614,13 +3575,13 @@ var app13 = new Hono13().post("/:name/chat", zValidator3("json", chatSchema), as
|
|
|
3614
3575
|
var chat_default = app13;
|
|
3615
3576
|
|
|
3616
3577
|
// src/web/routes/volute/conversations.ts
|
|
3617
|
-
import { zValidator as
|
|
3578
|
+
import { zValidator as zValidator5 } from "@hono/zod-validator";
|
|
3618
3579
|
import { Hono as Hono14 } from "hono";
|
|
3619
|
-
import { z as
|
|
3620
|
-
var createConvSchema =
|
|
3621
|
-
title:
|
|
3622
|
-
participantIds:
|
|
3623
|
-
participantNames:
|
|
3580
|
+
import { z as z5 } from "zod";
|
|
3581
|
+
var createConvSchema = z5.object({
|
|
3582
|
+
title: z5.string().optional(),
|
|
3583
|
+
participantIds: z5.array(z5.number()).optional(),
|
|
3584
|
+
participantNames: z5.array(z5.string()).optional()
|
|
3624
3585
|
});
|
|
3625
3586
|
var app14 = new Hono14().get("/:name/conversations", async (c) => {
|
|
3626
3587
|
const name = c.req.param("name");
|
|
@@ -3633,7 +3594,7 @@ var app14 = new Hono14().get("/:name/conversations", async (c) => {
|
|
|
3633
3594
|
const all = await listConversationsForUser(lookupId);
|
|
3634
3595
|
const convs = all.filter((c2) => c2.agent_name === name);
|
|
3635
3596
|
return c.json(convs);
|
|
3636
|
-
}).post("/:name/conversations",
|
|
3597
|
+
}).post("/:name/conversations", zValidator5("json", createConvSchema), async (c) => {
|
|
3637
3598
|
const name = c.req.param("name");
|
|
3638
3599
|
const user = c.get("user");
|
|
3639
3600
|
const body = c.req.valid("json");
|
|
@@ -3710,12 +3671,12 @@ var app14 = new Hono14().get("/:name/conversations", async (c) => {
|
|
|
3710
3671
|
var conversations_default = app14;
|
|
3711
3672
|
|
|
3712
3673
|
// src/web/routes/volute/user-conversations.ts
|
|
3713
|
-
import { zValidator as
|
|
3674
|
+
import { zValidator as zValidator6 } from "@hono/zod-validator";
|
|
3714
3675
|
import { Hono as Hono15 } from "hono";
|
|
3715
|
-
import { z as
|
|
3716
|
-
var createSchema =
|
|
3717
|
-
title:
|
|
3718
|
-
participantNames:
|
|
3676
|
+
import { z as z6 } from "zod";
|
|
3677
|
+
var createSchema = z6.object({
|
|
3678
|
+
title: z6.string().optional(),
|
|
3679
|
+
participantNames: z6.array(z6.string()).min(1)
|
|
3719
3680
|
});
|
|
3720
3681
|
var app15 = new Hono15().use("*", authMiddleware).get("/", async (c) => {
|
|
3721
3682
|
const user = c.get("user");
|
|
@@ -3729,7 +3690,7 @@ var app15 = new Hono15().use("*", authMiddleware).get("/", async (c) => {
|
|
|
3729
3690
|
}
|
|
3730
3691
|
const msgs = await getMessages(id);
|
|
3731
3692
|
return c.json(msgs);
|
|
3732
|
-
}).post("/",
|
|
3693
|
+
}).post("/", zValidator6("json", createSchema), async (c) => {
|
|
3733
3694
|
const user = c.get("user");
|
|
3734
3695
|
const body = c.req.valid("json");
|
|
3735
3696
|
const participantIds = /* @__PURE__ */ new Set();
|
|
@@ -3834,20 +3795,20 @@ async function startServer({
|
|
|
3834
3795
|
hostname = "127.0.0.1"
|
|
3835
3796
|
}) {
|
|
3836
3797
|
let assetsDir = "";
|
|
3837
|
-
let searchDir =
|
|
3798
|
+
let searchDir = dirname3(new URL(import.meta.url).pathname);
|
|
3838
3799
|
for (let i = 0; i < 5; i++) {
|
|
3839
|
-
const candidate =
|
|
3840
|
-
if (
|
|
3800
|
+
const candidate = resolve14(searchDir, "dist", "web-assets");
|
|
3801
|
+
if (existsSync9(candidate)) {
|
|
3841
3802
|
assetsDir = candidate;
|
|
3842
3803
|
break;
|
|
3843
3804
|
}
|
|
3844
|
-
searchDir =
|
|
3805
|
+
searchDir = dirname3(searchDir);
|
|
3845
3806
|
}
|
|
3846
3807
|
if (assetsDir) {
|
|
3847
3808
|
app_default.get("*", async (c) => {
|
|
3848
3809
|
const urlPath = new URL(c.req.url).pathname;
|
|
3849
3810
|
if (urlPath.startsWith("/api/")) return c.notFound();
|
|
3850
|
-
const filePath =
|
|
3811
|
+
const filePath = resolve14(assetsDir, urlPath.slice(1));
|
|
3851
3812
|
if (!filePath.startsWith(assetsDir)) return c.text("Forbidden", 403);
|
|
3852
3813
|
const s = await stat(filePath).catch(() => null);
|
|
3853
3814
|
if (s?.isFile()) {
|
|
@@ -3856,7 +3817,7 @@ async function startServer({
|
|
|
3856
3817
|
const body = await readFile2(filePath);
|
|
3857
3818
|
return c.body(body, 200, { "Content-Type": mime });
|
|
3858
3819
|
}
|
|
3859
|
-
const indexPath =
|
|
3820
|
+
const indexPath = resolve14(assetsDir, "index.html");
|
|
3860
3821
|
const indexStat = await stat(indexPath).catch(() => null);
|
|
3861
3822
|
if (indexStat?.isFile()) {
|
|
3862
3823
|
const body = await readFile2(indexPath, "utf-8");
|
|
@@ -3866,10 +3827,10 @@ async function startServer({
|
|
|
3866
3827
|
});
|
|
3867
3828
|
}
|
|
3868
3829
|
const server = serve({ fetch: app_default.fetch, port, hostname });
|
|
3869
|
-
await new Promise((
|
|
3830
|
+
await new Promise((resolve16, reject) => {
|
|
3870
3831
|
server.on("listening", () => {
|
|
3871
3832
|
logger_default.info("Volute UI running", { hostname, port });
|
|
3872
|
-
|
|
3833
|
+
resolve16();
|
|
3873
3834
|
});
|
|
3874
3835
|
server.on("error", (err) => {
|
|
3875
3836
|
reject(err);
|
|
@@ -3880,14 +3841,14 @@ async function startServer({
|
|
|
3880
3841
|
|
|
3881
3842
|
// src/daemon.ts
|
|
3882
3843
|
if (!process.env.VOLUTE_HOME) {
|
|
3883
|
-
process.env.VOLUTE_HOME =
|
|
3844
|
+
process.env.VOLUTE_HOME = resolve15(homedir2(), ".volute");
|
|
3884
3845
|
}
|
|
3885
3846
|
async function startDaemon(opts) {
|
|
3886
3847
|
const { port, hostname } = opts;
|
|
3887
3848
|
const myPid = String(process.pid);
|
|
3888
3849
|
const home = voluteHome();
|
|
3889
3850
|
if (!opts.foreground) {
|
|
3890
|
-
const log2 = new RotatingLog(
|
|
3851
|
+
const log2 = new RotatingLog(resolve15(home, "daemon.log"));
|
|
3891
3852
|
const write2 = (...args) => log2.write(`${format(...args)}
|
|
3892
3853
|
`);
|
|
3893
3854
|
console.log = write2;
|
|
@@ -3895,9 +3856,9 @@ async function startDaemon(opts) {
|
|
|
3895
3856
|
console.warn = write2;
|
|
3896
3857
|
console.info = write2;
|
|
3897
3858
|
}
|
|
3898
|
-
const DAEMON_PID_PATH =
|
|
3899
|
-
const DAEMON_JSON_PATH =
|
|
3900
|
-
|
|
3859
|
+
const DAEMON_PID_PATH = resolve15(home, "daemon.pid");
|
|
3860
|
+
const DAEMON_JSON_PATH = resolve15(home, "daemon.json");
|
|
3861
|
+
mkdirSync7(home, { recursive: true });
|
|
3901
3862
|
const token = process.env.VOLUTE_DAEMON_TOKEN || randomBytes(32).toString("hex");
|
|
3902
3863
|
process.env.VOLUTE_DAEMON_TOKEN = token;
|
|
3903
3864
|
process.env.VOLUTE_DAEMON_PORT = String(port);
|
|
@@ -3913,8 +3874,8 @@ async function startDaemon(opts) {
|
|
|
3913
3874
|
}
|
|
3914
3875
|
throw err;
|
|
3915
3876
|
}
|
|
3916
|
-
|
|
3917
|
-
|
|
3877
|
+
writeFileSync6(DAEMON_PID_PATH, myPid, { mode: 420 });
|
|
3878
|
+
writeFileSync6(DAEMON_JSON_PATH, `${JSON.stringify({ port, hostname, token }, null, 2)}
|
|
3918
3879
|
`, {
|
|
3919
3880
|
mode: 420
|
|
3920
3881
|
});
|
|
@@ -3937,6 +3898,7 @@ async function startDaemon(opts) {
|
|
|
3937
3898
|
if (!entry.running) continue;
|
|
3938
3899
|
try {
|
|
3939
3900
|
await manager.startAgent(entry.name);
|
|
3901
|
+
if (entry.stage === "seed") continue;
|
|
3940
3902
|
const dir = agentDir(entry.name);
|
|
3941
3903
|
await connectors.startConnectors(entry.name, dir, entry.port, port);
|
|
3942
3904
|
scheduler.loadSchedules(entry.name);
|
|
@@ -3968,13 +3930,13 @@ async function startDaemon(opts) {
|
|
|
3968
3930
|
console.error(`[daemon] running on ${hostname}:${port}, pid ${myPid}`);
|
|
3969
3931
|
function cleanup() {
|
|
3970
3932
|
try {
|
|
3971
|
-
if (
|
|
3933
|
+
if (readFileSync8(DAEMON_PID_PATH, "utf-8").trim() === myPid) {
|
|
3972
3934
|
unlinkSync2(DAEMON_PID_PATH);
|
|
3973
3935
|
}
|
|
3974
3936
|
} catch {
|
|
3975
3937
|
}
|
|
3976
3938
|
try {
|
|
3977
|
-
const data = JSON.parse(
|
|
3939
|
+
const data = JSON.parse(readFileSync8(DAEMON_JSON_PATH, "utf-8"));
|
|
3978
3940
|
if (data.token === token) {
|
|
3979
3941
|
unlinkSync2(DAEMON_JSON_PATH);
|
|
3980
3942
|
}
|