Skip to content

Commit 2e1bcb8

Browse files
authored
fix(desktop): ask before replacing a different CLI runtime (#4508)
* fix(desktop): ask before replacing a different CLI runtime Opening the App while the default CLI runtime was a different revision silently installed the App's bundled snapshot, which could move a separately updated CLI backwards. A start now classifies the installed runtime first: - no installed runtime: install the bundled snapshot, so a fresh machine still bootstraps in one launch; - the bundled snapshot already installed: nothing to do; - a different runtime installed: publish `runtime_pairing_required` with both revisions and stop before services, instead of replacing it. The boot surface renders that decision on the first screen - "update App and runtime" checks this App's channel and continues into the verified install, "use this App's runtime" installs the bundled snapshot and reconnects the same window - and no longer escalates a decision into the startup error projection. An approved update journal still finishes its installation without asking again, and an explicit `LOOPX_BIN` override is still never replaced automatically. Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> * fix(doctor): name the App/runtime pairing choice in desktop advice `loopx doctor` told operators that a mismatched App "may replace the CLI runtime" without saying what to do about it. The reported advice now names the decision the App asks for, and the contract test asserts that wording instead of a promise of silent replacement. Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> * docs(desktop): document the App/runtime pairing choice Record that automatic runtime preparation now applies only when nothing is installed, that a different installed runtime is the operator's decision, and what each choice does to the two layers. The LoopX project skill keeps its warning for older builds while naming the current behavior. Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> * test(desktop): cover the runtime pairing decision surface The browser smoke drives the boot surface through the decision: both choices stay available, nothing installs before one is chosen, the native boot-failure callback cannot overwrite it, escalation never relabels it as an error, the layout holds at phone width, and "update" checks then installs while "use this App's runtime" issues the align action and retires the chooser once services connect. The unit test keeps the pairing evidence bounded and free of private detail. Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> * chore(dashboard): rebuild the packaged workspace for the pairing phase The workspace update panel carries the new App phase, so the packaged chat assets must match a clean source build. Adds the new generation, keeps the previous one in the retention manifest, and repoints index.html. Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> --------- Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> Co-authored-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
1 parent 4aaad69 commit 2e1bcb8

15 files changed

Lines changed: 768 additions & 94 deletions

File tree

‎apps/desktop/loopx-control-plane/README.md‎

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,21 @@ The bundled runtime owns the CLI, HTTP APIs and workspace assets. A runtime-only
4242
CLI update cannot patch native startup or updater bugs; those require an App
4343
update. The App update workflow packages both layers from one Git revision.
4444

45-
On macOS, opening the App now automatically prepares its bundled runtime when
46-
the default CLI is missing or has a different source revision. This changes
47-
the previous startup behavior, which stopped at a manual repair gate. It can
48-
replace a separately updated default CLI with the App's matching snapshot;
49-
use an App update to move the paired installation forward. Automatic startup
50-
does not download another App or choose another update channel. Explicit
51-
`LOOPX_BIN` overrides are retained and are never replaced automatically: a
52-
mismatched override must be corrected by its owner.
45+
On macOS, opening the App prepares its bundled runtime automatically only when
46+
no default CLI runtime is installed: with nothing to replace, a fresh machine
47+
still bootstraps in one launch. When a *different* runtime is already selected,
48+
the first screen asks the operator instead of replacing it, because that
49+
default CLI may be the newer layer. The two choices are **Update App and
50+
runtime** (check this App's channel, then install the signed App and its
51+
matching snapshot together) and **Use this App's runtime** (install the
52+
snapshot this App carries, which aligns the CLI to the App's revision and can
53+
move it backwards). Services stay stopped until one of them is chosen, so an
54+
App that lags the CLI can no longer silently downgrade the CLI on open. Neither
55+
choice downloads another App on its own or selects another update channel, and
56+
both leave Goal data untouched. A channel with no newer build says so and
57+
leaves the CLI choice standing. Explicit `LOOPX_BIN` overrides are retained and
58+
are never replaced automatically: a mismatched override must be corrected by
59+
its owner.
5360

5461
The installer and App-owned services use the same bounded tool search,
5562
including standard Homebrew locations on macOS, without loading interactive

‎apps/desktop/loopx-control-plane/src-tauri/src/lib.rs‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ fn maintenance_origin(url: &Url) -> String {
2727
// echo verbatim into the boot surface, where it names the recovery panel's
2828
// actionable diagnostics instead of a misleading fixed message.
2929
fn boot_failure_message(error: &str) -> String {
30+
// The pairing decision is not a failure: the window is waiting for the
31+
// operator to choose between updating the App and aligning the CLI.
32+
if error == "runtime_pairing_required" {
33+
return "本机 LoopX 运行时与 App 自带的运行时不一致,请在上方选择「更新 App 与运行时」或「回退 CLI 到本 App 版本」后继续。"
34+
.to_string();
35+
}
3036
let is_stable_code = !error.is_empty()
3137
&& error.chars().all(|character| {
3238
character.is_ascii_lowercase() || character.is_ascii_digit() || character == '_'
@@ -219,9 +225,16 @@ mod tests {
219225
assert!(style.contains("@keyframes mark-breathe"));
220226
assert!(style.contains("prefers-reduced-motion: reduce"));
221227
assert!(style.contains("main[data-state=\"error\"] .progress::after"));
228+
assert!(style.contains("main[data-state=\"decision\"] .progress"));
222229
assert!(style.contains("--warning: #f5a623"));
223230
assert!(script.contains("desktop_update_status"));
224231
assert!(script.contains("window.loopxBootRetrying"));
232+
// The first screen must offer both operator choices, not a repair path
233+
// that silently replaces the CLI runtime.
234+
assert!(html.contains("id=\"pairing-align\""));
235+
assert!(html.contains("回退 CLI"));
236+
assert!(script.contains("runtime_pairing_required"));
237+
assert!(script.contains("\"align_runtime\""));
225238
// The boot surface must derive its error projection from the polled
226239
// snapshot itself and name the known fresh-Mac installer failure.
227240
assert!(script.contains("runtime_install_exit_2"));
@@ -231,6 +244,12 @@ mod tests {
231244
#[test]
232245
fn boot_failure_message_appends_stable_codes_only() {
233246
use super::boot_failure_message;
247+
// The pairing decision names both operator choices instead of the
248+
// generic startup failure text.
249+
let pairing = boot_failure_message("runtime_pairing_required");
250+
assert!(pairing.contains("更新 App 与运行时"));
251+
assert!(pairing.contains("回退 CLI 到本 App 版本"));
252+
assert!(!pairing.contains("错误码"));
234253
assert_eq!(
235254
boot_failure_message("runtime_install_exit_2"),
236255
"本地服务暂时无法启动,请检查安装或端口占用。(错误码 runtime_install_exit_2,详见恢复与更新面板)"

0 commit comments

Comments
 (0)