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:
yudshj authored and GitHub committed 2026-11-04 13:25:24 +00:00
1 parent 3f6cd80c9d
commit 9df26413ee
49 files changed
+232 -214

No files matched your search

+2 -2
View File
@@ -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
+3 -3
View File
@@ -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:
+3 -3
View File
@@ -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 查询:
+1 -2
View File
@@ -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
+3 -3
View File
@@ -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
+7 -18
View File
@@ -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")