diff --git a/.agents/notes/proposed/process/2026-09-06-python-runtime-windows-selfhosted.i18n.yaml b/.agents/notes/proposed/process/2026-09-06-python-runtime-windows-selfhosted.i18n.yaml index a3c173532e..f41aa1faf0 100644 --- a/.agents/notes/proposed/process/2026-09-06-python-runtime-windows-selfhosted.i18n.yaml +++ b/.agents/notes/proposed/process/2026-09-06-python-runtime-windows-selfhosted.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/proposed/process/2026-09-06-python-runtime-windows-selfhosted.md -2026-09-06-python-runtime-windows-selfhosted.md: f95389bb8b4d8de58dc6b4e8bad834836c6b325e -2026-09-06-python-runtime-windows-selfhosted.zh.md: 67accee54140d71f31acb43c02d0e3692070d529 +2026-09-06-python-runtime-windows-selfhosted.md: 0b59dfd0d6c08c4f889554d5d96b28d13fad79bc +2026-09-06-python-runtime-windows-selfhosted.zh.md: 2d7b28074dd88c692c02602cb4d5104c6a045369 diff --git a/.agents/notes/proposed/process/2026-09-06-python-runtime-windows-selfhosted.md b/.agents/notes/proposed/process/2026-09-06-python-runtime-windows-selfhosted.md index f95389bb8b..0b59dfd0d6 100644 --- a/.agents/notes/proposed/process/2026-09-06-python-runtime-windows-selfhosted.md +++ b/.agents/notes/proposed/process/2026-09-06-python-runtime-windows-selfhosted.md @@ -14,7 +14,7 @@ The [read-only prerequisite probe](https://github.com/deepseek-harness/deepseek- Route only the Windows x64 target in [the runtime workflow](../../../../.github/workflows/build-exe-for-python-sdk.yml) to the persistent pool when `inputs.ci && !inputs.release`, the repository is the canonical repository, and the event is either a same-repository non-fork, non-Dependabot PR or a master push. `DSH_CI_FAILOVER_WINDOWS=selfhosted` enables this routing; an unset or different value keeps the lane hosted. Release/manual builds, other events, Linux/macOS targets, planning, and the SDK-wheel job remain hosted. The implementation is pending native runtime validation. -The [native setup probe](https://github.com/deepseek-harness/deepseek-harness/actions/runs/34013261224/job/101432611073) downloads Python 3.10.20, verifies command resolution and a seeded smoke venv, asserts unchanged registered Python installations and Developer Mode, and proves job-root deletion. Windows recursive removal needs bounded retries after an observed non-empty-directory failure. The workflow additionally clears the exported compile-cache path and resets temporary-directory variables before action post-steps; focused tests pin those assignments, which are not part of the cited probe commit. The focused routing tests pass, and an inverted failover condition produces three expected failures before restoration. Full executable, wheel, and live-API validation remains pending. +The [native setup probe](https://github.com/deepseek-harness/deepseek-harness/actions/runs/34013261224/job/101432611073) downloads Python 3.10.20, verifies command resolution and a seeded smoke venv, asserts unchanged registered Python installations and Developer Mode, and proves job-root deletion. Windows recursive removal needs bounded retries after an observed non-empty-directory failure. The workflow additionally clears the exported compile-cache path and resets temporary-directory variables before action post-steps; focused tests pin those assignments, which are not part of the cited probe commit. The focused routing tests pass, and an inverted failover condition produces three expected failures before restoration. The first full native run builds the executable and wheel but fails when Python reads UTF-8 Session JSONL with the host GBK default. The setup exports Python UTF-8 mode and UTF-8 standard streams; a local forced-ASCII-locale subprocess reproduces the default-decoding failure and verifies the setting, while corrected native keyless/live-API validation remains pending. The [private setup script](../../../../scripts/setup-python-runtime-windows.ps1) bootstraps uv 0.11.23 inside a temporary venv using the preinstalled interpreter, then downloads managed Python 3.10 into a unique job directory with `--no-bin --no-registry`. It creates a seeded tooling venv without further Python downloads. These flags exist in the [pinned uv source](https://github.com/astral-sh/uv/blob/3cdf50e0924f1ace7a92ddbac98b12a958b87688/crates/uv-cli/src/lib.rs#L6672-L6713); the [implementation](https://github.com/astral-sh/uv/blob/3cdf50e0924f1ace7a92ddbac98b12a958b87688/crates/uv/src/commands/python/install.rs#L667-L723) suppresses executable links and registry registration. CI checks Developer Mode rather than enabling it. diff --git a/.agents/notes/proposed/process/2026-09-06-python-runtime-windows-selfhosted.zh.md b/.agents/notes/proposed/process/2026-09-06-python-runtime-windows-selfhosted.zh.md index 67accee541..2d7b28074d 100644 --- a/.agents/notes/proposed/process/2026-09-06-python-runtime-windows-selfhosted.zh.md +++ b/.agents/notes/proposed/process/2026-09-06-python-runtime-windows-selfhosted.zh.md @@ -14,7 +14,7 @@ Status: proposed 仅当 `inputs.ci && !inputs.release`、仓库为规范仓库,且事件为同仓库非 fork、非 Dependabot 的 PR(Pull Request)或 master 推送时,将 [runtime 工作流](../../../../.github/workflows/build-exe-for-python-sdk.yml) 的 Windows x64 目标路由到常驻运行器池。`DSH_CI_FAILOVER_WINDOWS=selfhosted` 启用此路由;未设置或其他值使通道留在托管运行器。发布/手动构建、其他事件、Linux/macOS 目标、规划作业与 SDK wheel 包作业继续使用托管运行器。实现尚待原生 runtime 验证。 -[原生准备探测](https://github.com/deepseek-harness/deepseek-harness/actions/runs/34013261224/job/101432611073) 下载 Python 3.10.20,验证命令解析与包含 pip 的冒烟 venv,断言已注册的 Python 安装与开发人员模式不变,并证明作业根目录已删除。观测到目录非空的删除失败后,Windows 递归删除使用有限重试。工作流另外在 action 后置步骤前清除导出的编译缓存路径并重置临时目录变量;定向测试固定这些赋值,它们不属于引用的探测提交。定向路由测试通过,反转故障切换条件会产生三个预期失败,随后恢复条件。完整可执行文件、wheel 包与真实 API 验证仍待完成。 +[原生准备探测](https://github.com/deepseek-harness/deepseek-harness/actions/runs/34013261224/job/101432611073) 下载 Python 3.10.20,验证命令解析与包含 pip 的冒烟 venv,断言已注册的 Python 安装与开发人员模式不变,并证明作业根目录已删除。观测到目录非空的删除失败后,Windows 递归删除使用有限重试。工作流另外在 action 后置步骤前清除导出的编译缓存路径并重置临时目录变量;定向测试固定这些赋值,它们不属于引用的探测提交。定向路由测试通过,反转故障切换条件会产生三个预期失败,随后恢复条件。首次完整原生运行成功构建可执行文件与 wheel 包,但 Python 用主机默认 GBK 编码读取 UTF-8 Session JSONL 时失败。准备脚本导出 Python UTF-8 模式与 UTF-8 标准流;本地强制 ASCII locale 的子进程复现默认解码失败并验证设置,修复后的原生 keyless/真实 API 验证仍待完成。 [私有准备脚本](../../../../scripts/setup-python-runtime-windows.ps1) 使用预装解释器,在临时 venv 内引导安装 uv 0.11.23,再通过 `--no-bin --no-registry` 将托管 Python 3.10 下载到唯一的作业目录。它创建包含初始工具包的工具 venv,禁止进一步下载 Python。[固定版本的 uv 源码](https://github.com/astral-sh/uv/blob/3cdf50e0924f1ace7a92ddbac98b12a958b87688/crates/uv-cli/src/lib.rs#L6672-L6713) 提供这些参数;[实现](https://github.com/astral-sh/uv/blob/3cdf50e0924f1ace7a92ddbac98b12a958b87688/crates/uv/src/commands/python/install.rs#L667-L723) 禁止创建可执行文件链接与注册表登记。CI 检查开发人员模式,不负责启用它。 diff --git a/scripts/python-runtime-selfhosted.spec.ts b/scripts/python-runtime-selfhosted.spec.ts index a2a072a7e4..075eb4c0c5 100644 --- a/scripts/python-runtime-selfhosted.spec.ts +++ b/scripts/python-runtime-selfhosted.spec.ts @@ -1,3 +1,4 @@ +import { spawnSync } from 'node:child_process' import { readFileSync } from 'node:fs' import { resolve } from 'node:path' import { runInNewContext } from 'node:vm' @@ -97,6 +98,28 @@ describe('Python runtime self-hosted routing', () => { expect(cleanup).toContain('maxRetries: 10, retryDelay: 100') }) + it('reads UTF-8 Session JSONL independently of the host locale', () => { + const setup = readFileSync(resolve(root, 'scripts/setup-python-runtime-windows.ps1'), 'utf8') + const utf8 = /PYTHONUTF8 = '([^']+)'/.exec(setup)?.[1] + expect(utf8).toBe('1') + const result = spawnSync(process.platform === 'win32' ? 'python' : 'python3', ['-c', [ + 'import pathlib, tempfile, sys', + 'assert sys.flags.utf8_mode == 1', + 'with tempfile.TemporaryDirectory(prefix="python-runtime-encoding-") as root:', + ' log = pathlib.Path(root) / "session.jsonl"', + ' text = chr(0x2014) + chr(0x4e2d)', + ' log.write_bytes(text.encode("utf-8"))', + ' assert log.read_text() == text', + ].join('\n')], { + env: { ...process.env, LC_ALL: 'C', LANG: 'C', PYTHONCOERCECLOCALE: '0', PYTHONUTF8: utf8 }, + encoding: 'utf8', + timeout: 10000, + }) + expect(result.error).toBeUndefined() + expect(result.signal).toBeNull() + expect(result.status, result.stderr).toBe(0) + }) + it('pins portable Python without registry or shared cache writes', () => { const setup = readFileSync(resolve(root, 'scripts/setup-python-runtime-windows.ps1'), 'utf8') expect(setup).toContain('--no-bin --no-registry 3.10') diff --git a/scripts/setup-python-runtime-windows.ps1 b/scripts/setup-python-runtime-windows.ps1 index 4303721bb6..13441c4f9f 100644 --- a/scripts/setup-python-runtime-windows.ps1 +++ b/scripts/setup-python-runtime-windows.ps1 @@ -5,6 +5,9 @@ New-Item -ItemType Directory -Path $root | Out-Null "root=$root" >> $env:GITHUB_OUTPUT $privateEnvironment = @{ + # Session JSONL and SDK pipes use UTF-8, including on Chinese Windows images. + PYTHONUTF8 = '1' + PYTHONIOENCODING = 'utf-8' TMP = $root TEMP = $root UV_CACHE_DIR = (Join-Path $root 'uv-cache')