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,96 @@
1
+ import { readCache, writeCache, cacheScopeSuffix } from './cache.js';
2
+ const PERSON_TTL_MS = 24 * 60 * 60 * 1000;
3
+ const BATCH_MAX = 100;
4
+ function personKey(scope) {
5
+ return `person-v1-${scope}`;
6
+ }
7
+ /** 读缓存里的全部人员(id → 记录),过期的条目会被丢弃。 */
8
+ async function readAll(env) {
9
+ const scope = await cacheScopeSuffix(env);
10
+ const hit = await readCache(env, personKey(scope), { ttlMs: PERSON_TTL_MS * 30 });
11
+ if (!hit)
12
+ return {};
13
+ const now = Date.now();
14
+ const fresh = {};
15
+ for (const [id, record] of Object.entries(hit.data ?? {})) {
16
+ if (record && now - Number(record.savedAt || 0) <= PERSON_TTL_MS)
17
+ fresh[id] = record;
18
+ }
19
+ return fresh;
20
+ }
21
+ async function writeAll(env, all) {
22
+ const scope = await cacheScopeSuffix(env);
23
+ await writeCache(env, personKey(scope), all);
24
+ }
25
+ /**
26
+ * 批量取人员信息(缓存优先,未命中的才打 `hrm.employee.batch`)。
27
+ * 返回 id → 记录;查不到的人不会出现在结果里。
28
+ */
29
+ export async function resolvePersonsCached(env, ids, options = {}) {
30
+ const wanted = [...new Set(ids.map((id) => String(id ?? '').trim()).filter((id) => id !== ''))];
31
+ const persons = new Map();
32
+ if (wanted.length === 0)
33
+ return { persons, requests: 0, fromCache: 0 };
34
+ const all = options.refresh === true ? {} : await readAll(env);
35
+ const missing = [];
36
+ let fromCache = 0;
37
+ for (const id of wanted) {
38
+ const hit = all[id];
39
+ if (hit) {
40
+ persons.set(id, hit);
41
+ fromCache += 1;
42
+ }
43
+ else
44
+ missing.push(id);
45
+ }
46
+ if (missing.length === 0)
47
+ return { persons, requests: 0, fromCache };
48
+ let requests = 0;
49
+ for (let index = 0; index < missing.length; index += BATCH_MAX) {
50
+ const chunk = missing.slice(index, index + BATCH_MAX);
51
+ try {
52
+ const { hrmEmployeeOperations } = await import('../../../hrm/operations/employee.js');
53
+ const batch = hrmEmployeeOperations.find((operation) => operation.name === 'hrm.employee.batch');
54
+ if (!batch)
55
+ break;
56
+ const result = await batch.handler({ ids: chunk }, env);
57
+ requests += 1;
58
+ const rows = (result?.data?.rows ?? result?.data?.userList ?? []);
59
+ for (const row of rows) {
60
+ const id = String(row?.id ?? '').trim();
61
+ if (!id)
62
+ continue;
63
+ const record = {
64
+ id,
65
+ name: String(row?.name ?? '').trim(),
66
+ ...(row?.department ? { department: String(row.department) } : {}),
67
+ ...(row?.departmentId ? { departmentId: String(row.departmentId) } : {}),
68
+ ...(row?.subCompany ? { subCompany: String(row.subCompany) } : {}),
69
+ ...(row?.position ? { position: String(row.position) } : {}),
70
+ ...(row?.mobile ? { mobile: String(row.mobile) } : {}),
71
+ ...(row?.jobNum ? { jobNum: String(row.jobNum) } : {}),
72
+ ...(row?.imCid ? { imCid: String(row.imCid) } : {}),
73
+ savedAt: Date.now(),
74
+ };
75
+ persons.set(id, record);
76
+ all[id] = record;
77
+ }
78
+ }
79
+ catch {
80
+ // 查失败不影响业务:调用方拿到的是缺项,自行降级
81
+ }
82
+ }
83
+ if (requests > 0)
84
+ await writeAll(env, all);
85
+ return { persons, requests, fromCache };
86
+ }
87
+ /** 兼容老用法:只要 id → 姓名。 */
88
+ export async function resolveNamesCached(env, ids) {
89
+ const { persons } = await resolvePersonsCached(env, ids);
90
+ const map = new Map();
91
+ for (const [id, person] of persons) {
92
+ if (person.name)
93
+ map.set(id, person.name);
94
+ }
95
+ return map;
96
+ }
@@ -1,6 +1,7 @@
1
1
  import { meetingSuccess, schemaOf, SCHEMA_DATETIME_MINUTE } from '../types.js';
2
2
  import { EB_BASE, ebBusinessSuccess, extractEbRows, queryBody } from './factory.js';
3
3
  import { readCache, writeCache, cacheScopeSuffix } from './cache.js';
4
+ import { busyRoomIds } from './usage-cache.js';
4
5
  // 会议室台账缓存 24h(用户 2026-10-30 明确要求:不要为了新鲜度把 TTL 缩到 15 分钟,太频繁)。
5
6
  // 「刚导入/新建的会议室看不到」靠两层兜底解决,而不是靠缩短 TTL:
6
7
  // ① **建会链路强制拉最新**(create.flow.prepare / quick / create.suggest 传 refreshRooms: true)——
@@ -65,11 +66,15 @@ function normalizeRoom(row) {
65
66
  place: toText(main.place).trim(),
66
67
  equipments: toText(main.equipments).trim(),
67
68
  mrType: toText(main.mr_type).trim(),
69
+ // ⛔ 2026-11-04 真机发现:以前没映射 room_desc,`roomDesc` 永远是 undefined,
70
+ // 于是「带客户优先接待类」里**按描述文案判定**那一半从来没生效过(只有名称含「接待」的能命中)。
71
+ // 例:广州会议室四 的描述「用来接待重要来宾…」明明写着,却被当普通房间。
72
+ roomDesc: toText(main.room_desc).trim(),
68
73
  };
69
74
  }
70
75
  /** 全量会议室:优先缓存(24h),`refresh:true` 强制重拉。 */
71
76
  export async function loadRooms(env, options = {}) {
72
- const key = `rooms-${await cacheScopeSuffix(env)}`;
77
+ const key = `rooms-v3-${await cacheScopeSuffix(env)}`;
73
78
  const warnings = [];
74
79
  if (!options.refresh) {
75
80
  const cached = await readCache(env, key, { ttlMs: ROOMS_TTL_MS });
@@ -123,10 +128,19 @@ export async function loadFrequentRooms(env, options = {}) {
123
128
  try {
124
129
  const { getMeetingOperation } = await import('../registry.js');
125
130
  const listOp = getMeetingOperation('meeting.eb.meeting.list');
126
- for (let pageNo = 1; pageNo <= 5; pageNo += 1) {
127
- const page = await listOp.handler({ creator: userId, begindatetime: sinceText, pageNo, pageSize: PAGE_SIZE }, env);
131
+ // ⛔ 2026-11-04 实测:会议列表接口**每页固定 10 条**(pageSize=200 也无效),
132
+ // 以前只扫 5 页 = 最近 50 场,而用户 180 天有 130+ 场 —— 统计被截断,
133
+ // 结果全被最近的测试会议带偏(「测试会议室1」排第一就是这么来的)。
134
+ // 现在按返回的 total 翻到底(封顶 40 页 / 400 场,避免异常数据打爆)。
135
+ let seen = 0;
136
+ let total = 0;
137
+ for (let pageNo = 1; pageNo <= 40; pageNo += 1) {
138
+ const page = await listOp.handler({ creator: userId, begindatetime: sinceText, pageNo, pageSize: 200 }, env);
128
139
  requests += 1;
129
140
  const rows = (page?.data?.rows ?? []);
141
+ if (rows.length === 0)
142
+ break;
143
+ total = Number(page?.data?.total ?? total) || total;
130
144
  for (const row of rows) {
131
145
  const main = row?.mainTable ?? row ?? {};
132
146
  const name = toText(main.address).trim();
@@ -134,7 +148,8 @@ export async function loadFrequentRooms(env, options = {}) {
134
148
  continue;
135
149
  counts.set(name, (counts.get(name) ?? 0) + 1);
136
150
  }
137
- if (rows.length < PAGE_SIZE)
151
+ seen += rows.length;
152
+ if (total > 0 && seen >= total)
138
153
  break;
139
154
  }
140
155
  }
@@ -190,13 +205,19 @@ export async function recommendRooms(env, input) {
190
205
  }
191
206
  const frequent = await loadFrequentRooms(env, { refresh: input.refreshFrequent === true });
192
207
  warnings.push(...frequent.warnings);
193
- const busy = await fetchBusyRoomIds(env, input.beginDatetime, input.endDatetime);
208
+ const busy = input.dayUsages
209
+ ? { ids: busyRoomIds(input.dayUsages, input.beginDatetime, input.endDatetime), requests: 0 }
210
+ : await fetchBusyRoomIds(env, input.beginDatetime, input.endDatetime);
194
211
  const frequentRank = new Map();
195
212
  frequent.order.forEach((item, index) => {
196
213
  frequentRank.set(item.name, { rank: index, count: item.count });
197
214
  });
198
215
  const memberCount = input.memberCount && input.memberCount > 0 ? input.memberCount : null;
199
216
  const requirement = input.requirement ?? {};
217
+ // 兼容顶层 hasCustomer(建会链路以前就是传顶层,打分却只读 requirement.hasCustomer,
218
+ // 导致「带客户优先接待类」从来没生效过 —— 2026-11-04 用户实测 3 个候选全是普通会议室)。
219
+ if (input.hasCustomer === true)
220
+ requirement.hasCustomer = true;
200
221
  const usable = roomData.rooms.filter((room) => room.status === '0');
201
222
  let busyCount = 0;
202
223
  const candidates = [];
@@ -255,6 +276,19 @@ export async function recommendRooms(env, input) {
255
276
  reasons.push(`设备含「${requirement.equipmentsKeyword}」`);
256
277
  score += 10;
257
278
  }
279
+ // 带客户时优先接待类:会议室描述(room_desc)或名称里带「接待/招待/贵宾/商务」的加分。
280
+ // ⛔ 不要把「客户」单独当关键词:`e-message(建议客户培训)` 这种培训室会被误判成接待类,
281
+ // 直接加 35 分排到第一(2026-11-04 真机实测)。真正的接待房名称/描述里一定带「接待」。
282
+ if (requirement.hasCustomer === true) {
283
+ const desc = String(room.roomDesc ?? '').trim();
284
+ const nameHit = room.name.match(/(接待|招待|贵宾|商务|vip)/iu);
285
+ const descHit = desc.match(/(接待|招待|贵宾|商务|vip)/iu);
286
+ const hostHit = nameHit ?? descHit;
287
+ if (hostHit) {
288
+ reasons.push(`带客户优先:${descHit ? `描述含「${desc.slice(0, 30)}」` : `名称含「${hostHit[1]}」`}`);
289
+ score += 35;
290
+ }
291
+ }
258
292
  if (isBusy) {
259
293
  reasons.push('该时段已被占用(按 includeBusy 一并返回)');
260
294
  score -= 100;
@@ -269,6 +303,7 @@ export async function recommendRooms(env, input) {
269
303
  place: room.place,
270
304
  equipments: room.equipments,
271
305
  roomAttrText: room.roomAttrText,
306
+ ...(String(room.roomDesc ?? '').trim() !== '' ? { roomDesc: String(room.roomDesc).trim() } : {}),
272
307
  frequentCount: freq?.count ?? 0,
273
308
  score,
274
309
  reasons,
@@ -354,6 +389,7 @@ export const ebRoomRecommendOperation = {
354
389
  mrType: { type: 'string', description: '会议室类型 id(mr_type)' },
355
390
  roomAttr: { type: 'string', enum: ['0', '1', '2'], description: '会议室属性:0 实体 / 1 虚拟 / 2 组合' },
356
391
  equipmentsKeyword: { type: 'string', description: '设备关键词(模糊匹配 equipments,如 投影/视频)' },
392
+ hasCustomer: { type: 'boolean', description: '本次会议是否带客户:true 时优先推荐接待类会议室(描述/名称含 接待/招待/客户/商务/贵宾 的加分)' },
357
393
  topN: { type: 'number', description: '返回候选数量(默认 3,最多 10)' },
358
394
  refresh: { type: 'boolean', description: 'true=刷新会议室全量与常用记录缓存' },
359
395
  includeBusy: { type: 'boolean', description: 'true=把该时段已被占用的会议室也返回(排在最后,带说明)' },
@@ -374,6 +410,8 @@ export const ebRoomRecommendOperation = {
374
410
  ...(args.mrType ? { mrType: String(args.mrType) } : {}),
375
411
  ...(args.roomAttr ? { roomAttr: String(args.roomAttr) } : {}),
376
412
  ...(args.equipmentsKeyword ? { equipmentsKeyword: String(args.equipmentsKeyword) } : {}),
413
+ // 2026-11-04 修:以前漏了它 —— 用户传了 hasCustomer,打分却读 requirement.hasCustomer,永远 false
414
+ ...(args.hasCustomer === true ? { hasCustomer: true } : {}),
377
415
  },
378
416
  ...(args.topN !== undefined ? { topN: Number(args.topN) } : {}),
379
417
  ...(args.refresh === true ? { refreshRooms: true, refreshFrequent: true } : {}),
@@ -401,7 +439,7 @@ export async function rememberFrequentRoom(env, roomId) {
401
439
  if (!id)
402
440
  return false;
403
441
  const scope = await cacheScopeSuffix(env);
404
- const roomsCached = await readCache(env, `rooms-${scope}`, { ttlMs: ROOMS_TTL_MS });
442
+ const roomsCached = await readCache(env, `rooms-v3-${scope}`, { ttlMs: ROOMS_TTL_MS });
405
443
  const name = roomsCached?.data?.find((room) => room.id === id)?.name ?? '';
406
444
  if (!name)
407
445
  return false;
@@ -64,6 +64,8 @@ export function summarizeRoomRows(rows) {
64
64
  allowMaxNum: toText(main.allow_maxnum),
65
65
  place: toText(main.place),
66
66
  equipments: toText(main.equipments),
67
+ // 会议室描述:列表接口本来就返回 `room_desc`(2026-11-02 核实),之前漏映射导致推荐时用不上
68
+ roomDesc: toText(main.room_desc),
67
69
  };
68
70
  });
69
71
  return {
@@ -0,0 +1,99 @@
1
+ import { EB_BASE, ebBusinessSuccess, extractEbRows, queryBody } from './factory.js';
2
+ import { readCache, writeCache, cacheScopeSuffix } from './cache.js';
3
+ /** 占用缓存 TTL:太长会放行别人刚订的房间,太短失去缓存意义 —— 5 分钟 + 提交即作废。 */
4
+ const USAGE_TTL_MS = 5 * 60 * 1000;
5
+ const MAX_PAGES = 10;
6
+ const PAGE_SIZE = 200;
7
+ /** 取 'yyyy-MM-dd HH:mm' 的日期部分。 */
8
+ export function dayOf(datetime) {
9
+ return String(datetime ?? '').trim().slice(0, 10);
10
+ }
11
+ /**
12
+ * 时间重叠判定:占用开始 < 我的结束 且 占用结束 > 我的开始。
13
+ * 字符串直接比较成立的前提是 'yyyy-MM-dd HH:mm' 定宽格式(EB 出参即如此)。
14
+ */
15
+ export function usageOverlaps(row, begin, end) {
16
+ return row.start < end && row.end > begin;
17
+ }
18
+ /** 从全天占用里筛出「某会议室在某时段」的占用记录(可排除自身会议)。 */
19
+ export function filterUsages(rows, options) {
20
+ return rows.filter((row) => {
21
+ if (options.roomId && row.roomId !== options.roomId)
22
+ return false;
23
+ if (options.excludeMtId && row.mtId && row.mtId === options.excludeMtId)
24
+ return false;
25
+ return usageOverlaps(row, options.begin, options.end);
26
+ });
27
+ }
28
+ /** 从全天占用算出「某时段被占用的会议室 id 集合」(替代按时段的 query_roomusage_list)。 */
29
+ export function busyRoomIds(rows, begin, end) {
30
+ const ids = new Set();
31
+ for (const row of rows) {
32
+ if (row.roomId && usageOverlaps(row, begin, end))
33
+ ids.add(row.roomId);
34
+ }
35
+ return ids;
36
+ }
37
+ function usageKey(scope, day) {
38
+ return `usage-v1-${scope}-${day}`;
39
+ }
40
+ /**
41
+ * 加载某天的全部会议室占用(缓存优先)。
42
+ * 返回 rows 供本地过滤;fromCache=true 表示没打网络(调用方可用于提示/计时)。
43
+ */
44
+ export async function loadDayUsages(env, day, options = {}) {
45
+ const scope = await cacheScopeSuffix(env);
46
+ const key = usageKey(scope, day);
47
+ if (options.refresh !== true) {
48
+ const cached = await readCache(env, key, { ttlMs: USAGE_TTL_MS });
49
+ if (cached) {
50
+ return { rows: cached.data, fromCache: true, ageMs: cached.ageMs, requests: 0 };
51
+ }
52
+ }
53
+ const session = await env.host.getSession();
54
+ const rows = [];
55
+ let requests = 0;
56
+ for (let pageNo = 1; pageNo <= MAX_PAGES; pageNo += 1) {
57
+ const response = await env.host.request({
58
+ method: 'POST',
59
+ path: `${EB_BASE}/getFormDataList/query_roomusage_list`,
60
+ operation: 'meeting.eb.roomUsage.list',
61
+ // 与 fetchBusyRoomIds 同一套算子语义:enddatetime lt 当天末尾、begindatetime gt 当天开头
62
+ query: { enddatetime: `${day} 23:59`, begindatetime: `${day} 00:00` },
63
+ body: queryBody(session.userId, { pageNo, pageSize: PAGE_SIZE }),
64
+ acceptBusiness: ebBusinessSuccess,
65
+ });
66
+ requests += 1;
67
+ const { rows: pageRows } = extractEbRows(response);
68
+ for (const row of pageRows) {
69
+ const main = row?.mainTable ?? row ?? {};
70
+ const roomId = String(main.address ?? main.room_id ?? '').trim();
71
+ const start = String(main.startdate ?? '').trim();
72
+ const end = String(main.enddate ?? '').trim();
73
+ if (roomId && start && end) {
74
+ rows.push({ roomId, mtId: String(main.mt_id ?? '').trim(), start, end });
75
+ }
76
+ }
77
+ if (pageRows.length < PAGE_SIZE)
78
+ break;
79
+ }
80
+ await writeCache(env, key, rows);
81
+ return { rows, fromCache: false, ageMs: 0, requests };
82
+ }
83
+ /** 作废某天的占用缓存(提交成功后必须调,否则 5 分钟内新订的房间会被旧台账掩盖)。 */
84
+ export async function invalidateDayUsages(env, day) {
85
+ const scope = await cacheScopeSuffix(env);
86
+ await clearCache(env, usageKey(scope, day));
87
+ }
88
+ /** 直接删缓存文件(写空记录会把"刚被订满"伪装成"全天空闲",必须真删)。 */
89
+ async function clearCache(env, key) {
90
+ try {
91
+ const { rm } = await import('node:fs/promises');
92
+ const { join } = await import('node:path');
93
+ const { meetingStateDir } = await import('../env.js');
94
+ await rm(join(meetingStateDir(env.ctx), 'cache', `${key}.json`), { force: true });
95
+ }
96
+ catch {
97
+ // 删失败不影响业务:TTL 5 分钟兜底
98
+ }
99
+ }
@@ -23,9 +23,24 @@
23
23
  */
24
24
  import { meetingValidationError } from '../errors.js';
25
25
  import { meetingSuccess } from './types.js';
26
- /** ESB 动作流 id 的**内置默认值**(参考租户配置)。换租户请用入参 `flowGenerateMinutes` / `flowGenerateDocument` 覆盖。 */
27
- const FLOW_GENERATE_MINUTES = '1325287772302863633';
28
- const FLOW_GENERATE_DOCUMENT = '1325287772302863628';
26
+ /**
27
+ * ESB 动作流标识:本租户 EB 后台的 **primary_key**(32 位十六进制)。
28
+ *
29
+ * ⛔ 触发时必须走「唯一值触发」入参 `uniqueIndent`:
30
+ * `esbFlowId` 只认 19 位数字 id,传 GUID 服务端直接报
31
+ * `can not cast to long, value : 7204…`(查模板)或 `{"code":400,"msg":"系统错误"}`(触发)。
32
+ * 2026-11-03 实测:同一组 id 用 `uniqueIndent` 触发,`resultCode=200 执行成功`。
33
+ *
34
+ * 换租户请用入参 `flowGenerateMinutes` / `flowGenerateDocument` 覆盖。
35
+ */
36
+ const FLOW_GENERATE_MINUTES = '7204936B2ACC4B12A1CECD1DAB60DD97';
37
+ const FLOW_GENERATE_DOCUMENT = '1F638AE31BC746C28A1D9DCF26D91037';
38
+ /** ESB「解析纪要文档/音频附件」(primary_key)。附件解析入口,当前由后台解析任务触发,此处仅登记备查。 */
39
+ const FLOW_PARSE_ATTACHMENT = 'AD8EE1B345FD4570AE1D0882FD1B5927';
40
+ /** ESB「获取解析纪要音频结果」(primary_key)。音视频附件提取完成后由调用方按需触发。 */
41
+ const FLOW_AUDIO_RESULT = '1DB38C10699A47E89DD7CEFDAA76B8A1';
42
+ /** 智能纪要页面 id(租户级,没有接口能查;反查不到时用它兜底)。 */
43
+ const DEFAULT_AI_MINUTE_PAGE = '1250830691712172036';
29
44
  function textOf(value) {
30
45
  return value === undefined || value === null ? '' : String(value);
31
46
  }
@@ -285,14 +300,14 @@ const meetingMinuteFromGroupChat = {
285
300
  },
286
301
  flowGenerateMinutes: {
287
302
  type: 'string',
288
- pattern: '^\\d+$',
289
- description: 'ESB「根据解析文本生成智能纪要文本」流程 id。省略时用内置默认值;**换租户必须覆盖**'
290
- + '(流程 id 是租户级配置,各环境不同)',
303
+ pattern: '^(\\d+|[0-9A-Fa-f]{32})$',
304
+ description: 'ESB「根据解析文本生成智能纪要文本」的 id。可传 19 位数字 id 或 32 位十六进制 primary_key'
305
+ + '(GUID 会自动走唯一值触发 uniqueIndent)。省略时用内置默认值;换租户必须覆盖',
291
306
  },
292
307
  flowGenerateDocument: {
293
308
  type: 'string',
294
- pattern: '^\\d+$',
295
- description: 'ESB「保存解析并生成文档」流程 id。省略时用内置默认值;换租户必须覆盖',
309
+ pattern: '^(\\d+|[0-9A-Fa-f]{32})$',
310
+ description: 'ESB「保存解析并生成文档」的 id,支持数字 id 或 32 位 primary_key;省略时用内置默认值',
296
311
  },
297
312
  },
298
313
  required: ['mt_id'],
@@ -472,7 +487,8 @@ const meetingMinuteFromGroupChat = {
472
487
  // 反查失败不影响主流程
473
488
  }
474
489
  if (!aiPageId) {
475
- warnings.push('未取到 mt_minutes_ai_page(可传 aiPageId);ESB 解析该字段可能必填,缺它会失败');
490
+ aiPageId = DEFAULT_AI_MINUTE_PAGE;
491
+ warnings.push(`未从已有纪要取到 mt_minutes_ai_page,已用本租户默认值 ${DEFAULT_AI_MINUTE_PAGE}`);
476
492
  }
477
493
  }
478
494
  // ⑥ 建纪要并写原文
@@ -512,6 +528,7 @@ const meetingMinuteFromGroupChat = {
512
528
  pipeline,
513
529
  aiPageId: aiPageId || null,
514
530
  };
531
+ let documentIdFromFlow = '';
515
532
  if (pipeline === 'text') {
516
533
  result.timings = { ...timings, total: Date.now() - startedAt };
517
534
  return meetingSuccess('meeting.minute.fromGroupChat', result, warnings.length > 0 ? { warnings } : undefined);
@@ -534,7 +551,11 @@ const meetingMinuteFromGroupChat = {
534
551
  const prepareOp = getEsbOperation('esb.trigger.prepare');
535
552
  const applyOp = getEsbOperation('esb.trigger.apply');
536
553
  const trigger = async (flowId, mainTable) => {
537
- const pre = await prepareOp.handler({ employeeId, esbFlowId: flowId, customParams: { mainTable } }, esbEnv);
554
+ // 数字 id → 普通触发 esbFlowId;32 位十六进制 primary_key → 唯一值触发 uniqueIndent
555
+ const target = /^\d+$/.test(flowId)
556
+ ? { esbFlowId: flowId }
557
+ : { uniqueIndent: flowId };
558
+ const pre = await prepareOp.handler({ employeeId, ...target, customParams: { mainTable } }, esbEnv);
538
559
  return applyOp.handler({ continuation: pre?.data?.continuation, confirm: true }, esbEnv);
539
560
  };
540
561
  try {
@@ -554,7 +575,9 @@ const meetingMinuteFromGroupChat = {
554
575
  }
555
576
  else {
556
577
  try {
557
- await trigger(flowDocument, { parse_content: parseContent, mt_minute_id: minuteId });
578
+ const docResult = await trigger(flowDocument, { parse_content: parseContent, mt_minute_id: minuteId });
579
+ // 628 的返回里带 `customData.mainTable.wdID`(文档 id);回读为空时用它兜底
580
+ documentIdFromFlow = textOf(docResult?.data?.actionData?.responseData?.customData?.mainTable?.wdID);
558
581
  mark('esbDocument', stepDocStart);
559
582
  }
560
583
  catch (error) {
@@ -568,7 +591,7 @@ const meetingMinuteFromGroupChat = {
568
591
  const row = final?.data?.data ?? {};
569
592
  result.finalStatus = textOf(row.minutes_status);
570
593
  result.parseContentLength = textOf(row.parse_content).length;
571
- result.documentId = textOf(row.mt_minutes) || null;
594
+ result.documentId = textOf(row.mt_minutes) || documentIdFromFlow || null;
572
595
  result.timings = { ...timings, total: Date.now() - startedAt };
573
596
  return meetingSuccess('meeting.minute.fromGroupChat', result, warnings.length > 0 ? { warnings } : undefined);
574
597
  },
@@ -1,15 +1,9 @@
1
1
  import { assertDatajsonSuccess, assertWeaverSuccess, } from '../host.js';
2
2
  import { projectValidationError } from '../errors.js';
3
3
  import { SCHEMA_FILTERS, SCHEMA_FIELDS, SCHEMA_ID_STRING, SCHEMA_PAGE_NO, SCHEMA_PAGE_SIZE, projectSuccess, schemaOf, } from './types.js';
4
- import { compactFilters, idString, operatorId, positiveInt, queryText, } from './shared.js';
4
+ import { buildCardUrl, compactFilters, idString, operatorId, positiveInt, queryText, resolveObjectId, } from './shared.js';
5
5
  const APP_TAG = 'weaver-project-eb';
6
- const DEFAULT_OBJ_IDS = { task: '1020270486210527232', project: '1020275562853482532' };
7
6
  const MIN_VERSION = [20, 6, 11];
8
- function buildCardUrl(baseUrl, objId, dataId) {
9
- if (!baseUrl || !objId || !dataId || /[\s/\\?#]/u.test(dataId))
10
- return null;
11
- return `${baseUrl.replace(/\/+$/u, '')}/sp/ebdfpage/card/0/${objId}/${dataId}`;
12
- }
13
7
  const CURRENT_USER_FILTERS = new Set([
14
8
  'manager',
15
9
  'participant',
@@ -114,7 +108,7 @@ async function decorateDetailUrls(host, operation, datajson) {
114
108
  if (targets.length === 0)
115
109
  return datajson;
116
110
  try {
117
- const objId = await getObject(host, operationTable(operation));
111
+ const objId = await resolveObjectId(host, operationTable(operation));
118
112
  const session = await host.getSession();
119
113
  for (const row of targets) {
120
114
  const url = buildCardUrl(session.baseUrl, objId, String(row.id));
@@ -161,19 +155,6 @@ async function getAppId(host) {
161
155
  throw new Error('当前环境未安装事井然项目管理应用');
162
156
  return appId;
163
157
  }
164
- async function getObject(host, value) {
165
- const response = await host.request({
166
- method: 'GET',
167
- path: '/api/ebproject/backendConsole/getCurrentObjId',
168
- query: { objId: DEFAULT_OBJ_IDS[value] },
169
- operation: 'project.object.id',
170
- });
171
- assertWeaverSuccess(response, 'project.object.id');
172
- const object = String(response?.data ?? '');
173
- if (!object)
174
- throw new Error('接口未返回当前环境对象 ID');
175
- return object;
176
- }
177
158
  async function datajsonRequest(host, operation, path, payload, query) {
178
159
  const response = await host.request({
179
160
  method: 'POST',
@@ -383,7 +364,7 @@ export const projectReadOperations = [
383
364
  inputSchema: schemaOf({ table: { type: 'string', enum: ['task', 'project'] } }, { required: ['table'] }),
384
365
  handler: async (args, { host }) => {
385
366
  const value = table(args.table);
386
- return projectSuccess('project.object.id', { table: value, objId: await getObject(host, value) });
367
+ return projectSuccess('project.object.id', { table: value, objId: await resolveObjectId(host, value) });
387
368
  },
388
369
  },
389
370
  {
@@ -417,7 +398,7 @@ export const projectReadOperations = [
417
398
  handler: async (args, { host }) => {
418
399
  const value = table(args.table);
419
400
  const groupId = args.groupId ? String(args.groupId) : await getAppId(host);
420
- const sourceId = args.sourceId ? String(args.sourceId) : await getObject(host, value);
401
+ const sourceId = args.sourceId ? String(args.sourceId) : await resolveObjectId(host, value);
421
402
  const response = await host.request({
422
403
  method: 'POST',
423
404
  path: '/api/ebuilder/common/ds/fields',
@@ -456,7 +437,7 @@ export const projectReadOperations = [
456
437
  handler: async (args, { host }) => {
457
438
  const value = table(args.table);
458
439
  const groupId = args.groupId ? String(args.groupId) : await getAppId(host);
459
- const sourceId = args.sourceId ? String(args.sourceId) : await getObject(host, value);
440
+ const sourceId = args.sourceId ? String(args.sourceId) : await resolveObjectId(host, value);
460
441
  const response = await host.request({
461
442
  method: 'GET',
462
443
  path: '/api/ebuilder/common/ds/options',
@@ -549,4 +530,4 @@ export const projectReadOperations = [
549
530
  hrmOperation('project.department.resolve', '/api/hrm/common/browser/data/department', 'organization', '按名称精确或模糊解析部门 ID'),
550
531
  hrmOperation('project.subcompany.resolve', '/api/hrm/common/browser/data/subcompany', 'organization', '按名称精确或模糊解析分部 ID'),
551
532
  ];
552
- export { getAppId, getObject, SCHEMA_FIELDS };
533
+ export { getAppId, resolveObjectId as getObject, SCHEMA_FIELDS };
@@ -1,4 +1,5 @@
1
1
  import { projectValidationError } from '../errors.js';
2
+ import { assertWeaverSuccess } from '../host.js';
2
3
  export function asObject(value, field = 'input') {
3
4
  if (!value || typeof value !== 'object' || Array.isArray(value)) {
4
5
  throw projectValidationError('field_invalid', `${field} 必须是 JSON 对象`);
@@ -66,3 +67,24 @@ export function stableFingerprint(value) {
66
67
  }
67
68
  return `${text.length}:${hash.toString(16)}`;
68
69
  }
70
+ export const DEFAULT_OBJ_IDS = { task: '1020270486210527232', project: '1020275562853482532' };
71
+ /** 详情卡片地址:`{baseUrl}/sp/ebdfpage/card/0/{objId}/{dataId}`。参数不全或 dataId 含分隔符时返回 null。 */
72
+ export function buildCardUrl(baseUrl, objId, dataId) {
73
+ if (!baseUrl || !objId || !dataId || /[\s/\\?#]/u.test(dataId))
74
+ return null;
75
+ return `${baseUrl.replace(/\/+$/u, '')}/sp/ebdfpage/card/0/${objId}/${dataId}`;
76
+ }
77
+ /** 解析当前环境下的表单对象 ID(列表/详情/新建共用)。 */
78
+ export async function resolveObjectId(host, value) {
79
+ const response = await host.request({
80
+ method: 'GET',
81
+ path: '/api/ebproject/backendConsole/getCurrentObjId',
82
+ query: { objId: DEFAULT_OBJ_IDS[value] },
83
+ operation: 'project.object.id',
84
+ });
85
+ assertWeaverSuccess(response, 'project.object.id');
86
+ const object = String(response?.data ?? '');
87
+ if (!object)
88
+ throw new Error('接口未返回当前环境对象 ID');
89
+ return object;
90
+ }
@@ -1,7 +1,7 @@
1
1
  import { projectPartialError, projectValidationError } from '../errors.js';
2
2
  import { assertDatajsonSuccess } from '../host.js';
3
3
  import { SCHEMA_CONFIRM, SCHEMA_CONTINUATION, SCHEMA_FIELDS, SCHEMA_ID_STRING, SCHEMA_RECORDS, projectSuccess, schemaOf, } from './types.js';
4
- import { asObject, idString, operatorId, stableFingerprint } from './shared.js';
4
+ import { asObject, buildCardUrl, idString, operatorId, resolveObjectId, stableFingerprint } from './shared.js';
5
5
  const WRITE_TARGETS = {
6
6
  taskCreate: { entity: 'task', action: 'create', listPath: '', detailPath: '/api/ebuilder/form/formdata/v2/getFormDataByPk/queryTaskDetail', writePath: '/api/ebuilder/form/formdata/v2/saveFormData/createTask' },
7
7
  taskUpdate: { entity: 'task', action: 'update', listPath: '', detailPath: '/api/ebuilder/form/formdata/v2/getFormDataByPk/queryTaskDetail', writePath: '/api/ebuilder/form/formdata/v2/updateFormData/updateTask' },
@@ -62,6 +62,31 @@ async function sendWrite(host, operation, path, datajsonObject) {
62
62
  assertDatajsonSuccess(response, operation);
63
63
  return response?.datajson ?? null;
64
64
  }
65
+ /**
66
+ * 新建成功后,把返回的 dataIds 映射成详情页地址,供展示层把记录名称渲染为可点击链接。
67
+ * 链接解析失败不影响创建结果(写入已成功),只附带 detail_url_warning。
68
+ */
69
+ async function decorateCreatedRecords(host, entity, data) {
70
+ if (!data || typeof data !== 'object')
71
+ return data;
72
+ const value = data;
73
+ const ids = Array.isArray(value.dataIds)
74
+ ? value.dataIds.map((item) => String(item)).filter((item) => item !== '')
75
+ : [];
76
+ if (ids.length === 0)
77
+ return data;
78
+ try {
79
+ const [objId, session] = await Promise.all([resolveObjectId(host, entity), host.getSession()]);
80
+ const urls = ids.map((id) => buildCardUrl(session.baseUrl, objId, id));
81
+ return urls.some((url) => url !== null) ? { ...value, detail_urls: urls } : data;
82
+ }
83
+ catch (error) {
84
+ return {
85
+ ...value,
86
+ detail_url_warning: `详情链接暂不可用:${error instanceof Error ? error.message : String(error)};创建已成功,不要因此重试。`,
87
+ };
88
+ }
89
+ }
65
90
  async function verifySession(env, continuation) {
66
91
  if (continuation.userId !== await operatorId(env.host)) {
67
92
  throw projectValidationError('continuation_mismatch', 'continuation 与当前登录会话不匹配');
@@ -119,7 +144,8 @@ function createOperations(namePrefix, target) {
119
144
  await verifySession(env, continuation);
120
145
  const datajson = asObject(continuation.datajson, 'continuation.datajson');
121
146
  const response = await sendWrite(env.host, applyName, target.writePath, datajson);
122
- return projectSuccess(applyName, response, { targetType: target.entity, writeAction: 'create' });
147
+ const data = await decorateCreatedRecords(env.host, target.entity, response);
148
+ return projectSuccess(applyName, data, { targetType: target.entity, writeAction: 'create' });
123
149
  },
124
150
  },
125
151
  ];