@oar-sh/oar 0.9.4 → 0.9.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/.github/extensions/web-relay/polling/polling-loop.mjs +12 -32
  2. package/.github/extensions/web-relay/skills/question-bridge.mjs +14 -39
  3. package/CHANGELOG.md +199 -0
  4. package/README.md +35 -6
  5. package/package.json +2 -1
  6. package/server/claude-worker/claude-remote-relay-tool.mjs +1 -0
  7. package/server/claude-worker/claude-session-process.mjs +411 -19
  8. package/server/claude-worker/claude-session-worker.mjs +5 -0
  9. package/server/claude-worker/claude-turn-failure.mjs +92 -0
  10. package/server/claude-worker/claude-turn-publisher.mjs +52 -3
  11. package/server/claude-worker/sdk-message-normalizer.mjs +13 -1
  12. package/server/copilot-worker/copilot-model-switch.mjs +29 -3
  13. package/server/copilot-worker/copilot-question-bridge.mjs +2 -4
  14. package/server/copilot-worker/copilot-runtime-tree.mjs +91 -0
  15. package/server/copilot-worker/copilot-sdk-adapter.mjs +48 -8
  16. package/server/copilot-worker/copilot-sdk-session-process.mjs +897 -166
  17. package/server/copilot-worker/copilot-sdk-session-worker.mjs +5 -0
  18. package/server/cursor-worker/cursor-sdk-adapter.mjs +80 -13
  19. package/server/cursor-worker/cursor-session-worker.mjs +5 -0
  20. package/server/cursor-worker/cursor-turn-runner.mjs +5 -2
  21. package/server/db-schema.mjs +7 -0
  22. package/server/grok-worker/acp-client.mjs +68 -12
  23. package/server/grok-worker/grok-sdk-adapter.mjs +4 -0
  24. package/server/grok-worker/grok-session-worker.mjs +5 -0
  25. package/server/grok-worker/grok-turn-runner.mjs +6 -2
  26. package/server/public/app/api-client.js +17 -2
  27. package/server/public/app/bootstrap.js +3 -0
  28. package/server/public/app/composer-control-state.mjs +4 -3
  29. package/server/public/app/conversation-view.js +97 -4
  30. package/server/public/app/journal-view.js +6 -0
  31. package/server/public/app/markdown-strikethrough.mjs +31 -0
  32. package/server/public/app/relay-board-inline.mjs +70 -0
  33. package/server/public/app/relay-board-view.js +157 -61
  34. package/server/public/app/relay-error-ctas.mjs +12 -3
  35. package/server/public/app/socket-handlers.js +9 -0
  36. package/server/public/app/steered-turn-merge.mjs +10 -1
  37. package/server/public/app/store.js +4 -1
  38. package/server/public/app/subagent-fold.mjs +61 -0
  39. package/server/public/app/usage-limit-ui.js +225 -0
  40. package/server/public/index.html +97 -4
  41. package/server/repositories/message-repository.mjs +10 -1
  42. package/server/repositories/question-repository.mjs +1 -0
  43. package/server/repositories/remote-relay-repository.mjs +30 -0
  44. package/server/routes/messages-routes.mjs +93 -17
  45. package/server/routes/relay-board-routes.mjs +56 -2
  46. package/server/routes/sessions-routes.mjs +40 -2
  47. package/server/server-runtime.mjs +33 -2
  48. package/server/services/preview-proxy-server.mjs +64 -36
  49. package/server/services/process-list-service.mjs +122 -0
  50. package/server/services/relay-console-log.mjs +199 -0
  51. package/server/services/remote-relay-caller-context.mjs +45 -20
  52. package/server/services/remote-relay-dispatcher.mjs +328 -21
  53. package/server/services/remote-relay-inbound.mjs +35 -0
  54. package/server/services/session-worker-launch-service.mjs +17 -2
  55. package/server/services/session-worker-process-service.mjs +85 -32
  56. package/server/services/session-worker-registry-service.mjs +1 -1
  57. package/server/services/session-worker-supervisor-service.mjs +33 -1
  58. package/server/services/usage-limit-pause-service.mjs +441 -0
  59. package/shared/ask-user-bridge.mjs +14 -39
  60. package/shared/claude-usage-limit.mjs +257 -0
  61. package/shared/control-poller.mjs +69 -20
  62. package/shared/failure-note-text.mjs +38 -0
  63. package/shared/question-wait.mjs +108 -0
  64. package/shared/remote-relay-contract.mjs +33 -2
  65. package/shared/steer-settle-failure.mjs +22 -0
  66. package/shared/steer-settle-markers.mjs +24 -0
  67. package/shared/worker-runtime/process-tree.mjs +483 -0
  68. package/shared/worker-runtime/relay-errors.mjs +31 -0
  69. package/shared/worker-runtime/send-and-wait-errors.mjs +4 -4
  70. package/shared/worker-runtime/turn-liveness.mjs +249 -0
  71. package/shared/worker-runtime/worker-log-file.mjs +97 -0
@@ -6,7 +6,7 @@ import {
6
6
  } from "../runtime/send-and-wait-errors.mjs";
7
7
  import { getActiveSession } from "../runtime/session-registry.mjs";
8
8
  import { DEFAULT_QUESTION_TIMEOUT_MS } from "../../../../shared/question-timeout.mjs";
9
- import { QUESTION_TIMEOUT_CONTINUATION_TEXT } from "../../../../shared/question-timeout.mjs";
9
+ import { waitForRelayQuestion } from "../../../../shared/question-wait.mjs";
10
10
  import { EMPTY_TURN_COMPLETION_NOTE } from "../../../../shared/empty-turn-completion.mjs";
11
11
  import { stripPromptContextPrefix } from "../skills/prompt-context.mjs";
12
12
 
@@ -473,37 +473,17 @@ export function createPollingLoop({
473
473
  return null;
474
474
  };
475
475
 
476
- async function waitForRelayQuestionAnswer(questionId, timeoutMs = DEFAULT_QUESTION_TIMEOUT_MS, pollIntervalMs = 1500) {
477
- const started = Date.now();
478
- while (true) {
479
- const { question } = await api("GET", `/api/relay-question/${questionId}`);
480
- if (!question) throw new Error("Relay question missing");
481
- if (question.status === "answered") {
482
- return {
483
- answer: String(question.answer || "").trim(),
484
- structuredAnswer: question.structuredAnswer && typeof question.structuredAnswer === "object"
485
- ? question.structuredAnswer
486
- : null,
487
- timedOut: false,
488
- };
489
- }
490
- if (question.status === "timed_out" || question.status === "cancelled") {
491
- return {
492
- answer: QUESTION_TIMEOUT_CONTINUATION_TEXT,
493
- structuredAnswer: null,
494
- timedOut: true,
495
- };
496
- }
497
- if (Date.now() - started >= timeoutMs) {
498
- await api("POST", `/api/relay-question/${questionId}/timeout`, {}).catch(() => {});
499
- return {
500
- answer: QUESTION_TIMEOUT_CONTINUATION_TEXT,
501
- structuredAnswer: null,
502
- timedOut: true,
503
- };
504
- }
505
- await sleep(pollIntervalMs);
506
- }
476
+ // The wait survives a relay that is briefly not there
477
+ // (shared/question-wait.mjs).
478
+ function waitForRelayQuestionAnswer(questionId, timeoutMs = DEFAULT_QUESTION_TIMEOUT_MS, pollIntervalMs = 1500) {
479
+ return waitForRelayQuestion({
480
+ api,
481
+ questionId,
482
+ deadlineMs: timeoutMs,
483
+ pollMs: pollIntervalMs,
484
+ sleep,
485
+ dbg,
486
+ });
507
487
  }
508
488
 
509
489
  async function processPendingSdkSessionDeletes() {
@@ -1,5 +1,5 @@
1
1
  import { getActiveSession } from '../runtime/session-registry.mjs';
2
- import { QUESTION_TIMEOUT_CONTINUATION_TEXT } from "../../../../shared/question-timeout.mjs";
2
+ import { createRelayQuestion, waitForRelayQuestion } from "../../../../shared/question-wait.mjs";
3
3
  import { extractRequestedSchema } from "../../../../shared/question-schema.mjs";
4
4
 
5
5
  export function createQuestionBridge({
@@ -49,39 +49,18 @@ export function createQuestionBridge({
49
49
  return Number.isFinite(fallback) && fallback >= 0 ? fallback : 0;
50
50
  }
51
51
 
52
- async function waitForRelayQuestionAnswer(questionId, timeoutMs = null) {
53
- const started = Date.now();
54
- const effectiveTimeoutMs = resolveQuestionWaitTimeoutMs(timeoutMs);
55
-
56
- while (true) {
57
- const { question } = await api("GET", `/api/relay-question/${questionId}`);
58
- if (!question) throw new Error("Relay question missing");
59
- if (question.status === "answered") {
60
- return {
61
- answer: String(question.answer || ""),
62
- structuredAnswer: question.structuredAnswer && typeof question.structuredAnswer === "object"
63
- ? question.structuredAnswer
64
- : null,
65
- timedOut: false,
66
- };
67
- }
68
- if (question.status === "timed_out" || question.status === "cancelled") {
69
- return {
70
- answer: QUESTION_TIMEOUT_CONTINUATION_TEXT,
71
- structuredAnswer: null,
72
- timedOut: true,
73
- };
74
- }
75
- if (Date.now() - started >= effectiveTimeoutMs) {
76
- await api("POST", `/api/relay-question/${questionId}/timeout`, {}).catch(() => {});
77
- return {
78
- answer: QUESTION_TIMEOUT_CONTINUATION_TEXT,
79
- structuredAnswer: null,
80
- timedOut: true,
81
- };
82
- }
83
- await sleep(questionPollMs);
84
- }
52
+ // The wait survives a relay that is briefly not there
53
+ // (shared/question-wait.mjs).
54
+ function waitForRelayQuestionAnswer(questionId, timeoutMs = null) {
55
+ return waitForRelayQuestion({
56
+ api,
57
+ questionId,
58
+ deadlineMs: resolveQuestionWaitTimeoutMs(timeoutMs),
59
+ pollMs: questionPollMs,
60
+ sleep,
61
+ trimAnswer: false,
62
+ dbg,
63
+ });
85
64
  }
86
65
 
87
66
  async function forwardRelayQuestion(request) {
@@ -112,11 +91,7 @@ export function createQuestionBridge({
112
91
  request: serializeRequest(request),
113
92
  };
114
93
 
115
- const created = await api("POST", "/api/relay-question", questionPayload);
116
- const questionId = created?.question?.id;
117
- if (!questionId) {
118
- throw new Error("Relay question could not be created");
119
- }
94
+ const questionId = await createRelayQuestion({ api, payload: questionPayload, sleep, dbg });
120
95
 
121
96
  dbg(
122
97
  "relay question created",
package/CHANGELOG.md CHANGED
@@ -5,6 +5,205 @@ All notable changes to OAR are documented here. The format follows
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.9.6] — 2026-09-29
9
+
10
+ Highlights: a Claude turn pauses at the usage limit and carries on after the
11
+ reset, a command that prints nothing no longer fails its turn, and agents on
12
+ paired relays can work with each other for a whole session.
13
+
14
+ ### Added
15
+
16
+ - The relay keeps a copy of its console in `relay-console.log` in its log
17
+ folder (`server/logs` unless you moved it), with a time stamp per line. The
18
+ file is rotated at 5 MB and three older copies are kept. The relay's token is
19
+ never written to it. Set `OAR_NO_CONSOLE_LOG=1` to switch it off.
20
+ - **Claude usage limit:** a Claude turn that runs into the subscription's
21
+ usage limit is paused instead of failed. Your message closes with a note,
22
+ the relay queues a message of its own ("continue where you left off") and
23
+ sends it a minute after the limit resets. A banner above the composer says
24
+ when, with **Resume now** and **Cancel**. The pause survives a relay
25
+ restart. A reset more than six hours away (a weekly limit) waits for you to
26
+ resume it.
27
+ - A banner warns in Claude conversations when the usage passes 90 % of a
28
+ limit, with the time of the reset, and can be hidden until the next one.
29
+
30
+ ### Changed
31
+
32
+ - **Remote relays:** agents on paired relays can now work with each other for
33
+ a whole session. A relay you mentioned stays in reach whatever prompts arrive
34
+ later, and a relay whose agent wrote to a conversation is open to it, so the
35
+ agent that was asked can answer, ask back or report later. Until now a prompt
36
+ from another relay's agent took the tool away for the rest of the turn, also
37
+ in a turn you had started yourself. A prompt that crossed two relays is still
38
+ not passed on to a third relay you did not unlock; reading and waiting are no
39
+ longer limited. After 30 prompts to other relays' agents without a message
40
+ from you, the next one asks you first.
41
+ - Subagents are folded to their header inside the running turn, so a turn with
42
+ many of them stays short to scroll. The header says how many steps the
43
+ subagent has taken and shows the latest one, as far as there is room. A tap
44
+ on the header unfolds a subagent and folds it again; what you chose by hand
45
+ stays. The Stop button of a finished subagent is gone.
46
+ - **Copilot, Cursor, Grok:** how long a turn may stay quiet now depends on what
47
+ the agent is doing: 2 minutes with nothing in flight, 5 minutes during a model
48
+ request, 30 minutes while a tool runs. Until now every silence of 2 minutes
49
+ failed the turn, including a command that simply printed nothing. The failure
50
+ note says how long the silence was and what was running. The three windows can
51
+ be set with `OAR_TURN_STALL_IDLE_MS`, `OAR_TURN_STALL_MODEL_MS` and
52
+ `OAR_TURN_STALL_TOOL_MS`.
53
+ - **Copilot:** a turn kept past 2 minutes of silence is checked: a runtime that
54
+ no longer answers fails the turn at once. The watchdog also covers the start of
55
+ a session, where a runtime that did not answer used to hold the message without
56
+ any failure, and a Stop during that start now takes effect at once.
57
+ - **Cursor:** a stalled run is cancelled, so the next message does not wait for
58
+ it.
59
+ - A plan board is part of the reply it belongs to: its content and its buttons
60
+ are shown inside the agent's reply bubble (inside the running turn's bubble
61
+ until the reply is there) instead of on a card of their own at the end of the
62
+ transcript. When the reply already is the plan, the plan is shown once, with
63
+ the buttons below it. After you chose, the plan stays and the bubble says
64
+ what was chosen.
65
+ - The choice on a plan board also sets the mode of the session: after
66
+ "Implement in autopilot" the composer says Autopilot, after "Stop here and
67
+ prompt myself" it says Agent, and the next message carries the work on. The
68
+ session used to stay in Plan, so the next message planned again. "Stop here"
69
+ leaves the mode as it is.
70
+
71
+ ### Fixed
72
+
73
+ - A session worker that was idle and whose process has ended (stopped from
74
+ outside, or crashed between turns) is no longer listed as ready with its old
75
+ process id. The next message started a new worker before as well; the list
76
+ now says what is there, about a minute and a half after the process ended.
77
+ - A reply that quotes or describes a failure note (an agent reporting on a
78
+ failed test, for example) is no longer taken for a failure itself. The relay
79
+ cut such a reply off at the quoted error code, put its own advice behind it
80
+ and stored the turn as failed. Where a failure is known from its text alone,
81
+ that text is now kept whole.
82
+ - A single `~` in a reply ("about", as in "~35 s … ~7 min") no longer strikes
83
+ out the text up to the next one. Strikethrough needs two tildes on each side:
84
+ `~~text~~`. The page now names the version of its Markdown library (15.0.12,
85
+ the one it was served until now) instead of taking whatever the CDN hands out.
86
+ - **Copilot:** a command the agent had started no longer runs on after its
87
+ runtime is gone. When the runtime died or was killed in the middle of a turn,
88
+ the turn failed as it should, but the command kept running with nobody left
89
+ to read its result. The relay now stops what such a runtime leaves behind
90
+ (first gently, then by force), and the failure note says that a command that
91
+ was still running is being stopped, as far as that is known at that moment:
92
+ the note comes at once and does not wait for the list of processes. A command
93
+ the agent started to outlive the session (`detach`) is left alone, and so are
94
+ the commands of a runtime that shuts down in good order.
95
+ - **Grok:** a quiet-turn window that is set for the model request or for a tool
96
+ is kept when the shared watchdog is switched off by the environment, and the
97
+ watchdog looks often enough for the shortest window that is set.
98
+ - **Windows:** stopping or deleting a session could end the worker of another
99
+ session as well. Windows hands the process id of an ended process out again,
100
+ and a worker whose creator was long gone could so look like a child of the
101
+ session that was being stopped. A process older than its parent is no longer
102
+ taken for its child, and the worker of another session never is.
103
+ - **Copilot:** a message sent while a command is running no longer cuts the
104
+ command short. It waits until the running tool call has ended and is steered
105
+ in then; the composer reads "Queue" for as long. Sent at once, the message
106
+ made the runtime push the command to the background: the turn ended early
107
+ with the answer to the new message alone, or failed with "No response was
108
+ returned", and the command's result arrived later in a turn of its own.
109
+ - A plan board's action ("Implement in autopilot" and the like) queues its
110
+ follow-up again. It failed with an error and left the board marked as acted
111
+ on, without the message that carries the plan out.
112
+ - A finished subagent no longer turns back to "Running" in the running turn
113
+ when another line of activity arrives or the page reloads.
114
+ - **Previews:** the first page load after a dev server was restarted no longer
115
+ ends in "The upstream connection failed: ECONNRESET". The preview lane keeps
116
+ its connections to the dev server, and the one it had kept was closed by the
117
+ restart. A request without a body is now sent once more on a connection of
118
+ its own: it reaches the restarted server, or the page says that nothing is
119
+ listening on the port.
120
+ - Links in the file viewer and in plan boards have a colour that can be read in
121
+ dark mode; they used the browser's default blue.
122
+ - A question or approval card no longer answers itself when the relay is
123
+ briefly unreachable, for example while it restarts. One failed check used to
124
+ end the wait: the agent was told to use its own judgement, or the approval
125
+ was refused, while the card stayed on screen. The wait now lasts through the
126
+ outage, for every provider.
127
+ - **Copilot:** a turn that fails is reported at once. The failure used to wait
128
+ for the runtime to finish stopping, which took three minutes on a runtime that
129
+ had stopped answering; stopping it is now bounded, and the next message waits
130
+ for it before a new runtime starts.
131
+ - **Copilot:** a failed turn keeps what the agent had written, above the failure
132
+ note. The note says how many tool calls ran and that their changes are still in
133
+ place, and it no longer advises restarting the relay. A message steered into
134
+ the failed turn gets a short marker with Resend instead of a second copy of
135
+ the failure.
136
+ - **Copilot:** a slow relay no longer makes a busy runtime look silent.
137
+ - **Copilot:** a reply the relay could not take (it was restarting, the
138
+ connection was reset) is offered again for up to five minutes. One failed
139
+ attempt used to put the message back in the queue, which threw the finished
140
+ answer away and ran the prompt a second time. A reply that cannot be saved at
141
+ all ends as a failure that says so, and nothing is run twice.
142
+ - **Copilot:** what the agent does by itself after a background task (a
143
+ continuation) is kept while the relay restarts. Its row was given up after
144
+ about a second, and the whole reply with it.
145
+ - **Copilot:** a relay that accepts live output and does not answer no longer
146
+ holds the turn: a live update waits 10 seconds at most, and after one that
147
+ ran out, live updates are skipped for 30 seconds. Tool lines skipped then are
148
+ missing from the transcript; the reply is not affected.
149
+ - **Copilot:** Stop works on a turn that is stuck. A runtime that does not
150
+ answer the Stop within 10 seconds is ended with the turn, and a turn the user
151
+ stopped ends as stopped even when the runtime died or went silent after it.
152
+ - **Copilot:** a runtime that exits, or stops answering, while the agent works
153
+ by itself after a background task is cleared away; the next message used to
154
+ fail on it. A lost connection during a model change is reported as that, not
155
+ as "pick another model".
156
+ - **Copilot:** a failed turn tells its failure once. A message sent into the
157
+ turn that the agent had not started on gets the short marker with Resend,
158
+ like one it had taken in.
159
+ - **Copilot:** background agents and shells are no longer closed with the
160
+ runtime because its list of them could not be read.
161
+ - **Stop button:** Stop keeps working for a message that waits on a question
162
+ card while the agent starts and finishes other work, and for a second message
163
+ stopped while the same turn goes on. It used to do nothing in both cases.
164
+ - **Copilot:** a turn the runtime itself ended with an error no longer tells
165
+ you to restart the relay. The worker starts a fresh runtime by itself; a
166
+ restart repairs nothing and costs every other running session.
167
+ - The note under a failed turn no longer advises restarting the relay: a restart
168
+ repairs nothing there and interrupts every other running session. It now says
169
+ to send the message again and to include the error code in a report.
170
+ - **Claude:** a turn the Claude account refuses (billing, an expired sign-in,
171
+ access the organisation has switched off) says to check the account or to sign
172
+ in again in Settings → Providers → Claude, with a button that opens the panel,
173
+ instead of "Retry the message". Its error code names the failure, for example
174
+ `relay.claude-billing-error`; it used to read `relay.claude-success`.
175
+
176
+ ## [0.9.5] — 2026-09-27
177
+
178
+ Highlights: a Claude reply on the wrong row no longer blocks the conversation,
179
+ workers on Windows write a log, and agents can set the effort of a session on
180
+ another relay.
181
+
182
+ ### Added
183
+
184
+ - **Remote relays**: an agent can set the reasoning effort of a session it starts or
185
+ prompts on another relay (`effort` on `create_session` and `send`), and `relay_info`
186
+ lists the efforts each model there takes.
187
+
188
+ ### Changed
189
+
190
+ - Session workers on Windows now write the same `worker-<session>.log` as on
191
+ Linux, in the relay's log directory; the console window keeps showing the
192
+ output.
193
+
194
+ ### Fixed
195
+
196
+ - **Remote relays:** waiting for a remote agent's reply no longer stops at an
197
+ interim answer ("still waiting for …") while the agent goes on in a turn of
198
+ its own without a background task, for example while its subagents work.
199
+ - A Claude reply published as a *background continuation* instead of under
200
+ your message no longer blocks the conversation for up to 5 minutes: after
201
+ about 45 seconds of silence your message closes with a small muted note
202
+ naming that reply, and the next message runs normally. The note sends no
203
+ second notification, and an agent on a paired relay receives the reply
204
+ itself, not the note. A stray re-init of the Claude CLI between turns no
205
+ longer sends the reply there in the first place.
206
+
8
207
  ## [0.9.4] — 2026-09-27
9
208
 
10
209
  Highlights: remote relays (pair OAR relays and let an agent on one work on the
package/README.md CHANGED
@@ -78,7 +78,7 @@ curl -fsSL oar.sh/install | sh
78
78
  The script checks for Node.js 22.13 or newer, runs `npm install -g @oar-sh/oar`, and then hands off to `oar setup` (with `--defaults` when there is no terminal to ask questions in). It refuses to run as root, because OAR installs per user. `OAR_VERSION=x.y.z` pins a version, `OAR_CHANNEL=beta` follows the beta channel, and `OAR_DRY_RUN=1` only prints what it would do:
79
79
 
80
80
  ```bash
81
- curl -fsSL oar.sh/install | OAR_VERSION=0.9.4 sh
81
+ curl -fsSL oar.sh/install | OAR_VERSION=0.9.6 sh
82
82
  ```
83
83
 
84
84
  Read it before you run it: [oar.sh/install](https://oar.sh/install).
@@ -89,7 +89,7 @@ Read it before you run it: [oar.sh/install](https://oar.sh/install).
89
89
  irm oar.sh/install.ps1 | iex
90
90
  ```
91
91
 
92
- The PowerShell installer (PowerShell 5 or newer) does the same: it checks Node.js, runs `npm install -g @oar-sh/oar`, then `oar setup`. It reads the same `OAR_VERSION`, `OAR_CHANNEL`, and `OAR_DRY_RUN` variables, for example `$env:OAR_VERSION = '0.9.4'` before the command.
92
+ The PowerShell installer (PowerShell 5 or newer) does the same: it checks Node.js, runs `npm install -g @oar-sh/oar`, then `oar setup`. It reads the same `OAR_VERSION`, `OAR_CHANNEL`, and `OAR_DRY_RUN` variables, for example `$env:OAR_VERSION = '0.9.6'` before the command.
93
93
 
94
94
  ### npm
95
95
 
@@ -326,6 +326,22 @@ Use **🤗 Select Models** to choose which variants show up in the composer, the
326
326
 
327
327
  ## Highlights
328
328
 
329
+ ### New in 0.9.6
330
+
331
+ - **Claude pauses at the usage limit**: a turn that runs into the subscription's usage limit is paused instead of failed. A banner above the composer says when it carries on, with **Resume now** and **Cancel**; after the reset the relay sends a message of its own that quotes what was refused. The pause survives a relay restart, and a reset more than six hours away waits for you. A second banner warns when the usage passes 90 % of a limit.
332
+ - **A quiet command no longer fails its turn** (Copilot, Cursor, Grok): how long a turn may stay silent depends on what the agent is doing: 2 minutes with nothing in flight, 5 minutes during a model request, 30 minutes while a tool runs.
333
+ - **Copilot turns that fail, fail well**: the failure shows at once, what the agent had written stays above the note, Stop works on a stuck turn, a finished reply survives a relay restart, and the commands of a runtime that died are stopped. A message you send while a command runs waits for the command instead of cutting it short.
334
+ - **Agents on paired relays can answer each other**: a relay you mentioned stays in reach for the whole session, and an agent that was asked from another relay can answer, ask back or report later. See [Remote relays](#remote-relays).
335
+ - **Less scrolling in a running turn**: subagents are folded to a one-line header that says what they are doing, and a plan board sits inside the reply it belongs to; its choice also sets the session's mode.
336
+ - **Failure notes that help**: no more advice to restart the relay, and a Claude account problem (billing, sign-in, access) says what to check. The relay keeps its console in `relay-console.log`.
337
+
338
+ ### New in 0.9.5
339
+
340
+ - **A misplaced Claude reply no longer blocks the conversation**: when a reply is published as a *background continuation* instead of under your message, the message closes after about 45 seconds of silence with a small note naming that reply, and the next message runs normally. It used to hold every later message for up to 5 minutes.
341
+ - **Effort for remote sessions**: an agent can set the reasoning effort of a session it starts or prompts on another relay; a new session takes the calling session's effort when the model there supports it. See [Remote relays](#remote-relays).
342
+ - **Waiting on another relay is steadier**: the wait no longer stops at an interim answer while the remote agent goes on in a turn of its own, for example while its subagents work.
343
+ - **Worker logs on Windows**: session workers now write the same `worker-<session>.log` as on Linux, in the relay's log directory, while the console window keeps showing the output.
344
+
329
345
  ### New in 0.9.4
330
346
 
331
347
  - **Remote relays**: pair two OAR relays in **Settings → Relays** by pasting the other relay's web address; the pairing works both ways. An agent in any session can then work on the other relay when you ask it to: list and read its sessions, prompt one, start a new one, wait for the reply, and pass a question the remote agent asks back to you. See [Remote relays](#remote-relays).
@@ -442,7 +458,9 @@ Use **➡️ Share conversation** in the `⋯` menu to publish a read-only link.
442
458
 
443
459
  ### The conversation menu
444
460
 
445
- Besides the entries above, the `⋯` menu holds **✍️ Edit conversation title**, **🖥️ Inspect tmux console** (a read-only view of the session's tmux pane), **🤗 Select Models**, **⚙️ Settings**, **🌄 Restart web relay** (queued until the current turn is idle), **💤 Suspend host**, and **☠️ Kill session** (stops the conversation's worker; an active turn then needs a retry or a new message). Its header shows the queue counts with **🚮 Empty queue**.
461
+ Besides the entries above, the `⋯` menu holds **✍️ Edit conversation title**, **🖥️ Inspect tmux console** (a read-only view of the session's tmux pane), **🤗 Select Models**, **⚙️ Settings**, **🌄 Restart web relay** (queued until the current turn is idle), **💤 Suspend host** (Windows; see below), and **☠️ Kill session** (stops the conversation's worker; an active turn then needs a retry or a new message). Its header shows the queue counts with **🚮 Empty queue**.
462
+
463
+ **💤 Suspend host** puts the machine the relay runs on to sleep, once the work is done. The confirmation lists what is still active. The relay then queues the suspend and fires it after 2 minutes with nothing active: no queued, running or parked turn, no background task of a live worker, and no open GitHub Actions run in the repository of a conversation that was busy since the request (read through `gh`; a repository whose state cannot be read blocks for 15 minutes, then is ignored). With nothing running when you confirm, it fires after a 30-second countdown. While a suspend is queued, every device shows a banner with the blockers or the countdown and a **Cancel** button, and a push notification reports the suspend. A relay restart drops a queued suspend and says so. **Show Suspend host action** in Settings hides the entry per browser.
446
464
 
447
465
  ## Settings and configuration reference
448
466
 
@@ -539,6 +557,8 @@ The config file is `server/config.json` in a git checkout and `~/.oar/config.jso
539
557
  | `COPILOT_WEB_RELAY_LOG_DIR` | Log directory |
540
558
  | `OAR_STATE_ROOT` | State root of a global install, instead of `~/.oar` / `%APPDATA%\oar` |
541
559
  | `OAR_NO_UPDATE_CHECK=1` | Never contact oar.sh, not even for a manual update check |
560
+ | `OAR_HOST_SUSPEND_DRY_RUN=1` | **Suspend host** logs the suspend instead of sleeping the machine |
561
+ | `OAR_MCP_SERVER=0` | Do not attach OAR's MCP server to Grok and the Copilot extension engine; they get the preview instructions as text again, and no `remote_relay` tool |
542
562
  | `COPILOT_SDK_PATH` | The Copilot CLI runtime's `copilot-sdk` directory, instead of the newest one in the CLI's package cache |
543
563
  | `COPILOT_PKG_DIR` | An additional Copilot CLI package cache to search |
544
564
  | `GH_TOKEN`, `GITHUB_TOKEN` | GitHub token for the Copilot usage card, instead of `gh auth token` |
@@ -666,14 +686,23 @@ locked in a conversation until you mention it in one of your messages: type `@`
666
686
  from the list, or write its name or its host name. An IP address or `localhost` never counts as a
667
687
  host name; a relay whose name is itself an address is unlocked only with a leading `@`. The
668
688
  mention unlocks that relay for the rest of
669
- the conversation. Prompts that arrive from another relay's agent never unlock anything, and a
670
- chain of relays prompting relays stops after two hops.
689
+ the conversation, whatever prompts arrive in it later.
690
+
691
+ **Agents can answer each other.** When an agent on a paired relay writes to a conversation, that
692
+ relay is open to the conversation from then on, within what **Agents may** allows, so the agent
693
+ that was asked can answer, ask back or report later. A prompt from an agent never unlocks any
694
+ other relay. A prompt that already crossed two relays is not passed on to a third one unless you
695
+ unlocked that relay yourself; reading and waiting are never limited. When the agent of a
696
+ conversation has sent 30 prompts to agents on other relays without a message from you, the next
697
+ one asks you first with a question card; **Allow** starts the count again.
671
698
 
672
699
  **What agents can do.** Every provider gets the same `remote_relay` tool (Claude, Cursor and
673
700
  Copilot directly; Grok and the Copilot extension engine through OAR's MCP server). Per paired
674
701
  relay, **Agents may** limits it to *read only*, *read and prompt* (existing sessions) or *full*
675
702
  (also create sessions, answer questions, stop turns, archive). In the *ask* and *plan* relay
676
- modes, every write action first asks you with a question card.
703
+ modes, every write action first asks you with a question card. For a session it starts, an agent
704
+ can set the provider, model, relay mode and reasoning effort; what it leaves out follows the
705
+ session it works in, as far as the other relay offers it.
677
706
 
678
707
  When an agent prompts a session on another relay, that relay shows a **↗ from …** badge with the
679
708
  sending relay, session and model on the message, and marks sessions an agent created in the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oar-sh/oar",
3
- "version": "0.9.4",
3
+ "version": "0.9.6",
4
4
  "type": "module",
5
5
  "description": "OAR — Open Agent Relay: drive your local coding agents from any browser",
6
6
  "license": "MIT",
@@ -30,6 +30,7 @@
30
30
  "server/uploads/image_input.md",
31
31
  "shared/",
32
32
  "!shared/**/*.test.mjs",
33
+ "!shared/**/*-test-harness.mjs",
33
34
  "!shared/**/fixtures/",
34
35
  ".github/extensions/web-relay/",
35
36
  "!.github/extensions/web-relay/**/*.test.mjs",
@@ -63,6 +63,7 @@ export const REMOTE_RELAY_TOOL_ZOD_SHAPE = {
63
63
  model: text('model'),
64
64
  cwd: text('cwd'),
65
65
  mode: choice('mode', REMOTE_RELAY_MODES),
66
+ effort: text('effort'),
66
67
  title: text('title'),
67
68
  question_id: text('question_id'),
68
69
  answer: text('answer'),