@emqo/claudebridge 0.6.2 → 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/adapters/discord.js +19 -2
- package/dist/adapters/telegram.js +22 -2
- package/dist/core/agent.d.ts +2 -2
- package/dist/core/agent.js +18 -16
- package/dist/core/i18n.js +2 -0
- package/package.json +1 -1
package/dist/adapters/discord.js
CHANGED
|
@@ -240,13 +240,21 @@ export class DiscordAdapter {
|
|
|
240
240
|
await channel.send(t(this.locale, "auto_starting", { id: task.id, desc: task.description }));
|
|
241
241
|
console.log(`[discord] auto-task #${task.id} for ${task.user_id}`);
|
|
242
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);
|
|
243
|
+
? await this.engine.runParallel(task.user_id, task.description, "discord", task.chat_id, undefined, 0)
|
|
244
|
+
: await this.engine.runStream(task.user_id, task.description, "discord", task.chat_id, undefined, 0);
|
|
245
245
|
if (res.timedOut) {
|
|
246
246
|
this.store.markTaskResult(task.id, "failed");
|
|
247
247
|
if (res.text)
|
|
248
248
|
this.store.setTaskResult(task.id, res.text.slice(0, 10000));
|
|
249
249
|
await channel.send(t(this.locale, "auto_failed", { id: task.id, err: "timed out" }));
|
|
250
|
+
const retryMatch = task.description.match(/\[retry (\d+)\/3\]/);
|
|
251
|
+
const retryCount = retryMatch ? parseInt(retryMatch[1]) : 0;
|
|
252
|
+
if (retryCount < 3) {
|
|
253
|
+
const retryDesc = retryCount === 0
|
|
254
|
+
? `[retry 1/3] Previous attempt of task #${task.id} timed out. Continue from where it left off: ${task.description}`
|
|
255
|
+
: task.description.replace(`[retry ${retryCount}/3]`, `[retry ${retryCount + 1}/3]`);
|
|
256
|
+
this.store.addTask(task.user_id, "discord", task.chat_id, retryDesc, undefined, true, task.parent_id || task.id, Date.now() + 120000);
|
|
257
|
+
}
|
|
250
258
|
return;
|
|
251
259
|
}
|
|
252
260
|
this.store.markTaskResult(task.id, "done");
|
|
@@ -267,6 +275,15 @@ export class DiscordAdapter {
|
|
|
267
275
|
catch (err) {
|
|
268
276
|
this.store.markTaskResult(task.id, "failed");
|
|
269
277
|
console.error(`[discord] auto-task #${task.id} failed:`, err);
|
|
278
|
+
// Self-healing: auto-retry failed tasks (max 3 retries)
|
|
279
|
+
const retryMatch = task.description.match(/\[retry (\d+)\/3\]/);
|
|
280
|
+
const retryCount = retryMatch ? parseInt(retryMatch[1]) : 0;
|
|
281
|
+
if (retryCount < 3) {
|
|
282
|
+
const retryDesc = retryCount === 0
|
|
283
|
+
? `[retry 1/3] Previous attempt of task #${task.id} failed (${(err.message || "unknown").slice(0, 100)}). Analyze the failure, fix the issue, then: ${task.description}`
|
|
284
|
+
: task.description.replace(`[retry ${retryCount}/3]`, `[retry ${retryCount + 1}/3]`);
|
|
285
|
+
this.store.addTask(task.user_id, "discord", task.chat_id, retryDesc, undefined, true, task.parent_id || task.id, Date.now() + 120000);
|
|
286
|
+
}
|
|
270
287
|
try {
|
|
271
288
|
const ch = await this.client.channels.fetch(task.chat_id);
|
|
272
289
|
if (ch?.isTextBased() && "send" in ch) {
|
|
@@ -394,13 +394,23 @@ export class TelegramAdapter {
|
|
|
394
394
|
try {
|
|
395
395
|
console.log(`[telegram] auto-task #${task.id} for ${task.user_id}`);
|
|
396
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);
|
|
397
|
+
? await this.engine.runParallel(task.user_id, task.description, "telegram", task.chat_id, undefined, 0)
|
|
398
|
+
: await this.engine.runStream(task.user_id, task.description, "telegram", task.chat_id, undefined, 0);
|
|
399
399
|
if (res.timedOut) {
|
|
400
400
|
this.store.markTaskResult(task.id, "failed");
|
|
401
401
|
if (res.text)
|
|
402
402
|
this.store.setTaskResult(task.id, res.text.slice(0, 10000));
|
|
403
403
|
await this.reply(chatId, t(this.locale, "auto_failed", { id: task.id, err: "timed out" }));
|
|
404
|
+
// Self-healing: auto-retry timed out tasks
|
|
405
|
+
const retryMatch = task.description.match(/\[retry (\d+)\/3\]/);
|
|
406
|
+
const retryCount = retryMatch ? parseInt(retryMatch[1]) : 0;
|
|
407
|
+
if (retryCount < 3) {
|
|
408
|
+
const retryDesc = retryCount === 0
|
|
409
|
+
? `[retry 1/3] Previous attempt of task #${task.id} timed out. Continue from where it left off: ${task.description}`
|
|
410
|
+
: task.description.replace(`[retry ${retryCount}/3]`, `[retry ${retryCount + 1}/3]`);
|
|
411
|
+
const retryId = this.store.addTask(task.user_id, "telegram", task.chat_id, retryDesc, undefined, true, task.parent_id || task.id, Date.now() + 120000);
|
|
412
|
+
await this.reply(chatId, t(this.locale, "auto_retry", { id: retryId, attempt: retryCount + 1, parent: task.id }));
|
|
413
|
+
}
|
|
404
414
|
return;
|
|
405
415
|
}
|
|
406
416
|
this.store.markTaskResult(task.id, "done");
|
|
@@ -428,6 +438,16 @@ export class TelegramAdapter {
|
|
|
428
438
|
catch (err) {
|
|
429
439
|
this.store.markTaskResult(task.id, "failed");
|
|
430
440
|
await this.reply(chatId, t(this.locale, "auto_failed", { id: task.id, err: err.message || "unknown" }));
|
|
441
|
+
// Self-healing: auto-retry failed tasks (max 3 retries)
|
|
442
|
+
const retryMatch = task.description.match(/\[retry (\d+)\/3\]/);
|
|
443
|
+
const retryCount = retryMatch ? parseInt(retryMatch[1]) : 0;
|
|
444
|
+
if (retryCount < 3) {
|
|
445
|
+
const retryDesc = retryCount === 0
|
|
446
|
+
? `[retry 1/3] Previous attempt of task #${task.id} failed (${(err.message || "unknown").slice(0, 100)}). Analyze the failure, fix the issue, then: ${task.description}`
|
|
447
|
+
: task.description.replace(`[retry ${retryCount}/3]`, `[retry ${retryCount + 1}/3]`);
|
|
448
|
+
const retryId = this.store.addTask(task.user_id, "telegram", task.chat_id, retryDesc, undefined, true, task.parent_id || task.id, Date.now() + 120000);
|
|
449
|
+
await this.reply(chatId, t(this.locale, "auto_retry", { id: retryId, attempt: retryCount + 1, parent: task.id }));
|
|
450
|
+
}
|
|
431
451
|
}
|
|
432
452
|
}
|
|
433
453
|
async checkApprovals() {
|
package/dist/core/agent.d.ts
CHANGED
|
@@ -26,8 +26,8 @@ export declare class AgentEngine {
|
|
|
26
26
|
getMaxParallel(): number;
|
|
27
27
|
getWorkDir(userId: string): string;
|
|
28
28
|
isLocked(userId: string): boolean;
|
|
29
|
-
runStream(userId: string, prompt: string, platform: string, chatId: string, onChunk?: StreamCallback): Promise<AgentResponse>;
|
|
30
|
-
runParallel(userId: string, prompt: string, platform: string, chatId: string, onChunk?: StreamCallback): Promise<AgentResponse>;
|
|
29
|
+
runStream(userId: string, prompt: string, platform: string, chatId: string, onChunk?: StreamCallback, overrideTimeoutMs?: number): Promise<AgentResponse>;
|
|
30
|
+
runParallel(userId: string, prompt: string, platform: string, chatId: string, onChunk?: StreamCallback, overrideTimeoutMs?: number): Promise<AgentResponse>;
|
|
31
31
|
private _executeWithRetry;
|
|
32
32
|
private _execute;
|
|
33
33
|
private _executeNoSession;
|
package/dist/core/agent.js
CHANGED
|
@@ -46,13 +46,13 @@ export class AgentEngine {
|
|
|
46
46
|
isLocked(userId) {
|
|
47
47
|
return this.lock.isLocked(userId);
|
|
48
48
|
}
|
|
49
|
-
async runStream(userId, prompt, platform, chatId, onChunk) {
|
|
49
|
+
async runStream(userId, prompt, platform, chatId, onChunk, overrideTimeoutMs) {
|
|
50
50
|
const release = await this.lock.acquire(userId);
|
|
51
51
|
try {
|
|
52
52
|
this.store.addHistory(userId, platform, "user", prompt);
|
|
53
53
|
const memories = this.config.agent.memory?.enabled ? this.store.getMemories(userId) : [];
|
|
54
54
|
const memoryPrompt = memories.length ? memories.map(m => `- ${m.content}`).join("\n") : "";
|
|
55
|
-
const res = await this._executeWithRetry(userId, prompt, platform, chatId, onChunk, memoryPrompt);
|
|
55
|
+
const res = await this._executeWithRetry(userId, prompt, platform, chatId, onChunk, memoryPrompt, overrideTimeoutMs);
|
|
56
56
|
this.store.addHistory(userId, platform, "assistant", res.text);
|
|
57
57
|
this.store.recordUsage(userId, platform, res.cost || 0);
|
|
58
58
|
if (this.config.agent.memory?.auto_summary)
|
|
@@ -63,7 +63,7 @@ export class AgentEngine {
|
|
|
63
63
|
release();
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
|
-
async runParallel(userId, prompt, platform, chatId, onChunk) {
|
|
66
|
+
async runParallel(userId, prompt, platform, chatId, onChunk, overrideTimeoutMs) {
|
|
67
67
|
// No per-user lock — parallel tasks are independent
|
|
68
68
|
// No session resume — fresh session to prevent conflicts
|
|
69
69
|
const memories = this.config.agent.memory?.enabled ? this.store.getMemories(userId) : [];
|
|
@@ -75,7 +75,7 @@ export class AgentEngine {
|
|
|
75
75
|
? this.rotator.next()
|
|
76
76
|
: { name: "cli-default", api_key: "", base_url: "", model: "" };
|
|
77
77
|
try {
|
|
78
|
-
const res = await this._executeNoSession(userId, prompt, platform, chatId, ep, onChunk, memoryPrompt);
|
|
78
|
+
const res = await this._executeNoSession(userId, prompt, platform, chatId, ep, onChunk, memoryPrompt, overrideTimeoutMs);
|
|
79
79
|
this.store.recordUsage(userId, platform, res.cost || 0);
|
|
80
80
|
return res;
|
|
81
81
|
}
|
|
@@ -92,7 +92,7 @@ export class AgentEngine {
|
|
|
92
92
|
}
|
|
93
93
|
throw lastErr;
|
|
94
94
|
}
|
|
95
|
-
async _executeWithRetry(userId, prompt, platform, chatId, onChunk, memoryPrompt) {
|
|
95
|
+
async _executeWithRetry(userId, prompt, platform, chatId, onChunk, memoryPrompt, overrideTimeoutMs) {
|
|
96
96
|
const maxRetries = Math.max(Math.min(this.rotator.count, 3), 1);
|
|
97
97
|
let lastErr;
|
|
98
98
|
for (let i = 0; i < maxRetries; i++) {
|
|
@@ -100,7 +100,7 @@ export class AgentEngine {
|
|
|
100
100
|
? this.rotator.next()
|
|
101
101
|
: { name: "cli-default", api_key: "", base_url: "", model: "" };
|
|
102
102
|
try {
|
|
103
|
-
return await this._execute(userId, prompt, platform, chatId, ep, onChunk, memoryPrompt);
|
|
103
|
+
return await this._execute(userId, prompt, platform, chatId, ep, onChunk, memoryPrompt, overrideTimeoutMs);
|
|
104
104
|
}
|
|
105
105
|
catch (err) {
|
|
106
106
|
lastErr = err;
|
|
@@ -115,7 +115,7 @@ export class AgentEngine {
|
|
|
115
115
|
}
|
|
116
116
|
throw lastErr;
|
|
117
117
|
}
|
|
118
|
-
_execute(userId, prompt, platform, chatId, ep, onChunk, memoryPrompt) {
|
|
118
|
+
_execute(userId, prompt, platform, chatId, ep, onChunk, memoryPrompt, overrideTimeoutMs) {
|
|
119
119
|
return new Promise((resolve, reject) => {
|
|
120
120
|
const sessionId = this.store.getSession(userId) || "";
|
|
121
121
|
const cwd = this.getWorkDir(userId);
|
|
@@ -150,11 +150,11 @@ export class AgentEngine {
|
|
|
150
150
|
const child = spawn("claude", args, { cwd, env, stdio: ["pipe", "pipe", "pipe"] });
|
|
151
151
|
child.stdin.end();
|
|
152
152
|
console.log(`[agent] spawned claude pid=${child.pid} cwd=${cwd} args=${args.join(" ")}`);
|
|
153
|
-
const timeoutMs = (this.config.agent.timeout_seconds || 600) * 1000;
|
|
154
|
-
const timer = setTimeout(() => { try {
|
|
153
|
+
const timeoutMs = overrideTimeoutMs !== undefined ? overrideTimeoutMs : (this.config.agent.timeout_seconds || 600) * 1000;
|
|
154
|
+
const timer = timeoutMs > 0 ? setTimeout(() => { try {
|
|
155
155
|
child.kill("SIGTERM");
|
|
156
156
|
}
|
|
157
|
-
catch { } }, timeoutMs);
|
|
157
|
+
catch { } }, timeoutMs) : null;
|
|
158
158
|
let fullText = "";
|
|
159
159
|
let newSessionId = sessionId;
|
|
160
160
|
let cost = 0;
|
|
@@ -202,7 +202,8 @@ export class AgentEngine {
|
|
|
202
202
|
console.log(`[agent] stderr: ${s.slice(0, 200)}`);
|
|
203
203
|
});
|
|
204
204
|
child.on("close", (code, signal) => {
|
|
205
|
-
|
|
205
|
+
if (timer)
|
|
206
|
+
clearTimeout(timer);
|
|
206
207
|
console.log(`[agent] claude exited code=${code} signal=${signal} fullText=${fullText.length}chars stderr=${stderr.slice(0, 200)}`);
|
|
207
208
|
if (signal === "SIGTERM") {
|
|
208
209
|
console.warn(`[agent] claude timed out after ${timeoutMs / 1000}s`);
|
|
@@ -225,7 +226,7 @@ export class AgentEngine {
|
|
|
225
226
|
child.on("error", reject);
|
|
226
227
|
});
|
|
227
228
|
}
|
|
228
|
-
_executeNoSession(userId, prompt, platform, chatId, ep, onChunk, memoryPrompt) {
|
|
229
|
+
_executeNoSession(userId, prompt, platform, chatId, ep, onChunk, memoryPrompt, overrideTimeoutMs) {
|
|
229
230
|
return new Promise((resolve, reject) => {
|
|
230
231
|
const cwd = this.getWorkDir(userId);
|
|
231
232
|
const args = ["-p", prompt, "--verbose", "--output-format", "stream-json", "--permission-mode", this.config.agent.permission_mode || "acceptEdits"];
|
|
@@ -257,11 +258,11 @@ export class AgentEngine {
|
|
|
257
258
|
const child = spawn("claude", args, { cwd, env, stdio: ["pipe", "pipe", "pipe"] });
|
|
258
259
|
child.stdin.end();
|
|
259
260
|
console.log(`[agent] spawned claude (parallel) pid=${child.pid} cwd=${cwd}`);
|
|
260
|
-
const timeoutMs = (this.config.agent.timeout_seconds || 600) * 1000;
|
|
261
|
-
const timer = setTimeout(() => { try {
|
|
261
|
+
const timeoutMs = overrideTimeoutMs !== undefined ? overrideTimeoutMs : (this.config.agent.timeout_seconds || 600) * 1000;
|
|
262
|
+
const timer = timeoutMs > 0 ? setTimeout(() => { try {
|
|
262
263
|
child.kill("SIGTERM");
|
|
263
264
|
}
|
|
264
|
-
catch { } }, timeoutMs);
|
|
265
|
+
catch { } }, timeoutMs) : null;
|
|
265
266
|
let fullText = "";
|
|
266
267
|
let sessionId = "";
|
|
267
268
|
let cost = 0;
|
|
@@ -300,7 +301,8 @@ export class AgentEngine {
|
|
|
300
301
|
let stderr = "";
|
|
301
302
|
child.stderr.on("data", (data) => { stderr += data.toString(); });
|
|
302
303
|
child.on("close", (code, signal) => {
|
|
303
|
-
|
|
304
|
+
if (timer)
|
|
305
|
+
clearTimeout(timer);
|
|
304
306
|
console.log(`[agent] claude (parallel) exited code=${code} signal=${signal} text=${fullText.length}chars`);
|
|
305
307
|
if (signal === "SIGTERM") {
|
|
306
308
|
console.warn(`[agent] claude (parallel) timed out after ${timeoutMs / 1000}s`);
|
package/dist/core/i18n.js
CHANGED
|
@@ -14,6 +14,7 @@ const messages = {
|
|
|
14
14
|
auto_scheduled: "Auto task #{id} scheduled (executes in {minutes} min):\n{desc}",
|
|
15
15
|
auto_done: "Auto task #{id} done (cost: ${cost}):",
|
|
16
16
|
auto_failed: "Auto task #{id} failed: {err}",
|
|
17
|
+
auto_retry: "Self-healing: auto task #{parent} failed, retry #{attempt}/3 queued as task #{id} (in 2min)",
|
|
17
18
|
page_expired: "Page expired. Please resend your question.",
|
|
18
19
|
approval_request: "Approval needed for auto task #{id}:\n{desc}",
|
|
19
20
|
approval_approved: "Auto task #{id} approved -- queued for execution.",
|
|
@@ -38,6 +39,7 @@ const messages = {
|
|
|
38
39
|
auto_scheduled: "自动任务 #{id} 已排程({minutes} 分钟后执行):\n{desc}",
|
|
39
40
|
auto_done: "自动任务 #{id} 完成(花费:${cost}):",
|
|
40
41
|
auto_failed: "自动任务 #{id} 失败:{err}",
|
|
42
|
+
auto_retry: "自愈机制:自动任务 #{parent} 失败,重试 #{attempt}/3 已排队为任务 #{id}(2分钟后执行)",
|
|
41
43
|
page_expired: "页面已过期,请重新发送问题。",
|
|
42
44
|
approval_request: "自动任务 #{id} 需要审批:\n{desc}",
|
|
43
45
|
approval_approved: "自动任务 #{id} 已批准 -- 已加入执行队列。",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@emqo/claudebridge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Bridge claude CLI to chat platforms (Telegram, Discord) with scheduled auto-tasks, autonomous project management, HITL approval, conditional branching, webhook triggers, parallel execution, and observability",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|