@wecom/wecom-openclaw-plugin 2026.3.31-beta.0 → 2026.4.1-beta.1

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.
Files changed (209) hide show
  1. package/README.md +4 -251
  2. package/dist/cjs/index.js +45 -0
  3. package/dist/cjs/index.js.map +1 -0
  4. package/dist/cjs/src/channel.js +339 -0
  5. package/dist/cjs/src/channel.js.map +1 -0
  6. package/dist/cjs/src/const.js +137 -0
  7. package/dist/cjs/src/const.js.map +1 -0
  8. package/dist/cjs/src/dm-policy.js +89 -0
  9. package/dist/cjs/src/dm-policy.js.map +1 -0
  10. package/dist/cjs/src/group-policy.js +133 -0
  11. package/dist/cjs/src/group-policy.js.map +1 -0
  12. package/dist/cjs/src/mcp/interceptors/biz-error.js +70 -0
  13. package/dist/cjs/src/mcp/interceptors/biz-error.js.map +1 -0
  14. package/dist/cjs/src/mcp/interceptors/index.js +73 -0
  15. package/dist/cjs/src/mcp/interceptors/index.js.map +1 -0
  16. package/dist/cjs/src/mcp/interceptors/msg-media.js +159 -0
  17. package/dist/cjs/src/mcp/interceptors/msg-media.js.map +1 -0
  18. package/dist/cjs/src/mcp/interceptors/smartpage-create.js +157 -0
  19. package/dist/cjs/src/mcp/interceptors/smartpage-create.js.map +1 -0
  20. package/dist/cjs/src/mcp/interceptors/smartpage-export.js +93 -0
  21. package/dist/cjs/src/mcp/interceptors/smartpage-export.js.map +1 -0
  22. package/dist/cjs/src/mcp/schema.js +104 -0
  23. package/dist/cjs/src/mcp/schema.js.map +1 -0
  24. package/dist/cjs/src/mcp/tool.js +208 -0
  25. package/dist/cjs/src/mcp/tool.js.map +1 -0
  26. package/dist/cjs/src/mcp/transport.js +417 -0
  27. package/dist/cjs/src/mcp/transport.js.map +1 -0
  28. package/dist/cjs/src/media-handler.js +129 -0
  29. package/dist/cjs/src/media-handler.js.map +1 -0
  30. package/dist/cjs/src/media-uploader.js +284 -0
  31. package/dist/cjs/src/media-uploader.js.map +1 -0
  32. package/dist/cjs/src/message-parser.js +134 -0
  33. package/dist/cjs/src/message-parser.js.map +1 -0
  34. package/dist/cjs/src/message-sender.js +89 -0
  35. package/dist/cjs/src/message-sender.js.map +1 -0
  36. package/dist/cjs/src/monitor.js +991 -0
  37. package/dist/cjs/src/monitor.js.map +1 -0
  38. package/dist/cjs/src/onboarding.js +182 -0
  39. package/dist/cjs/src/onboarding.js.map +1 -0
  40. package/dist/cjs/src/openclaw-compat.js +400 -0
  41. package/dist/cjs/src/openclaw-compat.js.map +1 -0
  42. package/dist/cjs/src/reqid-store.js +88 -0
  43. package/dist/cjs/src/reqid-store.js.map +1 -0
  44. package/dist/cjs/src/runtime.js +16 -0
  45. package/dist/cjs/src/runtime.js.map +1 -0
  46. package/dist/cjs/src/state-dir-resolve.js +35 -0
  47. package/dist/cjs/src/state-dir-resolve.js.map +1 -0
  48. package/dist/cjs/src/state-manager.js +158 -0
  49. package/dist/cjs/src/state-manager.js.map +1 -0
  50. package/dist/cjs/src/template-card-parser.js +641 -0
  51. package/dist/cjs/src/template-card-parser.js.map +1 -0
  52. package/dist/cjs/src/timeout.js +42 -0
  53. package/dist/cjs/src/timeout.js.map +1 -0
  54. package/dist/cjs/src/utils.js +60 -0
  55. package/dist/cjs/src/utils.js.map +1 -0
  56. package/dist/cjs/src/version.js +26 -0
  57. package/dist/cjs/src/version.js.map +1 -0
  58. package/dist/esm/index.js +41 -0
  59. package/dist/esm/index.js.map +1 -0
  60. package/dist/esm/src/channel.js +337 -0
  61. package/dist/esm/src/channel.js.map +1 -0
  62. package/dist/{src → esm/src}/const.js +30 -77
  63. package/dist/esm/src/const.js.map +1 -0
  64. package/dist/{src → esm/src}/dm-policy.js +9 -6
  65. package/dist/esm/src/dm-policy.js.map +1 -0
  66. package/dist/{src → esm/src}/group-policy.js +7 -3
  67. package/dist/esm/src/group-policy.js.map +1 -0
  68. package/dist/{src → esm/src}/mcp/interceptors/biz-error.js +6 -2
  69. package/dist/esm/src/mcp/interceptors/biz-error.js.map +1 -0
  70. package/dist/{src → esm/src}/mcp/interceptors/index.js +10 -6
  71. package/dist/esm/src/mcp/interceptors/index.js.map +1 -0
  72. package/dist/{src → esm/src}/mcp/interceptors/msg-media.js +10 -6
  73. package/dist/esm/src/mcp/interceptors/msg-media.js.map +1 -0
  74. package/dist/{src → esm/src}/mcp/interceptors/smartpage-create.js +6 -2
  75. package/dist/esm/src/mcp/interceptors/smartpage-create.js.map +1 -0
  76. package/dist/{src → esm/src}/mcp/interceptors/smartpage-export.js +6 -2
  77. package/dist/esm/src/mcp/interceptors/smartpage-export.js.map +1 -0
  78. package/dist/{src → esm/src}/mcp/schema.js +4 -1
  79. package/dist/esm/src/mcp/schema.js.map +1 -0
  80. package/dist/{src → esm/src}/mcp/tool.js +8 -4
  81. package/dist/esm/src/mcp/tool.js.map +1 -0
  82. package/dist/{src → esm/src}/mcp/transport.js +16 -15
  83. package/dist/esm/src/mcp/transport.js.map +1 -0
  84. package/dist/{src → esm/src}/media-handler.js +10 -6
  85. package/dist/esm/src/media-handler.js.map +1 -0
  86. package/dist/{src → esm/src}/media-uploader.js +10 -64
  87. package/dist/esm/src/media-uploader.js.map +1 -0
  88. package/dist/{src → esm/src}/message-parser.js +4 -1
  89. package/dist/esm/src/message-parser.js.map +1 -0
  90. package/dist/{src → esm/src}/message-sender.js +11 -46
  91. package/dist/esm/src/message-sender.js.map +1 -0
  92. package/dist/{src → esm/src}/monitor.js +282 -151
  93. package/dist/esm/src/monitor.js.map +1 -0
  94. package/dist/{src → esm/src}/onboarding.js +26 -22
  95. package/dist/esm/src/onboarding.js.map +1 -0
  96. package/dist/{src → esm/src}/openclaw-compat.js +27 -143
  97. package/dist/esm/src/openclaw-compat.js.map +1 -0
  98. package/dist/{src → esm/src}/reqid-store.js +7 -4
  99. package/dist/esm/src/reqid-store.js.map +1 -0
  100. package/dist/{src → esm/src}/runtime.js +5 -2
  101. package/dist/esm/src/runtime.js.map +1 -0
  102. package/dist/esm/src/state-dir-resolve.js +13 -0
  103. package/dist/esm/src/state-dir-resolve.js.map +1 -0
  104. package/dist/{src → esm/src}/state-manager.js +16 -84
  105. package/dist/esm/src/state-manager.js.map +1 -0
  106. package/dist/{src → esm/src}/template-card-parser.js +8 -17
  107. package/dist/esm/src/template-card-parser.js.map +1 -0
  108. package/dist/{src → esm/src}/timeout.js +5 -2
  109. package/dist/esm/src/timeout.js.map +1 -0
  110. package/dist/{src → esm/src}/utils.js +10 -24
  111. package/dist/esm/src/utils.js.map +1 -0
  112. package/dist/esm/src/version.js +23 -0
  113. package/dist/esm/src/version.js.map +1 -0
  114. package/dist/esm/types/index.d.ts +9 -0
  115. package/dist/{src → esm/types/src}/const.d.ts +0 -46
  116. package/dist/{src → esm/types/src}/interface.d.ts +2 -0
  117. package/dist/{src → esm/types/src}/message-sender.d.ts +0 -18
  118. package/dist/{src → esm/types/src}/openclaw-compat.d.ts +0 -49
  119. package/dist/esm/types/src/state-dir-resolve.d.ts +2 -0
  120. package/dist/{src → esm/types/src}/utils.d.ts +0 -39
  121. package/dist/esm/types/src/version.d.ts +2 -0
  122. package/dist/index.d.ts +4 -2
  123. package/package.json +7 -15
  124. package/dist/index.js +0 -113
  125. package/dist/src/accounts.d.ts +0 -57
  126. package/dist/src/accounts.js +0 -222
  127. package/dist/src/agent/api-client.d.ts +0 -94
  128. package/dist/src/agent/api-client.js +0 -292
  129. package/dist/src/agent/handler.d.ts +0 -63
  130. package/dist/src/agent/handler.js +0 -532
  131. package/dist/src/agent/index.d.ts +0 -5
  132. package/dist/src/agent/index.js +0 -5
  133. package/dist/src/agent/webhook.d.ts +0 -23
  134. package/dist/src/agent/webhook.js +0 -226
  135. package/dist/src/agent/xml.d.ts +0 -21
  136. package/dist/src/agent/xml.js +0 -41
  137. package/dist/src/channel.js +0 -564
  138. package/dist/src/chat-queue.d.ts +0 -31
  139. package/dist/src/chat-queue.js +0 -45
  140. package/dist/src/http.d.ts +0 -26
  141. package/dist/src/http.js +0 -93
  142. package/dist/src/interface.js +0 -4
  143. package/dist/src/mcp/index.js +0 -6
  144. package/dist/src/mcp/interceptors/types.js +0 -7
  145. package/dist/src/shared/command-auth.d.ts +0 -23
  146. package/dist/src/shared/command-auth.js +0 -73
  147. package/dist/src/shared/xml-parser.d.ts +0 -46
  148. package/dist/src/shared/xml-parser.js +0 -210
  149. package/dist/src/target.d.ts +0 -35
  150. package/dist/src/target.js +0 -67
  151. package/dist/src/template-card-manager.d.ts +0 -55
  152. package/dist/src/template-card-manager.js +0 -205
  153. package/dist/src/types/account.d.ts +0 -29
  154. package/dist/src/types/account.js +0 -4
  155. package/dist/src/types/config.d.ts +0 -80
  156. package/dist/src/types/config.js +0 -7
  157. package/dist/src/types/constants.d.ts +0 -42
  158. package/dist/src/types/constants.js +0 -42
  159. package/dist/src/types/index.d.ts +0 -7
  160. package/dist/src/types/index.js +0 -5
  161. package/dist/src/types/message.d.ts +0 -238
  162. package/dist/src/types/message.js +0 -5
  163. package/dist/src/version.d.ts +0 -2
  164. package/dist/src/version.js +0 -24
  165. package/dist/src/webhook/command-auth.d.ts +0 -47
  166. package/dist/src/webhook/command-auth.js +0 -96
  167. package/dist/src/webhook/gateway.d.ts +0 -36
  168. package/dist/src/webhook/gateway.js +0 -227
  169. package/dist/src/webhook/handler.d.ts +0 -18
  170. package/dist/src/webhook/handler.js +0 -420
  171. package/dist/src/webhook/helpers.d.ts +0 -156
  172. package/dist/src/webhook/helpers.js +0 -833
  173. package/dist/src/webhook/http.d.ts +0 -26
  174. package/dist/src/webhook/http.js +0 -94
  175. package/dist/src/webhook/index.d.ts +0 -11
  176. package/dist/src/webhook/index.js +0 -14
  177. package/dist/src/webhook/media.d.ts +0 -29
  178. package/dist/src/webhook/media.js +0 -88
  179. package/dist/src/webhook/monitor.d.ts +0 -59
  180. package/dist/src/webhook/monitor.js +0 -1147
  181. package/dist/src/webhook/state.d.ts +0 -220
  182. package/dist/src/webhook/state.js +0 -491
  183. package/dist/src/webhook/target.d.ts +0 -41
  184. package/dist/src/webhook/target.js +0 -132
  185. package/dist/src/webhook/types.d.ts +0 -347
  186. package/dist/src/webhook/types.js +0 -33
  187. /package/dist/{src → esm/types/src}/channel.d.ts +0 -0
  188. /package/dist/{src → esm/types/src}/dm-policy.d.ts +0 -0
  189. /package/dist/{src → esm/types/src}/group-policy.d.ts +0 -0
  190. /package/dist/{src → esm/types/src}/mcp/index.d.ts +0 -0
  191. /package/dist/{src → esm/types/src}/mcp/interceptors/biz-error.d.ts +0 -0
  192. /package/dist/{src → esm/types/src}/mcp/interceptors/index.d.ts +0 -0
  193. /package/dist/{src → esm/types/src}/mcp/interceptors/msg-media.d.ts +0 -0
  194. /package/dist/{src → esm/types/src}/mcp/interceptors/smartpage-create.d.ts +0 -0
  195. /package/dist/{src → esm/types/src}/mcp/interceptors/smartpage-export.d.ts +0 -0
  196. /package/dist/{src → esm/types/src}/mcp/interceptors/types.d.ts +0 -0
  197. /package/dist/{src → esm/types/src}/mcp/schema.d.ts +0 -0
  198. /package/dist/{src → esm/types/src}/mcp/tool.d.ts +0 -0
  199. /package/dist/{src → esm/types/src}/mcp/transport.d.ts +0 -0
  200. /package/dist/{src → esm/types/src}/media-handler.d.ts +0 -0
  201. /package/dist/{src → esm/types/src}/media-uploader.d.ts +0 -0
  202. /package/dist/{src → esm/types/src}/message-parser.d.ts +0 -0
  203. /package/dist/{src → esm/types/src}/monitor.d.ts +0 -0
  204. /package/dist/{src → esm/types/src}/onboarding.d.ts +0 -0
  205. /package/dist/{src → esm/types/src}/reqid-store.d.ts +0 -0
  206. /package/dist/{src → esm/types/src}/runtime.d.ts +0 -0
  207. /package/dist/{src → esm/types/src}/state-manager.d.ts +0 -0
  208. /package/dist/{src → esm/types/src}/template-card-parser.d.ts +0 -0
  209. /package/dist/{src → esm/types/src}/timeout.d.ts +0 -0
@@ -1,24 +0,0 @@
1
- import { existsSync, readFileSync } from "node:fs";
2
- import { dirname, resolve } from "node:path";
3
- import { fileURLToPath } from "node:url";
4
- /**
5
- * 从当前文件位置向上逐级查找 package.json
6
- * - ts 源码直接运行时(tsx/ts-node):src/version.ts → 向上 1 级
7
- * - tsc 编译后运行时:dist/src/version.js → 向上 2 级
8
- */
9
- const findPackageJson = () => {
10
- let dir = dirname(fileURLToPath(import.meta.url));
11
- while (true) {
12
- const candidate = resolve(dir, "package.json");
13
- if (existsSync(candidate))
14
- return candidate;
15
- const parent = dirname(dir);
16
- if (parent === dir)
17
- break; // 已到文件系统根目录
18
- dir = parent;
19
- }
20
- throw new Error("找不到 package.json");
21
- };
22
- const pkg = JSON.parse(readFileSync(findPackageJson(), "utf-8"));
23
- /** 插件版本号,运行时从 package.json 读取 */
24
- export const PLUGIN_VERSION = pkg.version ?? "";
@@ -1,47 +0,0 @@
1
- /**
2
- * 命令授权(Command Authorization)
3
- *
4
- * 从 lh 版 shared/command-auth.ts 迁移。
5
- * 适配新版 WeComConfig(dmPolicy / allowFrom 扁平化在顶层)。
6
- */
7
- import type { OpenClawConfig, PluginRuntime } from "openclaw/plugin-sdk";
8
- import type { WeComConfig } from "../utils.js";
9
- /** 命令授权结果 */
10
- export interface WecomCommandAuthResult {
11
- /** 当前消息是否是需要鉴权的命令 */
12
- shouldComputeAuth: boolean;
13
- /** 账号配置的私信策略 */
14
- dmPolicy: "pairing" | "allowlist" | "open" | "disabled";
15
- /** 当前发送者是否在白名单中 */
16
- senderAllowed: boolean;
17
- /** 是否配置了授权器 */
18
- authorizerConfigured: boolean;
19
- /** 最终授权结果:true=放行,false=拒绝,undefined=不需要鉴权 */
20
- commandAuthorized: boolean | undefined;
21
- /** 生效的白名单列表 */
22
- effectiveAllowFrom: string[];
23
- }
24
- /**
25
- * 解析命令授权状态
26
- *
27
- * 适配新版 WeComConfig 的扁平化字段:
28
- * - dmPolicy → accountConfig.dmPolicy
29
- * - allowFrom → accountConfig.allowFrom
30
- */
31
- export declare function resolveWecomCommandAuthorization(params: {
32
- core: PluginRuntime;
33
- cfg: OpenClawConfig;
34
- accountConfig: WeComConfig;
35
- rawBody: string;
36
- senderUserId: string;
37
- }): Promise<WecomCommandAuthResult>;
38
- /**
39
- * 构建未授权命令的中文提示文案
40
- *
41
- * @param scope - "bot"(智能机器人)或 "agent"(自建应用)
42
- */
43
- export declare function buildWecomUnauthorizedCommandPrompt(params: {
44
- senderUserId: string;
45
- dmPolicy: "pairing" | "allowlist" | "open" | "disabled";
46
- scope: "bot" | "agent";
47
- }): string;
@@ -1,96 +0,0 @@
1
- /**
2
- * 命令授权(Command Authorization)
3
- *
4
- * 从 lh 版 shared/command-auth.ts 迁移。
5
- * 适配新版 WeComConfig(dmPolicy / allowFrom 扁平化在顶层)。
6
- */
7
- // ============================================================================
8
- // 内部工具函数
9
- // ============================================================================
10
- /** 归一化白名单条目:去除空格、统一小写、去掉 wecom:/user:/userid: 前缀 */
11
- function normalizeWecomAllowFromEntry(raw) {
12
- return raw
13
- .trim()
14
- .toLowerCase()
15
- .replace(/^wecom:/, "")
16
- .replace(/^user:/, "")
17
- .replace(/^userid:/, "");
18
- }
19
- /** 判断发送者是否在白名单中 */
20
- function isWecomSenderAllowed(senderUserId, allowFrom) {
21
- const list = allowFrom.map((entry) => normalizeWecomAllowFromEntry(entry)).filter(Boolean);
22
- if (list.includes("*"))
23
- return true;
24
- const normalizedSender = normalizeWecomAllowFromEntry(senderUserId);
25
- if (!normalizedSender)
26
- return false;
27
- return list.includes(normalizedSender);
28
- }
29
- /**
30
- * 解析命令授权状态
31
- *
32
- * 适配新版 WeComConfig 的扁平化字段:
33
- * - dmPolicy → accountConfig.dmPolicy
34
- * - allowFrom → accountConfig.allowFrom
35
- */
36
- export async function resolveWecomCommandAuthorization(params) {
37
- const { core, cfg, accountConfig, rawBody, senderUserId } = params;
38
- const dmPolicy = (accountConfig.dmPolicy ?? "pairing");
39
- const configAllowFrom = (accountConfig.allowFrom ?? []).map((v) => String(v));
40
- const shouldComputeAuth = core.channel.commands.shouldComputeCommandAuthorized(rawBody, cfg);
41
- // WeCom channel 不支持 pairing CLI("Channel wecom does not support pairing"),
42
- // 因此 pairing 策略等同于 allowlist。
43
- // Policy 语义:
44
- // - open: 命令对所有人放行(除非更高层级的 access-groups 拒绝)
45
- // - allowlist: 命令需要 allowFrom 白名单
46
- // - pairing: 等同 allowlist(因 WeCom 不支持 pairing CLI)
47
- const effectiveAllowFrom = dmPolicy === "open" ? ["*"] : configAllowFrom;
48
- const senderAllowed = isWecomSenderAllowed(senderUserId, effectiveAllowFrom);
49
- const allowAllConfigured = effectiveAllowFrom.some((entry) => normalizeWecomAllowFromEntry(entry) === "*");
50
- const authorizerConfigured = allowAllConfigured || effectiveAllowFrom.length > 0;
51
- const useAccessGroups = cfg.commands?.useAccessGroups !== false;
52
- const commandAuthorized = shouldComputeAuth
53
- ? core.channel.commands.resolveCommandAuthorizedFromAuthorizers({
54
- useAccessGroups,
55
- authorizers: [{ configured: authorizerConfigured, allowed: senderAllowed }],
56
- })
57
- : undefined;
58
- return {
59
- shouldComputeAuth,
60
- dmPolicy,
61
- senderAllowed,
62
- authorizerConfigured,
63
- commandAuthorized,
64
- effectiveAllowFrom,
65
- };
66
- }
67
- // ============================================================================
68
- // 未授权提示文案构建
69
- // ============================================================================
70
- /**
71
- * 构建未授权命令的中文提示文案
72
- *
73
- * @param scope - "bot"(智能机器人)或 "agent"(自建应用)
74
- */
75
- export function buildWecomUnauthorizedCommandPrompt(params) {
76
- const user = params.senderUserId || "unknown";
77
- const policy = params.dmPolicy;
78
- const scopeLabel = params.scope === "bot" ? "Bot(智能机器人)" : "Agent(自建应用)";
79
- const dmPrefix = params.scope === "bot" ? "channels.wecom.bot" : "channels.wecom.agent";
80
- const allowCmd = (value) => `openclaw config set ${dmPrefix}.allowFrom '${value}'`;
81
- const policyCmd = (value) => `openclaw config set ${dmPrefix}.dmPolicy "${value}"`;
82
- if (policy === "disabled") {
83
- return [
84
- `无权限执行命令(${scopeLabel} 已禁用:dmPolicy=disabled)`,
85
- `触发者:${user}`,
86
- `管理员:${policyCmd("open")}(全放开)或 ${policyCmd("allowlist")}(白名单)`,
87
- ].join("\n");
88
- }
89
- return [
90
- `无权限执行命令(入口:${scopeLabel},userid:${user})`,
91
- `管理员全放开:${policyCmd("open")}`,
92
- `管理员放行该用户:${policyCmd("allowlist")}`,
93
- `然后设置白名单:${allowCmd(JSON.stringify([user]))}`,
94
- `如果仍被拦截:检查 commands.useAccessGroups/访问组`,
95
- ].join("\n");
96
- }
@@ -1,36 +0,0 @@
1
- /**
2
- * Webhook Gateway 生命周期管理
3
- *
4
- * 从 @mocrane/wecom gateway-monitor.ts 部分迁移(仅 Webhook 部分)。
5
- * 负责:初始化状态、注册 Target、启停管理。
6
- *
7
- * 关键设计:
8
- * - MonitorState 是全局单例(monitorState),所有账号共享同一个 StreamStore 和 ActiveReplyStore
9
- * - Target 注册/注销不影响 monitorState 生命周期,只控制 pruneTimer 的启停
10
- * - 每个账号注册多条路径(兼容历史路径 + 推荐路径 + 多账号路径)
11
- * - 按 accountId 管理各自的 unregister,stop 时只注销该账号的 Target
12
- */
13
- import type { WebhookGatewayContext } from "./types.js";
14
- import { WebhookMonitorState } from "./state.js";
15
- /**
16
- * 获取当前的 MonitorState 实例(全局单例)
17
- *
18
- * 供 monitor.ts 等内部模块调用以访问 StreamStore 和 ActiveReplyStore。
19
- */
20
- export declare function getMonitorState(): WebhookMonitorState;
21
- /**
22
- * 启动 Webhook Gateway
23
- *
24
- * 1. 验证 Webhook 配置
25
- * 2. 确保 pruneTimer 启动
26
- * 3. 设置 FlushHandler(仅首次)
27
- * 4. 解析并注册多条 Webhook 路径
28
- */
29
- export declare function startWebhookGateway(ctx: WebhookGatewayContext): void;
30
- /**
31
- * 停止 Webhook Gateway
32
- *
33
- * 1. 注销该账号的 Target(不影响其他账号)
34
- * 2. 如果没有任何活跃 Target,停止清理定时器
35
- */
36
- export declare function stopWebhookGateway(ctx: WebhookGatewayContext): void;
@@ -1,227 +0,0 @@
1
- /**
2
- * Webhook Gateway 生命周期管理
3
- *
4
- * 从 @mocrane/wecom gateway-monitor.ts 部分迁移(仅 Webhook 部分)。
5
- * 负责:初始化状态、注册 Target、启停管理。
6
- *
7
- * 关键设计:
8
- * - MonitorState 是全局单例(monitorState),所有账号共享同一个 StreamStore 和 ActiveReplyStore
9
- * - Target 注册/注销不影响 monitorState 生命周期,只控制 pruneTimer 的启停
10
- * - 每个账号注册多条路径(兼容历史路径 + 推荐路径 + 多账号路径)
11
- * - 按 accountId 管理各自的 unregister,stop 时只注销该账号的 Target
12
- */
13
- import { PRUNE_INTERVAL_MS, WEBHOOK_PATHS } from "./types.js";
14
- import { monitorState } from "./state.js";
15
- import { registerWecomWebhookTarget, hasActiveTargets } from "./target.js";
16
- import { startAgentForStream } from "./monitor.js";
17
- import { hasMultiAccounts } from "../accounts.js";
18
- import { getWeComRuntime } from "../runtime.js";
19
- // ============================================================================
20
- // 全局状态
21
- // ============================================================================
22
- /** 按 accountId 管理各账号的 Target 注销函数 */
23
- const accountUnregisters = new Map();
24
- /** FlushHandler 是否已设置(只需设置一次,因为 monitorState 是单例) */
25
- let flushHandlerInstalled = false;
26
- // ============================================================================
27
- // 路径解析
28
- // ============================================================================
29
- /**
30
- * 去除重复路径
31
- */
32
- function uniquePaths(paths) {
33
- return Array.from(new Set(paths.map((p) => p.trim()).filter(Boolean)));
34
- }
35
- /**
36
- * 解析 Bot Target 注册路径
37
- *
38
- * 多账号场景(matrixMode)下:注册带 accountId 后缀的路径 + 兼容老路径
39
- * 单账号场景下:只注册基础路径
40
- *
41
- * 参考 lh 版 resolveBotRegistrationPaths
42
- */
43
- function resolveBotRegistrationPaths(params) {
44
- if (params.matrixMode) {
45
- return uniquePaths([
46
- `${WEBHOOK_PATHS.BOT_PLUGIN}/${params.accountId}`,
47
- `${WEBHOOK_PATHS.BOT_ALT}/${params.accountId}`,
48
- // 兼容老路径:不带 accountId 后缀,签名验证会自动匹配到正确账号
49
- WEBHOOK_PATHS.BOT_PLUGIN,
50
- WEBHOOK_PATHS.BOT,
51
- WEBHOOK_PATHS.BOT_ALT,
52
- ]);
53
- }
54
- return uniquePaths([WEBHOOK_PATHS.BOT_PLUGIN, WEBHOOK_PATHS.BOT, WEBHOOK_PATHS.BOT_ALT]);
55
- }
56
- // ============================================================================
57
- // 公共 API
58
- // ============================================================================
59
- /**
60
- * 获取当前的 MonitorState 实例(全局单例)
61
- *
62
- * 供 monitor.ts 等内部模块调用以访问 StreamStore 和 ActiveReplyStore。
63
- */
64
- export function getMonitorState() {
65
- return monitorState;
66
- }
67
- /**
68
- * 启动 Webhook Gateway
69
- *
70
- * 1. 验证 Webhook 配置
71
- * 2. 确保 pruneTimer 启动
72
- * 3. 设置 FlushHandler(仅首次)
73
- * 4. 解析并注册多条 Webhook 路径
74
- */
75
- export function startWebhookGateway(ctx) {
76
- const { account, config, runtime } = ctx;
77
- const log = ctx.log ?? {
78
- info: (msg) => runtime.log(msg),
79
- error: (msg) => runtime.error(msg),
80
- };
81
- // 1. 验证必要配置(receiveId 非必填,可为空)
82
- if (!account.token || !account.encodingAESKey) {
83
- const missing = [];
84
- if (!account.token)
85
- missing.push("token");
86
- if (!account.encodingAESKey)
87
- missing.push("encodingAESKey");
88
- const errorMsg = `[webhook] Webhook 配置不完整,缺少: ${missing.join(", ")}`;
89
- log.error(errorMsg);
90
- ctx.setStatus?.({
91
- accountId: ctx.accountId,
92
- running: false,
93
- configured: false,
94
- lastError: errorMsg,
95
- });
96
- return;
97
- }
98
- log.info(`[webhook] 启动 Webhook Gateway (accountId=${ctx.accountId})`);
99
- // 2. 确保 pruneTimer 启动(幂等:如果已在运行,不会重复启动)
100
- monitorState.startPruning(PRUNE_INTERVAL_MS);
101
- // 3. 设置 FlushHandler(仅首次,因为 monitorState 是全局单例)
102
- if (!flushHandlerInstalled) {
103
- monitorState.streamStore.setFlushHandler((pending) => void flushPending(pending));
104
- flushHandlerInstalled = true;
105
- }
106
- // 4. 构造 Target 上下文
107
- const runtimeEnv = {
108
- log: (msg) => runtime.log(msg),
109
- error: (msg) => runtime.error(msg),
110
- };
111
- // 判断是否为多账号模式
112
- const matrixMode = hasMultiAccounts(ctx.config);
113
- const target = {
114
- account,
115
- config,
116
- runtime: runtimeEnv,
117
- core: (ctx.channelRuntime ?? runtime), // PluginRuntime 实例
118
- path: `${WEBHOOK_PATHS.BOT_PLUGIN}/${ctx.accountId}`, // 主路径(用于日志和状态显示)
119
- statusSink: ctx.setStatus
120
- ? (patch) => ctx.setStatus?.({ accountId: ctx.accountId, ...patch })
121
- : undefined,
122
- };
123
- // 5. 解析注册路径
124
- const paths = resolveBotRegistrationPaths({
125
- accountId: ctx.accountId,
126
- matrixMode,
127
- });
128
- // 6. 注册 Target(返回注销函数)
129
- // 如果该账号之前已注册(例如 reload),先注销
130
- const existingUnregister = accountUnregisters.get(ctx.accountId);
131
- if (existingUnregister) {
132
- existingUnregister();
133
- }
134
- const unregister = registerWecomWebhookTarget(target, paths);
135
- accountUnregisters.set(ctx.accountId, unregister);
136
- log.info(`[webhook] Webhook Target 已注册 (accountId=${ctx.accountId}, paths=[${paths.join(", ")}])`);
137
- // 7. 更新状态
138
- ctx.setStatus?.({
139
- accountId: ctx.accountId,
140
- running: true,
141
- configured: true,
142
- webhookPath: paths[0],
143
- lastStartAt: Date.now(),
144
- });
145
- }
146
- /**
147
- * 停止 Webhook Gateway
148
- *
149
- * 1. 注销该账号的 Target(不影响其他账号)
150
- * 2. 如果没有任何活跃 Target,停止清理定时器
151
- */
152
- export function stopWebhookGateway(ctx) {
153
- const log = ctx.log ?? {
154
- info: (msg) => ctx.runtime.log(msg),
155
- error: (msg) => ctx.runtime.error(msg),
156
- };
157
- log.info(`[webhook] 停止 Webhook Gateway (accountId=${ctx.accountId})`);
158
- // 1. 注销该账号的 Target
159
- const unregister = accountUnregisters.get(ctx.accountId);
160
- if (unregister) {
161
- unregister();
162
- accountUnregisters.delete(ctx.accountId);
163
- }
164
- // 2. 如果没有任何活跃 Target,停止 pruneTimer
165
- if (!hasActiveTargets()) {
166
- monitorState.stopPruning();
167
- }
168
- // 3. 更新状态
169
- ctx.setStatus?.({
170
- accountId: ctx.accountId,
171
- running: false,
172
- lastStopAt: Date.now(),
173
- });
174
- }
175
- // ============================================================================
176
- // flushPending 中间层(对齐原版 monitor.ts:1151-1192)
177
- // ============================================================================
178
- /**
179
- * **flushPending (刷新待处理消息 / 核心 Agent 触发点)**
180
- *
181
- * 当防抖计时器结束时被调用。
182
- * 核心逻辑:
183
- * 1. 聚合所有 pending 的消息内容(用于上下文)。
184
- * 2. 获取 PluginRuntime。
185
- * 3. 标记 Stream 为 Started。
186
- * 4. 调用 `startAgentForStream` 启动 Agent 流程。
187
- * 5. 处理异常并更新 Stream 状态为 Error。
188
- */
189
- async function flushPending(pending) {
190
- const { streamId, target, msg, contents, msgids, conversationKey, batchKey } = pending;
191
- const { streamStore } = monitorState;
192
- // Merge all message contents (each is already formatted by buildInboundBody)
193
- const mergedContents = contents.filter(c => c.trim()).join("\n").trim();
194
- let core = null;
195
- try {
196
- core = getWeComRuntime();
197
- }
198
- catch (err) {
199
- target.runtime.log?.(`[webhook] flush pending: runtime not ready: ${String(err)}`);
200
- streamStore.markFinished(streamId);
201
- target.runtime.log?.(`[webhook] queue: runtime not ready,结束批次并推进 streamId=${streamId}`);
202
- streamStore.onStreamFinished(streamId);
203
- return;
204
- }
205
- if (core) {
206
- streamStore.markStarted(streamId);
207
- const enrichedTarget = { ...target, core };
208
- target.runtime.log?.(`[webhook] flush pending: start batch streamId=${streamId} batchKey=${batchKey} conversationKey=${conversationKey} mergedCount=${contents.length}`);
209
- // Pass the first msg (with its media structure), and mergedContents for multi-message context
210
- startAgentForStream({
211
- target: enrichedTarget,
212
- accountId: target.account.accountId,
213
- msg,
214
- streamId,
215
- mergedContents: contents.length > 1 ? mergedContents : undefined,
216
- mergedMsgids: msgids.length > 1 ? msgids : undefined,
217
- }).catch((err) => {
218
- streamStore.updateStream(streamId, (state) => {
219
- state.error = err instanceof Error ? err.message : String(err);
220
- state.content = state.content || `Error: ${state.error}`;
221
- state.finished = true;
222
- });
223
- target.runtime.error?.(`[webhook] Agent 处理失败 (streamId=${streamId}): ${String(err)}`);
224
- streamStore.onStreamFinished(streamId);
225
- });
226
- }
227
- }
@@ -1,18 +0,0 @@
1
- /**
2
- * Webhook HTTP 请求处理
3
- *
4
- * 从 @mocrane/wecom monitor.ts handleWecomWebhookRequest 部分迁移 + 重构。
5
- * 负责:
6
- * 1. GET/POST 请求分流
7
- * 2. 签名验证(调用 crypto 模块)
8
- * 3. 消息解密
9
- * 4. 按消息类型分发到 monitor 层
10
- */
11
- import type { IncomingMessage, ServerResponse } from "node:http";
12
- /**
13
- * Webhook HTTP 请求总入口
14
- *
15
- * 处理企微 Bot Webhook 的 GET(URL 验证)和 POST(消息回调)请求。
16
- * 返回 true 表示已处理,false 表示不匹配(交给其他 handler)。
17
- */
18
- export declare function handleWecomWebhookRequest(req: IncomingMessage, res: ServerResponse): Promise<boolean>;