weaver-work-cli 0.1.39 → 0.1.42

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 (197) hide show
  1. package/README.md +4 -2
  2. package/dist/chunks/{archive-ENZYBBME.js → archive-J4I6MCKF.js} +2 -2
  3. package/dist/chunks/{archive-oa-YWLEC5V4.js → archive-oa-BRZKKTO4.js} +2 -2
  4. package/dist/chunks/{asset-OOV6DWPA.js → asset-L7D4FAOZ.js} +2 -2
  5. package/dist/chunks/{asset-idle-RE632MY5.js → asset-idle-DADLLV75.js} +2 -2
  6. package/dist/chunks/{attachment-content-RWN2FCLL.js → attachment-content-K4C7VLWQ.js} +3 -3
  7. package/dist/chunks/{blog-PEPOJAL7.js → blog-BVGQ7NZL.js} +2 -2
  8. package/dist/chunks/{caizhilian-WYIQDVPJ.js → caizhilian-SAADNFD6.js} +2 -2
  9. package/dist/chunks/{calendar-CIARBAOX.js → calendar-Q7NQY5Z2.js} +61 -23
  10. package/dist/chunks/{chunk-F2EQXUEE.js → chunk-3KTFS4IA.js} +397 -159
  11. package/dist/chunks/{chunk-G2JSIUWF.js → chunk-4PD5NWAX.js} +89 -0
  12. package/dist/chunks/{chunk-HJYVHXUQ.js → chunk-4RAE6JXP.js} +1 -1
  13. package/dist/chunks/{chunk-CQHGNCK2.js → chunk-72CKAUAG.js} +41 -8
  14. package/dist/chunks/{chunk-K3RUEEOL.js → chunk-APVFJUQH.js} +2 -2
  15. package/dist/chunks/{chunk-BJGXMFGD.js → chunk-BBKDXQEC.js} +2 -2
  16. package/dist/chunks/chunk-BIPYLLVS.js +501 -0
  17. package/dist/chunks/{chunk-GVPJZXJ2.js → chunk-EKWAQSLU.js} +92 -50
  18. package/dist/chunks/{chunk-XXJOSZTQ.js → chunk-FOAA27U5.js} +381 -57
  19. package/dist/chunks/{chunk-I2HB737U.js → chunk-G4UR6NXN.js} +2 -2
  20. package/dist/chunks/{chunk-YS5DPN4T.js → chunk-GEWYMQME.js} +1 -1
  21. package/dist/chunks/{chunk-P2DAISYW.js → chunk-GHXCDPTM.js} +1 -1
  22. package/dist/chunks/{chunk-L4N7B7ZD.js → chunk-IZ7S7QUS.js} +4 -4
  23. package/dist/chunks/{chunk-NLK2RZKG.js → chunk-JPRO6ER2.js} +122 -2214
  24. package/dist/chunks/{chunk-36DIFZAH.js → chunk-LADPYOWC.js} +2 -2
  25. package/dist/chunks/chunk-LDZQPUIA.js +1238 -0
  26. package/dist/chunks/chunk-NKZRQXST.js +2546 -0
  27. package/dist/chunks/{chunk-EINRJHQ3.js → chunk-QKNKR4RH.js} +3 -3
  28. package/dist/chunks/{chunk-T7JDJJ7H.js → chunk-R4CHUSKC.js} +126 -19
  29. package/dist/chunks/{chunk-EZGFHJFW.js → chunk-SA6VBIHY.js} +88 -15
  30. package/dist/chunks/{chunk-ZXRNHZTE.js → chunk-TTPJJJC4.js} +2 -2
  31. package/dist/chunks/{chunk-KTCB5R5H.js → chunk-W7BEHOSR.js} +2 -2
  32. package/dist/chunks/{chunk-MUQUHKPG.js → chunk-YRSZQUD2.js} +2 -2
  33. package/dist/chunks/{conflict-DRMDOL56.js → conflict-F2OMZRTO.js} +2 -2
  34. package/dist/chunks/{contract-73MERZWN.js → contract-5BETHRSG.js} +2 -2
  35. package/dist/chunks/{cowork-HNGBPD7A.js → cowork-3MN6EQQO.js} +27 -10
  36. package/dist/chunks/create-flow-5FQNNMVO.js +55 -0
  37. package/dist/chunks/{ebuilder-form-OY6DVNRK.js → ebuilder-form-SBHOYBUD.js} +3 -3
  38. package/dist/chunks/{ehr-RAQNEQO7.js → ehr-KMOJULKG.js} +432 -1234
  39. package/dist/chunks/env-LB7NBL2O.js +19 -0
  40. package/dist/chunks/{esb-6XV7C7YV.js → esb-W2PJCFCX.js} +4 -4
  41. package/dist/chunks/factory-6ZFQXQ46.js +30 -0
  42. package/dist/chunks/{fna-XTSRB7G7.js → fna-ZQPCUV4R.js} +2 -2
  43. package/dist/chunks/group-chat-2HHAJN46.js +20 -0
  44. package/dist/chunks/{host-YMBXWFWX.js → host-4CBHGOHT.js} +3 -3
  45. package/dist/chunks/{host-A7J6GDUP.js → host-ATKVNZAT.js} +3 -3
  46. package/dist/chunks/{host-3F5UM5GN.js → host-BQH22NTQ.js} +3 -3
  47. package/dist/chunks/{host-YAQAPYNP.js → host-X2CSLFM6.js} +3 -3
  48. package/dist/chunks/{host-VGPVP7YF.js → host-YTODKMSL.js} +3 -3
  49. package/dist/chunks/{hrm-TLHKC545.js → hrm-FFM4UJFS.js} +4 -4
  50. package/dist/chunks/{im-YOUCFMUK.js → im-VEHKDQPB.js} +8 -8
  51. package/dist/chunks/{invoice-WNBS2B4C.js → invoice-LVIKSIIF.js} +3 -3
  52. package/dist/chunks/{jiuchuanhui-XABWS5RN.js → jiuchuanhui-X6DINO5P.js} +2 -2
  53. package/dist/chunks/{lazy-dispatch-NLZ5UQQI.js → lazy-dispatch-KSWTN5L6.js} +4 -4
  54. package/dist/chunks/{mail-LRYMBFNL.js → mail-VDKRGUD3.js} +9 -4
  55. package/dist/chunks/{meeting-ESXL6MFI.js → meeting-CLC6HP3Z.js} +12 -10
  56. package/dist/chunks/{okr-EOV65SSF.js → okr-CHG4OO3U.js} +2 -2
  57. package/dist/chunks/{plan-YFPVKW6Y.js → plan-XWR75667.js} +2 -2
  58. package/dist/chunks/{project-MDVZUYQW.js → project-5BXOOKMG.js} +40 -23
  59. package/dist/chunks/recruit-stage-DGMFNNDE.js +4507 -0
  60. package/dist/chunks/{registry-VT5Y2HLA.js → registry-7FFOXIAW.js} +4 -4
  61. package/dist/chunks/{registry-QCLZEDAP.js → registry-BKBZPBAJ.js} +10 -8
  62. package/dist/chunks/{registry-YX4CSEMD.js → registry-RHMW6ANW.js} +2 -2
  63. package/dist/chunks/{registry-NYPYMZXG.js → registry-Z43434VG.js} +1 -1
  64. package/dist/chunks/{room-recommend-L3MWH4JW.js → room-recommend-62H7ZQQ6.js} +4 -4
  65. package/dist/chunks/{schedule-UASAVDVD.js → schedule-FNXKH3DQ.js} +4 -4
  66. package/dist/chunks/{workflow-KDGIUCNW.js → workflow-ADHKCAJK.js} +4 -4
  67. package/dist/index.js +33 -28
  68. package/dist/internal/e10/auth/commands.js +110 -56
  69. package/dist/internal/e10/auth/messages.js +6 -0
  70. package/dist/internal/e10/auth/session.js +28 -1
  71. package/dist/internal/e10/auth/xiaoe.js +315 -48
  72. package/dist/internal/e10/auth-recovery.js +36 -5
  73. package/dist/internal/e10/client.js +13 -0
  74. package/dist/internal/e10/context.js +61 -9
  75. package/dist/internal/e10/index.js +1 -1
  76. package/dist/internal/e10/network-error.js +93 -0
  77. package/dist/internal/e10/request-runtime.js +15 -0
  78. package/dist/shortcuts/calendar/operations/read.js +31 -22
  79. package/dist/shortcuts/calendar/operations/write.js +45 -1
  80. package/dist/shortcuts/cowork/operations/discuss-write.js +5 -1
  81. package/dist/shortcuts/cowork/operations/item-write.js +5 -3
  82. package/dist/shortcuts/cowork/operations/member-write.js +3 -1
  83. package/dist/shortcuts/cowork/operations/permission-write.js +5 -1
  84. package/dist/shortcuts/cowork/operations/shared.js +7 -0
  85. package/dist/shortcuts/ehr/operations/attend-write.js +43 -1
  86. package/dist/shortcuts/ehr/operations/hr-contact.js +7 -4
  87. package/dist/shortcuts/ehr/operations/hr-report.js +13 -7
  88. package/dist/shortcuts/ehr/operations/recruit-candidate-import.js +134 -59
  89. package/dist/shortcuts/ehr/operations/recruit-write.js +262 -17
  90. package/dist/shortcuts/ehr/operations/recruit.js +10 -4
  91. package/dist/shortcuts/ehr/operations/shared.js +47 -0
  92. package/dist/shortcuts/hrm/operations/org.js +118 -0
  93. package/dist/shortcuts/index.js +5 -0
  94. package/dist/shortcuts/mail/operations/mail-send.js +8 -2
  95. package/dist/shortcuts/meeting/operations/eb/catalog.js +9 -2
  96. package/dist/shortcuts/meeting/operations/eb/create-flow.js +461 -127
  97. package/dist/shortcuts/meeting/operations/eb/crm-search.js +87 -5
  98. package/dist/shortcuts/meeting/operations/eb/factory.js +33 -0
  99. package/dist/shortcuts/meeting/operations/eb/flow-memory.js +49 -0
  100. package/dist/shortcuts/meeting/operations/eb/group-chat.js +605 -0
  101. package/dist/shortcuts/meeting/operations/eb/index.js +2 -0
  102. package/dist/shortcuts/meeting/operations/eb/person-cache.js +96 -0
  103. package/dist/shortcuts/meeting/operations/eb/room-recommend.js +44 -6
  104. package/dist/shortcuts/meeting/operations/eb/room-summary.js +2 -0
  105. package/dist/shortcuts/meeting/operations/eb/usage-cache.js +99 -0
  106. package/dist/shortcuts/meeting/operations/from-group-chat.js +35 -12
  107. package/dist/shortcuts/project/operations/read.js +6 -25
  108. package/dist/shortcuts/project/operations/shared.js +22 -0
  109. package/dist/shortcuts/project/operations/write.js +28 -2
  110. package/dist/shortcuts/recruit-stage/context.js +119 -0
  111. package/dist/shortcuts/recruit-stage/continuation.js +73 -0
  112. package/dist/shortcuts/recruit-stage/errors.js +65 -0
  113. package/dist/shortcuts/recruit-stage/host.js +109 -0
  114. package/dist/shortcuts/recruit-stage/index.js +115 -0
  115. package/dist/shortcuts/recruit-stage/manifest.js +29 -0
  116. package/dist/shortcuts/recruit-stage/operations/interview-arrange.js +722 -0
  117. package/dist/shortcuts/recruit-stage/operations/interview-feedback.js +269 -0
  118. package/dist/shortcuts/recruit-stage/operations/jd-create.js +630 -0
  119. package/dist/shortcuts/recruit-stage/operations/list.js +431 -0
  120. package/dist/shortcuts/recruit-stage/operations/registry.js +38 -0
  121. package/dist/shortcuts/recruit-stage/operations/resume-import.js +1198 -0
  122. package/dist/shortcuts/recruit-stage/operations/search.js +158 -0
  123. package/dist/shortcuts/recruit-stage/operations/types.js +25 -0
  124. package/dist/shortcuts/recruit-stage/school-tiers.json +318 -0
  125. package/dist/shortcuts/recruit-stage/shared.js +608 -0
  126. package/dist/shortcuts/workflow/operations/form.js +21 -2
  127. package/dist/shortcuts/workflow/operations/todoSmartConvert/aigroupRules.js +52 -1
  128. package/dist/shortcuts/workflow/operations/todoSmartConvert/detailFieldRules.js +4 -2
  129. package/dist/shortcuts/workflow/operations/todoSmartConvert/summaryCache.js +19 -11
  130. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoDetailCache.js +17 -24
  131. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoDetailFetcher.js +14 -9
  132. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.collect.js +78 -10
  133. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.core.js +3 -1
  134. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.detail.js +17 -11
  135. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.group.js +16 -40
  136. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.groupSummary.js +3 -2
  137. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.js +52 -15
  138. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.pipeline.js +16 -1
  139. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.smartGroup.js +151 -63
  140. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.subGroup.js +56 -0
  141. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.summary.js +53 -16
  142. package/docs/e10-auth.md +22 -1
  143. package/docs/operation-manual.md +8 -0
  144. package/package.json +1 -1
  145. package/scripts/install-skill-to-xiaoe.mjs +105 -0
  146. package/scripts/mcp-meeting.mjs +234 -0
  147. package/scripts/package-skill.mjs +4 -2
  148. package/skill-template/business-info.json +24 -0
  149. package/skill-template/domains/recruit-stage.md +29 -0
  150. package/skills/weaver-e10-calendar/SKILL.md +2 -1
  151. package/skills/weaver-e10-calendar/references/calendar-write.md +54 -0
  152. package/skills/weaver-e10-cowork/SKILL.md +1 -1
  153. package/skills/weaver-e10-cowork/references/cowork-write.md +3 -1
  154. package/skills/weaver-e10-jucailin/SKILL.md +1 -1
  155. package/skills/weaver-e10-jucailin/references/attend-write.md +8 -0
  156. package/skills/weaver-e10-jucailin/references/hr-contact.md +8 -0
  157. package/skills/weaver-e10-jucailin/references/hr-report.md +8 -0
  158. package/skills/weaver-e10-jucailin/references/recruit-create.md +2 -2
  159. package/skills/weaver-e10-jucailin/references/recruit-read.md +1 -1
  160. package/skills/weaver-e10-jucailin/references/recruit-write.md +4 -3
  161. package/skills/weaver-e10-mail/SKILL.md +1 -1
  162. package/skills/weaver-e10-mail/references/mail-send.md +2 -0
  163. package/skills/weaver-e10-meeting/SKILL.md +119 -26
  164. package/skills/weaver-e10-meeting/references/meeting-minute-file.md +25 -11
  165. package/skills/weaver-e10-meeting.zip +0 -0
  166. package/skills/weaver-e10-recruit-stage/SKILL.md +226 -0
  167. package/skills/weaver-e10-recruit-stage/agents/openai.yaml +119 -0
  168. package/skills/weaver-e10-recruit-stage/lib/recruit-xiaoe-file.mjs +188 -0
  169. package/skills/weaver-e10-recruit-stage/references/recruit-arrange-interview.md +322 -0
  170. package/skills/weaver-e10-recruit-stage/references/recruit-capability-check.md +127 -0
  171. package/skills/weaver-e10-recruit-stage/references/recruit-check-install.md +65 -0
  172. package/skills/weaver-e10-recruit-stage/references/recruit-detail-process.md +63 -0
  173. package/skills/weaver-e10-recruit-stage/references/recruit-edit-interview-feedback.md +142 -0
  174. package/skills/weaver-e10-recruit-stage/references/recruit-jd-create.md +73 -0
  175. package/skills/weaver-e10-recruit-stage/references/recruit-jd-template.md +56 -0
  176. package/skills/weaver-e10-recruit-stage/references/recruit-list-candidate.md +101 -0
  177. package/skills/weaver-e10-recruit-stage/references/recruit-list-interview.md +148 -0
  178. package/skills/weaver-e10-recruit-stage/references/recruit-match-candidates.md +77 -0
  179. package/skills/weaver-e10-recruit-stage/references/recruit-output-links.md +59 -0
  180. package/skills/weaver-e10-recruit-stage/references/recruit-resolve-creator.md +69 -0
  181. package/skills/weaver-e10-recruit-stage/references/recruit-resolve-metadata.md +241 -0
  182. package/skills/weaver-e10-recruit-stage/references/recruit-resume-import.md +438 -0
  183. package/skills/weaver-e10-recruit-stage/references/recruit-resume-parse.md +128 -0
  184. package/skills/weaver-e10-recruit-stage/references/recruit-search-candidate.md +146 -0
  185. package/skills/weaver-e10-recruit-stage/references/recruit-urge-feedback.md +69 -0
  186. package/skills/weaver-e10-shared/SKILL.md +2 -2
  187. package/skills/weaver-e10-shared/references/e10-auth-and-session.md +4 -2
  188. package/skills/weaver-e10-shijingran/SKILL.md +3 -2
  189. package/skills/weaver-e10-shijingran/references/operations.md +3 -1
  190. package/skills/weaver-e10-shijingran/references/source-manifest.json +41 -40
  191. package/skills/weaver-e10-shijingran/references/write-operations.md +2 -0
  192. package/skills/weaver-e10-workflow/SKILL.md +7 -1
  193. package/skills/weaver-e10-workflow/aigroup/default.json +23 -9
  194. package/skills/weaver-e10-workflow/references/todo-smart-convert.md +37 -21
  195. package/skills/weaver-e10-workflow/tools/todo-summary.cjs +135 -43
  196. package/skills/weaver-e10-yepiaotong/SKILL.md +9 -10
  197. package/skills/weaver-e10-yepiaotong/references/invoice-agent-entry.md +6 -3
@@ -0,0 +1,605 @@
1
+ import { mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises';
2
+ import { resolve } from 'node:path';
3
+ import { meetingSuccess, schemaOf } from '../types.js';
4
+ import { meetingValidationError } from '../../errors.js';
5
+ import { meetingStateDir } from '../env.js';
6
+ /**
7
+ * 建会同步建群聊(2026-11-02 用户要求):
8
+ *
9
+ * ```
10
+ * 会议要素(名称/起止/参会人 id/会议室)
11
+ * → ESB 动作流 1333212605456498718(一次调用做完两件事:建群 + 群里发会议信息)
12
+ * → 动作流 responseData 是空对象、不回群 id
13
+ * → im.group.search 按「群名 = 建会时的会议名称」反查群 id(异步,要重试)
14
+ * → 会议 id 已知时回写 imchat_groupid;不知道就落一条「待绑」记录,等用户提交后再绑
15
+ * ```
16
+ *
17
+ * ⛔ 三个现场定案:
18
+ * 1. 入参字段名是 `mt_name` / `begindatetime` / `enddatetime` / `hrmmembers` / `address`,
19
+ * `hrmmembers` **必须是人员 id、逗号分隔**(传姓名建出来的群没有成员)。
20
+ * 2. 动作流**不返回群 id**(`{"resultCode":200,"resultMsg":"执行成功","actionData":{"responseData":{}}}`),
21
+ * 所以只能反查;反查不到就如实返回 `groupId:null`,**不许编一个 id 回写**。
22
+ * 3. 事后补绑**只认 requestId,不认会议名称**(2026-11-02 用户纠正):
23
+ * 用户在表单里可能改名、也可能有同名会议 —— 会议记录里的 `request_id` 才是唯一凭据。
24
+ */
25
+ /** 建群动作流 id(本租户配置);换租户用入参 `flowId` 覆盖。 */
26
+ export const DEFAULT_MEETING_GROUP_FLOW_ID = '1333212605456498718';
27
+ /** 会议表里存群 id 的字段。 */
28
+ const GROUP_FIELD = 'imchat_groupid';
29
+ /** 待绑记录目录(浏览器填单那一档落盘,等用户提交后再补绑)。 */
30
+ const PENDING_DIR = 'pending-forms';
31
+ async function buildEsbEnv(env) {
32
+ const { createEsbHost } = await import('../../../esb/host.js');
33
+ const { createEsbContinuationSigner } = await import('../../../esb/continuation.js');
34
+ return { ctx: env.ctx, host: createEsbHost(env.ctx), continuation: createEsbContinuationSigner(env.ctx) };
35
+ }
36
+ export async function buildImEnv(env) {
37
+ const { createImHost } = await import('../../../im/host.js');
38
+ const { createImContinuationSigner } = await import('../../../im/continuation.js');
39
+ return { ctx: env.ctx, host: createImHost(env.ctx), continuation: createImContinuationSigner(env.ctx) };
40
+ }
41
+ async function buildWorkflowEnv(env) {
42
+ const { createWorkflowHost } = await import('../../../workflow/host.js');
43
+ const { createWorkflowContinuationSigner } = await import('../../../workflow/continuation.js');
44
+ return { ctx: env.ctx, host: createWorkflowHost(env.ctx), continuation: createWorkflowContinuationSigner(env.ctx) };
45
+ }
46
+ /** 本地时间串 → 毫秒(`2026-11-02 22:25:59` / `2026/11/2 22:25` 都认)。 */
47
+ function parseLocalTime(value) {
48
+ const text = String(value ?? '').trim();
49
+ if (text === '')
50
+ return 0;
51
+ const normalized = /^\d{4}-\d{2}-\d{2}$/u.test(text) ? `${text} 00:00:00` : text;
52
+ const ts = new Date(normalized.replace(/-/g, '/')).getTime();
53
+ return Number.isFinite(ts) ? ts : 0;
54
+ }
55
+ function formatLocalTime(date) {
56
+ const pad = (n) => String(n).padStart(2, '0');
57
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} `
58
+ + `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
59
+ }
60
+ // ---------------------------------------------------------------------------
61
+ // 待绑记录(浏览器填单那一档)
62
+ // ---------------------------------------------------------------------------
63
+ function pendingDirOf(env) {
64
+ return resolve(meetingStateDir(env.ctx), PENDING_DIR);
65
+ }
66
+ /** 表单交给用户时落一条记录:群已建好,但还没有会议 id —— 等用户提交后按 requestId 补绑。 */
67
+ export async function savePendingForm(env, record) {
68
+ try {
69
+ const dir = pendingDirOf(env);
70
+ await mkdir(dir, { recursive: true });
71
+ const preparedAt = String(record.preparedAt ?? '').trim() || formatLocalTime(new Date());
72
+ // 基线:此刻「我发起的」里最新的一张单。之后新冒出来的那张才是这次提交的 ——
73
+ // workflow.search 的卡片没有创建时间、createTime 过滤在本环境也不生效,只能靠这个办法定位。
74
+ let baselineRequestId = '';
75
+ try {
76
+ const latest = await searchMyRequests(env, {
77
+ ...(record.workflowId ? { workflowId: record.workflowId } : {}),
78
+ });
79
+ baselineRequestId = latest[0]?.requestId ?? '';
80
+ }
81
+ catch {
82
+ baselineRequestId = '';
83
+ }
84
+ const body = { ...record, preparedAt, baselineRequestId };
85
+ const file = resolve(dir, `${Date.now()}-${String(record.workflowId || 'wf')}.json`);
86
+ await writeFile(file, `${JSON.stringify(body, null, 2)}\n`, 'utf8');
87
+ // 只留最近 30 条,避免长期堆积
88
+ const files = (await readdir(dir)).filter((one) => one.endsWith('.json')).sort();
89
+ for (const stale of files.slice(0, Math.max(0, files.length - 30))) {
90
+ await rm(resolve(dir, stale), { force: true });
91
+ }
92
+ }
93
+ catch {
94
+ // 落盘失败不影响建会:补绑时可以改传 requestId
95
+ }
96
+ }
97
+ /** 读最近一条待绑记录(按文件名倒序 = 时间倒序)。 */
98
+ async function loadLatestPending(env) {
99
+ try {
100
+ const dir = pendingDirOf(env);
101
+ const files = (await readdir(dir)).filter((one) => one.endsWith('.json')).sort().reverse();
102
+ for (const file of files) {
103
+ try {
104
+ const parsed = JSON.parse(await readFile(resolve(dir, file), 'utf8'));
105
+ if (parsed && String(parsed.groupId ?? '').trim() !== '')
106
+ return parsed;
107
+ }
108
+ catch {
109
+ // 坏文件跳过
110
+ }
111
+ }
112
+ return null;
113
+ }
114
+ catch {
115
+ return null;
116
+ }
117
+ }
118
+ // ---------------------------------------------------------------------------
119
+ // 群:建 / 查 / 绑
120
+ // ---------------------------------------------------------------------------
121
+ /** 在动作流返回里找群 id:只认键名带 group/chat 的,避免把会议 id、动作流 id 当成群 id。 */
122
+ function pickGroupId(value, depth = 0) {
123
+ if (value === null || value === undefined || depth > 6)
124
+ return '';
125
+ if (Array.isArray(value)) {
126
+ for (const one of value) {
127
+ const hit = pickGroupId(one, depth + 1);
128
+ if (hit)
129
+ return hit;
130
+ }
131
+ return '';
132
+ }
133
+ if (typeof value !== 'object')
134
+ return '';
135
+ const obj = value;
136
+ for (const [key, raw] of Object.entries(obj)) {
137
+ const normalized = key.toLowerCase().replace(/[_-]/gu, '');
138
+ if (normalized.includes('group') || normalized.includes('chat')) {
139
+ const text = raw === null || raw === undefined ? '' : String(raw).trim();
140
+ if (/^\d{6,}$/u.test(text))
141
+ return text;
142
+ }
143
+ }
144
+ for (const raw of Object.values(obj)) {
145
+ const hit = pickGroupId(raw, depth + 1);
146
+ if (hit)
147
+ return hit;
148
+ }
149
+ return '';
150
+ }
151
+ /** 按群名反查群 id:精确同名优先,再取创建时间最新的一个;多候选且非精确时宁可不认。 */
152
+ async function findGroupByName(env, name) {
153
+ const target = name.trim();
154
+ if (target === '')
155
+ return null;
156
+ try {
157
+ const { getImOperation } = await import('../../../im/operations/registry.js');
158
+ const search = getImOperation('im.group.search');
159
+ const imEnv = await buildImEnv(env);
160
+ const found = await search.handler({ name: target }, imEnv);
161
+ const groups = (found?.data?.groups ?? [])
162
+ .map((one) => ({
163
+ id: String(one.id ?? '').trim(),
164
+ name: String(one.name ?? '').trim(),
165
+ createTime: String(one.createTime ?? '').trim(),
166
+ }))
167
+ .filter((one) => one.id !== '');
168
+ if (groups.length === 0)
169
+ return null;
170
+ const exact = groups.filter((one) => one.name === target);
171
+ const pool = exact.length > 0 ? exact : groups;
172
+ if (exact.length === 0 && pool.length > 1)
173
+ return null;
174
+ pool.sort((a, b) => parseLocalTime(b.createTime) - parseLocalTime(a.createTime));
175
+ return { ...pool[0], exact: exact.length > 0 };
176
+ }
177
+ catch {
178
+ return null;
179
+ }
180
+ }
181
+ /** 反查群 id 带重试:动作流建群是**异步**的(实测 apply 返回后立刻查是 0 条,1~3 秒后才出现)。 */
182
+ async function findGroupWithRetry(env, name) {
183
+ const delays = [0, 1200, 2500, 4000];
184
+ for (let index = 0; index < delays.length; index += 1) {
185
+ if (delays[index] > 0)
186
+ await new Promise((done) => setTimeout(done, delays[index]));
187
+ const hit = await findGroupByName(env, name);
188
+ if (hit)
189
+ return hit;
190
+ }
191
+ return null;
192
+ }
193
+ /** 触发建群动作流(建群 + 群内发会议信息一次完成)。 */
194
+ async function triggerGroupFlow(env, input, meetingName, memberIds) {
195
+ const session = await env.host.getSession();
196
+ const employeeId = String(input.employeeId ?? session?.userId ?? '').trim();
197
+ if (!employeeId) {
198
+ return { actionData: null, error: '取不到当前登录用户 id,无法触发建群动作流' };
199
+ }
200
+ const mainTable = {
201
+ mt_name: meetingName,
202
+ begindatetime: String(input.beginDatetime ?? ''),
203
+ enddatetime: String(input.endDatetime ?? ''),
204
+ hrmmembers: memberIds.join(','),
205
+ };
206
+ if (input.address) {
207
+ // ⛔ 2026-11-04 用户反馈:建群动作流把 `address` **原样打进群消息**(「会议地点:100502820000000226」)。
208
+ // ESB 模板改不了,所以这里先把它解析成会议室名称再传;解析不到才退回原值。
209
+ // 另外入参可能是 `{id,name}` 对象或被 String() 过的 "[object Object]",一并兜住。
210
+ const raw = input.address;
211
+ let addressText = raw && typeof raw === 'object'
212
+ ? String(raw.name ?? raw.id ?? '')
213
+ : String(raw).trim();
214
+ if (addressText === '[object Object]')
215
+ addressText = '';
216
+ if (addressText) {
217
+ try {
218
+ const { resolveAddressRoom } = await import('./create-flow.js');
219
+ const room = await resolveAddressRoom(env, addressText);
220
+ if (room?.name)
221
+ addressText = room.name;
222
+ }
223
+ catch {
224
+ // 台账读不到不影响建群,退回原值
225
+ }
226
+ }
227
+ if (addressText)
228
+ mainTable.address = addressText;
229
+ }
230
+ try {
231
+ const { getEsbOperation } = await import('../../../esb/operations/registry.js');
232
+ const esbEnv = await buildEsbEnv(env);
233
+ const prepare = getEsbOperation('esb.trigger.prepare');
234
+ const apply = getEsbOperation('esb.trigger.apply');
235
+ const pre = await prepare.handler({
236
+ esbFlowId: String(input.flowId ?? '').trim() || DEFAULT_MEETING_GROUP_FLOW_ID,
237
+ employeeId,
238
+ customParams: { mainTable },
239
+ moduleSource: '',
240
+ }, esbEnv);
241
+ const applied = await apply.handler({ continuation: pre?.data?.continuation, confirm: true }, esbEnv);
242
+ if (applied?.ok === false) {
243
+ return {
244
+ actionData: applied?.data ?? null,
245
+ error: String(applied?.error?.message ?? applied?.message ?? '建群动作流执行失败').slice(0, 300),
246
+ };
247
+ }
248
+ return { actionData: applied?.data?.actionData ?? null };
249
+ }
250
+ catch (error) {
251
+ return { actionData: null, error: String(error?.message ?? error).slice(0, 300) };
252
+ }
253
+ }
254
+ /** 把群 id 写进会议表 `imchat_groupid`,并回读确认(接口不回该字段时如实返回 undefined)。 */
255
+ async function bindGroupToMeeting(env, meetingId, groupId) {
256
+ const id = String(meetingId).trim();
257
+ const group = String(groupId).trim();
258
+ if (id === '' || group === '')
259
+ return { ok: false, error: '没有会议 id 或群 id' };
260
+ try {
261
+ const { EB_BASE, ebBusinessSuccess, writeBody } = await import('./factory.js');
262
+ const session = await env.host.getSession();
263
+ // 走 updateFormData 直写:`imchat_groupid` 是系统维护字段,不在 change_meeting 的内置白名单里,
264
+ // 用 prepare/apply 链会被字段过滤掉;这里与原先补写客户字段同一条已验证通路。
265
+ await env.host.request({
266
+ method: 'POST',
267
+ path: `${EB_BASE}/updateFormData/change_meeting`,
268
+ query: { id },
269
+ operation: 'meeting.eb.meeting.group.ensure',
270
+ body: writeBody(String(session.userId), {
271
+ mainTable: { id, operate_type: 'UPDATE', [GROUP_FIELD]: group },
272
+ details: {},
273
+ operateType: {},
274
+ }),
275
+ acceptBusiness: ebBusinessSuccess,
276
+ });
277
+ }
278
+ catch (error) {
279
+ return { ok: false, error: String(error?.message ?? error).slice(0, 300) };
280
+ }
281
+ try {
282
+ const { getMeetingOperation } = await import('../registry.js');
283
+ const detail = getMeetingOperation('meeting.eb.meeting.detail');
284
+ const read = await detail.handler({ id }, env);
285
+ const main = (read?.data?.data ?? read?.data?.main ?? {});
286
+ const value = main[GROUP_FIELD];
287
+ if (value === undefined || value === null)
288
+ return { ok: true };
289
+ const text = String(value).trim();
290
+ if (text === group)
291
+ return { ok: true, readBack: text };
292
+ if (text !== '')
293
+ return { ok: false, error: `回读不一致:${text}` };
294
+ return { ok: true };
295
+ }
296
+ catch {
297
+ return { ok: true };
298
+ }
299
+ }
300
+ // ---------------------------------------------------------------------------
301
+ // 按 requestId 反查会议(唯一凭据:会议记录的 request_id)
302
+ // ---------------------------------------------------------------------------
303
+ /** 我发起的流程(最近一页,按创建时间倒序),供「用户刚提交完」这种场景找单号。 */
304
+ async function searchMyRequests(env, options = {}) {
305
+ try {
306
+ const { getWorkflowOperation } = await import('../../../workflow/operations/registry.js');
307
+ const op = getWorkflowOperation('workflow.search');
308
+ const wfEnv = await buildWorkflowEnv(env);
309
+ // 卡片里没有创建时间字段(实测 createdAt 为空),所以时间必须走服务端 createTime 过滤,
310
+ // 否则「今天所有我发起的单」都会进来,等于没筛。
311
+ const args = { category: 'mine', pageSize: 20, sort: 'createTime:desc' };
312
+ if (options.sinceMs) {
313
+ const from = formatLocalTime(new Date(options.sinceMs)).slice(0, 16);
314
+ const to = formatLocalTime(new Date()).slice(0, 16);
315
+ args.createTime = `${from}~${to}`;
316
+ }
317
+ const res = await op.handler(args, wfEnv);
318
+ const cards = (res?.data?.cards ?? [])
319
+ .map((one) => ({
320
+ requestId: String(one.requestId ?? '').trim(),
321
+ flowName: String(one.flowName ?? '').trim(),
322
+ workflowId: String(one.workflowId ?? '').trim(),
323
+ createdAt: String(one.createdAt ?? one.createTime ?? '').trim(),
324
+ }))
325
+ .filter((one) => one.requestId !== '');
326
+ return cards.filter((one) => {
327
+ if (options.workflowId && one.workflowId !== options.workflowId)
328
+ return false;
329
+ if (options.sinceMs) {
330
+ const created = parseLocalTime(one.createdAt);
331
+ if (created !== 0 && created < options.sinceMs)
332
+ return false;
333
+ }
334
+ return true;
335
+ });
336
+ }
337
+ catch {
338
+ return [];
339
+ }
340
+ }
341
+ /**
342
+ * 按 requestId 找会议:列表接口的 `request_id` 过滤参数**实测无效**(传什么都回全量),
343
+ * 所以只能「取最新若干条会议 → 逐个 detail 比对 request_id」。候选用 `create_time >= since` 收窄,
344
+ * 一次补绑最多查 10 条,正常只有 1~2 条。
345
+ */
346
+ async function resolveMeetingByRequestId(env, requestId, sinceMs) {
347
+ const target = String(requestId).trim();
348
+ if (target === '')
349
+ return null;
350
+ try {
351
+ const { getMeetingOperation } = await import('../registry.js');
352
+ const list = getMeetingOperation('meeting.eb.meeting.list');
353
+ const detail = getMeetingOperation('meeting.eb.meeting.detail');
354
+ // ① 直连过滤走不通:`request_id` 不是查询接口的入参(接口文档 2026-11-02 确认),
355
+ // 出参又是「按显示名称」(回流程标题)。只能扫表 + detail 比对单号。
356
+ // ② 取最新若干条,按创建时间收窄后逐个比对 request_id(detail 的出参是「按选项值」,回真单号)
357
+ const page = await list.handler({ pageSize: 30 }, env);
358
+ const rows = (page?.data?.rows ?? [])
359
+ .map((row) => (row?.mainTable ?? row ?? {}))
360
+ .filter((main) => String(main.id ?? '').trim() !== '');
361
+ const candidates = rows
362
+ .filter((main) => {
363
+ const created = parseLocalTime(main.create_time);
364
+ // create_time 缺失时不排除,交给 detail 判定
365
+ return created === 0 || sinceMs === 0 || created >= sinceMs - 120_000;
366
+ })
367
+ .slice(0, 10);
368
+ for (const candidate of candidates) {
369
+ const id = String(candidate.id).trim();
370
+ try {
371
+ const read = await detail.handler({ id }, env);
372
+ const main = (read?.data?.data ?? read?.data?.main ?? {});
373
+ if (String(main.request_id ?? '').trim() === target) {
374
+ return {
375
+ id,
376
+ name: String(main.mt_name ?? candidate.mt_name ?? '').trim(),
377
+ begin: String(main.begin_datetime ?? candidate.begin_datetime ?? '').trim(),
378
+ requestId: target,
379
+ };
380
+ }
381
+ }
382
+ catch {
383
+ // 单条读失败不影响继续找
384
+ }
385
+ }
386
+ return null;
387
+ }
388
+ catch {
389
+ return null;
390
+ }
391
+ }
392
+ /**
393
+ * 建会时同步建群 + 绑定(供 create.flow.apply / quick 内部调用,也单独暴露成 operation)。
394
+ *
395
+ * 幂等:同名群已存在时**不重复建**,直接复用;补绑只认 requestId(不认名称)。
396
+ */
397
+ export async function ensureMeetingGroupChat(env, input) {
398
+ const memberIds = (input.memberIds ?? []).map((one) => String(one).trim()).filter((one) => one !== '');
399
+ const pending = input.lookupPending === true ? await loadLatestPending(env) : null;
400
+ let meetingName = String(input.meetingName ?? pending?.mtName ?? '').trim();
401
+ let meetingId = String(input.meetingId ?? '').trim();
402
+ let requestId = String(input.requestId ?? '').trim();
403
+ let meetingLookup = null;
404
+ let requestCandidates;
405
+ // 补绑场景:没有会议 id 时,按「待绑记录 → 流程单 → 会议」这条链走(名称不参与判定)
406
+ if (meetingId === '' && (input.lookupPending === true || requestId !== '')) {
407
+ const sinceMs = parseLocalTime(pending?.preparedAt) || (Date.now() - 2 * 3600_000);
408
+ if (requestId === '') {
409
+ const requests = await searchMyRequests(env, {
410
+ ...(pending?.workflowId ? { workflowId: pending.workflowId } : {}),
411
+ });
412
+ // 用建群时记下的基线切分:排在基线上面的(更新)才是用户这次提交的单。
413
+ // 找不到基线就只认最新一条,并把候选交给用户确认(不猜)。
414
+ const baseline = String(pending?.baselineRequestId ?? '').trim();
415
+ const baselineIndex = baseline === '' ? -1 : requests.findIndex((one) => one.requestId === baseline);
416
+ const fresh = baselineIndex > 0
417
+ ? requests.slice(0, baselineIndex)
418
+ : baselineIndex === 0
419
+ ? []
420
+ : requests.slice(0, 3);
421
+ if (fresh.length === 1) {
422
+ requestId = fresh[0].requestId;
423
+ }
424
+ else if (fresh.length > 1) {
425
+ requestCandidates = fresh.slice(0, 5).map((one) => ({
426
+ requestId: one.requestId,
427
+ flowName: one.flowName,
428
+ createdAt: one.createdAt,
429
+ }));
430
+ }
431
+ }
432
+ if (requestId !== '') {
433
+ meetingLookup = await resolveMeetingByRequestId(env, requestId, sinceMs);
434
+ if (meetingLookup) {
435
+ meetingId = meetingLookup.id;
436
+ // 名称只用来**找群**(群名就是建群时的会议名);定位会议始终靠 requestId
437
+ if (meetingName === '')
438
+ meetingName = meetingLookup.name;
439
+ }
440
+ }
441
+ }
442
+ if (meetingName === '' && input.groupId === undefined && pending === null) {
443
+ return {
444
+ ok: false,
445
+ groupId: null,
446
+ groupName: null,
447
+ created: false,
448
+ bound: false,
449
+ meetingId: meetingId || null,
450
+ requestId: requestId || null,
451
+ error: '既没有会议名称,也没有本机待绑记录/群 id,无法建群或补绑',
452
+ };
453
+ }
454
+ // ① 已知群 id(重试/补绑场景);补绑时群 id 来自待绑记录
455
+ const knownGroupId = String(input.groupId ?? pending?.groupId ?? '').trim();
456
+ if (knownGroupId !== '') {
457
+ const bound = meetingId
458
+ ? await bindGroupToMeeting(env, meetingId, knownGroupId)
459
+ : {
460
+ ok: false,
461
+ error: requestCandidates
462
+ ? '同一时间窗有多个流程单,先让用户确认是哪一张(见 requestCandidates),再传 requestId'
463
+ : requestId
464
+ ? `流程单 ${requestId} 对应不到会议(可能还在审批、或会议尚未同步)`
465
+ : '没拿到流程单 id,无法定位会议',
466
+ };
467
+ return {
468
+ ok: bound.ok,
469
+ groupId: knownGroupId,
470
+ groupName: pending?.groupName ?? null,
471
+ created: false,
472
+ bound: bound.ok,
473
+ meetingId: meetingId || null,
474
+ requestId: requestId || null,
475
+ ...(input.lookupPending === true || requestId !== '' ? { meetingLookup } : {}),
476
+ ...(requestCandidates ? { requestCandidates } : {}),
477
+ ...(bound.readBack ? { readBack: bound.readBack } : {}),
478
+ ...(bound.error ? { error: bound.error } : {}),
479
+ };
480
+ }
481
+ // ② 已有同名群 → 直接复用(幂等,不重复建)
482
+ const existing = await findGroupByName(env, meetingName);
483
+ let groupId = existing?.id ?? '';
484
+ let groupName = existing?.name ?? '';
485
+ let created = false;
486
+ let error;
487
+ if (groupId === '') {
488
+ const triggered = await triggerGroupFlow(env, input, meetingName, memberIds);
489
+ error = triggered.error;
490
+ groupId = pickGroupId(triggered.actionData);
491
+ if (groupId === '') {
492
+ const newest = await findGroupWithRetry(env, meetingName);
493
+ groupId = newest?.id ?? '';
494
+ groupName = newest?.name ?? '';
495
+ }
496
+ created = groupId !== '';
497
+ }
498
+ if (groupId === '') {
499
+ return {
500
+ ok: false,
501
+ groupId: null,
502
+ groupName: null,
503
+ created: false,
504
+ bound: false,
505
+ meetingId: meetingId || null,
506
+ requestId: requestId || null,
507
+ ...(requestCandidates ? { requestCandidates } : {}),
508
+ ...(error ? { error } : { error: '建群动作流已触发,但没能反查到群 id(群名与会议名称不一致?)' }),
509
+ };
510
+ }
511
+ // ③ 有会议 id 才回写;没有就只回报群信息(等用户提交后按 requestId 补绑)
512
+ if (meetingId === '') {
513
+ return {
514
+ ok: true,
515
+ groupId,
516
+ groupName: groupName || meetingName,
517
+ created,
518
+ bound: false,
519
+ meetingId: null,
520
+ requestId: requestId || null,
521
+ ...(requestCandidates ? { requestCandidates } : {}),
522
+ ...(input.lookupPending === true && !requestCandidates
523
+ ? { error: '群已建好,但还没找到对应流程单(可能用户还没提交,或刚提交还没同步)' }
524
+ : {}),
525
+ };
526
+ }
527
+ const bound = await bindGroupToMeeting(env, meetingId, groupId);
528
+ return {
529
+ ok: bound.ok,
530
+ groupId,
531
+ groupName: groupName || meetingName,
532
+ created,
533
+ bound: bound.ok,
534
+ meetingId,
535
+ requestId: requestId || null,
536
+ ...(input.lookupPending === true || requestId !== '' ? { meetingLookup } : {}),
537
+ ...(requestCandidates ? { requestCandidates } : {}),
538
+ ...(bound.readBack ? { readBack: bound.readBack } : {}),
539
+ ...(bound.error ? { error: bound.error } : {}),
540
+ };
541
+ }
542
+ const ebMeetingGroupEnsure = {
543
+ name: 'meeting.eb.meeting.group.ensure',
544
+ resource: 'eb',
545
+ method: 'meeting.eb.meeting.group.ensure',
546
+ action: 'meeting.eb.meeting.group.ensure',
547
+ summary: '建会同步建群聊:按会议名称/起止/参会人调建群动作流(群内发会议信息),反查群 id;'
548
+ + '补绑会议记录时按 requestId 反查(不按会议名称)',
549
+ capability: 'eb.meeting',
550
+ // 建群是写操作但不破坏数据(可解散重建),按「先确认会议要素再调」的口径标注
551
+ risk: 'read-before-write',
552
+ inputSchema: schemaOf({
553
+ mt_name: { type: 'string', description: '建会时的会议名称(=群名称)。补绑时可不传,自动读本机待绑记录' },
554
+ begin_datetime: { type: 'string', description: '会议开始时间(yyyy-MM-dd HH:mm)' },
555
+ end_datetime: { type: 'string', description: '会议结束时间(yyyy-MM-dd HH:mm)' },
556
+ hrm_members: { type: 'string', description: '参会人员员工 id,逗号分隔(动作流要求 id,不要传姓名)' },
557
+ address: { type: 'string', description: '会议室 id(可选)' },
558
+ meetingId: { type: 'string', description: '会议 id:传了就只做绑群' },
559
+ requestId: { type: 'string', description: '流程单据 id:按它反查会议(会议记录的 request_id),比名称可靠' },
560
+ groupId: { type: 'string', description: '已知群 id:传入则跳过建群,直接绑定' },
561
+ lookupPending: {
562
+ type: 'boolean',
563
+ description: '补绑模式:读本机「表单已发出」记录,按流程单反查会议再绑群(用户点提交后用这个)',
564
+ },
565
+ flowId: { type: 'string', description: `覆盖建群动作流 id(默认 ${DEFAULT_MEETING_GROUP_FLOW_ID})` },
566
+ employeeId: { type: 'string', description: '覆盖触发人员 id(默认当前登录用户)' },
567
+ }, {}),
568
+ handler: async (args, env) => {
569
+ const result = await ensureMeetingGroupChat(env, {
570
+ ...(args.mt_name ? { meetingName: String(args.mt_name).trim() } : {}),
571
+ ...(args.begin_datetime ? { beginDatetime: String(args.begin_datetime).trim() } : {}),
572
+ ...(args.end_datetime ? { endDatetime: String(args.end_datetime).trim() } : {}),
573
+ memberIds: String(args.hrm_members ?? '')
574
+ .split(/[,;,;\s]+/u)
575
+ .map((one) => one.trim())
576
+ .filter((one) => one !== ''),
577
+ ...(args.address ? { address: String(args.address).trim() } : {}),
578
+ ...(args.meetingId ? { meetingId: String(args.meetingId).trim() } : {}),
579
+ ...(args.requestId ? { requestId: String(args.requestId).trim() } : {}),
580
+ ...(args.groupId ? { groupId: String(args.groupId).trim() } : {}),
581
+ ...(args.lookupPending === true ? { lookupPending: true } : {}),
582
+ ...(args.flowId ? { flowId: String(args.flowId).trim() } : {}),
583
+ ...(args.employeeId ? { employeeId: String(args.employeeId).trim() } : {}),
584
+ });
585
+ if (!result.ok && !result.groupId) {
586
+ throw meetingValidationError('group_create_failed', result.error ?? '建群失败', { requestId: result.requestId ?? null, requestCandidates: result.requestCandidates ?? [] });
587
+ }
588
+ const warnings = [];
589
+ if (result.requestCandidates && result.requestCandidates.length > 0) {
590
+ warnings.push(`同一时间窗查到 ${result.requestCandidates.length} 张流程单,没法确定是哪一张:`
591
+ + result.requestCandidates.map((one) => `${one.flowName || '(无标题)'} ${one.requestId}`).join('、')
592
+ + '。⛔ 不要自己猜 —— 让用户确认是哪一张,再把它的 requestId 传进来。');
593
+ }
594
+ if (result.bound) {
595
+ warnings.push(`群已绑定到会议记录(imchat_groupid=${result.groupId}${result.readBack ? ',回读一致' : ''})。`);
596
+ }
597
+ else if (!result.meetingId) {
598
+ warnings.push(result.error
599
+ ? `群已建好(群 id ${result.groupId}),但还没绑上:${result.error}`
600
+ : '群已建好,但还没有会议 id:用户提交后带 lookupPending:true(或 requestId)再跑一次即可补绑。');
601
+ }
602
+ return meetingSuccess('meeting.eb.meeting.group.ensure', { ...result }, warnings.length > 0 ? { warnings } : {});
603
+ },
604
+ };
605
+ export const meetingEbGroupChatOperations = [ebMeetingGroupEnsure];
@@ -8,6 +8,7 @@ import { meetingValidationError } from '../../errors.js';
8
8
  import { meetingEbScheduleOperations } from './schedule.js';
9
9
  import { meetingEbCrmOperations } from './crm-search.js';
10
10
  import { meetingEbCreateFlowOperations } from './create-flow.js';
11
+ import { meetingEbGroupChatOperations } from './group-chat.js';
11
12
  /**
12
13
  * 会议 EB 版(eBuilder)operations。
13
14
  *
@@ -170,5 +171,6 @@ export const meetingEbOperations = [
170
171
  ...meetingEbScheduleOperations,
171
172
  ...meetingEbCrmOperations,
172
173
  ...meetingEbCreateFlowOperations,
174
+ ...meetingEbGroupChatOperations,
173
175
  ebRoomRecommendOperation,
174
176
  ];