From 96c954f3bee4297351f01471ed3f6f437ed4a361 Mon Sep 17 00:00:00 2001 From: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Date: Sat, 1 Aug 2026 00:33:20 -0700 Subject: [PATCH 1/2] chore: remove force-added design docs from docs/ (#11891) Keep only the durable docs already allowlisted for tracking (STYLEGUIDE, assets, localized readme, and reference compatibility guides). Drop feature design notes, plans, and repro artifacts that were force-added past the existing docs ignore rules. --- docs/agent-status-over-wsl.md | 377 --- docs/android-emulation-streaming.md | 96 - docs/android-emulation.md | 376 --- docs/automations-navigation-stack.md | 75 - docs/browser-normal-download-behavior.md | 183 - .../8979-screenshots/after-fable-visible.png | Bin 30479 -> 0 bytes .../8979-screenshots/before-fable-hidden.png | Bin 24212 -> 0 bytes docs/claude-fable-weekly-usage-meter.md | 75 - docs/claude-scoped-oauth-usage-limits.md | 84 - docs/claude-usage-tracking-codexbar-parity.md | 391 --- docs/cmd-j-tab-session-search.md | 141 - ...ompact-worktree-card-setting-graduation.md | 94 - docs/configurable-open-in-menu.md | 126 - docs/delete-workspace-windows-unregistered.md | 152 - .../double-tap-modifier-keybindings-design.md | 247 -- docs/droid-orchestration-group.md | 83 - docs/editor-find-layout-aware-shortcut.md | 92 - docs/failed-automation-rerun-action.md | 50 - ...ing-terminal-panel-position-persistence.md | 73 - docs/github-tasks-close-reason-parity.md | 137 - docs/image-viewer-pinch-zoom.md | 119 - docs/issue-7649-vscode-wsl-launch.md | 95 - ...all-sessions-also-kills-empty-terminals.md | 203 -- docs/linear-issues-load-more.md | 133 - docs/linear-scope-selector.md | 126 - docs/native-chat-codex-tui-parity.md | 217 -- docs/new-worktree-sidebar-reveal.md | 79 - docs/orchestration-primitives.html | 2937 ---------------- docs/orchestration-reset-scope-validation.md | 89 - docs/persist-tree-view-source-control.md | 93 - .../2026-07-27-ssh-relay-pty-backpressure.md | 2949 ----------------- .../2026-07-18-daemon-lifecycle-retirement.md | 279 -- ...onpty-startup-query-and-focus-authority.md | 545 --- ...-daemon-generation-safety-investigation.md | 620 ---- .../plans/2026-07-27-ssh-reconnect-fanout.md | 1009 ------ ...orchestration-seamless-update-migration.md | 576 ---- .../remote-agent-session-host-authority.md | 456 --- .../resource-manager-memory-metrics.md | 54 - docs/refresh-github-issues-after-create.md | 138 - docs/remote-web-paste-activity-fixes.md | 108 - docs/renderer-memory-profile-2026-06-01.md | 171 - docs/show-full-tab-title-tooltip.md | 131 - ...source-control-push-failure-ai-recovery.md | 368 -- docs/ssh-config-target-compatibility.md | 61 - ...sh-handler-reregistration-port-forwards.md | 243 -- docs/terminal-close-confirmation.md | 114 - docs/terminal-main-owned-state.md | 99 - docs/terminal-scroll-intent-architecture.md | 329 -- docs/windows-secure-file-acl-hardening.md | 104 - docs/workspace-space-scan-resource-bounds.md | 238 -- docs/worktree-delete-preflight.md | 78 - docs/worktree-sidebar-drag-autoscroll.md | 139 - docs/wrapped-terminal-file-link-fragments.md | 115 - docs/wsl-osc7-sleep-wake-cwd.md | 153 - 54 files changed, 16020 deletions(-) delete mode 100644 docs/agent-status-over-wsl.md delete mode 100644 docs/android-emulation-streaming.md delete mode 100644 docs/android-emulation.md delete mode 100644 docs/automations-navigation-stack.md delete mode 100644 docs/browser-normal-download-behavior.md delete mode 100644 docs/bug-reproductions/8979-screenshots/after-fable-visible.png delete mode 100644 docs/bug-reproductions/8979-screenshots/before-fable-hidden.png delete mode 100644 docs/claude-fable-weekly-usage-meter.md delete mode 100644 docs/claude-scoped-oauth-usage-limits.md delete mode 100644 docs/claude-usage-tracking-codexbar-parity.md delete mode 100644 docs/cmd-j-tab-session-search.md delete mode 100644 docs/compact-worktree-card-setting-graduation.md delete mode 100644 docs/configurable-open-in-menu.md delete mode 100644 docs/delete-workspace-windows-unregistered.md delete mode 100644 docs/double-tap-modifier-keybindings-design.md delete mode 100644 docs/droid-orchestration-group.md delete mode 100644 docs/editor-find-layout-aware-shortcut.md delete mode 100644 docs/failed-automation-rerun-action.md delete mode 100644 docs/floating-terminal-panel-position-persistence.md delete mode 100644 docs/github-tasks-close-reason-parity.md delete mode 100644 docs/image-viewer-pinch-zoom.md delete mode 100644 docs/issue-7649-vscode-wsl-launch.md delete mode 100644 docs/kill-all-sessions-also-kills-empty-terminals.md delete mode 100644 docs/linear-issues-load-more.md delete mode 100644 docs/linear-scope-selector.md delete mode 100644 docs/native-chat-codex-tui-parity.md delete mode 100644 docs/new-worktree-sidebar-reveal.md delete mode 100644 docs/orchestration-primitives.html delete mode 100644 docs/orchestration-reset-scope-validation.md delete mode 100644 docs/persist-tree-view-source-control.md delete mode 100644 docs/plans/2026-07-27-ssh-relay-pty-backpressure.md delete mode 100644 docs/reference/plans/2026-07-18-daemon-lifecycle-retirement.md delete mode 100644 docs/reference/plans/2026-07-19-windows-conpty-startup-query-and-focus-authority.md delete mode 100644 docs/reference/plans/2026-07-21-windows-daemon-generation-safety-investigation.md delete mode 100644 docs/reference/plans/2026-07-27-ssh-reconnect-fanout.md delete mode 100644 docs/reference/plans/2026-07-28-orchestration-seamless-update-migration.md delete mode 100644 docs/reference/remote-agent-session-host-authority.md delete mode 100644 docs/reference/resource-manager-memory-metrics.md delete mode 100644 docs/refresh-github-issues-after-create.md delete mode 100644 docs/remote-web-paste-activity-fixes.md delete mode 100644 docs/renderer-memory-profile-2026-06-01.md delete mode 100644 docs/show-full-tab-title-tooltip.md delete mode 100644 docs/source-control-push-failure-ai-recovery.md delete mode 100644 docs/ssh-config-target-compatibility.md delete mode 100644 docs/ssh-handler-reregistration-port-forwards.md delete mode 100644 docs/terminal-close-confirmation.md delete mode 100644 docs/terminal-main-owned-state.md delete mode 100644 docs/terminal-scroll-intent-architecture.md delete mode 100644 docs/windows-secure-file-acl-hardening.md delete mode 100644 docs/workspace-space-scan-resource-bounds.md delete mode 100644 docs/worktree-delete-preflight.md delete mode 100644 docs/worktree-sidebar-drag-autoscroll.md delete mode 100644 docs/wrapped-terminal-file-link-fragments.md delete mode 100644 docs/wsl-osc7-sleep-wake-cwd.md diff --git a/docs/agent-status-over-wsl.md b/docs/agent-status-over-wsl.md deleted file mode 100644 index 5ea8992eda1..00000000000 --- a/docs/agent-status-over-wsl.md +++ /dev/null @@ -1,377 +0,0 @@ -# Agent Status over WSL (STA-1515) - -Status: implemented and rig-validated (2026-07-09 round 3 + 2026-07-10 round-4 re-run -pinned to the hardened build, Windows 11 + WSL2 NAT): Claude end-to-end live — -provisioning, working→done in the store, completion toast, loopback-only posture, and -restart resume over a daemon-surviving PTY with the instance-keyed endpoint dir reused -across restarts. The round-4 re-run also proved the `--exec` spawn form live (host -process table) and the stale-exit reinstall upgrading the guest to the new bundle -version in place. Residual: Codex's done/Stop leg unproven live — env-blocked on the -rig (no dev-profile Codex credentials AND the model backend is unreachable from the -guest under NAT); everything that fired behaved correctly. Confirm on a credentialed -rig with a guest-reachable backend, ideally on a fresh distro to also observe the -deferred Codex trust entries landing after config.toml is seeded. -Owner: brennanb2025. Linear: STA-1515. -Precedent this mirrors: the SSH agent-hook relay (`src/relay/agent-hook-server.ts`, -`src/shared/agent-hook-relay.ts`, ingest at `agentHookServer.ingestRemote` in -`src/main/agent-hooks/server.ts`). - -## Background — how we got here - -GitHub issue `7565` reported OMP agents in WSL worktrees disappearing from the worktree -sidebar after v1.4.124. Diagnosis split it into a regression and a pre-existing class gap: - -- The **regression** was a title-normalization change (PR `7447`) that stopped idle OMP - titles from producing the sidebar's title-derived fallback row. Decision: the sidebar is - moving to **hook-driven rows only** (fallback removal in flight, separate PR), so the - fallback was not restored. -- The **class gap** is that agent hooks have never worked from inside WSL for any agent. - Two scoped PRs fixed it for OMP alone (merged, live-validated on a Windows+WSL2-NAT rig): - - PR `7642` — Orca-managed WSL shells wrap interactive `omp` invocations with - `--extension "$ORCA_OMP_STATUS_EXTENSION"` (the env var is WSLENV `/p`-translated so - the WSL process reads the extension out of the Windows filesystem via `/mnt/c`). - - PR `7641` — when the extension's loopback POST cannot connect, it delivers via - Windows-side `/mnt/c/Windows/System32/curl.exe` (a Windows process, so *its* - `127.0.0.1` is the loopback Orca actually binds). Fire-and-forget spawn, - `--noproxy 127.0.0.1`, memoized WSL/curl probes, load-tolerant timeouts - (`--connect-timeout 3 --max-time 10`; 0.5s dropped events under load). - -This document is the full context for the general fix: every other hook client is still -dead from WSL, and the hooks-only sidebar change makes this work the gate for the -Windows+WSL story. - -## Why hooks don't work on Windows+WSL — two independent gaps - -### Gap A — transport - -The hook listener binds `127.0.0.1` only, deliberately (`src/main/agent-hooks/server.ts`, -`listen(0, '127.0.0.1')`; auth via `X-Orca-Agent-Hook-Token`, 403 otherwise). Every hook -client POSTs to a hardcoded `http://127.0.0.1:$ORCA_AGENT_HOOK_PORT/hook/`. - -WSL2 under default **NAT** networking is a VM with its own network namespace. Microsoft's -localhost forwarding is **one-way (Windows→WSL only)**: `127.0.0.1` inside WSL is WSL's -own loopback, so every POST dies `ECONNREFUSED` — silently, because hook clients are -deliberately fail-open. Reaching Windows from WSL would require the host vNIC IP (changes -per boot) + a non-loopback listener bind + a firewall rule — all three conflict with the -loopback-only security posture. - -The env coordinates DO cross correctly (`src/main/pty/wsl-orca-env.ts` -`addOrcaWslInteropEnv`: WSLENV `PORT/u TOKEN/u ENV/u VERSION/u` plus -`ORCA_AGENT_HOOK_ENDPOINT/p` path-translated; called from `src/main/ipc/pty.ts` and -`src/main/daemon/pty-subprocess.ts`). The address is simply unreachable. - -Opt-in **mirrored** networking (Win11, `.wslconfig`) shares loopback and makes plain fetch -work — the fix must not fight it. No `wslinfo` probing is needed: under mirrored mode the -relay's preferred-port bind collides with the Windows listener and the `EADDRINUSE` -fallback (below) handles it, while clients that go straight to the shared loopback reach -the Windows listener directly. Both delivery paths stay valid. - -### Gap B — installation - -Hook configs and scripts are written to the **Windows** home by every hook service: -Claude `settings.json` + managed scripts, Codex config, Gemini/Cursor/Droid/Devin/Grok/ -Copilot scripts, Amp/OpenCode plugin files, the Pi/OMP extension file. An agent inside WSL -reads the **WSL-side** `$HOME` and sees none of it. There is zero WSL-targeted install -code in `src/main/agent-hooks/` or any hook service. SSH remotes have the exact precedent -needed: dedicated remote installers (`src/main/ssh/ssh-relay-session.ts` remote -settings.json handling; PR `7744` installed Droid/Copilot hooks over SSH). - -Consequence: even mirrored-networking users get no hooks — transport fine, configs absent. -OMP escapes Gap B by *pointing across* the boundary (`/mnt/c` path via `/p` translation) -rather than installing WSL-side; that trick can carry file *content* for some clients, but -shell hooks still execute inside WSL and then hit Gap A regardless. - -## Transport map — every client, how it posts - -Endpoint file contract: `writeEndpointFile` (`src/shared/agent-hook-listener.ts`) emits -exactly four keys (`ORCA_AGENT_HOOK_PORT/TOKEN/ENV/VERSION`) to `endpoint.env` (POSIX) / -`endpoint.cmd` (Windows) — **no host field**. Shell clients source it to refresh stale -coords after an Orca restart; node clients parse it. It is never executed as a delivery -script. Clients prefer endpoint-FILE coords over env (restart re-coordination) — any -transport change must preserve that property. - -| Client | Mechanism | Runtime that POSTs | -| --- | --- | --- | -| Claude, Codex, Gemini, Cursor, Droid, Devin, Grok | managed shell script | `curl` (POSIX) / `curl.exe` (Windows), built in `src/main/agent-hooks/installer-utils.ts` | -| Copilot | managed script | `curl` (POSIX) / PowerShell `Invoke-WebRequest` (Windows) | -| command-code | managed script (parse-not-source hardened) | `curl` | -| Amp, OpenCode | in-process node plugin | `fetch` | -| Pi / OMP | bundled in-process extension (`src/main/pi/agent-status-extension-source.ts`) | `fetch`, now with the WSL curl.exe fallback | - -All of them target `127.0.0.1`. - -## Status quo + why this gates the hooks-only sidebar - -Today the worktree-card rows still have a title-derived fallback producer -(`src/renderer/src/components/sidebar/worktree-title-derived-agent-rows.ts`), so WSL users -DO currently see rows for title-rich agents (Claude `✳`/spinner titles, Gemini glyphs) — -degraded (generic text, no prompt/last-message preview, no notifications) but present. -Title-poor agents are dark (Codex — hence GH `6907`). When the hooks-only change removes -that producer, **every non-OMP agent in a WSL worktree loses its card row entirely until -this work ships**. Hook fidelity adds: prompt + last-assistant-message previews, -waiting/blocked precision, completion notifications, AI Vault / native chat session -integration. - -## Solution design — WSL relay + WSL-side installation - -### Transport (Gap A): guest-resident relay, host-owned stdio - -Run a small receiver **inside WSL on WSL's own loopback, listening on the very port the -clients were already given** (`$ORCA_AGENT_HOOK_PORT` — free inside WSL, since that port -only exists on the Windows side). Unmodified clients then deliver successfully with -**zero client changes**; the reporter's diagnostic relay in GH `7565` proved this shape -live. Forward each parsed envelope to the Windows host over the relay's **own stdio** -(Orca spawns it via `wsl.exe`, so it owns that pipe). Ingest through the existing trust -boundary: `agentHookServer.ingestRemote` (`src/main/agent-hooks/server.ts`), envelope -shape `src/shared/agent-hook-relay.ts` — identical to the SSH relay, which runs a -loopback-only receiver on the remote box and forwards over the SSH control channel. - -**Port binding.** Bind the inherited `$ORCA_AGENT_HOOK_PORT` first — it keeps every -already-crossed coordinate (env and the `/p`-translated endpoint file) truthful with zero -divergence. On `EADDRINUSE` inside the guest, fall back to the SSH relay's own pattern: -bind `127.0.0.1:0`, write a **WSL-side endpoint file**, and point WSL PTYs' -`ORCA_AGENT_HOOK_ENDPOINT` at it (clients already prefer endpoint-file coords over env). -The relay writes that WSL-side endpoint file in **both** modes so restart re-coordination -never depends on `/mnt/c` translation being readable. - -Lifecycle: one relay per distro **per Orca instance** (concurrent instances have distinct -ports, so guest listeners never collide); **ensure** — not just start — whenever a WSL PTY -exists: first spawn *and* daemon-PTY reattach after an Orca restart (WSL PTYs survive in -the daemon; the new instance has a new port + token and must respawn the relay before -surviving agents re-coordinate). The relay **exits when its stdin closes**: a lingering -guest listener would let WSL's own Windows→WSL forwarder grab the freed Windows-side port -and blackhole stale Windows-side hook posts. Restart if WSL restarts; token still -validated at the relay's HTTP receiver; harmless under mirrored networking (bind -fallback) and inert on non-WSL platforms (ensure only fires from WSL PTY spawns). - -Reliability contract (invariant class `agent-session.hook-transport`): hook clients are -fail-open silent, so the relay must not be — spawn failures, `EADDRINUSE` fallback, and -forward errors each leave a diagnosable breadcrumb; the `wsl.exe` "Catastrophic failure -(E_UNEXPECTED)" retry is **bounded** with backoff, never a spawn loop. Oracle: provider- -contract tests with fault injection (stdin close → exit, occupied port → fallback + file -rewrite, envelope round-trip to `ingestRemote`) rather than end-to-end flows only. - -Design notes from a survey of comparable WSL-capable tools (kept nameless per policy): -- Guest-resident component + host-owned channel + guest-side installation, explicitly - reusing the tool's SSH-remote machinery, **is the established pattern**. No surveyed - tool makes guest processes dial back to a Windows-localhost listener — the merged OMP - curl.exe stopgap is the outlier as a primary path (but see the round-4 revised - stance below: it survives as the no-node fallback). -- Prefer host-owned **stdio** over Windows→WSL localhost port forwarding (wslhost - forwarding is known-flaky under load; one surveyed tool dials the distro vNIC IP just to - avoid it — stdio sidesteps the question entirely). -- WSL offers no persistent control channel between separate `wsl.exe` invocations — - collapse the relay's ensure-installed + launch into **one idempotent script per spawn**. -- Install into the guest from inside the guest (download/extract in WSL) or stream the - binary through `wsl.exe` stdin — not by copying through `/mnt/c`. - -### Installation (Gap B): WSL-side hook installers - -Write agent hook configs/scripts into the WSL-side home, per agent, analogous to the SSH -remote installers — via `wsl.exe`-executed scripts (preferred, mirrors SSH most closely) -or `\\wsl.localhost\\...` writes. Without this half, the relay receives nothing: -the hook clients themselves are absent from the WSL filesystem. - -## Alternatives considered (and why not) - -1. **Endpoint-file host/URL field** — the file already crosses into WSL (`/p`-translated), - but clients read only PORT/TOKEN and hardcode the host, so all ~13 still need edits; - and a WSL-reachable listener bind breaks the loopback-only posture (LAN exposure, - firewall prompts). -2. **Replicate the curl.exe bridge per client** — the shell clients share ~2 generated - builders so it is cheaper than it sounds, but it is N point fixes, requires WSL interop - enabled (`/etc/wsl.conf` can disable it), pays a per-event process spawn (load-sensitive, - see validation facts), and keeps the ecosystem-outlier direction. -3. **Listener-side bind changes / rely on mirrored networking** — posture conflict / - opt-in only. -4. **OSC 9999 in-band status** (`src/shared/agent-status-osc.ts`, parsed per-pane in - `pty-transport.ts` and `orca-runtime.ts`) — zero-network and pane-attributed, but only - viable for in-process clients and carries status payloads, not the full hook event - vocabulary (prompts, tools, completion) — cannot replace the pipeline. - -## Facts + gotchas from the 2026-07-08 Windows-rig validation - -- curl.exe interop delivery works under NAT (shipped for OMP), but per-event process spawn - is load-sensitive: `--connect-timeout 0.5` dropped 3/3 events to a *healthy* listener - under load; fine at 3s. A resident relay avoids per-event spawns entirely. -- `wslinfo --networking-mode` distinguishes NAT vs mirrored. -- Clients prefer endpoint-FILE coords over env. Testing gotcha: unset - `ORCA_AGENT_HOOK_ENDPOINT` in synthetic tests or events go to the real running app. -- Server ingest silently drops paneKeys that are not `uuid:uuid`-shaped — use real-shaped - keys in synthetic validation. -- OMP is a Bun single-file binary; Bun's `node:child_process`/`fetch` compat held. Other - in-process clients run inside their agents' runtimes — verify per runtime. -- Environmental: fresh WSL 2.7.10 intermittently threw "Catastrophic failure - (E_UNEXPECTED)" from `wsl.exe -d -- bash -lc` under concurrent spawn load - (cleared by `wsl --terminate`). The relay spawn path should tolerate/retry this. -- Fork-PR CI runs sit in `action_required` until approved: - `gh api repos/stablyai/orca/actions/runs//approve -X POST`. - -## Acceptance - -On a default-config Windows 11 + WSL2 **NAT** machine: launch **Codex or Claude** -(explicitly not OMP) in a WSL worktree → live hook-driven worktree-card row with status -transitions and a completion notification; hook listener still bound to Windows loopback -only; zero per-client transport changes; hooks installed WSL-side automatically (no manual -config); harmless under mirrored networking and inert on non-WSL platforms. After an Orca -restart with the WSL agent still running (daemon-surviving PTY), status events resume -without relaunching the agent. - -## 2026-07-09 Windows-rig validation follow-ups - -The first live GUI run proved every mechanism in isolation but failed end-to-end, yielding -two fixes: - -1. **Link death must be handled, not just child death.** A mux protocol error or timeout - can kill the host↔guest link while the guest process (and its 204-returning receiver) - stays alive — the exact observed signature: hooks POST 204, store never populates. - `wsl-hook-relay-link.ts` now guarantees exactly-once death handling from either signal; - the manager breadcrumbs it, kills the child, and self-restarts after a short cooldown - (a live agent session produces no new PTY spawns to re-trigger ensure). - `ORCA_WSL_HOOK_RELAY_DEBUG=1` traces every received envelope pre-ingest so a live rig - can pinpoint any residual drop. The full host chain is pinned by a live integration - test (real bundle over real child stdio through the real manager into a real - `ingestRemote`). -2. **(Round 2) The renderer's SSH-era ownership gate dropped `wsl:*` events.** With the - link fixed, envelopes reached `ingestRemote` and the durable cache, but - `useIpcEvents.applyAgentStatus` compares the stamped connectionId against the owning - repo's — `"wsl:" !== null` for a local repo, so every WSL-relayed status died - before `setAgentStatus`/notifications. Fix: `wsl:*` ids are transport provenance, not - ownership — the gate normalizes them to local (null) via - `isWslHookRelayConnectionId`, while still rejecting WSL-stamped events against - SSH-owned repos. Provenance stays stamped (it made the drop diagnosable in the first - place). -3. **Codex reads a redirected home.** Orca launches WSL Codex with `CODEX_HOME` pointed at - the managed runtime home (`~/.local/share/orca/codex-runtime-home/home`), so installing - hooks to `~/.codex` left Codex dark. The installers now accept an explicit codex home; - the trust write into `config.toml` is deferred while that file doesn't exist (the - launch path seeds it only-if-absent — creating it first would cancel the seed), and the - manager re-runs the idempotent installers on later ensures (throttled) to upsert trust - once the seed lands. Consequence: the very first WSL Codex session after a cold relay - may miss hooks; the next one has them. - -## 2026-07-09 adversarial-review hardening (pre-rig round 3) - -Four independent review lenses over the full diff; confirmed findings fixed: - -- **Endpoint identity (all 4 reviewers)**: the guest endpoint dir was keyed by the - ephemeral Windows hook port, so a daemon-surviving agent kept sourcing the DEAD - `port-P1` file after an Orca restart — breaking the restart-resume acceptance criterion - and regressing shipped OMP recovery. Now keyed by a restart-stable instance key - (hash of the Windows endpoint file path = userData + namespace, crossed via - `ORCA_WSL_HOOK_INSTANCE`): the restarted instance's relay REWRITES the same file, which - is exactly what re-coordinates survivors. -- **Restart policy**: every failure now arms the restart timer (one failed relaunch no - longer ends self-recovery), and the timer probes `wsl --list --running` first — `wsl -d` - BOOTS a stopped distro, so recovery must never resurrect a VM the user shut down; a - stopped distro's state is dropped instead (next WSL terminal re-ensures). Failure - counters only reset after 2 min of stable uptime, so connect-then-die loops escalate to - the 10-min cap instead of cycling every 10s. -- **Install-dir versioning**: the guest install dir is namespaced by bundle version, so - concurrent Orca instances with different bundles (dev + prod) never reinstall over each - other; tmp files carry the guest PID. The install spawn also gained the 30s timeout it - was missing (a wedged wsl.exe could previously pin the state machine at 'starting' - forever). -- **Guest node resolution**: candidates (PATH, nvm glob, fixed paths) are each - version-probed, first pass wins — an apt node 12 on PATH no longer masks an nvm node 20 - into a false "no node >= 18" 10-minute cooldown. -- **wsl.exe text handling**: `WSL_UTF8=1` on all spawns + NUL-stripping on stderr, so the - "Catastrophic failure" transient-retry matcher and breadcrumbs survive UTF-16LE output. -- Smaller: ordered post-sentinel chunk handoff (frame-decoder desync race), port-fallback - breadcrumb now reaches host logs via the home handshake, bad home reply fails the - connect (was: silently 'running' without installs), missing-bundle warn-once, distro - map keys case-normalized, `disposeAll` wired to app `will-quit`, single-spawn Codex - trust catch-up via a one-shot 60s reinstall timer. - -Accepted gaps (reviewed, deliberately not addressed here): old version-namespaced -install dirs accrete across upgrades (~200KB each); an outdated running daemon -/p-translates the guest endpoint path until it restarts (hook scripts fall back to env -coords, which same-port binding keeps correct); default-distro resolution caches the -first non-empty answer for the app run. - -`wslDistroCache` no longer caches a transient empty list for the app run — an empty -result is re-probed after a short window, so a distro registered mid-session is picked -up. `isWslAvailable` likewise re-probes instead of latching a failure. - -## 2026-07-09 round-4 external adversarial review - -A second adversarial sweep (five independent lenses: guest relay + fs bridge, host -lifecycle state machine, app integration + renderer gate, design-vs-alternatives, and a -platform fact-check of every WSL claim). Design verdict: the guest-resident relay over -host-owned stdio is the right architecture — the zero-per-client-change chokepoint is -what the curl.exe alternative cannot match, and the lifecycle weight is inherent to any -guest-resident helper. Confirmed findings, all fixed on this branch: - -- **`dropState` identity race (major)**: the recovery timer re-checked state identity - only BEFORE the async `wsl --list --running` probe; an ensure() landing during the - probe could get its fresh state deleted by key — orphaning a live relay child outside - the map (unkillable by `disposeAll`, duplicate relay on next ensure). Fixed: identity - re-check after the probe await + identity-guarded delete in the manager. -- **Distro-running probe failed OPEN**: any probe error (including its 10s timeout) - reported "running", so recovery could `wsl -d` — and thereby BOOT — a distro the user - shut down, in exactly the wedged-wsl.exe failure mode where the probe errors. Now - fails closed: drop the state; the next WSL PTY spawn re-ensures. -- **Spawn form hardened to `--exec`**: `wsl.exe -- ` routes through the distro's - default login shell (Microsoft docs: only `--exec` runs "without using the default - Linux shell"), so a fish/nushell chsh could mangle the launch; `--exec sh -c`/-`s` - bypasses it and passes argv verbatim (no `$`-preprocessing, escaping shim dropped) — - same form as the Codex WSL login spawn. -- **Post-sentinel handoff microtask**: pending chunks flushed synchronously inside the - mux constructor, before the manager could register notification handlers — an - envelope arriving in the trailing bytes dispatched to zero handlers (recovered only - by the later replay request). Flush now rides a microtask: after the caller's - synchronous wiring, still ahead of any subsequent stdout IO event. -- **Relay process posture**: the guest relay now mirrors the SSH relay's - `uncaughtException` (log + exit → manager respawns) / `unhandledRejection` (log + - survive) handlers. -- **Replay cache recency cap**: the WSL relay has no per-pane teardown signal, so the - per-pane replay cache grew for the relay's lifetime; now capped at 256 panes, - evicting longest-idle first (meta map kept in lockstep). Backstop for SSH too. -- Smaller: guest launch script derives the stale-exit code from the shared contract - constant (was a hardcoded 42 twin); one-shot reinstall timer refuses to arm after - dispose; fs-bridge scope comment states the lexical (symlink-following) bound - honestly. New oracles: sentinel unit suite (chunk splits, overflow kill, timeout, - microtask handoff), fs-bridge scoping suite, 403 + fallback endpoint-file rewrite, - cache-cap eviction, and the recovery/manager race regressions. - -**Revised stance on the OMP curl.exe bridge — keep it, do not retire.** The relay -requires node ≥ 18 in the distro; a fresh WSL Ubuntu ships none, Codex CLI is a native -binary that brings none, and Claude Code's native installer no longer implies a system -node. A distro running only Codex would hit the no-node cooldown and stay dark — the -exact GH `6907` shape. The interop bridge is the one delivery path with no guest -runtime requirement, so it stays as the documented no-node fallback (currently wired -for OMP; extending it to the shared shell-script builders is the tracked follow-up if -no-node distros show up in telemetry). The relay remains the primary path: resident -(no per-event spawn cost) and interop-independent. - -## Implementation map - -- Guest: `src/relay/wsl-agent-hook-relay.ts` (entry; exits on stdin close), - `src/relay/wsl-hook-fs-bridge.ts` (home-scoped fs RPCs for installs), - `src/relay/agent-hook-server.ts` (`token`/`preferredPort` options + `EADDRINUSE` - fallback). Bundled by `config/scripts/build-relay.mjs` → `out/relay/wsl/`. -- Host: `src/main/agent-hooks/wsl-hook-relay-manager.ts` (per-distro state machine), - `wsl-hook-relay-launch.ts` (bundle resolve, guest launch/install scripts, spawn env, - sentinel wait), `wsl-hook-relay-link.ts` (envelope forward + exactly-once link-death - handling), `wsl-hook-relay-deps.ts` (DI seam), `wsl-hook-fs-adapter.ts` (SFTP-shaped - adapter + `installWslGuestHooks`, which targets Codex's managed runtime home). -- Wiring: `buildPtyHostEnv` (`src/main/ipc/pty.ts`) ensures the relay on every WSL spawn - and repoints `ORCA_AGENT_HOOK_ENDPOINT` at the guest endpoint file once known; - `src/main/pty/wsl-orca-env.ts` picks `/u` vs `/p` by value shape. -- Contract shared by both sides: `src/shared/wsl-hook-relay-contract.ts`. -- Oracles: `src/relay/wsl-agent-hook-relay.test.ts`, - `src/main/agent-hooks/wsl-hook-relay-manager.test.ts` (fault injection: stale-42 - reinstall, no-node-43 cooldown, bounded E_UNEXPECTED retry, exit re-ensure gating, and a - full installer run against an in-memory guest). - -## References - -- GitHub: issues `6907` (Codex/WSL), `7091` + `7565` (OMP, fixed), `7563` (WSL CLI - detection, adjacent); PRs `7642` + `7641` (OMP fixes), `7744` (SSH hook installers - precedent), `7447` (title-collapse regression). -- Linear: STA-1515 (this work; ticket comments carry the same context). -- Key files: `src/main/agent-hooks/server.ts`, `src/shared/agent-hook-listener.ts`, - `src/shared/agent-hook-relay.ts`, `src/relay/agent-hook-server.ts`, `src/relay/relay.ts`, - `src/main/pty/wsl-orca-env.ts`, `src/main/agent-hooks/installer-utils.ts`, - `src/main/pi/agent-status-extension-source.ts`, `src/main/ssh/ssh-relay-session.ts`, - `src/main/providers/windows-shell-args.ts`, `src/shared/wsl-login-shell-command.ts`. diff --git a/docs/android-emulation-streaming.md b/docs/android-emulation-streaming.md deleted file mode 100644 index 0b96ad84cc9..00000000000 --- a/docs/android-emulation-streaming.md +++ /dev/null @@ -1,96 +0,0 @@ -# Android Emulation — Live Pane Streaming - -The Android **control** path (device list, boot, tap/type/buttons/rotate/exec, -install/launch/permissions/ax/logcat) is complete and unit-tested. This document -covers the **live H.264 video pane** (scrcpy + WebCodecs), which requires the -Android SDK, a running AVD, the bundled `scrcpy-server.jar`, and Electron's -WebCodecs runtime for end-to-end validation. - -## What is built (committed) - -| Module | Tested | Notes | -|---|---|---| -| `android/scrcpy-control-protocol.ts` | ✅ unit | Byte-exact control encoders (touch/key/text/back). | -| `android/scrcpy-video-frame-parser.ts` | ✅ unit | scrcpy v2.4 codec-meta + frame-header parsing. | -| `android/scrcpy-server-deploy.ts` | ✅ unit | push / forward / server-start arg builders. | -| `android/scrcpy-stream-session.ts` | live-validated | Owns the server process + video/control sockets. | -| `emulator/scrcpy-video-registry.ts` | ✅ unit | Pub/sub bridging a session to renderer subscribers. | -| `ipc/emulator-video-stream.ts` | registration tested | `emulator:videoStream*` IPC; registered in `register-core-handlers`. | -| `emulator-pane/use-emulator-video-stream.ts` | live-validated | WebCodecs `VideoDecoder` → ``. | - -## Current wiring - -### 1. `scrcpy-server.jar` - -The version-pinned server jar must match `SCRCPY_SERVER_VERSION` in -`scrcpy-server-deploy.ts`. Runtime resolution checks the packaged resource and a -development fallback. - -### 2. `AndroidEmulatorBackend.startSession` - -`startSession` ensures the target is booted, resolves the bundled server jar, -starts `ScrcpyStreamSession`, and feeds `scrcpyVideoRegistry`. A -`createStreamSession` option lets unit tests inject a fake because the real -session does socket I/O. - -```ts -async startSession(deviceId: string): Promise { - const serial = await this.ensureBooted(deviceId) - const jar = resolveScrcpyServerJar() - if (!jar) { - throw new EmulatorError('emulator_helper_failed', 'scrcpy-server.jar not bundled (see docs/android-emulation-streaming.md).') - } - const session = await (this.createStreamSession ?? ScrcpyStreamSession.start)( - { runner: this.runner, sdk: this.requireSdk(), serial, localJarPath: jar, maxSize: 1024 }, - { - onMeta: (meta) => scrcpyVideoRegistry.pushMeta(serial, meta), - onFrame: (f) => scrcpyVideoRegistry.pushFrame(serial, { config: f.config, keyFrame: f.keyFrame, pts: String(f.pts), bytes: toArrayBuffer(f.data) }), - onError: () => scrcpyVideoRegistry.stop(serial), - onClose: () => scrcpyVideoRegistry.stop(serial) - } - ) - scrcpyVideoRegistry.register(serial, () => session.close()) - this.streamSessions.set(serial, session) - return { deviceUdid: serial, streamUrl: `scrcpy://${serial}`, wsUrl: '', streamCodec: 'h264' } -} -``` - -`stopHelperForDevice(serial)` stops the registry and closes the stored session. - -### 3. Low-latency input via the scrcpy control socket (optional refinement) - -Input already works via `adb shell input`. For smooth multi-touch, when a live -session exists, route `tap`/`gesture` through `session.sendControl(...)` using the -encoders in `scrcpy-control-protocol.ts` (convert normalized coords → -device pixels with `android-input-mapping`, then `encodeInjectTouchEvent`). - -### 4. Preload + pane - -- **Preload** (`src/preload/...` emulator API): exposes `startVideoStream`, - `stopVideoStream`, `onVideoStreamMeta`, `onVideoStreamFrame` wrapping the - `emulator:videoStream*` channels (mirror the existing `startFrameStream` etc.). -- **Pane** (`emulator-screen-stream-content.tsx`): when - `session.streamCodec === 'h264'`, renders the `` from - `useEmulatorVideoStream(deviceId, enabled)` instead of the MJPEG ``. -- **Hardware buttons** (`emulator-phone-hardware-buttons.tsx`): includes Android - variants (Back, Home, Recents, Power, Volume) selected by backend kind, per - `docs/STYLEGUIDE.md`. - -### 5. Validate on hardware - -```sh -pnpm build:cli -# boot an AVD (Android Studio or `emulator @`), then: -orca-dev emulator devices --json # see the device -orca-dev emulator tap 0.5 0.8 --device # control works today -# after wiring §2/§4: open the emulator pane and confirm the live frame + taps. -``` - -## Risks to validate first - -- **WebCodecs H.264 in Electron**: confirm `VideoDecoder.isConfigSupported({ codec: 'avc1.640028' })`. If unsupported, fall back to a wasm decoder (Broadway/tinyh264) or a main-process H.264→JPEG transcode into the existing MJPEG channel — neither changes the backend interface (it advertises the codec). -- **scrcpy server protocol/version**: the option set + handshake in - `scrcpy-server-deploy.ts` / `scrcpy-stream-session.ts` are pinned to v2.4 and - must match the bundled jar. -- **Annex-B vs avcC**: the renderer configures the decoder without a description - (Annex-B). If frames don't decode, extract the avcC from the config packet. diff --git a/docs/android-emulation.md b/docs/android-emulation.md deleted file mode 100644 index a850ec508f5..00000000000 --- a/docs/android-emulation.md +++ /dev/null @@ -1,376 +0,0 @@ -# Android Emulation - -## Problem - -Orca ships a built-in mobile emulator surface (live pane + `orca emulator` CLI + -agent skill), but it is **iOS Simulator only and macOS only**: - -- `src/main/emulator/emulator-availability.ts:32` hard-returns "unavailable" for - any `platform() !== 'darwin'`, so Windows and Linux users get nothing. -- The backend (`src/main/emulator/emulator-bridge.ts`) is wired directly to - `serve-sim` (`serve-sim-*.ts`) and `xcrun simctl` - (`simctl-simulator-devices.ts`), both Apple-only tooling. - -Android emulators run on Windows, Linux, and macOS via the Android SDK that -Android Studio installs. We want Android emulation as a first-class peer of the -iOS feature: full AVD lifecycle management, a live ~60fps pane, the full -tap/gesture/type/button/rotate control surface, accessibility tree, app -install/launch, runtime permissions, logcat, plus a dedicated -`orca-emulator-android` agent skill. - -## Current architecture (what we reuse vs. replace) - -The existing stack already separates a backend from everything above it. The -renderer pane, session registry, RPC/CLI shape, and tab system are effectively -**backend-agnostic** and are reused unchanged: - -- **Frame transport is main-owned.** `src/main/ipc/emulator-frame-stream.ts:40` - runs the MJPEG socket in the main process and forwards raw JPEG bytes to the - renderer over `emulator:frameStreamFrame`. The renderer - (`src/renderer/src/components/emulator-pane/use-emulator-frame-stream.ts:74`) - just wraps each frame in a `Blob`/``. The renderer is a **frame - consumer**, decoupled from the source. -- **Per-worktree "active emulator"** lives in - `src/main/emulator/emulator-session-registry.ts` (like the active browser - tab). Backend-agnostic. -- **RPC** is declared in `src/main/runtime/rpc/methods/emulator.ts` and - implemented in `src/main/runtime/orca-runtime-emulator.ts`. -- **CLI** is `src/cli/specs/emulator.ts` + `src/cli/handlers/emulator.ts`. -- **Pane** is `src/renderer/src/components/emulator-pane/**` (~50 files). - -What is iOS-bound and needs an Android sibling: - -- Device management: `xcrun simctl` → `adb` / `emulator` / `avdmanager`. -- Streaming helper: `serve-sim` (MJPEG/H.264 over HTTP+WS) → `scrcpy-server.jar` - (H.264 + control over adb-forwarded sockets). -- Input: serve-sim normalized-coord WS → adb-backed Android input commands. -- Availability gate: darwin-only → SDK-present on any OS. - -## Goals - -- Android emulation on **Windows, Linux, and macOS** (macOS users choose iOS or - Android in the same pane). -- **Full AVD lifecycle**: discover installed AVDs via the SDK, boot/shutdown - them from Orca, and attach to already-running emulators + physical `adb` - devices. -- **Live ~60fps pane** via scrcpy H.264 decoded in the renderer with WebCodecs. -- Control parity: tap, swipe/gesture, type, hardware buttons (Back, Home, - Recents, Power, Volume), rotate. -- Extra capabilities: accessibility tree (`uiautomator dump`), app - install/launch (`adb install` / `am start`), runtime permissions - (`pm grant/revoke/reset`), logcat capture. -- A dedicated `skills/orca-emulator-android/SKILL.md`. - -## Non-goals (v1) - -- Camera/sensor injection (the Android emulator's virtual-scene path is a much - larger problem than serve-sim's iOS camera injection; defer). -- Remote/SSH device control (matches the current iOS limitation; emulator - hardware is local). -- Wear OS / Android TV / Automotive form factors. -- Migrating the iOS backend to H.264 (the interface allows it later; not done - now). - -## Design - -### Extract an `EmulatorBackend` interface; make the bridge a router - -Today `EmulatorBridge` *is* the iOS implementation. Refactor it into a thin -router over a backend interface so iOS and Android share the session registry, -RPC/CLI shape, frame IPC, and tab system. This is the only change to existing -iOS behavior, and it is a pure extraction (no semantic change). - -New module `src/main/emulator/backends/emulator-backend.ts`: - -```ts -export type EmulatorBackendKind = 'ios' | 'android' -export type EmulatorStreamCodec = 'mjpeg' | 'h264' - -export type EmulatorDevice = { - backend: EmulatorBackendKind - id: string // opaque: simulator UDID, adb serial, or AVD name - name: string - state: 'shutdown' | 'booting' | 'booted' - kind?: string // form factor / api level, display only - isAvailable: boolean -} - -export type EmulatorBackendCapabilities = { - install: boolean - launch: boolean - permissions: boolean - accessibilityTree: boolean - logcat: boolean -} - -export interface EmulatorBackend { - readonly kind: EmulatorBackendKind - readonly capabilities: EmulatorBackendCapabilities - isSupportedOnHost(): boolean - checkAvailability(): Promise - listDevices(): Promise - bootDevice(id: string): Promise - startSession(id: string): Promise // includes streamCodec - tap(id, x, y): Promise - gesture(id, points): Promise - type(id, text): Promise - button(id, name): Promise - rotate(id, orientation): Promise - exec(id, command): Promise - // capability-gated: - installApp?(id, path): Promise - launchApp?(id, pkg, activity?): Promise - setPermission?(id, op): Promise - accessibilityTree?(id): Promise - logcat?(id, opts): Promise<...> - stopSession(id): Promise - kill(id): Promise - shutdown(id): Promise -} -``` - -- `src/main/emulator/backends/ios-emulator-backend.ts` — the existing serve-sim - + simctl logic extracted from `EmulatorBridge`, implementing the interface - with `kind: 'ios'`, `streamCodec: 'mjpeg'`, and capabilities mapped to what - serve-sim already supports. -- `src/main/emulator/backends/android-emulator-backend.ts` — new, orchestrates - the Android modules below with `kind: 'android'`, `streamCodec: 'h264'`. -- `EmulatorBridge` keeps its public method names (so RPC/runtime callers don't - churn) but becomes a router: it holds the available backends, resolves which - backend owns a given device/session (via the session registry's recorded - `backend` tag, or by probing `listDevices()` for an unknown id), and - delegates. The session registry record gains a `backend: EmulatorBackendKind` - field; `EmulatorSessionInfo` gains `streamCodec`. The existing `deviceUdid` - field is retained as the opaque `id` to preserve wire-compat across the - renderer and CLI. - -### New Android modules — `src/main/emulator/android/` - -Each module is small and single-purpose with a co-located `.test.ts`, matching -the existing `serve-sim-*` / `simctl-*` granularity (no file approaches the -`max-lines` limit): - -- `android-sdk-discovery.ts` — resolve the SDK root and the `adb` / `emulator` / - `avdmanager` binaries from `ANDROID_HOME`, then `ANDROID_SDK_ROOT`, then per-OS - defaults: `%LOCALAPPDATA%\Android\Sdk` (Windows), `~/Library/Android/sdk` - (macOS), `~/Android/Sdk` (Linux). All paths via `path.join`. -- `adb-devices.ts` — `adb devices -l`, resolve serial, `wait-for-device` + - `getprop sys.boot_completed` poll, and `wm size` for the device resolution. -- `avd-manager.ts` — `emulator -list-avds`, boot an AVD via a detached - `emulator @` spawn, shut down via `adb -s emu kill`. -- `scrcpy-server-deploy.ts` — push the version-pinned `scrcpy-server.jar`, start - it via `app_process`, and set up the `adb forward` tunnel(s). -- `scrcpy-stream-session.ts` — owns the server process, adb tunnel, and video - socket lifecycle. -- `scrcpy-video-frame-parser.ts` — read the video socket, parse scrcpy frame headers - (PTS + length), and emit H.264 access units plus the codec config (SPS/PPS). -- `scrcpy-control-protocol.ts` — encode scrcpy control messages (touch - down/move/up with pointer id + pressure, inject keycode, inject UTF-8 text, - scroll, set screen power, rotate, clipboard) for a future low-latency input path. -- `android-input-mapping.ts` — convert normalized 0–1 ↔ device pixels using the - live frame size; map button names → Android keycodes (BACK=4, HOME=3, - APP_SWITCH=187, POWER=26, VOLUME_UP=24, VOLUME_DOWN=25). -- `android-input-commands.ts` — current adb-backed tap/type/button/rotate/gesture - command construction. -- `uiautomator-tree.ts` — `adb shell uiautomator dump` → parsed XML tree. -- `android-app-control.ts` — `adb install `, `am start` package/activity. -- `android-permissions.ts` — `pm grant` / `revoke` / `reset`. -- `android-logcat.ts` — tail/filter `adb logcat` with bounded buffering. -- `android-availability.ts` — SDK present? AVDs + connected devices list, with - clear, surfaced messages (mirroring the iOS availability message style). - -### Streaming & control data flow - -**Control currently uses `adb shell input` commands.** Coordinates stay -normalized 0–1 at every public boundary (CLI, RPC, renderer); the Android backend -maps them to device pixels before issuing adb-backed tap/gesture/button commands. -The scrcpy control protocol encoders are present for a future low-latency input -path, but video streaming does not require that path. - -**Video path (H.264 → renderer WebCodecs):** - -1. `android-emulator-backend.startSession()` deploys + starts scrcpy-server, - opens the video + control sockets, and returns `EmulatorSessionInfo` with - `streamCodec: 'h264'`. -2. `scrcpy-video-stream.ts` reads access units and the SPS/PPS config and pushes - them over a **new IPC channel** `emulator:videoStream{Start,Config,Frame,Stop}` - (a sibling of the existing `emulator:frameStream*`), keyed by stream id. -3. New renderer hook - `src/renderer/src/components/emulator-pane/use-emulator-video-stream.ts` - feeds the access units to a WebCodecs `VideoDecoder`, drawing decoded - `VideoFrame`s to a ``. -4. `src/renderer/src/components/emulator-pane/emulator-screen-stream-content.tsx` - branches on `session.streamCodec`: `mjpeg` keeps today's `` path - untouched; `h264` uses the canvas path. No iOS behavior changes. - -## Coordinate & input mapping - -- Public API (CLI/RPC/pane gestures) stays normalized 0–1, top-left origin, as - the iOS path already mandates. -- `android-input-mapping.ts` multiplies by the current device display size before - adb input commands are built. Rotation changes the effective frame size; the - mapper reads the current size each gesture rather than caching. -- Hardware buttons: adb keyevents inject keycodes. Android adds - **Back** and **Recents** (no iOS equivalent); the button name → keycode map - and the renderer's hardware-button row gain Android variants. - -## RPC + CLI surface - -Extend `src/main/runtime/rpc/methods/emulator.ts`, -`src/main/runtime/orca-runtime-emulator.ts`, `src/cli/specs/emulator.ts`, and -`src/cli/handlers/emulator.ts`: - -- `orca emulator list` gains a **platform column** and shows iOS + Android - devices/AVDs together; device selection resolves the backend automatically - (by recorded session tag, else by which backend's `listDevices()` owns the id). -- Existing verbs (`attach`, `tap`, `gesture`, `type`, `button`, `rotate`, - `exec`, `kill`, `shutdown`) route unchanged to the resolved backend. -- New capability-gated verbs: `install`, `launch`, `permissions`, `ax`, - `logcat`. On a backend lacking the capability they fail with a clear - `emulator_unsupported` error rather than silently no-op. -- Existing `--worktree` / `--device` targeting is unchanged. - -## Renderer pane - -- `src/renderer/src/components/emulator-pane/emulator-phone-hardware-buttons.tsx` - → Android variant (Back, Home, Recents, Power, Volume) selected by backend - kind. -- Android device bezel/frame + Android entries (and a "boot AVD" affordance) in - the attach/list UI. -- `MobileEmulatorAgentSetupGuide*` → Android prerequisites step (install Android - Studio / SDK, set `ANDROID_HOME`). -- Codec-aware stream content (the canvas path above). -- All UI follows `docs/STYLEGUIDE.md`: existing tokens from - `src/renderer/src/assets/main.css` and shadcn primitives in - `src/renderer/src/components/ui/`; no new color/size/shadow values. -- Shortcut labels and any new accelerators use the platform checks required by - `AGENTS.md` (`CmdOrCtrl`, `⌘`/`Ctrl+`). - -## Packaging & dependencies - -- Bundle the single, version-pinned `scrcpy-server.jar` (~80 KB) as an app - resource; wire it into `config/electron-builder.config.cjs` and - `config/packaged-runtime-node-modules.cjs`. Pin the scrcpy version — the - server protocol is coupled to the jar. -- Do **not** bundle `adb` / `emulator` / `avdmanager` (large; Android Studio - installs them). Discover them at runtime and surface a clear setup message if - the SDK is absent. -- No new runtime npm dependency is required for decode (WebCodecs is built into - Electron's Chromium). The wasm-decoder fallback (see Risks) would add a dep - only if the WebCodecs spike fails. - -## Skill - -New `skills/orca-emulator-android/SKILL.md`, mirroring -`skills/orca-emulator/SKILL.md`: - -- Prerequisites: Android Studio / SDK installed, `ANDROID_HOME` (or - `ANDROID_SDK_ROOT`) set, at least one AVD or a connected device. -- The `orca emulator ...` command table (shared CLI; Android examples). -- Gotchas: Orca handles pixel ↔ normalized conversion (agents always pass 0–1); - adb device/serial targeting; no camera injection in v1; scrcpy version - coupling. -- Cross-reference from the iOS skill's "When NOT to use" (which already - anticipates an Android backend under the same namespace). -- Register it the same way `orca-emulator` is registered. - -## Availability & platform gating - -`src/main/emulator/emulator-availability.ts` becomes an aggregator that asks -each backend `isSupportedOnHost()` + `checkAvailability()`: - -- iOS backend: supported only on `darwin` (unchanged behavior/messages). -- Android backend: supported on any OS where the SDK is discoverable. -- The combined result drives the pane's availability UI; Windows/Linux report an - available mobile backend for the first time. - -## Edge cases - -- adb device in `offline` / `unauthorized` state → clear surfaced error, not a - hang. -- AVD boot timeout (cold boot can take minutes) → bounded wait with a - cancel/error path; pane shows "booting". -- SDK present but no AVDs and no devices → availability message points to "create - an AVD in Android Studio". -- Device rotates while a gesture is mid-flight → mapper re-reads frame size per - event; no cached dimensions. -- Multiple Android devices in one worktree → same "one active per worktree" - model as iOS; explicit `--device ` for the rest. -- WebCodecs decoder error / key-frame loss → request a new keyframe from scrcpy - and surface a transient "reconnecting" state (parity with the MJPEG reconnect - in `mjpeg-frame-stream.ts`). -- Windows path handling for the SDK and the pushed jar uses `path.join` only; - never assume `/` or `\`. -- App quit / pane close cleans up scrcpy-server, the adb tunnel, and (for managed - AVDs) the emulator, mirroring `EmulatorBridge.onAppQuit()` / - `destroyAllSessions()`. - -## Test plan - -Unit tests (co-located, node Vitest, matching the module's existing test -density): - -- `android-sdk-discovery` — env precedence + per-OS default paths (mock env/fs; - assert Windows/macOS/Linux branches). -- `adb-devices` — parse `adb devices -l` (booted, offline, unauthorized, - physical), boot-complete polling. -- `avd-manager` — parse `emulator -list-avds`, boot command construction. -- `scrcpy-control-channel` — exact byte encoding of touch/key/text/scroll - messages. -- `android-input-mapping` — normalized↔pixel round-trips, rotation, keycode map. -- `uiautomator-tree` — XML → tree parsing, including malformed input. -- `android-availability` + `emulator-availability` aggregation — iOS-only, - Android-only, both, neither. -- backend router resolution in `emulator-bridge` — id → backend, unknown id, - cross-backend isolation. - -Integration tests mock `adb` / `emulator` and the scrcpy sockets the same way -the iOS tests mock serve-sim (`serve-sim-*.test.ts`, `emulator-bridge.test.ts`). - -Electron validation (manual, on a machine with the Android SDK): - -- Boot an AVD from Orca; confirm the live pane streams and is responsive. -- tap / swipe / type / Back / Home / Recents / rotate. -- `ax`, `install` + `launch`, `permissions grant`, `logcat`. -- Cross-platform smoke on Windows (primary driver) and macOS (iOS + Android - coexistence). - -## Risks / verify-first - -- **Electron H.264 WebCodecs decode** — verify in a step-0 spike that an Electron - renderer `VideoDecoder` decodes scrcpy's H.264. Electron ships proprietary - codec decode, so this is expected to pass. Fallback if not: a wasm H.264 - decoder (Broadway / tinyh264) or dropping to a main-process H.264→JPEG - transcode — **neither changes the backend interface**, since the session - advertises its codec. -- **scrcpy-server protocol is version-coupled** to the bundled jar (same class of - risk as serve-sim's private SimulatorKit APIs). Pin the version; record it next - to the bundled jar. -- **adb/emulator environment variance** — offline/unauthorized devices, cold-boot - timeouts, missing SDK. All handled via explicit, surfaced errors. - -## Rollout - -1. Step-0 spike: confirm WebCodecs H.264 decode in the Electron renderer. -2. Extract the `EmulatorBackend` interface + `IosEmulatorBackend` (pure - refactor); keep all iOS tests green. -3. Android device management (`android-sdk-discovery`, `adb-devices`, - `avd-manager`, `android-availability`) + availability aggregation; surface - Android devices in `orca emulator list`. -4. scrcpy streaming (`scrcpy-server-deploy`, `scrcpy-video-stream`) + the video - IPC channel + the renderer WebCodecs canvas path; live pane renders. -5. scrcpy control (`scrcpy-control-channel`, `android-input-mapping`) + - tap/gesture/type/button/rotate end-to-end. -6. Extra capabilities: `ax`, `install`/`launch`, `permissions`, `logcat`. -7. Renderer polish: Android hardware buttons, bezel, setup guide. -8. Packaging (`scrcpy-server.jar` resource) + the `orca-emulator-android` skill. -9. Tests at each step; typecheck + lint; Electron validation on Windows + macOS. - -## Open decisions - -- Whether `orca emulator install`/`launch`/`logcat` should also be exposed for - iOS later (iOS install is `xcrun simctl install`); v1 leaves them - Android-only via capability flags. -- Whether to expose an explicit `orca emulator boot ` verb vs. folding boot - into `attach`; initial version folds boot into `attach` (parity with iOS, - which boots on attach) and adds a `--no-boot` opt-out. diff --git a/docs/automations-navigation-stack.md b/docs/automations-navigation-stack.md deleted file mode 100644 index 89d6aee2c5b..00000000000 --- a/docs/automations-navigation-stack.md +++ /dev/null @@ -1,75 +0,0 @@ -# Automations Navigation Stack - -## Problem - -- `worktree-nav-history.ts` models view entries as `'tasks'` only; `'automations'` cannot be recorded or replayed. -- `openTaskPage` records a view visit before switching view; `openAutomationsPage` does not. -- `closeTaskPage` rewinds history index when closing from a `'tasks'` history node; `closeAutomationsPage` does not. -- Keyboard history navigation already works on Automations (`Cmd/Ctrl+Alt+Arrow`), but titlebar Back/Forward is hidden there. -- `setWorktreeNavViewActivator` is currently Tasks-sentinel oriented in types/comments and must be widened for Automations. - -## Goal - -Make Automations a first-class entry in the existing mixed worktree/page navigation stack, matching Tasks behavior for open, back/forward traversal, close-page rewind, and titlebar controls. - -## Non-goals - -- Do not add persistence for navigation history; the existing stack is session-only and renderer-local. -- Do not preserve per-automation detail selection through Back/Forward beyond existing `selectedAutomationId` state. -- Do not change Activity, Settings, Space, Skills, or terminal navigation behavior. -- Do not add new shortcuts; reuse existing cross-platform `Cmd/Ctrl+Alt+Arrow` handling. - -## Design - -1. Add explicit view-entry type. - - `type WorktreeNavHistoryViewEntry = 'tasks' | 'automations'`. - - `type WorktreeNavHistoryEntry = string | WorktreeNavHistoryViewEntry`. - - Update `recordViewVisit`, `ViewActivateFn`, and `setWorktreeNavViewActivator` signatures accordingly. - - Update `isLiveEntry` to treat both page sentinels as live. - -2. Generalize history replay branch. - - In `navigateToIndex`, dispatch page sentinels through `viewActivator(entry)` and worktree ids through `activator(id)`. - - Keep page replay on `setActiveView(entry)` (not `openTaskPage`/`openAutomationsPage`) to avoid mutating `previousViewBefore*` and avoid appending history during replay. - - Keep existing index semantics: update index only after successful activation path. - -3. Record and close Automations like Tasks. - - `openAutomationsPage`: call `recordViewVisit('automations')` before switching `activeView`. - - `closeAutomationsPage`: if current history node is `'automations'`, rewind to `findPrevLiveWorktreeHistoryIndex(state)` when available; otherwise keep index unchanged. - - This rewind must apply regardless of close trigger (Esc / header X / any direct `closeAutomationsPage` call site). - -4. Align titlebar controls with shortcut scope. - - Show titlebar Back/Forward when `activeView` is `terminal`, `tasks`, or `automations`. - - Keep shortcut logic unchanged; it already includes Automations. - -5. Tests. - - `worktree-nav-history.test.ts`: add Automations sentinel coverage for replay path, adjacent dedupe, dead-worktree skip, and rewind/forward behavior. - - `ui.test.ts`: add Automations open/close history-index parity tests with Tasks, including “only automations in history” no-op rewind. - - `App.tsx`: assert Back/Forward controls render on Automations (not optional; this is where current behavior regressed from shortcut scope). - - `worktree-activation` wiring test coverage (or equivalent integration assertion) should verify `setWorktreeNavViewActivator` accepts/replays both sentinels. - -## Known residual quirks - -- Replay uses `setActiveView(...)`, so `previousViewBeforeTasks/Automations` is not recomputed on back/forward landing. Close from a replayed page can return to stale `previousViewBefore*`; this is existing Tasks behavior. -- History is capped at 50 entries. Long sessions may evict older entries, including page sentinels; this is existing behavior. -- History is renderer-local and session-local (no persistence, no cross-window reconciliation). -- Liveness is evaluated against current store state at navigation time. If a target worktree becomes invalid between target selection and activation, `activateAndRevealWorktree` may fail and index stays put. - -## Edge cases - -- `A -> Automations -> B`, Back lands on Automations, Back again lands on A. -- `A -> Automations -> Automations` records only one Automations entry. -- `A -> Automations`, close rewinds index to A; Forward reopens Automations. -- `Automations` as the only history entry: close leaves index at `0` (do not force `-1`, or Forward target is lost). -- If the prior worktree was deleted while Automations is open, Back/Close rewind skips it and lands on the next live prior entry. -- Back-to-Automations must not call `openAutomationsPage`, or it would overwrite `previousViewBeforeAutomations` and append duplicate history. -- Shortcut labels and handling remain cross-platform (`⌘⌥` on Mac, `Ctrl+Alt` elsewhere). -- Multi-window: each renderer has an independent history stack; no cross-window reconciliation is attempted. - -## Rollout - -1. Update `worktree-nav-history.ts` types, live-entry predicate, and replay branch. -2. Update `ui.ts` to record and rewind Automations visits. -3. Update `worktree-activation.ts` comments/types for generalized view activator. -4. Update `App.tsx` titlebar visibility and comments. -5. Add/adjust unit tests for history slice, UI slice, and titlebar visibility. -6. Run `worktree-nav-history.test.ts` and `ui.test.ts`, then `pnpm typecheck` and `pnpm lint`. diff --git a/docs/browser-normal-download-behavior.md b/docs/browser-normal-download-behavior.md deleted file mode 100644 index c1195625406..00000000000 --- a/docs/browser-normal-download-behavior.md +++ /dev/null @@ -1,183 +0,0 @@ -# Browser Normal Download Behavior - -## Problem - -The built-in browser does not behave like a normal desktop browser when a page downloads a file. - -- `src/main/browser/browser-session-registry.ts:553` installs a `will-download` handler for browser sessions. -- `src/main/browser/browser-manager.ts:952` pauses every download before Orca has a save path. -- `src/renderer/src/components/browser-pane/BrowserPane.tsx:4915` shows a `Save` / `Cancel` prompt instead of starting the download. -- `src/main/ipc/browser.ts:342` opens a native save dialog after the renderer clicks `Save`. -- `src/main/browser/browser-manager.ts:1026` calls `DownloadItem.setSavePath()` only after that renderer + dialog round trip. -- Electron's installed type contract says `setSavePath()` is only available during the session `will-download` callback (`node_modules/electron/electron.d.ts:8271`). - -That late path assignment explains the "clicked Save, still did not save" failure mode and makes the flow higher-friction than Chrome/Safari/Edge defaults. - -## Root Cause - -Orca treats downloads as renderer-approved actions. Electron treats the destination as a main-process `will-download` decision. The current flow crosses that boundary too late: it pauses the download, asks the renderer to approve it, asks the OS where to save it, and only then sets the save path. - -## Goal - -Make built-in browser downloads feel like a normal browser: - -- Download starts automatically. -- File saves to the OS Downloads directory by default. -- Name collisions are resolved without overwriting. -- Browser chrome shows progress and completion. -- Completed downloads offer familiar actions such as opening or revealing the file. -- Cancel remains available while downloading. - -## Non-goals - -- A full persistent download manager/history. -- A settings UI for "ask where to save every file." -- Cross-device transfer of downloads from a remote browser host to a local client. -- Changing generic filesystem downloads outside the browser. - -## Design - -1. **Resolve the save path synchronously in main** - - Add a small browser-specific destination module, e.g. `src/main/browser/browser-download-destination.ts`, that: - - - uses `app.getPath('downloads')`; - - normalizes the filename to a safe basename; - - uses `path.join` for cross-platform paths; - - avoids overwrites with browser-style suffixes (`report.csv`, `report (1).csv`); - - checks existing files synchronously because Electron requires `setSavePath()` during `will-download`; - - also checks a main-process reservation set for active browser downloads, so two same-name downloads that start before either file exists still get distinct paths; - - keys reservations by a normalized absolute path, with platform-aware case folding where the target filesystem is conventionally case-insensitive, so `Report.csv` and `report.csv` cannot collide on Windows; - - caps suffix attempts and fails the download with a clear error instead of spinning forever in a crowded Downloads directory. - - Do not create a placeholder file just to reserve the path: Electron may treat an existing target as an overwrite. The in-memory reservation prevents Orca-internal concurrent collisions; an external process can still create the same path after the check, so this remains best-effort at the filesystem boundary. - -2. **Set the destination during `will-download`** - - In `BrowserManager.handleGuestWillDownload`, compute and reserve the path, then call `DownloadItem.setSavePath()` immediately while still in the `will-download` call stack. Do not pause for renderer approval, do not open `dialog.showSaveDialog`, and do not use `setSaveDialogOptions()` for this flow because that still preserves dialog behavior. If destination resolution or `setSavePath()` fails, release the reservation, cancel the item, and send or queue a failed terminal event with a specific, non-secret error. - -3. **Track downloads from start to finish in main** - - Replace the approval-centric state with browser download state: - - - `downloading`: save path is already assigned; item is progressing, waiting on network, or temporarily interrupted. - - `completed` / `failed` / `canceled`: terminal states sent to the renderer. - - Register `updated` and `done` listeners immediately in `will-download`, store latest received-byte count plus any terminal result, and keep the existing guest-to-tab queue so downloads that start before registration still surface when the tab binds. Queue state snapshots, not every progress event: if a download finishes before the tab registers, flush a started row followed by its terminal state so the renderer does not drop an orphan progress/finish event. If an unregistered guest is destroyed or retired before the tab binds, cancel its queued active downloads and release their reservations instead of only dropping the pending queue entry. Send exactly one terminal event per download, detach only this feature's listener references during cleanup, and release the reserved path on every terminal path and on explicit cancel. - -4. **Update IPC shape for browser-like status** - - Keep the existing renderer event channels if that keeps the diff smaller, but change the payload contract from "approval requested" to "download started" semantics. Include `browserPageId` on start/progress/finish when the tab is known, include `savePath` on start and terminal data, and keep progress payloads to `downloadId`, byte counts, and transient state. Remove the renderer `acceptDownload` path for normal browser downloads and delete the save-dialog IPC test; `cancelDownload` remains useful while an item is active. - -5. **Render browser chrome, not an approval prompt** - - In `BrowserPane`, replace the `Save` / `Cancel` prompt with a compact download row/list under the toolbar: - - - active row: filename, origin, progress label, `Cancel`; - - completed row: filename, "Downloaded" status, `Open`, `Show`, dismiss; - - failed/canceled row: filename, concise failure/cancel reason, dismiss. - - Track more than one download per pane so a second download does not hide the first. Cap visible recent completed rows to a small number, such as three, to avoid growing the browser surface. This is a per-pane transient list, not persisted history. - -6. **Use existing shell bridges for actions** - - Use `window.api.shell.openFilePath(savePath)` for `Open` and `window.api.shell.openInFileManager(savePath)` for `Show`. `openPath` is a legacy reveal wrapper whose `void` return type prevents the preload bridge from reporting success or failure back to the renderer. If either action fails or the file has been moved/deleted, show an inline or toast failure that does not claim the file opened. - -## Data Flow - -```text -Browser session will-download - -> BrowserManager computes and reserves ~/Downloads/name.ext - -> DownloadItem.setSavePath(path) during will-download - -> BrowserManager tracks item and sends/queues browser download-started - -> BrowserPane renders progress row - -> DownloadItem updated/done - -> BrowserManager sends progress/finished - -> BrowserPane shows completed row with Open / Show -``` - -## Edge Cases - -- Duplicate filenames must not overwrite existing files. -- Simultaneous same-name downloads in the same Orca process must not choose the same path before either file exists. -- Same-name reservation checks must respect platform path identity, including case-insensitive collisions on Windows. -- External filesystem races between path selection and Chromium's file creation are unavoidable with `setSavePath()`; treat them as residual risk, not a guarantee. -- Path traversal or separator-like filenames must collapse to a safe basename. -- Empty filenames fall back to `download`. -- Unknown total bytes should show received bytes or a generic "Downloading" state without broken math. -- Multiple simultaneous downloads from the same tab must render independently. -- A download that starts, progresses, or finishes before the webview registers must still appear once the tab binds. -- A queued download whose guest is destroyed or retired before registration must be canceled and release its reserved path. -- Since this design intentionally avoids a global persistent download manager, closing the owning browser tab must cancel active downloads rather than leaving hidden file writes with no chrome. -- `setSavePath()` failure should not leave a hidden, still-running download. -- `updated` events with `interrupted` state should keep the row honest without treating them as terminal; `done` is the terminal source of truth. -- Explicit cancel and Electron's eventual `done` callback must not produce duplicate terminal UI events. -- Completed file actions may fail if the file was moved/deleted after download; UI must report that honestly. -- On Windows, Linux, and macOS, all paths are built with Node/Electron path APIs. -- In SSH/remote workflows, the file is saved on the machine running the browser runtime; UI copy must not imply cross-machine transfer. -- Web clients keep their existing "handled by server browser" behavior unless the server-side browser implements equivalent events. - -## Test Plan - -- Unit: destination builder sanitizes names, preserves extensions, picks collision-free paths, respects active path reservations including platform path identity, releases reservations, and stops at a bounded suffix limit. -- Unit: `BrowserManager.handleGuestWillDownload` calls `setSavePath()` during handling, does not pause/resume for approval, registers listeners immediately, sends started/progress/finished events exactly once, and cancels cleanly on destination failure. -- Unit: queued download-started/progress/finished state flushes after guest registration with the latest state. -- Unit: multiple download events for one browser page are tracked independently. -- Unit: guest cleanup cancels queued-but-unbound active downloads and releases reserved paths. -- Unit: tab unregister cancels every active download for that tab and releases reserved paths. -- IPC: remove `browser:acceptDownload` and its native save-dialog test for normal downloads; keep `browser:cancelDownload` authorization coverage. -- Renderer: download list state covers active, completed, failed, canceled, unknown-size, multiple same-tab downloads, bounded recent rows, and missing-file action failures. -- Shell IPC/preload: `Open` uses `openFilePath`; `Show` uses `openInFileManager` and surfaces structured failure. -- Electron validation: from a local test page, click a download link and verify a file appears in the OS Downloads folder without a save dialog. - -## UI Quality Bar - -The changed browser chrome should be quiet and dense, matching the toolbar area around it. Use existing tokens (`background`, `foreground`, `muted-foreground`, `border`, `accent`) and shadcn `Button` variants/sizes. Do not use amber warning color for ordinary downloads. The active state should be readable without looking like an error. Buttons must not wrap, overlap, or resize the toolbar/pane unexpectedly. Copy must describe real state only: "Downloading", "Downloaded", "Canceled", or the specific failure. - -## Review Screenshots - -Stage 5 must capture: - -1. Active download in progress, with no save prompt visible. -2. Completed download row with file actions visible. -3. A repeat download of the same filename showing successful completion without overwrite. -4. Multiple browser downloads visible at once or in the recent-download cap. -5. Adjacent browser toolbar smoke state after the download row is dismissed. - -## Rollout - -1. Add the destination builder and focused tests. -2. Move BrowserManager download handling to immediate save-path assignment and progress tracking. -3. Update browser IPC/preload/API types to remove approval semantics and carry save-path/status data. -4. Refresh BrowserPane state and UI for active/recent downloads. -5. Add renderer notices, tests, and localization catalog entries as required. -6. Run targeted unit tests, typecheck, lint, then Electron validation with screenshots. - -## Lightweight Eng Review - -- Scope: Kept to local built-in browser download behavior. Reduced by excluding a preferences UI, persistent download history, and remote-to-local transfer. The smallest useful normal-browser behavior is automatic save to Downloads plus transient progress/completion chrome. -- Architecture/data flow: Main owns destination choice and file writes because Electron requires `setSavePath()` during `will-download`. Renderer owns only display and user actions (`Cancel`, `Open`, `Show`, dismiss). Existing session policy installation remains the entry point; existing shell bridges handle file actions. Browser web clients remain unchanged. -- Failure modes covered: - - Late `setSavePath()` is removed by assigning the path synchronously in `will-download`. - - Duplicate filenames use existing-file checks plus active path reservations instead of overwriting each other inside Orca. - - Unsafe or empty filenames are normalized before `path.join`. - - Downloads starting or finishing before tab registration are snapshotted and flushed in order. - - Queued downloads are canceled if their guest is destroyed or retired before registration. - - Multiple downloads no longer overwrite a single pane state. - - Tab close/app shutdown cancels active items rather than leaving hidden downloads without chrome. - - File action failures after external deletion are surfaced without overclaiming. -- Test coverage required: - - Unit: `src/main/browser/browser-download-destination.test.ts` for basename/suffix/collision/reservation behavior and bounded failure. - - Unit: `src/main/browser/browser-manager.test.ts` for immediate `setSavePath`, listener registration, queued snapshots, progress/finish, cancellation, path-reservation release, and failure. - - Unit/IPC: `src/main/ipc/browser.test.ts` removes the native save-dialog accept path and keeps cancel authorization. - - Renderer: focused tests around download notice/list state if an existing seam is available; otherwise cover formatting helpers and rely on Electron validation for BrowserPane DOM behavior. - - Electron: local server download verifies file creation in Downloads without save dialog. -- Performance/blast radius: No startup cost. Per-download synchronous filesystem checks are bounded by the collision limit and happen only during `will-download`; this is acceptable for one user action but must not walk unbounded directories. Renderer IPC volume stays proportional to Electron download progress events already emitted. No migration. -- UI quality bar: Validation should judge a neutral browser-toolbar-adjacent download row/list against `docs/STYLEGUIDE.md`: no amber error styling for ordinary progress, compact button sizes, no overlap/wrapping, honest copy, clear active/completed/failed hierarchy, and stable layout while progress changes. -- Required review screenshots: - 1. Active download in progress with no save prompt. - 2. Completed download row with `Open` / `Show` actions. - 3. Repeat same-name download completed with distinct filename. - 4. Multiple downloads visible or capped as designed. - 5. Browser toolbar after dismissing download chrome. -- Residual risks: Headless Electron may not reliably prove absence of a native save dialog; validation should instead verify no Orca `Save` prompt appears and the file lands in Downloads. If the OS Downloads path is redirected or unavailable, behavior depends on Electron's `app.getPath('downloads')` result. diff --git a/docs/bug-reproductions/8979-screenshots/after-fable-visible.png b/docs/bug-reproductions/8979-screenshots/after-fable-visible.png deleted file mode 100644 index 0e8d49a8e34a75591b4a60bb417943393612b4da..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30479 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