@wu529778790/open-im 1.3.2-beta.1 → 1.3.2-beta.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/commands/handler.js +1 -1
- package/dist/feishu/event-handler.js +1 -1
- package/dist/index.js +2 -2
- package/dist/session/session-manager.d.ts +15 -6
- package/dist/session/session-manager.js +101 -35
- package/dist/shared/ai-task.js +10 -5
- package/dist/telegram/event-handler.js +1 -1
- package/dist/wechat/event-handler.js +1 -1
- package/dist/wework/event-handler.js +1 -1
- package/package.json +1 -1
package/dist/commands/handler.js
CHANGED
|
@@ -110,7 +110,7 @@ export class CommandHandler {
|
|
|
110
110
|
const version = await this.getAiVersion();
|
|
111
111
|
const workDir = this.deps.sessionManager.getWorkDir(userId);
|
|
112
112
|
const convId = this.deps.sessionManager.getConvId(userId);
|
|
113
|
-
const sessionId = this.deps.sessionManager.getSessionIdForConv(userId, convId);
|
|
113
|
+
const sessionId = this.deps.sessionManager.getSessionIdForConv(userId, convId, this.deps.config.aiCommand);
|
|
114
114
|
const lines = [
|
|
115
115
|
'📊 状态:',
|
|
116
116
|
'',
|
|
@@ -126,7 +126,7 @@ export function setupFeishuHandlers(config, sessionManager) {
|
|
|
126
126
|
return;
|
|
127
127
|
}
|
|
128
128
|
log.info(`[handleAIRequest] Adapter found, getting session...`);
|
|
129
|
-
const sessionId = convId ? sessionManager.getSessionIdForConv(userId, convId) : undefined;
|
|
129
|
+
const sessionId = convId ? sessionManager.getSessionIdForConv(userId, convId, config.aiCommand) : undefined;
|
|
130
130
|
log.info(`[handleAIRequest] Running ${config.aiCommand} for user ${userId}, sessionId=${sessionId ?? 'new'}`);
|
|
131
131
|
const toolId = config.aiCommand;
|
|
132
132
|
// 使用 CardKit 打字机效果(80ms 节流,约 12 次/秒,比 patch 5 QPS 更流畅)
|
package/dist/index.js
CHANGED
|
@@ -134,9 +134,9 @@ export async function main() {
|
|
|
134
134
|
log.info(`启用平台: ${config.enabledPlatforms.join(", ")}`);
|
|
135
135
|
const sessionManager = new SessionManager(config.claudeWorkDir, config.allowedBaseDirs);
|
|
136
136
|
// CLI 工具(Cursor/Codex)的 session 是进程级别的,服务重启后一定无效。
|
|
137
|
-
//
|
|
137
|
+
// 启动时仅清除 CLI 工具自己的 sessionId,保留 Claude 的持久上下文。
|
|
138
138
|
if (config.aiCommand !== 'claude') {
|
|
139
|
-
sessionManager.
|
|
139
|
+
sessionManager.clearAllCliSessionIds();
|
|
140
140
|
}
|
|
141
141
|
let telegramHandle = null;
|
|
142
142
|
let feishuHandle = null;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
type ToolId = 'claude' | 'codex' | 'cursor';
|
|
1
2
|
export declare class SessionManager {
|
|
2
3
|
private sessions;
|
|
3
4
|
private convSessionMap;
|
|
@@ -5,12 +6,12 @@ export declare class SessionManager {
|
|
|
5
6
|
private allowedBaseDirs;
|
|
6
7
|
private saveTimer;
|
|
7
8
|
constructor(defaultWorkDir: string, allowedBaseDirs: string[]);
|
|
8
|
-
getSessionIdForConv(userId: string, convId: string): string | undefined;
|
|
9
|
-
setSessionIdForConv(userId: string, convId: string, sessionId: string): void;
|
|
9
|
+
getSessionIdForConv(userId: string, convId: string, toolId: ToolId): string | undefined;
|
|
10
|
+
setSessionIdForConv(userId: string, convId: string, toolId: ToolId, sessionId: string): void;
|
|
10
11
|
/** 清除指定会话的 sessionId(用于 SDK 报 "No conversation found" 时) */
|
|
11
|
-
clearSessionForConv(userId: string, convId: string): void;
|
|
12
|
-
getSessionIdForThread(_userId: string, _threadId: string): string | undefined;
|
|
13
|
-
setSessionIdForThread(userId: string, threadId: string, sessionId: string): void;
|
|
12
|
+
clearSessionForConv(userId: string, convId: string, toolId: ToolId): void;
|
|
13
|
+
getSessionIdForThread(_userId: string, _threadId: string, _toolId: ToolId): string | undefined;
|
|
14
|
+
setSessionIdForThread(userId: string, threadId: string, toolId: ToolId, sessionId: string): void;
|
|
14
15
|
getWorkDir(userId: string): string;
|
|
15
16
|
hasUserSession(userId: string): boolean;
|
|
16
17
|
getConvId(userId: string): string;
|
|
@@ -20,8 +21,9 @@ export declare class SessionManager {
|
|
|
20
21
|
* 适用于 CLI 工具(Cursor/Codex),其 session 是进程级别的,
|
|
21
22
|
* 服务重启后旧的 session 一定无效,若不清除会导致 --resume 到中断任务。
|
|
22
23
|
*/
|
|
23
|
-
|
|
24
|
+
clearAllCliSessionIds(): void;
|
|
24
25
|
newSession(userId: string): boolean;
|
|
26
|
+
clearActiveToolSession(userId: string, toolId: ToolId): boolean;
|
|
25
27
|
addTurns(userId: string, turns: number): number;
|
|
26
28
|
addTurnsForThread(userId: string, threadId: string, turns: number): number;
|
|
27
29
|
getModel(userId: string, threadId?: string): string | undefined;
|
|
@@ -32,4 +34,11 @@ export declare class SessionManager {
|
|
|
32
34
|
private flushSync;
|
|
33
35
|
destroy(): void;
|
|
34
36
|
private doFlush;
|
|
37
|
+
private getConvSessionKey;
|
|
38
|
+
private getToolSessionId;
|
|
39
|
+
private setToolSessionId;
|
|
40
|
+
private clearToolSessionId;
|
|
41
|
+
private persistActiveConvSessions;
|
|
42
|
+
private clearConvSessionMappings;
|
|
35
43
|
}
|
|
44
|
+
export {};
|
|
@@ -17,42 +17,44 @@ export class SessionManager {
|
|
|
17
17
|
this.allowedBaseDirs = allowedBaseDirs;
|
|
18
18
|
this.load();
|
|
19
19
|
}
|
|
20
|
-
getSessionIdForConv(userId, convId) {
|
|
20
|
+
getSessionIdForConv(userId, convId, toolId) {
|
|
21
21
|
const s = this.sessions.get(userId);
|
|
22
22
|
if (s?.activeConvId === convId)
|
|
23
|
-
return s
|
|
24
|
-
return this.convSessionMap.get(
|
|
23
|
+
return this.getToolSessionId(s, toolId);
|
|
24
|
+
return this.convSessionMap.get(this.getConvSessionKey(userId, convId, toolId));
|
|
25
25
|
}
|
|
26
|
-
setSessionIdForConv(userId, convId, sessionId) {
|
|
26
|
+
setSessionIdForConv(userId, convId, toolId, sessionId) {
|
|
27
27
|
const s = this.sessions.get(userId);
|
|
28
28
|
if (s?.activeConvId === convId) {
|
|
29
|
-
s
|
|
29
|
+
this.setToolSessionId(s, toolId, sessionId);
|
|
30
30
|
this.save();
|
|
31
31
|
}
|
|
32
32
|
else {
|
|
33
|
-
this.convSessionMap.set(
|
|
33
|
+
this.convSessionMap.set(this.getConvSessionKey(userId, convId, toolId), sessionId);
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
/** 清除指定会话的 sessionId(用于 SDK 报 "No conversation found" 时) */
|
|
37
|
-
clearSessionForConv(userId, convId) {
|
|
37
|
+
clearSessionForConv(userId, convId, toolId) {
|
|
38
38
|
const s = this.sessions.get(userId);
|
|
39
39
|
if (s?.activeConvId === convId) {
|
|
40
|
-
s
|
|
40
|
+
this.clearToolSessionId(s, toolId);
|
|
41
41
|
this.save();
|
|
42
42
|
}
|
|
43
|
-
this.convSessionMap.delete(
|
|
44
|
-
log.info(`Cleared session for user ${userId}, convId=${convId}`);
|
|
43
|
+
this.convSessionMap.delete(this.getConvSessionKey(userId, convId, toolId));
|
|
44
|
+
log.info(`Cleared ${toolId} session for user ${userId}, convId=${convId}`);
|
|
45
45
|
}
|
|
46
|
-
getSessionIdForThread(_userId, _threadId) {
|
|
46
|
+
getSessionIdForThread(_userId, _threadId, _toolId) {
|
|
47
47
|
return undefined;
|
|
48
48
|
}
|
|
49
|
-
setSessionIdForThread(userId, threadId, sessionId) {
|
|
49
|
+
setSessionIdForThread(userId, threadId, toolId, sessionId) {
|
|
50
50
|
const s = this.sessions.get(userId);
|
|
51
51
|
if (s && !s.threads)
|
|
52
52
|
s.threads = {};
|
|
53
53
|
const t = s?.threads?.[threadId];
|
|
54
54
|
if (t) {
|
|
55
|
-
t.
|
|
55
|
+
if (!t.sessionIds)
|
|
56
|
+
t.sessionIds = {};
|
|
57
|
+
t.sessionIds[toolId] = sessionId;
|
|
56
58
|
this.save();
|
|
57
59
|
}
|
|
58
60
|
}
|
|
@@ -82,15 +84,12 @@ export class SessionManager {
|
|
|
82
84
|
const s = this.sessions.get(userId);
|
|
83
85
|
if (s) {
|
|
84
86
|
const oldConvId = s.activeConvId;
|
|
85
|
-
|
|
86
|
-
this.convSessionMap.set(`${userId}:${s.activeConvId}`, s.sessionId);
|
|
87
|
-
}
|
|
87
|
+
this.persistActiveConvSessions(userId, s);
|
|
88
88
|
s.workDir = realPath;
|
|
89
|
-
s.
|
|
89
|
+
s.sessionIds = {};
|
|
90
90
|
s.activeConvId = randomBytes(4).toString('hex');
|
|
91
|
-
// 清除旧的 convSessionMap 中的映射
|
|
92
91
|
if (oldConvId) {
|
|
93
|
-
this.
|
|
92
|
+
this.clearConvSessionMappings(userId, oldConvId);
|
|
94
93
|
}
|
|
95
94
|
}
|
|
96
95
|
else {
|
|
@@ -108,48 +107,69 @@ export class SessionManager {
|
|
|
108
107
|
* 适用于 CLI 工具(Cursor/Codex),其 session 是进程级别的,
|
|
109
108
|
* 服务重启后旧的 session 一定无效,若不清除会导致 --resume 到中断任务。
|
|
110
109
|
*/
|
|
111
|
-
|
|
110
|
+
clearAllCliSessionIds() {
|
|
112
111
|
let changed = false;
|
|
113
112
|
for (const [, s] of this.sessions) {
|
|
114
|
-
|
|
115
|
-
s
|
|
116
|
-
|
|
113
|
+
for (const toolId of ['codex', 'cursor']) {
|
|
114
|
+
if (this.getToolSessionId(s, toolId) !== undefined) {
|
|
115
|
+
this.clearToolSessionId(s, toolId);
|
|
116
|
+
changed = true;
|
|
117
|
+
}
|
|
117
118
|
}
|
|
118
119
|
if (s.threads) {
|
|
119
120
|
for (const t of Object.values(s.threads)) {
|
|
120
|
-
|
|
121
|
-
t.
|
|
122
|
-
|
|
121
|
+
for (const toolId of ['codex', 'cursor']) {
|
|
122
|
+
if (t.sessionIds?.[toolId] !== undefined) {
|
|
123
|
+
delete t.sessionIds[toolId];
|
|
124
|
+
changed = true;
|
|
125
|
+
}
|
|
123
126
|
}
|
|
124
127
|
}
|
|
125
128
|
}
|
|
126
129
|
}
|
|
130
|
+
for (const key of [...this.convSessionMap.keys()]) {
|
|
131
|
+
if (key.endsWith(':codex') || key.endsWith(':cursor')) {
|
|
132
|
+
this.convSessionMap.delete(key);
|
|
133
|
+
changed = true;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
127
136
|
if (changed) {
|
|
128
137
|
this.flushSync();
|
|
129
|
-
log.info('Cleared
|
|
138
|
+
log.info('Cleared CLI session IDs for codex/cursor on startup');
|
|
130
139
|
}
|
|
131
140
|
}
|
|
132
141
|
newSession(userId) {
|
|
133
142
|
const s = this.sessions.get(userId);
|
|
134
143
|
if (s) {
|
|
135
|
-
const
|
|
144
|
+
const oldSessionIds = { ...(s.sessionIds ?? {}) };
|
|
136
145
|
const oldConvId = s.activeConvId;
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
}
|
|
140
|
-
s.sessionId = undefined;
|
|
146
|
+
this.persistActiveConvSessions(userId, s);
|
|
147
|
+
s.sessionIds = {};
|
|
141
148
|
s.activeConvId = randomBytes(4).toString('hex');
|
|
142
149
|
s.totalTurns = 0;
|
|
143
|
-
// 清除旧的 convSessionMap 中的映射,防止恢复旧的 sessionId
|
|
144
150
|
if (oldConvId) {
|
|
145
|
-
this.
|
|
151
|
+
this.clearConvSessionMappings(userId, oldConvId);
|
|
146
152
|
}
|
|
147
153
|
this.flushSync();
|
|
148
|
-
log.info(`New session for user ${userId}: oldConvId=${oldConvId},
|
|
154
|
+
log.info(`New session for user ${userId}: oldConvId=${oldConvId}, oldSessionIds=${JSON.stringify(oldSessionIds)}, newConvId=${s.activeConvId}, sessionIds={}`);
|
|
149
155
|
return true;
|
|
150
156
|
}
|
|
151
157
|
return false;
|
|
152
158
|
}
|
|
159
|
+
clearActiveToolSession(userId, toolId) {
|
|
160
|
+
const s = this.sessions.get(userId);
|
|
161
|
+
if (!s)
|
|
162
|
+
return false;
|
|
163
|
+
const activeConvId = s.activeConvId;
|
|
164
|
+
const hadSession = this.getToolSessionId(s, toolId) !== undefined;
|
|
165
|
+
this.clearToolSessionId(s, toolId);
|
|
166
|
+
if (activeConvId) {
|
|
167
|
+
this.convSessionMap.delete(this.getConvSessionKey(userId, activeConvId, toolId));
|
|
168
|
+
}
|
|
169
|
+
this.flushSync();
|
|
170
|
+
log.info(`Cleared active ${toolId} session for user ${userId}, convId=${activeConvId ?? 'none'}`);
|
|
171
|
+
return hadSession;
|
|
172
|
+
}
|
|
153
173
|
addTurns(userId, turns) {
|
|
154
174
|
const s = this.sessions.get(userId);
|
|
155
175
|
if (!s)
|
|
@@ -243,6 +263,22 @@ export class SessionManager {
|
|
|
243
263
|
if (v && typeof v.workDir === 'string') {
|
|
244
264
|
if (!v.activeConvId)
|
|
245
265
|
v.activeConvId = randomBytes(4).toString('hex');
|
|
266
|
+
if (!v.sessionIds)
|
|
267
|
+
v.sessionIds = {};
|
|
268
|
+
if ('sessionId' in v) {
|
|
269
|
+
log.warn(`Legacy shared sessionId found for user ${k}; clearing it to avoid cross-tool resume conflicts`);
|
|
270
|
+
}
|
|
271
|
+
delete v.sessionId;
|
|
272
|
+
if (v.threads) {
|
|
273
|
+
for (const thread of Object.values(v.threads)) {
|
|
274
|
+
if (!thread.sessionIds)
|
|
275
|
+
thread.sessionIds = {};
|
|
276
|
+
if ('sessionId' in thread) {
|
|
277
|
+
log.warn(`Legacy thread sessionId found for user ${k}; clearing it during session migration`);
|
|
278
|
+
}
|
|
279
|
+
delete thread.sessionId;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
246
282
|
this.sessions.set(k, v);
|
|
247
283
|
}
|
|
248
284
|
}
|
|
@@ -288,4 +324,34 @@ export class SessionManager {
|
|
|
288
324
|
log.error('Failed to save sessions:', err);
|
|
289
325
|
}
|
|
290
326
|
}
|
|
327
|
+
getConvSessionKey(userId, convId, toolId) {
|
|
328
|
+
return `${userId}:${convId}:${toolId}`;
|
|
329
|
+
}
|
|
330
|
+
getToolSessionId(session, toolId) {
|
|
331
|
+
return session.sessionIds?.[toolId];
|
|
332
|
+
}
|
|
333
|
+
setToolSessionId(session, toolId, sessionId) {
|
|
334
|
+
if (!session.sessionIds)
|
|
335
|
+
session.sessionIds = {};
|
|
336
|
+
session.sessionIds[toolId] = sessionId;
|
|
337
|
+
}
|
|
338
|
+
clearToolSessionId(session, toolId) {
|
|
339
|
+
if (!session.sessionIds)
|
|
340
|
+
return;
|
|
341
|
+
delete session.sessionIds[toolId];
|
|
342
|
+
}
|
|
343
|
+
persistActiveConvSessions(userId, session) {
|
|
344
|
+
if (!session.activeConvId || !session.sessionIds)
|
|
345
|
+
return;
|
|
346
|
+
for (const [toolId, sessionId] of Object.entries(session.sessionIds)) {
|
|
347
|
+
if (sessionId) {
|
|
348
|
+
this.convSessionMap.set(this.getConvSessionKey(userId, session.activeConvId, toolId), sessionId);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
clearConvSessionMappings(userId, convId) {
|
|
353
|
+
for (const toolId of ['claude', 'codex', 'cursor']) {
|
|
354
|
+
this.convSessionMap.delete(this.getConvSessionKey(userId, convId, toolId));
|
|
355
|
+
}
|
|
356
|
+
}
|
|
291
357
|
}
|
package/dist/shared/ai-task.js
CHANGED
|
@@ -103,13 +103,13 @@ export function runAITask(deps, ctx, prompt, toolAdapter, platformAdapter) {
|
|
|
103
103
|
const handle = toolAdapter.run(prompt, ctx.sessionId, ctx.workDir, {
|
|
104
104
|
onSessionId: (id) => {
|
|
105
105
|
if (ctx.threadId)
|
|
106
|
-
sessionManager.setSessionIdForThread(ctx.userId, ctx.threadId, id);
|
|
106
|
+
sessionManager.setSessionIdForThread(ctx.userId, ctx.threadId, config.aiCommand, id);
|
|
107
107
|
else if (ctx.convId)
|
|
108
|
-
sessionManager.setSessionIdForConv(ctx.userId, ctx.convId, id);
|
|
108
|
+
sessionManager.setSessionIdForConv(ctx.userId, ctx.convId, config.aiCommand, id);
|
|
109
109
|
},
|
|
110
110
|
onSessionInvalid: () => {
|
|
111
111
|
if (ctx.convId)
|
|
112
|
-
sessionManager.clearSessionForConv(ctx.userId, ctx.convId);
|
|
112
|
+
sessionManager.clearSessionForConv(ctx.userId, ctx.convId, config.aiCommand);
|
|
113
113
|
},
|
|
114
114
|
onThinking: (t) => {
|
|
115
115
|
if (!firstContentLogged) {
|
|
@@ -186,9 +186,14 @@ export function runAITask(deps, ctx, prompt, toolAdapter, platformAdapter) {
|
|
|
186
186
|
log.error(`Task error for user ${ctx.userId}: ${error}`);
|
|
187
187
|
// CLI 工具(cursor/codex)出错时清除 sessionId,
|
|
188
188
|
// 防止 resume 到中途被中断的会话(如未完成的 tool call 循环)
|
|
189
|
-
// Claude SDK 不需要,其 session
|
|
189
|
+
// Claude SDK 不需要,其 session 是对话上下文,出错仍可继续。
|
|
190
|
+
// 这里只清当前任务所属会话里的当前工具 session,避免误伤其它 AI 工具
|
|
191
|
+
// 或用户在 /new、/cd 后已经切出的新会话。
|
|
190
192
|
if (config.aiCommand !== 'claude') {
|
|
191
|
-
|
|
193
|
+
if (ctx.convId)
|
|
194
|
+
sessionManager.clearSessionForConv(ctx.userId, ctx.convId, config.aiCommand);
|
|
195
|
+
else
|
|
196
|
+
sessionManager.clearActiveToolSession(ctx.userId, config.aiCommand);
|
|
192
197
|
log.info(`Session reset for user ${ctx.userId} due to ${config.aiCommand} task error`);
|
|
193
198
|
}
|
|
194
199
|
try {
|
|
@@ -91,7 +91,7 @@ export function setupTelegramHandlers(bot, config, sessionManager) {
|
|
|
91
91
|
return;
|
|
92
92
|
}
|
|
93
93
|
const sessionId = convId
|
|
94
|
-
? sessionManager.getSessionIdForConv(userId, convId)
|
|
94
|
+
? sessionManager.getSessionIdForConv(userId, convId, config.aiCommand)
|
|
95
95
|
: undefined;
|
|
96
96
|
log.info(`Running ${config.aiCommand} for user ${userId}, sessionId=${sessionId ?? "new"}`);
|
|
97
97
|
const toolId = config.aiCommand;
|
|
@@ -37,7 +37,7 @@ export function setupWeChatHandlers(config, sessionManager) {
|
|
|
37
37
|
await sendTextReply(chatId, `未配置 AI 工具: ${config.aiCommand}`);
|
|
38
38
|
return;
|
|
39
39
|
}
|
|
40
|
-
const sessionId = convId ? sessionManager.getSessionIdForConv(userId, convId) : undefined;
|
|
40
|
+
const sessionId = convId ? sessionManager.getSessionIdForConv(userId, convId, config.aiCommand) : undefined;
|
|
41
41
|
log.info(`[handleAIRequest] Running ${config.aiCommand} for user ${userId}, sessionId=${sessionId ?? 'new'}`);
|
|
42
42
|
const toolId = config.aiCommand;
|
|
43
43
|
let msgId;
|
|
@@ -38,7 +38,7 @@ export function setupWeWorkHandlers(config, sessionManager) {
|
|
|
38
38
|
await sendTextReply(chatId, `未配置 AI 工具: ${config.aiCommand}`, reqId);
|
|
39
39
|
return;
|
|
40
40
|
}
|
|
41
|
-
const sessionId = convId ? sessionManager.getSessionIdForConv(userId, convId) : undefined;
|
|
41
|
+
const sessionId = convId ? sessionManager.getSessionIdForConv(userId, convId, config.aiCommand) : undefined;
|
|
42
42
|
log.info(`[handleAIRequest] Running ${config.aiCommand} for user ${userId}, sessionId=${sessionId ?? 'new'}`);
|
|
43
43
|
const toolId = config.aiCommand;
|
|
44
44
|
let msgId;
|