volute 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agent-Z2B6EFEQ.js → agent-7JF7MT73.js} +13 -9
- package/dist/{agent-manager-PXBKA2GK.js → agent-manager-IMZ7ZMBF.js} +4 -4
- package/dist/channel-SMCNOIVQ.js +262 -0
- package/dist/{chunk-MW2KFO3B.js → chunk-62X577Y7.js} +10 -8
- package/dist/chunk-7ACDT3P2.js +265 -0
- package/dist/{chunk-MXUCNIBG.js → chunk-BX7KI4S3.js} +68 -3
- package/dist/{up-7ILD7GU7.js → chunk-EG45HBSJ.js} +16 -4
- package/dist/{chunk-HE67X4T6.js → chunk-H7AMDUIA.js} +1 -1
- package/dist/{chunk-7L4AN5D4.js → chunk-JR4UXCTO.js} +1 -1
- package/dist/{down-O7IFZLVJ.js → chunk-LLJNZPCU.js} +48 -13
- package/dist/{chunk-5X7HGB6L.js → chunk-NKXULRSW.js} +2 -1
- package/dist/{chunk-UX25Z2ND.js → chunk-UWHWAPGO.js} +7 -0
- package/dist/{chunk-UAVD2AHX.js → chunk-W76KWE23.js} +1 -1
- package/dist/chunk-ZZOOTYXK.js +583 -0
- package/dist/cli.js +22 -21
- package/dist/{connector-LYEMXQEV.js → connector-Y7JPNROO.js} +3 -3
- package/dist/connectors/discord.js +38 -7
- package/dist/connectors/slack.js +22 -3
- package/dist/connectors/telegram.js +34 -4
- package/dist/{create-RVCZN6HE.js → create-G525LWEA.js} +2 -2
- package/dist/{daemon-client-ZY6UUN2M.js → daemon-client-442IV43D.js} +2 -2
- package/dist/daemon-restart-4HVEKYFY.js +23 -0
- package/dist/daemon.js +1042 -809
- package/dist/{delete-3QH7VYIN.js → delete-UOU4AFQN.js} +7 -3
- package/dist/down-AZVH5TCD.js +11 -0
- package/dist/{env-4D4REPJF.js → env-7GLUJCWS.js} +2 -2
- package/dist/{history-OEONB53Z.js → history-H72ZUIBN.js} +2 -2
- package/dist/{import-MXJB2EII.js → import-AVKQJDYC.js} +2 -2
- package/dist/{logs-DF342W4M.js → logs-EDGK26AK.js} +1 -1
- package/dist/{message-ADHWFHSI.js → message-SCOQDR3P.js} +2 -2
- package/dist/{package-VQOE7JNH.js → package-T2WAVJOU.js} +1 -1
- package/dist/restart-O4ETYLJF.js +29 -0
- package/dist/{schedule-NAG6F463.js → schedule-S6QVC5ON.js} +2 -2
- package/dist/send-G7PE4DOJ.js +72 -0
- package/dist/{setup-RPRRGG2F.js → setup-F4TCWVSP.js} +2 -2
- package/dist/{start-TUOXDSFL.js → start-VHQ7LNWM.js} +2 -2
- package/dist/{status-A36EHRO4.js → status-QAJWXKMZ.js} +2 -2
- package/dist/{stop-AOJZLQ5X.js → stop-CAGCT5NI.js} +2 -2
- package/dist/up-RWZF6MLT.js +12 -0
- package/dist/{update-LPSIAWQ2.js → update-F7QWV2LB.js} +2 -2
- package/dist/{update-check-Y33QDCFL.js → update-check-B4J6IEQ4.js} +2 -2
- package/dist/{upgrade-FX2TKJ2S.js → upgrade-YXKPWDRU.js} +2 -2
- package/dist/{variant-LAB67OC2.js → variant-4Z6W3PP6.js} +2 -2
- package/dist/web-assets/assets/index-B1CqjUYD.js +308 -0
- package/dist/web-assets/index.html +1 -1
- package/package.json +1 -1
- package/templates/_base/.init/.config/scripts/session-reader.ts +59 -0
- package/templates/_base/_skills/sessions/SKILL.md +49 -0
- package/templates/_base/_skills/volute-agent/SKILL.md +13 -9
- package/templates/_base/src/lib/format-prefix.ts +6 -0
- package/templates/_base/src/lib/router.ts +30 -3
- package/templates/_base/src/lib/session-monitor.ts +400 -0
- package/templates/_base/src/lib/types.ts +2 -0
- package/templates/agent-sdk/src/agent.ts +16 -0
- package/templates/agent-sdk/src/lib/hooks/session-context.ts +32 -0
- package/templates/pi/src/agent.ts +7 -1
- package/templates/pi/src/lib/session-context-extension.ts +33 -0
- package/dist/channel-MK5OK2SI.js +0 -113
- package/dist/chunk-SMISE4SV.js +0 -226
- package/dist/conversation-ERXEQZTY.js +0 -163
- package/dist/send-66QMKRUH.js +0 -75
- package/dist/web-assets/assets/index-BbRmoxoA.js +0 -308
|
@@ -0,0 +1,583 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
resolveChannelId,
|
|
4
|
+
slugify,
|
|
5
|
+
writeChannelEntry
|
|
6
|
+
} from "./chunk-BX7KI4S3.js";
|
|
7
|
+
import {
|
|
8
|
+
voluteHome
|
|
9
|
+
} from "./chunk-UWHWAPGO.js";
|
|
10
|
+
import {
|
|
11
|
+
__export
|
|
12
|
+
} from "./chunk-K3NQKI34.js";
|
|
13
|
+
|
|
14
|
+
// src/lib/channels/discord.ts
|
|
15
|
+
var discord_exports = {};
|
|
16
|
+
__export(discord_exports, {
|
|
17
|
+
createConversation: () => createConversation,
|
|
18
|
+
listConversations: () => listConversations,
|
|
19
|
+
listUsers: () => listUsers,
|
|
20
|
+
read: () => read,
|
|
21
|
+
send: () => send
|
|
22
|
+
});
|
|
23
|
+
var API_BASE = "https://discord.com/api/v10";
|
|
24
|
+
function requireToken(env) {
|
|
25
|
+
const token = env.DISCORD_TOKEN;
|
|
26
|
+
if (!token) throw new Error("DISCORD_TOKEN not set");
|
|
27
|
+
return token;
|
|
28
|
+
}
|
|
29
|
+
async function discordGet(token, path) {
|
|
30
|
+
const res = await fetch(`${API_BASE}${path}`, {
|
|
31
|
+
headers: { Authorization: `Bot ${token}` }
|
|
32
|
+
});
|
|
33
|
+
if (!res.ok) {
|
|
34
|
+
throw new Error(`Discord API error: ${res.status} ${res.statusText}`);
|
|
35
|
+
}
|
|
36
|
+
return res.json();
|
|
37
|
+
}
|
|
38
|
+
async function read(env, channelSlug, limit) {
|
|
39
|
+
const token = requireToken(env);
|
|
40
|
+
const channelId = resolveChannelId2(env, channelSlug);
|
|
41
|
+
const res = await fetch(`${API_BASE}/channels/${channelId}/messages?limit=${limit}`, {
|
|
42
|
+
headers: { Authorization: `Bot ${token}` }
|
|
43
|
+
});
|
|
44
|
+
if (!res.ok) {
|
|
45
|
+
throw new Error(`Discord API error: ${res.status} ${res.statusText}`);
|
|
46
|
+
}
|
|
47
|
+
const messages = await res.json();
|
|
48
|
+
return messages.reverse().map((m) => `${m.author.username}: ${m.content}`).join("\n");
|
|
49
|
+
}
|
|
50
|
+
async function send(env, channelSlug, message) {
|
|
51
|
+
const token = requireToken(env);
|
|
52
|
+
const channelId = resolveChannelId2(env, channelSlug);
|
|
53
|
+
const res = await fetch(`${API_BASE}/channels/${channelId}/messages`, {
|
|
54
|
+
method: "POST",
|
|
55
|
+
headers: {
|
|
56
|
+
Authorization: `Bot ${token}`,
|
|
57
|
+
"Content-Type": "application/json"
|
|
58
|
+
},
|
|
59
|
+
body: JSON.stringify({ content: message })
|
|
60
|
+
});
|
|
61
|
+
if (!res.ok) {
|
|
62
|
+
throw new Error(`Discord API error: ${res.status} ${res.statusText}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async function listConversations(env) {
|
|
66
|
+
const token = requireToken(env);
|
|
67
|
+
const results = [];
|
|
68
|
+
const guilds = await discordGet(token, "/users/@me/guilds");
|
|
69
|
+
for (const guild of guilds) {
|
|
70
|
+
const channels = await discordGet(token, `/guilds/${guild.id}/channels`);
|
|
71
|
+
for (const ch of channels) {
|
|
72
|
+
if (ch.type !== 0) continue;
|
|
73
|
+
results.push({
|
|
74
|
+
id: `discord:${slugify(guild.name)}/${slugify(ch.name)}`,
|
|
75
|
+
platformId: ch.id,
|
|
76
|
+
name: `#${ch.name}`,
|
|
77
|
+
type: "channel"
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const dms = await discordGet(token, "/users/@me/channels");
|
|
82
|
+
for (const dm of dms) {
|
|
83
|
+
const recipients = dm.recipients?.map((r) => r.username) ?? [];
|
|
84
|
+
const slug = recipients.length === 0 ? `discord:${dm.id}` : recipients.length === 1 ? `discord:@${slugify(recipients[0])}` : `discord:@${recipients.map(slugify).sort().join(",")}`;
|
|
85
|
+
results.push({
|
|
86
|
+
id: slug,
|
|
87
|
+
platformId: dm.id,
|
|
88
|
+
name: recipients.join(", ") || "DM",
|
|
89
|
+
type: dm.type === 1 ? "dm" : "group"
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
return results;
|
|
93
|
+
}
|
|
94
|
+
async function listUsers(env) {
|
|
95
|
+
const token = requireToken(env);
|
|
96
|
+
const seen = /* @__PURE__ */ new Map();
|
|
97
|
+
const guilds = await discordGet(token, "/users/@me/guilds");
|
|
98
|
+
for (const guild of guilds) {
|
|
99
|
+
const members = await discordGet(token, `/guilds/${guild.id}/members?limit=1000`);
|
|
100
|
+
for (const m of members) {
|
|
101
|
+
if (!seen.has(m.user.id)) {
|
|
102
|
+
seen.set(m.user.id, {
|
|
103
|
+
id: m.user.id,
|
|
104
|
+
username: m.user.username,
|
|
105
|
+
type: m.user.bot ? "bot" : "human"
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return [...seen.values()];
|
|
111
|
+
}
|
|
112
|
+
async function createConversation(env, participants, _name) {
|
|
113
|
+
const token = requireToken(env);
|
|
114
|
+
if (participants.length !== 1) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
"Discord group creation not supported via bot \u2014 use threads in an existing channel"
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
const allUsers = await listUsers(env);
|
|
120
|
+
const target = allUsers.find((u) => u.username.toLowerCase() === participants[0].toLowerCase());
|
|
121
|
+
if (!target) {
|
|
122
|
+
throw new Error(`User not found: ${participants[0]}`);
|
|
123
|
+
}
|
|
124
|
+
const res = await fetch(`${API_BASE}/users/@me/channels`, {
|
|
125
|
+
method: "POST",
|
|
126
|
+
headers: {
|
|
127
|
+
Authorization: `Bot ${token}`,
|
|
128
|
+
"Content-Type": "application/json"
|
|
129
|
+
},
|
|
130
|
+
body: JSON.stringify({ recipient_id: target.id })
|
|
131
|
+
});
|
|
132
|
+
if (!res.ok) {
|
|
133
|
+
throw new Error(`Discord API error: ${res.status} ${res.statusText}`);
|
|
134
|
+
}
|
|
135
|
+
const dm = await res.json();
|
|
136
|
+
const slug = `discord:@${slugify(participants[0])}`;
|
|
137
|
+
const agentDir = env.VOLUTE_AGENT_DIR;
|
|
138
|
+
if (agentDir) {
|
|
139
|
+
writeChannelEntry(agentDir, slug, {
|
|
140
|
+
platformId: dm.id,
|
|
141
|
+
platform: "discord",
|
|
142
|
+
name: participants[0],
|
|
143
|
+
type: "dm"
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
return slug;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// src/lib/channels/slack.ts
|
|
150
|
+
var slack_exports = {};
|
|
151
|
+
__export(slack_exports, {
|
|
152
|
+
createConversation: () => createConversation2,
|
|
153
|
+
listConversations: () => listConversations2,
|
|
154
|
+
listUsers: () => listUsers2,
|
|
155
|
+
read: () => read2,
|
|
156
|
+
send: () => send2
|
|
157
|
+
});
|
|
158
|
+
var API_BASE2 = "https://slack.com/api";
|
|
159
|
+
function requireToken2(env) {
|
|
160
|
+
const token = env.SLACK_BOT_TOKEN;
|
|
161
|
+
if (!token) throw new Error("SLACK_BOT_TOKEN not set");
|
|
162
|
+
return token;
|
|
163
|
+
}
|
|
164
|
+
async function slackApi(token, method, body) {
|
|
165
|
+
const res = await fetch(`${API_BASE2}/${method}`, {
|
|
166
|
+
method: "POST",
|
|
167
|
+
headers: {
|
|
168
|
+
Authorization: `Bearer ${token}`,
|
|
169
|
+
"Content-Type": "application/json"
|
|
170
|
+
},
|
|
171
|
+
body: JSON.stringify(body)
|
|
172
|
+
});
|
|
173
|
+
if (!res.ok) {
|
|
174
|
+
throw new Error(`Slack API HTTP error: ${res.status} ${res.statusText}`);
|
|
175
|
+
}
|
|
176
|
+
const data = await res.json();
|
|
177
|
+
if (!data.ok) {
|
|
178
|
+
throw new Error(`Slack API error: ${data.error}`);
|
|
179
|
+
}
|
|
180
|
+
return data;
|
|
181
|
+
}
|
|
182
|
+
async function read2(env, channelSlug, limit) {
|
|
183
|
+
const token = requireToken2(env);
|
|
184
|
+
const channelId = resolveChannelId2(env, channelSlug);
|
|
185
|
+
const data = await slackApi(token, "conversations.history", {
|
|
186
|
+
channel: channelId,
|
|
187
|
+
limit
|
|
188
|
+
});
|
|
189
|
+
return data.messages.reverse().map((m) => `${m.user ?? m.bot_id ?? "unknown"}: ${m.text}`).join("\n");
|
|
190
|
+
}
|
|
191
|
+
async function send2(env, channelSlug, message) {
|
|
192
|
+
const token = requireToken2(env);
|
|
193
|
+
const channelId = resolveChannelId2(env, channelSlug);
|
|
194
|
+
await slackApi(token, "chat.postMessage", {
|
|
195
|
+
channel: channelId,
|
|
196
|
+
text: message
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
async function listConversations2(env) {
|
|
200
|
+
const token = requireToken2(env);
|
|
201
|
+
const authData = await slackApi(token, "auth.test", {});
|
|
202
|
+
const teamName = authData.team ?? "workspace";
|
|
203
|
+
const data = await slackApi(token, "conversations.list", {
|
|
204
|
+
types: "public_channel,private_channel,mpim,im",
|
|
205
|
+
limit: 1e3
|
|
206
|
+
});
|
|
207
|
+
const userMap = /* @__PURE__ */ new Map();
|
|
208
|
+
const imChannels = data.channels.filter((ch) => ch.is_im && ch.user);
|
|
209
|
+
if (imChannels.length > 0) {
|
|
210
|
+
const users = await listUsers2(env);
|
|
211
|
+
for (const u of users) {
|
|
212
|
+
userMap.set(u.id, u.username);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return data.channels.map((ch) => {
|
|
216
|
+
let type = "channel";
|
|
217
|
+
if (ch.is_im) type = "dm";
|
|
218
|
+
else if (ch.is_mpim) type = "group";
|
|
219
|
+
let slug;
|
|
220
|
+
let name;
|
|
221
|
+
if (ch.is_im && ch.user) {
|
|
222
|
+
const username = userMap.get(ch.user) ?? ch.user;
|
|
223
|
+
slug = `slack:@${slugify(username)}`;
|
|
224
|
+
name = username;
|
|
225
|
+
} else if (ch.name) {
|
|
226
|
+
slug = `slack:${slugify(teamName)}/${slugify(ch.name)}`;
|
|
227
|
+
name = ch.name;
|
|
228
|
+
} else {
|
|
229
|
+
slug = `slack:${ch.id}`;
|
|
230
|
+
name = ch.id;
|
|
231
|
+
}
|
|
232
|
+
return {
|
|
233
|
+
id: slug,
|
|
234
|
+
platformId: ch.id,
|
|
235
|
+
name,
|
|
236
|
+
type,
|
|
237
|
+
participantCount: ch.num_members
|
|
238
|
+
};
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
async function listUsers2(env) {
|
|
242
|
+
const token = requireToken2(env);
|
|
243
|
+
const data = await slackApi(token, "users.list", {});
|
|
244
|
+
return data.members.filter((m) => !m.deleted).map((m) => ({
|
|
245
|
+
id: m.id,
|
|
246
|
+
username: m.name,
|
|
247
|
+
type: m.is_bot ? "bot" : "human"
|
|
248
|
+
}));
|
|
249
|
+
}
|
|
250
|
+
async function createConversation2(env, participants, name) {
|
|
251
|
+
const token = requireToken2(env);
|
|
252
|
+
const allUsers = await listUsers2(env);
|
|
253
|
+
const ids = [];
|
|
254
|
+
for (const p of participants) {
|
|
255
|
+
const user = allUsers.find((u) => u.username.toLowerCase() === p.toLowerCase());
|
|
256
|
+
if (!user) throw new Error(`User not found: ${p}`);
|
|
257
|
+
ids.push(user.id);
|
|
258
|
+
}
|
|
259
|
+
const agentDir = env.VOLUTE_AGENT_DIR;
|
|
260
|
+
if (name) {
|
|
261
|
+
const createData = await slackApi(token, "conversations.create", {
|
|
262
|
+
name,
|
|
263
|
+
is_private: true
|
|
264
|
+
});
|
|
265
|
+
const channelId = createData.channel.id;
|
|
266
|
+
for (const userId of ids) {
|
|
267
|
+
await slackApi(token, "conversations.invite", {
|
|
268
|
+
channel: channelId,
|
|
269
|
+
users: userId
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
const authData = await slackApi(token, "auth.test", {});
|
|
273
|
+
const teamName = authData.team ?? "workspace";
|
|
274
|
+
const slug2 = `slack:${slugify(teamName)}/${slugify(name)}`;
|
|
275
|
+
if (agentDir) {
|
|
276
|
+
writeChannelEntry(agentDir, slug2, {
|
|
277
|
+
platformId: channelId,
|
|
278
|
+
platform: "slack",
|
|
279
|
+
name,
|
|
280
|
+
type: "channel"
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
return slug2;
|
|
284
|
+
}
|
|
285
|
+
const openData = await slackApi(token, "conversations.open", {
|
|
286
|
+
users: ids.join(",")
|
|
287
|
+
});
|
|
288
|
+
const platformId = openData.channel.id;
|
|
289
|
+
const slug = participants.length === 1 ? `slack:@${slugify(participants[0])}` : `slack:@${participants.map(slugify).sort().join(",")}`;
|
|
290
|
+
if (agentDir) {
|
|
291
|
+
writeChannelEntry(agentDir, slug, {
|
|
292
|
+
platformId,
|
|
293
|
+
platform: "slack",
|
|
294
|
+
name: participants.join(", "),
|
|
295
|
+
type: participants.length === 1 ? "dm" : "group"
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
return slug;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// src/lib/channels/telegram.ts
|
|
302
|
+
var telegram_exports = {};
|
|
303
|
+
__export(telegram_exports, {
|
|
304
|
+
createConversation: () => createConversation3,
|
|
305
|
+
listConversations: () => listConversations3,
|
|
306
|
+
listUsers: () => listUsers3,
|
|
307
|
+
read: () => read3,
|
|
308
|
+
send: () => send3
|
|
309
|
+
});
|
|
310
|
+
var API_BASE3 = "https://api.telegram.org";
|
|
311
|
+
function requireToken3(env) {
|
|
312
|
+
const token = env.TELEGRAM_BOT_TOKEN;
|
|
313
|
+
if (!token) throw new Error("TELEGRAM_BOT_TOKEN not set");
|
|
314
|
+
return token;
|
|
315
|
+
}
|
|
316
|
+
async function read3(_env, _channelSlug, _limit) {
|
|
317
|
+
throw new Error(
|
|
318
|
+
"Telegram Bot API does not support reading chat history. Use volute channel send instead."
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
async function send3(env, channelSlug, message) {
|
|
322
|
+
const token = requireToken3(env);
|
|
323
|
+
const chatId = resolveChannelId2(env, channelSlug);
|
|
324
|
+
const res = await fetch(`${API_BASE3}/bot${token}/sendMessage`, {
|
|
325
|
+
method: "POST",
|
|
326
|
+
headers: { "Content-Type": "application/json" },
|
|
327
|
+
body: JSON.stringify({ chat_id: chatId, text: message })
|
|
328
|
+
});
|
|
329
|
+
if (!res.ok) {
|
|
330
|
+
const body = await res.text().catch(() => "");
|
|
331
|
+
throw new Error(`Telegram API error: ${res.status} ${body}`);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
async function listConversations3() {
|
|
335
|
+
throw new Error(
|
|
336
|
+
"Telegram Bot API does not support listing conversations. Users must message the bot first."
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
async function listUsers3() {
|
|
340
|
+
throw new Error(
|
|
341
|
+
"Telegram Bot API does not support listing users. Users must message the bot first."
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
async function createConversation3() {
|
|
345
|
+
throw new Error(
|
|
346
|
+
"Telegram Bot API does not support creating conversations. Users must message the bot first."
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// src/lib/channels/volute.ts
|
|
351
|
+
var volute_exports = {};
|
|
352
|
+
__export(volute_exports, {
|
|
353
|
+
createConversation: () => createConversation4,
|
|
354
|
+
listConversations: () => listConversations4,
|
|
355
|
+
listUsers: () => listUsers4,
|
|
356
|
+
read: () => read4,
|
|
357
|
+
send: () => send4,
|
|
358
|
+
sendAndStream: () => sendAndStream
|
|
359
|
+
});
|
|
360
|
+
import { existsSync, readFileSync } from "fs";
|
|
361
|
+
import { resolve } from "path";
|
|
362
|
+
function getDaemonConfig() {
|
|
363
|
+
const configPath = resolve(voluteHome(), "daemon.json");
|
|
364
|
+
if (!existsSync(configPath)) {
|
|
365
|
+
throw new Error("Volute daemon is not running");
|
|
366
|
+
}
|
|
367
|
+
let config;
|
|
368
|
+
try {
|
|
369
|
+
config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
370
|
+
} catch (err) {
|
|
371
|
+
throw new Error(`Failed to parse ${configPath}: ${err}`);
|
|
372
|
+
}
|
|
373
|
+
if (typeof config.port !== "number") {
|
|
374
|
+
throw new Error(`Invalid or missing port in ${configPath}`);
|
|
375
|
+
}
|
|
376
|
+
const url = new URL("http://localhost");
|
|
377
|
+
url.hostname = config.hostname || "localhost";
|
|
378
|
+
url.port = String(config.port);
|
|
379
|
+
return { url: url.origin, token: config.token };
|
|
380
|
+
}
|
|
381
|
+
async function read4(env, channelSlug, limit) {
|
|
382
|
+
const agentName = env.VOLUTE_AGENT;
|
|
383
|
+
if (!agentName) throw new Error("VOLUTE_AGENT not set");
|
|
384
|
+
const conversationId = resolveChannelId2(env, channelSlug);
|
|
385
|
+
const { url, token } = getDaemonConfig();
|
|
386
|
+
const headers = { Origin: url };
|
|
387
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
388
|
+
const res = await fetch(
|
|
389
|
+
`${url}/api/agents/${encodeURIComponent(agentName)}/conversations/${encodeURIComponent(conversationId)}/messages`,
|
|
390
|
+
{ headers }
|
|
391
|
+
);
|
|
392
|
+
if (!res.ok) {
|
|
393
|
+
throw new Error(`Failed to read conversation: ${res.status} ${res.statusText}`);
|
|
394
|
+
}
|
|
395
|
+
const messages = await res.json();
|
|
396
|
+
return messages.slice(-limit).map((m) => {
|
|
397
|
+
const text = Array.isArray(m.content) ? m.content.filter((b) => b.type === "text").map((b) => b.text).join("") : m.content;
|
|
398
|
+
return `${m.sender_name ?? m.role}: ${text}`;
|
|
399
|
+
}).join("\n");
|
|
400
|
+
}
|
|
401
|
+
async function* sendAndStream(env, channelSlug, message) {
|
|
402
|
+
const agentName = env.VOLUTE_AGENT;
|
|
403
|
+
if (!agentName) throw new Error("VOLUTE_AGENT not set");
|
|
404
|
+
const conversationId = resolveChannelId2(env, channelSlug);
|
|
405
|
+
const { url, token } = getDaemonConfig();
|
|
406
|
+
const headers = {
|
|
407
|
+
"Content-Type": "application/json",
|
|
408
|
+
Origin: url
|
|
409
|
+
};
|
|
410
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
411
|
+
const res = await fetch(`${url}/api/agents/${encodeURIComponent(agentName)}/chat`, {
|
|
412
|
+
method: "POST",
|
|
413
|
+
headers,
|
|
414
|
+
body: JSON.stringify({ message, conversationId, sender: env.VOLUTE_SENDER ?? agentName })
|
|
415
|
+
});
|
|
416
|
+
if (!res.ok) {
|
|
417
|
+
const data = await res.json().catch(() => ({}));
|
|
418
|
+
throw new Error(data.error ?? `Failed to send: ${res.status}`);
|
|
419
|
+
}
|
|
420
|
+
if (!res.body) return;
|
|
421
|
+
const reader = res.body.getReader();
|
|
422
|
+
const decoder = new TextDecoder();
|
|
423
|
+
let buffer = "";
|
|
424
|
+
try {
|
|
425
|
+
while (true) {
|
|
426
|
+
const { done, value } = await reader.read();
|
|
427
|
+
if (done) break;
|
|
428
|
+
buffer += decoder.decode(value, { stream: true });
|
|
429
|
+
const lines = buffer.split("\n");
|
|
430
|
+
buffer = lines.pop() ?? "";
|
|
431
|
+
for (const line of lines) {
|
|
432
|
+
if (!line.startsWith("data:")) continue;
|
|
433
|
+
const data = line.slice(5).trim();
|
|
434
|
+
if (!data) continue;
|
|
435
|
+
try {
|
|
436
|
+
const event = JSON.parse(data);
|
|
437
|
+
yield event;
|
|
438
|
+
if (event.type === "done") return;
|
|
439
|
+
} catch (err) {
|
|
440
|
+
console.error(`[volute] failed to parse SSE data: ${data}`, err);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
} finally {
|
|
445
|
+
reader.releaseLock();
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
async function send4(env, channelSlug, message) {
|
|
449
|
+
for await (const event of sendAndStream(env, channelSlug, message)) {
|
|
450
|
+
if (event.type === "done") break;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
async function listConversations4(env) {
|
|
454
|
+
const agentName = env.VOLUTE_AGENT;
|
|
455
|
+
if (!agentName) throw new Error("VOLUTE_AGENT not set");
|
|
456
|
+
const { url, token } = getDaemonConfig();
|
|
457
|
+
const headers = { Origin: url };
|
|
458
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
459
|
+
const res = await fetch(`${url}/api/agents/${encodeURIComponent(agentName)}/conversations`, {
|
|
460
|
+
headers
|
|
461
|
+
});
|
|
462
|
+
if (!res.ok) {
|
|
463
|
+
throw new Error(`Failed to list conversations: ${res.status} ${res.statusText}`);
|
|
464
|
+
}
|
|
465
|
+
const convs = await res.json();
|
|
466
|
+
const results = [];
|
|
467
|
+
for (const conv of convs) {
|
|
468
|
+
let participantCount;
|
|
469
|
+
try {
|
|
470
|
+
const pRes = await fetch(
|
|
471
|
+
`${url}/api/agents/${encodeURIComponent(agentName)}/conversations/${encodeURIComponent(conv.id)}/participants`,
|
|
472
|
+
{ headers }
|
|
473
|
+
);
|
|
474
|
+
if (pRes.ok) {
|
|
475
|
+
const participants = await pRes.json();
|
|
476
|
+
participantCount = participants.length;
|
|
477
|
+
}
|
|
478
|
+
} catch (err) {
|
|
479
|
+
console.error(`[volute] failed to fetch participants for ${conv.id}:`, err);
|
|
480
|
+
}
|
|
481
|
+
const slug = conv.title ? `volute:${slugify(conv.title)}` : `volute:${conv.id}`;
|
|
482
|
+
results.push({
|
|
483
|
+
id: slug,
|
|
484
|
+
platformId: conv.id,
|
|
485
|
+
name: conv.title ?? "(untitled)",
|
|
486
|
+
type: participantCount === 2 ? "dm" : "group",
|
|
487
|
+
participantCount
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
return results;
|
|
491
|
+
}
|
|
492
|
+
async function listUsers4(_env) {
|
|
493
|
+
const { url, token } = getDaemonConfig();
|
|
494
|
+
const headers = { Origin: url };
|
|
495
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
496
|
+
const res = await fetch(`${url}/api/auth/users`, { headers });
|
|
497
|
+
if (!res.ok) {
|
|
498
|
+
throw new Error(`Failed to list users: ${res.status} ${res.statusText}`);
|
|
499
|
+
}
|
|
500
|
+
const data = await res.json();
|
|
501
|
+
return data.map((u) => ({
|
|
502
|
+
id: String(u.id),
|
|
503
|
+
username: u.username,
|
|
504
|
+
type: u.user_type
|
|
505
|
+
}));
|
|
506
|
+
}
|
|
507
|
+
async function createConversation4(env, participants, name) {
|
|
508
|
+
const agentName = env.VOLUTE_AGENT;
|
|
509
|
+
if (!agentName) throw new Error("VOLUTE_AGENT not set");
|
|
510
|
+
const { url, token } = getDaemonConfig();
|
|
511
|
+
const headers = {
|
|
512
|
+
"Content-Type": "application/json",
|
|
513
|
+
Origin: url
|
|
514
|
+
};
|
|
515
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
516
|
+
const res = await fetch(`${url}/api/agents/${encodeURIComponent(agentName)}/conversations`, {
|
|
517
|
+
method: "POST",
|
|
518
|
+
headers,
|
|
519
|
+
body: JSON.stringify({ participantNames: participants, title: name })
|
|
520
|
+
});
|
|
521
|
+
if (!res.ok) {
|
|
522
|
+
const data = await res.json().catch(() => ({}));
|
|
523
|
+
throw new Error(data.error ?? `Failed to create conversation: ${res.status}`);
|
|
524
|
+
}
|
|
525
|
+
const conv = await res.json();
|
|
526
|
+
const slug = name ? `volute:${slugify(name)}` : `volute:${conv.id}`;
|
|
527
|
+
const agentDir = env.VOLUTE_AGENT_DIR;
|
|
528
|
+
if (agentDir) {
|
|
529
|
+
writeChannelEntry(agentDir, slug, {
|
|
530
|
+
platformId: conv.id,
|
|
531
|
+
platform: "volute",
|
|
532
|
+
name: name ?? participants.join(", "),
|
|
533
|
+
type: participants.length <= 1 ? "dm" : "group"
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
return slug;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// src/lib/channels.ts
|
|
540
|
+
var CHANNELS = {
|
|
541
|
+
volute: {
|
|
542
|
+
name: "volute",
|
|
543
|
+
displayName: "Volute",
|
|
544
|
+
showToolCalls: true,
|
|
545
|
+
builtIn: true,
|
|
546
|
+
driver: volute_exports
|
|
547
|
+
},
|
|
548
|
+
discord: {
|
|
549
|
+
name: "discord",
|
|
550
|
+
displayName: "Discord",
|
|
551
|
+
showToolCalls: false,
|
|
552
|
+
driver: discord_exports
|
|
553
|
+
},
|
|
554
|
+
slack: {
|
|
555
|
+
name: "slack",
|
|
556
|
+
displayName: "Slack",
|
|
557
|
+
showToolCalls: false,
|
|
558
|
+
driver: slack_exports
|
|
559
|
+
},
|
|
560
|
+
telegram: {
|
|
561
|
+
name: "telegram",
|
|
562
|
+
displayName: "Telegram",
|
|
563
|
+
showToolCalls: false,
|
|
564
|
+
driver: telegram_exports
|
|
565
|
+
},
|
|
566
|
+
system: { name: "system", displayName: "System", showToolCalls: false }
|
|
567
|
+
};
|
|
568
|
+
function getChannelDriver(platform) {
|
|
569
|
+
return CHANNELS[platform]?.driver ?? null;
|
|
570
|
+
}
|
|
571
|
+
function resolveChannelId2(env, slug) {
|
|
572
|
+
const agentDir = env.VOLUTE_AGENT_DIR;
|
|
573
|
+
if (!agentDir) {
|
|
574
|
+
const colonIdx = slug.indexOf(":");
|
|
575
|
+
return colonIdx !== -1 ? slug.slice(colonIdx + 1) : slug;
|
|
576
|
+
}
|
|
577
|
+
return resolveChannelId(agentDir, slug);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
export {
|
|
581
|
+
CHANNELS,
|
|
582
|
+
getChannelDriver
|
|
583
|
+
};
|
package/dist/cli.js
CHANGED
|
@@ -9,49 +9,49 @@ 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-T2WAVJOU.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-7JF7MT73.js").then((m) => m.run(args));
|
|
19
19
|
break;
|
|
20
20
|
case "message":
|
|
21
|
-
await import("./message-
|
|
21
|
+
await import("./message-SCOQDR3P.js").then((m) => m.run(args));
|
|
22
22
|
break;
|
|
23
23
|
case "variant":
|
|
24
|
-
await import("./variant-
|
|
24
|
+
await import("./variant-4Z6W3PP6.js").then((m) => m.run(args));
|
|
25
25
|
break;
|
|
26
26
|
case "connector":
|
|
27
|
-
await import("./connector-
|
|
27
|
+
await import("./connector-Y7JPNROO.js").then((m) => m.run(args));
|
|
28
28
|
break;
|
|
29
29
|
case "channel":
|
|
30
|
-
await import("./channel-
|
|
30
|
+
await import("./channel-SMCNOIVQ.js").then((m) => m.run(args));
|
|
31
31
|
break;
|
|
32
32
|
case "schedule":
|
|
33
|
-
await import("./schedule-
|
|
34
|
-
break;
|
|
35
|
-
case "conversation":
|
|
36
|
-
await import("./conversation-ERXEQZTY.js").then((m) => m.run(args));
|
|
33
|
+
await import("./schedule-S6QVC5ON.js").then((m) => m.run(args));
|
|
37
34
|
break;
|
|
38
35
|
case "env":
|
|
39
|
-
await import("./env-
|
|
36
|
+
await import("./env-7GLUJCWS.js").then((m) => m.run(args));
|
|
40
37
|
break;
|
|
41
38
|
case "up":
|
|
42
|
-
await import("./up-
|
|
39
|
+
await import("./up-RWZF6MLT.js").then((m) => m.run(args));
|
|
43
40
|
break;
|
|
44
41
|
case "down":
|
|
45
|
-
await import("./down-
|
|
42
|
+
await import("./down-AZVH5TCD.js").then((m) => m.run(args));
|
|
43
|
+
break;
|
|
44
|
+
case "restart":
|
|
45
|
+
await import("./daemon-restart-4HVEKYFY.js").then((m) => m.run(args));
|
|
46
46
|
break;
|
|
47
47
|
case "setup":
|
|
48
|
-
await import("./setup-
|
|
48
|
+
await import("./setup-F4TCWVSP.js").then((m) => m.run(args));
|
|
49
49
|
break;
|
|
50
50
|
case "service":
|
|
51
51
|
await import("./service-HZNIDNJF.js").then((m) => m.run(args));
|
|
52
52
|
break;
|
|
53
53
|
case "update":
|
|
54
|
-
await import("./update-
|
|
54
|
+
await import("./update-F7QWV2LB.js").then((m) => m.run(args));
|
|
55
55
|
break;
|
|
56
56
|
case "--help":
|
|
57
57
|
case "-h":
|
|
@@ -62,6 +62,7 @@ Commands:
|
|
|
62
62
|
volute agent create <name> Create a new agent
|
|
63
63
|
volute agent start <name> Start an agent (daemonized)
|
|
64
64
|
volute agent stop <name> Stop an agent
|
|
65
|
+
volute agent restart <name> Restart an agent
|
|
65
66
|
volute agent delete <name> [--force] Delete an agent (--force removes files)
|
|
66
67
|
volute agent list List all agents
|
|
67
68
|
volute agent status <name> Check agent status
|
|
@@ -82,19 +83,19 @@ Commands:
|
|
|
82
83
|
|
|
83
84
|
volute channel read <uri> Read recent messages from a channel
|
|
84
85
|
volute channel send <uri> "<msg>" Send a message to a channel
|
|
86
|
+
volute channel list [<platform>] List conversations on a platform
|
|
87
|
+
volute channel users <platform> List users on a platform
|
|
88
|
+
volute channel create <platform> ... Create a conversation on a platform
|
|
85
89
|
|
|
86
90
|
volute schedule list List schedules for an agent
|
|
87
91
|
volute schedule add ... Add a cron schedule
|
|
88
92
|
volute schedule remove ... Remove a schedule
|
|
89
93
|
|
|
90
|
-
volute conversation create ... Create a group conversation
|
|
91
|
-
volute conversation list List conversations
|
|
92
|
-
volute conversation send <id> "<msg>" Send a message to a conversation
|
|
93
|
-
|
|
94
94
|
volute env <set|get|list|remove> Manage environment variables
|
|
95
95
|
|
|
96
96
|
volute up [--port N] Start the daemon (default: 4200)
|
|
97
97
|
volute down Stop the daemon
|
|
98
|
+
volute restart [--port N] Restart the daemon
|
|
98
99
|
|
|
99
100
|
volute service install [--port N] Install as system service (auto-start)
|
|
100
101
|
volute service uninstall Remove system service
|
|
@@ -108,7 +109,7 @@ Options:
|
|
|
108
109
|
--version, -v Show version number
|
|
109
110
|
--help, -h Show this help message
|
|
110
111
|
|
|
111
|
-
Agent-scoped commands (variant, connector, schedule, channel,
|
|
112
|
+
Agent-scoped commands (variant, connector, schedule, channel, message history)
|
|
112
113
|
use --agent <name> or VOLUTE_AGENT env var to identify the agent.`);
|
|
113
114
|
break;
|
|
114
115
|
default:
|
|
@@ -117,7 +118,7 @@ Run 'volute --help' for usage.`);
|
|
|
117
118
|
process.exit(1);
|
|
118
119
|
}
|
|
119
120
|
if (command !== "update") {
|
|
120
|
-
import("./update-check-
|
|
121
|
+
import("./update-check-B4J6IEQ4.js").then((m) => m.checkForUpdate()).then((result) => {
|
|
121
122
|
if (result.updateAvailable) {
|
|
122
123
|
console.error(`
|
|
123
124
|
Update available: ${result.current} \u2192 ${result.latest}`);
|