@wu529778790/open-im 1.9.3-beta.13 → 1.9.3-beta.14

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.
@@ -212,6 +212,7 @@ export class ClaudeSDKAdapter {
212
212
  let pendingTempId; // 记录临时 ID,用于 abort 时清理
213
213
  let runSettled = false;
214
214
  let currentStream; // 用于 abort 时立即中断 stream
215
+ let timeoutHandle;
215
216
  const permissionMode = options?.skipPermissions
216
217
  ? 'bypassPermissions'
217
218
  : options?.permissionMode === 'acceptEdits'
@@ -304,6 +305,8 @@ export class ClaudeSDKAdapter {
304
305
  log.info(`[V2] Result: subtype=${m.subtype}, num_turns=${m.num_turns}, sessionId=${actualSessionId ?? 'unknown'}`);
305
306
  // 检查会话错误
306
307
  if (!success) {
308
+ if (timeoutHandle)
309
+ clearTimeout(timeoutHandle);
307
310
  runSettled = true;
308
311
  const noConvErr = errs.find((e) => e.includes('No conversation found') || e.includes('session not found'));
309
312
  if (noConvErr) {
@@ -340,6 +343,8 @@ export class ClaudeSDKAdapter {
340
343
  result.result = accumulated;
341
344
  }
342
345
  runSettled = true;
346
+ if (timeoutHandle)
347
+ clearTimeout(timeoutHandle);
343
348
  callbacks.onComplete(result);
344
349
  return;
345
350
  }
@@ -348,6 +353,8 @@ export class ClaudeSDKAdapter {
348
353
  if (!streamClosed) {
349
354
  if (accumulated) {
350
355
  log.info('Stream ended without result message, using accumulated text');
356
+ if (timeoutHandle)
357
+ clearTimeout(timeoutHandle);
351
358
  runSettled = true;
352
359
  callbacks.onComplete({
353
360
  success: true,
@@ -362,6 +369,8 @@ export class ClaudeSDKAdapter {
362
369
  else {
363
370
  // 流结束但无 result 也无 accumulated:必须触发回调,否则 Promise 永远挂起
364
371
  log.warn('Stream ended with no result and no accumulated text, calling onError to prevent stuck state');
372
+ if (timeoutHandle)
373
+ clearTimeout(timeoutHandle);
365
374
  runSettled = true;
366
375
  callbacks.onError('AI 响应异常结束(无输出),请重试');
367
376
  }
@@ -384,6 +393,8 @@ export class ClaudeSDKAdapter {
384
393
  return;
385
394
  }
386
395
  runSettled = true;
396
+ if (timeoutHandle)
397
+ clearTimeout(timeoutHandle);
387
398
  const errorObj = err;
388
399
  const msg = errorObj.message || String(err);
389
400
  log.error(`Claude SDK V2 error: ${msg}`);
@@ -417,15 +428,38 @@ export class ClaudeSDKAdapter {
417
428
  runSession().catch((err) => {
418
429
  if (!runSettled) {
419
430
  runSettled = true;
431
+ if (timeoutHandle)
432
+ clearTimeout(timeoutHandle);
420
433
  const msg = err instanceof Error ? err.message : String(err);
421
434
  log.error(`Unhandled runSession error: ${msg}`);
422
435
  callbacks.onError(msg);
423
436
  }
424
437
  });
438
+ // 强制执行超时
439
+ if (options?.timeoutMs && options.timeoutMs > 0) {
440
+ timeoutHandle = setTimeout(() => {
441
+ if (!runSettled) {
442
+ log.warn(`Session timed out after ${options.timeoutMs}ms, aborting`);
443
+ abortController.abort();
444
+ // 立即中断 stream,不等下一条消息
445
+ if (currentStream) {
446
+ try {
447
+ currentStream.return?.();
448
+ }
449
+ catch { /* ignore */ }
450
+ }
451
+ runSettled = true;
452
+ callbacks.onError(`AI 响应超时(${Math.round(options.timeoutMs / 1000)}s),请重试`);
453
+ }
454
+ }, options.timeoutMs);
455
+ timeoutHandle.unref();
456
+ }
425
457
  return {
426
458
  abort: () => {
427
459
  log.info('Aborting session run');
428
460
  abortController.abort();
461
+ if (timeoutHandle)
462
+ clearTimeout(timeoutHandle);
429
463
  // 立即中断 stream,不等下一条消息
430
464
  if (currentStream) {
431
465
  try {
@@ -40,6 +40,7 @@ export class CodeBuddyAdapter {
40
40
  }, {
41
41
  skipPermissions: options?.skipPermissions,
42
42
  permissionMode: options?.permissionMode,
43
+ timeoutMs: options?.timeoutMs,
43
44
  model: options?.model,
44
45
  });
45
46
  }
@@ -12,6 +12,7 @@ export class CodexAdapter {
12
12
  const opts = {
13
13
  skipPermissions: options?.skipPermissions,
14
14
  permissionMode: options?.permissionMode,
15
+ timeoutMs: options?.timeoutMs,
15
16
  model: options?.model,
16
17
  chatId: options?.chatId,
17
18
  hookPort: options?.hookPort,
@@ -25,6 +25,7 @@ export interface RunOptions {
25
25
  skipPermissions?: boolean;
26
26
  /** Claude --permission-mode: default | acceptEdits | plan(yolo 时用 skipPermissions) */
27
27
  permissionMode?: 'default' | 'acceptEdits' | 'plan';
28
+ timeoutMs?: number;
28
29
  model?: string;
29
30
  chatId?: string;
30
31
  hookPort?: number;
@@ -19,6 +19,7 @@ export interface CodeBuddyRunCallbacks {
19
19
  export interface CodeBuddyRunOptions {
20
20
  skipPermissions?: boolean;
21
21
  permissionMode?: 'default' | 'acceptEdits' | 'plan';
22
+ timeoutMs?: number;
22
23
  model?: string;
23
24
  }
24
25
  export interface CodeBuddyRunHandle {
@@ -3,6 +3,18 @@ import { accessSync, constants } from 'node:fs';
3
3
  import { isAbsolute, join } from 'node:path';
4
4
  import { createLogger } from '../logger.js';
5
5
  const log = createLogger('CodeBuddyCli');
6
+ const MAX_TIMEOUT_MS = 2_147_483_647;
7
+ const DEFAULT_IDLE_TIMEOUT_MS = 10 * 60 * 1000;
8
+ function getIdleTimeoutMs(totalTimeoutMs) {
9
+ const raw = process.env.CODEBUDDY_IDLE_TIMEOUT_MS;
10
+ const parsed = raw ? Number.parseInt(raw, 10) : Number.NaN;
11
+ const configuredIdleTimeoutMs = Number.isFinite(parsed) && parsed > 0
12
+ ? Math.min(parsed, MAX_TIMEOUT_MS)
13
+ : DEFAULT_IDLE_TIMEOUT_MS;
14
+ return totalTimeoutMs > 0
15
+ ? Math.min(configuredIdleTimeoutMs, totalTimeoutMs)
16
+ : configuredIdleTimeoutMs;
17
+ }
6
18
  export function buildCodeBuddyArgs(prompt, sessionId, options) {
7
19
  const args = ['--print', '--output-format', 'stream-json'];
8
20
  if (options?.skipPermissions) {
@@ -191,7 +203,50 @@ export function runCodeBuddy(cliPath, prompt, sessionId, workDir, callbacks, opt
191
203
  let currentModel;
192
204
  const toolStats = {};
193
205
  const startTime = Date.now();
206
+ const timeoutMs = options?.timeoutMs && options.timeoutMs > 0
207
+ ? Math.min(options.timeoutMs, MAX_TIMEOUT_MS)
208
+ : 0;
209
+ const idleTimeoutMs = getIdleTimeoutMs(timeoutMs);
210
+ let timeoutHandle = null;
211
+ let idleTimeoutHandle = null;
194
212
  const stdoutState = { buffer: '' };
213
+ const clearTimers = () => {
214
+ if (timeoutHandle) {
215
+ clearTimeout(timeoutHandle);
216
+ timeoutHandle = null;
217
+ }
218
+ if (idleTimeoutHandle) {
219
+ clearTimeout(idleTimeoutHandle);
220
+ idleTimeoutHandle = null;
221
+ }
222
+ };
223
+ const resetIdleTimeout = () => {
224
+ if (idleTimeoutMs <= 0 || completed)
225
+ return;
226
+ if (idleTimeoutHandle)
227
+ clearTimeout(idleTimeoutHandle);
228
+ idleTimeoutHandle = setTimeout(() => {
229
+ if (completed)
230
+ return;
231
+ completed = true;
232
+ clearTimers();
233
+ if (!child.killed)
234
+ child.kill('SIGTERM');
235
+ callbacks.onError(`CodeBuddy 执行长时间无输出,已自动终止(${idleTimeoutMs}ms)`);
236
+ }, idleTimeoutMs);
237
+ };
238
+ if (timeoutMs > 0) {
239
+ timeoutHandle = setTimeout(() => {
240
+ if (completed)
241
+ return;
242
+ completed = true;
243
+ clearTimers();
244
+ if (!child.killed)
245
+ child.kill('SIGTERM');
246
+ callbacks.onError(`CodeBuddy 执行超时(${timeoutMs}ms),已终止`);
247
+ }, timeoutMs);
248
+ }
249
+ resetIdleTimeout();
195
250
  const MAX_STDERR = 8 * 1024;
196
251
  let stderrText = '';
197
252
  const handleErrorText = (message) => {
@@ -244,6 +299,7 @@ export function runCodeBuddy(cliPath, prompt, sessionId, workDir, callbacks, opt
244
299
  if (completed)
245
300
  return;
246
301
  completed = true;
302
+ clearTimers();
247
303
  const isError = payload.is_error === true;
248
304
  const resultText = typeof payload.result === 'string'
249
305
  ? payload.result
@@ -268,6 +324,7 @@ export function runCodeBuddy(cliPath, prompt, sessionId, workDir, callbacks, opt
268
324
  }
269
325
  };
270
326
  child.stdout?.on('data', (chunk) => {
327
+ resetIdleTimeout();
271
328
  stdoutState.buffer += chunk.toString();
272
329
  const payloads = extractBufferedPayloads(stdoutState);
273
330
  for (const payload of payloads) {
@@ -280,12 +337,14 @@ export function runCodeBuddy(cliPath, prompt, sessionId, workDir, callbacks, opt
280
337
  }
281
338
  });
282
339
  child.stderr?.on('data', (chunk) => {
340
+ resetIdleTimeout();
283
341
  stderrText += chunk.toString();
284
342
  if (stderrText.length > MAX_STDERR) {
285
343
  stderrText = stderrText.slice(-MAX_STDERR);
286
344
  }
287
345
  });
288
346
  child.on('close', (code) => {
347
+ clearTimers();
289
348
  if (completed)
290
349
  return;
291
350
  if (stdoutState.buffer.trim()) {
@@ -318,6 +377,7 @@ export function runCodeBuddy(cliPath, prompt, sessionId, workDir, callbacks, opt
318
377
  });
319
378
  });
320
379
  child.on('error', (err) => {
380
+ clearTimers();
321
381
  if (completed)
322
382
  return;
323
383
  completed = true;
@@ -326,6 +386,7 @@ export function runCodeBuddy(cliPath, prompt, sessionId, workDir, callbacks, opt
326
386
  return {
327
387
  abort: () => {
328
388
  completed = true;
389
+ clearTimers();
329
390
  if (!child.killed)
330
391
  child.kill('SIGTERM');
331
392
  },
@@ -22,6 +22,7 @@ export interface CodexRunCallbacks {
22
22
  export interface CodexRunOptions {
23
23
  skipPermissions?: boolean;
24
24
  permissionMode?: 'default' | 'acceptEdits' | 'plan';
25
+ timeoutMs?: number;
25
26
  model?: string;
26
27
  chatId?: string;
27
28
  hookPort?: number;
@@ -8,6 +8,8 @@ import { createInterface } from 'node:readline';
8
8
  import { createLogger } from '../logger.js';
9
9
  const log = createLogger('CodexCli');
10
10
  const windowsCodexLaunchCache = new Map();
11
+ const MAX_TIMEOUT_MS = 2_147_483_647;
12
+ const DEFAULT_IDLE_TIMEOUT_MS = 10 * 60 * 1000;
11
13
  const SUPPORTED_IMAGE_EXTENSIONS = new Set([
12
14
  '.png',
13
15
  '.jpg',
@@ -19,6 +21,16 @@ const SUPPORTED_IMAGE_EXTENSIONS = new Set([
19
21
  '.tiff',
20
22
  '.avif',
21
23
  ]);
24
+ function getIdleTimeoutMs(totalTimeoutMs) {
25
+ const raw = process.env.CODEX_IDLE_TIMEOUT_MS;
26
+ const parsed = raw ? Number.parseInt(raw, 10) : Number.NaN;
27
+ const configuredIdleTimeoutMs = Number.isFinite(parsed) && parsed > 0
28
+ ? Math.min(parsed, MAX_TIMEOUT_MS)
29
+ : DEFAULT_IDLE_TIMEOUT_MS;
30
+ return totalTimeoutMs > 0
31
+ ? Math.min(configuredIdleTimeoutMs, totalTimeoutMs)
32
+ : configuredIdleTimeoutMs;
33
+ }
22
34
  function parseCodexEvent(line) {
23
35
  const trimmed = line.trim();
24
36
  if (!trimmed)
@@ -213,7 +225,51 @@ export function runCodex(cliPath, prompt, sessionId, workDir, callbacks, options
213
225
  let completed = false;
214
226
  const toolStats = {};
215
227
  const startTime = Date.now();
228
+ const timeoutMs = options?.timeoutMs && options.timeoutMs > 0
229
+ ? Math.min(options.timeoutMs, MAX_TIMEOUT_MS)
230
+ : 0;
231
+ const idleTimeoutMs = getIdleTimeoutMs(timeoutMs);
232
+ let timeoutHandle = null;
233
+ let idleTimeoutHandle = null;
216
234
  const rl = createInterface({ input: child.stdout });
235
+ const clearTimers = () => {
236
+ if (timeoutHandle) {
237
+ clearTimeout(timeoutHandle);
238
+ timeoutHandle = null;
239
+ }
240
+ if (idleTimeoutHandle) {
241
+ clearTimeout(idleTimeoutHandle);
242
+ idleTimeoutHandle = null;
243
+ }
244
+ };
245
+ const failAndTerminate = (message, logMessage) => {
246
+ if (completed)
247
+ return;
248
+ completed = true;
249
+ clearTimers();
250
+ log.warn(logMessage);
251
+ rl.close();
252
+ if (!child.killed)
253
+ child.kill('SIGTERM');
254
+ callbacks.onError(message);
255
+ };
256
+ const resetIdleTimeout = () => {
257
+ if (idleTimeoutMs <= 0 || completed)
258
+ return;
259
+ if (idleTimeoutHandle)
260
+ clearTimeout(idleTimeoutHandle);
261
+ idleTimeoutHandle = setTimeout(() => {
262
+ failAndTerminate(`Codex 执行长时间无输出,已自动终止(${idleTimeoutMs}ms)`, `Codex CLI idle timeout after ${idleTimeoutMs}ms, killing pid=${child.pid}`);
263
+ }, idleTimeoutMs);
264
+ };
265
+ if (timeoutMs > 0) {
266
+ timeoutHandle = setTimeout(() => {
267
+ if (!completed && !child.killed) {
268
+ failAndTerminate(`执行超时(${timeoutMs}ms),已终止进程`, `Codex CLI timeout after ${timeoutMs}ms, killing pid=${child.pid}`);
269
+ }
270
+ }, timeoutMs);
271
+ }
272
+ resetIdleTimeout();
217
273
  const MAX_STDERR_HEAD = 4 * 1024;
218
274
  const MAX_STDERR_TAIL = 6 * 1024;
219
275
  let stderrHead = '';
@@ -221,6 +277,7 @@ export function runCodex(cliPath, prompt, sessionId, workDir, callbacks, options
221
277
  let stderrTotal = 0;
222
278
  let stderrHeadFull = false;
223
279
  child.stderr?.on('data', (chunk) => {
280
+ resetIdleTimeout();
224
281
  const text = chunk.toString();
225
282
  stderrTotal += text.length;
226
283
  if (!stderrHeadFull) {
@@ -238,6 +295,7 @@ export function runCodex(cliPath, prompt, sessionId, workDir, callbacks, options
238
295
  log.debug(`[stderr] ${text.trimEnd()}`);
239
296
  });
240
297
  rl.on('line', (line) => {
298
+ resetIdleTimeout();
241
299
  const event = parseCodexEvent(line);
242
300
  if (!event)
243
301
  return;
@@ -251,6 +309,7 @@ export function runCodex(cliPath, prompt, sessionId, workDir, callbacks, options
251
309
  }
252
310
  if (type === 'turn.failed') {
253
311
  completed = true;
312
+ clearTimers();
254
313
  const err = event.error;
255
314
  callbacks.onError(err?.message ?? 'Codex turn failed');
256
315
  return;
@@ -261,6 +320,7 @@ export function runCodex(cliPath, prompt, sessionId, workDir, callbacks, options
261
320
  return;
262
321
  }
263
322
  completed = true;
323
+ clearTimers();
264
324
  callbacks.onError(msg ?? 'Codex stream error');
265
325
  return;
266
326
  }
@@ -314,6 +374,7 @@ export function runCodex(cliPath, prompt, sessionId, workDir, callbacks, options
314
374
  }
315
375
  if (type === 'turn.completed') {
316
376
  completed = true;
377
+ clearTimers();
317
378
  callbacks.onComplete({
318
379
  success: true,
319
380
  result: accumulated,
@@ -331,6 +392,7 @@ export function runCodex(cliPath, prompt, sessionId, workDir, callbacks, options
331
392
  const finalize = () => {
332
393
  if (!rlClosed || !childClosed)
333
394
  return;
395
+ clearTimers();
334
396
  if (completed)
335
397
  return;
336
398
  if (exitCode !== null && exitCode !== 0) {
@@ -381,6 +443,7 @@ export function runCodex(cliPath, prompt, sessionId, workDir, callbacks, options
381
443
  child.on('error', (err) => {
382
444
  const errorCode = err.code;
383
445
  log.error(`Codex CLI spawn error: ${err.message}, code=${errorCode}, path=${cliPath}`);
446
+ clearTimers();
384
447
  if (!completed) {
385
448
  completed = true;
386
449
  callbacks.onError(`Failed to start Codex CLI: ${err.message}`);
@@ -391,6 +454,7 @@ export function runCodex(cliPath, prompt, sessionId, workDir, callbacks, options
391
454
  return {
392
455
  abort: () => {
393
456
  completed = true;
457
+ clearTimers();
394
458
  rl.close();
395
459
  if (!child.killed)
396
460
  child.kill('SIGTERM');
@@ -101,11 +101,14 @@ export declare const PAGE_TEXTS: {
101
101
  readonly codexCli: "Codex CLI path";
102
102
  readonly codebuddyCli: "CodeBuddy CLI path";
103
103
  readonly codexProxy: "Codex proxy";
104
+ readonly claudeTimeout: "Claude timeout (ms)";
104
105
  readonly claudeConfigPath: "Config file location";
105
106
  readonly claudeAuthToken: "ANTHROPIC_AUTH_TOKEN";
106
107
  readonly claudeBaseUrl: "ANTHROPIC_BASE_URL";
107
108
  readonly claudeModel: "ANTHROPIC_MODEL";
108
109
  readonly claudeProxy: "Proxy (optional)";
110
+ readonly codexTimeout: "Codex timeout (ms)";
111
+ readonly codebuddyTimeout: "CodeBuddy timeout (ms)";
109
112
  readonly hookPort: "Hook port";
110
113
  readonly logLevel: "Log level";
111
114
  readonly logLevelDefault: "default (app default)";
@@ -250,8 +253,11 @@ export declare const PAGE_TEXTS: {
250
253
  readonly codexCli: "Codex CLI 路径";
251
254
  readonly codebuddyCli: "CodeBuddy CLI 路径";
252
255
  readonly codexProxy: "Codex 代理";
256
+ readonly claudeTimeout: "Claude 超时(毫秒)";
253
257
  readonly claudeConfigPath: "配置文件位置";
254
258
  readonly claudeProxy: "代理(可选)";
259
+ readonly codexTimeout: "Codex 超时(毫秒)";
260
+ readonly codebuddyTimeout: "CodeBuddy 超时(毫秒)";
255
261
  readonly hookPort: "Hook 端口";
256
262
  readonly logLevel: "日志级别";
257
263
  readonly logLevelDefault: "default(程序默认)";
@@ -101,11 +101,14 @@ export const PAGE_TEXTS = {
101
101
  codexCli: "Codex CLI path",
102
102
  codebuddyCli: "CodeBuddy CLI path",
103
103
  codexProxy: "Codex proxy",
104
+ claudeTimeout: "Claude timeout (ms)",
104
105
  claudeConfigPath: "Config file location",
105
106
  claudeAuthToken: "ANTHROPIC_AUTH_TOKEN",
106
107
  claudeBaseUrl: "ANTHROPIC_BASE_URL",
107
108
  claudeModel: "ANTHROPIC_MODEL",
108
109
  claudeProxy: "Proxy (optional)",
110
+ codexTimeout: "Codex timeout (ms)",
111
+ codebuddyTimeout: "CodeBuddy timeout (ms)",
109
112
  hookPort: "Hook port",
110
113
  logLevel: "Log level",
111
114
  logLevelDefault: "default (app default)",
@@ -250,8 +253,11 @@ export const PAGE_TEXTS = {
250
253
  codexCli: "Codex CLI \u8def\u5f84",
251
254
  codebuddyCli: "CodeBuddy CLI \u8def\u5f84",
252
255
  codexProxy: "Codex \u4ee3\u7406",
256
+ claudeTimeout: "Claude \u8d85\u65f6\uff08\u6beb\u79d2\uff09",
253
257
  claudeConfigPath: "\u914d\u7f6e\u6587\u4ef6\u4f4d\u7f6e",
254
258
  claudeProxy: "\u4ee3\u7406\uff08\u53ef\u9009\uff09",
259
+ codexTimeout: "Codex \u8d85\u65f6\uff08\u6beb\u79d2\uff09",
260
+ codebuddyTimeout: "CodeBuddy \u8d85\u65f6\uff08\u6beb\u79d2\uff09",
255
261
  hookPort: "Hook \u7aef\u53e3",
256
262
  logLevel: "\u65e5\u5fd7\u7ea7\u522b",
257
263
  logLevelDefault: "default\uff08\u7a0b\u5e8f\u9ed8\u8ba4\uff09",
@@ -229,11 +229,14 @@ export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
229
229
  { id: "aiCommonTitle", key: "aiCommonTitle" },
230
230
  { id: "ai-aiCommand-label", key: "aiTool" },
231
231
  { id: "ai-claudeWorkDir-label", key: "workDir" },
232
+ { id: "ai-claudeTimeoutMs-label", key: "claudeTimeout" },
232
233
  { id: "ai-claudeConfigPath-label", key: "claudeConfigPath" },
233
234
  { id: "ai-claudeProxy-label", key: "claudeProxy" },
234
235
  { id: "ai-codexCliPath-label", key: "codexCli" },
236
+ { id: "ai-codexTimeoutMs-label", key: "codexTimeout" },
235
237
  { id: "ai-codexProxy-label", key: "codexProxy" },
236
238
  { id: "ai-codebuddyCliPath-label", key: "codebuddyCli" },
239
+ { id: "ai-codebuddyTimeoutMs-label", key: "codebuddyTimeout" },
237
240
  { id: "ai-hookPort-label", key: "hookPort" },
238
241
  { id: "ai-logLevel-label", key: "logLevel" },
239
242
  ],
@@ -596,11 +599,14 @@ export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
596
599
  const AI_FIELD_MAPPINGS = [
597
600
  { id: "ai-aiCommand", key: "aiCommand" },
598
601
  { id: "ai-claudeWorkDir", key: "claudeWorkDir" },
602
+ { id: "ai-claudeTimeoutMs", key: "claudeTimeoutMs" },
599
603
  { id: "ai-claudeConfigPath", key: "claudeConfigPath" },
600
604
  { id: "ai-claudeProxy", key: "claudeProxy" },
601
605
  { id: "ai-codexCliPath", key: "codexCliPath" },
606
+ { id: "ai-codexTimeoutMs", key: "codexTimeoutMs" },
602
607
  { id: "ai-codexProxy", key: "codexProxy" },
603
608
  { id: "ai-codebuddyCliPath", key: "codebuddyCliPath" },
609
+ { id: "ai-codebuddyTimeoutMs", key: "codebuddyTimeoutMs" },
604
610
  { id: "ai-hookPort", key: "hookPort" },
605
611
  { id: "ai-logLevel", key: "logLevel" },
606
612
  ];
@@ -917,8 +923,11 @@ export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
917
923
  ai: {
918
924
  aiCommand: getValue("ai-aiCommand"),
919
925
  claudeWorkDir: getValue("ai-claudeWorkDir"),
926
+ claudeTimeoutMs: getNumber("ai-claudeTimeoutMs"),
920
927
  claudeConfigPath: getValue("ai-claudeConfigPath"),
921
928
  claudeProxy: getValue("ai-claudeProxy"),
929
+ codexTimeoutMs: getNumber("ai-codexTimeoutMs"),
930
+ codebuddyTimeoutMs: getNumber("ai-codebuddyTimeoutMs"),
922
931
  codexCliPath: getValue("ai-codexCliPath"),
923
932
  codexProxy: getValue("ai-codexProxy"),
924
933
  codebuddyCliPath: getValue("ai-codebuddyCliPath"),
@@ -1237,6 +1237,10 @@ export const PAGE_HTML_PREFIX = String.raw `<!doctype html>
1237
1237
  </div>
1238
1238
  <div class="ai-card-body">
1239
1239
  <div id="ai-tool-claude" class="ai-tool-panel active" data-tool-panel="claude">
1240
+ <div class="form-group">
1241
+ <label class="form-label" id="ai-claudeTimeoutMs-label">Timeout (ms)</label>
1242
+ <input id="ai-claudeTimeoutMs" class="form-input" type="number" min="1" />
1243
+ </div>
1240
1244
  <div class="form-group">
1241
1245
  <label class="form-label" id="ai-claudeProxy-label">Proxy (optional)</label>
1242
1246
  <input id="ai-claudeProxy" class="form-input mono" type="text" />
@@ -1255,6 +1259,10 @@ export const PAGE_HTML_PREFIX = String.raw `<!doctype html>
1255
1259
  <label class="form-label" id="ai-codexCliPath-label">CLI Path</label>
1256
1260
  <input id="ai-codexCliPath" class="form-input mono" type="text" />
1257
1261
  </div>
1262
+ <div class="form-group">
1263
+ <label class="form-label" id="ai-codexTimeoutMs-label">Timeout (ms)</label>
1264
+ <input id="ai-codexTimeoutMs" class="form-input" type="number" min="1" />
1265
+ </div>
1258
1266
  <div class="form-group">
1259
1267
  <label class="form-label" id="ai-codexProxy-label">Proxy (optional)</label>
1260
1268
  <input id="ai-codexProxy" class="form-input mono" type="text" />
@@ -1266,6 +1274,10 @@ export const PAGE_HTML_PREFIX = String.raw `<!doctype html>
1266
1274
  <label class="form-label" id="ai-codebuddyCliPath-label">CLI Path</label>
1267
1275
  <input id="ai-codebuddyCliPath" class="form-input mono" type="text" />
1268
1276
  </div>
1277
+ <div class="form-group">
1278
+ <label class="form-label" id="ai-codebuddyTimeoutMs-label">Timeout (ms)</label>
1279
+ <input id="ai-codebuddyTimeoutMs" class="form-input" type="number" min="1" />
1280
+ </div>
1269
1281
  </div>
1270
1282
  </div>
1271
1283
  </div>
@@ -278,6 +278,7 @@ function buildInitialPayload(file) {
278
278
  ai: {
279
279
  aiCommand: file.aiCommand ?? "claude",
280
280
  claudeWorkDir: file.tools?.claude?.workDir ?? process.cwd(),
281
+ claudeTimeoutMs: file.tools?.claude?.timeoutMs ?? 600000,
281
282
  claudeConfigPath: process.platform === 'win32'
282
283
  ? getClaudeConfigHome() + "\\.claude\\settings.json"
283
284
  : getClaudeConfigHome() + "/.claude/settings.json",
@@ -285,6 +286,8 @@ function buildInitialPayload(file) {
285
286
  claudeBaseUrl: claudeEnv.ANTHROPIC_BASE_URL ?? "",
286
287
  claudeModel: claudeEnv.ANTHROPIC_MODEL ?? "",
287
288
  claudeProxy: file.tools?.claude?.proxy ?? "",
289
+ codexTimeoutMs: file.tools?.codex?.timeoutMs ?? 600000,
290
+ codebuddyTimeoutMs: file.tools?.codebuddy?.timeoutMs ?? 600000,
288
291
  codexCliPath: file.tools?.codex?.cliPath ?? "codex",
289
292
  codebuddyCliPath: file.tools?.codebuddy?.cliPath ?? "codebuddy",
290
293
  codexProxy: file.tools?.codex?.proxy ?? "",
@@ -324,6 +327,12 @@ function validatePayload(payload) {
324
327
  errors.push("WorkBuddy user ID is required.");
325
328
  if (!clean(payload.ai.claudeWorkDir))
326
329
  errors.push("Default work directory is required.");
330
+ if (!Number.isFinite(payload.ai.claudeTimeoutMs) || payload.ai.claudeTimeoutMs <= 0)
331
+ errors.push("Claude timeout must be positive.");
332
+ if (!Number.isFinite(payload.ai.codexTimeoutMs) || payload.ai.codexTimeoutMs <= 0)
333
+ errors.push("Codex timeout must be positive.");
334
+ if (!Number.isFinite(payload.ai.codebuddyTimeoutMs) || payload.ai.codebuddyTimeoutMs <= 0)
335
+ errors.push("CodeBuddy timeout must be positive.");
327
336
  return errors;
328
337
  }
329
338
  function validateConfigForPlatform(platform, config) {
@@ -413,6 +422,9 @@ function createProbeConfig(values) {
413
422
  aiCommand: "claude",
414
423
  codexCliPath: "codex",
415
424
  claudeWorkDir: process.cwd(),
425
+ claudeTimeoutMs: 600000,
426
+ codexTimeoutMs: 600000,
427
+ codebuddyTimeoutMs: 600000,
416
428
  logDir: "",
417
429
  logLevel: "INFO",
418
430
  codebuddyCliPath: "codebuddy",
@@ -576,6 +588,7 @@ function toFileConfig(payload, existing) {
576
588
  claude: {
577
589
  ...existing.tools?.claude,
578
590
  workDir: clean(payload.ai.claudeWorkDir) ?? process.cwd(),
591
+ timeoutMs: payload.ai.claudeTimeoutMs,
579
592
  proxy: clean(payload.ai.claudeProxy),
580
593
  // model is now saved to ~/.claude/settings.json as env var
581
594
  },
@@ -583,11 +596,13 @@ function toFileConfig(payload, existing) {
583
596
  ...existing.tools?.codex,
584
597
  cliPath: clean(payload.ai.codexCliPath) ?? "codex",
585
598
  workDir: clean(payload.ai.claudeWorkDir) ?? process.cwd(),
599
+ timeoutMs: payload.ai.codexTimeoutMs,
586
600
  proxy: clean(payload.ai.codexProxy),
587
601
  },
588
602
  codebuddy: {
589
603
  ...existing.tools?.codebuddy,
590
604
  cliPath: clean(payload.ai.codebuddyCliPath) ?? "codebuddy",
605
+ timeoutMs: payload.ai.codebuddyTimeoutMs,
591
606
  },
592
607
  },
593
608
  platforms: {
package/dist/config.d.ts CHANGED
@@ -28,6 +28,9 @@ export interface Config {
28
28
  claudeProxy?: string;
29
29
  /** Codex 访问 chatgpt.com 的代理(如 http://127.0.0.1:7890) */
30
30
  codexProxy?: string;
31
+ claudeTimeoutMs: number;
32
+ codexTimeoutMs: number;
33
+ codebuddyTimeoutMs: number;
31
34
  claudeWorkDir: string;
32
35
  claudeModel?: string;
33
36
  logDir: string;
@@ -134,6 +137,7 @@ interface FilePlatformWorkBuddy {
134
137
  export interface FileToolClaude {
135
138
  cliPath?: string;
136
139
  workDir?: string;
140
+ timeoutMs?: number;
137
141
  skipPermissions?: boolean;
138
142
  /** HTTP/HTTPS 代理,用于访问 Claude API(如 http://127.0.0.1:7890) */
139
143
  proxy?: string;
@@ -143,11 +147,13 @@ export interface FileToolClaude {
143
147
  export interface FileToolCodex {
144
148
  cliPath?: string;
145
149
  workDir?: string;
150
+ timeoutMs?: number;
146
151
  /** HTTP/HTTPS 代理,用于访问 chatgpt.com(如 http://127.0.0.1:7890) */
147
152
  proxy?: string;
148
153
  }
149
154
  export interface FileToolCodeBuddy {
150
155
  cliPath?: string;
156
+ timeoutMs?: number;
151
157
  }
152
158
  export interface FileConfig {
153
159
  telegramBotToken?: string;
package/dist/config.js CHANGED
@@ -20,8 +20,7 @@ const CODEX_AUTH_PATHS = [
20
20
  ];
21
21
  const OLD_ROOT_KEYS = [
22
22
  'claudeWorkDir',
23
- 'claudeTimeoutMs',
24
- 'claudeModel',
23
+ 'claudeTimeoutMs', 'claudeModel',
25
24
  ];
26
25
  // Config cache with mtime tracking
27
26
  let cachedConfig = null;
@@ -58,6 +57,7 @@ function migrateToNewConfigFormat(raw) {
58
57
  claude: {
59
58
  ...tc,
60
59
  workDir: tc.workDir ?? raw.claudeWorkDir ?? process.cwd(),
60
+ timeoutMs: tc.timeoutMs ?? raw.claudeTimeoutMs ?? 600000,
61
61
  proxy: tc.proxy,
62
62
  // model 现在通过 env 配置,不再在这里处理
63
63
  },
@@ -65,11 +65,13 @@ function migrateToNewConfigFormat(raw) {
65
65
  ...tcod,
66
66
  cliPath: tcod.cliPath ?? 'codex',
67
67
  workDir: tcod.workDir ?? raw.claudeWorkDir ?? process.cwd(),
68
+ timeoutMs: tcod.timeoutMs ?? raw.claudeTimeoutMs ?? 600000,
68
69
  proxy: tcod.proxy,
69
70
  },
70
71
  codebuddy: {
71
72
  ...tcb,
72
73
  cliPath: tcb.cliPath ?? 'codebuddy',
74
+ timeoutMs: tcb.timeoutMs ?? raw.claudeTimeoutMs ?? 600000,
73
75
  },
74
76
  };
75
77
  for (const k of OLD_ROOT_KEYS) {
@@ -405,6 +407,15 @@ export function loadConfig() {
405
407
  }
406
408
  }
407
409
  const claudeWorkDir = process.env.CLAUDE_WORK_DIR ?? tc.workDir ?? process.cwd();
410
+ const claudeTimeoutMs = process.env.CLAUDE_TIMEOUT_MS !== undefined
411
+ ? parseInt(process.env.CLAUDE_TIMEOUT_MS, 10) || 600000
412
+ : tc.timeoutMs ?? 600000;
413
+ const codexTimeoutMs = process.env.CODEX_TIMEOUT_MS !== undefined
414
+ ? parseInt(process.env.CODEX_TIMEOUT_MS, 10) || 600000
415
+ : tcod.timeoutMs ?? 600000;
416
+ const codebuddyTimeoutMs = process.env.CODEBUDDY_TIMEOUT_MS !== undefined
417
+ ? parseInt(process.env.CODEBUDDY_TIMEOUT_MS, 10) || 600000
418
+ : tcb.timeoutMs ?? 600000;
408
419
  // 6. 校验 Claude API 凭证(SDK 模式需要)
409
420
  // 支持:官方 API Key、Auth Token、或自定义 API(第三方模型等,BASE_URL + token)
410
421
  if (aiCommand === 'claude') {
@@ -628,6 +639,9 @@ export function loadConfig() {
628
639
  claudeProxy,
629
640
  codexProxy,
630
641
  claudeWorkDir,
642
+ claudeTimeoutMs,
643
+ codexTimeoutMs,
644
+ codebuddyTimeoutMs,
631
645
  claudeModel: process.env.ANTHROPIC_MODEL,
632
646
  logDir,
633
647
  logLevel,
@@ -6,6 +6,7 @@ import { ackMessage, downloadRobotMessageFile, registerSessionWebhook } from './
6
6
  import { CommandHandler } from '../commands/handler.js';
7
7
  import { getAdapter } from '../adapters/registry.js';
8
8
  import { runAITask } from '../shared/ai-task.js';
9
+ import { startTaskCleanup } from '../shared/task-cleanup.js';
9
10
  import { setActiveChatId, setDingTalkActiveTarget } from '../shared/active-chats.js';
10
11
  import { setChatUser } from '../shared/chat-user-map.js';
11
12
  import { createLogger } from '../logger.js';
@@ -158,6 +159,7 @@ export function setupDingTalkHandlers(config, sessionManager) {
158
159
  const accessControl = new AccessControl(config.dingtalkAllowedUserIds);
159
160
  const requestQueue = new RequestQueue();
160
161
  const runningTasks = new Map();
162
+ const stopTaskCleanup = startTaskCleanup(runningTasks);
161
163
  const commandHandler = new CommandHandler({
162
164
  config,
163
165
  sessionManager,
@@ -299,7 +301,7 @@ export function setupDingTalkHandlers(config, sessionManager) {
299
301
  ackMessage(callbackId, { queued: enqueueResult });
300
302
  }
301
303
  return {
302
- stop: () => { },
304
+ stop: () => stopTaskCleanup(),
303
305
  getRunningTaskCount: () => runningTasks.size,
304
306
  handleEvent,
305
307
  };
@@ -7,6 +7,7 @@ import { getAdapter } from '../adapters/registry.js';
7
7
  import { runAITask } from '../shared/ai-task.js';
8
8
  import { buildCardV2 } from './card-builder.js';
9
9
  import { disableStreaming, updateCardFull, destroySession } from './cardkit-manager.js';
10
+ import { startTaskCleanup } from '../shared/task-cleanup.js';
10
11
  import { CARDKIT_THROTTLE_MS } from '../constants.js';
11
12
  import { setActiveChatId } from '../shared/active-chats.js';
12
13
  import { setChatUser } from '../shared/chat-user-map.js';
@@ -125,6 +126,7 @@ export function setupFeishuHandlers(config, sessionManager) {
125
126
  const accessControl = new AccessControl(config.feishuAllowedUserIds);
126
127
  const requestQueue = new RequestQueue();
127
128
  const runningTasks = new Map();
129
+ const stopTaskCleanup = startTaskCleanup(runningTasks);
128
130
  const commandHandler = new CommandHandler({
129
131
  config,
130
132
  sessionManager,
@@ -606,7 +608,7 @@ export function setupFeishuHandlers(config, sessionManager) {
606
608
  }
607
609
  }
608
610
  return {
609
- stop: () => { },
611
+ stop: () => stopTaskCleanup(),
610
612
  getRunningTaskCount: () => runningTasks.size,
611
613
  handleEvent,
612
614
  };
@@ -5,6 +5,7 @@ import { sendThinkingMessage, updateMessage, sendFinalMessages, sendErrorMessage
5
5
  import { CommandHandler } from "../commands/handler.js";
6
6
  import { getAdapter } from "../adapters/registry.js";
7
7
  import { runAITask } from "../shared/ai-task.js";
8
+ import { startTaskCleanup } from "../shared/task-cleanup.js";
8
9
  import { setActiveChatId } from "../shared/active-chats.js";
9
10
  import { setChatUser } from "../shared/chat-user-map.js";
10
11
  import { createLogger } from "../logger.js";
@@ -118,6 +119,7 @@ export function setupQQHandlers(config, sessionManager) {
118
119
  const runningTasks = new Map();
119
120
  const recentEventIds = new Map();
120
121
  const recentEventFingerprints = new Map();
122
+ const stopTaskCleanup = startTaskCleanup(runningTasks);
121
123
  const commandHandler = new CommandHandler({
122
124
  config,
123
125
  sessionManager,
@@ -256,7 +258,7 @@ export function setupQQHandlers(config, sessionManager) {
256
258
  log.info(`QQ message handled: user=${userId}, chat=${chatId}, status=${enqueueResult}, attachments=${event.attachments?.length ?? 0}`);
257
259
  }
258
260
  return {
259
- stop: () => { },
261
+ stop: () => stopTaskCleanup(),
260
262
  getRunningTaskCount: () => runningTasks.size,
261
263
  handleEvent,
262
264
  };
package/dist/setup.js CHANGED
@@ -75,10 +75,11 @@ function printManualInstructions(configPath) {
75
75
  "tools": {
76
76
  "claude": {
77
77
  "cliPath": "claude",
78
- "workDir": "${process.cwd().replace(/\\/g, "/")}"
78
+ "workDir": "${process.cwd().replace(/\\/g, "/")}",
79
+ "timeoutMs": 600000
79
80
  },
80
81
  "codex": { "cliPath": "codex", "workDir": "${process.cwd().replace(/\\/g, "/")}", "proxy": "http://127.0.0.1:7890" },
81
- "codebuddy": { "cliPath": "codebuddy" }
82
+ "codebuddy": { "cliPath": "codebuddy", "timeoutMs": 600000 }
82
83
  },
83
84
  "platforms": {
84
85
  "telegram": {
@@ -861,6 +862,7 @@ export async function runInteractiveSetup() {
861
862
  ...baseTools.claude,
862
863
  cliPath: baseTools.claude?.cliPath ?? "claude",
863
864
  workDir,
865
+ timeoutMs: baseTools.claude?.timeoutMs ?? 600000,
864
866
  },
865
867
  codex: {
866
868
  ...baseTools.codex,
@@ -873,6 +875,7 @@ export async function runInteractiveSetup() {
873
875
  codebuddy: {
874
876
  ...baseTools.codebuddy,
875
877
  cliPath: baseTools.codebuddy?.cliPath ?? "codebuddy",
878
+ timeoutMs: baseTools.codebuddy?.timeoutMs ?? 600000,
876
879
  },
877
880
  },
878
881
  };
@@ -86,6 +86,11 @@ export function runAITask(deps, ctx, prompt, toolAdapter, platformAdapter) {
86
86
  // 使用 aiCommand 而不是 toolAdapter.toolId,确保 sessionId 的存储和查询使用相同的 key
87
87
  const aiCommand = resolvePlatformAiCommand(config, ctx.platform);
88
88
  const toolId = toolAdapter.toolId;
89
+ const timeoutMs = aiCommand === 'codex'
90
+ ? config.codexTimeoutMs
91
+ : aiCommand === 'codebuddy'
92
+ ? config.codebuddyTimeoutMs
93
+ : config.claudeTimeoutMs;
89
94
  const startRun = () => {
90
95
  log.info(`[AITask] Starting: userId=${ctx.userId}, initialSessionId=${currentSessionId ?? 'new'}, prompt="${prompt.slice(0, 50)}..."`);
91
96
  activeHandle = toolAdapter.run(prompt, currentSessionId, ctx.workDir, {
@@ -220,6 +225,7 @@ export function runAITask(deps, ctx, prompt, toolAdapter, platformAdapter) {
220
225
  resolve();
221
226
  },
222
227
  }, {
228
+ timeoutMs,
223
229
  model: sessionManager.getModel(ctx.userId, ctx.threadId) ?? config.claudeModel,
224
230
  chatId: ctx.chatId,
225
231
  // 默认跳过权限确认,保持全自动执行
@@ -35,6 +35,9 @@ describe("runAITask", () => {
35
35
  aiCommand: "codex",
36
36
  platforms: {},
37
37
  enabledPlatforms: [],
38
+ codexTimeoutMs: 600000,
39
+ claudeTimeoutMs: 600000,
40
+ codebuddyTimeoutMs: 600000,
38
41
  claudeModel: "",
39
42
  codexProxy: "",
40
43
  wechatUserId: "",
@@ -0,0 +1,2 @@
1
+ import type { TaskRunState } from './ai-task.js';
2
+ export declare function startTaskCleanup(runningTasks: Map<string, TaskRunState>): () => void;
@@ -0,0 +1,19 @@
1
+ import { createLogger } from '../logger.js';
2
+ const log = createLogger('TaskCleanup');
3
+ const TASK_TIMEOUT_MS = 30 * 60 * 1000;
4
+ const INTERVAL_MS = 10 * 60 * 1000;
5
+ export function startTaskCleanup(runningTasks) {
6
+ const timer = setInterval(() => {
7
+ const now = Date.now();
8
+ for (const [key, task] of runningTasks) {
9
+ if (now - task.startedAt > TASK_TIMEOUT_MS) {
10
+ log.warn(`Auto-cleaning timeout task: ${key}`);
11
+ task.settle();
12
+ task.handle.abort();
13
+ runningTasks.delete(key);
14
+ }
15
+ }
16
+ }, INTERVAL_MS);
17
+ timer.unref();
18
+ return () => clearInterval(timer);
19
+ }
@@ -6,6 +6,7 @@ import { sendThinkingMessage, updateMessage, sendFinalMessages, sendTextReply, s
6
6
  import { CommandHandler } from "../commands/handler.js";
7
7
  import { getAdapter } from "../adapters/registry.js";
8
8
  import { runAITask } from "../shared/ai-task.js";
9
+ import { startTaskCleanup } from "../shared/task-cleanup.js";
9
10
  import { TELEGRAM_THROTTLE_MS } from "../constants.js";
10
11
  import { setActiveChatId } from "../shared/active-chats.js";
11
12
  import { setChatUser } from "../shared/chat-user-map.js";
@@ -65,6 +66,7 @@ export function setupTelegramHandlers(bot, config, sessionManager) {
65
66
  const accessControl = new AccessControl(config.telegramAllowedUserIds);
66
67
  const requestQueue = new RequestQueue();
67
68
  const runningTasks = new Map();
69
+ const stopTaskCleanup = startTaskCleanup(runningTasks);
68
70
  const commandHandler = new CommandHandler({
69
71
  config,
70
72
  sessionManager,
@@ -486,7 +488,7 @@ export function setupTelegramHandlers(bot, config, sessionManager) {
486
488
  }
487
489
  });
488
490
  return {
489
- stop: () => { },
491
+ stop: () => stopTaskCleanup(),
490
492
  getRunningTaskCount: () => runningTasks.size,
491
493
  };
492
494
  }
@@ -8,6 +8,7 @@ import { sendThinkingMessage, updateMessage, sendFinalMessages, sendTextReply, s
8
8
  import { CommandHandler } from '../commands/handler.js';
9
9
  import { getAdapter } from '../adapters/registry.js';
10
10
  import { runAITask } from '../shared/ai-task.js';
11
+ import { startTaskCleanup } from '../shared/task-cleanup.js';
11
12
  import { WEWORK_THROTTLE_MS } from '../constants.js';
12
13
  import { setActiveChatId } from '../shared/active-chats.js';
13
14
  import { setChatUser } from '../shared/chat-user-map.js';
@@ -162,6 +163,7 @@ export function setupWeWorkHandlers(config, sessionManager) {
162
163
  const accessControl = new AccessControl(config.weworkAllowedUserIds);
163
164
  const requestQueue = new RequestQueue();
164
165
  const runningTasks = new Map();
166
+ const stopTaskCleanup = startTaskCleanup(runningTasks);
165
167
  // Mutable ref that captures the req_id of the message currently being handled.
166
168
  // WeWork requires req_id to reply; CommandHandler doesn't carry it, so we inject
167
169
  // it via a closure. WeWork delivers messages sequentially over WebSocket, so
@@ -330,7 +332,7 @@ export function setupWeWorkHandlers(config, sessionManager) {
330
332
  }
331
333
  }
332
334
  return {
333
- stop: () => { },
335
+ stop: () => stopTaskCleanup(),
334
336
  getRunningTaskCount: () => runningTasks.size,
335
337
  handleEvent,
336
338
  };
@@ -8,6 +8,7 @@ import { sendTextReply, sendErrorReply } from './message-sender.js';
8
8
  import { CommandHandler } from '../commands/handler.js';
9
9
  import { getAdapter } from '../adapters/registry.js';
10
10
  import { runAITask } from '../shared/ai-task.js';
11
+ import { startTaskCleanup } from '../shared/task-cleanup.js';
11
12
  import { WORKBUDDY_THROTTLE_MS } from '../constants.js';
12
13
  import { setActiveChatId } from '../shared/active-chats.js';
13
14
  import { setChatUser } from '../shared/chat-user-map.js';
@@ -18,6 +19,7 @@ export function setupWorkBuddyHandlers(config, sessionManager) {
18
19
  const requestQueue = new RequestQueue();
19
20
  const runningTasks = new Map();
20
21
  const taskKeyByChatId = new Map();
22
+ const stopTaskCleanup = startTaskCleanup(runningTasks);
21
23
  // Base dependencies for creating per-event CommandHandler
22
24
  const baseCommandDeps = {
23
25
  config,
@@ -114,7 +116,7 @@ export function setupWorkBuddyHandlers(config, sessionManager) {
114
116
  }
115
117
  }
116
118
  return {
117
- stop: () => { },
119
+ stop: () => stopTaskCleanup(),
118
120
  getRunningTaskCount: () => runningTasks.size,
119
121
  handleEvent,
120
122
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wu529778790/open-im",
3
- "version": "1.9.3-beta.13",
3
+ "version": "1.9.3-beta.14",
4
4
  "description": "Multi-platform IM bridge for AI CLI tools (Claude, Codex, CodeBuddy)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",