volute 0.4.0 → 0.5.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/README.md +22 -22
- package/dist/agent-Z2B6EFEQ.js +75 -0
- package/dist/{agent-manager-AUCKMGPR.js → agent-manager-PXBKA2GK.js} +4 -4
- package/dist/channel-MK5OK2SI.js +113 -0
- package/dist/chunk-5X7HGB6L.js +107 -0
- package/dist/{chunk-YGFIWIOF.js → chunk-7L4AN5D4.js} +1 -1
- package/dist/{chunk-VRVVQIYY.js → chunk-AZEL2IEK.js} +1 -1
- package/dist/chunk-B3R6L2GW.js +24 -0
- package/dist/{chunk-DNOXHLE5.js → chunk-HE67X4T6.js} +1 -1
- package/dist/{chunk-I6OHXCMV.js → chunk-MW2KFO3B.js} +47 -9
- package/dist/{chunk-5OCWMTVS.js → chunk-SMISE4SV.js} +77 -3
- package/dist/{chunk-SOZA2TLP.js → chunk-UAVD2AHX.js} +1 -1
- package/dist/{chunk-3C2XR4IY.js → chunk-UX25Z2ND.js} +113 -107
- package/dist/{chunk-GSPKUPKU.js → chunk-XUA3JUFK.js} +2 -1
- package/dist/chunk-ZYGKG6VC.js +22 -0
- package/dist/cli.js +86 -74
- package/dist/{connector-DKDJTLYZ.js → connector-LYEMXQEV.js} +11 -6
- package/dist/connectors/discord.js +3 -1
- package/dist/connectors/slack.js +14 -5
- package/dist/connectors/telegram.js +21 -2
- package/dist/conversation-ERXEQZTY.js +163 -0
- package/dist/create-RVCZN6HE.js +91 -0
- package/dist/{daemon-client-XR24PUJF.js → daemon-client-ZY6UUN2M.js} +2 -2
- package/dist/daemon.js +629 -177
- package/dist/{delete-55MXCEY5.js → delete-3QH7VYIN.js} +7 -8
- package/dist/{down-3OB6UVAJ.js → down-O7IFZLVJ.js} +1 -1
- package/dist/{env-JB27UAC3.js → env-4D4REPJF.js} +8 -5
- package/dist/{history-BKG74I43.js → history-OEONB53Z.js} +3 -3
- package/dist/{import-4CI2ZUTJ.js → import-MXJB2EII.js} +8 -8
- package/dist/{logs-NXFFGUKY.js → logs-DF342W4M.js} +2 -2
- package/dist/message-ADHWFHSI.js +32 -0
- package/dist/{package-Z2SFO2SV.js → package-VQOE7JNH.js} +1 -1
- package/dist/{schedule-A35SH4HT.js → schedule-NAG6F463.js} +10 -5
- package/dist/send-66QMKRUH.js +75 -0
- package/dist/{setup-2FDVN7OF.js → setup-RPRRGG2F.js} +5 -5
- package/dist/{start-LDPMCMYT.js → start-TUOXDSFL.js} +3 -3
- package/dist/{status-MVSQG54T.js → status-A36EHRO4.js} +3 -3
- package/dist/{stop-5PZTZCLL.js → stop-AOJZLQ5X.js} +6 -7
- package/dist/{up-F7TMTLRE.js → up-7ILD7GU7.js} +2 -2
- package/dist/update-LPSIAWQ2.js +140 -0
- package/dist/update-check-Y33QDCFL.js +17 -0
- package/dist/{upgrade-6ZW2RD64.js → upgrade-FX2TKJ2S.js} +16 -15
- package/dist/{variant-T64BKARF.js → variant-LAB67OC2.js} +15 -10
- package/dist/web-assets/assets/index-BbRmoxoA.js +308 -0
- package/dist/web-assets/index.html +2 -2
- package/drizzle/0003_clean_ego.sql +12 -0
- package/drizzle/meta/0003_snapshot.json +417 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +1 -1
- package/templates/_base/.init/.config/hooks/startup-context.sh +19 -1
- package/templates/_base/_skills/volute-agent/SKILL.md +110 -14
- package/templates/_base/home/.config/routes.json +10 -0
- package/templates/_base/home/VOLUTE.md +14 -35
- package/templates/_base/src/lib/format-prefix.ts +1 -1
- package/templates/_base/src/lib/router.ts +163 -16
- package/templates/_base/src/lib/routing.ts +55 -18
- package/templates/_base/src/lib/types.ts +3 -1
- package/templates/agent-sdk/.init/.config/routes.json +5 -0
- package/templates/agent-sdk/.init/CLAUDE.md +2 -2
- package/templates/agent-sdk/src/agent.ts +2 -1
- package/templates/agent-sdk/src/server.ts +8 -2
- package/templates/agent-sdk/volute-template.json +1 -1
- package/templates/pi/.init/.config/routes.json +5 -0
- package/templates/pi/.init/AGENTS.md +1 -1
- package/templates/pi/src/agent.ts +5 -3
- package/templates/pi/src/server.ts +1 -1
- package/templates/pi/volute-template.json +1 -1
- package/dist/channel-DQ6UY7QB.js +0 -67
- package/dist/chunk-ZHCE4DPY.js +0 -110
- package/dist/create-ILVOG75A.js +0 -79
- package/dist/send-3U6OTKG7.js +0 -57
- package/dist/web-assets/assets/index-NS621maO.js +0 -296
- package/templates/agent-sdk/.init/.config/sessions.json +0 -4
- package/templates/pi/.init/.config/sessions.json +0 -1
- package/dist/{service-SA4TTMDU.js → service-HZNIDNJF.js} +3 -3
package/dist/daemon.js
CHANGED
|
@@ -1,30 +1,36 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
} from "./chunk-5OCWMTVS.js";
|
|
5
|
-
import {
|
|
3
|
+
RotatingLog,
|
|
6
4
|
clearJsonMap,
|
|
7
5
|
getAgentManager,
|
|
8
6
|
initAgentManager,
|
|
9
7
|
loadJsonMap,
|
|
10
8
|
saveJsonMap
|
|
11
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-MW2KFO3B.js";
|
|
12
10
|
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
11
|
+
checkForUpdate,
|
|
12
|
+
checkForUpdateCached,
|
|
13
|
+
getCurrentVersion
|
|
14
|
+
} from "./chunk-5X7HGB6L.js";
|
|
15
|
+
import {
|
|
16
|
+
CHANNELS
|
|
17
|
+
} from "./chunk-SMISE4SV.js";
|
|
18
|
+
import {
|
|
19
|
+
collectPart
|
|
20
|
+
} from "./chunk-B3R6L2GW.js";
|
|
18
21
|
import {
|
|
19
22
|
readVoluteConfig,
|
|
20
23
|
writeVoluteConfig
|
|
21
24
|
} from "./chunk-NETNFBA5.js";
|
|
22
25
|
import {
|
|
23
26
|
loadMergedEnv
|
|
24
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-HE67X4T6.js";
|
|
25
28
|
import {
|
|
26
29
|
applyIsolation
|
|
27
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-UAVD2AHX.js";
|
|
31
|
+
import {
|
|
32
|
+
resolveVoluteBin
|
|
33
|
+
} from "./chunk-5SKQ6J7T.js";
|
|
28
34
|
import {
|
|
29
35
|
agentDir,
|
|
30
36
|
checkHealth,
|
|
@@ -38,26 +44,21 @@ import {
|
|
|
38
44
|
setAgentRunning,
|
|
39
45
|
setVariantRunning,
|
|
40
46
|
voluteHome
|
|
41
|
-
} from "./chunk-
|
|
47
|
+
} from "./chunk-UX25Z2ND.js";
|
|
42
48
|
import {
|
|
43
49
|
__export
|
|
44
50
|
} from "./chunk-K3NQKI34.js";
|
|
45
51
|
|
|
46
52
|
// src/daemon.ts
|
|
47
53
|
import { randomBytes } from "crypto";
|
|
48
|
-
import { mkdirSync as mkdirSync2, readFileSync as
|
|
49
|
-
import {
|
|
54
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
55
|
+
import { homedir } from "os";
|
|
56
|
+
import { resolve as resolve10 } from "path";
|
|
57
|
+
import { format } from "util";
|
|
50
58
|
|
|
51
59
|
// src/lib/connector-manager.ts
|
|
52
60
|
import { spawn } from "child_process";
|
|
53
|
-
import {
|
|
54
|
-
createWriteStream,
|
|
55
|
-
existsSync as existsSync2,
|
|
56
|
-
mkdirSync,
|
|
57
|
-
readFileSync as readFileSync2,
|
|
58
|
-
unlinkSync,
|
|
59
|
-
writeFileSync
|
|
60
|
-
} from "fs";
|
|
61
|
+
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, unlinkSync, writeFileSync } from "fs";
|
|
61
62
|
import { dirname, resolve as resolve2 } from "path";
|
|
62
63
|
|
|
63
64
|
// src/lib/connector-defs.ts
|
|
@@ -217,7 +218,7 @@ var ConnectorManager = class {
|
|
|
217
218
|
}
|
|
218
219
|
const logsDir = resolve2(agentDir2, ".volute", "logs");
|
|
219
220
|
mkdirSync(logsDir, { recursive: true });
|
|
220
|
-
const logStream =
|
|
221
|
+
const logStream = new RotatingLog(resolve2(logsDir, `${type}.log`));
|
|
221
222
|
const agentEnv = loadMergedEnv(agentDir2);
|
|
222
223
|
const prefix = `${type.toUpperCase()}_`;
|
|
223
224
|
const connectorEnv = Object.fromEntries(
|
|
@@ -292,19 +293,19 @@ var ConnectorManager = class {
|
|
|
292
293
|
const stopKey = `${agentName}:${type}`;
|
|
293
294
|
this.stopping.add(stopKey);
|
|
294
295
|
agentMap.delete(type);
|
|
295
|
-
await new Promise((
|
|
296
|
-
tracked.child.on("exit", () =>
|
|
296
|
+
await new Promise((resolve11) => {
|
|
297
|
+
tracked.child.on("exit", () => resolve11());
|
|
297
298
|
try {
|
|
298
299
|
tracked.child.kill("SIGTERM");
|
|
299
300
|
} catch {
|
|
300
|
-
|
|
301
|
+
resolve11();
|
|
301
302
|
}
|
|
302
303
|
setTimeout(() => {
|
|
303
304
|
try {
|
|
304
305
|
tracked.child.kill("SIGKILL");
|
|
305
306
|
} catch {
|
|
306
307
|
}
|
|
307
|
-
|
|
308
|
+
resolve11();
|
|
308
309
|
}, 5e3);
|
|
309
310
|
});
|
|
310
311
|
this.stopping.delete(stopKey);
|
|
@@ -502,7 +503,15 @@ var Scheduler = class {
|
|
|
502
503
|
console.error(`[scheduler] fired "${schedule.id}" for ${agentName}`);
|
|
503
504
|
}
|
|
504
505
|
try {
|
|
505
|
-
|
|
506
|
+
const reader = res.body?.getReader();
|
|
507
|
+
if (reader) {
|
|
508
|
+
try {
|
|
509
|
+
while (!(await reader.read()).done) {
|
|
510
|
+
}
|
|
511
|
+
} finally {
|
|
512
|
+
reader.releaseLock();
|
|
513
|
+
}
|
|
514
|
+
}
|
|
506
515
|
} catch {
|
|
507
516
|
}
|
|
508
517
|
} catch (err) {
|
|
@@ -539,18 +548,20 @@ import { migrate } from "drizzle-orm/libsql/migrator";
|
|
|
539
548
|
var schema_exports = {};
|
|
540
549
|
__export(schema_exports, {
|
|
541
550
|
agentMessages: () => agentMessages,
|
|
551
|
+
conversationParticipants: () => conversationParticipants,
|
|
542
552
|
conversations: () => conversations,
|
|
543
553
|
messages: () => messages,
|
|
544
554
|
sessions: () => sessions,
|
|
545
555
|
users: () => users
|
|
546
556
|
});
|
|
547
557
|
import { sql } from "drizzle-orm";
|
|
548
|
-
import { index, integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
|
|
558
|
+
import { index, integer, sqliteTable, text, uniqueIndex } from "drizzle-orm/sqlite-core";
|
|
549
559
|
var users = sqliteTable("users", {
|
|
550
560
|
id: integer("id").primaryKey({ autoIncrement: true }),
|
|
551
561
|
username: text("username").unique().notNull(),
|
|
552
562
|
password_hash: text("password_hash").notNull(),
|
|
553
563
|
role: text("role").notNull().default("pending"),
|
|
564
|
+
user_type: text("user_type").notNull().default("human"),
|
|
554
565
|
created_at: text("created_at").notNull().default(sql`(datetime('now'))`)
|
|
555
566
|
});
|
|
556
567
|
var conversations = sqliteTable(
|
|
@@ -586,6 +597,19 @@ var agentMessages = sqliteTable(
|
|
|
586
597
|
index("idx_agent_messages_channel").on(table.agent, table.channel)
|
|
587
598
|
]
|
|
588
599
|
);
|
|
600
|
+
var conversationParticipants = sqliteTable(
|
|
601
|
+
"conversation_participants",
|
|
602
|
+
{
|
|
603
|
+
conversation_id: text("conversation_id").notNull().references(() => conversations.id, { onDelete: "cascade" }),
|
|
604
|
+
user_id: integer("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
605
|
+
role: text("role").notNull().default("member"),
|
|
606
|
+
joined_at: text("joined_at").notNull().default(sql`(datetime('now'))`)
|
|
607
|
+
},
|
|
608
|
+
(table) => [
|
|
609
|
+
uniqueIndex("idx_cp_unique").on(table.conversation_id, table.user_id),
|
|
610
|
+
index("idx_cp_user_id").on(table.user_id)
|
|
611
|
+
]
|
|
612
|
+
);
|
|
589
613
|
var sessions = sqliteTable("sessions", {
|
|
590
614
|
id: text("id").primaryKey(),
|
|
591
615
|
userId: integer("user_id").references(() => users.id, { onDelete: "cascade" }).notNull(),
|
|
@@ -628,12 +652,13 @@ async function getDb() {
|
|
|
628
652
|
async function createUser(username, password) {
|
|
629
653
|
const db2 = await getDb();
|
|
630
654
|
const hash = hashSync(password, 10);
|
|
631
|
-
const [{ value }] = await db2.select({ value: count() }).from(users);
|
|
655
|
+
const [{ value }] = await db2.select({ value: count() }).from(users).where(eq(users.user_type, "human"));
|
|
632
656
|
const role = value === 0 ? "admin" : "pending";
|
|
633
657
|
const [result] = await db2.insert(users).values({ username, password_hash: hash, role }).returning({
|
|
634
658
|
id: users.id,
|
|
635
659
|
username: users.username,
|
|
636
660
|
role: users.role,
|
|
661
|
+
user_type: users.user_type,
|
|
637
662
|
created_at: users.created_at
|
|
638
663
|
});
|
|
639
664
|
return result;
|
|
@@ -642,6 +667,7 @@ async function verifyUser(username, password) {
|
|
|
642
667
|
const db2 = await getDb();
|
|
643
668
|
const row = await db2.select().from(users).where(eq(users.username, username)).get();
|
|
644
669
|
if (!row) return null;
|
|
670
|
+
if (row.user_type === "agent") return null;
|
|
645
671
|
if (!compareSync(password, row.password_hash)) return null;
|
|
646
672
|
const { password_hash: _, ...user } = row;
|
|
647
673
|
return user;
|
|
@@ -652,6 +678,7 @@ async function getUser(id) {
|
|
|
652
678
|
id: users.id,
|
|
653
679
|
username: users.username,
|
|
654
680
|
role: users.role,
|
|
681
|
+
user_type: users.user_type,
|
|
655
682
|
created_at: users.created_at
|
|
656
683
|
}).from(users).where(eq(users.id, id)).get();
|
|
657
684
|
return row ?? null;
|
|
@@ -662,6 +689,7 @@ async function getUserByUsername(username) {
|
|
|
662
689
|
id: users.id,
|
|
663
690
|
username: users.username,
|
|
664
691
|
role: users.role,
|
|
692
|
+
user_type: users.user_type,
|
|
665
693
|
created_at: users.created_at
|
|
666
694
|
}).from(users).where(eq(users.username, username)).get();
|
|
667
695
|
return row ?? null;
|
|
@@ -672,6 +700,7 @@ async function listUsers() {
|
|
|
672
700
|
id: users.id,
|
|
673
701
|
username: users.username,
|
|
674
702
|
role: users.role,
|
|
703
|
+
user_type: users.user_type,
|
|
675
704
|
created_at: users.created_at
|
|
676
705
|
}).from(users).orderBy(users.created_at).all();
|
|
677
706
|
}
|
|
@@ -681,9 +710,58 @@ async function listPendingUsers() {
|
|
|
681
710
|
id: users.id,
|
|
682
711
|
username: users.username,
|
|
683
712
|
role: users.role,
|
|
713
|
+
user_type: users.user_type,
|
|
684
714
|
created_at: users.created_at
|
|
685
715
|
}).from(users).where(eq(users.role, "pending")).orderBy(users.created_at).all();
|
|
686
716
|
}
|
|
717
|
+
async function listUsersByType(userType) {
|
|
718
|
+
const db2 = await getDb();
|
|
719
|
+
return db2.select({
|
|
720
|
+
id: users.id,
|
|
721
|
+
username: users.username,
|
|
722
|
+
role: users.role,
|
|
723
|
+
user_type: users.user_type,
|
|
724
|
+
created_at: users.created_at
|
|
725
|
+
}).from(users).where(eq(users.user_type, userType)).orderBy(users.created_at).all();
|
|
726
|
+
}
|
|
727
|
+
async function getOrCreateAgentUser(agentName) {
|
|
728
|
+
const db2 = await getDb();
|
|
729
|
+
const existing = await db2.select({
|
|
730
|
+
id: users.id,
|
|
731
|
+
username: users.username,
|
|
732
|
+
role: users.role,
|
|
733
|
+
user_type: users.user_type,
|
|
734
|
+
created_at: users.created_at
|
|
735
|
+
}).from(users).where(and(eq(users.username, agentName), eq(users.user_type, "agent"))).get();
|
|
736
|
+
if (existing) return existing;
|
|
737
|
+
try {
|
|
738
|
+
const [result] = await db2.insert(users).values({
|
|
739
|
+
username: agentName,
|
|
740
|
+
password_hash: "!agent",
|
|
741
|
+
role: "agent",
|
|
742
|
+
user_type: "agent"
|
|
743
|
+
}).returning({
|
|
744
|
+
id: users.id,
|
|
745
|
+
username: users.username,
|
|
746
|
+
role: users.role,
|
|
747
|
+
user_type: users.user_type,
|
|
748
|
+
created_at: users.created_at
|
|
749
|
+
});
|
|
750
|
+
return result;
|
|
751
|
+
} catch (err) {
|
|
752
|
+
if (err instanceof Error && err.message.includes("UNIQUE constraint")) {
|
|
753
|
+
const retried = await db2.select({
|
|
754
|
+
id: users.id,
|
|
755
|
+
username: users.username,
|
|
756
|
+
role: users.role,
|
|
757
|
+
user_type: users.user_type,
|
|
758
|
+
created_at: users.created_at
|
|
759
|
+
}).from(users).where(and(eq(users.username, agentName), eq(users.user_type, "agent"))).get();
|
|
760
|
+
if (retried) return retried;
|
|
761
|
+
}
|
|
762
|
+
throw err;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
687
765
|
async function approveUser(id) {
|
|
688
766
|
const db2 = await getDb();
|
|
689
767
|
await db2.update(users).set({ role: "user" }).where(and(eq(users.id, id), eq(users.role, "pending")));
|
|
@@ -733,7 +811,7 @@ var authMiddleware = createMiddleware(async (c, next) => {
|
|
|
733
811
|
if (authHeader?.startsWith("Bearer ")) {
|
|
734
812
|
const token = authHeader.slice(7);
|
|
735
813
|
if (token && isValidDaemonToken(token)) {
|
|
736
|
-
c.set("user", { id: 0, username: "cli", role: "admin" });
|
|
814
|
+
c.set("user", { id: 0, username: "cli", role: "admin", user_type: "human" });
|
|
737
815
|
await next();
|
|
738
816
|
return;
|
|
739
817
|
}
|
|
@@ -752,11 +830,55 @@ var authMiddleware = createMiddleware(async (c, next) => {
|
|
|
752
830
|
// src/web/server.ts
|
|
753
831
|
import { existsSync as existsSync7 } from "fs";
|
|
754
832
|
import { readFile as readFile2, stat } from "fs/promises";
|
|
755
|
-
import { dirname as dirname3, extname, resolve as
|
|
833
|
+
import { dirname as dirname3, extname, resolve as resolve9 } from "path";
|
|
756
834
|
import { serve } from "@hono/node-server";
|
|
757
835
|
|
|
836
|
+
// src/lib/log-buffer.ts
|
|
837
|
+
var LogBuffer = class {
|
|
838
|
+
entries = [];
|
|
839
|
+
maxSize = 1e3;
|
|
840
|
+
subscribers = /* @__PURE__ */ new Set();
|
|
841
|
+
append(entry) {
|
|
842
|
+
this.entries.push(entry);
|
|
843
|
+
if (this.entries.length > this.maxSize) {
|
|
844
|
+
this.entries.shift();
|
|
845
|
+
}
|
|
846
|
+
for (const sub of this.subscribers) {
|
|
847
|
+
sub(entry);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
getEntries() {
|
|
851
|
+
return [...this.entries];
|
|
852
|
+
}
|
|
853
|
+
subscribe(fn) {
|
|
854
|
+
this.subscribers.add(fn);
|
|
855
|
+
return () => this.subscribers.delete(fn);
|
|
856
|
+
}
|
|
857
|
+
};
|
|
858
|
+
var logBuffer = new LogBuffer();
|
|
859
|
+
|
|
860
|
+
// src/lib/logger.ts
|
|
861
|
+
function write(level, msg, data) {
|
|
862
|
+
const entry = {
|
|
863
|
+
level,
|
|
864
|
+
msg,
|
|
865
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
866
|
+
...data ? { data } : {}
|
|
867
|
+
};
|
|
868
|
+
const line = JSON.stringify(entry);
|
|
869
|
+
process.stderr.write(`${line}
|
|
870
|
+
`);
|
|
871
|
+
logBuffer.append(entry);
|
|
872
|
+
}
|
|
873
|
+
var log = {
|
|
874
|
+
info: (msg, data) => write("info", msg, data),
|
|
875
|
+
warn: (msg, data) => write("warn", msg, data),
|
|
876
|
+
error: (msg, data) => write("error", msg, data)
|
|
877
|
+
};
|
|
878
|
+
var logger_default = log;
|
|
879
|
+
|
|
758
880
|
// src/web/app.ts
|
|
759
|
-
import { Hono as
|
|
881
|
+
import { Hono as Hono13 } from "hono";
|
|
760
882
|
import { bodyLimit } from "hono/body-limit";
|
|
761
883
|
import { csrf } from "hono/csrf";
|
|
762
884
|
import { HTTPException } from "hono/http-exception";
|
|
@@ -767,6 +889,47 @@ import { resolve as resolve5 } from "path";
|
|
|
767
889
|
import { and as and2, desc, eq as eq3 } from "drizzle-orm";
|
|
768
890
|
import { Hono } from "hono";
|
|
769
891
|
import { stream } from "hono/streaming";
|
|
892
|
+
|
|
893
|
+
// src/lib/ndjson.ts
|
|
894
|
+
var MAX_BUFFER_SIZE = 1e6;
|
|
895
|
+
async function* readNdjson(body) {
|
|
896
|
+
const reader = body.getReader();
|
|
897
|
+
const decoder = new TextDecoder();
|
|
898
|
+
let buffer = "";
|
|
899
|
+
try {
|
|
900
|
+
while (true) {
|
|
901
|
+
const { done, value } = await reader.read();
|
|
902
|
+
if (done) break;
|
|
903
|
+
buffer += decoder.decode(value, { stream: true });
|
|
904
|
+
if (buffer.length > MAX_BUFFER_SIZE) {
|
|
905
|
+
logger_default.warn("ndjson: buffer exceeded 1MB, resetting");
|
|
906
|
+
buffer = "";
|
|
907
|
+
continue;
|
|
908
|
+
}
|
|
909
|
+
const lines = buffer.split("\n");
|
|
910
|
+
buffer = lines.pop() || "";
|
|
911
|
+
for (const line of lines) {
|
|
912
|
+
if (!line.trim()) continue;
|
|
913
|
+
try {
|
|
914
|
+
yield JSON.parse(line);
|
|
915
|
+
} catch {
|
|
916
|
+
logger_default.warn("ndjson: skipping invalid line", { line: line.slice(0, 100) });
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
if (buffer.trim()) {
|
|
921
|
+
try {
|
|
922
|
+
yield JSON.parse(buffer);
|
|
923
|
+
} catch {
|
|
924
|
+
logger_default.warn("ndjson: skipping invalid line", { line: buffer.slice(0, 100) });
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
} finally {
|
|
928
|
+
reader.releaseLock();
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
// src/web/routes/agents.ts
|
|
770
933
|
function getDaemonPort() {
|
|
771
934
|
try {
|
|
772
935
|
const data = JSON.parse(readFileSync3(resolve5(voluteHome(), "daemon.json"), "utf-8"));
|
|
@@ -784,10 +947,10 @@ async function getAgentStatus(name, port) {
|
|
|
784
947
|
}
|
|
785
948
|
const channels = [];
|
|
786
949
|
channels.push({
|
|
787
|
-
name: CHANNELS.
|
|
788
|
-
displayName: CHANNELS.
|
|
950
|
+
name: CHANNELS.volute.name,
|
|
951
|
+
displayName: CHANNELS.volute.displayName,
|
|
789
952
|
status: status === "running" ? "connected" : "disconnected",
|
|
790
|
-
showToolCalls: CHANNELS.
|
|
953
|
+
showToolCalls: CHANNELS.volute.showToolCalls
|
|
791
954
|
});
|
|
792
955
|
const connectorStatuses = getConnectorManager().getConnectorStatus(name);
|
|
793
956
|
for (const cs of connectorStatuses) {
|
|
@@ -1049,6 +1212,14 @@ var credentialsSchema = z.object({
|
|
|
1049
1212
|
var admin = new Hono2().use(authMiddleware).get("/users", async (c) => {
|
|
1050
1213
|
const user = c.get("user");
|
|
1051
1214
|
if (user.role !== "admin") return c.json({ error: "Forbidden" }, 403);
|
|
1215
|
+
const agents = readRegistry();
|
|
1216
|
+
for (const agent of agents) {
|
|
1217
|
+
await getOrCreateAgentUser(agent.name);
|
|
1218
|
+
}
|
|
1219
|
+
const type = c.req.query("type");
|
|
1220
|
+
if (type === "human" || type === "agent") {
|
|
1221
|
+
return c.json(await listUsersByType(type));
|
|
1222
|
+
}
|
|
1052
1223
|
return c.json(await listUsers());
|
|
1053
1224
|
}).get("/users/pending", async (c) => {
|
|
1054
1225
|
const user = c.get("user");
|
|
@@ -1101,6 +1272,8 @@ var app2 = new Hono2().post("/register", zValidator("json", credentialsSchema),
|
|
|
1101
1272
|
var auth_default = app2;
|
|
1102
1273
|
|
|
1103
1274
|
// src/web/routes/chat.ts
|
|
1275
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
1276
|
+
import { resolve as resolve6 } from "path";
|
|
1104
1277
|
import { zValidator as zValidator2 } from "@hono/zod-validator";
|
|
1105
1278
|
import { Hono as Hono3 } from "hono";
|
|
1106
1279
|
import { streamSSE } from "hono/streaming";
|
|
@@ -1108,7 +1281,7 @@ import { z as z2 } from "zod";
|
|
|
1108
1281
|
|
|
1109
1282
|
// src/lib/conversations.ts
|
|
1110
1283
|
import { randomUUID } from "crypto";
|
|
1111
|
-
import { and as and3, desc as desc2, eq as eq4, isNull, sql as sql2 } from "drizzle-orm";
|
|
1284
|
+
import { and as and3, desc as desc2, eq as eq4, inArray, isNull, sql as sql2 } from "drizzle-orm";
|
|
1112
1285
|
async function createConversation(agentName, channel, opts) {
|
|
1113
1286
|
const db2 = await getDb();
|
|
1114
1287
|
const id = randomUUID();
|
|
@@ -1119,6 +1292,15 @@ async function createConversation(agentName, channel, opts) {
|
|
|
1119
1292
|
user_id: opts?.userId ?? null,
|
|
1120
1293
|
title: opts?.title ?? null
|
|
1121
1294
|
});
|
|
1295
|
+
if (opts?.participantIds && opts.participantIds.length > 0) {
|
|
1296
|
+
await db2.insert(conversationParticipants).values(
|
|
1297
|
+
opts.participantIds.map((uid, i) => ({
|
|
1298
|
+
conversation_id: id,
|
|
1299
|
+
user_id: uid,
|
|
1300
|
+
role: i === 0 ? "owner" : "member"
|
|
1301
|
+
}))
|
|
1302
|
+
);
|
|
1303
|
+
}
|
|
1122
1304
|
return {
|
|
1123
1305
|
id,
|
|
1124
1306
|
agent_name: agentName,
|
|
@@ -1129,24 +1311,44 @@ async function createConversation(agentName, channel, opts) {
|
|
|
1129
1311
|
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1130
1312
|
};
|
|
1131
1313
|
}
|
|
1132
|
-
async function
|
|
1314
|
+
async function getParticipants(conversationId) {
|
|
1133
1315
|
const db2 = await getDb();
|
|
1134
|
-
const
|
|
1135
|
-
|
|
1316
|
+
const rows = await db2.select({
|
|
1317
|
+
userId: conversationParticipants.user_id,
|
|
1318
|
+
username: users.username,
|
|
1319
|
+
userType: users.user_type,
|
|
1320
|
+
role: conversationParticipants.role
|
|
1321
|
+
}).from(conversationParticipants).innerJoin(users, eq4(conversationParticipants.user_id, users.id)).where(eq4(conversationParticipants.conversation_id, conversationId)).all();
|
|
1322
|
+
return rows;
|
|
1323
|
+
}
|
|
1324
|
+
async function isParticipant(conversationId, userId) {
|
|
1325
|
+
const db2 = await getDb();
|
|
1326
|
+
const row = await db2.select({ user_id: conversationParticipants.user_id }).from(conversationParticipants).where(
|
|
1327
|
+
and3(
|
|
1328
|
+
eq4(conversationParticipants.conversation_id, conversationId),
|
|
1329
|
+
eq4(conversationParticipants.user_id, userId)
|
|
1330
|
+
)
|
|
1331
|
+
).get();
|
|
1332
|
+
return row != null;
|
|
1333
|
+
}
|
|
1334
|
+
async function listConversationsForUser(userId) {
|
|
1335
|
+
const db2 = await getDb();
|
|
1336
|
+
const participantRows = await db2.select({ conversation_id: conversationParticipants.conversation_id }).from(conversationParticipants).where(eq4(conversationParticipants.user_id, userId)).all();
|
|
1337
|
+
if (participantRows.length === 0) return [];
|
|
1338
|
+
const convIds = participantRows.map((r) => r.conversation_id);
|
|
1339
|
+
return db2.select().from(conversations).where(inArray(conversations.id, convIds)).orderBy(desc2(conversations.updated_at)).all();
|
|
1340
|
+
}
|
|
1341
|
+
async function isParticipantOrOwner(conversationId, userId) {
|
|
1342
|
+
if (await isParticipant(conversationId, userId)) return true;
|
|
1343
|
+
const db2 = await getDb();
|
|
1344
|
+
const row = await db2.select().from(conversations).where(and3(eq4(conversations.id, conversationId), eq4(conversations.user_id, userId))).get();
|
|
1345
|
+
return row != null;
|
|
1136
1346
|
}
|
|
1137
1347
|
async function deleteConversationForUser(id, userId) {
|
|
1138
|
-
|
|
1139
|
-
if (!conv) return false;
|
|
1348
|
+
if (!await isParticipantOrOwner(id, userId)) return false;
|
|
1140
1349
|
await deleteConversation(id);
|
|
1141
1350
|
return true;
|
|
1142
1351
|
}
|
|
1143
|
-
async function listConversations(agentName, opts) {
|
|
1144
|
-
const db2 = await getDb();
|
|
1145
|
-
if (opts?.userId != null) {
|
|
1146
|
-
return db2.select().from(conversations).where(and3(eq4(conversations.agent_name, agentName), eq4(conversations.user_id, opts.userId))).orderBy(desc2(conversations.updated_at)).all();
|
|
1147
|
-
}
|
|
1148
|
-
return db2.select().from(conversations).where(eq4(conversations.agent_name, agentName)).orderBy(desc2(conversations.updated_at)).all();
|
|
1149
|
-
}
|
|
1150
1352
|
async function addMessage(conversationId, role, senderName, content) {
|
|
1151
1353
|
const db2 = await getDb();
|
|
1152
1354
|
const serialized = JSON.stringify(content);
|
|
@@ -1182,6 +1384,34 @@ async function getMessages(conversationId) {
|
|
|
1182
1384
|
return { ...row, content };
|
|
1183
1385
|
});
|
|
1184
1386
|
}
|
|
1387
|
+
async function listConversationsWithParticipants(userId) {
|
|
1388
|
+
const convs = await listConversationsForUser(userId);
|
|
1389
|
+
if (convs.length === 0) return [];
|
|
1390
|
+
const db2 = await getDb();
|
|
1391
|
+
const convIds = convs.map((c) => c.id);
|
|
1392
|
+
const rows = await db2.select({
|
|
1393
|
+
conversationId: conversationParticipants.conversation_id,
|
|
1394
|
+
userId: users.id,
|
|
1395
|
+
username: users.username,
|
|
1396
|
+
userType: users.user_type,
|
|
1397
|
+
role: conversationParticipants.role
|
|
1398
|
+
}).from(conversationParticipants).innerJoin(users, eq4(conversationParticipants.user_id, users.id)).where(inArray(conversationParticipants.conversation_id, convIds));
|
|
1399
|
+
const byConv = /* @__PURE__ */ new Map();
|
|
1400
|
+
for (const r of rows) {
|
|
1401
|
+
let arr = byConv.get(r.conversationId);
|
|
1402
|
+
if (!arr) {
|
|
1403
|
+
arr = [];
|
|
1404
|
+
byConv.set(r.conversationId, arr);
|
|
1405
|
+
}
|
|
1406
|
+
arr.push({
|
|
1407
|
+
userId: r.userId,
|
|
1408
|
+
username: r.username,
|
|
1409
|
+
userType: r.userType,
|
|
1410
|
+
role: r.role
|
|
1411
|
+
});
|
|
1412
|
+
}
|
|
1413
|
+
return convs.map((c) => ({ ...c, participants: byConv.get(c.id) ?? [] }));
|
|
1414
|
+
}
|
|
1185
1415
|
async function deleteConversation(id) {
|
|
1186
1416
|
const db2 = await getDb();
|
|
1187
1417
|
await db2.delete(conversations).where(eq4(conversations.id, id));
|
|
@@ -1191,6 +1421,7 @@ async function deleteConversation(id) {
|
|
|
1191
1421
|
var chatSchema = z2.object({
|
|
1192
1422
|
message: z2.string().optional(),
|
|
1193
1423
|
conversationId: z2.string().optional(),
|
|
1424
|
+
sender: z2.string().optional(),
|
|
1194
1425
|
images: z2.array(
|
|
1195
1426
|
z2.object({
|
|
1196
1427
|
media_type: z2.string(),
|
|
@@ -1198,18 +1429,59 @@ var chatSchema = z2.object({
|
|
|
1198
1429
|
})
|
|
1199
1430
|
).optional()
|
|
1200
1431
|
});
|
|
1432
|
+
function getDaemonUrl() {
|
|
1433
|
+
const data = JSON.parse(readFileSync4(resolve6(voluteHome(), "daemon.json"), "utf-8"));
|
|
1434
|
+
return `http://127.0.0.1:${data.port}`;
|
|
1435
|
+
}
|
|
1436
|
+
function daemonFetchInternal(path, body) {
|
|
1437
|
+
const daemonUrl = getDaemonUrl();
|
|
1438
|
+
const token = process.env.VOLUTE_DAEMON_TOKEN;
|
|
1439
|
+
const headers = {
|
|
1440
|
+
"Content-Type": "application/json",
|
|
1441
|
+
Origin: daemonUrl
|
|
1442
|
+
};
|
|
1443
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
1444
|
+
return fetch(`${daemonUrl}${path}`, { method: "POST", headers, body });
|
|
1445
|
+
}
|
|
1446
|
+
function accumulateEvent(content, event) {
|
|
1447
|
+
if (event.type === "text") {
|
|
1448
|
+
const last = content[content.length - 1];
|
|
1449
|
+
if (last && last.type === "text") last.text += event.content;
|
|
1450
|
+
else content.push({ type: "text", text: event.content });
|
|
1451
|
+
} else if (event.type === "tool_use") {
|
|
1452
|
+
content.push({ type: "tool_use", name: event.name, input: event.input });
|
|
1453
|
+
} else if (event.type === "tool_result") {
|
|
1454
|
+
content.push({
|
|
1455
|
+
type: "tool_result",
|
|
1456
|
+
output: event.output,
|
|
1457
|
+
...event.is_error ? { is_error: true } : {}
|
|
1458
|
+
});
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
async function consumeAndPersist(res, conversationId, agentName) {
|
|
1462
|
+
if (!res.body) {
|
|
1463
|
+
console.warn(`[chat] no response body from ${agentName}`);
|
|
1464
|
+
return [];
|
|
1465
|
+
}
|
|
1466
|
+
const assistantContent = [];
|
|
1467
|
+
for await (const event of readNdjson(res.body)) {
|
|
1468
|
+
accumulateEvent(assistantContent, event);
|
|
1469
|
+
if (event.type === "done") break;
|
|
1470
|
+
}
|
|
1471
|
+
if (assistantContent.length === 0) return [];
|
|
1472
|
+
try {
|
|
1473
|
+
await addMessage(conversationId, "assistant", agentName, assistantContent);
|
|
1474
|
+
} catch (err) {
|
|
1475
|
+
console.error(`[chat] failed to persist conversation message from ${agentName}:`, err);
|
|
1476
|
+
}
|
|
1477
|
+
return assistantContent;
|
|
1478
|
+
}
|
|
1201
1479
|
var app3 = new Hono3().post("/:name/chat", zValidator2("json", chatSchema), async (c) => {
|
|
1202
1480
|
const name = c.req.param("name");
|
|
1203
|
-
const [baseName
|
|
1481
|
+
const [baseName] = name.split("@", 2);
|
|
1204
1482
|
const entry = findAgent(baseName);
|
|
1205
1483
|
if (!entry) return c.json({ error: "Agent not found" }, 404);
|
|
1206
|
-
|
|
1207
|
-
if (variantName) {
|
|
1208
|
-
const variant = findVariant(baseName, variantName);
|
|
1209
|
-
if (!variant) return c.json({ error: `Unknown variant: ${variantName}` }, 404);
|
|
1210
|
-
port = variant.port;
|
|
1211
|
-
}
|
|
1212
|
-
const { getAgentManager: getAgentManager2 } = await import("./agent-manager-AUCKMGPR.js");
|
|
1484
|
+
const { getAgentManager: getAgentManager2 } = await import("./agent-manager-PXBKA2GK.js");
|
|
1213
1485
|
if (!getAgentManager2().isRunning(name)) {
|
|
1214
1486
|
return c.json({ error: "Agent is not running" }, 409);
|
|
1215
1487
|
}
|
|
@@ -1218,18 +1490,34 @@ var app3 = new Hono3().post("/:name/chat", zValidator2("json", chatSchema), asyn
|
|
|
1218
1490
|
return c.json({ error: "message or images required" }, 400);
|
|
1219
1491
|
}
|
|
1220
1492
|
const user = c.get("user");
|
|
1493
|
+
const agentUser = await getOrCreateAgentUser(baseName);
|
|
1494
|
+
const senderName = user.id === 0 && body.sender ? body.sender : user.username;
|
|
1221
1495
|
let conversationId = body.conversationId;
|
|
1222
1496
|
if (conversationId) {
|
|
1223
|
-
|
|
1224
|
-
|
|
1497
|
+
if (user.id !== 0 && !await isParticipantOrOwner(conversationId, user.id)) {
|
|
1498
|
+
return c.json({ error: "Conversation not found" }, 404);
|
|
1499
|
+
}
|
|
1225
1500
|
} else {
|
|
1226
1501
|
const title = body.message ? body.message.slice(0, 80) : "Image message";
|
|
1227
|
-
const
|
|
1228
|
-
|
|
1229
|
-
|
|
1502
|
+
const participantIds = [];
|
|
1503
|
+
if (user.id !== 0) {
|
|
1504
|
+
participantIds.push(user.id);
|
|
1505
|
+
} else if (body.sender) {
|
|
1506
|
+
const senderAgent = findAgent(body.sender);
|
|
1507
|
+
if (senderAgent) {
|
|
1508
|
+
const senderAgentUser = await getOrCreateAgentUser(body.sender);
|
|
1509
|
+
participantIds.push(senderAgentUser.id);
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
participantIds.push(agentUser.id);
|
|
1513
|
+
const conv = await createConversation(baseName, "volute", {
|
|
1514
|
+
userId: user.id !== 0 ? user.id : void 0,
|
|
1515
|
+
title,
|
|
1516
|
+
participantIds
|
|
1230
1517
|
});
|
|
1231
1518
|
conversationId = conv.id;
|
|
1232
1519
|
}
|
|
1520
|
+
const channel = `volute:${conversationId}`;
|
|
1233
1521
|
const contentBlocks = [];
|
|
1234
1522
|
if (body.message) {
|
|
1235
1523
|
contentBlocks.push({ type: "text", text: body.message });
|
|
@@ -1239,89 +1527,87 @@ var app3 = new Hono3().post("/:name/chat", zValidator2("json", chatSchema), asyn
|
|
|
1239
1527
|
contentBlocks.push({ type: "image", media_type: img.media_type, data: img.data });
|
|
1240
1528
|
}
|
|
1241
1529
|
}
|
|
1242
|
-
await addMessage(conversationId, "user",
|
|
1243
|
-
const
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1530
|
+
await addMessage(conversationId, "user", senderName, contentBlocks);
|
|
1531
|
+
const participants = await getParticipants(conversationId);
|
|
1532
|
+
const agentParticipants = participants.filter((p) => p.userType === "agent");
|
|
1533
|
+
const participantNames = participants.map((p) => p.username);
|
|
1534
|
+
const manager = getAgentManager2();
|
|
1535
|
+
const runningAgents = agentParticipants.map((ap) => {
|
|
1536
|
+
const agentKey = ap.username === baseName ? name : ap.username;
|
|
1537
|
+
return manager.isRunning(agentKey) ? ap.username : null;
|
|
1538
|
+
}).filter((n) => n !== null && n !== senderName);
|
|
1539
|
+
if (runningAgents.length === 0) {
|
|
1540
|
+
return c.json({ error: "No running agents in this conversation" }, 409);
|
|
1541
|
+
}
|
|
1542
|
+
const isDM = participants.length === 2;
|
|
1543
|
+
const payload = JSON.stringify({
|
|
1544
|
+
content: contentBlocks,
|
|
1545
|
+
channel,
|
|
1546
|
+
sender: senderName,
|
|
1547
|
+
participants: participantNames,
|
|
1548
|
+
participantCount: participants.length,
|
|
1549
|
+
isDM
|
|
1250
1550
|
});
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1551
|
+
const responses = [];
|
|
1552
|
+
for (const agentName of runningAgents) {
|
|
1553
|
+
const targetName = agentName === baseName ? name : agentName;
|
|
1554
|
+
try {
|
|
1555
|
+
const res = await daemonFetchInternal(
|
|
1556
|
+
`/api/agents/${encodeURIComponent(targetName)}/message`,
|
|
1557
|
+
payload
|
|
1558
|
+
);
|
|
1559
|
+
if (res.ok && res.body) {
|
|
1560
|
+
responses.push({ name: agentName, res });
|
|
1561
|
+
} else {
|
|
1562
|
+
const errorBody = await res.text().catch(() => "");
|
|
1563
|
+
console.error(
|
|
1564
|
+
`[chat] agent ${agentName} responded with ${res.status}: ${errorBody.slice(0, 500)}`
|
|
1565
|
+
);
|
|
1566
|
+
}
|
|
1567
|
+
} catch (err) {
|
|
1568
|
+
console.error(`[chat] agent ${agentName} unreachable via daemon:`, err);
|
|
1569
|
+
}
|
|
1268
1570
|
}
|
|
1269
|
-
if (
|
|
1270
|
-
return c.json({ error: "No
|
|
1571
|
+
if (responses.length === 0) {
|
|
1572
|
+
return c.json({ error: "No agents reachable" }, 502);
|
|
1271
1573
|
}
|
|
1574
|
+
const primary = responses[0];
|
|
1575
|
+
const secondary = responses.slice(1);
|
|
1576
|
+
const secondaryPromises = secondary.map((s) => consumeAndPersist(s.res, conversationId, s.name));
|
|
1272
1577
|
return streamSSE(c, async (stream2) => {
|
|
1273
1578
|
await stream2.writeSSE({
|
|
1274
|
-
data: JSON.stringify({ type: "meta", conversationId })
|
|
1579
|
+
data: JSON.stringify({ type: "meta", conversationId, senderName: primary.name })
|
|
1275
1580
|
});
|
|
1276
1581
|
const assistantContent = [];
|
|
1277
|
-
|
|
1278
|
-
await
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
if (
|
|
1282
|
-
last.text += event.content;
|
|
1283
|
-
} else {
|
|
1284
|
-
assistantContent.push({ type: "text", text: event.content });
|
|
1285
|
-
}
|
|
1286
|
-
} else if (event.type === "tool_use") {
|
|
1287
|
-
assistantContent.push({
|
|
1288
|
-
type: "tool_use",
|
|
1289
|
-
name: event.name,
|
|
1290
|
-
input: event.input
|
|
1291
|
-
});
|
|
1292
|
-
} else if (event.type === "tool_result") {
|
|
1293
|
-
assistantContent.push({
|
|
1294
|
-
type: "tool_result",
|
|
1295
|
-
output: event.output,
|
|
1296
|
-
...event.is_error ? { is_error: true } : {}
|
|
1297
|
-
});
|
|
1582
|
+
try {
|
|
1583
|
+
for await (const event of readNdjson(primary.res.body)) {
|
|
1584
|
+
await stream2.writeSSE({ data: JSON.stringify(event) });
|
|
1585
|
+
accumulateEvent(assistantContent, event);
|
|
1586
|
+
if (event.type === "done") break;
|
|
1298
1587
|
}
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
}
|
|
1311
|
-
const summary = [textParts.join(""), ...toolParts].filter(Boolean).join("\n");
|
|
1312
|
-
if (summary) {
|
|
1313
|
-
await db2.insert(agentMessages).values({
|
|
1314
|
-
agent: baseName,
|
|
1315
|
-
channel: "web",
|
|
1316
|
-
role: "assistant",
|
|
1317
|
-
sender: baseName,
|
|
1318
|
-
content: summary
|
|
1319
|
-
});
|
|
1320
|
-
}
|
|
1321
|
-
}
|
|
1322
|
-
break;
|
|
1588
|
+
} catch (err) {
|
|
1589
|
+
console.error(`[chat] error streaming response from ${primary.name}:`, err);
|
|
1590
|
+
await stream2.writeSSE({
|
|
1591
|
+
data: JSON.stringify({ type: "error", message: "Stream interrupted" })
|
|
1592
|
+
});
|
|
1593
|
+
}
|
|
1594
|
+
if (assistantContent.length > 0) {
|
|
1595
|
+
try {
|
|
1596
|
+
await addMessage(conversationId, "assistant", primary.name, assistantContent);
|
|
1597
|
+
} catch (err) {
|
|
1598
|
+
console.error(`[chat] failed to persist response from ${primary.name}:`, err);
|
|
1323
1599
|
}
|
|
1324
1600
|
}
|
|
1601
|
+
const results = await Promise.allSettled(secondaryPromises);
|
|
1602
|
+
for (let i = 0; i < results.length; i++) {
|
|
1603
|
+
if (results[i].status === "rejected") {
|
|
1604
|
+
console.error(
|
|
1605
|
+
`[chat] secondary agent ${secondary[i].name} response failed:`,
|
|
1606
|
+
results[i].reason
|
|
1607
|
+
);
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
await stream2.writeSSE({ data: JSON.stringify({ type: "sync" }) });
|
|
1325
1611
|
});
|
|
1326
1612
|
});
|
|
1327
1613
|
var chat_default = app3;
|
|
@@ -1398,19 +1684,79 @@ var app4 = new Hono4().get("/:name/connectors", (c) => {
|
|
|
1398
1684
|
var connectors_default = app4;
|
|
1399
1685
|
|
|
1400
1686
|
// src/web/routes/conversations.ts
|
|
1687
|
+
import { zValidator as zValidator3 } from "@hono/zod-validator";
|
|
1401
1688
|
import { Hono as Hono5 } from "hono";
|
|
1689
|
+
import { z as z3 } from "zod";
|
|
1690
|
+
var createConvSchema = z3.object({
|
|
1691
|
+
title: z3.string().optional(),
|
|
1692
|
+
participantIds: z3.array(z3.number()).optional(),
|
|
1693
|
+
participantNames: z3.array(z3.string()).optional()
|
|
1694
|
+
});
|
|
1402
1695
|
var app5 = new Hono5().get("/:name/conversations", async (c) => {
|
|
1403
1696
|
const name = c.req.param("name");
|
|
1404
1697
|
const user = c.get("user");
|
|
1405
|
-
|
|
1698
|
+
let lookupId = user.id;
|
|
1699
|
+
if (user.id === 0) {
|
|
1700
|
+
const agentUser = await getOrCreateAgentUser(name);
|
|
1701
|
+
lookupId = agentUser.id;
|
|
1702
|
+
}
|
|
1703
|
+
const all = await listConversationsForUser(lookupId);
|
|
1704
|
+
const convs = all.filter((c2) => c2.agent_name === name);
|
|
1406
1705
|
return c.json(convs);
|
|
1706
|
+
}).post("/:name/conversations", zValidator3("json", createConvSchema), async (c) => {
|
|
1707
|
+
const name = c.req.param("name");
|
|
1708
|
+
const user = c.get("user");
|
|
1709
|
+
const body = c.req.valid("json");
|
|
1710
|
+
if (!body.participantIds?.length && !body.participantNames?.length) {
|
|
1711
|
+
return c.json({ error: "participantIds or participantNames required" }, 400);
|
|
1712
|
+
}
|
|
1713
|
+
const agentUser = await getOrCreateAgentUser(name);
|
|
1714
|
+
const participantSet = /* @__PURE__ */ new Set();
|
|
1715
|
+
if (user.id !== 0) participantSet.add(user.id);
|
|
1716
|
+
participantSet.add(agentUser.id);
|
|
1717
|
+
for (const id of body.participantIds ?? []) participantSet.add(id);
|
|
1718
|
+
if (body.participantNames) {
|
|
1719
|
+
for (const pname of body.participantNames) {
|
|
1720
|
+
const existing = await getUserByUsername(pname);
|
|
1721
|
+
if (existing) {
|
|
1722
|
+
participantSet.add(existing.id);
|
|
1723
|
+
continue;
|
|
1724
|
+
}
|
|
1725
|
+
if (findAgent(pname)) {
|
|
1726
|
+
const au = await getOrCreateAgentUser(pname);
|
|
1727
|
+
participantSet.add(au.id);
|
|
1728
|
+
continue;
|
|
1729
|
+
}
|
|
1730
|
+
return c.json({ error: `User not found: ${pname}` }, 400);
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
for (const id of participantSet) {
|
|
1734
|
+
if (id === user.id || id === agentUser.id) continue;
|
|
1735
|
+
const u = await getUser(id);
|
|
1736
|
+
if (!u) return c.json({ error: `User ${id} not found` }, 400);
|
|
1737
|
+
}
|
|
1738
|
+
const conv = await createConversation(name, "volute", {
|
|
1739
|
+
userId: user.id !== 0 ? user.id : void 0,
|
|
1740
|
+
title: body.title,
|
|
1741
|
+
participantIds: [...participantSet]
|
|
1742
|
+
});
|
|
1743
|
+
return c.json(conv, 201);
|
|
1407
1744
|
}).get("/:name/conversations/:id/messages", async (c) => {
|
|
1408
1745
|
const id = c.req.param("id");
|
|
1409
1746
|
const user = c.get("user");
|
|
1410
|
-
|
|
1411
|
-
|
|
1747
|
+
if (user.id !== 0 && !await isParticipantOrOwner(id, user.id)) {
|
|
1748
|
+
return c.json({ error: "Conversation not found" }, 404);
|
|
1749
|
+
}
|
|
1412
1750
|
const msgs = await getMessages(id);
|
|
1413
1751
|
return c.json(msgs);
|
|
1752
|
+
}).get("/:name/conversations/:id/participants", async (c) => {
|
|
1753
|
+
const id = c.req.param("id");
|
|
1754
|
+
const user = c.get("user");
|
|
1755
|
+
if (!await isParticipantOrOwner(id, user.id)) {
|
|
1756
|
+
return c.json({ error: "Conversation not found" }, 404);
|
|
1757
|
+
}
|
|
1758
|
+
const participants = await getParticipants(id);
|
|
1759
|
+
return c.json(participants);
|
|
1414
1760
|
}).delete("/:name/conversations/:id", async (c) => {
|
|
1415
1761
|
const id = c.req.param("id");
|
|
1416
1762
|
const user = c.get("user");
|
|
@@ -1423,18 +1769,18 @@ var conversations_default = app5;
|
|
|
1423
1769
|
// src/web/routes/files.ts
|
|
1424
1770
|
import { existsSync as existsSync5 } from "fs";
|
|
1425
1771
|
import { readdir, readFile, writeFile } from "fs/promises";
|
|
1426
|
-
import { resolve as
|
|
1427
|
-
import { zValidator as
|
|
1772
|
+
import { resolve as resolve7 } from "path";
|
|
1773
|
+
import { zValidator as zValidator4 } from "@hono/zod-validator";
|
|
1428
1774
|
import { Hono as Hono6 } from "hono";
|
|
1429
|
-
import { z as
|
|
1775
|
+
import { z as z4 } from "zod";
|
|
1430
1776
|
var ALLOWED_FILES = /* @__PURE__ */ new Set(["SOUL.md", "MEMORY.md", "CLAUDE.md", "VOLUTE.md"]);
|
|
1431
|
-
var saveFileSchema =
|
|
1777
|
+
var saveFileSchema = z4.object({ content: z4.string() });
|
|
1432
1778
|
var app6 = new Hono6().get("/:name/files", async (c) => {
|
|
1433
1779
|
const name = c.req.param("name");
|
|
1434
1780
|
const entry = findAgent(name);
|
|
1435
1781
|
if (!entry) return c.json({ error: "Agent not found" }, 404);
|
|
1436
1782
|
const dir = agentDir(name);
|
|
1437
|
-
const homeDir =
|
|
1783
|
+
const homeDir = resolve7(dir, "home");
|
|
1438
1784
|
if (!existsSync5(homeDir)) return c.json({ error: "Home directory missing" }, 404);
|
|
1439
1785
|
const allFiles = await readdir(homeDir);
|
|
1440
1786
|
const files = allFiles.filter((f) => f.endsWith(".md") && ALLOWED_FILES.has(f));
|
|
@@ -1448,13 +1794,13 @@ var app6 = new Hono6().get("/:name/files", async (c) => {
|
|
|
1448
1794
|
const entry = findAgent(name);
|
|
1449
1795
|
if (!entry) return c.json({ error: "Agent not found" }, 404);
|
|
1450
1796
|
const dir = agentDir(name);
|
|
1451
|
-
const filePath =
|
|
1797
|
+
const filePath = resolve7(dir, "home", filename);
|
|
1452
1798
|
if (!existsSync5(filePath)) {
|
|
1453
1799
|
return c.json({ error: "File not found" }, 404);
|
|
1454
1800
|
}
|
|
1455
1801
|
const content = await readFile(filePath, "utf-8");
|
|
1456
1802
|
return c.json({ filename, content });
|
|
1457
|
-
}).put("/:name/files/:filename",
|
|
1803
|
+
}).put("/:name/files/:filename", zValidator4("json", saveFileSchema), async (c) => {
|
|
1458
1804
|
const name = c.req.param("name");
|
|
1459
1805
|
const filename = c.req.param("filename");
|
|
1460
1806
|
if (!ALLOWED_FILES.has(filename)) {
|
|
@@ -1463,7 +1809,7 @@ var app6 = new Hono6().get("/:name/files", async (c) => {
|
|
|
1463
1809
|
const entry = findAgent(name);
|
|
1464
1810
|
if (!entry) return c.json({ error: "Agent not found" }, 404);
|
|
1465
1811
|
const dir = agentDir(name);
|
|
1466
|
-
const filePath =
|
|
1812
|
+
const filePath = resolve7(dir, "home", filename);
|
|
1467
1813
|
const { content } = c.req.valid("json");
|
|
1468
1814
|
await writeFile(filePath, content);
|
|
1469
1815
|
return c.json({ ok: true });
|
|
@@ -1473,7 +1819,7 @@ var files_default = app6;
|
|
|
1473
1819
|
// src/web/routes/logs.ts
|
|
1474
1820
|
import { spawn as spawn2 } from "child_process";
|
|
1475
1821
|
import { existsSync as existsSync6 } from "fs";
|
|
1476
|
-
import { resolve as
|
|
1822
|
+
import { resolve as resolve8 } from "path";
|
|
1477
1823
|
import { Hono as Hono7 } from "hono";
|
|
1478
1824
|
import { streamSSE as streamSSE2 } from "hono/streaming";
|
|
1479
1825
|
var app7 = new Hono7().get("/:name/logs", async (c) => {
|
|
@@ -1481,7 +1827,7 @@ var app7 = new Hono7().get("/:name/logs", async (c) => {
|
|
|
1481
1827
|
const entry = findAgent(name);
|
|
1482
1828
|
if (!entry) return c.json({ error: "Agent not found" }, 404);
|
|
1483
1829
|
const dir = agentDir(name);
|
|
1484
|
-
const logFile =
|
|
1830
|
+
const logFile = resolve8(dir, ".volute", "logs", "agent.log");
|
|
1485
1831
|
if (!existsSync6(logFile)) {
|
|
1486
1832
|
return c.json({ error: "No log file found" }, 404);
|
|
1487
1833
|
}
|
|
@@ -1500,9 +1846,9 @@ var app7 = new Hono7().get("/:name/logs", async (c) => {
|
|
|
1500
1846
|
stream2.onAbort(() => {
|
|
1501
1847
|
tail.kill();
|
|
1502
1848
|
});
|
|
1503
|
-
await new Promise((
|
|
1504
|
-
tail.on("exit",
|
|
1505
|
-
stream2.onAbort(
|
|
1849
|
+
await new Promise((resolve11) => {
|
|
1850
|
+
tail.on("exit", resolve11);
|
|
1851
|
+
stream2.onAbort(resolve11);
|
|
1506
1852
|
});
|
|
1507
1853
|
});
|
|
1508
1854
|
});
|
|
@@ -1604,19 +1950,99 @@ var app9 = new Hono9().get("/logs", async (c) => {
|
|
|
1604
1950
|
stream2.writeSSE({ data: JSON.stringify(entry) }).catch(() => {
|
|
1605
1951
|
});
|
|
1606
1952
|
});
|
|
1607
|
-
await new Promise((
|
|
1953
|
+
await new Promise((resolve11) => {
|
|
1608
1954
|
stream2.onAbort(() => {
|
|
1609
1955
|
unsubscribe();
|
|
1610
|
-
|
|
1956
|
+
resolve11();
|
|
1611
1957
|
});
|
|
1612
1958
|
});
|
|
1613
1959
|
});
|
|
1614
1960
|
});
|
|
1615
1961
|
var system_default = app9;
|
|
1616
1962
|
|
|
1617
|
-
// src/web/routes/
|
|
1963
|
+
// src/web/routes/update.ts
|
|
1964
|
+
import { spawn as spawn3 } from "child_process";
|
|
1618
1965
|
import { Hono as Hono10 } from "hono";
|
|
1619
|
-
var
|
|
1966
|
+
var bin;
|
|
1967
|
+
var app10 = new Hono10().get("/update", async (c) => {
|
|
1968
|
+
const result = await checkForUpdate();
|
|
1969
|
+
return c.json(result);
|
|
1970
|
+
}).post("/update", requireAdmin, async (c) => {
|
|
1971
|
+
bin ??= resolveVoluteBin();
|
|
1972
|
+
const child = spawn3(bin, ["update"], {
|
|
1973
|
+
stdio: "ignore",
|
|
1974
|
+
detached: true
|
|
1975
|
+
});
|
|
1976
|
+
child.on("error", (err) => {
|
|
1977
|
+
logger_default.error("Update process error", { error: err.message });
|
|
1978
|
+
});
|
|
1979
|
+
child.unref();
|
|
1980
|
+
return c.json({ ok: true, message: "Updating..." });
|
|
1981
|
+
});
|
|
1982
|
+
var update_default = app10;
|
|
1983
|
+
|
|
1984
|
+
// src/web/routes/user-conversations.ts
|
|
1985
|
+
import { zValidator as zValidator5 } from "@hono/zod-validator";
|
|
1986
|
+
import { Hono as Hono11 } from "hono";
|
|
1987
|
+
import { z as z5 } from "zod";
|
|
1988
|
+
var createSchema = z5.object({
|
|
1989
|
+
title: z5.string().optional(),
|
|
1990
|
+
participantNames: z5.array(z5.string()).min(1)
|
|
1991
|
+
});
|
|
1992
|
+
var app11 = new Hono11().use("*", authMiddleware).get("/", async (c) => {
|
|
1993
|
+
const user = c.get("user");
|
|
1994
|
+
const convs = await listConversationsWithParticipants(user.id);
|
|
1995
|
+
return c.json(convs);
|
|
1996
|
+
}).get("/:id/messages", async (c) => {
|
|
1997
|
+
const id = c.req.param("id");
|
|
1998
|
+
const user = c.get("user");
|
|
1999
|
+
if (user.id !== 0 && !await isParticipantOrOwner(id, user.id)) {
|
|
2000
|
+
return c.json({ error: "Conversation not found" }, 404);
|
|
2001
|
+
}
|
|
2002
|
+
const msgs = await getMessages(id);
|
|
2003
|
+
return c.json(msgs);
|
|
2004
|
+
}).post("/", zValidator5("json", createSchema), async (c) => {
|
|
2005
|
+
const user = c.get("user");
|
|
2006
|
+
const body = c.req.valid("json");
|
|
2007
|
+
const participantIds = /* @__PURE__ */ new Set();
|
|
2008
|
+
if (user.id !== 0) participantIds.add(user.id);
|
|
2009
|
+
let firstAgentName;
|
|
2010
|
+
for (const name of body.participantNames) {
|
|
2011
|
+
const existing = await getUserByUsername(name);
|
|
2012
|
+
if (existing) {
|
|
2013
|
+
participantIds.add(existing.id);
|
|
2014
|
+
if (!firstAgentName && existing.user_type === "agent") firstAgentName = name;
|
|
2015
|
+
continue;
|
|
2016
|
+
}
|
|
2017
|
+
if (findAgent(name)) {
|
|
2018
|
+
const au = await getOrCreateAgentUser(name);
|
|
2019
|
+
participantIds.add(au.id);
|
|
2020
|
+
if (!firstAgentName) firstAgentName = name;
|
|
2021
|
+
continue;
|
|
2022
|
+
}
|
|
2023
|
+
return c.json({ error: `User not found: ${name}` }, 400);
|
|
2024
|
+
}
|
|
2025
|
+
if (!firstAgentName) {
|
|
2026
|
+
return c.json({ error: "At least one agent participant is required" }, 400);
|
|
2027
|
+
}
|
|
2028
|
+
const conv = await createConversation(firstAgentName, "volute", {
|
|
2029
|
+
userId: user.id !== 0 ? user.id : void 0,
|
|
2030
|
+
title: body.title,
|
|
2031
|
+
participantIds: [...participantIds]
|
|
2032
|
+
});
|
|
2033
|
+
return c.json(conv, 201);
|
|
2034
|
+
}).delete("/:id", async (c) => {
|
|
2035
|
+
const id = c.req.param("id");
|
|
2036
|
+
const user = c.get("user");
|
|
2037
|
+
const deleted = await deleteConversationForUser(id, user.id);
|
|
2038
|
+
if (!deleted) return c.json({ error: "Conversation not found" }, 404);
|
|
2039
|
+
return c.json({ ok: true });
|
|
2040
|
+
});
|
|
2041
|
+
var user_conversations_default = app11;
|
|
2042
|
+
|
|
2043
|
+
// src/web/routes/variants.ts
|
|
2044
|
+
import { Hono as Hono12 } from "hono";
|
|
2045
|
+
var app12 = new Hono12().get("/:name/variants", async (c) => {
|
|
1620
2046
|
const name = c.req.param("name");
|
|
1621
2047
|
const entry = findAgent(name);
|
|
1622
2048
|
if (!entry) return c.json({ error: "Agent not found" }, 404);
|
|
@@ -1630,11 +2056,11 @@ var app10 = new Hono10().get("/:name/variants", async (c) => {
|
|
|
1630
2056
|
);
|
|
1631
2057
|
return c.json(results);
|
|
1632
2058
|
});
|
|
1633
|
-
var variants_default =
|
|
2059
|
+
var variants_default = app12;
|
|
1634
2060
|
|
|
1635
2061
|
// src/web/app.ts
|
|
1636
|
-
var
|
|
1637
|
-
|
|
2062
|
+
var app13 = new Hono13();
|
|
2063
|
+
app13.onError((err, c) => {
|
|
1638
2064
|
if (err instanceof HTTPException) {
|
|
1639
2065
|
return err.getResponse();
|
|
1640
2066
|
}
|
|
@@ -1645,10 +2071,10 @@ app11.onError((err, c) => {
|
|
|
1645
2071
|
});
|
|
1646
2072
|
return c.json({ error: "Internal server error" }, 500);
|
|
1647
2073
|
});
|
|
1648
|
-
|
|
2074
|
+
app13.notFound((c) => {
|
|
1649
2075
|
return c.json({ error: "Not found" }, 404);
|
|
1650
2076
|
});
|
|
1651
|
-
|
|
2077
|
+
app13.use("*", async (c, next) => {
|
|
1652
2078
|
const start = Date.now();
|
|
1653
2079
|
await next();
|
|
1654
2080
|
const duration = Date.now() - start;
|
|
@@ -1659,15 +2085,28 @@ app11.use("*", async (c, next) => {
|
|
|
1659
2085
|
duration
|
|
1660
2086
|
});
|
|
1661
2087
|
});
|
|
1662
|
-
|
|
1663
|
-
|
|
2088
|
+
app13.get("/api/health", (c) => {
|
|
2089
|
+
let version = "unknown";
|
|
2090
|
+
let cached = null;
|
|
2091
|
+
try {
|
|
2092
|
+
version = getCurrentVersion();
|
|
2093
|
+
cached = checkForUpdateCached();
|
|
2094
|
+
} catch (err) {
|
|
2095
|
+
logger_default.error("Health check error", { error: err.message });
|
|
2096
|
+
}
|
|
2097
|
+
return c.json({
|
|
2098
|
+
ok: true,
|
|
2099
|
+
version,
|
|
2100
|
+
...cached?.updateAvailable ? { updateAvailable: true, latest: cached.latest } : {}
|
|
2101
|
+
});
|
|
1664
2102
|
});
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
var
|
|
2103
|
+
app13.use("/api/*", bodyLimit({ maxSize: 10 * 1024 * 1024 }));
|
|
2104
|
+
app13.use("/api/*", csrf());
|
|
2105
|
+
app13.use("/api/agents/*", authMiddleware);
|
|
2106
|
+
app13.use("/api/conversations/*", authMiddleware);
|
|
2107
|
+
app13.use("/api/system/*", authMiddleware);
|
|
2108
|
+
var routes = app13.route("/api/auth", auth_default).route("/api/system", system_default).route("/api/system", update_default).route("/api/agents", agents_default).route("/api/agents", chat_default).route("/api/agents", connectors_default).route("/api/agents", schedules_default).route("/api/agents", logs_default).route("/api/agents", variants_default).route("/api/agents", files_default).route("/api/agents", conversations_default).route("/api/conversations", user_conversations_default);
|
|
2109
|
+
var app_default = app13;
|
|
1671
2110
|
|
|
1672
2111
|
// src/web/server.ts
|
|
1673
2112
|
var MIME_TYPES = {
|
|
@@ -1686,7 +2125,7 @@ async function startServer({
|
|
|
1686
2125
|
let assetsDir = "";
|
|
1687
2126
|
let searchDir = dirname3(new URL(import.meta.url).pathname);
|
|
1688
2127
|
for (let i = 0; i < 5; i++) {
|
|
1689
|
-
const candidate =
|
|
2128
|
+
const candidate = resolve9(searchDir, "dist", "web-assets");
|
|
1690
2129
|
if (existsSync7(candidate)) {
|
|
1691
2130
|
assetsDir = candidate;
|
|
1692
2131
|
break;
|
|
@@ -1696,7 +2135,8 @@ async function startServer({
|
|
|
1696
2135
|
if (assetsDir) {
|
|
1697
2136
|
app_default.get("*", async (c) => {
|
|
1698
2137
|
const urlPath = new URL(c.req.url).pathname;
|
|
1699
|
-
|
|
2138
|
+
if (urlPath.startsWith("/api/")) return c.notFound();
|
|
2139
|
+
const filePath = resolve9(assetsDir, urlPath.slice(1));
|
|
1700
2140
|
if (!filePath.startsWith(assetsDir)) return c.text("Forbidden", 403);
|
|
1701
2141
|
const s = await stat(filePath).catch(() => null);
|
|
1702
2142
|
if (s?.isFile()) {
|
|
@@ -1705,7 +2145,7 @@ async function startServer({
|
|
|
1705
2145
|
const body = await readFile2(filePath);
|
|
1706
2146
|
return c.body(body, 200, { "Content-Type": mime });
|
|
1707
2147
|
}
|
|
1708
|
-
const indexPath =
|
|
2148
|
+
const indexPath = resolve9(assetsDir, "index.html");
|
|
1709
2149
|
const indexStat = await stat(indexPath).catch(() => null);
|
|
1710
2150
|
if (indexStat?.isFile()) {
|
|
1711
2151
|
const body = await readFile2(indexPath, "utf-8");
|
|
@@ -1715,10 +2155,10 @@ async function startServer({
|
|
|
1715
2155
|
});
|
|
1716
2156
|
}
|
|
1717
2157
|
const server = serve({ fetch: app_default.fetch, port, hostname });
|
|
1718
|
-
await new Promise((
|
|
2158
|
+
await new Promise((resolve11, reject) => {
|
|
1719
2159
|
server.on("listening", () => {
|
|
1720
2160
|
logger_default.info("Volute UI running", { hostname, port });
|
|
1721
|
-
|
|
2161
|
+
resolve11();
|
|
1722
2162
|
});
|
|
1723
2163
|
server.on("error", (err) => {
|
|
1724
2164
|
reject(err);
|
|
@@ -1728,12 +2168,24 @@ async function startServer({
|
|
|
1728
2168
|
}
|
|
1729
2169
|
|
|
1730
2170
|
// src/daemon.ts
|
|
2171
|
+
if (!process.env.VOLUTE_HOME) {
|
|
2172
|
+
process.env.VOLUTE_HOME = resolve10(homedir(), ".volute");
|
|
2173
|
+
}
|
|
1731
2174
|
async function startDaemon(opts) {
|
|
1732
2175
|
const { port, hostname } = opts;
|
|
1733
2176
|
const myPid = String(process.pid);
|
|
1734
2177
|
const home = voluteHome();
|
|
1735
|
-
|
|
1736
|
-
|
|
2178
|
+
if (!opts.foreground) {
|
|
2179
|
+
const log2 = new RotatingLog(resolve10(home, "daemon.log"));
|
|
2180
|
+
const write2 = (...args) => log2.write(`${format(...args)}
|
|
2181
|
+
`);
|
|
2182
|
+
console.log = write2;
|
|
2183
|
+
console.error = write2;
|
|
2184
|
+
console.warn = write2;
|
|
2185
|
+
console.info = write2;
|
|
2186
|
+
}
|
|
2187
|
+
const DAEMON_PID_PATH = resolve10(home, "daemon.pid");
|
|
2188
|
+
const DAEMON_JSON_PATH = resolve10(home, "daemon.json");
|
|
1737
2189
|
mkdirSync2(home, { recursive: true });
|
|
1738
2190
|
const token = process.env.VOLUTE_DAEMON_TOKEN || randomBytes(32).toString("hex");
|
|
1739
2191
|
process.env.VOLUTE_DAEMON_TOKEN = token;
|
|
@@ -1786,13 +2238,13 @@ async function startDaemon(opts) {
|
|
|
1786
2238
|
console.error(`[daemon] running on ${hostname}:${port}, pid ${myPid}`);
|
|
1787
2239
|
function cleanup() {
|
|
1788
2240
|
try {
|
|
1789
|
-
if (
|
|
2241
|
+
if (readFileSync5(DAEMON_PID_PATH, "utf-8").trim() === myPid) {
|
|
1790
2242
|
unlinkSync2(DAEMON_PID_PATH);
|
|
1791
2243
|
}
|
|
1792
2244
|
} catch {
|
|
1793
2245
|
}
|
|
1794
2246
|
try {
|
|
1795
|
-
const data = JSON.parse(
|
|
2247
|
+
const data = JSON.parse(readFileSync5(DAEMON_JSON_PATH, "utf-8"));
|
|
1796
2248
|
if (data.token === token) {
|
|
1797
2249
|
unlinkSync2(DAEMON_JSON_PATH);
|
|
1798
2250
|
}
|