From 773134d0dd303a1d568a6b997c485331763a40e6 Mon Sep 17 00:00:00 2001 From: Seller-1990 Date: Tue, 6 Oct 2026 11:45:54 +0800 Subject: [PATCH 1/6] =?UTF-8?q?fix(launchd):=20=E6=B2=99=E7=AE=B1=20uninst?= =?UTF-8?q?all/status=20=E4=B8=8D=E5=86=8D=E6=93=8D=E4=BD=9C=E7=9C=9F?= =?UTF-8?q?=E5=AE=9E=E8=87=AA=E5=90=AF=E4=BD=9C=E4=B8=9A=EF=BC=88=E6=9C=AC?= =?UTF-8?q?=E6=9C=BA=20npm=20test=20=E4=BC=9A=E6=89=93=E6=AD=BB=E5=A4=96?= =?UTF-8?q?=E6=8C=82=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 真机故障:动不动就「无法连接本机 controller(外挂未运行?):Failed to fetch」。 根因是 launchd.cjs 的 uninstall():LABEL 是模块常量,plistPath(o.dir) 尊重 dir 覆盖, 但 bootout 硬编码 `gui//local.zcode.advisor`,完全忽略 dir。于是 test/launchd.test.js:180/186 传沙箱 dir 调用 uninstall 时,bootout 打的是用户**真实** 的自启作业。launchctl bootout 会终止该作业的进程并把它从 launchd 卸载,KeepAlive 监督随作业一起消失 —— 进程被杀且**永远不会自动重启**,直到用户重开一次 App。 所以本机每跑一次 `npm test`,外挂就被确定性打死一次(不是「动不动」,是必死); 用户重开 App 后又能用,下次测试再打死,表现为间歇性。CI 结构性看不见: test job 跑在 ubuntu-latest,/bin/launchctl 不存在 → ENOENT 被 `catch (_)` 吞掉。 修复: - 新增 realPlistPath()/managesRealAgent(p):以**路径**判定目标是否为用户真实那份 (而非「有没有传 dir」,这样显式指向真实目录或经 ZCODE_ADVISOR_LAUNCHD_DIR 重定向都能正确识别)。 - 新增 canTouchLaunchd(p):仅 darwin + 有 uid + 目标为真实 plist 时才碰 launchd 域。 显式判断取代「抛 ENOENT 再静默吞掉」——后者正是本 bug 在 CI 无法暴露的原因。 - uninstall()/status()/ensureLoaded() 三处同类缺陷一并收口;status() 增加 queried 字段,区分「没查过」与「查过、没加载」,不再把真实作业的态冒充沙箱 plist 的态 (装了 agent 的开发机上「已写入但未加载」断言必红)。 顺带修复生产日志污染:controller.cjs:135 在 require 期固化 LOG_FILE,6 个测试 require controller 却未隔离该 env,导致测试噪音(startApi 的「本机 API 就绪」、 saveUserConfig 的 0.2.17 迁移行)写进用户真实日志,把真实故障淹没、并让「外挂从未 启动」被误读成「外挂反复启动」。 回归防线: - test/launchd.test.js 用子进程打桩 launchctl,断言沙箱 uninstall 调用**零次** launchctl(已实测:对修复前代码该用例失败,对修复后通过)。 - 反向断言真实路径仍会 bootout(防止把卸载功能修坏)。 - test/static-guards.test.js 新增守卫:require controller 的测试必须隔离 ZCODE_ADVISOR_COMPANION_LOG。 验证:npm test 531 项全绿(530 pass / 1 既有 macOS-only skip);在真实 agent 运行时跑完整套件,进程 pid 前后不变;生产日志增量为 0。 --- .../zcode-advisor/tools/companion/launchd.cjs | 55 +++++++++++++- test/companion-lock.test.js | 6 ++ test/companion-zcode.test.js | 4 + test/health.test.js | 9 +++ test/inject.test.js | 3 + test/launchd.test.js | 73 +++++++++++++++++++ test/session-toggle.test.js | 4 + test/static-guards.test.js | 21 ++++++ tools/companion/launchd.cjs | 55 +++++++++++++- 9 files changed, 224 insertions(+), 6 deletions(-) diff --git a/plugins/zcode-advisor/tools/companion/launchd.cjs b/plugins/zcode-advisor/tools/companion/launchd.cjs index e832741..ab18047 100644 --- a/plugins/zcode-advisor/tools/companion/launchd.cjs +++ b/plugins/zcode-advisor/tools/companion/launchd.cjs @@ -115,6 +115,40 @@ function launchctl(args) { return execFileSync('/bin/launchctl', args, { stdio: 'pipe', timeout: 20000, encoding: 'utf8' }); } +// 用户真实自启作业的 plist 路径——**绕过一切覆盖**(dir 参数 / ZCODE_ADVISOR_LAUNCHD_DIR)。 +// +// 关键不变量:LABEL 是固定常量,重定向只能改 plist 的**路径**,改不掉 launchd +// 域里的作业名。所以一旦目标 plist 不是这一份,任何 +// `launchctl print/bootout gui//