Repository navigation
fix(chat): preserve accepted work during explicit managed replacement - #7582
Conversation
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)
动机 / Motivation
运行已受理消息的本机 Chat 服务维护者,在显式替换同一端口的旧服务时遇到此问题。旧路径只认出进程身份就终止它,排队输入仍留在历史中却失去原执行者;新路径先查原进程所属运行目录,忙时保留它,原任务结束后允许正常替换。已验证忙时零停止信号、零新服务启动、原消息字节保持,以及原任务结束后同一旧 PID 退出并准入新服务。本 PR 只覆盖显式受管替换入口;不拦截外部 launchctl bootout 或操作系统终止,不重放旧业务,也不声称修复了某次未知发起者的生产重启。
这是可独立使用的恢复保护增量。原服务实际安装采用、生产业务连续性和外部管理器直接停止仍不属于本次源码资格证明;合并前另有未通过的既有输出预算门。保留原工作比先停进程再猜测能否重试更符合现有维护合同;生产重启的具体触发者目前未知,不能据这个复现给它补上因果结论。
改动思路 / Approach
改动复用现有 chat_runtime_activation_state 和准入锁。准入锁把新消息持久化与维护停止串行化;它保护的是旧 listener 实际使用的目录。新命令即使指向空目录,也不能拿它证明旧任务已空闲。
机制放在原有 Python runtime_activation 边界合理:已受理任务的分类仍由 chat_ingress 单一所有者负责,新代码只做 macOS 管理器读取和受控停止。当前 PR 的边界是让真实 CLI 获得这项保护;没有新增工作队列、状态标记、模型 worker、调度器或权限。正常 update 原本已有保护,本 PR 没有把它重做一遍。下表是源码推导的分支说明,不替代执行证据。
| 原状态 | 旧显式入口 | 本版本 |
|---|---|---|
| 旧运行目录仍有排队或在途消息 | 可停止原消费者 | 保留原 PID,返回 chat_work_pending |
| 管理器、路由或历史无法核验 | 不核查这些状态便可停止 | 返回 chat_state_unverified,保留原服务 |
| 原任务已真正结束 | 正常替换 | 持锁核验原 PID 后正常替换 |
| 空闲端口 | 启动 | 保持启动路径 |
具体改动 / Changes
审查精确头 53834c43df068a46b08160b12f35870654604849。独立规格依据是改动前安装指南 原受管服务维护合同,spec_revision ffc90182bc2a896aa9980cf24dcf23a0e3276e3d。以下索引从该段现有条款提取,不是新造的产品要求:quiesce-owned-listener、retain-original-sessions、one-consumer-per-app 在本显式入口已落实并由原生进程、原文件历史和准入并发验收;logout-reboot-qualification 属于指南明确另行要求的登录/重启资格,本次 out_of_scope,不宣称已通过。
关键代码讲解
handle_support_control_command仍先组合启动参数。只有现有显式标志调用替换,新导入指向受保护 owner;参数失败不会先停止服务,替换拒绝时沿原loopx_chat_start_v0返回,serve_chat不会运行。_managed_service_pid用当前用户的真实launchctl print读取 PID 和已加载参数,要求它与 plist 参数逐项一致。只编辑 plist 不能证明运行进程用了新目录;多 PID、相似字段或参数不符都不能通过。replace_managed_loopx_chat复用 loopback、服务指纹及同用户 PID 核验,解析原绝对运行目录,持原准入锁完整读 Session/Turn,再复核管理器、路由和 listener 身份。只向固定的原 PID 发一次 SIGTERM,持锁等端口释放;超时不升级信号。摘要漏掉的孤立排队记录、后来新建的 Session 和不可读状态都不能变成空闲证明。
变更为 4 文件 +316/-9,生产代码新增约92行,其余主要是209行反例/并发测试和15行双语边界说明。沿用既有记录,无数据迁移。旧 dashboard_launcher 低层 Python stopper 保留,但仓库内生产调用搜索确认 CLI 已不再走它;直接 Python 调用不在本次安全声明内。后续可在核外部消费者后清理该 API,本次没有把它保留为回退路径。
对主干的风险 / Risks
最危险的回归是把新命令的空目录或刚改过的 plist 当成原消费者状态。此处以真实加载参数、原 PID、原运行目录及持锁后的再次核验共同防止;对另一服务的忙碌目录不会扩大为全机阻塞。解析不了的管理器保守拒绝,可能延后维护;原任务自行完成或修复真实路由后由原维护入口重试,没有新确认轮次,也不会代业务重放。
PASS:相关源码验收155项;另5项审查补测通过,覆盖未来 Session、35条以上旧记录与相反枚举顺序、失真摘要、关闭显式标志及非 macOS 管理器。3项原有 Windows 实机测试因当前 Darwin 跳过,不能当 Windows 实机资格。相同最终反例在不可变原基线呈14项语义失败、8项控制通过。真实隔离 macOS LaunchAgent/HTTP进程与 File 历史证明旧版会停止排队消费者;修复保留原 PID/全部历史,原合成任务结束后同一 PID 才退出,新服务才准入。原生样本使用 b147 的生产字节,与精确头生产文件逐字相同;不是生产 Bot、真实模型或真实外发验收。进程并发单测的 OS IO 有 mock,准入锁与历史为真实实现。
语义与 CI 对齐
复用现有状态和错误词汇;没有更改工作选择、预算和结算语义,也未查询、轮询或等待远端 CI。标准 premerge 为6项直接检查通过、17项选定检查执行,16通过、1失败。FAIL 保留:quota_should_run/multi_agent/json 在同一物理 fixture、同一命令、不可变 ffc90182bc2a896aa9980cf24dcf23a0e3276e3d 与 53834c43df068a46b08160b12f35870654604849 均为25,836字符,超过25,500硬上限。相同失败签名已独立归因到未改的输出路径;本 PR 不执行该路径,改变的不变量有独立通过证据。不能通过调大门限、删语义或缩工作负载消除红项;合并资格继续 HOLD。
早期测试失败和原始日志保留:fixture曾把signal0存活探测误作停止,现已修正测试;整合基线后旧前端包需正常重建。精确最终套件及独立原生/锁测试覆盖本次风险,未把一次后续通过充当所有历史问题的解释。外部 launchctl bootout、操作系统终止、全部生产业务连续性和安装后效果仍未资格。
我的整体评价 / Verdict
没有发现阻止本精确源码增量通过审查的代码问题。长期执行维度 improved:维护请求不再丢掉已受理工作,原任务结束后还能继续正常替换;用户体验 improved:复用原意图和授权,新增必需确认0轮,忙碌和未知状态更明确。visual_review 不适用:本次只改 CLI 和安装指南,没有渲染页面;未验证范围为生产安装、随后原 provider 业务连续性及外部停止。
全量扫描、复用准入锁和原管理器核验的成本与中断风险相称;没有第二个资格 owner 或持久状态。低层旧 API 可后续清理,受影响记录与旧消费者边界已明确。此批准仅针对精确头的独立增量,不等于已合并、已安装、父级 Bot 恢复完成或准出门通过。保留独立的输出预算合并阻塞;解除时仍须当前精确版本资格及正常采用验收。
English verdict: APPROVE — reviewed 53834c43df068a46b08160b12f35870654604849; accepted-work preservation is covered. The independently reproduced pre-existing CLI output-budget failure remains a separate merge-readiness hold. No production installation or complete Bot recovery is claimed.
Explicit managed Chat replacement could stop a verified listener while it still owned accepted input. The CLI now resolves that listener's loaded macOS LaunchAgent route, uses the existing strict Chat-work reader and ingress fence, and pins the verified PID through SIGTERM and port release. Unknown routes/state keep the listener running; ordinary startup on a free port is unchanged.
Validation:
quota_should_run/multi_agent/jsonis 25,836 characters against 25,500. The bound is unchanged and the failure is retained; this PR is not merge-qualified yet.This is the explicit managed replacement boundary. It does not intercept external
launchctl bootoutor OS termination. An occupied service without a verifiable matching macOS manager is left running. The existing lower-level Python helper remains unchanged; the shipped CLI uses the fenced runtime-activation owner. No new setting, store, worker or frontend surface is added.