weaver-work-cli 0.1.32 → 0.1.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. package/README.md +2 -2
  2. package/dist/chunks/{archive-UMGX72K6.js → archive-XBVH7JU7.js} +2 -2
  3. package/dist/chunks/{archive-oa-N2IYBLF3.js → archive-oa-MZM3EKFQ.js} +2 -2
  4. package/dist/chunks/{asset-GLEGKFG5.js → asset-4XO7UOUW.js} +2 -2
  5. package/dist/chunks/{attachment-content-AQNX5N3S.js → attachment-content-VXSWANAC.js} +3 -2
  6. package/dist/chunks/{blog-E44OTVDA.js → blog-PGZKOPSC.js} +2 -2
  7. package/dist/chunks/{caizhilian-DQUFZXZL.js → caizhilian-BH4ZVGUN.js} +2 -2
  8. package/dist/chunks/{calendar-JKPIRKFD.js → calendar-MXEMTIO4.js} +2 -2
  9. package/dist/chunks/{chunk-QWY6PGL6.js → chunk-2LV74IWP.js} +361 -56
  10. package/dist/chunks/chunk-3SO6QL6U.js +73 -0
  11. package/dist/chunks/chunk-5DQEEGCC.js +229 -0
  12. package/dist/chunks/{chunk-G3QJULO2.js → chunk-6LLJFKJE.js} +1 -1
  13. package/dist/chunks/{chunk-C6R3B2ML.js → chunk-BPE37FPB.js} +35 -7
  14. package/dist/chunks/chunk-BYATLLEF.js +68 -0
  15. package/dist/chunks/chunk-BYBSCN4X.js +932 -0
  16. package/dist/chunks/{chunk-CVZ2JOSW.js → chunk-F746NKNF.js} +1 -1
  17. package/dist/chunks/{chunk-GCG2NMWR.js → chunk-HXJQI5NM.js} +4 -2
  18. package/dist/chunks/{chunk-YRVDW4WN.js → chunk-I6QGIBDM.js} +2 -2
  19. package/dist/chunks/{chunk-H7J4DC5R.js → chunk-JCSN3JGM.js} +2 -2
  20. package/dist/chunks/{chunk-2GA5ZIQL.js → chunk-JNG42JKG.js} +30 -1
  21. package/dist/chunks/{chunk-DW6AUUKS.js → chunk-JUXVYKII.js} +9 -4
  22. package/dist/chunks/{chunk-6RKD4DRZ.js → chunk-JYKABYCV.js} +2 -2
  23. package/dist/chunks/{chunk-6EFWDL7Y.js → chunk-NCOVUAZK.js} +2 -2
  24. package/dist/chunks/{chunk-E4Z23GHM.js → chunk-QHFANTX7.js} +1114 -904
  25. package/dist/chunks/{ebuilder-form-V5FG2EHC.js → chunk-QYGO5EE2.js} +22 -1148
  26. package/dist/chunks/{chunk-IURUFGZK.js → chunk-T72N74ZT.js} +50 -33
  27. package/dist/chunks/{chunk-H6XSTSLM.js → chunk-TC6MZAO3.js} +137 -204
  28. package/dist/chunks/{chunk-WKI2FBIQ.js → chunk-VPUTVEMC.js} +737 -341
  29. package/dist/chunks/{chunk-WSKBSQ2U.js → chunk-VPXOPQJI.js} +2 -2
  30. package/dist/chunks/chunk-WXFPWCXG.js +774 -0
  31. package/dist/chunks/{chunk-4YC43QQ4.js → chunk-XMGSYD6S.js} +4 -4
  32. package/dist/chunks/conflict-JAB2LJEZ.js +21 -0
  33. package/dist/chunks/continuation-RN6PZN2M.js +11 -0
  34. package/dist/chunks/contract-XI2EEIZU.js +9110 -0
  35. package/dist/chunks/cowork-SCPGLBA4.js +2527 -0
  36. package/dist/chunks/ebuilder-form-EYLHNOTA.js +121 -0
  37. package/dist/chunks/{ehr-HMFY6VIL.js → ehr-EAP77IMT.js} +1344 -27
  38. package/dist/chunks/{esb-ZYSVB7SG.js → esb-2TGGXEE7.js} +4 -4
  39. package/dist/chunks/{fna-B7RKPAZD.js → fna-2X7ZE7BU.js} +13 -8
  40. package/dist/chunks/{host-OCH2BCCV.js → host-C6EL2RKL.js} +3 -3
  41. package/dist/chunks/{host-45WED2KK.js → host-I4NRK4MX.js} +3 -3
  42. package/dist/chunks/{host-I4Z6BFYX.js → host-Q4LZGCRU.js} +3 -3
  43. package/dist/chunks/host-UEI5FGDM.js +23 -0
  44. package/dist/chunks/{host-KNZ52LJZ.js → host-XCLDDXKQ.js} +3 -3
  45. package/dist/chunks/{hrm-2KAZGUHI.js → hrm-YXERJM76.js} +3 -3
  46. package/dist/chunks/{im-ACJJWH4U.js → im-A4VZN2TQ.js} +88 -12
  47. package/dist/chunks/{invoice-ZK3WCX2U.js → invoice-2Z6YPMIS.js} +3 -3
  48. package/dist/chunks/{jiuchuanhui-5PQD3IL2.js → jiuchuanhui-AR5GMKZB.js} +2 -2
  49. package/dist/chunks/{lazy-dispatch-JQXBW3GM.js → lazy-dispatch-QXW7P4X3.js} +4 -4
  50. package/dist/chunks/{mail-S2HDDXO2.js → mail-7GZ6CPBJ.js} +45 -24
  51. package/dist/chunks/{meeting-QVN3I37L.js → meeting-3NMGA3MB.js} +278 -43
  52. package/dist/chunks/{okr-QNG4VC2Q.js → okr-BZ6XUOAG.js} +2 -2
  53. package/dist/chunks/operations-5QL3AECO.js +11 -0
  54. package/dist/chunks/{plan-A25EMW6U.js → plan-DR5CMUYP.js} +2 -2
  55. package/dist/chunks/{project-G6XV66DZ.js → project-6J63MK33.js} +2 -2
  56. package/dist/chunks/{registry-DC6KR3NH.js → registry-KDD7DHNK.js} +4 -4
  57. package/dist/chunks/{registry-2VLUAESA.js → registry-OZSMDVPP.js} +2 -2
  58. package/dist/chunks/{registry-7C5UMIPL.js → registry-TKX2E7C7.js} +2 -2
  59. package/dist/chunks/{registry-TI3CIUXF.js → registry-VRTNC6UR.js} +8 -6
  60. package/dist/chunks/{room-recommend-WRTHO6Y2.js → room-recommend-XCWRMB5P.js} +4 -3
  61. package/dist/chunks/{schedule-KFIITGSK.js → schedule-IVBICP32.js} +4 -3
  62. package/dist/chunks/{workflow-DJ7SYWAF.js → workflow-D74NFK3C.js} +179 -6
  63. package/dist/index.js +35 -25
  64. package/dist/internal/e10/auth-recovery.js +14 -4
  65. package/dist/internal/e10/fetch-timing.js +31 -0
  66. package/dist/internal/e10/request-runtime.js +5 -0
  67. package/dist/shortcuts/contract/host.js +34 -59
  68. package/dist/shortcuts/cowork/continuation.js +70 -0
  69. package/dist/shortcuts/cowork/errors.js +59 -0
  70. package/dist/shortcuts/cowork/host.js +180 -0
  71. package/dist/shortcuts/cowork/index.js +166 -0
  72. package/dist/shortcuts/cowork/manifest.js +65 -0
  73. package/dist/shortcuts/cowork/operations/discuss-write.js +277 -0
  74. package/dist/shortcuts/cowork/operations/item-write.js +380 -0
  75. package/dist/shortcuts/cowork/operations/member-write.js +283 -0
  76. package/dist/shortcuts/cowork/operations/permission-write.js +286 -0
  77. package/dist/shortcuts/cowork/operations/read.js +423 -0
  78. package/dist/shortcuts/cowork/operations/registry.js +39 -0
  79. package/dist/shortcuts/cowork/operations/review-write.js +157 -0
  80. package/dist/shortcuts/cowork/operations/shared.js +215 -0
  81. package/dist/shortcuts/cowork/operations/types.js +39 -0
  82. package/dist/shortcuts/cowork/operations.js +19 -0
  83. package/dist/shortcuts/ehr/host.js +4 -1
  84. package/dist/shortcuts/ehr/operations/recruit-candidate-import.js +757 -0
  85. package/dist/shortcuts/ehr/operations/recruit-position.js +367 -0
  86. package/dist/shortcuts/ehr/operations/recruit-stats.js +133 -0
  87. package/dist/shortcuts/ehr/operations/recruit-write.js +43 -16
  88. package/dist/shortcuts/ehr/operations/recruit.js +182 -10
  89. package/dist/shortcuts/ehr/operations/registry.js +6 -0
  90. package/dist/shortcuts/fna/operations/expense-chart.js +19 -7
  91. package/dist/shortcuts/im/diagnostics.js +97 -0
  92. package/dist/shortcuts/im/host.js +58 -31
  93. package/dist/shortcuts/im/index.js +7 -2
  94. package/dist/shortcuts/im/operations/at-mention.js +127 -0
  95. package/dist/shortcuts/im/operations/file-user.js +10 -2
  96. package/dist/shortcuts/im/operations/group-read.js +26 -5
  97. package/dist/shortcuts/im/operations/group-write.js +35 -28
  98. package/dist/shortcuts/im/operations/msg-sync.js +7 -2
  99. package/dist/shortcuts/im/operations/msg-write.js +76 -10
  100. package/dist/shortcuts/im/operations/quote-mention.js +119 -0
  101. package/dist/shortcuts/im/operations/session.js +1 -1
  102. package/dist/shortcuts/im/operations/shared.js +10 -0
  103. package/dist/shortcuts/im/operations/types.js +13 -0
  104. package/dist/shortcuts/index.js +10 -0
  105. package/dist/shortcuts/mail/host.js +12 -1
  106. package/dist/shortcuts/mail/operations/types.js +17 -2
  107. package/dist/shortcuts/meeting/host.js +27 -34
  108. package/dist/shortcuts/meeting/index.js +79 -1
  109. package/dist/shortcuts/meeting/manifest.js +10 -0
  110. package/dist/shortcuts/meeting/operations/create.js +4 -1
  111. package/dist/shortcuts/meeting/operations/eb/catalog.js +10 -0
  112. package/dist/shortcuts/meeting/operations/eb/conflict.js +84 -8
  113. package/dist/shortcuts/meeting/operations/eb/create-flow.js +1195 -140
  114. package/dist/shortcuts/meeting/operations/eb/factory.js +170 -8
  115. package/dist/shortcuts/meeting/operations/eb/index.js +16 -3
  116. package/dist/shortcuts/meeting/operations/eb/room-recommend.js +50 -5
  117. package/dist/shortcuts/meeting/operations/eb-fallback.js +163 -0
  118. package/dist/shortcuts/meeting/operations/env.js +99 -46
  119. package/dist/shortcuts/meeting/operations/form-fields.js +110 -0
  120. package/dist/shortcuts/meeting/operations/invitation.js +79 -20
  121. package/dist/shortcuts/meeting/operations/registry.js +2 -0
  122. package/dist/shortcuts/meeting/operations/standard-guard.js +71 -0
  123. package/dist/shortcuts/meeting/operations.js +16 -0
  124. package/dist/shortcuts/workflow/endpoints.js +8 -3
  125. package/dist/shortcuts/workflow/index.js +110 -1
  126. package/dist/shortcuts/workflow/operations/form.js +204 -36
  127. package/dist/shortcuts/workflow/operations/read.js +104 -23
  128. package/dist/shortcuts/workflow/operations/todoSmartConvert/aigroupRules.js +448 -0
  129. package/dist/shortcuts/workflow/operations/todoSmartConvert/aigroupStore.js +106 -0
  130. package/dist/shortcuts/workflow/operations/todoSmartConvert/businessRules.js +47 -0
  131. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.collect.js +30 -23
  132. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.core.js +1 -0
  133. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.group.js +1 -0
  134. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.groupSummary.js +2 -1
  135. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.js +2 -4
  136. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.smartGroup.js +138 -181
  137. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.summary.js +12 -7
  138. package/dist/shortcuts/workflow/operations.js +23 -1
  139. package/docs/e10-auth.md +1 -1
  140. package/docs/ehr.md +1 -1
  141. package/docs/fna.md +2 -1
  142. package/docs/im.md +8 -1
  143. package/docs/todo-smart-convert/README.md +1 -1
  144. package/docs/todo-smart-convert/data-contract.md +2 -2
  145. package/docs/todo-smart-convert/subgroup-rules.md +6 -0
  146. package/package.json +1 -1
  147. package/skill-template/business-info.json +18 -0
  148. package/skill-template/domains/cowork.md +14 -0
  149. package/skill-template/domains/yimiaoban.md +3 -3
  150. package/skill-template/skill-template.md +1 -1
  151. package/skills/weaver-e10-blog/SKILL.md +5 -5
  152. package/skills/weaver-e10-caizhilian/SKILL.md +5 -5
  153. package/skills/weaver-e10-calendar/SKILL.md +5 -5
  154. package/skills/weaver-e10-cowork/SKILL.md +101 -0
  155. package/skills/weaver-e10-cowork/references/cowork-read.md +90 -0
  156. package/skills/weaver-e10-cowork/references/cowork-write.md +131 -0
  157. package/skills/weaver-e10-cowork/references/safety-boundaries.md +45 -0
  158. package/skills/weaver-e10-esb/SKILL.md +5 -5
  159. package/skills/weaver-e10-hrm/SKILL.md +5 -5
  160. package/skills/weaver-e10-jinchengda/SKILL.md +2 -2
  161. package/skills/weaver-e10-jiuchuanhui/SKILL.md +5 -5
  162. package/skills/weaver-e10-jucailin/SKILL.md +38 -14
  163. package/skills/weaver-e10-jucailin/references/recruit-create.md +104 -0
  164. package/skills/weaver-e10-jucailin/references/recruit-read.md +25 -0
  165. package/skills/weaver-e10-jucailin/references/recruit-write.md +13 -0
  166. package/skills/weaver-e10-jucailin/references/salary.md +15 -11
  167. package/skills/weaver-e10-mail/SKILL.md +17 -14
  168. package/skills/weaver-e10-meeting/SKILL.md +442 -46
  169. package/skills/weaver-e10-meeting/references/meeting-eb.md +135 -3
  170. package/skills/weaver-e10-meeting/references/meeting-invitation.md +28 -2
  171. package/skills/weaver-e10-meeting/references/meeting-queries.md +9 -6
  172. package/skills/weaver-e10-meeting/references/meeting-verification.md +8 -6
  173. package/skills/weaver-e10-meeting.zip +0 -0
  174. package/skills/weaver-e10-okr/SKILL.md +5 -5
  175. package/skills/weaver-e10-okr/references/source-manifest.json +3 -3
  176. package/skills/weaver-e10-plan/SKILL.md +5 -5
  177. package/skills/weaver-e10-qiyecheng/SKILL.md +5 -2
  178. package/skills/weaver-e10-qiyecheng/references/catalog.md +2 -2
  179. package/skills/weaver-e10-qiyecheng/references/expense-chart.md +15 -1
  180. package/skills/weaver-e10-qiyecheng/references/source-manifest.json +22 -21
  181. package/skills/weaver-e10-shared/SKILL.md +12 -12
  182. package/skills/weaver-e10-shared/references/e10-auth-and-session.md +4 -4
  183. package/skills/weaver-e10-wenshuding/SKILL.md +5 -5
  184. package/skills/weaver-e10-workflow/SKILL.md +29 -5
  185. package/skills/weaver-e10-workflow/aigroup/default.json +88 -0
  186. package/skills/weaver-e10-workflow/references/todo-smart-convert.md +66 -14
  187. package/skills/weaver-e10-workflow/references/workflow-agent-entry.md +1 -1
  188. package/skills/weaver-e10-workflow/references/workflow-memory.md +1 -1
  189. package/skills/weaver-e10-workflow/references/workflow-query.md +12 -10
  190. package/skills/weaver-e10-workflow/references/workflow-view.md +2 -2
  191. package/skills/weaver-e10-workflow/tools/todo-serve.cjs +239 -0
  192. package/skills/weaver-e10-workflow/{scripts → tools}/todo-summary.cjs +218 -28
  193. package/skills/weaver-e10-yepiaotong/SKILL.md +10 -8
  194. package/skills/weaver-e10-yepiaotong/references/invoice-reim.md +7 -7
  195. package/skills/weaver-e10-yepiaotong/references/reim-api-reference.md +10 -10
  196. package/skills/weaver-e10-yepiaotong/references/reim-examples.md +40 -37
  197. package/skills/weaver-e10-yepiaotong/references/reim-expense-subject-rules.md +4 -4
  198. package/skills/weaver-e10-yepiaotong/references/reim-form-fill-rules.md +27 -38
  199. package/skills/weaver-e10-yepiaotong/references/reim-phone-subsidy-rules.md +4 -4
  200. package/skills/weaver-e10-yepiaotong/references/reim-workflow.md +37 -29
  201. package/skills/weaver-e10-yimiaoban/SKILL.md +156 -33
  202. package/skills/weaver-e10-yimiaoban/references/ding-write.md +1 -1
  203. package/skills/weaver-e10-yimiaoban/references/error-codes.md +5 -1
  204. package/skills/weaver-e10-yimiaoban/references/group-read.md +5 -1
  205. package/skills/weaver-e10-yimiaoban/references/group-write.md +6 -4
  206. package/skills/weaver-e10-yimiaoban/references/msg-read.md +1 -0
  207. package/skills/weaver-e10-yimiaoban/references/msg-write.md +34 -2
  208. package/skills/weaver-e10-yimiaoban/references/safety-boundaries.md +5 -2
  209. package/skills/weaver-e10-yimiaoban/references/source-manifest.json +79 -58
  210. package/skills/weaver-e10-yimiaoban/references/user-remind.md +2 -2
  211. package/skills/weaver-e10-ziguanjia/SKILL.md +5 -5
@@ -1,8 +1,9 @@
1
1
  import { workflowValidationError } from '../../errors.js';
2
- import { BUSINESS_KIND_LABEL, detectBusinessKind, FINANCE_AMOUNT_THRESHOLD, FINANCE_GROUP_TITLE, formatFinanceGroupSummary, formatIssueSupportGroupSummary, formatLeaveGroupSummary, isDeterministicGroupKind, ISSUE_SUPPORT_GROUP_TITLE, LEAVE_DAY_THRESHOLD, LEAVE_GROUP_TITLE, normalizeCreators, } from './businessRules.js';
2
+ import { renderGroupSummaryTemplate, } from './businessRules.js';
3
3
  import { asRecord, readBoundedInt, } from './todoSmartConvert.core.js';
4
- import { focusAppliesToKind, isExpiringTodayRow, isExpiredUserOverTime, PRIORITY_GROUP_ID, PRIORITY_GROUP_TITLE, runGroup, GROUP_MIN_ITEMS_FOR_SUBGROUP, } from './todoSmartConvert.group.js';
4
+ import { PRIORITY_GROUP_ID, PRIORITY_GROUP_TITLE, runGroup, GROUP_MIN_ITEMS_FOR_SUBGROUP, } from './todoSmartConvert.group.js';
5
5
  import { fetchDetailFacts, stampsOfRows } from './todoSmartConvert.detail.js';
6
+ import { activeRuleFor, evaluateFocusKeywords, primeRules } from './aigroupRules.js';
6
7
  import { openSummaryCache } from './summaryCache.js';
7
8
  const DEFAULT_REQUEST_SUMMARY_MAX = 50;
8
9
  /** 把一批条目包成「单独展示」项(优先组、以及普通组里没同类可合并的条目都用它)。 */
@@ -38,9 +39,9 @@ export function readAiGroups(value) {
38
39
  if (!record) {
39
40
  throw workflowValidationError('ai_groups_invalid', `${label} 必须是对象 { groupId, title, requestIds }`, { retryAllowed: false });
40
41
  }
41
- const unknownKeys = Object.keys(record).filter((key) => !['groupId', 'title', 'requestIds', 'seq'].includes(key));
42
+ const unknownKeys = Object.keys(record).filter((key) => !['groupId', 'title', 'requestIds'].includes(key));
42
43
  if (unknownKeys.length > 0) {
43
- throw workflowValidationError('ai_groups_invalid', `${label} 不接受字段:${unknownKeys.join(', ')};只支持 groupId / title / requestIds / seq`, { retryAllowed: false, details: { unknownKeys } });
44
+ throw workflowValidationError('ai_groups_invalid', `${label} 不接受字段:${unknownKeys.join(', ')};只支持 groupId / title / requestIds`, { retryAllowed: false, details: { unknownKeys } });
44
45
  }
45
46
  const groupId = typeof record.groupId === 'string' ? record.groupId.trim() : '';
46
47
  if (!groupId) {
@@ -51,25 +52,11 @@ export function readAiGroups(value) {
51
52
  throw workflowValidationError('ai_groups_invalid', `${label}.title 必填:这是 AI 给该子分组起的名字,结构里要原样输出`, { retryAllowed: false });
52
53
  }
53
54
  const hasIds = Array.isArray(record.requestIds) && record.requestIds.length > 0;
54
- const hasSeq = Array.isArray(record.seq) && record.seq.length > 0;
55
- if (hasIds && hasSeq) {
56
- throw workflowValidationError('ai_groups_invalid', `${label} 不能同时给 requestIds 与 seq:用 seq(组内序号,推荐)或 requestIds(显式 ID)二选一`, { retryAllowed: false });
57
- }
58
- if (!hasIds && !hasSeq) {
59
- throw workflowValidationError('ai_groups_invalid', `${label} 需要 requestIds 或 seq(组内 1-based 序号,见 collect 的 groupingBrief)`, { retryAllowed: false });
60
- }
61
- const seqNumbers = [];
62
- if (hasSeq) {
63
- for (const item of record.seq) {
64
- const n = typeof item === 'number' ? item : Number(item);
65
- if (!Number.isInteger(n) || n < 1) {
66
- throw workflowValidationError('ai_groups_invalid', `${label}.seq 只接受 ≥1 的整数序号,收到 ${String(item)}`, { retryAllowed: false });
67
- }
68
- seqNumbers.push(n);
69
- }
55
+ if (!hasIds) {
56
+ throw workflowValidationError('ai_groups_invalid', `${label} 必须给 requestIds(非空的 requestId 字符串数组)`, { retryAllowed: false });
70
57
  }
71
58
  const ids = [];
72
- for (const item of (hasIds ? record.requestIds : [])) {
59
+ for (const item of record.requestIds) {
73
60
  const id = typeof item === 'string' ? item.trim() : typeof item === 'number' ? String(item) : '';
74
61
  if (!id) {
75
62
  throw workflowValidationError('ai_groups_invalid', `${label}.requestIds 只接受非空的 requestId 字符串`, { retryAllowed: false });
@@ -77,7 +64,7 @@ export function readAiGroups(value) {
77
64
  ids.push(id);
78
65
  }
79
66
  const bucket = map.get(groupId) ?? [];
80
- bucket.push({ groupId, title, requestIds: [...new Set(ids)], seq: [...new Set(seqNumbers)] });
67
+ bucket.push({ groupId, title, requestIds: [...new Set(ids)] });
81
68
  map.set(groupId, bucket);
82
69
  });
83
70
  return map;
@@ -109,42 +96,32 @@ function rowMetricsOf(row, metrics) {
109
96
  * 金额算不出来时(表单没有金额字段、标题里也没有 `X元`)退回「标题清单」模板,
110
97
  * 而不是编一个 `¥0 ~ ¥0` —— 半截统计比不给更糟。
111
98
  */
112
- function deterministicGroupSummary(kind, rows, metrics, restCount = 0) {
113
- if (!isDeterministicGroupKind(kind))
114
- return null;
115
- const creators = normalizeCreators(rows.map((row) => row.createrName));
116
- // 客户问题支持类:固定「N 条已过期或今日到期 + 其余 M 条单条」口径,条数由 CLI 算。
117
- if (kind === 'issue-support') {
118
- return formatIssueSupportGroupSummary({ count: rows.length, restCount, creators });
99
+ /**
100
+ * 取一条流程在某个「指标名」上的值(规则 `focusKeyword` 求值用)。
101
+ *
102
+ * - `sysArg=true`:指标名就是 `getTodoListData` 返回的属性名(如 `planFinishDatetime`);
103
+ * - `sysArg=false`:先映射到结构化指标(金额 / 天数 / 假别),再退到「详情字段名包含指标名」;
104
+ * 都取不到返回 `undefined` —— 调用方按「不命中 + warning」处理(2026-10-28 用户定:
105
+ * 正则提不到就平铺并告警,不做 AI 兜底)。
106
+ */
107
+ function metricValueOf(row, metric, sysArg, facts, metrics) {
108
+ if (sysArg) {
109
+ const key = Object.keys(row).find((item) => item.toLowerCase() === metric.toLowerCase());
110
+ return key ? row[key] : undefined;
119
111
  }
120
- if (kind === 'leave') {
121
- const items = rows.map((row) => rowMetricsOf(row, metrics));
122
- return formatLeaveGroupSummary({
123
- count: rows.length,
124
- leaveTypes: items.map((item) => item.leaveType),
125
- creators,
126
- });
112
+ const rowMetrics = metrics?.get(row.requestId);
113
+ if (rowMetrics) {
114
+ if (/金额|费用|价|合计/u.test(metric) && typeof rowMetrics.amount === 'number')
115
+ return rowMetrics.amount;
116
+ if (/天数|时长/u.test(metric) && typeof rowMetrics.leaveDays === 'number')
117
+ return rowMetrics.leaveDays;
118
+ if (/假别|假种|假期/u.test(metric) && typeof rowMetrics.leaveType === 'string')
119
+ return rowMetrics.leaveType;
127
120
  }
128
- if (kind === 'finance') {
129
- const items = rows.map((row) => rowMetricsOf(row, metrics));
130
- const amounts = items
131
- .map((item) => item.amount)
132
- .filter((value) => typeof value === 'number' && value > 0);
133
- // 防御:能进阈值组的前提就是「金额 ≤100」,所以这里必然有金额;
134
- // 万一真的一个都没有,留空交 AI 写,绝不编一个 ¥0 ~ ¥0。
135
- if (amounts.length === 0)
136
- return null;
137
- const sum = Number(amounts.reduce((total, value) => total + value, 0).toFixed(2));
138
- return formatFinanceGroupSummary({
139
- min: Math.min(...amounts),
140
- max: Math.max(...amounts),
141
- sum,
142
- categories: items.map((item) => item.feeCategory),
143
- creators,
144
- });
145
- }
146
- // 其他类不再产汇总项(isDeterministicGroupKind 已排除),走到这里直接返回 null。
147
- return null;
121
+ const fact = facts?.get(row.requestId);
122
+ const field = (fact?.fields ?? []).find((item) => String(item.fieldName ?? '').includes(metric));
123
+ const value = field?.value;
124
+ return value === undefined || String(value).trim() === '' ? undefined : value;
148
125
  }
149
126
  /**
150
127
  * 最终结构里的 `totalCount`:**去重后的待办条数**(= collect 阶段取到的全部条数)。
@@ -229,6 +206,8 @@ export async function runSmartGroup(args, env, options = {}) {
229
206
  // markdown 是中间态的东西,最终结构里没有,相关提示一并丢掉,免得误导调用方。
230
207
  const warnings = (base.warnings ?? [])
231
208
  .filter((text) => !text.startsWith('markdown 只展示前'));
209
+ // aigroup 规则(default.json + user-intents.json):每个 operation 只加载一次。
210
+ warnings.push(...primeRules(env.ctx).warnings);
232
211
  if (skipDetail) {
233
212
  warnings.push('withDetailContext=false:本次未拉取任何详情——请假/财务阈值改为按标题识别、跳过整组空判定,也不产 summaryBatches');
234
213
  }
@@ -243,7 +222,7 @@ export async function runSmartGroup(args, env, options = {}) {
243
222
  topPriority: true,
244
223
  title: group.title,
245
224
  count: group.count,
246
- // 只有优先组默认展开,其余分组一律收缩。
225
+ // 大分组(含优先组)默认展开;只有汇总项(ai二次分组)收缩。
247
226
  defaultExpanded: true,
248
227
  displayItems: requestItemsOf(group.requestDatas),
249
228
  };
@@ -256,126 +235,79 @@ export async function runSmartGroup(args, env, options = {}) {
256
235
  * 注意:不存在 `other` 兜底汇总项(2026-10-26 用户定稿),漏分 / 子组解散一律落到这里。
257
236
  */
258
237
  const singles = [];
259
- const kind = detectBusinessKind(group.title);
260
- const rawPlans = plans?.get(group.id) ?? [];
261
- /** 意图是否适用于本组(Q15 专属/通用意图表):不适用 = 按「没有意图」处理。 */
262
- const groupHasFocus = hasFocus && focusAppliesToKind(groupFocus, kind);
263
- /**
264
- * aiGroups 什么时候被采纳:
265
- * - 意图适用于本组:只有本组有计划才分;没给计划 = 视为无意图,退回确定性规则(Q8)。
266
- * - 内部留言:有没有意图都采纳(无意图按流程标题分、有意图按详情+意图分,素材由 collect 准备)。
267
- * - 其余类型:意图不适用就一律忽略(Q9)。
268
- */
269
- const plansAllowed = groupHasFocus || kind === 'internal-message';
270
- const groupPlans = plansAllowed ? rawPlans : [];
271
- if (rawPlans.length > 0 && !plansAllowed) {
272
- // 没有意图时不要说成「意图不适用于本类型」——那是误导,真实原因是本类型默认不做二次分组。
273
- warnings.push(hasFocus
274
- ? `「${group.title}」意图「${groupFocus}」不适用于本类型,已忽略 aiGroups,按无意图规则处理`
275
- : `「${group.title}」${BUSINESS_KIND_LABEL[kind]}默认不按标题二次分组,已忽略 aiGroups(要按侧重点分组请在 groupFocus 里说明)`);
276
- }
277
238
  /** 该组整体平铺(不进入 ai二次分组)。 */
278
239
  const flatReturn = () => ({
279
240
  id: group.id,
280
241
  topPriority: false,
281
242
  title: group.title,
282
243
  count: group.count,
283
- defaultExpanded: false,
244
+ defaultExpanded: true,
284
245
  displayItems: requestItemsOf(group.requestDatas),
285
246
  });
286
- // D0 满意度类:不做 ai二次分组,也不与 AI 交互。
287
- if (kind === 'satisfaction') {
247
+ const rawPlans = plans?.get(group.id) ?? [];
248
+ /** 本组生效规则(`aigroup` JSON):没有 `aiGroup` ⇒ 不做二次分组。 */
249
+ const rule = activeRuleFor(group.title);
250
+ if (!rule?.aiGroup) {
288
251
  if (rawPlans.length > 0)
289
- warnings.push(`「${group.title}」满意度类不做 ai二次分组,已忽略 aiGroups`);
290
- warnings.push(`「${group.title}」属于满意度类,全部平铺单条;summary 由标题兜底(不调 AI)`);
252
+ warnings.push(`「${group.title}」未配置 aiGroup,已忽略 aiGroups`);
253
+ if (!rule?.needSummary)
254
+ warnings.push(`「${group.title}」不需要 AI 摘要,summary 用标题兜底`);
255
+ warnings.push(`「${group.title}」未配置 ai二次分组,${group.requestDatas.length} 条平铺`);
291
256
  return flatReturn();
292
257
  }
293
- // D1 硬门槛:组内 <3 条一律平铺(即使给了 aiGroups 也忽略,不与 AI 交互)。
258
+ const groupRule = rule.aiGroup.groupRule;
259
+ const focusMode = groupRule.focus === true;
260
+ const groupPlans = rawPlans;
261
+ // 全局硬门槛:组内 <3 条一律平铺(规则/计划都覆盖不了)。
294
262
  if (group.requestDatas.length < GROUP_MIN_ITEMS_FOR_SUBGROUP) {
295
263
  if (rawPlans.length > 0)
296
- warnings.push(`「${group.title}」不足 3 条(硬门槛),忽略 aiGroups 并平铺`);
264
+ warnings.push(`「${group.title}」不足 ${GROUP_MIN_ITEMS_FOR_SUBGROUP} 条(硬门槛),忽略 aiGroups 并平铺`);
297
265
  return flatReturn();
298
266
  }
299
- // D2 意图适用但没给本组计划 = 视为没有意图(Q8:按阈值组/过期组等确定性规则)。
300
- // 但**内部留言例外**:它 ≥3 条时没有「平铺」这个第三态(见下面的强制校验)。
301
- if (groupHasFocus && groupPlans.length === 0 && kind !== 'internal-message') {
302
- warnings.push(`「${group.title}」意图适用但模型未给本组计划,按无意图处理`);
303
- }
304
- /**
305
- * 内部留言(Q1/Q16,2026-10-27 用户明确):**≥3 条必然做 ai二次分组**,没有「平铺」这个第三态。
306
- *
307
- * 只有两种维度,且都必须由模型给出计划(CLI 不替模型分组):
308
- * - 侧重点适用于内部留言 ⇒ 按「侧重点 + 流程详情数据」分组;
309
- * - 没有侧重点,或侧重点不适用 ⇒ **按流程标题分组**。
310
- *
311
- * 「没给计划」不是合法输入:静默平铺会把 200+ 条留言堆成一片(五年那批实测 223 条全平铺),
312
- * 而且 warning 会被误读成「技术上无法分组」。这里直接要求补计划,保持规则单一确定性。
313
- *
314
- * 注意:门槛在前面 D1 已经判过(<3 条直接平铺),所以走到这里一定 ≥3 条。
315
- */
316
- if (kind === 'internal-message' && groupPlans.length === 0) {
317
- throw workflowValidationError('ai_groups_required', `「${group.title}」属于内部留言且不少于 ${GROUP_MIN_ITEMS_FOR_SUBGROUP} 条,必须给本组 aiGroups:`
318
- + (groupHasFocus
319
- ? `用户侧重点「${groupFocus}」适用于内部留言,请按侧重点结合流程详情数据分组`
320
- : '当前没有适用于内部留言的侧重点,请**按流程标题**分组(同主题的通知/公告归到同一子组)')
321
- + `;可用 groupId:${group.id},组内序号 1..${group.requestDatas.length}`, {
267
+ /** `focus:false` 必须给计划(没有「平铺」这个第三态);`focus:true` 没计划时按 focusKeyword 确定性成组。 */
268
+ if (!focusMode && groupPlans.length === 0) {
269
+ const groupingPrompt = typeof groupRule.groupingPrompt === 'string' ? groupRule.groupingPrompt.trim() : '';
270
+ throw workflowValidationError('ai_groups_required', `「${group.title}」按规则要求做 AI 二次分组(focus=false,上下文:${groupRule.aiContext ?? '流程标题'}),`
271
+ + `必须给本组 aiGroups;可用 groupId:${group.id},requestIds 取本组 ${group.requestDatas.length} 条`
272
+ + (groupingPrompt ? `。分组口径(groupingPrompt):${groupingPrompt}` : ''), {
322
273
  retryAllowed: true,
323
- details: { groupId: group.id, count: group.requestDatas.length, groupFocus: groupHasFocus ? groupFocus : '' },
274
+ details: {
275
+ groupId: group.id,
276
+ count: group.requestDatas.length,
277
+ aiContext: groupRule.aiContext ?? '',
278
+ ...(groupingPrompt ? { groupingPrompt } : {}),
279
+ },
324
280
  });
325
281
  }
326
282
  if (groupPlans.length === 0) {
327
- // 无意图(或退回无意图):只有四类确定性规则,其余一律平铺。
328
- if (kind === 'leave' || kind === 'finance') {
329
- // 请假/财务:最多一个固定组;指标**只来自流程详情数据**,取不到的条目单独平铺(Q3/Q11)。
330
- const eligible = [];
331
- let unknown = 0;
332
- for (const row of group.requestDatas) {
333
- const metric = rowMetricsOf(row, metrics);
334
- const value = kind === 'leave' ? metric.leaveDays : metric.amount;
335
- const hit = kind === 'leave'
336
- ? (typeof value === 'number' && value <= LEAVE_DAY_THRESHOLD)
337
- : (typeof value === 'number' && value > 0 && value <= FINANCE_AMOUNT_THRESHOLD);
338
- if (hit)
339
- eligible.push(row);
340
- else {
341
- singles.push(row);
342
- if (typeof value !== 'number')
343
- unknown += 1;
344
- }
345
- }
346
- if (eligible.length > 0) {
347
- subGroups.push({
348
- title: kind === 'leave' ? LEAVE_GROUP_TITLE : FINANCE_GROUP_TITLE,
349
- requestDatas: eligible,
350
- });
351
- warnings.push(`「${group.title}」按${kind === 'leave' ? '请假类 1天以内' : '财务类 100元以内'}确定性汇总,其余条目平铺`);
352
- }
353
- if (unknown > 0) {
354
- warnings.push(`「${group.title}」有 ${unknown} 条流程详情数据取不到${kind === 'leave' ? '请假天数' : '报销金额'},不做 ai二次分组、按单条平铺`);
355
- }
283
+ // 规则 focus=true 且没有计划:按 focusKeyword 确定性成一组(与业务类型无关)。
284
+ const conditions = groupRule.focusKeyword ?? [];
285
+ const sysArg = groupRule.sysArg === true;
286
+ const hit = [];
287
+ const missing = new Set();
288
+ for (const row of group.requestDatas) {
289
+ const resolve = (metric) => {
290
+ const value = metricValueOf(row, metric, sysArg, facts, metrics);
291
+ if (value === undefined)
292
+ missing.add(metric);
293
+ return value;
294
+ };
295
+ if (conditions.length > 0 && evaluateFocusKeywords(conditions, resolve))
296
+ hit.push(row);
297
+ else
298
+ singles.push(row);
356
299
  }
357
- else if (kind === 'issue-support') {
358
- // 问题支持类:判据是**原始列表数据**的超时字段 userOverTime(Q4);无命中则整组平铺。
359
- const urgentRows = [];
360
- const rest = [];
361
- for (const row of group.requestDatas) {
362
- if (isExpiredUserOverTime(row.userOverTime) || isExpiringTodayRow(row))
363
- urgentRows.push(row);
364
- else
365
- rest.push(row);
366
- }
367
- singles.push(...rest);
368
- if (urgentRows.length > 0) {
369
- subGroups.push({ title: ISSUE_SUPPORT_GROUP_TITLE, requestDatas: urgentRows });
370
- warnings.push(`「${group.title}」按客户问题支持类口径:${urgentRows.length} 条已过期或今日到期,其余 ${rest.length} 条平铺`);
371
- }
372
- else {
373
- warnings.push(`「${group.title}」没有已过期或今日到期的条目(userOverTime 未命中),全部平铺`);
374
- }
300
+ const conditionText = conditions.map((condition) => condition.join(' ')).join(' 且 ');
301
+ const title = groupRule.groupTitle?.trim() || conditionText || '汇总';
302
+ if (hit.length > 0) {
303
+ subGroups.push({ title, requestDatas: hit });
304
+ warnings.push(`「${group.title}」按规则「${title}」命中 ${hit.length} 条(${conditionText}),其余 ${group.requestDatas.length - hit.length} 条平铺`);
375
305
  }
376
306
  else {
377
- singles.push(...group.requestDatas);
378
- warnings.push(`「${group.title}」默认不做 ai二次分组,${group.requestDatas.length} 条平铺`);
307
+ warnings.push(`「${group.title}」规则「${title}」命中 0 条${conditionText ? `(${conditionText})` : ''},全部平铺`);
308
+ }
309
+ if (missing.size > 0) {
310
+ warnings.push(`「${group.title}」指标取不到:${[...missing].join('、')},相关条目按单条平铺;请检查规则里的指标名`);
379
311
  }
380
312
  }
381
313
  else {
@@ -385,22 +317,12 @@ export async function runSmartGroup(args, env, options = {}) {
385
317
  * 模型若拿它当子组名,CLI 必须逐条核阈值——否则 2000 元的报销会出现在「100元以内」里。
386
318
  * 超阈值的条目剔出、转单条展示(一条都不丢),并点名。
387
319
  */
388
- const reservedTitle = kind === 'leave'
389
- ? LEAVE_GROUP_TITLE
390
- : kind === 'finance' ? FINANCE_GROUP_TITLE : '';
320
+ /** 保留标题 = 规则里的 groupTitle:计划若用它当子组名,必须逐条核 focusKeyword。 */
321
+ const reservedTitle = groupRule.groupTitle ?? '';
391
322
  const droppedByThreshold = [];
392
323
  for (const plan of groupPlans) {
393
324
  const datas = [];
394
- // `seq`(组内 1-based 序号)→ requestId:映射口径与 `groupingBrief[].items[].idx` 一致。
395
- const planRequestIds = plan.requestIds.length > 0
396
- ? plan.requestIds
397
- : plan.seq.map((n) => {
398
- const row = group.requestDatas[n - 1];
399
- if (!row) {
400
- throw workflowValidationError('ai_group_seq_out_of_range', `aiGroups 给「${group.title}」的 seq 里有 ${n},但该组只有 ${group.requestDatas.length} 条(序号从 1 开始)`, { retryAllowed: true, details: { groupId: group.id, seq: n, count: group.requestDatas.length } });
401
- }
402
- return row.requestId;
403
- });
325
+ const planRequestIds = plan.requestIds;
404
326
  for (const requestId of planRequestIds) {
405
327
  const data = byRequestId.get(requestId);
406
328
  if (!data) {
@@ -417,13 +339,11 @@ export async function runSmartGroup(args, env, options = {}) {
417
339
  if (reservedTitle && plan.title === reservedTitle) {
418
340
  const kept = [];
419
341
  const dropped = [];
342
+ const conditions = groupRule.focusKeyword ?? [];
343
+ const sysArg = groupRule.sysArg === true;
420
344
  for (const row of datas) {
421
- const metric = rowMetricsOf(row, metrics);
422
- const value = kind === 'leave' ? metric.leaveDays : metric.amount;
423
- const hit = kind === 'leave'
424
- ? (typeof value === 'number' && value <= LEAVE_DAY_THRESHOLD)
425
- : (typeof value === 'number' && value > 0 && value <= FINANCE_AMOUNT_THRESHOLD);
426
- (hit ? kept : dropped).push(row);
345
+ const resolve = (metric) => metricValueOf(row, metric, sysArg, facts, metrics);
346
+ (conditions.length > 0 && evaluateFocusKeywords(conditions, resolve) ? kept : dropped).push(row);
427
347
  }
428
348
  if (dropped.length > 0) {
429
349
  droppedByThreshold.push(...dropped);
@@ -454,10 +374,50 @@ export async function runSmartGroup(args, env, options = {}) {
454
374
  singles.push(...uncovered);
455
375
  warnings.push(`「${group.title}」有 ${uncovered.length} 条未被 AI 分组,按单条平铺(不造 other 子组)`);
456
376
  }
377
+ /**
378
+ * `focus=true` 的组**最多只留一个汇总项**(2026-10-28 用户定:侧重点产出多个子组时,
379
+ * 只保留涉及数据最多的那个,其余条目平铺——多组会造成重复展示、批量处理口径有争议)。
380
+ */
381
+ if (focusMode && subGroups.length > 1) {
382
+ const sorted = [...subGroups].sort((a, b) => b.requestDatas.length - a.requestDatas.length);
383
+ const [keep] = sorted;
384
+ const dropped = sorted.slice(1);
385
+ const droppedCount = dropped.reduce((sum, sub) => sum + sub.requestDatas.length, 0);
386
+ for (const sub of dropped)
387
+ singles.push(...sub.requestDatas);
388
+ subGroups.length = 0;
389
+ subGroups.push(keep);
390
+ warnings.push(`「${group.title}」侧重点产出 ${sorted.length} 个子组,按规则只保留条数最多的「${keep.title}」`
391
+ + `(${keep.requestDatas.length} 条),其余 ${droppedCount} 条改单条平铺`);
392
+ }
457
393
  }
394
+ /** 组摘要:规则 `needAI:false` 时用 JSON `template` 渲染,否则留空交 AI。 */
395
+ const renderSubGroupSummary = (sub) => {
396
+ const groupSummary = rule.aiGroup?.groupSummary;
397
+ if (!groupSummary || groupSummary.needAI || !groupSummary.template)
398
+ return '';
399
+ if (groupRule.groupTitle && sub.title !== groupRule.groupTitle)
400
+ return '';
401
+ const rows = sub.requestDatas;
402
+ const items = rows.map((row) => rowMetricsOf(row, metrics));
403
+ const amounts = items
404
+ .map((item) => item.amount)
405
+ .filter((value) => typeof value === 'number' && value > 0);
406
+ if (groupSummary.template.includes('{{min}}') && amounts.length === 0)
407
+ return '';
408
+ return renderGroupSummaryTemplate({
409
+ template: groupSummary.template,
410
+ tail: groupSummary.tail,
411
+ count: rows.length,
412
+ restCount: singles.length,
413
+ categories: items.map((item) => item.leaveType ?? item.feeCategory),
414
+ amounts,
415
+ creators: rows.map((row) => row.createrName),
416
+ });
417
+ };
458
418
  // 条数降序;sort 稳定,同条数保持 AI 给出的先后顺序。
459
419
  // 有侧重点时沿用模型顺序;没有侧重点时按条数降序。
460
- if (!groupHasFocus)
420
+ if (!focusMode)
461
421
  subGroups.sort((a, b) => b.requestDatas.length - a.requestDatas.length);
462
422
  const groupItems = subGroups.map((sub, index) => ({
463
423
  // 键顺序照新目标结构:type → id → title → count → summary → defaultExpanded → requestDatas
@@ -466,11 +426,7 @@ export async function runSmartGroup(args, env, options = {}) {
466
426
  title: sub.title,
467
427
  count: sub.requestDatas.length,
468
428
  // 组摘要口径不变(Q4):只有 CLI 自己造的三类固定组用固定文案;AI 子组一律留空交 groupSummary。
469
- summary: ((kind === 'leave' && sub.title === LEAVE_GROUP_TITLE)
470
- || (kind === 'finance' && sub.title === FINANCE_GROUP_TITLE)
471
- || (kind === 'issue-support' && sub.title === ISSUE_SUPPORT_GROUP_TITLE))
472
- ? (deterministicGroupSummary(kind, sub.requestDatas, metrics, singles.length) ?? '')
473
- : '',
429
+ summary: renderSubGroupSummary(sub),
474
430
  // 汇总项自身默认收缩(与大分组的展开状态互相独立)
475
431
  defaultExpanded: false,
476
432
  requestDatas: sub.requestDatas,
@@ -480,7 +436,8 @@ export async function runSmartGroup(args, env, options = {}) {
480
436
  topPriority: false,
481
437
  title: group.title,
482
438
  count: group.count,
483
- defaultExpanded: false,
439
+ // 大分组默认展开(与汇总项自身的收缩状态互相独立)
440
+ defaultExpanded: true,
484
441
  // 汇总项在前、单独条目在后。
485
442
  displayItems: [...groupItems, ...requestItemsOf(singles)],
486
443
  };
@@ -1,10 +1,15 @@
1
+ import { activeRuleFor } from './aigroupRules.js';
1
2
  import { workflowValidationError } from '../../errors.js';
2
3
  import { asRecord, readBoundedInt } from './todoSmartConvert.core.js';
3
4
  import { orderGroupsByIntent, readGroupOrder } from './todoSmartConvert.group.js';
4
- import { detectBusinessKind, needsRequestSummary, usesTitleSummary } from './businessRules.js';
5
+ import { detectBusinessKind, needsRequestSummary } from './businessRules.js';
5
6
  import { openSummaryCache } from './summaryCache.js';
6
7
  import { loadDetailContextProvider, stampsOfRows } from './todoSmartConvert.detail.js';
7
8
  import { requestItemsOf, rowsOfGroup, runSmartGroup, totalTodoCount, } from './todoSmartConvert.smartGroup.js';
9
+ /** 该组是否需要 AI 生成单条摘要(规则 needSummary 决定,默认需要)。 */
10
+ function needAiSummary(workflowName) {
11
+ return activeRuleFor(workflowName)?.needSummary ?? true;
12
+ }
8
13
  const DEFAULT_MAX_CHARS_PER_BATCH = 24_000;
9
14
  const DEFAULT_MAX_ITEMS_PER_BATCH = 100;
10
15
  const DEFAULT_REQUEST_SUMMARY_MAX = 50;
@@ -101,7 +106,7 @@ export function validateFinalGroups(value) {
101
106
  const count = displayItems.reduce((sum, item) => sum + (item.type === 'group' ? item.requestDatas.length : 1), 0);
102
107
  return topPriority
103
108
  ? { id, topPriority: true, title, count, defaultExpanded: true, displayItems }
104
- : { id, topPriority: false, title, count, defaultExpanded: false, displayItems };
109
+ : { id, topPriority: false, title, count, defaultExpanded: true, displayItems };
105
110
  });
106
111
  }
107
112
  /**
@@ -129,7 +134,7 @@ export function collectSummaryUnits(groups) {
129
134
  const units = [];
130
135
  for (const group of groups) {
131
136
  if (group.topPriority) {
132
- if (!needsRequestSummary(detectBusinessKind(group.title)))
137
+ if (!needAiSummary(group.title))
133
138
  continue;
134
139
  units.push({
135
140
  groupId: group.id,
@@ -185,7 +190,7 @@ export async function runSummaryContext(args, env, prefetched) {
185
190
  const groups = await readFinalGroups(args, env);
186
191
  const units = collectSummaryUnits(groups);
187
192
  const skippedSatisfaction = groups
188
- .filter((group) => usesTitleSummary(detectBusinessKind(group.title)))
193
+ .filter((group) => !needAiSummary(group.title))
189
194
  .reduce((sum, group) => sum + rowsOfGroup(group).length, 0);
190
195
  // 优先组是叠加视图 ⇒ 同一条待办会在两个位置各出现一次,统计口径必须去重。
191
196
  const requestIds = [...new Set(units.flatMap((unit) => unit.rows.map((row) => row.requestId)))].filter(Boolean);
@@ -394,7 +399,7 @@ export async function runSummary(args, env) {
394
399
  // 满意度类不走 AI、也不读缓存:直接用标题当摘要,保证 `summary` 不为空。
395
400
  let titleSummary = 0;
396
401
  for (const group of groups) {
397
- if (usesTitleSummary(detectBusinessKind(group.title))) {
402
+ if (!needAiSummary(group.title)) {
398
403
  for (const row of rowsOfGroup(group)) {
399
404
  if ((row.summary ?? '').trim())
400
405
  continue;
@@ -470,7 +475,7 @@ export async function runSummary(args, env) {
470
475
  }
471
476
  };
472
477
  for (const group of groups) {
473
- if (!needsRequestSummary(detectBusinessKind(group.title)))
478
+ if (!needAiSummary(group.title))
474
479
  continue;
475
480
  if (group.topPriority) {
476
481
  applyAll(rowsOfGroup(group));
@@ -499,7 +504,7 @@ export async function runSummary(args, env) {
499
504
  warnings.push(`有 ${prefilledKept} 条沿用本地摘要缓存(未重新生成)`);
500
505
  }
501
506
  const titleSummaryCount = groups
502
- .filter((group) => usesTitleSummary(detectBusinessKind(group.title)))
507
+ .filter((group) => !needAiSummary(group.title))
503
508
  .reduce((sum, group) => sum + rowsOfGroup(group).length, 0);
504
509
  if (titleSummaryCount > 0) {
505
510
  warnings.push(`有 ${titleSummaryCount} 条满意度类待办不调用 AI,summary 直接使用条目标题(保证非空)`);
@@ -59,6 +59,12 @@ function clarificationEnvelope(error, operation) {
59
59
  ? details.clarificationQuestion
60
60
  : error.message;
61
61
  const reason = typeof details.clarificationReason === 'string' ? details.clarificationReason : '';
62
+ // 必填入参缺失(当前只有 `workflow_required` 一档)时,把「缺了哪个键、该按什么形态补」一并下发:
63
+ // 模型原先只能看到「请提供完整名称」,无法区分「用户没说要发起什么」与「我键名写错了」。
64
+ const missingInput = Array.isArray(details.missingInput) ? details.missingInput.map(String) : [];
65
+ const requiredInput = details.requiredInput && typeof details.requiredInput === 'object'
66
+ ? details.requiredInput
67
+ : null;
62
68
  return workflowSuccess(operation, {
63
69
  type: 'workflowClarification',
64
70
  status: 'NEEDS_CLARIFICATION',
@@ -66,6 +72,8 @@ function clarificationEnvelope(error, operation) {
66
72
  clarificationKeyword: details.clarificationKeyword ?? null,
67
73
  clarificationQuestion: question,
68
74
  ...(reason ? { clarificationReason: reason } : {}),
75
+ ...(missingInput.length ? { missingInput } : {}),
76
+ ...(missingInput.length && requiredInput ? { requiredInput } : {}),
69
77
  candidates,
70
78
  candidateCount: candidates.length,
71
79
  note: clarificationNote(reason, candidates.length),
@@ -80,10 +88,24 @@ function clarificationEnvelope(error, operation) {
80
88
  * - `no_create_right` —— 流程**存在**但当前账号没有发起权限,**绝不能**说成"不存在"
81
89
  * (那正是把"没权限"伪装成"没有这条流程",与项目纪律相反)。
82
90
  *
83
- * 无 `reason` 时(名称解析 0 / 多命中)保持原文案不变。
91
+ * 2026-10-30 新增一档:
92
+ * - `workflow_required` —— **必填入参没给**(缺键/空数组/空字符串)。它不是「名称匹配不到」,
93
+ * 而是调用方少传了入参;文案必须让模型先认账、再问用户要流程名,而不是让用户去"核对名称"。
94
+ *
95
+ * 无 `reason` 时(名称解析 0 / 多命中 / 只给了填充词)保持原文案不变。
84
96
  */
85
97
  function clarificationNote(reason, candidateCount) {
86
98
  const base = '这不是失败,也**不代表查询已执行**:本次查询已停止,没有返回任何流程数据。';
99
+ // ★ 2026-10-30:`workflow_required` = **必填入参没给**,与「名称匹配不到」性质完全不同。
100
+ // 这里要模型先认账(少传了入参),而不是去问用户「是不是名字打错了」。
101
+ if (reason === 'workflow_required') {
102
+ return '本次命令**没有拿到必填入参 `workflow`**(没写这个键、空数组、空字符串都算没给)——'
103
+ + '命令停在了查询之前:**没有发起任何流程**,也没有去查可发起列表,'
104
+ + '这既不是执行失败,也不代表「没有匹配的流程」。'
105
+ + '请先向用户确认要发起哪个流程(**必须问,不要自己猜,也不要拿相似的名字代替**),'
106
+ + '再按 `requiredInput.workflow` 给出的形态把入参补上、重发同一条命令。'
107
+ + '注意 `--input` 接收的是**文件路径**(`-` 表示标准输入),内联 JSON 要用 `--input-json`。';
108
+ }
87
109
  if (reason === 'workflow_not_exist') {
88
110
  return base + '请如实告知用户:该工作流不存在(可能已删除或被停用),'
89
111
  + '请用户改用该流程的完整名称(或其它可用于检索的关键词)重新指定——**不要向用户索取 ID**。'
package/docs/e10-auth.md CHANGED
@@ -69,7 +69,7 @@ E10 目标地址按“显式 `--base-url` → 专用 `WEAVER_XIAOE_*` E10 配置
69
69
 
70
70
  `auth xiaoe` 会先打印不含凭证的 preflight 摘要,给出 `authMode=xiaoe`、`credentialSourceDetected`、`attempted`、各凭据源检测结果和目标 `baseUrl`。进入脚本分支时会打印 `Trying XiaoE token script credential...`;进入内置 Node 会话分支时会打印 `Trying built-in XiaoE client session credential...`。成功结果和后续 `auth status` 会显示 `credentialSourceStep`、`credentialSourceType`、`credentialSourceLabel`。
71
71
 
72
- 业务命令不会在缺少本地会话时主动建立 XiaoE 登录态,首次使用仍需显式执行 `auth xiaoe`。已有会话被 E10 接口明确拒绝为失效时,公共请求运行时会静默换票并重放原请求一次;不会打印中间登录步骤,不会自动打开浏览器,也不会对流式请求、网络错误、`partial` / `write_uncertain` 做重放。桌面客户端需要 XiaoE 专属业务行为时,Agent 必须显式传全局参数,例如 `weaver-work-cli --runtime-agent xiaoe workflow ...`;网页端由原有环境变量规则自动识别,不要求补这个参数。`--runtime-agent` 只控制运行时行为,不携带也不选择凭据。WorkBuddy、Codex 及其他智能体使用 `auth login --base-url <url> --agent_type <agent_type>` 的浏览器 OAuth 流程。
72
+ 业务命令不会在缺少本地会话时主动建立 XiaoE 登录态,首次使用仍需显式执行 `auth xiaoe`。已有 XiaoE 会话被 E10 接口明确拒绝为失效时,公共请求运行时会根据持久化会话的 `agentType` / `loginSource` 静默换票并重放原请求一次;这条路径不依赖 shared Skill、Agent、`XIAOE_*` 或 `--runtime-agent xiaoe`。它不会打印中间登录步骤,不会自动打开浏览器,也不会对流式请求、网络错误、`partial` / `write_uncertain` 做重放。`--runtime-agent xiaoe` 仅用于其他 XiaoE 专属业务行为,不携带也不选择凭据。WorkBuddy、Codex 及其他智能体首次认证使用 `auth login --base-url <url> --agent_type <agent_type>` 的浏览器 OAuth 流程。
73
73
 
74
74
  无法使用浏览器回调时,可以用 `auth set` 直接写入浏览器中复制到的
75
75
  `ETEAMSID`。命令会调用 `teamsCheck` 解析当前用户、租户和组织信息,并写入
package/docs/ehr.md CHANGED
@@ -56,7 +56,7 @@ weaver-work-cli --json ehr run <operation> --input -
56
56
 
57
57
  CLI 原样透传整个响应对象,不做重命名;取用方按上表判断。
58
58
 
59
- **每页条数**:由服务端按接口独立配置,**`pageSize` 传参无效**(实测):通讯录 100、出勤表格类 20、`worktime`/`leave.report` 10、hr 报表 20、薪酬 `bill.list`/`employee.list` 20、`empchange.list` 100。全量数据一律递增 `current` 翻页。
59
+ **每页条数**:由服务端按接口独立配置,**`pageSize` 传参无效**(实测):通讯录 100、出勤表格类 20、`worktime`/`leave.report` 10、hr 报表 20、薪酬 `bill.list` 20、`empchange.list` 100。全量数据一律递增 `current` 翻页。
60
60
 
61
61
  **成功响应的 `data` 恒存在**:业务数据为 `undefined` 时(如社保福利、调薪记录为空)统一补为 `null`,避免 `JSON.stringify` 省略该键导致调用方需判断键是否存在。
62
62
 
package/docs/fna.md CHANGED
@@ -21,7 +21,7 @@
21
21
  | `fna.expense.reimbursed.sum` | read | POST `expense/center/getSumReimbursedAmountByAi`;body 可空 |
22
22
  | `fna.expense.loan.sum` | read | POST `expense/center/getSumLoanAmountByAi`;只认数字 ID;空 data 自动重试 2 次;已知仅统计首条记录缺陷 |
23
23
  | `fna.expense.prepay.writeoff.sum` | read | POST `expense/center/selectPrepayWriteOffAmountBySkill`;body 可空 |
24
- | `fna.expense.chart` | read | GET `getExpenseChart`(`/api/secondev/fna/`);`userInput` 必填 ≤500 字;`maxRows` 本地截断 1-5000,默认 200;超时 180s;返回 `kind=rows/empty/clarify/blocked/unexpected` |
24
+ | `fna.expense.chart` | read | GET `getExpenseChart`(`/api/secondev/fna/`);`userInput` 必填 ≤500 字;`maxRows` 本地截断 1-5000,默认 200;超时 180s;返回 `kind=rows/empty/clarify/blocked/unexpected`;无权限文案额外返回 `meta.permissionDenied=true` |
25
25
  | `fna.loan.main.page` | read | POST `loanManagementMenu/mainPage` |
26
26
  | `fna.loan.main.list` | read | POST `loanManagementMenu/mainPageList`;`current`/`pageSize`/`menuCode`/`permissionSetCode` 必填 |
27
27
  | `fna.loan.remind.repay.prepare` | write | 校验 displayData 行并预览,不发送 |
@@ -65,6 +65,7 @@ weaver-work-cli --json fna run fna.invoice.writeoff.list --input-json '{"current
65
65
  - `menuCode`/`permissionSetCode` 环境相关,示例值需与环境核对。
66
66
  - 超 2^53 的 requestId 无法用 JS 数字精确表达,保留字符串并提示人工核验(真实环境 ID 一般在安全整数内,已按数字发送)。
67
67
  - `fna.expense.chart`:`code` 恒为 200,必须按 `data` 类型/`kind` 判成败;追问与安全拦截都是字符串 `data`,`meta.messageKindInferred=true` 表示归类为 CLI 推断。实测真实查询 3.3~66.6s,CLI 超时设 180s,禁止自动重试(每次都会消耗模型额度)。不做当前人强制过滤,结果可能是全租户范围;`userInput` 为商品化输入,个人口径必须改用 `fna.expense.reimbursed.sum`。二开包未安装时该 operation 会 404/500,需与其它 `fna.*` 正常但本 operation 失败区分。
68
+ - `fna.expense.chart` 的权限语义:文案含「权限 / 越权」时 CLI 归类为 `clarify` 并置 `meta.permissionDenied=true`(纯权限提示不带「不明确」关键词也照样识别,避免落进 `unexpected` 被误判)。这是**终止信号**,不是换问法信号:Agent 必须直接告知权限边界,禁止改调 `fna.expense.reimbursed.sum`、`fna.budget.*`、借款 / 核销等接口按部门 / 人员 / 金额拼装同一份报表,也禁止自写 SQL 绕过。
68
69
 
69
70
  ## 验证
70
71