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,131 @@
1
+ import { blogConfirmationError, blogPartialError, blogValidationError, isBlogNetworkError, } from '../errors.js';
2
+ import { asDate, ensureHtml, fetchDetailsByDate, htmlToText, objectFingerprint, resolveTargetUser, sessionMatches, } from './shared.js';
3
+ import { workflow } from './types.js';
4
+ /** 编辑 prepare:回查原内容 + 差异摘要 + 目标指纹,签发 continuation */
5
+ export class BlogEditPrepareOperation {
6
+ name = 'blog.edit.prepare';
7
+ async execute(input, { host, continuation }) {
8
+ const date = asDate(input.date, 'date');
9
+ const rawContent = typeof input.content === 'string' ? input.content.trim() : '';
10
+ if (!rawContent)
11
+ throw blogValidationError('input_invalid', 'content 必须是非空字符串');
12
+ const content = ensureHtml(rawContent);
13
+ const { userId, userName } = await resolveTargetUser(input, host);
14
+ const detail = await fetchDetailsByDate(host, date, userId);
15
+ if (!detail.found || !detail.blog) {
16
+ throw blogValidationError('edit_target_missing', `对应日期 ${date} 人员 ${userName ?? userId} 无日报,不可编辑;如需新建请使用 blog.publish.prepare`);
17
+ }
18
+ const original = detail.blog;
19
+ const originalText = htmlToText(original.content);
20
+ const newText = htmlToText(content);
21
+ if (originalText === newText) {
22
+ throw blogValidationError('changes_noop', '新内容与当前日报内容相同');
23
+ }
24
+ const session = await host.getSession();
25
+ const continuationToken = await continuation.issue({
26
+ operation: 'blog.edit.apply',
27
+ workflow: workflow('blog.edit', 'AWAITING_CONFIRMATION'),
28
+ baseUrl: session.baseUrl,
29
+ profile: session.profile,
30
+ userId: session.userId,
31
+ date,
32
+ targetUserId: userId,
33
+ blogId: original.id,
34
+ content,
35
+ fingerprint: objectFingerprint(original),
36
+ });
37
+ return {
38
+ status: 'AWAITING_CONFIRMATION',
39
+ data: {
40
+ date,
41
+ target: { user_id: userId, name: userName },
42
+ blog_id: original.id,
43
+ original: { content: original.content, content_text: originalText, submit_time: original.create_time_str },
44
+ new: { content, content_text: newText },
45
+ continuation: continuationToken,
46
+ expires_in_seconds: 600,
47
+ workflow: workflow('blog.edit', 'AWAITING_CONFIRMATION'),
48
+ next: '向用户复述原内容与新内容,用户确认后调用 blog.edit.apply 并传 confirm=true',
49
+ },
50
+ };
51
+ }
52
+ }
53
+ /** 编辑 apply:确认 + continuation + 目标指纹校验 + 写后回查内容一致性 */
54
+ export class BlogEditApplyOperation {
55
+ name = 'blog.edit.apply';
56
+ async execute(input, { host, continuation }) {
57
+ if (input.confirm !== true) {
58
+ throw blogConfirmationError('blog.edit.apply', { hint: '重新调用时传入 confirm=true' });
59
+ }
60
+ const lease = await continuation.verify(input.continuation, 'blog.edit.apply');
61
+ if (typeof lease.blogId !== 'string' || typeof lease.content !== 'string' || typeof lease.fingerprint !== 'string') {
62
+ throw blogValidationError('continuation_invalid', '编辑 continuation 缺少有效目标、内容或指纹');
63
+ }
64
+ const session = await host.getSession();
65
+ if (!sessionMatches(lease, session)) {
66
+ throw blogValidationError('context_mismatch', '确认上下文已变化,请重新准备编辑');
67
+ }
68
+ // 目标指纹校验:确认期间日报被他人/自己改过则拒绝
69
+ const current = await fetchDetailsByDate(host, lease.date, lease.targetUserId);
70
+ if (!current.found || !current.blog || objectFingerprint(current.blog) !== lease.fingerprint) {
71
+ throw blogValidationError('target_changed', '日报在确认后发生变化,请重新准备编辑');
72
+ }
73
+ let result;
74
+ try {
75
+ result = await host.blogRequest({
76
+ path: '/api/blog/prior/updateBlog',
77
+ body: {
78
+ timezoneOffset: -8,
79
+ blog: {
80
+ id: lease.blogId,
81
+ content: lease.content,
82
+ client: 'pc',
83
+ attachmentIds: '',
84
+ atLinkList: [],
85
+ isTm: 0,
86
+ isDbCheck: 0,
87
+ },
88
+ },
89
+ operation: 'blog.edit.apply',
90
+ });
91
+ }
92
+ catch (error) {
93
+ if (isBlogNetworkError(error)) {
94
+ throw blogPartialError('编辑请求发送后连接中断,结果不确定,禁止自动重试;请先用 blog.query_user 回查', {
95
+ blog_id: lease.blogId,
96
+ workflow: workflow('blog.edit', 'EXECUTING'),
97
+ }, { stage: 'update' });
98
+ }
99
+ throw error;
100
+ }
101
+ const after = await fetchDetailsByDate(host, lease.date, lease.targetUserId);
102
+ if (!after.found || !after.blog) {
103
+ throw blogPartialError('编辑接口已返回成功,但详情回查失败,结果不确定', {
104
+ blog_id: lease.blogId,
105
+ result,
106
+ workflow: workflow('blog.edit', 'VERIFYING'),
107
+ }, { stage: 'readback' });
108
+ }
109
+ if (htmlToText(after.blog.content) !== htmlToText(lease.content)) {
110
+ throw blogPartialError('编辑接口已返回成功,但回查内容与新内容不一致', {
111
+ blog_id: lease.blogId,
112
+ expected: htmlToText(lease.content),
113
+ actual: after.blog.content_text,
114
+ result,
115
+ workflow: workflow('blog.edit', 'VERIFYING'),
116
+ }, { stage: 'readback' });
117
+ }
118
+ return {
119
+ status: 'COMPLETE',
120
+ data: {
121
+ blog_id: lease.blogId,
122
+ date: lease.date,
123
+ updated: true,
124
+ verified: true,
125
+ content_text: after.blog.content_text,
126
+ result,
127
+ workflow: workflow('blog.edit', 'COMPLETE'),
128
+ },
129
+ };
130
+ }
131
+ }
@@ -0,0 +1,46 @@
1
+ import { ExitCode } from '../../../core/errors.js';
2
+ import { BlogCliError } from '../errors.js';
3
+ import { asDate, asInteger, asNonEmptyString, resolveExportOutput, writeExportFile } from './shared.js';
4
+ /** 日报报表导出:全量(当前账号可见范围)xlsx 文件流,落盘前校验 PK 魔数并拒绝覆盖 */
5
+ export class BlogExportOperation {
6
+ name = 'blog.export';
7
+ async execute(input, { host }) {
8
+ const startDate = asDate(input.start_date, 'start_date');
9
+ const endDate = asDate(input.end_date, 'end_date');
10
+ const output = asNonEmptyString(input.output, 'output');
11
+ const pageSize = asInteger(input.page_size, 'page_size', { min: 1, max: 500, dflt: 200 });
12
+ const resolved = await resolveExportOutput(output);
13
+ const result = await host.blogBinaryRequest({
14
+ path: '/api/blog/report/exportAllBlog',
15
+ body: {
16
+ startDate,
17
+ endDate,
18
+ blogType: 'all',
19
+ searchType: 'all',
20
+ disableType: 'all',
21
+ pageNo: 1,
22
+ pageSize,
23
+ isNeedConvert: false,
24
+ },
25
+ operation: 'blog.export',
26
+ });
27
+ const magicOk = result.content.subarray(0, 2).toString('latin1') === 'PK';
28
+ if (!magicOk) {
29
+ throw new BlogCliError('api', 'export_failed', '导出响应不是合法的 xlsx 文件流(缺少 PK 魔数)', {
30
+ details: { contentType: result.contentType, preview: result.content.subarray(0, 200).toString('utf8') },
31
+ exitCode: ExitCode.GeneralError,
32
+ });
33
+ }
34
+ await writeExportFile(resolved, result.content);
35
+ return {
36
+ status: 'COMPLETE',
37
+ data: {
38
+ output: resolved,
39
+ bytes: result.content.length,
40
+ content_type: result.contentType,
41
+ range: { start_date: startDate, end_date: endDate },
42
+ note: '导出范围为当前账号有权限查看的全部日报(不限于本人)',
43
+ },
44
+ };
45
+ }
46
+ }
@@ -0,0 +1,80 @@
1
+ import { blogConfirmationError, blogPartialError, blogValidationError, isBlogNetworkError, } from '../errors.js';
2
+ import { objectFingerprint, resolveTargetBlog, sessionMatches } from './shared.js';
3
+ import { workflow } from './types.js';
4
+ /** 点赞 / 取消点赞 prepare:定位目标日报,签发 continuation */
5
+ export class BlogLikePrepareOperation {
6
+ name = 'blog.like.prepare';
7
+ async execute(input, { host, continuation }) {
8
+ const cancel = input.cancel === true;
9
+ const target = await resolveTargetBlog(input, host, cancel ? '取消点赞' : '点赞');
10
+ const session = await host.getSession();
11
+ const continuationToken = await continuation.issue({
12
+ operation: 'blog.like.apply',
13
+ workflow: workflow('blog.like', 'AWAITING_CONFIRMATION'),
14
+ baseUrl: session.baseUrl,
15
+ profile: session.profile,
16
+ userId: session.userId,
17
+ blogId: target.blogId,
18
+ cancel,
19
+ fingerprint: objectFingerprint(target.blog ? { id: target.blog.id } : { id: target.blogId }),
20
+ });
21
+ return {
22
+ status: 'AWAITING_CONFIRMATION',
23
+ data: {
24
+ blog_id: target.blogId,
25
+ action: cancel ? 'cancelVote' : 'vote',
26
+ target: target.date ? { date: target.date, user_id: target.userId } : undefined,
27
+ continuation: continuationToken,
28
+ expires_in_seconds: 600,
29
+ workflow: workflow('blog.like', 'AWAITING_CONFIRMATION'),
30
+ next: '用户确认后调用 blog.like.apply 并传 confirm=true',
31
+ },
32
+ };
33
+ }
34
+ }
35
+ /** 点赞 apply:vote / cancelVote 二选一,以业务码作为生效依据(无只读回查接口) */
36
+ export class BlogLikeApplyOperation {
37
+ name = 'blog.like.apply';
38
+ async execute(input, { host, continuation }) {
39
+ if (input.confirm !== true) {
40
+ throw blogConfirmationError('blog.like.apply', { hint: '重新调用时传入 confirm=true' });
41
+ }
42
+ const lease = await continuation.verify(input.continuation, 'blog.like.apply');
43
+ if (typeof lease.blogId !== 'string' || typeof lease.cancel !== 'boolean') {
44
+ throw blogValidationError('continuation_invalid', '点赞 continuation 缺少有效目标或动作');
45
+ }
46
+ const session = await host.getSession();
47
+ if (!sessionMatches(lease, session)) {
48
+ throw blogValidationError('context_mismatch', '确认上下文已变化,请重新准备');
49
+ }
50
+ let result;
51
+ try {
52
+ result = await host.blogRequest({
53
+ path: lease.cancel ? '/api/blog/common/upvoteComponent/cancelVote' : '/api/blog/common/upvoteComponent/vote',
54
+ body: { componentId: lease.blogId, upvote: true },
55
+ operation: 'blog.like.apply',
56
+ });
57
+ }
58
+ catch (error) {
59
+ if (isBlogNetworkError(error)) {
60
+ throw blogPartialError('点赞请求发送后连接中断,结果不确定,禁止自动重试', {
61
+ blog_id: lease.blogId,
62
+ workflow: workflow('blog.like', 'EXECUTING'),
63
+ }, { stage: 'vote' });
64
+ }
65
+ throw error;
66
+ }
67
+ return {
68
+ status: 'COMPLETE',
69
+ data: {
70
+ blog_id: lease.blogId,
71
+ action: lease.cancel ? 'cancelVote' : 'vote',
72
+ // detailsByDate 不返回点赞数,动作是否生效以业务码(code=200, fail=false)为准
73
+ receipt: typeof result?.data === 'string' ? result.data : null,
74
+ verified: false,
75
+ note: '点赞状态无只读回查接口,动作生效以接口业务码为准',
76
+ workflow: workflow('blog.like', 'COMPLETE'),
77
+ },
78
+ };
79
+ }
80
+ }
@@ -0,0 +1,157 @@
1
+ import { blogConfirmationError, blogPartialError, blogValidationError, isBlogNetworkError, } from '../errors.js';
2
+ import { asDate, ensureHtml, fetchDetailsByDate, htmlToText, localDateStr, objectFingerprint, sessionMatches, } from './shared.js';
3
+ import { workflow } from './types.js';
4
+ /** 某日期的默认共享对象;prepare 阶段回填进 addBlog 的 permissionList */
5
+ export class BlogDefaultPermissionOperation {
6
+ name = 'blog.default_permission';
7
+ async execute(input, { host }) {
8
+ const date = asDate(input.date, 'date');
9
+ const value = await host.blogRequest({
10
+ method: 'GET',
11
+ path: '/api/blog/share/getDefaultPermission',
12
+ query: { blogTime: date },
13
+ operation: 'blog.default_permission',
14
+ });
15
+ return {
16
+ status: 'COMPLETE',
17
+ data: {
18
+ date,
19
+ add: value?.data?.add ?? null,
20
+ list: Array.isArray(value?.data?.list) ? value.data.list : [],
21
+ },
22
+ };
23
+ }
24
+ }
25
+ /**
26
+ * 发布 prepare:只读查重 + 默认共享回填 + 签发 continuation。
27
+ * 服务端不限制同日重复提交,因此必须在发布前先查当日是否已有日报。
28
+ */
29
+ export class BlogPublishPrepareOperation {
30
+ name = 'blog.publish.prepare';
31
+ async execute(input, { host, continuation }) {
32
+ const date = asDate(input.date, 'date');
33
+ const rawContent = typeof input.content === 'string' ? input.content.trim() : '';
34
+ if (!rawContent)
35
+ throw blogValidationError('input_invalid', 'content 必须是非空字符串');
36
+ const content = ensureHtml(rawContent);
37
+ const shareDefault = input.share_default === undefined ? true : input.share_default === true;
38
+ const session = await host.getSession();
39
+ const existing = await fetchDetailsByDate(host, date, session.userId);
40
+ if (existing.found && existing.blog) {
41
+ throw blogValidationError('publish_already_exists', `${date} 已有日报(id=${existing.blog.id}),不可重复发布;如需修改请使用 blog.edit.prepare`, {
42
+ date,
43
+ blog_id: existing.blog.id,
44
+ });
45
+ }
46
+ let permissionList = [];
47
+ if (shareDefault) {
48
+ const perm = await host.blogRequest({
49
+ method: 'GET',
50
+ path: '/api/blog/share/getDefaultPermission',
51
+ query: { blogTime: date },
52
+ operation: 'blog.publish.prepare',
53
+ });
54
+ permissionList = Array.isArray(perm?.data?.list) ? perm.data.list : [];
55
+ }
56
+ const continuationToken = await continuation.issue({
57
+ operation: 'blog.publish.apply',
58
+ workflow: workflow('blog.publish', 'AWAITING_CONFIRMATION'),
59
+ baseUrl: session.baseUrl,
60
+ profile: session.profile,
61
+ userId: session.userId,
62
+ date,
63
+ content,
64
+ permissionList,
65
+ fingerprint: objectFingerprint({ date, content, permissionList }),
66
+ });
67
+ return {
68
+ status: 'AWAITING_CONFIRMATION',
69
+ data: {
70
+ date,
71
+ content,
72
+ content_text: htmlToText(content),
73
+ share: { default: shareDefault, permission_count: permissionList.length },
74
+ continuation: continuationToken,
75
+ expires_in_seconds: 600,
76
+ workflow: workflow('blog.publish', 'AWAITING_CONFIRMATION'),
77
+ next: '向用户复述日期与内容,用户确认后调用 blog.publish.apply 并传 confirm=true',
78
+ },
79
+ };
80
+ }
81
+ }
82
+ /** 发布 apply:确认 + continuation + 上下文/指纹校验 + 写前查重 + 写后回查 */
83
+ export class BlogPublishApplyOperation {
84
+ name = 'blog.publish.apply';
85
+ async execute(input, { host, continuation }) {
86
+ if (input.confirm !== true) {
87
+ throw blogConfirmationError('blog.publish.apply', { hint: '重新调用时传入 confirm=true' });
88
+ }
89
+ const lease = await continuation.verify(input.continuation, 'blog.publish.apply');
90
+ if (typeof lease.date !== 'string' || typeof lease.content !== 'string' || typeof lease.fingerprint !== 'string') {
91
+ throw blogValidationError('continuation_invalid', '发布 continuation 缺少有效日期、内容或指纹');
92
+ }
93
+ const session = await host.getSession();
94
+ if (!sessionMatches(lease, session)) {
95
+ throw blogValidationError('context_mismatch', '确认上下文已变化,请重新准备发布');
96
+ }
97
+ const existing = await fetchDetailsByDate(host, lease.date, session.userId);
98
+ if (existing.found && existing.blog) {
99
+ throw blogValidationError('target_changed', `${lease.date} 在确认后出现了日报(id=${existing.blog.id}),请重新准备发布`);
100
+ }
101
+ if (objectFingerprint({ date: lease.date, content: lease.content, permissionList: lease.permissionList }) !== lease.fingerprint) {
102
+ throw blogValidationError('continuation_invalid', '发布 continuation 指纹校验失败');
103
+ }
104
+ let result;
105
+ try {
106
+ result = await host.blogRequest({
107
+ path: '/api/blog/prior/addBlog',
108
+ body: {
109
+ blog: {
110
+ blogTime: lease.date,
111
+ content: lease.content,
112
+ permissionList: Array.isArray(lease.permissionList) ? lease.permissionList : [],
113
+ isTm: 0,
114
+ isDbCheck: 0,
115
+ formData: '0',
116
+ attachmentIds: '',
117
+ atLinkList: [],
118
+ deleteTargetType: [],
119
+ client: 'pc',
120
+ },
121
+ timezoneOffset: -8,
122
+ },
123
+ operation: 'blog.publish.apply',
124
+ });
125
+ }
126
+ catch (error) {
127
+ if (isBlogNetworkError(error)) {
128
+ throw blogPartialError('发布请求发送后连接中断,结果不确定,禁止自动重试;请先用 blog.query_user 回查', {
129
+ date: lease.date,
130
+ workflow: workflow('blog.publish', 'EXECUTING'),
131
+ }, { stage: 'publish' });
132
+ }
133
+ throw error;
134
+ }
135
+ const after = await fetchDetailsByDate(host, lease.date, session.userId);
136
+ if (!after.found || !after.blog) {
137
+ throw blogPartialError('发布接口已返回,但回查未发现新日报,结果不确定', {
138
+ date: lease.date,
139
+ result,
140
+ workflow: workflow('blog.publish', 'VERIFYING'),
141
+ }, { stage: 'readback' });
142
+ }
143
+ const submitStatus = after.blog.create_time !== null && after.blog.blog_time !== null
144
+ && localDateStr(after.blog.create_time) === localDateStr(after.blog.blog_time) ? '正常' : '补交';
145
+ return {
146
+ status: 'COMPLETE',
147
+ data: {
148
+ date: lease.date,
149
+ blog_id: after.blog.id,
150
+ submit_status: submitStatus,
151
+ verified: true,
152
+ result,
153
+ workflow: workflow('blog.publish', 'COMPLETE'),
154
+ },
155
+ };
156
+ }
157
+ }
@@ -0,0 +1,58 @@
1
+ import { blogValidationError } from '../errors.js';
2
+ import { asDate, dateRange, fetchDetailsByDate, resolveTargetUser, searchUsersByName, submitStatus, } from './shared.js';
3
+ /** 单人日报查询:detailsByDate 逐日扫描(区间上限 62 天);查指定个人的唯一可靠路径 */
4
+ export async function runQueryUser(input, host) {
5
+ const startDate = asDate(input.date, 'date');
6
+ const days = input.end_date === undefined ? [startDate] : dateRange(startDate, input.end_date, 'date', 'end_date');
7
+ // 未指定 user_id/name 时默认查当前登录人(与 edit/comment/like/remind 口径一致,查自己的唯一路径)
8
+ const { userId, userName } = await resolveTargetUser(input, host);
9
+ const items = [];
10
+ for (const date of days) {
11
+ const detail = await fetchDetailsByDate(host, date, userId);
12
+ // 补交状态:有日报按 createTime vs blogTime 判定,无日报为「未提交」
13
+ const status = detail.blog ? submitStatus(detail.blog) : '未提交';
14
+ items.push({
15
+ date,
16
+ found: detail.found,
17
+ submitted: detail.found,
18
+ submit_status: status,
19
+ blog: detail.blog,
20
+ });
21
+ }
22
+ return {
23
+ status: 'COMPLETE',
24
+ data: {
25
+ user: { user_id: userId, name: userName },
26
+ range: { start_date: startDate, end_date: days[days.length - 1] },
27
+ total_days: days.length,
28
+ submitted_days: items.filter((item) => item.submitted).length,
29
+ normal_days: items.filter((item) => item.submit_status === '正常').length,
30
+ later_days: items.filter((item) => item.submit_status === '补交').length,
31
+ missing_days: items.filter((item) => item.submit_status === '未提交').length,
32
+ items,
33
+ },
34
+ };
35
+ }
36
+ export class BlogQueryUserOperation {
37
+ name = 'blog.query_user';
38
+ execute(input, { host }) {
39
+ return runQueryUser(input, host);
40
+ }
41
+ }
42
+ export class BlogResolveUsersOperation {
43
+ name = 'blog.resolve_users';
44
+ async execute(input, { host }) {
45
+ const name = typeof input.name === 'string' ? input.name.trim() : '';
46
+ if (!name)
47
+ throw blogValidationError('input_invalid', 'name 必须是非空字符串');
48
+ const users = await searchUsersByName(host, name);
49
+ return {
50
+ status: 'COMPLETE',
51
+ data: {
52
+ name,
53
+ matched: users.length,
54
+ users: users.map((user) => ({ user_id: user.id, name: user.name, department: user.department })),
55
+ },
56
+ };
57
+ }
58
+ }
@@ -0,0 +1,96 @@
1
+ import { asInteger, dateRange, localDateStr, normalizeBlogListItem, normalizeUserType, } from './shared.js';
2
+ /** 服务端单页上限(实测 pageSize 生效、pageNo 被忽略,因此只能靠单页拉满覆盖窗口) */
3
+ const MAX_PAGE_SIZE = 300;
4
+ /**
5
+ * 群体日报查询(blogs + filter.userType)。
6
+ *
7
+ * 服务端行为(内网租户实测):
8
+ * - `filter.startDate/endDate` 不生效(返回全量倒序),窗口裁剪在客户端完成;
9
+ * - `pageNo` 被忽略(第 1/2/3 页返回同一批数据),翻页拿不到更多数据;
10
+ * - `pageSize` 生效,上限 300,且会为「当天没交的人」补占位行(行数 ≈ 人数 × 天数)。
11
+ *
12
+ * 因此默认 pageSize=300 以最大化窗口覆盖;仍不够时在 `_note` 中提示缩小日期范围。
13
+ */
14
+ export async function runGroupQuery(input, host) {
15
+ const userType = normalizeUserType(input.user_type);
16
+ // 复用 dateRange:同时校验日期格式、区间顺序与 62 天上限(与 blog.query_user 口径一致)
17
+ const days = dateRange(input.start_date, input.end_date, 'start_date', 'end_date');
18
+ const startDate = days[0];
19
+ const endDate = days[days.length - 1];
20
+ const keywords = typeof input.keywords === 'string' ? input.keywords : '';
21
+ const pageSize = asInteger(input.page_size, 'page_size', { min: 1, max: MAX_PAGE_SIZE, dflt: MAX_PAGE_SIZE });
22
+ const pageNo = asInteger(input.page_no, 'page_no', { min: 1, max: 10_000, dflt: 1 });
23
+ // 窗口边界与 localDateStr 一致按本地时区 0 点计算,避免跨时区错位
24
+ const startMs = new Date(`${startDate}T00:00:00`).getTime();
25
+ const endMs = new Date(`${endDate}T23:59:59.999`).getTime();
26
+ const value = await host.blogRequest({
27
+ method: 'POST',
28
+ path: '/api/blog/query/blogs',
29
+ body: {
30
+ blogType: 'order',
31
+ keywords,
32
+ pageNo,
33
+ pageSize,
34
+ filter: {
35
+ userType,
36
+ startDate,
37
+ endDate,
38
+ timezoneOffset: -8,
39
+ },
40
+ },
41
+ operation: 'blog.query',
42
+ });
43
+ const raw = Array.isArray(value?.blogList) ? value.blogList : [];
44
+ const items = raw
45
+ .map((item) => normalizeBlogListItem(item))
46
+ .filter((item) => Boolean(item))
47
+ .filter((item) => item.date >= startMs && item.date <= endMs);
48
+ const oldestRaw = raw.length
49
+ ? Math.min(...raw.map((item) => Number(item?.date)).filter(Number.isFinite))
50
+ : null;
51
+ // 返回行数已顶到单页上限、且最旧一行仍晚于起始日 → 窗口更早的部分没拿到
52
+ const truncated = raw.length >= pageSize && oldestRaw !== null && localDateStr(oldestRaw) > startDate;
53
+ // 对外输出统一用 YYYY-MM-DD(毫秒原值保留在 date_ms,仅供程序化比较)
54
+ const view = items.map((item) => ({
55
+ date: item.date_str,
56
+ date_ms: item.date,
57
+ user_id: item.user_id,
58
+ username: item.username,
59
+ submit_status: item.submit_status,
60
+ blog: item.blog,
61
+ }));
62
+ const submitted = items.filter((item) => item.submitted);
63
+ const people = [...new Map(items.map((item) => [item.user_id, item.username])).entries()]
64
+ .map(([userId, username]) => ({ user_id: userId, username }));
65
+ // 未提交按「人天」列出(同一人部分日期未交也要暴露),并单独给出整窗一次未交的人员
66
+ const missing = items
67
+ .filter((item) => !item.submitted)
68
+ .map((item) => ({ date: item.date_str, user_id: item.user_id, username: item.username }));
69
+ const missingPeople = people
70
+ .filter((person) => !items.some((item) => item.user_id === person.user_id && item.submitted))
71
+ .map((person) => person.username);
72
+ return {
73
+ status: 'COMPLETE',
74
+ data: {
75
+ user_type: userType,
76
+ range: { start_date: startDate, end_date: endDate },
77
+ total_rows_in_window: items.length,
78
+ submitted_count: submitted.length,
79
+ missing_count: missing.length,
80
+ not_submitted: missing,
81
+ not_submitted_people: missingPeople,
82
+ people,
83
+ items: view,
84
+ page: { page_size: pageSize, page_no: pageNo, rows_returned: raw.length },
85
+ _note: truncated
86
+ ? `服务端单次最多返回 ${pageSize} 行(pageNo 被服务端忽略,无法翻页),本次最旧仅到 ${localDateStr(oldestRaw)},早于 ${startDate} 的部分未覆盖;请缩小日期范围(或减少人数范围)后重查`
87
+ : undefined,
88
+ },
89
+ };
90
+ }
91
+ export class BlogQueryOperation {
92
+ name = 'blog.query';
93
+ execute(input, { host }) {
94
+ return runGroupQuery(input, host);
95
+ }
96
+ }
@@ -0,0 +1,54 @@
1
+ import { blogValidationError } from '../errors.js';
2
+ import { blogOperations } from '../manifest.js';
3
+ import { BlogCommentsOperation } from './comments.js';
4
+ import { BlogCommentApplyOperation, BlogCommentPrepareOperation } from './comment.js';
5
+ import { BlogEditApplyOperation, BlogEditPrepareOperation } from './edit.js';
6
+ import { BlogExportOperation } from './export.js';
7
+ import { BlogLikeApplyOperation, BlogLikePrepareOperation } from './like.js';
8
+ import { BlogDefaultPermissionOperation, BlogPublishApplyOperation, BlogPublishPrepareOperation, } from './publish.js';
9
+ import { BlogQueryOperation } from './query.js';
10
+ import { BlogQueryUserOperation, BlogResolveUsersOperation } from './query-user.js';
11
+ import { BlogRemindApplyOperation, BlogRemindPrepareOperation } from './remind.js';
12
+ /** manifest(WHAT)与 handler(HOW)一一对应;任一侧缺项在启动时即抛错。 */
13
+ function buildRegistry() {
14
+ const registry = new Map();
15
+ const handlers = [
16
+ new BlogQueryOperation(),
17
+ new BlogQueryUserOperation(),
18
+ new BlogResolveUsersOperation(),
19
+ new BlogCommentsOperation(),
20
+ new BlogDefaultPermissionOperation(),
21
+ new BlogExportOperation(),
22
+ new BlogPublishPrepareOperation(),
23
+ new BlogPublishApplyOperation(),
24
+ new BlogEditPrepareOperation(),
25
+ new BlogEditApplyOperation(),
26
+ new BlogCommentPrepareOperation(),
27
+ new BlogCommentApplyOperation(),
28
+ new BlogLikePrepareOperation(),
29
+ new BlogLikeApplyOperation(),
30
+ new BlogRemindPrepareOperation(),
31
+ new BlogRemindApplyOperation(),
32
+ ];
33
+ for (const handler of handlers) {
34
+ if (registry.has(handler.name))
35
+ throw new Error(`duplicate blog operation handler: ${handler.name}`);
36
+ registry.set(handler.name, handler);
37
+ }
38
+ for (const contract of blogOperations) {
39
+ if (!registry.has(contract.name))
40
+ throw new Error(`missing blog operation handler: ${contract.name}`);
41
+ }
42
+ return registry;
43
+ }
44
+ const blogOperationRegistry = buildRegistry();
45
+ export function getBlogOperation(operation) {
46
+ const handler = blogOperationRegistry.get(operation);
47
+ if (!handler) {
48
+ throw blogValidationError('operation_unknown', `日报模块不支持操作:${operation}`);
49
+ }
50
+ return handler;
51
+ }
52
+ export function listBlogOperations() {
53
+ return [...blogOperationRegistry.keys()];
54
+ }