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.
Files changed (219) hide show
  1. package/dist/chunks/archive-U766FTFH.js +2537 -0
  2. package/dist/chunks/archive-oa-DMWEKX3L.js +1317 -0
  3. package/dist/chunks/asset-TCR7VDEV.js +2109 -0
  4. package/dist/chunks/attachment-content-AQNX5N3S.js +23 -0
  5. package/dist/chunks/blog-EVZWGDVM.js +2157 -0
  6. package/dist/chunks/caizhilian-JOWS6SL5.js +10087 -0
  7. package/dist/chunks/calendar-5WNCV66Q.js +1195 -0
  8. package/dist/chunks/chunk-2DUV44UX.js +15606 -0
  9. package/dist/chunks/chunk-324YPTC2.js +1607 -0
  10. package/dist/chunks/chunk-65QMVWHS.js +231 -0
  11. package/dist/chunks/chunk-6V7ZLF34.js +387 -0
  12. package/dist/chunks/chunk-AC7KJWC5.js +358 -0
  13. package/dist/chunks/chunk-AJ62BGAW.js +29 -0
  14. package/dist/chunks/chunk-BUSC6UAE.js +43 -0
  15. package/dist/chunks/chunk-CVZ2JOSW.js +62 -0
  16. package/dist/chunks/chunk-D4LTJSJD.js +477 -0
  17. package/dist/chunks/chunk-EG6ZU444.js +654 -0
  18. package/dist/chunks/chunk-EZALDOQH.js +383 -0
  19. package/dist/chunks/chunk-FMPJJYYI.js +106 -0
  20. package/dist/chunks/chunk-G2JSIUWF.js +769 -0
  21. package/dist/chunks/chunk-GCG2NMWR.js +703 -0
  22. package/dist/chunks/chunk-GPMFD4RD.js +75 -0
  23. package/dist/chunks/chunk-H3WDBYJW.js +508 -0
  24. package/dist/chunks/chunk-H6XSTSLM.js +2357 -0
  25. package/dist/chunks/chunk-J7RJTZSV.js +5556 -0
  26. package/dist/chunks/chunk-LUSPKZKY.js +53 -0
  27. package/dist/chunks/chunk-NIAJ5ZCG.js +73 -0
  28. package/dist/chunks/chunk-NOODWMCL.js +584 -0
  29. package/dist/chunks/chunk-OOUTBLZ4.js +51 -0
  30. package/dist/chunks/chunk-P26ZWSKU.js +73 -0
  31. package/dist/chunks/chunk-QWY6PGL6.js +3472 -0
  32. package/dist/chunks/chunk-R2IJAK7T.js +464 -0
  33. package/dist/chunks/chunk-R7O5PUWO.js +205 -0
  34. package/dist/chunks/chunk-RCGYCOMG.js +6962 -0
  35. package/dist/chunks/chunk-VZTXRG6X.js +403 -0
  36. package/dist/chunks/chunk-WHD4XMRP.js +161 -0
  37. package/dist/chunks/chunk-YLCYX7LE.js +371 -0
  38. package/dist/chunks/chunk-ZEDWSH32.js +68 -0
  39. package/dist/chunks/continuation-2CKSBLA6.js +11 -0
  40. package/dist/chunks/continuation-HZIZRZBX.js +13 -0
  41. package/dist/chunks/continuation-NLECEJPX.js +11 -0
  42. package/dist/chunks/ebuilder-form-NJ4YK6CC.js +2276 -0
  43. package/dist/chunks/ehr-MMYN36VA.js +5241 -0
  44. package/dist/chunks/employee-SAK2QYXX.js +11 -0
  45. package/dist/chunks/esb-HIWHOU7Q.js +141 -0
  46. package/dist/chunks/fna-KBACQTA5.js +1444 -0
  47. package/dist/chunks/host-3OHS5QUX.js +21 -0
  48. package/dist/chunks/host-IG6KEH5Q.js +19 -0
  49. package/dist/chunks/host-KQL5N4HO.js +23 -0
  50. package/dist/chunks/host-LLSZQCWR.js +17 -0
  51. package/dist/chunks/hrm-LYEC67ZQ.js +244 -0
  52. package/dist/chunks/im-RN2UKAEU.js +252 -0
  53. package/dist/chunks/invoice-EEPHLAF6.js +5179 -0
  54. package/dist/chunks/jiuchuanhui-WVVZYSJE.js +1940 -0
  55. package/dist/chunks/lazy-dispatch-MDOFFL2S.js +28 -0
  56. package/dist/chunks/mail-LNQLTF6L.js +2695 -0
  57. package/dist/chunks/meeting-XSD3BDVQ.js +504 -0
  58. package/dist/chunks/msg-parse-4GSQ2WTO.js +41 -0
  59. package/dist/chunks/okr-TMWTIU2K.js +2482 -0
  60. package/dist/chunks/plan-A2FSFA7T.js +2025 -0
  61. package/dist/chunks/project-T26QLBQF.js +1361 -0
  62. package/dist/chunks/registry-2VLUAESA.js +14 -0
  63. package/dist/chunks/registry-JCFEYXOE.js +18 -0
  64. package/dist/chunks/registry-NYPYMZXG.js +16 -0
  65. package/dist/chunks/registry-Q7OQJ3SU.js +19 -0
  66. package/dist/chunks/registry-ZJMJVWFA.js +19 -0
  67. package/dist/chunks/room-recommend-UR4ZDZS6.js +20 -0
  68. package/dist/chunks/schedule-5L2PM4M6.js +24 -0
  69. package/dist/chunks/state-Y2XBP6YE.js +15 -0
  70. package/dist/chunks/workflow-TR6PG2SL.js +245 -0
  71. package/dist/cmd/argv.js +45 -0
  72. package/dist/cmd/docs/index.js +3 -3
  73. package/dist/cmd/lazy-dispatch.js +20 -0
  74. package/dist/cmd/root.js +2 -4
  75. package/dist/cmd/setup/index.js +2 -2
  76. package/dist/cmd/skills/index.js +2 -2
  77. package/dist/core/package-paths.js +47 -0
  78. package/dist/index.js +163 -7
  79. package/dist/internal/e10/auth/xiaoe.js +3 -3
  80. package/dist/internal/e10/request-runtime.js +18 -62
  81. package/dist/internal/skills/detector.js +2 -2
  82. package/dist/shortcuts/contract/data/performance-catalog.js +1306 -0
  83. package/dist/shortcuts/contract/host.js +91 -35
  84. package/dist/shortcuts/contract/index.js +213 -0
  85. package/dist/shortcuts/contract/manifest.js +928 -0
  86. package/dist/shortcuts/contract/ooxml/docx.js +295 -0
  87. package/dist/shortcuts/contract/ooxml/xlsx.js +221 -0
  88. package/dist/shortcuts/contract/ooxml/zip.js +158 -0
  89. package/dist/shortcuts/contract/operations/assistant.js +1156 -0
  90. package/dist/shortcuts/contract/operations/clause-library.js +522 -0
  91. package/dist/shortcuts/contract/operations/compare.js +193 -0
  92. package/dist/shortcuts/contract/operations/credit-page.js +34 -0
  93. package/dist/shortcuts/contract/operations/flow.js +98 -0
  94. package/dist/shortcuts/contract/operations/page.js +66 -0
  95. package/dist/shortcuts/contract/operations/performance.js +733 -0
  96. package/dist/shortcuts/contract/operations/quote.js +557 -0
  97. package/dist/shortcuts/contract/operations/recommend.js +78 -0
  98. package/dist/shortcuts/contract/operations/registry.js +18 -0
  99. package/dist/shortcuts/contract/operations/review.js +83 -0
  100. package/dist/shortcuts/contract/operations/shared.js +37 -0
  101. package/dist/shortcuts/contract/operations/upload.js +108 -0
  102. package/dist/shortcuts/contract/operations/write.js +3 -2
  103. package/dist/shortcuts/index.js +140 -40
  104. package/dist/shortcuts/invoice/host.js +2 -1
  105. package/dist/shortcuts/invoice/manifest.js +36 -12
  106. package/dist/shortcuts/invoice/operations/issuing-qys.js +132 -36
  107. package/dist/shortcuts/invoice/operations/reim-auto.js +9 -0
  108. package/dist/shortcuts/invoice/operations/reim.js +81 -5
  109. package/dist/shortcuts/invoice/operations/shared.js +12 -5
  110. package/dist/shortcuts/meeting/host.js +45 -3
  111. package/dist/shortcuts/meeting/invitation/docx.js +442 -0
  112. package/dist/shortcuts/meeting/operations/attachment-content.js +418 -0
  113. package/dist/shortcuts/meeting/operations/attachment.js +442 -0
  114. package/dist/shortcuts/meeting/operations/eb/cache.js +57 -0
  115. package/dist/shortcuts/meeting/operations/eb/catalog.js +343 -28
  116. package/dist/shortcuts/meeting/operations/eb/conflict.js +372 -11
  117. package/dist/shortcuts/meeting/operations/eb/create-flow.js +710 -0
  118. package/dist/shortcuts/meeting/operations/eb/decision.js +47 -1
  119. package/dist/shortcuts/meeting/operations/eb/factory.js +129 -11
  120. package/dist/shortcuts/meeting/operations/eb/index.js +122 -2
  121. package/dist/shortcuts/meeting/operations/eb/room-recommend.js +378 -0
  122. package/dist/shortcuts/meeting/operations/eb/room-summary.js +84 -0
  123. package/dist/shortcuts/meeting/operations/eb/schedule.js +450 -0
  124. package/dist/shortcuts/meeting/operations/from-group-chat.js +576 -0
  125. package/dist/shortcuts/meeting/operations/invitation.js +458 -0
  126. package/dist/shortcuts/meeting/operations/minute-content.js +266 -0
  127. package/dist/shortcuts/meeting/operations/pipeline.js +489 -0
  128. package/dist/shortcuts/meeting/operations/qrcode.js +131 -0
  129. package/dist/shortcuts/meeting/operations/receipts.js +1 -1
  130. package/dist/shortcuts/meeting/operations/registry.js +18 -0
  131. package/dist/shortcuts/meeting/operations/summary.js +276 -0
  132. package/dist/shortcuts/meeting/operations/task-notify.js +325 -0
  133. package/dist/shortcuts/meeting/operations.js +42 -1
  134. package/dist/shortcuts/meeting/qr/encoder.js +427 -0
  135. package/dist/shortcuts/meeting/qr/render.js +103 -0
  136. package/dist/shortcuts/workflow/index.js +3 -0
  137. package/dist/shortcuts/workflow/operations/form.js +17 -5
  138. package/dist/shortcuts/workflow/operations/read.js +13 -6
  139. package/dist/shortcuts/workflow/operations/registry.js +2 -0
  140. package/dist/shortcuts/workflow/operations/todoSmartConvert/businessRules.js +331 -0
  141. package/dist/shortcuts/workflow/operations/todoSmartConvert/detailFieldRules.js +133 -0
  142. package/dist/shortcuts/workflow/operations/todoSmartConvert/summaryCache.js +326 -0
  143. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoDetailCache.js +113 -0
  144. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoDetailFetcher.js +259 -0
  145. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoListRows.js +64 -0
  146. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoOutputPlanner.js +28 -0
  147. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.aggregate.js +70 -0
  148. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.collect.js +267 -0
  149. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.core.js +392 -0
  150. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.detail.js +165 -0
  151. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.group.js +448 -0
  152. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.groupSummary.js +348 -0
  153. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.js +222 -0
  154. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.pipeline.js +66 -0
  155. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.smartGroup.js +502 -0
  156. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.summary.js +540 -0
  157. package/docs/_catalog.md +1 -0
  158. package/docs/contract.md +39 -4
  159. package/docs/invoice.md +7 -7
  160. package/docs/todo-smart-convert/README.md +171 -0
  161. package/docs/todo-smart-convert/data-contract.md +236 -0
  162. package/docs/todo-smart-convert/mock/ai-todo-query-target.json +209 -0
  163. package/docs/todo-smart-convert/subgroup-rules.md +245 -0
  164. package/package.json +3 -2
  165. package/scripts/bundle.mjs +63 -0
  166. package/scripts/package-skill.mjs +1 -1
  167. package/skill-template/business-info.json +25 -4
  168. package/skill-template/domains/jinchengda.md +16 -3
  169. package/skill-template/domains/yepiaotong.md +3 -3
  170. package/skills/weaver-e10-jinchengda/SKILL.md +111 -31
  171. package/skills/weaver-e10-jinchengda/references/contract-agent-entry.md +7 -13
  172. package/skills/weaver-e10-jinchengda/references/contract-ai-assistant.md +150 -0
  173. package/skills/weaver-e10-jinchengda/references/contract-ai-review.md +87 -0
  174. package/skills/weaver-e10-jinchengda/references/contract-clause-library-crud.md +111 -0
  175. package/skills/weaver-e10-jinchengda/references/contract-clause-recommender.md +0 -15
  176. package/skills/weaver-e10-jinchengda/references/contract-clause-reference-save.md +0 -13
  177. package/skills/weaver-e10-jinchengda/references/contract-credit-page.md +50 -0
  178. package/skills/weaver-e10-jinchengda/references/contract-disabled-capabilities.md +4 -2
  179. package/skills/weaver-e10-jinchengda/references/contract-doc-compare.md +74 -0
  180. package/skills/weaver-e10-jinchengda/references/contract-flow-launch.md +84 -0
  181. package/skills/weaver-e10-jinchengda/references/contract-performance-query.md +29 -0
  182. package/skills/weaver-e10-jinchengda/references/contract-quote-to-contract.md +28 -0
  183. package/skills/weaver-e10-jinchengda/references/contract-request-status.md +2 -21
  184. package/skills/weaver-e10-jinchengda/references/contract-request-write.md +0 -24
  185. package/skills/weaver-e10-jinchengda/references/contract-signing-subjects.md +0 -15
  186. package/skills/weaver-e10-jinchengda/references/contract-subject-create.md +0 -17
  187. package/skills/weaver-e10-jinchengda/references/contract-template-recommend.md +70 -0
  188. package/skills/weaver-e10-jinchengda/references/contract-template-selector.md +0 -15
  189. package/skills/weaver-e10-jinchengda/references/contract-workflow-discovery.md +0 -15
  190. package/skills/weaver-e10-jinchengda/references/source-manifest.json +1168 -152
  191. package/skills/weaver-e10-meeting/SKILL.md +493 -17
  192. package/skills/weaver-e10-meeting/references/meeting-change.md +4 -4
  193. package/skills/weaver-e10-meeting/references/meeting-create.md +6 -6
  194. package/skills/weaver-e10-meeting/references/meeting-eb.md +503 -27
  195. package/skills/weaver-e10-meeting/references/meeting-invitation.md +113 -0
  196. package/skills/weaver-e10-meeting/references/meeting-lifecycle.md +6 -6
  197. package/skills/weaver-e10-meeting/references/meeting-minute-file.md +425 -0
  198. package/skills/weaver-e10-meeting/references/meeting-queries.md +4 -4
  199. package/skills/weaver-e10-meeting/references/meeting-receipts-signs.md +15 -15
  200. package/skills/weaver-e10-meeting/references/meeting-verification.md +19 -11
  201. package/skills/weaver-e10-meeting.zip +0 -0
  202. package/skills/weaver-e10-workflow/SKILL.md +23 -1
  203. package/skills/weaver-e10-workflow/references/todo-smart-convert.md +282 -0
  204. package/skills/weaver-e10-workflow/scripts/todo-summary.cjs +695 -0
  205. package/skills/weaver-e10-yepiaotong/SKILL.md +15 -11
  206. package/skills/weaver-e10-yepiaotong/references/invoice-enterprise-list.md +1 -1
  207. package/skills/weaver-e10-yepiaotong/references/invoice-issuing-examples.md +10 -7
  208. package/skills/weaver-e10-yepiaotong/references/invoice-issuing-form-fill-rules.md +41 -14
  209. package/skills/weaver-e10-yepiaotong/references/invoice-issuing-workflow.md +54 -26
  210. package/skills/weaver-e10-yepiaotong/references/invoice-personal-list.md +6 -5
  211. package/skills/weaver-e10-yepiaotong/references/invoice-reim.md +14 -11
  212. package/skills/weaver-e10-yepiaotong/references/reim-api-reference.md +97 -56
  213. package/skills/weaver-e10-yepiaotong/references/reim-examples.md +84 -60
  214. package/skills/weaver-e10-yepiaotong/references/reim-form-fill-rules.md +142 -105
  215. package/skills/weaver-e10-yepiaotong/references/reim-matching-rules.md +53 -194
  216. package/skills/weaver-e10-yepiaotong/references/reim-phone-subsidy-rules.md +7 -7
  217. package/skills/weaver-e10-yepiaotong/references/reim-workflow.md +164 -72
  218. package/skills/weaver-e10-yepiaotong/references/source-manifest.json +8 -2
  219. package/templates/meeting/invitation.docx +0 -0
@@ -0,0 +1,326 @@
1
+ import { existsSync, readdirSync, statSync } from 'node:fs';
2
+ import { chmod, mkdir, readFile, writeFile } from 'node:fs/promises';
3
+ import { homedir } from 'node:os';
4
+ import { join, resolve } from 'node:path';
5
+ import { stateRoot } from '../../../../core/state.js';
6
+ /**
7
+ * `todoSmartConvert` 的**单条摘要缓存**(放在 Agent 真实 skill 目录下)。
8
+ *
9
+ * 只缓存摘要文本:详情是流水线中的临时事实,需要时重新请求并在本次调用内复用;
10
+ * 摘要是模型产出的结果,跨任务复用可以减少重复的模型调用。
11
+ *
12
+ * 缓存位置不放在项目工作区:
13
+ * - 摘要是「这条流程的业务内容」,跟着 skill 走更合理;
14
+ * - 缓存优先落在实际使用的 skill 目录,不写入项目工作区;
15
+ * - skill 目录随 skill 安装/卸载一起生效,换机器时可由 skill 分发带走(或自然失效)。
16
+ *
17
+ * 生命周期:**跨任务 7 天**。key 是 `${requestId}__${lastFeedBackDateTime}`,value 是该条的单条摘要。
18
+ * 流程有了新反馈(lastFeedBackDateTime 变化)key 就变了,摘要自然失效、重新生成——
19
+ * 用业务时间做版本号,比记录「摘要生成时间」更准。
20
+ *
21
+ * 目录定位顺序:`WEAVER_WORK_CLI_CACHE_DIR`(直接指定缓存目录,测试用)→
22
+ * `WEAVER_WORK_CLI_SKILL_DIR`(直接指定 skill 目录)→ 已知 Agent 的全局 skill 根目录
23
+ * (`~/.workbuddy/skills`、`~/.claude/skills` …)→ 全部不存在时退回 CLI 状态目录(并在 warnings 里说明)。
24
+ */
25
+ /** 与 `skills/` 下发布的目录名一致。 */
26
+ const SKILL_DIR_NAME = 'weaver-e10-workflow';
27
+ /** skill 内的缓存子目录。 */
28
+ 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
+ /** 单条摘要缓存(跨任务,7 天)。 */
37
+ export const SUMMARY_CACHE_FILE_NAME = 'todo-summary-cache.json';
38
+ /**
39
+ * 摘要缓存的**规则版本**。
40
+ *
41
+ * 版本变化时旧条目整体视为不可用,避免不同摘要字数口径混用。
42
+ */
43
+ export const SUMMARY_CACHE_VERSION = 2;
44
+ function uniqueDirs(dirs) {
45
+ return [...new Set(dirs.filter(Boolean))];
46
+ }
47
+ function stateCacheDir(ctx) {
48
+ return resolve(stateRoot(ctx), CACHE_DIR_NAME);
49
+ }
50
+ /**
51
+ * 扫已知 Agent 的 skills 根,找「本 skill 的目录」——判据是 `references/todo-smart-convert.md` 存在。
52
+ * 用于 skill 目录被改名(例如本地测试副本)后仍能定位到已攒的摘要缓存。
53
+ */
54
+ function discoverThisSkillDirs(home) {
55
+ const marker = join('references', 'todo-smart-convert.md');
56
+ const found = [];
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);
110
+ }
111
+ /** 定位缓存目录(不创建目录,纯路径计算)。 */
112
+ export function cacheRoots(ctx) {
113
+ const fallback = stateCacheDir(ctx);
114
+ const explicitCache = process.env.WEAVER_WORK_CLI_CACHE_DIR?.trim();
115
+ if (explicitCache) {
116
+ const dir = resolve(explicitCache);
117
+ return { preferred: dir, fallback: dir, inSkillDir: false, skillDir: null };
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));
126
+ }
127
+ // 目录名可能被改(如本地测试副本 `todoSmartGroupTest`):再按「本 skill 的特征文件」扫一遍,
128
+ // 避免改名后缓存落到 CLI 状态目录、把已攒的摘要全丢掉。
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 };
142
+ }
143
+ export async function readCacheFile(roots, fileName) {
144
+ for (const dir of uniqueDirs([roots.preferred, roots.fallback])) {
145
+ const file = join(dir, fileName);
146
+ if (!existsSync(file))
147
+ continue;
148
+ try {
149
+ return { text: await readFile(file, 'utf8'), file };
150
+ }
151
+ catch {
152
+ continue;
153
+ }
154
+ }
155
+ return null;
156
+ }
157
+ /**
158
+ * 写缓存文件。**直接覆盖写,不做 rename 原子替换**(与 `workflow/state.ts` 同一条结论:
159
+ * 宿主沙箱里 `rename` 恒被拦,还会连带产生删除动作与审批弹窗)。
160
+ * 首选目录不可写(只读安装、权限不足)时退回 CLI 状态目录;两处都失败返回 null——
161
+ * 缓存是加速设施,写不进去也绝不能让主流程失败。
162
+ */
163
+ export async function writeCacheFile(roots, fileName, payload) {
164
+ for (const dir of uniqueDirs([roots.preferred, roots.fallback])) {
165
+ const file = join(dir, fileName);
166
+ try {
167
+ await mkdir(dir, { recursive: true, mode: 0o700 });
168
+ await writeFile(file, payload, { mode: 0o600 });
169
+ if (process.platform !== 'win32')
170
+ await chmod(file, 0o600).catch(() => undefined);
171
+ return file;
172
+ }
173
+ catch {
174
+ continue;
175
+ }
176
+ }
177
+ return null;
178
+ }
179
+ // ---------------------------------------------------------------------------
180
+ // 单条摘要缓存(跨任务)
181
+ // ---------------------------------------------------------------------------
182
+ /** 摘要有效期:7 天。 */
183
+ export const SUMMARY_CACHE_TTL_MS = 7 * 24 * 60 * 60 * 1000;
184
+ /**
185
+ * 物理清理间隔:3 天。
186
+ *
187
+ * 读的时候本来就会把过期条目「当作不存在」,但那只是逻辑过期——文件里的脏数据还在长。
188
+ * 每 3 天在触发(任意一次打开缓存)时做一次按需清理,把过期条目真正从文件里删掉。
189
+ */
190
+ export const SUMMARY_CACHE_PRUNE_INTERVAL_MS = 3 * 24 * 60 * 60 * 1000;
191
+ /**
192
+ * 缓存 key:`<requestId>__<lastFeedBackDateTime>`。
193
+ *
194
+ * 把**最后反馈时间**拼进 key 是关键设计:流程一旦有新反馈,业务内容就变了,
195
+ * 旧摘要必须失效、重新生成。用业务时间当版本号,比「摘要生成时间 + TTL」更准,
196
+ * 也不会因为「刚写完摘要但流程又动了」而给出过期总结。
197
+ */
198
+ export function summaryCacheKey(requestId, lastFeedBackDateTime) {
199
+ const stamp = String(lastFeedBackDateTime ?? '').trim();
200
+ return `${requestId}__${stamp}`;
201
+ }
202
+ /**
203
+ * 缓存条目是否与当前目标字数口径匹配。
204
+ *
205
+ * 不传 `maxChars` 时不按字数过滤;传入后必须与写入口径一致。
206
+ */
207
+ function entryMatchesMaxChars(entry, maxChars) {
208
+ if (maxChars === undefined)
209
+ return true;
210
+ return (entry.maxChars ?? 50) === maxChars;
211
+ }
212
+ function normalizeSummaryEntry(raw) {
213
+ if (!raw || typeof raw !== 'object')
214
+ return null;
215
+ const entry = raw;
216
+ if (typeof entry.summary !== 'string' || !entry.summary.trim())
217
+ return null;
218
+ if (typeof entry.cachedAt !== 'number' || !Number.isFinite(entry.cachedAt))
219
+ return null;
220
+ const maxChars = typeof entry.maxChars === "number" && Number.isFinite(entry.maxChars) ? entry.maxChars : undefined;
221
+ return { summary: entry.summary, cachedAt: entry.cachedAt, ...(maxChars === undefined ? {} : { maxChars }) };
222
+ }
223
+ /**
224
+ * 打开摘要缓存(读 + 按需清理)。
225
+ *
226
+ * 清理规则:
227
+ * - **逻辑过期 7 天**:`cachedAt` 超过 7 天的条目,读的时候直接当不存在;
228
+ * - **物理清理 3 天**:距上次清理超过 3 天,就在这次打开时把过期条目真正写掉(顺带压缩文件)。
229
+ * 这样文件不会因为「只读不写」永远留着过期数据,也不会每次打开都重写一遍。
230
+ */
231
+ export async function openSummaryCache(ctx) {
232
+ const roots = cacheRoots(ctx);
233
+ const stored = await readCacheFile(roots, SUMMARY_CACHE_FILE_NAME);
234
+ const now = Date.now();
235
+ const entries = new Map();
236
+ let prunedAt = 0;
237
+ let loadedCount = 0;
238
+ // 文件不存在(或暂时不可读)就是首次运行:按空缓存继续,不追查原因、不阻塞主流程。
239
+ if (stored) {
240
+ try {
241
+ const parsed = JSON.parse(stored.text);
242
+ prunedAt = typeof parsed.prunedAt === 'number' ? parsed.prunedAt : 0;
243
+ // 版本不符 = 旧口径写的摘要,整份当不存在(下一次写会覆盖掉)
244
+ const versionOk = parsed.version === SUMMARY_CACHE_VERSION;
245
+ const rawEntries = versionOk && parsed.entries && typeof parsed.entries === 'object'
246
+ ? parsed.entries
247
+ : {};
248
+ for (const [key, raw] of Object.entries(rawEntries)) {
249
+ const entry = normalizeSummaryEntry(raw);
250
+ if (!entry)
251
+ continue;
252
+ loadedCount += 1;
253
+ if (now - entry.cachedAt > SUMMARY_CACHE_TTL_MS)
254
+ continue;
255
+ entries.set(key, entry);
256
+ }
257
+ }
258
+ catch {
259
+ // 坏文件按空缓存处理(下一次写会整体覆盖掉)
260
+ }
261
+ }
262
+ const pruned = loadedCount - entries.size;
263
+ // 距上次物理清理超过 3 天(或从未清理过)且文件确实存在 ⇒ 现在就清理。
264
+ // 文件不存在时不必凭空创建一个空文件。
265
+ const pruneDue = stored !== null && (prunedAt === 0 || now - prunedAt > SUMMARY_CACHE_PRUNE_INTERVAL_MS);
266
+ let file = stored?.file ?? null;
267
+ let dirty = false;
268
+ const cache = {
269
+ file,
270
+ inSkillDir: roots.inSkillDir,
271
+ size: entries.size,
272
+ hits: 0,
273
+ misses: 0,
274
+ pruned,
275
+ dirty: false,
276
+ get(requestId, lastFeedBackDateTime, maxChars) {
277
+ const key = summaryCacheKey(requestId, lastFeedBackDateTime);
278
+ const entry = entries.get(key);
279
+ if (entry && now - entry.cachedAt <= SUMMARY_CACHE_TTL_MS && entryMatchesMaxChars(entry, maxChars)) {
280
+ cache.hits += 1;
281
+ return entry.summary;
282
+ }
283
+ cache.misses += 1;
284
+ return null;
285
+ },
286
+ has(requestId, lastFeedBackDateTime, maxChars) {
287
+ const entry = entries.get(summaryCacheKey(requestId, lastFeedBackDateTime));
288
+ return entry !== undefined && entryMatchesMaxChars(entry, maxChars);
289
+ },
290
+ set(requestId, lastFeedBackDateTime, summary, maxChars) {
291
+ const text = String(summary ?? '').trim();
292
+ if (!requestId || !text)
293
+ return false;
294
+ entries.set(summaryCacheKey(requestId, lastFeedBackDateTime), {
295
+ summary: text,
296
+ cachedAt: Date.now(),
297
+ ...(maxChars === undefined ? {} : { maxChars }),
298
+ });
299
+ dirty = true;
300
+ cache.dirty = true;
301
+ cache.size = entries.size;
302
+ return true;
303
+ },
304
+ async save() {
305
+ if (!dirty && !pruneDue)
306
+ return file;
307
+ const payload = {
308
+ version: SUMMARY_CACHE_VERSION,
309
+ prunedAt: Date.now(),
310
+ entries: Object.fromEntries(entries),
311
+ };
312
+ const written = await writeCacheFile(roots, SUMMARY_CACHE_FILE_NAME, JSON.stringify(payload));
313
+ if (written) {
314
+ file = written;
315
+ cache.file = written;
316
+ dirty = false;
317
+ cache.dirty = false;
318
+ }
319
+ return written;
320
+ },
321
+ };
322
+ // 清理到期:立刻把过滤后的内容写回(哪怕本次没有新增条目)。
323
+ if (pruneDue)
324
+ await cache.save();
325
+ return cache;
326
+ }
@@ -0,0 +1,113 @@
1
+ import { cacheRoots, readCacheFile, writeCacheFile, SUMMARY_CACHE_PRUNE_INTERVAL_MS, SUMMARY_CACHE_TTL_MS, } from './summaryCache.js';
2
+ /**
3
+ * **流程详情数据缓存**(与摘要缓存并存,互不替代)。
4
+ *
5
+ * 规则(用户 2026-10-26 定稿):
6
+ * - 键:`requestId__lastFeedBackDateTime`(与该条待办的业务时间绑定——流程一动就换键);
7
+ * - 有效期:7 天,与摘要缓存一致;读的时候逻辑过期,超过清理间隔(3 天)时物理剔除;
8
+ * - **不设版本号**:过期条目逐条剔除、新条目直接加入,不做整份失效;
9
+ * - 详情为空的条目**也写**(`contextAvailable:false`),否则这类条目每次都要重拉;
10
+ * - 缓存文件缺失 ⇒ 视为从未缓存过,正常走接口。
11
+ *
12
+ * 位置:与摘要缓存同一个 `cache/` 目录(skill 目录优先,不可写时退回 CLI 状态目录)。
13
+ */
14
+ export const DETAIL_CACHE_FILE_NAME = 'todo-detail-cache.json';
15
+ /** 与摘要缓存一致:7 天。 */
16
+ export const DETAIL_CACHE_TTL_MS = SUMMARY_CACHE_TTL_MS;
17
+ /** 与摘要缓存一致:清理间隔 3 天。 */
18
+ export const DETAIL_CACHE_PRUNE_INTERVAL_MS = SUMMARY_CACHE_PRUNE_INTERVAL_MS;
19
+ export function detailCacheKey(requestId, lastFeedBackDateTime) {
20
+ const stamp = String(lastFeedBackDateTime ?? '').trim();
21
+ return `${requestId}__${stamp}`;
22
+ }
23
+ function normalizeEntry(raw) {
24
+ if (!raw || typeof raw !== 'object')
25
+ return null;
26
+ const value = raw;
27
+ if (typeof value.cachedAt !== 'number' || !Number.isFinite(value.cachedAt))
28
+ return null;
29
+ if (typeof value.context !== 'string')
30
+ return null;
31
+ const fields = Array.isArray(value.fields)
32
+ ? value.fields
33
+ .filter((item) => Boolean(item) && typeof item.fieldName === 'string'
34
+ && typeof item.value === 'string')
35
+ .map((item) => ({ fieldName: item.fieldName, value: item.value }))
36
+ : [];
37
+ const metrics = value.metrics && typeof value.metrics === 'object' ? value.metrics : {};
38
+ return {
39
+ context: value.context,
40
+ contextAvailable: value.contextAvailable === true,
41
+ metrics,
42
+ fields,
43
+ cachedAt: value.cachedAt,
44
+ };
45
+ }
46
+ export async function openDetailCache(ctx) {
47
+ const roots = cacheRoots(ctx);
48
+ const stored = await readCacheFile(roots, DETAIL_CACHE_FILE_NAME);
49
+ const now = Date.now();
50
+ const entries = new Map();
51
+ let prunedAt = 0;
52
+ let loaded = 0;
53
+ let pruned = 0;
54
+ // 文件不存在 = 首次运行:按空缓存继续,不追查原因、不阻塞主流程。
55
+ if (stored) {
56
+ try {
57
+ const parsed = JSON.parse(stored.text);
58
+ prunedAt = typeof parsed.prunedAt === 'number' ? parsed.prunedAt : 0;
59
+ for (const [key, raw] of Object.entries(parsed.entries ?? {})) {
60
+ const entry = normalizeEntry(raw);
61
+ if (!entry)
62
+ continue;
63
+ // 逻辑过期:超 7 天直接当不存在(并在本轮清理时物理剔除)
64
+ if (now - entry.cachedAt > DETAIL_CACHE_TTL_MS) {
65
+ pruned += 1;
66
+ continue;
67
+ }
68
+ entries.set(key, entry);
69
+ loaded += 1;
70
+ }
71
+ }
72
+ catch {
73
+ // 坏文件等同没有缓存:下一次写入会覆盖
74
+ }
75
+ }
76
+ const cache = {
77
+ file: stored?.file ?? null,
78
+ pruned: 0,
79
+ loaded,
80
+ get(requestId, lastFeedBackDateTime) {
81
+ const entry = entries.get(detailCacheKey(requestId, lastFeedBackDateTime));
82
+ if (!entry)
83
+ return null;
84
+ if (Date.now() - entry.cachedAt > DETAIL_CACHE_TTL_MS)
85
+ return null;
86
+ return entry;
87
+ },
88
+ has(requestId, lastFeedBackDateTime) {
89
+ return cache.get(requestId, lastFeedBackDateTime) !== null;
90
+ },
91
+ set(requestId, lastFeedBackDateTime, entry) {
92
+ if (!requestId)
93
+ return false;
94
+ entries.set(detailCacheKey(requestId, lastFeedBackDateTime), { ...entry, cachedAt: Date.now() });
95
+ return true;
96
+ },
97
+ async save() {
98
+ // 物理清理:距上次清理超过 3 天才真正重写,避免每次打开都写盘。
99
+ const shouldPrune = pruned > 0 && Date.now() - prunedAt > DETAIL_CACHE_PRUNE_INTERVAL_MS;
100
+ const payload = {
101
+ prunedAt: shouldPrune ? Date.now() : prunedAt,
102
+ entries: Object.fromEntries(entries),
103
+ };
104
+ const file = await writeCacheFile(roots, DETAIL_CACHE_FILE_NAME, JSON.stringify(payload));
105
+ if (file)
106
+ cache.file = file;
107
+ if (shouldPrune)
108
+ cache.pruned = pruned;
109
+ return file;
110
+ },
111
+ };
112
+ return cache;
113
+ }