Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
112 commits
Select commit Hold shift + click to select a range
ad493c3
fix(nix): refresh upstream Nix pins for 26.601.21317 (#390)
github-actions[bot] Jun 3, 2026
ec90c1e
Simplify README into a platform install guide (#391)
ilysenko Jun 3, 2026
3ea73fc
Update AGENTS maintainer guide (#392)
ilysenko Jun 3, 2026
a975d6a
Add optional X11 EWMH Computer Use feature
Jun 3, 2026
2003a90
fix(read-aloud): repair settings nav injection (#394)
avifenesh Jun 3, 2026
bff42b1
Address X11 Computer Use feature review
Jun 3, 2026
859215d
Merge pull request #393 from AlekseiSeleznev/codex/x11-ewmh-computer-…
ilysenko Jun 3, 2026
c42fca4
Fix fast-mode upstream patch drift (#397)
ilysenko Jun 3, 2026
905f8d9
Document Nix Codex CLI requirement (#399)
ilysenko Jun 3, 2026
c826e04
Handle missing polkit auth agent in updater (#396)
moxw Jun 3, 2026
53d18da
Fix locale override and settings menu for API key auth on Linux (#401)
losese Jun 3, 2026
d3c5634
Restore Linux desktop settings with native shortcuts (#405)
ilysenko Jun 3, 2026
2ccaeba
Make settings section-title patch tolerant of minified identifier dri…
csmashe Jun 4, 2026
61ad6e9
fix(read-aloud): repair settings route patch (#407)
joshyorko Jun 4, 2026
946adef
[codex] Document Nix-native Codex CLI setup (#403)
colonelpanic8 Jun 4, 2026
7fa8dcb
fix(nix): refresh upstream Nix pins for 26.602.30954 (#412)
github-actions[bot] Jun 5, 2026
2d62efc
fix(read-aloud): wait for first PCM chunk before launching aplay (#411)
avifenesh Jun 5, 2026
97a8d51
fix(linux): use native Codex titlebar overlay (#413)
jojomini1231-cloud Jun 5, 2026
abbad1e
fix(browser): enable Browser Use gates on Linux (#415)
losese Jun 5, 2026
5efcfef
[codex] Invalidate stale DMG cache (#409)
avifenesh Jun 5, 2026
b336c3f
fix(read-aloud): repair current settings aliases (#417)
joshyorko Jun 5, 2026
546b0c5
fix(updater): preserve enabled local features (#416)
avifenesh Jun 5, 2026
0649251
fix(nix): refresh upstream Nix pins for 26.602.40724 (#420)
github-actions[bot] Jun 5, 2026
785216e
[codex] Keep Linux Chrome plugin available (#418)
colonelpanic8 Jun 5, 2026
f44140f
[codex] Sync embedded computer-use-linux v0.2.5 (#419)
avifenesh Jun 6, 2026
44798b3
Fix Linux titlebar tooltip overlap (#421)
lw33 Jun 6, 2026
21cd3b8
sync embedded computer use: gnome-screenshot fallback (v0.2.6) (#2) (…
avifenesh Jun 6, 2026
002aed2
fix(chrome): enable external browser use gate on Linux (#423)
losese Jun 6, 2026
43b38a6
Add persistent Electron launch flags config
ilysenko Jun 7, 2026
2790161
Fix Linux About dialog icon handling (#430)
lw33 Jun 8, 2026
ad69b07
fix(nix): refresh upstream Nix pins for 26.602.71036 (#433)
github-actions[bot] Jun 9, 2026
fa1d4d5
DMG 26.602.71036 Patch Drifts (#434)
Leay15 Jun 9, 2026
a681414
fix(linux): show build info in settings (#436)
colonelpanic8 Jun 9, 2026
e4de367
fix(linux): log tray setup failures (#437)
ilysenko Jun 9, 2026
c2d3851
fix(linux): avoid build-info helper variable shadowing (#438)
colonelpanic8 Jun 9, 2026
1ac6d99
fix(nix): refresh upstream Nix pins for 26.608.12217 (#440)
github-actions[bot] Jun 10, 2026
b06def4
Start latest upstream drift investigation
ilysenko Jun 10, 2026
67bacdb
Fix latest upstream patch drift
ilysenko Jun 10, 2026
77b0612
Fix latest upstream patch drift
ilysenko Jun 10, 2026
cc7f939
Merge pull request #442 from ilysenko/codex/fix-upstream-drift-latest…
ilysenko Jun 10, 2026
c837abb
Fix Linux titlebar overlay alias shadowing
ilysenko Jun 10, 2026
4203550
Merge pull request #444 from ilysenko/codex/fix-native-titlebar-shado…
ilysenko Jun 10, 2026
5891db6
Fix renderer "Oops" crash from missing Sparkle autodownload stub
jurgenmahn Jun 10, 2026
8302598
fix(launcher): surface curl diagnostics and restore the terminal CLI …
ryan-mt Jun 11, 2026
af0586c
fix(linux-features): espeak voice precedence and use-strict-safe help…
ryan-mt Jun 11, 2026
26b8c5c
fix(build): portable awk header matching and correct stderr redirects
ryan-mt Jun 11, 2026
c24038c
fix(contrib): resolve the cached Codex.dmg where install.sh actually …
ryan-mt Jun 11, 2026
a8b5d1b
fix(linux): add resize repaint hook
Jun 11, 2026
f89d663
Complete Linux updater Sparkle autodownload stub
ilysenko Jun 11, 2026
7fb6d38
Merge pull request #447 from jurgenmahn/fix/linux-updater-sparkle-aut…
ilysenko Jun 11, 2026
6f5cddd
Merge pull request #448 from ryan-mt/fix/launcher-tty-diagnostics
ilysenko Jun 11, 2026
76978ba
Merge pull request #449 from ryan-mt/fix/contrib-user-local-dmg-path
ilysenko Jun 11, 2026
ddf1466
Merge pull request #453 from ryan-mt/fix/build-ci-plumbing
ilysenko Jun 11, 2026
277f0e6
Merge pull request #452 from ryan-mt/fix/feature-patch-runtime-bugs
ilysenko Jun 11, 2026
25c95ce
Merge pull request #455 from mayrainLN/codex/linux-resize-ghosting
ilysenko Jun 11, 2026
3bb0548
Fix Linux Browser Use route liveness fallback
ilysenko Jun 11, 2026
c310664
Merge pull request #456 from ilysenko/codex/fix-browser-use-route-liv…
ilysenko Jun 11, 2026
3e02935
Fix remote control device key alias collisions
ilysenko Jun 11, 2026
cb8c8db
Merge pull request #458 from ilysenko/codex/fix-remote-mobile-device-…
ilysenko Jun 11, 2026
7c63998
Force opaque Linux window surfaces (#460)
ilysenko Jun 11, 2026
5db258c
chrome-plugin: prefer active Linux Chrome profiles
moxw Jun 11, 2026
29b6fab
Keep rate limit footer visible in current composer
nofihq Jun 12, 2026
1187f36
nix: wrap launcher to bake CODEX_CLI_PATH via cliPackage option
colonelpanic8 Jun 12, 2026
1553edb
Keep rate limit footer visible in current composer
nofihq Jun 12, 2026
241554c
Fix broad footer migration cache assumption
nofihq Jun 12, 2026
ac0c926
chrome-plugin: preserve active profile safeguards
moxw Jun 12, 2026
d2419c5
fix(nix): refresh upstream Nix pins for 26.609.30741 (#463)
github-actions[bot] Jun 12, 2026
a1ff3bf
Fix Linux plugin marketplace feature sync
joshyorko Jun 12, 2026
ceb6bb8
fix(linux): keep titlebar overlay refreshes native (#469)
jojomini1231-cloud Jun 12, 2026
9803f56
fix remote mobile cli symlink cleanup (#471)
ilysenko Jun 12, 2026
ea4282b
Add frameless-titlebar Linux feature
wy3z Jun 12, 2026
0a57a6a
Merge pull request #466 from colonelpanic8/colonelpanic/nix-codex-cli…
ilysenko Jun 12, 2026
d6fa1ce
Handle core-patched zoom overlay ternary in frameless-titlebar
wy3z Jun 12, 2026
79fe846
fix: keep avatar overlay clickable inside window bounds
ilysenko Jun 12, 2026
77b6176
Merge pull request #473 from ilysenko/fix/avatar-overlay-window-hit
ilysenko Jun 12, 2026
5e44573
Merge pull request #472 from jwiseuk/feature/frameless-titlebar
ilysenko Jun 12, 2026
97b055c
fix: escape dynamic remote plugin feature keys
ilysenko Jun 12, 2026
3a60c32
Merge remote-tracking branch 'origin/main' into review/pr-470
ilysenko Jun 12, 2026
70d1e11
Merge pull request #470 from joshyorko/patchraptor/linux-plugin-marke…
ilysenko Jun 12, 2026
d0eb6eb
chrome-plugin: close discarded profile backends
moxw Jun 12, 2026
213e890
Keep Linux menu patch idempotent over frameless-titlebar output
wy3z Jun 12, 2026
76534e4
tests: drop unrelated browser shim smoke test
moxw Jun 12, 2026
023ace5
fix(updater): ignore already installed DMG updates
moxw Jun 12, 2026
cfd17bf
feat(patches): enforce critical patches at build time, fail-soft othe…
ilysenko Jun 12, 2026
feb3a56
feat(patches): rebalance ciPolicy — critical means launch-critical
ilysenko Jun 12, 2026
46c8b90
feat(patches): strategy telemetry + evidence-based dead-needle prune
ilysenko Jun 12, 2026
dbbbd64
fix(chrome): preserve Browser Use CDP access on Linux
moxw Jun 12, 2026
3a5a7cf
refactor(patches): split main-process.js by concern behind a stable b…
ilysenko Jun 12, 2026
e8f8386
refactor(patches): drop all pre-current fallback needles — fresh DMG …
ilysenko Jun 12, 2026
e37b512
fix(patches): escape the NUL dedup delimiter in strategy-telemetry
ilysenko Jun 12, 2026
27da553
Merge pull request #478 from ilysenko/feature/patch-system-revision
ilysenko Jun 12, 2026
13d5f58
Merge remote-tracking branch 'origin/main' into codex/filter-idle-chr…
ilysenko Jun 12, 2026
2edfb6d
refactor(chrome-plugin): drop never-shipped sync-filter migration entry
ilysenko Jun 12, 2026
065b506
Merge pull request #461 from moxw/codex/filter-idle-chrome-profiles
ilysenko Jun 12, 2026
9b6d959
Merge origin/main into rate limit footer PR
ilysenko Jun 12, 2026
5b94761
Merge pull request #468 from nofihq/codex/transparent-persistent-rate…
ilysenko Jun 12, 2026
ace8ff3
Merge origin/main into menu idempotency PR
ilysenko Jun 12, 2026
9a08532
Merge pull request #474 from jwiseuk/fix/menu-patch-feature-idempotency
ilysenko Jun 12, 2026
2152ca2
fix(updater): protect same-dmg recovery paths
ilysenko Jun 12, 2026
7570c4f
Merge pull request #475 from moxw/codex/fix-same-dmg-update-detection
ilysenko Jun 12, 2026
954ee61
Merge origin/main into Chrome Browser Use CDP PR
ilysenko Jun 12, 2026
80267c7
Merge pull request #476 from moxw/codex/chrome-browser-use-cdp
ilysenko Jun 12, 2026
1d2bd26
fix(installer): ignore space-joined electron helpers (#480)
ilysenko Jun 12, 2026
42dd996
chore(sync): merge linux-port upstream 1d2bd26
nisavid Jun 13, 2026
084bc14
docs(sync): record upstream sync merge
nisavid Jun 13, 2026
39ad758
docs(sync): link upstream sync pull request
nisavid Jun 13, 2026
e646b6f
fix(sync): address upstream review findings
nisavid Jun 14, 2026
7a665d8
fix(sync): satisfy code scanning review
nisavid Jun 14, 2026
6085a65
fix(sync): address remaining review threads
nisavid Jun 15, 2026
95bcf88
test(updater): relax wrapper git timeout
nisavid Jun 15, 2026
f96b299
fix(sync): address follow-up review
nisavid Jun 15, 2026
ca2af43
fix(ci): parse final official DMG headers
nisavid Jun 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ permissions:

env:
CARGO_TERM_COLOR: always
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
CI_PACKAGE_VERSION: 2026.04.28.000000+ci${{ github.run_number }}

jobs:
Expand Down
18 changes: 14 additions & 4 deletions .github/workflows/official-dmg-build-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ permissions:
contents: read

env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
OFFICIAL_DMG_URL: https://persistent.oaistatic.com/codex-app-prod/Codex.dmg
OFFICIAL_DMG_PATH: /tmp/codex-official-dmg-ci/Codex.dmg
DMG_CACHE_SCHEMA_VERSION: v1
Expand Down Expand Up @@ -65,9 +66,11 @@ jobs:

curl -fsSLI "$OFFICIAL_DMG_URL" > "$headers_file"

last_modified="$(awk 'tolower($0) ~ /^last-modified:/ {sub(/\r$/,""); sub(/^[^:]+: /,""); value=$0} END{print value}' "$headers_file")"
etag="$(awk 'tolower($0) ~ /^etag:/ {sub(/\r$/,""); sub(/^[^:]+: /,""); gsub(/"/,""); value=$0} END{print value}' "$headers_file")"
content_length="$(awk 'tolower($0) ~ /^content-length:/ {sub(/\r$/,""); sub(/^[^:]+: /,""); value=$0} END{print value}' "$headers_file")"
# Match header names case-insensitively without gawk's IGNORECASE,
# which is a no-op under mawk (the default awk on ubuntu runners).
last_modified="$(awk 'tolower($0) ~ /^last-modified:/ {sub(/\r$/,""); sub(/^[^:]+:[[:space:]]*/,""); value=$0} END{print value}' "$headers_file")"
etag="$(awk 'tolower($0) ~ /^etag:/ {sub(/\r$/,""); sub(/^[^:]+:[[:space:]]*/,""); gsub(/"/,""); value=$0} END{print value}' "$headers_file")"
content_length="$(awk 'tolower($0) ~ /^content-length:/ {sub(/\r$/,""); sub(/^[^:]+:[[:space:]]*/,""); value=$0} END{print value}' "$headers_file")"

if [ -z "$last_modified" ]; then
last_modified="unknown"
Expand Down Expand Up @@ -141,10 +144,16 @@ jobs:
node scripts/ci/validate-patch-report.js patch-report.json --profile official-dmg-build

- name: Upload official DMG metadata artifact
# The build step aborts on critical patch failures; keep uploading the
# patch report so the failure is diagnosable from the artifact.
if: always()
uses: actions/upload-artifact@v7
with:
name: official-dmg-metadata
path: official-dmg-metadata.json
path: |
official-dmg-metadata.json
patch-report.json
if-no-files-found: ignore

- name: Write build summary
run: |
Expand All @@ -160,4 +169,5 @@ jobs:
echo "- Tested At (UTC): \`${{ steps.local-dmg.outputs.tested_at_utc }}\`"
echo "- Cache Hit: \`${{ steps.dmg-cache.outputs.cache-hit || 'false' }}\`"
echo "- Build command: \`make build-app DMG=$OFFICIAL_DMG_PATH\`"
echo "- Patch report: \`patch-report.json\`"
} >> "$GITHUB_STEP_SUMMARY"
1 change: 1 addition & 0 deletions .github/workflows/update-codex-hash.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ jobs:
NIX_CONFIG: experimental-features = nix-command flakes
CACHIX_CACHE_NAME: codex-app-linux
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
*.dmg
*.dmg.metadata
node_modules/
__pycache__/
*.py[cod]
Expand Down
19 changes: 18 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions computer-use-linux/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "codex-computer-use-linux"
version = "0.1.2-linux-alpha2"
version = "0.2.6-linux-alpha1"
edition = "2021"

[[bin]]
Expand All @@ -18,13 +18,13 @@ base64 = "0.22.1"
cosmic-protocols = { git = "https://github.com/pop-os/cosmic-protocols", rev = "160b086abe03cd34a8a375d7fbe47b24308d1f38", default-features = false, features = ["client"] }
evdev = "0.13.2"
futures-util = "0.3.32"
image = { version = "0.25", default-features = false, features = ["png"] }
image = { version = "0.25", default-features = false, features = ["jpeg", "png"] }
libc = "0.2"
rmcp = { version = "1.6.0", features = ["transport-io"] }
schemars = "1.0"
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.149"
tokio = { version = "1.52.3", features = ["macros", "rt", "time"] }
tokio = { version = "1.52.3", features = ["macros", "process", "rt", "time"] }
wayland-client = "0.31.11"
wayland-protocols = { version = "0.32.9", features = ["client", "staging"] }
xkeysym = "0.2.1"
Expand Down
162 changes: 156 additions & 6 deletions computer-use-linux/src/bin/codex-chrome-extension-host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const OBSERVED_TURN_TTL: Duration = Duration::from_secs(6 * 60 * 60);
const ROLLOUT_SEARCH_MAX_DEPTH: usize = 5;

type SharedState = Arc<Mutex<HostState>>;
type SharedChromeWriter = Arc<Mutex<Box<dyn Write + Send>>>;
type SharedClientWriter = Arc<Mutex<UnixStream>>;

#[derive(Clone)]
Expand Down Expand Up @@ -64,7 +65,7 @@ impl ChromeClientRouteError {
}

struct HostState {
stdout: Arc<Mutex<io::Stdout>>,
stdout: SharedChromeWriter,
rollout_tracker: RolloutTracker,
extension_id: Option<String>,
clients: HashMap<usize, Client>,
Expand All @@ -77,7 +78,7 @@ struct HostState {

impl HostState {
fn new(
stdout: Arc<Mutex<io::Stdout>>,
stdout: SharedChromeWriter,
rollout_tracker: RolloutTracker,
extension_id: Option<String>,
) -> Self {
Expand Down Expand Up @@ -145,7 +146,7 @@ impl HostState {
#[derive(Clone)]
struct RolloutTracker {
inner: Arc<Mutex<RolloutTrackerState>>,
stdout: Arc<Mutex<io::Stdout>>,
stdout: SharedChromeWriter,
sessions_root: Option<PathBuf>,
}

Expand All @@ -162,7 +163,7 @@ struct ObservedTurn {
}

impl RolloutTracker {
fn new(stdout: Arc<Mutex<io::Stdout>>) -> Self {
fn new(stdout: SharedChromeWriter) -> Self {
let tracker = Self {
inner: Arc::new(Mutex::new(RolloutTrackerState {
observed: HashMap::new(),
Expand Down Expand Up @@ -310,7 +311,7 @@ fn main() -> Result<()> {
fs::set_permissions(&socket_path, fs::Permissions::from_mode(0o600))
.with_context(|| format!("failed to chmod {}", socket_path.display()))?;

let stdout = Arc::new(Mutex::new(io::stdout()));
let stdout: SharedChromeWriter = Arc::new(Mutex::new(Box::new(io::stdout())));
let rollout_tracker = RolloutTracker::new(Arc::clone(&stdout));
let extension_id = extension_id_from_args();
let state = Arc::new(Mutex::new(HostState::new(
Expand Down Expand Up @@ -1097,6 +1098,115 @@ mod tests {
assert_eq!(state.next_chrome_id, 1);
}

#[test]
fn forwards_client_raw_cdp_call_requests_to_chrome_without_filtering() {
let (mut host_state, output) = test_host_state_with_output();
host_state.clients.insert(1, test_client());
let state = Arc::new(Mutex::new(host_state));
let request = json!({
"jsonrpc": "2.0",
"id": "client-cdp-call-1",
"method": "tab_cdp_call",
"params": {
"browser_id": "browser-1",
"tab_id": "42",
"method": "Runtime.evaluate",
"params": {
"expression": "document.title",
"returnByValue": true
},
"target": {
"target_id": "target-1"
},
"timeout_ms": 5000
}
});

handle_client_message(&state, 1, request.clone());

let chrome_id = format!("linux-{}-1", process::id());
let forwarded = read_captured_message(&output);
assert_eq!(forwarded["id"], chrome_id);
assert_eq!(forwarded["method"], "tab_cdp_call");
assert_eq!(forwarded["params"], request["params"]);

let state = state.lock().unwrap();
let pending = state.pending_chrome_requests.get(&chrome_id).unwrap();
assert_eq!(pending.client_id, 1);
assert_eq!(pending.client_request_id, json!("client-cdp-call-1"));
assert!(!pending.fallback_extension_info);
}

#[test]
fn forwards_client_raw_cdp_event_requests_to_chrome_without_filtering() {
let (mut host_state, output) = test_host_state_with_output();
host_state.clients.insert(1, test_client());
let state = Arc::new(Mutex::new(host_state));
let request = json!({
"jsonrpc": "2.0",
"id": "client-cdp-events-1",
"method": "tab_cdp_events",
"params": {
"after_sequence": 7,
"browser_id": "browser-1",
"limit": 25,
"methods": ["Runtime.consoleAPICalled", "Target.attachedToTarget"],
"tab_id": "42",
"target": {
"session_id": "session-1"
},
"timeout_ms": 500
}
});

handle_client_message(&state, 1, request.clone());

let forwarded = read_captured_message(&output);
assert_eq!(forwarded["id"], format!("linux-{}-1", process::id()));
assert_eq!(forwarded["method"], "tab_cdp_events");
assert_eq!(forwarded["params"], request["params"]);
}

#[test]
fn forwards_chrome_raw_cdp_responses_to_the_requesting_client() {
let (client_writer, mut client_reader) = UnixStream::pair().unwrap();
let mut state = test_host_state();
state.clients.insert(
1,
Client {
writer: Arc::new(Mutex::new(client_writer)),
},
);
state.pending_chrome_requests.insert(
"linux-1-1".to_string(),
PendingChromeRequest {
client_id: 1,
client_request_id: json!("client-cdp-call-1"),
fallback_extension_info: false,
},
);
let state = Arc::new(Mutex::new(state));

handle_chrome_message(
&state,
json!({
"jsonrpc": "2.0",
"id": "linux-1-1",
"result": {
"result": {
"type": "string",
"value": "Codex"
}
}
}),
);

let message = read_frame(&mut client_reader).unwrap().unwrap();
assert_eq!(message["id"], "client-cdp-call-1");
assert_eq!(message["result"]["result"]["value"], "Codex");
assert!(state.lock().unwrap().pending_chrome_requests.is_empty());
}

#[test]
fn get_info_falls_back_when_runtime_get_version_is_missing() {
let (client_writer, mut client_reader) = UnixStream::pair().unwrap();
Expand Down Expand Up @@ -1194,7 +1304,7 @@ mod tests {
}

fn test_host_state() -> HostState {
let stdout = Arc::new(Mutex::new(io::stdout()));
let stdout: SharedChromeWriter = Arc::new(Mutex::new(Box::new(io::stdout())));
HostState::new(
Arc::clone(&stdout),
RolloutTracker {
Expand All @@ -1208,6 +1318,46 @@ mod tests {
)
}

fn test_host_state_with_output() -> (HostState, Arc<Mutex<Vec<u8>>>) {
let output = Arc::new(Mutex::new(Vec::new()));
let stdout: SharedChromeWriter = Arc::new(Mutex::new(Box::new(CaptureWriter {
output: Arc::clone(&output),
})));
let state = HostState::new(
Arc::clone(&stdout),
RolloutTracker {
inner: Arc::new(Mutex::new(RolloutTrackerState {
observed: HashMap::new(),
})),
stdout,
sessions_root: None,
},
Some("abcdefghijklmnopabcdefghijklmnop".to_string()),
);
(state, output)
}

fn read_captured_message(output: &Arc<Mutex<Vec<u8>>>) -> Value {
let data = output.lock().unwrap().clone();
let mut cursor = io::Cursor::new(data);
read_frame(&mut cursor).unwrap().unwrap()
}

struct CaptureWriter {
output: Arc<Mutex<Vec<u8>>>,
}

impl Write for CaptureWriter {
fn write(&mut self, buffer: &[u8]) -> io::Result<usize> {
self.output.lock().unwrap().extend_from_slice(buffer);
Ok(buffer.len())
}

fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}

fn unique_test_dir(prefix: &str) -> PathBuf {
let nonce = SystemTime::now()
.duration_since(UNIX_EPOCH)
Expand Down
7 changes: 7 additions & 0 deletions computer-use-linux/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ pub struct PlatformReport {
pub dbus_session_bus_address: Option<String>,
pub xdg_runtime_dir: Option<String>,
pub gnome_shell_version: Check,
pub gnome_screenshot: Check,
}

#[derive(Debug, Clone, Serialize, JsonSchema)]
Expand Down Expand Up @@ -218,6 +219,10 @@ fn capability_map(
if portals.screenshot.ok {
screenshot_backends.push("portal".to_string());
}
// Subprocess fallback for background/systemd contexts the DBus paths reject.
if platform.gnome_screenshot.ok {
screenshot_backends.push("gnome_screenshot".to_string());
}

let mut window_backends = Vec::new();
if windowing.codex_gnome_shell_extension.ok {
Expand Down Expand Up @@ -519,6 +524,7 @@ fn platform_report() -> PlatformReport {
dbus_session_bus_address: dbus_session_address(),
xdg_runtime_dir: xdg_runtime_dir().map(|path| path.display().to_string()),
gnome_shell_version: command_check("gnome-shell", &["--version"]),
gnome_screenshot: command_check("gnome-screenshot", &["--version"]),
}
}

Expand Down Expand Up @@ -979,6 +985,7 @@ mod tests {
dbus_session_bus_address: Some("unix:path=/run/user/1000/bus".to_string()),
xdg_runtime_dir: Some("/run/user/1000".to_string()),
gnome_shell_version: Check::ok("GNOME Shell 46.0"),
gnome_screenshot: Check::ok("gnome-screenshot 41.0"),
}
}

Expand Down
Loading