Brings the app-use feature (OPENSWARM_APP bridge, AppAgent delegation tool,
BrowserClickPoint, autopilot supervision) onto the linted eric/dev base.
- Resolved 9 conflicts favoring eric's refactored structure; renamed all of
pierre's _-prefixed names to p_/P_, kept eric's prior_messages resume guard.
- Made the 4 test-exercised bridge helpers public (p-private rule).
- Wired AppAgent into browser_delegation_tools + BuiltinTool registry and
threaded selected_app_output_ids -> OPENSWARM_SELECTED_APP_IDS (eric had
forward-ported the prompt but not the tool, so it was unreachable).
- Linter clean (underscore/p-private/ruff/cycles 0); 301 backend tests pass.
Two-loop design for fast-twitch apps (Flappy, Doodle Jump) the agent cannot
react to frame-by-frame over the network: the bridge runs policy() at frame
rate as the reflex (inner loop), while the supervisor steers on a slow cadence
via getState and __autopilot__ knobs (outer loop). Wires this through the
webapp_template bridge, the shell-injected bridge in main.js, and the APP
system prompt. Also deny the 'fullscreen' permission so a preview webview's
HTML5 fullscreen no longer hijacks the whole OpenSwarm window.
An npm install killed mid-warm leaves node_modules/ populated but no .bin/, so _ensure_warm_cache (which only checked the dir exists) trusted the half-tree forever and every app symlinked to it died with `vite: command not found`. Gate the cache on .bin/vite existing, and harden the app's frontend/run.sh skip-install guard the same way.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit 998e8605c6)
Add agentBridge.ts and import it FIRST in index.tsx so the bridge is
installed on the window before any app code runs.
The visitor / door / doorbell analogy:
- The visitor is the agent. It shows up wanting to operate the app:
read the rules, see the controls, take an action, check what happened.
- The door is window.OPENSWARM_APP. A single, always-present surface the
visitor can knock on: describe() to learn the app, getState() to read a
snapshot, invoke(name, args) to act.
- The doorbell is register({ rules, controls, getState, invoke }). The app
presses it on mount to say "I'm home, here's how to talk to me." Until it
does, describe()/getState() answer { __ready: false } so the visitor
knows the app is still booting and waits, instead of concluding nobody
lives here.
Why a hook and not just an .md:
A markdown instruction ("please expose your controls on window.X") is a
request the app can forget, half-implement, or drift from, and nothing
fails when it does. The bridge ships WITH the template and is imported
before app code, so the door exists from first paint whether or not the
author thought about agents. The app's only job is to press the doorbell
once; it never wires the plumbing, so it cannot get the plumbing wrong.
The contract is executable, not aspirational, which is the difference
between every generated app being agent-operable by default and hoping
each one remembered to be.
An npm install killed mid-warm leaves node_modules/ populated but no
.bin/, so _ensure_warm_cache (which only checked the dir exists) trusted
the half-tree forever and every app symlinked to it died with
`vite: command not found`. Gate the cache on .bin/vite existing, and
harden the app's frontend/run.sh skip-install guard the same way.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>