python: derive release version from repository

This commit is contained in:
Yichen Jiang
2026-07-13 17:49:01 +08:00
parent ef2754110a
commit fe3777cf27
16 changed files with 149 additions and 55 deletions

View File

@@ -61,7 +61,21 @@ jobs:
timeout-minutes: 5
outputs:
matrix: ${{ steps.plan.outputs.matrix }}
version: ${{ steps.version.outputs.version }}
steps:
- uses: actions/checkout@v6
- name: Resolve repository version
id: version
run: |
set -euo pipefail
version="$(jq -r '.version // empty' package.json)"
[[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || {
echo "::error::package.json version must be stable X.Y.Z, got '$version'"
exit 1
}
echo "version=$version" >> "$GITHUB_OUTPUT"
- name: Compute matrix from targets input
id: plan
env:
@@ -99,7 +113,7 @@ jobs:
sdk-wheel:
needs: plan
name: deepseek_harness-0.0.0-py3-none-any.whl
name: deepseek_harness-${{ needs.plan.outputs.version }}-py3-none-any.whl
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
@@ -116,13 +130,12 @@ jobs:
run: >-
python scripts/build-python-release.py
--package sdk
--tag python-v0.0.0
--output-dir dist-python
- uses: actions/upload-artifact@v6
with:
name: deepseek_harness-0.0.0-py3-none-any.whl
path: dist-python/deepseek_harness-0.0.0-py3-none-any.whl
name: deepseek_harness-${{ needs.plan.outputs.version }}-py3-none-any.whl
path: dist-python/deepseek_harness-${{ needs.plan.outputs.version }}-py3-none-any.whl
if-no-files-found: error
build:
@@ -189,15 +202,16 @@ jobs:
id: runtime
env:
TARGET: ${{ matrix.target }}
VERSION: ${{ needs.plan.outputs.version }}
run: |
set -euo pipefail
platform="${TARGET#node24-}"
exe="$PWD/dist-exe/dsh-jsonrpc-agent-pkg-$platform"
[ -x "$exe" ] || { echo "::error::$exe missing or not executable"; exit 1; }
case "$platform" in
linux-x64) wheel=deepseek_harness_runtime_bin-0.0.0-py3-none-manylinux_2_28_x86_64.whl ;;
linux-arm64) wheel=deepseek_harness_runtime_bin-0.0.0-py3-none-manylinux_2_28_aarch64.whl ;;
macos-arm64) wheel=deepseek_harness_runtime_bin-0.0.0-py3-none-macosx_11_0_arm64.whl ;;
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_11_0_arm64.whl ;;
*) echo "::error::Unsupported runtime platform $platform"; exit 1 ;;
esac
echo "platform=$platform" >> "$GITHUB_OUTPUT"
@@ -215,23 +229,24 @@ jobs:
run: >-
python scripts/build-python-release.py
--package runtime
--tag python-v0.0.0
--platform "${{ steps.runtime.outputs.platform }}"
--runtime-exe "${{ steps.runtime.outputs.exe }}"
--output-dir dist-python
- uses: actions/download-artifact@v8
with:
name: deepseek_harness-0.0.0-py3-none-any.whl
name: deepseek_harness-${{ needs.plan.outputs.version }}-py3-none-any.whl
path: dist-python
- name: Install only the SDK into a clean venv and run zero-config
env:
VERSION: ${{ needs.plan.outputs.version }}
run: |
set -euo pipefail
python -m venv "$RUNNER_TEMP/dsh-sdk-smoke"
"$RUNNER_TEMP/dsh-sdk-smoke/bin/python" -m pip install \
--find-links dist-python \
deepseek-harness==0.0.0
deepseek-harness=="$VERSION"
"$RUNNER_TEMP/dsh-sdk-smoke/bin/python" scripts/smoke-python-runtime.py \
--scenario sdk-default
@@ -251,6 +266,7 @@ jobs:
if: runner.os == 'Linux'
env:
RUNNER_ARCH: ${{ runner.arch }}
VERSION: ${{ needs.plan.outputs.version }}
run: |
set -euo pipefail
case "$RUNNER_ARCH" in
@@ -258,9 +274,9 @@ jobs:
ARM64) image=quay.io/pypa/manylinux_2_28_aarch64 ;;
*) echo "::error::Unsupported Linux runner architecture $RUNNER_ARCH"; exit 1 ;;
esac
docker run --rm -v "$PWD:/work" -w /work "$image" bash -euxo pipefail -c '
docker run --rm -e VERSION -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/dist-python deepseek-harness==0.0.0
/tmp/dsh-sdk/bin/python -m pip install --find-links /work/dist-python deepseek-harness=="$VERSION"
/tmp/dsh-sdk/bin/python /work/scripts/smoke-python-runtime.py --scenario sdk-default
'

View File

@@ -15,6 +15,8 @@ variables:
before_script:
- python3 -m venv .ci-python
- . .ci-python/bin/activate
- export DSH_VERSION="$(python -c 'import json; print(json.load(open("package.json"))["version"])')"
- 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 uv==0.11.23
sdk-wheel:
@@ -40,7 +42,7 @@ sdk-wheel:
- uv run --python 3.10 --group test --project python/sdk python scripts/smoke-python-runtime.py --scenario all --exe "$EXE"
- python scripts/build-python-release.py --package runtime --tag "$CI_COMMIT_TAG" --platform "$PLATFORM" --runtime-exe "$EXE" --output-dir "release/$PLATFORM"
- python -m venv .wheel-smoke
- .wheel-smoke/bin/python -m pip install --find-links "release/$PLATFORM" --find-links release/sdk deepseek-harness=="${CI_COMMIT_TAG#python-v}"
- .wheel-smoke/bin/python -m pip install --find-links "release/$PLATFORM" --find-links release/sdk deepseek-harness=="$DSH_VERSION"
- .wheel-smoke/bin/python scripts/smoke-python-runtime.py --scenario sdk-default
- |
if [ "${PLATFORM#linux-}" != "$PLATFORM" ]; then
@@ -53,7 +55,7 @@ sdk-wheel:
linux-arm64) image=quay.io/pypa/manylinux_2_28_aarch64 ;;
*) echo "Unsupported Linux platform $PLATFORM"; exit 1 ;;
esac
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==${CI_COMMIT_TAG#python-v} && /tmp/dsh-sdk/bin/python /work/scripts/smoke-python-runtime.py --scenario sdk-default"
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==$DSH_VERSION && /tmp/dsh-sdk/bin/python /work/scripts/smoke-python-runtime.py --scenario sdk-default"
fi
artifacts:
paths: [release/$PLATFORM/*.whl]
@@ -105,14 +107,15 @@ publish-python:
before_script:
- python3 -m venv .ci-python
- . .ci-python/bin/activate
- export DSH_VERSION="$(python -c 'import json; print(json.load(open("package.json"))["version"])')"
- 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:
- version="${CI_COMMIT_TAG#python-v}"
- test "$(find release -name '*.whl' | wc -l | tr -d ' ')" = 4
- test -f "release/sdk/deepseek_harness-${version}-py3-none-any.whl"
- test -f "release/linux-x64/deepseek_harness_runtime_bin-${version}-py3-none-manylinux_2_28_x86_64.whl"
- test -f "release/linux-arm64/deepseek_harness_runtime_bin-${version}-py3-none-manylinux_2_28_aarch64.whl"
- test -f "release/macos-arm64/deepseek_harness_runtime_bin-${version}-py3-none-macosx_11_0_arm64.whl"
- test -f "release/sdk/deepseek_harness-${DSH_VERSION}-py3-none-any.whl"
- test -f "release/linux-x64/deepseek_harness_runtime_bin-${DSH_VERSION}-py3-none-manylinux_2_28_x86_64.whl"
- test -f "release/linux-arm64/deepseek_harness_runtime_bin-${DSH_VERSION}-py3-none-manylinux_2_28_aarch64.whl"
- test -f "release/macos-arm64/deepseek_harness_runtime_bin-${DSH_VERSION}-py3-none-macosx_11_0_arm64.whl"
- python -m twine check release/*/*.whl
- export TWINE_USERNAME=gitlab-ci-token
- export TWINE_PASSWORD="$CI_JOB_TOKEN"

View File

@@ -18,7 +18,7 @@ packages/<group>/<pkg>/
Choose an existing group when one matches the package's role (`core`, `llm`, `bash`, `compact`, `subagent`, `todo`, `session-persistence`, `ui`, `util`, or `support`). A new group is allowed, but it is a pure container: no `package.json`, no source files, and packages still sit exactly one level below it.
package.json invariants (enforced by `pnpm run constraints` / `scripts/check-workspace-constraints.ts`): `private: true`, `version: 0.0.1`, `type: module`, `main: "lib/index.js"`, `types: "lib/types/index.d.ts"`, `exports["."].types: "./lib/types/index.d.ts"`, `exports["."].default: "./lib/index.js"`, `cordis` in BOTH peerDependencies and devDependencies (same range). Mirror every dsh peer dependency in devDependencies. `schemastery` goes in `dependencies` (it is a runtime validator), matching agent-loop. The `files` list is precise: `lib/index.js`, `lib/types/**/*.d.ts`, `lib/types/**/*.d.ts.map`, and `src`; do not publish `lib/types` JS or JS-map intermediates or stale root declaration files. CLI app packages with a package `bin` include `lib/bin.js` immediately after `lib/index.js` in `files`.
package.json invariants (enforced by `pnpm run constraints` / `scripts/check-workspace-constraints.ts`): `private: true`, a `version` matching the root `package.json`, `type: module`, `main: "lib/index.js"`, `types: "lib/types/index.d.ts"`, `exports["."].types: "./lib/types/index.d.ts"`, `exports["."].default: "./lib/index.js"`, `cordis` in BOTH peerDependencies and devDependencies (same range). Mirror every dsh peer dependency in devDependencies. `schemastery` goes in `dependencies` (it is a runtime validator), matching agent-loop. The `files` list is precise: `lib/index.js`, `lib/types/**/*.d.ts`, `lib/types/**/*.d.ts.map`, and `src`; do not publish `lib/types` JS or JS-map intermediates or stale root declaration files. CLI app packages with a package `bin` include `lib/bin.js` immediately after `lib/index.js` in `files`.
In-package relative imports use explicit `.ts` specifiers in source (for example, `export * from './types.ts'`). The compiler rewrites those to `.js` in emitted JS and leaves explicit `.ts` specifiers in declarations, which standard NodeNext/Node16 TypeScript consumers resolve to the sibling `.d.ts` files.

View File

@@ -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
2026-07-10-single-file-executable-sdk-runtime-distribution.md: 474a7af7f2cfe9177dec779e3eb50fd6034e806d
2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md: 1f436d63fc91420c6da6d92e32bd16d5936ced5d
2026-07-10-single-file-executable-sdk-runtime-distribution.md: 08fd6f530d1f564ca169a168c0f7d77bff6bf29f
2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md: 050e4f9a6e8bb03e59835f2c06dd3c701aa34eed

View File

@@ -42,13 +42,13 @@ The deploy root is [`python/sdk-runtime/package.json`](../../../../python/sdk-ru
[`scripts/build-exe-for-python-sdk.ts`](../../../../scripts/build-exe-for-python-sdk.ts): runtime closure verification → `pnpm run build` → (after clearing) `pnpm --filter dsh-jsonrpc-agent-pkg deploy --legacy --prod --config.node-linker=hoisted --config.auto-install-peers=false --config.link-workspace-packages=true` **directly into** `python/sdk-runtime/src/deepseek_harness_runtime/runtime/node/` → inject the pkg configuration (`bin` points at `node_modules/@deepseek-ai/dsh-jsonrpc-agent/lib/bin.js` inside the closure, `assets` is a full glob — dynamic import is invisible to pkg's static analysis, so everything must be packed in explicitly) → one `pkg --sea` per target → the executables `dsh-jsonrpc-agent-pkg-<platform>-<arch>` land in `dist-exe/` and are copied back into the runtime directory. CI treats them as intermediate test inputs and retains their platform wheels. All four deploy flags are grounded in measurement: `--legacy` is the mandatory path with inject-workspace-packages off; hoisted yields a zero-symlink file tree (most stable for the pkg VFS, physically guaranteeing a single cordis instance); disabling automatic peer installation keeps unpublished package names from triggering registry resolution; link-workspace-packages points the closure at workspace/vendor sources.
CI: [`.github/workflows/build-exe-for-python-sdk.yml`](../../../../.github/workflows/build-exe-for-python-sdk.yml), triggered explicitly only — `workflow_dispatch`, or the `build-exe` label on a pull request; native builds on the three platforms linux-x64 / linux-arm64 (`ubuntu-24.04-arm`) / macos-arm64, with `~/.pkg-cache` cached; macOS ad-hoc signing is handled by pkg. Each leg drives a mock SSE model through the SDK with the default config and a custom `cordis.yml`, drives the exe directly over NDJSON JSON-RPC, verifies the JSONL and final response, and installs release-shaped wheels into a clean venv without `runtime_bin`; Linux additionally inspects GLIBC requirements and runs in a manylinux 2.28 container. The run retains only four artifacts, each containing one release file: the platform-independent SDK wheel and the three native runtime wheels; bare executables and source bundles remain intermediate test inputs. [`.gitlab-ci.yml`](../../../../.gitlab-ci.yml) accepts only `python-vX.Y.Z` tag pipelines, builds one SDK wheel and three native runtime wheels, then a single serialized job checks and publishes all four to the project PyPI registry. Windows is a non-goal.
CI: [`.github/workflows/build-exe-for-python-sdk.yml`](../../../../.github/workflows/build-exe-for-python-sdk.yml), triggered explicitly only — `workflow_dispatch`, or the `build-exe` label on a pull request; native builds on the three platforms linux-x64 / linux-arm64 (`ubuntu-24.04-arm`) / macos-arm64, with `~/.pkg-cache` cached; macOS ad-hoc signing is handled by pkg. Each leg drives a mock SSE model through the SDK with the default config and a custom `cordis.yml`, drives the exe directly over NDJSON JSON-RPC, verifies the JSONL and final response, and installs release-shaped wheels into a clean venv without `runtime_bin`; Linux additionally inspects GLIBC requirements and runs in a manylinux 2.28 container. The run retains only four artifacts, each containing one release file: the platform-independent SDK wheel and the three native runtime wheels; bare executables and source bundles remain intermediate test inputs. [`.gitlab-ci.yml`](../../../../.gitlab-ci.yml) accepts only `python-vX.Y.Z` tag pipelines whose version matches the root `package.json`, builds one SDK wheel and three native runtime wheels, then a single serialized job checks and publishes all four to the project PyPI registry. Windows is a non-goal.
### Python SDK distribution: two carriers, exe for production, node for development
The Python SDK lives at [`python/`](../../../../python/README.md): `python/sdk` (the client) + `python/sdk-runtime` (the runtime carrier package). The runtime package's data directory holds three kinds of content: the checked-in default `runtime/cordis.yml`, the build-injected platform exe, and the build-injected `runtime/node/` closure tree. `resolve_bundled_launch_args()` automatic resolution **finds the exe only**; the node carrier is enabled only by an explicit `DSH_RUNTIME_MODE=node` (running `runtime/node/node_modules/@deepseek-ai/dsh-jsonrpc-agent/lib/bin.js`, requiring a system node ≥22.19), positioned as the development-verification channel for members of this repo, and does not enter wheel distributions.
[`scripts/build-python-release.py`](../../../../scripts/build-python-release.py) accepts only stable `python-vX.Y.Z` tags and stages both packages at `X.Y.Z`, with the SDK depending exactly on `deepseek-harness-runtime-bin==X.Y.Z`. The SDK is a `py3-none-any` wheel; the wheel-only runtime package contains exactly one exe and uses one of `py3-none-manylinux_2_28_x86_64`, `py3-none-manylinux_2_28_aarch64`, or `py3-none-macosx_11_0_arm64`. Its Hatch hook rejects sdists, universal tags, mixed executable payloads, and unsupported platforms.
[`scripts/build-python-release.py`](../../../../scripts/build-python-release.py) reads the authoritative stable `X.Y.Z` from the repository root `package.json` and stages both packages at that version, with the SDK depending exactly on `deepseek-harness-runtime-bin==X.Y.Z`. An optional `python-vX.Y.Z` release tag is a consistency assertion and is rejected when it differs from the repository version; the source `pyproject.toml` development sentinel never determines a release version. The SDK is a `py3-none-any` wheel; the wheel-only runtime package contains exactly one exe and uses one of `py3-none-manylinux_2_28_x86_64`, `py3-none-manylinux_2_28_aarch64`, or `py3-none-macosx_11_0_arm64`. Its Hatch hook rejects sdists, universal tags, mixed executable payloads, and unsupported platforms.
The exe's "must be explicitly configured" hard semantic is unchanged; the zero-config experience is restored by the wrapper: when the caller gave no `cordis`, named no explicit runtime, and the environment has no `DSH_CORDIS_CONFIG`, the client explicitly injects the checked-in default `cordis.yml` (agent-core + preloaded llm-deepseek + JSONL persistence + bash-local + the `dsh-jsonrpc` serving entry, with `!!js` environment-variable fallbacks) via `DSH_CORDIS_CONFIG`.

View File

@@ -42,13 +42,13 @@ deploy root 是 [`python/sdk-runtime/package.json`](../../../../python/sdk-runti
[`scripts/build-exe-for-python-sdk.ts`](../../../../scripts/build-exe-for-python-sdk.ts)runtime 闭包校验 → `pnpm run build` →(清空后)`pnpm --filter dsh-jsonrpc-agent-pkg deploy --legacy --prod --config.node-linker=hoisted --config.auto-install-peers=false --config.link-workspace-packages=true` **直落** `python/sdk-runtime/src/deepseek_harness_runtime/runtime/node/`→ 注入 pkg 配置(`bin` 指闭包内 `node_modules/@deepseek-ai/dsh-jsonrpc-agent/lib/bin.js``assets` 全量 glob——动态 import 对 pkg 静态分析不可见,必须显式全量打入)→ 每 target 一次 `pkg --sea` → 可执行文件 `dsh-jsonrpc-agent-pkg-<platform>-<arch>``dist-exe/` 并拷回 runtime 目录。CI 把它们作为测试中间输入,只保留对应的平台 wheel。deploy 四 flag 均有实测依据:`--legacy` 是未开 inject-workspace-packages 时的必选路径hoisted 产出零符号链接文件树pkg VFS 最稳、物理保证 cordis 单实例);关 peer 自动安装避免未发布包名触发 registry 解析link-workspace-packages 让闭包指向 workspace/vendor 源。
CI[`.github/workflows/build-exe-for-python-sdk.yml`](../../../../.github/workflows/build-exe-for-python-sdk.yml),仅显式触发——`workflow_dispatch` 手动派发,或给 PR 打 `build-exe` 标签linux-x64 / linux-arm64`ubuntu-24.04-arm`/ macos-arm64 三平台原生构建,并缓存 `~/.pkg-cache`macOS ad-hoc 签名由 pkg 处理。每个平台都以 mock SSE 模型分别通过默认配置和自定义 `cordis.yml` 驱动 SDK再以 NDJSON JSON-RPC 直接驱动 exe校验 JSONL 与最终响应,最后把 release 形态的 wheel 安装到干净 venv 中并在不传 `runtime_bin` 的情况下运行Linux 还检查 GLIBC 依赖并在 manylinux 2.28 容器中运行。整次运行只保留 4 个产物,每个只含一个发布文件:平台无关的 SDK wheel 与 3 个原生 runtime wheel裸 exe 和源码 bundle 只作为测试中间输入。[`.gitlab-ci.yml`](../../../../.gitlab-ci.yml) 只接受 `python-vX.Y.Z` tag 流水线,构建一个 SDK wheel 与 3 个原生 runtime wheel再由单个串行 job 校验并发布这 4 个文件到项目 PyPI 注册表。Windows 是非目标。
CI[`.github/workflows/build-exe-for-python-sdk.yml`](../../../../.github/workflows/build-exe-for-python-sdk.yml),仅显式触发——`workflow_dispatch` 手动派发,或给 PR 打 `build-exe` 标签linux-x64 / linux-arm64`ubuntu-24.04-arm`/ macos-arm64 三平台原生构建,并缓存 `~/.pkg-cache`macOS ad-hoc 签名由 pkg 处理。每个平台都以 mock SSE 模型分别通过默认配置和自定义 `cordis.yml` 驱动 SDK再以 NDJSON JSON-RPC 直接驱动 exe校验 JSONL 与最终响应,最后把 release 形态的 wheel 安装到干净 venv 中并在不传 `runtime_bin` 的情况下运行Linux 还检查 GLIBC 依赖并在 manylinux 2.28 容器中运行。整次运行只保留 4 个产物,每个只含一个发布文件:平台无关的 SDK wheel 与 3 个原生 runtime wheel裸 exe 和源码 bundle 只作为测试中间输入。[`.gitlab-ci.yml`](../../../../.gitlab-ci.yml) 只接受版本与根目录 `package.json` 匹配的 `python-vX.Y.Z` tag 流水线,构建一个 SDK wheel 与 3 个原生 runtime wheel再由单个串行 job 校验并发布这 4 个文件到项目 PyPI 注册表。Windows 是非目标。
### Python SDK 分发双载体exe 为生产、node 为开发
Python SDK 位于 [`python/`](../../../../python/README.md)`python/sdk`(客户端)+ `python/sdk-runtime`运行时载体包。runtime 包数据目录三类内容:检入的默认 `runtime/cordis.yml`、构建注入的平台 exe、构建注入的 `runtime/node/` 闭包树。`resolve_bundled_launch_args()` 自动解析**只找 exe**node 载体仅显式 `DSH_RUNTIME_MODE=node` 启用(跑 `runtime/node/node_modules/@deepseek-ai/dsh-jsonrpc-agent/lib/bin.js`,需系统 node ≥22.19),定位是本仓库成员的开发验证通道,不进 wheel 分发物。
[`scripts/build-python-release.py`](../../../../scripts/build-python-release.py) 只接受稳定的 `python-vX.Y.Z` tag并以 `X.Y.Z` 暂存两个包,同时让 SDK 精确依赖 `deepseek-harness-runtime-bin==X.Y.Z`。SDK 是 `py3-none-any` wheel只提供 wheel 的 runtime 包恰好包含一个 exetag 为 `py3-none-manylinux_2_28_x86_64``py3-none-manylinux_2_28_aarch64``py3-none-macosx_11_0_arm64`。其 Hatch 钩子拒绝 sdist、通用 tag、混合可执行载荷以及不支持的平台。
[`scripts/build-python-release.py`](../../../../scripts/build-python-release.py) 从仓库根目录 `package.json` 读取权威的稳定 `X.Y.Z`,并以该版本暂存两个包,同时让 SDK 精确依赖 `deepseek-harness-runtime-bin==X.Y.Z`可选的 `python-vX.Y.Z` 发布 tag 只是一项一致性断言,与仓库版本不同时会被拒绝;源码 `pyproject.toml` 中的开发占位版本从不决定发布版本。SDK 是 `py3-none-any` wheel只提供 wheel 的 runtime 包恰好包含一个 exetag 为 `py3-none-manylinux_2_28_x86_64``py3-none-manylinux_2_28_aarch64``py3-none-macosx_11_0_arm64`。其 Hatch 钩子拒绝 sdist、通用 tag、混合可执行载荷以及不支持的平台。
exe「必须显式配置」的硬语义不变零配置体验由 wrapper 恢复:调用方没给 `cordis`、没显式指定 runtime、环境无 `DSH_CORDIS_CONFIG` 时,客户端把检入的默认 `cordis.yml`agent-core + 预载 llm-deepseek + JSONL 持久化 + bash-local + `dsh-jsonrpc` serving 条目,`!!js` 环境变量兜底)显式注入 `DSH_CORDIS_CONFIG`

View File

@@ -15,7 +15,7 @@ Adopt **pnpm 11.7.0**, pinned via the `packageManager` field and installed throu
- **Workspaces** move from the `package.json` `workspaces` array + `.yarnrc.yml` to `pnpm-workspace.yaml` (`vendor/*`, `packages/*` — the same globs; `examples/*` stay non-workspace, matching the prior setup and tsdown's explicit globs).
- **Strict symlinked linker** (pnpm's default) replaces Yarn's hoisted `node-modules` linker. We deliberately add **no** `node-linker=hoisted` / `shamefully-hoist` escape hatch: pnpm's non-flat `node_modules` makes phantom dependencies (importing an undeclared transitive dep) fail loudly, which is a *feature* for a repo whose whole quality story is mechanical gates ([mechanical quality gates](2026-06-11-quality-gates.md)). The gate suite — typecheck, lint, test, build, knip — is the safety net that proves no such phantom imports exist.
- **Build-script allowlist.** pnpm 10+ does not run dependency lifecycle scripts unless allowlisted. `pnpm-workspace.yaml` carries an explicit `allowBuilds` map (`esbuild`, `lefthook`, `@google/genai`, `protobufjs`) — the same supply-chain-hardening posture the repo already takes toward model/tool output, now applied to install-time code execution. `peerDependencyRules.allowedVersions.typescript: '>=5 <7'` silences benign peer-range warnings for the in-repo TypeScript.
- **Constraints become package-manager-independent.** `yarn.config.cjs` (which imported `@yarnpkg/types` and used `Yarn.workspaces()` / `workspace.set()`) is replaced by `scripts/check-workspace-constraints.ts`, a plain tsx script run as `pnpm run constraints`. It enforces the identical invariants — every package `private: true`; `@deepseek-ai/dsh-*` packages declare `cordis` as both a peer- and dev-dependency with matching ranges, `version: 0.0.1`, `type: module`; vendored packages checked for privacy only — over the same `vendor` + `packages` scope.
- **Constraints become package-manager-independent.** `yarn.config.cjs` (which imported `@yarnpkg/types` and used `Yarn.workspaces()` / `workspace.set()`) is replaced by `scripts/check-workspace-constraints.ts`, a plain tsx script run as `pnpm run constraints`. It enforces the identical invariants — every package `private: true`; `@deepseek-ai/dsh-*` packages declare `cordis` as both a peer- and dev-dependency with matching ranges, use the root `package.json` version, and set `type: module`; vendored packages checked for privacy only — over the same `vendor` + `packages` scope.
- All `yarn …` verbs across CI, lefthook hooks, `package.json` scripts, and docs become `pnpm …` / `pnpm run …`. `yarn.lock``pnpm-lock.yaml` (lockfile v9). `.gitignore` swaps `.yarn/` for `.pnpm-store/`. Vendored READMEs (e.g. `vendor/cordis/README.md`) keep their upstream `yarn` examples untouched per the Vendoring Policy.
## Alternatives considered

View File

@@ -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
README.md: 2e2e3bff7e6903a1183cc445f590ecb327b92e65
README.zh.md: f14dad4688ed3c54f46ae28475779b0a8d163ae0
README.md: d9403733b7daff9b3e8dbf21e1bc0c68d61b88a7
README.zh.md: 28859e43b7332a8ef4c3cf199b97860447a011a8

View File

@@ -49,15 +49,16 @@ Two flavors, both for repo members:
## Distributing the Python packages
Python releases use stable tags of the form `python-vX.Y.Z`. The common staging script derives both distribution versions from the tag, pins `deepseek-harness-runtime-bin==X.Y.Z` in the SDK metadata, and rejects any other tag form. Build the pure SDK wheel once and one runtime wheel on each native platform:
The root [`package.json`](../package.json) version is authoritative for both Python distributions. The common staging script reads that version, injects it into both wheels, and pins the SDK metadata to the same `deepseek-harness-runtime-bin==X.Y.Z`; an optional `python-vX.Y.Z` release tag is accepted only when it matches the repository version. Build the pure SDK wheel once and one runtime wheel on each native platform:
```sh
python scripts/build-python-release.py --package sdk --tag python-v0.1.0 --output-dir dist-python
python scripts/build-python-release.py --package runtime --tag python-v0.1.0 --platform macos-arm64 --runtime-exe dist-exe/dsh-jsonrpc-agent-pkg-macos-arm64 --output-dir dist-python
pip install --find-links dist-python deepseek-harness==0.1.0
version="$(node -p "require('./package.json').version")"
python scripts/build-python-release.py --package sdk --output-dir dist-python
python scripts/build-python-release.py --package runtime --platform macos-arm64 --runtime-exe dist-exe/dsh-jsonrpc-agent-pkg-macos-arm64 --output-dir dist-python
pip install --find-links dist-python deepseek-harness=="$version"
```
The runtime distribution is wheel-only and rejects sdist builds, missing executables, and mixed-platform payloads. Its three wheel tags are `py3-none-manylinux_2_28_x86_64`, `py3-none-manylinux_2_28_aarch64`, and `py3-none-macosx_11_0_arm64`; the SDK remains `py3-none-any`. A tag pipeline builds these four non-conflicting files and publishes them together, so a normal `pip install deepseek-harness==X.Y.Z` selects the matching runtime wheel and `import deepseek_harness` needs no `runtime_bin`.
The runtime distribution is wheel-only and rejects sdist builds, missing executables, and mixed-platform payloads. Its three wheel tags are `py3-none-manylinux_2_28_x86_64`, `py3-none-manylinux_2_28_aarch64`, and `py3-none-macosx_11_0_arm64`; the SDK remains `py3-none-any`. A matching `python-vX.Y.Z` tag pipeline builds these four non-conflicting files and publishes them together, so a normal `pip install deepseek-harness==X.Y.Z` selects the matching runtime wheel and `import deepseek_harness` needs no `runtime_bin`.
## Zero-config semantics

View File

@@ -49,15 +49,16 @@ print(DeepSeekHarness().run("say hi").final_response) # auto-resolution picks
## 分发 Python 包
Python 发布只使用 `python-vX.Y.Z` 形式的稳定 tag。统一暂存脚本从 tag 推导两个分发物的版本,在 SDK 元数据中钉死 `deepseek-harness-runtime-bin==X.Y.Z`,并拒绝其他 tag 形式。纯 SDK wheel 只构建一次runtime wheel 则在每个原生平台各构建一个:
根目录 [`package.json`](../package.json) 的版本是两个 Python 分发物的权威版本。统一暂存脚本读取这个版本并注入两个 wheel同时在 SDK 元数据中钉死相同版本的 `deepseek-harness-runtime-bin==X.Y.Z`;可选的 `python-vX.Y.Z` 发布 tag 只有与仓库版本匹配时才会被接受。纯 SDK wheel 只构建一次runtime wheel 则在每个原生平台各构建一个:
```sh
python scripts/build-python-release.py --package sdk --tag python-v0.1.0 --output-dir dist-python
python scripts/build-python-release.py --package runtime --tag python-v0.1.0 --platform macos-arm64 --runtime-exe dist-exe/dsh-jsonrpc-agent-pkg-macos-arm64 --output-dir dist-python
pip install --find-links dist-python deepseek-harness==0.1.0
version="$(node -p "require('./package.json').version")"
python scripts/build-python-release.py --package sdk --output-dir dist-python
python scripts/build-python-release.py --package runtime --platform macos-arm64 --runtime-exe dist-exe/dsh-jsonrpc-agent-pkg-macos-arm64 --output-dir dist-python
pip install --find-links dist-python deepseek-harness=="$version"
```
runtime 分发物只提供 wheel并拒绝 sdist 构建、缺失可执行文件以及混合平台载荷。三个 wheel tag 分别是 `py3-none-manylinux_2_28_x86_64``py3-none-manylinux_2_28_aarch64``py3-none-macosx_11_0_arm64`SDK 保持 `py3-none-any`。tag 流水线统一构建并发布这 4 个互不冲突的文件,因此常规的 `pip install deepseek-harness==X.Y.Z` 会选中匹配平台的 runtime wheel`import deepseek_harness` 不需要 `runtime_bin`
runtime 分发物只提供 wheel并拒绝 sdist 构建、缺失可执行文件以及混合平台载荷。三个 wheel tag 分别是 `py3-none-manylinux_2_28_x86_64``py3-none-manylinux_2_28_aarch64``py3-none-macosx_11_0_arm64`SDK 保持 `py3-none-any`匹配的 `python-vX.Y.Z` tag 流水线统一构建并发布这 4 个互不冲突的文件,因此常规的 `pip install deepseek-harness==X.Y.Z` 会选中匹配平台的 runtime wheel`import deepseek_harness` 不需要 `runtime_bin`
## 零配置语义

View File

@@ -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
README.md: 1a63f76f62b95cb36f53e3f8ff42898b26122d05
README.zh.md: b214ed5e6d25a013def5429b92a14b7393f1e028
README.md: 4beac57526761bb150e90b60f0030ed311c4034d
README.zh.md: 3c715bce17ed1630ef0439b042d2b67047968c55

View File

@@ -15,7 +15,7 @@ Both carriers hold the same content, defined once: the [package.json](package.js
Missing carriers raise `FileNotFoundError` naming the acquisition routes: build via `scripts/build-exe-for-python-sdk.ts` in a deepseek-harness checkout, or install the matching platform runtime wheel produced by the `build-exe-for-python-sdk` CI workflow. The workflow retains wheels rather than standalone executable archives. Acquisition strategy is deliberately separate from the lookup interface, so an on-demand download can replace it later without touching callers.
Each wheel contains exactly one executable. The fixed tags are `py3-none-manylinux_2_28_x86_64`, `py3-none-manylinux_2_28_aarch64`, and `py3-none-macosx_11_0_arm64`; the build hook rejects `py3-none-any`, absent or multiple executables, and unsupported platform tags. Stable `python-vX.Y.Z` releases use the same `X.Y.Z` for this package and the SDK.
Each wheel contains exactly one executable. The fixed tags are `py3-none-manylinux_2_28_x86_64`, `py3-none-manylinux_2_28_aarch64`, and `py3-none-macosx_11_0_arm64`; the build hook rejects `py3-none-any`, absent or multiple executables, and unsupported platform tags. The repository root `package.json` supplies the shared version for this package and the SDK, and a `python-vX.Y.Z` release tag must match it.
## Resolution API

View File

@@ -15,7 +15,7 @@ Python SDK 的运行时载体包dist 名 `deepseek-harness-runtime-bin`,模
载体缺失时抛出 `FileNotFoundError` 并写明获取途径:在 deepseek-harness 检出中经 `scripts/build-exe-for-python-sdk.ts` 构建,或安装 `build-exe-for-python-sdk` CI 工作流生成的对应平台 runtime wheel。该工作流只保留 wheel不保留独立 exe 归档。获取策略与查找接口刻意分离,之后可以换成按需下载而不动任何调用方。
每个 wheel 只包含一个可执行文件。固定 tag 为 `py3-none-manylinux_2_28_x86_64``py3-none-manylinux_2_28_aarch64``py3-none-macosx_11_0_arm64`;构建钩子会拒绝 `py3-none-any`、可执行文件缺失或重复以及不支持的平台 tag。稳定的 `python-vX.Y.Z` 发布为本包和 SDK 使用同一个 `X.Y.Z`
每个 wheel 只包含一个可执行文件。固定 tag 为 `py3-none-manylinux_2_28_x86_64``py3-none-manylinux_2_28_aarch64``py3-none-macosx_11_0_arm64`;构建钩子会拒绝 `py3-none-any`、可执行文件缺失或重复以及不支持的平台 tag。仓库根目录的 `package.json` 为本包和 SDK 提供共同版本,`python-vX.Y.Z` 发布 tag 必须与其匹配
## 解析 API

View File

@@ -0,0 +1,39 @@
"""Tests for repository-owned Python release versions."""
from __future__ import annotations
import json
import runpy
from pathlib import Path
from types import SimpleNamespace
import pytest
ROOT = Path(__file__).resolve().parents[3]
SCRIPT = ROOT / "scripts" / "build-python-release.py"
build_python_release = SimpleNamespace(**runpy.run_path(str(SCRIPT)))
def test_repository_version_matches_root_package_json() -> None:
expected = json.loads((ROOT / "package.json").read_text())["version"]
assert build_python_release.repository_version() == expected
def test_release_tag_is_optional_for_non_release_builds() -> None:
build_python_release.validate_release_tag(None, "1.2.3")
def test_release_tag_must_match_repository_version() -> None:
build_python_release.validate_release_tag("python-v1.2.3", "1.2.3")
with pytest.raises(ValueError, match="expected 'python-v1.2.3'"):
build_python_release.validate_release_tag("python-v1.2.4", "1.2.3")
def test_repository_version_rejects_non_stable_versions(tmp_path: Path) -> None:
(tmp_path / "package.json").write_text('{"version":"1.2.3-dev"}\n')
with pytest.raises(ValueError, match="must be stable X.Y.Z"):
build_python_release.repository_version(tmp_path)

View File

@@ -1,10 +1,11 @@
#!/usr/bin/env python3
"""Stage and build one Python release wheel from a stable ``python-vX.Y.Z`` tag."""
"""Stage and build one Python wheel at the repository version."""
from __future__ import annotations
import argparse
import email
import json
import os
import re
import shutil
@@ -26,12 +27,16 @@ PLATFORMS = {
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--package", choices=("sdk", "runtime"), required=True)
parser.add_argument("--tag", required=True)
parser.add_argument(
"--tag",
help="optional python-vX.Y.Z release tag; it must match package.json",
)
parser.add_argument("--output-dir", type=Path, required=True)
parser.add_argument("--platform", choices=tuple(PLATFORMS))
parser.add_argument("--runtime-exe", type=Path)
args = parser.parse_args()
version = version_from_tag(args.tag)
version = repository_version()
validate_release_tag(args.tag, version)
if args.package == "runtime" and (args.platform is None or args.runtime_exe is None):
parser.error("runtime builds require --platform and --runtime-exe")
if args.package == "sdk" and (args.platform is not None or args.runtime_exe is not None):
@@ -58,11 +63,28 @@ def main() -> None:
print(expected)
def version_from_tag(tag: str) -> str:
match = re.fullmatch(r"python-v(\d+\.\d+\.\d+)", tag)
if match is None:
raise ValueError(f"release tag must match python-vX.Y.Z, got {tag!r}")
return match.group(1)
def repository_version(root: Path = ROOT) -> str:
package_json = root / "package.json"
try:
payload = json.loads(package_json.read_text())
except (OSError, json.JSONDecodeError) as error:
raise ValueError(f"could not read repository version from {package_json}") from error
version = payload.get("version") if isinstance(payload, dict) else None
if not isinstance(version, str) or re.fullmatch(r"\d+\.\d+\.\d+", version) is None:
raise ValueError(
f"{package_json} version must be stable X.Y.Z, got {version!r}"
)
return version
def validate_release_tag(tag: str | None, version: str) -> None:
if tag is None:
return
expected = f"python-v{version}"
if tag != expected:
raise ValueError(
f"release tag must match repository version: expected {expected!r}, got {tag!r}"
)
def copy_package(source: Path, destination: Path) -> None:

View File

@@ -61,6 +61,9 @@ function readJson(path: string): PackageManifest {
return JSON.parse(readFileSync(path, 'utf8')) as PackageManifest
}
const rootManifest = readJson(join(root, 'package.json'))
const repositoryVersion = rootManifest.version
/** Repo-relative dirs holding a package.json, walked to the configured depth. */
function packageDirs(base: string, depth: number): string[] {
if (depth === 1) {
@@ -78,7 +81,7 @@ function packageDirs(base: string, depth: number): string[] {
function workspaceManifests(): WorkspaceManifest[] {
const manifests: WorkspaceManifest[] = [
{ dir: '.', manifest: readJson(join(root, 'package.json')) },
{ dir: '.', manifest: rootManifest },
]
for (const { dir: base, depth } of workspaceGlobs) {
@@ -147,8 +150,8 @@ function checkWorkspace({ dir, manifest }: WorkspaceManifest): string[] {
if (peer && dev && peer !== dev) {
errors.push(`${label}: cordis peer (${peer}) and dev (${dev}) ranges must match`)
}
if (manifest.version !== '0.0.1') {
errors.push(`${label}: package.json must set "version": "0.0.1"`)
if (manifest.version !== repositoryVersion) {
errors.push(`${label}: package.json version must match root version ${repositoryVersion ?? '(missing)'}`)
}
if (manifest.type !== 'module') {
errors.push(`${label}: package.json must set "type": "module"`)
@@ -205,7 +208,16 @@ function checkHierarchyShape(): string[] {
return errors
}
const errors = [...workspaceManifests().flatMap(checkWorkspace), ...checkHierarchyShape()]
function checkRepositoryVersion(): string[] {
if (repositoryVersion && /^\d+\.\d+\.\d+$/.test(repositoryVersion)) return []
return ['package.json: version must be stable X.Y.Z']
}
const errors = [
...checkRepositoryVersion(),
...workspaceManifests().flatMap(checkWorkspace),
...checkHierarchyShape(),
]
if (errors.length > 0) {
console.error(errors.join('\n'))
process.exitCode = 1