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

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