@wu529778790/open-im 1.10.7-beta.2 → 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 会话已重置,下一条消息将使用全新上下文。`);
@@ -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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wu529778790/open-im",
3
- "version": "1.10.7-beta.2",
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",