diff --git a/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.i18n.yaml index 2550d722da..d12179dde4 100644 --- a/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.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-07-10-single-file-executable-sdk-runtime-distribution.md -2026-07-10-single-file-executable-sdk-runtime-distribution.md: 45191be901f8df048d48798158c83fdfd8134737 -2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md: b76035e6c81841e5eda4dcbcd575479996eff4b9 +2026-07-10-single-file-executable-sdk-runtime-distribution.md: 56c9770966647e97763e0eaf339e4589fba673fb +2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md: 04351105a56304020f5696cf1ce82d7a83ed2ced diff --git a/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md b/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md index 45191be901..56c9770966 100644 --- a/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md +++ b/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md @@ -72,7 +72,7 @@ The verification surface has three tiers. Mechanism tier: the measured conclusio Manual-driving caveat: the bin treats stdin EOF as "the client is gone" and disposes immediately, so a short-lived pipe aborts an in-flight turn — pipe-driven runs must keep stdin open until the turn ends. -The installed-wheel `sdk-office` scenario copies the target's complete payload to a private temporary directory, requires exactly one target backend (WASM on Linux, native on macOS/Windows), and converts DOCX once through the shipped SDK profile with that engine. It checks that the kit module resolves inside the relocated directory, that conversion reports the expected backend, and that the output contains PDF bytes. Wheel validation checks the declared engine assets and native helper execute bit; CI uploads the completed wheel, so nested helper permissions stay inside the ZIP archive. +The installed-wheel `sdk-office` scenario copies the target's complete payload to a private temporary directory, requires exactly one target backend (the declared native target, or WASM when no native target is declared), and converts DOCX once through the shipped SDK profile with that engine. It checks that the kit module resolves inside the relocated directory, that conversion reports the expected backend, and that the output contains PDF bytes. Wheel validation checks the declared engine assets and native helper execute bit; CI uploads the completed wheel, so nested helper permissions stay inside the ZIP archive. ## Alternatives considered diff --git a/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md b/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md index b76035e6c8..04351105a5 100644 --- a/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md @@ -72,7 +72,7 @@ Python 客户端使用所选 profile(默认 `sdk`)、有序 patch 文件和 手工驱动注意:`bin` 将 stdin EOF 视为「客户端已离开」并立即 dispose,生命周期较短的管道会中止进行中的轮次——管道驱动必须保持 stdin 打开,直到轮次结束。 -安装后 wheel 的 `sdk-office` 场景会将目标的完整载荷复制到私有临时目录,要求仅存在一个目标后端(Linux 为 WASM,macOS/Windows 为原生),并通过随附 SDK profile 使用该引擎转换一次 DOCX。它检查 kit 模块确实解析到迁移后的目录、转换报告预期后端,且输出包含 PDF 字节。wheel 校验会检查声明的引擎资源与原生辅助程序执行位;CI 上传已完成的 wheel,因此嵌套辅助程序权限保留在 ZIP 归档中。 +安装后 wheel 的 `sdk-office` 场景会将目标的完整载荷复制到私有临时目录,要求仅存在一个目标后端(使用已声明的原生目标,未声明原生目标时使用 WASM),并通过随附 SDK profile 使用该引擎转换一次 DOCX。它检查 kit 模块确实解析到迁移后的目录、转换报告预期后端,且输出包含 PDF 字节。wheel 校验会检查声明的引擎资源与原生辅助程序执行位;CI 上传已完成的 wheel,因此嵌套辅助程序权限保留在 ZIP 归档中。 ## 曾考虑的替代方案 diff --git a/.agents/notes/implemented/architecture/2026-09-15-platform-office-engines.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-15-platform-office-engines.i18n.yaml index 8fd429f5bf..e10cadf677 100644 --- a/.agents/notes/implemented/architecture/2026-09-15-platform-office-engines.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-15-platform-office-engines.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-platform-office-engines.md -2026-09-15-platform-office-engines.md: fa8656eb7505b599f76640077f9b957fd867269c -2026-09-15-platform-office-engines.zh.md: eed1879b9a0bd5fe5056670c414c4db4b81d9d9b +2026-09-15-platform-office-engines.md: a3d1365bf33006c31d53c54f1255f72c4663cb5d +2026-09-15-platform-office-engines.zh.md: dfffd1b9de33a2b138e0450f8a60d940fadcefd4 diff --git a/.agents/notes/implemented/architecture/2026-09-15-platform-office-engines.md b/.agents/notes/implemented/architecture/2026-09-15-platform-office-engines.md index fa8656eb75..a3d1365bf3 100644 --- a/.agents/notes/implemented/architecture/2026-09-15-platform-office-engines.md +++ b/.agents/notes/implemented/architecture/2026-09-15-platform-office-engines.md @@ -10,20 +10,20 @@ Installing WASM beside a usable native engine adds a second LibreOffice payload ## Decision -Harness depends on the kit API and uses its platform-filtered optional engine dependencies. macOS and Windows install their matching ARM64 or x64 native package; Linux installs the shared WASM package. WASM declares Linux as its npm OS. The provider has no direct WASM dependency. Missing native engines on macOS/Windows reject converter creation instead of selecting WASM. +Harness selects one engine from the installed kit API’s `optionalDependencies`. A declared `@deepseek-ai/libreoffice-kit-${platform}-${arch}` requires that native package; other targets require the shared WASM package. The supported native target set belongs to the kit release, not an operating-system branch in Harness. A missing declared native package is an incomplete installation and never selects WASM. The provider has no direct WASM dependency. -Python sidecar assembly copies only the target engine and its dependency closure. Wheel packaging and runtime lookup require that engine; the relocated conversion smoke checks the selected backend. Desktop carries the ordinary npm installation in application resources. Engine compilation, qualification, and publication remain in the kit repository. +Python sidecar assembly copies only the selected engine and its dependency closure. Wheel packaging and runtime lookup select from the same kit manifest; the relocated conversion smoke checks the selected backend. Desktop filters its npm installation to that engine before signing and integrity sealing. Engine compilation, resolver compatibility, npm platform metadata, qualification, and publication remain in the kit repository. ## Alternatives considered -**Keep WASM beside every native engine.** This tolerates a missing optional native package but increases every macOS/Windows installation. Fixed-platform distributions require their native engine to be present and tested instead. +**Keep WASM beside every native engine.** This tolerates a missing optional native package but increases every distribution with native support. Fixed-platform distributions require their declared native engine to be present and tested instead. -**Remove WASM on every platform.** Linux has no released native engine in this kit family. Keeping the shared WASM package on Linux preserves Office conversion without introducing a new native release target. +**Remove WASM on every platform.** Targets without a released native engine still need conversion. The shared WASM package serves those targets without introducing a new native release target. **Make the Python Office sidecar optional.** The runtime carries the shared `dsh` CLI and its Web profile as well as the default SDK profile. Requiring the target engine gives the installed wheel a complete shipped profile set and reports an incomplete payload before launch. SDK and headless users also pay the engine download and installed-size cost. ## Consequences -macOS/Windows distributions omit the WASM download and expanded assets. A missing native package becomes an installation defect that must be repaired; conversion does not download an engine. Linux retains its WASM resource and font requirements. The kit’s engine-selection tests and actual npm installation tests cover platform selection; Harness sidecar, wheel, and runtime-resolution tests cover packaging requirements. New package bytes require kit qualification and matching dependency integrity records before publication. +Distributions with a declared native target omit WASM assets. Other targets retain WASM resource and font requirements. The pinned kit declares macOS/Windows ARM64 and x64 native packages, so current Linux distributions select WASM; a kit release can add a native Linux target without changing Harness’s selection rule. This does not expand Harness’s supported release platforms. Harness sidecar, wheel, and runtime-resolution tests cover both declared native targets and WASM selection, including missing native packages. New package bytes require kit qualification and matching dependency integrity records before publication. The [public Python release workflow](../../../../.github/workflows/python-release.yml) rejects any wheel at or above 100,000,000 bytes. Selecting one engine reduces payload size but does not establish that a runtime wheel meets this limit; npm engine publication and local conversion are separate from wheel upload eligibility. diff --git a/.agents/notes/implemented/architecture/2026-09-15-platform-office-engines.zh.md b/.agents/notes/implemented/architecture/2026-09-15-platform-office-engines.zh.md index eed1879b9a..dfffd1b9de 100644 --- a/.agents/notes/implemented/architecture/2026-09-15-platform-office-engines.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-15-platform-office-engines.zh.md @@ -10,20 +10,20 @@ Status: implemented ## Decision -Harness 依赖 kit API,并使用它按平台筛选的可选引擎依赖。macOS 和 Windows 安装匹配的 ARM64 或 x64 原生包;Linux 安装共享 WASM 包。WASM 的 npm OS 声明为 Linux。provider 不直接依赖 WASM。macOS/Windows 缺少原生引擎时拒绝创建转换器,不会选择 WASM。 +Harness 从已安装 kit API 的 `optionalDependencies` 中选择一个引擎。声明了 `@deepseek-ai/libreoffice-kit-${platform}-${arch}` 的目标要求该原生包,其余目标要求共享 WASM 包。受支持的原生目标集合由 kit 发布版本决定,不由 Harness 中的操作系统分支决定。已声明的原生包缺失表示安装不完整,不会改选 WASM。provider 不直接依赖 WASM。 -Python sidecar 组装仅复制目标引擎及其依赖闭包。wheel 打包和运行时查找要求该引擎存在;迁移后的转换冒烟检查所选 backend。Desktop 在应用资源中携带常规 npm 安装结果。引擎编译、资格验证和发布仍由 kit 仓库负责。 +Python sidecar 组装仅复制所选引擎及其依赖闭包。wheel 打包和运行时查找依据同一份 kit 清单选择;迁移目录后的转换冒烟检查所选后端。Desktop 在签名和完整性封装前将 npm 安装结果过滤为该引擎。引擎编译、解析器兼容性、npm 平台元数据、资格验证与发布仍由 kit 仓库负责。 ## Alternatives considered -**在每个原生引擎旁保留 WASM。** 这能容忍可选原生包缺失,却会增大每个 macOS/Windows 安装。固定平台分发物改为要求其原生引擎存在且经过测试。 +**在每个原生引擎旁保留 WASM。** 这能容忍可选原生包缺失,却会增大每个支持原生引擎的分发物。固定平台分发物要求其已声明的原生引擎存在且经过测试。 -**在所有平台移除 WASM。** 当前 kit 家族没有已发布的 Linux 原生引擎。在 Linux 保留共享 WASM 包,既能继续转换 Office 文档,也无需引入新的原生发布目标。 +**在所有平台移除 WASM。** 没有已发布原生引擎的目标仍需转换能力。共享 WASM 包为这些目标提供转换,无需引入新的原生发布目标。 **将 Python Office 伴随目录设为可选。** 运行时承载共享的 `dsh` CLI 及其 Web profile,而不只有默认 SDK profile。强制携带目标引擎使已安装 wheel 具备完整的内置 profile 集合,并在启动前报告载荷不完整。SDK 和 headless 用户也承担引擎的下载与安装体积。 ## Consequences -macOS/Windows 分发物省去 WASM 下载和展开资源。原生包缺失成为必须修复的安装缺陷;转换不会下载引擎。Linux 保留 WASM 的资源和字体要求。kit 的引擎选择测试与真实 npm 安装测试覆盖平台选择;Harness 的 sidecar、wheel 和运行时解析测试覆盖打包要求。新包字节在发布前需要 kit 资格验证和匹配的依赖完整性记录。 +声明了原生目标的分发物省去 WASM 资源,其余目标保留 WASM 的资源和字体要求。锁定的 kit 声明了 macOS/Windows ARM64 和 x64 原生包,因此当前 Linux 分发物选择 WASM;kit 发布版本可以新增 Linux 原生目标,无需修改 Harness 的选择规则。这不扩展 Harness 支持的发布平台。Harness 的 sidecar、wheel 和运行时解析测试覆盖已声明原生目标与 WASM 选择,包括原生包缺失。新包字节在发布前需要 kit 资格验证和匹配的依赖完整性记录。 [公开 Python 发布工作流](../../../../.github/workflows/python-release.yml)拒绝任何大于等于 100,000,000 字节的 wheel。只选择一个引擎会减少载荷,但不能据此认定运行时 wheel 已满足此限制;npm 引擎发布、本地转换与 wheel 上传资格是不同的验证。 diff --git a/apps/desktop/README.i18n.yaml b/apps/desktop/README.i18n.yaml index c582de9b25..cc888f75d3 100644 --- a/apps/desktop/README.i18n.yaml +++ b/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: 7aa419b5ba405d15eeffc61831486c9648bea81c -README.zh.md: a2c5e9877fc7816a401feac50404818f1fdc5fe5 +README.md: 0122763f44c818a928c31f4c1571c42616675081 +README.zh.md: 8a59e1e3f86b87a31bb3c104fde96a3cede17bca diff --git a/apps/desktop/README.md b/apps/desktop/README.md index 7aa419b5ba..0122763f44 100644 --- a/apps/desktop/README.md +++ b/apps/desktop/README.md @@ -124,7 +124,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 one matching native engine on macOS/Windows or the WASM engine on Linux. 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/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 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. diff --git a/apps/desktop/README.zh.md b/apps/desktop/README.zh.md index a2c5e9877f..8a59e1e3f8 100644 --- a/apps/desktop/README.zh.md +++ b/apps/desktop/README.zh.md @@ -124,7 +124,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)在 macOS/Windows 携带一个匹配的原生引擎,在 Linux 携带 WASM 引擎。准备阶段在打包前拒绝缺少目标引擎的情况。引擎资源、许可证和 notices 保留在运行时依赖树中。 +[Office 转换提供方](../../packages/document/document-convert-libreoffice/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)。 diff --git a/apps/desktop/scripts/prepare-dsh.ts b/apps/desktop/scripts/prepare-dsh.ts index 18048375be..e5c789c165 100644 --- a/apps/desktop/scripts/prepare-dsh.ts +++ b/apps/desktop/scripts/prepare-dsh.ts @@ -28,6 +28,7 @@ import { } from './macos-runtime.ts' import { resolveDesktopBuildTarget, resolveDesktopTargetBuildPaths } from './desktop-build-paths.mjs' import { desktopRuntimeFileExclusion } from './runtime-file-policy.ts' +import { selectOfficeEngine } from '../../../scripts/libreoffice-engine.ts' const APP_ROOT = resolve(import.meta.dirname, '..') const BUILD_PATHS = resolveDesktopTargetBuildPaths() @@ -123,10 +124,12 @@ async function main(): Promise { const targetName = resolveDesktopBuildTarget() const target = { platform: process.platform, arch: targetName.endsWith('arm64') ? 'arm64' : 'x64' } const modules = join(BUILD_ROOT, 'node_modules') + const officeManifest = JSON.parse(readFileSync(join(modules, '@deepseek-ai/libreoffice-kit/package.json'), 'utf8')) + const officeEngine = selectOfficeEngine(officeManifest, target) mkdirSync(DSH_OUTPUT_ROOT, { recursive: true }) cpSync(modules, join(DSH_OUTPUT_ROOT, 'node_modules'), { recursive: true, dereference: true, - filter: source => desktopRuntimeFileExclusion(relative(modules, source), target) === undefined, + filter: source => desktopRuntimeFileExclusion(relative(modules, source), target, officeEngine) === undefined, }) writeFileSync(join(DSH_OUTPUT_ROOT, 'package.json'), `${JSON.stringify({ name: '@deepseek-ai/dsh-desktop-runtime', private: true, version: release.version, type: 'module', @@ -137,7 +140,6 @@ async function main(): Promise { throw new Error(`desktop runtime: missing private Host file ${file}`) } } - const officeEngine = target.platform === 'linux' ? 'wasm' : `${target.platform}-${target.arch}` if (!existsSync(join(DSH_OUTPUT_ROOT, 'node_modules', '@deepseek-ai', `libreoffice-kit-${officeEngine}`, 'prebuilds.json'))) { throw new Error(`desktop runtime: missing required LibreOffice engine ${officeEngine}`) } diff --git a/apps/desktop/scripts/runtime-file-policy.ts b/apps/desktop/scripts/runtime-file-policy.ts index 9e6ea293ab..1bc16b2a1c 100644 --- a/apps/desktop/scripts/runtime-file-policy.ts +++ b/apps/desktop/scripts/runtime-file-policy.ts @@ -6,10 +6,11 @@ * node_modules tree, including nested package containers. * @param path - Path relative to the production node_modules directory. * @param target - Platform and architecture of the Electron Node runtime. + * @param officeEngine - Engine selected from the installed kit manifest. * @returns Omission reason, or undefined when the entry must be copied. */ export function desktopRuntimeFileExclusion( - path: string, target: { platform: NodeJS.Platform; arch: string }, + path: string, target: { platform: NodeJS.Platform; arch: string }, officeEngine: string, ): string | undefined { const parts = path.split(/[\\/]/u) if (parts.some(part => ['.bin', '.pnpm', '.modules.yaml', '.pnpm-workspace-state-v1.json'].includes(part))) { @@ -24,8 +25,7 @@ export function desktopRuntimeFileExclusion( const name = packageParts.slice(0, nameParts).join('/') const entry = packageParts.slice(nameParts).join('/') if (name.startsWith('@deepseek-ai/libreoffice-kit-')) { - const engine = target.platform === 'linux' ? 'wasm' : `${target.platform}-${target.arch}` - if (name !== `@deepseek-ai/libreoffice-kit-${engine}`) return 'LibreOffice other platform' + if (name !== `@deepseek-ai/libreoffice-kit-${officeEngine}`) return 'LibreOffice other platform' } if (name === 'fs-ext' && /^build\/(?:Release|Debug)\/(?:obj(?:\/|$)|fs_ext\.(?:exp|lib|pdb|iobj|ipdb)$)/u.test(entry)) { return 'fs-ext compiler output' diff --git a/apps/desktop/tests/runtime-file-policy.spec.ts b/apps/desktop/tests/runtime-file-policy.spec.ts index 283a4f5842..76f5c8d95e 100644 --- a/apps/desktop/tests/runtime-file-policy.spec.ts +++ b/apps/desktop/tests/runtime-file-policy.spec.ts @@ -59,7 +59,7 @@ it('omits development artifacts while preserving executable modules, assets and } cpSync(modules, join(output, 'node_modules'), { recursive: true, dereference: true, - filter: path => desktopRuntimeFileExclusion(relative(modules, path), windows) === undefined, + filter: path => desktopRuntimeFileExclusion(relative(modules, path), windows, 'win32-x64') === undefined, }) for (const path of removed) expect(existsSync(join(output, 'node_modules', path)), path).toBe(false) for (const path of retained) expect(readFileSync(join(output, 'node_modules', path), 'utf8'), path).toBe(`payload:${path}`) @@ -72,15 +72,28 @@ it('omits development artifacts while preserving executable modules, assets and }) it('applies package-specific rules inside scoped and nested dependency containers', () => { - expect(desktopRuntimeFileExclusion('outer/node_modules/@mixmark-io/domino/test/data.html', windows)).toBeDefined() - expect(desktopRuntimeFileExclusion('outer\\node_modules\\node-pty\\prebuilds\\win32-arm64\\conpty.node', windows)).toBeDefined() - expect(desktopRuntimeFileExclusion('outer/node_modules/unrelated/test/data.html', windows)).toBeUndefined() - expect(desktopRuntimeFileExclusion('outer/fs-ext/build/Release/fs_ext.lib', windows)).toBeUndefined() + expect(desktopRuntimeFileExclusion('outer/node_modules/@mixmark-io/domino/test/data.html', windows, 'win32-x64')).toBeDefined() + expect(desktopRuntimeFileExclusion('outer\\node_modules\\node-pty\\prebuilds\\win32-arm64\\conpty.node', windows, 'win32-x64')).toBeDefined() + expect(desktopRuntimeFileExclusion('outer/node_modules/unrelated/test/data.html', windows, 'win32-x64')).toBeUndefined() + expect(desktopRuntimeFileExclusion('outer/fs-ext/build/Release/fs_ext.lib', windows, 'win32-x64')).toBeUndefined() }) it('retains native prebuilds for the selected macOS architecture', () => { const mac = { platform: 'darwin' as const, arch: 'arm64' } - expect(desktopRuntimeFileExclusion('node-pty/prebuilds/darwin-arm64/pty.node', mac)).toBeUndefined() - expect(desktopRuntimeFileExclusion('node-pty/prebuilds/darwin-x64/pty.node', mac)).toBeDefined() - expect(desktopRuntimeFileExclusion('node-pty/prebuilds/win32-x64/conpty.node', mac)).toBeDefined() + expect(desktopRuntimeFileExclusion('node-pty/prebuilds/darwin-arm64/pty.node', mac, 'darwin-arm64')).toBeUndefined() + expect(desktopRuntimeFileExclusion('node-pty/prebuilds/darwin-x64/pty.node', mac, 'darwin-arm64')).toBeDefined() + expect(desktopRuntimeFileExclusion('node-pty/prebuilds/win32-x64/conpty.node', mac, 'darwin-arm64')).toBeDefined() +}) + +it.each([ + ['linux', 'x64', 'linux-x64'], + ['linux', 'arm64', 'wasm'], + ['freebsd', 'x64', 'wasm'], +] as const)('retains only the selected Office engine for %s/%s', (platform, arch, engine) => { + for (const candidate of ['linux-x64', 'darwin-arm64', 'wasm']) { + const omitted = desktopRuntimeFileExclusion( + `@deepseek-ai/libreoffice-kit-${candidate}/prebuilds.json`, { platform, arch }, engine, + ) + expect(omitted === undefined).toBe(candidate === engine) + } }) diff --git a/docs/capability-seams.i18n.yaml b/docs/capability-seams.i18n.yaml index 37cbf2e0c9..58055065f9 100644 --- a/docs/capability-seams.i18n.yaml +++ b/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: 0a6b0ae80e1cbab9f6434035ac07cd4adb3fe7ec -capability-seams.zh.md: 87613f930550d03ac3c42d57dcb73860d1242916 +capability-seams.md: d4899950a6872e6d5b1ad4496d2ab88acda8d871 +capability-seams.zh.md: 9806901d3837ec41ceca2d7494cb03edde70ec4d diff --git a/docs/capability-seams.md b/docs/capability-seams.md index 0a6b0ae80e..d4899950a6 100644 --- a/docs/capability-seams.md +++ b/docs/capability-seams.md @@ -538,7 +538,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 native engines on macOS/Windows and Node WASM on Linux. | +| `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.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. | diff --git a/docs/capability-seams.zh.md b/docs/capability-seams.zh.md index 87613f9305..9806901d38 100644 --- a/docs/capability-seams.zh.md +++ b/docs/capability-seams.zh.md @@ -540,7 +540,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 字节在宿主上转换;macOS/Windows 使用原生引擎,Linux 使用 Node WASM。 | +| `ctx.documentConvert` | `seam` | [`document-convert`](../packages/document/document-convert) | [`document-convert-libreoffice`](../packages/document/document-convert-libreoffice) | - | - | 已授权的 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(智能体循环)与压缩功能调用提供方无关的流服务。 | diff --git a/docs/subsystems/document-convert.i18n.yaml b/docs/subsystems/document-convert.i18n.yaml index 8ec3e22776..47919b364c 100644 --- a/docs/subsystems/document-convert.i18n.yaml +++ b/docs/subsystems/document-convert.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/document-convert.md -document-convert.md: 2dea31918e92524606ad85826de276c2af05f06d -document-convert.zh.md: 22f0ec54615ebbf86743de6320ec4e8cc3fc8113 +document-convert.md: 1e17edb6314ef7c99cbf29313f64faa6a4b918cf +document-convert.zh.md: 478043bae519ecab2d3c2ccbc46426f14c9c4f6e diff --git a/docs/subsystems/document-convert.md b/docs/subsystems/document-convert.md index 2dea31918e..1e17edb631 100644 --- a/docs/subsystems/document-convert.md +++ b/docs/subsystems/document-convert.md @@ -29,7 +29,7 @@ The provider admits the deferred read before allocating source bytes, shares con ## Engine selection and limits -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. macOS and Windows require their matching ARM64 or x64 native engine; Linux uses Node WASM. 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 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. diff --git a/docs/subsystems/document-convert.zh.md b/docs/subsystems/document-convert.zh.md index 22f0ec5461..478043bae5 100644 --- a/docs/subsystems/document-convert.zh.md +++ b/docs/subsystems/document-convert.zh.md @@ -29,7 +29,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 包。macOS 和 Windows 要求匹配的 ARM64 或 x64 原生引擎;Linux 使用 Node WASM。[平台引擎决策](../../.agents/notes/implemented/architecture/2026-09-15-platform-office-engines.zh.md)定义安装和打包规则。元数据无效、必需资源缺失和转换错误都会拒绝请求,不切换引擎。转换在 Host 使用磁盘输入输出路径,不使用浏览器转换引擎或字体 RPC。 +外部 [`@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 缓存和面向模型的渲染不属于此提供方。 diff --git a/packages/document/README.i18n.yaml b/packages/document/README.i18n.yaml index 5c7acd04ef..59329ef29d 100644 --- a/packages/document/README.i18n.yaml +++ b/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: 0f9d3f1ae0cb154a445e34debe0bf49ef5400d1e -README.zh.md: 0325634d6c435b6d8aeebf202d04f84fa4975e33 +README.md: 693a831fb7a501f8c0376271715298c06b33efd8 +README.zh.md: ac4719874761c9267c25eee763013b5f7f5d9290 diff --git a/packages/document/README.md b/packages/document/README.md index 0f9d3f1ae0..693a831fb7 100644 --- a/packages/document/README.md +++ b/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. macOS and Windows use their matching native engine; Linux uses Node WASM. +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. ## Table of Contents diff --git a/packages/document/README.zh.md b/packages/document/README.zh.md index 0325634d6c..ac47198747 100644 --- a/packages/document/README.zh.md +++ b/packages/document/README.zh.md @@ -9,7 +9,7 @@ kind: "package-group" ## 摘要 -在宿主上将已授权的 Office 文件转换为可复用的 PDF。转换包族提供共享转换操作和 LibreOffice kit 提供方。macOS 和 Windows 使用匹配的原生引擎;Linux 使用 Node WASM。 +在宿主上将已授权的 Office 文件转换为可复用的 PDF。转换包族提供共享转换操作和 LibreOffice kit 提供方。声明了原生引擎的目标使用原生引擎,其余目标使用 Node WASM。 ## 目录 diff --git a/packages/document/document-convert-libreoffice/README.i18n.yaml b/packages/document/document-convert-libreoffice/README.i18n.yaml index 13f28d09b0..4aaae7abaf 100644 --- a/packages/document/document-convert-libreoffice/README.i18n.yaml +++ b/packages/document/document-convert-libreoffice/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/document-convert-libreoffice/README.md -README.md: 1c287b714ace289fa03f8be6da722fbe78e64065 -README.zh.md: 796d14c3bd873a3a6a24d4691f0d406d0894a122 +README.md: c083215ea2e3fa067043dac045becd892e0ca406 +README.zh.md: d10c1a14792c80b3608b3302958de4546b817cda diff --git a/packages/document/document-convert-libreoffice/README.md b/packages/document/document-convert-libreoffice/README.md index 1c287b714a..c083215ea2 100644 --- a/packages/document/document-convert-libreoffice/README.md +++ b/packages/document/document-convert-libreoffice/README.md @@ -9,7 +9,7 @@ English | [中文](README.zh.md) ## Summary -Convert Office documents to PDFs on the Host computer. macOS and Windows use their matching native LibreOffice engine; Linux uses Node WASM. The provider accepts DOC, DOCX, XLS, XLSX, PPT, and PPTX. OOXML conversion returns missing-font names; binary Office conversion returns an empty list. +Convert Office documents to PDFs on the Host computer. Targets with a declared native LibreOffice engine use it; other targets use Node WASM. The provider accepts DOC, DOCX, XLS, XLSX, PPT, and PPTX. OOXML conversion returns missing-font names; binary Office conversion returns an empty list. ## Table of Contents @@ -27,7 +27,7 @@ Convert Office documents to PDFs on the Host computer. macOS and Windows use the 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 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`. npm selects the matching macOS/Windows native package or the Linux-only WASM package. A missing native engine rejects conversion on macOS/Windows 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. +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. | Field | Default | Meaning | |---|---|---| diff --git a/packages/document/document-convert-libreoffice/README.zh.md b/packages/document/document-convert-libreoffice/README.zh.md index 796d14c3bd..d10c1a1479 100644 --- a/packages/document/document-convert-libreoffice/README.zh.md +++ b/packages/document/document-convert-libreoffice/README.zh.md @@ -9,7 +9,7 @@ kind: "package-reference" ## 概述 -在宿主计算机上将 Office 文档转换为 PDF。macOS 和 Windows 使用匹配的原生 LibreOffice 引擎;Linux 使用 Node WASM。提供方支持 DOC、DOCX、XLS、XLSX、PPT 和 PPTX。OOXML 转换返回缺失字体名称;二进制 Office 转换返回空列表。 +在宿主计算机上将 Office 文档转换为 PDF。声明了原生 LibreOffice 引擎的目标使用原生引擎,其余目标使用 Node WASM。提供方支持 DOC、DOCX、XLS、XLSX、PPT 和 PPTX。OOXML 转换返回缺失字体名称;二进制 Office 转换返回空列表。 ## 目录 @@ -27,7 +27,7 @@ kind: "package-reference" [Web bundle](../../bundle/web-app/README.zh.md)以 `document-convert` 条目挂载此提供方。独立组合通过 `cordis.yml` 条目挂载 `@deepseek-ai/dsh-document-convert-libreoffice`。 -此 provider 依赖独立发布的 [`@deepseek-ai/libreoffice-kit`](https://github.com/deepseek-harness/libreoffice-kit/tree/main/packages/entry) npm API,kit 版本为 `0.0.1`。npm 选择匹配的 macOS/Windows 原生包或仅限 Linux 的 WASM 包。macOS/Windows 缺少原生引擎时拒绝转换,不会选择 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 各自的职责。 +此 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 各自的职责。 | 字段 | 默认值 | 含义 | |---|---|---| diff --git a/packages/document/document-convert-libreoffice/package.json b/packages/document/document-convert-libreoffice/package.json index f16000844f..eb020306ef 100644 --- a/packages/document/document-convert-libreoffice/package.json +++ b/packages/document/document-convert-libreoffice/package.json @@ -1,6 +1,6 @@ { "name": "@deepseek-ai/dsh-document-convert-libreoffice", - "description": "Host Office conversion with native macOS/Windows engines and Linux Node WASM", + "description": "Host Office conversion with a declared native target engine or Node WASM", "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" diff --git a/python/sdk-runtime/README.i18n.yaml b/python/sdk-runtime/README.i18n.yaml index 631a6680d5..70ecb5d18f 100644 --- a/python/sdk-runtime/README.i18n.yaml +++ b/python/sdk-runtime/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 python/sdk-runtime/README.md -README.md: ba1814ce00dea9dd35bcfbb330801942b86af155 -README.zh.md: 792c7e06a29be987a25887882b6df976138b194f +README.md: adb2fb71c3893d9360d7593826fdca609f770b6e +README.zh.md: a4decd5040ada700e84b0f1b3bacba7bbec8a56f diff --git a/python/sdk-runtime/README.md b/python/sdk-runtime/README.md index ba1814ce00..adb2fb71c3 100644 --- a/python/sdk-runtime/README.md +++ b/python/sdk-runtime/README.md @@ -39,4 +39,4 @@ Production deployment permits unused workspace patches for packages outside the From the repository root, `pnpm exec tsx scripts/build-exe-for-python-sdk.ts` verifies the closure, builds packages, deploys a symlink-free tree, packages the selected target, and syncs the executable and sidecars into this module. `scripts/build-python-release.py` stages release-shaped wheels at the root repository version and pins `deepseek-harness-sdk` to the exact runtime version. -The installed-wheel smoke creates a clean virtual environment outside the checkout, proves the installed distribution and executable identities, then exercises default and customized SDK profiles, external plugins, MCP, native tools, direct JSON-RPC, committed snapshots, and the real provider on trusted runs. Its Office scenario relocates the complete target payload and converts DOCX with the required platform engine: native on macOS/Windows and WASM on Linux. See the [Python contributor workflow](../development.md) and [installed-wheel testing decision](../../.agents/notes/implemented/testing/2026-08-23-installed-python-wheel-black-box-ci.md). +The installed-wheel smoke creates a clean virtual environment outside the checkout, proves the installed distribution and executable identities, then exercises default and customized SDK profiles, external plugins, MCP, native tools, direct JSON-RPC, committed snapshots, and the real provider on trusted runs. Its Office scenario relocates the complete target payload and converts DOCX with the required platform engine: the target’s declared native engine, or WASM when no native engine is declared. See the [Python contributor workflow](../development.md) and [installed-wheel testing decision](../../.agents/notes/implemented/testing/2026-08-23-installed-python-wheel-black-box-ci.md). diff --git a/python/sdk-runtime/README.zh.md b/python/sdk-runtime/README.zh.md index 792c7e06a2..a4decd5040 100644 --- a/python/sdk-runtime/README.zh.md +++ b/python/sdk-runtime/README.zh.md @@ -39,4 +39,4 @@ Python bootstrap 从相邻目录解析 Office kit,让原生辅助程序与 URL 在仓库根目录运行 `pnpm exec tsx scripts/build-exe-for-python-sdk.ts`,会校验闭包、构建包、部署无符号链接的文件树、打包所选目标,并把可执行程序及伴随文件同步到本模块。`scripts/build-python-release.py` 按仓库根版本暂存发布形态的 wheel 包,并将 `deepseek-harness-sdk` 固定到完全相同的运行时版本。 -已安装 wheel 包冒烟测试会在检出目录外创建干净的虚拟环境,验证分发物与可执行程序的来源,然后覆盖默认及自定义 SDK profile、外部插件、MCP、原生工具、直接 JSON-RPC、检入快照,以及可信运行中的真实提供方。Office 场景会迁移完整的目标载荷目录,并使用所需的平台引擎转换 DOCX:macOS/Windows 使用原生引擎,Linux 使用 WASM。另见 [Python 贡献者工作流](../development.zh.md) 与 [installed-wheel 测试决策](../../.agents/notes/implemented/testing/2026-08-23-installed-python-wheel-black-box-ci.zh.md)。 +已安装 wheel 包冒烟测试会在检出目录外创建干净的虚拟环境,验证分发物与可执行程序的来源,然后覆盖默认及自定义 SDK profile、外部插件、MCP、原生工具、直接 JSON-RPC、检入快照,以及可信运行中的真实提供方。Office 场景会迁移完整的目标载荷目录,并使用所需的平台引擎转换 DOCX:使用目标已声明的原生引擎,未声明原生引擎时使用 WASM。另见 [Python 贡献者工作流](../development.zh.md) 与 [installed-wheel 测试决策](../../.agents/notes/implemented/testing/2026-08-23-installed-python-wheel-black-box-ci.zh.md)。 diff --git a/python/sdk-runtime/hatch_build.py b/python/sdk-runtime/hatch_build.py index d642eec5e2..23551b2705 100644 --- a/python/sdk-runtime/hatch_build.py +++ b/python/sdk-runtime/hatch_build.py @@ -74,7 +74,6 @@ class RuntimeBuildHook(BuildHookInterface): ) expected_executable = matches[0][1] target = next(name for name, value in _PLATFORMS.items() if value[0] == platform_tag) - engine = "wasm" if target.startswith("linux-") else target.replace("win-", "win32-").replace("macos-", "darwin-") runtime_dir = Path(self.root) / "src" / "deepseek_harness_runtime" / "runtime" runtime_files = sorted( runtime_dir.glob("deepseek-harness-sdk-runtime-*") if runtime_dir.is_dir() else [] @@ -96,9 +95,15 @@ class RuntimeBuildHook(BuildHookInterface): ) for executable in runtime_files: if executable == office: - for required in ("@deepseek-ai/libreoffice-kit/package.json", f"@deepseek-ai/libreoffice-kit-{engine}/prebuilds.json"): - if not (office / "node_modules" / required).is_file(): - raise RuntimeError(f"runtime Office dependency is missing: {office / required}") + adapter = office / "node_modules/@deepseek-ai/libreoffice-kit/package.json" + if not adapter.is_file(): + raise RuntimeError(f"runtime Office dependency is missing: {adapter}") + native = target.replace("win-", "win32-").replace("macos-", "darwin-") + declared = json.loads(adapter.read_text(encoding="utf-8")).get("optionalDependencies", {}) + engine = native if f"@deepseek-ai/libreoffice-kit-{native}" in declared else "wasm" + required = office / "node_modules" / f"@deepseek-ai/libreoffice-kit-{engine}/prebuilds.json" + if not required.is_file(): + raise RuntimeError(f"runtime Office dependency is missing: {required}") continue if not executable.is_file(): raise RuntimeError(f"runtime executable is not a file: {executable}") diff --git a/python/sdk-runtime/src/deepseek_harness_runtime/__init__.py b/python/sdk-runtime/src/deepseek_harness_runtime/__init__.py index 546bab36c7..48c2b26057 100644 --- a/python/sdk-runtime/src/deepseek_harness_runtime/__init__.py +++ b/python/sdk-runtime/src/deepseek_harness_runtime/__init__.py @@ -21,6 +21,7 @@ console command requires ``DSH_HOME`` for the same reason. from __future__ import annotations +import json import os import platform import shutil @@ -89,13 +90,20 @@ def bundled_runtime_path() -> Path: + _EXE_ACQUISITION_HINT ) office = path.with_name(f"{path.name.removesuffix('.exe')}-office") - engine = "wasm" if tag.startswith("linux-") else tag.replace("win-", "win32-").replace("macos-", "darwin-") - for required in ("@deepseek-ai/libreoffice-kit/package.json", f"@deepseek-ai/libreoffice-kit-{engine}/prebuilds.json"): - if not (office / "node_modules" / required).is_file(): - raise FileNotFoundError( - f"deepseek-harness-runtime-bin is missing the Office sidecar at {office}. " - + _EXE_ACQUISITION_HINT - ) + adapter = office / "node_modules/@deepseek-ai/libreoffice-kit/package.json" + if not adapter.is_file(): + raise FileNotFoundError( + f"deepseek-harness-runtime-bin is missing the Office sidecar at {office}. " + + _EXE_ACQUISITION_HINT + ) + native = tag.replace("win-", "win32-").replace("macos-", "darwin-") + declared = json.loads(adapter.read_text(encoding="utf-8")).get("optionalDependencies", {}) + engine = native if f"@deepseek-ai/libreoffice-kit-{native}" in declared else "wasm" + if not (office / "node_modules" / f"@deepseek-ai/libreoffice-kit-{engine}/prebuilds.json").is_file(): + raise FileNotFoundError( + f"deepseek-harness-runtime-bin is missing the Office sidecar engine {engine} at {office}. " + + _EXE_ACQUISITION_HINT + ) return path diff --git a/python/sdk/tests/test_release_version.py b/python/sdk/tests/test_release_version.py index b3a9e259ef..39022df00c 100644 --- a/python/sdk/tests/test_release_version.py +++ b/python/sdk/tests/test_release_version.py @@ -164,6 +164,7 @@ def test_stage_runtime_rejects_a_noncanonical_executable_name(tmp_path: Path) -> @pytest.mark.parametrize("platform_tag,selected", [ ("manylinux_2_28_x86_64", "wasm"), ("macosx_14_0_arm64", "darwin-arm64"), ("macosx_14_0_x86_64", "darwin-x64"), ("win_amd64", "win32-x64"), + ("manylinux_2_28_x86_64", "linux-x64"), ("macosx_14_0_arm64", "wasm"), ]) @pytest.mark.parametrize("invalid", [None, "asset", "missing-engine", "foreign-engine", "helper-mode"]) def test_office_wheel_requires_only_target_engine( @@ -177,7 +178,9 @@ def test_office_wheel_requires_only_target_engine( engine = ({"kind": "native", "executable": "bin/helper"} if native else {"kind": "wasm", "loader": "loader.cjs", "wasm": "engine.wasm", "data": "engine.data", "metadata": "fonts.json"}) with zipfile.ZipFile(wheel, "w") as archive: - archive.writestr(f"{root}/@deepseek-ai/libreoffice-kit/package.json", "{}") + archive.writestr(f"{root}/@deepseek-ai/libreoffice-kit/package.json", json.dumps({ + "optionalDependencies": {f"@deepseek-ai/libreoffice-kit-{selected}": "0.0.1"}, + })) base = f"{root}/@deepseek-ai/libreoffice-kit-{selected}" if invalid != "missing-engine": archive.writestr(f"{base}/prebuilds.json", json.dumps({"engine": engine})) diff --git a/python/sdk/tests/test_runtime_resolution.py b/python/sdk/tests/test_runtime_resolution.py index 0c6fa828a2..a1b071e7a0 100644 --- a/python/sdk/tests/test_runtime_resolution.py +++ b/python/sdk/tests/test_runtime_resolution.py @@ -2,6 +2,7 @@ from __future__ import annotations +import json import os import subprocess import sys @@ -19,14 +20,19 @@ from deepseek_harness_runtime import ( ) -def _office_sidecar(executable: Path) -> Path: +def _office_sidecar(executable: Path, native_targets: tuple[str, ...] = ("darwin-arm64", "darwin-x64", "win32-x64")) -> Path: office = executable.with_name(f"{executable.name.removesuffix('.exe')}-office") tag = executable.name.removeprefix("deepseek-harness-sdk-runtime-").removesuffix(".exe") - engine = "wasm" if tag.startswith("linux-") else tag.replace("win-", "win32-").replace("macos-", "darwin-") + native = tag.replace("win-", "win32-").replace("macos-", "darwin-") + engine = native if native in native_targets else "wasm" for required in ("@deepseek-ai/libreoffice-kit/package.json", f"@deepseek-ai/libreoffice-kit-{engine}/prebuilds.json"): path = office / "node_modules" / required path.parent.mkdir(parents=True, exist_ok=True) path.write_text("{}") + adapter = office / "node_modules/@deepseek-ai/libreoffice-kit/package.json" + adapter.write_text(json.dumps({"optionalDependencies": { + f"@deepseek-ai/libreoffice-kit-{target}": "0.0.1" for target in (*native_targets, "wasm") + }}), encoding="utf-8") return office @@ -229,8 +235,14 @@ def test_windows_console_branch_preserves_real_child_io_and_completion(tmp_path: assert sentinel.read_text() == "done" -@pytest.mark.parametrize("target", ["linux-x64", "linux-arm64", "macos-arm64", "macos-x64", "win-x64"]) -def test_runtime_requires_its_platform_office_engine(tmp_path: Path, monkeypatch: pytest.MonkeyPatch, target: str) -> None: +@pytest.mark.parametrize("target,native_targets", [ + ("linux-x64", ()), ("linux-arm64", ()), + ("macos-arm64", ("darwin-arm64",)), ("macos-x64", ("darwin-x64",)), ("win-x64", ("win32-x64",)), + ("linux-x64", ("linux-x64",)), ("macos-arm64", ()), +]) +def test_runtime_requires_its_platform_office_engine( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch, target: str, native_targets: tuple[str, ...], +) -> None: extension = ".exe" if target.startswith("win-") else "" executable = tmp_path / "runtime" / f"deepseek-harness-sdk-runtime-{target}{extension}" executable.parent.mkdir() @@ -238,13 +250,13 @@ def test_runtime_requires_its_platform_office_engine(tmp_path: Path, monkeypatch executable.with_name(f"{executable.stem}-rg{extension}").touch() if target.startswith("macos-"): Path(f"{executable}-spawn-helper").touch() - office = _office_sidecar(executable) + office = _office_sidecar(executable, native_targets) monkeypatch.setattr(runtime, "bundled_package_dir", lambda: tmp_path) monkeypatch.setattr(runtime, "_current_platform_tag", lambda: target) assert runtime.bundled_runtime_path() == executable engine = next(office.glob("node_modules/@deepseek-ai/libreoffice-kit-*/prebuilds.json")) engine.unlink() - foreign = office / "node_modules/@deepseek-ai" / ("libreoffice-kit-darwin-arm64" if target.startswith("linux-") else "libreoffice-kit-wasm") / "prebuilds.json" + foreign = office / "node_modules/@deepseek-ai" / ("libreoffice-kit-darwin-arm64" if engine.parent.name == "libreoffice-kit-wasm" else "libreoffice-kit-wasm") / "prebuilds.json" foreign.parent.mkdir(parents=True, exist_ok=True) foreign.write_text("{}") with pytest.raises(FileNotFoundError, match="Office sidecar"): diff --git a/scripts/build-exe-for-python-sdk-office.spec.ts b/scripts/build-exe-for-python-sdk-office.spec.ts index de7ae69b5b..273bf874e8 100644 --- a/scripts/build-exe-for-python-sdk-office.spec.ts +++ b/scripts/build-exe-for-python-sdk-office.spec.ts @@ -94,33 +94,37 @@ it('rejects an ancestor dependency outside the deployed closure', async () => { .rejects.toThrow('outside the deployed closure') }) -it('rejects an optional WASM engine resolved outside the deployed closure', async () => { +it('requires the declared WASM engine inside the deployed closure', async () => { const { root, staging, destination, packageAt } = await fixture() await packageAt('@deepseek-ai/libreoffice-kit', { optionalDependencies: { '@deepseek-ai/libreoffice-kit-wasm': '0.0.1' } }) - const ambient = await packageAt('@deepseek-ai/libreoffice-kit-wasm', {}, root) + await packageAt('@deepseek-ai/libreoffice-kit-wasm', {}, root) await expect(copyOfficeSidecar(staging, destination, { platform: 'linux', arch: 'x64' })) - .rejects.toThrow(`Python Office dependency is outside the deployed closure: ${ambient}`) + .rejects.toThrow('Python Office engine @deepseek-ai/libreoffice-kit-wasm required for linux/x64 is missing.') await expect(stat(destination)).rejects.toMatchObject({ code: 'ENOENT' }) }) it.each([ - ['darwin', 'arm64'], ['darwin', 'x64'], ['win32', 'arm64'], ['win32', 'x64'], ['linux', 'x64'], -])('copies only the %s/%s engine even when other engines are staged', async (platform, arch) => { + ['darwin', 'arm64', 'darwin-arm64'], ['darwin', 'x64', 'darwin-x64'], + ['win32', 'arm64', 'win32-arm64'], ['win32', 'x64', 'win32-x64'], + ['linux', 'x64', 'linux-x64'], ['linux', 'arm64', 'wasm'], ['freebsd', 'x64', 'wasm'], +])('copies only the %s/%s engine even when other engines are staged', async (platform, arch, selected) => { const { staging, destination, packageAt } = await fixture() - const targets = ['darwin-arm64', 'darwin-x64', 'win32-arm64', 'win32-x64', 'wasm'] + const targets = ['darwin-arm64', 'darwin-x64', 'win32-arm64', 'win32-x64', 'linux-x64', 'wasm'] const names = targets.map(target => `@deepseek-ai/libreoffice-kit-${target}`) await packageAt('@deepseek-ai/libreoffice-kit', { optionalDependencies: Object.fromEntries(names.map(name => [name, '0.0.1'])) }) for (const name of names) await packageAt(name) - const expected = `@deepseek-ai/libreoffice-kit-${platform === 'linux' ? 'wasm' : `${platform}-${arch}`}` + const expected = `@deepseek-ai/libreoffice-kit-${selected}` const packages = await copyOfficeSidecar(staging, destination, { platform, arch }) expect(packages.map(path => path.replaceAll('\\', '/'))).toEqual([ 'node_modules/@deepseek-ai/libreoffice-kit', `node_modules/${expected}`, ]) }) -it.each(['darwin', 'win32'])('%s requires its native package even when WASM is staged', async (platform) => { +it.each(['darwin', 'win32', 'linux'])('%s requires its native package even when WASM is staged', async (platform) => { const { staging, destination, packageAt } = await fixture() - await packageAt('@deepseek-ai/libreoffice-kit', { optionalDependencies: { '@deepseek-ai/libreoffice-kit-wasm': '0.0.1' } }) + await packageAt('@deepseek-ai/libreoffice-kit', { optionalDependencies: { + '@deepseek-ai/libreoffice-kit-wasm': '0.0.1', [`@deepseek-ai/libreoffice-kit-${platform}-arm64`]: '0.0.1', + } }) await packageAt('@deepseek-ai/libreoffice-kit-wasm') await expect(copyOfficeSidecar(staging, destination, { platform, arch: 'arm64' })) .rejects.toThrow(`Python Office engine @deepseek-ai/libreoffice-kit-${platform}-arm64 required for ${platform}/arm64 is missing.`) diff --git a/scripts/build-exe-for-python-sdk-office.ts b/scripts/build-exe-for-python-sdk-office.ts index 50385ed0f5..0233dca72f 100644 --- a/scripts/build-exe-for-python-sdk-office.ts +++ b/scripts/build-exe-for-python-sdk-office.ts @@ -3,6 +3,7 @@ import { existsSync } from 'node:fs' import { cp, mkdir, readFile, rm } from 'node:fs/promises' import { createRequire } from 'node:module' import { isAbsolute, join, relative, sep } from 'node:path' +import { selectOfficeEngine } from './libreoffice-engine.ts' /** pkg applies these exclusions to dependency `files` as well as root asset globs. */ export const OFFICE_ASSET_IGNORES = [ @@ -22,7 +23,7 @@ interface PackageManifest { /** * Copy the installed Office dependency tree without changing package contents or executable modes. - * Harness sidecars require the target native engine on macOS/Windows and WASM on Linux. + * Harness sidecars require the kit's declared target native engine, or WASM for other targets. * Missing target engines fail with their package name; missing required dependencies or paths outside the deployed closure also fail. * @param staging - Symlink-free deployed Node closure. * @param destination - Target-specific Office directory beside the executable; replaced when present. @@ -34,7 +35,9 @@ export async function copyOfficeSidecar( destination: string, target: { platform: string; arch: string }, ): Promise { - const engineName = `@deepseek-ai/libreoffice-kit-${target.platform === 'linux' ? 'wasm' : `${target.platform}-${target.arch}`}` + const entry = join(staging, 'node_modules', '@deepseek-ai', 'libreoffice-kit') + const manifest = JSON.parse(await readFile(join(entry, 'package.json'), 'utf8')) as PackageManifest + const engineName = `@deepseek-ai/libreoffice-kit-${selectOfficeEngine(manifest, target)}` const packages = new Set() async function visit(packageDirectory: string): Promise { @@ -53,7 +56,7 @@ export async function copyOfficeSidecar( ...Object.keys(manifest.peerDependencies ?? {}), ]) for (const name of dependencies) { - if (name.startsWith('@deepseek-ai/libreoffice-kit-') && name !== engineName) continue + if (name.startsWith('@deepseek-ai/libreoffice-kit-')) continue const optional = manifest.optionalDependencies?.[name] !== undefined || manifest.peerDependenciesMeta?.[name]?.optional === true const dependencyDirectory = (require.resolve.paths(name) ?? []) @@ -71,7 +74,7 @@ export async function copyOfficeSidecar( } } - await visit(join(staging, 'node_modules', '@deepseek-ai', 'libreoffice-kit')) + await visit(entry) const engineDirectory = join(staging, 'node_modules', engineName) if (!existsSync(engineDirectory)) throw new Error(`Python Office engine ${engineName} required for ${target.platform}/${target.arch} is missing.`) await visit(engineDirectory) diff --git a/scripts/build-python-release.py b/scripts/build-python-release.py index 04500c942b..4e5bfb4e1e 100644 --- a/scripts/build-python-release.py +++ b/scripts/build-python-release.py @@ -237,7 +237,9 @@ def verify_office_payload(archive: zipfile.ZipFile, office_modules: str, platfor raise RuntimeError("Office dependency is missing: libreoffice-kit") engines = f"{office_modules}/@deepseek-ai" target = next(name for name, value in PLATFORMS.items() if value[0] == platform_tag) - selected = "wasm" if target.startswith("linux-") else target.replace("win-", "win32-").replace("macos-", "darwin-") + native_target = target.replace("win-", "win32-").replace("macos-", "darwin-") + declared = json.loads(archive.read(adapter)).get("optionalDependencies", {}) + selected = native_target if f"@deepseek-ai/libreoffice-kit-{native_target}" in declared else "wasm" for required in (f"libreoffice-kit-{selected}/prebuilds.json",): if f"{engines}/{required}" not in names: raise RuntimeError(f"Office dependency is missing: {required}") diff --git a/scripts/gen-doc-graphs.ts b/scripts/gen-doc-graphs.ts index fbe5d3ec86..bf78760459 100644 --- a/scripts/gen-doc-graphs.ts +++ b/scripts/gen-doc-graphs.ts @@ -153,7 +153,7 @@ const SERVICE_ROLES: ServiceRole[] = [ { key: 'documentConvert', pkg: 'document-convert', title: 'Office to PDF conversion', mode: 'seam', implementations: ['document-convert-libreoffice'], consumers: [], - note: 'Authorized Office bytes are converted on the Host using native engines on macOS/Windows and Node WASM on Linux.', + note: 'Authorized Office bytes are converted on the Host using the declared native target engine, or Node WASM when no native target is declared.', }, { key: 'attachments', diff --git a/scripts/libreoffice-engine.spec.ts b/scripts/libreoffice-engine.spec.ts new file mode 100644 index 0000000000..763a313a75 --- /dev/null +++ b/scripts/libreoffice-engine.spec.ts @@ -0,0 +1,24 @@ +import { expect, it } from 'vitest' +import { selectOfficeEngine } from './libreoffice-engine.ts' + +it.each([ + ['darwin', 'arm64', 'darwin-arm64'], ['darwin', 'x64', 'darwin-x64'], + ['win32', 'arm64', 'win32-arm64'], ['win32', 'x64', 'win32-x64'], + ['linux', 'x64', 'wasm'], ['linux', 'arm64', 'wasm'], + ['darwin', 'other', 'wasm'], ['freebsd', 'x64', 'wasm'], +])('selects the declared engine for %s/%s', (platform, arch, expected) => { + const optionalDependencies = Object.fromEntries( + ['darwin-arm64', 'darwin-x64', 'win32-arm64', 'win32-x64', 'wasm'] + .map(engine => [`@deepseek-ai/libreoffice-kit-${engine}`, '0.0.1']), + ) + expect(selectOfficeEngine({ optionalDependencies }, { platform, arch })).toBe(expected) +}) + +it('selects a declared Linux native target without an OS-specific policy change', () => { + expect(selectOfficeEngine({ optionalDependencies: { '@deepseek-ai/libreoffice-kit-linux-x64': '1' } }, + { platform: 'linux', arch: 'x64' })).toBe('linux-x64') +}) + +it('uses WASM when no native targets are declared', () => { + expect(selectOfficeEngine({}, { platform: 'darwin', arch: 'x64' })).toBe('wasm') +}) diff --git a/scripts/libreoffice-engine.ts b/scripts/libreoffice-engine.ts new file mode 100644 index 0000000000..7b3b270daa --- /dev/null +++ b/scripts/libreoffice-engine.ts @@ -0,0 +1,17 @@ +/** Select the single LibreOffice payload from the kit's declared native targets. */ + +/** + * Require a declared native target even when its optional npm package is absent. + * Targets without a declared native engine use WASM. + * @param manifest - Installed LibreOffice kit package manifest. + * @param target - Node platform and architecture of the distribution. + * @returns Engine suffix used by the kit's npm packages. + */ +export function selectOfficeEngine( + manifest: { optionalDependencies?: Record }, + target: { platform: string; arch: string }, +): string { + const native = `${target.platform}-${target.arch}` + return Object.hasOwn(manifest.optionalDependencies ?? {}, `@deepseek-ai/libreoffice-kit-${native}`) + ? native : 'wasm' +} diff --git a/scripts/smoke-python-runtime.py b/scripts/smoke-python-runtime.py index e523813603..aef9b61154 100644 --- a/scripts/smoke-python-runtime.py +++ b/scripts/smoke-python-runtime.py @@ -805,7 +805,11 @@ def smoke_sdk_office(executable: Path) -> None: else: shutil.copy2(source, destination) office = root / f"{stem}-office" - expected_backend = "wasm" if sys.platform == "linux" else "native" + adapter = office / "node_modules/@deepseek-ai/libreoffice-kit/package.json" + native = stem.removeprefix("deepseek-harness-sdk-runtime-").replace("win-", "win32-").replace("macos-", "darwin-") + declared = json.loads(adapter.read_text(encoding="utf-8")).get("optionalDependencies", {}) + selected = native if f"@deepseek-ai/libreoffice-kit-{native}" in declared else "wasm" + expected_backend = "wasm" if selected == "wasm" else "native" engines = [ json.loads(manifest.read_text())["engine"]["kind"] for manifest in (office / "node_modules/@deepseek-ai").glob("libreoffice-kit-*/prebuilds.json")