@wu529778790/open-im 1.10.7-beta.1 → 1.10.7-beta.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.
@@ -123,6 +123,7 @@ export class CommandHandler {
123
123
  return true;
124
124
  }
125
125
  const entry = history[index - 1];
126
+ this.deps.requestQueue.cancelUser(userId);
126
127
  const ok = this.deps.sessionManager.resumeConv(userId, entry.convId);
127
128
  if (ok) {
128
129
  await this.replySender().sendTextReply(chatId, `✅ 已恢复会话 ${index} (${entry.convId}),共 ${entry.totalTurns}轮对话。\n继续发消息即可。`);
@@ -133,6 +134,7 @@ export class CommandHandler {
133
134
  return true;
134
135
  }
135
136
  async handleNew(chatId, userId) {
137
+ this.deps.requestQueue.cancelUser(userId);
136
138
  const ok = this.deps.sessionManager.newSession(userId);
137
139
  await this.replySender().sendTextReply(chatId, ok
138
140
  ? '✅ AI 会话已重置,下一条消息将使用全新上下文。'
@@ -175,6 +177,7 @@ export class CommandHandler {
175
177
  return true;
176
178
  }
177
179
  try {
180
+ this.deps.requestQueue.cancelUser(userId);
178
181
  const resolved = await this.deps.sessionManager.setWorkDir(userId, dir);
179
182
  await this.replySender().sendTextReply(chatId, `📁 工作目录已切换到: ${escapePathForMarkdown(resolved)}\n\n` +
180
183
  `🔄 AI 会话已重置,下一条消息将使用全新上下文。`);
package/dist/index.js CHANGED
@@ -214,9 +214,8 @@ export async function main() {
214
214
  }
215
215
  log.info(`启用平台: ${config.enabledPlatforms.join(", ")}`);
216
216
  const sessionManager = new SessionManager(startupCwd, config.claudeWorkDir);
217
- // CLI 工具(Codex/CodeBuddy)的 session 是进程级别的,服务重启后一定无效。
218
- // 启动时仅清除 CLI 工具自己的 sessionId,保留 Claude 的持久上下文。
219
- sessionManager.clearAllCliSessionIds();
217
+ // Codex/CodeBuddy sessionId 持久化在 sessions.json;重启后沿用以便 --resume 续聊。
218
+ // CLI 判定会话无效,ai-task 会通过 onSessionInvalid 清理并提示重试。
220
219
  // Track active platform handles and successfully initialized platforms
221
220
  const activeHandles = new Map();
222
221
  const successfulPlatforms = [];
@@ -1,8 +1,21 @@
1
1
  export type EnqueueResult = 'running' | 'queued' | 'rejected';
2
2
  export declare class RequestQueue {
3
3
  private queues;
4
- enqueue(userId: string, convId: string, prompt: string, execute: (prompt: string, signal: AbortSignal) => Promise<void>): EnqueueResult;
5
- /** 清除指定用户会话的所有排队任务(不中止正在运行的任务) */
6
- clear(userId: string, convId: string): number;
4
+ /** AbortController for the task currently running per user (key = userId). */
5
+ private runningControllers;
6
+ /**
7
+ * Enqueue an AI task. Tasks are serialized per `userId` only — `convId` is ignored for queue
8
+ * partitioning so that `/new` and `/cd` (which change `convId`) cannot leave a long-running
9
+ * request on the old conversation executing in parallel with new messages.
10
+ */
11
+ enqueue(userId: string, _convId: string, prompt: string, execute: (prompt: string, signal: AbortSignal) => Promise<void>): EnqueueResult;
12
+ /**
13
+ * Abort the running task for this user (if any) and drop all queued tasks.
14
+ * Used when `/new`, `/cd`, or `/resume` changes conversation state so stale completions
15
+ * are not delivered to the wrong WeChat/Telegram message.
16
+ */
17
+ cancelUser(userId: string): void;
18
+ /** 清除指定用户的所有排队任务(不中止正在运行的任务)。`convId` 仅保留兼容签名。 */
19
+ clear(userId: string, _convId: string): number;
7
20
  private run;
8
21
  }
@@ -3,8 +3,15 @@ const log = createLogger('Queue');
3
3
  const MAX_QUEUE_SIZE = 3;
4
4
  export class RequestQueue {
5
5
  queues = new Map();
6
- enqueue(userId, convId, prompt, execute) {
7
- const key = `${userId}:${convId}`;
6
+ /** AbortController for the task currently running per user (key = userId). */
7
+ runningControllers = new Map();
8
+ /**
9
+ * Enqueue an AI task. Tasks are serialized per `userId` only — `convId` is ignored for queue
10
+ * partitioning so that `/new` and `/cd` (which change `convId`) cannot leave a long-running
11
+ * request on the old conversation executing in parallel with new messages.
12
+ */
13
+ enqueue(userId, _convId, prompt, execute) {
14
+ const key = userId;
8
15
  let q = this.queues.get(key);
9
16
  if (!q) {
10
17
  q = { running: false, tasks: [] };
@@ -23,9 +30,28 @@ export class RequestQueue {
23
30
  });
24
31
  return 'running';
25
32
  }
26
- /** 清除指定用户会话的所有排队任务(不中止正在运行的任务) */
27
- clear(userId, convId) {
28
- const key = `${userId}:${convId}`;
33
+ /**
34
+ * Abort the running task for this user (if any) and drop all queued tasks.
35
+ * Used when `/new`, `/cd`, or `/resume` changes conversation state so stale completions
36
+ * are not delivered to the wrong WeChat/Telegram message.
37
+ */
38
+ cancelUser(userId) {
39
+ const key = userId;
40
+ const c = this.runningControllers.get(key);
41
+ if (c) {
42
+ c.abort();
43
+ }
44
+ const q = this.queues.get(key);
45
+ if (!q)
46
+ return;
47
+ const cleared = q.tasks.length;
48
+ q.tasks.length = 0;
49
+ if (cleared > 0)
50
+ log.info(`cancelUser: dropped ${cleared} queued task(s) for ${key}`);
51
+ }
52
+ /** 清除指定用户的所有排队任务(不中止正在运行的任务)。`convId` 仅保留兼容签名。 */
53
+ clear(userId, _convId) {
54
+ const key = userId;
29
55
  const q = this.queues.get(key);
30
56
  if (!q)
31
57
  return 0;
@@ -37,14 +63,22 @@ export class RequestQueue {
37
63
  }
38
64
  async run(key, prompt, execute) {
39
65
  const controller = new AbortController();
66
+ this.runningControllers.set(key, controller);
40
67
  try {
41
68
  await execute(prompt, controller.signal);
42
69
  }
43
70
  catch (err) {
44
- log.error(`Error executing task for ${key}:`, err);
45
- throw err;
71
+ const aborted = err instanceof Error && err.name === 'AbortError';
72
+ if (aborted) {
73
+ log.debug(`Task aborted for ${key}`);
74
+ }
75
+ else {
76
+ log.error(`Error executing task for ${key}:`, err);
77
+ throw err;
78
+ }
46
79
  }
47
80
  finally {
81
+ this.runningControllers.delete(key);
48
82
  const q = this.queues.get(key);
49
83
  if (!q)
50
84
  return;
@@ -25,11 +25,6 @@ export declare class SessionManager {
25
25
  hasUserSession(userId: string): boolean;
26
26
  getConvId(userId: string): string;
27
27
  setWorkDir(userId: string, workDir: string): Promise<string>;
28
- /**
29
- * 服务启动时调用:清除所有用户的 CLI sessionId。
30
- * Codex/CodeBuddy 的 session 是进程级别的,服务重启后旧 session 一定无效。
31
- */
32
- clearAllCliSessionIds(): void;
33
28
  newSession(userId: string): boolean;
34
29
  clearActiveToolSession(userId: string, toolId: ToolId): boolean;
35
30
  listConvHistory(userId: string): ConvHistoryEntry[];
@@ -136,41 +136,6 @@ export class SessionManager {
136
136
  log.info(`WorkDir changed for user ${userId}: ${realPath}, oldConvId=${oldConvId}`);
137
137
  return realPath;
138
138
  }
139
- /**
140
- * 服务启动时调用:清除所有用户的 CLI sessionId。
141
- * Codex/CodeBuddy 的 session 是进程级别的,服务重启后旧 session 一定无效。
142
- */
143
- clearAllCliSessionIds() {
144
- let changed = false;
145
- for (const [, s] of this.sessions) {
146
- for (const toolId of ['codex', 'codebuddy']) {
147
- if (this.getToolSessionId(s, toolId) !== undefined) {
148
- this.clearToolSessionId(s, toolId);
149
- changed = true;
150
- }
151
- }
152
- if (s.threads) {
153
- for (const t of Object.values(s.threads)) {
154
- for (const toolId of ['codex', 'codebuddy']) {
155
- if (t.sessionIds?.[toolId] !== undefined) {
156
- delete t.sessionIds[toolId];
157
- changed = true;
158
- }
159
- }
160
- }
161
- }
162
- }
163
- for (const key of [...this.convSessionMap.keys()]) {
164
- if (key.endsWith(':codex') || key.endsWith(':codebuddy')) {
165
- this.convSessionMap.delete(key);
166
- changed = true;
167
- }
168
- }
169
- if (changed) {
170
- this.flushSync();
171
- log.info('Cleared CLI session IDs for codex/codebuddy on startup');
172
- }
173
- }
174
139
  newSession(userId) {
175
140
  const s = this.sessions.get(userId);
176
141
  if (s) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wu529778790/open-im",
3
- "version": "1.10.7-beta.1",
3
+ "version": "1.10.7-beta.3",
4
4
  "description": "Multi-platform IM bridge for AI CLI tools (Claude, Codex, CodeBuddy)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",