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,418 @@
1
+ /**
2
+ * 会议 / 议题附件的「读取」链路(列表 → 下载 → 抽文本)。
3
+ *
4
+ * 背景(2026-10-28 用户提出「小E 没有获取附件和文档内容的能力,你有办法吗」):
5
+ * 此前 skill 里写着「附件回读只有文件名、拿不到 fileId ⇒ 无法下载」。**这个结论是错的**:
6
+ * 真机直连 `query_mtDetail_info` 看到的是 `mt_accessorys = "1331337310852210690"` —— 就是 **fileId**。
7
+ * 拿到 fileId 之后,用 E10 登录态 GET
8
+ * `/api/file/remotedownload/{fileId}/meeting/true` 就能取回原始字节(实测返回文件内容,不是 401/跳登录)。
9
+ *
10
+ * 于是三个 operation:
11
+ * - `meeting.attachment.list` 会议(含项目文件)/ 议题资料的附件清单(fileId + 归属字段)
12
+ * - `meeting.attachment.download` 按 fileId 或按会议批量落地到本地
13
+ * - `meeting.attachment.extract` 下载并抽文本(txt/md/csv/json/docx/xlsx),给 AI 读内容用
14
+ *
15
+ * 边界:只做「取内容 + 抽纯文本」。PDF/图片的解析不在会议线内(PDF 走 workbuddy-pdf 技能,图片走视觉模型),
16
+ * 这里只负责把文件取下来,不假装能读懂所有格式。
17
+ */
18
+ import { mkdir, writeFile } from 'node:fs/promises';
19
+ import { join, resolve } from 'node:path';
20
+ import { meetingValidationError } from '../errors.js';
21
+ import { meetingStateDir } from './env.js';
22
+ import { meetingSuccess, schemaOf, SCHEMA_ID } from './types.js';
23
+ import { readDocxEntries } from '../invitation/docx.js';
24
+ /** 会议主表里存放 fileId 列表的字段(值形如 "id1,id2" 或 JSON 数组)。 */
25
+ const MEETING_FILE_FIELDS = ['mt_accessorys', 'project_files'];
26
+ const FIELD_LABELS = {
27
+ mt_accessorys: '会议附件',
28
+ project_files: '项目文件',
29
+ related_materials: '议题资料',
30
+ };
31
+ const TEXT_EXTENSIONS = new Set(['.txt', '.md', '.csv', '.json', '.xml', '.html', '.htm', '.log', '.yaml', '.yml', '.tsv']);
32
+ const OFFICE_EXTENSIONS = new Set(['.docx', '.xlsx', '.pptx']);
33
+ /** 雪花 id 形态(15 位以上纯数字)。服务端偶尔会把附件字段返回成**文件名**,必须能识别。 */
34
+ export function looksLikeFileId(value) {
35
+ return /^\d{15,}$/.test(String(value ?? '').trim());
36
+ }
37
+ /** 把字段值解析成 id 列表(兼容 CSV、JSON 数组字符串、对象数组)。 */
38
+ export function parseFileIdList(value) {
39
+ if (value === undefined || value === null)
40
+ return [];
41
+ if (Array.isArray(value)) {
42
+ return value
43
+ .map((item) => (item && typeof item === 'object' ? String(item.id ?? '') : String(item ?? '')))
44
+ .map((item) => item.trim())
45
+ .filter((item) => item !== '');
46
+ }
47
+ const text = String(value).trim();
48
+ if (text === '')
49
+ return [];
50
+ if (text.startsWith('[')) {
51
+ try {
52
+ return parseFileIdList(JSON.parse(text));
53
+ }
54
+ catch {
55
+ // 不是合法 JSON,按 CSV 继续
56
+ }
57
+ }
58
+ return text
59
+ .split(/[,;,;\s]+/)
60
+ .map((item) => item.trim())
61
+ .filter((item) => item !== '' && item !== '[]');
62
+ }
63
+ /** 直接读 EB 详情原始响应里的主表(不经过 CLI 的摘要层,避免又出现"被裁剪"的坑)。 */
64
+ async function readRawMainTable(env, interfacePk, id, operation) {
65
+ const session = await env.host.getSession();
66
+ const response = await env.host.request({
67
+ method: 'POST',
68
+ path: `/api/ebuilder/form/formdata/v2/getFormDataByPk/${interfacePk}`,
69
+ operation,
70
+ body: { datajson: { mainTable: { id }, header: { operator: session.userId } } },
71
+ acceptBusiness: (value) => {
72
+ if (!value || typeof value !== 'object')
73
+ return false;
74
+ if (typeof value.status === 'boolean')
75
+ return value.status;
76
+ return Number(value.code ?? 200) === 200;
77
+ },
78
+ });
79
+ const dj = response?.datajson ?? response?.data ?? {};
80
+ const rows = dj?.datas ?? dj?.data ?? (Array.isArray(dj) ? dj : []);
81
+ const first = Array.isArray(rows) ? rows[0] : rows;
82
+ return first?.mainTable ?? first ?? {};
83
+ }
84
+ /** 会议附件清单(会议主表的 mt_accessorys / project_files)。 */
85
+ export async function listMeetingAttachments(env, mtId) {
86
+ const main = await readRawMainTable(env, 'query_mtDetail_info', mtId, 'meeting.attachment.list');
87
+ const refs = [];
88
+ for (const field of MEETING_FILE_FIELDS) {
89
+ for (const value of parseFileIdList(main[field])) {
90
+ refs.push({
91
+ fileId: value,
92
+ idLike: looksLikeFileId(value),
93
+ source: field,
94
+ sourceLabel: FIELD_LABELS[field] ?? field,
95
+ ownerId: mtId,
96
+ });
97
+ }
98
+ }
99
+ return {
100
+ refs,
101
+ raw: Object.fromEntries(MEETING_FILE_FIELDS.map((field) => [field, main[field] ?? ''])),
102
+ };
103
+ }
104
+ /** 议题资料清单(议题主表的 related_materials)。 */
105
+ export async function listTopicAttachments(env, topicId) {
106
+ const main = await readRawMainTable(env, 'query_mt_topic_info_new', topicId, 'meeting.attachment.list');
107
+ return parseFileIdList(main.related_materials).map((value) => ({
108
+ fileId: value,
109
+ idLike: looksLikeFileId(value),
110
+ source: 'related_materials',
111
+ sourceLabel: FIELD_LABELS.related_materials,
112
+ ownerId: topicId,
113
+ }));
114
+ }
115
+ function extFromContentType(contentType) {
116
+ const text = String(contentType ?? '').toLowerCase();
117
+ if (text.includes('wordprocessingml'))
118
+ return '.docx';
119
+ if (text.includes('spreadsheetml'))
120
+ return '.xlsx';
121
+ if (text.includes('presentationml'))
122
+ return '.pptx';
123
+ if (text.includes('pdf'))
124
+ return '.pdf';
125
+ if (text.includes('json'))
126
+ return '.json';
127
+ if (text.includes('html'))
128
+ return '.html';
129
+ if (text.includes('plain'))
130
+ return '.txt';
131
+ return '';
132
+ }
133
+ function nameFromDisposition(disposition) {
134
+ const text = String(disposition ?? '');
135
+ const encoded = /filename\*=UTF-8''([^;]+)/i.exec(text);
136
+ if (encoded) {
137
+ try {
138
+ return decodeURIComponent(encoded[1]).trim();
139
+ }
140
+ catch {
141
+ // 落到下一个分支
142
+ }
143
+ }
144
+ const plain = /filename="?([^";]+)"?/i.exec(text);
145
+ return plain ? plain[1].trim() : '';
146
+ }
147
+ /**
148
+ * 按 fileId 下载到本地。
149
+ *
150
+ * URL 用 `remotedownload/{fileId}/meeting/true`:真机实测 200 且返回原始字节;
151
+ * 不带 `meeting/true` 的 `/api/file/remotedownload/{fileId}` 是 404(别再试)。
152
+ */
153
+ export async function downloadMeetingFile(env, fileId, options = {}) {
154
+ if (!env.host.download) {
155
+ throw meetingValidationError('download_unsupported', '当前 host 不支持二进制下载(测试替身?),无法获取附件内容');
156
+ }
157
+ const response = await env.host.download({
158
+ path: `/api/file/remotedownload/${encodeURIComponent(fileId)}/meeting/true`,
159
+ });
160
+ const dispositionName = nameFromDisposition(response.contentDisposition ?? '');
161
+ const ext = extFromContentType(response.contentType);
162
+ const baseName = (options.fileName ?? dispositionName) || `${fileId}${ext}`;
163
+ const safeName = baseName.replace(/[\\/:*?"<>|]/g, '_');
164
+ const dir = options.outDir
165
+ ? resolve(options.outDir)
166
+ : join(meetingStateDir(env.ctx), 'attachments');
167
+ await mkdir(dir, { recursive: true });
168
+ const target = join(dir, safeName);
169
+ await writeFile(target, response.buffer);
170
+ return { path: target, bytes: response.buffer.length, contentType: response.contentType };
171
+ }
172
+ function decodeText(buffer) {
173
+ const utf8 = buffer.toString('utf8');
174
+ // 中文老文件常见 GBK:UTF-8 解出大量替换字符时退回 gb18030
175
+ if (utf8.includes('\uFFFD')) {
176
+ try {
177
+ return new TextDecoder('gb18030').decode(buffer);
178
+ }
179
+ catch {
180
+ return utf8;
181
+ }
182
+ }
183
+ return utf8;
184
+ }
185
+ function stripXml(xml) {
186
+ return xml
187
+ .replace(/<w:p[ >]/g, '\n<w:p ')
188
+ .replace(/<\/w:p>/g, '\n')
189
+ .replace(/<[^>]+>/g, '')
190
+ .replace(/&amp;/g, '&')
191
+ .replace(/&lt;/g, '<')
192
+ .replace(/&gt;/g, '>')
193
+ .replace(/&quot;/g, '"')
194
+ .replace(/&#39;/g, "'")
195
+ .replace(/\n{2,}/g, '\n')
196
+ .trim();
197
+ }
198
+ /** xlsx → TSV:读 sharedStrings + 第一张表,够 AI 读表结构。 */
199
+ function xlsxToText(buffer) {
200
+ const entries = readDocxEntries(buffer);
201
+ const find = (suffix) => {
202
+ const entry = entries.find((item) => item.name === suffix);
203
+ return entry ? Buffer.from(entry.data).toString('utf8') : '';
204
+ };
205
+ const sharedXml = find('xl/sharedStrings.xml');
206
+ const shared = [...sharedXml.matchAll(/<si>([\s\S]*?)<\/si>/g)].map((match) => stripXml(match[1]).replace(/\n/g, ' '));
207
+ const sheetPaths = entries.map((item) => item.name).filter((path) => /^xl\/worksheets\/sheet\d+\.xml$/.test(path)).sort();
208
+ const first = sheetPaths[0];
209
+ if (!first)
210
+ return '';
211
+ const sheetXml = find(first);
212
+ const lines = [];
213
+ for (const rowMatch of sheetXml.matchAll(/<row[^>]*>([\s\S]*?)<\/row>/g)) {
214
+ const cells = [];
215
+ for (const cellMatch of rowMatch[1].matchAll(/<c\b([^>]*)>([\s\S]*?)<\/c>/g)) {
216
+ // ⚠️ 类型属性必须从**开标签**里单独取:把 t="s" 塞进一个可选分组 + 非贪婪匹配时,
217
+ // 惰性量词会先把 `[^>]*?` 收成空串、可选分组整体跳过,结果所有单元格都当数字读(单测抓到过)。
218
+ const attrs = cellMatch[1] ?? '';
219
+ const type = /\bt="([^"]+)"/.exec(attrs)?.[1] ?? '';
220
+ const inner = cellMatch[2] ?? '';
221
+ const value = /<v>([\s\S]*?)<\/v>/.exec(inner)?.[1] ?? '';
222
+ const inline = /<is>([\s\S]*?)<\/is>/.exec(inner)?.[1] ?? '';
223
+ if (type === 's')
224
+ cells.push(shared[Number(value)] ?? '');
225
+ else if (type === 'inlineStr')
226
+ cells.push(stripXml(inline));
227
+ else
228
+ cells.push(stripXml(value));
229
+ }
230
+ if (cells.some((cell) => cell !== ''))
231
+ lines.push(cells.join('\t'));
232
+ }
233
+ return lines.join('\n');
234
+ }
235
+ function pptxToText(buffer) {
236
+ const entries = readDocxEntries(buffer);
237
+ return entries
238
+ .filter((item) => /^ppt\/slides\/slide\d+\.xml$/.test(item.name))
239
+ .sort((left, right) => left.name.localeCompare(right.name, undefined, { numeric: true }))
240
+ .map((item, index) => `# 第 ${index + 1} 页\n${stripXml(Buffer.from(item.data).toString('utf8'))}`)
241
+ .join('\n\n');
242
+ }
243
+ /** 从下载到的文件里抽纯文本(不支持的格式明确返回 unsupported,不编内容)。 */
244
+ export function extractText(buffer, fileName, contentType = '') {
245
+ const lower = String(fileName ?? '').toLowerCase();
246
+ const ext = /\.[a-z0-9]+$/.exec(lower)?.[0] ?? '';
247
+ if (ext === '.docx' || contentType.includes('wordprocessingml')) {
248
+ const entries = readDocxEntries(buffer);
249
+ const document = entries.find((item) => item.name === 'word/document.xml');
250
+ return { text: document ? stripXml(Buffer.from(document.data).toString('utf8')) : '', kind: 'docx' };
251
+ }
252
+ if (ext === '.xlsx' || contentType.includes('spreadsheetml')) {
253
+ return { text: xlsxToText(buffer), kind: 'xlsx' };
254
+ }
255
+ if (ext === '.pptx' || contentType.includes('presentationml')) {
256
+ return { text: pptxToText(buffer), kind: 'pptx' };
257
+ }
258
+ if (TEXT_EXTENSIONS.has(ext) || contentType.startsWith('text/') || contentType.includes('json') || contentType.includes('xml')) {
259
+ return { text: decodeText(buffer), kind: 'text' };
260
+ }
261
+ return { text: '', kind: 'unsupported' };
262
+ }
263
+ /** 附件清单(按会议 / 按议题)。 */
264
+ const attachmentList = {
265
+ name: 'meeting.attachment.list',
266
+ resource: 'attachment',
267
+ method: 'list',
268
+ action: 'meeting.attachment.list',
269
+ summary: '查询会议/议题的附件清单(返回 fileId):会议附件 mt_accessorys、项目文件 project_files、议题资料 related_materials;直接读 EB 详情原始值,不经摘要层',
270
+ capability: 'attachment',
271
+ risk: 'read',
272
+ inputSchema: schemaOf({
273
+ mtId: { ...SCHEMA_ID, description: '会议 id(查会议附件时必填,与 topicId 二选一)' },
274
+ topicId: { ...SCHEMA_ID, description: '议题 id(查议题资料时用)' },
275
+ }, { required: [] }),
276
+ handler: async (args, env) => {
277
+ const mtId = String(args.mtId ?? '').trim();
278
+ const topicId = String(args.topicId ?? '').trim();
279
+ if (!mtId && !topicId) {
280
+ throw meetingValidationError('id_required', '必须提供 mtId(会议)或 topicId(议题)');
281
+ }
282
+ const refs = [];
283
+ let raw = {};
284
+ if (mtId) {
285
+ const result = await listMeetingAttachments(env, mtId);
286
+ refs.push(...result.refs);
287
+ raw = result.raw;
288
+ }
289
+ if (topicId)
290
+ refs.push(...(await listTopicAttachments(env, topicId)));
291
+ return meetingSuccess('meeting.attachment.list', {
292
+ mtId: mtId || null,
293
+ topicId: topicId || null,
294
+ count: refs.length,
295
+ items: refs,
296
+ raw,
297
+ }, refs.length === 0 ? { warnings: ['该会议/议题当前没有附件(或字段值为空)'] } : {});
298
+ },
299
+ };
300
+ /** 下载附件到本地。 */
301
+ const attachmentDownload = {
302
+ name: 'meeting.attachment.download',
303
+ resource: 'attachment',
304
+ method: 'download',
305
+ action: 'meeting.attachment.download',
306
+ summary: '按 fileId 下载附件到本地(默认 ~/.weaver-work-cli/meeting/attachments/);也可传 mtId 一次下载该会议全部附件',
307
+ capability: 'attachment',
308
+ risk: 'read',
309
+ inputSchema: schemaOf({
310
+ fileId: { ...SCHEMA_ID, description: '文件 id(与 mtId 二选一)' },
311
+ mtId: { ...SCHEMA_ID, description: '会议 id:一次下载该会议全部附件与项目文件' },
312
+ outDir: { type: 'string', description: '落地目录(默认 <state>/meeting/attachments)' },
313
+ fileName: { type: 'string', description: '指定文件名(仅单文件时生效)' },
314
+ }, { required: [] }),
315
+ handler: async (args, env) => {
316
+ const fileId = String(args.fileId ?? '').trim();
317
+ const mtId = String(args.mtId ?? '').trim();
318
+ if (!fileId && !mtId) {
319
+ throw meetingValidationError('id_required', '必须提供 fileId 或 mtId');
320
+ }
321
+ const outDir = typeof args.outDir === 'string' && args.outDir.trim() !== '' ? String(args.outDir) : undefined;
322
+ if (fileId) {
323
+ const one = await downloadMeetingFile(env, fileId, {
324
+ ...(outDir ? { outDir } : {}),
325
+ ...(typeof args.fileName === 'string' ? { fileName: String(args.fileName) } : {}),
326
+ });
327
+ return meetingSuccess('meeting.attachment.download', { downloaded: [{ fileId, ...one }], count: 1 });
328
+ }
329
+ const refs = (await listMeetingAttachments(env, mtId)).refs;
330
+ if (refs.length === 0) {
331
+ throw meetingValidationError('attachment_empty', `会议 ${mtId} 没有附件可下载`);
332
+ }
333
+ const downloaded = [];
334
+ const warnings = [];
335
+ for (const ref of refs) {
336
+ try {
337
+ const one = await downloadMeetingFile(env, ref.fileId, { ...(outDir ? { outDir } : {}) });
338
+ downloaded.push({ fileId: ref.fileId, source: ref.source, ...one });
339
+ }
340
+ catch (error) {
341
+ warnings.push(`附件 ${ref.fileId}(${ref.sourceLabel})下载失败:${String(error?.message ?? error).slice(0, 120)}`);
342
+ }
343
+ }
344
+ return meetingSuccess('meeting.attachment.download', { mtId, downloaded, count: downloaded.length }, warnings.length > 0 ? { warnings } : {});
345
+ },
346
+ };
347
+ /** 下载 + 抽文本(给 AI 读内容)。 */
348
+ const attachmentExtract = {
349
+ name: 'meeting.attachment.extract',
350
+ resource: 'attachment',
351
+ method: 'extract',
352
+ action: 'meeting.attachment.extract',
353
+ summary: '下载附件并抽取纯文本(txt/md/csv/json/docx/xlsx/pptx),返回正文与 txt 落地路径;pdf/图片等不支持格式会明确标注,不编造内容',
354
+ capability: 'attachment',
355
+ risk: 'read',
356
+ inputSchema: schemaOf({
357
+ fileId: { ...SCHEMA_ID, description: '文件 id(与 mtId 二选一)' },
358
+ mtId: { ...SCHEMA_ID, description: '会议 id:抽取该会议全部附件(最多 10 个)' },
359
+ maxChars: { type: 'number', description: '每个文件返回正文的最大字符数(默认 4000,最多 20000)' },
360
+ outDir: { type: 'string', description: '落地目录(默认 <state>/meeting/attachments)' },
361
+ }, { required: [] }),
362
+ handler: async (args, env) => {
363
+ const fileId = String(args.fileId ?? '').trim();
364
+ const mtId = String(args.mtId ?? '').trim();
365
+ if (!fileId && !mtId) {
366
+ throw meetingValidationError('id_required', '必须提供 fileId 或 mtId');
367
+ }
368
+ const maxChars = Math.max(200, Math.min(20_000, Number(args.maxChars ?? 4000) || 4000));
369
+ const outDir = typeof args.outDir === 'string' && args.outDir.trim() !== '' ? String(args.outDir) : undefined;
370
+ const refs = fileId
371
+ ? [{ fileId, idLike: looksLikeFileId(fileId), source: 'explicit', sourceLabel: '指定文件' }]
372
+ : (await listMeetingAttachments(env, mtId)).refs.slice(0, 10);
373
+ if (refs.length === 0) {
374
+ throw meetingValidationError('attachment_empty', `会议 ${mtId} 没有附件可抽取`);
375
+ }
376
+ const files = [];
377
+ const warnings = [];
378
+ for (const ref of refs) {
379
+ try {
380
+ const downloaded = await downloadMeetingFile(env, ref.fileId, { ...(outDir ? { outDir } : {}) });
381
+ const { readFile } = await import('node:fs/promises');
382
+ const buffer = await readFile(downloaded.path);
383
+ const extracted = extractText(buffer, downloaded.path, downloaded.contentType);
384
+ const text = extracted.text.slice(0, maxChars);
385
+ let textPath;
386
+ const absolutePath = downloaded.path;
387
+ const isPlainText = /\.(txt|md|csv|json|xml|html?|log|ya?ml|tsv)$/i.test(absolutePath);
388
+ if (extracted.text && !isPlainText) {
389
+ // 原文本身就是纯文本时不另存(否则同名覆盖自己,纯属噪音)
390
+ textPath = `${absolutePath.replace(/\.[a-z0-9]+$/i, '')}.txt`;
391
+ await writeFile(textPath, extracted.text, 'utf8');
392
+ }
393
+ files.push({
394
+ fileId: ref.fileId,
395
+ source: ref.sourceLabel,
396
+ path: downloaded.path,
397
+ bytes: downloaded.bytes,
398
+ kind: extracted.kind,
399
+ chars: extracted.text.length,
400
+ ...(textPath ? { textPath } : {}),
401
+ text,
402
+ });
403
+ if (extracted.kind === 'unsupported') {
404
+ warnings.push(`${downloaded.path} 是 ${downloaded.contentType || '未知类型'},会议线不解析该格式(已下载,PDF 可用 pdf 技能、图片用视觉模型)`);
405
+ }
406
+ }
407
+ catch (error) {
408
+ warnings.push(`附件 ${ref.fileId} 处理失败:${String(error?.message ?? error).slice(0, 120)}`);
409
+ }
410
+ }
411
+ return meetingSuccess('meeting.attachment.extract', { mtId: mtId || null, count: files.length, files }, warnings.length > 0 ? { warnings } : {});
412
+ },
413
+ };
414
+ export const meetingAttachmentContentOperations = [
415
+ attachmentList,
416
+ attachmentDownload,
417
+ attachmentExtract,
418
+ ];