Repository navigation
feat(update): upgrade CLI and macOS App with one command - #6789
Conversation
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)
当前结论:REQUEST_CHANGES。精确版本 394e0bf27cd945b159b932fe91f0c411222ed55c 有一处已通过正常安装包复现的“升级未完成却报完成”问题。可以在现有汇总函数中修正,无需扩大升级机制。
动机
已有 CLI 安装的用户需要升级 LoopX,并继续使用原来的自动化任务。此前 CLI 更新、App 更新和重启需要分别处理;安装期间长时间没有输出也容易造成重复尝试。这个 PR 把已有安装器和原生更新器组合为一次明确的升级操作,并提供进度与分别读回的结果。这是一个有价值的、有边界的安装体验改进。
预期结果是:CLI、受管理的主机材料、已有 App 分别得到验证,部分失败时保留已完成部分并明确提示恢复。CLI-only 用户同样需要这个完整结果。当前已验证 CLI-only 正常分支,但自动化配置读取失败的分支仍退出 0,且文本显示升级完成,因此可用结果尚未完整交付。
评审依据是基线 a54c030ee79ea9270fed4ca94f9592f32a19232a 的 release-readiness:Supported Install And Update Paths 要求区分包获取、主机材料、运行时激活与扩展就绪;Atomic Local Promotion Failure Matrix 保留原安装器的原子替换及恢复责任;Merged Is Not Runtime-Active 要求实际安装身份读回。前者在本次失败分支未满足,后两项的既有 owner 得到复用。完整签名 App 替换、旧 App cohort 与发布默认资格仍属于独立发布验收,不能从本次单元测试或 CLI 结果推导。
本次边界限于原地版本升级与结果真实性。剩余缺口是下面的 CLI-only 汇总错误、原生测试的未归因失败以及明确列出的安装组合资格;下一步应先修正汇总并验证失败恢复,不增加迁移执行器、自动化调度或其他框架。
改动思路
结构上继续保留两个效果 owner:Python 的 self_update 负责原安装环境,Rust 的 maintenance::perform 负责 App 签名检查、替换、备份和服务资格。self_upgrade 只协调已明确请求的这两条路径。没有发现需要新增通用控制面能力或改写已有状态权威的理由。
UpgradeRequest 是一次明确终端请求的临时关联与观察投影,实际阶段来自原生维护流程;它不是 Goal、权限或结算凭证。CLI 与 App 独立发布,因此旧 App 不认识请求时明确超时是合理的兼容边界。现有更新器的签名校验、安装失败恢复和服务身份不能被这份文件替代。Tauri 的实际 restart 实现保留启动参数,因此没有把“替换后必然丢失 request id”列为问题。
下面是此版本已执行的 CLI-only 对照;仅 App 物理位置发现被固定为空,pip、旧配置读取、主机材料更新及安装后 doctor 均通过实际已安装包执行:
| 输入 | 实际结果 | 应有含义 |
|---|---|---|
| 正常隔离环境 | exit 0,ok=true,upgrade_complete=true |
升级完成 |
| 隔离环境的自动化 SQLite 来源不可读 | exit 0,ok=true,upgrade_complete=false,discovery_failed |
升级未完成,应保留恢复动作并返回非零 |
这个设计的主要成本是两种独立安装的关联读回,15 文件、+1059/-20 中生产代码为 +690/-19,另有测试与文档。现有 native owner 无法直接向发起 CLI 返回结果,因而这个局部协调有合理价值。当前 PR 边界应继续保持现有安装器、原生更新器和一次结果汇总;最高价值的 refine 是合并公共成功判定,避免无 App 分支绕过它。无需建立另一份升级状态机。
具体改动
support_control.py与support_control_update.py让upgrade复用同一个解析器和路由;裸upgrade执行,裸update仍只读。现有安装 owner、包管理器 ref 限制和多 App 预检继续决定是否允许执行。self_upgrade.py::build_upgrade_plan保留归档安装记录的 repo/ref;desktop_upgrade_plan识别标准安装位置和官方 App 身份;_restart_app通过 argv 传递路径并保留明确的LOOPX_BIN;_upgrade_desktop校验请求、channel、存活进程、磁盘身份与服务读回;finish_upgrade汇总结果。问题位于最后一步的提前返回。self_update.py的两种安装驱动复用_installed_readback,将新进程的实际 CLI 身份与旧计划分开。self_update_progress.py和self_update_download.py提供等待阶段信息;JSON stdout 保持为一个结果。进度信息的扩展是现有update apply的明确变化,不应声称所有输出完全不变。- Rust
upgrade_request.rs校验有界请求 id 和 channel,原子写入私有读回,并保留更新检查失败;maintenance.rs在原有阶段发布和服务就绪处投影结果,明确请求使用 30 秒检查预算,普通启动仍为 3 秒;services.rs只暴露原有已选择运行时的身份,lib.rs注册该局部模块。 - 两份测试覆盖解析器所有权、保留 ref、无 App、多 App、请求与磁盘身份不匹配、原生失败、部分成功和进度输出。三份说明分别覆盖安装指南、Desktop 使用和已安装 skill 的升级建议。skill 的新建议要求明确升级意图,不产生后台安装或新的 Goal 权限。
[P2] CLI-only 提前返回绕过完整成功判定。 self_upgrade.py:269–271 在 desktop.status == not_installed 时直接返回。原有 update_with_prompts 会在发现失败时保留 CLI 安装成功的 ok=true,同时设置 upgrade_complete=false 并提供恢复建议。具有 App 的分支会在函数尾部合并这两个结果,CLI-only 分支却跳过了它。最终 CLI 的 exit 选择和 render_upgrade_markdown 都只看 ok,因此输出了实际观察到的 LoopX upgrade completed.,而且精简文本没有展示未完成的自动化配置状态。
最小修复:不论是否安装 App,都经过同一完整结果汇总;在 CLI 已完成但主机材料未完成时保留 changes_applied=true、原有自动化修复提示和非零退出。补充 CLI-only 的正常、discovery_failed、待人工审阅三种回归,并保留已有 App 部分失败用例。无需重跑已合格的数据存储矩阵,也无需添加新编排。
对主干的风险
独立执行的 Python 相关测试为 77 passed、2 skipped;两项 skip 是 Windows-only 路径,在本机 macOS 不适用。Ruff 和既有 loopx-update-smoke.py 通过。另用相同输入执行了六组基线/当前版本的公开 CLI 只读及错误对照,退出、完整 JSON 与 changes_applied=false 一致。正常 wheel 的六个相关生产模块与评审源码逐字匹配,安装与失败对照没有更新日常 CLI、原 App 或活动 Goal。
原生全套串行测试在当前版本为 137 passed、1 failed、5 ignored;失败是 services::supervisor_tests::service_supervisor_reuses_matching_replaces_stale_and_rejects_foreign,其监听 fixture 未在期限内到达 Matching。相同全套命令在不可变基线为 135 passed、0 failed、5 ignored。五项忽略保留各自外部原生/已签名 App 环境要求;没有把它们记为通过。基线比较复用相同打包资源,仅用于测试源码比较,不代表基线签名 App 资格。当前失败原因仍未证明,不能仅因源码主体未变就归因基础设施,也不能选择后续一次绿色来抹去它。
构建最初分别因旧 frontend bundle 和缺少 Desktop runtime 资源停止;使用现有构建与打包脚本准备所需资源后完成实际 wheel 和原生测试。这两次是发生在测试前的环境准备失败,原记录保留。标准 diff 检查执行中被主机中断;已复用先前通过的直接检查、十项 catalog 和三项风险检查,只恢复剩余五项风险检查及十五路径公开边界扫描,均通过,maintainability 也通过。它们是恢复后的检查集合,不等于完整 premerge/CQR 凭证或合并准备度。没有查询或等待 GitHub CI,遵守当前本地验证政策。没有执行日常安装、发布、合并或用户状态故障注入。
我的整体评价
这项改动围绕一次明确的升级意图复用现有 owner,范围与收益总体相称;我不建议因为独立发布和恢复的真实难点继续叠加框架。有限的后续重构应落实在同一 finish_upgrade 汇总边界:让所有安装组合使用一个成功判定,并用原有修复入口恢复有问题的自动化配置。
当前仍是 REQUEST_CHANGES:修复上述可复现的 CLI-only 假完成,补足它的失败恢复回归,并解释当前原生测试失败所影响的资格。已通过的基础存储升级和历史失败各自保留,不把它们混成新的总前置。完整签名替换、真实旧 App 组合及安装后继续工作的证明仍按发布契约单独验收;本评审没有合并或发布结论。
English verdict: REQUEST_CHANGES — head 394e0bf27cd945b159b932fe91f0c411222ed55c. A normal non-editable installation reproduces CLI-only success/exit 0 while upgrade_complete=false after prompt-source discovery fails. Route the no-App branch through the same aggregate completion check and preserve recovery guidance. Python: 77 passed, 2 platform skips; six baseline/head CLI pairs match. Native: head 137 passed/1 failed/5 ignored versus base 135 passed/5 ignored; the current fixture failure remains unattributed. No merge or release qualification is implied.
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)
APPROVE,精确版本 e0d83ff9b453ac81cf29abe773ec2af8ae60bff6。此前评审发现的 CLI-only 假完成已修复;本次重新核验完整 16 文件差异、失败恢复和原生服务边界。签名候选发布资格与合并资格分别保留。
动机
已有 CLI 安装的用户需要升级 LoopX,并继续使用原来的自动化任务。此前 CLI 更新、App 更新和重启需要分别处理;安装期间长时间没有输出也容易造成重复尝试。这个 PR 复用原安装器和已有 App 更新入口,让一次明确的升级请求能得到进度及安装后的实际结果。
CLI、受管理的主机材料、已有 App 分别得到验证,部分失败时保留已完成部分并明确提示恢复。本次边界限于原地版本升级与结果真实性。剩余缺口限于发布阶段的完整签名 App 替换、旧 App 组合及真实用户继续工作的验收;本次补齐公开升级入口的失败与恢复,不扩展迁移框架。
改动思路
Python self_update 继续拥有 CLI 安装来源、包管理器、主机材料及安装后 doctor。Rust maintenance 继续拥有 App 签名更新、替换、备份、重启和服务资格。新增的 self_upgrade 只协调用户明确请求的现有操作;临时 request id 用于匹配一次请求的读回,不成为 Goal、Agent、调度或权限的第二份权威。
裸 upgrade 执行升级,裸 update 继续只读。CLI-only 不安装 App;检测到多个 App 或不兼容的包管理器 ref 请求时,在 CLI 变更前停止。旧 App 无法返回新读回时明确不完整,不能用“启动成功”替代更新完成。普通 App 启动保留原有 3 秒可选检查,明确终端请求使用 30 秒交互检查预算。
本次保持原安装器、原生更新器和一次结果汇总;3行修复已堵住无App分支的假完成,并删除测试端口先释放再绑定的夹具路径。native owner 本身不能向发起终端返回完整结果,因此局部协调有价值;更大的迁移执行器或控制面抽象没有必要。修复后的实际已安装 wheel 对照如下,仅物理 App 发现固定为空:
| 输入 | 退出与结果 | 保留的事实 |
|---|---|---|
| 正常隔离环境 | exit 0,ok=true,upgrade_complete=true |
实际 CLI 身份读回 |
| 合成自动化 SQLite 来源损坏 | exit 1,ok=false,upgrade_complete=false |
changes_applied=true、原 prompt 恢复提示 |
具体改动
评审依据是不可变版本 a54c030ee79ea9270fed4ca94f9592f32a19232a 的 release-readiness。Supported Install And Update Paths 的分层完成要求,现在由正常安装及发现失败对照满足;Atomic Local Promotion Failure Matrix 继续使用既有安装器/原生备份恢复;Merged Is Not Runtime-Active 通过新 CLI doctor 身份和请求、磁盘、进程、服务读回落实。完整签名替换及旧 App 发布组合仍需 release owner 验收,未因本次合并提前宣称通过。
整个差异为 16 文件、+1184/-61:10 个生产文件 +693/-19,3 个测试文件 +431/-42,3 个文档文件 +60。support_control.py 注册 alias;self_update.py 与 download/progress 模块提供共享安装身份读取和 stderr 等待反馈;Rust lib.rs 注册局部模块,maintenance.rs 发布原生阶段,services.rs 暴露已选运行时身份。安装指南、Desktop README 和已安装 skill reference 说明明确升级意图与兼容边界,没有改变首屏或新增 Lark 入口。
关键代码讲解
handle_update_command:同一解析器分派明确意图,只有原安装器声明支持 apply 才执行;随后沿update_with_prompts → finish_upgrade读回。原update只读/错误路径的六组完整 JSON 与退出码对照保持一致。finish_upgrade:无 App 分支现在也用已有upgrade_complete合并ok。安装成功但 prompt 未完成时,既保留已安装变更,也返回非零与原恢复动作;没有重新解释 prompt owner 的规则。缺少 CLI 身份仍不允许完成。_upgrade_desktop:通过已有原生维护更新 App,只接受匹配请求/channel、活进程、磁盘 App 身份与合格服务结果;未知或陈旧读回不能报成功。实际 Tauri restart 实现保留启动参数,没有把假设的 argv 丢失作为问题。UpgradeRequest:从受限 argv 建立一次临时关联,投影原生 phase 和服务身份,检查失败保持锁存,服务健康不能覆盖更新失败。
相邻有界改进在测试夹具:删除“先选端口、释放、再让 Python 绑定”的 helper,直接经 stdin 传递已绑定 socket。进程命令身份、Matching/Stale/Foreign/Unresponsive 判定、拒绝终止外来服务和原超时均保留。未增加运行时依赖或产品规则。
对主干的风险
本次独立 Python 验证 124 passed、2 Windows-only skipped,覆盖 upgrade、下载、激活、CLI owner 和 prompt upgrade。新增的三项断言在原 394e0bf 上失败,3 行修复后通过;另外覆盖正常 CLI-only、缺失安装身份和待审阅结果。真实 handler 使用损坏的合成 SQLite 来源,先退出 1 并显示恢复建议,修复该来源后用相同参数退出 0。正常非 editable wheel 还独立执行真实 pip、authority/主机材料与 doctor,六个生产模块与源码字节一致。没有以 mock 安装器代替这份真实安装对照。
原生全套同命令串行运行 138 passed、0 failed、5 ignored;原先失败的服务监督用例另在 4 个并发进程全部通过。历史 394e0bf 的 137/1/5、基线的 135/0/5 和最初资源准备失败保留:不能证明当时确实端口碰撞,也没有把失败改叫基础设施问题。当前覆盖消除了明确存在的释放/重绑间隙,并在原匹配条件和并发条件下验证;这比单挑一次绿色更有区分力。五项 ignore 仍需要各自隔离真实安装/签名 App/文件系统环境,不计为通过。
标准 premerge 的直接差异、双语 RFC、编译检查,以及 10 项 catalog、8 项风险、完整 16 路径公开边界扫描全部通过。Ruff、rustfmt、已有 update smoke 和 exact-scope change-quality 检查通过,质量凭证 cqr_de74bb7a8c02ab56e2e9 与当前差异一致。没有查询或等待 GitHub CI。现有 QA App 的作者证据仅属组件集成;本次没有发布签名候选、替换日常 App、升级日常 CLI 或对活动 Goal 注入故障。
语义与 CI 对齐
upgrade 保持本地命令 alias,复用 UpdateAction 与原生维护阶段;没有新增共享状态词汇。diff advisory 检查到 6 个 Python 来源和一个局部命令集合,不覆盖所有 Rust/动态语义;完整 semantic gate 已通过。进度和独立安装后身份也会出现在原 update apply,这是明确披露的行为变化。安装建议只指导明确的升级请求,不授予后台执行权限;默认关闭路径通过既有 CLI 对照及无 native request 的测试检查。
我的整体评价
APPROVE。长程工作的影响判为 preserved:原 Goal/Agent/任务、调度和已有 prompt owner 没有被新执行器替代,未完成的主机材料继续显式交给原恢复入口;这不声称证明模型采用或真实用户长期继续工作。用户体验判为 improved:一次已有授权意图获得进度、实际身份和诚实的完整/部分结果,失败可修正后重试,不增加确认轮次。
这是现有安装旅程的有界增量,机制与作用相称。旧假完成 finding 已通过新源码与实际安装反例关闭,历史原生失败的原因仍保留不确定性;当前全套及并发边界证据足以支持本版本评审。签名发布/旧 App 组合与完整用户继续工作交给现有发布验收;本评审不授权本机安装或发布。合并还必须读取这个不变 head 的原生 readiness。
English verdict: APPROVE — e0d83ff9b453ac81cf29abe773ec2af8ae60bff6. CLI-only incomplete prompt adoption now returns nonzero and preserves installation facts and recovery advice; actual normal-wheel positive/negative runs and same-command source recovery support the fix. Python: 124 passed, 2 platform skips. Rust: 138 passed, 5 ignored, plus four concurrent supervisor runs passed. Full local premerge and exact-scope quality receipt passed. The earlier fixture failure remains historical with unproven original cause; signed App replacement and older-App release cohorts remain explicit release qualification.
Updating LoopX currently leaves the operator coordinating CLI installation, App updates and restart separately. Buffered installer output can also leave the terminal silent for minutes, followed by an update report whose freshness fields still describe the previous installation.
This adds
loopx upgradeas one explicit update intent.loopx upgrade --ref mainselects Main for an archive installation and its standard macOS App. The command retains the installation owner, refreshes managed host material, invokes the native signed App updater and waits for App/service readback. CLI-only machines do not acquire an App. Text output shows the current waiting stage every ten seconds; JSON retains separate results and partial failures return nonzero.The existing
updateverb remains read-only by default, andupdate applyretains its CLI effects. Both receive progress and a separate post-install CLI identity. The App update panel, signing, replacement, rollback and runtime qualification retain their existing native owners. Normal App launches retain their three-second optional startup check; the explicit terminal request uses the existing thirty-second interactive budget. Main App builds can lag Git HEAD.Boundary and compatibility
--refrequests and unsupported owners fail before mutation. Multiple standard Apps also stop preflight.upgradeis a local CLI registration alias. The private App receipt is a local correlation/readback carrier, not a kernel vocabulary or authority receipt.Validation
Tested head:
e0d83ff9b453ac81cf29abe773ec2af8ae60bff6.review_requiredand missing-identity outcomes are also covered.changes_applied=true. Physical App discovery alone was fixed absent; no default user installation or Goal was used as a fixture.cqr_de74bb7a8c02ab56e2e9is valid. No GitHub CI was queried or waited on under the configured local-validation policy.updatebranches; complete JSON and exits matched. Existing update smoke passed again after repair.Remaining qualification
The exact candidate has not been published as a signed App or installed into a user's default CLI. Full signed replacement/restart and older-App cohort qualification remain with release validation. An older App without terminal readback can follow its existing startup updater, but cannot report terminal completion until a supporting build returns the receipt; timeout stays an explicit incomplete outcome. App discovery currently covers the existing standard macOS installation locations.