@oar-sh/oar 0.9.2 → 0.9.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/.github/extensions/web-relay/extension.mjs +6 -1
  2. package/.github/extensions/web-relay/skills/tool-activity.mjs +7 -0
  3. package/CHANGELOG.md +336 -0
  4. package/README.md +455 -423
  5. package/bin/oar.js +25 -10
  6. package/package.json +11 -5
  7. package/server/README.md +44 -4
  8. package/server/claude-worker/claude-remote-relay-tool.mjs +99 -0
  9. package/server/claude-worker/claude-sdk-adapter.mjs +26 -4
  10. package/server/claude-worker/claude-session-process.mjs +1073 -69
  11. package/server/claude-worker/claude-session-worker.mjs +45 -4
  12. package/server/claude-worker/claude-turn-publisher.mjs +81 -20
  13. package/server/claude-worker/claude-worker-link-wiring.mjs +9 -0
  14. package/server/claude-worker/sdk-message-normalizer.mjs +7 -0
  15. package/server/copilot-worker/copilot-ask-user-tool.mjs +89 -0
  16. package/server/copilot-worker/copilot-continuation-signals.mjs +55 -0
  17. package/server/copilot-worker/copilot-question-bridge.mjs +34 -12
  18. package/server/copilot-worker/copilot-remote-relay-tool.mjs +59 -0
  19. package/server/copilot-worker/copilot-sdk-event-normalizer.mjs +113 -14
  20. package/server/copilot-worker/copilot-sdk-session-process.mjs +2349 -498
  21. package/server/copilot-worker/copilot-sdk-session-worker.mjs +82 -20
  22. package/server/cursor-worker/cursor-remote-relay-tool.mjs +65 -0
  23. package/server/cursor-worker/cursor-turn-runner.mjs +45 -2
  24. package/server/cursor-worker/sdk-message-normalizer.mjs +25 -0
  25. package/server/db-schema.mjs +28 -0
  26. package/server/grok-worker/acp-client.mjs +92 -6
  27. package/server/grok-worker/grok-turn-runner.mjs +59 -8
  28. package/server/grok-worker/sdk-message-normalizer.mjs +13 -0
  29. package/server/mcp/oar-mcp-server.mjs +473 -0
  30. package/server/migrations/0004-steer-settle-markers.mjs +119 -0
  31. package/server/migrations/0005-remote-relays.mjs +90 -0
  32. package/server/public/app/action-confirmations.js +141 -13
  33. package/server/public/app/api-client.js +125 -2
  34. package/server/public/app/ask-user-view.js +102 -13
  35. package/server/public/app/attachments-view.js +37 -2
  36. package/server/public/app/background-task-timeout-options.mjs +9 -0
  37. package/server/public/app/background-tasks-view.mjs +33 -18
  38. package/server/public/app/bootstrap.js +55 -14
  39. package/server/public/app/composer-control-state.mjs +52 -34
  40. package/server/public/app/conversation-deep-link.mjs +42 -0
  41. package/server/public/app/conversation-draft-sync.mjs +187 -0
  42. package/server/public/app/conversation-list-filter.mjs +86 -0
  43. package/server/public/app/conversation-provider-indicator.mjs +8 -0
  44. package/server/public/app/conversation-view.js +1016 -200
  45. package/server/public/app/emoji-view.js +2 -0
  46. package/server/public/app/font-scaling.js +4 -37
  47. package/server/public/app/host-suspend-ui.js +272 -0
  48. package/server/public/app/journal-view.js +149 -6
  49. package/server/public/app/mention-autocomplete.mjs +298 -0
  50. package/server/public/app/model-selector-options.mjs +1 -1
  51. package/server/public/app/push-settings.js +1 -0
  52. package/server/public/app/pwa-install.js +8 -3
  53. package/server/public/app/question-multi-select.mjs +52 -0
  54. package/server/public/app/relay-prompt-sanitizer.mjs +61 -3
  55. package/server/public/app/remote-relay-origin-view.mjs +76 -0
  56. package/server/public/app/remote-relay-shared.mjs +85 -0
  57. package/server/public/app/remote-relays-settings.js +618 -0
  58. package/server/public/app/remote-relays-store.mjs +126 -0
  59. package/server/public/app/settings-modal.js +12 -8
  60. package/server/public/app/settings-tabs.js +1 -1
  61. package/server/public/app/socket-handlers.js +37 -1
  62. package/server/public/app/steered-turn-merge.mjs +100 -0
  63. package/server/public/app/store.js +126 -15
  64. package/server/public/app/sync-outbox.mjs +16 -8
  65. package/server/public/app/thinking-follow.js +260 -0
  66. package/server/public/app/tool-activity-emoji.mjs +40 -0
  67. package/server/public/app/transcript-viewport-anchor.mjs +210 -0
  68. package/server/public/index.html +463 -17
  69. package/server/public/sw.js +7 -0
  70. package/server/relay-tools.md +7 -1
  71. package/server/repositories/message-repository.mjs +68 -18
  72. package/server/repositories/question-repository.mjs +18 -0
  73. package/server/repositories/remote-relay-repository.mjs +75 -0
  74. package/server/repositories/session-repository.mjs +13 -32
  75. package/server/routes/ask-user-routes.mjs +42 -2
  76. package/server/routes/messages-routes.mjs +572 -56
  77. package/server/routes/remote-relay-routes.mjs +133 -0
  78. package/server/routes/sessions-routes.mjs +378 -84
  79. package/server/server-runtime.mjs +303 -25
  80. package/server/services/github-ci-activity-service.mjs +199 -0
  81. package/server/services/host-suspend-command.mjs +38 -0
  82. package/server/services/host-suspend-service.mjs +254 -0
  83. package/server/services/live-turn-picker.mjs +47 -0
  84. package/server/services/push-dispatch-service.mjs +37 -4
  85. package/server/services/relay-prompt-sanitizer.mjs +17 -2
  86. package/server/services/relay-question-context.mjs +46 -0
  87. package/server/services/remote-relay-caller-context.mjs +341 -0
  88. package/server/services/remote-relay-client.mjs +332 -0
  89. package/server/services/remote-relay-dispatcher.mjs +1500 -0
  90. package/server/services/remote-relay-inbound.mjs +187 -0
  91. package/server/services/remote-relay-pairing.mjs +189 -0
  92. package/server/services/remote-relay-registry-service.mjs +581 -0
  93. package/server/services/sdk-session-import-service.mjs +49 -7
  94. package/server/services/session-worker-launch-service.mjs +131 -12
  95. package/server/services/session-worker-registry-service.mjs +75 -0
  96. package/server/services/session-worker-supervisor-service.mjs +7 -1
  97. package/server/services/session-worker-websocket-service.mjs +23 -1
  98. package/server/services/steer-resend.mjs +45 -0
  99. package/shared/background-task-timeout.mjs +12 -4
  100. package/shared/control-poller.mjs +4 -1
  101. package/shared/question-multi-select.mjs +16 -0
  102. package/shared/relay-stop-failure.mjs +15 -0
  103. package/shared/remote-relay-contract.mjs +539 -0
  104. package/shared/remote-relay-mentions.mjs +129 -0
  105. package/shared/remote-relay-tool-core.mjs +314 -0
  106. package/shared/slash-command-guard.mjs +76 -0
  107. package/shared/steer-note.mjs +66 -0
  108. package/shared/steer-settle-failure.mjs +89 -0
  109. package/shared/steer-settle-markers.mjs +18 -0
  110. package/shared/worker-crash-guard.mjs +40 -25
  111. package/shared/worker-runtime/api-client.mjs +112 -22
  112. package/shared/worker-runtime/heartbeat.mjs +55 -1
  113. package/shared/worker-runtime/runner-link-wiring.mjs +44 -0
  114. package/shared/worker-runtime/worker-websocket-link.mjs +119 -8
  115. package/server/claude-worker/claude-session-test-harness.mjs +0 -250
  116. package/server/claude-worker/fixtures/workflow-run-record.json +0 -1
  117. package/server/copilot-worker/copilot-sdk-test-harness.mjs +0 -382
  118. package/server/copilot-worker/fixtures/abort-turn.json +0 -588
  119. package/server/copilot-worker/fixtures/ask-user-turn.json +0 -1154
  120. package/server/copilot-worker/fixtures/background-timer-continuation.json +0 -445
  121. package/server/copilot-worker/fixtures/background-timer-turn.json +0 -497
  122. package/server/copilot-worker/fixtures/happy-turn.json +0 -566
  123. package/server/copilot-worker/fixtures/quota-turn.json +0 -647
  124. package/server/copilot-worker/fixtures/reasoning-turn.json +0 -625
  125. package/server/copilot-worker/fixtures/resume-turn.json +0 -700
  126. package/server/copilot-worker/fixtures/subagent-turn.json +0 -1378
  127. package/server/copilot-worker/fixtures/tool-permission-turn.json +0 -1293
  128. package/server/routes/messages-routes-test-harness.mjs +0 -111
  129. package/server/services/fixtures/claude-auth-stub.cmd +0 -1
  130. package/server/services/fixtures/claude-auth-stub.mjs +0 -76
  131. package/server/services/fixtures/claude-auth-stub.sh +0 -4
  132. package/server/services/fixtures/cli-install-stub.cmd +0 -1
  133. package/server/services/fixtures/cli-install-stub.mjs +0 -98
  134. package/server/services/fixtures/cli-install-stub.sh +0 -4
  135. package/server/services/fixtures/grok-stub.cmd +0 -1
  136. package/server/services/fixtures/grok-stub.mjs +0 -89
  137. package/server/services/fixtures/grok-stub.sh +0 -4
  138. package/shared/fixtures/copilot-catalog-raw-2026-09-07.json +0 -2363
package/README.md CHANGED
@@ -1,287 +1,290 @@
1
1
  # OAR — Open Agent Relay
2
2
 
3
- Drive your local coding agents from any browser (phone, tablet, or second computer) through a self-hosted web relay.
3
+ Drive the coding agents on your machine from any browser (phone, tablet, or a second computer) through a self-hosted web relay.
4
4
 
5
5
  ```bash
6
6
  curl -fsSL oar.sh/install | sh
7
7
  ```
8
8
 
9
- Landing page and installers: [oar.sh](https://oar.sh). From a git checkout, the Quick start below still applies unchanged.
9
+ On Windows, run `irm oar.sh/install.ps1 | iex` in PowerShell. Landing page: [oar.sh](https://oar.sh). npm and git-checkout installs are covered under [Install](#install).
10
10
 
11
- GitHub Copilot CLI is the default runtime, and two more can be enabled per conversation: **OpenAI (BYOK)** and **Claude (Agent SDK)**. All three share the same chat UI, queue, history, file browser, and question cards — you pick the runtime when you start a conversation.
11
+ OAR relays six runtimes: **GitHub Copilot**, **OpenAI (BYOK)**, **OpenAI Image (BYOK)**, **Claude (Agent SDK)**, **Cursor (Agent SDK)**, and **Grok (CLI ACP)**. You pick one per conversation, and all of them share the same chat UI, queue, history, file browser, and question cards.
12
12
 
13
13
  ```text
14
- ┌── Copilot CLI session (default)
15
- [Browser] <--WebSocket--> [server.js :3333] ┼── OpenAI BYOK worker (your API key)
16
- └── Claude worker (host's Claude login)
14
+ ┌── Copilot SDK worker, or Copilot CLI + extension
15
+ ├── OpenAI Copilot runtime, your API key
16
+ [Browser] <--WebSocket--> [OAR relay :3333] ┼── OpenAI Image Images API, called by the relay
17
+ ├── Claude Agent SDK worker, host's Claude login
18
+ ├── Cursor Agent SDK worker, your Cursor API key
19
+ └── Grok Grok CLI over ACP, host's Grok login
17
20
  ```
18
21
 
19
- ## In action
22
+ The relay runs on your hardware, listens on localhost only until you change that, and collects no telemetry (see [Security notes](#security-notes)).
23
+
24
+ OAR is under active development, so expect occasional rough edges and some provider SDK features that are missing or incomplete.
20
25
 
21
- OAR is built to feel at home on both desktop and mobile. Your conversations can follow you from a browser tab to a PWA install, with file browsing and previews built in.
26
+ ## In action
22
27
 
23
- <div align="center">
24
- <table width="100%" cellspacing="0" cellpadding="0">
28
+ <table>
25
29
  <tr>
26
- <td rowspan="2" width="40%" align="center" valign="middle">
27
- <a href="docs/screenshots/mobile_session.jpg" target="_blank" rel="noopener noreferrer">
28
- <img src="docs/screenshots/mobile_session.jpg" alt="Mobile session view">
29
- </a>
30
- <div align="center"><small>Mobile session view with the composer and a PWA-style fullscreen layout.</small></div>
30
+ <td width="42%" rowspan="3" valign="top">
31
+ <a href="https://oar.sh/assets/shots/cfmail-mobile.png?v=0.9.3"><img src="https://oar.sh/assets/shots/cfmail-mobile.png?v=0.9.3" alt="OAR on a phone: a finished agent turn in a conversation, with the composer below"></a>
32
+ <br><sub>On a phone: the same conversations, installable as an app.</sub>
31
33
  </td>
32
- <td width="60%" align="right" valign="top">
33
- <a href="docs/screenshots/desktop_pwa_background_agents.png" target="_blank" rel="noopener noreferrer">
34
- <img src="docs/screenshots/desktop_pwa_background_agents.png" alt="Desktop PWA chat view with background agents">
35
- </a>
36
- <div align="center"><small>Desktop PWA App (Chrome), chat view with background agents running in parallel.</small></div>
34
+ <td width="58%" valign="top">
35
+ <a href="https://oar.sh/assets/shots/cfmail-agents.png?v=0.9.3"><img src="https://oar.sh/assets/shots/cfmail-agents.png?v=0.9.3" alt="Background subagents running in parallel in the task panel, each with live token counts, elapsed time, and its own Stop button"></a>
36
+ <br><sub>Agents working in parallel, each with live token counts and its own Stop.</sub>
37
37
  </td>
38
38
  </tr>
39
39
  <tr>
40
- <td width="60%" align="right" valign="bottom">
41
- <a href="docs/screenshots/desktop_pwa_file_viewer.png" target="_blank" rel="noopener noreferrer">
42
- <img src="docs/screenshots/desktop_pwa_file_viewer.png" alt="Desktop PWA file viewer">
43
- </a>
44
- <div align="center"><small>Integrated file viewer for previews and inline file browsing.</small></div>
40
+ <td valign="top">
41
+ <a href="https://oar.sh/assets/shots/cfmail-question.png?v=0.9.3"><img src="https://oar.sh/assets/shots/cfmail-question.png?v=0.9.3" alt="A question card in the middle of a turn: the agent offers several choices and a free-text reply"></a>
42
+ <br><sub>When the agent needs a decision, it asks, and waits for your answer.</sub>
43
+ </td>
44
+ </tr>
45
+ <tr>
46
+ <td valign="top">
47
+ <a href="https://oar.sh/assets/shots/pingcf-thoughts.png?v=0.9.3"><img src="https://oar.sh/assets/shots/pingcf-thoughts.png?v=0.9.3" alt="An expanded Thoughts block showing the agent's reasoning above its reply"></a>
48
+ <br><sub>Read the reasoning, not just the answer.</sub>
45
49
  </td>
46
50
  </tr>
47
51
  </table>
48
- </div>
49
52
 
50
- ## What this repository provides
53
+ <a id="quick-start"></a>
51
54
 
52
- OAR is split into three pieces:
55
+ ## Install
53
56
 
54
- 1. **Web relay server** (`server/`): queueing, persistence, auth, browser UI, file browser, uploads, and the OpenAI BYOK image path.
55
- 2. **Copilot CLI extension** (`.github/extensions/web-relay/`): polls the relay, executes turns, streams activity, bridges `ask_user` questions into web question cards.
56
- 3. **Claude worker** (`server/claude-worker/`): a per-conversation Node process that runs turns through the Claude Agent SDK and speaks the same relay contracts as the Copilot workers.
57
+ ### Prerequisites
57
58
 
59
+ You need Node.js plus whatever the runtimes you actually use need. Nothing else is required.
58
60
 
61
+ | Requirement | Needed for | Notes |
62
+ | ----------- | ---------- | ----- |
63
+ | Node.js 22.13 or newer | always | Both installers check this. Running the development test suite needs Node 24 (see [DEVELOPING.md](DEVELOPING.md#node-version)) |
64
+ | GitHub Copilot CLI (`copilot`), signed in | Copilot and OpenAI (BYOK) chats | Install it with `npm install -g @github/copilot`, then run `copilot` once on the relay host and sign in. The relay looks for the CLI's runtime when it starts, so restart the relay after installing or upgrading the CLI. Needs a GitHub Copilot plan that includes Copilot CLI |
65
+ | GitHub CLI (`gh`), signed in | the `oar` launcher command; Extension-engine sessions on Windows; the Copilot card in **Check Usage** | `gh copilot` is built into current GitHub CLI releases, so there is no extension to install. The usage card also accepts a token in `GH_TOKEN` or `GITHUB_TOKEN` |
66
+ | OpenAI API key | OpenAI and OpenAI Image chats | Entered in **⚙️ Settings**, stored in the relay database |
67
+ | A Claude login on the relay host | Claude chats | Log in from **⚙️ Settings → Providers → Claude → Relogin** (the panel can install the Claude Code CLI first), or run `claude` once on the host. The relay stores no Claude key |
68
+ | Cursor API key | Cursor chats | Entered in **⚙️ Settings**, stored in the relay database |
69
+ | Grok CLI, signed in | Grok chats | Install it and sign in from **⚙️ Settings → Providers → Grok**, or set `XAI_API_KEY` in the relay's environment |
70
+ | tmux (optional) | Linux and macOS | Session workers run in detached tmux sessions you can watch from the browser (**🖥️ Inspect tmux console**). Without tmux they run as plain background processes |
59
71
 
60
- ## Project Status
72
+ ### macOS and Linux
61
73
 
62
- OAR is still under active development, so expect occasional rough edges and some provider SDK features to be missing or incomplete for now.
74
+ ```bash
75
+ curl -fsSL oar.sh/install | sh
76
+ ```
63
77
 
78
+ The script checks for Node.js 22.13 or newer, runs `npm install -g @oar-sh/oar`, and then hands off to `oar setup` (with `--defaults` when there is no terminal to ask questions in). It refuses to run as root, because OAR installs per user. `OAR_VERSION=x.y.z` pins a version, `OAR_CHANNEL=beta` follows the beta channel, and `OAR_DRY_RUN=1` only prints what it would do:
64
79
 
65
- ## Highlights
80
+ ```bash
81
+ curl -fsSL oar.sh/install | OAR_VERSION=0.9.4 sh
82
+ ```
66
83
 
67
- - Remote chat UI for local coding agents — Copilot CLI, OpenAI (BYOK), Claude (Agent SDK), or Cursor (Agent SDK), chosen per conversation
68
- - Per-message **mode** picker: `plan`, `ask`, `agent`, `autopilot`
69
- - Per-message **model** and **reasoning effort** pickers (live model discovery + fallback catalog)
70
- - Streaming tool/activity updates *and* live assistant reply text while a turn runs
71
- - Nested **subagent bubbles**: each subagent gets its own live bubble with its own thoughts, activity, and streamed text, kept as collapsible sections after the turn finishes
72
- - **Background task panel** with live per-task state, model, and token use; Claude workflow tasks fold out into a progress tree of phases and agents, and leave a *Finished background task* card in the transcript when they complete
73
- - Mathematical and scientific notation rendering for TeX/LaTeX equations and chemical formulas
74
- - Web question cards for `ask_user` clarification flows (single-field text and multi-field structured forms)
75
- - Structured answer support: multi-field elicitation with JSON schema validation and UI-rendered forms
76
- - **Context usage** modal with a per-category token breakdown of the model's context window, plus a per-conversation **auto-compact window** slider for Claude sessions
77
- - **Transcript breaks**: day separators, a marker where a Claude session auto-compacted its context, and matching dots beside the scrollbar
78
- - **Plan usage** modal with subscription credits, rate-limit windows and reset countdowns for Copilot, Claude, Cursor, and Grok
79
- - **Image conversations** (OpenAI BYOK): generate images in chat and iterate on a generated image with *Edit this image*
80
- - **Share** a conversation by link, with per-message *Hide from shares* control
81
- - Conversation history stored in local SQLite
82
- - Conversation delete requests are relayed to Copilot CLI SDK `deleteSession()` so web deletes can remove resumable CLI sessions
83
- - Conversation **compact** workflow (`/compact`) to continue with summary carry-over
84
- - Workspace + drives browser with file preview, raw file access, and sticky hidden/heavy filters
85
- - **Git changes** modal: branch + ahead/behind info, pull, and a per-file diff viewer with *Changes only* / *Full file* modes
86
- - `@file:` and `@folder:` reference tokens with copy-to-clipboard helpers
87
- - Uploads and image attachment relay support
88
- - Optional SSH reverse tunnel support for internet access
89
- - PWA install support with installed-app fullscreen preference and browser-mode fallbacks
84
+ Read it before you run it: [oar.sh/install](https://oar.sh/install).
90
85
 
91
- ## Prerequisites
86
+ ### Windows
92
87
 
93
- Only Node.js and the runtime you actually intend to use are required.
88
+ ```powershell
89
+ irm oar.sh/install.ps1 | iex
90
+ ```
94
91
 
92
+ The PowerShell installer (PowerShell 5 or newer) does the same: it checks Node.js, runs `npm install -g @oar-sh/oar`, then `oar setup`. It reads the same `OAR_VERSION`, `OAR_CHANNEL`, and `OAR_DRY_RUN` variables, for example `$env:OAR_VERSION = '0.9.4'` before the command.
95
93
 
96
- | Requirement | Needed for | Notes |
97
- | ---------------------------- | ----------------------------- | ----------------------------------------- |
98
- | Node.js 24+ | always | Runs the relay server. Verified on 24; the unit suite requires 24+ (20 and 22 fail — see DEVELOPING.md) |
99
- | GitHub CLI (`gh`) | Copilot provider | Must be available in PATH |
100
- | GitHub Copilot CLI extension | Copilot provider | `gh extension install github/gh-copilot` |
101
- | Copilot subscription | Copilot provider | Individual, Business, or Enterprise |
102
- | OpenAI API key | OpenAI + OpenAI Image chats | Entered in **⚙️ Settings**, stored locally |
103
- | Claude Code CLI, logged in | Claude chats | Log in from **⚙️ Settings → Providers → Claude → Relogin**, or run `claude` once on the relay host; no API key is stored by the relay |
94
+ ### npm
104
95
 
96
+ ```bash
97
+ npm install -g @oar-sh/oar
98
+ oar setup
99
+ ```
105
100
 
106
- ## Quick start
101
+ ### From a git checkout
107
102
 
108
103
  ```bash
109
104
  git clone https://github.com/oar-sh/oar
110
105
  cd oar
111
106
  npm install
107
+ node bin/oar.js setup # writes server/config.json with a fresh auth token
108
+ npm start # the same as: node server/server.js
112
109
  ```
113
110
 
114
- Create `server/config.json`:
111
+ A checkout keeps all of its state inside the repository (`server/config.json`, `server/data/`, `server/uploads/`, `server/logs/`) and never updates itself. `node server/server.js --port <port> --token <token>` overrides the config for one run without saving it.
112
+
113
+ You can also write `server/config.json` by hand; every key you leave out takes its default (see the [configuration reference](#configuration-reference-serverconfigjson)):
115
114
 
116
115
  ```json
117
116
  {
118
- "authToken": "change-me",
117
+ "authToken": "change-me-to-a-long-random-secret",
119
118
  "port": 3333,
120
- "localhostOnly": true,
121
- "pollIntervalMs": 3000,
122
- "processingTimeoutMs": 600000,
123
- "conversationSessionMode": "isolated"
119
+ "localhostOnly": true
124
120
  }
125
121
  ```
126
122
 
127
- Start Copilot with the relay extension:
123
+ Development workflows, tests, and relay internals are in [DEVELOPING.md](DEVELOPING.md).
128
124
 
129
- ```bash
130
- npm run copilot:relay
131
- ```
125
+ ### Start the relay
132
126
 
133
- If you installed the extension globally in `~/.copilot/extensions/web-relay/`, you can also start plain Copilot from any repository:
127
+ `oar setup` creates the config and prints the relay URL with a QR code, but it does not start the relay. Pick one:
128
+
129
+ - **Linux (global install):** accept the systemd user service that `oar setup` offers, then run `systemctl --user enable --now oar`. The relay then starts when you log in; run `loginctl enable-linger "$USER"` once if it should keep running while you are logged out.
130
+ - **Windows:** start the relay once with `oar`, then choose **⚙️ Settings → General → Autostart (Windows)**: *At sign-in* opens a visible terminal after you log on, and *At system startup* runs it headless before anyone signs in, after one admin confirmation on the PC itself.
131
+ - **Any platform:** `oar` starts the relay in the background if it is not already running, then opens the Copilot CLI (`gh copilot`) in the same shell. A relay that `oar` started stops again when that Copilot session ends, and `oar` needs the GitHub CLI.
132
+
133
+ To run only the relay from a global install, without a Copilot session, start the server the way the systemd unit does:
134
134
 
135
135
  ```bash
136
- gh copilot
136
+ COPILOT_WEB_RELAY_CONFIG="$HOME/.oar/config.json" \
137
+ COPILOT_WEB_RELAY_DATA_DIR="$HOME/.oar/data" \
138
+ COPILOT_WEB_RELAY_LOG_DIR="$HOME/.oar/logs" \
139
+ node "$(npm root -g)/@oar-sh/oar/server/server.js"
137
140
  ```
138
141
 
139
- In that setup, the extension auto-starts and supervises `server.js` for the active CLI session; `npm run copilot:relay` is just a convenience launcher for this repository.
142
+ On Windows, point the same three variables at `%APPDATA%\oar\config.json`, `%APPDATA%\oar\data`, and `%APPDATA%\oar\logs`. The directory you start it from becomes the default working directory for new sessions.
140
143
 
141
- On Windows, the relay's visible launcher path now targets a stable per-workspace Windows Terminal window name so later foreground launches reuse the same window instead of opening new desktop windows. Use the hidden/stdio fallback only when you explicitly need it.
144
+ Then open the URL that `oar setup` printed, or scan its QR code. With `localhostOnly` on (the default) that is `http://localhost:3333/` on the relay host; reach it from other devices through a [tunnel](#remote-access), or set `localhostOnly` to `false` in the config file and restart the relay for LAN access. The URL carries your token once (`?token=…`); after that, the relay keeps the browser signed in with an HttpOnly cookie.
142
145
 
143
- Open:
146
+ ### The `oar` command
144
147
 
145
- ```text
146
- http://<your-pc-ip>:3333/
147
- ```
148
+ | Command | What it does |
149
+ | ------- | ------------ |
150
+ | `oar` | Starts the relay in the background unless one is already running, then runs `gh copilot` in the same shell. The relay's output goes to its log directory, not your terminal |
151
+ | `oar -- <args>` | The same, passing `<args>` through to the Copilot CLI (`gh copilot -- <args>`) |
152
+ | `oar --port <port>` | Finds or starts the relay on `<port>` for this run, overriding the config's `port` without changing the file; a config this command creates saves it. Without it, `oar` uses the config's `port` (default `3333`) |
153
+ | `oar --migrate-from <dir>` | Global installs: copies the relay state of a pre-rename git checkout at `<dir>` into the OAR state root, once. The source is never moved or changed, and a relay still running from it blocks the copy. `oar setup` accepts the same option |
154
+ | `oar --install-extension` | Writes or refreshes the user-global Copilot CLI extension wrapper in `~/.copilot/extensions/web-relay/`, then exits. Plain `oar` does this on every run; `--no-install-extension` skips it |
155
+ | `oar setup [--defaults]` | Creates or updates the config: generates an auth token (with a config already present it offers a new one, which signs every device out), asks whether a new config should allow LAN access and whether to enable the managed Cloudflare tunnel, offers the systemd user service on Linux global installs, then prints the relay URL and a QR code. An existing config keeps its `localhostOnly` value. `--defaults` accepts every default without asking |
156
+ | `oar doctor` | Prints the version, Node.js, install mode, state root, config path, port, whether an auth token is set, the tunnel mode, the database path and size, and which provider CLIs (`gh`, `claude`, `grok`) answer. It changes nothing |
157
+ | `oar update [--beta] [--to X.Y.Z]` | Global installs: looks up the newest release of your channel in `https://oar.sh/latest.json` (`--beta` for the beta channel), installs it with `npm install -g`, and asks a running relay to restart once no turn is running. `--to X.Y.Z` installs that version straight from npm, which works even when `OAR_NO_UPDATE_CHECK=1` blocks the lookup |
158
+ | `oar --version`, `oar --help` | Prints the version, or the usage |
148
159
 
149
- When `localhostOnly` is `true`, use `http://localhost:3333/` from the same machine.
160
+ ### Where OAR keeps its files
150
161
 
151
- Sign in once with your token. The relay then uses an HttpOnly auth cookie.
162
+ A global install keeps its state in `~/.oar` (`%APPDATA%\oar` on Windows; `OAR_STATE_ROOT` moves it): `config.json`, `data/` (the database and uploads), and `logs/` (`server.log` when `oar` started the relay, and a `worker-<session>.log` per Node session worker). `npm install -g` updates never touch it. A git checkout uses `server/config.json`, `server/data/`, `server/uploads/`, and `server/logs/` instead.
152
163
 
153
- For day-to-day development workflows, relay restart steps, and worker debugging notes, see [`DEVELOPING.md`](./DEVELOPING.md).
164
+ `COPILOT_WEB_RELAY_CONFIG`, `COPILOT_WEB_RELAY_DATA_DIR`, and `COPILOT_WEB_RELAY_LOG_DIR` override the config file, the data directory, and the log directory. `oar doctor` prints the paths in use.
154
165
 
155
- ## Runtime modes and startup commands
166
+ ### Updating
156
167
 
157
- | Command | Purpose |
158
- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
159
- | `oar` | Global npm command (`npm i -g @oar-sh/oar`, or `npm link` from a checkout) that starts the web relay if needed, then runs `gh copilot` in the same shell |
160
- | `oar --install-extension` | Installs/updates a user-global `web-relay` wrapper entrypoint and exits |
161
- | `npm run copilot:relay` | Starts Copilot CLI with an initial prompt so the extension loads and the relay worker link comes online |
162
- | `node server/server.js` | The one way to start the relay server. `npm start` is an alias for exactly this command |
168
+ - **Global install:** **⚙️ Settings → General → Check for updates** shows a newer release with an **Update** button, which installs it with npm and restarts the relay once no turn is running. `oar update` does the same from a terminal. Automatic checks are opt-in: **Check for updates automatically** asks oar.sh about twice a day. `OAR_NO_UPDATE_CHECK=1` in the relay's environment blocks every check, manual ones included.
169
+ - **Git checkout:** `git pull`, `npm install`, then restart the relay (**🌄 Restart web relay** in the conversation's `⋯` menu).
163
170
 
164
- ### The single server entry point
171
+ <a id="providers"></a>
165
172
 
166
- `server/server.js` is the only entry point, in every mode — manual runs, the CLI
167
- extension, `oar`, Windows autostart, and the e2e runner all start it.
168
- Its role is chosen by argv, never by the environment:
173
+ ## Runtimes
169
174
 
170
- - `node server/server.js` — the process stays attached to your terminal as a
171
- supervisor and runs the real server (`server-runtime.mjs`) in a worker child it
172
- marks with `--relay-runtime`. Exit code 75 relaunches the worker; any other
173
- non-zero exit is retried up to 3 times before the supervisor gives up.
174
- - `node server/server.js --supervised` — runs the server in this process and
175
- exits 75 on restart, leaving restarts to whoever spawned it. The CLI extension
176
- passes this so its own bounded-backoff supervision is the only one in play.
175
+ The runtime (called the *provider* in the UI) is chosen in **New Chat** and then fixed for that conversation: once a conversation has sent its first message it keeps that provider, and its composer is locked to that provider's models. The composer states this above the input — `🔒 Session locked to GitHub Copilot / OpenAI / OpenAI Image / Claude SDK / Cursor SDK / Grok models.`, colour-coded per provider. OpenAI sessions also pin one exact model, which the note names in parentheses and the model dropdown shows as a disabled `🔒` entry.
177
176
 
178
- Role flags travel on argv because the server's environment is inherited by tmux
179
- worker sessions and by the Copilot CLI it launches — an env-based flag would be
180
- read by unrelated servers started further down that tree.
177
+ Turning a provider **off** (or removing the OpenAI key) rebinds conversations that have not sent a message yet back to Copilot, so you are never left with a conversation pointing at a runtime that can no longer start. Conversations already in flight, and conversations belonging to a different provider, are left alone.
181
178
 
182
- ### Single runtime owner rule
179
+ | Runtime | Enable via | Auth | Notes |
180
+ | ------- | ---------- | ---- | ----- |
181
+ | **Copilot** (default) | always available | the relay host's Copilot CLI login | Two engines: the headless **SDK** worker (default) or the Copilot CLI with OAR's **Extension**. The only runtime that reports Copilot usage |
182
+ | **OpenAI (BYOK)** | ⚙️ Settings → Providers → OpenAI | your API key, stored in the relay database | Runs the Copilot runtime, on either engine, against an OpenAI-compatible endpoint |
183
+ | **OpenAI Image (BYOK)** | ⚙️ Settings → Providers → OpenAI | the same key | The relay calls the OpenAI Images API itself; a chat whose replies are images |
184
+ | **Claude (Agent SDK)** | ⚙️ Settings → Providers → Claude | the relay host's Claude login, **switchable from the panel** | A dedicated Node worker per conversation. The Claude Code CLI can be installed and updated from the panel |
185
+ | **Cursor (Agent SDK)** | ⚙️ Settings → Providers → Cursor | your Cursor API key, stored in the relay database | A dedicated Node worker per conversation through the Cursor Agent SDK |
186
+ | **Grok (CLI ACP)** | ⚙️ Settings → Providers → Grok | the relay host's Grok CLI login, **sign in and out from the panel** | Drives the Grok CLI's `grok agent stdio` over ACP. The CLI can be installed and updated from the panel |
183
187
 
184
- Run only one relay owner at a time:
188
+ ### GitHub Copilot — engine choice
185
189
 
186
- 1. **Extension-managed mode** (the normal one): the Copilot CLI extension owns the relay worker WebSocket and fallback dequeue loop. `github`/`openai` turns run in Copilot CLI sessions the server launches per conversation.
187
- 2. **Standalone mode** (manual escape hatch): start the server, then run `node server/relay.mjs` by hand. It spawns its own Copilot CLI process and polls `GET /api/pending` over HTTP. Use it only when the extension transport is unavailable — there is no npm script for it.
190
+ Copilot conversations, and OpenAI (BYOK) ones, run on one of two engines, chosen in **⚙️ Settings → Providers → Copilot → Copilot engine**. The setting is per relay and is read whenever a conversation's worker starts; sessions already running keep their engine until their worker restarts.
188
191
 
189
- Do not run extension-managed relay transport together with standalone relay runtime transport.
190
- Do not restart the web relay unless the user has explicitly given permission.
191
- If a manual restart is requested, use `POST /api/relay/shutdown` only.
192
- Do not run tests that spawn Copilot CLI clients unless the user explicitly permits it.
193
- Unit tests (`npm test`) and e2e tests (`npm run test:e2e`) are isolated from a live relay and may run beside it; see `DEVELOPING.md` → Tests.
192
+ | Engine | What runs | Notes |
193
+ | ------ | --------- | ----- |
194
+ | **SDK** (default) | A headless Node worker per conversation, driving the Copilot CLI's bundled SDK runtime | Needs only a Copilot CLI that is installed and signed in; there is no extension to install or keep in sync. This engine has [mid-turn steering](#while-a-turn-runs), the [background task panel](#background-tasks) with a working **Stop**, **Cancel** on a steered message the runtime has not picked up yet, and multi-select question cards, for every Copilot model, hosted or BYOK. There is no TUI: the tmux console shows the worker's log |
195
+ | **Extension** | The Copilot CLI in a terminal session with OAR's web-relay extension loaded | The engine OAR started with. **🖥️ Inspect tmux console** shows the live Copilot TUI (Linux and macOS, with tmux). Messages sent during a turn queue behind it |
194
196
 
195
- In extension-managed mode, the worker WebSocket begins after the CLI session becomes active (typically after the first prompt), with HTTP dequeue kept only as fallback when the socket is unavailable.
196
- The extension now supervises managed `server.js` restarts (bounded backoff) while the CLI session is alive, and stops restart attempts on session shutdown.
197
- When the CLI extension connects, it also prints the relay info window (local/network/remote/auth URLs) directly in the Copilot CLI client.
197
+ The SDK engine has been the default since 0.9.2. A relay where nobody has saved a choice uses it whenever it can and falls back to **Extension** when it cannot; a saved choice always wins. Saving **SDK** is refused, with the reason shown in place of the engine description, when:
198
198
 
199
- On Windows, **Settings → Autostart (Windows)** can add a per-user Startup entry. It opens a visible terminal at sign-in and runs the installed `node server\server.js` path. This starts only the web relay server; a Copilot CLI session using the extension must attach separately before queued turns can be processed. Turning the setting off removes the OAR Startup entry.
199
+ - *"The Copilot SDK was not found when the relay started (COPILOT_SDK_PATH did not resolve)…"*: the relay found no Copilot CLI runtime at startup. It looks in the Copilot CLI's package cache, newest version first (`~/.cache/copilot/pkg` on Linux, `~/Library/Application Support/copilot/pkg` on macOS, `%LOCALAPPDATA%\copilot\pkg` on Windows; `COPILOT_PKG_DIR` adds a location), unless `COPILOT_SDK_PATH` names the runtime's `copilot-sdk` directory directly. Install or upgrade the Copilot CLI, run it once, **and restart the relay**: the launch environment is snapshotted at startup, so a CLI installed since then is not visible yet.
200
+ - *"The SDK engine requires session worker routing, which is disabled on this relay (SESSION_WORKER_ROUTING_ENABLED)…"*: with routing off no SDK worker is ever spawned. Turn **Session worker routing** back on in **⚙️ Settings → Features** (it is on by default) and restart the relay.
200
201
 
201
- Do not restart the relay by killing processes; use `POST /api/relay/shutdown` instead.
202
+ A refusal never changes the stored engine; the select snaps back to the engine the relay actually runs.
202
203
 
203
- Manual relay control details:
204
+ SDK-engine turns report their own per-turn billing to the relay, which appears as the **Last SDK worker turn** section on the Copilot card in **Check Usage**. The card's meters come from the account-level quota API and are correct for both engines.
204
205
 
205
- - `POST /api/relay/shutdown` without `restart` queues a normal relay shutdown.
206
- - `POST /api/relay/shutdown` with `restart: true` queues an intentional self-restart.
207
- - Requests are localhost-only and still require relay auth.
208
- - The relay waits until the queue is idle before acting; this endpoint does not interrupt an in-flight turn.
209
- - `/api/status` exposes the queued relay exit state as `relayShutdown` so the UI/logs can distinguish idle, queued, and shutting-down restart/shutdown flows.
210
- - Restart ownership follows the argv role, so exactly one supervisor acts on exit code 75:
211
- - `node server/server.js` — the attached supervisor respawns its worker child in the same terminal session
212
- - `node server/server.js --supervised` — the server exits 75 and the CLI extension relaunches it (bounded backoff, and it stops trying once the CLI session shuts down)
206
+ ### OpenAI (BYOK) and OpenAI Image
213
207
 
214
- ### Global npm command (Windows first)
208
+ Save your key in **⚙️ Settings → Providers → OpenAI**, together with the **OpenAI model ID** (default `gpt-4o`) and an optional **Base URL** (default `https://api.openai.com/v1`). **Enable OpenAI API key for New Chat model selection** is on once a key is saved, and saving discovers the endpoint's models from `/v1/models`.
215
209
 
216
- You can install the repo locally and get a global `oar` command without publishing:
210
+ - **OpenAI (BYOK)** conversations run the Copilot runtime against that endpoint with your key, on whichever Copilot engine is selected.
211
+ - **OpenAI Image (BYOK)** conversations are chats whose replies are images. **New Chat** asks for **Quality** and **Size** instead of reasoning effort, and **Edit this image** under a generated image iterates on it.
217
212
 
218
- ```powershell
219
- npm link
220
- # or
221
- npm install -g .
222
- ```
213
+ ### Claude (Agent SDK)
223
214
 
224
- Run it from any folder to start the web relay server for that folder's workspace root, then immediately hand the shell to `gh copilot` without a bootstrap prompt. If a relay is already active, the command reuses it and still opens Copilot in the same shell.
215
+ Turn on **⚙️ Settings → Providers → Claude → Enable Claude for New Chat model selection**. The relay authenticates through the Claude credentials on the host machine (`~/.claude`), so there is no key to enter.
225
216
 
226
- Relay server output is written to a logfile under `%LOCALAPPDATA%\copilot-remote\logs` (git checkouts) or `%APPDATA%\oar\logs` (global installs) by default (or `COPILOT_WEB_RELAY_LOG_DIR` if you set it), so it stays out of the CLI terminal.
217
+ The same panel manages the account itself. The row at the top names the signed-in account and plan, and:
227
218
 
228
- If you want custom token/tunnel settings from a specific `server/config.json`, point `COPILOT_WEB_RELAY_CONFIG` at that file before launching. A plain `npm install -g .` does not bundle the repo-local gitignored config file.
219
+ - **Relogin** runs the Claude CLI's login on the relay host and brings the flow to the browser: the authorize link appears inline with a **Copy link** button — open it on any device, authorize on claude.ai, then paste the returned code back into the field. The relay holds no Claude secret; the CLI rewrites the host credentials, and a fresh model discovery runs straight after, so switching accounts needs no relay restart. The flow is pushed over the socket, so you can start it on one device and finish it on another, and closing the modal does not lose it.
220
+ - **Logout** asks for confirmation first and tells you how many Claude workers are running. Running Claude sessions keep the previous account's token until their worker exits; new sessions use the new account.
229
221
 
230
- Manual relay shutdowns are queued via `POST /api/relay/shutdown` and only take effect after the current turn goes idle, so they are not a way to interrupt a turn in progress.
222
+ If Claude has never been logged in on the host, running `claude` there once works too.
231
223
 
232
- Roadmap for later launcher modes:
224
+ Enabling Claude also runs model discovery against the Agent SDK and adds the discovered `claude-*` model IDs to the pickers (default model `claude-sonnet-5`). Use **🤗 Select Models → Claude SDK** to choose which of them appear in the composer; the configured default model always stays enabled. Discovery runs the Claude Code build bundled with the Agent SDK, so `claude update` alone never surfaces a new Claude model: that arrives with an OAR update.
233
225
 
234
- 1. **Option 2**: launch/attach a Copilot CLI session directly.
235
- 2. **Option 3**: support `oar -- [gh copilot args]` pass-through.
236
- 3. **Session resume**: add `--session-id=<...>` handoff once the session orchestration contract is defined.
226
+ What Claude conversations support:
237
227
 
238
- ## Using the web UI
228
+ - Per-message model and reasoning effort (`none`, `low`, `medium`, `high`, `xhigh`, `max`), changeable between turns, plus **Ultracode** on models that support `xhigh` — `xhigh` effort *and* multi-agent workflow orchestration, at a matching jump in token use
229
+ - All four relay modes — `plan` maps to the SDK's plan permission mode and produces a **Plan ready** board, `ask` and `autopilot` adjust the system prompt
230
+ - Image and file attachments (images up to 5 MB are inlined; larger files are passed as paths for Claude to read)
231
+ - Question cards (including multi-select), thinking/thought streams, live reply streaming, and nested subagent bubbles
232
+ - **Mid-turn steering** with **Steer** / **Queue**, **Stop** on the reply bubble, and **Resend** for steered messages a Stop cut off (see [While a turn runs](#while-a-turn-runs))
233
+ - Background tasks that outlive the reply that started them, in the composer's task panel, including **Ultracode** workflow trees (see [Background tasks](#background-tasks))
234
+ - Session continuity across worker restarts — the native Agent SDK session id is stored and resumed
235
+ - Real context-window metrics, reported after each turn, and a per-conversation auto-compact window (see [Usage and context](#usage-and-context))
239
236
 
240
- On startup, the relay imports locally persisted Copilot sessions through the installed Copilot SDK into its database. Session lists, details, sharing, and history refreshes use that database; an unavailable SDK runtime is reported as an import failure and is not replaced with filesystem discovery.
241
-
242
- - Start a chat with **New Chat**, which asks for the **working directory**, **provider**, **model**, and **reasoning effort** (or **quality** and **size**, for image chats) before the conversation exists. The working directory picker lists the known CWDs (current session, relay workspace, browser folder, recent roots) plus a **Custom path…** entry, defaults to the last directory you picked, and the chosen directory is applied before the session worker first launches. With Copilot as the only provider the provider row is hidden.
243
- - Choose **mode** and **model** per message in the composer.
244
- - Use **Compact** to branch to a fresh conversation seeded with summary context.
245
- - Type **`/preview`** to publish a local dev server or directory on the public preview host without involving the agent: `/preview 5173 [label]`, `/preview ./dist [label]`, `/preview list`, `/preview close`. Agents can do the same via the `preview` tool (Claude/Cursor) or the documented API (see `docs/preview-servers.md`).
246
- - Use **Browse files** to inspect workspace/drives and open previews. The **Hidden** and **Heavy** toolbar filters are remembered per browser, and a refresh re-opens the folders you had expanded.
247
- - Click file/folder copy controls to insert `@file:...` / `@folder:...` tokens.
248
- - Use **🌿 Git changes** in the conversation `⋯` menu to review the workspace repository: the header shows the branch with ahead/behind counts and a **Pull** button, and the list shows every staged, unstaged, and untracked file (deleted files struck through). Clicking a file opens a diff viewer with **Changes only** and **Full file** modes; closing it returns to the still-open list.
249
- - Answer clarification prompts in relay question cards (from `ask_user`, or Claude's `AskUserQuestion`).
250
- - Watch the reply arrive: assistant text streams into the pending bubble as it is generated, and any subagent the turn spawns gets its own nested bubble with its own thoughts, activity, and text.
251
- - Use **Check Usage** (`📊`) in the conversation menu for plan usage across every configured provider: remaining credits, rate-limit windows, reset countdowns, and collapsible cost/token detail. Each provider gets its own tab, opening on the conversation's own provider, and the card names the signed-in account (email and plan) beneath its title. Sources differ per provider:
252
- - **Copilot** — live quota (AI credits or premium requests, chat, plan), plus per-model/product billed cost when your GitHub token can read personal billing. Conversations running on the experimental SDK engine add a **Last SDK worker turn** section (AI credits actually spent, tokens, model calls, overage) with the model and how long ago it was captured; it is one turn's numbers rather than a running total, and it stops being shown once it is more than seven days old.
253
- - **Claude** — subscription limit windows (5-hour, weekly, per-model), extra-usage credits, session cost, and local usage attribution. Read from the live session at the end of a turn; the relay never starts a hidden turn to refresh it, so the newest reading is from your last Claude turn.
254
- - **Cursor** — spend from the Cursor SDK measured against the monthly allowances you enter in Settings, split into the Cursor Models and Other Models pools. Cursor exposes no account API for included pools, so these figures are estimates and the Spending dashboard remains authoritative.
255
- - **Grok** — per-turn tokens and estimated cost from the agent prompt result (no live plan-quota API over ACP). Optional monthly USD allowance in Settings for an estimated remaining meter; card is hidden when Grok is disabled. Billing: [console.x.ai](https://console.x.ai).
256
- - Per-reply usage lines are recorded only for Copilot turns — OpenAI, Claude, Cursor, and Grok turns do not consume Copilot premium requests, and no usage line is attached to them.
257
- - Use the **Context** button for a per-category breakdown of the conversation's context window: a usage bar, a token/percentage table, and free space. Claude sessions report exact SDK categories; Copilot sessions show the coarser system/tools + messages + buffer split, labelled as a lower-bound estimate when the runtime no longer emits full buckets.
258
- - Claude conversations additionally get an **auto-compact window** slider in that modal. Claude Code compacts a session once it approaches a model-tuned window (around 967k tokens on Opus 5), which is why long conversations rarely compact at all; setting a smaller window makes it happen sooner and keeps turns cheaper. *Auto* hands the choice back to the CLI. The smallest window is 100k, because the CLI silently ignores anything below that and falls back to its own default. The line beneath the slider reports the window actually in force and where it came from — your setting, the model default, or the `CLAUDE_CODE_AUTO_COMPACT_WINDOW` environment override — and fills in once the conversation's first turn completes. The change reaches a running session on its next message.
259
- - The transcript marks day boundaries, and marks the point where a Claude session compacted its context with the tokens before and after. Both appear as dots beside the scrollbar for the messages currently loaded.
260
- - Use **Share** in the conversation menu to publish a read-only link. Hover any message and choose **Hide from shares** to keep it out of the shared view without deleting it; hidden messages stay fully visible to you and are marked as hidden.
261
- - External links in chat open in a new tab with `noopener`/`noreferrer`; workspace file mentions stay in the in-app preview.
262
- - Workspace browsing follows the selected CLI session's effective CWD. Running sessions keep their learned runtime CWD, menu changes update the next-launch CWD, and chat `cd ...` commands do not retarget the browser.
237
+ Differences from Copilot conversations:
263
238
 
264
- ## Providers
239
+ - Cancelling one individual subagent works only for backgrounded ones; a subagent running inside the current turn can be stopped only by stopping the whole turn
240
+ - Claude turns are not included in the Copilot usage line, and no usage line is attached to their replies (Claude's own plan limits appear in **Check Usage**)
241
+ - The browsable **Session** root points at the Agent SDK's project directory rather than a Copilot session-state folder
265
242
 
266
- The provider is chosen in **New Chat** and then fixed for that conversation: once a conversation has sent its first message it keeps that provider, and its composer is locked to that provider's models. The composer states this above the input — `🔒 Session locked to GitHub Copilot / OpenAI / OpenAI Image / Claude SDK / Cursor SDK models.`, colour-coded per provider. OpenAI sessions also pin one exact model, which the note names in brackets and the model dropdown shows as a disabled `🔒` entry.
243
+ ### Cursor (Agent SDK)
267
244
 
268
- Turning a provider **off** (or removing the OpenAI key) rebinds conversations that have not yet sent a message back to Copilot, so you are never left with a conversation pointing at a runtime that can no longer start. Conversations already in flight, and conversations belonging to a different provider, are left alone.
245
+ Turn on **⚙️ Settings → Providers → Cursor**, paste your Cursor API key, and enable it for New Chat model selection. Saving the key runs model discovery and also discovers each model's supported reasoning-effort tiers; use **🤗 Select Models → Cursor SDK** to choose which models appear in the composer (the configured default model, `composer-2.5` unless you change it, always stays enabled).
269
246
 
247
+ What Cursor conversations support:
270
248
 
271
- | Provider | Enable via | Auth | Notes |
272
- | --------------------- | --------------------------------------- | ------------------------------------- | --------------------------------------------------------------------- |
273
- | **Copilot** (default) | always available | your `gh` / Copilot CLI login | Full relay feature set; the only provider that reports Copilot usage. Two engines: the CLI extension (default) or an experimental headless SDK worker |
274
- | **OpenAI (BYOK)** | ⚙️ Settings → Providers → OpenAI | your API key, stored in the relay DB | Runs the Copilot CLI in BYOK mode against an OpenAI-compatible endpoint |
275
- | **OpenAI Image (BYOK)** | ⚙️ Settings → Providers → OpenAI | same key | Calls the OpenAI Images API directly; a chat whose replies are images |
276
- | **Claude (Agent SDK)** | ⚙️ Settings → Providers → Claude | the relay host's logged-in Claude CLI — **switchable from the panel** | No API key is stored; runs a dedicated Node worker per conversation. The CLI itself can be installed/updated from the panel |
277
- | **Cursor (Agent SDK)** | ⚙️ Settings → Providers → Cursor | your Cursor API key, stored in the relay DB | Runs a dedicated Node worker per conversation through the Cursor Agent SDK |
278
- | **Grok (CLI ACP)** | ⚙️ Settings → Providers → Grok | the relay host's Grok CLI login — **sign in / out from the panel** | No API key is stored; drives `grok agent stdio` over ACP. The CLI itself can be installed/updated from the panel |
249
+ - Per-message model and reasoning effort — effort tiers come from per-model discovery, and `none` means the model's default behavior
250
+ - All four relay modes — `plan` uses the SDK's native plan mode and produces a **Plan ready** board; `ask` and `autopilot` ride as instructions on the message text, injected only when the mode changes
251
+ - Live reply streaming, question cards, and **Stop** to abort the running turn
252
+ - The browsable **Session** root points at the worker's per-session agent store, created on the session's first turn
253
+ - Expired cached agent handles are recreated and retried automatically once — a second auth failure means the API key itself is invalid
254
+
255
+ Like Claude, Cursor turns are not included in the Copilot usage line and no usage line is attached to their replies. Cursor spend is tracked separately in **Check Usage**; set your monthly pool allowances and billing reset day under **⚙️ Settings → Providers → Cursor → Cursor monthly plan allowance**. The optional **Dashboard session token** in the same panel unlocks live *Included in plan* bars; it is detected automatically from the host's Cursor IDE login when there is one, and headless hosts can set `CURSOR_SESSION_TOKEN` instead.
256
+
257
+ ### Grok (CLI ACP)
258
+
259
+ Turn on **⚙️ Settings → Providers → Grok**. There is no key to enter: the relay drives the Grok CLI on the host and uses whatever account that CLI is signed in to (or `XAI_API_KEY` in the host environment).
260
+
261
+ The same panel manages both the CLI and the account:
262
+
263
+ - The **Grok CLI** row installs or updates the CLI itself — see
264
+ [Installing a provider CLI from the relay](#installing-a-provider-cli-from-the-relay).
265
+ - **Sign in** starts the CLI's device-code login on the relay host and brings it to the browser: the
266
+ x.ai authorization link appears inline with a **Copy link** button, and the `XXXX-XXXX` code is
267
+ shown next to it so you can check it against what the browser displays. Open the link on any
268
+ device, confirm, and the panel flips to signed-in **by itself** — nothing is pasted back through
269
+ the relay, because the CLI polls x.ai and finishes on its own. Model discovery re-runs straight
270
+ after, so switching accounts needs no relay restart. The flow is pushed over the socket: start it
271
+ on one device, finish it on another, and closing the modal does not lose it.
272
+ - **Sign out** asks for confirmation first and tells you how many Grok workers are running. Running
273
+ sessions keep the previous token until their worker exits.
274
+
275
+ The relay holds no Grok secret: the device code is public by design, and the token is written by the
276
+ CLI straight into `~/.grok/auth.json`. Running `grok login` on the host still works as before.
277
+
278
+ Grok conversations support live reply streaming, thoughts, plan boards, subagent lifecycle chips,
279
+ **Stop**, session resume across worker restarts, per-turn context metrics, and the live weekly quota
280
+ bar in **Check Usage**. The model is fixed per conversation (ACP has no mid-session switch; the default
281
+ is `grok-4.5`), and question cards are not available — the protocol has no ask-user surface.
279
282
 
280
283
  ### Installing a provider CLI from the relay
281
284
 
282
285
  Grok and Claude both run as CLIs on the relay host, and the failure mode used to be a dead end: a
283
286
  turn fails with *"Grok CLI was not found on PATH"* and the only fix is a shell on the host — the one
284
- thing the relay exists to avoid. Each provider sub-tab now carries a CLI row at the top:
287
+ thing the relay exists to avoid. The Copilot, Claude, and Grok sub-tabs therefore carry a CLI row at the top:
285
288
 
286
289
  ```
287
290
  Grok CLI not installed [ Install ]
@@ -302,141 +305,172 @@ Grok CLI 1.0.13 · ~/.grok/bin/grok · native · up to date [ Update ]
302
305
  - If your Claude was installed with npm into a folder the relay user cannot write, `claude doctor`
303
306
  says so and the button becomes **Switch to native installer** — Anthropic's own recommended fix.
304
307
  The npm copy stays where it is; the native build takes precedence on PATH.
305
- - The **Copilot** row is read-only: that CLI is managed with npm on this host, so there is nothing
308
+ - The **Copilot** row is read-only: that CLI is managed with npm on the host, so there is nothing
306
309
  the relay could usefully run.
307
310
 
308
311
  When a turn fails because a CLI is missing or signed out, the failed reply itself carries the fix as
309
312
  a button — **Install Grok CLI**, **Sign in to Grok**, **Claude settings** — which opens the right
310
313
  panel and, for an install, the same confirmation sheet.
311
314
 
312
- ### GitHub Copilot — engine choice
313
-
314
- Copilot conversations can run on either of two engines, chosen in **⚙️ Settings → Providers → Copilot → Copilot engine**. The setting is per relay and applies to newly started conversations; sessions already running keep the engine they started on until their worker restarts.
315
-
316
- | Engine | What runs | Trade-offs |
317
- | ------ | --------- | ---------- |
318
- | **Extension** (default) | The Copilot CLI in a terminal session with the web-relay extension loaded | The engine everything has shipped on. Attach to a live session with the tmux inspector (`tmux attach -t <sdk-session-id>`) |
319
- | **SDK** (experimental) | A headless Node worker per conversation, driving the CLI's bundled SDK runtime over JSON-RPC | No CLI extension to install or keep in sync — first run needs only a Copilot CLI that is installed and logged in. **No tmux inspector** for those sessions: there is no TUI to attach to |
315
+ ### Models
320
316
 
321
- Switching to **SDK** can be refused, and the panel says why in place of the engine description:
317
+ The composer's model picker is the union of every enabled provider's catalog, filtered to the models the active conversation's provider can actually serve:
322
318
 
323
- - *"The Copilot SDK was not found when the relay started (COPILOT_SDK_PATH did not resolve)…"* — no Copilot CLI bundle with an SDK was found. Install or upgrade the GitHub Copilot CLI **and restart the relay**: the launch environment is snapshotted at startup, so a CLI installed since then is not visible yet.
324
- - *"The SDK engine requires session worker routing, which is disabled on this relay (SESSION_WORKER_ROUTING_ENABLED)…"* — with routing off no SDK worker is ever spawned, so the setting would have no effect.
319
+ - **Copilot** models are discovered from the installed Copilot CLI runtime a few seconds after the relay starts, and refreshed by the sessions that run on it. Each model shows the reasoning efforts and context window the runtime reports. The list starts with `auto`, then groups models by vendor (OpenAI, Anthropic, Google, xAI, Microsoft, Azure OpenAI, Moonshot AI, then any others alphabetically), newest version first. Before the first discovery, a fresh relay offers a curated set: `gpt-5.4`, `gpt-5.4-mini` (the default), `gpt-5.3-codex`, `claude-sonnet-4.6`, and `claude-haiku-4.5`.
320
+ - **OpenAI (BYOK)** models are discovered from `/v1/models` when the key is saved or re-enabled.
321
+ - **Claude** models are discovered from the Agent SDK when the provider is enabled. Bracketed `[1m]` long-context variants (such as `claude-opus-5[1m]`) do not appear as separate entries; they surface as a 1M option in the composer's context-size dropdown for the base model.
322
+ - **Cursor** models (and their per-model reasoning-effort tiers) are discovered when the API key is saved or the provider is re-enabled.
323
+ - **Grok** models are discovered from the Grok CLI over ACP when the provider is enabled, after a sign-in, and after a CLI install from the panel.
325
324
 
326
- A refusal never changes the stored engine; the select snaps back to the engine the relay actually runs.
325
+ Use **🤗 Select Models** to choose which variants show up in the composer, then **💾 Save enabled models**; **Refresh** reruns discovery for every enabled runtime. The modal has one tab per runtime — **Copilot**, **OpenAI**, **Claude SDK**, **Cursor SDK**, **Grok** — and each tab lists only the models that runtime serves; there is no cross-runtime switching inside a conversation.
327
326
 
328
- SDK-engine turns report their own per-turn billing to the relay, which appears as the **Last SDK worker turn** section on the Copilot card in **Check Usage** (see above). The card's meters come from the account-level quota API and are correct for both engines.
327
+ ## Highlights
329
328
 
330
- ### Claude (Agent SDK)
329
+ ### New in 0.9.4
331
330
 
332
- Turn on **⚙️ Settings → Providers → Claude → Enable Claude for New Chat model selection**. The relay authenticates through the Claude credentials present on the host machine (`~/.claude`), so there is no key to enter.
331
+ - **Remote relays**: pair two OAR relays in **Settings → Relays** by pasting the other relay's web address; the pairing works both ways. An agent in any session can then work on the other relay when you ask it to: list and read its sessions, prompt one, start a new one, wait for the reply, and pass a question the remote agent asks back to you. See [Remote relays](#remote-relays).
332
+ - **You decide when a relay is in reach**: a paired relay stays locked in a conversation until you mention it (`@` picks it from a list). Per relay, **Agents may** limits agents to *read only*, *read and prompt*, or *full*, and in ask and plan mode every write action asks you first. The receiving relay marks such messages with a **↗ from …** badge.
333
+ - **Every provider gets the same tools**: Grok and the Copilot extension engine now get `remote_relay` and `preview` as real tools through OAR's MCP server, instead of instruction text.
334
+ - **Follow the live reply**: three toggles in the running reply's header keep its thoughts, its answer, or its tool list pinned just above the composer as the reply grows. Scrolling the transcript by hand turns it off; typing in the composer does not.
335
+ - **Suspend host waits for the work to finish** (Windows): the relay queues the suspend and fires it after 2 minutes with no turn, background task, or GitHub Actions run active, or after a 30-second countdown when nothing was running. Every device shows a banner with what is still active and a **Cancel** button; a queued relay restart shows the same banner.
336
+ - **Stop** asks for confirmation before it cancels the running turn.
337
+ - **Fewer stuck and failed sends**: switching the Claude model mid-conversation no longer strands the next message, and the relay keeps idle connections open for 65 seconds, so a request sent after a short pause is no longer reset (behind a proxy or tunnel: an occasional 502).
333
338
 
334
- The same panel manages the account itself. The row at the top names the signed-in account and plan, and:
339
+ ### Everything else
335
340
 
336
- - **Relogin** runs the Claude CLI's login on the relay host and brings the flow to the browser: the authorize link appears inline with a **Copy link** button — open it on any device, authorize on claude.ai, then paste the returned code back into the field. The relay holds no Claude secret; the CLI rewrites the host credentials, and a fresh model discovery runs straight after, so switching accounts needs no relay restart. The flow is pushed over the socket, so you can start it on one device and finish it on another, and closing the modal does not lose it.
337
- - **Logout** asks for confirmation first and tells you how many Claude workers are running. Running Claude sessions keep the previous account's token until their worker exits; new sessions use the new account.
341
+ - Remote chat UI for local coding agents, with the runtime chosen per conversation
342
+ - Per-message **mode** (`plan`, `ask`, `agent`, `autopilot`), **model**, and **reasoning effort** pickers, backed by live model discovery
343
+ - Streaming tool activity, thoughts, and live assistant reply text while a turn runs
344
+ - Nested **subagent bubbles**: each subagent gets its own live bubble with its own thoughts, activity, and streamed text, kept as collapsible sections after the turn finishes
345
+ - **Background task panel** with live per-task state, model, and token use; Claude workflow tasks fold out into a progress tree of phases and agents, and leave a *Finished background task* card in the transcript when they complete
346
+ - Question cards for clarification: one-click choices, multi-select checkmarks, free text, and multi-field structured forms validated against their JSON schema
347
+ - Mathematical and scientific notation rendering for TeX/LaTeX equations and chemical formulas
348
+ - **Context usage** modal with a per-category token breakdown of the model's context window, plus a per-conversation **auto-compact window** slider for Claude sessions
349
+ - **Transcript breaks**: day separators, a marker where a Claude session auto-compacted its context, and matching dots beside the scrollbar
350
+ - **Plan usage** modal with subscription credits, rate-limit windows, and reset countdowns for Copilot, Claude, Cursor, and Grok
351
+ - **Image conversations** (OpenAI Image): generate images in chat and iterate on a generated image with **Edit this image**
352
+ - Agents can embed **images, video, and audio** in a reply by their absolute path, on every runtime; clicking an embedded image opens the file viewer with zoom, download, and copy
353
+ - **Screenshot annotations**: mark up an uploaded screenshot with highlighter strokes before or after sending; the original upload is never modified
354
+ - **Share** a conversation by read-only link, and hide individual messages from the shared view
355
+ - Conversation history in local SQLite, including the Copilot sessions already stored on the host
356
+ - `/compact` continues in a fresh conversation seeded with a summary; `/preview` publishes a local dev server or folder on a public preview host
357
+ - Workspace and drive browser with file previews, **Git changes** with a diff viewer, and `@file:` / `@folder:` reference tokens
358
+ - **Push notifications** for questions, finished or failed turns, plan boards, and a CLI going offline
359
+ - Opt-in **self-update** from Settings or `oar update`
360
+ - Remote access through a managed **SSH** reverse tunnel or **Cloudflare Tunnel**
361
+ - Installable **PWA** with an installed-app fullscreen preference and browser-mode fallbacks
338
362
 
339
- If Claude has never been logged in on the host, running `claude` there once still works as before.
363
+ ## Using the web UI
340
364
 
341
- Enabling it also runs model discovery against the Agent SDK and adds the discovered `claude-*` model IDs to the pickers. Use **Select Models → Claude SDK** to choose which of them appear in the composer; the configured default model always stays enabled.
365
+ ### Starting a conversation
342
366
 
343
- What Claude conversations support:
367
+ - Start a chat with **+ New Chat**, which asks for the **Working directory**, **Provider**, **Model**, and **Reasoning effort** (or **Quality** and **Size**, for image chats; **Context window** where a model offers several) before the conversation exists. The working-directory list offers the known directories (current session, relay workspace, browser folder, recent roots), a **Custom path…** entry, and a 📁 folder picker; it defaults to the directory you picked last, and the chosen directory is applied before the session worker first launches. With Copilot as the only provider the provider row is hidden.
368
+ - Choose the **mode** and **model** per message in the composer.
369
+ - The composer knows two slash commands, with autocomplete: **`/compact`** branches to a fresh conversation seeded with summary context, and **`/preview`** publishes a local dev server or directory on the public preview host without involving the agent (`/preview 5173 [label]`, `/preview ./dist [label]`, `/preview list`, `/preview close`). Agents can do the same through the `preview` tool (Claude and Cursor) or the documented API (see [docs/preview-servers.md](docs/preview-servers.md)). Any other single-line text starting with `/` is held back once with an *Unknown command* notice; press send again to send it as text.
344
370
 
345
- - Per-message model and reasoning effort (`none`, `low`, `medium`, `high`, `xhigh`, `max`), changeable between turns, plus **Ultracode** on models that support `xhigh` — `xhigh` effort *and* multi-agent workflow orchestration, at a matching jump in token use
346
- - All four relay modes — `plan` maps to the SDK's plan permission mode and produces a **Plan ready** board, `ask` and `autopilot` adjust the system prompt
347
- - Image and file attachments (images up to ~5 MB are inlined; larger files are passed as paths for Claude to read)
348
- - Question cards, thinking/thought streams, live reply streaming, and nested subagent bubbles
349
- - **Stop** to abort the running turn
350
- - Background tasks that outlive the reply that started them: the composer's task panel lists them live with their own **Stop**, and an **Ultracode** workflow folds out into a tree of its phases and agents (state, model, tokens). When the workflow finishes, the summarizing reply keeps a collapsed *Finished background task* card holding the final tree, which survives reloads
351
- - Session continuity across worker restarts — the native Agent SDK session id is stored and resumed
352
- - Real context-window metrics, reported after each turn
371
+ ### Relay modes
353
372
 
354
- Differences from Copilot conversations:
373
+ | Mode | Behavior |
374
+ | ----------- | ------------------------------------------------------------ |
375
+ | `ask` | Clarification-first behavior before implementation |
376
+ | `plan` | Planning response style (no implementation unless requested) |
377
+ | `agent` | Interactive coding agent behavior |
378
+ | `autopilot` | Action-first behavior; asks only when truly blocking |
355
379
 
356
- - Cancelling one individual subagent works only for backgrounded ones; a subagent running inside the current turn can be stopped only by stopping the whole turn
357
- - Claude turns are not included in the Copilot usage line, and no usage line is attached to their replies (Claude's own plan limits appear in **Check Usage**)
358
- - The browsable **Session** root points at the Agent SDK's project directory rather than a Copilot session-state folder
380
+ ### While a turn runs
359
381
 
360
- ### Cursor (Agent SDK)
382
+ - Assistant text streams into the pending bubble as it is generated, next to the turn's tool activity and thoughts. Any subagent the turn spawns gets its own nested bubble with its own thoughts, activity, and text.
383
+ - **Steer or queue.** On Claude, and on Copilot's SDK engine, a message you send during a live turn is pushed into that turn; the send button reads **Steer**. A folded message keeps a compact *merged* marker in the transcript, and one the agent answers separately gets its own reply. While the turn waits on a question card or plan approval, or is compacting, the button reads **Queue** and its tooltip says why: the message waits as a pending bubble (with **Cancel**) and steers in once the card is answered or the compaction ends. On the other runtimes, and on Copilot's Extension engine, messages sent during a turn queue behind it, and the button reads **Queue**.
384
+ - **Stop** is on the running reply's bubble, whose header stays pinned at the top while a long reply scrolls; the send button never turns into Stop. Queued messages carry their own **Cancel** until they are picked up. A steered message that a Stop cut off is marked *Stopped with the turn — not answered* and offers **Resend**; the button then reads **Resent** on every device.
385
+ - On Copilot, **Stop** ends only the reply: background agents the turn started keep running under their own **Stop** in the task panel.
361
386
 
362
- Turn on **⚙️ Settings → Providers → Cursor**, paste your Cursor API key, and enable it for New Chat model selection. Saving the key runs model discovery and also discovers each model's supported reasoning-effort tiers; use **Select Models → Cursor SDK** to choose which models appear in the composer (the configured default model always stays enabled).
387
+ ### Question cards
363
388
 
364
- What Cursor conversations support:
389
+ - Agents ask clarifying questions through question cards: `ask_user` on Copilot and Cursor, `AskUserQuestion` on Claude. A choice answers with one click; a question that allows several answers shows checkmarks and one **Reply with selection** button, whose reply lists every ticked choice plus anything you typed. Structured requests render as multi-field forms.
390
+ - On Copilot's SDK engine, the model gets OAR's own `ask_user` tool, which can mark a question as multi-select; a question worded "select all that apply" counts too, and every Copilot choice card that accepts free text has a **Select several** switch.
391
+ - A card waits 8 hours for an answer (2 hours on Copilot's Extension engine). After that the agent is told nobody answered and continues according to the conversation's mode. A turn that is waiting on a card is never treated as stuck.
392
+ - Grok has no question cards: ACP has no ask-user surface.
365
393
 
366
- - Per-message model and reasoning effort — effort tiers come from per-model discovery, and `none` means the model's default behavior
367
- - All four relay modes — `plan` uses the SDK's native plan mode and produces a **Plan ready** board; `ask` and `autopilot` ride as instructions on the message text, injected only when the mode changes
368
- - Live reply streaming, question cards, and **Stop** to abort the running turn
369
- - The browsable **Session** root points at the worker's per-session agent store, created on the session's first turn
370
- - Expired cached agent handles are recreated and retried automatically once — a second auth failure means the API key itself is invalid
394
+ ### Background tasks
371
395
 
372
- Like Claude, Cursor turns are not included in the Copilot usage line and no usage line is attached to their replies. Cursor spend is tracked separately in **Check Usage**; set your monthly pool allowances and billing reset day under Settings → Providers → Cursor → Cursor monthly plan allowance.
396
+ - The composer's task panel lists background work that outlives the reply that started it: backgrounded commands, subagents, monitors, and workflows on Claude; background agents and detached shells on Copilot's SDK engine. Each row shows its state, the model it runs on, the command it is running right now, its token count, and its own **Stop**. The panel scrolls when it holds more tasks than fit.
397
+ - An **Ultracode** workflow folds out into a tree of its phases and agents (state, model, tokens). When it finishes, the summarizing reply keeps a collapsed *Finished background task* card holding the final tree, which survives reloads.
398
+ - A background task's result arrives as its own *continuation* reply.
399
+ - **⚙️ Settings → General → Background task timeout** (default 4 h; 0 = no limit, up to 10 h) caps how long background tasks alone may keep a Claude or Copilot session running after its reply. When the limit is reached, the tasks are stopped.
373
400
 
374
- ### Grok (CLI ACP)
401
+ ### Drafts across devices
375
402
 
376
- Turn on **⚙️ Settings → Providers → Grok**. There is no key to enter: the relay drives the Grok CLI
377
- on the host and uses whatever account that CLI is signed in to (or `XAI_API_KEY` in the host
378
- environment).
403
+ The composer draft, attachments included, is saved per conversation on the relay and follows you to your other devices. Saves happen only on real edits and are version-checked, so an idle device never overwrites the draft you are typing on another one, and a device you are not typing on picks up the other's draft. When two devices edit at once, the newest keystroke wins and the replaced text is offered back with **Restore** (and **Undo**). A draft longer than 20,000 characters is saved up to that length, and the composer says the rest is not saved.
379
404
 
380
- The same panel manages both the CLI and the account:
405
+ ### The conversation list
381
406
 
382
- - The **Grok CLI** row installs or updates the CLI itself — see
383
- [Installing a provider CLI from the relay](#installing-a-provider-cli-from-the-relay).
384
- - **Sign in** starts the CLI's device-code login on the relay host and brings it to the browser: the
385
- x.ai authorization link appears inline with a **Copy link** button, and the `XXXX-XXXX` code is
386
- shown next to it so you can check it against what the browser displays. Open the link on any
387
- device, confirm, and the panel flips to signed-in **by itself** — nothing is pasted back through
388
- the relay, because the CLI polls x.ai and finishes on its own. Model discovery re-runs straight
389
- after, so switching accounts needs no relay restart. The flow is pushed over the socket: start it
390
- on one device, finish it on another, and closing the modal does not lose it.
391
- - **Sign out** asks for confirmation first and tells you how many Grok workers are running. Running
392
- sessions keep the previous token until their worker exits.
407
+ - **Filter conversations…** above the list matches titles, ignoring case. It clears with **×** or Escape, and while it is active it loads older pages, so it searches every conversation, not just the loaded ones.
408
+ - **🔍** in the conversation header searches message text across all conversations.
409
+ - On startup, the relay imports the Copilot sessions stored on the host (through the installed Copilot runtime) into its database, so they appear in the list with their history. If the runtime is unavailable, the import is reported as failed rather than guessed from the filesystem.
410
+ - Deleting a conversation stops its session worker and removes its CLI session too (Copilot through the runtime, Claude by deleting that session's transcript). An imported Copilot session the relay never ran is only hidden: its CLI session stays, and nothing is stopped for it. A conversation that is still working — a running turn, one waiting on a question card or approval, or live background tasks — is not deleted, and the sidebar says why so you can stop it first.
393
411
 
394
- The relay holds no Grok secret: the device code is public by design, and the token is written by the
395
- CLI straight into `~/.grok/auth.json`. Running `grok login` on the host still works as before.
412
+ ### Files, git, and previews
396
413
 
397
- Grok conversations support live reply streaming, thoughts, plan boards, subagent lifecycle chips,
398
- **Stop**, session resume across worker restarts, per-turn context metrics, and the live weekly quota
399
- bar in **Check Usage**. The model is fixed per conversation (ACP has no mid-session switch), and
400
- question cards are not available — the protocol has no ask-user surface.
414
+ - Use **📁 Browse files** to inspect the workspace (💼), drives (📀), or the session's own folder (🕵️), and to open previews. The **Hidden** and **Heavy** toolbar filters are remembered per browser, and a refresh re-opens the folders you had expanded.
415
+ - Click the file and folder copy controls to insert `@file:...` / `@folder:...` tokens.
416
+ - Workspace browsing follows the selected session's effective working directory. Running sessions keep their learned runtime directory, **🗂️ Change CWD** in the `⋯` menu changes the directory for the next launch, and `cd ...` typed in chat does not retarget the browser.
417
+ - Use **🌿 Git changes** in the conversation `⋯` menu to review the workspace repository: the header shows the branch with ahead/behind counts and a **⬇ Pull** button, and the list shows every staged, unstaged, and untracked file (deleted files struck through). Clicking a file opens a diff viewer with **Changes** and **Full file** modes; closing it returns to the still-open list.
418
+ - Agents can embed images, video, and audio in a reply by absolute path; they render inline.
419
+ - Tap an image attachment in the composer (it carries a 🖍️ badge), or use **🖍️ Annotate** in the file viewer, to mark it up with highlighter strokes. The annotated copy is uploaded; the original stays untouched.
420
+ - External links in chat open in a new tab with `noopener`/`noreferrer`; workspace file mentions stay in the in-app preview.
401
421
 
402
- ## Relay modes
422
+ ### Usage and context
403
423
 
424
+ - Use **📊 Check Usage** in the conversation `⋯` menu for plan usage across every configured provider: remaining credits, rate-limit windows, reset countdowns, and collapsible cost/token detail. Each provider gets its own tab, opening on the conversation's own provider, and the card names the signed-in account (email and plan) beneath its title. Sources differ per provider:
425
+ - **Copilot** — live quota (AI credits or premium requests, chat, plan), plus per-model/product billed cost when your GitHub token can read personal billing. Conversations on the SDK engine add a **Last SDK worker turn** section (AI credits actually spent, tokens, model calls, overage) with the model and how long ago it was captured; it is one turn's numbers rather than a running total, and it stops being shown once it is more than seven days old.
426
+ - **Claude** — subscription limit windows (5-hour, weekly, per-model), extra-usage credits, session cost, and local usage attribution. Read from the live session at the end of a turn; the relay never starts a hidden turn to refresh it, so the newest reading is from your last Claude turn.
427
+ - **Cursor** — spend from the Cursor SDK measured against the monthly allowances you enter in Settings, split into the Cursor Models and Other Models pools; these figures are estimates, and Cursor's Spending dashboard remains authoritative. With a dashboard session token, the card adds live *Included in plan* bars.
428
+ - **Grok** — the live weekly subscription quota, read with the host's Grok CLI login, plus per-turn tokens and estimated cost from the agent's prompt result. An optional monthly USD allowance in Settings adds an estimated remaining meter; the card is hidden when Grok is disabled. Billing: [console.x.ai](https://console.x.ai).
429
+ - Per-reply usage lines are recorded only for Copilot turns — OpenAI, Claude, Cursor, and Grok turns do not consume Copilot premium requests, and no usage line is attached to them.
430
+ - Use the **🧠** context button for a per-category breakdown of the conversation's context window: a usage bar, a token/percentage table, and free space. Claude sessions report exact SDK categories; Copilot sessions show the coarser system/tools + messages + buffer split, labelled as a lower-bound estimate when the runtime no longer emits full buckets.
431
+ - Claude conversations additionally get an **auto-compact window** slider in that modal. Claude Code compacts a session once it approaches a model-tuned window (around 967k tokens on a 1M-context model), which is why long conversations rarely compact at all; setting a smaller window makes it happen sooner and keeps turns cheaper. *Auto* hands the choice back to the CLI. The smallest window is 100k, because the CLI silently ignores anything below that and falls back to its own default. The line beneath the slider reports the window actually in force and where it came from — your setting, the model default, or the `CLAUDE_CODE_AUTO_COMPACT_WINDOW` environment override — and fills in once the conversation's first turn completes. The change reaches a running session on its next message.
432
+ - The transcript marks day boundaries, and marks the point where a Claude session compacted its context with the tokens before and after. Both appear as dots beside the scrollbar for the messages currently loaded.
404
433
 
405
- | Mode | Behavior |
406
- | ----------- | ------------------------------------------------------------ |
407
- | `ask` | Clarification-first behavior before implementation |
408
- | `plan` | Planning response style (no implementation unless requested) |
409
- | `agent` | Interactive coding agent behavior |
410
- | `autopilot` | Action-first behavior; asks only when truly blocking |
434
+ ### Sharing
411
435
 
436
+ Use **➡️ Share conversation** in the `⋯` menu to publish a read-only link. Each message has a **Hide** button that keeps it out of the shared view without deleting it; hidden messages stay fully visible to you, marked *Hidden from shared viewers*, with an **Unhide** button. Anyone with the link can read the shared view, without signing in.
412
437
 
413
- ## Models
438
+ ### Notifications and the app
414
439
 
415
- The composer's model picker is the union of every enabled provider's catalog, filtered to the models the active conversation's provider can actually serve:
440
+ - **⚙️ Settings → Notifications** turns on push notifications per device: for questions from the agent, completed or failed turns, plan boards, and the CLI going offline. They need a secure context (HTTPS, or `localhost`), are sent only while no device has the app in the foreground, and show generic text unless you opt in to message previews on that device.
441
+ - The **⬇** button in the sidebar installs OAR as an app (PWA). **Relay name** in Settings sets its label on every device that connects to this relay, and is the name [paired relays](#remote-relays) know it by.
416
442
 
417
- - **Copilot** models come from live snapshot updates published by the active CLI runtime, falling back to a curated set (`claude-sonnet-4.6`, `claude-haiku-4.5`, `gpt-5.4`, `gpt-5.4-mini`, `gpt-5.3-codex`).
418
- - **OpenAI (BYOK)** models are discovered from `/v1/models` when the key is saved or re-enabled.
419
- - **Claude** models are discovered from the Agent SDK when the provider is enabled. Bracketed `[1m]` long-context variants (such as `claude-opus-5[1m]`) do not appear as separate entries; they surface as a 1M option in the composer's context-size dropdown for the base model.
420
- - **Cursor** models (and their per-model reasoning-effort tiers) are discovered when the API key is saved or the provider is re-enabled.
443
+ ### The conversation menu
421
444
 
422
- Use **Select Models** to choose which variants show up in the composer. The modal has one tab per runtime — **Copilot**, **OpenAI**, **Claude SDK**, **Cursor SDK** — and each tab lists only the models that runtime serves; there is no cross-runtime switching inside a conversation.
445
+ Besides the entries above, the `⋯` menu holds **✍️ Edit conversation title**, **🖥️ Inspect tmux console** (a read-only view of the session's tmux pane), **🤗 Select Models**, **⚙️ Settings**, **🌄 Restart web relay** (queued until the current turn is idle), **💤 Suspend host**, and **☠️ Kill session** (stops the conversation's worker; an active turn then needs a retry or a new message). Its header shows the queue counts with **🚮 Empty queue**.
423
446
 
424
- Selection is persisted in browser storage and attached per message.
447
+ ## Settings and configuration reference
425
448
 
426
- ## Settings (⚙️ in the web UI)
449
+ ### Settings (⚙️ in the web UI)
427
450
 
428
- The modal is organised into four tabs — **General**, **Providers** (with an OpenAI / Claude / Grok / Cursor sub-tab each), **Previews**, and **Notifications** — and reopens on the tab you used last. Most of these settings live in the relay database rather than `server/config.json`, and apply to every browser that connects:
451
+ The modal is organised into six tabs — **General**, **Providers** (with a **Copilot**, **OpenAI**, **Claude**, **Grok**, and **Cursor** sub-tab), **Relays**, **Previews**, **Notifications**, and **Features** — and reopens on the tab you used last. Unless noted as per browser, these settings live in the relay database rather than the config file, and apply to every browser that connects:
429
452
 
430
- | Setting | Default | What it does |
431
- | -------------------------- | ---------- | ------------------------------------------------------------------------------- |
432
- | OpenAI API key / model / base URL | — | Enables the OpenAI and OpenAI Image providers |
433
- | Claude (Agent SDK) | disabled | Enables Claude as a New Chat provider and runs model discovery |
434
- | Claude account (Relogin / Logout) | host login | Switches the Claude account the relay host's CLI uses, from the browser (see [Claude (Agent SDK)](#claude-agent-sdk)) |
435
- | Max turn duration | `60 min` | Hard cap on how long one turn may run before the relay requeues it (see below) |
436
- | Default session workspace root | — | CWD used by sessions that have no configured root |
437
- | Install app name | — | Label used for future PWA installs (per browser) |
438
- | Show Suspend host action | on | UI visibility of the **💤 Suspend host** menu action |
439
- | Autostart (Windows) | off | Adds a per-user Startup entry that launches the relay server at sign-in |
453
+ | Tab | Setting | Default | What it does |
454
+ | --- | ------- | ------- | ------------ |
455
+ | General | Theme, Text scaling | dark theme | Per browser |
456
+ | General | Show Suspend host action | on | Per browser: shows **💤 Suspend host** in the conversation menu |
457
+ | General | Max turn duration | `60 min` | Hard cap on how long one turn may run before the relay requeues it (0 = no limit, up to 10 h; see below) |
458
+ | General | Background task timeout | `4 h` | How long background tasks alone may keep a Claude or Copilot session running after its reply; then they are stopped (0 = no limit, up to 10 h) |
459
+ | General | Autostart (Windows) | Off | *At sign-in* (a visible terminal after you log on) or *At system startup* (headless, no logon needed, one admin confirmation) |
460
+ | General | Relay name | `OAR` (the hostname for paired relays) | Label of the installed PWA on every device, and this relay's name for [paired relays](#remote-relays) |
461
+ | General | Check for updates automatically | off | Opt-in: the relay asks oar.sh for the latest version about twice a day. **Check for updates** works regardless; `OAR_NO_UPDATE_CHECK=1` blocks both |
462
+ | General | Default CWD for new sessions | relay workspace root | Working directory for newly created sessions that have none of their own |
463
+ | Providers | Copilot engine | SDK | Extension when this relay cannot run the SDK engine (see [engine choice](#github-copilot--engine-choice)) |
464
+ | Providers | OpenAI API key / model / base URL | — / `gpt-4o` / `https://api.openai.com/v1` | Enables the OpenAI and OpenAI Image providers |
465
+ | Providers | Claude (Agent SDK) | off | Enables Claude as a New Chat provider and runs model discovery; default model `claude-sonnet-5` |
466
+ | Providers | Claude account (Relogin / Logout) | host login | Switches the Claude account the relay host's CLI uses, from the browser (see [Claude (Agent SDK)](#claude-agent-sdk)) |
467
+ | Providers | Cursor API key / model | — / `composer-2.5` | Enables Cursor; plus monthly plan allowances and the optional dashboard session token |
468
+ | Providers | Grok | off | Enables Grok; default model `grok-4.5`; **Sign in** / **Sign out**; optional monthly allowance |
469
+ | Relays | Public address, Accept prompts from other relays' agents | browser address / on | How paired relays reach this one, and whether their agents may prompt sessions here (see [Remote relays](#remote-relays)) |
470
+ | Relays | Remote relays | — | Pair a relay by pasting its web address; per relay **Agents may** read only, read and prompt, or do everything |
471
+ | Previews | Live previews | — | Lists the published previews, each of which you can close |
472
+ | Notifications | Push notifications | off | Per device: which events notify, and whether titles and message previews are included |
473
+ | Features | Feature flags | see below | **Session worker routing** (on), **Continuation answer routing** (on), **Generated-image continuity** (on), and a reserved **Worker fallback restart** (off, no effect yet). Changes apply after a relay restart; a `COPILOT_REMOTE_<FLAG>` environment variable pins a flag, e.g. `COPILOT_REMOTE_SESSION_WORKER_ROUTING_ENABLED=0` |
440
474
 
441
475
  ### How a stuck turn is detected
442
476
 
@@ -447,26 +481,24 @@ Two independent guards, both of which exempt a turn that is waiting on an unansw
447
481
 
448
482
  When either trips, the turn is returned to the queue rather than lost.
449
483
 
450
- ## Configuration reference (`server/config.json`)
484
+ ### Configuration reference (`server/config.json`)
451
485
 
486
+ The config file is `server/config.json` in a git checkout and `~/.oar/config.json` (`%APPDATA%\oar\config.json`) for a global install; `COPILOT_WEB_RELAY_CONFIG` points the relay at another file. It holds the auth token, so the relay keeps it owner-only. The relay reads it at startup: restart the relay after editing it.
452
487
 
453
488
  | Key | Default | Description |
454
489
  | -------------------------- | -------------------- | ------------------------------------------------------------------------- |
455
- | `authToken` | generated if missing | Required for API/UI auth; set explicitly for stable access |
490
+ | `authToken` | generated if empty | Required for API/UI auth. If empty, the relay generates a random token on every start and prints it to its log without saving it; `oar setup` writes a stable one |
456
491
  | `port` | `3333` | HTTP + WebSocket port |
457
492
  | `localhostOnly` | `true` | Bind only to loopback (`127.0.0.1`) and disable LAN/WAN access |
458
- | `pollIntervalMs` | `3000` | CLI heartbeat/poll cadence |
493
+ | `dataDir` | `data/` beside the server | Database and uploads; a relative path resolves against the config file's directory. `COPILOT_WEB_RELAY_DATA_DIR` wins, and global installs started by `oar` use `~/.oar/data` |
459
494
  | `processingTimeoutMs` | `600000` | Inactivity window before a turn is treated as stale (not a cap on turn length) |
460
- | `ask_user` timeout | `900000` | `ask_user` question wait; edit `shared/question-timeout.mjs` to change it |
461
- | `conversationSessionMode` | `isolated` | Configured strategy (`isolated` / `shared`) exposed in status |
462
- | `restartGracefulTimeoutMs` | `8000` | Graceful restart wait before force fallback |
463
- | `restartShutdownTimeoutMs` | `45000` | Drain timeout while waiting for active queue job completion |
464
- | `restartSpawnTimeoutMs` | `18000` | Max wait for resume/restart phase per attempt |
465
- | `restartRebindTimeoutMs` | `20000` | Max wait for rebind/session-sync completion per attempt |
466
- | `restartMaxAttempts` | `3` | Bounded restart attempts before terminal exhaustion |
467
- | `restartRetryBackoffMs` | `[1000,3000,7000]` | Deterministic retry backoff schedule in milliseconds |
495
+ | `conversationSessionMode` | `isolated` | Configured strategy (`isolated` / `shared`) recorded on sessions and exposed in status |
468
496
  | `maxRequeueRetries` | `5` | Queue retry limit for failed processing |
469
497
  | `remotePath` | `""` | URL base path when reverse-proxied under a subpath; also drives PWA URLs and socket.io path |
498
+ | `trustProxy` | `"loopback"` | Which proxies may set `X-Forwarded-*` headers (Express `trust proxy`); widen it with a hop count or subnet when a proxy on another host forwards to the relay |
499
+ | `workspaceRootAllowList` | unset | Directories that conversations may start in (they and their subdirectories); unset allows any. `COPILOT_WORKSPACE_ROOT_ALLOW_LIST` sets it too, separated by the platform's path delimiter |
500
+ | `publicHostnames` | `[]` | Hostnames the relay itself answers on; the preview lane refuses to share one |
501
+ | `tunnelMarkerHeaders` | `[]` | Extra edge-injected headers that mark tunnel traffic (see [Session-worker path guard](#session-worker-path-guard)) |
470
502
  | `sshTunnel.mode` | `disabled` | Tunnel mode (`disabled` or `managed`) |
471
503
  | `sshTunnel.enabled` | `false` | Legacy alias (`true` => `managed`) |
472
504
  | `sshTunnel.required` | `false` | Pause dequeue while managed tunnel is disconnected |
@@ -476,24 +508,59 @@ When either trips, the turn is returned to the queue rather than lost.
476
508
  | `sshTunnel.host` | — | SSH host |
477
509
  | `sshTunnel.remotePort` | — | Remote forwarded port |
478
510
  | `sshTunnel.identityFile` | optional | SSH key path (falls back to default agent/key) |
479
- | `cloudflaredTunnel.mode` | `disabled` | Cloudflare tunnel mode (`disabled` or `managed`) |
511
+ | `cloudflaredTunnel.mode` | `disabled` | Cloudflare tunnel mode (`disabled` or `managed`; `enabled: true` is a legacy alias for `managed`) |
480
512
  | `cloudflaredTunnel.required` | `false` | Pause dequeue while the managed Cloudflare tunnel is disconnected |
481
- | `cloudflaredTunnel.token` | — | Tunnel token issued by the router panel |
513
+ | `cloudflaredTunnel.token` | — | The tunnel token from your own Cloudflare Zero Trust tunnel |
482
514
  | `cloudflaredTunnel.binary` | *(auto)* | `cloudflared` path; defaults to the npm package, then `PATH` |
483
515
  | `cloudflaredTunnel.extraArgs` | `[]` | Extra arguments appended to `cloudflared tunnel run` |
484
- | `tunnelMarkerHeaders` | `[]` | Extra edge-injected headers that mark tunnel traffic (see worker-path guard) |
485
- | `publicHostnames` | `[]` | Hostnames the relay itself answers on; the preview lane refuses to share one |
486
- | `previews.enabled` | `false` | Publish local dev servers on a separate listener (see `docs/preview-servers.md`) |
516
+ | `previews.enabled` | `false` | Publish local dev servers on a separate listener (see [docs/preview-servers.md](docs/preview-servers.md)) |
487
517
  | `previews.port` | `port + 1` | Loopback port for the preview listener; `0` picks an ephemeral port |
488
518
  | `previews.bindHost` | `127.0.0.1` | Bind address; non-loopback needs `previews.allowPublicBind` |
489
519
  | `previews.publicBaseUrl` | — | Public base URL on a hostname **different** from the relay's |
490
520
  | `previews.allowedTargetHosts` | `[]` | Upstreams allowed beyond loopback (container/VM IPs) |
491
521
  | `previews.maxLive` | `8` | Maximum simultaneously published previews |
522
+ | `pushVapidSubject` | `mailto:copilot-remote@example.com` | Contact URI the relay sends to push services with Web Push requests |
523
+ | `sharedPresenceMaxPerConversation` | `200` | Most live viewers tracked per shared conversation |
524
+ | `sharedPresenceMaxGlobal` | `5000` | Most live shared-link viewers tracked in total |
525
+ | `cliBinaries` | *(written by the relay)* | Provider CLI paths resolved after an install from Settings, restored at startup |
526
+ | `restartGracefulTimeoutMs` | `8000` | Graceful restart wait before force fallback |
527
+ | `restartShutdownTimeoutMs` | `45000` | Drain timeout while waiting for active queue job completion |
528
+ | `restartSpawnTimeoutMs` | `18000` | Max wait for resume/restart phase per attempt |
529
+ | `restartRebindTimeoutMs` | `20000` | Max wait for rebind/session-sync completion per attempt |
530
+ | `restartMaxAttempts` | `3` | Bounded restart attempts before terminal exhaustion |
531
+ | `restartRetryBackoffMs` | `[1000,3000,7000]` | Deterministic retry backoff schedule in milliseconds |
492
532
 
533
+ ### Environment variables
493
534
 
494
- > Session mismatch recovery is restart-driven: the relay restart orchestrator parks queue work, restarts/rebinds the CLI runtime, and resumes dequeueing after rebind confirmation. The extension no longer attempts in-process session switch APIs from the dequeue/send path.
535
+ | Variable | Effect |
536
+ | -------- | ------ |
537
+ | `COPILOT_WEB_RELAY_CONFIG` | Config file to use |
538
+ | `COPILOT_WEB_RELAY_DATA_DIR` | Data directory (database and uploads) |
539
+ | `COPILOT_WEB_RELAY_LOG_DIR` | Log directory |
540
+ | `OAR_STATE_ROOT` | State root of a global install, instead of `~/.oar` / `%APPDATA%\oar` |
541
+ | `OAR_NO_UPDATE_CHECK=1` | Never contact oar.sh, not even for a manual update check |
542
+ | `COPILOT_SDK_PATH` | The Copilot CLI runtime's `copilot-sdk` directory, instead of the newest one in the CLI's package cache |
543
+ | `COPILOT_PKG_DIR` | An additional Copilot CLI package cache to search |
544
+ | `GH_TOKEN`, `GITHUB_TOKEN` | GitHub token for the Copilot usage card, instead of `gh auth token` |
545
+ | `XAI_API_KEY` | Grok authentication instead of the Grok CLI login |
546
+ | `CURSOR_SESSION_TOKEN` | Cursor dashboard session token for live plan bars |
547
+ | `CLAUDE_CONFIG_DIR` | Claude configuration directory, instead of `~/.claude` |
548
+ | `CLAUDE_CODE_AUTO_COMPACT_WINDOW` | Overrides the Claude auto-compact window |
549
+ | `COPILOT_CLOUDFLARED_MODE`, `COPILOT_CLOUDFLARED_TOKEN`, `COPILOT_CLOUDFLARED_BINARY` | Override `cloudflaredTunnel.mode`, `.token`, and `.binary` |
550
+ | `COPILOT_WORKSPACE_ROOT_ALLOW_LIST` | Same as `workspaceRootAllowList` |
551
+ | `COPILOT_REMOTE_<FLAG>` | Pins a feature flag from the **Features** tab (`1`/`0`, `true`/`false`) |
495
552
 
496
- ## Optional remote internet access (SSH tunnel)
553
+ Many internal names still say "copilot" (`COPILOT_WEB_RELAY_*`, `copilot.db`) from the project's earlier life; they apply to every runtime.
554
+
555
+ ### API overview
556
+
557
+ Every authenticated HTTP route accepts `Authorization: Bearer <token>` or the login cookie; the routes are documented in [server/README.md](server/README.md#api-reference).
558
+
559
+ ## Remote access
560
+
561
+ `localhostOnly` controls only the relay's own listener (`127.0.0.1` vs `0.0.0.0`). To reach the relay from outside your network, prefer one of the two managed tunnels below over opening a port. They are independent and can run at the same time.
562
+
563
+ ### Optional remote internet access (SSH tunnel)
497
564
 
498
565
  Configure:
499
566
 
@@ -509,8 +576,7 @@ Configure:
509
576
  }
510
577
  ```
511
578
 
512
- `localhostOnly` controls only the local relay listener (`127.0.0.1` vs `0.0.0.0`).
513
- SSH tunnel exposure is controlled independently by `sshTunnel.remoteBind`.
579
+ SSH tunnel exposure is controlled by `sshTunnel.remoteBind`, independently of `localhostOnly`.
514
580
 
515
581
  Then reverse proxy on the VPS (example Caddy):
516
582
 
@@ -522,11 +588,17 @@ relay.example.com {
522
588
 
523
589
  The relay auto-reconnects tunnel drops with exponential backoff.
524
590
 
525
- ## Optional remote internet access (Cloudflare Tunnel)
591
+ ### Optional remote internet access (Cloudflare Tunnel)
526
592
 
527
593
  An alternative to the SSH tunnel that needs no VPS and no inbound port: the relay
528
594
  supervises Cloudflare's `cloudflared` binary, and Cloudflare carries your hostname down
529
- to `127.0.0.1:3333`. Both modes are independent and may run at the same time.
595
+ to `127.0.0.1:3333`.
596
+
597
+ 1. In your own Cloudflare Zero Trust account, create a tunnel and route a public hostname on
598
+ your zone to `http://localhost:3333` (or your `port`). That routing lives in Cloudflare, not
599
+ on this machine.
600
+ 2. Put the tunnel's token into the config and restart the relay. (`oar setup` can switch the
601
+ tunnel on for you; it still needs the token.)
530
602
 
531
603
  ```json
532
604
  "cloudflaredTunnel": {
@@ -538,10 +610,8 @@ to `127.0.0.1:3333`. Both modes are independent and may run at the same time.
538
610
  }
539
611
  ```
540
612
 
541
- Hostname-to-machine bindings are managed remotely in the router panel (see the
542
- `cpr-router` project), never configured on this machine — paste the token it gives you and
543
- start the relay. Environment overrides: `COPILOT_CLOUDFLARED_MODE`,
544
- `COPILOT_CLOUDFLARED_TOKEN`, `COPILOT_CLOUDFLARED_BINARY`.
613
+ Environment overrides: `COPILOT_CLOUDFLARED_MODE`, `COPILOT_CLOUDFLARED_TOKEN`,
614
+ `COPILOT_CLOUDFLARED_BINARY`.
545
615
 
546
616
  `localhostOnly` stays `true`: `cloudflared` connects outbound and nothing binds publicly.
547
617
  The binary resolves from `cloudflaredTunnel.binary`, then the optional `cloudflared` npm
@@ -570,148 +640,110 @@ covering `/api/shared/*` — shared views poll for liveness and an edge-cached r
570
640
  would pin viewers to a stale snapshot. Likewise, a Cloudflare Access application over the
571
641
  bound hostname breaks share links unless it bypasses `/shared/*` and `/api/shared/*`.
572
642
 
573
- ## Global extension install (optional)
574
-
575
- Install a user-global extension entrypoint for use across repositories:
576
-
577
- ```text
578
- %USERPROFILE%\.copilot\extensions\web-relay\ (Windows)
579
- ~/.copilot/extensions/web-relay/ (Linux/macOS)
580
- ```
581
-
582
- Recommended command:
583
-
584
- ```bash
585
- oar --install-extension
586
- ```
587
-
588
- This writes/updates `extension.mjs` in the user-global extension directory as a wrapper that imports the repository extension entrypoint directly.
589
- The wrapper also avoids double-loading when you start Copilot from this repository itself, so the
590
- project-local extension remains the single runtime owner in repo-root sessions.
591
-
592
- Useful environment variables:
593
-
594
- - `COPILOT_WEB_RELAY_SERVER_DIR` (recommended)
595
- - `COPILOT_WEB_RELAY_ROOT`
596
- - `COPILOT_WEB_RELAY_CONFIG`
597
- - `COPILOT_WEB_RELAY_TOOLS`
598
- - `COPILOT_WEB_RELAY_LOG_DIR`
599
- - `COPILOT_WEB_RELAY_NODE`
600
-
601
- Project-local extension files still take precedence when both exist.
602
-
603
- If the same extension is available both project-local (`.github/extensions/web-relay/`) and user-global (`~/.copilot/extensions/web-relay/`), Copilot may show duplicates in extension management. Keep only one active copy to avoid double-loading.
643
+ ## Remote relays
644
+
645
+ Several OAR relays can be paired, for example the one on your workstation and the one on a
646
+ server. An agent in any session can then work on the other relay when you ask it to: list and
647
+ read its sessions, prompt one, start a new one, wait for the reply, and pass a question the
648
+ remote agent asks back to you.
649
+
650
+ **Pairing.** Open **Settings → Relays** and paste the other relay's web address (the URL you
651
+ open it with in the browser). OAR checks the address with its own token, or with a
652
+ `?token=` the pasted link carries, and asks for a token only when that fails. It reads the
653
+ other relay's name from its API. With **Also add this relay there** checked, it introduces
654
+ itself to the other relay using the address in **Public address**, so the pairing works in both
655
+ directions. When the two relays use different tokens, that introduction hands over this relay's
656
+ token, so the other relay's agents can then work here. Each side can remove the other on its own,
657
+ but removing a relay does not revoke a token it already holds: rotate the token for that. A relay
658
+ that is already paired keeps the address and token stored for it when it introduces itself again;
659
+ change those in **Settings → Relays**.
660
+
661
+ A relay's name is the **Relay name** under **General** (it also names the installed app); when it
662
+ is empty, the machine's hostname is used.
663
+
664
+ **Mention a relay to unlock it.** Agents can see which relays are paired, but a relay stays
665
+ locked in a conversation until you mention it in one of your messages: type `@` and pick it
666
+ from the list, or write its name or its host name. An IP address or `localhost` never counts as a
667
+ host name; a relay whose name is itself an address is unlocked only with a leading `@`. The
668
+ mention unlocks that relay for the rest of
669
+ the conversation. Prompts that arrive from another relay's agent never unlock anything, and a
670
+ chain of relays prompting relays stops after two hops.
671
+
672
+ **What agents can do.** Every provider gets the same `remote_relay` tool (Claude, Cursor and
673
+ Copilot directly; Grok and the Copilot extension engine through OAR's MCP server). Per paired
674
+ relay, **Agents may** limits it to *read only*, *read and prompt* (existing sessions) or *full*
675
+ (also create sessions, answer questions, stop turns, archive). In the *ask* and *plan* relay
676
+ modes, every write action first asks you with a question card.
677
+
678
+ When an agent prompts a session on another relay, that relay shows a **↗ from …** badge with the
679
+ sending relay, session and model on the message, and marks sessions an agent created in the
680
+ conversation list. The remote agent sees a one-line header saying that another agent is talking
681
+ to it. Turns started this way do not send "reply ready" notifications on the remote relay;
682
+ question cards still do. **Accept prompts from other relays' agents** in **Settings → Relays**
683
+ switches incoming agent prompts off.
684
+
685
+ The calls go through your own relay, which holds the other relay's token: agents never see a
686
+ token. Remote addresses must use `https://`; plain `http://` is accepted only for loopback (an
687
+ SSH port forward, for example) and private network ranges.
604
688
 
605
- ## API overview
606
-
607
- Common routes:
608
-
609
- - Browser/API: `/api/message`, `/api/conversations`, `/api/conversation/:id`, `/api/status`, `/api/models`, `/api/usage`, `/api/context/:conversationId`
610
- - Settings: `/api/settings/openai`, `/api/settings/claude`, `/api/settings/grok`, `/api/settings/cursor`, `/api/settings/copilot`, `/api/settings/turn-ceiling`, `/api/settings/windows-autostart`
611
- - Relay control: `/api/relay/shutdown`, `/api/relay/pause`, `/api/relay/resume`
612
- - Worker bridge: `/api/pending`, `/api/response`, `/api/activity`, `/api/stream`, `/api/thought`, `/api/heartbeat`
613
- - Claude worker: `/api/claude-native-session`, `/api/claude-context-usage`, `/api/claude-plan-usage`
614
- - Claude account auth: `/api/claude/auth/status`, `/api/claude/auth/login/start`, `/api/claude/auth/login/code`, `/api/claude/auth/login/cancel`, `/api/claude/auth/logout`
615
- - Grok account auth: `/api/grok/auth/status`, `/api/grok/auth/login/start`, `/api/grok/auth/login/cancel`, `/api/grok/auth/logout`
616
- - Provider CLI install: `/api/cli/status`, `/api/cli/install`, `/api/cli/install/cancel`
617
- - Cursor worker: `/api/cursor-agent-id`, `/api/cursor-context-usage`, `/api/cursor-plan-usage`
618
- - Questions: `/api/relay-question`, `/api/relay-question/:id`, `/api/relay-question/:id/answer`
619
- - Sharing: `/api/conversation/:id/share`, `/api/conversation/:id/message/:messageId/share-visibility`, `/api/shared/:token`
620
- - Images: `/api/openai/images/generate`, `/api/image-operations/:operationId/execute`, `/api/generated-image/:conversationId/:messageId/:imageId/content`
621
- - File access: `/api/files/*`, `/api/files-preview/*`, `/api/repo/tree`, `/api/drives/*`
622
- - Git: `/api/git/status`, `/api/git/diff`, `/api/git/pull`
623
- - Previews: `/api/previews`, `/api/previews/:token` (publish a local dev server; see `docs/preview-servers.md`)
624
- - Uploads: `/api/upload`, `/api/upload/:sha256/content`
625
-
626
- All authenticated routes accept either:
627
-
628
- - `Authorization: Bearer <token>`
629
- - auth cookie from prior login
689
+ ## Security notes
630
690
 
631
- For deeper implementation/API details, see `[server/README.md](server/README.md)`.
691
+ - The auth token guards the API and the Socket.IO channel. A browser signs in once, with the token or with a URL carrying `?token=` (such as the one `oar setup` prints and puts in its QR code), and then holds an HttpOnly cookie for 30 days, marked `Secure` when the relay is reached over HTTPS. Treat the token, and that URL, like a password.
692
+ - Keep the config file private (the relay keeps it owner-only) and rotate `authToken` if it is exposed: `oar setup` offers a new token, which signs every device out.
693
+ - `localhostOnly` (default `true`) keeps the relay on loopback. Beyond your LAN, use HTTPS through one of the [tunnels](#remote-access) rather than an open port.
694
+ - Agents act with the permissions of the user the relay runs as. OAR adds reach, not a sandbox: whoever holds the token can have them run commands on the host. `workspaceRootAllowList` limits the directories conversations may start in.
695
+ - Shared conversation links and preview links are public by design: anyone with the URL can read the shared transcript or reach the previewed app, without signing in.
696
+ - A [paired relay](#remote-relays) holds a token that opens the other relay completely. The per-relay "Agents may" limit and the "Accept prompts from other relays' agents" switch are honoured by OAR's own traffic, but anyone holding the token can still use the full API: the token is the real boundary. A token entered for a paired relay is stored like the API keys below and never sent to browsers or agents.
697
+ - API keys you enter (OpenAI, Cursor) are stored in the relay database on the host and never sent to browsers. Claude and Grok credentials stay with their CLIs on the host.
698
+ - OAR has no telemetry. Its only request to oar.sh is the update check, which is off until you enable it.
632
699
 
633
700
  ## Troubleshooting
634
701
 
635
-
636
702
  | Symptom | What to check |
637
703
  | ---------------------------------- | -------------------------------------------------------------------------------- |
638
- | UI says CLI offline | Send one CLI prompt to trigger extension session start, then check `/api/status` |
639
- | Messages stuck pending | Ensure only one relay owner is running and only one process owns port `3333` |
640
- | Wrong/old model shown | Check `/api/models` and extension logs for model snapshot updates |
641
- | Clarification card not progressing | Answer via the web card; relay resumes after question status becomes `answered` |
704
+ | Banner says *CLI is offline* | No session worker has reported in during the last 10 seconds, which can be normal on an idle relay. Send a message: the relay starts that conversation's worker, and the banner clears once it connects. If it stays, check `/api/status` and the worker's `worker-<session>.log` in the log directory |
705
+ | `oar` exits with *Copilot CLI process error: spawn gh ENOENT* | `oar` runs `gh copilot` after starting the relay, and stops the relay again when that fails. Install the GitHub CLI, or [run only the relay](#start-the-relay) |
706
+ | Settings refuses the **SDK** engine | Install or upgrade the Copilot CLI, run `copilot` once, and restart the relay (see [engine choice](#github-copilot--engine-choice)) |
707
+ | Messages stuck pending | Only one relay may run per data directory (a second one exits on the singleton lock), and only one process may own port `3333`. `oar doctor` shows the config and database in use |
708
+ | Wrong or old model list | **🤗 Select Models → Refresh** reruns discovery; `/api/model-variants` shows the Copilot catalog's `source` and `refreshedAt` |
709
+ | Clarification card not progressing | Answer via the web card; the turn resumes once the question status becomes `answered` |
642
710
  | File links fail | Verify auth token/cookie and that paths are inside allowed workspace/drive roots |
643
711
  | Claude missing from New Chat | Enable it in **⚙️ Settings → Providers → Claude**; the toggle is off by default |
644
712
  | Claude reply says it cannot authenticate | Press **Claude settings** on the failed reply, then **Relogin** (or run `claude` on the relay host), and retry the turn |
645
713
  | Grok reply says the CLI was not found | Press **Install Grok CLI** on the failed reply, or install it from **⚙️ Settings → Providers → Grok**; no relay restart is needed afterwards |
646
714
  | Grok reply says authentication failed | Press **Sign in to Grok** on the failed reply and confirm the device code in a browser |
647
- | Claude model list empty or stale | Re-save the Claude settings, or use **Select Models → Refresh** to rerun discovery |
648
715
  | Long turn requeued unexpectedly | Raise or clear **Max turn duration** in Settings (0 = no limit) |
649
- | No usage line under a reply | Expected for OpenAI, Claude, and Cursor turns; only Copilot turns record plan usage |
650
-
651
-
652
- ## Security notes
653
-
654
- - Auth is token-based and enforced on API + Socket.IO.
655
- - Successful auth sets an HttpOnly cookie for browser sessions.
656
- - Keep `server/config.json` private and rotate `authToken` if exposed.
657
- - Set `localhostOnly` to `true` to force local-only access (no LAN/WAN listener).
658
- - If exposed beyond LAN, use HTTPS and a strong token.
716
+ | Background agent or shell stopped on its own | It reached the **Background task timeout** (default 4 h); raise it or set 0 for no limit |
717
+ | A conversation will not delete | It is still working: a running turn, one waiting on a question card or approval, or live background tasks. Stop it, then delete |
718
+ | A conversation seems wedged | **☠️ Kill session** in the `⋯` menu stops its worker; retry the turn or send a new message |
719
+ | **🌄 Restart web relay** fails with *localhost-only* | The restart endpoint accepts loopback connections only: use it on the relay host or through a tunnel, not over a direct LAN connection |
720
+ | `npm install -g @oar-sh/oar` fails with node-gyp or prebuild errors | `better-sqlite3` has no prebuilt binary for your platform; install a C/C++ build toolchain and Python, then retry |
721
+ | No usage line under a reply | Expected for OpenAI, Claude, Cursor, and Grok turns; only Copilot turns record plan usage |
659
722
 
660
723
  ## Repository layout
661
724
 
662
725
  ```text
663
726
  oar/
664
- ├── .github/extensions/web-relay/ # Copilot CLI extension (worker WebSocket, ask_user bridge, model snapshotting)
727
+ ├── .github/extensions/web-relay/ # Copilot CLI extension for the Extension engine (worker link, ask_user bridge, model snapshots)
728
+ ├── bin/ # The `oar` command (oar.js) and a Windows cmd shim for checkouts (bat/oar.bat)
729
+ ├── docs/ # Preview-server guide, Copilot BYOK notes, SDK feature tracker
665
730
  ├── server/
666
731
  │ ├── claude-worker/ # Claude Agent SDK session worker (turn runner, ask-user bridge, attachments)
732
+ │ ├── copilot-worker/ # Copilot SDK engine session worker (steering, background tasks, questions)
667
733
  │ ├── cursor-worker/ # Cursor Agent SDK session worker (turn runner, mode nudges, auth retry)
668
- │ ├── public/app/ # Browser app modules
734
+ │ ├── grok-worker/ # Grok CLI session worker over ACP
735
+ │ ├── migrations/ # Database migrations
736
+ │ ├── public/ # Browser app (index.html, app/ modules, PWA shell)
737
+ │ ├── repositories/ # SQLite data access
669
738
  │ ├── routes/ # Express route registration
670
- │ ├── services/ # Relay services (workers, context usage, images, tunnels)
671
- │ └── server.js # Express + Socket.IO relay server
672
- ├── shared/ # Code shared by server, extension, and workers
673
- ├── docs/ # Project planning notes
739
+ │ ├── services/ # Relay services (workers, usage, images, tunnels, previews, updates)
740
+ │ ├── tools/ # Diagnostic tools (OpenAI BYOK capture proxy)
741
+ │ ├── server.js # Entry point: supervisor plus relay runtime
742
+ │ └── server-runtime.mjs # Express + Socket.IO relay server
743
+ ├── shared/ # Code shared by the server, the extension, and the workers
744
+ │ └── worker-runtime/ # Common worker plumbing (relay API client, heartbeat, worker link)
745
+ ├── tests/ # Playwright end-to-end suite and its isolated relay harness
746
+ ├── CHANGELOG.md
747
+ ├── DEVELOPING.md # Development workflows, tests, relay internals
674
748
  └── README.md
675
749
  ```
676
-
677
- ## Extra screenshots
678
-
679
- More views from the same app experience:
680
-
681
- <div align="center">
682
- <table width="100%" cellspacing="0" cellpadding="0">
683
- <tr>
684
- <td rowspan="2" width="50%" align="center" valign="middle">
685
- <a href="docs/screenshots/mobile_session_input.jpg" target="_blank" rel="noopener noreferrer">
686
- <img src="docs/screenshots/mobile_session_input.jpg" alt="Mobile session composer">
687
- </a>
688
- <div align="center"><small>Mobile chat composer in portrait, with the keyboard open and the input ready to send.</small></div>
689
- </td>
690
- <td width="50%" align="right" valign="top">
691
- <a href="docs/screenshots/desktop_pwa_chrome_portrait.png" target="_blank" rel="noopener noreferrer">
692
- <img src="docs/screenshots/desktop_pwa_chrome_portrait.png" alt="Desktop PWA portrait view">
693
- </a>
694
- <div align="center"><small>Desktop PWA running in portrait mode, sized for a narrow browser window.</small></div>
695
- </td>
696
- </tr>
697
- <tr>
698
- <td width="50%" align="right" valign="bottom">
699
- <a href="docs/screenshots/desktop_pwa_workspace_file_explorer.png" target="_blank" rel="noopener noreferrer">
700
- <img src="docs/screenshots/desktop_pwa_workspace_file_explorer.png" alt="Workspace file explorer">
701
- </a>
702
- <div align="center"><small>Workspace file explorer with folder browsing and file previews.</small></div>
703
- </td>
704
- </tr>
705
- </table>
706
-
707
- <table width="50%" cellspacing="0" cellpadding="0">
708
- <tr>
709
- <td align="center">
710
- <a href="docs/screenshots/mobile_file_viewer.jpg" target="_blank" rel="noopener noreferrer">
711
- <img src="docs/screenshots/mobile_file_viewer.jpg" alt="Mobile file viewer">
712
- </a>
713
- <div align="center"><small>Mobile (PWA/Browser) file viewer showing an image preview and download actions.</small></div>
714
- </td>
715
- </tr>
716
- </table>
717
- </div>