@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.
- package/dist/adapters/claude-sdk-adapter.d.ts +4 -0
- package/dist/adapters/claude-sdk-adapter.js +18 -2
- package/dist/adapters/registry.js +2 -1
- package/dist/config/types.d.ts +6 -0
- package/dist/config-web.js +1 -0
- package/dist/config.js +14 -0
- package/dist/index.js +2 -5
- package/dist/qq/client.js +11 -2
- package/dist/telegram/message-sender.js +1 -0
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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 >
|
|
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
|
}
|
package/dist/config/types.d.ts
CHANGED
|
@@ -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) */
|
package/dist/config-web.js
CHANGED
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)
|
|
108
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|