Repository navigation
fix(native-accounts): coordinate product clients during handoff - #7193
Conversation
An unattended handoff failed with other_clients_busy: after the App root was force-quit, an orphaned App code-mode host and the LoopX chat transport still held the GPT home, so the credential transaction never ran. Retire holders that belong to the same App bundle before waiting for idle, and pause the product-owned chat launch agents that keep an app-server attached while idle, restoring them even when the handoff fails. Unrelated CLI/file owners remain a hold and are never killed. Verified with a new offline smoke (App-bundle helper reaping, chat agent pause/restore), the native host, provider, recovery and operator smokes. Bump 0.12.4. Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>
loopx-agent
left a comment
There was a problem hiding this comment.
Reviewer: model_agent; gpt-6.1-sol; OpenAI; runtime_reported; reasoning_effort=max
Request changes conclusion (author-owned PR; GitHub blocks formal self-review)
发现 P1 进程归属缺口与 P2 服务恢复缺口,以下覆盖整个 45602881febee1c90f91ebd614628f710505b464 提案。
动机
已启用原生账号自动换号及 GPT 客户端重启、额度耗尽后仍被本地进程占用挡住的使用者。
旧版本在 App 退出后仍遇到 helper 或 Chat 占用而停止换号;新版本尝试终止 helper、暂停 Chat、完成换号再恢复服务,减少手工关闭和重开。
干净模拟流程能暂停并恢复服务、完成换号;但无关 CLI 使用同一 App 内置二进制时被终止,bootout 部分生效后超时会漏掉恢复,恢复非零也可能返回换号成功。
本次不授权终止无关 CLI、不改变额度选择或凭据事务 owner,不重放业务;本评审未操作真实账号、App、launch agent 或发送进程信号。
改动思路
进程和服务协调应留在现有 native IO adapter,原凭据事务与 idle 锁继续归原 owner;但二进制路径前缀不能替代被授权 App 实例的身份,局部列表也不足以承受部分生效的暂停失败。
本 PR 只修授权换号的本地占用协调;最小修复是原实例归属栅栏及可恢复的暂停/恢复,不需要改额度 selector、复制账号 vault 或增加常驻执行器。
原有设置/CLI、detached intent、TypeScript 账号选择和 native 凭据事务均保持。新增处理放在原 App 关闭后、idle 等待前,并在 finally 恢复已暂停服务。思路可减少人工关闭,但“产品自有”和“同一二进制”都不能自动扩大用户的实例授权;所有恢复分支必须知道哪些服务可能已停,不能仅依赖成功返回的局部列表。
下表是相同不可变 base/head 生产 switch 方法的模拟 IO 对照,未实际调用 kill、launchctl 或账号写入。
| 输入 | 基线 | 当前 head |
|---|---|---|
| 普通成功/重启关闭 | 无新增服务效果 | 正常恢复/关闭时仍零效果 |
| 无关 CLI 使用 App 内置 codex | 保持 other_clients_busy,不发信号 | 发 SIGTERM,继续换号 |
| bootout 已卸载后超时 | 原服务仍运行 | paused 赋值未完成,finally 无法恢复 |
| 恢复 bootstrap 返回非零 | 原服务仍运行 | 仍可返回换号成功,服务停着 |
具体改动
全量5文件+175/-30:原 native IO 文件、新80行离线 smoke、CI调用及两处版本号0.12.4。原 TypeScript selector、凭据 writer、额度 freshness、selection CAS 和历史数据没有修改。规格为 packages/loopx-codex-provider-routing/NATIVE.md,spec_revision b8b9c1d77c17edf75e0d8860e533a7785b9bd224,在 diff 前读取。按原章节映射:inspected-client-only 未满足,原文要求只关闭已观察 GPT 实例、其余CLI/file owners保持占用;failure-recovery 未满足,新增暂停的部分效果未进入恢复路径;default-off-account-effects 的原可选授权门保留,restart=false 的双版本对照均无信号与服务操作。没有添加新法规或未来 RFC 门槛。
关键代码讲解
NativeHost.switch在原 selection/quota 前置检查后关闭原 App,新增 retire/pause,继续由原 manager idle/switch 写凭据;服务恢复放入 finally。不过paused = pause_chat_agents()只有整个 helper 成功返回才有恢复列表。retire_app_helpers对 lsof 返回的所有home holder读 ps命令,仅靠startswith(bundle + "/")决定 SIGTERM。没有验证它属于刚关闭的 App 启动、是否 orphan 或 PID 启动身份;用户从该 bundle 内二进制单独启动的 CLI 也会命中。pause_chat_agents根据固定 label/plist 和 loaded状态执行bootout;直到外部操作返回才追加列表。若操作已生效、等待超时,调用者还保留空列表。resume_chat_agents执行 bootstrap,但不检查返回码或重读 loaded 状态。即使恢复失败,后面的账号读回仍可能成功,使用户误以为整个自动切换可用。
对主干的风险
[P1] 终止必须绑定已观察的 App 启动,而不是 bundle 路径。 在相同 restart_clients=true, force_quit_clients=false 输入下,单独从 App 内置 codex 启动的CLI持有同一home。不可变基线保留 other_clients_busy、无信号;当前生产 switch 调用新 reaper,对该独立 PID 发SIGTERM并继续换号。原正常关闭的身份 fence 不会约束后续遍历得到的新 PID。最小修复是复用原实例的可核验 helper 归属与启动身份,无法证明或归属变化就保持占用;补独立同bundle CLI与后来新App启动的负例,不能用扩大kill权限解决idle失败。
[P2] 暂停的部分效果必须仍有恢复 owner。 精确生产 switch 中,模拟bootout已卸载服务后抛TimeoutExpired,helper不返回,finally收到空列表,服务未恢复、凭据未切换。另一对照中换号完成,但恢复bootstrap返回5被忽略,switch仍返回成功而服务停着。应在可能生效的暂停前保留恢复意图,逐项维护/调和未知结果;检查恢复返回码和服务状态,失败应留下明确可恢复状态,而不是吞掉错误或盲目重复换号。
五个现有离线smoke通过;operator首轮因评审环境少package/src而导入失败,补正确PYTHONPATH后22项通过,原失败保留,不称产品修复。新双版本十场景对照包含正常、关闭、独立CLI、部分暂停超时及恢复非零,后三项在head出现上述违约;所有系统调用、服务和凭据事务都被模拟,未声称真实macOS效果资格。现有测试只stub单个helper和完全成功的launchctl,无法证明原switch失败恢复闭环。修复后补原switch负例,并在独立临时原生App/服务环境验证归属、暂停、恢复及后续正常请求,不操作真实账户或生产服务来做故障注入。
语义与 CI 对齐
原配置默认仍关闭,restart=false基线/head均不触发新增效果;已开启restart的用户会自动得到更广的进程/Chat服务处理。这个默认行为变化已明确记录,但原NATIVE.md的无关CLI保持占用与实例授权义务没有因版本更新失效。没有新共享枚举或Core调度义务;局部paused列表是效果恢复信息,不能把成功返回当成完整事实。native premerge5direct/1selected执行通过,汇总仍quality_stale_receipt/exit1,不称完整资格通过。CI waiting关闭,本次未查询或等待远端CI。
我的整体评价
当前交付为not_yet_proven:长程工作和用户体验都有具体回归,独立CLI可能丢失进行中的工作,Chat可能停在无人恢复的状态;正常smoke通过不能消除这些后果。修复应留在原实例归属与服务恢复owner,无需新账号库、后台监控或额外确认轮次。进程和服务协调应留在现有 native IO adapter,原凭据事务与 idle 锁继续归原 owner;但二进制路径前缀不能替代被授权 App 实例的身份,局部列表也不足以承受部分生效的暂停失败。 本 PR 只修授权换号的本地占用协调;最小修复是原实例归属栅栏及可恢复的暂停/恢复,不需要改额度 selector、复制账号 vault 或增加常驻执行器。
已检查受影响的既有 CLI/私聊服务/设置消费者;没有页面布局、控件或折叠呈现改动,视觉变更验收不适用。 本次未验生产安装采用、真实用户业务效果、持续轨迹与完整成本;账号项未执行真实本地客户端或凭据事务。
代码量与原有占用问题基本相称,新smoke应保留并加入原switch的负例,不必为减少行数删除已有事务兼容。现有idle/CAS/rollback继续归native manager,未测试的真实客户端资格明确保持未知。
English verdict: REQUEST_CHANGES at 4560288. The new reaper SIGTERMs an unrelated CLI merely because it uses the same App-bundled binary; the same base case correctly holds. A partially successful bootout timeout loses restoration ownership, and nonzero bootstrap restoration can be reported as success. Fence original-launch ownership and retain/read back pause recovery across ambiguous effects. Five offline smokes pass; deterministic exact-switch base/head counterexamples fail. No live process, service or credential effects were exercised.
| capture_output=True, text=True, timeout=5) | ||
| except (OSError, subprocess.SubprocessError): | ||
| continue | ||
| if not command.stdout.strip().startswith(bundle + "/"): |
There was a problem hiding this comment.
[P1] Fence helper termination to the inspected App launch
An unrelated shell launches the bundled codex CLI and holds the same native home after the inspected App exits. Prefix matches, SIGTERM kills independent CLI even with force_quit_clients=false. Base holds other_clients_busy without signaling. Require proven original-launch ownership with executable/start/ancestry fences; unrelated or uncertain owner remains hold. Independently exercised through the exact production switch with simulated IO at immutable base/head; no live process or credential effects.
| # an unrelated CLI/file owner remains a hold, never a kill | ||
| # target. | ||
| self.retire_app_helpers() | ||
| paused = self.pause_chat_agents() |
There was a problem hiding this comment.
[P2] Retain recovery ownership before partially successful service pause
bootout unloads the loaded Chat service then raises TimeoutExpired; helper never returns. A separate bootstrap nonzero restoration case is also covered. Assignment to paused never completes, finally receives empty list and cannot restore; nonzero restore also returns success. Original service is left unavailable. Record/retain recovery intent before effect, reconcile ambiguous stop and restore/read back in all paths; expose durable pending restoration instead of silently succeeding. Independently exercised through the exact production switch with simulated IO at immutable base/head; no live process or credential effects.
…61024 Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>
Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>
loopx-agent
left a comment
There was a problem hiding this comment.
Reviewer: model_agent | gpt-6.1-sol | OpenAI | runtime_reported | reasoning_effort=max
Request changes conclusion (author-owned PR; GitHub blocks formal self-review)
动机
需要在原生客户端仍持有同一账号目录时,按已有授权切换账号并重新打开客户端的用户。
正常退出 App 后,它的残留 helper 和同目录 Chat 服务仍可能占用文件,使账号事务被拒绝;上一轮实现还曾把独立 CLI 当成 App 后代,或在暂停、恢复服务失败时丢失恢复事实。
用户已启用客户端重启后请求一次账号切换:原来残留占用使事务停在原账号;当前隔离 macOS 验证能退休已观察的 App 后代、暂时释放测试服务占用、切换合成账号并恢复服务。独立 CLI 仍应阻止切换。
正常交接和旧评审的进程身份、部分暂停、恢复失败问题已有独立正反例支持;但既有账号交接测试从基线 71 项通过变成当前 64 过、7 失败,现有夹具未适配新恢复入口,尚不能完成该提交的验证交付。
本评审只资格化隔离测试 App、唯一测试 launchd 服务与合成账号的机械交接和恢复,不证明生产 Chat 在途业务连续性、真实账号采用或长程净收益;默认关闭、独立 CLI 保留、原账号事务 owner 和原选择/配额栅栏均保留。
改动思路
进程身份和服务效果属于原生 provider I/O,账号 idle、锁、CAS 和事务继续复用原 Accounts owner。私有恢复事实必须在外部效果前保存,不能用服务名或可执行文件相同推断进程所有权,也不能把非零/超时当作服务已恢复。Python 留在既有 macOS provider 边界,不新增控制平面决策 owner。
退出前按内核父子关系记录 App 后代的 PID、可执行文件和微秒级启动身份;退出后只处理这些仍匹配的后代。对已确认持有同目录的允许列表服务,先写私有恢复意图再暂停,按原 plist 摘要恢复并读取实际加载状态。恢复由原 worker 身份隔离,失败意图供后续请求处理。
同步 tests/presentation/test_codex_accounts.py 的 client_host 夹具,提供隔离 manager.root 及明确的进程/服务输入,保留原有退出顺序、拒绝、选择变化和配额断言后重跑 71 项;不要跳过旧用例或增加只为掩盖夹具缺项的运行时回退。
旧评审的独立 CLI 误杀、部分暂停意图丢失、恢复失败误报均重新映射到当前完整 head,不能只凭旧提交或 green smoke 判断已解决。当前的恢复机制有真实原生边界支持,但既有测试失败仍须在本提交修复。
具体改动
[P2] 当前新增的入口恢复会先调用 _chat_recovery(),而既有 tests/presentation/test_codex_accounts.py:650 的 client_host 通过 NativeHost.__new__ 构造实例,manager 只有 switch/idle,缺少新路径所需的 root。相同测试、解释器和其余源码,基线 71 过,当前 64 过、7 失败,均为 AttributeError: SimpleNamespace has no attribute root,原退出顺序、拒绝、手动选择和过期配额断言因此没有执行。这是 PR 引入的测试接口回归,不是已证明的生产 Accounts 崩溃。请随新增恢复/进程接口补齐隔离夹具并保留全部原断言,然后重跑该文件;五个包级 smoke 不能替代这组既有覆盖。
不可变已接受规格:packages/loopx-codex-provider-routing/NATIVE.md,版本 7c48375df5aa4824c8d6fba0baef1f96de6cc87e。先读原规格再审补丁;下列是审计映射标签,非伪造原编号。
-
native-owner-and-idle(implemented):Native Accounts remains the sole credential transaction owner; all other file owners hold switching. -
observed-client-only(implemented):Only the observed original App and its actual descendants may be retired; independent CLI remains untouched. -
failed-handoff-recovery(implemented):Failure reopens the original App and restores known client effects without replaying selection. -
existing-handoff-coverage(not_met):Existing handoff/refusal/selection/quota checks remain executable at the new native interface.
6 files +602/-31; production +289/-29。完整差异、既有 owner/调用者、全量讨论、DCO 与公开边界已核验。新增私有效果日志与 provider 0.12.4 同部署;没有 public CLI/schema 版本分支。进程/服务恢复日志须保留到效果读回完成,不能靠升级丢弃。当前新恢复调用让旧 new 夹具缺项,必须同步夹具。
关键代码讲解
inspect_client_helpers(packages/loopx-codex-provider-routing/src/loopx_codex_provider_routing/native_host.py:286):正常退出前遍历内核父子关系并记录真实后代身份;同一个 bundle 中的独立 CLI 不因此成为后代。
retire_app_helpers(packages/loopx-codex-provider-routing/src/loopx_codex_provider_routing/native_host.py:314):退出后只对预先观察且 PID/exe/启动身份仍匹配的 helper 发出 TERM;新 App、独立占用者和身份变化仍 hold。
pause_chat_agents(packages/loopx-codex-provider-routing/src/loopx_codex_provider_routing/native_host.py:362):确认允许标签、私有 plist 摘要和同目录占用,先持久化恢复事实再 bootout,部分失败也保留可恢复事实。
resume_chat_agents(packages/loopx-codex-provider-routing/src/loopx_codex_provider_routing/native_host.py:432):保护仍活着的原 worker,按原定义恢复并读回加载状态;失败保持意图,后续请求不重放账号选择。
NativeHost.switch(packages/loopx-codex-provider-routing/src/loopx_codex_provider_routing/native_host.py:476):新增入口恢复随后使用原 Accounts 事务;这里使旧 client_host 夹具先访问缺失的 manager.root,7 项既有测试在行为断言前失败。
对主干的风险
五个包级 smoke 全通过(operator 含 22 项);预合并 5 项直接/编译检查与 1 项公开边界检查通过。8 个场景在不可变基线、旧缺陷提交、当前提交共 24 个配对执行,独立 CLI、部分卸载、失败恢复、定义变化和关闭开关均有明确断言。真实 macOS 的 6 个隔离执行覆盖正常交接、独立 CLI、部分效果丢回复、恢复失败后续请求和并发 worker 保护。
同一未修改的 tests/presentation/test_codex_accounts.py,加载不可变基线 native_host.py 时 71 过;当前 head 为 64 过、7 失败,均在 _chat_recovery 访问 self.manager.root 时抛 AttributeError,未进入原有行为断言。真实 Accounts 有 root,因此这是新增接口与既有测试夹具不兼容,不能据此宣称生产账号 owner 缺少 root。
7 项当前验证回归属于本 PR,包级 smoke 和真实隔离演练不能将其改记为无关失败。真实 launchctl 故障使用受控的丢回复/非零注入;只替换允许列表中的测试服务标签,未操作生产 Chat、用户 App 或真实账号。初次测试清理遗漏了一个自己创建的 helper,随后按内核身份精确清理并读回零残留;原失败记录保留。未验证生产业务、已安装版本采用或长期收益。
确定性三臂只替换不可变 native_host 模块并保留完整前置输入;实际 macOS 演练使用真实内核身份、AppKit、launchctl 和原账号 owner,OS PID/启动时间各自保留,不能把它称为完全相同的进程快照。部分失败注入明确限定在回复/恢复 I/O,没有替代账号写入 owner。
语义与 CI 对齐
chat-recovery.json records an observed, unfinished external effect: original worker PID/exe/start identity plus allowed service label/plist digest. It is neither consent, account choice nor a second credential transaction owner. Missing means no pending restoration; live matching worker is protected; changed definitions or unresolved bootstrap retain the pending fact.
restart_clients remains default false; force quit remains separately authorized; only observed App descendants and loaded allowed-label services actually holding the same account home may be coordinated. 开关关闭/未恢复入口做了实际基线与当前配对。原配额、结算、权限和停止条件不靠文案推断。CI 按契约未查询、轮询或等待;本地检查结果、跳过与人工 hold 分别保留。
我的整体评价
REQUEST_CHANGES,仅对应 7193@0ffbde9fe8e2f2295d4e1c6703003daf682c92a6。旧缺陷修复有独立证据,但当前 7 项既有测试接口回归阻止该提交通过评审;修复夹具后审新的完整 head。
长期工作:preserved;使用体验:improved。正常交接和旧评审的进程身份、部分暂停、恢复失败问题已有独立正反例支持;但既有账号交接测试从基线 71 项通过变成当前 64 过、7 失败,现有夹具未适配新恢复入口,尚不能完成该提交的验证交付。
已检查现有 native settings/CLI 和 provider 交接读回;本 PR 未改变页面、布局或视觉素材,不需新截图。 未验证范围:7 项当前验证回归属于本 PR,包级 smoke 和真实隔离演练不能将其改记为无关失败。真实 launchctl 故障使用受控的丢回复/非零注入;只替换允许列表中的测试服务标签,未操作生产 Chat、用户 App 或真实账号。初次测试清理遗漏了一个自己创建的 helper,随后按内核身份精确清理并读回零残留;原失败记录保留。未验证生产业务、已安装版本采用或长期收益。
有界重构判断:同步 tests/presentation/test_codex_accounts.py 的 client_host 夹具,提供隔离 manager.root 及明确的进程/服务输入,保留原有退出顺序、拒绝、选择变化和配额断言后重跑 71 项;不要跳过旧用例或增加只为掩盖夹具缺项的运行时回退。 本评审只资格化隔离测试 App、唯一测试 launchd 服务与合成账号的机械交接和恢复,不证明生产 Chat 在途业务连续性、真实账号采用或长程净收益;默认关闭、独立 CLI 保留、原账号事务 owner 和原选择/配额栅栏均保留。
这是 author-owned COMMENTED 结论,不是 GitHub 正式 self-approval/rejection。当前批准与 approval-closeout、readiness、人工要求和合并权限分开读回;本评审不合并。
English verdict: REQUEST_CHANGES. REQUEST_CHANGES. The observed-process and durable service-recovery fixes pass independent simulated and isolated real macOS checks, including preservation of an independent CLI. However, the unchanged existing account handoff suite regresses from 71 passing at the immutable base to 64 passing and seven fixture-interface failures at this head. Adapt client_host to the new recovery inputs without removing its original assertions. This is a test fixture regression, not evidence that real Accounts lacks root.
| force_quit_clients: bool = False, not_after: float | None = None) -> dict: | ||
| if sys.platform != "darwin": | ||
| raise HostError("platform_unavailable") | ||
| self.resume_chat_agents() |
There was a problem hiding this comment.
[P2] 当前新增的入口恢复会先调用 _chat_recovery(),而既有 tests/presentation/test_codex_accounts.py:650 的 client_host 通过 NativeHost.__new__ 构造实例,manager 只有 switch/idle,缺少新路径所需的 root。相同测试、解释器和其余源码,基线 71 过,当前 64 过、7 失败,均为 AttributeError: SimpleNamespace has no attribute root,原退出顺序、拒绝、手动选择和过期配额断言因此没有执行。这是 PR 引入的测试接口回归,不是已证明的生产 Accounts 崩溃。请随新增恢复/进程接口补齐隔离夹具并保留全部原断言,然后重跑该文件;五个包级 smoke 不能替代这组既有覆盖。
…61025 Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>
Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>
loopx-agent
left a comment
There was a problem hiding this comment.
Reviewer: model_agent | gpt-6.1-sol | OpenAI | runtime_reported | reasoning_effort=max
Approval conclusion (author-owned PR; GitHub blocks formal self-approval)
动机
显式允许重启客户端、需要原生账号安全交接并恢复原产品客户端的用户。
原 App 正常退出后,其子 helper 与同 home 的 Chat 服务仍可能占用凭据目录,导致安全空闲门拒绝切换。较早补丁又曾误杀独立 CLI,并在卸载回复丢失或恢复失败时遗失恢复责任;上一头还有七项旧夹具缺 root 的回归。
当前完整文件 71 项通过,五项包 smoke 与编译通过;37 个 test 函数的断言和 decorators/参数 AST 均保留。8 个同输入场景跨不可变基线、原缺陷与当前共 24 次执行;另有 6 次真实 macOS kernel/AppKit/launchctl/原 Accounts 演练:普通交接由拒绝 A 到切 B 并恢复,独立 CLI 保留拒绝 A,卸载丢回复恢复 A,恢复失败保 B 并明确报错、后续恢复成功,并发 status 不撤销活 worker 的暂停。
完成原生交接安全与恢复的有界修正;仅合成账号、独有测试 App/服务和私有 home。未安装共享版本、触碰生产账号或 Chat、调用真实模型;模型理解、真实用户采用和长期收益未测。
改动思路
保留原 Accounts idle/锁/selection/quota 原子事务,不靠杀掉所有持有者绕过空闲门。仅 NativeHost 补客户端生命周期见证与恢复,现有设置/CLI 承接权限;新通用 supervisor 或第二账号 owner 均无必要。
退出前冻结实际父子关系和内核身份,退出后只清理仍同身份且持有此 home 的已见后代。已加载、实际持有同 home 的已知 Chat 服务,在卸载效果前持久保存私有恢复意图;恢复后读实际 loaded 状态,失败明确返回并由下一 native 请求继续恢复,不重复账号事务。
内核身份、唯一恢复 journal 与直接 Host helpers 各有实际效果/失败边界,不能靠 bundle 路径或成功回复压缩掉。新增夹具只补完整私有 root 与显式进程/服务输入;原断言/参数不删除、不跳过。
具体改动
本结论仅对应 7193@6f5aa66bb24f3d1724861595b0be403f1ae44332。先读不可变原规格 packages/loopx-codex-provider-routing/NATIVE.md(920f18809d906b840325c4f40013e9224e4a3a2e),再审完整补丁与讨论。以下是审计映射标签,不冒充原规格编号。
-
observed-launch-only:Only the original observed App launch and independently verified exact descendants may be retired; unrelated owners remain holds. -
opt-in-and-original-transaction:Restart/force remain explicit independent permissions; original idle, locking, selection and quota fences remain. -
recover-without-replay:Observe and preserve original service definition, retain restoration before ambiguous effects, recover without repeating account selection. -
live-adoption-and-long-term-utility(out_of_scope):完成原生交接安全与恢复的有界修正;仅合成账号、独有测试 App/服务和私有 home。未安装共享版本、触碰生产账号或 Chat、调用真实模型;模型理解、真实用户采用和长期收益未测。
7 files +636/-56; production +289/-29。新增包内私有 chat-recovery intent 保存允许 label、原 plist SHA、worker PID/内核身份;它是恢复事实而非账号选择权限。复用既有 SCHEMA/HostError envelope;chat_pause_failed/chat_resume_failed 等为本 native provider 内的具体错误,不是新的通用控制平面义务。 每个 commit 的 DCO 与全部路径公开边界均核对。
关键代码讲解
NativeHost.inspect_client_helpers(packages/loopx-codex-provider-routing/src/loopx_codex_provider_routing/native_host.py:286):退出前以实际父子关系记录后代和内核身份;同目录的独立 CLI 不因此获得清理授权。
NativeHost.pause_chat_agents(packages/loopx-codex-provider-routing/src/loopx_codex_provider_routing/native_host.py:362):只暂停实际持有当前 home 的已见允许服务,效果前持久写原 plist 摘要及 worker 身份。
NativeHost.resume_chat_agents(packages/loopx-codex-provider-routing/src/loopx_codex_provider_routing/native_host.py:432):活 worker 的 intent 不被并发读抢回;恢复以当前定义和实际 loaded readback 为准,失败留 intent。
NativeHost.switch(packages/loopx-codex-provider-routing/src/loopx_codex_provider_routing/native_host.py:476):仍由原空闲/锁/CAS/selection/quota owner 完成一次账号事务;finally 恢复客户端,恢复不重放账号选择。
对主干的风险
Ruff 声明全范围、配置的 17 文件 mypy、完整 71 pytest、五项包 smoke(operator 22 场景)和编译全过。标准预合并 5 项直接检查与 1 项公开边界检查全过,严格质量回执 cqr_3a3c61fa4aeb700969db 为 PASS,精确范围 verify valid;无自动测试失败。
六个生产/文档/metadata/smoke 路径与上一已审头逐字相同,但本次仍按新精确 head 运行全部验证;新增旧测试夹具修复了上一头 64 过/7 失败,旧失败记录保持在旧头。包仅 macOS 实际演练;其他平台门继续拒绝,生产进程竞态与真实账号长时间采用未资格化。
独立 CLI 不终止;非当前实例及 home 范围保持 hold。bootout 已生效但回复丢失仍有 intent;bootstrap 非零不当成功,实际 loaded 才收尾。更改服务定义保留原字节并明确失败。恢复幂等不增加 switch_count,restart-off 的完整结果及效果与基线相同。
真实 macOS 演练使用唯一合成 App/label 和合成 JWT,注入卸载丢回复及恢复失败。真正账号事务和 kernel/AppKit/launchctl 参与;不是生产账号使用证明。原 history 字节保留;真实 drill 的 last.json 自等式不证明事务次数,次数以独立确定性 switch_count 与源码边界为准。12 次独立清理均确认零 owned 进程/服务。
语义与 CI 对齐
显式 restart_clients=true;原 App 的已观察后代及同 home 已见服务树。独立 CLI、旧 orphan、不同 home、后续新 App、改变的 plist 和身份不完整均不借用授权。force 仍独立默认关闭;不能重放已提交工作。 restart-off 完整结果与效果对照相同;NATIVE.md 是对机器边界的披露,不是自动重启或重放授权。 未查询、轮询或等待 CI;实际本地失败、质量回执、人工 hold、就绪和权限分别保留。
我的整体评价
APPROVE,仅批准 7193@6f5aa66bb24f3d1724861595b0be403f1ae44332 的有界改动判断;未发现由这次补丁引入且尚未修复的阻塞缺陷。 上一头的七项夹具失败在本头已按原断言完整重跑修复,不改写旧头历史。
长期工作与使用体验:有界路径 improved;不扩大为父目标完成或真实采用/长期净收益。完整 diff 无 UI 布局/文案控件变化;原 restart/force 设置和 CLI/API 仍承接权限,真实原生 App 演练验证协调路径。 完成原生交接安全与恢复的有界修正;仅合成账号、独有测试 App/服务和私有 home。未安装共享版本、触碰生产账号或 Chat、调用真实模型;模型理解、真实用户采用和长期收益未测。
有界重构判断:内核身份、唯一恢复 journal 与直接 Host helpers 各有实际效果/失败边界,不能靠 bundle 路径或成功回复压缩掉。新增夹具只补完整私有 root 与显式进程/服务输入;原断言/参数不删除、不跳过。
本次为 author-owned COMMENTED 批准结论;GitHub 不支持正式自批。 approval-closeout、合并就绪、质量/人工要求与 merge authority 分开读回;本评审不合并。
English verdict: APPROVE. APPROVE. The complete existing suite now passes all 71 tests with unchanged assertions and parameters; all five package smokes pass. Twenty-four same-input counterfactual executions and six real isolated macOS drills cover exact App descendants, independent CLI holds, partial unload, restore failure/recovery and concurrent status. Restart-off parity is preserved. The exact-scope quality receipt verifies PASS and local premerge passes. Synthetic accounts and owned fixtures only; no production adoption, shared install or merge authority is inferred.
Problem
An opted-in exhausted-account handoff can remain blocked by an App helper that outlives the App, or by the LoopX Chat transport holding the same Codex home. The first implementation also treated an unrelated CLI using the App-bundled binary as eligible for termination, and could lose Chat restoration after a partially successful unload.
Resulting behavior
Current fixture repair and validation
The latest review found a test-interface regression: the
client_hostfixture lacked the native recovery root and process inputs. This repair adds an isolated temporary home/root, an explicit synthetic App identity, an empty helper set and no installed Chat agents. Production recovery code remains in use; no runtime fallback, assertion removal or test skip was added. The six existing PR files are unchanged from0ffbde9fe8e2f2295d4e1c6703003daf682c92a6.Validated at
6f5aa66bb24f3d1724861595b0be403f1ae44332, after a normal merge of target main920f18809d906b840325c4f40013e9224e4a3a2e:tests/presentation/test_codex_accounts.pynow has 71 passing. All assertions, parametrization and skip decorators across its 37 test functions are preserved. The run includes both disposable macOS AppKit fixture cases.This repair changes only the existing test fixture and its temporary-directory inputs. It does not rerun the broader signed-App/launch-agent drill below, switch a real account or interrupt production Chat. A fresh review of the complete new head remains separate from these passing checks; no merge is performed.
Earlier validation recorded at
0ffbde9fe8e2f2295d4e1c6703003daf682c92a6loopx checkalso reports two unrelated pre-existing local Goal authority errors; its six-file publication scan passes with one non-literal credential-reference warning. This is not reported as a clean global state check.The macOS qualification uses synthetic credentials and a disposable service. It does not exercise a real account swap, production service interruption, paid model request or installed adoption. An old orphan whose parentage was never observed remains an explicit hold; the patch does not infer ownership from its bundle path.
中文摘要:修复换号协调中的进程归属与 Chat 恢复问题。只处理退出前已观察且内核身份未变的 App 后代;无关 CLI、旧未观察孤儿和后来启动的 App 保持占用。Chat 的暂停先保存恢复意图,失败和并发状态查询均有边界;恢复不成功不会冒报成功,也不重做已提交的换号。离线反例和隔离 macOS 原生链路已通过,真实账户和生产采用另行验收。
本轮修复:补齐账号交接测试夹具的隔离目录、进程身份和空服务输入;不修改生产逻辑,不删除原断言或跳过测试。已复现 64 过 / 7 失败,修复后 71 项全部通过;5 个包级 smoke、Ruff、编译及预合并检查通过。新提交仍须独立评审,未执行合并。