volute 0.3.0 → 0.4.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 +7 -7
- package/dist/{agent-manager-2LU6KULR.js → agent-manager-AUCKMGPR.js} +4 -4
- package/dist/{channel-H7N4SGR2.js → channel-DQ6UY7QB.js} +17 -40
- package/dist/{chunk-RALYNMHR.js → chunk-3C2XR4IY.js} +1 -1
- package/dist/chunk-5OCWMTVS.js +152 -0
- package/dist/{chunk-YEIHRP2J.js → chunk-DNOXHLE5.js} +1 -1
- package/dist/{chunk-IPIPLGME.js → chunk-I6OHXCMV.js} +4 -4
- package/dist/chunk-MXUCNIBG.js +168 -0
- package/dist/{chunk-DEUAVGSA.js → chunk-SOZA2TLP.js} +1 -1
- package/dist/{chunk-VVD3XO3E.js → chunk-YGFIWIOF.js} +1 -1
- package/dist/{chunk-N4YNKR3Q.js → chunk-ZHCE4DPY.js} +20 -0
- package/dist/cli.js +36 -24
- package/dist/connector-DKDJTLYZ.js +152 -0
- package/dist/connectors/discord.js +102 -158
- package/dist/connectors/slack.js +170 -0
- package/dist/connectors/telegram.js +156 -0
- package/dist/{create-RSWWMGKT.js → create-ILVOG75A.js} +5 -5
- package/dist/{daemon-client-27KMQQKX.js → daemon-client-XR24PUJF.js} +2 -2
- package/dist/daemon.js +271 -151
- package/dist/{delete-4ERL2QHH.js → delete-55MXCEY5.js} +5 -5
- package/dist/{down-HRC4MQCT.js → down-3OB6UVAJ.js} +1 -1
- package/dist/{env-DBWDTIP6.js → env-JB27UAC3.js} +2 -2
- package/dist/{history-W7BD2H74.js → history-BKG74I43.js} +4 -4
- package/dist/{import-6HTSSDFW.js → import-4CI2ZUTJ.js} +17 -2
- package/dist/{logs-NHWGHNBF.js → logs-NXFFGUKY.js} +1 -1
- package/dist/package-Z2SFO2SV.js +89 -0
- package/dist/{schedule-DKZ2E2CL.js → schedule-A35SH4HT.js} +4 -4
- package/dist/{send-5LEJXPYV.js → send-3U6OTKG7.js} +8 -4
- package/dist/{setup-ZMNTOJAV.js → setup-2FDVN7OF.js} +4 -4
- package/dist/{start-2BSXX6BS.js → start-LDPMCMYT.js} +2 -2
- package/dist/{status-N23CV27T.js → status-MVSQG54T.js} +2 -2
- package/dist/{stop-DSKBIJ2D.js → stop-5PZTZCLL.js} +2 -2
- package/dist/{up-4UGID4DM.js → up-F7TMTLRE.js} +1 -1
- package/dist/{upgrade-BGFVRCVP.js → upgrade-6ZW2RD64.js} +32 -19
- package/dist/{variant-JPLJTS2P.js → variant-T64BKARF.js} +130 -18
- package/dist/web-assets/assets/{index-BC5eSqbY.js → index-NS621maO.js} +23 -23
- package/dist/web-assets/index.html +1 -1
- package/package.json +3 -1
- package/templates/_base/_skills/volute-agent/SKILL.md +5 -4
- package/templates/_base/home/VOLUTE.md +18 -6
- package/templates/_base/src/lib/file-handler.ts +46 -0
- package/templates/_base/src/lib/router.ts +180 -0
- package/templates/_base/src/lib/routing.ts +100 -0
- package/templates/_base/src/lib/types.ts +13 -2
- package/templates/_base/src/lib/volute-server.ts +20 -48
- package/templates/agent-sdk/src/agent.ts +268 -82
- package/templates/agent-sdk/src/server.ts +12 -3
- package/templates/pi/src/agent.ts +277 -58
- package/templates/pi/src/server.ts +15 -4
- package/dist/chunk-MY74SUOL.js +0 -81
- package/dist/connector-6LWB5PRU.js +0 -96
- package/templates/_base/src/lib/sessions.ts +0 -71
- package/templates/agent-sdk/src/lib/agent-sessions.ts +0 -204
- package/templates/pi/src/lib/agent-sessions.ts +0 -210
package/dist/daemon.js
CHANGED
|
@@ -1,26 +1,30 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
CHANNELS
|
|
4
|
+
} from "./chunk-5OCWMTVS.js";
|
|
2
5
|
import {
|
|
3
6
|
clearJsonMap,
|
|
4
7
|
getAgentManager,
|
|
5
8
|
initAgentManager,
|
|
6
9
|
loadJsonMap,
|
|
7
10
|
saveJsonMap
|
|
8
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-I6OHXCMV.js";
|
|
9
12
|
import {
|
|
13
|
+
collectPart,
|
|
10
14
|
logBuffer,
|
|
11
15
|
logger_default,
|
|
12
16
|
readNdjson
|
|
13
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-ZHCE4DPY.js";
|
|
14
18
|
import {
|
|
15
19
|
readVoluteConfig,
|
|
16
20
|
writeVoluteConfig
|
|
17
21
|
} from "./chunk-NETNFBA5.js";
|
|
18
22
|
import {
|
|
19
23
|
loadMergedEnv
|
|
20
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-DNOXHLE5.js";
|
|
21
25
|
import {
|
|
22
26
|
applyIsolation
|
|
23
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-SOZA2TLP.js";
|
|
24
28
|
import {
|
|
25
29
|
agentDir,
|
|
26
30
|
checkHealth,
|
|
@@ -34,32 +38,106 @@ import {
|
|
|
34
38
|
setAgentRunning,
|
|
35
39
|
setVariantRunning,
|
|
36
40
|
voluteHome
|
|
37
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-3C2XR4IY.js";
|
|
38
42
|
import {
|
|
39
43
|
__export
|
|
40
44
|
} from "./chunk-K3NQKI34.js";
|
|
41
45
|
|
|
42
46
|
// src/daemon.ts
|
|
43
47
|
import { randomBytes } from "crypto";
|
|
44
|
-
import { mkdirSync as mkdirSync2, readFileSync as
|
|
45
|
-
import { resolve as
|
|
48
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync4, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
49
|
+
import { resolve as resolve9 } from "path";
|
|
46
50
|
|
|
47
51
|
// src/lib/connector-manager.ts
|
|
48
52
|
import { spawn } from "child_process";
|
|
49
53
|
import {
|
|
50
54
|
createWriteStream,
|
|
51
|
-
existsSync,
|
|
55
|
+
existsSync as existsSync2,
|
|
52
56
|
mkdirSync,
|
|
53
|
-
readFileSync,
|
|
57
|
+
readFileSync as readFileSync2,
|
|
54
58
|
unlinkSync,
|
|
55
59
|
writeFileSync
|
|
56
60
|
} from "fs";
|
|
57
|
-
import { dirname, resolve } from "path";
|
|
61
|
+
import { dirname, resolve as resolve2 } from "path";
|
|
62
|
+
|
|
63
|
+
// src/lib/connector-defs.ts
|
|
64
|
+
import { existsSync, readFileSync } from "fs";
|
|
65
|
+
import { resolve } from "path";
|
|
66
|
+
var BUILTIN_DEFS = {
|
|
67
|
+
discord: {
|
|
68
|
+
displayName: "Discord",
|
|
69
|
+
description: "Connect to Discord as a bot",
|
|
70
|
+
envVars: [
|
|
71
|
+
{
|
|
72
|
+
name: "DISCORD_TOKEN",
|
|
73
|
+
required: true,
|
|
74
|
+
description: "Discord bot token",
|
|
75
|
+
scope: "agent"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: "DISCORD_GUILD_ID",
|
|
79
|
+
required: false,
|
|
80
|
+
description: "Discord server ID (optional, for slash commands)",
|
|
81
|
+
scope: "agent"
|
|
82
|
+
}
|
|
83
|
+
]
|
|
84
|
+
},
|
|
85
|
+
slack: {
|
|
86
|
+
displayName: "Slack",
|
|
87
|
+
description: "Connect to Slack via Socket Mode",
|
|
88
|
+
envVars: [
|
|
89
|
+
{
|
|
90
|
+
name: "SLACK_BOT_TOKEN",
|
|
91
|
+
required: true,
|
|
92
|
+
description: "Slack bot token (xoxb-...)",
|
|
93
|
+
scope: "agent"
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: "SLACK_APP_TOKEN",
|
|
97
|
+
required: true,
|
|
98
|
+
description: "Slack app-level token (xapp-...) for Socket Mode",
|
|
99
|
+
scope: "agent"
|
|
100
|
+
}
|
|
101
|
+
]
|
|
102
|
+
},
|
|
103
|
+
telegram: {
|
|
104
|
+
displayName: "Telegram",
|
|
105
|
+
description: "Connect to Telegram via long polling",
|
|
106
|
+
envVars: [
|
|
107
|
+
{
|
|
108
|
+
name: "TELEGRAM_BOT_TOKEN",
|
|
109
|
+
required: true,
|
|
110
|
+
description: "Telegram bot token from BotFather",
|
|
111
|
+
scope: "agent"
|
|
112
|
+
}
|
|
113
|
+
]
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
function getConnectorDef(type, connectorDir) {
|
|
117
|
+
if (BUILTIN_DEFS[type]) return BUILTIN_DEFS[type];
|
|
118
|
+
if (connectorDir) {
|
|
119
|
+
const jsonPath = resolve(connectorDir, "connector.json");
|
|
120
|
+
if (existsSync(jsonPath)) {
|
|
121
|
+
try {
|
|
122
|
+
return JSON.parse(readFileSync(jsonPath, "utf-8"));
|
|
123
|
+
} catch (err) {
|
|
124
|
+
console.warn(`Failed to parse ${jsonPath}: ${err}`);
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
function checkMissingEnvVars(def, env) {
|
|
132
|
+
return def.envVars.filter((v) => v.required && !env[v.name]);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// src/lib/connector-manager.ts
|
|
58
136
|
function searchUpwards(...segments) {
|
|
59
137
|
let searchDir = dirname(new URL(import.meta.url).pathname);
|
|
60
138
|
for (let i = 0; i < 5; i++) {
|
|
61
|
-
const candidate =
|
|
62
|
-
if (
|
|
139
|
+
const candidate = resolve2(searchDir, ...segments);
|
|
140
|
+
if (existsSync2(candidate)) return candidate;
|
|
63
141
|
searchDir = dirname(searchDir);
|
|
64
142
|
}
|
|
65
143
|
return null;
|
|
@@ -85,6 +163,20 @@ var ConnectorManager = class {
|
|
|
85
163
|
}
|
|
86
164
|
}
|
|
87
165
|
}
|
|
166
|
+
checkConnectorEnv(type, agentDir2) {
|
|
167
|
+
const agentConnectorDir = resolve2(agentDir2, "connectors", type);
|
|
168
|
+
const userConnectorDir = resolve2(voluteHome(), "connectors", type);
|
|
169
|
+
const connectorDir = existsSync2(agentConnectorDir) ? agentConnectorDir : existsSync2(userConnectorDir) ? userConnectorDir : void 0;
|
|
170
|
+
const def = getConnectorDef(type, connectorDir);
|
|
171
|
+
if (!def) return null;
|
|
172
|
+
const env = loadMergedEnv(agentDir2);
|
|
173
|
+
const missing = checkMissingEnvVars(def, env);
|
|
174
|
+
if (missing.length === 0) return null;
|
|
175
|
+
return {
|
|
176
|
+
missing: missing.map((v) => ({ name: v.name, description: v.description })),
|
|
177
|
+
connectorName: def.displayName
|
|
178
|
+
};
|
|
179
|
+
}
|
|
88
180
|
async startConnector(agentName, agentDir2, agentPort, type, daemonPort) {
|
|
89
181
|
const existing = this.connectors.get(agentName)?.get(type);
|
|
90
182
|
if (existing) {
|
|
@@ -106,15 +198,15 @@ var ConnectorManager = class {
|
|
|
106
198
|
this.connectors.get(agentName)?.delete(type);
|
|
107
199
|
}
|
|
108
200
|
this.killOrphanConnector(agentDir2, type);
|
|
109
|
-
const agentConnector =
|
|
110
|
-
const userConnector =
|
|
201
|
+
const agentConnector = resolve2(agentDir2, "connectors", type, "index.ts");
|
|
202
|
+
const userConnector = resolve2(voluteHome(), "connectors", type, "index.ts");
|
|
111
203
|
const builtinConnector = this.resolveBuiltinConnector(type);
|
|
112
204
|
let connectorScript;
|
|
113
205
|
let runtime;
|
|
114
|
-
if (
|
|
206
|
+
if (existsSync2(agentConnector)) {
|
|
115
207
|
connectorScript = agentConnector;
|
|
116
|
-
runtime =
|
|
117
|
-
} else if (
|
|
208
|
+
runtime = resolve2(agentDir2, "node_modules", ".bin", "tsx");
|
|
209
|
+
} else if (existsSync2(userConnector)) {
|
|
118
210
|
connectorScript = userConnector;
|
|
119
211
|
runtime = this.resolveVoluteTsx();
|
|
120
212
|
} else if (builtinConnector) {
|
|
@@ -123,9 +215,9 @@ var ConnectorManager = class {
|
|
|
123
215
|
} else {
|
|
124
216
|
throw new Error(`No connector code found for type: ${type}`);
|
|
125
217
|
}
|
|
126
|
-
const logsDir =
|
|
218
|
+
const logsDir = resolve2(agentDir2, ".volute", "logs");
|
|
127
219
|
mkdirSync(logsDir, { recursive: true });
|
|
128
|
-
const logStream = createWriteStream(
|
|
220
|
+
const logStream = createWriteStream(resolve2(logsDir, `${type}.log`), { flags: "a" });
|
|
129
221
|
const agentEnv = loadMergedEnv(agentDir2);
|
|
130
222
|
const prefix = `${type.toUpperCase()}_`;
|
|
131
223
|
const connectorEnv = Object.fromEntries(
|
|
@@ -137,8 +229,9 @@ var ConnectorManager = class {
|
|
|
137
229
|
...process.env,
|
|
138
230
|
VOLUTE_AGENT_PORT: String(agentPort),
|
|
139
231
|
VOLUTE_AGENT_NAME: agentName,
|
|
232
|
+
VOLUTE_AGENT_DIR: agentDir2,
|
|
140
233
|
...daemonPort ? {
|
|
141
|
-
VOLUTE_DAEMON_URL: `http://
|
|
234
|
+
VOLUTE_DAEMON_URL: `http://127.0.0.1:${daemonPort}`,
|
|
142
235
|
VOLUTE_DAEMON_TOKEN: process.env.VOLUTE_DAEMON_TOKEN
|
|
143
236
|
} : {},
|
|
144
237
|
...connectorEnv
|
|
@@ -146,8 +239,12 @@ var ConnectorManager = class {
|
|
|
146
239
|
};
|
|
147
240
|
await applyIsolation(spawnOpts, agentName);
|
|
148
241
|
const child = spawn(runtime, [connectorScript], spawnOpts);
|
|
242
|
+
let lastStderr = "";
|
|
149
243
|
child.stdout?.pipe(logStream);
|
|
150
|
-
child.stderr?.
|
|
244
|
+
child.stderr?.on("data", (chunk) => {
|
|
245
|
+
logStream.write(chunk);
|
|
246
|
+
lastStderr = chunk.toString().trim();
|
|
247
|
+
});
|
|
151
248
|
if (child.pid) {
|
|
152
249
|
this.saveConnectorPid(agentDir2, type, child.pid);
|
|
153
250
|
}
|
|
@@ -165,6 +262,7 @@ var ConnectorManager = class {
|
|
|
165
262
|
if (this.shuttingDown) return;
|
|
166
263
|
if (this.stopping.has(stopKey)) return;
|
|
167
264
|
console.error(`[daemon] connector ${type} for ${agentName} exited with code ${code}`);
|
|
265
|
+
if (lastStderr) console.error(`[daemon] last output: ${lastStderr}`);
|
|
168
266
|
const attempts = this.restartAttempts.get(stopKey) ?? 0;
|
|
169
267
|
if (attempts >= MAX_RESTART_ATTEMPTS) {
|
|
170
268
|
console.error(
|
|
@@ -194,19 +292,19 @@ var ConnectorManager = class {
|
|
|
194
292
|
const stopKey = `${agentName}:${type}`;
|
|
195
293
|
this.stopping.add(stopKey);
|
|
196
294
|
agentMap.delete(type);
|
|
197
|
-
await new Promise((
|
|
198
|
-
tracked.child.on("exit", () =>
|
|
295
|
+
await new Promise((resolve10) => {
|
|
296
|
+
tracked.child.on("exit", () => resolve10());
|
|
199
297
|
try {
|
|
200
298
|
tracked.child.kill("SIGTERM");
|
|
201
299
|
} catch {
|
|
202
|
-
|
|
300
|
+
resolve10();
|
|
203
301
|
}
|
|
204
302
|
setTimeout(() => {
|
|
205
303
|
try {
|
|
206
304
|
tracked.child.kill("SIGKILL");
|
|
207
305
|
} catch {
|
|
208
306
|
}
|
|
209
|
-
|
|
307
|
+
resolve10();
|
|
210
308
|
}, 5e3);
|
|
211
309
|
});
|
|
212
310
|
this.stopping.delete(stopKey);
|
|
@@ -238,7 +336,7 @@ var ConnectorManager = class {
|
|
|
238
336
|
}));
|
|
239
337
|
}
|
|
240
338
|
connectorPidPath(agentDir2, type) {
|
|
241
|
-
return
|
|
339
|
+
return resolve2(agentDir2, ".volute", "connectors", `${type}.pid`);
|
|
242
340
|
}
|
|
243
341
|
saveConnectorPid(agentDir2, type, pid) {
|
|
244
342
|
const pidPath = this.connectorPidPath(agentDir2, type);
|
|
@@ -253,9 +351,9 @@ var ConnectorManager = class {
|
|
|
253
351
|
}
|
|
254
352
|
killOrphanConnector(agentDir2, type) {
|
|
255
353
|
const pidPath = this.connectorPidPath(agentDir2, type);
|
|
256
|
-
if (!
|
|
354
|
+
if (!existsSync2(pidPath)) return;
|
|
257
355
|
try {
|
|
258
|
-
const pid = parseInt(
|
|
356
|
+
const pid = parseInt(readFileSync2(pidPath, "utf-8").trim(), 10);
|
|
259
357
|
if (pid > 0) {
|
|
260
358
|
process.kill(pid, "SIGTERM");
|
|
261
359
|
console.error(`[daemon] killed orphan connector ${type} (pid ${pid})`);
|
|
@@ -286,7 +384,7 @@ function getConnectorManager() {
|
|
|
286
384
|
}
|
|
287
385
|
|
|
288
386
|
// src/lib/scheduler.ts
|
|
289
|
-
import { resolve as
|
|
387
|
+
import { resolve as resolve3 } from "path";
|
|
290
388
|
import { CronExpressionParser } from "cron-parser";
|
|
291
389
|
var Scheduler = class {
|
|
292
390
|
schedules = /* @__PURE__ */ new Map();
|
|
@@ -296,7 +394,7 @@ var Scheduler = class {
|
|
|
296
394
|
daemonPort = null;
|
|
297
395
|
daemonToken = null;
|
|
298
396
|
get statePath() {
|
|
299
|
-
return
|
|
397
|
+
return resolve3(voluteHome(), "scheduler-state.json");
|
|
300
398
|
}
|
|
301
399
|
start(daemonPort, daemonToken) {
|
|
302
400
|
this.daemonPort = daemonPort ?? null;
|
|
@@ -374,10 +472,12 @@ var Scheduler = class {
|
|
|
374
472
|
channel: "system:scheduler",
|
|
375
473
|
sender: schedule.id
|
|
376
474
|
});
|
|
475
|
+
const controller = new AbortController();
|
|
476
|
+
const timeout = setTimeout(() => controller.abort(), 12e4);
|
|
377
477
|
try {
|
|
378
478
|
let res;
|
|
379
479
|
if (this.daemonPort && this.daemonToken) {
|
|
380
|
-
const daemonUrl = `http://
|
|
480
|
+
const daemonUrl = `http://127.0.0.1:${this.daemonPort}`;
|
|
381
481
|
res = await fetch(`${daemonUrl}/api/agents/${encodeURIComponent(agentName)}/message`, {
|
|
382
482
|
method: "POST",
|
|
383
483
|
headers: {
|
|
@@ -385,13 +485,15 @@ var Scheduler = class {
|
|
|
385
485
|
Authorization: `Bearer ${this.daemonToken}`,
|
|
386
486
|
Origin: daemonUrl
|
|
387
487
|
},
|
|
388
|
-
body
|
|
488
|
+
body,
|
|
489
|
+
signal: controller.signal
|
|
389
490
|
});
|
|
390
491
|
} else {
|
|
391
|
-
res = await fetch(`http://
|
|
492
|
+
res = await fetch(`http://127.0.0.1:${entry.port}/message`, {
|
|
392
493
|
method: "POST",
|
|
393
494
|
headers: { "Content-Type": "application/json" },
|
|
394
|
-
body
|
|
495
|
+
body,
|
|
496
|
+
signal: controller.signal
|
|
395
497
|
});
|
|
396
498
|
}
|
|
397
499
|
if (!res.ok) {
|
|
@@ -399,8 +501,14 @@ var Scheduler = class {
|
|
|
399
501
|
} else {
|
|
400
502
|
console.error(`[scheduler] fired "${schedule.id}" for ${agentName}`);
|
|
401
503
|
}
|
|
504
|
+
try {
|
|
505
|
+
await res.body?.cancel();
|
|
506
|
+
} catch {
|
|
507
|
+
}
|
|
402
508
|
} catch (err) {
|
|
403
509
|
console.error(`[scheduler] failed to fire "${schedule.id}" for ${agentName}:`, err);
|
|
510
|
+
} finally {
|
|
511
|
+
clearTimeout(timeout);
|
|
404
512
|
}
|
|
405
513
|
}
|
|
406
514
|
};
|
|
@@ -421,8 +529,8 @@ import { compareSync, hashSync } from "bcryptjs";
|
|
|
421
529
|
import { and, count, eq } from "drizzle-orm";
|
|
422
530
|
|
|
423
531
|
// src/lib/db.ts
|
|
424
|
-
import { chmodSync, existsSync as
|
|
425
|
-
import { dirname as dirname2, resolve as
|
|
532
|
+
import { chmodSync, existsSync as existsSync3 } from "fs";
|
|
533
|
+
import { dirname as dirname2, resolve as resolve4 } from "path";
|
|
426
534
|
import { fileURLToPath } from "url";
|
|
427
535
|
import { drizzle } from "drizzle-orm/libsql";
|
|
428
536
|
import { migrate } from "drizzle-orm/libsql/migrator";
|
|
@@ -498,11 +606,11 @@ var messages = sqliteTable(
|
|
|
498
606
|
|
|
499
607
|
// src/lib/db.ts
|
|
500
608
|
var __dirname = dirname2(fileURLToPath(import.meta.url));
|
|
501
|
-
var migrationsFolder =
|
|
609
|
+
var migrationsFolder = existsSync3(resolve4(__dirname, "../drizzle")) ? resolve4(__dirname, "../drizzle") : resolve4(__dirname, "../../drizzle");
|
|
502
610
|
var db = null;
|
|
503
611
|
async function getDb() {
|
|
504
612
|
if (db) return db;
|
|
505
|
-
const dbPath = process.env.VOLUTE_DB_PATH ||
|
|
613
|
+
const dbPath = process.env.VOLUTE_DB_PATH || resolve4(voluteHome(), "volute.db");
|
|
506
614
|
db = drizzle({ connection: { url: `file:${dbPath}` }, schema: schema_exports });
|
|
507
615
|
await migrate(db, { migrationsFolder });
|
|
508
616
|
try {
|
|
@@ -642,9 +750,9 @@ var authMiddleware = createMiddleware(async (c, next) => {
|
|
|
642
750
|
});
|
|
643
751
|
|
|
644
752
|
// src/web/server.ts
|
|
645
|
-
import { existsSync as
|
|
753
|
+
import { existsSync as existsSync7 } from "fs";
|
|
646
754
|
import { readFile as readFile2, stat } from "fs/promises";
|
|
647
|
-
import { dirname as dirname3, extname, resolve as
|
|
755
|
+
import { dirname as dirname3, extname, resolve as resolve8 } from "path";
|
|
648
756
|
import { serve } from "@hono/node-server";
|
|
649
757
|
|
|
650
758
|
// src/web/app.ts
|
|
@@ -654,25 +762,14 @@ import { csrf } from "hono/csrf";
|
|
|
654
762
|
import { HTTPException } from "hono/http-exception";
|
|
655
763
|
|
|
656
764
|
// src/web/routes/agents.ts
|
|
657
|
-
import { existsSync as
|
|
658
|
-
import { resolve as
|
|
765
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, rmSync } from "fs";
|
|
766
|
+
import { resolve as resolve5 } from "path";
|
|
659
767
|
import { and as and2, desc, eq as eq3 } from "drizzle-orm";
|
|
660
768
|
import { Hono } from "hono";
|
|
661
769
|
import { stream } from "hono/streaming";
|
|
662
|
-
|
|
663
|
-
// src/lib/channels.ts
|
|
664
|
-
var CHANNELS = {
|
|
665
|
-
web: { name: "web", displayName: "Web UI", showToolCalls: true },
|
|
666
|
-
discord: { name: "discord", displayName: "Discord", showToolCalls: false },
|
|
667
|
-
cli: { name: "cli", displayName: "CLI", showToolCalls: true },
|
|
668
|
-
agent: { name: "agent", displayName: "Agent", showToolCalls: true },
|
|
669
|
-
system: { name: "system", displayName: "System", showToolCalls: false }
|
|
670
|
-
};
|
|
671
|
-
|
|
672
|
-
// src/web/routes/agents.ts
|
|
673
770
|
function getDaemonPort() {
|
|
674
771
|
try {
|
|
675
|
-
const data = JSON.parse(
|
|
772
|
+
const data = JSON.parse(readFileSync3(resolve5(voluteHome(), "daemon.json"), "utf-8"));
|
|
676
773
|
return data.port;
|
|
677
774
|
} catch {
|
|
678
775
|
return void 0;
|
|
@@ -717,7 +814,7 @@ var app = new Hono().get("/", async (c) => {
|
|
|
717
814
|
const name = c.req.param("name");
|
|
718
815
|
const entry = findAgent(name);
|
|
719
816
|
if (!entry) return c.json({ error: "Agent not found" }, 404);
|
|
720
|
-
if (!
|
|
817
|
+
if (!existsSync4(agentDir(name))) return c.json({ error: "Agent directory missing" }, 404);
|
|
721
818
|
const { status, channels } = await getAgentStatus(name, entry.port);
|
|
722
819
|
const variants = readVariants(name);
|
|
723
820
|
const manager = getAgentManager();
|
|
@@ -743,7 +840,7 @@ var app = new Hono().get("/", async (c) => {
|
|
|
743
840
|
if (!variant) return c.json({ error: `Unknown variant: ${variantName}` }, 404);
|
|
744
841
|
} else {
|
|
745
842
|
const dir = agentDir(baseName);
|
|
746
|
-
if (!
|
|
843
|
+
if (!existsSync4(dir)) return c.json({ error: "Agent directory missing" }, 404);
|
|
747
844
|
}
|
|
748
845
|
const manager = getAgentManager();
|
|
749
846
|
if (manager.isRunning(name)) {
|
|
@@ -770,7 +867,7 @@ var app = new Hono().get("/", async (c) => {
|
|
|
770
867
|
if (!variant) return c.json({ error: `Unknown variant: ${variantName}` }, 404);
|
|
771
868
|
} else {
|
|
772
869
|
const dir = agentDir(baseName);
|
|
773
|
-
if (!
|
|
870
|
+
if (!existsSync4(dir)) return c.json({ error: "Agent directory missing" }, 404);
|
|
774
871
|
}
|
|
775
872
|
const manager = getAgentManager();
|
|
776
873
|
const connectorManager = getConnectorManager();
|
|
@@ -825,7 +922,7 @@ var app = new Hono().get("/", async (c) => {
|
|
|
825
922
|
}
|
|
826
923
|
removeAllVariants(name);
|
|
827
924
|
removeAgent(name);
|
|
828
|
-
if (force &&
|
|
925
|
+
if (force && existsSync4(dir)) {
|
|
829
926
|
rmSync(dir, { recursive: true, force: true });
|
|
830
927
|
}
|
|
831
928
|
return c.json({ ok: true });
|
|
@@ -847,14 +944,22 @@ var app = new Hono().get("/", async (c) => {
|
|
|
847
944
|
let parsed = null;
|
|
848
945
|
try {
|
|
849
946
|
parsed = JSON.parse(body);
|
|
850
|
-
} catch {
|
|
947
|
+
} catch (err) {
|
|
948
|
+
console.error(`[daemon] failed to parse message body for ${baseName}:`, err);
|
|
851
949
|
}
|
|
852
950
|
const channel = parsed?.channel ?? "unknown";
|
|
853
951
|
const db2 = await getDb();
|
|
854
952
|
if (parsed) {
|
|
855
953
|
try {
|
|
856
954
|
const sender = parsed.sender ?? null;
|
|
857
|
-
|
|
955
|
+
let content;
|
|
956
|
+
if (typeof parsed.content === "string") {
|
|
957
|
+
content = parsed.content;
|
|
958
|
+
} else if (Array.isArray(parsed.content)) {
|
|
959
|
+
content = parsed.content.filter((p) => p.type === "text" && p.text).map((p) => p.text).join("\n");
|
|
960
|
+
} else {
|
|
961
|
+
content = JSON.stringify(parsed.content);
|
|
962
|
+
}
|
|
858
963
|
await db2.insert(agentMessages).values({
|
|
859
964
|
agent: baseName,
|
|
860
965
|
channel,
|
|
@@ -866,11 +971,17 @@ var app = new Hono().get("/", async (c) => {
|
|
|
866
971
|
console.error(`[daemon] failed to persist inbound message for ${baseName}:`, err);
|
|
867
972
|
}
|
|
868
973
|
}
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
974
|
+
let res;
|
|
975
|
+
try {
|
|
976
|
+
res = await fetch(`http://127.0.0.1:${port}/message`, {
|
|
977
|
+
method: "POST",
|
|
978
|
+
headers: { "Content-Type": "application/json" },
|
|
979
|
+
body
|
|
980
|
+
});
|
|
981
|
+
} catch (err) {
|
|
982
|
+
console.error(`[daemon] agent ${name} unreachable on port ${port}:`, err);
|
|
983
|
+
return c.json({ error: "Agent is not reachable" }, 502);
|
|
984
|
+
}
|
|
874
985
|
if (!res.ok) {
|
|
875
986
|
return c.json({ error: `Agent responded with ${res.status}` }, res.status);
|
|
876
987
|
}
|
|
@@ -878,57 +989,39 @@ var app = new Hono().get("/", async (c) => {
|
|
|
878
989
|
return c.json({ error: "No response body from agent" }, 502);
|
|
879
990
|
}
|
|
880
991
|
c.header("Content-Type", "application/x-ndjson");
|
|
992
|
+
const encoder = new TextEncoder();
|
|
881
993
|
return stream(c, async (s) => {
|
|
882
|
-
const reader = res.body.getReader();
|
|
883
|
-
const decoder = new TextDecoder();
|
|
884
|
-
let buffer = "";
|
|
885
994
|
const textParts = [];
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
for (const line of lines) {
|
|
895
|
-
if (!line.trim()) continue;
|
|
896
|
-
try {
|
|
897
|
-
const event = JSON.parse(line);
|
|
898
|
-
if (event.type === "text") {
|
|
899
|
-
textParts.push(event.content);
|
|
900
|
-
}
|
|
901
|
-
} catch {
|
|
902
|
-
console.warn(`[daemon] malformed NDJSON line from ${baseName}`);
|
|
903
|
-
}
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
if (buffer.trim()) {
|
|
907
|
-
try {
|
|
908
|
-
const event = JSON.parse(buffer);
|
|
909
|
-
if (event.type === "text") {
|
|
910
|
-
textParts.push(event.content);
|
|
911
|
-
}
|
|
912
|
-
} catch {
|
|
913
|
-
console.warn(`[daemon] malformed NDJSON trailing data from ${baseName}`);
|
|
914
|
-
}
|
|
995
|
+
const toolParts = [];
|
|
996
|
+
for await (const event of readNdjson(res.body)) {
|
|
997
|
+
await s.write(encoder.encode(`${JSON.stringify(event)}
|
|
998
|
+
`));
|
|
999
|
+
const part = collectPart(event);
|
|
1000
|
+
if (part != null) {
|
|
1001
|
+
if (event.type === "tool_use") toolParts.push(part);
|
|
1002
|
+
else textParts.push(part);
|
|
915
1003
|
}
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
}
|
|
1004
|
+
}
|
|
1005
|
+
const content = [textParts.join(""), ...toolParts].filter(Boolean).join("\n");
|
|
1006
|
+
if (content) {
|
|
1007
|
+
try {
|
|
1008
|
+
await db2.insert(agentMessages).values({
|
|
1009
|
+
agent: baseName,
|
|
1010
|
+
channel,
|
|
1011
|
+
role: "assistant",
|
|
1012
|
+
sender: baseName,
|
|
1013
|
+
content
|
|
1014
|
+
});
|
|
1015
|
+
} catch (err) {
|
|
1016
|
+
console.error(`[daemon] failed to persist assistant response for ${baseName}:`, err);
|
|
927
1017
|
}
|
|
928
|
-
} finally {
|
|
929
|
-
reader.releaseLock();
|
|
930
1018
|
}
|
|
931
1019
|
});
|
|
1020
|
+
}).get("/:name/history/channels", async (c) => {
|
|
1021
|
+
const name = c.req.param("name");
|
|
1022
|
+
const db2 = await getDb();
|
|
1023
|
+
const rows = await db2.selectDistinct({ channel: agentMessages.channel }).from(agentMessages).where(eq3(agentMessages.agent, name));
|
|
1024
|
+
return c.json(rows.map((r) => r.channel));
|
|
932
1025
|
}).get("/:name/history", async (c) => {
|
|
933
1026
|
const name = c.req.param("name");
|
|
934
1027
|
const channel = c.req.query("channel");
|
|
@@ -1116,7 +1209,7 @@ var app3 = new Hono3().post("/:name/chat", zValidator2("json", chatSchema), asyn
|
|
|
1116
1209
|
if (!variant) return c.json({ error: `Unknown variant: ${variantName}` }, 404);
|
|
1117
1210
|
port = variant.port;
|
|
1118
1211
|
}
|
|
1119
|
-
const { getAgentManager: getAgentManager2 } = await import("./agent-manager-
|
|
1212
|
+
const { getAgentManager: getAgentManager2 } = await import("./agent-manager-AUCKMGPR.js");
|
|
1120
1213
|
if (!getAgentManager2().isRunning(name)) {
|
|
1121
1214
|
return c.json({ error: "Agent is not running" }, 409);
|
|
1122
1215
|
}
|
|
@@ -1155,15 +1248,21 @@ var app3 = new Hono3().post("/:name/chat", zValidator2("json", chatSchema), asyn
|
|
|
1155
1248
|
sender: user.username,
|
|
1156
1249
|
content: body.message ?? "[image]"
|
|
1157
1250
|
});
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1251
|
+
let res;
|
|
1252
|
+
try {
|
|
1253
|
+
res = await fetch(`http://127.0.0.1:${port}/message`, {
|
|
1254
|
+
method: "POST",
|
|
1255
|
+
headers: { "Content-Type": "application/json" },
|
|
1256
|
+
body: JSON.stringify({
|
|
1257
|
+
content: contentBlocks,
|
|
1258
|
+
channel: "web",
|
|
1259
|
+
sender: user.username
|
|
1260
|
+
})
|
|
1261
|
+
});
|
|
1262
|
+
} catch (err) {
|
|
1263
|
+
console.error(`[chat] agent ${name} unreachable on port ${port}:`, err);
|
|
1264
|
+
return c.json({ error: "Agent is not reachable" }, 502);
|
|
1265
|
+
}
|
|
1167
1266
|
if (!res.ok) {
|
|
1168
1267
|
return c.json({ error: `Agent responded with ${res.status}` }, res.status);
|
|
1169
1268
|
}
|
|
@@ -1200,13 +1299,23 @@ var app3 = new Hono3().post("/:name/chat", zValidator2("json", chatSchema), asyn
|
|
|
1200
1299
|
if (event.type === "done") {
|
|
1201
1300
|
if (assistantContent.length > 0) {
|
|
1202
1301
|
await addMessage(conversationId, "assistant", baseName, assistantContent);
|
|
1203
|
-
const textParts =
|
|
1204
|
-
|
|
1302
|
+
const textParts = [];
|
|
1303
|
+
const toolParts = [];
|
|
1304
|
+
for (const b of assistantContent) {
|
|
1305
|
+
const part = collectPart(b);
|
|
1306
|
+
if (part != null) {
|
|
1307
|
+
if (b.type === "tool_use") toolParts.push(part);
|
|
1308
|
+
else textParts.push(part);
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
const summary = [textParts.join(""), ...toolParts].filter(Boolean).join("\n");
|
|
1312
|
+
if (summary) {
|
|
1205
1313
|
await db2.insert(agentMessages).values({
|
|
1206
1314
|
agent: baseName,
|
|
1207
1315
|
channel: "web",
|
|
1208
1316
|
role: "assistant",
|
|
1209
|
-
|
|
1317
|
+
sender: baseName,
|
|
1318
|
+
content: summary
|
|
1210
1319
|
});
|
|
1211
1320
|
}
|
|
1212
1321
|
}
|
|
@@ -1243,13 +1352,24 @@ var app4 = new Hono4().get("/:name/connectors", (c) => {
|
|
|
1243
1352
|
const entry = findAgent(name);
|
|
1244
1353
|
if (!entry) return c.json({ error: "Agent not found" }, 404);
|
|
1245
1354
|
const dir = agentDir(name);
|
|
1355
|
+
const manager = getConnectorManager();
|
|
1356
|
+
const envCheck = manager.checkConnectorEnv(type, dir);
|
|
1357
|
+
if (envCheck) {
|
|
1358
|
+
return c.json(
|
|
1359
|
+
{
|
|
1360
|
+
error: "missing_env",
|
|
1361
|
+
missing: envCheck.missing,
|
|
1362
|
+
connectorName: envCheck.connectorName
|
|
1363
|
+
},
|
|
1364
|
+
400
|
|
1365
|
+
);
|
|
1366
|
+
}
|
|
1246
1367
|
const config = readVoluteConfig(dir) ?? {};
|
|
1247
1368
|
const connectors = config.connectors ?? [];
|
|
1248
1369
|
if (!connectors.includes(type)) {
|
|
1249
1370
|
config.connectors = [...connectors, type];
|
|
1250
1371
|
writeVoluteConfig(dir, config);
|
|
1251
1372
|
}
|
|
1252
|
-
const manager = getConnectorManager();
|
|
1253
1373
|
try {
|
|
1254
1374
|
await manager.startConnector(name, dir, entry.port, type);
|
|
1255
1375
|
return c.json({ ok: true });
|
|
@@ -1301,9 +1421,9 @@ var app5 = new Hono5().get("/:name/conversations", async (c) => {
|
|
|
1301
1421
|
var conversations_default = app5;
|
|
1302
1422
|
|
|
1303
1423
|
// src/web/routes/files.ts
|
|
1304
|
-
import { existsSync as
|
|
1424
|
+
import { existsSync as existsSync5 } from "fs";
|
|
1305
1425
|
import { readdir, readFile, writeFile } from "fs/promises";
|
|
1306
|
-
import { resolve as
|
|
1426
|
+
import { resolve as resolve6 } from "path";
|
|
1307
1427
|
import { zValidator as zValidator3 } from "@hono/zod-validator";
|
|
1308
1428
|
import { Hono as Hono6 } from "hono";
|
|
1309
1429
|
import { z as z3 } from "zod";
|
|
@@ -1314,8 +1434,8 @@ var app6 = new Hono6().get("/:name/files", async (c) => {
|
|
|
1314
1434
|
const entry = findAgent(name);
|
|
1315
1435
|
if (!entry) return c.json({ error: "Agent not found" }, 404);
|
|
1316
1436
|
const dir = agentDir(name);
|
|
1317
|
-
const homeDir =
|
|
1318
|
-
if (!
|
|
1437
|
+
const homeDir = resolve6(dir, "home");
|
|
1438
|
+
if (!existsSync5(homeDir)) return c.json({ error: "Home directory missing" }, 404);
|
|
1319
1439
|
const allFiles = await readdir(homeDir);
|
|
1320
1440
|
const files = allFiles.filter((f) => f.endsWith(".md") && ALLOWED_FILES.has(f));
|
|
1321
1441
|
return c.json(files);
|
|
@@ -1328,8 +1448,8 @@ var app6 = new Hono6().get("/:name/files", async (c) => {
|
|
|
1328
1448
|
const entry = findAgent(name);
|
|
1329
1449
|
if (!entry) return c.json({ error: "Agent not found" }, 404);
|
|
1330
1450
|
const dir = agentDir(name);
|
|
1331
|
-
const filePath =
|
|
1332
|
-
if (!
|
|
1451
|
+
const filePath = resolve6(dir, "home", filename);
|
|
1452
|
+
if (!existsSync5(filePath)) {
|
|
1333
1453
|
return c.json({ error: "File not found" }, 404);
|
|
1334
1454
|
}
|
|
1335
1455
|
const content = await readFile(filePath, "utf-8");
|
|
@@ -1343,7 +1463,7 @@ var app6 = new Hono6().get("/:name/files", async (c) => {
|
|
|
1343
1463
|
const entry = findAgent(name);
|
|
1344
1464
|
if (!entry) return c.json({ error: "Agent not found" }, 404);
|
|
1345
1465
|
const dir = agentDir(name);
|
|
1346
|
-
const filePath =
|
|
1466
|
+
const filePath = resolve6(dir, "home", filename);
|
|
1347
1467
|
const { content } = c.req.valid("json");
|
|
1348
1468
|
await writeFile(filePath, content);
|
|
1349
1469
|
return c.json({ ok: true });
|
|
@@ -1352,8 +1472,8 @@ var files_default = app6;
|
|
|
1352
1472
|
|
|
1353
1473
|
// src/web/routes/logs.ts
|
|
1354
1474
|
import { spawn as spawn2 } from "child_process";
|
|
1355
|
-
import { existsSync as
|
|
1356
|
-
import { resolve as
|
|
1475
|
+
import { existsSync as existsSync6 } from "fs";
|
|
1476
|
+
import { resolve as resolve7 } from "path";
|
|
1357
1477
|
import { Hono as Hono7 } from "hono";
|
|
1358
1478
|
import { streamSSE as streamSSE2 } from "hono/streaming";
|
|
1359
1479
|
var app7 = new Hono7().get("/:name/logs", async (c) => {
|
|
@@ -1361,8 +1481,8 @@ var app7 = new Hono7().get("/:name/logs", async (c) => {
|
|
|
1361
1481
|
const entry = findAgent(name);
|
|
1362
1482
|
if (!entry) return c.json({ error: "Agent not found" }, 404);
|
|
1363
1483
|
const dir = agentDir(name);
|
|
1364
|
-
const logFile =
|
|
1365
|
-
if (!
|
|
1484
|
+
const logFile = resolve7(dir, ".volute", "logs", "agent.log");
|
|
1485
|
+
if (!existsSync6(logFile)) {
|
|
1366
1486
|
return c.json({ error: "No log file found" }, 404);
|
|
1367
1487
|
}
|
|
1368
1488
|
return streamSSE2(c, async (stream2) => {
|
|
@@ -1380,9 +1500,9 @@ var app7 = new Hono7().get("/:name/logs", async (c) => {
|
|
|
1380
1500
|
stream2.onAbort(() => {
|
|
1381
1501
|
tail.kill();
|
|
1382
1502
|
});
|
|
1383
|
-
await new Promise((
|
|
1384
|
-
tail.on("exit",
|
|
1385
|
-
stream2.onAbort(
|
|
1503
|
+
await new Promise((resolve10) => {
|
|
1504
|
+
tail.on("exit", resolve10);
|
|
1505
|
+
stream2.onAbort(resolve10);
|
|
1386
1506
|
});
|
|
1387
1507
|
});
|
|
1388
1508
|
});
|
|
@@ -1451,7 +1571,7 @@ var app8 = new Hono8().get("/:name/schedules", (c) => {
|
|
|
1451
1571
|
const body = await c.req.text();
|
|
1452
1572
|
const message = `[webhook: ${event}] ${body}`;
|
|
1453
1573
|
try {
|
|
1454
|
-
const res = await fetch(`http://
|
|
1574
|
+
const res = await fetch(`http://127.0.0.1:${entry.port}/message`, {
|
|
1455
1575
|
method: "POST",
|
|
1456
1576
|
headers: { "Content-Type": "application/json" },
|
|
1457
1577
|
body: JSON.stringify({
|
|
@@ -1484,10 +1604,10 @@ var app9 = new Hono9().get("/logs", async (c) => {
|
|
|
1484
1604
|
stream2.writeSSE({ data: JSON.stringify(entry) }).catch(() => {
|
|
1485
1605
|
});
|
|
1486
1606
|
});
|
|
1487
|
-
await new Promise((
|
|
1607
|
+
await new Promise((resolve10) => {
|
|
1488
1608
|
stream2.onAbort(() => {
|
|
1489
1609
|
unsubscribe();
|
|
1490
|
-
|
|
1610
|
+
resolve10();
|
|
1491
1611
|
});
|
|
1492
1612
|
});
|
|
1493
1613
|
});
|
|
@@ -1566,8 +1686,8 @@ async function startServer({
|
|
|
1566
1686
|
let assetsDir = "";
|
|
1567
1687
|
let searchDir = dirname3(new URL(import.meta.url).pathname);
|
|
1568
1688
|
for (let i = 0; i < 5; i++) {
|
|
1569
|
-
const candidate =
|
|
1570
|
-
if (
|
|
1689
|
+
const candidate = resolve8(searchDir, "dist", "web-assets");
|
|
1690
|
+
if (existsSync7(candidate)) {
|
|
1571
1691
|
assetsDir = candidate;
|
|
1572
1692
|
break;
|
|
1573
1693
|
}
|
|
@@ -1576,7 +1696,7 @@ async function startServer({
|
|
|
1576
1696
|
if (assetsDir) {
|
|
1577
1697
|
app_default.get("*", async (c) => {
|
|
1578
1698
|
const urlPath = new URL(c.req.url).pathname;
|
|
1579
|
-
const filePath =
|
|
1699
|
+
const filePath = resolve8(assetsDir, urlPath.slice(1));
|
|
1580
1700
|
if (!filePath.startsWith(assetsDir)) return c.text("Forbidden", 403);
|
|
1581
1701
|
const s = await stat(filePath).catch(() => null);
|
|
1582
1702
|
if (s?.isFile()) {
|
|
@@ -1585,7 +1705,7 @@ async function startServer({
|
|
|
1585
1705
|
const body = await readFile2(filePath);
|
|
1586
1706
|
return c.body(body, 200, { "Content-Type": mime });
|
|
1587
1707
|
}
|
|
1588
|
-
const indexPath =
|
|
1708
|
+
const indexPath = resolve8(assetsDir, "index.html");
|
|
1589
1709
|
const indexStat = await stat(indexPath).catch(() => null);
|
|
1590
1710
|
if (indexStat?.isFile()) {
|
|
1591
1711
|
const body = await readFile2(indexPath, "utf-8");
|
|
@@ -1595,10 +1715,10 @@ async function startServer({
|
|
|
1595
1715
|
});
|
|
1596
1716
|
}
|
|
1597
1717
|
const server = serve({ fetch: app_default.fetch, port, hostname });
|
|
1598
|
-
await new Promise((
|
|
1718
|
+
await new Promise((resolve10, reject) => {
|
|
1599
1719
|
server.on("listening", () => {
|
|
1600
1720
|
logger_default.info("Volute UI running", { hostname, port });
|
|
1601
|
-
|
|
1721
|
+
resolve10();
|
|
1602
1722
|
});
|
|
1603
1723
|
server.on("error", (err) => {
|
|
1604
1724
|
reject(err);
|
|
@@ -1612,8 +1732,8 @@ async function startDaemon(opts) {
|
|
|
1612
1732
|
const { port, hostname } = opts;
|
|
1613
1733
|
const myPid = String(process.pid);
|
|
1614
1734
|
const home = voluteHome();
|
|
1615
|
-
const DAEMON_PID_PATH =
|
|
1616
|
-
const DAEMON_JSON_PATH =
|
|
1735
|
+
const DAEMON_PID_PATH = resolve9(home, "daemon.pid");
|
|
1736
|
+
const DAEMON_JSON_PATH = resolve9(home, "daemon.json");
|
|
1617
1737
|
mkdirSync2(home, { recursive: true });
|
|
1618
1738
|
const token = process.env.VOLUTE_DAEMON_TOKEN || randomBytes(32).toString("hex");
|
|
1619
1739
|
process.env.VOLUTE_DAEMON_TOKEN = token;
|
|
@@ -1666,13 +1786,13 @@ async function startDaemon(opts) {
|
|
|
1666
1786
|
console.error(`[daemon] running on ${hostname}:${port}, pid ${myPid}`);
|
|
1667
1787
|
function cleanup() {
|
|
1668
1788
|
try {
|
|
1669
|
-
if (
|
|
1789
|
+
if (readFileSync4(DAEMON_PID_PATH, "utf-8").trim() === myPid) {
|
|
1670
1790
|
unlinkSync2(DAEMON_PID_PATH);
|
|
1671
1791
|
}
|
|
1672
1792
|
} catch {
|
|
1673
1793
|
}
|
|
1674
1794
|
try {
|
|
1675
|
-
const data = JSON.parse(
|
|
1795
|
+
const data = JSON.parse(readFileSync4(DAEMON_JSON_PATH, "utf-8"));
|
|
1676
1796
|
if (data.token === token) {
|
|
1677
1797
|
unlinkSync2(DAEMON_JSON_PATH);
|
|
1678
1798
|
}
|