@wu529778790/open-im 1.10.7-beta.2 → 1.10.7-beta.4
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 +3 -0
- package/dist/constants.d.ts +2 -0
- package/dist/constants.js +2 -0
- package/dist/queue/request-queue.d.ts +16 -3
- package/dist/queue/request-queue.js +41 -7
- package/dist/workbuddy/message-sender.d.ts +1 -1
- package/dist/workbuddy/message-sender.js +30 -9
- package/package.json +1 -1
package/dist/commands/handler.js
CHANGED
|
@@ -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/constants.d.ts
CHANGED
|
@@ -36,3 +36,5 @@ export declare const MAX_FEISHU_MESSAGE_LENGTH = 4000;
|
|
|
36
36
|
export declare const MAX_STREAMING_CONTENT_LENGTH = 25000;
|
|
37
37
|
export declare const MAX_WEWORK_MESSAGE_LENGTH = 2048;
|
|
38
38
|
export declare const MAX_DINGTALK_MESSAGE_LENGTH = 2048;
|
|
39
|
+
/** WeChat KF (微信客服) 单条消息最大字符数 */
|
|
40
|
+
export declare const MAX_WORKBUDDY_MESSAGE_LENGTH = 2000;
|
package/dist/constants.js
CHANGED
|
@@ -66,3 +66,5 @@ export const MAX_FEISHU_MESSAGE_LENGTH = 4000;
|
|
|
66
66
|
export const MAX_STREAMING_CONTENT_LENGTH = 25000;
|
|
67
67
|
export const MAX_WEWORK_MESSAGE_LENGTH = 2048;
|
|
68
68
|
export const MAX_DINGTALK_MESSAGE_LENGTH = 2048;
|
|
69
|
+
/** WeChat KF (微信客服) 单条消息最大字符数 */
|
|
70
|
+
export const MAX_WORKBUDDY_MESSAGE_LENGTH = 2000;
|
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
export type EnqueueResult = 'running' | 'queued' | 'rejected';
|
|
2
2
|
export declare class RequestQueue {
|
|
3
3
|
private queues;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
7
|
-
|
|
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
|
-
|
|
28
|
-
|
|
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
|
-
|
|
45
|
-
|
|
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;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import type { WorkBuddyCentrifugeClient } from './centrifuge-client.js';
|
|
5
5
|
/**
|
|
6
|
-
* Send text reply to WeChat KF
|
|
6
|
+
* Send text reply to WeChat KF, splitting long messages automatically.
|
|
7
7
|
*/
|
|
8
8
|
export declare function sendTextReply(_client: WorkBuddyCentrifugeClient | null, chatId: string, text: string, msgId: string): Promise<void>;
|
|
9
9
|
/**
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* WorkBuddy Message Sender - Send responses to WeChat KF
|
|
3
3
|
*/
|
|
4
|
+
import { randomUUID } from 'node:crypto';
|
|
4
5
|
import { createLogger } from '../logger.js';
|
|
5
|
-
import { toReplyPlainText } from '../shared/utils.js';
|
|
6
|
+
import { splitLongContent, toReplyPlainText } from '../shared/utils.js';
|
|
7
|
+
import { MAX_WORKBUDDY_MESSAGE_LENGTH } from '../constants.js';
|
|
6
8
|
import { getCentrifugeClient } from './client.js';
|
|
7
9
|
const log = createLogger('WorkBuddySender');
|
|
8
10
|
/**
|
|
9
|
-
* Send text reply to WeChat KF
|
|
11
|
+
* Send text reply to WeChat KF, splitting long messages automatically.
|
|
10
12
|
*/
|
|
11
13
|
export async function sendTextReply(_client, chatId, text, msgId) {
|
|
12
14
|
const client = _client ?? getCentrifugeClient();
|
|
@@ -14,13 +16,32 @@ export async function sendTextReply(_client, chatId, text, msgId) {
|
|
|
14
16
|
log.warn('WorkBuddy client not available, cannot send reply');
|
|
15
17
|
return;
|
|
16
18
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
const plainText = toReplyPlainText(text);
|
|
20
|
+
const parts = splitLongContent(plainText, MAX_WORKBUDDY_MESSAGE_LENGTH);
|
|
21
|
+
if (parts.length === 1) {
|
|
22
|
+
log.info(`Sending WorkBuddy reply to chatId=${chatId}, msgId=${msgId}, len=${plainText.length}`);
|
|
23
|
+
await client.sendPromptResponse({
|
|
24
|
+
session_id: chatId,
|
|
25
|
+
prompt_id: msgId,
|
|
26
|
+
content: [{ type: 'text', text: plainText }],
|
|
27
|
+
stop_reason: 'end_turn',
|
|
28
|
+
});
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
log.info(`Sending WorkBuddy reply in ${parts.length} parts to chatId=${chatId}, msgId=${msgId}, totalLen=${plainText.length}`);
|
|
32
|
+
for (let i = 0; i < parts.length; i++) {
|
|
33
|
+
const partText = i === 0
|
|
34
|
+
? `${parts[i]}\n\n_(1/${parts.length})_`
|
|
35
|
+
: `_(续 ${i + 1}/${parts.length})_\n\n${parts[i]}`;
|
|
36
|
+
const partMsgId = i === 0 ? msgId : randomUUID();
|
|
37
|
+
await client.sendPromptResponse({
|
|
38
|
+
session_id: chatId,
|
|
39
|
+
prompt_id: partMsgId,
|
|
40
|
+
content: [{ type: 'text', text: partText }],
|
|
41
|
+
stop_reason: 'end_turn',
|
|
42
|
+
});
|
|
43
|
+
log.info(`WorkBuddy part ${i + 1}/${parts.length} sent, msgId=${partMsgId}`);
|
|
44
|
+
}
|
|
24
45
|
}
|
|
25
46
|
/**
|
|
26
47
|
* Send error response to WeChat KF
|