@wecom/wecom-openclaw-plugin 2026.4.2 → 2026.4.3-beta.0
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/README.md +395 -10
- package/dist/index.d.ts +2 -4
- package/dist/index.js +58 -0
- package/dist/src/accounts.d.ts +57 -0
- package/dist/src/accounts.js +222 -0
- package/dist/src/agent/api-client.d.ts +94 -0
- package/dist/src/agent/api-client.js +292 -0
- package/dist/src/agent/handler.d.ts +63 -0
- package/dist/src/agent/handler.js +551 -0
- package/dist/src/agent/index.d.ts +5 -0
- package/dist/src/agent/index.js +5 -0
- package/dist/src/agent/webhook.d.ts +24 -0
- package/dist/src/agent/webhook.js +228 -0
- package/dist/src/agent/xml.d.ts +21 -0
- package/dist/src/agent/xml.js +41 -0
- package/dist/src/channel.js +579 -0
- package/dist/src/chat-queue.d.ts +31 -0
- package/dist/src/chat-queue.js +45 -0
- package/dist/{esm/types/src → src}/const.d.ts +46 -0
- package/dist/{esm/src → src}/const.js +77 -30
- package/dist/{esm/src → src}/dm-policy.js +6 -9
- package/dist/src/dynamic-agent.d.ts +37 -0
- package/dist/src/dynamic-agent.js +60 -0
- package/dist/src/dynamic-routing.d.ts +65 -0
- package/dist/src/dynamic-routing.js +58 -0
- package/dist/{esm/src → src}/group-policy.js +8 -22
- package/dist/src/http.d.ts +26 -0
- package/dist/src/http.js +93 -0
- package/dist/{esm/types/src → src}/interface.d.ts +0 -2
- package/dist/src/interface.js +4 -0
- package/dist/src/mcp/index.js +6 -0
- package/dist/{esm/src → src}/mcp/interceptors/biz-error.js +2 -6
- package/dist/{esm/src → src}/mcp/interceptors/index.js +6 -10
- package/dist/{esm/src → src}/mcp/interceptors/msg-media.js +6 -10
- package/dist/{esm/src → src}/mcp/interceptors/smartpage-create.js +2 -6
- package/dist/{esm/src → src}/mcp/interceptors/smartpage-export.js +2 -6
- package/dist/src/mcp/interceptors/types.js +7 -0
- package/dist/{esm/src → src}/mcp/schema.js +1 -4
- package/dist/{esm/src → src}/mcp/tool.js +4 -8
- package/dist/{esm/src → src}/mcp/transport.js +15 -16
- package/dist/{esm/src → src}/media-handler.js +6 -10
- package/dist/{esm/src → src}/media-uploader.js +64 -10
- package/dist/{esm/src → src}/message-parser.js +1 -4
- package/dist/{esm/types/src → src}/message-sender.d.ts +18 -0
- package/dist/{esm/src → src}/message-sender.js +45 -11
- package/dist/{esm/src → src}/monitor.js +145 -292
- package/dist/{esm/src → src}/onboarding.js +22 -26
- package/dist/{esm/types/src → src}/openclaw-compat.d.ts +49 -0
- package/dist/{esm/src → src}/openclaw-compat.js +133 -27
- package/dist/{esm/src → src}/reqid-store.js +4 -7
- package/dist/{esm/src → src}/runtime.js +2 -5
- package/dist/src/shared/command-auth.d.ts +23 -0
- package/dist/src/shared/command-auth.js +73 -0
- package/dist/src/shared/xml-parser.d.ts +46 -0
- package/dist/src/shared/xml-parser.js +210 -0
- package/dist/{esm/src → src}/state-dir-resolve.js +3 -7
- package/dist/{esm/src → src}/state-manager.js +84 -16
- package/dist/src/target.d.ts +35 -0
- package/dist/src/target.js +67 -0
- package/dist/src/template-card-manager.d.ts +55 -0
- package/dist/src/template-card-manager.js +205 -0
- package/dist/{esm/src → src}/template-card-parser.js +17 -8
- package/dist/{esm/src → src}/timeout.js +2 -5
- package/dist/src/types/account.d.ts +29 -0
- package/dist/src/types/account.js +4 -0
- package/dist/src/types/config.d.ts +91 -0
- package/dist/src/types/config.js +7 -0
- package/dist/src/types/constants.d.ts +42 -0
- package/dist/src/types/constants.js +42 -0
- package/dist/src/types/index.d.ts +7 -0
- package/dist/src/types/index.js +5 -0
- package/dist/src/types/message.d.ts +238 -0
- package/dist/src/types/message.js +5 -0
- package/dist/{esm/types/src → src}/utils.d.ts +41 -0
- package/dist/{esm/src → src}/utils.js +24 -10
- package/dist/src/version.d.ts +2 -0
- package/dist/src/version.js +24 -0
- package/dist/src/webhook/command-auth.d.ts +47 -0
- package/dist/src/webhook/command-auth.js +96 -0
- package/dist/src/webhook/gateway.d.ts +36 -0
- package/dist/src/webhook/gateway.js +235 -0
- package/dist/src/webhook/handler.d.ts +18 -0
- package/dist/src/webhook/handler.js +420 -0
- package/dist/src/webhook/helpers.d.ts +156 -0
- package/dist/src/webhook/helpers.js +833 -0
- package/dist/src/webhook/http.d.ts +26 -0
- package/dist/src/webhook/http.js +94 -0
- package/dist/src/webhook/index.d.ts +11 -0
- package/dist/src/webhook/index.js +14 -0
- package/dist/src/webhook/media.d.ts +29 -0
- package/dist/src/webhook/media.js +88 -0
- package/dist/src/webhook/monitor.d.ts +59 -0
- package/dist/src/webhook/monitor.js +1161 -0
- package/dist/src/webhook/state.d.ts +220 -0
- package/dist/src/webhook/state.js +491 -0
- package/dist/src/webhook/target.d.ts +41 -0
- package/dist/src/webhook/target.js +132 -0
- package/dist/src/webhook/types.d.ts +347 -0
- package/dist/src/webhook/types.js +33 -0
- package/dist/src/webhook/video-frame.d.ts +13 -0
- package/dist/src/webhook/video-frame.js +30 -0
- package/package.json +15 -7
- package/dist/cjs/index.js +0 -45
- package/dist/cjs/index.js.map +0 -1
- package/dist/cjs/src/channel.js +0 -339
- package/dist/cjs/src/channel.js.map +0 -1
- package/dist/cjs/src/const.js +0 -137
- package/dist/cjs/src/const.js.map +0 -1
- package/dist/cjs/src/dm-policy.js +0 -89
- package/dist/cjs/src/dm-policy.js.map +0 -1
- package/dist/cjs/src/group-policy.js +0 -133
- package/dist/cjs/src/group-policy.js.map +0 -1
- package/dist/cjs/src/mcp/interceptors/biz-error.js +0 -70
- package/dist/cjs/src/mcp/interceptors/biz-error.js.map +0 -1
- package/dist/cjs/src/mcp/interceptors/index.js +0 -73
- package/dist/cjs/src/mcp/interceptors/index.js.map +0 -1
- package/dist/cjs/src/mcp/interceptors/msg-media.js +0 -159
- package/dist/cjs/src/mcp/interceptors/msg-media.js.map +0 -1
- package/dist/cjs/src/mcp/interceptors/smartpage-create.js +0 -157
- package/dist/cjs/src/mcp/interceptors/smartpage-create.js.map +0 -1
- package/dist/cjs/src/mcp/interceptors/smartpage-export.js +0 -93
- package/dist/cjs/src/mcp/interceptors/smartpage-export.js.map +0 -1
- package/dist/cjs/src/mcp/schema.js +0 -104
- package/dist/cjs/src/mcp/schema.js.map +0 -1
- package/dist/cjs/src/mcp/tool.js +0 -208
- package/dist/cjs/src/mcp/tool.js.map +0 -1
- package/dist/cjs/src/mcp/transport.js +0 -417
- package/dist/cjs/src/mcp/transport.js.map +0 -1
- package/dist/cjs/src/media-handler.js +0 -129
- package/dist/cjs/src/media-handler.js.map +0 -1
- package/dist/cjs/src/media-uploader.js +0 -284
- package/dist/cjs/src/media-uploader.js.map +0 -1
- package/dist/cjs/src/message-parser.js +0 -134
- package/dist/cjs/src/message-parser.js.map +0 -1
- package/dist/cjs/src/message-sender.js +0 -89
- package/dist/cjs/src/message-sender.js.map +0 -1
- package/dist/cjs/src/monitor.js +0 -991
- package/dist/cjs/src/monitor.js.map +0 -1
- package/dist/cjs/src/onboarding.js +0 -182
- package/dist/cjs/src/onboarding.js.map +0 -1
- package/dist/cjs/src/openclaw-compat.js +0 -400
- package/dist/cjs/src/openclaw-compat.js.map +0 -1
- package/dist/cjs/src/reqid-store.js +0 -88
- package/dist/cjs/src/reqid-store.js.map +0 -1
- package/dist/cjs/src/runtime.js +0 -16
- package/dist/cjs/src/runtime.js.map +0 -1
- package/dist/cjs/src/state-dir-resolve.js +0 -35
- package/dist/cjs/src/state-dir-resolve.js.map +0 -1
- package/dist/cjs/src/state-manager.js +0 -158
- package/dist/cjs/src/state-manager.js.map +0 -1
- package/dist/cjs/src/template-card-parser.js +0 -641
- package/dist/cjs/src/template-card-parser.js.map +0 -1
- package/dist/cjs/src/timeout.js +0 -42
- package/dist/cjs/src/timeout.js.map +0 -1
- package/dist/cjs/src/utils.js +0 -60
- package/dist/cjs/src/utils.js.map +0 -1
- package/dist/cjs/src/version.js +0 -26
- package/dist/cjs/src/version.js.map +0 -1
- package/dist/esm/index.js +0 -41
- package/dist/esm/index.js.map +0 -1
- package/dist/esm/src/channel.js +0 -337
- package/dist/esm/src/channel.js.map +0 -1
- package/dist/esm/src/const.js.map +0 -1
- package/dist/esm/src/dm-policy.js.map +0 -1
- package/dist/esm/src/group-policy.js.map +0 -1
- package/dist/esm/src/mcp/interceptors/biz-error.js.map +0 -1
- package/dist/esm/src/mcp/interceptors/index.js.map +0 -1
- package/dist/esm/src/mcp/interceptors/msg-media.js.map +0 -1
- package/dist/esm/src/mcp/interceptors/smartpage-create.js.map +0 -1
- package/dist/esm/src/mcp/interceptors/smartpage-export.js.map +0 -1
- package/dist/esm/src/mcp/schema.js.map +0 -1
- package/dist/esm/src/mcp/tool.js.map +0 -1
- package/dist/esm/src/mcp/transport.js.map +0 -1
- package/dist/esm/src/media-handler.js.map +0 -1
- package/dist/esm/src/media-uploader.js.map +0 -1
- package/dist/esm/src/message-parser.js.map +0 -1
- package/dist/esm/src/message-sender.js.map +0 -1
- package/dist/esm/src/monitor.js.map +0 -1
- package/dist/esm/src/onboarding.js.map +0 -1
- package/dist/esm/src/openclaw-compat.js.map +0 -1
- package/dist/esm/src/reqid-store.js.map +0 -1
- package/dist/esm/src/runtime.js.map +0 -1
- package/dist/esm/src/state-dir-resolve.js.map +0 -1
- package/dist/esm/src/state-manager.js.map +0 -1
- package/dist/esm/src/template-card-parser.js.map +0 -1
- package/dist/esm/src/timeout.js.map +0 -1
- package/dist/esm/src/utils.js.map +0 -1
- package/dist/esm/src/version.js +0 -23
- package/dist/esm/src/version.js.map +0 -1
- package/dist/esm/types/index.d.ts +0 -9
- package/dist/esm/types/src/version.d.ts +0 -2
- /package/dist/{esm/types/src → src}/channel.d.ts +0 -0
- /package/dist/{esm/types/src → src}/dm-policy.d.ts +0 -0
- /package/dist/{esm/types/src → src}/group-policy.d.ts +0 -0
- /package/dist/{esm/types/src → src}/mcp/index.d.ts +0 -0
- /package/dist/{esm/types/src → src}/mcp/interceptors/biz-error.d.ts +0 -0
- /package/dist/{esm/types/src → src}/mcp/interceptors/index.d.ts +0 -0
- /package/dist/{esm/types/src → src}/mcp/interceptors/msg-media.d.ts +0 -0
- /package/dist/{esm/types/src → src}/mcp/interceptors/smartpage-create.d.ts +0 -0
- /package/dist/{esm/types/src → src}/mcp/interceptors/smartpage-export.d.ts +0 -0
- /package/dist/{esm/types/src → src}/mcp/interceptors/types.d.ts +0 -0
- /package/dist/{esm/types/src → src}/mcp/schema.d.ts +0 -0
- /package/dist/{esm/types/src → src}/mcp/tool.d.ts +0 -0
- /package/dist/{esm/types/src → src}/mcp/transport.d.ts +0 -0
- /package/dist/{esm/types/src → src}/media-handler.d.ts +0 -0
- /package/dist/{esm/types/src → src}/media-uploader.d.ts +0 -0
- /package/dist/{esm/types/src → src}/message-parser.d.ts +0 -0
- /package/dist/{esm/types/src → src}/monitor.d.ts +0 -0
- /package/dist/{esm/types/src → src}/onboarding.d.ts +0 -0
- /package/dist/{esm/types/src → src}/reqid-store.d.ts +0 -0
- /package/dist/{esm/types/src → src}/runtime.d.ts +0 -0
- /package/dist/{esm/types/src → src}/state-dir-resolve.d.ts +0 -0
- /package/dist/{esm/types/src → src}/state-manager.d.ts +0 -0
- /package/dist/{esm/types/src → src}/template-card-parser.d.ts +0 -0
- /package/dist/{esm/types/src → src}/timeout.d.ts +0 -0
|
@@ -1,21 +1,3 @@
|
|
|
1
|
-
import * as os from 'os';
|
|
2
|
-
import * as path from 'path';
|
|
3
|
-
import { WSClient, WSAuthFailureError, WSReconnectExhaustedError, generateReqId } from '@wecom/aibot-node-sdk';
|
|
4
|
-
import { getWeComRuntime } from './runtime.js';
|
|
5
|
-
import { getDefaultMediaLocalRoots } from './openclaw-compat.js';
|
|
6
|
-
import { SCENE_WECOM_OPENCLAW, WS_MAX_AUTH_FAILURE_ATTEMPTS, WS_MAX_RECONNECT_ATTEMPTS, WS_HEARTBEAT_INTERVAL_MS, EVENT_ENTER_CHECK_UPDATE, CMD_ENTER_EVENT_REPLY, CHANNEL_ID, MEDIA_IMAGE_PLACEHOLDER, MEDIA_DOCUMENT_PLACEHOLDER, THINKING_MESSAGE } from './const.js';
|
|
7
|
-
export { WeComCommand } from './const.js';
|
|
8
|
-
import { parseMessageContent } from './message-parser.js';
|
|
9
|
-
import { sendWeComReply, StreamExpiredError } from './message-sender.js';
|
|
10
|
-
import { downloadAndSaveImages, downloadAndSaveFiles } from './media-handler.js';
|
|
11
|
-
import { uploadAndSendMedia } from './media-uploader.js';
|
|
12
|
-
import { maskTemplateCardBlocks, extractTemplateCards } from './template-card-parser.js';
|
|
13
|
-
import { checkGroupPolicy } from './group-policy.js';
|
|
14
|
-
import { checkDmPolicy } from './dm-policy.js';
|
|
15
|
-
import { startMessageStateCleanup, setWeComWebSocket, warmupReqIdStore, stopMessageStateCleanup, cleanupAccount, setReqIdForChat, setMessageState, deleteMessageState } from './state-manager.js';
|
|
16
|
-
export { getWeComWebSocket } from './state-manager.js';
|
|
17
|
-
import { PLUGIN_VERSION } from './version.js';
|
|
18
|
-
|
|
19
1
|
/**
|
|
20
2
|
* 企业微信 WebSocket 监控器主模块
|
|
21
3
|
*
|
|
@@ -33,156 +15,26 @@ import { PLUGIN_VERSION } from './version.js';
|
|
|
33
15
|
* - state-manager.ts : 全局状态管理(带 TTL 清理)
|
|
34
16
|
* - timeout.ts : 超时工具
|
|
35
17
|
*/
|
|
36
|
-
|
|
37
|
-
*
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (now - entry.createdAt >= TEMPLATE_CARD_CACHE_TTL_MS) {
|
|
54
|
-
sentTemplateCardByTaskId.delete(key);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
if (sentTemplateCardByTaskId.size <= TEMPLATE_CARD_CACHE_MAX_SIZE) {
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
const sortedEntries = [...sentTemplateCardByTaskId.entries()].sort((a, b) => a[1].createdAt - b[1].createdAt);
|
|
61
|
-
const removeCount = sentTemplateCardByTaskId.size - TEMPLATE_CARD_CACHE_MAX_SIZE;
|
|
62
|
-
for (const [key] of sortedEntries.slice(0, removeCount)) {
|
|
63
|
-
sentTemplateCardByTaskId.delete(key);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
function cloneTemplateCard(card) {
|
|
67
|
-
return JSON.parse(JSON.stringify(card));
|
|
68
|
-
}
|
|
69
|
-
function saveTemplateCardToCache(params) {
|
|
70
|
-
const { accountId, templateCard, runtime } = params;
|
|
71
|
-
const taskId = templateCard.task_id;
|
|
72
|
-
if (!taskId) {
|
|
73
|
-
runtime.log?.("[wecom][template-card] Skip cache: template card has no task_id");
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
sentTemplateCardByTaskId.set(getTemplateCardCacheKey(accountId, taskId), {
|
|
77
|
-
templateCard: cloneTemplateCard(templateCard),
|
|
78
|
-
createdAt: Date.now(),
|
|
79
|
-
});
|
|
80
|
-
pruneTemplateCardCache();
|
|
81
|
-
}
|
|
82
|
-
function getTemplateCardFromCache(accountId, taskId) {
|
|
83
|
-
pruneTemplateCardCache();
|
|
84
|
-
const cached = sentTemplateCardByTaskId.get(getTemplateCardCacheKey(accountId, taskId));
|
|
85
|
-
if (!cached) {
|
|
86
|
-
return undefined;
|
|
87
|
-
}
|
|
88
|
-
return cloneTemplateCard(cached.templateCard);
|
|
89
|
-
}
|
|
90
|
-
function buildSelectedOptionMap(templateCardEvent) {
|
|
91
|
-
const selectedMap = new Map();
|
|
92
|
-
const selectedItems = templateCardEvent?.selected_items?.selected_item ?? [];
|
|
93
|
-
for (const item of selectedItems) {
|
|
94
|
-
const questionKey = item.question_key?.trim();
|
|
95
|
-
if (!questionKey) {
|
|
96
|
-
continue;
|
|
97
|
-
}
|
|
98
|
-
const optionIds = item.option_ids?.option_id?.filter(Boolean) ?? [];
|
|
99
|
-
selectedMap.set(questionKey, optionIds);
|
|
100
|
-
}
|
|
101
|
-
return selectedMap;
|
|
102
|
-
}
|
|
103
|
-
function applySelectedStateToTemplateCard(params) {
|
|
104
|
-
const { templateCard, selectedMap, templateCardEvent } = params;
|
|
105
|
-
const nextCard = cloneTemplateCard(templateCard);
|
|
106
|
-
if (templateCardEvent?.task_id) {
|
|
107
|
-
nextCard.task_id = templateCardEvent.task_id;
|
|
108
|
-
}
|
|
109
|
-
if (templateCardEvent?.card_type) {
|
|
110
|
-
nextCard.card_type = templateCardEvent.card_type;
|
|
111
|
-
}
|
|
112
|
-
// 交互完成后将提交按钮文案更新为已提交,提升用户感知
|
|
113
|
-
if (nextCard.submit_button?.text) {
|
|
114
|
-
nextCard.submit_button.text = "已提交";
|
|
115
|
-
}
|
|
116
|
-
if (nextCard.checkbox?.question_key) {
|
|
117
|
-
const selectedIds = selectedMap.get(nextCard.checkbox.question_key) ?? [];
|
|
118
|
-
nextCard.checkbox.disable = true;
|
|
119
|
-
if (Array.isArray(nextCard.checkbox.option_list)) {
|
|
120
|
-
nextCard.checkbox.option_list = nextCard.checkbox.option_list.map((option) => ({
|
|
121
|
-
...option,
|
|
122
|
-
is_checked: selectedIds.includes(option.id),
|
|
123
|
-
}));
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
if (Array.isArray(nextCard.select_list)) {
|
|
127
|
-
nextCard.select_list = nextCard.select_list.map((selection) => {
|
|
128
|
-
const selectedIds = selectedMap.get(selection.question_key) ?? [];
|
|
129
|
-
return {
|
|
130
|
-
...selection,
|
|
131
|
-
disable: true,
|
|
132
|
-
selected_id: selectedIds[0] ?? selection.selected_id,
|
|
133
|
-
};
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
if (nextCard.button_selection?.question_key) {
|
|
137
|
-
const selectedIds = selectedMap.get(nextCard.button_selection.question_key) ?? [];
|
|
138
|
-
nextCard.button_selection.disable = true;
|
|
139
|
-
if (selectedIds[0]) {
|
|
140
|
-
nextCard.button_selection.selected_id = selectedIds[0];
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
return nextCard;
|
|
144
|
-
}
|
|
145
|
-
async function updateTemplateCardOnEvent(params) {
|
|
146
|
-
const { frame, accountId, runtime, wsClient } = params;
|
|
147
|
-
const body = frame.body;
|
|
148
|
-
const templateCardEvent = body.event?.template_card_event;
|
|
149
|
-
const taskId = templateCardEvent?.task_id;
|
|
150
|
-
if (!taskId) {
|
|
151
|
-
runtime.log?.(`[${accountId}] [template-card-update] Skip update: missing task_id in callback`);
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
const cachedCard = getTemplateCardFromCache(accountId, taskId);
|
|
155
|
-
if (!cachedCard) {
|
|
156
|
-
runtime.log?.(`[${accountId}] [template-card-update] Skip update: task_id=${taskId} not found in cache`);
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
const selectedMap = buildSelectedOptionMap(templateCardEvent);
|
|
160
|
-
const updatedCard = applySelectedStateToTemplateCard({
|
|
161
|
-
templateCard: cachedCard,
|
|
162
|
-
selectedMap,
|
|
163
|
-
templateCardEvent,
|
|
164
|
-
});
|
|
165
|
-
await wsClient.updateTemplateCard(frame, updatedCard, [body.from.userid]);
|
|
166
|
-
runtime.log?.(`[${accountId}] [template-card-update] Updated card by task_id=${taskId}`);
|
|
167
|
-
// 将更新后的卡片写回缓存,后续多次点击时状态保持一致
|
|
168
|
-
saveTemplateCardToCache({
|
|
169
|
-
accountId,
|
|
170
|
-
templateCard: updatedCard,
|
|
171
|
-
runtime,
|
|
172
|
-
});
|
|
173
|
-
}
|
|
18
|
+
import * as os from "os";
|
|
19
|
+
import * as path from "path";
|
|
20
|
+
import { WSClient, generateReqId, WSAuthFailureError, WSReconnectExhaustedError } from "@wecom/aibot-node-sdk";
|
|
21
|
+
import { getWeComRuntime } from "./runtime.js";
|
|
22
|
+
import { getDefaultMediaLocalRoots, resolveStateDir } from "./openclaw-compat.js";
|
|
23
|
+
import { CHANNEL_ID, THINKING_MESSAGE, MEDIA_IMAGE_PLACEHOLDER, MEDIA_DOCUMENT_PLACEHOLDER, WS_HEARTBEAT_INTERVAL_MS, WS_MAX_RECONNECT_ATTEMPTS, WS_MAX_AUTH_FAILURE_ATTEMPTS, EVENT_ENTER_CHECK_UPDATE, CMD_ENTER_EVENT_REPLY, SCENE_WECOM_OPENCLAW, } from "./const.js";
|
|
24
|
+
import { parseMessageContent } from "./message-parser.js";
|
|
25
|
+
import { sendWeComReply, StreamExpiredError } from "./message-sender.js";
|
|
26
|
+
import { downloadAndSaveImages, downloadAndSaveFiles } from "./media-handler.js";
|
|
27
|
+
import { uploadAndSendMedia } from "./media-uploader.js";
|
|
28
|
+
import { maskTemplateCardBlocks } from "./template-card-parser.js";
|
|
29
|
+
import { updateTemplateCardOnEvent, processTemplateCardsIfNeeded, } from "./template-card-manager.js";
|
|
30
|
+
import { checkGroupPolicy } from "./group-policy.js";
|
|
31
|
+
import { checkDmPolicy } from "./dm-policy.js";
|
|
32
|
+
import { setWeComWebSocket, setMessageState, deleteMessageState, setReqIdForChat, warmupReqIdStore, startMessageStateCleanup, stopMessageStateCleanup, cleanupAccount, } from "./state-manager.js";
|
|
33
|
+
import { PLUGIN_VERSION } from "./version.js";
|
|
34
|
+
import { processDynamicRouting } from "./dynamic-routing.js";
|
|
174
35
|
// ============================================================================
|
|
175
36
|
// 媒体本地路径白名单扩展
|
|
176
37
|
// ============================================================================
|
|
177
|
-
/**
|
|
178
|
-
* 解析 openclaw 状态目录(与 plugin-sdk 内部逻辑保持一致)
|
|
179
|
-
*/
|
|
180
|
-
function resolveStateDir() {
|
|
181
|
-
const stateOverride = process.env.OPENCLAW_STATE_DIR?.trim() || process.env.CLAWDBOT_STATE_DIR?.trim();
|
|
182
|
-
if (stateOverride)
|
|
183
|
-
return stateOverride;
|
|
184
|
-
return path.join(os.homedir(), ".openclaw");
|
|
185
|
-
}
|
|
186
38
|
/**
|
|
187
39
|
* 在 getDefaultMediaLocalRoots() 基础上,将 stateDir 本身也加入白名单,
|
|
188
40
|
* 并合并用户在 WeComConfig 中配置的自定义 mediaLocalRoots。
|
|
@@ -236,13 +88,17 @@ function buildMediaErrorSummary(mediaUrl, result) {
|
|
|
236
88
|
}
|
|
237
89
|
return `⚠️ 文件发送失败:无法处理文件 ${mediaUrl},请稍后再试。`;
|
|
238
90
|
}
|
|
91
|
+
export { WeComCommand } from "./const.js";
|
|
92
|
+
export { getWeComWebSocket, setReqIdForChat, getReqIdForChatAsync, getReqIdForChat, deleteReqIdForChat, warmupReqIdStore, flushReqIdStore, } from "./state-manager.js";
|
|
93
|
+
export { sendWeComReply } from "./message-sender.js";
|
|
239
94
|
// ============================================================================
|
|
240
95
|
// 消息上下文构建
|
|
241
96
|
// ============================================================================
|
|
242
97
|
/**
|
|
243
98
|
* 构建消息上下文
|
|
99
|
+
* @returns 消息上下文对象
|
|
244
100
|
*/
|
|
245
|
-
function buildMessageContext(frame, account, config, text, mediaList, quoteContent) {
|
|
101
|
+
function buildMessageContext(frame, account, config, text, mediaList, quoteContent, runtime) {
|
|
246
102
|
const core = getWeComRuntime();
|
|
247
103
|
const body = frame.body;
|
|
248
104
|
const chatId = body.chatid || body.from.userid;
|
|
@@ -257,6 +113,24 @@ function buildMessageContext(frame, account, config, text, mediaList, quoteConte
|
|
|
257
113
|
id: chatId,
|
|
258
114
|
},
|
|
259
115
|
});
|
|
116
|
+
// ===== 动态 Agent 路由注入 =====
|
|
117
|
+
const routingResult = processDynamicRouting({
|
|
118
|
+
route,
|
|
119
|
+
config,
|
|
120
|
+
core,
|
|
121
|
+
accountId: account.accountId,
|
|
122
|
+
chatType: chatType === "group" ? "group" : "dm",
|
|
123
|
+
chatId,
|
|
124
|
+
senderId: body.from.userid,
|
|
125
|
+
log: runtime?.log ? (...args) => runtime.log?.(...args) : undefined,
|
|
126
|
+
error: runtime?.error ? (...args) => runtime.error?.(...args) : undefined,
|
|
127
|
+
});
|
|
128
|
+
// 应用动态路由结果
|
|
129
|
+
if (routingResult.routeModified) {
|
|
130
|
+
route.agentId = routingResult.finalAgentId;
|
|
131
|
+
route.sessionKey = routingResult.finalSessionKey;
|
|
132
|
+
}
|
|
133
|
+
// ===== 动态 Agent 路由注入结束 =====
|
|
260
134
|
// 构建会话标签
|
|
261
135
|
const fromLabel = chatType === "group" ? `group:${chatId}` : `user:${body.from.userid}`;
|
|
262
136
|
// 当只有媒体没有文本时,使用占位符标识媒体类型
|
|
@@ -267,8 +141,12 @@ function buildMessageContext(frame, account, config, text, mediaList, quoteConte
|
|
|
267
141
|
const mediaTypes = mediaList.length > 0
|
|
268
142
|
? mediaList.map((m) => m.contentType).filter(Boolean)
|
|
269
143
|
: undefined;
|
|
144
|
+
// 使用 route.agentId 解析 storePath(多 agent 场景下 session 路径隔离)
|
|
145
|
+
const storePath = core.channel.session.resolveStorePath(config.session?.store, {
|
|
146
|
+
agentId: route.agentId,
|
|
147
|
+
});
|
|
270
148
|
// 构建标准消息上下文
|
|
271
|
-
|
|
149
|
+
const ctxPayload = core.channel.reply.finalizeInboundContext({
|
|
272
150
|
Body: messageBody,
|
|
273
151
|
RawBody: messageBody,
|
|
274
152
|
CommandBody: messageBody,
|
|
@@ -277,7 +155,7 @@ function buildMessageContext(frame, account, config, text, mediaList, quoteConte
|
|
|
277
155
|
To: `${CHANNEL_ID}:${chatId}`,
|
|
278
156
|
SenderId: body.from.userid,
|
|
279
157
|
SessionKey: route.sessionKey,
|
|
280
|
-
AccountId:
|
|
158
|
+
AccountId: route.accountId,
|
|
281
159
|
ChatType: chatType,
|
|
282
160
|
ConversationLabel: fromLabel,
|
|
283
161
|
Timestamp: Date.now(),
|
|
@@ -296,6 +174,7 @@ function buildMessageContext(frame, account, config, text, mediaList, quoteConte
|
|
|
296
174
|
MediaUrls: mediaPaths,
|
|
297
175
|
ReplyToBody: quoteContent,
|
|
298
176
|
});
|
|
177
|
+
return { ctxPayload, route, storePath, chatId, chatType };
|
|
299
178
|
}
|
|
300
179
|
/**
|
|
301
180
|
* 发送"思考中"消息
|
|
@@ -367,68 +246,38 @@ async function sendMediaBatch(ctx, mediaUrls) {
|
|
|
367
246
|
* 替换掉 thinking 动画,否则 thinking 会一直残留。
|
|
368
247
|
*
|
|
369
248
|
* 关闭策略(按优先级):
|
|
370
|
-
* 0. [新增] 有模板卡片代码块 → 提取卡片并主动发送,用剩余文本关闭流
|
|
371
249
|
* 1. 有可见文本 → 用完整文本关闭
|
|
372
|
-
* 2.
|
|
373
|
-
* 3.
|
|
374
|
-
* 4.
|
|
375
|
-
* (agent 可能已通过内置 message 工具直接发送了文件,
|
|
376
|
-
* 该路径走 outbound.sendMedia 完全绕过 deliver 回调,
|
|
377
|
-
* 所以 state 中无记录,但文件已实际送达)
|
|
250
|
+
* 2. 有模板卡片发送成功 → "📋 卡片消息已发送。"
|
|
251
|
+
* 3. 有媒体成功发送(通过 deliver 回调) → 用友好提示"文件已发送"
|
|
252
|
+
* 4. 媒体发送失败 → 直接用错误摘要替换 thinking
|
|
378
253
|
*
|
|
379
254
|
* 降级策略:
|
|
380
255
|
* - 当 streamExpired=true(errcode 846608)时,流式通道已不可用(>6分钟),
|
|
381
256
|
* 改用 wsClient.sendMessage 主动发送完整文本。
|
|
257
|
+
*
|
|
258
|
+
* 注意:模板卡片的检测和发送已在 finishThinkingStream 之前由
|
|
259
|
+
* processTemplateCardsIfNeeded 完成,此处只关心最后的消息发送。
|
|
382
260
|
*/
|
|
383
261
|
async function finishThinkingStream(ctx) {
|
|
384
262
|
const { wsClient, frame, state, runtime } = ctx;
|
|
385
263
|
const body = frame.body;
|
|
386
264
|
const chatId = body.chatid || body.from.userid;
|
|
387
|
-
const visibleText =
|
|
388
|
-
// ── 模板卡片检测与发送 ──────────────────────────────────────────────
|
|
389
|
-
// 在确定 finishText 之前,先检查累积文本中是否包含模板卡片 JSON 代码块。
|
|
390
|
-
// 若检测到合法卡片,通过 sendMessage 主动发送后,用剩余文本关闭流。
|
|
391
|
-
if (visibleText) {
|
|
392
|
-
runtime.log?.(`[wecom][template-card] finishThinkingStream: visibleText exists, length=${visibleText.length}, running extractTemplateCards...`);
|
|
393
|
-
const logFn = (...args) => {
|
|
394
|
-
runtime.log?.(...args);
|
|
395
|
-
};
|
|
396
|
-
const { cards, remainingText } = extractTemplateCards(state.accumulatedText, logFn);
|
|
397
|
-
runtime.log?.(`[wecom][template-card] finishThinkingStream: extractTemplateCards result — cards=${cards.length}, remainingTextLength=${remainingText.length}`);
|
|
398
|
-
if (cards.length > 0) {
|
|
399
|
-
runtime.log?.(`[wecom][template-card] finishThinkingStream: ${cards.length} card(s) detected, card_types=[${cards.map(c => c.cardType).join(", ")}]`);
|
|
400
|
-
await sendTemplateCards(ctx, cards);
|
|
401
|
-
// 用剩余文本关闭流(可能为空)
|
|
402
|
-
const trimmedRemaining = stripThinkTags(remainingText);
|
|
403
|
-
const finishText = trimmedRemaining
|
|
404
|
-
? remainingText
|
|
405
|
-
: (state.hasTemplateCard ? "📋 卡片消息已发送。" : "");
|
|
406
|
-
runtime.log?.(`[wecom][template-card] finishThinkingStream: closing stream with finishText="${finishText.slice(0, 100)}...", hasTemplateCard=${state.hasTemplateCard}`);
|
|
407
|
-
await sendWeComReply({ wsClient, frame, text: finishText, runtime, finish: true, streamId: state.streamId });
|
|
408
|
-
return;
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
else {
|
|
412
|
-
runtime.log?.(`[wecom][template-card] finishThinkingStream: no visibleText, skipping template card extraction`);
|
|
413
|
-
}
|
|
414
|
-
// ── 模板卡片检测结束 ────────────────────────────────────────────────
|
|
265
|
+
const visibleText = state.accumulatedText;
|
|
415
266
|
let finishText = state.accumulatedText;
|
|
416
267
|
if (visibleText) {
|
|
417
|
-
// 有可见文本:用完整文本关闭流(覆盖 thinking 为真实内容)
|
|
418
268
|
finishText = state.accumulatedText;
|
|
419
269
|
}
|
|
270
|
+
else if (state.hasTemplateCard) {
|
|
271
|
+
finishText = "📋 卡片消息已发送。";
|
|
272
|
+
}
|
|
420
273
|
else if (state.hasMedia) {
|
|
421
274
|
if (state.hasMediaFailed && state.mediaErrorSummary) {
|
|
422
|
-
// 媒体成功发送:用友好提示告知用户
|
|
423
275
|
finishText = finishText ? `${finishText}\n\n${state.mediaErrorSummary}` : state.mediaErrorSummary;
|
|
424
276
|
}
|
|
425
277
|
else if (!finishText) {
|
|
426
278
|
finishText = "📎 文件已发送,请查收。";
|
|
427
279
|
}
|
|
428
280
|
}
|
|
429
|
-
// if (!finishText) {
|
|
430
|
-
// finishText = "✅ 处理完成。";
|
|
431
|
-
// }
|
|
432
281
|
if (finishText) {
|
|
433
282
|
// 尝试流式发送;若已知过期或发送时发现过期,统一降级为主动发送
|
|
434
283
|
let expired = state.streamExpired;
|
|
@@ -454,46 +303,11 @@ async function finishThinkingStream(ctx) {
|
|
|
454
303
|
}
|
|
455
304
|
}
|
|
456
305
|
}
|
|
457
|
-
/**
|
|
458
|
-
* 逐个发送已提取的模板卡片(通过 wsClient.sendMessage 主动推送)
|
|
459
|
-
*
|
|
460
|
-
* 发送失败不阻塞流程,仅记录错误日志。
|
|
461
|
-
*/
|
|
462
|
-
async function sendTemplateCards(ctx, cards) {
|
|
463
|
-
const { wsClient, frame, state, runtime, account } = ctx;
|
|
464
|
-
const body = frame.body;
|
|
465
|
-
const chatId = body.chatid || body.from.userid;
|
|
466
|
-
for (const card of cards) {
|
|
467
|
-
try {
|
|
468
|
-
runtime.log?.(`[wecom][template-card] Sending card_type=${card.cardType} to chatId=${chatId}`);
|
|
469
|
-
const rawTemplateCard = card.cardJson;
|
|
470
|
-
if (typeof rawTemplateCard.card_type !== "string") {
|
|
471
|
-
runtime.error?.("[wecom][template-card] Skip sending invalid card: missing card_type");
|
|
472
|
-
continue;
|
|
473
|
-
}
|
|
474
|
-
const templateCard = rawTemplateCard;
|
|
475
|
-
await wsClient.sendMessage(chatId, {
|
|
476
|
-
msgtype: "template_card",
|
|
477
|
-
template_card: templateCard,
|
|
478
|
-
});
|
|
479
|
-
state.hasTemplateCard = true;
|
|
480
|
-
saveTemplateCardToCache({
|
|
481
|
-
accountId: account.accountId,
|
|
482
|
-
templateCard,
|
|
483
|
-
runtime,
|
|
484
|
-
});
|
|
485
|
-
runtime.log?.(`[wecom][template-card] Card sent successfully: card_type=${card.cardType}`);
|
|
486
|
-
}
|
|
487
|
-
catch (err) {
|
|
488
|
-
runtime.error?.(`[wecom][template-card] Failed to send card: card_type=${card.cardType}, error=${JSON.stringify(err)}`);
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
306
|
/**
|
|
493
307
|
* 路由消息到核心处理流程并处理回复
|
|
494
308
|
*/
|
|
495
309
|
async function routeAndDispatchMessage(params) {
|
|
496
|
-
const { ctxPayload, config, account, wsClient, frame, state, runtime, onCleanup } = params;
|
|
310
|
+
const { ctxPayload, route, storePath, chatId, chatType, config, account, wsClient, frame, state, runtime, onCleanup } = params;
|
|
497
311
|
const core = getWeComRuntime();
|
|
498
312
|
const ctx = { wsClient, frame, state, account, runtime };
|
|
499
313
|
// 防止 onCleanup 被多次调用(onError 回调与 catch 块可能重复触发)
|
|
@@ -506,23 +320,26 @@ async function routeAndDispatchMessage(params) {
|
|
|
506
320
|
};
|
|
507
321
|
let isShowThink = !(account.sendThinkingMessage ?? true);
|
|
508
322
|
try {
|
|
323
|
+
// 记录 inbound session 元数据(session 追踪)
|
|
324
|
+
await core.channel.session.recordInboundSession({
|
|
325
|
+
storePath,
|
|
326
|
+
sessionKey: ctxPayload.SessionKey ?? route.sessionKey,
|
|
327
|
+
ctx: ctxPayload,
|
|
328
|
+
updateLastRoute: chatType !== "group"
|
|
329
|
+
? {
|
|
330
|
+
sessionKey: route.mainSessionKey,
|
|
331
|
+
channel: CHANNEL_ID,
|
|
332
|
+
to: `${CHANNEL_ID}:${chatId}`,
|
|
333
|
+
accountId: route.accountId,
|
|
334
|
+
}
|
|
335
|
+
: undefined,
|
|
336
|
+
onRecordError: (err) => {
|
|
337
|
+
runtime.error?.(`[wecom] failed updating session meta: ${String(err)}`);
|
|
338
|
+
},
|
|
339
|
+
});
|
|
509
340
|
await core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
|
|
510
341
|
ctx: ctxPayload,
|
|
511
342
|
cfg: config,
|
|
512
|
-
// replyResolver: async (ctx, opts) => {
|
|
513
|
-
// const startTime = Date.now();
|
|
514
|
-
// const TEN_MINUTES = 7 * 60 * 1000;
|
|
515
|
-
//
|
|
516
|
-
// console.log('开始输出内容');
|
|
517
|
-
//
|
|
518
|
-
// while (Date.now() - startTime < TEN_MINUTES) {
|
|
519
|
-
// // 每隔一段时间发送一个 block reply
|
|
520
|
-
// await opts?.onBlockReply?.({ text: `输出内容 ${new Date().toISOString()}` });
|
|
521
|
-
// await new Promise(resolve => setTimeout(resolve, 30 * 1000)); // 每5秒输出一次
|
|
522
|
-
// }
|
|
523
|
-
//
|
|
524
|
-
// return { text: "10分钟输出完成" }; // 最终回复
|
|
525
|
-
// },
|
|
526
343
|
dispatcherOptions: {
|
|
527
344
|
onReplyStart: async () => {
|
|
528
345
|
if (!isShowThink && state.streamId && !state.accumulatedText) {
|
|
@@ -536,11 +353,10 @@ async function routeAndDispatchMessage(params) {
|
|
|
536
353
|
}
|
|
537
354
|
},
|
|
538
355
|
deliver: async (payload, info) => {
|
|
539
|
-
|
|
540
|
-
// runtime.log?.(`[openclaw -> plugin] kind=${info.kind}, text=${payload.text ?? ''}, mediaUrl=${payload.mediaUrl ?? ''}, mediaUrls=${JSON.stringify(payload.mediaUrls ?? [])}`);
|
|
356
|
+
runtime.log?.(`[openclaw -> plugin] kind=${info.kind}, text=${payload.text ?? ''}, mediaUrl=${payload.mediaUrl ?? ''}, mediaUrls=${JSON.stringify(payload.mediaUrls ?? [])}`);
|
|
541
357
|
// 累积文本
|
|
542
358
|
if (payload.text) {
|
|
543
|
-
state.accumulatedText +=
|
|
359
|
+
state.accumulatedText += `${payload.text || ''}`;
|
|
544
360
|
}
|
|
545
361
|
// 发送媒体(统一走主动发送)
|
|
546
362
|
const mediaUrls = payload.mediaUrls?.length ? payload.mediaUrls : payload.mediaUrl ? [payload.mediaUrl] : [];
|
|
@@ -563,12 +379,12 @@ async function routeAndDispatchMessage(params) {
|
|
|
563
379
|
// 中间帧:有可见文本时流式更新(流式过期后跳过,等 deliver 完成后主动发送)
|
|
564
380
|
// 使用 maskTemplateCardBlocks 遮罩正在构建中的模板卡片代码块,
|
|
565
381
|
// 避免 JSON 源码在流式输出过程中暴露给终端用户
|
|
566
|
-
if (
|
|
382
|
+
if (state.accumulatedText && !state.streamExpired) {
|
|
567
383
|
try {
|
|
568
384
|
const displayText = maskTemplateCardBlocks(state.accumulatedText, (...args) => runtime.log?.(...args));
|
|
569
|
-
if (displayText !== state.accumulatedText) {
|
|
570
|
-
|
|
571
|
-
}
|
|
385
|
+
// if (displayText !== state.accumulatedText) {
|
|
386
|
+
// runtime.log?.(`[wecom][template-card] Mid-frame masked: original=${state.accumulatedText.length}chars, masked=${displayText.length}chars`);
|
|
387
|
+
// }
|
|
572
388
|
await sendWeComReply({ wsClient, frame, text: displayText, runtime, finish: false, streamId: state.streamId });
|
|
573
389
|
}
|
|
574
390
|
catch (err) {
|
|
@@ -587,15 +403,24 @@ async function routeAndDispatchMessage(params) {
|
|
|
587
403
|
},
|
|
588
404
|
},
|
|
589
405
|
});
|
|
406
|
+
// 模板卡片检测与发送(在关闭 thinking 流之前独立处理)
|
|
407
|
+
const cardResult = await processTemplateCardsIfNeeded({ wsClient, frame, state, account, runtime });
|
|
408
|
+
if (cardResult) {
|
|
409
|
+
// 卡片已发送,用剩余文本替换累积文本
|
|
410
|
+
state.accumulatedText = cardResult.remainingText;
|
|
411
|
+
}
|
|
590
412
|
// 关闭 thinking 流
|
|
591
413
|
await finishThinkingStream(ctx);
|
|
592
414
|
safeCleanup();
|
|
593
415
|
}
|
|
594
416
|
catch (err) {
|
|
595
417
|
runtime.error?.(`[wecom][plugin] Failed to process message: ${String(err)}`);
|
|
596
|
-
// 即使 dispatch
|
|
597
|
-
// 避免 deliver 已成功发送媒体但后续出错时 thinking 消息残留或被错误文案覆盖
|
|
418
|
+
// 即使 dispatch 抛异常,也需要处理卡片和关闭 thinking 流
|
|
598
419
|
try {
|
|
420
|
+
const cardResult = await processTemplateCardsIfNeeded({ wsClient, frame, state, account, runtime });
|
|
421
|
+
if (cardResult) {
|
|
422
|
+
state.accumulatedText = cardResult.remainingText;
|
|
423
|
+
}
|
|
599
424
|
await finishThinkingStream(ctx);
|
|
600
425
|
}
|
|
601
426
|
catch (finishErr) {
|
|
@@ -605,20 +430,12 @@ async function routeAndDispatchMessage(params) {
|
|
|
605
430
|
}
|
|
606
431
|
}
|
|
607
432
|
/**
|
|
608
|
-
*
|
|
609
|
-
*
|
|
610
|
-
* 处理流程:
|
|
611
|
-
* 1. 解析消息内容(文本、图片、引用)
|
|
612
|
-
* 2. 群组策略检查(仅群聊)
|
|
613
|
-
* 3. DM Policy 访问控制检查(仅私聊)
|
|
614
|
-
* 4. 下载并保存图片
|
|
615
|
-
* 5. 初始化消息状态
|
|
616
|
-
* 6. 发送"思考中"消息
|
|
617
|
-
* 7. 路由消息到核心处理流程
|
|
433
|
+
* 解析并校验企业微信消息(防抖前阶段:Step 1-4)
|
|
618
434
|
*
|
|
619
|
-
*
|
|
435
|
+
* 执行消息解析、策略检查、媒体下载等前置操作,
|
|
436
|
+
* 返回一个可用于防抖缓冲的 entry,或 null(消息被过滤/跳过时)。
|
|
620
437
|
*/
|
|
621
|
-
async function
|
|
438
|
+
async function prepareWeComMessage(params) {
|
|
622
439
|
const { frame, account, config, runtime, wsClient } = params;
|
|
623
440
|
const body = frame.body;
|
|
624
441
|
const chatId = body.chatid || body.from.userid;
|
|
@@ -640,7 +457,7 @@ async function processWeComMessage(params) {
|
|
|
640
457
|
// 如果既没有文本也没有图片也没有文件也没有引用内容,则跳过
|
|
641
458
|
if (!text && imageUrls.length === 0 && fileUrls.length === 0) {
|
|
642
459
|
runtime.log?.("[wecom][plugin] Skipping empty message (no text, image, file or quote)");
|
|
643
|
-
return;
|
|
460
|
+
return null;
|
|
644
461
|
}
|
|
645
462
|
// Step 2: 群组策略检查(仅群聊)
|
|
646
463
|
if (chatType === "group") {
|
|
@@ -652,7 +469,7 @@ async function processWeComMessage(params) {
|
|
|
652
469
|
runtime,
|
|
653
470
|
});
|
|
654
471
|
if (!groupPolicyResult.allowed) {
|
|
655
|
-
return;
|
|
472
|
+
return null;
|
|
656
473
|
}
|
|
657
474
|
}
|
|
658
475
|
// Step 3: DM Policy 访问控制检查(仅私聊)
|
|
@@ -665,7 +482,7 @@ async function processWeComMessage(params) {
|
|
|
665
482
|
runtime,
|
|
666
483
|
});
|
|
667
484
|
if (!dmPolicyResult.allowed) {
|
|
668
|
-
return;
|
|
485
|
+
return null;
|
|
669
486
|
}
|
|
670
487
|
// Step 4: 下载并保存图片和文件
|
|
671
488
|
const [imageMediaList, fileMediaList] = await Promise.all([
|
|
@@ -687,6 +504,28 @@ async function processWeComMessage(params) {
|
|
|
687
504
|
}),
|
|
688
505
|
]);
|
|
689
506
|
const mediaList = [...imageMediaList, ...fileMediaList];
|
|
507
|
+
return {
|
|
508
|
+
frame,
|
|
509
|
+
account,
|
|
510
|
+
config,
|
|
511
|
+
runtime,
|
|
512
|
+
wsClient,
|
|
513
|
+
text,
|
|
514
|
+
mediaList,
|
|
515
|
+
quoteContent,
|
|
516
|
+
messageId,
|
|
517
|
+
chatId,
|
|
518
|
+
reqId,
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* 处理企业微信消息(Step 5-7)
|
|
523
|
+
*
|
|
524
|
+
* 接收解析后的消息数据,执行初始化状态、发送 thinking、路由到核心。
|
|
525
|
+
* 同一会话中的消息通过串行队列保证按序执行。
|
|
526
|
+
*/
|
|
527
|
+
async function processWeComMessageNow(entry) {
|
|
528
|
+
const { frame, account, config, runtime, wsClient, text, mediaList, quoteContent, messageId, chatId, reqId } = entry;
|
|
690
529
|
// Step 5: 初始化消息状态
|
|
691
530
|
setReqIdForChat(chatId, reqId, account.accountId);
|
|
692
531
|
const streamId = generateReqId("stream");
|
|
@@ -701,11 +540,15 @@ async function processWeComMessage(params) {
|
|
|
701
540
|
// await sendThinkingReply({ wsClient, frame, streamId, runtime });
|
|
702
541
|
// }
|
|
703
542
|
// Step 7: 构建上下文并路由到核心处理流程(带整体超时保护)
|
|
704
|
-
const ctxPayload = buildMessageContext(frame, account, config, text, mediaList, quoteContent);
|
|
543
|
+
const { ctxPayload, route, storePath, chatId: resolvedChatId, chatType } = buildMessageContext(frame, account, config, text, mediaList, quoteContent, runtime);
|
|
705
544
|
// runtime.log?.(`[plugin -> openclaw] body=${text}, mediaPaths=${JSON.stringify(mediaList.map(m => m.path))}${quoteContent ? `, quote=${quoteContent}` : ''}`);
|
|
706
545
|
try {
|
|
707
546
|
await routeAndDispatchMessage({
|
|
708
547
|
ctxPayload,
|
|
548
|
+
route,
|
|
549
|
+
storePath,
|
|
550
|
+
chatId: resolvedChatId,
|
|
551
|
+
chatType,
|
|
709
552
|
config,
|
|
710
553
|
account,
|
|
711
554
|
wsClient,
|
|
@@ -749,10 +592,9 @@ function createSdkLogger(runtime, accountId) {
|
|
|
749
592
|
* 监听企业微信 WebSocket 连接
|
|
750
593
|
* 使用 aibot-node-sdk 简化连接管理
|
|
751
594
|
*/
|
|
752
|
-
async function monitorWeComProvider(options) {
|
|
595
|
+
export async function monitorWeComProvider(options) {
|
|
753
596
|
const { account, config, runtime, abortSignal, setStatus } = options;
|
|
754
597
|
runtime.log?.(`[${account.accountId}] [${PLUGIN_VERSION}] Initializing WSClient with SDK...`);
|
|
755
|
-
runtime.error?.(`[${account.accountId}] [diag] monitor boot marker: build=20260325-event-debug-1`);
|
|
756
598
|
// 启动消息状态定期清理
|
|
757
599
|
startMessageStateCleanup();
|
|
758
600
|
return new Promise((resolve, reject) => {
|
|
@@ -894,13 +736,26 @@ async function monitorWeComProvider(options) {
|
|
|
894
736
|
// 监听普通消息
|
|
895
737
|
wsClient.on("message", async (frame) => {
|
|
896
738
|
try {
|
|
897
|
-
await
|
|
739
|
+
const entry = await prepareWeComMessage({
|
|
898
740
|
frame,
|
|
899
741
|
account,
|
|
900
742
|
config,
|
|
901
743
|
runtime,
|
|
902
744
|
wsClient,
|
|
903
745
|
});
|
|
746
|
+
if (!entry)
|
|
747
|
+
return;
|
|
748
|
+
// 排队逻辑暂时关闭,直接处理消息
|
|
749
|
+
// const { status } = enqueueWeComChatTask({
|
|
750
|
+
// accountId: entry.account.accountId,
|
|
751
|
+
// chatId: entry.chatId,
|
|
752
|
+
// task: () => processWeComMessageNow(entry),
|
|
753
|
+
// });
|
|
754
|
+
//
|
|
755
|
+
// if (status === "queued") {
|
|
756
|
+
// runtime.log?.(`[wecom] Chat task queued for chat=${entry.chatId} (previous task still running)`);
|
|
757
|
+
// }
|
|
758
|
+
await processWeComMessageNow(entry);
|
|
904
759
|
}
|
|
905
760
|
catch (err) {
|
|
906
761
|
runtime.error?.(`[${account.accountId}] Failed to process message: ${String(err)}`);
|
|
@@ -913,7 +768,6 @@ async function monitorWeComProvider(options) {
|
|
|
913
768
|
const eventBody = frame.body;
|
|
914
769
|
const eventType = eventBody.event?.eventtype;
|
|
915
770
|
runtime.log?.(`[${account.accountId}] Received event callback: eventtype=${eventType ?? ""}, msgid=${eventBody.msgid ?? ""}`);
|
|
916
|
-
runtime.error?.(`[${account.accountId}] [diag] event-listener fired: eventtype=${eventType ?? ""}, msgid=${eventBody.msgid ?? ""}`);
|
|
917
771
|
if (eventType !== "template_card_event") {
|
|
918
772
|
return;
|
|
919
773
|
}
|
|
@@ -930,20 +784,22 @@ async function monitorWeComProvider(options) {
|
|
|
930
784
|
catch (updateErr) {
|
|
931
785
|
runtime.error?.(`[${account.accountId}] [template-card-update] Failed to update template card: ${String(updateErr)}`);
|
|
932
786
|
}
|
|
933
|
-
await
|
|
787
|
+
const entry = await prepareWeComMessage({
|
|
934
788
|
frame,
|
|
935
789
|
account,
|
|
936
790
|
config,
|
|
937
791
|
runtime,
|
|
938
792
|
wsClient,
|
|
939
793
|
});
|
|
794
|
+
if (entry) {
|
|
795
|
+
await processWeComMessageNow(entry);
|
|
796
|
+
}
|
|
940
797
|
}
|
|
941
798
|
catch (err) {
|
|
942
799
|
runtime.error?.(`[${account.accountId}] Failed to process template_card_event: ${String(err)}`);
|
|
943
800
|
}
|
|
944
801
|
});
|
|
945
802
|
runtime.log?.(`[${account.accountId}] Event listeners attached: message + event(template_card_event)`);
|
|
946
|
-
runtime.error?.(`[${account.accountId}] [diag] listeners-ready marker`);
|
|
947
803
|
// 启动前预热 reqId 缓存,确保完成后再建立连接,避免 getSync 在预热完成前返回 undefined
|
|
948
804
|
warmupReqIdStore(account.accountId, (...args) => runtime.log?.(...args))
|
|
949
805
|
.then((count) => {
|
|
@@ -958,6 +814,3 @@ async function monitorWeComProvider(options) {
|
|
|
958
814
|
});
|
|
959
815
|
});
|
|
960
816
|
}
|
|
961
|
-
|
|
962
|
-
export { monitorWeComProvider, sendWeComReply, setReqIdForChat, warmupReqIdStore };
|
|
963
|
-
//# sourceMappingURL=monitor.js.map
|