volute 0.6.0 → 0.8.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 +13 -13
- package/dist/{agent-X7GJLBLW.js → agent-YORVRB6I.js} +10 -10
- package/dist/{agent-manager-JDVXU3ON.js → agent-manager-CMMH5KQQ.js} +4 -4
- package/dist/{channel-SMCNOIVQ.js → channel-RDGHBFSI.js} +16 -56
- package/dist/{chunk-JR4UXCTO.js → chunk-23L3MKEV.js} +1 -1
- package/dist/{chunk-5SKQ6J7T.js → chunk-5C5JWR2L.js} +15 -7
- package/dist/{chunk-UWHWAPGO.js → chunk-DP2DX4WV.js} +9 -1
- package/dist/chunk-ECPQXRLB.js +264 -0
- package/dist/{down-FXWAN66A.js → chunk-HZ5LTOEJ.js} +48 -13
- package/dist/{chunk-W76KWE23.js → chunk-IQXBMFZG.js} +6 -4
- package/dist/{chunk-ZZOOTYXK.js → chunk-LIPPXNIE.js} +60 -74
- package/dist/{chunk-BX7KI4S3.js → chunk-N6MLQ26B.js} +23 -96
- package/dist/{chunk-H7AMDUIA.js → chunk-QF22MYDJ.js} +6 -5
- package/dist/{chunk-AOKAQGO4.js → chunk-RT6Y7AR3.js} +2 -1
- package/dist/{chunk-G6ZNGLUX.js → chunk-W6TMWYU3.js} +133 -78
- package/dist/{up-CSX3ZUIU.js → chunk-XSJ27WEM.js} +2 -2
- package/dist/cli.js +25 -19
- package/dist/{connector-Y7JPNROO.js → connector-ZP6MEFF4.js} +3 -3
- package/dist/connectors/discord.js +24 -61
- package/dist/connectors/slack.js +21 -38
- package/dist/connectors/telegram.js +31 -49
- package/dist/{create-G525LWEA.js → create-HGJHLABX.js} +22 -17
- package/dist/{daemon-client-442IV43D.js → daemon-client-54J3EIZD.js} +2 -2
- package/dist/daemon-restart-CPBLMMRI.js +23 -0
- package/dist/daemon.js +397 -661
- package/dist/{delete-2PH2CGDY.js → delete-45TGQC4N.js} +13 -4
- package/dist/down-O4EWZTVA.js +11 -0
- package/dist/{env-7GLUJCWS.js → env-KMNYGVZ2.js} +7 -9
- package/dist/{history-H72ZUIBN.js → history-PXJVYLVY.js} +2 -2
- package/dist/{import-AVKQJDYC.js → import-CNEDF3TD.js} +6 -6
- package/dist/{logs-EDGK26AK.js → logs-TZB3MTLZ.js} +5 -4
- package/dist/{package-4DP4Y4UO.js → package-5UCKNK6J.js} +1 -1
- package/dist/{restart-O4ETYLJF.js → restart-KVH3TK5N.js} +2 -2
- package/dist/{schedule-S6QVC5ON.js → schedule-HCUCBNQI.js} +2 -2
- package/dist/send-BNC2S5BY.js +162 -0
- package/dist/{service-HZNIDNJF.js → service-R4MCNBOA.js} +1 -1
- package/dist/{setup-F4TCWVSP.js → setup-JXDCJX7W.js} +25 -6
- package/dist/{start-VHQ7LNWM.js → start-QU73YTJW.js} +2 -2
- package/dist/{status-QAJWXKMZ.js → status-Q6ZQJXNI.js} +2 -2
- package/dist/{stop-CAGCT5NI.js → stop-N7U5N6A7.js} +2 -2
- package/dist/up-V6EAA7OZ.js +12 -0
- package/dist/{update-XSIX3GGP.js → update-EUCZ7XGG.js} +3 -3
- package/dist/{update-check-5ZADDHCK.js → update-check-SM4244SU.js} +2 -2
- package/dist/{upgrade-YXKPWDRU.js → upgrade-CZF6PN7Y.js} +4 -4
- package/dist/{variant-4Z6W3PP6.js → variant-RKXPN5DH.js} +20 -46
- package/dist/web-assets/assets/index-D-3zx6vs.js +307 -0
- package/dist/web-assets/index.html +1 -1
- package/drizzle/0004_magical_silverclaw.sql +1 -0
- package/drizzle/meta/0004_snapshot.json +410 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +1 -1
- package/templates/_base/_skills/volute-agent/SKILL.md +32 -16
- package/templates/_base/home/.config/routes.json +4 -8
- package/templates/_base/home/VOLUTE.md +16 -14
- package/templates/_base/src/lib/auto-reply.ts +38 -0
- package/templates/_base/src/lib/daemon-client.ts +53 -0
- package/templates/_base/src/lib/router.ts +66 -14
- package/templates/_base/src/lib/routing.ts +48 -9
- package/templates/_base/src/lib/startup.ts +1 -25
- package/templates/_base/src/lib/types.ts +2 -1
- package/templates/_base/src/lib/volute-server.ts +29 -14
- package/templates/agent-sdk/src/agent.ts +53 -111
- package/templates/agent-sdk/src/lib/content.ts +41 -0
- package/templates/agent-sdk/src/lib/session-store.ts +43 -0
- package/templates/agent-sdk/src/lib/stream-consumer.ts +66 -0
- package/templates/agent-sdk/src/server.ts +5 -13
- package/templates/pi/.init/AGENTS.md +5 -5
- package/templates/pi/src/agent.ts +32 -84
- package/templates/pi/src/lib/content.ts +15 -0
- package/templates/pi/src/lib/event-handler.ts +74 -0
- package/templates/pi/src/lib/resolve-model.ts +21 -0
- package/templates/pi/src/server.ts +3 -7
- package/dist/chunk-B3R6L2GW.js +0 -24
- package/dist/chunk-ZYGKG6VC.js +0 -22
- package/dist/message-SCOQDR3P.js +0 -32
- package/dist/send-G7PE4DOJ.js +0 -72
- package/dist/web-assets/assets/index-D5PzIndO.js +0 -308
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
loadMergedEnv
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-QF22MYDJ.js";
|
|
5
5
|
import {
|
|
6
6
|
applyIsolation
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-IQXBMFZG.js";
|
|
8
8
|
import {
|
|
9
9
|
agentDir,
|
|
10
10
|
findAgent,
|
|
11
11
|
findVariant,
|
|
12
12
|
setAgentRunning,
|
|
13
13
|
setVariantRunning,
|
|
14
|
-
|
|
14
|
+
stateDir,
|
|
15
15
|
voluteHome
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-DP2DX4WV.js";
|
|
17
17
|
|
|
18
18
|
// src/lib/agent-manager.ts
|
|
19
19
|
import { execFile, spawn } from "child_process";
|
|
20
|
-
import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync2,
|
|
20
|
+
import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
21
21
|
import { resolve } from "path";
|
|
22
22
|
import { promisify } from "util";
|
|
23
23
|
|
|
@@ -59,14 +59,21 @@ function clearJsonMap(path, map) {
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
// src/lib/rotating-log.ts
|
|
62
|
-
import {
|
|
62
|
+
import {
|
|
63
|
+
createWriteStream,
|
|
64
|
+
existsSync as existsSync2,
|
|
65
|
+
renameSync,
|
|
66
|
+
rmSync,
|
|
67
|
+
statSync
|
|
68
|
+
} from "fs";
|
|
63
69
|
import { Writable } from "stream";
|
|
64
70
|
var MAX_SIZE = 10 * 1024 * 1024;
|
|
65
71
|
var RotatingLog = class extends Writable {
|
|
66
|
-
constructor(path, maxSize = MAX_SIZE) {
|
|
72
|
+
constructor(path, maxSize = MAX_SIZE, maxFiles = 5) {
|
|
67
73
|
super();
|
|
68
74
|
this.path = path;
|
|
69
75
|
this.maxSize = maxSize;
|
|
76
|
+
this.maxFiles = maxFiles;
|
|
70
77
|
this.on("error", () => {
|
|
71
78
|
});
|
|
72
79
|
try {
|
|
@@ -82,6 +89,13 @@ var RotatingLog = class extends Writable {
|
|
|
82
89
|
this.size += chunk.length;
|
|
83
90
|
if (this.size > this.maxSize) {
|
|
84
91
|
try {
|
|
92
|
+
const oldest = `${this.path}.${this.maxFiles}`;
|
|
93
|
+
if (existsSync2(oldest)) rmSync(oldest);
|
|
94
|
+
for (let i = this.maxFiles - 1; i >= 1; i--) {
|
|
95
|
+
const from = `${this.path}.${i}`;
|
|
96
|
+
const to = `${this.path}.${i + 1}`;
|
|
97
|
+
if (existsSync2(from)) renameSync(from, to);
|
|
98
|
+
}
|
|
85
99
|
renameSync(this.path, `${this.path}.1`);
|
|
86
100
|
const oldStream = this.stream;
|
|
87
101
|
this.stream = createWriteStream(this.path);
|
|
@@ -99,6 +113,9 @@ var RotatingLog = class extends Writable {
|
|
|
99
113
|
|
|
100
114
|
// src/lib/agent-manager.ts
|
|
101
115
|
var execFileAsync = promisify(execFile);
|
|
116
|
+
function agentPidPath(name) {
|
|
117
|
+
return resolve(stateDir(name), "agent.pid");
|
|
118
|
+
}
|
|
102
119
|
var MAX_RESTART_ATTEMPTS = 5;
|
|
103
120
|
var BASE_RESTART_DELAY = 3e3;
|
|
104
121
|
var MAX_RESTART_DELAY = 6e4;
|
|
@@ -107,6 +124,7 @@ var AgentManager = class {
|
|
|
107
124
|
stopping = /* @__PURE__ */ new Set();
|
|
108
125
|
shuttingDown = false;
|
|
109
126
|
restartAttempts = /* @__PURE__ */ new Map();
|
|
127
|
+
pendingContext = /* @__PURE__ */ new Map();
|
|
110
128
|
resolveTarget(name) {
|
|
111
129
|
const [baseName, variantName] = name.split("@", 2);
|
|
112
130
|
const entry = findAgent(baseName);
|
|
@@ -127,6 +145,34 @@ var AgentManager = class {
|
|
|
127
145
|
const target = this.resolveTarget(name);
|
|
128
146
|
const { dir, isVariant, baseName, variantName } = target;
|
|
129
147
|
const port = target.port;
|
|
148
|
+
const pidFile = agentPidPath(name);
|
|
149
|
+
try {
|
|
150
|
+
if (existsSync3(pidFile)) {
|
|
151
|
+
const stalePid = parseInt(readFileSync2(pidFile, "utf-8").trim(), 10);
|
|
152
|
+
if (stalePid > 0) {
|
|
153
|
+
try {
|
|
154
|
+
process.kill(stalePid, 0);
|
|
155
|
+
const { stdout } = await execFileAsync("ps", ["-p", String(stalePid), "-o", "args="]);
|
|
156
|
+
if (stdout.includes("server.ts")) {
|
|
157
|
+
console.error(`[daemon] killing stale agent process ${stalePid} for ${name}`);
|
|
158
|
+
process.kill(-stalePid, "SIGTERM");
|
|
159
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
160
|
+
} else {
|
|
161
|
+
console.error(
|
|
162
|
+
`[daemon] stale PID ${stalePid} for ${name} is not an agent process, skipping`
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
} catch (err) {
|
|
166
|
+
if (err.code !== "ESRCH") {
|
|
167
|
+
console.error(`[daemon] failed to check/kill stale process for ${name}:`, err);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
rmSync2(pidFile, { force: true });
|
|
172
|
+
}
|
|
173
|
+
} catch (err) {
|
|
174
|
+
console.error(`[daemon] failed to read PID file for ${name}:`, err);
|
|
175
|
+
}
|
|
130
176
|
try {
|
|
131
177
|
const res = await fetch(`http://127.0.0.1:${port}/health`);
|
|
132
178
|
if (res.ok) {
|
|
@@ -136,13 +182,18 @@ var AgentManager = class {
|
|
|
136
182
|
}
|
|
137
183
|
} catch {
|
|
138
184
|
}
|
|
139
|
-
const
|
|
140
|
-
const logsDir = resolve(voluteDir, "logs");
|
|
185
|
+
const logsDir = resolve(stateDir(name), "logs");
|
|
141
186
|
mkdirSync(logsDir, { recursive: true });
|
|
142
187
|
const logStream = new RotatingLog(resolve(logsDir, "agent.log"));
|
|
143
|
-
const agentEnv = loadMergedEnv(
|
|
144
|
-
const
|
|
145
|
-
|
|
188
|
+
const agentEnv = loadMergedEnv(name);
|
|
189
|
+
const env = {
|
|
190
|
+
...process.env,
|
|
191
|
+
...agentEnv,
|
|
192
|
+
VOLUTE_AGENT: name,
|
|
193
|
+
VOLUTE_STATE_DIR: stateDir(name),
|
|
194
|
+
VOLUTE_AGENT_DIR: dir,
|
|
195
|
+
VOLUTE_AGENT_PORT: String(port)
|
|
196
|
+
};
|
|
146
197
|
const tsxBin = resolve(dir, "node_modules", ".bin", "tsx");
|
|
147
198
|
const spawnOpts = {
|
|
148
199
|
cwd: dir,
|
|
@@ -185,82 +236,83 @@ var AgentManager = class {
|
|
|
185
236
|
}
|
|
186
237
|
throw err;
|
|
187
238
|
}
|
|
239
|
+
if (child.pid) {
|
|
240
|
+
try {
|
|
241
|
+
writeFileSync2(pidFile, String(child.pid));
|
|
242
|
+
} catch (err) {
|
|
243
|
+
console.error(`[daemon] failed to write PID file for ${name}:`, err);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
188
246
|
if (this.restartAttempts.delete(name)) this.saveCrashAttempts();
|
|
189
|
-
this.setupCrashRecovery(name, child
|
|
247
|
+
this.setupCrashRecovery(name, child);
|
|
190
248
|
if (isVariant) {
|
|
191
249
|
setVariantRunning(baseName, variantName, true);
|
|
192
250
|
} else {
|
|
193
251
|
setAgentRunning(name, true);
|
|
194
252
|
}
|
|
195
253
|
console.error(`[daemon] started agent ${name} on port ${port}`);
|
|
254
|
+
await this.deliverPendingContext(name);
|
|
255
|
+
}
|
|
256
|
+
setPendingContext(name, context) {
|
|
257
|
+
this.pendingContext.set(name, context);
|
|
196
258
|
}
|
|
197
|
-
|
|
259
|
+
async deliverPendingContext(name) {
|
|
260
|
+
const context = this.pendingContext.get(name);
|
|
261
|
+
if (!context) return;
|
|
262
|
+
const tracked = this.agents.get(name);
|
|
263
|
+
if (!tracked) return;
|
|
264
|
+
this.pendingContext.delete(name);
|
|
265
|
+
const parts = [];
|
|
266
|
+
if (context.type === "merge" || context.type === "merged") {
|
|
267
|
+
parts.push(`[system] Variant "${context.name}" has been merged and you have been restarted.`);
|
|
268
|
+
} else {
|
|
269
|
+
parts.push("[system] You have been restarted.");
|
|
270
|
+
}
|
|
271
|
+
if (context.summary) parts.push(`Changes: ${context.summary}`);
|
|
272
|
+
if (context.justification) parts.push(`Why: ${context.justification}`);
|
|
273
|
+
if (context.memory) parts.push(`Context: ${context.memory}`);
|
|
274
|
+
try {
|
|
275
|
+
await fetch(`http://127.0.0.1:${tracked.port}/message`, {
|
|
276
|
+
method: "POST",
|
|
277
|
+
headers: { "Content-Type": "application/json" },
|
|
278
|
+
body: JSON.stringify({
|
|
279
|
+
content: [{ type: "text", text: parts.join("\n") }],
|
|
280
|
+
channel: "system"
|
|
281
|
+
})
|
|
282
|
+
});
|
|
283
|
+
} catch (err) {
|
|
284
|
+
console.error(`[daemon] failed to deliver pending context to ${name}:`, err);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
setupCrashRecovery(name, child) {
|
|
198
288
|
child.on("exit", async (code) => {
|
|
199
289
|
this.agents.delete(name);
|
|
200
290
|
if (this.shuttingDown || this.stopping.has(name)) return;
|
|
201
291
|
console.error(`[daemon] agent ${name} exited with code ${code}`);
|
|
202
|
-
const
|
|
203
|
-
if (
|
|
204
|
-
console.error(`[daemon]
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
const attempts = this.restartAttempts.get(name) ?? 0;
|
|
211
|
-
if (attempts >= MAX_RESTART_ATTEMPTS) {
|
|
212
|
-
console.error(`[daemon] ${name} crashed ${attempts} times \u2014 giving up on restart`);
|
|
213
|
-
const [base, variant] = name.split("@", 2);
|
|
214
|
-
if (variant) {
|
|
215
|
-
setVariantRunning(base, variant, false);
|
|
216
|
-
} else {
|
|
217
|
-
setAgentRunning(name, false);
|
|
218
|
-
}
|
|
219
|
-
return;
|
|
292
|
+
const attempts = this.restartAttempts.get(name) ?? 0;
|
|
293
|
+
if (attempts >= MAX_RESTART_ATTEMPTS) {
|
|
294
|
+
console.error(`[daemon] ${name} crashed ${attempts} times \u2014 giving up on restart`);
|
|
295
|
+
const [base, variant] = name.split("@", 2);
|
|
296
|
+
if (variant) {
|
|
297
|
+
setVariantRunning(base, variant, false);
|
|
298
|
+
} else {
|
|
299
|
+
setAgentRunning(name, false);
|
|
220
300
|
}
|
|
221
|
-
|
|
222
|
-
this.restartAttempts.set(name, attempts + 1);
|
|
223
|
-
this.saveCrashAttempts();
|
|
224
|
-
console.error(
|
|
225
|
-
`[daemon] crash recovery for ${name} \u2014 attempt ${attempts + 1}/${MAX_RESTART_ATTEMPTS}, restarting in ${delay}ms`
|
|
226
|
-
);
|
|
227
|
-
setTimeout(() => {
|
|
228
|
-
if (this.shuttingDown) return;
|
|
229
|
-
this.startAgent(name).catch((err) => {
|
|
230
|
-
console.error(`[daemon] failed to restart ${name}:`, err);
|
|
231
|
-
});
|
|
232
|
-
}, delay);
|
|
301
|
+
return;
|
|
233
302
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
if (err) {
|
|
245
|
-
console.error(`[daemon] invalid variant name in restart.json for ${name}: ${err}`);
|
|
246
|
-
return false;
|
|
247
|
-
}
|
|
248
|
-
console.error(`[daemon] merging variant for ${name}: ${signal.name}`);
|
|
249
|
-
const mergeArgs = ["merge", name, signal.name];
|
|
250
|
-
if (signal.summary) mergeArgs.push("--summary", signal.summary);
|
|
251
|
-
if (signal.justification) mergeArgs.push("--justification", signal.justification);
|
|
252
|
-
if (signal.memory) mergeArgs.push("--memory", signal.memory);
|
|
253
|
-
const { VOLUTE_DAEMON_TOKEN: _t, ...mergeEnv } = process.env;
|
|
254
|
-
await execFileAsync("volute", mergeArgs, {
|
|
255
|
-
cwd: dir,
|
|
256
|
-
env: { ...mergeEnv, VOLUTE_SUPERVISOR: "1" }
|
|
303
|
+
const delay = Math.min(BASE_RESTART_DELAY * 2 ** attempts, MAX_RESTART_DELAY);
|
|
304
|
+
this.restartAttempts.set(name, attempts + 1);
|
|
305
|
+
this.saveCrashAttempts();
|
|
306
|
+
console.error(
|
|
307
|
+
`[daemon] crash recovery for ${name} \u2014 attempt ${attempts + 1}/${MAX_RESTART_ATTEMPTS}, restarting in ${delay}ms`
|
|
308
|
+
);
|
|
309
|
+
setTimeout(() => {
|
|
310
|
+
if (this.shuttingDown) return;
|
|
311
|
+
this.startAgent(name).catch((err) => {
|
|
312
|
+
console.error(`[daemon] failed to restart ${name}:`, err);
|
|
257
313
|
});
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
} catch (e) {
|
|
261
|
-
console.error(`[daemon] failed to handle restart for ${name}:`, e);
|
|
262
|
-
return false;
|
|
263
|
-
}
|
|
314
|
+
}, delay);
|
|
315
|
+
});
|
|
264
316
|
}
|
|
265
317
|
async stopAgent(name) {
|
|
266
318
|
const tracked = this.agents.get(name);
|
|
@@ -285,11 +337,14 @@ var AgentManager = class {
|
|
|
285
337
|
});
|
|
286
338
|
this.stopping.delete(name);
|
|
287
339
|
if (this.restartAttempts.delete(name)) this.saveCrashAttempts();
|
|
288
|
-
|
|
289
|
-
if (
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
340
|
+
rmSync2(agentPidPath(name), { force: true });
|
|
341
|
+
if (!this.shuttingDown) {
|
|
342
|
+
const [baseName, variantName] = name.split("@", 2);
|
|
343
|
+
if (variantName) {
|
|
344
|
+
setVariantRunning(baseName, variantName, false);
|
|
345
|
+
} else {
|
|
346
|
+
setAgentRunning(name, false);
|
|
347
|
+
}
|
|
293
348
|
}
|
|
294
349
|
console.error(`[daemon] stopped agent ${name}`);
|
|
295
350
|
}
|
|
@@ -4,8 +4,7 @@ import {
|
|
|
4
4
|
} from "./chunk-D424ZQGI.js";
|
|
5
5
|
import {
|
|
6
6
|
voluteHome
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-K3NQKI34.js";
|
|
7
|
+
} from "./chunk-DP2DX4WV.js";
|
|
9
8
|
|
|
10
9
|
// src/commands/up.ts
|
|
11
10
|
import { spawn } from "child_process";
|
|
@@ -103,6 +102,7 @@ async function run(args) {
|
|
|
103
102
|
console.error(`Check logs: ${logFile}`);
|
|
104
103
|
process.exit(1);
|
|
105
104
|
}
|
|
105
|
+
|
|
106
106
|
export {
|
|
107
107
|
readGlobalConfig,
|
|
108
108
|
run
|
package/dist/cli.js
CHANGED
|
@@ -9,46 +9,52 @@ if (!process.env.VOLUTE_HOME) {
|
|
|
9
9
|
var command = process.argv[2];
|
|
10
10
|
var args = process.argv.slice(3);
|
|
11
11
|
if (command === "--version" || command === "-v") {
|
|
12
|
-
const { default: pkg } = await import("./package-
|
|
12
|
+
const { default: pkg } = await import("./package-5UCKNK6J.js");
|
|
13
13
|
console.log(pkg.version);
|
|
14
14
|
process.exit(0);
|
|
15
15
|
}
|
|
16
16
|
switch (command) {
|
|
17
17
|
case "agent":
|
|
18
|
-
await import("./agent-
|
|
18
|
+
await import("./agent-YORVRB6I.js").then((m) => m.run(args));
|
|
19
19
|
break;
|
|
20
|
-
case "
|
|
21
|
-
await import("./
|
|
20
|
+
case "send":
|
|
21
|
+
await import("./send-BNC2S5BY.js").then((m) => m.run(args));
|
|
22
|
+
break;
|
|
23
|
+
case "history":
|
|
24
|
+
await import("./history-PXJVYLVY.js").then((m) => m.run(args));
|
|
22
25
|
break;
|
|
23
26
|
case "variant":
|
|
24
|
-
await import("./variant-
|
|
27
|
+
await import("./variant-RKXPN5DH.js").then((m) => m.run(args));
|
|
25
28
|
break;
|
|
26
29
|
case "connector":
|
|
27
|
-
await import("./connector-
|
|
30
|
+
await import("./connector-ZP6MEFF4.js").then((m) => m.run(args));
|
|
28
31
|
break;
|
|
29
32
|
case "channel":
|
|
30
|
-
await import("./channel-
|
|
33
|
+
await import("./channel-RDGHBFSI.js").then((m) => m.run(args));
|
|
31
34
|
break;
|
|
32
35
|
case "schedule":
|
|
33
|
-
await import("./schedule-
|
|
36
|
+
await import("./schedule-HCUCBNQI.js").then((m) => m.run(args));
|
|
34
37
|
break;
|
|
35
38
|
case "env":
|
|
36
|
-
await import("./env-
|
|
39
|
+
await import("./env-KMNYGVZ2.js").then((m) => m.run(args));
|
|
37
40
|
break;
|
|
38
41
|
case "up":
|
|
39
|
-
await import("./up-
|
|
42
|
+
await import("./up-V6EAA7OZ.js").then((m) => m.run(args));
|
|
40
43
|
break;
|
|
41
44
|
case "down":
|
|
42
|
-
await import("./down-
|
|
45
|
+
await import("./down-O4EWZTVA.js").then((m) => m.run(args));
|
|
46
|
+
break;
|
|
47
|
+
case "restart":
|
|
48
|
+
await import("./daemon-restart-CPBLMMRI.js").then((m) => m.run(args));
|
|
43
49
|
break;
|
|
44
50
|
case "setup":
|
|
45
|
-
await import("./setup-
|
|
51
|
+
await import("./setup-JXDCJX7W.js").then((m) => m.run(args));
|
|
46
52
|
break;
|
|
47
53
|
case "service":
|
|
48
|
-
await import("./service-
|
|
54
|
+
await import("./service-R4MCNBOA.js").then((m) => m.run(args));
|
|
49
55
|
break;
|
|
50
56
|
case "update":
|
|
51
|
-
await import("./update-
|
|
57
|
+
await import("./update-EUCZ7XGG.js").then((m) => m.run(args));
|
|
52
58
|
break;
|
|
53
59
|
case "--help":
|
|
54
60
|
case "-h":
|
|
@@ -67,8 +73,8 @@ Commands:
|
|
|
67
73
|
volute agent upgrade <name> Upgrade agent to latest template
|
|
68
74
|
volute agent import <path> Import an OpenClaw workspace
|
|
69
75
|
|
|
70
|
-
volute
|
|
71
|
-
volute
|
|
76
|
+
volute send <target> "<msg>" Send a message (agent DM, channel, etc.)
|
|
77
|
+
volute history [--agent <name>] View message history
|
|
72
78
|
|
|
73
79
|
volute variant create <name> Create a variant (worktree + server)
|
|
74
80
|
volute variant list List variants for an agent
|
|
@@ -79,7 +85,6 @@ Commands:
|
|
|
79
85
|
volute connector disconnect <type> Disable a connector for an agent
|
|
80
86
|
|
|
81
87
|
volute channel read <uri> Read recent messages from a channel
|
|
82
|
-
volute channel send <uri> "<msg>" Send a message to a channel
|
|
83
88
|
volute channel list [<platform>] List conversations on a platform
|
|
84
89
|
volute channel users <platform> List users on a platform
|
|
85
90
|
volute channel create <platform> ... Create a conversation on a platform
|
|
@@ -92,6 +97,7 @@ Commands:
|
|
|
92
97
|
|
|
93
98
|
volute up [--port N] Start the daemon (default: 4200)
|
|
94
99
|
volute down Stop the daemon
|
|
100
|
+
volute restart [--port N] Restart the daemon
|
|
95
101
|
|
|
96
102
|
volute service install [--port N] Install as system service (auto-start)
|
|
97
103
|
volute service uninstall Remove system service
|
|
@@ -105,7 +111,7 @@ Options:
|
|
|
105
111
|
--version, -v Show version number
|
|
106
112
|
--help, -h Show this help message
|
|
107
113
|
|
|
108
|
-
Agent-scoped commands (variant, connector, schedule, channel
|
|
114
|
+
Agent-scoped commands (send, history, variant, connector, schedule, channel)
|
|
109
115
|
use --agent <name> or VOLUTE_AGENT env var to identify the agent.`);
|
|
110
116
|
break;
|
|
111
117
|
default:
|
|
@@ -114,7 +120,7 @@ Run 'volute --help' for usage.`);
|
|
|
114
120
|
process.exit(1);
|
|
115
121
|
}
|
|
116
122
|
if (command !== "update") {
|
|
117
|
-
import("./update-check-
|
|
123
|
+
import("./update-check-SM4244SU.js").then((m) => m.checkForUpdate()).then((result) => {
|
|
118
124
|
if (result.updateAvailable) {
|
|
119
125
|
console.error(`
|
|
120
126
|
Update available: ${result.current} \u2192 ${result.latest}`);
|
|
@@ -6,16 +6,16 @@ import {
|
|
|
6
6
|
agentEnvPath,
|
|
7
7
|
readEnv,
|
|
8
8
|
writeEnv
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-QF22MYDJ.js";
|
|
10
10
|
import {
|
|
11
11
|
parseArgs
|
|
12
12
|
} from "./chunk-D424ZQGI.js";
|
|
13
13
|
import {
|
|
14
14
|
daemonFetch
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-23L3MKEV.js";
|
|
16
16
|
import {
|
|
17
17
|
agentDir
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-DP2DX4WV.js";
|
|
19
19
|
import "./chunk-K3NQKI34.js";
|
|
20
20
|
|
|
21
21
|
// src/commands/connector.ts
|
|
@@ -1,27 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
buildChannelSlug,
|
|
4
|
-
fireAndForget,
|
|
5
|
-
handleAgentMessage,
|
|
6
4
|
loadEnv,
|
|
7
5
|
loadFollowedChannels,
|
|
8
6
|
reportTyping,
|
|
7
|
+
sendToAgent,
|
|
9
8
|
slugify,
|
|
10
|
-
splitMessage,
|
|
11
9
|
writeChannelEntry
|
|
12
|
-
} from "../chunk-
|
|
10
|
+
} from "../chunk-N6MLQ26B.js";
|
|
11
|
+
import "../chunk-DP2DX4WV.js";
|
|
13
12
|
import "../chunk-K3NQKI34.js";
|
|
14
13
|
|
|
15
14
|
// src/connectors/discord.ts
|
|
16
|
-
import {
|
|
17
|
-
AttachmentBuilder,
|
|
18
|
-
ChannelType,
|
|
19
|
-
Client,
|
|
20
|
-
Events,
|
|
21
|
-
GatewayIntentBits,
|
|
22
|
-
Partials
|
|
23
|
-
} from "discord.js";
|
|
24
|
-
var DISCORD_MAX_LENGTH = 2e3;
|
|
15
|
+
import { ChannelType, Client, Events, GatewayIntentBits, Partials } from "discord.js";
|
|
25
16
|
var TYPING_INTERVAL_MS = 8e3;
|
|
26
17
|
var env = loadEnv();
|
|
27
18
|
var token = process.env.DISCORD_TOKEN;
|
|
@@ -102,14 +93,16 @@ client.on(Events.MessageCreate, async (message) => {
|
|
|
102
93
|
channelName: channelName ?? message.channelId,
|
|
103
94
|
serverName: message.guild?.name
|
|
104
95
|
});
|
|
105
|
-
|
|
106
|
-
writeChannelEntry(env.
|
|
96
|
+
try {
|
|
97
|
+
writeChannelEntry(env.agentName, channelKey, {
|
|
107
98
|
platformId: message.channelId,
|
|
108
99
|
platform: "discord",
|
|
109
100
|
name: channelName ? `#${channelName}` : void 0,
|
|
110
101
|
server: message.guild?.name,
|
|
111
102
|
type: isDM ? "dm" : "channel"
|
|
112
103
|
});
|
|
104
|
+
} catch (err) {
|
|
105
|
+
console.error(`[discord] failed to write channel entry for ${channelKey}:`, err);
|
|
113
106
|
}
|
|
114
107
|
const participantCount = isDM ? 2 : message.guild?.memberCount;
|
|
115
108
|
const payload = {
|
|
@@ -122,15 +115,20 @@ client.on(Events.MessageCreate, async (message) => {
|
|
|
122
115
|
...message.guild?.name ? { serverName: message.guild.name } : {},
|
|
123
116
|
...participantCount ? { participantCount } : {}
|
|
124
117
|
};
|
|
118
|
+
reportTyping(env, channelKey, senderName, false);
|
|
125
119
|
if (isFollowedChannel && !isMentioned) {
|
|
126
|
-
await
|
|
120
|
+
const result = await sendToAgent(env, payload);
|
|
121
|
+
if (!result.ok)
|
|
122
|
+
message.reply(result.error ?? "Failed to process message").catch((err) => {
|
|
123
|
+
console.warn(`[discord] failed to send error reply: ${err}`);
|
|
124
|
+
});
|
|
127
125
|
return;
|
|
128
126
|
}
|
|
129
127
|
await handleDiscordMessage(message, payload);
|
|
130
128
|
});
|
|
131
129
|
client.on(Events.TypingStart, (typing) => {
|
|
132
130
|
if (typing.user.bot) return;
|
|
133
|
-
const sender = typing.
|
|
131
|
+
const sender = typing.user.displayName || typing.user.username || typing.user.id || "unknown";
|
|
134
132
|
const typingChannel = typing.guild ? `discord:${slugify(typing.guild.name)}/${slugify("name" in typing.channel ? String(typing.channel.name) : typing.channel.id)}` : `discord:@${slugify(typing.user.username ?? typing.user.id)}`;
|
|
135
133
|
reportTyping(env, typingChannel, sender, true);
|
|
136
134
|
});
|
|
@@ -138,54 +136,19 @@ async function handleDiscordMessage(message, payload) {
|
|
|
138
136
|
const channel = message.channel;
|
|
139
137
|
if (!("sendTyping" in channel)) return;
|
|
140
138
|
const typingInterval = setInterval(() => {
|
|
141
|
-
channel.sendTyping().catch(() => {
|
|
139
|
+
channel.sendTyping().catch((err) => {
|
|
140
|
+
console.warn(`[discord] sendTyping failed: ${err}`);
|
|
142
141
|
});
|
|
143
142
|
}, TYPING_INTERVAL_MS);
|
|
144
|
-
channel.sendTyping().catch(() => {
|
|
143
|
+
channel.sendTyping().catch((err) => {
|
|
144
|
+
console.warn(`[discord] sendTyping failed: ${err}`);
|
|
145
145
|
});
|
|
146
|
-
let replied = false;
|
|
147
146
|
try {
|
|
148
|
-
await
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
const ext = img.media_type.split("/")[1] || "png";
|
|
154
|
-
return new AttachmentBuilder(Buffer.from(img.data, "base64"), {
|
|
155
|
-
name: `image-${i}.${ext}`
|
|
156
|
-
});
|
|
157
|
-
});
|
|
158
|
-
if (chunks.length === 0 && imageFiles.length > 0) {
|
|
159
|
-
const sendFn = replied ? channel.send.bind(channel) : message.reply.bind(message);
|
|
160
|
-
await sendFn({ content: "\u200B", files: imageFiles }).catch((err) => {
|
|
161
|
-
console.error(`Failed to send message: ${err}`);
|
|
162
|
-
});
|
|
163
|
-
replied = true;
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
for (let i = 0; i < chunks.length; i++) {
|
|
167
|
-
const isLast = i === chunks.length - 1;
|
|
168
|
-
const opts = {
|
|
169
|
-
content: chunks[i]
|
|
170
|
-
};
|
|
171
|
-
if (isLast && imageFiles.length > 0) opts.files = imageFiles;
|
|
172
|
-
try {
|
|
173
|
-
if (!replied) {
|
|
174
|
-
await message.reply(opts);
|
|
175
|
-
replied = true;
|
|
176
|
-
} else {
|
|
177
|
-
await channel.send(opts);
|
|
178
|
-
}
|
|
179
|
-
} catch (err) {
|
|
180
|
-
console.error(`Failed to send message: ${err}`);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
},
|
|
184
|
-
onError: async (msg) => {
|
|
185
|
-
await message.reply(msg).catch(() => {
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
});
|
|
147
|
+
const result = await sendToAgent(env, payload);
|
|
148
|
+
if (!result.ok)
|
|
149
|
+
message.reply(result.error ?? "Failed to process message").catch((err) => {
|
|
150
|
+
console.warn(`[discord] failed to send error reply: ${err}`);
|
|
151
|
+
});
|
|
189
152
|
} finally {
|
|
190
153
|
clearInterval(typingInterval);
|
|
191
154
|
}
|