weaver-work-cli 0.1.39 → 0.1.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/dist/chunks/{archive-ENZYBBME.js → archive-J4I6MCKF.js} +2 -2
- package/dist/chunks/{archive-oa-YWLEC5V4.js → archive-oa-BRZKKTO4.js} +2 -2
- package/dist/chunks/{asset-OOV6DWPA.js → asset-L7D4FAOZ.js} +2 -2
- package/dist/chunks/{asset-idle-RE632MY5.js → asset-idle-DADLLV75.js} +2 -2
- package/dist/chunks/{attachment-content-RWN2FCLL.js → attachment-content-K4C7VLWQ.js} +3 -3
- package/dist/chunks/{blog-PEPOJAL7.js → blog-BVGQ7NZL.js} +2 -2
- package/dist/chunks/{caizhilian-WYIQDVPJ.js → caizhilian-SAADNFD6.js} +2 -2
- package/dist/chunks/{calendar-CIARBAOX.js → calendar-Q7NQY5Z2.js} +61 -23
- package/dist/chunks/{chunk-F2EQXUEE.js → chunk-3KTFS4IA.js} +397 -159
- package/dist/chunks/{chunk-G2JSIUWF.js → chunk-4PD5NWAX.js} +89 -0
- package/dist/chunks/{chunk-HJYVHXUQ.js → chunk-4RAE6JXP.js} +1 -1
- package/dist/chunks/{chunk-CQHGNCK2.js → chunk-72CKAUAG.js} +41 -8
- package/dist/chunks/{chunk-K3RUEEOL.js → chunk-APVFJUQH.js} +2 -2
- package/dist/chunks/{chunk-BJGXMFGD.js → chunk-BBKDXQEC.js} +2 -2
- package/dist/chunks/chunk-BIPYLLVS.js +501 -0
- package/dist/chunks/{chunk-GVPJZXJ2.js → chunk-EKWAQSLU.js} +92 -50
- package/dist/chunks/{chunk-XXJOSZTQ.js → chunk-FOAA27U5.js} +381 -57
- package/dist/chunks/{chunk-I2HB737U.js → chunk-G4UR6NXN.js} +2 -2
- package/dist/chunks/{chunk-YS5DPN4T.js → chunk-GEWYMQME.js} +1 -1
- package/dist/chunks/{chunk-P2DAISYW.js → chunk-GHXCDPTM.js} +1 -1
- package/dist/chunks/{chunk-L4N7B7ZD.js → chunk-IZ7S7QUS.js} +4 -4
- package/dist/chunks/{chunk-NLK2RZKG.js → chunk-JPRO6ER2.js} +122 -2214
- package/dist/chunks/{chunk-36DIFZAH.js → chunk-LADPYOWC.js} +2 -2
- package/dist/chunks/chunk-LDZQPUIA.js +1238 -0
- package/dist/chunks/chunk-NKZRQXST.js +2546 -0
- package/dist/chunks/{chunk-EINRJHQ3.js → chunk-QKNKR4RH.js} +3 -3
- package/dist/chunks/{chunk-T7JDJJ7H.js → chunk-R4CHUSKC.js} +126 -19
- package/dist/chunks/{chunk-EZGFHJFW.js → chunk-SA6VBIHY.js} +88 -15
- package/dist/chunks/{chunk-ZXRNHZTE.js → chunk-TTPJJJC4.js} +2 -2
- package/dist/chunks/{chunk-KTCB5R5H.js → chunk-W7BEHOSR.js} +2 -2
- package/dist/chunks/{chunk-MUQUHKPG.js → chunk-YRSZQUD2.js} +2 -2
- package/dist/chunks/{conflict-DRMDOL56.js → conflict-F2OMZRTO.js} +2 -2
- package/dist/chunks/{contract-73MERZWN.js → contract-5BETHRSG.js} +2 -2
- package/dist/chunks/{cowork-HNGBPD7A.js → cowork-3MN6EQQO.js} +27 -10
- package/dist/chunks/create-flow-5FQNNMVO.js +55 -0
- package/dist/chunks/{ebuilder-form-OY6DVNRK.js → ebuilder-form-SBHOYBUD.js} +3 -3
- package/dist/chunks/{ehr-RAQNEQO7.js → ehr-KMOJULKG.js} +432 -1234
- package/dist/chunks/env-LB7NBL2O.js +19 -0
- package/dist/chunks/{esb-6XV7C7YV.js → esb-W2PJCFCX.js} +4 -4
- package/dist/chunks/factory-6ZFQXQ46.js +30 -0
- package/dist/chunks/{fna-XTSRB7G7.js → fna-ZQPCUV4R.js} +2 -2
- package/dist/chunks/group-chat-2HHAJN46.js +20 -0
- package/dist/chunks/{host-YMBXWFWX.js → host-4CBHGOHT.js} +3 -3
- package/dist/chunks/{host-A7J6GDUP.js → host-ATKVNZAT.js} +3 -3
- package/dist/chunks/{host-3F5UM5GN.js → host-BQH22NTQ.js} +3 -3
- package/dist/chunks/{host-YAQAPYNP.js → host-X2CSLFM6.js} +3 -3
- package/dist/chunks/{host-VGPVP7YF.js → host-YTODKMSL.js} +3 -3
- package/dist/chunks/{hrm-TLHKC545.js → hrm-FFM4UJFS.js} +4 -4
- package/dist/chunks/{im-YOUCFMUK.js → im-VEHKDQPB.js} +8 -8
- package/dist/chunks/{invoice-WNBS2B4C.js → invoice-LVIKSIIF.js} +3 -3
- package/dist/chunks/{jiuchuanhui-XABWS5RN.js → jiuchuanhui-X6DINO5P.js} +2 -2
- package/dist/chunks/{lazy-dispatch-NLZ5UQQI.js → lazy-dispatch-KSWTN5L6.js} +4 -4
- package/dist/chunks/{mail-LRYMBFNL.js → mail-VDKRGUD3.js} +9 -4
- package/dist/chunks/{meeting-ESXL6MFI.js → meeting-CLC6HP3Z.js} +12 -10
- package/dist/chunks/{okr-EOV65SSF.js → okr-CHG4OO3U.js} +2 -2
- package/dist/chunks/{plan-YFPVKW6Y.js → plan-XWR75667.js} +2 -2
- package/dist/chunks/{project-MDVZUYQW.js → project-5BXOOKMG.js} +40 -23
- package/dist/chunks/recruit-stage-DGMFNNDE.js +4507 -0
- package/dist/chunks/{registry-VT5Y2HLA.js → registry-7FFOXIAW.js} +4 -4
- package/dist/chunks/{registry-QCLZEDAP.js → registry-BKBZPBAJ.js} +10 -8
- package/dist/chunks/{registry-YX4CSEMD.js → registry-RHMW6ANW.js} +2 -2
- package/dist/chunks/{registry-NYPYMZXG.js → registry-Z43434VG.js} +1 -1
- package/dist/chunks/{room-recommend-L3MWH4JW.js → room-recommend-62H7ZQQ6.js} +4 -4
- package/dist/chunks/{schedule-UASAVDVD.js → schedule-FNXKH3DQ.js} +4 -4
- package/dist/chunks/{workflow-KDGIUCNW.js → workflow-ADHKCAJK.js} +4 -4
- package/dist/index.js +33 -28
- package/dist/internal/e10/auth/commands.js +110 -56
- package/dist/internal/e10/auth/messages.js +6 -0
- package/dist/internal/e10/auth/session.js +28 -1
- package/dist/internal/e10/auth/xiaoe.js +315 -48
- package/dist/internal/e10/auth-recovery.js +36 -5
- package/dist/internal/e10/client.js +13 -0
- package/dist/internal/e10/context.js +61 -9
- package/dist/internal/e10/index.js +1 -1
- package/dist/internal/e10/network-error.js +93 -0
- package/dist/internal/e10/request-runtime.js +15 -0
- package/dist/shortcuts/calendar/operations/read.js +31 -22
- package/dist/shortcuts/calendar/operations/write.js +45 -1
- package/dist/shortcuts/cowork/operations/discuss-write.js +5 -1
- package/dist/shortcuts/cowork/operations/item-write.js +5 -3
- package/dist/shortcuts/cowork/operations/member-write.js +3 -1
- package/dist/shortcuts/cowork/operations/permission-write.js +5 -1
- package/dist/shortcuts/cowork/operations/shared.js +7 -0
- package/dist/shortcuts/ehr/operations/attend-write.js +43 -1
- package/dist/shortcuts/ehr/operations/hr-contact.js +7 -4
- package/dist/shortcuts/ehr/operations/hr-report.js +13 -7
- package/dist/shortcuts/ehr/operations/recruit-candidate-import.js +134 -59
- package/dist/shortcuts/ehr/operations/recruit-write.js +262 -17
- package/dist/shortcuts/ehr/operations/recruit.js +10 -4
- package/dist/shortcuts/ehr/operations/shared.js +47 -0
- package/dist/shortcuts/hrm/operations/org.js +118 -0
- package/dist/shortcuts/index.js +5 -0
- package/dist/shortcuts/mail/operations/mail-send.js +8 -2
- package/dist/shortcuts/meeting/operations/eb/catalog.js +9 -2
- package/dist/shortcuts/meeting/operations/eb/create-flow.js +461 -127
- package/dist/shortcuts/meeting/operations/eb/crm-search.js +87 -5
- package/dist/shortcuts/meeting/operations/eb/factory.js +33 -0
- package/dist/shortcuts/meeting/operations/eb/flow-memory.js +49 -0
- package/dist/shortcuts/meeting/operations/eb/group-chat.js +605 -0
- package/dist/shortcuts/meeting/operations/eb/index.js +2 -0
- package/dist/shortcuts/meeting/operations/eb/person-cache.js +96 -0
- package/dist/shortcuts/meeting/operations/eb/room-recommend.js +44 -6
- package/dist/shortcuts/meeting/operations/eb/room-summary.js +2 -0
- package/dist/shortcuts/meeting/operations/eb/usage-cache.js +99 -0
- package/dist/shortcuts/meeting/operations/from-group-chat.js +35 -12
- package/dist/shortcuts/project/operations/read.js +6 -25
- package/dist/shortcuts/project/operations/shared.js +22 -0
- package/dist/shortcuts/project/operations/write.js +28 -2
- package/dist/shortcuts/recruit-stage/context.js +119 -0
- package/dist/shortcuts/recruit-stage/continuation.js +73 -0
- package/dist/shortcuts/recruit-stage/errors.js +65 -0
- package/dist/shortcuts/recruit-stage/host.js +109 -0
- package/dist/shortcuts/recruit-stage/index.js +115 -0
- package/dist/shortcuts/recruit-stage/manifest.js +29 -0
- package/dist/shortcuts/recruit-stage/operations/interview-arrange.js +722 -0
- package/dist/shortcuts/recruit-stage/operations/interview-feedback.js +269 -0
- package/dist/shortcuts/recruit-stage/operations/jd-create.js +630 -0
- package/dist/shortcuts/recruit-stage/operations/list.js +431 -0
- package/dist/shortcuts/recruit-stage/operations/registry.js +38 -0
- package/dist/shortcuts/recruit-stage/operations/resume-import.js +1198 -0
- package/dist/shortcuts/recruit-stage/operations/search.js +158 -0
- package/dist/shortcuts/recruit-stage/operations/types.js +25 -0
- package/dist/shortcuts/recruit-stage/school-tiers.json +318 -0
- package/dist/shortcuts/recruit-stage/shared.js +608 -0
- package/dist/shortcuts/workflow/operations/form.js +21 -2
- package/dist/shortcuts/workflow/operations/todoSmartConvert/aigroupRules.js +52 -1
- package/dist/shortcuts/workflow/operations/todoSmartConvert/detailFieldRules.js +4 -2
- package/dist/shortcuts/workflow/operations/todoSmartConvert/summaryCache.js +19 -11
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoDetailCache.js +17 -24
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoDetailFetcher.js +14 -9
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.collect.js +78 -10
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.core.js +3 -1
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.detail.js +17 -11
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.group.js +16 -40
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.groupSummary.js +3 -2
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.js +52 -15
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.pipeline.js +16 -1
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.smartGroup.js +151 -63
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.subGroup.js +56 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.summary.js +53 -16
- package/docs/e10-auth.md +22 -1
- package/docs/operation-manual.md +8 -0
- package/package.json +1 -1
- package/scripts/install-skill-to-xiaoe.mjs +105 -0
- package/scripts/mcp-meeting.mjs +234 -0
- package/scripts/package-skill.mjs +4 -2
- package/skill-template/business-info.json +24 -0
- package/skill-template/domains/recruit-stage.md +29 -0
- package/skills/weaver-e10-calendar/SKILL.md +2 -1
- package/skills/weaver-e10-calendar/references/calendar-write.md +54 -0
- package/skills/weaver-e10-cowork/SKILL.md +1 -1
- package/skills/weaver-e10-cowork/references/cowork-write.md +3 -1
- package/skills/weaver-e10-jucailin/SKILL.md +1 -1
- package/skills/weaver-e10-jucailin/references/attend-write.md +8 -0
- package/skills/weaver-e10-jucailin/references/hr-contact.md +8 -0
- package/skills/weaver-e10-jucailin/references/hr-report.md +8 -0
- package/skills/weaver-e10-jucailin/references/recruit-create.md +2 -2
- package/skills/weaver-e10-jucailin/references/recruit-read.md +1 -1
- package/skills/weaver-e10-jucailin/references/recruit-write.md +4 -3
- package/skills/weaver-e10-mail/SKILL.md +1 -1
- package/skills/weaver-e10-mail/references/mail-send.md +2 -0
- package/skills/weaver-e10-meeting/SKILL.md +119 -26
- package/skills/weaver-e10-meeting/references/meeting-minute-file.md +25 -11
- package/skills/weaver-e10-meeting.zip +0 -0
- package/skills/weaver-e10-recruit-stage/SKILL.md +226 -0
- package/skills/weaver-e10-recruit-stage/agents/openai.yaml +119 -0
- package/skills/weaver-e10-recruit-stage/lib/recruit-xiaoe-file.mjs +188 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-arrange-interview.md +322 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-capability-check.md +127 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-check-install.md +65 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-detail-process.md +63 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-edit-interview-feedback.md +142 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-jd-create.md +73 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-jd-template.md +56 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-list-candidate.md +101 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-list-interview.md +148 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-match-candidates.md +77 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-output-links.md +59 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-resolve-creator.md +69 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-resolve-metadata.md +241 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-resume-import.md +438 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-resume-parse.md +128 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-search-candidate.md +146 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-urge-feedback.md +69 -0
- package/skills/weaver-e10-shared/SKILL.md +2 -2
- package/skills/weaver-e10-shared/references/e10-auth-and-session.md +4 -2
- package/skills/weaver-e10-shijingran/SKILL.md +3 -2
- package/skills/weaver-e10-shijingran/references/operations.md +3 -1
- package/skills/weaver-e10-shijingran/references/source-manifest.json +41 -40
- package/skills/weaver-e10-shijingran/references/write-operations.md +2 -0
- package/skills/weaver-e10-workflow/SKILL.md +7 -1
- package/skills/weaver-e10-workflow/aigroup/default.json +23 -9
- package/skills/weaver-e10-workflow/references/todo-smart-convert.md +37 -21
- package/skills/weaver-e10-workflow/tools/todo-summary.cjs +135 -43
- package/skills/weaver-e10-yepiaotong/SKILL.md +9 -10
- package/skills/weaver-e10-yepiaotong/references/invoice-agent-entry.md +6 -3
|
@@ -115,29 +115,32 @@ export function isExpiringTodayRow(row, now = Date.now()) {
|
|
|
115
115
|
return isExpiringTodayUserOverTime(row.userOverTime, now);
|
|
116
116
|
}
|
|
117
117
|
/**
|
|
118
|
-
*
|
|
118
|
+
* 「建议优先处理的流程」筛选规则(2026-11-03 起可在 `generalRules.priorityGroup.rules` 配置启用子集):
|
|
119
119
|
*
|
|
120
|
-
* 1.
|
|
121
|
-
* 2.
|
|
120
|
+
* 1. **超时**(`overdue`)必收(`userOverTime` / `overTime` 判定,与紧急程度无关);
|
|
121
|
+
* 2. **紧急程度**(`levelFallback`)按 `requestLevel` 取:整批有 `=2`(紧急)就只要 2;没有 2 才退到 `=1`(重要);
|
|
122
122
|
* `=0`(正常)**不因等级入选**——降级链到 1 就停,不倒到 0;
|
|
123
123
|
* 3. 整批连 1 级都没有(全 0)时,等级条件不生效,只按超时筛;此时若一条都没超时,
|
|
124
124
|
* 优先组为空("实在没有紧急的就算了"),不硬凑。
|
|
125
125
|
*
|
|
126
126
|
* 注意这是**筛选**,不是视图叠加:入选条目的 `requestLevel` 只有 2(或 2 缺失时的 1)与超时两类。
|
|
127
|
+
* `enabledRules` 缺省 = 全部启用(与历史行为一致);预留 `expiringToday`(今天到期)等后续扩展。
|
|
127
128
|
*/
|
|
128
|
-
export function selectPriorityRows(rows) {
|
|
129
|
+
export function selectPriorityRows(rows, enabledRules) {
|
|
130
|
+
const rules = enabledRules ?? new Set(['overdue', 'levelFallback']);
|
|
129
131
|
let maxLevel = 0;
|
|
130
132
|
for (const row of rows) {
|
|
131
133
|
if (row.requestLevel > maxLevel)
|
|
132
134
|
maxLevel = row.requestLevel;
|
|
133
135
|
}
|
|
134
|
-
const targetLevel = maxLevel >= 2 ? 2
|
|
136
|
+
const targetLevel = rules.has('levelFallback') && maxLevel >= 2 ? 2
|
|
137
|
+
: rules.has('levelFallback') && maxLevel >= 1 ? 1 : undefined;
|
|
135
138
|
const selected = [];
|
|
136
139
|
let overdueCount = 0;
|
|
137
140
|
let levelCount = 0;
|
|
138
141
|
let bothCount = 0;
|
|
139
142
|
for (const row of rows) {
|
|
140
|
-
const overdue = isOverdueRow(row);
|
|
143
|
+
const overdue = rules.has('overdue') && isOverdueRow(row);
|
|
141
144
|
const byLevel = targetLevel !== undefined && row.requestLevel >= targetLevel;
|
|
142
145
|
if (overdue)
|
|
143
146
|
overdueCount += 1;
|
|
@@ -323,12 +326,11 @@ export function runGroup(args) {
|
|
|
323
326
|
const { rawCount, rows, duplicateCount, unreadableCount } = readItems(args.items);
|
|
324
327
|
const groupBy = readGroupBy(args.groupBy);
|
|
325
328
|
const topN = readTopN(args.topN);
|
|
326
|
-
//
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
const restRows = rows;
|
|
329
|
+
// 2026-11-03 用户定:分组阶段**不再**产出「建议优先处理的流程」——它是最终结构上的一个置顶
|
|
330
|
+
// 视图,等摘要全部回填后用 `assemblePriorityGroup()` 按同一条规则从成品数据里筛出来。
|
|
331
|
+
// 这样前期(摘要单位、闸门、统计)都只需处理一份数据,不必到处特判 topPriority。
|
|
330
332
|
const buckets = new Map();
|
|
331
|
-
for (const row of
|
|
333
|
+
for (const row of rows) {
|
|
332
334
|
const key = groupKeyOf(row, groupBy);
|
|
333
335
|
let bucket = buckets.get(key);
|
|
334
336
|
if (!bucket) {
|
|
@@ -356,20 +358,8 @@ export function runGroup(args) {
|
|
|
356
358
|
stats: statsOf(bucket.rows),
|
|
357
359
|
...(bucket.ids.length > 1 ? { workflowIds: bucket.ids } : {}),
|
|
358
360
|
}));
|
|
359
|
-
const groups =
|
|
360
|
-
|
|
361
|
-
id: PRIORITY_GROUP_ID,
|
|
362
|
-
topPriority: true,
|
|
363
|
-
title: PRIORITY_GROUP_TITLE,
|
|
364
|
-
count: priorityRows.length,
|
|
365
|
-
requestDatas: priorityRows.map((row) => toRequestData(row)),
|
|
366
|
-
stats: statsOf(priorityRows),
|
|
367
|
-
}, ...workflowGroups]
|
|
368
|
-
: workflowGroups;
|
|
369
|
-
const displayedWorkflow = workflowGroups.slice(0, topN);
|
|
370
|
-
const displayed = priorityRows.length > 0 && groupBy === 'workflow'
|
|
371
|
-
? [groups[0], ...displayedWorkflow]
|
|
372
|
-
: displayedWorkflow;
|
|
361
|
+
const groups = workflowGroups;
|
|
362
|
+
const displayed = workflowGroups.slice(0, topN);
|
|
373
363
|
const rest = workflowGroups.slice(topN);
|
|
374
364
|
const restCount = rest.reduce((sum, group) => sum + group.count, 0);
|
|
375
365
|
const overall = statsOf(rows);
|
|
@@ -386,20 +376,6 @@ export function runGroup(args) {
|
|
|
386
376
|
? `有 ${unknownGroupCount} 条缺少工作流名称,已归入「${UNKNOWN_GROUP}」`
|
|
387
377
|
: `有 ${unknownGroupCount} 条缺少分组字段,已归入「${UNKNOWN_GROUP}」`);
|
|
388
378
|
}
|
|
389
|
-
if (priorityRows.length > 0 && groupBy === 'workflow') {
|
|
390
|
-
const levelText = priority.targetLevel === 2
|
|
391
|
-
? '紧急(requestLevel=2)'
|
|
392
|
-
: priority.targetLevel === 1
|
|
393
|
-
? '重要(requestLevel=1;本次无 2 级数据,已按规则降级取 1,0 级不取)'
|
|
394
|
-
: '无等级(本次整批没有 ≥1 级的条目,只按超时筛)';
|
|
395
|
-
warnings.push(`「${PRIORITY_GROUP_TITLE}」筛出 ${priorityRows.length} 条:超时 ${priority.overdueCount} 条 / ` +
|
|
396
|
-
`等级命中 ${priority.levelCount} 条(两者都满足 ${priority.bothCount} 条),等级口径=${levelText};` +
|
|
397
|
-
`这 ${priorityRows.length} 条同时保留在各自工作流分组中(叠加视图:Σcount 会大于 totalCount,` +
|
|
398
|
-
`totalCount 按去重条数计 = ${rows.length})`);
|
|
399
|
-
}
|
|
400
|
-
else if (priorityRows.length === 0 && groupBy === 'workflow') {
|
|
401
|
-
warnings.push(`本次没有超时条目,也没有 requestLevel≥1 的条目,故不输出「${PRIORITY_GROUP_TITLE}」分组`);
|
|
402
|
-
}
|
|
403
379
|
if (rest.length > 0) {
|
|
404
380
|
warnings.push(`markdown 只展示前 ${topN} 个工作流分组,其余 ${rest.length} 组共 ${restCount} 条仍在 wfIdGroup 中(未截断)`);
|
|
405
381
|
}
|
|
@@ -434,7 +410,7 @@ export function runGroup(args) {
|
|
|
434
410
|
},
|
|
435
411
|
wfIdGroup: groups,
|
|
436
412
|
/** 第二部分(非优先组)的总条数与组数,前端折叠/分页直接用它,不用自己减 */
|
|
437
|
-
restTotal:
|
|
413
|
+
restTotal: rows.length,
|
|
438
414
|
restGroupCount: workflowGroups.length,
|
|
439
415
|
restGroups: rest.length > 0 ? { groupCount: rest.length, count: restCount } : null,
|
|
440
416
|
warnings,
|
package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.groupSummary.js
CHANGED
|
@@ -69,8 +69,9 @@ export function collectGroupSummaryBatches(groups, isCtxEmpty, maxCharsFor = ()
|
|
|
69
69
|
continue;
|
|
70
70
|
const kind = detectBusinessKind(group.title);
|
|
71
71
|
for (const sub of groupItemsOf(group)) {
|
|
72
|
-
// CLI
|
|
73
|
-
|
|
72
|
+
// 已有摘要就不产批次:①CLI 固定文案(needAI:false);②模型随 aiGroups 一起给的
|
|
73
|
+
// 子组摘要(2026-11-03 合并交互)——两者都不需要再问一次模型。
|
|
74
|
+
if ((sub.summary ?? '').trim() !== '')
|
|
74
75
|
continue;
|
|
75
76
|
const allRows = Array.isArray(sub.requestDatas) ? sub.requestDatas : [];
|
|
76
77
|
const rows = isCtxEmpty ? allRows.filter((row) => !isCtxEmpty(row.requestId)) : allRows;
|
|
@@ -5,8 +5,9 @@ import { collectRawTodos } from './todoSmartConvert.collect.js';
|
|
|
5
5
|
import { runSmartConvert } from './todoSmartConvert.aggregate.js';
|
|
6
6
|
import { runGroup } from './todoSmartConvert.group.js';
|
|
7
7
|
import { inferStage, readStage } from './todoSmartConvert.pipeline.js';
|
|
8
|
-
import { runSmartGroup } from './todoSmartConvert.smartGroup.js';
|
|
8
|
+
import { runSmartGroup, assemblePriorityGroup } from './todoSmartConvert.smartGroup.js';
|
|
9
9
|
import { runSummary, runSummaryContext } from './todoSmartConvert.summary.js';
|
|
10
|
+
import { runSubGroup } from './todoSmartConvert.subGroup.js';
|
|
10
11
|
import { runGroupSummary, runGroupSummaryContext } from './todoSmartConvert.groupSummary.js';
|
|
11
12
|
import { schemaOf, } from '../types.js';
|
|
12
13
|
const inputSchema = schemaOf({
|
|
@@ -58,6 +59,7 @@ const inputSchema = schemaOf({
|
|
|
58
59
|
},
|
|
59
60
|
},
|
|
60
61
|
withDetailContext: { type: 'boolean', description: 'smartGroup 是否同进程生成单条摘要上下文。' },
|
|
62
|
+
withSubGroup: { type: 'boolean', description: 'summary 回填后是否同进程接着做 ai 二次分组(stage=subGroup 等价)。' },
|
|
61
63
|
withGroupSummaryContext: { type: 'boolean', description: 'summary 是否同进程生成组摘要上下文。' },
|
|
62
64
|
detailPrefetch: { type: 'boolean', description: '废弃兼容参数,已忽略;collect 只在给了 groupFocus 且意图适用于本组时按需预取分组字段(走详情缓存)。' },
|
|
63
65
|
groupBy: { type: 'string', enum: [...GROUP_BY_VALUES], default: 'workflow' },
|
|
@@ -80,6 +82,7 @@ const inputSchema = schemaOf({
|
|
|
80
82
|
tabid: { type: 'string' },
|
|
81
83
|
tabName: { type: 'string' },
|
|
82
84
|
subCategory: { type: 'string' },
|
|
85
|
+
isremarks: { type: 'string', description: '备注筛选(getTodoListData params.isremarks),多个数字用英文逗号拼接,仅作用于本汇总取数' },
|
|
83
86
|
workflow: { type: 'array', items: { type: 'string' } },
|
|
84
87
|
creator: { type: 'string' },
|
|
85
88
|
requestName: { type: 'string' },
|
|
@@ -139,6 +142,19 @@ export const workflowTodoSmartConvertOperations = [{
|
|
|
139
142
|
: readStage(args.stage);
|
|
140
143
|
const hasItems = Array.isArray(args.items);
|
|
141
144
|
const resumed = stage === 'collect' && hasItems;
|
|
145
|
+
/**
|
|
146
|
+
* 把「建议优先处理的流程」拼到**最终结构**上(2026-11-03 用户定:放到最后生成)。
|
|
147
|
+
* 只在会产出成品结构的阶段(summary / groupSummary)调用;幂等,重复调用不会叠加。
|
|
148
|
+
*/
|
|
149
|
+
const withPriorityView = (data, extraWarnings) => {
|
|
150
|
+
if ((args.groupBy ?? 'workflow') !== 'workflow')
|
|
151
|
+
return { data, warnings: extraWarnings };
|
|
152
|
+
const { groups, warning } = assemblePriorityGroup(data.wfIdGroup ?? []);
|
|
153
|
+
return {
|
|
154
|
+
data: { ...data, wfIdGroup: groups },
|
|
155
|
+
warnings: warning ? [...extraWarnings, warning] : extraWarnings,
|
|
156
|
+
};
|
|
157
|
+
};
|
|
142
158
|
if ((stage === 'group' || stage === 'smartGroup' || stage === 'aggregate') && !hasItems) {
|
|
143
159
|
throw workflowValidationError('items_required', 'stage=' + stage + ' 需要 items:先执行 stage=collect,再回灌 data.items', { retryAllowed: false });
|
|
144
160
|
}
|
|
@@ -166,28 +182,48 @@ export const workflowTodoSmartConvertOperations = [{
|
|
|
166
182
|
throw workflowValidationError('ai_summaries_required', 'stage=summary 需要 aiSummaries:先执行 stage=summaryContext', { retryAllowed: false });
|
|
167
183
|
}
|
|
168
184
|
const result = await runSummary(args, env);
|
|
185
|
+
// 2026-11-03 用户定:**ai 二次分组后移到单条摘要之后**(两者不再耦合)。
|
|
186
|
+
// `withSubGroup` 时在同一次进程内接着做二次分组,省掉一轮 CLI 启动(≈6~7s)。
|
|
187
|
+
// 顺序:summary(回填单条摘要) → subGroup(切子组) → priority 装配 → 组摘要批次。
|
|
188
|
+
let baseData = result.data;
|
|
189
|
+
let baseWarnings = result.warnings;
|
|
190
|
+
let subGrouped = false;
|
|
191
|
+
if (args.withSubGroup === true) {
|
|
192
|
+
const sub = await runSubGroup({ ...args, wfIdGroup: baseData.wfIdGroup }, env);
|
|
193
|
+
baseData = { ...baseData, wfIdGroup: sub.data.wfIdGroup };
|
|
194
|
+
baseWarnings = [...baseWarnings, ...sub.warnings];
|
|
195
|
+
subGrouped = true;
|
|
196
|
+
}
|
|
197
|
+
const final = withPriorityView(baseData, baseWarnings);
|
|
169
198
|
if (args.withGroupSummaryContext === true) {
|
|
170
199
|
const context = await runGroupSummaryContext({
|
|
171
|
-
wfIdGroup:
|
|
172
|
-
smartSummaryTitle:
|
|
173
|
-
totalCount:
|
|
200
|
+
wfIdGroup: final.data.wfIdGroup,
|
|
201
|
+
smartSummaryTitle: final.data.smartSummaryTitle,
|
|
202
|
+
totalCount: final.data.totalCount,
|
|
174
203
|
}, env);
|
|
175
204
|
if (Array.isArray(context.data.groupSummaryBatches) && context.data.groupSummaryBatches.length === 0) {
|
|
176
|
-
return finish(env, args, startedAt, stage,
|
|
177
|
-
branch: 'todo-smart-convert', stage, aiSummarized: true, withGroupSummaryContext: true, ...result.meta,
|
|
178
|
-
},
|
|
205
|
+
return finish(env, args, startedAt, stage, final.data, {
|
|
206
|
+
branch: 'todo-smart-convert', stage, aiSummarized: true, subGrouped, withGroupSummaryContext: true, ...result.meta,
|
|
207
|
+
}, final.warnings);
|
|
179
208
|
}
|
|
180
209
|
return finish(env, args, startedAt, stage, {
|
|
181
|
-
...
|
|
210
|
+
...final.data,
|
|
182
211
|
groupSummaryBatches: context.data.groupSummaryBatches,
|
|
183
212
|
groupSummaryStats: context.data.groupSummaryStats,
|
|
184
213
|
}, {
|
|
185
|
-
branch: 'todo-smart-convert', stage, aiSummarized: true, withGroupSummaryContext: true, ...result.meta,
|
|
186
|
-
}, [...
|
|
214
|
+
branch: 'todo-smart-convert', stage, aiSummarized: true, subGrouped, withGroupSummaryContext: true, ...result.meta,
|
|
215
|
+
}, [...final.warnings, ...context.warnings]);
|
|
187
216
|
}
|
|
188
|
-
return finish(env, args, startedAt, stage,
|
|
189
|
-
branch: 'todo-smart-convert', stage, aiSummarized: true, ...result.meta,
|
|
190
|
-
},
|
|
217
|
+
return finish(env, args, startedAt, stage, final.data, {
|
|
218
|
+
branch: 'todo-smart-convert', stage, aiSummarized: true, subGrouped, ...result.meta,
|
|
219
|
+
}, final.warnings);
|
|
220
|
+
}
|
|
221
|
+
if (stage === 'subGroup') {
|
|
222
|
+
const result = await runSubGroup(args, env);
|
|
223
|
+
const final = withPriorityView(result.data, result.warnings);
|
|
224
|
+
return finish(env, args, startedAt, stage, final.data, {
|
|
225
|
+
branch: 'todo-smart-convert', stage, aiGrouped: Array.isArray(args.aiGroups), subGrouped: true,
|
|
226
|
+
}, final.warnings);
|
|
191
227
|
}
|
|
192
228
|
if (stage === 'groupSummaryContext') {
|
|
193
229
|
const result = await runGroupSummaryContext(args, env);
|
|
@@ -200,9 +236,10 @@ export const workflowTodoSmartConvertOperations = [{
|
|
|
200
236
|
throw workflowValidationError('ai_group_summaries_required', 'stage=groupSummary 需要 aiGroupSummaries:先执行 stage=groupSummaryContext', { retryAllowed: false });
|
|
201
237
|
}
|
|
202
238
|
const result = await runGroupSummary(args, env);
|
|
203
|
-
|
|
239
|
+
const final = withPriorityView(result.data, result.warnings);
|
|
240
|
+
return finish(env, args, startedAt, stage, final.data, {
|
|
204
241
|
branch: 'todo-smart-convert', stage, aiSummarized: true, ...result.meta,
|
|
205
|
-
},
|
|
242
|
+
}, final.warnings);
|
|
206
243
|
}
|
|
207
244
|
if (stage === 'group' || resumed) {
|
|
208
245
|
return finish(env, args, startedAt, 'group', runGroup(args), {
|
|
@@ -32,7 +32,22 @@ export function inferStage(args) {
|
|
|
32
32
|
return 'groupSummary';
|
|
33
33
|
if (Array.isArray(args.aiSummaries))
|
|
34
34
|
return 'summary';
|
|
35
|
-
|
|
35
|
+
if (hasAnyRequestSummary(args.wfIdGroup)) {
|
|
36
|
+
// 2026-11-03:ai 二次分组后移到单条摘要之后。
|
|
37
|
+
// 单条摘要已填、但结构里还没有汇总项(`type:"group"`)⇒ 下一步是 subGroup;
|
|
38
|
+
// 已经有汇总项 ⇒ 说明二次分组做过,下一步才是组摘要。
|
|
39
|
+
return hasAnySubGroup(args.wfIdGroup) ? 'groupSummaryContext' : 'subGroup';
|
|
40
|
+
}
|
|
41
|
+
return 'summaryContext';
|
|
42
|
+
}
|
|
43
|
+
/** 结构里是否已经存在汇总项(`displayItems[].type === "group"`)。 */
|
|
44
|
+
export function hasAnySubGroup(groups) {
|
|
45
|
+
for (const raw of groups) {
|
|
46
|
+
const items = raw?.displayItems;
|
|
47
|
+
if (Array.isArray(items) && items.some((item) => item?.type === 'group'))
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
return false;
|
|
36
51
|
}
|
|
37
52
|
/** 从**未经校验**的分组对象里摊平出条目(推断阶段用,同时兼容新旧两种结构)。 */
|
|
38
53
|
export function rowsOfRawGroup(group) {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { workflowValidationError } from '../../errors.js';
|
|
2
2
|
import { renderGroupSummaryTemplate, } from './businessRules.js';
|
|
3
3
|
import { asRecord, readBoundedInt, } from './todoSmartConvert.core.js';
|
|
4
|
-
import { 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, selectPriorityRows, sortPriorityRows, } from './todoSmartConvert.group.js';
|
|
5
5
|
import { fetchDetailFacts, stampsOfRows } from './todoSmartConvert.detail.js';
|
|
6
|
-
import { activeRuleFor, evaluateFocusKeywords, primeRules } from './aigroupRules.js';
|
|
6
|
+
import { activeRuleFor, evaluateFocusKeywords, generalRules, primeRules } from './aigroupRules.js';
|
|
7
7
|
import { openSummaryCache, promptTagOf } from './summaryCache.js';
|
|
8
8
|
import { openDetailCache, detailMaterialTagOf } from './todoDetailCache.js';
|
|
9
9
|
const DEFAULT_REQUEST_SUMMARY_MAX = 50;
|
|
@@ -19,6 +19,54 @@ export function rowsOfGroup(group) {
|
|
|
19
19
|
export function groupItemsOf(group) {
|
|
20
20
|
return group.displayItems.filter((item) => item.type === 'group');
|
|
21
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* 最终结构上的「建议优先处理的流程」置顶视图(2026-11-03 用户定:改到**最后一步**生成)。
|
|
24
|
+
*
|
|
25
|
+
* - 输入是**成品结构**(单条摘要、组摘要都已回填):按同一条规则(`generalRules.priorityGroup`)
|
|
26
|
+
* 从各工作流分组的 `displayItems` 里摊平条目、筛出「超时 / 紧急程度」命中项,复制一份成
|
|
27
|
+
* `id=priority`、`unshift` 到首位;
|
|
28
|
+
* - **幂等**:输入里已有 priority 组时先剔除再重建,stage 串联多次调用结果一致;
|
|
29
|
+
* - 叠加视图语义不变:入选条目仍保留在原工作流分组里 ⇒ `Σcount` 仍大于 `totalCount`
|
|
30
|
+
* (`totalCount` 始终按去重条数计);
|
|
31
|
+
* - 判定字段(`overTime` / `userOverTime` / `requestLevel`)都在 `requestData` 上,不必回查列表;
|
|
32
|
+
* 原排序用的 `receivedTime` 不在 requestData 中,同档退化为按 `requestId` 稳定排序。
|
|
33
|
+
*/
|
|
34
|
+
export function assemblePriorityGroup(groups) {
|
|
35
|
+
const base = (groups ?? []).filter((group) => group.id !== PRIORITY_GROUP_ID && group.topPriority !== true);
|
|
36
|
+
const pgConfig = generalRules()?.priorityGroup ?? null;
|
|
37
|
+
const title = pgConfig?.title?.trim() || PRIORITY_GROUP_TITLE;
|
|
38
|
+
const rules = new Set(pgConfig?.rules ?? ['overdue', 'levelFallback']);
|
|
39
|
+
const rows = base.flatMap((group) => rowsOfGroup(group));
|
|
40
|
+
if (rows.length === 0)
|
|
41
|
+
return { groups: base };
|
|
42
|
+
const selection = selectPriorityRows(rows, rules);
|
|
43
|
+
const selected = sortPriorityRows(selection.rows);
|
|
44
|
+
if (selected.length === 0) {
|
|
45
|
+
return { groups: base, warning: `本次没有超时条目,也没有 requestLevel≥1 的条目,故不输出「${title}」分组` };
|
|
46
|
+
}
|
|
47
|
+
const levelText = selection.targetLevel === 2
|
|
48
|
+
? '紧急(requestLevel=2)'
|
|
49
|
+
: selection.targetLevel === 1
|
|
50
|
+
? '重要(requestLevel=1;本次无 2 级数据,已按规则降级取 1,0 级不取)'
|
|
51
|
+
: '无等级(本次整批没有 ≥1 级的条目,只按超时筛)';
|
|
52
|
+
const priorityGroup = {
|
|
53
|
+
id: PRIORITY_GROUP_ID,
|
|
54
|
+
topPriority: true,
|
|
55
|
+
title,
|
|
56
|
+
count: selected.length,
|
|
57
|
+
// 大分组(含优先组)默认展开;只有汇总项收缩
|
|
58
|
+
defaultExpanded: true,
|
|
59
|
+
// 复制一份:优先组是独立视图,确保两边互不影响
|
|
60
|
+
displayItems: requestItemsOf(selected.map((row) => ({ ...row }))),
|
|
61
|
+
};
|
|
62
|
+
return {
|
|
63
|
+
groups: [priorityGroup, ...base],
|
|
64
|
+
warning: `「${title}」筛出 ${selected.length} 条:超时 ${selection.overdueCount} 条 / `
|
|
65
|
+
+ `等级命中 ${selection.levelCount} 条(两者都满足 ${selection.bothCount} 条),等级口径=${levelText};`
|
|
66
|
+
+ `这 ${selected.length} 条同时保留在各自工作流分组中(叠加视图:Σcount 会大于 totalCount,`
|
|
67
|
+
+ 'totalCount 按去重条数计)',
|
|
68
|
+
};
|
|
69
|
+
}
|
|
22
70
|
/**
|
|
23
71
|
* 校验并索引 `aiGroups`。
|
|
24
72
|
*
|
|
@@ -40,10 +88,11 @@ export function readAiGroups(value) {
|
|
|
40
88
|
if (!record) {
|
|
41
89
|
throw workflowValidationError('ai_groups_invalid', `${label} 必须是对象 { groupId, title, requestIds }`, { retryAllowed: false });
|
|
42
90
|
}
|
|
43
|
-
const unknownKeys = Object.keys(record).filter((key) => !['groupId', 'title', 'requestIds'].includes(key));
|
|
91
|
+
const unknownKeys = Object.keys(record).filter((key) => !['groupId', 'title', 'requestIds', 'summary'].includes(key));
|
|
44
92
|
if (unknownKeys.length > 0) {
|
|
45
|
-
throw workflowValidationError('ai_groups_invalid', `${label} 不接受字段:${unknownKeys.join(', ')};只支持 groupId / title / requestIds`, { retryAllowed: false, details: { unknownKeys } });
|
|
93
|
+
throw workflowValidationError('ai_groups_invalid', `${label} 不接受字段:${unknownKeys.join(', ')};只支持 groupId / title / requestIds / summary`, { retryAllowed: false, details: { unknownKeys } });
|
|
46
94
|
}
|
|
95
|
+
const planSummary = typeof record.summary === 'string' ? record.summary.trim() : '';
|
|
47
96
|
const groupId = typeof record.groupId === 'string' ? record.groupId.trim() : '';
|
|
48
97
|
if (!groupId) {
|
|
49
98
|
throw workflowValidationError('ai_groups_invalid', `${label}.groupId 必填且必须是字符串(19 位 workflowId 用数字会失真;值取 stage=group 的 wfIdGroup[i].id)`, { retryAllowed: false });
|
|
@@ -65,7 +114,12 @@ export function readAiGroups(value) {
|
|
|
65
114
|
ids.push(id);
|
|
66
115
|
}
|
|
67
116
|
const bucket = map.get(groupId) ?? [];
|
|
68
|
-
bucket.push({
|
|
117
|
+
bucket.push({
|
|
118
|
+
groupId,
|
|
119
|
+
title,
|
|
120
|
+
requestIds: [...new Set(ids)],
|
|
121
|
+
...(planSummary ? { summary: planSummary } : {}),
|
|
122
|
+
});
|
|
69
123
|
map.set(groupId, bucket);
|
|
70
124
|
});
|
|
71
125
|
return map;
|
|
@@ -159,48 +213,29 @@ function rowKeyForCount(row) {
|
|
|
159
213
|
return `id:${id}`;
|
|
160
214
|
return `tx:${row.requestNameTitle ?? ''}|${row.creater ?? ''}|${row.nodeId ?? ''}`;
|
|
161
215
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
:
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
const
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
};
|
|
186
|
-
const cachedIds = new Set();
|
|
187
|
-
const allRows = [];
|
|
188
|
-
for (const group of groups) {
|
|
189
|
-
for (const row of group.requestDatas ?? []) {
|
|
190
|
-
allRows.push(row);
|
|
191
|
-
if (summaryCache.get(row.requestId, row.lastFeedBackDateTime, summaryMaxChars, promptTagOf(activeRuleFor(group.title)?.requestSummaryPrompt ?? ''), materialTagOfRow(row.requestId, row.lastFeedBackDateTime)))
|
|
192
|
-
cachedIds.add(row.requestId);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
// 详情缓存已经按 `requestId__lastFeedBackDateTime` 落盘,命中时零请求;
|
|
196
|
-
// 所以「摘要命中」不再等于「可以不拉详情」——否则阈值分组会因为没有指标而失效(回归 10.2)。
|
|
197
|
-
const detailIds = [...new Set(allRows.map((row) => row.requestId).filter(Boolean))];
|
|
198
|
-
// stamps 决定详情缓存的键:不传就永远 miss(缓存白建)。
|
|
199
|
-
const stamps = stampsOfRows(allRows);
|
|
200
|
-
const facts = skipDetail
|
|
201
|
-
? new Map()
|
|
202
|
-
: (options.facts ?? (await fetchDetailFacts(env, detailIds, args, stamps)).facts);
|
|
203
|
-
const metrics = skipDetail ? undefined : (options.metrics ?? metricsOfFacts(facts));
|
|
216
|
+
/** 平铺结构:不做 ai 二次分组,`displayItems` 全是单条。 */
|
|
217
|
+
export function flattenFinalGroups(groups) {
|
|
218
|
+
return groups.map((group) => ({
|
|
219
|
+
id: group.id,
|
|
220
|
+
topPriority: group.id === PRIORITY_GROUP_ID,
|
|
221
|
+
title: group.title,
|
|
222
|
+
count: group.count,
|
|
223
|
+
defaultExpanded: true,
|
|
224
|
+
displayItems: requestItemsOf(group.requestDatas),
|
|
225
|
+
}));
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* 在**已有结构**上应用 aigroup 规则的 ai 二次分组(2026-11-03 解耦)。
|
|
229
|
+
*
|
|
230
|
+
* 输入是首次 workflowName 分组产出的平铺结构(条目上可能已经带着单条摘要),
|
|
231
|
+
* 输出带汇总项的结构;既不重新分组、也不碰详情缓存,纯粹做「组内切子组」。
|
|
232
|
+
*/
|
|
233
|
+
export function applySubGroups(groups, options) {
|
|
234
|
+
const plans = options.plans;
|
|
235
|
+
const facts = options.facts;
|
|
236
|
+
const metrics = options.metrics;
|
|
237
|
+
const warnings = options.warnings;
|
|
238
|
+
// 计划本身的合法性:未知 groupId / 对优先组做二次分组,都要硬失败(2026-11-03 迁移到本阶段)。
|
|
204
239
|
const knownGroupIds = new Set(groups.map((group) => group.id));
|
|
205
240
|
for (const groupId of plans?.keys() ?? []) {
|
|
206
241
|
if (!knownGroupIds.has(groupId)) {
|
|
@@ -210,18 +245,6 @@ export async function runSmartGroup(args, env, options = {}) {
|
|
|
210
245
|
throw workflowValidationError('ai_group_priority_unsupported', `「${PRIORITY_GROUP_TITLE}」不做二次分组:目标结构里它只有 displayItems(全是单条展示),没有汇总项;请把它的条目从 aiGroups 里去掉`, { retryAllowed: true });
|
|
211
246
|
}
|
|
212
247
|
}
|
|
213
|
-
// markdown 是中间态的东西,最终结构里没有,相关提示一并丢掉,免得误导调用方。
|
|
214
|
-
const warnings = (base.warnings ?? [])
|
|
215
|
-
.filter((text) => !text.startsWith('markdown 只展示前'));
|
|
216
|
-
// aigroup 规则(default.json + user-intents.json):每个 operation 只加载一次。
|
|
217
|
-
warnings.push(...primeRules(env.ctx).warnings);
|
|
218
|
-
if (skipDetail) {
|
|
219
|
-
warnings.push('withDetailContext=false:本次未拉取任何详情——请假/财务阈值改为按标题识别、跳过整组空判定,也不产 summaryBatches');
|
|
220
|
-
}
|
|
221
|
-
else if (cachedIds.size > 0) {
|
|
222
|
-
warnings.push(`有 ${cachedIds.size} 条命中本地摘要缓存(这些条目不用再调 AI);`
|
|
223
|
-
+ '详情仍按缓存优先取一次——阈值指标只认流程详情数据,跳过详情会让阈值分组失效');
|
|
224
|
-
}
|
|
225
248
|
const finalGroups = groups.map((group) => {
|
|
226
249
|
if (group.id === PRIORITY_GROUP_ID) {
|
|
227
250
|
return {
|
|
@@ -358,7 +381,11 @@ export async function runSmartGroup(args, env, options = {}) {
|
|
|
358
381
|
}
|
|
359
382
|
groupRows = kept;
|
|
360
383
|
}
|
|
361
|
-
subGroups.push({
|
|
384
|
+
subGroups.push({
|
|
385
|
+
title: plan.title,
|
|
386
|
+
requestDatas: groupRows,
|
|
387
|
+
...(plan.summary ? { summary: plan.summary } : {}),
|
|
388
|
+
});
|
|
362
389
|
}
|
|
363
390
|
// 子组 <2 不成立(含有意图的子组):解散,条目平铺(Q5)。
|
|
364
391
|
const kept = subGroups.filter((sub) => sub.requestDatas.length >= 2);
|
|
@@ -400,6 +427,10 @@ export async function runSmartGroup(args, env, options = {}) {
|
|
|
400
427
|
}
|
|
401
428
|
/** 组摘要:规则 `needAI:false` 时用 JSON `template` 渲染,否则留空交 AI。 */
|
|
402
429
|
const renderSubGroupSummary = (sub) => {
|
|
430
|
+
// ① 模型随 aiGroups 一起给的摘要优先(同一次交互产出,见 AiSmartGroupPlan.summary)
|
|
431
|
+
if ((sub.summary ?? '').trim())
|
|
432
|
+
return sub.summary;
|
|
433
|
+
// ② 否则走规则里的固定模板(needAI:false 的确定性组)
|
|
403
434
|
const groupSummary = rule.aiGroup?.groupSummary;
|
|
404
435
|
if (!groupSummary || groupSummary.needAI || !groupSummary.template)
|
|
405
436
|
return '';
|
|
@@ -449,10 +480,67 @@ export async function runSmartGroup(args, env, options = {}) {
|
|
|
449
480
|
displayItems: [...groupItems, ...requestItemsOf(singles)],
|
|
450
481
|
};
|
|
451
482
|
});
|
|
483
|
+
return finalGroups;
|
|
484
|
+
}
|
|
485
|
+
export async function runSmartGroup(args, env, options = {}) {
|
|
486
|
+
const base = runGroup(args);
|
|
487
|
+
const groups = (base.wfIdGroup ?? []);
|
|
488
|
+
/** `null` = 确定性模式;数组(可能为空)= 兼容模式 */
|
|
489
|
+
const plans = args.aiGroups === undefined || args.aiGroups === null
|
|
490
|
+
? undefined
|
|
491
|
+
: readAiGroups(args.aiGroups);
|
|
492
|
+
// 注意:`aiGroups: []` 与「未传」现在等价(默认不分组),不再需要用空数组区分语义。
|
|
493
|
+
// 有侧重点时分组 = 标题语义 + 侧重点;子组顺序沿用模型返回顺序。
|
|
494
|
+
// 无侧重点时普通组按条数降序,请假/财务使用阈值规则。
|
|
495
|
+
const groupFocus = typeof args.groupFocus === 'string' ? args.groupFocus.trim() : '';
|
|
496
|
+
const hasFocus = groupFocus.length > 0;
|
|
497
|
+
// 摘要字数口径:缓存命中要按口径过滤(10 字要求不能吃 50 字的旧摘要)。
|
|
498
|
+
const summaryMaxChars = readBoundedInt(args.summaryMaxChars, "summaryMaxChars", DEFAULT_REQUEST_SUMMARY_MAX, 10, 500);
|
|
499
|
+
// 详情用来算阈值指标 + 判定空上下文;摘要缓存只省 AI,不省详情(阈值指标只认流程详情数据)。
|
|
500
|
+
// withDetailContext=false 时只生成结构、不获取详情(此时没有指标,阈值规则自然不生效)。
|
|
501
|
+
const skipDetail = args.withDetailContext === false;
|
|
502
|
+
const summaryCache = await openSummaryCache(env.ctx);
|
|
503
|
+
// 摘要条目的素材特征值:详情素材变了 ⇒ 旧摘要视为不存在(该条重新写摘要)
|
|
504
|
+
const detailCacheForTag = skipDetail ? null : await openDetailCache(env.ctx);
|
|
505
|
+
const materialTagOfRow = (requestId, stamp) => {
|
|
506
|
+
const entry = detailCacheForTag?.get(requestId, stamp);
|
|
507
|
+
return entry ? detailMaterialTagOf(entry) : undefined;
|
|
508
|
+
};
|
|
509
|
+
const cachedIds = new Set();
|
|
510
|
+
const allRows = [];
|
|
511
|
+
for (const group of groups) {
|
|
512
|
+
for (const row of group.requestDatas ?? []) {
|
|
513
|
+
allRows.push(row);
|
|
514
|
+
if (summaryCache.get(row.requestId, row.lastFeedBackDateTime, summaryMaxChars, promptTagOf(activeRuleFor(group.title)?.requestSummaryPrompt ?? ''), materialTagOfRow(row.requestId, row.lastFeedBackDateTime)))
|
|
515
|
+
cachedIds.add(row.requestId);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
// 详情缓存已经按 `requestId__lastFeedBackDateTime` 落盘,命中时零请求;
|
|
519
|
+
// 所以「摘要命中」不再等于「可以不拉详情」——否则阈值分组会因为没有指标而失效(回归 10.2)。
|
|
520
|
+
const detailIds = [...new Set(allRows.map((row) => row.requestId).filter(Boolean))];
|
|
521
|
+
// stamps 决定详情缓存的键:不传就永远 miss(缓存白建)。
|
|
522
|
+
const stamps = stampsOfRows(allRows);
|
|
523
|
+
const facts = skipDetail
|
|
524
|
+
? new Map()
|
|
525
|
+
: (options.facts ?? (await fetchDetailFacts(env, detailIds, args, stamps)).facts);
|
|
526
|
+
// markdown 是中间态的东西,最终结构里没有,相关提示一并丢掉,免得误导调用方。
|
|
527
|
+
const warnings = (base.warnings ?? [])
|
|
528
|
+
.filter((text) => !text.startsWith('markdown 只展示前'));
|
|
529
|
+
// aigroup 规则(default.json + user-intents.json):每个 operation 只加载一次。
|
|
530
|
+
warnings.push(...primeRules(env.ctx).warnings);
|
|
531
|
+
if (skipDetail) {
|
|
532
|
+
warnings.push('withDetailContext=false:本次未拉取任何详情——请假/财务阈值改为按标题识别、跳过整组空判定,也不产 summaryBatches');
|
|
533
|
+
}
|
|
534
|
+
else if (cachedIds.size > 0) {
|
|
535
|
+
warnings.push(`有 ${cachedIds.size} 条命中本地摘要缓存(这些条目不用再调 AI);`
|
|
536
|
+
+ '详情仍按缓存优先取一次——阈值指标只认流程详情数据,跳过详情会让阈值分组失效');
|
|
537
|
+
}
|
|
452
538
|
// 分组顺序完全沿用第一次 workflowName 分组顺序。
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
539
|
+
// 2026-11-03 用户定:本阶段**只做** workflowName 首次分组,产出平铺 displayItems。
|
|
540
|
+
// ai 二次分组一律由 `stage=subGroup` 调 `applySubGroups()` 完成(旧的就地分组分支已删除)。
|
|
541
|
+
const orderedGroups = flattenFinalGroups(groups);
|
|
542
|
+
if (plans !== undefined) {
|
|
543
|
+
warnings.push('aiGroups 已收到,但本阶段不做二次分组:留到 stage=subGroup 应用');
|
|
456
544
|
}
|
|
457
545
|
return {
|
|
458
546
|
data: {
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { applySubGroups, metricsOfFacts, readAiGroups, rowsOfGroup, totalTodoCount, } from './todoSmartConvert.smartGroup.js';
|
|
2
|
+
import { fetchDetailFacts, stampsOfRows } from './todoSmartConvert.detail.js';
|
|
3
|
+
import { readFinalGroups, readSmartSummaryTitle } from './todoSmartConvert.summary.js';
|
|
4
|
+
/**
|
|
5
|
+
* `stage=subGroup`:**ai 二次分组**(2026-11-03 从 `smartGroup` 拆出来独立成阶段)。
|
|
6
|
+
*
|
|
7
|
+
* 为什么要拆:二次分组曾经和「workflowName 首次分组」绑在同一个 stage 里,导致
|
|
8
|
+
* `focus:false` 的组在**写单条摘要之前**就强制要求模型给出 `aiGroups`——摘要与分组互相耦合。
|
|
9
|
+
* 现在的链路:
|
|
10
|
+
*
|
|
11
|
+
* ```
|
|
12
|
+
* collect → smartGroup(仅首次分组 + 平铺) → summaryContext → summary(单条摘要)
|
|
13
|
+
* → subGroup(本阶段:ai 二次分组) → groupSummaryContext → groupSummary
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* 本阶段只做「组内切子组」:不重新分组、不改摘要、详情全部走缓存(命中率 100%,
|
|
17
|
+
* 因为 collect 已经全量回填)。条目上已有的单条摘要随 `requestDatas` 一起带进子组。
|
|
18
|
+
*/
|
|
19
|
+
export async function runSubGroup(args, env) {
|
|
20
|
+
const groups = await readFinalGroups(args, env);
|
|
21
|
+
const warnings = [];
|
|
22
|
+
// 优先级组是最终结构上的叠加视图,不参与二次分组(导出前由 assemblePriorityGroup 重建)。
|
|
23
|
+
const wfGroups = groups
|
|
24
|
+
.filter((group) => !group.topPriority)
|
|
25
|
+
.map((group) => ({
|
|
26
|
+
id: group.id,
|
|
27
|
+
title: group.title,
|
|
28
|
+
count: group.count,
|
|
29
|
+
requestDatas: rowsOfGroup(group),
|
|
30
|
+
}));
|
|
31
|
+
const plans = args.aiGroups === undefined || args.aiGroups === null
|
|
32
|
+
? undefined
|
|
33
|
+
: readAiGroups(args.aiGroups);
|
|
34
|
+
// 阈值类二次分组(请假天数 / 报销金额 / 计划完成时间)要指标 ⇒ 仍要详情;
|
|
35
|
+
// collect 已全量回填,这里等价于纯缓存命中、零 HTTP。
|
|
36
|
+
const rows = wfGroups.flatMap((group) => group.requestDatas);
|
|
37
|
+
const detailIds = [...new Set(rows.map((row) => row.requestId).filter(Boolean))];
|
|
38
|
+
const facts = new Map();
|
|
39
|
+
let metrics;
|
|
40
|
+
if (args.withDetailContext !== false && detailIds.length > 0) {
|
|
41
|
+
const result = await fetchDetailFacts(env, detailIds, args, stampsOfRows(rows));
|
|
42
|
+
for (const [requestId, fact] of result.facts)
|
|
43
|
+
facts.set(requestId, fact);
|
|
44
|
+
metrics = metricsOfFacts(facts);
|
|
45
|
+
warnings.push(...result.warnings);
|
|
46
|
+
}
|
|
47
|
+
const finalGroups = applySubGroups(wfGroups, { plans, facts, metrics, warnings });
|
|
48
|
+
return {
|
|
49
|
+
data: {
|
|
50
|
+
smartSummaryTitle: readSmartSummaryTitle(args, `您当前有 ${totalTodoCount(groups)} 条待办,清单如下:`),
|
|
51
|
+
totalCount: totalTodoCount(groups),
|
|
52
|
+
wfIdGroup: finalGroups,
|
|
53
|
+
},
|
|
54
|
+
warnings,
|
|
55
|
+
};
|
|
56
|
+
}
|