@oar-sh/oar 0.9.22 → 0.9.24

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 (68) hide show
  1. package/CHANGELOG.md +258 -0
  2. package/README.md +9 -9
  3. package/bin/oar.js +82 -4
  4. package/package.json +16 -15
  5. package/server/agent-sdk-loader.mjs +116 -0
  6. package/server/agent-sdks/claude/entry.mjs +4 -0
  7. package/server/agent-sdks/claude/package-lock.json +1438 -0
  8. package/server/agent-sdks/claude/package.json +13 -0
  9. package/server/agent-sdks/cursor/entry-sqlite.mjs +2 -0
  10. package/server/agent-sdks/cursor/entry.mjs +3 -0
  11. package/server/agent-sdks/cursor/package-lock.json +171 -0
  12. package/server/agent-sdks/cursor/package.json +11 -0
  13. package/server/agent-sdks/koffi/entry.cjs +2 -0
  14. package/server/agent-sdks/koffi/package-lock.json +368 -0
  15. package/server/agent-sdks/koffi/package.json +10 -0
  16. package/server/claude-worker/claude-cli-pinned-env.mjs +4 -1
  17. package/server/claude-worker/claude-preview-tool.mjs +5 -2
  18. package/server/claude-worker/claude-remote-relay-tool.mjs +5 -2
  19. package/server/claude-worker/claude-sdk-adapter.mjs +4 -1
  20. package/server/copilot-worker/copilot-sdk-session-process.mjs +7 -4
  21. package/server/cursor-worker/cursor-sdk-adapter.mjs +5 -3
  22. package/server/public/app/agent-sdk-ui.js +349 -0
  23. package/server/public/app/api-client.js +91 -8
  24. package/server/public/app/bootstrap.js +204 -69
  25. package/server/public/app/model-refresh-ui.mjs +413 -0
  26. package/server/public/app/no-conversation-state.mjs +5 -2
  27. package/server/public/app/provider-readiness.mjs +67 -4
  28. package/server/public/app/settings-modal.js +167 -0
  29. package/server/public/app/socket-handlers.js +21 -0
  30. package/server/public/index.html +95 -2
  31. package/server/routes/agent-sdk-routes.mjs +73 -0
  32. package/server/routes/messages-routes.mjs +102 -37
  33. package/server/routes/sessions-routes.mjs +236 -110
  34. package/server/server-runtime.mjs +416 -121
  35. package/server/services/about-service.mjs +26 -6
  36. package/server/services/agent-sdk-launch-gate.mjs +124 -0
  37. package/server/services/agent-sdk-paths.mjs +307 -0
  38. package/server/services/agent-sdk-store.mjs +1211 -0
  39. package/server/services/claude-api-settings-service.mjs +54 -17
  40. package/server/services/cli-install-service.mjs +3 -2
  41. package/server/services/copilot-model-discovery-service.mjs +7 -1
  42. package/server/services/database-file.mjs +17 -1
  43. package/server/services/deepseek-settings-service.mjs +58 -20
  44. package/server/services/earlier-database-service.mjs +5 -3
  45. package/server/services/file-serving.mjs +13 -0
  46. package/server/services/model-refresh-scheduler.mjs +153 -0
  47. package/server/services/model-variant-catalog-service.mjs +141 -9
  48. package/server/services/native-addon-build-service.mjs +224 -0
  49. package/server/services/oar-cli-helpers.mjs +76 -10
  50. package/server/services/oar-state-migration-service.mjs +2 -12
  51. package/server/services/oar-state-root.mjs +17 -0
  52. package/server/services/provider-model-refresh.mjs +326 -0
  53. package/server/services/provider-model-state.mjs +395 -0
  54. package/server/services/relay-console-log.mjs +2 -3
  55. package/server/services/relay-singleton-guard.mjs +72 -10
  56. package/server/services/remote-relay-loopback.mjs +3 -3
  57. package/server/services/sdk-session-sync-service.mjs +1 -1
  58. package/server/services/session-worker-launch-service.mjs +13 -0
  59. package/server/services/session-worker-supervisor-service.mjs +19 -1
  60. package/server/services/session-worker-websocket-service.mjs +15 -0
  61. package/server/services/update-install-service.mjs +34 -3
  62. package/server/services/worker-launch-note-service.mjs +14 -2
  63. package/server/services/worker-recycle-service.mjs +145 -11
  64. package/shared/model-refresh-schedule.mjs +120 -0
  65. package/shared/worker-runtime/direct-openai-image.mjs +8 -4
  66. package/server/services/package-data-rescue-preinstall.mjs +0 -22
  67. package/server/services/package-data-rescue.mjs +0 -139
  68. package/server/tty-console-bootstrap.mjs +0 -75
package/CHANGELOG.md CHANGED
@@ -3,6 +3,264 @@
3
3
  All notable changes to OAR are documented here. The format follows
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow semver.
5
5
 
6
+ ## [0.9.24] — 2026-11-08
7
+
8
+ Highlights: a much smaller install. The Claude and Cursor agent SDKs leave
9
+ the package, and the relay downloads the one a provider needs from npm when
10
+ it is set up or first used: the package takes about 50 MB on Linux x64
11
+ instead of about 650 MB, plus the SDKs of the providers you use. Neither the
12
+ start nor a first chat waits for the download; the chat shows "Installing
13
+ the … SDK" and runs when it ends. Copilot needs nothing downloaded. The
14
+ relay also checks each provider for new models on its own, every 24 hours
15
+ by default, announces new ones with a toast and a "New" tag, and lets you
16
+ choose per provider whether new models start on; OpenAI models can now be
17
+ picked as well. gpt-image replies above about 3 MB are no longer lost.
18
+
19
+ ### Added
20
+
21
+ - **Check for new models.** The relay now asks each switched-on provider
22
+ for its model list on its own: a minute after it starts for the
23
+ providers that are due, then every 24 hours. Claude and Grok are asked
24
+ only when their CLI is installed or they are signed in. Settings →
25
+ General has a new Models section with the switch, how often (every 6 h,
26
+ 12 h, 24 h, 3 days or 7 days) and one "Check <provider>" box per
27
+ provider, each with its last check or "Not set up". A provider whose
28
+ list was refreshed some other way (Refresh in Select Models, a settings
29
+ save, a sign-in, and for Copilot the look it takes when the relay
30
+ starts) waits a full interval again, and a failed check is tried again
31
+ after an hour at the earliest.
32
+ - **New models are announced.** A refresh that finds models it has not
33
+ seen before shows a toast, such as "2 new Cursor models (on)", whose Show
34
+ opens Select Models on that provider. The chat menu's Select Models has a
35
+ dot, and each new model a "New" tag, until Select Models is opened.
36
+ - **Agent SDK rows in Settings.** The Claude, Claude API, DeepSeek and
37
+ Cursor tabs show their SDK's state, the pinned and the installed version,
38
+ the install log and the next automatic retry, with Install, Cancel, Retry
39
+ and Reinstall. On a Windows relay, Settings → General → This relay has a
40
+ "Windows helper" row for koffi, with Retry.
41
+ - **`oar sdk`, and the agent SDKs in `oar doctor`.** `oar doctor` shows the
42
+ SDK folder and, per SDK, the pinned version, the installed one and its
43
+ state; `oar sdk` prints the same lines. `oar sdk install
44
+ claude|cursor|koffi [--reinstall]` installs one without the relay, for a
45
+ headless host whose web UI is the problem.
46
+
47
+ ### Changed
48
+
49
+ - **The agent SDKs are downloaded on demand.** The Claude Agent SDK (Claude,
50
+ Claude API and DeepSeek chats) and the Cursor SDK are no longer in the
51
+ package, which is smaller by roughly their size: about 50 MB installed
52
+ on Linux x64 instead of about 650 MB. The relay downloads the one a
53
+ provider needs from npm, at the version this release pins: when you
54
+ set the provider up (a Claude login, a provider switched on, a key saved),
55
+ at the provider's first chat, and at start for the providers in use. The
56
+ start never waits for it, and neither does a first chat: it opens at
57
+ once, shows "Installing the … SDK" and delivers the message when the
58
+ install ends; a failed install shows its cause in the chat and keeps the
59
+ message queued until a Retry in Settings, or the automatic retry,
60
+ installs the SDK. The SDKs live in `sdks/` in the state root
61
+ (`OAR_SDK_DIR` moves it; on Windows it must be a local folder of at most
62
+ 120 characters): about 360–370 MB for Claude on Windows and Linux x64
63
+ and 345–350 MB on macOS arm64, most of it the Claude Code binary, and
64
+ 40–50 MB for Cursor. The pinned version and the one before are kept, and
65
+ any folder a running session uses. An install's progress (npm's output,
66
+ the bytes it downloads) is checked every 15 seconds: 5 minutes without
67
+ any counts as a network failure. An install takes at most 2 hours,
68
+ enough for the Claude SDK on links down to about 0.4 Mbit/s. A download
69
+ that fails for want of a connection, or stalls like that, is tried again
70
+ after 1, 5 and 15 minutes. OAR needs the npm registry once per SDK
71
+ version and does not work offline; the host's npm config (registry,
72
+ proxy, cafile, cache, offline settings) applies. Where a vendor has no
73
+ build, the row says "Not available on this platform": the Cursor SDK on
74
+ Windows arm64, both SDKs on Android under Termux (run OAR in a proot
75
+ Debian there), 32-bit ARM and FreeBSD. See docs/install.md, Agent SDKs.
76
+ - **How an SDK is installed.** `npm ci` from a lockfile that ships with OAR,
77
+ at exact versions, into a folder of its own that counts as installed only
78
+ once its marker, which lists the files and their sizes, checks out.
79
+ Installs run with scripts disabled, koffi's included. A git checkout runs
80
+ the same agent SDK versions as devDependencies, as long as they are
81
+ installed there (not after `npm install --omit=dev`, for example);
82
+ otherwise, or with `OAR_AGENT_SDK_SOURCE=store`, it downloads them like
83
+ an install. Below the pinned packages the versions can differ: the
84
+ Claude SDK's own lockfile has newer releases of eight of them than the
85
+ package's lockfile (@babel/runtime 7.29.13, encodeurl 2.0.1,
86
+ eventsource-parser 3.1.1, express-rate-limit 8.7.3, jose 6.2.15,
87
+ on-finished 2.4.2, range-parser 1.3.0, standardwebhooks 1.1.1).
88
+ - **The Copilot SDK leaves the package.** No server code used
89
+ `@github/copilot-sdk`: Copilot workers run on the SDK that comes with the
90
+ Copilot CLI, and the CLI resolves the extension's
91
+ `@github/copilot-sdk/extension` import itself. Copilot needs nothing
92
+ downloaded.
93
+ - **A plain `npm install -g @oar-sh/oar`.** koffi went with the Copilot
94
+ SDK, so no shipped dependency has an install script any more: npm 12
95
+ needs no `--allow-scripts`, and the **Update** button and `oar update`
96
+ pass none. On Windows the relay downloads koffi itself (about 3 MB) for
97
+ its lock check, with scripts disabled like the SDKs: its binary comes
98
+ prebuilt in `@koromix/koffi-win32-<arch>`.
99
+ - **The Windows lock check without koffi.** Until koffi is there, a relay
100
+ lock whose owner's start time is unknown counts as stale when it was
101
+ written before the last boot, or when its process id now runs a program
102
+ other than Node. A lock left by a crash or a reboot, whose process id
103
+ Windows has handed on, no longer keeps the relay from starting, which
104
+ matters most for a relay started at boot after an update reboot. The
105
+ take-over of an earlier database uses the same rule. The start after
106
+ koffi arrived has the full check again.
107
+ - `engine.io` 6.6.11 (was 6.6.9) under socket.io, from a lockfile refresh:
108
+ a new version of a package already in the tree.
109
+ - In the Claude SDK's install: `@modelcontextprotocol/sdk` 1.32.2 (was
110
+ 1.30.0), pinned there as the SDK's peer, with `hono` 4.13.17 (was
111
+ 4.13.9), `@hono/node-server` 2.1.6 (was 2.1.0), `fast-uri` 3.1.9 (was
112
+ 3.1.8) and `ip-address` 10.7.6 (was 10.4.0). All are new versions of
113
+ packages that were already in the tree; no package was added.
114
+ - **Whether a new model starts on is yours to choose.** Every Select
115
+ Models tab, Copilot and OpenAI included, starts with "Turn new models on
116
+ automatically", on by default. Models that are off today stay off; only
117
+ models found from now on follow the switch. A new effort level of a
118
+ Copilot model follows the model's other levels.
119
+ - **A model you turn off stays off.** Each provider now remembers every
120
+ model it has offered and whether it is on. Unticking everything leaves
121
+ the default model on instead of turning every model back on; a model that
122
+ leaves the list and comes back keeps its state; removing a Cursor,
123
+ DeepSeek, Claude API or OpenAI key and adding one again finds the models
124
+ as they were. When every model you turned on has left the list, the
125
+ default model stands in and the provider's tab says so. The pickers show
126
+ the same models after the upgrade as before.
127
+ - **OpenAI models can be picked.** The Select Models OpenAI tab lists the
128
+ key's models with checkboxes, like the other providers; after the
129
+ upgrade all of them stay on.
130
+
131
+ ### Fixed
132
+
133
+ - **gpt-image replies above about 3 MB were lost, and the turn was billed up
134
+ to five times.** The relay checked the base64 of a generated image with a
135
+ regular expression over the whole string. V8 stops optimising regexps once
136
+ a process has generated more than 1 MB of regexp code and holds more than
137
+ 16 MB of executable memory, which a relay reaches after some uptime; the
138
+ unoptimised check then overflows V8's regexp stack at 4,194,304 characters
139
+ with "Maximum call stack size exceeded", the reply was answered as a
140
+ provider outage, and the queue retried the paid generation four more times
141
+ before "Relay timeout after 5 attempts". The base64 and data-URL checks on
142
+ the relay's image paths (OpenAI replies, generated images, edit
143
+ attachments) are character loops now; a reply the relay cannot process
144
+ after the provider answered 2xx fails the message at once, with the reason
145
+ and a note that the generation was billed, instead of being requeued, and
146
+ the error is logged with its stack.
147
+
148
+ ### Upgrade notes
149
+
150
+ - **The first Claude or Cursor chat after the update may wait.** The new
151
+ relay downloads the agent SDKs of the providers in use at its first start,
152
+ in the background, once: about 360–370 MB for Claude (345–350 MB on
153
+ macOS arm64) and 40–50 MB for Cursor. A chat that comes first shows
154
+ "Installing the … SDK" and runs once the download is done. A host that
155
+ cannot reach the npm registry, or a mirror set in its npm config, cannot
156
+ run Claude, Claude API, DeepSeek or Cursor chats on this version.
157
+ - **Sessions still on 0.9.23's worker wait one worker start.** A session
158
+ whose worker still runs the old code is given a new worker before its
159
+ next message, so the first message after the update waits for that start
160
+ instead of failing.
161
+ - **No migration.** As before, the relay copies its database to the
162
+ backups folder before the first start of a new version.
163
+ - **Going back to 0.9.23** needs no database step. Install it with
164
+ `npm install -g --allow-scripts=koffi @oar-sh/oar@0.9.23` (in a git
165
+ checkout: `git checkout v0.9.23`, then `npm ci`). 0.9.23 does not use
166
+ `sdks/` in the state root; delete it once no 0.9.24 relay runs. Model
167
+ choices made on 0.9.24 do not go back with it: for Claude, Claude API,
168
+ Cursor, Grok and DeepSeek, 0.9.23 shows the models that were on at the
169
+ update, and OpenAI offers every model again; Copilot keeps its choices.
170
+
171
+ ## [0.9.23] — 2026-11-07
172
+
173
+ Highlights: a dependency round. The Copilot, Claude and Cursor SDKs, zod,
174
+ express, socket.io, ws and better-sqlite3 move to newer releases:
175
+ the tree holds one zod, without CVE-2023-54404, and one express, and
176
+ prebuild-install, which is no longer maintained, is gone. OAR's package has
177
+ no install script any more, which package scanners flag. On Termux, 32-bit
178
+ ARM and FreeBSD, run **`oar build-native`** once after updating to this
179
+ version: it builds the SQLite binding, and later updates build it
180
+ themselves.
181
+
182
+ ### Added
183
+
184
+ - **`oar build-native`.** better-sqlite3 13 ships its SQLite binding for
185
+ Linux, macOS and Windows on x64 and arm64. On Android under Termux, 32-bit
186
+ ARM and FreeBSD, `oar build-native` compiles it in the installed package
187
+ (Python 3, make and a C++ compiler; on Termux `pkg install python clang
188
+ make`). The **Update** button and `oar update` run it there themselves
189
+ before the relay restarts; a build that fails ends the update in an error
190
+ and leaves the running relay alone. Elsewhere it has nothing to do, so an
191
+ installer may run it everywhere. A relay started without the binding says
192
+ which command to run and exits instead of printing a stack trace.
193
+ - **What the agent SDKs contact.** docs/providers.md lists the services the
194
+ Copilot, Claude and Cursor SDKs and their CLIs talk to, and what the
195
+ Statsig client inside the Cursor SDK does.
196
+
197
+ ### Changed
198
+
199
+ - **No install script.** OAR's package has no `preinstall` any more. From
200
+ 0.9.14 to 0.9.22 it copied the data a relay before 0.9.14 kept inside the
201
+ package out before npm deleted it. npm 12 never ran it without
202
+ `--allow-scripts`, and package scanners flag every package that has one.
203
+ A copy those versions made is still taken over at the next start.
204
+ - **`--allow-scripts=koffi`.** better-sqlite3 13 has no install script, so
205
+ a manual install with npm 12 is now
206
+ `npm install -g --allow-scripts=koffi @oar-sh/oar`; the installers, the
207
+ **Update** button and `oar update` pass it themselves.
208
+ - **No terminal console.** The optional `tty-console` dependency is gone,
209
+ and neo-blessed with it. A relay started in a terminal prints its log
210
+ lines, each with its time, as under a service; routine worker lines are
211
+ no longer held back.
212
+ - **Express 5.** The file routes take express 5's syntax and serve the
213
+ same paths. Static JavaScript is served as `text/javascript`.
214
+ - `better-sqlite3` 13.0.3 (was 12.11.1); `prebuild-install`, which is no
215
+ longer maintained, and its subtree are gone. New in the tree:
216
+ `node-addon-api` 8.9.2, the headers of the source build.
217
+ - `express` 5.3.0 (was 4.22.3), the major the MCP SDK already used: one
218
+ express instead of two, and array-flatten, mime 1 and the rest of express
219
+ 4's subtree are gone.
220
+ - `@anthropic-ai/claude-agent-sdk` 0.3.319 (was 0.3.304; bundled Claude
221
+ Code 2.1.319). The key chats (DeepSeek, Claude API) also pin the
222
+ cloud-sessions token and the two new switches that would let a folder's
223
+ settings widen which certificates Claude Code trusts.
224
+ - `@cursor/sdk` 1.3.1 (was 1.0.38). Its context-compaction summaries are
225
+ not shown yet.
226
+ - `@github/copilot-sdk` 1.0.29 (was 1.0.23; bundled Copilot CLI 1.0.107)
227
+ and `zod` 4.6.7 (was 4.4.3): one zod in the tree, without
228
+ CVE-2023-54404 (GHSA-mrhx-c7xc-m3w5).
229
+ - `socket.io` 4.8.4 (was 4.8.3) and `ws` 8.22.1 (was 8.21.3). `uuid` is
230
+ gone: Node's `crypto.randomUUID()` makes the same ids.
231
+ - package.json names its author and its bug tracker.
232
+
233
+ ### Upgrade notes
234
+
235
+ - **From 0.9.13 or older, copy the data out of the package first.** A relay
236
+ before 0.9.14 could keep its database, uploads and config inside the
237
+ package folder, which `npm install -g` deletes, and this version no longer
238
+ copies them out. Copy `data/`, `uploads/` and `config.json` from the
239
+ installed package's `server` folder into a new folder under
240
+ `rescued-package-data` in the data folder (for example
241
+ `~/.oar/data/rescued-package-data/before-update/`); the first start takes
242
+ them over. From 0.9.14 on there is nothing to do.
243
+ - **Termux, 32-bit ARM, FreeBSD: the update from 0.9.22 needs
244
+ `oar build-native` once.** The Update button and `oar update` of 0.9.22
245
+ install 0.9.23 but do not build the binding, so the relay stops at its
246
+ first start on 0.9.23: its log names the command, and the page cannot
247
+ reach the relay. In Termux run:
248
+
249
+ ```sh
250
+ pkg install python clang make
251
+ oar build-native
252
+ oar start
253
+ ```
254
+
255
+ Later updates build the binding themselves, before the relay restarts.
256
+ - **Linux needs glibc 2.34 or newer,** for example Debian 12, Ubuntu 22.04
257
+ or RHEL 9. The SQLite binding better-sqlite3 13 ships for Linux on x64
258
+ and arm64 needs it, and a binding built with `oar build-native` would not
259
+ be used there. On an older system a relay that updates to 0.9.23 stays
260
+ down at its first start and says to update the operating system; stay on
261
+ 0.9.22 until the system is updated.
262
+ - **No migration.** Going back to 0.9.22 needs no database step.
263
+
6
264
  ## [0.9.22] — 2026-11-05
7
265
 
8
266
  Highlights: questions that don't block move into the chat header. A **📓**
package/README.md CHANGED
@@ -45,29 +45,29 @@ flowchart LR
45
45
  <table>
46
46
  <tr>
47
47
  <td width="42%" rowspan="4" valign="top">
48
- <a href="https://oar.sh/assets/shots/cfmail-mobile.png?v=0.9.22"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/cfmail-mobile.light.png?v=0.9.22"><img src="https://oar.sh/assets/shots/cfmail-mobile.png?v=0.9.22" alt="OAR on a phone: a finished agent turn in a conversation, with the composer below"></picture></a>
48
+ <a href="https://oar.sh/assets/shots/cfmail-mobile.png?v=0.9.24"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/cfmail-mobile.light.png?v=0.9.24"><img src="https://oar.sh/assets/shots/cfmail-mobile.png?v=0.9.24" alt="OAR on a phone: a finished agent turn in a conversation, with the composer below"></picture></a>
49
49
  <br><sub>On a phone: the same conversations, installable as an app.</sub>
50
50
  </td>
51
51
  <td width="58%" valign="top">
52
- <a href="https://oar.sh/assets/shots/cfmail-agents.png?v=0.9.22"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/cfmail-agents.light.png?v=0.9.22"><img src="https://oar.sh/assets/shots/cfmail-agents.png?v=0.9.22" alt="Background subagents running in parallel in the task panel, each with live token counts, elapsed time, and its own Stop button"></picture></a>
52
+ <a href="https://oar.sh/assets/shots/cfmail-agents.png?v=0.9.24"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/cfmail-agents.light.png?v=0.9.24"><img src="https://oar.sh/assets/shots/cfmail-agents.png?v=0.9.24" alt="Background subagents running in parallel in the task panel, each with live token counts, elapsed time, and its own Stop button"></picture></a>
53
53
  <br><sub>Agents working in parallel, each with live token counts and its own Stop.</sub>
54
54
  </td>
55
55
  </tr>
56
56
  <tr>
57
57
  <td valign="top">
58
- <a href="https://oar.sh/assets/shots/cfmail-question.png?v=0.9.22"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/cfmail-question.light.png?v=0.9.22"><img src="https://oar.sh/assets/shots/cfmail-question.png?v=0.9.22" alt="A question card in the middle of a turn: the agent offers several choices and a free-text reply"></picture></a>
58
+ <a href="https://oar.sh/assets/shots/cfmail-question.png?v=0.9.24"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/cfmail-question.light.png?v=0.9.24"><img src="https://oar.sh/assets/shots/cfmail-question.png?v=0.9.24" alt="A question card in the middle of a turn: the agent offers several choices and a free-text reply"></picture></a>
59
59
  <br><sub>When the agent needs a decision, it asks, and waits for your answer.</sub>
60
60
  </td>
61
61
  </tr>
62
62
  <tr>
63
63
  <td valign="top">
64
- <a href="https://oar.sh/assets/shots/pingcf-thoughts.png?v=0.9.22"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/pingcf-thoughts.light.png?v=0.9.22"><img src="https://oar.sh/assets/shots/pingcf-thoughts.png?v=0.9.22" alt="An expanded Thoughts block showing the agent's reasoning above its reply"></picture></a>
64
+ <a href="https://oar.sh/assets/shots/pingcf-thoughts.png?v=0.9.24"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/pingcf-thoughts.light.png?v=0.9.24"><img src="https://oar.sh/assets/shots/pingcf-thoughts.png?v=0.9.24" alt="An expanded Thoughts block showing the agent's reasoning above its reply"></picture></a>
65
65
  <br><sub>Read the reasoning, not just the answer.</sub>
66
66
  </td>
67
67
  </tr>
68
68
  <tr>
69
69
  <td valign="top">
70
- <a href="https://oar.sh/assets/shots/pagewright-watching.png?v=0.9.22"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/pagewright-watching.light.png?v=0.9.22"><img src="https://oar.sh/assets/shots/pagewright-watching.png?v=0.9.22" 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"></picture></a>
70
+ <a href="https://oar.sh/assets/shots/pagewright-watching.png?v=0.9.24"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/pagewright-watching.light.png?v=0.9.24"><img src="https://oar.sh/assets/shots/pagewright-watching.png?v=0.9.24" 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"></picture></a>
71
71
  <br><sub>Agents that watch agents: woken by events, approving nothing.</sub>
72
72
  </td>
73
73
  </tr>
@@ -118,11 +118,11 @@ API keys are entered in **⚙️ Settings** and stay on the relay host. The Clau
118
118
  <table>
119
119
  <tr>
120
120
  <td width="62%" valign="top">
121
- <a href="https://oar.sh/assets/shots/cfmail-steering.png?v=0.9.22"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/cfmail-steering.light.png?v=0.9.22"><img src="https://oar.sh/assets/shots/cfmail-steering.png?v=0.9.22" alt="A running Claude turn with a message steered into it mid-turn, the reply streaming with its tool calls, and the Steer button in the composer"></picture></a>
121
+ <a href="https://oar.sh/assets/shots/cfmail-steering.png?v=0.9.24"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/cfmail-steering.light.png?v=0.9.24"><img src="https://oar.sh/assets/shots/cfmail-steering.png?v=0.9.24" alt="A running Claude turn with a message steered into it mid-turn, the reply streaming with its tool calls, and the Steer button in the composer"></picture></a>
122
122
  <br><sub>Send a message while the agent works, and it steers the running turn.</sub>
123
123
  </td>
124
124
  <td width="38%" valign="top">
125
- <a href="https://oar.sh/assets/shots/usage-copilot-compact.png?v=0.9.22"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/usage-copilot-compact.light.png?v=0.9.22"><img src="https://oar.sh/assets/shots/usage-copilot-compact.png?v=0.9.22" alt="The GitHub Copilot card in Check Usage: premium requests and plan quota with reset dates, and the cost of the last turn"></picture></a>
125
+ <a href="https://oar.sh/assets/shots/usage-copilot-compact.png?v=0.9.24"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/usage-copilot-compact.light.png?v=0.9.24"><img src="https://oar.sh/assets/shots/usage-copilot-compact.png?v=0.9.24" alt="The GitHub Copilot card in Check Usage: premium requests and plan quota with reset dates, and the cost of the last turn"></picture></a>
126
126
  <br><sub>What each plan has left, in one place.</sub>
127
127
  </td>
128
128
  </tr>
@@ -139,7 +139,7 @@ API keys are entered in **⚙️ Settings** and stay on the relay host. The Clau
139
139
  - **Share** a conversation as a read-only link, hide single messages and the host's paths from it, and see when it was opened ([Sharing](docs/web-ui.md#sharing)).
140
140
  - **On your phone**: install OAR as an app and get push notifications; back walks through the conversations you opened, a pinch zooms the whole app, and held sideways it keeps a phone's layout ([Notifications and the app](docs/web-ui.md#notifications-and-the-app), [back and forward](docs/web-ui.md#back-and-forward), [zoom](docs/web-ui.md#zoom), [a phone held sideways](docs/web-ui.md#a-phone-held-sideways)).
141
141
 
142
- The full tour is in [Using the web UI](docs/web-ui.md); every setting and config key is in [Settings and configuration](docs/configuration.md). What changed in each release is in the [changelog](CHANGELOG.md), with [0.9.22](CHANGELOG.md#0922--2026-11-05) as the latest.
142
+ The full tour is in [Using the web UI](docs/web-ui.md); every setting and config key is in [Settings and configuration](docs/configuration.md). What changed in each release is in the [changelog](CHANGELOG.md), with [0.9.24](CHANGELOG.md#0924--2026-11-08) as the latest.
143
143
 
144
144
  ## Remote access
145
145
 
@@ -190,7 +190,7 @@ flowchart LR
190
190
 
191
191
  You stay in charge: per paired relay you decide whether agents may only read, also prompt, or do everything, and in the `ask` and `plan` modes every write asks you first. A subscription only notifies; it approves nothing.
192
192
 
193
- <p><a href="https://oar.sh/assets/shots/pagewright-cross-relay.png?v=0.9.22"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/pagewright-cross-relay.light.png?v=0.9.22"><img src="https://oar.sh/assets/shots/pagewright-cross-relay.png?v=0.9.22" alt="A session watching two sessions on a paired relay: a reply arrives as a relay-event row, and after a short outage the events the other relay kept arrive as one replayed row" width="720"></picture></a></p>
193
+ <p><a href="https://oar.sh/assets/shots/pagewright-cross-relay.png?v=0.9.24"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/pagewright-cross-relay.light.png?v=0.9.24"><img src="https://oar.sh/assets/shots/pagewright-cross-relay.png?v=0.9.24" alt="A session watching two sessions on a paired relay: a reply arrives as a relay-event row, and after a short outage the events the other relay kept arrive as one replayed row" width="720"></picture></a></p>
194
194
 
195
195
  How pairing, permissions and limits work is in [Agents working together](docs/agents-together.md).
196
196
 
package/bin/oar.js CHANGED
@@ -14,9 +14,11 @@ import {
14
14
  } from '../server/services/oar-state-migration-service.mjs';
15
15
  import { readConfiguredDataDir, resolveDataDir, resolveUploadDir } from '../server/services/data-dir-resolution.mjs';
16
16
  import { RESCUE_DIR_NAME, TAKEOVER_PROGRESS_FILE } from '../server/services/earlier-database-service.mjs';
17
+ import { NATIVE_BUILD_COMMAND, buildBetterSqlite3, needsSourceBuild } from '../server/services/native-addon-build-service.mjs';
17
18
  import { DB_FILE_NAME, DB_FILE_NAMES, LEGACY_DB_FILE_NAME, resolveDatabaseFile } from '../server/services/database-file.mjs';
18
19
  import { BACKUP_PROGRESS_FILE, BACKUP_WAIT_FILE, backupDatabase, configWarnings, describeBackups, sanitizeReason } from '../server/services/db-backup-service.mjs';
19
20
  import { defaultIsProcessAlive, lockHolderMayRun, processIdentity } from '../server/services/relay-singleton-guard.mjs';
21
+ import { createAgentSdkStore } from '../server/services/agent-sdk-store.mjs';
20
22
  import {
21
23
  getLauncherLogDir,
22
24
  getLegacyManagedConfigDir,
@@ -25,8 +27,9 @@ import {
25
27
  resolveStateLayout,
26
28
  } from '../server/services/oar-state-layout.mjs';
27
29
  import {
28
- buildDefaultConfig, buildServicePath, buildSystemdUnit, classifySystemdUnit, describeRelayAddress, DOCTOR_PROBES, generateAuthToken,
29
- listSystemdDropIns, NPM_ALLOW_SCRIPTS_ARG, parseCliArgs, parseSystemdVersion, primaryLanAddress, renderDoctorReport,
30
+ buildDefaultConfig, buildServicePath, buildSystemdUnit, classifySystemdUnit, describeAgentSdks, describeRelayAddress, DOCTOR_PROBES,
31
+ generateAuthToken, listSystemdDropIns, parseCliArgs, parseSystemdVersion, primaryLanAddress, renderAgentSdkLines,
32
+ renderDoctorReport,
30
33
  resolveNpmInvocation, systemdUnitMarkerRoot, systemdUnitRunsCheckout, usageText,
31
34
  } from '../server/services/oar-cli-helpers.mjs';
32
35
  import { describeConfiguredTunnel, describeTunnelState } from '../server/services/cloudflared-tunnel-service.mjs';
@@ -2255,7 +2258,6 @@ export async function runDoctor(options = {}) {
2255
2258
  return { id: probe.id, ok: false };
2256
2259
  }
2257
2260
  });
2258
- probes.push({ id: 'cursor', ok: true, version: 'bundled (@cursor/sdk)' });
2259
2261
 
2260
2262
  const port = endpointOf(paths.config).port;
2261
2263
  const holder = paths.config ? createWindowsPortCheck(ctx)(port) : null;
@@ -2276,10 +2278,58 @@ export async function runDoctor(options = {}) {
2276
2278
  warnings: holder ? [windowsPortWarning(port, holder)] : [],
2277
2279
  // The relay resolves a relative cloudflaredTunnel.binary against server/.
2278
2280
  tunnel: describeConfiguredTunnel(paths.config?.cloudflaredTunnel, { env: ctx.env, configBaseDir: path.join(ctx.packageRoot, 'server') }),
2281
+ agentSdks: describeAgentSdks({ env: ctx.env, platform: ctx.platform }),
2279
2282
  }));
2280
2283
  return { code: 0, reason: 'doctor' };
2281
2284
  }
2282
2285
 
2286
+ /**
2287
+ * `oar sdk [status]`: the agent SDKs as `oar doctor` shows them.
2288
+ * `oar sdk install <claude|cursor|koffi> [--reinstall]`: installs one through
2289
+ * the relay's store, without the relay, for a headless host whose web UI is
2290
+ * the problem; npm's output streams to the terminal. Exit 1 when it did not
2291
+ * end ready.
2292
+ */
2293
+ export async function runSdk(options = {}) {
2294
+ const ctx = cliContext(options);
2295
+ const argv = ctx.argv || [];
2296
+ if (argv[0] !== 'install') {
2297
+ ctx.logger.log(renderAgentSdkLines(describeAgentSdks({ env: ctx.env, platform: ctx.platform })).join('\n'));
2298
+ return { code: 0, reason: 'sdk-status' };
2299
+ }
2300
+ const id = argv[1];
2301
+ if (!id) {
2302
+ ctx.logger.error('[oar] oar sdk install needs the SDK: claude, cursor or koffi.');
2303
+ return { code: 2, reason: 'usage' };
2304
+ }
2305
+ const store = (ctx.createAgentSdkStore || createAgentSdkStore)({ env: ctx.env, platform: ctx.platform, logger: { log: () => {} } });
2306
+ let shown = '';
2307
+ const unsubscribe = store.subscribe((snapshot) => {
2308
+ const text = snapshot.sdks[id]?.log || '';
2309
+ // The log is the retained tail: print what is new since the last look.
2310
+ const fresh = text.startsWith(shown) ? text.slice(shown.length) : text;
2311
+ if (fresh) ctx.stdout.write(fresh);
2312
+ shown = text;
2313
+ });
2314
+ try {
2315
+ const started = store.install(id, { force: argv.includes('--reinstall'), reason: 'oar sdk install' });
2316
+ if (!started.ok) {
2317
+ ctx.logger.error(`[oar] ${started.error}`);
2318
+ return { code: 1, reason: 'sdk-install-failed' };
2319
+ }
2320
+ const status = started.ready ? started.status : await store.ensure(id, { reason: 'oar sdk install', retryFailed: true });
2321
+ if (status?.state !== 'ready') {
2322
+ ctx.logger.error(`[oar] ${status?.label || id}: ${status?.error || 'not installed'}`);
2323
+ return { code: 1, reason: 'sdk-install-failed' };
2324
+ }
2325
+ ctx.logger.log(`[oar] ${status.label} ${status.installed} is ready in ${status.dir}.`);
2326
+ return { code: 0, reason: 'sdk-installed' };
2327
+ } finally {
2328
+ unsubscribe();
2329
+ store.dispose();
2330
+ }
2331
+ }
2332
+
2283
2333
  /**
2284
2334
  * `oar backup [--reason <text>]`: a copy of the relay's database, online
2285
2335
  * (safe while the relay runs), into the backup folder with the start's
@@ -2391,7 +2441,7 @@ async function runUpdate({ argv = [], env = process.env, logger = console, fetch
2391
2441
  logger.log(`[oar] Updating @oar-sh/oar ${runningVersion} -> ${target} ...`);
2392
2442
  const npm = resolveNpmInvocation({ packageRoot, env });
2393
2443
  const exitCode = await new Promise((resolve) => {
2394
- const child = spawnImpl(npm.command, ['install', '-g', ...npm.prefixArgs, NPM_ALLOW_SCRIPTS_ARG, `@oar-sh/oar@${target}`], {
2444
+ const child = spawnImpl(npm.command, ['install', '-g', ...npm.prefixArgs, `@oar-sh/oar@${target}`], {
2395
2445
  stdio: 'inherit',
2396
2446
  env: npm.env,
2397
2447
  ...(process.platform === 'win32' ? { shell: true, windowsHide: true } : {}),
@@ -2407,6 +2457,15 @@ async function runUpdate({ argv = [], env = process.env, logger = console, fetch
2407
2457
  return { code: 1, reason: 'npm-failed' };
2408
2458
  }
2409
2459
  logger.log(`[oar] Installed @oar-sh/oar@${target}.`);
2460
+ // No prebuilt SQLite binding here: build it before the relay restarts on
2461
+ // the new version (native-addon-build-service.mjs).
2462
+ if (needsSourceBuild()) {
2463
+ const build = await buildBetterSqlite3({ packageRoot, env: npm.env, spawnImpl, logger, onOutput: (text) => process.stdout.write(text) });
2464
+ if (!build.ok) {
2465
+ logger.error(`[oar] The relay keeps running the version it has loaded. Run ${NATIVE_BUILD_COMMAND}, then restart it.`);
2466
+ return { code: 1, reason: 'native-build-failed' };
2467
+ }
2468
+ }
2410
2469
 
2411
2470
  // A running relay picks the new code up on restart; ask for the queue-idle
2412
2471
  // deferred one so no in-flight turn is cut off.
@@ -2446,6 +2505,23 @@ async function runUpdate({ argv = [], env = process.env, logger = console, fetch
2446
2505
  return { code: 0, reason: 'updated' };
2447
2506
  }
2448
2507
 
2508
+ /**
2509
+ * `oar build-native [--force]`: the SQLite binding of better-sqlite3 where no
2510
+ * prebuilt one ships (Termux, 32-bit ARM, FreeBSD); elsewhere nothing to do,
2511
+ * so an installer may run it everywhere. `--force` builds anyway, which only
2512
+ * tests the toolchain where a prebuilt binding ships: better-sqlite3 loads
2513
+ * that one first.
2514
+ */
2515
+ async function runBuildNative({ argv = [], env = process.env, logger = console, spawnImpl = spawn } = {}) {
2516
+ const force = argv.includes('--force');
2517
+ if (!force && !needsSourceBuild()) {
2518
+ logger.log(`[oar] better-sqlite3 ships a prebuilt SQLite binding for ${process.platform}-${process.arch}; nothing to build.`);
2519
+ return { code: 0, reason: 'prebuilt' };
2520
+ }
2521
+ const result = await buildBetterSqlite3({ packageRoot: resolvePackageRoot(), force, env, spawnImpl, logger, onOutput: (text) => process.stdout.write(text) });
2522
+ return result.ok ? { code: 0, reason: 'built' } : { code: 1, reason: 'build-failed' };
2523
+ }
2524
+
2449
2525
  /** `oar` without a command: the menu in a terminal, the status and the usage elsewhere. */
2450
2526
  async function runMenuCommand(options = {}) {
2451
2527
  const ctx = cliContext(options);
@@ -2495,6 +2571,8 @@ const COMMANDS = {
2495
2571
  doctor: runDoctor,
2496
2572
  backup: runBackup,
2497
2573
  update: runUpdate,
2574
+ 'build-native': runBuildNative,
2575
+ sdk: runSdk,
2498
2576
  help: async ({ logger = console } = {}) => {
2499
2577
  logger.log(usageText());
2500
2578
  return { code: 0, reason: 'help' };
package/package.json CHANGED
@@ -1,14 +1,18 @@
1
1
  {
2
2
  "name": "@oar-sh/oar",
3
- "version": "0.9.22",
3
+ "version": "0.9.24",
4
4
  "type": "module",
5
5
  "description": "OAR — Open Agent Relay: drive your local coding agents from any browser",
6
6
  "license": "MIT",
7
+ "author": "Simon",
7
8
  "homepage": "https://oar.sh",
8
9
  "repository": {
9
10
  "type": "git",
10
11
  "url": "git+https://github.com/oar-sh/oar.git"
11
12
  },
13
+ "bugs": {
14
+ "url": "https://github.com/oar-sh/oar/issues"
15
+ },
12
16
  "engines": {
13
17
  "node": ">=22.13"
14
18
  },
@@ -41,7 +45,6 @@
41
45
  "LICENSE"
42
46
  ],
43
47
  "scripts": {
44
- "preinstall": "node server/services/package-data-rescue-preinstall.mjs",
45
48
  "start": "node server/server.js",
46
49
  "copilot:relay": "gh copilot -- --allow-all",
47
50
  "test": "node --import ./scripts/node-test-keep-alive.mjs --import ./scripts/node-test-worktree-guard.mjs --test \"server/**/*.test.mjs\" \"shared/**/*.test.mjs\" \".github/extensions/web-relay/**/*.test.mjs\" \"scripts/**/*.test.mjs\"",
@@ -55,24 +58,22 @@
55
58
  "test:e2e:raw": "node ./node_modules/@playwright/test/cli.js test --config tests/playwright.config.mjs"
56
59
  },
57
60
  "dependencies": {
58
- "@anthropic-ai/claude-agent-sdk": "0.3.304",
59
- "@cursor/sdk": "1.0.38",
60
- "@github/copilot-sdk": "1.0.23",
61
- "better-sqlite3": "12.11.1",
62
- "express": "4.22.3",
61
+ "better-sqlite3": "13.0.3",
62
+ "express": "5.3.0",
63
63
  "qrcode-terminal": "0.12.0",
64
- "socket.io": "4.8.3",
65
- "uuid": "14.0.1",
64
+ "socket.io": "4.8.4",
66
65
  "web-push": "3.6.7",
67
- "ws": "8.21.3",
68
- "zod": "4.4.3"
69
- },
70
- "optionalDependencies": {
71
- "tty-console": "1.0.4"
66
+ "ws": "8.22.1"
72
67
  },
73
68
  "devDependencies": {
69
+ "@anthropic-ai/claude-agent-sdk": "0.3.319",
70
+ "@anthropic-ai/sdk": "0.116.0",
71
+ "@cursor/sdk": "1.3.1",
72
+ "@modelcontextprotocol/sdk": "1.32.2",
74
73
  "@playwright/test": "^1.60.0",
75
- "jsdom": "^30.0.1"
74
+ "jsdom": "^30.0.1",
75
+ "koffi": "3.3.4",
76
+ "zod": "4.6.7"
76
77
  },
77
78
  "overrides": {
78
79
  "undici": "6.28.0"