# Local agent interface

The local bridge lets an agent control the currently open Aperio browser session after a person explicitly pairs it. It requires Node.js 18 or newer and binds only to `127.0.0.1`; it does not expose the interface on the network. Run the downloaded `aperio-agent.mjs` with the exact origin of the page open in the browser:

```powershell
node aperio-agent.mjs serve --origin http://localhost:3000
```

The bridge prints a new random token each time it starts. Enter it in the page's agent connection control. Keep the token private: anyone who has it and can reach the local bridge can issue commands. Only one browser session can be paired at a time. Disconnect from the page when finished. If the page closes unexpectedly, the session expires after about a minute of inactivity; restarting the bridge releases it immediately and creates a new token. Browser throttling may also cause a background page to time out: reconnect after returning. Agent-initiated motion stops once the page detects the disconnect. Stop the bridge with Ctrl+C.

In another PowerShell terminal, provide the printed token to the CLI:

```powershell
$env:APERIO_AGENT_TOKEN = '<token printed by bridge>'
node aperio-agent.mjs capabilities
node aperio-agent.mjs state
'{"waypoints":[[0,0,0,0,0]],"revision":"<revision from state>"}' | node aperio-agent.mjs check -
'{"waypoints":[[0,0,0,0,0]],"revision":"<revision from state>"}' | node aperio-agent.mjs move -
node aperio-agent.mjs stop
```

Use the model's current joint count and values from `state`; the numbers above are illustrative. Waypoints contain model-space joint angles in degrees. Read the current revision from `state` before checking or moving, and use the same revision for the planned path. The browser validates the revision and joint limits and performs the model's endpoint-only discrete collision check; this does not establish physical safety. `move` returning `started` means motion began, not that it finished: it can stop midway, including on disconnect. Read `state` to observe the current outcome. Use `stop` to request a stop and wait for its response; an unavailable, frozen, or disconnected page cannot acknowledge it. Disconnecting the browser session stops an Agent-initiated motion once the disconnection is detected; closing the panel alone does not disconnect it. Commands expire after 25 seconds including queue time and checking, so split long paths into smaller segments and fetch a fresh revision for each. All command responses are JSON; errors cause the CLI to exit nonzero.

Run `node aperio-agent.mjs --help` for usage. The source in the repository is `scripts/agent-bridge.mjs`. The default bridge port is 4318. To change it, pass `--port <port>` to the bridge and set `APERIO_AGENT_PORT` for the CLI; use the same loopback address and port in the browser connection control. The allowed origin must match the page exactly, including scheme and port, and have no trailing slash or path. The token is valid only until that bridge process stops. Avoid putting it in shared scripts or command history.

## Browser protocol

The bridge accepts only the configured `Origin` on browser routes and handles CORS preflights. Requests use JSON and omit credentials. All endpoints below are POSTs to `http://127.0.0.1:4318` (or the configured port):

- `/pair`: `{ "token": "..." }` returns `{ "session": "..." }`. Pairing fails if a session already exists.
- `/poll`: `{ "session": "..." }` immediately returns `{ "command": null }` or `{ "command": { "id": "...", "type": "...", "payload": {}, "expiresAt": 123 } }`. `expiresAt` is a Unix timestamp in milliseconds. Commands are delivered at most once, and there is at most one in flight.
- `/result`: `{ "session": "...", "id": "...", "result": <JSON> }` or `{ "session": "...", "id": "...", "error": "..." }` completes the command. Results for unknown or expired commands are rejected.
- `/unpair`: `{ "session": "..." }` disconnects the browser and fails queued and in-flight CLI requests.

The browser must check `expiresAt` immediately before carrying out any command, and must not execute an expired command. Commands expire after 25 seconds, including queue time; an expired command is never redelivered. The bridge accepts at most eight queued or in-flight ordinary commands; each request body is limited to 32 KiB. A stop is accepted even when the queue is full, cancels earlier queued and in-flight requests with 409, and blocks new commands until the browser acknowledges it. The page keeps polling during checks: stop invalidates the running check or move and stops motion, although a check already running may take time to finish computing. A cancelled request's 409 does not itself confirm that the page has stopped; wait for the stop response. If a stop is already pending, another command receives 409. Disconnecting a CLI request does not cancel a command already dispatched to the browser; it may still execute before its deadline. Use `stop` if needed. `check` and `move` payloads require `{ "waypoints": number[][], "revision": string }`; the bridge bounds their size, while the browser enforces model and motion rules.

The CLI uses the token in the `x-aperio-token` header on `/command`. That endpoint rejects requests carrying an `Origin` and does not grant browser CORS access. The bridge checks the loopback Host header as well as the browser origin. Pairing grants control to the local token holder, so only pair a page and agent you trust.
