@ai-setting/roy-agent-core 1.7.76 → 1.7.85
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/config/config-protection.d.ts +89 -0
- package/dist/config/index.d.ts +1 -0
- package/dist/config/index.js +16 -0
- package/dist/env/agent/agent-component.d.ts +42 -21
- package/dist/env/agent/agent-component.js +3 -3
- package/dist/env/agent/index.js +3 -3
- package/dist/env/agent/types.d.ts +32 -12
- package/dist/env/error-reply.d.ts +6 -0
- package/dist/env/error-reply.js +1 -1
- package/dist/env/event-source/bounty-im-handler.d.ts +8 -0
- package/dist/env/event-source/es-command.d.ts +39 -0
- package/dist/env/event-source/es-plugins.d.ts +68 -0
- package/dist/env/event-source/event-source-component.d.ts +15 -0
- package/dist/env/event-source/event-source-handlers.d.ts +68 -0
- package/dist/env/event-source/event-source-handlers.js +22 -10
- package/dist/env/event-source/index.d.ts +5 -1
- package/dist/env/event-source/index.js +59 -10
- package/dist/env/event-source/types.d.ts +101 -1
- package/dist/env/event-source/wecom-card-action-handler.d.ts +141 -0
- package/dist/env/event-source/wecom-card-builder.d.ts +220 -0
- package/dist/env/event-source/wecom-cli-handler.d.ts +266 -0
- package/dist/env/event-source/wecom-cli-sender.d.ts +88 -0
- package/dist/env/event-source/wecom-media.d.ts +130 -0
- package/dist/env/event-source/wecom-respond-registry.d.ts +151 -0
- package/dist/env/event-source/wecom-ws-bridge.d.ts +213 -0
- package/dist/env/index.js +19 -17
- package/dist/env/task/backend/index.js +37 -1
- package/dist/env/task/backend/task-store-backend.d.ts +12 -0
- package/dist/env/task/backend/types.d.ts +19 -0
- package/dist/env/task/delegate/delegate-tool.d.ts +62 -2
- package/dist/env/task/delegate/delegate-tool.js +3 -1
- package/dist/env/task/delegate/index.js +1 -1
- package/dist/env/task/index.js +4 -4
- package/dist/env/task/mcp/index.js +3 -0
- package/dist/env/task/mcp/task-mcp-server.d.ts +1 -1
- package/dist/env/task/plugins/fake-user-message-helper.d.ts +10 -2
- package/dist/env/task/plugins/index.d.ts +3 -0
- package/dist/env/task/plugins/index.js +36 -4
- package/dist/env/task/plugins/lark-card-patch-notifier.d.ts +41 -2
- package/dist/env/task/plugins/lark-card-patch-notifier.js +1 -1
- package/dist/env/task/plugins/lark-cli-task-notify-plugin.d.ts +16 -0
- package/dist/env/task/plugins/task-op-watermark-plugin.d.ts +112 -0
- package/dist/env/task/plugins/wecom-card-state-store.d.ts +121 -0
- package/dist/env/task/plugins/wecom-task-notify-plugin.d.ts +224 -0
- package/dist/env/task/storage/index.js +2 -2
- package/dist/env/task/storage/sqlite-task-store.d.ts +54 -0
- package/dist/env/task/task-component.d.ts +16 -0
- package/dist/env/task/tools/operation/operation-types.d.ts +13 -13
- package/dist/env/tool/built-in/index.d.ts +2 -0
- package/dist/env/tool/built-in/index.js +6 -1
- package/dist/env/tool/index.js +3 -2
- package/dist/env/workflow/index.js +3 -3
- package/dist/env/workflow/plugins/index.js +2 -2
- package/dist/env/workflow/tools/index.js +2 -2
- package/dist/env/workflow/types/event.d.ts +4 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +71 -20
- package/dist/shared/@ai-setting/{roy-agent-core-v87njmq7.js → roy-agent-core-0dh8npnz.js} +1 -1
- package/dist/shared/@ai-setting/roy-agent-core-2stt4rp5.js +134 -0
- package/dist/shared/@ai-setting/{roy-agent-core-mww0mnd9.js → roy-agent-core-7bpj859q.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-nmx9934r.js → roy-agent-core-7dd0s3xb.js} +15 -5
- package/dist/shared/@ai-setting/{roy-agent-core-ghnagzrh.js → roy-agent-core-b31v3qpv.js} +853 -48
- package/dist/shared/@ai-setting/{roy-agent-core-cxy143m4.js → roy-agent-core-begm7ytz.js} +63 -2
- package/dist/shared/@ai-setting/{roy-agent-core-aa2j7bk9.js → roy-agent-core-cha0jc7n.js} +40 -26
- package/dist/shared/@ai-setting/{roy-agent-core-p3zv1mx5.js → roy-agent-core-d1n4jbza.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-q5r75vev.js → roy-agent-core-dkrvmjsh.js} +17 -75
- package/dist/shared/@ai-setting/{roy-agent-core-abqhkbn3.js → roy-agent-core-ezvpreqz.js} +23 -0
- package/dist/shared/@ai-setting/{roy-agent-core-99nwgj84.js → roy-agent-core-fzw9xzm1.js} +1 -1
- package/dist/shared/@ai-setting/roy-agent-core-h21gngxf.js +247 -0
- package/dist/shared/@ai-setting/roy-agent-core-m3vjepzz.js +544 -0
- package/dist/shared/@ai-setting/{roy-agent-core-zycadbpx.js → roy-agent-core-p6ks90tb.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-ntq0gz7q.js → roy-agent-core-q813vafd.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-z43aj03k.js → roy-agent-core-qk9hd89p.js} +21 -2
- package/dist/shared/@ai-setting/{roy-agent-core-6cy43nss.js → roy-agent-core-rykf7c04.js} +49 -9
- package/dist/shared/@ai-setting/{roy-agent-core-8ez6mccb.js → roy-agent-core-tk98f92b.js} +4 -0
- package/dist/shared/@ai-setting/{roy-agent-core-bxkjk7zx.js → roy-agent-core-vx8zc847.js} +4 -1
- package/dist/shared/@ai-setting/roy-agent-core-vyr6svt6.js +31 -0
- package/dist/shared/@ai-setting/{roy-agent-core-t3rrb959.js → roy-agent-core-xf6z96qy.js} +2086 -163
- package/package.json +10 -1
- package/dist/shared/@ai-setting/roy-agent-core-f77k3gqt.js +0 -71
- /package/dist/shared/@ai-setting/{roy-agent-core-b4x6x4k2.js → roy-agent-core-m1fgtnrc.js} +0 -0
- /package/dist/shared/@ai-setting/{roy-agent-core-v85n42ve.js → roy-agent-core-ze5cqpf2.js} +0 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Task #5203 — 企微真卡片(template_card)构造器
|
|
3
|
+
*
|
|
4
|
+
* ## 为什么存在
|
|
5
|
+
*
|
|
6
|
+
* #5193 用 markdown 模拟进展卡片(wecom-cli msg_type 无 template_card)。
|
|
7
|
+
* Task #5202 官方文档实锤:aibot WS 长连接协议**原生支持** template_card
|
|
8
|
+
* (aibot_send_msg 直发 + aibot_respond_update_msg 更新)——
|
|
9
|
+
* wecom-cli 只是没封装,走 WecomWsBridge 直发即可。
|
|
10
|
+
*
|
|
11
|
+
* ## 协议依据(官方文档取证,Task #5202/#5203)
|
|
12
|
+
*
|
|
13
|
+
* - 主动推送(101463):`{cmd:"aibot_send_msg", headers:{req_id},
|
|
14
|
+
* body:{chatid, chat_type, msgtype:"template_card", template_card:{...}}}`
|
|
15
|
+
* chat_type:1 单聊(userid)/ 2 群聊 / 0 兼容
|
|
16
|
+
* - 模板卡片类型(101032):
|
|
17
|
+
* - text_notice:main_title / emphasis_content 必填,card_action 必填
|
|
18
|
+
* - button_interaction:main_title / button_list / task_id 必填,
|
|
19
|
+
* button_list ≤6,按钮 key 最长 1024 字节不可重复
|
|
20
|
+
* - task_id:同一个机器人不能重复,只能由数字字母和 "_-@" 组成,≤128 字节
|
|
21
|
+
* - 卡片更新(101463):`{cmd:"aibot_respond_update_msg",
|
|
22
|
+
* headers:{req_id:事件回调透传}, body:{response_type:"update_template_card",
|
|
23
|
+
* template_card:{...}}}` — 仅适用于 template_card_event,5 秒内回复;
|
|
24
|
+
* template_card.task_id 须与回调收到的 task_id 一致
|
|
25
|
+
* - 按钮点击回调:template_card_event,event.event_key = 按钮 key
|
|
26
|
+
*
|
|
27
|
+
* ## task_id 策略
|
|
28
|
+
*
|
|
29
|
+
* 企微的 task_id 是「同一张卡的标识」(更新时用),而我们的任务
|
|
30
|
+
* 生命周期会**多次发卡**(每次进展一张新卡,企微无主动 patch)。
|
|
31
|
+
* ⇒ 发卡时用 `newWecomCardTaskId(taskId)`(带序号/时间戳,保证不重复);
|
|
32
|
+
* 更新时必须用**回调里带回的 task_id**(buildWecomCardUpdateFrame 的
|
|
33
|
+
* taskId 参数 = 回调解析出的原始 task_id)。
|
|
34
|
+
*
|
|
35
|
+
* @see wecom-ws-bridge.ts(帧发送)
|
|
36
|
+
* @see wecom-task-notify-plugin.ts(hook 接线)
|
|
37
|
+
*/
|
|
38
|
+
/** 标准按钮动作 key(回调 event_key 据此分发) */
|
|
39
|
+
export declare const WecomCardActionKey: {
|
|
40
|
+
/** 查看详情 */
|
|
41
|
+
readonly VIEW: "task_view";
|
|
42
|
+
/** 标记完成 */
|
|
43
|
+
readonly MARK_DONE: "task_mark_done";
|
|
44
|
+
/** 重新打开 */
|
|
45
|
+
readonly REOPEN: "task_reopen";
|
|
46
|
+
};
|
|
47
|
+
export type WecomCardActionKey = (typeof WecomCardActionKey)[keyof typeof WecomCardActionKey];
|
|
48
|
+
/** 企微按钮 style:1 默认蓝 / 2 次要灰 / 3 / 4 强调(官方 1~4) */
|
|
49
|
+
export type WecomButtonStyle = 1 | 2 | 3 | 4;
|
|
50
|
+
export interface WecomCardButton {
|
|
51
|
+
text: string;
|
|
52
|
+
style?: WecomButtonStyle;
|
|
53
|
+
key: string;
|
|
54
|
+
}
|
|
55
|
+
export interface WecomHorizontalContent {
|
|
56
|
+
keyname: string;
|
|
57
|
+
value: string;
|
|
58
|
+
type?: 0 | 1;
|
|
59
|
+
url?: string;
|
|
60
|
+
}
|
|
61
|
+
export interface WecomMilestoneLite {
|
|
62
|
+
title: string;
|
|
63
|
+
timestamp: string;
|
|
64
|
+
description?: string;
|
|
65
|
+
}
|
|
66
|
+
/** text_notice 卡片结构(只声明我们用到的字段) */
|
|
67
|
+
export interface WecomTextNoticeCard {
|
|
68
|
+
card_type: "text_notice";
|
|
69
|
+
main_title: {
|
|
70
|
+
title: string;
|
|
71
|
+
desc?: string;
|
|
72
|
+
};
|
|
73
|
+
emphasis_content: {
|
|
74
|
+
title: string;
|
|
75
|
+
desc: string;
|
|
76
|
+
};
|
|
77
|
+
sub_title_text?: string;
|
|
78
|
+
horizontal_content_list: WecomHorizontalContent[];
|
|
79
|
+
quote_area: {
|
|
80
|
+
type?: number;
|
|
81
|
+
title: string;
|
|
82
|
+
quote_text: string;
|
|
83
|
+
};
|
|
84
|
+
card_action: {
|
|
85
|
+
type: number;
|
|
86
|
+
url?: string;
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/** button_interaction 卡片结构(只声明我们用到的字段) */
|
|
90
|
+
export interface WecomButtonInteractionCard {
|
|
91
|
+
card_type: "button_interaction";
|
|
92
|
+
main_title: {
|
|
93
|
+
title: string;
|
|
94
|
+
desc?: string;
|
|
95
|
+
};
|
|
96
|
+
sub_title_text?: string;
|
|
97
|
+
horizontal_content_list?: WecomHorizontalContent[];
|
|
98
|
+
quote_area?: {
|
|
99
|
+
type?: number;
|
|
100
|
+
title: string;
|
|
101
|
+
quote_text: string;
|
|
102
|
+
};
|
|
103
|
+
button_list: WecomCardButton[];
|
|
104
|
+
card_action?: {
|
|
105
|
+
type: number;
|
|
106
|
+
url?: string;
|
|
107
|
+
};
|
|
108
|
+
/** ★ 必填:同一机器人不能重复;更新时须与回调一致 */
|
|
109
|
+
task_id: string;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* 任务的稳定 task_id 前缀:`roy-task-<taskId>`。
|
|
113
|
+
*
|
|
114
|
+
* 用途:
|
|
115
|
+
* - 发卡时作为 `newWecomCardTaskId` 的前缀(可从回调 task_id 反解任务号)
|
|
116
|
+
* - 更新时**不能**用它(发卡用的是带序号的全量 id)——
|
|
117
|
+
* 更新帧的 task_id 必须原样用回调带回的值
|
|
118
|
+
*/
|
|
119
|
+
export declare function wecomCardTaskIdForTask(taskId: number): string;
|
|
120
|
+
/**
|
|
121
|
+
* 生成一张新卡的 task_id:`roy-task-<taskId>-<seq>-<rand>`。
|
|
122
|
+
*
|
|
123
|
+
* 协议硬约束:同一机器人不能重复 → 加序号 + 随机后缀。
|
|
124
|
+
* 字符集:数字字母和 "_-@"(官方 101032)。
|
|
125
|
+
*/
|
|
126
|
+
export declare function newWecomCardTaskId(taskId: number): string;
|
|
127
|
+
/**
|
|
128
|
+
* 构造 text_notice 进展卡(任务状态一览)。
|
|
129
|
+
*
|
|
130
|
+
* 信息密度对齐 #5193 markdown 卡:标题+进度+状态+时间线+最新操作。
|
|
131
|
+
*/
|
|
132
|
+
export declare function buildWecomTextNoticeCard(input: {
|
|
133
|
+
taskId: number;
|
|
134
|
+
title: string;
|
|
135
|
+
statusLabel: string;
|
|
136
|
+
progress: number;
|
|
137
|
+
description?: string;
|
|
138
|
+
changeSummary?: string;
|
|
139
|
+
milestones: WecomMilestoneLite[];
|
|
140
|
+
/** 终态(completed/cancelled/…)→ sub_title_text 收尾文案 */
|
|
141
|
+
terminal?: boolean;
|
|
142
|
+
}): WecomTextNoticeCard;
|
|
143
|
+
/**
|
|
144
|
+
* 构造 button_interaction 交互卡(可点击按钮更新状态)。
|
|
145
|
+
*
|
|
146
|
+
* @param input.taskId 用于生成 task_id(回调据此反解任务)
|
|
147
|
+
* @param input.buttons 超过 6 个时只取前 6 个(协议上限)
|
|
148
|
+
*/
|
|
149
|
+
export declare function buildWecomButtonInteractionCard(input: {
|
|
150
|
+
taskId: number;
|
|
151
|
+
title: string;
|
|
152
|
+
statusLabel: string;
|
|
153
|
+
progress: number;
|
|
154
|
+
description?: string;
|
|
155
|
+
changeSummary?: string;
|
|
156
|
+
milestones?: WecomMilestoneLite[];
|
|
157
|
+
buttons: WecomCardButton[];
|
|
158
|
+
/** 已有的 task_id(更新场景复用;发新卡时不传自动生成) */
|
|
159
|
+
cardTaskId?: string;
|
|
160
|
+
}): WecomButtonInteractionCard;
|
|
161
|
+
/**
|
|
162
|
+
* 构造 aibot_send_msg 直发 template_card 帧。
|
|
163
|
+
*
|
|
164
|
+
* @param input.chatType "single"(chat_type=1,chatid=userid)|
|
|
165
|
+
* "group"(chat_type=2,chatid=群 chatid)
|
|
166
|
+
*/
|
|
167
|
+
export declare function buildWecomSendCardFrame(input: {
|
|
168
|
+
chatId: string;
|
|
169
|
+
chatType: "single" | "group";
|
|
170
|
+
card: WecomTextNoticeCard | WecomButtonInteractionCard;
|
|
171
|
+
/** 显式 req_id(缺省自动生成 aibot_send_msg_ 前缀) */
|
|
172
|
+
reqId?: string;
|
|
173
|
+
}): {
|
|
174
|
+
cmd: string;
|
|
175
|
+
headers: {
|
|
176
|
+
req_id: string;
|
|
177
|
+
};
|
|
178
|
+
body: Record<string, unknown>;
|
|
179
|
+
};
|
|
180
|
+
/**
|
|
181
|
+
* 构造 aibot_respond_update_msg 更新帧(按钮点击后 5 秒窗口内)。
|
|
182
|
+
*
|
|
183
|
+
* ★ 协议硬约束:
|
|
184
|
+
* - req_id 必须**原样透传** template_card_event 回调帧的 req_id
|
|
185
|
+
* - template_card.task_id 必须与回调收到的 task_id 一致
|
|
186
|
+
* (回调的 task_id 是发卡时的全量 id,不是 roy-task-<n> 前缀)
|
|
187
|
+
*
|
|
188
|
+
* @param input.taskId 回调解析出的**原始** task_id(原样回填)
|
|
189
|
+
*/
|
|
190
|
+
export declare function buildWecomCardUpdateFrame(input: {
|
|
191
|
+
reqId: string;
|
|
192
|
+
/** 回调带回的原始 task_id(原样回填,不做转换) */
|
|
193
|
+
taskId: string;
|
|
194
|
+
statusLabel: string;
|
|
195
|
+
note?: string;
|
|
196
|
+
/** 更新后的按钮列表(如把「标记完成」换成「已确认」) */
|
|
197
|
+
buttonOverrides?: WecomCardButton[];
|
|
198
|
+
/**
|
|
199
|
+
* ★ Task #5203 时间线详情:操作记录(milestones + processSteps)
|
|
200
|
+
* 渲染成 quote_area 引用文本(每行一条,最新在上)。
|
|
201
|
+
*/
|
|
202
|
+
timeline?: WecomMilestoneLite[];
|
|
203
|
+
/**
|
|
204
|
+
* ★ Task #5273:任务标题(可选)。
|
|
205
|
+
*
|
|
206
|
+
* 用户反馈更新卡缺任务号/标题 → main_title 从
|
|
207
|
+
* `📋 ${statusLabel}` 升级为
|
|
208
|
+
* `📋 Task #<n> · <title> · ${statusLabel}`(缺省字段自动省略,
|
|
209
|
+
* 向后兼容旧调用方)。
|
|
210
|
+
*/
|
|
211
|
+
taskTitle?: string;
|
|
212
|
+
/** ★ Task #5273:roy 任务号(可选,与 taskTitle 配对渲染) */
|
|
213
|
+
taskNumber?: number;
|
|
214
|
+
}): {
|
|
215
|
+
cmd: string;
|
|
216
|
+
headers: {
|
|
217
|
+
req_id: string;
|
|
218
|
+
};
|
|
219
|
+
body: Record<string, unknown>;
|
|
220
|
+
};
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Task #5142 — `wecom-cli` EventSource handler
|
|
3
|
+
*
|
|
4
|
+
* ## 定位
|
|
5
|
+
*
|
|
6
|
+
* 企业微信(WeCom)官方 CLI 是 `@wecom/cli`(Rust 编译,npm 名 `@wecom/cli`,
|
|
7
|
+
* 二进制名 `wecom-cli`,仓库 https://github.com/WeComTeam/wecom-cli)。
|
|
8
|
+
* 本 handler 把企微接入 roy-agent 的 EventSource 体系,esType = **`wecom-cli`**。
|
|
9
|
+
*
|
|
10
|
+
* ## 为什么 esType 必须是新类型名(不能抄 `lark-cli`)
|
|
11
|
+
*
|
|
12
|
+
* `getHandlerForEsType(esType)` 是 v2 notify prompt 的**唯一分派 key**。
|
|
13
|
+
* 若落库写成 `lark-cli`,主 agent 会拿到「用 lark-cli 发到 oc_xxx」的 prompt ——
|
|
14
|
+
* 而企微根本没有 `oc_xxx`,agent 必然发不出去。
|
|
15
|
+
*
|
|
16
|
+
* ## 入站 transport 三方案(调研结论)
|
|
17
|
+
*
|
|
18
|
+
* | 方案 | 机制 | 结论 |
|
|
19
|
+
* |---|---|---|
|
|
20
|
+
* | A | 企微 webhook 回调 URL | 需**公网可达 URL** + 消息加解密(EncodingAESKey)。本机是内网 → 不可直接用 |
|
|
21
|
+
* | B | 轮询 `wecom-cli chat messages list` | 只支持**最近 7 天**,且需先知道 `chat_id` → 只能作补充,不能作主路径 |
|
|
22
|
+
* | C | 第三方桥接(CLI-WeChat-Bridge / golembot) | 依赖非官方项目(AGPL-3.0 传染风险)→ 只作参考实现 |
|
|
23
|
+
* | **D(采用)** | **企微长连接 WebSocket** `wss://openws.work.weixin.qq.com` | ✅ **无需公网 IP**、无需加解密;官方 SDK `@wecom/aibot-node-sdk` 已封装认证/心跳/重连/reply 串行队列 |
|
|
24
|
+
*
|
|
25
|
+
* 企微官方文档:
|
|
26
|
+
* - 长连接:https://developer.work.weixin.qq.com/document/path/101463
|
|
27
|
+
* - 接收消息(webhook 模式):https://developer.work.weixin.qq.com/document/path/100719
|
|
28
|
+
* - 官方 Node SDK:https://www.npmjs.com/package/@wecom/aibot-node-sdk
|
|
29
|
+
*
|
|
30
|
+
* ## 长连接协议要点(来自官方文档 101463)
|
|
31
|
+
*
|
|
32
|
+
* - 握手:`wss://openws.work.weixin.qq.com` → 发送 `aibot_subscribe`(body: bot_id + secret)
|
|
33
|
+
* - 入站:`aibot_msg_callback`(消息)/ `aibot_event_callback`(事件)
|
|
34
|
+
* - 出站:`aibot_respond_msg`(被动回复,**必须透传回调的 req_id**)/
|
|
35
|
+
* `aibot_send_msg`(主动推送)/ `aibot_respond_welcome_msg` / `aibot_respond_update_msg`
|
|
36
|
+
* - 心跳:`ping`,建议 30s
|
|
37
|
+
* - **每个机器人同一时刻只允许 1 条长连接**(新连接会踢掉旧连接)⇒ 必须做单实例互斥
|
|
38
|
+
* - 收到回调后 **24h 内**可回复该会话;限频 **30 条/分钟、1000 条/小时**
|
|
39
|
+
* - 流式消息需 10 分钟内 `finish=true`
|
|
40
|
+
*
|
|
41
|
+
* ## 本 handler 的边界(Phase 1 骨架)
|
|
42
|
+
*
|
|
43
|
+
* Phase 1 只做「spawn 子进程 + 解析行 JSON + 产出 EventSourceEvent」:
|
|
44
|
+
* 入站通过 `wecom-cli` 的**行 JSON 输出**(未来由长连接桥接进程产出 NDJSON),
|
|
45
|
+
* 出站由 notify prompt 指导 agent 直接调 `wecom-cli message aibot send --json ...`。
|
|
46
|
+
*
|
|
47
|
+
* @see packages/core/src/env/event-source/wecom-cli-handler.test.ts
|
|
48
|
+
*/
|
|
49
|
+
import type { EventSourceHandler, EventSourceConfig, EventMetadata, ReplyChannel } from "./types";
|
|
50
|
+
/** 企微事件源类型名(必须是**新**类型名,不能抄 lark-cli) */
|
|
51
|
+
export declare const WECOM_CLI_ES_TYPE = "wecom-cli";
|
|
52
|
+
/**
|
|
53
|
+
* 企微 chatId 前缀。
|
|
54
|
+
*
|
|
55
|
+
* 企微的 `chatid` 与飞书的 `oc_xxx` 语义不同(企微群聊 id 是业务方自己的
|
|
56
|
+
* chatid,单聊则是 userid),下游按前缀即可反解「这是企微会话」,
|
|
57
|
+
* 避免与 lark / cli 的 chatId 撞车。
|
|
58
|
+
*/
|
|
59
|
+
export declare const WECOM_CHAT_ID_PREFIX = "wecom:";
|
|
60
|
+
/** 企微长连接默认地址(官方文档 101463) */
|
|
61
|
+
export declare const WECOM_WS_DEFAULT_URL = "wss://openws.work.weixin.qq.com";
|
|
62
|
+
interface WecomCliConfig {
|
|
63
|
+
botId?: string;
|
|
64
|
+
secret?: string;
|
|
65
|
+
/** 覆盖默认命令(高级用法) */
|
|
66
|
+
command?: string;
|
|
67
|
+
/** 群机器人 webhook(可选) */
|
|
68
|
+
webhook?: string;
|
|
69
|
+
/**
|
|
70
|
+
* 独立凭据文件路径(Task #5228)。
|
|
71
|
+
*
|
|
72
|
+
* 多 bot 场景(如 wecom-bot-2)用独立凭据文件隔离凭据;
|
|
73
|
+
* 曾是 dead config(声明≠实现),现已接入 resolveWecomCredentials。
|
|
74
|
+
*/
|
|
75
|
+
credentialsPath?: string;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* 从 EventSourceConfig 读取企微相关字段。
|
|
79
|
+
*
|
|
80
|
+
* 优先级:顶级字段 → `options.*`(CLI 透传路径)。
|
|
81
|
+
*/
|
|
82
|
+
export declare function readWecomCliConfig(config: EventSourceConfig): WecomCliConfig;
|
|
83
|
+
/** 默认凭据文件路径(~/.roy-agent/wecom-credentials.json,权限 600) */
|
|
84
|
+
export declare const WECOM_CREDENTIALS_DEFAULT_PATH: string;
|
|
85
|
+
/** 凭据文件解析结果 */
|
|
86
|
+
export interface WecomCredentials {
|
|
87
|
+
botId: string;
|
|
88
|
+
secret: string;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* 读取企微凭据文件(不抛)。
|
|
92
|
+
*
|
|
93
|
+
* 文件形态:`{"agent_id":"1000003","secret":"..."}`
|
|
94
|
+
* (agent_id 即智能机器人 BotID)。
|
|
95
|
+
*
|
|
96
|
+
* 缺失 / 坏 JSON / 字段不全 → null(调用方据此降级)。
|
|
97
|
+
*/
|
|
98
|
+
export declare function readWecomCredentialsFile(path: string): WecomCredentials | null;
|
|
99
|
+
/**
|
|
100
|
+
* 解析企微凭据(优先级:config 显式配置 > options.credentialsPath
|
|
101
|
+
* 独立凭据文件 > 默认凭据文件)。
|
|
102
|
+
*
|
|
103
|
+
* ★ Task #5228:`options.credentialsPath` 曾是 dead config——
|
|
104
|
+
* wecom-bot-2 配置了独立凭据文件却仍用生产默认凭据连接 → 无回复。
|
|
105
|
+
* 声明 ≠ 实现(与 #4946 provider temperature 同类坑)。
|
|
106
|
+
*
|
|
107
|
+
* 全缺 → undefined:调用方(start)据此**优雅降级**为骨架模式,
|
|
108
|
+
* 不 crash、不假装在收消息。
|
|
109
|
+
*/
|
|
110
|
+
export declare function resolveWecomCredentials(config: EventSourceConfig, credentialsPath?: string): WecomCredentials | undefined;
|
|
111
|
+
/** `buildWecomReplyChannel` 的入参 */
|
|
112
|
+
export interface WecomReplyChannelInput {
|
|
113
|
+
/** 企微会话 id(群聊有,单聊通常无) */
|
|
114
|
+
chatId?: string;
|
|
115
|
+
/** 消息 id(msgid) */
|
|
116
|
+
messageId?: string;
|
|
117
|
+
/** 会话类型:single / group */
|
|
118
|
+
chatType?: string;
|
|
119
|
+
/** 发送者 userid */
|
|
120
|
+
userId?: string;
|
|
121
|
+
/**
|
|
122
|
+
* ★ Task #5311(CV M1):事件源 ID —— 失败回执的 WS fallback 需要它
|
|
123
|
+
* 反查注册实例(`getWecomRespondTarget(sourceId)`)。缺了只剩 CLI 一条路。
|
|
124
|
+
*/
|
|
125
|
+
sourceId?: string;
|
|
126
|
+
/** ★ Task #5311:原始 req_id —— WS 被动回帧(带引用语义)需要 */
|
|
127
|
+
reqId?: string;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* 构造企微的 replyChannel。
|
|
131
|
+
*
|
|
132
|
+
* ★ 硬约束:`_captureReplyChannelSnapshot()`(delegate-tool.ts:2864)要求
|
|
133
|
+
* `chatId || messageId` **至少一个非空**,否则整份快照被丢弃 →
|
|
134
|
+
* notify event 无回复目标 → 规则通知静默丢失。
|
|
135
|
+
*
|
|
136
|
+
* 因此本函数**恒不返回 chatId/messageId 双空**:
|
|
137
|
+
* - 群聊 → chatId = 原始 `chatid`
|
|
138
|
+
* - 单聊 → chatId = 原始 `userid`(企微单聊的回复目标就是 userid)
|
|
139
|
+
* - 全空 → chatId = `"wecom:unknown"`(占位,保证下游不丢快照)
|
|
140
|
+
*
|
|
141
|
+
* ⚠️ **`chatId` 必须是「可直接用于回复命令」的原始值**,不能加前缀:
|
|
142
|
+
* `extractReplyContext()` 返回的 chatId 会一路流到 notify prompt,
|
|
143
|
+
* agent 会把它原样塞进 `wecom-cli message aibot send --json '{"chat_id":"..."}'`。
|
|
144
|
+
* 加了前缀就等于给 agent 塞了个错误的 chat_id。
|
|
145
|
+
*
|
|
146
|
+
* 渠道区分靠 `type: "wecom-cli"` + `params.chatType`,
|
|
147
|
+
* 原始值同时冗余存进 `params.wecomChatId` / `params.wecomUserId`。
|
|
148
|
+
*/
|
|
149
|
+
export declare function buildWecomReplyChannel(input: WecomReplyChannelInput): ReplyChannel;
|
|
150
|
+
/** 解析结果 */
|
|
151
|
+
export interface ParsedWecomFrame {
|
|
152
|
+
metadata: EventMetadata;
|
|
153
|
+
text: string;
|
|
154
|
+
/** 原始 req_id(长连接回复必须透传) */
|
|
155
|
+
reqId?: string;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* ★ Task #5271:构建企微 user message(payload.message)。
|
|
159
|
+
*
|
|
160
|
+
* 企微的 user message 是**纯文本**(lark 是
|
|
161
|
+
* JSON.stringify({rawData, recommendedAction}),recommendedAction
|
|
162
|
+
* 能进 user message;企微不能)。#5251 把 quotedText 挂到
|
|
163
|
+
* recommendedAction.replyContext,但 LLM 永远看不到
|
|
164
|
+
* (#5212 MAJOR-2 同型问题在企微重演)。
|
|
165
|
+
*
|
|
166
|
+
* replyContext 存在时把引用内容拼进正文,格式:
|
|
167
|
+
*
|
|
168
|
+
* [用户正文]
|
|
169
|
+
*
|
|
170
|
+
* [引用消息] 被引用的原文内容
|
|
171
|
+
*
|
|
172
|
+
* 无 replyContext 时返回原正文(字节不变,向后兼容)。
|
|
173
|
+
*/
|
|
174
|
+
export declare function buildWecomUserMessage(text: string, replyContext?: {
|
|
175
|
+
quotedText?: string;
|
|
176
|
+
}): string;
|
|
177
|
+
/**
|
|
178
|
+
* 解析企微长连接帧 → EventSourceEvent 元数据。
|
|
179
|
+
*
|
|
180
|
+
* 无效帧返回 `null`(不抛)—— 非法输入不能拖垮整个事件源。
|
|
181
|
+
*
|
|
182
|
+
* @param frame 原始帧(`aibot_msg_callback` / `aibot_event_callback`)
|
|
183
|
+
*/
|
|
184
|
+
export declare function parseWecomFrame(raw: unknown): ParsedWecomFrame | null;
|
|
185
|
+
/** 企微渠道 system prompt 的章节标题(lean 卡片指回这里) */
|
|
186
|
+
export declare const WECOM_CHANNEL_PROMPT_SECTION_TITLE = "\u2B50 \u4F01\u4E1A\u5FAE\u4FE1\u56DE\u590D\u89C4\u8303\uFF08system prompt \u6CE8\u5165\u7248\uFF09";
|
|
187
|
+
/** `buildWecomChannelSystemPrompt` 入参(只允许渠道级配置,禁 event) */
|
|
188
|
+
export interface WecomChannelSystemPromptInput {
|
|
189
|
+
/** 智能机器人 BotID(渠道身份,可选) */
|
|
190
|
+
botId?: string;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* 构造企微渠道的 system prompt(单点构造,只读渠道配置)。
|
|
194
|
+
*
|
|
195
|
+
* ★ 与 buildLarkChannelSystemPrompt 同款纪律:
|
|
196
|
+
* - 构造入参**只来自渠道配置**,禁止读 event/rawEvent
|
|
197
|
+
* (否则字节漂移破坏 LLM prompt cache 前缀一致性);
|
|
198
|
+
* - 同一入参多次调用**逐字节一致**;
|
|
199
|
+
* - per-event 动态部分(chatId/messageId/正文)只进
|
|
200
|
+
* recommendedAction 动态卡片,不进这里。
|
|
201
|
+
*
|
|
202
|
+
* 内容:回复命令模板 + chatId 语义(单聊=userid / 群聊=chatid)+
|
|
203
|
+
* 出站纪律 + 文档交付规范(文档类生成不落本地路径,给企微可读内容)。
|
|
204
|
+
*/
|
|
205
|
+
export declare function buildWecomChannelSystemPrompt(opts: WecomChannelSystemPromptInput): string;
|
|
206
|
+
/** `buildWecomNotifyPrompt` 入参 */
|
|
207
|
+
export interface WecomNotifyPromptInput {
|
|
208
|
+
chatId?: string;
|
|
209
|
+
messageId?: string;
|
|
210
|
+
chatType?: string;
|
|
211
|
+
userId?: string;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* 构造 notify prompt —— 四段式(与 lark-cli 版结构对齐,内容换成企微)。
|
|
215
|
+
*
|
|
216
|
+
* ① 用哪个 CLI 回消息
|
|
217
|
+
* ② 什么 flag(--json / chat_id / msg_type / markdown.content)
|
|
218
|
+
* ③ 禁什么(禁 curl、禁直连 qyapi、禁 lark-cli flag)
|
|
219
|
+
* ④ 怎么判成功(exit code + 解析 error.type / error.code)
|
|
220
|
+
*/
|
|
221
|
+
export declare function buildWecomNotifyPrompt(input: WecomNotifyPromptInput): string;
|
|
222
|
+
/**
|
|
223
|
+
* 探测 `wecom-cli` 二进制是否存在。
|
|
224
|
+
*
|
|
225
|
+
* 为什么需要预探测:`spawn("exec <cmd>", {shell:true})` 在二进制缺失时
|
|
226
|
+
* **不会同步抛错**,而是让 shell 以 exit 127 退出 —— 事件源会短暂进入
|
|
227
|
+
* "running" 再变 error,状态闪烁且 CLI status 展示不及时。
|
|
228
|
+
*
|
|
229
|
+
* 预探测让「二进制不存在」在 start() 里**同步**变成可见的 error 状态。
|
|
230
|
+
*
|
|
231
|
+
* ⚠️ **Windows 兼容**(DS review MINOR M3):Windows 上可执行文件可能是
|
|
232
|
+
* `wecom-cli.exe` / `wecom-cli.cmd`,纯命令名探测必须带这些扩展名兜底,
|
|
233
|
+
* 否则在 Windows 上永远判定「找不到」。
|
|
234
|
+
*
|
|
235
|
+
* @param binName 二进制名(默认 `wecom-cli`)
|
|
236
|
+
*/
|
|
237
|
+
export declare function resolveWecomBinary(binName?: string): {
|
|
238
|
+
found: boolean;
|
|
239
|
+
path?: string;
|
|
240
|
+
};
|
|
241
|
+
/**
|
|
242
|
+
* 从自定义命令串里解析出「可执行文件」并探测是否存在。
|
|
243
|
+
*
|
|
244
|
+
* 自定义 command 的形态是 shell 命令串(`exec <cmd> <args...>`),
|
|
245
|
+
* 可能带 `exec ` 前缀与引号。这里只做**保守**判定:
|
|
246
|
+
* - 取第一个「像路径」的 token(含 `/`)
|
|
247
|
+
* - 若是绝对路径且不存在 → 判定不可用
|
|
248
|
+
* - 其它情况一律返回 found=true(不误伤,避免把正常命令判死)
|
|
249
|
+
*
|
|
250
|
+
* @returns found=false 表示该命令的绝对路径可执行文件不存在
|
|
251
|
+
*/
|
|
252
|
+
export declare function probeCommandBinary(command: string): {
|
|
253
|
+
found: boolean;
|
|
254
|
+
path?: string;
|
|
255
|
+
};
|
|
256
|
+
/** WsBridge 需要的最小 WebSocket 面(测试 mock 友好) */
|
|
257
|
+
export interface WebSocketLike {
|
|
258
|
+
on(ev: string, cb: (...args: unknown[]) => void): void;
|
|
259
|
+
send(data: string): void;
|
|
260
|
+
close(): void;
|
|
261
|
+
terminate(): void;
|
|
262
|
+
}
|
|
263
|
+
/** 生产默认工厂:动态 import('ws')(bun/Node 双 runtime 兼容) */
|
|
264
|
+
export declare function defaultWecomWsFactory(): Promise<(url: string) => WebSocketLike>;
|
|
265
|
+
export declare const wecomCliHandler: EventSourceHandler;
|
|
266
|
+
export {};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Task #5174(用户明令 2026-11-08 11:29)— 企微出站发送器(CLI 主路径)
|
|
3
|
+
*
|
|
4
|
+
* ## 为什么改 CLI
|
|
5
|
+
*
|
|
6
|
+
* 用户明令:「彻底改造下回复出站用 cli 吧,已经验证可以收到消息了,不用 ws 回」。
|
|
7
|
+
* `wecom-cli auth init` 扫码授权已完成(auth show = authorized),CLI 出站实测
|
|
8
|
+
* `success: true`;WS 回帧(`aibot_respond_msg`)保留为 **fallback**。
|
|
9
|
+
*
|
|
10
|
+
* ## CLI 出站的三个硬约束(实测)
|
|
11
|
+
*
|
|
12
|
+
* 1. **msg_type 没有 text** —— 只支持 markdown / image / file / voice / video;
|
|
13
|
+
* 传 `text` 报「存在未知字段'text'」⇒ 统一用 `markdown`。
|
|
14
|
+
* 2. **chat_id 必须是真实 userid**(形如 `wotnB7Wg...`),WS 回调里的
|
|
15
|
+
* `from.userid`(如 `DongZhaoKun`)会被拒(40073 非法的 chat_id)。
|
|
16
|
+
* 因此需要 `contact users search` 解析 + 内存缓存。
|
|
17
|
+
* 3. 响应是 JSON,成功形如 `{"success": true}`,失败形如
|
|
18
|
+
* `{"error": {"code": ..., "message": ...}}`。
|
|
19
|
+
*
|
|
20
|
+
* @see Task #5173 操作记录 #40148(实测取证)
|
|
21
|
+
*/
|
|
22
|
+
/** 默认发送硬超时(CLI 正常 < 2s,15s 是充裕余量) */
|
|
23
|
+
export declare const WECOM_CLI_SEND_TIMEOUT_MS = 15000;
|
|
24
|
+
/** contact 解析硬超时 */
|
|
25
|
+
export declare const WECOM_CLI_RESOLVE_TIMEOUT_MS = 15000;
|
|
26
|
+
/**
|
|
27
|
+
* 解析 wecom-cli 可执行文件路径。
|
|
28
|
+
*
|
|
29
|
+
* 优先级:`WECOM_CLI_BIN` 环境变量 → `wecom-cli`(PATH)。
|
|
30
|
+
* 本机 CLI 是 npm 装的平台子包,未在 PATH 里时必须用 env 指定。
|
|
31
|
+
*
|
|
32
|
+
* ## ⚠️ 隔离 HOME 部署必读(Task #5174 实测)
|
|
33
|
+
*
|
|
34
|
+
* CLI 授权凭据在 `$HOME/.config/wecom/credentials.enc`。若 roy-agent 跑在
|
|
35
|
+
* 隔离 HOME(测试 / 多租户)下,**只拷 credentials.enc 无效** ——
|
|
36
|
+
* `auth show` 仍会报 unauthorized。
|
|
37
|
+
*
|
|
38
|
+
* CLI 支持 `WECOM_CLI_CONFIG_DIR` 覆盖配置目录,部署时设置它指向
|
|
39
|
+
* **已授权**的配置目录即可(本机示例:`/home/dzk/.config/wecom`)。
|
|
40
|
+
* 该变量由子进程从 `process.env` 继承,本模块无需显式处理。
|
|
41
|
+
*/
|
|
42
|
+
export declare function resolveWecomCliBinary(): string;
|
|
43
|
+
/** 构造 `message aibot send` 的 argv(不含二进制名) */
|
|
44
|
+
export declare function buildWecomCliSendArgs(input: {
|
|
45
|
+
chatId: string;
|
|
46
|
+
content: string;
|
|
47
|
+
}): string[];
|
|
48
|
+
/** 构造 `contact users search` 的 argv(用于把 WS userid 解析成 CLI chat_id) */
|
|
49
|
+
export declare function buildWecomCliResolveArgs(keywords: string): string[];
|
|
50
|
+
/**
|
|
51
|
+
* 解析 CLI 输出(stdout 是 JSON)。
|
|
52
|
+
*
|
|
53
|
+
* - `{"error": {...}}` → 失败(读 error.code / error.message)
|
|
54
|
+
* - `{"success": true}` → 成功
|
|
55
|
+
* - 无法 parse → 失败(把原文片段带出来便于排查)
|
|
56
|
+
*/
|
|
57
|
+
export declare function parseWecomCliResult(stdout: string): {
|
|
58
|
+
ok: true;
|
|
59
|
+
} | {
|
|
60
|
+
ok: false;
|
|
61
|
+
error: string;
|
|
62
|
+
};
|
|
63
|
+
/** 从 contact search 输出解析真实 userid */
|
|
64
|
+
export declare function parseWecomCliUserSearch(stdout: string, fallback: string): string;
|
|
65
|
+
/** 测试用:清空解析缓存 */
|
|
66
|
+
export declare function __resetWecomCliChatIdCacheForTest(): void;
|
|
67
|
+
/**
|
|
68
|
+
* 把 WS 回调里的 userid 解析成 CLI 可用的 chat_id。
|
|
69
|
+
*
|
|
70
|
+
* 已有缓存 → 直接返回;否则 `contact users search --set keywords=<userid>`
|
|
71
|
+
* 取 `users[0].userid`;解析失败返回原值(让 CLI 自己报 40073,便于排障)。
|
|
72
|
+
*/
|
|
73
|
+
export declare function resolveWecomCliChatId(userId: string): Promise<string>;
|
|
74
|
+
/**
|
|
75
|
+
* 用 CLI 发 markdown 消息(**永不抛**)。
|
|
76
|
+
*
|
|
77
|
+
* @returns `{ok:true}` / `{ok:false, error}`;调用方据此决定是否走 WS fallback
|
|
78
|
+
*/
|
|
79
|
+
export declare function sendWecomCliMarkdown(input: {
|
|
80
|
+
chatId: string;
|
|
81
|
+
content: string;
|
|
82
|
+
timeoutMs?: number;
|
|
83
|
+
}): Promise<{
|
|
84
|
+
ok: true;
|
|
85
|
+
} | {
|
|
86
|
+
ok: false;
|
|
87
|
+
error: string;
|
|
88
|
+
}>;
|