fix(remote_env): 批量pip失败时降级逐包安装(部分成功优于全败)

This commit is contained in:
ymq 2026-08-28 21:15:58 +08:00
parent ea17307a6c
commit 023d4a67fa

View File

@ -312,14 +312,55 @@ async def install_remote_deps(env: dict, specs: list, pip_index: str,
installed = list(specs)
failed = []
else:
installed = [s for s in specs if all(
not s.lower().startswith(re.split(r"[\[<>=!~\s]", f, 1)[0].lower())
for f in failed)]
# 批量失败 → 逐包降级:一次 SSH 会话内逐包安装pip 幂等,已装的秒过),
# 部分成功优于全败。
return await _install_per_package(env, specs, pip_index, rel, timeout)
return {"ok": rc == 0, "installed": installed, "failed": failed,
"venv": "~/" + rel + "/.venv-skills",
"tail": out[-800:]}
async def _install_per_package(env: dict, specs: list, pip_index: str,
rel: str, timeout: int) -> dict:
"""逐包安装(批量失败后的降级路径)。返回格式与 install_remote_deps 一致。"""
payload = base64.b64encode(json.dumps(specs).encode()).decode()
index_arg = ('-i ' + shlex.quote(pip_index) + ' ') if pip_index else ''
script = (
'RD="$HOME/' + rel + '"; VENV="$RD/.venv-skills"; '
'[ -x "$VENV/bin/pip" ] || { echo "VENV_MISSING"; exit 4; }; '
'set -f; '
'echo "' + payload + '" | base64 -d | python3 -c '
'"import json,sys;print(chr(10).join(json.load(sys.stdin)))" | '
'while IFS= read -r pkg; do '
' [ -n "$pkg" ] || continue; '
' if "$VENV/bin/pip" install --no-input --disable-pip-version-check '
+ index_arg + '"$pkg" >/dev/null 2>&1; then echo "OK:$pkg"; '
' else echo "FAILED:$pkg"; fi; '
'done'
)
ssh_cmd = ["ssh"] + _ssh_common_args(env) + [_ssh_target(env), script]
try:
proc = await asyncio.create_subprocess_exec(
*ssh_cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
stdout, _stderr = await asyncio.wait_for(proc.communicate(), timeout=timeout + 30)
except asyncio.TimeoutError:
return {"ok": False, "error": f"逐包安装超时({timeout}s可重跑"}
except Exception as e:
return {"ok": False, "error": "SSH 执行失败: " + str(e)[:300]}
out = stdout.decode("utf-8", "replace")
installed, failed = [], []
for line in out.splitlines():
if line.startswith("OK:"):
installed.append(line[3:])
elif line.startswith("FAILED:"):
failed.append(line[7:])
return {"ok": len(failed) == 0 and len(installed) > 0,
"installed": installed, "failed": failed,
"venv": "~/" + rel + "/.venv-skills",
"mode": "per-package", "tail": out[-500:]}
# ── 总编排 ────────────────────────────────────────────────────
async def init_remote_env(env: dict, org_id: str = "", sor=None,