# 7-43 ONLYOFFICE Plugin Bridge Design v1 > 状态:process > > Owner:07-ai / ONLYOFFICE live bridge / Page AI target runtime > > 创建时间:2026-06-01 > > 当前口径:工具层 session/scope 安全阻断、真实 iframe 非 dry-run 写入落点验证、Page AI target picker 到真实 Office iframe session 的端到端绑定均已落地;本文继续留在 `process`,用于承接后续 recipe 扩展、Office 主文档加载噪音治理和 P2 plugin bridge 产品化收口,不再作为 P0 安全阻断项。 ## 背景 当前本机 ONLYOFFICE DocumentServer 由 Docker 暴露在宿主机 `8082`: - `mnote-onlyoffice-documentserver`: `onlyoffice/documentserver:9.4.0` - 端口映射:`0.0.0.0:8082 -> container:80` - MNote 公开入口:`http://127.0.0.1:3000` - MNote 通过 `/onlyoffice-server` 反代 DocumentServer 静态资源和 editor iframe 实测当前社区镜像不暴露 `docEditor.createConnector()`,因此不能依赖 ONLYOFFICE Automation API。MNote 的 live Office agent 能力应建立在社区版可用的 Plugin API 上:插件 iframe 使用 `window.Asc.plugin.executeMethod()` 和 `window.Asc.plugin.callCommand()` 调用 Office JavaScript API。 ## 目标 建立一个完整、可扩展、可验证的 MNote ONLYOFFICE bridge 插件,让 Hermes、Reasonix 和页面 AI 能通过白名单 recipe 操作当前打开的 ONLYOFFICE 文档。 目标能力: - 支持当前浏览器内打开的 Word、Excel、PPT live editor session。 - 通过 MNote bridge API 派发命令,插件执行后回传 JSON-safe result。 - 不暴露任意 JavaScript 执行能力。 - 写操作继续遵守 MNote tool 权限、`dryRun` 和 `idempotencyKey` 模型。 - 所有新增 recipe 必须同时支持 browser direct smoke 和 tool API smoke。 非目标: - 不绕过 ONLYOFFICE Developer / Automation API 授权。 - 不后台批量编辑任意 Office 文件;离线批处理仍优先使用 `officecli`。 - 不把 ONLYOFFICE iframe DOM 当作编辑接口。 ## 端口与 URL 边界 有两条 base URL,必须严格区分: 1. 浏览器可达的 MNote origin - 示例:`http://127.0.0.1:3000` - 用途:插件 iframe 调用 MNote bridge API。 - 来源:`location.origin` - 禁止替换成 `host.docker.internal`,因为浏览器侧可能无法解析。 2. DocumentServer 容器可达的 MNote origin - 示例:`http://host.docker.internal:3000` - 用途:`document.url`、`callbackUrl`、local file proxy。 - 来源:`ONLYOFFICE_DOCUMENT_URL_BASE` - 依赖 compose 中 `extra_hosts: ["host.docker.internal:host-gateway"]`。 DocumentServer 静态资源访问: - Browser -> MNote -> DocumentServer - URL 形态:`/onlyoffice-server/web-apps/apps/api/documents/api.js` - 插件 SDK:`/onlyoffice-server/sdkjs-plugins/v1/plugins.js` ## 加载流程 1. 用户打开 `/onlyoffice?...`。 2. MNote 页面加载 `/onlyoffice-server/web-apps/apps/api/documents/api.js`。 3. MNote 构造 `DocsAPI.DocEditor` config。 4. `document.url` 和 `callbackUrl` 使用 `ONLYOFFICE_DOCUMENT_URL_BASE`,供 DocumentServer 容器访问。 5. `editorConfig.plugins.pluginsData` 指向: `/api/onlyoffice/bridge/plugin/config?sessionId=...&apiBase=http://127.0.0.1:3000` 6. ONLYOFFICE 加载 MNote bridge 插件 iframe。 7. 插件 iframe 调用 `POST /api/onlyoffice/bridge/session` 注册 session。 8. 插件通过 `GET /api/onlyoffice/bridge/commands/next` 长轮询取命令。 9. 插件执行白名单 recipe。 10. 插件调用 `POST /api/onlyoffice/bridge/results` 回传结果。 ## Bridge API 当前 API: - `GET /api/onlyoffice/bridge/plugin/config` - `GET /api/onlyoffice/bridge/plugin/index` - `GET /api/onlyoffice/bridge/plugin/index/config.json` - `GET /api/onlyoffice/bridge/plugin/index/{state}` - `POST /api/onlyoffice/bridge/session` - `GET /api/onlyoffice/bridge/session/current` - `POST /api/onlyoffice/bridge/session/close` - `GET /api/onlyoffice/bridge/sessions` - `GET /api/onlyoffice/bridge/capabilities` - `POST /api/onlyoffice/bridge/commands` - `GET /api/onlyoffice/bridge/commands/next` - `POST /api/onlyoffice/bridge/results` - `GET /api/onlyoffice/bridge/results` ## Session 模型 每个打开的 ONLYOFFICE 页面生成一个 `sessionId`: ```text mnote-oo-{docKey}-{tabRandomSuffix} ``` 服务端保存: - `sessionId` - `editorType`: `word` / `cell` / `slide` - `documentId` - `assetId` - `fileType` - `docKey` - `pageOrigin` - `lastSeenMillis` - `pendingCommands` - `pendingResults` 多 session 规则: - 插件每次 `/commands/next` 都刷新 `lastSeenMillis`。 - `session.current` 返回最近活跃 session。 - 严肃写操作应显式传 `onlyofficeSessionId`,避免多标签页误写。 - 不同 editorType 的 recipe 必须在插件或 wrapper 层做类型保护。 2026-06-01 复核:`sessionId` 已加入浏览器 tab 级随机后缀,tool 侧读写均要求显式 `onlyofficeSessionId` / `bridgeSessionId`,并按 `aiAccessScope.allowedResourceIds` 校验 session resource。当前 HTTP / mock plugin 层已有 `task515`、`task516`、`task517` 证据;真实 ONLYOFFICE iframe / DocumentServer 层新增 `task518` 证据,覆盖插件 autostart 注册、同一文档双 tab 不共用 session、A/B 资源 scope mismatch 403、授权 dry-run 200,以及非 dry-run 写入 B 后导出验证 A 不含 marker、B 含 marker。随后 `task523` 补齐 Page AI target picker 到 Office session 的端到端 UI 绑定:真实文档页打开 Office resource tab 后,Page AI run payload 会把 iframe live `onlyofficeSessionId` 冻结进 `editorTarget`、`targetPackage` 和 `targetPackage.targets[0]`,且不再把 Office target 当作 Markdown buffer 查询 `/api/documents/buffer-state`。 2026-06-01 续补:已新增 `GET /api/onlyoffice/bridge/session/current`、`POST /api/onlyoffice/bridge/session/close`,session state 已写入 `docKey` / `pageOrigin`,`/onlyoffice` 页面会把当前文档 `docKey` 与页面 origin 透传给 bridge plugin config。已通过 `cargo test --manifest-path rust/Cargo.toml -p mnote-web onlyoffice_bridge -- --test-threads=1`、`node scripts/task517-onlyoffice-bridge-plugin-direct-smoke.js`、`MNOTE_UI_BASE_URL=http://127.0.0.1:3302 node scripts/task516-onlyoffice-bridge-multisession-browser-smoke.js`。本文继续保留在 `process`,只跟踪 Office 主文档加载噪音治理和第三批 recipe 逐项实测。 ## Recipe 分层 ### 已实现 recipe Word / 通用: - `selection.get` - `document.insert_text` - `document.replace_selection` - `document.insert_html` - `document.export` - `document.search_replace` - `document.insert_table` - `document.get_comments` - `document.add_comment` Excel: - `sheet.get_sheets` - `sheet.add_sheet` - `sheet.rename_sheet` - `sheet.get_range` - `sheet.get_range_values` - `sheet.get_values` - `sheet.set_value` - `sheet.set_formula` - `sheet.batch_set_values` - `sheet.set_range_values` - `sheet.format_range` - `sheet.set_dimensions` - `sheet.sort_range` - `sheet.add_chart` PPT: - `presentation.get_slides` - `presentation.get_slide_texts` - `presentation.get_shapes` - `presentation.add_text_slide` - `presentation.replace_text` - `presentation.set_shape_text` - `presentation.delete_slide` - `presentation.add_table` - `presentation.clear_slide` - `presentation.add_shape` ### 第二批 recipe 说明 Word: - `document.search_replace` 走 `Api.GetDocument().SearchAndReplace(...)`,适合精确文本替换。纯搜索暂不单独暴露,全文读取先用 `document.export`。 - `document.insert_table` 走 `Api.CreateTable(cols, rows)` 并 `doc.Push(table)`,可用二维 `data` 填充单元格;默认插入到文档末尾。 - `document.get_comments` 走 `doc.GetAllComments()`,返回评论 id、作者、正文和引用文本。 - `document.add_comment` 支持给当前选区或 `document_start` 添加评论;选区模式依赖 `doc.GetRangeBySelect()`,没有选区时应显式用 `target=document_start`。 Excel: - `sheet.get_sheets` 走 `Api.GetSheets()` / `worksheet.GetName()`,用于让 agent 明确当前 workbook sheet 结构。 - `sheet.add_sheet` 走 `Api.AddSheet(name)`,用于创建新 sheet。 - `sheet.rename_sheet` 走 `worksheet.SetName(name)`,用于重命名指定 `sheetIndex` 的 sheet。 - `sheet.get_range_values` / `sheet.set_range_values` 接受 A1 地址(例如 `B2:C3`),适合按用户可见坐标读写二维区域。 - `sheet.set_range_values` 只写入 A1 range 与 `values` 的交集;调用方需要保证二维数据尺寸符合预期。 - `sheet.format_range` 走 `ApiRange` 格式 API,只暴露小范围字体粗斜体、下划线、填充色、字体色、字号、字体名、对齐、数字格式。 - `sheet.set_dimensions` 走 `worksheet.SetColumnWidth` / `worksheet.SetRowHeight`,行列索引沿插件内部零基坐标。 - `sheet.sort_range` 走 `range.SetSort(...)`,只暴露 A1 range、range 内 1 基 `keyColumn`、升降序和是否有表头。 - `sheet.add_chart` 走 `worksheet.AddChart(...)`,只暴露 A1 数据区、图表类型、基础尺寸和插入位置。 PPT: - `presentation.get_slide_texts` 读取 slide 文本框纯文本。 - `presentation.replace_text` 替换文本框纯文本;命中的文本框会按纯文本段落重建,不承诺保留复杂 run 格式。 - `presentation.get_shapes` 返回每页 shape 的 `slideIndex` / `shapeIndex` / `shapeId` / text 预览,用于精确定位文本框。 - `presentation.set_shape_text` 按 `slideIndex + shapeIndex` 重写指定 shape 文本;只面向文本 shape,复杂样式不承诺保留。 - `presentation.delete_slide` 删除指定 `slideIndex` 的幻灯片;调用前应先读 slide 列表确认目标页。 - `presentation.add_table` 走 `Api.CreateTable(...)` + `slide.AddObject(table)`,支持二维 `data` 和基础位置/尺寸。 - `presentation.clear_slide` 走 `slide.RemoveAllObjects()`,用于清空指定页对象;这是高影响写操作,调用前必须确认目标页。 - `presentation.add_shape` 走 `Api.CreateShape(...)` + `slide.AddObject(shape)`,只暴露基础形状、文字、填充色、位置和尺寸。 ### 第三批 recipe - 已实装:Excel 排序、Excel 图表、PPT 表格。 - 已实装:PPT 清空页对象、PPT 添加基础形状。 - 待验证后再扩:Word 图片、修订、content controls、表格增删行列和样式。 - 待验证后再扩:Excel 筛选、工作表删除/移动。 - 待验证后再扩:PPT 图片、重排 slide、主题/布局、shape 样式与位置。 - PDF/forms 字段读取与填写。 第三批必须逐项实测,不允许凭 API 名称直接暴露给 agent。 ## 权限与安全 插件只负责执行当前 editor session 允许的动作;MNote 负责业务权限: - 当前用户是否能打开文档。 - agent 是否拥有 `office.read` / `office.write`。 - 写操作是否显式 `dryRun`。 - 写操作是否带 `idempotencyKey`。 安全规则: - 不暴露 raw JavaScript。 - action 必须是白名单。 - payload 必须 schema 校验。 - 批量单元格读写默认限制为 5000 cells。 - result 必须 JSON-safe。 - command timeout 默认 25 秒。 ## 验证基线 每个 recipe 必须三层验证: 1. Rust unit test - manifest 暴露 - route dispatch - dryRun / 权限 / queue result 2. Browser direct smoke - 直接调用 `window.__MNOTE_ONLYOFFICE_BRIDGE__.run(...)` - 不经过 Hermes/Reasonix - 必须保存截图并检查 `pageErrors=[]` 3. Tool API smoke - 通过 `/api/hermes/tools/mnote/call` - 验证 agent 实际可调用 完成标准: - `cargo test -p mnote-web onlyoffice_ -- --test-threads=1` - `cargo test -p mnote-web hermes_tools_manifest_returns_first_batch_tools -- --test-threads=1` - `cargo test -p mnote-web skill_registry_exposes_onlyoffice_live_skill_to_agents -- --test-threads=1` - `node scripts/task517-onlyoffice-bridge-plugin-direct-smoke.js` 覆盖 bridge plugin index 在 mock `Asc.plugin` 环境中的 session 注册、`docKey/pageOrigin` 元数据、command 消费、result 回传和 token 拒绝。 - `node scripts/task518-onlyoffice-real-iframe-session-scope-smoke.js` 覆盖真实 ONLYOFFICE iframe / DocumentServer 下插件 autostart、同文档双 tab session salt、bridge command 回收、resource scope 拒写、授权 dry-run,以及授权 B session 非 dry-run 写入后 A/B 导出内容不串台。 - `node scripts/task523-page-ai-onlyoffice-real-target-session-smoke.js` 覆盖真实文档页内 Office resource tab -> Page AI target picker -> run payload 的 live session 绑定,并断言 Office target 不触发 Markdown buffer-state 404。 - 关键 browser direct smoke 通过 - `git diff --check` 通过 - `codegraph sync .` 已运行 2026-06-01 进展:已新增并通过 `task517-onlyoffice-bridge-plugin-direct-smoke.js`,证明 MNote bridge plugin index 与 bridge HTTP loop 可在真实 Chromium 中直接运行。随后新增并通过 `task518-onlyoffice-real-iframe-session-scope-smoke.js`,在真实 ONLYOFFICE iframe / DocumentServer 下打开同一 docx 两个 tab 和另一个 docx,验证三者 sessionId 均不同、同文档双 tab 共享 docKey 但使用不同随机 salt、`selection.get` 可各自回收、scope=A 显式调用 B session 返回 403、scope=B 授权 dry-run 返回 200。`task518` 已继续扩展为非 dry-run 写入 B 并导出 A/B 内容验证不串台,截图保存在 `tmp/task518-onlyoffice-real-iframe-session-scope-smoke/screenshots/office-a-after-write.png` 与 `office-b-after-write.png`。`task523-page-ai-onlyoffice-real-target-session-smoke.js` 已补齐 Page AI target picker 到 `onlyofficeSessionId` 的真实 UI 绑定,截图保存在 `tmp/task523-page-ai-onlyoffice-real-target-session-smoke/screenshots/`。 `task518` 已把 Office 主文档加载噪音分类为三类:bridge plugin translation 404、ONLYOFFICE 内置插件噪音、主文档加载失败。smoke 会断言 bridge session 注册和 command loop 正常,并把 `errorCode=-18` 或 editor 未 ready 归为主文档失败,避免把打不开文档误判为插件噪音。 补充验证矩阵: - `task515-onlyoffice-live-scope-http-smoke.js`:覆盖缺 explicit session、scope mismatch、缺 scope 和授权 dry-run。 - `task516-onlyoffice-bridge-multisession-browser-smoke.js`:覆盖 A/B bridge session token、`docKey/pageOrigin` 元数据、command queue、result 回收互不串台,以及 `session/current` / `session/close`。 - `task517-onlyoffice-bridge-plugin-direct-smoke.js`:覆盖 mock `Asc.plugin` 下 plugin index 注册、`docKey/pageOrigin` 透传和 command loop。 - `task518-onlyoffice-real-iframe-session-scope-smoke.js`:覆盖真实 ONLYOFFICE iframe / DocumentServer 下插件 autostart、同文档双 tab session 隔离、A/B session command 回收、`resource:onlyoffice` scope mismatch 403、授权 scope dry-run、授权非 dry-run 写入 B 后导出验证 A 不含 marker / B 含 marker。 - `task523-page-ai-onlyoffice-real-target-session-smoke.js`:覆盖真实文档页打开 Office resource tab、等待 iframe bridge ready、通过 Page AI target picker 选择 Office target,并验证 live `onlyofficeSessionId` 写入 run payload。