@lark-project/openclaw-lark-project 2026.3.131

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 (368) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +80 -0
  3. package/README.zh.md +80 -0
  4. package/dist/index.js +172 -0
  5. package/dist/index.js.map +7 -0
  6. package/dist/skills/feishu-bitable/SKILL.md +248 -0
  7. package/dist/skills/feishu-bitable/references/examples.md +813 -0
  8. package/dist/skills/feishu-bitable/references/field-properties.md +763 -0
  9. package/dist/skills/feishu-bitable/references/record-values.md +911 -0
  10. package/dist/skills/feishu-calendar/SKILL.md +244 -0
  11. package/dist/skills/feishu-channel-rules/SKILL.md +18 -0
  12. package/dist/skills/feishu-channel-rules/references/markdown-syntax.md +138 -0
  13. package/dist/skills/feishu-create-doc/SKILL.md +719 -0
  14. package/dist/skills/feishu-fetch-doc/SKILL.md +93 -0
  15. package/dist/skills/feishu-im-read/SKILL.md +163 -0
  16. package/dist/skills/feishu-project/SKILL.md +122 -0
  17. package/dist/skills/feishu-task/SKILL.md +293 -0
  18. package/dist/skills/feishu-troubleshoot/SKILL.md +70 -0
  19. package/dist/skills/feishu-update-doc/SKILL.md +285 -0
  20. package/dist/src/card/builder.js +293 -0
  21. package/dist/src/card/builder.js.map +7 -0
  22. package/dist/src/card/cardkit.js +126 -0
  23. package/dist/src/card/cardkit.js.map +7 -0
  24. package/dist/src/card/flush-controller.js +107 -0
  25. package/dist/src/card/flush-controller.js.map +7 -0
  26. package/dist/src/card/markdown-style.js +57 -0
  27. package/dist/src/card/markdown-style.js.map +7 -0
  28. package/dist/src/card/reply-dispatcher-types.js +39 -0
  29. package/dist/src/card/reply-dispatcher-types.js.map +7 -0
  30. package/dist/src/card/reply-dispatcher.js +245 -0
  31. package/dist/src/card/reply-dispatcher.js.map +7 -0
  32. package/dist/src/card/reply-mode.js +29 -0
  33. package/dist/src/card/reply-mode.js.map +7 -0
  34. package/dist/src/card/streaming-card-controller.js +653 -0
  35. package/dist/src/card/streaming-card-controller.js.map +7 -0
  36. package/dist/src/card/unavailable-guard.js +76 -0
  37. package/dist/src/card/unavailable-guard.js.map +7 -0
  38. package/dist/src/channel/abort-detect.js +79 -0
  39. package/dist/src/channel/abort-detect.js.map +7 -0
  40. package/dist/src/channel/chat-queue.js +50 -0
  41. package/dist/src/channel/chat-queue.js.map +7 -0
  42. package/dist/src/channel/config-adapter.js +89 -0
  43. package/dist/src/channel/config-adapter.js.map +7 -0
  44. package/dist/src/channel/directory.js +133 -0
  45. package/dist/src/channel/directory.js.map +7 -0
  46. package/dist/src/channel/event-handlers.js +175 -0
  47. package/dist/src/channel/event-handlers.js.map +7 -0
  48. package/dist/src/channel/monitor.js +108 -0
  49. package/dist/src/channel/monitor.js.map +7 -0
  50. package/dist/src/channel/onboarding-config.js +76 -0
  51. package/dist/src/channel/onboarding-config.js.map +7 -0
  52. package/dist/src/channel/onboarding-migrate.js +55 -0
  53. package/dist/src/channel/onboarding-migrate.js.map +7 -0
  54. package/dist/src/channel/onboarding.js +285 -0
  55. package/dist/src/channel/onboarding.js.map +7 -0
  56. package/dist/src/channel/plugin.js +260 -0
  57. package/dist/src/channel/plugin.js.map +7 -0
  58. package/dist/src/channel/probe.js +14 -0
  59. package/dist/src/channel/probe.js.map +7 -0
  60. package/dist/src/channel/types.js +1 -0
  61. package/dist/src/channel/types.js.map +7 -0
  62. package/dist/src/commands/auth.js +73 -0
  63. package/dist/src/commands/auth.js.map +7 -0
  64. package/dist/src/commands/diagnose.js +658 -0
  65. package/dist/src/commands/diagnose.js.map +7 -0
  66. package/dist/src/commands/doctor.js +327 -0
  67. package/dist/src/commands/doctor.js.map +7 -0
  68. package/dist/src/commands/index.js +124 -0
  69. package/dist/src/commands/index.js.map +7 -0
  70. package/dist/src/core/accounts.js +129 -0
  71. package/dist/src/core/accounts.js.map +7 -0
  72. package/dist/src/core/agent-config.js +60 -0
  73. package/dist/src/core/agent-config.js.map +7 -0
  74. package/dist/src/core/api-error.js +55 -0
  75. package/dist/src/core/api-error.js.map +7 -0
  76. package/dist/src/core/app-owner-fallback.js +17 -0
  77. package/dist/src/core/app-owner-fallback.js.map +7 -0
  78. package/dist/src/core/app-scope-checker.js +95 -0
  79. package/dist/src/core/app-scope-checker.js.map +7 -0
  80. package/dist/src/core/auth-errors.js +120 -0
  81. package/dist/src/core/auth-errors.js.map +7 -0
  82. package/dist/src/core/chat-info-cache.js +102 -0
  83. package/dist/src/core/chat-info-cache.js.map +7 -0
  84. package/dist/src/core/config-schema.js +150 -0
  85. package/dist/src/core/config-schema.js.map +7 -0
  86. package/dist/src/core/device-flow.js +174 -0
  87. package/dist/src/core/device-flow.js.map +7 -0
  88. package/dist/src/core/feishu-fetch.js +12 -0
  89. package/dist/src/core/feishu-fetch.js.map +7 -0
  90. package/dist/src/core/footer-config.js +16 -0
  91. package/dist/src/core/footer-config.js.map +7 -0
  92. package/dist/src/core/lark-client.js +322 -0
  93. package/dist/src/core/lark-client.js.map +7 -0
  94. package/dist/src/core/lark-logger.js +92 -0
  95. package/dist/src/core/lark-logger.js.map +7 -0
  96. package/dist/src/core/lark-ticket.js +18 -0
  97. package/dist/src/core/lark-ticket.js.map +7 -0
  98. package/dist/src/core/message-unavailable.js +119 -0
  99. package/dist/src/core/message-unavailable.js.map +7 -0
  100. package/dist/src/core/owner-policy.js +25 -0
  101. package/dist/src/core/owner-policy.js.map +7 -0
  102. package/dist/src/core/permission-url.js +37 -0
  103. package/dist/src/core/permission-url.js.map +7 -0
  104. package/dist/src/core/project-auth.js +177 -0
  105. package/dist/src/core/project-auth.js.map +7 -0
  106. package/dist/src/core/project-oauth-flow.js +124 -0
  107. package/dist/src/core/project-oauth-flow.js.map +7 -0
  108. package/dist/src/core/project-token-store.js +172 -0
  109. package/dist/src/core/project-token-store.js.map +7 -0
  110. package/dist/src/core/raw-request.js +45 -0
  111. package/dist/src/core/raw-request.js.map +7 -0
  112. package/dist/src/core/scope-manager.js +62 -0
  113. package/dist/src/core/scope-manager.js.map +7 -0
  114. package/dist/src/core/security-check.js +118 -0
  115. package/dist/src/core/security-check.js.map +7 -0
  116. package/dist/src/core/shutdown-hooks.js +37 -0
  117. package/dist/src/core/shutdown-hooks.js.map +7 -0
  118. package/dist/src/core/targets.js +55 -0
  119. package/dist/src/core/targets.js.map +7 -0
  120. package/dist/src/core/token-store.js +215 -0
  121. package/dist/src/core/token-store.js.map +7 -0
  122. package/dist/src/core/tool-client.js +335 -0
  123. package/dist/src/core/tool-client.js.map +7 -0
  124. package/dist/src/core/tool-scopes.js +207 -0
  125. package/dist/src/core/tool-scopes.js.map +7 -0
  126. package/dist/src/core/tools-config.js +57 -0
  127. package/dist/src/core/tools-config.js.map +7 -0
  128. package/dist/src/core/types.js +1 -0
  129. package/dist/src/core/types.js.map +7 -0
  130. package/dist/src/core/uat-client.js +124 -0
  131. package/dist/src/core/uat-client.js.map +7 -0
  132. package/dist/src/core/version.js +27 -0
  133. package/dist/src/core/version.js.map +7 -0
  134. package/dist/src/messaging/converters/audio.js +19 -0
  135. package/dist/src/messaging/converters/audio.js.map +7 -0
  136. package/dist/src/messaging/converters/calendar.js +46 -0
  137. package/dist/src/messaging/converters/calendar.js.map +7 -0
  138. package/dist/src/messaging/converters/content-converter.js +61 -0
  139. package/dist/src/messaging/converters/content-converter.js.map +7 -0
  140. package/dist/src/messaging/converters/file.js +18 -0
  141. package/dist/src/messaging/converters/file.js.map +7 -0
  142. package/dist/src/messaging/converters/folder.js +18 -0
  143. package/dist/src/messaging/converters/folder.js.map +7 -0
  144. package/dist/src/messaging/converters/hongbao.js +14 -0
  145. package/dist/src/messaging/converters/hongbao.js.map +7 -0
  146. package/dist/src/messaging/converters/image.js +16 -0
  147. package/dist/src/messaging/converters/image.js.map +7 -0
  148. package/dist/src/messaging/converters/index.js +48 -0
  149. package/dist/src/messaging/converters/index.js.map +7 -0
  150. package/dist/src/messaging/converters/interactive/card-converter.js +1040 -0
  151. package/dist/src/messaging/converters/interactive/card-converter.js.map +7 -0
  152. package/dist/src/messaging/converters/interactive/card-utils.js +36 -0
  153. package/dist/src/messaging/converters/interactive/card-utils.js.map +7 -0
  154. package/dist/src/messaging/converters/interactive/index.js +19 -0
  155. package/dist/src/messaging/converters/interactive/index.js.map +7 -0
  156. package/dist/src/messaging/converters/interactive/legacy.js +53 -0
  157. package/dist/src/messaging/converters/interactive/legacy.js.map +7 -0
  158. package/dist/src/messaging/converters/interactive/types.js +23 -0
  159. package/dist/src/messaging/converters/interactive/types.js.map +7 -0
  160. package/dist/src/messaging/converters/location.js +17 -0
  161. package/dist/src/messaging/converters/location.js.map +7 -0
  162. package/dist/src/messaging/converters/merge-forward.js +143 -0
  163. package/dist/src/messaging/converters/merge-forward.js.map +7 -0
  164. package/dist/src/messaging/converters/post.js +113 -0
  165. package/dist/src/messaging/converters/post.js.map +7 -0
  166. package/dist/src/messaging/converters/share.js +22 -0
  167. package/dist/src/messaging/converters/share.js.map +7 -0
  168. package/dist/src/messaging/converters/sticker.js +16 -0
  169. package/dist/src/messaging/converters/sticker.js.map +7 -0
  170. package/dist/src/messaging/converters/system.js +25 -0
  171. package/dist/src/messaging/converters/system.js.map +7 -0
  172. package/dist/src/messaging/converters/text.js +12 -0
  173. package/dist/src/messaging/converters/text.js.map +7 -0
  174. package/dist/src/messaging/converters/todo.js +37 -0
  175. package/dist/src/messaging/converters/todo.js.map +7 -0
  176. package/dist/src/messaging/converters/types.js +1 -0
  177. package/dist/src/messaging/converters/types.js.map +7 -0
  178. package/dist/src/messaging/converters/unknown.js +13 -0
  179. package/dist/src/messaging/converters/unknown.js.map +7 -0
  180. package/dist/src/messaging/converters/utils.js +35 -0
  181. package/dist/src/messaging/converters/utils.js.map +7 -0
  182. package/dist/src/messaging/converters/video-chat.js +21 -0
  183. package/dist/src/messaging/converters/video-chat.js.map +7 -0
  184. package/dist/src/messaging/converters/video.js +30 -0
  185. package/dist/src/messaging/converters/video.js.map +7 -0
  186. package/dist/src/messaging/converters/vote.js +24 -0
  187. package/dist/src/messaging/converters/vote.js.map +7 -0
  188. package/dist/src/messaging/inbound/dedup.js +82 -0
  189. package/dist/src/messaging/inbound/dedup.js.map +7 -0
  190. package/dist/src/messaging/inbound/dispatch-builders.js +98 -0
  191. package/dist/src/messaging/inbound/dispatch-builders.js.map +7 -0
  192. package/dist/src/messaging/inbound/dispatch-commands.js +94 -0
  193. package/dist/src/messaging/inbound/dispatch-commands.js.map +7 -0
  194. package/dist/src/messaging/inbound/dispatch-context.js +96 -0
  195. package/dist/src/messaging/inbound/dispatch-context.js.map +7 -0
  196. package/dist/src/messaging/inbound/dispatch.js +150 -0
  197. package/dist/src/messaging/inbound/dispatch.js.map +7 -0
  198. package/dist/src/messaging/inbound/enrich.js +137 -0
  199. package/dist/src/messaging/inbound/enrich.js.map +7 -0
  200. package/dist/src/messaging/inbound/gate-effects.js +28 -0
  201. package/dist/src/messaging/inbound/gate-effects.js.map +7 -0
  202. package/dist/src/messaging/inbound/gate.js +163 -0
  203. package/dist/src/messaging/inbound/gate.js.map +7 -0
  204. package/dist/src/messaging/inbound/handler.js +132 -0
  205. package/dist/src/messaging/inbound/handler.js.map +7 -0
  206. package/dist/src/messaging/inbound/media-resolver.js +70 -0
  207. package/dist/src/messaging/inbound/media-resolver.js.map +7 -0
  208. package/dist/src/messaging/inbound/mention.js +50 -0
  209. package/dist/src/messaging/inbound/mention.js.map +7 -0
  210. package/dist/src/messaging/inbound/parse-io.js +41 -0
  211. package/dist/src/messaging/inbound/parse-io.js.map +7 -0
  212. package/dist/src/messaging/inbound/parse.js +79 -0
  213. package/dist/src/messaging/inbound/parse.js.map +7 -0
  214. package/dist/src/messaging/inbound/permission.js +30 -0
  215. package/dist/src/messaging/inbound/permission.js.map +7 -0
  216. package/dist/src/messaging/inbound/policy.js +83 -0
  217. package/dist/src/messaging/inbound/policy.js.map +7 -0
  218. package/dist/src/messaging/inbound/reaction-handler.js +162 -0
  219. package/dist/src/messaging/inbound/reaction-handler.js.map +7 -0
  220. package/dist/src/messaging/inbound/user-name-cache.js +172 -0
  221. package/dist/src/messaging/inbound/user-name-cache.js.map +7 -0
  222. package/dist/src/messaging/outbound/actions.js +239 -0
  223. package/dist/src/messaging/outbound/actions.js.map +7 -0
  224. package/dist/src/messaging/outbound/chat-manage.js +74 -0
  225. package/dist/src/messaging/outbound/chat-manage.js.map +7 -0
  226. package/dist/src/messaging/outbound/deliver.js +162 -0
  227. package/dist/src/messaging/outbound/deliver.js.map +7 -0
  228. package/dist/src/messaging/outbound/fetch.js +7 -0
  229. package/dist/src/messaging/outbound/fetch.js.map +7 -0
  230. package/dist/src/messaging/outbound/forward.js +31 -0
  231. package/dist/src/messaging/outbound/forward.js.map +7 -0
  232. package/dist/src/messaging/outbound/media-url-utils.js +101 -0
  233. package/dist/src/messaging/outbound/media-url-utils.js.map +7 -0
  234. package/dist/src/messaging/outbound/media.js +463 -0
  235. package/dist/src/messaging/outbound/media.js.map +7 -0
  236. package/dist/src/messaging/outbound/outbound.js +95 -0
  237. package/dist/src/messaging/outbound/outbound.js.map +7 -0
  238. package/dist/src/messaging/outbound/reactions.js +312 -0
  239. package/dist/src/messaging/outbound/reactions.js.map +7 -0
  240. package/dist/src/messaging/outbound/send.js +194 -0
  241. package/dist/src/messaging/outbound/send.js.map +7 -0
  242. package/dist/src/messaging/outbound/typing.js +77 -0
  243. package/dist/src/messaging/outbound/typing.js.map +7 -0
  244. package/dist/src/messaging/shared/message-lookup.js +84 -0
  245. package/dist/src/messaging/shared/message-lookup.js.map +7 -0
  246. package/dist/src/messaging/types.js +1 -0
  247. package/dist/src/messaging/types.js.map +7 -0
  248. package/dist/src/tools/auto-auth.js +714 -0
  249. package/dist/src/tools/auto-auth.js.map +7 -0
  250. package/dist/src/tools/helpers.js +133 -0
  251. package/dist/src/tools/helpers.js.map +7 -0
  252. package/dist/src/tools/mcp/doc/create.js +35 -0
  253. package/dist/src/tools/mcp/doc/create.js.map +7 -0
  254. package/dist/src/tools/mcp/doc/fetch.js +33 -0
  255. package/dist/src/tools/mcp/doc/fetch.js.map +7 -0
  256. package/dist/src/tools/mcp/doc/index.js +32 -0
  257. package/dist/src/tools/mcp/doc/index.js.map +7 -0
  258. package/dist/src/tools/mcp/doc/update.js +61 -0
  259. package/dist/src/tools/mcp/doc/update.js.map +7 -0
  260. package/dist/src/tools/mcp/project/endpoint.js +25 -0
  261. package/dist/src/tools/mcp/project/endpoint.js.map +7 -0
  262. package/dist/src/tools/mcp/project/index.js +27 -0
  263. package/dist/src/tools/mcp/project/index.js.map +7 -0
  264. package/dist/src/tools/mcp/project/tools.js +579 -0
  265. package/dist/src/tools/mcp/project/tools.js.map +7 -0
  266. package/dist/src/tools/mcp/shared.js +170 -0
  267. package/dist/src/tools/mcp/shared.js.map +7 -0
  268. package/dist/src/tools/oapi/bitable/app-table-field.js +244 -0
  269. package/dist/src/tools/oapi/bitable/app-table-field.js.map +7 -0
  270. package/dist/src/tools/oapi/bitable/app-table-record.js +501 -0
  271. package/dist/src/tools/oapi/bitable/app-table-record.js.map +7 -0
  272. package/dist/src/tools/oapi/bitable/app-table-view.js +226 -0
  273. package/dist/src/tools/oapi/bitable/app-table-view.js.map +7 -0
  274. package/dist/src/tools/oapi/bitable/app-table.js +278 -0
  275. package/dist/src/tools/oapi/bitable/app-table.js.map +7 -0
  276. package/dist/src/tools/oapi/bitable/app.js +200 -0
  277. package/dist/src/tools/oapi/bitable/app.js.map +7 -0
  278. package/dist/src/tools/oapi/bitable/index.js +13 -0
  279. package/dist/src/tools/oapi/bitable/index.js.map +7 -0
  280. package/dist/src/tools/oapi/calendar/calendar.js +131 -0
  281. package/dist/src/tools/oapi/calendar/calendar.js.map +7 -0
  282. package/dist/src/tools/oapi/calendar/event-attendee.js +301 -0
  283. package/dist/src/tools/oapi/calendar/event-attendee.js.map +7 -0
  284. package/dist/src/tools/oapi/calendar/event.js +834 -0
  285. package/dist/src/tools/oapi/calendar/event.js.map +7 -0
  286. package/dist/src/tools/oapi/calendar/freebusy.js +111 -0
  287. package/dist/src/tools/oapi/calendar/freebusy.js.map +7 -0
  288. package/dist/src/tools/oapi/calendar/index.js +11 -0
  289. package/dist/src/tools/oapi/calendar/index.js.map +7 -0
  290. package/dist/src/tools/oapi/chat/chat.js +132 -0
  291. package/dist/src/tools/oapi/chat/chat.js.map +7 -0
  292. package/dist/src/tools/oapi/chat/index.js +11 -0
  293. package/dist/src/tools/oapi/chat/index.js.map +7 -0
  294. package/dist/src/tools/oapi/chat/members.js +83 -0
  295. package/dist/src/tools/oapi/chat/members.js.map +7 -0
  296. package/dist/src/tools/oapi/common/get-user.js +95 -0
  297. package/dist/src/tools/oapi/common/get-user.js.map +7 -0
  298. package/dist/src/tools/oapi/common/index.js +7 -0
  299. package/dist/src/tools/oapi/common/index.js.map +7 -0
  300. package/dist/src/tools/oapi/common/search-user.js +67 -0
  301. package/dist/src/tools/oapi/common/search-user.js.map +7 -0
  302. package/dist/src/tools/oapi/drive/doc-comments.js +310 -0
  303. package/dist/src/tools/oapi/drive/doc-comments.js.map +7 -0
  304. package/dist/src/tools/oapi/drive/doc-media.js +314 -0
  305. package/dist/src/tools/oapi/drive/doc-media.js.map +7 -0
  306. package/dist/src/tools/oapi/drive/file.js +548 -0
  307. package/dist/src/tools/oapi/drive/file.js.map +7 -0
  308. package/dist/src/tools/oapi/drive/index.js +29 -0
  309. package/dist/src/tools/oapi/drive/index.js.map +7 -0
  310. package/dist/src/tools/oapi/helpers.js +199 -0
  311. package/dist/src/tools/oapi/helpers.js.map +7 -0
  312. package/dist/src/tools/oapi/im/format-messages.js +128 -0
  313. package/dist/src/tools/oapi/im/format-messages.js.map +7 -0
  314. package/dist/src/tools/oapi/im/index.js +15 -0
  315. package/dist/src/tools/oapi/im/index.js.map +7 -0
  316. package/dist/src/tools/oapi/im/message-read.js +404 -0
  317. package/dist/src/tools/oapi/im/message-read.js.map +7 -0
  318. package/dist/src/tools/oapi/im/message.js +179 -0
  319. package/dist/src/tools/oapi/im/message.js.map +7 -0
  320. package/dist/src/tools/oapi/im/resource.js +126 -0
  321. package/dist/src/tools/oapi/im/resource.js.map +7 -0
  322. package/dist/src/tools/oapi/im/time-utils.js +169 -0
  323. package/dist/src/tools/oapi/im/time-utils.js.map +7 -0
  324. package/dist/src/tools/oapi/im/user-name-uat.js +103 -0
  325. package/dist/src/tools/oapi/im/user-name-uat.js.map +7 -0
  326. package/dist/src/tools/oapi/index.js +56 -0
  327. package/dist/src/tools/oapi/index.js.map +7 -0
  328. package/dist/src/tools/oapi/sdk-types.js +1 -0
  329. package/dist/src/tools/oapi/sdk-types.js.map +7 -0
  330. package/dist/src/tools/oapi/search/doc-search.js +215 -0
  331. package/dist/src/tools/oapi/search/doc-search.js.map +7 -0
  332. package/dist/src/tools/oapi/search/index.js +25 -0
  333. package/dist/src/tools/oapi/search/index.js.map +7 -0
  334. package/dist/src/tools/oapi/sheets/index.js +25 -0
  335. package/dist/src/tools/oapi/sheets/index.js.map +7 -0
  336. package/dist/src/tools/oapi/sheets/sheet.js +652 -0
  337. package/dist/src/tools/oapi/sheets/sheet.js.map +7 -0
  338. package/dist/src/tools/oapi/task/comment.js +151 -0
  339. package/dist/src/tools/oapi/task/comment.js.map +7 -0
  340. package/dist/src/tools/oapi/task/index.js +11 -0
  341. package/dist/src/tools/oapi/task/index.js.map +7 -0
  342. package/dist/src/tools/oapi/task/subtask.js +175 -0
  343. package/dist/src/tools/oapi/task/subtask.js.map +7 -0
  344. package/dist/src/tools/oapi/task/task.js +405 -0
  345. package/dist/src/tools/oapi/task/task.js.map +7 -0
  346. package/dist/src/tools/oapi/task/tasklist.js +366 -0
  347. package/dist/src/tools/oapi/task/tasklist.js.map +7 -0
  348. package/dist/src/tools/oapi/wiki/index.js +27 -0
  349. package/dist/src/tools/oapi/wiki/index.js.map +7 -0
  350. package/dist/src/tools/oapi/wiki/space-node.js +311 -0
  351. package/dist/src/tools/oapi/wiki/space-node.js.map +7 -0
  352. package/dist/src/tools/oapi/wiki/space.js +148 -0
  353. package/dist/src/tools/oapi/wiki/space.js.map +7 -0
  354. package/dist/src/tools/oauth-batch-auth.js +125 -0
  355. package/dist/src/tools/oauth-batch-auth.js.map +7 -0
  356. package/dist/src/tools/oauth-cards.js +269 -0
  357. package/dist/src/tools/oauth-cards.js.map +7 -0
  358. package/dist/src/tools/oauth.js +538 -0
  359. package/dist/src/tools/oauth.js.map +7 -0
  360. package/dist/src/tools/onboarding-auth.js +101 -0
  361. package/dist/src/tools/onboarding-auth.js.map +7 -0
  362. package/dist/src/tools/project-oauth.js +305 -0
  363. package/dist/src/tools/project-oauth.js.map +7 -0
  364. package/dist/src/tools/tat/im/index.js +9 -0
  365. package/dist/src/tools/tat/im/index.js.map +7 -0
  366. package/dist/src/tools/tat/im/resource.js +123 -0
  367. package/dist/src/tools/tat/im/resource.js.map +7 -0
  368. package/package.json +64 -0
@@ -0,0 +1,215 @@
1
+ import { execFile as execFileCb } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ import { mkdir, unlink, readFile, writeFile, chmod } from "node:fs/promises";
4
+ import { join } from "node:path";
5
+ import { homedir } from "node:os";
6
+ import { randomBytes, createCipheriv, createDecipheriv } from "node:crypto";
7
+ import { larkLogger } from "./lark-logger";
8
+ const log = larkLogger("core/token-store");
9
+ const execFile = promisify(execFileCb);
10
+ const KEYCHAIN_SERVICE = "openclaw-feishu-uat";
11
+ const REFRESH_AHEAD_MS = 5 * 60 * 1e3;
12
+ function accountKey(appId, userOpenId) {
13
+ return `${appId}:${userOpenId}`;
14
+ }
15
+ function maskToken(token) {
16
+ if (token.length <= 8) return "****";
17
+ return `****${token.slice(-4)}`;
18
+ }
19
+ const darwinBackend = {
20
+ async get(service, account) {
21
+ try {
22
+ const { stdout } = await execFile("security", ["find-generic-password", "-s", service, "-a", account, "-w"]);
23
+ return stdout.trim() || null;
24
+ } catch {
25
+ return null;
26
+ }
27
+ },
28
+ async set(service, account, data) {
29
+ try {
30
+ await execFile("security", ["delete-generic-password", "-s", service, "-a", account]);
31
+ } catch {
32
+ }
33
+ await execFile("security", ["add-generic-password", "-s", service, "-a", account, "-w", data]);
34
+ },
35
+ async remove(service, account) {
36
+ try {
37
+ await execFile("security", ["delete-generic-password", "-s", service, "-a", account]);
38
+ } catch {
39
+ }
40
+ }
41
+ };
42
+ const LINUX_UAT_DIR = join(process.env.XDG_DATA_HOME || join(homedir(), ".local", "share"), "openclaw-feishu-uat");
43
+ const MASTER_KEY_PATH = join(LINUX_UAT_DIR, "master.key");
44
+ const MASTER_KEY_BYTES = 32;
45
+ const IV_BYTES = 12;
46
+ const TAG_BYTES = 16;
47
+ function linuxSafeFileName(account) {
48
+ return account.replace(/[^a-zA-Z0-9._-]/g, "_") + ".enc";
49
+ }
50
+ async function ensureLinuxCredDir() {
51
+ await mkdir(LINUX_UAT_DIR, { recursive: true, mode: 448 });
52
+ }
53
+ async function getMasterKey() {
54
+ try {
55
+ const key2 = await readFile(MASTER_KEY_PATH);
56
+ if (key2.length === MASTER_KEY_BYTES) return key2;
57
+ log.warn("master key has unexpected length, regenerating");
58
+ } catch (err) {
59
+ if (!(err instanceof Error) || err.code !== "ENOENT") {
60
+ log.warn(`failed to read master key: ${err instanceof Error ? err.message : err}`);
61
+ }
62
+ }
63
+ await ensureLinuxCredDir();
64
+ const key = randomBytes(MASTER_KEY_BYTES);
65
+ await writeFile(MASTER_KEY_PATH, key, { mode: 384 });
66
+ await chmod(MASTER_KEY_PATH, 384);
67
+ log.info("generated new master key for encrypted file storage");
68
+ return key;
69
+ }
70
+ function encryptData(plaintext, key) {
71
+ const iv = randomBytes(IV_BYTES);
72
+ const cipher = createCipheriv("aes-256-gcm", key, iv);
73
+ const enc = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
74
+ return Buffer.concat([iv, cipher.getAuthTag(), enc]);
75
+ }
76
+ function decryptData(data, key) {
77
+ if (data.length < IV_BYTES + TAG_BYTES) return null;
78
+ try {
79
+ const iv = data.subarray(0, IV_BYTES);
80
+ const tag = data.subarray(IV_BYTES, IV_BYTES + TAG_BYTES);
81
+ const enc = data.subarray(IV_BYTES + TAG_BYTES);
82
+ const decipher = createDecipheriv("aes-256-gcm", key, iv);
83
+ decipher.setAuthTag(tag);
84
+ return Buffer.concat([decipher.update(enc), decipher.final()]).toString("utf8");
85
+ } catch {
86
+ return null;
87
+ }
88
+ }
89
+ const linuxBackend = {
90
+ async get(_service, account) {
91
+ try {
92
+ const key = await getMasterKey();
93
+ const data = await readFile(join(LINUX_UAT_DIR, linuxSafeFileName(account)));
94
+ return decryptData(data, key);
95
+ } catch {
96
+ return null;
97
+ }
98
+ },
99
+ async set(_service, account, data) {
100
+ const key = await getMasterKey();
101
+ await ensureLinuxCredDir();
102
+ const filePath = join(LINUX_UAT_DIR, linuxSafeFileName(account));
103
+ const encrypted = encryptData(data, key);
104
+ await writeFile(filePath, encrypted, { mode: 384 });
105
+ await chmod(filePath, 384);
106
+ },
107
+ async remove(_service, account) {
108
+ try {
109
+ await unlink(join(LINUX_UAT_DIR, linuxSafeFileName(account)));
110
+ } catch {
111
+ }
112
+ }
113
+ };
114
+ const WIN32_UAT_DIR = join(
115
+ process.env.LOCALAPPDATA ?? join(process.env.USERPROFILE ?? homedir(), "AppData", "Local"),
116
+ KEYCHAIN_SERVICE
117
+ );
118
+ const WIN32_MASTER_KEY_PATH = join(WIN32_UAT_DIR, "master.key");
119
+ function win32SafeFileName(account) {
120
+ return account.replace(/[^a-zA-Z0-9._-]/g, "_") + ".enc";
121
+ }
122
+ async function ensureWin32CredDir() {
123
+ await mkdir(WIN32_UAT_DIR, { recursive: true });
124
+ }
125
+ async function getWin32MasterKey() {
126
+ try {
127
+ const key2 = await readFile(WIN32_MASTER_KEY_PATH);
128
+ if (key2.length === MASTER_KEY_BYTES) return key2;
129
+ log.warn("win32 master key has unexpected length, regenerating");
130
+ } catch (err) {
131
+ if (!(err instanceof Error) || err.code !== "ENOENT") {
132
+ log.warn(`failed to read win32 master key: ${err instanceof Error ? err.message : err}`);
133
+ }
134
+ }
135
+ await ensureWin32CredDir();
136
+ const key = randomBytes(MASTER_KEY_BYTES);
137
+ await writeFile(WIN32_MASTER_KEY_PATH, key);
138
+ log.info("generated new master key for win32 encrypted file storage");
139
+ return key;
140
+ }
141
+ const win32Backend = {
142
+ async get(_service, account) {
143
+ try {
144
+ const key = await getWin32MasterKey();
145
+ const data = await readFile(join(WIN32_UAT_DIR, win32SafeFileName(account)));
146
+ return decryptData(data, key);
147
+ } catch {
148
+ return null;
149
+ }
150
+ },
151
+ async set(_service, account, data) {
152
+ const key = await getWin32MasterKey();
153
+ await ensureWin32CredDir();
154
+ const filePath = join(WIN32_UAT_DIR, win32SafeFileName(account));
155
+ const encrypted = encryptData(data, key);
156
+ await writeFile(filePath, encrypted);
157
+ },
158
+ async remove(_service, account) {
159
+ try {
160
+ await unlink(join(WIN32_UAT_DIR, win32SafeFileName(account)));
161
+ } catch {
162
+ }
163
+ }
164
+ };
165
+ function createBackend() {
166
+ switch (process.platform) {
167
+ case "darwin":
168
+ return darwinBackend;
169
+ case "linux":
170
+ return linuxBackend;
171
+ case "win32":
172
+ return win32Backend;
173
+ default:
174
+ log.warn(`unsupported platform "${process.platform}", falling back to macOS backend`);
175
+ return darwinBackend;
176
+ }
177
+ }
178
+ const backend = createBackend();
179
+ async function getStoredToken(appId, userOpenId) {
180
+ try {
181
+ const json = await backend.get(KEYCHAIN_SERVICE, accountKey(appId, userOpenId));
182
+ if (!json) return null;
183
+ return JSON.parse(json);
184
+ } catch {
185
+ return null;
186
+ }
187
+ }
188
+ async function setStoredToken(token) {
189
+ const key = accountKey(token.appId, token.userOpenId);
190
+ const payload = JSON.stringify(token);
191
+ await backend.set(KEYCHAIN_SERVICE, key, payload);
192
+ log.info(`saved UAT for ${token.userOpenId} (at:${maskToken(token.accessToken)})`);
193
+ }
194
+ async function removeStoredToken(appId, userOpenId) {
195
+ await backend.remove(KEYCHAIN_SERVICE, accountKey(appId, userOpenId));
196
+ log.info(`removed UAT for ${userOpenId}`);
197
+ }
198
+ function tokenStatus(token) {
199
+ const now = Date.now();
200
+ if (now < token.expiresAt - REFRESH_AHEAD_MS) {
201
+ return "valid";
202
+ }
203
+ if (now < token.refreshExpiresAt) {
204
+ return "needs_refresh";
205
+ }
206
+ return "expired";
207
+ }
208
+ export {
209
+ getStoredToken,
210
+ maskToken,
211
+ removeStoredToken,
212
+ setStoredToken,
213
+ tokenStatus
214
+ };
215
+ //# sourceMappingURL=token-store.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/core/token-store.ts"],
4
+ "sourcesContent": ["/**\n * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n *\n * UAT (User Access Token) persistent storage with cross-platform support.\n *\n * Stores OAuth token data using OS-native credential services so that tokens\n * survive process restarts without introducing plain-text local files.\n *\n * Platform backends:\n * macOS \u2013 Keychain Access via `security` CLI\n * Linux \u2013 AES-256-GCM encrypted files (XDG_DATA_HOME)\n * Windows \u2013 AES-256-GCM encrypted files (%LOCALAPPDATA%)\n *\n * Storage layout:\n * Service = \"openclaw-feishu-uat\"\n * Account = \"{appId}:{userOpenId}\"\n * Password = JSON-serialised StoredUAToken\n */\n\nimport { execFile as execFileCb } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { mkdir, unlink, readFile, writeFile, chmod } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { randomBytes, createCipheriv, createDecipheriv } from 'node:crypto';\nimport { larkLogger } from './lark-logger';\n\nconst log = larkLogger('core/token-store');\n\nconst execFile = promisify(execFileCb);\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface StoredUAToken {\n userOpenId: string;\n appId: string;\n accessToken: string;\n refreshToken: string;\n expiresAt: number; // Unix ms \u2013 access_token expiry\n refreshExpiresAt: number; // Unix ms \u2013 refresh_token expiry\n scope: string;\n grantedAt: number; // Unix ms \u2013 original grant time\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst KEYCHAIN_SERVICE = 'openclaw-feishu-uat';\n\n/** Refresh proactively when access_token expires within this window. */\nconst REFRESH_AHEAD_MS = 5 * 60 * 1000; // 5 minutes\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction accountKey(appId: string, userOpenId: string): string {\n return `${appId}:${userOpenId}`;\n}\n\n/** Mask a token for safe logging: only the last 4 chars are visible. */\nexport function maskToken(token: string): string {\n if (token.length <= 8) return '****';\n return `****${token.slice(-4)}`;\n}\n\n// ---------------------------------------------------------------------------\n// Backend interface\n// ---------------------------------------------------------------------------\n\ninterface KeychainBackend {\n get(service: string, account: string): Promise<string | null>;\n set(service: string, account: string, data: string): Promise<void>;\n remove(service: string, account: string): Promise<void>;\n}\n\n// ---------------------------------------------------------------------------\n// macOS backend \u2013 Keychain Access via `security` CLI\n// ---------------------------------------------------------------------------\n\nconst darwinBackend: KeychainBackend = {\n async get(service, account) {\n try {\n const { stdout } = await execFile('security', ['find-generic-password', '-s', service, '-a', account, '-w']);\n return stdout.trim() || null;\n } catch {\n return null;\n }\n },\n\n async set(service, account, data) {\n // Delete first \u2013 `add-generic-password` fails if the item already exists.\n try {\n await execFile('security', ['delete-generic-password', '-s', service, '-a', account]);\n } catch {\n // Not found \u2013 fine.\n }\n await execFile('security', ['add-generic-password', '-s', service, '-a', account, '-w', data]);\n },\n\n async remove(service, account) {\n try {\n await execFile('security', ['delete-generic-password', '-s', service, '-a', account]);\n } catch {\n // Already absent \u2013 fine.\n }\n },\n};\n\n// ---------------------------------------------------------------------------\n// Linux backend \u2013 AES-256-GCM encrypted files (XDG Base Directory)\n//\n// Headless Linux servers typically lack D-Bus / GNOME Keyring, so we store\n// tokens as AES-256-GCM encrypted files instead of using `secret-tool`.\n//\n// Storage path: ${XDG_DATA_HOME:-~/.local/share}/openclaw-feishu-uat/\n// ---------------------------------------------------------------------------\n\nconst LINUX_UAT_DIR = join(process.env.XDG_DATA_HOME || join(homedir(), '.local', 'share'), 'openclaw-feishu-uat');\nconst MASTER_KEY_PATH = join(LINUX_UAT_DIR, 'master.key');\nconst MASTER_KEY_BYTES = 32; // AES-256\nconst IV_BYTES = 12; // GCM recommended\nconst TAG_BYTES = 16; // GCM auth tag\n\n/** Convert account key to a filesystem-safe filename. */\nfunction linuxSafeFileName(account: string): string {\n return account.replace(/[^a-zA-Z0-9._-]/g, '_') + '.enc';\n}\n\n/** Ensure the credentials directory exists with mode 0700. */\nasync function ensureLinuxCredDir(): Promise<void> {\n await mkdir(LINUX_UAT_DIR, { recursive: true, mode: 0o700 });\n}\n\n/**\n * Load or create the 32-byte master key.\n *\n * On first run, generates a random key and writes it to disk (mode 0600).\n * On subsequent runs, reads the existing key file.\n */\nasync function getMasterKey(): Promise<Buffer> {\n try {\n const key = await readFile(MASTER_KEY_PATH);\n if (key.length === MASTER_KEY_BYTES) return key;\n log.warn('master key has unexpected length, regenerating');\n } catch (err: unknown) {\n if (!(err instanceof Error) || (err as NodeJS.ErrnoException).code !== 'ENOENT') {\n log.warn(`failed to read master key: ${err instanceof Error ? err.message : err}`);\n }\n }\n\n await ensureLinuxCredDir();\n const key = randomBytes(MASTER_KEY_BYTES);\n await writeFile(MASTER_KEY_PATH, key, { mode: 0o600 });\n await chmod(MASTER_KEY_PATH, 0o600);\n log.info('generated new master key for encrypted file storage');\n return key;\n}\n\n/** AES-256-GCM encrypt. Returns [12-byte IV][16-byte tag][ciphertext]. */\nfunction encryptData(plaintext: string, key: Buffer): Buffer {\n const iv = randomBytes(IV_BYTES);\n const cipher = createCipheriv('aes-256-gcm', key, iv);\n const enc = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);\n return Buffer.concat([iv, cipher.getAuthTag(), enc]);\n}\n\n/** AES-256-GCM decrypt. Returns plaintext or `null` on failure. */\nfunction decryptData(data: Buffer, key: Buffer): string | null {\n if (data.length < IV_BYTES + TAG_BYTES) return null;\n try {\n const iv = data.subarray(0, IV_BYTES);\n const tag = data.subarray(IV_BYTES, IV_BYTES + TAG_BYTES);\n const enc = data.subarray(IV_BYTES + TAG_BYTES);\n const decipher = createDecipheriv('aes-256-gcm', key, iv);\n decipher.setAuthTag(tag);\n return Buffer.concat([decipher.update(enc), decipher.final()]).toString('utf8');\n } catch {\n return null;\n }\n}\n\nconst linuxBackend: KeychainBackend = {\n async get(_service, account) {\n try {\n const key = await getMasterKey();\n const data = await readFile(join(LINUX_UAT_DIR, linuxSafeFileName(account)));\n return decryptData(data, key);\n } catch {\n return null;\n }\n },\n\n async set(_service, account, data) {\n const key = await getMasterKey();\n await ensureLinuxCredDir();\n const filePath = join(LINUX_UAT_DIR, linuxSafeFileName(account));\n const encrypted = encryptData(data, key);\n await writeFile(filePath, encrypted, { mode: 0o600 });\n await chmod(filePath, 0o600);\n },\n\n async remove(_service, account) {\n try {\n await unlink(join(LINUX_UAT_DIR, linuxSafeFileName(account)));\n } catch {\n // Already absent \u2013 fine.\n }\n },\n};\n\n// ---------------------------------------------------------------------------\n// Windows backend \u2013 AES-256-GCM encrypted files\n//\n// Replaces the previous DPAPI-via-PowerShell approach which was unreliable\n// (PowerShell cold-start latency, execution policy restrictions, cmd.exe\n// command-line length limits, and unavailability in containers).\n//\n// Uses the same AES-256-GCM scheme as the Linux backend with its own\n// independent storage directory and master key.\n//\n// Storage path: %LOCALAPPDATA%\\openclaw-feishu-uat\\\n// ---------------------------------------------------------------------------\n\nconst WIN32_UAT_DIR = join(\n process.env.LOCALAPPDATA ?? join(process.env.USERPROFILE ?? homedir(), 'AppData', 'Local'),\n KEYCHAIN_SERVICE,\n);\nconst WIN32_MASTER_KEY_PATH = join(WIN32_UAT_DIR, 'master.key');\n\n/** Convert account key to a filesystem-safe filename (whitelist approach). */\nfunction win32SafeFileName(account: string): string {\n return account.replace(/[^a-zA-Z0-9._-]/g, '_') + '.enc';\n}\n\nasync function ensureWin32CredDir(): Promise<void> {\n await mkdir(WIN32_UAT_DIR, { recursive: true });\n}\n\nasync function getWin32MasterKey(): Promise<Buffer> {\n try {\n const key = await readFile(WIN32_MASTER_KEY_PATH);\n if (key.length === MASTER_KEY_BYTES) return key;\n log.warn('win32 master key has unexpected length, regenerating');\n } catch (err: unknown) {\n if (!(err instanceof Error) || (err as NodeJS.ErrnoException).code !== 'ENOENT') {\n log.warn(`failed to read win32 master key: ${err instanceof Error ? err.message : err}`);\n }\n }\n\n await ensureWin32CredDir();\n const key = randomBytes(MASTER_KEY_BYTES);\n await writeFile(WIN32_MASTER_KEY_PATH, key);\n log.info('generated new master key for win32 encrypted file storage');\n return key;\n}\n\nconst win32Backend: KeychainBackend = {\n async get(_service, account) {\n try {\n const key = await getWin32MasterKey();\n const data = await readFile(join(WIN32_UAT_DIR, win32SafeFileName(account)));\n return decryptData(data, key);\n } catch {\n return null;\n }\n },\n\n async set(_service, account, data) {\n const key = await getWin32MasterKey();\n await ensureWin32CredDir();\n const filePath = join(WIN32_UAT_DIR, win32SafeFileName(account));\n const encrypted = encryptData(data, key);\n await writeFile(filePath, encrypted);\n },\n\n async remove(_service, account) {\n try {\n await unlink(join(WIN32_UAT_DIR, win32SafeFileName(account)));\n } catch {\n // Already absent \u2013 fine.\n }\n },\n};\n\n// ---------------------------------------------------------------------------\n// Platform selection\n// ---------------------------------------------------------------------------\n\nfunction createBackend(): KeychainBackend {\n switch (process.platform) {\n case 'darwin':\n return darwinBackend;\n case 'linux':\n return linuxBackend;\n case 'win32':\n return win32Backend;\n default:\n log.warn(`unsupported platform \"${process.platform}\", falling back to macOS backend`);\n return darwinBackend;\n }\n}\n\nconst backend = createBackend();\n\n// ---------------------------------------------------------------------------\n// Public API \u2013 Credential operations\n// ---------------------------------------------------------------------------\n\n/**\n * Read the stored UAT for a given (appId, userOpenId) pair.\n * Returns `null` when no entry exists or the payload is unparseable.\n */\nexport async function getStoredToken(appId: string, userOpenId: string): Promise<StoredUAToken | null> {\n try {\n const json = await backend.get(KEYCHAIN_SERVICE, accountKey(appId, userOpenId));\n if (!json) return null;\n return JSON.parse(json) as StoredUAToken;\n } catch {\n return null;\n }\n}\n\n/**\n * Persist a UAT using the platform credential store.\n *\n * Overwrites any existing entry for the same (appId, userOpenId).\n */\nexport async function setStoredToken(token: StoredUAToken): Promise<void> {\n const key = accountKey(token.appId, token.userOpenId);\n const payload = JSON.stringify(token);\n await backend.set(KEYCHAIN_SERVICE, key, payload);\n log.info(`saved UAT for ${token.userOpenId} (at:${maskToken(token.accessToken)})`);\n}\n\n/**\n * Remove a stored UAT from the credential store.\n */\nexport async function removeStoredToken(appId: string, userOpenId: string): Promise<void> {\n await backend.remove(KEYCHAIN_SERVICE, accountKey(appId, userOpenId));\n log.info(`removed UAT for ${userOpenId}`);\n}\n\n// ---------------------------------------------------------------------------\n// Token validity check\n// ---------------------------------------------------------------------------\n\n/**\n * Determine the freshness of a stored token.\n *\n * - `\"valid\"` \u2013 access_token is still good (expires > 5 min from now)\n * - `\"needs_refresh\"` \u2013 access_token expired/expiring but refresh_token is valid\n * - `\"expired\"` \u2013 both tokens are expired; re-authorization required\n */\nexport function tokenStatus(token: StoredUAToken): 'valid' | 'needs_refresh' | 'expired' {\n const now = Date.now();\n if (now < token.expiresAt - REFRESH_AHEAD_MS) {\n return 'valid';\n }\n if (now < token.refreshExpiresAt) {\n return 'needs_refresh';\n }\n return 'expired';\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["key"]
7
+ }
@@ -0,0 +1,335 @@
1
+ import * as Lark from "@larksuiteoapi/node-sdk";
2
+ import { getLarkAccount, getEnabledLarkAccounts } from "./accounts";
3
+ import { LarkClient } from "./lark-client";
4
+ import { getTicket } from "./lark-ticket";
5
+ import { callWithUAT } from "./uat-client";
6
+ import { getStoredToken } from "./token-store";
7
+ import { getAppGrantedScopes, invalidateAppScopeCache, missingScopes } from "./app-scope-checker";
8
+ import { getAppOwnerFallback } from "./app-owner-fallback";
9
+ import { larkLogger } from "./lark-logger";
10
+ import { getRequiredScopes } from "./scope-manager";
11
+ import { rawLarkRequest } from "./raw-request";
12
+ import { assertOwnerAccessStrict } from "./owner-policy";
13
+ import {
14
+ LARK_ERROR,
15
+ NeedAuthorizationError,
16
+ AppScopeCheckFailedError,
17
+ AppScopeMissingError,
18
+ UserAuthRequiredError,
19
+ UserScopeInsufficientError
20
+ } from "./auth-errors";
21
+ const tcLog = larkLogger("core/tool-client");
22
+ class ToolClient {
23
+ config;
24
+ /** 当前解析的账号信息(appId、appSecret 保证存在)。 */
25
+ account;
26
+ /** 当前请求的用户 open_id(来自 LarkTicket,可能为 undefined)。 */
27
+ senderOpenId;
28
+ /** Lark SDK 实例(TAT 身份),直接调用即可。 */
29
+ sdk;
30
+ constructor(params) {
31
+ this.account = params.account;
32
+ this.senderOpenId = params.senderOpenId;
33
+ this.sdk = params.sdk;
34
+ this.config = params.config;
35
+ }
36
+ // -------------------------------------------------------------------------
37
+ // invoke() — 统一 API 调用入口
38
+ // -------------------------------------------------------------------------
39
+ /**
40
+ * 统一 API 调用入口。
41
+ *
42
+ * 自动处理:
43
+ * - 根据 API meta 选择 UAT / TAT
44
+ * - 严格模式:检查应用和用户是否拥有所有 API 要求的 scope
45
+ * - 无 token 或 scope 不足时抛出结构化错误
46
+ * - UAT 模式下复用 callWithUAT 的 refresh + retry
47
+ *
48
+ * @param apiName - meta.json 中的 toolName,如 `"calendar.v4.calendarEvent.create"`
49
+ * @param fn - API 调用逻辑。UAT 时 opts 已注入 token,TAT 时 opts 为 undefined。
50
+ * @param options - 可选配置:
51
+ * - `as`: 指定 UAT/TAT
52
+ * - `userOpenId`: 覆盖用户 ID
53
+ *
54
+ * @throws {@link AppScopeMissingError} 应用未开通 API 所需 scope
55
+ * @throws {@link UserAuthRequiredError} 用户未授权或 scope 不足
56
+ * @throws {@link UserScopeInsufficientError} 服务端报用户 scope 不足
57
+ *
58
+ * @example
59
+ * // UAT 调用 — 通过 { as: "user" } 指定
60
+ * const res = await client.invoke(
61
+ * "calendar.v4.calendarEvent.create",
62
+ * (sdk, opts) => sdk.calendar.calendarEvent.create(payload, opts),
63
+ * { as: "user" },
64
+ * );
65
+ *
66
+ * @example
67
+ * // TAT 调用
68
+ * const res = await client.invoke(
69
+ * "calendar.v4.calendar.list",
70
+ * (sdk) => sdk.calendar.calendar.list(payload),
71
+ * { as: "tenant" },
72
+ * );
73
+ *
74
+ */
75
+ async invoke(toolAction, fn, options) {
76
+ return this._invokeInternal(toolAction, fn, options);
77
+ }
78
+ /**
79
+ * 内部 invoke 实现,只支持 ToolActionKey(严格类型检查)
80
+ */
81
+ async _invokeInternal(toolAction, fn, options) {
82
+ const feishuEntry = this.config.plugins?.entries?.feishu;
83
+ if (feishuEntry && feishuEntry.enabled !== false) {
84
+ throw new Error(
85
+ "\u274C \u68C0\u6D4B\u5230\u65E7\u7248\u63D2\u4EF6\u672A\u7981\u7528\u3002\n\u{1F449} \u8BF7\u4F9D\u6B21\u8FD0\u884C\u547D\u4EE4\uFF1A\n```\nopenclaw config set plugins.entries.feishu.enabled false --json\nopenclaw gateway restart\n```"
86
+ );
87
+ }
88
+ const requiredScopes = getRequiredScopes(toolAction);
89
+ const tokenType = options?.as ?? "user";
90
+ const appCheckScopes = tokenType === "user" ? [.../* @__PURE__ */ new Set([...requiredScopes, "offline_access"])] : requiredScopes;
91
+ let appScopeVerified = true;
92
+ if (appCheckScopes.length > 0) {
93
+ const appGrantedScopes = await getAppGrantedScopes(this.sdk, this.account.appId, tokenType);
94
+ if (appGrantedScopes.length > 0) {
95
+ const missingAppScopes = missingScopes(appGrantedScopes, appCheckScopes);
96
+ if (missingAppScopes.length > 0) {
97
+ throw new AppScopeMissingError(
98
+ { apiName: toolAction, scopes: missingAppScopes, appId: this.account.appId },
99
+ "all",
100
+ tokenType,
101
+ requiredScopes
102
+ );
103
+ }
104
+ } else {
105
+ appScopeVerified = false;
106
+ }
107
+ }
108
+ if (tokenType === "tenant") {
109
+ return this.invokeAsTenant(toolAction, fn, requiredScopes);
110
+ }
111
+ let userOpenId = options?.userOpenId ?? this.senderOpenId;
112
+ if (!userOpenId) {
113
+ const fallbackUserId = await getAppOwnerFallback(this.account, this.sdk);
114
+ if (fallbackUserId) {
115
+ userOpenId = fallbackUserId;
116
+ tcLog.info(`Using app owner as fallback user`, {
117
+ toolAction,
118
+ appId: this.account.appId,
119
+ ownerId: fallbackUserId
120
+ });
121
+ }
122
+ }
123
+ return this.invokeAsUser(toolAction, fn, requiredScopes, userOpenId, appScopeVerified);
124
+ }
125
+ /**
126
+ * invoke() 的非抛出包装,适用于"允许失败"的子操作。
127
+ *
128
+ * - 成功 → `{ ok: true, data }`
129
+ * - 用户授权错误(可通过 OAuth 恢复)→ `{ ok: false, authHint }`
130
+ * - 应用权限缺失 / appScopeVerified=false → **仍然 throw**(需管理员操作)
131
+ * - 其他错误 → `{ ok: false, error }`
132
+ */
133
+ // -------------------------------------------------------------------------
134
+ // invokeByPath() — SDK 未覆盖的 API 调用入口
135
+ // -------------------------------------------------------------------------
136
+ /**
137
+ * 对 SDK 未覆盖的飞书 API 发起 raw HTTP 请求,同时复用 invoke() 的
138
+ * auth/scope/refresh 全链路。
139
+ *
140
+ * @param apiName - 逻辑 API 名称(用于日志和错误信息),如 `"im.v1.chatP2p.batchQuery"`
141
+ * @param path - API 路径(以 `/open-apis/` 开头),如 `"/open-apis/im/v1/chat_p2p/batch_query"`
142
+ * @param options - HTTP 方法、body、query 及 InvokeOptions(as、userOpenId 等)
143
+ *
144
+ * @example
145
+ * ```typescript
146
+ * const res = await client.invokeByPath<{ data: { items: Array<{ chat_id: string }> } }>(
147
+ * "im.v1.chatP2p.batchQuery",
148
+ * "/open-apis/im/v1/chat_p2p/batch_query",
149
+ * {
150
+ * method: "POST",
151
+ * body: { chatter_ids: [openId] },
152
+ * as: "user",
153
+ * },
154
+ * );
155
+ * ```
156
+ */
157
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
158
+ async invokeByPath(toolAction, path, options) {
159
+ const fn = async (_sdk, _opts, uat) => {
160
+ return this.rawRequest(path, {
161
+ method: options?.method,
162
+ body: options?.body,
163
+ query: options?.query,
164
+ headers: options?.headers,
165
+ accessToken: uat
166
+ });
167
+ };
168
+ return this._invokeInternal(toolAction, fn, options);
169
+ }
170
+ // -------------------------------------------------------------------------
171
+ // Private: TAT path
172
+ // -------------------------------------------------------------------------
173
+ async invokeAsTenant(toolAction, fn, requiredScopes) {
174
+ try {
175
+ return await fn(this.sdk);
176
+ } catch (err) {
177
+ this.rethrowStructuredError(err, toolAction, requiredScopes, void 0, "tenant");
178
+ throw err;
179
+ }
180
+ }
181
+ // -------------------------------------------------------------------------
182
+ // Private: UAT path
183
+ // -------------------------------------------------------------------------
184
+ async invokeAsUser(toolAction, fn, requiredScopes, userOpenId, appScopeVerified) {
185
+ if (!userOpenId) {
186
+ throw new UserAuthRequiredError("unknown", {
187
+ apiName: toolAction,
188
+ scopes: requiredScopes,
189
+ appScopeVerified,
190
+ appId: this.account.appId
191
+ });
192
+ }
193
+ await assertOwnerAccessStrict(this.account, this.sdk, userOpenId);
194
+ const stored = await getStoredToken(this.account.appId, userOpenId);
195
+ if (!stored) {
196
+ throw new UserAuthRequiredError(userOpenId, {
197
+ apiName: toolAction,
198
+ scopes: requiredScopes,
199
+ appScopeVerified,
200
+ appId: this.account.appId
201
+ });
202
+ }
203
+ if (appScopeVerified && stored.scope && requiredScopes.length > 0) {
204
+ const userGrantedScopes = new Set(stored.scope.split(/\s+/).filter(Boolean));
205
+ const missingUserScopes = requiredScopes.filter((s) => !userGrantedScopes.has(s));
206
+ if (missingUserScopes.length > 0) {
207
+ throw new UserAuthRequiredError(userOpenId, {
208
+ apiName: toolAction,
209
+ scopes: missingUserScopes,
210
+ appScopeVerified,
211
+ appId: this.account.appId
212
+ });
213
+ }
214
+ }
215
+ try {
216
+ return await callWithUAT(
217
+ {
218
+ userOpenId,
219
+ appId: this.account.appId,
220
+ appSecret: this.account.appSecret,
221
+ domain: this.account.brand
222
+ },
223
+ (accessToken) => fn(this.sdk, Lark.withUserAccessToken(accessToken), accessToken)
224
+ );
225
+ } catch (err) {
226
+ if (err instanceof NeedAuthorizationError) {
227
+ throw new UserAuthRequiredError(userOpenId, {
228
+ apiName: toolAction,
229
+ scopes: requiredScopes,
230
+ appScopeVerified
231
+ });
232
+ }
233
+ this.rethrowStructuredError(err, toolAction, requiredScopes, userOpenId, "user");
234
+ throw err;
235
+ }
236
+ }
237
+ // -------------------------------------------------------------------------
238
+ // Private: raw HTTP request
239
+ // -------------------------------------------------------------------------
240
+ /**
241
+ * 发起 raw HTTP 请求到飞书 API,委托 rawLarkRequest 处理。
242
+ */
243
+ async rawRequest(path, options) {
244
+ return rawLarkRequest({
245
+ brand: this.account.brand,
246
+ path,
247
+ ...options
248
+ });
249
+ }
250
+ // -------------------------------------------------------------------------
251
+ // Private: structured error detection
252
+ // -------------------------------------------------------------------------
253
+ /**
254
+ * 识别飞书服务端错误码并转换为结构化错误。
255
+ *
256
+ * - LARK_ERROR.APP_SCOPE_MISSING (99991672) → AppScopeMissingError(清缓存后抛出)
257
+ * - LARK_ERROR.USER_SCOPE_INSUFFICIENT (99991679) → UserScopeInsufficientError
258
+ */
259
+ rethrowStructuredError(err, apiName, effectiveScopes, userOpenId, tokenType) {
260
+ const code = (
261
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
262
+ err?.code ?? err?.response?.data?.code
263
+ );
264
+ if (code === LARK_ERROR.APP_SCOPE_MISSING) {
265
+ invalidateAppScopeCache(this.account.appId);
266
+ throw new AppScopeMissingError(
267
+ {
268
+ apiName,
269
+ scopes: effectiveScopes,
270
+ appId: this.account.appId
271
+ },
272
+ "all",
273
+ tokenType
274
+ );
275
+ }
276
+ if (code === LARK_ERROR.USER_SCOPE_INSUFFICIENT && userOpenId) {
277
+ throw new UserScopeInsufficientError(userOpenId, {
278
+ apiName,
279
+ scopes: effectiveScopes
280
+ });
281
+ }
282
+ }
283
+ }
284
+ function createToolClient(config, accountIndex = 0) {
285
+ const ticket = getTicket();
286
+ let account;
287
+ if (ticket?.accountId) {
288
+ const resolved = getLarkAccount(config, ticket.accountId);
289
+ if (!resolved.configured) {
290
+ throw new Error(
291
+ `Feishu account "${ticket.accountId}" is not configured (missing appId or appSecret). Please check channels.feishu.accounts.${ticket.accountId} in your config.`
292
+ );
293
+ }
294
+ if (!resolved.enabled) {
295
+ throw new Error(
296
+ `Feishu account "${ticket.accountId}" is disabled. Set channels.feishu.accounts.${ticket.accountId}.enabled to true, or remove it to use defaults.`
297
+ );
298
+ }
299
+ account = resolved;
300
+ }
301
+ if (!account) {
302
+ const accounts = getEnabledLarkAccounts(config);
303
+ if (accounts.length === 0) {
304
+ throw new Error(
305
+ "No enabled Feishu accounts configured. Please add appId and appSecret in config under channels.feishu"
306
+ );
307
+ }
308
+ if (accountIndex >= accounts.length) {
309
+ throw new Error(`Requested account index ${accountIndex} but only ${accounts.length} accounts available`);
310
+ }
311
+ const fallback = accounts[accountIndex];
312
+ if (!fallback.configured) {
313
+ throw new Error(`Account at index ${accountIndex} is not fully configured (missing appId or appSecret)`);
314
+ }
315
+ account = fallback;
316
+ }
317
+ const larkClient = LarkClient.fromAccount(account);
318
+ return new ToolClient({
319
+ account,
320
+ senderOpenId: ticket?.senderOpenId,
321
+ sdk: larkClient.sdk,
322
+ config
323
+ });
324
+ }
325
+ export {
326
+ AppScopeCheckFailedError,
327
+ AppScopeMissingError,
328
+ LARK_ERROR,
329
+ NeedAuthorizationError,
330
+ ToolClient,
331
+ UserAuthRequiredError,
332
+ UserScopeInsufficientError,
333
+ createToolClient
334
+ };
335
+ //# sourceMappingURL=tool-client.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/core/tool-client.ts"],
4
+ "sourcesContent": ["/**\n * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n *\n * ToolClient \u2014 \u5DE5\u5177\u5C42\u7EDF\u4E00\u5BA2\u6237\u7AEF\u3002\n *\n * \u4E13\u4E3A `src/tools/` \u4E0B\u7684\u5DE5\u5177\u8BBE\u8BA1\uFF0C\u5C01\u88C5 account \u89E3\u6790\u3001SDK \u7BA1\u7406\u3001\n * TAT/UAT \u81EA\u52A8\u5207\u6362\u548C scope \u9884\u68C0\u3002\u5DE5\u5177\u4EE3\u7801\u53EA\u9700\u58F0\u660E API \u540D\u79F0\u548C\u8C03\u7528\u903B\u8F91\uFF0C\n * \u8EAB\u4EFD\u9009\u62E9/scope \u6821\u9A8C/token \u7BA1\u7406\u5168\u90E8\u7531 `invoke()` \u5185\u805A\u5904\u7406\u3002\n *\n * \u7528\u6CD5\uFF1A\n * ```typescript\n * const client = createToolClient(config);\n *\n * // UAT \u8C03\u7528 \u2014 \u901A\u8FC7 { as: \"user\" } \u6307\u5B9A\u7528\u6237\u8EAB\u4EFD\n * const res = await client.invoke(\n * \"calendar.v4.calendarEvent.create\",\n * (sdk, opts) => sdk.calendar.calendarEvent.create(payload, opts),\n * { as: \"user\" },\n * );\n *\n * // TAT \u8C03\u7528 \u2014 \u9ED8\u8BA4\u8D70\u5E94\u7528\u8EAB\u4EFD\n * const res = await client.invoke(\n * \"calendar.v4.calendar.list\",\n * (sdk) => sdk.calendar.calendar.list(payload),\n * { as: \"tenant\" },\n * );\n * ```\n */\n\nimport * as Lark from '@larksuiteoapi/node-sdk';\nimport type { ClawdbotConfig } from 'openclaw/plugin-sdk';\nimport type { ConfiguredLarkAccount } from './types';\nimport { getLarkAccount, getEnabledLarkAccounts } from './accounts';\nimport { LarkClient } from './lark-client';\nimport { getTicket } from './lark-ticket';\nimport { callWithUAT } from './uat-client';\nimport { getStoredToken } from './token-store';\nimport { getAppGrantedScopes, invalidateAppScopeCache, missingScopes } from './app-scope-checker';\nimport { getAppOwnerFallback } from './app-owner-fallback';\nimport { larkLogger } from './lark-logger';\nimport { type ToolActionKey, getRequiredScopes } from './scope-manager';\nimport { rawLarkRequest } from './raw-request';\nimport { assertOwnerAccessStrict } from './owner-policy';\nimport {\n LARK_ERROR,\n NeedAuthorizationError,\n AppScopeCheckFailedError,\n AppScopeMissingError,\n UserAuthRequiredError,\n UserScopeInsufficientError,\n} from './auth-errors';\nimport type { ScopeErrorInfo, AuthHint, TryInvokeResult } from './auth-errors';\n\n// Re-export for backward compatibility \u2014 \u4E0B\u6E38\u6A21\u5757\u53EF\u7EE7\u7EED\u4ECE tool-client \u5BFC\u5165\nexport {\n LARK_ERROR,\n NeedAuthorizationError,\n AppScopeCheckFailedError,\n AppScopeMissingError,\n UserAuthRequiredError,\n UserScopeInsufficientError,\n};\nexport type { ScopeErrorInfo, AuthHint, TryInvokeResult };\n\nconst tcLog = larkLogger('core/tool-client');\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Per-request options returned by `Lark.withUserAccessToken()`. */\ntype LarkRequestOptions = ReturnType<typeof Lark.withUserAccessToken>;\n\n/**\n * @deprecated \u4F7F\u7528 `InvokeFn` \u4EE3\u66FF\u3002\n * Callback that receives the SDK client and per-request UAT options.\n */\nexport type ApiFn<T> = (sdk: Lark.Client, opts: LarkRequestOptions) => Promise<T>;\n\n/**\n * invoke() \u7684\u56DE\u8C03\u7B7E\u540D\u3002\n *\n * - UAT \u6A21\u5F0F\uFF1A`opts` \u4E3A `Lark.withUserAccessToken(token)`\uFF0C\u9700\u4F20\u7ED9 SDK \u65B9\u6CD5\uFF1B`uat` \u4E3A User Access Token \u539F\u59CB\u5B57\u7B26\u4E32\n * - TAT \u6A21\u5F0F\uFF1A`opts` \u4E3A `undefined`\uFF0CSDK \u9ED8\u8BA4\u8D70\u5E94\u7528\u8EAB\u4EFD\uFF1B`uat` \u4E5F\u4E3A `undefined`\n */\nexport type InvokeFn<T> = (sdk: Lark.Client, opts?: LarkRequestOptions, uat?: string) => Promise<T>;\n\n/** invoke() \u7684\u9009\u9879\u3002 */\nexport interface InvokeOptions {\n /** \u5F3A\u5236 token \u7C7B\u578B\u3002\u7701\u7565\u65F6\u6839\u636E API meta \u81EA\u52A8\u9009\u62E9\uFF08\u4F18\u5148 user\uFF09\u3002 */\n as?: 'user' | 'tenant';\n /** \u8986\u76D6 senderOpenId\u3002 */\n userOpenId?: string;\n /** \u76F4\u63A5\u6307\u5B9A\u6240\u9700 scopes\uFF0C\u8DF3\u8FC7\u4ECE meta.json \u8BFB\u53D6\u3002\u5BBD\u677E\u6A21\u5F0F\uFF1A\u53EA\u8981\u5E94\u7528\u62E5\u6709\u90E8\u5206 scope\uFF08\u4EA4\u96C6\u975E\u7A7A\uFF09\u5373\u53EF\u8C03\u7528\u3002 */\n /** \u4E25\u683C\u6A21\u5F0F\uFF1A\u6307\u5B9A\u6240\u9700 scopes\uFF0C\u5E94\u7528\u5FC5\u987B\u62E5\u6709\u6240\u6709 scope\uFF0C\u7F3A\u4E00\u4E2A\u90FD\u4F1A\u62A5 AppScopeMissingError\u3002 */\n}\n\n/** invokeByPath() \u7684\u9009\u9879 \u2014 \u5728 InvokeOptions \u57FA\u7840\u4E0A\u589E\u52A0 HTTP \u8BF7\u6C42\u53C2\u6570\u3002 */\nexport type InvokeByPathOptions = InvokeOptions & {\n method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\n body?: unknown;\n query?: Record<string, string>;\n /** \u81EA\u5B9A\u4E49\u8BF7\u6C42 header\uFF0C\u4F1A\u4E0E Authorization / Content-Type \u5408\u5E76\uFF08\u81EA\u5B9A\u4E49\u4F18\u5148\uFF09\u3002 */\n headers?: Record<string, string>;\n};\n\n// ---------------------------------------------------------------------------\n// ToolClient\n// ---------------------------------------------------------------------------\n\nexport class ToolClient {\n readonly config: ClawdbotConfig;\n /** \u5F53\u524D\u89E3\u6790\u7684\u8D26\u53F7\u4FE1\u606F\uFF08appId\u3001appSecret \u4FDD\u8BC1\u5B58\u5728\uFF09\u3002 */\n readonly account: ConfiguredLarkAccount;\n\n /** \u5F53\u524D\u8BF7\u6C42\u7684\u7528\u6237 open_id\uFF08\u6765\u81EA LarkTicket\uFF0C\u53EF\u80FD\u4E3A undefined\uFF09\u3002 */\n readonly senderOpenId: string | undefined;\n\n /** Lark SDK \u5B9E\u4F8B\uFF08TAT \u8EAB\u4EFD\uFF09\uFF0C\u76F4\u63A5\u8C03\u7528\u5373\u53EF\u3002 */\n readonly sdk: Lark.Client;\n\n constructor(params: {\n account: ConfiguredLarkAccount;\n senderOpenId: string | undefined;\n sdk: Lark.Client;\n config: ClawdbotConfig;\n }) {\n this.account = params.account;\n this.senderOpenId = params.senderOpenId;\n this.sdk = params.sdk;\n this.config = params.config;\n }\n\n // -------------------------------------------------------------------------\n // invoke() \u2014 \u7EDF\u4E00 API \u8C03\u7528\u5165\u53E3\n // -------------------------------------------------------------------------\n\n /**\n * \u7EDF\u4E00 API \u8C03\u7528\u5165\u53E3\u3002\n *\n * \u81EA\u52A8\u5904\u7406\uFF1A\n * - \u6839\u636E API meta \u9009\u62E9 UAT / TAT\n * - \u4E25\u683C\u6A21\u5F0F\uFF1A\u68C0\u67E5\u5E94\u7528\u548C\u7528\u6237\u662F\u5426\u62E5\u6709\u6240\u6709 API \u8981\u6C42\u7684 scope\n * - \u65E0 token \u6216 scope \u4E0D\u8DB3\u65F6\u629B\u51FA\u7ED3\u6784\u5316\u9519\u8BEF\n * - UAT \u6A21\u5F0F\u4E0B\u590D\u7528 callWithUAT \u7684 refresh + retry\n *\n * @param apiName - meta.json \u4E2D\u7684 toolName\uFF0C\u5982 `\"calendar.v4.calendarEvent.create\"`\n * @param fn - API \u8C03\u7528\u903B\u8F91\u3002UAT \u65F6 opts \u5DF2\u6CE8\u5165 token\uFF0CTAT \u65F6 opts \u4E3A undefined\u3002\n * @param options - \u53EF\u9009\u914D\u7F6E\uFF1A\n * - `as`: \u6307\u5B9A UAT/TAT\n * - `userOpenId`: \u8986\u76D6\u7528\u6237 ID\n *\n * @throws {@link AppScopeMissingError} \u5E94\u7528\u672A\u5F00\u901A API \u6240\u9700 scope\n * @throws {@link UserAuthRequiredError} \u7528\u6237\u672A\u6388\u6743\u6216 scope \u4E0D\u8DB3\n * @throws {@link UserScopeInsufficientError} \u670D\u52A1\u7AEF\u62A5\u7528\u6237 scope \u4E0D\u8DB3\n *\n * @example\n * // UAT \u8C03\u7528 \u2014 \u901A\u8FC7 { as: \"user\" } \u6307\u5B9A\n * const res = await client.invoke(\n * \"calendar.v4.calendarEvent.create\",\n * (sdk, opts) => sdk.calendar.calendarEvent.create(payload, opts),\n * { as: \"user\" },\n * );\n *\n * @example\n * // TAT \u8C03\u7528\n * const res = await client.invoke(\n * \"calendar.v4.calendar.list\",\n * (sdk) => sdk.calendar.calendar.list(payload),\n * { as: \"tenant\" },\n * );\n *\n */\n async invoke<T>(toolAction: ToolActionKey, fn: InvokeFn<T>, options?: InvokeOptions): Promise<T> {\n return this._invokeInternal(toolAction, fn, options);\n }\n\n /**\n * \u5185\u90E8 invoke \u5B9E\u73B0\uFF0C\u53EA\u652F\u6301 ToolActionKey\uFF08\u4E25\u683C\u7C7B\u578B\u68C0\u67E5\uFF09\n */\n private async _invokeInternal<T>(toolAction: ToolActionKey, fn: InvokeFn<T>, options?: InvokeOptions): Promise<T> {\n // \u68C0\u67E5\u65E7\u7248\u63D2\u4EF6\u662F\u5426\u5DF2\u7981\u7528 (error)\n const feishuEntry = this.config.plugins?.entries?.feishu;\n if (feishuEntry && feishuEntry.enabled !== false) {\n throw new Error(\n '\u274C \u68C0\u6D4B\u5230\u65E7\u7248\u63D2\u4EF6\u672A\u7981\u7528\u3002\\n' +\n '\uD83D\uDC49 \u8BF7\u4F9D\u6B21\u8FD0\u884C\u547D\u4EE4\uFF1A\\n' +\n '```\\n' +\n 'openclaw config set plugins.entries.feishu.enabled false --json\\n' +\n 'openclaw gateway restart\\n' +\n '```',\n );\n }\n\n // 2. \u4ECE scope.ts \u67E5\u8BE2 API \u9700\u8981\u7684 scopes\uFF08Required Scopes\uFF09\n const requiredScopes = getRequiredScopes(toolAction);\n\n // 3. \u51B3\u5B9A token \u7C7B\u578B\uFF08\u9ED8\u8BA4 user\uFF0C\u7528\u6237\u53EF\u901A\u8FC7 options.as \u8986\u76D6\uFF09\n const tokenType = options?.as ?? 'user';\n\n // ---- App Granted Scopes \u68C0\u67E5\uFF08\u5E94\u7528\u5DF2\u5F00\u901A\u7684\u6743\u9650\uFF09----\n // UAT \u8C03\u7528\u989D\u5916\u68C0\u67E5 offline_access\uFF08OAuth Device Flow \u7684\u524D\u63D0\u6743\u9650\uFF09\uFF0C\n // \u4F46\u4E0D\u52A0\u5165 requiredScopes\uFF08\u907F\u514D\u963B\u65AD\u4E1A\u52A1 scope \u8FDB\u5165\u7528\u6237\u6388\u6743\u6D41\u7A0B\uFF09\u3002\n const appCheckScopes = tokenType === 'user' ? [...new Set([...requiredScopes, 'offline_access'])] : requiredScopes;\n\n let appScopeVerified = true;\n if (appCheckScopes.length > 0) {\n const appGrantedScopes = await getAppGrantedScopes(this.sdk, this.account.appId, tokenType);\n\n if (appGrantedScopes.length > 0) {\n // \u4E25\u683C\u6A21\u5F0F\uFF1A\u5E94\u7528\u5FC5\u987B\u5F00\u901A\u6240\u6709 Required Scopes\uFF08+ offline_access\uFF09\n const missingAppScopes = missingScopes(appGrantedScopes, appCheckScopes);\n if (missingAppScopes.length > 0) {\n throw new AppScopeMissingError(\n { apiName: toolAction, scopes: missingAppScopes, appId: this.account.appId },\n 'all',\n tokenType,\n requiredScopes,\n );\n }\n } else {\n // \u67E5\u8BE2\u5931\u8D25\uFF08\u8FD4\u56DE\u7A7A\u6570\u7EC4\uFF09\u2192 \u6807\u8BB0 appScopeVerified=false\uFF0C\u8DF3\u8FC7\u672C\u5730 scope \u9884\u68C0\uFF0C\n // \u8BA9\u670D\u52A1\u7AEF\u6765\u5224\u65AD\u662F\u5E94\u7528\u7F3A\u6743\u9650\u8FD8\u662F\u7528\u6237\u7F3A\u6388\u6743\u3002\n appScopeVerified = false;\n }\n }\n\n // 5. \u6267\u884C\u8C03\u7528\n if (tokenType === 'tenant') {\n return this.invokeAsTenant(toolAction, fn, requiredScopes);\n }\n\n // 5.1 \u83B7\u53D6 userOpenId\uFF0C\u652F\u6301\u515C\u5E95\u903B\u8F91\n let userOpenId = options?.userOpenId ?? this.senderOpenId;\n\n // 5.2 \u515C\u5E95\u903B\u8F91\uFF1A\u5982\u679C\u6CA1\u6709 senderOpenId\uFF0C\u5C1D\u8BD5\u4F7F\u7528\u5E94\u7528\u6240\u6709\u8005\n if (!userOpenId) {\n const fallbackUserId = await getAppOwnerFallback(this.account, this.sdk);\n if (fallbackUserId) {\n userOpenId = fallbackUserId;\n tcLog.info(`Using app owner as fallback user`, {\n toolAction,\n appId: this.account.appId,\n ownerId: fallbackUserId,\n });\n }\n }\n\n return this.invokeAsUser(toolAction, fn, requiredScopes, userOpenId, appScopeVerified);\n }\n\n /**\n * invoke() \u7684\u975E\u629B\u51FA\u5305\u88C5\uFF0C\u9002\u7528\u4E8E\"\u5141\u8BB8\u5931\u8D25\"\u7684\u5B50\u64CD\u4F5C\u3002\n *\n * - \u6210\u529F \u2192 `{ ok: true, data }`\n * - \u7528\u6237\u6388\u6743\u9519\u8BEF\uFF08\u53EF\u901A\u8FC7 OAuth \u6062\u590D\uFF09\u2192 `{ ok: false, authHint }`\n * - \u5E94\u7528\u6743\u9650\u7F3A\u5931 / appScopeVerified=false \u2192 **\u4ECD\u7136 throw**\uFF08\u9700\u7BA1\u7406\u5458\u64CD\u4F5C\uFF09\n * - \u5176\u4ED6\u9519\u8BEF \u2192 `{ ok: false, error }`\n */\n // -------------------------------------------------------------------------\n // invokeByPath() \u2014 SDK \u672A\u8986\u76D6\u7684 API \u8C03\u7528\u5165\u53E3\n // -------------------------------------------------------------------------\n\n /**\n * \u5BF9 SDK \u672A\u8986\u76D6\u7684\u98DE\u4E66 API \u53D1\u8D77 raw HTTP \u8BF7\u6C42\uFF0C\u540C\u65F6\u590D\u7528 invoke() \u7684\n * auth/scope/refresh \u5168\u94FE\u8DEF\u3002\n *\n * @param apiName - \u903B\u8F91 API \u540D\u79F0\uFF08\u7528\u4E8E\u65E5\u5FD7\u548C\u9519\u8BEF\u4FE1\u606F\uFF09\uFF0C\u5982 `\"im.v1.chatP2p.batchQuery\"`\n * @param path - API \u8DEF\u5F84\uFF08\u4EE5 `/open-apis/` \u5F00\u5934\uFF09\uFF0C\u5982 `\"/open-apis/im/v1/chat_p2p/batch_query\"`\n * @param options - HTTP \u65B9\u6CD5\u3001body\u3001query \u53CA InvokeOptions\uFF08as\u3001userOpenId \u7B49\uFF09\n *\n * @example\n * ```typescript\n * const res = await client.invokeByPath<{ data: { items: Array<{ chat_id: string }> } }>(\n * \"im.v1.chatP2p.batchQuery\",\n * \"/open-apis/im/v1/chat_p2p/batch_query\",\n * {\n * method: \"POST\",\n * body: { chatter_ids: [openId] },\n * as: \"user\",\n * },\n * );\n * ```\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async invokeByPath<T = any>(toolAction: ToolActionKey, path: string, options?: InvokeByPathOptions): Promise<T> {\n const fn: InvokeFn<T> = async (_sdk, _opts, uat) => {\n return this.rawRequest<T>(path, {\n method: options?.method,\n body: options?.body,\n query: options?.query,\n headers: options?.headers,\n accessToken: uat,\n });\n };\n return this._invokeInternal(toolAction, fn, options);\n }\n\n // -------------------------------------------------------------------------\n // Private: TAT path\n // -------------------------------------------------------------------------\n\n private async invokeAsTenant<T>(toolAction: ToolActionKey, fn: InvokeFn<T>, requiredScopes: string[]): Promise<T> {\n try {\n return await fn(this.sdk);\n } catch (err) {\n this.rethrowStructuredError(err, toolAction, requiredScopes, undefined, 'tenant');\n throw err;\n }\n }\n\n // -------------------------------------------------------------------------\n // Private: UAT path\n // -------------------------------------------------------------------------\n\n private async invokeAsUser<T>(\n toolAction: ToolActionKey,\n fn: InvokeFn<T>,\n requiredScopes: string[],\n userOpenId: string | undefined,\n appScopeVerified: boolean,\n ): Promise<T> {\n if (!userOpenId) {\n throw new UserAuthRequiredError('unknown', {\n apiName: toolAction,\n scopes: requiredScopes,\n appScopeVerified,\n appId: this.account.appId,\n });\n }\n\n // Owner \u68C0\u67E5\uFF1A\u975E owner \u7528\u6237\u76F4\u63A5\u62D2\u7EDD\uFF08\u4ECE uat-client.ts \u8FC1\u79FB\u81F3\u6B64\uFF09\n await assertOwnerAccessStrict(this.account, this.sdk, userOpenId);\n\n // \u9884\u68C0\uFF1A\u662F\u5426\u6709\u5DF2\u5B58\u50A8\u7684 token\n const stored = await getStoredToken(this.account.appId, userOpenId);\n if (!stored) {\n throw new UserAuthRequiredError(userOpenId, {\n apiName: toolAction,\n scopes: requiredScopes,\n appScopeVerified,\n appId: this.account.appId,\n });\n }\n\n // \u9884\u68C0\uFF1Atoken \u7684 scope \u662F\u5426\u6EE1\u8DB3 API \u8981\u6C42\n // ---- User Granted Scopes \u68C0\u67E5\uFF08\u7528\u6237\u6388\u6743\u7684\u6743\u9650\uFF09----\n // \u4EC5\u5728 App Granted Scopes \u68C0\u67E5\u6210\u529F\u65F6\u8FDB\u884C\u672C\u5730\u9884\u68C0\u3002\n // \u5F53 App Scope \u68C0\u67E5\u5931\u8D25\u65F6\uFF08appScopeVerified=false\uFF09\uFF0C\u8DF3\u8FC7\u9884\u68C0\uFF0C\n // \u8BA9\u8BF7\u6C42\u8D70\u5230\u670D\u52A1\u7AEF \u2014 \u670D\u52A1\u7AEF\u4F1A\u8FD4\u56DE\u51C6\u786E\u7684\u9519\u8BEF\u7801\uFF1A\n // LARK_ERROR.APP_SCOPE_MISSING (99991672) \u2192 App Granted Scopes \u7F3A\u5931\uFF08\u7BA1\u7406\u5458\u9700\u5728\u5F00\u653E\u5E73\u53F0\u5F00\u901A\uFF09\n // LARK_ERROR.USER_SCOPE_INSUFFICIENT (99991679) \u2192 User Granted Scopes \u7F3A\u5931\uFF08\u9700\u5F15\u5BFC\u7528\u6237 OAuth \u6388\u6743\uFF09\n if (appScopeVerified && stored.scope && requiredScopes.length > 0) {\n // \u68C0\u67E5\u7528\u6237\u662F\u5426\u6388\u6743\u4E86\u6240\u6709 Required Scopes\n const userGrantedScopes = new Set(stored.scope.split(/\\s+/).filter(Boolean));\n const missingUserScopes = requiredScopes.filter((s) => !userGrantedScopes.has(s));\n if (missingUserScopes.length > 0) {\n throw new UserAuthRequiredError(userOpenId, {\n apiName: toolAction,\n scopes: missingUserScopes,\n appScopeVerified,\n appId: this.account.appId,\n });\n }\n }\n\n // \u901A\u8FC7 callWithUAT \u6267\u884C\uFF08\u81EA\u52A8 refresh + retry\uFF09\n try {\n return await callWithUAT(\n {\n userOpenId,\n appId: this.account.appId,\n appSecret: this.account.appSecret,\n domain: this.account.brand,\n },\n (accessToken) => fn(this.sdk, Lark.withUserAccessToken(accessToken), accessToken),\n );\n } catch (err) {\n if (err instanceof NeedAuthorizationError) {\n throw new UserAuthRequiredError(userOpenId, {\n apiName: toolAction,\n scopes: requiredScopes,\n appScopeVerified,\n });\n }\n this.rethrowStructuredError(err, toolAction, requiredScopes, userOpenId, 'user');\n throw err;\n }\n }\n\n // -------------------------------------------------------------------------\n // Private: raw HTTP request\n // -------------------------------------------------------------------------\n\n /**\n * \u53D1\u8D77 raw HTTP \u8BF7\u6C42\u5230\u98DE\u4E66 API\uFF0C\u59D4\u6258 rawLarkRequest \u5904\u7406\u3002\n */\n private async rawRequest<T>(\n path: string,\n options: {\n method?: string;\n body?: unknown;\n query?: Record<string, string>;\n headers?: Record<string, string>;\n accessToken?: string;\n },\n ): Promise<T> {\n return rawLarkRequest<T>({\n brand: this.account.brand,\n path,\n ...options,\n });\n }\n\n // -------------------------------------------------------------------------\n // Private: structured error detection\n // -------------------------------------------------------------------------\n\n /**\n * \u8BC6\u522B\u98DE\u4E66\u670D\u52A1\u7AEF\u9519\u8BEF\u7801\u5E76\u8F6C\u6362\u4E3A\u7ED3\u6784\u5316\u9519\u8BEF\u3002\n *\n * - LARK_ERROR.APP_SCOPE_MISSING (99991672) \u2192 AppScopeMissingError\uFF08\u6E05\u7F13\u5B58\u540E\u629B\u51FA\uFF09\n * - LARK_ERROR.USER_SCOPE_INSUFFICIENT (99991679) \u2192 UserScopeInsufficientError\n */\n private rethrowStructuredError(\n err: unknown,\n apiName: string,\n effectiveScopes: string[],\n userOpenId?: string,\n tokenType?: 'user' | 'tenant',\n ): void {\n const code =\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (err as any)?.code ?? (err as any)?.response?.data?.code;\n\n if (code === LARK_ERROR.APP_SCOPE_MISSING) {\n // \u5E94\u7528 scope \u4E0D\u8DB3 \u2014 \u6E05\u7F13\u5B58\uFF08\u7BA1\u7406\u5458\u53EF\u80FD\u521A\u5F00\u901A\uFF09\n invalidateAppScopeCache(this.account.appId);\n throw new AppScopeMissingError(\n {\n apiName,\n scopes: effectiveScopes,\n appId: this.account.appId,\n },\n 'all',\n tokenType,\n );\n }\n\n if (code === LARK_ERROR.USER_SCOPE_INSUFFICIENT && userOpenId) {\n throw new UserScopeInsufficientError(userOpenId, {\n apiName,\n scopes: effectiveScopes,\n });\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * \u4ECE\u914D\u7F6E\u521B\u5EFA {@link ToolClient}\u3002\n *\n * \u81EA\u52A8\u4ECE\u5F53\u524D {@link LarkTicket} \u89E3\u6790 accountId \u548C senderOpenId\u3002\n * \u5982\u679C LarkTicket \u4E0D\u53EF\u7528\uFF08\u5982\u975E\u6D88\u606F\u573A\u666F\uFF09\uFF0C\u56DE\u9000\u5230 `accountIndex`\n * \u6307\u5B9A\u7684\u8D26\u53F7\u3002\n *\n * @param config - OpenClaw \u914D\u7F6E\u5BF9\u8C61\n * @param accountIndex - \u56DE\u9000\u8D26\u53F7\u7D22\u5F15\uFF08\u9ED8\u8BA4 0\uFF09\n */\nexport function createToolClient(config: ClawdbotConfig, accountIndex = 0): ToolClient {\n const ticket = getTicket();\n\n // 1. \u89E3\u6790\u8D26\u53F7\n let account: ConfiguredLarkAccount | undefined;\n\n if (ticket?.accountId) {\n const resolved = getLarkAccount(config, ticket.accountId);\n if (!resolved.configured) {\n throw new Error(\n `Feishu account \"${ticket.accountId}\" is not configured (missing appId or appSecret). ` +\n `Please check channels.feishu.accounts.${ticket.accountId} in your config.`,\n );\n }\n if (!resolved.enabled) {\n throw new Error(\n `Feishu account \"${ticket.accountId}\" is disabled. ` +\n `Set channels.feishu.accounts.${ticket.accountId}.enabled to true, or remove it to use defaults.`,\n );\n }\n account = resolved;\n }\n\n if (!account) {\n const accounts = getEnabledLarkAccounts(config);\n if (accounts.length === 0) {\n throw new Error(\n 'No enabled Feishu accounts configured. ' + 'Please add appId and appSecret in config under channels.feishu',\n );\n }\n if (accountIndex >= accounts.length) {\n throw new Error(`Requested account index ${accountIndex} but only ${accounts.length} accounts available`);\n }\n const fallback = accounts[accountIndex];\n if (!fallback.configured) {\n throw new Error(`Account at index ${accountIndex} is not fully configured (missing appId or appSecret)`);\n }\n account = fallback;\n }\n\n // 2. \u83B7\u53D6 SDK \u5B9E\u4F8B\uFF08\u590D\u7528 LarkClient \u7684\u7F13\u5B58\uFF09\n const larkClient = LarkClient.fromAccount(account);\n\n // 3. \u7EC4\u88C5 ToolClient\n return new ToolClient({\n account,\n senderOpenId: ticket?.senderOpenId,\n sdk: larkClient.sdk,\n config,\n });\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }