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.
- package/README.md +2 -2
- package/dist/chunks/{archive-UMGX72K6.js → archive-XBVH7JU7.js} +2 -2
- package/dist/chunks/{archive-oa-N2IYBLF3.js → archive-oa-MZM3EKFQ.js} +2 -2
- package/dist/chunks/{asset-GLEGKFG5.js → asset-4XO7UOUW.js} +2 -2
- package/dist/chunks/{attachment-content-AQNX5N3S.js → attachment-content-VXSWANAC.js} +3 -2
- package/dist/chunks/{blog-E44OTVDA.js → blog-PGZKOPSC.js} +2 -2
- package/dist/chunks/{caizhilian-DQUFZXZL.js → caizhilian-BH4ZVGUN.js} +2 -2
- package/dist/chunks/{calendar-JKPIRKFD.js → calendar-MXEMTIO4.js} +2 -2
- package/dist/chunks/{chunk-QWY6PGL6.js → chunk-2LV74IWP.js} +361 -56
- package/dist/chunks/chunk-3SO6QL6U.js +73 -0
- package/dist/chunks/chunk-5DQEEGCC.js +229 -0
- package/dist/chunks/{chunk-G3QJULO2.js → chunk-6LLJFKJE.js} +1 -1
- package/dist/chunks/{chunk-C6R3B2ML.js → chunk-BPE37FPB.js} +35 -7
- package/dist/chunks/chunk-BYATLLEF.js +68 -0
- package/dist/chunks/chunk-BYBSCN4X.js +932 -0
- package/dist/chunks/{chunk-CVZ2JOSW.js → chunk-F746NKNF.js} +1 -1
- package/dist/chunks/{chunk-GCG2NMWR.js → chunk-HXJQI5NM.js} +4 -2
- package/dist/chunks/{chunk-YRVDW4WN.js → chunk-I6QGIBDM.js} +2 -2
- package/dist/chunks/{chunk-H7J4DC5R.js → chunk-JCSN3JGM.js} +2 -2
- package/dist/chunks/{chunk-2GA5ZIQL.js → chunk-JNG42JKG.js} +30 -1
- package/dist/chunks/{chunk-DW6AUUKS.js → chunk-JUXVYKII.js} +9 -4
- package/dist/chunks/{chunk-6RKD4DRZ.js → chunk-JYKABYCV.js} +2 -2
- package/dist/chunks/{chunk-6EFWDL7Y.js → chunk-NCOVUAZK.js} +2 -2
- package/dist/chunks/{chunk-E4Z23GHM.js → chunk-QHFANTX7.js} +1114 -904
- package/dist/chunks/{ebuilder-form-V5FG2EHC.js → chunk-QYGO5EE2.js} +22 -1148
- package/dist/chunks/{chunk-IURUFGZK.js → chunk-T72N74ZT.js} +50 -33
- package/dist/chunks/{chunk-H6XSTSLM.js → chunk-TC6MZAO3.js} +137 -204
- package/dist/chunks/{chunk-WKI2FBIQ.js → chunk-VPUTVEMC.js} +737 -341
- package/dist/chunks/{chunk-WSKBSQ2U.js → chunk-VPXOPQJI.js} +2 -2
- package/dist/chunks/chunk-WXFPWCXG.js +774 -0
- package/dist/chunks/{chunk-4YC43QQ4.js → chunk-XMGSYD6S.js} +4 -4
- package/dist/chunks/conflict-JAB2LJEZ.js +21 -0
- package/dist/chunks/continuation-RN6PZN2M.js +11 -0
- package/dist/chunks/contract-XI2EEIZU.js +9110 -0
- package/dist/chunks/cowork-SCPGLBA4.js +2527 -0
- package/dist/chunks/ebuilder-form-EYLHNOTA.js +121 -0
- package/dist/chunks/{ehr-HMFY6VIL.js → ehr-EAP77IMT.js} +1344 -27
- package/dist/chunks/{esb-ZYSVB7SG.js → esb-2TGGXEE7.js} +4 -4
- package/dist/chunks/{fna-B7RKPAZD.js → fna-2X7ZE7BU.js} +13 -8
- package/dist/chunks/{host-OCH2BCCV.js → host-C6EL2RKL.js} +3 -3
- package/dist/chunks/{host-45WED2KK.js → host-I4NRK4MX.js} +3 -3
- package/dist/chunks/{host-I4Z6BFYX.js → host-Q4LZGCRU.js} +3 -3
- package/dist/chunks/host-UEI5FGDM.js +23 -0
- package/dist/chunks/{host-KNZ52LJZ.js → host-XCLDDXKQ.js} +3 -3
- package/dist/chunks/{hrm-2KAZGUHI.js → hrm-YXERJM76.js} +3 -3
- package/dist/chunks/{im-ACJJWH4U.js → im-A4VZN2TQ.js} +88 -12
- package/dist/chunks/{invoice-ZK3WCX2U.js → invoice-2Z6YPMIS.js} +3 -3
- package/dist/chunks/{jiuchuanhui-5PQD3IL2.js → jiuchuanhui-AR5GMKZB.js} +2 -2
- package/dist/chunks/{lazy-dispatch-JQXBW3GM.js → lazy-dispatch-QXW7P4X3.js} +4 -4
- package/dist/chunks/{mail-S2HDDXO2.js → mail-7GZ6CPBJ.js} +45 -24
- package/dist/chunks/{meeting-QVN3I37L.js → meeting-3NMGA3MB.js} +278 -43
- package/dist/chunks/{okr-QNG4VC2Q.js → okr-BZ6XUOAG.js} +2 -2
- package/dist/chunks/operations-5QL3AECO.js +11 -0
- package/dist/chunks/{plan-A25EMW6U.js → plan-DR5CMUYP.js} +2 -2
- package/dist/chunks/{project-G6XV66DZ.js → project-6J63MK33.js} +2 -2
- package/dist/chunks/{registry-DC6KR3NH.js → registry-KDD7DHNK.js} +4 -4
- package/dist/chunks/{registry-2VLUAESA.js → registry-OZSMDVPP.js} +2 -2
- package/dist/chunks/{registry-7C5UMIPL.js → registry-TKX2E7C7.js} +2 -2
- package/dist/chunks/{registry-TI3CIUXF.js → registry-VRTNC6UR.js} +8 -6
- package/dist/chunks/{room-recommend-WRTHO6Y2.js → room-recommend-XCWRMB5P.js} +4 -3
- package/dist/chunks/{schedule-KFIITGSK.js → schedule-IVBICP32.js} +4 -3
- package/dist/chunks/{workflow-DJ7SYWAF.js → workflow-D74NFK3C.js} +179 -6
- package/dist/index.js +35 -25
- package/dist/internal/e10/auth-recovery.js +14 -4
- package/dist/internal/e10/fetch-timing.js +31 -0
- package/dist/internal/e10/request-runtime.js +5 -0
- package/dist/shortcuts/contract/host.js +34 -59
- package/dist/shortcuts/cowork/continuation.js +70 -0
- package/dist/shortcuts/cowork/errors.js +59 -0
- package/dist/shortcuts/cowork/host.js +180 -0
- package/dist/shortcuts/cowork/index.js +166 -0
- package/dist/shortcuts/cowork/manifest.js +65 -0
- package/dist/shortcuts/cowork/operations/discuss-write.js +277 -0
- package/dist/shortcuts/cowork/operations/item-write.js +380 -0
- package/dist/shortcuts/cowork/operations/member-write.js +283 -0
- package/dist/shortcuts/cowork/operations/permission-write.js +286 -0
- package/dist/shortcuts/cowork/operations/read.js +423 -0
- package/dist/shortcuts/cowork/operations/registry.js +39 -0
- package/dist/shortcuts/cowork/operations/review-write.js +157 -0
- package/dist/shortcuts/cowork/operations/shared.js +215 -0
- package/dist/shortcuts/cowork/operations/types.js +39 -0
- package/dist/shortcuts/cowork/operations.js +19 -0
- package/dist/shortcuts/ehr/host.js +4 -1
- package/dist/shortcuts/ehr/operations/recruit-candidate-import.js +757 -0
- package/dist/shortcuts/ehr/operations/recruit-position.js +367 -0
- package/dist/shortcuts/ehr/operations/recruit-stats.js +133 -0
- package/dist/shortcuts/ehr/operations/recruit-write.js +43 -16
- package/dist/shortcuts/ehr/operations/recruit.js +182 -10
- package/dist/shortcuts/ehr/operations/registry.js +6 -0
- package/dist/shortcuts/fna/operations/expense-chart.js +19 -7
- package/dist/shortcuts/im/diagnostics.js +97 -0
- package/dist/shortcuts/im/host.js +58 -31
- package/dist/shortcuts/im/index.js +7 -2
- package/dist/shortcuts/im/operations/at-mention.js +127 -0
- package/dist/shortcuts/im/operations/file-user.js +10 -2
- package/dist/shortcuts/im/operations/group-read.js +26 -5
- package/dist/shortcuts/im/operations/group-write.js +35 -28
- package/dist/shortcuts/im/operations/msg-sync.js +7 -2
- package/dist/shortcuts/im/operations/msg-write.js +76 -10
- package/dist/shortcuts/im/operations/quote-mention.js +119 -0
- package/dist/shortcuts/im/operations/session.js +1 -1
- package/dist/shortcuts/im/operations/shared.js +10 -0
- package/dist/shortcuts/im/operations/types.js +13 -0
- package/dist/shortcuts/index.js +10 -0
- package/dist/shortcuts/mail/host.js +12 -1
- package/dist/shortcuts/mail/operations/types.js +17 -2
- package/dist/shortcuts/meeting/host.js +27 -34
- package/dist/shortcuts/meeting/index.js +79 -1
- package/dist/shortcuts/meeting/manifest.js +10 -0
- package/dist/shortcuts/meeting/operations/create.js +4 -1
- package/dist/shortcuts/meeting/operations/eb/catalog.js +10 -0
- package/dist/shortcuts/meeting/operations/eb/conflict.js +84 -8
- package/dist/shortcuts/meeting/operations/eb/create-flow.js +1195 -140
- package/dist/shortcuts/meeting/operations/eb/factory.js +170 -8
- package/dist/shortcuts/meeting/operations/eb/index.js +16 -3
- package/dist/shortcuts/meeting/operations/eb/room-recommend.js +50 -5
- package/dist/shortcuts/meeting/operations/eb-fallback.js +163 -0
- package/dist/shortcuts/meeting/operations/env.js +99 -46
- package/dist/shortcuts/meeting/operations/form-fields.js +110 -0
- package/dist/shortcuts/meeting/operations/invitation.js +79 -20
- package/dist/shortcuts/meeting/operations/registry.js +2 -0
- package/dist/shortcuts/meeting/operations/standard-guard.js +71 -0
- package/dist/shortcuts/meeting/operations.js +16 -0
- package/dist/shortcuts/workflow/endpoints.js +8 -3
- package/dist/shortcuts/workflow/index.js +110 -1
- package/dist/shortcuts/workflow/operations/form.js +204 -36
- package/dist/shortcuts/workflow/operations/read.js +104 -23
- package/dist/shortcuts/workflow/operations/todoSmartConvert/aigroupRules.js +448 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/aigroupStore.js +106 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/businessRules.js +47 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.collect.js +30 -23
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.core.js +1 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.group.js +1 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.groupSummary.js +2 -1
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.js +2 -4
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.smartGroup.js +138 -181
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.summary.js +12 -7
- package/dist/shortcuts/workflow/operations.js +23 -1
- package/docs/e10-auth.md +1 -1
- package/docs/ehr.md +1 -1
- package/docs/fna.md +2 -1
- package/docs/im.md +8 -1
- package/docs/todo-smart-convert/README.md +1 -1
- package/docs/todo-smart-convert/data-contract.md +2 -2
- package/docs/todo-smart-convert/subgroup-rules.md +6 -0
- package/package.json +1 -1
- package/skill-template/business-info.json +18 -0
- package/skill-template/domains/cowork.md +14 -0
- package/skill-template/domains/yimiaoban.md +3 -3
- package/skill-template/skill-template.md +1 -1
- package/skills/weaver-e10-blog/SKILL.md +5 -5
- package/skills/weaver-e10-caizhilian/SKILL.md +5 -5
- package/skills/weaver-e10-calendar/SKILL.md +5 -5
- package/skills/weaver-e10-cowork/SKILL.md +101 -0
- package/skills/weaver-e10-cowork/references/cowork-read.md +90 -0
- package/skills/weaver-e10-cowork/references/cowork-write.md +131 -0
- package/skills/weaver-e10-cowork/references/safety-boundaries.md +45 -0
- package/skills/weaver-e10-esb/SKILL.md +5 -5
- package/skills/weaver-e10-hrm/SKILL.md +5 -5
- package/skills/weaver-e10-jinchengda/SKILL.md +2 -2
- package/skills/weaver-e10-jiuchuanhui/SKILL.md +5 -5
- package/skills/weaver-e10-jucailin/SKILL.md +38 -14
- package/skills/weaver-e10-jucailin/references/recruit-create.md +104 -0
- package/skills/weaver-e10-jucailin/references/recruit-read.md +25 -0
- package/skills/weaver-e10-jucailin/references/recruit-write.md +13 -0
- package/skills/weaver-e10-jucailin/references/salary.md +15 -11
- package/skills/weaver-e10-mail/SKILL.md +17 -14
- package/skills/weaver-e10-meeting/SKILL.md +442 -46
- package/skills/weaver-e10-meeting/references/meeting-eb.md +135 -3
- package/skills/weaver-e10-meeting/references/meeting-invitation.md +28 -2
- package/skills/weaver-e10-meeting/references/meeting-queries.md +9 -6
- package/skills/weaver-e10-meeting/references/meeting-verification.md +8 -6
- package/skills/weaver-e10-meeting.zip +0 -0
- package/skills/weaver-e10-okr/SKILL.md +5 -5
- package/skills/weaver-e10-okr/references/source-manifest.json +3 -3
- package/skills/weaver-e10-plan/SKILL.md +5 -5
- package/skills/weaver-e10-qiyecheng/SKILL.md +5 -2
- package/skills/weaver-e10-qiyecheng/references/catalog.md +2 -2
- package/skills/weaver-e10-qiyecheng/references/expense-chart.md +15 -1
- package/skills/weaver-e10-qiyecheng/references/source-manifest.json +22 -21
- package/skills/weaver-e10-shared/SKILL.md +12 -12
- package/skills/weaver-e10-shared/references/e10-auth-and-session.md +4 -4
- package/skills/weaver-e10-wenshuding/SKILL.md +5 -5
- package/skills/weaver-e10-workflow/SKILL.md +29 -5
- package/skills/weaver-e10-workflow/aigroup/default.json +88 -0
- package/skills/weaver-e10-workflow/references/todo-smart-convert.md +66 -14
- package/skills/weaver-e10-workflow/references/workflow-agent-entry.md +1 -1
- package/skills/weaver-e10-workflow/references/workflow-memory.md +1 -1
- package/skills/weaver-e10-workflow/references/workflow-query.md +12 -10
- package/skills/weaver-e10-workflow/references/workflow-view.md +2 -2
- package/skills/weaver-e10-workflow/tools/todo-serve.cjs +239 -0
- package/skills/weaver-e10-workflow/{scripts → tools}/todo-summary.cjs +218 -28
- package/skills/weaver-e10-yepiaotong/SKILL.md +10 -8
- package/skills/weaver-e10-yepiaotong/references/invoice-reim.md +7 -7
- package/skills/weaver-e10-yepiaotong/references/reim-api-reference.md +10 -10
- package/skills/weaver-e10-yepiaotong/references/reim-examples.md +40 -37
- package/skills/weaver-e10-yepiaotong/references/reim-expense-subject-rules.md +4 -4
- package/skills/weaver-e10-yepiaotong/references/reim-form-fill-rules.md +27 -38
- package/skills/weaver-e10-yepiaotong/references/reim-phone-subsidy-rules.md +4 -4
- package/skills/weaver-e10-yepiaotong/references/reim-workflow.md +37 -29
- package/skills/weaver-e10-yimiaoban/SKILL.md +156 -33
- package/skills/weaver-e10-yimiaoban/references/ding-write.md +1 -1
- package/skills/weaver-e10-yimiaoban/references/error-codes.md +5 -1
- package/skills/weaver-e10-yimiaoban/references/group-read.md +5 -1
- package/skills/weaver-e10-yimiaoban/references/group-write.md +6 -4
- package/skills/weaver-e10-yimiaoban/references/msg-read.md +1 -0
- package/skills/weaver-e10-yimiaoban/references/msg-write.md +34 -2
- package/skills/weaver-e10-yimiaoban/references/safety-boundaries.md +5 -2
- package/skills/weaver-e10-yimiaoban/references/source-manifest.json +79 -58
- package/skills/weaver-e10-yimiaoban/references/user-remind.md +2 -2
- package/skills/weaver-e10-ziguanjia/SKILL.md +5 -5
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { workflowValidationError } from '../../errors.js';
|
|
2
|
-
import {
|
|
2
|
+
import { renderGroupSummaryTemplate, } from './businessRules.js';
|
|
3
3
|
import { asRecord, readBoundedInt, } from './todoSmartConvert.core.js';
|
|
4
|
-
import {
|
|
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'
|
|
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
|
|
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
|
-
|
|
55
|
-
|
|
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
|
|
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)]
|
|
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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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:
|
|
244
|
+
defaultExpanded: true,
|
|
284
245
|
displayItems: requestItemsOf(group.requestDatas),
|
|
285
246
|
});
|
|
286
|
-
|
|
287
|
-
|
|
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}
|
|
290
|
-
|
|
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
|
-
|
|
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}」不足
|
|
264
|
+
warnings.push(`「${group.title}」不足 ${GROUP_MIN_ITEMS_FOR_SUBGROUP} 条(硬门槛),忽略 aiGroups 并平铺`);
|
|
297
265
|
return flatReturn();
|
|
298
266
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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: {
|
|
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
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
const value =
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
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
|
-
|
|
378
|
-
|
|
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
|
-
|
|
389
|
-
|
|
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
|
-
|
|
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
|
|
422
|
-
|
|
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 (!
|
|
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: (
|
|
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
|
-
|
|
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
|
|
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:
|
|
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 (!
|
|
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) =>
|
|
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 (
|
|
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 (!
|
|
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) =>
|
|
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
|
-
*
|
|
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
|
|
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
|
|
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
|
|