dsh-ppt-studio 1.1.2 → 1.1.5

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 (55) hide show
  1. package/README.md +38 -18
  2. package/agent-presets/ppt/agent.cordis.yml +22 -15
  3. package/cordis.patch.yml +19 -14
  4. package/docs/01-/351/234/200/346/261/202/344/270/216/347/233/256/346/240/207.md +1 -1
  5. package/docs/02-/346/212/200/346/234/257/346/212/245/345/221/212.md +5 -5
  6. package/docs/03-/346/233/264/346/226/260/346/227/245/345/277/227.md +127 -0
  7. package/docs/05-/350/277/255/344/273/243/346/265/201/347/250/213.md +34 -3
  8. package/docs/06-/350/257/204/345/256/241/344/270/216/346/265/213/350/257/225.md +5 -3
  9. package/examples/fx/fx-pro/preview/04_pages_04_chart.html +1 -1
  10. package/examples/fx/fx-pro/preview/deck.html +1 -1
  11. package/examples/fx/fx-pro/preview/measured.json +1 -1
  12. package/examples/fx/fx-pro.pptx +0 -0
  13. package/examples/fx/seed.pptx +0 -0
  14. package/examples/fx/single.pptx +0 -0
  15. package/examples/fx/spliced.pptx +0 -0
  16. package/examples/smoke/imported/deck.yaml +6 -6
  17. package/examples/smoke/imported/import-styles.json +1 -33
  18. package/examples/smoke/imported/loss-audit.json +99 -0
  19. package/examples/smoke/imported/pages/slide_03.yaml +5 -36
  20. package/examples/smoke/imported/preview-rt/03_pages_slide_03.html +1 -1
  21. package/examples/smoke/imported/preview-rt/deck.html +1 -1
  22. package/examples/smoke/imported/preview-rt/layout.json +26 -78
  23. package/examples/smoke/imported/reference/previews/03.png +0 -0
  24. package/examples/smoke/imported/source.pptx +0 -0
  25. package/examples/smoke/out-smoke.pptx +0 -0
  26. package/examples/smoke/preview/03_pages_03_chart.html +1 -1
  27. package/examples/smoke/preview/deck.html +1 -1
  28. package/lib/capabilities.js +189 -0
  29. package/lib/home.js +32 -0
  30. package/lib/index.js +278 -112
  31. package/lib/office-qa.js +111 -0
  32. package/lib/pptd/export-pptx.js +159 -10
  33. package/lib/pptd/import-pptx.js +12 -0
  34. package/lib/pptd/loss-audit.js +363 -0
  35. package/lib/pptd/nativeChart.js +454 -0
  36. package/lib/pptd/schema.js +48 -1
  37. package/lib/pptd/svgCharts.js +180 -37
  38. package/lib/pptxPy.js +96 -11
  39. package/lib/preset-delivery.js +107 -9
  40. package/lib/preview-server.js +7 -5
  41. package/lib/router.js +3 -2
  42. package/lib/state.js +8 -6
  43. package/lib/templates.js +4 -4
  44. package/lib/tools.js +81 -21
  45. package/package.json +2 -1
  46. package/scripts/audit-manual-facts.mjs +4 -2
  47. package/scripts/e2e-1.0.mjs +2 -2
  48. package/scripts/fixtures/workflow-baseline.json +20 -15
  49. package/scripts/install.mjs +13 -11
  50. package/scripts/smoke.mjs +390 -66
  51. package/scripts/verify-loss-audit.mjs +245 -0
  52. package/scripts/verify-native-charts.mjs +544 -0
  53. package/scripts/verify-preset-scope.mjs +593 -0
  54. package/skills/ppt-studio-data/SKILL.md +5 -7
  55. package/skills/ppt-studio-manual/SKILL.md +2 -2
@@ -0,0 +1,189 @@
1
+ /**
2
+ * 部署能力探测(2026-09-26,第二轮)——把"这台机器上有哪些增强通道可用"收敛到**一处**。
3
+ *
4
+ * 为什么需要它:官方三个 office 技能(`office-docx/pptx/xlsx`)与它们假定的运行时(捆绑 Python +
5
+ * LibreOffice kit)**只在桌面端具备**(`dsh-skill-office` 由 `dsh-desktop-host` 程序化挂载;
6
+ * `web`/`headless`/`acp` 的出厂组合不挂,npm 版也没有 Python 分发)。于是插件必须**先探测再说话**:
7
+ * · python-pptx 兜底引擎选解释器(优先捆绑 Python,而不是指望用户 PATH 上恰好有);
8
+ * · 兜底产物是否接官方 QA(`check_office.py` 结构自证 + LibreOffice 渲染抽检);
9
+ * · `ppt_state.capabilities` 如实报告"有/没有"——绝不指引模型调用不存在的工具或技能。
10
+ *
11
+ * **三套根要分清**(2026-09-26 宿主实测结论,别混用):
12
+ * ① 安装副本:`<dshHome>/dsh-runtimes/dsh-primary-runtime`(桌面端把 payload 复制到这里;
13
+ * `load_workspace_dependencies` 返回给模型的也是这一套);
14
+ * ② carrier:`<install>/resources/runtime/primary-runtime`(桌面应用自带的原件);
15
+ * ③ asar 解包:`<install>/resources/app.asar.unpacked/...`(原生件,如 LibreOffice kit)。
16
+ *
17
+ * 探测原则:**只做文件存在性检查**(廉价、无副作用、不 spawn);解释器的可用性由调用方
18
+ * 一次性验证(`pptxPy.js` 的 `findPython()`)。所有探测函数**每次调用求值**(能力可能在会话中途出现,
19
+ * 例如宿主装好 runtime 之后),并返回 `null` 而不是抛错。
20
+ */
21
+ import { existsSync } from 'node:fs'
22
+ import { spawnSync } from 'node:child_process'
23
+ import { dirname, join } from 'node:path'
24
+ import { dshHome } from './home.js'
25
+
26
+ /** 官方三技能的名字(用于在技能注册表里探测"托管端是否提供")。 */
27
+ export const OFFICE_SKILL_NAMES = ['office-docx', 'office-pptx', 'office-xlsx']
28
+
29
+ /** 是否 Windows(捆绑 Python/Kit 的路径形状只对 Windows 做过实测)。 */
30
+ function isWin() {
31
+ return process.platform === 'win32'
32
+ }
33
+
34
+ /**
35
+ * 从某个目录**逐级上溯**找桌面应用的 `resources` 目录(用 `app.asar` / `runtime/office-skills` 当锚点)。
36
+ * 为什么不只看 `dirname(process.execPath)`:桌面端里本插件可能跑在**子进程 node** 中
37
+ * (`resources/runtime/primary-runtime/dependencies/node/bin/node.exe`),那时 execPath 在深层,
38
+ * 必须一路上溯才能找到 `<install>/resources`。找不到返回 null(web/CLI 上就该是 null)。
39
+ */
40
+ export function resourcesFrom(startDir) {
41
+ // 显式覆盖缝(测试/非常规布局):`PPT_STUDIO_RESOURCES_DIR=<install>/resources`。
42
+ // 只用它来**定位**资源,不改变任何判定语义;指向不合法时忽略并继续正常上溯。
43
+ const override = process.env.PPT_STUDIO_RESOURCES_DIR
44
+ if (typeof override === 'string' && override.trim() !== '') {
45
+ const cand = join(override)
46
+ if (existsSync(join(cand, 'app.asar')) || existsSync(join(cand, 'app.asar.unpacked')) || existsSync(join(cand, 'runtime', 'office-skills'))) return cand
47
+ }
48
+ let dir = startDir
49
+ for (let i = 0; i < 8; i++) {
50
+ const cand = join(dir, 'resources')
51
+ if (existsSync(join(cand, 'app.asar')) || existsSync(join(cand, 'app.asar.unpacked')) || existsSync(join(cand, 'runtime', 'office-skills'))) {
52
+ return cand
53
+ }
54
+ const parent = dirname(dir)
55
+ if (parent === dir) break
56
+ dir = parent
57
+ }
58
+ return null
59
+ }
60
+
61
+ /** 桌面应用的 `resources` 目录(`process.execPath` 起逐级上溯;非桌面部署返回 null)。 */
62
+ export function resourcesDir() {
63
+ const exe = process.execPath || ''
64
+ if (!exe) return null
65
+ return resourcesFrom(dirname(exe))
66
+ }
67
+
68
+ /** 环境变量缝(SDK/容器部署用;桌面端不设这两个变量,走 carrier 推导)。 */
69
+ export function primaryRuntimeFromEnv() {
70
+ return process.env.DSH_PRIMARY_RUNTIME || process.env.DSH_BUNDLED_PRIMARY_RUNTIME || null
71
+ }
72
+
73
+ /** primary-runtime 的候选根(按"模型看到的 → 应用自带的"排序)。 */
74
+ export function primaryRuntimeRoots() {
75
+ const out = []
76
+ const env = primaryRuntimeFromEnv()
77
+ if (env) out.push({ root: env, source: 'env(DSH_PRIMARY_RUNTIME)' })
78
+ out.push({ root: join(dshHome(), 'dsh-runtimes', 'dsh-primary-runtime'), source: 'installed(dsh-runtimes)' })
79
+ const res = resourcesDir()
80
+ if (res) out.push({ root: join(res, 'runtime', 'primary-runtime'), source: 'carrier(resources/runtime)' })
81
+ return out
82
+ }
83
+
84
+ /** 捆绑 Python 的候选(按上表顺序;`python.exe` 是 Windows 布局,其他平台给出 posix 形状)。 */
85
+ export function pythonCandidates() {
86
+ const exe = isWin() ? 'python.exe' : join('bin', 'python3')
87
+ return primaryRuntimeRoots().map(({ root, source }) => ({
88
+ path: join(root, 'dependencies', 'python', exe),
89
+ node: join(root, 'dependencies', 'node', isWin() ? 'bin/node.exe' : 'bin/node'),
90
+ pnpm: join(root, 'dependencies', 'pnpm', isWin() ? 'bin/pnpm.mjs' : 'bin/pnpm.cjs'),
91
+ root,
92
+ source,
93
+ }))
94
+ }
95
+
96
+ /** 第一个真实存在的捆绑 Python(没有则 null)。 */
97
+ export function findBundledPython() {
98
+ for (const cand of pythonCandidates()) if (existsSync(cand.path)) return cand
99
+ return null
100
+ }
101
+
102
+ /** 捆绑 Node(桌面端给 LibreOffice kit 用;没有则 null)。 */
103
+ export function findBundledNode() {
104
+ for (const cand of pythonCandidates()) if (existsSync(cand.node)) return cand.node
105
+ return null
106
+ }
107
+
108
+ /** LibreOffice kit CLI(官方 office 技能渲染/转 PDF/重算公式用的那一个);探测不到返回 null。 */
109
+ export function findLibreofficeCli() {
110
+ const res = resourcesDir()
111
+ const cands = []
112
+ if (res) {
113
+ // 桌面端实测落点:app.asar.unpacked 下的 asar 内依赖
114
+ cands.push(join(res, 'app.asar.unpacked', 'dsh', 'node_modules', '@deepseek-ai', 'libreoffice-kit', 'lib', 'cli.js'))
115
+ cands.push(join(res, 'app.asar.unpacked', 'node_modules', '@deepseek-ai', 'libreoffice-kit', 'lib', 'cli.js'))
116
+ }
117
+ if (res) cands.push(join(res, 'runtime', 'node_modules', '@deepseek-ai', 'libreoffice-kit', 'lib', 'cli.js'))
118
+ return cands.find((p) => existsSync(p)) ?? null
119
+ }
120
+
121
+ /** 官方 office 技能的资产根与结构检查脚本(桌面端 `resources/runtime/office-skills`)。 */
122
+ export function findOfficeAssets() {
123
+ const res = resourcesDir()
124
+ if (!res) return null
125
+ const skillsDir = join(res, 'runtime', 'office-skills')
126
+ if (!existsSync(skillsDir)) return null
127
+ const checker = join(skillsDir, 'scripts', 'check_office.py')
128
+ return { skillsDir, checker: existsSync(checker) ? checker : null }
129
+ }
130
+
131
+ /**
132
+ * 任意可用的 Python(**只为跑官方 `check_office.py`**:它只用标准库,任何 python3 都行)。
133
+ * 顺序:捆绑 Python > PATH 上的 `python` / `py` / `python3`。探不到返回 null。
134
+ * 与 `pptxPy.findPython()` 的区别:那个要求 `import pptx`,这个不要求。
135
+ */
136
+ export function findAnyPython() {
137
+ const bundled = findBundledPython()
138
+ if (bundled !== null) return { path: bundled.path, source: `bundled:${bundled.source}` }
139
+ for (const cmd of ['python', 'py', 'python3']) {
140
+ try {
141
+ const r = spawnSync(cmd, ['-c', 'print(1)'], { encoding: 'utf8', timeout: 15000 })
142
+ if (r.status === 0 && String(r.stdout).includes('1')) return { path: cmd, source: 'path' }
143
+ } catch { /* try next */ }
144
+ }
145
+ return null
146
+ }
147
+
148
+ /**
149
+ * 汇总探测结果(**纯文件检查**,不 spawn、不抛)。`ppt_state.capabilities` 用它;
150
+ * 兜底引擎与 QA 通道用它决定"能不能走增强路径"。
151
+ */
152
+ export function detectCapabilities() {
153
+ const py = findBundledPython()
154
+ const node = findBundledNode()
155
+ const cli = findLibreofficeCli()
156
+ const office = findOfficeAssets()
157
+ return {
158
+ platform: process.platform,
159
+ bundledPython: py === null ? null : { path: py.path, source: py.source },
160
+ bundledNode: node,
161
+ libreofficeKit: cli === null ? null : { cli, node },
162
+ officeSkillsAssets: office === null ? null : { skillsDir: office.skillsDir, checker: office.checker },
163
+ primaryRuntime: primaryRuntimeRoots().map(({ root, source }) => ({ root, source, present: existsSync(root) })),
164
+ }
165
+ }
166
+
167
+ /**
168
+ * 技能注册表探测:托管的三个 office 技能在本会话里**是否真的可用**(模型侧能不能加载)。
169
+ * 与 `detectCapabilities()` 分开,因为这一条要问服务、是异步的。
170
+ */
171
+ export async function probeOfficeSkills(ctx) {
172
+ let skills
173
+ try { skills = ctx?.get?.('skills') } catch { skills = undefined }
174
+ if (skills === undefined) return { available: false, found: [], reason: 'skills 服务缺失(极简装配)' }
175
+ const found = []
176
+ for (const name of OFFICE_SKILL_NAMES) {
177
+ try {
178
+ const one = await skills.get(name)
179
+ if (one !== undefined && one !== null) found.push(name)
180
+ } catch { /* 单个技能查询失败不影响其余 */ }
181
+ }
182
+ return {
183
+ available: found.length === OFFICE_SKILL_NAMES.length,
184
+ found,
185
+ reason: found.length === OFFICE_SKILL_NAMES.length
186
+ ? '托管端提供(桌面端/SDK 才有)'
187
+ : '本部署未提供这三个技能(web/headless 出厂组合不挂 dsh-skill-office)',
188
+ }
189
+ }
package/lib/home.js ADDED
@@ -0,0 +1,32 @@
1
+ /**
2
+ * DSH 用户目录解析 —— 本插件**唯一**的实现。
3
+ *
4
+ * 为什么要有这个文件(2026-09-26,方案 B 第二批):
5
+ * 此前口径是分裂的 ——
6
+ * · `templates.js`(用户模板层)与 `index.js`(诊断日志)**调用时求值**、认 `DSH_HOME`;
7
+ * · `state.js`(会话状态)与 `preview-server.js`(预览根)是**模块常量** `homedir()/.dsh`,忽略 `DSH_HOME`。
8
+ * 实测后果两条:
9
+ * ① 设了 `DSH_HOME` 的便携/CI 部署里,会话状态与预览**逃出隔离目录**(写进真实 `~/.dsh`);
10
+ * ② 本机跑 `npm test` 会把 smoke 模板与预览缓存写进开发机**真实**的 `~/.dsh/ppt-studio/` ——
11
+ * 与 smoke 自己那句"不碰开发机/CI 上真实目录"的承诺直接冲突(因此清理过两次真实目录)。
12
+ *
13
+ * 契约(两条都重要):
14
+ * · **每次调用求值**:不能做成模块常量,否则测试/CI 无法用 `DSH_HOME` 覆盖(这也是 templates.js 当初的做法)。
15
+ * · **未设 `DSH_HOME` 时与旧行为等价**(`homedir()/.dsh`)⇒ 普通用户行为零变化、无需迁移。
16
+ */
17
+ import { join } from 'node:path'
18
+ import { homedir } from 'node:os'
19
+
20
+ /** DSH home:`DSH_HOME` 优先,缺省 `~/.dsh`(Node 的 `homedir()` 在 Windows 认 `USERPROFILE`、POSIX 认 `HOME`)。 */
21
+ export function dshHome() {
22
+ return process.env.DSH_HOME || join(homedir(), '.dsh')
23
+ }
24
+
25
+ /**
26
+ * 本插件的用户数据目录:`<dshHome>/ppt-studio`。
27
+ * 会话状态(`session-<id>.json`)、预览根(`preview/<token>/`)、用户模板层(`templates/`)、
28
+ * 诊断日志(`debug.log`)都在这一层下面 —— 全插件共用同一个根,避免再出现口径分裂。
29
+ */
30
+ export function pptStudioDir() {
31
+ return join(dshHome(), 'ppt-studio')
32
+ }