diff --git a/Agent.md b/Agent.md index d6439f9c..dcbdcf9c 100644 --- a/Agent.md +++ b/Agent.md @@ -118,7 +118,7 @@ Community needs voiced in HN agent-UI discussions map directly to EMRG's design: pkill -f "emrg.server"; rm -f ~/.emrg/emrgd.port; python -m emrg ``` -Python: `uv run pytest tests/ -v` (958) — import check: `uv run python -c "from emrg.client.app import run_client"` +Python: `uv run pytest tests/ -v` (959) — import check: `uv run python -c "from emrg.client.app import run_client"` GUI: `cd emrg/gui && npm test` (260: 45 daemon_client + 19 conn-manager + 22 app-commands + 131 renderer smoke + 16 i18n + 7 integration + 3 commands + 8 build-config + 7 gui-state + 2 tool-group) — syntax: `node --check main.js preload.js daemon_client.js renderer/js/*.js` CI: `uv run pytest` (ubuntu + **windows-2025 matrix** — Windows pytest 回归在 PR CI 即失败,v0.2.29 教训 #725) + GUI tests + **actionlint workflow lint** (`rhysd/actionlint@v1.7.12` gate, #444 — workflow 解析错误在 PR CI 即失败,如 `if:` secrets 上下文) Re-trigger: `scripts/re-trigger-ci.sh [branch]` (workflow_dispatch, #527 — 替代空 commit 重触发:Actions outage 会整段丢弃 push 事件,dispatch 走 API 路径不受影响) diff --git a/packaging/make-installer.sh b/packaging/make-installer.sh index 202575db..4fb14127 100755 --- a/packaging/make-installer.sh +++ b/packaging/make-installer.sh @@ -413,6 +413,32 @@ begin // (从不杀自己),#847 self-held 归属已处理自身锁,且 stop_all 纯标准库从不加载 // websockets。恢复 v0.2.49 的 "{PythonExe}" "{StopScript}",无需任何替代 flag。 LogFile := ExpandConstant('{tmp}\stop_all.log'); + // rant 2026-08-19T00:44:52 — 安装器韧性:旧版 runtime python 可能已被早期失败安装 + // (v0.2.49/v0.2.50 的 -I 事故)损坏,启动即崩 "Failed to import encodings"(连 + // stop_all.log 都没有)→ 用坏 python 跑 stop_all 必然失败 → 升级安装被硬中止。 + // 宿主实测:删除旧 install\bin 后走"干净安装"分支即成功 —— v0.2.51 包本身完好, + // 脆弱点在升级路径。修复:跑 stop_all 前先健康检查旧 python 能否启动 + // (-c "import encodings, sys",python-build-standalone 启动即加载 encodings), + // 启动失败 → 跳过 stop_all 继续安装(宁可旧进程锁文件让安装器报 DeleteFile、 + // 可重试可重启恢复,也不能因坏 python 卡死升级)。健康检查与 stop_all 同款: + // 经 {cmd} 重定向到日志,失败信息进日志 + Setup log。 + if Exec(ExpandConstant('{cmd}'), '/c ""' + PythonExe + '" -c "import encodings, sys" > "' + LogFile + '" 2>&1"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) then + begin + if ResultCode <> 0 then + begin + LogText := ''; + if FileExists(LogFile) then + LoadStringFromFile(LogFile, LogText); + Log('PrepareToInstall: old runtime python failed health check (exit ' + IntToStr(ResultCode) + '), skipping stop_all and continuing install. ' + LogText); + Exit; + end; + end + else + begin + Log('PrepareToInstall: could not run old runtime python health check, skipping stop_all and continuing install.'); + Exit; + end; + // 健康检查通过:旧 python 可启动 → 正常跑 stop_all(保留现有逻辑) if Exec(ExpandConstant('{cmd}'), '/c ""' + PythonExe + '" "' + StopScript + '" > "' + LogFile + '" 2>&1"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) then begin if ResultCode <> 0 then diff --git a/tests/test_installer_stop.py b/tests/test_installer_stop.py index be10c78e..e5a43553 100644 --- a/tests/test_installer_stop.py +++ b/tests/test_installer_stop.py @@ -15,6 +15,9 @@ PrepareToInstall 用 runtime python 运行 {tmp} 提取版 5. build-runtime.sh 把 emrg/_stop_all.py 复制进 runtime bin/stop_all.py (stop-emrg.cmd 不再打包;CRLF 转换循环只剩 emrg.cmd emrgd.cmd) + 6. PrepareToInstall 旧 python 健康检查(rant 2026-08-19T00:44:52): + stop_all 前先 -c "import encodings, sys" 验证旧 runtime python 可启动, + 损坏(encodings 崩)→ 跳过 stop_all 继续安装(不硬中止升级) """ from pathlib import Path @@ -279,6 +282,30 @@ def test_make_installer_iss_has_prepare_to_install(): assert "restart the computer" in content +def test_make_installer_iss_old_python_health_check(): + """rant 2026-08-19T00:44:52 — 安装器韧性:旧版 python-dist 损坏(encodings 崩, + v0.2.49/v0.2.50 -I 事故遗留)时,升级安装不得因坏 python 硬中止。 + + 宿主实测:v0.2.51 升级报 `ModuleNotFoundError: No module named 'encodings'` + (连 stop_all.log 都没有,启动即崩);删除旧 install\\bin 后走干净安装分支 + 即成功 —— 包本身完好,脆弱点在 PrepareToInstall 用旧 python 跑 stop_all。 + 修复 = 跑 stop_all 前先健康检查旧 python 可启动(-c "import encodings, sys"), + 失败 → 跳过 stop_all(干净安装路径,不中止)。 + """ + content = _read("packaging/make-installer.sh") + # 健康检查命令与 stop_all 同款:经 {cmd} 重定向到日志(2>&1) + assert '''""' + PythonExe + '" -c "import encodings, sys"''' in content + assert content.count("2>&1") >= 2 # 健康检查 + stop_all 两处 Exec 重定向 + # 失败 → 跳过 stop_all 继续安装(Result 保持 '' → 不中止),信息进日志 + assert "skipping stop_all" in content + assert "health check" in content + assert "Log('PrepareToInstall: old runtime python failed health check" in content + # 健康检查必须先于 stop_all 执行 + assert content.index("import encodings, sys") < content.index('''""' + PythonExe + '" "' + StopScript''') + # 健康检查通过后仍保留原 stop_all 调用 + assert '''""' + PythonExe + '" "' + StopScript''' in content + + def test_build_runtime_copies_stop_all_not_stop_emrg(): content = _read("packaging/build-runtime.sh") # 复制 emrg/_stop_all.py → bin/stop_all.py(不再复制 stop-emrg.cmd)