From 1b9a7f7ff58befe46dc4ce1cf5f4e702af312b87 Mon Sep 17 00:00:00 2001 From: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com> Date: Fri, 13 Nov 2026 22:30:21 +0000 Subject: [PATCH] fix(computer): leave running apps alone and keep the launch env clean --- .../Sources/OrcaComputerUseMacOS/main.swift | 17 ++++++++++++-- native/computer-use-windows/runtime.ps1 | 23 ++++++++++++++----- src/shared/cli-argument-boundary.ts | 1 + 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/native/computer-use-macos/Sources/OrcaComputerUseMacOS/main.swift b/native/computer-use-macos/Sources/OrcaComputerUseMacOS/main.swift index 94b748a949b..d39e6aded96 100644 --- a/native/computer-use-macos/Sources/OrcaComputerUseMacOS/main.swift +++ b/native/computer-use-macos/Sources/OrcaComputerUseMacOS/main.swift @@ -816,8 +816,16 @@ final class Provider { guard let url = installedApplicationURL(query) else { throw ProviderError.coded("app_not_found", "app '\(query)' is not running and no installed app has that name, bundle ID or path") } - if let bundleId = Bundle(url: url)?.bundleIdentifier, blockedBundleIds.contains(bundleId) { - throw ProviderError.coded("app_blocked", "app '\(bundleId)' is blocked for safety") + if let bundleId = Bundle(url: url)?.bundleIdentifier { + if blockedBundleIds.contains(bundleId) { + throw ProviderError.coded("app_blocked", "app '\(bundleId)' is blocked for safety") + } + // Why: a path or display-name query misses the running check above, and menu bar apps aren't listed. + if let running = NSRunningApplication.runningApplications(withBundleIdentifier: bundleId) + .first(where: { !$0.isTerminated }), + let app = appByPid(running.processIdentifier) { + return renderLaunch(app, launched: false) + } } let pid: pid_t do { @@ -834,6 +842,11 @@ final class Provider { guard let app = appByPid(pid) else { throw ProviderError.coded("app_launch_failed", "'\(query)' exited right after it launched") } + // Why: Launch Services reports the app once it checks in; activation, asked for or not, lands after it finishes launching. + let deadline = Date().addingTimeInterval(5) + while !app.app.isFinishedLaunching, !app.app.isTerminated, Date() < deadline { + Thread.sleep(forTimeInterval: 0.05) + } return renderLaunch(app, launched: true) } diff --git a/native/computer-use-windows/runtime.ps1 b/native/computer-use-windows/runtime.ps1 index 8f58f2832d4..3106195fcaf 100644 --- a/native/computer-use-windows/runtime.ps1 +++ b/native/computer-use-windows/runtime.ps1 @@ -1589,12 +1589,18 @@ function Resolve-OrcaLaunchExecutable([string]$Query) { throw "appNotFound(`"$Query`")" } -function Wait-OrcaLaunchedWindow([int]$ProcessId, [int]$TimeoutMilliseconds) { +function Wait-OrcaLaunchedWindow([int]$ProcessId, [string]$ProcessName, [DateTime]$StartedAt, [int]$TimeoutMilliseconds) { $deadline = [DateTime]::UtcNow.AddMilliseconds($TimeoutMilliseconds) while ([DateTime]::UtcNow -lt $deadline) { $process = Get-Process -Id $ProcessId -ErrorAction SilentlyContinue - if ($null -eq $process) { return $null } - if ($process.MainWindowHandle -ne 0) { return $process } + if ($null -ne $process -and $process.MainWindowHandle -ne 0) { return $process } + if ($null -eq $process) { + # Why: stubs such as Windows 11's notepad.exe start the real app under the same name and exit. + $handoff = Get-OrcaWindowProcesses | Where-Object { + $_.ProcessName -ieq $ProcessName -and $_.StartTime -ge $StartedAt + } | Select-Object -First 1 + if ($null -ne $handoff) { return $handoff } + } Start-Sleep -Milliseconds 100 } Get-Process -Id $ProcessId -ErrorAction SilentlyContinue @@ -1612,8 +1618,10 @@ function Invoke-OrcaLaunchApp($Operation) { $query = ([string]$Operation.app).Trim() if ([string]::IsNullOrWhiteSpace($query)) { throw 'appNotFound("")' } $processName = [System.IO.Path]::GetFileNameWithoutExtension($query) - # Why: starting a running app again usually opens a second window over the user's work. - $running = Get-OrcaWindowProcesses | Where-Object { $_.ProcessName -ieq $processName } | Select-Object -First 1 + # Why: starting a running app again usually opens a second window over the user's work; tray-only apps count too. + $session = [System.Diagnostics.Process]::GetCurrentProcess().SessionId + $running = @(Get-Process -Name ([WildcardPattern]::Escape($processName)) -ErrorAction SilentlyContinue | Where-Object { $_.SessionId -eq $session }) | + Sort-Object { $_.MainWindowHandle -eq 0 } | Select-Object -First 1 if ($null -ne $running) { Assert-OrcaProcessAllowed $running return New-OrcaLaunchResult $running $false @@ -1625,6 +1633,9 @@ function Invoke-OrcaLaunchApp($Operation) { if ($name.Contains($fragment.Replace(" ", ""))) { throw "appBlocked(`"$name`")" } } $activate = [bool]$Operation.activate + # Why: Orca runs this script with ELECTRON_RUN_AS_NODE=1, which would start an Electron app as bare Node. + Remove-Item Env:ELECTRON_RUN_AS_NODE -ErrorAction SilentlyContinue + $startedAt = (Get-Date).AddSeconds(-1) try { $processId = if ($activate) { (Start-Process -FilePath $executable -PassThru).Id @@ -1634,7 +1645,7 @@ function Invoke-OrcaLaunchApp($Operation) { } catch { throw "app_launch_failed: could not launch '$query': $($_.Exception.Message)" } - $process = Wait-OrcaLaunchedWindow $processId 10000 + $process = Wait-OrcaLaunchedWindow $processId ([System.IO.Path]::GetFileNameWithoutExtension($executable)) $startedAt 10000 if ($null -eq $process) { throw "app_launch_failed: the process started for '$query' exited; if it handed off to another process, find that one with list-apps" } diff --git a/src/shared/cli-argument-boundary.ts b/src/shared/cli-argument-boundary.ts index 72aa11b8735..876ff766436 100644 --- a/src/shared/cli-argument-boundary.ts +++ b/src/shared/cli-argument-boundary.ts @@ -3,6 +3,7 @@ export const CLI_GLOBAL_FLAGS: readonly string[] = ['help', 'json', ...CLI_GLOBA export const CLI_BOOLEAN_FLAGS = new Set([ 'accept-changed-state', + 'activate', 'agent-wait', 'all', 'allow-failed-archive-hook',