Related docs:
../README.md../AGENTS.md— maintainer verification (npm run verify, lifecycle), Pitmuxsmoke expectations, and upstream rebaseliningTOOL_CONTRACT.md— fullelectronandqa.attachedfield contractsCOMMAND_REFERENCE.md— workflow snippets in the broader native command surfaceARCHITECTURE.md— wrapper design and the closedRQ-0068recipe-layer decisionSUPPORT_MATRIX.md—RQ-0096Electron support row and verification gates
This guide is the entry point for using pi-agent-browser-native against desktop Electron applications. The wrapper exposes agent_browser_electron with flat action fields that owns the awkward discover → launch → attach → probe → cleanup sequence so agents do not hand-build --remote-debugging-port argv, poll DevToolsActivePort, and kill profile directories. After attach, the rest of the native agent_browser surface (snapshot, find, click, fill, get, eval --stdin, batch, persistent browser code, and advanced attached QA) works the same way it does against a web page.
This document is structured for users, not implementers. Field-level rules live in TOOL_CONTRACT.md; this guide focuses on when and how to use them, and on the safety and ownership boundary the wrapper enforces.
- Pi users who want an agent to operate a local Electron app the same way it operates a web page.
- Coding agents that need a low-context lifecycle for desktop apps such as VS Code, Cursor, Obsidian, Slack, or any app built on Electron, without re-implementing the CDP attach dance every session.
- Maintainers and reviewers validating the wrapper's Electron behavior before release; verification evidence lives under
RQ-0096inSUPPORT_MATRIX.md.
It is not an upstream agent-browser reference and it does not replace the canonical TOOL_CONTRACT.md for exact field semantics, validation rules, or failure categories.
Enable the tools with agent_browser_tools { "enable": ["electron", "qa"] }. Below, electron.launch and similar labels mean agent_browser_electron { "action": "launch", ... }; qa.attached means agent_browser_qa { "attached": true, ... }. These are separate tools, not nested inputs on agent_browser.
electron.list → discover Electron apps (host-only; no upstream spawn)
electron.launch → launch a wrapper-owned isolated app, attach via CDP, hand off (snapshot|tabs|connect)
electron.status → liveness, debug-port, and target inspection (read-only)
electron.probe → compact one-call state read (title/url/focus/tabs/snapshot)
electron.cleanup → close managed session, stop the tracked process, remove the temp profile
qa.attached → smoke check against the currently attached session (no URL)
Two ownership modes coexist:
- Wrapper-owned launches —
electron.launchstarts a brand-new app process with an isolated temporary user-data-dir and an OS-chosen debug port. The wrapper records alaunchIdfor every such launch andelectron.cleanuponly operates on thoselaunchIds. - Manually launched apps — you start the Electron app yourself (for example with
open -a Slack --args --remote-debugging-port=9222 --remote-allow-origins='*'), then attach with{ "args": ["connect", "9222"], "sessionMode": "fresh" }. The wrapper does not own that process; you are responsible for shutting it down and cleaning its profile.
Choosing between the two is a real decision, not a stylistic one. See Wrapper-owned vs manually launched.
Use agent_browser_electron for the lifecycle rows below and agent_browser for the native snapshot: discover, launch with the default snapshot handoff, inspect, then clean up.
{ "action": "list", "query": "code" }
{ "action": "launch", "appName": "Visual Studio Code", "handoff": "snapshot" }
{ "args": ["snapshot", "-i"] }
{ "action": "probe", "timeoutMs": 5000 }
{ "action": "cleanup", "launchId": "electron-…" }The launch result carries both a launchId (used by status/probe/cleanup) and an attached sessionName (used by browser-style snapshot/tab/click/find calls). Read both from details.electron.launch and details.electron.identifiers. With default implicit session reuse, the quick-start args: ["snapshot", "-i"] line uses that attached session without an extra --session argument; pass --session explicitly when you target a named upstream session instead.
For a quick "is the app actually showing what we expect?" smoke check after attach:
{ "attached": true, "expectedText": "Explorer", "screenshotPath": ".dogfood/electron.png" }qa.attached runs against the current managed session without opening a URL, so it works for any attached app — wrapper-owned or manually launched.
Pick the mode that matches the state you need.
electron.launch (wrapper-owned) |
args: ["connect", …] (manual host launch) |
|
|---|---|---|
| Profile | Isolated temporary userDataDir |
The app's normal profile (your real signed-in state) |
| Debug port | OS-chosen via --remote-debugging-port=0 and DevToolsActivePort |
Caller-supplied port (for example 9222) |
| Signed-in state | No — first-run or empty profile | Yes — whatever is in the launched profile |
| Already-running app | Cannot attach to it | Required (or relaunch yourself with a debug port) |
| Lifecycle ownership | Wrapper owns shutdown and profile cleanup | You own shutdown and profile cleanup |
| When to use | Anything you can do against a fresh app: tooling, UX flows, scripted local QA, exploring panels, packaged debugging | Tasks that explicitly need the user's signed-in Slack/Obsidian/VS Code state |
| How to clean up | electron.cleanup with the returned launchId |
Close the app yourself; do not call electron.cleanup |
When the explicit goal is the user's signed-in local app state and the app is not already running:
# macOS example
open -a Slack --args --remote-debugging-port=9222 --remote-allow-origins='*'Then attach and choose a ready target before using refs:
{ "args": ["connect", "9222"], "sessionMode": "fresh" }
{ "args": ["tab", "list"] }
{ "args": ["tab", "t2"] }
{ "args": ["snapshot", "-i"] }
{ "attached": true, "expectedText": "Channels" }A successful connect means the CDP endpoint accepted the session; it does not prove the app has an active rendered page yet. Prefer details.nextActions when present: verify-connected-session-url performs the only page read allowed while the attached target is unverified, and list-connected-session-tabs inspects attached targets. A verified target clears the guard; otherwise navigate explicitly or select the intended tab. After the read-only list, select or confirm a stable t<N> target, verify it with get url, and run snapshot -i explicitly before trusting refs. If the first snapshot -i says No active page, follow list-tabs-after-no-active-page. If it returns no useful refs without that error, manually run tab list, select a stable t<N> id for the app surface, then retry a condition wait or snapshot -i on that selected target.
If the app is already running without a debug port, ask before relaunching it — relaunching may lose unsaved state and Electron's single-instance behavior will silently drop a second invocation's --remote-debugging-port flag.
Use this ladder for desktop-host readiness instead of blind sleep loops:
- Prefer a real condition when one exists:
wait --text,wait --url,wait --fn,wait --load <state>, orwait --download. - After raw
connect, inspect targets withtab list, select the stabletab t<N>app surface, then use a condition wait orsnapshot -ion that selected surface. - After wrapper-owned
electron.launch, useelectron.probeorelectron.statuswhen launch health, debug-port liveness, or target mismatch matters. - Use
qa.attachedwhen the readiness check can be expressed as expected text or selector plus diagnostics against the current managed session. - Use fixed waits only as a last resort. For legitimately slow waits, pass an explicit upstream wait timeout and let the wrapper derive the subprocess watchdog, or set top-level
timeoutMsto at least the wait duration plus a small grace window. - Treat a fixed-wait payload such as
"waited":"timeout"as elapsed time, not proof that the host finished. Verify with an observed condition, freshsnapshot -i, or screenshot before continuing.
This project is not adding a first-class host-idle primitive yet. Revisit that only if repeated desktop smokes show that condition waits, qa.attached, electron.probe, snapshots, and screenshots cannot cover the workflow.
The exact field schemas, validation rules, and details.* payload shapes live in TOOL_CONTRACT.md#agent_browser_electron. This section is a usage-oriented overview.
Host-only scan; does not spawn upstream agent-browser. macOS (/Applications/*.app, ~/Applications/*.app) and Linux (.desktop launchers under standard XDG, Flatpak, and Snap locations) are supported in v1. On Windows (and any non-macOS/non-Linux host), list returns details.electron.platform: "unsupported" with an empty apps array—use executablePath (or a host appPath that resolves to a verifiable Electron binary) for launch instead; inspectElectronExecutablePath in extensions/agent-browser/lib/electron/discovery.ts still gates Windows executables before spawn.
{ "action": "list", "query": "code", "maxResults": 25 }Returns app metadata under details.electron.apps: name, optional bundleId/desktopId, appPath, executablePath, platform, and optional non-blocking sensitivity annotations. Apps flagged as likely sensitive (categories such as notes, chat, mail, developer-workspace, or passwords-auth) are printed with [likely sensitive: …]. These are advisory hints, not enforcement; see Safety and ownership for the policy boundary.
Pass exactly one target: appPath, appName, bundleId, or executablePath. The wrapper resolves the target, verifies Electron runtime and app payload evidence, applies optional caller-owned allow / deny policy, creates an isolated temp userDataDir, launches with --remote-debugging-port=0 plus safe defaults, reads DevToolsActivePort, then attaches through upstream connect as a fresh managed session.
Payload evidence accepts app.asar, an app/ directory, or the stock npm Electron runtime's default_app.asar. macOS also requires Contents/Frameworks/Electron Framework.framework; Linux/Windows require the existing Chromium resource evidence. These checks apply to discovery and explicit paths alike, and remain a correctness gate rather than a security boundary.
For an unpackaged development app, point executablePath at the stock Electron binary and put the app directory in appArgs:
{
"action": "launch",
"executablePath": "/path/to/node_modules/electron/dist/Electron.app/Contents/MacOS/Electron",
"appArgs": ["/path/to/fixture-app"]
}{
"action": "launch",
"appName": "Visual Studio Code",
"handoff": "snapshot",
"targetType": "page",
"timeoutMs": 30000,
"appArgs": ["--disable-telemetry"]
}Handoff selection (handoff field):
| Value | Behavior | When to use |
|---|---|---|
"snapshot" (default) |
Attach, verify get url, list targets, capture snapshot -i in one call |
You need interactive refs immediately for clicks/fills |
"tabs" |
Attach, verify get url, and list targets only |
Safer diagnostic start when you only need target discovery |
"connect" |
Attach and stop | You will run your own follow-up commands |
targetType defaults to "page"; use "webview" or "any" for apps whose useful UI is exposed as a webview target.
CDP readiness requires /json/version to contain a nonempty, non-whitespace Browser or browser string. An arbitrary JSON object is not readiness evidence; other version metadata is optional. The same rule determines debug-port liveness in status and cleanup.
Optional timeoutMs on electron.launch sets the host readiness polling budget for DevToolsActivePort and CDP metadata. The clock starts after target discovery and policy checks; upstream attach and handoff have separate subprocess budgets. When omitted, the default is 15 seconds with a hard maximum of 120 seconds, matching ELECTRON_LAUNCH_DEFAULT_TIMEOUT_MS and ELECTRON_LAUNCH_MAX_TIMEOUT_MS in extensions/agent-browser/lib/electron/launch.ts. Pi cancellation is separate: an already-cancelled call never launches the app, while cancellation during readiness polling or URL/tab/snapshot handoff closes the managed session, stops the tracked process, removes its isolated profile, and returns failureCategory: "aborted" without waiting for the launch timeout.
Wrapper-owned launches always use an isolated temp profile and an OS-chosen port. If wrapper validation, managed-session policy, or the post-attach live-URL handoff guard fails after the host app starts, the wrapper immediately stops that process and removes the isolated profile; it retains a partial tracked record only when cleanup itself cannot finish. --user-data-dir, --remote-debugging-port, --remote-debugging-address, --remote-debugging-pipe, and bare -- in appArgs are rejected. There is no caller-supplied port and no way to make electron.launch reuse the app's normal signed-in profile or attach to an already-running app — by design. Use the manual path described above when those are the actual requirements.
Read-only inspection of one or more tracked launches. Without launchId or all, it selects the single active wrapper launch when unambiguous.
{ "action": "status" }
{ "action": "status", "launchId": "electron-…" }
{ "action": "status", "all": true }Reports cleanupState, current debug-port and PID liveness, bounded CDP targets, and freshly measured userDataDirState under details.electron.statuses. An explicit launchId can inspect a historical cleaned launch record; default and all: true selection exclude cleaned records. Cleanup history does not determine current liveness. The tracked profile path is present, absent (only native lstat ENOENT), or unknown (other filesystem errors); a dangling symlink is present. This measures that path only, not all app residue, and is not stored in the launch record. Its managed-session title/URL reads hold the normal daemon-policy lock and owned restore context. Mismatch fields surface when the current managed session or tab no longer matches a live wrapper launch target — typically the cue to follow reattach-electron-launch before trusting old refs.
Status and probe compare complete URLs before shortening them for display. Managed-session/probe URL summaries remain capped at 300 characters (probe tab URLs at 240); probe page-target/ref state retains the full URL.
probe collapses what would otherwise be separate get url / get title / focused-element eval / tab list / snapshot -i calls into one bounded result. Use it instead of chaining those reads when you just need a quick "where are we?" check. The wrapper holds the managed-session daemon-policy lock for the probe and runs every underlying read with the session's owned restore decision, so probing cannot restart the daemon under a different restore key.
{ "action": "probe" }
{ "action": "probe", "launchId": "electron-…", "timeoutMs": 5000 }Output appears under details.electron.probe: title, url, focusedElement, activeTab, tabs, compact snapshot metadata (refCount, refIds, optional text preview and omission counts), and errors. If every underlying read fails, the tool fails with failureCategory: "upstream-error"; it does not report a successful empty partial probe. Probes reject an unverified target before helper reads, then verify the live URL again before title, eval, tab, or snapshot helpers so external target drift cannot expose the wrong page. A current-managed probe also persists top-level details.namespace, sessionTabTarget, and refSnapshot so Pi reload/branch replay keeps the same namespaced page identity; unverified transitions persist as details.sessionTabTargetUnknown: true until get url or explicit navigation establishes a trustworthy target. When launchId is given, the probe is tied to that tracked launch and will surface mismatch guidance if the wrapper sees a session or target drift; visible output also includes debug-port/pid liveness so a stale about:blank against a dead launch is unmistakable.
timeoutMs bounds each underlying read subprocess. Use it for dense desktop apps when the default budget is too short, or to fail fast when you suspect the app process is wedged.
Closes the tracked managed session, stops only the wrapper-tracked process, verifies that the debug port no longer serves /json/version, and removes the wrapper-created userDataDir. Cleanup waits for actual exit when it has a tracked child handle. For a live restored PID without that handle, it signals only when the process start identity recorded at launch matches the live process, which proves the PID was not reused. Records without an identity (for example from older versions), unreadable identities, and mismatches leave the process and profile intact; stop such a process manually.
Cleanup partial failures fail the tool result with failureCategory: "cleanup-failed" and the retry-electron-cleanup next action references the same launchId so retries are bounded.
{ "action": "cleanup", "launchId": "electron-…" }
{ "action": "cleanup", "all": true }electron.cleanup never targets:
- manually launched apps
- externally supplied debug ports
- arbitrary Electron processes the wrapper did not start
- explicit screenshots, downloads, PDFs, traces, HAR files, or recordings saved to caller-chosen paths
For manual launches, close commands (close, quit, or exit) only close the browser/CDP session. Close the app yourself and clean its profile/temp files with normal host tools.
On Pi quit, active wrapper-owned Electron launches are best-effort cleaned. On /reload, the current branch-visible active Electron launch and its isolated temp userDataDir are preserved for continuity while off-branch owned Electron launches are cleaned before process-local ownership is cleared. First reuse after reload/resume checks that the PID and profile are present, the saved browser WebSocket endpoint still matches the live app, and upstream get cdp-url points to that browser or one of its current targets. Ordinary browser calls, status reads, and probes share this check under the existing session lock; no reconnect or ref reset is needed. The get cdp-url read honors the caller's timeoutMs and cancellation, while localhost CDP requests keep their short fixed fetch budgets. Missing or mismatched evidence does not grant reuse, and generic restore-disabled sessions keep their existing rules. If cleanup is partial and skips or fails user-data-dir removal because the process or debug port is still live, the generic temp sweep preserves that profile path across reload, quit, repeated temp cleanup, process-exit cleanup, and stale temp-root pruning after restart rather than deleting it out from under the remaining host resource. If electron.cleanup closes the attached managed session but host process/profile cleanup is partial, later default browser calls still rotate away from that closed wrapper-managed session. Stale restored records (PID gone, port dead) are reported instead of guessed at or killed.
agent_browser_electron with action: "list" has no timeoutMs field. For every other action, flat timeoutMs applies to different surfaces, not an end-to-end action deadline. Authoritative rules and env overrides live in the Electron contract in TOOL_CONTRACT.md#agent_browser_electron.
| Action | What timeoutMs covers when set |
Typical default when omitted |
|---|---|---|
launch |
Host-side wait for DevToolsActivePort and CDP readiness |
15 s, hard-capped at 120 s (normalizeTimeoutMs in extensions/agent-browser/lib/electron/launch.ts) |
status |
Each optional managed-session get url / get title subprocess, including get cdp-url when verifying a restored connection |
Normal wrapper subprocess budget (35 s, or PI_AGENT_BROWSER_PROCESS_TIMEOUT_MS); localhost CDP probes use 1000 ms each (ELECTRON_CDP_FETCH_TIMEOUT_MS in extensions/agent-browser/lib/electron/cdp.ts) |
cleanup |
Applied separately to managed-session close and the initial tracked-process exit wait; not a deadline for debug-port checks or profile removal |
PI_AGENT_BROWSER_IMPLICIT_SESSION_CLOSE_TIMEOUT_MS when set, else 35000 ms (getImplicitSessionCloseTimeoutMs in extensions/agent-browser/lib/runtime.ts, passed through cleanupTrackedElectronHostLaunches in extensions/agent-browser/lib/orchestration/electron-host/index.ts) |
probe |
Each upstream read: optional get cdp-url verification, then get url, get title, focused eval --stdin, tab list, and snapshot -i |
Same wrapper subprocess default (35 s, or PI_AGENT_BROWSER_PROCESS_TIMEOUT_MS, from getAgentBrowserProcessTimeoutMs in extensions/agent-browser/lib/process.ts) |
agent_browser_qa has two flat forms: { url, … } and { attached: true, … }. The attached form is the right tool for Electron smoke checks after either launch path because it does not open a URL and runs all checks against the current managed session.
{
"attached": true,
"expectedText": "Explorer",
"expectedSelector": "@e1",
"checkConsole": true,
"checkErrors": true,
"screenshotPath": ".dogfood/electron.png"
}qa.attached rejects url and is incompatible with sessionMode: "fresh" — attach first with electron.launch or raw connect, then run qa.attached. Preserved-buffer diagnostics (checkConsole, checkErrors, checkNetwork) default to false for attached QA; opt into them when you want historical session buffers to fail the smoke. The full field rules and pass/fail classification live in TOOL_CONTRACT.md#agent_browser_qa.
In attached Electron sessions, broad selectors such as body, html, main, or [role=application] can read the entire app shell. When get text <selector> looks too broad, the wrapper may attach details.electronGetTextScopeWarning and a snapshot-for-electron-text-scope next action; prefer a fresh snapshot -i, a current @ref, or a narrower panel selector.
Enable source and use agent_browser_source for experimental hints about the source file/component behind a visible element. It is opt-in and evidence-based: it reports confidence and evidence rather than claiming a guaranteed mapping. The same experimental helper works against packaged Electron apps, but with two important boundaries:
- Scope of the workspace scan.
sourceLookupwalks the Pi session cwd (defaultmaxWorkspaceFiles: 2000, hard cap 5000). It does not unpackapp.asaror installed app resources. For packaged apps where the source lives insideContents/Resources/app.asar, the workspace-search lane will commonly return no candidates. - React DevTools requirement.
react inspect <id>requires the session to have been launched with--enable react-devtoolsbefore first navigation. For Electron, the wrapper'selectron.launchpath does not inject--enable react-devtoolsinto the Electron process; that flag belongs to upstreamagent-browserChromium launches. If the Electron app does not already expose a React DevTools backend, expectreact inspectto fail; DOM-attribute and workspace-search candidates may still surface.
For wrapper-tracked packaged Electron sessions where status is no-candidates, the wrapper attaches workspaceRoot plus optional electronContext (launchId?, appName?, appPath?, executablePath?, sessionName?, url?) and limitations explaining the bundle/asar boundary, plus snapshot-electron-session, probe-electron-launch, and list-electron-tabs next actions so you can inspect the live app and decide whether to widen the workspace or pull source out-of-band before re-running the lookup.
{ "selector": "#save", "reactFiberId": "2", "componentName": "SaveButton" }Treat sourceLookup output as a starting point for navigation, not a substitute for reading code. Full contract: TOOL_CONTRACT.md#agent_browser_source.
Remote debugging exposes app content (DOM, network, JavaScript) to the attached browser tool. The wrapper ships isolation defaults; it does not classify any app as too-risky-to-launch.
- Launches with
--user-data-dir=<wrapper-created-temp>and--remote-debugging-port=0. - Reads the OS-chosen port from
DevToolsActivePort. - Adds
--disable-extensions,--no-first-run, and--no-default-browser-checkalongside sanitized callerappArgs. - Rejects
appArgsthat try to override lifecycle/debug flags. - Refuses to launch non-Electron targets (correctness gate, not a security gate).
- Treats
electron.cleanupas wrapper-owned only; never touches manually launched apps.
- The decision to launch or attach to a sensitive app in the first place.
- Optional
allow/denypolicy lists when you want guardrails. - Profile and process cleanup for manually launched apps.
- Host-file cleanup for any explicit screenshots, downloads, HARs, traces, or recordings saved to caller-chosen paths.
electron.cleanupdoes not touch these.
Both lists match appName, bundleId, desktopId, appPath, or executablePath by substring.
{
"action": "launch",
"appName": "Slack",
"allow": ["Slack"],
"deny": ["1Password", "Bitwarden"]
}Rules:
- If
allowis set, the target must match at least one entry. - If
denyis set, a matching target is rejected. denywins on conflict.- With neither set, launch is permitted.
Policy mismatches fail with failureCategory: "policy-blocked" and details.electron.failure.policy names the matched list and entry.
electron.list may annotate common private-data apps (notes, chat, mail, developer-workspace, passwords-auth) with sensitivity.level: "likely-sensitive" and a visible [likely sensitive: …] marker. These are advisory hints only. They do not block launch and they do not replace caller allow / deny.
details.failureCategory values you should expect from Electron flows, with the recovery move:
| Category | When | Recovery |
|---|---|---|
validation-error |
Bad input (missing target, conflicting fields, non-Electron target) | Fix the request; the message names the problem |
policy-blocked |
Caller allow / deny rejected the launch |
Adjust the policy or pick a different target |
timeout |
DevToolsActivePort never appeared in time |
Inspect details.electron.failure.diagnostics (PID, profile path, port file state, elapsed/timeout); retry with a higher timeoutMs if the app legitimately needs more time |
upstream-error |
Launch/attach/spawn/CDP failure that does not fit a more specific bucket | Inspect details.electron.failure.diagnostics; the app may be missing dependencies or hitting a CDP race |
tab-drift |
A successful-looking command was followed by a dead process / debug port / unrecoverable about:blank |
Use the appended status-electron-launch / probe-electron-launch next actions, then decide whether to relaunch |
cleanup-failed |
Cleanup only partially succeeded | Inspect details.electron.cleanup.results[].steps for remaining process/port/profile state; retry-electron-cleanup references the same launchId |
stale-ref |
@e… ref reused after a navigation/rerender |
Take a fresh snapshot -i (or follow refresh-electron-refs-after-rerender when the wrapper appends it) |
Failed startup diagnostics include outputCaptured, stdoutTail / stderrTail, and stdoutTruncated / stderrTruncated. Each tail reads at most the last 4096 source bytes before UTF-8 decoding and normal credential redaction, and appears in both visible failure text and structured details. Empty output is reported explicitly; stdoutError / stderrError report capture-read or close errors without replacing the original startup reason, exit status, or cleanup warning.
The app writes to mode-0600 stdout.log and stderr.log inside its isolated profile. Regular-file capture lets a retained app keep writing independently of Pi's output streams. POSIX launches are detached and can outlive the host; Windows launches remain host-owned and stop when the host exits. Preserved logs survive either outcome. Logs follow profile preservation and removal; the read limit is not a lifetime disk limit. If failed-startup process cleanup cannot finish, the profile and logs are protected from general temp cleanup. Any failure to persist that protection appears alongside the original failure.cleanupError; in-memory protection remains. Use the reported PID and profile path to resolve that failed cleanup before removing files.
Single-instance Electron behavior is a common cause of timeout and upstream-error. Many Electron apps enforce a single running instance and silently drop a second invocation's --remote-debugging-port flag. If the app is already running without a debug port, quit it first or use the manual host-launch path against the existing instance instead.
- The app is enforcing single-instance; quit the running copy first, then retry.
- The app may have moved its Electron framework directory; pass
executablePathexplicitly. timeoutMsis too short for a heavy app; raise it (launch.timeoutMsis bounded but generous).- Read the redacted stdout/stderr tails in the failure text or
details.electron.failure.diagnosticsfirst; dependency and startup errors often explain the failure.DevToolsActivePort, port number, PID liveness, and timing provide the remaining context.
- On Linux, the binary may be a custom rebrand without
chrome_*.paksiblings, an AppImage without a.desktopentry, or a statically linked fork. PassexecutablePathdirectly. - On macOS, apps installed outside
/Applicationsand~/Applicationsare not scanned in v1. PassappPathorexecutablePathexplicitly. - Windows hosts report
platform: "unsupported"fromelectron.list; always passexecutablePath(or a resolvableappPath) forlaunch.
- Verify both runtime resources and an app payload (
app.asar,app/, ordefault_app.asar) exist beside the executable. On macOS, the Electron Framework is also required; Chromium paks alone do not establish an Electron target. - The stock npm Electron runtime is supported without repackaging. Pass its binary as
executablePathand your unpackaged app directory as anappArgsoperand.
- Some Electron apps take a beat to render. The default
handoff: "snapshot"already retries briefly; if it still reports no refs, runtab list, select the intended stablet<N>app tab, then runsnapshot -iagain. - For raw
connect, do the same target check before assuming the signed-in app is ready; the attach can succeed before an active page is available. - For apps whose UI lives in a webview, switch
targetTypeto"webview"or"any"so the wrapper attaches to the right CDP target.
- A successful upstream
clickmeans the action was dispatched, not that the app handled it. Re-snapshot, checkdetails.pageChangeSummary, or useqa.attachedto verify. - Electron apps frequently rerender in place (no URL change). The wrapper may attach
refresh-electron-refs-after-rerenderto remind you to re-snapshot before reusing@e…refs.
- Custom quick-input controls (VS Code's quick-pick, command palette, etc.) often need focus + keyboard typing rather than a direct
fill. The wrapper attachesdetails.fillVerificationwhenget valuedisagrees with the requested text; followinspect-after-fill-verificationand switch to focus +keyboard typebefore submitting.
- Broad selectors (
body,html,main,[role=application]) read the entire shell. Use a current@refor a narrower panel selector. The wrapper attachesdetails.electronGetTextScopeWarningand asnapshot-for-electron-text-scopenext action when it detects this pattern.
- Expected when the app's source lives inside
app.asar. The wrapper does not unpack bundles. Useelectron.probe/snapshot-electron-session/list-electron-tabsnext actions to inspect the live UI, or pull source separately into the Pi session cwd before re-running the lookup.
- A shortened URL ending in
...is display-only; long URLs alone do not cause mismatch or reattach guidance. Status/probe compare full normalized URLs. electron.statusmay report a live wrapper launch while the managed session has drifted toabout:blank. Followreattach-electron-launch, then refresh refs before reusing old@e…handles. For non-wrapper tab drift wheredetails.nextActionsnamesselect-intended-tab-after-drift, use that stablet<N>action plussnapshot-after-tab-recoverybefore continuing.
agent_browser_code can inspect/interact with the attached browser by selecting its returned sessionName (and namespace when present). The browser survives the cell; variables reset. Electron host discovery/launch/cleanup stays on agent_browser_electron. Updated cooperating Pi processes order helpers and actions with the shared socket/namespace/session lock; a whole code cell holds that boundary. Multiple named native sessions attached to the same external app are not a universal app lock. Humans and external clients can still change the app.
Recovery actions now name agent_browser_electron with flat parameters, or agent_browser for native operations. Enable electron through the loader if a returned lifecycle action is not active. See 0.7 migration.
Before ending the task:
- Call
electron.cleanup(orelectron.cleanupwithall: true) for every wrapper-ownedlaunchIdyou started. The result reports per-step state formanaged-session,process,debug-port, anduser-data-dir. - Confirm
details.electron.cleanup.summarydoes not list remaining resources. - For manually launched apps, close the app yourself and clean any profile or temp files you created.
electron.cleanupwill not (and should not) touch them. - Remove any explicit screenshots, recordings, downloads, PDFs, traces, or HAR files you saved to caller-chosen paths. Artifact cleanup is host-owned; the wrapper only reports them under
details.artifactsanddetails.artifactCleanup.
If cleanup returns failureCategory: "cleanup-failed", inspect details.electron.cleanup.results[].steps and use retry-electron-cleanup for the same launchId. Do not invent new cleanup commands for processes the wrapper did not start.
- Fake-upstream Electron discovery/lifecycle tests cover list/launch/status/probe/cleanup without a real app.
- Real-app validation is a manual
tmuxsmoke pass perAGENTS.md. Electron is a narrow typed lifecycle (list/launch/status/probe/cleanup) adopted because agents repeatedly failed the manual discover→debug-port→attach→cleanup sequence; it is not a generic recipe runtime (RQ-0068/RQ-0096).
- For exact field semantics, schemas, and
details.*payloads:TOOL_CONTRACT.md#agent_browser_electronandTOOL_CONTRACT.md#agent_browser_qa. - For workflow examples woven into the broader command surface:
COMMAND_REFERENCE.md. - For the closed
RQ-0068recipe-layer decision that bounds why Electron support is a typed shorthand and not a generic recipe runtime:ARCHITECTURE.md. - For the full release-readiness audit and the
RQ-0096evidence row:SUPPORT_MATRIX.md.