refactor(office): Office 技能统一读取包内 assets,移除包外 office-skills 拷贝与 DSH_BUNDLED_OFFICE_SKILLS
- web-app、sdk-app 的 skill-office 不再覆盖 assetRoot;pythonEnvironment 不变 - Desktop 与 Python SDK 打包不再生成 office-skills 资源目录,删除 prepareOfficeSkillAssets 与 skillSource - Desktop 打包 smoke 校验 4 个 Office 技能可从运行时归档列出并加载 - 同步测试、README、Agent Note,新增升级指南 office-skill-resources
This commit is contained in:
1 parent
3f6cd80c9d
commit
9df26413ee
49 files changed
+232
-214
No files matched your search
+2
-2
@@ -9,8 +9,8 @@
|
||||
en: 129d30508926bbc5
|
||||
zh: 15feff44556fc9b7
|
||||
/agent-note-office-skills-in-every-deployment-with-one-office-kit-package/decision:
|
||||
en: 2605a9eddd6f91e6
|
||||
zh: 08536ad09042586d
|
||||
en: 7d70a22025ab858e
|
||||
zh: cc37cb9932e78787
|
||||
/agent-note-office-skills-in-every-deployment-with-one-office-kit-package/alternatives-considered:
|
||||
en: 167c52da0171028f
|
||||
zh: 9c87c2370d0719df
|
||||
|
||||
@@ -12,6 +12,8 @@ Office skills were mounted only by Desktop and the Python SDK, so ordinary `dsh
|
||||
|
||||
The [Web bundle](../../../../packages/bundle/web-app/cordis.patch.yml) and the [SDK bundle](../../../../packages/bundle/sdk-app/cordis.patch.yml) mount [`skill-office`](../../../../packages/skill/skill-office/README.md) in Desktop, ordinary `dsh web`, and the SDK. This supersedes the mounting decision of [progressive Office skills](2026-09-27-progressive-office-skills.md); its skill structure remains current, and the [on-demand Office tools](2026-10-31-native-office-tool.md) replace its shell execution.
|
||||
|
||||
Every deployment reads the Office skills from the `skill-office` package's `assets/`, which hold only `SKILL.md` files. The Host reads them with ordinary file reads: Desktop's Electron Host reads them inside `app.asar`, and the SDK executable embeds them, so no carrier copies the skills to a separate resource directory. A composition selects another directory through `assetRoot`, or overrides individual skills with same-name filesystem skills.
|
||||
|
||||
The bundled Python environment ([`tool-workspace-dependencies`](../../../../packages/skill/tool-workspace-dependencies/README.md)) mounts only in Desktop and in the SDK when a primary runtime is available. `skill-office` declares `pythonEnvironment: 'workspace-dependencies' | 'user'` explicitly; each bundle row selects the value for its deployment. The default is `'workspace-dependencies'` because older SDK patches replace the complete `config` of the `skill-office` row; such a patch omits the new key and keeps the bundled-environment instructions it was written for.
|
||||
|
||||
[`@deepseek-ai/dsh-office-kit`](../../../../packages/document/office-kit/README.md) owns Kit location and the shared conversion service. Kit location is an internal implementation of the provider and is not exported: the provider passes its own `import.meta.resolve` lookup, and native engine paths use ASAR-unpacked mapping. Location reads no environment variables. The [on-demand Office tools](2026-10-31-native-office-tool.md) run Kit operations inside the Host, so no deployment publishes Kit CLI paths or Office executable variables.
|
||||
|
||||
@@ -12,6 +12,8 @@ Status: implemented
|
||||
|
||||
[Web bundle](../../../../packages/bundle/web-app/cordis.patch.yml) 和 [SDK bundle](../../../../packages/bundle/sdk-app/cordis.patch.yml) 在 Desktop、普通 `dsh web` 和 SDK 中都挂载 [`skill-office`](../../../../packages/skill/skill-office/README.zh.md)。这取代了[渐进式 Office 技能](2026-09-27-progressive-office-skills.zh.md)中的挂载决策;其技能结构仍然有效,[按需激活的 Office 工具](2026-10-31-native-office-tool.zh.md)取代了其 shell 执行方式。
|
||||
|
||||
所有部署都从 `skill-office` 包的 `assets/` 读取 Office 技能,该目录只包含 `SKILL.md` 文件。Host 直接读取这些文件:Desktop 的 Electron Host 在 `app.asar` 内读取,SDK 可执行程序则内嵌这些文件,因此没有载体把技能复制到单独的资源目录。组合可以通过 `assetRoot` 选择其他目录,或用同名文件系统技能覆盖单个技能。
|
||||
|
||||
内置 Python 环境([`tool-workspace-dependencies`](../../../../packages/skill/tool-workspace-dependencies/README.zh.md))只在 Desktop 以及有主运行时的 SDK 中挂载。`skill-office` 显式声明 `pythonEnvironment: 'workspace-dependencies' | 'user'`,由各 bundle 的配置行按部署选择取值。默认值为 `'workspace-dependencies'`,因为旧 SDK patch 会整体替换 `skill-office` 行的 `config`;这样的 patch 不含新键,仍保留它所针对的内置环境说明。
|
||||
|
||||
[`@deepseek-ai/dsh-office-kit`](../../../../packages/document/office-kit/README.zh.md) 负责 Kit 定位和共享转换服务。Kit 定位是提供方的内部实现,不对外导出:提供方传入自己的 `import.meta.resolve` 查找函数,原生引擎路径使用 ASAR 解包映射。定位不读取环境变量。[按需激活的 Office 工具](2026-10-31-native-office-tool.zh.md)在 Host 内执行 Kit 操作,因此任何部署都不发布 Kit CLI 路径或 Office 可执行文件变量。
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createServer } from 'node:http'
|
||||
import { cp, mkdir, mkdtemp, readFile, readdir, rm, writeFile } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { dirname, join } from 'node:path'
|
||||
import { dirname, join, sep } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { zstdDecompressSync } from 'node:zlib'
|
||||
import { resolveExampleLaunch } from '@deepseek-ai/dsh-loader-smoke'
|
||||
@@ -537,16 +537,14 @@ async function officeFixture(root: string, pythonOnly: boolean) {
|
||||
await mkdir(paths.pythonPackages, { recursive: true })
|
||||
if (paths.nodePackages !== undefined) await mkdir(paths.nodePackages, { recursive: true })
|
||||
await writeFile(join(source, 'runtime.json'), JSON.stringify(manifest))
|
||||
await cp(join(repoRoot, 'packages/skill/skill-office/assets'), join(root, 'resources', 'office-skills'), { recursive: true })
|
||||
return { source, paths }
|
||||
}
|
||||
|
||||
it.each(['unset', 'empty', 'full', 'python-only', 'python-only-patched', 'missing-assets', 'wrong-type'] as const)('composes Office resources through the SDK profile (%s)', async (mode) => {
|
||||
it.each(['unset', 'empty', 'full', 'python-only', 'python-only-patched', 'wrong-type'] as const)('composes Office resources through the SDK profile (%s)', async (mode) => {
|
||||
const root = await mkdtemp(join(tmpdir(), 'sdk-office-'))
|
||||
onTestFinished(() => rm(root, { recursive: true, force: true }))
|
||||
const enabled = mode !== 'unset' && mode !== 'empty'
|
||||
const { source, paths } = await officeFixture(root, mode.startsWith('python-only'))
|
||||
if (mode === 'missing-assets') await rm(join(root, 'resources', 'office-skills'), { recursive: true })
|
||||
if (mode === 'wrong-type') {
|
||||
await rm(paths.python)
|
||||
await mkdir(paths.python)
|
||||
@@ -554,7 +552,7 @@ it.each(['unset', 'empty', 'full', 'python-only', 'python-only-patched', 'missin
|
||||
const requests: Record<string, unknown>[] = []
|
||||
const toolUses = [
|
||||
...enabled ? [{ type: 'tool_use', id: 'workspace-dependencies', name: 'load_workspace_dependencies', input: {} } as const] : [],
|
||||
...mode === 'missing-assets' ? [] : [{ type: 'tool_use', id: 'office-docx', name: 'skill', input: { name: 'office-docx' } } as const],
|
||||
{ type: 'tool_use', id: 'office-docx', name: 'skill', input: { name: 'office-docx' } } as const,
|
||||
]
|
||||
const server = createServer((request, response) => {
|
||||
let body = ''
|
||||
@@ -572,9 +570,12 @@ it.each(['unset', 'empty', 'full', 'python-only', 'python-only-patched', 'missin
|
||||
if (address === null || typeof address === 'string') throw new Error('model fixture did not bind')
|
||||
const home = join(root, 'home')
|
||||
const cliPatch = join(root, 'cli.patch.yml')
|
||||
// This patch replaces the row's config without pythonEnvironment, as patches written before that option did;
|
||||
// the skill-office default keeps directing the model to the bundled environment.
|
||||
await writeFile(cliPatch, JSON.stringify(mode === 'python-only-patched' ? [{ id: 'skill-office', config: { assetRoot: join(root, 'resources', 'office-skills') } }] : []))
|
||||
const packagedSkills = join(repoRoot, 'packages/skill/skill-office/assets')
|
||||
const patchedSkills = join(root, 'patched-skills')
|
||||
if (mode === 'python-only-patched') await cp(packagedSkills, patchedSkills, { recursive: true })
|
||||
// This patch replaces the row's config with only assetRoot; the skill-office default keeps directing the model to
|
||||
// the bundled environment.
|
||||
await writeFile(cliPatch, JSON.stringify(mode === 'python-only-patched' ? [{ id: 'skill-office', config: { assetRoot: patchedSkills } }] : []))
|
||||
const officeLaunch = resolveExampleLaunch({
|
||||
srcBin: fileURLToPath(new URL('../../../src/bin.ts', import.meta.url)), mode: 'lib',
|
||||
configArgs: ['--profile', 'sdk', '--patch', cliPatch],
|
||||
@@ -605,24 +606,24 @@ it.each(['unset', 'empty', 'full', 'python-only', 'python-only-patched', 'missin
|
||||
}, () => stderr)
|
||||
const names = (requests[0]!.tools as { name: string }[]).map(value => value.name)
|
||||
expect(names.includes('load_workspace_dependencies')).toBe(enabled)
|
||||
expect(JSON.stringify(requests[0]!.messages).includes('`office`')).toBe(mode !== 'missing-assets')
|
||||
expect(JSON.stringify(requests[0]!.messages)).toContain('`office`')
|
||||
const officeTools = ['office_convert', 'office_recalculate', 'office_render']
|
||||
expect(names.filter(name => name.startsWith('office'))).toEqual([])
|
||||
const lastNames = (requests.at(-1)!.tools as { name: string }[]).map(value => value.name)
|
||||
// Loading an Office skill declares the Office tools from the following request.
|
||||
expect(lastNames.filter(name => name.startsWith('office')).sort()).toEqual(mode === 'missing-assets' ? [] : officeTools)
|
||||
expect(lastNames.filter(name => name.startsWith('office')).sort()).toEqual(officeTools)
|
||||
expect(names).not.toContain('render_document')
|
||||
for (const name of ['office-docx', 'office-pptx', 'office-xlsx']) {
|
||||
expect(JSON.stringify(requests[0]!.messages).includes(name)).toBe(mode !== 'missing-assets')
|
||||
expect(JSON.stringify(requests[0]!.messages)).toContain(name)
|
||||
}
|
||||
expect(requests).toHaveLength(toolUses.length + 1)
|
||||
if (mode !== 'missing-assets') {
|
||||
const messages = requests.at(-1)!.messages as { content: { type: string; tool_use_id?: string; content?: unknown }[] }[]
|
||||
const skill = JSON.stringify(messages.flatMap(message => message.content).find(block => block.tool_use_id === 'office-docx'))
|
||||
expect(skill).toContain('## Python environment')
|
||||
expect(skill.includes('Otherwise call `load_workspace_dependencies`')).toBe(enabled)
|
||||
expect(skill.includes('`python3` or `python` on PATH')).toBe(!enabled)
|
||||
}
|
||||
const messages = requests.at(-1)!.messages as { content: { type: string; tool_use_id?: string; content?: unknown }[] }[]
|
||||
const skill = JSON.stringify(messages.flatMap(message => message.content).find(block => block.tool_use_id === 'office-docx'))
|
||||
const skillDirectory = join(mode === 'python-only-patched' ? patchedSkills : packagedSkills, 'office-docx').split(sep).join('/')
|
||||
expect(skill).toContain(JSON.stringify(`Base directory for this skill: ${skillDirectory}`).slice(1, -1))
|
||||
expect(skill).toContain('## Python environment')
|
||||
expect(skill.includes('Otherwise call `load_workspace_dependencies`')).toBe(enabled)
|
||||
expect(skill.includes('`python3` or `python` on PATH')).toBe(!enabled)
|
||||
if (mode === 'wrong-type') {
|
||||
const messages = requests[1]!.messages as { content: { type: string; tool_use_id?: string; content?: unknown }[] }[]
|
||||
const result = messages.flatMap(message => message.content).find(block => block.tool_use_id === 'workspace-dependencies')
|
||||
|
||||
@@ -27,8 +27,8 @@
|
||||
en: 3724c53da4f7647b
|
||||
zh: 76ae549d43c99bb4
|
||||
/deepseek-harness-desktop/key-technical-decisions/bundled-workspace-dependencies:
|
||||
en: a6a3ce8700e11faa
|
||||
zh: 9c3f915f9c1e8c93
|
||||
en: 9eb34500d71cbc0a
|
||||
zh: 90e6a98d4c2c0f14
|
||||
/deepseek-harness-desktop/bundled-command-runtime:
|
||||
en: 372297005b57a08b
|
||||
zh: 074a635ac17e4362
|
||||
|
||||
@@ -103,7 +103,7 @@ Signed Windows packaging scans the primary runtime and application production de
|
||||
|
||||
Desktop carries independent Python, Node.js and pnpm distributions. Python includes numpy, pandas, python-docx, python-pptx, openpyxl, Pillow, lxml and XlsxWriter with their complete dependencies. The `load_workspace_dependencies` tool installs this payload offline on first use under `$DSH_HOME/dsh-runtimes/dsh-primary-runtime` (normally `~/.dsh/dsh-runtimes/dsh-primary-runtime`) and returns absolute interpreter, pnpm script and library paths plus `pythonDistributions`, the bundled distribution names and versions. The version report excludes user-installed additions. Office tasks prefer these libraries unless user or workspace instructions select another environment. Execute the pnpm script with the returned Node executable. The returned Node library directory is reserved for bundled libraries, not pnpm's global installation directory.
|
||||
|
||||
Desktop reads the [Office skills](../../packages/skill/skill-office/README.md) from `runtime/office-skills` outside ASAR, and their Python authoring uses the bundled libraries above; the skill package itself may remain in ASAR. Missing or invalid Office resources, or an unloadable Office profile row, do not stop Host startup.
|
||||
Desktop reads the [Office skills](../../packages/skill/skill-office/README.md) from the skill package's assets inside ASAR, and their Python authoring uses the bundled libraries above. Missing or invalid Office resources, or an unloadable Office profile row, do not stop Host startup.
|
||||
|
||||
The payload follows the Desktop release. `runtime.json` records the Desktop version, target, top-level interpreter/package-manager versions and the Python distribution map, and a digest of the selected target’s locked payload inputs and assembly format. Distribution names use PEP 503 normalization; duplicate normalized names reject the manifest. Legacy `components` manifests remain readable through normalization, with their existing library-version consistency checks. Matching installations are reused; a dependency or archive change replaces the directory after a complete staged copy even when the Desktop version stays unchanged. Older manifests without a digest are replaced on their next installation. User-added Python packages remain only while the payload identity matches. A failed directory replacement retains the previous installation; Windows may refuse replacement while an interpreter is still running.
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ Windows 签名打包按 PE 文件内容扫描第一方运行时和应用生产
|
||||
|
||||
Desktop 携带独立的 Python、Node.js 和 pnpm 分发包。Python 包含 numpy、pandas、python-docx、python-pptx、openpyxl、Pillow、lxml、XlsxWriter 及其完整依赖。`load_workspace_dependencies` 工具首次使用时,将该产物离线安装到 `$DSH_HOME/dsh-runtimes/dsh-primary-runtime`(通常为 `~/.dsh/dsh-runtimes/dsh-primary-runtime`),并返回解释器、pnpm 脚本和库目录的绝对路径,以及记录内置分发包名称与版本的 `pythonDistributions`。版本报告不包含用户自行安装的包。Office 任务默认使用这些库,用户或工作区指令指定其他环境时遵循其要求。pnpm 脚本通过返回的 Node 可执行文件运行。返回的 Node 库目录为随包交付的库预留,不是 pnpm 的全局安装目录。
|
||||
|
||||
Desktop 从 ASAR 之外的 `runtime/office-skills` 读取 [Office 技能](../../packages/skill/skill-office/README.zh.md),其 Python 创作使用上述内置库;技能包本身可以留在 ASAR 中。Office 资源缺失或无效、Office profile 行无法加载时,都不会阻止 Host 启动。
|
||||
Desktop 从 ASAR 内技能包的 assets 读取 [Office 技能](../../packages/skill/skill-office/README.zh.md),其 Python 创作使用上述内置库。Office 资源缺失或无效、Office profile 行无法加载时,都不会阻止 Host 启动。
|
||||
|
||||
该产物随 Desktop 版本发布。`runtime.json` 记录 Desktop 版本、目标平台、顶层解释器和包管理器版本及 Python 分发包版本表,以及所选目标的锁定产物输入与组装格式的摘要。分发包名称按 PEP 503 归一化;名称归一化后重复时,清单会被拒绝。旧 `components` 清单通过归一化继续可读,并保留其原有库版本一致性校验。匹配的安装会被复用;依赖或压缩包变化后,即使 Desktop 版本不变,也会在完整暂存副本完成后替换目录。不含摘要的旧清单会在下次安装时被替换。用户自行添加的 Python 包仅在产物身份一致时保留。目录替换失败时保留之前的安装;解释器仍在运行时,Windows 可能拒绝替换。
|
||||
|
||||
|
||||
@@ -45,8 +45,8 @@
|
||||
en: b42f103358d0dfd7
|
||||
zh: a534164ad9eb00da
|
||||
/desktop-packaging/runtime-file-selection-and-installation-checks:
|
||||
en: 15f31e576a1dda3e
|
||||
zh: a8b589657ad73179
|
||||
en: a92b8fda03ae3f19
|
||||
zh: 5b2f0b4e1cad1745
|
||||
/desktop-packaging/desktop-runtime-preparation:
|
||||
en: 84be38ab3136fff7
|
||||
zh: bb4ed12ee0e1dfa0
|
||||
|
||||
@@ -252,7 +252,7 @@ Desktop packs workspace packages locally and installs external dependencies thro
|
||||
|
||||
The [Electron shell file list](../scripts/electron-builder-config.mjs) excludes recognized JavaScript/CSS/TypeScript source maps and TypeScript build caches from the separate `resources/app.asar/node_modules` dependency collection. These exclusions preserve runtime code, native modules, WASM, data files (including ordinary `.map` assets), licenses, and notices; [shell archive tests](../tests/shell-file-selection.spec.ts) check retained bytes and exclusions.
|
||||
|
||||
The [Office conversion provider](../../../packages/document/office-kit/README.md) is a Host service mounted by web-app, so every application that loads web-app, including `dsh web` and Desktop, provides it together with the [Office tools](../../../packages/document/tool-office/README.md). The provider [resolves the Kit's engine itself](../../../packages/document/office-kit/README.md#understand-the-implementation), including the mapping of native engine manifests to unpacked physical directories. Desktop packaging only carries and unpacks the engine: it carries the target’s declared native engine, or WASM when the kit declares no matching native target, and preparation rejects a missing target engine before packaging. The complete Office dependencies, including the JavaScript libraries and the selected engine’s executables, data, licenses, and notices, are unpacked under `resources/app.asar.unpacked/dsh/node_modules/`. The Desktop shell passes the primary runtime location to the Host in [main.ts](../src/main.ts), and the Host sets it as `DSH_PRIMARY_RUNTIME`; neither passes a Kit location. Office skills are available in every application that loads web-app; only Desktop mounts the bundled Python environment (`workspace-dependencies`). On macOS, the native helper receives the JIT entitlement required by [LibreOffice’s UNO bridge](https://github.com/LibreOffice/core/blob/master/sysui/desktop/macosx/hardened_runtime.xcent.in). The Computer Use helper applications under `@deepseek-ai/dsh-experimental-cua-macos/native/bin` are also unpacked whole under `resources/app.asar.unpacked/dsh/node_modules/`, and the provider opens that copy.
|
||||
The [Office conversion provider](../../../packages/document/office-kit/README.md) is a Host service mounted by web-app, so every application that loads web-app, including `dsh web` and Desktop, provides it together with the [Office tools](../../../packages/document/tool-office/README.md). The provider [resolves the Kit's engine itself](../../../packages/document/office-kit/README.md#understand-the-implementation), including the mapping of native engine manifests to unpacked physical directories. Desktop packaging only carries and unpacks the engine: it carries the target’s declared native engine, or WASM when the kit declares no matching native target, and preparation rejects a missing target engine before packaging. The complete Office dependencies, including the JavaScript libraries and the selected engine’s executables, data, licenses, and notices, are unpacked under `resources/app.asar.unpacked/dsh/node_modules/`. The Desktop shell passes the primary runtime location to the Host in [main.ts](../src/main.ts), and the Host sets it as `DSH_PRIMARY_RUNTIME`; neither passes a Kit location. Office skills are available in every application that loads web-app, and Desktop reads them from the skill package inside `resources/app.asar`; only Desktop mounts the bundled Python environment (`workspace-dependencies`). On macOS, the native helper receives the JIT entitlement required by [LibreOffice’s UNO bridge](https://github.com/LibreOffice/core/blob/master/sysui/desktop/macosx/hardened_runtime.xcent.in). The Computer Use helper applications under `@deepseek-ai/dsh-experimental-cua-macos/native/bin` are also unpacked whole under `resources/app.asar.unpacked/dsh/node_modules/`, and the provider opens that copy.
|
||||
|
||||
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; The [payload smoke](../tests/fixtures/runtime-payload-smoke.mjs) runs under Electron RunAsNode before the Host smoke and final inventory verification. The payload smoke resolves the search tool’s ripgrep executable and verifies text search and file enumeration. Signed Windows builds run these checks after dependency signing; other builds run them during `prepare:dsh`. The [Host smoke](../scripts/smoke-runtime.ts) creates DOCX, XLSX, and PPTX inputs with bundled Python and runs each through the real Office provider: PDF conversion, PNG rendering, conversion to the matching OpenDocument format, and, for XLSX, recalculation, checking each output's signature. Every assembled application, including directory and unsigned builds, repeats the payload and Host checks against ASAR. Archive integrity checks compare the complete archived descriptor with preparation, file bytes and membership in both archive and unpacked trees, recorded executable flags for archived files, and physical permissions for unpacked files. Conversion failures stop packaging before a release record is written; macOS DMG/ZIP builds run these checks before notarization.
|
||||
|
||||
|
||||
@@ -252,7 +252,7 @@ Desktop 在本地打包工作区包,并通过目标捆绑的 Node 和 pnpm 安
|
||||
|
||||
[Electron 外壳文件清单](../scripts/electron-builder-config.mjs)从单独收集的 `resources/app.asar/node_modules` 依赖中排除已识别的 JavaScript/CSS/TypeScript source map 和 TypeScript 构建缓存。这些排除规则保留运行时代码、原生模块、WASM、数据文件(包括普通 `.map` 资源)、许可证和 notices;[外壳归档测试](../tests/shell-file-selection.spec.ts)检查保留文件的字节和排除项。
|
||||
|
||||
[Office 转换提供方](../../../packages/document/office-kit/README.zh.md)是 web-app 挂载的 Host 服务,因此所有加载 web-app 的应用(包括 `dsh web` 和 Desktop)都提供它以及 [Office 工具](../../../packages/document/tool-office/README.zh.md)。该提供方[自行解析 Kit 的引擎](../../../packages/document/office-kit/README.zh.md#understand-the-implementation),包括把原生引擎清单映射到解包后的实体目录。Desktop 打包只负责携带和解包引擎:携带目标声明的原生引擎;kit 未声明匹配的原生目标时携带 WASM;目标引擎缺失时,准备阶段在打包前拒绝。完整的 Office 依赖(包括 JavaScript 库以及所选引擎的可执行文件、数据、许可证和声明)解包到 `resources/app.asar.unpacked/dsh/node_modules/` 下。Desktop 壳在 [main.ts](../src/main.ts) 中把主运行时位置传给 Host,由 Host 设为 `DSH_PRIMARY_RUNTIME`;两者都不传 Kit 位置。所有加载 web-app 的应用都提供 Office 技能;只有 Desktop 挂载内置 Python 环境(`workspace-dependencies`)。在 macOS 上,原生 helper 获得 [LibreOffice UNO 桥](https://github.com/LibreOffice/core/blob/master/sysui/desktop/macosx/hardened_runtime.xcent.in)所需的 JIT 权限。`@deepseek-ai/dsh-experimental-cua-macos/native/bin` 下的 Computer Use helper 应用也整体解包到 `resources/app.asar.unpacked/dsh/node_modules/` 下,提供方打开该副本。 `@deepseek-ai/dsh-experimental-cua-macos/native/bin` 下的 Computer Use helper 应用也整体解包到 `resources/app.asar.unpacked/dsh/node_modules/` 下,提供方打开该副本。
|
||||
[Office 转换提供方](../../../packages/document/office-kit/README.zh.md)是 web-app 挂载的 Host 服务,因此所有加载 web-app 的应用(包括 `dsh web` 和 Desktop)都提供它以及 [Office 工具](../../../packages/document/tool-office/README.zh.md)。该提供方[自行解析 Kit 的引擎](../../../packages/document/office-kit/README.zh.md#understand-the-implementation),包括把原生引擎清单映射到解包后的实体目录。Desktop 打包只负责携带和解包引擎:携带目标声明的原生引擎;kit 未声明匹配的原生目标时携带 WASM;目标引擎缺失时,准备阶段在打包前拒绝。完整的 Office 依赖(包括 JavaScript 库以及所选引擎的可执行文件、数据、许可证和声明)解包到 `resources/app.asar.unpacked/dsh/node_modules/` 下。Desktop 壳在 [main.ts](../src/main.ts) 中把主运行时位置传给 Host,由 Host 设为 `DSH_PRIMARY_RUNTIME`;两者都不传 Kit 位置。所有加载 web-app 的应用都提供 Office 技能,Desktop 从 `resources/app.asar` 内的技能包读取它们;只有 Desktop 挂载内置 Python 环境(`workspace-dependencies`)。在 macOS 上,原生 helper 获得 [LibreOffice UNO 桥](https://github.com/LibreOffice/core/blob/master/sysui/desktop/macosx/hardened_runtime.xcent.in)所需的 JIT 权限。`@deepseek-ai/dsh-experimental-cua-macos/native/bin` 下的 Computer Use helper 应用也整体解包到 `resources/app.asar.unpacked/dsh/node_modules/` 下,提供方打开该副本。
|
||||
|
||||
打包应用运行编译后的 JavaScript 和预生成的 Typert 元数据,不编译 TypeScript 插件。源码级调试导航和编辑器声明仍可从开发包中获取。[复制规则测试](../tests/runtime-file-policy.spec.ts)覆盖排除项和保留资源;[产物 smoke](../tests/fixtures/runtime-payload-smoke.mjs) 在 Host smoke 和最终清单验证之前,使用 Electron RunAsNode 执行。产物 smoke 解析搜索工具使用的 ripgrep 可执行文件,并验证文本搜索和文件枚举。Windows 签名构建在依赖签名后运行这些检查;其他构建在 `prepare:dsh` 中运行。[Host smoke](../scripts/smoke-runtime.ts) 使用捆绑的 Python 创建 DOCX、XLSX 和 PPTX 输入,并通过真实 Office 提供方逐一执行:PDF 转换、PNG 渲染、转换为对应的 OpenDocument 格式,以及针对 XLSX 的重算,检查每个输出的签名。每个组装后的应用(包括目录包和未签名构建)都会针对 ASAR 重复产物和 Host 检查。归档完整性检查将归档内完整描述符与准备结果比对,并核对归档和解包目录中的文件内容与清单、归档内文件记录的执行标志,以及解包文件的物理权限。转换失败会在写入发布记录前终止打包;macOS DMG/ZIP 构建在公证前执行这些检查。
|
||||
|
||||
|
||||
@@ -13,7 +13,8 @@ import { createPluginProfile } from '../src/project-manager.ts'
|
||||
import type { DesktopRuntimeDescriptor } from '../src/runtime-tree.ts'
|
||||
|
||||
/**
|
||||
* Check CLI package operations, Host startup, its frontend, external plugins, and real Office rendering, conversion, and recalculation.
|
||||
* Check CLI package operations, Host startup, its frontend, external plugins, packaged Office skill loading,
|
||||
* and real Office rendering, conversion, and recalculation.
|
||||
* @param root - Materialized dsh resources.
|
||||
* @param node - Prepared target Electron executable.
|
||||
* @param runtime - Verified resource descriptor.
|
||||
@@ -100,6 +101,21 @@ export function apply(ctx) {
|
||||
response.end(inspect(error, { depth: 5 }))
|
||||
}
|
||||
} }))
|
||||
ctx.effect(() => ctx.webServer.register({ kind: 'exact', path: '/desktop-smoke-skills',
|
||||
async handler(_request, response) {
|
||||
try {
|
||||
const names = ['office', 'office-docx', 'office-pptx', 'office-xlsx']
|
||||
const listed = new Set((await ctx.skills.list()).map(skill => skill.name))
|
||||
const loaded = await Promise.all(names.map(async name => {
|
||||
const skill = listed.has(name) ? await ctx.skills.get(name) : undefined
|
||||
return { name, path: skill?.path ?? null, bytes: skill?.content.length ?? 0 }
|
||||
}))
|
||||
response.end(JSON.stringify(loaded))
|
||||
} catch (error) {
|
||||
response.statusCode = 500
|
||||
response.end(inspect(error, { depth: 5 }))
|
||||
}
|
||||
} }))
|
||||
for (const input of ${JSON.stringify(inputs)}) {
|
||||
ctx.effect(() => ctx.webServer.register({ kind: 'exact', path: '/desktop-smoke-office/' + input.extension,
|
||||
async handler(_request, response) {
|
||||
@@ -142,7 +158,7 @@ export function apply(ctx) {
|
||||
}
|
||||
}
|
||||
`)
|
||||
writeFileSync(join(plugin, 'bundle.yml'), '- insert:\n - id: desktop-runtime-smoke-plugin\n name: desktop-runtime-smoke-plugin\n inject: [webServer, officeKit, pluginManager]\n')
|
||||
writeFileSync(join(plugin, 'bundle.yml'), '- insert:\n - id: desktop-runtime-smoke-plugin\n name: desktop-runtime-smoke-plugin\n inject: [webServer, officeKit, pluginManager, skills]\n')
|
||||
cpSync(plugin, join(profile, 'node_modules', pluginName), { recursive: true })
|
||||
const manifest = JSON.parse(readFileSync(join(profile, 'package.json'), 'utf8')) as {
|
||||
dependencies?: Record<string, string>
|
||||
@@ -172,6 +188,13 @@ export function apply(ctx) {
|
||||
if (!installation.ok || !installed.ok || await installed.text() !== 'installed plugin ready') {
|
||||
throw new Error(`desktop runtime: bundled pnpm plugin installation failed: ${installationResult}`)
|
||||
}
|
||||
const skills = await fetch(new URL('/desktop-smoke-skills', ready.url), { headers: { cookie }, signal: AbortSignal.timeout(120_000) })
|
||||
if (!skills.ok) throw new Error(`desktop runtime: Office skill loading failed: ${await skills.text()}`)
|
||||
const loadedSkills = await skills.json() as Array<{ name: string; path: string | null; bytes: number }>
|
||||
if (loadedSkills.some(skill => skill.path === null || skill.bytes === 0)) {
|
||||
throw new Error(`desktop runtime: packaged Office skills are not loadable: ${JSON.stringify(loadedSkills)}`)
|
||||
}
|
||||
console.log('desktop runtime: packaged Office skills listed and loaded')
|
||||
for (const { extension } of inputs) {
|
||||
const converted = await fetch(new URL(`/desktop-smoke-office/${extension}`, ready.url), {
|
||||
headers: { cookie }, signal: AbortSignal.timeout(120_000),
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
/** Built Desktop Host startup with incomplete or unusable Office resources; run after the repository build. */
|
||||
|
||||
import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { assertNever } from '@deepseek-ai/dsh-util-values'
|
||||
import { DesktopHostProcess } from '../src/host-process.ts'
|
||||
import { builtHost, prepareDesktopHome, repository } from './harness.ts'
|
||||
import { builtHost, prepareDesktopHome } from './harness.ts'
|
||||
|
||||
afterEach(() => { vi.unstubAllEnvs() })
|
||||
|
||||
@@ -47,11 +46,9 @@ export function apply(ctx) {
|
||||
|
||||
describe.skipIf(!existsSync(builtHost))('built Desktop Host Office resources', () => {
|
||||
it.each([
|
||||
['one missing Office skill file', 'missing-skill'],
|
||||
['an empty runtime directory', 'empty-runtime'],
|
||||
['an unusable primary runtime path', 'relative-source'],
|
||||
['complete Office resources', 'complete'],
|
||||
] as const)('starts the Host with %s', async (_label, mode) => {
|
||||
] as const)('starts the Host with %s and the packaged Office skills', async (_label, mode) => {
|
||||
const root = mkdtempSync(join(tmpdir(), 'dsh-desktop-office-'))
|
||||
let host: DesktopHostProcess | undefined
|
||||
const failures: Error[] = []
|
||||
@@ -59,10 +56,6 @@ describe.skipIf(!existsSync(builtHost))('built Desktop Host Office resources', (
|
||||
const { project, paths } = await prepareDesktopHome(root)
|
||||
const runtime = join(root, 'runtime')
|
||||
mkdirSync(runtime)
|
||||
if (mode !== 'empty-runtime') {
|
||||
cpSync(join(repository, 'packages/skill/skill-office/assets'), join(runtime, 'office-skills'), { recursive: true })
|
||||
if (mode === 'missing-skill') rmSync(join(runtime, 'office-skills', 'office-docx', 'SKILL.md'))
|
||||
}
|
||||
writeFileSync(join(paths.profile, 'cordis.patch.yml'), '- id: webserver\n config:\n host: 127.0.0.1\n port: 0\n')
|
||||
installSkillProbe(paths.profile)
|
||||
const primaryRuntime = mode === 'relative-source' ? 'runtime/primary-runtime' : join(runtime, 'primary-runtime')
|
||||
@@ -81,24 +74,10 @@ describe.skipIf(!existsSync(builtHost))('built Desktop Host Office resources', (
|
||||
expect(skills.officeKit).toBe(true)
|
||||
// Agent presets, not the Host root, mount the Office tools.
|
||||
expect(skills.tools).toEqual([])
|
||||
switch (mode) {
|
||||
case 'complete':
|
||||
expect(skills.names.toSorted()).toEqual(OFFICE_SKILLS)
|
||||
expect(skills.content['office-docx']).toEqual(expect.any(String))
|
||||
expect(skills.content.office).toContain('`office_render`')
|
||||
break
|
||||
case 'missing-skill':
|
||||
expect(skills.names.toSorted()).toEqual(OFFICE_SKILLS.filter(name => name !== 'office-docx'))
|
||||
expect(skills.content['office-docx']).toBeNull()
|
||||
expect(skills.content.office).toContain('`office_render`')
|
||||
break
|
||||
case 'empty-runtime':
|
||||
case 'relative-source':
|
||||
expect(skills).toMatchObject({ names: [], content: { office: null, 'office-docx': null } })
|
||||
break
|
||||
default:
|
||||
assertNever(mode)
|
||||
}
|
||||
// skill-office reads the skills from its packaged assets, independent of the primary runtime directory.
|
||||
expect(skills.names.toSorted()).toEqual(OFFICE_SKILLS)
|
||||
expect(skills.content['office-docx']).toEqual(expect.any(String))
|
||||
expect(skills.content.office).toContain('`office_render`')
|
||||
expect(failures).toEqual([])
|
||||
} finally {
|
||||
await host?.stop()
|
||||
|
||||
@@ -10,7 +10,7 @@ import { DesktopHostProcess } from '../src/host-process.ts'
|
||||
import { desktopClientMetadata } from '../src/client-metadata.ts'
|
||||
import { connectDesktopWelcome, type DesktopWelcomeBackend } from '../src/welcome-backend.ts'
|
||||
import { authenticateWebHost } from '../src/web-document.ts'
|
||||
import { builtHost, prepareDesktopHome, repository } from './harness.ts'
|
||||
import { builtHost, prepareDesktopHome } from './harness.ts'
|
||||
|
||||
afterEach(() => { vi.unstubAllEnvs() })
|
||||
|
||||
@@ -80,7 +80,6 @@ describe.skipIf(!existsSync(builtHost))('built Desktop welcome flow', () => {
|
||||
const platform = await mockPlatform()
|
||||
try {
|
||||
const { home, project, paths } = await prepareDesktopHome(root)
|
||||
cpSync(join(repository, 'packages/skill/skill-office/assets'), join(root, 'runtime/office-skills'), { recursive: true })
|
||||
const nodeBin = join(root, 'runtime/primary-runtime/dependencies/node/bin')
|
||||
mkdirSync(nodeBin, { recursive: true })
|
||||
cpSync(process.execPath, join(nodeBin, process.platform === 'win32' ? 'node.exe' : 'node'))
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
en: 6c5aaef399e15878
|
||||
zh: 92c4ae1a8d812790
|
||||
/office-operation-skills-are-replaced-by-office-tools/change:
|
||||
en: 0781bed511c577fc
|
||||
zh: 046098b87627786e
|
||||
en: 1d12b14e002bcf3b
|
||||
zh: ac844c2aeb9f9053
|
||||
/office-operation-skills-are-replaced-by-office-tools/migration:
|
||||
en: 9646620e0c4636c3
|
||||
zh: be896458a90937ba
|
||||
@@ -19,7 +19,7 @@ Configuration changes:
|
||||
| `DSH_BUNDLED_OFFICE_CLI` and `DSH_BUNDLED_OFFICE_NODE` | None; no component reads them |
|
||||
| `DSH_BUNDLED_PRIMARY_RUNTIME` | `DSH_PRIMARY_RUNTIME` |
|
||||
|
||||
`skill-office` gains `pythonEnvironment`. Its default, `'workspace-dependencies'`, directs the Python skills to `load_workspace_dependencies`; a composition without `@deepseek-ai/dsh-tool-workspace-dependencies` must set `'user'`. `DSH_BUNDLED_OFFICE_SKILLS` keeps its meaning.
|
||||
`skill-office` gains `pythonEnvironment`. Its default, `'workspace-dependencies'`, directs the Python skills to `load_workspace_dependencies`; a composition without `@deepseek-ai/dsh-tool-workspace-dependencies` must set `'user'`. [Office skill resources](../office-skill-resources/guide.md) covers the removal of `DSH_BUNDLED_OFFICE_SKILLS`.
|
||||
|
||||
The Office tools parse untrusted documents with the native LibreOffice Kit engine in the Host process, outside the shell sandbox and PTC permissions, with the Host user's permissions; the filesystem backend still authorizes inputs. The `office-kit` row gains `outputDirectory` (default `$DSH_HOME/office-kit/outputs`), `outputRetentionMs`, and `maxOutputStoreBytes`.
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ description: "删除 `office-render`、`office-recalculate` 和 `office-export-p
|
||||
| `DSH_BUNDLED_OFFICE_CLI` 和 `DSH_BUNDLED_OFFICE_NODE` | 无;不再有组件读取它们 |
|
||||
| `DSH_BUNDLED_PRIMARY_RUNTIME` | `DSH_PRIMARY_RUNTIME` |
|
||||
|
||||
`skill-office` 新增 `pythonEnvironment`。默认值 `'workspace-dependencies'` 让 Python 技能调用 `load_workspace_dependencies`;没有挂载 `@deepseek-ai/dsh-tool-workspace-dependencies` 的组合必须设为 `'user'`。`DSH_BUNDLED_OFFICE_SKILLS` 含义不变。
|
||||
`skill-office` 新增 `pythonEnvironment`。默认值 `'workspace-dependencies'` 让 Python 技能调用 `load_workspace_dependencies`;没有挂载 `@deepseek-ai/dsh-tool-workspace-dependencies` 的组合必须设为 `'user'`。`DSH_BUNDLED_OFFICE_SKILLS` 的删除见 [Office 技能资源](../office-skill-resources/guide.zh.md)。
|
||||
|
||||
Office 工具在 Host 进程内用原生 LibreOffice Kit 引擎解析不可信的文档,不受 shell 沙箱和 PTC 权限约束,并具有 Host 用户的权限;输入仍由文件系统后端授权。`office-kit` 条目新增 `outputDirectory`(默认 `$DSH_HOME/office-kit/outputs`)、`outputRetentionMs` 和 `maxOutputStoreBytes`。
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
# Bilingual-pair consistency record for guide.md (docs/i18n/README.md): per heading
|
||||
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/upgrade-guide/v0.2.2-alpha.3/office-skill-resources/guide.md
|
||||
/:
|
||||
en: be76cf53968415f9
|
||||
zh: e641c2ede9b53fcd
|
||||
/office-skills-load-from-the-packaged-assets:
|
||||
en: 9dadceb35e48b91c
|
||||
zh: 5c7103427581a6bd
|
||||
/office-skills-load-from-the-packaged-assets/change:
|
||||
en: da134862e17a7bd2
|
||||
zh: dc31dc816c669add
|
||||
/office-skills-load-from-the-packaged-assets/migration:
|
||||
en: e9c9fc8e2ec5a9b0
|
||||
zh: e736d57c47fb111c
|
||||
@@ -0,0 +1,36 @@
|
||||
---
|
||||
kind: upgrade-guide
|
||||
description: "`DSH_BUNDLED_OFFICE_SKILLS` and the `office-skills/` resource directories are removed; Office skills load from the `skill-office` package's assets."
|
||||
---
|
||||
|
||||
# Office skills load from the packaged assets
|
||||
|
||||
English | [中文](guide.zh.md)
|
||||
|
||||
## Change
|
||||
|
||||
The Web and SDK profiles no longer set `assetRoot` on the `skill-office` row, so [`@deepseek-ai/dsh-skill-office`](../../../../packages/skill/skill-office/README.md) reads the Office skills from its packaged `assets/` in every deployment:
|
||||
|
||||
| Removed | Replacement |
|
||||
|---|---|
|
||||
| `DSH_BUNDLED_OFFICE_SKILLS` | `assetRoot` in a profile patch, or same-name filesystem skills |
|
||||
| Desktop `resources/runtime/office-skills/` | The skill package inside `resources/app.asar` |
|
||||
| Python runtime wheel `<platform>-<arch>/office-skills/` | The skill package inside the runtime executable |
|
||||
| `office-skills/` beside `primary-runtime/` in `pnpm run prepare:primary-runtime` output and the downloaded authoring payload | None |
|
||||
|
||||
`pythonEnvironment` and its values are unchanged. This affects launchers that set `DSH_BUNDLED_OFFICE_SKILLS`, tools that read or edit skill files in those directories, and container images that copy `office-skills/` beside `primary-runtime/`.
|
||||
|
||||
## Migration
|
||||
|
||||
1. Stop setting `DSH_BUNDLED_OFFICE_SKILLS`. To keep a custom skill directory, set `assetRoot` in `$DSH_HOME/profiles/<profile>/cordis.patch.yml`. A patch replaces the row's whole `config`, so also set `pythonEnvironment: user` unless the profile mounts `workspace-dependencies`, which requires a nonempty `DSH_PRIMARY_RUNTIME` in Desktop and the SDK:
|
||||
|
||||
```yaml
|
||||
- id: skill-office
|
||||
config:
|
||||
assetRoot: /absolute/path/to/office-skills
|
||||
pythonEnvironment: user
|
||||
```
|
||||
|
||||
2. Alternatively, place a skill named `office`, `office-docx`, `office-pptx`, or `office-xlsx` in a [project or user skill directory](../../../../packages/skill/skill-filesystem/README.md); filesystem skills take precedence over the bundled skills with the same name.
|
||||
3. Remove steps that copy or read `office-skills/` in Desktop resources, Python wheels, or prepared primary-runtime payloads. Containers copy only `primary-runtime/` and keep `DSH_PRIMARY_RUNTIME` pointing at it.
|
||||
4. Confirm: in a new Session, load `office-docx`; its result names a base directory under the skill package's `assets/`, under the configured `assetRoot`, or in the overriding skill directory.
|
||||
@@ -0,0 +1,36 @@
|
||||
---
|
||||
kind: upgrade-guide
|
||||
description: "`DSH_BUNDLED_OFFICE_SKILLS` 和 `office-skills/` 资源目录被删除;Office 技能从 `skill-office` 包的 assets 加载。"
|
||||
---
|
||||
|
||||
# Office 技能从包内 assets 加载
|
||||
|
||||
[English](guide.md) | 中文
|
||||
|
||||
## 变更
|
||||
|
||||
Web 和 SDK profile 不再为 `skill-office` 行设置 `assetRoot`,因此 [`@deepseek-ai/dsh-skill-office`](../../../../packages/skill/skill-office/README.zh.md) 在所有部署中都从包内 `assets/` 读取 Office 技能:
|
||||
|
||||
| 已删除 | 替代 |
|
||||
|---|---|
|
||||
| `DSH_BUNDLED_OFFICE_SKILLS` | profile patch 中的 `assetRoot`,或同名文件系统技能 |
|
||||
| Desktop `resources/runtime/office-skills/` | `resources/app.asar` 内的技能包 |
|
||||
| Python 运行时 wheel 的 `<platform>-<arch>/office-skills/` | 运行时可执行程序内的技能包 |
|
||||
| `pnpm run prepare:primary-runtime` 输出和下载的创作载荷中与 `primary-runtime/` 同级的 `office-skills/` | 无 |
|
||||
|
||||
`pythonEnvironment` 及其取值不变。受影响的有:设置 `DSH_BUNDLED_OFFICE_SKILLS` 的启动器,读取或编辑上述目录中技能文件的工具,以及把 `office-skills/` 复制到 `primary-runtime/` 旁边的容器镜像。
|
||||
|
||||
## 迁移
|
||||
|
||||
1. 不再设置 `DSH_BUNDLED_OFFICE_SKILLS`。要继续使用自定义技能目录,在 `$DSH_HOME/profiles/<profile>/cordis.patch.yml` 中设置 `assetRoot`。patch 会替换该行的整个 `config`,因此除非 profile 挂载了 `workspace-dependencies`(Desktop 和 SDK 都要求 `DSH_PRIMARY_RUNTIME` 非空),还要设置 `pythonEnvironment: user`:
|
||||
|
||||
```yaml
|
||||
- id: skill-office
|
||||
config:
|
||||
assetRoot: /absolute/path/to/office-skills
|
||||
pythonEnvironment: user
|
||||
```
|
||||
|
||||
2. 也可以在[项目或用户技能目录](../../../../packages/skill/skill-filesystem/README.zh.md)中放置名为 `office`、`office-docx`、`office-pptx` 或 `office-xlsx` 的技能;文件系统技能优先于同名内置技能。
|
||||
3. 删除从 Desktop 资源、Python wheel 或准备好的主运行时载荷中复制或读取 `office-skills/` 的步骤。容器只复制 `primary-runtime/`,并让 `DSH_PRIMARY_RUNTIME` 继续指向它。
|
||||
4. 确认:在新 Session 中加载 `office-docx`,其结果给出的基础目录位于技能包的 `assets/` 下、所配置的 `assetRoot` 下,或覆盖它的技能目录中。
|
||||
@@ -15,8 +15,8 @@
|
||||
en: 4e53164159b5ce41
|
||||
zh: 89582dc0294d0776
|
||||
/deepseek-ai-dsh-sdk-app/use-this-package:
|
||||
en: b8bd8cefbb8c12a0
|
||||
zh: 2a3df8cb862a3f7f
|
||||
en: 544309ea0bdf6d79
|
||||
zh: 67f1f40c075d38b2
|
||||
/deepseek-ai-dsh-sdk-app/model-experience:
|
||||
en: 215e7ba838619b7b
|
||||
zh: a311da3843709f90
|
||||
|
||||
@@ -37,7 +37,7 @@ The SDK uses the base `read`, `write`, and `edit` defaults. To add `str_replace_
|
||||
|
||||
The model can use [`plugin_manager`](../../boot/plugin-manager/README.md) in native and PTC modes. Every action, including listing, requires full access or approval for that call. The default workspace-write/ask policy needs a client approval responder: enable `approvalRequests` and handle `approval/request` through `harness.client.onRequest()`, or supply an approver in the composition. Without one, the operation is refused as unavailable. For Python package installation/removal, explicitly prepare Node and pnpm with `download_primary_runtime()` before starting the runtime; the packaged launcher then supplies them to plugin management without requiring system pnpm. Without a selected payload, package operations need `pnpm` on `PATH` or an explicit `plugin-manager.pnpmCommand`. See the [interactive approval example](../../../python/sdk/examples/manage_plugins.py). Saved profile changes report `restart-required` with `no-hmr`; the current runtime and its sessions retain their tools until the caller closes and starts the runtime again. Set `tool-plugin-manager.disabled: true` through a profile patch to hide this tool. The standalone `sdk-minimal` profile does not include it.
|
||||
|
||||
The Python runtime exposes explicitly downloaded authoring resources and Office sidecars to this profile. `DSH_PRIMARY_RUNTIME` overrides the authoring payload; an empty value disables its query. Office skills, the Office kit, and the [Office tools](../../document/tool-office/README.md) always load: with an authoring payload the skills use its Office assets and bundled Python environment; without one they use the packaged assets and direct the model to the user's existing Python. The tool reports an unavailable LibreOffice Kit until an Office sidecar is downloaded or selected. Profile patches can disable `skill-office` or replace its `assetRoot` independently of `workspace-dependencies`; filesystem skills with the same name take precedence. See the [runtime package](../../../python/sdk-runtime/README.md) for downloads and configuration.
|
||||
The Python runtime exposes explicitly downloaded authoring resources and Office sidecars to this profile. `DSH_PRIMARY_RUNTIME` overrides the authoring payload; an empty value disables its query. Office skills, the Office kit, and the [Office tools](../../document/tool-office/README.md) always load. The skills use their packaged assets; with an authoring payload they direct the model to its bundled Python environment, and without one to the user's existing Python. The tool reports an unavailable LibreOffice Kit until an Office sidecar is downloaded or selected. Profile patches can disable `skill-office` or replace its `assetRoot` independently of `workspace-dependencies`; filesystem skills with the same name take precedence. See the [runtime package](../../../python/sdk-runtime/README.md) for downloads and configuration.
|
||||
|
||||
-----
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ SDK 使用 base 默认提供的 `read`、`write` 和 `edit`。要添加 `str_rep
|
||||
|
||||
模型可以在 native 和 PTC 模式下使用 [`plugin_manager`](../../boot/plugin-manager/README.zh.md)。每个操作(包括列举)都要求完整权限或本次调用的批准。默认 workspace-write/ask 策略需要客户端审批应答方:启用 `approvalRequests`,并通过 `harness.client.onRequest()` 处理 `approval/request`,或在组合中提供审批器。没有应答方时,操作因 unavailable 被拒绝。Python 安装/移除包时,先显式调用 `download_primary_runtime()` 准备 Node 和 pnpm,再启动运行时;打包启动器会将它们提供给插件管理,无需系统 pnpm。未选择载荷时,包操作仍要求 `PATH` 中有 `pnpm`,或显式配置 `plugin-manager.pnpmCommand`。可运行的接入方式见[交互审批示例](../../../python/sdk/examples/manage_plugins.py)。保存的 profile 变更返回 `restart-required` 和 `no-hmr`;当前运行时及其会话保留原有工具,直到调用方关闭并重新启动运行时。通过 profile patch 设置 `tool-plugin-manager.disabled: true` 可隐藏此工具。独立的 `sdk-minimal` profile 不包含它。
|
||||
|
||||
Python 运行时向本 profile 提供显式下载的创作资源和 Office sidecar。`DSH_PRIMARY_RUNTIME` 覆盖创作载荷;空值禁用其查询。Office skills、Office 套件和 [Office 工具](../../document/tool-office/README.zh.md)总是加载:有创作载荷时 skills 使用其 Office assets 和内置 Python 环境;没有时使用包内 assets,并指引模型使用用户已有的 Python。下载或选择 Office sidecar 之前,工具报告 LibreOffice Kit 不可用。Profile patch 可独立于 `workspace-dependencies` 禁用 `skill-office` 或替换其 `assetRoot`;同名文件系统 skills 优先。下载与配置见[运行时包](../../../python/sdk-runtime/README.zh.md)。
|
||||
Python 运行时向本 profile 提供显式下载的创作资源和 Office sidecar。`DSH_PRIMARY_RUNTIME` 覆盖创作载荷;空值禁用其查询。Office skills、Office 套件和 [Office 工具](../../document/tool-office/README.zh.md)总是加载。skills 使用包内 assets;有创作载荷时指引模型使用其内置 Python 环境,没有时指引模型使用用户已有的 Python。下载或选择 Office sidecar 之前,工具报告 LibreOffice Kit 不可用。Profile patch 可独立于 `workspace-dependencies` 禁用 `skill-office` 或替换其 `assetRoot`;同名文件系统 skills 优先。下载与配置见[运行时包](../../../python/sdk-runtime/README.zh.md)。
|
||||
|
||||
-----
|
||||
|
||||
|
||||
@@ -28,11 +28,10 @@
|
||||
|
||||
# DSH_PRIMARY_RUNTIME names the primary runtime; the packaged launcher fills it
|
||||
# from its download cache when unset, and an empty value opts out. Each row can
|
||||
# be patched independently. Office skills and the Office tools always load. With
|
||||
# a primary runtime the skills use its Office assets and Python environment;
|
||||
# without one the expressions evaluate to `undefined`, which the skill-office
|
||||
# schema treats as unset, so the packaged assets and the user's Python apply.
|
||||
# A nonempty DSH_BUNDLED_OFFICE_SKILLS overrides the skill directory.
|
||||
# be patched independently. Office skills and the Office tools always load, and
|
||||
# the skills use their packaged assets. With a primary runtime the skills keep
|
||||
# the default pythonEnvironment and use its Python environment; without one they
|
||||
# direct the model to the user's Python.
|
||||
- insert:
|
||||
- id: office-kit
|
||||
name: '@deepseek-ai/dsh-office-kit'
|
||||
@@ -52,7 +51,6 @@
|
||||
- id: skill-office
|
||||
name: '@deepseek-ai/dsh-skill-office'
|
||||
config:
|
||||
assetRoot: !!js "process.env.DSH_BUNDLED_OFFICE_SKILLS || (process.env.DSH_PRIMARY_RUNTIME ? process.getBuiltinModule('node:path').resolve(process.env.DSH_PRIMARY_RUNTIME, '..', 'office-skills') : undefined)"
|
||||
pythonEnvironment: !!js "process.env.DSH_PRIMARY_RUNTIME ? undefined : 'user'"
|
||||
|
||||
# Share native desktop tools and model context without starting a Web server.
|
||||
|
||||
@@ -33,8 +33,8 @@
|
||||
en: fdf38cb6e1b24464
|
||||
zh: c14a8bcac9d4e5cd
|
||||
/deepseek-ai-dsh-web-app/understand-the-implementation:
|
||||
en: 495b52ed02770027
|
||||
zh: 4d8c676220125691
|
||||
en: 0e09b74fe49aa698
|
||||
zh: e10899a708e344c8
|
||||
/deepseek-ai-dsh-web-app/understand-the-implementation/patch-semantics:
|
||||
en: a6d126f30b1594d7
|
||||
zh: 7f0ccc8a55d6f743
|
||||
|
||||
@@ -102,7 +102,7 @@ Each browser session selects a shipped preset (`standard` by default). The Agent
|
||||
|
||||
The bundle is one patch layer of five files plus one runtime glue plugin: `cordis.patch.yml` carries the host rows and the preset registry, and each `presets/<id>.patch.yml` inserts one shipped preset declaration, applied in the order `dsh.bundle.patch` lists them. 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, the advertised application URL, the plain-HTTP exposure warning, the bash variable, and the readiness announcements. The `office-kit` row mounts one lazy [Office conversion provider](../../document/office-kit/README.md) for Host consumers, including Desktop compositions using this bundle, and keeps converted files under `$DSH_HOME/office-kit/outputs`. The `standard` and `ptc` presets mount the [Office tools](../../document/tool-office/README.md) in every profile, whether or not Kit is installed, and declare them after an Office skill loads; the `minimal` preset omits them. The conversion service's Remote methods authorize preview reads, while Document Preview owns the Office viewer and Client cache.
|
||||
|
||||
The `skill-office` row mounts the [Office skills](../../skill/skill-office/README.md) in every profile. Outside Desktop it selects `pythonEnvironment: 'user'` and reads `DSH_BUNDLED_OFFICE_SKILLS`, which only the [Python SDK launcher](../../../python/sdk-runtime/README.md) sets; an unset variable keeps the packaged assets. Desktop also mounts the bundled dependency-query row, reads the skills from the carrier's primary runtime, and keeps the default `pythonEnvironment: 'workspace-dependencies'`; other profiles leave the dependency-query row disabled. The [Desktop application](../../../apps/desktop/README.md#bundled-workspace-dependencies) owns resource packaging.
|
||||
The `skill-office` row mounts the [Office skills](../../skill/skill-office/README.md) from their packaged assets in every profile. Outside Desktop it selects `pythonEnvironment: 'user'`. Desktop also mounts the bundled dependency-query row and keeps the default `pythonEnvironment: 'workspace-dependencies'`; other profiles leave the dependency-query row disabled. The [Desktop application](../../../apps/desktop/README.md#bundled-workspace-dependencies) owns resource packaging.
|
||||
|
||||
### Patch semantics
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ dsh --profile web --no-open --host "$(hostname -i | awk '{print $1}')" --public-
|
||||
|
||||
此 bundle 由一层五个文件的补丁和一个运行时胶水插件组成:`cordis.patch.yml` 承载宿主行和 preset 注册表,每个 `presets/<id>.patch.yml` 插入一条随发行版交付的 preset 声明,按 `dsh.bundle.patch` 列出的顺序应用。存储栈与投影缓存来自 `dsh-base`;Web 叠加层的工作区和消息反馈条目消费共享的 `storageDomain` 服务。补丁重述 base 有意省略的界面专用值,插入 Web 专用宿主条目和浏览器插件列表,再将 Agent 层移到预设后面。胶水插件负责 dist 服务、公告应用 URL、明文 HTTP 暴露警告、bash 变量和就绪通知。`office-kit` 条目为宿主消费者挂载一个延迟创建引擎的 [Office 转换提供方](../../document/office-kit/README.zh.md),使用此 bundle 的 Desktop 组合也共享该提供方,转换后的文件保存在 `$DSH_HOME/office-kit/outputs`。`standard` 和 `ptc` 预设在所有 profile 中挂载 [Office 工具](../../document/tool-office/README.zh.md),无论是否安装 Kit,并在加载 Office 技能后声明它们;`minimal` 预设不挂载它们。转换服务的 Remote 方法负责预览读取授权,Document Preview 负责 Office 查看器和客户端缓存。
|
||||
|
||||
`skill-office` 行在所有 profile 中挂载 [Office 技能](../../skill/skill-office/README.zh.md)。Desktop 之外选择 `pythonEnvironment: 'user'`,并读取 `DSH_BUNDLED_OFFICE_SKILLS`;只有 [Python SDK 启动器](../../../python/sdk-runtime/README.zh.md)会设置该变量,未设置时保留包内 assets。Desktop 还挂载内置依赖查询行,从载体的主运行时读取技能,并保留默认的 `pythonEnvironment: 'workspace-dependencies'`;其他 profile 禁用依赖查询行。资源打包由 [Desktop 应用](../../../apps/desktop/README.zh.md#bundled-workspace-dependencies)负责。
|
||||
`skill-office` 行在所有 profile 中从包内 assets 挂载 [Office 技能](../../skill/skill-office/README.zh.md)。Desktop 之外选择 `pythonEnvironment: 'user'`。Desktop 还挂载内置依赖查询行,并保留默认的 `pythonEnvironment: 'workspace-dependencies'`;其他 profile 禁用依赖查询行。资源打包由 [Desktop 应用](../../../apps/desktop/README.zh.md#bundled-workspace-dependencies)负责。
|
||||
|
||||
### patch 语义
|
||||
|
||||
|
||||
@@ -293,12 +293,11 @@
|
||||
config:
|
||||
outputDirectory: !!js dshHomePath('office-kit', 'outputs')
|
||||
|
||||
# Desktop supplies its Office skills and Python environment through the primary
|
||||
# runtime named by DSH_PRIMARY_RUNTIME. A Desktop profile launched without a
|
||||
# nonempty value disables workspace-dependencies and, like the other profiles,
|
||||
# uses the packaged skills and directs the model to the user's Python. Outside
|
||||
# Desktop, a nonempty DSH_BUNDLED_OFFICE_SKILLS selects the skill directory;
|
||||
# otherwise the packaged skills apply.
|
||||
# Every profile uses the packaged Office skills. Desktop supplies their Python
|
||||
# environment through the primary runtime named by DSH_PRIMARY_RUNTIME. A
|
||||
# Desktop profile launched without a nonempty value disables
|
||||
# workspace-dependencies and, like the other profiles, directs the model to
|
||||
# the user's Python.
|
||||
- id: workspace-dependencies
|
||||
name: '@deepseek-ai/dsh-tool-workspace-dependencies'
|
||||
disabled: !!js "ctx.get('profileContext')?.name !== 'desktop' || !process.env.DSH_PRIMARY_RUNTIME"
|
||||
@@ -309,7 +308,6 @@
|
||||
- id: skill-office
|
||||
name: '@deepseek-ai/dsh-skill-office'
|
||||
config:
|
||||
assetRoot: !!js "(ctx.get('profileContext')?.name === 'desktop' ? process.env.DSH_PRIMARY_RUNTIME && process.getBuiltinModule('node:path').join(process.env.DSH_PRIMARY_RUNTIME, '..', 'office-skills') : process.env.DSH_BUNDLED_OFFICE_SKILLS) || undefined"
|
||||
pythonEnvironment: !!js "ctx.get('profileContext')?.name === 'desktop' && process.env.DSH_PRIMARY_RUNTIME ? undefined : 'user'"
|
||||
|
||||
# The right Sidebar's document tab: bounded file reads with selectable
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/** Web application Office rows over a real Loader tree: skills, profile gating, Python, preset Office tools, and failure containment. */
|
||||
|
||||
import { cp, mkdir, mkdtemp, realpath, rm, writeFile } from 'node:fs/promises'
|
||||
import { mkdir, mkdtemp, realpath, rm, writeFile } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { dirname, join, sep } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
@@ -56,16 +56,11 @@ it('mounts the Office tools in the standard and PTC presets and omits it from th
|
||||
expect(host).not.toContain('tool-office')
|
||||
})
|
||||
|
||||
async function boot(
|
||||
profile: string, primaryRuntime: (root: string) => string | undefined, skills: (root: string) => string | undefined = () => undefined,
|
||||
mounted: readonly string[] = ROWS,
|
||||
) {
|
||||
async function boot(profile: string, primaryRuntime: (root: string) => string | undefined, mounted: readonly string[] = ROWS) {
|
||||
const root = await realpath(await mkdtemp(join(tmpdir(), 'dsh-desktop-office-rows-')))
|
||||
onTestFinished(() => rm(root, { recursive: true, force: true }))
|
||||
vi.stubEnv('DSH_HOME', join(root, 'home'))
|
||||
vi.stubEnv('DSH_PRIMARY_RUNTIME', primaryRuntime(root))
|
||||
vi.stubEnv('DSH_BUNDLED_OFFICE_SKILLS', skills(root))
|
||||
await cp(assets, join(root, 'office-skills'), { recursive: true })
|
||||
const nodePath = join(root, 'primary-runtime', 'dependencies', 'node', 'bin', process.platform === 'win32' ? 'node.exe' : 'node')
|
||||
await mkdir(dirname(nodePath), { recursive: true })
|
||||
await writeFile(nodePath, '')
|
||||
@@ -120,16 +115,15 @@ async function convertReport(ctx: Context, workspace: string, callId: string) {
|
||||
} finally { toolSet.dispose() }
|
||||
}
|
||||
|
||||
it('mounts Desktop skills from the primary runtime beside the Office tools', async () => {
|
||||
it('mounts packaged Desktop skills with the primary runtime Python beside the Office tools', async () => {
|
||||
const { ctx, root } = await boot('desktop', root => join(root, 'primary-runtime'))
|
||||
// Desktop omits pythonEnvironment; the skill-office default selects the bundled environment.
|
||||
expect(ctx.loader.resolve('skill-office').fiber?.effectiveConfig).toEqual({
|
||||
assetRoot: join(root, 'office-skills'), pythonEnvironment: 'workspace-dependencies',
|
||||
})
|
||||
expect(ctx.loader.resolve('skill-office').fiber?.effectiveConfig).toStrictEqual({ pythonEnvironment: 'workspace-dependencies' })
|
||||
expect(ctx.loader.resolve('workspace-dependencies').fiber?.effectiveConfig).toEqual({
|
||||
source: join(root, 'primary-runtime'), root: join(root, 'home', 'dsh-runtimes', 'dsh-primary-runtime'),
|
||||
})
|
||||
expect((await ctx.skills.list()).map(skill => skill.name)).toEqual(names)
|
||||
expect((await ctx.skills.get('office-docx'))?.resourceBase).toEqual({ kind: 'directory', path: join(assets, 'office-docx').split(sep).join('/') })
|
||||
expect(ctx.tools.schemas().map(tool => tool.name).sort()).toEqual(['load_workspace_dependencies', ...OFFICE_TOOLS])
|
||||
expect(ctx.officeKit.outputDirectory).toBe(join(root, 'home', 'office-kit', 'outputs'))
|
||||
expect((await ctx.skills.get('office-docx'))?.content).toMatch(/## Python environment\n\n.*`load_workspace_dependencies`/u)
|
||||
@@ -151,7 +145,7 @@ it('keeps the Office tools and returns the unavailable Kit error to the model wh
|
||||
})
|
||||
|
||||
it('mounts the preset Office tools without the Office kit row and reaches the service after the row loads', async () => {
|
||||
const { ctx, workspace, entries } = await boot('web', () => undefined, () => undefined, ROWS.filter(id => id !== 'office-kit'))
|
||||
const { ctx, workspace, entries } = await boot('web', () => undefined, ROWS.filter(id => id !== 'office-kit'))
|
||||
vi.mocked(resolveKitModule).mockClear()
|
||||
await writeFile(join(workspace, 'report.docx'), 'docx bytes')
|
||||
await ctx.loader.resolve('tool-office').fiber!.await()
|
||||
@@ -169,8 +163,7 @@ it('mounts the preset Office tools without the Office kit row and reaches the se
|
||||
it('mounts Office skills with packaged defaults and the user Python outside the Desktop profile without launcher variables', async () => {
|
||||
const { ctx } = await boot('web', () => undefined)
|
||||
expect(ctx.loader.resolve('skill-office').disabled).toBe(false)
|
||||
// The path expression evaluates to `undefined`; the schema leaves it unset so skill-office applies its packaged default.
|
||||
expect(ctx.loader.resolve('skill-office').fiber?.effectiveConfig).toStrictEqual({ assetRoot: undefined, pythonEnvironment: 'user' })
|
||||
expect(ctx.loader.resolve('skill-office').fiber?.effectiveConfig).toStrictEqual({ pythonEnvironment: 'user' })
|
||||
expect(ctx.loader.resolve('workspace-dependencies').disabled).toBe(true)
|
||||
expect((await ctx.skills.list()).map(skill => skill.name)).toEqual(names)
|
||||
expect((await ctx.skills.get('office-docx'))?.resourceBase).toEqual({ kind: 'directory', path: join(assets, 'office-docx').split(sep).join('/') })
|
||||
@@ -186,7 +179,7 @@ it('mounts packaged Office skills with the user Python in a Desktop profile laun
|
||||
await ctx.loader.resolve('skill-office').fiber!.await()
|
||||
expect(ctx.loader.resolve('workspace-dependencies').disabled).toBe(true)
|
||||
expect(ctx.loader.resolve('workspace-dependencies').fiber).toBeUndefined()
|
||||
expect(ctx.loader.resolve('skill-office').fiber?.effectiveConfig).toStrictEqual({ assetRoot: undefined, pythonEnvironment: 'user' })
|
||||
expect(ctx.loader.resolve('skill-office').fiber?.effectiveConfig).toStrictEqual({ pythonEnvironment: 'user' })
|
||||
expect((await ctx.skills.list()).map(skill => skill.name)).toEqual(names)
|
||||
expect((await ctx.skills.get('office-docx'))?.resourceBase).toEqual({ kind: 'directory', path: join(assets, 'office-docx').split(sep).join('/') })
|
||||
const docx = (await ctx.skills.get('office-docx'))?.content
|
||||
@@ -195,31 +188,19 @@ it('mounts packaged Office skills with the user Python in a Desktop profile laun
|
||||
expect(ctx.tools.schemas().map(tool => tool.name).sort()).toEqual(OFFICE_TOOLS)
|
||||
})
|
||||
|
||||
it('reads Office skills from the launcher skill directory outside the Desktop profile', async () => {
|
||||
const { ctx, root } = await boot('web', () => undefined, root => join(root, 'office-skills'))
|
||||
expect(ctx.loader.resolve('skill-office').fiber?.effectiveConfig).toStrictEqual({ assetRoot: join(root, 'office-skills'), pythonEnvironment: 'user' })
|
||||
expect((await ctx.skills.list()).map(skill => skill.name)).toEqual(names)
|
||||
expect((await ctx.skills.get('office-docx'))?.resourceBase)
|
||||
.toEqual({ kind: 'directory', path: join(root, 'office-skills', 'office-docx').split(sep).join('/') })
|
||||
})
|
||||
|
||||
it.each([
|
||||
['desktop', (): string => '', (): undefined => undefined],
|
||||
['web', (): undefined => undefined, (): string => ''],
|
||||
] as const)('treats empty launcher variables as unset in the %s profile', async (profile, primaryRuntime, skills) => {
|
||||
const { ctx } = await boot(profile, primaryRuntime, skills)
|
||||
it('treats an empty primary runtime variable as unset in the Desktop profile', async () => {
|
||||
const { ctx } = await boot('desktop', () => '')
|
||||
await ctx.loader.resolve('skill-office').fiber!.await()
|
||||
expect(ctx.loader.resolve('workspace-dependencies').disabled).toBe(true)
|
||||
expect(ctx.loader.resolve('skill-office').fiber?.effectiveConfig).toStrictEqual({ assetRoot: undefined, pythonEnvironment: 'user' })
|
||||
expect(ctx.loader.resolve('skill-office').fiber?.effectiveConfig).toStrictEqual({ pythonEnvironment: 'user' })
|
||||
expect((await ctx.skills.list()).map(skill => skill.name)).toEqual(names)
|
||||
})
|
||||
|
||||
it('fails only the Office skill rows when the carrier supplies an unusable primary runtime path', async () => {
|
||||
it('fails only the dependency-query row when the carrier supplies an unusable primary runtime path', async () => {
|
||||
const { ctx } = await boot('desktop', () => 'primary-runtime')
|
||||
for (const id of ['skill-office', 'workspace-dependencies']) {
|
||||
await expect(ctx.loader.resolve(id).fiber!.await()).rejects.toThrow('absolute')
|
||||
}
|
||||
expect(await ctx.skills.list()).toEqual([])
|
||||
await expect(ctx.loader.resolve('workspace-dependencies').fiber!.await()).rejects.toThrow('absolute')
|
||||
await ctx.loader.resolve('skill-office').fiber!.await()
|
||||
expect((await ctx.skills.list()).map(skill => skill.name)).toEqual(names)
|
||||
expect(ctx.tools.schemas().map(tool => tool.name).sort()).toEqual(OFFICE_TOOLS)
|
||||
})
|
||||
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
en: c361195a73cf5755
|
||||
zh: 745b04e7be0be369
|
||||
/deepseek-ai-dsh-skill-office/use-this-package:
|
||||
en: 5b94a88d614b102e
|
||||
zh: dd27cb2f87ea26d8
|
||||
en: 2195e74b3648bbdc
|
||||
zh: dd73de9705e5774f
|
||||
/deepseek-ai-dsh-skill-office/use-this-package/progressive-loading:
|
||||
en: 34dba830aba25713
|
||||
zh: 0d670be50fd30115
|
||||
|
||||
@@ -35,7 +35,7 @@ Mount the skill provider beside the skill registry and `dsh-tool-skill`. The pro
|
||||
|
||||
| Field | Default | Meaning |
|
||||
|---|---|---|
|
||||
| `assetRoot` | Carrier resources or packaged `assets/` | Absolute directory containing the four skill folders. |
|
||||
| `assetRoot` | Packaged `assets/` | Absolute directory containing the four skill folders. |
|
||||
| `pythonEnvironment` | `'workspace-dependencies'` | Python that `office-docx`, `office-pptx`, and `office-xlsx` direct the model to; see below. |
|
||||
|
||||
`pythonEnvironment` selects one fixed section appended to those three skills. Both values first defer to an explicit user or applicable AGENTS.md environment requirement. `'workspace-dependencies'` directs the model to call `load_workspace_dependencies` and use the bundled Python and libraries it returns; mount it only with `dsh-tool-workspace-dependencies`. A custom composition that mounts this package without `dsh-tool-workspace-dependencies` must configure `pythonEnvironment: user`. `'user'` directs the model to the project's or user's existing Python on PATH, to check that the required libraries import, and to name missing packages instead of installing them globally without the user's agreement.
|
||||
|
||||
@@ -35,7 +35,7 @@ kind: "package-reference"
|
||||
|
||||
| 字段 | 默认值 | 含义 |
|
||||
|---|---|---|
|
||||
| `assetRoot` | 载体资源或包内 `assets/` | 包含四个技能目录的绝对路径。 |
|
||||
| `assetRoot` | 包内 `assets/` | 包含四个技能目录的绝对路径。 |
|
||||
| `pythonEnvironment` | `'workspace-dependencies'` | `office-docx`、`office-pptx` 和 `office-xlsx` 指引模型使用的 Python;见下文。 |
|
||||
|
||||
`pythonEnvironment` 选择追加到这三个技能末尾的一段固定说明。两个取值都先遵守用户或适用 AGENTS.md 明确要求的环境。`'workspace-dependencies'` 指引模型调用 `load_workspace_dependencies`,使用其返回的内置 Python 与库;仅在同时挂载 `dsh-tool-workspace-dependencies` 时使用。只挂载本包而不挂载 `dsh-tool-workspace-dependencies` 的自定义组合必须配置 `pythonEnvironment: user`。`'user'` 指引模型使用项目或用户已有的 PATH 上的 Python,先检查所需库能否 import,缺少时说明需要安装的包,未经用户同意不向全局环境安装。
|
||||
|
||||
@@ -69,7 +69,7 @@ function persistentReadFailure(error: unknown): boolean {
|
||||
}
|
||||
|
||||
/**
|
||||
* Register Office skills with resources readable by the script interpreter.
|
||||
* Register Office skills whose `SKILL.md` files the Host reads from `assetRoot`.
|
||||
* Skill files are read during catalog discovery and loading, and each distinct failure is reported once. A missing
|
||||
* or invalid skill is omitted from a complete catalog, and its failed load returns `undefined` and invalidates the
|
||||
* catalog. Any other read error leaves discovery incomplete, so the registry retries it, and rejects a load.
|
||||
|
||||
@@ -24,8 +24,8 @@
|
||||
en: 0575105cbe07c108
|
||||
zh: 125d54d45a72bf43
|
||||
/deepseek-ai-dsh-tool-workspace-dependencies/use-this-package/build-a-carrier-payload:
|
||||
en: 0f3e570525053f09
|
||||
zh: 1b0cd3b653e1bd5d
|
||||
en: 40e14ab104153a9d
|
||||
zh: 34da896f77184227
|
||||
/deepseek-ai-dsh-tool-workspace-dependencies/understand-the-implementation:
|
||||
en: 0121e57a754cb26f
|
||||
zh: 2933fc0415da013d
|
||||
|
||||
@@ -50,9 +50,9 @@ The packaged `sdk` profile uses its bundled Python and Office skills by default;
|
||||
|
||||
### Build a carrier payload
|
||||
|
||||
From a repository checkout with dependencies installed, `CI=true pnpm run prepare:primary-runtime --target linux-x64 --output /tmp/dsh-office` writes `primary-runtime/` and `office-skills/`. The shared [download lock](../../../scripts/primary-runtime/lock.json) also covers `linux-arm64`, `mac-arm64`, `mac-x64`, and `win-x64`. `--python-only` omits Node.js and pnpm; `--cache` selects the hash-verified archive cache. The entry executes interpreter and Office read/write checks only for a native target. Cross-target builds require those checks on the target host before deployment.
|
||||
From a repository checkout with dependencies installed, `CI=true pnpm run prepare:primary-runtime --target linux-x64 --output /tmp/dsh-office` writes `primary-runtime/`. The shared [download lock](../../../scripts/primary-runtime/lock.json) also covers `linux-arm64`, `mac-arm64`, `mac-x64`, and `win-x64`. `--python-only` omits Node.js and pnpm; `--cache` selects the hash-verified archive cache. The entry executes interpreter and Office read/write checks only for a native target. Cross-target builds require those checks on the target host before deployment.
|
||||
|
||||
A container can copy both directories into an immutable image layer and set `DSH_PRIMARY_RUNTIME` to the absolute `primary-runtime/` path. The SDK queries that payload in place. Desktop uses the same builder and retains its Harness-home installation and signing checks.
|
||||
A container can copy that directory into an immutable image layer and set `DSH_PRIMARY_RUNTIME` to the absolute `primary-runtime/` path. The SDK queries that payload in place. Desktop uses the same builder and retains its Harness-home installation and signing checks.
|
||||
|
||||
-----
|
||||
|
||||
|
||||
@@ -50,9 +50,9 @@ kind: "package-reference"
|
||||
|
||||
### 构建载体 payload
|
||||
|
||||
在已安装依赖的仓库 checkout 中运行 `CI=true pnpm run prepare:primary-runtime --target linux-x64 --output /tmp/dsh-office`,会生成 `primary-runtime/` 和 `office-skills/`。[共享下载锁](../../../scripts/primary-runtime/lock.json)还覆盖 `linux-arm64`、`mac-arm64`、`mac-x64` 和 `win-x64`。`--python-only` 省略 Node.js 和 pnpm;`--cache` 选择经过哈希校验的归档缓存。入口仅对本机目标执行解释器与 Office 读写检查。跨目标构建必须在部署前到目标主机执行这些检查。
|
||||
在已安装依赖的仓库 checkout 中运行 `CI=true pnpm run prepare:primary-runtime --target linux-x64 --output /tmp/dsh-office`,会生成 `primary-runtime/`。[共享下载锁](../../../scripts/primary-runtime/lock.json)还覆盖 `linux-arm64`、`mac-arm64`、`mac-x64` 和 `win-x64`。`--python-only` 省略 Node.js 和 pnpm;`--cache` 选择经过哈希校验的归档缓存。入口仅对本机目标执行解释器与 Office 读写检查。跨目标构建必须在部署前到目标主机执行这些检查。
|
||||
|
||||
容器可将这两个目录复制到不可变镜像层,并将 `DSH_PRIMARY_RUNTIME` 设为 `primary-runtime/` 的绝对路径。SDK 原位查询该 payload。Desktop 使用同一构建器,并保留 Harness-home 安装与签名检查。
|
||||
容器可将该目录复制到不可变镜像层,并将 `DSH_PRIMARY_RUNTIME` 设为 `primary-runtime/` 的绝对路径。SDK 原位查询该 payload。Desktop 使用同一构建器,并保留 Harness-home 安装与签名检查。
|
||||
|
||||
-----
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
en: 1ea1d8e6d2c14b02
|
||||
zh: 8e0b9903158667a1
|
||||
/deepseek-harness-runtime-bin/installed-commands-and-artifacts:
|
||||
en: 2d4c437113482e00
|
||||
zh: c40aac1cbb55951a
|
||||
en: b1314b95771612fa
|
||||
zh: f06b96e1feba3757
|
||||
/deepseek-harness-runtime-bin/python-module-api:
|
||||
en: 78a200e328d96637
|
||||
zh: f3b7a0b69b7a39e0
|
||||
|
||||
@@ -10,7 +10,7 @@ The wheel installs a `dsh` console command and the `deepseek_harness_runtime` Py
|
||||
|
||||
Production executables are named `deepseek-harness-sdk-runtime-<platform>-<arch>` under the module's `runtime/` directory; Windows uses the `.exe` suffix. Linux and macOS wheels include a target-native `-rg` sidecar, Windows includes `-rg.exe`, and macOS also includes `-spawn-helper` for `node-pty`. Published targets are Linux x64, Linux arm64, macOS arm64, macOS x64, and Windows x64. The wheel tag and payload must match exactly; no Windows arm64 wheel is published.
|
||||
|
||||
The wheel carries only the executable, native execution helpers, and `<platform>-<arch>/` with `downloads.json` and the lightweight `office-skills/` tree. CPython, authoring libraries, standalone Node, pnpm, and the complete Office sidecar are optional downloads. Importing Python modules, starting SDK profiles, and processing Office files never download resources.
|
||||
The wheel carries only the executable, native execution helpers, and `<platform>-<arch>/` with `downloads.json`. Office skills ship inside the executable. CPython, authoring libraries, standalone Node, pnpm, and the complete Office sidecar are optional downloads. Importing Python modules, starting SDK profiles, and processing Office files never download resources.
|
||||
|
||||
Download either resource explicitly before using it:
|
||||
|
||||
@@ -21,11 +21,11 @@ office = download_office()
|
||||
primary_runtime = download_primary_runtime()
|
||||
```
|
||||
|
||||
`download_office()` downloads the locked npm Kit, target engine, and complete dependency tree, plus standalone Node for the Python `deepseek_harness.office` module. `download_primary_runtime()` independently downloads CPython, locked Python libraries, Node, pnpm, and Office skills. Both return absolute resource directories and reuse completed caches. The authoring download validates its target, interpreters, library directory and skills before returning. The existing local `installPrimaryRuntime()` operation still copies an already available payload; it does not download. The shared builder applies the [NumPy/pandas test-file exclusions](../../apps/desktop/README.md#bundled-workspace-dependencies).
|
||||
`download_office()` downloads the locked npm Kit, target engine, and complete dependency tree, plus standalone Node for the Python `deepseek_harness.office` module. `download_primary_runtime()` independently downloads CPython, locked Python libraries, Node, and pnpm. Both return absolute resource directories and reuse completed caches. The authoring download validates its target, interpreters, and library directory before returning. The existing local `installPrimaryRuntime()` operation still copies an already available payload; it does not download. The shared builder applies the [NumPy/pandas test-file exclusions](../../apps/desktop/README.md#bundled-workspace-dependencies).
|
||||
|
||||
`DSH_RESOURCE_CACHE` selects an absolute cache root, defaulting to `~/.cache/deepseek-harness/resources`. Resource identities include the release's locked inputs. Downloads verify archive hashes and package identities, preserve executable permissions, and publish completed directories atomically. Failed downloads can be retried. `DSH_RESOURCE_DOWNLOAD_TIMEOUT_MS` bounds each archive download, including its body; the default is `300000`, and accepted values are integers from `1` to `2147483647`. Downloads follow the Harness proxy environment policy. Installed wheel files are never modified. Release versions have separate cache directories; unused versions require manual removal. Downloads use the locked upstream URLs without a registry or mirror override.
|
||||
|
||||
On subsequent launches the packaged bootstrap exposes downloaded resources to the SDK profile. `DSH_PRIMARY_RUNTIME` overrides the cached authoring environment; an empty value disables its query and package-manager selection. `DSH_OFFICE_SIDECAR` can select an absolute sidecar directory with its standalone Node. Users may provide their own Python environment and document libraries without downloading the authoring environment. Office skills are always available: with the authoring environment they direct the model to its bundled Python and libraries, and without it to the user's existing Python. Without a selected Office sidecar the Office tools report LibreOffice Kit as unavailable. External authoring payloads retain the `primary-runtime/` plus sibling `office-skills/` layout. The SDK reads resources in place without copying them into `DSH_HOME`. The `sdk-minimal` profile does not mount these providers.
|
||||
On subsequent launches the packaged bootstrap exposes downloaded resources to the SDK profile. `DSH_PRIMARY_RUNTIME` overrides the cached authoring environment; an empty value disables its query and package-manager selection. `DSH_OFFICE_SIDECAR` can select an absolute sidecar directory with its standalone Node. Users may provide their own Python environment and document libraries without downloading the authoring environment. Office skills are always available from the executable's packaged assets: with the authoring environment they direct the model to its bundled Python and libraries, and without it to the user's existing Python. Without a selected Office sidecar the Office tools report LibreOffice Kit as unavailable. The SDK reads resources in place without copying them into `DSH_HOME`. The `sdk-minimal` profile does not mount these providers.
|
||||
|
||||
Skill selection is independent of delivery: project, custom-directory, and user filesystem skills override same-name bundled skills. An SDK patch can disable only the Office provider while retaining the Python query:
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ wheel 包会安装 `dsh` 控制台命令和 `deepseek_harness_runtime` Python
|
||||
|
||||
生产可执行程序位于模块的 `runtime/` 目录,命名为 `deepseek-harness-sdk-runtime-<platform>-<arch>`;Windows 使用 `.exe` 后缀。Linux 与 macOS wheel 包含目标平台原生的 `-rg` 伴随程序,Windows 包含 `-rg.exe`,macOS 还包含 `node-pty` 使用的 `-spawn-helper`。已发布目标是 Linux x64、Linux arm64、macOS arm64、macOS x64 与 Windows x64。wheel 包标签必须与载荷严格匹配;不发布 Windows arm64 wheel 包。
|
||||
|
||||
wheel 仅携带可执行程序、原生执行辅助程序,以及包含 `downloads.json` 和轻量 `office-skills/` 目录的 `<platform>-<arch>/`。CPython、创作库、独立 Node、pnpm 和完整 Office sidecar 均为可选下载资源。导入 Python 模块、启动 SDK profile 和处理 Office 文件都不会下载资源。
|
||||
wheel 仅携带可执行程序、原生执行辅助程序,以及包含 `downloads.json` 的 `<platform>-<arch>/`。Office skills 内置于可执行程序中。CPython、创作库、独立 Node、pnpm 和完整 Office sidecar 均为可选下载资源。导入 Python 模块、启动 SDK profile 和处理 Office 文件都不会下载资源。
|
||||
|
||||
使用前显式下载所需的任一种资源:
|
||||
|
||||
@@ -21,11 +21,11 @@ office = download_office()
|
||||
primary_runtime = download_primary_runtime()
|
||||
```
|
||||
|
||||
`download_office()` 下载锁定版本的 npm Kit、目标引擎及完整依赖树,并准备供 Python `deepseek_harness.office` 模块使用的独立 Node。`download_primary_runtime()` 独立下载 CPython、锁定的 Python 库、Node、pnpm 和 Office skills。两者返回绝对资源目录,并复用完整缓存。创作下载会在返回前校验目标平台、解释器、库目录与 skills。现有本地 `installPrimaryRuntime()` 操作仍只复制已有载荷,不执行下载。 共享构建器采用 [NumPy/pandas 测试文件裁剪规则](../../apps/desktop/README.zh.md#bundled-workspace-dependencies)。
|
||||
`download_office()` 下载锁定版本的 npm Kit、目标引擎及完整依赖树,并准备供 Python `deepseek_harness.office` 模块使用的独立 Node。`download_primary_runtime()` 独立下载 CPython、锁定的 Python 库、Node 和 pnpm。两者返回绝对资源目录,并复用完整缓存。创作下载会在返回前校验目标平台、解释器与库目录。现有本地 `installPrimaryRuntime()` 操作仍只复制已有载荷,不执行下载。 共享构建器采用 [NumPy/pandas 测试文件裁剪规则](../../apps/desktop/README.zh.md#bundled-workspace-dependencies)。
|
||||
|
||||
`DSH_RESOURCE_CACHE` 指定绝对缓存根目录,默认为 `~/.cache/deepseek-harness/resources`。资源身份包含发布版本锁定的输入。下载会校验归档哈希和包身份、保留执行权限,并原子发布完整目录。下载失败后可重试。`DSH_RESOURCE_DOWNLOAD_TIMEOUT_MS` 限制每个归档的下载耗时,包含响应内容;默认值为 `300000`,接受 `1` 到 `2147483647` 的整数。下载遵循 Harness 代理环境策略。已安装的 wheel 文件不会被修改。不同发布版本使用独立缓存目录;不再使用的版本需手动删除。下载使用锁定的上游 URL,不提供 registry 或镜像覆盖。
|
||||
|
||||
后续启动时,打包 bootstrap 将已下载资源提供给 SDK profile。`DSH_PRIMARY_RUNTIME` 覆盖缓存中的创作环境;空值禁用其查询和包管理器选择。`DSH_OFFICE_SIDECAR` 可选择包含独立 Node 的绝对 sidecar 目录。用户可自行提供 Python 环境和文档库,无需下载创作环境。Office skills 总是可用:有创作环境时指引模型使用其内置 Python 与库,没有时指引模型使用用户已有的 Python。未选择 Office sidecar 时,Office 工具报告 LibreOffice Kit 不可用。外部创作载荷保留 `primary-runtime/` 及同级 `office-skills/` 布局。SDK 原位读取资源,不复制到 `DSH_HOME`。`sdk-minimal` profile 不挂载这些提供方。
|
||||
后续启动时,打包 bootstrap 将已下载资源提供给 SDK profile。`DSH_PRIMARY_RUNTIME` 覆盖缓存中的创作环境;空值禁用其查询和包管理器选择。`DSH_OFFICE_SIDECAR` 可选择包含独立 Node 的绝对 sidecar 目录。用户可自行提供 Python 环境和文档库,无需下载创作环境。Office skills 总是可用,来自可执行程序内置的包内 assets:有创作环境时指引模型使用其内置 Python 与库,没有时指引模型使用用户已有的 Python。未选择 Office sidecar 时,Office 工具报告 LibreOffice Kit 不可用。SDK 原位读取资源,不复制到 `DSH_HOME`。`sdk-minimal` profile 不挂载这些提供方。
|
||||
|
||||
选择 skills 与交付资源相互独立:项目、自定义目录和用户文件系统 skills 优先于同名随包 skills。SDK patch 可以仅禁用 Office 提供方,同时保留 Python 查询:
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ async function downloadResource(kind) {
|
||||
await downloadOfficeSidecar(downloads.office, staging, archives)
|
||||
} else {
|
||||
await preparePrimaryRuntime({ target: downloads.target, output: staging, cache: archives,
|
||||
version: downloads.version, pnpmArchive: downloads.pnpm, skillSource: join(resourceDirectory, 'office-skills') })
|
||||
version: downloads.version, pnpmArchive: downloads.pnpm })
|
||||
}
|
||||
await writeFile(join(staging, 'complete'), downloads.identity)
|
||||
try { await rename(staging, destination) } catch (error) {
|
||||
@@ -68,7 +68,6 @@ if (office !== undefined) {
|
||||
},
|
||||
})
|
||||
}
|
||||
if (downloads !== undefined) process.env.DSH_BUNDLED_OFFICE_SKILLS = join(resourceDirectory, 'office-skills')
|
||||
|
||||
const selectorName = 'DSH_SUBPROCESS_RUNNER'
|
||||
const selection = process.env[selectorName]
|
||||
|
||||
@@ -176,7 +176,7 @@ def download_office() -> Path:
|
||||
|
||||
|
||||
def download_primary_runtime() -> Path:
|
||||
"""Download locked CPython, libraries, Node, pnpm and skills into the resource cache.
|
||||
"""Download locked CPython, libraries, Node and pnpm into the resource cache.
|
||||
|
||||
Return the absolute primary-runtime directory. This is independent of the
|
||||
Office engine download and does not copy resources into a Harness home.
|
||||
|
||||
@@ -14,7 +14,7 @@ from pathlib import Path
|
||||
|
||||
|
||||
def validate_resources(root: Path | zipfile.Path, target: str) -> None:
|
||||
"""Reject a missing or wrong-target Python/Node environment or bundled Office skill tree."""
|
||||
"""Reject a missing or wrong-target Python/Node environment."""
|
||||
manifest_path = root / "primary-runtime/runtime.json"
|
||||
if not manifest_path.is_file():
|
||||
raise FileNotFoundError(f"runtime authoring resources are missing: {manifest_path}")
|
||||
@@ -32,9 +32,7 @@ def validate_resources(root: Path | zipfile.Path, target: str) -> None:
|
||||
executable = python_root / ("python.exe" if platform == "win32" else "bin/python3")
|
||||
packages = python_root / ("Lib/site-packages" if platform == "win32" else f"lib/python{version.rsplit('.', 1)[0]}/site-packages")
|
||||
node = root / "primary-runtime/dependencies/node/bin" / ("node.exe" if platform == "win32" else "node")
|
||||
required = [executable, node]
|
||||
required.extend(root / f"office-skills/office-{kind}/SKILL.md" for kind in ("docx", "pptx", "xlsx"))
|
||||
for path in required:
|
||||
for path in (executable, node):
|
||||
if not path.is_file():
|
||||
raise FileNotFoundError(f"runtime authoring resource is missing: {path}")
|
||||
if not packages.is_dir():
|
||||
@@ -57,9 +55,6 @@ def validate_downloads(root: Path | zipfile.Path, target: str) -> dict:
|
||||
or not isinstance(value.get("office"), list) or not value["office"]
|
||||
or not isinstance(value.get("pnpm"), dict)):
|
||||
raise ValueError(f"invalid runtime download manifest: {path}")
|
||||
for file in (f"office-{kind}/SKILL.md" for kind in ("docx", "pptx", "xlsx")):
|
||||
if not (root / "office-skills" / file).is_file():
|
||||
raise FileNotFoundError(f"runtime Office skill is missing: {root / 'office-skills' / file}")
|
||||
return value
|
||||
|
||||
|
||||
|
||||
@@ -150,9 +150,9 @@ def test_stage_runtime_copies_platform_payload(
|
||||
office_asset.parent.mkdir(parents=True)
|
||||
office_asset.write_bytes(b"office data")
|
||||
resources = executable.with_name(executable.name.removeprefix("deepseek-harness-sdk-runtime-").removesuffix(".exe"))
|
||||
resource = resources / "office-skills/office-docx/SKILL.md"
|
||||
resource = resources / "downloads.json"
|
||||
resource.parent.mkdir(parents=True)
|
||||
resource.write_text("# Word documents\n")
|
||||
resource.write_text("{}\n")
|
||||
destination = tmp_path / "staging"
|
||||
|
||||
build_python_release.stage_runtime(destination, "1.2.3", executable, executable.name)
|
||||
@@ -164,7 +164,7 @@ def test_stage_runtime_copies_platform_payload(
|
||||
if path.is_file()
|
||||
} == expected
|
||||
assert not (runtime_dir / office.name).exists()
|
||||
assert (runtime_dir / resources.name / resource.relative_to(resources)).read_text() == "# Word documents\n"
|
||||
assert (runtime_dir / resources.name / resource.relative_to(resources)).read_text() == "{}\n"
|
||||
pyproject = (destination / "pyproject.toml").read_text()
|
||||
assert 'license = "MIT"' in pyproject
|
||||
assert 'license-files = ["LICENSE", "THIRD_PARTY_NOTICES.md"]' in pyproject
|
||||
|
||||
@@ -56,10 +56,6 @@ def _resource_sidecars(executable: Path, native_targets: tuple[str, ...] = ("dar
|
||||
node.chmod(0o755)
|
||||
packages = python.parent if tag.startswith("win-") else python.parent.parent
|
||||
(packages / ("Lib/site-packages" if tag.startswith("win-") else "lib/python3.12/site-packages")).mkdir(parents=True)
|
||||
for file in (f"office-{kind}/SKILL.md" for kind in ("docx", "pptx", "xlsx")):
|
||||
path = resources / "office-skills" / file
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.touch()
|
||||
return office
|
||||
|
||||
|
||||
@@ -69,7 +65,7 @@ def test_unknown_explicit_mode_fails_loud() -> None:
|
||||
|
||||
|
||||
@pytest.mark.parametrize("target", ["linux-x64", "linux-arm64", "macos-arm64", "macos-x64", "win-x64"])
|
||||
@pytest.mark.parametrize("invalid", [None, "manifest", "platform", "python", "skills", "packages", "mode", "node", "node-mode"])
|
||||
@pytest.mark.parametrize("invalid", [None, "manifest", "platform", "python", "packages", "mode", "node", "node-mode"])
|
||||
def test_authoring_resources_validate_installed_and_wheel_payloads(tmp_path: Path, target: str, invalid: str | None) -> None:
|
||||
executable = tmp_path / f"deepseek-harness-sdk-runtime-{target}"
|
||||
_resource_sidecars(executable)
|
||||
@@ -91,8 +87,6 @@ def test_authoring_resources_validate_installed_and_wheel_payloads(tmp_path: Pat
|
||||
node.chmod(0o644)
|
||||
elif invalid == "python":
|
||||
python.unlink()
|
||||
elif invalid == "skills":
|
||||
(root / "office-skills/office-docx/SKILL.md").unlink()
|
||||
elif invalid == "packages":
|
||||
next(root.rglob("site-packages")).rmdir()
|
||||
elif invalid == "mode":
|
||||
@@ -372,7 +366,7 @@ def test_download_decodes_utf8_in_an_ascii_locale(tmp_path: Path) -> None:
|
||||
|
||||
|
||||
@pytest.mark.parametrize("missing", ["primary-runtime/runtime.json", "primary-runtime/dependencies/python/bin/python3",
|
||||
"primary-runtime/dependencies/node/bin/node", "office-skills/office-docx/SKILL.md"])
|
||||
"primary-runtime/dependencies/node/bin/node"])
|
||||
def test_primary_download_rejects_an_incomplete_payload(tmp_path: Path, monkeypatch: pytest.MonkeyPatch, missing: str) -> None:
|
||||
_resource_sidecars(tmp_path / "deepseek-harness-sdk-runtime-linux-x64")
|
||||
root = tmp_path / "linux-x64"
|
||||
@@ -411,7 +405,7 @@ def test_office_resolution_requires_explicit_download(tmp_path: Path, monkeypatc
|
||||
assert office_launch_args(tmp_path / "linux-x64", "linux-x64") == (str(node), str(cli))
|
||||
|
||||
|
||||
@pytest.mark.parametrize("invalid", ["target", "identity", "office", "pnpm", "skills"])
|
||||
@pytest.mark.parametrize("invalid", ["target", "identity", "office", "pnpm"])
|
||||
def test_download_manifest_rejects_invalid_wheel_resources(tmp_path: Path, invalid: str) -> None:
|
||||
from deepseek_harness_runtime._resources import validate_downloads
|
||||
|
||||
@@ -419,12 +413,7 @@ def test_download_manifest_rejects_invalid_wheel_resources(tmp_path: Path, inval
|
||||
root = tmp_path / "linux-x64"
|
||||
path = root / "downloads.json"
|
||||
manifest = json.loads(path.read_text(encoding="utf-8"))
|
||||
if invalid == "skills":
|
||||
(root / "office-skills/office-docx/SKILL.md").unlink()
|
||||
with pytest.raises(FileNotFoundError, match="Office skill"):
|
||||
validate_downloads(root, "linux-x64")
|
||||
else:
|
||||
manifest[invalid] = {"target": "win-x64", "identity": "z" * 64, "office": [], "pnpm": []}[invalid]
|
||||
path.write_text(json.dumps(manifest), encoding="utf-8")
|
||||
with pytest.raises(ValueError, match="invalid runtime download manifest"):
|
||||
validate_downloads(root, "linux-x64")
|
||||
manifest[invalid] = {"target": "win-x64", "identity": "z" * 64, "office": [], "pnpm": []}[invalid]
|
||||
path.write_text(json.dumps(manifest), encoding="utf-8")
|
||||
with pytest.raises(ValueError, match="invalid runtime download manifest"):
|
||||
validate_downloads(root, "linux-x64")
|
||||
@@ -26,5 +26,7 @@ describe('Python runtime executable assets', () => {
|
||||
expect(result.stdout).not.toContain('skill-badge/assets/**/*')
|
||||
expect(result.stdout).toContain('node_modules/@deepseek-ai/dsh-sandbox-windows-acl/assets/**/*')
|
||||
expect(result.stdout).not.toContain('node_modules/**/*.py')
|
||||
// skill-office reads its SKILL.md files from the package's assets directory.
|
||||
expect(result.stdout).toContain('"node_modules/**/*.md"')
|
||||
})
|
||||
})
|
||||
@@ -179,7 +179,8 @@ describe('Python runtime executable builder CLI', () => {
|
||||
expect(result.stdout).toContain('exec pkg')
|
||||
expect(result.stdout).toContain('--sea --compress Brotli --targets node24-macos-x64')
|
||||
expect(result.stdout).toContain('keep only node-pty prebuilds darwin-x64')
|
||||
expect(result.stdout).toContain('lock optional resource downloads and copy Office skills')
|
||||
expect(result.stdout).toContain(`lock optional resource downloads in ${join(root, 'dist-exe', 'macos-x64')}`)
|
||||
expect(result.stdout).not.toContain('office-skills')
|
||||
expect(result.stdout).toContain(join(root, 'dist-exe', 'macos-x64'))
|
||||
})
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ import { resolveLinuxNodePtyAddon, resolveWindowsNodePtyAddons } from './executa
|
||||
import { officeSidecarArchives, runtimeNpmArchive, OFFICE_ASSET_IGNORES } from './build-exe-for-python-sdk-office.ts'
|
||||
import { syncPythonRuntimeProduct } from './build-exe-for-python-sdk-resources.ts'
|
||||
import { deduplicateStagedWorkspacePackages, materializeStagedLinks } from './build-exe-for-python-sdk-staging.ts'
|
||||
import { prepareOfficeSkillAssets } from './primary-runtime/prepare.ts'
|
||||
import primaryLock from './primary-runtime/lock.json' with { type: 'json' }
|
||||
|
||||
const root = resolve(import.meta.dirname, '..')
|
||||
@@ -412,7 +411,7 @@ class SingleExeBuild {
|
||||
const ripgrep = await this.copyRipgrepSidecar(target, product)
|
||||
const resources = join(this.outDir, `${target.platform}-${target.arch}`)
|
||||
if (this.cli.dryRun) {
|
||||
console.log(`build-exe-for-python-sdk: [dry-run] lock optional resource downloads and copy Office skills to ${resources}`)
|
||||
console.log(`build-exe-for-python-sdk: [dry-run] lock optional resource downloads in ${resources}`)
|
||||
} else {
|
||||
await rm(resources, { recursive: true, force: true })
|
||||
await mkdir(resources, { recursive: true })
|
||||
@@ -422,7 +421,6 @@ class SingleExeBuild {
|
||||
const inputs = { version, target: `${target.platform === 'macos' ? 'mac' : target.platform}-${target.arch}`, office, pnpm }
|
||||
const identity = createHash('sha256').update(JSON.stringify({ ...inputs, primaryLock })).digest('hex')
|
||||
await writeFile(join(resources, 'downloads.json'), JSON.stringify({ ...inputs, identity }, undefined, 2) + '\n')
|
||||
await prepareOfficeSkillAssets(join(this.staging, 'node_modules/@deepseek-ai/dsh-skill-office/assets'), join(resources, 'office-skills'))
|
||||
}
|
||||
if (target.platform !== 'macos') return [product, ripgrep, resources]
|
||||
const spawnHelper = `${product}-spawn-helper`
|
||||
|
||||
@@ -5,7 +5,7 @@ import { tmpdir } from 'node:os'
|
||||
import { basename, join } from 'node:path'
|
||||
import { zipSync } from 'fflate'
|
||||
import { expect, it } from 'vitest'
|
||||
import { downloadPrimaryRuntimeAsset, prepareOfficeSkillAssets, primaryRuntimePayloadDigest, prunePnpmForeignNativePackages, smokePrimaryRuntime, unpackPrimaryRuntimeWheel } from './prepare.ts'
|
||||
import { downloadPrimaryRuntimeAsset, primaryRuntimePayloadDigest, prunePnpmForeignNativePackages, smokePrimaryRuntime, unpackPrimaryRuntimeWheel } from './prepare.ts'
|
||||
import lock from './lock.json' with { type: 'json' }
|
||||
|
||||
it('covers every SDK wheel target with the shared interpreter lock', () => {
|
||||
@@ -132,26 +132,6 @@ it('rejects library files requiring an unsupported installation scheme', async (
|
||||
}
|
||||
})
|
||||
|
||||
it('copies complete Office resources outside the application archive and removes obsolete assets', async () => {
|
||||
const root = await mkdtemp(join(tmpdir(), 'desktop-office-assets-'))
|
||||
try {
|
||||
const source = join(root, 'package', 'assets')
|
||||
const destination = join(root, 'Contents', 'Resources', 'runtime', 'office-skills')
|
||||
await mkdir(source, { recursive: true })
|
||||
for (const name of ['office-docx', 'office-pptx', 'office-xlsx']) {
|
||||
await mkdir(join(source, name))
|
||||
await writeFile(join(source, name, 'SKILL.md'), `# ${name}\n`)
|
||||
}
|
||||
await prepareOfficeSkillAssets(source, destination)
|
||||
await writeFile(join(destination, 'obsolete.py'), 'old helper')
|
||||
await prepareOfficeSkillAssets(source, destination)
|
||||
for (const name of ['office-docx', 'office-pptx', 'office-xlsx']) {
|
||||
expect(await readFile(join(destination, name, 'SKILL.md'), 'utf8')).toBe(`# ${name}\n`)
|
||||
}
|
||||
await expect(readFile(join(destination, 'obsolete.py'))).rejects.toMatchObject({ code: 'ENOENT' })
|
||||
} finally { await rm(root, { recursive: true, force: true }) }
|
||||
})
|
||||
|
||||
it('gives Python-only payloads a distinct identity', () => {
|
||||
expect(primaryRuntimePayloadDigest('linux-x64', lock, undefined)).not.toBe(primaryRuntimePayloadDigest('linux-x64', lock, '11.7.0'))
|
||||
})
|
||||
|
||||
@@ -172,17 +172,6 @@ export function prunePnpmForeignNativePackages(pnpm: string, platform: NodeJS.Pl
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the skill package's complete asset tree to ordinary filesystem resources.
|
||||
* @param source - The package's assets directory.
|
||||
* @param destination - External Office skill resource directory.
|
||||
* @returns Resolves after replacing the external assets with the complete package tree.
|
||||
*/
|
||||
export async function prepareOfficeSkillAssets(source: string, destination: string): Promise<void> {
|
||||
rmSync(destination, { recursive: true, force: true })
|
||||
await cp(source, destination, { recursive: true, dereference: true })
|
||||
}
|
||||
|
||||
/** A locked interpreter and wheel target. */
|
||||
export type PrimaryRuntimeTarget = keyof typeof lock.targets
|
||||
|
||||
@@ -190,7 +179,7 @@ export type PrimaryRuntimeTarget = keyof typeof lock.targets
|
||||
export interface PreparePrimaryRuntimeOptions {
|
||||
/** Target whose archives and wheels are downloaded. */
|
||||
readonly target: PrimaryRuntimeTarget
|
||||
/** Resource directory receiving primary-runtime/ and office-skills/. */
|
||||
/** Resource directory receiving primary-runtime/. */
|
||||
readonly output: string
|
||||
/** SHA-256-addressed archive cache. */
|
||||
readonly cache: string
|
||||
@@ -198,16 +187,14 @@ export interface PreparePrimaryRuntimeOptions {
|
||||
readonly version: string
|
||||
/** Locked npm pnpm tarball for installed carriers; build hosts otherwise copy their installed pnpm. */
|
||||
readonly pnpmArchive?: RuntimeNpmArchive
|
||||
/** Ordinary filesystem Office assets; build hosts otherwise resolve the workspace package. */
|
||||
readonly skillSource?: string
|
||||
/** Omit Node.js and pnpm for carriers providing only Python. */
|
||||
readonly pythonOnly?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Materialize locked interpreters, libraries and Office resources without executing target code.
|
||||
* Materialize locked interpreters and libraries without executing target code.
|
||||
* @param options - Explicit target and carrier-owned output locations.
|
||||
* @returns Resolves after the complete payload and skills have been copied to the output directory.
|
||||
* @returns Resolves after the complete payload has been copied to the output directory.
|
||||
*/
|
||||
export async function preparePrimaryRuntime(options: PreparePrimaryRuntimeOptions): Promise<void> {
|
||||
const { target } = options
|
||||
@@ -259,9 +246,6 @@ export async function preparePrimaryRuntime(options: PreparePrimaryRuntimeOption
|
||||
} finally {
|
||||
rmSync(staging, { recursive: true, force: true })
|
||||
}
|
||||
const assets = options.skillSource ?? join(dirname(createRequire(import.meta.url).resolve('@deepseek-ai/dsh-skill-office/package.json')), 'assets')
|
||||
await prepareOfficeSkillAssets(assets,
|
||||
join(paths.runtime, 'office-skills'))
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -407,7 +407,6 @@ describe('translation scope discovery', () => {
|
||||
'coverage/report/README.md',
|
||||
'python/sdk-runtime/src/deepseek_harness_runtime/runtime/deepseek-harness-sdk-runtime-macos-arm64/README.md',
|
||||
'python/sdk-runtime/src/deepseek_harness_runtime/runtime/node/README.md',
|
||||
'python/sdk-runtime/src/deepseek_harness_runtime/runtime/macos-arm64/office-skills/office-docx/SKILL.md',
|
||||
])('excludes non-source or non-README path %s', (file) => {
|
||||
expect(isTranslationScopeFile(file)).toBe(false)
|
||||
})
|
||||
|
||||
Reference in new issue
Block a user