weaver-work-cli 0.1.38 → 0.1.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -3
- package/dist/chunks/{archive-PDV724YK.js → archive-JHTONRST.js} +556 -66
- package/dist/chunks/{archive-oa-4XE4MNXQ.js → archive-oa-FCHLCPHC.js} +2 -2
- package/dist/chunks/{asset-PDL2QE2N.js → asset-RLIHC7DB.js} +2 -2
- package/dist/chunks/asset-idle-KVOFTLCP.js +670 -0
- package/dist/chunks/{attachment-content-MDDGOE5E.js → attachment-content-RZYDB3MB.js} +3 -3
- package/dist/chunks/{blog-UYR3EUXW.js → blog-42OZTJNP.js} +2 -2
- package/dist/chunks/{caizhilian-LG2X5NMF.js → caizhilian-KRK547K5.js} +2 -2
- package/dist/chunks/{calendar-FG4BAZHE.js → calendar-RTWAMKHD.js} +2 -2
- package/dist/chunks/{chunk-J7USS3ED.js → chunk-4CTESSKI.js} +4 -4
- package/dist/chunks/{chunk-OJQUJZQF.js → chunk-4TEDNCON.js} +45 -5
- package/dist/chunks/chunk-57IECMWX.js +487 -0
- package/dist/chunks/{chunk-ELYDWBZJ.js → chunk-7BRU4EFH.js} +13 -7
- package/dist/chunks/{chunk-6LTFIFLM.js → chunk-7E6MUOEK.js} +2 -2
- package/dist/chunks/{chunk-KKRL3REV.js → chunk-7U577Q7U.js} +2 -2
- package/dist/chunks/{chunk-UJJD4QWF.js → chunk-CAQNBBQA.js} +2 -2
- package/dist/chunks/{chunk-CORFRD7Q.js → chunk-CXRQWOZ5.js} +2 -2
- package/dist/chunks/{chunk-GGJFMNJM.js → chunk-FLS4DPGP.js} +110 -16
- package/dist/chunks/{chunk-JSMFPO43.js → chunk-I566ZYNF.js} +1 -1
- package/dist/chunks/{chunk-6FTTSA45.js → chunk-IZSSVYAG.js} +2 -2
- package/dist/chunks/{chunk-XJY7G4DD.js → chunk-JWRZM2H2.js} +3 -3
- package/dist/chunks/{chunk-NI6GIPIM.js → chunk-LFMSDBZN.js} +529 -395
- package/dist/chunks/{chunk-VRYHGG6O.js → chunk-LN447PY2.js} +92 -51
- package/dist/chunks/{chunk-NR6RKXPA.js → chunk-NETCCARD.js} +4 -2
- package/dist/chunks/{chunk-Z32KWY6G.js → chunk-NJKREDA3.js} +1 -1
- package/dist/chunks/{chunk-FUWVHMVV.js → chunk-SPRHHSVC.js} +2 -2
- package/dist/chunks/{chunk-IZUDA353.js → chunk-T6EGVBKU.js} +1 -1
- package/dist/chunks/{chunk-DMV2TOMH.js → chunk-VNBU55SL.js} +428 -145
- package/dist/chunks/{chunk-UZZBA6PV.js → chunk-WFFNM65B.js} +47 -6
- package/dist/chunks/{chunk-EG6ZU444.js → chunk-ZUX3P6BQ.js} +10 -5
- package/dist/chunks/{conflict-BQZEQ4SP.js → conflict-VR55AOOO.js} +2 -2
- package/dist/chunks/{contract-XIRAFTEV.js → contract-HI4FNPYE.js} +2 -2
- package/dist/chunks/{cowork-BLG62BL2.js → cowork-HJLI353D.js} +12 -5
- package/dist/chunks/{ebuilder-form-NUKG5QW4.js → ebuilder-form-HTGI63UU.js} +3 -3
- package/dist/chunks/{ehr-2STW5ILH.js → ehr-GYXIYH3A.js} +2 -2
- package/dist/chunks/env-DW3G4HUO.js +19 -0
- package/dist/chunks/{esb-UUDQUNKY.js → esb-I5INCEFE.js} +4 -4
- package/dist/chunks/factory-TKXGXS6L.js +30 -0
- package/dist/chunks/{fna-6RCY4NU5.js → fna-K2MHTQ24.js} +2 -2
- package/dist/chunks/group-chat-VYBNMJLG.js +18 -0
- package/dist/chunks/{host-FWDOSRH5.js → host-256GVCAH.js} +3 -3
- package/dist/chunks/{host-EJZFROU3.js → host-D5SERMAJ.js} +3 -3
- package/dist/chunks/{host-RHM3NPXV.js → host-JM4YVHFM.js} +3 -3
- package/dist/chunks/{host-7RXEDMAH.js → host-L3VLITMT.js} +3 -3
- package/dist/chunks/{host-UFF33TWJ.js → host-ZUQTJYYK.js} +3 -3
- package/dist/chunks/{hrm-PKJ6ELSH.js → hrm-VLRBKTZB.js} +3 -3
- package/dist/chunks/{im-YJAXS7CO.js → im-QK3VTLXK.js} +8 -8
- package/dist/chunks/{invoice-HWVSXGX6.js → invoice-T5JTA32H.js} +3 -3
- package/dist/chunks/{jiuchuanhui-L7VKILZB.js → jiuchuanhui-P2FNEQVZ.js} +2 -2
- package/dist/chunks/{lazy-dispatch-NMYEIHRH.js → lazy-dispatch-XRRI46M6.js} +5 -5
- package/dist/chunks/{mail-B42RRU7G.js → mail-3Y7MJ6PS.js} +9 -4
- package/dist/chunks/{meeting-CFYVBJSS.js → meeting-M7LTXGJE.js} +11 -10
- package/dist/chunks/{okr-GCLF6V7W.js → okr-OBC6S67L.js} +2 -2
- package/dist/chunks/{plan-ZVL7GBRI.js → plan-5H6I53SK.js} +2 -2
- package/dist/chunks/{project-4YWAAE2K.js → project-L75QZV4S.js} +2 -2
- package/dist/chunks/{registry-EP4VTHZS.js → registry-KMHCPAAD.js} +9 -8
- package/dist/chunks/{registry-OZSMDVPP.js → registry-QGHFNUWS.js} +2 -2
- package/dist/chunks/{registry-FRJ6H7BT.js → registry-RXPN6BBT.js} +4 -4
- package/dist/chunks/{registry-SG2PGVCF.js → registry-UGRBCBHO.js} +3 -3
- package/dist/chunks/{room-recommend-R3JVUUAC.js → room-recommend-A3UDC3CA.js} +4 -4
- package/dist/chunks/{schedule-NIJBTYDO.js → schedule-5GCACCK7.js} +4 -4
- package/dist/chunks/{workflow-BB24DFBY.js → workflow-M5ZJ7DWF.js} +11 -8
- package/dist/core/output.js +16 -6
- package/dist/index.js +33 -28
- package/dist/internal/e10/auth/commands.js +109 -56
- package/dist/internal/e10/auth/messages.js +1 -0
- package/dist/internal/e10/auth/session.js +11 -1
- package/dist/internal/e10/auth/xiaoe.js +29 -4
- package/dist/internal/e10/auth-recovery.js +13 -6
- package/dist/internal/e10/client.js +13 -0
- package/dist/internal/e10/context.js +24 -3
- package/dist/internal/e10/request-runtime.js +12 -1
- package/dist/shortcuts/archive/host.js +2 -0
- package/dist/shortcuts/archive/manifest.js +1 -1
- package/dist/shortcuts/archive/operations/borrow-list.js +13 -1
- package/dist/shortcuts/archive/operations/flow-url.js +1 -1
- package/dist/shortcuts/archive/operations/prelib.js +472 -0
- package/dist/shortcuts/archive/operations/registry.js +2 -0
- package/dist/shortcuts/archive/operations/upload.js +1 -1
- package/dist/shortcuts/asset-idle/errors.js +44 -0
- package/dist/shortcuts/asset-idle/host.js +161 -0
- package/dist/shortcuts/asset-idle/index.js +114 -0
- package/dist/shortcuts/asset-idle/manifest.js +159 -0
- package/dist/shortcuts/asset-idle/operations/idle.js +47 -0
- package/dist/shortcuts/asset-idle/operations/registry.js +33 -0
- package/dist/shortcuts/asset-idle/operations/shared.js +142 -0
- package/dist/shortcuts/asset-idle/operations/types.js +9 -0
- package/dist/shortcuts/asset-idle/operations.js +15 -0
- package/dist/shortcuts/cowork/operations/item-write.js +10 -2
- 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 +5 -1
- package/dist/shortcuts/meeting/operations/eb/create-flow.js +381 -104
- package/dist/shortcuts/meeting/operations/eb/crm-search.js +87 -5
- package/dist/shortcuts/meeting/operations/eb/flow-memory.js +49 -0
- package/dist/shortcuts/meeting/operations/eb/group-chat.js +583 -0
- package/dist/shortcuts/meeting/operations/eb/index.js +2 -0
- package/dist/shortcuts/meeting/operations/eb/room-recommend.js +19 -3
- 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/workflow/host.js +3 -0
- package/dist/shortcuts/workflow/index.js +1 -1
- package/dist/shortcuts/workflow/operations/form.js +65 -171
- package/dist/shortcuts/workflow/operations/todoSmartConvert/aigroupRules.js +129 -27
- package/dist/shortcuts/workflow/operations/todoSmartConvert/aigroupStore.js +6 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/summaryCache.js +60 -117
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoDetailCache.js +44 -7
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoDetailFetcher.js +37 -29
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.collect.js +48 -8
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.core.js +1 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.detail.js +22 -18
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.group.js +17 -9
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.groupSummary.js +34 -10
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.js +5 -2
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.smartGroup.js +9 -2
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.summary.js +122 -20
- package/docs/_catalog.md +3 -0
- package/docs/asset-idle.md +45 -0
- package/docs/e10-auth.md +16 -1
- package/docs/todo-smart-convert/subgroup-rules.md +1 -1
- package/package.json +1 -1
- package/scripts/mcp-meeting.mjs +234 -0
- package/scripts/package-skill.mjs +2 -0
- package/skill-template/business-info.json +14 -1
- package/skill-template/domains/asset-idle.md +13 -0
- package/skill-template/domains/wenshuding.md +3 -0
- package/skills/weaver-e10-archive-oa/SKILL.md +1 -0
- package/skills/weaver-e10-archive-oa/references/my-borrow.md +49 -0
- package/skills/weaver-e10-asset-idle/SKILL.md +73 -0
- package/skills/weaver-e10-asset-idle/references/idle-list.md +62 -0
- package/skills/weaver-e10-asset-idle/references/idle-stats-and-charts.md +65 -0
- package/skills/weaver-e10-asset-idle/references/source-manifest.json +193 -0
- package/skills/weaver-e10-asset-idle/references/widget-style.md +205 -0
- package/skills/weaver-e10-caizhilian/SKILL.md +29 -11
- package/skills/weaver-e10-caizhilian/references/auth-and-install.md +12 -0
- package/skills/weaver-e10-caizhilian/references/files.md +1 -1
- package/skills/weaver-e10-caizhilian/references/setting-encrypt.md +1 -1
- package/skills/weaver-e10-caizhilian/references/source-manifest.json +45 -45
- package/skills/weaver-e10-cowork/SKILL.md +2 -0
- package/skills/weaver-e10-cowork/references/cowork-write.md +1 -1
- package/skills/weaver-e10-jucailin/SKILL.md +2 -0
- package/skills/weaver-e10-jucailin/references/recruit-read.md +2 -0
- package/skills/weaver-e10-jucailin/references/recruit-write.md +1 -1
- package/skills/weaver-e10-mail/SKILL.md +3 -0
- package/skills/weaver-e10-mail/references/mail-send.md +2 -0
- package/skills/weaver-e10-meeting/SKILL.md +88 -25
- package/skills/weaver-e10-meeting/references/meeting-minute-file.md +25 -11
- package/skills/weaver-e10-meeting.zip +0 -0
- package/skills/weaver-e10-shared/SKILL.md +5 -5
- package/skills/weaver-e10-shared/references/e10-auth-and-session.md +3 -3
- package/skills/weaver-e10-wenshuding/SKILL.md +29 -6
- package/skills/weaver-e10-wenshuding/references/archive-borrow-list.md +14 -0
- package/skills/weaver-e10-wenshuding/references/archive-download.md +2 -0
- package/skills/weaver-e10-wenshuding/references/archive-flow.md +1 -1
- package/skills/weaver-e10-wenshuding/references/archive-prelib-arrange.md +76 -0
- package/skills/weaver-e10-wenshuding/references/archive-prelib-list.md +107 -0
- package/skills/weaver-e10-wenshuding/references/archive-prelib-open.md +59 -0
- package/skills/weaver-e10-wenshuding/references/archive-upload.md +4 -1
- package/skills/weaver-e10-wenshuding/references/source-manifest.json +108 -65
- package/skills/weaver-e10-workflow/SKILL.md +9 -2
- package/skills/weaver-e10-workflow/aigroup/default.json +166 -19
- package/skills/weaver-e10-workflow/references/todo-smart-convert.md +72 -34
- package/skills/weaver-e10-workflow/tools/todo-serve.cjs +113 -25
- package/skills/weaver-e10-workflow/tools/todo-summary.cjs +350 -50
- package/dist/chunks/{chunk-2LV74IWP.js → chunk-E7OK5WD5.js} +5 -5
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
*
|
|
21
21
|
* 归一再交给 `normalizeDetailResponse`(`read.ts`)→ `formData.fields[]` → 业务裁剪
|
|
22
22
|
* (`detailFieldRules.ts`:丢掉 `主送` 这类分发字段;内部留言类只留 `留言内容/发起人/发起时间`)
|
|
23
|
-
* →
|
|
23
|
+
* → aigroup 规则排除清单(`excludeOriginFormDataFields`,fieldName 精确匹配)
|
|
24
|
+
* → 字段名额(≤200 个)与单值截断(≤500 字,2026-10-30 用户定稿)。
|
|
24
25
|
*
|
|
25
26
|
* ★ `isReadRequest:false` 是硬要求:批量取数**不得**把用户的待办标成已读。`workflow.detail`
|
|
26
27
|
* 传的是 `true`,所以它不能被这里复用。
|
|
@@ -33,16 +34,23 @@
|
|
|
33
34
|
*/
|
|
34
35
|
import { WORKFLOW_ENDPOINTS } from '../../endpoints.js';
|
|
35
36
|
import { extractRowMetrics } from './businessRules.js';
|
|
36
|
-
import { dropDistributionDetailFields, isInternalMessageDetail, pickInternalMessageFieldPairs, } from './detailFieldRules.js';
|
|
37
|
-
import {
|
|
37
|
+
import { dropDistributionDetailFields, isInternalMessageDetail, normalizeDetailFieldName, pickInternalMessageFieldPairs, } from './detailFieldRules.js';
|
|
38
|
+
import { excludeFieldsFor, excludeTagOf } from './aigroupRules.js';
|
|
39
|
+
import { normalizeDetailResponse } from '../read.js';
|
|
38
40
|
/** 默认并发上限。 */
|
|
39
41
|
export const DETAIL_BATCH_CONCURRENCY = 10;
|
|
40
42
|
/** 单条详情默认超时。批量取数不是「补卡片」,不套用 5s 的短超时。 */
|
|
41
43
|
export const DETAIL_BATCH_TIMEOUT_MS = 10_000;
|
|
42
44
|
/** 默认重试次数(仅网络/超时;业务错误与登录失效不重试)。 */
|
|
43
45
|
export const DETAIL_BATCH_RETRIES = 1;
|
|
44
|
-
/**
|
|
45
|
-
|
|
46
|
+
/**
|
|
47
|
+
* 详情素材的字段裁剪口径(2026-10-30 用户定稿,取代旧 selectSummaryFields 投影):
|
|
48
|
+
* - 排除清单(aigroup JSON `excludeOriginFormDataFields`)按 fieldName 精确匹配剔除后,
|
|
49
|
+
* **最多保留 200 个字段**(取前 200 个);
|
|
50
|
+
* - 单个字段值超过 **500 字只保留前 500 字**。
|
|
51
|
+
*/
|
|
52
|
+
export const DETAIL_MATERIAL_FIELD_LIMIT = 200;
|
|
53
|
+
export const DETAIL_MATERIAL_VALUE_LIMIT = 500;
|
|
46
54
|
/** 常见 HTML 实体 → 字符。`“` 这类 OA 富文本里到处都是,不解码会原样带进摘要。 */
|
|
47
55
|
const HTML_ENTITIES = {
|
|
48
56
|
amp: '&', lt: '<', gt: '>', quot: '"', apos: "'", nbsp: ' ',
|
|
@@ -126,41 +134,43 @@ async function fetchOneDetail(env, requestId, userId, options) {
|
|
|
126
134
|
const normalized = normalizeDetailResponse(response);
|
|
127
135
|
const base = (normalized?.basicInfo ?? {});
|
|
128
136
|
const rawFields = Array.isArray(normalized?.formData?.fields) ? normalized.formData.fields : [];
|
|
137
|
+
// ⓪ basicInfo 里**无条件剔除** requestTitle / flowName(2026-11-01 用户定稿:给 AI 的素材与
|
|
138
|
+
// 本地缓存都不允许出现这两个字段)——标题在素材里出现会诱导模型"按标题写摘要"、
|
|
139
|
+
// 甚至产出"标题未体现"这类降级话术;标题本身已由列表行(requestNameTitle)承载,素材里不需要。
|
|
140
|
+
const basicInfo = Object.fromEntries(Object.entries(base).filter(([key]) => key !== 'requestTitle' && key !== 'flowName'));
|
|
129
141
|
// ① 分发类字段(`主送` …)**无条件丢弃**:值是几十个部门的收件人清单,对摘要零信息量,
|
|
130
|
-
//
|
|
142
|
+
// 还会撑大喂给模型的上下文。必须在排除清单之前丢(技术卫生,用户不可在 JSON 里恢复)。
|
|
131
143
|
const usableFields = dropDistributionDetailFields(rawFields);
|
|
132
144
|
// ② 内部留言类:只保留 留言内容 / 发起人 / 发起时间(其余是分发元数据,见 `detailFieldRules.ts`)。
|
|
133
145
|
const internalMessage = isInternalMessageDetail(base.workflowName, usableFields.map((field) => field?.fieldName));
|
|
134
|
-
// ③
|
|
146
|
+
// ③ 字段值只清洗(实体解码 / 富文本残留 / 压缩空白),不在这一步截断。
|
|
135
147
|
const cleanedFields = usableFields.map((field) => ({
|
|
136
148
|
...field,
|
|
137
149
|
value: cleanFieldText(String(field?.value ?? '')),
|
|
138
150
|
}));
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const
|
|
151
|
+
// ④ 规则排除清单(aigroup JSON `excludeOriginFormDataFields`):按 fieldName 精确匹配剔除,
|
|
152
|
+
// 两侧都过 `normalizeDetailFieldName`,防【】包裹 / 空白等排版差异绕过精确匹配。
|
|
153
|
+
const excludeFields = excludeFieldsFor(base.workflowName);
|
|
154
|
+
const excludeSet = new Set(excludeFields.map((name) => normalizeDetailFieldName(name)));
|
|
155
|
+
const picked = (internalMessage
|
|
156
|
+
? pickInternalMessageFieldPairs(cleanedFields, base)
|
|
157
|
+
: cleanedFields.filter((field) => !excludeSet.has(normalizeDetailFieldName(field?.fieldName))))
|
|
158
|
+
.slice(0, DETAIL_MATERIAL_FIELD_LIMIT)
|
|
143
159
|
.map((field) => ({
|
|
144
160
|
fieldName: String(field?.fieldName ?? '').trim(),
|
|
145
|
-
value: String(field?.value ?? ''),
|
|
161
|
+
value: String(field?.value ?? '').slice(0, DETAIL_MATERIAL_VALUE_LIMIT),
|
|
146
162
|
}))
|
|
147
163
|
.filter((field) => field.fieldName && field.value);
|
|
148
|
-
//
|
|
164
|
+
// ⑤ 结构化指标(请假天数 / 假别 / 金额 / 费用类别):**只从流程详情数据的字段里取**,
|
|
149
165
|
// 不做标题兜底(2026-10-26 用户口径:详情取不到指标就不进阈值组,标题不能当证据)。
|
|
166
|
+
// 在排除清单**之前**的可用字段上取——排除只管「喂模型的素材」,不影响阈值判定。
|
|
150
167
|
const metrics = extractRowMetrics(usableFields);
|
|
151
168
|
return {
|
|
152
169
|
requestId,
|
|
153
|
-
basicInfo
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
createdAt: String(base.createdAt ?? ''),
|
|
158
|
-
currentNode: String(base.currentNode ?? ''),
|
|
159
|
-
requestLevel: String(base.requestLevel ?? ''),
|
|
160
|
-
flowStatus: String(base.flowStatus ?? ''),
|
|
161
|
-
},
|
|
162
|
-
summaryFields,
|
|
163
|
-
contextAvailable: summaryFields.length > 0,
|
|
170
|
+
basicInfo,
|
|
171
|
+
formFields: picked,
|
|
172
|
+
contextAvailable: picked.length > 0,
|
|
173
|
+
excludeTag: excludeTagOf(excludeFields),
|
|
164
174
|
metrics,
|
|
165
175
|
};
|
|
166
176
|
}
|
|
@@ -230,12 +240,10 @@ export async function fetchRequestDetailsBatch(env, requestIds, options = {}) {
|
|
|
230
240
|
failures.push({ requestId, error: reason });
|
|
231
241
|
details.set(requestId, {
|
|
232
242
|
requestId,
|
|
233
|
-
basicInfo: {
|
|
234
|
-
|
|
235
|
-
currentNode: '', requestLevel: '', flowStatus: '',
|
|
236
|
-
},
|
|
237
|
-
summaryFields: [],
|
|
243
|
+
basicInfo: {},
|
|
244
|
+
formFields: [],
|
|
238
245
|
contextAvailable: false,
|
|
246
|
+
excludeTag: '',
|
|
239
247
|
metrics: {},
|
|
240
248
|
error: reason,
|
|
241
249
|
});
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import { activeRuleFor } from './aigroupRules.js';
|
|
1
|
+
import { activeRuleFor, generalRules } from './aigroupRules.js';
|
|
2
|
+
import { writeFileSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
2
5
|
import { WORKFLOW_ENDPOINTS } from '../../endpoints.js';
|
|
3
6
|
import { saveSearchIndex } from '../../state.js';
|
|
4
7
|
import { buildSearchQuery } from '../read.js';
|
|
5
8
|
import { projectDisplayRows } from './todoListRows.js';
|
|
6
9
|
import { responseData } from '../shared.js';
|
|
7
|
-
import { openSummaryCache, SUMMARY_CACHE_FILE_NAME } from './summaryCache.js';
|
|
10
|
+
import { openSummaryCache, promptTagOf, SUMMARY_CACHE_FILE_NAME } from './summaryCache.js';
|
|
11
|
+
import { openDetailCache, detailMaterialTagOf } from './todoDetailCache.js';
|
|
8
12
|
import { fetchDetailFacts, stampsOfRows } from './todoSmartConvert.detail.js';
|
|
9
13
|
import { GROUP_MIN_ITEMS_FOR_SUBGROUP, isExpiringTodayRow, isOverdueRow, runGroup, } from './todoSmartConvert.group.js';
|
|
10
14
|
import { DEFAULT_TABID, FETCH_PAGE_SIZE, LIST_ENDPOINT_BY_CATEGORY, MAX_FETCH_LIMIT, MAX_FETCH_PAGES, guardListOnlyHost, readBoundedInt, readCategory, readFetchLimit, readPageSize, rowFromAny, } from './todoSmartConvert.core.js';
|
|
@@ -18,16 +22,26 @@ export async function collectRawTodos(args, env) {
|
|
|
18
22
|
// 取数阶段只允许 getTodoListData,getTodo* 分页/统计接口一律拦掉。
|
|
19
23
|
const listEnv = guardListOnlyHost(env);
|
|
20
24
|
const searchArgs = { category };
|
|
21
|
-
for (const key of ['tabid', 'tabName', 'subCategory', 'workflow', 'creator', 'requestName', 'receiveTime', 'requestLevel']) {
|
|
25
|
+
for (const key of ['tabid', 'tabName', 'subCategory', 'isremarks', 'workflow', 'creator', 'requestName', 'receiveTime', 'requestLevel']) {
|
|
22
26
|
const value = args[key];
|
|
23
27
|
if (value !== undefined && value !== null && value !== '')
|
|
24
28
|
searchArgs[key] = value;
|
|
25
29
|
}
|
|
30
|
+
// 通用规则(2026-11-03):默认排除抄送类身份(generalRules.identityFilter.defaultIsremarks)。
|
|
31
|
+
// 用户显式传 isremarks 时不覆盖;「包含抄送 / 只要抄送」等诉求由调用方按提示词显式传 --isremarks。
|
|
32
|
+
// 注意:warnings 在下方声明,默认值注入放到声明之后(见「通用规则默认身份过滤」块)。
|
|
26
33
|
// 显式给 tabid 才走平铺路径(见 DEFAULT_TABID 注释);用户另有子分类条件时原样尊重。
|
|
27
34
|
const explicitTab = ['tabid', 'tabName', 'subCategory'].some((key) => searchArgs[key] !== undefined && String(searchArgs[key]).trim() !== '');
|
|
28
35
|
if (!explicitTab)
|
|
29
36
|
searchArgs.tabid = DEFAULT_TABID;
|
|
30
37
|
const warnings = [];
|
|
38
|
+
if (searchArgs.isremarks === undefined) {
|
|
39
|
+
const defaultIsremarks = generalRules()?.identityFilter?.defaultIsremarks?.trim();
|
|
40
|
+
if (defaultIsremarks) {
|
|
41
|
+
searchArgs.isremarks = defaultIsremarks;
|
|
42
|
+
warnings.push(`已按通用规则默认排除抄送类身份(isremarks=${defaultIsremarks});如需包含抄送,请在提示词中说明`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
31
45
|
if (cappedFrom !== undefined) {
|
|
32
46
|
// 超过上限时显式告知,避免调用方误以为已取全量。
|
|
33
47
|
warnings.push(`单次最多只能取 ${MAX_FETCH_LIMIT} 条(${MAX_FETCH_PAGES} 次请求 × ${FETCH_PAGE_SIZE} 条),`
|
|
@@ -49,6 +63,19 @@ export async function collectRawTodos(args, env) {
|
|
|
49
63
|
const listPath = LIST_ENDPOINT_BY_CATEGORY[category] ?? WORKFLOW_ENDPOINTS.TODO_LIST;
|
|
50
64
|
for (let pageNo = 1; pageNo <= plannedPages; pageNo += 1) {
|
|
51
65
|
const { query } = await buildSearchQuery(listEnv, { ...searchArgs, current: pageNo, pageSize }, 'list');
|
|
66
|
+
// 2026-11-02:isremarks(备注筛选,多个数字英文逗号拼接)只由汇总链路透传,放 params 层。
|
|
67
|
+
// 注入点故意放在 collect 内部而非共享的 buildSearchQuery,保证 workflow.search 的代码路径零改动。
|
|
68
|
+
const isremarks = String(searchArgs.isremarks ?? '').trim();
|
|
69
|
+
if (isremarks !== '') {
|
|
70
|
+
query.params = { ...(query.params || {}), isremarks };
|
|
71
|
+
}
|
|
72
|
+
// 调试留痕(环境变量开启才写):把最终发往列表接口的 body 原样落盘,便于核对拼接结果。
|
|
73
|
+
if (process.env.WEAVER_WORK_CLI_DUMP_LIST_BODY) {
|
|
74
|
+
try {
|
|
75
|
+
writeFileSync(join(tmpdir(), `weaver-list-body-page${pageNo}.json`), JSON.stringify({ path: listPath, body: query }, null, 2));
|
|
76
|
+
}
|
|
77
|
+
catch { /* 留痕失败不影响主流程 */ }
|
|
78
|
+
}
|
|
52
79
|
const response = await listEnv.host.workflowRequest({
|
|
53
80
|
method: 'POST',
|
|
54
81
|
path: listPath,
|
|
@@ -144,11 +171,24 @@ export async function collectRawTodos(args, env) {
|
|
|
144
171
|
wfIdGroup = (grouped.wfIdGroup ?? []);
|
|
145
172
|
// 摘要缓存 key 为 requestId 与最后反馈时间;反馈变化会自然失效。
|
|
146
173
|
const summaryCache = await openSummaryCache(env.ctx);
|
|
147
|
-
//
|
|
148
|
-
|
|
174
|
+
// 字数口径按每条所属工作流的规则算(入参 > 规则 summaryMaxChars > 兜底),
|
|
175
|
+
// 否则不同口径的旧摘要会被错命中/错判失效。
|
|
176
|
+
const collectMaxCharsByGroup = new Map();
|
|
177
|
+
const collectMaxCharsFor = (workflowName) => {
|
|
178
|
+
const key = workflowName ?? '';
|
|
179
|
+
const hit = collectMaxCharsByGroup.get(key);
|
|
180
|
+
if (hit !== undefined)
|
|
181
|
+
return hit;
|
|
182
|
+
const value = readBoundedInt(args.summaryMaxChars ?? activeRuleFor(key)?.summaryMaxChars, "summaryMaxChars", DEFAULT_REQUEST_SUMMARY_MAX, 10, 500);
|
|
183
|
+
collectMaxCharsByGroup.set(key, value);
|
|
184
|
+
return value;
|
|
185
|
+
};
|
|
149
186
|
const cachedSummaryIds = new Set();
|
|
187
|
+
// 摘要条目的素材特征值:详情素材变了 ⇒ 旧摘要失效、重新走 AI(2026-11-01 根治)
|
|
188
|
+
const detailCacheForTag = await openDetailCache(env.ctx);
|
|
150
189
|
for (const row of items) {
|
|
151
|
-
const
|
|
190
|
+
const detailEntry = detailCacheForTag.get(row.requestId, row.lastFeedBackDateTime);
|
|
191
|
+
const cached = summaryCache.get(row.requestId, row.lastFeedBackDateTime, collectMaxCharsFor(row.workflowName), promptTagOf(activeRuleFor(row.workflowName ?? '')?.requestSummaryPrompt ?? ''), detailEntry ? detailMaterialTagOf(detailEntry) : undefined);
|
|
152
192
|
if (!cached)
|
|
153
193
|
continue;
|
|
154
194
|
row.summary = cached;
|
|
@@ -237,8 +277,8 @@ export async function collectRawTodos(args, env) {
|
|
|
237
277
|
+ `(缓存文件:${summaryCache.file ?? SUMMARY_CACHE_FILE_NAME})`);
|
|
238
278
|
}
|
|
239
279
|
if (!summaryCache.inSkillDir) {
|
|
240
|
-
warnings.push('
|
|
241
|
-
+ '
|
|
280
|
+
warnings.push('无当前 skill 上下文(WEAVER_WORK_CLI_SKILL_DIR 未设置):摘要缓存与 aigroup 规则均未读写,'
|
|
281
|
+
+ '本次按「从未生成过」走实时逻辑;请经 skill 的 tools 脚本调用');
|
|
242
282
|
}
|
|
243
283
|
}
|
|
244
284
|
return {
|
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
import { fetchRequestDetailsBatch } from './todoDetailFetcher.js';
|
|
2
2
|
import { readBoundedInt } from './todoSmartConvert.core.js';
|
|
3
|
-
import { openDetailCache } from './todoDetailCache.js';
|
|
3
|
+
import { openDetailCache, DETAIL_CACHE_ENTRY_VERSION } from './todoDetailCache.js';
|
|
4
|
+
import { excludeTagFor } from './aigroupRules.js';
|
|
4
5
|
/**
|
|
5
|
-
*
|
|
6
|
+
* 把「全量 basicInfo + 排除清单过滤后的 formData 字段」拼成喂模型的文本(2026-10-30 用户定稿)。
|
|
6
7
|
*
|
|
7
|
-
*
|
|
8
|
+
* 每行一条 `fieldName:value`——字段归属一目了然;basicInfo 的键值同样按 `key:value` 逐行放在最前。
|
|
9
|
+
* 字段来源与截断口径见 `todoDetailFetcher`(排除清单 → ≤200 个字段 → 单值 ≤500 字)。
|
|
8
10
|
*/
|
|
9
11
|
export function contextOfDetail(detail) {
|
|
10
|
-
|
|
12
|
+
const basicLines = Object.entries(detail.basicInfo ?? {})
|
|
13
|
+
.filter(([, value]) => String(value ?? '').trim() !== '')
|
|
14
|
+
.map(([key, value]) => `${key}:${String(value).trim()}`);
|
|
15
|
+
const fieldLines = (detail.formFields ?? []).map((field) => `${field.fieldName}:${field.value}`);
|
|
16
|
+
return [...basicLines, ...fieldLines].join('\n');
|
|
11
17
|
}
|
|
12
18
|
const EMPTY_DETAIL_STATS = {
|
|
13
19
|
requested: 0,
|
|
@@ -19,9 +25,8 @@ const EMPTY_DETAIL_STATS = {
|
|
|
19
25
|
concurrency: 0,
|
|
20
26
|
durationMs: 0,
|
|
21
27
|
};
|
|
22
|
-
/** 缓存里存的、也是 `DetailFact`
|
|
23
|
-
export
|
|
24
|
-
export const DETAIL_MATERIAL_VALUE_LIMIT = 80;
|
|
28
|
+
/** 缓存里存的、也是 `DetailFact` 用的字段裁剪口径:排除清单 → ≤200 个字段、单值 ≤500 字(定义在 `todoDetailFetcher.ts`)。 */
|
|
29
|
+
export { DETAIL_MATERIAL_FIELD_LIMIT, DETAIL_MATERIAL_VALUE_LIMIT } from './todoDetailFetcher.js';
|
|
25
30
|
/**
|
|
26
31
|
* 从条目集合里建 `requestId → lastFeedBackDateTime`。
|
|
27
32
|
*
|
|
@@ -44,10 +49,7 @@ export function stampsOfRows(rows) {
|
|
|
44
49
|
return stamps;
|
|
45
50
|
}
|
|
46
51
|
function materialFieldsOf(detail) {
|
|
47
|
-
return detail.
|
|
48
|
-
.filter((field) => field.value.length <= 200)
|
|
49
|
-
.slice(0, DETAIL_MATERIAL_FIELD_LIMIT)
|
|
50
|
-
.map((field) => ({ fieldName: field.fieldName, value: field.value.slice(0, DETAIL_MATERIAL_VALUE_LIMIT) }));
|
|
52
|
+
return detail.formFields.map((field) => ({ fieldName: field.fieldName, value: field.value }));
|
|
51
53
|
}
|
|
52
54
|
/**
|
|
53
55
|
* 取流程详情数据:**缓存优先**(键 = `requestId__lastFeedBackDateTime`,7 天),
|
|
@@ -65,17 +67,18 @@ export async function fetchDetailFacts(env, requestIds, args, stamps) {
|
|
|
65
67
|
const startedAt = Date.now();
|
|
66
68
|
const cache = await openDetailCache(env.ctx);
|
|
67
69
|
stats.cacheFile = cache.file;
|
|
68
|
-
// ①
|
|
70
|
+
// ① 缓存命中:不发请求。命中还要核对排除口径:JSON 里的 excludeOriginFormDataFields
|
|
71
|
+
// 变了 ⇒ excludeTag 对不上 ⇒ 视为 miss,按新口径重拉重写(与摘要缓存的 pk 机制同思路)。
|
|
69
72
|
const missing = [];
|
|
70
73
|
for (const requestId of unique) {
|
|
71
74
|
const hit = cache.get(requestId, stamps?.get(requestId));
|
|
72
|
-
if (!hit) {
|
|
75
|
+
if (!hit || hit.excludeTag !== excludeTagFor(hit.basicInfo.workflowName ?? '')) {
|
|
73
76
|
missing.push(requestId);
|
|
74
77
|
continue;
|
|
75
78
|
}
|
|
76
79
|
facts.set(requestId, {
|
|
77
|
-
context: hit.
|
|
78
|
-
contextAvailable: hit.
|
|
80
|
+
context: contextOfDetail({ basicInfo: hit.basicInfo, formFields: hit.fields }),
|
|
81
|
+
contextAvailable: hit.fields.length > 0,
|
|
79
82
|
metrics: hit.metrics,
|
|
80
83
|
fields: hit.fields,
|
|
81
84
|
});
|
|
@@ -94,7 +97,7 @@ export async function fetchDetailFacts(env, requestIds, args, stamps) {
|
|
|
94
97
|
for (const [requestId, detail] of details) {
|
|
95
98
|
const fact = {
|
|
96
99
|
context: contextOfDetail(detail),
|
|
97
|
-
contextAvailable: detail.
|
|
100
|
+
contextAvailable: detail.formFields.length > 0,
|
|
98
101
|
metrics: detail.metrics ?? {},
|
|
99
102
|
fields: materialFieldsOf(detail),
|
|
100
103
|
};
|
|
@@ -106,10 +109,11 @@ export async function fetchDetailFacts(env, requestIds, args, stamps) {
|
|
|
106
109
|
stats.contextEmpty += 1;
|
|
107
110
|
// 详情为空也写缓存:否则这类条目每次都要重拉
|
|
108
111
|
if (cache.set(requestId, stamps?.get(requestId), {
|
|
109
|
-
|
|
110
|
-
|
|
112
|
+
v: DETAIL_CACHE_ENTRY_VERSION,
|
|
113
|
+
basicInfo: detail.basicInfo,
|
|
111
114
|
metrics: fact.metrics,
|
|
112
115
|
fields: fact.fields,
|
|
116
|
+
excludeTag: detail.excludeTag,
|
|
113
117
|
}))
|
|
114
118
|
written += 1;
|
|
115
119
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { workflowValidationError } from '../../errors.js';
|
|
2
|
+
import { generalRules } from './aigroupRules.js';
|
|
2
3
|
import { UNKNOWN_GROUP, groupKeyOf, readGroupBy, readItems, readTopN, assertKnownKeys, } from './todoSmartConvert.core.js';
|
|
3
4
|
import { intentAppliesToKind, } from './businessRules.js';
|
|
4
5
|
/** 紧急程度以 requestLevel=2 或显示文本「紧急」为准;1 级是「重要」。 */
|
|
@@ -115,29 +116,32 @@ export function isExpiringTodayRow(row, now = Date.now()) {
|
|
|
115
116
|
return isExpiringTodayUserOverTime(row.userOverTime, now);
|
|
116
117
|
}
|
|
117
118
|
/**
|
|
118
|
-
*
|
|
119
|
+
* 「建议优先处理的流程」筛选规则(2026-11-03 起可在 `generalRules.priorityGroup.rules` 配置启用子集):
|
|
119
120
|
*
|
|
120
|
-
* 1.
|
|
121
|
-
* 2.
|
|
121
|
+
* 1. **超时**(`overdue`)必收(`userOverTime` / `overTime` 判定,与紧急程度无关);
|
|
122
|
+
* 2. **紧急程度**(`levelFallback`)按 `requestLevel` 取:整批有 `=2`(紧急)就只要 2;没有 2 才退到 `=1`(重要);
|
|
122
123
|
* `=0`(正常)**不因等级入选**——降级链到 1 就停,不倒到 0;
|
|
123
124
|
* 3. 整批连 1 级都没有(全 0)时,等级条件不生效,只按超时筛;此时若一条都没超时,
|
|
124
125
|
* 优先组为空("实在没有紧急的就算了"),不硬凑。
|
|
125
126
|
*
|
|
126
127
|
* 注意这是**筛选**,不是视图叠加:入选条目的 `requestLevel` 只有 2(或 2 缺失时的 1)与超时两类。
|
|
128
|
+
* `enabledRules` 缺省 = 全部启用(与历史行为一致);预留 `expiringToday`(今天到期)等后续扩展。
|
|
127
129
|
*/
|
|
128
|
-
export function selectPriorityRows(rows) {
|
|
130
|
+
export function selectPriorityRows(rows, enabledRules) {
|
|
131
|
+
const rules = enabledRules ?? new Set(['overdue', 'levelFallback']);
|
|
129
132
|
let maxLevel = 0;
|
|
130
133
|
for (const row of rows) {
|
|
131
134
|
if (row.requestLevel > maxLevel)
|
|
132
135
|
maxLevel = row.requestLevel;
|
|
133
136
|
}
|
|
134
|
-
const targetLevel = maxLevel >= 2 ? 2
|
|
137
|
+
const targetLevel = rules.has('levelFallback') && maxLevel >= 2 ? 2
|
|
138
|
+
: rules.has('levelFallback') && maxLevel >= 1 ? 1 : undefined;
|
|
135
139
|
const selected = [];
|
|
136
140
|
let overdueCount = 0;
|
|
137
141
|
let levelCount = 0;
|
|
138
142
|
let bothCount = 0;
|
|
139
143
|
for (const row of rows) {
|
|
140
|
-
const overdue = isOverdueRow(row);
|
|
144
|
+
const overdue = rules.has('overdue') && isOverdueRow(row);
|
|
141
145
|
const byLevel = targetLevel !== undefined && row.requestLevel >= targetLevel;
|
|
142
146
|
if (overdue)
|
|
143
147
|
overdueCount += 1;
|
|
@@ -324,7 +328,11 @@ export function runGroup(args) {
|
|
|
324
328
|
const groupBy = readGroupBy(args.groupBy);
|
|
325
329
|
const topN = readTopN(args.topN);
|
|
326
330
|
// 优先组是叠加视图:筛出的条目**不从**工作流分组剔除,两个视图各自完整。
|
|
327
|
-
|
|
331
|
+
// 2026-11-03:标题与入选规则可在 default.json 的 generalRules.priorityGroup 配置;缺省 = 内置全量规则。
|
|
332
|
+
const pgConfig = generalRules()?.priorityGroup ?? null;
|
|
333
|
+
const pgTitle = pgConfig?.title?.trim() || PRIORITY_GROUP_TITLE;
|
|
334
|
+
const pgRules = new Set(pgConfig?.rules ?? ['overdue', 'levelFallback']);
|
|
335
|
+
const priority = selectPriorityRows(rows, pgRules);
|
|
328
336
|
const priorityRows = sortPriorityRows(priority.rows);
|
|
329
337
|
const restRows = rows;
|
|
330
338
|
const buckets = new Map();
|
|
@@ -360,7 +368,7 @@ export function runGroup(args) {
|
|
|
360
368
|
? [{
|
|
361
369
|
id: PRIORITY_GROUP_ID,
|
|
362
370
|
topPriority: true,
|
|
363
|
-
title:
|
|
371
|
+
title: pgTitle,
|
|
364
372
|
count: priorityRows.length,
|
|
365
373
|
requestDatas: priorityRows.map((row) => toRequestData(row)),
|
|
366
374
|
stats: statsOf(priorityRows),
|
|
@@ -392,7 +400,7 @@ export function runGroup(args) {
|
|
|
392
400
|
: priority.targetLevel === 1
|
|
393
401
|
? '重要(requestLevel=1;本次无 2 级数据,已按规则降级取 1,0 级不取)'
|
|
394
402
|
: '无等级(本次整批没有 ≥1 级的条目,只按超时筛)';
|
|
395
|
-
warnings.push(`「${
|
|
403
|
+
warnings.push(`「${pgTitle}」筛出 ${priorityRows.length} 条:超时 ${priority.overdueCount} 条 / ` +
|
|
396
404
|
`等级命中 ${priority.levelCount} 条(两者都满足 ${priority.bothCount} 条),等级口径=${levelText};` +
|
|
397
405
|
`这 ${priorityRows.length} 条同时保留在各自工作流分组中(叠加视图:Σcount 会大于 totalCount,` +
|
|
398
406
|
`totalCount 按去重条数计 = ${rows.length})`);
|
package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.groupSummary.js
CHANGED
|
@@ -13,6 +13,23 @@ const INTERNAL_MESSAGE_SUMMARY_HINT = '内部留言:只按原文直接概括
|
|
|
13
13
|
const GROUP_SUMMARY_TAIL = GROUP_SUMMARY_TAIL_TEXT;
|
|
14
14
|
/** 组级摘要建议长度上限。 */
|
|
15
15
|
const DEFAULT_GROUP_SUMMARY_MAX = 100;
|
|
16
|
+
/**
|
|
17
|
+
* 组摘要目标字数解析器:**入参 > aigroup 规则(`groupSummaryMaxChars`)> 兜底 100**。
|
|
18
|
+
*
|
|
19
|
+
* 规则是主要来源(2026-10-30 用户要求「摘要规则必须从 JSON 取」),入参只在用户当轮明确给了
|
|
20
|
+
* 口径时覆盖它。按工作流名缓存,避免逐组重复解析规则。
|
|
21
|
+
*/
|
|
22
|
+
function makeGroupMaxCharsResolver(args) {
|
|
23
|
+
const cache = new Map();
|
|
24
|
+
return (workflowName) => {
|
|
25
|
+
const hit = cache.get(workflowName);
|
|
26
|
+
if (hit !== undefined)
|
|
27
|
+
return hit;
|
|
28
|
+
const value = readBoundedInt(args.groupSummaryMaxChars ?? activeRuleFor(workflowName)?.groupSummaryMaxChars, 'groupSummaryMaxChars', DEFAULT_GROUP_SUMMARY_MAX, 20, 1000);
|
|
29
|
+
cache.set(workflowName, value);
|
|
30
|
+
return value;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
16
33
|
/** `¥123` / `¥1,280.50` / `123元` 三种写法;每条只取第一个命中,避免把「14 小时」当金额。 */
|
|
17
34
|
const GROUP_AMOUNT_PATTERNS = [
|
|
18
35
|
/[¥¥]\s*(\d[\d,]*(?:\.\d{1,2})?)/u,
|
|
@@ -45,7 +62,7 @@ export function createrPhraseOf(creators) {
|
|
|
45
62
|
* `smartGroup` 已为确定性路径的二次分组汇总项写入固定 `summary`;AI 路径和内部留言仍可进入
|
|
46
63
|
* `groupSummaryBatches`,由后续阶段补写组摘要。
|
|
47
64
|
*/
|
|
48
|
-
export function collectGroupSummaryBatches(groups, isCtxEmpty,
|
|
65
|
+
export function collectGroupSummaryBatches(groups, isCtxEmpty, maxCharsFor = () => DEFAULT_GROUP_SUMMARY_MAX) {
|
|
49
66
|
const batches = [];
|
|
50
67
|
for (const group of groups) {
|
|
51
68
|
if (group.topPriority)
|
|
@@ -82,6 +99,8 @@ export function collectGroupSummaryBatches(groups, isCtxEmpty, maxChars = DEFAUL
|
|
|
82
99
|
amounts.push(amount);
|
|
83
100
|
}
|
|
84
101
|
const amountReliable = items.length >= 2 && amounts.length === items.length;
|
|
102
|
+
/** 组摘要口径(规则 `groupSummary.prompt`):`needAI:true` 的组必有,没配则不下发 */
|
|
103
|
+
const groupPrompt = (activeRuleFor(group.title)?.aiGroup?.groupSummary?.prompt ?? '').trim();
|
|
85
104
|
batches.push({
|
|
86
105
|
groupId: sub.id,
|
|
87
106
|
parentGroupId: group.id,
|
|
@@ -91,7 +110,8 @@ export function collectGroupSummaryBatches(groups, isCtxEmpty, maxChars = DEFAUL
|
|
|
91
110
|
totalCount: allRows.length,
|
|
92
111
|
kind,
|
|
93
112
|
...(kind === 'internal-message' ? { hint: INTERNAL_MESSAGE_SUMMARY_HINT } : {}),
|
|
94
|
-
|
|
113
|
+
...(groupPrompt ? { prompt: groupPrompt } : {}),
|
|
114
|
+
suggestedMaxChars: maxCharsFor(group.title),
|
|
95
115
|
...(emptySkipped > 0 ? { emptySkipped } : {}),
|
|
96
116
|
items,
|
|
97
117
|
stats: {
|
|
@@ -174,8 +194,9 @@ export async function runGroupSummaryContext(args, env) {
|
|
|
174
194
|
const groups = await readFinalGroups(args, env);
|
|
175
195
|
const { isCtxEmpty, warnings: emptyWarnings } = await loadCtxEmptyPredicate(env, groups, args);
|
|
176
196
|
// 组摘要目标字数:用户给了就用用户的,没给才用默认 100。
|
|
177
|
-
const
|
|
178
|
-
const batches = collectGroupSummaryBatches(groups, isCtxEmpty,
|
|
197
|
+
const groupMaxCharsFor = makeGroupMaxCharsResolver(args);
|
|
198
|
+
const batches = collectGroupSummaryBatches(groups, isCtxEmpty, groupMaxCharsFor);
|
|
199
|
+
const distinctChars = [...new Set(batches.map((batch) => batch.suggestedMaxChars))].sort((left, right) => left - right);
|
|
179
200
|
const totalCount = totalTodoCount(groups);
|
|
180
201
|
const warnings = [...emptyWarnings];
|
|
181
202
|
const skippedEmpty = batches.reduce((sum, batch) => sum + (batch.emptySkipped ?? 0), 0);
|
|
@@ -208,7 +229,7 @@ export async function runGroupSummaryContext(args, env) {
|
|
|
208
229
|
/** CLI 固定文案的汇总项个数(不进批次,`smartGroup` 阶段已写好) */
|
|
209
230
|
deterministicGroups,
|
|
210
231
|
tail: GROUP_SUMMARY_TAIL,
|
|
211
|
-
suggestedMaxChars:
|
|
232
|
+
...(distinctChars.length === 1 ? { suggestedMaxChars: distinctChars[0] } : { suggestedMaxCharsList: distinctChars }),
|
|
212
233
|
},
|
|
213
234
|
},
|
|
214
235
|
warnings,
|
|
@@ -259,8 +280,9 @@ export async function runGroupSummary(args, env) {
|
|
|
259
280
|
const summaries = readAiGroupSummaries(args.aiGroupSummaries);
|
|
260
281
|
const { isCtxEmpty, warnings: emptyWarnings } = await loadCtxEmptyPredicate(env, groups, args);
|
|
261
282
|
// 组摘要目标字数:与 `runGroupSummaryContext` 保持同一口径(用户给了就用用户的)。
|
|
262
|
-
const
|
|
263
|
-
const batches = collectGroupSummaryBatches(groups, isCtxEmpty,
|
|
283
|
+
const groupMaxCharsFor = makeGroupMaxCharsResolver(args);
|
|
284
|
+
const batches = collectGroupSummaryBatches(groups, isCtxEmpty, groupMaxCharsFor);
|
|
285
|
+
const distinctChars = [...new Set(batches.map((batch) => batch.suggestedMaxChars))].sort((left, right) => left - right);
|
|
264
286
|
const knownGroupIds = new Set(batches.map((batch) => batch.groupId));
|
|
265
287
|
for (const groupId of summaries.keys()) {
|
|
266
288
|
if (!knownGroupIds.has(groupId)) {
|
|
@@ -282,6 +304,8 @@ export async function runGroupSummary(args, env) {
|
|
|
282
304
|
if (group.topPriority)
|
|
283
305
|
continue;
|
|
284
306
|
const kind = detectBusinessKind(group.title);
|
|
307
|
+
/** 本组的组摘要字数口径(入参 > 规则 > 兜底) */
|
|
308
|
+
const groupMaxChars = groupMaxCharsFor(group.title);
|
|
285
309
|
for (const sub of groupItemsOf(group)) {
|
|
286
310
|
if (isDeterministicGroupKind(kind)) {
|
|
287
311
|
// ★ 固定文案:`smartGroup` 阶段已写好,这里**不覆盖**。
|
|
@@ -294,7 +318,7 @@ export async function runGroupSummary(args, env) {
|
|
|
294
318
|
}
|
|
295
319
|
filled += 1;
|
|
296
320
|
summarizedGroupCount += 1;
|
|
297
|
-
if (summary.length >
|
|
321
|
+
if (summary.length > groupMaxChars)
|
|
298
322
|
tooLong += 1;
|
|
299
323
|
const batch = batches.find((item) => item.groupId === sub.id);
|
|
300
324
|
if (batch && batch.stats.createrPhrase && !summary.includes(batch.stats.createrPhrase)) {
|
|
@@ -314,7 +338,7 @@ export async function runGroupSummary(args, env) {
|
|
|
314
338
|
}
|
|
315
339
|
filled += 1;
|
|
316
340
|
summarizedGroupCount += 1;
|
|
317
|
-
if (summary.length >
|
|
341
|
+
if (summary.length > groupMaxChars)
|
|
318
342
|
tooLong += 1;
|
|
319
343
|
const batch = batches.find((item) => item.groupId === sub.id);
|
|
320
344
|
if (batch && batch.stats.createrPhrase && !summary.includes(batch.stats.createrPhrase)) {
|
|
@@ -325,7 +349,7 @@ export async function runGroupSummary(args, env) {
|
|
|
325
349
|
if (empty > 0)
|
|
326
350
|
warnings.push(`有 ${empty} 个汇总项没有拿到组摘要,summary 保持空串(未编造)`);
|
|
327
351
|
if (tooLong > 0) {
|
|
328
|
-
warnings.push(`有 ${tooLong} 个组摘要超过建议的 ${
|
|
352
|
+
warnings.push(`有 ${tooLong} 个组摘要超过建议的 ${distinctChars.length > 0 ? distinctChars.join('/') : DEFAULT_GROUP_SUMMARY_MAX} 字,已原样保留(CLI 不改写文案)`);
|
|
329
353
|
}
|
|
330
354
|
if (missingCreater > 0) {
|
|
331
355
|
warnings.push(`有 ${missingCreater} 个组摘要没有包含算好的「由 … 发起」短语(发起人由 CLI 统计,模型不该自己数),`
|
|
@@ -80,6 +80,7 @@ const inputSchema = schemaOf({
|
|
|
80
80
|
tabid: { type: 'string' },
|
|
81
81
|
tabName: { type: 'string' },
|
|
82
82
|
subCategory: { type: 'string' },
|
|
83
|
+
isremarks: { type: 'string', description: '备注筛选(getTodoListData params.isremarks),多个数字用英文逗号拼接,仅作用于本汇总取数' },
|
|
83
84
|
workflow: { type: 'array', items: { type: 'string' } },
|
|
84
85
|
creator: { type: 'string' },
|
|
85
86
|
requestName: { type: 'string' },
|
|
@@ -88,8 +89,10 @@ const inputSchema = schemaOf({
|
|
|
88
89
|
groupFocus: { type: 'string' },
|
|
89
90
|
maxCharsPerBatch: { type: 'integer', minimum: 500, maximum: 200000, default: 24000 },
|
|
90
91
|
maxItemsPerBatch: { type: 'integer', minimum: 1, maximum: 500, default: 100 },
|
|
91
|
-
|
|
92
|
-
|
|
92
|
+
// 字数口径不设 schema 默认:默认值来自 aigroup 规则(default.json 的 defaults),
|
|
93
|
+
// 入参只在用户当轮明确给口径时覆盖规则(2026-10-30 用户要求「摘要规则必须从 JSON 取」)。
|
|
94
|
+
summaryMaxChars: { type: 'integer', minimum: 10, maximum: 500 },
|
|
95
|
+
groupSummaryMaxChars: { type: 'integer', minimum: 20, maximum: 1000 },
|
|
93
96
|
detailConcurrency: { type: 'integer', minimum: 1, maximum: 32, default: 10 },
|
|
94
97
|
detailTimeoutMs: { type: 'integer', minimum: 1000, maximum: 60000, default: 10000 },
|
|
95
98
|
skillTiming: { type: 'object' },
|
|
@@ -4,7 +4,8 @@ import { asRecord, readBoundedInt, } from './todoSmartConvert.core.js';
|
|
|
4
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
6
|
import { activeRuleFor, evaluateFocusKeywords, primeRules } from './aigroupRules.js';
|
|
7
|
-
import { openSummaryCache } from './summaryCache.js';
|
|
7
|
+
import { openSummaryCache, promptTagOf } from './summaryCache.js';
|
|
8
|
+
import { openDetailCache, detailMaterialTagOf } from './todoDetailCache.js';
|
|
8
9
|
const DEFAULT_REQUEST_SUMMARY_MAX = 50;
|
|
9
10
|
/** 把一批条目包成「单独展示」项(优先组、以及普通组里没同类可合并的条目都用它)。 */
|
|
10
11
|
export function requestItemsOf(rows) {
|
|
@@ -176,12 +177,18 @@ export async function runSmartGroup(args, env, options = {}) {
|
|
|
176
177
|
// withDetailContext=false 时只生成结构、不获取详情(此时没有指标,阈值规则自然不生效)。
|
|
177
178
|
const skipDetail = args.withDetailContext === false;
|
|
178
179
|
const summaryCache = await openSummaryCache(env.ctx);
|
|
180
|
+
// 摘要条目的素材特征值:详情素材变了 ⇒ 旧摘要视为不存在(该条重新写摘要)
|
|
181
|
+
const detailCacheForTag = skipDetail ? null : await openDetailCache(env.ctx);
|
|
182
|
+
const materialTagOfRow = (requestId, stamp) => {
|
|
183
|
+
const entry = detailCacheForTag?.get(requestId, stamp);
|
|
184
|
+
return entry ? detailMaterialTagOf(entry) : undefined;
|
|
185
|
+
};
|
|
179
186
|
const cachedIds = new Set();
|
|
180
187
|
const allRows = [];
|
|
181
188
|
for (const group of groups) {
|
|
182
189
|
for (const row of group.requestDatas ?? []) {
|
|
183
190
|
allRows.push(row);
|
|
184
|
-
if (summaryCache.get(row.requestId, row.lastFeedBackDateTime, summaryMaxChars))
|
|
191
|
+
if (summaryCache.get(row.requestId, row.lastFeedBackDateTime, summaryMaxChars, promptTagOf(activeRuleFor(group.title)?.requestSummaryPrompt ?? ''), materialTagOfRow(row.requestId, row.lastFeedBackDateTime)))
|
|
185
192
|
cachedIds.add(row.requestId);
|
|
186
193
|
}
|
|
187
194
|
}
|