@wecom/wecom-openclaw-plugin 2026.7.2 → 2026.8.17-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 (184) hide show
  1. package/README.md +33 -4
  2. package/dist/index.js +14 -21
  3. package/dist/src/cli/argv.d.ts +36 -0
  4. package/dist/src/cli/argv.js +155 -0
  5. package/dist/src/cli/const.d.ts +94 -0
  6. package/dist/src/cli/const.js +107 -0
  7. package/dist/src/cli/credentials.d.ts +54 -0
  8. package/dist/src/cli/credentials.js +342 -0
  9. package/dist/src/cli/index.d.ts +5 -0
  10. package/dist/src/cli/index.js +5 -0
  11. package/dist/src/cli/legacy-tool-warning.d.ts +12 -0
  12. package/dist/src/cli/legacy-tool-warning.js +20 -0
  13. package/dist/src/cli/locate.d.ts +32 -0
  14. package/dist/src/cli/locate.js +196 -0
  15. package/dist/src/cli/process-launcher.d.ts +1 -0
  16. package/dist/src/cli/process-launcher.js +1 -0
  17. package/dist/src/cli/process-output.d.ts +14 -0
  18. package/dist/src/cli/process-output.js +43 -0
  19. package/dist/src/cli/tool.d.ts +46 -0
  20. package/dist/src/cli/tool.js +363 -0
  21. package/dist/src/const.d.ts +0 -8
  22. package/dist/src/const.js +0 -14
  23. package/dist/src/monitor.js +1 -12
  24. package/dist/src/state-manager.d.ts +0 -19
  25. package/dist/src/state-manager.js +1 -34
  26. package/dist/src/utils.d.ts +27 -0
  27. package/openclaw.plugin.json +1 -1
  28. package/package.json +3 -3
  29. package/skills/wecom-send-media/SKILL.md +2 -0
  30. package/skills/wecom-send-template-card/SKILL.md +1 -1
  31. package/skills/wecomcli-calendar/SKILL.md +302 -0
  32. package/skills/wecomcli-calendar/references/calendar-agenda.md +228 -0
  33. package/skills/wecomcli-calendar/references/calendar-cancel.md +110 -0
  34. package/skills/wecomcli-calendar/references/calendar-create.md +240 -0
  35. package/skills/wecomcli-calendar/references/calendar-freebusy.md +209 -0
  36. package/skills/wecomcli-calendar/references/calendar-meeting-room.md +172 -0
  37. package/skills/wecomcli-calendar/references/calendar-search.md +208 -0
  38. package/skills/wecomcli-calendar/references/calendar-update.md +274 -0
  39. package/skills/wecomcli-contact/SKILL.md +59 -0
  40. package/skills/wecomcli-disk/SKILL.md +401 -0
  41. package/skills/wecomcli-doc/SKILL.md +140 -0
  42. package/skills/wecomcli-doc/references/doc-contents-append.md +22 -0
  43. package/skills/wecomcli-doc/references/doc-contents-overwrite.md +29 -0
  44. package/skills/wecomcli-doc/references/doc-create.md +161 -0
  45. package/skills/wecomcli-doc/scripts/build_docx.py +1375 -0
  46. package/skills/wecomcli-doc-manage/SKILL.md +135 -0
  47. package/skills/wecomcli-doc-manage/references/doc-members-update.md +26 -0
  48. package/skills/wecomcli-doc-manage/references/doc-names-update.md +22 -0
  49. package/skills/wecomcli-doc-manage/references/doc-rules-update.md +24 -0
  50. package/skills/wecomcli-email/SKILL.md +216 -0
  51. package/skills/wecomcli-email/references/forward-mail.md +131 -0
  52. package/skills/wecomcli-email/references/get-mail.md +168 -0
  53. package/skills/wecomcli-email/references/reply-mail.md +140 -0
  54. package/skills/wecomcli-email/references/search-mail.md +113 -0
  55. package/skills/wecomcli-email/references/security.md +53 -0
  56. package/skills/wecomcli-email/references/send-mail.md +186 -0
  57. package/skills/wecomcli-email/references/send-schedule.md +83 -0
  58. package/skills/wecomcli-media/SKILL.md +100 -0
  59. package/skills/wecomcli-meeting/SKILL.md +374 -0
  60. package/skills/wecomcli-meeting/references/meeting-cancel.md +115 -0
  61. package/skills/wecomcli-meeting/references/meeting-create.md +169 -0
  62. package/skills/wecomcli-meeting/references/meeting-list.md +228 -0
  63. package/skills/wecomcli-meeting/references/meeting-original-get.md +100 -0
  64. package/skills/wecomcli-meeting/references/meeting-search.md +175 -0
  65. package/skills/wecomcli-meeting/references/meeting-update.md +219 -0
  66. package/skills/wecomcli-message/SKILL.md +213 -0
  67. package/skills/wecomcli-preflight/SKILL.md +147 -0
  68. package/skills/wecomcli-sheet/SKILL.md +177 -0
  69. package/skills/wecomcli-sheet/references/sheet-contents-update.md +49 -0
  70. package/skills/wecomcli-sheet/references/sheet-ranges-get.md +47 -0
  71. package/skills/wecomcli-sheet/references/sheet-rows-append.md +47 -0
  72. package/skills/wecomcli-sheet/references/sheet-subsheets-add.md +28 -0
  73. package/skills/wecomcli-sheet/references/sheet-subsheets-delete.md +22 -0
  74. package/skills/wecomcli-smartpage/SKILL.md +172 -0
  75. package/skills/wecomcli-smartpage/references/data-driven-pages.md +50 -0
  76. package/skills/wecomcli-smartpage/references/formula/arraylist.md +369 -0
  77. package/skills/wecomcli-smartpage/references/formula/datetime.md +283 -0
  78. package/skills/wecomcli-smartpage/references/formula/logic.md +247 -0
  79. package/skills/wecomcli-smartpage/references/formula/math.md +362 -0
  80. package/skills/wecomcli-smartpage/references/formula/operators.md +246 -0
  81. package/skills/wecomcli-smartpage/references/formula/pageblock.md +76 -0
  82. package/skills/wecomcli-smartpage/references/formula/templates.md +410 -0
  83. package/skills/wecomcli-smartpage/references/formula/text.md +377 -0
  84. package/skills/wecomcli-smartpage/references/formula/user.md +22 -0
  85. package/skills/wecomcli-smartpage/references/formula-reference.md +192 -0
  86. package/skills/wecomcli-smartpage/references/mdx-syntax.md +739 -0
  87. package/skills/wecomcli-smartpage/references/smartpage-edit.md +519 -0
  88. package/skills/wecomcli-smartsheet/SKILL.md +153 -0
  89. package/skills/wecomcli-smartsheet/assets/templates/README.md +53 -0
  90. package/skills/wecomcli-smartsheet/assets/templates/ai_efficiency.md +709 -0
  91. package/skills/wecomcli-smartsheet/assets/templates/connect_to_app.md +380 -0
  92. package/skills/wecomcli-smartsheet/assets/templates/financial_accounting.md +369 -0
  93. package/skills/wecomcli-smartsheet/assets/templates/hr_and_administration.md +475 -0
  94. package/skills/wecomcli-smartsheet/assets/templates/ledger_records.md +156 -0
  95. package/skills/wecomcli-smartsheet/assets/templates/manufacturing.md +395 -0
  96. package/skills/wecomcli-smartsheet/assets/templates/marketing.md +186 -0
  97. package/skills/wecomcli-smartsheet/assets/templates/office_essentials.md +299 -0
  98. package/skills/wecomcli-smartsheet/assets/templates/personal_efficiency.md +70 -0
  99. package/skills/wecomcli-smartsheet/assets/templates/procurement_logistics.md +325 -0
  100. package/skills/wecomcli-smartsheet/assets/templates/project_management.md +564 -0
  101. package/skills/wecomcli-smartsheet/assets/templates/rd_process_customer.md +222 -0
  102. package/skills/wecomcli-smartsheet/assets/templates/rd_process_ops.md +105 -0
  103. package/skills/wecomcli-smartsheet/assets/templates/rd_process_project.md +109 -0
  104. package/skills/wecomcli-smartsheet/assets/templates/rd_process_research.md +92 -0
  105. package/skills/wecomcli-smartsheet/assets/templates/sales_and_operations.md +446 -0
  106. package/skills/wecomcli-smartsheet/assets/templates/store_management.md +431 -0
  107. package/skills/wecomcli-smartsheet/assets/templates/team_tasks.md +274 -0
  108. package/skills/wecomcli-smartsheet/assets/templates/wechat_customer.md +384 -0
  109. package/skills/wecomcli-smartsheet/assets/templates/work_report.md +100 -0
  110. package/skills/wecomcli-smartsheet/references/common.md +145 -0
  111. package/skills/wecomcli-smartsheet/references/smart-sheet-chart-types.md +95 -0
  112. package/skills/wecomcli-smartsheet/references/smart-sheet-edit.md +611 -0
  113. package/skills/wecomcli-smartsheet/references/smart-sheet-field-types.md +438 -0
  114. package/skills/wecomcli-smartsheet/references/smart-sheet-formula.md +845 -0
  115. package/skills/wecomcli-smartsheet/references/smart-sheet-read.md +411 -0
  116. package/skills/wecomcli-smartsheet/references/smart-sheet-record-values.md +203 -0
  117. package/skills/wecomcli-smartsheet/references/smart-sheet-view-types.md +356 -0
  118. package/skills/{wecom-smartsheet/references/webhook-examples.md → wecomcli-smartsheet/references/smart-sheet-webhook-examples.md} +23 -32
  119. package/skills/wecomcli-smartsheet/references/smart-sheet-webhook.md +169 -0
  120. package/skills/wecomcli-todo/SKILL.md +75 -0
  121. package/skills/wecomcli-todo/references/todo-create.md +139 -0
  122. package/skills/wecomcli-todo/references/todo-delete.md +65 -0
  123. package/skills/wecomcli-todo/references/todo-finish.md +74 -0
  124. package/skills/wecomcli-todo/references/todo-get.md +68 -0
  125. package/skills/wecomcli-todo/references/todo-list.md +135 -0
  126. package/skills/wecomcli-todo/references/todo-update.md +114 -0
  127. package/dist/src/mcp/index.d.ts +0 -6
  128. package/dist/src/mcp/index.js +0 -10
  129. package/dist/src/mcp/interceptors/biz-error.d.ts +0 -11
  130. package/dist/src/mcp/interceptors/biz-error.js +0 -69
  131. package/dist/src/mcp/interceptors/doc-auth-error.d.ts +0 -10
  132. package/dist/src/mcp/interceptors/doc-auth-error.js +0 -171
  133. package/dist/src/mcp/interceptors/index.d.ts +0 -35
  134. package/dist/src/mcp/interceptors/index.js +0 -70
  135. package/dist/src/mcp/interceptors/msg-media.d.ts +0 -11
  136. package/dist/src/mcp/interceptors/msg-media.js +0 -133
  137. package/dist/src/mcp/interceptors/smartpage-create.d.ts +0 -30
  138. package/dist/src/mcp/interceptors/smartpage-create.js +0 -132
  139. package/dist/src/mcp/interceptors/smartpage-export.d.ts +0 -17
  140. package/dist/src/mcp/interceptors/smartpage-export.js +0 -87
  141. package/dist/src/mcp/interceptors/smartsheet-upload.d.ts +0 -22
  142. package/dist/src/mcp/interceptors/smartsheet-upload.js +0 -263
  143. package/dist/src/mcp/interceptors/types.d.ts +0 -64
  144. package/dist/src/mcp/interceptors/types.js +0 -7
  145. package/dist/src/mcp/schema.d.ts +0 -11
  146. package/dist/src/mcp/schema.js +0 -99
  147. package/dist/src/mcp/tool.d.ts +0 -63
  148. package/dist/src/mcp/tool.js +0 -224
  149. package/dist/src/mcp/transport.d.ts +0 -94
  150. package/dist/src/mcp/transport.js +0 -549
  151. package/skills/wecom-contact/SKILL.md +0 -162
  152. package/skills/wecom-doc/SKILL.md +0 -127
  153. package/skills/wecom-meeting/SKILL.md +0 -441
  154. package/skills/wecom-meeting/references/example-full.md +0 -30
  155. package/skills/wecom-meeting/references/example-reminder.md +0 -46
  156. package/skills/wecom-meeting/references/example-security.md +0 -22
  157. package/skills/wecom-meeting/references/response-get-meeting-info.md +0 -148
  158. package/skills/wecom-msg/SKILL.md +0 -157
  159. package/skills/wecom-msg/references/api-get-messages.md +0 -93
  160. package/skills/wecom-msg/references/api-get-msg-chat-list.md +0 -58
  161. package/skills/wecom-msg/references/api-get-msg-media.md +0 -44
  162. package/skills/wecom-msg/references/api-send-message.md +0 -39
  163. package/skills/wecom-preflight/SKILL.md +0 -141
  164. package/skills/wecom-schedule/SKILL.md +0 -161
  165. package/skills/wecom-schedule/references/api-check-availability.md +0 -56
  166. package/skills/wecom-schedule/references/api-create-schedule.md +0 -38
  167. package/skills/wecom-schedule/references/api-get-schedule-detail.md +0 -81
  168. package/skills/wecom-schedule/references/api-update-schedule.md +0 -32
  169. package/skills/wecom-schedule/references/ref-reminders.md +0 -24
  170. package/skills/wecom-sheet/SKILL.md +0 -131
  171. package/skills/wecom-sheet/references/sheet-add-sub.md +0 -54
  172. package/skills/wecom-sheet/references/sheet-append-data.md +0 -64
  173. package/skills/wecom-sheet/references/sheet-delete-sub.md +0 -38
  174. package/skills/wecom-sheet/references/sheet-update-range-data.md +0 -95
  175. package/skills/wecom-smartpage/SKILL.md +0 -119
  176. package/skills/wecom-smartpage/references/smartpage-create.md +0 -130
  177. package/skills/wecom-smartpage/references/smartpage-export.md +0 -168
  178. package/skills/wecom-smartsheet/SKILL.md +0 -241
  179. package/skills/wecom-smartsheet/references/smartsheet-cell-value-formats.md +0 -163
  180. package/skills/wecom-smartsheet/references/smartsheet-field-types.md +0 -44
  181. package/skills/wecom-smartsheet/references/smartsheet-get-records.md +0 -187
  182. package/skills/wecom-smartsheet/references/webhook-fallback.md +0 -183
  183. package/skills/wecom-todo/SKILL.md +0 -392
  184. package/skills/wecom-todo/examples/workflows.md +0 -163
@@ -0,0 +1,114 @@
1
+ # 修改待办 — `wecom-cli todo update`
2
+
3
+ 批量更新待办的标题、描述、分派人名单或截止时间。
4
+
5
+ ## 调用方式
6
+
7
+ 使用 wecom-cli 工具按照下面格式调用
8
+
9
+ ```
10
+ wecom-cli todo update --json '<JSON 参数>'
11
+ ```
12
+
13
+ ## 参数
14
+
15
+ 外层为对象,待更新的待办放在 `items` 数组中(支持批量):
16
+
17
+ | 字段 | 类型 | 必填 | 语义 |
18
+ |---|---|---|---|
19
+ | `items` | array | 是 | 更新条目数组,每项结构见下,单次最多 20 条;超出需分批 |
20
+
21
+ `items[]` 元素结构(仅传需修改的字段,未传字段保持不变):
22
+
23
+ | 字段 | 类型 | 必填 | 默认值 | 语义 |
24
+ |---|---|---|---|---|
25
+ | `todo_id` | string | 是 | — | 待办 ID(前缀 `td`) |
26
+ | `title` | string | 否 | — | 新的短标题 |
27
+ | `description` | string | 否 | — | 新的详细描述 |
28
+ | `followers` | array | 否 | — | **全量替换**后的分派人列表,最多 50 人;用户给姓名时使用 `wecomcli-contact` 技能获取 `userid`(前缀 `wo`) |
29
+ | `deadline` | object | 否 | — | 新的截止时间;结构见 SKILL.md `deadline` 对象规范。**传空对象 `{}` 表示清空已设置的截止时间**;不传字段则保持原值 |
30
+ | `remind_at_deadline` | boolean | 否 | `false` | 提醒时机,须与 `deadline` 同传:`true`=截止时刻提醒(仅 `datetime`);`false`/不传=按后台默认提前时间提醒(**非关闭提醒**)。脱离 `deadline` 单独传无效 |
31
+
32
+ `followers` 对象结构:
33
+
34
+ | 子字段 | 类型 | 必填 | 语义 |
35
+ |---|---|---|---|
36
+ | `userid` | string | 是 | 分派人 `userid`,前缀 `wo` |
37
+
38
+ > 入参的 `followers` 子对象**只接收 `userid`**。`wecom-cli todo list` / `wecom-cli todo get` 返回的 `followers` 还含 `user_name` / `user_status` / `update_time`,转入更新入参时全部剥掉,只保留 `userid`。
39
+
40
+ > `followers` 是全量替换,不是增量添加。只新增或移除部分参与人时,先从 `todo list` / `todo get` 取得现有名单,在本地合并或删减,再把所有应保留的参与人重新传入。
41
+
42
+ > 用户说"把我也加进去"、"分派给我和某某"时,`followers` 里同样要带上当前用户自己的 `userid`。
43
+
44
+ ### 修改截止时间与提醒
45
+
46
+ - `remind_at_deadline` 必须与 `deadline` 一起传,语义与 create 完全一致;**只传 `remind_at_deadline`、不带 `deadline` 不会生效**,不要这么做。
47
+ - 用户只改**截止时间/到期时间**时,填写新的 `deadline`,不要传 `remind_at_deadline`(即按后台默认提前时间提醒)。
48
+ - 用户要求把待办改成"某时间提醒 / 定时提醒 / 截止时提醒"且给出具体时刻时,将该时刻作为新的 `deadline.type=datetime`,并传 `remind_at_deadline=true`;只给日期时不传 `remind_at_deadline=true`。
49
+ - **`remind_at_deadline=false` 或不传 ≠ 关闭提醒**,而是按后台默认提前时间提醒。**update 入参没有关闭提醒的开关**(`remind_at_deadline` 只切换提醒时机;是否真正提醒由后台判断):用户要"取消提醒 / 关掉提醒 / 别提醒了"时,直接告知目前不支持关闭待办提醒;若用户坚持完全不提醒,唯一办法是连同截止时间一起清空(`deadline: {}`,会一并删掉截止时间),须先向用户确认再操作。
50
+ - 用户要求"某时间截止,并提前 X 提醒"时,`deadline` 永远填用户说的**截止时间**,不要填提前后的提醒时刻。当前入参不能直接设置"提前 X";更新后用返回的 `extra_info` 判断系统提醒时间是否刚好满足 X。
51
+
52
+ ### 示例入参
53
+
54
+ 更新标题、截止时间并设置截止时提醒:
55
+
56
+ ```json
57
+ {
58
+ "items": [
59
+ {
60
+ "todo_id": "td_xxx",
61
+ "title": "调整后的周会材料",
62
+ "deadline": {
63
+ "type": "datetime",
64
+ "value": "2026-05-13 09:00:00"
65
+ },
66
+ "remind_at_deadline": true
67
+ }
68
+ ]
69
+ }
70
+ ```
71
+
72
+ 清空截止时间、清空分派人:
73
+
74
+ ```json
75
+ {
76
+ "items": [
77
+ {
78
+ "todo_id": "td_xxx",
79
+ "deadline": {},
80
+ "followers": []
81
+ }
82
+ ]
83
+ }
84
+ ```
85
+
86
+ ## 返回
87
+
88
+ 外层为对象,结果在 `items` 数组中,与入参 `items` 一一对应:
89
+
90
+ | 字段 | 类型 | 语义 |
91
+ |---|---|---|
92
+ | `items` | array | 更新结果数组 |
93
+
94
+ `items[]` 元素结构:
95
+
96
+ | 字段 | 类型 | 语义 |
97
+ |---|---|---|
98
+ | `success` | boolean | 是否更新成功 |
99
+ | `todo_id` | string | 待办 ID |
100
+ | `extra_info` | string | 提醒时刻只读信息,可能不提醒 |
101
+ | `errmsg` | string | 失败原因,仅 `success=false` 时存在 |
102
+
103
+ ## 使用规则
104
+
105
+ - **如果上下文没有对应待办 ID**:**必须**先阅读 `references/todo-list.md`,在待办列表中找到需要修改的待办。
106
+ - **避免冗余更新**:如果用户只是把待办**已经记录过的内容又复述了一遍**(例如标题已经等于用户这次说的内容),这是确认而不是修改,**不要发起 `update`**,直接回复"这条已经记好了"即可。尤其**不要把 `description` 更新成与 `title` 相同的内容**——description 只用于承载标题之外的补充信息,没有新增信息就不要写。
107
+ - **补全信息先查上下文**:用户要求"写清楚点"、补充参与人/时间/链接/单号时,先从当前会话、待办详情和可用的聊天/记忆检索结果中找;能确定就更新,找不到或有歧义时再一次性向用户确认,避免直接让用户重发。
108
+ - **仅改部分字段**:未传的字段保持原值;若要清空 `followers`,传空数组 `[]`;若要清空 `deadline`,传空对象 `{}`。**没有关闭提醒的入参**:`remind_at_deadline=false`/不传只是改成默认提前提醒,不会关闭提醒(详见「修改截止时间与提醒」)
109
+ - **本次更新传了 `remind_at_deadline=true` 或用户提到提醒诉求** 且更新成功时,**必须**在最终回复中附上提醒说明(注意 `remind_at_deadline` 只对 datetime 生效):
110
+ - 用户要求"提前 X 提醒"时,核对 `extra_info` 是否为用户要求的提前提醒时间(即截止时间提前 X 后的时刻);匹配则说明已满足,不匹配或无 `extra_info` 则按固定话术说明:`目前不支持直接创建您需要的提醒时间,已为您设置截止时间为 XX,请到企业微信待办功能中手动修改提醒时间。`(XX 填本次 `deadline.value`)。
111
+ - 用户要求"截止时/到点提醒"时,只有 `deadline.type=datetime` 才应传 `remind_at_deadline=true`;若 `extra_info` 不等于 `deadline.value` 或缺失,仍需引导到企业微信待办功能中修改提醒时间。
112
+ - 返回里有 `extra_info`(且非"提前 X 提醒"场景)→ 引用 `extra_info` 里的时刻告诉用户届时会自动提醒。
113
+ - 返回里没有 `extra_info`(且非"提前 X 提醒"场景)→ 说明返回未确认提醒时间,引导用户到企业微信待办应用中检查/修改提醒时间。
114
+ - 不要另建定时任务来模拟待办提醒,避免重复提醒。仅改 `deadline` 但未要求提醒时,无需额外提醒说明。
@@ -1,6 +0,0 @@
1
- /**
2
- * MCP 模块统一导出
3
- */
4
- export { createWeComMcpTool } from "./tool.js";
5
- export { sendJsonRpc, clearCategoryCache, clearAccountCache, resolveCurrentAccountId, McpRpcError, McpHttpError, type McpToolInfo } from "./transport.js";
6
- export { cleanSchemaForGemini } from "./schema.js";
@@ -1,10 +0,0 @@
1
- /**
2
- * MCP 模块统一导出
3
- */
4
- export { createWeComMcpTool } from "./tool.js";
5
- export { sendJsonRpc, clearCategoryCache, clearAccountCache, resolveCurrentAccountId, McpRpcError, McpHttpError } from "./transport.js";
6
- export { cleanSchemaForGemini } from "./schema.js";
7
- // 注意:parseSessionKeyChat 已废弃不再导出。
8
- // OpenClaw core 构建 sessionKey 时会把 chatId 小写化,
9
- // 企业微信接口(如 aibot_send_biz_msg)是大小写敏感的,反解结果不可用。
10
- // chatId 请通过 state-manager 的 getSessionChatInfo(sessionKey) 获取。
@@ -1,11 +0,0 @@
1
- /**
2
- * 业务错误码检查拦截器
3
- *
4
- * 检查 tools/call 返回结果中是否包含需要清理缓存的业务错误码。
5
- * MCP Server 可能在正常的 JSON-RPC 响应中返回业务层错误,
6
- * 这些错误被包裹在 result.content[].text 中,需要解析后判断。
7
- *
8
- * 此拦截器对所有 call 调用生效。
9
- */
10
- import type { CallInterceptor } from "./types.js";
11
- export declare const bizErrorInterceptor: CallInterceptor;
@@ -1,69 +0,0 @@
1
- /**
2
- * 业务错误码检查拦截器
3
- *
4
- * 检查 tools/call 返回结果中是否包含需要清理缓存的业务错误码。
5
- * MCP Server 可能在正常的 JSON-RPC 响应中返回业务层错误,
6
- * 这些错误被包裹在 result.content[].text 中,需要解析后判断。
7
- *
8
- * 此拦截器对所有 call 调用生效。
9
- */
10
- import { clearCategoryCache } from "../transport.js";
11
- // ============================================================================
12
- // 常量
13
- // ============================================================================
14
- /**
15
- * 需要触发缓存清理的业务错误码集合
16
- *
17
- * 这些错误码出现在 MCP 工具调用返回的 content 文本中(业务层面),
18
- * 与 JSON-RPC 层面的错误码不同,需要在此处额外检测。
19
- *
20
- * - 850001/ 851014: 机器人授权过期/ 重置,需清理缓存以便下次重新拉取配置
21
- */
22
- const BIZ_CACHE_CLEAR_ERROR_CODES = new Set([850001, 851014]);
23
- // ============================================================================
24
- // 拦截器实现
25
- // ============================================================================
26
- export const bizErrorInterceptor = {
27
- name: "biz-error",
28
- /** 对所有 call 调用生效 */
29
- match: () => true,
30
- /** 检查返回结果中的业务错误码,必要时清理缓存 */
31
- afterCall(ctx, result) {
32
- checkBizErrorAndClearCache(result, ctx);
33
- // 不修改 result,透传给下一个拦截器
34
- return result;
35
- },
36
- };
37
- // ============================================================================
38
- // 内部实现
39
- // ============================================================================
40
- /**
41
- * 检查 tools/call 的返回结果中是否包含需要清理缓存的业务错误码
42
- */
43
- function checkBizErrorAndClearCache(result, ctx) {
44
- if (!result || typeof result !== "object")
45
- return;
46
- const { content } = result;
47
- if (!Array.isArray(content))
48
- return;
49
- for (const item of content) {
50
- if (item.type !== "text" || !item.text)
51
- continue;
52
- try {
53
- const parsed = JSON.parse(item.text);
54
- if (typeof parsed.errcode === "number" && BIZ_CACHE_CLEAR_ERROR_CODES.has(parsed.errcode)) {
55
- const accountId = ctx.accountId;
56
- if (!accountId) {
57
- console.warn(`[mcp] 检测到业务错误码 ${parsed.errcode} (category="${ctx.category}"),但 ctx.accountId 为空,跳过缓存清理`);
58
- return;
59
- }
60
- console.log(`[mcp] 检测到业务错误码 ${parsed.errcode} (accountId="${accountId}", category="${ctx.category}"),清理缓存`);
61
- clearCategoryCache(accountId, ctx.category);
62
- return;
63
- }
64
- }
65
- catch {
66
- // text 不是 JSON 格式,跳过
67
- }
68
- }
69
- }
@@ -1,10 +0,0 @@
1
- /**
2
- * 文档授权错误拦截器
3
- *
4
- * 当 category=doc 的 MCP 调用返回 errcode=851013/851014/851008(文档授权错误)时:
5
- * 1. 通过 aibot_send_biz_msg 命令向用户发送授权引导卡片
6
- * 2. 拦截 help_message 内容,不将其传递给 LLM
7
- * 3. 返回简化响应,告知 LLM 授权卡片已发送、无需再做处理
8
- */
9
- import type { CallInterceptor } from "./types.js";
10
- export declare const docAuthErrorInterceptor: CallInterceptor;
@@ -1,171 +0,0 @@
1
- /**
2
- * 文档授权错误拦截器
3
- *
4
- * 当 category=doc 的 MCP 调用返回 errcode=851013/851014/851008(文档授权错误)时:
5
- * 1. 通过 aibot_send_biz_msg 命令向用户发送授权引导卡片
6
- * 2. 拦截 help_message 内容,不将其传递给 LLM
7
- * 3. 返回简化响应,告知 LLM 授权卡片已发送、无需再做处理
8
- */
9
- import { generateReqId } from "@wecom/aibot-node-sdk";
10
- import { getWeComWebSocket } from "../../state-manager.js";
11
- import { resolveCurrentAccountId } from "../transport.js";
12
- import { withTimeout } from "../../timeout.js";
13
- import { AIBOT_SEND_BIZ_MSG_CMD, BIZ_MSG_SEND_TIMEOUT_MS } from "../../const.js";
14
- // ============================================================================
15
- // 常量
16
- // ============================================================================
17
- /** 需要拦截的文档授权错误码集合 */
18
- /* https://doc.weixin.qq.com/sheet/e3_AFcARgbdAFwCNU0pwubawRtGzcd6z?scode=AJEAIQdfAAoEpqtRfRAMEA4QYkACk&tab=BB08J2 */
19
- const DOC_AUTH_ERROR_CODES = new Set([851013, 851014, 851008]);
20
- /**
21
- * 业务消息类型枚举(对应 AiBotBizMsgType)
22
- * - 1: 文档权限
23
- */
24
- const AIBOT_BIZ_MSG_TYPE_DOC_READ_AUTH = 1;
25
- /**
26
- * 聊天类型枚举(对应 AiBotBizMsgChatType)
27
- * - 1: 单聊
28
- * - 2: 群聊
29
- */
30
- const AIBOT_BIZ_MSG_CHAT_TYPE_SINGLE = 1;
31
- const AIBOT_BIZ_MSG_CHAT_TYPE_GROUP = 2;
32
- // ============================================================================
33
- // 拦截器实现
34
- // ============================================================================
35
- export const docAuthErrorInterceptor = {
36
- name: "doc-auth-error",
37
- /** 仅对 doc 品类生效 */
38
- match: (ctx) => ctx.category === "doc",
39
- /** 检测文档授权错误,发送授权引导卡片并拦截原始响应 */
40
- async afterCall(ctx, result) {
41
- return interceptDocAuthError(ctx, result);
42
- },
43
- };
44
- // ============================================================================
45
- // 内部实现
46
- // ============================================================================
47
- /**
48
- * 从 MCP 返回结果中解析业务 JSON
49
- */
50
- function parseBizData(result) {
51
- if (!result || typeof result !== "object")
52
- return null;
53
- const { content } = result;
54
- if (!Array.isArray(content))
55
- return null;
56
- const textItem = content.find((c) => c.type === "text" && typeof c.text === "string");
57
- if (!textItem)
58
- return null;
59
- try {
60
- return JSON.parse(textItem.text);
61
- }
62
- catch {
63
- return null;
64
- }
65
- }
66
- /**
67
- * 通过 WSClient 发送 aibot_send_biz_msg 命令
68
- *
69
- * 按照 WwOpenWsSendAiBotBizMsgReq proto 定义构造请求体:
70
- * - chat_id: 会话标识
71
- * - userid: 发送者 userid
72
- * - biz_type: 业务消息类型(AiBotBizMsgType)
73
- * - chat_type: 聊天类型(AiBotBizMsgChatType,1=单聊,2=群聊)
74
- */
75
- async function sendBizMsg(params) {
76
- const accountId = params.accountId || resolveCurrentAccountId();
77
- const wsClient = getWeComWebSocket(accountId);
78
- if (!wsClient) {
79
- console.warn(`[mcp] doc-auth-error: WSClient 未连接 (accountId="${accountId}"),无法发送授权卡片`);
80
- return;
81
- }
82
- const reqId = generateReqId("biz_msg");
83
- // 构造 WwOpenWsSendAiBotBizMsgReqBody
84
- const body = {
85
- biz_type: AIBOT_BIZ_MSG_TYPE_DOC_READ_AUTH,
86
- };
87
- if (params.chatId) {
88
- body.chat_id = params.chatId;
89
- }
90
- if (params.userid) {
91
- body.userid = params.userid;
92
- }
93
- if (params.chatType) {
94
- body.chat_type = params.chatType === "group"
95
- ? AIBOT_BIZ_MSG_CHAT_TYPE_GROUP
96
- : AIBOT_BIZ_MSG_CHAT_TYPE_SINGLE;
97
- }
98
- console.log(`[mcp] doc-auth-error: 发送 aibot_send_biz_msg, body=${JSON.stringify(body)}`);
99
- try {
100
- await withTimeout(wsClient.reply({ headers: { req_id: reqId } }, body, AIBOT_SEND_BIZ_MSG_CMD), BIZ_MSG_SEND_TIMEOUT_MS, `aibot_send_biz_msg timed out after ${BIZ_MSG_SEND_TIMEOUT_MS}ms`);
101
- console.log(`[mcp] doc-auth-error: 授权卡片发送成功 (chatId="${params.chatId}", userid="${params.userid}", chatType="${params.chatType}")`);
102
- }
103
- catch (err) {
104
- console.error(`[mcp] doc-auth-error: 授权卡片发送失败: ${err instanceof Error ? err.message : JSON.stringify(err)}`);
105
- }
106
- }
107
- /**
108
- * 拦截文档授权错误
109
- *
110
- * 检测 errcode=851013/851014/851008 时:
111
- * 1. 通过 aibot_send_biz_msg 发送授权引导卡片(携带 chat_id、userid、chat_type)
112
- * 2. 返回简化响应,告知 LLM 授权卡片已直接发送给用户
113
- *
114
- * 数据来源链路(chatId 全程保留原始大小写,单一事实来源):
115
- *
116
- * aibot_msg_callback.body.chatid (原始大小写)
117
- * ↓
118
- * monitor.ts:processWeComMessageNow
119
- * → setSessionChatInfo(route.sessionKey, { chatId, chatType }) ← 以 sessionKey 为键
120
- * ↓
121
- * OpenClaw core 调用工具工厂,传入 ctx.sessionKey
122
- * ↓
123
- * index.ts:registerTool
124
- * → getSessionChatInfo(ctx.sessionKey) 精确取回 { chatId, chatType }
125
- * → createWeComMcpTool({ chatId, chatType })
126
- * ↓
127
- * tool.ts:handleCall 将其注入 CallContext
128
- * ↓
129
- * 本拦截器直接使用 ctx.chatId / ctx.chatType (无需反解 sessionKey、无需兜底)
130
- *
131
- * ⚠️ 切勿使用 parseSessionKeyChat(ctx.sessionKey) 反向解析 chatId:
132
- * OpenClaw core 构建 sessionKey 时会将 peer.id 强制小写化,
133
- * 企业微信 aibot_send_biz_msg 的 chat_id 是大小写敏感的,
134
- * 小写后会收到 errcode=93006 (invalid chatid)。
135
- */
136
- async function interceptDocAuthError(ctx, result) {
137
- const bizData = parseBizData(result);
138
- if (!bizData)
139
- return result;
140
- const errcode = bizData.errcode;
141
- if (typeof errcode !== "number" || !DOC_AUTH_ERROR_CODES.has(errcode)) {
142
- return result;
143
- }
144
- console.log(`[mcp] doc-auth-error: 检测到文档授权错误 errcode=${errcode}, ` +
145
- `method=${ctx.method}, category=${ctx.category}`);
146
- // chatId / chatType 已由 monitor.ts → state-manager → index.ts 透传到 ctx,
147
- // 这里直接使用即可,原始大小写保留不变。
148
- const { requesterUserId: userid, chatId, chatType, accountId } = ctx;
149
- if (!chatId || !chatType) {
150
- console.warn(`[mcp] doc-auth-error: ctx 缺少 chatId/chatType(可能会话信息未记录),` +
151
- `userid="${userid}", chatId="${chatId}", chatType="${chatType}",跳过发送授权卡片`);
152
- }
153
- else {
154
- console.log(`[mcp] doc-auth-error: 发送授权卡片参数: userid="${userid}", chatId="${chatId}", chatType="${chatType}"`);
155
- await sendBizMsg({ chatId, userid, chatType, accountId });
156
- }
157
- // 构造简化响应:不将 help_message 传递给 LLM,
158
- // 告知 LLM 授权提示已直接发送给用户,无需再做任何操作
159
- const simplifiedResult = {
160
- errcode,
161
- errmsg: bizData.errmsg ?? "authorization error",
162
- _biz_msg_sent: true,
163
- _user_hint: "文档授权提示卡片已直接发送给用户,无需再向用户转述任何授权相关的信息。请告知用户:已发送授权引导,请按照提示完成授权后重试。",
164
- };
165
- return {
166
- content: [{
167
- type: "text",
168
- text: JSON.stringify(simplifiedResult),
169
- }],
170
- };
171
- }
@@ -1,35 +0,0 @@
1
- /**
2
- * MCP call 拦截器注册表与调度入口
3
- *
4
- * 所有 call 拦截器在此注册,按注册顺序执行。
5
- * 新增拦截器只需:
6
- * 1. 在 interceptors/ 目录下新建文件,实现 CallInterceptor 接口
7
- * 2. 在下方 interceptors 数组中注册
8
- *
9
- * tool.ts 的 handleCall 无需任何改动。
10
- */
11
- import type { CallContext } from "./types.js";
12
- import type { SendJsonRpcOptions } from "../transport.js";
13
- export type { CallContext, CallInterceptor, BeforeCallOptions } from "./types.js";
14
- /** resolveBeforeCall 的返回值 */
15
- export interface ResolvedBeforeCall {
16
- /** 合并后的 sendJsonRpc 选项(如超时时间) */
17
- options?: SendJsonRpcOptions;
18
- /** 替换后的 args(如从本地文件读取的请求体) */
19
- args?: Record<string, unknown>;
20
- }
21
- /**
22
- * 收集匹配的 beforeCall 配置,合并后返回
23
- *
24
- * 合并策略:
25
- * - timeoutMs: 取所有拦截器返回值中的最大值
26
- * - args: 后注册的拦截器覆盖前者(一般同一调用只有一个拦截器会返回 args)
27
- */
28
- export declare function resolveBeforeCall(ctx: CallContext): Promise<ResolvedBeforeCall>;
29
- /**
30
- * 依次执行匹配的 afterCall 拦截器,管道式传递 result
31
- *
32
- * 前一个拦截器的返回值作为下一个拦截器的输入。
33
- * 拦截器若不需要修改 result,应原样返回。
34
- */
35
- export declare function runAfterCall(ctx: CallContext, result: unknown): Promise<unknown>;
@@ -1,70 +0,0 @@
1
- /**
2
- * MCP call 拦截器注册表与调度入口
3
- *
4
- * 所有 call 拦截器在此注册,按注册顺序执行。
5
- * 新增拦截器只需:
6
- * 1. 在 interceptors/ 目录下新建文件,实现 CallInterceptor 接口
7
- * 2. 在下方 interceptors 数组中注册
8
- *
9
- * tool.ts 的 handleCall 无需任何改动。
10
- */
11
- import { bizErrorInterceptor } from "./biz-error.js";
12
- import { docAuthErrorInterceptor } from "./doc-auth-error.js";
13
- import { mediaInterceptor } from "./msg-media.js";
14
- import { smartpageCreateInterceptor } from "./smartpage-create.js";
15
- import { smartpageExportInterceptor } from "./smartpage-export.js";
16
- import { smartsheetUploadInterceptor } from "./smartsheet-upload.js";
17
- // ============================================================================
18
- // 拦截器注册表(按注册顺序执行)
19
- // ============================================================================
20
- const interceptors = [
21
- bizErrorInterceptor, // 业务错误码检查(所有 call 生效)
22
- docAuthErrorInterceptor, // 文档授权错误拦截(category=doc, errcode=851013/851014/851008)
23
- mediaInterceptor, // get_msg_media base64 拦截
24
- smartpageCreateInterceptor, // smartpage_create 本地文件读取
25
- smartpageExportInterceptor, // smartpage_get_export_result content → 本地文件
26
- smartsheetUploadInterceptor, // smartsheet_add_records / update_records 本地文件上传
27
- ];
28
- /**
29
- * 收集匹配的 beforeCall 配置,合并后返回
30
- *
31
- * 合并策略:
32
- * - timeoutMs: 取所有拦截器返回值中的最大值
33
- * - args: 后注册的拦截器覆盖前者(一般同一调用只有一个拦截器会返回 args)
34
- */
35
- export async function resolveBeforeCall(ctx) {
36
- let mergedTimeoutMs;
37
- let mergedArgs;
38
- for (const interceptor of interceptors) {
39
- if (!interceptor.match(ctx) || !interceptor.beforeCall)
40
- continue;
41
- const opts = await interceptor.beforeCall(ctx);
42
- if (opts?.timeoutMs !== undefined) {
43
- mergedTimeoutMs = mergedTimeoutMs === undefined
44
- ? opts.timeoutMs
45
- : Math.max(mergedTimeoutMs, opts.timeoutMs);
46
- }
47
- if (opts?.args !== undefined) {
48
- mergedArgs = opts.args;
49
- }
50
- }
51
- return {
52
- options: mergedTimeoutMs !== undefined ? { timeoutMs: mergedTimeoutMs } : undefined,
53
- args: mergedArgs,
54
- };
55
- }
56
- /**
57
- * 依次执行匹配的 afterCall 拦截器,管道式传递 result
58
- *
59
- * 前一个拦截器的返回值作为下一个拦截器的输入。
60
- * 拦截器若不需要修改 result,应原样返回。
61
- */
62
- export async function runAfterCall(ctx, result) {
63
- let current = result;
64
- for (const interceptor of interceptors) {
65
- if (!interceptor.match(ctx) || !interceptor.afterCall)
66
- continue;
67
- current = await interceptor.afterCall(ctx, current);
68
- }
69
- return current;
70
- }
@@ -1,11 +0,0 @@
1
- /**
2
- * get_msg_media 响应拦截器
3
- *
4
- * 核心逻辑:
5
- * 1. beforeCall: 设置延长的超时时间(120s),因为 base64 数据可达 ~27MB
6
- * 2. afterCall: 从 MCP result 的 content[].text 中提取 base64_data,
7
- * 解码为 Buffer 并通过 saveMediaBuffer 保存到本地媒体目录,
8
- * 替换响应中的 base64_data 为 local_path,避免大模型被 base64 数据消耗 token
9
- */
10
- import type { CallInterceptor } from "./types.js";
11
- export declare const mediaInterceptor: CallInterceptor;
@@ -1,133 +0,0 @@
1
- /**
2
- * get_msg_media 响应拦截器
3
- *
4
- * 核心逻辑:
5
- * 1. beforeCall: 设置延长的超时时间(120s),因为 base64 数据可达 ~27MB
6
- * 2. afterCall: 从 MCP result 的 content[].text 中提取 base64_data,
7
- * 解码为 Buffer 并通过 saveMediaBuffer 保存到本地媒体目录,
8
- * 替换响应中的 base64_data 为 local_path,避免大模型被 base64 数据消耗 token
9
- */
10
- import * as fs from "node:fs/promises";
11
- import * as path from "node:path";
12
- import { MEDIA_DOWNLOAD_TIMEOUT_MS } from "../transport.js";
13
- import { getWeComRuntime } from "../../runtime.js";
14
- import { detectMime } from "../../openclaw-compat.js";
15
- // ============================================================================
16
- // 拦截器实现
17
- // ============================================================================
18
- export const mediaInterceptor = {
19
- name: "media",
20
- /** 仅对 get_msg_media 方法生效 */
21
- match: (ctx) => ctx.method === "get_msg_media",
22
- /** 设置延长的超时时间 */
23
- beforeCall() {
24
- return { timeoutMs: MEDIA_DOWNLOAD_TIMEOUT_MS };
25
- },
26
- /** 拦截响应:base64 → 本地文件 */
27
- async afterCall(ctx, result) {
28
- return interceptMediaResponse(result);
29
- },
30
- };
31
- // ============================================================================
32
- // 内部实现
33
- // ============================================================================
34
- /**
35
- * 拦截 get_msg_media 的 MCP 响应
36
- *
37
- * 1. 从 MCP result 的 content[].text 中提取业务 JSON
38
- * 2. 提取 media_item.base64_data,解码为 Buffer
39
- * 3. 通过 openclaw SDK 的 saveMediaBuffer 保存到本地媒体目录
40
- * 4. 替换响应:移除 base64_data,加入 local_path
41
- *
42
- * 这样大模型只看到轻量的文件路径信息,不会被 base64 数据消耗 token。
43
- */
44
- async function interceptMediaResponse(result) {
45
- const t0 = performance.now();
46
- // 1. 提取 MCP result 中的 content 数组
47
- const content = result?.content;
48
- if (!Array.isArray(content))
49
- return result;
50
- const textItem = content.find((c) => c.type === "text" && typeof c.text === "string");
51
- if (!textItem)
52
- return result;
53
- // 2. 解析业务 JSON
54
- let bizData;
55
- try {
56
- bizData = JSON.parse(textItem.text);
57
- }
58
- catch {
59
- // 非 JSON 格式,原样返回
60
- return result;
61
- }
62
- // 3. 校验业务响应:errcode !== 0 或无 media_item 时原样返回
63
- if (bizData.errcode !== 0)
64
- return result;
65
- const mediaItem = bizData.media_item;
66
- if (!mediaItem || typeof mediaItem.base64_data !== "string")
67
- return result;
68
- const base64Data = mediaItem.base64_data;
69
- const mediaName = mediaItem.name;
70
- const mediaType = mediaItem.type;
71
- const mediaId = mediaItem.media_id;
72
- const tParsed = performance.now();
73
- // 4. 解码 base64 → Buffer
74
- const buffer = Buffer.from(base64Data, "base64");
75
- const tDecoded = performance.now();
76
- // 5. 检测 contentType,并通过 saveMediaBuffer 保存到本地媒体目录
77
- const contentType = await detectMime({ buffer, filePath: mediaName }) ?? "application/octet-stream";
78
- const tMimeDetected = performance.now();
79
- // 企业微信聊天记录附件可达 20MB(文件消息上限),
80
- // 而 saveMediaBuffer 默认 maxBytes 为 5MB(针对 outbound 场景),
81
- // 此处显式放宽到 20MB 以支持大文件下载。
82
- const INBOUND_MAX_BYTES = 20 * 1024 * 1024; // 20MB
83
- const core = getWeComRuntime();
84
- const saved = await core.channel.media.saveMediaBuffer(buffer, contentType, "inbound", INBOUND_MAX_BYTES, // maxBytes: 放宽到 20MB,匹配企业微信文件消息上限
85
- mediaName);
86
- // 5.1 补偿:核心库 EXT_BY_MIME 可能缺少某些格式映射(如 audio/amr),
87
- // 导致保存的文件没有后缀。此处检测并修复。
88
- const MIME_EXT_PATCH = {
89
- "audio/amr": ".amr",
90
- };
91
- const patchExt = MIME_EXT_PATCH[contentType];
92
- if (patchExt && !path.extname(saved.path)) {
93
- const newPath = saved.path + patchExt;
94
- try {
95
- await fs.rename(saved.path, newPath);
96
- saved.path = newPath;
97
- }
98
- catch {
99
- // rename 失败不影响主流程,文件仍可用
100
- }
101
- }
102
- const tSaved = performance.now();
103
- // 6. 构造精简响应,移除 base64_data,加入本地路径
104
- const newBizData = {
105
- errcode: 0,
106
- errmsg: "ok",
107
- media_item: {
108
- media_id: mediaId,
109
- name: mediaName ?? saved.path.split("/").pop(),
110
- type: mediaType,
111
- local_path: saved.path,
112
- size: buffer.length,
113
- content_type: saved.contentType,
114
- },
115
- };
116
- const tEnd = performance.now();
117
- // 耗时日志:各环节耗时(ms)
118
- console.log(`[mcp] get_msg_media 拦截成功: media_id=${mediaId ?? "unknown"}, ` +
119
- `type=${mediaType ?? "unknown"}, size=${buffer.length}, saved=${saved.path}\n` +
120
- ` ⏱ 耗时明细 (总 ${(tEnd - t0).toFixed(1)}ms):\n` +
121
- ` 解析响应 JSON: ${(tParsed - t0).toFixed(1)}ms\n` +
122
- ` base64 解码: ${(tDecoded - tParsed).toFixed(1)}ms (${(base64Data.length / 1024).toFixed(0)}KB base64 → ${(buffer.length / 1024).toFixed(0)}KB buffer)\n` +
123
- ` MIME 检测: ${(tMimeDetected - tDecoded).toFixed(1)}ms (${contentType})\n` +
124
- ` saveMediaBuffer: ${(tSaved - tMimeDetected).toFixed(1)}ms\n` +
125
- ` 构造响应: ${(tEnd - tSaved).toFixed(1)}ms`);
126
- // 7. 返回修改后的 MCP result 结构
127
- return {
128
- content: [{
129
- type: "text",
130
- text: JSON.stringify(newBizData),
131
- }],
132
- };
133
- }