@web-ppt/agent-mcp 0.0.0-stage → 0.8.0-beta.2

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/CHANGELOG.md ADDED
@@ -0,0 +1,522 @@
1
+ # 更新日志
2
+
3
+ 本文件记录对使用者可见的变化。版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
4
+
5
+ ## 0.8.0-beta.2 - 2026-11-04
6
+
7
+ - 新增仓库 Agent 配对评测工具:复用生产 MCP 比较自动 lint 反馈,保存全部尝试和真实产物;`npm run eval:agent` 可离线重判已提交证据,真实模型调用使用独立显式命令。评测结果不作为发布门禁,不增加 SDK 运行时依赖。
8
+ - 修复图元固件生成依赖本机 LibreOffice 缓存的问题:使用已登记哈希的原有 EMF / WMF 资产,生成字节保持原基线,并校验缓存污染与跨进程确定性。
9
+ - 模板二进制边界改为审计 npm 实际发布文件清单,覆盖全部发布包;仓库私有评测文稿可保留,SDK 包夹带二进制模板仍会被拒绝。
10
+ - 官网语言回归隔离后台字体完成回调,保留视图、页码、内容和下载断言;触屏回归使用实际设备能力仿真,并在场景结束恢复原输入状态与原生字体方法。
11
+
12
+ 九包继续使用同一 beta 版本,SDK 编辑行为与 `0.8.0-beta.1` 保持一致。GitHub 发布链路以 MCP 包的真实 OIDC 新版本发布作为首次激活验收。
13
+
14
+ `@web-ppt/collab` 发布入口为 12.20KB gzip;排除 peer 的完整测试薄包为 14,333B gzip。
15
+
16
+ Windows PowerPoint 真机验收继续延后,未记为通过。
17
+
18
+ ## 0.8.0-beta.1 - 2026-11-04
19
+
20
+ 本版已发布到 npm `next`,九包需安装同一版本。`0.7.0` 的已实现能力一并纳入本次 beta;既有八包的 `latest` 保持 `0.6.0`,`agent-mcp` 首版为本次 beta。九包已通过官方 registry 全新安装、SDK 入口、填充 Schema 与真实 MCP 握手验证。
21
+
22
+ - 新增 `@web-ppt/edit-core/fill`:按结构化大纲选择模板版式,生成标题、章节、正文、双栏、图片及八种原生图表;生成副本可修复出血、字体、对比度和文字适配,完整检查后保存并规范化重开。输入保持不变,大纲 Schema 公开,跨进程产物字节确定。
23
+ - 修复备注增量保存缺少备注母版闭包导致独立读取器超时;仅作用显式改写或新建备注,保持未编辑来源字节、已有母版设计和完整撤销语义。
24
+ - 官网编辑器按需提供八个原生 WebMCP 工具,浏览器 Agent 可读取、检查、修改、撤销重做、渲染和下载当前文稿,复用现有历史、恢复与协同。切换或关闭文稿时注销并取消工具;不支持接口时不加载插件。
25
+ - 修复 Agent `SetLink` 命令误把超链接目标当作母版或版式目标而拒绝合法链接。
26
+ - 修复本机 MCP 保存和渲染的文件写入未完整纳入会话队列;关闭与后续操作等待交付结束,保存写入成功后才确认保存点。
27
+ - 新增 `@web-ppt/edit-core/agent` 命令工具面:全部公开命令由 TypeScript 生成 JSON Schema,支持单条 / 批量原子编辑、受影响页布局观察、单元素样式查询、撤销 / 重做、Node SVG / PNG 渲染及保存;独立按需入口不引入 MCP / 模型依赖。
28
+ - 新增 `@web-ppt/agent-mcp` 本机 stdio MCP server 与 Agent Skill:打开、describe、单元素查询、lint、SVG/PNG 渲染、原子编辑、撤销/重做、保存和关闭;会话串行、关闭取消、原子落盘且无网络请求。真实 Claude Code 与 Codex 固定任务产物已通过 LibreOffice 与 Quick Look。
29
+
30
+ `@web-ppt/collab` 发布入口为 12.20KB gzip;排除 peer 的完整测试薄包为 14,333B gzip。
31
+
32
+ Windows PowerPoint 真机验收继续延后,未记为通过;稳定版仍需 beta 反馈与真机验收。
33
+
34
+ ## 0.6.0 - 2026-11-02
35
+
36
+ 0.6.0 稳定版:`0.6.0-beta.1` 的全部内容转正到 `latest`,包括 Agent 结构化读出、布局检查与 Node 无 Office 渲染,矢量 PDF 正式入口,视频音轨与内嵌视频合成,以及 OLE DOCX / XLSX 深度编辑官网入口。相对 0.5.0 的逐项变化见下方 `0.6.0-beta.1` 条目。
37
+
38
+ 安装:无版本号安装即得 0.6.0,八包必须同版本;`@next` 标签留给 0.7 线。
39
+
40
+ `@web-ppt/collab` 发布入口为 12.20KB gzip;排除 peer 的完整测试薄包为 14,333B gzip。
41
+
42
+ Windows PowerPoint 真机验收经维护者决定延后,未记为通过;0.6.0 先行转正,四项自动门禁与 LibreOffice 外部打开清单全部通过。
43
+
44
+ ## 0.6.0-beta.1 - 2026-10-31
45
+
46
+ 0.6.0 第一批与第二批全部七张票据收口:Agent Node 渲染、矢量 PDF 转正、视频音轨证据、内嵌视频合成、OLE 深度编辑官网入口。
47
+
48
+ - 新增 `@web-ppt/core/node` 按需入口(Agent 无 Office 渲染):`renderSlideSvgInNode` / `renderSlidePngInNode` 在纯 Node 环境渲染 `.pptx` / `.ppt` 为原生 `<text>` SVG 或宿主光栅化 PNG;测字与光栅化全部注入(推荐 `@web-ppt/fonts/glyphs` 的 `withFontMeasurement`),缺注入抛稳定 reason(`node-measure-required` / `node-rasterizer-required`)不退回估算断行;跨进程确定性按资源字节寻址口径验证,与浏览器原生路径 148/152 页整页 SVG 指纹逐字节一致(差异页归因:公式测量源 / 艺术字字距放大)。
49
+ - 矢量 PDF 转为正式入口(去除实验性标记):验收对照扩展到 MuPDF / poppler / CoreGraphics / PDFium 四个独立实现;文字提取(含中文与组合字符)对第三方文本引擎有效,文字渐变(Pattern colorspace)在 PDFium 下方向断言通过。透明文字渐变与渐变变换方言经裁决无输入语义来源(渲染层不产出该形态),覆盖检查器按稳定原因显式拒绝。
50
+ - 内嵌视频合成(`videoOverlay`,仍默认关闭)升级:组链经 `placeGroupChild` 折算、元素 rotate / flip / 裁剪 / 透明度 / CSS filter 与媒体间层叠在画布按 SVG 渲染一致重建;`playback.loop` 循环取模;跨页策略为元素随所属页呈现。可变帧率按时间戳取帧、60s 长视频内存有界(<60MB)、`video-overlay-frame-unavailable` 稳定复现样本(MSE 缺口媒体)均通过独立验收。
51
+ - 视频音轨(`audio`,已随 0.5.0-beta.6 发布)补齐验收证据:FFmpeg 独立测量的脉冲与画面标记同轴(偏差 ≤130ms 声明边界)、`AbortSignal` 取消与资源释放、循环音频跨页持续。
52
+ - OLE 嵌入文档官网面板补齐:嵌入 DOCX 的 run 级改字(保留相邻 run 格式)、嵌入 XLSX 的单元格样式(字体 / 填充 / 边框)与 sharedStrings 富文本入口,中英文文案与撤销语义齐全;`readEmbeddedPackage` 自 `@web-ppt/edit-core/ole` 公开导出供宿主提取嵌入文档;改写后的 DOCX / XLSX 经 LibreOffice 打开与重写往返验证(文字、颜色与 run 边界保留)。
53
+ - `@web-ppt/collab` 发布入口为 12.20KB gzip;排除 peer 的完整测试薄包为 14,333B gzip,两种口径分别核对。
54
+
55
+ ## 0.5.0 - 2026-10-29
56
+
57
+ 0.5.0 稳定版:`0.5.0-beta.7` 的全部内容转正到 `latest`,编辑线自此成为稳定能力——编辑文档模型与三层视图、模板/主题/母版/版式设计画布、图表数据与样式、批注、页面确保适合、SmartArt/OLE/墨迹内部编辑、高级渲染、PNG/SVG/批量 ZIP/可打印 HTML/图片 PDF 与实验性矢量 PDF/WebM 导出、原生 `.ppt` 生成保存、无来源富文本复制,以及 Agent `describe` 结构化读出与 `lint` 布局检查按需入口。相对 0.4.5 的逐项变化见下方 `0.5.0-beta.1` 至 `0.5.0-beta.7` 条目。
58
+
59
+ 破坏性变更核对:无 API 撤除;0.5 线新增能力均为增量或按需入口,唯一语义变化是 `elementFrameToSlideMatrix` / `elementParentToSlideMatrix` 改为沿渲染链求值(仅非等比组合内非 90° 整数倍旋转等场景结果不同,见 beta.7 条目)。
60
+
61
+ 安装:无版本号安装即得 0.5.0,八包必须同版本;`@next` 标签留给 0.6 线。
62
+
63
+ `@web-ppt/collab` 发布入口为 12.20KB gzip;排除 peer 的完整测试薄包为 14,333B gzip,两种口径分别核对。
64
+
65
+ Windows PowerPoint 真机验收经维护者决定延后,0.5.0 先行转正;四项自动门禁(含 LibreOffice 外部打开清单)全部通过,后续问题以 0.5.x 补丁版修复。
66
+
67
+ ## 0.5.0-beta.7 - 2026-10-26
68
+
69
+ ### 新增
70
+
71
+ - 新增按需 `@web-ppt/edit-core/lint` 七项布局检查与 `/lint/sampler` SVG 颜色采样协议:稳定元素定位、真实字体测量、显式资源错误、进度与取消,随包提供生成 Schema。
72
+ - 新增按需 `@web-ppt/edit-core/describe`:从当前 `EditDoc` 读出页面、设计来源、稳定元素 ID、页面外框和段落文字;支持完整 JSON 的字符 / token 预算及显式截断,随包发布由 TypeScript 类型生成的 JSON Schema。
73
+ - `presentationToVideo` 新增实验性 `videoOverlay`:导出时以 `<video>` 当前帧替代内嵌视频的海报帧,媒体起始时刻与音轨共用同一时间轴;默认关闭时产物不变。
74
+ - `savePpt` 原生写入首尾两色线性渐变(两端透明度分别保留)、十种预设虚线、五种默认尺寸箭头、图片四边矩形裁剪与形状名;图案填充、多色标或径向渐变、自定义虚线与非默认箭头尺寸仍以 `PptSaveError` 拒绝
75
+ - `@web-ppt/edit-core/ole` 新增 `setRun`(嵌入 DOCX 单个 run 改字,相邻格式保留)、`setCellStyle`(嵌入 XLSX 字体 / 填充 / 边框)与 `setCellRichText`(sharedStrings 富文本);`OleParagraph` 新增 `runs` 与表格行列 `table`,`OleCell` 新增 `styleIndex`、`style` 与 `richText`
76
+ - 实验性矢量 PDF 支持 dashed / dotted / double / wavy 文字装饰线,以及文字描边(描边轨在填色轨之下,等效 `paint-order:stroke`)。
77
+
78
+ ### 修复
79
+
80
+ - 编辑性能基准统一使用完整源码别名和同一个运行时,不再依赖尚未构建的 dist 或重复的会话 WeakMap;增加干净构建状态的真实回归。
81
+ - 组合同时带旋转与非等比缩放时不再拒绝解组:解组改按 `placeGroupChild` / `placeGroupFrame` 的渲染外框回填孩子 frame,旋转子元素的宽高按转角象限承接组缩放;非等比缩放下预设形状的渲染路径会固化为自由形状覆盖,保存写回 `custGeom`,箭头、圆角等依赖宽高比例的几何不再变形。只有需要改变旋转的框架对象(图表 / OLE 等)仍显式拒绝。
82
+ - 读取 `.ppt` 时线性渐变不再一律按竖向、前景色在上渲染:方向取自 fillAngle,首尾色按 fillAngle 的符号与 fillFocus 还原(与 LibreOffice 的 PowerPoint 兼容规则一致,焦点 40–60 为两侧对称渐变),终点透明度取自 fillBackOpacity;图片的 cropFrom* 裁剪与 wzName 形状名不再丢失。
83
+ - `presentationToVideo` 的 `videoOverlay`:内嵌视频加载失败或超时不再静默退回海报帧,改为经 `onWarning` 以稳定原因 `video-overlay-unavailable` 暴露(附页号与元素名),seek 超时或帧不可用报 `video-overlay-frame-unavailable`,导出均不中断;就绪条件改为首帧可画,避免只取到元数据时首帧停在海报上。未开启 `videoOverlay` 时产物不变。
84
+ - 修复 `.pptx` 媒体源解析只认经典 `a:audioFile` / `a:videoFile` 引用:PowerPoint 2010+ 内嵌 mp3 / mp4 等时把经典关系写成 `Target="NULL"` 外链、字节只挂在 `p14:media` 的 `r:embed` 上,此前 `MediaInfo.src` 为 `null`,player 模式不生成播放器、编辑生成保存会丢失媒体字节。现按经典引用 → `p14:media`(`r:link` 优先于 `r:embed`)逐个取第一个可解析目标:包内关系要求部件存在,外链关系要求是带 scheme 的 URL;正常文件的解析结果不变。
85
+ - 修正视频导出过期说明:`presentationToVideo` 已支持可选音轨;含内嵌媒体时 `mediaPosters: true` 在开启 `audio` / `videoOverlay` 时仍必需,拒绝信息与文档同步说明原因。
86
+ - 英语缩写中的弯撇号(`I’m`、`that’s`)不再按全角标点挤压半个 em,避免撇号与后一个字母重叠(#8)。中文句读的挤压规则不变。
87
+ - 对象动画「伸展」按 PowerPoint 子类型播放:水平展开(across)从中线向两侧长,自底部 / 顶部 / 左侧 / 右侧钉住对应边长出,不再一律从中心缩放并淡入;退场缩回同一条边。「挥舞」按 PowerPoint 的 `sin(2.5π·t)` 宽度(横向为高度)轨迹翻转并叠加淡入,中途会转到背面镜像,不再是一次 `rotateY`。
88
+ - 组合缩放只映射子元素外框:渲染时把 `chOff` / `chExt` 折算进子元素的位置、尺寸、路径与表格行列,不再对子树整体 `scale()`,描边、字号、内边距与效果半径保持物理尺寸(与 PowerPoint、LibreOffice 一致)。WPS 产出的归一化组合(子坐标空间不足 1px)不再出现千像素描边或逐字换行;普通缩放组合内的字号与描边也不再随组放大,非等比组合里的旋转子元素保持矩形。编辑器按同一份刚体外框工作:选择框、命中、框选、对齐 / 分布、吸附、裁剪、顶点与调节柄不再把非等比组合里的旋转子元素当成斜切的平行四边形;缩放与旋转在父组渲染坐标里计算后换算回模型,旋转跨过 45° 等边界时画面宽高保持不变(模型宽高随组缩放承接的轴向互换),粘贴进非等比组合同样按画面落位。`@web-ppt/core` 公开 `placeGroupChild`、`placeGroupFrame` 与 `unplaceGroupFrame`;`@web-ppt/edit-core` / `@web-ppt/editor` 公开 `renderedElement`、`renderedFrameToSlideMatrix`、`renderedContentToSlideMatrix`、`elementParentToRenderedParentMatrix`、`elementEditFrame`、`elementEditParentToSlideMatrix`、`slideToElementEditParentPoint` 与 `editFrameToModelFrame`(edit-core 另有 `groupEditChildrenToSlideMatrix`、`groupEditFrameToModelFrame`);`elementFrameToSlideMatrix` 与 `elementParentToSlideMatrix` 改为沿渲染链求值,只在非等比组合内的非 90° 整数倍旋转、或此类组合嵌套时结果不同(#7)。
89
+ - `wedgeRectCallout` / `wedgeRoundRectCallout` 按 ECMA-376 guide 求值:尾巴从尾尖所在一侧的边伸出,圆角取 `adj3`;此前底座固定在下边,尾尖朝上时尾巴穿过整个框只剩一条细线(#7 附件)。
90
+ - 非占位符形状 `p:style` 的 `fontRef` 颜色压过 presentation `defaultTextStyle` 的方案色,形状自身 `lstStyle` 与 `rPr` 仍优先;PowerPoint 默认形状里的白字不再渲染成黑字。
91
+ - 背景 `bgRef` 按 ST_StyleMatrixColumnIndex 取主题样式:`1001` 起对应 `bgFillStyleLst` 第 1 项、`1`–`999` 对应 `fillStyleLst`;此前 `idx="1001"` 被夹到列表末项,最常见的白色背景被画成主题第三档渐变。
92
+ - run 的字体栈只在文字含复杂文种(阿拉伯文、希伯来文、泰文、天城文等)时才纳入 `a:cs` 字体;此前拉丁字体缺席时 `cs`(如 WPS 常写的 Arial Black)会接管拉丁字母,标题被撑宽断行。新增公开函数 `textRunFontStack` 供编辑投影共用。
93
+ - 只写 `asvg:svgBlip`、`a:blip` 上没有位图兼容副本的图片(WPS 插入的图标)按 SVG 部件渲染,不再显示为不支持的对象。
94
+ - 有封面且源可用的音频不再叠加灰色播放标识(封面本身就是喇叭图标);`media: 'player'` 下音频不显示原生控件条,`Viewer` 点击封面播放 / 暂停,播放被拒绝时同样派发 `web-ppt-playback-error`。官网查看器改用 player 模式,放映中点击媒体不翻页。
95
+ - 等线缺席时西文改用合成字族:`local()` 引用系统 Arial 度量族,粗体 `size-adjust: 95.4%`、下划线 `74.6%`,只在页面出现等线时声明;`@web-ppt/fonts` 的替换路径对 Arimo 套用同一组参数。取代窄体回退与连续空格半字宽的处理;依据是 #8 附带的 PowerPoint 导出图逐词拟合,断行与填空线长度与 PowerPoint 一致。
96
+ - `Viewer` 执行动画时间线里的 `mediacall` 媒体调用(`playFrom` / `pause` / `stop` 等),`afterPrev` 等定时触发的音视频按时播放,切页或取消时暂停;`play()` 被浏览器拒绝时容器派发 `web-ppt-playback-error`。媒体调用步骤不再给图标留下强调动画的半透明终态(#7)。
97
+
98
+ `@web-ppt/collab` 发布入口为 12.20KB gzip;排除 peer 的完整测试薄包为 14,333B gzip,两种口径分别核对。
99
+
100
+ ## 0.5.0-beta.6 - 2026-10-10
101
+
102
+ ### 新增
103
+
104
+ - `presentationToVideo` 新增可选音轨:`audio: true` 时解析 timing 树媒体节点(`p:audio` 的音量 / 静音 / 循环 / 触发批次),按页时间轴确定性混音后以 Opus 48kHz 编码进 WebM 双轨;视频文件的 AAC 音轨同样提取混入,无音轨或解码失败经 `onWarning` 以稳定原因跳过、不阻断导出。无音频媒体时仍为无声视频,默认关闭时产物与上一版逐字节一致。公开 `WebmWriter` 音频轨与 `mixAudioClips` 混音核;`MediaInfo` 扩展 `spid` 与 `playback` 播放语义。
105
+ - 实验性矢量 PDF 的文字渐变原生支持(Pattern colorspace):任意轴线性渐变、同段多 run 各自分框(按字形墨迹范围)、下划线 / 删除线继承渐变。坐标口径经 MuPDF / poppler / CoreGraphics 三实现实测裁决;透明渐变因渲染层无输入语义暂按稳定原因回退。
106
+
107
+ ### 改进
108
+
109
+ - CJK 标点挤压补全 PowerPoint 完整规则:连续标点对、行首起始标点与行尾句读无条件收半格;断行判定仍按全角,与 PowerPoint 断行基线一致。
110
+ - 网络字体到货后的重排补齐数学布局缓存失效:`invalidateTextMeasureCaches` 公开导出,`Viewer.refresh()` 完整覆盖字体环境变化。
111
+ - 文档体系新增功能设计标准、分层依赖规范与 `.pptx` / `.ppt` 规范地图;依赖方向由 `check-architecture-boundary` 在 build 与 verify 机械审计。
112
+
113
+ ### 修复
114
+
115
+ - 修复矢量 PDF 跨页复用渐变 Pattern 时未在当前页注册资源导致的整段取色错误(单页导出不触发)。
116
+ - 修正 README 已知限制与能力矩阵的三处失真(EMF+ / OMML / 3D)及打开密码文件的能力描述。
117
+
118
+ `@web-ppt/collab` 发布入口为 12.20KB gzip;排除 peer 的完整测试薄包为 14,333B gzip,两种口径分别核对。
119
+
120
+ ## 0.5.0-beta.5 - 2026-09-10
121
+
122
+ ### 新增
123
+
124
+ - 高级文本生成保存与跨文稿复制支持公式、艺术字及已声明的高级文字效果;经典图表支持多级类别与共享工作簿同步,保留历史、恢复、协同和原生保存闭环。
125
+ - 新增实验性 `@web-ppt/core/pdf/vector` 及独立 browser 适配入口:支持范围内的文字可搜索,图形保留矢量,特殊效果按对象回退为图片并返回位置说明。
126
+ 编辑器导出窗口通过 Cordis 文件 / 字体服务提供入口,缺字体可定位并加载本机字体后重试;取消、换稿和退出等待实际资源消费结束。此版本不代表 005 整项验收完成;复杂文字效果、更多 SVG 组合、其他 PDF 阅读器及长文稿成本仍待收口。
127
+ - 新增 `@web-ppt/fonts/glyphs` 可选字体 Provider 及 HarfBuzz、Worker、FontFace 适配:验证原始容器与嵌入限制,返回定位字形、轮廓和 UTF-16 原文簇;整形器和字体字节由宿主提供。
128
+ 产品编辑器由 Cordis 文稿服务检查、应用和释放字体,提供中英文缺字诊断与本机替换;原始字体模式避免主线程重复解压,替换和安装失败会归还旧字体预算。当前字体资源用于预览及图片 / SVG / 实验性矢量 PDF 导出,不写入 PPTX。
129
+ - 可选 `@web-ppt/collab/migration` 入口为扩展字段保留带原 stamp 的 `set/del` 操作证据;来源元素或页面删除后仍可恢复。
130
+ 与默认入口共用协同会话和回滚,旧检查点缺少证据时保持未知;共享图表可按原操作裁决矛盾字段和删除,后续原操作可补齐缺失证据。
131
+ - 编辑核心可原子执行已裁决的扩展迁移凭据,保留来源操作、删除赢家和冷恢复语义;拒绝不完整来源及父子字段冲突。
132
+ 可选协同入口在远端接收和延迟消息消费时重新裁决,保留更晚本地操作;默认入口拒绝凭据,原始版本不会替换为运输消息的时钟。
133
+ 恢复日志在提交前准备声明,旧字段按原地址和顺序重放;解析器乱序释放不会复活旧实例。
134
+ - 包体积核对改为默认入口及全部相对静态分块逐文件 gzip 后求和,修正旧表漏算共享分块的问题;peer 与动态入口仍另计。
135
+ - 官网编辑产品层通过同一 Cordis 应用管理文稿、打开、新建、恢复、文件、业务工具与页面状态;页面插件持有模式、导航、历史和视口,入口仅保留启动及重试。
136
+ 新打开取消旧模板和图片选择,退出先停止页面与 SDK 编辑输入,再等待已提交文件交付,最后释放文稿、监听、观察器和弹窗;重建后命令不重复。发布 SDK 保持无框架依赖。
137
+ - 图片插入、替换和背景接口支持 `options.signal`;取消会释放待选入口,并在读取、解码及提交边界阻止迟到修改,
138
+ 避免应用退出等待文件交付期间旧工具继续写入文稿。
139
+ - 共享图表入口迁移独立数据区域的旧局部编辑,保留旧撤销历史、协同时钟及迟到消息;支持加载前复制页及
140
+ 多旧所有者/新旧数据的相等或不同字段合并。删除整张原页及中间副本后,幸存副本仍可编辑、保存和冷恢复。
141
+ 可选协同入口让复制快照继承可证明的原 set/del 版本,保留副本远端更新后的撤销重做,并裁决已加载共享入口的迟到复制。
142
+ 缺失原版本或同版本矛盾的字段、无来源记录及日志的旧裸模型、身份冲突仍保留只读,完整范围见迁移记录。
143
+
144
+ ### 验证与范围
145
+
146
+ - `@web-ppt/collab` 发布入口为 12.20KB gzip;包含相对静态分块。排除 peer 的完整测试薄包为 14,333B gzip,两种口径分别核对。
147
+ - 字体及 PDF 独立读取依赖固定版本并接入 CI / 发布环境;矢量 PDF 源码与产物契约纳入常规功能和一致性门禁。
148
+ - 保留 beta.4 的密码弹窗与 CI 稳定性修复。视频仍无音轨、内嵌视频合成和长尾格式扩展待后续;Windows PowerPoint 真机继续暂缓。
149
+ - 模块成果与下一步见[本轮交付说明](https://github.com/unStone/web-ppt/blob/master/docs/releases/0.5.0-beta.5.md)。
150
+
151
+ ## 0.5.0-beta.4 - 2026-09-09
152
+
153
+ ### 剩余功能收口
154
+
155
+ - 补齐四个产品入口的只读批注面板、PNG/SVG/ZIP 标记与打印正文;生成保存不再丢失批注,复制页独立分配批注部件和索引。
156
+ - 批注复制覆盖连续保存、撤销/重做、来源释放、外部补丁、恢复、最大索引及显式 Internal/未知 XML 扩展。
157
+ - 十页混合固件贯通七种原生现代布局、地图回退、经典数据、WAV/MP4、图片/立体和批注;新增包名产物契约及 0.8 功能完成度门禁。
158
+ - 新增按需图片效果和立体编辑,官网控件共用历史、恢复、协同与两条保存;双色调在 PNG/独立 SVG 中使用真实颜色。
159
+ - 官网与查看器遇到 ChartEx 自动按需启用原生解析,加载失败继续显示来源预览;SDK 默认行为不变。
160
+ - 新增可选画布读屏目录和 EditContext,真实 Chromium 覆盖预编辑、连续输入、撤销及加载失败回退。
161
+ - 修复经典图表系列标题与子节点顺序、零挤出重开及未加载扩展时双色调生成保存丢失。
162
+ - 补充混合内容保存旅程、公开 API 契约和迁移说明;Windows 真机按用户要求暂缓。
163
+
164
+ ### 新增
165
+
166
+ - 新增 `@web-ppt/core/chart-ex` 与显式解析 hook:树状图、旭日、直方图/Pareto、箱线、瀑布和漏斗输出原生 Schema;
167
+ 地图、歧义数据与不支持的来源保留 Office fallback。主题、母版与保存后继续沿用文稿打开时的解析配置。
168
+ SDK 默认入口不加载实现;官网按内容类型自动加载。其他类型真实 PPTX 和 Office 原生布局验收尚未完成。
169
+ - 官网增加中英文基础入口:三张页面输出静态英文镜像与双语 SEO,URL 优先于本机偏好和浏览器语言;
170
+ 原地切换保留文稿、选区和撤销历史,中文默认不下载英文词库。编辑页打开/保存/图片 ZIP、转换确认、
171
+ 模板选择、本机恢复、动态工具与无障碍文案均已接入;模板和恢复提示内可直接切换语言。
172
+ - 媒体按需入口支持 PCM WAV / MP4(含分片)、显式 HTTP(S) 外链、确定性音频图标和独立海报替换;
173
+ editor / React / Vue 和官网媒体工具转发同一 API,默认入口不加载媒体校验或图标。
174
+ 官网支持选中媒体试听、失效外链提示与按需媒体日志恢复,外链仅在点击播放时访问;切换文稿和关闭工具释放播放器。
175
+ - 经典图表新增按需数据编辑入口,支持系列、类别和数值增删改;稀疏覆盖贯通历史、恢复与协同,保存同时同步
176
+ 图表缓存和内嵌工作簿。官网数据检查器动态加载,默认入口不携带图表编辑实现。
177
+ - 主题、母版、版式与内置模板现已组成同一条公开编辑链:主题经 `exec`,母版与版式经 `execDesign`
178
+ 精确传播到依赖页,页面直设继续优先;三套模板共用历史、恢复、字段级协同与保存协议。
179
+ - 新增独立按需模板入口:极光、刊页、夜幕三套确定性配方各自物化主题、母版、五种常用版式和可编辑标题页;
180
+ 官网、无框架 editor 与 React/Vue 只转发同一目录,默认入口不加载配方或预览数据。
181
+ - 编辑模型补齐字符、自动编号和图片项目符号;结构化来源值、富文本剪贴板、格式刷、表格文字、恢复、协同
182
+ 以及补丁/生成保存使用同一套项目符号语义。
183
+ - 新增预设形状切换、DrawingML 调节值和独立 `@web-ppt/editor/adjustments` 拖柄入口;187 个预设中
184
+ 120 个含句柄的定义由固定规范源生成,未使用时不进入 editor 主包。
185
+ - 高级字符格式新增 17 种精确下划线、双删除线、高亮、字距、大小写与基线上下标;`ClearFormat` 删除
186
+ 来源直设并保留文字、动态字段、公式和链接身份。
187
+ - 新增按需入口 `@web-ppt/core/image-zip`:以稳定原页码命名批量导出动画终态 PNG,支持隐藏页策略、
188
+ 1–8 路有界并发、确定性 ZIP 元数据与逐页进度;任一页失败会携带页码原子拒绝,不返回半成品。
189
+ 它复用单页 data URI + `foreignObject` 光栅化与 `SecurityError` 原生文本回退,不引入第二套渲染器。
190
+ - 编辑模型、无框架 editor 与 React/Vue 类型入口补齐对象等距分布、替代文字、节管理和页面尺寸;四组能力
191
+ 贯通来源恢复、历史/恢复、字段级协同及保留型/生成式保存,页面尺寸采用不重排元素的“最大化”语义。
192
+ - 无框架 editor 新增仅触屏启用的细描边命中容差、双指缩放/平移和 500ms 长按上下文请求;导航结果经
193
+ adapter 与 React/Vue 公开 seam 交给宿主,不写模型、历史、恢复日志或保存内容,查看模式保留页面滚动。
194
+ - `EditorSession.toPresentation()` 公开当前编辑态只读投影,让官网可直接批量导出图片 ZIP,同时保持脏状态
195
+ 与保存点不变;资源生命周期仍由会话统一拥有。
196
+
197
+ ### 改进
198
+
199
+ - `@web-ppt/collab` 发布入口为 11.76KB gzip;契约测试使用的完整薄包在排除 peer 后为 14,288B gzip。
200
+
201
+ ### 修复
202
+
203
+ - 官网打开加密 `.pptx` / `.ppt` 时弹出密码对话框:错误密码可原地重试,正确后继续查看;core 暴露可识别的 `PasswordRequiredError`,避免界面依赖错误文案判断。
204
+ - 官网内联 CSS 后仅删除无引用产物,并等 Vite 生成预加载清单后再检查,避免共享样式缺失导致动态编辑模块打不开。
205
+ - 无封面媒体新增海报后可保存;兼容分支媒体继续保留整壳限制。生成保存不再重放旧海报关系。
206
+ - 公开 Patch 事件补齐图片资源闭包,修复接收端撤销回收后无法重做;提交及历史双向闭包超限在提交前原子拒绝,
207
+ 合并历史超限则保留独立撤销单元。
208
+ 无 OOXML 所属页的新建对象可恢复,仍被无来源海报引用的资源不可被远端删除。
209
+ - MC 兼容内容按声明命名空间与实际能力选择;真实 ChartEx 漏斗回退图恢复显示,编辑以完整兼容外壳为身份。
210
+ 移动、复制、层级、撤销与两条保存同步保留所有表示及原始数据;编辑会话保留兼容来源,原包释放后仍可生成保存,缺失或冲突的源依赖明确报错。
211
+ - 复制、外部补丁回放和恢复分配对象 ID 时计入未显示的兼容分支,避免新增形状撞 ID;OPC 写回不再因
212
+ Node Buffer 的切片共享内存而污染调用方输入。
213
+ - 0.7 集成验收打通“模板 → 主题 → 母版 → 版式 → 普通页面 → 保存重开”,并修复普通页面事务可绕过
214
+ 设计画布权限的问题;普通页面权限隔离现由唯一提交入口统一执行。
215
+ - LibreOffice 与 Windows PowerPoint 改为消费同一份 11 件 Office 清单;清单、产物字节和 PowerPoint
216
+ 报告均绑定当前提交,避免两套验收范围悄然分叉。
217
+ - 0.6 跨能力新建旅程打通表格、项目符号、预设形状、高级字符、等距分布、替代文字、节、页面尺寸、
218
+ 触屏与批量导出;恢复后分别走补丁和生成保存,再由 Chrome 与 LibreOffice 重开验证。
219
+ - 生成保存现在保留动态页码字段与替代文字;此前这些能力单独测试可用,但从空白文稿混合编辑后会拒绝
220
+ 动态字段或丢失 `cNvPr@title/@descr`。
221
+
222
+ ## 0.5.0-beta.3 - 2026-08-31
223
+
224
+ ### 新增
225
+
226
+ - `@web-ppt/edit-core` 新增稳定表格网格:任意位置插删行列、行高列宽、合并拆分与直接单元格格式都按
227
+ 稳定行列身份寻址,并通过公开 `queryTableGrid` 与纯数据命令进入历史、恢复和保存链路。
228
+ - 表格 tombstone 与完整合并真值让删除只改变可见投影;被合并覆盖的文字和格式休眠而不销毁,拆分、撤销及
229
+ 保存重开后仍恢复同一逻辑格。`@web-ppt/collab` 同步覆盖并发插删、合并和“至少保留一行一列”的确定性收敛;
230
+ 发布入口为 11.76KB gzip;契约测试使用的完整薄包在排除 peer 后为 14,288B gzip,两种口径不混用。
231
+
232
+ ### 改进
233
+
234
+ - 发布前一致性门禁统一核对版本、许可证、链接、断言数、快照数、包体积和发布包清单,并把 0.6 后续能力拆为
235
+ 可独立验收的 Wayfinder 票据。
236
+
237
+ ## 0.5.0-beta.2 - 2026-08-30
238
+
239
+ ### 新增
240
+
241
+ - 无原包文档现在可确定性生成完整 PPTX;`.ppt` 编辑后可明确另存为 `.pptx`,官网同时贯通空白新建、
242
+ 真实版式加页、保存下载与本机恢复。
243
+ - 编辑模型与无框架 DOM 层新增组合/解组、九级文本列表升降级、附录 B 快捷键、上下文工具栏、
244
+ 顶点编辑扩展和表格样式应用/写回。
245
+ - 新增可选的 `@web-ppt/collab`:字段级 LWW、分数序与确定性页序重物化把 `Editor` 补丁流接到
246
+ 可插拔 provider,支持离线幂等回放、remove-wins、远端历史 rebase 和 BroadcastChannel 双标签页。
247
+ OOXML 元素身份由 provider 分配的副本 slot 精确分区;协同包不进入单机 `editor` 入口。
248
+ 发布入口为 10.70KB gzip;契约测试使用的完整薄包在排除 peer 后为 13,023B gzip,两种口径不混用。
249
+
250
+ ### 改进
251
+
252
+ - 官网延后加载编辑引擎,样本在构建期预渲染并内联关键 CSS,同时补齐 WCAG 对比度与稳定 chunk 命名。
253
+ - 浏览器性能契约将功能失败与预算超标分离,并在检测到并行负载时只允许一次全新 Chrome 重测。
254
+
255
+ ## 0.5.0-beta.1 - 2026-08-27
256
+
257
+ ### 新增
258
+
259
+ - `@web-ppt/edit-core`、`@web-ppt/editor` 与框架适配层新增元素动画编辑闭环:稳定
260
+ `SlideId` / `ElementId` 时间线支持来源恢复、显式清空、撤销恢复、删除/复制页联动,以及
261
+ view/edit 共用的无副作用即时预览。首个安全目录覆盖入场/退场的 appear、fade、fly、wipe、zoom、
262
+ dissolve,强调 spin/grow 和 2–256 顶点运动路径;复杂 MCE、build、扩展或条件时间树会标记
263
+ `sourceReadonly`,只有调用方明确整条替换才写回;公开 `MAX_ANIMATION_STEPS=128`,保证允许写入的
264
+ 最重时间线仍能在有界解析预算内完整回读。
265
+ - 元素动画保存按 OOXML 官方 preset/filter 重建规范 `p:tnLst`,保留 timing 根属性、`bldLst`、
266
+ `extLst` 与无关 MCE,并精确回读 trigger、delay、duration、方向和路径。真实 Chrome 实测 60 元素
267
+ 启动、200 页批量提交、单页反馈 p95 为 0.8/3.5/0.4ms;确定性 5 页固件及 LibreOffice 打开验证通过。
268
+
269
+ - `@web-ppt/editor` 新增显式启用的纯浏览器自动保存:完整源字节 SHA-256 把同内容输入绑定到同一日志,
270
+ IndexedDB 以串行批次原子追加恢复帧,并按分块数、总估算字节、记录数和保留期压缩清理。`openEditor`
271
+ 在解析和挂载前完成 restore/discard/cancel 决策,clean 尾帧不提示;adapter 与 React/Vue 共用
272
+ `recovering + onRecovery` seam;原子代际占位拒绝旧页面在 discard 后复活日志。真实 Chrome 中 50MB
273
+ 指纹为 42.2ms,1,000 帧写入/关闭重开恢复为 131.5/14.9ms,压缩后 10 个分块,同步提交增量 0.000ms。
274
+
275
+ - `@web-ppt/edit-core` 新增版本化 `RecoveryFrame`、`Editor.subscribeRecovery()` 与
276
+ `restoreRecoveryFrames()`:事务、撤销/重做、非历史写入、选区、保存点、图片资源和身份水位都能按
277
+ 严格序号写成纯 JSON。会话图片 URL 在 patch 中先变成保留 token,资源闭包缺失会原子拒绝;回放先在
278
+ 写时复制模型上完整验真,并拒绝身份水位倒退,坏尾帧不会污染目标;恢复保留选区与脏状态,
279
+ 但不伪造旧历史。未订阅时普通编辑零日志深拷贝。210 页 / 50MB 基准中,1,000 帧为 316.3KB,
280
+ JSON 往返 1.6ms、回放 269.9ms;`@web-ppt/editor.openEditor()` 可在挂载前透传同一日志。
281
+
282
+ - `renderSlideToSvg` 新增 `RenderOptions.idPrefix`。指定后,同一页与同一前缀会生成
283
+ 逐字节相同的 SVG,供编辑器增量更新和确定性比较使用;省略时仍维持全局唯一 id,
284
+ 主视图与缩略图同时挂载不会发生 defs 冲突。
285
+ - 新增 `renderElementToSvg(element, options) → { markup, defs }`。编辑器可用每元素唯一的
286
+ 稳定 `idPrefix` 只重渲脏元素,并在同一次 DOM 更新中替换它自己的节点与 defs;
287
+ 整页与元素入口复用同一分发和两条文本路径,不存在第二套近似渲染器。
288
+ - 新增无 DOM 的 `renderTextBodyToHtml(text, width, height, options)`。屏幕预览和后续
289
+ contenteditable 覆盖层共用同一份段落、run、CJK 标点、分栏与 autofit 排版;默认输出
290
+ 可反解的编辑标记,并隔离危险链接协议,原生 SVG 文本导出路径保持独立。
291
+ - 新增纯函数 `layoutText(text, width, height, options)`。它与原生 `<text>` 输出共用断行、
292
+ CJK 挤压、分栏、行距及 autofit,并公开段落/run 身份、UTF-16 字符区间与光标停靠点,
293
+ 供 Safari engine 模式命中选区;竖排返回仿射变换,公式保持原子,测量器可注入。
294
+ - `renderTextBodyToHtml` 新增 `{ layout: 'engine' }`:直接把 `layoutText()` 的视觉行与源 UTF-16
295
+ 半开区间序列化为绝对定位、禁止二次断行的可编辑 HTML。`@web-ppt/editor` 的 `textMode: 'svg'`
296
+ 以及 `auto` Safari 降级现在让静态 SVG 与 `contenteditable` 共用这组行盒;硬换行、空 run/段、
297
+ 项目符号、RTL、竖排、分栏、公式和裸 autofit 在进入、输入、提交与退出时保持同一布局。真实
298
+ Chrome 的 2,000 字符完整上屏多轮 p95 为 8.3–17.8ms、行盒最大偏差 0.009px;editor 入口为
299
+ 27.46KB gzip。
300
+ - `parse` 新增完全可选的 `edit` / `keepPackage`:前者保留页与元素的 OOXML 回写锚点、
301
+ 占位符身份,后者通过 `Presentation.package` 保留原始 ZIP 与解压 part。默认解析的
302
+ 对象形状和资源生命周期不变;`dispose()` 会同时释放保留的原包。
303
+ - `.pptx` 与 `.ppt` 的编辑解析会额外保留预设形状的 `preset + adj` 语义(OOXML 含
304
+ 版式/母版继承),并新增纯函数 `resolveGeomPath`;编辑投影改变宽高后可重新求值路径,
305
+ 不再复用解析期烘焙的几何。
306
+ - 新增无 DOM、无框架的 `@web-ppt/edit-core`:把解析结果转换为带稳定身份与分数 z 序的
307
+ 扁平 `EditDoc`,以 `src` / `ovr` 分离源值和用户改动,并通过 `toSlide` 投影回现有
308
+ 渲染 Schema。元素、组祖先和页面缓存按修改路径精确失效;图表、SmartArt、OLE、
309
+ 墨迹与媒体只开放框架级变换,内部派生节点不可误编辑。
310
+ - `@web-ppt/edit-core` 新增无 DOM 的 `Editor`、可 JSON 序列化的 `SetXfrm` 与双向 patch。
311
+ 事务失败整体回滚;撤销/重做恢复选区,同路径改动按 500ms 分组,远端 origin 不进入本地历史,
312
+ 默认保留 200 组 / 8MB。固定种子 200 条命令通过撤销、重做与 JSON 回放全等,500 条非法命令
313
+ 不污染文档;210 页实测 200 组撤销/重做含脏页重渲均约 0.5ms/次,历史约 64KB。
314
+ - 新增按需入口 `@web-ppt/edit-core/xml`:无 DOM 的保留型 XML 树支持 UTF-8 / UTF-16
315
+ 字节回环、限定名与 namespace URI 查询、属性最小改写和 OOXML sequence 有序插入;声明、
316
+ 注释、PI、前缀、属性顺序、自闭合形态及 `AlternateContent` 不被重建。默认编辑模型入口为
317
+ 8.28KB gzip,保存期 XML 入口独立为 7.14KB gzip。
318
+ - 新增按需入口 `@web-ppt/edit-core/opc`:无修改保存复用原始字节;脏 part 定点重压,净条目的
319
+ 本地头、extra field 与压缩流逐字直通。新增/删除 part、重复保存和确定性输出均有契约;zip64、
320
+ 数据描述符、存档注释、加密条目、未知压缩与多磁盘格式会给出原因并整包重压。入口为 4.27KB gzip,
321
+ 210 页 / 50.6MB 修改并序列化 3 页后的实测保存为 84.0ms;保存结果可随文档或独立释放。
322
+ - 新增 M0 自动门禁:29 份固件(含吸附、加密 OOXML、RC4 `.ppt` 与 hardcases)的只读链路和
323
+ 编辑投影分别在独立进程渲染,两条文本路径共 232 对原始 SVG 指纹必须完全一致;
324
+ 210 页基准同时强制检查默认路径零编辑状态、编辑常驻内存增量、文本行盒与提交重渲预算。
325
+ - `@web-ppt/editor` 在编辑模式直接消费浏览器 SVG 命中:组默认选外层,双击 / Escape
326
+ 逐层进退,Alt+点击按 `elementsFromPoint` z 序循环;选择只更新交互层,多视图各自拥有监听器。
327
+ - `@web-ppt/editor` 公开元素本地 / 幻灯片 / 屏幕坐标变换,并为普通、旋转翻转和嵌套组元素绘制
328
+ OBB、8 个缩放柄与旋转柄。手柄尺寸不随 zoom 变化;真实 Chrome 三档缩放的最大矩阵偏差为
329
+ 0.000px,9 个手柄的位置与尺寸最大偏差同为 0.000px,60 元素页完整选择框上屏 p95 为 0.100ms。
330
+ - `@web-ppt/editor` 支持主指针拖动单选与多选:3px 阈值后以 pointer capture 和 rAF 合并幽灵帧,
331
+ 松手只提交一个可撤销 `SetXfrm` 事务;嵌套旋转翻转组按各自父空间换算。取消、丢失捕获、切页、
332
+ view 模式和销毁均不写模型。真实 Chrome 嵌套幽灵偏差 0.000px,60 元素拖动帧 p95 0.100ms。
333
+ - `@web-ppt/editor` 绑定 8 个恒屏幕尺寸缩放柄,支持单选/多选、旋转翻转与嵌套组、Shift 等比、
334
+ Alt 中心缩放及过锚翻面;移动与缩放共用可回收的 pointer capture/rAF 状态机,松手一次提交并可
335
+ 精确保存。真实 Chrome 三档 zoom 的手柄/锚点最大偏差 0.009px,缩放帧 p95 0.100ms;60 个
336
+ 45° 元素的近奇异多选缩放 p95 0.600ms。
337
+ - `@web-ppt/editor` 绑定旋转柄:单选按元素父空间处理自身翻转与多层旋转/翻转组,多选围绕共同 AABB
338
+ 中心同步旋转;角度跨 ±180° 连续累计,Shift 可动态吸附 15°,松手以一个事务精确保存。真实 Chrome
339
+ 三档 zoom 的嵌套/多选幽灵最大偏差为 0.000/0.009px,60 元素旋转帧 p95 0.300ms,可信 pointer
340
+ capture 与撤销门禁通过。
341
+ - `@web-ppt/editor` 的移动手势新增屏幕 6px 吸附:画布中线/边缘、显式页边距、同组兄弟边缘/中线与
342
+ 横纵等距按稳定优先级逐轴裁决;`Ctrl` 可动态关闭,视图也可用 `snapping` / `setSnapping()` 全局切换。
343
+ 对齐线和双向等距箭头只进入 interaction SVG,取消零提交。真实 Chrome 三档 zoom、缩放组和等距线
344
+ 最大偏差均为 0.000px,60 元素吸附帧 p95 0.200ms,可信 pointer capture 与撤销门禁通过;editor
345
+ 发布入口实测为 15.87KB gzip。
346
+
347
+ ## 0.4.5
348
+
349
+ ### 修复
350
+
351
+ - **`@web-ppt/viewer-core` 与 `@web-ppt/fonts` 的 npm 页面在 0.4.4 里仍是中文**。
352
+ 包内容是对的(tarball 里躺着英文版),错的是 npm 用来渲染页面的那份元数据。
353
+
354
+ 原因是 npm 认哪个文件当 README 并不看「是不是叫 README.md」:
355
+ `@npmcli/package-json` 用 `{README,README.*}` 去 glob 再取第一个像 markdown
356
+ 的命中,而 0.4.4 引入的 `README.zh-CN.md` 同样匹配 `README.*`,实测还排在
357
+ `README.md` 前面。`files` 只挡住了 tarball,挡不住元数据。
358
+
359
+ 本地化 README 改用连字符(`README-zh-CN.md`),并在 `sync-package-docs.mjs`
360
+ 里加了守卫:包目录里出现 `README.*` 旁支就直接让发布失败。
361
+
362
+ ## 0.4.4
363
+
364
+ 本版没有代码改动,只动文档——但 npm 上看到的东西全变了,所以值得单独发一版。
365
+
366
+ ### 变更
367
+
368
+ - **npm 页面改英文**。三个包的 README 与 `description` 之前全是中文,
369
+ 而 npm 的读者以国际开发者为主,等于最有说服力的能力矩阵对他们不存在。
370
+ 现在 `@web-ppt/core` 的 npm 页取仓库的 `README.en.md`,另两个包各自
371
+ 维护英文版,中文移到同目录的 `README.zh-CN.md`,两边顶部互链。
372
+
373
+ GitHub 首页仍是中文,两个入口各自面向自己的人群。
374
+
375
+ - **README 首屏补了演示 GIF 与定位段**。GIF 由 `npm run demo-gif` 录制,
376
+ 录的是仓库自己生成的 `fixtures/showcase.pptx`,引擎改了重跑即可,
377
+ 不是一次性的手工录屏。
378
+
379
+ ## 0.4.3
380
+
381
+ ### 变更
382
+
383
+ - **静态画面改回动画终态**,撤销 0.4.1 里改成初始态的那次调整。
384
+
385
+ 对齐 PowerPoint 的模型:普通视图与缩略图看到的是「这一页演完的样子」,
386
+ 按 F5 进入演示才从第一步开始建。所以**进全屏时画面跳一次是预期行为**,
387
+ 不是 bug —— 0.4.1 当时是把这个跳变当成缺陷去消除的,方向错了。
388
+
389
+ 「不能简单摊开全画」这条不变:一页里入场与退场的元素属于不同时刻,
390
+ 全画出来等于把几帧叠在一起。全员退场的收尾页终态是空白,那种情况仍然
391
+ 退回全部可见。
392
+
393
+ ## 0.4.2
394
+
395
+ ### 修复
396
+
397
+ - **动画目标是「组」时,整组藏不住**。`visibility` 和 `display:none` 不一样:
398
+ 它虽然继承,但后代显式写 `visible` 会把祖先的 `hidden` 顶掉。查看器原本给
399
+ 隐藏集之外的每个元素都写死 `visible`,于是组藏了、组里每个形状却各自写着
400
+ `visible`,整组照常显示——看着就像「预览显示的还是动画终态」。
401
+
402
+ 现在不在隐藏集里就清空这条声明,让继承自然生效。之前只在动画目标为叶子形状
403
+ 的样本上验过,那种结构没有子元素,所以一直没暴露。
404
+
405
+ ## 0.4.1
406
+
407
+ 全是中文排版的修正。四处都由同一份小学课件暴露出来,每一处都拿 PowerPoint
408
+ 自己写进文件的 `spAutoFit` 框高做的判据 —— 那个高度是它按实际行数算完存下来的,
409
+ 等于文件自己作证该排几行。
410
+
411
+ ### 修复
412
+
413
+ - **CJK 标点挤压**。汉字和全角标点都占一整格,但标点的墨迹只占半格
414
+ (`,` `。` 的墨在左半格,`(` `《` 在右半格)。一行放不下时 PowerPoint 会把
415
+ 这些空半格挤掉,我们没做,于是多断出一行。规则取「放不下才挤」:放得下时
416
+ 保持全角。两条渲染路径算同一笔账——原生 `<text>` 用逐字符 `dx`,
417
+ HTML 用负边距,都与字体无关,不依赖字体是否提供 `halt` 半角替换字形。
418
+
419
+ 顺带一句:这类问题**换字体救不了**。所有中文字体的方格一样大,黑体、
420
+ PingFang、思源黑体量出来完全相同
421
+ - **量不到字时的中文估宽**。回退估算原本把每个字符都当 0.55em,中文因此窄掉
422
+ 将近一半。Node / jsdom 里恒走这条路,于是**自动缩放对中文一直缩不够**——
423
+ autofit 固件实测旧值排出来 204px、框只有 140px,仍然溢出;按全角整格算
424
+ 之后是 146px
425
+ - **百分比行距的基准**。`lnSpc/spcPct` 是「单倍行距」的百分比,而单倍行距是
426
+ **字体的行高**(≈1.2em),不是字号。把 150% 直接当 CSS `line-height:1.5` 用,
427
+ 每行矮两成。中文文档里 1.5 倍行距是标配,影响面不小;行距偏紧还会连累
428
+ 自动缩放与居中 / 底对齐文本的位置
429
+ - **静态画面改取动画初始态**(原本取终态)。缩略图、主视图、进演示模式后的
430
+ 第一帧现在是同一个画面,不再「先把这页演完、再从头演一遍」。
431
+ 「不能全部画出来」的理由不变:一页里入场与退场的元素属于不同时刻,摊开画
432
+ 等于把几帧叠在一起。例外从「全员退场的收尾页」换成「整页都带入场动画的
433
+ 封面页」——那种情况初始态一片空白,退回全部可见
434
+
435
+ ### 其它
436
+
437
+ - 测试:1848 → **1873 项断言**,快照 160 → 162 个。`sample-autofit.pptx` 补一页
438
+ 把百分比行距与绝对行距放在一起对照——之前 17 个固件里没有一个用过
439
+ `spcPct`,这条路径从没被走过
440
+ - `@web-ppt/viewer-core` 与 `@web-ppt/fonts` 本版无改动,跟随版本号对齐
441
+
442
+ ## 0.4.0
443
+
444
+ 本版围绕**字体**:嵌入字体此前从来没有真正生效过,替换字体则完全没有。
445
+ 顺带修掉一个让静态画面把动画的几帧叠在一起的问题。
446
+
447
+ ### 新增
448
+
449
+ - **嵌入字体真的能用了**。PowerPoint 的 `ppt/fonts/*.fntdata` 不是裸 TTF,是 EOT 容器,
450
+ 而且实测**全部**开着 MTX 压缩(POI 语料 6/6、ORCID 样本 10/10)。此前把这段字节
451
+ 原样当 `font/ttf` 塞进 `@font-face`,浏览器一个都不认,只在控制台留一行
452
+ `invalid sfntVersion`。现在 core 自己剥容器(含异或混淆),MTX 解压走
453
+ `setFontDecoder()` 注入——官网接的是 [`mtx-decompressor`](https://www.npmjs.com/package/mtx-decompressor) 的 `eotToTtf`,
454
+ core 本身仍然只依赖 fflate。解不出来就跳过这个字体,不再塞浏览器注定拒绝的字节
455
+ - **`collectFonts(slides)`**:统计若干页用到哪些字体、每个字体要渲染哪些字。
456
+ 纯函数、只依赖 `types.ts`,`.ppt` 链路同样适用,Worker 里能跑
457
+ - **新包 [`@web-ppt/fonts`](packages/fonts)**(gzip 2.8KB,**包里零字节字体**):
458
+ 文件没带字体、本机也没装时的兜底。拉丁换**度量兼容**的免费字体
459
+ (Calibri→Carlito、Arial→Arimo 等,前进宽度逐字相等,断行与 PowerPoint 对齐),
460
+ 中文换思源系 / 霞鹜文楷。切片指向钉死版本的 fontsource,按 `unicode-range` 只下用到的那几片
461
+ - **`Viewer.refresh()`**:重渲当前页而不动页码与动画进度。网络字体到货后必须重渲——
462
+ 排版是同步的、加载是异步的,首帧一定是按回退字体断的行
463
+
464
+ ### 修复
465
+
466
+ - **静态渲染取动画终态,而不是「全部可见」**。一页里入场与退场的元素属于不同时刻,
467
+ 全画出来等于把几帧叠在一起:ORCID 那份样本的第 7 页三段文字本该逐条替换,
468
+ 叠起来一个字都读不出。终态会清空整页时(全员退场的收尾页)退回全部可见
469
+ - **run 没写 `a:latin` 时落到主题的 minorFont**。ECMA-376 的继承链最后一站就是
470
+ `fontScheme/minorFont`,此前直接当「没有字体」,渲染掉到 CSS 通用回退上,
471
+ 字宽与 PowerPoint 对不齐。真实文件大多在母版 txStyles 里写了 `+mn-lt` 所以少见,
472
+ 但这是「断行对不齐」的一个隐藏来源
473
+ - 字体栈去重:主题的 ea 字体常常正好是回退列表里的 `PingFang SC`,之前会拼出重复项
474
+
475
+ ### 其它
476
+
477
+ - 测试:1689 → **1848 项断言**,快照 158 → 160 个;新增 `sample-embedfont.pptx`
478
+ 覆盖嵌入字体的四种容器形态(未压缩 EOT / 未压缩+异或 / MTX 压缩 / 裸 TTF)
479
+ - `@web-ppt/core` gzip 84KB 不变;`@web-ppt/viewer-core` 6.8KB → 7.4KB
480
+ - 两个既有包的 peer 依赖同步升到 `^0.4.0`
481
+
482
+ ## 0.3.0
483
+
484
+ 本版把 ECMA-376 的几处主要缺口补齐,并首次拿 **Apache POI 的 242 个公开测试文件**跑批验证:
485
+ 排除 22 个故意损坏的模糊测试样本后,真实文件成功率 **210/220 = 95.5%**。
486
+
487
+ ### 新增
488
+
489
+ - **预设几何补齐到规范全集**:163 → **187 个**。此前缺的 24 个走的是
490
+ `PRESETS[name] ?? PRESETS.rect`,会**静默画成矩形**,不报错也不进 `unsupported`——
491
+ `accentCallout*` 这类常见形状出现即无声画错
492
+ - **运动路径动画**:`p:animMotion@path` 此前从未被读取,`presetClass="path"` 一律退化成淡入。
493
+ 现按弧长等距重采样成位移点,播放层走 linear 缓动(PowerPoint 的路径动画是匀速)
494
+ - **切换效果 21 → 41 种**:补上 PowerPoint 2010+ 的全部 19 种 p14 扩展,以及 p159 的
495
+ **morph**——按形状 id 在前后两页之间配对做几何补间,不是淡入淡出
496
+ - **数学公式 OMML 真排版**:此前只把 `m:t` 拼成一行文字(分式变成 `a+b/2c`)。
497
+ 现做完整箱模型排版,覆盖分式 / 根式 / 上下标 / 大算符 / 定界符 / 矩阵 / 重音 / 极限等 12 类结构
498
+ - **加密文档**:
499
+ - `.pptx` 标准加密(AES-ECB)与敏捷加密(AES-CBC 分段)
500
+ - `.ppt` 老式 RC4 CryptoAPI(40 / 56 / 128 位)
501
+ - 用法 `parse(input, { password })`;密码错误抛 `WrongPasswordError`,与「文件损坏」区分开
502
+ - **PICT(Apple QuickDraw)解码器**:Mac 版 PowerPoint 存的 OLE 预览与图片此前只能是裂图
503
+ - **SmartArt 无缓存 drawing part 时自行排布**:python-pptx / Google Slides 导出的文件
504
+ 常常只写数据与版式定义,此前只能得到灰色占位框。现支持线性 / 循环 / 金字塔 / 层级 /
505
+ 蛇形 / 辐射六种布局族
506
+
507
+ ### 修复
508
+
509
+ - `borderCallout2` 的引线是三点**开放**子路径,会被 `fill` 补成实心楔形——引线画成了黑三角
510
+ - 后台标签页的 `document.timeline` 是暂停的,`Animation.finished` 永不 resolve,
511
+ 切换的收尾逻辑(移除旧图层)就永远不执行,配合自动换片会让图层持续堆积
512
+ - OLE 预览优先读 `p:oleObj` 内嵌的 `p:pic`(Office 2010+ 的主要写法),旧式 VML 快照作为后备
513
+
514
+ ### 其它
515
+
516
+ - `@web-ppt/core` gzip 68KB → 84KB(新增的解码器与公式排版;加密与 PICT 走 hook 注入,可 tree-shake)
517
+ - 测试:1298 → **1689 项断言**,快照 142 → 158 个
518
+ - `@web-ppt/viewer-core` 的 peer 依赖同步升到 `^0.3.0`
519
+
520
+ ## 0.2.0 及更早
521
+
522
+ 见 [提交历史](https://github.com/unStone/web-ppt/commits/master)。
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 unStone
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,16 @@
1
- # Temporary Holding Version
1
+ # @web-ppt/agent-mcp
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Local-only Model Context Protocol stdio server and Agent Skill for deterministic PowerPoint reading, editing, rendering and saving. No model, HTTP server, remote transport or bundled font bytes are included.
4
+
5
+ The package reuses `@web-ppt/edit-core/agent` for all document operations. Files are read and written only at paths supplied by the caller, and saves use a same-directory temporary file followed by an atomic rename.
6
+
7
+ ```bash
8
+ npm i @web-ppt/core@next @web-ppt/edit-core@next @web-ppt/agent-mcp@next
9
+ web-ppt-agent
10
+ ```
11
+
12
+ The `skills/web-ppt` directory contains the Agent Skill with the read → edit → lint → render loop and save guidance. See [implementation plan](https://github.com/unStone/web-ppt/blob/master/docs/design/agent-mcp.md).
13
+
14
+ ## License
15
+
16
+ MIT.
@@ -0,0 +1,6 @@
1
+ import { A as a, a as c, c as n } from "./server-IR4Cfdfl.js";
2
+ export {
3
+ a as AgentMcpError,
4
+ c as AgentMcpSessionManager,
5
+ n as createAgentMcpServer
6
+ };
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env node
2
+ import { readFile as c } from "node:fs/promises";
3
+ import { dirname as a, resolve as p } from "node:path";
4
+ import { pathToFileURL as f } from "node:url";
5
+ import { StdioServerTransport as w } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ import { c as l, l as s } from "./server-IR4Cfdfl.js";
7
+ async function i(r, e, o) {
8
+ if (e === void 0) return;
9
+ if (typeof e != "string" || !e) throw new Error(`${o} 模块路径必须是非空字符串`);
10
+ const t = s(p(r, e)), n = await import(f(t).href);
11
+ if (typeof n[o] != "function") throw new Error(`${t} 未导出 ${o} 函数`);
12
+ return n[o];
13
+ }
14
+ async function m(r) {
15
+ if (!r.length) return {};
16
+ if (r.length !== 2 || r[0] !== "--config") throw new Error("用法:web-ppt-agent [--config <本地 JSON 文件>]");
17
+ const e = s(r[1]), o = JSON.parse(await c(e, "utf8"));
18
+ if (!o || typeof o != "object" || Array.isArray(o)) throw new Error("CLI 配置必须是 JSON 对象");
19
+ const t = a(e);
20
+ return {
21
+ measureText: await i(t, o.measureModule, "measureText"),
22
+ rasterize: await i(t, o.rasterizerModule, "rasterize")
23
+ };
24
+ }
25
+ async function d() {
26
+ const r = await m(process.argv.slice(2)), { server: e, manager: o } = l(r), t = new w();
27
+ t.onclose = () => {
28
+ o.dispose();
29
+ };
30
+ const n = () => {
31
+ t.close().finally(() => o.dispose());
32
+ };
33
+ process.once("SIGINT", n), process.once("SIGTERM", n), await e.connect(t), console.error("web-ppt-agent 已通过 stdio 就绪");
34
+ }
35
+ d().catch((r) => {
36
+ console.error(`web-ppt-agent 启动失败:${r instanceof Error ? r.message : String(r)}`), process.exitCode = 1;
37
+ });
@@ -0,0 +1,2 @@
1
+ export declare function localPath(value: unknown): string;
2
+ export declare function atomicWrite(path: string, bytes: Uint8Array): Promise<void>;
@@ -0,0 +1,4 @@
1
+ export { AgentMcpSessionManager } from './session';
2
+ export { createAgentMcpServer } from './server';
3
+ export { AgentMcpError } from './types';
4
+ export type { AgentMcpOpenResult, AgentMcpOptions, AgentMcpRenderResult, AgentMcpSaveResult } from './types';
@@ -0,0 +1,221 @@
1
+ import { Server as g } from "@modelcontextprotocol/sdk/server/index.js";
2
+ import { ListToolsRequestSchema as v, CallToolRequestSchema as b } from "@modelcontextprotocol/sdk/types.js";
3
+ import { openAgentSession as S, listAgentTools as $ } from "@web-ppt/edit-core/agent";
4
+ import { open as k, rename as q, rm as A, readFile as E } from "node:fs/promises";
5
+ import { disposeOpcPackage as I } from "@web-ppt/edit-core/opc";
6
+ import { serializeEditDoc as x } from "@web-ppt/edit-core/save";
7
+ import { resolve as P, dirname as _, basename as M } from "node:path";
8
+ class l extends Error {
9
+ constructor(e, t) {
10
+ super(t), this.reason = e, this.name = "AgentMcpError";
11
+ }
12
+ }
13
+ let R = 0;
14
+ function h(s) {
15
+ if (typeof s != "string" || !s.trim())
16
+ throw new l("invalid-path", "文件路径必须是非空字符串");
17
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(s))
18
+ throw new l("invalid-path", `只接受本地文件路径,收到 URL:${s}`);
19
+ return P(s);
20
+ }
21
+ async function w(s, e) {
22
+ const t = `${_(s)}/.${M(s)}.web-ppt-${process.pid}-${++R}.tmp`;
23
+ let n;
24
+ try {
25
+ n = await k(t, "wx"), await n.writeFile(e), await n.sync(), await n.close(), n = void 0, await q(t, s);
26
+ } catch (o) {
27
+ throw n && await n.close().catch(() => {
28
+ }), await A(t, { force: !0 }).catch(() => {
29
+ }), new l("save-failed", `写入 ${s} 失败:${o instanceof Error ? o.message : String(o)}`);
30
+ }
31
+ }
32
+ class T {
33
+ constructor(e = {}) {
34
+ this.options = e, this.sessions = /* @__PURE__ */ new Map(), this.sequence = 0, this.active = !0;
35
+ }
36
+ async open(e) {
37
+ this.assertActive();
38
+ const t = h(e);
39
+ let n;
40
+ try {
41
+ n = await E(t);
42
+ } catch (i) {
43
+ throw new l("open-failed", `读取 ${t} 失败:${i instanceof Error ? i.message : String(i)}`);
44
+ }
45
+ const o = `session-${++this.sequence}`, a = new AbortController();
46
+ let r;
47
+ try {
48
+ r = await S(n, {
49
+ ...this.options,
50
+ idPrefix: `mcp-${o}-`,
51
+ lint: { ...this.options.lint, signal: a.signal }
52
+ });
53
+ } catch (i) {
54
+ throw new l("open-failed", `打开 ${t} 失败:${i instanceof Error ? i.message : String(i)}`);
55
+ }
56
+ if (!this.active)
57
+ throw r.dispose(), new l("session-disposed", "会话管理器已经释放");
58
+ return this.sessions.set(o, { session: r, controller: a, queue: Promise.resolve(), closing: !1 }), { sessionId: o, path: t };
59
+ }
60
+ async call(e, t, n) {
61
+ return this.run(e, (o) => o.call(t, n));
62
+ }
63
+ async save(e, t) {
64
+ const n = h(t);
65
+ return this.run(e, async (o) => {
66
+ const { editor: a, doc: r } = o, i = a.captureSavepoint(), p = r.package && !r.package.disposed ? "patch" : "generated", c = await x(r);
67
+ return c.package !== r.package && I(c.package), await w(n, c.bytes), i(), { ok: !0, value: {
68
+ bytes: Buffer.from(c.bytes).toString("base64"),
69
+ path: n,
70
+ saveMode: p,
71
+ packageMode: c.mode,
72
+ fallbackReason: c.fallbackReason,
73
+ preservedEntries: c.preservedEntries,
74
+ rewrittenEntries: c.rewrittenEntries
75
+ } };
76
+ });
77
+ }
78
+ async render(e, t, n) {
79
+ const o = n === void 0 ? void 0 : h(n);
80
+ return this.run(e, async (a) => {
81
+ const r = await a.call("render", t);
82
+ if (!r.ok || o === void 0) return r;
83
+ const i = r.value.format === "svg" ? new TextEncoder().encode(r.value.data) : Buffer.from(r.value.data, "base64");
84
+ return await w(o, i), { ok: !0, value: { ...r.value, path: o } };
85
+ });
86
+ }
87
+ async close(e) {
88
+ const t = this.entry(e);
89
+ t.closing = !0, t.controller.abort();
90
+ const n = t.queue.then(() => (t.session.dispose(), this.sessions.delete(e), { closed: !0 }));
91
+ return t.queue = n.then(() => {
92
+ }, () => {
93
+ }), n;
94
+ }
95
+ async dispose() {
96
+ if (!this.active) return;
97
+ this.active = !1;
98
+ const e = [...this.sessions.values()];
99
+ for (const t of e)
100
+ t.closing = !0, t.controller.abort();
101
+ await Promise.allSettled(e.map((t) => t.queue));
102
+ for (const t of e) t.session.dispose();
103
+ this.sessions.clear();
104
+ }
105
+ entry(e) {
106
+ this.assertActive();
107
+ const t = this.sessions.get(e);
108
+ if (!t || t.closing) throw new l("target-not-found", `找不到打开的会话:${e}`);
109
+ return t;
110
+ }
111
+ run(e, t) {
112
+ const n = this.entry(e), o = n.queue.then(() => {
113
+ if (!this.active || n.closing) throw new l("session-disposed", `会话已经关闭:${e}`);
114
+ return t(n.session);
115
+ });
116
+ return n.queue = o.then(() => {
117
+ }, () => {
118
+ }), o;
119
+ }
120
+ assertActive() {
121
+ if (!this.active) throw new l("session-disposed", "会话管理器已经释放");
122
+ }
123
+ }
124
+ function y(s, e) {
125
+ let t = s;
126
+ for (; typeof t.$ref == "string"; )
127
+ t = t.$ref.split("/").slice(1).reduce((r, i) => r[i], s);
128
+ const n = { ...t.properties, sessionId: { type: "string" } };
129
+ (e === "save" || e === "render") && (n.path = { type: "string" });
130
+ const o = /* @__PURE__ */ new Set([...t.required ?? [], "sessionId"]);
131
+ return e === "save" && o.add("path"), {
132
+ ...t,
133
+ type: "object",
134
+ properties: n,
135
+ required: [...o],
136
+ ...s.definitions ? { definitions: s.definitions } : {}
137
+ };
138
+ }
139
+ function N() {
140
+ const s = [{
141
+ name: "ppt_open",
142
+ description: "打开本地 PPTX / PPT 并创建编辑会话",
143
+ inputSchema: {
144
+ type: "object",
145
+ properties: { path: { type: "string" } },
146
+ required: ["path"],
147
+ additionalProperties: !1
148
+ }
149
+ }];
150
+ for (const e of $()) {
151
+ const t = e.name === "save" ? "save" : e.name === "render" ? "render" : "call";
152
+ s.push({
153
+ name: `ppt_${e.name}`,
154
+ description: e.description,
155
+ inputSchema: y(e.inputSchema, t)
156
+ });
157
+ }
158
+ return s.push({
159
+ name: "ppt_close",
160
+ description: "关闭会话并释放文档资源",
161
+ inputSchema: y({ type: "object", additionalProperties: !1 }, "close")
162
+ }), s;
163
+ }
164
+ function f(s) {
165
+ const e = s;
166
+ return {
167
+ isError: !0,
168
+ content: [{ type: "text", text: JSON.stringify({
169
+ ok: !1,
170
+ error: {
171
+ reason: typeof e?.reason == "string" ? e.reason : "tool-failed",
172
+ message: typeof e?.message == "string" ? e.message : String(s),
173
+ commandIndex: typeof e?.commandIndex == "number" ? e.commandIndex : null,
174
+ elementId: typeof e?.elementId == "string" ? e.elementId : null,
175
+ slideNumber: typeof e?.slideNumber == "number" ? e.slideNumber : null
176
+ }
177
+ }) }]
178
+ };
179
+ }
180
+ function O(s) {
181
+ if (!s || typeof s != "object" || Array.isArray(s)) throw new l("invalid-input", "工具参数必须是对象");
182
+ return s;
183
+ }
184
+ function m(s) {
185
+ if (typeof s.sessionId != "string" || !s.sessionId) throw new l("invalid-input", "sessionId 必须是非空字符串");
186
+ return s.sessionId;
187
+ }
188
+ function J(s = {}) {
189
+ const e = new T(s), t = new g({ name: "web-ppt-agent", version: "0.8.0-beta.1" }, { capabilities: { tools: {} } }), n = N();
190
+ return t.setRequestHandler(v, async () => ({ tools: n })), t.setRequestHandler(b, async (o) => {
191
+ try {
192
+ const a = o.params.name, r = O(o.params.arguments ?? {});
193
+ let i;
194
+ if (a === "ppt_open") i = await e.open(r.path);
195
+ else if (a === "ppt_close") i = await e.close(m(r));
196
+ else if (a === "ppt_save") {
197
+ const p = await e.save(m(r), r.path);
198
+ if (!p.ok) return f(p.error);
199
+ const { bytes: c, ...d } = p.value;
200
+ i = d;
201
+ } else if (a === "ppt_render") {
202
+ const { sessionId: p, path: c, ...d } = r, u = await e.render(m(r), d, c);
203
+ if (!u.ok) return f(u.error);
204
+ i = u.value;
205
+ } else if (a.startsWith("ppt_")) {
206
+ const p = a.slice(4), { sessionId: c, ...d } = r, u = await e.call(m(r), p, d);
207
+ if (!u.ok) return f(u.error);
208
+ i = u.value;
209
+ } else throw new l("target-not-found", `未知工具:${a}`);
210
+ return { content: [{ type: "text", text: JSON.stringify({ ok: !0, value: i }) }] };
211
+ } catch (a) {
212
+ return f(a);
213
+ }
214
+ }), { server: t, manager: e };
215
+ }
216
+ export {
217
+ l as A,
218
+ T as a,
219
+ J as c,
220
+ h as l
221
+ };
@@ -0,0 +1,40 @@
1
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import { AgentMcpSessionManager } from './session';
3
+ import type { AgentMcpOptions } from './types';
4
+ export declare function createAgentMcpServer(options?: AgentMcpOptions): {
5
+ server: Server<{
6
+ method: string;
7
+ params?: {
8
+ [x: string]: unknown;
9
+ _meta?: {
10
+ [x: string]: unknown;
11
+ progressToken?: string | number | undefined;
12
+ "io.modelcontextprotocol/related-task"?: {
13
+ taskId: string;
14
+ } | undefined;
15
+ } | undefined;
16
+ } | undefined;
17
+ }, {
18
+ method: string;
19
+ params?: {
20
+ [x: string]: unknown;
21
+ _meta?: {
22
+ [x: string]: unknown;
23
+ progressToken?: string | number | undefined;
24
+ "io.modelcontextprotocol/related-task"?: {
25
+ taskId: string;
26
+ } | undefined;
27
+ } | undefined;
28
+ } | undefined;
29
+ }, {
30
+ [x: string]: unknown;
31
+ _meta?: {
32
+ [x: string]: unknown;
33
+ progressToken?: string | number | undefined;
34
+ "io.modelcontextprotocol/related-task"?: {
35
+ taskId: string;
36
+ } | undefined;
37
+ } | undefined;
38
+ }>;
39
+ manager: AgentMcpSessionManager;
40
+ };
@@ -0,0 +1,20 @@
1
+ import type { AgentToolName, AgentToolInput, AgentToolResult, AgentRenderInput } from '@web-ppt/edit-core/agent';
2
+ import type { AgentMcpOpenResult, AgentMcpOptions, AgentMcpRenderResult, AgentMcpSaveResult } from './types';
3
+ export declare class AgentMcpSessionManager {
4
+ private readonly options;
5
+ private readonly sessions;
6
+ private sequence;
7
+ private active;
8
+ constructor(options?: AgentMcpOptions);
9
+ open(pathInput: unknown): Promise<AgentMcpOpenResult>;
10
+ call<T extends AgentToolName>(sessionId: string, name: T, input: AgentToolInput<T>): Promise<AgentToolResult<T>>;
11
+ save(sessionId: string, pathInput: unknown): Promise<AgentMcpSaveResult>;
12
+ render(sessionId: string, input: AgentRenderInput, pathInput?: unknown): Promise<AgentMcpRenderResult>;
13
+ close(sessionId: string): Promise<{
14
+ readonly closed: true;
15
+ }>;
16
+ dispose(): Promise<void>;
17
+ private entry;
18
+ private run;
19
+ private assertActive;
20
+ }
@@ -0,0 +1,22 @@
1
+ import type { AgentSessionOptions, AgentToolFailure, AgentSaveResult, AgentRenderResult } from '@web-ppt/edit-core/agent';
2
+ export type AgentMcpOptions = Omit<AgentSessionOptions, 'idPrefix' | 'presentation'>;
3
+ export declare class AgentMcpError extends Error {
4
+ readonly reason: string;
5
+ constructor(reason: string, message: string);
6
+ }
7
+ export interface AgentMcpOpenResult {
8
+ readonly sessionId: string;
9
+ readonly path: string;
10
+ }
11
+ export type AgentMcpSaveResult = {
12
+ readonly ok: true;
13
+ readonly value: AgentSaveResult & {
14
+ readonly path: string;
15
+ };
16
+ } | AgentToolFailure;
17
+ export type AgentMcpRenderResult = {
18
+ readonly ok: true;
19
+ readonly value: AgentRenderResult & {
20
+ readonly path?: string;
21
+ };
22
+ } | AgentToolFailure;
package/package.json CHANGED
@@ -1,6 +1,62 @@
1
1
  {
2
2
  "name": "@web-ppt/agent-mcp",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.8.0-beta.2",
4
+ "description": "Local-only MCP stdio server and Agent Skill for deterministic PPT reading, editing, rendering and saving.",
5
+ "keywords": [
6
+ "pptx",
7
+ "ppt",
8
+ "powerpoint",
9
+ "mcp",
10
+ "agent",
11
+ "stdio",
12
+ "offline",
13
+ "typescript"
14
+ ],
15
+ "license": "MIT",
16
+ "author": "unStone",
17
+ "homepage": "https://github.com/unStone/web-ppt#readme",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/unStone/web-ppt.git",
21
+ "directory": "packages/agent-mcp"
22
+ },
23
+ "type": "module",
24
+ "sideEffects": false,
25
+ "main": "./dist/agent-mcp.js",
26
+ "module": "./dist/agent-mcp.js",
27
+ "types": "./dist/index.d.ts",
28
+ "bin": {
29
+ "web-ppt-agent": "./dist/cli.js"
30
+ },
31
+ "exports": {
32
+ ".": {
33
+ "types": "./dist/index.d.ts",
34
+ "import": "./dist/agent-mcp.js"
35
+ },
36
+ "./package.json": "./package.json"
37
+ },
38
+ "files": [
39
+ "dist",
40
+ "skills",
41
+ "README.md",
42
+ "CHANGELOG.md",
43
+ "LICENSE"
44
+ ],
45
+ "scripts": {
46
+ "build": "vite build && tsc -p tsconfig.build.json",
47
+ "prepublishOnly": "npm run build",
48
+ "prepack": "node ../../tooling/sync-package-docs.mjs"
49
+ },
50
+ "peerDependencies": {
51
+ "@web-ppt/core": "^0.8.0-beta.2",
52
+ "@web-ppt/edit-core": "^0.8.0-beta.2"
53
+ },
54
+ "dependencies": {
55
+ "@cfworker/json-schema": "^4.1.1",
56
+ "@modelcontextprotocol/sdk": "^1.32.2",
57
+ "zod": "^4.2.0"
58
+ },
59
+ "publishConfig": {
60
+ "access": "public"
61
+ }
62
+ }
@@ -0,0 +1,41 @@
1
+ ---
2
+ name: web-ppt
3
+ description: Read, edit, inspect, render and save PPTX or PPT files through the local web-ppt-agent MCP server. Use when the user asks to modify an existing presentation, replace text or images, add slides, fix layout overflow, or render slides without desktop Office.
4
+ license: MIT
5
+ compatibility: Requires a locally running web-ppt-agent MCP server. File access is limited to paths explicitly supplied by the user or the current task.
6
+ ---
7
+
8
+ # Web-PPT
9
+
10
+ Use the `ppt_*` MCP tools for all presentation operations. Do not unzip the presentation, edit OOXML directly, invoke a model service, or fetch remote resources. The server keeps file access local to the paths explicitly supplied for the task.
11
+
12
+ ## Required loop
13
+
14
+ 1. `ppt_open` with the source path. Keep the returned `sessionId`.
15
+ 2. `ppt_describe` to identify slide and element IDs. Use `ppt_element` when current style details are needed.
16
+ 3. `ppt_lint` before editing when layout problems matter. Text/font checks require the host's real font measurement and font inventory; do not assume they are configured.
17
+ 4. `ppt_edit` with existing commands only. Group dependent changes in one atomic batch and read the affected-slide lint result.
18
+ 5. `ppt_render` to inspect the current projection. SVG needs real measurement; PNG also needs a configured rasterizer. Write the output only to a requested local path.
19
+ 6. Repeat describe/edit/lint/render until the requested content and checks are correct. Use `ppt_undo` / `ppt_redo` for history instead of reverse commands.
20
+ 7. `ppt_save` with the requested destination path, then `ppt_close`.
21
+
22
+ ## Commands and observations
23
+
24
+ Use stable IDs from describe, not array positions or rendered child IDs. `AddImage`, `ReplaceImage`, and `SetBackgroundImage` take Base64 bytes. When the host permits shell commands, use the local `base64` utility to obtain those bytes; do not guess image content. Use `FitTextShape` for supported autofit cases, or adjust the text body/frame with existing commands. Every edit is one transaction; an invalid command rejects the whole batch without changing the document or history.
25
+
26
+ Read `lint.status` separately from the edit result. `failed` observation does not mean the transaction failed and does not justify another blind edit. Resolve the observation's reason first.
27
+
28
+ ## Failure handling
29
+
30
+ - `invalid-input`: correct the request against the tool's JSON Schema and retry.
31
+ - `invalid-base64`: supply canonical Base64 bytes.
32
+ - `target-not-found`: re-run describe and use current IDs; do not invent replacements.
33
+ - `command-rejected`: inspect `commandIndex` and the command's semantic constraint.
34
+ - `no-history`: stop undoing or redoing.
35
+ - `node-measure-required`: request/configure real font measurement; never substitute estimated widths.
36
+ - `node-rasterizer-required`: request/configure a rasterizer or use SVG if acceptable.
37
+ - `session-disposed`: reopen the file and rebuild the editing plan from current content.
38
+
39
+ ## Saving and boundaries
40
+
41
+ `ppt_save` always requires an explicit local destination. It preserves the original package through patch saving when possible and uses generated saving when no patchable package remains. It never uploads the document or creates an HTTP service. Do not claim Windows PowerPoint validation from MCP success alone; use an independent reader when available.