weaver-work-cli 0.1.28 → 0.1.31
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/dist/chunks/archive-U766FTFH.js +2537 -0
- package/dist/chunks/archive-oa-DMWEKX3L.js +1317 -0
- package/dist/chunks/asset-TCR7VDEV.js +2109 -0
- package/dist/chunks/attachment-content-AQNX5N3S.js +23 -0
- package/dist/chunks/blog-EVZWGDVM.js +2157 -0
- package/dist/chunks/caizhilian-JOWS6SL5.js +10087 -0
- package/dist/chunks/calendar-5WNCV66Q.js +1195 -0
- package/dist/chunks/chunk-2DUV44UX.js +15606 -0
- package/dist/chunks/chunk-324YPTC2.js +1607 -0
- package/dist/chunks/chunk-65QMVWHS.js +231 -0
- package/dist/chunks/chunk-6V7ZLF34.js +387 -0
- package/dist/chunks/chunk-AC7KJWC5.js +358 -0
- package/dist/chunks/chunk-AJ62BGAW.js +29 -0
- package/dist/chunks/chunk-BUSC6UAE.js +43 -0
- package/dist/chunks/chunk-CVZ2JOSW.js +62 -0
- package/dist/chunks/chunk-D4LTJSJD.js +477 -0
- package/dist/chunks/chunk-EG6ZU444.js +654 -0
- package/dist/chunks/chunk-EZALDOQH.js +383 -0
- package/dist/chunks/chunk-FMPJJYYI.js +106 -0
- package/dist/chunks/chunk-G2JSIUWF.js +769 -0
- package/dist/chunks/chunk-GCG2NMWR.js +703 -0
- package/dist/chunks/chunk-GPMFD4RD.js +75 -0
- package/dist/chunks/chunk-H3WDBYJW.js +508 -0
- package/dist/chunks/chunk-H6XSTSLM.js +2357 -0
- package/dist/chunks/chunk-J7RJTZSV.js +5556 -0
- package/dist/chunks/chunk-LUSPKZKY.js +53 -0
- package/dist/chunks/chunk-NIAJ5ZCG.js +73 -0
- package/dist/chunks/chunk-NOODWMCL.js +584 -0
- package/dist/chunks/chunk-OOUTBLZ4.js +51 -0
- package/dist/chunks/chunk-P26ZWSKU.js +73 -0
- package/dist/chunks/chunk-QWY6PGL6.js +3472 -0
- package/dist/chunks/chunk-R2IJAK7T.js +464 -0
- package/dist/chunks/chunk-R7O5PUWO.js +205 -0
- package/dist/chunks/chunk-RCGYCOMG.js +6962 -0
- package/dist/chunks/chunk-VZTXRG6X.js +403 -0
- package/dist/chunks/chunk-WHD4XMRP.js +161 -0
- package/dist/chunks/chunk-YLCYX7LE.js +371 -0
- package/dist/chunks/chunk-ZEDWSH32.js +68 -0
- package/dist/chunks/continuation-2CKSBLA6.js +11 -0
- package/dist/chunks/continuation-HZIZRZBX.js +13 -0
- package/dist/chunks/continuation-NLECEJPX.js +11 -0
- package/dist/chunks/ebuilder-form-NJ4YK6CC.js +2276 -0
- package/dist/chunks/ehr-MMYN36VA.js +5241 -0
- package/dist/chunks/employee-SAK2QYXX.js +11 -0
- package/dist/chunks/esb-HIWHOU7Q.js +141 -0
- package/dist/chunks/fna-KBACQTA5.js +1444 -0
- package/dist/chunks/host-3OHS5QUX.js +21 -0
- package/dist/chunks/host-IG6KEH5Q.js +19 -0
- package/dist/chunks/host-KQL5N4HO.js +23 -0
- package/dist/chunks/host-LLSZQCWR.js +17 -0
- package/dist/chunks/hrm-LYEC67ZQ.js +244 -0
- package/dist/chunks/im-RN2UKAEU.js +252 -0
- package/dist/chunks/invoice-EEPHLAF6.js +5179 -0
- package/dist/chunks/jiuchuanhui-WVVZYSJE.js +1940 -0
- package/dist/chunks/lazy-dispatch-MDOFFL2S.js +28 -0
- package/dist/chunks/mail-LNQLTF6L.js +2695 -0
- package/dist/chunks/meeting-XSD3BDVQ.js +504 -0
- package/dist/chunks/msg-parse-4GSQ2WTO.js +41 -0
- package/dist/chunks/okr-TMWTIU2K.js +2482 -0
- package/dist/chunks/plan-A2FSFA7T.js +2025 -0
- package/dist/chunks/project-T26QLBQF.js +1361 -0
- package/dist/chunks/registry-2VLUAESA.js +14 -0
- package/dist/chunks/registry-JCFEYXOE.js +18 -0
- package/dist/chunks/registry-NYPYMZXG.js +16 -0
- package/dist/chunks/registry-Q7OQJ3SU.js +19 -0
- package/dist/chunks/registry-ZJMJVWFA.js +19 -0
- package/dist/chunks/room-recommend-UR4ZDZS6.js +20 -0
- package/dist/chunks/schedule-5L2PM4M6.js +24 -0
- package/dist/chunks/state-Y2XBP6YE.js +15 -0
- package/dist/chunks/workflow-TR6PG2SL.js +245 -0
- package/dist/cmd/argv.js +45 -0
- package/dist/cmd/docs/index.js +3 -3
- package/dist/cmd/lazy-dispatch.js +20 -0
- package/dist/cmd/root.js +2 -4
- package/dist/cmd/setup/index.js +2 -2
- package/dist/cmd/skills/index.js +2 -2
- package/dist/core/package-paths.js +47 -0
- package/dist/index.js +163 -7
- package/dist/internal/e10/auth/xiaoe.js +3 -3
- package/dist/internal/e10/request-runtime.js +18 -62
- package/dist/internal/skills/detector.js +2 -2
- package/dist/shortcuts/contract/data/performance-catalog.js +1306 -0
- package/dist/shortcuts/contract/host.js +91 -35
- package/dist/shortcuts/contract/index.js +213 -0
- package/dist/shortcuts/contract/manifest.js +928 -0
- package/dist/shortcuts/contract/ooxml/docx.js +295 -0
- package/dist/shortcuts/contract/ooxml/xlsx.js +221 -0
- package/dist/shortcuts/contract/ooxml/zip.js +158 -0
- package/dist/shortcuts/contract/operations/assistant.js +1156 -0
- package/dist/shortcuts/contract/operations/clause-library.js +522 -0
- package/dist/shortcuts/contract/operations/compare.js +193 -0
- package/dist/shortcuts/contract/operations/credit-page.js +34 -0
- package/dist/shortcuts/contract/operations/flow.js +98 -0
- package/dist/shortcuts/contract/operations/page.js +66 -0
- package/dist/shortcuts/contract/operations/performance.js +733 -0
- package/dist/shortcuts/contract/operations/quote.js +557 -0
- package/dist/shortcuts/contract/operations/recommend.js +78 -0
- package/dist/shortcuts/contract/operations/registry.js +18 -0
- package/dist/shortcuts/contract/operations/review.js +83 -0
- package/dist/shortcuts/contract/operations/shared.js +37 -0
- package/dist/shortcuts/contract/operations/upload.js +108 -0
- package/dist/shortcuts/contract/operations/write.js +3 -2
- package/dist/shortcuts/index.js +140 -40
- package/dist/shortcuts/invoice/host.js +2 -1
- package/dist/shortcuts/invoice/manifest.js +36 -12
- package/dist/shortcuts/invoice/operations/issuing-qys.js +132 -36
- package/dist/shortcuts/invoice/operations/reim-auto.js +9 -0
- package/dist/shortcuts/invoice/operations/reim.js +81 -5
- package/dist/shortcuts/invoice/operations/shared.js +12 -5
- package/dist/shortcuts/meeting/host.js +45 -3
- package/dist/shortcuts/meeting/invitation/docx.js +442 -0
- package/dist/shortcuts/meeting/operations/attachment-content.js +418 -0
- package/dist/shortcuts/meeting/operations/attachment.js +442 -0
- package/dist/shortcuts/meeting/operations/eb/cache.js +57 -0
- package/dist/shortcuts/meeting/operations/eb/catalog.js +343 -28
- package/dist/shortcuts/meeting/operations/eb/conflict.js +372 -11
- package/dist/shortcuts/meeting/operations/eb/create-flow.js +710 -0
- package/dist/shortcuts/meeting/operations/eb/decision.js +47 -1
- package/dist/shortcuts/meeting/operations/eb/factory.js +129 -11
- package/dist/shortcuts/meeting/operations/eb/index.js +122 -2
- package/dist/shortcuts/meeting/operations/eb/room-recommend.js +378 -0
- package/dist/shortcuts/meeting/operations/eb/room-summary.js +84 -0
- package/dist/shortcuts/meeting/operations/eb/schedule.js +450 -0
- package/dist/shortcuts/meeting/operations/from-group-chat.js +576 -0
- package/dist/shortcuts/meeting/operations/invitation.js +458 -0
- package/dist/shortcuts/meeting/operations/minute-content.js +266 -0
- package/dist/shortcuts/meeting/operations/pipeline.js +489 -0
- package/dist/shortcuts/meeting/operations/qrcode.js +131 -0
- package/dist/shortcuts/meeting/operations/receipts.js +1 -1
- package/dist/shortcuts/meeting/operations/registry.js +18 -0
- package/dist/shortcuts/meeting/operations/summary.js +276 -0
- package/dist/shortcuts/meeting/operations/task-notify.js +325 -0
- package/dist/shortcuts/meeting/operations.js +42 -1
- package/dist/shortcuts/meeting/qr/encoder.js +427 -0
- package/dist/shortcuts/meeting/qr/render.js +103 -0
- package/dist/shortcuts/workflow/index.js +3 -0
- package/dist/shortcuts/workflow/operations/form.js +17 -5
- package/dist/shortcuts/workflow/operations/read.js +13 -6
- package/dist/shortcuts/workflow/operations/registry.js +2 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/businessRules.js +331 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/detailFieldRules.js +133 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/summaryCache.js +326 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoDetailCache.js +113 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoDetailFetcher.js +259 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoListRows.js +64 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoOutputPlanner.js +28 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.aggregate.js +70 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.collect.js +267 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.core.js +392 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.detail.js +165 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.group.js +448 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.groupSummary.js +348 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.js +222 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.pipeline.js +66 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.smartGroup.js +502 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.summary.js +540 -0
- package/docs/_catalog.md +1 -0
- package/docs/contract.md +39 -4
- package/docs/invoice.md +7 -7
- package/docs/todo-smart-convert/README.md +171 -0
- package/docs/todo-smart-convert/data-contract.md +236 -0
- package/docs/todo-smart-convert/mock/ai-todo-query-target.json +209 -0
- package/docs/todo-smart-convert/subgroup-rules.md +245 -0
- package/package.json +3 -2
- package/scripts/bundle.mjs +63 -0
- package/scripts/package-skill.mjs +1 -1
- package/skill-template/business-info.json +25 -4
- package/skill-template/domains/jinchengda.md +16 -3
- package/skill-template/domains/yepiaotong.md +3 -3
- package/skills/weaver-e10-jinchengda/SKILL.md +111 -31
- package/skills/weaver-e10-jinchengda/references/contract-agent-entry.md +7 -13
- package/skills/weaver-e10-jinchengda/references/contract-ai-assistant.md +150 -0
- package/skills/weaver-e10-jinchengda/references/contract-ai-review.md +87 -0
- package/skills/weaver-e10-jinchengda/references/contract-clause-library-crud.md +111 -0
- package/skills/weaver-e10-jinchengda/references/contract-clause-recommender.md +0 -15
- package/skills/weaver-e10-jinchengda/references/contract-clause-reference-save.md +0 -13
- package/skills/weaver-e10-jinchengda/references/contract-credit-page.md +50 -0
- package/skills/weaver-e10-jinchengda/references/contract-disabled-capabilities.md +4 -2
- package/skills/weaver-e10-jinchengda/references/contract-doc-compare.md +74 -0
- package/skills/weaver-e10-jinchengda/references/contract-flow-launch.md +84 -0
- package/skills/weaver-e10-jinchengda/references/contract-performance-query.md +29 -0
- package/skills/weaver-e10-jinchengda/references/contract-quote-to-contract.md +28 -0
- package/skills/weaver-e10-jinchengda/references/contract-request-status.md +2 -21
- package/skills/weaver-e10-jinchengda/references/contract-request-write.md +0 -24
- package/skills/weaver-e10-jinchengda/references/contract-signing-subjects.md +0 -15
- package/skills/weaver-e10-jinchengda/references/contract-subject-create.md +0 -17
- package/skills/weaver-e10-jinchengda/references/contract-template-recommend.md +70 -0
- package/skills/weaver-e10-jinchengda/references/contract-template-selector.md +0 -15
- package/skills/weaver-e10-jinchengda/references/contract-workflow-discovery.md +0 -15
- package/skills/weaver-e10-jinchengda/references/source-manifest.json +1168 -152
- package/skills/weaver-e10-meeting/SKILL.md +493 -17
- package/skills/weaver-e10-meeting/references/meeting-change.md +4 -4
- package/skills/weaver-e10-meeting/references/meeting-create.md +6 -6
- package/skills/weaver-e10-meeting/references/meeting-eb.md +503 -27
- package/skills/weaver-e10-meeting/references/meeting-invitation.md +113 -0
- package/skills/weaver-e10-meeting/references/meeting-lifecycle.md +6 -6
- package/skills/weaver-e10-meeting/references/meeting-minute-file.md +425 -0
- package/skills/weaver-e10-meeting/references/meeting-queries.md +4 -4
- package/skills/weaver-e10-meeting/references/meeting-receipts-signs.md +15 -15
- package/skills/weaver-e10-meeting/references/meeting-verification.md +19 -11
- package/skills/weaver-e10-meeting.zip +0 -0
- package/skills/weaver-e10-workflow/SKILL.md +23 -1
- package/skills/weaver-e10-workflow/references/todo-smart-convert.md +282 -0
- package/skills/weaver-e10-workflow/scripts/todo-summary.cjs +695 -0
- package/skills/weaver-e10-yepiaotong/SKILL.md +15 -11
- package/skills/weaver-e10-yepiaotong/references/invoice-enterprise-list.md +1 -1
- package/skills/weaver-e10-yepiaotong/references/invoice-issuing-examples.md +10 -7
- package/skills/weaver-e10-yepiaotong/references/invoice-issuing-form-fill-rules.md +41 -14
- package/skills/weaver-e10-yepiaotong/references/invoice-issuing-workflow.md +54 -26
- package/skills/weaver-e10-yepiaotong/references/invoice-personal-list.md +6 -5
- package/skills/weaver-e10-yepiaotong/references/invoice-reim.md +14 -11
- package/skills/weaver-e10-yepiaotong/references/reim-api-reference.md +97 -56
- package/skills/weaver-e10-yepiaotong/references/reim-examples.md +84 -60
- package/skills/weaver-e10-yepiaotong/references/reim-form-fill-rules.md +142 -105
- package/skills/weaver-e10-yepiaotong/references/reim-matching-rules.md +53 -194
- package/skills/weaver-e10-yepiaotong/references/reim-phone-subsidy-rules.md +7 -7
- package/skills/weaver-e10-yepiaotong/references/reim-workflow.md +164 -72
- package/skills/weaver-e10-yepiaotong/references/source-manifest.json +8 -2
- package/templates/meeting/invitation.docx +0 -0
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
import { workflowValidationError } from '../../errors.js';
|
|
2
|
+
import { asRecord, readBoundedInt } from './todoSmartConvert.core.js';
|
|
3
|
+
import { orderGroupsByIntent, readGroupOrder } from './todoSmartConvert.group.js';
|
|
4
|
+
import { detectBusinessKind, needsRequestSummary, usesTitleSummary } from './businessRules.js';
|
|
5
|
+
import { openSummaryCache } from './summaryCache.js';
|
|
6
|
+
import { loadDetailContextProvider, stampsOfRows } from './todoSmartConvert.detail.js';
|
|
7
|
+
import { requestItemsOf, rowsOfGroup, runSmartGroup, totalTodoCount, } from './todoSmartConvert.smartGroup.js';
|
|
8
|
+
const DEFAULT_MAX_CHARS_PER_BATCH = 24_000;
|
|
9
|
+
const DEFAULT_MAX_ITEMS_PER_BATCH = 100;
|
|
10
|
+
const DEFAULT_REQUEST_SUMMARY_MAX = 50;
|
|
11
|
+
const INTERNAL_MESSAGE_SUMMARY_HINT = '内部留言:只按原文直接概括「谁说了什么」,不要过度解读、不要推测动机或影响,严格按本批 suggestedMaxChars 控制字数';
|
|
12
|
+
/** 兼容两种拼写:新结构用 `smartSummaryTitle`,早前产物里是 `smartSmmaryTitle`(少一个 m)。 */
|
|
13
|
+
export function readSmartSummaryTitle(args, fallback) {
|
|
14
|
+
const value = args.smartSummaryTitle ?? args.smartSmmaryTitle;
|
|
15
|
+
return typeof value === 'string' && value.trim() !== '' ? value : fallback;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 读 `displayItems`,同时兼容旧回灌结构。
|
|
19
|
+
*
|
|
20
|
+
* 新结构:`[{type:"request",requestData}|{type:"group",id,title,count,summary,defaultExpanded,requestDatas}]`
|
|
21
|
+
* 旧结构:优先组 `requestDatas[]`、普通组 `smartGroup[]`(无 `type`、无 `defaultExpanded`)。
|
|
22
|
+
*
|
|
23
|
+
* 旧结构只可能出现在回灌数据里。这里统一归一成新结构,
|
|
24
|
+
* 免得为了几个字段名把已有产物全部作废。`defaultExpanded` 一律**重算**、不信任输入值。
|
|
25
|
+
*/
|
|
26
|
+
export function readDisplayItems(record, index, title, topPriority) {
|
|
27
|
+
if (Array.isArray(record.displayItems)) {
|
|
28
|
+
return record.displayItems.map((rawItem, itemIndex) => {
|
|
29
|
+
const label = `wfIdGroup[${index}]「${title}」.displayItems[${itemIndex}]`;
|
|
30
|
+
const item = asRecord(rawItem);
|
|
31
|
+
if (!item) {
|
|
32
|
+
throw workflowValidationError('wfidgroup_invalid', `${label} 必须是对象`, { retryAllowed: false });
|
|
33
|
+
}
|
|
34
|
+
if (item.type === 'request') {
|
|
35
|
+
const requestData = asRecord(item.requestData);
|
|
36
|
+
if (!requestData) {
|
|
37
|
+
throw workflowValidationError('wfidgroup_invalid', `${label} 是 type:"request",必须带 requestData 对象(一条待办的完整字段)`, { retryAllowed: false });
|
|
38
|
+
}
|
|
39
|
+
return { type: 'request', requestData: requestData };
|
|
40
|
+
}
|
|
41
|
+
if (item.type === 'group') {
|
|
42
|
+
const requestDatas = Array.isArray(item.requestDatas) ? item.requestDatas : null;
|
|
43
|
+
if (!requestDatas) {
|
|
44
|
+
throw workflowValidationError('wfidgroup_invalid', `${label} 是 type:"group",必须带 requestDatas 数组(折叠的明细)`, { retryAllowed: false });
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
type: 'group',
|
|
48
|
+
id: typeof item.id === 'string' ? item.id : '',
|
|
49
|
+
title: typeof item.title === 'string' ? item.title : '',
|
|
50
|
+
count: requestDatas.length,
|
|
51
|
+
summary: typeof item.summary === 'string' ? item.summary : '',
|
|
52
|
+
defaultExpanded: false,
|
|
53
|
+
requestDatas,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
throw workflowValidationError('wfidgroup_invalid', `${label}.type 只支持 "request"(单独一条)/ "group"(汇总项),收到 ${JSON.stringify(item.type)}`, { retryAllowed: false });
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
// —— 旧结构兜底 ——
|
|
60
|
+
if (topPriority) {
|
|
61
|
+
const requestDatas = Array.isArray(record.requestDatas) ? record.requestDatas : null;
|
|
62
|
+
if (!requestDatas) {
|
|
63
|
+
throw workflowValidationError('wfidgroup_invalid', `wfIdGroup[${index}]「${title}」是优先组,必须带 displayItems 或旧结构的 requestDatas 数组`, { retryAllowed: false });
|
|
64
|
+
}
|
|
65
|
+
return requestItemsOf(requestDatas);
|
|
66
|
+
}
|
|
67
|
+
const legacy = Array.isArray(record.smartGroup) ? record.smartGroup : null;
|
|
68
|
+
if (!legacy) {
|
|
69
|
+
throw workflowValidationError('wfidgroup_invalid', `wfIdGroup[${index}]「${title}」是普通组,必须带 displayItems 或旧结构的 smartGroup 数组(先跑 stage=smartGroup)`, { retryAllowed: false });
|
|
70
|
+
}
|
|
71
|
+
return legacy.map((rawSub, subIndex) => {
|
|
72
|
+
const requestDatas = Array.isArray(rawSub?.requestDatas) ? rawSub.requestDatas : [];
|
|
73
|
+
return {
|
|
74
|
+
type: 'group',
|
|
75
|
+
id: typeof rawSub?.id === 'string' ? rawSub.id : `${record.id}-${subIndex + 1}`,
|
|
76
|
+
title: typeof rawSub?.title === 'string' ? rawSub.title : '',
|
|
77
|
+
count: requestDatas.length,
|
|
78
|
+
summary: typeof rawSub?.summary === 'string' ? rawSub.summary : '',
|
|
79
|
+
defaultExpanded: false,
|
|
80
|
+
requestDatas,
|
|
81
|
+
};
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* 最终结构的轻量校验:只认上面那两种组形态(优先组 / 普通组),其它一律报错(不静默修正)。
|
|
86
|
+
* `count` 一律**重算**成 `displayItems` 摊平后的条数——这是唯一计数规则,不采信输入里的 count。
|
|
87
|
+
*/
|
|
88
|
+
export function validateFinalGroups(value) {
|
|
89
|
+
return value.map((raw, index) => {
|
|
90
|
+
const record = asRecord(raw);
|
|
91
|
+
if (!record) {
|
|
92
|
+
throw workflowValidationError('wfidgroup_invalid', `wfIdGroup[${index}] 必须是对象`, { retryAllowed: false });
|
|
93
|
+
}
|
|
94
|
+
const id = typeof record.id === 'string' ? record.id.trim() : '';
|
|
95
|
+
const title = typeof record.title === 'string' ? record.title : '';
|
|
96
|
+
if (!id) {
|
|
97
|
+
throw workflowValidationError('wfidgroup_invalid', `wfIdGroup[${index}].id 必填且必须是字符串(19 位 id 用数字会失真)`, { retryAllowed: false });
|
|
98
|
+
}
|
|
99
|
+
const topPriority = record.topPriority === true;
|
|
100
|
+
const displayItems = readDisplayItems(record, index, title, topPriority);
|
|
101
|
+
const count = displayItems.reduce((sum, item) => sum + (item.type === 'group' ? item.requestDatas.length : 1), 0);
|
|
102
|
+
return topPriority
|
|
103
|
+
? { id, topPriority: true, title, count, defaultExpanded: true, displayItems }
|
|
104
|
+
: { id, topPriority: false, title, count, defaultExpanded: false, displayItems };
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* 取最终结构:优先用回灌进来的 `wfIdGroup`(`stage=smartGroup` / `stage=summaryContext` 的产出),
|
|
109
|
+
* 否则由 `items` 现场重建(有 `aiGroups` 走兼容模式,没有就走确定性模式)。两条路都落到同一种
|
|
110
|
+
* `FinalWfIdGroup[]`。
|
|
111
|
+
*
|
|
112
|
+
* 重建要从详情里读指标(请假天数/金额),所以这里是 `async`——指标由 `runSmartGroup` 内部
|
|
113
|
+
* 现拉详情得到(详情缓存已删除),本函数不再自己算一份。
|
|
114
|
+
*/
|
|
115
|
+
export async function readFinalGroups(args, env) {
|
|
116
|
+
if (Array.isArray(args.wfIdGroup)) {
|
|
117
|
+
// 回灌的结构通常已经是排好序的;这里再套一次 `groupOrder` 只是为了「顺序意图在后续阶段才给出」
|
|
118
|
+
// 也能生效(重排是幂等的,顺序一致时结果不变)。
|
|
119
|
+
return orderGroupsByIntent(validateFinalGroups(args.wfIdGroup), readGroupOrder(args)).groups;
|
|
120
|
+
}
|
|
121
|
+
if (Array.isArray(args.items)) {
|
|
122
|
+
const { data } = await runSmartGroup(args, env);
|
|
123
|
+
return data.wfIdGroup;
|
|
124
|
+
}
|
|
125
|
+
throw workflowValidationError('final_groups_required', '缺少最终结构:用 --input-ref 回灌上一步(stage=smartGroup 或 stage=summaryContext)的输出,'
|
|
126
|
+
+ '或传 items 现场重建(传 aiGroups 走 AI 分组,不传按业务类型确定性分组)', { retryAllowed: false });
|
|
127
|
+
}
|
|
128
|
+
export function collectSummaryUnits(groups) {
|
|
129
|
+
const units = [];
|
|
130
|
+
for (const group of groups) {
|
|
131
|
+
if (group.topPriority) {
|
|
132
|
+
if (!needsRequestSummary(detectBusinessKind(group.title)))
|
|
133
|
+
continue;
|
|
134
|
+
units.push({
|
|
135
|
+
groupId: group.id,
|
|
136
|
+
groupTitle: group.title,
|
|
137
|
+
// 优先组没有汇总项,用组自身 id 当键——组摘要那一步同样按这个键取
|
|
138
|
+
subGroupId: group.id,
|
|
139
|
+
subGroupTitle: group.title,
|
|
140
|
+
kind: detectBusinessKind(group.title),
|
|
141
|
+
rows: rowsOfGroup(group),
|
|
142
|
+
});
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
const kind = detectBusinessKind(group.title);
|
|
146
|
+
if (!needsRequestSummary(kind))
|
|
147
|
+
continue;
|
|
148
|
+
for (const item of group.displayItems) {
|
|
149
|
+
if (item.type === 'group') {
|
|
150
|
+
units.push({
|
|
151
|
+
groupId: group.id,
|
|
152
|
+
groupTitle: group.title,
|
|
153
|
+
subGroupId: item.id,
|
|
154
|
+
subGroupTitle: item.title,
|
|
155
|
+
kind,
|
|
156
|
+
rows: item.requestDatas,
|
|
157
|
+
});
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
// 单独展示的一条:键用 `request-<requestId>`,只参与单条摘要批次,不出组摘要
|
|
161
|
+
units.push({
|
|
162
|
+
groupId: group.id,
|
|
163
|
+
groupTitle: group.title,
|
|
164
|
+
subGroupId: `request-${item.requestData.requestId}`,
|
|
165
|
+
subGroupTitle: item.requestData.requestNameTitle ?? '',
|
|
166
|
+
kind,
|
|
167
|
+
rows: [item.requestData],
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return units;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* `stage=summaryContext`:按最终分组打包摘要批次,并按需获取详情。
|
|
175
|
+
*
|
|
176
|
+
* - **详情为空的条目不喂给 AI**:不进批次,CLI 直接把 `requestNameTitle` 复制到它的
|
|
177
|
+
* `summary`(单条数据也需要摘要,但没有上下文可概括,标题即摘要);
|
|
178
|
+
* - 某个分组下**详情全空** ⇒ 整组不产生批次,结构上保留父组。
|
|
179
|
+
*
|
|
180
|
+
* 输出带着完整的最终结构,因为下一步 `stage=summary`
|
|
181
|
+
* 既要摘要也要结构,而 `--input-ref` 一次只能回灌一份 data —— 把两者放在同一份里,
|
|
182
|
+
* 回灌链才只需要一次引用。
|
|
183
|
+
*/
|
|
184
|
+
export async function runSummaryContext(args, env, prefetched) {
|
|
185
|
+
const groups = await readFinalGroups(args, env);
|
|
186
|
+
const units = collectSummaryUnits(groups);
|
|
187
|
+
const skippedSatisfaction = groups
|
|
188
|
+
.filter((group) => usesTitleSummary(detectBusinessKind(group.title)))
|
|
189
|
+
.reduce((sum, group) => sum + rowsOfGroup(group).length, 0);
|
|
190
|
+
// 优先组是叠加视图 ⇒ 同一条待办会在两个位置各出现一次,统计口径必须去重。
|
|
191
|
+
const requestIds = [...new Set(units.flatMap((unit) => unit.rows.map((row) => row.requestId)))].filter(Boolean);
|
|
192
|
+
// 单条摘要目标字数:用户给了口径(如「每条 30 字」)就用用户的,没给才用默认 50。
|
|
193
|
+
const summaryMaxChars = readBoundedInt(args.summaryMaxChars, 'summaryMaxChars', DEFAULT_REQUEST_SUMMARY_MAX, 10, 500);
|
|
194
|
+
// 摘要缓存命中时直接回填,未命中的条目才进入 AI 批次。
|
|
195
|
+
// 预填必须在「准备详情」之前做——已有摘要的条目根本不需要详情(详情只为写摘要服务)。
|
|
196
|
+
const summaryCache = await openSummaryCache(env.ctx);
|
|
197
|
+
/** 已有摘要(缓存命中 / collect 已回填)而跳过 AI 的条数 */
|
|
198
|
+
let prefilled = 0;
|
|
199
|
+
const prefilledIds = new Set();
|
|
200
|
+
const needsDetail = [];
|
|
201
|
+
for (const unit of units) {
|
|
202
|
+
for (const row of unit.rows) {
|
|
203
|
+
const cached = summaryCache.get(row.requestId, row.lastFeedBackDateTime, summaryMaxChars);
|
|
204
|
+
if (cached) {
|
|
205
|
+
row.summary = cached;
|
|
206
|
+
prefilled += 1;
|
|
207
|
+
prefilledIds.add(row.requestId);
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
needsDetail.push(row.requestId);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// 详情缓存键依赖 lastFeedBackDateTime:从本轮条目里建 stamps,否则缓存永远 miss。
|
|
214
|
+
const stamps = stampsOfRows(units.flatMap((unit) => unit.rows));
|
|
215
|
+
const provider = await loadDetailContextProvider(env, [...new Set(needsDetail)], args, prefetched, stamps);
|
|
216
|
+
const maxChars = readBoundedInt(args.maxCharsPerBatch, 'maxCharsPerBatch', DEFAULT_MAX_CHARS_PER_BATCH, 500, 200_000);
|
|
217
|
+
const maxItems = readBoundedInt(args.maxItemsPerBatch, 'maxItemsPerBatch', DEFAULT_MAX_ITEMS_PER_BATCH, 1, 500);
|
|
218
|
+
const batches = [];
|
|
219
|
+
let contextAvailable = 0;
|
|
220
|
+
let contextEmpty = 0;
|
|
221
|
+
let titleCopied = 0;
|
|
222
|
+
/**
|
|
223
|
+
* 已经进过 AI 批次的 requestId。
|
|
224
|
+
*
|
|
225
|
+
* 优先组是叠加视图 ⇒ 同一条待办会先在优先组出现、再在工作流组出现。同一条只喂一次模型,
|
|
226
|
+
* 另一份副本由 `stage=summary` 回填时按 requestId 一并写上(`runSummary` 就是按 id 遍历的),
|
|
227
|
+
* 所以这里跳过不会让任何一份摘要空缺,只是省掉一次重复的上下文。
|
|
228
|
+
*/
|
|
229
|
+
const claimed = new Set();
|
|
230
|
+
for (const unit of units) {
|
|
231
|
+
let bucket = [];
|
|
232
|
+
let chars = 0;
|
|
233
|
+
let seq = 0;
|
|
234
|
+
const flush = () => {
|
|
235
|
+
if (bucket.length === 0)
|
|
236
|
+
return;
|
|
237
|
+
seq += 1;
|
|
238
|
+
batches.push({
|
|
239
|
+
batchId: `${unit.subGroupId}#${seq}`,
|
|
240
|
+
groupId: unit.groupId,
|
|
241
|
+
groupTitle: unit.groupTitle,
|
|
242
|
+
subGroupId: unit.subGroupId,
|
|
243
|
+
subGroupTitle: unit.subGroupTitle,
|
|
244
|
+
itemCount: bucket.length,
|
|
245
|
+
charCount: chars,
|
|
246
|
+
kind: unit.kind,
|
|
247
|
+
...(unit.kind === 'internal-message' ? { hint: INTERNAL_MESSAGE_SUMMARY_HINT } : {}),
|
|
248
|
+
suggestedMaxChars: summaryMaxChars,
|
|
249
|
+
items: bucket,
|
|
250
|
+
});
|
|
251
|
+
bucket = [];
|
|
252
|
+
chars = 0;
|
|
253
|
+
};
|
|
254
|
+
for (const row of unit.rows) {
|
|
255
|
+
// 摘要缓存已命中(上一步预填)⇒ 不再喂 AI
|
|
256
|
+
if (prefilledIds.has(row.requestId))
|
|
257
|
+
continue;
|
|
258
|
+
const { context, contextAvailable: available } = provider.get(row.requestId);
|
|
259
|
+
if (!available) {
|
|
260
|
+
// ★ 详情为空 ⇒ 不喂 AI,summary 直接复制标题(确定性操作,不走模型)。
|
|
261
|
+
// 这里**不做** claimed 跳过:叠加视图下每个副本各自持有 summary 字段,都得填一次。
|
|
262
|
+
const firstSeen = !row.requestId || !claimed.has(row.requestId);
|
|
263
|
+
if (!(row.summary ?? '').trim()) {
|
|
264
|
+
row.summary = row.requestNameTitle ?? '';
|
|
265
|
+
if (firstSeen)
|
|
266
|
+
titleCopied += 1;
|
|
267
|
+
}
|
|
268
|
+
// 统计按 requestId 去重,避免同一条在优先组和工作流组里被数两遍
|
|
269
|
+
if (firstSeen) {
|
|
270
|
+
contextEmpty += 1;
|
|
271
|
+
if (row.requestId)
|
|
272
|
+
claimed.add(row.requestId);
|
|
273
|
+
}
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
// 叠加视图:这条已经在别处进过批次,跳过当前副本(摘要回填按 requestId 覆盖两份)
|
|
277
|
+
if (row.requestId && claimed.has(row.requestId))
|
|
278
|
+
continue;
|
|
279
|
+
if (row.requestId)
|
|
280
|
+
claimed.add(row.requestId);
|
|
281
|
+
contextAvailable += 1;
|
|
282
|
+
const item = {
|
|
283
|
+
requestId: row.requestId,
|
|
284
|
+
requestNameTitle: row.requestNameTitle,
|
|
285
|
+
context,
|
|
286
|
+
contextAvailable: true,
|
|
287
|
+
};
|
|
288
|
+
const itemChars = context.length + row.requestNameTitle.length + 40;
|
|
289
|
+
if (bucket.length > 0 && (bucket.length >= maxItems || chars + itemChars > maxChars))
|
|
290
|
+
flush();
|
|
291
|
+
bucket.push(item);
|
|
292
|
+
chars += itemChars;
|
|
293
|
+
}
|
|
294
|
+
flush();
|
|
295
|
+
}
|
|
296
|
+
const warnings = [...provider.warnings];
|
|
297
|
+
if (skippedSatisfaction > 0) {
|
|
298
|
+
warnings.push(`有 ${skippedSatisfaction} 条满意度类待办不走 AI,summary 将在回填阶段直接使用条目标题`);
|
|
299
|
+
}
|
|
300
|
+
if (titleCopied > 0) {
|
|
301
|
+
warnings.push(`有 ${titleCopied} 条详情数据为空,已不喂给 AI,summary 直接使用标题(requestNameTitle)填充`);
|
|
302
|
+
}
|
|
303
|
+
if (prefilled > 0) {
|
|
304
|
+
warnings.push(`有 ${prefilled} 条已有本地摘要(key=requestId__lastFeedBackDateTime),未再调用 AI;`
|
|
305
|
+
+ `本次需要 AI 写摘要的只剩 ${requestIds.length - prefilled - titleCopied} 条`);
|
|
306
|
+
}
|
|
307
|
+
return {
|
|
308
|
+
data: {
|
|
309
|
+
smartSummaryTitle: readSmartSummaryTitle(args, `您当前有 ${totalTodoCount(groups)} 条待办,清单如下:`),
|
|
310
|
+
totalCount: totalTodoCount(groups),
|
|
311
|
+
wfIdGroup: groups,
|
|
312
|
+
summaryBatches: batches,
|
|
313
|
+
summaryStats: {
|
|
314
|
+
requestTotal: requestIds.length,
|
|
315
|
+
/** 本次真正发出去的详情请求条数(摘要缓存命中的不发) */
|
|
316
|
+
detailFetched: provider.stats.fetched + provider.reused,
|
|
317
|
+
/** 其中由上一阶段(smartGroup)facts 直接复用、未发请求的条数 */
|
|
318
|
+
detailReused: provider.reused,
|
|
319
|
+
/** 其中真正走了 HTTP 的条数 */
|
|
320
|
+
detailNetworkFetched: provider.stats.fetched,
|
|
321
|
+
contextAvailable,
|
|
322
|
+
contextEmpty,
|
|
323
|
+
failed: provider.stats.failed,
|
|
324
|
+
titleCopied,
|
|
325
|
+
/** 本地摘要命中、跳过 AI 的条数 */
|
|
326
|
+
prefilled,
|
|
327
|
+
/** 单条摘要的目标字数(默认 50,可由 summaryMaxChars 覆盖) */
|
|
328
|
+
suggestedMaxChars: summaryMaxChars,
|
|
329
|
+
/** 内部留言类批次:摘要要按「最低推理强度」写 */
|
|
330
|
+
internalMessageBatches: batches.filter((batch) => batch.kind === 'internal-message').length,
|
|
331
|
+
skippedSatisfaction,
|
|
332
|
+
summaryCacheFile: summaryCache.file,
|
|
333
|
+
batchCount: batches.length,
|
|
334
|
+
/** 本次详情取数的并发与耗时(现场拉,不再是占位值) */
|
|
335
|
+
concurrency: provider.stats.concurrency,
|
|
336
|
+
durationMs: provider.stats.durationMs,
|
|
337
|
+
maxCharsPerBatch: maxChars,
|
|
338
|
+
maxItemsPerBatch: maxItems,
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
warnings,
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
/** 校验并索引 `aiSummaries`:扁平 `[{ requestId, summary }]`(与 `aiGroups` 同风格)。 */
|
|
345
|
+
export function readAiSummaries(value) {
|
|
346
|
+
if (!Array.isArray(value)) {
|
|
347
|
+
throw workflowValidationError('ai_summaries_invalid', 'aiSummaries 必须是数组,每项形如 { requestId, summary }', { retryAllowed: false });
|
|
348
|
+
}
|
|
349
|
+
const map = new Map();
|
|
350
|
+
value.forEach((raw, index) => {
|
|
351
|
+
const label = `aiSummaries[${index}]`;
|
|
352
|
+
const record = asRecord(raw);
|
|
353
|
+
if (!record) {
|
|
354
|
+
throw workflowValidationError('ai_summaries_invalid', `${label} 必须是对象 { requestId, summary }`, { retryAllowed: false });
|
|
355
|
+
}
|
|
356
|
+
const unknownKeys = Object.keys(record).filter((key) => !['requestId', 'summary'].includes(key));
|
|
357
|
+
if (unknownKeys.length > 0) {
|
|
358
|
+
throw workflowValidationError('ai_summaries_invalid', `${label} 不接受字段:${unknownKeys.join(', ')};只支持 requestId / summary`, { retryAllowed: false, details: { unknownKeys } });
|
|
359
|
+
}
|
|
360
|
+
const requestId = typeof record.requestId === 'string' ? record.requestId.trim() : '';
|
|
361
|
+
if (!requestId) {
|
|
362
|
+
throw workflowValidationError('ai_summaries_invalid', `${label}.requestId 必填且必须是字符串(19 位 id 用数字会失真)`, { retryAllowed: false });
|
|
363
|
+
}
|
|
364
|
+
if (typeof record.summary !== 'string') {
|
|
365
|
+
throw workflowValidationError('ai_summaries_invalid', `${label}.summary 必须是字符串`, { retryAllowed: false });
|
|
366
|
+
}
|
|
367
|
+
if (map.has(requestId)) {
|
|
368
|
+
throw workflowValidationError('ai_summary_duplicate_request', `requestId「${requestId}」在 aiSummaries 里出现了多次,一条数据只能有一个摘要`, { retryAllowed: true, details: { requestId } });
|
|
369
|
+
}
|
|
370
|
+
map.set(requestId, record.summary.trim());
|
|
371
|
+
});
|
|
372
|
+
return map;
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* `stage=summary`:把模型写的摘要填进最终结构中对应待办的 `summary`。
|
|
376
|
+
*
|
|
377
|
+
* 输出沿用目标 demo 的唯一业务结构:`smartSummaryTitle + totalCount + wfIdGroup`。
|
|
378
|
+
* 摘要只写入对应条目的 `summary`;后续组摘要直接消费 `wfIdGroup`,不额外输出重复映射。
|
|
379
|
+
*
|
|
380
|
+
* 漏写不报错(记 warning、留空串)——模型漏几条不该让整批 300 条重跑;但 id 不存在、
|
|
381
|
+
* 重复出现属于「结果与源数据对不上」,硬失败。
|
|
382
|
+
*/
|
|
383
|
+
export async function runSummary(args, env) {
|
|
384
|
+
const groups = await readFinalGroups(args, env);
|
|
385
|
+
const summaries = readAiSummaries(args.aiSummaries);
|
|
386
|
+
// 单条摘要目标字数:与 `runSummaryContext` 保持同一口径(用户给了就用用户的)。
|
|
387
|
+
const summaryMaxChars = readBoundedInt(args.summaryMaxChars, 'summaryMaxChars', DEFAULT_REQUEST_SUMMARY_MAX, 10, 500);
|
|
388
|
+
// ★ 本次 AI 写出来的摘要顺手进缓存(key=requestId__lastFeedBackDateTime),
|
|
389
|
+
// 下次同一条流程的待办再来时可以复用摘要。
|
|
390
|
+
const summaryCache = await openSummaryCache(env.ctx);
|
|
391
|
+
let cacheWritten = 0;
|
|
392
|
+
// 缓存预填(与 `summaryContext` 同口径):只跑 `summary` 阶段时也能拿到已写好的摘要,
|
|
393
|
+
// 否则重跑 finish(不带 aiSummaries)会把整份摘要清成空串。
|
|
394
|
+
// 满意度类不走 AI、也不读缓存:直接用标题当摘要,保证 `summary` 不为空。
|
|
395
|
+
let titleSummary = 0;
|
|
396
|
+
for (const group of groups) {
|
|
397
|
+
if (usesTitleSummary(detectBusinessKind(group.title))) {
|
|
398
|
+
for (const row of rowsOfGroup(group)) {
|
|
399
|
+
if ((row.summary ?? '').trim())
|
|
400
|
+
continue;
|
|
401
|
+
row.summary = row.requestNameTitle ?? '';
|
|
402
|
+
if ((row.summary ?? '').trim())
|
|
403
|
+
titleSummary += 1;
|
|
404
|
+
}
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
for (const row of rowsOfGroup(group)) {
|
|
408
|
+
if (!row?.requestId || (row.summary ?? '').trim())
|
|
409
|
+
continue;
|
|
410
|
+
const cached = summaryCache.get(row.requestId, row.lastFeedBackDateTime, summaryMaxChars);
|
|
411
|
+
if (cached) {
|
|
412
|
+
row.summary = cached;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
const knownRequestIds = new Set();
|
|
417
|
+
for (const group of groups) {
|
|
418
|
+
for (const row of rowsOfGroup(group)) {
|
|
419
|
+
if (row?.requestId)
|
|
420
|
+
knownRequestIds.add(row.requestId);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
for (const requestId of summaries.keys()) {
|
|
424
|
+
if (!knownRequestIds.has(requestId)) {
|
|
425
|
+
throw workflowValidationError('ai_summary_unknown_request', `aiSummaries 里的 requestId「${requestId}」不在本次最终结构里(可能 id 传错,或摘要是上一轮数据留下的)`, { retryAllowed: true, details: { requestId } });
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
const warnings = [];
|
|
429
|
+
let filled = 0;
|
|
430
|
+
let empty = 0;
|
|
431
|
+
let tooLong = 0;
|
|
432
|
+
/** 详情为空、CLI 已用标题填充并被本次保留的条数 */
|
|
433
|
+
let titleKept = 0;
|
|
434
|
+
/** 本地摘要缓存命中(不是标题兜底)并被本次保留下来的条数 */
|
|
435
|
+
let prefilledKept = 0;
|
|
436
|
+
/** 统计用:同一条待办在叠加视图里出现两次,计数只算一次 */
|
|
437
|
+
const countedIds = new Set();
|
|
438
|
+
const applyAll = (rows) => {
|
|
439
|
+
for (const row of rows) {
|
|
440
|
+
const summary = summaries.get(row.requestId) ?? '';
|
|
441
|
+
const firstTime = !row.requestId || !countedIds.has(row.requestId);
|
|
442
|
+
if (row.requestId)
|
|
443
|
+
countedIds.add(row.requestId);
|
|
444
|
+
if (!summary && (row.summary ?? '').trim() !== '') {
|
|
445
|
+
// 没有 AI 摘要,但结构里已经有摘要:
|
|
446
|
+
// - 详情为空 ⇒ CLI 用标题填的(确定性操作、不覆盖、不计空);
|
|
447
|
+
// - 本地缓存命中 ⇒ 已经是写过的摘要,同样保留。
|
|
448
|
+
if (firstTime) {
|
|
449
|
+
if ((row.summary ?? '').trim() === (row.requestNameTitle ?? '').trim())
|
|
450
|
+
titleKept += 1;
|
|
451
|
+
else
|
|
452
|
+
prefilledKept += 1;
|
|
453
|
+
}
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
if (summary) {
|
|
457
|
+
if (firstTime) {
|
|
458
|
+
filled += 1;
|
|
459
|
+
if (summary.length > summaryMaxChars)
|
|
460
|
+
tooLong += 1;
|
|
461
|
+
}
|
|
462
|
+
if (summaryCache.set(row.requestId, row.lastFeedBackDateTime, summary, summaryMaxChars))
|
|
463
|
+
cacheWritten += 1;
|
|
464
|
+
}
|
|
465
|
+
else if (firstTime) {
|
|
466
|
+
empty += 1;
|
|
467
|
+
}
|
|
468
|
+
// 空串就是空串:不拿标题凑、不编造
|
|
469
|
+
row.summary = summary;
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
for (const group of groups) {
|
|
473
|
+
if (!needsRequestSummary(detectBusinessKind(group.title)))
|
|
474
|
+
continue;
|
|
475
|
+
if (group.topPriority) {
|
|
476
|
+
applyAll(rowsOfGroup(group));
|
|
477
|
+
continue;
|
|
478
|
+
}
|
|
479
|
+
for (const item of group.displayItems) {
|
|
480
|
+
if (item.type === 'group') {
|
|
481
|
+
applyAll(item.requestDatas);
|
|
482
|
+
continue;
|
|
483
|
+
}
|
|
484
|
+
// 单独展示的一条:只回填它自己
|
|
485
|
+
applyAll([item.requestData]);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
if (empty > 0) {
|
|
489
|
+
warnings.push(`有 ${empty} 条没有拿到 AI 摘要,summary 保持空串(未用标题兜底、未编造)`);
|
|
490
|
+
}
|
|
491
|
+
if (titleKept > 0) {
|
|
492
|
+
warnings.push(`有 ${titleKept} 条详情为空的条目保留 CLI 填充的标题摘要(不参与 AI 回填)`);
|
|
493
|
+
}
|
|
494
|
+
if (tooLong > 0) {
|
|
495
|
+
warnings.push(`有 ${tooLong} 条摘要超过目标长度 ${summaryMaxChars} 字(可按用户口径调整),已原样保留;`
|
|
496
|
+
+ '如需控制长度请重跑模型侧(CLI 不改写文案)');
|
|
497
|
+
}
|
|
498
|
+
if (prefilledKept > 0) {
|
|
499
|
+
warnings.push(`有 ${prefilledKept} 条沿用本地摘要缓存(未重新生成)`);
|
|
500
|
+
}
|
|
501
|
+
const titleSummaryCount = groups
|
|
502
|
+
.filter((group) => usesTitleSummary(detectBusinessKind(group.title)))
|
|
503
|
+
.reduce((sum, group) => sum + rowsOfGroup(group).length, 0);
|
|
504
|
+
if (titleSummaryCount > 0) {
|
|
505
|
+
warnings.push(`有 ${titleSummaryCount} 条满意度类待办不调用 AI,summary 直接使用条目标题(保证非空)`);
|
|
506
|
+
}
|
|
507
|
+
// 缓存写不进去不致命:只是下次还得重新问 AI,不能因此让本轮结果失败。
|
|
508
|
+
let summaryCacheFile = summaryCache.file;
|
|
509
|
+
if (cacheWritten > 0) {
|
|
510
|
+
const written = await summaryCache.save();
|
|
511
|
+
if (written)
|
|
512
|
+
summaryCacheFile = written;
|
|
513
|
+
else
|
|
514
|
+
warnings.push(`摘要缓存写入失败(${cacheWritten} 条),本次结果不受影响;下次仍会重新生成`);
|
|
515
|
+
}
|
|
516
|
+
return {
|
|
517
|
+
data: {
|
|
518
|
+
smartSummaryTitle: readSmartSummaryTitle(args, `您当前有 ${totalTodoCount(groups)} 条待办,清单如下:`),
|
|
519
|
+
totalCount: totalTodoCount(groups),
|
|
520
|
+
wfIdGroup: groups,
|
|
521
|
+
},
|
|
522
|
+
meta: {
|
|
523
|
+
summarized: filled,
|
|
524
|
+
empty,
|
|
525
|
+
/** 单条摘要的目标字数(默认 50,可由 summaryMaxChars 覆盖) */
|
|
526
|
+
suggestedMaxChars: summaryMaxChars,
|
|
527
|
+
titleCopied: titleKept,
|
|
528
|
+
/** 本地摘要缓存命中、未重新生成的条数 */
|
|
529
|
+
prefilled: prefilledKept,
|
|
530
|
+
skippedSatisfaction: titleSummaryCount,
|
|
531
|
+
/** 满意度类用条目标题当摘要、未调用 AI 的条数 */
|
|
532
|
+
titleSummary,
|
|
533
|
+
/** 本次新写入缓存的条数 */
|
|
534
|
+
summaryCacheWritten: cacheWritten,
|
|
535
|
+
summaryCacheFile,
|
|
536
|
+
},
|
|
537
|
+
warnings,
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
// ---------------------------------------------------------------------------
|
package/docs/_catalog.md
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
| OKR 目标管理(目标查询/详情、关键成果、目标对齐、评论)、okr operation、标准版与 ebuilder 双链路、写操作 prepare/apply | `docs/okr.md` |
|
|
17
17
|
| eBuilder 表单(菜单定位、List/NList、字段/浏览、OpenAPI 与自定义接口)、ebuilder-form operation、写操作 prepare/apply | `docs/ebuilder-form.md` |
|
|
18
18
|
| E10 ESB 中心自定义触发动作流(入参模板查询 / prepare-apply 触发)、esb operation | `docs/esb.md` |
|
|
19
|
+
| 待办智能汇总(workflow 内独立分支:跨条目取数/分组/摘要/金额合计,与列表查询解耦) | `docs/todo-smart-convert/README.md` |
|
|
19
20
|
| 事井然项目管理(项目/任务/字典/字段解析)、project operation、写操作 prepare/apply | `docs/project.md` |
|
|
20
21
|
| 快速安装 CLI,并使用 CLI/Skill Maker 生成、验证、打包和安装业务模块 Skill | `docs/operation-manual.md` |
|
|
21
22
|
| 把随包 Agent Skill 安装/升级到本机 Agent(Codex/WorkBuddy 目录、从本地仓库发布最新版) | `docs/agent-skill-install.md` |
|
package/docs/contract.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
源码位于 `src/shortcuts/contract`。命令注册、operation manifest 和业务执行逻辑分离;具体执行逻辑在 `src/shortcuts/contract/operations` 中按 operation class 维护。
|
|
6
6
|
|
|
7
|
-
面向 Agent 的说明位于 `skills/weaver-e10-jinchengda`。入口 `SKILL.md` 只负责共享规则和 reference 路由;具体细节拆在 `skills/weaver-
|
|
7
|
+
面向 Agent 的说明位于 `skills/weaver-e10-jinchengda`。入口 `SKILL.md` 只负责共享规则和 reference 路由;具体细节拆在 `skills/weaver-e10-jinchengda/references/`,与飞书 CLI 的业务 Skill 组织方式保持一致。
|
|
8
8
|
|
|
9
9
|
## 公共前置
|
|
10
10
|
|
|
@@ -49,24 +49,48 @@ weaver-work-cli --json contract run contract.workflow.query --input-json '{"work
|
|
|
49
49
|
|---|---|
|
|
50
50
|
| 合同类型与模板 | `contract.type.tree`、`contract.template.tree`、`contract.template.list`、`contract.template.detail` |
|
|
51
51
|
| 条款查询 | `contract.clause.tree`、`contract.clause.list`、`contract.clause.group.list`、`contract.clause.search`、`contract.clause.tags` |
|
|
52
|
+
| 条款库维护 | `contract.clause.permission`、`contract.clause.types`、`contract.clause.check`、`contract.clause.browser`、`contract.clause.subcompany`;`contract.clause.category.*` / `contract.clause.*` / `contract.clause.group.*` 的 `create|update|delete` + `.prepare/.apply` |
|
|
52
53
|
| 条款引用 | `contract.clause.reference.get`、`contract.clause.reference.save.prepare/apply` |
|
|
53
54
|
| 签约主体 | `contract.subject.app`、`contract.subject.form`、`contract.subject.fields`、`contract.subject.options`、`contract.subject.query`、`contract.subject.create.prepare/apply` |
|
|
54
55
|
| 流程解析 | `contract.workflow.query`、`contract.workflow.list`、`contract.workflow.form.info`(三段式:loadBaseParam → loadFrameParam → getFormLayout)、`contract.workflow.field.options` |
|
|
55
56
|
| 流程写操作 | `contract.request.create.prepare/apply`、`contract.request.save.prepare/apply`、`contract.request.submit.prepare/apply`、`contract.request.supervise.prepare/apply` |
|
|
56
57
|
| 流程状态 | `contract.request.status`、`contract.request.log` |
|
|
58
|
+
| 合同智能审查 | `contract.review.upload`(ebContract 两段式上传 + 审查页)、`contract.review.export`(`getAiReviewRecord`) |
|
|
59
|
+
| 发起流程入口 | `contract.flow.page`、`contract.flow.launch`(workflow 模块上传 + `automaticFormFilling`) |
|
|
60
|
+
| 模板推荐 | `contract.recommend.doc`(`recommendContractDoc`) |
|
|
61
|
+
| 文档比对 | `contract.compare.upload`(docCompared)、`contract.compare.search`、`contract.compare.versions`、`contract.compare.page`、`contract.compare.diff` |
|
|
62
|
+
| 企业资信 | `contract.credit.page`(仅页面入口) |
|
|
63
|
+
| 业绩核算 | `contract.performance.link/fields/source.entry`(纯本地)+ `contract.performance.menu.search/list.init/list.filters/list.query/receipt.query`(只读查询) |
|
|
64
|
+
| 报价单转合同 | `contract.quote.rmb/sheets/parse/fill`(本地 OOXML 读写,零依赖) |
|
|
65
|
+
| 合同智能助手 | `contract.assistant.init`、`contract.assistant.meta`、`contract.assistant.list`、`contract.assistant.doc.query`、`contract.assistant.config.query`、`contract.assistant.tips.query`、`contract.assistant.doc.folder.search`、`contract.assistant.keygroup.search`;`contract.assistant.create/delete`、`contract.assistant.doc.save`、`contract.assistant.agent.update`、`contract.assistant.tips.create/delete`、`contract.assistant.rule.classification.*`、`contract.assistant.rule.*`、`contract.assistant.rule.group.*`、`contract.assistant.element.create/delete`、`contract.assistant.flow.*`(写操作均 prepare/apply) |
|
|
57
66
|
|
|
58
67
|
完整字段与风险等级以 `weaver-work-cli contract schema` 为准。
|
|
59
68
|
|
|
69
|
+
## 文件上传与页面地址
|
|
70
|
+
|
|
71
|
+
- 上传统一走 `POST /api/file/preUploadCheck` → `POST /api/file/module/upload`;`module` 按场景固定:审查 `ebContract`、流程 `workflow`、比对 `docCompared`,不可混用。
|
|
72
|
+
- 预检返回非空 `fileid` 即为秒传,跳过第二段;否则用 `preUploadId` + `uploadType` 带文件本体上传。
|
|
73
|
+
- 成功判据是拿到非空 `fileId`,HTTP 200 不算成功;`uploadType=chunk` 时以 `upload_chunk_unsupported` 停止。
|
|
74
|
+
- 只接受 `.doc` / `.docx`,单文件上限 200MB。
|
|
75
|
+
- `sp/` 页面地址由字符串字面拼接生成,**不做整体编码**:`:`、`/`、`?`、`&`、`=`、`[`、`]` 与中文保持原样;仅资信页 `name[N]` 的值做一次 `encodeURIComponent`。
|
|
76
|
+
|
|
77
|
+
## 条款库鉴权门
|
|
78
|
+
|
|
79
|
+
条款库全部 operation(含查询类)在发请求前先调 `GET /api/ebd/contract/component/checkPermissionV2?moduleFlag=termComponent`(带业务头 `ebbusinessid: 10000000000000000`),只有 `data.checkPermission === true` 才放行;否则返回 `policy/clause_no_permission`(退出码 6)并中止。条款写操作要求 `clauseNum` / `clauseVersion` / `clauseGroup` 必传,`contractType` 与 `contractTypeName` 成对,条款组 `belongSub` 必传(不分部传空串)。
|
|
80
|
+
|
|
60
81
|
## 业务成功码适配
|
|
61
82
|
|
|
62
83
|
源资料中各接口的成功码字段并不统一,适配层按路径分组判定,不凭 HTTP 200 判定业务成功:
|
|
63
84
|
|
|
64
85
|
| 路径 | 成功判定 |
|
|
65
86
|
|---|---|
|
|
66
|
-
| `/api/ebd/**` | 顶层 `code = 0` |
|
|
87
|
+
| `/api/ebd/**` | 顶层 `code = 0` 或 `200`,且 `status` 不为 false、`fail` 不为 true |
|
|
88
|
+
| `/api/file/**`、`/api/doc/**` | 顶层 `code = 0`/`200` 且 `status`/`fail` 未置失败;无 code 时以 `data` 是否落地为准 |
|
|
67
89
|
| `/api/ebuilder/form/obj/getObjsByTag`、`/api/bs/ebuilder/app/tags/getAppIds`、`/api/ebuilder/common/ds/fields`、`/api/ebuilder/common/ds/options` | 顶层 `code = 200` 且 `status` 不为 false |
|
|
90
|
+
| `/api/ebuilder/form/list/**`(助手列表 getListDatas) | 顶层 `code = 0`/`200` 且 `status` 不为 false |
|
|
68
91
|
| `/api/ebuilder/form/dataset/v2/**` | `message.code = "0"` |
|
|
69
92
|
| `/api/workflow/**` | `message.errcode = "0"`(`getFieldOption` 上游示例为 `200`,同样视为成功) |
|
|
93
|
+
| `/api/workflow/core/flowPage/**`、`/api/workflow/core/form/**`、`/api/ebuilder/flow/form/**` | 无 `message` 节点;只有非 0/200 的 `errcode` 或 `fail=true` / `status=false` 才算失败 |
|
|
70
94
|
|
|
71
95
|
## 环境与 ID 约束
|
|
72
96
|
|
|
@@ -82,10 +106,21 @@ weaver-work-cli --json contract run contract.workflow.query --input-json '{"work
|
|
|
82
106
|
- 写请求超时或断线返回 `partial/write_uncertain`(退出码 11),禁止自动重放。
|
|
83
107
|
- `submitRequest` 返回 `1200306` 时解析缺失字段补全后重试(最多 3 轮);返回 `1200301` 时停止并诊断会话状态。
|
|
84
108
|
|
|
109
|
+
## 合同智能助手要点
|
|
110
|
+
|
|
111
|
+
- ID 链路:`init` -> `meta`(ebFormId/viewId)-> `list`(ai_id)-> `config.query`(Control=rule_config_id、Extract=config_id)。
|
|
112
|
+
- 列表接口 `total` 恒为 0,以返回行数为准;助手列表 type 固定 `ai_background_config`。
|
|
113
|
+
- 启用/停用智能体只改 `status_info`;`display` 是平台保留字段,输入含 `display` 直接拒绝(`policy/display_forbidden`)。
|
|
114
|
+
- 删除类接口普遍假成功(删助手/提示语/关联流程),apply 强制回读复核;关联流程删除强制带 `type=contract_ai_workflow_data`。
|
|
115
|
+
- 内控规则三步:分类 -> 规则 -> 规则组关联 Control,缺最后一步页面不可见;`default_type` 服务端互斥(后写覆盖先写)。
|
|
116
|
+
- `pzxx` / `element_mxb` / `contract_rule_source` 必须是 JSON 字符串,对象入参由 CLI 自动转换;`element_content` 与 `element_mxb` 两份重复数据都要传(缺省 content 由 CLI 生成)。
|
|
117
|
+
- 助手关联流程 `toolId=803297036482249914` 固定;一个流程全局只能绑定一次;`showPosition=3` 需 `fieldId`、`term_config` 需 `contractTypeField`、`key_config` 不能为空(NEED-CONFIRM 由 CLI 强制校验)。
|
|
118
|
+
- 提示语无编辑接口(先删后增),`aizjid` 序号由 CLI 按最大序号 +1 自动补算;saveAiDoc 同表按 id 覆盖,传 id 前先经 `queryAiDoc` 校验记录类型。
|
|
119
|
+
|
|
85
120
|
## 暂未实现
|
|
86
121
|
|
|
87
|
-
|
|
122
|
+
企业资信**数据**查询(诉讼、被执行、变更、监控)和撤回/退回流程在源资料中缺少接口路径、参数或确认契约,已列入 `withheldOperations`;资信只提供页面入口 `contract.credit.page`。详见 `skills/weaver-e10-jinchengda/references/contract-disabled-capabilities.md`。
|
|
88
123
|
|
|
89
124
|
## 源资料指纹
|
|
90
125
|
|
|
91
|
-
`skills/weaver-
|
|
126
|
+
`skills/weaver-e10-jinchengda/references/source-manifest.json` 记录了源资料的 `sha256` 与聚合指纹,用于后续检测源资料更新;更新检测确认前不要覆盖已生成的 CLI 与 Skill。
|