Răsfoiți Sursa

refactor(document): consolidate Office conversion into office-to-pdf

yudshj 4 zile în urmă
părinte
comite
d3ed04defa
62 a modificat fișierele cu 453 adăugiri și 697 ștergeri
  1. 2 2
      .agents/notes/implemented/architecture/2026-09-14-independent-libreoffice-kit.i18n.yaml
  2. 1 1
      .agents/notes/implemented/architecture/2026-09-14-independent-libreoffice-kit.md
  3. 1 1
      .agents/notes/implemented/architecture/2026-09-14-independent-libreoffice-kit.zh.md
  4. 2 2
      .agents/notes/implemented/architecture/2026-09-15-bounded-office-rendering.i18n.yaml
  5. 4 2
      .agents/notes/implemented/architecture/2026-09-15-bounded-office-rendering.md
  6. 4 2
      .agents/notes/implemented/architecture/2026-09-15-bounded-office-rendering.zh.md
  7. 2 2
      apps/desktop/README.i18n.yaml
  8. 1 1
      apps/desktop/README.md
  9. 1 1
      apps/desktop/README.zh.md
  10. 2 2
      docs/capability-seams.i18n.yaml
  11. 4 6
      docs/capability-seams.md
  12. 4 6
      docs/capability-seams.zh.md
  13. 2 2
      docs/config-catalog.i18n.yaml
  14. 50 51
      docs/config-catalog.md
  15. 50 51
      docs/config-catalog.zh.md
  16. 2 2
      docs/module-graph.i18n.yaml
  17. 2 5
      docs/module-graph.md
  18. 2 5
      docs/module-graph.zh.md
  19. 2 2
      docs/subsystems/README.i18n.yaml
  20. 1 1
      docs/subsystems/README.md
  21. 1 1
      docs/subsystems/README.zh.md
  22. 3 3
      docs/subsystems/office-to-pdf.i18n.yaml
  23. 12 13
      docs/subsystems/office-to-pdf.md
  24. 12 13
      docs/subsystems/office-to-pdf.zh.md
  25. 2 2
      packages/bundle/web-app/README.i18n.yaml
  26. 1 1
      packages/bundle/web-app/README.md
  27. 1 1
      packages/bundle/web-app/README.zh.md
  28. 2 2
      packages/bundle/web-app/cordis.patch.yml
  29. 1 2
      packages/bundle/web-app/package.json
  30. 10 10
      packages/bundle/web-app/tests/document-conversion.e2e.ts
  31. 2 2
      packages/document/README.i18n.yaml
  32. 3 4
      packages/document/README.md
  33. 3 4
      packages/document/README.zh.md
  34. 0 6
      packages/document/document-convert-libreoffice/README.i18n.yaml
  35. 0 43
      packages/document/document-convert-libreoffice/package.json
  36. 0 75
      packages/document/document-convert/README.md
  37. 0 75
      packages/document/document-convert/README.zh.md
  38. 0 44
      packages/document/document-convert/src/index.ts
  39. 0 71
      packages/document/document-convert/src/types.ts
  40. 0 18
      packages/document/document-convert/tsconfig.json
  41. 3 3
      packages/document/office-to-pdf/README.i18n.yaml
  42. 6 4
      packages/document/office-to-pdf/README.md
  43. 6 4
      packages/document/office-to-pdf/README.zh.md
  44. 11 7
      packages/document/office-to-pdf/package.json
  45. 15 0
      packages/document/office-to-pdf/src/errors.ts
  46. 31 0
      packages/document/office-to-pdf/src/identity.ts
  47. 35 14
      packages/document/office-to-pdf/src/index.ts
  48. 6 6
      packages/document/office-to-pdf/src/output.ts
  49. 25 24
      packages/document/office-to-pdf/src/queue.ts
  50. 43 0
      packages/document/office-to-pdf/src/types.ts
  51. 0 0
      packages/document/office-to-pdf/tests/output.spec.ts
  52. 10 11
      packages/document/office-to-pdf/tests/provider.spec.ts
  53. 6 7
      packages/document/office-to-pdf/tests/queue.spec.ts
  54. 1 1
      packages/document/office-to-pdf/tsconfig.json
  55. 1 1
      packages/experimental/webworker-runtime/src/node/external_packages/libreoffice-kit.ts
  56. 47 47
      packages/extensions/tool-cordis/src/api-catalog.ts
  57. 4 17
      pnpm-lock.yaml
  58. 5 5
      scripts/gen-cordis-catalog.ts
  59. 2 2
      scripts/gen-doc-graphs.ts
  60. 1 2
      scripts/verify-package-readme-model-experience.ts
  61. 2 3
      tsconfig.base.json
  62. 1 2
      tsconfig.host.json

+ 2 - 2
.agents/notes/implemented/architecture/2026-09-14-independent-libreoffice-kit.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-14-independent-libreoffice-kit.md
-2026-09-14-independent-libreoffice-kit.md: f9f27585d157025477e5f5d5f6d74dfca142ce83
-2026-09-14-independent-libreoffice-kit.zh.md: 18bf20e28c8f66ea581ac7823f7b004e89153b90
+2026-09-14-independent-libreoffice-kit.md: 13efc41e56a4810273fbb6d9314eeda6053dbe92
+2026-09-14-independent-libreoffice-kit.zh.md: 720ca2ba3f57546825ae14b5b5f0b086fbb7f287

+ 1 - 1
.agents/notes/implemented/architecture/2026-09-14-independent-libreoffice-kit.md

@@ -10,7 +10,7 @@ LibreOffice compilation, source patches, platform qualification, and large binar
 
 ## Decision
 
-The `deepseek-harness/libreoffice-kit` repository owns the reusable `@deepseek-ai/libreoffice-kit` Node API, its Worker, font handling, engine selection, build recipes, patches, tests, and releases. The API has no Cordis dependency. Harness owns the adapter from its `DocumentConverter` service to this API, Session authorization, conversion lifetime, transport, Web UI, and application packaging.
+The `deepseek-harness/libreoffice-kit` repository owns the reusable `@deepseek-ai/libreoffice-kit` Node API, its Worker, font handling, engine selection, build recipes, patches, tests, and releases. The API has no Cordis dependency. Harness owns the adapter from its `OfficeToPdf` service to this API, Session authorization, conversion lifetime, transport, Web UI, and application packaging.
 
 Kit releases run independently of Harness releases. The kit repository qualifies and publishes the Node API and engine npm packages at a shared version, starting at `0.0.1`. Harness consumes an exact npm version and commits its dependency resolution in `pnpm-lock.yaml`; Harness releases neither build nor publish kit packages.
 

+ 1 - 1
.agents/notes/implemented/architecture/2026-09-14-independent-libreoffice-kit.zh.md

@@ -10,7 +10,7 @@ LibreOffice 编译、源码补丁、平台资格验证和大型二进制发布
 
 ## Decision
 
-`deepseek-harness/libreoffice-kit` 仓库维护可复用的 `@deepseek-ai/libreoffice-kit` Node API、Worker、字体处理、引擎选择、构建配方、补丁、测试和发布。API 不依赖 Cordis。Harness 负责将自己的 `DocumentConverter` 服务适配到此 API,以及 Session 授权、转换生命周期、传输、Web UI 和应用打包。
+`deepseek-harness/libreoffice-kit` 仓库维护可复用的 `@deepseek-ai/libreoffice-kit` Node API、Worker、字体处理、引擎选择、构建配方、补丁、测试和发布。API 不依赖 Cordis。Harness 负责将自己的 `OfficeToPdf` 服务适配到此 API,以及 Session 授权、转换生命周期、传输、Web UI 和应用打包。
 
 kit 发布流程独立于 Harness 发布流程。kit 仓库验证并以统一版本发布 Node API 和引擎 npm 包,起始版本为 `0.0.1`。Harness 消费精确的 npm 版本,并在 `pnpm-lock.yaml` 中提交依赖解析结果;Harness 发布既不构建也不发布 kit 包。
 

+ 2 - 2
.agents/notes/implemented/architecture/2026-09-15-bounded-office-rendering.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-15-bounded-office-rendering.md
-2026-09-15-bounded-office-rendering.md: c8ffd5f476e9c5a7c7a0f85706872b2da1690681
-2026-09-15-bounded-office-rendering.zh.md: a03a605dd66142d6457d35154a0e1628dcfa7632
+2026-09-15-bounded-office-rendering.md: 242434361e665359ebfa6d934b5257fc0df12ed9
+2026-09-15-bounded-office-rendering.zh.md: b8268a5e9c421bb7b205136f66213acaa7aa8548

+ 4 - 2
.agents/notes/implemented/architecture/2026-09-15-bounded-office-rendering.md

@@ -10,9 +10,9 @@ Office preview and explicit document inspection can request the same conversion.
 
 ## Decision
 
-The `document-convert` service returns complete PDF bytes. Page rasterization and user presentation remain separate consumers, so conversion naming does not imply image rendering or preview UI.
+The `office-to-pdf` service returns complete PDF bytes. Page rasterization and user presentation remain separate consumers, so conversion naming does not imply image rendering or preview UI.
 
-The [Host provider](../../../../packages/document/document-convert-libreoffice/README.md) owns a shared conversion queue and transient content cache. Authorized source metadata enters admission before source bytes are loaded. The source callback receives reserved byte capacity and returns its read version; changed sources fail without publishing aliases. Exact source bytes and Office extension determine the digest. Each renderer lifetime adds a generation so engine/font/configuration replacement invalidates reuse.
+The [Host provider](../../../../packages/document/office-to-pdf/README.md) owns a shared conversion queue and transient content cache. Authorized source metadata enters admission before source bytes are loaded. The source callback receives reserved byte capacity and returns its read version; changed sources fail without publishing aliases. Exact source bytes and Office extension determine the digest. Each renderer lifetime adds a generation so engine/font/configuration replacement invalidates reuse.
 
 A bounded source-version index avoids repeated reads after authorization; the digest remains the identity for sharing conversion across distinct paths. Ready PDFs use an entry/byte-bounded LRU. Queued jobs contain metadata and deferred callbacks. Reader, queue, source-byte, and conversion limits also apply before work completes. Each active source locator belongs to live readers, so cancellation cannot grow retained source metadata independently of reader admission. Unknown source sizes reserve the input cap; cancellation retains active capacity until actual read/conversion cleanup settles.
 
@@ -28,6 +28,8 @@ Foreground preview and explicit QA requests precede background work. Disabling b
 
 **Build a second prewarm or QA converter.** Independent queues duplicate resource ownership and cannot prioritize shared foreground work.
 
+**Separate service-definition and provider packages for the sole LibreOffice implementation.** They evolve together and have no independent alternative implementation. One `office-to-pdf` package supplies the mountable service without duplicated package, dependency, and release configuration. Native and WASM engine selection remains inside the kit; a second independent implementation can justify extracting an interface from actual consumer needs.
+
 ## Consequences
 
 The cache is transient and cannot bypass source authorization. Oversized PDFs can be returned without retention, and failed or canceled conversions are retried on a later explicit request. Source reservations measure binary bytes; base64 expansion, engine RSS, caller-retained output, and PDF.js page memory remain outside those limits. With one configured conversion slot, foreground work waits for an already-running background conversion to finish.

+ 4 - 2
.agents/notes/implemented/architecture/2026-09-15-bounded-office-rendering.zh.md

@@ -10,9 +10,9 @@ Office 预览和显式文档检查可能请求相同转换。仅缓存已完成
 
 ## 决策
 
-`document-convert` 服务返回完整 PDF 字节。页面栅格化和用户展示由独立消费方负责,因此转换命名不隐含图片渲染或预览 UI。
+`office-to-pdf` 服务返回完整 PDF 字节。页面栅格化和用户展示由独立消费方负责,因此转换命名不隐含图片渲染或预览 UI。
 
-[宿主提供方](../../../../packages/document/document-convert-libreoffice/README.zh.md)拥有共享转换队列和临时内容缓存。已授权的源文件元数据在加载字节之前进入准入流程。源回调接收预留的字节容量并返回读取版本;源文件变化会导致失败,不发布别名。确切的源字节和 Office 扩展名决定摘要。每个渲染器生命周期附加代次,因此引擎、字体或配置替换会使复用失效。
+[宿主提供方](../../../../packages/document/office-to-pdf/README.zh.md)拥有共享转换队列和临时内容缓存。已授权的源文件元数据在加载字节之前进入准入流程。源回调接收预留的字节容量并返回读取版本;源文件变化会导致失败,不发布别名。确切的源字节和 Office 扩展名决定摘要。每个渲染器生命周期附加代次,因此引擎、字体或配置替换会使复用失效。
 
 有界的源版本索引在授权后避免重复读取;摘要仍是不同路径间共享转换的身份。已就绪 PDF 使用按条目与字节限制的 LRU。排队任务包含元数据和延迟回调。读取方、队列、源字节与转换限制在工作完成前也适用。每个在途源定位信息归属于活跃读取方,因此取消操作不能让保留的源元数据脱离读取方准入限制增长。未知源大小预留输入上限;取消后仍保留活动容量,直至实际读取、转换与清理结束。
 
@@ -28,6 +28,8 @@ Office 预览和显式文档检查可能请求相同转换。仅缓存已完成
 
 **另建预热或 QA 转换器。** 独立队列重复拥有资源,无法优先调度共享前台工作。
 
+**为唯一的 LibreOffice 实现拆分服务定义和提供方包。** 两者共同演化,没有独立的替代实现。单个 `office-to-pdf` 包直接提供可挂载服务,避免重复维护包、依赖和发布配置。原生与 WASM 引擎选择仍由 kit 负责;若出现独立的第二种实现,再根据实际消费者提取接口。
+
 ## 后果
 
 缓存为临时数据,不能绕过源授权。超出缓存上限的 PDF 可返回而不保留;失败或取消的转换在后续显式请求时重试。源预留按二进制字节计量;base64 膨胀、引擎 RSS、调用方保留的输出和 PDF.js 页面内存不计入这些限制。仅配置一个转换槽位时,前台工作等待已运行的后台转换结束。

+ 2 - 2
apps/desktop/README.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write apps/desktop/README.md
-README.md: a76e1d0d44f7ef935d9514f5bfd4b271cda6e074
-README.zh.md: 8911afee6a5f1ec9df8c0dabf93aa81f21bc1571
+README.md: cd303f6557143e14aece0dd323286fbee41e54da
+README.zh.md: 55f70a092cbd42d3fc8802891014f5d147a7d2ca

+ 1 - 1
apps/desktop/README.md

@@ -120,7 +120,7 @@ Each target owns its packed package inputs, prepared runtime, package set, dsh t
 
 Desktop packs workspace packages locally and installs external dependencies through the target's bundled Node and pnpm. [Desktop's file policy](scripts/runtime-file-policy.ts) then filters the immutable `resources/app.asar/dsh/node_modules` copy before signing and integrity sealing. It omits TypeScript declarations, recognized JavaScript/CSS/TypeScript source maps, TypeScript build caches, Domino's test directory, selected native compiler outputs, and node-pty prebuilds for other platforms. It preserves runtime JavaScript, native modules and their DLL/EXE helpers, WASM, unknown assets, licenses, and notices. The policy does not alter npm tarballs, the bundled package manager, or user-installed plugin files.
 
-The [Office conversion provider](../../packages/document/document-convert-libreoffice/README.md) carries the target’s declared native engine, or WASM when the kit declares no matching native target. Preparation rejects a missing target engine before packaging. Engine assets, licenses, and notices remain in the runtime dependency tree.
+The [Office conversion provider](../../packages/document/office-to-pdf/README.md) carries the target’s declared native engine, or WASM when the kit declares no matching native target. Preparation rejects a missing target engine before packaging. Engine assets, licenses, and notices remain in the runtime dependency tree.
 
 The packaged application runs compiled JavaScript and pre-generated Typert metadata; it does not compile TypeScript plugins. Source-level debugger navigation and editor declarations remain available in development packages. [Copy-policy tests](tests/runtime-file-policy.spec.ts) cover exclusions and retained assets; `prepare:dsh` runs the [payload smoke](tests/fixtures/runtime-payload-smoke.mjs) under Electron RunAsNode before the Host smoke and final inventory verification.
 

+ 1 - 1
apps/desktop/README.zh.md

@@ -120,7 +120,7 @@ macOS arm64 命令要求 Apple Silicon。macOS x64 命令可以在 Intel macOS 
 
 Desktop 在本地打包工作区包,并通过目标捆绑的 Node 和 pnpm 安装外部依赖。[Desktop 文件策略](scripts/runtime-file-policy.ts)随后在签名和完整性封装前过滤不可变的 `resources/app.asar/dsh/node_modules` 副本。它排除 TypeScript 声明、已识别的 JavaScript/CSS/TypeScript source map、TypeScript 构建缓存、Domino 测试目录、选定的原生编译器输出和其他平台的 node-pty 预构建文件。它保留运行时 JavaScript、原生模块及其 DLL/EXE 辅助文件、WASM、未知资源、许可证和 notices。该策略不修改 npm tarball、捆绑的包管理器或用户安装的插件文件。
 
-[Office 转换提供方](../../packages/document/document-convert-libreoffice/README.zh.md)携带目标已声明的原生引擎;kit 未声明匹配原生目标时携带 WASM 引擎。准备阶段在打包前拒绝缺少目标引擎的情况。引擎资源、许可证和 notices 保留在运行时依赖树中。
+[Office 转换提供方](../../packages/document/office-to-pdf/README.zh.md)携带目标已声明的原生引擎;kit 未声明匹配原生目标时携带 WASM 引擎。准备阶段在打包前拒绝缺少目标引擎的情况。引擎资源、许可证和 notices 保留在运行时依赖树中。
 
 打包应用运行编译后的 JavaScript 和预生成的 Typert 元数据,不编译 TypeScript 插件。源码级调试导航和编辑器声明仍可从开发包中获取。[复制规则测试](tests/runtime-file-policy.spec.ts)覆盖排除项和保留资源;`prepare:dsh` 在 Host smoke 和最终清单验证之前,使用 Electron RunAsNode 执行[产物 smoke](tests/fixtures/runtime-payload-smoke.mjs)。
 

+ 2 - 2
docs/capability-seams.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write docs/capability-seams.md
-capability-seams.md: 6b0b48b86cc5413fdc122ce3225577f86cbd6001
-capability-seams.zh.md: 7d714141a5e8deb4b7be0ca176db520a58a48832
+capability-seams.md: 88308afe9d8acff4d021f57ce5884cace44c894d
+capability-seams.zh.md: a81cbb977d85234f203db64309ef5cfa0268f3f3

+ 4 - 6
docs/capability-seams.md

@@ -26,9 +26,8 @@ flowchart LR
   svc_computerUse["ctx.computerUse<br/>Computer-use provider registration"]
   pkg_experimental_computer_use_cua_driver_mcp["experimental-computer-use-cua-driver-mcp"]
   pkg_experimental_computer_use_cua_driver_native["experimental-computer-use-cua-driver-native"]
-  pkg_document_convert["document-convert"]
-  svc_documentConvert["ctx.documentConvert<br/>Office to PDF conversion"]
-  pkg_document_convert_libreoffice["document-convert-libreoffice"]
+  pkg_office_to_pdf["office-to-pdf"]
+  svc_officeToPdf["ctx.officeToPdf<br/>Office to PDF conversion"]
   pkg_attachment["attachment"]
   svc_attachments["ctx.attachments<br/>Durable binary attachment storage"]
   pkg_attachment_local["attachment-local"]
@@ -284,8 +283,6 @@ flowchart LR
   pkg_credentials --> svc_credentials
   pkg_credentials_local --> svc_credentials
   pkg_deepseek_llm_api_extensions --> svc_deepseekLlmApiExtensions
-  pkg_document_convert --> svc_documentConvert
-  pkg_document_convert_libreoffice --> svc_documentConvert
   pkg_experimental_agent_team --> svc_agentTeams
   pkg_experimental_browser_use_chrome_devtools_mcp --> svc_browserUse
   pkg_experimental_browser_use_playwright_mcp --> svc_browserUse
@@ -318,6 +315,7 @@ flowchart LR
   pkg_mcp_client --> svc_mcpResources
   pkg_mcp_resources --> svc_mcpResources
   pkg_message_feedback --> svc_messageFeedback
+  pkg_office_to_pdf --> svc_officeToPdf
   pkg_permission_presets --> svc_permissionPresets
   pkg_plan_mode --> svc_planMode
   pkg_plugin_manager --> svc_pluginManager
@@ -535,7 +533,7 @@ flowchart LR
 | `ctx.mcpResources` | `seam` | [`mcp-resources`](../packages/mcp/mcp-resources) | [`mcp-client`](../packages/mcp/mcp-client) | [`mcp-resources`](../packages/mcp/mcp-resources) | - | Connection-owned providers serve shared resource tools in the calling agent scope. |
 | `ctx.browserUse` | `seam` | [`browser-use`](../packages/browser-use/browser-use) | [`experimental-browser-use-playwright-mcp`](../packages/experimental/browser-use-playwright-mcp), [`experimental-browser-use-chrome-devtools-mcp`](../packages/experimental/browser-use-chrome-devtools-mcp), [`experimental-browser-use-stagehand-native`](../packages/experimental/browser-use-stagehand-native) | [`experimental-browser-use-playwright-mcp`](../packages/experimental/browser-use-playwright-mcp), [`experimental-browser-use-chrome-devtools-mcp`](../packages/experimental/browser-use-chrome-devtools-mcp), [`experimental-browser-use-stagehand-native`](../packages/experimental/browser-use-stagehand-native) | - | One provider-owned name per service instance. Providers own their tools and browser resources per live Session; the shared service has no browser operation API. |
 | `ctx.computerUse` | `seam` | [`computer-use`](../packages/computer-use/computer-use) | [`experimental-computer-use-cua-driver-mcp`](../packages/experimental/computer-use-cua-driver-mcp), [`experimental-computer-use-cua-driver-native`](../packages/experimental/computer-use-cua-driver-native) | [`experimental-computer-use-cua-driver-mcp`](../packages/experimental/computer-use-cua-driver-mcp), [`experimental-computer-use-cua-driver-native`](../packages/experimental/computer-use-cua-driver-native) | - | One provider-owned name per service instance. Each provider also owns its model tools; the service has no common action API, runtime selection, or Session workflow lock. |
-| `ctx.documentConvert` | `seam` | [`document-convert`](../packages/document/document-convert) | [`document-convert-libreoffice`](../packages/document/document-convert-libreoffice) | - | - | Authorized Office bytes are converted on the Host using the declared native target engine, or Node WASM when no native target is declared. |
+| `ctx.officeToPdf` | `core` | [`office-to-pdf`](../packages/document/office-to-pdf) | - | - | - | Authorized Office bytes are converted on the Host using the declared native target engine, or Node WASM when no native target is declared. |
 | `ctx.attachments` | `seam` | [`attachment`](../packages/attachment/attachment) | [`attachment-local`](../packages/attachment/attachment-local) | [`api-session-controller`](../packages/api/session-controller), [`tool-fs`](../packages/fs/tool-fs), [`llm-pi-ai`](../packages/llm/llm-pi-ai), [`llm-deepseek`](../packages/llm/llm-deepseek) | - | The host commits accepted images before session events; provider adapters resolve authorized durable references into provider-native content. |
 | `ctx.fileUploads` | `core` | [`client-file-upload`](../packages/client/file-upload) | - | [`api-session-controller`](../packages/api/session-controller) | - | Owns streaming intake, durable storage, and staged receipt lifetime; the Session controller binds receipts to accepted submissions. |
 | `ctx.llm` | `seam` | [`llm`](../packages/llm/llm) | [`llm-deepseek`](../packages/llm/llm-deepseek), [`llm-pi-ai`](../packages/llm/llm-pi-ai), [`llm-replay`](../packages/test-support/llm-replay) | [`agent-loop`](../packages/core/agent-loop), [`compaction-basic`](../packages/compaction/compaction-basic) | - | Adapters register provider implementations; the loop and compaction call the provider-neutral stream service. |

+ 4 - 6
docs/capability-seams.zh.md

@@ -28,9 +28,8 @@ flowchart LR
   svc_computerUse["ctx.computerUse<br/>Computer-use provider registration"]
   pkg_experimental_computer_use_cua_driver_mcp["experimental-computer-use-cua-driver-mcp"]
   pkg_experimental_computer_use_cua_driver_native["experimental-computer-use-cua-driver-native"]
-  pkg_document_convert["document-convert"]
-  svc_documentConvert["ctx.documentConvert<br/>Office to PDF conversion"]
-  pkg_document_convert_libreoffice["document-convert-libreoffice"]
+  pkg_office_to_pdf["office-to-pdf"]
+  svc_officeToPdf["ctx.officeToPdf<br/>Office to PDF conversion"]
   pkg_attachment["attachment"]
   svc_attachments["ctx.attachments<br/>Durable binary attachment storage"]
   pkg_attachment_local["attachment-local"]
@@ -286,8 +285,6 @@ flowchart LR
   pkg_credentials --> svc_credentials
   pkg_credentials_local --> svc_credentials
   pkg_deepseek_llm_api_extensions --> svc_deepseekLlmApiExtensions
-  pkg_document_convert --> svc_documentConvert
-  pkg_document_convert_libreoffice --> svc_documentConvert
   pkg_experimental_agent_team --> svc_agentTeams
   pkg_experimental_browser_use_chrome_devtools_mcp --> svc_browserUse
   pkg_experimental_browser_use_playwright_mcp --> svc_browserUse
@@ -320,6 +317,7 @@ flowchart LR
   pkg_mcp_client --> svc_mcpResources
   pkg_mcp_resources --> svc_mcpResources
   pkg_message_feedback --> svc_messageFeedback
+  pkg_office_to_pdf --> svc_officeToPdf
   pkg_permission_presets --> svc_permissionPresets
   pkg_plan_mode --> svc_planMode
   pkg_plugin_manager --> svc_pluginManager
@@ -537,7 +535,7 @@ flowchart LR
 | `ctx.mcpResources` | `seam` | [`mcp-resources`](../packages/mcp/mcp-resources) | [`mcp-client`](../packages/mcp/mcp-client) | [`mcp-resources`](../packages/mcp/mcp-resources) | - | 连接所有者提供的操作在调用 agent 的作用域内服务于共享资源工具。 |
 | `ctx.browserUse` | `seam` | [`browser-use`](../packages/browser-use/browser-use) | [`experimental-browser-use-playwright-mcp`](../packages/experimental/browser-use-playwright-mcp), [`experimental-browser-use-chrome-devtools-mcp`](../packages/experimental/browser-use-chrome-devtools-mcp), [`experimental-browser-use-stagehand-native`](../packages/experimental/browser-use-stagehand-native) | [`experimental-browser-use-playwright-mcp`](../packages/experimental/browser-use-playwright-mcp), [`experimental-browser-use-chrome-devtools-mcp`](../packages/experimental/browser-use-chrome-devtools-mcp), [`experimental-browser-use-stagehand-native`](../packages/experimental/browser-use-stagehand-native) | - | 每个服务实例注册一个提供方拥有的名称。提供方按实时 Session 拥有自己的工具与浏览器资源;共享服务不提供浏览器操作 API。 |
 | `ctx.computerUse` | `seam` | [`computer-use`](../packages/computer-use/computer-use) | [`experimental-computer-use-cua-driver-mcp`](../packages/experimental/computer-use-cua-driver-mcp), [`experimental-computer-use-cua-driver-native`](../packages/experimental/computer-use-cua-driver-native) | [`experimental-computer-use-cua-driver-mcp`](../packages/experimental/computer-use-cua-driver-mcp), [`experimental-computer-use-cua-driver-native`](../packages/experimental/computer-use-cua-driver-native) | - | 每个服务实例只注册一个提供方自定的名称。各提供方也拥有自己的模型工具;服务不提供通用操作 API、运行时选择或 Session 流程锁。 |
-| `ctx.documentConvert` | `seam` | [`document-convert`](../packages/document/document-convert) | [`document-convert-libreoffice`](../packages/document/document-convert-libreoffice) | - | - | 已授权的 Office 字节在宿主上使用已声明的原生目标引擎转换;未声明原生目标时使用 Node WASM。 |
+| `ctx.officeToPdf` | `core` | [`office-to-pdf`](../packages/document/office-to-pdf) | - | - | - | 已授权的 Office 字节在宿主上使用已声明的原生目标引擎转换;未声明原生目标时使用 Node WASM。 |
 | `ctx.attachments` | `seam` | [`attachment`](../packages/attachment/attachment) | [`attachment-local`](../packages/attachment/attachment-local) | [`api-session-controller`](../packages/api/session-controller), [`tool-fs`](../packages/fs/tool-fs), [`llm-pi-ai`](../packages/llm/llm-pi-ai), [`llm-deepseek`](../packages/llm/llm-deepseek) | - | 宿主会在会话事件之前提交已接受的图片;提供方适配器将已授权的持久引用解析为提供方原生内容。 |
 | `ctx.fileUploads` | `core` | [`client-file-upload`](../packages/client/file-upload) | - | [`api-session-controller`](../packages/api/session-controller) | - | 负责流式接收、持久存储和暂存回执生命周期;Session Controller 将回执绑定到已接受的提交。 |
 | `ctx.llm` | `seam` | [`llm`](../packages/llm/llm) | [`llm-deepseek`](../packages/llm/llm-deepseek), [`llm-pi-ai`](../packages/llm/llm-pi-ai), [`llm-replay`](../packages/test-support/llm-replay) | [`agent-loop`](../packages/core/agent-loop), [`compaction-basic`](../packages/compaction/compaction-basic) | - | 适配器注册提供方实现;agent loop(智能体循环)与压缩功能调用提供方无关的流服务。 |

+ 2 - 2
docs/config-catalog.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write docs/config-catalog.md
-config-catalog.md: a9885dec32cf39a260960559d628b9c1ce219c1a
-config-catalog.zh.md: 481aa16a57f6e8828809a2956bcc04d573c6cabe
+config-catalog.md: 7f9a195843af4bef80634ddf1501e30d71eff859
+config-catalog.zh.md: 6ce8b9ebb185ebb2f7991de9ed4db9de060de66d

+ 50 - 51
docs/config-catalog.md

@@ -548,56 +548,6 @@ export interface Config {
 
 Source: [`packages/credentials/credentials-local/src/index.ts:64`](../packages/credentials/credentials-local/src/index.ts)
 
-<a id="deepseek-aidsh-document-convert-libreoffice"></a>
-
-## `@deepseek-ai/dsh-document-convert-libreoffice`
-
-```ts config-catalog
-/** Provider concurrency and kit rendering/font configuration. */
-export interface Config {
-  /** Maximum simultaneous conversions; queued callers remain cancellable. */
-  maxConcurrentConversions: number
-  /** Maximum metadata-only jobs awaiting source admission. */
-  maxQueuedJobs: number
-  /** Maximum outstanding conversion readers. */
-  maxReaders: number
-  /** Maximum reserved bytes across admitted source reads and conversions. */
-  maxSourceBytes: number
-  /** Maximum concurrent background jobs; zero refuses speculative work. */
-  maxBackgroundConversions: number
-  /** Maximum retained content-addressed PDFs. */
-  maxCachedEntries: number
-  /** Maximum retained PDF bytes. */
-  maxCachedBytes: number
-  /** Maximum retained source-version aliases to cached content. */
-  maxSourceEntries: number
-  /** Conversion deadline in milliseconds; excludes the DSH queue. */
-  timeoutMs: number
-  /** Maximum authorized source bytes. */
-  maxInputBytes: number
-  /** Maximum complete PDF bytes. */
-  maxOutputBytes: number
-  /** Exported raster-image DPI. */
-  maxImageResolution: number
-  /** Maximum OOXML ZIP entries. */
-  maxArchiveEntries: number
-  /** Maximum total declared uncompressed OOXML bytes. */
-  maxUncompressedBytes: number
-  /** Absolute font roots; omission uses the kit's platform defaults. */
-  fontDirectories?: string[]
-  /** Ordered font-family preference groups; omission retains the kit defaults. */
-  fontFallbacks?: string[][]
-  /** Maximum physical font files indexed by each converter. */
-  maxFontFiles: number
-  /** Maximum individual font-file bytes. */
-  maxFontFileBytes: number
-  /** Maximum original font bytes loaded for a conversion. */
-  maxLoadedFontBytes: number
-}
-```
-
-Source: [`packages/document/document-convert-libreoffice/src/index.ts:14`](../packages/document/document-convert-libreoffice/src/index.ts)
-
 <a id="deepseek-aidsh-experimental-agent-team"></a>
 
 ## `@deepseek-ai/dsh-experimental-agent-team`
@@ -1768,6 +1718,56 @@ export interface Config {
 
 Source: [`packages/feedback/message-feedback/src/index.ts:40`](../packages/feedback/message-feedback/src/index.ts)
 
+<a id="deepseek-aidsh-office-to-pdf"></a>
+
+## `@deepseek-ai/dsh-office-to-pdf`
+
+```ts config-catalog
+/** Provider concurrency and kit rendering/font configuration. */
+export interface Config {
+  /** Maximum simultaneous conversions; queued callers remain cancellable. */
+  maxConcurrentConversions: number
+  /** Maximum metadata-only jobs awaiting source admission. */
+  maxQueuedJobs: number
+  /** Maximum outstanding conversion readers. */
+  maxReaders: number
+  /** Maximum reserved bytes across admitted source reads and conversions. */
+  maxSourceBytes: number
+  /** Maximum concurrent background jobs; zero refuses speculative work. */
+  maxBackgroundConversions: number
+  /** Maximum retained content-addressed PDFs. */
+  maxCachedEntries: number
+  /** Maximum retained PDF bytes. */
+  maxCachedBytes: number
+  /** Maximum retained source-version aliases to cached content. */
+  maxSourceEntries: number
+  /** Conversion deadline in milliseconds; excludes the DSH queue. */
+  timeoutMs: number
+  /** Maximum authorized source bytes. */
+  maxInputBytes: number
+  /** Maximum complete PDF bytes. */
+  maxOutputBytes: number
+  /** Exported raster-image DPI. */
+  maxImageResolution: number
+  /** Maximum OOXML ZIP entries. */
+  maxArchiveEntries: number
+  /** Maximum total declared uncompressed OOXML bytes. */
+  maxUncompressedBytes: number
+  /** Absolute font roots; omission uses the kit's platform defaults. */
+  fontDirectories?: string[]
+  /** Ordered font-family preference groups; omission retains the kit defaults. */
+  fontFallbacks?: string[][]
+  /** Maximum physical font files indexed by each converter. */
+  maxFontFiles: number
+  /** Maximum individual font-file bytes. */
+  maxFontFileBytes: number
+  /** Maximum original font bytes loaded for a conversion. */
+  maxLoadedFontBytes: number
+}
+```
+
+Source: [`packages/document/office-to-pdf/src/index.ts:27`](../packages/document/office-to-pdf/src/index.ts)
+
 <a id="deepseek-aidsh-permission-presets"></a>
 
 ## `@deepseek-ai/dsh-permission-presets`
@@ -3805,7 +3805,6 @@ Abstract service classes — a deployment loads a concrete implementation packag
 - `@deepseek-ai/dsh-attachment` — abstract `AttachmentStore` ([`packages/attachment/attachment/src/index.ts`](../packages/attachment/attachment/src/index.ts))
 - `@deepseek-ai/dsh-compaction` — abstract `CompactionEngine` ([`packages/compaction/compaction/src/index.ts`](../packages/compaction/compaction/src/index.ts))
 - `@deepseek-ai/dsh-credentials` — abstract `CredentialProvider` ([`packages/credentials/credentials/src/index.ts`](../packages/credentials/credentials/src/index.ts))
-- `@deepseek-ai/dsh-document-convert` — abstract `DocumentConverter` ([`packages/document/document-convert/src/index.ts`](../packages/document/document-convert/src/index.ts))
 - `@deepseek-ai/dsh-file-reference` — abstract `FileReferenceService` ([`packages/context/file-reference/src/index.ts`](../packages/context/file-reference/src/index.ts))
 - `@deepseek-ai/dsh-fs` — abstract `FileSystem` ([`packages/fs/fs/src/index.ts`](../packages/fs/fs/src/index.ts))
 - `@deepseek-ai/dsh-host-directory-picker` — abstract `DirectoryPicker` ([`packages/host/directory-picker/src/index.ts`](../packages/host/directory-picker/src/index.ts))

+ 50 - 51
docs/config-catalog.zh.md

@@ -550,56 +550,6 @@ export interface Config {
 
 来源:[`packages/credentials/credentials-local/src/index.ts:64`](../packages/credentials/credentials-local/src/index.ts)
 
-<a id="deepseek-aidsh-document-convert-libreoffice"></a>
-
-## `@deepseek-ai/dsh-document-convert-libreoffice`
-
-```ts config-catalog
-/** Provider concurrency and kit rendering/font configuration. */
-export interface Config {
-  /** Maximum simultaneous conversions; queued callers remain cancellable. */
-  maxConcurrentConversions: number
-  /** Maximum metadata-only jobs awaiting source admission. */
-  maxQueuedJobs: number
-  /** Maximum outstanding conversion readers. */
-  maxReaders: number
-  /** Maximum reserved bytes across admitted source reads and conversions. */
-  maxSourceBytes: number
-  /** Maximum concurrent background jobs; zero refuses speculative work. */
-  maxBackgroundConversions: number
-  /** Maximum retained content-addressed PDFs. */
-  maxCachedEntries: number
-  /** Maximum retained PDF bytes. */
-  maxCachedBytes: number
-  /** Maximum retained source-version aliases to cached content. */
-  maxSourceEntries: number
-  /** Conversion deadline in milliseconds; excludes the DSH queue. */
-  timeoutMs: number
-  /** Maximum authorized source bytes. */
-  maxInputBytes: number
-  /** Maximum complete PDF bytes. */
-  maxOutputBytes: number
-  /** Exported raster-image DPI. */
-  maxImageResolution: number
-  /** Maximum OOXML ZIP entries. */
-  maxArchiveEntries: number
-  /** Maximum total declared uncompressed OOXML bytes. */
-  maxUncompressedBytes: number
-  /** Absolute font roots; omission uses the kit's platform defaults. */
-  fontDirectories?: string[]
-  /** Ordered font-family preference groups; omission retains the kit defaults. */
-  fontFallbacks?: string[][]
-  /** Maximum physical font files indexed by each converter. */
-  maxFontFiles: number
-  /** Maximum individual font-file bytes. */
-  maxFontFileBytes: number
-  /** Maximum original font bytes loaded for a conversion. */
-  maxLoadedFontBytes: number
-}
-```
-
-源码: [`packages/document/document-convert-libreoffice/src/index.ts:14`](../packages/document/document-convert-libreoffice/src/index.ts)
-
 <a id="deepseek-aidsh-experimental-agent-team"></a>
 
 ## `@deepseek-ai/dsh-experimental-agent-team`
@@ -1770,6 +1720,56 @@ export interface Config {
 
 来源:[`packages/feedback/message-feedback/src/index.ts:40`](../packages/feedback/message-feedback/src/index.ts)
 
+<a id="deepseek-aidsh-office-to-pdf"></a>
+
+## `@deepseek-ai/dsh-office-to-pdf`
+
+```ts config-catalog
+/** Provider concurrency and kit rendering/font configuration. */
+export interface Config {
+  /** Maximum simultaneous conversions; queued callers remain cancellable. */
+  maxConcurrentConversions: number
+  /** Maximum metadata-only jobs awaiting source admission. */
+  maxQueuedJobs: number
+  /** Maximum outstanding conversion readers. */
+  maxReaders: number
+  /** Maximum reserved bytes across admitted source reads and conversions. */
+  maxSourceBytes: number
+  /** Maximum concurrent background jobs; zero refuses speculative work. */
+  maxBackgroundConversions: number
+  /** Maximum retained content-addressed PDFs. */
+  maxCachedEntries: number
+  /** Maximum retained PDF bytes. */
+  maxCachedBytes: number
+  /** Maximum retained source-version aliases to cached content. */
+  maxSourceEntries: number
+  /** Conversion deadline in milliseconds; excludes the DSH queue. */
+  timeoutMs: number
+  /** Maximum authorized source bytes. */
+  maxInputBytes: number
+  /** Maximum complete PDF bytes. */
+  maxOutputBytes: number
+  /** Exported raster-image DPI. */
+  maxImageResolution: number
+  /** Maximum OOXML ZIP entries. */
+  maxArchiveEntries: number
+  /** Maximum total declared uncompressed OOXML bytes. */
+  maxUncompressedBytes: number
+  /** Absolute font roots; omission uses the kit's platform defaults. */
+  fontDirectories?: string[]
+  /** Ordered font-family preference groups; omission retains the kit defaults. */
+  fontFallbacks?: string[][]
+  /** Maximum physical font files indexed by each converter. */
+  maxFontFiles: number
+  /** Maximum individual font-file bytes. */
+  maxFontFileBytes: number
+  /** Maximum original font bytes loaded for a conversion. */
+  maxLoadedFontBytes: number
+}
+```
+
+来源: [`packages/document/office-to-pdf/src/index.ts:27`](../packages/document/office-to-pdf/src/index.ts)
+
 <a id="deepseek-aidsh-permission-presets"></a>
 
 ## `@deepseek-ai/dsh-permission-presets`
@@ -3807,7 +3807,6 @@ export interface Config {
 - `@deepseek-ai/dsh-attachment` — 抽象 `AttachmentStore`([`packages/attachment/attachment/src/index.ts`](../packages/attachment/attachment/src/index.ts))
 - `@deepseek-ai/dsh-compaction` — 抽象 `CompactionEngine`([`packages/compaction/compaction/src/index.ts`](../packages/compaction/compaction/src/index.ts))
 - `@deepseek-ai/dsh-credentials` — 抽象 `Credentials`([`packages/credentials/credentials/src/index.ts`](../packages/credentials/credentials/src/index.ts))
-- `@deepseek-ai/dsh-document-convert` — 抽象 `DocumentConverter`([`packages/document/document-convert/src/index.ts`](../packages/document/document-convert/src/index.ts))
 - `@deepseek-ai/dsh-file-reference` — 抽象 `FileReferenceService`([`packages/context/file-reference/src/index.ts`](../packages/context/file-reference/src/index.ts))
 - `@deepseek-ai/dsh-fs` — 抽象 `FileSystem`([`packages/fs/fs/src/index.ts`](../packages/fs/fs/src/index.ts))
 - `@deepseek-ai/dsh-host-directory-picker` — 抽象 `DirectoryPicker`([`packages/host/directory-picker/src/index.ts`](../packages/host/directory-picker/src/index.ts))

+ 2 - 2
docs/module-graph.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write docs/module-graph.md
-module-graph.md: 1d3fa93237e94d1347f512d1f7714b47eef91401
-module-graph.zh.md: f80d2b20b0a81cb282e4e097bcfed0851f6d3e81
+module-graph.md: 2949593ccf9f67732d0a91b375e6a1b0e613e502
+module-graph.zh.md: d80d5a5947926753d0ef344884a3f4df4fad53e7

+ 2 - 5
docs/module-graph.md

@@ -222,8 +222,7 @@ flowchart TD
     pkg_credentials_local["credentials-local"]
   end
   subgraph group_document["packages/document"]
-    pkg_document_convert["document-convert"]
-    pkg_document_convert_libreoffice["document-convert-libreoffice"]
+    pkg_office_to_pdf["office-to-pdf"]
   end
   subgraph group_experimental["packages/experimental"]
     pkg_experimental_agent_team["experimental-agent-team"]
@@ -409,7 +408,6 @@ flowchart TD
   pkg_browser_use --> pkg_brand
   pkg_computer_use --> pkg_brand
   pkg_credentials --> pkg_invariants
-  pkg_document_convert_libreoffice --> pkg_document_convert
   pkg_experimental_inspector --> pkg_client_modules
   pkg_experimental_inspector --> pkg_host_webserver
   pkg_experimental_webworker_runtime --> pkg_client_connection
@@ -1370,7 +1368,7 @@ flowchart TD
 | [`client-ui-workflow-run`](../packages/client/ui-workflow-run) | `client` | — |
 | [`client-ui-workspace`](../packages/client/ui-workspace) | `client` | — |
 | [`client-web`](../packages/client/web) | `client` | — |
-| [`document-convert`](../packages/document/document-convert) | `document` | — |
+| [`office-to-pdf`](../packages/document/office-to-pdf) | `document` | — |
 | [`experimental-agent-team-profile`](../packages/experimental/agent-team-profile) | `experimental` | — |
 | [`experimental-agent-team-web-profile`](../packages/experimental/agent-team-web-profile) | `experimental` | — |
 | [`experimental-webworker-packer`](../packages/experimental/webworker-packer) | `experimental` | — |
@@ -1399,7 +1397,6 @@ flowchart TD
 | [`browser-use`](../packages/browser-use/browser-use) | `browser-use` | [`brand`](../packages/util/brand) |
 | [`computer-use`](../packages/computer-use/computer-use) | `computer-use` | [`brand`](../packages/util/brand) |
 | [`credentials`](../packages/credentials/credentials) | `credentials` | [`invariants`](../packages/runtime-diagnostics/invariants) |
-| [`document-convert-libreoffice`](../packages/document/document-convert-libreoffice) | `document` | [`document-convert`](../packages/document/document-convert) |
 | [`experimental-inspector`](../packages/experimental/inspector) | `experimental` | [`client-modules`](../packages/client/modules), [`host-webserver`](../packages/host/webserver) |
 | [`experimental-webworker-runtime`](../packages/experimental/webworker-runtime) | `experimental` | [`client-connection`](../packages/client/connection), [`client-modules`](../packages/client/modules), [`host-webserver`](../packages/host/webserver) |
 | [`host-directory-picker-auto`](../packages/host/directory-picker-auto) | `host` | [`client-ui-directory-picker-browse`](../packages/client/ui-directory-picker-browse), [`client-ui-directory-picker-native`](../packages/client/ui-directory-picker-native), [`host-directory-picker-browse`](../packages/host/directory-picker-browse), [`host-directory-picker-native`](../packages/host/directory-picker-native), [`host-webserver`](../packages/host/webserver) |

+ 2 - 5
docs/module-graph.zh.md

@@ -224,8 +224,7 @@ flowchart TD
     pkg_credentials_local["credentials-local"]
   end
   subgraph group_document["packages/document"]
-    pkg_document_convert["document-convert"]
-    pkg_document_convert_libreoffice["document-convert-libreoffice"]
+    pkg_office_to_pdf["office-to-pdf"]
   end
   subgraph group_experimental["packages/experimental"]
     pkg_experimental_agent_team["experimental-agent-team"]
@@ -411,7 +410,6 @@ flowchart TD
   pkg_browser_use --> pkg_brand
   pkg_computer_use --> pkg_brand
   pkg_credentials --> pkg_invariants
-  pkg_document_convert_libreoffice --> pkg_document_convert
   pkg_experimental_inspector --> pkg_client_modules
   pkg_experimental_inspector --> pkg_host_webserver
   pkg_experimental_webworker_runtime --> pkg_client_connection
@@ -1372,7 +1370,7 @@ flowchart TD
 | [`client-ui-workflow-run`](../packages/client/ui-workflow-run) | `client` | — |
 | [`client-ui-workspace`](../packages/client/ui-workspace) | `client` | — |
 | [`client-web`](../packages/client/web) | `client` | — |
-| [`document-convert`](../packages/document/document-convert) | `document` | — |
+| [`office-to-pdf`](../packages/document/office-to-pdf) | `document` | — |
 | [`experimental-agent-team-profile`](../packages/experimental/agent-team-profile) | `experimental` | — |
 | [`experimental-agent-team-web-profile`](../packages/experimental/agent-team-web-profile) | `experimental` | — |
 | [`experimental-webworker-packer`](../packages/experimental/webworker-packer) | `experimental` | — |
@@ -1401,7 +1399,6 @@ flowchart TD
 | [`browser-use`](../packages/browser-use/browser-use) | `browser-use` | [`brand`](../packages/util/brand) |
 | [`computer-use`](../packages/computer-use/computer-use) | `computer-use` | [`brand`](../packages/util/brand) |
 | [`credentials`](../packages/credentials/credentials) | `credentials` | [`invariants`](../packages/runtime-diagnostics/invariants) |
-| [`document-convert-libreoffice`](../packages/document/document-convert-libreoffice) | `document` | [`document-convert`](../packages/document/document-convert) |
 | [`experimental-inspector`](../packages/experimental/inspector) | `experimental` | [`client-modules`](../packages/client/modules), [`host-webserver`](../packages/host/webserver) |
 | [`experimental-webworker-runtime`](../packages/experimental/webworker-runtime) | `experimental` | [`client-connection`](../packages/client/connection), [`client-modules`](../packages/client/modules), [`host-webserver`](../packages/host/webserver) |
 | [`host-directory-picker-auto`](../packages/host/directory-picker-auto) | `host` | [`client-ui-directory-picker-browse`](../packages/client/ui-directory-picker-browse), [`client-ui-directory-picker-native`](../packages/client/ui-directory-picker-native), [`host-directory-picker-browse`](../packages/host/directory-picker-browse), [`host-directory-picker-native`](../packages/host/directory-picker-native), [`host-webserver`](../packages/host/webserver) |

+ 2 - 2
docs/subsystems/README.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write docs/subsystems/README.md
-README.md: f3593271de63f0436ccd0e704e787c7436b1ac00
-README.zh.md: 4f5ffba4b757e2d835a630cea14c09c7ecdb4980
+README.md: a9202f07f10c15d4e15a26615887753c0d17beb2
+README.zh.md: bfcbae5b68927db72180ce4c7032884752c37089

+ 1 - 1
docs/subsystems/README.md

@@ -29,7 +29,7 @@ One page per subsystem of the DeepSeek Harness: what it is, the data structures
 | [mcp.md](mcp.md) | external MCP connections, scoped tools and resources, server instructions, protocol results, and configuration ownership |
 | [user-questions.md](user-questions.md) | the UI-backed human question/answer seam: `AskUserQuestionRequest`, answer/options vocabulary, provider API, error taxonomy |
 | [approval.md](approval.md) | the one-shot user-approval seam: `ApprovalRequest`, `ApprovalOutcome`, per-session policy, audit events, and answerer contracts |
-| [document-convert.md](document-convert.md) | authorized Office-to-PDF conversion, native/WASM engines, and bounded shared reuse |
+| [office-to-pdf.md](office-to-pdf.md) | authorized Office-to-PDF conversion, native/WASM engines, and bounded shared reuse |
 | [attachment.md](attachment.md) | durable image identity and metadata, validation inputs, verified reads, and the `AttachmentStore` seam |
 | [shell.md](shell.md) | the shell executor seam: `ShellExecRequest`/`Spec`, `ShellRunResult`, background `ShellProcess` handles |
 | [subprocess.md](subprocess.md) | the subprocess seam: fully-explicit `SubprocessSpawnSpec`, offset-based output readers, unclassified `SubprocessOutcome`, and the managed `DSH_*` environment vocabulary |

+ 1 - 1
docs/subsystems/README.zh.md

@@ -29,7 +29,7 @@
 | [mcp.md](mcp.zh.md) | 外部 MCP 连接、作用域工具与资源、服务器指令、协议结果与配置归属 |
 | [user-questions.md](user-questions.zh.md) | UI 支持的人工问答 seam:`AskUserQuestionRequest`、answer/options 词汇、提供方 API、错误分类体系 |
 | [approval.md](approval.zh.md) | 一次性用户审批 seam:`ApprovalRequest`、`ApprovalOutcome`、逐会话策略、审计事件和 answerer 约定 |
-| [document-convert.md](document-convert.zh.md) | 已授权的 Office 到 PDF 转换、原生/WASM 引擎与有界共享复用 |
+| [office-to-pdf.md](office-to-pdf.zh.md) | 已授权的 Office 到 PDF 转换、原生/WASM 引擎与有界共享复用 |
 | [attachment.md](attachment.zh.md) | 持久图片标识与元数据、校验输入、经校验读取,以及 `AttachmentStore` seam |
 | [shell.md](shell.zh.md) | shell 执行器 seam:`ShellExecRequest`/`Spec`、`ShellRunResult`、后台 `ShellProcess` 句柄 |
 | [subprocess.md](subprocess.zh.md) | 子进程 seam:完全显式的 `SubprocessSpawnSpec`、基于偏移的输出读取器、不含分类的 `SubprocessOutcome`,以及受管 `DSH_*` 环境词汇 |

+ 3 - 3
docs/subsystems/document-convert.i18n.yaml → docs/subsystems/office-to-pdf.i18n.yaml

@@ -1,6 +1,6 @@
 # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
-#   pnpm run verify-translation-pairing --write docs/subsystems/document-convert.md
-document-convert.md: 1e17edb6314ef7c99cbf29313f64faa6a4b918cf
-document-convert.zh.md: 478043bae519ecab2d3c2ccbc46426f14c9c4f6e
+#   pnpm run verify-translation-pairing --write docs/subsystems/office-to-pdf.md
+office-to-pdf.md: 71254a3a8a8b4665d8d42b935cc836cb7c644b10
+office-to-pdf.zh.md: c11889fc9bd4693d90ec60696fce4b57693fd027

+ 12 - 13
docs/subsystems/document-convert.md → docs/subsystems/office-to-pdf.md

@@ -1,6 +1,6 @@
-# Document conversion
+# Office to PDF
 
-English | [中文](document-convert.zh.md)
+English | [中文](office-to-pdf.zh.md)
 
 The [document package family](../../packages/document/README.md) converts Office files to PDFs on the Node Host. Consumers authorize source reads and own presentation; the shared provider owns conversion, bounded admission, and transient PDF reuse. This subsystem creates no model-facing tool or Session event.
 
@@ -8,15 +8,14 @@ The [document package family](../../packages/document/README.md) converts Office
 
 | Owner | Responsibility |
 |---|---|
-| [document-convert](../../packages/document/document-convert/README.md) | `ctx.documentConvert`: authorized Office bytes and complete PDF results |
-| [document-convert-libreoffice](../../packages/document/document-convert-libreoffice/README.md) | Host concurrency, reusable kit converters, private scratch files, and bounded PDF reads |
+| [office-to-pdf](../../packages/document/office-to-pdf/README.md) | `ctx.officeToPdf`: shared LibreOffice conversion, bounded admission, and PDF caching |
 | [Web bundle](../../packages/bundle/web-app/README.md) | One configurable conversion provider shared by Host consumers |
 
 ## Requests and results
 
-[`DocumentConvertRequest`](../../packages/document/document-convert/src/types.ts) contains an already-authorized source key/version, optional stat size, a deferred `read(signal, maxBytes)` callback, foreground/background priority, and a `DocumentExtension`: `doc`, `docx`, `xls`, `xlsx`, `ppt`, or `pptx`. `DocumentConverter.convert(request, signal?)` returns one complete PDF result. Cancellation follows the caller and provider lifetimes; validation, output, and engine failures reject with a classified `DocumentConvertError`.
+[`OfficeToPdfRequest`](../../packages/document/office-to-pdf/src/types.ts) contains an already-authorized source key/version, optional stat size, a deferred `read(signal, maxBytes)` callback, foreground/background priority, and a `OfficeExtension`: `doc`, `docx`, `xls`, `xlsx`, `ppt`, or `pptx`. `OfficeToPdf.convert(request, signal?)` returns one complete PDF result. Cancellation follows the caller and provider lifetimes; validation, output, and engine failures reject with a classified `OfficeToPdfError`.
 
-`DocumentConvertPriority` is `foreground` for requested preview/QA and `background` for speculation. `DocumentSourceKey` brands the caller-owned authorized source locator. `DocumentConverterGeneration` brands a provider lifetime, and `DocumentConvertKey` brands its content identity; neither opaque value is parsed by consumers.
+`OfficeToPdfPriority` is `foreground` for requested preview/QA and `background` for speculation. `OfficeSourceKey` brands the caller-owned authorized source locator. `OfficeToPdfGeneration` brands a provider lifetime, and `OfficeToPdfKey` brands its content identity; neither opaque value is parsed by consumers.
 
 | Result field | Meaning |
 |---|---|
@@ -31,7 +30,7 @@ The provider admits the deferred read before allocating source bytes, shares con
 
 The external [`@deepseek-ai/libreoffice-kit`](https://github.com/deepseek-harness/libreoffice-kit) Node API selects its precompiled engines. The kit has an independent version and release workflow, defined by the [release ownership decision](../../.agents/notes/implemented/architecture/2026-09-14-independent-libreoffice-kit.md). Application builds install the published npm packages. Application packaging requires the target’s declared native engine, or Node WASM when the kit declares no native engine for that target. The [platform engine decision](../../.agents/notes/implemented/architecture/2026-09-15-platform-office-engines.md) defines installation and packaging. Invalid metadata, missing required assets, and conversion errors reject without switching engines. Conversion uses disk input and output paths on the Host, with no browser conversion engine or font RPC.
 
-The [Host provider configuration](../../packages/document/document-convert-libreoffice/README.md#use-this-package) owns concurrency, deadlines, input/output limits, archive limits, image resolution, and font access. Native/WASM implementation and asset distribution belong to the kit workspace. System LibreOffice discovery, runtime engine downloads, persistent PDF caching, and model-facing rendering are outside this provider.
+The [Host provider configuration](../../packages/document/office-to-pdf/README.md#use-this-package) owns concurrency, deadlines, input/output limits, archive limits, image resolution, and font access. Native/WASM implementation and asset distribution belong to the kit workspace. System LibreOffice discovery, runtime engine downloads, persistent PDF caching, and model-facing rendering are outside this provider.
 
 <!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
 
@@ -41,11 +40,11 @@ The [Host provider configuration](../../packages/document/document-convert-libre
 
 Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — the language sides differ only in locale-specific paired document paths. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md](../cordis-api/inherited.md).
 
-<a id="ctxdocumentconvert--documentconverter-abstract-seam"></a>
+<a id="ctxofficetopdf--officetopdf"></a>
 
-### `ctx.documentConvert` — `DocumentConverter` (abstract seam)
+### `ctx.officeToPdf` — `OfficeToPdf`
 
-Load one provider subclass per context; consumers own source authorization.
+A provider lifetime owns all converters, queued calls, and temporary files.
 
 ```ts cordis-catalog
 /**
@@ -53,10 +52,10 @@ Load one provider subclass per context; consumers own source authorization.
  * @param request - authorized metadata and deferred bounded source read.
  * @param signal - caller cancellation; provider disposal also stops active work.
  * @returns caller-owned PDF bytes after conversion and scratch cleanup settle; canceled readers reject independently.
- * @throws {DocumentConvertError} Invalid input, unusable output, or engine failure; cancellation rejects with its reason.
+ * @throws {OfficeToPdfError} Invalid input, unusable output, or engine failure; cancellation rejects with its reason.
  */
-abstract convert(request: DocumentConvertRequest, signal?: AbortSignal): Promise<DocumentConvertResult>
+convert(request: OfficeToPdfRequest, signal?: AbortSignal): Promise<OfficeToPdfResult>
 ```
 
-Source: [`packages/document/document-convert/src/index.ts`](../../packages/document/document-convert/src/index.ts)
+Source: [`packages/document/office-to-pdf/src/index.ts`](../../packages/document/office-to-pdf/src/index.ts)
 <!-- END GENERATED cordis-surface -->

+ 12 - 13
docs/subsystems/document-convert.zh.md → docs/subsystems/office-to-pdf.zh.md

@@ -1,6 +1,6 @@
-# 文档转换
+# Office 转 PDF
 
-[English](document-convert.md) | 中文
+[English](office-to-pdf.md) | 中文
 
 [document 包族](../../packages/document/README.zh.md) 在 Node 宿主上将 Office 文件转换为 PDF。消费者负责源文件读取授权与展示;共享提供方负责转换、有界准入和临时 PDF 复用。此子系统不创建面向模型的工具或 Session 事件。
 
@@ -8,15 +8,14 @@
 
 | 所有者 | 职责 |
 |---|---|
-| [document-convert](../../packages/document/document-convert/README.zh.md) | `ctx.documentConvert`:已授权 Office 字节和完整 PDF 结果 |
-| [document-convert-libreoffice](../../packages/document/document-convert-libreoffice/README.zh.md) | Host 并发、可复用 kit 转换器、私有临时文件和有上限的 PDF 读取 |
+| [office-to-pdf](../../packages/document/office-to-pdf/README.zh.md) | `ctx.officeToPdf`:共享 LibreOffice 转换、有界准入和 PDF 缓存 |
 | [Web bundle](../../packages/bundle/web-app/README.zh.md) | 由宿主消费者共享的单个可配置转换提供方 |
 
 ## 请求和结果
 
-[`DocumentConvertRequest`](../../packages/document/document-convert/src/types.ts)包含已授权的源键与版本、可选 stat 大小、延迟的 `read(signal, maxBytes)` 回调、前台或后台优先级以及 `DocumentExtension`:`doc`、`docx`、`xls`、`xlsx`、`ppt` 或 `pptx`。`DocumentConverter.convert(request, signal?)` 返回一个完整 PDF 结果。取消遵循调用方和提供方生命周期;校验、输出和引擎失败以分类的 `DocumentConvertError` 拒绝。
+[`OfficeToPdfRequest`](../../packages/document/office-to-pdf/src/types.ts)包含已授权的源键与版本、可选 stat 大小、延迟的 `read(signal, maxBytes)` 回调、前台或后台优先级以及 `OfficeExtension`:`doc`、`docx`、`xls`、`xlsx`、`ppt` 或 `pptx`。`OfficeToPdf.convert(request, signal?)` 返回一个完整 PDF 结果。取消遵循调用方和提供方生命周期;校验、输出和引擎失败以分类的 `OfficeToPdfError` 拒绝。
 
-`DocumentConvertPriority` 对请求的预览或 QA 使用 `foreground`,对推测工作使用 `background`。`DocumentSourceKey` 为调用方拥有的已授权源定位符增加品牌类型。`DocumentConverterGeneration` 表示提供方生命周期,`DocumentConvertKey` 表示其内容身份;消费者不解析这两种不透明值。
+`OfficeToPdfPriority` 对请求的预览或 QA 使用 `foreground`,对推测工作使用 `background`。`OfficeSourceKey` 为调用方拥有的已授权源定位符增加品牌类型。`OfficeToPdfGeneration` 表示提供方生命周期,`OfficeToPdfKey` 表示其内容身份;消费者不解析这两种不透明值。
 
 | 结果字段 | 含义 |
 |---|---|
@@ -31,7 +30,7 @@
 
 外部 [`@deepseek-ai/libreoffice-kit`](https://github.com/deepseek-harness/libreoffice-kit) Node API 选择其预编译引擎。kit 独立维护版本和发布流程,具体归属由[发布归属决策](../../.agents/notes/implemented/architecture/2026-09-14-independent-libreoffice-kit.zh.md)定义。应用构建时安装已发布的 npm 包。应用打包要求目标已声明的原生引擎;kit 未为该目标声明原生引擎时使用 Node WASM。[平台引擎决策](../../.agents/notes/implemented/architecture/2026-09-15-platform-office-engines.zh.md)定义安装和打包规则。元数据无效、必需资源缺失和转换错误都会拒绝请求,不切换引擎。转换在 Host 使用磁盘输入输出路径,不使用浏览器转换引擎或字体 RPC。
 
-[Host 提供方配置](../../packages/document/document-convert-libreoffice/README.zh.md#use-this-package)负责并发、期限、输入输出上限、归档上限、图像分辨率和字体访问。原生/WASM 实现和资产分发属于 kit 工作区。系统 LibreOffice 探测、运行时引擎下载、持久 PDF 缓存和面向模型的渲染不属于此提供方。
+[Host 提供方配置](../../packages/document/office-to-pdf/README.zh.md#use-this-package)负责并发、期限、输入输出上限、归档上限、图像分辨率和字体访问。原生/WASM 实现和资产分发属于 kit 工作区。系统 LibreOffice 探测、运行时引擎下载、持久 PDF 缓存和面向模型的渲染不属于此提供方。
 
 <!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
 
@@ -41,11 +40,11 @@
 
 Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — the language sides differ only in locale-specific paired document paths. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.zh.md#dispatch-modes), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md](../cordis-api/inherited.md).
 
-<a id="ctxdocumentconvert--documentconverter-abstract-seam"></a>
+<a id="ctxofficetopdf--officetopdf"></a>
 
-### `ctx.documentConvert` — `DocumentConverter` (abstract seam)
+### `ctx.officeToPdf` — `OfficeToPdf`
 
-Load one provider subclass per context; consumers own source authorization.
+A provider lifetime owns all converters, queued calls, and temporary files.
 
 ```ts cordis-catalog
 /**
@@ -53,10 +52,10 @@ Load one provider subclass per context; consumers own source authorization.
  * @param request - authorized metadata and deferred bounded source read.
  * @param signal - caller cancellation; provider disposal also stops active work.
  * @returns caller-owned PDF bytes after conversion and scratch cleanup settle; canceled readers reject independently.
- * @throws {DocumentConvertError} Invalid input, unusable output, or engine failure; cancellation rejects with its reason.
+ * @throws {OfficeToPdfError} Invalid input, unusable output, or engine failure; cancellation rejects with its reason.
  */
-abstract convert(request: DocumentConvertRequest, signal?: AbortSignal): Promise<DocumentConvertResult>
+convert(request: OfficeToPdfRequest, signal?: AbortSignal): Promise<OfficeToPdfResult>
 ```
 
-Source: [`packages/document/document-convert/src/index.ts`](../../packages/document/document-convert/src/index.ts)
+Source: [`packages/document/office-to-pdf/src/index.ts`](../../packages/document/office-to-pdf/src/index.ts)
 <!-- END GENERATED cordis-surface -->

+ 2 - 2
packages/bundle/web-app/README.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write packages/bundle/web-app/README.md
-README.md: 2b5c4f48f95fc40b7e25c33e4b14a510c0ace34f
-README.zh.md: a8117f8f7d1c3f12016d37c37b81e5a8734531aa
+README.md: f733218e857eaf42a8fb2779646b941c5cc1bd4f
+README.zh.md: 5c058067c6f849486229e9e400109565eccc3aea

+ 1 - 1
packages/bundle/web-app/README.md

@@ -73,7 +73,7 @@ Each browser session composes its own agent from the shipped presets (the `stand
 <details>
 <summary>Implementation internals — click to expand</summary>
 
-The bundle is one patch plus one runtime glue plugin. The storage stack and projection cache come from `dsh-base`; the web overlay's workspace and message-feedback rows consume that shared `storageDomain` service. The patch restates the surface-specific values the base deliberately omits, inserts the web-only host rows and browser roster, then moves the agent plane behind presets. The glue plugin owns dist serving, trust sampling, prompt sections, the bash variable, and the readiness announcements. The `document-convert` row mounts one lazy [Office conversion provider](../../document/document-convert-libreoffice/README.md) for Host consumers, including Desktop compositions using this bundle.
+The bundle is one patch plus one runtime glue plugin. The storage stack and projection cache come from `dsh-base`; the web overlay's workspace and message-feedback rows consume that shared `storageDomain` service. The patch restates the surface-specific values the base deliberately omits, inserts the web-only host rows and browser roster, then moves the agent plane behind presets. The glue plugin owns dist serving, trust sampling, prompt sections, the bash variable, and the readiness announcements. The `office-to-pdf` row mounts one lazy [Office conversion provider](../../document/office-to-pdf/README.md) for Host consumers, including Desktop compositions using this bundle.
 
 ### Patch semantics
 

+ 1 - 1
packages/bundle/web-app/README.zh.md

@@ -73,7 +73,7 @@ dsh --profile web --no-open --port 8080
 <details>
 <summary>实现细节——点击展开</summary>
 
-此 bundle 由一个补丁和一个运行时胶水插件组成。存储栈与投影缓存来自 `dsh-base`;Web 叠加层的工作区和消息反馈条目消费共享的 `storageDomain` 服务。补丁重述 base 有意省略的界面专用值,插入 Web 专用宿主条目和浏览器插件列表,再将 Agent 层移到预设后面。胶水插件负责 dist 服务、信任采样、提示词段落、bash 变量和就绪通知。`document-convert` 条目为宿主消费者挂载一个延迟创建引擎的 [Office 转换提供方](../../document/document-convert-libreoffice/README.zh.md),使用此 bundle 的 Desktop 组合也共享该提供方。
+此 bundle 由一个补丁和一个运行时胶水插件组成。存储栈与投影缓存来自 `dsh-base`;Web 叠加层的工作区和消息反馈条目消费共享的 `storageDomain` 服务。补丁重述 base 有意省略的界面专用值,插入 Web 专用宿主条目和浏览器插件列表,再将 Agent 层移到预设后面。胶水插件负责 dist 服务、信任采样、提示词段落、bash 变量和就绪通知。`office-to-pdf` 条目为宿主消费者挂载一个延迟创建引擎的 [Office 转换提供方](../../document/office-to-pdf/README.zh.md),使用此 bundle 的 Desktop 组合也共享该提供方。
 
 ### patch 语义
 

+ 2 - 2
packages/bundle/web-app/cordis.patch.yml

@@ -225,8 +225,8 @@
       name: '@deepseek-ai/dsh-client-ui-sidebar-right'
 
 
-    - id: document-convert
-      name: '@deepseek-ai/dsh-document-convert-libreoffice'
+    - id: office-to-pdf
+      name: '@deepseek-ai/dsh-office-to-pdf'
 
     # The right Sidebar's document tab: bounded file reads with selectable
     # Markdown, code, HTML, PDF, and plain-text renderers.

+ 1 - 2
packages/bundle/web-app/package.json

@@ -122,8 +122,7 @@
     "open": "^11.0.0",
     "@deepseek-ai/dsh-api-terminal-controller": "workspace:^",
     "@deepseek-ai/dsh-client-ui-sidebar-terminal": "workspace:^",
-    "@deepseek-ai/dsh-document-convert": "workspace:^",
-    "@deepseek-ai/dsh-document-convert-libreoffice": "workspace:^"
+    "@deepseek-ai/dsh-office-to-pdf": "workspace:^"
   },
   "peerDependencies": {
     "@deepseek-ai/cordis": "workspace:^",

+ 10 - 10
packages/bundle/web-app/tests/document-conversion.e2e.ts

@@ -7,8 +7,8 @@ import { Context } from '@deepseek-ai/cordis'
 import Loader from '@deepseek-ai/cordis-plugin-loader'
 import Include, { applyEntryPatches } from '@deepseek-ai/cordis-plugin-include'
 import { loadOverlayPatches } from '@deepseek-ai/dsh-app-boot'
-import { DocumentSourceKey, type DocumentConvertRequest } from '@deepseek-ai/dsh-document-convert'
-import * as LibreOfficeProvider from '@deepseek-ai/dsh-document-convert-libreoffice'
+import { OfficeSourceKey, type OfficeToPdfRequest } from '@deepseek-ai/dsh-office-to-pdf'
+import * as LibreOfficeProvider from '@deepseek-ai/dsh-office-to-pdf'
 import { expect, it, onTestFinished } from 'vitest'
 
 it('loads one shared conversion row and retains caller-owned PDFs after disposal', async () => {
@@ -20,9 +20,9 @@ it('loads one shared conversion row and retains caller-owned PDFs after disposal
   })
   const rows = loadOverlayPatches('conversion-test', fileURLToPath(new URL('../cordis.patch.yml', import.meta.url)))
     .flatMap(patch => patch.insert ?? [])
-    .filter(row => row.name === '@deepseek-ai/dsh-document-convert-libreoffice')
-  expect(rows.map(row => row.id)).toEqual(['document-convert'])
-  const configured = applyEntryPatches(rows, [{ id: 'document-convert', config: { maxConcurrentConversions: 1 } }],
+    .filter(row => row.name === '@deepseek-ai/dsh-office-to-pdf')
+  expect(rows.map(row => row.id)).toEqual(['office-to-pdf'])
+  const configured = applyEntryPatches(rows, [{ id: 'office-to-pdf', config: { maxConcurrentConversions: 1 } }],
     (message) => { throw new Error(message) })
   const configPath = join(directory, 'cordis.yml')
   await writeFile(configPath, JSON.stringify(configured))
@@ -33,21 +33,21 @@ it('loads one shared conversion row and retains caller-owned PDFs after disposal
   ctx.loader.internal = {
     version: 'v2',
     async import(specifier: string) {
-      if (specifier !== '@deepseek-ai/dsh-document-convert-libreoffice') throw new Error(`Unexpected plugin: ${specifier}`)
+      if (specifier !== '@deepseek-ai/dsh-office-to-pdf') throw new Error(`Unexpected plugin: ${specifier}`)
       return LibreOfficeProvider
     },
   } as unknown as NonNullable<typeof ctx.loader.internal>
   await ctx.loader.create({ name: 'cordis:include', config: { path: pathToFileURL(configPath).href } })
   await ctx.loader.await()
-  const entry = [...ctx.loader.entries()].find(candidate => candidate.options.id === 'document-convert')!
+  const entry = [...ctx.loader.entries()].find(candidate => candidate.options.id === 'office-to-pdf')!
   await entry.fiber!.await()
   expect(ctx.get('documentRenderController')).toBeUndefined()
   expect(ctx.get('skills')).toBeUndefined()
   const bytes = await readFile(new URL('./fixtures/document-conversion.docx', import.meta.url))
   let reads = 0
-  const request = (key: string): DocumentConvertRequest => ({
+  const request = (key: string): OfficeToPdfRequest => ({
     extension: 'docx', priority: 'foreground',
-    source: { key: DocumentSourceKey(key), version: 'fixture', bytes: bytes.length,
+    source: { key: OfficeSourceKey(key), version: 'fixture', bytes: bytes.length,
       async read(signal, maxBytes) {
         signal.throwIfAborted()
         expect(bytes.length).toBeLessThanOrEqual(maxBytes)
@@ -56,7 +56,7 @@ it('loads one shared conversion row and retains caller-owned PDFs after disposal
       },
     },
   })
-  const provider = ctx.documentConvert
+  const provider = ctx.officeToPdf
   const [first, second] = await Promise.all([provider.convert(request('first')), provider.convert(request('second'))])
   expect(reads).toBe(2)
   expect(Buffer.from(first.pdf).subarray(0, 5).toString()).toBe('%PDF-')

+ 2 - 2
packages/document/README.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write packages/document/README.md
-README.md: 693a831fb7a501f8c0376271715298c06b33efd8
-README.zh.md: ac4719874761c9267c25eee763013b5f7f5d9290
+README.md: 8993c608169ef08abe45babcf33ce6552c147425
+README.zh.md: c5775ae4deb9f4dea36df104f93c8e016e44abbd

+ 3 - 4
packages/document/README.md

@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
 
 ## Summary
 
-Convert authorized Office files to reusable PDFs on the Host. The conversion family provides a shared conversion operation and a LibreOffice kit provider. Targets with a declared native engine use it; other targets use Node WASM.
+Convert authorized Office files to reusable PDFs on the Host. The shared service converts through LibreOffice kit. Targets with a declared native engine use it; other targets use Node WASM.
 
 ## Table of Contents
 
@@ -26,8 +26,7 @@ Each package owns its configuration and lifetime rules; the subsystem reference
 
 | Package | Role | ctx key |
 |---|---|---|
-| [document-convert](document-convert/README.md) | Authorized Office bytes to complete PDF results | `ctx.documentConvert` |
-| [document-convert-libreoffice](document-convert-libreoffice/README.md) | Host kit provider with cancellable concurrency and private scratch files | `ctx.documentConvert` |
+| [office-to-pdf](office-to-pdf/README.md) | Authorized Office bytes to complete PDFs with bounded queues and caching | `ctx.officeToPdf` |
 
 -----
 
@@ -36,7 +35,7 @@ Each package owns its configuration and lifetime rules; the subsystem reference
 
 Consumers own source authorization and presentation.
 
-- [Document conversion](../../docs/subsystems/document-convert.md) — shared operation and generated service reference.
+- [Document conversion](../../docs/subsystems/office-to-pdf.md) — shared operation and generated service reference.
 - [Independent kit ownership](../../.agents/notes/implemented/architecture/2026-09-14-independent-libreoffice-kit.md) — engine distribution and application integration.
 - [Workspace Files](../api/workspace-files/README.md) — authorized bounded source reads.
 

+ 3 - 4
packages/document/README.zh.md

@@ -9,7 +9,7 @@ kind: "package-group"
 
 ## 摘要
 
-在宿主上将已授权的 Office 文件转换为可复用的 PDF。转换包族提供共享转换操作和 LibreOffice kit 提供方。声明了原生引擎的目标使用原生引擎,其余目标使用 Node WASM。
+在宿主上将已授权的 Office 文件转换为可复用的 PDF。共享服务使用 LibreOffice kit 执行转换。声明了原生引擎的目标使用原生引擎,其余目标使用 Node WASM。
 
 ## 目录
 
@@ -26,8 +26,7 @@ kind: "package-group"
 
 | 包 | 职责 | ctx 键 |
 |---|---|---|
-| [document-convert](document-convert/README.zh.md) | 将已授权 Office 字节转换为完整 PDF 结果 | `ctx.documentConvert` |
-| [document-convert-libreoffice](document-convert-libreoffice/README.zh.md) | 使用可取消并发和私有临时文件的 Host kit 提供方 | `ctx.documentConvert` |
+| [office-to-pdf](office-to-pdf/README.zh.md) | 将已授权 Office 字节转换为完整 PDF,并提供有界队列和缓存 | `ctx.officeToPdf` |
 
 -----
 
@@ -36,7 +35,7 @@ kind: "package-group"
 
 消费者负责源文件授权与展示。
 
-- [文档转换](../../docs/subsystems/document-convert.zh.md) — 共享操作和生成的服务参考。
+- [文档转换](../../docs/subsystems/office-to-pdf.zh.md) — 共享操作和生成的服务参考。
 - [独立 kit 所有权](../../.agents/notes/implemented/architecture/2026-09-14-independent-libreoffice-kit.zh.md) — 引擎分发与应用集成。
 - [工作区文件](../api/workspace-files/README.zh.md) — 已授权的有界源文件读取。
 

+ 0 - 6
packages/document/document-convert-libreoffice/README.i18n.yaml

@@ -1,6 +0,0 @@
-# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
-# side as of the last confirmed-consistent state. Both languages carry equal authority;
-# after editing either side, bring the other along and re-record with:
-#   pnpm run verify-translation-pairing --write packages/document/document-convert-libreoffice/README.md
-README.md: c083215ea2e3fa067043dac045becd892e0ca406
-README.zh.md: d10c1a14792c80b3608b3302958de4546b817cda

+ 0 - 43
packages/document/document-convert-libreoffice/package.json

@@ -1,43 +0,0 @@
-{
-  "name": "@deepseek-ai/dsh-document-convert-libreoffice",
-  "description": "Host Office conversion with a declared native target engine or Node WASM",
-  "version": "0.1.6-alpha.1",
-  "publishConfig": {
-    "access": "public"
-  },
-  "type": "module",
-  "main": "lib/index.js",
-  "types": "lib/types/index.d.ts",
-  "license": "MIT",
-  "repository": {
-    "type": "git",
-    "url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
-    "directory": "packages/document/document-convert-libreoffice"
-  },
-  "exports": {
-    ".": {
-      "types": "./lib/types/index.d.ts",
-      "default": "./lib/index.js"
-    },
-    "./src/*": "./src/*",
-    "./package.json": "./package.json"
-  },
-  "files": [
-    "lib/index.js",
-    "lib/types/**/*.d.ts"
-  ],
-  "peerDependencies": {
-    "@deepseek-ai/cordis": "workspace:^",
-    "@deepseek-ai/dsh-document-convert": "workspace:^"
-  },
-  "devDependencies": {
-    "@deepseek-ai/cordis": "workspace:^",
-    "@deepseek-ai/dsh-document-convert": "workspace:^",
-    "@deepseek-ai/cordis-plugin-loader": "workspace:^",
-    "@deepseek-ai/cordis-plugin-include": "workspace:^"
-  },
-  "dependencies": {
-    "@deepseek-ai/libreoffice-kit": "0.0.1",
-    "@deepseek-ai/schemastery": "workspace:^"
-  }
-}

+ 0 - 75
packages/document/document-convert/README.md

@@ -1,75 +0,0 @@
----
-description: "Authorized Office input and complete PDF output for Host conversion providers."
-kind: "package-reference"
----
-
-# @deepseek-ai/dsh-document-convert
-
-English | [中文](README.zh.md)
-
-## Summary
-
-Convert authorized `.doc`, `.docx`, `.xls`, `.xlsx`, `.ppt`, and `.pptx` bytes to a complete PDF. Callers receive PDF bytes and, for OOXML inputs, missing-font families without changing the original file or adding model context.
-
-## Table of Contents
-
-- [Use this package](#use-this-package)
-- [Understand the implementation](#understand-the-implementation)
-- [Further Exploration](#further-exploration)
-- [Model Experience](#model-experience)
-- [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
-- [Dev Note](#dev-note)
-
------
-
-<a id="use-this-package"></a>
-## Use this package
-
-Mount the [LibreOffice provider](../document-convert-libreoffice/README.md) to supply `ctx.documentConvert`. The abstract service has no mountable implementation or configuration. Callers authorize and stat their source before passing its identity, version, optional byte size, deferred bounded read, Office extension, and scheduling priority to `convert()`. The provider admits metadata before calling the read callback. A changed source version rejects conversion.
-
------
-
-<a id="understand-the-implementation"></a>
-## Understand the implementation
-
-<details>
-<summary>Implementation internals — click to expand</summary>
-
-Providers return independent caller-owned PDF bytes, a conversion cache key, and their rendering generation. Consumers check `generation` before reusing cached PDFs; configuration replacement creates a new generation. Individual reader cancellation rejects promptly; provider disposal waits for actual reads, conversion, and scratch cleanup. Cancellation rejects with its reason; classified conversion failures use `DocumentConvertError`. Providers and consumers share this package as a peer dependency so `instanceof DocumentConvertError` recognizes their failures. No runtime invariant companion is published because this service declares operations without an independent retained observation.
-
-</details>
-
------
-
-<a id="further-exploration"></a>
-## Further Exploration
-
-- [Document conversion](../../../docs/subsystems/document-convert.md) — composition and ownership.
-- [Workspace Files](../../api/workspace-files/README.md) — Session file authorization and bounded reads.
-
------
-
-<a id="model-experience"></a>
-## Model Experience
-
-None, as this package converts bytes without model-facing tools, messages, or Session events.
-
-#### KV Cache effect
-
-None; conversion does not construct or modify model requests.
-
-## Known Limitations and Deferred Work
-
-<a id="known-limitations-and-deferred-work"></a>
-
-- The service accepts only the six listed Office formats; source authorization belongs to consumers, while shared conversion reuse belongs to the provider.
-
-<a id="dev-note"></a>
-### Dev Note
-
-<details>
-<summary>Working context for maintainers — click to expand</summary>
-
-None.
-
-</details>

+ 0 - 75
packages/document/document-convert/README.zh.md

@@ -1,75 +0,0 @@
----
-description: "宿主转换提供方使用的已授权 Office 输入与完整 PDF 输出。"
-kind: "package-reference"
----
-
-# @deepseek-ai/dsh-document-convert
-
-[English](README.md) | 中文
-
-## 概述
-
-将已授权的 `.doc`、`.docx`、`.xls`、`.xlsx`、`.ppt` 和 `.pptx` 字节转换为完整 PDF。调用方获得 PDF 字节;OOXML 输入还提供缺失字体名称,不修改原文件或添加模型上下文。
-
-## 目录
-
-- [使用此包](#use-this-package)
-- [理解实现](#understand-the-implementation)
-- [延伸阅读](#further-exploration)
-- [模型体验](#model-experience)
-- [已知限制与延后工作](#known-limitations-and-deferred-work)
-- [开发备注](#dev-note)
-
------
-
-<a id="use-this-package"></a>
-## 使用此包
-
-挂载 [LibreOffice 提供方](../document-convert-libreoffice/README.zh.md)以提供 `ctx.documentConvert`。抽象服务没有可挂载实现或配置。调用方须先授权并 stat 源文件,再向 `convert()` 传入标识、版本、可选字节数、延迟的有界读取、Office 扩展名和调度优先级。提供方在调用读取回调前准入元数据。源版本变化时拒绝转换。
-
------
-
-<a id="understand-the-implementation"></a>
-## 理解实现
-
-<details>
-<summary>实现内部细节 — 点击展开</summary>
-
-提供方返回各调用方独立拥有的 PDF 字节、转换缓存键和转换 generation。消费者在复用缓存 PDF 前检查 `generation`;替换配置会创建新的 generation。单个读取方取消时立即拒绝;提供方销毁会等待实际读取、转换和临时文件清理。取消以其原因拒绝;已分类的转换失败使用 `DocumentConvertError`。提供方和消费者通过 peer dependency 共享此包,使 `instanceof DocumentConvertError` 能识别双方的失败。此服务仅声明操作,没有独立保留的观测,因此不发布运行时不变量伴随入口。
-
-</details>
-
------
-
-<a id="further-exploration"></a>
-## 延伸阅读
-
-- [文档转换](../../../docs/subsystems/document-convert.zh.md) — 组合和所有权。
-- [工作区文件](../../api/workspace-files/README.zh.md) — Session 文件授权与有界读取。
-
------
-
-<a id="model-experience"></a>
-## 模型体验
-
-无,因为此包仅转换字节,不提供面向模型的工具、消息或 Session 事件。
-
-#### KV Cache effect
-
-无;转换不会构造或修改模型请求。
-
-## 已知限制与延后工作
-
-<a id="known-limitations-and-deferred-work"></a>
-
-- 服务仅接受上述六种 Office 格式;源文件授权由消费者负责,共享转换复用由提供方负责。
-
-<a id="dev-note"></a>
-### 开发备注
-
-<details>
-<summary>维护者工作上下文 — 点击展开</summary>
-
-无。
-
-</details>

+ 0 - 44
packages/document/document-convert/src/index.ts

@@ -1,44 +0,0 @@
-/** Host document conversion: authorized Office bytes become a complete PDF. */
-import { Context, Service } from '@deepseek-ai/cordis'
-import type { DocumentConvertErrorCode, DocumentConvertRequest, DocumentConvertResult, DocumentConverterGeneration } from './types.ts'
-
-export * from './types.ts'
-
-/** Classified conversion failure; engine details stay in the cause. */
-export class DocumentConvertError extends Error {
-  /**
-   * @param code - category suitable for a conversion consumer.
-   * @param message - diagnostic explaining the failed conversion.
-   * @param options - underlying engine or filesystem failure.
-   */
-  constructor(readonly code: DocumentConvertErrorCode, message: string, options?: ErrorOptions) {
-    super(message, options)
-    this.name = 'DocumentConvertError'
-  }
-}
-
-declare module '@deepseek-ai/cordis' {
-  interface Context {
-    /** One Host converter for already-authorized Office bytes. */
-    documentConvert: DocumentConverter
-  }
-}
-
-/** Load one provider subclass per context; consumers own source authorization. */
-export abstract class DocumentConverter extends Service {
-  constructor(ctx: Context) { super(ctx, 'documentConvert') }
-
-  /** Changes whenever engine, font, or conversion configuration is replaced. */
-  abstract readonly generation: DocumentConverterGeneration
-
-  /**
-   * Convert Office bytes without modifying the source or writing Session events.
-   * @param request - authorized metadata and deferred bounded source read.
-   * @param signal - caller cancellation; provider disposal also stops active work.
-   * @returns caller-owned PDF bytes after conversion and scratch cleanup settle; canceled readers reject independently.
-   * @throws {DocumentConvertError} Invalid input, unusable output, or engine failure; cancellation rejects with its reason.
-   */
-  abstract convert(request: DocumentConvertRequest, signal?: AbortSignal): Promise<DocumentConvertResult>
-}
-
-export default DocumentConverter

+ 0 - 71
packages/document/document-convert/src/types.ts

@@ -1,71 +0,0 @@
-/** Authorized Office input and complete PDF output, independent of the conversion engine. */
-import { brandString, type Branded } from '@deepseek-ai/dsh-brand'
-
-/** Binary Office and Office Open XML formats supported by document preview. */
-export type DocumentExtension = 'doc' | 'docx' | 'xls' | 'xlsx' | 'ppt' | 'pptx'
-
-/** Authorized execution scope and canonical source path, encoded by the consumer. */
-export type DocumentSourceKey = Branded<'DocumentSourceKey'>
-/**
- * Label an authorized source locator for pre-read deduplication.
- * @param key - unambiguous encoding of authorization scope, execution world, and canonical path.
- * @returns branded source locator; source authorization remains the caller's responsibility.
- */
-export function DocumentSourceKey(key: string): DocumentSourceKey { return brandString<DocumentSourceKey>(key) }
-
-/** One provider lifetime, including its engine, rendering settings, and font configuration. */
-export type DocumentConverterGeneration = Branded<'DocumentConverterGeneration'>
-/**
- * Label a provider lifetime.
- * @param value - unique generation created by the provider.
- * @returns branded converter generation.
- */
-export function DocumentConverterGeneration(value: string): DocumentConverterGeneration {
-  return brandString<DocumentConverterGeneration>(value)
-}
-
-/** Provider generation and source-content digest; consumers must not parse it. */
-export type DocumentConvertKey = Branded<'DocumentConvertKey'>
-/**
- * Label a converter-owned content identity.
- * @param value - generation and content identity created by the provider.
- * @returns branded conversion identity.
- */
-export function DocumentConvertKey(value: string): DocumentConvertKey { return brandString<DocumentConvertKey>(value) }
-
-/** Foreground previews and explicit QA precede speculative background conversion. */
-export type DocumentConvertPriority = 'foreground' | 'background'
-
-/** Source authorization and metadata lookup must finish before submitting a request. */
-export interface DocumentConvertRequest {
-  readonly extension: DocumentExtension
-  readonly priority: DocumentConvertPriority
-  readonly source: {
-    readonly key: DocumentSourceKey
-    readonly version: string
-    /** Authorized stat size; omission reserves the provider's entire input limit. */
-    readonly bytes?: number
-    /**
-     * Read only after provider admission; do not capture already-buffered input in queued production requests.
-     * @param signal - shared conversion lifetime, independent of an individual reader.
-     * @param maxBytes - reserved source capacity; read at most this plus one overflow sentinel byte.
-     * @returns owned bytes and the actual read version; a changed version rejects conversion.
-     */
-    read(signal: AbortSignal, maxBytes: number): Promise<{ readonly bytes: Uint8Array; readonly version: string }>
-  }
-}
-
-/** Successful conversion; failed and interrupted conversions reject instead. */
-export interface DocumentConvertResult {
-  /** Caller-owned complete PDF, valid after provider disposal. */
-  readonly pdf: Uint8Array
-  /** Requested OOXML font families unavailable to this conversion; binary Office formats return an empty list. */
-  readonly missingFonts: string[]
-  readonly cacheKey: DocumentConvertKey
-  readonly generation: DocumentConverterGeneration
-}
-
-/** Failures a conversion consumer can present without exposing engine diagnostics. */
-export type DocumentConvertErrorCode =
-  | 'input-too-large' | 'output-too-large' | 'invalid-document' | 'unsupported-format'
-  | 'invalid-output' | 'timeout' | 'unavailable' | 'failed' | 'busy' | 'source-changed'

+ 0 - 18
packages/document/document-convert/tsconfig.json

@@ -1,18 +0,0 @@
-{
-  "extends": "../../../tsconfig.base.json",
-  "compilerOptions": {
-    "rootDir": "src",
-    "outDir": "lib/types"
-  },
-  "include": [
-    "src"
-  ],
-  "references": [
-    {
-      "path": "../../../vendor/cordis"
-    },
-    {
-      "path": "../../util/brand"
-    }
-  ]
-}

+ 3 - 3
packages/document/document-convert/README.i18n.yaml → packages/document/office-to-pdf/README.i18n.yaml

@@ -1,6 +1,6 @@
 # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
-#   pnpm run verify-translation-pairing --write packages/document/document-convert/README.md
-README.md: fbc98feef30c4e32c255b82f57ea1aad5a68940d
-README.zh.md: 3fa18652036d4823d45e3ccc38d03f599b3c4326
+#   pnpm run verify-translation-pairing --write packages/document/office-to-pdf/README.md
+README.md: 75f26612e649a4722543505cca9956541f07007b
+README.zh.md: d9ecfb7347d22952b6100324926163649389773d

+ 6 - 4
packages/document/document-convert-libreoffice/README.md → packages/document/office-to-pdf/README.md

@@ -3,7 +3,7 @@ description: "Host Office conversion with the independently published LibreOffic
 kind: "package-reference"
 ---
 
-# @deepseek-ai/dsh-document-convert-libreoffice
+# @deepseek-ai/dsh-office-to-pdf
 
 English | [中文](README.zh.md)
 
@@ -25,7 +25,9 @@ Convert Office documents to PDFs on the Host computer. Targets with a declared n
 <a id="use-this-package"></a>
 ## Use this package
 
-The [Web bundle](../../bundle/web-app/README.md) mounts this provider as `document-convert`. Independent compositions mount `@deepseek-ai/dsh-document-convert-libreoffice` as a `cordis.yml` row.
+The [Web bundle](../../bundle/web-app/README.md) mounts this provider as `office-to-pdf`. Independent compositions mount `@deepseek-ai/dsh-office-to-pdf` as a `cordis.yml` row.
+
+Callers submit authorized source identity, version, optional byte size, a deferred bounded read, Office extension, and scheduling priority through `ctx.officeToPdf.convert()`. A changed source version rejects conversion. Results contain caller-owned PDF bytes, missing fonts, a cache key, and a conversion generation that changes on configuration replacement. Cancellation rejects with its reason; conversion failures use `OfficeToPdfError`.
 
 The provider depends on the independently published [`@deepseek-ai/libreoffice-kit`](https://github.com/deepseek-harness/libreoffice-kit/tree/main/packages/entry) npm API at kit version `0.0.1`. Application packaging selects the matching native package declared in the kit’s `optionalDependencies`, or WASM when no native package is declared for that target. A missing declared native engine rejects packaging without selecting WASM. The [platform engine decision](../../../.agents/notes/implemented/architecture/2026-09-15-platform-office-engines.md) defines installation and packaging; the [release ownership decision](../../../.agents/notes/implemented/architecture/2026-09-14-independent-libreoffice-kit.md) defines the independent kit and Harness responsibilities.
 
@@ -38,7 +40,7 @@ The provider depends on the independently published [`@deepseek-ai/libreoffice-k
 | `maxImageResolution` | `192` | Maximum raster-image DPI; overrides the kit default of `144`. |
 | `fontFallbacks` | Kit defaults | Ordered font-family preference groups; each group requires at least two names containing non-whitespace characters. |
 
-The [configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-document-convert-libreoffice) owns the full font, archive, and image settings. `fontDirectories` accepts absolute directories; omission uses the kit platform defaults. Explicit `fontFallbacks` replaces the kit's default groups. Installed requested fonts retain precedence, and other system fonts remain eligible for uncovered glyphs. Native engines can select installed metric-compatible fonts before these preferences.
+The [configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-office-to-pdf) owns the full font, archive, and image settings. `fontDirectories` accepts absolute directories; omission uses the kit platform defaults. Explicit `fontFallbacks` replaces the kit's default groups. Installed requested fonts retain precedence, and other system fonts remain eligible for uncovered glyphs. Native engines can select installed metric-compatible fonts before these preferences.
 
 The provider retains successful PDFs by converter generation, Office extension, and SHA-256 of the exact source bytes. A bounded source-version index avoids rereading known content after an authorized stat; content identity also shares conversion across different source paths. Least-recently-used PDFs leave at either retention limit, together with their aliases. Failures and oversized cache entries are not retained. Every result has independent PDF/font buffers. Ready alias hits consume no reader slot; active source locators are released when their last reader leaves. Reopening a source after its final reader cancels rereads its bytes before sharing by digest, even if another source kept the conversion alive or its PDF is ready.
 
@@ -65,7 +67,7 @@ Each concurrent slot lazily creates and reuses one kit converter. The provider w
 <a id="further-exploration"></a>
 ## Further Exploration
 
-- [Rendering service](../document-convert/README.md) — input and result ownership.
+- [Office to PDF](../../../docs/subsystems/office-to-pdf.md) — composition and input/result ownership.
 - [Workspace Files](../../api/workspace-files/README.md) — Session file authorization and bounded reads.
 
 -----

+ 6 - 4
packages/document/document-convert-libreoffice/README.zh.md → packages/document/office-to-pdf/README.zh.md

@@ -3,7 +3,7 @@ description: "通过独立发布的 LibreOffice kit 在 Host 转换 Office 文
 kind: "package-reference"
 ---
 
-# @deepseek-ai/dsh-document-convert-libreoffice
+# @deepseek-ai/dsh-office-to-pdf
 
 [English](README.md) | 中文
 
@@ -25,7 +25,9 @@ kind: "package-reference"
 <a id="use-this-package"></a>
 ## 使用此包
 
-[Web bundle](../../bundle/web-app/README.zh.md)以 `document-convert` 条目挂载此提供方。独立组合通过 `cordis.yml` 条目挂载 `@deepseek-ai/dsh-document-convert-libreoffice`。
+[Web bundle](../../bundle/web-app/README.zh.md)以 `office-to-pdf` 条目挂载此提供方。独立组合通过 `cordis.yml` 条目挂载 `@deepseek-ai/dsh-office-to-pdf`。
+
+调用方通过 `ctx.officeToPdf.convert()` 提交已授权源的标识、版本、可选字节数、延迟的有界读取、Office 扩展名和调度优先级。源版本变化会拒绝转换。结果包含调用方拥有的 PDF 字节、缺失字体、缓存键和转换 generation;配置替换后 generation 随之改变。取消以原因为拒绝值,转换失败使用 `OfficeToPdfError`。
 
 此 provider 依赖独立发布的 [`@deepseek-ai/libreoffice-kit`](https://github.com/deepseek-harness/libreoffice-kit/tree/main/packages/entry) npm API,kit 版本为 `0.0.1`。应用打包选择 kit 的 `optionalDependencies` 中声明的匹配原生包;目标没有声明原生包时选择 WASM。已声明的原生引擎缺失时拒绝打包,不会选择 WASM。[平台引擎决策](../../../.agents/notes/implemented/architecture/2026-09-15-platform-office-engines.zh.md)定义安装与打包策略;[发布归属决策](../../../.agents/notes/implemented/architecture/2026-09-14-independent-libreoffice-kit.zh.md)定义独立 kit 与 Harness 各自的职责。
 
@@ -38,7 +40,7 @@ kind: "package-reference"
 | `maxImageResolution` | `192` | 最大光栅图像 DPI;覆盖 kit 的默认值 `144`。 |
 | `fontFallbacks` | kit 默认值 | 有序字体族优先组;每组至少包含两个含非空白字符的名称。 |
 
-[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-document-convert-libreoffice)定义全部字体、归档和图像设置。`fontDirectories` 接受绝对目录;省略时使用 kit 的平台默认值。显式 `fontFallbacks` 替换 kit 的默认分组。已安装的请求字体仍优先使用,缺失字形仍可由其他系统字体提供。原生引擎可能在应用这些优先规则前选中已安装的度量兼容字体。
+[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-office-to-pdf)定义全部字体、归档和图像设置。`fontDirectories` 接受绝对目录;省略时使用 kit 的平台默认值。显式 `fontFallbacks` 替换 kit 的默认分组。已安装的请求字体仍优先使用,缺失字形仍可由其他系统字体提供。原生引擎可能在应用这些优先规则前选中已安装的度量兼容字体。
 
 提供方按转换 generation、Office 扩展名和精确源字节的 SHA-256 保留成功 PDF。有界的源版本索引在授权 stat 后避免重读已知内容;内容标识也会在不同源路径之间共享转换。达到任一保留上限时,最近最少使用的 PDF 及其别名一同移除。不保留失败或超过缓存上限的结果。每个结果具有独立的 PDF 与字体缓冲区。已就绪别名命中不占用读取方名额;同一源的最后一个读取方离开时,立即释放其在途定位信息。源的最后一个读取方取消后,再次打开该源会重新读取字节,再按内容摘要共享转换,即使其他源仍保持该转换运行或其 PDF 已就绪。
 
@@ -65,7 +67,7 @@ kind: "package-reference"
 <a id="further-exploration"></a>
 ## 延伸阅读
 
-- [渲染服务](../document-convert/README.zh.md) — 输入和结果所有权。
+- [Office 转 PDF](../../../docs/subsystems/office-to-pdf.zh.md) — 组合与输入、结果所有权。
 - [工作区文件](../../api/workspace-files/README.zh.md) — Session 文件授权与有界读取。
 
 -----

+ 11 - 7
packages/document/document-convert/package.json → packages/document/office-to-pdf/package.json

@@ -1,6 +1,6 @@
 {
-  "name": "@deepseek-ai/dsh-document-convert",
-  "description": "Authorized Office bytes to PDF rendering service",
+  "name": "@deepseek-ai/dsh-office-to-pdf",
+  "description": "Shared Office-to-PDF conversion with bounded queues and caching",
   "version": "0.1.6-alpha.1",
   "publishConfig": {
     "access": "public"
@@ -12,19 +12,19 @@
   "repository": {
     "type": "git",
     "url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
-    "directory": "packages/document/document-convert"
+    "directory": "packages/document/office-to-pdf"
   },
   "exports": {
     ".": {
       "types": "./lib/types/index.d.ts",
       "default": "./lib/index.js"
     },
+    "./src/*": "./src/*",
+    "./package.json": "./package.json",
     "./types": {
       "types": "./lib/types/types.d.ts",
       "default": "./lib/types/types.js"
-    },
-    "./src/*": "./src/*",
-    "./package.json": "./package.json"
+    }
   },
   "files": [
     "lib/index.js",
@@ -35,9 +35,13 @@
     "@deepseek-ai/cordis": "workspace:^"
   },
   "devDependencies": {
-    "@deepseek-ai/cordis": "workspace:^"
+    "@deepseek-ai/cordis": "workspace:^",
+    "@deepseek-ai/cordis-plugin-loader": "workspace:^",
+    "@deepseek-ai/cordis-plugin-include": "workspace:^"
   },
   "dependencies": {
+    "@deepseek-ai/libreoffice-kit": "0.0.1",
+    "@deepseek-ai/schemastery": "workspace:^",
     "@deepseek-ai/dsh-brand": "workspace:^"
   }
 }

+ 15 - 0
packages/document/office-to-pdf/src/errors.ts

@@ -0,0 +1,15 @@
+/** Classified failures shared by conversion and its consumers. */
+import type { OfficeToPdfErrorCode } from './types.ts'
+
+/** Classified conversion failure; engine details stay in the cause. */
+export class OfficeToPdfError extends Error {
+  /**
+   * @param code - category suitable for a conversion consumer.
+   * @param message - diagnostic explaining the failed conversion.
+   * @param options - underlying engine or filesystem failure.
+   */
+  constructor(readonly code: OfficeToPdfErrorCode, message: string, options?: ErrorOptions) {
+    super(message, options)
+    this.name = 'OfficeToPdfError'
+  }
+}

+ 31 - 0
packages/document/office-to-pdf/src/identity.ts

@@ -0,0 +1,31 @@
+/** Source locators and converter-owned identities for shared PDF reuse. */
+import { brandString, type Branded } from '@deepseek-ai/dsh-brand'
+
+/** Authorized execution scope and canonical source path, encoded by the consumer. */
+export type OfficeSourceKey = Branded<'OfficeSourceKey'>
+/**
+ * Label an authorized source locator for pre-read deduplication.
+ * @param key - unambiguous encoding of authorization scope, execution world, and canonical path.
+ * @returns branded source locator; source authorization remains the caller's responsibility.
+ */
+export function OfficeSourceKey(key: string): OfficeSourceKey { return brandString<OfficeSourceKey>(key) }
+
+/** One provider lifetime, including its engine, rendering settings, and font configuration. */
+export type OfficeToPdfGeneration = Branded<'OfficeToPdfGeneration'>
+/**
+ * Label a provider lifetime.
+ * @param value - unique generation created by the provider.
+ * @returns branded converter generation.
+ */
+export function OfficeToPdfGeneration(value: string): OfficeToPdfGeneration {
+  return brandString<OfficeToPdfGeneration>(value)
+}
+
+/** Provider generation and source-content digest; consumers must not parse it. */
+export type OfficeToPdfKey = Branded<'OfficeToPdfKey'>
+/**
+ * Label a converter-owned content identity.
+ * @param value - generation and content identity created by the provider.
+ * @returns branded conversion identity.
+ */
+export function OfficeToPdfKey(value: string): OfficeToPdfKey { return brandString<OfficeToPdfKey>(value) }

+ 35 - 14
packages/document/document-convert-libreoffice/src/index.ts → packages/document/office-to-pdf/src/index.ts

@@ -3,13 +3,26 @@ import { randomUUID } from 'node:crypto'
 import { mkdtemp, rm, writeFile } from 'node:fs/promises'
 import { tmpdir } from 'node:os'
 import { isAbsolute, join } from 'node:path'
-import type { Context } from '@deepseek-ai/cordis'
+import { Context, Service } from '@deepseek-ai/cordis'
 import { createConverter, type Converter, type ConverterOptions } from '@deepseek-ai/libreoffice-kit'
-import { DocumentConverter, DocumentConvertError, DocumentConverterGeneration, type DocumentExtension, type DocumentConvertRequest, type DocumentConvertResult } from '@deepseek-ai/dsh-document-convert'
 import z from '@deepseek-ai/schemastery'
+import { OfficeToPdfError } from './errors.ts'
+import { OfficeToPdfGeneration } from './identity.ts'
+import type { OfficeExtension, OfficeToPdfRequest, OfficeToPdfResult } from './types.ts'
 import { readPdf } from './output.ts'
 import { ConversionQueue } from './queue.ts'
 
+export * from './errors.ts'
+export * from './identity.ts'
+export * from './types.ts'
+
+declare module '@deepseek-ai/cordis' {
+  interface Context {
+    /** Shared Host conversion of already-authorized Office bytes to PDF. */
+    officeToPdf: OfficeToPdf
+  }
+}
+
 /** Provider concurrency and kit rendering/font configuration. */
 export interface Config {
   /** Maximum simultaneous conversions; queued callers remain cancellable. */
@@ -81,9 +94,10 @@ interface Slot {
 }
 
 /** A provider lifetime owns all converters, queued calls, and temporary files. */
-export class LibreOfficeConverter extends DocumentConverter {
+export class OfficeToPdf extends Service {
   static Config = Config
-  readonly generation = DocumentConverterGeneration(randomUUID())
+  /** Changes whenever engine, font, or conversion configuration is replaced. */
+  readonly generation: OfficeToPdfGeneration = OfficeToPdfGeneration(randomUUID())
   private readonly slots: Slot[] = []
   private readonly queue: ConversionQueue
   private readonly options: ConverterOptions
@@ -93,7 +107,7 @@ export class LibreOfficeConverter extends DocumentConverter {
    * @param config - resolved rendering, font, and concurrency limits.
    */
   constructor(ctx: Context, private readonly config: Config) {
-    super(ctx)
+    super(ctx, 'officeToPdf')
     if (config.fontDirectories?.some(path => !isAbsolute(path))) throw new Error('fontDirectories must contain absolute paths.')
     if (config.maxSourceBytes < config.maxInputBytes) throw new Error('maxSourceBytes must be at least maxInputBytes.')
     const { fontDirectories, fontFallbacks, timeoutMs, maxInputBytes, maxOutputBytes, maxImageResolution,
@@ -115,11 +129,18 @@ export class LibreOfficeConverter extends DocumentConverter {
     })
   }
 
-  convert(request: DocumentConvertRequest, signal?: AbortSignal): Promise<DocumentConvertResult> {
+  /**
+   * Convert Office bytes without modifying the source or writing Session events.
+   * @param request - authorized metadata and deferred bounded source read.
+   * @param signal - caller cancellation; provider disposal also stops active work.
+   * @returns caller-owned PDF bytes after conversion and scratch cleanup settle; canceled readers reject independently.
+   * @throws {OfficeToPdfError} Invalid input, unusable output, or engine failure; cancellation rejects with its reason.
+   */
+  convert(request: OfficeToPdfRequest, signal?: AbortSignal): Promise<OfficeToPdfResult> {
     return this.queue.read(request, signal)
   }
 
-  private async convertBytes(bytes: Uint8Array, extension: DocumentExtension, signal: AbortSignal): Promise<Pick<DocumentConvertResult, 'pdf' | 'missingFonts'>> {
+  private async convertBytes(bytes: Uint8Array, extension: OfficeExtension, signal: AbortSignal): Promise<Pick<OfficeToPdfResult, 'pdf' | 'missingFonts'>> {
     signal.throwIfAborted()
     let slot = this.slots.find(candidate => !candidate.busy)
     if (slot === undefined) { slot = { busy: false }; this.slots.push(slot) }
@@ -134,7 +155,7 @@ export class LibreOfficeConverter extends DocumentConverter {
       }
       const converter = await slot.converter
       signal.throwIfAborted()
-      directory = await mkdtemp(join(tmpdir(), 'dsh-document-convert-'))
+      directory = await mkdtemp(join(tmpdir(), 'dsh-office-to-pdf-'))
       const inputPath = join(directory, `source.${extension}`)
       const outputPath = join(directory, 'converted.pdf')
       await writeFile(inputPath, bytes, { flag: 'wx', mode: 0o600, signal })
@@ -144,20 +165,20 @@ export class LibreOfficeConverter extends DocumentConverter {
       let pdf: Uint8Array
       try { pdf = await readPdf(outputPath, this.config.maxOutputBytes, signal) }
       catch (cause) {
-        if (cause instanceof DocumentConvertError) throw cause
-        throw new DocumentConvertError('invalid-output', 'The converter PDF could not be read.', { cause })
+        if (cause instanceof OfficeToPdfError) throw cause
+        throw new OfficeToPdfError('invalid-output', 'The converter PDF could not be read.', { cause })
       }
       signal.throwIfAborted()
       return { pdf, missingFonts: result.missingFonts }
     } catch (cause) {
       signal.throwIfAborted()
-      if (cause instanceof DocumentConvertError) throw cause
+      if (cause instanceof OfficeToPdfError) throw cause
       const code = typeof cause === 'object' && cause !== null && 'code' in cause ? cause.code : undefined
       switch (code) {
         case 'input-too-large': case 'output-too-large': case 'invalid-document': case 'unsupported-format':
         case 'invalid-output': case 'timeout': case 'unavailable':
-          throw new DocumentConvertError(code, 'LibreOffice conversion failed.', { cause })
-        default: throw new DocumentConvertError('failed', 'LibreOffice conversion failed.', { cause })
+          throw new OfficeToPdfError(code, 'LibreOffice conversion failed.', { cause })
+        default: throw new OfficeToPdfError('failed', 'LibreOffice conversion failed.', { cause })
       }
     } finally {
       try { if (directory !== undefined) await rm(directory, { recursive: true, force: true }) }
@@ -166,4 +187,4 @@ export class LibreOfficeConverter extends DocumentConverter {
   }
 }
 
-export default LibreOfficeConverter
+export default OfficeToPdf

+ 6 - 6
packages/document/document-convert-libreoffice/src/output.ts → packages/document/office-to-pdf/src/output.ts

@@ -1,7 +1,7 @@
 /** Read only the bounded, regular PDF created inside a private task directory. */
 import { constants } from 'node:fs'
 import { lstat, open } from 'node:fs/promises'
-import { DocumentConvertError } from '@deepseek-ai/dsh-document-convert'
+import { OfficeToPdfError } from './errors.ts'
 
 /**
  * Refuse missing, link-shaped, oversized, truncated, and non-PDF output.
@@ -13,12 +13,12 @@ import { DocumentConvertError } from '@deepseek-ai/dsh-document-convert'
 export async function readPdf(path: string, limit: number, signal: AbortSignal): Promise<Uint8Array> {
   signal.throwIfAborted()
   const entry = await lstat(path)
-  if (!entry.isFile()) throw new DocumentConvertError('invalid-output', 'The converter output is not a regular file.')
+  if (!entry.isFile()) throw new OfficeToPdfError('invalid-output', 'The converter output is not a regular file.')
   const file = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW)
   try {
     const info = await file.stat()
-    if (!info.isFile()) throw new DocumentConvertError('invalid-output', 'The converter output is not a regular file.')
-    if (info.size > limit) throw new DocumentConvertError('output-too-large', 'The converted PDF exceeds maxOutputBytes.')
+    if (!info.isFile()) throw new OfficeToPdfError('invalid-output', 'The converter output is not a regular file.')
+    if (info.size > limit) throw new OfficeToPdfError('output-too-large', 'The converted PDF exceeds maxOutputBytes.')
     const bytes = Buffer.alloc(info.size + 1)
     let length = 0
     while (length < bytes.length) {
@@ -28,11 +28,11 @@ export async function readPdf(path: string, limit: number, signal: AbortSignal):
       length += read.bytesRead
     }
     signal.throwIfAborted()
-    if (length > limit) throw new DocumentConvertError('output-too-large', 'The converted PDF exceeds maxOutputBytes.')
+    if (length > limit) throw new OfficeToPdfError('output-too-large', 'The converted PDF exceeds maxOutputBytes.')
     const pdf = bytes.subarray(0, length)
     if (length !== info.size || !pdf.subarray(0, 8).toString('ascii').match(/^%PDF-\d\.\d/u)
       || !pdf.subarray(-1024).toString('ascii').trimEnd().endsWith('%%EOF')) {
-      throw new DocumentConvertError('invalid-output', 'The converter did not produce a complete PDF.')
+      throw new OfficeToPdfError('invalid-output', 'The converter did not produce a complete PDF.')
     }
     return pdf
   } finally { await file.close() }

+ 25 - 24
packages/document/document-convert-libreoffice/src/queue.ts → packages/document/office-to-pdf/src/queue.ts

@@ -1,34 +1,35 @@
 /** Bounded source admission, shared content conversion, and caller-owned PDF delivery. */
 import { createHash } from 'node:crypto'
-import { DocumentConvertError, DocumentConvertKey, type DocumentExtension, type DocumentConverterGeneration,
-  type DocumentConvertRequest, type DocumentConvertResult } from '@deepseek-ai/dsh-document-convert'
+import { OfficeToPdfError } from './errors.ts'
+import { OfficeToPdfKey, type OfficeToPdfGeneration } from './identity.ts'
+import type { OfficeExtension, OfficeToPdfRequest, OfficeToPdfResult } from './types.ts'
 import type { Config } from './index.ts'
 
-type Converted = Pick<DocumentConvertResult, 'pdf' | 'missingFonts'>
-type Convert = (bytes: Uint8Array, extension: DocumentExtension, signal: AbortSignal) => Promise<Converted>
+type Converted = Pick<OfficeToPdfResult, 'pdf' | 'missingFonts'>
+type Convert = (bytes: Uint8Array, extension: OfficeExtension, signal: AbortSignal) => Promise<Converted>
 interface Reader {
   job: Job
   readonly source: string
-  readonly priority: DocumentConvertRequest['priority']
-  readonly resolve: (value: DocumentConvertResult) => void
+  readonly priority: OfficeToPdfRequest['priority']
+  readonly resolve: (value: OfficeToPdfResult) => void
   readonly reject: (error: unknown) => void
   readonly cleanup: () => void
 }
 interface Job {
-  readonly request: DocumentConvertRequest
+  readonly request: OfficeToPdfRequest
   readonly controller: AbortController
   readonly readers: Set<Reader>
   readonly sources: Set<string>
-  priority: DocumentConvertRequest['priority']
+  priority: OfficeToPdfRequest['priority']
   state: 'queued' | 'running' | 'finished'
 }
 
 /** One converter generation owns every queued source, conversion, reader, and retained PDF. */
 export class ConversionQueue {
-  private readonly ready = new Map<DocumentConvertKey, DocumentConvertResult>()
-  private readonly aliases = new Map<string, DocumentConvertKey>()
+  private readonly ready = new Map<OfficeToPdfKey, OfficeToPdfResult>()
+  private readonly aliases = new Map<string, OfficeToPdfKey>()
   private readonly sources = new Map<string, Job>()
-  private readonly digests = new Map<DocumentConvertKey, Job>()
+  private readonly digests = new Map<OfficeToPdfKey, Job>()
   private readonly queue: Job[] = []
   private readonly tasks = new Set<Promise<void>>()
   private readonly jobs = new Set<Job>()
@@ -44,7 +45,7 @@ export class ConversionQueue {
    * @param generation - provider lifetime; prevents reuse after engine or font replacement.
    * @param convert - executes one admitted conversion and settles after scratch cleanup.
    */
-  constructor(private readonly config: Config, private readonly generation: DocumentConverterGeneration,
+  constructor(private readonly config: Config, private readonly generation: OfficeToPdfGeneration,
     private readonly convert: Convert) {}
 
   /**
@@ -53,11 +54,11 @@ export class ConversionQueue {
    * @param signal - this reader's cancellation; the final reader cancels shared work.
    * @returns independent PDF bytes; busy or canceled readers reject without releasing active engine capacity early.
    */
-  async read(request: DocumentConvertRequest, signal?: AbortSignal): Promise<DocumentConvertResult> {
+  async read(request: OfficeToPdfRequest, signal?: AbortSignal): Promise<OfficeToPdfResult> {
     signal?.throwIfAborted()
     if (this.disposed) throw this.unavailable()
     if (request.source.bytes !== undefined && request.source.bytes > this.config.maxInputBytes) {
-      throw new DocumentConvertError('input-too-large', 'The Office source exceeds maxInputBytes.')
+      throw new OfficeToPdfError('input-too-large', 'The Office source exceeds maxInputBytes.')
     }
     const source = JSON.stringify([request.source.key, request.source.version, request.extension])
     const alias = this.aliases.get(source)
@@ -87,7 +88,7 @@ export class ConversionQueue {
     const shared = job
     if (request.priority === 'foreground') shared.priority = 'foreground'
     this.readers++
-    const promise = new Promise<DocumentConvertResult>((resolve, reject) => {
+    const promise = new Promise<OfficeToPdfResult>((resolve, reject) => {
       const abort = (): void => {
         this.release(reader)
         const reason: unknown = signal?.reason
@@ -114,9 +115,9 @@ export class ConversionQueue {
     await Promise.allSettled(this.tasks)
   }
 
-  private busy(): DocumentConvertError { return new DocumentConvertError('busy', 'The document converter has reached its admission limit.') }
-  private unavailable(): DocumentConvertError { return new DocumentConvertError('unavailable', 'The document converter is unavailable.') }
-  private copy(result: DocumentConvertResult): DocumentConvertResult {
+  private busy(): OfficeToPdfError { return new OfficeToPdfError('busy', 'The document converter has reached its admission limit.') }
+  private unavailable(): OfficeToPdfError { return new OfficeToPdfError('unavailable', 'The document converter is unavailable.') }
+  private copy(result: OfficeToPdfResult): OfficeToPdfResult {
     return { ...result, pdf: Uint8Array.from(result.pdf), missingFonts: [...result.missingFonts] }
   }
   private release(reader: Reader): void {
@@ -177,10 +178,10 @@ export class ConversionQueue {
     signal.throwIfAborted()
     const input = await job.request.source.read(signal, reserved)
     signal.throwIfAborted()
-    if (input.version !== job.request.source.version) throw new DocumentConvertError('source-changed', 'The source changed while waiting for conversion.')
-    if (input.bytes.byteLength > reserved) throw new DocumentConvertError('input-too-large', 'The source exceeds its reserved read capacity.')
+    if (input.version !== job.request.source.version) throw new OfficeToPdfError('source-changed', 'The source changed while waiting for conversion.')
+    if (input.bytes.byteLength > reserved) throw new OfficeToPdfError('input-too-large', 'The source exceeds its reserved read capacity.')
     const digest = createHash('sha256').update(job.request.extension).update('\0').update(input.bytes).digest('hex')
-    const key = DocumentConvertKey(`${this.generation}:${digest}`)
+    const key = OfficeToPdfKey(`${this.generation}:${digest}`)
     const cached = this.ready.get(key)
     if (cached !== undefined) {
       this.ready.delete(key)
@@ -207,7 +208,7 @@ export class ConversionQueue {
     } finally { if (this.digests.get(key) === job) this.digests.delete(key) }
   }
 
-  private finish(job: Job, result: DocumentConvertResult): void {
+  private finish(job: Job, result: OfficeToPdfResult): void {
     for (const source of job.sources) this.sources.delete(source)
     if (this.ready.has(result.cacheKey)) {
       for (const source of job.sources) {
@@ -218,10 +219,10 @@ export class ConversionQueue {
     }
     for (const reader of job.readers) { this.release(reader); reader.resolve(this.copy(result)) }
   }
-  private retain(result: DocumentConvertResult): void {
+  private retain(result: OfficeToPdfResult): void {
     if (result.pdf.byteLength > this.config.maxCachedBytes) return
     while (this.ready.size >= this.config.maxCachedEntries || this.cachedBytes + result.pdf.byteLength > this.config.maxCachedBytes) {
-      const [key, oldest] = this.ready.entries().next().value as [DocumentConvertKey, DocumentConvertResult]
+      const [key, oldest] = this.ready.entries().next().value as [OfficeToPdfKey, OfficeToPdfResult]
       this.ready.delete(key)
       this.cachedBytes -= oldest.pdf.byteLength
       for (const [source, digest] of this.aliases) if (digest === key) this.aliases.delete(source)

+ 43 - 0
packages/document/office-to-pdf/src/types.ts

@@ -0,0 +1,43 @@
+/** Authorized Office input and complete PDF output, for the shared Host converter. */
+import type { OfficeSourceKey, OfficeToPdfGeneration, OfficeToPdfKey } from './identity.ts'
+export type { OfficeSourceKey, OfficeToPdfGeneration, OfficeToPdfKey } from './identity.ts'
+
+/** Binary Office and Office Open XML formats supported by Office-to-PDF conversion. */
+export type OfficeExtension = 'doc' | 'docx' | 'xls' | 'xlsx' | 'ppt' | 'pptx'
+
+/** Foreground previews and explicit QA precede speculative background conversion. */
+export type OfficeToPdfPriority = 'foreground' | 'background'
+
+/** Source authorization and metadata lookup must finish before submitting a request. */
+export interface OfficeToPdfRequest {
+  readonly extension: OfficeExtension
+  readonly priority: OfficeToPdfPriority
+  readonly source: {
+    readonly key: OfficeSourceKey
+    readonly version: string
+    /** Authorized stat size; omission reserves the provider's entire input limit. */
+    readonly bytes?: number
+    /**
+     * Read only after provider admission; do not capture already-buffered input in queued production requests.
+     * @param signal - shared conversion lifetime, independent of an individual reader.
+     * @param maxBytes - reserved source capacity; read at most this plus one overflow sentinel byte.
+     * @returns owned bytes and the actual read version; a changed version rejects conversion.
+     */
+    read(signal: AbortSignal, maxBytes: number): Promise<{ readonly bytes: Uint8Array; readonly version: string }>
+  }
+}
+
+/** Successful conversion; failed and interrupted conversions reject instead. */
+export interface OfficeToPdfResult {
+  /** Caller-owned complete PDF, valid after provider disposal. */
+  readonly pdf: Uint8Array
+  /** Requested OOXML font families unavailable to this conversion; binary Office formats return an empty list. */
+  readonly missingFonts: string[]
+  readonly cacheKey: OfficeToPdfKey
+  readonly generation: OfficeToPdfGeneration
+}
+
+/** Failures a conversion consumer can present without exposing engine diagnostics. */
+export type OfficeToPdfErrorCode =
+  | 'input-too-large' | 'output-too-large' | 'invalid-document' | 'unsupported-format'
+  | 'invalid-output' | 'timeout' | 'unavailable' | 'failed' | 'busy' | 'source-changed'

+ 0 - 0
packages/document/document-convert-libreoffice/tests/output.spec.ts → packages/document/office-to-pdf/tests/output.spec.ts


+ 10 - 11
packages/document/document-convert-libreoffice/tests/provider.spec.ts → packages/document/office-to-pdf/tests/provider.spec.ts

@@ -1,23 +1,22 @@
 /** Disk output, resource bounds, and cancellation around the external kit. */
 import { access, mkdir, readFile, stat, writeFile } from 'node:fs/promises'
 import { dirname } from 'node:path'
-import { DocumentSourceKey, type DocumentConvertRequest } from '@deepseek-ai/dsh-document-convert'
 import { Context } from '@deepseek-ai/cordis'
 import type { Converter, ConverterOptions } from '@deepseek-ai/libreoffice-kit'
 import { afterEach, beforeEach, expect, it, vi } from 'vitest'
-import LibreOfficeConverter, { Config } from '../src/index.ts'
+import OfficeToPdf, { Config, OfficeSourceKey, type OfficeToPdfRequest } from '../src/index.ts'
 
 const kit = vi.hoisted(() => ({ create: vi.fn<(options?: ConverterOptions) => Promise<Converter>>() }))
 vi.mock('@deepseek-ai/libreoffice-kit', () => ({ createConverter: kit.create }))
 
 const pdf = Buffer.from('%PDF-1.7\npreview\n%%EOF\n')
 const input = new Uint8Array([80, 75, 3, 4])
-const request: DocumentConvertRequest = { extension: 'docx', priority: 'foreground', source: {
-  key: DocumentSourceKey('source'), version: 'v1', bytes: input.length,
+const request: OfficeToPdfRequest = { extension: 'docx', priority: 'foreground', source: {
+  key: OfficeSourceKey('source'), version: 'v1', bytes: input.length,
   read: async () => ({ bytes: input, version: 'v1' }),
 } }
-function distinct(index: number): DocumentConvertRequest {
-  return { ...request, source: { ...request.source, key: DocumentSourceKey(`source-${index}`),
+function distinct(index: number): OfficeToPdfRequest {
+  return { ...request, source: { ...request.source, key: OfficeSourceKey(`source-${index}`),
     read: async () => ({ bytes: new Uint8Array([80, 75, 3, index]), version: 'v1' }) } }
 }
 let ctx: Context
@@ -35,9 +34,9 @@ beforeEach(() => {
 })
 afterEach(async () => { await ctx.fiber.dispose() })
 
-async function mount(config: Partial<Config> = {}): Promise<LibreOfficeConverter> {
-  await ctx.plugin(LibreOfficeConverter, config)
-  return ctx.documentConvert as LibreOfficeConverter
+async function mount(config: Partial<Config> = {}): Promise<OfficeToPdf> {
+  await ctx.plugin(OfficeToPdf, config)
+  return ctx.officeToPdf
 }
 
 it('preserves the kit font defaults when font configuration is omitted', async () => {
@@ -231,12 +230,12 @@ it('rejects caller cancellation before allocating any conversion resources', asy
 })
 
 it('rejects relative font directories during provider configuration', async () => {
-  expect(() => new LibreOfficeConverter(ctx, Config({ fontDirectories: ['relative/fonts'] })))
+  expect(() => new OfficeToPdf(ctx, Config({ fontDirectories: ['relative/fonts'] })))
     .toThrow('fontDirectories must contain absolute paths')
 })
 
 it('rejects source capacity below one permitted input before allocating a converter', () => {
-  expect(() => new LibreOfficeConverter(ctx, Config({ maxInputBytes: 4, maxSourceBytes: 3 })))
+  expect(() => new OfficeToPdf(ctx, Config({ maxInputBytes: 4, maxSourceBytes: 3 })))
     .toThrow('maxSourceBytes must be at least maxInputBytes')
   expect(kit.create).not.toHaveBeenCalled()
 })

+ 6 - 7
packages/document/document-convert-libreoffice/tests/queue.spec.ts → packages/document/office-to-pdf/tests/queue.spec.ts

@@ -1,18 +1,17 @@
 /** Controlled source and engine completions exercise admission and shared content ownership. */
-import { DocumentSourceKey, DocumentConverterGeneration, type DocumentConvertRequest } from '@deepseek-ai/dsh-document-convert'
 import { expect, it, onTestFinished, vi } from 'vitest'
 import { ConversionQueue } from '../src/queue.ts'
-import { Config } from '../src/index.ts'
+import { Config, OfficeSourceKey, OfficeToPdfGeneration, type OfficeToPdfRequest } from '../src/index.ts'
 
 const output = { pdf: new Uint8Array([37, 80, 68, 70]), missingFonts: ['Font'] }
-function source(key: string, byte = 1, priority: DocumentConvertRequest['priority'] = 'foreground') {
-  const read = vi.fn<DocumentConvertRequest['source']['read']>().mockResolvedValue({ bytes: new Uint8Array([byte]), version: 'v1' })
-  const request: DocumentConvertRequest = { extension: 'docx', priority, source: { key: DocumentSourceKey(key), version: 'v1', bytes: 1, read } }
+function source(key: string, byte = 1, priority: OfficeToPdfRequest['priority'] = 'foreground') {
+  const read = vi.fn<OfficeToPdfRequest['source']['read']>().mockResolvedValue({ bytes: new Uint8Array([byte]), version: 'v1' })
+  const request: OfficeToPdfRequest = { extension: 'docx', priority, source: { key: OfficeSourceKey(key), version: 'v1', bytes: 1, read } }
   return { read, request }
 }
 function harness(config: Partial<Config> = {}) {
   const convert = vi.fn<ConstructorParameters<typeof ConversionQueue>[2]>().mockResolvedValue(output)
-  const queue = new ConversionQueue(Config(config), DocumentConverterGeneration('test'), convert)
+  const queue = new ConversionQueue(Config(config), OfficeToPdfGeneration('test'), convert)
   onTestFinished(() => queue.dispose())
   return { queue, convert }
 }
@@ -336,7 +335,7 @@ it('separates Office extensions and converter generations in content identity',
   const h = harness(), a = source('a')
   const first = await h.queue.read(a.request)
   const otherFormat = await h.queue.read({ ...a.request, extension: 'pptx' })
-  const other = new ConversionQueue(Config({}), DocumentConverterGeneration('replacement'), h.convert)
+  const other = new ConversionQueue(Config({}), OfficeToPdfGeneration('replacement'), h.convert)
   onTestFinished(() => other.dispose())
   const replacement = await other.read(a.request)
   expect(first.cacheKey).not.toBe(otherFormat.cacheKey)

+ 1 - 1
packages/document/document-convert-libreoffice/tsconfig.json → packages/document/office-to-pdf/tsconfig.json

@@ -15,7 +15,7 @@
       "path": "../../../vendor/schemastery"
     },
     {
-      "path": "../document-convert"
+      "path": "../../util/brand"
     }
   ]
 }

+ 1 - 1
packages/experimental/webworker-runtime/src/node/external_packages/libreoffice-kit.ts

@@ -3,7 +3,7 @@ import { notAvailableError } from '../notImplementedFail.ts'
 
 /**
  * Refuse converter creation with the kit's unavailable error code.
- * @returns A rejected promise consumed by the Host document-convert provider.
+ * @returns A rejected promise consumed by the Host office-to-pdf provider.
  */
 export function createConverter(): Promise<never> {
   return Promise.reject(Object.assign(notAvailableError('@deepseek-ai/libreoffice-kit', 'createConverter'), {

+ 47 - 47
packages/extensions/tool-cordis/src/api-catalog.ts

@@ -874,25 +874,6 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
       },
     ],
   },
-  {
-    key: 'documentConvert',
-    summary: 'Load one provider subclass per context; consumers own source authorization.',
-    description: 'Load one provider subclass per context; consumers own source authorization.',
-    methods: [
-      {
-        signature: 'abstract readonly generation: DocumentConverterGeneration',
-        description: 'Changes whenever engine, font, or conversion configuration is replaced.',
-        parameters: [],
-      },
-      {
-        signature: 'abstract convert(request: DocumentConvertRequest, signal?: AbortSignal): Promise<DocumentConvertResult>',
-        description: 'Convert Office bytes without modifying the source or writing Session events.',
-        parameters: [{ name: 'request', description: 'authorized metadata and deferred bounded source read.' }, { name: 'signal', description: 'caller cancellation; provider disposal also stops active work.' }],
-        returns: 'caller-owned PDF bytes after conversion and scratch cleanup settle; canceled readers reject independently.',
-        throws: ['{DocumentConvertError} Invalid input, unusable output, or engine failure; cancellation rejects with its reason.'],
-      },
-    ],
-  },
   {
     key: 'fileReferences',
     summary: 'Host capability for cancellable file-reference discovery.',
@@ -1389,6 +1370,25 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
       },
     ],
   },
+  {
+    key: 'officeToPdf',
+    summary: 'A provider lifetime owns all converters, queued calls, and temporary files.',
+    description: 'A provider lifetime owns all converters, queued calls, and temporary files.',
+    methods: [
+      {
+        signature: 'readonly generation: OfficeToPdfGeneration = OfficeToPdfGeneration(randomUUID())',
+        description: 'Changes whenever engine, font, or conversion configuration is replaced.',
+        parameters: [],
+      },
+      {
+        signature: 'convert(request: OfficeToPdfRequest, signal?: AbortSignal): Promise<OfficeToPdfResult>',
+        description: 'Convert Office bytes without modifying the source or writing Session events.',
+        parameters: [{ name: 'request', description: 'authorized metadata and deferred bounded source read.' }, { name: 'signal', description: 'caller cancellation; provider disposal also stops active work.' }],
+        returns: 'caller-owned PDF bytes after conversion and scratch cleanup settle; canceled readers reject independently.',
+        throws: ['{OfficeToPdfError} Invalid input, unusable output, or engine failure; cancellation rejects with its reason.'],
+      },
+    ],
+  },
   {
     key: 'permissionPresets',
     summary: 'Owns the deployment\'s configured permission presets, the fixed Auto integration hook, and their write path.',
@@ -4442,34 +4442,6 @@ export const TYPE_API: readonly TypeApiEntry[] = [
     name: 'DirectoryRegistrationHandle',
     declaration: 'export interface DirectoryRegistrationHandle {\n    (): void;\n    replace(entries: readonly LlmConfigurableProvider[]): void;\n}',
   },
-  {
-    name: 'DocumentConverterGeneration',
-    declaration: 'export type DocumentConverterGeneration = Branded<\'DocumentConverterGeneration\'>;',
-  },
-  {
-    name: 'DocumentConvertKey',
-    declaration: 'export type DocumentConvertKey = Branded<\'DocumentConvertKey\'>;',
-  },
-  {
-    name: 'DocumentConvertPriority',
-    declaration: 'export type DocumentConvertPriority = \'foreground\' | \'background\';',
-  },
-  {
-    name: 'DocumentConvertRequest',
-    declaration: 'export interface DocumentConvertRequest {\n    readonly extension: DocumentExtension;\n    readonly priority: DocumentConvertPriority;\n    readonly source: {\n        readonly key: DocumentSourceKey;\n        readonly version: string;\n        readonly bytes?: number;\n        read(signal: AbortSignal, maxBytes: number): Promise<{\n            readonly bytes: Uint8Array;\n            readonly version: string;\n        }>;\n    };\n}',
-  },
-  {
-    name: 'DocumentConvertResult',
-    declaration: 'export interface DocumentConvertResult {\n    readonly pdf: Uint8Array;\n    readonly missingFonts: string[];\n    readonly cacheKey: DocumentConvertKey;\n    readonly generation: DocumentConverterGeneration;\n}',
-  },
-  {
-    name: 'DocumentExtension',
-    declaration: 'export type DocumentExtension = \'doc\' | \'docx\' | \'xls\' | \'xlsx\' | \'ppt\' | \'pptx\';',
-  },
-  {
-    name: 'DocumentSourceKey',
-    declaration: 'export type DocumentSourceKey = Branded<\'DocumentSourceKey\'>;',
-  },
   {
     name: 'Domain',
     declaration: 'export interface Domain<S extends DomainSpec> {\n    readonly name: string;\n    readonly global: DomainGlobalHandleOf<S>;\n    table<N extends keyof S[\'tables\'] & string>(name: N): KvTable<TableKeyOf<S, N>, TableValueOf<S, N>>;\n    close(): Promise<void>;\n}',
@@ -5114,6 +5086,34 @@ export const TYPE_API: readonly TypeApiEntry[] = [
     name: 'ObjectJsonSchema',
     declaration: 'export type ObjectJsonSchema = JsonSchemaNode & {\n    type: \'object\';\n};',
   },
+  {
+    name: 'OfficeExtension',
+    declaration: 'export type OfficeExtension = \'doc\' | \'docx\' | \'xls\' | \'xlsx\' | \'ppt\' | \'pptx\';',
+  },
+  {
+    name: 'OfficeSourceKey',
+    declaration: 'export type OfficeSourceKey = Branded<\'OfficeSourceKey\'>;',
+  },
+  {
+    name: 'OfficeToPdfGeneration',
+    declaration: 'export type OfficeToPdfGeneration = Branded<\'OfficeToPdfGeneration\'>;',
+  },
+  {
+    name: 'OfficeToPdfKey',
+    declaration: 'export type OfficeToPdfKey = Branded<\'OfficeToPdfKey\'>;',
+  },
+  {
+    name: 'OfficeToPdfPriority',
+    declaration: 'export type OfficeToPdfPriority = \'foreground\' | \'background\';',
+  },
+  {
+    name: 'OfficeToPdfRequest',
+    declaration: 'export interface OfficeToPdfRequest {\n    readonly extension: OfficeExtension;\n    readonly priority: OfficeToPdfPriority;\n    readonly source: {\n        readonly key: OfficeSourceKey;\n        readonly version: string;\n        readonly bytes?: number;\n        read(signal: AbortSignal, maxBytes: number): Promise<{\n            readonly bytes: Uint8Array;\n            readonly version: string;\n        }>;\n    };\n}',
+  },
+  {
+    name: 'OfficeToPdfResult',
+    declaration: 'export interface OfficeToPdfResult {\n    readonly pdf: Uint8Array;\n    readonly missingFonts: string[];\n    readonly cacheKey: OfficeToPdfKey;\n    readonly generation: OfficeToPdfGeneration;\n}',
+  },
   {
     name: 'OneShotSubagentDescriptorData',
     declaration: 'export interface OneShotSubagentDescriptorData extends SubagentDescriptorBase {\n    readonly mode: \'one-shot\';\n    readonly label?: string;\n}',

+ 4 - 17
pnpm-lock.yaml

@@ -2205,12 +2205,6 @@ importers:
       '@deepseek-ai/dsh-cordis-host-runner':
         specifier: workspace:^
         version: link:../../extensions/cordis-host-runner
-      '@deepseek-ai/dsh-document-convert':
-        specifier: workspace:^
-        version: link:../../document/document-convert
-      '@deepseek-ai/dsh-document-convert-libreoffice':
-        specifier: workspace:^
-        version: link:../../document/document-convert-libreoffice
       '@deepseek-ai/dsh-file-reference':
         specifier: workspace:^
         version: link:../../context/file-reference
@@ -2244,6 +2238,9 @@ importers:
       '@deepseek-ai/dsh-message-feedback':
         specifier: workspace:^
         version: link:../../feedback/message-feedback
+      '@deepseek-ai/dsh-office-to-pdf':
+        specifier: workspace:^
+        version: link:../../document/office-to-pdf
       '@deepseek-ai/dsh-session-log-export':
         specifier: workspace:^
         version: link:../../session-query/session-log-export
@@ -5814,18 +5811,11 @@ importers:
         specifier: workspace:^
         version: link:../../util/launch-environment
 
-  packages/document/document-convert:
+  packages/document/office-to-pdf:
     dependencies:
       '@deepseek-ai/dsh-brand':
         specifier: workspace:^
         version: link:../../util/brand
-    devDependencies:
-      '@deepseek-ai/cordis':
-        specifier: workspace:^
-        version: link:../../../vendor/cordis
-
-  packages/document/document-convert-libreoffice:
-    dependencies:
       '@deepseek-ai/libreoffice-kit':
         specifier: 0.0.1
         version: 0.0.1
@@ -5842,9 +5832,6 @@ importers:
       '@deepseek-ai/cordis-plugin-loader':
         specifier: workspace:^
         version: link:../../../vendor/loader
-      '@deepseek-ai/dsh-document-convert':
-        specifier: workspace:^
-        version: link:../document-convert
 
   packages/experimental/agent-team:
     dependencies:

+ 5 - 5
scripts/gen-cordis-catalog.ts

@@ -63,7 +63,7 @@ export const SERVICE_PAGE: Record<string, string> = {
   agentPresets: 'core.md',
   agents: 'core.md',
   approval: 'approval.md',
-  documentConvert: 'document-convert.md',
+  officeToPdf: 'office-to-pdf.md',
   attachments: 'attachment.md',
   shell: 'shell.md',
   shellEnv: 'shell.md',
@@ -274,10 +274,10 @@ export const LINK_MAP: Readonly<Record<string, string>> = {
   PluginInstallProgress: 'boot.md',
   BrowserUseProviderName: 'browser-use.md',
   ComputerUseProviderName: 'computer-use.md',
-  DocumentConvertRequest: 'document-convert.md',
-  DocumentConvertResult: 'document-convert.md',
-  DocumentConvertPriority: 'document-convert.md',
-  DocumentConverterGeneration: 'document-convert.md',
+  OfficeToPdfRequest: 'office-to-pdf.md',
+  OfficeToPdfResult: 'office-to-pdf.md',
+  OfficeToPdfPriority: 'office-to-pdf.md',
+  OfficeToPdfGeneration: 'office-to-pdf.md',
   Agent: 'core.md',
   AgentCancelCause: 'core.md',
   AgentFactory: 'core.md',

+ 2 - 2
scripts/gen-doc-graphs.ts

@@ -151,8 +151,8 @@ const SERVICE_ROLES: ServiceRole[] = [
     note: 'One provider-owned name per service instance. Each provider also owns its model tools; the service has no common action API, runtime selection, or Session workflow lock.',
   },
   {
-    key: 'documentConvert', pkg: 'document-convert', title: 'Office to PDF conversion',
-    mode: 'seam', implementations: ['document-convert-libreoffice'], consumers: [],
+    key: 'officeToPdf', pkg: 'office-to-pdf', title: 'Office to PDF conversion',
+    mode: 'core', consumers: [],
     note: 'Authorized Office bytes are converted on the Host using the declared native target engine, or Node WASM when no native target is declared.',
   },
   {

+ 1 - 2
scripts/verify-package-readme-model-experience.ts

@@ -53,8 +53,7 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly<Record<string, SentenceContract>> = {
   'packages/browser-use/browser-use': { kind: 'none', reason: 'The registry only reserves a provider name; providers own browser tools and Session resources.' },
   'packages/experimental/browser-use-runtime': { kind: 'indirect', reason: 'Browser providers call the library to own resources and expose upstream MCP tools.' },
   'packages/computer-use/computer-use': { kind: 'none', reason: 'The registry only reserves provider names; providers own all model-facing tools and guidance.' },
-  'packages/document/document-convert': { kind: 'none', reason: 'Conversion callers own model tools and messages.' },
-  'packages/document/document-convert-libreoffice': { kind: 'none', reason: 'The provider returns PDF bytes without constructing model input.' },
+  'packages/document/office-to-pdf': { kind: 'none', reason: 'The provider returns PDF bytes without constructing model input.' },
   'packages/attachment/attachment': { kind: 'indirect', reason: 'The storage seam delegates model request rendering to provider adapters.' },
   'packages/attachment/attachment-local': { kind: 'indirect', reason: 'The local backend delegates model request rendering to provider adapters.' },
   'packages/shell/shell': { kind: 'indirect', reason: 'The service interface delegates all model rendering to dsh-tool-bash.' },

+ 2 - 3
tsconfig.base.json

@@ -37,7 +37,7 @@
       "@deepseek-ai/dsh-experimental-browser-use-stagehand-native": ["./packages/experimental/browser-use-stagehand-native/src/index.ts"],
       "@deepseek-ai/dsh-experimental-browser-use-runtime/mcp": ["./packages/experimental/browser-use-runtime/src/mcp.ts"],
 
-      "@deepseek-ai/dsh-document-convert/types": ["./packages/document/document-convert/src/types.ts"],
+      "@deepseek-ai/dsh-office-to-pdf/types": ["./packages/document/office-to-pdf/src/types.ts"],
       "@deepseek-ai/dsh-api-terminal-controller": ["./packages/api/terminal-controller/src/index.ts"],
       "@deepseek-ai/dsh-api-terminal-controller/types": ["./packages/api/terminal-controller/src/types.ts"],
       "@deepseek-ai/dsh-api-terminal-controller/client": ["./packages/api/terminal-controller/src/client/index.ts"],
@@ -325,8 +325,6 @@
       "@deepseek-ai/dsh-credentials-local": ["./packages/credentials/credentials-local/src"],
       "@deepseek-ai/dsh-deepseek-llm-api-extensions": ["./packages/llm/deepseek-llm-api-extensions/src"],
       "@deepseek-ai/dsh-deque": ["./packages/util/deque/src"],
-      "@deepseek-ai/dsh-document-convert": ["./packages/document/document-convert/src"],
-      "@deepseek-ai/dsh-document-convert-libreoffice": ["./packages/document/document-convert-libreoffice/src"],
       "@deepseek-ai/dsh-file-reference": ["./packages/context/file-reference/src"],
       "@deepseek-ai/dsh-file-reference-local": ["./packages/context/file-reference-local/src"],
       "@deepseek-ai/dsh-fs": ["./packages/fs/fs/src"],
@@ -366,6 +364,7 @@
       "@deepseek-ai/dsh-mcp-resources": ["./packages/mcp/mcp-resources/src"],
       "@deepseek-ai/dsh-message-feedback": ["./packages/feedback/message-feedback/src"],
       "@deepseek-ai/dsh-native-command": ["./packages/util/native-command/src"],
+      "@deepseek-ai/dsh-office-to-pdf": ["./packages/document/office-to-pdf/src"],
       "@deepseek-ai/dsh-output-retention": ["./packages/util/output-retention/src"],
       "@deepseek-ai/dsh-package-manifest": ["./packages/util/package-manifest/src"],
       "@deepseek-ai/dsh-permission-presets": ["./packages/interaction/permission-presets/src"],

+ 1 - 2
tsconfig.host.json

@@ -159,8 +159,7 @@
     { "path": "./packages/computer-use/computer-use" },
     { "path": "./packages/experimental/computer-use-cua-driver-mcp" },
     { "path": "./packages/experimental/computer-use-cua-driver-native" },
-    { "path": "./packages/document/document-convert" },
-    { "path": "./packages/document/document-convert-libreoffice" },
+    { "path": "./packages/document/office-to-pdf" },
     { "path": "./packages/client/ui-deliverables/tsconfig.host.json" },
     { "path": "./vendor/cosmokit" },
     { "path": "./vendor/schemastery" },