Files
mnote/design/07-ai/process/7-43-onlyoffice-plugin-bridge-design-v1.md
T
lix-2026 1882db7681 收口 MNote P0 P1 P2 审查尾项
- 归档 OnlyOffice live bridge、Page AI、mindmap、design governance 与相关 bug 条目
- 补齐 MinerU OCR 后端 runtime 合同与 smoke/test 基线
- 收口 ChatOnly/Doubao、ObjectIdentity、Page Aggregate compat 与 runtime owner 文档口径

验证:
- cargo test --manifest-path rust/Cargo.toml -p mnote-web local_ocr -- --test-threads=1
- cargo test --manifest-path rust/Cargo.toml -p mnote-web onlyoffice_bridge -- --test-threads=1
- git diff --check
- git diff --cached --check
- codegraph index . --force && codegraph status .
- codegraph sync . && codegraph status .
2026-06-01 09:29:12 +08:00

15 KiB
Raw Blame History

7-43 ONLYOFFICE Plugin Bridge Design v1

状态:process

Owner07-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 权限、dryRunidempotencyKey 模型。
  • 所有新增 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.urlcallbackUrl、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.urlcallbackUrl 使用 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

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 层已有 task515task516task517 证据;真实 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 冻结进 editorTargettargetPackagetargetPackage.targets[0],且不再把 Office target 当作 Markdown buffer 查询 /api/documents/buffer-state

2026-06-01 续补:已新增 GET /api/onlyoffice/bridge/session/currentPOST /api/onlyoffice/bridge/session/closesession state 已写入 docKey / pageOrigin/onlyoffice 页面会把当前文档 docKey 与页面 origin 透传给 bridge plugin config。已通过 cargo test --manifest-path rust/Cargo.toml -p mnote-web onlyoffice_bridge -- --test-threads=1node scripts/task517-onlyoffice-bridge-plugin-direct-smoke.jsMNOTE_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_replaceApi.GetDocument().SearchAndReplace(...),适合精确文本替换。纯搜索暂不单独暴露,全文读取先用 document.export
  • document.insert_tableApi.CreateTable(cols, rows)doc.Push(table),可用二维 data 填充单元格;默认插入到文档末尾。
  • document.get_commentsdoc.GetAllComments(),返回评论 id、作者、正文和引用文本。
  • document.add_comment 支持给当前选区或 document_start 添加评论;选区模式依赖 doc.GetRangeBySelect(),没有选区时应显式用 target=document_start

Excel

  • sheet.get_sheetsApi.GetSheets() / worksheet.GetName(),用于让 agent 明确当前 workbook sheet 结构。
  • sheet.add_sheetApi.AddSheet(name),用于创建新 sheet。
  • sheet.rename_sheetworksheet.SetName(name),用于重命名指定 sheetIndex 的 sheet。
  • sheet.get_range_values / sheet.set_range_values 接受 A1 地址(例如 B2:C3),适合按用户可见坐标读写二维区域。
  • sheet.set_range_values 只写入 A1 range 与 values 的交集;调用方需要保证二维数据尺寸符合预期。
  • sheet.format_rangeApiRange 格式 API,只暴露小范围字体粗斜体、下划线、填充色、字体色、字号、字体名、对齐、数字格式。
  • sheet.set_dimensionsworksheet.SetColumnWidth / worksheet.SetRowHeight,行列索引沿插件内部零基坐标。
  • sheet.sort_rangerange.SetSort(...),只暴露 A1 range、range 内 1 基 keyColumn、升降序和是否有表头。
  • sheet.add_chartworksheet.AddChart(...),只暴露 A1 数据区、图表类型、基础尺寸和插入位置。

PPT

  • presentation.get_slide_texts 读取 slide 文本框纯文本。
  • presentation.replace_text 替换文本框纯文本;命中的文本框会按纯文本段落重建,不承诺保留复杂 run 格式。
  • presentation.get_shapes 返回每页 shape 的 slideIndex / shapeIndex / shapeId / text 预览,用于精确定位文本框。
  • presentation.set_shape_textslideIndex + shapeIndex 重写指定 shape 文本;只面向文本 shape,复杂样式不承诺保留。
  • presentation.delete_slide 删除指定 slideIndex 的幻灯片;调用前应先读 slide 列表确认目标页。
  • presentation.add_tableApi.CreateTable(...) + slide.AddObject(table),支持二维 data 和基础位置/尺寸。
  • presentation.clear_slideslide.RemoveAllObjects(),用于清空指定页对象;这是高影响写操作,调用前必须确认目标页。
  • presentation.add_shapeApi.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.pngoffice-b-after-write.pngtask523-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。