weaver-work-cli 0.1.32 → 0.1.36

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 (211) hide show
  1. package/README.md +2 -2
  2. package/dist/chunks/{archive-UMGX72K6.js → archive-XBVH7JU7.js} +2 -2
  3. package/dist/chunks/{archive-oa-N2IYBLF3.js → archive-oa-MZM3EKFQ.js} +2 -2
  4. package/dist/chunks/{asset-GLEGKFG5.js → asset-4XO7UOUW.js} +2 -2
  5. package/dist/chunks/{attachment-content-AQNX5N3S.js → attachment-content-VXSWANAC.js} +3 -2
  6. package/dist/chunks/{blog-E44OTVDA.js → blog-PGZKOPSC.js} +2 -2
  7. package/dist/chunks/{caizhilian-DQUFZXZL.js → caizhilian-BH4ZVGUN.js} +2 -2
  8. package/dist/chunks/{calendar-JKPIRKFD.js → calendar-MXEMTIO4.js} +2 -2
  9. package/dist/chunks/{chunk-QWY6PGL6.js → chunk-2LV74IWP.js} +361 -56
  10. package/dist/chunks/chunk-3SO6QL6U.js +73 -0
  11. package/dist/chunks/chunk-5DQEEGCC.js +229 -0
  12. package/dist/chunks/{chunk-G3QJULO2.js → chunk-6LLJFKJE.js} +1 -1
  13. package/dist/chunks/{chunk-C6R3B2ML.js → chunk-BPE37FPB.js} +35 -7
  14. package/dist/chunks/chunk-BYATLLEF.js +68 -0
  15. package/dist/chunks/chunk-BYBSCN4X.js +932 -0
  16. package/dist/chunks/{chunk-CVZ2JOSW.js → chunk-F746NKNF.js} +1 -1
  17. package/dist/chunks/{chunk-GCG2NMWR.js → chunk-HXJQI5NM.js} +4 -2
  18. package/dist/chunks/{chunk-YRVDW4WN.js → chunk-I6QGIBDM.js} +2 -2
  19. package/dist/chunks/{chunk-H7J4DC5R.js → chunk-JCSN3JGM.js} +2 -2
  20. package/dist/chunks/{chunk-2GA5ZIQL.js → chunk-JNG42JKG.js} +30 -1
  21. package/dist/chunks/{chunk-DW6AUUKS.js → chunk-JUXVYKII.js} +9 -4
  22. package/dist/chunks/{chunk-6RKD4DRZ.js → chunk-JYKABYCV.js} +2 -2
  23. package/dist/chunks/{chunk-6EFWDL7Y.js → chunk-NCOVUAZK.js} +2 -2
  24. package/dist/chunks/{chunk-E4Z23GHM.js → chunk-QHFANTX7.js} +1114 -904
  25. package/dist/chunks/{ebuilder-form-V5FG2EHC.js → chunk-QYGO5EE2.js} +22 -1148
  26. package/dist/chunks/{chunk-IURUFGZK.js → chunk-T72N74ZT.js} +50 -33
  27. package/dist/chunks/{chunk-H6XSTSLM.js → chunk-TC6MZAO3.js} +137 -204
  28. package/dist/chunks/{chunk-WKI2FBIQ.js → chunk-VPUTVEMC.js} +737 -341
  29. package/dist/chunks/{chunk-WSKBSQ2U.js → chunk-VPXOPQJI.js} +2 -2
  30. package/dist/chunks/chunk-WXFPWCXG.js +774 -0
  31. package/dist/chunks/{chunk-4YC43QQ4.js → chunk-XMGSYD6S.js} +4 -4
  32. package/dist/chunks/conflict-JAB2LJEZ.js +21 -0
  33. package/dist/chunks/continuation-RN6PZN2M.js +11 -0
  34. package/dist/chunks/contract-XI2EEIZU.js +9110 -0
  35. package/dist/chunks/cowork-SCPGLBA4.js +2527 -0
  36. package/dist/chunks/ebuilder-form-EYLHNOTA.js +121 -0
  37. package/dist/chunks/{ehr-HMFY6VIL.js → ehr-EAP77IMT.js} +1344 -27
  38. package/dist/chunks/{esb-ZYSVB7SG.js → esb-2TGGXEE7.js} +4 -4
  39. package/dist/chunks/{fna-B7RKPAZD.js → fna-2X7ZE7BU.js} +13 -8
  40. package/dist/chunks/{host-OCH2BCCV.js → host-C6EL2RKL.js} +3 -3
  41. package/dist/chunks/{host-45WED2KK.js → host-I4NRK4MX.js} +3 -3
  42. package/dist/chunks/{host-I4Z6BFYX.js → host-Q4LZGCRU.js} +3 -3
  43. package/dist/chunks/host-UEI5FGDM.js +23 -0
  44. package/dist/chunks/{host-KNZ52LJZ.js → host-XCLDDXKQ.js} +3 -3
  45. package/dist/chunks/{hrm-2KAZGUHI.js → hrm-YXERJM76.js} +3 -3
  46. package/dist/chunks/{im-ACJJWH4U.js → im-A4VZN2TQ.js} +88 -12
  47. package/dist/chunks/{invoice-ZK3WCX2U.js → invoice-2Z6YPMIS.js} +3 -3
  48. package/dist/chunks/{jiuchuanhui-5PQD3IL2.js → jiuchuanhui-AR5GMKZB.js} +2 -2
  49. package/dist/chunks/{lazy-dispatch-JQXBW3GM.js → lazy-dispatch-QXW7P4X3.js} +4 -4
  50. package/dist/chunks/{mail-S2HDDXO2.js → mail-7GZ6CPBJ.js} +45 -24
  51. package/dist/chunks/{meeting-QVN3I37L.js → meeting-3NMGA3MB.js} +278 -43
  52. package/dist/chunks/{okr-QNG4VC2Q.js → okr-BZ6XUOAG.js} +2 -2
  53. package/dist/chunks/operations-5QL3AECO.js +11 -0
  54. package/dist/chunks/{plan-A25EMW6U.js → plan-DR5CMUYP.js} +2 -2
  55. package/dist/chunks/{project-G6XV66DZ.js → project-6J63MK33.js} +2 -2
  56. package/dist/chunks/{registry-DC6KR3NH.js → registry-KDD7DHNK.js} +4 -4
  57. package/dist/chunks/{registry-2VLUAESA.js → registry-OZSMDVPP.js} +2 -2
  58. package/dist/chunks/{registry-7C5UMIPL.js → registry-TKX2E7C7.js} +2 -2
  59. package/dist/chunks/{registry-TI3CIUXF.js → registry-VRTNC6UR.js} +8 -6
  60. package/dist/chunks/{room-recommend-WRTHO6Y2.js → room-recommend-XCWRMB5P.js} +4 -3
  61. package/dist/chunks/{schedule-KFIITGSK.js → schedule-IVBICP32.js} +4 -3
  62. package/dist/chunks/{workflow-DJ7SYWAF.js → workflow-D74NFK3C.js} +179 -6
  63. package/dist/index.js +35 -25
  64. package/dist/internal/e10/auth-recovery.js +14 -4
  65. package/dist/internal/e10/fetch-timing.js +31 -0
  66. package/dist/internal/e10/request-runtime.js +5 -0
  67. package/dist/shortcuts/contract/host.js +34 -59
  68. package/dist/shortcuts/cowork/continuation.js +70 -0
  69. package/dist/shortcuts/cowork/errors.js +59 -0
  70. package/dist/shortcuts/cowork/host.js +180 -0
  71. package/dist/shortcuts/cowork/index.js +166 -0
  72. package/dist/shortcuts/cowork/manifest.js +65 -0
  73. package/dist/shortcuts/cowork/operations/discuss-write.js +277 -0
  74. package/dist/shortcuts/cowork/operations/item-write.js +380 -0
  75. package/dist/shortcuts/cowork/operations/member-write.js +283 -0
  76. package/dist/shortcuts/cowork/operations/permission-write.js +286 -0
  77. package/dist/shortcuts/cowork/operations/read.js +423 -0
  78. package/dist/shortcuts/cowork/operations/registry.js +39 -0
  79. package/dist/shortcuts/cowork/operations/review-write.js +157 -0
  80. package/dist/shortcuts/cowork/operations/shared.js +215 -0
  81. package/dist/shortcuts/cowork/operations/types.js +39 -0
  82. package/dist/shortcuts/cowork/operations.js +19 -0
  83. package/dist/shortcuts/ehr/host.js +4 -1
  84. package/dist/shortcuts/ehr/operations/recruit-candidate-import.js +757 -0
  85. package/dist/shortcuts/ehr/operations/recruit-position.js +367 -0
  86. package/dist/shortcuts/ehr/operations/recruit-stats.js +133 -0
  87. package/dist/shortcuts/ehr/operations/recruit-write.js +43 -16
  88. package/dist/shortcuts/ehr/operations/recruit.js +182 -10
  89. package/dist/shortcuts/ehr/operations/registry.js +6 -0
  90. package/dist/shortcuts/fna/operations/expense-chart.js +19 -7
  91. package/dist/shortcuts/im/diagnostics.js +97 -0
  92. package/dist/shortcuts/im/host.js +58 -31
  93. package/dist/shortcuts/im/index.js +7 -2
  94. package/dist/shortcuts/im/operations/at-mention.js +127 -0
  95. package/dist/shortcuts/im/operations/file-user.js +10 -2
  96. package/dist/shortcuts/im/operations/group-read.js +26 -5
  97. package/dist/shortcuts/im/operations/group-write.js +35 -28
  98. package/dist/shortcuts/im/operations/msg-sync.js +7 -2
  99. package/dist/shortcuts/im/operations/msg-write.js +76 -10
  100. package/dist/shortcuts/im/operations/quote-mention.js +119 -0
  101. package/dist/shortcuts/im/operations/session.js +1 -1
  102. package/dist/shortcuts/im/operations/shared.js +10 -0
  103. package/dist/shortcuts/im/operations/types.js +13 -0
  104. package/dist/shortcuts/index.js +10 -0
  105. package/dist/shortcuts/mail/host.js +12 -1
  106. package/dist/shortcuts/mail/operations/types.js +17 -2
  107. package/dist/shortcuts/meeting/host.js +27 -34
  108. package/dist/shortcuts/meeting/index.js +79 -1
  109. package/dist/shortcuts/meeting/manifest.js +10 -0
  110. package/dist/shortcuts/meeting/operations/create.js +4 -1
  111. package/dist/shortcuts/meeting/operations/eb/catalog.js +10 -0
  112. package/dist/shortcuts/meeting/operations/eb/conflict.js +84 -8
  113. package/dist/shortcuts/meeting/operations/eb/create-flow.js +1195 -140
  114. package/dist/shortcuts/meeting/operations/eb/factory.js +170 -8
  115. package/dist/shortcuts/meeting/operations/eb/index.js +16 -3
  116. package/dist/shortcuts/meeting/operations/eb/room-recommend.js +50 -5
  117. package/dist/shortcuts/meeting/operations/eb-fallback.js +163 -0
  118. package/dist/shortcuts/meeting/operations/env.js +99 -46
  119. package/dist/shortcuts/meeting/operations/form-fields.js +110 -0
  120. package/dist/shortcuts/meeting/operations/invitation.js +79 -20
  121. package/dist/shortcuts/meeting/operations/registry.js +2 -0
  122. package/dist/shortcuts/meeting/operations/standard-guard.js +71 -0
  123. package/dist/shortcuts/meeting/operations.js +16 -0
  124. package/dist/shortcuts/workflow/endpoints.js +8 -3
  125. package/dist/shortcuts/workflow/index.js +110 -1
  126. package/dist/shortcuts/workflow/operations/form.js +204 -36
  127. package/dist/shortcuts/workflow/operations/read.js +104 -23
  128. package/dist/shortcuts/workflow/operations/todoSmartConvert/aigroupRules.js +448 -0
  129. package/dist/shortcuts/workflow/operations/todoSmartConvert/aigroupStore.js +106 -0
  130. package/dist/shortcuts/workflow/operations/todoSmartConvert/businessRules.js +47 -0
  131. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.collect.js +30 -23
  132. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.core.js +1 -0
  133. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.group.js +1 -0
  134. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.groupSummary.js +2 -1
  135. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.js +2 -4
  136. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.smartGroup.js +138 -181
  137. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.summary.js +12 -7
  138. package/dist/shortcuts/workflow/operations.js +23 -1
  139. package/docs/e10-auth.md +1 -1
  140. package/docs/ehr.md +1 -1
  141. package/docs/fna.md +2 -1
  142. package/docs/im.md +8 -1
  143. package/docs/todo-smart-convert/README.md +1 -1
  144. package/docs/todo-smart-convert/data-contract.md +2 -2
  145. package/docs/todo-smart-convert/subgroup-rules.md +6 -0
  146. package/package.json +1 -1
  147. package/skill-template/business-info.json +18 -0
  148. package/skill-template/domains/cowork.md +14 -0
  149. package/skill-template/domains/yimiaoban.md +3 -3
  150. package/skill-template/skill-template.md +1 -1
  151. package/skills/weaver-e10-blog/SKILL.md +5 -5
  152. package/skills/weaver-e10-caizhilian/SKILL.md +5 -5
  153. package/skills/weaver-e10-calendar/SKILL.md +5 -5
  154. package/skills/weaver-e10-cowork/SKILL.md +101 -0
  155. package/skills/weaver-e10-cowork/references/cowork-read.md +90 -0
  156. package/skills/weaver-e10-cowork/references/cowork-write.md +131 -0
  157. package/skills/weaver-e10-cowork/references/safety-boundaries.md +45 -0
  158. package/skills/weaver-e10-esb/SKILL.md +5 -5
  159. package/skills/weaver-e10-hrm/SKILL.md +5 -5
  160. package/skills/weaver-e10-jinchengda/SKILL.md +2 -2
  161. package/skills/weaver-e10-jiuchuanhui/SKILL.md +5 -5
  162. package/skills/weaver-e10-jucailin/SKILL.md +38 -14
  163. package/skills/weaver-e10-jucailin/references/recruit-create.md +104 -0
  164. package/skills/weaver-e10-jucailin/references/recruit-read.md +25 -0
  165. package/skills/weaver-e10-jucailin/references/recruit-write.md +13 -0
  166. package/skills/weaver-e10-jucailin/references/salary.md +15 -11
  167. package/skills/weaver-e10-mail/SKILL.md +17 -14
  168. package/skills/weaver-e10-meeting/SKILL.md +442 -46
  169. package/skills/weaver-e10-meeting/references/meeting-eb.md +135 -3
  170. package/skills/weaver-e10-meeting/references/meeting-invitation.md +28 -2
  171. package/skills/weaver-e10-meeting/references/meeting-queries.md +9 -6
  172. package/skills/weaver-e10-meeting/references/meeting-verification.md +8 -6
  173. package/skills/weaver-e10-meeting.zip +0 -0
  174. package/skills/weaver-e10-okr/SKILL.md +5 -5
  175. package/skills/weaver-e10-okr/references/source-manifest.json +3 -3
  176. package/skills/weaver-e10-plan/SKILL.md +5 -5
  177. package/skills/weaver-e10-qiyecheng/SKILL.md +5 -2
  178. package/skills/weaver-e10-qiyecheng/references/catalog.md +2 -2
  179. package/skills/weaver-e10-qiyecheng/references/expense-chart.md +15 -1
  180. package/skills/weaver-e10-qiyecheng/references/source-manifest.json +22 -21
  181. package/skills/weaver-e10-shared/SKILL.md +12 -12
  182. package/skills/weaver-e10-shared/references/e10-auth-and-session.md +4 -4
  183. package/skills/weaver-e10-wenshuding/SKILL.md +5 -5
  184. package/skills/weaver-e10-workflow/SKILL.md +29 -5
  185. package/skills/weaver-e10-workflow/aigroup/default.json +88 -0
  186. package/skills/weaver-e10-workflow/references/todo-smart-convert.md +66 -14
  187. package/skills/weaver-e10-workflow/references/workflow-agent-entry.md +1 -1
  188. package/skills/weaver-e10-workflow/references/workflow-memory.md +1 -1
  189. package/skills/weaver-e10-workflow/references/workflow-query.md +12 -10
  190. package/skills/weaver-e10-workflow/references/workflow-view.md +2 -2
  191. package/skills/weaver-e10-workflow/tools/todo-serve.cjs +239 -0
  192. package/skills/weaver-e10-workflow/{scripts → tools}/todo-summary.cjs +218 -28
  193. package/skills/weaver-e10-yepiaotong/SKILL.md +10 -8
  194. package/skills/weaver-e10-yepiaotong/references/invoice-reim.md +7 -7
  195. package/skills/weaver-e10-yepiaotong/references/reim-api-reference.md +10 -10
  196. package/skills/weaver-e10-yepiaotong/references/reim-examples.md +40 -37
  197. package/skills/weaver-e10-yepiaotong/references/reim-expense-subject-rules.md +4 -4
  198. package/skills/weaver-e10-yepiaotong/references/reim-form-fill-rules.md +27 -38
  199. package/skills/weaver-e10-yepiaotong/references/reim-phone-subsidy-rules.md +4 -4
  200. package/skills/weaver-e10-yepiaotong/references/reim-workflow.md +37 -29
  201. package/skills/weaver-e10-yimiaoban/SKILL.md +156 -33
  202. package/skills/weaver-e10-yimiaoban/references/ding-write.md +1 -1
  203. package/skills/weaver-e10-yimiaoban/references/error-codes.md +5 -1
  204. package/skills/weaver-e10-yimiaoban/references/group-read.md +5 -1
  205. package/skills/weaver-e10-yimiaoban/references/group-write.md +6 -4
  206. package/skills/weaver-e10-yimiaoban/references/msg-read.md +1 -0
  207. package/skills/weaver-e10-yimiaoban/references/msg-write.md +34 -2
  208. package/skills/weaver-e10-yimiaoban/references/safety-boundaries.md +5 -2
  209. package/skills/weaver-e10-yimiaoban/references/source-manifest.json +79 -58
  210. package/skills/weaver-e10-yimiaoban/references/user-remind.md +2 -2
  211. package/skills/weaver-e10-ziguanjia/SKILL.md +5 -5
@@ -0,0 +1,119 @@
1
+ import { imValidationError } from '../errors.js';
2
+ import { assertImOk, str, unwrapInner } from './shared.js';
3
+ import { parseContent, parseMsg } from './msg-parse.js';
4
+ /** 调用方是否带了引用/回复入参。 */
5
+ export function hasQuoteInput(args) {
6
+ return args.quoteMsgId !== undefined || args.replyMsgId !== undefined;
7
+ }
8
+ /** 解析被引消息 id(纯数字、非 0)。 */
9
+ function quotedId(value) {
10
+ const id = str(value);
11
+ if (!id || !/^\d+$/.test(id) || id === '0') {
12
+ throw imValidationError('quote_id_invalid', 'quoteMsgId/replyMsgId 必须是非 0 的纯数字消息 id');
13
+ }
14
+ return id;
15
+ }
16
+ /** 从消息对象提取被引内容纯文本(txt 渲染 / [图片] / [文件]名)。 */
17
+ function quotedContentText(msgObj) {
18
+ if (!msgObj)
19
+ return '';
20
+ const parsed = parseContent(msgObj);
21
+ return str(parsed.content).trim();
22
+ }
23
+ /**
24
+ * prepare 阶段解析引用/回复入参:定位被引消息并组装 ref/rmsg 对象。
25
+ * - quoteMsgId 与 replyMsgId 互斥(quote_conflict);
26
+ * - 被引消息拉不到/找不到时报 quote_msg_not_found;
27
+ * - 被引人姓名走 hrm(resolveUsersByIds),回落消息内嵌 sname。
28
+ */
29
+ export async function resolveQuotedRef(args, env, target) {
30
+ const hasQuote = args.quoteMsgId !== undefined && args.quoteMsgId !== null && args.quoteMsgId !== '';
31
+ const hasReply = args.replyMsgId !== undefined && args.replyMsgId !== null && args.replyMsgId !== '';
32
+ if (hasQuote && hasReply) {
33
+ throw imValidationError('quote_conflict', 'quoteMsgId(引用)与 replyMsgId(回复)只能二选一');
34
+ }
35
+ if (!hasQuote && !hasReply)
36
+ return null;
37
+ const kind = hasQuote ? 'quote' : 'reply';
38
+ const msgId = quotedId(hasQuote ? args.quoteMsgId : args.replyMsgId);
39
+ const isSingle = !target.groupId;
40
+ if (isSingle && (!target.toUid || !target.toCid)) {
41
+ throw imValidationError('quote_target_required', '单聊引用需要 toUid + toCid 才能定位被引消息');
42
+ }
43
+ const scopeLabel = isSingle ? `与 ${target.toUid} 的单聊` : `群 ${target.groupId}`;
44
+ let rows = [];
45
+ try {
46
+ // 单条定位:id ± 1 秒(id 为纳秒级时间戳,开边界语义)
47
+ const idBig = BigInt(msgId);
48
+ const window = { start: String(idBig + 1000000000n), end: String(idBig - 1000000000n), max_num: 5 };
49
+ const res = isSingle
50
+ ? await env.host.request({
51
+ method: 'POST',
52
+ path: '/api/em/msg/executeIm/msg/syncSingleMsg',
53
+ body: { trans_id: 0, dev: 4, enc_type: 0, datas: [{ from_cid: String(target.toCid), from_uid: String(target.toUid), ...window }] },
54
+ operation: 'im.msg.send.single.quoteResolve',
55
+ })
56
+ : await env.host.request({
57
+ method: 'POST',
58
+ path: '/api/em/msg/executeIm/chat/syncGroupMsg',
59
+ body: { trans_id: 0, dev: 4, type: 0, enc_type: 0, datas: [{ group_id: String(target.groupId), ...window }] },
60
+ operation: 'im.msg.send.group.quoteResolve',
61
+ });
62
+ assertImOk(res, '定位被引消息');
63
+ const outer = unwrapInner(res)?.datas || [];
64
+ for (const group of outer)
65
+ rows.push(...(group?.datas || group?.data || []));
66
+ }
67
+ catch (error) {
68
+ throw imValidationError('quote_msg_not_found', `无法定位被引消息 ${msgId}:${error instanceof Error ? error.message : String(error)}`, { ...target, quotedMsgId: msgId });
69
+ }
70
+ const row = rows.find((r) => str(r?.ser_msgid) === msgId);
71
+ if (!row) {
72
+ throw imValidationError('quote_msg_not_found', `消息 ${msgId} 不在${scopeLabel}内(或已被撤回),无法引用`, {
73
+ ...target,
74
+ quotedMsgId: msgId,
75
+ });
76
+ }
77
+ const msgObj = parseMsg(row.msg);
78
+ const fromUid = str(row.from_uid) || str(msgObj?.suid);
79
+ const fromCid = str(row.from_cid) || str(msgObj?.scid);
80
+ let fromName = str(msgObj?.sname);
81
+ if (fromUid) {
82
+ try {
83
+ const personMap = await env.host.resolveUsersByIds([fromUid]);
84
+ fromName = personMap[fromUid]?.name || fromName;
85
+ }
86
+ catch {
87
+ // hrm 失败时回落消息内嵌姓名
88
+ }
89
+ }
90
+ const ref = {
91
+ id: msgId,
92
+ t: str(msgObj?.type ?? row.kind ?? '1') || '1',
93
+ f: '0',
94
+ time: Number(row.msgtime) > 0 ? Number(row.msgtime) * 1000 : Date.now(),
95
+ fname: fromName || fromUid || '',
96
+ fuser: { cid: fromCid, uid: fromUid },
97
+ rcon: quotedContentText(msgObj),
98
+ display: '',
99
+ ...(kind === 'reply' ? { oid: msgId } : {}),
100
+ };
101
+ return { kind, quotedMsgId: msgId, fromName: ref.fname, fromUid, text: ref.rcon, ref };
102
+ }
103
+ /** 拉取侧:从消息对象提取顶层引用/回复摘要(msg.msg.ref / msg.msg.rmsg)。 */
104
+ export function extractQuoteSummary(msgObj) {
105
+ const raw = msgObj ? (msgObj.rmsg ?? msgObj.ref) : undefined;
106
+ if (!raw || typeof raw !== 'object')
107
+ return undefined;
108
+ const kind = msgObj?.rmsg ? 'reply' : 'quote';
109
+ return {
110
+ kind,
111
+ id: str(raw.id),
112
+ msgType: str(raw.t),
113
+ fromName: str(raw.fname),
114
+ fromUid: str(raw.fuser?.uid),
115
+ fromCid: str(raw.fuser?.cid),
116
+ msgtime: raw.time,
117
+ text: str(raw.rcon),
118
+ };
119
+ }
@@ -241,7 +241,7 @@ const sysmsgTypeSync = {
241
241
  inputSchema: schemaOf({
242
242
  gids: { type: 'array', items: { type: 'integer' }, description: '系统分组 id(与 tids 至少其一)' },
243
243
  tids: { type: 'array', items: { type: 'integer' }, description: '消息类型 id' },
244
- }, { description: 'gids / tids 至少其一' }),
244
+ }, { requiredAny: ['gids', 'tids'], description: '按 gids/tids 拉取系统消息分组/类型配置(至少其一)' }),
245
245
  handler: async (args, { host }) => {
246
246
  const gids = Array.isArray(args.gids) ? args.gids.map((x) => Number(x)).filter((n) => Number.isInteger(n)) : [];
247
247
  const tids = Array.isArray(args.tids) ? args.tids.map((x) => Number(x)).filter((n) => Number.isInteger(n)) : [];
@@ -244,6 +244,16 @@ export function assertImOk(value, actionLabel) {
244
244
  export function unwrapInner(value) {
245
245
  return value?.data?.data ?? value?.data;
246
246
  }
247
+ /**
248
+ * 服务端在建群/入群时自动补入的「系统成员」cid(群内系统消息的投递载体)。
249
+ * 属产品可选能力(租户侧可关闭),不是真实员工:姓名会回落成 uid,hrm 解析不到。
250
+ * 统计真实成员数、判断「是否只剩自己」时必须排除它。
251
+ */
252
+ export const SYSTEM_MEMBER_CID = '10002';
253
+ /** 是否服务端系统成员(非真实员工)。 */
254
+ export function isSystemMember(member) {
255
+ return str(member?.cid) === SYSTEM_MEMBER_CID;
256
+ }
247
257
  /** 群成员条目判定(成员行至少含 uid 或 user 身份)。 */
248
258
  function isMemberRow(value) {
249
259
  return Boolean(value) && typeof value === 'object' && (value.uid !== undefined || value.user !== undefined);
@@ -8,11 +8,24 @@ export function imSuccess(action, data, meta) {
8
8
  }
9
9
  /** 构造「对象类型 + 严格禁止额外字段」的 JSON Schema。 */
10
10
  export function schemaOf(properties, options = {}) {
11
+ const clauses = [];
12
+ if (options.requiredAny?.length)
13
+ clauses.push({ anyOf: options.requiredAny.map((key) => ({ required: [key] })) });
14
+ if (options.anyOf?.length)
15
+ clauses.push({ anyOf: options.anyOf });
16
+ if (options.allOf?.length)
17
+ clauses.push(...options.allOf);
18
+ const condition = clauses.length === 1
19
+ ? clauses[0]
20
+ : clauses.length > 1
21
+ ? { allOf: clauses }
22
+ : {};
11
23
  return {
12
24
  type: 'object',
13
25
  additionalProperties: false,
14
26
  properties,
15
27
  ...(options.required?.length ? { required: options.required } : {}),
28
+ ...condition,
16
29
  ...(options.description ? { description: options.description } : {}),
17
30
  };
18
31
  }
@@ -35,6 +35,11 @@ export const builtinShortcuts = [
35
35
  description: '泛微E10聚才林(人事/出勤/薪酬)CLI',
36
36
  load: async () => (await import('./ehr/index.js')).ehrShortcut,
37
37
  }),
38
+ lazyShortcut({
39
+ name: 'cowork',
40
+ description: '泛微E10协作区(协作事项/讨论/审批/共享权限)CLI',
41
+ load: async () => (await import('./cowork/index.js')).coworkShortcut,
42
+ }),
38
43
  lazyShortcut({
39
44
  name: 'archive',
40
45
  description: 'E10 文书定档案管理 CLI',
@@ -125,6 +130,11 @@ export const builtinShortcuts = [
125
130
  description: '泛微E10采知连(文档中心)CLI',
126
131
  load: async () => (await import('./caizhilian/index.js')).caizhilianShortcut,
127
132
  }),
133
+ lazyShortcut({
134
+ name: 'contract',
135
+ description: '泛微E10今承达合同管理 CLI',
136
+ load: async () => (await import('./contract/index.js')).contractShortcut,
137
+ }),
128
138
  ];
129
139
  /** 全部 shortcut 的 CLI 元数据(不触发任何实现模块加载)。 */
130
140
  export function shortcutMetadata() {
@@ -4,6 +4,7 @@ import { requireE10Session } from '../../internal/e10/context.js';
4
4
  import { getActiveProfile } from '../../internal/e10/auth/session.js';
5
5
  import { E10ApiError, createE10RequestRuntime } from '../../internal/e10/request-runtime.js';
6
6
  import { mailAuthError, mailNetworkError, MailCliError, mailValidationError } from './errors.js';
7
+ import { mailPerf } from './operations/types.js';
7
8
  function inferProfile(ctx) {
8
9
  const options = ctx.getGlobalOptions();
9
10
  return options.profile || getActiveProfile();
@@ -96,10 +97,15 @@ function mailMessage(value) {
96
97
  export function createMailHost(ctx, dependencies = {}) {
97
98
  let e10Session = null;
98
99
  let sessionContext = null;
99
- const requireSession = dependencies.requireSession || requireE10Session;
100
+ // 与 workflow 模块同口径(2026-10-29 实测):auth 文件里已有登录时落盘的 userId,
101
+ // 业务命令不需要 teamsCheck 的登录人信息富化 ⇒ 跳过它,省掉每次调用一次串行 HTTPS 往返(实测约 150~250ms)。
102
+ const requireSession = dependencies.requireSession
103
+ || ((innerCtx) => requireE10Session(innerCtx, { skipProfileEnrichment: true }));
100
104
  async function ensureSession() {
101
105
  if (!e10Session) {
106
+ const sessionStart = Date.now();
102
107
  e10Session = await requireSession(ctx);
108
+ mailPerf.sessionMs = Date.now() - sessionStart;
103
109
  sessionContext = {
104
110
  baseUrl: e10Session.getBaseUrl(),
105
111
  profile: inferProfile(ctx),
@@ -125,6 +131,7 @@ export function createMailHost(ctx, dependencies = {}) {
125
131
  assertPath(path);
126
132
  const resolvedMethod = method || (body === undefined && form === undefined && multipart === undefined ? 'GET' : 'POST');
127
133
  let response;
134
+ const reqStart = Date.now();
128
135
  try {
129
136
  response = await (await runtime()).requestJson({
130
137
  module: 'mail',
@@ -149,6 +156,10 @@ export function createMailHost(ctx, dependencies = {}) {
149
156
  }
150
157
  throw error;
151
158
  }
159
+ finally {
160
+ mailPerf.requestCount += 1;
161
+ mailPerf.requestMs += Date.now() - reqStart;
162
+ }
152
163
  if (response.status === 401 || response.status === 302) {
153
164
  throw mailAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE, { httpStatus: response.status });
154
165
  }
@@ -1,9 +1,24 @@
1
1
  export const READ = 'read';
2
2
  export const WRITE = 'write';
3
+ /** 进程内耗时收集器(周军锋 2026-10-20 要求:全链路耗时可量化)。host 写入、mailSuccess 注出;内部诊断字段不进回答。 */
4
+ export const mailPerf = {
5
+ t0: Date.now(),
6
+ sessionMs: 0,
7
+ requestCount: 0,
8
+ requestMs: 0,
9
+ };
3
10
  export function mailSuccess(action, data, meta) {
4
11
  const payload = { ok: true, action, data };
5
- if (meta && Object.keys(meta).length > 0)
6
- payload.meta = meta;
12
+ const perf = {
13
+ elapsed_ms: Date.now() - mailPerf.t0,
14
+ session_ms: mailPerf.sessionMs || undefined,
15
+ requests: mailPerf.requestCount || undefined,
16
+ network_ms: mailPerf.requestMs || undefined,
17
+ };
18
+ for (const k of Object.keys(perf))
19
+ if (perf[k] === undefined)
20
+ delete perf[k];
21
+ payload.meta = { ...(meta || {}), ...perf };
7
22
  return payload;
8
23
  }
9
24
  /** 构造「对象类型 + 严格禁止额外字段」的 JSON Schema。 */
@@ -111,49 +111,42 @@ export function createMeetingHost(ctx, dependencies = {}) {
111
111
  return sessionContext;
112
112
  }
113
113
  async function download(request) {
114
- const session = await ensureSession();
115
- const url = new URL(request.path, session.getBaseUrl());
116
- for (const [key, value] of Object.entries(request.query ?? {})) {
117
- if (value !== undefined && value !== null && String(value) !== '')
118
- url.searchParams.set(key, String(value));
119
- }
120
- const controller = new AbortController();
121
- const timer = setTimeout(() => controller.abort(), request.timeoutMs ?? 60_000);
114
+ assertPath(request.path);
115
+ let response;
122
116
  try {
123
- const response = await (dependencies.fetch || globalThis.fetch)(url.toString(), {
124
- headers: {
125
- accept: '*/*',
126
- cookie: session.cookieHeader(),
127
- eteamsid: session.eteamsId,
128
- 'user-agent': session.uaHeader(),
129
- origin: session.getBaseUrl(),
130
- },
131
- redirect: 'manual',
132
- signal: controller.signal,
117
+ response = await (await runtime()).requestBinary({
118
+ module: 'meeting',
119
+ operation: 'meeting.attachment.download',
120
+ method: 'GET',
121
+ path: request.path,
122
+ query: omitEmptyQueryValues(request.query),
123
+ headers: { accept: '*/*' },
124
+ timeoutMs: request.timeoutMs ?? 60_000,
133
125
  });
134
- if (response.status === 401 || response.status === 302) {
135
- throw meetingAuthError('session_expired', '登录态已失效:下载需有效登录态,请重新登录后重试', { httpStatus: response.status });
136
- }
137
- if (response.status !== 200) {
138
- throw new MeetingCliError('api', 'download_failed', `下载失败 HTTP ${response.status}`, {
139
- code: response.status,
140
- exitCode: 1,
141
- });
142
- }
143
- return {
144
- buffer: Buffer.from(await response.arrayBuffer()),
145
- contentType: response.headers.get('content-type') ?? '',
146
- contentDisposition: response.headers.get('content-disposition') ?? '',
147
- };
148
126
  }
149
- finally {
150
- clearTimeout(timer);
127
+ catch (error) {
128
+ mapRuntimeNetworkError(error);
129
+ }
130
+ if (response.status === 401 || response.status === 302) {
131
+ throw meetingAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE, { httpStatus: response.status });
132
+ }
133
+ if (response.status !== 200) {
134
+ throw new MeetingCliError('api', 'download_failed', `下载失败 HTTP ${response.status}`, {
135
+ code: response.status,
136
+ exitCode: 1,
137
+ });
151
138
  }
139
+ return {
140
+ buffer: response.content,
141
+ contentType: response.contentType,
142
+ contentDisposition: response.headers.get('content-disposition') ?? '',
143
+ };
152
144
  }
153
145
  async function runtime() {
154
146
  return createE10RequestRuntime(ctx, await ensureSession(), {
155
147
  interceptors: dependencies.interceptors,
156
148
  fetch: dependencies.fetch,
149
+ recoverAuthentication: dependencies.recoverAuthentication,
157
150
  });
158
151
  }
159
152
  async function request(request) {
@@ -2,7 +2,7 @@ import { readFile } from 'node:fs/promises';
2
2
  import { ExitCode, isCliError, toErrorEnvelope } from '../../core/errors.js';
3
3
  import { createMeetingContinuationSigner } from './continuation.js';
4
4
  import { createMeetingHost } from './host.js';
5
- import { meetingManifest } from './manifest.js';
5
+ import { meetingManifest, OPERATION_INDEX_HINT } from './manifest.js';
6
6
  import { executeMeetingOperation } from './operations.js';
7
7
  import { meetingValidationError } from './errors.js';
8
8
  function asOptions(value) {
@@ -102,6 +102,84 @@ export const meetingShortcut = {
102
102
  .action(action((ctx) => {
103
103
  ctx.output.write(meetingManifest, { json: true });
104
104
  }));
105
+ // 能力清单:**纯文本、不压缩**。
106
+ // 为什么必须单独开一条命令:`meeting schema --json` 完整 308KB,stdout 只回 20KB 预览,
107
+ // operations 数组被截到前 10 条 —— agent 拿这个判断"有没有某能力"必然误判
108
+ // (2026-10-30 真机:回复用户"当前 CLI 版本没有建会建议时间操作",实际 suggest 一直存在)。
109
+ meeting.command('ops')
110
+ .description('列出全部会议 operation(纯文本不压缩);判断"有没有某能力"用这个,不要用 schema')
111
+ .option('--grep <keyword>', '只看名字或摘要命中关键词的(不区分大小写)')
112
+ .option('--capability <name>', '只看某个能力分组,如 eb / meeting / room / conflict / sign')
113
+ .option('--json', 'JSON 输出(注意:JSON 走信封压缩,条目多时会被截断,查全量请用文本)')
114
+ .action(action((ctx, ...rest) => {
115
+ // commander 对无位置参数的子命令传的是 (options, command),别取错 —— 用 "没有 opts 方法"
116
+ // 把 command 实例挑出去(2026-10-30:一开始取了最后一个,拿到 command 对象,导致 --grep 失效)。
117
+ const options = rest.find((item) => item && typeof item === 'object' && typeof item.opts !== 'function') ?? {};
118
+ const keyword = String(options?.grep ?? '').trim().toLowerCase();
119
+ const capability = String(options?.capability ?? '').trim().toLowerCase();
120
+ const all = meetingManifest.operations;
121
+ const hit = all.filter((item) => {
122
+ // 分组支持前缀:--capability eb 能命中 eb.meeting / eb.room / eb.sign ...
123
+ if (capability && !String(item.capability ?? '').toLowerCase().startsWith(capability))
124
+ return false;
125
+ if (!keyword)
126
+ return true;
127
+ return item.name.toLowerCase().includes(keyword)
128
+ || String(item.summary ?? '').toLowerCase().includes(keyword);
129
+ });
130
+ if (options?.json === true) {
131
+ ctx.output.write({
132
+ ok: true,
133
+ schemaVersion: 1,
134
+ operationIndexHint: OPERATION_INDEX_HINT,
135
+ count: hit.length,
136
+ total: all.length,
137
+ operations: hit.map((item) => ({
138
+ name: item.name,
139
+ capability: item.capability,
140
+ risk: item.risk,
141
+ summary: item.summary,
142
+ })),
143
+ withheldOperations: meetingManifest.withheldOperations,
144
+ }, { json: true });
145
+ return;
146
+ }
147
+ const clip = (text, max) => (text.length > max ? `${text.slice(0, max)}…` : text);
148
+ const lines = [
149
+ `泛微E10会议 CLI 能力清单:命中 ${hit.length} / 共 ${all.length} 项`
150
+ + `${keyword ? `(关键词「${keyword}」)` : ''}${capability ? `(分组 ${capability})` : ''}`,
151
+ '用法:weaver-work-cli --json meeting run <operation> --input-json \'{...}\'',
152
+ '',
153
+ ];
154
+ const groups = new Map();
155
+ for (const item of hit) {
156
+ const key = String(item.capability ?? 'other');
157
+ if (!groups.has(key))
158
+ groups.set(key, []);
159
+ groups.get(key).push(item);
160
+ }
161
+ for (const [group, items] of [...groups.entries()].sort((a, b) => b[1].length - a[1].length)) {
162
+ lines.push(`## ${group}(${items.length})`);
163
+ for (const item of items) {
164
+ lines.push(` ${item.name} ${clip(String(item.summary ?? ''), 96)}`);
165
+ }
166
+ lines.push('');
167
+ }
168
+ if (!capability || 'withheld'.startsWith(capability)) {
169
+ const withheld = meetingManifest.withheldOperations.filter((item) => {
170
+ if (!keyword)
171
+ return true;
172
+ return item.name.toLowerCase().includes(keyword) || item.reason.toLowerCase().includes(keyword);
173
+ });
174
+ if (withheld.length > 0) {
175
+ lines.push(`## 不可用(${withheld.length})`);
176
+ for (const item of withheld) {
177
+ lines.push(` ${item.name} ${clip(item.reason, 120)}`);
178
+ }
179
+ }
180
+ }
181
+ process.stdout.write(`${lines.join('\n')}\n`);
182
+ }));
105
183
  meeting.command('run')
106
184
  .description('Run a meeting operation with JSON input')
107
185
  .argument('<operation>', 'operation name, e.g. meeting.room.list')
@@ -62,8 +62,18 @@ function buildOperations() {
62
62
  inputSchema: definition.inputSchema,
63
63
  }));
64
64
  }
65
+ /**
66
+ * 给模型的**能力自查提示**。放在 manifest 靠前位置、且控制在 200 字符内 ——
67
+ * 输出压缩时字符串最多留 200 字符,这句话必须能完整活下来。
68
+ * 2026-10-30 真机事故:`meeting schema --json` 完整 308KB、预览只给 20KB,
69
+ * operations 数组被截到前 10 条,`meeting.eb.meeting.create.suggest` 根本不在预览里,
70
+ * agent 于是回复用户"当前 CLI 版本没有建会建议时间操作"——能力是有的,是被压缩藏住了。
71
+ */
72
+ export const OPERATION_INDEX_HINT = '本 JSON 可能被压缩截断,别据此判断能力是否存在;'
73
+ + '完整清单跑 `weaver-work-cli meeting ops`(纯文本、不压缩,支持 --grep 关键词)';
65
74
  export const meetingManifest = {
66
75
  schemaVersion: 1,
76
+ operationIndexHint: OPERATION_INDEX_HINT,
67
77
  name: 'meeting',
68
78
  title: '泛微E10会议管理(双模式:标准版 Java REST + EB 版 eBuilder)',
69
79
  version: '1.0.0',
@@ -144,7 +144,10 @@ async function assertCreatePathAllowed(args, env) {
144
144
  throw meetingConflictError('conflict_rejected', '该租户为周期会议配置了工作流(repeatWorkFlowBaseIds 非空),周期会议必须走流程建会;CLI v1 未集成工作流发起,请通过工作流流程创建', { repeatWorkFlowBaseIds: repeatWorkflowIds });
145
145
  }
146
146
  if (!isRepeat && Number(base.onlyFlowCreate) === 1) {
147
- throw meetingConflictError('conflict_rejected', '该租户 onlyFlowCreate=1:会议只能通过流程创建;CLI v1 未集成工作流发起,请通过工作流流程创建', { workFlowBaseIds: base.workFlowBaseIds ?? '' });
147
+ throw meetingConflictError('conflict_rejected', '该租户 onlyFlowCreate=1:会议只能通过流程创建,直连建会已被拒绝(不会写入任何数据)。'
148
+ + '不用停手 —— 改走 **meeting.eb.meeting.create.flow**(prepare→apply)即可:'
149
+ + '它会载入建会流程表单、提交审批,并在会议生成后自动出邀请函。'
150
+ + '如需直连建会,请管理员先在会议应用设置中关闭该开关。', { workFlowBaseIds: base.workFlowBaseIds ?? '' });
148
151
  }
149
152
  }
150
153
  /** 强制冲突检测:接口失败视为冲突(拒绝建会),cansub=false 拒绝提交。 */
@@ -204,8 +204,18 @@ export const EB_ENDPOINTS = [
204
204
  interfacePk: 'query_meeting_room_list',
205
205
  countPk: 'query_meeting_room_count',
206
206
  queryParams: ['manager', 'departmentId', 'mr_type', 'room_attr'],
207
+ // 按 e-builder openApi 文档:这些是 `mainTable` 节点的传入字段,服务端据此过滤。
208
+ // ⚠️ 2026-10-30 实测(用户刚在后端把「会议室名称」配成传入字段后):
209
+ // 服务端是**精确匹配** —— room_name=测试会议室1 → 1 条;room_name=测试 / 重庆 → 0 条
210
+ // (全量里明明有「重庆会议室一/二」)。模糊由 factory 的 fallback 在本地兜底。
211
+ mainTableParams: ['room_name', 'status', 'place', 'org_id', 'hrm_ids'],
207
212
  summarizeRows: summarizeRoomRows,
208
213
  summary: 'EB 版会议室列表(query_meeting_room_list + count):自带 statusText 与状态字典',
214
+ emptyHint: 'EB 会议室表(`uf_mt_room_base`)查不到任何记录。可能是:① 该环境确实还没建过会议室;'
215
+ + '② 会议应用的「会议室列表」接口/视图未配置好(`query_meeting_room_list` 读不到数据,但表里有数据)。'
216
+ + '**这不代表"环境是标准版"——绝对不要改用 `meeting.room.list`**:那是标准版会议应用的会议室,'
217
+ + '与 EB 完全是两套数据,拿给用户就是凭空捏造房间。请如实告诉用户"EB 会议室查不到",'
218
+ + '并请管理员检查会议应用的会议室配置。',
209
219
  notes: [
210
220
  '**status:`0` = 正常(可用)、`1` = 封存(不可用)** —— 不是"启用/停用",`0` 才是正常。',
211
221
  '明细在 `data.progress.items[]`:`statusText` 已把编码翻成中文,`summary.unusable` 直接列出不可用的房间名;需要原始编码时看 `status`。',
@@ -91,13 +91,18 @@ export async function fetchEbAppSettings(env, operation) {
91
91
  const valid = rows.filter((row) => String(row.data_status ?? '1') === '1');
92
92
  const ordered = [...valid].sort((left, right) => (String(right.update_time ?? '').localeCompare(String(left.update_time ?? ''))));
93
93
  const base = ordered[0] ?? {};
94
+ // ⚠️ 管控开关必须「取严不取松」(2026-10-29 真机事故):
95
+ // 生产环境库里可能有多份设置记录,其中最新一份的开关是空的、旧的一份才是 1。
96
+ // 此前只按最新一条取值 ⇒ 闸门被绕过,only_flow_create=1 的租户也能直连建出会议。
97
+ // 建会路径是**管控**而非偏好:只要任意一份有效记录开了,就必须走流程。
98
+ const strictest = (key) => (valid.some((row) => String(row[key] ?? '') === '1') ? '1' : String(base[key] ?? ''));
94
99
  return {
95
100
  roomChk: String(base.room_conflict_chk ?? ''),
96
101
  roomAction: String(base.room_conflict ?? ''),
97
102
  memberChk: String(base.member_conflict_chk ?? ''),
98
103
  memberAction: String(base.member_conflict ?? ''),
99
- onlyFlowCreate: String(base.only_flow_create ?? ''),
100
- openMtApprove: String(base.open_mt_approve ?? ''),
104
+ onlyFlowCreate: strictest('only_flow_create'),
105
+ openMtApprove: strictest('open_mt_approve'),
101
106
  mtNewWf: String(base.mt_new_wf ?? ''),
102
107
  mtRepeatNewWf: String(base.mt_repeat_new_wf ?? ''),
103
108
  settingsRows: valid.length,
@@ -131,7 +136,12 @@ export async function fetchEbAppSettings(env, operation) {
131
136
  * - 开关为空 / 非 `1` → 放行(交由后续冲突检测与用户确认)。
132
137
  *
133
138
  * 拒绝后 CLI 不代为发起流程(流程集成不在本模块范围),只把已确认的信息交回调用方,
134
- * 由调用方转 `weaver-e10-workflow`。
139
+ * 由调用方改用 `meeting.eb.meeting.create.flow.prepare/quick`(按 `mt_new_wf` 走流程建会)。
140
+ *
141
+ * ⚠️ 2026-10-29 更正:这里原先写的是"由调用方转 `weaver-e10-workflow`",是过时口径 ——
142
+ * 转去通用 workflow 模块会拿不到会议表单字段映射/会议室联动/邀请函,且容易让 agent
143
+ * 用关键词检索流程(真实事故:检索出「会议室预定流程-北方大区业务运营中心」当配置项)。
144
+ * 会议流程必须走 `create.flow`。
135
145
  */
136
146
  export function assertEbCreatePathAllowed(settings) {
137
147
  if (settings.error) {
@@ -146,7 +156,14 @@ export function assertEbCreatePathAllowed(settings) {
146
156
  }
147
157
  if (blocked.length === 0)
148
158
  return;
149
- throw meetingConflictError('conflict_rejected', `应用设置不允许直接建会:${blocked.join(';')}。该应用的会议必须经流程创建`
159
+ // 多条设置时,warnings 会随抛错一起丢掉 ⇒ 必须把「存在多份设置、按最严判定」写进错误本身,
160
+ // 否则排查的人只会看到"不许建会",不知道库里有两份冲突的设置要清理。
161
+ const multiRow = (settings.settingsRows ?? 0) > 1
162
+ ? `(注意:会议应用设置存在 ${settings.settingsRows} 条有效记录,管控开关按**最严**取值 —— 任意一份开了就走流程;`
163
+ + `冲突策略等其余字段仍按最新一条 id=${settings.settingsId ?? '未知'}、更新于 ${settings.settingsUpdatedAt || '未知'} 判断。`
164
+ + '建议让管理员在系统页面清理重复的设置记录。)'
165
+ : '';
166
+ throw meetingConflictError('conflict_rejected', `应用设置不允许直接建会:${blocked.join(';')}。${multiRow}该应用的会议必须经流程创建`
150
167
  + `(新建会议工作流 mt_new_wf=${settings.mtNewWf || '未配置'},周期会议工作流 mt_repeat_new_wf=${settings.mtRepeatNewWf || '未配置'})——`
151
168
  + 'CLI 不直接建会、也不代替流程建会(否则会产生流程之外、request_id 为空的会议,且与流程/ESB 自动建会重复)。'
152
169
  + '这属于应用管控,CLI 不会绕过 —— 但也不用停手:改走 **meeting.eb.meeting.create.flow**'
@@ -156,6 +173,9 @@ export function assertEbCreatePathAllowed(settings) {
156
173
  openMtApprove: settings.openMtApprove,
157
174
  mtNewWf: settings.mtNewWf,
158
175
  mtRepeatNewWf: settings.mtRepeatNewWf,
176
+ settingsRows: settings.settingsRows ?? 0,
177
+ settingsId: settings.settingsId ?? '',
178
+ settingsUpdatedAt: settings.settingsUpdatedAt ?? '',
159
179
  });
160
180
  }
161
181
  /** 会议室冲突:查占用视图(query_roomusage_list),时间重叠即冲突。 */
@@ -354,7 +374,42 @@ export const ebMeetingWritePreflight = async ({ endpoint, payload, args, env, ex
354
374
  const begin = typeof main.begin_datetime === 'string' ? main.begin_datetime : '';
355
375
  const end = typeof main.end_datetime === 'string' ? main.end_datetime : '';
356
376
  const address = typeof main.address === 'string' && main.address ? main.address : undefined;
357
- const hrmMembers = typeof main.hrm_members === 'string' && main.hrm_members ? main.hrm_members : undefined;
377
+ // ⛔ 参会人必须是**员工 id**,不能是姓名(2026-10-29 真机事故):
378
+ // 传姓名时会议主表能写进去(hrm_members 是文本渲染字段),但
379
+ // ① 回执/签到按 id 建 → 建不起来,参会人收不到邀请;
380
+ // ② 人员冲突检测按 id 比对 → 一个都查不到,等于冲突检测被静默跳过。
381
+ // 所以在这里把姓名解析成 id 再往下走,解析不出来的明确列出来,不静默丢。
382
+ const memberWarnings = [];
383
+ const rawMembers = typeof main.hrm_members === 'string' ? main.hrm_members.trim() : '';
384
+ if (rawMembers) {
385
+ const items = rawMembers.split(/[,;,;]+/u).map((item) => item.trim()).filter((item) => item !== '');
386
+ const nameLike = items.filter((item) => !/^\d+$/u.test(item));
387
+ if (nameLike.length > 0) {
388
+ const { resolveMemberIdsByName } = await import('./schedule.js');
389
+ const idsByName = await resolveMemberIdsByName(env, nameLike);
390
+ const resolved = items.map((item) => idsByName.get(item) ?? item);
391
+ payload.mainTable.hrm_members = [...new Set(resolved)].join(',');
392
+ const unresolved = nameLike.filter((name) => !idsByName.has(name));
393
+ if (unresolved.length > 0) {
394
+ memberWarnings.push(`参会人中有 ${unresolved.length} 位未能解析为员工 id:${unresolved.join('、')}`
395
+ + ' —— 已按原名写入。**这几位的回执与签到建不起来,且不会参与人员冲突检测**,请核对姓名或改用员工 id。');
396
+ }
397
+ const resolvedNames = nameLike.filter((name) => idsByName.has(name));
398
+ if (resolvedNames.length > 0) {
399
+ memberWarnings.push(`参会人已由姓名解析为员工 id:${resolvedNames.join('、')}`);
400
+ }
401
+ }
402
+ }
403
+ // 冲突检测只认数字 id(`toIdCsv` 会硬校验):解析不出 id 的那几位仍然留在主表里(不静默丢人),
404
+ // 但不参与人员冲突检测 —— 否则一个姓名就会让整场建会直接 id_invalid 失败。
405
+ const memberItems = String(main.hrm_members ?? '').split(/[,;,;]+/u).map((item) => item.trim()).filter((item) => item !== '');
406
+ const numericMembers = memberItems.filter((item) => /^\d+$/u.test(item));
407
+ if (memberItems.length > numericMembers.length) {
408
+ const skipped = memberItems.filter((item) => !/^\d+$/u.test(item));
409
+ memberWarnings.push(`参会人 ${skipped.join('、')} 不是员工 id,**不参与本次人员冲突检测**`
410
+ + '(这几位的回执与签到也建不起来)——请用员工 id 或可唯一匹配的姓名重新提交。');
411
+ }
412
+ const hrmMembers = numericMembers.length > 0 ? numericMembers.join(',') : undefined;
358
413
  // 建会必读应用设置(闸门);变更只在确有冲突检测目标时才读(避免多一次请求)。
359
414
  const settings = isCreate || address || hrmMembers ? await fetchEbAppSettings(env, operation) : undefined;
360
415
  const preflightWarnings = [];
@@ -367,10 +422,11 @@ export const ebMeetingWritePreflight = async ({ endpoint, payload, args, env, ex
367
422
  + '如需以另一条为准,请先在系统页面清理重复的设置记录。');
368
423
  }
369
424
  }
425
+ const head = [...preflightWarnings, ...memberWarnings];
370
426
  if (!begin || !end)
371
- return preflightWarnings;
427
+ return head;
372
428
  if (!address && !hrmMembers)
373
- return preflightWarnings;
429
+ return head;
374
430
  const outcome = await runEbConflictCheck({
375
431
  beginDatetime: begin,
376
432
  endDatetime: end,
@@ -424,8 +480,28 @@ export const ebMeetingWritePreflight = async ({ endpoint, payload, args, env, ex
424
480
  extraWarnings.push(`共同空闲时段计算失败(${String(error?.message ?? error).slice(0, 80)}),请手动换时间`);
425
481
  }
426
482
  }
483
+ // ⛔ 冲突必须**拦下来让用户拍板**,不能只在 warnings 里提一句就放行
484
+ // (2026-10-29 真机事故:CLI 返回 ok + continuation,智能体直接提交了,
485
+ // 用户事后才发现和已有会议撞了 2 小时 —— 冲突提示等于没有)。
486
+ if (args.conflictAck !== true) {
487
+ const detail = outcome.member.conflicts
488
+ .map((item) => `${item.conflictedMembers.join('、')} 在 ${item.begin}~${item.end} 已有「${item.name}」`)
489
+ .join(';');
490
+ const slots = extras.slotOptions ?? [];
491
+ const slotText = slots.length > 0
492
+ ? `当天所有人都有空的时段:${slots.map((item) => `[${item.key}] ${item.label}`).join(';')}`
493
+ : (extras.dayBlocked ? String(extras.dayBlocked.summary ?? '当天已排满') : '当天没有全员空闲时段');
494
+ throw meetingConflictError('conflict_rejected', `参会人时间冲突,**未写入任何数据**:${detail}。${slotText}。`
495
+ + '请用**选项方式**让用户确认:改到上面的候选时段,或明确接受冲突'
496
+ + '(确认后带 conflictAck=true 重新提交,届时才会真正写入)。', {
497
+ conflicts: outcome.member.conflicts,
498
+ slotOptions: extras.slotOptions ?? [],
499
+ suggestedSlots: extras.suggestedSlots ?? [],
500
+ dayBlocked: extras.dayBlocked ?? null,
501
+ });
502
+ }
427
503
  }
428
- return [...preflightWarnings, ...outcome.warnings, ...extraWarnings];
504
+ return [...preflightWarnings, ...memberWarnings, ...outcome.warnings, ...extraWarnings];
429
505
  };
430
506
  /** 只查不建:会议室冲突检测 operation。 */
431
507
  const ebConflictRoom = {