weaver-work-cli 0.1.38 → 0.1.40

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 (166) hide show
  1. package/README.md +7 -3
  2. package/dist/chunks/{archive-PDV724YK.js → archive-JHTONRST.js} +556 -66
  3. package/dist/chunks/{archive-oa-4XE4MNXQ.js → archive-oa-FCHLCPHC.js} +2 -2
  4. package/dist/chunks/{asset-PDL2QE2N.js → asset-RLIHC7DB.js} +2 -2
  5. package/dist/chunks/asset-idle-KVOFTLCP.js +670 -0
  6. package/dist/chunks/{attachment-content-MDDGOE5E.js → attachment-content-RZYDB3MB.js} +3 -3
  7. package/dist/chunks/{blog-UYR3EUXW.js → blog-42OZTJNP.js} +2 -2
  8. package/dist/chunks/{caizhilian-LG2X5NMF.js → caizhilian-KRK547K5.js} +2 -2
  9. package/dist/chunks/{calendar-FG4BAZHE.js → calendar-RTWAMKHD.js} +2 -2
  10. package/dist/chunks/{chunk-J7USS3ED.js → chunk-4CTESSKI.js} +4 -4
  11. package/dist/chunks/{chunk-OJQUJZQF.js → chunk-4TEDNCON.js} +45 -5
  12. package/dist/chunks/chunk-57IECMWX.js +487 -0
  13. package/dist/chunks/{chunk-ELYDWBZJ.js → chunk-7BRU4EFH.js} +13 -7
  14. package/dist/chunks/{chunk-6LTFIFLM.js → chunk-7E6MUOEK.js} +2 -2
  15. package/dist/chunks/{chunk-KKRL3REV.js → chunk-7U577Q7U.js} +2 -2
  16. package/dist/chunks/{chunk-UJJD4QWF.js → chunk-CAQNBBQA.js} +2 -2
  17. package/dist/chunks/{chunk-CORFRD7Q.js → chunk-CXRQWOZ5.js} +2 -2
  18. package/dist/chunks/{chunk-GGJFMNJM.js → chunk-FLS4DPGP.js} +110 -16
  19. package/dist/chunks/{chunk-JSMFPO43.js → chunk-I566ZYNF.js} +1 -1
  20. package/dist/chunks/{chunk-6FTTSA45.js → chunk-IZSSVYAG.js} +2 -2
  21. package/dist/chunks/{chunk-XJY7G4DD.js → chunk-JWRZM2H2.js} +3 -3
  22. package/dist/chunks/{chunk-NI6GIPIM.js → chunk-LFMSDBZN.js} +529 -395
  23. package/dist/chunks/{chunk-VRYHGG6O.js → chunk-LN447PY2.js} +92 -51
  24. package/dist/chunks/{chunk-NR6RKXPA.js → chunk-NETCCARD.js} +4 -2
  25. package/dist/chunks/{chunk-Z32KWY6G.js → chunk-NJKREDA3.js} +1 -1
  26. package/dist/chunks/{chunk-FUWVHMVV.js → chunk-SPRHHSVC.js} +2 -2
  27. package/dist/chunks/{chunk-IZUDA353.js → chunk-T6EGVBKU.js} +1 -1
  28. package/dist/chunks/{chunk-DMV2TOMH.js → chunk-VNBU55SL.js} +428 -145
  29. package/dist/chunks/{chunk-UZZBA6PV.js → chunk-WFFNM65B.js} +47 -6
  30. package/dist/chunks/{chunk-EG6ZU444.js → chunk-ZUX3P6BQ.js} +10 -5
  31. package/dist/chunks/{conflict-BQZEQ4SP.js → conflict-VR55AOOO.js} +2 -2
  32. package/dist/chunks/{contract-XIRAFTEV.js → contract-HI4FNPYE.js} +2 -2
  33. package/dist/chunks/{cowork-BLG62BL2.js → cowork-HJLI353D.js} +12 -5
  34. package/dist/chunks/{ebuilder-form-NUKG5QW4.js → ebuilder-form-HTGI63UU.js} +3 -3
  35. package/dist/chunks/{ehr-2STW5ILH.js → ehr-GYXIYH3A.js} +2 -2
  36. package/dist/chunks/env-DW3G4HUO.js +19 -0
  37. package/dist/chunks/{esb-UUDQUNKY.js → esb-I5INCEFE.js} +4 -4
  38. package/dist/chunks/factory-TKXGXS6L.js +30 -0
  39. package/dist/chunks/{fna-6RCY4NU5.js → fna-K2MHTQ24.js} +2 -2
  40. package/dist/chunks/group-chat-VYBNMJLG.js +18 -0
  41. package/dist/chunks/{host-FWDOSRH5.js → host-256GVCAH.js} +3 -3
  42. package/dist/chunks/{host-EJZFROU3.js → host-D5SERMAJ.js} +3 -3
  43. package/dist/chunks/{host-RHM3NPXV.js → host-JM4YVHFM.js} +3 -3
  44. package/dist/chunks/{host-7RXEDMAH.js → host-L3VLITMT.js} +3 -3
  45. package/dist/chunks/{host-UFF33TWJ.js → host-ZUQTJYYK.js} +3 -3
  46. package/dist/chunks/{hrm-PKJ6ELSH.js → hrm-VLRBKTZB.js} +3 -3
  47. package/dist/chunks/{im-YJAXS7CO.js → im-QK3VTLXK.js} +8 -8
  48. package/dist/chunks/{invoice-HWVSXGX6.js → invoice-T5JTA32H.js} +3 -3
  49. package/dist/chunks/{jiuchuanhui-L7VKILZB.js → jiuchuanhui-P2FNEQVZ.js} +2 -2
  50. package/dist/chunks/{lazy-dispatch-NMYEIHRH.js → lazy-dispatch-XRRI46M6.js} +5 -5
  51. package/dist/chunks/{mail-B42RRU7G.js → mail-3Y7MJ6PS.js} +9 -4
  52. package/dist/chunks/{meeting-CFYVBJSS.js → meeting-M7LTXGJE.js} +11 -10
  53. package/dist/chunks/{okr-GCLF6V7W.js → okr-OBC6S67L.js} +2 -2
  54. package/dist/chunks/{plan-ZVL7GBRI.js → plan-5H6I53SK.js} +2 -2
  55. package/dist/chunks/{project-4YWAAE2K.js → project-L75QZV4S.js} +2 -2
  56. package/dist/chunks/{registry-EP4VTHZS.js → registry-KMHCPAAD.js} +9 -8
  57. package/dist/chunks/{registry-OZSMDVPP.js → registry-QGHFNUWS.js} +2 -2
  58. package/dist/chunks/{registry-FRJ6H7BT.js → registry-RXPN6BBT.js} +4 -4
  59. package/dist/chunks/{registry-SG2PGVCF.js → registry-UGRBCBHO.js} +3 -3
  60. package/dist/chunks/{room-recommend-R3JVUUAC.js → room-recommend-A3UDC3CA.js} +4 -4
  61. package/dist/chunks/{schedule-NIJBTYDO.js → schedule-5GCACCK7.js} +4 -4
  62. package/dist/chunks/{workflow-BB24DFBY.js → workflow-M5ZJ7DWF.js} +11 -8
  63. package/dist/core/output.js +16 -6
  64. package/dist/index.js +33 -28
  65. package/dist/internal/e10/auth/commands.js +109 -56
  66. package/dist/internal/e10/auth/messages.js +1 -0
  67. package/dist/internal/e10/auth/session.js +11 -1
  68. package/dist/internal/e10/auth/xiaoe.js +29 -4
  69. package/dist/internal/e10/auth-recovery.js +13 -6
  70. package/dist/internal/e10/client.js +13 -0
  71. package/dist/internal/e10/context.js +24 -3
  72. package/dist/internal/e10/request-runtime.js +12 -1
  73. package/dist/shortcuts/archive/host.js +2 -0
  74. package/dist/shortcuts/archive/manifest.js +1 -1
  75. package/dist/shortcuts/archive/operations/borrow-list.js +13 -1
  76. package/dist/shortcuts/archive/operations/flow-url.js +1 -1
  77. package/dist/shortcuts/archive/operations/prelib.js +472 -0
  78. package/dist/shortcuts/archive/operations/registry.js +2 -0
  79. package/dist/shortcuts/archive/operations/upload.js +1 -1
  80. package/dist/shortcuts/asset-idle/errors.js +44 -0
  81. package/dist/shortcuts/asset-idle/host.js +161 -0
  82. package/dist/shortcuts/asset-idle/index.js +114 -0
  83. package/dist/shortcuts/asset-idle/manifest.js +159 -0
  84. package/dist/shortcuts/asset-idle/operations/idle.js +47 -0
  85. package/dist/shortcuts/asset-idle/operations/registry.js +33 -0
  86. package/dist/shortcuts/asset-idle/operations/shared.js +142 -0
  87. package/dist/shortcuts/asset-idle/operations/types.js +9 -0
  88. package/dist/shortcuts/asset-idle/operations.js +15 -0
  89. package/dist/shortcuts/cowork/operations/item-write.js +10 -2
  90. package/dist/shortcuts/index.js +5 -0
  91. package/dist/shortcuts/mail/operations/mail-send.js +8 -2
  92. package/dist/shortcuts/meeting/operations/eb/catalog.js +5 -1
  93. package/dist/shortcuts/meeting/operations/eb/create-flow.js +381 -104
  94. package/dist/shortcuts/meeting/operations/eb/crm-search.js +87 -5
  95. package/dist/shortcuts/meeting/operations/eb/flow-memory.js +49 -0
  96. package/dist/shortcuts/meeting/operations/eb/group-chat.js +583 -0
  97. package/dist/shortcuts/meeting/operations/eb/index.js +2 -0
  98. package/dist/shortcuts/meeting/operations/eb/room-recommend.js +19 -3
  99. package/dist/shortcuts/meeting/operations/eb/room-summary.js +2 -0
  100. package/dist/shortcuts/meeting/operations/eb/usage-cache.js +99 -0
  101. package/dist/shortcuts/meeting/operations/from-group-chat.js +35 -12
  102. package/dist/shortcuts/workflow/host.js +3 -0
  103. package/dist/shortcuts/workflow/index.js +1 -1
  104. package/dist/shortcuts/workflow/operations/form.js +65 -171
  105. package/dist/shortcuts/workflow/operations/todoSmartConvert/aigroupRules.js +129 -27
  106. package/dist/shortcuts/workflow/operations/todoSmartConvert/aigroupStore.js +6 -0
  107. package/dist/shortcuts/workflow/operations/todoSmartConvert/summaryCache.js +60 -117
  108. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoDetailCache.js +44 -7
  109. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoDetailFetcher.js +37 -29
  110. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.collect.js +48 -8
  111. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.core.js +1 -0
  112. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.detail.js +22 -18
  113. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.group.js +17 -9
  114. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.groupSummary.js +34 -10
  115. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.js +5 -2
  116. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.smartGroup.js +9 -2
  117. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.summary.js +122 -20
  118. package/docs/_catalog.md +3 -0
  119. package/docs/asset-idle.md +45 -0
  120. package/docs/e10-auth.md +16 -1
  121. package/docs/todo-smart-convert/subgroup-rules.md +1 -1
  122. package/package.json +1 -1
  123. package/scripts/mcp-meeting.mjs +234 -0
  124. package/scripts/package-skill.mjs +2 -0
  125. package/skill-template/business-info.json +14 -1
  126. package/skill-template/domains/asset-idle.md +13 -0
  127. package/skill-template/domains/wenshuding.md +3 -0
  128. package/skills/weaver-e10-archive-oa/SKILL.md +1 -0
  129. package/skills/weaver-e10-archive-oa/references/my-borrow.md +49 -0
  130. package/skills/weaver-e10-asset-idle/SKILL.md +73 -0
  131. package/skills/weaver-e10-asset-idle/references/idle-list.md +62 -0
  132. package/skills/weaver-e10-asset-idle/references/idle-stats-and-charts.md +65 -0
  133. package/skills/weaver-e10-asset-idle/references/source-manifest.json +193 -0
  134. package/skills/weaver-e10-asset-idle/references/widget-style.md +205 -0
  135. package/skills/weaver-e10-caizhilian/SKILL.md +29 -11
  136. package/skills/weaver-e10-caizhilian/references/auth-and-install.md +12 -0
  137. package/skills/weaver-e10-caizhilian/references/files.md +1 -1
  138. package/skills/weaver-e10-caizhilian/references/setting-encrypt.md +1 -1
  139. package/skills/weaver-e10-caizhilian/references/source-manifest.json +45 -45
  140. package/skills/weaver-e10-cowork/SKILL.md +2 -0
  141. package/skills/weaver-e10-cowork/references/cowork-write.md +1 -1
  142. package/skills/weaver-e10-jucailin/SKILL.md +2 -0
  143. package/skills/weaver-e10-jucailin/references/recruit-read.md +2 -0
  144. package/skills/weaver-e10-jucailin/references/recruit-write.md +1 -1
  145. package/skills/weaver-e10-mail/SKILL.md +3 -0
  146. package/skills/weaver-e10-mail/references/mail-send.md +2 -0
  147. package/skills/weaver-e10-meeting/SKILL.md +88 -25
  148. package/skills/weaver-e10-meeting/references/meeting-minute-file.md +25 -11
  149. package/skills/weaver-e10-meeting.zip +0 -0
  150. package/skills/weaver-e10-shared/SKILL.md +5 -5
  151. package/skills/weaver-e10-shared/references/e10-auth-and-session.md +3 -3
  152. package/skills/weaver-e10-wenshuding/SKILL.md +29 -6
  153. package/skills/weaver-e10-wenshuding/references/archive-borrow-list.md +14 -0
  154. package/skills/weaver-e10-wenshuding/references/archive-download.md +2 -0
  155. package/skills/weaver-e10-wenshuding/references/archive-flow.md +1 -1
  156. package/skills/weaver-e10-wenshuding/references/archive-prelib-arrange.md +76 -0
  157. package/skills/weaver-e10-wenshuding/references/archive-prelib-list.md +107 -0
  158. package/skills/weaver-e10-wenshuding/references/archive-prelib-open.md +59 -0
  159. package/skills/weaver-e10-wenshuding/references/archive-upload.md +4 -1
  160. package/skills/weaver-e10-wenshuding/references/source-manifest.json +108 -65
  161. package/skills/weaver-e10-workflow/SKILL.md +9 -2
  162. package/skills/weaver-e10-workflow/aigroup/default.json +166 -19
  163. package/skills/weaver-e10-workflow/references/todo-smart-convert.md +72 -34
  164. package/skills/weaver-e10-workflow/tools/todo-serve.cjs +113 -25
  165. package/skills/weaver-e10-workflow/tools/todo-summary.cjs +350 -50
  166. package/dist/chunks/{chunk-2LV74IWP.js → chunk-E7OK5WD5.js} +5 -5
@@ -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
  },
@@ -2,6 +2,7 @@ import { E10_SESSION_EXPIRED_MESSAGE } from '../../internal/e10/auth/messages.js
2
2
  import { requireE10Session } from '../../internal/e10/context.js';
3
3
  import { getActiveProfile } from '../../internal/e10/auth/session.js';
4
4
  import { createE10RequestRuntime } from '../../internal/e10/request-runtime.js';
5
+ import { isE10AuthenticationRequired } from '../../internal/e10/auth-recovery.js';
5
6
  import { workflowApiError, workflowAuthError, workflowNetworkError } from './errors.js';
6
7
  const DEFAULT_TIMEOUT_MS = 30_000;
7
8
  /**
@@ -81,6 +82,8 @@ export function isAtustRedirect(status, value) {
81
82
  export function isLoginExpired(value, httpStatus) {
82
83
  if (httpStatus === 401 || httpStatus === 403)
83
84
  return true;
85
+ if (isE10AuthenticationRequired({ status: httpStatus ?? 200, value }))
86
+ return true;
84
87
  if (isAtustRedirect(httpStatus, value))
85
88
  return true;
86
89
  const joined = payloadTexts(value).join('\n').toLowerCase();
@@ -215,7 +215,7 @@ export const workflowShortcut = {
215
215
  const user = readUserIntents({ ctx: ctx });
216
216
  ctx.output.write({
217
217
  dir: loaded.dir,
218
- defaultFile: join(loaded.dir, 'default.json'),
218
+ defaultFile: loaded.dir ? join(loaded.dir, 'default.json') : null,
219
219
  userIntentFile: user.file,
220
220
  userIntentExists: user.exists,
221
221
  userIntentError: user.error,
@@ -34,8 +34,16 @@ import { buildChatSceneInput, formatLocalNow, requestFormFill } from './chatscen
34
34
  const TEXTAREA_COMPONENTS = new Set(['textarea', 'richtext', 'editor', 'html', 'htmltext']);
35
35
  const SELECT_COMPONENTS = new Set(['select', 'selectmultiple', 'radiobox', 'checkbox', 'radio', 'multiselect']);
36
36
  const BROWSER_COMPONENTS = new Set([
37
- 'employee', 'department', 'subcompany', 'jobtitle', 'job', 'relatebrowser',
38
- 'browser', 'workflow', 'ebuilder', 'customer', 'project',
37
+ // 人员 / 组织类
38
+ 'employee', 'department', 'subcompany', 'jobtitle', 'job',
39
+ 'employeeorganization', 'employeeshare', 'employeeauthshare', 'odocreceivecomponent',
40
+ // 关联类型(设计器「表单组件 / 关联类型」面板)
41
+ 'ebuilder', 'mainline', 'project', 'task', 'document', 'workflow', 'formcomponent',
42
+ 'customer', 'customercomponent', 'cluecomponent', 'ordercomponent', 'contactcomponent',
43
+ 'productioncomponent', 'contractcomponent', 'activitycomponent', 'competitorcomponent',
44
+ 'kpiflowcomponent', 'workreportcomponent', 'attendcomponent', 'agendacomponent', 'chancecomponent',
45
+ 'relatebrowser', 'relateodoc', 'relatetag', 'relateintegrate', 'modulecomponent', 'paas',
46
+ 'quotecomponent', 'drivingrecord', 'browser',
39
47
  ]);
40
48
  const NON_INPUT_COMPONENTS = new Set([
41
49
  'paragraph', 'label', 'divider', 'option', 'detailtable', 'group', 'layout', 'internaltitle', 'multilingualtag',
@@ -43,6 +51,19 @@ const NON_INPUT_COMPONENTS = new Set([
43
51
  const READONLY_COMPONENTS = new Set([
44
52
  'dividingline', 'paragraph', 'slidercomponent', 'button', 'subtablecontrol', 'mobileinteraction',
45
53
  ]);
54
+ /**
55
+ * 发票(`EinvoiceComponent`)/ 附件(`FileComponent`)字段也按浏览按钮口径对待。
56
+ *
57
+ * 这两个组件不在 `BROWSER_COMPONENTS` 里、也没有 `browserType`,所以 `extractFields`
58
+ * 算出的 `isBrowser` 是 false —— 但它们的值同样是**对象 id**(发票 id / 附件 fileid),
59
+ * 与浏览按钮的记录 id 同性质。凡按 `isBrowser` 分流的场合(出参 `isBrowser` 标记、
60
+ * 小e URL 参数的 `##` 前缀)都要用本判定把这两个组件补上。**只做对外口径补齐,
61
+ * 不改 `extractFields` 的 `isBrowser` 计算**——那会影响值解析/联动等内部分支,超出本判据的职责。
62
+ */
63
+ function isBrowserLikeComponentKey(componentKey) {
64
+ const normalized = String(componentKey ?? '').toLowerCase();
65
+ return normalized === 'einvoicecomponent' || normalized === 'filecomponent';
66
+ }
46
67
  /** 系统字段键:流程标题/紧急程度/密级;流程编号 REQUESTMARK 忽略不校验。 */
47
68
  const SYSTEM_FIELD_KEYS = {
48
69
  REQUEST_NAMEL: '流程标题',
@@ -593,14 +614,7 @@ function extractFields(layoutData, requiredMap, disableEditMap, displayMap) {
593
614
  // (Python 的 `bool({})` 为 False)——直接照抄 Python 的 `bool(browser_type)` 会让**每个字段**
594
615
  // 都被判成浏览按钮字段,文本/富文本字段随之走 name→ID 解析并报 BROWSER NOT_FOUND,
595
616
  // 内容永远写不进去(2026-09-16「内部留言」留言内容字段实证)。必须判"非空对象"。
596
- // 组件名有两种写法:eBuilder 表单里常带 `Component` 后缀(`CustomerComponent` /
597
- // `EmployeeComponent`),标准流程表单里是不带的(`customer` / `employee`)。
598
- // ⛔ 只认前者会漏判:2026-10-31 会议申请流程的「参会客户」是 `CustomerComponent`,
599
- // 被当成普通文本字段 ⇒ 提交 `[{id,name}]` 报「文本字段只接受字符串、数字或布尔值」。
600
- const componentBase = normalized.replace(/component$/, '');
601
- const isBrowser = Object.keys(browserType).length > 0
602
- || BROWSER_COMPONENTS.has(normalized)
603
- || BROWSER_COMPONENTS.has(componentBase);
617
+ const isBrowser = Object.keys(browserType).length > 0 || BROWSER_COMPONENTS.has(normalized);
604
618
  const isSelect = SELECT_COMPONENTS.has(normalized) || options.length > 0;
605
619
  result.push({
606
620
  id: fieldId,
@@ -2147,31 +2161,6 @@ async function buildCreateFormData(workflowId, env, options = {}) {
2147
2161
  continue;
2148
2162
  visibleFields.push(f);
2149
2163
  }
2150
- /**
2151
- * 明细表**整表被判不可见**时的显式告警(2026-10-31)。
2152
- *
2153
- * 场景:明细表被摆在了页面网格的**隐藏行 / 隐藏列**上(`pageLayouts[0].config.workbook.Sheet1.rows[n].visible=false`),
2154
- * 于是它的列字段全部被 `hiddenCellIds` 过滤掉。此前是**静默丢弃** —— 模型只看到"表单里没有这张明细表",
2155
- * 分不清「表单确实没配」和「配了但摆在隐藏行上」,只能回一句"没有这张表",用户无从下手。
2156
- * 这里按 subFormId 汇总,整表字段全灭的才告警(部分列隐藏属正常设计)。
2157
- */
2158
- const hiddenBySubform = new Map();
2159
- for (const f of fields) {
2160
- const fid = String(f.id ?? '');
2161
- const sid = String(f.subFormId ?? '').trim();
2162
- if (!sid || !hiddenCellIds.has(fid))
2163
- continue;
2164
- hiddenBySubform.set(sid, (hiddenBySubform.get(sid) || 0) + 1);
2165
- }
2166
- const survivingSubforms = new Set(visibleFields.map((f) => String(f.subFormId ?? '').trim()).filter(Boolean));
2167
- const invisibleSubformWarnings = [];
2168
- for (const [sid, count] of hiddenBySubform) {
2169
- if (survivingSubforms.has(sid))
2170
- continue;
2171
- invisibleSubformWarnings.push(`明细表 ${sid} 的 ${count} 个字段全部被判为「不可见」(该明细表所在的行/列在表单布局里是隐藏的),`
2172
- + '因此没有出现在字段清单里。要么在表单设计器里把明细表挪到可见行,要么取消隐藏它所在的行/列,然后重新载入表单。');
2173
- }
2174
- let layoutWarning = invisibleSubformWarnings.length ? invisibleSubformWarnings.join(';') : null;
2175
2164
  const defaults = normalizeDefaults(layoutData.defaultDetails);
2176
2165
  // 计算必填/可编辑/明细必填(按默认值 + onLoadForm 联动)
2177
2166
  const systemFieldValues = _normalizeSystemFieldValues(layoutData.businessOperatorObj);
@@ -2236,52 +2225,6 @@ async function buildCreateFormData(workflowId, env, options = {}) {
2236
2225
  }
2237
2226
  }
2238
2227
  const folderOptionValues = _mainValueMap(defaults);
2239
- /**
2240
- * 「条件显示」的明细表(2026-10-31,会议申请流程「参会客户明细」实证)
2241
- *
2242
- * 表单可以给明细表配**显示联动**:触发字段有值时 `show`、为空时 `hideLine`。
2243
- * 会议申请流程的「参会客户明细」(subFormId `1332370499762806784`)就是这样 ——
2244
- * 触发字段是「参会客户」(`crm_members`),空着时整表隐藏。
2245
- *
2246
- * 旧行为把它与「明细表被摆在隐藏行」一视同仁 ⇒ 整表字段从 `fields` 里消失,
2247
- * 建会链路既解析不到明细表也写不进明细,模型只能回「表单里没有这张表」。
2248
- * 现在:布局上确实存在、且有成对 `show` 规则的表,按**条件可见**处理 ——
2249
- * 字段照常下发(标 `conditional: true`),并在 `layoutWarning` 里说明触发字段。
2250
- */
2251
- const conditionalHiddenSubforms = new Set();
2252
- const conditionalMainFields = new Set();
2253
- const conditionalHints = collectDetailShowLinkages(layoutData.viewLinkages, fields);
2254
- /**
2255
- * 主表字段同理:「参会客户」(`crm_members`) 自己也有成对的 `show` / `hideLine` 规则
2256
- * (空值时连自己都隐藏)。若不这样处理,建会链路的 `crm_members` 永远映射不到字段。
2257
- */
2258
- const fieldShowIds = collectFieldShowLinkageIds(layoutData.viewLinkages);
2259
- for (const fid of Array.from(mainHidden)) {
2260
- if (!fieldShowIds.has(fid))
2261
- continue;
2262
- if (hiddenCellIds.has(fid))
2263
- continue;
2264
- const target = visibleFields.find((f) => String(f.id ?? '') === fid);
2265
- if (!target)
2266
- continue;
2267
- conditionalMainFields.add(fid);
2268
- mainHidden.delete(fid);
2269
- invisibleSubformWarnings.push(`字段「${target.name}」(${fid}) 因显示联动隐藏(该字段本身是触发字段),已照常下发并标 conditional: true`);
2270
- }
2271
- for (const sid of Array.from(hiddenSubforms)) {
2272
- const hint = conditionalHints.get(sid);
2273
- if (!hint)
2274
- continue;
2275
- const usable = visibleFields.some((f) => String(f.subFormId ?? '') === sid && !hiddenCellIds.has(String(f.id ?? '')));
2276
- if (!usable)
2277
- continue;
2278
- conditionalHiddenSubforms.add(sid);
2279
- hiddenSubforms.delete(sid);
2280
- invisibleSubformWarnings.push(`明细表 ${sid}${hint.tableName ? `(${hint.tableName})` : ''} 因显示联动隐藏,触发字段:`
2281
- + `${hint.triggerNames.join('、') || hint.triggerIds.join('、')}。`
2282
- + '该表字段已照常下发(标 conditional: true),把触发字段填上后表单里就会显示;不要回「表单里没有这张明细表」。');
2283
- }
2284
- layoutWarning = invisibleSubformWarnings.length ? invisibleSubformWarnings.join(';') : null;
2285
2228
  for (const field of visibleFields) {
2286
2229
  const fieldId = String(field.id ?? '');
2287
2230
  const subFormId = String(field.subFormId ?? '');
@@ -2381,11 +2324,6 @@ async function buildCreateFormData(workflowId, env, options = {}) {
2381
2324
  else if (slimSubFormId) {
2382
2325
  item.subFormId = slimSubFormId;
2383
2326
  }
2384
- // 条件可见的明细表(见 conditionalHiddenSubforms 注释):字段可用,但当前触发字段没值时表单里不显示。
2385
- if (slimSubFormId && conditionalHiddenSubforms.has(slimSubFormId))
2386
- item.conditional = true;
2387
- if (!slimSubFormId && conditionalMainFields.has(String(field.id ?? '')))
2388
- item.conditional = true;
2389
2327
  item.componentKey = field.componentKey;
2390
2328
  // 表单初始化时就有的值(OA 预填 + onLoadForm 联动带出)——有才出现该键;**没有默认值的字段不带**。
2391
2329
  // CLI 拼 `data` 时会自动并入,模型**不必显式传**;用户本次明确改写了它才按用户说的覆盖(见
@@ -2404,6 +2342,9 @@ async function buildCreateFormData(workflowId, env, options = {}) {
2404
2342
  if (field[key])
2405
2343
  item[key] = field[key];
2406
2344
  }
2345
+ // 发票 / 附件字段的值也是对象 id,对外同样标 `isBrowser`(见 isBrowserLikeComponentKey 说明)
2346
+ if (!item.isBrowser && isBrowserLikeComponentKey(field.componentKey))
2347
+ item.isBrowser = true;
2407
2348
  if (String(field.componentKey ?? '').toLowerCase() === 'employeescope')
2408
2349
  item.isEmployeeScope = true;
2409
2350
  if (String(field.componentKey ?? '').toLowerCase() === 'einvoicecomponent')
@@ -2544,8 +2485,6 @@ async function buildCreateFormData(workflowId, env, options = {}) {
2544
2485
  },
2545
2486
  systemFieldWarning: minorWarning,
2546
2487
  requiredInfoWarning: requiredInfoWarningText(context),
2547
- // 明细表整表不可见时的告警(见 layoutWarning 注释):有值时**不要**回"表单里没有这张明细表"。
2548
- layoutWarning,
2549
2488
  // 原 Python 把完整 context 落盘;此处通过 continuation 承载,返回结构保持文档契约。
2550
2489
  continuation: token,
2551
2490
  },
@@ -4047,74 +3986,6 @@ function mergeLinkageDetails(merged, linkageDetails) {
4047
3986
  function emptyLinkageVisibility() {
4048
3987
  return [new Set(), {}, {}, {}];
4049
3988
  }
4050
- /**
4051
- * 收集主表字段的 `show` 联动字段 id(`linkageObj=field` 且 `linkageProp=show`)。
4052
- * 这类字段的隐藏是**条件性**的,按条件可见处理,见 `buildCreateFormData` 的 `conditionalMainFields`。
4053
- */
4054
- export function collectFieldShowLinkageIds(linkages) {
4055
- const out = new Set();
4056
- if (!Array.isArray(linkages))
4057
- return out;
4058
- for (const rule of linkages) {
4059
- const details = Array.isArray(rule?.linkageDetails) ? rule.linkageDetails : [];
4060
- for (const detail of details) {
4061
- if (!detail || typeof detail !== 'object')
4062
- continue;
4063
- if (String(detail.linkageObj ?? '') !== 'field')
4064
- continue;
4065
- if (String(detail.linkageProp ?? '') !== 'show')
4066
- continue;
4067
- const fid = String(detail.linkageValue ?? '').trim();
4068
- if (fid)
4069
- out.add(fid);
4070
- }
4071
- }
4072
- return out;
4073
- }
4074
- /**
4075
- * 收集「明细表的显示联动」——`linkageObj=detail` 且 `linkageProp=show` 的规则。
4076
- *
4077
- * 用途见 `buildCreateFormData` 里 `conditionalHiddenSubforms` 的注释:配了 `show` 规则的明细表
4078
- * 是**条件可见**(另有配套的 `hideLine` 规则),不能因为当期条件不成立就把整表字段丢掉。
4079
- * 返回 `subFormId → { triggerIds, triggerNames, tableName }`,triggerNames 供告警文本直接引用。
4080
- */
4081
- export function collectDetailShowLinkages(linkages, fields) {
4082
- const out = new Map();
4083
- if (!Array.isArray(linkages))
4084
- return out;
4085
- const list = Array.isArray(fields) ? fields : [];
4086
- const nameOf = (id) => {
4087
- for (const f of list) {
4088
- if (String(f?.id ?? '') === id)
4089
- return String(f?.name ?? id);
4090
- }
4091
- return id;
4092
- };
4093
- for (const rule of linkages) {
4094
- const details = Array.isArray(rule?.linkageDetails) ? rule.linkageDetails : [];
4095
- for (const detail of details) {
4096
- if (!detail || typeof detail !== 'object')
4097
- continue;
4098
- if (String(detail.linkageObj ?? '') !== 'detail')
4099
- continue;
4100
- if (String(detail.linkageProp ?? '') !== 'show')
4101
- continue;
4102
- const sid = String(detail.linkageValue ?? '').trim();
4103
- if (!sid)
4104
- continue;
4105
- const conds = Array.isArray(rule?.condition?.conditionDetails) ? rule.condition.conditionDetails : [];
4106
- const triggerIds = conds
4107
- .map((c) => String(c?.targetId ?? '').trim())
4108
- .filter(Boolean);
4109
- out.set(sid, {
4110
- triggerIds,
4111
- triggerNames: triggerIds.map(nameOf),
4112
- tableName: nameOf(sid) === sid ? '' : nameOf(sid),
4113
- });
4114
- }
4115
- }
4116
- return out;
4117
- }
4118
3989
  function computeLinkageVisibility(linkages, entries, defaults, linkageDetails, systemFieldValues, fields) {
4119
3990
  const hiddenSubforms = new Set();
4120
3991
  const rowHidden = {};
@@ -4879,8 +4750,19 @@ const XIAOE_FILL_FORM_SDK_OPTIONS = {
4879
4750
  */
4880
4751
  const XIAOE_INITIAL_VALUES_MULTI_SEPARATOR = ',';
4881
4752
  const XIAOE_INITIAL_VALUES_ROW_SEPARATOR = ';';
4753
+ /**
4754
+ * 浏览按钮字段的值是**对象 id** 时加的前缀(2026-11-02 用户裁定)。
4755
+ *
4756
+ * 口径:`isBrowser === true` 的字段值来自 `[{id, name}]` 里的 `id`,落进 `customParams` / `src`
4757
+ * 时写成 `##<id>`,让页面能区分「这是记录 id」与「这是用户手打的名称」;而**名称不拼前缀**
4758
+ * (非浏览字段的值本来就是字符串,原样透传)。
4759
+ *
4760
+ * 多值逐个判定,所以 `##<id>,<名称>` 是合法形态(同一字段里既有解析出的 id、又有原样名称)。
4761
+ * 明细多行同口径:`##<id>;<名称>`。
4762
+ */
4763
+ const XIAOE_BROWSER_ID_PREFIX = '##';
4882
4764
  /** 单个标量 → URL 传参里的那一段文本。对象按「id 优先」取标识(附件取到的就是 fileid)。 */
4883
- function xiaoEUrlParamScalar(value) {
4765
+ function xiaoEUrlParamScalar(value, prefixBrowserId = false) {
4884
4766
  if (value === null || value === undefined)
4885
4767
  return '';
4886
4768
  if (typeof value === 'object') {
@@ -4893,24 +4775,26 @@ function xiaoEUrlParamScalar(value) {
4893
4775
  if (picked === null || picked === undefined)
4894
4776
  continue;
4895
4777
  const text = String(picked).trim();
4896
- if (text)
4897
- return text;
4778
+ if (!text)
4779
+ continue;
4780
+ // 浏览按钮字段:这一段的来源是对象 id(不是用户给的名称),按 2026-11-02 口径加 `##`。
4781
+ return prefixBrowserId ? `${XIAOE_BROWSER_ID_PREFIX}${text}` : text;
4898
4782
  }
4899
4783
  return '';
4900
4784
  }
4901
4785
  return String(value).trim();
4902
4786
  }
4903
4787
  /** 单个字段的值 → URL 传参里的那一段文本(数组按「同字段多值」用 `,` 连接)。 */
4904
- function xiaoEUrlParamValue(value) {
4788
+ function xiaoEUrlParamValue(value, prefixBrowserId = false) {
4905
4789
  if (value === null || value === undefined)
4906
4790
  return '';
4907
4791
  if (Array.isArray(value)) {
4908
4792
  return value
4909
- .map((item) => xiaoEUrlParamScalar(item))
4793
+ .map((item) => xiaoEUrlParamScalar(item, prefixBrowserId))
4910
4794
  .filter(Boolean)
4911
4795
  .join(XIAOE_INITIAL_VALUES_MULTI_SEPARATOR);
4912
4796
  }
4913
- return xiaoEUrlParamScalar(value);
4797
+ return xiaoEUrlParamScalar(value, prefixBrowserId);
4914
4798
  }
4915
4799
  /**
4916
4800
  * `params.customParams` 里**字段值**那一份 = `{字段id: 值}`(用户裁定用**字段 id**,不用数据 key);
@@ -4921,6 +4805,11 @@ function xiaoEUrlParamValue(value) {
4921
4805
  *
4922
4806
  * 明细字段(`preview` 里带 `dataIndex`)按**行号升序**取值、用 `;` 连接;主表字段就是它自己的值。
4923
4807
  * 值为空(没解析出内容)的字段整个省略,不生成空参数。
4808
+ *
4809
+ * **浏览按钮字段的值加 `##` 前缀**(2026-11-02 用户裁定,见 `XIAOE_BROWSER_ID_PREFIX`):这类字段解析
4810
+ * 出来的每一段都是对象 id,写成 `##<id>`;名称(非浏览字段的字符串)原样不加。多值/多行逐个判定,
4811
+ * 因此 `##<id>,<名称>`、`##<id>;<名称>` 都是合法形态。`src` 末尾的字段参数与这里同源,一起带上。
4812
+ * 发票 / 附件字段按 `isBrowserLikeComponentKey` 一并算浏览按钮口径(值同为对象 id,2026-11-03 用户裁定)。
4924
4813
  */
4925
4814
  /**
4926
4815
  * 小e 档交给浏览器页面的字段值:只留**可见且可编辑**的那些(2026-10-21 用户裁定)。
@@ -4963,15 +4852,25 @@ function editablePreviewForXiaoE(preview, fields, visibility) {
4963
4852
  }
4964
4853
  return kept;
4965
4854
  }
4966
- function xiaoECustomParams(preview) {
4855
+ function xiaoECustomParams(preview, fields) {
4967
4856
  const ordered = [];
4968
4857
  const mainValues = new Map();
4969
4858
  const detailValues = new Map();
4859
+ // 「这个字段是不是浏览按钮」决定要不要给对象 id 拼 `##`(2026-11-02 用户裁定)——
4860
+ // 判据取 `extractFields` 算好的 `isBrowser`,与值解析走的是同一份结论,不在这里二次推断;
4861
+ // 发票 / 附件字段不在 BROWSER_COMPONENTS(isBrowser 为 false),但其值同样是对象 id,
4862
+ // 用 isBrowserLikeComponentKey 一并补上(2026-11-03 用户裁定)。
4863
+ const browserFieldIds = new Set();
4864
+ for (const field of toArray(fields)) {
4865
+ const id = String(field?.id ?? '').trim();
4866
+ if (id && (field?.isBrowser || isBrowserLikeComponentKey(field?.componentKey)))
4867
+ browserFieldIds.add(id);
4868
+ }
4970
4869
  for (const item of toArray(preview)) {
4971
4870
  const fieldId = String(item?.fieldId ?? '').trim();
4972
4871
  if (!fieldId)
4973
4872
  continue;
4974
- const text = xiaoEUrlParamValue(item?.value);
4873
+ const text = xiaoEUrlParamValue(item?.value, browserFieldIds.has(fieldId));
4975
4874
  if (!text)
4976
4875
  continue;
4977
4876
  const isDetail = item?.dataIndex !== undefined && item?.dataIndex !== null;
@@ -5220,12 +5119,7 @@ async function runCreateRequest(args, env, options = {}) {
5220
5119
  * (`buildCreateFormData`,那边的联动结果要用来算字段的必填 / 可编辑清单);这里两者都不跑 ——
5221
5120
  * 本档既不做必填判定、也不发写请求。非小e 环境行为不变。
5222
5121
  */
5223
- // ⛔ 2026-10-31 真机回归:调用方可以显式要求「小e 环境也走服务端提交」(meeting 建会直提用)。
5224
- // 起因:小e 的浏览器填单页只给**文本类**字段赋值,浏览按钮(参会人员 / 参会客户)传过去整段是空 ——
5225
- // 用户看到的就是"填了但表单里这两个字段没有值"。服务端提交这条路上这些字段走标准 JSON,
5226
- // 能完整落库,所以建会这类「字段必须完整」的场景应走直提,浏览器填单只留给需要人工核对/补填的场合。
5227
- const forceServerSubmit = args.forceServerSubmit === true;
5228
- const isXiaoE = forceServerSubmit ? false : hasXiaoEEnv(env.ctx.env, env.ctx.getGlobalOptions?.());
5122
+ const isXiaoE = hasXiaoEEnv(env.ctx.env, env.ctx.getGlobalOptions?.());
5229
5123
  const linkageDetails = isXiaoE
5230
5124
  ? []
5231
5125
  : await runFieldLinkage(env, linkageContextFromPayload(context), entries, null, merged, false, context.defaults || []);
@@ -5392,7 +5286,7 @@ async function runCreateRequest(args, env, options = {}) {
5392
5286
  baseUrl: xiaoEBaseUrl,
5393
5287
  title: String(context.workflow?.name ?? ''),
5394
5288
  workflowId: String(context.workflow?.id ?? context.runtime?.workflow_id ?? ''),
5395
- customParams: xiaoECustomParams(fillPreview),
5289
+ customParams: xiaoECustomParams(fillPreview, fields),
5396
5290
  }),
5397
5291
  });
5398
5292
  }