# Dual Agent Orchestrator

Local-first AI coding orchestrator with a mobile-friendly web UI for iPhone/Android.

## Quick start

```bash
pip install -e .
dual-agent serve --fake
# open http://127.0.0.1:8765
```

## Accessing from a phone (LAN access)

By default the server binds to `127.0.0.1` (loopback only). To reach it from a
phone on the same Wi-Fi network you need `--lan --token`.

### Option A — HTTPS (recommended; required for PWA install)

Generate a self-signed certificate once, then start with TLS:

```bash
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem \
    -days 365 -nodes -subj '/CN=dual-agent'
TOKEN=$(openssl rand -hex 16)
dual-agent serve --lan --tls-cert cert.pem --tls-key key.pem --token "$TOKEN"
```

The startup banner will show `https://` URLs:

```
https://127.0.0.1:8765
LAN: https://192.168.1.42:8765
TLS enabled: token and session cookie are encrypted in transit.
```

On the phone, accept the self-signed certificate warning once (tap "Proceed" /
"Advanced → Accept"), then authenticate using the `#token=` fragment:

```
https://192.168.1.42:8765/#token=<your-token>
```

### Option B — plain HTTP (no PWA install; token unencrypted on the wire)

```bash
TOKEN=$(openssl rand -hex 16)
dual-agent serve --lan --token "$TOKEN"
```

The startup banner will show `http://` URLs. The token and session cookie travel
in cleartext — anyone who can observe LAN traffic can capture them and gain full
API access. Only use this mode on a physically isolated, trusted network.

```
http://127.0.0.1:8765
LAN: http://192.168.1.42:8765
⚠  TRANSPORT-UNPROTECTED: ...
```

### Authentication on first visit

Open the LAN URL and append `#token=<your-token>` as a **hash fragment** — for
example `https://192.168.1.42:8765/#token=<your-token>`. The hash fragment is
never sent to the server; the page's JavaScript reads it, exchanges it for a
session cookie via `POST /cookie`, then clears it from the URL. Alternatively,
open the URL without the hash and enter the token in the login form. The browser
stores the session cookie; subsequent requests use the cookie automatically.

**Security notes**
- `--lan` binds `0.0.0.0`; only do this on a trusted private network.
- Without `--token` anyone on the network can access the service.
- The token controls access but does NOT provide confidentiality without TLS.
- The token is never written to logs or URLs.
- The `DUAL_AGENT_TOKEN` environment variable is an alternative to `--token`.

## Adding to your phone's home screen (PWA)

The UI ships a Web App Manifest and a network-first service worker so it can be
installed as a Progressive Web App.

**HTTPS is required.** Browsers enforce that Service Workers and PWA install
prompts ("Add to Home Screen") are only available on `https://` origins (or
`localhost`). Over plain `http://` on LAN the app works as a normal web page but
the Service Worker will not register and the install prompt will not appear.
Use [Option A](#option-a--https-recommended-required-for-pwa-install) above.

**iOS Safari**
1. Open the HTTPS LAN URL in Safari (accept the self-signed cert warning once).
2. Tap the Share button → "Add to Home Screen".
3. The app opens in standalone mode (no browser chrome).

**Android Chrome**
1. Open the HTTPS LAN URL in Chrome (accept the self-signed cert warning once).
2. Tap the three-dot menu → "Add to Home screen" (or accept the install prompt).
3. The app opens in standalone mode.

The service worker uses a network-first strategy: API calls and task state are
**never** served from cache. The app shell (HTML/JS), manifest, and icon are
cached after the first successful authenticated load so the UI opens offline.
When the network is unavailable the app shows a "连接已断开" banner and waits
for the network to come back before refreshing state — task state always comes
from the server, never from a stale cache.

## Development

```bash
pip install -e ".[dev]"
python -m pytest
```

Run with fake adapters (no real Codex/Claude needed):

```bash
dual-agent serve --fake --lan --token testtoken
```
