@wu529778790/open-im 1.10.2-beta.6 → 1.10.2

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.
@@ -9,6 +9,10 @@
9
9
  * 认证:ANTHROPIC_API_KEY 或 CLAUDE_CODE_OAUTH_TOKEN
10
10
  */
11
11
  import type { ToolAdapter, RunCallbacks, RunOptions, RunHandle } from './tool-adapter.interface.js';
12
+ /**
13
+ * 由 initAdapters 根据配置调用。ttlMinutes≤0 时关闭空闲回收(仍受 MAX_ACTIVE_SESSIONS 限制)。
14
+ */
15
+ export declare function configureClaudeSdkSessionIdle(ttlMinutes: number): void;
12
16
  export declare class ClaudeSDKAdapter implements ToolAdapter {
13
17
  readonly toolId = "claude-sdk";
14
18
  /**
@@ -53,16 +53,32 @@ const activeStreams = new Set();
53
53
  const sessionLastUsed = new Map();
54
54
  // 跟踪正在执行任务的 session ID,防止空闲清理误杀运行中的长任务
55
55
  const runningSessions = new Set();
56
- const SESSION_IDLE_TTL_MS = 30 * 60 * 1000; // 30 分钟未使用则清理
56
+ let sessionIdleTtlMs = 30 * 60 * 1000; // 默认 30 分钟未使用则清理
57
+ let sessionIdleCleanupDisabled = false;
57
58
  const CLEANUP_INTERVAL_MS = 5 * 60 * 1000; // 每 5 分钟检查一次
58
59
  const MAX_ACTIVE_SESSIONS = 100;
59
60
  let sessionSeq = 0;
61
+ /**
62
+ * 由 initAdapters 根据配置调用。ttlMinutes≤0 时关闭空闲回收(仍受 MAX_ACTIVE_SESSIONS 限制)。
63
+ */
64
+ export function configureClaudeSdkSessionIdle(ttlMinutes) {
65
+ if (ttlMinutes <= 0) {
66
+ sessionIdleCleanupDisabled = true;
67
+ log.info('Claude SDK: idle session cleanup disabled (sessionIdleTtlMinutes=0)');
68
+ }
69
+ else {
70
+ sessionIdleCleanupDisabled = false;
71
+ sessionIdleTtlMs = ttlMinutes * 60 * 1000;
72
+ }
73
+ }
60
74
  const cleanupInterval = setInterval(() => {
75
+ if (sessionIdleCleanupDisabled)
76
+ return;
61
77
  const now = Date.now();
62
78
  for (const [id, lastUsed] of sessionLastUsed) {
63
79
  if (runningSessions.has(id))
64
80
  continue; // 跳过正在运行任务的 session
65
- if (now - lastUsed > SESSION_IDLE_TTL_MS) {
81
+ if (now - lastUsed > sessionIdleTtlMs) {
66
82
  const session = activeSessions.get(id);
67
83
  if (session) {
68
84
  try {
@@ -1,5 +1,5 @@
1
1
  import { getConfiguredAiCommands } from '../config.js';
2
- import { ClaudeSDKAdapter } from './claude-sdk-adapter.js';
2
+ import { ClaudeSDKAdapter, configureClaudeSdkSessionIdle } from './claude-sdk-adapter.js';
3
3
  import { CodexAdapter } from './codex-adapter.js';
4
4
  import { CodeBuddyAdapter } from './codebuddy-adapter.js';
5
5
  import { createLogger } from '../logger.js';
@@ -10,6 +10,7 @@ export function initAdapters(config) {
10
10
  for (const aiCommand of getConfiguredAiCommands(config)) {
11
11
  if (aiCommand === 'claude') {
12
12
  log.info('Claude Agent SDK adapter enabled');
13
+ configureClaudeSdkSessionIdle(config.claudeSessionIdleTtlMinutes);
13
14
  adapters.set('claude', new ClaudeSDKAdapter());
14
15
  continue;
15
16
  }
@@ -29,6 +29,10 @@ export interface Config {
29
29
  /** Codex 访问 chatgpt.com 的代理(如 http://127.0.0.1:7890) */
30
30
  codexProxy?: string;
31
31
  claudeWorkDir: string;
32
+ /**
33
+ * Claude SDK 进程内会话空闲多久后回收(分钟)。0 表示关闭空闲回收(仍受适配器内 MAX_ACTIVE_SESSIONS 限制)。默认 30。
34
+ */
35
+ claudeSessionIdleTtlMinutes: number;
32
36
  claudeModel?: string;
33
37
  /** 是否跳过 AI 工具的权限确认(默认 true) */
34
38
  skipPermissions?: boolean;
@@ -137,6 +141,8 @@ export interface FileToolClaude {
137
141
  cliPath?: string;
138
142
  workDir?: string;
139
143
  skipPermissions?: boolean;
144
+ /** 空闲会话回收间隔(分钟),0 表示关闭 */
145
+ sessionIdleTtlMinutes?: number;
140
146
  /** HTTP/HTTPS 代理,用于访问 Claude API(如 http://127.0.0.1:7890) */
141
147
  proxy?: string;
142
148
  /** Claude API 配置(优先级:环境变量 > tools.claude.env > ~/.claude/settings.json) */
@@ -458,6 +458,7 @@ function createProbeConfig(values) {
458
458
  aiCommand: "claude",
459
459
  codexCliPath: "codex",
460
460
  claudeWorkDir: process.cwd(),
461
+ claudeSessionIdleTtlMinutes: 30,
461
462
  logDir: "",
462
463
  logLevel: "INFO",
463
464
  codebuddyCliPath: "codebuddy",
package/dist/config.js CHANGED
@@ -219,6 +219,19 @@ export function loadConfig() {
219
219
  const skipPermissions = process.env.OPEN_IM_SKIP_PERMISSIONS === 'false'
220
220
  ? false
221
221
  : (tc.skipPermissions ?? true);
222
+ const envIdleRaw = process.env.OPEN_IM_CLAUDE_SESSION_IDLE_TTL_MINUTES;
223
+ const envIdleParsed = envIdleRaw !== undefined && envIdleRaw !== '' ? Number.parseInt(envIdleRaw, 10) : NaN;
224
+ const fileIdle = tc.sessionIdleTtlMinutes;
225
+ let claudeSessionIdleTtlMinutes;
226
+ if (Number.isFinite(envIdleParsed)) {
227
+ claudeSessionIdleTtlMinutes = Math.max(0, envIdleParsed);
228
+ }
229
+ else if (typeof fileIdle === 'number' && Number.isFinite(fileIdle)) {
230
+ claudeSessionIdleTtlMinutes = Math.max(0, fileIdle);
231
+ }
232
+ else {
233
+ claudeSessionIdleTtlMinutes = 30;
234
+ }
222
235
  // 6. 校验 Claude API 凭证(SDK 模式需要)
223
236
  // 支持:官方 API Key、Auth Token、或自定义 API(第三方模型等,BASE_URL + token)
224
237
  if (aiCommand === 'claude') {
@@ -442,6 +455,7 @@ export function loadConfig() {
442
455
  claudeProxy,
443
456
  codexProxy,
444
457
  claudeWorkDir,
458
+ claudeSessionIdleTtlMinutes,
445
459
  claudeModel: process.env.ANTHROPIC_MODEL,
446
460
  skipPermissions,
447
461
  logDir,
package/dist/index.js CHANGED
@@ -104,11 +104,8 @@ async function sendLifecycleNotification(platform, message) {
104
104
  return;
105
105
  }
106
106
  log.info(`[${platform}] Sending lifecycle notification to chatId=${chatId}`);
107
- await mod.sendNotification(chatId, message).then(() => {
108
- log.info(`[${platform}] Lifecycle notification sent successfully`);
109
- }).catch((err) => {
110
- log.warn(`Failed to send ${platform} notification:`, err);
111
- });
107
+ await mod.sendNotification(chatId, message);
108
+ log.info(`[${platform}] Lifecycle notification sent successfully`);
112
109
  }
113
110
  function buildStartupMessage(platform, appVersion, aiCommand, defaultWorkDir, sessionManager) {
114
111
  let sessionDir;
package/dist/qq/client.js CHANGED
@@ -254,11 +254,20 @@ async function connectWebSocket(config, handler) {
254
254
  settle(() => { }); // 清理 ready timeout
255
255
  clearTimers();
256
256
  ws = null;
257
- log.info(`QQ gateway closed: ${code} ${reason.toString()}`);
257
+ const reasonStr = reason.toString();
258
+ if (code === 4009) {
259
+ log.info(`QQ gateway session idle timeout (4009), reconnecting… (${reasonStr})`);
260
+ }
261
+ else {
262
+ log.info(`QQ gateway closed: ${code} ${reasonStr}`);
263
+ }
258
264
  if (stopped)
259
265
  return;
260
- if (code === 4004 || code === 4006 || code === 4007 || code === 4009) {
266
+ // 4009 仅为长连接会话过期,HTTP access_token 仍有效,勿清空 tokenState
267
+ if (code === 4004 || code === 4006 || code === 4007) {
261
268
  tokenState = null;
269
+ }
270
+ if (code === 4004 || code === 4006 || code === 4007 || code === 4009) {
262
271
  sessionId = null;
263
272
  seq = null;
264
273
  }
@@ -124,6 +124,7 @@ export async function sendTextReply(chatId, text) {
124
124
  }
125
125
  catch (err) {
126
126
  log.error("Failed to send text:", err);
127
+ throw err;
127
128
  }
128
129
  }
129
130
  export async function sendImageReply(chatId, imagePath) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wu529778790/open-im",
3
- "version": "1.10.2-beta.6",
3
+ "version": "1.10.2",
4
4
  "description": "Multi-platform IM bridge for AI CLI tools (Claude, Codex, CodeBuddy)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",