@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
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 模板卡片管理器
|
|
3
|
+
*
|
|
4
|
+
* 负责:
|
|
5
|
+
* - 模板卡片缓存管理(内存级,带 TTL 和大小限制)
|
|
6
|
+
* - 卡片交互事件处理(更新卡片 UI 状态)
|
|
7
|
+
* - 模板卡片发送(通过 wsClient.sendMessage 主动推送)
|
|
8
|
+
* - 从 LLM 回复中检测并处理模板卡片
|
|
9
|
+
*/
|
|
10
|
+
import { extractTemplateCards } from "./template-card-parser.js";
|
|
11
|
+
import { TEMPLATE_CARD_CACHE_TTL_MS, TEMPLATE_CARD_CACHE_MAX_SIZE, } from "./const.js";
|
|
12
|
+
const sentTemplateCardByTaskId = new Map();
|
|
13
|
+
function getTemplateCardCacheKey(accountId, taskId) {
|
|
14
|
+
return `${accountId}:${taskId}`;
|
|
15
|
+
}
|
|
16
|
+
function pruneTemplateCardCache() {
|
|
17
|
+
const now = Date.now();
|
|
18
|
+
for (const [key, entry] of sentTemplateCardByTaskId) {
|
|
19
|
+
if (now - entry.createdAt >= TEMPLATE_CARD_CACHE_TTL_MS) {
|
|
20
|
+
sentTemplateCardByTaskId.delete(key);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
if (sentTemplateCardByTaskId.size <= TEMPLATE_CARD_CACHE_MAX_SIZE) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const sortedEntries = [...sentTemplateCardByTaskId.entries()].sort((a, b) => a[1].createdAt - b[1].createdAt);
|
|
27
|
+
const removeCount = sentTemplateCardByTaskId.size - TEMPLATE_CARD_CACHE_MAX_SIZE;
|
|
28
|
+
for (const [key] of sortedEntries.slice(0, removeCount)) {
|
|
29
|
+
sentTemplateCardByTaskId.delete(key);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function cloneTemplateCard(card) {
|
|
33
|
+
return JSON.parse(JSON.stringify(card));
|
|
34
|
+
}
|
|
35
|
+
export function saveTemplateCardToCache(params) {
|
|
36
|
+
const { accountId, templateCard, runtime } = params;
|
|
37
|
+
const taskId = templateCard.task_id;
|
|
38
|
+
if (!taskId) {
|
|
39
|
+
runtime.log?.("[wecom][template-card] Skip cache: template card has no task_id");
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
sentTemplateCardByTaskId.set(getTemplateCardCacheKey(accountId, taskId), {
|
|
43
|
+
templateCard: cloneTemplateCard(templateCard),
|
|
44
|
+
createdAt: Date.now(),
|
|
45
|
+
});
|
|
46
|
+
pruneTemplateCardCache();
|
|
47
|
+
}
|
|
48
|
+
export function getTemplateCardFromCache(accountId, taskId) {
|
|
49
|
+
pruneTemplateCardCache();
|
|
50
|
+
const cached = sentTemplateCardByTaskId.get(getTemplateCardCacheKey(accountId, taskId));
|
|
51
|
+
if (!cached) {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
return cloneTemplateCard(cached.templateCard);
|
|
55
|
+
}
|
|
56
|
+
function buildSelectedOptionMap(templateCardEvent) {
|
|
57
|
+
const selectedMap = new Map();
|
|
58
|
+
const selectedItems = templateCardEvent?.selected_items?.selected_item ?? [];
|
|
59
|
+
for (const item of selectedItems) {
|
|
60
|
+
const questionKey = item.question_key?.trim();
|
|
61
|
+
if (!questionKey) {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const optionIds = item.option_ids?.option_id?.filter(Boolean) ?? [];
|
|
65
|
+
selectedMap.set(questionKey, optionIds);
|
|
66
|
+
}
|
|
67
|
+
return selectedMap;
|
|
68
|
+
}
|
|
69
|
+
function applySelectedStateToTemplateCard(params) {
|
|
70
|
+
const { templateCard, selectedMap, templateCardEvent } = params;
|
|
71
|
+
const nextCard = cloneTemplateCard(templateCard);
|
|
72
|
+
if (templateCardEvent?.task_id) {
|
|
73
|
+
nextCard.task_id = templateCardEvent.task_id;
|
|
74
|
+
}
|
|
75
|
+
if (templateCardEvent?.card_type) {
|
|
76
|
+
nextCard.card_type = templateCardEvent.card_type;
|
|
77
|
+
}
|
|
78
|
+
if (nextCard.submit_button?.text) {
|
|
79
|
+
nextCard.submit_button.text = "已提交";
|
|
80
|
+
}
|
|
81
|
+
if (nextCard.checkbox?.question_key) {
|
|
82
|
+
const selectedIds = selectedMap.get(nextCard.checkbox.question_key) ?? [];
|
|
83
|
+
nextCard.checkbox.disable = true;
|
|
84
|
+
if (Array.isArray(nextCard.checkbox.option_list)) {
|
|
85
|
+
nextCard.checkbox.option_list = nextCard.checkbox.option_list.map((option) => ({
|
|
86
|
+
...option,
|
|
87
|
+
is_checked: selectedIds.includes(option.id),
|
|
88
|
+
}));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (Array.isArray(nextCard.select_list)) {
|
|
92
|
+
nextCard.select_list = nextCard.select_list.map((selection) => {
|
|
93
|
+
const selectedIds = selectedMap.get(selection.question_key) ?? [];
|
|
94
|
+
return {
|
|
95
|
+
...selection,
|
|
96
|
+
disable: true,
|
|
97
|
+
selected_id: selectedIds[0] ?? selection.selected_id,
|
|
98
|
+
};
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
if (nextCard.button_selection?.question_key) {
|
|
102
|
+
const selectedIds = selectedMap.get(nextCard.button_selection.question_key) ?? [];
|
|
103
|
+
nextCard.button_selection.disable = true;
|
|
104
|
+
if (selectedIds[0]) {
|
|
105
|
+
nextCard.button_selection.selected_id = selectedIds[0];
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return nextCard;
|
|
109
|
+
}
|
|
110
|
+
export async function updateTemplateCardOnEvent(params) {
|
|
111
|
+
const { frame, accountId, runtime, wsClient } = params;
|
|
112
|
+
const body = frame.body;
|
|
113
|
+
const templateCardEvent = body.event?.template_card_event;
|
|
114
|
+
const taskId = templateCardEvent?.task_id;
|
|
115
|
+
if (!taskId) {
|
|
116
|
+
runtime.log?.(`[${accountId}] [template-card-update] Skip update: missing task_id in callback`);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const cachedCard = getTemplateCardFromCache(accountId, taskId);
|
|
120
|
+
if (!cachedCard) {
|
|
121
|
+
runtime.log?.(`[${accountId}] [template-card-update] Skip update: task_id=${taskId} not found in cache (cache is in-memory only, may have been cleared after restart)`);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const selectedMap = buildSelectedOptionMap(templateCardEvent);
|
|
125
|
+
const updatedCard = applySelectedStateToTemplateCard({
|
|
126
|
+
templateCard: cachedCard,
|
|
127
|
+
selectedMap,
|
|
128
|
+
templateCardEvent,
|
|
129
|
+
});
|
|
130
|
+
await wsClient.updateTemplateCard(frame, updatedCard, [body.from.userid]);
|
|
131
|
+
runtime.log?.(`[${accountId}] [template-card-update] Updated card by task_id=${taskId}`);
|
|
132
|
+
saveTemplateCardToCache({
|
|
133
|
+
accountId,
|
|
134
|
+
templateCard: updatedCard,
|
|
135
|
+
runtime,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
// ============================================================================
|
|
139
|
+
// 模板卡片发送
|
|
140
|
+
// ============================================================================
|
|
141
|
+
/**
|
|
142
|
+
* 逐个发送已提取的模板卡片(通过 wsClient.sendMessage 主动推送)
|
|
143
|
+
*
|
|
144
|
+
* 发送失败不阻塞流程,仅记录错误日志。
|
|
145
|
+
*/
|
|
146
|
+
export async function sendTemplateCards(params) {
|
|
147
|
+
const { wsClient, frame, state, runtime, account, cards } = params;
|
|
148
|
+
const body = frame.body;
|
|
149
|
+
const chatId = body.chatid || body.from.userid;
|
|
150
|
+
for (const card of cards) {
|
|
151
|
+
try {
|
|
152
|
+
runtime.log?.(`[wecom][template-card] Sending card_type=${card.cardType} to chatId=${chatId}`);
|
|
153
|
+
const rawTemplateCard = card.cardJson;
|
|
154
|
+
if (typeof rawTemplateCard.card_type !== "string") {
|
|
155
|
+
runtime.error?.("[wecom][template-card] Skip sending invalid card: missing card_type");
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
const templateCard = rawTemplateCard;
|
|
159
|
+
await wsClient.sendMessage(chatId, {
|
|
160
|
+
msgtype: "template_card",
|
|
161
|
+
template_card: templateCard,
|
|
162
|
+
});
|
|
163
|
+
state.hasTemplateCard = true;
|
|
164
|
+
saveTemplateCardToCache({
|
|
165
|
+
accountId: account.accountId,
|
|
166
|
+
templateCard,
|
|
167
|
+
runtime,
|
|
168
|
+
});
|
|
169
|
+
runtime.log?.(`[wecom][template-card] Card sent successfully: card_type=${card.cardType}`);
|
|
170
|
+
}
|
|
171
|
+
catch (err) {
|
|
172
|
+
runtime.error?.(`[wecom][template-card] Failed to send card: card_type=${card.cardType}, error=${JSON.stringify(err)}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// ============================================================================
|
|
177
|
+
// 模板卡片检测与处理(从 finishThinkingStream 中分离)
|
|
178
|
+
// ============================================================================
|
|
179
|
+
/**
|
|
180
|
+
* 从累积文本中检测并发送模板卡片。
|
|
181
|
+
*
|
|
182
|
+
* 在 finishThinkingStream 之前调用,将卡片处理和流关闭解耦。
|
|
183
|
+
*
|
|
184
|
+
* @returns 移除卡片代码块后的剩余文本(如果没有卡片则返回 null,表示无需修改)
|
|
185
|
+
*/
|
|
186
|
+
export async function processTemplateCardsIfNeeded(params) {
|
|
187
|
+
const { state, runtime } = params;
|
|
188
|
+
const visibleText = state.accumulatedText?.trim();
|
|
189
|
+
if (!visibleText) {
|
|
190
|
+
runtime.log?.(`[wecom][template-card] processTemplateCardsIfNeeded: no visibleText, skipping`);
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
runtime.log?.(`[wecom][template-card] processTemplateCardsIfNeeded: visibleText exists, length=${visibleText.length}, running extractTemplateCards...`);
|
|
194
|
+
const logFn = (...args) => {
|
|
195
|
+
runtime.log?.(...args);
|
|
196
|
+
};
|
|
197
|
+
const { cards, remainingText } = extractTemplateCards(state.accumulatedText, logFn);
|
|
198
|
+
runtime.log?.(`[wecom][template-card] processTemplateCardsIfNeeded: extractTemplateCards result — cards=${cards.length}, remainingTextLength=${remainingText.length}`);
|
|
199
|
+
if (cards.length === 0) {
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
runtime.log?.(`[wecom][template-card] processTemplateCardsIfNeeded: ${cards.length} card(s) detected, card_types=[${cards.map(c => c.cardType).join(", ")}]`);
|
|
203
|
+
await sendTemplateCards({ ...params, cards });
|
|
204
|
+
return { remainingText, cardsDetected: true };
|
|
205
|
+
}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { VALID_CARD_TYPES } from './const.js';
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* 模板卡片解析器
|
|
5
3
|
*
|
|
@@ -9,6 +7,7 @@ import { VALID_CARD_TYPES } from './const.js';
|
|
|
9
7
|
* 同时提供 maskTemplateCardBlocks 函数,用于在流式中间帧中隐藏正在构建的卡片代码块,
|
|
10
8
|
* 避免 JSON 源码暴露给终端用户。
|
|
11
9
|
*/
|
|
10
|
+
import { VALID_CARD_TYPES } from "./const.js";
|
|
12
11
|
// ============================================================================
|
|
13
12
|
// LLM 输出字段类型修正
|
|
14
13
|
// ============================================================================
|
|
@@ -239,6 +238,19 @@ function normalizeTemplateCardFields(card, log) {
|
|
|
239
238
|
}
|
|
240
239
|
return card;
|
|
241
240
|
}
|
|
241
|
+
// ============================================================================
|
|
242
|
+
// 必填字段校验与自动补全
|
|
243
|
+
// ============================================================================
|
|
244
|
+
/** task_id 合法字符:数字、字母、_-@ */
|
|
245
|
+
const TASK_ID_RE = /^[a-zA-Z0-9_\-@]+$/;
|
|
246
|
+
/**
|
|
247
|
+
* 生成一个合法的 task_id。
|
|
248
|
+
* 格式:task_{cardType}_{timestamp}_{随机4位},确保唯一且符合 API 要求。
|
|
249
|
+
*/
|
|
250
|
+
function generateTaskId(cardType) {
|
|
251
|
+
const rand = Math.random().toString(36).slice(2, 6);
|
|
252
|
+
return `task_${cardType}_${Date.now()}_${rand}`;
|
|
253
|
+
}
|
|
242
254
|
/**
|
|
243
255
|
* 校验并补全模板卡片的必填字段。
|
|
244
256
|
*
|
|
@@ -533,7 +545,7 @@ const UNCLOSED_BLOCK_RE = /```(?:json)?\s*\n[\s\S]*$/;
|
|
|
533
545
|
* @param text - LLM 回复的完整文本
|
|
534
546
|
* @returns 提取结果,包含卡片列表和剩余文本
|
|
535
547
|
*/
|
|
536
|
-
function extractTemplateCards(text, log) {
|
|
548
|
+
export function extractTemplateCards(text, log) {
|
|
537
549
|
const cards = [];
|
|
538
550
|
/** 需要从原文中移除的代码块(记录完整匹配内容) */
|
|
539
551
|
const blocksToRemove = [];
|
|
@@ -601,7 +613,7 @@ function extractTemplateCards(text, log) {
|
|
|
601
613
|
* @param text - 当前累积文本
|
|
602
614
|
* @returns 遮罩后的展示文本
|
|
603
615
|
*/
|
|
604
|
-
function maskTemplateCardBlocks(text, log) {
|
|
616
|
+
export function maskTemplateCardBlocks(text, log) {
|
|
605
617
|
let masked = text;
|
|
606
618
|
let closedMaskCount = 0;
|
|
607
619
|
let unclosedMasked = false;
|
|
@@ -629,10 +641,7 @@ function maskTemplateCardBlocks(text, log) {
|
|
|
629
641
|
}
|
|
630
642
|
// 有遮罩行为时才打日志,避免每帧都刷屏
|
|
631
643
|
if (closedMaskCount > 0 || unclosedMasked) {
|
|
632
|
-
log?.(`[template-card-parser] maskTemplateCardBlocks: closedMasked=${closedMaskCount}, unclosedMasked=${unclosedMasked}, textLength=${text.length}, maskedLength=${masked.length}`);
|
|
644
|
+
// log?.(`[template-card-parser] maskTemplateCardBlocks: closedMasked=${closedMaskCount}, unclosedMasked=${unclosedMasked}, textLength=${text.length}, maskedLength=${masked.length}`);
|
|
633
645
|
}
|
|
634
646
|
return masked;
|
|
635
647
|
}
|
|
636
|
-
|
|
637
|
-
export { extractTemplateCards, maskTemplateCardBlocks };
|
|
638
|
-
//# sourceMappingURL=template-card-parser.js.map
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* @param message - 超时错误消息
|
|
12
12
|
* @returns 带超时保护的 Promise
|
|
13
13
|
*/
|
|
14
|
-
function withTimeout(promise, timeoutMs, message) {
|
|
14
|
+
export function withTimeout(promise, timeoutMs, message) {
|
|
15
15
|
if (timeoutMs <= 0 || !Number.isFinite(timeoutMs)) {
|
|
16
16
|
return promise;
|
|
17
17
|
}
|
|
@@ -28,12 +28,9 @@ function withTimeout(promise, timeoutMs, message) {
|
|
|
28
28
|
/**
|
|
29
29
|
* 超时错误类型
|
|
30
30
|
*/
|
|
31
|
-
class TimeoutError extends Error {
|
|
31
|
+
export class TimeoutError extends Error {
|
|
32
32
|
constructor(message) {
|
|
33
33
|
super(message);
|
|
34
34
|
this.name = "TimeoutError";
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
|
-
|
|
38
|
-
export { TimeoutError, withTimeout };
|
|
39
|
-
//# sourceMappingURL=timeout.js.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WeCom 账号类型定义
|
|
3
|
+
*/
|
|
4
|
+
import type { WecomAgentConfig, WecomNetworkConfig } from "./config.js";
|
|
5
|
+
/**
|
|
6
|
+
* 解析后的 Agent 账号
|
|
7
|
+
*/
|
|
8
|
+
export type ResolvedAgentAccount = {
|
|
9
|
+
/** 账号 ID */
|
|
10
|
+
accountId: string;
|
|
11
|
+
/** 是否启用 */
|
|
12
|
+
enabled: boolean;
|
|
13
|
+
/** 是否配置完整 */
|
|
14
|
+
configured: boolean;
|
|
15
|
+
/** 企业 ID */
|
|
16
|
+
corpId: string;
|
|
17
|
+
/** 应用 Secret */
|
|
18
|
+
corpSecret: string;
|
|
19
|
+
/** 应用 ID (数字,可选) */
|
|
20
|
+
agentId?: number;
|
|
21
|
+
/** 回调 Token */
|
|
22
|
+
token: string;
|
|
23
|
+
/** 回调加密密钥 */
|
|
24
|
+
encodingAESKey: string;
|
|
25
|
+
/** 原始配置 */
|
|
26
|
+
config: WecomAgentConfig;
|
|
27
|
+
/** 网络配置(来自 channels.wecom.network) */
|
|
28
|
+
network?: WecomNetworkConfig;
|
|
29
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WeCom 子模块配置类型定义
|
|
3
|
+
*
|
|
4
|
+
* 注意:顶层配置类型 WeComConfig 定义在 src/utils.ts 中,以平铺结构为准。
|
|
5
|
+
* 本文件仅定义 Agent/Bot/DM/Network/Media 等子模块的配置类型。
|
|
6
|
+
*/
|
|
7
|
+
/** 媒体处理配置 */
|
|
8
|
+
export type WecomMediaConfig = {
|
|
9
|
+
tempDir?: string;
|
|
10
|
+
retentionHours?: number;
|
|
11
|
+
cleanupOnStart?: boolean;
|
|
12
|
+
maxBytes?: number;
|
|
13
|
+
};
|
|
14
|
+
/** 网络配置 */
|
|
15
|
+
export type WecomNetworkConfig = {
|
|
16
|
+
timeoutMs?: number;
|
|
17
|
+
retries?: number;
|
|
18
|
+
retryDelayMs?: number;
|
|
19
|
+
/**
|
|
20
|
+
* 出口代理(用于企业可信 IP 固定出口场景)。
|
|
21
|
+
* 示例: "http://proxy.company.local:3128"
|
|
22
|
+
*/
|
|
23
|
+
egressProxyUrl?: string;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Bot 模式配置 (智能体)
|
|
27
|
+
* 用于接收 JSON 格式回调 + 流式回复
|
|
28
|
+
*/
|
|
29
|
+
export type WecomBotConfig = {
|
|
30
|
+
/** 智能机器人 ID(用于 Matrix 模式二次身份确认,webhook 模式) */
|
|
31
|
+
aibotid?: string;
|
|
32
|
+
/** 回调 Token (企微后台生成,webhook 模式必填) */
|
|
33
|
+
token?: string;
|
|
34
|
+
/** 回调加密密钥 (企微后台生成,webhook 模式必填) */
|
|
35
|
+
encodingAESKey?: string;
|
|
36
|
+
/**
|
|
37
|
+
* BotId 列表(可选,用于审计与告警)。
|
|
38
|
+
* - 回调路由优先由 URL + 签名决定;botIds 不参与强制拦截。
|
|
39
|
+
* - 当解密后的 aibotid 不在 botIds 中时,仅记录告警日志。
|
|
40
|
+
*/
|
|
41
|
+
botIds?: string[];
|
|
42
|
+
/** 接收者 ID (可选,用于解密校验) */
|
|
43
|
+
receiveId?: string;
|
|
44
|
+
/** 流式消息占位符 */
|
|
45
|
+
streamPlaceholderContent?: string;
|
|
46
|
+
/** 欢迎语 */
|
|
47
|
+
welcomeText?: string;
|
|
48
|
+
/** DM 策略: 'open' 允许所有人, 'pairing' 需要配对, 'allowlist' 仅允许列表, 'disabled' 禁用 */
|
|
49
|
+
dmPolicy?: 'open' | 'pairing' | 'allowlist' | 'disabled';
|
|
50
|
+
/** 允许的用户列表,为空表示允许所有人 */
|
|
51
|
+
allowFrom?: Array<string | number>;
|
|
52
|
+
/** 连接模式:webhook(默认)或 websocket */
|
|
53
|
+
connectionMode?: 'webhook' | 'websocket';
|
|
54
|
+
/** 机器人 BotID(websocket 模式必填,企微后台获取) */
|
|
55
|
+
botId?: string;
|
|
56
|
+
/** 机器人 Secret(websocket 模式必填,企微后台获取) */
|
|
57
|
+
secret?: string;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Agent 模式配置 (自建应用)
|
|
61
|
+
* 用于接收 XML 格式回调 + API 主动发送
|
|
62
|
+
*/
|
|
63
|
+
export type WecomAgentConfig = {
|
|
64
|
+
/** 企业 ID */
|
|
65
|
+
corpId: string;
|
|
66
|
+
/** 应用 Secret */
|
|
67
|
+
corpSecret: string;
|
|
68
|
+
/** 应用 ID(可选;不填时可接收回调,但主动发送需具备该字段) */
|
|
69
|
+
agentId?: number | string;
|
|
70
|
+
/** 回调 Token (企微后台「设置API接收」) */
|
|
71
|
+
token: string;
|
|
72
|
+
/** 回调加密密钥 (企微后台「设置API接收」) */
|
|
73
|
+
encodingAESKey: string;
|
|
74
|
+
/** 欢迎语 */
|
|
75
|
+
welcomeText?: string;
|
|
76
|
+
/** DM 策略: 'open' 允许所有人, 'pairing' 需要配对, 'allowlist' 仅允许列表, 'disabled' 禁用 */
|
|
77
|
+
dmPolicy?: 'open' | 'pairing' | 'allowlist' | 'disabled';
|
|
78
|
+
/** 允许的用户列表,为空表示允许所有人 */
|
|
79
|
+
allowFrom?: Array<string | number>;
|
|
80
|
+
};
|
|
81
|
+
/** 动态 Agent 配置 */
|
|
82
|
+
export type WecomDynamicAgentsConfig = {
|
|
83
|
+
/** 是否启用动态 Agent */
|
|
84
|
+
enabled?: boolean;
|
|
85
|
+
/** 私聊:是否为每个用户创建独立 Agent */
|
|
86
|
+
dmCreateAgent?: boolean;
|
|
87
|
+
/** 群聊:是否启用动态 Agent */
|
|
88
|
+
groupEnabled?: boolean;
|
|
89
|
+
/** 管理员列表(绕过动态路由,使用主 Agent) */
|
|
90
|
+
adminUsers?: string[];
|
|
91
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WeCom 双模式常量定义
|
|
3
|
+
*/
|
|
4
|
+
/** 固定 Webhook 路径 */
|
|
5
|
+
export declare const WEBHOOK_PATHS: {
|
|
6
|
+
/** Bot 模式历史兼容路径(不再维护) */
|
|
7
|
+
readonly BOT: "/wecom";
|
|
8
|
+
/** Bot 模式历史备用兼容路径(不再维护) */
|
|
9
|
+
readonly BOT_ALT: "/wecom/bot";
|
|
10
|
+
/** Agent 模式历史兼容路径(不再维护) */
|
|
11
|
+
readonly AGENT: "/wecom/agent";
|
|
12
|
+
/** Bot 模式推荐路径前缀 */
|
|
13
|
+
readonly BOT_PLUGIN: "/plugins/wecom/bot";
|
|
14
|
+
/** Agent 模式推荐路径前缀 */
|
|
15
|
+
readonly AGENT_PLUGIN: "/plugins/wecom/agent";
|
|
16
|
+
};
|
|
17
|
+
/** 企业微信 API 端点 */
|
|
18
|
+
export declare const API_ENDPOINTS: {
|
|
19
|
+
readonly GET_TOKEN: "https://qyapi.weixin.qq.com/cgi-bin/gettoken";
|
|
20
|
+
readonly SEND_MESSAGE: "https://qyapi.weixin.qq.com/cgi-bin/message/send";
|
|
21
|
+
readonly SEND_APPCHAT: "https://qyapi.weixin.qq.com/cgi-bin/appchat/send";
|
|
22
|
+
readonly UPLOAD_MEDIA: "https://qyapi.weixin.qq.com/cgi-bin/media/upload";
|
|
23
|
+
readonly DOWNLOAD_MEDIA: "https://qyapi.weixin.qq.com/cgi-bin/media/get";
|
|
24
|
+
};
|
|
25
|
+
/** 各类限制常量 */
|
|
26
|
+
export declare const LIMITS: {
|
|
27
|
+
/** 文本消息最大字节数 */
|
|
28
|
+
readonly TEXT_MAX_BYTES: 2048;
|
|
29
|
+
/** Token 刷新缓冲时间 (提前刷新) */
|
|
30
|
+
readonly TOKEN_REFRESH_BUFFER_MS: 60000;
|
|
31
|
+
/** HTTP 请求超时 */
|
|
32
|
+
readonly REQUEST_TIMEOUT_MS: 15000;
|
|
33
|
+
/** 最大请求体大小 */
|
|
34
|
+
readonly MAX_REQUEST_BODY_SIZE: number;
|
|
35
|
+
};
|
|
36
|
+
/** AES 加密常量 */
|
|
37
|
+
export declare const CRYPTO: {
|
|
38
|
+
/** PKCS#7 块大小 */
|
|
39
|
+
readonly PKCS7_BLOCK_SIZE: 32;
|
|
40
|
+
/** AES Key 长度 */
|
|
41
|
+
readonly AES_KEY_LENGTH: 32;
|
|
42
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WeCom 双模式常量定义
|
|
3
|
+
*/
|
|
4
|
+
/** 固定 Webhook 路径 */
|
|
5
|
+
export const WEBHOOK_PATHS = {
|
|
6
|
+
/** Bot 模式历史兼容路径(不再维护) */
|
|
7
|
+
BOT: "/wecom",
|
|
8
|
+
/** Bot 模式历史备用兼容路径(不再维护) */
|
|
9
|
+
BOT_ALT: "/wecom/bot",
|
|
10
|
+
/** Agent 模式历史兼容路径(不再维护) */
|
|
11
|
+
AGENT: "/wecom/agent",
|
|
12
|
+
/** Bot 模式推荐路径前缀 */
|
|
13
|
+
BOT_PLUGIN: "/plugins/wecom/bot",
|
|
14
|
+
/** Agent 模式推荐路径前缀 */
|
|
15
|
+
AGENT_PLUGIN: "/plugins/wecom/agent",
|
|
16
|
+
};
|
|
17
|
+
/** 企业微信 API 端点 */
|
|
18
|
+
export const API_ENDPOINTS = {
|
|
19
|
+
GET_TOKEN: "https://qyapi.weixin.qq.com/cgi-bin/gettoken",
|
|
20
|
+
SEND_MESSAGE: "https://qyapi.weixin.qq.com/cgi-bin/message/send",
|
|
21
|
+
SEND_APPCHAT: "https://qyapi.weixin.qq.com/cgi-bin/appchat/send",
|
|
22
|
+
UPLOAD_MEDIA: "https://qyapi.weixin.qq.com/cgi-bin/media/upload",
|
|
23
|
+
DOWNLOAD_MEDIA: "https://qyapi.weixin.qq.com/cgi-bin/media/get",
|
|
24
|
+
};
|
|
25
|
+
/** 各类限制常量 */
|
|
26
|
+
export const LIMITS = {
|
|
27
|
+
/** 文本消息最大字节数 */
|
|
28
|
+
TEXT_MAX_BYTES: 2048,
|
|
29
|
+
/** Token 刷新缓冲时间 (提前刷新) */
|
|
30
|
+
TOKEN_REFRESH_BUFFER_MS: 60_000,
|
|
31
|
+
/** HTTP 请求超时 */
|
|
32
|
+
REQUEST_TIMEOUT_MS: 15_000,
|
|
33
|
+
/** 最大请求体大小 */
|
|
34
|
+
MAX_REQUEST_BODY_SIZE: 1024 * 1024,
|
|
35
|
+
};
|
|
36
|
+
/** AES 加密常量 */
|
|
37
|
+
export const CRYPTO = {
|
|
38
|
+
/** PKCS#7 块大小 */
|
|
39
|
+
PKCS7_BLOCK_SIZE: 32,
|
|
40
|
+
/** AES Key 长度 */
|
|
41
|
+
AES_KEY_LENGTH: 32,
|
|
42
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WeCom 类型统一导出
|
|
3
|
+
*/
|
|
4
|
+
export * from "./constants.js";
|
|
5
|
+
export type { WecomMediaConfig, WecomNetworkConfig, WecomBotConfig, WecomAgentConfig, } from "./config.js";
|
|
6
|
+
export type { ResolvedAgentAccount, } from "./account.js";
|
|
7
|
+
export type { WecomBotInboundBase, WecomBotInboundText, WecomBotInboundVoice, WecomBotInboundVideo, WecomBotInboundStreamRefresh, WecomBotInboundEvent, WecomBotInboundMessage, WecomAgentInboundMessage, WecomInboundQuote, WecomTemplateCard, WecomOutboundMessage, } from "./message.js";
|