@wu529778790/open-im 1.11.8-beta.5 → 1.11.8-beta.7

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.
@@ -23,6 +23,7 @@ export declare class CommandHandler {
23
23
  dispatch(text: string, chatId: string, userId: string, platform: Platform, handleClaudeRequest: ClaudeRequestHandler,
24
24
  /** 若提供,本条消息的斜杠命令回复走此 sender(须与 handleTextFlow 的 sendTextReply 一致,如带 msgId)。 */
25
25
  senderOverride?: MessageSender): Promise<boolean>;
26
+ private handleAutopilotStatus;
26
27
  private handleHelp;
27
28
  private handlePlugins;
28
29
  private handleSessions;
@@ -1,5 +1,6 @@
1
1
  import { resolvePlatformAiCommand } from '../config.js';
2
2
  import { escapePathForMarkdown } from '../shared/utils.js';
3
+ import { getAutopilotPendingStatus } from '../shared/ai-task.js';
3
4
  import { TERMINAL_ONLY_COMMANDS } from '../constants.js';
4
5
  import { createLogger } from '../logger.js';
5
6
  import { ClaudeSDKAdapter } from '../adapters/claude-sdk-adapter.js';
@@ -103,6 +104,8 @@ export class CommandHandler {
103
104
  return this.handlePwd(chatId, userId);
104
105
  if (t === '/status')
105
106
  return this.handleStatus(chatId, userId, platform);
107
+ if (t === '/autopilot')
108
+ return this.handleAutopilotStatus(chatId, userId);
106
109
  // 快捷命令 — 直接发送预设 prompt 给 AI
107
110
  if (t === '/git commit')
108
111
  return this.handleQuickCommand(chatId, userId, 'git commit -m "AI generated commit"', platform);
@@ -133,6 +136,36 @@ export class CommandHandler {
133
136
  }
134
137
  return runBody();
135
138
  }
139
+ async handleAutopilotStatus(chatId, userId) {
140
+ const ap = this.deps.config.autopilot;
141
+ const lines = [
142
+ '🤖 限流自动恢复 (Autopilot)',
143
+ '',
144
+ `状态: ${ap.enabled ? '✅ 已启用' : '❌ 已禁用'}`,
145
+ `最大重试: ${ap.maxRetries} 次`,
146
+ `默认等待: ${ap.defaultIntervalHours} 小时`,
147
+ `短延迟: ${ap.shortRetrySeconds} 秒`,
148
+ `恢复提示: "${ap.autoResumePrompt}"`,
149
+ ];
150
+ const pending = getAutopilotPendingStatus(userId);
151
+ if (pending) {
152
+ const remaining = Math.max(pending.retryAt.getTime() - Date.now(), 0);
153
+ const hours = Math.floor(remaining / 3600000);
154
+ const minutes = Math.floor((remaining % 3600000) / 60000);
155
+ lines.push('');
156
+ lines.push('⏳ 当前等待中:');
157
+ lines.push(` 类型: ${pending.type}`);
158
+ lines.push(` 恢复时间: ${pending.retryAt.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })}`);
159
+ lines.push(` 剩余: ${hours > 0 ? `${hours}小时` : ''}${minutes}分钟`);
160
+ lines.push(` 重试次数: ${pending.retryCount}/${ap.maxRetries}`);
161
+ }
162
+ else {
163
+ lines.push('');
164
+ lines.push('当前无等待中的限流恢复任务。');
165
+ }
166
+ await this.replySender().sendTextReply(chatId, lines.join('\n'));
167
+ return true;
168
+ }
136
169
  async handleHelp(chatId) {
137
170
  const help = [
138
171
  '📋 可用命令:',
@@ -151,6 +184,7 @@ export class CommandHandler {
151
184
  '/status - 显示状态',
152
185
  '/cd <路径> - 切换工作目录',
153
186
  '/pwd - 当前工作目录',
187
+ '/autopilot - 查看限流自动恢复状态',
154
188
  '',
155
189
  '⚡ 快捷命令:',
156
190
  '/git commit - 提交代码',
@@ -53,6 +53,17 @@ export interface Config {
53
53
  /** 可选 Bearer,与采集端一致 */
54
54
  token?: string;
55
55
  };
56
+ /**
57
+ * 限流自动恢复(Autopilot)。
58
+ * 当 AI 工具遇到限流错误时,自动等待并在恢复后重试。
59
+ */
60
+ autopilot: {
61
+ enabled: boolean;
62
+ maxRetries: number;
63
+ defaultIntervalHours: number;
64
+ shortRetrySeconds: number;
65
+ autoResumePrompt: string;
66
+ };
56
67
  platforms: {
57
68
  telegram?: {
58
69
  enabled: boolean;
@@ -176,6 +187,14 @@ export interface FileToolClaude {
176
187
  proxy?: string;
177
188
  /** Claude API 配置(优先级:环境变量 > tools.claude.env > ~/.claude/settings.json) */
178
189
  env?: Record<string, string>;
190
+ /** 限流自动恢复配置 */
191
+ autopilot?: {
192
+ enabled?: boolean;
193
+ maxRetries?: number;
194
+ defaultIntervalHours?: number;
195
+ shortRetrySeconds?: number;
196
+ autoResumePrompt?: string;
197
+ };
179
198
  }
180
199
  export interface FileToolCodex {
181
200
  cliPath?: string;
@@ -210,6 +210,10 @@ export declare const PAGE_TEXTS: {
210
210
  readonly wizardAuthApiKey: "API Key";
211
211
  readonly wizardAuthToken: "Auth Token";
212
212
  readonly wizardLoading: "Loading...";
213
+ readonly aiCommandPickerTitle: "🤖 AI Tool";
214
+ readonly aiCommandPickerHint: "Messages from this channel will be replied by the AI selected above";
215
+ readonly platformStatusOn: "Connected";
216
+ readonly platformStatusOff: "Not connected";
213
217
  };
214
218
  readonly zh: {
215
219
  readonly pageTitle: "open-im 本地控制台";
@@ -419,5 +423,9 @@ export declare const PAGE_TEXTS: {
419
423
  readonly wizardAuthApiKey: "API Key";
420
424
  readonly wizardAuthToken: "Auth Token";
421
425
  readonly wizardLoading: "加载中...";
426
+ readonly aiCommandPickerTitle: "🤖 AI 工具";
427
+ readonly aiCommandPickerHint: "这个渠道收到的消息会用上面的 AI 回复";
428
+ readonly platformStatusOn: "已连接";
429
+ readonly platformStatusOff: "未连接";
422
430
  };
423
431
  };
@@ -211,6 +211,10 @@ export const PAGE_TEXTS = {
211
211
  wizardAuthApiKey: "API Key",
212
212
  wizardAuthToken: "Auth Token",
213
213
  wizardLoading: "Loading...",
214
+ aiCommandPickerTitle: "🤖 AI Tool",
215
+ aiCommandPickerHint: "Messages from this channel will be replied by the AI selected above",
216
+ platformStatusOn: "Connected",
217
+ platformStatusOff: "Not connected",
214
218
  },
215
219
  zh: {
216
220
  pageTitle: "open-im \u672c\u5730\u63a7\u5236\u53f0",
@@ -421,5 +425,9 @@ export const PAGE_TEXTS = {
421
425
  wizardAuthApiKey: "API Key",
422
426
  wizardAuthToken: "Auth Token",
423
427
  wizardLoading: "\u52a0\u8f7d\u4e2d...",
428
+ aiCommandPickerTitle: "🤖 AI 工具",
429
+ aiCommandPickerHint: "这个渠道收到的消息会用上面的 AI 回复",
430
+ platformStatusOn: "已连接",
431
+ platformStatusOff: "未连接",
424
432
  }
425
433
  };
@@ -503,6 +503,13 @@ function createProbeConfig(values) {
503
503
  logDir: "",
504
504
  logLevel: "INFO",
505
505
  telemetry: { enabled: true },
506
+ autopilot: {
507
+ enabled: true,
508
+ maxRetries: 5,
509
+ defaultIntervalHours: 5,
510
+ shortRetrySeconds: 60,
511
+ autoResumePrompt: "继续",
512
+ },
506
513
  codebuddyCliPath: "codebuddy",
507
514
  opencodeCliPath: "opencode",
508
515
  platforms: {},
package/dist/config.js CHANGED
@@ -8,7 +8,7 @@ import { accessSync, constants } from 'node:fs';
8
8
  import { execFileSync } from 'node:child_process';
9
9
  import { join, isAbsolute } from 'node:path';
10
10
  import { createLogger } from './logger.js';
11
- import { APP_HOME, } from './constants.js';
11
+ import { APP_HOME, AUTOPILOT_ENABLED_DEFAULT, AUTOPILOT_MAX_RETRIES, AUTOPILOT_DEFAULT_INTERVAL_HOURS, AUTOPILOT_SHORT_RETRY_SECONDS, AUTOPILOT_DEFAULT_PROMPT, } from './constants.js';
12
12
  const log = createLogger('config');
13
13
  import { isAiCommand, AI_TOOL_BY_ID } from './adapters/tool-registry.js';
14
14
  /**
@@ -200,6 +200,24 @@ export function loadConfig() {
200
200
  const tc = file.tools?.claude ?? {};
201
201
  const tcod = file.tools?.codex ?? {};
202
202
  const tcb = file.tools?.codebuddy ?? {};
203
+ // Autopilot(限流自动恢复)配置
204
+ const autopilotFile = tc.autopilot ?? {};
205
+ const autopilot = {
206
+ enabled: process.env.OPEN_IM_AUTOPILOT !== 'false'
207
+ && (autopilotFile.enabled ?? AUTOPILOT_ENABLED_DEFAULT),
208
+ maxRetries: (() => {
209
+ const envVal = process.env.OPEN_IM_AUTOPILOT_MAX_RETRIES;
210
+ if (envVal !== undefined && envVal !== '') {
211
+ const n = Number.parseInt(envVal, 10);
212
+ if (Number.isFinite(n) && n >= 0)
213
+ return n;
214
+ }
215
+ return autopilotFile.maxRetries ?? AUTOPILOT_MAX_RETRIES;
216
+ })(),
217
+ defaultIntervalHours: autopilotFile.defaultIntervalHours ?? AUTOPILOT_DEFAULT_INTERVAL_HOURS,
218
+ shortRetrySeconds: autopilotFile.shortRetrySeconds ?? AUTOPILOT_SHORT_RETRY_SECONDS,
219
+ autoResumePrompt: autopilotFile.autoResumePrompt ?? AUTOPILOT_DEFAULT_PROMPT,
220
+ };
203
221
  const claudeProxy = process.env.CLAUDE_PROXY ?? tc.proxy;
204
222
  const codexProxy = process.env.CODEX_PROXY ?? tcod.proxy;
205
223
  // Windows 下若 CLI 路径仍是默认名,尝试在 npm 全局目录定位 .cmd。
@@ -532,6 +550,7 @@ export function loadConfig() {
532
550
  telemetry: {
533
551
  enabled: telemetryEnabled,
534
552
  },
553
+ autopilot,
535
554
  platforms,
536
555
  };
537
556
  }
@@ -71,5 +71,15 @@ export declare const MAX_WORKBUDDY_MESSAGE_LENGTH = 2000;
71
71
  export declare const MAX_CLAWBOT_MESSAGE_LENGTH = 2000;
72
72
  /** ClawBot 长轮询间隔 */
73
73
  export declare const CLAWBOT_POLL_INTERVAL_MS = 3000;
74
+ /** Autopilot 默认开启 */
75
+ export declare const AUTOPILOT_ENABLED_DEFAULT = true;
76
+ /** 最大连续自动重试次数 */
77
+ export declare const AUTOPILOT_MAX_RETRIES = 5;
78
+ /** 无法从错误消息提取重置时间时的默认等待小时数 */
79
+ export declare const AUTOPILOT_DEFAULT_INTERVAL_HOURS = 5;
80
+ /** 529/临时限流的短延迟秒数 */
81
+ export declare const AUTOPILOT_SHORT_RETRY_SECONDS = 60;
82
+ /** 自动恢复时发送的提示词 */
83
+ export declare const AUTOPILOT_DEFAULT_PROMPT = "\u7EE7\u7EED";
74
84
  export declare const DEFAULT_OPEN_IM_COAUTHOR_ADDR = "529778790@qq.com";
75
85
  export declare const TERMINAL_ONLY_COMMANDS: Set<string>;
package/dist/constants.js CHANGED
@@ -90,6 +90,17 @@ export const MAX_CLAWBOT_MESSAGE_LENGTH = 2000;
90
90
  // ─── 轮询间隔 ───
91
91
  /** ClawBot 长轮询间隔 */
92
92
  export const CLAWBOT_POLL_INTERVAL_MS = 3000;
93
+ // ─── Autopilot(限流自动恢复) ───
94
+ /** Autopilot 默认开启 */
95
+ export const AUTOPILOT_ENABLED_DEFAULT = true;
96
+ /** 最大连续自动重试次数 */
97
+ export const AUTOPILOT_MAX_RETRIES = 5;
98
+ /** 无法从错误消息提取重置时间时的默认等待小时数 */
99
+ export const AUTOPILOT_DEFAULT_INTERVAL_HOURS = 5;
100
+ /** 529/临时限流的短延迟秒数 */
101
+ export const AUTOPILOT_SHORT_RETRY_SECONDS = 60;
102
+ /** 自动恢复时发送的提示词 */
103
+ export const AUTOPILOT_DEFAULT_PROMPT = "继续";
93
104
  // ─── Git 共同作者 ───
94
105
  export const DEFAULT_OPEN_IM_COAUTHOR_ADDR = "529778790@qq.com";
95
106
  // ─── 终端独占命令 ───
@@ -133,7 +133,24 @@ export function createPlatformAIRequestHandler(deps) {
133
133
  };
134
134
  // 7. Run AI task
135
135
  try {
136
- await runAITask({ config, sessionManager }, {
136
+ await runAITask({
137
+ config,
138
+ sessionManager,
139
+ autopilot: {
140
+ onAutoPilotContinue: (continuePrompt) => {
141
+ log.info(`[${platform}] Autopilot: re-enqueuing "${continuePrompt}" for user ${userId}`);
142
+ handleAIRequest({
143
+ userId,
144
+ chatId,
145
+ prompt: continuePrompt,
146
+ workDir,
147
+ convId,
148
+ }).catch((err) => {
149
+ log.error(`[${platform}] Autopilot re-enqueue failed:`, err);
150
+ });
151
+ },
152
+ },
153
+ }, {
137
154
  userId,
138
155
  chatId,
139
156
  workDir,
@@ -4,9 +4,24 @@
4
4
  import type { Config } from '../config.js';
5
5
  import type { SessionManager } from '../session/session-manager.js';
6
6
  import type { ToolAdapter } from '../adapters/tool-adapter.interface.js';
7
+ /** 查询指定用户的 autopilot 等待状态(供 /autopilot 命令使用)。 */
8
+ export declare function getAutopilotPendingStatus(userId: string): {
9
+ retryAt: Date;
10
+ type: string;
11
+ retryCount: number;
12
+ } | undefined;
13
+ /** 清除指定用户的 autopilot 状态(测试用)。 */
14
+ export declare function clearAutopilotState(userId: string): void;
7
15
  export interface TaskDeps {
8
16
  config: Config;
9
17
  sessionManager: SessionManager;
18
+ /**
19
+ * Autopilot 回调:限流恢复后调用,将 autoResumePrompt 作为新消息
20
+ * 通过平台的请求队列重新入队。如果不提供,autopilot 不执行恢复动作。
21
+ */
22
+ autopilot?: {
23
+ onAutoPilotContinue: (prompt: string) => void;
24
+ };
10
25
  }
11
26
  export interface TaskContext {
12
27
  userId: string;
@@ -6,8 +6,99 @@ import { captureError } from './sentry.js';
6
6
  import { formatToolStats, formatToolCallNotification, getContextWarning, getAIToolDisplayName, toReplyPlainText, } from './utils.js';
7
7
  import { createLogger } from '../logger.js';
8
8
  const log = createLogger('AITask');
9
+ /** 每用户连续 autopilot 重试计数。成功完成或遇到非限流错误时清零。 */
10
+ const autopilotRetryCount = new Map();
11
+ /** 当前等待中的 autopilot 定时器(用于 /autopilot 状态查询)。 */
12
+ const pendingAutopilotTimers = new Map();
13
+ /** 查询指定用户的 autopilot 等待状态(供 /autopilot 命令使用)。 */
14
+ export function getAutopilotPendingStatus(userId) {
15
+ return pendingAutopilotTimers.get(userId);
16
+ }
17
+ /** 清除指定用户的 autopilot 状态(测试用)。 */
18
+ export function clearAutopilotState(userId) {
19
+ autopilotRetryCount.delete(userId);
20
+ pendingAutopilotTimers.delete(userId);
21
+ }
22
+ /**
23
+ * 判断错误是否为限流类错误(用于决定是否保留 session)。
24
+ * 覆盖所有已知的限流模式:429/529/session limit/overloaded/temporary throttle。
25
+ */
9
26
  function isUsageLimitError(error) {
10
- return /usage limit/i.test(error) || /try again at\s+\d{1,2}:\d{2}\s*(AM|PM)/i.test(error);
27
+ return /usage\s*limit/i.test(error)
28
+ || /try\s+again\s+at\s+\d{1,2}:\d{2}/i.test(error)
29
+ || /rate\s*limit/i.test(error)
30
+ || /\b429\b/.test(error)
31
+ || /\b529\b/.test(error)
32
+ || /overloaded/i.test(error)
33
+ || /temporarily\s+limiting/i.test(error)
34
+ || /session\s+limit/i.test(error)
35
+ || /you['\u2019]ve\s+hit\s+your/i.test(error);
36
+ }
37
+ /**
38
+ * 从错误消息中提取限流详情。仅在 isUsageLimitError() 返回 true 后调用。
39
+ */
40
+ function classifyRateLimit(error, config) {
41
+ const now = Date.now();
42
+ const shortMs = config.autopilot.shortRetrySeconds * 1000;
43
+ const defaultMs = config.autopilot.defaultIntervalHours * 3600 * 1000;
44
+ // Session quota / usage limit(最常见,等待时间最长)
45
+ if (/session\s*limit|usage\s*limit|opus\s*limit|you['\u2019]ve\s+hit\s+your/i.test(error)) {
46
+ const timeMatch = error.match(/try\s+again\s+at\s+(\d{1,2}):(\d{2})\s*(AM|PM)?/i);
47
+ if (timeMatch) {
48
+ let hours = parseInt(timeMatch[1], 10);
49
+ const minutes = parseInt(timeMatch[2], 10);
50
+ const ampm = timeMatch[3]?.toUpperCase();
51
+ if (ampm === 'PM' && hours < 12)
52
+ hours += 12;
53
+ if (ampm === 'AM' && hours === 12)
54
+ hours = 0;
55
+ const retryAt = new Date();
56
+ retryAt.setHours(hours, minutes, 0, 0);
57
+ if (retryAt.getTime() <= now)
58
+ retryAt.setDate(retryAt.getDate() + 1);
59
+ return { detected: true, type: 'session_limit', retryAt, isLongWait: true };
60
+ }
61
+ return { detected: true, type: 'session_limit', retryAt: new Date(now + defaultMs), isLongWait: true };
62
+ }
63
+ // 529 overloaded / temporarily limiting(短延迟)
64
+ if (/\b529\b|overloaded|temporarily\s+limiting/i.test(error)) {
65
+ return {
66
+ detected: true,
67
+ type: /overloaded/i.test(error) ? 'overloaded' : 'temporary',
68
+ retryAt: new Date(now + shortMs),
69
+ isLongWait: false,
70
+ };
71
+ }
72
+ // 429 rate limit(使用默认周期)
73
+ if (/\b429\b|rate\s*limit/i.test(error)) {
74
+ return { detected: true, type: 'api_rate_limit', retryAt: new Date(now + defaultMs), isLongWait: true };
75
+ }
76
+ return { detected: false, type: null, retryAt: null, isLongWait: false };
77
+ }
78
+ function rateLimitTypeLabel(type) {
79
+ switch (type) {
80
+ case 'session_limit': return '会话额度限制';
81
+ case 'api_rate_limit': return 'API 速率限制';
82
+ case 'overloaded': return '服务器过载';
83
+ case 'temporary': return '临时限流';
84
+ default: return '限流';
85
+ }
86
+ }
87
+ function formatTimeHHMM(date) {
88
+ return `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
89
+ }
90
+ function formatDuration(ms) {
91
+ if (ms <= 0)
92
+ return '即将';
93
+ const totalSec = Math.ceil(ms / 1000);
94
+ const hours = Math.floor(totalSec / 3600);
95
+ const minutes = Math.floor((totalSec % 3600) / 60);
96
+ const seconds = totalSec % 60;
97
+ if (hours > 0)
98
+ return `${hours}小时${minutes > 0 ? `${minutes}分钟` : ''}`;
99
+ if (minutes > 0)
100
+ return `${minutes}分钟${seconds > 0 ? `${seconds}秒` : ''}`;
101
+ return `${seconds}秒`;
11
102
  }
12
103
  /**
13
104
  * 将 AI/CLI 错误文本归类为一个稳定的 errorType 字符串,用于遥测聚合。
@@ -211,6 +302,8 @@ export function runAITask(deps, ctx, prompt, toolAdapter, platformAdapter) {
211
302
  if (settled)
212
303
  return;
213
304
  settled = true;
305
+ // 成功完成 → 清除 autopilot 重试计数
306
+ autopilotRetryCount.delete(ctx.userId);
214
307
  if (pendingUpdate) {
215
308
  clearTimeout(pendingUpdate);
216
309
  pendingUpdate = null;
@@ -263,6 +356,83 @@ export function runAITask(deps, ctx, prompt, toolAdapter, platformAdapter) {
263
356
  }
264
357
  settled = true;
265
358
  log.error(`Task error for user ${ctx.userId}: ${error}`);
359
+ // ── Autopilot 拦截 ──
360
+ const apConfig = config.autopilot;
361
+ const currentRetries = autopilotRetryCount.get(ctx.userId) ?? 0;
362
+ if (apConfig?.enabled &&
363
+ deps.autopilot &&
364
+ isUsageLimitError(error) &&
365
+ currentRetries < apConfig.maxRetries) {
366
+ const rateInfo = classifyRateLimit(error, config);
367
+ if (rateInfo.detected && rateInfo.retryAt) {
368
+ const retryCount = currentRetries + 1;
369
+ autopilotRetryCount.set(ctx.userId, retryCount);
370
+ // 记录等待状态
371
+ pendingAutopilotTimers.set(ctx.userId, {
372
+ retryAt: rateInfo.retryAt,
373
+ type: rateInfo.type,
374
+ retryCount,
375
+ });
376
+ // 通知用户
377
+ const typeLabel = rateLimitTypeLabel(rateInfo.type);
378
+ const timeStr = formatTimeHHMM(rateInfo.retryAt);
379
+ const remaining = formatDuration(rateInfo.retryAt.getTime() - Date.now());
380
+ const statusMsg = `⏳ 检测到${typeLabel},将在 ${timeStr}(${remaining}后)自动恢复 (${retryCount}/${apConfig.maxRetries})`;
381
+ log.info(`[Autopilot] ${statusMsg} for user ${ctx.userId}`);
382
+ try {
383
+ await platformAdapter.sendError(hadSessionInvalid
384
+ ? '当前 Claude 会话已失效,已自动执行 /new 重置会话,请重新发送刚才的问题。'
385
+ : `${error}\n\n${statusMsg}`);
386
+ }
387
+ catch (err) {
388
+ log.error('Failed to send autopilot status:', err);
389
+ }
390
+ cleanup();
391
+ resolve();
392
+ // 异步等待 → 恢复(不阻塞 Promise 解决)
393
+ const retryAt = rateInfo.retryAt;
394
+ const delayMs = Math.max(retryAt.getTime() - Date.now(), 0);
395
+ log.info(`[Autopilot] Scheduling retry in ${delayMs}ms for user ${ctx.userId}`);
396
+ // 分段定时器(setTimeout 最大 ~24.8 天)
397
+ const MAX_SEGMENT = 2_147_483_647; // 2^31 - 1
398
+ const scheduleRetry = (remaining) => {
399
+ if (remaining <= 0) {
400
+ // 定时器到期 → 清除状态 → 发送恢复消息
401
+ pendingAutopilotTimers.delete(ctx.userId);
402
+ log.info(`[Autopilot] Timer fired for user ${ctx.userId}, sending "${apConfig.autoResumePrompt}"`);
403
+ try {
404
+ platformAdapter.streamUpdate('', `🔄 限额已恢复,正在自动重试... (${retryCount})`);
405
+ }
406
+ catch {
407
+ /* ignore */
408
+ }
409
+ deps.autopilot.onAutoPilotContinue(apConfig.autoResumePrompt);
410
+ return;
411
+ }
412
+ const segment = Math.min(remaining, MAX_SEGMENT);
413
+ const timer = setTimeout(() => scheduleRetry(remaining - segment), segment);
414
+ if (typeof timer === 'object' && 'unref' in timer)
415
+ timer.unref();
416
+ // 如果任务被 abort(/new、cancelUser),取消待执行的定时器
417
+ if (ctx.signal) {
418
+ if (ctx.signal.aborted) {
419
+ clearTimeout(timer);
420
+ pendingAutopilotTimers.delete(ctx.userId);
421
+ return;
422
+ }
423
+ ctx.signal.addEventListener('abort', () => {
424
+ clearTimeout(timer);
425
+ pendingAutopilotTimers.delete(ctx.userId);
426
+ log.info(`[Autopilot] Timer cancelled by abort for user ${ctx.userId}`);
427
+ }, { once: true });
428
+ }
429
+ };
430
+ scheduleRetry(delayMs);
431
+ return;
432
+ }
433
+ }
434
+ // ── 非限流错误或 autopilot 未启用/耗尽重试 ──
435
+ autopilotRetryCount.delete(ctx.userId);
266
436
  if (isUsageLimitError(error)) {
267
437
  // Usage limit errors: keep session for all tools (user can retry later)
268
438
  log.warn(`Keeping ${aiCommand} session for user ${ctx.userId} after usage limit error`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wu529778790/open-im",
3
- "version": "1.11.8-beta.5",
3
+ "version": "1.11.8-beta.7",
4
4
  "description": "Your AI coding assistant, in every chat app. Multi-platform IM bridge for Claude Code, Codex, and CodeBuddy.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",