@oar-sh/oar 0.9.14 → 0.9.16
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/CHANGELOG.md +179 -0
- package/README.md +9 -9
- package/bin/oar.js +525 -12
- package/package.json +3 -1
- package/server/claude-worker/claude-remote-relay-tool.mjs +36 -1
- package/server/claude-worker/claude-session-process.mjs +4 -4
- package/server/copilot-worker/copilot-sdk-session-process.mjs +2 -2
- package/server/db-schema.mjs +52 -0
- package/server/migrations/0019-relay-event-subscription-options.mjs +106 -0
- package/server/migrations/0020-conversation-may-create-sessions.mjs +67 -0
- package/server/migrations/0021-conversation-group-agent-marks.mjs +77 -0
- package/server/migrations/0022-relay-schedules.mjs +142 -0
- package/server/migrations/0023-relay-asks.mjs +141 -0
- package/server/public/app/ask-user-view.js +3 -0
- package/server/public/app/bootstrap.js +61 -3
- package/server/public/app/context-usage-view.mjs +17 -0
- package/server/public/app/conversation-group-order.mjs +35 -0
- package/server/public/app/inbox-ui.js +356 -0
- package/server/public/app/inbox-view.mjs +370 -0
- package/server/public/app/journal-view.js +23 -2
- package/server/public/app/relay-event-subscriptions-ui.js +4 -2
- package/server/public/app/relay-event-view.mjs +33 -7
- package/server/public/app/relay-schedule-view.mjs +119 -0
- package/server/public/app/relay-schedules-ui.js +172 -0
- package/server/public/app/settings-modal.js +17 -3
- package/server/public/app/socket-handlers.js +27 -3
- package/server/public/app/store.js +18 -5
- package/server/public/index.html +159 -2
- package/server/relay-tools.md +18 -3
- package/server/repositories/conversation-group-repository.mjs +97 -11
- package/server/repositories/remote-relay-repository.mjs +20 -2
- package/server/repositories/session-repository.mjs +7 -1
- package/server/routes/ask-user-routes.mjs +57 -3
- package/server/routes/cache-routes.mjs +6 -0
- package/server/routes/conversation-group-routes.mjs +17 -2
- package/server/routes/messages-routes.mjs +3 -3
- package/server/routes/relay-ask-routes.mjs +387 -0
- package/server/routes/relay-event-routes.mjs +3 -1
- package/server/routes/relay-schedule-routes.mjs +57 -0
- package/server/routes/sessions-routes.mjs +613 -56
- package/server/server-runtime.mjs +80 -1
- package/server/services/cache-rebuild-service.mjs +19 -2
- package/server/services/conversation-preferences-service.mjs +17 -0
- package/server/services/oar-cli-helpers.mjs +12 -4
- package/server/services/relay-ask-link-service.mjs +174 -0
- package/server/services/relay-ask-service.mjs +604 -0
- package/server/services/relay-event-service.mjs +589 -73
- package/server/services/relay-schedule-service.mjs +596 -0
- package/server/services/remote-relay-caller-context.mjs +89 -20
- package/server/services/remote-relay-dispatcher.mjs +956 -30
- package/server/services/windows-autostart-service.mjs +88 -16
- package/server/services/windows-boot-autostart-service.mjs +775 -126
- package/shared/relay-asks.mjs +145 -0
- package/shared/relay-events.mjs +268 -18
- package/shared/relay-schedules.mjs +128 -0
- package/shared/remote-relay-contract.mjs +553 -25
- package/shared/remote-relay-tool-core.mjs +16 -5
- package/.github/extensions/web-relay/README.md +0 -190
- package/server/README.md +0 -2334
|
@@ -162,21 +162,32 @@ export async function executeRemoteRelayTool(input, { api, conversationId = '',
|
|
|
162
162
|
}
|
|
163
163
|
const parsed = validateRemoteRelayToolInput(input);
|
|
164
164
|
if (!parsed.ok) return parsed;
|
|
165
|
-
|
|
165
|
+
// Only this check sees what the agent wrote: the relay gets the arguments
|
|
166
|
+
// the action takes, so the ones it does not take are named here.
|
|
167
|
+
const withIgnored = (result) => withIgnoredArguments(result, parsed.ignored);
|
|
168
|
+
if (signal?.aborted) return withIgnored(remoteRelayCancelledResult());
|
|
166
169
|
try {
|
|
167
170
|
const response = await api('POST', REMOTE_RELAY_TOOL_ENDPOINT, {
|
|
168
171
|
conversationId: conversation,
|
|
169
172
|
action: parsed.action,
|
|
170
173
|
args: parsed.args,
|
|
171
174
|
}, { longCall: true, ...(signal ? { signal } : {}) });
|
|
172
|
-
if (response && typeof response === 'object' && !Array.isArray(response)) return response;
|
|
173
|
-
return { ok: true, result: response ?? null };
|
|
175
|
+
if (response && typeof response === 'object' && !Array.isArray(response)) return withIgnored(response);
|
|
176
|
+
return withIgnored({ ok: true, result: response ?? null });
|
|
174
177
|
} catch (error) {
|
|
175
|
-
if (signal?.aborted || isAbortError(error)) return remoteRelayCancelledResult();
|
|
176
|
-
return remoteRelayErrorResult(error);
|
|
178
|
+
if (signal?.aborted || isAbortError(error)) return withIgnored(remoteRelayCancelledResult());
|
|
179
|
+
return withIgnored(remoteRelayErrorResult(error));
|
|
177
180
|
}
|
|
178
181
|
}
|
|
179
182
|
|
|
183
|
+
/** `result` with `ignored` (the arguments the action did not take) merged in. */
|
|
184
|
+
export function withIgnoredArguments(result, ignored) {
|
|
185
|
+
const extra = Array.isArray(ignored) ? ignored.map(toText).filter(Boolean) : [];
|
|
186
|
+
if (!extra.length || !result || typeof result !== 'object' || Array.isArray(result)) return result;
|
|
187
|
+
const known = Array.isArray(result.ignored) ? result.ignored.map(toText).filter(Boolean) : [];
|
|
188
|
+
return { ...result, ignored: [...new Set([...known, ...extra])].sort() };
|
|
189
|
+
}
|
|
190
|
+
|
|
180
191
|
/**
|
|
181
192
|
* Whether a worker should register the tool: at least one remote is paired,
|
|
182
193
|
* or this relay lets agents start and use sessions on it (the local target).
|
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
# Web Relay Extension
|
|
2
|
-
|
|
3
|
-
The Copilot CLI extension that bridges local CLI sessions with the web relay server. This extension runs as a background agent in the CLI, polls the relay for pending turns, executes them, and streams activity back to the browser UI.
|
|
4
|
-
|
|
5
|
-
> Scope: this extension serves the **GitHub Copilot** and **OpenAI (BYOK)** providers. Claude
|
|
6
|
-
> conversations do not go through it — they run `server/claude-worker/claude-session-worker.mjs`,
|
|
7
|
-
> a standalone Node worker that implements the same relay contracts. See `server/README.md`.
|
|
8
|
-
|
|
9
|
-
## Architecture Overview
|
|
10
|
-
|
|
11
|
-
```
|
|
12
|
-
[Browser UI] <--WebSocket--> [server.js :3333] <--HTTP polling--> [CLI Extension]
|
|
13
|
-
|
|
|
14
|
-
[Session Workers]
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
- **`extension.mjs`** — Entry point; registers tools, initializes polling loop, manages extension lifecycle.
|
|
18
|
-
- **`polling/polling-loop.mjs`** — Main polling interval; fetches pending turns, executes via session workers, streams updates.
|
|
19
|
-
- **`runtime/worker-websocket-link.mjs`** — WebSocket bridge; handles turn execution, tool calls, activity streaming.
|
|
20
|
-
- **`skills/`** — Tool implementations: question routing, tool activity, tmux input bridge, reasoning streams.
|
|
21
|
-
- **`utils/`** — Helpers: tmux input bridge, prompts, configuration.
|
|
22
|
-
- **`server-lifecycle/`** — Manages relay server startup (extension-managed mode).
|
|
23
|
-
|
|
24
|
-
## Key Components
|
|
25
|
-
|
|
26
|
-
### Extension Entry Point (`extension.mjs`)
|
|
27
|
-
|
|
28
|
-
- Registers extension tools and skill handlers
|
|
29
|
-
- Starts the relay server if needed (extension-managed mode)
|
|
30
|
-
- Initializes polling loop on first `gh copilot` invocation
|
|
31
|
-
- Handles relay shutdown and restart requests
|
|
32
|
-
|
|
33
|
-
### Polling Loop (`polling/polling-loop.mjs`)
|
|
34
|
-
|
|
35
|
-
- Runs continuously every `pollIntervalMs` (default 3000ms)
|
|
36
|
-
- Fetches `/api/pending` to get next turn from the relay queue
|
|
37
|
-
- Spawns or reuses session workers to execute turns
|
|
38
|
-
- Streams tool/activity updates back to `/api/messages/{id}/events`
|
|
39
|
-
- Handles worker failures and retries
|
|
40
|
-
- Routes answers to `/api/ask-user` when agent needs user input
|
|
41
|
-
|
|
42
|
-
### Worker WebSocket Link (`runtime/worker-websocket-link.mjs`)
|
|
43
|
-
|
|
44
|
-
- Opens WebSocket connection to `localhost:3333/ws/worker/{sdkSessionId}`
|
|
45
|
-
- Bridges turn execution between the relay and the CLI session
|
|
46
|
-
- Handles streaming of:
|
|
47
|
-
- User messages sent to the agent
|
|
48
|
-
- Tool invocation events
|
|
49
|
-
- Streaming text output
|
|
50
|
-
- Model/reasoning choices
|
|
51
|
-
- Completion events
|
|
52
|
-
|
|
53
|
-
### Question Routing (`skills/question-routing-hooks.mjs`)
|
|
54
|
-
|
|
55
|
-
- Intercepts `onElicitationRequest` and `onUserInputRequest` from the CLI session
|
|
56
|
-
- Uses structured elicitation (`requestedSchema`) as the canonical ask-user flow
|
|
57
|
-
- For structured answers:
|
|
58
|
-
- Extracts `requestSchema` (JSON schema)
|
|
59
|
-
- Registers question in relay database
|
|
60
|
-
- Waits for browser to render form and user to submit
|
|
61
|
-
- Retrieves `structuredAnswer` and returns `ElicitationResponse`
|
|
62
|
-
|
|
63
|
-
### Tool Activity (`skills/tool-activity.mjs`)
|
|
64
|
-
|
|
65
|
-
- Hooks Copilot SDK tool invocations
|
|
66
|
-
- Streams tool call details (name, arguments, status) to the relay
|
|
67
|
-
- Relays tool results back to the browser UI for real-time activity display
|
|
68
|
-
- Web Search and Web Fetch activity includes bounded query/URL details and output previews
|
|
69
|
-
- `store_memory` activity includes the available `subject`, `fact`, `citations`, `reason`, and `scope` metadata with preserved field content and line breaks
|
|
70
|
-
- `vote_memory` activity includes the available `fact`, `direction`, `reason`, and `scope` metadata with preserved field content and line breaks
|
|
71
|
-
- Activity is persisted by the relay and is therefore available to authorized shared viewers and replayed history
|
|
72
|
-
|
|
73
|
-
### Subagent attribution
|
|
74
|
-
|
|
75
|
-
The SDK `agentId` on a request is propagated as `subagentRunId` on question activity, tool-result
|
|
76
|
-
activity, and `report_intent` thoughts. Without it, work done by a subagent would be rendered in the
|
|
77
|
-
parent turn's bubble instead of the subagent's own. `skills/subagent-lifecycle.mjs` also preserves an
|
|
78
|
-
already-known run's parent and display name when the run is re-discovered (for example on its next
|
|
79
|
-
tool call), so a nested subagent is never demoted to a root or renamed mid-turn.
|
|
80
|
-
|
|
81
|
-
### Reasoning Stream (`skills/reasoning-stream.mjs`)
|
|
82
|
-
|
|
83
|
-
- (Planned) Optional feature to stream extended reasoning/thinking from models
|
|
84
|
-
- Can be toggled behind a feature flag
|
|
85
|
-
|
|
86
|
-
## Configuration
|
|
87
|
-
|
|
88
|
-
The extension loads configuration from:
|
|
89
|
-
|
|
90
|
-
1. `server/config.json` (default settings)
|
|
91
|
-
2. Environment variables (e.g., `COPILOT_REMOTE_POLLING_INTERVAL`)
|
|
92
|
-
3. Relay API responses (feature flags, runtime config)
|
|
93
|
-
|
|
94
|
-
Key config keys read by the extension:
|
|
95
|
-
|
|
96
|
-
- `pollIntervalMs` — How often to fetch pending turns (ms)
|
|
97
|
-
- `processingTimeoutMs` — Max turn execution time (ms)
|
|
98
|
-
- `workerRestartIntervalMs` — Backoff between worker restarts on failure (ms)
|
|
99
|
-
- Feature flags: `SESSION_WORKER_ROUTING_ENABLED`, `SESSION_WORKER_CONTINUATION_ROUTING_ENABLED`
|
|
100
|
-
|
|
101
|
-
## Session Worker Lifecycle
|
|
102
|
-
|
|
103
|
-
1. **Create** — New session worker spawned by polling loop on first turn for an `sdkSessionId`
|
|
104
|
-
2. **Connect** — Worker opens WebSocket link to relay and registers with `/api/workers`
|
|
105
|
-
3. **Execute** — Worker receives turns, executes via CLI session, streams activity
|
|
106
|
-
4. **Heartbeat** — Worker periodically sends keep-alive to indicate it's ready
|
|
107
|
-
5. **Degraded** — If heartbeat fails, relay marks worker as "degraded"; polling loop may restart it
|
|
108
|
-
6. **Restart** — If worker process exits unexpectedly or is in a bad state, polling loop spawns a replacement
|
|
109
|
-
|
|
110
|
-
## Startup Behavior
|
|
111
|
-
|
|
112
|
-
### Extension-Managed Mode (Default)
|
|
113
|
-
|
|
114
|
-
- Extension detects if relay is running: `GET /api/status` health check
|
|
115
|
-
- If relay is down, extension starts `server.js` via `npm start` from the relay repo root
|
|
116
|
-
- Extension owns the relay lifecycle and keeps it running as long as CLI is active
|
|
117
|
-
- Polling loop keeps running even between user turns
|
|
118
|
-
- On exit, extension gracefully shuts down relay
|
|
119
|
-
|
|
120
|
-
### Standalone Mode
|
|
121
|
-
|
|
122
|
-
- User manually runs `npm start` or `npm run copilot:relay` from relay repo
|
|
123
|
-
- CLI extension still runs polling loop, but doesn't supervise the relay
|
|
124
|
-
- Relay stays running after CLI exits (server continues listening on port 3333)
|
|
125
|
-
|
|
126
|
-
**Single-Owner Rule:** Never run both modes simultaneously; it will cause port conflicts and undefined behavior.
|
|
127
|
-
|
|
128
|
-
## Browser-to-Extension Communication
|
|
129
|
-
|
|
130
|
-
### WebSocket Message Flow
|
|
131
|
-
|
|
132
|
-
1. Browser sends message: `POST /api/messages` (queues with relay)
|
|
133
|
-
2. Extension polls: `GET /api/pending` (fetches from queue)
|
|
134
|
-
3. Extension opens WebSocket: `ws://localhost:3333/ws/worker/{sdkSessionId}`
|
|
135
|
-
4. Browser listens: `GET /api/messages/{id}/events` (Server-Sent Events stream)
|
|
136
|
-
5. Worker streams: Tool invocations, text chunks, activity updates
|
|
137
|
-
6. Browser renders: Real-time chat, tool activity, streaming text
|
|
138
|
-
|
|
139
|
-
### Question Flow
|
|
140
|
-
|
|
141
|
-
1. Agent/tool calls `onElicitationRequest(schema)` or `onUserInputRequest()`
|
|
142
|
-
2. Question routing skill detects and registers in relay
|
|
143
|
-
3. Browser fetches: `GET /api/ask-user` (gets pending questions)
|
|
144
|
-
4. Browser renders: Form or text input based on schema type
|
|
145
|
-
5. User submits: `POST /api/ask-user` with answer
|
|
146
|
-
6. Extension retrieves: `GET /api/pending` (gets question + answer)
|
|
147
|
-
7. Extension returns: `ElicitationResponse` to agent
|
|
148
|
-
|
|
149
|
-
## Testing
|
|
150
|
-
|
|
151
|
-
Test files located in:
|
|
152
|
-
- `runtime/worker-websocket-link.test.mjs` — WebSocket bridge tests
|
|
153
|
-
- `skills/*.test.mjs` — Skill handler tests
|
|
154
|
-
|
|
155
|
-
Run them with the Node test runner (there is no `npm test` script). Match `*.test.mjs` explicitly —
|
|
156
|
-
pointing the runner at a directory makes it try to execute the implementation modules too:
|
|
157
|
-
```bash
|
|
158
|
-
node --test .github/extensions/web-relay/skills/*.test.mjs
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
## Troubleshooting
|
|
162
|
-
|
|
163
|
-
### Relay won't start
|
|
164
|
-
- Check `npm install` has been run
|
|
165
|
-
- Verify Node.js 24+ is in PATH
|
|
166
|
-
- Check port 3333 is available (or use `"port"` in config)
|
|
167
|
-
- See extension logs: `server/logs/ext-debug.log`
|
|
168
|
-
|
|
169
|
-
### Polling loop not running
|
|
170
|
-
- Check extension is loaded: `gh extension list | grep web-relay`
|
|
171
|
-
- Check relay is healthy: `curl http://localhost:3333/api/status`
|
|
172
|
-
- Check CLI session is connected: Check browser sidebar for "Online" status
|
|
173
|
-
|
|
174
|
-
### Worker keeps restarting
|
|
175
|
-
- Check worker process logs: `server/logs/` directory
|
|
176
|
-
- Increase `processingTimeoutMs` if turns are timing out
|
|
177
|
-
- Check Copilot SDK issues: `gh copilot` errors in terminal
|
|
178
|
-
|
|
179
|
-
### Structured answers not appearing
|
|
180
|
-
- Check browser console for rendering errors
|
|
181
|
-
- Verify `request_schema` is valid JSON: Relay logs may show schema parse errors
|
|
182
|
-
- Check migration was applied: `server/data/copilot.db` should have `relay_questions.structured_answer` column
|
|
183
|
-
|
|
184
|
-
## Contributing
|
|
185
|
-
|
|
186
|
-
When modifying the extension:
|
|
187
|
-
- Keep polling loop responsive: avoid long synchronous operations
|
|
188
|
-
- Don't block WebSocket handling: stream events instead of buffering large responses
|
|
189
|
-
- Test with both macOS/Linux (tmux paths) and Windows (TTY-Console)
|
|
190
|
-
- Update test files alongside feature changes
|