weaver-work-cli 0.1.8 → 0.1.21

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 (271) hide show
  1. package/README.md +25 -17
  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 +435 -0
  14. package/dist/core/state.js +26 -0
  15. package/dist/internal/e10/auth/commands.js +34 -19
  16. package/dist/internal/e10/auth/session.js +74 -2
  17. package/dist/internal/e10/auth/xiaoe.js +359 -94
  18. package/dist/internal/e10/context.js +18 -6
  19. package/dist/internal/e10/request-runtime.js +8 -1
  20. package/dist/internal/skills/install.js +148 -28
  21. package/dist/internal/skills/reader.js +12 -9
  22. package/dist/shortcuts/blog/continuation.js +74 -0
  23. package/dist/shortcuts/blog/errors.js +60 -0
  24. package/dist/shortcuts/blog/host.js +175 -0
  25. package/dist/shortcuts/blog/index.js +324 -0
  26. package/dist/shortcuts/blog/manifest.js +415 -0
  27. package/dist/shortcuts/blog/operations/comment.js +125 -0
  28. package/dist/shortcuts/blog/operations/comments.js +31 -0
  29. package/dist/shortcuts/blog/operations/edit.js +131 -0
  30. package/dist/shortcuts/blog/operations/export.js +46 -0
  31. package/dist/shortcuts/blog/operations/like.js +80 -0
  32. package/dist/shortcuts/blog/operations/publish.js +157 -0
  33. package/dist/shortcuts/blog/operations/query-user.js +58 -0
  34. package/dist/shortcuts/blog/operations/query.js +96 -0
  35. package/dist/shortcuts/blog/operations/registry.js +54 -0
  36. package/dist/shortcuts/blog/operations/remind.js +176 -0
  37. package/dist/shortcuts/blog/operations/shared.js +330 -0
  38. package/dist/shortcuts/blog/operations/types.js +16 -0
  39. package/dist/shortcuts/blog/operations.js +9 -0
  40. package/dist/shortcuts/caizhilian/continuation.js +70 -0
  41. package/dist/shortcuts/caizhilian/errors.js +53 -0
  42. package/dist/shortcuts/caizhilian/host.js +190 -0
  43. package/dist/shortcuts/caizhilian/index.js +186 -0
  44. package/dist/shortcuts/caizhilian/manifest.js +42 -0
  45. package/dist/shortcuts/caizhilian/operations/api.js +801 -0
  46. package/dist/shortcuts/caizhilian/operations/confirm.js +75 -0
  47. package/dist/shortcuts/caizhilian/operations/files.js +306 -0
  48. package/dist/shortcuts/caizhilian/operations/form.js +170 -0
  49. package/dist/shortcuts/caizhilian/operations/handlers/collab.js +611 -0
  50. package/dist/shortcuts/caizhilian/operations/handlers/document.js +890 -0
  51. package/dist/shortcuts/caizhilian/operations/handlers/file-collection.js +1227 -0
  52. package/dist/shortcuts/caizhilian/operations/handlers/file-upload.js +192 -0
  53. package/dist/shortcuts/caizhilian/operations/handlers/fileops.js +570 -0
  54. package/dist/shortcuts/caizhilian/operations/handlers/folder.js +1024 -0
  55. package/dist/shortcuts/caizhilian/operations/handlers/search.js +318 -0
  56. package/dist/shortcuts/caizhilian/operations/handlers/setting-attach.js +294 -0
  57. package/dist/shortcuts/caizhilian/operations/handlers/setting-backup.js +423 -0
  58. package/dist/shortcuts/caizhilian/operations/handlers/setting-encrypt.js +238 -0
  59. package/dist/shortcuts/caizhilian/operations/handlers/setting-storage.js +364 -0
  60. package/dist/shortcuts/caizhilian/operations/handlers/system.js +106 -0
  61. package/dist/shortcuts/caizhilian/operations/registry.js +151 -0
  62. package/dist/shortcuts/caizhilian/operations/shared.js +408 -0
  63. package/dist/shortcuts/caizhilian/operations/specs.js +698 -0
  64. package/dist/shortcuts/caizhilian/operations/types.js +57 -0
  65. package/dist/shortcuts/caizhilian/operations.js +21 -0
  66. package/dist/shortcuts/contract/continuation.js +70 -0
  67. package/dist/shortcuts/contract/errors.js +62 -0
  68. package/dist/shortcuts/contract/host.js +205 -0
  69. package/dist/shortcuts/contract/index.js +318 -0
  70. package/dist/shortcuts/contract/manifest.js +596 -0
  71. package/dist/shortcuts/contract/operations/clause-reference.js +78 -0
  72. package/dist/shortcuts/contract/operations/form-info.js +371 -0
  73. package/dist/shortcuts/contract/operations/read.js +118 -0
  74. package/dist/shortcuts/contract/operations/registry.js +43 -0
  75. package/dist/shortcuts/contract/operations/request.js +300 -0
  76. package/dist/shortcuts/contract/operations/shared.js +133 -0
  77. package/dist/shortcuts/contract/operations/subject.js +157 -0
  78. package/dist/shortcuts/contract/operations/template.js +81 -0
  79. package/dist/shortcuts/contract/operations/types.js +7 -0
  80. package/dist/shortcuts/contract/operations/workflow.js +52 -0
  81. package/dist/shortcuts/contract/operations/write.js +131 -0
  82. package/dist/shortcuts/contract/operations.js +8 -0
  83. package/dist/shortcuts/hrm/index.js +83 -2
  84. package/dist/shortcuts/hrm/manifest.js +16 -9
  85. package/dist/shortcuts/hrm/operations/employee.js +160 -30
  86. package/dist/shortcuts/hrm/operations/org.js +17 -3
  87. package/dist/shortcuts/hrm/operations/position.js +18 -2
  88. package/dist/shortcuts/hrm/operations/registry.js +2 -0
  89. package/dist/shortcuts/hrm/operations/resolve.js +449 -0
  90. package/dist/shortcuts/index.js +5 -1
  91. package/dist/shortcuts/invoice/index.js +74 -36
  92. package/dist/shortcuts/invoice/manifest.js +674 -4
  93. package/dist/shortcuts/invoice/markdown.js +172 -0
  94. package/dist/shortcuts/invoice/operations/add.js +3 -3
  95. package/dist/shortcuts/invoice/operations/delete.js +2 -2
  96. package/dist/shortcuts/invoice/operations/get.js +10 -2
  97. package/dist/shortcuts/invoice/operations/import.js +2 -2
  98. package/dist/shortcuts/invoice/operations/registry.js +8 -1
  99. package/dist/shortcuts/invoice/operations/reim-auto.js +719 -0
  100. package/dist/shortcuts/invoice/operations/reim.js +85 -2
  101. package/dist/shortcuts/invoice/operations/shared.js +75 -8
  102. package/dist/shortcuts/invoice/operations/update.js +2 -2
  103. package/dist/shortcuts/invoice/operations/upload-and-validate.js +93 -0
  104. package/dist/shortcuts/invoice/operations/validate-preview.js +2 -1
  105. package/dist/shortcuts/invoice/projection.js +328 -0
  106. package/dist/shortcuts/meeting/host.js +18 -1
  107. package/dist/shortcuts/meeting/manifest.js +30 -18
  108. package/dist/shortcuts/meeting/operations/eb/catalog.js +390 -0
  109. package/dist/shortcuts/meeting/operations/eb/conflict.js +389 -0
  110. package/dist/shortcuts/meeting/operations/eb/factory.js +389 -0
  111. package/dist/shortcuts/meeting/operations/eb/index.js +22 -0
  112. package/dist/shortcuts/meeting/operations/env.js +8 -1
  113. package/dist/shortcuts/meeting/operations/registry.js +2 -0
  114. package/dist/shortcuts/meeting/operations/signs.js +216 -2
  115. package/dist/shortcuts/workflow/endpoints.js +2 -1
  116. package/dist/shortcuts/workflow/errors.js +8 -2
  117. package/dist/shortcuts/workflow/host.js +11 -4
  118. package/dist/shortcuts/workflow/operations/form.js +1016 -590
  119. package/dist/shortcuts/workflow/operations/read.js +913 -231
  120. package/dist/shortcuts/workflow/operations/shared.js +21 -0
  121. package/dist/shortcuts/workflow/operations/write.js +750 -52
  122. package/dist/shortcuts/workflow/operations.js +36 -10
  123. package/docs/_catalog.md +6 -0
  124. package/docs/agent-invoice.md +5 -1
  125. package/docs/agent-io-channel-design.md +468 -0
  126. package/docs/agent-skill-install.md +9 -4
  127. package/docs/blog.md +123 -0
  128. package/docs/caizhilian.md +304 -0
  129. package/docs/contract.md +91 -0
  130. package/docs/e10-auth.md +4 -2
  131. package/docs/hrm.md +53 -11
  132. package/docs/invoice.md +5 -1
  133. package/docs/meeting.md +1 -1
  134. package/docs/operation-manual.md +15 -3
  135. package/docs/skill-review-2026-09-19.md +2 -2
  136. package/package.json +1 -1
  137. package/scripts/package-skill.mjs +32 -20
  138. package/scripts/xiaoe_env.py +118 -0
  139. package/skill-template/business-info.json +113 -14
  140. package/skill-template/domains/blog.md +24 -0
  141. package/skill-template/domains/caizhilian.md +22 -0
  142. package/skill-template/domains/hrm.md +2 -1
  143. package/skill-template/domains/jinchengda.md +23 -0
  144. package/skill-template/domains/meeting.md +1 -1
  145. package/skill-template/domains/workflow.md +1 -1
  146. package/skill-template/skill-template.md +1 -1
  147. package/skills/weaver-e10-blog/SKILL.md +111 -0
  148. package/skills/weaver-e10-blog/references/blog-comment.md +51 -0
  149. package/skills/weaver-e10-blog/references/blog-comments.md +39 -0
  150. package/skills/weaver-e10-blog/references/blog-default-permission.md +38 -0
  151. package/skills/weaver-e10-blog/references/blog-disabled-capabilities.md +11 -0
  152. package/skills/weaver-e10-blog/references/blog-edit.md +51 -0
  153. package/skills/weaver-e10-blog/references/blog-export.md +42 -0
  154. package/skills/weaver-e10-blog/references/blog-like.md +50 -0
  155. package/skills/weaver-e10-blog/references/blog-publish.md +52 -0
  156. package/skills/weaver-e10-blog/references/blog-query-user.md +45 -0
  157. package/skills/weaver-e10-blog/references/blog-query.md +49 -0
  158. package/skills/weaver-e10-blog/references/blog-remind.md +51 -0
  159. package/skills/weaver-e10-blog/references/blog-resolve-users.md +38 -0
  160. package/skills/weaver-e10-blog/references/source-manifest.json +1540 -0
  161. package/skills/weaver-e10-caizhilian/SKILL.md +217 -0
  162. package/skills/weaver-e10-caizhilian/references/agent-entry.md +106 -0
  163. package/skills/weaver-e10-caizhilian/references/collaboration.md +112 -0
  164. package/skills/weaver-e10-caizhilian/references/document.md +111 -0
  165. package/skills/weaver-e10-caizhilian/references/file-collection.md +173 -0
  166. package/skills/weaver-e10-caizhilian/references/file-upload.md +309 -0
  167. package/skills/weaver-e10-caizhilian/references/files.md +120 -0
  168. package/skills/weaver-e10-caizhilian/references/folder.md +122 -0
  169. package/skills/weaver-e10-caizhilian/references/org-browser.md +104 -0
  170. package/skills/weaver-e10-caizhilian/references/setting-attach.md +440 -0
  171. package/skills/weaver-e10-caizhilian/references/setting-backup.md +372 -0
  172. package/skills/weaver-e10-caizhilian/references/setting-encrypt.md +399 -0
  173. package/skills/weaver-e10-caizhilian/references/setting-storage.md +603 -0
  174. package/skills/weaver-e10-caizhilian/references/source-manifest.json +5694 -0
  175. package/skills/weaver-e10-caizhilian/references/withheld.md +93 -0
  176. package/skills/weaver-e10-caizhilian/references/write-protocol.md +120 -0
  177. package/skills/weaver-e10-calendar/SKILL.md +4 -3
  178. package/skills/weaver-e10-ebuilder-form/SKILL.md +4 -9
  179. package/skills/weaver-e10-ebuilder-form/references/context-routing.md +0 -9
  180. package/skills/weaver-e10-ebuilder-form/references/custom-api.md +1 -5
  181. package/skills/weaver-e10-ebuilder-form/references/fields-browser.md +1 -5
  182. package/skills/weaver-e10-ebuilder-form/references/list-nlist.md +1 -5
  183. package/skills/weaver-e10-ebuilder-form/references/openapi-read.md +1 -5
  184. package/skills/weaver-e10-ebuilder-form/references/openapi-write.md +1 -5
  185. package/skills/weaver-e10-esb/SKILL.md +2 -2
  186. package/skills/weaver-e10-esb/references/source-manifest.json +9 -9
  187. package/skills/weaver-e10-hrm/SKILL.md +104 -9
  188. package/skills/weaver-e10-hrm/references/employee.md +89 -15
  189. package/skills/weaver-e10-hrm/references/org.md +114 -5
  190. package/skills/weaver-e10-hrm/references/position.md +14 -7
  191. package/skills/weaver-e10-hrm/references/source-manifest.json +1 -1
  192. package/skills/weaver-e10-jinchengda/SKILL.md +167 -0
  193. package/skills/weaver-e10-jinchengda/references/contract-agent-entry.md +82 -0
  194. package/skills/weaver-e10-jinchengda/references/contract-clause-recommender.md +69 -0
  195. package/skills/weaver-e10-jinchengda/references/contract-clause-reference-save.md +78 -0
  196. package/skills/weaver-e10-jinchengda/references/contract-disabled-capabilities.md +27 -0
  197. package/skills/weaver-e10-jinchengda/references/contract-request-status.md +71 -0
  198. package/skills/weaver-e10-jinchengda/references/contract-request-write.md +94 -0
  199. package/skills/weaver-e10-jinchengda/references/contract-signing-subjects.md +89 -0
  200. package/skills/weaver-e10-jinchengda/references/contract-subject-create.md +77 -0
  201. package/skills/weaver-e10-jinchengda/references/contract-template-selector.md +66 -0
  202. package/skills/weaver-e10-jinchengda/references/contract-workflow-discovery.md +94 -0
  203. package/skills/weaver-e10-jinchengda/references/source-manifest.json +259 -0
  204. package/skills/weaver-e10-jiuchuanhui/SKILL.md +5 -11
  205. package/skills/weaver-e10-jucailin/SKILL.md +4 -3
  206. package/skills/weaver-e10-mail/SKILL.md +4 -11
  207. package/skills/weaver-e10-meeting/SKILL.md +66 -32
  208. package/skills/weaver-e10-meeting/references/meeting-change.md +5 -2
  209. package/skills/weaver-e10-meeting/references/meeting-create.md +2 -1
  210. package/skills/weaver-e10-meeting/references/meeting-eb.md +114 -0
  211. package/skills/weaver-e10-meeting/references/meeting-lifecycle.md +9 -8
  212. package/skills/weaver-e10-meeting/references/meeting-receipts-signs.md +35 -7
  213. package/skills/weaver-e10-meeting/references/meeting-update.md +1 -1
  214. package/skills/weaver-e10-okr/SKILL.md +6 -11
  215. package/skills/weaver-e10-okr/references/source-manifest.json +3 -3
  216. package/skills/weaver-e10-plan/SKILL.md +3 -3
  217. package/skills/weaver-e10-plan/references/plan-collect-and-compose.md +1 -1
  218. package/skills/weaver-e10-plan/references/plan-overview-and-routing.md +1 -1
  219. package/skills/weaver-e10-plan/references/source-manifest.json +13 -13
  220. package/skills/weaver-e10-qiyecheng/SKILL.md +4 -10
  221. package/skills/weaver-e10-qiyecheng/references/source-manifest.json +9 -9
  222. package/skills/weaver-e10-shared/SKILL.md +42 -34
  223. package/skills/weaver-e10-shared/references/e10-auth-and-session.md +16 -43
  224. package/skills/weaver-e10-shared/references/json-output-contract.md +20 -11
  225. package/skills/weaver-e10-shared/references/non-interactive-environment.md +21 -14
  226. package/skills/weaver-e10-shijingran/SKILL.md +2 -1
  227. package/skills/weaver-e10-shijingran/references/source-manifest.json +9 -9
  228. package/skills/weaver-e10-skill-maker/SKILL.md +4 -10
  229. package/skills/weaver-e10-skill-maker/references/business-skill-generation.md +5 -3
  230. package/skills/weaver-e10-skill-maker/references/module-cli-generation.md +12 -27
  231. package/skills/weaver-e10-skill-maker/references/module-gap-audit-and-repair.md +17 -5
  232. package/skills/weaver-e10-skill-maker/references/source-update-detection.md +4 -27
  233. package/skills/weaver-e10-skill-maker/references/weaver-skill-style.md +1 -1
  234. package/skills/weaver-e10-wenshuding/SKILL.md +5 -16
  235. package/skills/weaver-e10-wenshuding/references/source-manifest.json +7 -7
  236. package/skills/weaver-e10-workflow/SKILL.md +41 -46
  237. package/skills/weaver-e10-workflow/references/source-manifest.json +39 -75
  238. package/skills/weaver-e10-workflow/references/workflow-batch.md +90 -29
  239. package/skills/weaver-e10-workflow/references/workflow-create.md +83 -109
  240. package/skills/weaver-e10-workflow/references/workflow-memory.md +19 -9
  241. package/skills/weaver-e10-workflow/references/workflow-operate.md +2 -2
  242. package/skills/weaver-e10-workflow/references/workflow-query.md +40 -27
  243. package/skills/weaver-e10-workflow/references/workflow-share.md +18 -2
  244. package/skills/weaver-e10-workflow/references/workflow-view.md +3 -4
  245. package/skills/weaver-e10-yepiaotong/SKILL.md +74 -39
  246. package/skills/weaver-e10-yepiaotong/references/invoice-detail.md +22 -1
  247. package/skills/weaver-e10-yepiaotong/references/invoice-enterprise-list.md +3 -1
  248. package/skills/weaver-e10-yepiaotong/references/invoice-output-view.md +135 -0
  249. package/skills/weaver-e10-yepiaotong/references/invoice-personal-list.md +15 -3
  250. package/skills/weaver-e10-yepiaotong/references/invoice-reim.md +1 -1
  251. package/skills/weaver-e10-yepiaotong/references/invoice-summarize.py +217 -0
  252. package/skills/weaver-e10-yepiaotong/references/invoice-upload-and-validate.md +88 -0
  253. package/skills/weaver-e10-yepiaotong/references/invoice-validation-preview.md +7 -2
  254. package/skills/weaver-e10-yepiaotong/references/reim-api-reference.md +89 -9
  255. package/skills/weaver-e10-yepiaotong/references/reim-examples.md +1 -0
  256. package/skills/weaver-e10-yepiaotong/references/reim-expense-subject-rules.md +70 -49
  257. package/skills/weaver-e10-yepiaotong/references/reim-fast-path.md +119 -0
  258. package/skills/weaver-e10-yepiaotong/references/reim-form-fill-rules.md +55 -36
  259. package/skills/weaver-e10-yepiaotong/references/reim-matching-rules.md +4 -0
  260. package/skills/weaver-e10-yepiaotong/references/reim-phone-subsidy-rules.md +149 -0
  261. package/skills/weaver-e10-yepiaotong/references/reim-workflow.md +125 -5
  262. package/skills/weaver-e10-yepiaotong/references/source-manifest.json +430 -63
  263. package/skills/weaver-e10-yimiaoban/SKILL.md +4 -12
  264. package/skills/weaver-e10-yimiaoban/references/source-manifest.json +3 -3
  265. package/skills/weaver-e10-ziguanjia/SKILL.md +2 -1
  266. package/skills/weaver-e10-ziguanjia/references/ziguanjia-agent-entry.md +3 -3
  267. package/tools/weaver-skill-detector-1.0.13/SKILL.md +9 -9
  268. package/tools/weaver-skill-detector-1.0.13/references/checklist.md +3 -3
  269. package/tools/weaver-skill-detector-1.0.13/scripts/__pycache__/validate_skill.cpython-313.pyc +0 -0
  270. package/tools/weaver-skill-detector-1.0.13/scripts/validate_skill.py +98 -91
  271. package/scripts/sync-connector-skills.mjs +0 -172
@@ -0,0 +1,75 @@
1
+ import { caizhilianConfirmationError, caizhilianValidationError } from '../errors.js';
2
+ import { fingerprint } from './shared.js';
3
+ /** apply 类写操作必须显式确认。 */
4
+ export function requireConfirm(args, action) {
5
+ if (args.confirm !== true) {
6
+ throw caizhilianConfirmationError(action, {
7
+ hint: `执行 ${action} 必须显式确认:传入 confirm=true(CLI 用 --yes)`,
8
+ });
9
+ }
10
+ }
11
+ /**
12
+ * prepare 阶段签发 continuation。
13
+ * 环境指纹(baseUrl + 登录账号)写进票据,apply 时校验,避免跨环境/跨账号复用。
14
+ */
15
+ export async function issueLease(env, action, payload, ttlMs = 10 * 60_000) {
16
+ const session = await env.host.getSession();
17
+ const continuation = await env.continuation.issue({
18
+ operation: action,
19
+ baseUrl: session.baseUrl,
20
+ userId: session.userId,
21
+ ...payload,
22
+ }, ttlMs);
23
+ return { continuation, expiresInMs: ttlMs };
24
+ }
25
+ /** apply 阶段校验确认 + continuation,返回 prepare 时写入的快照。 */
26
+ export async function verifyLease(args, action, env) {
27
+ requireConfirm(args, action);
28
+ if (typeof args.continuation !== 'string' || !args.continuation.trim()) {
29
+ throw caizhilianValidationError('continuation_required', `${action} 必须传入 prepare 返回的 continuation`);
30
+ }
31
+ const lease = await env.continuation.verify(args.continuation.trim(), action);
32
+ const session = await env.host.getSession();
33
+ if ((lease.baseUrl && lease.baseUrl !== session.baseUrl) || (lease.userId && lease.userId !== session.userId)) {
34
+ throw caizhilianValidationError('continuation_context_mismatch', '确认上下文已变化(环境或登录账号不同),请重新执行 prepare');
35
+ }
36
+ return lease;
37
+ }
38
+ /** 校验 apply 入参与 prepare 快照一致,避免目标漂移。 */
39
+ export function assertLeaseTarget(lease, args, keys, action) {
40
+ for (const key of keys) {
41
+ if (args[key] === undefined || args[key] === null || args[key] === '')
42
+ continue;
43
+ if (String(lease[key] ?? '') !== String(args[key])) {
44
+ throw caizhilianValidationError('continuation_target_changed', `${action} 的参数 ${key} 与 prepare 时不一致,请重新执行 prepare`);
45
+ }
46
+ }
47
+ }
48
+ // ── 入参指纹(通用防漂移)────────────────────────────────
49
+ /** 计算入参指纹时排除的确认控制字段。 */
50
+ const LEASE_ARG_EXCLUDED_KEYS = new Set(['confirm', 'continuation']);
51
+ /** 入参指纹:排除 confirm / continuation 后按 key 排序序列化。 */
52
+ export function leaseArgsFingerprint(args) {
53
+ const picked = {};
54
+ for (const key of Object.keys(args).sort()) {
55
+ if (LEASE_ARG_EXCLUDED_KEYS.has(key))
56
+ continue;
57
+ if (args[key] === undefined)
58
+ continue;
59
+ picked[key] = args[key];
60
+ }
61
+ return fingerprint(picked);
62
+ }
63
+ /** prepare 签发票据:自动附带入参指纹(apply 时由 assertLeaseArgs 校验)。 */
64
+ export async function issueWriteLease(env, action, args, payload, ttlMs = 10 * 60_000) {
65
+ return issueLease(env, action, { ...payload, argsFingerprint: leaseArgsFingerprint(args) }, ttlMs);
66
+ }
67
+ /** apply 校验整体入参与 prepare 时一致(比 assertLeaseTarget 更严格的全量防漂移)。 */
68
+ export function assertLeaseArgs(lease, args, action) {
69
+ const expected = typeof lease.argsFingerprint === 'string' ? lease.argsFingerprint : '';
70
+ if (!expected)
71
+ return;
72
+ if (expected !== leaseArgsFingerprint(args)) {
73
+ throw caizhilianValidationError('continuation_args_changed', `${action} 的入参与 prepare 时不一致,请重新执行 prepare`);
74
+ }
75
+ }
@@ -0,0 +1,306 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { existsSync } from 'node:fs';
3
+ import { mkdir, readFile, stat, writeFile } from 'node:fs/promises';
4
+ import { basename, dirname, extname, join, resolve } from 'node:path';
5
+ import { caizhilianValidationError } from '../errors.js';
6
+ import { CAIZHILAN_MAX_DOWNLOAD_BYTES, CAIZHILAN_TRANSFER_TIMEOUT_MS } from '../host.js';
7
+ import { responseData } from './shared.js';
8
+ /**
9
+ * 文件传输层:1:1 对应源 Skill 的
10
+ * `_plugin_helpers.upload_file_to_server` / `_plugin_helpers._download_attachment_stream`
11
+ * / `http_client.HttpClient._handle_download` / `_utils.read_file_metadata`。
12
+ */
13
+ /** 源 _plugin_helpers.UPLOAD_DOWNLOAD_TIMEOUT = 120.0 秒。 */
14
+ export const TRANSFER_TIMEOUT_MS = CAIZHILAN_TRANSFER_TIMEOUT_MS;
15
+ /** 源 _plugin_helpers.IMAGE_ALLOWED_EXT。 */
16
+ export const IMAGE_ALLOWED_EXT = ['.bmp', '.gif', '.jpeg', '.jpg', '.png', '.webp'];
17
+ /** 源 document_plugins._EXT_TO_CONTENT_TYPE。 */
18
+ export const EXT_TO_CONTENT_TYPE = {
19
+ '.doc': 'WORD',
20
+ '.docx': 'WORD',
21
+ '.xls': 'EXCEL',
22
+ '.xlsx': 'EXCEL',
23
+ '.ppt': 'PPT',
24
+ '.pptx': 'PPT',
25
+ };
26
+ /** 源 document_plugins.OFFICE_ALLOWED_EXT = set(_EXT_TO_CONTENT_TYPE)。 */
27
+ export const OFFICE_ALLOWED_EXT = Object.keys(EXT_TO_CONTENT_TYPE);
28
+ /** 源 document_plugins._CONTENT_TYPE_EXT_MAP(替换正文时的扩展名白名单)。 */
29
+ export const CONTENT_TYPE_EXT_MAP = {
30
+ WORD: ['.doc', '.docx'],
31
+ EXCEL: ['.xls', '.xlsx'],
32
+ PPT: ['.ppt', '.pptx'],
33
+ PDF: ['.pdf'],
34
+ OFD: ['.ofd'],
35
+ };
36
+ /** 源 _plugin_helpers._FIXED_QUERY_PARAMS。 */
37
+ export const FIXED_DOWNLOAD_QUERY = {
38
+ customParam: '{"fromPCDoc":1}',
39
+ authModule: 'document',
40
+ module: 'document',
41
+ };
42
+ /** 源 assets/config.json:http.download.default_dir = ""(未配置默认下载目录)。 */
43
+ export const DOWNLOAD_DEFAULT_DIR = '';
44
+ const MIME_BY_EXT = {
45
+ '.png': 'image/png',
46
+ '.jpg': 'image/jpeg',
47
+ '.jpeg': 'image/jpeg',
48
+ '.gif': 'image/gif',
49
+ '.webp': 'image/webp',
50
+ '.bmp': 'image/bmp',
51
+ '.pdf': 'application/pdf',
52
+ '.doc': 'application/msword',
53
+ '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
54
+ '.xls': 'application/vnd.ms-excel',
55
+ '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
56
+ '.ppt': 'application/vnd.ms-powerpoint',
57
+ '.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
58
+ '.md': 'text/markdown',
59
+ '.markdown': 'text/markdown',
60
+ '.txt': 'text/plain',
61
+ '.json': 'application/json',
62
+ '.ofd': 'application/ofd',
63
+ };
64
+ /** 1:1 对应源 _utils.read_file_metadata。 */
65
+ export async function readFileMetadata(filePath) {
66
+ const text = typeof filePath === 'string' ? filePath.trim() : '';
67
+ if (!text)
68
+ throw caizhilianValidationError('file_path_invalid', '文件路径无效');
69
+ let info;
70
+ try {
71
+ info = await stat(text);
72
+ }
73
+ catch {
74
+ throw caizhilianValidationError('file_not_found', `文件不存在: ${text}`);
75
+ }
76
+ if (!info.isFile())
77
+ throw caizhilianValidationError('file_not_found', `文件不存在: ${text}`);
78
+ let content;
79
+ try {
80
+ content = await readFile(text);
81
+ }
82
+ catch {
83
+ throw caizhilianValidationError('file_unreadable', `无法读取文件: ${text}`);
84
+ }
85
+ const name = basename(text);
86
+ const ext = extname(name);
87
+ return {
88
+ path: resolve(text),
89
+ name,
90
+ size: content.length,
91
+ md5: createHash('md5').update(content).digest('hex'),
92
+ extName: ext.replace(/^\./, ''),
93
+ modifyTime: Math.floor(info.mtimeMs),
94
+ content,
95
+ };
96
+ }
97
+ /** 1:1 对应源 upload_file_to_server 的 allowed_ext 校验(含 sorted() 提示文案)。 */
98
+ export function assertAllowedExt(meta, allowed) {
99
+ const ext = extname(meta.name).toLowerCase();
100
+ if (!allowed.includes(ext)) {
101
+ throw caizhilianValidationError('file_type_not_allowed', `不支持的文件类型 ${ext},仅支持: ${[...allowed].sort().join(', ')}`);
102
+ }
103
+ }
104
+ function multipartField(value) {
105
+ if (typeof value === 'string')
106
+ return value;
107
+ if (typeof value === 'number' || typeof value === 'boolean')
108
+ return String(value);
109
+ return JSON.stringify(value);
110
+ }
111
+ /**
112
+ * 组装 multipart/form-data。源 http_client 把 body 中的普通字段作为表单字段提交,
113
+ * `_file_fields` 中声明的键作为文件字段提交。
114
+ */
115
+ export function buildMultipart(fields, file) {
116
+ const form = new FormData();
117
+ for (const [key, value] of Object.entries(fields)) {
118
+ if (value === undefined || value === null)
119
+ continue;
120
+ form.append(key, multipartField(value));
121
+ }
122
+ if (file) {
123
+ const mime = MIME_BY_EXT[extname(file.meta.name).toLowerCase()] || 'application/octet-stream';
124
+ form.append(file.field, new Blob([new Uint8Array(file.meta.content)], { type: mime }), file.meta.name);
125
+ }
126
+ return form;
127
+ }
128
+ /** 1:1 对应源 _plugin_helpers.upload_file_to_server(含 MD5 秒传短路)。 */
129
+ export async function uploadFileToServer(host, filePath, options = {}) {
130
+ const meta = options.preloadedMeta ?? (await readFileMetadata(filePath));
131
+ if (options.allowedExt)
132
+ assertAllowedExt(meta, options.allowedExt);
133
+ const preCheckBody = {
134
+ compressImg: 'false',
135
+ module: 'document',
136
+ size: String(meta.size),
137
+ name: meta.name,
138
+ MD5: meta.md5,
139
+ lastModified: String(meta.modifyTime),
140
+ lastModifiedDate: String(meta.modifyTime),
141
+ ...(options.extraBody ?? {}),
142
+ };
143
+ const preResponse = await host.request({
144
+ method: 'POST',
145
+ path: '/api/file/preUploadCheck',
146
+ multipart: buildMultipart(preCheckBody),
147
+ timeoutMs: TRANSFER_TIMEOUT_MS,
148
+ operation: options.operation ?? 'caizhilian.file.pre-upload-check',
149
+ });
150
+ const preData = responseData(preResponse);
151
+ if (options.requiredPermission) {
152
+ const permissionType = asRecord(preData.permissionType);
153
+ if (!permissionType[options.requiredPermission]) {
154
+ throw caizhilianValidationError('permission_denied', options.permissionDeniedMessage ?? `无上传权限(${options.requiredPermission})`);
155
+ }
156
+ }
157
+ const moduleDto = asRecord(preData.uploadModuleDto);
158
+ const moduleFileId = moduleDto.fileid;
159
+ if (isPositiveId(moduleFileId)) {
160
+ const result = {
161
+ file_id: String(moduleFileId),
162
+ name: moduleDto.name || meta.name,
163
+ size: moduleDto.size || meta.size,
164
+ ext_name: moduleDto.extName || '',
165
+ };
166
+ for (const field of options.extraResultFields ?? [])
167
+ result[field] = moduleDto[field] ?? 0;
168
+ return result;
169
+ }
170
+ const preUploadId = preData.preUploadId;
171
+ const uploadType = preData.uploadType;
172
+ if (!preUploadId || !uploadType) {
173
+ throw caizhilianValidationError('upload_precheck_invalid', '预上传检查响应缺少 preUploadId 或 uploadType');
174
+ }
175
+ const uploadBody = {
176
+ ...preCheckBody,
177
+ preUploadId: String(preUploadId),
178
+ uploadType: String(uploadType),
179
+ };
180
+ const uploadResponse = await host.request({
181
+ method: 'POST',
182
+ path: options.uploadPath ?? '/api/file/module/upload',
183
+ multipart: buildMultipart(uploadBody, { field: 'file', meta }),
184
+ timeoutMs: TRANSFER_TIMEOUT_MS,
185
+ operation: options.operation ?? 'caizhilian.file.upload',
186
+ });
187
+ const data = responseData(uploadResponse);
188
+ const result = {
189
+ file_id: String(data.fileid ?? ''),
190
+ name: data.name || meta.name,
191
+ size: data.size || meta.size,
192
+ ext_name: data.extName || '',
193
+ };
194
+ for (const field of options.extraResultFields ?? [])
195
+ result[field] = data[field] ?? 0;
196
+ return result;
197
+ }
198
+ function asRecord(value) {
199
+ return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
200
+ }
201
+ function isPositiveId(value) {
202
+ if (value === undefined || value === null || value === '')
203
+ return false;
204
+ const parsed = Number(value);
205
+ return Number.isFinite(parsed) && parsed > 0;
206
+ }
207
+ /** 1:1 对应源 resolve_download_save_path + _download_attachment_stream + _handle_download。 */
208
+ export async function downloadAttachment(host, request) {
209
+ const savePath = resolveSavePathArg(request.savePath);
210
+ const query = { ...FIXED_DOWNLOAD_QUERY, docId: request.docId, ...(request.extraQuery ?? {}) };
211
+ const path = `/api/file/remotedownload/${encodeURIComponent(request.attachmentId)}/upload/true`;
212
+ const response = await host.requestBinary({
213
+ method: 'GET',
214
+ path,
215
+ query,
216
+ timeoutMs: TRANSFER_TIMEOUT_MS,
217
+ operation: request.operation,
218
+ });
219
+ const maxMb = CAIZHILAN_MAX_DOWNLOAD_BYTES / 1024 / 1024;
220
+ if (response.content.length > CAIZHILAN_MAX_DOWNLOAD_BYTES) {
221
+ throw caizhilianValidationError('file_too_large', `文件大小超过限制(${(response.content.length / 1024 / 1024).toFixed(1)}MB > ${maxMb.toFixed(0)}MB)`);
222
+ }
223
+ const target = renameIfExists(await resolveTargetPath(savePath, response.headers, path));
224
+ await mkdir(dirname(target), { recursive: true });
225
+ await writeFile(target, response.content);
226
+ return { file_path: target, file_name: basename(target), file_size: response.content.length };
227
+ }
228
+ /** 源 resolve_download_save_path:save_path 优先,其次 config 的 default_dir,都没有则报 no_save_path。 */
229
+ export function resolveSavePathArg(savePath) {
230
+ const text = typeof savePath === 'string' ? savePath.trim() : '';
231
+ if (text)
232
+ return text;
233
+ if (DOWNLOAD_DEFAULT_DIR)
234
+ return DOWNLOAD_DEFAULT_DIR;
235
+ throw caizhilianValidationError('no_save_path', '未指定保存路径且未配置默认下载目录。请提供 save_path 参数指定保存位置(可以是完整文件路径,也可以是目录)。', { error_type: 'no_save_path', default_dir_configured: false });
236
+ }
237
+ async function resolveTargetPath(savePath, headers, urlPath) {
238
+ const absolute = resolve(savePath);
239
+ const looksLikeDir = savePath.endsWith('/') || savePath.endsWith('\\');
240
+ if (!looksLikeDir)
241
+ return absolute;
242
+ return join(absolute, extractDownloadFilename(headers, urlPath));
243
+ }
244
+ /** 1:1 对应源 http_client._extract_download_filename(已过滤路径穿越)。 */
245
+ export function extractDownloadFilename(headers, urlPath) {
246
+ const disposition = headers.get('content-disposition') || '';
247
+ if (disposition.toLowerCase().includes('filename')) {
248
+ const star = /filename\*\s*=\s*[A-Za-z0-9-]*''["']?([^"';\n]*)["']?/i.exec(disposition);
249
+ if (star && star[1].trim())
250
+ return sanitizeFilename(safeDecode(star[1].trim()));
251
+ const plain = /filename(?!\*)[^;=\n]*=["']?([^"';\n]*)["']?/i.exec(disposition);
252
+ if (plain && plain[1].trim())
253
+ return sanitizeFilename(safeDecode(plain[1].trim()));
254
+ }
255
+ const fromUrl = basename(urlPath.split('?')[0].replace(/\/+$/, ''));
256
+ if (fromUrl && fromUrl.includes('.'))
257
+ return sanitizeFilename(safeDecode(fromUrl));
258
+ return 'download';
259
+ }
260
+ function safeDecode(value) {
261
+ try {
262
+ return decodeURIComponent(value);
263
+ }
264
+ catch {
265
+ return value;
266
+ }
267
+ }
268
+ function sanitizeFilename(value) {
269
+ const cleaned = basename(value).replace(/[\\/:*?"<>|\u0000-\u001f]/g, '_').replace(/^\.+/, '');
270
+ return cleaned || 'download';
271
+ }
272
+ /** 源 http_client._rename_if_exists:conflict_strategy 默认 rename。 */
273
+ export function renameIfExists(target) {
274
+ const dir = dirname(target);
275
+ const ext = extname(target);
276
+ const stem = basename(target, ext);
277
+ let candidate = target;
278
+ for (let index = 1; index < 1000; index += 1) {
279
+ if (!existsSyncSafe(candidate))
280
+ return candidate;
281
+ candidate = join(dir, `${stem}(${index})${ext}`);
282
+ }
283
+ return candidate;
284
+ }
285
+ /** 同步存在性检查:仅用于下载命名冲突处理,异常按“不存在”处理。 */
286
+ function existsSyncSafe(path) {
287
+ try {
288
+ return existsSync(path);
289
+ }
290
+ catch {
291
+ return false;
292
+ }
293
+ }
294
+ /**
295
+ * 校验本地文件与 prepare 快照一致(防「prepare 后文件被改动再 apply」)。
296
+ * lease 中的 file_md5 / file_size 由 prepare 写入。
297
+ */
298
+ export function assertFileUnchanged(meta, lease, field = 'file_path') {
299
+ const expectedMd5 = typeof lease.file_md5 === 'string' ? lease.file_md5 : '';
300
+ const expectedSize = lease.file_size === undefined || lease.file_size === null ? NaN : Number(lease.file_size);
301
+ const md5Changed = Boolean(expectedMd5) && expectedMd5 !== meta.md5;
302
+ const sizeChanged = Number.isFinite(expectedSize) && expectedSize !== meta.size;
303
+ if (md5Changed || sizeChanged) {
304
+ throw caizhilianValidationError('file_changed', `本地文件在 prepare 之后发生变化(${field}),请重新执行 prepare:${meta.path}`, { field, expected_md5: expectedMd5 || undefined, actual_md5: meta.md5 });
305
+ }
306
+ }
@@ -0,0 +1,170 @@
1
+ import { asList, asRecord } from './api.js';
2
+ import { isPlainObject, pyInt } from './shared.js';
3
+ /**
4
+ * 共享/权限表单配置解析(1:1 对应源 scripts/folder_plugins.py 的 _parse_* 系列)。
5
+ *
6
+ * 文件夹权限表单(folder_plugins)与文档共享表单(document_plugins.FetchDocShareFormPlugin)
7
+ * 复用同一套 cascadeRules / items 解析逻辑,因此抽到本模块,避免两份拷贝。
8
+ */
9
+ /** 源 _DEFAULT_SEC_LEVEL / _DEFAULT_SEC_ALLOWED。 */
10
+ export const DEFAULT_SEC_LEVEL = { min: 0, max: 100 };
11
+ export const DEFAULT_SEC_ALLOWED = { min: -999, max: 999 };
12
+ /** 源 _build_obj_type_list 的 _NO_SHARE_LEVEL_TYPES / _ID_FIELD_PREFIXES。 */
13
+ export const NO_SHARE_LEVEL_TYPES = ['settingPermission', 'newCreateFolderPremisstion'];
14
+ export const ID_FIELD_PREFIXES = ['obj_id'];
15
+ /** 源 layout 遍历:只取 list 分组。 */
16
+ export function layoutGroups(respData) {
17
+ return asList(respData.layout).filter((group) => Array.isArray(group));
18
+ }
19
+ export function findObjTypeItem(respData) {
20
+ for (const group of layoutGroups(respData)) {
21
+ for (const item of group) {
22
+ if (isPlainObject(item) && item.id === 'objType')
23
+ return item;
24
+ }
25
+ }
26
+ return undefined;
27
+ }
28
+ /** 源 _parse_cascade_rules。 */
29
+ export function parseCascadeRules(respData) {
30
+ const item = findObjTypeItem(respData);
31
+ return item ? asRecord(asRecord(item.cascadeRules).objType) : {};
32
+ }
33
+ /** 源 _parse_cascade_rules_outer。 */
34
+ export function parseCascadeRulesOuter(respData) {
35
+ const item = findObjTypeItem(respData);
36
+ return item ? asRecord(asRecord(item.cascadeRulesOuter).objType) : {};
37
+ }
38
+ /** 源 _parse_field_cascade_rules。 */
39
+ export function parseFieldCascadeRules(respData) {
40
+ const result = {};
41
+ for (const group of layoutGroups(respData)) {
42
+ for (const item of group) {
43
+ if (!isPlainObject(item))
44
+ continue;
45
+ const fieldId = item.id;
46
+ const cascade = item.cascadeRules;
47
+ if (fieldId && cascade && fieldId !== 'objType')
48
+ result[String(fieldId)] = cascade;
49
+ }
50
+ }
51
+ return result;
52
+ }
53
+ /** 源 _parse_obj_type_options。 */
54
+ export function parseObjTypeOptions(respData) {
55
+ const options = asList(asRecord(asRecord(respData.items).objType).options);
56
+ const nameMap = {};
57
+ const availableIds = new Set();
58
+ for (const option of options) {
59
+ if (!isPlainObject(option))
60
+ continue;
61
+ const oid = option.id ?? '';
62
+ if (!oid)
63
+ continue;
64
+ nameMap[String(oid)] = 'content' in option ? option.content : oid;
65
+ availableIds.add(String(oid));
66
+ }
67
+ return { nameMap, availableIds };
68
+ }
69
+ /** 源 _parse_param_options(含 cascadeRules 里补出的隐藏参数)。 */
70
+ export function parseParamOptions(respData, fieldCascadeRules) {
71
+ const items = asRecord(respData.items);
72
+ const paramOptions = {};
73
+ for (const [fieldName, fieldConfig] of Object.entries(items)) {
74
+ if (fieldName === 'objType')
75
+ continue;
76
+ const options = asList(asRecord(fieldConfig).options);
77
+ if (!options.length)
78
+ continue;
79
+ paramOptions[fieldName] = options
80
+ .filter(isPlainObject)
81
+ .map((option) => ({ id: option.id ?? '', name: option.content ?? '' }));
82
+ }
83
+ if (fieldCascadeRules) {
84
+ for (const cascade of Object.values(fieldCascadeRules)) {
85
+ for (const optionRules of Object.values(asRecord(cascade))) {
86
+ if (!isPlainObject(optionRules))
87
+ continue;
88
+ for (const optionRule of Object.values(optionRules)) {
89
+ if (!isPlainObject(optionRule))
90
+ continue;
91
+ for (const [subField, subDefault] of Object.entries(asRecord(optionRule.datas))) {
92
+ if (subField in paramOptions || subField in items)
93
+ continue;
94
+ if (typeof subDefault === 'string')
95
+ paramOptions[subField] = [{ id: subDefault, name: subDefault }];
96
+ }
97
+ }
98
+ }
99
+ }
100
+ }
101
+ return paramOptions;
102
+ }
103
+ /** 源 _parse_seclevel_allowed。 */
104
+ export function parseSeclevelAllowed(respData) {
105
+ const seclevelConfig = asRecord(respData.items).seclevel;
106
+ if (!isPlainObject(seclevelConfig))
107
+ return { ...DEFAULT_SEC_ALLOWED };
108
+ const other = seclevelConfig.otherParams;
109
+ if (!isPlainObject(other))
110
+ return { ...DEFAULT_SEC_ALLOWED };
111
+ try {
112
+ return { min: pyInt(other.min, -999), max: pyInt(other.max, 999) };
113
+ }
114
+ catch {
115
+ return { ...DEFAULT_SEC_ALLOWED };
116
+ }
117
+ }
118
+ /** 源 _build_obj_type_list:把级联规则整理成对外可见的对象类型清单。 */
119
+ export function buildObjTypeList(input) {
120
+ const includeShareFields = !NO_SHARE_LEVEL_TYPES.includes(input.permissionType);
121
+ let hasDownload = false;
122
+ if (input.fieldCascadeRules && input.fieldCascadeRules.share_level) {
123
+ for (const rules of Object.values(asRecord(input.fieldCascadeRules.share_level))) {
124
+ if (!isPlainObject(rules))
125
+ continue;
126
+ for (const rule of Object.values(rules)) {
127
+ if (isPlainObject(rule) && 'download' in asRecord(rule.datas))
128
+ hasDownload = true;
129
+ }
130
+ }
131
+ }
132
+ const result = [];
133
+ for (const [objType, outerRuleRaw] of Object.entries(input.cascadeRulesOuter)) {
134
+ if (input.availableIds.size > 0 && !input.availableIds.has(objType))
135
+ continue;
136
+ const outerRule = asRecord(outerRuleRaw);
137
+ const showFields = asList(outerRule.show);
138
+ const params = showFields.filter((field) => field !== 'objType');
139
+ const idParams = params.filter((field) => typeof field === 'string' && ID_FIELD_PREFIXES.some((prefix) => field.startsWith(prefix)));
140
+ const needId = idParams.length > 0;
141
+ if (includeShareFields && hasDownload && params.includes('share_level') && !params.includes('download')) {
142
+ params.push('download');
143
+ }
144
+ const item = {
145
+ id: objType,
146
+ name: objType in input.objTypeNames ? input.objTypeNames[objType] : objType,
147
+ need_id: needId,
148
+ params,
149
+ };
150
+ if (needId)
151
+ item.id_param = idParams[0] === undefined ? '' : String(idParams[0]);
152
+ if (params.includes('seclevel')) {
153
+ let secDefault = { ...DEFAULT_SEC_LEVEL };
154
+ const rule = asRecord(input.cascadeRules[objType]);
155
+ const datas = asRecord(rule.datas);
156
+ const rawSec = datas.seclevel;
157
+ if (isPlainObject(rawSec)) {
158
+ try {
159
+ secDefault = { min: pyInt(rawSec.min, DEFAULT_SEC_LEVEL.min), max: pyInt(rawSec.max, DEFAULT_SEC_LEVEL.max) };
160
+ }
161
+ catch {
162
+ secDefault = { ...DEFAULT_SEC_LEVEL };
163
+ }
164
+ }
165
+ item.seclevel = { default: secDefault, allowed: input.seclevelAllowed ?? { ...DEFAULT_SEC_ALLOWED } };
166
+ }
167
+ result.push(item);
168
+ }
169
+ return result;
170
+ }