volute 0.3.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -29
- package/dist/agent-Z2B6EFEQ.js +75 -0
- package/dist/{agent-manager-AUCKMGPR.js → agent-manager-PXBKA2GK.js} +4 -4
- package/dist/channel-MK5OK2SI.js +113 -0
- package/dist/chunk-5X7HGB6L.js +107 -0
- package/dist/{chunk-YGFIWIOF.js → chunk-7L4AN5D4.js} +1 -1
- package/dist/{chunk-VRVVQIYY.js → chunk-AZEL2IEK.js} +1 -1
- package/dist/chunk-B3R6L2GW.js +24 -0
- package/dist/{chunk-DNOXHLE5.js → chunk-HE67X4T6.js} +1 -1
- package/dist/{chunk-I6OHXCMV.js → chunk-MW2KFO3B.js} +47 -9
- package/dist/chunk-MXUCNIBG.js +168 -0
- package/dist/chunk-SMISE4SV.js +226 -0
- package/dist/{chunk-SOZA2TLP.js → chunk-UAVD2AHX.js} +1 -1
- package/dist/{chunk-3C2XR4IY.js → chunk-UX25Z2ND.js} +113 -107
- package/dist/{chunk-GSPKUPKU.js → chunk-XUA3JUFK.js} +2 -1
- package/dist/chunk-ZYGKG6VC.js +22 -0
- package/dist/cli.js +98 -75
- package/dist/connector-LYEMXQEV.js +157 -0
- package/dist/connectors/discord.js +104 -161
- package/dist/connectors/slack.js +179 -0
- package/dist/connectors/telegram.js +175 -0
- package/dist/conversation-ERXEQZTY.js +163 -0
- package/dist/create-RVCZN6HE.js +91 -0
- package/dist/{daemon-client-XR24PUJF.js → daemon-client-ZY6UUN2M.js} +2 -2
- package/dist/daemon.js +824 -252
- package/dist/{delete-GQ7JEK2S.js → delete-3QH7VYIN.js} +8 -9
- package/dist/{down-3OB6UVAJ.js → down-O7IFZLVJ.js} +1 -1
- package/dist/{env-JB27UAC3.js → env-4D4REPJF.js} +8 -5
- package/dist/{history-3VRUBGGV.js → history-OEONB53Z.js} +5 -5
- package/dist/{import-K4MP2GX7.js → import-MXJB2EII.js} +23 -8
- package/dist/{logs-NXFFGUKY.js → logs-DF342W4M.js} +2 -2
- package/dist/message-ADHWFHSI.js +32 -0
- package/dist/package-VQOE7JNH.js +89 -0
- package/dist/{schedule-4I5TYHFH.js → schedule-NAG6F463.js} +12 -7
- package/dist/send-66QMKRUH.js +75 -0
- package/dist/{setup-SRS7AUAA.js → setup-RPRRGG2F.js} +6 -6
- package/dist/{start-LDPMCMYT.js → start-TUOXDSFL.js} +3 -3
- package/dist/{status-MVSQG54T.js → status-A36EHRO4.js} +3 -3
- package/dist/{stop-5PZTZCLL.js → stop-AOJZLQ5X.js} +6 -7
- package/dist/{up-UT3IMKCA.js → up-7ILD7GU7.js} +2 -2
- package/dist/update-LPSIAWQ2.js +140 -0
- package/dist/update-check-Y33QDCFL.js +17 -0
- package/dist/{upgrade-CDKECCGN.js → upgrade-FX2TKJ2S.js} +16 -15
- package/dist/{variant-CVYM3EQG.js → variant-LAB67OC2.js} +17 -12
- package/dist/web-assets/assets/index-BbRmoxoA.js +308 -0
- package/dist/web-assets/index.html +2 -2
- package/drizzle/0003_clean_ego.sql +12 -0
- package/drizzle/meta/0003_snapshot.json +417 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +3 -1
- package/templates/_base/.init/.config/hooks/startup-context.sh +19 -1
- package/templates/_base/_skills/volute-agent/SKILL.md +112 -16
- package/templates/_base/home/.config/routes.json +10 -0
- package/templates/_base/home/VOLUTE.md +19 -28
- package/templates/_base/src/lib/file-handler.ts +46 -0
- package/templates/_base/src/lib/format-prefix.ts +1 -1
- package/templates/_base/src/lib/router.ts +327 -0
- package/templates/_base/src/lib/routing.ts +137 -0
- package/templates/_base/src/lib/types.ts +16 -3
- package/templates/_base/src/lib/volute-server.ts +20 -48
- package/templates/agent-sdk/.init/.config/routes.json +5 -0
- package/templates/agent-sdk/.init/CLAUDE.md +2 -2
- package/templates/agent-sdk/src/agent.ts +269 -82
- package/templates/agent-sdk/src/server.ts +19 -4
- package/templates/agent-sdk/volute-template.json +1 -1
- package/templates/pi/.init/.config/routes.json +5 -0
- package/templates/pi/.init/AGENTS.md +1 -1
- package/templates/pi/src/agent.ts +279 -58
- package/templates/pi/src/server.ts +15 -4
- package/templates/pi/volute-template.json +1 -1
- package/dist/channel-7FZ6D25H.js +0 -90
- package/dist/chunk-N4YNKR3Q.js +0 -90
- package/dist/connector-TVJULIRT.js +0 -96
- package/dist/create-BRG2DBWI.js +0 -79
- package/dist/send-UK3JBZIB.js +0 -53
- package/dist/web-assets/assets/index-BC5eSqbY.js +0 -296
- package/templates/_base/src/lib/sessions.ts +0 -71
- package/templates/agent-sdk/.init/.config/sessions.json +0 -4
- package/templates/agent-sdk/src/lib/agent-sessions.ts +0 -204
- package/templates/pi/.init/.config/sessions.json +0 -1
- package/templates/pi/src/lib/agent-sessions.ts +0 -210
- package/dist/{service-SA4TTMDU.js → service-HZNIDNJF.js} +3 -3
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
fireAndForget,
|
|
4
|
+
handleAgentMessage,
|
|
5
|
+
loadEnv,
|
|
6
|
+
loadFollowedChannels,
|
|
7
|
+
onShutdown,
|
|
8
|
+
splitMessage
|
|
9
|
+
} from "../chunk-MXUCNIBG.js";
|
|
10
|
+
import "../chunk-K3NQKI34.js";
|
|
11
|
+
|
|
12
|
+
// src/connectors/slack.ts
|
|
13
|
+
import { App } from "@slack/bolt";
|
|
14
|
+
var SLACK_MAX_LENGTH = 4e3;
|
|
15
|
+
var env = loadEnv();
|
|
16
|
+
var botToken = process.env.SLACK_BOT_TOKEN;
|
|
17
|
+
var appToken = process.env.SLACK_APP_TOKEN;
|
|
18
|
+
if (!botToken || !appToken) {
|
|
19
|
+
console.error("Missing required env vars: SLACK_BOT_TOKEN, SLACK_APP_TOKEN");
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
var followedChannelNames = loadFollowedChannels(env, "slack");
|
|
23
|
+
var followedChannelIds = /* @__PURE__ */ new Set();
|
|
24
|
+
var app = new App({
|
|
25
|
+
token: botToken,
|
|
26
|
+
socketMode: true,
|
|
27
|
+
appToken
|
|
28
|
+
});
|
|
29
|
+
var botUserId;
|
|
30
|
+
var serverName;
|
|
31
|
+
app.message(async ({ message, say }) => {
|
|
32
|
+
if (message.subtype) return;
|
|
33
|
+
if (!("user" in message) || !("text" in message)) return;
|
|
34
|
+
if ("bot_id" in message && message.bot_id) return;
|
|
35
|
+
const isDM = message.channel_type === "im" || message.channel_type === "mpim";
|
|
36
|
+
const isMentioned = !isDM && botUserId && message.text?.includes(`<@${botUserId}>`);
|
|
37
|
+
const isFollowedChannel = !isDM && followedChannelIds.has(message.channel);
|
|
38
|
+
if (!isDM && !isMentioned && !isFollowedChannel) return;
|
|
39
|
+
let text = message.text ?? "";
|
|
40
|
+
if (isMentioned && botUserId) {
|
|
41
|
+
text = text.replace(new RegExp(`<@${botUserId}>`, "g"), "").trim();
|
|
42
|
+
}
|
|
43
|
+
const content = [];
|
|
44
|
+
if (text) content.push({ type: "text", text });
|
|
45
|
+
if ("files" in message && message.files) {
|
|
46
|
+
for (const file of message.files) {
|
|
47
|
+
if (!file.mimetype?.startsWith("image/") || !file.url_private) continue;
|
|
48
|
+
try {
|
|
49
|
+
const res = await fetch(file.url_private, {
|
|
50
|
+
headers: { Authorization: `Bearer ${botToken}` }
|
|
51
|
+
});
|
|
52
|
+
if (!res.ok) {
|
|
53
|
+
console.error(`Failed to download attachment: HTTP ${res.status}`);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
const buffer = Buffer.from(await res.arrayBuffer());
|
|
57
|
+
content.push({
|
|
58
|
+
type: "image",
|
|
59
|
+
media_type: file.mimetype,
|
|
60
|
+
data: buffer.toString("base64")
|
|
61
|
+
});
|
|
62
|
+
} catch (err) {
|
|
63
|
+
console.error(`Failed to download attachment: ${err}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (content.length === 0) return;
|
|
68
|
+
let channelName;
|
|
69
|
+
let numMembers;
|
|
70
|
+
if (message.channel_type !== "im") {
|
|
71
|
+
try {
|
|
72
|
+
const info = await app.client.conversations.info({
|
|
73
|
+
channel: message.channel
|
|
74
|
+
});
|
|
75
|
+
channelName = info.channel?.name;
|
|
76
|
+
numMembers = info.channel?.num_members;
|
|
77
|
+
} catch (err) {
|
|
78
|
+
console.warn(`Failed to get channel info: ${err}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
let senderName = message.user;
|
|
82
|
+
try {
|
|
83
|
+
const userInfo = await app.client.users.info({
|
|
84
|
+
user: message.user
|
|
85
|
+
});
|
|
86
|
+
senderName = userInfo.user?.profile?.display_name || userInfo.user?.profile?.real_name || message.user;
|
|
87
|
+
} catch (err) {
|
|
88
|
+
console.warn(`Failed to get user info: ${err}`);
|
|
89
|
+
}
|
|
90
|
+
const channelKey = `slack:${message.channel}`;
|
|
91
|
+
const participantCount = message.channel_type === "im" ? 2 : numMembers;
|
|
92
|
+
const payload = {
|
|
93
|
+
content,
|
|
94
|
+
channel: channelKey,
|
|
95
|
+
sender: senderName,
|
|
96
|
+
platform: "Slack",
|
|
97
|
+
...isDM ? { isDM: true } : {},
|
|
98
|
+
...channelName ? { channelName } : {},
|
|
99
|
+
...serverName ? { serverName } : {},
|
|
100
|
+
...participantCount ? { participantCount } : {}
|
|
101
|
+
};
|
|
102
|
+
if (isFollowedChannel && !isMentioned) {
|
|
103
|
+
await fireAndForget(env, payload);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
await handleAgentMessage(env, payload, {
|
|
107
|
+
onFlush: async (text2, images) => {
|
|
108
|
+
for (const img of images) {
|
|
109
|
+
const ext = img.media_type.split("/")[1] || "png";
|
|
110
|
+
try {
|
|
111
|
+
await app.client.filesUploadV2({
|
|
112
|
+
channel_id: message.channel,
|
|
113
|
+
file: Buffer.from(img.data, "base64"),
|
|
114
|
+
filename: `image.${ext}`
|
|
115
|
+
});
|
|
116
|
+
} catch (err) {
|
|
117
|
+
console.error(`Failed to upload image: ${err}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (!text2) return;
|
|
121
|
+
const chunks = splitMessage(text2, SLACK_MAX_LENGTH);
|
|
122
|
+
for (const chunk of chunks) {
|
|
123
|
+
try {
|
|
124
|
+
await say(chunk);
|
|
125
|
+
} catch (err) {
|
|
126
|
+
console.error(`Failed to send message: ${err}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
onError: async (msg) => {
|
|
131
|
+
await say(msg).catch(() => {
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
async function start() {
|
|
137
|
+
await app.start();
|
|
138
|
+
const auth = await app.client.auth.test();
|
|
139
|
+
if (!auth.user_id) {
|
|
140
|
+
throw new Error("auth.test succeeded but returned no user_id");
|
|
141
|
+
}
|
|
142
|
+
botUserId = auth.user_id;
|
|
143
|
+
serverName = auth.team;
|
|
144
|
+
console.log(
|
|
145
|
+
`Connected to Slack as bot user ${botUserId}${serverName ? ` in ${serverName}` : ""}`
|
|
146
|
+
);
|
|
147
|
+
console.log(`Bridging to agent: ${env.agentName} via ${env.baseUrl}/message`);
|
|
148
|
+
if (followedChannelNames.length > 0) {
|
|
149
|
+
try {
|
|
150
|
+
let cursor;
|
|
151
|
+
do {
|
|
152
|
+
const result = await app.client.conversations.list({
|
|
153
|
+
types: "public_channel,private_channel",
|
|
154
|
+
limit: 200,
|
|
155
|
+
...cursor ? { cursor } : {}
|
|
156
|
+
});
|
|
157
|
+
for (const ch of result.channels ?? []) {
|
|
158
|
+
if (followedChannelNames.includes(ch.name)) {
|
|
159
|
+
followedChannelIds.add(ch.id);
|
|
160
|
+
console.log(`Following #${ch.name} (${ch.id})`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
cursor = result.response_metadata?.next_cursor || void 0;
|
|
164
|
+
} while (cursor);
|
|
165
|
+
} catch (err) {
|
|
166
|
+
console.error(`Failed to resolve channel names: ${err}`);
|
|
167
|
+
}
|
|
168
|
+
if (followedChannelIds.size === 0 && followedChannelNames.length > 0) {
|
|
169
|
+
console.warn(`No channels found matching: ${followedChannelNames.join(", ")}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
onShutdown(async () => {
|
|
174
|
+
await app.stop();
|
|
175
|
+
});
|
|
176
|
+
start().catch((err) => {
|
|
177
|
+
console.error("Failed to start Slack connector:", err);
|
|
178
|
+
process.exit(1);
|
|
179
|
+
});
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
fireAndForget,
|
|
4
|
+
handleAgentMessage,
|
|
5
|
+
loadEnv,
|
|
6
|
+
loadFollowedChannels,
|
|
7
|
+
splitMessage
|
|
8
|
+
} from "../chunk-MXUCNIBG.js";
|
|
9
|
+
import "../chunk-K3NQKI34.js";
|
|
10
|
+
|
|
11
|
+
// src/connectors/telegram.ts
|
|
12
|
+
import { Input, Telegraf } from "telegraf";
|
|
13
|
+
import { message } from "telegraf/filters";
|
|
14
|
+
var TELEGRAM_MAX_LENGTH = 4096;
|
|
15
|
+
var TYPING_INTERVAL_MS = 5e3;
|
|
16
|
+
var env = loadEnv();
|
|
17
|
+
var botToken = process.env.TELEGRAM_BOT_TOKEN;
|
|
18
|
+
if (!botToken) {
|
|
19
|
+
console.error("Missing required env var: TELEGRAM_BOT_TOKEN");
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
var followedChatIds = loadFollowedChannels(env, "telegram");
|
|
23
|
+
var followedChatIdSet = new Set(followedChatIds.map(String));
|
|
24
|
+
var bot = new Telegraf(botToken);
|
|
25
|
+
bot.on(message("text"), async (ctx) => {
|
|
26
|
+
if (ctx.message.from.is_bot) return;
|
|
27
|
+
const isDM = ctx.chat.type === "private";
|
|
28
|
+
const botUsername = ctx.botInfo.username;
|
|
29
|
+
const isMentioned = !isDM && ctx.message.entities?.some(
|
|
30
|
+
(e) => e.type === "mention" && ctx.message.text.substring(e.offset, e.offset + e.length) === `@${botUsername}`
|
|
31
|
+
);
|
|
32
|
+
const isFollowedChat = !isDM && followedChatIdSet.has(String(ctx.chat.id));
|
|
33
|
+
if (!isDM && !isMentioned && !isFollowedChat) return;
|
|
34
|
+
let text = ctx.message.text;
|
|
35
|
+
if (isMentioned && botUsername) {
|
|
36
|
+
text = text.replace(new RegExp(`@${botUsername}`, "g"), "").trim();
|
|
37
|
+
}
|
|
38
|
+
const content = [];
|
|
39
|
+
if (text) content.push({ type: "text", text });
|
|
40
|
+
if (content.length === 0) return;
|
|
41
|
+
const senderName = ctx.message.from.first_name + (ctx.message.from.last_name ? ` ${ctx.message.from.last_name}` : "");
|
|
42
|
+
const chatTitle = "title" in ctx.chat ? ctx.chat.title : void 0;
|
|
43
|
+
let participantCount = isDM ? 2 : void 0;
|
|
44
|
+
if (!isDM) {
|
|
45
|
+
try {
|
|
46
|
+
participantCount = await ctx.telegram.getChatMembersCount(ctx.chat.id);
|
|
47
|
+
} catch (err) {
|
|
48
|
+
console.warn(`Failed to get member count for chat ${ctx.chat.id}: ${err}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const payload = {
|
|
52
|
+
content,
|
|
53
|
+
channel: `telegram:${ctx.chat.id}`,
|
|
54
|
+
sender: senderName,
|
|
55
|
+
platform: "Telegram",
|
|
56
|
+
...isDM ? { isDM: true } : {},
|
|
57
|
+
...chatTitle ? { channelName: chatTitle } : {},
|
|
58
|
+
...participantCount ? { participantCount } : {}
|
|
59
|
+
};
|
|
60
|
+
if (isFollowedChat && !isMentioned) {
|
|
61
|
+
await fireAndForget(env, payload);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
await handleTelegramMessage(
|
|
65
|
+
ctx.chat.id,
|
|
66
|
+
payload,
|
|
67
|
+
(text2) => ctx.reply(text2),
|
|
68
|
+
(source) => ctx.replyWithPhoto(source)
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
bot.on(message("photo"), async (ctx) => {
|
|
72
|
+
if (ctx.message.from.is_bot) return;
|
|
73
|
+
const isDM = ctx.chat.type === "private";
|
|
74
|
+
const isFollowedChat = !isDM && followedChatIdSet.has(String(ctx.chat.id));
|
|
75
|
+
if (!isDM && !isFollowedChat) return;
|
|
76
|
+
const content = [];
|
|
77
|
+
const caption = ctx.message.caption;
|
|
78
|
+
if (caption) content.push({ type: "text", text: caption });
|
|
79
|
+
const photos = ctx.message.photo;
|
|
80
|
+
const largest = photos[photos.length - 1];
|
|
81
|
+
try {
|
|
82
|
+
const fileUrl = await ctx.telegram.getFileLink(largest.file_id);
|
|
83
|
+
const res = await fetch(fileUrl.href);
|
|
84
|
+
if (!res.ok) {
|
|
85
|
+
console.error(`Failed to download photo: HTTP ${res.status}`);
|
|
86
|
+
} else {
|
|
87
|
+
const buffer = Buffer.from(await res.arrayBuffer());
|
|
88
|
+
content.push({
|
|
89
|
+
type: "image",
|
|
90
|
+
media_type: "image/jpeg",
|
|
91
|
+
data: buffer.toString("base64")
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
} catch (err) {
|
|
95
|
+
console.error(`Failed to download photo: ${err}`);
|
|
96
|
+
}
|
|
97
|
+
if (content.length === 0) return;
|
|
98
|
+
const senderName = ctx.message.from.first_name + (ctx.message.from.last_name ? ` ${ctx.message.from.last_name}` : "");
|
|
99
|
+
const chatTitle = "title" in ctx.chat ? ctx.chat.title : void 0;
|
|
100
|
+
let participantCount = isDM ? 2 : void 0;
|
|
101
|
+
if (!isDM) {
|
|
102
|
+
try {
|
|
103
|
+
participantCount = await ctx.telegram.getChatMembersCount(ctx.chat.id);
|
|
104
|
+
} catch (err) {
|
|
105
|
+
console.warn(`Failed to get member count for chat ${ctx.chat.id}: ${err}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const payload = {
|
|
109
|
+
content,
|
|
110
|
+
channel: `telegram:${ctx.chat.id}`,
|
|
111
|
+
sender: senderName,
|
|
112
|
+
platform: "Telegram",
|
|
113
|
+
...isDM ? { isDM: true } : {},
|
|
114
|
+
...chatTitle ? { channelName: chatTitle } : {},
|
|
115
|
+
...participantCount ? { participantCount } : {}
|
|
116
|
+
};
|
|
117
|
+
if (isFollowedChat) {
|
|
118
|
+
await fireAndForget(env, payload);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
await handleTelegramMessage(
|
|
122
|
+
ctx.chat.id,
|
|
123
|
+
payload,
|
|
124
|
+
(text) => ctx.reply(text),
|
|
125
|
+
(source) => ctx.replyWithPhoto(source)
|
|
126
|
+
);
|
|
127
|
+
});
|
|
128
|
+
async function handleTelegramMessage(chatId, payload, reply, replyWithPhoto) {
|
|
129
|
+
const typingInterval = setInterval(() => {
|
|
130
|
+
bot.telegram.sendChatAction(chatId, "typing").catch(() => {
|
|
131
|
+
});
|
|
132
|
+
}, TYPING_INTERVAL_MS);
|
|
133
|
+
bot.telegram.sendChatAction(chatId, "typing").catch(() => {
|
|
134
|
+
});
|
|
135
|
+
try {
|
|
136
|
+
await handleAgentMessage(env, payload, {
|
|
137
|
+
onFlush: async (text, images) => {
|
|
138
|
+
for (const img of images) {
|
|
139
|
+
try {
|
|
140
|
+
await replyWithPhoto(Input.fromBuffer(Buffer.from(img.data, "base64")));
|
|
141
|
+
} catch (err) {
|
|
142
|
+
console.error(`Failed to send image: ${err}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (!text) return;
|
|
146
|
+
const chunks = splitMessage(text, TELEGRAM_MAX_LENGTH);
|
|
147
|
+
for (const chunk of chunks) {
|
|
148
|
+
try {
|
|
149
|
+
await reply(chunk);
|
|
150
|
+
} catch (err) {
|
|
151
|
+
console.error(`Failed to send message: ${err}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
onError: async (msg) => {
|
|
156
|
+
await reply(msg).catch(() => {
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
} finally {
|
|
161
|
+
clearInterval(typingInterval);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
bot.launch().then(() => {
|
|
165
|
+
console.log(`Connected to Telegram as @${bot.botInfo?.username}`);
|
|
166
|
+
console.log(`Bridging to agent: ${env.agentName} via ${env.baseUrl}/message`);
|
|
167
|
+
if (followedChatIds.length > 0) {
|
|
168
|
+
console.log(`Following chats: ${followedChatIds.join(", ")}`);
|
|
169
|
+
}
|
|
170
|
+
}).catch((err) => {
|
|
171
|
+
console.error("Failed to start Telegram connector:", err);
|
|
172
|
+
process.exit(1);
|
|
173
|
+
});
|
|
174
|
+
process.once("SIGINT", () => bot.stop("SIGINT"));
|
|
175
|
+
process.once("SIGTERM", () => bot.stop("SIGTERM"));
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
readStdin
|
|
4
|
+
} from "./chunk-ZYGKG6VC.js";
|
|
5
|
+
import {
|
|
6
|
+
resolveAgentName
|
|
7
|
+
} from "./chunk-AZEL2IEK.js";
|
|
8
|
+
import {
|
|
9
|
+
summarizeTool
|
|
10
|
+
} from "./chunk-B3R6L2GW.js";
|
|
11
|
+
import {
|
|
12
|
+
parseArgs
|
|
13
|
+
} from "./chunk-D424ZQGI.js";
|
|
14
|
+
import {
|
|
15
|
+
daemonFetch
|
|
16
|
+
} from "./chunk-7L4AN5D4.js";
|
|
17
|
+
import "./chunk-UX25Z2ND.js";
|
|
18
|
+
import "./chunk-K3NQKI34.js";
|
|
19
|
+
|
|
20
|
+
// src/commands/conversation.ts
|
|
21
|
+
import { userInfo } from "os";
|
|
22
|
+
async function run(args) {
|
|
23
|
+
const subcommand = args[0];
|
|
24
|
+
switch (subcommand) {
|
|
25
|
+
case "create":
|
|
26
|
+
await createConversation(args.slice(1));
|
|
27
|
+
break;
|
|
28
|
+
case "list":
|
|
29
|
+
await listConversations(args.slice(1));
|
|
30
|
+
break;
|
|
31
|
+
case "send":
|
|
32
|
+
await sendToConversation(args.slice(1));
|
|
33
|
+
break;
|
|
34
|
+
case "--help":
|
|
35
|
+
case "-h":
|
|
36
|
+
case void 0:
|
|
37
|
+
printUsage();
|
|
38
|
+
break;
|
|
39
|
+
default:
|
|
40
|
+
printUsage();
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function printUsage() {
|
|
45
|
+
console.log(`Usage:
|
|
46
|
+
volute conversation create --participants user1,agent1 [--title "..."] [--agent <name>]
|
|
47
|
+
volute conversation list [--agent <name>]
|
|
48
|
+
volute conversation send <id> "<message>" [--agent <name>]
|
|
49
|
+
echo "message" | volute conversation send <id> [--agent <name>]`);
|
|
50
|
+
}
|
|
51
|
+
async function createConversation(args) {
|
|
52
|
+
const { flags } = parseArgs(args, {
|
|
53
|
+
agent: { type: "string" },
|
|
54
|
+
participants: { type: "string" },
|
|
55
|
+
title: { type: "string" }
|
|
56
|
+
});
|
|
57
|
+
const agentName = resolveAgentName(flags);
|
|
58
|
+
if (!flags.participants) {
|
|
59
|
+
console.error("--participants is required (comma-separated usernames)");
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
const participantNames = flags.participants.split(",").map((s) => s.trim());
|
|
63
|
+
const res = await daemonFetch(`/api/agents/${encodeURIComponent(agentName)}/conversations`, {
|
|
64
|
+
method: "POST",
|
|
65
|
+
headers: { "Content-Type": "application/json" },
|
|
66
|
+
body: JSON.stringify({ participantNames, title: flags.title })
|
|
67
|
+
});
|
|
68
|
+
if (!res.ok) {
|
|
69
|
+
const data = await res.json();
|
|
70
|
+
console.error(data.error ?? `Failed to create conversation: ${res.status}`);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
const conv = await res.json();
|
|
74
|
+
console.log(`Created conversation: ${conv.id}`);
|
|
75
|
+
if (conv.title) console.log(`Title: ${conv.title}`);
|
|
76
|
+
}
|
|
77
|
+
async function listConversations(args) {
|
|
78
|
+
const { flags } = parseArgs(args, {
|
|
79
|
+
agent: { type: "string" }
|
|
80
|
+
});
|
|
81
|
+
const agentName = resolveAgentName(flags);
|
|
82
|
+
const res = await daemonFetch(`/api/agents/${encodeURIComponent(agentName)}/conversations`);
|
|
83
|
+
if (!res.ok) {
|
|
84
|
+
const data = await res.json();
|
|
85
|
+
console.error(data.error ?? `Failed to list conversations: ${res.status}`);
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
const convs = await res.json();
|
|
89
|
+
if (convs.length === 0) {
|
|
90
|
+
console.log("No conversations.");
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
for (const conv of convs) {
|
|
94
|
+
const title = conv.title || "(untitled)";
|
|
95
|
+
console.log(`${conv.id} ${title} ${conv.updated_at}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async function sendToConversation(args) {
|
|
99
|
+
const { positional, flags } = parseArgs(args, {
|
|
100
|
+
agent: { type: "string" }
|
|
101
|
+
});
|
|
102
|
+
const conversationId = positional[0];
|
|
103
|
+
const message = positional[1] ?? await readStdin();
|
|
104
|
+
if (!conversationId || !message) {
|
|
105
|
+
console.error('Usage: volute conversation send <id> "<message>" [--agent <name>]');
|
|
106
|
+
console.error(' echo "message" | volute conversation send <id> [--agent <name>]');
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
const agentName = resolveAgentName(flags);
|
|
110
|
+
const res = await daemonFetch(`/api/agents/${encodeURIComponent(agentName)}/chat`, {
|
|
111
|
+
method: "POST",
|
|
112
|
+
headers: { "Content-Type": "application/json" },
|
|
113
|
+
body: JSON.stringify({
|
|
114
|
+
message,
|
|
115
|
+
conversationId,
|
|
116
|
+
sender: process.env.VOLUTE_AGENT || userInfo().username
|
|
117
|
+
})
|
|
118
|
+
});
|
|
119
|
+
if (!res.ok) {
|
|
120
|
+
const data = await res.json();
|
|
121
|
+
console.error(data.error ?? `Failed to send message: ${res.status}`);
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
if (!res.body) {
|
|
125
|
+
console.error("No response body");
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
const reader = res.body.getReader();
|
|
129
|
+
const decoder = new TextDecoder();
|
|
130
|
+
let buffer = "";
|
|
131
|
+
while (true) {
|
|
132
|
+
const { done, value } = await reader.read();
|
|
133
|
+
if (done) break;
|
|
134
|
+
buffer += decoder.decode(value, { stream: true });
|
|
135
|
+
const lines = buffer.split("\n");
|
|
136
|
+
buffer = lines.pop() ?? "";
|
|
137
|
+
for (const line of lines) {
|
|
138
|
+
if (!line.startsWith("data:")) continue;
|
|
139
|
+
const data = line.slice(5).trim();
|
|
140
|
+
if (!data) continue;
|
|
141
|
+
let event;
|
|
142
|
+
try {
|
|
143
|
+
event = JSON.parse(data);
|
|
144
|
+
} catch {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (event.type === "text") {
|
|
148
|
+
process.stdout.write(event.content);
|
|
149
|
+
} else if (event.type === "tool_use") {
|
|
150
|
+
process.stderr.write(`${summarizeTool(event.name, event.input)}
|
|
151
|
+
`);
|
|
152
|
+
}
|
|
153
|
+
if (event.type === "done") {
|
|
154
|
+
process.stdout.write("\n");
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
process.stdout.write("\n");
|
|
160
|
+
}
|
|
161
|
+
export {
|
|
162
|
+
run
|
|
163
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
applyInitFiles,
|
|
4
|
+
composeTemplate,
|
|
5
|
+
copyTemplateToDir,
|
|
6
|
+
findTemplatesRoot,
|
|
7
|
+
listFiles
|
|
8
|
+
} from "./chunk-XUA3JUFK.js";
|
|
9
|
+
import {
|
|
10
|
+
parseArgs
|
|
11
|
+
} from "./chunk-D424ZQGI.js";
|
|
12
|
+
import {
|
|
13
|
+
chownAgentDir,
|
|
14
|
+
createAgentUser,
|
|
15
|
+
ensureVoluteGroup
|
|
16
|
+
} from "./chunk-UAVD2AHX.js";
|
|
17
|
+
import {
|
|
18
|
+
exec,
|
|
19
|
+
execInherit
|
|
20
|
+
} from "./chunk-5SKQ6J7T.js";
|
|
21
|
+
import {
|
|
22
|
+
addAgent,
|
|
23
|
+
agentDir,
|
|
24
|
+
ensureVoluteHome,
|
|
25
|
+
nextPort
|
|
26
|
+
} from "./chunk-UX25Z2ND.js";
|
|
27
|
+
import "./chunk-K3NQKI34.js";
|
|
28
|
+
|
|
29
|
+
// src/commands/create.ts
|
|
30
|
+
import { existsSync, rmSync } from "fs";
|
|
31
|
+
import { resolve } from "path";
|
|
32
|
+
var TEMPLATE_BRANCH = "volute/template";
|
|
33
|
+
async function run(args) {
|
|
34
|
+
const { positional, flags } = parseArgs(args, {
|
|
35
|
+
template: { type: "string" }
|
|
36
|
+
});
|
|
37
|
+
const name = positional[0];
|
|
38
|
+
const template = flags.template ?? "agent-sdk";
|
|
39
|
+
if (!name) {
|
|
40
|
+
console.error("Usage: volute agent create <name> [--template <name>]");
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
ensureVoluteHome();
|
|
44
|
+
const dest = agentDir(name);
|
|
45
|
+
if (existsSync(dest)) {
|
|
46
|
+
console.error(`Agent already exists: ${name}`);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
const templatesRoot = findTemplatesRoot();
|
|
50
|
+
const { composedDir, manifest } = composeTemplate(templatesRoot, template);
|
|
51
|
+
try {
|
|
52
|
+
copyTemplateToDir(composedDir, dest, name, manifest);
|
|
53
|
+
applyInitFiles(dest);
|
|
54
|
+
const port = nextPort();
|
|
55
|
+
addAgent(name, port);
|
|
56
|
+
console.log("Installing dependencies...");
|
|
57
|
+
await execInherit("npm", ["install"], { cwd: dest });
|
|
58
|
+
try {
|
|
59
|
+
await exec("git", ["init"], { cwd: dest });
|
|
60
|
+
await initTemplateBranch(dest, composedDir, manifest);
|
|
61
|
+
} catch (err) {
|
|
62
|
+
rmSync(resolve(dest, ".git"), { recursive: true, force: true });
|
|
63
|
+
console.warn(
|
|
64
|
+
"\nWarning: git setup failed \u2014 variants and upgrades won't be available.",
|
|
65
|
+
"\nTo fix: ensure git is installed with user.name/user.email configured."
|
|
66
|
+
);
|
|
67
|
+
console.warn("Details:", err.message ?? err);
|
|
68
|
+
}
|
|
69
|
+
ensureVoluteGroup();
|
|
70
|
+
createAgentUser(name);
|
|
71
|
+
chownAgentDir(dest, name);
|
|
72
|
+
console.log(`
|
|
73
|
+
Created agent: ${name} (port ${port})`);
|
|
74
|
+
console.log(`
|
|
75
|
+
volute agent start ${name}`);
|
|
76
|
+
} finally {
|
|
77
|
+
rmSync(composedDir, { recursive: true, force: true });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
async function initTemplateBranch(projectRoot, composedDir, manifest) {
|
|
81
|
+
const templateFiles = listFiles(composedDir).filter((f) => !f.startsWith(".init/") && !f.startsWith(".init\\")).map((f) => manifest.rename[f] ?? f);
|
|
82
|
+
await exec("git", ["checkout", "--orphan", TEMPLATE_BRANCH], { cwd: projectRoot });
|
|
83
|
+
await exec("git", ["add", "--", ...templateFiles], { cwd: projectRoot });
|
|
84
|
+
await exec("git", ["commit", "-m", "template update"], { cwd: projectRoot });
|
|
85
|
+
await exec("git", ["checkout", "-b", "main"], { cwd: projectRoot });
|
|
86
|
+
await exec("git", ["add", "-A"], { cwd: projectRoot });
|
|
87
|
+
await exec("git", ["commit", "-m", "initial commit"], { cwd: projectRoot });
|
|
88
|
+
}
|
|
89
|
+
export {
|
|
90
|
+
run
|
|
91
|
+
};
|