weaver-work-cli 0.1.28 → 0.1.31

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 (219) hide show
  1. package/dist/chunks/archive-U766FTFH.js +2537 -0
  2. package/dist/chunks/archive-oa-DMWEKX3L.js +1317 -0
  3. package/dist/chunks/asset-TCR7VDEV.js +2109 -0
  4. package/dist/chunks/attachment-content-AQNX5N3S.js +23 -0
  5. package/dist/chunks/blog-EVZWGDVM.js +2157 -0
  6. package/dist/chunks/caizhilian-JOWS6SL5.js +10087 -0
  7. package/dist/chunks/calendar-5WNCV66Q.js +1195 -0
  8. package/dist/chunks/chunk-2DUV44UX.js +15606 -0
  9. package/dist/chunks/chunk-324YPTC2.js +1607 -0
  10. package/dist/chunks/chunk-65QMVWHS.js +231 -0
  11. package/dist/chunks/chunk-6V7ZLF34.js +387 -0
  12. package/dist/chunks/chunk-AC7KJWC5.js +358 -0
  13. package/dist/chunks/chunk-AJ62BGAW.js +29 -0
  14. package/dist/chunks/chunk-BUSC6UAE.js +43 -0
  15. package/dist/chunks/chunk-CVZ2JOSW.js +62 -0
  16. package/dist/chunks/chunk-D4LTJSJD.js +477 -0
  17. package/dist/chunks/chunk-EG6ZU444.js +654 -0
  18. package/dist/chunks/chunk-EZALDOQH.js +383 -0
  19. package/dist/chunks/chunk-FMPJJYYI.js +106 -0
  20. package/dist/chunks/chunk-G2JSIUWF.js +769 -0
  21. package/dist/chunks/chunk-GCG2NMWR.js +703 -0
  22. package/dist/chunks/chunk-GPMFD4RD.js +75 -0
  23. package/dist/chunks/chunk-H3WDBYJW.js +508 -0
  24. package/dist/chunks/chunk-H6XSTSLM.js +2357 -0
  25. package/dist/chunks/chunk-J7RJTZSV.js +5556 -0
  26. package/dist/chunks/chunk-LUSPKZKY.js +53 -0
  27. package/dist/chunks/chunk-NIAJ5ZCG.js +73 -0
  28. package/dist/chunks/chunk-NOODWMCL.js +584 -0
  29. package/dist/chunks/chunk-OOUTBLZ4.js +51 -0
  30. package/dist/chunks/chunk-P26ZWSKU.js +73 -0
  31. package/dist/chunks/chunk-QWY6PGL6.js +3472 -0
  32. package/dist/chunks/chunk-R2IJAK7T.js +464 -0
  33. package/dist/chunks/chunk-R7O5PUWO.js +205 -0
  34. package/dist/chunks/chunk-RCGYCOMG.js +6962 -0
  35. package/dist/chunks/chunk-VZTXRG6X.js +403 -0
  36. package/dist/chunks/chunk-WHD4XMRP.js +161 -0
  37. package/dist/chunks/chunk-YLCYX7LE.js +371 -0
  38. package/dist/chunks/chunk-ZEDWSH32.js +68 -0
  39. package/dist/chunks/continuation-2CKSBLA6.js +11 -0
  40. package/dist/chunks/continuation-HZIZRZBX.js +13 -0
  41. package/dist/chunks/continuation-NLECEJPX.js +11 -0
  42. package/dist/chunks/ebuilder-form-NJ4YK6CC.js +2276 -0
  43. package/dist/chunks/ehr-MMYN36VA.js +5241 -0
  44. package/dist/chunks/employee-SAK2QYXX.js +11 -0
  45. package/dist/chunks/esb-HIWHOU7Q.js +141 -0
  46. package/dist/chunks/fna-KBACQTA5.js +1444 -0
  47. package/dist/chunks/host-3OHS5QUX.js +21 -0
  48. package/dist/chunks/host-IG6KEH5Q.js +19 -0
  49. package/dist/chunks/host-KQL5N4HO.js +23 -0
  50. package/dist/chunks/host-LLSZQCWR.js +17 -0
  51. package/dist/chunks/hrm-LYEC67ZQ.js +244 -0
  52. package/dist/chunks/im-RN2UKAEU.js +252 -0
  53. package/dist/chunks/invoice-EEPHLAF6.js +5179 -0
  54. package/dist/chunks/jiuchuanhui-WVVZYSJE.js +1940 -0
  55. package/dist/chunks/lazy-dispatch-MDOFFL2S.js +28 -0
  56. package/dist/chunks/mail-LNQLTF6L.js +2695 -0
  57. package/dist/chunks/meeting-XSD3BDVQ.js +504 -0
  58. package/dist/chunks/msg-parse-4GSQ2WTO.js +41 -0
  59. package/dist/chunks/okr-TMWTIU2K.js +2482 -0
  60. package/dist/chunks/plan-A2FSFA7T.js +2025 -0
  61. package/dist/chunks/project-T26QLBQF.js +1361 -0
  62. package/dist/chunks/registry-2VLUAESA.js +14 -0
  63. package/dist/chunks/registry-JCFEYXOE.js +18 -0
  64. package/dist/chunks/registry-NYPYMZXG.js +16 -0
  65. package/dist/chunks/registry-Q7OQJ3SU.js +19 -0
  66. package/dist/chunks/registry-ZJMJVWFA.js +19 -0
  67. package/dist/chunks/room-recommend-UR4ZDZS6.js +20 -0
  68. package/dist/chunks/schedule-5L2PM4M6.js +24 -0
  69. package/dist/chunks/state-Y2XBP6YE.js +15 -0
  70. package/dist/chunks/workflow-TR6PG2SL.js +245 -0
  71. package/dist/cmd/argv.js +45 -0
  72. package/dist/cmd/docs/index.js +3 -3
  73. package/dist/cmd/lazy-dispatch.js +20 -0
  74. package/dist/cmd/root.js +2 -4
  75. package/dist/cmd/setup/index.js +2 -2
  76. package/dist/cmd/skills/index.js +2 -2
  77. package/dist/core/package-paths.js +47 -0
  78. package/dist/index.js +163 -7
  79. package/dist/internal/e10/auth/xiaoe.js +3 -3
  80. package/dist/internal/e10/request-runtime.js +18 -62
  81. package/dist/internal/skills/detector.js +2 -2
  82. package/dist/shortcuts/contract/data/performance-catalog.js +1306 -0
  83. package/dist/shortcuts/contract/host.js +91 -35
  84. package/dist/shortcuts/contract/index.js +213 -0
  85. package/dist/shortcuts/contract/manifest.js +928 -0
  86. package/dist/shortcuts/contract/ooxml/docx.js +295 -0
  87. package/dist/shortcuts/contract/ooxml/xlsx.js +221 -0
  88. package/dist/shortcuts/contract/ooxml/zip.js +158 -0
  89. package/dist/shortcuts/contract/operations/assistant.js +1156 -0
  90. package/dist/shortcuts/contract/operations/clause-library.js +522 -0
  91. package/dist/shortcuts/contract/operations/compare.js +193 -0
  92. package/dist/shortcuts/contract/operations/credit-page.js +34 -0
  93. package/dist/shortcuts/contract/operations/flow.js +98 -0
  94. package/dist/shortcuts/contract/operations/page.js +66 -0
  95. package/dist/shortcuts/contract/operations/performance.js +733 -0
  96. package/dist/shortcuts/contract/operations/quote.js +557 -0
  97. package/dist/shortcuts/contract/operations/recommend.js +78 -0
  98. package/dist/shortcuts/contract/operations/registry.js +18 -0
  99. package/dist/shortcuts/contract/operations/review.js +83 -0
  100. package/dist/shortcuts/contract/operations/shared.js +37 -0
  101. package/dist/shortcuts/contract/operations/upload.js +108 -0
  102. package/dist/shortcuts/contract/operations/write.js +3 -2
  103. package/dist/shortcuts/index.js +140 -40
  104. package/dist/shortcuts/invoice/host.js +2 -1
  105. package/dist/shortcuts/invoice/manifest.js +36 -12
  106. package/dist/shortcuts/invoice/operations/issuing-qys.js +132 -36
  107. package/dist/shortcuts/invoice/operations/reim-auto.js +9 -0
  108. package/dist/shortcuts/invoice/operations/reim.js +81 -5
  109. package/dist/shortcuts/invoice/operations/shared.js +12 -5
  110. package/dist/shortcuts/meeting/host.js +45 -3
  111. package/dist/shortcuts/meeting/invitation/docx.js +442 -0
  112. package/dist/shortcuts/meeting/operations/attachment-content.js +418 -0
  113. package/dist/shortcuts/meeting/operations/attachment.js +442 -0
  114. package/dist/shortcuts/meeting/operations/eb/cache.js +57 -0
  115. package/dist/shortcuts/meeting/operations/eb/catalog.js +343 -28
  116. package/dist/shortcuts/meeting/operations/eb/conflict.js +372 -11
  117. package/dist/shortcuts/meeting/operations/eb/create-flow.js +710 -0
  118. package/dist/shortcuts/meeting/operations/eb/decision.js +47 -1
  119. package/dist/shortcuts/meeting/operations/eb/factory.js +129 -11
  120. package/dist/shortcuts/meeting/operations/eb/index.js +122 -2
  121. package/dist/shortcuts/meeting/operations/eb/room-recommend.js +378 -0
  122. package/dist/shortcuts/meeting/operations/eb/room-summary.js +84 -0
  123. package/dist/shortcuts/meeting/operations/eb/schedule.js +450 -0
  124. package/dist/shortcuts/meeting/operations/from-group-chat.js +576 -0
  125. package/dist/shortcuts/meeting/operations/invitation.js +458 -0
  126. package/dist/shortcuts/meeting/operations/minute-content.js +266 -0
  127. package/dist/shortcuts/meeting/operations/pipeline.js +489 -0
  128. package/dist/shortcuts/meeting/operations/qrcode.js +131 -0
  129. package/dist/shortcuts/meeting/operations/receipts.js +1 -1
  130. package/dist/shortcuts/meeting/operations/registry.js +18 -0
  131. package/dist/shortcuts/meeting/operations/summary.js +276 -0
  132. package/dist/shortcuts/meeting/operations/task-notify.js +325 -0
  133. package/dist/shortcuts/meeting/operations.js +42 -1
  134. package/dist/shortcuts/meeting/qr/encoder.js +427 -0
  135. package/dist/shortcuts/meeting/qr/render.js +103 -0
  136. package/dist/shortcuts/workflow/index.js +3 -0
  137. package/dist/shortcuts/workflow/operations/form.js +17 -5
  138. package/dist/shortcuts/workflow/operations/read.js +13 -6
  139. package/dist/shortcuts/workflow/operations/registry.js +2 -0
  140. package/dist/shortcuts/workflow/operations/todoSmartConvert/businessRules.js +331 -0
  141. package/dist/shortcuts/workflow/operations/todoSmartConvert/detailFieldRules.js +133 -0
  142. package/dist/shortcuts/workflow/operations/todoSmartConvert/summaryCache.js +326 -0
  143. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoDetailCache.js +113 -0
  144. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoDetailFetcher.js +259 -0
  145. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoListRows.js +64 -0
  146. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoOutputPlanner.js +28 -0
  147. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.aggregate.js +70 -0
  148. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.collect.js +267 -0
  149. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.core.js +392 -0
  150. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.detail.js +165 -0
  151. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.group.js +448 -0
  152. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.groupSummary.js +348 -0
  153. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.js +222 -0
  154. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.pipeline.js +66 -0
  155. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.smartGroup.js +502 -0
  156. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.summary.js +540 -0
  157. package/docs/_catalog.md +1 -0
  158. package/docs/contract.md +39 -4
  159. package/docs/invoice.md +7 -7
  160. package/docs/todo-smart-convert/README.md +171 -0
  161. package/docs/todo-smart-convert/data-contract.md +236 -0
  162. package/docs/todo-smart-convert/mock/ai-todo-query-target.json +209 -0
  163. package/docs/todo-smart-convert/subgroup-rules.md +245 -0
  164. package/package.json +3 -2
  165. package/scripts/bundle.mjs +63 -0
  166. package/scripts/package-skill.mjs +1 -1
  167. package/skill-template/business-info.json +25 -4
  168. package/skill-template/domains/jinchengda.md +16 -3
  169. package/skill-template/domains/yepiaotong.md +3 -3
  170. package/skills/weaver-e10-jinchengda/SKILL.md +111 -31
  171. package/skills/weaver-e10-jinchengda/references/contract-agent-entry.md +7 -13
  172. package/skills/weaver-e10-jinchengda/references/contract-ai-assistant.md +150 -0
  173. package/skills/weaver-e10-jinchengda/references/contract-ai-review.md +87 -0
  174. package/skills/weaver-e10-jinchengda/references/contract-clause-library-crud.md +111 -0
  175. package/skills/weaver-e10-jinchengda/references/contract-clause-recommender.md +0 -15
  176. package/skills/weaver-e10-jinchengda/references/contract-clause-reference-save.md +0 -13
  177. package/skills/weaver-e10-jinchengda/references/contract-credit-page.md +50 -0
  178. package/skills/weaver-e10-jinchengda/references/contract-disabled-capabilities.md +4 -2
  179. package/skills/weaver-e10-jinchengda/references/contract-doc-compare.md +74 -0
  180. package/skills/weaver-e10-jinchengda/references/contract-flow-launch.md +84 -0
  181. package/skills/weaver-e10-jinchengda/references/contract-performance-query.md +29 -0
  182. package/skills/weaver-e10-jinchengda/references/contract-quote-to-contract.md +28 -0
  183. package/skills/weaver-e10-jinchengda/references/contract-request-status.md +2 -21
  184. package/skills/weaver-e10-jinchengda/references/contract-request-write.md +0 -24
  185. package/skills/weaver-e10-jinchengda/references/contract-signing-subjects.md +0 -15
  186. package/skills/weaver-e10-jinchengda/references/contract-subject-create.md +0 -17
  187. package/skills/weaver-e10-jinchengda/references/contract-template-recommend.md +70 -0
  188. package/skills/weaver-e10-jinchengda/references/contract-template-selector.md +0 -15
  189. package/skills/weaver-e10-jinchengda/references/contract-workflow-discovery.md +0 -15
  190. package/skills/weaver-e10-jinchengda/references/source-manifest.json +1168 -152
  191. package/skills/weaver-e10-meeting/SKILL.md +493 -17
  192. package/skills/weaver-e10-meeting/references/meeting-change.md +4 -4
  193. package/skills/weaver-e10-meeting/references/meeting-create.md +6 -6
  194. package/skills/weaver-e10-meeting/references/meeting-eb.md +503 -27
  195. package/skills/weaver-e10-meeting/references/meeting-invitation.md +113 -0
  196. package/skills/weaver-e10-meeting/references/meeting-lifecycle.md +6 -6
  197. package/skills/weaver-e10-meeting/references/meeting-minute-file.md +425 -0
  198. package/skills/weaver-e10-meeting/references/meeting-queries.md +4 -4
  199. package/skills/weaver-e10-meeting/references/meeting-receipts-signs.md +15 -15
  200. package/skills/weaver-e10-meeting/references/meeting-verification.md +19 -11
  201. package/skills/weaver-e10-meeting.zip +0 -0
  202. package/skills/weaver-e10-workflow/SKILL.md +23 -1
  203. package/skills/weaver-e10-workflow/references/todo-smart-convert.md +282 -0
  204. package/skills/weaver-e10-workflow/scripts/todo-summary.cjs +695 -0
  205. package/skills/weaver-e10-yepiaotong/SKILL.md +15 -11
  206. package/skills/weaver-e10-yepiaotong/references/invoice-enterprise-list.md +1 -1
  207. package/skills/weaver-e10-yepiaotong/references/invoice-issuing-examples.md +10 -7
  208. package/skills/weaver-e10-yepiaotong/references/invoice-issuing-form-fill-rules.md +41 -14
  209. package/skills/weaver-e10-yepiaotong/references/invoice-issuing-workflow.md +54 -26
  210. package/skills/weaver-e10-yepiaotong/references/invoice-personal-list.md +6 -5
  211. package/skills/weaver-e10-yepiaotong/references/invoice-reim.md +14 -11
  212. package/skills/weaver-e10-yepiaotong/references/reim-api-reference.md +97 -56
  213. package/skills/weaver-e10-yepiaotong/references/reim-examples.md +84 -60
  214. package/skills/weaver-e10-yepiaotong/references/reim-form-fill-rules.md +142 -105
  215. package/skills/weaver-e10-yepiaotong/references/reim-matching-rules.md +53 -194
  216. package/skills/weaver-e10-yepiaotong/references/reim-phone-subsidy-rules.md +7 -7
  217. package/skills/weaver-e10-yepiaotong/references/reim-workflow.md +164 -72
  218. package/skills/weaver-e10-yepiaotong/references/source-manifest.json +8 -2
  219. package/templates/meeting/invitation.docx +0 -0
@@ -1,4 +1,4 @@
1
- import { meetingConflictError } from '../../errors.js';
1
+ import { meetingConflictError, meetingValidationError } from '../../errors.js';
2
2
  import { meetingSuccess, SCHEMA_DATETIME_MINUTE, SCHEMA_ID } from '../types.js';
3
3
  import { assertDateTimeMinute, toIdCsv } from '../shared.js';
4
4
  import { EB_BASE, ebBusinessSuccess, extractEbRows, queryBody } from './factory.js';
@@ -77,13 +77,20 @@ async function listAllEbRows(env, interfacePk, baseQuery, operation) {
77
77
  return { rows, requests };
78
78
  }
79
79
  /**
80
- * 读取应用设置(query_meeting_baseset,返回 1 条,多条取第一条):
80
+ * 读取应用设置(query_meeting_baseset):
81
81
  * 同时取「冲突策略」与「建会路径开关」,一次请求供两处使用。
82
+ *
83
+ * 口径:正常情况下该表只有**一条有效记录**(`data_status=1`)。
84
+ * 但接口返回里可能混入历史/无效记录,且顺序不保证 —— 直接"取第一条"有机会读到失效配置,
85
+ * 表现为"同一个请求时而放行、时而被打回"。因此这里先按 `data_status` 过滤,再按 `update_time`
86
+ * 取最新一条,并把条数带回(多条时上层会提示以哪条为准)。
82
87
  */
83
88
  export async function fetchEbAppSettings(env, operation) {
84
89
  try {
85
- const rows = await requestEbList(env, 'query_meeting_baseset', { pageNo: 1, pageSize: 5 }, operation);
86
- const base = rows[0] ?? {};
90
+ const rows = await requestEbList(env, 'query_meeting_baseset', { pageNo: 1, pageSize: 20 }, operation);
91
+ const valid = rows.filter((row) => String(row.data_status ?? '1') === '1');
92
+ const ordered = [...valid].sort((left, right) => (String(right.update_time ?? '').localeCompare(String(left.update_time ?? ''))));
93
+ const base = ordered[0] ?? {};
87
94
  return {
88
95
  roomChk: String(base.room_conflict_chk ?? ''),
89
96
  roomAction: String(base.room_conflict ?? ''),
@@ -93,6 +100,9 @@ export async function fetchEbAppSettings(env, operation) {
93
100
  openMtApprove: String(base.open_mt_approve ?? ''),
94
101
  mtNewWf: String(base.mt_new_wf ?? ''),
95
102
  mtRepeatNewWf: String(base.mt_repeat_new_wf ?? ''),
103
+ settingsRows: valid.length,
104
+ settingsId: String(base.id ?? ''),
105
+ settingsUpdatedAt: String(base.update_time ?? ''),
96
106
  };
97
107
  }
98
108
  catch (error) {
@@ -105,6 +115,7 @@ export async function fetchEbAppSettings(env, operation) {
105
115
  openMtApprove: '',
106
116
  mtNewWf: '',
107
117
  mtRepeatNewWf: '',
118
+ settingsRows: 0,
108
119
  error: errorText(error),
109
120
  };
110
121
  }
@@ -138,7 +149,9 @@ export function assertEbCreatePathAllowed(settings) {
138
149
  throw meetingConflictError('conflict_rejected', `应用设置不允许直接建会:${blocked.join(';')}。该应用的会议必须经流程创建`
139
150
  + `(新建会议工作流 mt_new_wf=${settings.mtNewWf || '未配置'},周期会议工作流 mt_repeat_new_wf=${settings.mtRepeatNewWf || '未配置'})——`
140
151
  + 'CLI 不直接建会、也不代替流程建会(否则会产生流程之外、request_id 为空的会议,且与流程/ESB 自动建会重复)。'
141
- + '请改用 weaver-e10-workflow 发起流程建会;如需 CLI 直接建会,请管理员先在会议应用设置中关闭该开关。', {
152
+ + '这属于应用管控,CLI 不会绕过 —— 但也不用停手:改走 **meeting.eb.meeting.create.flow**'
153
+ + '(prepare→apply)即可,它会按 mt_new_wf 载入建会流程表单、提交审批,并在会议生成后自动出邀请函。'
154
+ + '如需直连建会,请管理员先在会议应用设置中关闭该开关。', {
142
155
  onlyFlowCreate: settings.onlyFlowCreate,
143
156
  openMtApprove: settings.openMtApprove,
144
157
  mtNewWf: settings.mtNewWf,
@@ -330,7 +343,7 @@ export async function runEbConflictCheck(input, env, operation, options = {}) {
330
343
  * 连冲突检测都不必做(结论已定局);读应用设置失败同样拒绝。
331
344
  * 2. 冲突检测仅在拿到起止时间、且传了 address / hrm_members 时才发请求(无目标即无冲突)。
332
345
  */
333
- export const ebMeetingWritePreflight = async ({ endpoint, payload, args, env }) => {
346
+ export const ebMeetingWritePreflight = async ({ endpoint, payload, args, env, extras }) => {
334
347
  const isCreate = endpoint.interfacePk === 'create_meeting';
335
348
  const isChange = endpoint.interfacePk === 'change_meeting';
336
349
  if (!isCreate && !isChange)
@@ -344,12 +357,20 @@ export const ebMeetingWritePreflight = async ({ endpoint, payload, args, env })
344
357
  const hrmMembers = typeof main.hrm_members === 'string' && main.hrm_members ? main.hrm_members : undefined;
345
358
  // 建会必读应用设置(闸门);变更只在确有冲突检测目标时才读(避免多一次请求)。
346
359
  const settings = isCreate || address || hrmMembers ? await fetchEbAppSettings(env, operation) : undefined;
347
- if (settings && isCreate)
360
+ const preflightWarnings = [];
361
+ if (settings && isCreate) {
348
362
  assertEbCreatePathAllowed(settings);
363
+ if ((settings.settingsRows ?? 0) > 1) {
364
+ // 多条设置时只按最新一条判断,但必须告诉用户:另一份设置里的开关不生效
365
+ preflightWarnings.push(`会议应用设置存在 ${settings.settingsRows} 条记录,本次按最新一条(id=${settings.settingsId ?? '未知'},`
366
+ + `更新于 ${settings.settingsUpdatedAt || '未知'})判断建会路径与冲突策略;`
367
+ + '如需以另一条为准,请先在系统页面清理重复的设置记录。');
368
+ }
369
+ }
349
370
  if (!begin || !end)
350
- return [];
371
+ return preflightWarnings;
351
372
  if (!address && !hrmMembers)
352
- return [];
373
+ return preflightWarnings;
353
374
  const outcome = await runEbConflictCheck({
354
375
  beginDatetime: begin,
355
376
  endDatetime: end,
@@ -358,7 +379,53 @@ export const ebMeetingWritePreflight = async ({ endpoint, payload, args, env })
358
379
  ...(typeof main.org_members === 'string' && main.org_members ? { orgMembers: main.org_members } : {}),
359
380
  ...(typeof args.id === 'string' || typeof args.id === 'number' ? { excludeMtId: String(args.id) } : {}),
360
381
  }, env, operation, settings ? { settings } : {});
361
- return outcome.warnings;
382
+ const extraWarnings = [];
383
+ // 冲突了不能只说"撞了"——直接算出「所有人共同空闲」的候选时段,让用户能选
384
+ // (2026-10-28 用户反馈:冲突提示有了,但没有给出可选的日期/时段)。
385
+ if (outcome.member && outcome.member.conflicts.length > 0) {
386
+ extras.memberConflicts = outcome.member.conflicts;
387
+ if (begin && end && hrmMembers) {
388
+ try {
389
+ const durationMinutes = Math.max(15, Math.round((Date.parse(end.replace(' ', 'T')) - Date.parse(begin.replace(' ', 'T'))) / 60000) || 60);
390
+ // 用户口径(2026-10-28):**约的是哪天就只推荐哪天**,不跨天乱推;
391
+ // 并且同时核对「会议回执」与「日程(ESB)」,不能只看会议。
392
+ const day = begin.slice(0, 10);
393
+ const free = await findCommonFreeSlots(env, {
394
+ rangeStart: `${day} 00:00`,
395
+ rangeEnd: `${day} 23:59`,
396
+ durationMinutes,
397
+ members: hrmMembers.split(/[,;,;\s]+/).map((item) => item.trim()).filter((item) => item !== ''),
398
+ ...(typeof args.id === 'string' || typeof args.id === 'number' ? { excludeMtId: String(args.id) } : {}),
399
+ maxSlots: 3,
400
+ maxDays: 1,
401
+ includeSchedule: true,
402
+ });
403
+ if (free.slots.length > 0) {
404
+ extras.suggestedSlots = free.slots;
405
+ extras.slotOptions = free.slots.map((slot, index) => ({
406
+ key: `T${index + 1}`,
407
+ // 选项里必须写清"为什么推荐这个时段",否则用户看到的只是两个时间点(2026-10-28 用户反馈)
408
+ label: `${slot.begin.slice(11)}~${slot.end.slice(11)}(${day}) · 全部参会人空闲`,
409
+ value: { begin: slot.begin, end: slot.end },
410
+ reason: '该时段所有参会人都没有会议与日程占用',
411
+ }));
412
+ extraWarnings.push(`当天(${day})推荐改期到:${free.slots.map((slot, index) => `[T${index + 1}] ${slot.begin.slice(11)}~${slot.end.slice(11)}`).join(';')}`
413
+ + '(已同时核对会议回执与日程)——请用选项方式让用户挑一个。');
414
+ }
415
+ else if (free.blockingSummary) {
416
+ extraWarnings.push(free.blockingSummary);
417
+ extras.dayBlocked = { day, summary: free.blockingSummary };
418
+ }
419
+ extras.scheduleItems = free.scheduleItems;
420
+ extras.busyByMember = free.busyByMember;
421
+ extras.availabilityCost = free.cost;
422
+ }
423
+ catch (error) {
424
+ extraWarnings.push(`共同空闲时段计算失败(${String(error?.message ?? error).slice(0, 80)}),请手动换时间`);
425
+ }
426
+ }
427
+ }
428
+ return [...preflightWarnings, ...outcome.warnings, ...extraWarnings];
362
429
  };
363
430
  /** 只查不建:会议室冲突检测 operation。 */
364
431
  const ebConflictRoom = {
@@ -458,4 +525,298 @@ const ebConflictMember = {
458
525
  }, outcome.warnings.length > 0 ? { warnings: outcome.warnings } : {});
459
526
  },
460
527
  };
461
- export const meetingEbConflictOperations = [ebConflictRoom, ebConflictMember];
528
+ function formatNowForRange() {
529
+ const now = new Date();
530
+ const pad = (n) => String(n).padStart(2, '0');
531
+ return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}`;
532
+ }
533
+ function formatMinuteLocal(date) {
534
+ return formatMinute(date);
535
+ }
536
+ function parseMinute(text) {
537
+ const match = /^(\d{4})-(\d{2})-(\d{2})[ T](\d{2}):(\d{2})/.exec(String(text ?? '').trim());
538
+ if (!match)
539
+ return null;
540
+ return new Date(Number(match[1]), Number(match[2]) - 1, Number(match[3]), Number(match[4]), Number(match[5]), 0, 0);
541
+ }
542
+ function formatMinute(date) {
543
+ const pad = (n) => String(n).padStart(2, '0');
544
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
545
+ }
546
+ /** 员工 id → 姓名(批量;失败返回空表,summary 里退回 id)。 */
547
+ async function resolveMemberNames(env, ids) {
548
+ const map = new Map();
549
+ const unique = [...new Set(ids.filter((id) => id !== ''))];
550
+ if (unique.length === 0)
551
+ return map;
552
+ try {
553
+ const { hrmEmployeeOperations } = await import('../../../hrm/operations/employee.js');
554
+ const batch = hrmEmployeeOperations.find((operation) => operation.name === 'hrm.employee.batch');
555
+ if (!batch)
556
+ return map;
557
+ const result = await batch.handler({ ids: unique }, env);
558
+ const rows = (result?.data?.rows ?? result?.data?.userList ?? []);
559
+ for (const row of rows) {
560
+ const id = String(row.id ?? '').trim();
561
+ const name = String(row.name ?? '').trim();
562
+ if (id && name)
563
+ map.set(id, name);
564
+ }
565
+ }
566
+ catch {
567
+ // 名字拿不到就用 id,不影响判定
568
+ }
569
+ return map;
570
+ }
571
+ /**
572
+ * 扫「所有人共同空闲」的时段。
573
+ *
574
+ * 两个维度都要算(2026-10-28 用户要求):
575
+ * ① **会议回执**:一次拉候选 + 按 mt_id 批量回执;
576
+ * ② **日程(ESB)**:一次调用覆盖该范围全部符合条件日程(会议日程类型由服务端过滤),
577
+ * 只返回提示语时也能解析出「时间 + 日程名 + 人」,因为查询范围限定在同一天,HH:mm 可安全补上日期。
578
+ *
579
+ * 默认**只看 rangeStart 当天**(用户口径:约的是哪天就推荐哪天)。
580
+ */
581
+ export async function findCommonFreeSlots(env, input) {
582
+ const members = input.members.map((item) => String(item).trim()).filter((item) => item !== '');
583
+ const wanted = new Set(members);
584
+ const duration = Math.max(15, Math.min(24 * 60, Math.round(input.durationMinutes || 60)));
585
+ const step = Math.max(15, Math.min(120, Math.round(input.stepMinutes ?? 30)));
586
+ const workStart = Math.max(0, Math.min(23, input.workStartHour ?? 9));
587
+ const workEnd = Math.max(workStart + 1, Math.min(24, input.workEndHour ?? 18));
588
+ const maxSlots = Math.max(1, Math.min(20, input.maxSlots ?? 3));
589
+ const maxDays = Math.max(1, Math.min(14, input.maxDays ?? 1));
590
+ const includeSchedule = input.includeSchedule !== false;
591
+ const rangeStart = parseMinute(input.rangeStart);
592
+ const rangeEnd = parseMinute(input.rangeEnd);
593
+ const cost = { candidateCalls: 0, receiptCalls: 0, candidates: 0, busyIntervals: 0, scheduleCalls: 0 };
594
+ const empty = { slots: [], idle: false, cost, scheduleItems: [], busyByMember: [] };
595
+ if (!rangeStart || !rangeEnd)
596
+ return empty;
597
+ const detail = new Map();
598
+ const pushBusy = (memberId, item) => {
599
+ const list = detail.get(memberId) ?? [];
600
+ list.push(item);
601
+ detail.set(memberId, list);
602
+ cost.busyIntervals += 1;
603
+ };
604
+ // ① 会议维度
605
+ const candidate = await listAllEbRows(env, 'query_mtDetail_list', { begindatetime: input.rangeStart, enddatetime: input.rangeEnd, mt_status: ACTIVE_MT_STATUS }, 'meeting.eb.conflict.availability');
606
+ cost.candidateCalls += candidate.requests;
607
+ const meetings = candidate.rows
608
+ .map((row) => ({
609
+ id: String(row.id ?? ''),
610
+ name: String(row.mt_name ?? row.name ?? row.mt_name ?? ''),
611
+ begin: parseMinute(String(row.begin_datetime ?? '')),
612
+ end: parseMinute(String(row.end_datetime ?? '')),
613
+ }))
614
+ .filter((item) => item.id && item.id !== (input.excludeMtId ?? '') && item.begin && item.end);
615
+ cost.candidates = meetings.length;
616
+ const byId = new Map(meetings.map((item) => [item.id, item]));
617
+ for (let index = 0; index < meetings.length; index += MT_ID_BATCH) {
618
+ const csv = meetings.slice(index, index + MT_ID_BATCH).map((item) => item.id).join(',');
619
+ const page = await listAllEbRows(env, 'query_meeting_receipt_list', { mt_id: csv }, 'meeting.eb.conflict.availability');
620
+ cost.receiptCalls += page.requests;
621
+ for (const row of page.rows) {
622
+ const meetingId = String(row.mt_id ?? '');
623
+ const memberId = String(row.member_id ?? '');
624
+ const memberType = String(row.member_type ?? '');
625
+ if (!meetingId || !memberId)
626
+ continue;
627
+ if (memberType && memberType !== '1')
628
+ continue;
629
+ if (!wanted.has(memberId))
630
+ continue;
631
+ const meeting = byId.get(meetingId);
632
+ if (!meeting?.begin || !meeting?.end)
633
+ continue;
634
+ pushBusy(memberId, {
635
+ source: 'meeting',
636
+ title: meeting.name || '会议',
637
+ begin: formatMinute(meeting.begin),
638
+ end: formatMinute(meeting.end),
639
+ });
640
+ }
641
+ }
642
+ // ② 日程维度(ESB 一次调完整段范围)
643
+ const scheduleItems = [];
644
+ if (includeSchedule) {
645
+ try {
646
+ const { runScheduleConflictFlow, parseScheduleConflictMessage, resolveMemberIdsByName } = await import('./schedule.js');
647
+ const dayBegin = new Date(rangeStart.getFullYear(), rangeStart.getMonth(), rangeStart.getDate(), 0, 0, 0, 0);
648
+ const dayEnd = new Date(rangeStart.getFullYear(), rangeStart.getMonth(), rangeStart.getDate(), 23, 59, 0, 0);
649
+ const flow = await runScheduleConflictFlow(env, {
650
+ startDate: formatMinute(dayBegin),
651
+ endDate: formatMinute(dayEnd),
652
+ memberIds: members,
653
+ });
654
+ cost.scheduleCalls += 1;
655
+ const rawItems = [];
656
+ for (const row of flow.rows) {
657
+ const main = row?.mainTable ?? row ?? {};
658
+ const type = main.agn_type;
659
+ const typeId = Array.isArray(type) ? String(type[0]?.id ?? '') : String(type?.id ?? type ?? '');
660
+ void typeId;
661
+ rawItems.push({
662
+ title: String(main.agn_name ?? main.name ?? '').trim() || '日程',
663
+ begin: String(main.start_time ?? '').trim(),
664
+ end: String(main.end_time ?? '').trim(),
665
+ names: String(main.participants ?? '')
666
+ .split(/[,;,;\s]+/)
667
+ .map((item) => item.trim())
668
+ .filter((item) => item !== ''),
669
+ });
670
+ }
671
+ if (rawItems.length === 0) {
672
+ for (const item of parseScheduleConflictMessage(flow.message)) {
673
+ rawItems.push({ title: item.title, begin: item.startTime, end: item.endTime, names: item.memberNames });
674
+ }
675
+ }
676
+ const idsByName = await resolveMemberIdsByName(env, rawItems.flatMap((item) => item.names));
677
+ const datePrefix = `${rangeStart.getFullYear()}-${String(rangeStart.getMonth() + 1).padStart(2, '0')}-${String(rangeStart.getDate()).padStart(2, '0')}`;
678
+ for (const item of rawItems) {
679
+ const beginText = item.begin.includes('-') ? item.begin : `${datePrefix} ${item.begin}`;
680
+ const endText = item.end.includes('-') ? item.end : `${datePrefix} ${item.end}`;
681
+ const beginDate = parseMinute(beginText);
682
+ const endDate = parseMinute(endText);
683
+ if (!beginDate || !endDate)
684
+ continue;
685
+ const memberIds = item.names.map((name) => idsByName.get(name) ?? '').filter((id) => id !== '');
686
+ scheduleItems.push({ title: item.title, begin: beginText, end: endText, members: memberIds.length > 0 ? memberIds : item.names });
687
+ for (const memberId of memberIds) {
688
+ if (!wanted.has(memberId))
689
+ continue;
690
+ pushBusy(memberId, { source: 'schedule', title: item.title, begin: beginText, end: endText });
691
+ }
692
+ }
693
+ }
694
+ catch {
695
+ // ESB 失败不阻断推荐:退化为只看会议回执,调用方从 scheduleItems 为空可看出未校验
696
+ }
697
+ }
698
+ const names = await resolveMemberNames(env, [...detail.keys()]);
699
+ const busyByMember = [...detail.entries()].map(([memberId, items]) => ({
700
+ memberId,
701
+ ...(names.get(memberId) ? { memberName: names.get(memberId) } : {}),
702
+ items: items.sort((left, right) => left.begin.localeCompare(right.begin)),
703
+ }));
704
+ const overlaps = (slotStart, slotEnd) => {
705
+ for (const entry of busyByMember) {
706
+ for (const item of entry.items) {
707
+ const begin = parseMinute(item.begin);
708
+ const end = parseMinute(item.end);
709
+ if (!begin || !end)
710
+ continue;
711
+ if (slotStart < end && slotEnd > begin)
712
+ return true;
713
+ }
714
+ }
715
+ return false;
716
+ };
717
+ const now = new Date();
718
+ const slots = [];
719
+ const dayCount = Math.min(maxDays, Math.ceil((rangeEnd.getTime() - rangeStart.getTime()) / 86_400_000) + 1);
720
+ for (let day = 0; day < dayCount && slots.length < maxSlots; day += 1) {
721
+ const dayStart = new Date(rangeStart.getFullYear(), rangeStart.getMonth(), rangeStart.getDate() + day, workStart, 0, 0, 0);
722
+ const dayLimit = new Date(rangeStart.getFullYear(), rangeStart.getMonth(), rangeStart.getDate() + day, workEnd, 0, 0, 0);
723
+ for (let cursor = new Date(dayStart); cursor.getTime() + duration * 60_000 <= dayLimit.getTime(); cursor = new Date(cursor.getTime() + step * 60_000)) {
724
+ const slotStart = new Date(cursor);
725
+ const slotEnd = new Date(cursor.getTime() + duration * 60_000);
726
+ if (slotStart.getTime() < now.getTime() + 5 * 60_000)
727
+ continue; // 过去的时间不算
728
+ if (slotStart < rangeStart || slotEnd > rangeEnd)
729
+ continue;
730
+ if (overlaps(slotStart, slotEnd))
731
+ continue;
732
+ slots.push({ begin: formatMinute(slotStart), end: formatMinute(slotEnd) });
733
+ if (slots.length >= maxSlots)
734
+ break;
735
+ }
736
+ }
737
+ let blockingSummary;
738
+ if (slots.length === 0) {
739
+ const day = `${rangeStart.getFullYear()}-${String(rangeStart.getMonth() + 1).padStart(2, '0')}-${String(rangeStart.getDate()).padStart(2, '0')}`;
740
+ const perMember = busyByMember.map((entry) => {
741
+ const items = entry.items
742
+ .slice(0, 3)
743
+ .map((item) => `${item.source === 'meeting' ? '会议' : '日程'}《${item.title}》${item.begin.slice(11)}~${item.end.slice(11)}`)
744
+ .join('、');
745
+ const more = entry.items.length > 3 ? ` 等 ${entry.items.length} 项` : '';
746
+ return `${entry.memberName ?? entry.memberId}:${items}${more}`;
747
+ });
748
+ blockingSummary = perMember.length > 0
749
+ ? `${day} 当天 ${workStart}:00-${workEnd}:00 内没有所有人都空闲的 ${duration} 分钟时段(已同时核对会议回执与日程)——${perMember.join(';')}`
750
+ : `${day} 当天 ${workStart}:00-${workEnd}:00 内没有找到符合条件的 ${duration} 分钟时段,建议换个日期或缩短时长`;
751
+ }
752
+ return {
753
+ slots,
754
+ idle: cost.busyIntervals === 0,
755
+ cost,
756
+ scheduleItems,
757
+ busyByMember,
758
+ ...(blockingSummary ? { blockingSummary } : {}),
759
+ };
760
+ }
761
+ /** 只查不建:给「某几个人、要开多久」找共同空闲时段。 */
762
+ const ebConflictAvailability = {
763
+ name: 'meeting.eb.conflict.availability',
764
+ resource: 'eb',
765
+ method: 'meeting.eb.conflict.availability',
766
+ action: 'meeting.eb.conflict.availability',
767
+ summary: '参会人共同空闲时段推荐(会议维度):一次拉候选会议 + 批量回执,内存切 30 分钟 slot,给出所有人都空闲的时间段(默认最多 3 个,往后 7 天)',
768
+ capability: 'eb.conflict',
769
+ risk: 'read',
770
+ inputSchema: {
771
+ type: 'object',
772
+ additionalProperties: false,
773
+ properties: {
774
+ rangeStart: { ...SCHEMA_DATETIME_MINUTE, description: '搜索范围开始(必填,yyyy-MM-dd HH:mm)' },
775
+ rangeEnd: { ...SCHEMA_DATETIME_MINUTE, description: '搜索范围结束(必填,yyyy-MM-dd HH:mm)' },
776
+ durationMinutes: { type: 'number', description: '会议时长(分钟,默认 60)' },
777
+ hrmMembers: { type: 'string', description: '参会人 id,逗号分隔(必填)' },
778
+ mtId: { ...SCHEMA_ID, description: '变更会议时排除自身' },
779
+ workStartHour: { type: 'number', description: '工作时段起始小时(默认 9)' },
780
+ workEndHour: { type: 'number', description: '工作时段结束小时(默认 18)' },
781
+ stepMinutes: { type: 'number', description: '候选步长(分钟,默认 30)' },
782
+ maxSlots: { type: 'number', description: '返回几个时段(默认 3,最多 20)' },
783
+ maxDays: { type: 'number', description: '往后找几天(默认 1 = 只找当天,符合"约的是哪天就推荐哪天")' },
784
+ includeSchedule: { type: 'boolean', description: '是否同时核对日程(ESB),默认 true' },
785
+ },
786
+ required: ['rangeStart', 'rangeEnd', 'hrmMembers'],
787
+ },
788
+ handler: async (args, env) => {
789
+ const members = String(args.hrmMembers ?? '')
790
+ .split(/[,;,;\s]+/)
791
+ .map((item) => item.trim())
792
+ .filter((item) => item !== '');
793
+ if (members.length === 0) {
794
+ throw meetingValidationError('members_required', 'hrmMembers 不能为空');
795
+ }
796
+ const result = await findCommonFreeSlots(env, {
797
+ rangeStart: String(args.rangeStart),
798
+ rangeEnd: String(args.rangeEnd),
799
+ durationMinutes: Number(args.durationMinutes ?? 60),
800
+ members,
801
+ ...(args.mtId ? { excludeMtId: String(args.mtId) } : {}),
802
+ ...(args.workStartHour !== undefined ? { workStartHour: Number(args.workStartHour) } : {}),
803
+ ...(args.workEndHour !== undefined ? { workEndHour: Number(args.workEndHour) } : {}),
804
+ ...(args.stepMinutes !== undefined ? { stepMinutes: Number(args.stepMinutes) } : {}),
805
+ ...(args.maxSlots !== undefined ? { maxSlots: Number(args.maxSlots) } : {}),
806
+ ...(args.maxDays !== undefined ? { maxDays: Number(args.maxDays) } : {}),
807
+ ...(args.includeSchedule !== undefined ? { includeSchedule: args.includeSchedule !== false } : {}),
808
+ });
809
+ return meetingSuccess('meeting.eb.conflict.availability', {
810
+ slots: result.slots,
811
+ idle: result.idle,
812
+ members,
813
+ scheduleItems: result.scheduleItems,
814
+ busyByMember: result.busyByMember,
815
+ ...(result.blockingSummary ? { blockingSummary: result.blockingSummary } : {}),
816
+ cost: result.cost,
817
+ }, result.slots.length === 0
818
+ ? { warnings: [result.blockingSummary ?? '范围内没有找到所有人都空闲的时段,建议换个日期或缩短时长'] }
819
+ : {});
820
+ },
821
+ };
822
+ export const meetingEbConflictOperations = [ebConflictRoom, ebConflictMember, ebConflictAvailability];