15 KiB
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,必须严格区分:
-
浏览器可达的 MNote origin
- 示例:
http://127.0.0.1:3000 - 用途:插件 iframe 调用 MNote bridge API。
- 来源:
location.origin - 禁止替换成
host.docker.internal,因为浏览器侧可能无法解析。
- 示例:
-
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
加载流程
- 用户打开
/onlyoffice?...。 - MNote 页面加载
/onlyoffice-server/web-apps/apps/api/documents/api.js。 - MNote 构造
DocsAPI.DocEditorconfig。 document.url和callbackUrl使用ONLYOFFICE_DOCUMENT_URL_BASE,供 DocumentServer 容器访问。editorConfig.plugins.pluginsData指向:/api/onlyoffice/bridge/plugin/config?sessionId=...&apiBase=http://127.0.0.1:3000- ONLYOFFICE 加载 MNote bridge 插件 iframe。
- 插件 iframe 调用
POST /api/onlyoffice/bridge/session注册 session。 - 插件通过
GET /api/onlyoffice/bridge/commands/next长轮询取命令。 - 插件执行白名单 recipe。
- 插件调用
POST /api/onlyoffice/bridge/results回传结果。
Bridge API
当前 API:
GET /api/onlyoffice/bridge/plugin/configGET /api/onlyoffice/bridge/plugin/indexGET /api/onlyoffice/bridge/plugin/index/config.jsonGET /api/onlyoffice/bridge/plugin/index/{state}POST /api/onlyoffice/bridge/sessionGET /api/onlyoffice/bridge/session/currentPOST /api/onlyoffice/bridge/session/closeGET /api/onlyoffice/bridge/sessionsGET /api/onlyoffice/bridge/capabilitiesPOST /api/onlyoffice/bridge/commandsGET /api/onlyoffice/bridge/commands/nextPOST /api/onlyoffice/bridge/resultsGET /api/onlyoffice/bridge/results
Session 模型
每个打开的 ONLYOFFICE 页面生成一个 sessionId:
mnote-oo-{docKey}-{tabRandomSuffix}
服务端保存:
sessionIdeditorType:word/cell/slidedocumentIdassetIdfileTypedocKeypageOriginlastSeenMillispendingCommandspendingResults
多 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.getdocument.insert_textdocument.replace_selectiondocument.insert_htmldocument.exportdocument.search_replacedocument.insert_tabledocument.get_commentsdocument.add_comment
Excel:
sheet.get_sheetssheet.add_sheetsheet.rename_sheetsheet.get_rangesheet.get_range_valuessheet.get_valuessheet.set_valuesheet.set_formulasheet.batch_set_valuessheet.set_range_valuessheet.format_rangesheet.set_dimensionssheet.sort_rangesheet.add_chart
PPT:
presentation.get_slidespresentation.get_slide_textspresentation.get_shapespresentation.add_text_slidepresentation.replace_textpresentation.set_shape_textpresentation.delete_slidepresentation.add_tablepresentation.clear_slidepresentation.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 必须三层验证:
-
Rust unit test
- manifest 暴露
- route dispatch
- dryRun / 权限 / queue result
-
Browser direct smoke
- 直接调用
window.__MNOTE_ONLYOFFICE_BRIDGE__.run(...) - 不经过 Hermes/Reasonix
- 必须保存截图并检查
pageErrors=[]
- 直接调用
-
Tool API smoke
- 通过
/api/mnote/tools/call;旧/api/hermes/tools/mnote/call只作为 legacy alias 验证 - 验证 agent 实际可调用
- 通过
完成标准:
cargo test -p mnote-web onlyoffice_ -- --test-threads=1cargo test -p mnote-web hermes_tools_manifest_returns_first_batch_tools -- --test-threads=1cargo test -p mnote-web skill_registry_exposes_onlyoffice_live_skill_to_agents -- --test-threads=1node scripts/task517-onlyoffice-bridge-plugin-direct-smoke.js覆盖 bridge plugin index 在 mockAsc.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:覆盖 mockAsc.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:onlyofficescope 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,并验证 liveonlyofficeSessionId写入 run payload。