@emqo/claudebridge 0.1.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 +228 -0
- package/config.yaml.example +52 -0
- package/dist/adapters/base.d.ts +12 -0
- package/dist/adapters/base.js +19 -0
- package/dist/adapters/discord.d.ts +18 -0
- package/dist/adapters/discord.js +313 -0
- package/dist/adapters/telegram.d.ts +27 -0
- package/dist/adapters/telegram.js +418 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +111 -0
- package/dist/core/agent.d.ts +32 -0
- package/dist/core/agent.js +218 -0
- package/dist/core/config.d.ts +58 -0
- package/dist/core/config.js +49 -0
- package/dist/core/i18n.d.ts +5 -0
- package/dist/core/i18n.js +102 -0
- package/dist/core/intent.d.ts +10 -0
- package/dist/core/intent.js +84 -0
- package/dist/core/keys.d.ts +22 -0
- package/dist/core/keys.js +42 -0
- package/dist/core/lock.d.ts +12 -0
- package/dist/core/lock.js +57 -0
- package/dist/core/markdown.d.ts +7 -0
- package/dist/core/markdown.js +51 -0
- package/dist/core/permissions.d.ts +8 -0
- package/dist/core/permissions.js +22 -0
- package/dist/core/store.d.ts +64 -0
- package/dist/core/store.js +145 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +75 -0
- package/package.json +46 -0
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
import { chunkText } from "./base.js";
|
|
2
|
+
import { reloadConfig } from "../core/config.js";
|
|
3
|
+
import { toTelegramMarkdown } from "../core/markdown.js";
|
|
4
|
+
import { t, getCommandDescriptions } from "../core/i18n.js";
|
|
5
|
+
import { detectIntent } from "../core/intent.js";
|
|
6
|
+
const EDIT_INTERVAL = 1500;
|
|
7
|
+
export class TelegramAdapter {
|
|
8
|
+
engine;
|
|
9
|
+
store;
|
|
10
|
+
config;
|
|
11
|
+
locale;
|
|
12
|
+
running = false;
|
|
13
|
+
offset = 0;
|
|
14
|
+
reminderTimer;
|
|
15
|
+
autoTimer;
|
|
16
|
+
autoRunning = false;
|
|
17
|
+
constructor(engine, store, config, locale = "en") {
|
|
18
|
+
this.engine = engine;
|
|
19
|
+
this.store = store;
|
|
20
|
+
this.config = config;
|
|
21
|
+
this.locale = locale;
|
|
22
|
+
}
|
|
23
|
+
get api() {
|
|
24
|
+
return `https://api.telegram.org/bot${this.config.token}`;
|
|
25
|
+
}
|
|
26
|
+
async call(method, body) {
|
|
27
|
+
for (let i = 0; i < 3; i++) {
|
|
28
|
+
try {
|
|
29
|
+
const ctrl = new AbortController();
|
|
30
|
+
const timer = setTimeout(() => ctrl.abort(), 15000);
|
|
31
|
+
const res = await fetch(`${this.api}/${method}`, {
|
|
32
|
+
method: "POST",
|
|
33
|
+
headers: { "Content-Type": "application/json" },
|
|
34
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
35
|
+
signal: ctrl.signal,
|
|
36
|
+
});
|
|
37
|
+
clearTimeout(timer);
|
|
38
|
+
const json = await res.json();
|
|
39
|
+
return json.result;
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
if (i === 2)
|
|
43
|
+
throw err;
|
|
44
|
+
await new Promise(r => setTimeout(r, 1000 * (i + 1)));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async reply(chatId, text, parseMode) {
|
|
49
|
+
return this.call("sendMessage", {
|
|
50
|
+
chat_id: chatId,
|
|
51
|
+
text,
|
|
52
|
+
...(parseMode ? { parse_mode: parseMode } : {}),
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
async editMsg(chatId, msgId, text, parseMode) {
|
|
56
|
+
try {
|
|
57
|
+
await this.call("editMessageText", {
|
|
58
|
+
chat_id: chatId,
|
|
59
|
+
message_id: msgId,
|
|
60
|
+
text,
|
|
61
|
+
...(parseMode ? { parse_mode: parseMode } : {}),
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
catch { }
|
|
65
|
+
}
|
|
66
|
+
async handleUpdate(update) {
|
|
67
|
+
const msg = update.message;
|
|
68
|
+
if (!msg)
|
|
69
|
+
return;
|
|
70
|
+
const uid = msg.from?.id;
|
|
71
|
+
const chatId = msg.chat.id;
|
|
72
|
+
if (!uid)
|
|
73
|
+
return;
|
|
74
|
+
const groupId = msg.chat.type !== "private" ? String(chatId) : undefined;
|
|
75
|
+
if (!this.engine.access.isAllowed(String(uid), groupId)) {
|
|
76
|
+
console.log(`[telegram] user ${uid} not allowed`);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const text = (msg.text || "").trim();
|
|
80
|
+
console.log(`[telegram] ${uid}: ${text.slice(0, 50)}`);
|
|
81
|
+
// Commands
|
|
82
|
+
if (text === "/start" || text === "/help") {
|
|
83
|
+
await this.reply(chatId, t(this.locale, "help"));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (text === "/new") {
|
|
87
|
+
this.store.clearSession(String(uid));
|
|
88
|
+
await this.reply(chatId, t(this.locale, "session_cleared"));
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (text === "/usage") {
|
|
92
|
+
const u = this.store.getUsage(String(uid));
|
|
93
|
+
await this.reply(chatId, `Requests: ${u.count}\nCost: $${u.total_cost.toFixed(4)}`);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (text === "/allusage") {
|
|
97
|
+
const rows = this.store.getUsageAll();
|
|
98
|
+
if (!rows.length) {
|
|
99
|
+
await this.reply(chatId, t(this.locale, "no_usage"));
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
await this.reply(chatId, rows.map(r => `${r.user_id}: ${r.count} reqs, $${r.total_cost.toFixed(4)}`).join("\n"));
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (text === "/history") {
|
|
106
|
+
const rows = this.store.getHistory(String(uid), 5);
|
|
107
|
+
if (!rows.length) {
|
|
108
|
+
await this.reply(chatId, t(this.locale, "no_history"));
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const out = rows.reverse().map(r => `[${new Date(r.created_at).toLocaleString()}] ${r.role}: ${r.content.slice(0, 150)}`).join("\n\n");
|
|
112
|
+
await this.reply(chatId, out);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (text === "/model") {
|
|
116
|
+
const eps = this.engine.getEndpoints();
|
|
117
|
+
await this.reply(chatId, `Endpoints (${eps.length}):\n` + eps.map(e => `• ${e.name}: ${e.model || "default"}`).join("\n"));
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (text === "/reload") {
|
|
121
|
+
try {
|
|
122
|
+
const c = reloadConfig();
|
|
123
|
+
this.engine.reloadConfig(c);
|
|
124
|
+
this.locale = c.locale;
|
|
125
|
+
await this.reply(chatId, t(this.locale, "config_reloaded"));
|
|
126
|
+
}
|
|
127
|
+
catch (e) {
|
|
128
|
+
await this.reply(chatId, t(this.locale, "reload_failed") + e.message);
|
|
129
|
+
}
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (text.startsWith("/remember ")) {
|
|
133
|
+
const content = text.slice(10).trim();
|
|
134
|
+
if (!content) {
|
|
135
|
+
await this.reply(chatId, t(this.locale, "usage_remember"));
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
this.store.addMemory(String(uid), content);
|
|
139
|
+
await this.reply(chatId, t(this.locale, "memory_saved"));
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (text === "/memories") {
|
|
143
|
+
const mems = this.store.getMemories(String(uid));
|
|
144
|
+
if (!mems.length) {
|
|
145
|
+
await this.reply(chatId, t(this.locale, "no_memories"));
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
await this.reply(chatId, mems.map(m => `[${m.source}] ${m.content}`).join("\n\n"));
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (text === "/forget") {
|
|
152
|
+
this.store.clearMemories(String(uid));
|
|
153
|
+
await this.reply(chatId, t(this.locale, "memories_cleared"));
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
if (text.startsWith("/task ")) {
|
|
157
|
+
const desc = text.slice(6).trim();
|
|
158
|
+
if (!desc) {
|
|
159
|
+
await this.reply(chatId, t(this.locale, "usage_task"));
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const id = this.store.addTask(String(uid), "telegram", String(chatId), desc);
|
|
163
|
+
await this.reply(chatId, t(this.locale, "task_added", { id }));
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (text === "/tasks") {
|
|
167
|
+
const tasks = this.store.getTasks(String(uid));
|
|
168
|
+
if (!tasks.length) {
|
|
169
|
+
await this.reply(chatId, t(this.locale, "no_tasks"));
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
await this.reply(chatId, tasks.map(t => `#${t.id} ${t.description}${t.remind_at ? ` ⏰${new Date(t.remind_at).toLocaleString()}` : ""}`).join("\n"));
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (text.startsWith("/done ")) {
|
|
176
|
+
const id = parseInt(text.slice(6).trim());
|
|
177
|
+
if (isNaN(id)) {
|
|
178
|
+
await this.reply(chatId, t(this.locale, "usage_done"));
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const ok = this.store.completeTask(id, String(uid));
|
|
182
|
+
await this.reply(chatId, ok ? t(this.locale, "task_done", { id }) : t(this.locale, "task_not_found", { id }));
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (text.startsWith("/remind ")) {
|
|
186
|
+
const match = text.match(/^\/remind\s+(\d+)m\s+(.+)$/);
|
|
187
|
+
if (!match) {
|
|
188
|
+
await this.reply(chatId, t(this.locale, "usage_remind"));
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
const mins = parseInt(match[1]);
|
|
192
|
+
const desc = match[2].trim();
|
|
193
|
+
const remindAt = Date.now() + mins * 60000;
|
|
194
|
+
const id = this.store.addTask(String(uid), "telegram", String(chatId), desc, remindAt);
|
|
195
|
+
await this.reply(chatId, t(this.locale, "reminder_set", { id, mins }));
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (text.startsWith("/auto ")) {
|
|
199
|
+
const desc = text.slice(6).trim();
|
|
200
|
+
if (!desc) {
|
|
201
|
+
await this.reply(chatId, t(this.locale, "usage_auto"));
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const id = this.store.addTask(String(uid), "telegram", String(chatId), desc, undefined, true);
|
|
205
|
+
await this.reply(chatId, t(this.locale, "auto_queued", { id }));
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
if (text === "/autotasks") {
|
|
209
|
+
const all = this.store.getAutoTasks(String(uid));
|
|
210
|
+
if (!all.length) {
|
|
211
|
+
await this.reply(chatId, t(this.locale, "no_auto_tasks"));
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
await this.reply(chatId, all.map(t => `#${t.id} [${t.status}] ${t.description}`).join("\n"));
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (text.startsWith("/cancelauto ")) {
|
|
218
|
+
const id = parseInt(text.slice(12).trim());
|
|
219
|
+
if (isNaN(id)) {
|
|
220
|
+
await this.reply(chatId, t(this.locale, "usage_cancelauto"));
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
this.store.markTaskResult(id, "cancelled");
|
|
224
|
+
await this.reply(chatId, t(this.locale, "auto_cancelled", { id }));
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
// File upload
|
|
228
|
+
if (msg.document || msg.photo) {
|
|
229
|
+
let fileId;
|
|
230
|
+
let fileName;
|
|
231
|
+
if (msg.document) {
|
|
232
|
+
fileId = msg.document.file_id;
|
|
233
|
+
fileName = msg.document.file_name || "upload";
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
const p = msg.photo[msg.photo.length - 1];
|
|
237
|
+
fileId = p.file_id;
|
|
238
|
+
fileName = "photo.jpg";
|
|
239
|
+
}
|
|
240
|
+
try {
|
|
241
|
+
const file = await this.call("getFile", { file_id: fileId });
|
|
242
|
+
const url = `https://api.telegram.org/file/bot${this.config.token}/${file.file_path}`;
|
|
243
|
+
const resp = await fetch(url);
|
|
244
|
+
const buf = Buffer.from(await resp.arrayBuffer());
|
|
245
|
+
const { mkdirSync, writeFileSync } = await import("fs");
|
|
246
|
+
const { join } = await import("path");
|
|
247
|
+
const ws = join("workspaces", String(uid));
|
|
248
|
+
mkdirSync(ws, { recursive: true });
|
|
249
|
+
writeFileSync(join(ws, fileName), buf);
|
|
250
|
+
const prompt = msg.caption || `Analyze the uploaded file: ${fileName}`;
|
|
251
|
+
await this.handlePrompt(chatId, String(uid), prompt);
|
|
252
|
+
}
|
|
253
|
+
catch (e) {
|
|
254
|
+
await this.reply(chatId, t(this.locale, "upload_failed") + e.message);
|
|
255
|
+
}
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
// Intent detection (before sending to Claude)
|
|
259
|
+
if (text && !text.startsWith("/") && this.engine.getIntentConfig()?.enabled !== false) {
|
|
260
|
+
const intent = await detectIntent(text, this.engine.getRotator(), this.engine.getIntentConfig());
|
|
261
|
+
if (intent.type === "reminder" && intent.minutes && intent.description) {
|
|
262
|
+
const remindAt = Date.now() + intent.minutes * 60000;
|
|
263
|
+
const id = this.store.addTask(String(uid), "telegram", String(chatId), intent.description, remindAt);
|
|
264
|
+
await this.reply(chatId, t(this.locale, "intent_reminder_set", { mins: intent.minutes, desc: intent.description, id }));
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (intent.type === "task" && intent.description) {
|
|
268
|
+
const id = this.store.addTask(String(uid), "telegram", String(chatId), intent.description);
|
|
269
|
+
await this.reply(chatId, t(this.locale, "intent_task_added", { id, desc: intent.description }));
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
if (intent.type === "memory" && intent.description) {
|
|
273
|
+
this.store.addMemory(String(uid), intent.description, "nlp");
|
|
274
|
+
await this.reply(chatId, t(this.locale, "intent_memory_saved", { desc: intent.description }));
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
if (intent.type === "forget") {
|
|
278
|
+
this.store.clearMemories(String(uid));
|
|
279
|
+
await this.reply(chatId, t(this.locale, "memories_cleared"));
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
if (intent.type === "clear_session") {
|
|
283
|
+
this.store.clearSession(String(uid));
|
|
284
|
+
await this.reply(chatId, t(this.locale, "session_cleared"));
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
// Text message
|
|
289
|
+
if (text)
|
|
290
|
+
await this.handlePrompt(chatId, String(uid), text);
|
|
291
|
+
}
|
|
292
|
+
async handlePrompt(chatId, uid, text) {
|
|
293
|
+
if (this.engine.isLocked(uid)) {
|
|
294
|
+
await this.reply(chatId, t(this.locale, "still_processing"));
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
const placeholder = await this.reply(chatId, t(this.locale, "thinking"));
|
|
298
|
+
const msgId = placeholder.message_id;
|
|
299
|
+
let lastEdit = 0;
|
|
300
|
+
try {
|
|
301
|
+
console.log(`[telegram] running claude for ${uid}...`);
|
|
302
|
+
const res = await this.engine.runStream(uid, text, "telegram", async (_chunk, full) => {
|
|
303
|
+
const now = Date.now();
|
|
304
|
+
if (now - lastEdit < EDIT_INTERVAL)
|
|
305
|
+
return;
|
|
306
|
+
lastEdit = now;
|
|
307
|
+
const preview = full.slice(-3500) + "\n\n⏳...";
|
|
308
|
+
await this.editMsg(chatId, msgId, preview);
|
|
309
|
+
});
|
|
310
|
+
console.log(`[telegram] claude done for ${uid}, cost=$${res.cost?.toFixed(4)}`);
|
|
311
|
+
const maxLen = this.config.chunk_size || 4000;
|
|
312
|
+
const md = toTelegramMarkdown(res.text);
|
|
313
|
+
const chunks = chunkText(md, maxLen);
|
|
314
|
+
try {
|
|
315
|
+
await this.editMsg(chatId, msgId, chunks[0], "MarkdownV2");
|
|
316
|
+
}
|
|
317
|
+
catch {
|
|
318
|
+
await this.editMsg(chatId, msgId, res.text);
|
|
319
|
+
}
|
|
320
|
+
for (let i = 1; i < chunks.length; i++) {
|
|
321
|
+
try {
|
|
322
|
+
await this.reply(chatId, chunks[i], "MarkdownV2");
|
|
323
|
+
}
|
|
324
|
+
catch {
|
|
325
|
+
await this.reply(chatId, chunks[i]);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
catch (err) {
|
|
330
|
+
console.error("[telegram] claude error:", err);
|
|
331
|
+
await this.editMsg(chatId, msgId, `Error: ${err.message || "unknown"}`);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
async start() {
|
|
335
|
+
this.running = true;
|
|
336
|
+
console.log("[telegram] starting long polling...");
|
|
337
|
+
this.reminderTimer = setInterval(() => this.checkReminders(), 30000);
|
|
338
|
+
this.autoTimer = setInterval(() => this.processAutoTasks(), 60000);
|
|
339
|
+
await this.registerCommands();
|
|
340
|
+
while (this.running) {
|
|
341
|
+
try {
|
|
342
|
+
const ctrl = new AbortController();
|
|
343
|
+
const timer = setTimeout(() => ctrl.abort(), 15000);
|
|
344
|
+
const res = await fetch(`${this.api}/getUpdates?offset=${this.offset}&timeout=5`, { signal: ctrl.signal });
|
|
345
|
+
clearTimeout(timer);
|
|
346
|
+
const json = await res.json();
|
|
347
|
+
if (!json.ok) {
|
|
348
|
+
console.error("[telegram] poll error:", json);
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
for (const update of json.result) {
|
|
352
|
+
this.offset = update.update_id + 1;
|
|
353
|
+
this.handleUpdate(update).catch(e => console.error("[telegram] handler error:", e));
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
catch (err) {
|
|
357
|
+
console.error("[telegram] poll fetch error:", err);
|
|
358
|
+
await new Promise(r => setTimeout(r, 3000));
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
stop() {
|
|
363
|
+
this.running = false;
|
|
364
|
+
if (this.reminderTimer)
|
|
365
|
+
clearInterval(this.reminderTimer);
|
|
366
|
+
if (this.autoTimer)
|
|
367
|
+
clearInterval(this.autoTimer);
|
|
368
|
+
}
|
|
369
|
+
async registerCommands() {
|
|
370
|
+
try {
|
|
371
|
+
await this.call("setMyCommands", { commands: getCommandDescriptions(this.locale) });
|
|
372
|
+
console.log("[telegram] commands registered");
|
|
373
|
+
}
|
|
374
|
+
catch (e) {
|
|
375
|
+
console.error("[telegram] failed to register commands:", e);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
async checkReminders() {
|
|
379
|
+
try {
|
|
380
|
+
const due = this.store.getDueReminders().filter(r => r.platform === "telegram");
|
|
381
|
+
for (const r of due) {
|
|
382
|
+
await this.reply(Number(r.chat_id), t(this.locale, "reminder_notify", { desc: r.description }));
|
|
383
|
+
this.store.markReminderSent(r.id);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
catch (e) {
|
|
387
|
+
console.error("[telegram] reminder error:", e);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
async processAutoTasks() {
|
|
391
|
+
if (this.autoRunning)
|
|
392
|
+
return;
|
|
393
|
+
const task = this.store.getNextAutoTask();
|
|
394
|
+
if (!task)
|
|
395
|
+
return;
|
|
396
|
+
this.autoRunning = true;
|
|
397
|
+
this.store.markTaskRunning(task.id);
|
|
398
|
+
const chatId = Number(task.chat_id);
|
|
399
|
+
await this.reply(chatId, t(this.locale, "auto_starting", { id: task.id, desc: task.description }));
|
|
400
|
+
try {
|
|
401
|
+
console.log(`[telegram] auto-task #${task.id} for ${task.user_id}`);
|
|
402
|
+
const res = await this.engine.runStream(task.user_id, task.description, "telegram");
|
|
403
|
+
this.store.markTaskResult(task.id, "done");
|
|
404
|
+
const maxLen = this.config.chunk_size || 4000;
|
|
405
|
+
const chunks = chunkText(res.text || "(no output)", maxLen);
|
|
406
|
+
await this.reply(chatId, t(this.locale, "auto_done", { id: task.id, cost: (res.cost || 0).toFixed(4) }));
|
|
407
|
+
for (const c of chunks)
|
|
408
|
+
await this.reply(chatId, c);
|
|
409
|
+
}
|
|
410
|
+
catch (err) {
|
|
411
|
+
this.store.markTaskResult(task.id, "failed");
|
|
412
|
+
await this.reply(chatId, t(this.locale, "auto_failed", { id: task.id, err: err.message || "unknown" }));
|
|
413
|
+
}
|
|
414
|
+
finally {
|
|
415
|
+
this.autoRunning = false;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from "child_process";
|
|
3
|
+
import { readFileSync, writeFileSync, unlinkSync, mkdirSync, copyFileSync, existsSync } from "fs";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
import { homedir } from "os";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
7
|
+
import { dirname } from "path";
|
|
8
|
+
const DIR = join(homedir(), ".claudebridge");
|
|
9
|
+
const PID_FILE = join(DIR, "claudebridge.pid");
|
|
10
|
+
const LOG_FILE = join(DIR, "claudebridge.log");
|
|
11
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
+
const __dirname = dirname(__filename);
|
|
13
|
+
const ENTRY = join(__dirname, "index.js");
|
|
14
|
+
function ensureDir() { mkdirSync(DIR, { recursive: true }); }
|
|
15
|
+
function readPid() {
|
|
16
|
+
try {
|
|
17
|
+
const pid = parseInt(readFileSync(PID_FILE, "utf-8").trim(), 10);
|
|
18
|
+
process.kill(pid, 0);
|
|
19
|
+
return pid;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function writePid(pid) { ensureDir(); writeFileSync(PID_FILE, String(pid)); }
|
|
26
|
+
function removePid() { try {
|
|
27
|
+
unlinkSync(PID_FILE);
|
|
28
|
+
}
|
|
29
|
+
catch { } }
|
|
30
|
+
const args = process.argv.slice(2);
|
|
31
|
+
const cmd = args.find(a => !a.startsWith("-")) || "start";
|
|
32
|
+
const cfgIdx = args.indexOf("--config");
|
|
33
|
+
const cfgPath = cfgIdx !== -1 ? args[cfgIdx + 1] : undefined;
|
|
34
|
+
const daemon = args.includes("--daemon") || args.includes("-d");
|
|
35
|
+
switch (cmd) {
|
|
36
|
+
case "start": {
|
|
37
|
+
const existing = readPid();
|
|
38
|
+
if (existing) {
|
|
39
|
+
console.log(`Already running (PID ${existing})`);
|
|
40
|
+
process.exit(0);
|
|
41
|
+
}
|
|
42
|
+
const childArgs = [ENTRY, ...(cfgPath ? ["--config", cfgPath] : [])];
|
|
43
|
+
if (daemon) {
|
|
44
|
+
ensureDir();
|
|
45
|
+
const { openSync } = await import("fs");
|
|
46
|
+
const logFd = openSync(LOG_FILE, "a");
|
|
47
|
+
const child = spawn("node", childArgs, { detached: true, stdio: ["ignore", logFd, logFd] });
|
|
48
|
+
child.unref();
|
|
49
|
+
writePid(child.pid);
|
|
50
|
+
console.log(`Started in background (PID ${child.pid}), log: ${LOG_FILE}`);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
const child = spawn("node", childArgs, { stdio: "inherit" });
|
|
54
|
+
writePid(child.pid);
|
|
55
|
+
child.on("exit", (code) => { removePid(); process.exit(code ?? 0); });
|
|
56
|
+
for (const sig of ["SIGINT", "SIGTERM"])
|
|
57
|
+
process.on(sig, () => child.kill(sig));
|
|
58
|
+
}
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
case "stop": {
|
|
62
|
+
const pid = readPid();
|
|
63
|
+
if (!pid) {
|
|
64
|
+
console.log("Not running");
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
process.kill(pid, "SIGTERM");
|
|
68
|
+
removePid();
|
|
69
|
+
console.log(`Stopped (PID ${pid})`);
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
case "status": {
|
|
73
|
+
const pid = readPid();
|
|
74
|
+
if (pid) {
|
|
75
|
+
console.log(`Running (PID ${pid})`);
|
|
76
|
+
process.exit(0);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
console.log("Not running");
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
case "reload": {
|
|
85
|
+
const pid = readPid();
|
|
86
|
+
if (!pid) {
|
|
87
|
+
console.log("Not running");
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
process.kill(pid, "SIGHUP");
|
|
91
|
+
console.log(`Reload signal sent (PID ${pid})`);
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
case "init": {
|
|
95
|
+
if (existsSync("config.yaml")) {
|
|
96
|
+
console.log("config.yaml already exists");
|
|
97
|
+
process.exit(0);
|
|
98
|
+
}
|
|
99
|
+
const example = join(__dirname, "..", "config.yaml.example");
|
|
100
|
+
if (!existsSync(example)) {
|
|
101
|
+
console.error("config.yaml.example not found");
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
copyFileSync(example, "config.yaml");
|
|
105
|
+
console.log("Created config.yaml from template");
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
default:
|
|
109
|
+
console.log("Usage: claudebridge <start|stop|status|reload|init> [--config path] [--daemon|-d]");
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Config } from "./config.js";
|
|
2
|
+
import { Store } from "./store.js";
|
|
3
|
+
import { AccessControl } from "./permissions.js";
|
|
4
|
+
import { EndpointRotator } from "./keys.js";
|
|
5
|
+
export interface AgentResponse {
|
|
6
|
+
text: string;
|
|
7
|
+
sessionId: string;
|
|
8
|
+
cost?: number;
|
|
9
|
+
}
|
|
10
|
+
export type StreamCallback = (chunk: string, full: string) => void | Promise<void>;
|
|
11
|
+
export declare class AgentEngine {
|
|
12
|
+
private config;
|
|
13
|
+
private store;
|
|
14
|
+
private lock;
|
|
15
|
+
private rotator;
|
|
16
|
+
access: AccessControl;
|
|
17
|
+
constructor(config: Config, store: Store);
|
|
18
|
+
reloadConfig(config: Config): void;
|
|
19
|
+
getEndpoints(): {
|
|
20
|
+
name: string;
|
|
21
|
+
model: string;
|
|
22
|
+
}[];
|
|
23
|
+
getRotator(): EndpointRotator;
|
|
24
|
+
getIntentConfig(): import("./config.js").IntentConfig;
|
|
25
|
+
getEndpointCount(): number;
|
|
26
|
+
private getWorkDir;
|
|
27
|
+
isLocked(userId: string): boolean;
|
|
28
|
+
runStream(userId: string, prompt: string, platform: string, onChunk?: StreamCallback): Promise<AgentResponse>;
|
|
29
|
+
private _executeWithRetry;
|
|
30
|
+
private _execute;
|
|
31
|
+
private _autoSummarize;
|
|
32
|
+
}
|