mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-09-09 04:02:35 +00:00
Merge remote-tracking branch 'origin/master' into dshw/pr-deepseek-harness-deepseek-harness-2672
# Conflicts: # docs/event-producer-consumer.i18n.yaml # docs/event-producer-consumer.md # docs/event-producer-consumer.zh.md # packages/acp/acp/tests/harness.ts # packages/api/session-controller/README.i18n.yaml # packages/api/session-controller/README.md # packages/api/session-controller/README.zh.md # packages/api/session-controller/tests/commands-queue-attachment.host.spec.ts # packages/context/agent-instructions/tests/agent-instructions.e2e.ts # packages/fs/tool-fs/tests/harness.ts # packages/preset/agent-presets/tests/invariant.spec.ts # packages/preset/agent-presets/tests/mount.spec.ts # packages/preset/agent-presets/tests/remote.spec.ts # packages/test-support/agent-loop-testkit/package.json
This commit is contained in:
@@ -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: aa0144d7eaa66711d0f08316d4445da060918ca8
|
||||
development.zh.md: a35f6fc8de1bdbd282fd8999a1440fde0c400b34
|
||||
development.md: 39c47b7d2a86995eea017c77fb147c63f90412b1
|
||||
development.zh.md: fe7b4dbb2ca2e341dfb9ceb57959b7d9807f54d2
|
||||
|
||||
@@ -27,7 +27,7 @@ uv run --project python/sdk pytest
|
||||
|
||||
`python/sdk/tests/test_bundled_runtime.py` exercises available bundled carriers and skips a carrier when its artifact has not been built. For repository-wide test policy, see [Testing](../docs/testing.md).
|
||||
|
||||
That suite drives fake runtime peers. `scripts/smoke-python-runtime.py` drives the packaged runtime instead. The required `python-runtime` CI job builds every published native target, installs the matching SDK and runtime wheels into a new Python 3.10 virtual environment, runs outside the checkout with `PYTHONPATH` and `DSH_RUNTIME_MODE` unset, proves that both modules and the executable came from those distributions, and then runs every keyless scenario. A focused local source-SDK run can select one built executable and scenario:
|
||||
That suite drives fake runtime peers. `scripts/smoke-python-runtime.py` drives the packaged runtime instead. The `python-runtime` CI jobs build Linux x64 and Windows x64 on pull requests, and Linux arm64 plus both macOS architectures on master pushes. Each selected target installs the matching SDK and runtime wheels into a new Python 3.10 virtual environment, runs outside the checkout with `PYTHONPATH` and `DSH_RUNTIME_MODE` unset, proves that both modules and the executable came from those distributions, and then runs every keyless scenario. A focused local source-SDK run can select one built executable and scenario:
|
||||
|
||||
```sh
|
||||
uv run --project python/sdk python scripts/smoke-python-runtime.py \
|
||||
@@ -36,7 +36,7 @@ uv run --project python/sdk python scripts/smoke-python-runtime.py \
|
||||
|
||||
Three scenarios compare committed expected output under `scripts/snapshots/python-sdk-single-exe/`. `minimal/model-visible.json` pins the Linux/macOS `sdk-minimal` profile's assembled system prompts, advertised tool schemas, and model-visible messages; `minimal/win-x64/model-visible.json` pins its PowerShell counterpart. A plugin that contributes an unintended system section or user message therefore fails the job, and every message the profile emits is compared. `advanced/` pins one complex process's SDK result and parent/child session logs across every target. `restart/` launches two complete SDK runtime processes against one persistence root and snapshots their isolated model histories, high-level results, and separate durable logs across every target. Rerun the owning scenario with `--update-snapshots` and review that diff before committing it.
|
||||
|
||||
Trusted pull requests also run `--scenario sdk-live --installed-wheel` on every native target. That scenario performs two tool-using turns against `https://api.deepseek.com`, verifies the created file externally, and fails when the repository secret is absent instead of self-skipping. Fork and Dependabot pull requests run the complete keyless installed-wheel path but receive no key.
|
||||
Trusted pull requests and master pushes also run `--scenario sdk-live --installed-wheel` on each selected native target. That scenario performs two tool-using turns against `https://api.deepseek.com`, verifies the created file externally, and fails when the repository secret is absent instead of self-skipping. Fork and Dependabot pull requests run the complete keyless installed-wheel path but receive no key.
|
||||
|
||||
An interactive smoke test needs `DEEPSEEK_API_KEY` in the environment or repository-root `.env`:
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ uv run --project python/sdk pytest
|
||||
|
||||
`python/sdk/tests/test_bundled_runtime.py` 会运行可用的内置载体;某个载体的产物尚未构建时,会跳过该载体。仓库级测试政策见 [测试](../docs/testing.zh.md)。
|
||||
|
||||
该套件面向的是伪造的运行时对端。`scripts/smoke-python-runtime.py` 面向打包运行时。必需的 `python-runtime` CI 任务会构建每个已发布原生目标,把匹配的 SDK wheel 包与运行时 wheel 包安装进新的 Python 3.10 虚拟环境,在 checkout 外清除 `PYTHONPATH` 与 `DSH_RUNTIME_MODE` 后运行,证明两个模块及可执行文件都来自这些 distribution,然后运行全部 keyless 场景。聚焦的本地源码 SDK 运行可以选择一个已构建可执行文件与场景:
|
||||
该套件面向的是伪造的运行时对端。`scripts/smoke-python-runtime.py` 面向打包运行时。`python-runtime` CI 任务在拉取请求上构建 Linux x64 与 Windows x64,在 master 推送上构建 Linux arm64 与两种 macOS 架构。每个选定目标把匹配的 SDK wheel 包与运行时 wheel 包安装进新的 Python 3.10 虚拟环境,在 checkout 外清除 `PYTHONPATH` 与 `DSH_RUNTIME_MODE` 后运行,证明两个模块及可执行文件都来自这些 distribution,然后运行全部 keyless 场景。聚焦的本地源码 SDK 运行可以选择一个已构建可执行文件与场景:
|
||||
|
||||
```sh
|
||||
uv run --project python/sdk python scripts/smoke-python-runtime.py \
|
||||
@@ -36,7 +36,7 @@ uv run --project python/sdk python scripts/smoke-python-runtime.py \
|
||||
|
||||
其中三个场景会比对 `scripts/snapshots/python-sdk-single-exe/` 下已提交的期望输出。`minimal/model-visible.json` 固定 Linux/macOS `sdk-minimal` profile 所组装的系统提示词、对外公布的工具 schema 与模型可见消息;`minimal/win-x64/model-visible.json` 固定对应的 PowerShell 版本。因此,插件一旦贡献出计划外的系统分段或 user 消息,该任务即失败,且该 profile 发出的每条消息都会参与比对。`advanced/` 跨所有目标固定一个复杂进程的 SDK 结果及父/子会话日志。`restart/` 针对同一持久化根目录启动两个完整 SDK 运行时进程,并跨所有目标固定其彼此隔离的模型历史、高层结果与独立持久日志。重新运行对应场景时加上 `--update-snapshots`,并在提交前审阅该差异。
|
||||
|
||||
可信拉取请求还会在每个原生目标上运行 `--scenario sdk-live --installed-wheel`。该场景面向 `https://api.deepseek.com` 执行两个使用工具的轮次,从外部验证已创建文件,并在仓库密钥缺失时失败而不是自行 skip。Fork 与 Dependabot 拉取请求会运行完整的 keyless 安装后 wheel 路径,但不会获得密钥。
|
||||
可信拉取请求与 master 推送还会在各自选定的原生目标上运行 `--scenario sdk-live --installed-wheel`。该场景面向 `https://api.deepseek.com` 执行两个使用工具的轮次,从外部验证已创建文件,并在仓库密钥缺失时失败而不是自行 skip。Fork 与 Dependabot 拉取请求会运行完整的 keyless 安装后 wheel 路径,但不会获得密钥。
|
||||
|
||||
交互式冒烟测试需要环境变量或仓库根目录 `.env` 中存在 `DEEPSEEK_API_KEY`:
|
||||
|
||||
|
||||
@@ -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: 050ae85d9b0a38b82a3c66a84c3d8f34e137be6c
|
||||
README.zh.md: 7066d7224752294c25b58cfe8fb6a94524a2013d
|
||||
README.md: fb7478f305015e60dafd23861ab6fd91f10757f3
|
||||
README.zh.md: 7c663aba8d387fb7b1048afe28d50faee7350303
|
||||
|
||||
@@ -19,7 +19,7 @@ Both carriers execute the same `dsh` grammar and shipped profiles, including the
|
||||
- `bundled_package_dir() -> Path` returns the installed module-data root and verifies its release metadata.
|
||||
- `bundled_runtime_path() -> Path` returns the current platform executable and verifies required sidecars.
|
||||
- `resolve_bundled_launch_args(mode=None) -> tuple[str, ...]` returns the executable argv by default. Explicit `mode="node"` or `DSH_RUNTIME_MODE=node` selects the repo-only Node carrier.
|
||||
- `main()` implements the installed `dsh` console command and rejects an absent or blank `DSH_HOME` before replacing the Python process.
|
||||
- `main()` implements the installed `dsh` console command and rejects an absent or blank `DSH_HOME`. On Windows it waits for the bundled process with inherited standard streams and forwards its exit status; on POSIX it replaces the Python process.
|
||||
|
||||
Unsupported platforms and missing executables or sidecars raise `FileNotFoundError` with the build and installation routes. Unknown runtime modes raise `ValueError`.
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ Wheel 会安装 `dsh` 控制台命令和 `deepseek_harness_runtime` Python 模
|
||||
- `bundled_package_dir() -> Path` 返回已安装模块数据根目录,并校验发布元数据。
|
||||
- `bundled_runtime_path() -> Path` 返回当前平台可执行程序,并校验必需伴随文件。
|
||||
- `resolve_bundled_launch_args(mode=None) -> tuple[str, ...]` 默认返回可执行程序 argv。显式 `mode="node"` 或 `DSH_RUNTIME_MODE=node` 会选择仅限仓库使用的 Node 载体。
|
||||
- `main()` 实现已安装的 `dsh` 控制台命令,并在替换 Python 进程前拒绝缺失或空白的 `DSH_HOME`。
|
||||
- `main()` 实现已安装的 `dsh` 控制台命令,并拒绝缺失或空白的 `DSH_HOME`。在 Windows 上,它让打包进程继承标准流,等待其结束并转发退出状态;在 POSIX 上,它替换 Python 进程。
|
||||
|
||||
不支持的平台以及缺失的可执行程序或伴随文件会抛出 `FileNotFoundError`,并指出构建与安装路径。未知运行时模式会抛出 `ValueError`。
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env node
|
||||
/** Private entry owned by the Python single-file runtime packaging. */
|
||||
|
||||
const selectorName = 'DSH_SUBPROCESS_RUNNER'
|
||||
const selection = process.env[selectorName]
|
||||
|
||||
if (selection === undefined) {
|
||||
const { runCli } = await import('@deepseek-ai/dsh/lib/bin.js')
|
||||
await runCli()
|
||||
} else {
|
||||
Reflect.deleteProperty(process.env, selectorName)
|
||||
const { runSelectedSubprocessRunner } = await import('@deepseek-ai/dsh-subprocess-local/runner')
|
||||
await runSelectedSubprocessRunner(selection)
|
||||
}
|
||||
@@ -24,6 +24,7 @@ from __future__ import annotations
|
||||
import os
|
||||
import platform
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
@@ -156,7 +157,7 @@ def _node_launch_args() -> tuple[str, str]:
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Execute the bundled dsh CLI with an explicitly selected Harness home."""
|
||||
"""Launch the CLI with explicit DSH_HOME; wait on Windows, replace the process on POSIX."""
|
||||
if not os.environ.get("DSH_HOME", "").strip():
|
||||
print(
|
||||
"dsh: the Python runtime command requires an explicit DSH_HOME; "
|
||||
@@ -165,6 +166,9 @@ def main() -> None:
|
||||
)
|
||||
raise SystemExit(2)
|
||||
argv = (*resolve_bundled_launch_args(), *sys.argv[1:])
|
||||
if sys.platform == "win32":
|
||||
# Windows CRT exec does not replace the process; wait and preserve the runtime status.
|
||||
raise SystemExit(subprocess.run(argv, env=os.environ).returncode)
|
||||
os.execvpe(argv[0], argv, os.environ)
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,11 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
|
||||
import deepseek_harness_runtime as runtime
|
||||
import pytest
|
||||
@@ -129,7 +133,7 @@ def test_python_dsh_command_executes_the_bundled_cli(
|
||||
called: dict[str, object] = {}
|
||||
monkeypatch.setenv("DSH_HOME", "/explicit/home")
|
||||
monkeypatch.setattr(runtime, "resolve_bundled_launch_args", lambda: ("/runtime",))
|
||||
monkeypatch.setattr(runtime.sys, "argv", ["dsh", "plugin", "--profile", "sdk", "list"])
|
||||
monkeypatch.setattr(runtime, "sys", SimpleNamespace(platform="linux", argv=["dsh", "plugin", "--profile", "sdk", "list"]))
|
||||
|
||||
def execvpe(file: str, args: tuple[str, ...], env: dict[str, str]) -> None:
|
||||
called.update(file=file, args=args, home=env.get("DSH_HOME"))
|
||||
@@ -143,3 +147,51 @@ def test_python_dsh_command_executes_the_bundled_cli(
|
||||
"args": ("/runtime", "plugin", "--profile", "sdk", "list"),
|
||||
"home": "/explicit/home",
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("returncode", [0, 37, 513])
|
||||
def test_windows_console_waits_and_forwards_runtime_status(monkeypatch: pytest.MonkeyPatch, returncode: int) -> None:
|
||||
monkeypatch.setenv("DSH_HOME", "/explicit/home")
|
||||
monkeypatch.setattr(runtime, "sys", SimpleNamespace(platform="win32", argv=["dsh", "plugin", "argument with spaces", "中文"]))
|
||||
monkeypatch.setattr(runtime, "resolve_bundled_launch_args", lambda: ("runtime.exe",))
|
||||
called = []
|
||||
|
||||
def run(args: tuple[str, ...], **kwargs: object) -> subprocess.CompletedProcess[str]:
|
||||
called.append((args, kwargs))
|
||||
return subprocess.CompletedProcess(args, returncode)
|
||||
|
||||
def forbidden_exec(*args: object) -> None:
|
||||
pytest.fail("Windows console must wait instead of entering CRT exec")
|
||||
|
||||
monkeypatch.setattr(subprocess, "run", run)
|
||||
monkeypatch.setattr(runtime.os, "execvpe", forbidden_exec)
|
||||
with pytest.raises(SystemExit) as result:
|
||||
main()
|
||||
assert result.value.code == returncode
|
||||
assert called == [(("runtime.exe", "plugin", "argument with spaces", "中文"), {"env": os.environ})]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("returncode", [0, 37, pytest.param(513, marks=pytest.mark.skipif(sys.platform != "win32", reason="POSIX truncates process exit codes to eight bits"))])
|
||||
def test_windows_console_branch_preserves_real_child_io_and_completion(tmp_path: Path, returncode: int) -> None:
|
||||
child = tmp_path / "child with spaces.py"
|
||||
sentinel = tmp_path / "finished"
|
||||
child.write_text(
|
||||
"import pathlib,sys\n"
|
||||
"assert sys.argv[1] == 'argument with spaces'\n"
|
||||
"assert sys.argv[2] == '中文'\n"
|
||||
"print('stdout-中文', flush=True)\n"
|
||||
"print('stderr-中文', file=sys.stderr, flush=True)\n"
|
||||
f"pathlib.Path({str(sentinel)!r}).write_text('done')\n"
|
||||
f"raise SystemExit({returncode})\n", encoding="utf-8",
|
||||
)
|
||||
driver = (
|
||||
"import deepseek_harness_runtime as runtime; from types import SimpleNamespace; "
|
||||
f"runtime.sys = SimpleNamespace(platform='win32', argv=['dsh', 'argument with spaces', '中文']); "
|
||||
f"runtime.resolve_bundled_launch_args = lambda: ({sys.executable!r}, {str(child)!r}); runtime.main()"
|
||||
)
|
||||
result = subprocess.run([sys.executable, "-c", driver], capture_output=True, text=True, encoding="utf-8",
|
||||
env={**os.environ, "DSH_HOME": str(tmp_path), "PYTHONIOENCODING": "utf-8"}, timeout=15)
|
||||
assert result.returncode == returncode, result.stderr
|
||||
assert result.stdout == "stdout-中文\n"
|
||||
assert result.stderr == "stderr-中文\n"
|
||||
assert sentinel.read_text() == "done"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import runpy
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
@@ -262,3 +263,17 @@ def test_snapshot_generation_filename_must_match_header(tmp_path: Path) -> None:
|
||||
|
||||
with pytest.raises(AssertionError, match="filename declares Session format v1"):
|
||||
SMOKE["selected_snapshot_session_files"](tmp_path)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("returncode", [1, -1073741819, 3221225477])
|
||||
def test_profile_plugin_failure_reports_native_exit_status(monkeypatch: pytest.MonkeyPatch, returncode: int) -> None:
|
||||
def failed_install(*args: object, **kwargs: object) -> subprocess.CompletedProcess[str]:
|
||||
return subprocess.CompletedProcess(args=[], returncode=returncode, stdout="", stderr="")
|
||||
|
||||
monkeypatch.setattr(subprocess, "run", failed_install)
|
||||
with pytest.raises(AssertionError) as error:
|
||||
SMOKE["smoke_sdk_profile_plugin"]("http://127.0.0.1:1")
|
||||
message = str(error.value)
|
||||
assert f"returncode={returncode}" in message
|
||||
assert f"0x{returncode & 0xffffffff:08x}" in message
|
||||
assert "stdout='' stderr=''" in message
|
||||
|
||||
Reference in New Issue
Block a user