Skip to content

feat(client): 跨平台常驻兼容性升级——能力探测 fallback、鸿蒙/WSL 适配、自愈与更新回滚 | cross-platform persistence - #82

Open
370025263 wants to merge 9 commits into
mainfrom
feat/cross-platform-persistence
Open

feat(client): 跨平台常驻兼容性升级——能力探测 fallback、鸿蒙/WSL 适配、自愈与更新回滚 | cross-platform persistence#82
370025263 wants to merge 9 commits into
mainfrom
feat/cross-platform-persistence

Conversation

@370025263

@370025263 370025263 commented Jul 12, 2026

Copy link
Copy Markdown
Collaborator

Summary

跨平台常驻兼容性升级(openspec: cross-platform-persistence):把「按平台名硬编码」的常驻策略改成按能力探测逐级降级,覆盖 Windows / WSL / Ubuntu / 鸿蒙 / 其他 Linux;补齐无 systemd 环境的崩溃自愈与开机自启;自动更新加健康检查 + 回滚 + 坏版本拉黑。每一级降级都在 xskill status 里如实汇报(crash_recovery / boot_autostart / degraded),不伪装成完整常驻,也不因为不完美而拒绝服务。

Changes

废除的武断点

  • WSL 无 systemd 不再硬失败(删 WSLSystemdRequiredBackend)。旧策略双重错误:过苛(明明能以降级模式常驻),且没解决真问题——即使 systemd+linger 齐备,Windows 重启后 WSL VM 也不会自动拉起,真·开机自启只能靠 Windows 侧触发器。
  • systemd linger 失败从 raise 降为 degraded 警告。

新增的兜底层

  • supervisor.py:watchdog 指数退避(1s→300s 封顶,健康 10min 归零)拉起 connect --foreground,SIGTERM 级联、child_pid 回写、防双跑。无 systemd 的 Linux/WSL/鸿蒙 + Windows startup_folder 降级路径共用。
  • 开机自启与后端正交:WSL 经 interop 挂 Windows 计划任务(wsl.exe -d <distro> … xskill start --quiet);linux/鸿蒙挂 crontab @reboot(marker 幂等装卸);挂不上只记 degraded。
  • 鸿蒙识别:os-release ID/ID_LIKE ∈ {harmonyos, openharmony, ohos},仅影响文案与自启方式,主链路与 Linux 一致。
  • _pid_alive 识别僵尸态:容器/精简环境 PID 1 不收割孤儿,Z 态误判存活会让 status 误报 running(docker 矩阵实测抓出的真 bug)。

updater 产品级加固

  • 装完先子进程健康检查(python -m xskill --version),失败 pip 回滚旧版——坏 wheel 不再造成「重启即崩」死循环。
  • 坏版本进 ~/.xskill/update_journal.json 黑名单(PyPI/server 渠道都跳过);pip 网络失败不拉黑;成功记 last_good
  • supervisor 托管时(XSKILL_SUPERVISED=1)全平台统一非零退出交 watchdog 用新版本拉起。

兼容性:state 文件新键全部增量,旧 method 值继续被识别;XSKILL_CONNECT_BACKEND=detached 裸模式语义不变,另支持 supervised|systemd 显式覆盖。

Test plan

  • pytest tests/ --ignore=tests/docker_e2e --ignore=tests/live:1231 passed;唯一失败 test_canary_flip_promote_and_install_new_version 在干净 main 上同样失败(存量环境相关,与本 PR 无关)
  • 新增/重写 78 例常驻+更新相关单测与 e2e 全绿
  • Linux 真进程 e2e(本机 Alinux):supervised 全生命周期——connect 常驻 → SIGKILL 子进程 → watchdog 自愈拉起新 pid → stop 后 watchdog/子进程全退、cron 条目移除、start --quiet 幂等
  • docker 发行版矩阵 4/4 实测通过:ubuntu:24.04 / debian:12 / openEuler 24.03 / 鸿蒙模拟(openEuler + os-release 覆写,断言 flavor=harmony):bash tests/docker_e2e/platform_matrix/run.sh all
  • WSL 策略矩阵(无 systemd→supervised+Windows 任务;interop 拼装;linger 失败降级)单测覆盖(CI 无真 WSL)
  • Windows 真机 schtasks 生命周期 CI 通过(windows-latest,XSKILL_WIN_E2E=1,真 Task Scheduler)
  • CI 接线:lifecycle job 增自愈 e2e;nightly/dispatch 跑 docker 矩阵

多端实测抓出并已修复的 3 个真 bug

  1. 容器僵尸误判存活(docker 矩阵抓出):PID 1 不收割孤儿时,被停掉的 watchdog 滞留为僵尸,signal-0 探测误判其存活 → status 误报 running。_pid_alive 现读 /proc/<pid>/stat 识别 Z 态。
  2. Windows 控制台编码崩溃(windows e2e 第 1 轮抓出):cp1252 控制台下 CLI 中文输出 UnicodeEncodeError,且炸点在 schtasks 任务装好之后——用户看到 traceback+exit 1 实际却成功。入口统一 reconfigure UTF-8。
  3. pythonw 下常驻进程秒崩(windows e2e 第 2 轮抓出,存量 bug):schtasks/启动文件夹用 pythonw 免弹窗,但 pythonw 的 sys.stdout/stderr 是 None,connect --foreground 开头的 print 直接 AttributeError——Windows 主路径常驻此前从未真正跑起来过。入口补 devnull 流。

另外 windows e2e 第 3 轮确认:无交互登录会话(CI/服务上下文/断开 RDP)里 LogonTrigger 任务 /Run 返回 0 但永不启动 → 新增「按观测验证」降级:观测窗口内拿不到任务进程就 direct-spawn supervisor,计划任务保留作下次登录自启。

CI 现状说明

ut-it 的 macOS/Windows 腿在 main 上本就是红的(test_skill_tools_atom 路径解析、test_dashboard_console_p2 WinError 5,均与本 PR 无关;最近 3 次 main run 全 failure)。本 PR 把连接类 e2e 的 needs 从 ut-it 改为 verify-build,避免关键验证被无关红灯连坐——存量红建议另开 issue 修。

Linked issues

🤖 Generated with Claude Code

370025263 and others added 4 commits July 12, 2026 17:54
能力探测式后端选择、WSL 去武断化(废除无 systemd 硬失败)、鸿蒙识别、
supervised watchdog 崩溃自愈、cron/WSL-interop 开机自启、updater 健康
检查+回滚+坏版本拉黑,以及多端测试矩阵设计。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…d persistence chain

- 新增 supervisor.py:watchdog 指数退避拉起 connect --foreground,
  无 systemd 平台(精简 Linux/未启 systemd 的 WSL/鸿蒙)的自愈层
- LinuxServiceBackend 重写:systemd → supervised 能力降级;废除
  WSLSystemdRequiredBackend 硬失败;linger 失败降级为警告
- 开机自启挂载与后端正交:WSL 经 interop 挂 Windows 计划任务
  (systemd+linger 也管不了 Windows 重启后 VM 不自启,必须 Windows
  侧触发);linux/鸿蒙用 crontab @reboot(marker 幂等管理)
- 鸿蒙识别(os-release ID/ID_LIKE ∈ harmonyos/openharmony/ohos),
  仅影响提示与自启方式,主链路与 Linux 一致
- Windows startup_folder 降级改拉 supervisor——降级路径也有自愈
- status 新增 flavor/crash_recovery/boot_autostart/degraded 字段,
  每级降级如实汇报
- _pid_alive 识别僵尸态:容器里 PID1 不收割孤儿,Z 态误判存活会让
  status 误报 running
- cli: connect --supervise(内部)、start --quiet(自启触发器幂等入口)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…llback, blacklist

- pip 装完先子进程验证 python -m xskill --version,失败回滚旧版:
  坏 wheel 不再把常驻进程带进重启即崩死循环
- 健康检查失败的版本记 ~/.xskill/update_journal.json 黑名单,
  PyPI/server 两条渠道都跳过,杜绝升级-崩-回滚空转;成功记 last_good
- pip 安装失败(网络抖动)不拉黑,下轮重试
- supervisor 托管(XSKILL_SUPERVISED=1)时全平台统一非零退出交
  watchdog 重启;手动 xskill update 同样走验证+回滚

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…sts & CI wiring

- 重写 test_wsl_persistence_policy:WSL 无 systemd 落 supervised 而非
  拒绝;interop 任务拼装;自启决策表;鸿蒙识别;cron marker 幂等
- 新增 test_supervisor(退避/respawn/防双跑)、
  test_updater_health_rollback(回滚/拉黑/journal 容损)
- e2e: supervised 真进程自愈(kill 子进程→watchdog 拉起→stop 全清理,
  假 crontab shim 不动真 crontab);Windows 真机 schtasks 生命周期
  (XSKILL_WIN_E2E=1 门控)
- docker 平台矩阵 ubuntu:24.04/debian:12/openEuler/鸿蒙模拟,容器内
  跑同一套自愈 e2e;CI: windows e2e job + nightly 矩阵 job

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
try:
deadline = _time.time() + 5
while _time.time() < deadline:
stat = open(f"/proc/{pid}/stat").read()
# comm 可含空格/括号,状态字段取最后一个 ')' 之后的首个 token
if stat.rsplit(")", 1)[1].split()[0] == "Z":
return False
except (OSError, IndexError):
subprocess.run(
["taskkill", "/PID", str(pid), "/T", "/F"],
capture_output=True, check=False)
except OSError:
["schtasks.exe", "/Delete", "/TN", WINDOWS_WSL_BOOT_TASK, "/F"],
capture_output=True, text=True, check=False, timeout=15,
)
except (OSError, subprocess.SubprocessError):
continue
try:
signal.signal(sig, self._request_stop)
except (OSError, ValueError):
stat = Path(f"/proc/{pid}/stat").read_text(encoding="ascii")
if stat.rsplit(")", 1)[1].split()[0] == "Z":
return False
except (OSError, IndexError):
370025263 and others added 2 commits July 12, 2026 18:08
…le connect e2e from pre-existing ut-it reds

main 最近 3 次 CI 本就 failure(test_skill_tools_atom /tmp 符号链接、
test_dashboard_console_p2 WinError 5,均与常驻链路无关),needs: ut-it
会让本 PR 的关键验证(Windows schtasks e2e、自愈 e2e)永远 skipped。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…out on Windows

CI windows e2e 首跑抓到的真 bug:cp1252 控制台下 cmd_connect 的中文提示
直接炸 UnicodeEncodeError,且炸点在 schtasks 任务已装好之后——用户看到
traceback + 退出码 1,实际却成功了。main() 入口统一 reconfigure utf-8。

附带:lifecycle e2e 的 stub PyPI 补非 dev 版 0.0.0(CI 浅克隆下本地版本
是 dev 版会被 updater 过滤,releases 只剩 dev 版时查询结果为空)。该 e2e
此前在 main 上一直被 ut-it 存量红灯连坐 skip,从未暴露。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread src/xskill/cli.py
for stream in (sys.stdout, sys.stderr):
try:
stream.reconfigure(encoding="utf-8", errors="replace")
except (AttributeError, OSError, ValueError):
…treams under pythonw

Windows e2e 第二轮抓出的存量 bug:schtasks/启动文件夹常驻用 pythonw 免
弹窗,但 pythonw 的 sys.stdout/stderr 是 None,connect --foreground 开头
的 print 直接 AttributeError——常驻进程秒死、schtasks 每分钟空转重启、
pid 永远为 None。main() 入口补 devnull 流(与 UTF-8 重配同处兜底),
updater 健康检查子进程同样受益。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
def test_main_reconfigures_streams_to_utf8(monkeypatch, tmp_path):
monkeypatch.setattr(cli.sys, "platform", "win32")
# 模拟 cp1252 控制台:中文写入会炸的流
out = open(tmp_path / "out.txt", "w", encoding="cp1252")
monkeypatch.setattr(cli.sys, "platform", "win32")
# 模拟 cp1252 控制台:中文写入会炸的流
out = open(tmp_path / "out.txt", "w", encoding="cp1252")
err = open(tmp_path / "err.txt", "w", encoding="cp1252")
…r 降级 | verify by observation, not exit code

Windows e2e 第三轮定位:LogonTrigger 任务(未存凭据)只能在用户已登录的
交互会话启动;服务上下文/CI/断开 RDP 里 /Run 返回 0 但任务永不进
Running。新策略:/Run 后观测窗口内拿不到任务进程 PID → 直接 detach 拉起
supervisor 保证当下常驻(launch=direct-spawn,自愈=watchdog),计划任务
保留作下次登录自启;stop 时连 watchdog 进程树一起清。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
try:
subprocess.run(["taskkill", "/PID", str(pid), "/T", "/F"],
capture_output=True, check=False)
except OSError:
- SKILL.md 增加「Step 1 检测平台」:WSL/鸿蒙/systemd 探测方法 + 平台→reference 路由表
- 新增 platform-{windows,wsl,linux-systemd,linux-nosystemd}.md,按代码真实降级链写常驻机制与实修坑
- installation.md 手工常驻段替换为能力探测 start/stop/status 流程 + status 降级字段

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant