weaver-work-cli 0.1.8 → 0.1.20

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 (263) hide show
  1. package/README.md +11 -11
  2. package/dist/cmd/action.js +76 -0
  3. package/dist/cmd/index.js +2 -0
  4. package/dist/cmd/refs/index.js +103 -0
  5. package/dist/cmd/register.js +31 -0
  6. package/dist/cmd/root.js +1 -0
  7. package/dist/cmd/setup/index.js +25 -5
  8. package/dist/cmd/skills/index.js +37 -5
  9. package/dist/core/connector.js +54 -0
  10. package/dist/core/context.js +2 -1
  11. package/dist/core/input.js +48 -0
  12. package/dist/core/output.js +298 -3
  13. package/dist/core/refs.js +432 -0
  14. package/dist/core/state.js +26 -0
  15. package/dist/internal/e10/auth/commands.js +2 -2
  16. package/dist/internal/e10/auth/session.js +14 -0
  17. package/dist/internal/e10/auth/xiaoe.js +116 -51
  18. package/dist/internal/e10/request-runtime.js +5 -0
  19. package/dist/internal/skills/install.js +148 -28
  20. package/dist/internal/skills/reader.js +12 -9
  21. package/dist/shortcuts/blog/continuation.js +74 -0
  22. package/dist/shortcuts/blog/errors.js +60 -0
  23. package/dist/shortcuts/blog/host.js +175 -0
  24. package/dist/shortcuts/blog/index.js +324 -0
  25. package/dist/shortcuts/blog/manifest.js +415 -0
  26. package/dist/shortcuts/blog/operations/comment.js +125 -0
  27. package/dist/shortcuts/blog/operations/comments.js +31 -0
  28. package/dist/shortcuts/blog/operations/edit.js +131 -0
  29. package/dist/shortcuts/blog/operations/export.js +46 -0
  30. package/dist/shortcuts/blog/operations/like.js +80 -0
  31. package/dist/shortcuts/blog/operations/publish.js +157 -0
  32. package/dist/shortcuts/blog/operations/query-user.js +58 -0
  33. package/dist/shortcuts/blog/operations/query.js +96 -0
  34. package/dist/shortcuts/blog/operations/registry.js +54 -0
  35. package/dist/shortcuts/blog/operations/remind.js +176 -0
  36. package/dist/shortcuts/blog/operations/shared.js +330 -0
  37. package/dist/shortcuts/blog/operations/types.js +16 -0
  38. package/dist/shortcuts/blog/operations.js +9 -0
  39. package/dist/shortcuts/caizhilian/continuation.js +70 -0
  40. package/dist/shortcuts/caizhilian/errors.js +53 -0
  41. package/dist/shortcuts/caizhilian/host.js +190 -0
  42. package/dist/shortcuts/caizhilian/index.js +186 -0
  43. package/dist/shortcuts/caizhilian/manifest.js +42 -0
  44. package/dist/shortcuts/caizhilian/operations/api.js +801 -0
  45. package/dist/shortcuts/caizhilian/operations/confirm.js +75 -0
  46. package/dist/shortcuts/caizhilian/operations/files.js +306 -0
  47. package/dist/shortcuts/caizhilian/operations/form.js +170 -0
  48. package/dist/shortcuts/caizhilian/operations/handlers/collab.js +611 -0
  49. package/dist/shortcuts/caizhilian/operations/handlers/document.js +890 -0
  50. package/dist/shortcuts/caizhilian/operations/handlers/file-collection.js +346 -0
  51. package/dist/shortcuts/caizhilian/operations/handlers/file-upload.js +192 -0
  52. package/dist/shortcuts/caizhilian/operations/handlers/fileops.js +570 -0
  53. package/dist/shortcuts/caizhilian/operations/handlers/folder.js +969 -0
  54. package/dist/shortcuts/caizhilian/operations/handlers/search.js +318 -0
  55. package/dist/shortcuts/caizhilian/operations/handlers/setting-attach.js +294 -0
  56. package/dist/shortcuts/caizhilian/operations/handlers/setting-backup.js +423 -0
  57. package/dist/shortcuts/caizhilian/operations/handlers/setting-encrypt.js +238 -0
  58. package/dist/shortcuts/caizhilian/operations/handlers/setting-storage.js +364 -0
  59. package/dist/shortcuts/caizhilian/operations/handlers/system.js +106 -0
  60. package/dist/shortcuts/caizhilian/operations/registry.js +151 -0
  61. package/dist/shortcuts/caizhilian/operations/shared.js +408 -0
  62. package/dist/shortcuts/caizhilian/operations/specs.js +623 -0
  63. package/dist/shortcuts/caizhilian/operations/types.js +57 -0
  64. package/dist/shortcuts/caizhilian/operations.js +21 -0
  65. package/dist/shortcuts/contract/continuation.js +70 -0
  66. package/dist/shortcuts/contract/errors.js +62 -0
  67. package/dist/shortcuts/contract/host.js +205 -0
  68. package/dist/shortcuts/contract/index.js +318 -0
  69. package/dist/shortcuts/contract/manifest.js +596 -0
  70. package/dist/shortcuts/contract/operations/clause-reference.js +78 -0
  71. package/dist/shortcuts/contract/operations/form-info.js +371 -0
  72. package/dist/shortcuts/contract/operations/read.js +118 -0
  73. package/dist/shortcuts/contract/operations/registry.js +43 -0
  74. package/dist/shortcuts/contract/operations/request.js +300 -0
  75. package/dist/shortcuts/contract/operations/shared.js +133 -0
  76. package/dist/shortcuts/contract/operations/subject.js +157 -0
  77. package/dist/shortcuts/contract/operations/template.js +81 -0
  78. package/dist/shortcuts/contract/operations/types.js +7 -0
  79. package/dist/shortcuts/contract/operations/workflow.js +52 -0
  80. package/dist/shortcuts/contract/operations/write.js +131 -0
  81. package/dist/shortcuts/contract/operations.js +8 -0
  82. package/dist/shortcuts/hrm/index.js +83 -2
  83. package/dist/shortcuts/hrm/operations/employee.js +160 -30
  84. package/dist/shortcuts/hrm/operations/org.js +17 -3
  85. package/dist/shortcuts/hrm/operations/position.js +18 -2
  86. package/dist/shortcuts/index.js +5 -1
  87. package/dist/shortcuts/invoice/index.js +74 -36
  88. package/dist/shortcuts/invoice/manifest.js +674 -4
  89. package/dist/shortcuts/invoice/markdown.js +172 -0
  90. package/dist/shortcuts/invoice/operations/add.js +3 -3
  91. package/dist/shortcuts/invoice/operations/delete.js +2 -2
  92. package/dist/shortcuts/invoice/operations/get.js +10 -2
  93. package/dist/shortcuts/invoice/operations/import.js +2 -2
  94. package/dist/shortcuts/invoice/operations/registry.js +8 -1
  95. package/dist/shortcuts/invoice/operations/reim-auto.js +719 -0
  96. package/dist/shortcuts/invoice/operations/reim.js +85 -2
  97. package/dist/shortcuts/invoice/operations/shared.js +75 -8
  98. package/dist/shortcuts/invoice/operations/update.js +2 -2
  99. package/dist/shortcuts/invoice/operations/upload-and-validate.js +93 -0
  100. package/dist/shortcuts/invoice/operations/validate-preview.js +2 -1
  101. package/dist/shortcuts/invoice/projection.js +328 -0
  102. package/dist/shortcuts/meeting/host.js +18 -1
  103. package/dist/shortcuts/meeting/manifest.js +30 -18
  104. package/dist/shortcuts/meeting/operations/eb/catalog.js +390 -0
  105. package/dist/shortcuts/meeting/operations/eb/conflict.js +389 -0
  106. package/dist/shortcuts/meeting/operations/eb/factory.js +389 -0
  107. package/dist/shortcuts/meeting/operations/eb/index.js +22 -0
  108. package/dist/shortcuts/meeting/operations/env.js +8 -1
  109. package/dist/shortcuts/meeting/operations/registry.js +2 -0
  110. package/dist/shortcuts/meeting/operations/signs.js +216 -2
  111. package/dist/shortcuts/workflow/endpoints.js +2 -1
  112. package/dist/shortcuts/workflow/errors.js +8 -2
  113. package/dist/shortcuts/workflow/host.js +6 -3
  114. package/dist/shortcuts/workflow/operations/form.js +931 -589
  115. package/dist/shortcuts/workflow/operations/read.js +128 -205
  116. package/dist/shortcuts/workflow/operations/shared.js +21 -0
  117. package/dist/shortcuts/workflow/operations/write.js +750 -52
  118. package/dist/shortcuts/workflow/operations.js +36 -10
  119. package/docs/_catalog.md +6 -0
  120. package/docs/agent-invoice.md +5 -1
  121. package/docs/agent-io-channel-design.md +468 -0
  122. package/docs/agent-skill-install.md +9 -4
  123. package/docs/blog.md +123 -0
  124. package/docs/caizhilian.md +304 -0
  125. package/docs/contract.md +91 -0
  126. package/docs/e10-auth.md +2 -2
  127. package/docs/hrm.md +19 -9
  128. package/docs/invoice.md +5 -1
  129. package/docs/meeting.md +1 -1
  130. package/docs/operation-manual.md +15 -3
  131. package/docs/skill-review-2026-09-19.md +2 -2
  132. package/package.json +2 -2
  133. package/scripts/package-skill.mjs +32 -20
  134. package/skill-template/business-info.json +113 -14
  135. package/skill-template/domains/blog.md +24 -0
  136. package/skill-template/domains/caizhilian.md +22 -0
  137. package/skill-template/domains/jinchengda.md +23 -0
  138. package/skill-template/domains/meeting.md +1 -1
  139. package/skill-template/domains/workflow.md +1 -1
  140. package/skill-template/skill-template.md +1 -1
  141. package/skills/weaver-e10-blog/SKILL.md +111 -0
  142. package/skills/weaver-e10-blog/references/blog-comment.md +51 -0
  143. package/skills/weaver-e10-blog/references/blog-comments.md +39 -0
  144. package/skills/weaver-e10-blog/references/blog-default-permission.md +38 -0
  145. package/skills/weaver-e10-blog/references/blog-disabled-capabilities.md +11 -0
  146. package/skills/weaver-e10-blog/references/blog-edit.md +51 -0
  147. package/skills/weaver-e10-blog/references/blog-export.md +42 -0
  148. package/skills/weaver-e10-blog/references/blog-like.md +50 -0
  149. package/skills/weaver-e10-blog/references/blog-publish.md +52 -0
  150. package/skills/weaver-e10-blog/references/blog-query-user.md +45 -0
  151. package/skills/weaver-e10-blog/references/blog-query.md +49 -0
  152. package/skills/weaver-e10-blog/references/blog-remind.md +51 -0
  153. package/skills/weaver-e10-blog/references/blog-resolve-users.md +38 -0
  154. package/skills/weaver-e10-blog/references/source-manifest.json +1540 -0
  155. package/skills/weaver-e10-caizhilian/SKILL.md +216 -0
  156. package/skills/weaver-e10-caizhilian/references/agent-entry.md +106 -0
  157. package/skills/weaver-e10-caizhilian/references/collaboration.md +112 -0
  158. package/skills/weaver-e10-caizhilian/references/document.md +111 -0
  159. package/skills/weaver-e10-caizhilian/references/file-collection.md +136 -0
  160. package/skills/weaver-e10-caizhilian/references/file-upload.md +309 -0
  161. package/skills/weaver-e10-caizhilian/references/files.md +120 -0
  162. package/skills/weaver-e10-caizhilian/references/folder.md +118 -0
  163. package/skills/weaver-e10-caizhilian/references/org-browser.md +104 -0
  164. package/skills/weaver-e10-caizhilian/references/setting-attach.md +410 -0
  165. package/skills/weaver-e10-caizhilian/references/setting-backup.md +372 -0
  166. package/skills/weaver-e10-caizhilian/references/setting-encrypt.md +399 -0
  167. package/skills/weaver-e10-caizhilian/references/setting-storage.md +603 -0
  168. package/skills/weaver-e10-caizhilian/references/source-manifest.json +5079 -0
  169. package/skills/weaver-e10-caizhilian/references/withheld.md +93 -0
  170. package/skills/weaver-e10-caizhilian/references/write-protocol.md +120 -0
  171. package/skills/weaver-e10-calendar/SKILL.md +4 -3
  172. package/skills/weaver-e10-ebuilder-form/SKILL.md +4 -9
  173. package/skills/weaver-e10-ebuilder-form/references/context-routing.md +0 -9
  174. package/skills/weaver-e10-ebuilder-form/references/custom-api.md +1 -5
  175. package/skills/weaver-e10-ebuilder-form/references/fields-browser.md +1 -5
  176. package/skills/weaver-e10-ebuilder-form/references/list-nlist.md +1 -5
  177. package/skills/weaver-e10-ebuilder-form/references/openapi-read.md +1 -5
  178. package/skills/weaver-e10-ebuilder-form/references/openapi-write.md +1 -5
  179. package/skills/weaver-e10-esb/SKILL.md +2 -2
  180. package/skills/weaver-e10-esb/references/source-manifest.json +9 -9
  181. package/skills/weaver-e10-hrm/SKILL.md +54 -6
  182. package/skills/weaver-e10-hrm/references/employee.md +89 -15
  183. package/skills/weaver-e10-hrm/references/org.md +7 -4
  184. package/skills/weaver-e10-hrm/references/position.md +14 -7
  185. package/skills/weaver-e10-hrm/references/source-manifest.json +1 -1
  186. package/skills/weaver-e10-jinchengda/SKILL.md +166 -0
  187. package/skills/weaver-e10-jinchengda/references/contract-agent-entry.md +82 -0
  188. package/skills/weaver-e10-jinchengda/references/contract-clause-recommender.md +69 -0
  189. package/skills/weaver-e10-jinchengda/references/contract-clause-reference-save.md +78 -0
  190. package/skills/weaver-e10-jinchengda/references/contract-disabled-capabilities.md +27 -0
  191. package/skills/weaver-e10-jinchengda/references/contract-request-status.md +71 -0
  192. package/skills/weaver-e10-jinchengda/references/contract-request-write.md +94 -0
  193. package/skills/weaver-e10-jinchengda/references/contract-signing-subjects.md +89 -0
  194. package/skills/weaver-e10-jinchengda/references/contract-subject-create.md +77 -0
  195. package/skills/weaver-e10-jinchengda/references/contract-template-selector.md +66 -0
  196. package/skills/weaver-e10-jinchengda/references/contract-workflow-discovery.md +94 -0
  197. package/skills/weaver-e10-jinchengda/references/source-manifest.json +259 -0
  198. package/skills/weaver-e10-jiuchuanhui/SKILL.md +5 -11
  199. package/skills/weaver-e10-jucailin/SKILL.md +4 -3
  200. package/skills/weaver-e10-mail/SKILL.md +4 -11
  201. package/skills/weaver-e10-meeting/SKILL.md +66 -32
  202. package/skills/weaver-e10-meeting/references/meeting-change.md +5 -2
  203. package/skills/weaver-e10-meeting/references/meeting-create.md +2 -1
  204. package/skills/weaver-e10-meeting/references/meeting-eb.md +114 -0
  205. package/skills/weaver-e10-meeting/references/meeting-lifecycle.md +9 -8
  206. package/skills/weaver-e10-meeting/references/meeting-receipts-signs.md +35 -7
  207. package/skills/weaver-e10-meeting/references/meeting-update.md +1 -1
  208. package/skills/weaver-e10-okr/SKILL.md +6 -11
  209. package/skills/weaver-e10-okr/references/source-manifest.json +3 -3
  210. package/skills/weaver-e10-plan/SKILL.md +3 -3
  211. package/skills/weaver-e10-plan/references/plan-collect-and-compose.md +1 -1
  212. package/skills/weaver-e10-plan/references/plan-overview-and-routing.md +1 -1
  213. package/skills/weaver-e10-plan/references/source-manifest.json +13 -13
  214. package/skills/weaver-e10-qiyecheng/SKILL.md +4 -10
  215. package/skills/weaver-e10-qiyecheng/references/source-manifest.json +9 -9
  216. package/skills/weaver-e10-shared/SKILL.md +36 -25
  217. package/skills/weaver-e10-shared/references/e10-auth-and-session.md +10 -31
  218. package/skills/weaver-e10-shared/references/json-output-contract.md +20 -11
  219. package/skills/weaver-e10-shijingran/SKILL.md +2 -1
  220. package/skills/weaver-e10-shijingran/references/source-manifest.json +9 -9
  221. package/skills/weaver-e10-skill-maker/SKILL.md +4 -10
  222. package/skills/weaver-e10-skill-maker/references/business-skill-generation.md +5 -3
  223. package/skills/weaver-e10-skill-maker/references/module-cli-generation.md +12 -27
  224. package/skills/weaver-e10-skill-maker/references/module-gap-audit-and-repair.md +17 -5
  225. package/skills/weaver-e10-skill-maker/references/source-update-detection.md +4 -27
  226. package/skills/weaver-e10-wenshuding/SKILL.md +5 -16
  227. package/skills/weaver-e10-wenshuding/references/source-manifest.json +7 -7
  228. package/skills/weaver-e10-workflow/SKILL.md +32 -37
  229. package/skills/weaver-e10-workflow/references/source-manifest.json +39 -75
  230. package/skills/weaver-e10-workflow/references/workflow-batch.md +90 -29
  231. package/skills/weaver-e10-workflow/references/workflow-create.md +79 -109
  232. package/skills/weaver-e10-workflow/references/workflow-memory.md +19 -9
  233. package/skills/weaver-e10-workflow/references/workflow-operate.md +2 -2
  234. package/skills/weaver-e10-workflow/references/workflow-query.md +38 -20
  235. package/skills/weaver-e10-workflow/references/workflow-share.md +18 -2
  236. package/skills/weaver-e10-workflow/references/workflow-view.md +3 -4
  237. package/skills/weaver-e10-yepiaotong/SKILL.md +75 -39
  238. package/skills/weaver-e10-yepiaotong/references/invoice-detail.md +22 -1
  239. package/skills/weaver-e10-yepiaotong/references/invoice-enterprise-list.md +3 -1
  240. package/skills/weaver-e10-yepiaotong/references/invoice-output-view.md +135 -0
  241. package/skills/weaver-e10-yepiaotong/references/invoice-personal-list.md +15 -3
  242. package/skills/weaver-e10-yepiaotong/references/invoice-reim.md +1 -1
  243. package/skills/weaver-e10-yepiaotong/references/invoice-summarize.py +217 -0
  244. package/skills/weaver-e10-yepiaotong/references/invoice-upload-and-validate.md +88 -0
  245. package/skills/weaver-e10-yepiaotong/references/invoice-validation-preview.md +7 -2
  246. package/skills/weaver-e10-yepiaotong/references/reim-api-reference.md +89 -9
  247. package/skills/weaver-e10-yepiaotong/references/reim-examples.md +1 -0
  248. package/skills/weaver-e10-yepiaotong/references/reim-expense-subject-rules.md +70 -49
  249. package/skills/weaver-e10-yepiaotong/references/reim-fast-path.md +119 -0
  250. package/skills/weaver-e10-yepiaotong/references/reim-form-fill-rules.md +55 -36
  251. package/skills/weaver-e10-yepiaotong/references/reim-matching-rules.md +4 -0
  252. package/skills/weaver-e10-yepiaotong/references/reim-phone-subsidy-rules.md +149 -0
  253. package/skills/weaver-e10-yepiaotong/references/reim-workflow.md +125 -5
  254. package/skills/weaver-e10-yepiaotong/references/source-manifest.json +430 -63
  255. package/skills/weaver-e10-yimiaoban/SKILL.md +4 -12
  256. package/skills/weaver-e10-yimiaoban/references/source-manifest.json +3 -3
  257. package/skills/weaver-e10-ziguanjia/SKILL.md +2 -1
  258. package/skills/weaver-e10-ziguanjia/references/ziguanjia-agent-entry.md +3 -3
  259. package/tools/weaver-skill-detector-1.0.13/SKILL.md +9 -9
  260. package/tools/weaver-skill-detector-1.0.13/references/checklist.md +3 -3
  261. package/tools/weaver-skill-detector-1.0.13/scripts/__pycache__/validate_skill.cpython-313.pyc +0 -0
  262. package/tools/weaver-skill-detector-1.0.13/scripts/validate_skill.py +98 -91
  263. package/scripts/sync-connector-skills.mjs +0 -172
@@ -0,0 +1,890 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { basename, extname } from 'node:path';
3
+ import { CaizhilianCliError, caizhilianValidationError } from '../../errors.js';
4
+ import { asList, asRecord, buildCreateDocumentBody, fetchCurrentUserInfo, fetchDocBaseInfo, nestedRecord, runCreatePreamble, updateDocBasicField, } from '../api.js';
5
+ import { issueWriteLease } from '../confirm.js';
6
+ import { CONTENT_TYPE_EXT_MAP, EXT_TO_CONTENT_TYPE, OFFICE_ALLOWED_EXT, assertFileUnchanged, readFileMetadata, uploadFileToServer, } from '../files.js';
7
+ import { assertEnum, fingerprint, optionalId, optionalIdList, optionalList, optionalRawText, requireId, requireText, } from '../shared.js';
8
+ import { caizhilianPrepared, caizhilianSuccess } from '../types.js';
9
+ /**
10
+ * 文档域操作(1:1 对应源 scripts/document_plugins.py 的文档创建/修改部分
11
+ * + doc-business-operations/fetch_doc_base_info_by_docid.json 响应模板
12
+ * + search_operations.json 的 fetch_current_user_info)。
13
+ *
14
+ * 写操作按 weaver-work-cli 规范拆成 prepare(只读回查 + 组装请求体 + 签发票据)
15
+ * 与 apply(真正产生副作用),业务请求体与源保持逐字段一致。
16
+ */
17
+ /** 源 folder_type 取值(build_create_document_body 直接 str() 透传)。 */
18
+ const FOLDER_TYPES = ['company', 'privy', 'sys'];
19
+ /** 源 document_plugins.MARKDOWN_ALLOWED_EXT。 */
20
+ const MARKDOWN_ALLOWED_EXT = ['.md', '.markdown'];
21
+ /** 源 _execute_create_and_extract 的创建接口。 */
22
+ const CREATE_DETAIL_PATH = '/api/doc/document/createDetail';
23
+ /** 源 CreateDocumentByUploadPlugin.MAX_UPLOAD_WORKERS。 */
24
+ const MAX_UPLOAD_WORKERS = 8;
25
+ /** 源 UpdateDocFieldPlugin._FIELD_MAP:field -> (filed, arg_key, allow_empty)。 */
26
+ const FIELD_MAP = {
27
+ name: { filed: 'name', argKey: 'name', allowEmpty: false },
28
+ keywords: { filed: 'keywords', argKey: 'keywords', allowEmpty: true },
29
+ abstract: { filed: 'abstractText', argKey: 'abstract_text', allowEmpty: true },
30
+ auto_invalid_date: { filed: 'autoInvalidateDate', argKey: 'auto_invalid_date', allowEmpty: true },
31
+ };
32
+ /** 源 sorted(_FIELD_MAP) 的报错文案顺序。 */
33
+ const FIELD_MAP_KEYS_SORTED = Object.keys(FIELD_MAP).sort();
34
+ // ── caizhilian.document.get ───────────────────────────────
35
+ async function getDocument(args, env) {
36
+ const action = 'caizhilian.document.get';
37
+ const docId = requireId(args.doc_id, 'doc_id');
38
+ const info = await fetchDocBaseInfo(env, docId, action);
39
+ return caizhilianSuccess(action, info);
40
+ }
41
+ // ── caizhilian.user.current ───────────────────────────────
42
+ async function currentUser(_args, env) {
43
+ const action = 'caizhilian.user.current';
44
+ return caizhilianSuccess(action, await fetchCurrentUserInfo(env, action));
45
+ }
46
+ // ── 文档创建:入参归一化与前置 ────────────────────────────
47
+ /**
48
+ * 源 validate_required_params + build_create_document_body 的取值语义:
49
+ * 必填项按源顺序校验,可选项仅在“真值”时写入(空串/空数组等同未传)。
50
+ */
51
+ function normalizeCreateArgs(args, required) {
52
+ const out = {};
53
+ for (const key of required) {
54
+ switch (key) {
55
+ case 'name':
56
+ out.name = requireText(args.name, 'name');
57
+ break;
58
+ case 'folder_id':
59
+ out.folder_id = requireId(args.folder_id, 'folder_id');
60
+ break;
61
+ case 'folder_type':
62
+ out.folder_type = assertEnum(args.folder_type, FOLDER_TYPES, 'folder_type');
63
+ break;
64
+ case 'author_id':
65
+ out.author_id = requireId(args.author_id, 'author_id');
66
+ break;
67
+ case 'file_path':
68
+ out.file_path = requireText(args.file_path, 'file_path');
69
+ break;
70
+ case 'file_paths':
71
+ out.file_paths = args.file_paths;
72
+ break;
73
+ default:
74
+ if (args[key] === undefined || args[key] === null || args[key] === '') {
75
+ throw caizhilianValidationError('field_required', `缺少必填参数: ${key}`);
76
+ }
77
+ out[key] = args[key];
78
+ }
79
+ }
80
+ const summary = optionalRawText(args.summary);
81
+ if (summary !== undefined)
82
+ out.summary = summary;
83
+ const abstractText = optionalRawText(args.abstract_text);
84
+ if (abstractText !== undefined)
85
+ out.abstract_text = abstractText;
86
+ const keywords = optionalRawText(args.keywords);
87
+ if (keywords !== undefined)
88
+ out.keywords = keywords;
89
+ const autoInvalidateDate = optionalRawText(args.auto_invalidate_date);
90
+ if (autoInvalidateDate !== undefined)
91
+ out.auto_invalidate_date = autoInvalidateDate;
92
+ const coverPictureId = optionalId(args.cover_picture_id, 'cover_picture_id');
93
+ if (coverPictureId !== undefined)
94
+ out.cover_picture_id = coverPictureId;
95
+ if (args.doc_category !== undefined && args.doc_category !== null && args.doc_category !== '') {
96
+ const parsed = Number(args.doc_category);
97
+ if (!Number.isInteger(parsed)) {
98
+ throw caizhilianValidationError('doc_category_invalid', 'doc_category 必须是整数');
99
+ }
100
+ out.doc_category = parsed;
101
+ }
102
+ const tags = optionalList(args.tags, 'tags');
103
+ if (tags)
104
+ out.tags = tags;
105
+ const participantIds = optionalIdList(args.participant_ids, 'participant_ids');
106
+ if (participantIds)
107
+ out.participant_ids = participantIds;
108
+ const virtualFolderIds = optionalIdList(args.virtual_folder_ids, 'virtual_folder_ids');
109
+ if (virtualFolderIds)
110
+ out.virtual_folder_ids = virtualFolderIds;
111
+ const filePath = optionalRawText(args.file_path);
112
+ if (filePath !== undefined)
113
+ out.file_path = filePath;
114
+ return out;
115
+ }
116
+ /** 源「文件夹兜底结果被共享兜底覆盖」的可观测告警(1:1 保留源行为,但显式提示)。 */
117
+ const FOLDER_FALLBACK_KEYS = ['participant_ids', 'virtual_folder_ids', 'folder_default_share_new'];
118
+ function droppedFallbackWarnings(preamble) {
119
+ const dropped = FOLDER_FALLBACK_KEYS.filter((key) => preamble.folderFallbackArgs[key] !== undefined && preamble.args[key] === undefined);
120
+ if (!dropped.length)
121
+ return [];
122
+ return [
123
+ `源行为 1:1 保留:文件夹默认值兜底结果(${dropped.join('、')})被默认共享兜底覆盖,未写入创建请求体;`
124
+ + '如需回填请显式传入 participant_ids / virtual_folder_ids,或改用 caizhilian.document.create-by-upload',
125
+ ];
126
+ }
127
+ /** 创建类操作 prepare 的统一预览结构。 */
128
+ function createPreview(docType, body) {
129
+ const doc = asRecord(body.document);
130
+ return {
131
+ doc_type: docType,
132
+ content_type: doc.contentType ?? '',
133
+ name: doc.name ?? '',
134
+ folder_id: body.folderId ?? '',
135
+ folder_type: body.folderType ?? '',
136
+ author_id: asRecord(doc.author).id ?? '',
137
+ doc_status_name: doc.docStatusName ?? '',
138
+ content_length: typeof doc.summary === 'string' ? doc.summary.length : 0,
139
+ content_file_id: doc.content ?? '',
140
+ keywords: doc.keywords ?? '',
141
+ abstract_text: doc.abstractText ?? '',
142
+ auto_invalidate_date: doc.autoInvalidateDate ?? '',
143
+ doc_category: doc.docCategory ?? null,
144
+ cover_picture_id: doc.coverPictureId ?? '',
145
+ tags_count: asList(doc.tags).length,
146
+ participant_ids: asList(body.shareList).map((item) => String(asRecord(item).sid ?? '')),
147
+ virtual_folder_ids: asList(doc.virtualFolders).map((item) => String(asRecord(item).id ?? '')),
148
+ doc_share_list_count: asList(body.docShareList).length,
149
+ request: { method: 'POST', path: CREATE_DETAIL_PATH },
150
+ };
151
+ }
152
+ /** 源 _read_markdown_file:UTF-8 优先,解码失败回退 GBK。 */
153
+ async function readMarkdownFile(filePath) {
154
+ let buffer;
155
+ try {
156
+ buffer = await readFile(filePath);
157
+ }
158
+ catch (error) {
159
+ throw caizhilianValidationError('file_unreadable', `文件读取失败: ${filePath}(${error instanceof Error ? error.message : String(error)})`);
160
+ }
161
+ try {
162
+ return new TextDecoder('utf-8', { fatal: true }).decode(buffer);
163
+ }
164
+ catch {
165
+ // 落到 GBK 回退
166
+ }
167
+ try {
168
+ return new TextDecoder('gbk').decode(buffer);
169
+ }
170
+ catch {
171
+ throw caizhilianValidationError('file_encoding_unknown', `文件编码无法识别(已尝试 UTF-8 / GBK): ${filePath}`);
172
+ }
173
+ }
174
+ function assertMarkdownExt(filePath) {
175
+ const ext = extname(filePath).toLowerCase();
176
+ if (!MARKDOWN_ALLOWED_EXT.includes(ext)) {
177
+ throw caizhilianValidationError('file_type_not_allowed', `不支持的文件类型 ${ext},仅支持: ${[...MARKDOWN_ALLOWED_EXT].sort().join(', ')}`);
178
+ }
179
+ return ext;
180
+ }
181
+ /** 批量并发(源 ThreadPoolExecutor(max_workers=min(8, n))),保持输入顺序输出。 */
182
+ async function mapWithConcurrency(items, limit, worker) {
183
+ const results = new Array(items.length);
184
+ let cursor = 0;
185
+ const runners = new Array(Math.min(limit, items.length)).fill(0).map(async () => {
186
+ while (cursor < items.length) {
187
+ const index = cursor;
188
+ cursor += 1;
189
+ results[index] = await worker(items[index], index);
190
+ }
191
+ });
192
+ await Promise.all(runners);
193
+ return results;
194
+ }
195
+ // ── 写操作 apply 名(continuation 与操作绑定,防跨操作串用)──
196
+ const CREATE_HTML_APPLY = 'caizhilian.document.create-html.apply';
197
+ const CREATE_MARKDOWN_APPLY = 'caizhilian.document.create-markdown.apply';
198
+ const CREATE_OFFICE_APPLY = 'caizhilian.document.create-office.apply';
199
+ const CREATE_BY_UPLOAD_APPLY = 'caizhilian.document.create-by-upload.apply';
200
+ const UPDATE_FIELD_APPLY = 'caizhilian.document.update-field.apply';
201
+ const UPDATE_AUTHOR_APPLY = 'caizhilian.document.update-author.apply';
202
+ const UPDATE_HTML_APPLY = 'caizhilian.document.update-html.apply';
203
+ const REPLACE_CONTENT_APPLY = 'caizhilian.document.replace-content.apply';
204
+ /** 源 _execute_create_and_extract:POST createDetail 后取 data.document.id。 */
205
+ async function executeCreate(env, body, action) {
206
+ const response = await env.host.request({ method: 'POST', path: CREATE_DETAIL_PATH, body, operation: action });
207
+ return { doc_id: String(nestedRecord(response, 'data', 'document').id ?? '') };
208
+ }
209
+ /** prepare 票据载荷:目标键 + 请求体指纹(不存正文等大字段)。 */
210
+ function planLeasePayload(plan) {
211
+ const doc = asRecord(plan.body.document);
212
+ return {
213
+ content_type: plan.contentType,
214
+ folder_id: String(plan.body.folderId ?? ''),
215
+ author_id: String(asRecord(doc.author).id ?? ''),
216
+ name: String(doc.name ?? ''),
217
+ body_fingerprint: fingerprint(plan.body),
218
+ ...(plan.meta ? { file_path: plan.meta.path, file_md5: plan.meta.md5, file_size: plan.meta.size } : {}),
219
+ };
220
+ }
221
+ /** apply 前校验请求体与 prepare 快照一致(文件夹默认值/权限变化会让指纹不同)。 */
222
+ function assertPlanUnchanged(lease, plan, action) {
223
+ const expected = typeof lease.body_fingerprint === 'string' ? lease.body_fingerprint : '';
224
+ if (!expected)
225
+ return;
226
+ if (expected !== fingerprint(plan.body)) {
227
+ throw caizhilianValidationError('plan_changed', `${action} 的请求体与 prepare 时不一致(文件夹默认值或权限可能已变化),请重新执行 prepare`);
228
+ }
229
+ }
230
+ /** 本地文件预览(prepare 阶段只读,不上传)。 */
231
+ function filePreview(meta) {
232
+ return { file_name: meta.name, file_size: meta.size, file_md5: meta.md5, ext_name: meta.extName };
233
+ }
234
+ // ── caizhilian.document.create-html ───────────────────────
235
+ /** 源 CreateHtmlDocumentPlugin.execute。 */
236
+ async function planCreateHtml(args, env, action) {
237
+ const normalized = normalizeCreateArgs(args, ['name', 'folder_id', 'folder_type', 'author_id']);
238
+ const preamble = await runCreatePreamble(env, normalized, { docType: 'html', operation: action });
239
+ return {
240
+ body: buildCreateDocumentBody(preamble.args, 'html'),
241
+ contentType: 'html',
242
+ warnings: droppedFallbackWarnings(preamble),
243
+ };
244
+ }
245
+ const createHtmlHandler = {
246
+ targetKeys: ['folder_id', 'author_id'],
247
+ async prepare(args, env) {
248
+ const action = 'caizhilian.document.create-html';
249
+ const plan = await planCreateHtml(args, env, action);
250
+ const lease = await issueWriteLease(env, CREATE_HTML_APPLY, args, planLeasePayload(plan));
251
+ return caizhilianPrepared(action, createPreview('html', plan.body), lease, plan.warnings);
252
+ },
253
+ async apply(args, env, lease) {
254
+ const action = CREATE_HTML_APPLY;
255
+ const plan = await planCreateHtml(args, env, 'caizhilian.document.create-html');
256
+ assertPlanUnchanged(lease, plan, action);
257
+ return caizhilianSuccess(action, await executeCreate(env, plan.body, action), {
258
+ content_type: plan.contentType,
259
+ }, plan.warnings);
260
+ },
261
+ };
262
+ // ── caizhilian.document.create-markdown ───────────────────
263
+ /** 源 CreateMarkdownDocumentPlugin.execute:file_path 可选,读入后覆盖 summary。 */
264
+ async function planCreateMarkdown(args, env, action) {
265
+ const normalized = normalizeCreateArgs(args, ['name', 'folder_id', 'folder_type', 'author_id']);
266
+ const preamble = await runCreatePreamble(env, normalized, { docType: 'markdown', operation: action });
267
+ let effective = preamble.args;
268
+ let meta;
269
+ const filePath = optionalRawText(effective.file_path);
270
+ if (filePath !== undefined) {
271
+ meta = await readFileMetadata(filePath);
272
+ assertMarkdownExt(meta.path);
273
+ effective = { ...effective, summary: await readMarkdownFile(meta.path) };
274
+ }
275
+ return {
276
+ body: buildCreateDocumentBody(effective, 'markdown'),
277
+ contentType: 'markdown',
278
+ warnings: droppedFallbackWarnings(preamble),
279
+ ...(meta ? { meta } : {}),
280
+ };
281
+ }
282
+ const createMarkdownHandler = {
283
+ targetKeys: ['folder_id', 'author_id'],
284
+ async prepare(args, env) {
285
+ const action = 'caizhilian.document.create-markdown';
286
+ const plan = await planCreateMarkdown(args, env, action);
287
+ const lease = await issueWriteLease(env, CREATE_MARKDOWN_APPLY, args, planLeasePayload(plan));
288
+ return caizhilianPrepared(action, { ...createPreview('markdown', plan.body), ...(plan.meta ? filePreview(plan.meta) : {}) }, lease, plan.warnings);
289
+ },
290
+ async apply(args, env, lease) {
291
+ const action = CREATE_MARKDOWN_APPLY;
292
+ const plan = await planCreateMarkdown(args, env, 'caizhilian.document.create-markdown');
293
+ if (plan.meta)
294
+ assertFileUnchanged(plan.meta, lease, 'file_path');
295
+ assertPlanUnchanged(lease, plan, action);
296
+ return caizhilianSuccess(action, await executeCreate(env, plan.body, action), {
297
+ content_type: plan.contentType,
298
+ }, plan.warnings);
299
+ },
300
+ };
301
+ // ── caizhilian.document.create-office ─────────────────────
302
+ /** 源 CreateOfficeDocumentPlugin.execute(校验顺序与源一致:扩展名 → 前置 → 名称缺省 → 上传)。 */
303
+ async function planCreateOffice(args, env, action) {
304
+ const normalized = normalizeCreateArgs(args, ['file_path', 'folder_id', 'folder_type', 'author_id']);
305
+ const filePath = requireText(normalized.file_path, 'file_path');
306
+ const ext = extname(filePath).toLowerCase();
307
+ if (!OFFICE_ALLOWED_EXT.includes(ext)) {
308
+ throw caizhilianValidationError('file_type_not_allowed', `不支持的文件类型 ${ext},仅支持: ${[...OFFICE_ALLOWED_EXT].sort().join(', ')}`);
309
+ }
310
+ const contentType = EXT_TO_CONTENT_TYPE[ext];
311
+ const preamble = await runCreatePreamble(env, normalized, { docType: contentType, operation: action });
312
+ const effective = { ...preamble.args };
313
+ if (optionalRawText(effective.name) === undefined) {
314
+ effective.name = basename(filePath, extname(filePath));
315
+ }
316
+ const meta = await readFileMetadata(filePath);
317
+ return {
318
+ body: buildCreateDocumentBody(effective, contentType),
319
+ contentType,
320
+ warnings: droppedFallbackWarnings(preamble),
321
+ meta,
322
+ };
323
+ }
324
+ const createOfficeHandler = {
325
+ targetKeys: ['folder_id', 'author_id', 'file_path'],
326
+ async prepare(args, env) {
327
+ const action = 'caizhilian.document.create-office';
328
+ const plan = await planCreateOffice(args, env, action);
329
+ const lease = await issueWriteLease(env, CREATE_OFFICE_APPLY, args, planLeasePayload(plan));
330
+ return caizhilianPrepared(action, {
331
+ ...createPreview(plan.contentType, plan.body),
332
+ ...filePreview(plan.meta),
333
+ upload: { path: '/api/file/module/upload', extra_body: { createDoc: 'false' }, deferred_to_apply: true },
334
+ }, lease, plan.warnings);
335
+ },
336
+ async apply(args, env, lease) {
337
+ const action = CREATE_OFFICE_APPLY;
338
+ const plan = await planCreateOffice(args, env, 'caizhilian.document.create-office');
339
+ const meta = plan.meta;
340
+ assertFileUnchanged(meta, lease, 'file_path');
341
+ assertPlanUnchanged(lease, plan, action);
342
+ const uploaded = await uploadFileToServer(env.host, meta.path, {
343
+ allowedExt: OFFICE_ALLOWED_EXT,
344
+ extraBody: { createDoc: 'false' },
345
+ operation: action,
346
+ preloadedMeta: meta,
347
+ });
348
+ plan.body.document.content = String(uploaded.file_id);
349
+ const data = await executeCreate(env, plan.body, action);
350
+ return caizhilianSuccess(action, { ...data, file_id: uploaded.file_id }, {
351
+ content_type: plan.contentType,
352
+ }, plan.warnings);
353
+ },
354
+ };
355
+ // ── caizhilian.document.create-by-upload ──────────────────
356
+ /** 源 CreateDocumentByUploadPlugin._normalize_file_paths:兼容字符串/数组,去重保序。 */
357
+ function normalizeFilePaths(raw) {
358
+ let paths;
359
+ if (typeof raw === 'string')
360
+ paths = [raw];
361
+ else if (Array.isArray(raw))
362
+ paths = raw;
363
+ else
364
+ return [];
365
+ const seen = new Set();
366
+ const result = [];
367
+ for (const item of paths) {
368
+ const text = item === undefined || item === null ? '' : String(item).trim();
369
+ if (text && !seen.has(text)) {
370
+ seen.add(text);
371
+ result.push(text);
372
+ }
373
+ }
374
+ return result;
375
+ }
376
+ /** 源 CreateDocumentByUploadPlugin.execute 的前置部分(校验 → 前置动作 → 三段 JSON)。 */
377
+ async function planCreateByUpload(args, env, action) {
378
+ const normalized = normalizeCreateArgs(args, ['file_paths', 'folder_id']);
379
+ // 源仅校验 folder_type 必填,不校验取值,且创建请求体不使用该字段。
380
+ normalized.folder_type = requireText(args.folder_type, 'folder_type');
381
+ const filePaths = normalizeFilePaths(normalized.file_paths);
382
+ if (!filePaths.length) {
383
+ throw caizhilianValidationError('list_required', 'file_paths 不能为空');
384
+ }
385
+ const authorIdArg = optionalRawText(args.author_id);
386
+ const preamble = await runCreatePreamble(env, normalized, {
387
+ needUserInfo: authorIdArg === undefined,
388
+ includeShareFallback: false,
389
+ forceQueryShareNew: true,
390
+ operation: action,
391
+ });
392
+ const effective = preamble.args;
393
+ const authorId = authorIdArg ?? preamble.employeeId ?? '';
394
+ const properties = {
395
+ folderId: String(effective.folder_id ?? ''),
396
+ authorId,
397
+ publicFormDataId: '',
398
+ customFormDataId: '',
399
+ haveVirtualFolder: false,
400
+ printCount: 0,
401
+ doccategory: String(optionalRawText(effective.doc_category) ?? 0),
402
+ isUploadDoc: true,
403
+ createDocByUpload: true,
404
+ setCoordinateFlag: false,
405
+ };
406
+ if (optionalRawText(effective.keywords) !== undefined)
407
+ properties.keywords = effective.keywords;
408
+ if (optionalRawText(effective.auto_invalidate_date) !== undefined) {
409
+ properties.autoInvalidateDate = effective.auto_invalidate_date;
410
+ }
411
+ if (optionalRawText(effective.abstract_text) !== undefined)
412
+ properties.abstracttext = effective.abstract_text;
413
+ if (optionalRawText(effective.virtual_folder_ids) !== undefined) {
414
+ properties.virtualFolderIds = effective.virtual_folder_ids;
415
+ }
416
+ if (optionalRawText(effective.tags) !== undefined) {
417
+ const publicIds = [];
418
+ const myIds = [];
419
+ for (const tag of asList(effective.tags)) {
420
+ if (!tag || typeof tag !== 'object' || Array.isArray(tag))
421
+ continue;
422
+ const tagId = String(asRecord(tag).id ?? '');
423
+ if (!tagId)
424
+ continue;
425
+ if (asRecord(tag).privacy)
426
+ myIds.push(tagId);
427
+ else
428
+ publicIds.push(tagId);
429
+ }
430
+ if (publicIds.length)
431
+ properties.publicTagIds = publicIds;
432
+ if (myIds.length)
433
+ properties.myTagIds = myIds;
434
+ }
435
+ const participants = {
436
+ entryType: 'user',
437
+ batchupload: true,
438
+ module: 'document',
439
+ createDocByUpload: true,
440
+ shareType: 'participants',
441
+ sids: asList(effective.participant_ids).map((pid) => String(pid)).join(','),
442
+ };
443
+ const sharers = {
444
+ departmentIds: '',
445
+ groupIds: '',
446
+ subcompanyIds: '',
447
+ sids: '',
448
+ map: { canSendMsg: false },
449
+ module: 'document',
450
+ shareType: 'sharer',
451
+ defIds: '',
452
+ uploadDocAddShareList: [],
453
+ batchupload: true,
454
+ isUploadDoc: true,
455
+ createDocByUpload: true,
456
+ };
457
+ const defaultShareNew = asList(effective.folder_default_share_new);
458
+ if (defaultShareNew.length) {
459
+ sharers.defIds = defaultShareNew
460
+ .filter((entry) => asRecord(entry).id !== undefined && asRecord(entry).id !== null)
461
+ .map((entry) => String(asRecord(entry).id))
462
+ .join(',');
463
+ }
464
+ const folderId = String(effective.folder_id ?? '');
465
+ const extraBody = {
466
+ mainlineId: '',
467
+ folderId,
468
+ createDocByUpload: 'true',
469
+ createDocType: 'createContentDoc',
470
+ properties: JSON.stringify(properties),
471
+ participants: JSON.stringify(participants),
472
+ sharers: JSON.stringify(sharers),
473
+ extraParameters: '{}',
474
+ };
475
+ const metas = [];
476
+ for (const filePath of filePaths)
477
+ metas.push(await readFileMetadata(filePath));
478
+ return {
479
+ filePaths,
480
+ folderId,
481
+ authorId,
482
+ properties,
483
+ participants,
484
+ sharers,
485
+ extraBody,
486
+ warnings: droppedFallbackWarnings(preamble),
487
+ metas,
488
+ };
489
+ }
490
+ function uploadPlanFingerprint(plan) {
491
+ return fingerprint({
492
+ folder_id: plan.folderId,
493
+ author_id: plan.authorId,
494
+ properties: plan.properties,
495
+ participants: plan.participants,
496
+ sharers: plan.sharers,
497
+ });
498
+ }
499
+ function uploadPlanLeasePayload(plan) {
500
+ return {
501
+ folder_id: plan.folderId,
502
+ author_id: plan.authorId,
503
+ file_count: plan.metas.length,
504
+ files: plan.metas.map((meta) => ({ path: meta.path, name: meta.name, size: meta.size, md5: meta.md5 })),
505
+ plan_fingerprint: uploadPlanFingerprint(plan),
506
+ };
507
+ }
508
+ function assertUploadPlanUnchanged(lease, plan, action) {
509
+ const expected = typeof lease.plan_fingerprint === 'string' ? lease.plan_fingerprint : '';
510
+ if (expected && expected !== uploadPlanFingerprint(plan)) {
511
+ throw caizhilianValidationError('plan_changed', `${action} 的上传参数与 prepare 时不一致(文件夹默认值或权限可能已变化),请重新执行 prepare`);
512
+ }
513
+ const snapshots = asList(lease.files).map((item) => asRecord(item));
514
+ plan.metas.forEach((meta, index) => {
515
+ const snapshot = snapshots[index];
516
+ if (!snapshot)
517
+ return;
518
+ assertFileUnchanged(meta, { file_md5: snapshot.md5, file_size: snapshot.size }, `file_paths[${index}]`);
519
+ });
520
+ }
521
+ /** 源 _upload_one:extra_result_fields=['docId'],返回 doc_id/file_id/name/size/ext_name。 */
522
+ async function uploadOneDocument(env, meta, plan, action) {
523
+ const uploaded = await uploadFileToServer(env.host, meta.path, {
524
+ extraBody: plan.extraBody,
525
+ extraResultFields: ['docId'],
526
+ operation: action,
527
+ preloadedMeta: meta,
528
+ });
529
+ const docId = uploaded.docId;
530
+ return {
531
+ doc_id: docId === undefined || docId === null || docId === 0 || docId === '0' ? '' : String(docId),
532
+ file_id: uploaded.file_id,
533
+ name: uploaded.name,
534
+ size: uploaded.size,
535
+ ext_name: uploaded.ext_name,
536
+ };
537
+ }
538
+ function uploadErrorMessage(error) {
539
+ if (error instanceof Error && error.message)
540
+ return error.message;
541
+ return `上传异常: ${String(error)}`;
542
+ }
543
+ const createByUploadHandler = {
544
+ targetKeys: ['folder_id'],
545
+ async prepare(args, env) {
546
+ const action = 'caizhilian.document.create-by-upload';
547
+ const plan = await planCreateByUpload(args, env, action);
548
+ const lease = await issueWriteLease(env, CREATE_BY_UPLOAD_APPLY, args, uploadPlanLeasePayload(plan));
549
+ return caizhilianPrepared(action, {
550
+ folder_id: plan.folderId,
551
+ author_id: plan.authorId,
552
+ file_count: plan.metas.length,
553
+ files: plan.metas.map((meta) => filePreview(meta)),
554
+ properties: plan.properties,
555
+ participants: plan.participants,
556
+ sharers: plan.sharers,
557
+ upload: {
558
+ path: '/api/file/module/upload',
559
+ extra_result_fields: ['docId'],
560
+ max_workers: MAX_UPLOAD_WORKERS,
561
+ deferred_to_apply: true,
562
+ },
563
+ }, lease, plan.warnings);
564
+ },
565
+ async apply(args, env, lease) {
566
+ const action = CREATE_BY_UPLOAD_APPLY;
567
+ const plan = await planCreateByUpload(args, env, 'caizhilian.document.create-by-upload');
568
+ assertUploadPlanUnchanged(lease, plan, action);
569
+ if (plan.metas.length === 1) {
570
+ const data = await uploadOneDocument(env, plan.metas[0], plan, action);
571
+ return caizhilianSuccess(action, data, { batch: false }, plan.warnings);
572
+ }
573
+ const warnings = [...plan.warnings];
574
+ const results = await mapWithConcurrency(plan.metas, MAX_UPLOAD_WORKERS, async (meta, index) => {
575
+ try {
576
+ return await uploadOneDocument(env, meta, plan, action);
577
+ }
578
+ catch (error) {
579
+ const message = uploadErrorMessage(error);
580
+ warnings.push(`[WARN] 文件上传失败: ${basename(plan.filePaths[index])} -> ${message}`);
581
+ return { name: basename(plan.filePaths[index]), error: message };
582
+ }
583
+ });
584
+ const successCount = results.filter((item) => item && !('error' in item)).length;
585
+ const failedCount = results.length - successCount;
586
+ return caizhilianSuccess(action, { results, summary: { total: results.length, success: successCount, failed: failedCount } }, {
587
+ batch: true,
588
+ message: `批量上传完成:共 ${results.length} 个文件,成功 ${successCount} 个,失败 ${failedCount} 个`,
589
+ }, warnings);
590
+ },
591
+ };
592
+ // ── caizhilian.document.update-field ──────────────────────
593
+ /** 源 UpdateDocFieldPlugin:field 非法时的文案(sorted(_FIELD_MAP))。 */
594
+ function assertUpdateField(field) {
595
+ const text = typeof field === 'string' ? field.trim() : field === undefined || field === null ? '' : String(field);
596
+ const entry = FIELD_MAP[text];
597
+ if (!entry) {
598
+ throw caizhilianValidationError('enum_invalid', `field 必须为 ${FIELD_MAP_KEYS_SORTED.join(', ')} 之一,收到: '${text}'`);
599
+ }
600
+ return { field: text, filed: entry.filed, argKey: entry.argKey, allowEmpty: entry.allowEmpty };
601
+ }
602
+ /**
603
+ * 取值语义:CLI 契约用 `value`;源读 `args[arg_key]`,因此 value 缺省时回落到同名字段
604
+ * (见待验证项:源与契约字段名不一致)。
605
+ */
606
+ function resolveFieldValue(args, argKey) {
607
+ if (args.value !== undefined && args.value !== null)
608
+ return args.value;
609
+ if (args[argKey] !== undefined && args[argKey] !== null)
610
+ return args[argKey];
611
+ return '';
612
+ }
613
+ /** fetch_doc_base_info_by_docid 模板中可读回的当前值字段(autoInvalidateDate 不在模板内)。 */
614
+ const CURRENT_VALUE_FIELD = {
615
+ name: 'docName',
616
+ keywords: 'keywords',
617
+ abstract: 'abstractText',
618
+ };
619
+ const updateFieldHandler = {
620
+ targetKeys: ['doc_id'],
621
+ async prepare(args, env) {
622
+ const action = 'caizhilian.document.update-field';
623
+ const field = assertUpdateField(args.field);
624
+ const docId = requireId(args.doc_id, 'doc_id');
625
+ const value = resolveFieldValue(args, field.argKey);
626
+ if (!field.allowEmpty && !String(value ?? '').trim()) {
627
+ throw caizhilianValidationError('field_empty', `${field.argKey} 不能为空字符串`);
628
+ }
629
+ const info = await fetchDocBaseInfo(env, docId, action);
630
+ const currentKey = CURRENT_VALUE_FIELD[field.field];
631
+ const lease = await issueWriteLease(env, UPDATE_FIELD_APPLY, args, {
632
+ doc_id: docId,
633
+ field: field.field,
634
+ filed: field.filed,
635
+ value_fingerprint: fingerprint(value ?? ''),
636
+ });
637
+ return caizhilianPrepared(action, {
638
+ doc_id: docId,
639
+ doc_name: info.docName ?? null,
640
+ field: field.field,
641
+ filed: field.filed,
642
+ arg_key: field.argKey,
643
+ allow_empty: field.allowEmpty,
644
+ value: value ?? '',
645
+ current_value: currentKey ? (info[currentKey] ?? null) : null,
646
+ request: { method: 'POST', path: `/api/doc/document/update/${encodeURIComponent(docId)}` },
647
+ }, lease);
648
+ },
649
+ async apply(args, env, lease) {
650
+ const action = UPDATE_FIELD_APPLY;
651
+ const field = assertUpdateField(args.field);
652
+ const docId = requireId(args.doc_id, 'doc_id');
653
+ const value = resolveFieldValue(args, field.argKey);
654
+ const expected = typeof lease.value_fingerprint === 'string' ? lease.value_fingerprint : '';
655
+ if (expected && expected !== fingerprint(value ?? '')) {
656
+ throw caizhilianValidationError('plan_changed', `${action} 的目标值与 prepare 时不一致,请重新执行 prepare`);
657
+ }
658
+ const data = await updateDocBasicField(env, { docId, filed: field.filed, argKey: field.argKey, docField: field.filed, allowEmpty: field.allowEmpty, value }, action);
659
+ return caizhilianSuccess(action, data);
660
+ },
661
+ };
662
+ // ── caizhilian.document.update-author ─────────────────────
663
+ const updateAuthorHandler = {
664
+ targetKeys: ['doc_id', 'user_id'],
665
+ async prepare(args, env) {
666
+ const action = 'caizhilian.document.update-author';
667
+ const docId = requireId(args.doc_id, 'doc_id');
668
+ const userId = requireText(args.user_id, 'user_id');
669
+ const info = await fetchDocBaseInfo(env, docId, action);
670
+ const lease = await issueWriteLease(env, UPDATE_AUTHOR_APPLY, args, { doc_id: docId, user_id: userId });
671
+ return caizhilianPrepared(action, {
672
+ doc_id: docId,
673
+ doc_name: info.docName ?? null,
674
+ user_id: userId,
675
+ current_author_id: info.authorId ?? null,
676
+ current_author_name: info.authorName ?? null,
677
+ request: {
678
+ method: 'POST',
679
+ path: `/api/doc/document/updateAuthor/${encodeURIComponent(docId)}/${encodeURIComponent(userId)}`,
680
+ },
681
+ }, lease);
682
+ },
683
+ async apply(args, env) {
684
+ const action = UPDATE_AUTHOR_APPLY;
685
+ const docId = requireId(args.doc_id, 'doc_id');
686
+ const userId = requireText(args.user_id, 'user_id');
687
+ await env.host.request({
688
+ method: 'POST',
689
+ path: `/api/doc/document/updateAuthor/${encodeURIComponent(docId)}/${encodeURIComponent(userId)}`,
690
+ operation: action,
691
+ });
692
+ return caizhilianSuccess(action, { doc_id: docId, user_id: userId });
693
+ },
694
+ };
695
+ // ── caizhilian.document.update-html ───────────────────────
696
+ /** 源 update_doc_html_content 的请求体(filed 拼写沿用源)。 */
697
+ function buildUpdateHtmlBody(docId, summary) {
698
+ return {
699
+ id: docId,
700
+ filed: 'summary',
701
+ document: { summary, shareOut: false },
702
+ operateType: 'saveSummary',
703
+ operation: 'save',
704
+ createNewVersion: true,
705
+ };
706
+ }
707
+ function requireSummary(args) {
708
+ const summary = optionalRawText(args.summary);
709
+ if (summary === undefined)
710
+ throw caizhilianValidationError('field_required', '缺少必填参数: summary');
711
+ return summary;
712
+ }
713
+ const updateHtmlHandler = {
714
+ targetKeys: ['doc_id'],
715
+ async prepare(args, env) {
716
+ const action = 'caizhilian.document.update-html';
717
+ const docId = requireId(args.doc_id, 'doc_id');
718
+ const summary = requireSummary(args);
719
+ const info = await fetchDocBaseInfo(env, docId, action);
720
+ const lease = await issueWriteLease(env, UPDATE_HTML_APPLY, args, {
721
+ doc_id: docId,
722
+ summary_length: summary.length,
723
+ summary_fingerprint: fingerprint(summary),
724
+ });
725
+ return caizhilianPrepared(action, {
726
+ doc_id: docId,
727
+ doc_name: info.docName ?? null,
728
+ content_type: info.contentType ?? null,
729
+ summary_length: summary.length,
730
+ current_summary_length: typeof info.summary === 'string' ? info.summary.length : 0,
731
+ create_new_version: true,
732
+ request: { method: 'POST', path: `/api/doc/document/update/${encodeURIComponent(docId)}` },
733
+ }, lease);
734
+ },
735
+ async apply(args, env, lease) {
736
+ const action = UPDATE_HTML_APPLY;
737
+ const docId = requireId(args.doc_id, 'doc_id');
738
+ const summary = requireSummary(args);
739
+ const expected = typeof lease.summary_fingerprint === 'string' ? lease.summary_fingerprint : '';
740
+ if (expected && expected !== fingerprint(summary)) {
741
+ throw caizhilianValidationError('plan_changed', `${action} 的正文与 prepare 时不一致,请重新执行 prepare`);
742
+ }
743
+ await env.host.request({
744
+ method: 'POST',
745
+ path: `/api/doc/document/update/${encodeURIComponent(docId)}`,
746
+ body: buildUpdateHtmlBody(docId, summary),
747
+ operation: action,
748
+ });
749
+ return caizhilianSuccess(action, { doc_id: docId, summary });
750
+ },
751
+ };
752
+ // ── caizhilian.document.replace-content ───────────────────
753
+ const REPLACE_UPLOAD_PATH = '/api/file/reUpload';
754
+ const REPLACE_NOTIFY_PATH = '/api/doc/document/afterReplaceDocumentContent';
755
+ /** 源 int(old_file_id) 的可解析判定。 */
756
+ function isIntLike(value) {
757
+ if (typeof value === 'number')
758
+ return Number.isFinite(value);
759
+ if (typeof value !== 'string')
760
+ return false;
761
+ return /^\s*[-+]?\d+\s*$/.test(value);
762
+ }
763
+ /** 源 coordinate == 1 的判定(数字 1 / true 命中;字符串 '1' 不命中)。 */
764
+ function isCooperative(value) {
765
+ return (typeof value === 'number' && value === 1) || value === true;
766
+ }
767
+ /** 源 ReplaceDocumentContentPlugin 的三步中的前两步(只读回查 + 校验)。 */
768
+ async function planReplaceContent(args, env, action) {
769
+ const docId = requireText(args.doc_id, 'doc_id');
770
+ const meta = await readFileMetadata(args.file_path);
771
+ let info;
772
+ try {
773
+ info = await fetchDocBaseInfo(env, docId, action);
774
+ }
775
+ catch (error) {
776
+ // 「文档不存在」的明确提示直接透传,不要被下面的泛化文案覆盖。
777
+ if (error instanceof CaizhilianCliError && error.subtype === 'doc_not_found')
778
+ throw error;
779
+ throw caizhilianValidationError('doc_info_unavailable', `获取文档信息失败: ${error instanceof Error && error.message ? error.message : String(error)}`);
780
+ }
781
+ const oldFileId = info.content;
782
+ const contentType = info.contentType === undefined ? '' : info.contentType;
783
+ const permission = asRecord(info.permission);
784
+ if (isCooperative(info.coordinate)) {
785
+ throw caizhilianValidationError('coordinate_not_supported', '协同文档不支持替换正文,此功能仅适用于文件型正文的文档');
786
+ }
787
+ if (!permission.replaceMainText) {
788
+ throw caizhilianValidationError('permission_denied', '当前无替换正文权限,请联系文档负责人或管理员确认权限设置');
789
+ }
790
+ if (oldFileId === undefined || oldFileId === null || oldFileId === '' || oldFileId === 0) {
791
+ throw caizhilianValidationError('content_missing', '该文档当前没有正文文件,无法执行替换操作');
792
+ }
793
+ if (!isIntLike(oldFileId)) {
794
+ throw caizhilianValidationError('content_id_invalid', `正文文件ID无效: ${String(oldFileId)}`);
795
+ }
796
+ const typeText = typeof contentType === 'string' ? contentType : String(contentType ?? '');
797
+ let allowedExts;
798
+ if (CONTENT_TYPE_EXT_MAP[typeText])
799
+ allowedExts = CONTENT_TYPE_EXT_MAP[typeText];
800
+ else if (typeText.trim())
801
+ allowedExts = [`.${typeText.trim().toLowerCase()}`];
802
+ else
803
+ throw caizhilianValidationError('content_type_unknown', '文件格式无法识别:文档正文类型为空或未知');
804
+ const ext = extname(meta.name).toLowerCase();
805
+ if (!allowedExts.includes(ext)) {
806
+ throw caizhilianValidationError('file_type_mismatch', `文件格式不匹配:文档正文类型为 ${typeText},不支持 ${ext} 格式`);
807
+ }
808
+ return {
809
+ docId,
810
+ docName: typeof info.docName === 'string' ? info.docName : null,
811
+ contentType,
812
+ oldFileId: String(oldFileId),
813
+ allowedExts,
814
+ meta,
815
+ };
816
+ }
817
+ const replaceContentHandler = {
818
+ targetKeys: ['doc_id', 'file_path'],
819
+ async prepare(args, env) {
820
+ const action = 'caizhilian.document.replace-content';
821
+ const plan = await planReplaceContent(args, env, action);
822
+ const lease = await issueWriteLease(env, REPLACE_CONTENT_APPLY, args, {
823
+ doc_id: plan.docId,
824
+ old_file_id: plan.oldFileId,
825
+ content_type: plan.contentType,
826
+ file_path: plan.meta.path,
827
+ file_md5: plan.meta.md5,
828
+ file_size: plan.meta.size,
829
+ });
830
+ return caizhilianPrepared(action, {
831
+ doc_id: plan.docId,
832
+ doc_name: plan.docName,
833
+ content_type: plan.contentType,
834
+ old_file_id: plan.oldFileId,
835
+ allowed_exts: plan.allowedExts,
836
+ ...filePreview(plan.meta),
837
+ upload: {
838
+ path: REPLACE_UPLOAD_PATH,
839
+ extra_body: { authModule: 'document', oldFileId: plan.oldFileId, employeePartTimeId: '' },
840
+ required_permission: 'reupload',
841
+ extra_result_fields: ['version'],
842
+ deferred_to_apply: true,
843
+ },
844
+ notify: { method: 'POST', path: REPLACE_NOTIFY_PATH, body: { docId: plan.docId }, failure_is_warning: true },
845
+ }, lease);
846
+ },
847
+ async apply(args, env, lease) {
848
+ const action = REPLACE_CONTENT_APPLY;
849
+ const plan = await planReplaceContent(args, env, 'caizhilian.document.replace-content');
850
+ assertFileUnchanged(plan.meta, lease, 'file_path');
851
+ if (typeof lease.old_file_id === 'string' && lease.old_file_id && lease.old_file_id !== plan.oldFileId) {
852
+ throw caizhilianValidationError('plan_changed', `${action} 的正文文件 ID 与 prepare 时不一致(正文可能已被他人替换),请重新执行 prepare`);
853
+ }
854
+ const uploaded = await uploadFileToServer(env.host, plan.meta.path, {
855
+ extraBody: { authModule: 'document', oldFileId: plan.oldFileId, employeePartTimeId: '' },
856
+ uploadPath: REPLACE_UPLOAD_PATH,
857
+ requiredPermission: 'reupload',
858
+ permissionDeniedMessage: '无替换正文上传权限',
859
+ extraResultFields: ['version'],
860
+ operation: action,
861
+ preloadedMeta: plan.meta,
862
+ });
863
+ const warnings = [];
864
+ try {
865
+ await env.host.request({
866
+ method: 'POST',
867
+ path: REPLACE_NOTIFY_PATH,
868
+ body: { docId: plan.docId },
869
+ operation: action,
870
+ });
871
+ }
872
+ catch (error) {
873
+ warnings.push(`[WARN] 替换完成通知发送失败: ${uploadErrorMessage(error)}`);
874
+ }
875
+ return caizhilianSuccess(action, uploaded, { doc_id: plan.docId, content_type: plan.contentType }, warnings);
876
+ },
877
+ };
878
+ // ── 处理器汇总 ────────────────────────────────────────────
879
+ export const caizhilianDocumentHandlers = {
880
+ 'caizhilian.document.get': { execute: getDocument },
881
+ 'caizhilian.user.current': { execute: currentUser },
882
+ 'caizhilian.document.create-html': createHtmlHandler,
883
+ 'caizhilian.document.create-markdown': createMarkdownHandler,
884
+ 'caizhilian.document.create-office': createOfficeHandler,
885
+ 'caizhilian.document.create-by-upload': createByUploadHandler,
886
+ 'caizhilian.document.update-field': updateFieldHandler,
887
+ 'caizhilian.document.update-author': updateAuthorHandler,
888
+ 'caizhilian.document.update-html': updateHtmlHandler,
889
+ 'caizhilian.document.replace-content': replaceContentHandler,
890
+ };