@oar-sh/oar 0.9.9 → 0.9.10

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 (85) hide show
  1. package/CHANGELOG.md +247 -0
  2. package/README.md +78 -9
  3. package/bin/bat/oar.bat +46 -46
  4. package/package.json +2 -2
  5. package/server/README.md +286 -3
  6. package/server/claude-worker/claude-remote-relay-tool.mjs +10 -0
  7. package/server/claude-worker/claude-session-process.mjs +35 -1
  8. package/server/claude-worker/claude-turn-publisher.mjs +3 -0
  9. package/server/claude-worker/sdk-message-normalizer.mjs +31 -4
  10. package/server/claude-worker/subagent-timeline-recorder.mjs +572 -0
  11. package/server/copilot-worker/copilot-sdk-session-process.mjs +3 -1
  12. package/server/cursor-worker/cursor-sdk-adapter.mjs +56 -2
  13. package/server/db-schema.mjs +36 -0
  14. package/server/migrations/0009-subagent-timeline.mjs +135 -0
  15. package/server/migrations/0010-subagent-timeline-pruning.mjs +89 -0
  16. package/server/migrations/0011-relay-event-subscriptions.mjs +125 -0
  17. package/server/migrations/0012-cross-relay-event-subscriptions.mjs +116 -0
  18. package/server/public/app/agent-exchange-fold-view.mjs +248 -0
  19. package/server/public/app/api-client.js +61 -0
  20. package/server/public/app/archive-confirm.mjs +28 -0
  21. package/server/public/app/ask-user-view.js +3 -0
  22. package/server/public/app/attachments-view.js +516 -6
  23. package/server/public/app/background-tasks-view.mjs +18 -26
  24. package/server/public/app/bootstrap.js +186 -35
  25. package/server/public/app/chat-actions-menu-state.mjs +116 -0
  26. package/server/public/app/claude-cloud-conversation-ui.js +10 -22
  27. package/server/public/app/composer-control-state.mjs +16 -0
  28. package/server/public/app/composer-keyboard.mjs +54 -0
  29. package/server/public/app/conversation-view.js +361 -85
  30. package/server/public/app/explorer-edit-state.mjs +68 -0
  31. package/server/public/app/host-suspend-ui.js +6 -3
  32. package/server/public/app/journal-view.js +146 -26
  33. package/server/public/app/no-conversation-state.mjs +58 -0
  34. package/server/public/app/provider-readiness.mjs +120 -0
  35. package/server/public/app/relay-board-view.js +3 -0
  36. package/server/public/app/relay-event-subscriptions-ui.js +184 -0
  37. package/server/public/app/relay-event-view.mjs +234 -0
  38. package/server/public/app/settings-modal.js +53 -1
  39. package/server/public/app/socket-handlers.js +59 -28
  40. package/server/public/app/store.js +19 -2
  41. package/server/public/app/subagent-row.mjs +138 -0
  42. package/server/public/app/subagent-timeline-view.mjs +186 -0
  43. package/server/public/app/subagent-viewer-watch.mjs +50 -0
  44. package/server/public/app/subagent-viewer.mjs +534 -0
  45. package/server/public/app/task-row-format.mjs +29 -0
  46. package/server/public/app/thinking-follow.js +4 -0
  47. package/server/public/app/transcript-jump-bottom-view.mjs +290 -0
  48. package/server/public/app/transcript-jump-bottom.mjs +99 -0
  49. package/server/public/app/transcript-reload-freshness.mjs +25 -0
  50. package/server/public/app/update-ui.js +1 -1
  51. package/server/public/app/viewer-history.mjs +5 -3
  52. package/server/public/index.html +396 -5
  53. package/server/repositories/message-repository.mjs +29 -3
  54. package/server/repositories/question-repository.mjs +9 -3
  55. package/server/repositories/remote-relay-repository.mjs +6 -2
  56. package/server/repositories/subagent-timeline-statements.mjs +170 -0
  57. package/server/routes/ask-user-routes.mjs +3 -0
  58. package/server/routes/messages-routes.mjs +515 -50
  59. package/server/routes/relay-event-routes.mjs +157 -0
  60. package/server/routes/remote-relay-routes.mjs +15 -0
  61. package/server/routes/sessions-routes.mjs +72 -0
  62. package/server/server-runtime.mjs +245 -62
  63. package/server/services/explorer-entry-name.mjs +26 -0
  64. package/server/services/explorer-mutations.mjs +122 -0
  65. package/server/services/preview-proxy-server.mjs +41 -23
  66. package/server/services/push-dispatch-service.mjs +18 -0
  67. package/server/services/relay-event-link-service.mjs +289 -0
  68. package/server/services/relay-event-service.mjs +1935 -0
  69. package/server/services/relay-shutdown-gate.mjs +31 -0
  70. package/server/services/remote-relay-caller-context.mjs +52 -7
  71. package/server/services/remote-relay-dispatcher.mjs +494 -2
  72. package/server/services/remote-relay-registry-service.mjs +11 -1
  73. package/server/services/session-activity-tracker.mjs +487 -0
  74. package/server/services/session-history-refresh-service.mjs +7 -0
  75. package/server/services/stale-attempt-log-limiter.mjs +96 -0
  76. package/server/services/stale-recovery-clock-guard.mjs +57 -0
  77. package/server/services/stale-subagent-run-sweep.mjs +109 -0
  78. package/server/services/subagent-record-retention.mjs +164 -0
  79. package/server/services/subagent-timeline-service.mjs +328 -0
  80. package/server/services/subagent-viewer-rooms.mjs +119 -0
  81. package/server/services/upgrade-socket-guard.mjs +16 -0
  82. package/shared/relay-events.mjs +371 -0
  83. package/shared/remote-relay-contract.mjs +157 -5
  84. package/shared/subagent-record-retention.mjs +31 -0
  85. package/shared/subagent-timeline.mjs +79 -0
package/CHANGELOG.md CHANGED
@@ -5,6 +5,253 @@ All notable changes to OAR are documented here. The format follows
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.9.10] — 2026-10-12
9
+
10
+ Highlights: sessions watch other sessions. An agent subscribes to sessions on
11
+ its own relay or on a paired one and is woken by what happens there:
12
+ questions waiting for an answer, errors, replies, finished background work,
13
+ sessions gone quiet. A subscription only notifies; it approves nothing, and
14
+ the first one in a conversation asks you on a card. Also new: a sub-agent's
15
+ whole record opens in a viewer, a note between agents and its reply fold into
16
+ one row, `remote_relay` reports what the provider plans have left, the file
17
+ browser creates, renames and deletes folders and files, a ⌄ button jumps back
18
+ to the latest message, archiving an active session asks first, and Settings
19
+ opens with no conversation open.
20
+
21
+ ### Upgrade notes
22
+
23
+ - **Running sessions pick up the new tools when their worker restarts.** A
24
+ session worker builds its `remote_relay` tool when it starts, so a worker
25
+ that was already running before the update offers neither `subscribe` nor
26
+ `usage`, and records no sub-agent timeline, until it is restarted: **☠️
27
+ Kill session** on an idle session is enough, the next message starts a new
28
+ worker. The database migrations run by themselves on the first start.
29
+
30
+ ### Added
31
+
32
+ - **A session can watch other sessions and be woken by what happens there.**
33
+ With `remote_relay`'s new `subscribe` an agent asks to be told about
34
+ questions waiting for an answer, errors (a failed turn, the usage limit, a
35
+ worker that died), replies, background agents and shell tasks that finish
36
+ or fail, sessions that went quiet while working, and the moment nothing
37
+ runs any more, for listed sessions or all of them. OAR writes a short event
38
+ message into the watcher's conversation: a new turn when it is idle, a
39
+ steer into the running turn where the provider steers, the next turn
40
+ otherwise. The message says it is automated and approves nothing, and shows
41
+ as a compact row, never as one of your messages. Events within 30 s come
42
+ together; past 20 wake-ups an hour the rest comes as a digest every ten
43
+ minutes, while questions and errors still come at once. An optional
44
+ heartbeat says the watching works while anything runs. Subscriptions
45
+ survive a restart and end on unsubscribe, when the watcher or the watched
46
+ session is archived, or after firing once. The first subscribe of a
47
+ conversation, and any to all sessions, asks you on an approval card;
48
+ `transfer_subscriptions` hands every subscription of an old watcher to a
49
+ new one after your approval. The watcher's header shows **watching N
50
+ sessions** with an Unsubscribe button per subscription, and a watched
51
+ session shows a **watched** badge.
52
+
53
+ - **Watch sessions on a paired relay.** `subscribe`, `unsubscribe`,
54
+ `list_subscriptions` and `transfer_subscriptions` also take a paired
55
+ relay, with the same events and options. The relay must be unlocked in
56
+ the conversation and allow at least reading, and the other relay must
57
+ accept other relays' agents; the approval card names that relay, and
58
+ nothing is asked there. This relay fetches the events every few seconds
59
+ over the pairing (a pairing in one direction is enough), and they arrive
60
+ like those of its own sessions, their lines starting with the relay's
61
+ name. If the link drops, the other relay keeps what happens (up to 500
62
+ events per subscription, for 7 days) and delivers it as one row tagged
63
+ **replayed** once it answers again, questions and errors first, between
64
+ "link lost" and "link restored". Subscriptions survive restarts of either
65
+ relay and end on unsubscribe (reaching the other relay later if it was
66
+ offline), when the watcher or the watched session is archived, or when
67
+ the pairing is removed. An older relay answers that it needs an update.
68
+
69
+ - **list_sessions and read_session say what a session is doing.** `state`
70
+ is `running_turn`, `background_only` or `idle`, and `tasks` lists its
71
+ background agents and shell tasks with when they started, when they last
72
+ did something, how they ended and the exit code where it is known. A
73
+ session with background agents running no longer reads as inactive.
74
+
75
+ - **Agents can check what the provider plans have left.** `remote_relay`'s
76
+ new `usage` action answers, for the relay an agent runs on or a paired
77
+ one, what each provider's plan has left: Cursor's spend against its
78
+ limit and when it resets, Claude's windows and whether a usage limit has
79
+ paused turns, Copilot and Grok where they are tracked; a provider that is
80
+ not set up says so. It needs only read permission and returns no account
81
+ names, addresses or tokens.
82
+
83
+ - **Notes between agents fold away.** A prompt another agent sent a
84
+ session, from a paired relay or from this one, and the session's reply to
85
+ it show as one row, "note from linux-test · report builder · replied",
86
+ which opens to both, on a desktop and on a phone. Your own messages and
87
+ the replies to them stay as they are, and a turn you steered a message
88
+ into, or one with a question or card open for you, is never folded. A
89
+ note still being worked on stays open and folds by itself once it is
90
+ answered; one you fold or open yourself stays as you left it.
91
+
92
+ - **Open a sub-agent's whole record.** A sub-agent's title in the composer's
93
+ background-task panel opens a viewer with everything the agent did, in
94
+ order: its thoughts, every tool call with its full input and its result,
95
+ its reply text and the agents it started itself, live while it runs. The
96
+ finished reply lists the turn's sub-agents as the same rows, with model,
97
+ type, tokens, time and how they ended, so the record stays one click away.
98
+ A child agent opens in place, and back returns to its parent. Stop is in
99
+ the viewer too. Claude records everything; Copilot and Cursor show what
100
+ they report. A tool result is kept to its first 4 KB, and a record to 2000
101
+ entries. Only a page with the viewer open receives the record as it grows.
102
+
103
+ - **Choose how long sub-agent tool output is kept.** Settings → General →
104
+ **Keep sub-agent tool output** offers 7, 14, 30 or 90 days (the default)
105
+ or always. Once a finished agent's record is older than that, its tool
106
+ inputs and results are removed, once a day; its thoughts, its reply, its
107
+ notes and the one-line summary of each tool call stay, and the viewer says
108
+ what was removed.
109
+
110
+ - **Folders can be created in the file browser.** A **📁+ New folder**
111
+ button in the toolbar opens a row at the top of the folder pane; the name
112
+ is typed there and Enter creates the folder inside the open one and opens
113
+ it. It works in the repository, on the drives and in the session folder,
114
+ and in the folder picker of **New chat** and **Change CWD**, so a new
115
+ project folder can be made and picked in one go. A taken name or a name
116
+ the system refuses is reported under the input.
117
+
118
+ - **Files and folders can be renamed and deleted in the file browser.** A
119
+ right-click, or a long press on a phone, on a folder in the tree or a file
120
+ in the folder pane opens a menu with **Rename** and **Delete**; a folder's
121
+ menu also offers **New folder here**. Rename edits the name in place: Enter
122
+ saves, Escape cancels, and an unchanged name is a cancel. Delete asks in a
123
+ dialog that says what goes: a file, an empty folder, or a folder with so
124
+ many files and folders in it; for a folder with content the folder's name
125
+ must be typed before the red button enables. Links are never followed or
126
+ changed; roots (the repository root, `/`, a drive, the session folder)
127
+ cannot be renamed or deleted, and the session folder is read-only apart
128
+ from new folders.
129
+
130
+ - **Jump back to the latest message.** A round ⌄ button above the composer
131
+ fades in once the transcript is about a screen above its end, and its
132
+ badge counts the rows that arrived since (an agent's folded exchange
133
+ counts once). A click goes back to the end, smoothly up to three screens
134
+ away and at once beyond, and the transcript follows new rows again; from
135
+ a search result or a pin deep in the history it loads the end.
136
+
137
+ ### Changed
138
+
139
+ - **Archiving a session whose process is still active asks first.** A green
140
+ session, idle or working, now opens "Archive this session? Its process is
141
+ still active." from the row's 🗄, its menu and the chat menu alike; Cancel
142
+ or Escape keeps it. An inactive session archives at once, as before.
143
+
144
+ - **A message from another device no longer pulls a reader who scrolled up
145
+ down to the end.** Another client's message, an agent's prompt included,
146
+ is followed like any live row: only while the transcript is at its end.
147
+
148
+ - The npm package guard allows 6.75 MiB unpacked (was 6.5 MiB); the
149
+ sub-agent records and the event subscriptions, now across paired relays,
150
+ grew the server.
151
+
152
+ ### Fixed
153
+
154
+ - **A dropped websocket can no longer take the relay down.** A browser that
155
+ reset its connection while a preview's websocket, or any websocket to the
156
+ relay, was still being set up ended the whole relay with `read ECONNRESET`.
157
+
158
+ - **A queued relay restart no longer waits forever on a busy relay.** The
159
+ restart used to wait for the whole queue to empty while new messages kept
160
+ being handed to workers, so each new turn postponed it again. It now waits
161
+ for the running turns only; queued messages are held until the relay is
162
+ back and run then. The banner says which.
163
+
164
+ - **Waking from sleep no longer restarts the running turns.** After the host
165
+ slept for hours, every worker heartbeat looked hours old the moment the
166
+ relay's timer fired again, so live turns were recovered as stale and their
167
+ messages delivered a second time while the workers' own replies were
168
+ refused, hundreds of warnings in minutes. The relay now notices the clock
169
+ jump and gives live workers a full inactivity window to report before
170
+ anything is recovered; a superseded attempt's refusals are logged once and
171
+ summarized, not line by line.
172
+
173
+ - **A Cursor turn waiting on a long command is no longer ended as stalled.**
174
+ When the agent started a command in the background and then waited for
175
+ it, the wait counted as a model request, and the turn was ended after five
176
+ minutes of quiet. The wait now gets the 30 minutes a running tool gets.
177
+
178
+ - **An event's fields cannot add lines to a watcher's event message.** A
179
+ line break in a session title, a task description or a field of a paired
180
+ relay's event could start a line without the `[relay event]` prefix. Every
181
+ field is now written as one line, and what a paired relay sends is stored
182
+ field by field from a fixed list, never as the payload it sent.
183
+
184
+ - **The relay event tables no longer grow without bound.** Delivered events
185
+ and ended subscriptions were never deleted, and the retention check for
186
+ paired relays scanned the whole event table every 30 seconds. Delivered
187
+ events now go after two hours (the wake-up count reads the last hour),
188
+ ended subscriptions after a week, and the check uses its index.
189
+
190
+ - **The "watching N sessions" list opens in full.** The header cut it off,
191
+ so on a phone it did not show at all. It now opens under the chip on top
192
+ of the page, and Escape or a tap elsewhere closes it.
193
+
194
+ - **Sub-agent runs a worker never closed no longer count as running work.**
195
+ A run whose worker went away without reporting its end stayed `running`
196
+ for good, so a subscription to all sessions never saw `all_idle` and
197
+ reported the session stalled over and over, and the suspend watchdog saw
198
+ work. Such runs are now closed shortly after the relay starts and every
199
+ ten minutes (never while their worker is alive), and a run row not touched
200
+ for a day is not counted meanwhile.
201
+
202
+ - **A background agent's output stays with the turn that started it.** Its
203
+ thoughts and tool calls no longer land in a later turn's reply, and between
204
+ turns they are no longer lost.
205
+
206
+ - **A background agent no longer shows as finished the moment it starts.**
207
+ Its row now ends when the agent does.
208
+
209
+ - **A sub-agent stopped by a failed turn or a worker crash settles on screen
210
+ without a reload.** The update used to arrive without its turn, and the
211
+ page ignored it.
212
+
213
+ - **The transcript keeps following the live reply when a background task
214
+ starts.** The task panel appearing, opening or growing shrank the
215
+ transcript from below and left it that much short of the end, so the next
216
+ live row stopped following. It now stays at the end.
217
+
218
+ - **The transcript follows the agent's reply as it streams in.** Each piece
219
+ of streamed text was scrolled to before it was shown, so the first new
220
+ line left the reader short of the end and the rest was not followed.
221
+
222
+ - **Going back to the end while a turn finishes sticks.** The reload at the
223
+ end of a turn put a reader back where they were when it was asked for, so
224
+ scrolling to the end, or pressing ⌄, during it was undone and the next
225
+ rows were not followed. It now keeps where the reader is when it lands.
226
+
227
+ - **⌄ no longer opens the keyboard on a phone.** The composer is no longer
228
+ focused when the page loads on a phone, and ⌄ keeps it focused only while
229
+ the keyboard is already open, so a tap on it just scrolls.
230
+
231
+ - **A message that arrives while the conversation reloads stays on screen.**
232
+ A reload asked for just before another client's message or a reply landed
233
+ rebuilt the transcript without it, until the next reload. The reload now
234
+ asks again when rows arrived on the way.
235
+
236
+ - **A message sent right after switching conversations shows up where it was
237
+ sent.** A quick run through the list, or a click back on the conversation
238
+ still open, could hand the view to an earlier click once the draft of the
239
+ conversation left behind had saved. The message then went to that
240
+ conversation, or its bubble and the live reply stayed hidden until the
241
+ conversation was opened again. A click now opens its conversation at once.
242
+
243
+ - **The live reply stays on screen right after a send.** A poll answer read
244
+ just before the agent took the message could end the turn on the page, and
245
+ a reply update lost on a busy connection left the bubble empty; both waited
246
+ for the conversation to be opened again.
247
+
248
+ - **Settings opens with no conversation open.** On a fresh install the ⋯
249
+ menu did nothing until a chat existed, so no provider could be turned on.
250
+ The menu now works on an empty screen, with the entries that need a
251
+ conversation left out, and the empty screen and New Chat point to
252
+ Settings → Providers until a provider is ready instead of starting a
253
+ Copilot chat that cannot run.
254
+
8
255
  ## [0.9.9] — 2026-10-07
9
256
 
10
257
  Highlights: `oar` opens a menu and gains stop, restart, status, url and
package/README.md CHANGED
@@ -35,27 +35,33 @@ OAR is under active development, so expect occasional rough edges and some provi
35
35
 
36
36
  <table>
37
37
  <tr>
38
- <td width="42%" rowspan="3" valign="top">
39
- <a href="https://oar.sh/assets/shots/cfmail-mobile.png?v=0.9.8"><img src="https://oar.sh/assets/shots/cfmail-mobile.png?v=0.9.8" alt="OAR on a phone: a finished agent turn in a conversation, with the composer below"></a>
38
+ <td width="42%" rowspan="4" valign="top">
39
+ <a href="https://oar.sh/assets/shots/cfmail-mobile.png?v=0.9.10"><img src="https://oar.sh/assets/shots/cfmail-mobile.png?v=0.9.10" alt="OAR on a phone: a finished agent turn in a conversation, with the composer below"></a>
40
40
  <br><sub>On a phone: the same conversations, installable as an app.</sub>
41
41
  </td>
42
42
  <td width="58%" valign="top">
43
- <a href="https://oar.sh/assets/shots/cfmail-agents.png?v=0.9.8"><img src="https://oar.sh/assets/shots/cfmail-agents.png?v=0.9.8" alt="Background subagents running in parallel in the task panel, each with live token counts, elapsed time, and its own Stop button"></a>
43
+ <a href="https://oar.sh/assets/shots/cfmail-agents.png?v=0.9.10"><img src="https://oar.sh/assets/shots/cfmail-agents.png?v=0.9.10" alt="Background subagents running in parallel in the task panel, each with live token counts, elapsed time, and its own Stop button"></a>
44
44
  <br><sub>Agents working in parallel, each with live token counts and its own Stop.</sub>
45
45
  </td>
46
46
  </tr>
47
47
  <tr>
48
48
  <td valign="top">
49
- <a href="https://oar.sh/assets/shots/cfmail-question.png?v=0.9.8"><img src="https://oar.sh/assets/shots/cfmail-question.png?v=0.9.8" alt="A question card in the middle of a turn: the agent offers several choices and a free-text reply"></a>
49
+ <a href="https://oar.sh/assets/shots/cfmail-question.png?v=0.9.10"><img src="https://oar.sh/assets/shots/cfmail-question.png?v=0.9.10" alt="A question card in the middle of a turn: the agent offers several choices and a free-text reply"></a>
50
50
  <br><sub>When the agent needs a decision, it asks, and waits for your answer.</sub>
51
51
  </td>
52
52
  </tr>
53
53
  <tr>
54
54
  <td valign="top">
55
- <a href="https://oar.sh/assets/shots/pingcf-thoughts.png?v=0.9.8"><img src="https://oar.sh/assets/shots/pingcf-thoughts.png?v=0.9.8" alt="An expanded Thoughts block showing the agent's reasoning above its reply"></a>
55
+ <a href="https://oar.sh/assets/shots/pingcf-thoughts.png?v=0.9.10"><img src="https://oar.sh/assets/shots/pingcf-thoughts.png?v=0.9.10" alt="An expanded Thoughts block showing the agent's reasoning above its reply"></a>
56
56
  <br><sub>Read the reasoning, not just the answer.</sub>
57
57
  </td>
58
58
  </tr>
59
+ <tr>
60
+ <td valign="top">
61
+ <a href="https://oar.sh/assets/shots/pagewright-watching.png?v=0.9.10"><img src="https://oar.sh/assets/shots/pagewright-watching.png?v=0.9.10" alt="A session watching two other sessions: a finished reply arrives as a compact relay-event row, the watching 2 sessions list is open with Unsubscribe, and a request to also watch a session on a paired relay waits on an Allow / Deny card"></a>
62
+ <br><sub>Agents that watch agents: woken by events, approving nothing.</sub>
63
+ </td>
64
+ </tr>
59
65
  </table>
60
66
 
61
67
  <a id="quick-start"></a>
@@ -101,7 +107,7 @@ The relay is started as a systemd user service where a systemd user session exis
101
107
 
102
108
  ```bash
103
109
  curl -fsSL oar.sh/install | sh -s -- --yes
104
- curl -fsSL oar.sh/install | OAR_VERSION=0.9.9 OAR_PORT=3400 sh
110
+ curl -fsSL oar.sh/install | OAR_VERSION=0.9.10 OAR_PORT=3400 sh
105
111
  ```
106
112
 
107
113
  The installer needs `curl` or `wget`, and `tar` when it fetches Node.js. It has no Node.js build for musl systems (Alpine) or for CPUs other than x64 and arm64; there, install Node.js yourself first. Releases before 0.9.8 are installed the same way, but their `oar setup` cannot start the relay: see [Start the relay](#start-the-relay).
@@ -114,7 +120,7 @@ Read it before you run it: [oar.sh/install](https://oar.sh/install).
114
120
  irm oar.sh/install.ps1 | iex
115
121
  ```
116
122
 
117
- The PowerShell installer (PowerShell 5 or newer) expects Node.js 22.13 or newer to be installed: 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.9'` before the command.
123
+ The PowerShell installer (PowerShell 5 or newer) expects Node.js 22.13 or newer to be installed: 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.10'` before the command.
118
124
 
119
125
  ### npm
120
126
 
@@ -423,6 +429,16 @@ Use **🤗 Select Models** to choose which variants show up in the composer, the
423
429
 
424
430
  ## Highlights
425
431
 
432
+ ### New in 0.9.10
433
+
434
+ - **Watching sessions**: an agent subscribes to other sessions, on its own relay or on a paired one, and is woken by what happens there: a question waiting for an answer, an error, a reply, a background task that finished or failed, a session gone quiet, nothing running any more. Each wake-up is a short automated message in its conversation, shown as a compact row. Subscriptions only notify; they approve nothing. The first subscribe of a conversation asks you on a card, and the header shows **watching N sessions** with an Unsubscribe per subscription. See [Watching sessions](#watching-sessions).
435
+ - **Notes between agents fold away**: a prompt another agent sent a session and the session's reply show as one row that opens to both.
436
+ - **A sub-agent's whole record**: its title in the task panel, or its row under the finished reply, opens a viewer with its thoughts, every tool call with input and result, and its reply, live while it runs. **Keep sub-agent tool output** under Settings → General decides how long tool inputs and results are kept.
437
+ - **What the plans have left**: `remote_relay`'s `usage` action tells an agent what each provider's plan has left on its own relay or a paired one, before it starts work that costs; `list_sessions` and `read_session` say whether a session is running a turn, only background work, or nothing.
438
+ - **Folders in the file browser**: **📁+ New folder**, and a right-click (a long press on a phone) on a file or folder to rename or delete it.
439
+ - **Back to the latest message**: a ⌄ button above the composer, with a count of the rows that arrived since, jumps back to the end, and the transcript follows new rows again.
440
+ - **Fewer surprises**: archiving a session whose process is still active asks first, Settings opens with no conversation open (so a fresh install can turn on a provider), a queued relay restart no longer waits forever on a busy relay, and waking the host from sleep no longer restarts the running turns.
441
+
426
442
  ### New in 0.9.7
427
443
 
428
444
  - **Claude Cloud (experimental, off by default)**: a chat that runs Claude Code in a sandbox at Anthropic, on its own clone of a GitHub repository. Pick the repository and branch from a list, follow the turn live, answer its questions, stop it, change the model between turns; the results come back as pushed branches. See [Claude Cloud](#claude-cloud) for what it needs and what it does with the Claude login.
@@ -506,6 +522,7 @@ Use **🤗 Select Models** to choose which variants show up in the composer, the
506
522
  - **Steer or queue.** On Claude, and on Copilot's SDK engine, a message you send during a live turn is pushed into that turn; the send button reads **Steer**. A folded message keeps a compact *merged* marker in the transcript, and one the agent answers separately gets its own reply. While the turn waits on a question card or plan approval, or is compacting, the button reads **Queue** and its tooltip says why: the message waits as a pending bubble (with **Cancel**) and steers in once the card is answered or the compaction ends. On the other runtimes, and on Copilot's Extension engine, messages sent during a turn queue behind it, and the button reads **Queue**.
507
523
  - **Stop** is on the running reply's bubble, whose header stays pinned at the top while a long reply scrolls; the send button never turns into Stop. Queued messages carry their own **Cancel** until they are picked up. A steered message that a Stop cut off is marked *Stopped with the turn — not answered* and offers **Resend**; the button then reads **Resent** on every device.
508
524
  - On Copilot, **Stop** ends only the reply: background agents the turn started keep running under their own **Stop** in the task panel.
525
+ - The transcript follows new rows while you are at its end. Once you scroll about a screen up, a round **⌄** button fades in above the composer, with a badge counting the rows that arrived since; it takes you back to the end and the transcript follows again. From a search result or a pin deep in the history it loads the end first.
509
526
 
510
527
  ### Question cards
511
528
 
@@ -519,6 +536,7 @@ Use **🤗 Select Models** to choose which variants show up in the composer, the
519
536
  - The composer's task panel lists background work that outlives the reply that started it: backgrounded commands, subagents, monitors, and workflows on Claude; background agents and detached shells on Copilot's SDK engine. Each row shows its state, the model it runs on, the command it is running right now, its token count, and its own **Stop**. The panel scrolls when it holds more tasks than fit.
520
537
  - An **Ultracode** workflow folds out into a tree of its phases and agents (state, model, tokens). When it finishes, the summarizing reply keeps a collapsed *Finished background task* card holding the final tree, which survives reloads.
521
538
  - A background task's result arrives as its own *continuation* reply.
539
+ - A sub-agent's title in the panel opens its whole record: its thoughts, every tool call with its full input and its result, its reply and the agents it started itself, in order and live while it runs, with its own **Stop**. The finished reply lists the turn's sub-agents as the same rows (model, type, tokens, time, how they ended), so the record stays one click away; a child agent opens in place and back returns to its parent. Claude records everything; Copilot and Cursor show what they report. A tool result is kept to its first 4 KB and a record to 2000 entries; **Keep sub-agent tool output** in Settings → General decides how long tool inputs and results are kept.
522
540
  - **⚙️ Settings → General → Background task timeout** (default 4 h; 0 = no limit, up to 10 h) caps how long background tasks alone may keep a Claude or Copilot session running after its reply. When the limit is reached, the tasks are stopped.
523
541
 
524
542
  ### Drafts across devices
@@ -530,13 +548,14 @@ The composer draft, attachments included, is saved per conversation on the relay
530
548
  - **Filter conversations…** above the list matches titles, ignoring case. It clears with **×** or Escape, and while it is active it loads older pages, so it searches every conversation, not just the loaded ones.
531
549
  - **🔍** in the conversation header searches message text across all conversations.
532
550
  - On startup, the relay imports the Copilot sessions stored on the host (through the installed Copilot runtime) into its database, so they appear in the list with their history. If the runtime is unavailable, the import is reported as failed rather than guessed from the filesystem.
533
- - **Archive** (🗄 on the row, or **Archive conversation** in the `⋯` menu) takes a conversation out of the list without deleting anything: its session worker is stopped, and a Claude Cloud chat's session at Anthropic is archived with it. The 🗄 toggle above the list shows the archived conversations; **Unarchive** (📂) brings one back, cloud session included. An archived chat that is opened cannot be sent to until it is unarchived; a message that reaches it anyway (an agent's through a paired relay, say) unarchives it. A conversation that is still working is not archived — stop it first.
551
+ - **Archive** (🗄 on the row, or **Archive conversation** in the `⋯` menu) takes a conversation out of the list without deleting anything: its session worker is stopped, and a Claude Cloud chat's session at Anthropic is archived with it. The 🗄 toggle above the list shows the archived conversations; **Unarchive** (📂) brings one back, cloud session included. An archived chat that is opened cannot be sent to until it is unarchived; a message that reaches it anyway (an agent's through a paired relay, say) unarchives it. Archiving a conversation whose session process is still active (green in the list, idle or working) asks first, because archiving stops it. A conversation that is still working is not archived — stop it first.
534
552
  - A **right-click on a row** (a long press on a phone) opens a menu with Open, Edit title, Stop turn, Kill session, Archive or Unarchive, and Delete, so a session can be stopped or killed without opening it.
535
553
  - Deleting a conversation stops its session worker and removes its CLI session too (Copilot through the runtime, Claude by deleting that session's transcript). A Claude Cloud conversation's session at Anthropic is archived, not deleted. An imported Copilot session the relay never ran is only hidden: its CLI session stays, and nothing is stopped for it. A conversation that is still working — a running turn, one waiting on a question card or approval, or live background tasks — is not deleted, and the sidebar says why so you can stop it first.
536
554
 
537
555
  ### Files, git, and previews
538
556
 
539
557
  - Use **📁 Browse files** to inspect the workspace (💼), drives (📀), or the session's own folder (🕵️), and to open previews. The **Hidden** and **Heavy** toolbar filters are remembered per browser, and a refresh re-opens the folders you had expanded.
558
+ - **📁+ New folder** in the browser's toolbar opens a name field at the top of the folder pane; Enter creates the folder inside the open one and opens it. It works in the repository, on the drives, in the session folder, and in the folder picker of **New chat** and **Change CWD**. A right-click on a folder in the tree or a file in the folder pane (a long press on a phone) offers **Rename** and **Delete**, and on a folder **New folder here**. Delete says what goes, and a folder with content asks for its name to be typed first. Links are never followed or changed; roots (the repository root, `/`, a drive, the session folder) cannot be renamed or deleted, and the session folder is read-only apart from new folders.
540
559
  - Click the file and folder copy controls to insert `@file:...` / `@folder:...` tokens.
541
560
  - Workspace browsing follows the selected session's effective working directory. Running sessions keep their learned runtime directory, **🗂️ Change CWD** in the `⋯` menu changes the directory for the next launch, and `cd ...` typed in chat does not retarget the browser. A Claude Cloud chat has no **Change CWD**: it works on a clone at Anthropic.
542
561
  - Use **🌿 Git changes** in the conversation `⋯` menu to review the workspace repository: the header shows the branch with ahead/behind counts and a **⬇ Pull** button, and the list shows every staged, unstaged, and untracked file (deleted files struck through). Clicking a file opens a diff viewer with **Changes** and **Full file** modes; closing it returns to the still-open list.
@@ -580,7 +599,7 @@ Besides the entries above, the `⋯` menu holds **✍️ Edit conversation title
580
599
 
581
600
  ### Settings (⚙️ in the web UI)
582
601
 
583
- The modal is organised into six tabs — **General**, **Providers** (with a **Copilot**, **OpenAI**, **Claude**, **Claude Cloud**, **Grok**, and **Cursor** sub-tab), **Relays**, **Previews**, **Notifications**, and **Features** — and reopens on the tab you used last. Unless noted as per browser, these settings live in the relay database rather than the config file, and apply to every browser that connects:
602
+ The modal is organised into six tabs — **General**, **Providers** (with a **Copilot**, **OpenAI**, **Claude**, **Claude Cloud**, **Grok**, and **Cursor** sub-tab), **Relays**, **Previews**, **Notifications**, and **Features** — and reopens on the tab you used last. The `⋯` menu opens it with no conversation open too, so a fresh install can turn on a provider before the first chat; until a provider is ready, the empty screen and **New Chat** point to Settings → Providers. Unless noted as per browser, these settings live in the relay database rather than the config file, and apply to every browser that connects:
584
603
 
585
604
  | Tab | Setting | Default | What it does |
586
605
  | --- | ------- | ------- | ------------ |
@@ -588,6 +607,7 @@ The modal is organised into six tabs — **General**, **Providers** (with a **Co
588
607
  | General | Show Suspend host action | on | Per browser: shows **💤 Suspend host** in the conversation menu |
589
608
  | General | Max turn duration | `60 min` | Hard cap on how long one turn may run before the relay requeues it (0 = no limit, up to 10 h; see below) |
590
609
  | General | Background task timeout | `4 h` | How long background tasks alone may keep a Claude or Copilot session running after its reply; then they are stopped (0 = no limit, up to 10 h) |
610
+ | General | Keep sub-agent tool output | `90 days` | 7, 14, 30 or 90 days, or always: once a finished sub-agent's record is older, its tool inputs and results are removed (once a day); its thoughts, reply and the one-line summary of each tool call stay |
591
611
  | General | Autostart (Windows) | Off | *At sign-in* (a visible terminal after you log on) or *At system startup* (headless, no logon needed, one admin confirmation) |
592
612
  | General | Relay name | `OAR` (the hostname for paired relays) | Label of the installed PWA on every device, and this relay's name for [paired relays](#remote-relays) |
593
613
  | General | Check for updates automatically | off | Opt-in: the relay asks oar.sh for the latest version about twice a day. **Check for updates** works regardless; `OAR_NO_UPDATE_CHECK=1` blocks both |
@@ -839,6 +859,12 @@ is started with `provider: "claude-cloud"`, a `repo` (a GitHub URL or `owner/rep
839
859
  the default model of the other relay's Claude Cloud tab. Claude Cloud must be switched on there.
840
860
  A cloud session that exists can be read, prompted, waited for and stopped like any other.
841
861
 
862
+ Two read actions help an agent plan: `usage` says what each provider's plan has left on a relay
863
+ (Cursor's spend against its limit, Claude's windows and whether a usage limit has paused turns,
864
+ Copilot and Grok where they are tracked; never account names, addresses or tokens), and
865
+ `list_sessions` / `read_session` give each session's `state` (`running_turn`, `background_only` or
866
+ `idle`) with its background agents and shell tasks.
867
+
842
868
  A `send`, `create_session` or `wait` call waits for the reply for as long as the agent asks, up to
843
869
  **Longest wait per tool call** of the relay the agent runs on (10 minutes unless you changed it,
844
870
  see [Agent sessions](#agent-sessions)). When the time is up the agent gets what there is so far
@@ -907,6 +933,49 @@ to this relay's agents when they wait for a session on a paired relay, so the sl
907
933
  whether or not the switch above it is on. A turn that waits is still subject to **Max turn duration**
908
934
  under **General** (60 minutes by default), which does not know about the wait.
909
935
 
936
+ ### Watching sessions
937
+
938
+ An agent can watch other sessions instead of polling them: with `remote_relay`'s `subscribe` it
939
+ asks to be woken when something happens there. It works for every provider, on this relay (with
940
+ [agent sessions](#agent-sessions) switched on) and on a paired one.
941
+
942
+ **What wakes it.** A question waiting for an answer (with its options), an error (a failed turn,
943
+ the usage limit, a worker that died), a reply (with a preview), a background agent or shell task
944
+ that finished or failed, a session that went quiet while working (15 minutes by default), and the
945
+ moment nothing runs any more in the watched sessions. A subscription names sessions, or `all`
946
+ for every session, later ones included; the watcher's own conversation is never watched. It can
947
+ filter replies and task results by a regular expression, end after it fired once, and add a
948
+ heartbeat while work runs.
949
+
950
+ **What it looks like.** OAR writes a short message into the watcher's conversation: a new turn
951
+ when the watcher is idle, a steer into the running turn on Claude and Copilot's SDK engine, the
952
+ next turn otherwise. The message says it is automated, not from you, and that it approves
953
+ nothing; it shows as a compact row, never as one of your messages. Events within 30 seconds come
954
+ as one message; past 20 wake-ups an hour the rest comes as a digest every ten minutes, while
955
+ questions and errors still come at once.
956
+
957
+ **What it may do.** A subscription only notifies. It approves nothing and answers
958
+ nothing; what an event asks for is decided as before, by you or by the agent within what
959
+ it may do anyway. The first subscribe of a conversation, and any to `all`, asks you on an approval
960
+ card naming the sessions (and the relay); `transfer_subscriptions`, which hands every subscription
961
+ of an old watcher to a new one, always asks.
962
+
963
+ **Where you see it.** The watcher's header shows **watching N sessions**; the chip opens the list
964
+ of its subscriptions with an **Unsubscribe** button each. A watched session shows a **watched**
965
+ badge.
966
+
967
+ **Lifetime.** Subscriptions survive a restart and end on unsubscribe, when the watcher or the
968
+ watched session is archived, after firing once (`once`), or, for a paired relay, when the pairing
969
+ is removed.
970
+
971
+ **On a paired relay** the relay must be unlocked in the conversation and allow at least reading,
972
+ and the other relay must accept other relays' agents; nothing is asked there. This relay fetches
973
+ the events every few seconds over the pairing (a pairing in one direction is enough), and their
974
+ lines start with the other relay's name. If the link drops, the other relay keeps what happens
975
+ (up to 500 events per subscription, for 7 days) and delivers it as one row tagged **replayed** once
976
+ it answers again, questions and errors first, between "link lost" and "link restored". A relay
977
+ that runs an older OAR answers that it needs an update.
978
+
910
979
  ## Security notes
911
980
 
912
981
  - The auth token guards the API and the Socket.IO channel. A browser signs in once, with the token or with a URL carrying `?token=` (such as the one `oar setup` and `oar url` print), and then holds an HttpOnly cookie for 30 days, marked `Secure` when the relay is reached over HTTPS. Treat the token, and that URL, like a password.
package/bin/bat/oar.bat CHANGED
@@ -1,46 +1,46 @@
1
- @echo off
2
- setlocal EnableExtensions
3
-
4
- set "SCRIPT_DIR=%~dp0"
5
- if "%SCRIPT_DIR:~-1%"=="\" set "SCRIPT_DIR=%SCRIPT_DIR:~0,-1%"
6
-
7
- for %%I in ("%SCRIPT_DIR%\..\..") do set "REPO_ROOT=%%~fI"
8
-
9
- if not exist "%REPO_ROOT%\package.json" (
10
- echo [oar] Unable to locate the repository root from "%SCRIPT_DIR%".
11
- exit /b 1
12
- )
13
-
14
- echo(;%PATH%; | findstr /I /C:";%SCRIPT_DIR%;" >nul
15
- if errorlevel 1 (
16
- call :AddToUserPath "%SCRIPT_DIR%"
17
- if errorlevel 1 (
18
- echo [oar] Warning: could not update your user PATH.
19
- ) else (
20
- echo [oar] Ensured "%SCRIPT_DIR%" is in your user PATH.
21
- echo [oar] Open a new cmd.exe window to use oar by name.
22
- echo.
23
- )
24
- )
25
-
26
- set "COPILOT_WORKSPACE_ROOT=%REPO_ROOT%"
27
- set "COPILOT_WEB_RELAY_ROOT=%REPO_ROOT%"
28
- set "COPILOT_WEB_RELAY_SERVER_DIR=%REPO_ROOT%\server"
29
-
30
- pushd "%REPO_ROOT%" >nul
31
- if errorlevel 1 (
32
- echo [oar] Failed to change to repo root: "%REPO_ROOT%".
33
- exit /b 1
34
- )
35
-
36
- echo [oar] Starting Copilot from "%REPO_ROOT%"...
37
- gh copilot -- --allow-all
38
- set "EXIT_CODE=%errorlevel%"
39
- popd >nul
40
- exit /b %EXIT_CODE%
41
-
42
- :AddToUserPath
43
- set "TARGET_DIR=%~1"
44
- powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -Command "$target = [System.IO.Path]::GetFullPath($env:TARGET_DIR.TrimEnd([IO.Path]::DirectorySeparatorChar, [IO.Path]::AltDirectorySeparatorChar)); $current = [Environment]::GetEnvironmentVariable('Path', 'User'); $parts = @(); if ($current) { $parts = $current -split ';' | ForEach-Object { $_.TrimEnd([IO.Path]::DirectorySeparatorChar, [IO.Path]::AltDirectorySeparatorChar) } | Where-Object { $_ } }; if ($parts -contains $target) { exit 0 }; $next = ($parts + $target) -join ';'; [Environment]::SetEnvironmentVariable('Path', $next, 'User')" >nul
45
- if errorlevel 1 exit /b 1
46
- exit /b 0
1
+ @echo off
2
+ setlocal EnableExtensions
3
+
4
+ set "SCRIPT_DIR=%~dp0"
5
+ if "%SCRIPT_DIR:~-1%"=="\" set "SCRIPT_DIR=%SCRIPT_DIR:~0,-1%"
6
+
7
+ for %%I in ("%SCRIPT_DIR%\..\..") do set "REPO_ROOT=%%~fI"
8
+
9
+ if not exist "%REPO_ROOT%\package.json" (
10
+ echo [oar] Unable to locate the repository root from "%SCRIPT_DIR%".
11
+ exit /b 1
12
+ )
13
+
14
+ echo(;%PATH%; | findstr /I /C:";%SCRIPT_DIR%;" >nul
15
+ if errorlevel 1 (
16
+ call :AddToUserPath "%SCRIPT_DIR%"
17
+ if errorlevel 1 (
18
+ echo [oar] Warning: could not update your user PATH.
19
+ ) else (
20
+ echo [oar] Ensured "%SCRIPT_DIR%" is in your user PATH.
21
+ echo [oar] Open a new cmd.exe window to use oar by name.
22
+ echo.
23
+ )
24
+ )
25
+
26
+ set "COPILOT_WORKSPACE_ROOT=%REPO_ROOT%"
27
+ set "COPILOT_WEB_RELAY_ROOT=%REPO_ROOT%"
28
+ set "COPILOT_WEB_RELAY_SERVER_DIR=%REPO_ROOT%\server"
29
+
30
+ pushd "%REPO_ROOT%" >nul
31
+ if errorlevel 1 (
32
+ echo [oar] Failed to change to repo root: "%REPO_ROOT%".
33
+ exit /b 1
34
+ )
35
+
36
+ echo [oar] Starting Copilot from "%REPO_ROOT%"...
37
+ gh copilot -- --allow-all
38
+ set "EXIT_CODE=%errorlevel%"
39
+ popd >nul
40
+ exit /b %EXIT_CODE%
41
+
42
+ :AddToUserPath
43
+ set "TARGET_DIR=%~1"
44
+ powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -Command "$target = [System.IO.Path]::GetFullPath($env:TARGET_DIR.TrimEnd([IO.Path]::DirectorySeparatorChar, [IO.Path]::AltDirectorySeparatorChar)); $current = [Environment]::GetEnvironmentVariable('Path', 'User'); $parts = @(); if ($current) { $parts = $current -split ';' | ForEach-Object { $_.TrimEnd([IO.Path]::DirectorySeparatorChar, [IO.Path]::AltDirectorySeparatorChar) } | Where-Object { $_ } }; if ($parts -contains $target) { exit 0 }; $next = ($parts + $target) -join ';'; [Environment]::SetEnvironmentVariable('Path', $next, 'User')" >nul
45
+ if errorlevel 1 exit /b 1
46
+ exit /b 0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oar-sh/oar",
3
- "version": "0.9.9",
3
+ "version": "0.9.10",
4
4
  "type": "module",
5
5
  "description": "OAR — Open Agent Relay: drive your local coding agents from any browser",
6
6
  "license": "MIT",
@@ -41,7 +41,7 @@
41
41
  "scripts": {
42
42
  "start": "node server/server.js",
43
43
  "copilot:relay": "gh copilot -- --allow-all",
44
- "test": "node --test \"server/**/*.test.mjs\" \"shared/**/*.test.mjs\" \".github/extensions/web-relay/**/*.test.mjs\" \"scripts/**/*.test.mjs\"",
44
+ "test": "node --import ./scripts/node-test-keep-alive.mjs --test \"server/**/*.test.mjs\" \"shared/**/*.test.mjs\" \".github/extensions/web-relay/**/*.test.mjs\" \"scripts/**/*.test.mjs\"",
45
45
  "test:agents:smoke": "node --test \"tests/agents/**/*.test.mjs\"",
46
46
  "test:e2e": "node tests/run-e2e.mjs",
47
47
  "setup:git": "node scripts/setup-git.mjs",