mirror of
https://github.com/stablyai/orca.git
synced 2026-11-15 10:27:33 +00:00
fix(browser): retry orcad's agent-browser config write after a failure
This commit is contained in:
+33
@@ -0,0 +1,33 @@
|
||||
// Both pick the socket directory, so dropping either loses a running helper and its live tabs.
|
||||
const SOCKET_LOCATION_KEYS = new Set(['AGENT_BROWSER_SOCKET_DIR', 'AGENT_BROWSER_NAMESPACE'])
|
||||
|
||||
// No idle timeout: this daemon owns the user’s remote Chromium and all its live tabs.
|
||||
// agent-browser 0.33+ reads an unset timeout as one hour, so only "0" keeps it unbounded.
|
||||
export function externalChromiumAgentBrowserEnvironment(options: {
|
||||
inheritedEnv: NodeJS.ProcessEnv
|
||||
executablePath: string
|
||||
profilePath: string
|
||||
sessionName: string
|
||||
configPath: string
|
||||
browserArgs?: readonly string[]
|
||||
}): NodeJS.ProcessEnv {
|
||||
// Only the socket location is inherited: a user's AGENT_BROWSER_HEADED or _EXTENSIONS would open a
|
||||
// visible window. Windows env keys ignore case.
|
||||
const env = Object.fromEntries(
|
||||
Object.entries(options.inheritedEnv).filter(([key]) => {
|
||||
const upper = key.toUpperCase()
|
||||
return !upper.startsWith('AGENT_BROWSER_') || SOCKET_LOCATION_KEYS.has(upper)
|
||||
})
|
||||
)
|
||||
return {
|
||||
...env,
|
||||
AGENT_BROWSER_IDLE_TIMEOUT_MS:
|
||||
options.inheritedEnv.AGENT_BROWSER_IDLE_TIMEOUT_MS?.trim() || '0',
|
||||
AGENT_BROWSER_EXECUTABLE_PATH: options.executablePath,
|
||||
AGENT_BROWSER_PROFILE: options.profilePath,
|
||||
AGENT_BROWSER_SESSION: options.sessionName,
|
||||
// An explicit config skips ~/.agent-browser/config.json and ./agent-browser.json.
|
||||
AGENT_BROWSER_CONFIG: options.configPath,
|
||||
AGENT_BROWSER_ARGS: options.browserArgs?.join('\n') ?? ''
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
} from '../../../browser/agent-browser/observation/agent-browser-screenshot-arguments'
|
||||
import { runProcess } from '@orca/process-host'
|
||||
import { resolveExternalChromiumProfilePath } from './external-chromium-profile-path'
|
||||
import { externalChromiumAgentBrowserEnvironment } from './external-chromium-agent-browser-environment'
|
||||
|
||||
const COMMAND_TIMEOUT_MS = 90_000
|
||||
const MAX_OUTPUT_BYTES = 50 * 1024 * 1024
|
||||
@@ -73,40 +74,6 @@ function classifyAgentBrowserError(message: string): string {
|
||||
return 'browser_error'
|
||||
}
|
||||
|
||||
// Both pick the socket directory, so dropping either loses a running helper and its live tabs.
|
||||
const SOCKET_LOCATION_KEYS = new Set(['AGENT_BROWSER_SOCKET_DIR', 'AGENT_BROWSER_NAMESPACE'])
|
||||
|
||||
// No idle timeout: this daemon owns the user’s remote Chromium and all its live tabs.
|
||||
// agent-browser 0.33+ reads an unset timeout as one hour, so only "0" keeps it unbounded.
|
||||
export function externalChromiumAgentBrowserEnvironment(options: {
|
||||
inheritedEnv: NodeJS.ProcessEnv
|
||||
executablePath: string
|
||||
profilePath: string
|
||||
sessionName: string
|
||||
configPath: string
|
||||
browserArgs?: readonly string[]
|
||||
}): NodeJS.ProcessEnv {
|
||||
// Only the socket location is inherited: a user's AGENT_BROWSER_HEADED or _EXTENSIONS would open a
|
||||
// visible window. Windows env keys ignore case.
|
||||
const env = Object.fromEntries(
|
||||
Object.entries(options.inheritedEnv).filter(([key]) => {
|
||||
const upper = key.toUpperCase()
|
||||
return !upper.startsWith('AGENT_BROWSER_') || SOCKET_LOCATION_KEYS.has(upper)
|
||||
})
|
||||
)
|
||||
return {
|
||||
...env,
|
||||
AGENT_BROWSER_IDLE_TIMEOUT_MS:
|
||||
options.inheritedEnv.AGENT_BROWSER_IDLE_TIMEOUT_MS?.trim() || '0',
|
||||
AGENT_BROWSER_EXECUTABLE_PATH: options.executablePath,
|
||||
AGENT_BROWSER_PROFILE: options.profilePath,
|
||||
AGENT_BROWSER_SESSION: options.sessionName,
|
||||
// An explicit config skips ~/.agent-browser/config.json and ./agent-browser.json.
|
||||
AGENT_BROWSER_CONFIG: options.configPath,
|
||||
AGENT_BROWSER_ARGS: options.browserArgs?.join('\n') ?? ''
|
||||
}
|
||||
}
|
||||
|
||||
// A profile held by another Chromium also fails ProcessSingleton; only an access failure means unwritable.
|
||||
function unwritableProfileLockLine(message: string): string | undefined {
|
||||
return message
|
||||
@@ -144,12 +111,21 @@ export class ExternalChromiumBrowserSession {
|
||||
|
||||
// Empty, so agent-browser's defaults (headless, no extensions) apply instead of a user's config.
|
||||
private configPath(): Promise<string> {
|
||||
this.configPathWrite ??= (async () => {
|
||||
const path = join(this.statePath, 'agent-browser-config.json')
|
||||
await mkdir(this.statePath, { recursive: true, mode: 0o700 })
|
||||
await writeFile(path, '{}\n', { mode: 0o600 })
|
||||
return path
|
||||
})()
|
||||
if (!this.configPathWrite) {
|
||||
const write = (async () => {
|
||||
const path = join(this.statePath, 'agent-browser-config.json')
|
||||
await mkdir(this.statePath, { recursive: true, mode: 0o700 })
|
||||
await writeFile(path, '{}\n', { mode: 0o600 })
|
||||
return path
|
||||
})()
|
||||
// A failed write retries on the next command instead of failing every later one.
|
||||
write.catch(() => {
|
||||
if (this.configPathWrite === write) {
|
||||
this.configPathWrite = null
|
||||
}
|
||||
})
|
||||
this.configPathWrite = write
|
||||
}
|
||||
return this.configPathWrite
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user