ソースを参照

Merge remote-tracking branch 'origin/master' into worktree/2984-generic-file-upload

creatixchu 2 週間 前
コミット
1967bedd9f
41 ファイル変更349 行追加125 行削除
  1. 2 2
      .agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.i18n.yaml
  2. 0 0
      .agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md
  3. 0 0
      .agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md
  4. 2 2
      .agents/notes/implemented/architecture/2026-08-22-single-dsh-application-launcher.i18n.yaml
  5. 1 1
      .agents/notes/implemented/architecture/2026-08-22-single-dsh-application-launcher.md
  6. 1 1
      .agents/notes/implemented/architecture/2026-08-22-single-dsh-application-launcher.zh.md
  7. 2 2
      .agents/notes/implemented/architecture/2026-08-23-python-sdk-dsh-profile-runtime.i18n.yaml
  8. 1 1
      .agents/notes/implemented/architecture/2026-08-23-python-sdk-dsh-profile-runtime.md
  9. 1 1
      .agents/notes/implemented/architecture/2026-08-23-python-sdk-dsh-profile-runtime.zh.md
  10. 2 2
      .agents/notes/implemented/architecture/2026-08-23-python-sdk-windows-x64-runtime.i18n.yaml
  11. 2 2
      .agents/notes/implemented/architecture/2026-08-23-python-sdk-windows-x64-runtime.md
  12. 2 2
      .agents/notes/implemented/architecture/2026-08-23-python-sdk-windows-x64-runtime.zh.md
  13. 2 2
      .agents/notes/implemented/process/2026-08-11-python-publication-workflow.i18n.yaml
  14. 3 3
      .agents/notes/implemented/process/2026-08-11-python-publication-workflow.md
  15. 3 3
      .agents/notes/implemented/process/2026-08-11-python-publication-workflow.zh.md
  16. 2 2
      .agents/notes/implemented/testing/2026-08-23-installed-python-wheel-black-box-ci.i18n.yaml
  17. 3 3
      .agents/notes/implemented/testing/2026-08-23-installed-python-wheel-black-box-ci.md
  18. 3 3
      .agents/notes/implemented/testing/2026-08-23-installed-python-wheel-black-box-ci.zh.md
  19. 22 10
      .github/workflows/build-exe-for-python-sdk.yml
  20. 1 1
      .github/workflows/ci.yml
  21. 3 2
      .github/workflows/python-release.yml
  22. 24 3
      .gitlab-ci.yml
  23. 2 2
      docs/user/guide/providers.i18n.yaml
  24. 70 17
      docs/user/guide/providers.md
  25. 70 17
      docs/user/guide/providers.zh.md
  26. 2 2
      python/development.i18n.yaml
  27. 3 3
      python/development.md
  28. 3 3
      python/development.zh.md
  29. 2 2
      python/sdk-runtime/README.i18n.yaml
  30. 1 1
      python/sdk-runtime/README.md
  31. 1 1
      python/sdk-runtime/README.zh.md
  32. 4 0
      python/sdk-runtime/platforms.json
  33. 1 2
      python/sdk-runtime/src/deepseek_harness_runtime/__init__.py
  34. 30 1
      python/sdk/tests/test_macos_deployment_target.py
  35. 3 1
      python/sdk/tests/test_release_version.py
  36. 2 3
      python/sdk/tests/test_runtime_resolution.py
  37. 13 0
      scripts/build-exe-for-python-sdk.spec.ts
  38. 1 1
      scripts/build-exe-for-python-sdk.ts
  39. 26 10
      scripts/check-macos-deployment-target.py
  40. 29 8
      scripts/ci-workflow.spec.ts
  41. 4 3
      scripts/verify-runtime-closure.spec.ts

+ 2 - 2
.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md
-2026-07-10-single-file-executable-sdk-runtime-distribution.md: ab09691b48684b8ebae269df4d585b499423e671
-2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md: d986105b087357bd8301251dca14736a5b427bbb
+2026-07-10-single-file-executable-sdk-runtime-distribution.md: 558358167a5e7d37bc79c42003a183b0c4c40d17
+2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md: 0f7daec5b79e6f526dde7a01c5ef85b07e955707

ファイルの差分が大きいため隠しています
+ 0 - 0
.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md


ファイルの差分が大きいため隠しています
+ 0 - 0
.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md


+ 2 - 2
.agents/notes/implemented/architecture/2026-08-22-single-dsh-application-launcher.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-22-single-dsh-application-launcher.md
-2026-08-22-single-dsh-application-launcher.md: feac31b3eafced8158a8d79a0e5967a8de5e2f87
-2026-08-22-single-dsh-application-launcher.zh.md: 88fe4ef4d4e0436ecb450e3f5319be7acee6882b
+2026-08-22-single-dsh-application-launcher.md: 068c59c1695ac41216e4df7012eaeb9f80e1e5a6
+2026-08-22-single-dsh-application-launcher.zh.md: 1e082a3d6de391c70a38031caafc44781bc2c7e4

+ 1 - 1
.agents/notes/implemented/architecture/2026-08-22-single-dsh-application-launcher.md

@@ -48,7 +48,7 @@ Direct SDK use follows normal Harness-home resolution: explicit `dshHome`, inher
 
 The Python runtime wheel packages the ordinary `@deepseek-ai/dsh` CLI from `node_modules/@deepseek-ai/dsh/lib/bin.js` through the private `dsh-python-runtime-closure` deploy manifest. The Python client selects `dsh --profile sdk` by default, ordered patch files, and an explicit Harness home; the runnable example under `python/sdk/examples` selects `sdk-minimal`. The installed `dsh` console command exposes the same profile grammar and the separately packaged `web` application.
 
-The executable family is `deepseek-harness-sdk-runtime-<platform>-<arch>`. The SDK wire, wheel and import distribution names, sidecar names, and wire identity `deepseek-harness-sdk-runtime` remain stable. The SDK package family is `@deepseek-ai/dsh-sdk-client`, `@deepseek-ai/dsh-sdk-protocol`, and `@deepseek-ai/dsh-sdk-jsonrpc-server`; `@deepseek-ai/dsh-acp` remains the ACP protocol plugin. There is no Python-specific Node application, checked-in complete config, compatibility package, forwarding executable, fallback parser, or SDK/ACP launcher alias. The [Python profile-runtime decision](2026-08-23-python-sdk-dsh-profile-runtime.md) owns this launch, and the [Windows x64 runtime decision](2026-08-23-python-sdk-windows-x64-runtime.md) owns the fourth carrier.
+The executable family is `deepseek-harness-sdk-runtime-<platform>-<arch>`. The SDK wire, wheel and import distribution names, sidecar names, and wire identity `deepseek-harness-sdk-runtime` remain stable. The SDK package family is `@deepseek-ai/dsh-sdk-client`, `@deepseek-ai/dsh-sdk-protocol`, and `@deepseek-ai/dsh-sdk-jsonrpc-server`; `@deepseek-ai/dsh-acp` remains the ACP protocol plugin. There is no Python-specific Node application, checked-in complete config, compatibility package, forwarding executable, fallback parser, or SDK/ACP launcher alias. The [Python profile-runtime decision](2026-08-23-python-sdk-dsh-profile-runtime.md) owns this launch, and the [Windows x64 runtime decision](2026-08-23-python-sdk-windows-x64-runtime.md) owns the Windows carrier.
 
 ### Enforcement
 

+ 1 - 1
.agents/notes/implemented/architecture/2026-08-22-single-dsh-application-launcher.zh.md

@@ -48,7 +48,7 @@ SDK 用户通过 profile 自定义插件。`dsh plugin --profile <name> ...` 管
 
 Python 运行时 wheel 通过私有 `dsh-python-runtime-closure` 部署 manifest,打包来自 `node_modules/@deepseek-ai/dsh/lib/bin.js` 的普通 `@deepseek-ai/dsh` CLI。Python 客户端默认选择 `dsh --profile sdk`、有序 patch 文件与显式 Harness home;`python/sdk/examples` 下的可运行示例选择 `sdk-minimal`。安装的 `dsh` 控制台命令暴露相同 profile 语法与单独打包的 `web` 应用。
 
-可执行文件族是 `deepseek-harness-sdk-runtime-<platform>-<arch>`。SDK 协议格式、wheel 与 import 分发名称、伴随文件名称,以及协议 identity `deepseek-harness-sdk-runtime` 保持稳定。SDK 包族是 `@deepseek-ai/dsh-sdk-client`、`@deepseek-ai/dsh-sdk-protocol` 与 `@deepseek-ai/dsh-sdk-jsonrpc-server`;`@deepseek-ai/dsh-acp` 继续作为 ACP 协议插件。仓库不保留 Python 专用 Node 应用、检入的完整配置、兼容包、转发可执行文件、后备解析器或 SDK/ACP 启动别名。[Python profile 运行时决策](2026-08-23-python-sdk-dsh-profile-runtime.zh.md)负责该启动方式,[Windows x64 运行时决策](2026-08-23-python-sdk-windows-x64-runtime.zh.md)负责第四个载体。
+可执行文件族是 `deepseek-harness-sdk-runtime-<platform>-<arch>`。SDK 协议格式、wheel 与 import 分发名称、伴随文件名称,以及协议 identity `deepseek-harness-sdk-runtime` 保持稳定。SDK 包族是 `@deepseek-ai/dsh-sdk-client`、`@deepseek-ai/dsh-sdk-protocol` 与 `@deepseek-ai/dsh-sdk-jsonrpc-server`;`@deepseek-ai/dsh-acp` 继续作为 ACP 协议插件。仓库不保留 Python 专用 Node 应用、检入的完整配置、兼容包、转发可执行文件、后备解析器或 SDK/ACP 启动别名。[Python profile 运行时决策](2026-08-23-python-sdk-dsh-profile-runtime.zh.md)负责该启动方式,[Windows x64 运行时决策](2026-08-23-python-sdk-windows-x64-runtime.zh.md)负责 Windows 载体。
 
 ### 强制校验
 

+ 2 - 2
.agents/notes/implemented/architecture/2026-08-23-python-sdk-dsh-profile-runtime.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-23-python-sdk-dsh-profile-runtime.md
-2026-08-23-python-sdk-dsh-profile-runtime.md: 4af7812db6818b65c754a43ec1a7f973d1cbcbf9
-2026-08-23-python-sdk-dsh-profile-runtime.zh.md: 155e7d2ae0b0ba3e4163dd85a31de90bef9d588a
+2026-08-23-python-sdk-dsh-profile-runtime.md: 07e3bd522b1952a8e6257f79eb292fa0ef18e5ef
+2026-08-23-python-sdk-dsh-profile-runtime.zh.md: 1b68c50d93e0fd6d2555115891772b00f95bb7d4

+ 1 - 1
.agents/notes/implemented/architecture/2026-08-23-python-sdk-dsh-profile-runtime.md

@@ -34,7 +34,7 @@ The zero-code deployment manifest is `dsh-python-runtime-closure`. It packages `
 
 Plain Node profiles use symlinks in `$DSH_HOME/profiles/node_modules` to share installation packages with external plugins. An operating-system symlink cannot traverse pkg's `/snapshot` filesystem, so the packaged CLI writes small real ESM proxy packages instead. Each proxy resolves the source package's explicit ESM export map directly under Node import conditions, exposes targets that exist in the installation, and re-exports their virtual module URLs. Export rows without an ESM runtime target and executable-only or declaration-only packages produce no unusable proxy entry; malformed export maps fail startup. A complete matching generation returns without acquiring the cross-process writer lock. A missing or stale entry acquires the lock, rechecks the generation, and repairs it without exposing partial proxies; either carrier can replace the other carrier's managed entry. Loader rows and external plugin peers therefore resolve through the normal profile parent walk while retaining one Cordis and one instance of each bundled module.
 
-The published target set is Linux x64, Linux arm64, macOS arm64, and Windows x64. Installed-wheel black-box CI owns artifact provenance, default and patched profiles, external bundle installation, native tools, MCP, direct JSON-RPC, snapshots, and trusted real-provider turns on every target. The [Windows x64 runtime decision](2026-08-23-python-sdk-windows-x64-runtime.md) owns the fourth artifact and its platform-specific shell surface.
+The published target set is Linux x64, Linux arm64, macOS arm64, macOS x64, and Windows x64. Installed-wheel black-box CI owns artifact provenance, default and patched profiles, external bundle installation, native tools, MCP, direct JSON-RPC, snapshots, and trusted real-provider turns on every target. The [Windows x64 runtime decision](2026-08-23-python-sdk-windows-x64-runtime.md) owns the Windows artifact and its platform-specific shell surface.
 
 ## Existing decisions and supersession
 

+ 1 - 1
.agents/notes/implemented/architecture/2026-08-23-python-sdk-dsh-profile-runtime.zh.md

@@ -34,7 +34,7 @@ Python SDK 分发一个私有 Node 应用,直接启动完整外部 `cordis.yml
 
 普通 Node profile 在 `$DSH_HOME/profiles/node_modules` 中使用符号链接,让外部插件共享安装包。操作系统符号链接无法进入 pkg 的 `/snapshot` 文件系统,因此打包 CLI 改为写入小型真实 ESM 代理包。每个代理直接按 Node import 条件解析源包的显式 ESM exports map,公开安装中实际存在的目标,并重新导出其虚拟模块 URL。没有 ESM 运行时目标的 export 项以及仅含可执行入口或类型声明入口的包不会产生不可用的代理条目;格式错误的 exports map 会导致启动失败。完整且匹配的 generation 不会获取跨进程写入锁。缺失或过期的配置项会获取该锁、重新检查 generation,并在不暴露半成品代理的前提下修复;任一载体都可以替换另一载体留下的受管配置项。Loader 配置项和外部插件 peer 因而可以通过普通 profile 逐级向上查找解析,同时保留一个 Cordis 和每个内置模块的单一实例。
 
-已发布目标集合是 Linux x64、Linux arm64、macOS arm64 与 Windows x64。Installed-wheel 黑盒 CI 在每个目标上负责产物来源、默认及 patched profile、外部 bundle 安装、原生工具、MCP、直接 JSON-RPC、快照,以及可信真实提供方轮次。[Windows x64 运行时决策](2026-08-23-python-sdk-windows-x64-runtime.zh.md)负责第四个产物及其平台专属 shell surface。
+已发布目标集合是 Linux x64、Linux arm64、macOS arm64、macOS x64 与 Windows x64。Installed-wheel 黑盒 CI 在每个目标上负责产物来源、默认及 patched profile、外部 bundle 安装、原生工具、MCP、直接 JSON-RPC、快照,以及可信真实提供方轮次。[Windows x64 运行时决策](2026-08-23-python-sdk-windows-x64-runtime.zh.md)负责 Windows 产物及其平台专属 shell surface。
 
 ## 既有决策与取代关系
 

+ 2 - 2
.agents/notes/implemented/architecture/2026-08-23-python-sdk-windows-x64-runtime.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-23-python-sdk-windows-x64-runtime.md
-2026-08-23-python-sdk-windows-x64-runtime.md: 59a46d99f9e7ed411aeffbb541bbe3bb0c752078
-2026-08-23-python-sdk-windows-x64-runtime.zh.md: 3ab972aabb8135c8bc6285d129ba7bc9335eb11f
+2026-08-23-python-sdk-windows-x64-runtime.md: 1b59dec47036e7351b212b408a31847a1a1c9639
+2026-08-23-python-sdk-windows-x64-runtime.zh.md: 75cea660c3e5cc8e0222e72dc2d07a0c802aeecd

+ 2 - 2
.agents/notes/implemented/architecture/2026-08-23-python-sdk-windows-x64-runtime.md

@@ -20,7 +20,7 @@ The Python process still launches the ordinary `dsh --profile sdk` application a
 
 The executable builder accepts `win` as a pkg platform only with x64, requires the Windows build to run under x64 Node on a Windows host, preserves `.exe` names, and copies `@vscode/ripgrep-win32-x64` as the conventional `-rg.exe` sidecar. Pnpm subprocesses use a caller-supplied JavaScript entry through `process.execPath`. When the caller exposes a `.cmd` shim, the builder resolves the installed `pnpm.mjs` or `pnpm.cjs` through `PNPM_HOME`; it fails if no JavaScript entry exists instead of spawning the shim or enabling a command shell.
 
-The required GitHub matrix builds `node24-win-x64` on `windows-2025` beside the three existing targets. The public GitHub release and GitLab tag pipeline each publish the same four runtime wheels plus the pure SDK wheel. Windows arm64 is absent from target parsing, manifests, matrices, release contents, and documentation.
+The required GitHub matrix builds `node24-win-x64` on `windows-2025` beside Linux x64, Linux arm64, macOS arm64, and macOS x64. The public GitHub release and GitLab tag pipeline each publish the same five runtime wheels plus the pure SDK wheel. Windows arm64 is absent from target parsing, manifests, matrices, release contents, and documentation.
 
 ### Installed-wheel behavior
 
@@ -48,4 +48,4 @@ This decision partially supersedes the Windows non-goal in the [single-file runt
 
 ## Consequences
 
-Python installation now selects a Node-free Windows x64 runtime with the same explicit-home and profile customization model as Linux and macOS. Every pull request pays for a fourth executable, runtime wheel, full keyless blackbox, and—on trusted heads—real provider task. Release validation retains five wheels instead of four. Windows arm64 users receive an explicit unsupported-platform failure until a separate native product decision supplies and proves that carrier.
+Python installation selects a Node-free Windows x64 runtime with the same explicit-home and profile customization model as Linux and macOS. Every pull request builds the Windows executable and runtime wheel as one of five native targets, runs the full keyless blackbox, and—on trusted heads—runs the real-provider task. Release validation retains six wheels. Windows arm64 users receive an explicit unsupported-platform failure until a separate native product decision supplies and proves that carrier.

+ 2 - 2
.agents/notes/implemented/architecture/2026-08-23-python-sdk-windows-x64-runtime.zh.md

@@ -20,7 +20,7 @@ Python 进程仍按 [Python profile 运行时决策](2026-08-23-python-sdk-dsh-p
 
 可执行文件构建器仅允许 x64 使用 pkg 的 `win` 平台,并要求 Windows 构建在 Windows 宿主的 x64 Node 下运行;构建器保留 `.exe` 文件名,并把 `@vscode/ripgrep-win32-x64` 复制为常规 `-rg.exe` sidecar。Pnpm 子进程通过 `process.execPath` 执行调用方提供的 JavaScript 入口。当调用方暴露 `.cmd` shim 时,构建器会通过 `PNPM_HOME` 解析已安装的 `pnpm.mjs` 或 `pnpm.cjs`;如果不存在 JavaScript 入口,构建会失败,而不会启动 shim 或启用命令 shell。
 
-必需 GitHub 矩阵会在 `windows-2025` 上构建 `node24-win-x64`,与现有三个目标并列。公开 GitHub 发布与 GitLab 标签流水线都会发布同一组四个运行时 wheel 加纯 SDK wheel。目标解析、manifest、矩阵、发布内容与文档均不包含 Windows arm64。
+必需 GitHub 矩阵会在 `windows-2025` 上构建 `node24-win-x64`,与 Linux x64、Linux arm64、macOS arm64 和 macOS x64 并列。公开 GitHub 发布与 GitLab 标签流水线都会发布同一组五个运行时 wheel 加纯 SDK wheel。目标解析、manifest、矩阵、发布内容与文档均不包含 Windows arm64。
 
 ### Installed-wheel 行为
 
@@ -48,4 +48,4 @@ Windows lane 会创建干净的 Windows 虚拟环境,安装版本精确匹配
 
 ## Consequences
 
-Python 安装现在会选择无需 Node 的 Windows x64 运行时,并与 Linux、macOS 使用同一套显式 home 与 profile 自定义模型。每个拉取请求都要承担第四个可执行文件、运行时 wheel 与完整 keyless 黑盒测试;可信 head 还要承担真实提供方任务。候选发行版验证会保留五个而不是四个 wheel。Windows arm64 用户会收到明确的不支持平台错误,直到另一项原生产品决策提供并证明该载体。
+Python 安装会选择无需 Node 的 Windows x64 运行时,并与 Linux、macOS 使用同一套显式 home 与 profile 自定义模型。每个拉取请求都会把 Windows 可执行文件和运行时 wheel 作为五个原生目标之一进行构建,运行完整 keyless 黑盒测试,并在可信 head 上运行真实提供方任务。候选发行版验证会保留六个 wheel。Windows arm64 用户会收到明确的不支持平台错误,直到另一项原生产品决策提供并证明该载体。

+ 2 - 2
.agents/notes/implemented/process/2026-08-11-python-publication-workflow.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-08-11-python-publication-workflow.md
-2026-08-11-python-publication-workflow.md: 282bd453013da9b745c601f7b1f4be2cbd133629
-2026-08-11-python-publication-workflow.zh.md: 279dc4b5798d5ceb5968f92c58a7f57c4f2c5cdd
+2026-08-11-python-publication-workflow.md: afb18ee97d0708d8a79885a8f6639681e97eef72
+2026-08-11-python-publication-workflow.zh.md: f2d045f686c41c3e45a3cd6739dc5e1b37c58478

+ 3 - 3
.agents/notes/implemented/process/2026-08-11-python-publication-workflow.md

@@ -6,15 +6,15 @@ English | [中文](2026-08-11-python-publication-workflow.zh.md)
 
 ## Problem
 
-The Python SDK comprises one platform-independent client wheel and four native runtime wheels that must carry one version and become installable as a set. Public PyPI uploads expose package metadata and files immediately, cannot replace an uploaded filename, and create a temporarily unusable SDK if its exact runtime dependency has not arrived. The private repository needs to exercise the complete native build and validation sequence without publishing any artifact externally.
+The Python SDK comprises one platform-independent client wheel and five native runtime wheels that must carry one version and become installable as a set. Public PyPI uploads expose package metadata and files immediately, cannot replace an uploaded filename, and create a temporarily unusable SDK if its exact runtime dependency has not arrived. The private repository needs to exercise the complete native build and validation sequence without publishing any artifact externally.
 
 ## Decision
 
-The `Release (Python)` GitHub workflow exposes credential-free validation to manual runs with `publish=false`. The run calls the native wheel builder for all four platforms, installs the Linux release set on Python 3.10 and 3.14, downloads the five resulting artifacts, verifies their exact filenames and package metadata, enforces PyPI's default per-file size limit, records SHA-256 hashes, and retains one aggregate release candidate. These jobs have only repository read permission and no registry credential or OIDC permission, and a dry run cannot enter either publication job.
+The `Release (Python)` GitHub workflow exposes credential-free validation to manual runs with `publish=false`. The run calls the native wheel builder for all five targets, installs the Linux release set on Python 3.10 and 3.14, downloads the six resulting artifacts, verifies their exact filenames and package metadata, enforces PyPI's default per-file size limit, records SHA-256 hashes, and retains one aggregate release candidate. These jobs have only repository read permission and no registry credential or OIDC permission, and a dry run cannot enter either publication job.
 
 A run with `publish=true` must use the `python-v<repository-version>` tag in the private automation repository, match that repository's `github.repository` to its repository-scoped `PYPI_PUBLISHER_REPOSITORY` variable, find `PUBLIC_PYPI_RELEASE_ENABLED=true`, and receive approval from the `pypi-runtime` and `pypi` GitHub environments for runtime and SDK publication, respectively. The read-only public mirror supplies the package metadata URLs but does not run release Actions. Only the two publication jobs receive `id-token: write`; PyPI Trusted Publishing exchanges the private repository identity for short-lived project credentials, so the repository stores no PyPI token.
 
-Publication consumes the aggregate artifact produced and checked in the same workflow run. Each publication job verifies the retained `SHA256SUMS` before selecting its upload set. A runtime job uploads all four platform wheels before a dependent job uploads the SDK wheel because PyPI uploads are not atomic and the SDK pins the runtime distribution at the exact same version. Neither job checks out source or rebuilds a wheel. Separating them lets GitHub's failed-job retry resume an SDK failure without attempting to replace immutable runtime files.
+Publication consumes the aggregate artifact produced and checked in the same workflow run. Each publication job verifies the retained `SHA256SUMS` before selecting its upload set. A runtime job uploads all five platform wheels before a dependent job uploads the SDK wheel because PyPI uploads are not atomic and the SDK pins the runtime distribution at the exact same version. Neither job checks out source or rebuilds a wheel. Separating them lets GitHub's failed-job retry resume an SDK failure without attempting to replace immutable runtime files.
 
 Both publication actions disable public attestations. The action still uses Trusted Publishing for authentication, while omitting provenance that would disclose the private publisher repository instead of the public source mirror.
 

+ 3 - 3
.agents/notes/implemented/process/2026-08-11-python-publication-workflow.zh.md

@@ -6,15 +6,15 @@ Status: implemented
 
 ## 问题
 
-Python SDK 由一个平台无关的客户端 wheel 包和个原生运行时 wheel 包组成,它们必须使用同一版本,并作为一组可安装。public PyPI 上传会立即公开包元数据和文件,无法替换已上传的同名文件;如果精确版本的运行时依赖尚未到达,还会产生暂时不可用的 SDK。私有仓库需要在不向外发布任何产物的情况下,执行完整的原生构建与验证流程。
+Python SDK 由一个平台无关的客户端 wheel 包和个原生运行时 wheel 包组成,它们必须使用同一版本,并作为一组可安装。public PyPI 上传会立即公开包元数据和文件,无法替换已上传的同名文件;如果精确版本的运行时依赖尚未到达,还会产生暂时不可用的 SDK。私有仓库需要在不向外发布任何产物的情况下,执行完整的原生构建与验证流程。
 
 ## 决策
 
-GitHub 的 `Release (Python)` 工作流为设置 `publish=false` 的手动运行提供无凭据验证。该运行会为全部四个平台调用原生 wheel 包构建器,在 Python 3.10 和 3.14 上安装 Linux 发行集合,下载所得份产物,验证其精确文件名和包元数据,执行 PyPI 默认单文件大小限制,记录 SHA-256 哈希,并保留一份汇总候选发行版。这些作业只有仓库读取权限,没有注册表凭据或 OIDC 权限,dry-run 运行无法进入任何发布作业。
+GitHub 的 `Release (Python)` 工作流为设置 `publish=false` 的手动运行提供无凭据验证。该运行会为全部五个目标调用原生 wheel 包构建器,在 Python 3.10 和 3.14 上安装 Linux 发行集合,下载所得份产物,验证其精确文件名和包元数据,执行 PyPI 默认单文件大小限制,记录 SHA-256 哈希,并保留一份汇总候选发行版。这些作业只有仓库读取权限,没有注册表凭据或 OIDC 权限,dry-run 运行无法进入任何发布作业。
 
 设置 `publish=true` 时,运行必须在私有自动化仓库使用 `python-v<repository-version>` 标签,将该仓库的 `github.repository` 与其仓库级 `PYPI_PUBLISHER_REPOSITORY` 变量匹配,找到 `PUBLIC_PYPI_RELEASE_ENABLED=true`,并分别获得 GitHub `pypi-runtime` 和 `pypi` 环境对运行时与 SDK 发布的批准。只读公开镜像提供包元数据 URL,但不运行发布 Actions。只有两个发布作业获得 `id-token: write`;PyPI Trusted Publishing 会把私有仓库身份换成短期项目凭据,因此仓库不保存 PyPI token。
 
-发布过程使用同一次工作流运行中生成并检查过的汇总产物。每个发布作业都会在选择上传文件前验证保留的 `SHA256SUMS`。一个运行时作业先上传全部个平台 wheel 包,再由依赖它的作业上传 SDK wheel 包,因为 PyPI 上传不是原子操作,而 SDK 会把运行时分发包固定到完全相同的版本。两个作业都不会检出源码,也不会重新构建 wheel 包。将它们拆开后,GitHub 的失败作业重试可以在 SDK 上传失败时继续执行,而不会尝试替换不可变的运行时文件。
+发布过程使用同一次工作流运行中生成并检查过的汇总产物。每个发布作业都会在选择上传文件前验证保留的 `SHA256SUMS`。一个运行时作业先上传全部个平台 wheel 包,再由依赖它的作业上传 SDK wheel 包,因为 PyPI 上传不是原子操作,而 SDK 会把运行时分发包固定到完全相同的版本。两个作业都不会检出源码,也不会重新构建 wheel 包。将它们拆开后,GitHub 的失败作业重试可以在 SDK 上传失败时继续执行,而不会尝试替换不可变的运行时文件。
 
 两个发布 action 都会禁用公开 attestation。action 仍使用 Trusted Publishing 进行身份认证,同时不上传会披露私有发布仓库而非公开源码镜像的 provenance。
 

+ 2 - 2
.agents/notes/implemented/testing/2026-08-23-installed-python-wheel-black-box-ci.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write .agents/notes/implemented/testing/2026-08-23-installed-python-wheel-black-box-ci.md
-2026-08-23-installed-python-wheel-black-box-ci.md: 0ac3bc63ef391536a761ad6db9d0854a3beebe01
-2026-08-23-installed-python-wheel-black-box-ci.zh.md: 365da458d3eb33dbc82dcdafaebea593cc4fe971
+2026-08-23-installed-python-wheel-black-box-ci.md: a1c5d5f0a8040747caa2148f6b4990fd58828cbe
+2026-08-23-installed-python-wheel-black-box-ci.zh.md: ab2b6c11a2aa49a78bad990b780019f704edef51

+ 3 - 3
.agents/notes/implemented/testing/2026-08-23-installed-python-wheel-black-box-ci.md

@@ -30,7 +30,7 @@ Fork and Dependabot pull requests never receive the repository secret. Their nat
 
 ### Required targets
 
-The pull-request `python-runtime` job calls the reusable builder for Linux x64, Linux arm64, macOS arm64, and Windows x64. Its aggregate result remains a dependency of `all checks passed`, so a failed, cancelled, or missing native carrier blocks the required verdict. The [Windows x64 runtime decision](../architecture/2026-08-23-python-sdk-windows-x64-runtime.md) owns the fourth target and its PowerShell-specific minimal snapshot.
+The pull-request `python-runtime` job calls the reusable builder for Linux x64, Linux arm64, macOS arm64, macOS x64, and Windows x64. Its aggregate result remains a dependency of `all checks passed`, so a failed, cancelled, or missing native carrier blocks the required verdict. The [Windows x64 runtime decision](../architecture/2026-08-23-python-sdk-windows-x64-runtime.md) owns the Windows target and its PowerShell-specific minimal snapshot.
 
 ## Existing decisions and supersession
 
@@ -38,7 +38,7 @@ This decision supersedes the single-target topology in the archived [required Py
 
 ## Alternatives considered
 
-**Keep Linux x64 as the only required carrier.** Rejected because native addons, executable construction, wheel tags, and helper files differ across the four published targets. Release-time discovery is too late for an artifact that every Python SDK installation selects by platform.
+**Keep Linux x64 as the only required carrier.** Rejected because native addons, executable construction, wheel tags, and helper files differ across the five published targets. Release-time discovery is too late for an artifact that every Python SDK installation selects by platform.
 
 **Run full behavior before wheel construction and keep two small installed smokes.** Rejected because that proves the executable against source imports, then proves too little through the distribution users install. The clean installed environment is the stronger common location for the same scenarios.
 
@@ -48,4 +48,4 @@ This decision supersedes the single-target topology in the archived [required Py
 
 ## Consequences
 
-Every pull request pays for four native executable and wheel builds plus deterministic installed-artifact scenarios. Trusted same-repository pull requests also pay for one two-turn DeepSeek task per target. In exchange, the required result describes the files Python users install, proves every published carrier before merge, and cannot pass by importing the checkout or silently skipping the real provider.
+Every pull request pays for five native executable and wheel builds plus deterministic installed-artifact scenarios. Trusted same-repository pull requests also pay for one two-turn DeepSeek task per target. In exchange, the required result describes the files Python users install, proves every published carrier before merge, and cannot pass by importing the checkout or silently skipping the real provider.

+ 3 - 3
.agents/notes/implemented/testing/2026-08-23-installed-python-wheel-black-box-ci.zh.md

@@ -30,7 +30,7 @@ Fork 与 Dependabot 拉取请求永远不会获得仓库密钥。它们的原生
 
 ### 必需目标
 
-拉取请求的 `python-runtime` job 会针对 Linux x64、Linux arm64、macOS arm64 与 Windows x64 调用可复用构建器。其聚合结果仍是 `all checks passed` 的依赖项,因此任一原生载体失败、取消或缺失都会阻止必需判定通过。[Windows x64 运行时决策](../architecture/2026-08-23-python-sdk-windows-x64-runtime.zh.md)负责第四个目标及其 PowerShell 专属极简快照。
+拉取请求的 `python-runtime` job 会针对 Linux x64、Linux arm64、macOS arm64、macOS x64 与 Windows x64 调用可复用构建器。其聚合结果仍是 `all checks passed` 的依赖项,因此任一原生载体失败、取消或缺失都会阻止必需判定通过。[Windows x64 运行时决策](../architecture/2026-08-23-python-sdk-windows-x64-runtime.zh.md)负责 Windows 目标及其 PowerShell 专属极简快照。
 
 ## Existing decisions and supersession
 
@@ -38,7 +38,7 @@ Fork 与 Dependabot 拉取请求永远不会获得仓库密钥。它们的原生
 
 ## Alternatives considered
 
-**只保留 Linux x64 必需载体。** 否决:个已发布目标的原生 addon、可执行文件构建、wheel 包标签与 helper 文件不同。等到发布时才发现问题,对每个 Python SDK 安装都会按平台选择的产物而言太晚。
+**只保留 Linux x64 必需载体。** 否决:个已发布目标的原生 addon、可执行文件构建、wheel 包标签与 helper 文件不同。等到发布时才发现问题,对每个 Python SDK 安装都会按平台选择的产物而言太晚。
 
 **在 wheel 构建前运行完整行为,并保留两个很小的安装后冒烟测试。** 否决:这只能证明可执行文件配合源码 import 工作,再通过 distribution 证明很少的行为。干净安装环境是在同一批场景中验证用户实际安装内容的更强位置。
 
@@ -48,4 +48,4 @@ Fork 与 Dependabot 拉取请求永远不会获得仓库密钥。它们的原生
 
 ## Consequences
 
-每个拉取请求都会承担个原生可执行文件及 wheel 包构建,并运行确定性的安装后产物场景。可信的同仓库拉取请求还会在每个目标上承担一次双轮 DeepSeek 任务。相应地,必需结果描述 Python 用户实际安装的文件,在合并前证明每个已发布载体,并且不能通过导入 checkout 或静默跳过真实提供方而通过。
+每个拉取请求都会承担个原生可执行文件及 wheel 包构建,并运行确定性的安装后产物场景。可信的同仓库拉取请求还会在每个目标上承担一次双轮 DeepSeek 任务。相应地,必需结果描述 Python 用户实际安装的文件,在合并前证明每个已发布载体,并且不能通过导入 checkout 或静默跳过真实提供方而通过。

+ 22 - 10
.github/workflows/build-exe-for-python-sdk.yml

@@ -2,7 +2,7 @@ name: Build single-exe
 
 # Native builds for the release targets; see
 # .agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md.
-# A full target run retains one SDK wheel and four runtime wheels; subset
+# A full target run retains one SDK wheel and five runtime wheels; subset
 # dispatch retains the SDK wheel and selected runtime wheels. Bare executables
 # and source closures are test inputs. Run manually or call it from the Python
 # release workflow. There is no `pull_request` trigger: a label trigger would
@@ -12,7 +12,7 @@ on:
   workflow_call:
     inputs:
       targets:
-        description: Comma-separated pkg targets to build; empty builds all four.
+        description: Comma-separated pkg targets to build; empty builds all five.
         type: string
         required: false
         default: ''
@@ -36,7 +36,7 @@ on:
         description: >-
           Comma-separated pkg targets to build. Any subset of:
           node24-linux-x64, node24-linux-arm64, node24-macos-arm64,
-          node24-win-x64. Empty builds all four.
+          node24-macos-x64, node24-win-x64. Empty builds all five.
         type: string
         required: false
         default: ''
@@ -89,7 +89,7 @@ jobs:
         id: plan
         env:
           # Blank dispatch inputs build all targets.
-          TARGETS: ${{ inputs.targets || 'node24-linux-x64,node24-linux-arm64,node24-macos-arm64,node24-win-x64' }}
+          TARGETS: ${{ inputs.targets || 'node24-linux-x64,node24-linux-arm64,node24-macos-arm64,node24-macos-x64,node24-win-x64' }}
         run: |
           set -euo pipefail
           matrix='[]'
@@ -98,14 +98,15 @@ jobs:
             t="$(echo "$raw" | xargs)" # trim surrounding whitespace
             [ -z "$t" ] && continue
             # Native-only: hosted arm64 Linux uses ubuntu-24.04-arm, while
-            # macos-latest is Apple Silicon.
+            # macos-latest is Apple Silicon; macos-15-intel is native x64.
             case "$t" in
               node24-linux-x64)   runner=ubuntu-latest ;;
               node24-linux-arm64) runner=ubuntu-24.04-arm ;;
               node24-macos-arm64) runner=macos-latest ;;
+              node24-macos-x64)   runner=macos-15-intel ;;
               node24-win-x64)     runner=windows-2025 ;;
               *)
-                echo "::error::Unknown target '$t'. Supported: node24-linux-x64, node24-linux-arm64, node24-macos-arm64, node24-win-x64."
+                echo "::error::Unknown target '$t'. Supported: node24-linux-x64, node24-linux-arm64, node24-macos-arm64, node24-macos-x64, node24-win-x64."
                 exit 1
                 ;;
             esac
@@ -251,6 +252,7 @@ jobs:
             linux-x64) wheel=deepseek_harness_runtime_bin-$VERSION-py3-none-manylinux_2_28_x86_64.whl ;;
             linux-arm64) wheel=deepseek_harness_runtime_bin-$VERSION-py3-none-manylinux_2_28_aarch64.whl ;;
             macos-arm64) wheel=deepseek_harness_runtime_bin-$VERSION-py3-none-macosx_14_0_arm64.whl ;;
+            macos-x64) wheel=deepseek_harness_runtime_bin-$VERSION-py3-none-macosx_14_0_x86_64.whl ;;
             *) echo "::error::Unsupported runtime platform $platform"; exit 1 ;;
           esac
           [ -x "$exe" ] || { echo "::error::$exe missing or not executable"; exit 1; }
@@ -432,13 +434,23 @@ jobs:
             exit 1
           }
 
-      - name: Check macOS deployment target
+      - name: Check macOS payload architecture and deployment target
         if: runner.os == 'macOS'
         env:
           EXE: ${{ steps.runtime-posix.outputs.exe }}
-        run: >-
-          python3 scripts/check-macos-deployment-target.py
-          "$EXE" "$EXE-spawn-helper"
+          PLATFORM: ${{ steps.runtime-posix.outputs.platform }}
+        run: |
+          set -euo pipefail
+          case "$PLATFORM" in
+            macos-arm64) macho_arch=arm64 ;;
+            macos-x64) macho_arch=x86_64 ;;
+            *) echo "::error::Unsupported macOS platform $PLATFORM"; exit 1 ;;
+          esac
+          for payload in "$EXE" "$EXE-rg" "$EXE-spawn-helper"; do
+            lipo "$payload" -verify_arch "$macho_arch"
+          done
+          python3 scripts/check-macos-deployment-target.py \
+            --platform "$PLATFORM" "$EXE" "$EXE-rg" "$EXE-spawn-helper"
 
       - name: Run wheel in a manylinux 2.28 container
         if: runner.os == 'Linux'

+ 1 - 1
.github/workflows/ci.yml

@@ -328,7 +328,7 @@ jobs:
     name: python runtime / release-shaped matrix
     uses: ./.github/workflows/build-exe-for-python-sdk.yml
     with:
-      targets: node24-linux-x64,node24-linux-arm64,node24-macos-arm64,node24-win-x64
+      targets: node24-linux-x64,node24-linux-arm64,node24-macos-arm64,node24-macos-x64,node24-win-x64
       ci: true
     secrets:
       DEEPSEEK_API_KEY_EXTERNAL: ${{ secrets.DEEPSEEK_API_KEY_EXTERNAL }}

+ 3 - 2
.github/workflows/python-release.yml

@@ -24,10 +24,10 @@ concurrency:
 
 jobs:
   build:
-    name: Build five wheels
+    name: Build six wheels
     uses: ./.github/workflows/build-exe-for-python-sdk.yml
     with:
-      targets: node24-linux-x64,node24-linux-arm64,node24-macos-arm64,node24-win-x64
+      targets: node24-linux-x64,node24-linux-arm64,node24-macos-arm64,node24-macos-x64,node24-win-x64
       release: true
 
   python-compat:
@@ -149,6 +149,7 @@ jobs:
           actual="$(mktemp)"
           printf '%s\n' \
             "deepseek_harness_runtime_bin-$VERSION-py3-none-macosx_14_0_arm64.whl" \
+            "deepseek_harness_runtime_bin-$VERSION-py3-none-macosx_14_0_x86_64.whl" \
             "deepseek_harness_runtime_bin-$VERSION-py3-none-manylinux_2_28_aarch64.whl" \
             "deepseek_harness_runtime_bin-$VERSION-py3-none-manylinux_2_28_x86_64.whl" \
             "deepseek_harness_runtime_bin-$VERSION-py3-none-win_amd64.whl" \

+ 24 - 3
.gitlab-ci.yml

@@ -60,8 +60,16 @@ sdk-wheel:
         docker run --rm -v "$PWD:/work" -w /work "$image" bash -euxo pipefail -c "/opt/python/cp310-cp310/bin/python -m venv /tmp/dsh-sdk && /tmp/dsh-sdk/bin/python -m pip install --find-links /work/release/$PLATFORM --find-links /work/release/sdk deepseek-harness-sdk==$DSH_WHEEL_VERSION && /tmp/dsh-sdk/bin/python /work/scripts/smoke-python-runtime.py --scenario sdk-default && /tmp/dsh-sdk/bin/python /work/scripts/smoke-python-runtime.py --scenario sdk-mcp"
       fi
     - |
-      if [ "$PLATFORM" = macos-arm64 ]; then
-        python3 scripts/check-macos-deployment-target.py "$EXE" "$EXE-spawn-helper"
+      if [ "${PLATFORM#macos-}" != "$PLATFORM" ]; then
+        case "$PLATFORM" in
+          macos-arm64) macho_arch=arm64 ;;
+          macos-x64) macho_arch=x86_64 ;;
+          *) echo "Unsupported macOS platform $PLATFORM"; exit 1 ;;
+        esac
+        for payload in "$EXE" "$EXE-rg" "$EXE-spawn-helper"; do
+          lipo "$payload" -verify_arch "$macho_arch"
+        done
+        python3 scripts/check-macos-deployment-target.py --platform "$PLATFORM" "$EXE" "$EXE-rg" "$EXE-spawn-helper"
       fi
   artifacts:
     paths: [release/$PLATFORM/*.whl]
@@ -97,6 +105,16 @@ runtime-macos-arm64:
     - job: sdk-wheel
       artifacts: true
 
+runtime-macos-x64:
+  extends: .runtime-wheel
+  tags: [macos-x64]
+  variables:
+    PKG_TARGET: node24-macos-x64
+    PLATFORM: macos-x64
+  needs:
+    - job: sdk-wheel
+      artifacts: true
+
 runtime-windows-x64:
   stage: build
   tags: [windows-x64]
@@ -147,6 +165,8 @@ publish-python:
       artifacts: true
     - job: runtime-macos-arm64
       artifacts: true
+    - job: runtime-macos-x64
+      artifacts: true
     - job: runtime-windows-x64
       artifacts: true
   before_script:
@@ -157,11 +177,12 @@ publish-python:
     - test "$CI_COMMIT_TAG" = "python-v$DSH_VERSION" || { echo "Tag $CI_COMMIT_TAG does not match package.json version $DSH_VERSION"; exit 1; }
     - python -m pip install twine==6.2.0
   script:
-    - test "$(find release -name '*.whl' | wc -l | tr -d ' ')" = 5
+    - test "$(find release -name '*.whl' | wc -l | tr -d ' ')" = 6
     - test -f "release/sdk/deepseek_harness_sdk-${DSH_WHEEL_VERSION}-py3-none-any.whl"
     - test -f "release/linux-x64/deepseek_harness_runtime_bin-${DSH_WHEEL_VERSION}-py3-none-manylinux_2_28_x86_64.whl"
     - test -f "release/linux-arm64/deepseek_harness_runtime_bin-${DSH_WHEEL_VERSION}-py3-none-manylinux_2_28_aarch64.whl"
     - test -f "release/macos-arm64/deepseek_harness_runtime_bin-${DSH_WHEEL_VERSION}-py3-none-macosx_14_0_arm64.whl"
+    - test -f "release/macos-x64/deepseek_harness_runtime_bin-${DSH_WHEEL_VERSION}-py3-none-macosx_14_0_x86_64.whl"
     - test -f "release/win-x64/deepseek_harness_runtime_bin-${DSH_WHEEL_VERSION}-py3-none-win_amd64.whl"
     - python -m twine check release/*/*.whl
     - export TWINE_USERNAME=gitlab-ci-token

+ 2 - 2
docs/user/guide/providers.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write docs/user/guide/providers.md
-providers.md: 59682d38c71893c16118639cb2b24f63497a3c13
-providers.zh.md: a72be6c706f7ce6d29a802bfaa7f65eedb2eb991
+providers.md: 0db3280760c487c8469399bc9668b0685213e375
+providers.zh.md: 3af9096f5a77ee40afe2ee788aa22a0758dd0e86

+ 70 - 17
docs/user/guide/providers.md

@@ -12,21 +12,39 @@ Open **Settings → Models**. The DeepSeek card exposes one API-key field; enter
 
 Keys are write-only. The page receives a redacted descriptor after saving, never the literal secret. The key is stored in `$DSH_HOME/.credentials.yaml`, while settings retain only its credential reference.
 
-## Add a catalog provider
+## Add a built-in provider
 
-Choose **Add provider**, select a provider such as Anthropic or OpenAI, enter its API key, and save. The installed catalog supplies the endpoint, protocol, and model list.
+Choose **Add provider** and pick a provider dsh ships with; the list shows provider ids such as `anthropic`, `openai`, `moonshotai` for Kimi, or `zai` for GLM. Enter its API key and save. The installed catalog supplies the endpoint, protocol, and model list.
 
-Providers with native authentication need their native credentials instead. Bedrock, Vertex, Azure, and Codex use AWS credentials and a region, an ADC project, an `api-version`, and OAuth respectively; filling only the API-key field does not configure them.
+Providers that sign in with OAuth, such as Codex, are not supported here yet.
 
 ## Add a custom provider
 
-Choose **Add a custom provider** for a company gateway, self-hosted server, or provider absent from the installed catalog. Supply a lowercase Provider ID, base URL, API protocol, credential, and at least one model.
+Choose **Add a custom provider** for a company gateway, self-hosted server, or provider absent from the installed catalog. Supply a lowercase Provider ID, base URL, API protocol, credential, and at least one model. The **API protocol** must be the one your gateway speaks, and the form offers three: `openai-completions` for OpenAI Chat Completions, `openai-responses` for the OpenAI Responses API, and `anthropic-messages` for the Anthropic Messages API. A provider speaks one protocol, so a gateway that serves two needs two providers.
 
 ![The custom provider form: Provider ID, display name, base URL, API protocol, and API key](providers-custom-form.png)
 
 The Provider ID is permanent because requests, saved sessions, model defaults, and credential references use it. To rename a provider, add a new provider and delete the old one. The display name, base URL, protocol, credential, and models remain editable.
 
-Under **Model catalog**, choose **Fetch available models** to query the base URL and credential currently shown in the form. Selecting candidates updates the draft; the provider is not stored until you save. Catalog providers use their installed catalog without a network request.
+### Discover models
+
+Under **Model catalog**, choose **Fetch available models** to ask the endpoint which models it serves. The request uses the base URL, protocol, and key currently in the form, or a saved provider's stored key, and the reply opens a searchable picker: search, tick the models you want, and choose **Add selected**. Nothing is stored until you save or create the provider.
+
+Discovery reads the listing formats common gateways publish, but not every endpoint answers in one of them, so treat it as a convenience rather than a guarantee: when it fails or lists nothing, add the model ids by hand and they work just the same. A built-in provider is always answered from the installed catalog, even when its base URL points at a gateway, so fetch through a custom provider to see what the gateway really serves.
+
+## Select a model
+
+Configured providers appear in the model picker. Selecting a model also makes it the default for new sessions. A session that has already sent a request retains the model recorded in its own log.
+
+If a saved default names a provider that was deleted, the composer displays **Select model** and blocks input until another model is selected.
+
+## Advanced configuration
+
+The generated [plugin configuration catalog](../../config-catalog.md) lists every supported field and default for every plugin; [`dsh-llm-pi-ai`](../../config-catalog.md#deepseek-aidsh-llm-pi-ai) is the provider section this page configures. The [`dsh-llm-pi-ai`](../../../packages/llm/llm-pi-ai/README.md) and [`dsh-llm-deepseek`](../../../packages/llm/llm-deepseek/README.md) references own direct `settings.yaml` configuration, catalog resolution, reasoning controls, credentials, and adapter errors.
+
+::: tip The form is deliberately small
+The Models page exposes only what a route needs to exist: the API key, display name, base URL, API protocol, and for each model its id, display name, context window, and max output tokens. Every other field — reasoning effort levels, image input, request-compatibility switches, headers, timeouts, retry policy — is set in `$DSH_HOME/settings.yaml`, the same document the page writes. Edit it directly, or, when the browser runs on the same machine as the server, open it with **Open configuration file** in the Settings header; the adapters re-read it on the next request, so nothing needs a restart. The subsections below cover the fields most gateways need.
+:::
 
 ### Image input
 
@@ -64,7 +82,7 @@ llm-pi-ai:
         - id: second-model
 ```
 
-`defaultInput` is a fallback, not an override, and defaults to `[text]`: on a catalog provider it answers only for models the catalog does not describe, so it never removes images from a catalog model that has them. Narrow one of those with that model's own `input`. A catalog provider has no `models` list to put it in, so write it under `modelOverrides`, keyed by model id:
+`defaultInput` is a fallback, not an override, and defaults to `[text]`: on a built-in provider it answers only for models its catalog does not describe, so it never removes images from a catalog model that has them. Narrow one of those with that model's own `input`. A built-in provider has no `models` list to put it in, so write it under `modelOverrides`, keyed by model id:
 
 ```yaml
 llm-pi-ai:
@@ -79,6 +97,48 @@ Every list must name at least one modality except a model's own, where an empty
 
 Both fields state a claim about your endpoint rather than checking it. A model that declares images its endpoint does not serve is not caught here; the provider rejects the request instead.
 
+### Reasoning effort
+
+The model picker offers an **Effort** menu for a model that declares reasoning levels. A built-in provider's models inherit their levels from the installed catalog. A model you enter by hand declares none, so the Effort entry does not appear in the menu and the endpoint's own default decides whether the model thinks. Declare the levels with `reasoningEfforts` in `$DSH_HOME/settings.yaml`:
+
+```yaml
+llm-pi-ai:
+  providers:
+    my-gateway:
+      apiKeyEnv: GATEWAY_API_KEY
+      api: openai-completions
+      baseURL: https://gateway.example/v1
+      reasoning: high
+      models:
+        - id: my-reasoner
+          reasoningEfforts:
+            off:
+            high: high
+            max: max
+```
+
+Each key is a level the menu offers, and its value is the spelling sent on the wire as `reasoning_effort`, so `max: xhigh` renames a level for a gateway with its own vocabulary. Only `off` may stay empty, because for most endpoints not thinking is the parameter's absence. The route's `reasoning` is the level used while a session has picked none; choosing an effort in the picker saves it, with the model, as the default for new sessions.
+
+An `off` left empty sends nothing, which only stops a model that thinks on request; an `off` given a value sends that value as `reasoning_effort` instead. A model that thinks unless told not to — DeepSeek V4 behind an OpenAI-compatible gateway, for example — needs `compat.thinkingFormat: deepseek`, which makes `off` send `thinking: {type: disabled}` and every other level send `thinking: {type: enabled}` beside the effort:
+
+```yaml
+      models:
+        - id: deepseek-v4-pro
+          compat:
+            thinkingFormat: deepseek
+          reasoningEfforts:
+            off:
+            high: high
+            max: max
+```
+
+A built-in provider's model whose gateway does not reason loses its levels with `reasoningEfforts: false` under `modelOverrides`; selecting an effort for it is then refused as `UNSUPPORTED_REASONING_EFFORT`. DeepSeek's own route needs none of this: its models already offer `off`, `low`, `high`, and `max`, and `llm-deepseek.reasoningEffort` sets the default the picker starts from:
+
+```yaml
+llm-deepseek:
+  reasoningEffort: max
+```
+
 ### Request compatibility
 
 A gateway can hold a working key at a reachable address and still refuse every request. pi-ai decides the shape of a request — which role carries the system prompt, which field caps the output, how a thinking level travels — from the endpoint's URL, and an address it does not recognize is addressed as though it were OpenAI itself. Most OpenAI-compatible gateways refuse at least one thing OpenAI accepts.
@@ -115,23 +175,16 @@ Each switch belongs to the protocols that declare it, so a switch valid on one `
 
 Every switch, its accepted values, and the protocols that take it are listed under `PiAiCompatProfile` in the [generated `dsh-llm-pi-ai` configuration reference](../../config-catalog.md#deepseek-aidsh-llm-pi-ai) — which is derived from the source, so it cannot fall behind what the adapter accepts.
 
-## Select a model
-
-Configured providers appear in the model picker. Selecting a model also makes it the default for new sessions. A session that has already sent a request retains the model recorded in its own log.
-
-If a saved default names a provider that was deleted, the composer displays **Select model** and blocks input until another model is selected.
-
 ## Troubleshooting
 
 - **`MISSING_CREDENTIAL`** — Store the provider key through the Models page or supply the referenced environment variable.
 - **`UNKNOWN_MODEL`** — Select a configured model or add the missing model to the custom provider.
 - **Fetching available models returns 401** — Check the key. Model discovery calls the OpenAI-compatible `GET /models` endpoint; enter models manually for endpoints that do not provide it.
+- **Fetching available models reports neither a `data` array nor a `models` object** — The endpoint's listing is in a format discovery does not read. Enter the models by hand.
 - **The gateway refuses every request although the key and URL are right** — Its request shape differs from OpenAI's. Start with `compat.supportsDeveloperRole: false` and `compat.maxTokensField: max_tokens` on the route.
 - **Only reasoning models fail** — pi-ai sends their system prompt as the `developer` role, which the gateway rejects. Set `compat.supportsDeveloperRole: false`.
+- **The Effort menu does not appear for a model you entered by hand** — It declares no levels. Add `reasoningEfforts` to the model in `settings.yaml`.
+- **`off` does not stop a DeepSeek model from thinking** — An empty `off` sends no reasoning field at all, and an endpoint that thinks by default keeps thinking. Set `compat.thinkingFormat: deepseek` on the model or the route.
 - **A compat switch is refused as having no value** — A key written with nothing after the colon. Give it a value, or remove the key to keep the installed catalog's.
-- **An image is refused before sending** — The model declares no image modality. Give a custom provider's model `input: [text, image]`; DeepSeek's own chat-completions route is text-only and cannot be configured otherwise.
+- **An image is refused before sending** — The model declares no image modality. Give a custom provider's model `input: [text, image]`; on DeepSeek's own route, select `deepseek-v4-flash-vision-exp`, the model that declares images.
 - **The provider rejects a request carrying an image** — The model declares images its endpoint does not actually serve. Remove `image` from whichever list granted it — the model's `input`, or the route's `defaultInput` — then start a new session: the attached image stays in the session log, so the same request repeats until the session moves off it.
-
-## Advanced configuration
-
-The generated [plugin configuration catalog](../../config-catalog.md) lists every supported field and default for every plugin; [`dsh-llm-pi-ai`](../../config-catalog.md#deepseek-aidsh-llm-pi-ai) is the provider section this page configures. The [`dsh-llm-pi-ai`](../../../packages/llm/llm-pi-ai/README.md) and [`dsh-llm-deepseek`](../../../packages/llm/llm-deepseek/README.md) references own direct `settings.yaml` configuration, catalog resolution, reasoning controls, credentials, and adapter errors.

+ 70 - 17
docs/user/guide/providers.zh.md

@@ -12,21 +12,39 @@
 
 密钥是只写的。保存后,页面只会收到脱敏描述符,永远不会收到明文密钥。密钥存储在 `$DSH_HOME/.credentials.yaml` 中,settings 只保留它的凭据引用。
 
-## 添加目录提供方
+## 添加内置提供方
 
-选择**添加提供方**,选取 Anthropic 或 OpenAI 等提供方,输入其 API 密钥并保存。已安装目录会提供端点、协议和模型列表。
+选择**添加提供方**,选取 dsh 自带的提供方;列表显示的是提供方 id,例如 `anthropic`、`openai`、Kimi 对应的 `moonshotai`、GLM 对应的 `zai`。输入其 API 密钥并保存。已安装目录会提供端点、协议和模型列表。
 
-使用原生认证的提供方需要各自的原生凭据。Bedrock、Vertex、Azure 和 Codex 分别使用 AWS 凭据与区域、ADC 项目、`api-version` 和 OAuth;只填写 API 密钥字段无法完成配置
+通过 OAuth 登录的提供方(例如 Codex)暂不支持
 
 ## 添加自定义提供方
 
-对于公司网关、自建服务器或已安装目录中不存在的提供方,选择**添加自定义提供方**。提供小写 Provider ID、基础 URL、API 协议、凭据和至少一个模型。
+对于公司网关、自建服务器或已安装目录中不存在的提供方,选择**添加自定义提供方**。提供小写 Provider ID、基础 URL、API 协议、凭据和至少一个模型。**API 协议**必须选网关实际使用的那一种,表单提供三种:`openai-completions` 对应 OpenAI Chat Completions,`openai-responses` 对应 OpenAI Responses API,`anthropic-messages` 对应 Anthropic Messages API。一个提供方只使用一种协议,网关同时提供两种时需要建两个提供方。
 
 ![自定义提供方表单:Provider ID、显示名称、API 地址、API 协议、API 密钥](providers-custom-form.zh.png)
 
 Provider ID 是永久的,因为请求、已保存会话、模型默认值和凭据引用都会使用它。如需重命名提供方,请添加新提供方并删除旧提供方。显示名称、基础 URL、协议、凭据和模型仍可编辑。
 
-在**模型目录**中选择**获取可用模型**,可查询表单当前显示的基础 URL 和凭据。选择候选项只会更新草稿;保存前不会存储提供方。目录提供方使用已安装目录,不发起网络请求。
+### 探测模型
+
+在**模型目录**中选择**获取可用模型**,即可询问端点它提供哪些模型。请求使用表单当前的 API 地址、协议和密钥,已保存的提供方则用已存储的密钥;响应会打开一个可搜索的选择框,搜索、勾选想要的模型,再点**添加所选**。保存或创建提供方之前不会存储任何内容。
+
+探测读取的是常见网关公开的列表格式,但并非每个端点都用这些格式作答,所以它只是便利手段而非保证:探测失败或列表为空时,手动添加模型 ID 即可,效果完全一样。内置提供方一律由已安装目录作答,即使其 API 地址指向网关也是如此,要查看网关实际提供的模型,请通过自定义提供方探测。
+
+## 选择模型
+
+已配置的提供方会出现在模型选择器中。选择模型也会将其设为新会话的默认值。已发送过请求的会话会保留自身日志中记录的模型。
+
+如果已保存默认值指向已删除的提供方,输入框会显示**选择模型**,并在选择其他模型前阻止输入。
+
+## 进阶配置
+
+自动生成的[插件配置目录](../../config-catalog.zh.md)列出每个插件的所有受支持字段与默认值;[`dsh-llm-pi-ai`](../../config-catalog.zh.md#deepseek-aidsh-llm-pi-ai) 就是本页所配置的那个提供方段落。[`dsh-llm-pi-ai`](../../../packages/llm/llm-pi-ai/README.zh.md) 和 [`dsh-llm-deepseek`](../../../packages/llm/llm-deepseek/README.zh.md) 参考文档负责直接 `settings.yaml` 配置、目录解析、推理控制、凭据与适配器错误。
+
+::: tip 表单刻意保持精简
+模型页只开放让一条路由得以存在的字段:API 密钥、显示名称、API 地址、API 协议,以及每个模型的 ID、显示名称、上下文窗口和最大输出 token 数。其余所有字段——推理等级、图片输入、请求兼容性开关、请求头、超时、重试策略——都在 `$DSH_HOME/settings.yaml` 中设置,也就是模型页写入的同一份文档。可以直接编辑它;浏览器与服务器在同一台机器时,也可以点击设置页顶部的**打开配置文件**打开它。适配器会在下一次请求时重新读取,无需重启任何东西。下面各小节介绍多数网关会用到的字段。
+:::
 
 ### 图片输入
 
@@ -64,7 +82,7 @@ llm-pi-ai:
         - id: second-model
 ```
 
-`defaultInput` 是回退值而不是覆盖值,默认为 `[text]`:在目录提供方上,它只为目录未描述的模型作答,因此绝不会把目录中本就具备图片能力的模型的该能力去掉。要收窄这类模型,请用它自己的 `input`。目录提供方没有可供填写的 `models` 列表,因此写在 `modelOverrides` 下,以模型 id 为键:
+`defaultInput` 是回退值而不是覆盖值,默认为 `[text]`:在内置提供方上,它只为其目录未描述的模型作答,因此绝不会把目录中本就具备图片能力的模型的该能力去掉。要收窄这类模型,请用它自己的 `input`。内置提供方没有可供填写的 `models` 列表,因此写在 `modelOverrides` 下,以模型 id 为键:
 
 ```yaml
 llm-pi-ai:
@@ -79,6 +97,48 @@ llm-pi-ai:
 
 这两个字段都是对你端点的断言,而不是对它的检查。声明了端点并不提供的图片能力的模型不会在这里被拦下,改由提供方拒绝该请求。
 
+### 推理等级
+
+对于声明了推理等级的模型,模型选择器会提供**推理等级**菜单。内置提供方的模型从已安装目录继承其等级。手动录入的模型不声明任何等级,因此模型菜单里不会出现推理等级项,由端点自身的默认值决定模型是否思考。请在 `$DSH_HOME/settings.yaml` 中用 `reasoningEfforts` 声明等级:
+
+```yaml
+llm-pi-ai:
+  providers:
+    my-gateway:
+      apiKeyEnv: GATEWAY_API_KEY
+      api: openai-completions
+      baseURL: https://gateway.example/v1
+      reasoning: high
+      models:
+        - id: my-reasoner
+          reasoningEfforts:
+            off:
+            high: high
+            max: max
+```
+
+每个键都是菜单提供的一个等级,其值是在协议上以 `reasoning_effort` 发送的写法,因此 `max: xhigh` 可以为自有一套词汇的网关重命名某个等级。只有 `off` 可以留空,因为对多数端点来说,不思考就是不传该参数。路由的 `reasoning` 是会话尚未选择等级时采用的等级;在选择器中选定某个等级后,它会与模型一起保存为新会话的默认值。
+
+留空的 `off` 什么都不发送,这只能让「按请求才思考」的模型停下来;给 `off` 一个值,则会把该值作为 `reasoning_effort` 发送。对于「不明确关闭就会思考」的模型——例如 OpenAI 兼容网关后面的 DeepSeek V4——需要 `compat.thinkingFormat: deepseek`:它让 `off` 发送 `thinking: {type: disabled}`,其他每个等级则在 effort 之外再发送 `thinking: {type: enabled}`:
+
+```yaml
+      models:
+        - id: deepseek-v4-pro
+          compat:
+            thinkingFormat: deepseek
+          reasoningEfforts:
+            off:
+            high: high
+            max: max
+```
+
+网关并不提供推理能力的内置提供方模型,可在 `modelOverrides` 下用 `reasoningEfforts: false` 去掉其等级;之后再为它选择等级会被拒绝并报 `UNSUPPORTED_REASONING_EFFORT`。DeepSeek 自身的路由不需要以上任何配置:其模型已经提供 `off`、`low`、`high` 和 `max`,`llm-deepseek.reasoningEffort` 设置选择器的起始默认值:
+
+```yaml
+llm-deepseek:
+  reasoningEffort: max
+```
+
 ### 请求兼容性
 
 网关可能持有可用的密钥、地址也通得到,却仍然拒绝每一个请求。pi-ai 依据端点的 URL 决定请求的形状——系统提示词由哪个角色承载、输出上限写在哪个字段、思考级别如何传输——而对于它无法识别的地址,会当作 OpenAI 本身来对待。多数 OpenAI 兼容网关至少会拒绝 OpenAI 所接受的某一样东西。
@@ -115,23 +175,16 @@ llm-pi-ai:
 
 全部开关、各自接受的取值,以及接受它们的协议,都列在[生成的 `dsh-llm-pi-ai` 配置参考](../../config-catalog.zh.md#deepseek-aidsh-llm-pi-ai)的 `PiAiCompatProfile` 之下——该参考派生自源码,因此不会落后于适配器实际接受的内容。
 
-## 选择模型
-
-已配置的提供方会出现在模型选择器中。选择模型也会将其设为新会话的默认值。已发送过请求的会话会保留自身日志中记录的模型。
-
-如果已保存默认值指向已删除的提供方,输入框会显示**选择模型**,并在选择其他模型前阻止输入。
-
 ## 排错
 
 - **`MISSING_CREDENTIAL`**:通过模型页存储提供方密钥,或提供被引用的环境变量。
 - **`UNKNOWN_MODEL`**:选择已配置的模型,或向自定义提供方添加缺失的模型。
 - **获取可用模型返回 401**:检查密钥。模型发现会调用 OpenAI 兼容的 `GET /models` 端点;对于不提供该端点的服务,请手动输入模型。
+- **获取可用模型提示既没有 `data` 数组也没有 `models` 对象**:端点返回的列表格式不在探测的读取范围内。请手动输入模型。
 - **密钥与地址都正确,网关却拒绝每一个请求**:它的请求形状与 OpenAI 不同。先在路由上设 `compat.supportsDeveloperRole: false` 与 `compat.maxTokensField: max_tokens`。
 - **只有推理模型失败**:pi-ai 把它们的系统提示词以 `developer` 角色发出,而网关拒绝该角色。设 `compat.supportsDeveloperRole: false`。
+- **手动录入的模型没有推理等级菜单**:该模型没有声明任何等级。在 `settings.yaml` 中给该模型加上 `reasoningEfforts`。
+- **`off` 无法让 DeepSeek 模型停止思考**:留空的 `off` 不发送任何推理字段,默认思考的端点就继续思考。请在模型或路由上设置 `compat.thinkingFormat: deepseek`。
 - **某个 compat 开关因没有值而被拒绝**:冒号后什么都没写。给它一个值,或删掉该键以沿用已安装 catalog 的值。
-- **图片在发送前被拒绝**:该模型未声明图片模态。请给自定义提供方的模型加上 `input: [text, image]`;DeepSeek 自身的 chat-completions 路由是纯文本的,且无法通过配置改变。
+- **图片在发送前被拒绝**:该模型未声明图片模态。请给自定义提供方的模型加上 `input: [text, image]`;在 DeepSeek 自身的路由上,请选择声明了图片能力的模型 `deepseek-v4-flash-vision-exp`
 - **提供方拒绝了带图片的请求**:该模型声明了其端点实际并不提供的图片能力。请从授予它图片能力的那个列表中移除 `image`——可能是模型的 `input`,也可能是路由的 `defaultInput`——然后开启新会话:附加的图片会留在会话日志里,因此在会话离开它之前,同一个请求会不断重复。
-
-## 进阶配置
-
-自动生成的[插件配置目录](../../config-catalog.zh.md)列出每个插件的所有受支持字段与默认值;[`dsh-llm-pi-ai`](../../config-catalog.zh.md#deepseek-aidsh-llm-pi-ai) 就是本页所配置的那个提供方段落。[`dsh-llm-pi-ai`](../../../packages/llm/llm-pi-ai/README.zh.md) 和 [`dsh-llm-deepseek`](../../../packages/llm/llm-deepseek/README.zh.md) 参考文档负责直接 `settings.yaml` 配置、目录解析、推理控制、凭据与适配器错误。

+ 2 - 2
python/development.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write python/development.md
-development.md: 72acaec2b3ec2142fc176d940f1a2854e44ebad8
-development.zh.md: 8bd17ad7e604b1cdc4456053bcec95c9e8e503cb
+development.md: aa0144d7eaa66711d0f08316d4445da060918ca8
+development.zh.md: a35f6fc8de1bdbd282fd8999a1440fde0c400b34

+ 3 - 3
python/development.md

@@ -13,7 +13,7 @@ pnpm install
 pnpm exec tsx scripts/build-exe-for-python-sdk.ts
 ```
 
-Use `--skip-build` when the required `lib/` artifacts already exist, or `--targets=node24-linux-x64,node24-linux-arm64,node24-macos-arm64,node24-win-x64` to select platforms. Build each target on its native architecture. Products land in `dist-exe/` and the script syncs the selected carriers into `python/sdk-runtime/`. Windows emits `.exe` and `-rg.exe`; macOS also syncs the matching spawn helper required by `node-pty`.
+Use `--skip-build` when the required `lib/` artifacts already exist, or `--targets=node24-linux-x64,node24-linux-arm64,node24-macos-arm64,node24-macos-x64,node24-win-x64` to select platforms. Build each target on its native architecture. Products land in `dist-exe/` and the script syncs the selected carriers into `python/sdk-runtime/`. Windows emits `.exe` and `-rg.exe`; macOS also syncs the matching spawn helper required by `node-pty`.
 
 ## Validate the SDK
 
@@ -79,11 +79,11 @@ pip install \
   "dist-python/deepseek_harness_runtime_bin-$version-py3-none-macosx_14_0_arm64.whl"
 ```
 
-The runtime distribution is wheel-only. The release pipeline publishes four platform wheels with the pure SDK wheel: Linux x64, Linux arm64, macOS 14 or newer on arm64, and Windows x64 (`win_amd64`). A `python-v<repository-version>` tag is accepted only when it matches the repository version; prerelease repository versions such as `0.0.1-rc.1` use their normalized PEP 440 spelling, such as `0.0.1rc1`, inside wheel filenames and metadata.
+The runtime distribution is wheel-only. The release pipeline publishes five platform wheels with the pure SDK wheel: Linux x64, Linux arm64, macOS 14 or newer on arm64 and x64, and Windows x64 (`win_amd64`). A `python-v<repository-version>` tag is accepted only when it matches the repository version; prerelease repository versions such as `0.0.1-rc.1` use their normalized PEP 440 spelling, such as `0.0.1rc1`, inside wheel filenames and metadata.
 
 ## Validate a release candidate
 
-Manually run the GitHub `Release (Python)` workflow with `publish=false` to build all five wheels, install the Linux release set on Python 3.10 and 3.14, check exact filenames and metadata, enforce PyPI's default per-file size limit, and retain one aggregate artifact with SHA-256 hashes. The run has no registry credentials; a dry run cannot enter either publication job.
+Manually run the GitHub `Release (Python)` workflow with `publish=false` to build all six wheels, install the Linux release set on Python 3.10 and 3.14, check exact filenames and metadata, enforce PyPI's default per-file size limit, and retain one aggregate artifact with SHA-256 hashes. The run has no registry credentials; a dry run cannot enter either publication job.
 
 Public publication runs from the private automation repository. Package metadata points to the separate read-only public source mirror, which does not run release Actions. The private repository defines the repository variable `PYPI_PUBLISHER_REPOSITORY` as its own `owner/name` and keeps `PUBLIC_PYPI_RELEASE_ENABLED=false` except during an intentional release.
 

+ 3 - 3
python/development.zh.md

@@ -13,7 +13,7 @@ pnpm install
 pnpm exec tsx scripts/build-exe-for-python-sdk.ts
 ```
 
-所需 `lib/` 产物已存在时使用 `--skip-build`;如需选择平台,请使用 `--targets=node24-linux-x64,node24-linux-arm64,node24-macos-arm64,node24-win-x64`。每个目标都应在其原生架构上构建。产物写入 `dist-exe/`,脚本会将所选载体同步到 `python/sdk-runtime/`。Windows 会生成 `.exe` 与 `-rg.exe`;macOS 构建还会同步 `node-pty` 所需的配套 spawn 辅助程序。
+所需 `lib/` 产物已存在时使用 `--skip-build`;如需选择平台,请使用 `--targets=node24-linux-x64,node24-linux-arm64,node24-macos-arm64,node24-macos-x64,node24-win-x64`。每个目标都应在其原生架构上构建。产物写入 `dist-exe/`,脚本会将所选载体同步到 `python/sdk-runtime/`。Windows 会生成 `.exe` 与 `-rg.exe`;macOS 构建还会同步 `node-pty` 所需的配套 spawn 辅助程序。
 
 ## 验证 SDK
 
@@ -79,11 +79,11 @@ pip install \
   "dist-python/deepseek_harness_runtime_bin-$version-py3-none-macosx_14_0_arm64.whl"
 ```
 
-运行时分发包仅提供 wheel 包。发布流水线会连同纯 SDK wheel 包一起发布个平台 wheel 包:Linux x64、Linux arm64、macOS 14 或更高版本的 arm64,以及 Windows x64(`win_amd64`)。只有与仓库版本匹配时,才接受 `python-v<repository-version>` 标签;`0.0.1-rc.1` 之类的仓库预发布版本在 wheel 包文件名和元数据中使用规范化的 PEP 440 写法,例如 `0.0.1rc1`。
+运行时分发包仅提供 wheel 包。发布流水线会连同纯 SDK wheel 包一起发布个平台 wheel 包:Linux x64、Linux arm64、macOS 14 或更高版本的 arm64 与 x64,以及 Windows x64(`win_amd64`)。只有与仓库版本匹配时,才接受 `python-v<repository-version>` 标签;`0.0.1-rc.1` 之类的仓库预发布版本在 wheel 包文件名和元数据中使用规范化的 PEP 440 写法,例如 `0.0.1rc1`。
 
 ## 验证候选发行版
 
-手动运行 GitHub 的 `Release (Python)` 工作流并设置 `publish=false`,即可构建全部个 wheel 包,在 Python 3.10 和 3.14 上安装 Linux 发行集合,检查精确文件名和元数据,执行 PyPI 默认单文件大小限制,并保留一份带 SHA-256 哈希的汇总产物。该运行没有注册表凭据,dry-run 运行无法进入任何发布作业。
+手动运行 GitHub 的 `Release (Python)` 工作流并设置 `publish=false`,即可构建全部个 wheel 包,在 Python 3.10 和 3.14 上安装 Linux 发行集合,检查精确文件名和元数据,执行 PyPI 默认单文件大小限制,并保留一份带 SHA-256 哈希的汇总产物。该运行没有注册表凭据,dry-run 运行无法进入任何发布作业。
 
 公开发布从私有自动化仓库运行。包元数据指向独立的只读公开源码镜像,该镜像不运行发布 Actions。私有仓库把仓库变量 `PYPI_PUBLISHER_REPOSITORY` 定义为自身的 `owner/name`,并且只在有意发布期间把 `PUBLIC_PYPI_RELEASE_ENABLED` 从 `false` 改为 `true`。
 

+ 2 - 2
python/sdk-runtime/README.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write python/sdk-runtime/README.md
-README.md: 28695259928a7edc6e6cf67e737f1012729df5a4
-README.zh.md: f23b253cfe47d9f1ae24568b51d9db810c7a4a9f
+README.md: 050ae85d9b0a38b82a3c66a84c3d8f34e137be6c
+README.zh.md: 7066d7224752294c25b58cfe8fb6a94524a2013d

+ 1 - 1
python/sdk-runtime/README.md

@@ -8,7 +8,7 @@ Platform runtime wheel for the DeepSeek Harness Python SDK. It packages the norm
 
 The wheel installs a `dsh` console command and the `deepseek_harness_runtime` Python module. `dsh` forwards its arguments to the bundled executable and requires a non-empty `DSH_HOME`; it never falls back to `~/.dsh`.
 
-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, and Windows x64. The wheel tag and payload must match exactly; no Windows arm64 wheel is published.
+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.
 
 Repository builds also materialize a dev-only `runtime/node/` carrier. It runs `node runtime/node/node_modules/@deepseek-ai/dsh/lib/bin.js` on system Node 22.19 or newer. It is never selected automatically and is excluded from wheels and sdists.
 

+ 1 - 1
python/sdk-runtime/README.zh.md

@@ -8,7 +8,7 @@ DeepSeek Harness Python SDK 的平台运行时 wheel。它把普通 `dsh` CLI 
 
 Wheel 会安装 `dsh` 控制台命令和 `deepseek_harness_runtime` Python 模块。`dsh` 将参数转发给内置可执行程序,并要求非空 `DSH_HOME`;它不会回退到 `~/.dsh`。
 
-生产可执行程序位于模块的 `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 与 Windows x64。Wheel tag 必须与载荷严格匹配;不发布 Windows arm64 wheel。
+生产可执行程序位于模块的 `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 tag 必须与载荷严格匹配;不发布 Windows arm64 wheel。
 
 仓库构建还会物化仅限开发的 `runtime/node/` 载体。它在系统 Node 22.19 或更高版本上运行 `node runtime/node/node_modules/@deepseek-ai/dsh/lib/bin.js`。系统不会自动选择它,而且 wheel 与 sdist 均不包含它。
 

+ 4 - 0
python/sdk-runtime/platforms.json

@@ -11,6 +11,10 @@
     "tag": "macosx_14_0_arm64",
     "executable": "deepseek-harness-sdk-runtime-macos-arm64"
   },
+  "macos-x64": {
+    "tag": "macosx_14_0_x86_64",
+    "executable": "deepseek-harness-sdk-runtime-macos-x64"
+  },
   "win-x64": {
     "tag": "win_amd64",
     "executable": "deepseek-harness-sdk-runtime-win-x64.exe"

+ 1 - 2
python/sdk-runtime/src/deepseek_harness_runtime/__init__.py

@@ -120,12 +120,11 @@ def _current_platform_tag() -> str:
         plat is None
         or arch is None
         or (plat == "win" and arch != "x64")
-        or (plat == "macos" and arch != "arm64")
     ):
         raise FileNotFoundError(
             "no bundled DeepSeek Harness SDK runtime exists for this platform "
             f"(sys.platform={sys.platform!r}, machine={platform.machine()!r}); supported: "
-            "Linux x64/arm64, macOS arm64, and Windows x64. " + _EXE_ACQUISITION_HINT
+            "Linux x64/arm64, macOS x64/arm64, and Windows x64. " + _EXE_ACQUISITION_HINT
         )
     return f"{plat}-{arch}"
 

+ 30 - 1
python/sdk/tests/test_macos_deployment_target.py

@@ -16,8 +16,15 @@ checker = SimpleNamespace(**runpy.run_path(str(SCRIPT)))
 
 def test_otool_parser_uses_the_newest_macho_slice() -> None:
     output = """
+Load command 8
+      cmd LC_VERSION_MIN_MACOSX
+  cmdsize 16
+  version 10.7
+      sdk 11.1
+Load command 9
       cmd LC_BUILD_VERSION
     minos 11.0
+Load command 10
       cmd LC_BUILD_VERSION
     minos 13.5
     """
@@ -26,12 +33,34 @@ def test_otool_parser_uses_the_newest_macho_slice() -> None:
 
 
 def test_otool_parser_requires_a_deployment_target() -> None:
-    with pytest.raises(ValueError, match="contains no LC_BUILD_VERSION"):
+    with pytest.raises(ValueError, match="contains no macOS deployment target"):
         checker.parse_otool_deployment_target("Load command 0\n")
 
 
+def test_otool_parser_ignores_unrelated_version_fields() -> None:
+    output = """
+Load command 1
+          cmd LC_ID_DYLIB
+      cmdsize 48
+      current version 14.1.0
+compatibility version 1.0.0
+    """
+
+    with pytest.raises(ValueError, match="contains no macOS deployment target"):
+        checker.parse_otool_deployment_target(output)
+
+
 def test_wheel_tag_rejects_a_newer_executable_target() -> None:
     checker.ensure_compatible(Path("runtime"), (13, 5), "macosx_14_0_arm64")
+    checker.ensure_compatible(Path("runtime-x64"), (10, 7), "macosx_14_0_x86_64")
 
     with pytest.raises(RuntimeError, match="requires macOS 14.1"):
         checker.ensure_compatible(Path("spawn-helper"), (14, 1), "macosx_14_0_arm64")
+
+
+def test_wheel_tag_accepts_only_supported_macos_architectures() -> None:
+    assert checker.claimed_version("macosx_14_0_arm64") == (14, 0)
+    assert checker.claimed_version("macosx_14_0_x86_64") == (14, 0)
+
+    with pytest.raises(ValueError, match="unsupported macOS wheel platform tag"):
+        checker.claimed_version("macosx_14_0_universal2")

+ 3 - 1
python/sdk/tests/test_release_version.py

@@ -60,6 +60,8 @@ def test_pep440_version_spells_a_prerelease_the_python_way() -> None:
 def test_macos_wheel_tag_does_not_claim_unsupported_node_platforms() -> None:
     assert build_python_release.PLATFORMS["macos-arm64"][0] == "macosx_14_0_arm64"
     assert build_python_release.PLATFORMS["macos-arm64"][1] == "deepseek-harness-sdk-runtime-macos-arm64"
+    assert build_python_release.PLATFORMS["macos-x64"][0] == "macosx_14_0_x86_64"
+    assert build_python_release.PLATFORMS["macos-x64"][1] == "deepseek-harness-sdk-runtime-macos-x64"
 
 
 def test_windows_wheel_tag_and_payload_are_x64_only() -> None:
@@ -95,7 +97,7 @@ def test_stage_sdk_keeps_distribution_module_and_runtime_pin_distinct(tmp_path:
 
 @pytest.mark.parametrize(
     ("target", "with_helper"),
-    [("linux-x64", False), ("macos-arm64", True), ("win-x64.exe", False)],
+    [("linux-x64", False), ("macos-arm64", True), ("macos-x64", True), ("win-x64.exe", False)],
 )
 def test_stage_runtime_copies_platform_payload(
     tmp_path: Path, target: str, with_helper: bool

+ 2 - 3
python/sdk/tests/test_runtime_resolution.py

@@ -79,12 +79,11 @@ def test_current_platform_supports_windows_x64_only(monkeypatch: pytest.MonkeyPa
         runtime._current_platform_tag()
 
 
-def test_current_platform_rejects_macos_x64(monkeypatch: pytest.MonkeyPatch) -> None:
+def test_current_platform_supports_macos_x64(monkeypatch: pytest.MonkeyPatch) -> None:
     monkeypatch.setattr(runtime.sys, "platform", "darwin")
     monkeypatch.setattr(runtime.platform, "machine", lambda: "x86_64")
 
-    with pytest.raises(FileNotFoundError, match="macOS arm64"):
-        runtime._current_platform_tag()
+    assert runtime._current_platform_tag() == "macos-x64"
 
 
 def test_runtime_requires_ripgrep_sidecar(

+ 13 - 0
scripts/build-exe-for-python-sdk.spec.ts

@@ -59,6 +59,19 @@ describe('Python runtime executable builder CLI', () => {
     expect(result.stdout).not.toMatch(/pnpm\.cmd/i)
   })
 
+  it('accepts the macOS x64 pkg target', () => {
+    const result = run(
+      { npm_execpath: 'C:\\tools\\pnpm.cjs' },
+      '--skip-build',
+      '--dry-run',
+      '--targets=node24-macos-x64',
+    )
+
+    expect(result.status).toBe(0)
+    expect(result.stdout).toContain('exec pkg')
+    expect(result.stdout).toContain('--sea --targets node24-macos-x64')
+  })
+
   it('rejects a Windows arm64 product before any build step', () => {
     const result = run(
       { npm_execpath: 'C:\\tools\\pnpm.cjs' },

+ 1 - 1
scripts/build-exe-for-python-sdk.ts

@@ -207,7 +207,7 @@ class BuildCli {
     return [
       'Usage: pnpm exec tsx scripts/build-exe-for-python-sdk.ts [flags]',
       '',
-      '  --targets=<t1,t2,...>  pkg targets, e.g. node24-linux-x64,node24-linux-arm64,node24-macos-arm64,node24-win-x64.',
+      '  --targets=<t1,t2,...>  pkg targets, e.g. node24-linux-x64,node24-linux-arm64,node24-macos-arm64,node24-macos-x64,node24-win-x64.',
       '                         Default: the host platform only (on node24).',
       '  --skip-build           skip `pnpm run build` (lib/ artifacts must already exist).',
       '  --dry-run              print every command and config patch without executing.',

+ 26 - 10
scripts/check-macos-deployment-target.py

@@ -12,7 +12,11 @@ from pathlib import Path
 
 ROOT = Path(__file__).resolve().parents[1]
 RELEASE = runpy.run_path(str(ROOT / "scripts" / "build-python-release.py"))
-MACOS_PLATFORM_TAG = RELEASE["PLATFORMS"]["macos-arm64"][0]
+MACOS_PLATFORMS = {
+    name: details[0]
+    for name, details in RELEASE["PLATFORMS"].items()
+    if name.startswith("macos-")
+}
 
 
 def parse_version(value: str) -> tuple[int, ...]:
@@ -24,7 +28,7 @@ def parse_version(value: str) -> tuple[int, ...]:
 
 def claimed_version(platform_tag: str) -> tuple[int, ...]:
     """Return the minimum macOS version encoded by a wheel platform tag."""
-    match = re.fullmatch(r"macosx_(\d+)_(\d+)_arm64", platform_tag)
+    match = re.fullmatch(r"macosx_(\d+)_(\d+)_(?:arm64|x86_64)", platform_tag)
     if match is None:
         raise ValueError(f"unsupported macOS wheel platform tag: {platform_tag!r}")
     return int(match.group(1)), int(match.group(2))
@@ -32,12 +36,22 @@ def claimed_version(platform_tag: str) -> tuple[int, ...]:
 
 def parse_otool_deployment_target(output: str) -> tuple[int, ...]:
     """Return the newest deployment target from one or more Mach-O slices."""
-    versions = [
-        parse_version(match.group(1))
-        for match in re.finditer(r"^\s*minos\s+(\d+(?:\.\d+)*)\s*$", output, re.MULTILINE)
-    ]
+    versions: list[tuple[int, ...]] = []
+    command: str | None = None
+    for line in output.splitlines():
+        stripped = line.strip()
+        if re.fullmatch(r"Load command \d+", stripped):
+            command = None
+        elif stripped == "cmd LC_BUILD_VERSION":
+            command = "build"
+        elif stripped == "cmd LC_VERSION_MIN_MACOSX":
+            command = "minimum"
+        elif command == "build" and (match := re.fullmatch(r"minos\s+(\d+(?:\.\d+)*)", stripped)):
+            versions.append(parse_version(match.group(1)))
+        elif command == "minimum" and (match := re.fullmatch(r"version\s+(\d+(?:\.\d+)*)", stripped)):
+            versions.append(parse_version(match.group(1)))
     if not versions:
-        raise ValueError("otool output contains no LC_BUILD_VERSION deployment target")
+        raise ValueError("otool output contains no macOS deployment target load command")
     return max(versions)
 
 
@@ -73,7 +87,7 @@ def ensure_compatible(
 
 
 def validate_deployment_targets(
-    executables: list[Path], platform_tag: str = MACOS_PLATFORM_TAG
+    executables: list[Path], platform_tag: str
 ) -> list[tuple[Path, tuple[int, ...]]]:
     """Validate every executable and return its measured deployment target."""
     measured = [(executable, deployment_target(executable)) for executable in executables]
@@ -84,11 +98,13 @@ def validate_deployment_targets(
 
 def main() -> None:
     parser = argparse.ArgumentParser(description=__doc__)
+    parser.add_argument("--platform", choices=tuple(MACOS_PLATFORMS), required=True)
     parser.add_argument("executables", type=Path, nargs="+")
     args = parser.parse_args()
-    for executable, version in validate_deployment_targets(args.executables):
+    platform_tag = MACOS_PLATFORMS[args.platform]
+    for executable, version in validate_deployment_targets(args.executables, platform_tag):
         rendered = ".".join(str(part) for part in version)
-        print(f"{executable}: macOS {rendered} <= {MACOS_PLATFORM_TAG}")
+        print(f"{executable}: macOS {rendered} <= {platform_tag}")
 
 
 if __name__ == "__main__":

+ 29 - 8
scripts/ci-workflow.spec.ts

@@ -370,7 +370,7 @@ describe('CI workflow', () => {
       name: 'python runtime / release-shaped matrix',
       uses: './.github/workflows/build-exe-for-python-sdk.yml',
       with: {
-        targets: 'node24-linux-x64,node24-linux-arm64,node24-macos-arm64,node24-win-x64',
+        targets: 'node24-linux-x64,node24-linux-arm64,node24-macos-arm64,node24-macos-x64,node24-win-x64',
         ci: true,
       },
       secrets: {
@@ -462,7 +462,7 @@ describe('Python release workflows', () => {
     expect(build).toMatchObject({
       uses: './.github/workflows/build-exe-for-python-sdk.yml',
       with: {
-        targets: 'node24-linux-x64,node24-linux-arm64,node24-macos-arm64,node24-win-x64',
+        targets: 'node24-linux-x64,node24-linux-arm64,node24-macos-arm64,node24-macos-x64,node24-win-x64',
         release: true,
       },
     })
@@ -531,7 +531,7 @@ describe('Python release workflows', () => {
 
     const buildSteps: unknown[] = build.steps
     const manylinuxAddon = buildSteps.find(step => isRecord(step) && step.name === 'Rebuild Linux node-pty against manylinux 2.28')
-    const macosCheck = buildSteps.find(step => isRecord(step) && step.name === 'Check macOS deployment target')
+    const macosCheck = buildSteps.find(step => isRecord(step) && step.name === 'Check macOS payload architecture and deployment target')
     const manylinuxSmoke = buildSteps.find(step => isRecord(step) && step.name === 'Run wheel in a manylinux 2.28 container')
     const cleanVenvPosix = buildSteps.find(step => isRecord(step) && step.name === 'Install local SDK and runtime wheels into a clean venv (POSIX)')
     const cleanVenvWindows = buildSteps.find(step => isRecord(step) && step.name === 'Install local SDK and runtime wheels into a clean venv (Windows)')
@@ -541,7 +541,8 @@ describe('Python release workflows', () => {
     const realApiPreflightWindows = buildSteps.find(step => isRecord(step) && step.name === 'Preflight installed-wheel real API test (Windows)')
     const installedRealApiPosix = buildSteps.find(step => isRecord(step) && step.name === 'Run installed-wheel real API black-box test (POSIX)')
     const installedRealApiWindows = buildSteps.find(step => isRecord(step) && step.name === 'Run installed-wheel real API black-box test (Windows)')
-    if (!isRecord(cleanVenvPosix) || !isRecord(cleanVenvWindows)
+    if (!isRecord(macosCheck) || typeof macosCheck.run !== 'string'
+      || !isRecord(cleanVenvPosix) || !isRecord(cleanVenvWindows)
       || !isRecord(installedKeylessPosix) || !isRecord(installedKeylessWindows)
       || !isRecord(realApiPreflightPosix) || !isRecord(realApiPreflightWindows)
       || !isRecord(installedRealApiPosix) || !isRecord(installedRealApiWindows)) {
@@ -564,6 +565,9 @@ describe('Python release workflows', () => {
     expect(JSON.stringify(plan.steps)).toContain('pep440_version')
     const workflowJson = JSON.stringify(workflow)
     expect(workflowJson).toContain('macosx_14_0_arm64')
+    expect(workflowJson).toContain('macosx_14_0_x86_64')
+    expect(workflowJson).toContain('node24-macos-x64')
+    expect(workflowJson).toContain('macos-15-intel')
     expect(workflowJson).toContain('win_amd64')
     expect(workflowJson).toContain('node24-win-x64')
     expect(workflowJson).toContain('windows-2025')
@@ -583,8 +587,10 @@ describe('Python release workflows', () => {
     expect(JSON.stringify(manylinuxAddon)).toContain('node-pty-glibc-versions.txt')
     expect(JSON.stringify(manylinuxAddon)).toContain('le 2.28')
     expect(macosCheck).toMatchObject({ if: "runner.os == 'macOS'" })
-    expect(JSON.stringify(macosCheck)).toContain('scripts/check-macos-deployment-target.py')
-    expect(JSON.stringify(macosCheck)).toContain('$EXE-spawn-helper')
+    expect(macosCheck.run).toContain('scripts/check-macos-deployment-target.py')
+    expect(macosCheck.run).toContain('lipo "$payload" -verify_arch')
+    expect(macosCheck.run).toContain('$EXE-rg')
+    expect(macosCheck.run).toContain('$EXE-spawn-helper')
     expect(JSON.stringify(installedKeylessPosix)).toContain('--scenario all')
     expect(JSON.stringify(installedKeylessPosix)).toContain('env -u PYTHONPATH')
     expect(JSON.stringify(installedKeylessWindows)).toContain('--scenario all --installed-wheel')
@@ -620,14 +626,29 @@ describe('Python release workflows', () => {
     }
     const runtimeScript: unknown[] = runtimeWheel.script
     const macosCheck = runtimeScript.find(
-      step => typeof step === 'string' && step.includes('PLATFORM" = macos-arm64'),
+      step => typeof step === 'string' && step.includes('${PLATFORM#macos-}'),
     )
     if (typeof macosCheck !== 'string') {
       throw new TypeError('GitLab CI must check the macOS deployment target')
     }
 
     expect(macosCheck).toContain('scripts/check-macos-deployment-target.py')
-    expect(macosCheck).toContain('"$EXE" "$EXE-spawn-helper"')
+    expect(macosCheck).toContain('lipo "$payload" -verify_arch')
+    expect(macosCheck).toContain('"$EXE" "$EXE-rg" "$EXE-spawn-helper"')
+  })
+
+  it('builds the macOS x64 wheel on the matching GitLab runner', () => {
+    const workflow = loadWorkflow('.gitlab-ci.yml')
+    const macosX64 = workflow['runtime-macos-x64']
+    const publish = workflow['publish-python']
+    if (!isRecord(macosX64) || !isRecord(publish) || !Array.isArray(publish.needs)) {
+      throw new TypeError('GitLab CI must define the macOS x64 runtime and publication jobs')
+    }
+
+    expect(macosX64.tags).toEqual(['macos-x64'])
+    expect(macosX64.variables).toMatchObject({ PKG_TARGET: 'node24-macos-x64', PLATFORM: 'macos-x64' })
+    expect(publish.needs).toContainEqual({ job: 'runtime-macos-x64', artifacts: true })
+    expect(JSON.stringify(publish.script)).toContain('macosx_14_0_x86_64.whl')
   })
 
   it('builds and black-box tests the Windows x64 wheel in GitLab', () => {

+ 4 - 3
scripts/verify-runtime-closure.spec.ts

@@ -21,6 +21,7 @@ const platforms = {
   'linux-x64': { tag: 'manylinux_2_28_x86_64', executable: 'runtime-linux-x64' },
   'linux-arm64': { tag: 'manylinux_2_28_aarch64', executable: 'runtime-linux-arm64' },
   'macos-arm64': { tag: 'macosx_14_0_arm64', executable: 'runtime-macos-arm64' },
+  'macos-x64': { tag: 'macosx_14_0_x86_64', executable: 'runtime-macos-x64' },
   'win-x64': { tag: 'win_amd64', executable: 'runtime-win-x64.exe' },
 }
 
@@ -64,7 +65,7 @@ describe('verifyRuntimeClosure', () => {
     expect(result.presetCount).toBe(1)
     expect(result.failures).toEqual([
       'standard preset -> @scope/linux (linux-arm64, linux-x64)',
-      'standard preset -> @scope/macos (macos-arm64)',
+      'standard preset -> @scope/macos (macos-arm64, macos-x64)',
       'standard preset -> @scope/windows (win-x64)',
     ])
   })
@@ -83,7 +84,7 @@ describe('verifyRuntimeClosure', () => {
     const result = await verifyRuntimeClosure(root)
 
     expect(result.failures).toEqual([
-      'standard preset -> @scope/conditional (linux-arm64, linux-x64, macos-arm64, win-x64)',
+      'standard preset -> @scope/conditional (linux-arm64, linux-x64, macos-arm64, macos-x64, win-x64)',
     ])
   })
 
@@ -117,7 +118,7 @@ describe('verifyRuntimeClosure', () => {
     const result = await verifyRuntimeClosure(root)
 
     expect(result.failures).toEqual([
-      'standard preset -> @scope/plugin [runtime dependency is "1.2.3"; expected workspace:] (linux-arm64, linux-x64, macos-arm64, win-x64)',
+      'standard preset -> @scope/plugin [runtime dependency is "1.2.3"; expected workspace:] (linux-arm64, linux-x64, macos-arm64, macos-x64, win-x64)',
     ])
   })
 

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません