@wu529778790/open-im 1.9.3-beta.9 → 1.9.4-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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 {
@@ -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');
@@ -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 status and setup progress";
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 platformsHint: "Follow the setup checklist on Overview first. Disabled platforms still keep saved values.";
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 uses API keys in ~/.claude/settings.json (editable below). Codex / CodeBuddy need a CLI path.";
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 platformsHint: "建议先在概览页按引导步骤操作。禁用的平台仍保留已保存的值。";
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 的 API Key 写在 ~/.claude/settings.json(下方可编辑)。Codex / CodeBuddy 需填 CLI 路径。";
240
+ readonly aiHint: "先选默认 AI。Claude SDK:左侧栏「配置文件」→ ~/.claude/settings.jsonCodex / 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(可重置后查看)。";