app
Codex
Public Made by Adomby adom
Codex in Adom Hydrogen: ecosystem setup, dock dashboard and live engineering demos.
← Commit history
Merge PR #91: Fix Codex Desktop agreement flags and startup observation; verify on crr (fix/crr-desktop-setup -> main) merge
Showing changes introduced by the merge (against its first parent).
9 files changed
+261−49
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.