@oar-sh/oar 0.9.23 → 0.9.25

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 (110) hide show
  1. package/.github/extensions/web-relay/skills/reasoning-stream.mjs +3 -1
  2. package/CHANGELOG.md +307 -0
  3. package/README.md +12 -11
  4. package/bin/oar.js +54 -4
  5. package/package.json +11 -7
  6. package/server/agent-sdk-loader.mjs +116 -0
  7. package/server/agent-sdks/claude/entry.mjs +4 -0
  8. package/server/agent-sdks/claude/package-lock.json +1438 -0
  9. package/server/agent-sdks/claude/package.json +13 -0
  10. package/server/agent-sdks/cursor/entry-sqlite.mjs +2 -0
  11. package/server/agent-sdks/cursor/entry.mjs +3 -0
  12. package/server/agent-sdks/cursor/package-lock.json +171 -0
  13. package/server/agent-sdks/cursor/package.json +11 -0
  14. package/server/agent-sdks/koffi/entry.cjs +2 -0
  15. package/server/agent-sdks/koffi/package-lock.json +368 -0
  16. package/server/agent-sdks/koffi/package.json +10 -0
  17. package/server/claude-worker/claude-preview-tool.mjs +5 -2
  18. package/server/claude-worker/claude-remote-relay-tool.mjs +20 -2
  19. package/server/claude-worker/claude-sdk-adapter.mjs +4 -1
  20. package/server/claude-worker/sdk-message-normalizer.mjs +29 -1
  21. package/server/copilot-worker/copilot-sdk-session-process.mjs +13 -8
  22. package/server/cursor-worker/cursor-sdk-adapter.mjs +5 -3
  23. package/server/cursor-worker/sdk-message-normalizer.mjs +13 -0
  24. package/server/db-schema.mjs +19 -0
  25. package/server/grok-worker/sdk-message-normalizer.mjs +3 -0
  26. package/server/migrations/0039-media-index.mjs +167 -0
  27. package/server/migrations/0040-image-session-defaults.mjs +93 -0
  28. package/server/public/app/agent-sdk-ui.js +349 -0
  29. package/server/public/app/api-client.js +149 -8
  30. package/server/public/app/ask-user-view.js +2 -1
  31. package/server/public/app/attachments-view.js +66 -9
  32. package/server/public/app/bootstrap.js +289 -94
  33. package/server/public/app/chat-actions-menu-state.mjs +1 -0
  34. package/server/public/app/context-media-tab.js +143 -0
  35. package/server/public/app/conversation-view.js +90 -5
  36. package/server/public/app/image-composer-ui.js +518 -0
  37. package/server/public/app/image-turn-placeholder.mjs +233 -0
  38. package/server/public/app/journal-view.js +12 -2
  39. package/server/public/app/media-gallery-model.mjs +202 -0
  40. package/server/public/app/media-gallery-ui.js +524 -0
  41. package/server/public/app/media-view.js +79 -0
  42. package/server/public/app/model-refresh-ui.mjs +413 -0
  43. package/server/public/app/no-conversation-state.mjs +5 -2
  44. package/server/public/app/openai-image-composer.mjs +492 -0
  45. package/server/public/app/openai-image-cost-ui.js +426 -0
  46. package/server/public/app/openai-image-spend.mjs +443 -0
  47. package/server/public/app/plan-usage-view.mjs +38 -6
  48. package/server/public/app/provider-readiness.mjs +67 -4
  49. package/server/public/app/relay-error-ctas.mjs +5 -0
  50. package/server/public/app/session-history-ui.js +2 -0
  51. package/server/public/app/settings-modal.js +171 -0
  52. package/server/public/app/socket-handlers.js +47 -0
  53. package/server/public/app/tab-strip-fade.mjs +1 -1
  54. package/server/public/app/viewer-gallery.mjs +23 -1
  55. package/server/public/index.html +478 -9
  56. package/server/relay-tools.md +1 -0
  57. package/server/repositories/image-conversation-repository.mjs +69 -0
  58. package/server/repositories/media-repository.mjs +305 -0
  59. package/server/routes/agent-sdk-routes.mjs +73 -0
  60. package/server/routes/media-routes.mjs +361 -0
  61. package/server/routes/messages-routes.mjs +1467 -343
  62. package/server/routes/sessions-routes.mjs +596 -123
  63. package/server/server-runtime.mjs +414 -81
  64. package/server/services/about-service.mjs +26 -6
  65. package/server/services/agent-sdk-launch-gate.mjs +124 -0
  66. package/server/services/agent-sdk-paths.mjs +307 -0
  67. package/server/services/agent-sdk-store.mjs +1211 -0
  68. package/server/services/claude-api-settings-service.mjs +54 -17
  69. package/server/services/cli-install-service.mjs +3 -2
  70. package/server/services/copilot-model-discovery-service.mjs +7 -1
  71. package/server/services/database-file.mjs +23 -5
  72. package/server/services/deepseek-settings-service.mjs +58 -20
  73. package/server/services/earlier-database-service.mjs +4 -2
  74. package/server/services/generated-image-cleanup-service.mjs +2 -1
  75. package/server/services/generated-image-files.mjs +110 -0
  76. package/server/services/image-input-paths.mjs +389 -0
  77. package/server/services/image-operation-service.mjs +6 -2
  78. package/server/services/media-extract.mjs +653 -0
  79. package/server/services/media-index-service.mjs +221 -0
  80. package/server/services/model-refresh-scheduler.mjs +153 -0
  81. package/server/services/model-variant-catalog-service.mjs +141 -9
  82. package/server/services/oar-cli-helpers.mjs +72 -9
  83. package/server/services/oar-state-migration-service.mjs +2 -12
  84. package/server/services/oar-state-root.mjs +17 -0
  85. package/server/services/openai-image-preview-turns.mjs +330 -0
  86. package/server/services/openai-image-spend-service.mjs +516 -0
  87. package/server/services/openai-image-stream.mjs +170 -0
  88. package/server/services/plan-usage-openai-image.mjs +148 -0
  89. package/server/services/provider-model-refresh.mjs +326 -0
  90. package/server/services/provider-model-state.mjs +395 -0
  91. package/server/services/relay-singleton-guard.mjs +72 -10
  92. package/server/services/remote-relay-caller-context.mjs +6 -0
  93. package/server/services/remote-relay-dispatcher.mjs +386 -22
  94. package/server/services/remote-relay-registry-service.mjs +3 -1
  95. package/server/services/session-worker-launch-service.mjs +13 -0
  96. package/server/services/session-worker-supervisor-service.mjs +19 -1
  97. package/server/services/session-worker-websocket-service.mjs +15 -0
  98. package/server/services/token-usage-service.mjs +15 -0
  99. package/server/services/update-install-service.mjs +2 -2
  100. package/server/services/worker-launch-note-service.mjs +14 -2
  101. package/server/services/worker-recycle-service.mjs +145 -11
  102. package/shared/image-header.mjs +90 -0
  103. package/shared/model-refresh-schedule.mjs +120 -0
  104. package/shared/openai-image-options.mjs +299 -0
  105. package/shared/openai-image-presets.mjs +175 -0
  106. package/shared/openai-image-previews.mjs +40 -0
  107. package/shared/openai-image-prices.mjs +291 -0
  108. package/shared/openai-image-spend.mjs +90 -0
  109. package/shared/remote-relay-contract.mjs +126 -4
  110. package/shared/worker-runtime/direct-openai-image.mjs +98 -48
@@ -168,7 +168,9 @@ export function createReasoningStreamHandlers({
168
168
  const reasoningId = reasoningIdFor(event);
169
169
  const deltaContent = String(event?.data?.deltaContent ?? event?.data?.delta ?? "");
170
170
  if (!deltaContent) return;
171
- const accumulated = (reasoningAccum.get(reasoningId) || "") + deltaContent;
171
+ // Capped before the gate: past the cap the text stops changing, so the
172
+ // same 16 KiB is not posted again and the accumulator stops growing.
173
+ const accumulated = capThought((reasoningAccum.get(reasoningId) || "") + deltaContent);
172
174
  reasoningAccum.set(reasoningId, accumulated);
173
175
  const lastSent = reasoningLastSent.get(reasoningId) || "";
174
176
  if (!shouldEmitDelta(accumulated, lastSent)) return;
package/CHANGELOG.md CHANGED
@@ -3,6 +3,313 @@
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.25] — 2026-11-09
7
+
8
+ Highlights: a media gallery, and agents that make images. Every picture,
9
+ video and file of a session is now in one place, the Media tab of the 🧠
10
+ modal, and 🖼️ Media in the ︙ menu shows those of every session of the
11
+ relay, with filters and a search; the file viewer moves through the whole
12
+ gallery. Agents drive OpenAI Image sessions through `relay_tool`: they send
13
+ pictures to edit and set the size, quality, count, format and background.
14
+ While gpt-image works, the reply shows partial previews; the composer gets
15
+ a custom size, a count, a format, a background and presets, and the
16
+ gpt-image-2.5 and 2.6 models take xhigh and max quality. Every image turn
17
+ shows what it cost, a daily cap of USD 5 per relay unless you change it
18
+ keeps image spend in check, and a failed image turn costs at most two calls
19
+ instead of up to five. The relay's memory no longer grows while several
20
+ agents stream long thinking at once.
21
+
22
+ ### Added
23
+
24
+ - **Media gallery.** The 🧠 modal has two tabs, Context (where it opens)
25
+ and Media: the session's pictures and videos as a square grid under month
26
+ headers, three columns on a phone, with a Files tab for attached PDFs,
27
+ audio and documents. **🖼️ Media** in the ︙ menu shows the media of every
28
+ session of the relay, with filters (All, Mine, Agent, Generated, a
29
+ provider, a session, Hide archived) and a search. It collects uploads,
30
+ `@file:` pictures, generated images and the pictures agents put in their
31
+ replies; a picture shown in several messages appears once, at its newest
32
+ message, and a deleted local file is left out. A tap opens the file
33
+ viewer, which now moves through the whole gallery (it loads more at the
34
+ end) and offers **Open in session** and **Copy path / link**. The pictures
35
+ on screen that could not load while the relay was away load again once it
36
+ is back; one scrolled out of view waits for the next reconnect.
37
+ Owner only: not in shared views, and no agent reads it through
38
+ `relay_tool`, on this relay or a paired one.
39
+ - **Media index.** The relay keeps the gallery's index in its database
40
+ (migration 0039) and fills it for older messages in the background after
41
+ the upgrade, in short slices.
42
+ - **Agents drive OpenAI Image sessions.** `relay_tool`'s `send` and
43
+ `create_session` take `images`, up to 4 absolute paths of PNG, JPEG or
44
+ WebP files on the target relay, and `image`, the turn's size (any W×H
45
+ within the model's limits), quality, count (1 to 4), output format and
46
+ background; `update_session` with `image` sets a session's own defaults
47
+ (migration 0040). The target relay takes only files inside the session's
48
+ working directory or images one of its sessions generated, follows links
49
+ to the real file, and checks each file's first bytes. Replies list the
50
+ pictures as `images` with their paths there, and a reply without text
51
+ reads like `1 image · 1536x1024 · gpt-image-2`. A paired relay must run
52
+ this version (`imageInputs` in `relay_info`); an older one is refused
53
+ before anything is sent. A new image session without a model starts on
54
+ gpt-image-2.5-flare (else its dated snapshot, else gpt-image-2, else the
55
+ first gpt-image model listed), and is refused when none is listed.
56
+
57
+ - **Image previews.** While a gpt-image model makes an image, the reply's
58
+ bubble shows a timer with the size and quality, then two partial previews
59
+ as OpenAI streams them, then the final image. Previews reach your open
60
+ clients only, never the transcript, shared views, the media gallery or
61
+ agents, and their files go when the turn ends.
62
+ - **Image composer.** An image chat's composer has a Custom… size (any
63
+ W×H the model takes, checked as you type), a Count (1 to 4), a Format
64
+ and a Background for gpt-image models.
65
+
66
+ - **What image turns cost.** Every try of an OpenAI image turn is priced
67
+ as it ends, from the usage OpenAI reports and its published rates, and
68
+ estimated where OpenAI reports no usage for a try that may have been
69
+ billed; a model the relay has no price for counts at the dearest listed
70
+ price. The reply line ends with the cost, the chat menu shows what a
71
+ chat's images cost, and Check Usage has an OpenAI images card.
72
+ - **A daily cap for OpenAI images.** USD 5 per relay and day unless you
73
+ change it (Settings → Providers → OpenAI; 0 is no cap). A request that
74
+ would pass it is not sent, and once it is reached new image messages are
75
+ refused, from you and from agents alike; estimates count toward it. A
76
+ warning shows from 80 %. Allow for today lifts it until midnight.
77
+ - **Image presets.** Five built-in bundles of size, quality, format and
78
+ background, and your own (Save as preset… in the composer); agents name
79
+ one as image.preset.
80
+ - **xhigh and max image quality.** The gpt-image-2.5 and 2.6 models take
81
+ the two higher qualities, in the composer and for agents; other models
82
+ keep auto, low, medium and high. Sizes for gpt-image-2 and later need at
83
+ least 655,360 pixels (1024x640), as OpenAI requires.
84
+
85
+ ### Changed
86
+
87
+ - **OpenAI Image turns.** All attached images go to the edit, in order,
88
+ not only the first, and the format and background reach OpenAI. Quality
89
+ is checked per model family before the call (gpt-image: auto, low,
90
+ medium, high). OpenAI gets 3 minutes for a new image and 5 for
91
+ an edit (`openaiImageGenerateTimeoutMs`, `openaiImageEditTimeoutMs`).
92
+ - **Copilot hides image models.** Copilot's `gpt-image-*` and `dall-e-*`
93
+ ids, which its runtime cannot serve, are left out of its model list; a
94
+ Copilot chat on one is refused with a pointer to OpenAI Image.
95
+
96
+ ### Fixed
97
+
98
+ - **An image turn costs at most two calls.** A timeout, a server error or
99
+ a broken connection is tried once more, then the turn ends with a note
100
+ that says whether the request may have been billed; before, the turn went
101
+ back into the queue up to five times and ended as "Relay timeout after 5
102
+ attempts", each attempt a new billed request. When the second try works,
103
+ the reply notes that the first may have been billed as well. A relay
104
+ restart during the call no longer repeats it either.
105
+ - **The relay's own image route is local only.** A paired relay's token can
106
+ no longer call it directly, past the queue.
107
+ - **An agent's prompt header no longer reaches the image model.**
108
+ - **Relay memory no longer grows while several agents stream long thinking
109
+ at the same time.** A few sessions thinking at length at once could make
110
+ the relay hold more and more memory until it ran out. Thinking still shows
111
+ live; a slow connection may skip some of the updates in between, and the
112
+ whole thought arrives when it ends and after a reconnect, as before.
113
+
114
+ ### Upgrade notes
115
+
116
+ - **The first start takes a little longer on a big database.** Before it
117
+ listens, the relay copies its database to the backups folder, as before,
118
+ and migration 0039 creates the media index and queues every message for
119
+ it once: about 0.2 s per 200,000 messages on a small server. Migration
120
+ 0040 adds two columns. Nothing else waits.
121
+ - **The gallery fills in the background.** After the start the relay
122
+ indexes the older messages in slices of about 20 ms every half second,
123
+ in no particular order: about 3 minutes per 200,000 messages on a small
124
+ server, longer on a phone or with long messages. Until it ends, the
125
+ gallery shows what is indexed and "Indexing older messages… (N left)". A
126
+ restart picks up where it stopped.
127
+ - **The daily cap for OpenAI images is on after the update**: USD 5 per
128
+ relay and day, in Settings → Providers → OpenAI (0 is no cap). Without an
129
+ OpenAI key nothing changes, and Check Usage shows no OpenAI images card
130
+ until something was booked in the month.
131
+ - **Sessions still on 0.9.24's worker wait one worker start.** A session
132
+ whose worker still runs the old code is given a new worker before its
133
+ next message, as in 0.9.24. A Claude session's agent gets `images` and
134
+ `image` in `relay_tool` with its new worker.
135
+ - **Mixed versions work together**, except for images across relays: a
136
+ 0.9.25 relay refuses to send `images` or `image` to a paired 0.9.24 relay
137
+ and sends nothing, and a 0.9.24 relay cannot send them.
138
+ - **Going back to 0.9.24** needs no database step. Install it with
139
+ `npm install -g @oar-sh/oar@0.9.24` (in a git checkout:
140
+ `git checkout v0.9.24`, then a relay restart). 0.9.24 ignores the media
141
+ index and the image defaults and has no daily image cap, so image turns
142
+ are not limited there. The index's triggers stay in the database and
143
+ queue the messages written on 0.9.24, which a later 0.9.25 indexes. To
144
+ remove the index, drop its two triggers first, then its three tables, as
145
+ `server/migrations/0039-media-index.mjs` describes; a table dropped
146
+ before its trigger breaks every new message.
147
+
148
+ ## [0.9.24] — 2026-11-08
149
+
150
+ Highlights: a much smaller install. The Claude and Cursor agent SDKs leave
151
+ the package, and the relay downloads the one a provider needs from npm when
152
+ it is set up or first used: the package takes about 50 MB on Linux x64
153
+ instead of about 650 MB, plus the SDKs of the providers you use. Neither the
154
+ start nor a first chat waits for the download; the chat shows "Installing
155
+ the … SDK" and runs when it ends. Copilot needs nothing downloaded. The
156
+ relay also checks each provider for new models on its own, every 24 hours
157
+ by default, announces new ones with a toast and a "New" tag, and lets you
158
+ choose per provider whether new models start on; OpenAI models can now be
159
+ picked as well. gpt-image replies above about 3 MB are no longer lost.
160
+
161
+ ### Added
162
+
163
+ - **Check for new models.** The relay now asks each switched-on provider
164
+ for its model list on its own: a minute after it starts for the
165
+ providers that are due, then every 24 hours. Claude and Grok are asked
166
+ only when their CLI is installed or they are signed in. Settings →
167
+ General has a new Models section with the switch, how often (every 6 h,
168
+ 12 h, 24 h, 3 days or 7 days) and one "Check <provider>" box per
169
+ provider, each with its last check or "Not set up". A provider whose
170
+ list was refreshed some other way (Refresh in Select Models, a settings
171
+ save, a sign-in, and for Copilot the look it takes when the relay
172
+ starts) waits a full interval again, and a failed check is tried again
173
+ after an hour at the earliest.
174
+ - **New models are announced.** A refresh that finds models it has not
175
+ seen before shows a toast, such as "2 new Cursor models (on)", whose Show
176
+ opens Select Models on that provider. The chat menu's Select Models has a
177
+ dot, and each new model a "New" tag, until Select Models is opened.
178
+ - **Agent SDK rows in Settings.** The Claude, Claude API, DeepSeek and
179
+ Cursor tabs show their SDK's state, the pinned and the installed version,
180
+ the install log and the next automatic retry, with Install, Cancel, Retry
181
+ and Reinstall. On a Windows relay, Settings → General → This relay has a
182
+ "Windows helper" row for koffi, with Retry.
183
+ - **`oar sdk`, and the agent SDKs in `oar doctor`.** `oar doctor` shows the
184
+ SDK folder and, per SDK, the pinned version, the installed one and its
185
+ state; `oar sdk` prints the same lines. `oar sdk install
186
+ claude|cursor|koffi [--reinstall]` installs one without the relay, for a
187
+ headless host whose web UI is the problem.
188
+
189
+ ### Changed
190
+
191
+ - **The agent SDKs are downloaded on demand.** The Claude Agent SDK (Claude,
192
+ Claude API and DeepSeek chats) and the Cursor SDK are no longer in the
193
+ package, which is smaller by roughly their size: about 50 MB installed
194
+ on Linux x64 instead of about 650 MB. The relay downloads the one a
195
+ provider needs from npm, at the version this release pins: when you
196
+ set the provider up (a Claude login, a provider switched on, a key saved),
197
+ at the provider's first chat, and at start for the providers in use. The
198
+ start never waits for it, and neither does a first chat: it opens at
199
+ once, shows "Installing the … SDK" and delivers the message when the
200
+ install ends; a failed install shows its cause in the chat and keeps the
201
+ message queued until a Retry in Settings, or the automatic retry,
202
+ installs the SDK. The SDKs live in `sdks/` in the state root
203
+ (`OAR_SDK_DIR` moves it; on Windows it must be a local folder of at most
204
+ 120 characters): about 360–370 MB for Claude on Windows and Linux x64
205
+ and 345–350 MB on macOS arm64, most of it the Claude Code binary, and
206
+ 40–50 MB for Cursor. The pinned version and the one before are kept, and
207
+ any folder a running session uses. An install's progress (npm's output,
208
+ the bytes it downloads) is checked every 15 seconds: 5 minutes without
209
+ any counts as a network failure. An install takes at most 2 hours,
210
+ enough for the Claude SDK on links down to about 0.4 Mbit/s. A download
211
+ that fails for want of a connection, or stalls like that, is tried again
212
+ after 1, 5 and 15 minutes. OAR needs the npm registry once per SDK
213
+ version and does not work offline; the host's npm config (registry,
214
+ proxy, cafile, cache, offline settings) applies. Where a vendor has no
215
+ build, the row says "Not available on this platform": the Cursor SDK on
216
+ Windows arm64, both SDKs on Android under Termux (run OAR in a proot
217
+ Debian there), 32-bit ARM and FreeBSD. See docs/install.md, Agent SDKs.
218
+ - **How an SDK is installed.** `npm ci` from a lockfile that ships with OAR,
219
+ at exact versions, into a folder of its own that counts as installed only
220
+ once its marker, which lists the files and their sizes, checks out.
221
+ Installs run with scripts disabled, koffi's included. A git checkout runs
222
+ the same agent SDK versions as devDependencies, as long as they are
223
+ installed there (not after `npm install --omit=dev`, for example);
224
+ otherwise, or with `OAR_AGENT_SDK_SOURCE=store`, it downloads them like
225
+ an install. Below the pinned packages the versions can differ: the
226
+ Claude SDK's own lockfile has newer releases of eight of them than the
227
+ package's lockfile (@babel/runtime 7.29.13, encodeurl 2.0.1,
228
+ eventsource-parser 3.1.1, express-rate-limit 8.7.3, jose 6.2.15,
229
+ on-finished 2.4.2, range-parser 1.3.0, standardwebhooks 1.1.1).
230
+ - **The Copilot SDK leaves the package.** No server code used
231
+ `@github/copilot-sdk`: Copilot workers run on the SDK that comes with the
232
+ Copilot CLI, and the CLI resolves the extension's
233
+ `@github/copilot-sdk/extension` import itself. Copilot needs nothing
234
+ downloaded.
235
+ - **A plain `npm install -g @oar-sh/oar`.** koffi went with the Copilot
236
+ SDK, so no shipped dependency has an install script any more: npm 12
237
+ needs no `--allow-scripts`, and the **Update** button and `oar update`
238
+ pass none. On Windows the relay downloads koffi itself (about 3 MB) for
239
+ its lock check, with scripts disabled like the SDKs: its binary comes
240
+ prebuilt in `@koromix/koffi-win32-<arch>`.
241
+ - **The Windows lock check without koffi.** Until koffi is there, a relay
242
+ lock whose owner's start time is unknown counts as stale when it was
243
+ written before the last boot, or when its process id now runs a program
244
+ other than Node. A lock left by a crash or a reboot, whose process id
245
+ Windows has handed on, no longer keeps the relay from starting, which
246
+ matters most for a relay started at boot after an update reboot. The
247
+ take-over of an earlier database uses the same rule. The start after
248
+ koffi arrived has the full check again.
249
+ - `engine.io` 6.6.11 (was 6.6.9) under socket.io, from a lockfile refresh:
250
+ a new version of a package already in the tree.
251
+ - In the Claude SDK's install: `@modelcontextprotocol/sdk` 1.32.2 (was
252
+ 1.30.0), pinned there as the SDK's peer, with `hono` 4.13.17 (was
253
+ 4.13.9), `@hono/node-server` 2.1.6 (was 2.1.0), `fast-uri` 3.1.9 (was
254
+ 3.1.8) and `ip-address` 10.7.6 (was 10.4.0). All are new versions of
255
+ packages that were already in the tree; no package was added.
256
+ - **Whether a new model starts on is yours to choose.** Every Select
257
+ Models tab, Copilot and OpenAI included, starts with "Turn new models on
258
+ automatically", on by default. Models that are off today stay off; only
259
+ models found from now on follow the switch. A new effort level of a
260
+ Copilot model follows the model's other levels.
261
+ - **A model you turn off stays off.** Each provider now remembers every
262
+ model it has offered and whether it is on. Unticking everything leaves
263
+ the default model on instead of turning every model back on; a model that
264
+ leaves the list and comes back keeps its state; removing a Cursor,
265
+ DeepSeek, Claude API or OpenAI key and adding one again finds the models
266
+ as they were. When every model you turned on has left the list, the
267
+ default model stands in and the provider's tab says so. The pickers show
268
+ the same models after the upgrade as before.
269
+ - **OpenAI models can be picked.** The Select Models OpenAI tab lists the
270
+ key's models with checkboxes, like the other providers; after the
271
+ upgrade all of them stay on.
272
+
273
+ ### Fixed
274
+
275
+ - **gpt-image replies above about 3 MB were lost, and the turn was billed up
276
+ to five times.** The relay checked the base64 of a generated image with a
277
+ regular expression over the whole string. V8 stops optimising regexps once
278
+ a process has generated more than 1 MB of regexp code and holds more than
279
+ 16 MB of executable memory, which a relay reaches after some uptime; the
280
+ unoptimised check then overflows V8's regexp stack at 4,194,304 characters
281
+ with "Maximum call stack size exceeded", the reply was answered as a
282
+ provider outage, and the queue retried the paid generation four more times
283
+ before "Relay timeout after 5 attempts". The base64 and data-URL checks on
284
+ the relay's image paths (OpenAI replies, generated images, edit
285
+ attachments) are character loops now; a reply the relay cannot process
286
+ after the provider answered 2xx fails the message at once, with the reason
287
+ and a note that the generation was billed, instead of being requeued, and
288
+ the error is logged with its stack.
289
+
290
+ ### Upgrade notes
291
+
292
+ - **The first Claude or Cursor chat after the update may wait.** The new
293
+ relay downloads the agent SDKs of the providers in use at its first start,
294
+ in the background, once: about 360–370 MB for Claude (345–350 MB on
295
+ macOS arm64) and 40–50 MB for Cursor. A chat that comes first shows
296
+ "Installing the … SDK" and runs once the download is done. A host that
297
+ cannot reach the npm registry, or a mirror set in its npm config, cannot
298
+ run Claude, Claude API, DeepSeek or Cursor chats on this version.
299
+ - **Sessions still on 0.9.23's worker wait one worker start.** A session
300
+ whose worker still runs the old code is given a new worker before its
301
+ next message, so the first message after the update waits for that start
302
+ instead of failing.
303
+ - **No migration.** As before, the relay copies its database to the
304
+ backups folder before the first start of a new version.
305
+ - **Going back to 0.9.23** needs no database step. Install it with
306
+ `npm install -g --allow-scripts=koffi @oar-sh/oar@0.9.23` (in a git
307
+ checkout: `git checkout v0.9.23`, then `npm ci`). 0.9.23 does not use
308
+ `sdks/` in the state root; delete it once no 0.9.24 relay runs. Model
309
+ choices made on 0.9.24 do not go back with it: for Claude, Claude API,
310
+ Cursor, Grok and DeepSeek, 0.9.23 shows the models that were on at the
311
+ update, and OpenAI offers every model again; Copilot keeps its choices.
312
+
6
313
  ## [0.9.23] — 2026-11-07
7
314
 
8
315
  Highlights: a dependency round. The Copilot, Claude and Cursor SDKs, zod,
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.23"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/cfmail-mobile.light.png?v=0.9.23"><img src="https://oar.sh/assets/shots/cfmail-mobile.png?v=0.9.23" 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.25"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/cfmail-mobile.light.png?v=0.9.25"><img src="https://oar.sh/assets/shots/cfmail-mobile.png?v=0.9.25" 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.23"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/cfmail-agents.light.png?v=0.9.23"><img src="https://oar.sh/assets/shots/cfmail-agents.png?v=0.9.23" 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.25"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/cfmail-agents.light.png?v=0.9.25"><img src="https://oar.sh/assets/shots/cfmail-agents.png?v=0.9.25" 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.23"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/cfmail-question.light.png?v=0.9.23"><img src="https://oar.sh/assets/shots/cfmail-question.png?v=0.9.23" 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.25"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/cfmail-question.light.png?v=0.9.25"><img src="https://oar.sh/assets/shots/cfmail-question.png?v=0.9.25" 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.23"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/pingcf-thoughts.light.png?v=0.9.23"><img src="https://oar.sh/assets/shots/pingcf-thoughts.png?v=0.9.23" 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.25"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/pingcf-thoughts.light.png?v=0.9.25"><img src="https://oar.sh/assets/shots/pingcf-thoughts.png?v=0.9.25" 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.23"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/pagewright-watching.light.png?v=0.9.23"><img src="https://oar.sh/assets/shots/pagewright-watching.png?v=0.9.23" 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.25"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/pagewright-watching.light.png?v=0.9.25"><img src="https://oar.sh/assets/shots/pagewright-watching.png?v=0.9.25" 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>
@@ -100,7 +100,7 @@ The runtime (the *provider* in the UI) is chosen in **New Chat** and stays fixed
100
100
  | ------- | ---------- | ------------- |
101
101
  | [**GitHub Copilot**](docs/providers.md#github-copilot--engine-choice) (default) | GitHub's coding agent, with every model your Copilot plan offers | The Copilot CLI, signed in on the relay host |
102
102
  | [**OpenAI (BYOK)**](docs/providers.md#openai-byok-and-openai-image) | Copilot's agent against OpenAI or a compatible endpoint | Your own API key |
103
- | [**OpenAI Image**](docs/providers.md#openai-byok-and-openai-image) | A chat whose replies are images | The same key |
103
+ | [**OpenAI Image**](docs/providers.md#openai-byok-and-openai-image) | A chat whose replies are images, with a daily spending cap; agents can use it too | The same key |
104
104
  | [**Claude**](docs/providers.md#claude-agent-sdk) | Claude Code on your machine | A Claude login, made from **⚙️ Settings** |
105
105
  | [**Claude API**](docs/providers.md#claude-api) | Claude Code on your machine, billed to a Claude Console API key; a Claude chat can switch between the two | A Claude Console API key |
106
106
  | [**Claude Cloud**](docs/providers.md#claude-cloud) (experimental) | Claude Code in a sandbox at Anthropic, on a GitHub repository | A claude.ai account with GitHub connected |
@@ -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.23"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/cfmail-steering.light.png?v=0.9.23"><img src="https://oar.sh/assets/shots/cfmail-steering.png?v=0.9.23" 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.25"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/cfmail-steering.light.png?v=0.9.25"><img src="https://oar.sh/assets/shots/cfmail-steering.png?v=0.9.25" 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.23"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/usage-copilot-compact.light.png?v=0.9.23"><img src="https://oar.sh/assets/shots/usage-copilot-compact.png?v=0.9.23" 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.25"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/usage-copilot-compact.light.png?v=0.9.25"><img src="https://oar.sh/assets/shots/usage-copilot-compact.png?v=0.9.25" 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>
@@ -134,12 +134,13 @@ API keys are entered in **⚙️ Settings** and stay on the relay host. The Clau
134
134
  - **Background work**: a task panel lists the commands and sub-agents that outlive a reply, each with its own **Stop** and its full record; a command's console shows its output live, says when it has gone quiet and counts a test run's passes and failures; the panel and its consoles come back after a relay restart, and a queued restart shows what it waits for and asks after a maximum wait ([Background tasks](docs/web-ui.md#background-tasks), [After a relay restart](docs/web-ui.md#after-a-relay-restart)).
135
135
  - **Choose how the agent works**: `ask`, `plan`, `agent` or `autopilot`, plus model and reasoning effort, per message, from the composer's **⚙** ([The composer](docs/web-ui.md#the-composer), [Relay modes](docs/web-ui.md#relay-modes)).
136
136
  - **Files and git**: browse the workspace, preview files, review git changes in a diff viewer, and publish a dev server as a preview link ([Files, git, and previews](docs/web-ui.md#files-git-and-previews), [preview servers](docs/preview-servers.md)).
137
- - **Usage and context**: plan limits and credits of every provider, the estimated spend of DeepSeek and Claude API keys, and what fills the model's context window ([Usage and context](docs/web-ui.md#usage-and-context)).
137
+ - **Media**: the pictures and videos of a session, or of every session, in one gallery, with a file viewer that moves through all of them ([Media](docs/web-ui.md#media)).
138
+ - **Usage and context**: plan limits and credits of every provider, the estimated spend of DeepSeek and Claude API keys and of OpenAI images, and what fills the model's context window ([Usage and context](docs/web-ui.md#usage-and-context)).
138
139
  - **Your conversations everywhere**: the history lives on the relay, drafts follow you from device to device, and conversations can be searched, archived, grouped and pinned ([The conversation list](docs/web-ui.md#the-conversation-list), [drafts](docs/web-ui.md#drafts-across-devices), [session groups](docs/web-ui.md#session-groups), [pins](docs/web-ui.md#pinned-messages)).
139
140
  - **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
141
  - **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
142
 
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.23](CHANGELOG.md#0923--2026-11-07) as the latest.
143
+ 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.25](CHANGELOG.md#0925--2026-11-09) as the latest.
143
144
 
144
145
  ## Remote access
145
146
 
@@ -190,7 +191,7 @@ flowchart LR
190
191
 
191
192
  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
193
 
193
- <p><a href="https://oar.sh/assets/shots/pagewright-cross-relay.png?v=0.9.23"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/pagewright-cross-relay.light.png?v=0.9.23"><img src="https://oar.sh/assets/shots/pagewright-cross-relay.png?v=0.9.23" 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
+ <p><a href="https://oar.sh/assets/shots/pagewright-cross-relay.png?v=0.9.25"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/pagewright-cross-relay.light.png?v=0.9.25"><img src="https://oar.sh/assets/shots/pagewright-cross-relay.png?v=0.9.25" 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
195
 
195
196
  How pairing, permissions and limits work is in [Agents working together](docs/agents-together.md).
196
197
 
package/bin/oar.js CHANGED
@@ -18,6 +18,7 @@ import { NATIVE_BUILD_COMMAND, buildBetterSqlite3, needsSourceBuild } from '../s
18
18
  import { DB_FILE_NAME, DB_FILE_NAMES, LEGACY_DB_FILE_NAME, resolveDatabaseFile } from '../server/services/database-file.mjs';
19
19
  import { BACKUP_PROGRESS_FILE, BACKUP_WAIT_FILE, backupDatabase, configWarnings, describeBackups, sanitizeReason } from '../server/services/db-backup-service.mjs';
20
20
  import { defaultIsProcessAlive, lockHolderMayRun, processIdentity } from '../server/services/relay-singleton-guard.mjs';
21
+ import { createAgentSdkStore } from '../server/services/agent-sdk-store.mjs';
21
22
  import {
22
23
  getLauncherLogDir,
23
24
  getLegacyManagedConfigDir,
@@ -26,8 +27,9 @@ import {
26
27
  resolveStateLayout,
27
28
  } from '../server/services/oar-state-layout.mjs';
28
29
  import {
29
- buildDefaultConfig, buildServicePath, buildSystemdUnit, classifySystemdUnit, describeRelayAddress, DOCTOR_PROBES, generateAuthToken,
30
- 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,
31
33
  resolveNpmInvocation, systemdUnitMarkerRoot, systemdUnitRunsCheckout, usageText,
32
34
  } from '../server/services/oar-cli-helpers.mjs';
33
35
  import { describeConfiguredTunnel, describeTunnelState } from '../server/services/cloudflared-tunnel-service.mjs';
@@ -2256,7 +2258,6 @@ export async function runDoctor(options = {}) {
2256
2258
  return { id: probe.id, ok: false };
2257
2259
  }
2258
2260
  });
2259
- probes.push({ id: 'cursor', ok: true, version: 'bundled (@cursor/sdk)' });
2260
2261
 
2261
2262
  const port = endpointOf(paths.config).port;
2262
2263
  const holder = paths.config ? createWindowsPortCheck(ctx)(port) : null;
@@ -2277,10 +2278,58 @@ export async function runDoctor(options = {}) {
2277
2278
  warnings: holder ? [windowsPortWarning(port, holder)] : [],
2278
2279
  // The relay resolves a relative cloudflaredTunnel.binary against server/.
2279
2280
  tunnel: describeConfiguredTunnel(paths.config?.cloudflaredTunnel, { env: ctx.env, configBaseDir: path.join(ctx.packageRoot, 'server') }),
2281
+ agentSdks: describeAgentSdks({ env: ctx.env, platform: ctx.platform }),
2280
2282
  }));
2281
2283
  return { code: 0, reason: 'doctor' };
2282
2284
  }
2283
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
+
2284
2333
  /**
2285
2334
  * `oar backup [--reason <text>]`: a copy of the relay's database, online
2286
2335
  * (safe while the relay runs), into the backup folder with the start's
@@ -2392,7 +2441,7 @@ async function runUpdate({ argv = [], env = process.env, logger = console, fetch
2392
2441
  logger.log(`[oar] Updating @oar-sh/oar ${runningVersion} -> ${target} ...`);
2393
2442
  const npm = resolveNpmInvocation({ packageRoot, env });
2394
2443
  const exitCode = await new Promise((resolve) => {
2395
- 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}`], {
2396
2445
  stdio: 'inherit',
2397
2446
  env: npm.env,
2398
2447
  ...(process.platform === 'win32' ? { shell: true, windowsHide: true } : {}),
@@ -2523,6 +2572,7 @@ const COMMANDS = {
2523
2572
  backup: runBackup,
2524
2573
  update: runUpdate,
2525
2574
  'build-native': runBuildNative,
2575
+ sdk: runSdk,
2526
2576
  help: async ({ logger = console } = {}) => {
2527
2577
  logger.log(usageText());
2528
2578
  return { code: 0, reason: 'help' };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oar-sh/oar",
3
- "version": "0.9.23",
3
+ "version": "0.9.25",
4
4
  "type": "module",
5
5
  "description": "OAR — Open Agent Relay: drive your local coding agents from any browser",
6
6
  "license": "MIT",
@@ -25,6 +25,7 @@
25
25
  "server/",
26
26
  "!server/**/*.test.mjs",
27
27
  "!server/**/*-test-harness.mjs",
28
+ "!server/**/*-test-fixtures.mjs",
28
29
  "!server/**/fixtures/",
29
30
  "!server/README.md",
30
31
  "!server/data/",
@@ -36,6 +37,7 @@
36
37
  "shared/",
37
38
  "!shared/**/*.test.mjs",
38
39
  "!shared/**/*-test-harness.mjs",
40
+ "!shared/**/*-test-fixtures.mjs",
39
41
  "!shared/**/fixtures/",
40
42
  ".github/extensions/web-relay/",
41
43
  "!.github/extensions/web-relay/**/*.test.mjs",
@@ -58,20 +60,22 @@
58
60
  "test:e2e:raw": "node ./node_modules/@playwright/test/cli.js test --config tests/playwright.config.mjs"
59
61
  },
60
62
  "dependencies": {
61
- "@anthropic-ai/claude-agent-sdk": "0.3.319",
62
- "@cursor/sdk": "1.3.1",
63
- "@github/copilot-sdk": "1.0.29",
64
63
  "better-sqlite3": "13.0.3",
65
64
  "express": "5.3.0",
66
65
  "qrcode-terminal": "0.12.0",
67
66
  "socket.io": "4.8.4",
68
67
  "web-push": "3.6.7",
69
- "ws": "8.22.1",
70
- "zod": "4.6.7"
68
+ "ws": "8.22.1"
71
69
  },
72
70
  "devDependencies": {
71
+ "@anthropic-ai/claude-agent-sdk": "0.3.319",
72
+ "@anthropic-ai/sdk": "0.116.0",
73
+ "@cursor/sdk": "1.3.1",
74
+ "@modelcontextprotocol/sdk": "1.32.2",
73
75
  "@playwright/test": "^1.60.0",
74
- "jsdom": "^30.0.1"
76
+ "jsdom": "^30.0.1",
77
+ "koffi": "3.3.4",
78
+ "zod": "4.6.7"
75
79
  },
76
80
  "overrides": {
77
81
  "undici": "6.28.0"