volute 0.7.0 → 0.8.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.
- package/README.md +16 -14
- package/dist/{agent-7JF7MT73.js → agent-YORVRB6I.js} +10 -10
- package/dist/{agent-manager-IMZ7ZMBF.js → agent-manager-CMMH5KQQ.js} +4 -4
- package/dist/{channel-SMCNOIVQ.js → channel-RDGHBFSI.js} +16 -56
- package/dist/{chunk-JR4UXCTO.js → chunk-23L3MKEV.js} +1 -1
- package/dist/{chunk-5SKQ6J7T.js → chunk-5C5JWR2L.js} +15 -7
- package/dist/{chunk-UWHWAPGO.js → chunk-DP2DX4WV.js} +9 -1
- package/dist/{chunk-7ACDT3P2.js → chunk-ECPQXRLB.js} +1 -2
- package/dist/{chunk-LLJNZPCU.js → chunk-HZ5LTOEJ.js} +1 -1
- package/dist/{chunk-W76KWE23.js → chunk-IQXBMFZG.js} +6 -4
- package/dist/{chunk-ZZOOTYXK.js → chunk-LIPPXNIE.js} +60 -74
- package/dist/{chunk-BX7KI4S3.js → chunk-N6MLQ26B.js} +23 -96
- package/dist/{chunk-H7AMDUIA.js → chunk-QF22MYDJ.js} +6 -5
- package/dist/{chunk-NKXULRSW.js → chunk-RT6Y7AR3.js} +1 -1
- package/dist/{chunk-62X577Y7.js → chunk-W6TMWYU3.js} +126 -73
- package/dist/{chunk-EG45HBSJ.js → chunk-XSJ27WEM.js} +1 -1
- package/dist/cli.js +22 -20
- package/dist/{connector-Y7JPNROO.js → connector-ZP6MEFF4.js} +3 -3
- package/dist/connectors/discord.js +18 -59
- package/dist/connectors/slack.js +21 -38
- package/dist/connectors/telegram.js +31 -49
- package/dist/{create-G525LWEA.js → create-HGJHLABX.js} +22 -17
- package/dist/{daemon-client-442IV43D.js → daemon-client-54J3EIZD.js} +2 -2
- package/dist/{daemon-restart-4HVEKYFY.js → daemon-restart-CPBLMMRI.js} +3 -3
- package/dist/daemon.js +342 -402
- package/dist/{delete-UOU4AFQN.js → delete-45TGQC4N.js} +10 -5
- package/dist/{down-AZVH5TCD.js → down-O4EWZTVA.js} +2 -2
- package/dist/{env-7GLUJCWS.js → env-KMNYGVZ2.js} +7 -9
- package/dist/{history-H72ZUIBN.js → history-PXJVYLVY.js} +2 -2
- package/dist/{import-AVKQJDYC.js → import-CNEDF3TD.js} +6 -6
- package/dist/{logs-EDGK26AK.js → logs-TZB3MTLZ.js} +5 -4
- package/dist/{package-T2WAVJOU.js → package-RJSONENE.js} +1 -1
- package/dist/{restart-O4ETYLJF.js → restart-KVH3TK5N.js} +2 -2
- package/dist/{schedule-S6QVC5ON.js → schedule-HCUCBNQI.js} +2 -2
- package/dist/send-BNC2S5BY.js +162 -0
- package/dist/{service-HZNIDNJF.js → service-XCADRKIS.js} +8 -1
- package/dist/{setup-F4TCWVSP.js → setup-32KH5KLN.js} +85 -26
- package/dist/{start-VHQ7LNWM.js → start-QU73YTJW.js} +2 -2
- package/dist/{status-QAJWXKMZ.js → status-Q6ZQJXNI.js} +2 -2
- package/dist/{stop-CAGCT5NI.js → stop-N7U5N6A7.js} +2 -2
- package/dist/{up-RWZF6MLT.js → up-V6EAA7OZ.js} +2 -2
- package/dist/{update-F7QWV2LB.js → update-EUCZ7XGG.js} +3 -3
- package/dist/{update-check-B4J6IEQ4.js → update-check-SM4244SU.js} +2 -2
- package/dist/{upgrade-YXKPWDRU.js → upgrade-CZF6PN7Y.js} +4 -4
- package/dist/{variant-4Z6W3PP6.js → variant-RKXPN5DH.js} +20 -46
- package/dist/web-assets/assets/index-D-3zx6vs.js +307 -0
- package/dist/web-assets/index.html +1 -1
- package/drizzle/0004_magical_silverclaw.sql +1 -0
- package/drizzle/meta/0004_snapshot.json +410 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +1 -1
- package/templates/_base/_skills/volute-agent/SKILL.md +32 -16
- package/templates/_base/home/.config/routes.json +4 -8
- package/templates/_base/home/VOLUTE.md +16 -14
- package/templates/_base/src/lib/auto-reply.ts +38 -0
- package/templates/_base/src/lib/daemon-client.ts +53 -0
- package/templates/_base/src/lib/router.ts +66 -14
- package/templates/_base/src/lib/routing.ts +48 -9
- package/templates/_base/src/lib/startup.ts +1 -25
- package/templates/_base/src/lib/types.ts +2 -1
- package/templates/_base/src/lib/volute-server.ts +29 -14
- package/templates/agent-sdk/src/agent.ts +53 -111
- package/templates/agent-sdk/src/lib/content.ts +41 -0
- package/templates/agent-sdk/src/lib/session-store.ts +43 -0
- package/templates/agent-sdk/src/lib/stream-consumer.ts +66 -0
- package/templates/agent-sdk/src/server.ts +5 -13
- package/templates/pi/.init/AGENTS.md +5 -5
- package/templates/pi/src/agent.ts +32 -84
- package/templates/pi/src/lib/content.ts +15 -0
- package/templates/pi/src/lib/event-handler.ts +74 -0
- package/templates/pi/src/lib/resolve-model.ts +21 -0
- package/templates/pi/src/server.ts +3 -7
- package/dist/chunk-B3R6L2GW.js +0 -24
- package/dist/chunk-ZYGKG6VC.js +0 -22
- package/dist/message-SCOQDR3P.js +0 -32
- package/dist/send-G7PE4DOJ.js +0 -72
- package/dist/web-assets/assets/index-B1CqjUYD.js +0 -308
package/dist/daemon.js
CHANGED
|
@@ -6,18 +6,15 @@ import {
|
|
|
6
6
|
initAgentManager,
|
|
7
7
|
loadJsonMap,
|
|
8
8
|
saveJsonMap
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-W6TMWYU3.js";
|
|
10
10
|
import {
|
|
11
11
|
checkForUpdate,
|
|
12
12
|
checkForUpdateCached,
|
|
13
13
|
getCurrentVersion
|
|
14
|
-
} from "./chunk-
|
|
15
|
-
import {
|
|
16
|
-
collectPart
|
|
17
|
-
} from "./chunk-B3R6L2GW.js";
|
|
14
|
+
} from "./chunk-RT6Y7AR3.js";
|
|
18
15
|
import {
|
|
19
16
|
CHANNELS
|
|
20
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-LIPPXNIE.js";
|
|
21
18
|
import {
|
|
22
19
|
agentMessages,
|
|
23
20
|
approveUser,
|
|
@@ -36,24 +33,24 @@ import {
|
|
|
36
33
|
sessions,
|
|
37
34
|
users,
|
|
38
35
|
verifyUser
|
|
39
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-ECPQXRLB.js";
|
|
40
37
|
import {
|
|
41
38
|
readVoluteConfig,
|
|
42
39
|
writeVoluteConfig
|
|
43
40
|
} from "./chunk-NETNFBA5.js";
|
|
44
41
|
import {
|
|
45
42
|
loadMergedEnv
|
|
46
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-QF22MYDJ.js";
|
|
47
44
|
import {
|
|
48
45
|
slugify,
|
|
49
46
|
writeChannelEntry
|
|
50
|
-
} from "./chunk-
|
|
47
|
+
} from "./chunk-N6MLQ26B.js";
|
|
51
48
|
import {
|
|
52
49
|
applyIsolation
|
|
53
|
-
} from "./chunk-
|
|
50
|
+
} from "./chunk-IQXBMFZG.js";
|
|
54
51
|
import {
|
|
55
52
|
resolveVoluteBin
|
|
56
|
-
} from "./chunk-
|
|
53
|
+
} from "./chunk-5C5JWR2L.js";
|
|
57
54
|
import {
|
|
58
55
|
agentDir,
|
|
59
56
|
checkHealth,
|
|
@@ -67,15 +64,17 @@ import {
|
|
|
67
64
|
removeAllVariants,
|
|
68
65
|
setAgentRunning,
|
|
69
66
|
setVariantRunning,
|
|
67
|
+
stateDir,
|
|
68
|
+
validateBranchName,
|
|
70
69
|
voluteHome
|
|
71
|
-
} from "./chunk-
|
|
70
|
+
} from "./chunk-DP2DX4WV.js";
|
|
72
71
|
import "./chunk-K3NQKI34.js";
|
|
73
72
|
|
|
74
73
|
// src/daemon.ts
|
|
75
74
|
import { randomBytes } from "crypto";
|
|
76
|
-
import { mkdirSync as
|
|
75
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
77
76
|
import { homedir } from "os";
|
|
78
|
-
import { resolve as
|
|
77
|
+
import { resolve as resolve10 } from "path";
|
|
79
78
|
import { format } from "util";
|
|
80
79
|
|
|
81
80
|
// src/lib/connector-manager.ts
|
|
@@ -186,13 +185,13 @@ var ConnectorManager = class {
|
|
|
186
185
|
}
|
|
187
186
|
}
|
|
188
187
|
}
|
|
189
|
-
checkConnectorEnv(type, agentDir2) {
|
|
188
|
+
checkConnectorEnv(type, agentName, agentDir2) {
|
|
190
189
|
const agentConnectorDir = resolve2(agentDir2, "connectors", type);
|
|
191
190
|
const userConnectorDir = resolve2(voluteHome(), "connectors", type);
|
|
192
191
|
const connectorDir = existsSync2(agentConnectorDir) ? agentConnectorDir : existsSync2(userConnectorDir) ? userConnectorDir : void 0;
|
|
193
192
|
const def = getConnectorDef(type, connectorDir);
|
|
194
193
|
if (!def) return null;
|
|
195
|
-
const env = loadMergedEnv(
|
|
194
|
+
const env = loadMergedEnv(agentName);
|
|
196
195
|
const missing = checkMissingEnvVars(def, env);
|
|
197
196
|
if (missing.length === 0) return null;
|
|
198
197
|
return {
|
|
@@ -220,7 +219,7 @@ var ConnectorManager = class {
|
|
|
220
219
|
});
|
|
221
220
|
this.connectors.get(agentName)?.delete(type);
|
|
222
221
|
}
|
|
223
|
-
this.killOrphanConnector(
|
|
222
|
+
this.killOrphanConnector(agentName, type);
|
|
224
223
|
const agentConnector = resolve2(agentDir2, "connectors", type, "index.ts");
|
|
225
224
|
const userConnector = resolve2(voluteHome(), "connectors", type, "index.ts");
|
|
226
225
|
const builtinConnector = this.resolveBuiltinConnector(type);
|
|
@@ -238,10 +237,10 @@ var ConnectorManager = class {
|
|
|
238
237
|
} else {
|
|
239
238
|
throw new Error(`No connector code found for type: ${type}`);
|
|
240
239
|
}
|
|
241
|
-
const logsDir = resolve2(
|
|
240
|
+
const logsDir = resolve2(stateDir(agentName), "logs");
|
|
242
241
|
mkdirSync(logsDir, { recursive: true });
|
|
243
242
|
const logStream = new RotatingLog(resolve2(logsDir, `${type}.log`));
|
|
244
|
-
const agentEnv = loadMergedEnv(
|
|
243
|
+
const agentEnv = loadMergedEnv(agentName);
|
|
245
244
|
const prefix = `${type.toUpperCase()}_`;
|
|
246
245
|
const connectorEnv = Object.fromEntries(
|
|
247
246
|
Object.entries(agentEnv).filter(([k]) => k.startsWith(prefix))
|
|
@@ -269,7 +268,7 @@ var ConnectorManager = class {
|
|
|
269
268
|
lastStderr = chunk.toString().trim();
|
|
270
269
|
});
|
|
271
270
|
if (child.pid) {
|
|
272
|
-
this.saveConnectorPid(
|
|
271
|
+
this.saveConnectorPid(agentName, type, child.pid);
|
|
273
272
|
}
|
|
274
273
|
if (!this.connectors.has(agentName)) {
|
|
275
274
|
this.connectors.set(agentName, /* @__PURE__ */ new Map());
|
|
@@ -315,26 +314,27 @@ var ConnectorManager = class {
|
|
|
315
314
|
const stopKey = `${agentName}:${type}`;
|
|
316
315
|
this.stopping.add(stopKey);
|
|
317
316
|
agentMap.delete(type);
|
|
318
|
-
await new Promise((
|
|
319
|
-
tracked.child.on("exit", () =>
|
|
317
|
+
await new Promise((resolve11) => {
|
|
318
|
+
tracked.child.on("exit", () => resolve11());
|
|
320
319
|
try {
|
|
321
320
|
tracked.child.kill("SIGTERM");
|
|
322
321
|
} catch {
|
|
323
|
-
|
|
322
|
+
resolve11();
|
|
324
323
|
}
|
|
325
324
|
setTimeout(() => {
|
|
326
325
|
try {
|
|
327
326
|
tracked.child.kill("SIGKILL");
|
|
328
327
|
} catch {
|
|
329
328
|
}
|
|
330
|
-
|
|
329
|
+
resolve11();
|
|
331
330
|
}, 5e3);
|
|
332
331
|
});
|
|
333
332
|
this.stopping.delete(stopKey);
|
|
334
333
|
this.restartAttempts.delete(stopKey);
|
|
335
334
|
try {
|
|
336
|
-
this.removeConnectorPid(
|
|
337
|
-
} catch {
|
|
335
|
+
this.removeConnectorPid(agentName, type);
|
|
336
|
+
} catch (err) {
|
|
337
|
+
console.error(`[daemon] failed to remove PID file for ${type}/${agentName}:`, err);
|
|
338
338
|
}
|
|
339
339
|
console.error(`[daemon] stopped connector ${type} for ${agentName}`);
|
|
340
340
|
}
|
|
@@ -358,22 +358,22 @@ var ConnectorManager = class {
|
|
|
358
358
|
running: !tracked.child.killed
|
|
359
359
|
}));
|
|
360
360
|
}
|
|
361
|
-
connectorPidPath(
|
|
362
|
-
return resolve2(
|
|
361
|
+
connectorPidPath(agentName, type) {
|
|
362
|
+
return resolve2(stateDir(agentName), "connectors", `${type}.pid`);
|
|
363
363
|
}
|
|
364
|
-
saveConnectorPid(
|
|
365
|
-
const pidPath = this.connectorPidPath(
|
|
364
|
+
saveConnectorPid(agentName, type, pid) {
|
|
365
|
+
const pidPath = this.connectorPidPath(agentName, type);
|
|
366
366
|
mkdirSync(dirname(pidPath), { recursive: true });
|
|
367
367
|
writeFileSync(pidPath, String(pid));
|
|
368
368
|
}
|
|
369
|
-
removeConnectorPid(
|
|
369
|
+
removeConnectorPid(agentName, type) {
|
|
370
370
|
try {
|
|
371
|
-
unlinkSync(this.connectorPidPath(
|
|
371
|
+
unlinkSync(this.connectorPidPath(agentName, type));
|
|
372
372
|
} catch {
|
|
373
373
|
}
|
|
374
374
|
}
|
|
375
|
-
killOrphanConnector(
|
|
376
|
-
const pidPath = this.connectorPidPath(
|
|
375
|
+
killOrphanConnector(agentName, type) {
|
|
376
|
+
const pidPath = this.connectorPidPath(agentName, type);
|
|
377
377
|
if (!existsSync2(pidPath)) return;
|
|
378
378
|
try {
|
|
379
379
|
const pid = parseInt(readFileSync2(pidPath, "utf-8").trim(), 10);
|
|
@@ -406,8 +406,37 @@ function getConnectorManager() {
|
|
|
406
406
|
return instance;
|
|
407
407
|
}
|
|
408
408
|
|
|
409
|
-
// src/lib/
|
|
409
|
+
// src/lib/migrate-state.ts
|
|
410
|
+
import { copyFileSync, existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync } from "fs";
|
|
410
411
|
import { resolve as resolve3 } from "path";
|
|
412
|
+
function migrateAgentState(name) {
|
|
413
|
+
const src = resolve3(agentDir(name), ".volute");
|
|
414
|
+
if (!existsSync3(src)) return;
|
|
415
|
+
const dest = stateDir(name);
|
|
416
|
+
mkdirSync2(dest, { recursive: true });
|
|
417
|
+
for (const file of ["env.json", "channels.json"]) {
|
|
418
|
+
const srcPath = resolve3(src, file);
|
|
419
|
+
const destPath = resolve3(dest, file);
|
|
420
|
+
if (existsSync3(srcPath) && !existsSync3(destPath)) {
|
|
421
|
+
copyFileSync(srcPath, destPath);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
const srcLogs = resolve3(src, "logs");
|
|
425
|
+
const destLogs = resolve3(dest, "logs");
|
|
426
|
+
if (existsSync3(srcLogs) && !existsSync3(destLogs)) {
|
|
427
|
+
mkdirSync2(destLogs, { recursive: true });
|
|
428
|
+
for (const file of readdirSync(srcLogs)) {
|
|
429
|
+
try {
|
|
430
|
+
copyFileSync(resolve3(srcLogs, file), resolve3(destLogs, file));
|
|
431
|
+
} catch (err) {
|
|
432
|
+
console.error(`[migrate] failed to copy log ${file} for ${name}:`, err);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// src/lib/scheduler.ts
|
|
439
|
+
import { resolve as resolve4 } from "path";
|
|
411
440
|
import { CronExpressionParser } from "cron-parser";
|
|
412
441
|
var Scheduler = class {
|
|
413
442
|
schedules = /* @__PURE__ */ new Map();
|
|
@@ -417,7 +446,7 @@ var Scheduler = class {
|
|
|
417
446
|
daemonPort = null;
|
|
418
447
|
daemonToken = null;
|
|
419
448
|
get statePath() {
|
|
420
|
-
return
|
|
449
|
+
return resolve4(voluteHome(), "scheduler-state.json");
|
|
421
450
|
}
|
|
422
451
|
start(daemonPort, daemonToken) {
|
|
423
452
|
this.daemonPort = daemonPort ?? null;
|
|
@@ -524,18 +553,8 @@ var Scheduler = class {
|
|
|
524
553
|
} else {
|
|
525
554
|
console.error(`[scheduler] fired "${schedule.id}" for ${agentName}`);
|
|
526
555
|
}
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
if (reader) {
|
|
530
|
-
try {
|
|
531
|
-
while (!(await reader.read()).done) {
|
|
532
|
-
}
|
|
533
|
-
} finally {
|
|
534
|
-
reader.releaseLock();
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
} catch {
|
|
538
|
-
}
|
|
556
|
+
await res.text().catch(() => {
|
|
557
|
+
});
|
|
539
558
|
} catch (err) {
|
|
540
559
|
console.error(`[scheduler] failed to fire "${schedule.id}" for ${agentName}:`, err);
|
|
541
560
|
} finally {
|
|
@@ -696,18 +715,8 @@ ${summary}`
|
|
|
696
715
|
`[token-budget] replayed ${messages2.length} queued message(s) for ${agentName}`
|
|
697
716
|
);
|
|
698
717
|
}
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
if (reader) {
|
|
702
|
-
try {
|
|
703
|
-
while (!(await reader.read()).done) {
|
|
704
|
-
}
|
|
705
|
-
} finally {
|
|
706
|
-
reader.releaseLock();
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
} catch {
|
|
710
|
-
}
|
|
718
|
+
await res.text().catch(() => {
|
|
719
|
+
});
|
|
711
720
|
} catch (err) {
|
|
712
721
|
console.error(`[token-budget] failed to replay for ${agentName}:`, err);
|
|
713
722
|
const state = this.budgets.get(agentName);
|
|
@@ -788,9 +797,9 @@ var authMiddleware = createMiddleware(async (c, next) => {
|
|
|
788
797
|
});
|
|
789
798
|
|
|
790
799
|
// src/web/server.ts
|
|
791
|
-
import { existsSync as
|
|
800
|
+
import { existsSync as existsSync7 } from "fs";
|
|
792
801
|
import { readFile as readFile2, stat } from "fs/promises";
|
|
793
|
-
import { dirname as dirname2, extname, resolve as
|
|
802
|
+
import { dirname as dirname2, extname, resolve as resolve9 } from "path";
|
|
794
803
|
import { serve } from "@hono/node-server";
|
|
795
804
|
|
|
796
805
|
// src/lib/log-buffer.ts
|
|
@@ -844,50 +853,12 @@ import { csrf } from "hono/csrf";
|
|
|
844
853
|
import { HTTPException } from "hono/http-exception";
|
|
845
854
|
|
|
846
855
|
// src/web/routes/agents.ts
|
|
847
|
-
import {
|
|
848
|
-
import {
|
|
856
|
+
import { execFile } from "child_process";
|
|
857
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, rmSync } from "fs";
|
|
858
|
+
import { resolve as resolve5 } from "path";
|
|
859
|
+
import { promisify } from "util";
|
|
849
860
|
import { and, desc, eq as eq2 } from "drizzle-orm";
|
|
850
861
|
import { Hono } from "hono";
|
|
851
|
-
import { stream } from "hono/streaming";
|
|
852
|
-
|
|
853
|
-
// src/lib/ndjson.ts
|
|
854
|
-
var MAX_BUFFER_SIZE = 1e6;
|
|
855
|
-
async function* readNdjson(body) {
|
|
856
|
-
const reader = body.getReader();
|
|
857
|
-
const decoder = new TextDecoder();
|
|
858
|
-
let buffer = "";
|
|
859
|
-
try {
|
|
860
|
-
while (true) {
|
|
861
|
-
const { done, value } = await reader.read();
|
|
862
|
-
if (done) break;
|
|
863
|
-
buffer += decoder.decode(value, { stream: true });
|
|
864
|
-
if (buffer.length > MAX_BUFFER_SIZE) {
|
|
865
|
-
logger_default.warn("ndjson: buffer exceeded 1MB, resetting");
|
|
866
|
-
buffer = "";
|
|
867
|
-
continue;
|
|
868
|
-
}
|
|
869
|
-
const lines = buffer.split("\n");
|
|
870
|
-
buffer = lines.pop() || "";
|
|
871
|
-
for (const line of lines) {
|
|
872
|
-
if (!line.trim()) continue;
|
|
873
|
-
try {
|
|
874
|
-
yield JSON.parse(line);
|
|
875
|
-
} catch {
|
|
876
|
-
logger_default.warn("ndjson: skipping invalid line", { line: line.slice(0, 100) });
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
}
|
|
880
|
-
if (buffer.trim()) {
|
|
881
|
-
try {
|
|
882
|
-
yield JSON.parse(buffer);
|
|
883
|
-
} catch {
|
|
884
|
-
logger_default.warn("ndjson: skipping invalid line", { line: buffer.slice(0, 100) });
|
|
885
|
-
}
|
|
886
|
-
}
|
|
887
|
-
} finally {
|
|
888
|
-
reader.releaseLock();
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
862
|
|
|
892
863
|
// src/lib/typing.ts
|
|
893
864
|
var DEFAULT_TTL_MS = 1e4;
|
|
@@ -957,11 +928,38 @@ function getTypingMap() {
|
|
|
957
928
|
}
|
|
958
929
|
|
|
959
930
|
// src/web/routes/agents.ts
|
|
931
|
+
var execFileAsync = promisify(execFile);
|
|
932
|
+
async function startAgentFull(name, baseName, variantName) {
|
|
933
|
+
await getAgentManager().startAgent(name);
|
|
934
|
+
if (variantName) return;
|
|
935
|
+
const dir = agentDir(baseName);
|
|
936
|
+
const entry = findAgent(baseName);
|
|
937
|
+
await getConnectorManager().startConnectors(baseName, dir, entry.port, getDaemonPort());
|
|
938
|
+
getScheduler().loadSchedules(baseName);
|
|
939
|
+
const config = readVoluteConfig(dir);
|
|
940
|
+
if (config?.tokenBudget) {
|
|
941
|
+
getTokenBudget().setBudget(
|
|
942
|
+
baseName,
|
|
943
|
+
config.tokenBudget,
|
|
944
|
+
config.tokenBudgetPeriodMinutes ?? DEFAULT_BUDGET_PERIOD_MINUTES
|
|
945
|
+
);
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
function extractTextContent(content) {
|
|
949
|
+
if (typeof content === "string") return content;
|
|
950
|
+
if (Array.isArray(content)) {
|
|
951
|
+
return content.filter((p) => p.type === "text" && p.text).map((p) => p.text).join("\n");
|
|
952
|
+
}
|
|
953
|
+
return JSON.stringify(content);
|
|
954
|
+
}
|
|
960
955
|
function getDaemonPort() {
|
|
961
956
|
try {
|
|
962
|
-
const data = JSON.parse(readFileSync3(
|
|
957
|
+
const data = JSON.parse(readFileSync3(resolve5(voluteHome(), "daemon.json"), "utf-8"));
|
|
963
958
|
return data.port;
|
|
964
|
-
} catch {
|
|
959
|
+
} catch (err) {
|
|
960
|
+
if (err?.code !== "ENOENT") {
|
|
961
|
+
console.error("[daemon] failed to read daemon.json:", err);
|
|
962
|
+
}
|
|
965
963
|
return void 0;
|
|
966
964
|
}
|
|
967
965
|
}
|
|
@@ -1008,7 +1006,7 @@ var app = new Hono().get("/", async (c) => {
|
|
|
1008
1006
|
const name = c.req.param("name");
|
|
1009
1007
|
const entry = findAgent(name);
|
|
1010
1008
|
if (!entry) return c.json({ error: "Agent not found" }, 404);
|
|
1011
|
-
if (!
|
|
1009
|
+
if (!existsSync4(agentDir(name))) return c.json({ error: "Agent directory missing" }, 404);
|
|
1012
1010
|
const { status, channels } = await getAgentStatus(name, entry.port);
|
|
1013
1011
|
const variants = readVariants(name);
|
|
1014
1012
|
const manager = getAgentManager();
|
|
@@ -1034,27 +1032,13 @@ var app = new Hono().get("/", async (c) => {
|
|
|
1034
1032
|
if (!variant) return c.json({ error: `Unknown variant: ${variantName}` }, 404);
|
|
1035
1033
|
} else {
|
|
1036
1034
|
const dir = agentDir(baseName);
|
|
1037
|
-
if (!
|
|
1035
|
+
if (!existsSync4(dir)) return c.json({ error: "Agent directory missing" }, 404);
|
|
1038
1036
|
}
|
|
1039
|
-
|
|
1040
|
-
if (manager.isRunning(name)) {
|
|
1037
|
+
if (getAgentManager().isRunning(name)) {
|
|
1041
1038
|
return c.json({ error: "Agent already running" }, 409);
|
|
1042
1039
|
}
|
|
1043
1040
|
try {
|
|
1044
|
-
await
|
|
1045
|
-
if (!variantName) {
|
|
1046
|
-
const dir = agentDir(baseName);
|
|
1047
|
-
await getConnectorManager().startConnectors(baseName, dir, entry.port, getDaemonPort());
|
|
1048
|
-
getScheduler().loadSchedules(baseName);
|
|
1049
|
-
const config = readVoluteConfig(dir);
|
|
1050
|
-
if (config?.tokenBudget) {
|
|
1051
|
-
getTokenBudget().setBudget(
|
|
1052
|
-
baseName,
|
|
1053
|
-
config.tokenBudget,
|
|
1054
|
-
config.tokenBudgetPeriodMinutes ?? DEFAULT_BUDGET_PERIOD_MINUTES
|
|
1055
|
-
);
|
|
1056
|
-
}
|
|
1057
|
-
}
|
|
1041
|
+
await startAgentFull(name, baseName, variantName);
|
|
1058
1042
|
return c.json({ ok: true });
|
|
1059
1043
|
} catch (err) {
|
|
1060
1044
|
return c.json({ error: err instanceof Error ? err.message : "Failed to start agent" }, 500);
|
|
@@ -1069,32 +1053,54 @@ var app = new Hono().get("/", async (c) => {
|
|
|
1069
1053
|
if (!variant) return c.json({ error: `Unknown variant: ${variantName}` }, 404);
|
|
1070
1054
|
} else {
|
|
1071
1055
|
const dir = agentDir(baseName);
|
|
1072
|
-
if (!
|
|
1056
|
+
if (!existsSync4(dir)) return c.json({ error: "Agent directory missing" }, 404);
|
|
1057
|
+
}
|
|
1058
|
+
let context;
|
|
1059
|
+
const contentType = c.req.header("content-type");
|
|
1060
|
+
if (contentType?.includes("application/json")) {
|
|
1061
|
+
try {
|
|
1062
|
+
const body = await c.req.json();
|
|
1063
|
+
if (body?.context) context = body.context;
|
|
1064
|
+
} catch (err) {
|
|
1065
|
+
console.error(`[daemon] failed to parse restart context for ${name}:`, err);
|
|
1066
|
+
}
|
|
1073
1067
|
}
|
|
1074
1068
|
const manager = getAgentManager();
|
|
1075
|
-
const connectorManager = getConnectorManager();
|
|
1076
1069
|
try {
|
|
1077
1070
|
if (manager.isRunning(name)) {
|
|
1078
1071
|
if (!variantName) {
|
|
1079
|
-
await
|
|
1072
|
+
await getConnectorManager().stopConnectors(baseName);
|
|
1080
1073
|
getTokenBudget().removeBudget(baseName);
|
|
1081
1074
|
}
|
|
1082
1075
|
await manager.stopAgent(name);
|
|
1083
1076
|
}
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
const
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
const config = readVoluteConfig(dir);
|
|
1090
|
-
if (config?.tokenBudget) {
|
|
1091
|
-
getTokenBudget().setBudget(
|
|
1092
|
-
baseName,
|
|
1093
|
-
config.tokenBudget,
|
|
1094
|
-
config.tokenBudgetPeriodMinutes ?? DEFAULT_BUDGET_PERIOD_MINUTES
|
|
1095
|
-
);
|
|
1077
|
+
if (context?.type === "merge" && context.name && !variantName) {
|
|
1078
|
+
const mergeVariantName = String(context.name);
|
|
1079
|
+
const branchErr = validateBranchName(mergeVariantName);
|
|
1080
|
+
if (branchErr) {
|
|
1081
|
+
return c.json({ error: `Invalid variant name: ${branchErr}` }, 400);
|
|
1096
1082
|
}
|
|
1083
|
+
console.error(`[daemon] merging variant for ${baseName}: ${mergeVariantName}`);
|
|
1084
|
+
const mergeArgs = [
|
|
1085
|
+
"variant",
|
|
1086
|
+
"merge",
|
|
1087
|
+
mergeVariantName,
|
|
1088
|
+
"--agent",
|
|
1089
|
+
baseName,
|
|
1090
|
+
"--skip-verify"
|
|
1091
|
+
];
|
|
1092
|
+
if (context.summary) mergeArgs.push("--summary", String(context.summary));
|
|
1093
|
+
if (context.justification) mergeArgs.push("--justification", String(context.justification));
|
|
1094
|
+
if (context.memory) mergeArgs.push("--memory", String(context.memory));
|
|
1095
|
+
await execFileAsync("volute", mergeArgs, {
|
|
1096
|
+
cwd: agentDir(baseName),
|
|
1097
|
+
env: { ...process.env, VOLUTE_SUPERVISOR: "1" }
|
|
1098
|
+
});
|
|
1097
1099
|
}
|
|
1100
|
+
if (context) {
|
|
1101
|
+
manager.setPendingContext(name, context);
|
|
1102
|
+
}
|
|
1103
|
+
await startAgentFull(name, baseName, variantName);
|
|
1098
1104
|
return c.json({ ok: true });
|
|
1099
1105
|
} catch (err) {
|
|
1100
1106
|
return c.json({ error: err instanceof Error ? err.message : "Failed to restart agent" }, 500);
|
|
@@ -1138,7 +1144,11 @@ var app = new Hono().get("/", async (c) => {
|
|
|
1138
1144
|
removeAllVariants(name);
|
|
1139
1145
|
removeAgent(name);
|
|
1140
1146
|
await deleteAgentUser(name);
|
|
1141
|
-
|
|
1147
|
+
const state = stateDir(name);
|
|
1148
|
+
if (existsSync4(state)) {
|
|
1149
|
+
rmSync(state, { recursive: true, force: true });
|
|
1150
|
+
}
|
|
1151
|
+
if (force && existsSync4(dir)) {
|
|
1142
1152
|
rmSync(dir, { recursive: true, force: true });
|
|
1143
1153
|
}
|
|
1144
1154
|
return c.json({ ok: true });
|
|
@@ -1168,18 +1178,10 @@ var app = new Hono().get("/", async (c) => {
|
|
|
1168
1178
|
if (parsed) {
|
|
1169
1179
|
try {
|
|
1170
1180
|
const sender2 = parsed.sender ?? null;
|
|
1171
|
-
|
|
1172
|
-
if (typeof parsed.content === "string") {
|
|
1173
|
-
content = parsed.content;
|
|
1174
|
-
} else if (Array.isArray(parsed.content)) {
|
|
1175
|
-
content = parsed.content.filter((p) => p.type === "text" && p.text).map((p) => p.text).join("\n");
|
|
1176
|
-
} else {
|
|
1177
|
-
content = JSON.stringify(parsed.content);
|
|
1178
|
-
}
|
|
1181
|
+
const content = extractTextContent(parsed.content);
|
|
1179
1182
|
await db.insert(agentMessages).values({
|
|
1180
1183
|
agent: baseName,
|
|
1181
1184
|
channel,
|
|
1182
|
-
role: "user",
|
|
1183
1185
|
sender: sender2,
|
|
1184
1186
|
content
|
|
1185
1187
|
});
|
|
@@ -1190,31 +1192,13 @@ var app = new Hono().get("/", async (c) => {
|
|
|
1190
1192
|
const budget = getTokenBudget();
|
|
1191
1193
|
const budgetStatus = budget.checkBudget(baseName);
|
|
1192
1194
|
if (budgetStatus === "exceeded") {
|
|
1193
|
-
|
|
1194
|
-
if (parsed) {
|
|
1195
|
-
if (typeof parsed.content === "string") {
|
|
1196
|
-
textContent = parsed.content;
|
|
1197
|
-
} else if (Array.isArray(parsed.content)) {
|
|
1198
|
-
textContent = parsed.content.filter((p) => p.type === "text" && p.text).map((p) => p.text).join("\n");
|
|
1199
|
-
}
|
|
1200
|
-
}
|
|
1195
|
+
const textContent = parsed ? extractTextContent(parsed.content) : "";
|
|
1201
1196
|
budget.enqueue(baseName, {
|
|
1202
1197
|
channel,
|
|
1203
1198
|
sender: parsed?.sender ?? null,
|
|
1204
1199
|
textContent
|
|
1205
1200
|
});
|
|
1206
|
-
c.
|
|
1207
|
-
const encoder2 = new TextEncoder();
|
|
1208
|
-
return stream(c, async (s) => {
|
|
1209
|
-
await s.write(
|
|
1210
|
-
encoder2.encode(
|
|
1211
|
-
`${JSON.stringify({ type: "text", content: "[Token budget exceeded \u2014 message queued for next period]" })}
|
|
1212
|
-
`
|
|
1213
|
-
)
|
|
1214
|
-
);
|
|
1215
|
-
await s.write(encoder2.encode(`${JSON.stringify({ type: "done" })}
|
|
1216
|
-
`));
|
|
1217
|
-
});
|
|
1201
|
+
return c.json({ error: "Token budget exceeded \u2014 message queued for next period" }, 429);
|
|
1218
1202
|
}
|
|
1219
1203
|
const typingMap = getTypingMap();
|
|
1220
1204
|
const sender = parsed?.sender ?? "";
|
|
@@ -1238,63 +1222,38 @@ var app = new Hono().get("/", async (c) => {
|
|
|
1238
1222
|
budget.acknowledgeWarning(baseName);
|
|
1239
1223
|
forwardBody = JSON.stringify(parsed);
|
|
1240
1224
|
}
|
|
1241
|
-
|
|
1225
|
+
typingMap.set(channel, baseName, { persistent: true });
|
|
1226
|
+
const conversationId = parsed?.conversationId ?? null;
|
|
1227
|
+
if (conversationId) typingMap.set(`volute:${conversationId}`, baseName, { persistent: true });
|
|
1242
1228
|
try {
|
|
1243
|
-
res = await fetch(`http://127.0.0.1:${port}/message`, {
|
|
1229
|
+
const res = await fetch(`http://127.0.0.1:${port}/message`, {
|
|
1244
1230
|
method: "POST",
|
|
1245
1231
|
headers: { "Content-Type": "application/json" },
|
|
1246
1232
|
body: forwardBody
|
|
1247
1233
|
});
|
|
1234
|
+
if (!res.ok) {
|
|
1235
|
+
const text = await res.text().catch(() => "");
|
|
1236
|
+
console.error(`[daemon] agent ${name} responded with ${res.status}: ${text}`);
|
|
1237
|
+
return c.json({ error: `Agent responded with ${res.status}` }, res.status);
|
|
1238
|
+
}
|
|
1239
|
+
let result;
|
|
1240
|
+
try {
|
|
1241
|
+
result = await res.json();
|
|
1242
|
+
} catch (parseErr) {
|
|
1243
|
+
console.error(`[daemon] agent ${name} returned non-JSON response:`, parseErr);
|
|
1244
|
+
return c.json({ error: "Agent returned invalid response" }, 502);
|
|
1245
|
+
}
|
|
1246
|
+
if (result.usage) {
|
|
1247
|
+
budget.recordUsage(baseName, result.usage.input_tokens, result.usage.output_tokens);
|
|
1248
|
+
}
|
|
1249
|
+
return c.json({ ok: true });
|
|
1248
1250
|
} catch (err) {
|
|
1249
1251
|
console.error(`[daemon] agent ${name} unreachable on port ${port}:`, err);
|
|
1250
1252
|
return c.json({ error: "Agent is not reachable" }, 502);
|
|
1253
|
+
} finally {
|
|
1254
|
+
typingMap.delete(channel, baseName);
|
|
1255
|
+
if (conversationId) typingMap.delete(`volute:${conversationId}`, baseName);
|
|
1251
1256
|
}
|
|
1252
|
-
if (!res.ok) {
|
|
1253
|
-
return c.json({ error: `Agent responded with ${res.status}` }, res.status);
|
|
1254
|
-
}
|
|
1255
|
-
if (!res.body) {
|
|
1256
|
-
return c.json({ error: "No response body from agent" }, 502);
|
|
1257
|
-
}
|
|
1258
|
-
c.header("Content-Type", "application/x-ndjson");
|
|
1259
|
-
const encoder = new TextEncoder();
|
|
1260
|
-
typingMap.set(channel, baseName, { persistent: true });
|
|
1261
|
-
return stream(c, async (s) => {
|
|
1262
|
-
try {
|
|
1263
|
-
const textParts = [];
|
|
1264
|
-
const toolParts = [];
|
|
1265
|
-
for await (const event of readNdjson(res.body)) {
|
|
1266
|
-
if (event.type === "usage") {
|
|
1267
|
-
const input = typeof event.input_tokens === "number" ? event.input_tokens : 0;
|
|
1268
|
-
const output = typeof event.output_tokens === "number" ? event.output_tokens : 0;
|
|
1269
|
-
budget.recordUsage(baseName, input, output);
|
|
1270
|
-
continue;
|
|
1271
|
-
}
|
|
1272
|
-
await s.write(encoder.encode(`${JSON.stringify(event)}
|
|
1273
|
-
`));
|
|
1274
|
-
const part = collectPart(event);
|
|
1275
|
-
if (part != null) {
|
|
1276
|
-
if (event.type === "tool_use") toolParts.push(part);
|
|
1277
|
-
else textParts.push(part);
|
|
1278
|
-
}
|
|
1279
|
-
}
|
|
1280
|
-
const content = [textParts.join(""), ...toolParts].filter(Boolean).join("\n");
|
|
1281
|
-
if (content) {
|
|
1282
|
-
try {
|
|
1283
|
-
await db.insert(agentMessages).values({
|
|
1284
|
-
agent: baseName,
|
|
1285
|
-
channel,
|
|
1286
|
-
role: "assistant",
|
|
1287
|
-
sender: baseName,
|
|
1288
|
-
content
|
|
1289
|
-
});
|
|
1290
|
-
} catch (err) {
|
|
1291
|
-
console.error(`[daemon] failed to persist assistant response for ${baseName}:`, err);
|
|
1292
|
-
}
|
|
1293
|
-
}
|
|
1294
|
-
} finally {
|
|
1295
|
-
typingMap.delete(channel, baseName);
|
|
1296
|
-
}
|
|
1297
|
-
});
|
|
1298
1257
|
}).get("/:name/budget", async (c) => {
|
|
1299
1258
|
const name = c.req.param("name");
|
|
1300
1259
|
const [baseName] = name.split("@", 2);
|
|
@@ -1318,8 +1277,7 @@ var app = new Hono().get("/", async (c) => {
|
|
|
1318
1277
|
await db.insert(agentMessages).values({
|
|
1319
1278
|
agent: baseName,
|
|
1320
1279
|
channel: body.channel,
|
|
1321
|
-
|
|
1322
|
-
sender: baseName,
|
|
1280
|
+
sender: body.sender ?? baseName,
|
|
1323
1281
|
content: body.content
|
|
1324
1282
|
});
|
|
1325
1283
|
} catch (err) {
|
|
@@ -1445,7 +1403,7 @@ var app3 = new Hono3().get("/:name/connectors", (c) => {
|
|
|
1445
1403
|
if (!entry) return c.json({ error: "Agent not found" }, 404);
|
|
1446
1404
|
const dir = agentDir(name);
|
|
1447
1405
|
const manager = getConnectorManager();
|
|
1448
|
-
const envCheck = manager.checkConnectorEnv(type, dir);
|
|
1406
|
+
const envCheck = manager.checkConnectorEnv(type, name, dir);
|
|
1449
1407
|
if (envCheck) {
|
|
1450
1408
|
return c.json(
|
|
1451
1409
|
{
|
|
@@ -1490,21 +1448,18 @@ var app3 = new Hono3().get("/:name/connectors", (c) => {
|
|
|
1490
1448
|
var connectors_default = app3;
|
|
1491
1449
|
|
|
1492
1450
|
// src/web/routes/files.ts
|
|
1493
|
-
import { existsSync as
|
|
1494
|
-
import { readdir, readFile
|
|
1495
|
-
import { resolve as
|
|
1496
|
-
import { zValidator as zValidator2 } from "@hono/zod-validator";
|
|
1451
|
+
import { existsSync as existsSync5 } from "fs";
|
|
1452
|
+
import { readdir, readFile } from "fs/promises";
|
|
1453
|
+
import { resolve as resolve6 } from "path";
|
|
1497
1454
|
import { Hono as Hono4 } from "hono";
|
|
1498
|
-
import { z as z2 } from "zod";
|
|
1499
1455
|
var ALLOWED_FILES = /* @__PURE__ */ new Set(["SOUL.md", "MEMORY.md", "CLAUDE.md", "VOLUTE.md"]);
|
|
1500
|
-
var saveFileSchema = z2.object({ content: z2.string() });
|
|
1501
1456
|
var app4 = new Hono4().get("/:name/files", async (c) => {
|
|
1502
1457
|
const name = c.req.param("name");
|
|
1503
1458
|
const entry = findAgent(name);
|
|
1504
1459
|
if (!entry) return c.json({ error: "Agent not found" }, 404);
|
|
1505
1460
|
const dir = agentDir(name);
|
|
1506
|
-
const homeDir =
|
|
1507
|
-
if (!
|
|
1461
|
+
const homeDir = resolve6(dir, "home");
|
|
1462
|
+
if (!existsSync5(homeDir)) return c.json({ error: "Home directory missing" }, 404);
|
|
1508
1463
|
const allFiles = await readdir(homeDir);
|
|
1509
1464
|
const files = allFiles.filter((f) => f.endsWith(".md") && ALLOWED_FILES.has(f));
|
|
1510
1465
|
return c.json(files);
|
|
@@ -1517,61 +1472,47 @@ var app4 = new Hono4().get("/:name/files", async (c) => {
|
|
|
1517
1472
|
const entry = findAgent(name);
|
|
1518
1473
|
if (!entry) return c.json({ error: "Agent not found" }, 404);
|
|
1519
1474
|
const dir = agentDir(name);
|
|
1520
|
-
const filePath =
|
|
1521
|
-
if (!
|
|
1475
|
+
const filePath = resolve6(dir, "home", filename);
|
|
1476
|
+
if (!existsSync5(filePath)) {
|
|
1522
1477
|
return c.json({ error: "File not found" }, 404);
|
|
1523
1478
|
}
|
|
1524
1479
|
const content = await readFile(filePath, "utf-8");
|
|
1525
1480
|
return c.json({ filename, content });
|
|
1526
|
-
}).put("/:name/files/:filename", zValidator2("json", saveFileSchema), async (c) => {
|
|
1527
|
-
const name = c.req.param("name");
|
|
1528
|
-
const filename = c.req.param("filename");
|
|
1529
|
-
if (!ALLOWED_FILES.has(filename)) {
|
|
1530
|
-
return c.json({ error: "File not allowed" }, 403);
|
|
1531
|
-
}
|
|
1532
|
-
const entry = findAgent(name);
|
|
1533
|
-
if (!entry) return c.json({ error: "Agent not found" }, 404);
|
|
1534
|
-
const dir = agentDir(name);
|
|
1535
|
-
const filePath = resolve5(dir, "home", filename);
|
|
1536
|
-
const { content } = c.req.valid("json");
|
|
1537
|
-
await writeFile(filePath, content);
|
|
1538
|
-
return c.json({ ok: true });
|
|
1539
1481
|
});
|
|
1540
1482
|
var files_default = app4;
|
|
1541
1483
|
|
|
1542
1484
|
// src/web/routes/logs.ts
|
|
1543
1485
|
import { spawn as spawn2 } from "child_process";
|
|
1544
|
-
import { existsSync as
|
|
1545
|
-
import { resolve as
|
|
1486
|
+
import { existsSync as existsSync6 } from "fs";
|
|
1487
|
+
import { resolve as resolve7 } from "path";
|
|
1546
1488
|
import { Hono as Hono5 } from "hono";
|
|
1547
1489
|
import { streamSSE } from "hono/streaming";
|
|
1548
1490
|
var app5 = new Hono5().get("/:name/logs", async (c) => {
|
|
1549
1491
|
const name = c.req.param("name");
|
|
1550
1492
|
const entry = findAgent(name);
|
|
1551
1493
|
if (!entry) return c.json({ error: "Agent not found" }, 404);
|
|
1552
|
-
const
|
|
1553
|
-
|
|
1554
|
-
if (!existsSync5(logFile)) {
|
|
1494
|
+
const logFile = resolve7(stateDir(name), "logs", "agent.log");
|
|
1495
|
+
if (!existsSync6(logFile)) {
|
|
1555
1496
|
return c.json({ error: "No log file found" }, 404);
|
|
1556
1497
|
}
|
|
1557
|
-
return streamSSE(c, async (
|
|
1498
|
+
return streamSSE(c, async (stream) => {
|
|
1558
1499
|
const tail = spawn2("tail", ["-n", "200", "-f", logFile]);
|
|
1559
1500
|
const onData = (data) => {
|
|
1560
1501
|
const lines = data.toString().split("\n");
|
|
1561
1502
|
for (const line of lines) {
|
|
1562
1503
|
if (line) {
|
|
1563
|
-
|
|
1504
|
+
stream.writeSSE({ data: line }).catch(() => {
|
|
1564
1505
|
});
|
|
1565
1506
|
}
|
|
1566
1507
|
}
|
|
1567
1508
|
};
|
|
1568
1509
|
tail.stdout.on("data", onData);
|
|
1569
|
-
|
|
1510
|
+
stream.onAbort(() => {
|
|
1570
1511
|
tail.kill();
|
|
1571
1512
|
});
|
|
1572
|
-
await new Promise((
|
|
1573
|
-
tail.on("exit",
|
|
1574
|
-
|
|
1513
|
+
await new Promise((resolve11) => {
|
|
1514
|
+
tail.on("exit", resolve11);
|
|
1515
|
+
stream.onAbort(resolve11);
|
|
1575
1516
|
});
|
|
1576
1517
|
});
|
|
1577
1518
|
});
|
|
@@ -1665,18 +1606,18 @@ import { streamSSE as streamSSE2 } from "hono/streaming";
|
|
|
1665
1606
|
var app7 = new Hono7().get("/logs", async (c) => {
|
|
1666
1607
|
const user = c.get("user");
|
|
1667
1608
|
if (user.role !== "admin") return c.json({ error: "Forbidden" }, 403);
|
|
1668
|
-
return streamSSE2(c, async (
|
|
1609
|
+
return streamSSE2(c, async (stream) => {
|
|
1669
1610
|
for (const entry of logBuffer.getEntries()) {
|
|
1670
|
-
await
|
|
1611
|
+
await stream.writeSSE({ data: JSON.stringify(entry) });
|
|
1671
1612
|
}
|
|
1672
1613
|
const unsubscribe = logBuffer.subscribe((entry) => {
|
|
1673
|
-
|
|
1614
|
+
stream.writeSSE({ data: JSON.stringify(entry) }).catch(() => {
|
|
1674
1615
|
});
|
|
1675
1616
|
});
|
|
1676
|
-
await new Promise((
|
|
1677
|
-
|
|
1617
|
+
await new Promise((resolve11) => {
|
|
1618
|
+
stream.onAbort(() => {
|
|
1678
1619
|
unsubscribe();
|
|
1679
|
-
|
|
1620
|
+
resolve11();
|
|
1680
1621
|
});
|
|
1681
1622
|
});
|
|
1682
1623
|
});
|
|
@@ -1684,15 +1625,15 @@ var app7 = new Hono7().get("/logs", async (c) => {
|
|
|
1684
1625
|
var system_default = app7;
|
|
1685
1626
|
|
|
1686
1627
|
// src/web/routes/typing.ts
|
|
1687
|
-
import { zValidator as
|
|
1628
|
+
import { zValidator as zValidator2 } from "@hono/zod-validator";
|
|
1688
1629
|
import { Hono as Hono8 } from "hono";
|
|
1689
|
-
import { z as
|
|
1690
|
-
var typingSchema =
|
|
1691
|
-
channel:
|
|
1692
|
-
sender:
|
|
1693
|
-
active:
|
|
1630
|
+
import { z as z2 } from "zod";
|
|
1631
|
+
var typingSchema = z2.object({
|
|
1632
|
+
channel: z2.string().min(1),
|
|
1633
|
+
sender: z2.string().min(1),
|
|
1634
|
+
active: z2.boolean()
|
|
1694
1635
|
});
|
|
1695
|
-
var app8 = new Hono8().post("/:name/typing",
|
|
1636
|
+
var app8 = new Hono8().post("/:name/typing", zValidator2("json", typingSchema), (c) => {
|
|
1696
1637
|
const { channel, sender, active } = c.req.valid("json");
|
|
1697
1638
|
const map = getTypingMap();
|
|
1698
1639
|
if (active) {
|
|
@@ -1752,11 +1693,39 @@ var variants_default = app10;
|
|
|
1752
1693
|
|
|
1753
1694
|
// src/web/routes/volute/chat.ts
|
|
1754
1695
|
import { readFileSync as readFileSync4 } from "fs";
|
|
1755
|
-
import { resolve as
|
|
1756
|
-
import { zValidator as
|
|
1696
|
+
import { resolve as resolve8 } from "path";
|
|
1697
|
+
import { zValidator as zValidator3 } from "@hono/zod-validator";
|
|
1757
1698
|
import { Hono as Hono11 } from "hono";
|
|
1758
1699
|
import { streamSSE as streamSSE3 } from "hono/streaming";
|
|
1759
|
-
import { z as
|
|
1700
|
+
import { z as z3 } from "zod";
|
|
1701
|
+
|
|
1702
|
+
// src/lib/conversation-events.ts
|
|
1703
|
+
var subscribers = /* @__PURE__ */ new Map();
|
|
1704
|
+
function subscribe(conversationId, callback) {
|
|
1705
|
+
let set = subscribers.get(conversationId);
|
|
1706
|
+
if (!set) {
|
|
1707
|
+
set = /* @__PURE__ */ new Set();
|
|
1708
|
+
subscribers.set(conversationId, set);
|
|
1709
|
+
}
|
|
1710
|
+
set.add(callback);
|
|
1711
|
+
return () => {
|
|
1712
|
+
set.delete(callback);
|
|
1713
|
+
if (set.size === 0) subscribers.delete(conversationId);
|
|
1714
|
+
};
|
|
1715
|
+
}
|
|
1716
|
+
function publish(conversationId, event) {
|
|
1717
|
+
const set = subscribers.get(conversationId);
|
|
1718
|
+
if (!set) return;
|
|
1719
|
+
for (const cb of set) {
|
|
1720
|
+
try {
|
|
1721
|
+
cb(event);
|
|
1722
|
+
} catch (err) {
|
|
1723
|
+
console.error("[conversation-events] subscriber threw:", err);
|
|
1724
|
+
set.delete(cb);
|
|
1725
|
+
if (set.size === 0) subscribers.delete(conversationId);
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1760
1729
|
|
|
1761
1730
|
// src/lib/conversations.ts
|
|
1762
1731
|
import { randomUUID } from "crypto";
|
|
@@ -1845,7 +1814,7 @@ async function addMessage(conversationId, role, senderName, content) {
|
|
|
1845
1814
|
await db.update(conversations).set({ title }).where(and2(eq3(conversations.id, conversationId), isNull(conversations.title)));
|
|
1846
1815
|
}
|
|
1847
1816
|
}
|
|
1848
|
-
|
|
1817
|
+
const msg = {
|
|
1849
1818
|
id: result.id,
|
|
1850
1819
|
conversation_id: conversationId,
|
|
1851
1820
|
role,
|
|
@@ -1853,6 +1822,15 @@ async function addMessage(conversationId, role, senderName, content) {
|
|
|
1853
1822
|
content,
|
|
1854
1823
|
created_at: result.created_at
|
|
1855
1824
|
};
|
|
1825
|
+
publish(conversationId, {
|
|
1826
|
+
type: "message",
|
|
1827
|
+
id: msg.id,
|
|
1828
|
+
role: msg.role,
|
|
1829
|
+
senderName: msg.sender_name,
|
|
1830
|
+
content: msg.content,
|
|
1831
|
+
createdAt: msg.created_at
|
|
1832
|
+
});
|
|
1833
|
+
return msg;
|
|
1856
1834
|
}
|
|
1857
1835
|
async function getMessages(conversationId) {
|
|
1858
1836
|
const db = await getDb();
|
|
@@ -1915,20 +1893,24 @@ async function deleteConversation(id) {
|
|
|
1915
1893
|
}
|
|
1916
1894
|
|
|
1917
1895
|
// src/web/routes/volute/chat.ts
|
|
1918
|
-
var chatSchema =
|
|
1919
|
-
message:
|
|
1920
|
-
conversationId:
|
|
1921
|
-
sender:
|
|
1922
|
-
images:
|
|
1923
|
-
|
|
1924
|
-
media_type:
|
|
1925
|
-
data:
|
|
1896
|
+
var chatSchema = z3.object({
|
|
1897
|
+
message: z3.string().optional(),
|
|
1898
|
+
conversationId: z3.string().optional(),
|
|
1899
|
+
sender: z3.string().optional(),
|
|
1900
|
+
images: z3.array(
|
|
1901
|
+
z3.object({
|
|
1902
|
+
media_type: z3.string(),
|
|
1903
|
+
data: z3.string()
|
|
1926
1904
|
})
|
|
1927
1905
|
).optional()
|
|
1928
1906
|
});
|
|
1929
1907
|
function getDaemonUrl() {
|
|
1930
|
-
|
|
1931
|
-
|
|
1908
|
+
try {
|
|
1909
|
+
const data = JSON.parse(readFileSync4(resolve8(voluteHome(), "daemon.json"), "utf-8"));
|
|
1910
|
+
return `http://${daemonLoopback()}:${data.port}`;
|
|
1911
|
+
} catch (err) {
|
|
1912
|
+
throw new Error(`Failed to read daemon config: ${err instanceof Error ? err.message : err}`);
|
|
1913
|
+
}
|
|
1932
1914
|
}
|
|
1933
1915
|
function daemonFetchInternal(path, body) {
|
|
1934
1916
|
const daemonUrl = getDaemonUrl();
|
|
@@ -1940,40 +1922,7 @@ function daemonFetchInternal(path, body) {
|
|
|
1940
1922
|
if (token) headers.Authorization = `Bearer ${token}`;
|
|
1941
1923
|
return fetch(`${daemonUrl}${path}`, { method: "POST", headers, body });
|
|
1942
1924
|
}
|
|
1943
|
-
|
|
1944
|
-
if (event.type === "text") {
|
|
1945
|
-
const last = content[content.length - 1];
|
|
1946
|
-
if (last && last.type === "text") last.text += event.content;
|
|
1947
|
-
else content.push({ type: "text", text: event.content });
|
|
1948
|
-
} else if (event.type === "tool_use") {
|
|
1949
|
-
content.push({ type: "tool_use", name: event.name, input: event.input });
|
|
1950
|
-
} else if (event.type === "tool_result") {
|
|
1951
|
-
content.push({
|
|
1952
|
-
type: "tool_result",
|
|
1953
|
-
output: event.output,
|
|
1954
|
-
...event.is_error ? { is_error: true } : {}
|
|
1955
|
-
});
|
|
1956
|
-
}
|
|
1957
|
-
}
|
|
1958
|
-
async function consumeAndPersist(res, conversationId, agentName) {
|
|
1959
|
-
if (!res.body) {
|
|
1960
|
-
console.warn(`[chat] no response body from ${agentName}`);
|
|
1961
|
-
return [];
|
|
1962
|
-
}
|
|
1963
|
-
const assistantContent = [];
|
|
1964
|
-
for await (const event of readNdjson(res.body)) {
|
|
1965
|
-
accumulateEvent(assistantContent, event);
|
|
1966
|
-
if (event.type === "done") break;
|
|
1967
|
-
}
|
|
1968
|
-
if (assistantContent.length === 0) return [];
|
|
1969
|
-
try {
|
|
1970
|
-
await addMessage(conversationId, "assistant", agentName, assistantContent);
|
|
1971
|
-
} catch (err) {
|
|
1972
|
-
console.error(`[chat] failed to persist conversation message from ${agentName}:`, err);
|
|
1973
|
-
}
|
|
1974
|
-
return assistantContent;
|
|
1975
|
-
}
|
|
1976
|
-
var app11 = new Hono11().post("/:name/chat", zValidator4("json", chatSchema), async (c) => {
|
|
1925
|
+
var app11 = new Hono11().post("/:name/chat", zValidator3("json", chatSchema), async (c) => {
|
|
1977
1926
|
const name = c.req.param("name");
|
|
1978
1927
|
const [baseName] = name.split("@", 2);
|
|
1979
1928
|
const entry = findAgent(baseName);
|
|
@@ -1991,7 +1940,6 @@ var app11 = new Hono11().post("/:name/chat", zValidator4("json", chatSchema), as
|
|
|
1991
1940
|
return c.json({ error: "Conversation not found" }, 404);
|
|
1992
1941
|
}
|
|
1993
1942
|
} else {
|
|
1994
|
-
const title = body.message ? body.message.slice(0, 80) : "Image message";
|
|
1995
1943
|
const participantIds = [];
|
|
1996
1944
|
if (user.id !== 0) {
|
|
1997
1945
|
participantIds.push(user.id);
|
|
@@ -2010,6 +1958,8 @@ var app11 = new Hono11().post("/:name/chat", zValidator4("json", chatSchema), as
|
|
|
2010
1958
|
}
|
|
2011
1959
|
}
|
|
2012
1960
|
if (!conversationId) {
|
|
1961
|
+
const participantNames2 = /* @__PURE__ */ new Set([senderName, baseName]);
|
|
1962
|
+
const title = [...participantNames2].join(", ");
|
|
2013
1963
|
const conv2 = await createConversation(baseName, "volute", {
|
|
2014
1964
|
userId: user.id !== 0 ? user.id : void 0,
|
|
2015
1965
|
title,
|
|
@@ -2034,108 +1984,86 @@ var app11 = new Hono11().post("/:name/chat", zValidator4("json", chatSchema), as
|
|
|
2034
1984
|
const participants = await getParticipants(conversationId);
|
|
2035
1985
|
const agentParticipants = participants.filter((p) => p.userType === "agent");
|
|
2036
1986
|
const participantNames = participants.map((p) => p.username);
|
|
2037
|
-
const { getAgentManager: getAgentManager2 } = await import("./agent-manager-
|
|
1987
|
+
const { getAgentManager: getAgentManager2 } = await import("./agent-manager-CMMH5KQQ.js");
|
|
2038
1988
|
const manager = getAgentManager2();
|
|
2039
1989
|
const runningAgents = agentParticipants.map((ap) => {
|
|
2040
1990
|
const agentKey = ap.username === baseName ? name : ap.username;
|
|
2041
1991
|
return manager.isRunning(agentKey) ? ap.username : null;
|
|
2042
1992
|
}).filter((n) => n !== null && n !== senderName);
|
|
2043
1993
|
const isDM = participants.length === 2;
|
|
2044
|
-
const
|
|
2045
|
-
writeChannelEntry(dir, channel, {
|
|
1994
|
+
const channelEntry = {
|
|
2046
1995
|
platformId: conversationId,
|
|
2047
1996
|
platform: "volute",
|
|
2048
1997
|
name: convTitle ?? void 0,
|
|
2049
1998
|
type: isDM ? "dm" : "group"
|
|
2050
|
-
}
|
|
1999
|
+
};
|
|
2000
|
+
for (const ap of agentParticipants) {
|
|
2001
|
+
try {
|
|
2002
|
+
writeChannelEntry(ap.username, channel, channelEntry);
|
|
2003
|
+
} catch (err) {
|
|
2004
|
+
console.warn(`[chat] failed to write channel entry for ${ap.username}:`, err);
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2051
2007
|
const typingMap = getTypingMap();
|
|
2052
2008
|
const currentlyTyping = typingMap.get(channel);
|
|
2053
2009
|
const payload = JSON.stringify({
|
|
2054
2010
|
content: contentBlocks,
|
|
2055
2011
|
channel,
|
|
2012
|
+
conversationId,
|
|
2056
2013
|
sender: senderName,
|
|
2057
2014
|
participants: participantNames,
|
|
2058
2015
|
participantCount: participants.length,
|
|
2059
2016
|
isDM,
|
|
2060
2017
|
...currentlyTyping.length > 0 ? { typing: currentlyTyping } : {}
|
|
2061
2018
|
});
|
|
2062
|
-
const responses = [];
|
|
2063
2019
|
for (const agentName of runningAgents) {
|
|
2064
2020
|
const targetName = agentName === baseName ? name : agentName;
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
);
|
|
2070
|
-
if (res.ok && res.body) {
|
|
2071
|
-
responses.push({ name: agentName, res });
|
|
2072
|
-
} else {
|
|
2073
|
-
const errorBody = await res.text().catch(() => "");
|
|
2074
|
-
console.error(
|
|
2075
|
-
`[chat] agent ${agentName} responded with ${res.status}: ${errorBody.slice(0, 500)}`
|
|
2076
|
-
);
|
|
2021
|
+
daemonFetchInternal(`/api/agents/${encodeURIComponent(targetName)}/message`, payload).then(async (res) => {
|
|
2022
|
+
if (!res.ok) {
|
|
2023
|
+
const text = await res.text().catch(() => "");
|
|
2024
|
+
console.error(`[chat] agent ${agentName} responded ${res.status}: ${text}`);
|
|
2077
2025
|
}
|
|
2078
|
-
}
|
|
2026
|
+
}).catch((err) => {
|
|
2079
2027
|
console.error(`[chat] agent ${agentName} unreachable via daemon:`, err);
|
|
2080
|
-
}
|
|
2081
|
-
}
|
|
2082
|
-
if (responses.length === 0) {
|
|
2083
|
-
return streamSSE3(c, async (stream2) => {
|
|
2084
|
-
await stream2.writeSSE({
|
|
2085
|
-
data: JSON.stringify({ type: "meta", conversationId })
|
|
2086
|
-
});
|
|
2087
|
-
await stream2.writeSSE({ data: JSON.stringify({ type: "sync" }) });
|
|
2088
2028
|
});
|
|
2089
2029
|
}
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
const
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2030
|
+
return c.json({ ok: true, conversationId });
|
|
2031
|
+
}).get("/:name/conversations/:id/events", async (c) => {
|
|
2032
|
+
const conversationId = c.req.param("id");
|
|
2033
|
+
const user = c.get("user");
|
|
2034
|
+
if (user.id !== 0 && !await isParticipantOrOwner(conversationId, user.id)) {
|
|
2035
|
+
return c.json({ error: "Conversation not found" }, 404);
|
|
2036
|
+
}
|
|
2037
|
+
return streamSSE3(c, async (stream) => {
|
|
2038
|
+
const unsubscribe = subscribe(conversationId, (event) => {
|
|
2039
|
+
stream.writeSSE({ data: JSON.stringify(event) }).catch((err) => {
|
|
2040
|
+
if (!stream.aborted) console.error("[chat] SSE write error:", err);
|
|
2041
|
+
});
|
|
2096
2042
|
});
|
|
2097
|
-
const
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
await stream2.writeSSE({ data: JSON.stringify(event) });
|
|
2101
|
-
accumulateEvent(assistantContent, event);
|
|
2102
|
-
if (event.type === "done") break;
|
|
2103
|
-
}
|
|
2104
|
-
} catch (err) {
|
|
2105
|
-
console.error(`[chat] error streaming response from ${primary.name}:`, err);
|
|
2106
|
-
await stream2.writeSSE({
|
|
2107
|
-
data: JSON.stringify({ type: "error", message: "Stream interrupted" })
|
|
2043
|
+
const keepAlive = setInterval(() => {
|
|
2044
|
+
stream.writeSSE({ data: "" }).catch((err) => {
|
|
2045
|
+
if (!stream.aborted) console.error("[chat] SSE ping error:", err);
|
|
2108
2046
|
});
|
|
2109
|
-
}
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
}
|
|
2116
|
-
}
|
|
2117
|
-
const results = await Promise.allSettled(secondaryPromises);
|
|
2118
|
-
for (let i = 0; i < results.length; i++) {
|
|
2119
|
-
if (results[i].status === "rejected") {
|
|
2120
|
-
console.error(
|
|
2121
|
-
`[chat] secondary agent ${secondary[i].name} response failed:`,
|
|
2122
|
-
results[i].reason
|
|
2123
|
-
);
|
|
2124
|
-
}
|
|
2125
|
-
}
|
|
2126
|
-
await stream2.writeSSE({ data: JSON.stringify({ type: "sync" }) });
|
|
2047
|
+
}, 15e3);
|
|
2048
|
+
await new Promise((resolve11) => {
|
|
2049
|
+
stream.onAbort(() => {
|
|
2050
|
+
unsubscribe();
|
|
2051
|
+
clearInterval(keepAlive);
|
|
2052
|
+
resolve11();
|
|
2053
|
+
});
|
|
2054
|
+
});
|
|
2127
2055
|
});
|
|
2128
2056
|
});
|
|
2129
2057
|
var chat_default = app11;
|
|
2130
2058
|
|
|
2131
2059
|
// src/web/routes/volute/conversations.ts
|
|
2132
|
-
import { zValidator as
|
|
2060
|
+
import { zValidator as zValidator4 } from "@hono/zod-validator";
|
|
2133
2061
|
import { Hono as Hono12 } from "hono";
|
|
2134
|
-
import { z as
|
|
2135
|
-
var createConvSchema =
|
|
2136
|
-
title:
|
|
2137
|
-
participantIds:
|
|
2138
|
-
participantNames:
|
|
2062
|
+
import { z as z4 } from "zod";
|
|
2063
|
+
var createConvSchema = z4.object({
|
|
2064
|
+
title: z4.string().optional(),
|
|
2065
|
+
participantIds: z4.array(z4.number()).optional(),
|
|
2066
|
+
participantNames: z4.array(z4.string()).optional()
|
|
2139
2067
|
});
|
|
2140
2068
|
var app12 = new Hono12().get("/:name/conversations", async (c) => {
|
|
2141
2069
|
const name = c.req.param("name");
|
|
@@ -2148,7 +2076,7 @@ var app12 = new Hono12().get("/:name/conversations", async (c) => {
|
|
|
2148
2076
|
const all = await listConversationsForUser(lookupId);
|
|
2149
2077
|
const convs = all.filter((c2) => c2.agent_name === name);
|
|
2150
2078
|
return c.json(convs);
|
|
2151
|
-
}).post("/:name/conversations",
|
|
2079
|
+
}).post("/:name/conversations", zValidator4("json", createConvSchema), async (c) => {
|
|
2152
2080
|
const name = c.req.param("name");
|
|
2153
2081
|
const user = c.get("user");
|
|
2154
2082
|
const body = c.req.valid("json");
|
|
@@ -2189,9 +2117,13 @@ var app12 = new Hono12().get("/:name/conversations", async (c) => {
|
|
|
2189
2117
|
console.warn(`[conversations] DM conversation ${existingId} found but not retrievable`);
|
|
2190
2118
|
}
|
|
2191
2119
|
}
|
|
2120
|
+
let title = body.title;
|
|
2121
|
+
if (!title && body.participantNames?.length) {
|
|
2122
|
+
title = body.participantNames.join(", ");
|
|
2123
|
+
}
|
|
2192
2124
|
const conv = await createConversation(name, "volute", {
|
|
2193
2125
|
userId: user.id !== 0 ? user.id : void 0,
|
|
2194
|
-
title
|
|
2126
|
+
title,
|
|
2195
2127
|
participantIds
|
|
2196
2128
|
});
|
|
2197
2129
|
return c.json(conv, 201);
|
|
@@ -2221,12 +2153,12 @@ var app12 = new Hono12().get("/:name/conversations", async (c) => {
|
|
|
2221
2153
|
var conversations_default = app12;
|
|
2222
2154
|
|
|
2223
2155
|
// src/web/routes/volute/user-conversations.ts
|
|
2224
|
-
import { zValidator as
|
|
2156
|
+
import { zValidator as zValidator5 } from "@hono/zod-validator";
|
|
2225
2157
|
import { Hono as Hono13 } from "hono";
|
|
2226
|
-
import { z as
|
|
2227
|
-
var createSchema =
|
|
2228
|
-
title:
|
|
2229
|
-
participantNames:
|
|
2158
|
+
import { z as z5 } from "zod";
|
|
2159
|
+
var createSchema = z5.object({
|
|
2160
|
+
title: z5.string().optional(),
|
|
2161
|
+
participantNames: z5.array(z5.string()).min(1)
|
|
2230
2162
|
});
|
|
2231
2163
|
var app13 = new Hono13().use("*", authMiddleware).get("/", async (c) => {
|
|
2232
2164
|
const user = c.get("user");
|
|
@@ -2240,7 +2172,7 @@ var app13 = new Hono13().use("*", authMiddleware).get("/", async (c) => {
|
|
|
2240
2172
|
}
|
|
2241
2173
|
const msgs = await getMessages(id);
|
|
2242
2174
|
return c.json(msgs);
|
|
2243
|
-
}).post("/",
|
|
2175
|
+
}).post("/", zValidator5("json", createSchema), async (c) => {
|
|
2244
2176
|
const user = c.get("user");
|
|
2245
2177
|
const body = c.req.valid("json");
|
|
2246
2178
|
const participantIds = /* @__PURE__ */ new Set();
|
|
@@ -2346,8 +2278,8 @@ async function startServer({
|
|
|
2346
2278
|
let assetsDir = "";
|
|
2347
2279
|
let searchDir = dirname2(new URL(import.meta.url).pathname);
|
|
2348
2280
|
for (let i = 0; i < 5; i++) {
|
|
2349
|
-
const candidate =
|
|
2350
|
-
if (
|
|
2281
|
+
const candidate = resolve9(searchDir, "dist", "web-assets");
|
|
2282
|
+
if (existsSync7(candidate)) {
|
|
2351
2283
|
assetsDir = candidate;
|
|
2352
2284
|
break;
|
|
2353
2285
|
}
|
|
@@ -2357,7 +2289,7 @@ async function startServer({
|
|
|
2357
2289
|
app_default.get("*", async (c) => {
|
|
2358
2290
|
const urlPath = new URL(c.req.url).pathname;
|
|
2359
2291
|
if (urlPath.startsWith("/api/")) return c.notFound();
|
|
2360
|
-
const filePath =
|
|
2292
|
+
const filePath = resolve9(assetsDir, urlPath.slice(1));
|
|
2361
2293
|
if (!filePath.startsWith(assetsDir)) return c.text("Forbidden", 403);
|
|
2362
2294
|
const s = await stat(filePath).catch(() => null);
|
|
2363
2295
|
if (s?.isFile()) {
|
|
@@ -2366,7 +2298,7 @@ async function startServer({
|
|
|
2366
2298
|
const body = await readFile2(filePath);
|
|
2367
2299
|
return c.body(body, 200, { "Content-Type": mime });
|
|
2368
2300
|
}
|
|
2369
|
-
const indexPath =
|
|
2301
|
+
const indexPath = resolve9(assetsDir, "index.html");
|
|
2370
2302
|
const indexStat = await stat(indexPath).catch(() => null);
|
|
2371
2303
|
if (indexStat?.isFile()) {
|
|
2372
2304
|
const body = await readFile2(indexPath, "utf-8");
|
|
@@ -2376,10 +2308,10 @@ async function startServer({
|
|
|
2376
2308
|
});
|
|
2377
2309
|
}
|
|
2378
2310
|
const server = serve({ fetch: app_default.fetch, port, hostname });
|
|
2379
|
-
await new Promise((
|
|
2311
|
+
await new Promise((resolve11, reject) => {
|
|
2380
2312
|
server.on("listening", () => {
|
|
2381
2313
|
logger_default.info("Volute UI running", { hostname, port });
|
|
2382
|
-
|
|
2314
|
+
resolve11();
|
|
2383
2315
|
});
|
|
2384
2316
|
server.on("error", (err) => {
|
|
2385
2317
|
reject(err);
|
|
@@ -2390,14 +2322,14 @@ async function startServer({
|
|
|
2390
2322
|
|
|
2391
2323
|
// src/daemon.ts
|
|
2392
2324
|
if (!process.env.VOLUTE_HOME) {
|
|
2393
|
-
process.env.VOLUTE_HOME =
|
|
2325
|
+
process.env.VOLUTE_HOME = resolve10(homedir(), ".volute");
|
|
2394
2326
|
}
|
|
2395
2327
|
async function startDaemon(opts) {
|
|
2396
2328
|
const { port, hostname } = opts;
|
|
2397
2329
|
const myPid = String(process.pid);
|
|
2398
2330
|
const home = voluteHome();
|
|
2399
2331
|
if (!opts.foreground) {
|
|
2400
|
-
const log2 = new RotatingLog(
|
|
2332
|
+
const log2 = new RotatingLog(resolve10(home, "daemon.log"));
|
|
2401
2333
|
const write2 = (...args) => log2.write(`${format(...args)}
|
|
2402
2334
|
`);
|
|
2403
2335
|
console.log = write2;
|
|
@@ -2405,11 +2337,12 @@ async function startDaemon(opts) {
|
|
|
2405
2337
|
console.warn = write2;
|
|
2406
2338
|
console.info = write2;
|
|
2407
2339
|
}
|
|
2408
|
-
const DAEMON_PID_PATH =
|
|
2409
|
-
const DAEMON_JSON_PATH =
|
|
2410
|
-
|
|
2340
|
+
const DAEMON_PID_PATH = resolve10(home, "daemon.pid");
|
|
2341
|
+
const DAEMON_JSON_PATH = resolve10(home, "daemon.json");
|
|
2342
|
+
mkdirSync3(home, { recursive: true });
|
|
2411
2343
|
const token = process.env.VOLUTE_DAEMON_TOKEN || randomBytes(32).toString("hex");
|
|
2412
2344
|
process.env.VOLUTE_DAEMON_TOKEN = token;
|
|
2345
|
+
process.env.VOLUTE_DAEMON_PORT = String(port);
|
|
2413
2346
|
process.env.VOLUTE_DAEMON_HOSTNAME = hostname;
|
|
2414
2347
|
let server;
|
|
2415
2348
|
try {
|
|
@@ -2435,6 +2368,13 @@ async function startDaemon(opts) {
|
|
|
2435
2368
|
const tokenBudget = getTokenBudget();
|
|
2436
2369
|
tokenBudget.start(port, token);
|
|
2437
2370
|
const registry = readRegistry();
|
|
2371
|
+
for (const entry of registry) {
|
|
2372
|
+
try {
|
|
2373
|
+
migrateAgentState(entry.name);
|
|
2374
|
+
} catch (err) {
|
|
2375
|
+
console.error(`[daemon] failed to migrate state for ${entry.name}:`, err);
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2438
2378
|
for (const entry of registry) {
|
|
2439
2379
|
if (!entry.running) continue;
|
|
2440
2380
|
try {
|