@emqo/claudebridge 0.4.0 → 0.5.1
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/LICENSE +21 -0
- package/README.md +243 -84
- package/config.yaml.example +17 -0
- package/dist/adapters/discord.d.ts +6 -1
- package/dist/adapters/discord.js +88 -11
- package/dist/adapters/telegram.d.ts +7 -1
- package/dist/adapters/telegram.js +132 -13
- package/dist/core/agent.d.ts +3 -0
- package/dist/core/agent.js +119 -0
- package/dist/core/config.d.ts +16 -0
- package/dist/core/config.js +3 -0
- package/dist/core/i18n.js +32 -18
- package/dist/core/store.d.ts +45 -1
- package/dist/core/store.js +63 -2
- package/dist/ctl.js +32 -3
- package/dist/index.js +9 -0
- package/dist/skills/bridge.js +32 -0
- package/dist/webhook.d.ts +18 -0
- package/dist/webhook.js +160 -0
- package/package.json +13 -5
package/dist/adapters/discord.js
CHANGED
|
@@ -13,7 +13,9 @@ export class DiscordAdapter {
|
|
|
13
13
|
client;
|
|
14
14
|
reminderTimer;
|
|
15
15
|
autoTimer;
|
|
16
|
-
|
|
16
|
+
approvalTimer;
|
|
17
|
+
activeAutoTasks = 0;
|
|
18
|
+
maxParallel = 1;
|
|
17
19
|
constructor(engine, store, config, locale = "en") {
|
|
18
20
|
this.engine = engine;
|
|
19
21
|
this.store = store;
|
|
@@ -98,6 +100,30 @@ export class DiscordAdapter {
|
|
|
98
100
|
}
|
|
99
101
|
return;
|
|
100
102
|
}
|
|
103
|
+
if (text.startsWith("!approve ")) {
|
|
104
|
+
const taskId = parseInt(text.split(" ")[1]);
|
|
105
|
+
if (isNaN(taskId)) {
|
|
106
|
+
await msg.reply("Usage: !approve <task_id>");
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const ok = this.store.approveTask(taskId);
|
|
110
|
+
await msg.reply(ok ? t(this.locale, "approval_approved", { id: taskId }) : t(this.locale, "approval_decided", { id: taskId }));
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (text.startsWith("!reject ")) {
|
|
114
|
+
const taskId = parseInt(text.split(" ")[1]);
|
|
115
|
+
if (isNaN(taskId)) {
|
|
116
|
+
await msg.reply("Usage: !reject <task_id>");
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const ok = this.store.rejectTask(taskId);
|
|
120
|
+
await msg.reply(ok ? t(this.locale, "approval_rejected", { id: taskId }) : t(this.locale, "approval_decided", { id: taskId }));
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (text === "!status") {
|
|
124
|
+
await this.handleStatusCommand(msg);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
101
127
|
// File upload handling
|
|
102
128
|
if (msg.attachments.size > 0) {
|
|
103
129
|
const ws = this.engine.getWorkDir(msg.author.id);
|
|
@@ -133,7 +159,7 @@ export class DiscordAdapter {
|
|
|
133
159
|
const now = Date.now();
|
|
134
160
|
if (now - lastEdit < EDIT_INTERVAL)
|
|
135
161
|
return;
|
|
136
|
-
const preview = full.slice(-1900) + "\n\n
|
|
162
|
+
const preview = full.slice(-1900) + "\n\n...";
|
|
137
163
|
if (preview === lastText)
|
|
138
164
|
return;
|
|
139
165
|
lastText = preview;
|
|
@@ -165,14 +191,19 @@ export class DiscordAdapter {
|
|
|
165
191
|
console.log("[discord] starting bot...");
|
|
166
192
|
await this.client.login(this.config.token);
|
|
167
193
|
console.log(`[discord] logged in as ${this.client.user?.tag}`);
|
|
194
|
+
this.maxParallel = this.engine.getMaxParallel();
|
|
195
|
+
console.log(`[discord] max_parallel=${this.maxParallel}`);
|
|
168
196
|
this.reminderTimer = setInterval(() => this.checkReminders(), 30000);
|
|
169
197
|
this.autoTimer = setInterval(() => this.processAutoTasks(), 60000);
|
|
198
|
+
this.approvalTimer = setInterval(() => this.checkApprovals(), 15000);
|
|
170
199
|
}
|
|
171
200
|
stop() {
|
|
172
201
|
if (this.reminderTimer)
|
|
173
202
|
clearInterval(this.reminderTimer);
|
|
174
203
|
if (this.autoTimer)
|
|
175
204
|
clearInterval(this.autoTimer);
|
|
205
|
+
if (this.approvalTimer)
|
|
206
|
+
clearInterval(this.approvalTimer);
|
|
176
207
|
this.client.destroy();
|
|
177
208
|
}
|
|
178
209
|
async checkReminders() {
|
|
@@ -190,13 +221,17 @@ export class DiscordAdapter {
|
|
|
190
221
|
}
|
|
191
222
|
}
|
|
192
223
|
async processAutoTasks() {
|
|
193
|
-
|
|
224
|
+
const available = this.maxParallel - this.activeAutoTasks;
|
|
225
|
+
if (available <= 0)
|
|
194
226
|
return;
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
227
|
+
const tasks = this.store.getNextAutoTasks("discord", available);
|
|
228
|
+
for (const task of tasks) {
|
|
229
|
+
this.activeAutoTasks++;
|
|
230
|
+
this.store.markTaskRunning(task.id);
|
|
231
|
+
this.runAutoTask(task).finally(() => { this.activeAutoTasks--; });
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
async runAutoTask(task) {
|
|
200
235
|
try {
|
|
201
236
|
const ch = await this.client.channels.fetch(task.chat_id);
|
|
202
237
|
if (!ch?.isTextBased() || !("send" in ch))
|
|
@@ -204,13 +239,23 @@ export class DiscordAdapter {
|
|
|
204
239
|
const channel = ch;
|
|
205
240
|
await channel.send(t(this.locale, "auto_starting", { id: task.id, desc: task.description }));
|
|
206
241
|
console.log(`[discord] auto-task #${task.id} for ${task.user_id}`);
|
|
207
|
-
const res =
|
|
242
|
+
const res = this.maxParallel > 1
|
|
243
|
+
? await this.engine.runParallel(task.user_id, task.description, "discord", task.chat_id)
|
|
244
|
+
: await this.engine.runStream(task.user_id, task.description, "discord", task.chat_id);
|
|
208
245
|
this.store.markTaskResult(task.id, "done");
|
|
246
|
+
if (res.text)
|
|
247
|
+
this.store.setTaskResult(task.id, res.text.slice(0, 10000));
|
|
209
248
|
const maxLen = this.config.chunk_size || 1900;
|
|
210
249
|
const chunks = chunkText(res.text || "(no output)", maxLen);
|
|
211
250
|
await channel.send(t(this.locale, "auto_done", { id: task.id, cost: (res.cost || 0).toFixed(4) }));
|
|
212
251
|
for (const c of chunks)
|
|
213
252
|
await channel.send(c);
|
|
253
|
+
// Chain progress reporting
|
|
254
|
+
if (task.parent_id) {
|
|
255
|
+
const progress = this.store.getChainProgress(task.parent_id);
|
|
256
|
+
const costSuffix = res.cost ? ` | Cost: $${res.cost.toFixed(4)}` : "";
|
|
257
|
+
await channel.send(t(this.locale, "chain_progress", { id: task.parent_id, done: progress.done, total: progress.total, cost: costSuffix }));
|
|
258
|
+
}
|
|
214
259
|
}
|
|
215
260
|
catch (err) {
|
|
216
261
|
this.store.markTaskResult(task.id, "failed");
|
|
@@ -223,8 +268,40 @@ export class DiscordAdapter {
|
|
|
223
268
|
}
|
|
224
269
|
catch { }
|
|
225
270
|
}
|
|
226
|
-
|
|
227
|
-
|
|
271
|
+
}
|
|
272
|
+
async checkApprovals() {
|
|
273
|
+
try {
|
|
274
|
+
const pending = this.store.getPendingApprovals("discord");
|
|
275
|
+
for (const task of pending) {
|
|
276
|
+
const ch = await this.client.channels.fetch(task.chat_id);
|
|
277
|
+
if (ch?.isTextBased() && "send" in ch) {
|
|
278
|
+
await ch.send(t(this.locale, "approval_request", { id: task.id, desc: task.description }) +
|
|
279
|
+
`\n\nReply \`!approve ${task.id}\` or \`!reject ${task.id}\``);
|
|
280
|
+
}
|
|
281
|
+
this.store.markReminderSent(task.id);
|
|
282
|
+
}
|
|
228
283
|
}
|
|
284
|
+
catch (e) {
|
|
285
|
+
console.error("[discord] approval check error:", e);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
async handleStatusCommand(msg) {
|
|
289
|
+
const recent = this.store.getRecentAutoTasks("discord", 10);
|
|
290
|
+
if (!recent.length) {
|
|
291
|
+
await msg.reply(t(this.locale, "no_auto_tasks"));
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
const statusEmoji = {
|
|
295
|
+
auto: "[queue]", running: "[run]", done: "[done]", failed: "[fail]",
|
|
296
|
+
approval_pending: "[pending]", cancelled: "[cancel]",
|
|
297
|
+
};
|
|
298
|
+
const lines = recent.map(task => {
|
|
299
|
+
const chain = task.parent_id ? ` (chain #${task.parent_id})` : "";
|
|
300
|
+
return `${statusEmoji[task.status] || "[?]"} #${task.id} [${task.status}] ${task.description.slice(0, 60)}${chain}`;
|
|
301
|
+
});
|
|
302
|
+
const stats = this.store.getAutoTaskStats();
|
|
303
|
+
const summary = stats.map(s => `${s.status}: ${s.count}`).join(" | ");
|
|
304
|
+
const report = `${t(this.locale, "status_report")}\n${lines.join("\n")}\n\nSummary: ${summary}`;
|
|
305
|
+
await msg.reply(report);
|
|
229
306
|
}
|
|
230
307
|
}
|
|
@@ -11,7 +11,9 @@ export declare class TelegramAdapter implements Adapter {
|
|
|
11
11
|
private offset;
|
|
12
12
|
private reminderTimer?;
|
|
13
13
|
private autoTimer?;
|
|
14
|
-
private
|
|
14
|
+
private approvalTimer?;
|
|
15
|
+
private activeAutoTasks;
|
|
16
|
+
private maxParallel;
|
|
15
17
|
private pages;
|
|
16
18
|
private static PAGE_TTL;
|
|
17
19
|
constructor(engine: AgentEngine, store: Store, config: TelegramConfig, locale?: string);
|
|
@@ -28,4 +30,8 @@ export declare class TelegramAdapter implements Adapter {
|
|
|
28
30
|
private registerCommands;
|
|
29
31
|
private checkReminders;
|
|
30
32
|
private processAutoTasks;
|
|
33
|
+
private runAutoTask;
|
|
34
|
+
private checkApprovals;
|
|
35
|
+
private handleApprovalCallback;
|
|
36
|
+
private handleStatusCommand;
|
|
31
37
|
}
|
|
@@ -12,7 +12,9 @@ export class TelegramAdapter {
|
|
|
12
12
|
offset = 0;
|
|
13
13
|
reminderTimer;
|
|
14
14
|
autoTimer;
|
|
15
|
-
|
|
15
|
+
approvalTimer;
|
|
16
|
+
activeAutoTasks = 0;
|
|
17
|
+
maxParallel = 1;
|
|
16
18
|
pages = new Map();
|
|
17
19
|
static PAGE_TTL = 30 * 60 * 1000; // 30 minutes
|
|
18
20
|
constructor(engine, store, config, locale = "en") {
|
|
@@ -72,7 +74,13 @@ export class TelegramAdapter {
|
|
|
72
74
|
}
|
|
73
75
|
async handleUpdate(update) {
|
|
74
76
|
if (update.callback_query) {
|
|
75
|
-
|
|
77
|
+
const data = update.callback_query.data || "";
|
|
78
|
+
if (data.startsWith("approve:") || data.startsWith("reject:")) {
|
|
79
|
+
await this.handleApprovalCallback(update.callback_query);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
await this.handlePageCallback(update.callback_query);
|
|
83
|
+
}
|
|
76
84
|
return;
|
|
77
85
|
}
|
|
78
86
|
const msg = update.message;
|
|
@@ -140,6 +148,10 @@ export class TelegramAdapter {
|
|
|
140
148
|
}
|
|
141
149
|
return;
|
|
142
150
|
}
|
|
151
|
+
if (text === "/status") {
|
|
152
|
+
await this.handleStatusCommand(chatId, String(uid));
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
143
155
|
// File upload
|
|
144
156
|
if (msg.document || msg.photo) {
|
|
145
157
|
let fileId;
|
|
@@ -254,7 +266,7 @@ export class TelegramAdapter {
|
|
|
254
266
|
if (now - lastEdit < EDIT_INTERVAL)
|
|
255
267
|
return;
|
|
256
268
|
lastEdit = now;
|
|
257
|
-
const preview = full.slice(-3500) + "\n\n
|
|
269
|
+
const preview = full.slice(-3500) + "\n\n...";
|
|
258
270
|
await this.editMsg(chatId, msgId, preview);
|
|
259
271
|
});
|
|
260
272
|
console.log(`[telegram] claude done for ${uid}, cost=$${res.cost?.toFixed(4)}`);
|
|
@@ -307,9 +319,11 @@ export class TelegramAdapter {
|
|
|
307
319
|
}
|
|
308
320
|
async start() {
|
|
309
321
|
this.running = true;
|
|
310
|
-
|
|
322
|
+
this.maxParallel = this.engine.getMaxParallel();
|
|
323
|
+
console.log(`[telegram] starting long polling... (max_parallel=${this.maxParallel})`);
|
|
311
324
|
this.reminderTimer = setInterval(() => this.checkReminders(), 30000);
|
|
312
325
|
this.autoTimer = setInterval(() => this.processAutoTasks(), 60000);
|
|
326
|
+
this.approvalTimer = setInterval(() => this.checkApprovals(), 15000);
|
|
313
327
|
await this.registerCommands();
|
|
314
328
|
while (this.running) {
|
|
315
329
|
try {
|
|
@@ -339,6 +353,8 @@ export class TelegramAdapter {
|
|
|
339
353
|
clearInterval(this.reminderTimer);
|
|
340
354
|
if (this.autoTimer)
|
|
341
355
|
clearInterval(this.autoTimer);
|
|
356
|
+
if (this.approvalTimer)
|
|
357
|
+
clearInterval(this.approvalTimer);
|
|
342
358
|
}
|
|
343
359
|
async registerCommands() {
|
|
344
360
|
try {
|
|
@@ -362,31 +378,134 @@ export class TelegramAdapter {
|
|
|
362
378
|
}
|
|
363
379
|
}
|
|
364
380
|
async processAutoTasks() {
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
const task = this.store.getNextAutoTask("telegram");
|
|
368
|
-
if (!task)
|
|
381
|
+
const available = this.maxParallel - this.activeAutoTasks;
|
|
382
|
+
if (available <= 0)
|
|
369
383
|
return;
|
|
370
|
-
this.
|
|
371
|
-
|
|
384
|
+
const tasks = this.store.getNextAutoTasks("telegram", available);
|
|
385
|
+
for (const task of tasks) {
|
|
386
|
+
this.activeAutoTasks++;
|
|
387
|
+
this.store.markTaskRunning(task.id);
|
|
388
|
+
this.runAutoTask(task).finally(() => { this.activeAutoTasks--; });
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
async runAutoTask(task) {
|
|
372
392
|
const chatId = Number(task.chat_id);
|
|
373
393
|
await this.reply(chatId, t(this.locale, "auto_starting", { id: task.id, desc: task.description }));
|
|
374
394
|
try {
|
|
375
395
|
console.log(`[telegram] auto-task #${task.id} for ${task.user_id}`);
|
|
376
|
-
const res =
|
|
396
|
+
const res = this.maxParallel > 1
|
|
397
|
+
? await this.engine.runParallel(task.user_id, task.description, "telegram", task.chat_id)
|
|
398
|
+
: await this.engine.runStream(task.user_id, task.description, "telegram", task.chat_id);
|
|
377
399
|
this.store.markTaskResult(task.id, "done");
|
|
400
|
+
if (res.text)
|
|
401
|
+
this.store.setTaskResult(task.id, res.text.slice(0, 10000));
|
|
378
402
|
const maxLen = this.config.chunk_size || 4000;
|
|
379
403
|
const chunks = chunkText(res.text || "(no output)", maxLen);
|
|
380
404
|
await this.reply(chatId, t(this.locale, "auto_done", { id: task.id, cost: (res.cost || 0).toFixed(4) }));
|
|
381
405
|
for (const c of chunks)
|
|
382
406
|
await this.reply(chatId, c);
|
|
407
|
+
// Chain progress reporting
|
|
408
|
+
if (task.parent_id) {
|
|
409
|
+
const progress = this.store.getChainProgress(task.parent_id);
|
|
410
|
+
const costSuffix = res.cost ? ` | Cost: $${res.cost.toFixed(4)}` : "";
|
|
411
|
+
await this.reply(chatId, t(this.locale, "chain_progress", { id: task.parent_id, done: progress.done, total: progress.total, cost: costSuffix }));
|
|
412
|
+
}
|
|
383
413
|
}
|
|
384
414
|
catch (err) {
|
|
385
415
|
this.store.markTaskResult(task.id, "failed");
|
|
386
416
|
await this.reply(chatId, t(this.locale, "auto_failed", { id: task.id, err: err.message || "unknown" }));
|
|
387
417
|
}
|
|
388
|
-
|
|
389
|
-
|
|
418
|
+
}
|
|
419
|
+
async checkApprovals() {
|
|
420
|
+
try {
|
|
421
|
+
const pending = this.store.getPendingApprovals("telegram");
|
|
422
|
+
for (const task of pending) {
|
|
423
|
+
const chatId = Number(task.chat_id);
|
|
424
|
+
const keyboard = {
|
|
425
|
+
inline_keyboard: [[
|
|
426
|
+
{ text: "Approve", callback_data: `approve:${task.id}` },
|
|
427
|
+
{ text: "Reject", callback_data: `reject:${task.id}` },
|
|
428
|
+
]],
|
|
429
|
+
};
|
|
430
|
+
await this.call("sendMessage", {
|
|
431
|
+
chat_id: chatId,
|
|
432
|
+
text: t(this.locale, "approval_request", { id: task.id, desc: task.description }),
|
|
433
|
+
reply_markup: keyboard,
|
|
434
|
+
});
|
|
435
|
+
this.store.markReminderSent(task.id); // reuse reminder_sent to avoid re-sending
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
catch (e) {
|
|
439
|
+
console.error("[telegram] approval check error:", e);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
async handleApprovalCallback(cb) {
|
|
443
|
+
const data = cb.data || "";
|
|
444
|
+
const cbId = cb.id;
|
|
445
|
+
const answer = (text) => this.call("answerCallbackQuery", { callback_query_id: cbId, text, show_alert: true });
|
|
446
|
+
const [action, idStr] = data.split(":");
|
|
447
|
+
const taskId = parseInt(idStr);
|
|
448
|
+
if (isNaN(taskId)) {
|
|
449
|
+
await answer("Invalid task ID");
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
if (action === "approve") {
|
|
453
|
+
const ok = this.store.approveTask(taskId);
|
|
454
|
+
if (ok) {
|
|
455
|
+
await answer(t(this.locale, "approval_approved", { id: taskId }));
|
|
456
|
+
// Edit the original message to show approved
|
|
457
|
+
if (cb.message) {
|
|
458
|
+
try {
|
|
459
|
+
await this.call("editMessageText", {
|
|
460
|
+
chat_id: cb.message.chat.id,
|
|
461
|
+
message_id: cb.message.message_id,
|
|
462
|
+
text: t(this.locale, "approval_approved", { id: taskId }),
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
catch { }
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
else {
|
|
469
|
+
await answer(t(this.locale, "approval_decided", { id: taskId }));
|
|
470
|
+
}
|
|
390
471
|
}
|
|
472
|
+
else if (action === "reject") {
|
|
473
|
+
const ok = this.store.rejectTask(taskId);
|
|
474
|
+
if (ok) {
|
|
475
|
+
await answer(t(this.locale, "approval_rejected", { id: taskId }));
|
|
476
|
+
if (cb.message) {
|
|
477
|
+
try {
|
|
478
|
+
await this.call("editMessageText", {
|
|
479
|
+
chat_id: cb.message.chat.id,
|
|
480
|
+
message_id: cb.message.message_id,
|
|
481
|
+
text: t(this.locale, "approval_rejected", { id: taskId }),
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
catch { }
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
else {
|
|
488
|
+
await answer(t(this.locale, "approval_decided", { id: taskId }));
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
async handleStatusCommand(chatId, userId) {
|
|
493
|
+
const recent = this.store.getRecentAutoTasks("telegram", 10);
|
|
494
|
+
if (!recent.length) {
|
|
495
|
+
await this.reply(chatId, t(this.locale, "no_auto_tasks"));
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
const statusEmoji = {
|
|
499
|
+
auto: "[queue]", running: "[run]", done: "[done]", failed: "[fail]",
|
|
500
|
+
approval_pending: "[pending]", cancelled: "[cancel]",
|
|
501
|
+
};
|
|
502
|
+
const lines = recent.map(task => {
|
|
503
|
+
const chain = task.parent_id ? ` (chain #${task.parent_id})` : "";
|
|
504
|
+
return `${statusEmoji[task.status] || "[?]"} #${task.id} [${task.status}] ${task.description.slice(0, 60)}${chain}`;
|
|
505
|
+
});
|
|
506
|
+
const stats = this.store.getAutoTaskStats();
|
|
507
|
+
const summary = stats.map(s => `${s.status}: ${s.count}`).join(" | ");
|
|
508
|
+
const report = `${t(this.locale, "status_report")}\n${lines.join("\n")}\n\nSummary: ${summary}`;
|
|
509
|
+
await this.reply(chatId, report);
|
|
391
510
|
}
|
|
392
511
|
}
|
package/dist/core/agent.d.ts
CHANGED
|
@@ -22,10 +22,13 @@ export declare class AgentEngine {
|
|
|
22
22
|
}[];
|
|
23
23
|
getRotator(): EndpointRotator;
|
|
24
24
|
getEndpointCount(): number;
|
|
25
|
+
getMaxParallel(): number;
|
|
25
26
|
getWorkDir(userId: string): string;
|
|
26
27
|
isLocked(userId: string): boolean;
|
|
27
28
|
runStream(userId: string, prompt: string, platform: string, chatId: string, onChunk?: StreamCallback): Promise<AgentResponse>;
|
|
29
|
+
runParallel(userId: string, prompt: string, platform: string, chatId: string, onChunk?: StreamCallback): Promise<AgentResponse>;
|
|
28
30
|
private _executeWithRetry;
|
|
29
31
|
private _execute;
|
|
32
|
+
private _executeNoSession;
|
|
30
33
|
private _autoSummarize;
|
|
31
34
|
}
|
package/dist/core/agent.js
CHANGED
|
@@ -32,6 +32,9 @@ export class AgentEngine {
|
|
|
32
32
|
getEndpointCount() {
|
|
33
33
|
return this.rotator.count;
|
|
34
34
|
}
|
|
35
|
+
getMaxParallel() {
|
|
36
|
+
return this.config.agent.max_parallel || 1;
|
|
37
|
+
}
|
|
35
38
|
getWorkDir(userId) {
|
|
36
39
|
if (!this.config.workspace.isolation) {
|
|
37
40
|
return this.config.agent.cwd || process.cwd();
|
|
@@ -60,6 +63,35 @@ export class AgentEngine {
|
|
|
60
63
|
release();
|
|
61
64
|
}
|
|
62
65
|
}
|
|
66
|
+
async runParallel(userId, prompt, platform, chatId, onChunk) {
|
|
67
|
+
// No per-user lock — parallel tasks are independent
|
|
68
|
+
// No session resume — fresh session to prevent conflicts
|
|
69
|
+
const memories = this.config.agent.memory?.enabled ? this.store.getMemories(userId) : [];
|
|
70
|
+
const memoryPrompt = memories.length ? memories.map(m => `- ${m.content}`).join("\n") : "";
|
|
71
|
+
const maxRetries = Math.max(Math.min(this.rotator.count, 3), 1);
|
|
72
|
+
let lastErr;
|
|
73
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
74
|
+
const ep = this.rotator.count
|
|
75
|
+
? this.rotator.next()
|
|
76
|
+
: { name: "cli-default", api_key: "", base_url: "", model: "" };
|
|
77
|
+
try {
|
|
78
|
+
const res = await this._executeNoSession(userId, prompt, platform, chatId, ep, onChunk, memoryPrompt);
|
|
79
|
+
this.store.recordUsage(userId, platform, res.cost || 0);
|
|
80
|
+
return res;
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
lastErr = err;
|
|
84
|
+
const msg = String(err?.message || "");
|
|
85
|
+
if (msg.includes("429") || msg.includes("401") || msg.includes("529")) {
|
|
86
|
+
console.warn(`[agent] endpoint ${ep.name} failed (parallel), rotating`);
|
|
87
|
+
this.rotator.markFailed(ep);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
throw err;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
throw lastErr;
|
|
94
|
+
}
|
|
63
95
|
async _executeWithRetry(userId, prompt, platform, chatId, onChunk, memoryPrompt) {
|
|
64
96
|
const maxRetries = Math.max(Math.min(this.rotator.count, 3), 1);
|
|
65
97
|
let lastErr;
|
|
@@ -192,6 +224,93 @@ export class AgentEngine {
|
|
|
192
224
|
child.on("error", reject);
|
|
193
225
|
});
|
|
194
226
|
}
|
|
227
|
+
_executeNoSession(userId, prompt, platform, chatId, ep, onChunk, memoryPrompt) {
|
|
228
|
+
return new Promise((resolve, reject) => {
|
|
229
|
+
const cwd = this.getWorkDir(userId);
|
|
230
|
+
const args = ["-p", prompt, "--verbose", "--output-format", "stream-json", "--permission-mode", this.config.agent.permission_mode || "acceptEdits"];
|
|
231
|
+
if (ep.model)
|
|
232
|
+
args.push("--model", ep.model);
|
|
233
|
+
// No session resume — fresh session for parallel execution
|
|
234
|
+
if (this.config.agent.system_prompt)
|
|
235
|
+
args.push("--system-prompt", this.config.agent.system_prompt);
|
|
236
|
+
let appendPrompt = "";
|
|
237
|
+
if (memoryPrompt)
|
|
238
|
+
appendPrompt += `User memories:\n${memoryPrompt}\n\n`;
|
|
239
|
+
if (this.config.agent.skill?.enabled !== false) {
|
|
240
|
+
appendPrompt += generateSkillDoc({ userId, chatId, platform, locale: this.config.locale || "en" });
|
|
241
|
+
}
|
|
242
|
+
if (appendPrompt)
|
|
243
|
+
args.push("--append-system-prompt", appendPrompt.trim());
|
|
244
|
+
if (this.config.agent.allowed_tools?.length)
|
|
245
|
+
args.push("--allowed-tools", this.config.agent.allowed_tools.join(","));
|
|
246
|
+
if (this.config.agent.max_turns)
|
|
247
|
+
args.push("--max-turns", String(this.config.agent.max_turns));
|
|
248
|
+
if (this.config.agent.max_budget_usd)
|
|
249
|
+
args.push("--max-budget-usd", String(this.config.agent.max_budget_usd));
|
|
250
|
+
const env = { ...process.env };
|
|
251
|
+
if (ep.api_key)
|
|
252
|
+
env.ANTHROPIC_API_KEY = ep.api_key;
|
|
253
|
+
if (ep.base_url)
|
|
254
|
+
env.ANTHROPIC_BASE_URL = ep.base_url;
|
|
255
|
+
env.CLAUDEBRIDGE_DB = pathResolve("./data/claudebridge.db");
|
|
256
|
+
const child = spawn("claude", args, { cwd, env, stdio: ["pipe", "pipe", "pipe"] });
|
|
257
|
+
child.stdin.end();
|
|
258
|
+
console.log(`[agent] spawned claude (parallel) pid=${child.pid} cwd=${cwd}`);
|
|
259
|
+
const timeoutMs = (this.config.agent.timeout_seconds || 300) * 1000;
|
|
260
|
+
const timer = setTimeout(() => { try {
|
|
261
|
+
child.kill("SIGTERM");
|
|
262
|
+
}
|
|
263
|
+
catch { } }, timeoutMs);
|
|
264
|
+
let fullText = "";
|
|
265
|
+
let sessionId = "";
|
|
266
|
+
let cost = 0;
|
|
267
|
+
let buffer = "";
|
|
268
|
+
child.stdout.on("data", (data) => {
|
|
269
|
+
buffer += data.toString();
|
|
270
|
+
const lines = buffer.split("\n");
|
|
271
|
+
buffer = lines.pop() || "";
|
|
272
|
+
for (const line of lines) {
|
|
273
|
+
if (!line.trim())
|
|
274
|
+
continue;
|
|
275
|
+
try {
|
|
276
|
+
const msg = JSON.parse(line);
|
|
277
|
+
if (msg.type === "system" && msg.subtype === "init" && msg.session_id) {
|
|
278
|
+
sessionId = msg.session_id;
|
|
279
|
+
}
|
|
280
|
+
if (msg.type === "assistant" && msg.message?.content) {
|
|
281
|
+
for (const block of msg.message.content) {
|
|
282
|
+
if (block.type === "text" && block.text) {
|
|
283
|
+
fullText += block.text + "\n";
|
|
284
|
+
if (onChunk)
|
|
285
|
+
onChunk(block.text, fullText);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
if (msg.type === "result") {
|
|
290
|
+
if (msg.result)
|
|
291
|
+
fullText = msg.result;
|
|
292
|
+
if (msg.total_cost_usd)
|
|
293
|
+
cost = msg.total_cost_usd;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
catch { }
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
let stderr = "";
|
|
300
|
+
child.stderr.on("data", (data) => { stderr += data.toString(); });
|
|
301
|
+
child.on("close", (code, signal) => {
|
|
302
|
+
clearTimeout(timer);
|
|
303
|
+
console.log(`[agent] claude (parallel) exited code=${code} signal=${signal} text=${fullText.length}chars`);
|
|
304
|
+
if (code === 0 || fullText.trim() || signal === "SIGTERM") {
|
|
305
|
+
resolve({ text: fullText.trim() || (signal === "SIGTERM" ? "(timed out)" : "(no response)"), sessionId, cost });
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
reject(new Error(`claude exited ${code}: ${stderr.slice(0, 500)}`));
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
child.on("error", reject);
|
|
312
|
+
});
|
|
313
|
+
}
|
|
195
314
|
_autoSummarize(userId, prompt, response) {
|
|
196
315
|
const ep = this.rotator.count
|
|
197
316
|
? this.rotator.next()
|
package/dist/core/config.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ export interface AgentConfig {
|
|
|
16
16
|
system_prompt: string;
|
|
17
17
|
cwd: string;
|
|
18
18
|
timeout_seconds: number;
|
|
19
|
+
max_parallel: number;
|
|
19
20
|
memory: MemoryConfig;
|
|
20
21
|
skill: SkillConfig;
|
|
21
22
|
}
|
|
@@ -41,6 +42,19 @@ export interface RedisConfig {
|
|
|
41
42
|
enabled: boolean;
|
|
42
43
|
url: string;
|
|
43
44
|
}
|
|
45
|
+
export interface WebhookConfig {
|
|
46
|
+
enabled: boolean;
|
|
47
|
+
port: number;
|
|
48
|
+
token: string;
|
|
49
|
+
github_secret: string;
|
|
50
|
+
}
|
|
51
|
+
export interface CronEntry {
|
|
52
|
+
schedule_minutes: number;
|
|
53
|
+
user_id: string;
|
|
54
|
+
platform: string;
|
|
55
|
+
chat_id: string;
|
|
56
|
+
description: string;
|
|
57
|
+
}
|
|
44
58
|
export interface Config {
|
|
45
59
|
endpoints: Endpoint[];
|
|
46
60
|
agent: AgentConfig;
|
|
@@ -52,6 +66,8 @@ export interface Config {
|
|
|
52
66
|
telegram: TelegramConfig;
|
|
53
67
|
discord: DiscordConfig;
|
|
54
68
|
};
|
|
69
|
+
webhook: WebhookConfig;
|
|
70
|
+
cron: CronEntry[];
|
|
55
71
|
}
|
|
56
72
|
export declare function loadConfig(path?: string): Config;
|
|
57
73
|
export declare function reloadConfig(): Config;
|
package/dist/core/config.js
CHANGED
|
@@ -11,6 +11,7 @@ export function loadConfig(path) {
|
|
|
11
11
|
agent: {
|
|
12
12
|
...raw.agent,
|
|
13
13
|
timeout_seconds: raw.agent?.timeout_seconds ?? 300,
|
|
14
|
+
max_parallel: raw.agent?.max_parallel ?? 1,
|
|
14
15
|
memory: { enabled: true, auto_summary: true, max_memories: 50, ...raw.agent?.memory },
|
|
15
16
|
skill: { enabled: true, ...raw.agent?.skill },
|
|
16
17
|
},
|
|
@@ -19,6 +20,8 @@ export function loadConfig(path) {
|
|
|
19
20
|
redis: raw.redis || { enabled: false, url: "" },
|
|
20
21
|
locale: raw.locale || "en",
|
|
21
22
|
platforms: raw.platforms,
|
|
23
|
+
webhook: { enabled: false, port: 3100, token: "", github_secret: "", ...raw.webhook },
|
|
24
|
+
cron: raw.cron || [],
|
|
22
25
|
};
|
|
23
26
|
// defaults for each endpoint
|
|
24
27
|
for (const ep of c.endpoints) {
|