@fased/fased 0.1.36 → 0.1.37

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 (244) hide show
  1. package/config/capability-catalog.json +241 -0
  2. package/config/dependency-ownership.json +130 -0
  3. package/config/official-channel-catalog.json +39 -0
  4. package/dist/agents/tools/web-fetch-utils.js +2 -1
  5. package/dist/agents/tools/web-fetch-visibility.js +3 -1
  6. package/dist/brand.js +1 -1
  7. package/dist/browser/pw-session.js +2 -1
  8. package/dist/browser/pw-tools-core.state.js +3 -2
  9. package/dist/browser/runtime-dependency.js +27 -0
  10. package/dist/build-info.json +3 -3
  11. package/dist/canvas-host/a2ui/.bundle.hash +1 -1
  12. package/dist/capabilities/catalog.js +228 -0
  13. package/dist/cli/components-cli.js +105 -0
  14. package/dist/cli/hooks-cli.js +1 -1
  15. package/dist/cli/plugins-cli.js +3 -3
  16. package/dist/cli/program/register.subclis.js +9 -0
  17. package/dist/cli/update-cli/update-command.js +1 -1
  18. package/dist/commands/doctor-capabilities.js +26 -0
  19. package/dist/commands/doctor.js +2 -0
  20. package/dist/control-ui/assets/{app-DXYMrRJ9.js → app-JBrTSsNu.js} +1480 -1465
  21. package/dist/control-ui/assets/{index-iGQkJE0l.js → index-H-2DWAnH.js} +3 -3
  22. package/dist/control-ui/index.html +1 -1
  23. package/dist/gateway/method-scopes.js +1 -0
  24. package/dist/gateway/server-methods/plugins-marketplace.js +1 -1
  25. package/dist/gateway/server-methods/services.js +8 -0
  26. package/dist/gateway/server-methods-list.js +1 -0
  27. package/dist/infra/optional-runtime-dependency.js +46 -0
  28. package/dist/media/image-ops.js +6 -1
  29. package/dist/media/input-files.js +11 -2
  30. package/dist/media/mime.js +6 -1
  31. package/dist/memory/sqlite-vec.js +7 -1
  32. package/dist/plugin-sdk/{accounts-VqAX3HJ0.js → accounts-BwkH5bTD.js} +1 -1
  33. package/dist/plugin-sdk/{accounts-COY-15zJ.js → accounts-C5BjfxoO.js} +1 -1
  34. package/dist/plugin-sdk/{accounts-Zgt0NP54.js → accounts-RT6y7n3H.js} +1 -1
  35. package/dist/plugin-sdk/{actions-lB3PCwsz.js → actions-BrU_JMqM.js} +2 -2
  36. package/dist/plugin-sdk/{actions-BI5QViLk.js → actions-CUdVHzeJ.js} +2 -2
  37. package/dist/plugin-sdk/{active-listener-DKZiSaRA.js → active-listener-CE8t5g6l.js} +1 -1
  38. package/dist/plugin-sdk/{active-listener-D6_pdTMC.js → active-listener-Dx1SEGDn.js} +1 -1
  39. package/dist/plugin-sdk/{audio-preflight-Dx8J7XhV.js → audio-preflight-C8SbY4fV.js} +22 -22
  40. package/dist/plugin-sdk/{audio-preflight-CeRstduc.js → audio-preflight-CuABlLDL.js} +22 -22
  41. package/dist/plugin-sdk/{audit-BImNOQvk.js → audit-DxB2Kml5.js} +11 -11
  42. package/dist/plugin-sdk/{audit-CKWkq0Un.js → audit-sSeRDwJb.js} +11 -11
  43. package/dist/plugin-sdk/channel-plugin-common.js +3 -2
  44. package/dist/plugin-sdk/{chunk-skAJZWCT.js → chunk-KCxbJLsV.js} +1 -1
  45. package/dist/plugin-sdk/{chunk-Cjw4712g.js → chunk-M2d7r-Zt.js} +1 -1
  46. package/dist/plugin-sdk/{chunk-items-Df5811D0.js → chunk-items-BGbYyNvO.js} +1 -1
  47. package/dist/plugin-sdk/command-status.js +22 -22
  48. package/dist/plugin-sdk/{commands-registry-5J619bQ2.js → commands-registry-CdT-nl7v.js} +2 -2
  49. package/dist/plugin-sdk/{commands-registry-CoLAT03J.js → commands-registry-DoWdYAJd.js} +2 -2
  50. package/dist/plugin-sdk/{common-CSTxewqY.js → common-Cpw-vuuM.js} +1 -1
  51. package/dist/plugin-sdk/{common-BiF0sTnY.js → common-DJWrErdE.js} +1 -1
  52. package/dist/plugin-sdk/{config-BxVjLTIv.js → config-C0qggNKQ.js} +7 -7
  53. package/dist/plugin-sdk/{config-TFokWwvO.js → config-CgwTaper.js} +45 -2
  54. package/dist/plugin-sdk/{config-DXlISPlx.js → config-DtqAN2L3.js} +45 -2
  55. package/dist/plugin-sdk/{config-DgD5T-Ui.js → config-g1MG9Bet.js} +7 -7
  56. package/dist/plugin-sdk/discord.js +2 -1
  57. package/dist/plugin-sdk/{fetch-guard-DPMynms8.js → fetch-guard-BLdujDnt.js} +1 -1
  58. package/dist/plugin-sdk/{fetch-guard-COLmW2TM.js → fetch-guard-DTKgMN9f.js} +1 -1
  59. package/dist/plugin-sdk/{handle-action-C-UndB8E.js → handle-action-B61D85yo.js} +12 -12
  60. package/dist/plugin-sdk/{handle-action-hhufKxmU.js → handle-action-CjcxGzQD.js} +12 -12
  61. package/dist/plugin-sdk/index.js +22 -22
  62. package/dist/plugin-sdk/{login-DL1oBBfL.js → login-CB_nCjX3.js} +3 -3
  63. package/dist/plugin-sdk/{login-YveoqEvH.js → login-CfRfRNc7.js} +2 -2
  64. package/dist/plugin-sdk/{login-Dnxas1dI.js → login-CzUuDd1v.js} +3 -3
  65. package/dist/plugin-sdk/{login-qr-CVQsGWKq.js → login-qr-D7MEpFza.js} +3 -3
  66. package/dist/plugin-sdk/{login-qr-B343JNXJ.js → login-qr-DgxfBFVN.js} +3 -3
  67. package/dist/plugin-sdk/{manager-BytwRIfK.js → manager-9see0IX5.js} +8 -3
  68. package/dist/plugin-sdk/{manager-BZjd_PCt.js → manager-D2kZ0Cps.js} +8 -3
  69. package/dist/plugin-sdk/{markdown-tables-krKiU4cT.js → markdown-tables-CCSD52rA.js} +1 -1
  70. package/dist/plugin-sdk/{markdown-tables-9rJDQ1X-.js → markdown-tables-DWUNvLrc.js} +1 -1
  71. package/dist/plugin-sdk/{media-D7ZkWU-A.js → media-C7YT4DeU.js} +13 -4
  72. package/dist/plugin-sdk/{media-C6C2c9a4.js → media-Cxw-xLGU.js} +13 -4
  73. package/dist/plugin-sdk/{monitor-CVQNDfhs.js → monitor-9X7wapUN.js} +27 -27
  74. package/dist/plugin-sdk/{monitor-BCxkQBoh.js → monitor-B3IfJzKX.js} +26 -26
  75. package/dist/plugin-sdk/{monitor-BzDW50H6.js → monitor-CTjAST_W.js} +27 -27
  76. package/dist/plugin-sdk/{monitor-BsLMfn3U.js → monitor-tYFfy2u-.js} +26 -26
  77. package/dist/plugin-sdk/{openresponses-http-BB67Z4QM.js → openresponses-http-DEB2fEya.js} +22 -22
  78. package/dist/plugin-sdk/{openresponses-http-NE76GGhj.js → openresponses-http-YgMg1jnp.js} +22 -22
  79. package/dist/plugin-sdk/{ssrf-u0Ul7okY.js → optional-runtime-dependency-szS49voX.js} +46 -2
  80. package/dist/plugin-sdk/{outbound-PGNWU6T-.js → outbound-BCxOxdi-.js} +6 -6
  81. package/dist/plugin-sdk/{outbound-BHWJD9q0.js → outbound-JIwQbpRY.js} +6 -6
  82. package/dist/plugin-sdk/{outbound-attachment-BVzfhqnk.js → outbound-attachment-C-jSFaEB.js} +1 -1
  83. package/dist/plugin-sdk/{outbound-attachment-ClMvNW17.js → outbound-attachment-D6tj5Y5H.js} +1 -1
  84. package/dist/plugin-sdk/{pi-embedded-runner-DQ6UPx6-.js → pi-embedded-runner-CUVk8CIj.js} +72 -60
  85. package/dist/plugin-sdk/{pi-model-discovery-D51L5E9I.js → pi-model-discovery-DadlvsZW.js} +1 -1
  86. package/dist/plugin-sdk/{pi-model-discovery-runtime-DxoFm4Vd.js → pi-model-discovery-runtime-C5ssE6EW.js} +1 -1
  87. package/dist/plugin-sdk/{pi-model-discovery-runtime-DRuh4PNV.js → pi-model-discovery-runtime-DAaOPt64.js} +2 -2
  88. package/dist/plugin-sdk/{pi-model-discovery-runtime-CQOauapl.js → pi-model-discovery-runtime-LB-5lX5L.js} +1 -1
  89. package/dist/plugin-sdk/{pi-model-discovery-runtime-BTWgkx4x.js → pi-model-discovery-runtime-gd77b0UF.js} +2 -2
  90. package/dist/plugin-sdk/{pi-model-discovery-CkH5l9X5.js → pi-model-discovery-yifTIDIn.js} +1 -1
  91. package/dist/plugin-sdk/{pw-ai-DZs6uYRd.js → pw-ai-SNCI9UXT.js} +6 -5
  92. package/dist/plugin-sdk/{pw-ai-Cn-EUPZK.js → pw-ai-sH1Pi-Fh.js} +6 -5
  93. package/dist/plugin-sdk/{registry-B4HvWzob.js → registry-DNkJsNsz.js} +4 -3
  94. package/dist/plugin-sdk/{replies-jDMUBsNO.js → replies-fvzN5wnU.js} +2 -2
  95. package/dist/plugin-sdk/{replies-B_LW9c9f.js → replies-zebgZCHX.js} +2 -2
  96. package/dist/plugin-sdk/{resolve-route-_YZHbS-z.js → resolve-route-CNhTJG_2.js} +1 -1
  97. package/dist/plugin-sdk/{resolve-route-DlHzggnG.js → resolve-route-DO0WR4nB.js} +1 -1
  98. package/dist/plugin-sdk/{resolve-utils-zBhVX3Vx.js → resolve-utils-EFP1DYqG.js} +1 -1
  99. package/dist/plugin-sdk/{retry-policy-DGLs5ebP.js → retry-policy-BLYEgIwP.js} +1 -1
  100. package/dist/plugin-sdk/{retry-policy-DaodShGa.js → retry-policy-sM01QZT2.js} +1 -1
  101. package/dist/plugin-sdk/{runtime-Dd0sswuW.js → runtime-BPyuAmka.js} +4 -3
  102. package/dist/plugin-sdk/{paths-DW-U2r7Y.js → runtime-dependency-B-1u41Ph.js} +27 -3
  103. package/dist/plugin-sdk/{paths-CBCpJ5bu.js → runtime-dependency-Csswyv5r.js} +27 -3
  104. package/dist/plugin-sdk/sat-runtime.js +1 -0
  105. package/dist/plugin-sdk/{send-8remZhsH.js → send-B09HRfHN.js} +6 -6
  106. package/dist/plugin-sdk/{send-DaaAscWj.js → send-BBYcgXA4.js} +5 -5
  107. package/dist/plugin-sdk/{send-DFTB0cnx.js → send-BQXQA81e.js} +6 -6
  108. package/dist/plugin-sdk/{send-CtMVvMJs.js → send-BzFdvD_i.js} +6 -6
  109. package/dist/plugin-sdk/{send-Di9irjS-.js → send-C3u-F_8s.js} +6 -6
  110. package/dist/plugin-sdk/{send-Dl88ZKq2.js → send-D3836_UN.js} +6 -6
  111. package/dist/plugin-sdk/{send-ifgZCHVB.js → send-D7tr-EsH.js} +5 -5
  112. package/dist/plugin-sdk/{send-DvUpYh0d.js → send-DRGzRdI3.js} +6 -6
  113. package/dist/plugin-sdk/{send-D2jlPeKH.js → send-DfkPKz_0.js} +4 -4
  114. package/dist/plugin-sdk/{send-QSt6nbny.js → send-DsxFXrCO.js} +4 -4
  115. package/dist/plugin-sdk/{session-CknrceVU.js → session-DG_bYisw.js} +2 -2
  116. package/dist/plugin-sdk/{session-CyRTcJ8N.js → session-EIStzK0E.js} +2 -2
  117. package/dist/plugin-sdk/{skill-commands-DHxkTA0s.js → skill-commands-CkgGmWBS.js} +2 -2
  118. package/dist/plugin-sdk/{skill-commands-CLXAG5sU.js → skill-commands-hyZA0LE3.js} +2 -2
  119. package/dist/plugin-sdk/{slack-BpJXkwxF.js → slack-C0WZVJPZ.js} +34 -34
  120. package/dist/plugin-sdk/{slack-Br9c6Yzs.js → slack-DtlPw_PN.js} +34 -34
  121. package/dist/plugin-sdk/{slack-actions-8tLGt1Ht.js → slack-actions-BQawWGTJ.js} +11 -11
  122. package/dist/plugin-sdk/{slack-actions-CDSSRo8Y.js → slack-actions-DDMszx56.js} +11 -11
  123. package/dist/plugin-sdk/slack.js +2 -1
  124. package/dist/plugin-sdk/src/brand.d.ts +2 -2
  125. package/dist/plugin-sdk/src/browser/runtime-dependency.d.ts +3 -0
  126. package/dist/plugin-sdk/src/infra/optional-runtime-dependency.d.ts +6 -0
  127. package/dist/plugin-sdk/{ssrf-fG4UHKBw.js → ssrf-Cce6LubA.js} +41 -7
  128. package/dist/plugin-sdk/{status-DoxnzyMC.js → status-CIiBeb9B.js} +72 -60
  129. package/dist/plugin-sdk/{sticker-cache-zmJ39ByC.js → sticker-cache-9houHChV.js} +3 -3
  130. package/dist/plugin-sdk/{sticker-cache-D7WcMYsF.js → sticker-cache-Dwi-XsgB.js} +3 -3
  131. package/dist/plugin-sdk/{tables-CkxblF0u.js → tables-BD7H5ug-.js} +1 -1
  132. package/dist/plugin-sdk/{tables-RBtf8G4P.js → tables-CJE8kB35.js} +1 -1
  133. package/dist/plugin-sdk/{telegram-actions-DAeYXSLk.js → telegram-actions-Bu1edXF2.js} +25 -25
  134. package/dist/plugin-sdk/{telegram-actions-BDeS3ocd.js → telegram-actions-CRXVO8lG.js} +25 -25
  135. package/dist/plugin-sdk/telegram.js +2 -1
  136. package/dist/plugin-sdk/{thread-bindings.lifecycle-DsB94WbE.js → thread-bindings.lifecycle-Djem8FWF.js} +2 -2
  137. package/dist/plugin-sdk/{thread-bindings.lifecycle-DMhV3TsR.js → thread-bindings.lifecycle-gmHv3hc7.js} +2 -2
  138. package/dist/plugin-sdk/{thread-bindings.state-DYWpy7hJ.js → thread-bindings.state-CNT2vxm3.js} +2 -2
  139. package/dist/plugin-sdk/{thread-bindings.state-DG5cBaRn.js → thread-bindings.state-D8kN2X6H.js} +2 -2
  140. package/dist/plugin-sdk/{web-IJ4NoGPJ.js → web-DCrVQr6y.js} +26 -26
  141. package/dist/plugin-sdk/{web-DJwacWQo.js → web-Ds6OWw3C.js} +26 -26
  142. package/dist/plugin-sdk/{whatsapp-actions-DAY8hWpD.js → whatsapp-actions-68Mfs2dJ.js} +12 -12
  143. package/dist/plugin-sdk/{whatsapp-actions-6opfo-oB.js → whatsapp-actions-DRqtzvTO.js} +12 -12
  144. package/dist/plugin-sdk/whatsapp.js +6 -5
  145. package/dist/plugin-sdk/{wsl-B2euLEc2.js → wsl-DcM2PgSS.js} +3 -3
  146. package/dist/plugin-sdk/{wsl-B8eyviuv.js → wsl-PXrdWitE.js} +3 -3
  147. package/dist/plugins/update.js +1 -1
  148. package/dist/tts/tts-core.js +6 -1
  149. package/dist/wizard/onboarding.finalize.js +9 -0
  150. package/extensions/acpx/package.json +1 -1
  151. package/extensions/bluebubbles/package.json +1 -1
  152. package/extensions/copilot-proxy/package.json +1 -1
  153. package/extensions/diagnostics-otel/package.json +1 -1
  154. package/extensions/fased-federation/package.json +1 -1
  155. package/extensions/google-gemini-cli-auth/package.json +1 -1
  156. package/extensions/imessage/package.json +1 -1
  157. package/extensions/irc/package.json +1 -1
  158. package/extensions/line/package.json +1 -1
  159. package/extensions/llm-task/package.json +1 -1
  160. package/extensions/lobster/package.json +1 -1
  161. package/extensions/matrix/CHANGELOG.md +6 -0
  162. package/extensions/matrix/package.json +1 -1
  163. package/extensions/mattermost/package.json +1 -1
  164. package/extensions/memory-core/package.json +2 -2
  165. package/extensions/memory-lancedb/package.json +1 -1
  166. package/extensions/minimax-portal-auth/package.json +1 -1
  167. package/extensions/msteams/CHANGELOG.md +6 -0
  168. package/extensions/msteams/package.json +1 -1
  169. package/extensions/nextcloud-talk/package.json +1 -1
  170. package/extensions/nostr/CHANGELOG.md +6 -0
  171. package/extensions/nostr/package.json +1 -1
  172. package/extensions/open-prose/package.json +1 -1
  173. package/extensions/sat-mining/package.json +1 -1
  174. package/extensions/signal/package.json +1 -1
  175. package/extensions/synology-chat/package.json +1 -1
  176. package/extensions/tlon/package.json +1 -1
  177. package/extensions/twitch/CHANGELOG.md +6 -0
  178. package/extensions/twitch/package.json +1 -1
  179. package/extensions/voice-call/CHANGELOG.md +6 -0
  180. package/extensions/voice-call/package.json +1 -1
  181. package/extensions/zalo/CHANGELOG.md +6 -0
  182. package/extensions/zalo/package.json +1 -1
  183. package/extensions/zalouser/CHANGELOG.md +6 -0
  184. package/extensions/zalouser/package.json +1 -1
  185. package/package.json +22 -15
  186. package/extensions/feishu/fased.plugin.json +0 -10
  187. package/extensions/feishu/index.ts +0 -63
  188. package/extensions/feishu/package.json +0 -33
  189. package/extensions/feishu/skills/feishu-doc/SKILL.md +0 -211
  190. package/extensions/feishu/skills/feishu-doc/references/block-types.md +0 -103
  191. package/extensions/feishu/skills/feishu-drive/SKILL.md +0 -97
  192. package/extensions/feishu/skills/feishu-perm/SKILL.md +0 -119
  193. package/extensions/feishu/skills/feishu-wiki/SKILL.md +0 -111
  194. package/extensions/feishu/src/accounts.ts +0 -159
  195. package/extensions/feishu/src/bitable.ts +0 -713
  196. package/extensions/feishu/src/bot.ts +0 -979
  197. package/extensions/feishu/src/channel.ts +0 -359
  198. package/extensions/feishu/src/client.ts +0 -118
  199. package/extensions/feishu/src/config-schema.ts +0 -226
  200. package/extensions/feishu/src/dedup.ts +0 -54
  201. package/extensions/feishu/src/directory.ts +0 -177
  202. package/extensions/feishu/src/doc-schema.ts +0 -122
  203. package/extensions/feishu/src/docx.ts +0 -998
  204. package/extensions/feishu/src/drive-schema.ts +0 -46
  205. package/extensions/feishu/src/drive.ts +0 -232
  206. package/extensions/feishu/src/dynamic-agent.ts +0 -131
  207. package/extensions/feishu/src/external-keys.ts +0 -19
  208. package/extensions/feishu/src/media.ts +0 -447
  209. package/extensions/feishu/src/mention.ts +0 -133
  210. package/extensions/feishu/src/monitor.ts +0 -434
  211. package/extensions/feishu/src/onboarding.ts +0 -430
  212. package/extensions/feishu/src/outbound.ts +0 -56
  213. package/extensions/feishu/src/perm-schema.ts +0 -52
  214. package/extensions/feishu/src/perm.ts +0 -180
  215. package/extensions/feishu/src/policy.ts +0 -116
  216. package/extensions/feishu/src/probe.ts +0 -44
  217. package/extensions/feishu/src/reactions.ts +0 -160
  218. package/extensions/feishu/src/reply-dispatcher.ts +0 -239
  219. package/extensions/feishu/src/runtime.ts +0 -14
  220. package/extensions/feishu/src/send-result.ts +0 -29
  221. package/extensions/feishu/src/send-target.ts +0 -25
  222. package/extensions/feishu/src/send.ts +0 -313
  223. package/extensions/feishu/src/streaming-card.ts +0 -218
  224. package/extensions/feishu/src/targets.ts +0 -78
  225. package/extensions/feishu/src/tool-account.ts +0 -58
  226. package/extensions/feishu/src/tools-config.ts +0 -21
  227. package/extensions/feishu/src/types.ts +0 -81
  228. package/extensions/feishu/src/typing.ts +0 -80
  229. package/extensions/feishu/src/wiki-schema.ts +0 -55
  230. package/extensions/feishu/src/wiki.ts +0 -237
  231. package/extensions/googlechat/fased.plugin.json +0 -9
  232. package/extensions/googlechat/index.ts +0 -19
  233. package/extensions/googlechat/package.json +0 -37
  234. package/extensions/googlechat/src/accounts.ts +0 -147
  235. package/extensions/googlechat/src/actions.ts +0 -176
  236. package/extensions/googlechat/src/api.ts +0 -282
  237. package/extensions/googlechat/src/auth.ts +0 -137
  238. package/extensions/googlechat/src/channel.ts +0 -591
  239. package/extensions/googlechat/src/monitor.ts +0 -996
  240. package/extensions/googlechat/src/onboarding.ts +0 -344
  241. package/extensions/googlechat/src/runtime.ts +0 -14
  242. package/extensions/googlechat/src/targets.ts +0 -65
  243. package/extensions/googlechat/src/types.config.ts +0 -3
  244. package/extensions/googlechat/src/types.ts +0 -73
@@ -1,998 +0,0 @@
1
- import { promises as fs } from "node:fs";
2
- import { basename } from "node:path";
3
- import { Readable } from "stream";
4
- import type * as Lark from "@larksuiteoapi/node-sdk";
5
- import { Type } from "@sinclair/typebox";
6
- import type { FasedAgentPluginApi } from "fased/plugin-sdk";
7
- import { listEnabledFeishuAccounts } from "./accounts.js";
8
- import { FeishuDocSchema, type FeishuDocParams } from "./doc-schema.js";
9
- import { getFeishuRuntime } from "./runtime.js";
10
- import {
11
- createFeishuToolClient,
12
- resolveAnyEnabledFeishuToolsConfig,
13
- resolveFeishuToolAccount,
14
- } from "./tool-account.js";
15
-
16
- // ============ Helpers ============
17
-
18
- function json(data: unknown) {
19
- return {
20
- content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }],
21
- details: data,
22
- };
23
- }
24
-
25
- /** Extract image URLs from markdown content */
26
- function extractImageUrls(markdown: string): string[] {
27
- const regex = /!\[[^\]]*\]\(([^)]+)\)/g;
28
- const urls: string[] = [];
29
- let match;
30
- while ((match = regex.exec(markdown)) !== null) {
31
- const url = match[1].trim();
32
- if (url.startsWith("http://") || url.startsWith("https://")) {
33
- urls.push(url);
34
- }
35
- }
36
- return urls;
37
- }
38
-
39
- const BLOCK_TYPE_NAMES: Record<number, string> = {
40
- 1: "Page",
41
- 2: "Text",
42
- 3: "Heading1",
43
- 4: "Heading2",
44
- 5: "Heading3",
45
- 12: "Bullet",
46
- 13: "Ordered",
47
- 14: "Code",
48
- 15: "Quote",
49
- 17: "Todo",
50
- 18: "Bitable",
51
- 21: "Diagram",
52
- 22: "Divider",
53
- 23: "File",
54
- 27: "Image",
55
- 30: "Sheet",
56
- 31: "Table",
57
- 32: "TableCell",
58
- };
59
-
60
- // Block types that cannot be created via documentBlockChildren.create API
61
- const UNSUPPORTED_CREATE_TYPES = new Set([31, 32]);
62
-
63
- /** Clean blocks for insertion (remove unsupported types and read-only fields) */
64
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK block types
65
- function cleanBlocksForInsert(blocks: any[]): { cleaned: any[]; skipped: string[] } {
66
- const skipped: string[] = [];
67
- const cleaned = blocks
68
- .filter((block) => {
69
- if (UNSUPPORTED_CREATE_TYPES.has(block.block_type)) {
70
- const typeName = BLOCK_TYPE_NAMES[block.block_type] || `type_${block.block_type}`;
71
- skipped.push(typeName);
72
- return false;
73
- }
74
- return true;
75
- })
76
- .map((block) => {
77
- if (block.block_type === 31 && block.table?.merge_info) {
78
- const { merge_info: _merge_info, ...tableRest } = block.table;
79
- return { ...block, table: tableRest };
80
- }
81
- return block;
82
- });
83
- return { cleaned, skipped };
84
- }
85
-
86
- // ============ Core Functions ============
87
-
88
- async function convertMarkdown(client: Lark.Client, markdown: string) {
89
- const res = await client.docx.document.convert({
90
- data: { content_type: "markdown", content: markdown },
91
- });
92
- if (res.code !== 0) {
93
- throw new Error(res.msg);
94
- }
95
- return {
96
- blocks: res.data?.blocks ?? [],
97
- firstLevelBlockIds: res.data?.first_level_block_ids ?? [],
98
- };
99
- }
100
-
101
- function sortBlocksByFirstLevel(blocks: any[], firstLevelIds: string[]): any[] {
102
- if (!firstLevelIds || firstLevelIds.length === 0) return blocks;
103
- const sorted = firstLevelIds.map((id) => blocks.find((b) => b.block_id === id)).filter(Boolean);
104
- const sortedIds = new Set(firstLevelIds);
105
- const remaining = blocks.filter((b) => !sortedIds.has(b.block_id));
106
- return [...sorted, ...remaining];
107
- }
108
-
109
- /* eslint-disable @typescript-eslint/no-explicit-any -- SDK block types */
110
- async function insertBlocks(
111
- client: Lark.Client,
112
- docToken: string,
113
- blocks: any[],
114
- parentBlockId?: string,
115
- index?: number,
116
- ): Promise<{ children: any[]; skipped: string[] }> {
117
- /* eslint-enable @typescript-eslint/no-explicit-any */
118
- const { cleaned, skipped } = cleanBlocksForInsert(blocks);
119
- const blockId = parentBlockId ?? docToken;
120
-
121
- if (cleaned.length === 0) {
122
- return { children: [], skipped };
123
- }
124
-
125
- const res = await client.docx.documentBlockChildren.create({
126
- path: { document_id: docToken, block_id: blockId },
127
- data: {
128
- children: cleaned,
129
- ...(index !== undefined && { index }),
130
- },
131
- });
132
- if (res.code !== 0) {
133
- throw new Error(res.msg);
134
- }
135
- return { children: res.data?.children ?? [], skipped };
136
- }
137
-
138
- async function clearDocumentContent(client: Lark.Client, docToken: string) {
139
- const existing = await client.docx.documentBlock.list({
140
- path: { document_id: docToken },
141
- });
142
- if (existing.code !== 0) {
143
- throw new Error(existing.msg);
144
- }
145
-
146
- const childIds =
147
- existing.data?.items
148
- ?.filter((b) => b.parent_id === docToken && b.block_type !== 1)
149
- .map((b) => b.block_id) ?? [];
150
-
151
- if (childIds.length > 0) {
152
- const res = await client.docx.documentBlockChildren.batchDelete({
153
- path: { document_id: docToken, block_id: docToken },
154
- data: { start_index: 0, end_index: childIds.length },
155
- });
156
- if (res.code !== 0) {
157
- throw new Error(res.msg);
158
- }
159
- }
160
-
161
- return childIds.length;
162
- }
163
-
164
- async function uploadImageToDocx(
165
- client: Lark.Client,
166
- blockId: string,
167
- imageBuffer: Buffer,
168
- fileName: string,
169
- ): Promise<string> {
170
- const res = await client.drive.media.uploadAll({
171
- data: {
172
- file_name: fileName,
173
- parent_type: "docx_image",
174
- parent_node: blockId,
175
- size: imageBuffer.length,
176
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK stream type
177
- file: Readable.from(imageBuffer) as any,
178
- },
179
- });
180
-
181
- const fileToken = res?.file_token;
182
- if (!fileToken) {
183
- throw new Error("Image upload failed: no file_token returned");
184
- }
185
- return fileToken;
186
- }
187
-
188
- async function downloadImage(url: string, maxBytes: number): Promise<Buffer> {
189
- const fetched = await getFeishuRuntime().channel.media.fetchRemoteMedia({ url, maxBytes });
190
- return fetched.buffer;
191
- }
192
-
193
- async function resolveUploadInput(
194
- url: string | undefined,
195
- filePath: string | undefined,
196
- maxBytes: number,
197
- explicitFileName?: string,
198
- ): Promise<{ buffer: Buffer; fileName: string }> {
199
- if (!url && !filePath) {
200
- throw new Error("Either url or file_path is required");
201
- }
202
- if (url && filePath) {
203
- throw new Error("Provide only one of url or file_path");
204
- }
205
-
206
- if (url) {
207
- const fetched = await getFeishuRuntime().channel.media.fetchRemoteMedia({ url, maxBytes });
208
- const urlPath = new URL(url).pathname;
209
- const guessed = urlPath.split("/").pop() || "upload.bin";
210
- return {
211
- buffer: fetched.buffer,
212
- fileName: explicitFileName || guessed,
213
- };
214
- }
215
-
216
- const buffer = await fs.readFile(filePath!);
217
- if (buffer.length > maxBytes) {
218
- throw new Error(`Local file exceeds limit: ${buffer.length} bytes > ${maxBytes} bytes`);
219
- }
220
- return {
221
- buffer,
222
- fileName: explicitFileName || basename(filePath!),
223
- };
224
- }
225
-
226
- /* eslint-disable @typescript-eslint/no-explicit-any -- SDK block types */
227
- async function processImages(
228
- client: Lark.Client,
229
- docToken: string,
230
- markdown: string,
231
- insertedBlocks: any[],
232
- maxBytes: number,
233
- ): Promise<number> {
234
- /* eslint-enable @typescript-eslint/no-explicit-any */
235
- const imageUrls = extractImageUrls(markdown);
236
- if (imageUrls.length === 0) {
237
- return 0;
238
- }
239
-
240
- const imageBlocks = insertedBlocks.filter((b) => b.block_type === 27);
241
-
242
- let processed = 0;
243
- for (let i = 0; i < Math.min(imageUrls.length, imageBlocks.length); i++) {
244
- const url = imageUrls[i];
245
- const blockId = imageBlocks[i].block_id;
246
-
247
- try {
248
- const buffer = await downloadImage(url, maxBytes);
249
- const urlPath = new URL(url).pathname;
250
- const fileName = urlPath.split("/").pop() || `image_${i}.png`;
251
- const fileToken = await uploadImageToDocx(client, blockId, buffer, fileName);
252
-
253
- await client.docx.documentBlock.patch({
254
- path: { document_id: docToken, block_id: blockId },
255
- data: {
256
- replace_image: { token: fileToken },
257
- },
258
- });
259
-
260
- processed++;
261
- } catch (err) {
262
- console.error(`Failed to process image ${url}:`, err);
263
- }
264
- }
265
-
266
- return processed;
267
- }
268
-
269
- async function uploadImageBlock(
270
- client: Lark.Client,
271
- docToken: string,
272
- maxBytes: number,
273
- url?: string,
274
- filePath?: string,
275
- parentBlockId?: string,
276
- filename?: string,
277
- index?: number,
278
- ) {
279
- const blockId = parentBlockId ?? docToken;
280
-
281
- // Feishu API does not allow creating empty image blocks (block_type 27).
282
- // Workaround: use markdown conversion to create a placeholder image block,
283
- // then upload the real image and patch the block.
284
- const placeholderMd = "![img](https://via.placeholder.com/800x600.png)";
285
- const converted = await convertMarkdown(client, placeholderMd);
286
- const sorted = sortBlocksByFirstLevel(converted.blocks, converted.firstLevelBlockIds);
287
- const { children: inserted } = await insertBlocks(client, docToken, sorted, blockId, index);
288
-
289
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK return shape
290
- const imageBlock = inserted.find((b: any) => b.block_type === 27);
291
- const imageBlockId = imageBlock?.block_id;
292
- if (!imageBlockId) {
293
- throw new Error("Failed to create image block via markdown placeholder");
294
- }
295
-
296
- const upload = await resolveUploadInput(url, filePath, maxBytes, filename);
297
- const fileToken = await uploadImageToDocx(client, imageBlockId, upload.buffer, upload.fileName);
298
-
299
- const patchRes = await client.docx.documentBlock.patch({
300
- path: { document_id: docToken, block_id: imageBlockId },
301
- data: {
302
- replace_image: { token: fileToken },
303
- },
304
- });
305
- if (patchRes.code !== 0) {
306
- throw new Error(patchRes.msg);
307
- }
308
-
309
- return {
310
- success: true,
311
- block_id: imageBlockId,
312
- file_token: fileToken,
313
- file_name: upload.fileName,
314
- size: upload.buffer.length,
315
- };
316
- }
317
-
318
- async function uploadFileBlock(
319
- client: Lark.Client,
320
- docToken: string,
321
- maxBytes: number,
322
- url?: string,
323
- filePath?: string,
324
- parentBlockId?: string,
325
- filename?: string,
326
- ) {
327
- const blockId = parentBlockId ?? docToken;
328
-
329
- // Feishu API does not allow creating empty file blocks (block_type 23).
330
- // Workaround: create a placeholder text block, then replace it with file content.
331
- // Actually, file blocks need a different approach: use markdown link as placeholder.
332
- const upload = await resolveUploadInput(url, filePath, maxBytes, filename);
333
-
334
- // Create a placeholder text block first
335
- const placeholderMd = `[${upload.fileName}](https://example.com/placeholder)`;
336
- const converted = await convertMarkdown(client, placeholderMd);
337
- const sorted = sortBlocksByFirstLevel(converted.blocks, converted.firstLevelBlockIds);
338
- const { children: inserted } = await insertBlocks(client, docToken, sorted, blockId);
339
-
340
- // Get the first inserted block - we'll delete it and create the file in its place
341
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK return shape
342
- const placeholderBlock = inserted[0];
343
- if (!placeholderBlock?.block_id) {
344
- throw new Error("Failed to create placeholder block for file upload");
345
- }
346
-
347
- // Delete the placeholder
348
- const parentId = placeholderBlock.parent_id ?? blockId;
349
- const childrenRes = await client.docx.documentBlockChildren.get({
350
- path: { document_id: docToken, block_id: parentId },
351
- });
352
- if (childrenRes.code !== 0) {
353
- throw new Error(childrenRes.msg);
354
- }
355
- const items = childrenRes.data?.items ?? [];
356
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK block type
357
- const placeholderIdx = items.findIndex(
358
- (item: any) => item.block_id === placeholderBlock.block_id,
359
- );
360
- if (placeholderIdx >= 0) {
361
- const deleteRes = await client.docx.documentBlockChildren.batchDelete({
362
- path: { document_id: docToken, block_id: parentId },
363
- data: { start_index: placeholderIdx, end_index: placeholderIdx + 1 },
364
- });
365
- if (deleteRes.code !== 0) {
366
- throw new Error(deleteRes.msg);
367
- }
368
- }
369
-
370
- // Upload file to Feishu drive
371
- const fileRes = await client.drive.media.uploadAll({
372
- data: {
373
- file_name: upload.fileName,
374
- parent_type: "docx_file",
375
- parent_node: docToken,
376
- size: upload.buffer.length,
377
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK stream type
378
- file: Readable.from(upload.buffer) as any,
379
- },
380
- });
381
-
382
- const fileToken = fileRes?.file_token;
383
- if (!fileToken) {
384
- throw new Error("File upload failed: no file_token returned");
385
- }
386
-
387
- return {
388
- success: true,
389
- file_token: fileToken,
390
- file_name: upload.fileName,
391
- size: upload.buffer.length,
392
- note: "File uploaded to drive. Use the file_token to reference it. Direct file block creation is not supported by the Feishu API.",
393
- };
394
- }
395
-
396
- // ============ Actions ============
397
-
398
- const STRUCTURED_BLOCK_TYPES = new Set([14, 18, 21, 23, 27, 30, 31, 32]);
399
-
400
- async function readDoc(client: Lark.Client, docToken: string) {
401
- const [contentRes, infoRes, blocksRes] = await Promise.all([
402
- client.docx.document.rawContent({ path: { document_id: docToken } }),
403
- client.docx.document.get({ path: { document_id: docToken } }),
404
- client.docx.documentBlock.list({ path: { document_id: docToken } }),
405
- ]);
406
-
407
- if (contentRes.code !== 0) {
408
- throw new Error(contentRes.msg);
409
- }
410
-
411
- const blocks = blocksRes.data?.items ?? [];
412
- const blockCounts: Record<string, number> = {};
413
- const structuredTypes: string[] = [];
414
-
415
- for (const b of blocks) {
416
- const type = b.block_type ?? 0;
417
- const name = BLOCK_TYPE_NAMES[type] || `type_${type}`;
418
- blockCounts[name] = (blockCounts[name] || 0) + 1;
419
-
420
- if (STRUCTURED_BLOCK_TYPES.has(type) && !structuredTypes.includes(name)) {
421
- structuredTypes.push(name);
422
- }
423
- }
424
-
425
- let hint: string | undefined;
426
- if (structuredTypes.length > 0) {
427
- hint = `This document contains ${structuredTypes.join(", ")} which are NOT included in the plain text above. Use feishu_doc with action: "list_blocks" to get full content.`;
428
- }
429
-
430
- return {
431
- title: infoRes.data?.document?.title,
432
- content: contentRes.data?.content,
433
- revision_id: infoRes.data?.document?.revision_id,
434
- block_count: blocks.length,
435
- block_types: blockCounts,
436
- ...(hint && { hint }),
437
- };
438
- }
439
-
440
- async function createDoc(
441
- client: Lark.Client,
442
- title: string,
443
- folderToken?: string,
444
- ownerOpenId?: string,
445
- ownerPermType: "view" | "edit" | "full_access" = "full_access",
446
- ) {
447
- const res = await client.docx.document.create({
448
- data: { title, folder_token: folderToken },
449
- });
450
- if (res.code !== 0) {
451
- throw new Error(res.msg);
452
- }
453
- const doc = res.data?.document;
454
- const docToken = doc?.document_id;
455
- if (!docToken) {
456
- throw new Error("Document creation succeeded but no document_id was returned");
457
- }
458
- let ownerPermissionAdded = false;
459
-
460
- // Auto add owner permission if ownerOpenId is provided
461
- if (docToken && ownerOpenId) {
462
- try {
463
- await client.drive.permissionMember.create({
464
- path: { token: docToken },
465
- params: { type: "docx", need_notification: false },
466
- data: {
467
- member_type: "openid",
468
- member_id: ownerOpenId,
469
- perm: ownerPermType,
470
- },
471
- });
472
- ownerPermissionAdded = true;
473
- } catch (err) {
474
- console.warn("Failed to add owner permission (non-critical):", err);
475
- }
476
- }
477
-
478
- return {
479
- document_id: docToken,
480
- title: doc?.title,
481
- url: `https://feishu.cn/docx/${docToken}`,
482
- ...(ownerOpenId &&
483
- ownerPermissionAdded && {
484
- owner_permission_added: true,
485
- owner_open_id: ownerOpenId,
486
- owner_perm_type: ownerPermType,
487
- }),
488
- };
489
- }
490
-
491
- async function writeDoc(client: Lark.Client, docToken: string, markdown: string, maxBytes: number) {
492
- const deleted = await clearDocumentContent(client, docToken);
493
-
494
- const { blocks, firstLevelBlockIds } = await convertMarkdown(client, markdown);
495
- if (blocks.length === 0) {
496
- return { success: true, blocks_deleted: deleted, blocks_added: 0, images_processed: 0 };
497
- }
498
- const sortedBlocks = sortBlocksByFirstLevel(blocks, firstLevelBlockIds);
499
-
500
- const { children: inserted, skipped } = await insertBlocks(client, docToken, sortedBlocks);
501
- const imagesProcessed = await processImages(client, docToken, markdown, inserted, maxBytes);
502
-
503
- return {
504
- success: true,
505
- blocks_deleted: deleted,
506
- blocks_added: inserted.length,
507
- images_processed: imagesProcessed,
508
- ...(skipped.length > 0 && {
509
- warning: `Skipped unsupported block types: ${skipped.join(", ")}. Tables are not supported via this API.`,
510
- }),
511
- };
512
- }
513
-
514
- async function appendDoc(
515
- client: Lark.Client,
516
- docToken: string,
517
- markdown: string,
518
- maxBytes: number,
519
- ) {
520
- const { blocks, firstLevelBlockIds } = await convertMarkdown(client, markdown);
521
- if (blocks.length === 0) {
522
- throw new Error("Content is empty");
523
- }
524
- const sortedBlocks = sortBlocksByFirstLevel(blocks, firstLevelBlockIds);
525
-
526
- const { children: inserted, skipped } = await insertBlocks(client, docToken, sortedBlocks);
527
- const imagesProcessed = await processImages(client, docToken, markdown, inserted, maxBytes);
528
-
529
- return {
530
- success: true,
531
- blocks_added: inserted.length,
532
- images_processed: imagesProcessed,
533
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK block type
534
- block_ids: inserted.map((b: any) => b.block_id),
535
- ...(skipped.length > 0 && {
536
- warning: `Skipped unsupported block types: ${skipped.join(", ")}. Tables are not supported via this API.`,
537
- }),
538
- };
539
- }
540
-
541
- async function createTable(
542
- client: Lark.Client,
543
- docToken: string,
544
- rowSize: number,
545
- columnSize: number,
546
- parentBlockId?: string,
547
- columnWidth?: number[],
548
- ) {
549
- if (columnWidth && columnWidth.length !== columnSize) {
550
- throw new Error("column_width length must equal column_size");
551
- }
552
-
553
- const blockId = parentBlockId ?? docToken;
554
- const res = await client.docx.documentBlockChildren.create({
555
- path: { document_id: docToken, block_id: blockId },
556
- data: {
557
- children: [
558
- {
559
- block_type: 31,
560
- table: {
561
- property: {
562
- row_size: rowSize,
563
- column_size: columnSize,
564
- ...(columnWidth && columnWidth.length > 0 ? { column_width: columnWidth } : {}),
565
- },
566
- },
567
- },
568
- ],
569
- },
570
- });
571
-
572
- if (res.code !== 0) {
573
- throw new Error(res.msg);
574
- }
575
-
576
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK return type
577
- const tableBlock = (res.data?.children as any[] | undefined)?.find((b) => b.block_type === 31);
578
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK return shape may vary by version
579
- const cells = (tableBlock?.children as any[] | undefined) ?? [];
580
-
581
- return {
582
- success: true,
583
- table_block_id: tableBlock?.block_id,
584
- row_size: rowSize,
585
- column_size: columnSize,
586
- // row-major cell ids, if API returns them directly
587
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK return type
588
- table_cell_block_ids: cells.map((c: any) => c.block_id).filter(Boolean),
589
- raw_children_count: res.data?.children?.length ?? 0,
590
- };
591
- }
592
-
593
- async function writeTableCells(
594
- client: Lark.Client,
595
- docToken: string,
596
- tableBlockId: string,
597
- values: string[][],
598
- ) {
599
- if (!values.length || !values[0]?.length) {
600
- throw new Error("values must be a non-empty 2D array");
601
- }
602
-
603
- const tableRes = await client.docx.documentBlock.get({
604
- path: { document_id: docToken, block_id: tableBlockId },
605
- });
606
- if (tableRes.code !== 0) {
607
- throw new Error(tableRes.msg);
608
- }
609
-
610
- const tableBlock = tableRes.data?.block;
611
- if (tableBlock?.block_type !== 31) {
612
- throw new Error("table_block_id is not a table block");
613
- }
614
-
615
- // SDK types are loose here across versions
616
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK block payload
617
- const tableData = (tableBlock as any).table;
618
- const rows = tableData?.property?.row_size as number | undefined;
619
- const cols = tableData?.property?.column_size as number | undefined;
620
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK block payload
621
- const cellIds = (tableData?.cells as any[] | undefined) ?? [];
622
-
623
- if (!rows || !cols || !cellIds.length) {
624
- throw new Error(
625
- "Table cell IDs unavailable from table block. Use list_blocks/get_block and pass explicit cell block IDs if needed.",
626
- );
627
- }
628
-
629
- const writeRows = Math.min(values.length, rows);
630
- let written = 0;
631
-
632
- for (let r = 0; r < writeRows; r++) {
633
- const rowValues = values[r] ?? [];
634
- const writeCols = Math.min(rowValues.length, cols);
635
-
636
- for (let c = 0; c < writeCols; c++) {
637
- const cellId = cellIds[r * cols + c];
638
- if (!cellId) continue;
639
-
640
- // table cell is a container block: clear existing children, then create text child blocks
641
- const childrenRes = await client.docx.documentBlockChildren.get({
642
- path: { document_id: docToken, block_id: cellId },
643
- });
644
- if (childrenRes.code !== 0) {
645
- throw new Error(childrenRes.msg);
646
- }
647
-
648
- const existingChildren = childrenRes.data?.items ?? [];
649
- if (existingChildren.length > 0) {
650
- const delRes = await client.docx.documentBlockChildren.batchDelete({
651
- path: { document_id: docToken, block_id: cellId },
652
- data: { start_index: 0, end_index: existingChildren.length },
653
- });
654
- if (delRes.code !== 0) {
655
- throw new Error(delRes.msg);
656
- }
657
- }
658
-
659
- const text = rowValues[c] ?? "";
660
- const converted = await convertMarkdown(client, text);
661
- const sorted = sortBlocksByFirstLevel(converted.blocks, converted.firstLevelBlockIds);
662
-
663
- if (sorted.length > 0) {
664
- await insertBlocks(client, docToken, sorted, cellId);
665
- }
666
-
667
- written++;
668
- }
669
- }
670
-
671
- return {
672
- success: true,
673
- table_block_id: tableBlockId,
674
- cells_written: written,
675
- table_size: { rows, cols },
676
- };
677
- }
678
-
679
- async function createTableWithValues(
680
- client: Lark.Client,
681
- docToken: string,
682
- rowSize: number,
683
- columnSize: number,
684
- values: string[][],
685
- parentBlockId?: string,
686
- columnWidth?: number[],
687
- ) {
688
- const created = await createTable(
689
- client,
690
- docToken,
691
- rowSize,
692
- columnSize,
693
- parentBlockId,
694
- columnWidth,
695
- );
696
-
697
- const tableBlockId = created.table_block_id;
698
- if (!tableBlockId) {
699
- throw new Error("create_table succeeded but table_block_id is missing");
700
- }
701
-
702
- const written = await writeTableCells(client, docToken, tableBlockId, values);
703
- return {
704
- success: true,
705
- table_block_id: tableBlockId,
706
- row_size: rowSize,
707
- column_size: columnSize,
708
- cells_written: written.cells_written,
709
- };
710
- }
711
-
712
- async function updateBlock(
713
- client: Lark.Client,
714
- docToken: string,
715
- blockId: string,
716
- content: string,
717
- ) {
718
- const blockInfo = await client.docx.documentBlock.get({
719
- path: { document_id: docToken, block_id: blockId },
720
- });
721
- if (blockInfo.code !== 0) {
722
- throw new Error(blockInfo.msg);
723
- }
724
-
725
- const res = await client.docx.documentBlock.patch({
726
- path: { document_id: docToken, block_id: blockId },
727
- data: {
728
- update_text_elements: {
729
- elements: [{ text_run: { content } }],
730
- },
731
- },
732
- });
733
- if (res.code !== 0) {
734
- throw new Error(res.msg);
735
- }
736
-
737
- return { success: true, block_id: blockId };
738
- }
739
-
740
- async function deleteBlock(client: Lark.Client, docToken: string, blockId: string) {
741
- const blockInfo = await client.docx.documentBlock.get({
742
- path: { document_id: docToken, block_id: blockId },
743
- });
744
- if (blockInfo.code !== 0) {
745
- throw new Error(blockInfo.msg);
746
- }
747
-
748
- const parentId = blockInfo.data?.block?.parent_id ?? docToken;
749
-
750
- const children = await client.docx.documentBlockChildren.get({
751
- path: { document_id: docToken, block_id: parentId },
752
- });
753
- if (children.code !== 0) {
754
- throw new Error(children.msg);
755
- }
756
-
757
- const items = children.data?.items ?? [];
758
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK block type
759
- const index = items.findIndex((item: any) => item.block_id === blockId);
760
- if (index === -1) {
761
- throw new Error("Block not found");
762
- }
763
-
764
- const res = await client.docx.documentBlockChildren.batchDelete({
765
- path: { document_id: docToken, block_id: parentId },
766
- data: { start_index: index, end_index: index + 1 },
767
- });
768
- if (res.code !== 0) {
769
- throw new Error(res.msg);
770
- }
771
-
772
- return { success: true, deleted_block_id: blockId };
773
- }
774
-
775
- async function listBlocks(client: Lark.Client, docToken: string) {
776
- const res = await client.docx.documentBlock.list({
777
- path: { document_id: docToken },
778
- });
779
- if (res.code !== 0) {
780
- throw new Error(res.msg);
781
- }
782
-
783
- return {
784
- blocks: res.data?.items ?? [],
785
- };
786
- }
787
-
788
- async function getBlock(client: Lark.Client, docToken: string, blockId: string) {
789
- const res = await client.docx.documentBlock.get({
790
- path: { document_id: docToken, block_id: blockId },
791
- });
792
- if (res.code !== 0) {
793
- throw new Error(res.msg);
794
- }
795
-
796
- return {
797
- block: res.data?.block,
798
- };
799
- }
800
-
801
- async function listAppScopes(client: Lark.Client) {
802
- const res = await client.application.scope.list({});
803
- if (res.code !== 0) {
804
- throw new Error(res.msg);
805
- }
806
-
807
- const scopes = res.data?.scopes ?? [];
808
- const granted = scopes.filter((s) => s.grant_status === 1);
809
- const pending = scopes.filter((s) => s.grant_status !== 1);
810
-
811
- return {
812
- granted: granted.map((s) => ({ name: s.scope_name, type: s.scope_type })),
813
- pending: pending.map((s) => ({ name: s.scope_name, type: s.scope_type })),
814
- summary: `${granted.length} granted, ${pending.length} pending`,
815
- };
816
- }
817
-
818
- // ============ Tool Registration ============
819
-
820
- export function registerFeishuDocTools(api: FasedAgentPluginApi) {
821
- if (!api.config) {
822
- api.logger.debug?.("feishu_doc: No config available, skipping doc tools");
823
- return;
824
- }
825
-
826
- // Check if any account is configured
827
- const accounts = listEnabledFeishuAccounts(api.config);
828
- if (accounts.length === 0) {
829
- api.logger.debug?.("feishu_doc: No Feishu accounts configured, skipping doc tools");
830
- return;
831
- }
832
-
833
- // Register if enabled on any account; account routing is resolved per execution.
834
- const toolsCfg = resolveAnyEnabledFeishuToolsConfig(accounts);
835
-
836
- const registered: string[] = [];
837
- type FeishuDocExecuteParams = FeishuDocParams & { accountId?: string };
838
-
839
- const getClient = (params: { accountId?: string } | undefined, defaultAccountId?: string) =>
840
- createFeishuToolClient({ api, executeParams: params, defaultAccountId });
841
-
842
- const getMediaMaxBytes = (
843
- params: { accountId?: string } | undefined,
844
- defaultAccountId?: string,
845
- ) =>
846
- (resolveFeishuToolAccount({ api, executeParams: params, defaultAccountId }).config
847
- ?.mediaMaxMb ?? 30) *
848
- 1024 *
849
- 1024;
850
-
851
- // Main document tool with action-based dispatch
852
- if (toolsCfg.doc) {
853
- api.registerTool(
854
- (ctx) => {
855
- const defaultAccountId = ctx.agentAccountId;
856
- return {
857
- name: "feishu_doc",
858
- label: "Feishu Doc",
859
- description:
860
- "Feishu document operations. Actions: read, write, append, create, list_blocks, get_block, update_block, delete_block, create_table, write_table_cells, create_table_with_values, upload_image, upload_file",
861
- parameters: FeishuDocSchema,
862
- async execute(_toolCallId, params) {
863
- const p = params as FeishuDocExecuteParams;
864
- try {
865
- const client = getClient(p, defaultAccountId);
866
- switch (p.action) {
867
- case "read":
868
- return json(await readDoc(client, p.doc_token));
869
- case "write":
870
- return json(
871
- await writeDoc(
872
- client,
873
- p.doc_token,
874
- p.content,
875
- getMediaMaxBytes(p, defaultAccountId),
876
- ),
877
- );
878
- case "append":
879
- return json(
880
- await appendDoc(
881
- client,
882
- p.doc_token,
883
- p.content,
884
- getMediaMaxBytes(p, defaultAccountId),
885
- ),
886
- );
887
- case "create":
888
- return json(
889
- await createDoc(
890
- client,
891
- p.title,
892
- p.folder_token,
893
- p.owner_open_id,
894
- p.owner_perm_type,
895
- ),
896
- );
897
- case "list_blocks":
898
- return json(await listBlocks(client, p.doc_token));
899
- case "get_block":
900
- return json(await getBlock(client, p.doc_token, p.block_id));
901
- case "update_block":
902
- return json(await updateBlock(client, p.doc_token, p.block_id, p.content));
903
- case "delete_block":
904
- return json(await deleteBlock(client, p.doc_token, p.block_id));
905
- case "create_table":
906
- return json(
907
- await createTable(
908
- client,
909
- p.doc_token,
910
- p.row_size,
911
- p.column_size,
912
- p.parent_block_id,
913
- p.column_width,
914
- ),
915
- );
916
- case "write_table_cells":
917
- return json(
918
- await writeTableCells(client, p.doc_token, p.table_block_id, p.values),
919
- );
920
- case "create_table_with_values":
921
- return json(
922
- await createTableWithValues(
923
- client,
924
- p.doc_token,
925
- p.row_size,
926
- p.column_size,
927
- p.values,
928
- p.parent_block_id,
929
- p.column_width,
930
- ),
931
- );
932
- case "upload_image":
933
- return json(
934
- await uploadImageBlock(
935
- client,
936
- p.doc_token,
937
- getMediaMaxBytes(p, defaultAccountId),
938
- p.url,
939
- p.file_path,
940
- p.parent_block_id,
941
- p.filename,
942
- p.index,
943
- ),
944
- );
945
- case "upload_file":
946
- return json(
947
- await uploadFileBlock(
948
- client,
949
- p.doc_token,
950
- getMediaMaxBytes(p, defaultAccountId),
951
- p.url,
952
- p.file_path,
953
- p.parent_block_id,
954
- p.filename,
955
- ),
956
- );
957
- default:
958
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- exhaustive check fallback
959
- return json({ error: `Unknown action: ${(p as any).action}` });
960
- }
961
- } catch (err) {
962
- return json({ error: err instanceof Error ? err.message : String(err) });
963
- }
964
- },
965
- };
966
- },
967
- { name: "feishu_doc" },
968
- );
969
- registered.push("feishu_doc");
970
- }
971
-
972
- // Keep feishu_app_scopes as independent tool
973
- if (toolsCfg.scopes) {
974
- api.registerTool(
975
- (ctx) => ({
976
- name: "feishu_app_scopes",
977
- label: "Feishu App Scopes",
978
- description:
979
- "List current app permissions (scopes). Use to debug permission issues or check available capabilities.",
980
- parameters: Type.Object({}),
981
- async execute() {
982
- try {
983
- const result = await listAppScopes(getClient(undefined, ctx.agentAccountId));
984
- return json(result);
985
- } catch (err) {
986
- return json({ error: err instanceof Error ? err.message : String(err) });
987
- }
988
- },
989
- }),
990
- { name: "feishu_app_scopes" },
991
- );
992
- registered.push("feishu_app_scopes");
993
- }
994
-
995
- if (registered.length > 0) {
996
- api.logger.debug?.(`feishu_doc: Registered ${registered.join(", ")}`);
997
- }
998
- }