weaver-work-cli 0.1.36 → 0.1.39
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-XBVH7JU7.js → archive-ENZYBBME.js} +559 -68
- package/dist/chunks/{archive-oa-MZM3EKFQ.js → archive-oa-YWLEC5V4.js} +4 -3
- package/dist/chunks/{asset-4XO7UOUW.js → asset-OOV6DWPA.js} +8 -7
- package/dist/chunks/asset-idle-RE632MY5.js +670 -0
- package/dist/chunks/{attachment-content-VXSWANAC.js → attachment-content-RWN2FCLL.js} +3 -3
- package/dist/chunks/{blog-PGZKOPSC.js → blog-PEPOJAL7.js} +5 -4
- package/dist/chunks/{caizhilian-BH4ZVGUN.js → caizhilian-WYIQDVPJ.js} +5 -4
- package/dist/chunks/{calendar-MXEMTIO4.js → calendar-CIARBAOX.js} +4 -3
- package/dist/chunks/{chunk-NCOVUAZK.js → chunk-36DIFZAH.js} +4 -3
- package/dist/chunks/{chunk-I6QGIBDM.js → chunk-BJGXMFGD.js} +4 -3
- package/dist/chunks/{chunk-TC6MZAO3.js → chunk-CQHGNCK2.js} +52 -2
- package/dist/chunks/{chunk-WXFPWCXG.js → chunk-EINRJHQ3.js} +3 -3
- package/dist/chunks/{chunk-JUXVYKII.js → chunk-EZGFHJFW.js} +201 -19
- package/dist/chunks/{chunk-VPUTVEMC.js → chunk-F2EQXUEE.js} +436 -290
- package/dist/chunks/{chunk-JYKABYCV.js → chunk-GVPJZXJ2.js} +3 -3
- package/dist/chunks/{chunk-6LLJFKJE.js → chunk-HJYVHXUQ.js} +1 -1
- package/dist/chunks/{chunk-T72N74ZT.js → chunk-I2HB737U.js} +5 -4
- package/dist/chunks/{chunk-VPXOPQJI.js → chunk-K3RUEEOL.js} +4 -2
- package/dist/chunks/{chunk-HXJQI5NM.js → chunk-KTCB5R5H.js} +3 -2
- package/dist/chunks/{chunk-XMGSYD6S.js → chunk-L4N7B7ZD.js} +4 -4
- package/dist/chunks/{chunk-QYGO5EE2.js → chunk-MUQUHKPG.js} +4 -3
- package/dist/chunks/{chunk-QHFANTX7.js → chunk-NLK2RZKG.js} +1348 -90
- package/dist/chunks/{chunk-F746NKNF.js → chunk-P2DAISYW.js} +1 -1
- package/dist/chunks/{chunk-BPE37FPB.js → chunk-T7JDJJ7H.js} +3 -3
- package/dist/chunks/{chunk-JNG42JKG.js → chunk-XXJOSZTQ.js} +6 -6
- package/dist/chunks/{chunk-5DQEEGCC.js → chunk-YS5DPN4T.js} +1 -1
- package/dist/chunks/{chunk-EG6ZU444.js → chunk-ZUX3P6BQ.js} +10 -5
- package/dist/chunks/{chunk-JCSN3JGM.js → chunk-ZXRNHZTE.js} +7 -6
- package/dist/chunks/{conflict-JAB2LJEZ.js → conflict-DRMDOL56.js} +2 -2
- package/dist/chunks/{contract-XI2EEIZU.js → contract-73MERZWN.js} +4 -3
- package/dist/chunks/{cowork-SCPGLBA4.js → cowork-HNGBPD7A.js} +4 -3
- package/dist/chunks/{ebuilder-form-EYLHNOTA.js → ebuilder-form-OY6DVNRK.js} +3 -3
- package/dist/chunks/{ehr-EAP77IMT.js → ehr-RAQNEQO7.js} +4 -3
- package/dist/chunks/{esb-2TGGXEE7.js → esb-6XV7C7YV.js} +4 -4
- package/dist/chunks/{fna-2X7ZE7BU.js → fna-XTSRB7G7.js} +4 -3
- package/dist/chunks/{host-Q4LZGCRU.js → host-3F5UM5GN.js} +3 -3
- package/dist/chunks/{host-XCLDDXKQ.js → host-A7J6GDUP.js} +3 -3
- package/dist/chunks/{host-I4NRK4MX.js → host-VGPVP7YF.js} +3 -3
- package/dist/chunks/{host-C6EL2RKL.js → host-YAQAPYNP.js} +3 -3
- package/dist/chunks/{host-UEI5FGDM.js → host-YMBXWFWX.js} +3 -3
- package/dist/chunks/{hrm-YXERJM76.js → hrm-TLHKC545.js} +3 -3
- package/dist/chunks/{im-A4VZN2TQ.js → im-YOUCFMUK.js} +3 -3
- package/dist/chunks/{invoice-2Z6YPMIS.js → invoice-WNBS2B4C.js} +3 -3
- package/dist/chunks/{jiuchuanhui-AR5GMKZB.js → jiuchuanhui-XABWS5RN.js} +4 -3
- package/dist/chunks/{lazy-dispatch-QXW7P4X3.js → lazy-dispatch-NLZ5UQQI.js} +5 -5
- package/dist/chunks/{mail-7GZ6CPBJ.js → mail-LRYMBFNL.js} +4 -3
- package/dist/chunks/{meeting-3NMGA3MB.js → meeting-ESXL6MFI.js} +13 -12
- package/dist/chunks/{okr-BZ6XUOAG.js → okr-EOV65SSF.js} +4 -3
- package/dist/chunks/{plan-DR5CMUYP.js → plan-YFPVKW6Y.js} +4 -3
- package/dist/chunks/{project-6J63MK33.js → project-MDVZUYQW.js} +4 -3
- package/dist/chunks/{registry-VRTNC6UR.js → registry-QCLZEDAP.js} +8 -8
- package/dist/chunks/{registry-KDD7DHNK.js → registry-VT5Y2HLA.js} +4 -4
- package/dist/chunks/{registry-TKX2E7C7.js → registry-YX4CSEMD.js} +3 -3
- package/dist/chunks/{room-recommend-XCWRMB5P.js → room-recommend-L3MWH4JW.js} +4 -4
- package/dist/chunks/{schedule-IVBICP32.js → schedule-UASAVDVD.js} +4 -4
- package/dist/chunks/{workflow-D74NFK3C.js → workflow-KDGIUCNW.js} +9 -6
- package/dist/core/output.js +16 -6
- package/dist/index.js +33 -28
- package/dist/internal/e10/auth/messages.js +1 -0
- package/dist/internal/e10/auth-recovery.js +127 -7
- package/dist/internal/e10/client.js +16 -2
- package/dist/internal/e10/context.js +16 -3
- package/dist/internal/e10/request-runtime.js +106 -8
- package/dist/shortcuts/archive/host.js +5 -2
- 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/archive-oa/host.js +2 -1
- package/dist/shortcuts/asset/host.js +6 -5
- 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/blog/host.js +3 -2
- package/dist/shortcuts/caizhilian/host.js +3 -2
- package/dist/shortcuts/calendar/host.js +2 -1
- package/dist/shortcuts/contract/host.js +2 -1
- package/dist/shortcuts/cowork/host.js +2 -1
- package/dist/shortcuts/ebuilder-form/host.js +3 -1
- package/dist/shortcuts/ehr/host.js +2 -1
- package/dist/shortcuts/esb/host.js +2 -1
- package/dist/shortcuts/fna/host.js +2 -1
- package/dist/shortcuts/hrm/host.js +2 -1
- package/dist/shortcuts/im/host.js +3 -2
- package/dist/shortcuts/index.js +5 -0
- package/dist/shortcuts/invoice/host.js +5 -4
- package/dist/shortcuts/jiuchuanhui/host.js +2 -1
- package/dist/shortcuts/mail/host.js +2 -1
- package/dist/shortcuts/meeting/host.js +3 -2
- package/dist/shortcuts/meeting/invitation/text.js +71 -0
- package/dist/shortcuts/meeting/operations/eb/create-flow.js +1073 -50
- package/dist/shortcuts/meeting/operations/eb/crm-search.js +478 -0
- package/dist/shortcuts/meeting/operations/eb/factory.js +60 -1
- package/dist/shortcuts/meeting/operations/eb/index.js +2 -0
- package/dist/shortcuts/meeting/operations/invitation.js +122 -9
- package/dist/shortcuts/okr/host.js +2 -1
- package/dist/shortcuts/plan/host.js +2 -1
- package/dist/shortcuts/project/host.js +2 -1
- package/dist/shortcuts/workflow/host.js +3 -0
- package/dist/shortcuts/workflow/index.js +1 -1
- package/dist/shortcuts/workflow/operations/form.js +44 -11
- package/dist/shortcuts/workflow/operations/todoSmartConvert/aigroupRules.js +77 -26
- package/dist/shortcuts/workflow/operations/todoSmartConvert/aigroupStore.js +6 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/summaryCache.js +59 -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 +20 -6
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.detail.js +22 -18
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.groupSummary.js +34 -10
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.js +4 -2
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.smartGroup.js +9 -2
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.summary.js +110 -20
- package/docs/_catalog.md +3 -0
- package/docs/asset-idle.md +45 -0
- package/docs/e10-auth.md +1 -1
- package/docs/todo-smart-convert/subgroup-rules.md +1 -1
- package/package.json +1 -1
- 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-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-meeting/SKILL.md +250 -301
- package/skills/weaver-e10-meeting/references/meeting-eb.md +203 -0
- package/skills/weaver-e10-meeting/references/meeting-invitation.md +37 -5
- package/skills/weaver-e10-meeting.zip +0 -0
- package/skills/weaver-e10-okr/references/source-manifest.json +3 -3
- 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 +5 -2
- package/skills/weaver-e10-workflow/aigroup/default.json +154 -19
- package/skills/weaver-e10-workflow/references/todo-smart-convert.md +59 -27
- package/skills/weaver-e10-workflow/tools/todo-serve.cjs +113 -25
- package/skills/weaver-e10-workflow/tools/todo-summary.cjs +292 -42
- package/skills/weaver-e10-yimiaoban/references/source-manifest.json +3 -3
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import { existsSync
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
2
|
import { chmod, mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
3
|
-
import { homedir } from 'node:os';
|
|
4
3
|
import { join, resolve } from 'node:path';
|
|
5
|
-
import { stateRoot } from '../../../../core/state.js';
|
|
6
4
|
/**
|
|
7
5
|
* `todoSmartConvert` 的**单条摘要缓存**(放在 Agent 真实 skill 目录下)。
|
|
8
6
|
*
|
|
@@ -18,21 +16,13 @@ import { stateRoot } from '../../../../core/state.js';
|
|
|
18
16
|
* 流程有了新反馈(lastFeedBackDateTime 变化)key 就变了,摘要自然失效、重新生成——
|
|
19
17
|
* 用业务时间做版本号,比记录「摘要生成时间」更准。
|
|
20
18
|
*
|
|
21
|
-
*
|
|
22
|
-
* `WEAVER_WORK_CLI_SKILL_DIR
|
|
23
|
-
*
|
|
19
|
+
* 目录定位(2026-11-01 用户定稿:**缓存的生成与提取都取自当前 skill 自己的 `cache/`**):
|
|
20
|
+
* `WEAVER_WORK_CLI_SKILL_DIR`(tools 脚本把自身所在 skill 目录显式指进来)⇒ 直接用 `<该目录>/cache`;
|
|
21
|
+
* 未设置(裸 CLI,无当前 skill 上下文)⇒ **拒绝读写缓存**(refused),调用方按「没有缓存」处理。
|
|
22
|
+
* 用户已不要求多安装发现与排名——各自 agent 独立安装一份 skill,脚本锚定自身目录即可。
|
|
24
23
|
*/
|
|
25
|
-
/** 与 `skills/` 下发布的目录名一致。 */
|
|
26
|
-
const SKILL_DIR_NAME = 'weaver-e10-workflow';
|
|
27
24
|
/** skill 内的缓存子目录。 */
|
|
28
25
|
const CACHE_DIR_NAME = 'cache';
|
|
29
|
-
/**
|
|
30
|
-
* 已知 Agent 产物的用户级根目录(按优先级)。
|
|
31
|
-
*
|
|
32
|
-
* 只看 `~/<agent>/skills/<skill>/SKILL.md` 是否存在——存在即认为「这个 Agent 装了这个 skill」。
|
|
33
|
-
* **不看工作区里的 `.workbuddy/skills`**:项目内目录属于「本地项目」,用户明确不让缓存放那儿。
|
|
34
|
-
*/
|
|
35
|
-
const AGENT_HOME_DIRS = ['.workbuddy', '.claude', '.codebuddy', '.codex', '.cursor', '.gemini'];
|
|
36
26
|
/** 单条摘要缓存(跨任务,7 天)。 */
|
|
37
27
|
export const SUMMARY_CACHE_FILE_NAME = 'todo-summary-cache.json';
|
|
38
28
|
/**
|
|
@@ -42,105 +32,31 @@ export const SUMMARY_CACHE_FILE_NAME = 'todo-summary-cache.json';
|
|
|
42
32
|
*/
|
|
43
33
|
export const SUMMARY_CACHE_VERSION = 2;
|
|
44
34
|
function uniqueDirs(dirs) {
|
|
45
|
-
return [...new Set(dirs.filter(Boolean))];
|
|
46
|
-
}
|
|
47
|
-
function stateCacheDir(ctx) {
|
|
48
|
-
return resolve(stateRoot(ctx), CACHE_DIR_NAME);
|
|
35
|
+
return [...new Set(dirs.filter((dir) => Boolean(dir)))];
|
|
49
36
|
}
|
|
50
37
|
/**
|
|
51
|
-
*
|
|
52
|
-
*
|
|
38
|
+
* 解析「当前 skill 目录」:只认 `WEAVER_WORK_CLI_SKILL_DIR`(tools 脚本锚定自身目录);
|
|
39
|
+
* 未设置 = 裸 CLI,无当前 skill 上下文。
|
|
53
40
|
*/
|
|
54
|
-
function
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
for (const agent of AGENT_HOME_DIRS) {
|
|
58
|
-
const root = join(home, agent, 'skills');
|
|
59
|
-
const entries = (() => {
|
|
60
|
-
try {
|
|
61
|
-
return readdirSync(root, { withFileTypes: true });
|
|
62
|
-
}
|
|
63
|
-
catch {
|
|
64
|
-
return [];
|
|
65
|
-
}
|
|
66
|
-
})();
|
|
67
|
-
for (const entry of entries) {
|
|
68
|
-
if (!entry.isDirectory())
|
|
69
|
-
continue;
|
|
70
|
-
const dir = join(root, entry.name);
|
|
71
|
-
if (existsSync(join(dir, marker)))
|
|
72
|
-
found.push(dir);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
return found;
|
|
76
|
-
}
|
|
77
|
-
/** 读文件 mtime(毫秒);读不到(不存在/无权限)返回 0。 */
|
|
78
|
-
function mtimeOf(file) {
|
|
79
|
-
try {
|
|
80
|
-
return statSync(file).mtimeMs;
|
|
81
|
-
}
|
|
82
|
-
catch {
|
|
83
|
-
return 0;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
/**
|
|
87
|
-
* 多个候选 skill 目录同时存在时,优先选择已有缓存且安装完整的目录。
|
|
88
|
-
*
|
|
89
|
-
* 排序键(依次比较,越小越优先):
|
|
90
|
-
* 1. 目录里**已有摘要缓存** ⇒ 优先(连续性:别把攒好的摘要换到另一份安装上)
|
|
91
|
-
* 2. 有 `references/todo-smart-convert.md` ⇒ 优先(完整安装优于半解压的残目录)
|
|
92
|
-
* 3. `SKILL.md` 的 mtime 更新 ⇒ 优先(刚复制/刚升级的那份大概率就是正在用的)
|
|
93
|
-
* 4. 原始顺序(`WEAVER_WORK_CLI_SKILL_DIR` 指定的永远排最前)
|
|
94
|
-
*/
|
|
95
|
-
function rankSkillDirs(dirs) {
|
|
96
|
-
const scored = dirs.map((dir, index) => ({
|
|
97
|
-
dir,
|
|
98
|
-
index,
|
|
99
|
-
hasCache: existsSync(join(dir, CACHE_DIR_NAME, SUMMARY_CACHE_FILE_NAME)) ? 1 : 0,
|
|
100
|
-
cacheMtime: mtimeOf(join(dir, CACHE_DIR_NAME, SUMMARY_CACHE_FILE_NAME)),
|
|
101
|
-
hasMarker: existsSync(join(dir, 'references', 'todo-smart-convert.md')) ? 1 : 0,
|
|
102
|
-
skillMtime: mtimeOf(join(dir, 'SKILL.md')),
|
|
103
|
-
}));
|
|
104
|
-
scored.sort((a, b) => b.hasCache - a.hasCache
|
|
105
|
-
|| b.cacheMtime - a.cacheMtime
|
|
106
|
-
|| b.hasMarker - a.hasMarker
|
|
107
|
-
|| b.skillMtime - a.skillMtime
|
|
108
|
-
|| a.index - b.index);
|
|
109
|
-
return scored.map((item) => item.dir);
|
|
41
|
+
export function resolveSkillDir() {
|
|
42
|
+
const explicit = process.env.WEAVER_WORK_CLI_SKILL_DIR?.trim();
|
|
43
|
+
return explicit ? resolve(explicit) : null;
|
|
110
44
|
}
|
|
111
45
|
/** 定位缓存目录(不创建目录,纯路径计算)。 */
|
|
112
46
|
export function cacheRoots(ctx) {
|
|
113
|
-
|
|
114
|
-
const
|
|
115
|
-
if (
|
|
116
|
-
|
|
117
|
-
return { preferred:
|
|
118
|
-
}
|
|
119
|
-
const home = homedir();
|
|
120
|
-
const explicitSkill = process.env.WEAVER_WORK_CLI_SKILL_DIR?.trim();
|
|
121
|
-
const candidates = [];
|
|
122
|
-
if (explicitSkill)
|
|
123
|
-
candidates.push(resolve(explicitSkill));
|
|
124
|
-
for (const agent of AGENT_HOME_DIRS) {
|
|
125
|
-
candidates.push(join(home, agent, 'skills', SKILL_DIR_NAME));
|
|
47
|
+
void ctx;
|
|
48
|
+
const skillDir = resolveSkillDir();
|
|
49
|
+
if (!skillDir) {
|
|
50
|
+
// 裸 CLI:无当前 skill 上下文 ⇒ 缓存拒绝读写(用户 2026-11-01 定稿:拒绝执行)
|
|
51
|
+
return { preferred: null, fallback: null, inSkillDir: false, skillDir: null, refused: true };
|
|
126
52
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
candidates.push(...discoverThisSkillDirs(home));
|
|
130
|
-
// 只认「装了本 skill」的目录,再在多份安装之间择优(见 rankSkillDirs)。
|
|
131
|
-
const installed = uniqueDirs(candidates).filter((dir) => existsSync(join(dir, 'SKILL.md')));
|
|
132
|
-
const [skillDir] = rankSkillDirs(installed);
|
|
133
|
-
if (skillDir) {
|
|
134
|
-
return {
|
|
135
|
-
preferred: join(skillDir, CACHE_DIR_NAME),
|
|
136
|
-
fallback,
|
|
137
|
-
inSkillDir: true,
|
|
138
|
-
skillDir,
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
return { preferred: fallback, fallback, inSkillDir: false, skillDir: null };
|
|
53
|
+
const cacheDir = join(skillDir, CACHE_DIR_NAME);
|
|
54
|
+
return { preferred: cacheDir, fallback: cacheDir, inSkillDir: true, skillDir, refused: false };
|
|
142
55
|
}
|
|
143
56
|
export async function readCacheFile(roots, fileName) {
|
|
57
|
+
// 裸 CLI(无当前 skill 上下文):拒绝读缓存(用户 2026-11-01 定稿)
|
|
58
|
+
if (roots.refused || !roots.preferred)
|
|
59
|
+
return null;
|
|
144
60
|
for (const dir of uniqueDirs([roots.preferred, roots.fallback])) {
|
|
145
61
|
const file = join(dir, fileName);
|
|
146
62
|
if (!existsSync(file))
|
|
@@ -161,6 +77,9 @@ export async function readCacheFile(roots, fileName) {
|
|
|
161
77
|
* 缓存是加速设施,写不进去也绝不能让主流程失败。
|
|
162
78
|
*/
|
|
163
79
|
export async function writeCacheFile(roots, fileName, payload) {
|
|
80
|
+
// 裸 CLI(无当前 skill 上下文):拒绝写缓存(用户 2026-11-01 定稿);目录不可写时静默放弃(不另设兜底)
|
|
81
|
+
if (roots.refused || !roots.preferred)
|
|
82
|
+
return null;
|
|
164
83
|
for (const dir of uniqueDirs([roots.preferred, roots.fallback])) {
|
|
165
84
|
const file = join(dir, fileName);
|
|
166
85
|
try {
|
|
@@ -199,15 +118,28 @@ export function summaryCacheKey(requestId, lastFeedBackDateTime) {
|
|
|
199
118
|
const stamp = String(lastFeedBackDateTime ?? '').trim();
|
|
200
119
|
return `${requestId}__${stamp}`;
|
|
201
120
|
}
|
|
121
|
+
/** 口径提示的入库形式:trim 后原样存储;空口径返回空串(条目不落 pk 字段)。 */
|
|
122
|
+
export function promptTagOf(prompt) {
|
|
123
|
+
return String(prompt ?? '').trim();
|
|
124
|
+
}
|
|
202
125
|
/**
|
|
203
|
-
*
|
|
126
|
+
* 缓存条目是否与当前目标字数 / 口径 / 详情素材匹配。
|
|
204
127
|
*
|
|
205
|
-
* 不传 `maxChars`
|
|
128
|
+
* - 不传 `maxChars` 时不按字数过滤;传入后必须与写入口径一致;
|
|
129
|
+
* - `pk`(口径特征值)同理:规则改了 `requestSummaryPrompt` ⇒ pk 变化 ⇒ 旧摘要自动失效;
|
|
130
|
+
* - `dv`(详情素材特征值,2026-11-01 根治):
|
|
131
|
+
* 条目没有 dv = 旧条目,兼容命中;
|
|
132
|
+
* 条目有 dv 时必须与当前素材特征值一致——当前**没有**详情素材(无法核验)也视为失效
|
|
133
|
+
* (与「缓存没了就当从未生成过、直接走实时逻辑」的口径一致)。
|
|
206
134
|
*/
|
|
207
|
-
function
|
|
208
|
-
if (maxChars
|
|
209
|
-
return
|
|
210
|
-
|
|
135
|
+
function entryMatches(entry, maxChars, pk, dv) {
|
|
136
|
+
if (maxChars !== undefined && (entry.maxChars ?? 50) !== maxChars)
|
|
137
|
+
return false;
|
|
138
|
+
if (pk !== undefined && (entry.pk ?? '') !== pk)
|
|
139
|
+
return false;
|
|
140
|
+
if (entry.dv !== undefined && entry.dv !== dv)
|
|
141
|
+
return false;
|
|
142
|
+
return true;
|
|
211
143
|
}
|
|
212
144
|
function normalizeSummaryEntry(raw) {
|
|
213
145
|
if (!raw || typeof raw !== 'object')
|
|
@@ -218,7 +150,15 @@ function normalizeSummaryEntry(raw) {
|
|
|
218
150
|
if (typeof entry.cachedAt !== 'number' || !Number.isFinite(entry.cachedAt))
|
|
219
151
|
return null;
|
|
220
152
|
const maxChars = typeof entry.maxChars === "number" && Number.isFinite(entry.maxChars) ? entry.maxChars : undefined;
|
|
221
|
-
|
|
153
|
+
const pk = typeof entry.pk === 'string' && entry.pk !== '' ? entry.pk : undefined;
|
|
154
|
+
const dv = typeof entry.dv === 'string' && entry.dv !== '' ? entry.dv : undefined;
|
|
155
|
+
return {
|
|
156
|
+
summary: entry.summary,
|
|
157
|
+
cachedAt: entry.cachedAt,
|
|
158
|
+
...(maxChars === undefined ? {} : { maxChars }),
|
|
159
|
+
...(pk === undefined ? {} : { pk }),
|
|
160
|
+
...(dv === undefined ? {} : { dv }),
|
|
161
|
+
};
|
|
222
162
|
}
|
|
223
163
|
/**
|
|
224
164
|
* 打开摘要缓存(读 + 按需清理)。
|
|
@@ -273,21 +213,21 @@ export async function openSummaryCache(ctx) {
|
|
|
273
213
|
misses: 0,
|
|
274
214
|
pruned,
|
|
275
215
|
dirty: false,
|
|
276
|
-
get(requestId, lastFeedBackDateTime, maxChars) {
|
|
216
|
+
get(requestId, lastFeedBackDateTime, maxChars, pk, dv) {
|
|
277
217
|
const key = summaryCacheKey(requestId, lastFeedBackDateTime);
|
|
278
218
|
const entry = entries.get(key);
|
|
279
|
-
if (entry && now - entry.cachedAt <= SUMMARY_CACHE_TTL_MS &&
|
|
219
|
+
if (entry && now - entry.cachedAt <= SUMMARY_CACHE_TTL_MS && entryMatches(entry, maxChars, pk, dv)) {
|
|
280
220
|
cache.hits += 1;
|
|
281
221
|
return entry.summary;
|
|
282
222
|
}
|
|
283
223
|
cache.misses += 1;
|
|
284
224
|
return null;
|
|
285
225
|
},
|
|
286
|
-
has(requestId, lastFeedBackDateTime, maxChars) {
|
|
226
|
+
has(requestId, lastFeedBackDateTime, maxChars, pk, dv) {
|
|
287
227
|
const entry = entries.get(summaryCacheKey(requestId, lastFeedBackDateTime));
|
|
288
|
-
return entry !== undefined &&
|
|
228
|
+
return entry !== undefined && entryMatches(entry, maxChars, pk, dv);
|
|
289
229
|
},
|
|
290
|
-
set(requestId, lastFeedBackDateTime, summary, maxChars) {
|
|
230
|
+
set(requestId, lastFeedBackDateTime, summary, maxChars, pk, dv) {
|
|
291
231
|
const text = String(summary ?? '').trim();
|
|
292
232
|
if (!requestId || !text)
|
|
293
233
|
return false;
|
|
@@ -295,6 +235,8 @@ export async function openSummaryCache(ctx) {
|
|
|
295
235
|
summary: text,
|
|
296
236
|
cachedAt: Date.now(),
|
|
297
237
|
...(maxChars === undefined ? {} : { maxChars }),
|
|
238
|
+
...(pk === undefined || pk === '' ? {} : { pk }),
|
|
239
|
+
...(dv === undefined || dv === '' ? {} : { dv }),
|
|
298
240
|
});
|
|
299
241
|
dirty = true;
|
|
300
242
|
cache.dirty = true;
|
|
@@ -2,11 +2,12 @@ import { cacheRoots, readCacheFile, writeCacheFile, SUMMARY_CACHE_PRUNE_INTERVAL
|
|
|
2
2
|
/**
|
|
3
3
|
* **流程详情数据缓存**(与摘要缓存并存,互不替代)。
|
|
4
4
|
*
|
|
5
|
-
* 规则(用户 2026-10-26
|
|
5
|
+
* 规则(用户 2026-10-26 定稿;条目格式 2026-10-30 升 v2):
|
|
6
6
|
* - 键:`requestId__lastFeedBackDateTime`(与该条待办的业务时间绑定——流程一动就换键);
|
|
7
7
|
* - 有效期:7 天,与摘要缓存一致;读的时候逻辑过期,超过清理间隔(3 天)时物理剔除;
|
|
8
|
-
* -
|
|
9
|
-
*
|
|
8
|
+
* - **条目带格式版本 `v`**:v2 起素材 = 排除清单过滤后的 formData + 全量 basicInfo;
|
|
9
|
+
* 旧格式条目(无 `v`)读取时视为无效,被新条目逐步覆盖;排除清单变化靠 `excludeTag` 失效;
|
|
10
|
+
* - 详情为空的条目**也写**(`fields: []`),否则这类条目每次都要重拉;
|
|
10
11
|
* - 缓存文件缺失 ⇒ 视为从未缓存过,正常走接口。
|
|
11
12
|
*
|
|
12
13
|
* 位置:与摘要缓存同一个 `cache/` 目录(skill 目录优先,不可写时退回 CLI 状态目录)。
|
|
@@ -16,18 +17,53 @@ export const DETAIL_CACHE_FILE_NAME = 'todo-detail-cache.json';
|
|
|
16
17
|
export const DETAIL_CACHE_TTL_MS = SUMMARY_CACHE_TTL_MS;
|
|
17
18
|
/** 与摘要缓存一致:清理间隔 3 天。 */
|
|
18
19
|
export const DETAIL_CACHE_PRUNE_INTERVAL_MS = SUMMARY_CACHE_PRUNE_INTERVAL_MS;
|
|
20
|
+
/**
|
|
21
|
+
* 缓存条目格式版本。v2(2026-10-30):
|
|
22
|
+
* - 素材改为「排除清单过滤后的 formData `fieldName:value` + 全量 basicInfo」,
|
|
23
|
+
* 取代旧版「selectSummaryFields 投影 + 8 字段/80 字截断」;
|
|
24
|
+
* - 新增 `excludeTag`(排除清单原文拼接)——清单一变旧条目整体失效;
|
|
25
|
+
* - v1 条目(无 `v` 键)读取时直接视为无效并逐步被覆盖。
|
|
26
|
+
*/
|
|
27
|
+
export const DETAIL_CACHE_ENTRY_VERSION = 3;
|
|
19
28
|
export function detailCacheKey(requestId, lastFeedBackDateTime) {
|
|
20
29
|
const stamp = String(lastFeedBackDateTime ?? '').trim();
|
|
21
30
|
return `${requestId}__${stamp}`;
|
|
22
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* 详情素材特征值(2026-11-01 根治「素材升级后旧摘要继续命中」):对 `basicInfo + fields`
|
|
34
|
+
* 的稳定序列化做 djb2 哈希。任何素材变化——详情管线升级、`excludeOriginFormDataFields`
|
|
35
|
+
* 调整、字段增减——都会让它改变;摘要缓存条目的 `dv` 与之比对即可自动失效重写。
|
|
36
|
+
*/
|
|
37
|
+
export function detailMaterialTagOf(entry) {
|
|
38
|
+
const basic = entry.basicInfo ?? {};
|
|
39
|
+
const basicKeys = Object.keys(basic).sort();
|
|
40
|
+
const material = JSON.stringify([
|
|
41
|
+
basicKeys.map((key) => [key, basic[key]]),
|
|
42
|
+
(entry.fields ?? []).map((field) => [field.fieldName, field.value]),
|
|
43
|
+
]);
|
|
44
|
+
let hash = 5381;
|
|
45
|
+
for (let i = 0; i < material.length; i += 1)
|
|
46
|
+
hash = ((hash << 5) + hash + material.charCodeAt(i)) >>> 0;
|
|
47
|
+
return hash.toString(16);
|
|
48
|
+
}
|
|
23
49
|
function normalizeEntry(raw) {
|
|
24
50
|
if (!raw || typeof raw !== 'object')
|
|
25
51
|
return null;
|
|
26
52
|
const value = raw;
|
|
53
|
+
// 旧格式(v1:context + 8 字段/80 字截断)与新口径不兼容,直接视为无效
|
|
54
|
+
if (value.v !== DETAIL_CACHE_ENTRY_VERSION)
|
|
55
|
+
return null;
|
|
27
56
|
if (typeof value.cachedAt !== 'number' || !Number.isFinite(value.cachedAt))
|
|
28
57
|
return null;
|
|
29
|
-
if (typeof value.
|
|
58
|
+
if (typeof value.excludeTag !== 'string')
|
|
30
59
|
return null;
|
|
60
|
+
const basicInfo = {};
|
|
61
|
+
if (value.basicInfo && typeof value.basicInfo === 'object') {
|
|
62
|
+
for (const [key, item] of Object.entries(value.basicInfo)) {
|
|
63
|
+
if (typeof item === 'string')
|
|
64
|
+
basicInfo[key] = item;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
31
67
|
const fields = Array.isArray(value.fields)
|
|
32
68
|
? value.fields
|
|
33
69
|
.filter((item) => Boolean(item) && typeof item.fieldName === 'string'
|
|
@@ -36,10 +72,11 @@ function normalizeEntry(raw) {
|
|
|
36
72
|
: [];
|
|
37
73
|
const metrics = value.metrics && typeof value.metrics === 'object' ? value.metrics : {};
|
|
38
74
|
return {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
metrics,
|
|
75
|
+
v: DETAIL_CACHE_ENTRY_VERSION,
|
|
76
|
+
basicInfo,
|
|
42
77
|
fields,
|
|
78
|
+
metrics,
|
|
79
|
+
excludeTag: value.excludeTag,
|
|
43
80
|
cachedAt: value.cachedAt,
|
|
44
81
|
};
|
|
45
82
|
}
|
|
@@ -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
|
});
|
|
@@ -4,7 +4,8 @@ import { saveSearchIndex } from '../../state.js';
|
|
|
4
4
|
import { buildSearchQuery } from '../read.js';
|
|
5
5
|
import { projectDisplayRows } from './todoListRows.js';
|
|
6
6
|
import { responseData } from '../shared.js';
|
|
7
|
-
import { openSummaryCache, SUMMARY_CACHE_FILE_NAME } from './summaryCache.js';
|
|
7
|
+
import { openSummaryCache, promptTagOf, SUMMARY_CACHE_FILE_NAME } from './summaryCache.js';
|
|
8
|
+
import { openDetailCache, detailMaterialTagOf } from './todoDetailCache.js';
|
|
8
9
|
import { fetchDetailFacts, stampsOfRows } from './todoSmartConvert.detail.js';
|
|
9
10
|
import { GROUP_MIN_ITEMS_FOR_SUBGROUP, isExpiringTodayRow, isOverdueRow, runGroup, } from './todoSmartConvert.group.js';
|
|
10
11
|
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';
|
|
@@ -144,11 +145,24 @@ export async function collectRawTodos(args, env) {
|
|
|
144
145
|
wfIdGroup = (grouped.wfIdGroup ?? []);
|
|
145
146
|
// 摘要缓存 key 为 requestId 与最后反馈时间;反馈变化会自然失效。
|
|
146
147
|
const summaryCache = await openSummaryCache(env.ctx);
|
|
147
|
-
//
|
|
148
|
-
|
|
148
|
+
// 字数口径按每条所属工作流的规则算(入参 > 规则 summaryMaxChars > 兜底),
|
|
149
|
+
// 否则不同口径的旧摘要会被错命中/错判失效。
|
|
150
|
+
const collectMaxCharsByGroup = new Map();
|
|
151
|
+
const collectMaxCharsFor = (workflowName) => {
|
|
152
|
+
const key = workflowName ?? '';
|
|
153
|
+
const hit = collectMaxCharsByGroup.get(key);
|
|
154
|
+
if (hit !== undefined)
|
|
155
|
+
return hit;
|
|
156
|
+
const value = readBoundedInt(args.summaryMaxChars ?? activeRuleFor(key)?.summaryMaxChars, "summaryMaxChars", DEFAULT_REQUEST_SUMMARY_MAX, 10, 500);
|
|
157
|
+
collectMaxCharsByGroup.set(key, value);
|
|
158
|
+
return value;
|
|
159
|
+
};
|
|
149
160
|
const cachedSummaryIds = new Set();
|
|
161
|
+
// 摘要条目的素材特征值:详情素材变了 ⇒ 旧摘要失效、重新走 AI(2026-11-01 根治)
|
|
162
|
+
const detailCacheForTag = await openDetailCache(env.ctx);
|
|
150
163
|
for (const row of items) {
|
|
151
|
-
const
|
|
164
|
+
const detailEntry = detailCacheForTag.get(row.requestId, row.lastFeedBackDateTime);
|
|
165
|
+
const cached = summaryCache.get(row.requestId, row.lastFeedBackDateTime, collectMaxCharsFor(row.workflowName), promptTagOf(activeRuleFor(row.workflowName ?? '')?.requestSummaryPrompt ?? ''), detailEntry ? detailMaterialTagOf(detailEntry) : undefined);
|
|
152
166
|
if (!cached)
|
|
153
167
|
continue;
|
|
154
168
|
row.summary = cached;
|
|
@@ -237,8 +251,8 @@ export async function collectRawTodos(args, env) {
|
|
|
237
251
|
+ `(缓存文件:${summaryCache.file ?? SUMMARY_CACHE_FILE_NAME})`);
|
|
238
252
|
}
|
|
239
253
|
if (!summaryCache.inSkillDir) {
|
|
240
|
-
warnings.push('
|
|
241
|
-
+ '
|
|
254
|
+
warnings.push('无当前 skill 上下文(WEAVER_WORK_CLI_SKILL_DIR 未设置):摘要缓存与 aigroup 规则均未读写,'
|
|
255
|
+
+ '本次按「从未生成过」走实时逻辑;请经 skill 的 tools 脚本调用');
|
|
242
256
|
}
|
|
243
257
|
}
|
|
244
258
|
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
|
}
|