← All Pull Requests

Fix Codex Desktop agreement flags and startup observation; verify on crr #5

Merged opened by John Lauer 2026-09-08
Merges fix/crr-desktop-setup → main

Addresses adom/codex #1 with Codex Bridge 0.1.2 candidate.

  • Pass both Microsoft Store agreement flags only with explicit consent; return exact installer program/args and preserve output if verification fails.
  • Distinguish agreement gates from unrelated install failures.
  • Retry transient startup inventory observations after one activation, preserving diagnostics and requiring an observed window for success.
  • Update setup guidance to crr (ConfRoomROG); leave arav-rog to Caleb.

Verification: 19 Linux tests, 17 Windows unit tests executed natively on crr, Linux/Windows Clippy with warnings denied, and Windows release build pass. Candidate deployed dev-pinned on crr; binary/manifest SHA256 matched. Live no-consent install correctly stops at Store package agreements and returns exact args.

Pending: user acceptance of Store source/package agreements on crr. Successful installation, real first launch/sign-in, desktop chat and plugin tests remain unverified there. Startup recovery is tested with injected failures; original inventory error cause remains unconfirmed. Keep issue #1 open. No main merge or public package/runtime release yet.

Full evidence and checksums: docs/CRR-DESKTOP-SETUP-TEST.md.

Diff Skip to comments

bridge/Cargo.lock+1−1
@@ -50,7 +50,7 @@ checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901"  [[package]] name = "codex-bridge"-version = "0.1.1"+version = "0.1.2" dependencies = [  "base64",  "serde",
bridge/Cargo.toml+1−1
@@ -1,6 +1,6 @@ [package] name = "codex-bridge"-version = "0.1.1"+version = "0.1.2" edition = "2021" publish = false 
bridge/README.md+10−5
@@ -93,16 +93,21 @@ is not proof of a valid account; a running process is not proof of completed set  ## 2. Install, sign in, and finish Windows setup +Current testing uses **crr (`ConfRoomROG`)**. arav-rog is in use by Caleb;+the arav-rog screenshots and results in this guide are historical.+ ```sh-ab --target arav-rog --ai-thread codex-bridge-guide codex_desktop_install '{}'-ab --target arav-rog --ai-thread codex-bridge-guide codex_progress '{"jobId":"<returned-job-id>"}'-ab --target arav-rog --ai-thread codex-bridge-guide codex_desktop_open '{}'+ab --target ConfRoomROG --ai-thread codex-bridge-guide codex_desktop_install '{}'+ab --target ConfRoomROG --ai-thread codex-bridge-guide codex_progress '{"jobId":"<returned-job-id>"}'+ab --target ConfRoomROG --ai-thread codex-bridge-guide codex_desktop_open '{}' ```  The tested official Store ID is `9PLM9XGG6VKS`, package `OpenAI.Codex`. Its displayed Store/app name was **ChatGPT** in this test. If the installer asks-for package agreements, obtain the user's actual acceptance before retrying with-`{"acceptAgreements":true}`. Existing installations are detected and retained.+for agreements, obtain the user's actual acceptance of both the Store source and+package agreements for this target before retrying with `{"acceptAgreements":true}`.+Bridge 0.1.2 supplies both agreement flags and returns `installer.args` plus the+installer output. Existing installations are detected and retained.  ### Reuse an existing Codex sign-in 
bridge/bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "codex",   "displayName": "Codex",-  "version": "0.1.1",+  "version": "0.1.2",   "description": "Compiled Rust bridge for the OpenAI Codex desktop app",   "author": "Adom",   "docs": "https://wiki.adom.inc/adom/codex",
bridge/src/desktop.rs+157−29
@@ -36,7 +36,7 @@ else {{ @{{installed=$false;platform='windows';supported=$true;windows=$items}}         Duration::from_secs(15),     )?;     if result["success"] != true {-        return Err("desktop_inventory_failed".into());+        return Err(format!("desktop_inventory_failed: {result}"));     }     serde_json::from_str(         result["stdout"]@@ -45,7 +45,7 @@ else {{ @{{installed=$false;platform='windows';supported=$true;windows=$items}}             .trim_start_matches('\u{feff}')             .trim(),     )-    .map_err(|e| e.to_string())+    .map_err(|e| format!("desktop_inventory_invalid_json: {e}; diagnostics={result}")) } pub fn executable() -> Result<PathBuf, String> {     if let Some(path) = env::var_os("CODEX_BRIDGE_CLI") {@@ -86,16 +86,8 @@ pub fn executable() -> Result<PathBuf, String> {     }     Err("desktop_cli_not_found".into()) }-pub fn install(accept: bool) -> Result<Value, String> {-    if !cfg!(windows) {-        return Err("unsupported_platform".into());-    }-    let before = inventory()?;-    if before["installed"] == true {-        return Ok(json!({"alreadyInstalled":true,"desktop":before}));-    }-    let mut cmd = Command::new("winget.exe");-    cmd.args([+fn install_args(accept: bool) -> Vec<&'static str> {+    let mut args = vec![         "install",         "--id",         STORE_ID,@@ -104,20 +96,50 @@ pub fn install(accept: bool) -> Result<Value, String> {         "msstore",         "--silent",         "--disable-interactivity",-    ]);+    ];     if accept {-        cmd.arg("--accept-package-agreements");+        args.extend(["--accept-source-agreements", "--accept-package-agreements"]);     }-    let result = run(cmd, None, Duration::from_secs(900))?;-    let after = inventory()?;+    args+}++pub fn install(accept: bool) -> Result<Value, String> {+    if !cfg!(windows) {+        return Err("unsupported_platform".into());+    }+    let before = inventory()?;+    if before["installed"] == true {+        return Ok(json!({"alreadyInstalled":true,"desktop":before}));+    }+    let mut cmd = Command::new("winget.exe");+    let args = install_args(accept);+    cmd.args(&args);+    let mut result = match run(cmd, None, Duration::from_secs(900)) {+        Ok(result) => result,+        Err(error) => json!({"success":false,"error":error}),+    };+    result["program"] = json!("winget.exe");+    result["args"] = json!(args);+    Ok(install_result(accept, result, inventory()))+}++fn install_result(accept: bool, result: Value, after: Result<Value, String>) -> Value {+    let after = match after {+        Ok(after) => after,+        Err(error) => {+            return json!({"success":false,"errorCode":"install_verification_failed",+            "installer":result,"inventoryError":error,+            "_hint":"The installer may have completed. Check codex_status before retrying installation."});+        }+    };     if after["installed"] != true {-        return Ok(-            json!({"success":false,"errorCode":if accept {"install_failed"} else {"blocked_on_user"},-            "blockedBy":if accept {Value::Null} else {json!("Microsoft Store package agreements")},-            "installer":result,"desktop":after,"_hint":"Inspect installer output. After user agreement acceptance, call codex_desktop_install with acceptAgreements:true."}),-        );+        let agreement_blocked =+            !accept && matches!(result["exitCode"].as_i64(), Some(-1978335162 | -1978335167));+        return json!({"success":false,"errorCode":if agreement_blocked {"blocked_on_user"} else {"install_failed"},+            "blockedBy":if agreement_blocked {json!("Microsoft Store source and package agreements")} else {Value::Null},+            "installer":result,"desktop":after,"_hint":"Inspect installer.args, exitCode, stdout and stderr. Only after the user accepts BOTH Microsoft Store source and package agreements on this target, call codex_desktop_install with acceptAgreements:true."});     }-    Ok(json!({"desktop":after,"installer":result}))+    json!({"desktop":after,"installer":result}) } pub fn uninstall() -> Result<Value, String> {     let before = inventory()?;@@ -149,16 +171,47 @@ pub fn launch() -> Result<Value, String> {     let script=format!("$ErrorActionPreference='Stop'; Start-Process explorer.exe -ArgumentList {} -WindowStyle Minimized",ps_quote(&format!("shell:AppsFolder\\{family}!App")));     let result = powershell(&script, Duration::from_secs(15))?;     if result["success"] != true {-        return Err("desktop_launch_failed".into());+        return Err(format!("desktop_launch_failed: {result}"));     }-    for _ in 0..30 {-        let inv = inventory()?;-        if inv["windows"].as_array().is_some_and(|a| !a.is_empty()) {-            return Ok(inv);+    observe_window(+        inventory,+        || std::thread::sleep(Duration::from_millis(500)),+        30,+    )+}++fn observe_window(+    mut inspect: impl FnMut() -> Result<Value, String>,+    mut pause: impl FnMut(),+    attempts: usize,+) -> Result<Value, String> {+    let mut failures = 0;+    let mut last_error = None;+    for attempt in 0..attempts {+        match inspect() {+            Ok(mut inv) if inv["windows"].as_array().is_some_and(|a| !a.is_empty()) => {+                if failures > 0 {+                    inv["observationWarnings"] =+                        json!({"failedPolls":failures,"lastError":last_error});+                }+                return Ok(inv);+            }+            Ok(_) => {}+            // App activation can race a package/process query. Observe again, never relaunch.+            Err(error) => {+                failures += 1;+                last_error = Some(error);+            }+        }+        if attempt + 1 < attempts {+            pause();         }-        std::thread::sleep(Duration::from_millis(500));     }-    Err("desktop_window_not_observed".into())+    Err(format!(+        "desktop_window_not_observed: {}",+        json!({"failedPolls":failures,"lastInventoryError":last_error,+        "_hint":"Activation was sent once. Check codex_status and the app window before retrying open."})+    )) } pub fn auth_import(path: &Path) -> Result<Value, String> {     auth_import_to(path, &home())@@ -222,6 +275,81 @@ fn auth_import_to(path: &Path, dir: &Path) -> Result<Value, String> { mod tests {     use super::*;     #[test]+    fn installer_diagnostics_survive_verification_failure() {+        let installer = json!({"exitCode":0,"args":install_args(true),"stdout":"Installed"});+        let result = install_result(+            true,+            installer.clone(),+            Err("desktop_inventory_failed: timed out".into()),+        );+        assert_eq!(result["errorCode"], "install_verification_failed");+        assert_eq!(result["installer"], installer);+    }+    #[test]+    fn unrelated_install_errors_are_not_reported_as_missing_consent() {+        for code in [-1978335162, -1978335167] {+            assert_eq!(+                install_result(+                    false,+                    json!({"exitCode":code}),+                    Ok(json!({"installed":false}))+                )["errorCode"],+                "blocked_on_user"+            );+        }+        for installer in [json!({"exitCode":1}), json!({"error":"winget unavailable"})] {+            assert_eq!(+                install_result(false, installer, Ok(json!({"installed":false})))["errorCode"],+                "install_failed"+            );+        }+    }+    #[test]+    fn launch_observation_recovers_after_transient_inventory_error() {+        let mut results = [+            Err("desktop_inventory_failed: transient".into()),+            Ok(json!({"windows":[]})),+            Ok(json!({"windows":[{"hwnd":42,"title":"Sign in"}]})),+        ]+        .into_iter();+        let mut pauses = 0;+        let result = observe_window(|| results.next().unwrap(), || pauses += 1, 3).unwrap();+        assert_eq!(result["windows"][0]["hwnd"], 42);+        assert_eq!(result["observationWarnings"]["failedPolls"], 1);+        assert_eq!(pauses, 2);+    }+    #[test]+    fn launch_observation_failure_is_bounded_and_preserves_diagnostics() {+        let mut calls = 0;+        let error = observe_window(+            || {+                calls += 1;+                Err("desktop_inventory_failed: test detail".into())+            },+            || {},+            3,+        )+        .unwrap_err();+        assert_eq!(calls, 3);+        assert!(error.starts_with("desktop_window_not_observed:"));+        assert!(error.contains("test detail"));+        assert!(observe_window(|| Ok(json!({"windows":[]})), || {}, 2).is_err());+    }+    #[test]+    fn install_requires_consent_for_both_agreement_flags() {+        let args = install_args(false);+        assert!(!args.iter().any(|arg| arg.starts_with("--accept-")));+        let accepted = install_args(true);+        assert_eq!(&accepted[..args.len()], args);+        assert_eq!(+            &accepted[args.len()..],+            ["--accept-source-agreements", "--accept-package-agreements"]+        );+        assert!(args.contains(&"--disable-interactivity"));+        assert!(args.windows(2).any(|pair| pair == ["--id", STORE_ID]));+        assert!(args.windows(2).any(|pair| pair == ["--source", "msstore"]));+    }+    #[test]     fn import_preserves_existing_credentials_and_returns_no_values() {         let root = env::temp_dir().join(format!("codex-auth-test-{}", uuid::Uuid::new_v4()));         fs::create_dir_all(&root).unwrap();
bridge/verbs.json+2−2
@@ -58,13 +58,13 @@       "name": "codex_desktop_install",       "summary": "Install official Microsoft Store package 9PLM9XGG6VKS",       "input": {-        "acceptAgreements": "boolean; true only after user acceptance"+        "acceptAgreements": "boolean; true only after user accepts both Microsoft Store source and package agreements for the selected target"       },       "timeoutSeconds": 5,       "longRunning": true,       "statusVerb": "codex_progress",       "example": {},-      "hint": "Native arguments are forwarded as structured JSON. Poll events and pending requests for progress."+      "hint": "Poll codex_progress; inspect result.success and installer.args/exitCode/stdout/stderr. acceptAgreements:true passes BOTH Store source and package acceptance flags only after user consent on this target. If install_verification_failed, check codex_status before retrying: installation may already have completed. Send the AB toast before any expected UAC step."     },     {       "name": "codex_desktop_uninstall",
docs/CRR-DESKTOP-SETUP-TEST.mdadded+63
@@ -0,0 +1,63 @@+# Codex Bridge 0.1.2 candidate: crr setup regression checks++Date: 2026-09-08. Issue: [adom/codex #1](https://wiki.adom.inc/adom/codex/issues/1).++## Target and boundaries++- User-selected crr resolved to `ConfRoomROG`, Windows, AB 2.1.78.+- Calls used `--target ConfRoomROG --ai-thread codex-crr-fixes`; responses+  identified peer `ConfRoomROG` over the mesh.+- No Codex Desktop package, Codex processes, native auth cache or Codex bridge+  existed before this test. Existing unrelated apps and bridges were preserved.+- arav-rog is being used by Caleb. No calls were sent to it.++## Changes and verification++- Explicit agreement acceptance now supplies both `--accept-source-agreements`+  and `--accept-package-agreements`. Without consent, neither is supplied.+- WinGet remains noninteractive and selects the exact official Store ID+  `9PLM9XGG6VKS`, not a name search.+- Installer results include program and exact args, even on spawn failure.+  Post-install inventory failures retain installer diagnostics and advise checking+  status before retrying. Unrelated failures no longer masquerade as consent gates.+- Launch issues one activation and retries observations after transient inventory+  failures. It retains diagnostics and never reports success without a window.+- 19 Linux tests passed (18 unit plus one HTTP integration test).+- 17 Windows unit tests passed natively on crr, including synthetic launch polling+  recovery, Store argument construction, Windows credential ACL/preservation,+  native RPC correlation, redaction and UAC guidance. The temporary test executable+  was removed afterward.+- Linux and Windows-target Clippy passed with warnings denied; Windows x64 release+  cross-build passed.+- AB discovered and ran the development-pinned candidate on crr, port 58496.+  Deployed executable and manifest hashes matched the local build.++## Live install boundary++`codex_desktop_install {}` stopped at the actual Microsoft Store package-agreement+gate. Nested result was `success:false`, `errorCode:blocked_on_user`, WinGet exit+`-1978335167`; output named ChatGPT, publisher OpenAI and Store ID `9PLM9XGG6VKS`.+The exact non-accepting argument list was returned. Desktop remained absent.++Agreement acceptance for crr is pending. No agreements were accepted and no auth+cache was transferred. Successful installation, first launch/sign-in, actual desktop+chat and plugin operations have NOT yet been retested on crr. Its Store source did+not block this attempt; the unused-source agreement case is covered by argument+tests and the issue reporter's evidence, not a fresh-source live test here.++The original `desktop_inventory_failed` launch report has not been reproduced on+crr. Recovery is regression-tested with injected transient failures; the original+PowerShell failure's cause remains unconfirmed. Keep issue #1 open for live follow-up.++## Candidate provenance++Bridge version: `0.1.2`, development candidate only. No public runtime release,+auto-update manifest or container package publication was performed.++SHA256:++```text+codex-bridge.exe: d7e6d720fee0f78186adb5cc030546b3c58ed0fd4bc3824f58d05f9c61f8fc5b+bridge.json: 51c9a1efa98b28c0306ae1af4baa5d90dd4ffe6fddf2443724d340d7b8478176+codex-bridge-windows-x64.zip: b07bd40b6c2e56037f3c0af07ac072fb590c7f402bbb24a809d3b5aca8f509b2+```
docs/DESKTOP-BRIDGE.md+19−8
@@ -16,7 +16,9 @@ Run `bash bridge/build.sh`. The build tests and creates its root, plus SHA256SUMS. Linux cross-building needs the Rust target `x86_64-pc-windows-gnu`, MinGW-w64, zip and sha256sum. -The candidate is dev-pinned on **arav-rog**, not AdomLapper. There is no public+The current test target is **crr (`ConfRoomROG`)**. arav-rog is now used by Caleb;+leave it alone. Its earlier results below are historical, not a current test target.+The candidate is development-pinned, not a public runtime release. There is no public auto-update manifest until review. For a development deployment, discover the host's LOCALAPPDATA, stage the ZIP members under `Adom Bridge/bridges-cache/codex` with a `.dev-pin` marker using AB's SDK workflow,@@ -28,11 +30,11 @@ the ZIP and a SHA256-bearing manifest on adom/codex for `bridge_install`. Always pass an explicit target and recognizable caller identity:  ```sh-ab --target arav-rog --ai-thread codex-desktop-bridge codex_status '{}'-ab --target arav-rog --ai-thread codex-desktop-bridge codex_describe '{}'-ab --target arav-rog --ai-thread codex-desktop-bridge codex_desktop_install '{}'-ab --target arav-rog --ai-thread codex-desktop-bridge codex_progress '{"jobId":"..."}'-ab --target arav-rog --ai-thread codex-desktop-bridge codex_desktop_open '{}'+ab --target ConfRoomROG --ai-thread codex-desktop-bridge codex_status '{}'+ab --target ConfRoomROG --ai-thread codex-desktop-bridge codex_describe '{}'+ab --target ConfRoomROG --ai-thread codex-desktop-bridge codex_desktop_install '{}'+ab --target ConfRoomROG --ai-thread codex-desktop-bridge codex_progress '{"jobId":"..."}'+ab --target ConfRoomROG --ai-thread codex-desktop-bridge codex_desktop_open '{}' ```  Long operations return a job ID immediately. Poll until `stillRunning:false`,@@ -42,8 +44,17 @@ they do not survive restart. Inspect native state before retrying a timed-out mutation. Native events have sequence cursors and a bounded buffer.  The official Store ID `9PLM9XGG6VKS` is currently titled **ChatGPT** and includes-Codex (package `OpenAI.Codex`). Only after actual user acceptance, pass-`acceptAgreements:true`. Existing installations are left intact. Launch may+Codex (package `OpenAI.Codex`). Only after actual user acceptance of both the+Store source and package agreements on the selected target, pass+`acceptAgreements:true`. Bridge 0.1.2 supplies both `--accept-source-agreements`+and `--accept-package-agreements`, keeping `--disable-interactivity`. Results+include `installer.program`, `installer.args`, exitCode, stdout and stderr.+`install_verification_failed` preserves this output if the post-install inventory+fails; check `codex_status` before retrying an installation that may have completed.+Launch observes repeatedly after a single activation, tolerating transient inventory+errors. Recovered errors appear in `observationWarnings`; exhausted observation+returns `desktop_window_not_observed` with the last inventory diagnostics.+Existing installations are left intact. Launch may foreground the app according to Windows activation behavior; observation does not.  Before a setup action expected to trigger UAC (especially **Finish setup**), send
skills/codex-desktop-bridge/SKILL.md+7−2
@@ -11,9 +11,14 @@ Read adom/codex `docs/DESKTOP-BRIDGE.md` and installed AB SDK/runtime skills bef deployment. Source is `bridge/` in adom/codex, not AB core. Preserve development pins.  1. Run `adom-codex context`, discover targets, and pass `--target` and `--ai-thread`-   on every call. Initial test host is arav-rog; never silently use AdomLapper.+   on every call. Current test host is crr (`ConfRoomROG`). arav-rog is in use+   by Caleb; do not use it for testing. Never silently use AdomLapper. 2. Call `codex_status` and `codex_describe`. Store ID 9PLM9XGG6VKS is now ChatGPT-   and includes Codex. `acceptAgreements:true` requires actual user acceptance.+   and includes Codex. `acceptAgreements:true` requires actual user acceptance+   of BOTH Store source and package agreements for the selected target. In bridge+   0.1.2+, it supplies both WinGet flags. Inspect `installer.args`, exitCode,+   stdout and stderr. An `install_verification_failed` result retains installer+   output; check status before retrying because the package may be installed. 3. Reuse only a user-authorized staged native cache via `codex_auth_import`.    Never print tokens. Preserve existing credentials, remove staging promptly,    and verify account plus the visible app. Native login is the fallback.

Comments

No comments yet.

Log in to comment.