@oar-sh/oar 0.9.0
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.
- package/.github/extensions/web-relay/README.md +190 -0
- package/.github/extensions/web-relay/extension.mjs +972 -0
- package/.github/extensions/web-relay/model-api/model-switching.mjs +338 -0
- package/.github/extensions/web-relay/polling/heartbeat.mjs +55 -0
- package/.github/extensions/web-relay/polling/polling-loop.mjs +1105 -0
- package/.github/extensions/web-relay/runtime/api-client.mjs +34 -0
- package/.github/extensions/web-relay/runtime/banner-state.mjs +126 -0
- package/.github/extensions/web-relay/runtime/config-loader.mjs +104 -0
- package/.github/extensions/web-relay/runtime/debug-log.mjs +27 -0
- package/.github/extensions/web-relay/runtime/send-and-wait-errors.mjs +170 -0
- package/.github/extensions/web-relay/runtime/session-binding.mjs +78 -0
- package/.github/extensions/web-relay/runtime/session-io.mjs +349 -0
- package/.github/extensions/web-relay/runtime/session-join-retry.mjs +44 -0
- package/.github/extensions/web-relay/runtime/session-registry.mjs +29 -0
- package/.github/extensions/web-relay/runtime/session-runtime-manager.mjs +156 -0
- package/.github/extensions/web-relay/runtime/session-sync-bridge.mjs +74 -0
- package/.github/extensions/web-relay/runtime/worker-websocket-link.mjs +325 -0
- package/.github/extensions/web-relay/runtime/workspace-root.mjs +55 -0
- package/.github/extensions/web-relay/server-lifecycle/managed-server.mjs +386 -0
- package/.github/extensions/web-relay/skills/prompt-context.mjs +160 -0
- package/.github/extensions/web-relay/skills/question-bridge.mjs +141 -0
- package/.github/extensions/web-relay/skills/question-routing-hooks.mjs +463 -0
- package/.github/extensions/web-relay/skills/reasoning-stream.mjs +285 -0
- package/.github/extensions/web-relay/skills/subagent-lifecycle.mjs +345 -0
- package/.github/extensions/web-relay/skills/tool-activity.mjs +243 -0
- package/.github/extensions/web-relay/utils/process-utils.mjs +25 -0
- package/.github/extensions/web-relay/utils/token-generation.mjs +58 -0
- package/CHANGELOG.md +25 -0
- package/LICENSE +21 -0
- package/README.md +717 -0
- package/bin/bat/oar.bat +46 -0
- package/bin/oar.js +665 -0
- package/package.json +68 -0
- package/server/README.md +1379 -0
- package/server/claude-worker/claude-attachments.mjs +134 -0
- package/server/claude-worker/claude-preview-tool.mjs +57 -0
- package/server/claude-worker/claude-sdk-adapter.mjs +420 -0
- package/server/claude-worker/claude-session-process.mjs +2391 -0
- package/server/claude-worker/claude-session-test-harness.mjs +236 -0
- package/server/claude-worker/claude-session-worker.mjs +186 -0
- package/server/claude-worker/claude-transcript-relocator.mjs +189 -0
- package/server/claude-worker/claude-turn-publisher.mjs +291 -0
- package/server/claude-worker/fixtures/workflow-run-record.json +1 -0
- package/server/claude-worker/sdk-message-normalizer.mjs +629 -0
- package/server/claude-worker/workflow-progress-digest.mjs +269 -0
- package/server/copilot-sdk-runtime.mjs +126 -0
- package/server/copilot-worker/copilot-attachments.mjs +97 -0
- package/server/copilot-worker/copilot-byok-provider.mjs +121 -0
- package/server/copilot-worker/copilot-continuation-signals.mjs +349 -0
- package/server/copilot-worker/copilot-plan-board.mjs +110 -0
- package/server/copilot-worker/copilot-prompt-context.mjs +86 -0
- package/server/copilot-worker/copilot-question-bridge.mjs +216 -0
- package/server/copilot-worker/copilot-sdk-adapter.mjs +494 -0
- package/server/copilot-worker/copilot-sdk-event-normalizer.mjs +800 -0
- package/server/copilot-worker/copilot-sdk-session-process.mjs +1729 -0
- package/server/copilot-worker/copilot-sdk-session-worker.mjs +167 -0
- package/server/copilot-worker/copilot-sdk-test-harness.mjs +329 -0
- package/server/copilot-worker/fixtures/abort-turn.json +588 -0
- package/server/copilot-worker/fixtures/ask-user-turn.json +1154 -0
- package/server/copilot-worker/fixtures/background-timer-continuation.json +445 -0
- package/server/copilot-worker/fixtures/background-timer-turn.json +497 -0
- package/server/copilot-worker/fixtures/happy-turn.json +566 -0
- package/server/copilot-worker/fixtures/quota-turn.json +647 -0
- package/server/copilot-worker/fixtures/reasoning-turn.json +625 -0
- package/server/copilot-worker/fixtures/resume-turn.json +700 -0
- package/server/copilot-worker/fixtures/subagent-turn.json +1378 -0
- package/server/copilot-worker/fixtures/tool-permission-turn.json +1293 -0
- package/server/cursor-worker/cursor-ask-user-tool.mjs +83 -0
- package/server/cursor-worker/cursor-attachments.mjs +88 -0
- package/server/cursor-worker/cursor-context-usage.mjs +85 -0
- package/server/cursor-worker/cursor-preview-tool.mjs +44 -0
- package/server/cursor-worker/cursor-sdk-adapter.mjs +554 -0
- package/server/cursor-worker/cursor-session-worker.mjs +145 -0
- package/server/cursor-worker/cursor-subagent-roster.mjs +81 -0
- package/server/cursor-worker/cursor-turn-runner.mjs +717 -0
- package/server/cursor-worker/sdk-message-normalizer.mjs +485 -0
- package/server/db-schema.mjs +932 -0
- package/server/features.mjs +106 -0
- package/server/grok-worker/acp-client.mjs +434 -0
- package/server/grok-worker/acp-host-services.mjs +277 -0
- package/server/grok-worker/grok-context-usage.mjs +77 -0
- package/server/grok-worker/grok-sdk-adapter.mjs +334 -0
- package/server/grok-worker/grok-session-worker.mjs +145 -0
- package/server/grok-worker/grok-turn-runner.mjs +523 -0
- package/server/grok-worker/sdk-message-normalizer.mjs +311 -0
- package/server/migrations/0001-add-structured-answer.mjs +80 -0
- package/server/migrations/0002-recent-workspace-roots-path-key.mjs +121 -0
- package/server/migrations/0003-push-subscriptions.mjs +87 -0
- package/server/public/app/action-confirmations.js +267 -0
- package/server/public/app/activity-replay-state.mjs +149 -0
- package/server/public/app/api-client.js +1082 -0
- package/server/public/app/ask-user-view.js +545 -0
- package/server/public/app/attachments-view.js +2112 -0
- package/server/public/app/auto-compact-window-options.mjs +59 -0
- package/server/public/app/background-tasks-view.mjs +494 -0
- package/server/public/app/bootstrap.js +4347 -0
- package/server/public/app/claude-auth-ui.js +394 -0
- package/server/public/app/claude-thinking-options.mjs +38 -0
- package/server/public/app/cli-install-ui.js +455 -0
- package/server/public/app/code-copy.mjs +85 -0
- package/server/public/app/composer-attachment-cache.mjs +151 -0
- package/server/public/app/composer-control-state.mjs +107 -0
- package/server/public/app/composer-paste.mjs +132 -0
- package/server/public/app/context-tier-options.mjs +68 -0
- package/server/public/app/context-usage-view.mjs +338 -0
- package/server/public/app/conversation-draft-timestamp-utils.mjs +11 -0
- package/server/public/app/conversation-preferences.mjs +91 -0
- package/server/public/app/conversation-provider-indicator.mjs +88 -0
- package/server/public/app/conversation-view.js +2898 -0
- package/server/public/app/cwd-menu-placement.mjs +85 -0
- package/server/public/app/cwd-picker.js +605 -0
- package/server/public/app/emoji-view.js +101 -0
- package/server/public/app/external-link-policy.mjs +80 -0
- package/server/public/app/file-preview-navigation.mjs +51 -0
- package/server/public/app/font-scaling.js +262 -0
- package/server/public/app/git-changes-view.js +339 -0
- package/server/public/app/git-diff-model.mjs +109 -0
- package/server/public/app/grok-auth-ui.js +387 -0
- package/server/public/app/image-downscale.mjs +174 -0
- package/server/public/app/in-flight-snapshot.mjs +40 -0
- package/server/public/app/infinite-loader.js +164 -0
- package/server/public/app/journal-view.js +1141 -0
- package/server/public/app/known-cwd-options.mjs +41 -0
- package/server/public/app/launch-cwd-path.mjs +23 -0
- package/server/public/app/live-message-dedupe.mjs +57 -0
- package/server/public/app/message-search-view.js +242 -0
- package/server/public/app/model-selector-options.mjs +31 -0
- package/server/public/app/model-vision-support.mjs +56 -0
- package/server/public/app/new-conversation-model-choice.mjs +67 -0
- package/server/public/app/plan-usage-view.mjs +307 -0
- package/server/public/app/preview-cards.mjs +273 -0
- package/server/public/app/preview-command.mjs +103 -0
- package/server/public/app/push-settings.js +370 -0
- package/server/public/app/pwa-install.js +456 -0
- package/server/public/app/question-schema-view.mjs +71 -0
- package/server/public/app/reasoning-effort-labels.mjs +42 -0
- package/server/public/app/relay-board-view.js +117 -0
- package/server/public/app/relay-dot-state.mjs +69 -0
- package/server/public/app/relay-error-ctas.mjs +52 -0
- package/server/public/app/relay-prompt-sanitizer.mjs +114 -0
- package/server/public/app/relay-thoughts.mjs +98 -0
- package/server/public/app/relay-watchdog-policy.mjs +29 -0
- package/server/public/app/repo-browser-preferences.mjs +79 -0
- package/server/public/app/repo-browser-tree-state.mjs +97 -0
- package/server/public/app/router.js +657 -0
- package/server/public/app/selection-guard.mjs +93 -0
- package/server/public/app/settings-modal.js +1484 -0
- package/server/public/app/settings-tabs.js +129 -0
- package/server/public/app/slash-autocomplete.mjs +188 -0
- package/server/public/app/slash-commands.mjs +183 -0
- package/server/public/app/socket-handlers.js +703 -0
- package/server/public/app/status-store.mjs +242 -0
- package/server/public/app/status-view.mjs +315 -0
- package/server/public/app/store.js +1454 -0
- package/server/public/app/stream-state.mjs +73 -0
- package/server/public/app/streaming-dom-patch.mjs +23 -0
- package/server/public/app/sync-outbox.mjs +208 -0
- package/server/public/app/tap-actions.js +62 -0
- package/server/public/app/thread-order.mjs +54 -0
- package/server/public/app/tmux-inspector-view.js +422 -0
- package/server/public/app/transcript-separators.mjs +282 -0
- package/server/public/app-icon-192.png +0 -0
- package/server/public/app-icon-512.png +0 -0
- package/server/public/app-icon.svg +4 -0
- package/server/public/favicon.ico +0 -0
- package/server/public/index.html +5743 -0
- package/server/public/manifest.webmanifest +32 -0
- package/server/public/sw.js +307 -0
- package/server/relay-exit-codes.mjs +4 -0
- package/server/relay-self-restart.mjs +170 -0
- package/server/relay-tools.md +27 -0
- package/server/relay.mjs +1026 -0
- package/server/repositories/image-conversation-repository.mjs +317 -0
- package/server/repositories/message-repository.mjs +434 -0
- package/server/repositories/question-repository.mjs +161 -0
- package/server/repositories/session-repository.mjs +320 -0
- package/server/repositories/share-visibility-statements.mjs +24 -0
- package/server/routes/ask-user-routes.mjs +292 -0
- package/server/routes/cache-routes.mjs +44 -0
- package/server/routes/git-routes.mjs +92 -0
- package/server/routes/messages-routes-test-harness.mjs +111 -0
- package/server/routes/messages-routes.mjs +7107 -0
- package/server/routes/preview-routes.mjs +108 -0
- package/server/routes/push-routes.mjs +59 -0
- package/server/routes/relay-board-routes.mjs +268 -0
- package/server/routes/sessions-routes.mjs +6150 -0
- package/server/server-runtime.mjs +7463 -0
- package/server/server.js +24 -0
- package/server/services/ask-user-routing-service.mjs +117 -0
- package/server/services/cache-rebuild-service.mjs +236 -0
- package/server/services/claude-auth-service.mjs +578 -0
- package/server/services/claude-context-usage.mjs +240 -0
- package/server/services/claude-session-root-service.mjs +228 -0
- package/server/services/cli-install-service.mjs +1152 -0
- package/server/services/cli-process-runner.mjs +181 -0
- package/server/services/cloudflared-tunnel-service.mjs +358 -0
- package/server/services/context-snapshot-service.mjs +499 -0
- package/server/services/context-usage-view.mjs +160 -0
- package/server/services/conversation-preferences-service.mjs +208 -0
- package/server/services/cursor-dashboard-usage.mjs +217 -0
- package/server/services/cursor-session-root-service.mjs +62 -0
- package/server/services/delete-archive-service.mjs +154 -0
- package/server/services/drives-path-helpers.mjs +66 -0
- package/server/services/fixtures/claude-auth-stub.sh +62 -0
- package/server/services/fixtures/cli-install-stub.sh +75 -0
- package/server/services/fixtures/grok-stub.sh +86 -0
- package/server/services/generated-image-cleanup-service.mjs +114 -0
- package/server/services/git-changes-service.mjs +195 -0
- package/server/services/github-billing-usage.mjs +133 -0
- package/server/services/grok-auth-service.mjs +593 -0
- package/server/services/grok-billing-usage.mjs +120 -0
- package/server/services/image-context-service.mjs +106 -0
- package/server/services/image-operation-service.mjs +135 -0
- package/server/services/keyed-mutex-service.mjs +82 -0
- package/server/services/mime-sniffer.mjs +118 -0
- package/server/services/oar-cli-helpers.mjs +109 -0
- package/server/services/oar-state-migration-service.mjs +243 -0
- package/server/services/plan-usage-claude.mjs +387 -0
- package/server/services/plan-usage-contract.mjs +271 -0
- package/server/services/plan-usage-copilot.mjs +421 -0
- package/server/services/plan-usage-cursor.mjs +566 -0
- package/server/services/plan-usage-grok.mjs +436 -0
- package/server/services/plan-usage-service.mjs +487 -0
- package/server/services/preview-health-probe.mjs +82 -0
- package/server/services/preview-proxy-server.mjs +498 -0
- package/server/services/preview-registry-service.mjs +457 -0
- package/server/services/preview-static-handler.mjs +237 -0
- package/server/services/provider-model-selection.mjs +91 -0
- package/server/services/provider-reasoning-effort.mjs +94 -0
- package/server/services/push-active-device-service.mjs +57 -0
- package/server/services/push-dispatch-service.mjs +459 -0
- package/server/services/rate-limit-service.mjs +63 -0
- package/server/services/relay-bridge-owner-service.mjs +84 -0
- package/server/services/relay-cli-launcher-service.mjs +209 -0
- package/server/services/relay-prompt-sanitizer.mjs +137 -0
- package/server/services/relay-queue-gate-service.mjs +38 -0
- package/server/services/relay-restart-orchestrator-service.mjs +935 -0
- package/server/services/relay-singleton-guard.mjs +124 -0
- package/server/services/safe-served-content.mjs +57 -0
- package/server/services/sdk-session-import-service.mjs +271 -0
- package/server/services/sdk-session-sync-service.mjs +207 -0
- package/server/services/session-history-refresh-service.mjs +497 -0
- package/server/services/session-transcript-service.mjs +419 -0
- package/server/services/session-worker-launch-service.mjs +786 -0
- package/server/services/session-worker-process-service.mjs +319 -0
- package/server/services/session-worker-registry-service.mjs +194 -0
- package/server/services/session-worker-stop-service.mjs +141 -0
- package/server/services/session-worker-supervisor-service.mjs +898 -0
- package/server/services/session-worker-websocket-service.mjs +483 -0
- package/server/services/ssh-tunnel-manager-service.mjs +423 -0
- package/server/services/status-event-service.mjs +158 -0
- package/server/services/tmux-inspector-access-policy.mjs +77 -0
- package/server/services/tmux-inspector-socket-service.mjs +191 -0
- package/server/services/tmux-inspector-stream-service.mjs +552 -0
- package/server/services/tunnel-worker-path-guard.mjs +107 -0
- package/server/services/upload-sweep.mjs +60 -0
- package/server/services/usage-snapshot-helpers.mjs +209 -0
- package/server/services/windows-autostart-service.mjs +115 -0
- package/server/services/workspace-root-defaults-service.mjs +34 -0
- package/server/services/workspace-root-path-policy.mjs +196 -0
- package/server/services/workspace-root-relaunch-service.mjs +132 -0
- package/server/services/workspace-symlink-guard.mjs +54 -0
- package/server/tools/capture-proxy.mjs +173 -0
- package/server/tty-console-bootstrap.mjs +75 -0
- package/server/uploads/README.md +36 -0
- package/server/uploads/image_input.md +540 -0
- package/server/windows-terminal-launcher.mjs +29 -0
- package/server/workspace-root.mjs +87 -0
- package/shared/ask-user-bridge.mjs +131 -0
- package/shared/auto-compact-window.mjs +116 -0
- package/shared/background-task-timeout.mjs +52 -0
- package/shared/claude-thinking.mjs +101 -0
- package/shared/context-window-fallbacks.mjs +148 -0
- package/shared/control-poller.mjs +115 -0
- package/shared/empty-turn-completion.mjs +20 -0
- package/shared/model-catalog-freshness.mjs +11 -0
- package/shared/model-descriptors.mjs +118 -0
- package/shared/model-id.mjs +147 -0
- package/shared/model-refresh.mjs +24 -0
- package/shared/openai-reasoning.mjs +13 -0
- package/shared/plan-lines.mjs +13 -0
- package/shared/preview-instructions.mjs +77 -0
- package/shared/preview-tool-core.mjs +191 -0
- package/shared/provider-routing.mjs +29 -0
- package/shared/question-schema.mjs +315 -0
- package/shared/question-timeout.mjs +18 -0
- package/shared/stream-emit-gating.mjs +28 -0
- package/shared/subagent-run-id.mjs +17 -0
- package/shared/thought-cap.mjs +13 -0
- package/shared/tool-activity.mjs +98 -0
- package/shared/turn-ceiling.mjs +53 -0
- package/shared/worker-bootstrap.mjs +44 -0
- package/shared/worker-crash-guard.mjs +60 -0
package/README.md
ADDED
|
@@ -0,0 +1,717 @@
|
|
|
1
|
+
# OAR — Open Agent Relay
|
|
2
|
+
|
|
3
|
+
Drive your local coding agents from any browser (phone, tablet, or second computer) through a self-hosted web relay.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
curl -fsSL oar.sh/install | sh
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Landing page and installers: [oar.sh](https://oar.sh). From a git checkout, the Quick start below still applies unchanged.
|
|
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.
|
|
12
|
+
|
|
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)
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## In action
|
|
20
|
+
|
|
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.
|
|
22
|
+
|
|
23
|
+
<div align="center">
|
|
24
|
+
<table width="100%" cellspacing="0" cellpadding="0">
|
|
25
|
+
<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>
|
|
31
|
+
</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>
|
|
37
|
+
</td>
|
|
38
|
+
</tr>
|
|
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>
|
|
45
|
+
</td>
|
|
46
|
+
</tr>
|
|
47
|
+
</table>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
## What this repository provides
|
|
51
|
+
|
|
52
|
+
OAR is split into three pieces:
|
|
53
|
+
|
|
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
|
+
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
## Project Status
|
|
61
|
+
|
|
62
|
+
OAR is still under active development, so expect occasional rough edges and some provider SDK features to be missing or incomplete for now.
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
## Highlights
|
|
66
|
+
|
|
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
|
|
90
|
+
|
|
91
|
+
## Prerequisites
|
|
92
|
+
|
|
93
|
+
Only Node.js and the runtime you actually intend to use are required.
|
|
94
|
+
|
|
95
|
+
|
|
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 |
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
## Quick start
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
git clone https://github.com/oar-sh/oar
|
|
110
|
+
cd oar
|
|
111
|
+
npm install
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Create `server/config.json`:
|
|
115
|
+
|
|
116
|
+
```json
|
|
117
|
+
{
|
|
118
|
+
"authToken": "change-me",
|
|
119
|
+
"port": 3333,
|
|
120
|
+
"localhostOnly": true,
|
|
121
|
+
"pollIntervalMs": 3000,
|
|
122
|
+
"processingTimeoutMs": 600000,
|
|
123
|
+
"conversationSessionMode": "isolated"
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Start Copilot with the relay extension:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
npm run copilot:relay
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
If you installed the extension globally in `~/.copilot/extensions/web-relay/`, you can also start plain Copilot from any repository:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
gh copilot
|
|
137
|
+
```
|
|
138
|
+
|
|
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.
|
|
140
|
+
|
|
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.
|
|
142
|
+
|
|
143
|
+
Open:
|
|
144
|
+
|
|
145
|
+
```text
|
|
146
|
+
http://<your-pc-ip>:3333/
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
When `localhostOnly` is `true`, use `http://localhost:3333/` from the same machine.
|
|
150
|
+
|
|
151
|
+
Sign in once with your token. The relay then uses an HttpOnly auth cookie.
|
|
152
|
+
|
|
153
|
+
For day-to-day development workflows, relay restart steps, and worker debugging notes, see [`DEVELOPING.md`](./DEVELOPING.md).
|
|
154
|
+
|
|
155
|
+
## Runtime modes and startup commands
|
|
156
|
+
|
|
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 |
|
|
163
|
+
|
|
164
|
+
### The single server entry point
|
|
165
|
+
|
|
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:
|
|
169
|
+
|
|
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.
|
|
177
|
+
|
|
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.
|
|
181
|
+
|
|
182
|
+
### Single runtime owner rule
|
|
183
|
+
|
|
184
|
+
Run only one relay owner at a time:
|
|
185
|
+
|
|
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.
|
|
188
|
+
|
|
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.
|
|
194
|
+
|
|
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.
|
|
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.
|
|
200
|
+
|
|
201
|
+
Do not restart the relay by killing processes; use `POST /api/relay/shutdown` instead.
|
|
202
|
+
|
|
203
|
+
Manual relay control details:
|
|
204
|
+
|
|
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)
|
|
213
|
+
|
|
214
|
+
### Global npm command (Windows first)
|
|
215
|
+
|
|
216
|
+
You can install the repo locally and get a global `oar` command without publishing:
|
|
217
|
+
|
|
218
|
+
```powershell
|
|
219
|
+
npm link
|
|
220
|
+
# or
|
|
221
|
+
npm install -g .
|
|
222
|
+
```
|
|
223
|
+
|
|
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.
|
|
225
|
+
|
|
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.
|
|
227
|
+
|
|
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.
|
|
229
|
+
|
|
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.
|
|
231
|
+
|
|
232
|
+
Roadmap for later launcher modes:
|
|
233
|
+
|
|
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.
|
|
237
|
+
|
|
238
|
+
## Using the web UI
|
|
239
|
+
|
|
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.
|
|
263
|
+
|
|
264
|
+
## Providers
|
|
265
|
+
|
|
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.
|
|
267
|
+
|
|
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.
|
|
269
|
+
|
|
270
|
+
|
|
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 |
|
|
279
|
+
|
|
280
|
+
### Installing a provider CLI from the relay
|
|
281
|
+
|
|
282
|
+
Grok and Claude both run as CLIs on the relay host, and the failure mode used to be a dead end: a
|
|
283
|
+
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:
|
|
285
|
+
|
|
286
|
+
```
|
|
287
|
+
Grok CLI not installed [ Install ]
|
|
288
|
+
Grok CLI 1.0.13 · ~/.grok/bin/grok · native · up to date [ Update ]
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
- **Install** asks for confirmation first, naming the exact command and the directory it writes into.
|
|
292
|
+
The commands are the vendors' own one-liners (`curl -fsSL https://x.ai/cli/install.sh | bash`,
|
|
293
|
+
`curl -fsSL https://claude.ai/install.sh | bash`, or their PowerShell equivalents on Windows) and
|
|
294
|
+
they are hardcoded in the relay — nothing you type reaches a shell. They run as the relay user,
|
|
295
|
+
into your home directory, never under sudo.
|
|
296
|
+
- The output streams into the panel live, and the log survives closing the modal or watching from
|
|
297
|
+
another device. One install at a time, relay-wide.
|
|
298
|
+
- When it finishes, the relay resolves the binary, wires it into the environment it launches workers
|
|
299
|
+
with, and remembers it across restarts — **no relay restart is needed**. Sessions already running
|
|
300
|
+
keep the binary they started with.
|
|
301
|
+
- **Update** runs the CLI's own updater (`grok update`, `claude update`), not the install script again.
|
|
302
|
+
- If your Claude was installed with npm into a folder the relay user cannot write, `claude doctor`
|
|
303
|
+
says so and the button becomes **Switch to native installer** — Anthropic's own recommended fix.
|
|
304
|
+
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
|
|
306
|
+
the relay could usefully run.
|
|
307
|
+
|
|
308
|
+
When a turn fails because a CLI is missing or signed out, the failed reply itself carries the fix as
|
|
309
|
+
a button — **Install Grok CLI**, **Sign in to Grok**, **Claude settings** — which opens the right
|
|
310
|
+
panel and, for an install, the same confirmation sheet.
|
|
311
|
+
|
|
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 |
|
|
320
|
+
|
|
321
|
+
Switching to **SDK** can be refused, and the panel says why in place of the engine description:
|
|
322
|
+
|
|
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.
|
|
325
|
+
|
|
326
|
+
A refusal never changes the stored engine; the select snaps back to the engine the relay actually runs.
|
|
327
|
+
|
|
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.
|
|
329
|
+
|
|
330
|
+
### Claude (Agent SDK)
|
|
331
|
+
|
|
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.
|
|
333
|
+
|
|
334
|
+
The same panel manages the account itself. The row at the top names the signed-in account and plan, and:
|
|
335
|
+
|
|
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.
|
|
338
|
+
|
|
339
|
+
If Claude has never been logged in on the host, running `claude` there once still works as before.
|
|
340
|
+
|
|
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.
|
|
342
|
+
|
|
343
|
+
What Claude conversations support:
|
|
344
|
+
|
|
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
|
|
353
|
+
|
|
354
|
+
Differences from Copilot conversations:
|
|
355
|
+
|
|
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
|
|
359
|
+
|
|
360
|
+
### Cursor (Agent SDK)
|
|
361
|
+
|
|
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).
|
|
363
|
+
|
|
364
|
+
What Cursor conversations support:
|
|
365
|
+
|
|
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
|
|
371
|
+
|
|
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.
|
|
373
|
+
|
|
374
|
+
### Grok (CLI ACP)
|
|
375
|
+
|
|
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).
|
|
379
|
+
|
|
380
|
+
The same panel manages both the CLI and the account:
|
|
381
|
+
|
|
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.
|
|
393
|
+
|
|
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.
|
|
396
|
+
|
|
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.
|
|
401
|
+
|
|
402
|
+
## Relay modes
|
|
403
|
+
|
|
404
|
+
|
|
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 |
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
## Models
|
|
414
|
+
|
|
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:
|
|
416
|
+
|
|
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.
|
|
421
|
+
|
|
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.
|
|
423
|
+
|
|
424
|
+
Selection is persisted in browser storage and attached per message.
|
|
425
|
+
|
|
426
|
+
## Settings (⚙️ in the web UI)
|
|
427
|
+
|
|
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:
|
|
429
|
+
|
|
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 |
|
|
440
|
+
|
|
441
|
+
### How a stuck turn is detected
|
|
442
|
+
|
|
443
|
+
Two independent guards, both of which exempt a turn that is waiting on an unanswered question card:
|
|
444
|
+
|
|
445
|
+
1. **Inactivity** — a turn is only considered stale after `processingTimeoutMs` with no sign of life from its worker. Worker heartbeats name the message they are working on, so a turn that legitimately runs for an hour of tool calls keeps resetting this window.
|
|
446
|
+
2. **Max turn duration** — an absolute ceiling on elapsed time, set with the Settings slider (0 = no limit, up to 10 hours). This exists purely to catch a worker that has hung while still heartbeating.
|
|
447
|
+
|
|
448
|
+
When either trips, the turn is returned to the queue rather than lost.
|
|
449
|
+
|
|
450
|
+
## Configuration reference (`server/config.json`)
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
| Key | Default | Description |
|
|
454
|
+
| -------------------------- | -------------------- | ------------------------------------------------------------------------- |
|
|
455
|
+
| `authToken` | generated if missing | Required for API/UI auth; set explicitly for stable access |
|
|
456
|
+
| `port` | `3333` | HTTP + WebSocket port |
|
|
457
|
+
| `localhostOnly` | `true` | Bind only to loopback (`127.0.0.1`) and disable LAN/WAN access |
|
|
458
|
+
| `pollIntervalMs` | `3000` | CLI heartbeat/poll cadence |
|
|
459
|
+
| `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 |
|
|
468
|
+
| `maxRequeueRetries` | `5` | Queue retry limit for failed processing |
|
|
469
|
+
| `remotePath` | `""` | URL base path when reverse-proxied under a subpath; also drives PWA URLs and socket.io path |
|
|
470
|
+
| `sshTunnel.mode` | `disabled` | Tunnel mode (`disabled` or `managed`) |
|
|
471
|
+
| `sshTunnel.enabled` | `false` | Legacy alias (`true` => `managed`) |
|
|
472
|
+
| `sshTunnel.required` | `false` | Pause dequeue while managed tunnel is disconnected |
|
|
473
|
+
| `sshTunnel.remoteBind` | `loopback` | Remote bind mode for SSH `-R` (`loopback` or `public`) |
|
|
474
|
+
| `sshTunnel.command` | `ssh` | SSH executable path/command |
|
|
475
|
+
| `sshTunnel.user` | — | SSH user |
|
|
476
|
+
| `sshTunnel.host` | — | SSH host |
|
|
477
|
+
| `sshTunnel.remotePort` | — | Remote forwarded port |
|
|
478
|
+
| `sshTunnel.identityFile` | optional | SSH key path (falls back to default agent/key) |
|
|
479
|
+
| `cloudflaredTunnel.mode` | `disabled` | Cloudflare tunnel mode (`disabled` or `managed`) |
|
|
480
|
+
| `cloudflaredTunnel.required` | `false` | Pause dequeue while the managed Cloudflare tunnel is disconnected |
|
|
481
|
+
| `cloudflaredTunnel.token` | — | Tunnel token issued by the router panel |
|
|
482
|
+
| `cloudflaredTunnel.binary` | *(auto)* | `cloudflared` path; defaults to the npm package, then `PATH` |
|
|
483
|
+
| `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`) |
|
|
487
|
+
| `previews.port` | `port + 1` | Loopback port for the preview listener; `0` picks an ephemeral port |
|
|
488
|
+
| `previews.bindHost` | `127.0.0.1` | Bind address; non-loopback needs `previews.allowPublicBind` |
|
|
489
|
+
| `previews.publicBaseUrl` | — | Public base URL on a hostname **different** from the relay's |
|
|
490
|
+
| `previews.allowedTargetHosts` | `[]` | Upstreams allowed beyond loopback (container/VM IPs) |
|
|
491
|
+
| `previews.maxLive` | `8` | Maximum simultaneously published previews |
|
|
492
|
+
|
|
493
|
+
|
|
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.
|
|
495
|
+
|
|
496
|
+
## Optional remote internet access (SSH tunnel)
|
|
497
|
+
|
|
498
|
+
Configure:
|
|
499
|
+
|
|
500
|
+
```json
|
|
501
|
+
"sshTunnel": {
|
|
502
|
+
"mode": "managed",
|
|
503
|
+
"required": false,
|
|
504
|
+
"remoteBind": "loopback",
|
|
505
|
+
"user": "ubuntu",
|
|
506
|
+
"host": "relay.example.com",
|
|
507
|
+
"remotePort": 4444,
|
|
508
|
+
"identityFile": "~/.ssh/id_rsa"
|
|
509
|
+
}
|
|
510
|
+
```
|
|
511
|
+
|
|
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`.
|
|
514
|
+
|
|
515
|
+
Then reverse proxy on the VPS (example Caddy):
|
|
516
|
+
|
|
517
|
+
```text
|
|
518
|
+
relay.example.com {
|
|
519
|
+
reverse_proxy localhost:4444
|
|
520
|
+
}
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
The relay auto-reconnects tunnel drops with exponential backoff.
|
|
524
|
+
|
|
525
|
+
## Optional remote internet access (Cloudflare Tunnel)
|
|
526
|
+
|
|
527
|
+
An alternative to the SSH tunnel that needs no VPS and no inbound port: the relay
|
|
528
|
+
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.
|
|
530
|
+
|
|
531
|
+
```json
|
|
532
|
+
"cloudflaredTunnel": {
|
|
533
|
+
"mode": "managed",
|
|
534
|
+
"required": false,
|
|
535
|
+
"token": "<tunnel token>",
|
|
536
|
+
"binary": "",
|
|
537
|
+
"extraArgs": []
|
|
538
|
+
}
|
|
539
|
+
```
|
|
540
|
+
|
|
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`.
|
|
545
|
+
|
|
546
|
+
`localhostOnly` stays `true`: `cloudflared` connects outbound and nothing binds publicly.
|
|
547
|
+
The binary resolves from `cloudflaredTunnel.binary`, then the optional `cloudflared` npm
|
|
548
|
+
package, then `PATH`; a managed config with no resolvable binary is reported as a config
|
|
549
|
+
error instead of crashing. Connection drops reconnect with jittered exponential backoff,
|
|
550
|
+
and repeated fast exits (deleted tunnel or bad token) are reported as `auth-or-config`
|
|
551
|
+
instead of hammering Cloudflare.
|
|
552
|
+
|
|
553
|
+
The relay status dot turns **amber** while the Cloudflare tunnel is connected, so it is
|
|
554
|
+
obvious at a glance that the relay is reachable from the internet rather than only from
|
|
555
|
+
this machine. It stays green when no tunnel is configured and grey when the relay itself
|
|
556
|
+
is unreachable; a managed tunnel that has dropped keeps the dot green — the relay still
|
|
557
|
+
answers locally — and reports the drop in the tooltip.
|
|
558
|
+
|
|
559
|
+
### Session-worker path guard
|
|
560
|
+
|
|
561
|
+
Any public tunnel forwards *every* path on the bound hostname to port `3333`, including
|
|
562
|
+
the internal session-worker WebSocket endpoints. Requests to those paths that carry an
|
|
563
|
+
edge marker header (`cf-ray`, plus anything listed in `tunnelMarkerHeaders`) are rejected
|
|
564
|
+
with `403` on both the request and upgrade paths. Local workers connect over `127.0.0.1`
|
|
565
|
+
without such a header and are unaffected, and shared conversation links keep working
|
|
566
|
+
anonymously over the tunnel.
|
|
567
|
+
|
|
568
|
+
Shared links work unchanged through Cloudflare. Do not add a Cloudflare Cache Rule
|
|
569
|
+
covering `/api/shared/*` — shared views poll for liveness and an edge-cached response
|
|
570
|
+
would pin viewers to a stale snapshot. Likewise, a Cloudflare Access application over the
|
|
571
|
+
bound hostname breaks share links unless it bypasses `/shared/*` and `/api/shared/*`.
|
|
572
|
+
|
|
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.
|
|
604
|
+
|
|
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
|
|
630
|
+
|
|
631
|
+
For deeper implementation/API details, see `[server/README.md](server/README.md)`.
|
|
632
|
+
|
|
633
|
+
## Troubleshooting
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
| Symptom | What to check |
|
|
637
|
+
| ---------------------------------- | -------------------------------------------------------------------------------- |
|
|
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` |
|
|
642
|
+
| File links fail | Verify auth token/cookie and that paths are inside allowed workspace/drive roots |
|
|
643
|
+
| Claude missing from New Chat | Enable it in **⚙️ Settings → Providers → Claude**; the toggle is off by default |
|
|
644
|
+
| 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
|
+
| 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
|
+
| 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
|
+
| 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.
|
|
659
|
+
|
|
660
|
+
## Repository layout
|
|
661
|
+
|
|
662
|
+
```text
|
|
663
|
+
oar/
|
|
664
|
+
├── .github/extensions/web-relay/ # Copilot CLI extension (worker WebSocket, ask_user bridge, model snapshotting)
|
|
665
|
+
├── server/
|
|
666
|
+
│ ├── claude-worker/ # Claude Agent SDK session worker (turn runner, ask-user bridge, attachments)
|
|
667
|
+
│ ├── cursor-worker/ # Cursor Agent SDK session worker (turn runner, mode nudges, auth retry)
|
|
668
|
+
│ ├── public/app/ # Browser app modules
|
|
669
|
+
│ ├── 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
|
|
674
|
+
└── README.md
|
|
675
|
+
```
|
|
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>
|