@wu529778790/open-im 1.9.3-beta.9 → 1.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/claude-sdk-adapter.js +24 -34
- package/dist/adapters/codebuddy-adapter.js +0 -1
- package/dist/adapters/codex-adapter.js +0 -1
- package/dist/adapters/tool-adapter.interface.d.ts +0 -1
- package/dist/codebuddy/cli-runner.d.ts +0 -1
- package/dist/codebuddy/cli-runner.js +0 -61
- package/dist/codex/cli-runner.d.ts +0 -1
- package/dist/codex/cli-runner.js +0 -64
- package/dist/config-web-page-i18n.d.ts +20 -48
- package/dist/config-web-page-i18n.js +20 -48
- package/dist/config-web-page-script.js +37 -112
- package/dist/config-web-page-template.js +118 -242
- package/dist/config-web.js +0 -15
- package/dist/config.d.ts +0 -6
- package/dist/config.js +2 -16
- package/dist/dingtalk/event-handler.js +1 -3
- package/dist/feishu/event-handler.js +1 -3
- package/dist/qq/event-handler.js +1 -3
- package/dist/setup.js +2 -5
- package/dist/shared/ai-task.js +0 -6
- package/dist/shared/ai-task.test.js +0 -3
- package/dist/telegram/event-handler.js +1 -3
- package/dist/wework/event-handler.js +1 -3
- package/dist/workbuddy/event-handler.js +1 -3
- package/package.json +1 -1
- package/dist/shared/task-cleanup.d.ts +0 -2
- package/dist/shared/task-cleanup.js +0 -19
|
@@ -18,11 +18,15 @@ const activeSessions = new Map();
|
|
|
18
18
|
const activeStreams = new Set();
|
|
19
19
|
// 空闲会话清理:跟踪最后使用时间,定期清除超时会话
|
|
20
20
|
const sessionLastUsed = new Map();
|
|
21
|
+
// 跟踪正在执行任务的 session ID,防止空闲清理误杀运行中的长任务
|
|
22
|
+
const runningSessions = new Set();
|
|
21
23
|
const SESSION_IDLE_TTL_MS = 30 * 60 * 1000; // 30 分钟未使用则清理
|
|
22
24
|
const CLEANUP_INTERVAL_MS = 5 * 60 * 1000; // 每 5 分钟检查一次
|
|
23
25
|
const cleanupInterval = setInterval(() => {
|
|
24
26
|
const now = Date.now();
|
|
25
27
|
for (const [id, lastUsed] of sessionLastUsed) {
|
|
28
|
+
if (runningSessions.has(id))
|
|
29
|
+
continue; // 跳过正在运行任务的 session
|
|
26
30
|
if (now - lastUsed > SESSION_IDLE_TTL_MS) {
|
|
27
31
|
const session = activeSessions.get(id);
|
|
28
32
|
if (session) {
|
|
@@ -48,6 +52,8 @@ function lazyCleanupIdleSessions() {
|
|
|
48
52
|
return;
|
|
49
53
|
const now = Date.now();
|
|
50
54
|
for (const [id, lastUsed] of sessionLastUsed) {
|
|
55
|
+
if (runningSessions.has(id))
|
|
56
|
+
continue; // 跳过正在运行任务的 session
|
|
51
57
|
if (now - lastUsed > SESSION_IDLE_TTL_MS) {
|
|
52
58
|
const s = activeSessions.get(id);
|
|
53
59
|
if (s) {
|
|
@@ -212,7 +218,6 @@ export class ClaudeSDKAdapter {
|
|
|
212
218
|
let pendingTempId; // 记录临时 ID,用于 abort 时清理
|
|
213
219
|
let runSettled = false;
|
|
214
220
|
let currentStream; // 用于 abort 时立即中断 stream
|
|
215
|
-
let timeoutHandle;
|
|
216
221
|
const permissionMode = options?.skipPermissions
|
|
217
222
|
? 'bypassPermissions'
|
|
218
223
|
: options?.permissionMode === 'acceptEdits'
|
|
@@ -221,6 +226,7 @@ export class ClaudeSDKAdapter {
|
|
|
221
226
|
? 'plan'
|
|
222
227
|
: 'default';
|
|
223
228
|
const runSession = async () => {
|
|
229
|
+
let trackedRunningId; // 用于 finally 中清理 runningSessions
|
|
224
230
|
try {
|
|
225
231
|
// 检查环境变量
|
|
226
232
|
const hasApiKey = !!process.env.ANTHROPIC_API_KEY;
|
|
@@ -235,6 +241,8 @@ export class ClaudeSDKAdapter {
|
|
|
235
241
|
if (returnedId.startsWith('pending-')) {
|
|
236
242
|
pendingTempId = returnedId;
|
|
237
243
|
}
|
|
244
|
+
runningSessions.add(returnedId);
|
|
245
|
+
trackedRunningId = returnedId;
|
|
238
246
|
// 发送用户消息
|
|
239
247
|
await session.send(prompt);
|
|
240
248
|
// 获取响应流
|
|
@@ -264,6 +272,11 @@ export class ClaudeSDKAdapter {
|
|
|
264
272
|
sessionLastUsed.set(newSessionId, Date.now());
|
|
265
273
|
if (idToClean)
|
|
266
274
|
sessionLastUsed.delete(idToClean);
|
|
275
|
+
// 更新 runningSessions:移除旧 ID,添加新 ID
|
|
276
|
+
if (idToClean)
|
|
277
|
+
runningSessions.delete(idToClean);
|
|
278
|
+
runningSessions.add(newSessionId);
|
|
279
|
+
trackedRunningId = newSessionId;
|
|
267
280
|
actualSessionId = newSessionId;
|
|
268
281
|
log.info(`[V2] Got actual sessionId: ${newSessionId}`);
|
|
269
282
|
callbacks.onSessionId?.(newSessionId);
|
|
@@ -305,8 +318,6 @@ export class ClaudeSDKAdapter {
|
|
|
305
318
|
log.info(`[V2] Result: subtype=${m.subtype}, num_turns=${m.num_turns}, sessionId=${actualSessionId ?? 'unknown'}`);
|
|
306
319
|
// 检查会话错误
|
|
307
320
|
if (!success) {
|
|
308
|
-
if (timeoutHandle)
|
|
309
|
-
clearTimeout(timeoutHandle);
|
|
310
321
|
runSettled = true;
|
|
311
322
|
const noConvErr = errs.find((e) => e.includes('No conversation found') || e.includes('session not found'));
|
|
312
323
|
if (noConvErr) {
|
|
@@ -343,8 +354,6 @@ export class ClaudeSDKAdapter {
|
|
|
343
354
|
result.result = accumulated;
|
|
344
355
|
}
|
|
345
356
|
runSettled = true;
|
|
346
|
-
if (timeoutHandle)
|
|
347
|
-
clearTimeout(timeoutHandle);
|
|
348
357
|
callbacks.onComplete(result);
|
|
349
358
|
return;
|
|
350
359
|
}
|
|
@@ -353,8 +362,6 @@ export class ClaudeSDKAdapter {
|
|
|
353
362
|
if (!streamClosed) {
|
|
354
363
|
if (accumulated) {
|
|
355
364
|
log.info('Stream ended without result message, using accumulated text');
|
|
356
|
-
if (timeoutHandle)
|
|
357
|
-
clearTimeout(timeoutHandle);
|
|
358
365
|
runSettled = true;
|
|
359
366
|
callbacks.onComplete({
|
|
360
367
|
success: true,
|
|
@@ -369,8 +376,6 @@ export class ClaudeSDKAdapter {
|
|
|
369
376
|
else {
|
|
370
377
|
// 流结束但无 result 也无 accumulated:必须触发回调,否则 Promise 永远挂起
|
|
371
378
|
log.warn('Stream ended with no result and no accumulated text, calling onError to prevent stuck state');
|
|
372
|
-
if (timeoutHandle)
|
|
373
|
-
clearTimeout(timeoutHandle);
|
|
374
379
|
runSettled = true;
|
|
375
380
|
callbacks.onError('AI 响应异常结束(无输出),请重试');
|
|
376
381
|
}
|
|
@@ -393,8 +398,6 @@ export class ClaudeSDKAdapter {
|
|
|
393
398
|
return;
|
|
394
399
|
}
|
|
395
400
|
runSettled = true;
|
|
396
|
-
if (timeoutHandle)
|
|
397
|
-
clearTimeout(timeoutHandle);
|
|
398
401
|
const errorObj = err;
|
|
399
402
|
const msg = errorObj.message || String(err);
|
|
400
403
|
log.error(`Claude SDK V2 error: ${msg}`);
|
|
@@ -423,43 +426,30 @@ export class ClaudeSDKAdapter {
|
|
|
423
426
|
}
|
|
424
427
|
callbacks.onError(msg);
|
|
425
428
|
}
|
|
429
|
+
finally {
|
|
430
|
+
// 无论成功、失败还是 abort,都从运行中集合移除
|
|
431
|
+
if (trackedRunningId) {
|
|
432
|
+
runningSessions.delete(trackedRunningId);
|
|
433
|
+
}
|
|
434
|
+
// 也清理 actualSessionId(可能在 init 后更新了)
|
|
435
|
+
if (actualSessionId && actualSessionId !== trackedRunningId) {
|
|
436
|
+
runningSessions.delete(actualSessionId);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
426
439
|
};
|
|
427
440
|
// 启动会话(不等待),catch 兜底防止 unhandledRejection 导致用户请求挂起
|
|
428
441
|
runSession().catch((err) => {
|
|
429
442
|
if (!runSettled) {
|
|
430
443
|
runSettled = true;
|
|
431
|
-
if (timeoutHandle)
|
|
432
|
-
clearTimeout(timeoutHandle);
|
|
433
444
|
const msg = err instanceof Error ? err.message : String(err);
|
|
434
445
|
log.error(`Unhandled runSession error: ${msg}`);
|
|
435
446
|
callbacks.onError(msg);
|
|
436
447
|
}
|
|
437
448
|
});
|
|
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
449
|
return {
|
|
458
450
|
abort: () => {
|
|
459
451
|
log.info('Aborting session run');
|
|
460
452
|
abortController.abort();
|
|
461
|
-
if (timeoutHandle)
|
|
462
|
-
clearTimeout(timeoutHandle);
|
|
463
453
|
// 立即中断 stream,不等下一条消息
|
|
464
454
|
if (currentStream) {
|
|
465
455
|
try {
|
|
@@ -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;
|
|
@@ -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
|
},
|
package/dist/codex/cli-runner.js
CHANGED
|
@@ -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');
|
|
@@ -8,13 +8,14 @@ export declare const PAGE_TEXTS: {
|
|
|
8
8
|
readonly heroKicker: "Local AI bridge";
|
|
9
9
|
readonly langButton: "中文";
|
|
10
10
|
readonly darkModeToggle: "Toggle dark mode";
|
|
11
|
+
readonly headerToolbarAria: "Bridge: validate, save, start, stop";
|
|
11
12
|
readonly controlCenter: "Control center";
|
|
12
13
|
readonly sidebarNoteTitle: "Local workflow";
|
|
13
14
|
readonly sidebarNoteBody: "Configure at least one platform, save the config, then start the bridge from Service.";
|
|
14
15
|
readonly mode: "Flow";
|
|
15
16
|
readonly dashboardTitle: "Dashboard";
|
|
16
17
|
readonly dashboardSubtitle: "Platform health status";
|
|
17
|
-
readonly dashboardSubtitleFull: "Platform
|
|
18
|
+
readonly dashboardSubtitleFull: "Platform and service status";
|
|
18
19
|
readonly quickActionsTitle: "Quick Actions";
|
|
19
20
|
readonly serviceTitle: "Service Control";
|
|
20
21
|
readonly serviceHint: "Validate, save, start, and stop the local bridge from one place.";
|
|
@@ -40,7 +41,13 @@ export declare const PAGE_TEXTS: {
|
|
|
40
41
|
readonly serviceIdleMeta: "Bridge has not been started yet";
|
|
41
42
|
readonly listSeparator: ", ";
|
|
42
43
|
readonly platformsTitle: "Platforms";
|
|
43
|
-
readonly
|
|
44
|
+
readonly navConfigFiles: "Config files";
|
|
45
|
+
readonly configFilesTitle: "Config files (JSON)";
|
|
46
|
+
readonly configFilesHint: "Edit the files on disk directly. Each card has its own Save. Platform / AI forms and Service → Save config still apply.";
|
|
47
|
+
readonly openImConfigCardHint: "Full open-im configuration. Use Format, then Save. Invalid JSON is rejected.";
|
|
48
|
+
readonly claudeSettingsCardHint: "Claude SDK environment (ANTHROPIC_API_KEY, ANTHROPIC_BASE_URL, ANTHROPIC_MODEL, etc.). Set API access here without shell exports.";
|
|
49
|
+
readonly claudeJsonShortcutHint: "Edit ~/.claude/settings.json in the sidebar «Config files» section below.";
|
|
50
|
+
readonly platformsHint: "Disabled platforms keep their saved values.";
|
|
44
51
|
readonly enabled: "Enabled";
|
|
45
52
|
readonly readyState: "Ready";
|
|
46
53
|
readonly setupRequired: "Setup required";
|
|
@@ -81,7 +88,7 @@ export declare const PAGE_TEXTS: {
|
|
|
81
88
|
readonly optional: "Optional";
|
|
82
89
|
readonly commaSeparatedIds: "Comma-separated IDs";
|
|
83
90
|
readonly aiTitle: "AI Tooling";
|
|
84
|
-
readonly aiHint: "Pick the default tool first. Claude SDK
|
|
91
|
+
readonly aiHint: "Pick the default tool first. Claude SDK keys: sidebar Config files → ~/.claude/settings.json. Codex / CodeBuddy need a CLI path here.";
|
|
85
92
|
readonly claudeNote: "Claude credentials are still read from environment variables or ~/.claude/settings.json. This page manages local bridge config, not Claude account auth.";
|
|
86
93
|
readonly aiCommonTitle: "Shared defaults";
|
|
87
94
|
readonly aiCommonHint: "Choose the default AI tool first. Tool-specific fields are shown below.";
|
|
@@ -94,14 +101,11 @@ export declare const PAGE_TEXTS: {
|
|
|
94
101
|
readonly codexCli: "Codex CLI path";
|
|
95
102
|
readonly codebuddyCli: "CodeBuddy CLI path";
|
|
96
103
|
readonly codexProxy: "Codex proxy";
|
|
97
|
-
readonly claudeTimeout: "Claude timeout (ms)";
|
|
98
104
|
readonly claudeConfigPath: "Config file location";
|
|
99
105
|
readonly claudeAuthToken: "ANTHROPIC_AUTH_TOKEN";
|
|
100
106
|
readonly claudeBaseUrl: "ANTHROPIC_BASE_URL";
|
|
101
107
|
readonly claudeModel: "ANTHROPIC_MODEL";
|
|
102
108
|
readonly claudeProxy: "Proxy (optional)";
|
|
103
|
-
readonly codexTimeout: "Codex timeout (ms)";
|
|
104
|
-
readonly codebuddyTimeout: "CodeBuddy timeout (ms)";
|
|
105
109
|
readonly hookPort: "Hook port";
|
|
106
110
|
readonly logLevel: "Log level";
|
|
107
111
|
readonly logLevelDefault: "default (app default)";
|
|
@@ -134,28 +138,10 @@ export declare const PAGE_TEXTS: {
|
|
|
134
138
|
readonly saveBtn: "Save";
|
|
135
139
|
readonly jsonValid: "Valid JSON";
|
|
136
140
|
readonly jsonInvalid: "Invalid JSON: {error}";
|
|
137
|
-
readonly wizardTitle: "First-time setup";
|
|
138
|
-
readonly wizardStep1Title: "Connect an IM channel";
|
|
139
|
-
readonly wizardStep1Desc: "Enable at least one platform below and fill every required field. Use Check config when available.";
|
|
140
|
-
readonly wizardStep2Title: "Set the default AI tool";
|
|
141
|
-
readonly wizardStep2Desc: "Claude SDK: API keys live in ~/.claude/settings.json (expand on this page). Codex / CodeBuddy: set the CLI path in the AI section.";
|
|
142
|
-
readonly wizardStep3Title: "Validate and save";
|
|
143
|
-
readonly wizardStep3Desc: "Click Validate, then Save config. Fix any errors shown in the banner above.";
|
|
144
|
-
readonly wizardStep4Title: "Start the bridge";
|
|
145
|
-
readonly wizardStep4Desc: "Open Service and click Start bridge. If it exits, read ~/.open-im/logs.";
|
|
146
|
-
readonly wizardStatusDone: "Done";
|
|
147
|
-
readonly wizardStatusTodo: "To do";
|
|
148
|
-
readonly wizardJumpPlatforms: "Open Platforms";
|
|
149
|
-
readonly wizardJumpAi: "Open AI";
|
|
150
|
-
readonly wizardJumpService: "Open Service";
|
|
151
141
|
readonly validationNoPlatformEnabled: "Enable at least one IM platform and fill its required credentials before saving.";
|
|
152
142
|
readonly validationPlatformIncomplete: "Platform \"{platform}\" is enabled but these required fields are empty: {fields}";
|
|
153
143
|
readonly validationAiCodexNoCli: "Default AI is Codex but Codex CLI path is empty. Set it under AI Tooling or change the default tool.";
|
|
154
144
|
readonly validationAiCodebuddyNoCli: "Default AI is CodeBuddy but CodeBuddy CLI path is empty. Set it under AI Tooling or change the default tool.";
|
|
155
|
-
readonly onboardingTitle: "Welcome to open-im";
|
|
156
|
-
readonly onboardingDismiss: "Got it";
|
|
157
|
-
readonly onboardingReadme: "README (setup)";
|
|
158
|
-
readonly onboardingBody: "<p><strong>open-im</strong> bridges Telegram, Feishu, QQ, WeWork, DingTalk, and WorkBuddy to Claude / Codex / CodeBuddy on your machine.</p><ol style=\"margin:12px 0 12px 18px;line-height:1.65\"><li>Pick <strong>one</strong> chat app in <strong>Platforms</strong> and paste credentials (see hints under each field).</li><li>Set the <strong>default AI</strong> and, for Claude SDK, API keys in <strong>~/.claude/settings.json</strong> on this page.</li><li>Use <strong>Validate</strong> then <strong>Save config</strong>, then <strong>Start bridge</strong> under Service.</li><li>CLI alternative: run <code style=\"background:var(--bg-tertiary);padding:2px 6px;border-radius:4px\">open-im init</code> in a terminal.</li></ol>";
|
|
159
145
|
readonly tipTelegramToken: "From Telegram, open <a href=\"https://t.me/BotFather\" target=\"_blank\" rel=\"noopener\">@BotFather</a> → /newbot → copy the token.";
|
|
160
146
|
readonly tipFeishuAppId: "Feishu Open Platform → your app → Credentials → App ID.";
|
|
161
147
|
readonly tipFeishuSecret: "Same page → App Secret (click show / reset if needed).";
|
|
@@ -174,13 +160,14 @@ export declare const PAGE_TEXTS: {
|
|
|
174
160
|
readonly heroKicker: "本地 AI 桥接";
|
|
175
161
|
readonly langButton: "EN";
|
|
176
162
|
readonly darkModeToggle: "切换暗黑模式";
|
|
163
|
+
readonly headerToolbarAria: "桥接:校验、保存、启动、停止";
|
|
177
164
|
readonly controlCenter: "控制中心";
|
|
178
165
|
readonly sidebarNoteTitle: "本地工作流";
|
|
179
166
|
readonly sidebarNoteBody: "至少配置一个平台,保存配置,然后在服务控制区启动桥接。";
|
|
180
167
|
readonly mode: "模式";
|
|
181
168
|
readonly dashboardTitle: "概览";
|
|
182
169
|
readonly dashboardSubtitle: "平台健康状态";
|
|
183
|
-
readonly dashboardSubtitleFull: "
|
|
170
|
+
readonly dashboardSubtitleFull: "平台与服务状态";
|
|
184
171
|
readonly quickActionsTitle: "快捷操作";
|
|
185
172
|
readonly refreshHealth: "刷新健康状态";
|
|
186
173
|
readonly viewConfig: "查看配置";
|
|
@@ -204,7 +191,13 @@ export declare const PAGE_TEXTS: {
|
|
|
204
191
|
readonly serviceIdleMeta: "桥接服务尚未启动";
|
|
205
192
|
readonly listSeparator: "、";
|
|
206
193
|
readonly platformsTitle: "平台配置";
|
|
207
|
-
readonly
|
|
194
|
+
readonly navConfigFiles: "配置文件";
|
|
195
|
+
readonly configFilesTitle: "配置文件(JSON)";
|
|
196
|
+
readonly configFilesHint: "直接编辑磁盘上的文件,每张卡片有独立保存按钮。平台 / AI 表单与服务区的「保存配置」仍然有效。";
|
|
197
|
+
readonly openImConfigCardHint: "open-im 完整配置。先格式化再保存;JSON 不合法时无法写入。";
|
|
198
|
+
readonly claudeSettingsCardHint: "Claude SDK 环境变量(ANTHROPIC_API_KEY、ANTHROPIC_BASE_URL、ANTHROPIC_MODEL 等)。在此配置 API,无需在终端 export。";
|
|
199
|
+
readonly claudeJsonShortcutHint: "提示:请在左侧栏「配置文件」分区编辑 ~/.claude/settings.json。";
|
|
200
|
+
readonly platformsHint: "禁用的平台仍保留已保存的值。";
|
|
208
201
|
readonly enabled: "启用";
|
|
209
202
|
readonly readyState: "已就绪";
|
|
210
203
|
readonly setupRequired: "需要完成配置";
|
|
@@ -244,7 +237,7 @@ export declare const PAGE_TEXTS: {
|
|
|
244
237
|
readonly optional: "可选";
|
|
245
238
|
readonly commaSeparatedIds: "多个 ID 用逗号分隔";
|
|
246
239
|
readonly aiTitle: "AI 工具配置";
|
|
247
|
-
readonly aiHint: "先选默认 AI。Claude SDK
|
|
240
|
+
readonly aiHint: "先选默认 AI。Claude SDK:左侧栏「配置文件」→ ~/.claude/settings.json。Codex / CodeBuddy 在此填 CLI 路径。";
|
|
248
241
|
readonly claudeNote: "Claude 凭证仍然从环境变量或 ~/.claude/settings.json 读取。这个页面只管理本地桥接配置,不负责 Claude 账号登录。";
|
|
249
242
|
readonly aiCommonTitle: "公共默认配置";
|
|
250
243
|
readonly aiCommonHint: "先选择默认 AI 工具,下方再显示对应的工具专属配置。";
|
|
@@ -257,11 +250,8 @@ export declare const PAGE_TEXTS: {
|
|
|
257
250
|
readonly codexCli: "Codex CLI 路径";
|
|
258
251
|
readonly codebuddyCli: "CodeBuddy CLI 路径";
|
|
259
252
|
readonly codexProxy: "Codex 代理";
|
|
260
|
-
readonly claudeTimeout: "Claude 超时(毫秒)";
|
|
261
253
|
readonly claudeConfigPath: "配置文件位置";
|
|
262
254
|
readonly claudeProxy: "代理(可选)";
|
|
263
|
-
readonly codexTimeout: "Codex 超时(毫秒)";
|
|
264
|
-
readonly codebuddyTimeout: "CodeBuddy 超时(毫秒)";
|
|
265
255
|
readonly hookPort: "Hook 端口";
|
|
266
256
|
readonly logLevel: "日志级别";
|
|
267
257
|
readonly logLevelDefault: "default(程序默认)";
|
|
@@ -294,28 +284,10 @@ export declare const PAGE_TEXTS: {
|
|
|
294
284
|
readonly saveBtn: "保存";
|
|
295
285
|
readonly jsonValid: "JSON 有效";
|
|
296
286
|
readonly jsonInvalid: "JSON 无效:{error}";
|
|
297
|
-
readonly wizardTitle: "首次使用引导";
|
|
298
|
-
readonly wizardStep1Title: "接入一个 IM 渠道";
|
|
299
|
-
readonly wizardStep1Desc: "在下方启用至少一个平台,并填写所有必填项。有条件时用「校验配置」测试凭证。";
|
|
300
|
-
readonly wizardStep2Title: "设置默认 AI 工具";
|
|
301
|
-
readonly wizardStep2Desc: "Claude SDK:API Key 写在 ~/.claude/settings.json(本页可展开编辑)。Codex / CodeBuddy:在 AI 区填 CLI 路径。";
|
|
302
|
-
readonly wizardStep3Title: "校验并保存";
|
|
303
|
-
readonly wizardStep3Desc: "先点「校验」,再点「保存配置」。按顶部提示修复错误。";
|
|
304
|
-
readonly wizardStep4Title: "启动桥接";
|
|
305
|
-
readonly wizardStep4Desc: "到「服务」区点「启动桥接」。若马上退出,查 ~/.open-im/logs 日志。";
|
|
306
|
-
readonly wizardStatusDone: "已完成";
|
|
307
|
-
readonly wizardStatusTodo: "待完成";
|
|
308
|
-
readonly wizardJumpPlatforms: "打开平台配置";
|
|
309
|
-
readonly wizardJumpAi: "打开 AI 配置";
|
|
310
|
-
readonly wizardJumpService: "打开服务控制";
|
|
311
287
|
readonly validationNoPlatformEnabled: "保存前请至少启用一个 IM 平台,并填完必填凭证。";
|
|
312
288
|
readonly validationPlatformIncomplete: "平台「{platform}」已启用,但以下必填项为空:{fields}";
|
|
313
289
|
readonly validationAiCodexNoCli: "默认 AI 为 Codex,但 Codex CLI 路径为空。请在 AI 区填写,或改默认工具。";
|
|
314
290
|
readonly validationAiCodebuddyNoCli: "默认 AI 为 CodeBuddy,但 CodeBuddy CLI 路径为空。请在 AI 区填写,或改默认工具。";
|
|
315
|
-
readonly onboardingTitle: "欢迎使用 open-im";
|
|
316
|
-
readonly onboardingDismiss: "知道了";
|
|
317
|
-
readonly onboardingReadme: "README(部署说明)";
|
|
318
|
-
readonly onboardingBody: "<p><strong>open-im</strong> 在本机把 Telegram、飞书、QQ、企微、钉钉、WorkBuddy 等渠道连到 Claude / Codex / CodeBuddy。</p><ol style=\"margin:12px 0 12px 18px;line-height:1.65\"><li>在<strong>平台配置</strong>选一个聊天应用,按字段下方提示填凭证。</li><li>设<strong>默认 AI</strong>;用 Claude SDK 时把 API Key 写进<strong>~/.claude/settings.json</strong>(本页可编辑)。</li><li>先<strong>校验</strong>再<strong>保存配置</strong>,然后到<strong>服务</strong>启动桥接。</li><li>也可在终端运行 <code style=\"background:var(--bg-tertiary);padding:2px 6px;border-radius:4px\">open-im init</code> 交互配置。</li></ol>";
|
|
319
291
|
readonly tipTelegramToken: "在 Telegram 搜 <a href=\"https://t.me/BotFather\" target=\"_blank\" rel=\"noopener\">@BotFather</a>,发 /newbot 创建机器人后复制 Token。";
|
|
320
292
|
readonly tipFeishuAppId: "飞书开放平台 → 应用 → 凭证与基础信息 → App ID。";
|
|
321
293
|
readonly tipFeishuSecret: "同一页 App Secret(可重置后查看)。";
|