dsh-ppt-studio 1.1.2 → 1.1.4
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 +38 -18
- package/agent-presets/ppt/agent.cordis.yml +22 -15
- package/cordis.patch.yml +19 -14
- package/docs/01-/351/234/200/346/261/202/344/270/216/347/233/256/346/240/207.md +1 -1
- package/docs/02-/346/212/200/346/234/257/346/212/245/345/221/212.md +5 -5
- package/docs/03-/346/233/264/346/226/260/346/227/245/345/277/227.md +105 -0
- package/docs/05-/350/277/255/344/273/243/346/265/201/347/250/213.md +34 -3
- package/docs/06-/350/257/204/345/256/241/344/270/216/346/265/213/350/257/225.md +5 -3
- package/examples/fx/fx-pro/preview/04_pages_04_chart.html +1 -1
- package/examples/fx/fx-pro/preview/deck.html +1 -1
- package/examples/fx/fx-pro/preview/measured.json +1 -1
- package/examples/fx/fx-pro.pptx +0 -0
- package/examples/fx/seed.pptx +0 -0
- package/examples/fx/single.pptx +0 -0
- package/examples/fx/spliced.pptx +0 -0
- package/examples/smoke/imported/deck.yaml +6 -6
- package/examples/smoke/imported/import-styles.json +1 -33
- package/examples/smoke/imported/loss-audit.json +99 -0
- package/examples/smoke/imported/pages/slide_03.yaml +5 -36
- package/examples/smoke/imported/preview-rt/03_pages_slide_03.html +1 -1
- package/examples/smoke/imported/preview-rt/deck.html +1 -1
- package/examples/smoke/imported/preview-rt/layout.json +26 -78
- package/examples/smoke/imported/reference/previews/03.png +0 -0
- package/examples/smoke/imported/source.pptx +0 -0
- package/examples/smoke/out-smoke.pptx +0 -0
- package/examples/smoke/preview/03_pages_03_chart.html +1 -1
- package/examples/smoke/preview/deck.html +1 -1
- package/lib/capabilities.js +189 -0
- package/lib/home.js +32 -0
- package/lib/index.js +278 -112
- package/lib/office-qa.js +111 -0
- package/lib/pptd/export-pptx.js +159 -10
- package/lib/pptd/import-pptx.js +12 -0
- package/lib/pptd/loss-audit.js +363 -0
- package/lib/pptd/nativeChart.js +454 -0
- package/lib/pptd/schema.js +48 -1
- package/lib/pptd/svgCharts.js +180 -37
- package/lib/pptxPy.js +96 -11
- package/lib/preset-delivery.js +107 -9
- package/lib/preview-server.js +7 -5
- package/lib/router.js +3 -2
- package/lib/state.js +8 -6
- package/lib/templates.js +4 -4
- package/lib/tools.js +81 -21
- package/package.json +2 -1
- package/scripts/audit-manual-facts.mjs +4 -2
- package/scripts/e2e-1.0.mjs +2 -2
- package/scripts/fixtures/workflow-baseline.json +10 -10
- package/scripts/install.mjs +13 -11
- package/scripts/smoke.mjs +390 -66
- package/scripts/verify-loss-audit.mjs +245 -0
- package/scripts/verify-native-charts.mjs +529 -0
- package/scripts/verify-preset-scope.mjs +593 -0
- package/skills/ppt-studio-data/SKILL.md +5 -7
- package/skills/ppt-studio-manual/SKILL.md +2 -2
package/lib/preview-server.js
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* PPT 预览服务(需求 A「对话内看 PPT」,v0.6.0):
|
|
3
3
|
* host 侧:webServer 注册 `/ppt-preview/` 前缀路由,静态服务于"预览根"。
|
|
4
|
-
* - 预览根 =
|
|
4
|
+
* - 预览根 = `<dshHome>/ppt-studio/preview/<token>/`:pages/(render 的 html)+ media/(deck 媒体)。
|
|
5
5
|
* render 输出的 html 引用 `../media/`(相对 deck 根)→ 预览根内 pages/../media/ 恰好解析。
|
|
6
|
+
* **路径每次调用求值**(认 `DSH_HOME`;见 src/home.js 的口径统一说明)。
|
|
6
7
|
* - URL 为同源相对路径(/ppt-preview/<token>/pages/deck.html):GUI 页面本身就在 host 上,
|
|
7
8
|
* 点击/iframe 均同源,不依赖端口探测。
|
|
8
9
|
*/
|
|
9
10
|
import { mkdir, rm, copyFile, cp, readFile, stat } from 'node:fs/promises'
|
|
10
11
|
import { existsSync } from 'node:fs'
|
|
11
12
|
import { join, dirname, extname, normalize } from 'node:path'
|
|
12
|
-
import { homedir } from 'node:os'
|
|
13
13
|
import { createHash } from 'node:crypto'
|
|
14
|
+
import { pptStudioDir } from './home.js'
|
|
14
15
|
import { resolveDeck } from './pptd/schema.js'
|
|
15
16
|
import { renderDeck } from './pptd/render-html.js'
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
/** 预览根(调用时求值)。 */
|
|
19
|
+
export function previewRoot() { return join(pptStudioDir(), 'preview') }
|
|
18
20
|
const tokens = new Map() // token -> previewRoot
|
|
19
21
|
|
|
20
22
|
const MIME = {
|
|
@@ -37,7 +39,7 @@ export async function buildPreview(dir) {
|
|
|
37
39
|
const ctx = await resolveDeck(dir)
|
|
38
40
|
const r = await renderDeck(ctx, {})
|
|
39
41
|
const token = createHash('md5').update(dir).digest('hex').slice(0, 10)
|
|
40
|
-
const root = join(
|
|
42
|
+
const root = join(previewRoot(), token)
|
|
41
43
|
await rm(root, { recursive: true, force: true })
|
|
42
44
|
await mkdir(join(root, 'pages'), { recursive: true })
|
|
43
45
|
for (const f of r.htmlFiles) {
|
|
@@ -71,7 +73,7 @@ export async function buildPreview(dir) {
|
|
|
71
73
|
/** token → 预览根(内存优先,miss 时从 .meta.json 恢复并回填)。 */
|
|
72
74
|
async function resolveToken(token) {
|
|
73
75
|
if (tokens.has(token)) return tokens.get(token)
|
|
74
|
-
const root = join(
|
|
76
|
+
const root = join(previewRoot(), token)
|
|
75
77
|
try {
|
|
76
78
|
const meta = JSON.parse(await readFile(join(root, '.meta.json'), 'utf8'))
|
|
77
79
|
if (meta?.sourceDir) {
|
package/lib/router.js
CHANGED
|
@@ -113,7 +113,7 @@ export function workflowSection(taskType, cfg) {
|
|
|
113
113
|
'4. 页数:≤8 页(用户指定除外);每页文本精简(每块 ≤40 字为默认参考,用户要求可放宽)。',
|
|
114
114
|
'5. 审阅:ppt_render + ppt_verify 必跑——内容互压/出界/文本溢出/预期外重叠(设计意图声明制)必须清零;出界分级同标准模式(超页面不可声明;安全区外有意元素逐项加 expectedOutOfSafeArea);autoDeclare 后附一句意图说明;跳过截图视觉审阅与美学迭代。',
|
|
115
115
|
'6. 交付:ppt_export(引擎 auto=pptd)→ 交付说明标注"快速模式:未经视觉审阅"。',
|
|
116
|
-
'7. 引擎:auto 默认 = pptd(自研主引擎,图表矢量拼绘);python-pptx 仅显式指定或 pptd
|
|
116
|
+
'7. 引擎:auto 默认 = pptd(自研主引擎,图表矢量拼绘);python-pptx 仅显式指定或 pptd 失败时使用(其图表为原生可编辑图表 + 内嵌数据工作簿)。不要自行切换引擎。',
|
|
117
117
|
'8. 用户明确要求始终优先;回到完整模式:/ppt normal。',
|
|
118
118
|
].join('\n'),
|
|
119
119
|
}
|
|
@@ -150,9 +150,10 @@ export function workflowSection(taskType, cfg) {
|
|
|
150
150
|
'7. 交付前:S4 全通过 + S6 导出 .pptx + 交付说明(素材来源、未视觉审阅项、修改指引)。**内容审阅(M3 重写 2026-09-18)**:有具体数字/事实主张的稿子,交付前用 `ppt_crosscheck` 生成「审阅材料包」(全页正文 + 素材清单 + 审阅协议)→ 再按协议做一次内容审阅:**默认用独立子代理**(全新上下文、只给材料包与素材文件;**用户明确禁止子代理时改自审并标注「自审(同上下文)」**),逐条判"一致 / 冲突 / 无来源支撑 / 无法核实",重点核对同一指标跨页值是否一致、数字是否有依据、结论是否强于数据;**没有外部素材时只能判"无法核实",不得报绿**。该工具**只汇总材料、不做判定、不构成门禁**(旧版 grounded/unmapped 状态与跨页数字分组已废除)。制作进程中**供用户看稿优先用 ppt_preview(dir)**(返回同源预览链接,用户点击即看,附在交付信息里);导出前/后均可预览。**修改既有精美 PPT 的某一页(edit/augment 高频场景)**:ppt_import 参考层读真身 → 改工作区页并审阅清零 → **`ppt_splice`**(只把该页替换进原稿:保留源母版横幅/页脚/备注关系/媒体,其余页条目 SHA256 逐字节一致——不要整册近似重渲伤及其他页)→ 交付整册副本;用户要"单页独立版"再 **`ppt_slice`**;抽查该页观感用 **`ppt_visual pages=N`**(按页真渲染,不必整册);"看起来就是模板原样"用 `ppt_patch`(手术贴模板)。',
|
|
151
151
|
'8. 版式系统(审美基线,用户风格优先):写页面之前先定版式规则——默认基线:8px 网格对齐、3-4 级字号阶梯、4-5 色主题(只在 $theme 内取色)、统一间距节奏(8/16/24px)、左右外边界一致、构图重心居中;**用户对风格有要求(字体/字号/配色/密度)一律优先,默认基线仅为未指定时的风格参考**。文字:短句化(每块 ≤60 字为默认参考,可依内容放宽),**文本最小字号以用户指令为准**(hardConstraints;未给下限不设强制),长句在语义断点显式 \\\\n 换行。',
|
|
152
152
|
'9. 审美建议(ppt_verify 输出的 [·] 建议)不是门禁,但请逐条斟酌采纳;overlap/out-of-page/text-overflow 错误仍必须清零(审美改进不得引入元素区块冲突)。',
|
|
153
|
-
'10.
|
|
153
|
+
'10. 引擎:**PPT 制作唯一通道 = pptd**(`deck.yaml` + `pages/*.yaml`;图表矢量拼绘)。pptd 硬失败时自动回退 python-pptx(报告醒目标注降级;其图表为**原生可编辑图表 + 内嵌数据工作簿**);python-pptx 仅用户显式指定时使用。**不要**用 python-pptx 或官方 `office-pptx` 技能从零拼版来绕过本工作流的门禁——它们只适合**读取**既有 pptx、或作为上面的兜底(能不能用由 `ppt_state` 的 `capabilities` 如实报告)。除非用户要求,不要自行切换引擎。',
|
|
154
154
|
'11. 质量档 audit(/ppt quality audit,从严门禁):禁 autoDeclare(工具已拦);每页必须 ppt_shot + 读图视觉审阅(无读图能力则降级结构 Lint 并在交付说明标注"未经视觉审阅");导出报告必须包含并核对 audit 回读断言(页数/尺寸/最小字号 ≥ minFontSize);有 Office 时导出后自动 **Office 真渲染**(成品视觉审核,逐页 read_image);交付说明含素材来源与数据脱敏/假设标注。',
|
|
155
155
|
'12. **真渲染抽检铁律(P1 事故教训)**:有 Office 时,真渲染抽查**必须覆盖所有含 table/chart/image/custGeom 的页**(每类至少一页,其余页轮换)——不要只挑"美观重点页";抽查后逐页 read_image(渲染图已带 1px=1pt 比例标注水印,降低坐标误判)。导出报告会给出建议覆盖页清单(P8 行)。',
|
|
156
|
+
'13. **改既有 PPT 的两条纪律**:① **就地手术优先**——任务若是"改某几页 / 改一处文字 / 换一张图",优先 `ppt_splice`(把工作区某一页替换进原稿,**其余页逐字节不变**;用户要"单页独立版"用 `ppt_slice`;要"看起来就是模板原样"用 `ppt_patch`),不要图省事整册重渲。`ppt_import` 会给出**页码/元素级**的"无法保真特性"清单(工程内 `loss-audit.json` + 一行结论),并按 ✓/⚠/ℹ 三档区分:✓ 干净、⚠ 有会丢的可见特性(动画/阴影/超链接/切换/SmartArt/EMF 等)、ℹ 仅主题样式表里的渐变模板未被引用(不影响观感);**⚠ 命中时"只改某几页"必须走 `ppt_splice`,并在交付说明里写明"整册重渲会丢 <类别 N 处>"**。② **Excel 素材的数字要么重算、要么标注**:从 xlsx 取数时公式的缓存值可能陈旧或缺失;探测到捆绑 LibreOffice kit 就用它的 `recalculate` 重算,做不到时必须在交付说明里写明"公式值未重算(可能陈旧)",别把旧缓存值当已核对的数据。',
|
|
156
157
|
].join('\n'),
|
|
157
158
|
}
|
|
158
159
|
}
|
package/lib/state.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ppt-studio 状态:会话级(路由/档位)+ 项目级(state.json)。
|
|
3
|
-
* 会话状态持久化到
|
|
3
|
+
* 会话状态持久化到 `<dshHome>/ppt-studio/session-<id>.json`,供 resume 恢复。
|
|
4
|
+
* **路径每次调用求值**(认 `DSH_HOME`)——见 src/home.js 顶部的口径统一说明;
|
|
5
|
+
* 此处原为模块常量 `homedir()/.dsh`,会让隔离 DSH_HOME 的测试/便携部署把状态写进真实家目录。
|
|
4
6
|
*/
|
|
5
7
|
import { readFile, writeFile, mkdir, access } from 'node:fs/promises'
|
|
6
8
|
import { existsSync } from 'node:fs'
|
|
7
9
|
import { join } from 'node:path'
|
|
8
|
-
import {
|
|
10
|
+
import { pptStudioDir } from './home.js'
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
export
|
|
12
|
+
/** 会话状态目录(调用时求值)。 */
|
|
13
|
+
export function sessionDir() { return pptStudioDir() }
|
|
12
14
|
|
|
13
15
|
export const DEFAULT_SESSION = () => ({
|
|
14
16
|
routing: 'auto', // auto | on | off(语义判断开关)
|
|
@@ -25,7 +27,7 @@ export const DEFAULT_SESSION = () => ({
|
|
|
25
27
|
deckDir: null,
|
|
26
28
|
})
|
|
27
29
|
|
|
28
|
-
const sessionPath = (sessionId) => join(
|
|
30
|
+
const sessionPath = (sessionId) => join(sessionDir(), `session-${sanitize(sessionId)}.json`)
|
|
29
31
|
|
|
30
32
|
export async function loadSession(sessionId) {
|
|
31
33
|
return (await loadSessionDiag(sessionId)).state
|
|
@@ -44,7 +46,7 @@ export async function loadSessionDiag(sessionId) {
|
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
export async function saveSession(sessionId, state) {
|
|
47
|
-
await mkdir(
|
|
49
|
+
await mkdir(sessionDir(), { recursive: true })
|
|
48
50
|
await writeFile(sessionPath(sessionId), JSON.stringify(state, null, 2))
|
|
49
51
|
}
|
|
50
52
|
|
package/lib/templates.js
CHANGED
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
import { readFile, readdir, rm } from 'node:fs/promises'
|
|
8
8
|
import { existsSync } from 'node:fs'
|
|
9
9
|
import { join, dirname, resolve, sep } from 'node:path'
|
|
10
|
-
import { homedir } from 'node:os'
|
|
11
10
|
import { fileURLToPath } from 'node:url'
|
|
12
11
|
import YAML from 'yaml'
|
|
12
|
+
import { pptStudioDir } from './home.js'
|
|
13
13
|
|
|
14
14
|
export const TEMPLATES_DIR = join(dirname(fileURLToPath(import.meta.url)), '..', 'templates')
|
|
15
15
|
|
|
@@ -22,11 +22,11 @@ export const TEMPLATES_DIR = join(dirname(fileURLToPath(import.meta.url)), '..',
|
|
|
22
22
|
* **用户自己攒的模板会被整目录覆盖掉**。用户层目录在 node_modules 之外,升级不碰。
|
|
23
23
|
*
|
|
24
24
|
* **每次调用求值**(故意不做成模块常量):测试/CI 可以把 `DSH_HOME` 指到临时目录来验证这一层,
|
|
25
|
-
* 而常量会在 import 时就定死、无法覆盖。
|
|
25
|
+
* 而常量会在 import 时就定死、无法覆盖。2026-09-26 起该口径由 `src/home.js` 统一提供
|
|
26
|
+
* (state/preview/diag 也都改成了调用时求值,不再各自实现)。
|
|
26
27
|
*/
|
|
27
28
|
export function userTemplatesDir() {
|
|
28
|
-
|
|
29
|
-
return join(home, 'ppt-studio', 'templates')
|
|
29
|
+
return join(pptStudioDir(), 'templates')
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
/** 按 id 找模板目录:**用户层优先**(同名时用户覆盖随包),再找随包层。找不到返回 null。 */
|
package/lib/tools.js
CHANGED
|
@@ -26,6 +26,8 @@ import { crosscheckReport } from './crosscheck.js'
|
|
|
26
26
|
import { buildPreview } from './preview-server.js'
|
|
27
27
|
import { findPowerPoint, renderPptxToPng } from './msrender.js'
|
|
28
28
|
import { runPythonExport, findPython } from './pptxPy.js'
|
|
29
|
+
import { detectCapabilities, probeOfficeSkills } from './capabilities.js'
|
|
30
|
+
import { runLibreOfficeRender, runOfficeStructureCheck } from './office-qa.js'
|
|
29
31
|
import { imageInfo } from './imgmeta.js'
|
|
30
32
|
import { loadProjectDiag, loadSessionDiag } from './state.js'
|
|
31
33
|
|
|
@@ -163,6 +165,42 @@ async function loadCtx(dir) {
|
|
|
163
165
|
return resolveDeck(dir)
|
|
164
166
|
}
|
|
165
167
|
|
|
168
|
+
/**
|
|
169
|
+
* 第三方结构自证(一行;2026-09-26 第二轮 ②):用官方 `check_office.py` 独立检查产物的 ZIP/XML/rels 完整性与页数。
|
|
170
|
+
* 为什么值得每次跑:我们自己的 parity 是"自己写、自己查",而这一条是**外部**检查——两者同时绿才算真绿。
|
|
171
|
+
* 本部署没有官方资产时**如实说"未执行"**(web/headless 就是这种情况),不假装跑过。
|
|
172
|
+
*/
|
|
173
|
+
function officialQaNote(file, slides) {
|
|
174
|
+
const qa = runOfficeStructureCheck(file, { count: slides ?? 0 })
|
|
175
|
+
if (!qa.available) return `\nℹ 第三方结构自证:未执行(${qa.reason})`
|
|
176
|
+
if (qa.ok) {
|
|
177
|
+
return `\n✅ 第三方结构自证(官方 check_office.py):ZIP/XML/rels 完整`
|
|
178
|
+
+ `${qa.summary?.slides !== undefined ? `、${qa.summary.slides} 页` : ''}——独立于我们自己的 parity 回读`
|
|
179
|
+
}
|
|
180
|
+
return `\n✗ 第三方结构自证失败:${qa.reason}——**请勿交付**,并把本行反馈插件团队`
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* 兜底路径的"环境说明"(2026-09-26,第二轮):**用了哪个解释器** + 本部署有没有更强的替代通道。
|
|
185
|
+
* 为什么写在这里而不是提示段:兜底是罕见路径,说明只该在真的降级时出现(能力自适应),
|
|
186
|
+
* 且必须如实——官方 `office-pptx` 技能在我们这里只当"读取/补充",不改变"pptd 才是主通道"。
|
|
187
|
+
*/
|
|
188
|
+
async function fallbackNote(ctx, r) {
|
|
189
|
+
const src = String(r?.interpreterSource ?? '')
|
|
190
|
+
const where = src.startsWith('bundled')
|
|
191
|
+
? `捆绑 Python(${r?.interpreter};自带 python-pptx/lxml/Pillow/XlsxWriter)`
|
|
192
|
+
: `PATH 解释器(${r?.interpreter ?? '?'})`
|
|
193
|
+
const notes = [`\n解释器:${where}`]
|
|
194
|
+
try {
|
|
195
|
+
const skills = await probeOfficeSkills(ctx)
|
|
196
|
+
if (skills.available) {
|
|
197
|
+
notes.push('本部署提供官方 `office-pptx` 技能(同一套捆绑 Python + LibreOffice kit 渲染 + `check_office.py` 结构自检):'
|
|
198
|
+
+ '需要更细的读取/修改或渲染核对时**加载它**。注意它不改变引擎优先级——pptd 才是主通道,本产物是降级结果。')
|
|
199
|
+
}
|
|
200
|
+
} catch { /* 探测失败不影响导出 */ }
|
|
201
|
+
return notes.join('\n')
|
|
202
|
+
}
|
|
203
|
+
|
|
166
204
|
export function registerTools(ctx) {
|
|
167
205
|
const reg = (tool) => ctx.effect(() => ctx.tools.register(defineTool(tool)), `ppt-studio: ${tool.name}`)
|
|
168
206
|
|
|
@@ -262,7 +300,7 @@ export function registerTools(ctx) {
|
|
|
262
300
|
|
|
263
301
|
reg({
|
|
264
302
|
name: 'ppt_visual',
|
|
265
|
-
description: 'Office 真渲染通道(v0.8.0
|
|
303
|
+
description: 'Office 真渲染通道(v0.8.0;2026-09-26 起带 LibreOffice 降级):优先用本机 PowerPoint COM 把 .pptx 逐页渲染成 PNG(1920×1080)——① 理解用户原稿(参考任务先看后做)② 成品视觉审核(导出后对 pptx 审核真实观感)。pages="15" / "15,18" 只渲染指定页(保持原页号命名;A5 反馈二)——看一页不再等整册。**本机无 Office 时自动降级到捆绑 LibreOffice kit**(桌面端有;报告里会标注引擎与缺字诊断,且明说"不能当逐像素等于 PowerPoint 的判据")——两条都没有时明确报告不可用。渲染后请逐页 read_image 查看;视觉理解写入设计摘要/交付说明',
|
|
266
304
|
parameters: {
|
|
267
305
|
pptx: { type: 'string', required: true, description: '源/成品 .pptx 绝对路径(用户原稿或 out.pptx)' },
|
|
268
306
|
out: { type: 'string', description: '输出目录(相对当前目录或绝对;缺省 <pptx 同目录>/..-rendered)' },
|
|
@@ -271,20 +309,42 @@ export function registerTools(ctx) {
|
|
|
271
309
|
output: markdownResult(),
|
|
272
310
|
async execute(args) {
|
|
273
311
|
try {
|
|
274
|
-
if (!findPowerPoint()) {
|
|
275
|
-
return '⚠ 本机无 Microsoft Office(PowerPoint COM 不可用):Office 真渲染通道不可用。可继续使用 HTML 预览(ppt_preview)+ 结构断言;本步骤不影响其他工作流。'
|
|
276
|
-
}
|
|
277
312
|
const pagesSel = typeof args.pages === 'string' ? parsePagesArg(args.pages) : null
|
|
278
313
|
const outDir = args.out ?? join(dirname(args.pptx), 'rendered')
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
314
|
+
if (findPowerPoint()) {
|
|
315
|
+
const r = await renderPptxToPng(args.pptx, outDir, { pages: pagesSel ? [...pagesSel] : undefined })
|
|
316
|
+
if (!r.pages) throw new Error('渲染结果为空(请检查 pptx 是否可打开)')
|
|
317
|
+
return [
|
|
318
|
+
`✓ Office 真渲染完成(引擎:PowerPoint COM,${r.pages} 页${pagesSel ? `,页码 [${[...pagesSel].sort((a, b) => a - b).join(', ')}]` : ''} → ${outDir}):`,
|
|
319
|
+
'',
|
|
320
|
+
...r.files.map((f) => ` - ${f}`),
|
|
321
|
+
'',
|
|
322
|
+
'下一步:逐页 read_image 查看(理解原稿/审核成品观感)。',
|
|
323
|
+
].join('\n')
|
|
324
|
+
}
|
|
325
|
+
// 无 Office ⇒ 试**捆绑 LibreOffice kit**(2026-09-26 第二轮 ②):不依赖 Office/COM 的真渲染。
|
|
326
|
+
// 用途:没有 Office 的机器(CI、纯 web 部署)也能做视觉抽检;但**引擎不同**,
|
|
327
|
+
// 排版/字体与 PowerPoint 不是一套 ⇒ 绝不能当"逐像素等于 PowerPoint"的判据(交付说明要标注)。
|
|
328
|
+
const lo = runLibreOfficeRender(args.pptx, outDir, { pages: pagesSel ? [...pagesSel].sort((a, b) => a - b).join(',') : '', dpi: 144 })
|
|
329
|
+
if (lo.available && lo.ok) {
|
|
330
|
+
return [
|
|
331
|
+
`✓ LibreOffice 真渲染完成(本机无 Office → 降级到捆绑 LibreOffice kit;${lo.files.length} 张 → ${outDir}):`,
|
|
332
|
+
'',
|
|
333
|
+
...(lo.images.length
|
|
334
|
+
? lo.images.map((i) => ` - 原第 ${i.page} 页 → ${i.path}(${i.width}×${i.height})`)
|
|
335
|
+
: lo.files.map((f) => ` - ${f}`)),
|
|
336
|
+
'',
|
|
337
|
+
'⚠ 引擎差异:LibreOffice 的排版/字体与 PowerPoint 不是同一套引擎,此结果**不能**当"逐像素等于 PowerPoint"的判据——请在交付说明里标注"视觉审阅基于 LibreOffice 渲染"。',
|
|
338
|
+
...(lo.missingFonts.length ? [`⚠ 缺字诊断 ${lo.missingFonts.length} 项:${JSON.stringify(lo.missingFonts).slice(0, 240)}`] : []),
|
|
339
|
+
'',
|
|
340
|
+
'下一步:逐页 read_image 查看。',
|
|
341
|
+
].join('\n')
|
|
342
|
+
}
|
|
343
|
+
if (!lo.available) {
|
|
344
|
+
return `⚠ 真渲染通道不可用:本机无 Microsoft Office,且本部署没有 LibreOffice kit(${lo.reason})。`
|
|
345
|
+
+ '可继续使用 HTML 预览(ppt_preview)+ 结构断言;本步骤不影响其他工作流。'
|
|
346
|
+
}
|
|
347
|
+
return `✗ 真渲染都失败了:无 Office;LibreOffice kit 也失败:${lo.reason}\n(可降级:ppt_preview HTML 预览 + ppt_verify 结构断言)`
|
|
288
348
|
} catch (error) {
|
|
289
349
|
return `✗ Office 真渲染失败:${error?.message ?? String(error)}\n(可降级:ppt_preview HTML 预览 + ppt_verify 结构断言)`
|
|
290
350
|
}
|
|
@@ -605,7 +665,7 @@ export function registerTools(ctx) {
|
|
|
605
665
|
|
|
606
666
|
reg({
|
|
607
667
|
name: 'ppt_export',
|
|
608
|
-
description: '导出 .pptx。engine 缺省 auto(=pptd 自研主引擎,图表矢量拼绘;hard 失败时自动回退 python-pptx 并醒目标注降级);python-pptx
|
|
668
|
+
description: '导出 .pptx。engine 缺省 auto(=pptd 自研主引擎,图表矢量拼绘;hard 失败时自动回退 python-pptx 并醒目标注降级);python-pptx 仅显式指定(其图表为原生可编辑图表 + 内嵌数据工作簿)。out 支持绝对路径(原样使用)或文件名(相对 deck 目录)。audit 质量档自动追加回读断言(页数/尺寸/最小字号)',
|
|
609
669
|
parameters: {
|
|
610
670
|
dir: dirSchema,
|
|
611
671
|
engine: { type: 'string', enum: ['auto', 'pptd', 'python-pptx'], description: '缺省 auto(=pptd);python-pptx 需 python 环境' },
|
|
@@ -647,9 +707,9 @@ export function registerTools(ctx) {
|
|
|
647
707
|
}
|
|
648
708
|
if (eff.engine === 'python-pptx' && !eff.allowFallback) {
|
|
649
709
|
const py = findPython()
|
|
650
|
-
if (!py.has) return `⚠ python-pptx
|
|
710
|
+
if (!py.has) return `⚠ python-pptx 引擎不可用(未检测到带 python-pptx 的解释器):${py.cmd ? '请给它 pip install python-pptx' : '未找到 python 解释器,也没探测到捆绑 Python'}。可改用默认 pptd 引擎。`
|
|
651
711
|
const r = await runPythonExport(ctx0, outName)
|
|
652
|
-
return `✓ 已导出(python-pptx 引擎):${r.file}\n
|
|
712
|
+
return `✓ 已导出(python-pptx 引擎):${r.file}\n图表为**原生可编辑图表**(带内嵌数据工作簿;引擎 A 的图表是矢量拼绘、不可改数据)。${await fallbackNote(ctx0, r)}${officialQaNote(r.file, ctx0.pages?.length)}${await withAudit(r.file)}${engineNote}`
|
|
653
713
|
}
|
|
654
714
|
try {
|
|
655
715
|
const r = await exportPptx(ctx0, { out: outName, engine: 'pptd' })
|
|
@@ -673,8 +733,8 @@ export function registerTools(ctx) {
|
|
|
673
733
|
: ''
|
|
674
734
|
const parityNote = r.parity
|
|
675
735
|
? (r.parity.ok
|
|
676
|
-
? `\n✅ 元素 parity 回读:表 ${r.parity.tablesExp}/${r.parity.tablesOut} · 图 ${r.parity.imagesExp}/${r.parity.imagesOut} · 媒体部件 ${r.parity.mediaExp ?? '?'}/${r.parity.mediaOut ?? '?'} · 线方向 ${r.parity.linesExp ?? 0}/${r.parity.linesOut ?? 0}
|
|
677
|
-
: `\n✗ 元素 parity 不一致:表 期望${r.parity.tablesExp}/实际${r.parity.tablesOut} · 图 期望${r.parity.imagesExp}/实际${r.parity.imagesOut} · 媒体 期望${r.parity.mediaExp ?? '?'}/实际${r.parity.mediaOut ?? '?'} · 线 期望${r.parity.linesExp ?? 0}/实际${r.parity.linesOut ?? 0}/方向错${r.parity.linesWrong ?? 0} · 非法帧 ${r.parity.illegalFrames}——请勿交付,附本输出反馈插件团队`)
|
|
736
|
+
? `\n✅ 元素 parity 回读:表 ${r.parity.tablesExp}/${r.parity.tablesOut} · 图 ${r.parity.imagesExp}/${r.parity.imagesOut} · 媒体部件 ${r.parity.mediaExp ?? '?'}/${r.parity.mediaOut ?? '?'} · 线方向 ${r.parity.linesExp ?? 0}/${r.parity.linesOut ?? 0} · **文本内容 ${r.parity.textExp ?? 0}/${r.parity.textExp ?? 0}** · 图表图形 ${r.parity.chartShapesExp ?? 0}/${r.parity.chartShapesOut ?? 0}(均逐条从 OOXML 反推自证) · 结构合法(无 PowerPoint 弃帧类嵌套 xfrm)`
|
|
737
|
+
: `\n✗ 元素 parity 不一致:表 期望${r.parity.tablesExp}/实际${r.parity.tablesOut} · 图 期望${r.parity.imagesExp}/实际${r.parity.imagesOut} · 媒体 期望${r.parity.mediaExp ?? '?'}/实际${r.parity.mediaOut ?? '?'} · 线 期望${r.parity.linesExp ?? 0}/实际${r.parity.linesOut ?? 0}/方向错${r.parity.linesWrong ?? 0} · 文本缺 ${(r.parity.textMissing ?? []).length} 处${(r.parity.textMissing ?? []).length ? `(${r.parity.textMissing.slice(0, 3).join(';')}${r.parity.textMissing.length > 3 ? '…' : ''})` : ''} · 图表图形 期望${r.parity.chartShapesExp ?? 0}/实际${r.parity.chartShapesOut ?? 0} · 非法帧 ${r.parity.illegalFrames}——请勿交付,附本输出反馈插件团队`)
|
|
678
738
|
: ''
|
|
679
739
|
// P8(测试反馈):真渲染抽查策略——含复杂元素的页必须覆盖(P1 事故的直接防线)
|
|
680
740
|
const riskyPages = (ctx0.pages ?? []).map((p, i) => {
|
|
@@ -684,7 +744,7 @@ export function registerTools(ctx) {
|
|
|
684
744
|
const renderNote = riskyPages.length
|
|
685
745
|
? `\nP8 真渲染抽查:建议优先覆盖含 table/chart/image/custGeom 的页 → 第 ${riskyPages.join('、')} 页(每类至少一页;audit 档导出自动全页真渲染)`
|
|
686
746
|
: ''
|
|
687
|
-
return `✓ 已导出(pptd 引擎,${r.slides} 页):${r.file}${fit}${phNote}${renamedNote}${parityNote}${renderNote}${floorNote}${chartNote}${await withAudit(r.file)}${engineNote}`
|
|
747
|
+
return `✓ 已导出(pptd 引擎,${r.slides} 页):${r.file}${fit}${phNote}${renamedNote}${parityNote}${renderNote}${floorNote}${chartNote}${officialQaNote(r.file, r.slides)}${await withAudit(r.file)}${engineNote}`
|
|
688
748
|
} catch (error) {
|
|
689
749
|
// 自动回退链(C1 决定):auto 且 pptd 硬失败 → 有 python-pptx 则兜底并醒目标注降级(绝不静默)
|
|
690
750
|
if (eff.allowFallback) {
|
|
@@ -692,7 +752,7 @@ export function registerTools(ctx) {
|
|
|
692
752
|
if (py.has) {
|
|
693
753
|
try {
|
|
694
754
|
const r2 = await runPythonExport(ctx0, outName)
|
|
695
|
-
return `⚠ pptd 引擎失败,已自动降级 python-pptx
|
|
755
|
+
return `⚠ pptd 引擎失败,已自动降级 python-pptx(其图表为原生可编辑图表 + 内嵌数据工作簿):${error?.message ?? error}\n✓ 已导出(python-pptx 兜底):${r2.file}\n建议排查 pptd 失败原因(见上)或改用质量更高的模式。${await fallbackNote(ctx0, r2)}${officialQaNote(r2.file, ctx0.pages?.length)}${await withAudit(r2.file)}`
|
|
696
756
|
} catch (e2) {
|
|
697
757
|
return `✗ pptd 失败(${error?.message ?? error})且 python-pptx 兜底也失败:${e2?.message ?? e2}`
|
|
698
758
|
}
|
|
@@ -777,7 +837,7 @@ export function registerTools(ctx) {
|
|
|
777
837
|
const refLine = r.reference?.previews?.length
|
|
778
838
|
? `\n参考层(参考双轨 v0.9.1——创作前先 read_image 看 reference/previews/*.png 真身再动手,与"按模板做"同一通道):reference/source.pptx + ${r.reference.previews.length} 页真渲染`
|
|
779
839
|
: `\n⚠ 无整页真渲染参考(无 Office/渲染失败):可用 ppt_visual 对 source.pptx 补渲;骨架层仍可用`
|
|
780
|
-
return `✓ 已导入:${r.outDir}(${r.pages} 页,${r.media.length} 个媒体;原始 pptx 已保留为 source.pptx —— 参考双轨真相层)${refLine}\n⚠ ${r.warnings.join(';')}`
|
|
840
|
+
return `✓ 已导入:${r.outDir}(${r.pages} 页,${r.media.length} 个媒体;原始 pptx 已保留为 source.pptx —— 参考双轨真相层)${refLine}\n⚠ ${r.warnings.join(';')}${r.lossLine ? '\n' + r.lossLine : ''}`
|
|
781
841
|
} catch (error) {
|
|
782
842
|
return `✗ 导入失败:\n${errText(error)}`
|
|
783
843
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-ppt-studio",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.4",
|
|
4
4
|
"description": "在 DeepSeek Harness 里做 PPT:说需求 → 逐页制作 → 导出可编辑的 .pptx。含布局检查、Office 真渲染复核,以及可扩展的本地模板库",
|
|
5
5
|
"//publish": "2026-09-18 定:发 npm,**无 scope** 包名 `dsh-ppt-studio`。**状态(2026-09-26):已发布** —— `dsh-ppt-studio@1.1.0` 在 npm 上(`dist-tags.latest`、public,发布者 `zbsph`),registry 的 tarball 与 GitHub Release 资产**逐字节相同**(sha256 `9303cc5c…`)。首个版本是 `npm publish <发版 tgz>` 直发的;**从下一版起改走 Trusted Publishing(OIDC)全自动通道**:`.github/workflows/publish.yml` 由 release 事件触发 → 下载 Release 资产 → OIDC `npm publish` → 发后自证 integrity —— 零长期 token、零手工(npm 已公告:Bypass-2FA 直发 2027-01 移除)。发布纪律不变:**用发版时那个 tgz**(`.sync-state.json` 的 `localTgz`),**别在 main 前进后跑裸 `npm publish`**(会按当前工作区重新打包 ⇒ 破坏'两条通道同一份字节')。手动补发入口:Actions → Publish to npm → Run workflow。见 README §10 与 docs/05「发布-同步纪律」。",
|
|
6
6
|
"repository": { "type": "git", "url": "git+https://github.com/zbsph/dsh-ppt-studio.git" },
|
|
@@ -71,6 +71,7 @@
|
|
|
71
71
|
"test:preset": "node scripts/check-preset.mjs",
|
|
72
72
|
"test:e2e": "node scripts/e2e-1.0.mjs",
|
|
73
73
|
"test:bundle": "node scripts/verify-bundle-install.mjs",
|
|
74
|
+
"test:scope": "node scripts/verify-preset-scope.mjs",
|
|
74
75
|
"check:lib": "node scripts/check-lib-freshness.mjs",
|
|
75
76
|
"audit:facts": "node scripts/audit-manual-facts.mjs",
|
|
76
77
|
"probe:effect": "node scripts/probe-effect-semantics.mjs",
|
|
@@ -91,8 +91,10 @@ const tableKeys = src.schema.match(/table: \['cols', 'rows', 'header'\]/)
|
|
|
91
91
|
check('table 只有 cols/rows/header(手册"没有逐列对齐字段"成立)', !!tableKeys, `schema.js ELEMENT_KEYS: ${tableKeys?.[0] ?? '未找到'}`)
|
|
92
92
|
|
|
93
93
|
const chartKeys = src.schema.match(/chart: \['chart'\]/)
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
// 事实更正(2026-09-26 第三轮:chart 默认 native):schema 现在**有**可选视觉字段(render/legend/labels/axes)——
|
|
95
|
+
// 默认 native 时图表**自带**坐标轴/网格/图例/数据标签;只有 `render: vector` 才回到"作者自己补标签"。
|
|
96
|
+
const chartHasRender = /CHART_RENDER\s*=\s*\['native',\s*'vector'\]/.test(src.schema)
|
|
97
|
+
check('chart 的可选视觉字段 = render/legend/labels/axes(手册须按 native/vector 两路描述)', !!chartKeys && chartHasRender, `schema.js ELEMENT_KEYS.chart=${chartKeys?.[0] ?? '?'};CHART_RENDER 常量=${chartHasRender}`)
|
|
96
98
|
|
|
97
99
|
const trunc = Number((src.svgCharts.match(/c\.length\s*>\s*(\d+)/) ?? [])[1])
|
|
98
100
|
const claimedTrunc = [...all.matchAll(/超过\s*(\d+)\s*字截断/g)].map((m) => Number(m[1]))
|
package/scripts/e2e-1.0.mjs
CHANGED
|
@@ -77,8 +77,8 @@ if (m.measured) {
|
|
|
77
77
|
const exp = await exportPptx(ctx, { out: join(fx, 'fx-pro.pptx'), engine: 'pptd' })
|
|
78
78
|
const z = zipRead(await readFile(exp.file))
|
|
79
79
|
const slides = [...z.keys()].filter((k) => /^ppt\/slides\/slide\d+\.xml$/.test(k))
|
|
80
|
-
const chartParts = [...z.keys()].filter((k) => k.startsWith('ppt/charts/'))
|
|
81
|
-
ok('⑤ export = 12 张 slide +
|
|
80
|
+
const chartParts = [...z.keys()].filter((k) => k.startsWith('ppt/charts/') && k.endsWith('.xml') && !k.includes('_rels'))
|
|
81
|
+
ok('⑤ export = 12 张 slide + 1 个**原生图表部件**(chart 默认 native;矢量降级见 smoke §57)', slides.length === seedPages && chartParts.length === 1, `${slides.length} slides / chart=${chartParts.length}`)
|
|
82
82
|
// 连线方向自证(2026-09-14 真实反馈:预览对、PowerPoint 里线镜像/× 掉一笔)
|
|
83
83
|
ok('⑤ export parity 自证:表/图/线方向全绿(线逐条从 OOXML 反推端点)',
|
|
84
84
|
exp.parity?.ok === true && exp.parity.linesExp > 0 && exp.parity.linesExp === exp.parity.linesOut && exp.parity.linesWrong === 0,
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"7. 交付前:S4 全通过 + S6 导出 .pptx + 交付说明(素材来源、未视觉审阅项、修改指引)。**内容审阅(M3 重写 2026-09-18)**:有具体数字/事实主张的稿子,交付前用 `ppt_crosscheck` 生成「审阅材料包」(全页正文 + 素材清单 + 审阅协议)→ 再按协议做一次内容审阅:**默认用独立子代理**(全新上下文、只给材料包与素材文件;**用户明确禁止子代理时改自审并标注「自审(同上下文)」**),逐条判\"一致 / 冲突 / 无来源支撑 / 无法核实\",重点核对同一指标跨页值是否一致、数字是否有依据、结论是否强于数据;**没有外部素材时只能判\"无法核实\",不得报绿**。该工具**只汇总材料、不做判定、不构成门禁**(旧版 grounded/unmapped 状态与跨页数字分组已废除)。制作进程中**供用户看稿优先用 ppt_preview(dir)**(返回同源预览链接,用户点击即看,附在交付信息里);导出前/后均可预览。**修改既有精美 PPT 的某一页(edit/augment 高频场景)**:ppt_import 参考层读真身 → 改工作区页并审阅清零 → **`ppt_splice`**(只把该页替换进原稿:保留源母版横幅/页脚/备注关系/媒体,其余页条目 SHA256 逐字节一致——不要整册近似重渲伤及其他页)→ 交付整册副本;用户要\"单页独立版\"再 **`ppt_slice`**;抽查该页观感用 **`ppt_visual pages=N`**(按页真渲染,不必整册);\"看起来就是模板原样\"用 `ppt_patch`(手术贴模板)。",
|
|
36
36
|
"8. 版式系统(审美基线,用户风格优先):写页面之前先定版式规则——默认基线:8px 网格对齐、3-4 级字号阶梯、4-5 色主题(只在 $theme 内取色)、统一间距节奏(8/16/24px)、左右外边界一致、构图重心居中;**用户对风格有要求(字体/字号/配色/密度)一律优先,默认基线仅为未指定时的风格参考**。文字:短句化(每块 ≤60 字为默认参考,可依内容放宽),**文本最小字号以用户指令为准**(hardConstraints;未给下限不设强制),长句在语义断点显式 \\\\n 换行。",
|
|
37
37
|
"9. 审美建议(ppt_verify 输出的 [·] 建议)不是门禁,但请逐条斟酌采纳;overlap/out-of-page/text-overflow 错误仍必须清零(审美改进不得引入元素区块冲突)。",
|
|
38
|
-
"10.
|
|
38
|
+
"10. 引擎:**PPT 制作唯一通道 = pptd**(`deck.yaml` + `pages/*.yaml`;图表矢量拼绘)。pptd 硬失败时自动回退 python-pptx(报告醒目标注降级;其图表为**原生可编辑图表 + 内嵌数据工作簿**);python-pptx 仅用户显式指定时使用。**不要**用 python-pptx 或官方 `office-pptx` 技能从零拼版来绕过本工作流的门禁——它们只适合**读取**既有 pptx、或作为上面的兜底(能不能用由 `ppt_state` 的 `capabilities` 如实报告)。除非用户要求,不要自行切换引擎。",
|
|
39
39
|
"11. 质量档 audit(/ppt quality audit,从严门禁):禁 autoDeclare(工具已拦);每页必须 ppt_shot + 读图视觉审阅(无读图能力则降级结构 Lint 并在交付说明标注\"未经视觉审阅\");导出报告必须包含并核对 audit 回读断言(页数/尺寸/最小字号 ≥ minFontSize);有 Office 时导出后自动 **Office 真渲染**(成品视觉审核,逐页 read_image);交付说明含素材来源与数据脱敏/假设标注。",
|
|
40
40
|
"12. **真渲染抽检铁律(P1 事故教训)**:有 Office 时,真渲染抽查**必须覆盖所有含 table/chart/image/custGeom 的页**(每类至少一页,其余页轮换)——不要只挑\"美观重点页\";抽查后逐页 read_image(渲染图已带 1px=1pt 比例标注水印,降低坐标误判)。导出报告会给出建议覆盖页清单(P8 行)。"
|
|
41
41
|
]
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"7. 交付前:S4 全通过 + S6 导出 .pptx + 交付说明(素材来源、未视觉审阅项、修改指引)。**内容审阅(M3 重写 2026-09-18)**:有具体数字/事实主张的稿子,交付前用 `ppt_crosscheck` 生成「审阅材料包」(全页正文 + 素材清单 + 审阅协议)→ 再按协议做一次内容审阅:**默认用独立子代理**(全新上下文、只给材料包与素材文件;**用户明确禁止子代理时改自审并标注「自审(同上下文)」**),逐条判\"一致 / 冲突 / 无来源支撑 / 无法核实\",重点核对同一指标跨页值是否一致、数字是否有依据、结论是否强于数据;**没有外部素材时只能判\"无法核实\",不得报绿**。该工具**只汇总材料、不做判定、不构成门禁**(旧版 grounded/unmapped 状态与跨页数字分组已废除)。制作进程中**供用户看稿优先用 ppt_preview(dir)**(返回同源预览链接,用户点击即看,附在交付信息里);导出前/后均可预览。**修改既有精美 PPT 的某一页(edit/augment 高频场景)**:ppt_import 参考层读真身 → 改工作区页并审阅清零 → **`ppt_splice`**(只把该页替换进原稿:保留源母版横幅/页脚/备注关系/媒体,其余页条目 SHA256 逐字节一致——不要整册近似重渲伤及其他页)→ 交付整册副本;用户要\"单页独立版\"再 **`ppt_slice`**;抽查该页观感用 **`ppt_visual pages=N`**(按页真渲染,不必整册);\"看起来就是模板原样\"用 `ppt_patch`(手术贴模板)。",
|
|
75
75
|
"8. 版式系统(审美基线,用户风格优先):写页面之前先定版式规则——默认基线:8px 网格对齐、3-4 级字号阶梯、4-5 色主题(只在 $theme 内取色)、统一间距节奏(8/16/24px)、左右外边界一致、构图重心居中;**用户对风格有要求(字体/字号/配色/密度)一律优先,默认基线仅为未指定时的风格参考**。文字:短句化(每块 ≤60 字为默认参考,可依内容放宽),**文本最小字号以用户指令为准**(hardConstraints;未给下限不设强制),长句在语义断点显式 \\\\n 换行。",
|
|
76
76
|
"9. 审美建议(ppt_verify 输出的 [·] 建议)不是门禁,但请逐条斟酌采纳;overlap/out-of-page/text-overflow 错误仍必须清零(审美改进不得引入元素区块冲突)。",
|
|
77
|
-
"10.
|
|
77
|
+
"10. 引擎:**PPT 制作唯一通道 = pptd**(`deck.yaml` + `pages/*.yaml`;图表矢量拼绘)。pptd 硬失败时自动回退 python-pptx(报告醒目标注降级;其图表为**原生可编辑图表 + 内嵌数据工作簿**);python-pptx 仅用户显式指定时使用。**不要**用 python-pptx 或官方 `office-pptx` 技能从零拼版来绕过本工作流的门禁——它们只适合**读取**既有 pptx、或作为上面的兜底(能不能用由 `ppt_state` 的 `capabilities` 如实报告)。除非用户要求,不要自行切换引擎。",
|
|
78
78
|
"11. 质量档 audit(/ppt quality audit,从严门禁):禁 autoDeclare(工具已拦);每页必须 ppt_shot + 读图视觉审阅(无读图能力则降级结构 Lint 并在交付说明标注\"未经视觉审阅\");导出报告必须包含并核对 audit 回读断言(页数/尺寸/最小字号 ≥ minFontSize);有 Office 时导出后自动 **Office 真渲染**(成品视觉审核,逐页 read_image);交付说明含素材来源与数据脱敏/假设标注。",
|
|
79
79
|
"12. **真渲染抽检铁律(P1 事故教训)**:有 Office 时,真渲染抽查**必须覆盖所有含 table/chart/image/custGeom 的页**(每类至少一页,其余页轮换)——不要只挑\"美观重点页\";抽查后逐页 read_image(渲染图已带 1px=1pt 比例标注水印,降低坐标误判)。导出报告会给出建议覆盖页清单(P8 行)。"
|
|
80
80
|
]
|
|
@@ -113,7 +113,7 @@
|
|
|
113
113
|
"7. 交付前:S4 全通过 + S6 导出 .pptx + 交付说明(素材来源、未视觉审阅项、修改指引)。**内容审阅(M3 重写 2026-09-18)**:有具体数字/事实主张的稿子,交付前用 `ppt_crosscheck` 生成「审阅材料包」(全页正文 + 素材清单 + 审阅协议)→ 再按协议做一次内容审阅:**默认用独立子代理**(全新上下文、只给材料包与素材文件;**用户明确禁止子代理时改自审并标注「自审(同上下文)」**),逐条判\"一致 / 冲突 / 无来源支撑 / 无法核实\",重点核对同一指标跨页值是否一致、数字是否有依据、结论是否强于数据;**没有外部素材时只能判\"无法核实\",不得报绿**。该工具**只汇总材料、不做判定、不构成门禁**(旧版 grounded/unmapped 状态与跨页数字分组已废除)。制作进程中**供用户看稿优先用 ppt_preview(dir)**(返回同源预览链接,用户点击即看,附在交付信息里);导出前/后均可预览。**修改既有精美 PPT 的某一页(edit/augment 高频场景)**:ppt_import 参考层读真身 → 改工作区页并审阅清零 → **`ppt_splice`**(只把该页替换进原稿:保留源母版横幅/页脚/备注关系/媒体,其余页条目 SHA256 逐字节一致——不要整册近似重渲伤及其他页)→ 交付整册副本;用户要\"单页独立版\"再 **`ppt_slice`**;抽查该页观感用 **`ppt_visual pages=N`**(按页真渲染,不必整册);\"看起来就是模板原样\"用 `ppt_patch`(手术贴模板)。",
|
|
114
114
|
"8. 版式系统(审美基线,用户风格优先):写页面之前先定版式规则——默认基线:8px 网格对齐、3-4 级字号阶梯、4-5 色主题(只在 $theme 内取色)、统一间距节奏(8/16/24px)、左右外边界一致、构图重心居中;**用户对风格有要求(字体/字号/配色/密度)一律优先,默认基线仅为未指定时的风格参考**。文字:短句化(每块 ≤60 字为默认参考,可依内容放宽),**文本最小字号以用户指令为准**(hardConstraints;未给下限不设强制),长句在语义断点显式 \\\\n 换行。",
|
|
115
115
|
"9. 审美建议(ppt_verify 输出的 [·] 建议)不是门禁,但请逐条斟酌采纳;overlap/out-of-page/text-overflow 错误仍必须清零(审美改进不得引入元素区块冲突)。",
|
|
116
|
-
"10.
|
|
116
|
+
"10. 引擎:**PPT 制作唯一通道 = pptd**(`deck.yaml` + `pages/*.yaml`;图表矢量拼绘)。pptd 硬失败时自动回退 python-pptx(报告醒目标注降级;其图表为**原生可编辑图表 + 内嵌数据工作簿**);python-pptx 仅用户显式指定时使用。**不要**用 python-pptx 或官方 `office-pptx` 技能从零拼版来绕过本工作流的门禁——它们只适合**读取**既有 pptx、或作为上面的兜底(能不能用由 `ppt_state` 的 `capabilities` 如实报告)。除非用户要求,不要自行切换引擎。",
|
|
117
117
|
"11. 质量档 audit(/ppt quality audit,从严门禁):禁 autoDeclare(工具已拦);每页必须 ppt_shot + 读图视觉审阅(无读图能力则降级结构 Lint 并在交付说明标注\"未经视觉审阅\");导出报告必须包含并核对 audit 回读断言(页数/尺寸/最小字号 ≥ minFontSize);有 Office 时导出后自动 **Office 真渲染**(成品视觉审核,逐页 read_image);交付说明含素材来源与数据脱敏/假设标注。",
|
|
118
118
|
"12. **真渲染抽检铁律(P1 事故教训)**:有 Office 时,真渲染抽查**必须覆盖所有含 table/chart/image/custGeom 的页**(每类至少一页,其余页轮换)——不要只挑\"美观重点页\";抽查后逐页 read_image(渲染图已带 1px=1pt 比例标注水印,降低坐标误判)。导出报告会给出建议覆盖页清单(P8 行)。"
|
|
119
119
|
]
|
|
@@ -152,7 +152,7 @@
|
|
|
152
152
|
"7. 交付前:S4 全通过 + S6 导出 .pptx + 交付说明(素材来源、未视觉审阅项、修改指引)。**内容审阅(M3 重写 2026-09-18)**:有具体数字/事实主张的稿子,交付前用 `ppt_crosscheck` 生成「审阅材料包」(全页正文 + 素材清单 + 审阅协议)→ 再按协议做一次内容审阅:**默认用独立子代理**(全新上下文、只给材料包与素材文件;**用户明确禁止子代理时改自审并标注「自审(同上下文)」**),逐条判\"一致 / 冲突 / 无来源支撑 / 无法核实\",重点核对同一指标跨页值是否一致、数字是否有依据、结论是否强于数据;**没有外部素材时只能判\"无法核实\",不得报绿**。该工具**只汇总材料、不做判定、不构成门禁**(旧版 grounded/unmapped 状态与跨页数字分组已废除)。制作进程中**供用户看稿优先用 ppt_preview(dir)**(返回同源预览链接,用户点击即看,附在交付信息里);导出前/后均可预览。**修改既有精美 PPT 的某一页(edit/augment 高频场景)**:ppt_import 参考层读真身 → 改工作区页并审阅清零 → **`ppt_splice`**(只把该页替换进原稿:保留源母版横幅/页脚/备注关系/媒体,其余页条目 SHA256 逐字节一致——不要整册近似重渲伤及其他页)→ 交付整册副本;用户要\"单页独立版\"再 **`ppt_slice`**;抽查该页观感用 **`ppt_visual pages=N`**(按页真渲染,不必整册);\"看起来就是模板原样\"用 `ppt_patch`(手术贴模板)。",
|
|
153
153
|
"8. 版式系统(审美基线,用户风格优先):写页面之前先定版式规则——默认基线:8px 网格对齐、3-4 级字号阶梯、4-5 色主题(只在 $theme 内取色)、统一间距节奏(8/16/24px)、左右外边界一致、构图重心居中;**用户对风格有要求(字体/字号/配色/密度)一律优先,默认基线仅为未指定时的风格参考**。文字:短句化(每块 ≤60 字为默认参考,可依内容放宽),**文本最小字号以用户指令为准**(hardConstraints;未给下限不设强制),长句在语义断点显式 \\\\n 换行。",
|
|
154
154
|
"9. 审美建议(ppt_verify 输出的 [·] 建议)不是门禁,但请逐条斟酌采纳;overlap/out-of-page/text-overflow 错误仍必须清零(审美改进不得引入元素区块冲突)。",
|
|
155
|
-
"10.
|
|
155
|
+
"10. 引擎:**PPT 制作唯一通道 = pptd**(`deck.yaml` + `pages/*.yaml`;图表矢量拼绘)。pptd 硬失败时自动回退 python-pptx(报告醒目标注降级;其图表为**原生可编辑图表 + 内嵌数据工作簿**);python-pptx 仅用户显式指定时使用。**不要**用 python-pptx 或官方 `office-pptx` 技能从零拼版来绕过本工作流的门禁——它们只适合**读取**既有 pptx、或作为上面的兜底(能不能用由 `ppt_state` 的 `capabilities` 如实报告)。除非用户要求,不要自行切换引擎。",
|
|
156
156
|
"11. 质量档 audit(/ppt quality audit,从严门禁):禁 autoDeclare(工具已拦);每页必须 ppt_shot + 读图视觉审阅(无读图能力则降级结构 Lint 并在交付说明标注\"未经视觉审阅\");导出报告必须包含并核对 audit 回读断言(页数/尺寸/最小字号 ≥ minFontSize);有 Office 时导出后自动 **Office 真渲染**(成品视觉审核,逐页 read_image);交付说明含素材来源与数据脱敏/假设标注。",
|
|
157
157
|
"12. **真渲染抽检铁律(P1 事故教训)**:有 Office 时,真渲染抽查**必须覆盖所有含 table/chart/image/custGeom 的页**(每类至少一页,其余页轮换)——不要只挑\"美观重点页\";抽查后逐页 read_image(渲染图已带 1px=1pt 比例标注水印,降低坐标误判)。导出报告会给出建议覆盖页清单(P8 行)。"
|
|
158
158
|
]
|
|
@@ -191,7 +191,7 @@
|
|
|
191
191
|
"7. 交付前:S4 全通过 + S6 导出 .pptx + 交付说明(素材来源、未视觉审阅项、修改指引)。**内容审阅(M3 重写 2026-09-18)**:有具体数字/事实主张的稿子,交付前用 `ppt_crosscheck` 生成「审阅材料包」(全页正文 + 素材清单 + 审阅协议)→ 再按协议做一次内容审阅:**默认用独立子代理**(全新上下文、只给材料包与素材文件;**用户明确禁止子代理时改自审并标注「自审(同上下文)」**),逐条判\"一致 / 冲突 / 无来源支撑 / 无法核实\",重点核对同一指标跨页值是否一致、数字是否有依据、结论是否强于数据;**没有外部素材时只能判\"无法核实\",不得报绿**。该工具**只汇总材料、不做判定、不构成门禁**(旧版 grounded/unmapped 状态与跨页数字分组已废除)。制作进程中**供用户看稿优先用 ppt_preview(dir)**(返回同源预览链接,用户点击即看,附在交付信息里);导出前/后均可预览。**修改既有精美 PPT 的某一页(edit/augment 高频场景)**:ppt_import 参考层读真身 → 改工作区页并审阅清零 → **`ppt_splice`**(只把该页替换进原稿:保留源母版横幅/页脚/备注关系/媒体,其余页条目 SHA256 逐字节一致——不要整册近似重渲伤及其他页)→ 交付整册副本;用户要\"单页独立版\"再 **`ppt_slice`**;抽查该页观感用 **`ppt_visual pages=N`**(按页真渲染,不必整册);\"看起来就是模板原样\"用 `ppt_patch`(手术贴模板)。",
|
|
192
192
|
"8. 版式系统(审美基线,用户风格优先):写页面之前先定版式规则——默认基线:8px 网格对齐、3-4 级字号阶梯、4-5 色主题(只在 $theme 内取色)、统一间距节奏(8/16/24px)、左右外边界一致、构图重心居中;**用户对风格有要求(字体/字号/配色/密度)一律优先,默认基线仅为未指定时的风格参考**。文字:短句化(每块 ≤60 字为默认参考,可依内容放宽),**文本最小字号以用户指令为准**(hardConstraints;未给下限不设强制),长句在语义断点显式 \\\\n 换行。",
|
|
193
193
|
"9. 审美建议(ppt_verify 输出的 [·] 建议)不是门禁,但请逐条斟酌采纳;overlap/out-of-page/text-overflow 错误仍必须清零(审美改进不得引入元素区块冲突)。",
|
|
194
|
-
"10.
|
|
194
|
+
"10. 引擎:**PPT 制作唯一通道 = pptd**(`deck.yaml` + `pages/*.yaml`;图表矢量拼绘)。pptd 硬失败时自动回退 python-pptx(报告醒目标注降级;其图表为**原生可编辑图表 + 内嵌数据工作簿**);python-pptx 仅用户显式指定时使用。**不要**用 python-pptx 或官方 `office-pptx` 技能从零拼版来绕过本工作流的门禁——它们只适合**读取**既有 pptx、或作为上面的兜底(能不能用由 `ppt_state` 的 `capabilities` 如实报告)。除非用户要求,不要自行切换引擎。",
|
|
195
195
|
"11. 质量档 audit(/ppt quality audit,从严门禁):禁 autoDeclare(工具已拦);每页必须 ppt_shot + 读图视觉审阅(无读图能力则降级结构 Lint 并在交付说明标注\"未经视觉审阅\");导出报告必须包含并核对 audit 回读断言(页数/尺寸/最小字号 ≥ minFontSize);有 Office 时导出后自动 **Office 真渲染**(成品视觉审核,逐页 read_image);交付说明含素材来源与数据脱敏/假设标注。",
|
|
196
196
|
"12. **真渲染抽检铁律(P1 事故教训)**:有 Office 时,真渲染抽查**必须覆盖所有含 table/chart/image/custGeom 的页**(每类至少一页,其余页轮换)——不要只挑\"美观重点页\";抽查后逐页 read_image(渲染图已带 1px=1pt 比例标注水印,降低坐标误判)。导出报告会给出建议覆盖页清单(P8 行)。"
|
|
197
197
|
]
|
|
@@ -212,7 +212,7 @@
|
|
|
212
212
|
"4. 页数:≤8 页(用户指定除外);每页文本精简(每块 ≤40 字为默认参考,用户要求可放宽)。",
|
|
213
213
|
"5. 审阅:ppt_render + ppt_verify 必跑——内容互压/出界/文本溢出/预期外重叠(设计意图声明制)必须清零;出界分级同标准模式(超页面不可声明;安全区外有意元素逐项加 expectedOutOfSafeArea);autoDeclare 后附一句意图说明;跳过截图视觉审阅与美学迭代。",
|
|
214
214
|
"6. 交付:ppt_export(引擎 auto=pptd)→ 交付说明标注\"快速模式:未经视觉审阅\"。",
|
|
215
|
-
"7. 引擎:auto 默认 = pptd(自研主引擎,图表矢量拼绘);python-pptx 仅显式指定或 pptd
|
|
215
|
+
"7. 引擎:auto 默认 = pptd(自研主引擎,图表矢量拼绘);python-pptx 仅显式指定或 pptd 失败时使用(其图表为原生可编辑图表 + 内嵌数据工作簿)。不要自行切换引擎。",
|
|
216
216
|
"8. 用户明确要求始终优先;回到完整模式:/ppt normal。"
|
|
217
217
|
]
|
|
218
218
|
},
|
|
@@ -232,7 +232,7 @@
|
|
|
232
232
|
"4. 页数:≤8 页(用户指定除外);每页文本精简(每块 ≤40 字为默认参考,用户要求可放宽)。",
|
|
233
233
|
"5. 审阅:ppt_render + ppt_verify 必跑——内容互压/出界/文本溢出/预期外重叠(设计意图声明制)必须清零;出界分级同标准模式(超页面不可声明;安全区外有意元素逐项加 expectedOutOfSafeArea);autoDeclare 后附一句意图说明;跳过截图视觉审阅与美学迭代。",
|
|
234
234
|
"6. 交付:ppt_export(引擎 auto=pptd)→ 交付说明标注\"快速模式:未经视觉审阅\"。",
|
|
235
|
-
"7. 引擎:auto 默认 = pptd(自研主引擎,图表矢量拼绘);python-pptx 仅显式指定或 pptd
|
|
235
|
+
"7. 引擎:auto 默认 = pptd(自研主引擎,图表矢量拼绘);python-pptx 仅显式指定或 pptd 失败时使用(其图表为原生可编辑图表 + 内嵌数据工作簿)。不要自行切换引擎。",
|
|
236
236
|
"8. 用户明确要求始终优先;回到完整模式:/ppt normal。"
|
|
237
237
|
]
|
|
238
238
|
},
|
|
@@ -252,7 +252,7 @@
|
|
|
252
252
|
"4. 页数:≤8 页(用户指定除外);每页文本精简(每块 ≤40 字为默认参考,用户要求可放宽)。",
|
|
253
253
|
"5. 审阅:ppt_render + ppt_verify 必跑——内容互压/出界/文本溢出/预期外重叠(设计意图声明制)必须清零;出界分级同标准模式(超页面不可声明;安全区外有意元素逐项加 expectedOutOfSafeArea);autoDeclare 后附一句意图说明;跳过截图视觉审阅与美学迭代。",
|
|
254
254
|
"6. 交付:ppt_export(引擎 auto=pptd)→ 交付说明标注\"快速模式:未经视觉审阅\"。",
|
|
255
|
-
"7. 引擎:auto 默认 = pptd(自研主引擎,图表矢量拼绘);python-pptx 仅显式指定或 pptd
|
|
255
|
+
"7. 引擎:auto 默认 = pptd(自研主引擎,图表矢量拼绘);python-pptx 仅显式指定或 pptd 失败时使用(其图表为原生可编辑图表 + 内嵌数据工作簿)。不要自行切换引擎。",
|
|
256
256
|
"8. 用户明确要求始终优先;回到完整模式:/ppt normal。"
|
|
257
257
|
]
|
|
258
258
|
},
|
|
@@ -272,7 +272,7 @@
|
|
|
272
272
|
"4. 页数:≤8 页(用户指定除外);每页文本精简(每块 ≤40 字为默认参考,用户要求可放宽)。",
|
|
273
273
|
"5. 审阅:ppt_render + ppt_verify 必跑——内容互压/出界/文本溢出/预期外重叠(设计意图声明制)必须清零;出界分级同标准模式(超页面不可声明;安全区外有意元素逐项加 expectedOutOfSafeArea);autoDeclare 后附一句意图说明;跳过截图视觉审阅与美学迭代。",
|
|
274
274
|
"6. 交付:ppt_export(引擎 auto=pptd)→ 交付说明标注\"快速模式:未经视觉审阅\"。",
|
|
275
|
-
"7. 引擎:auto 默认 = pptd(自研主引擎,图表矢量拼绘);python-pptx 仅显式指定或 pptd
|
|
275
|
+
"7. 引擎:auto 默认 = pptd(自研主引擎,图表矢量拼绘);python-pptx 仅显式指定或 pptd 失败时使用(其图表为原生可编辑图表 + 内嵌数据工作簿)。不要自行切换引擎。",
|
|
276
276
|
"8. 用户明确要求始终优先;回到完整模式:/ppt normal。"
|
|
277
277
|
]
|
|
278
278
|
},
|
|
@@ -292,7 +292,7 @@
|
|
|
292
292
|
"4. 页数:≤8 页(用户指定除外);每页文本精简(每块 ≤40 字为默认参考,用户要求可放宽)。",
|
|
293
293
|
"5. 审阅:ppt_render + ppt_verify 必跑——内容互压/出界/文本溢出/预期外重叠(设计意图声明制)必须清零;出界分级同标准模式(超页面不可声明;安全区外有意元素逐项加 expectedOutOfSafeArea);autoDeclare 后附一句意图说明;跳过截图视觉审阅与美学迭代。",
|
|
294
294
|
"6. 交付:ppt_export(引擎 auto=pptd)→ 交付说明标注\"快速模式:未经视觉审阅\"。",
|
|
295
|
-
"7. 引擎:auto 默认 = pptd(自研主引擎,图表矢量拼绘);python-pptx 仅显式指定或 pptd
|
|
295
|
+
"7. 引擎:auto 默认 = pptd(自研主引擎,图表矢量拼绘);python-pptx 仅显式指定或 pptd 失败时使用(其图表为原生可编辑图表 + 内嵌数据工作簿)。不要自行切换引擎。",
|
|
296
296
|
"8. 用户明确要求始终优先;回到完整模式:/ppt normal。"
|
|
297
297
|
]
|
|
298
298
|
}
|
package/scripts/install.mjs
CHANGED
|
@@ -70,19 +70,21 @@ const legacyPkgDir = join(profileDir, 'node_modules', '@dsh-external', 'dsh-ppt-
|
|
|
70
70
|
const legacyPresetDir = join(prefix, '.agent-presets', 'ppt')
|
|
71
71
|
|
|
72
72
|
if (args.includes('--isolate')) {
|
|
73
|
-
console.error(`✗ --isolate 在 DSH 0.1.7
|
|
73
|
+
console.error(`✗ --isolate 在 DSH 0.1.7 上不再可用(**隔离现在是默认行为**,不需要这个开关)
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
·
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
75
|
+
原因:
|
|
76
|
+
· 插件声明「PPT 工作室」预设时会追加一行**自定位行**(本包入口的绝对 \`file://\` 名字)
|
|
77
|
+
⇒ 预设的常驻组合把插件再挂一次,那一次的工具/技能/提示段只落在**预设作用域**
|
|
78
|
+
⇒ 只有该预设的会话看得到,**其他预设完全不受影响**(2026-09-26 真宿主实测通过)。
|
|
79
|
+
· 所以"默认装法"与"隔离装法"已经合并:直接跑本脚本(不带 --isolate)即可。
|
|
80
|
+
· 0.1.6 时代的 \`--isolate\` 靠"预设目录内 junction + 相对路径行"实现;0.1.7 删除了目录发现,
|
|
81
|
+
且预设行按注册表所在 ctx 的 baseUrl 解析(= harness 包目录)⇒ 那条路必然产出 broken 预设,
|
|
82
|
+
本安装器不再写它(也不再写 \`<dshHome>/.agent-presets/\`)。
|
|
80
83
|
|
|
81
84
|
可选做法:
|
|
82
|
-
·
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
\`file://\` 名字(见 README §10「隔离安装」)。这条路尚未实现——宁可明确拒绝,也不静默失败。`)
|
|
85
|
+
· 默认安装:直接跑本脚本 —— 隔离即默认,一条命令。
|
|
86
|
+
· 想回到"所有预设都能用":把 profile 里 \`ppt-studio\` 行的 config 改成 \`scope: 'profile'\`
|
|
87
|
+
(见 README §1.6「隔离 / 传统档」)。`)
|
|
86
88
|
process.exit(1)
|
|
87
89
|
}
|
|
88
90
|
|
|
@@ -96,7 +98,7 @@ if (!existsSync(join(root, 'lib', 'index.js'))) {
|
|
|
96
98
|
}
|
|
97
99
|
|
|
98
100
|
const steps = []
|
|
99
|
-
steps.push(
|
|
101
|
+
steps.push(`装配模式:profile bundle(**隔离默认**:能力面只出现在「PPT 工作室」预设里;把装配行的 scope 改成 'profile' 可回到全局)`)
|
|
100
102
|
|
|
101
103
|
// ── 1) 包本体可达性 + bundle 装配 ─────────────────────────────────────────────
|
|
102
104
|
const profilePkgFile = join(profileDir, 'package.json')
|