# Changelog All notable changes to OAR are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow semver. ## [0.9.25] β€” 2026-11-09 Highlights: a media gallery, and agents that make images. Every picture, video and file of a session is now in one place, the Media tab of the 🧠 modal, and πŸ–ΌοΈ Media in the οΈ™ menu shows those of every session of the relay, with filters and a search; the file viewer moves through the whole gallery. Agents drive OpenAI Image sessions through `relay_tool`: they send pictures to edit and set the size, quality, count, format and background. While gpt-image works, the reply shows partial previews; the composer gets a custom size, a count, a format, a background and presets, and the gpt-image-2.5 and 2.6 models take xhigh and max quality. Every image turn shows what it cost, a daily cap of USD 5 per relay unless you change it keeps image spend in check, and a failed image turn costs at most two calls instead of up to five. The relay's memory no longer grows while several agents stream long thinking at once. ### Added - **Media gallery.** The 🧠 modal has two tabs, Context (where it opens) and Media: the session's pictures and videos as a square grid under month headers, three columns on a phone, with a Files tab for attached PDFs, audio and documents. **πŸ–ΌοΈ Media** in the οΈ™ menu shows the media of every session of the relay, with filters (All, Mine, Agent, Generated, a provider, a session, Hide archived) and a search. It collects uploads, `@file:` pictures, generated images and the pictures agents put in their replies; a picture shown in several messages appears once, at its newest message, and a deleted local file is left out. A tap opens the file viewer, which now moves through the whole gallery (it loads more at the end) and offers **Open in session** and **Copy path / link**. The pictures on screen that could not load while the relay was away load again once it is back; one scrolled out of view waits for the next reconnect. Owner only: not in shared views, and no agent reads it through `relay_tool`, on this relay or a paired one. - **Media index.** The relay keeps the gallery's index in its database (migration 0039) and fills it for older messages in the background after the upgrade, in short slices. - **Agents drive OpenAI Image sessions.** `relay_tool`'s `send` and `create_session` take `images`, up to 4 absolute paths of PNG, JPEG or WebP files on the target relay, and `image`, the turn's size (any WΓ—H within the model's limits), quality, count (1 to 4), output format and background; `update_session` with `image` sets a session's own defaults (migration 0040). The target relay takes only files inside the session's working directory or images one of its sessions generated, follows links to the real file, and checks each file's first bytes. Replies list the pictures as `images` with their paths there, and a reply without text reads like `1 image Β· 1536x1024 Β· gpt-image-2`. A paired relay must run this version (`imageInputs` in `relay_info`); an older one is refused before anything is sent. A new image session without a model starts on gpt-image-2.5-flare (else its dated snapshot, else gpt-image-2, else the first gpt-image model listed), and is refused when none is listed. - **Image previews.** While a gpt-image model makes an image, the reply's bubble shows a timer with the size and quality, then two partial previews as OpenAI streams them, then the final image. Previews reach your open clients only, never the transcript, shared views, the media gallery or agents, and their files go when the turn ends. - **Image composer.** An image chat's composer has a Custom… size (any WΓ—H the model takes, checked as you type), a Count (1 to 4), a Format and a Background for gpt-image models. - **What image turns cost.** Every try of an OpenAI image turn is priced as it ends, from the usage OpenAI reports and its published rates, and estimated where OpenAI reports no usage for a try that may have been billed; a model the relay has no price for counts at the dearest listed price. The reply line ends with the cost, the chat menu shows what a chat's images cost, and Check Usage has an OpenAI images card. - **A daily cap for OpenAI images.** USD 5 per relay and day unless you change it (Settings β†’ Providers β†’ OpenAI; 0 is no cap). A request that would pass it is not sent, and once it is reached new image messages are refused, from you and from agents alike; estimates count toward it. A warning shows from 80 %. Allow for today lifts it until midnight. - **Image presets.** Five built-in bundles of size, quality, format and background, and your own (Save as preset… in the composer); agents name one as image.preset. - **xhigh and max image quality.** The gpt-image-2.5 and 2.6 models take the two higher qualities, in the composer and for agents; other models keep auto, low, medium and high. Sizes for gpt-image-2 and later need at least 655,360 pixels (1024x640), as OpenAI requires. ### Changed - **OpenAI Image turns.** All attached images go to the edit, in order, not only the first, and the format and background reach OpenAI. Quality is checked per model family before the call (gpt-image: auto, low, medium, high). OpenAI gets 3 minutes for a new image and 5 for an edit (`openaiImageGenerateTimeoutMs`, `openaiImageEditTimeoutMs`). - **Copilot hides image models.** Copilot's `gpt-image-*` and `dall-e-*` ids, which its runtime cannot serve, are left out of its model list; a Copilot chat on one is refused with a pointer to OpenAI Image. ### Fixed - **An image turn costs at most two calls.** A timeout, a server error or a broken connection is tried once more, then the turn ends with a note that says whether the request may have been billed; before, the turn went back into the queue up to five times and ended as "Relay timeout after 5 attempts", each attempt a new billed request. When the second try works, the reply notes that the first may have been billed as well. A relay restart during the call no longer repeats it either. - **The relay's own image route is local only.** A paired relay's token can no longer call it directly, past the queue. - **An agent's prompt header no longer reaches the image model.** - **Relay memory no longer grows while several agents stream long thinking at the same time.** A few sessions thinking at length at once could make the relay hold more and more memory until it ran out. Thinking still shows live; a slow connection may skip some of the updates in between, and the whole thought arrives when it ends and after a reconnect, as before. ### Upgrade notes - **The first start takes a little longer on a big database.** Before it listens, the relay copies its database to the backups folder, as before, and migration 0039 creates the media index and queues every message for it once: about 0.2 s per 200,000 messages on a small server. Migration 0040 adds two columns. Nothing else waits. - **The gallery fills in the background.** After the start the relay indexes the older messages in slices of about 20 ms every half second, in no particular order: about 3 minutes per 200,000 messages on a small server, longer on a phone or with long messages. Until it ends, the gallery shows what is indexed and "Indexing older messages… (N left)". A restart picks up where it stopped. - **The daily cap for OpenAI images is on after the update**: USD 5 per relay and day, in Settings β†’ Providers β†’ OpenAI (0 is no cap). Without an OpenAI key nothing changes, and Check Usage shows no OpenAI images card until something was booked in the month. - **Sessions still on 0.9.24's worker wait one worker start.** A session whose worker still runs the old code is given a new worker before its next message, as in 0.9.24. A Claude session's agent gets `images` and `image` in `relay_tool` with its new worker. - **Mixed versions work together**, except for images across relays: a 0.9.25 relay refuses to send `images` or `image` to a paired 0.9.24 relay and sends nothing, and a 0.9.24 relay cannot send them. - **Going back to 0.9.24** needs no database step. Install it with `npm install -g @oar-sh/oar@0.9.24` (in a git checkout: `git checkout v0.9.24`, then a relay restart). 0.9.24 ignores the media index and the image defaults and has no daily image cap, so image turns are not limited there. The index's triggers stay in the database and queue the messages written on 0.9.24, which a later 0.9.25 indexes. To remove the index, drop its two triggers first, then its three tables, as `server/migrations/0039-media-index.mjs` describes; a table dropped before its trigger breaks every new message. ## [0.9.24] β€” 2026-11-08 Highlights: a much smaller install. The Claude and Cursor agent SDKs leave the package, and the relay downloads the one a provider needs from npm when it is set up or first used: the package takes about 50 MB on Linux x64 instead of about 650 MB, plus the SDKs of the providers you use. Neither the start nor a first chat waits for the download; the chat shows "Installing the … SDK" and runs when it ends. Copilot needs nothing downloaded. The relay also checks each provider for new models on its own, every 24 hours by default, announces new ones with a toast and a "New" tag, and lets you choose per provider whether new models start on; OpenAI models can now be picked as well. gpt-image replies above about 3 MB are no longer lost. ### Added - **Check for new models.** The relay now asks each switched-on provider for its model list on its own: a minute after it starts for the providers that are due, then every 24 hours. Claude and Grok are asked only when their CLI is installed or they are signed in. Settings β†’ General has a new Models section with the switch, how often (every 6 h, 12 h, 24 h, 3 days or 7 days) and one "Check " box per provider, each with its last check or "Not set up". A provider whose list was refreshed some other way (Refresh in Select Models, a settings save, a sign-in, and for Copilot the look it takes when the relay starts) waits a full interval again, and a failed check is tried again after an hour at the earliest. - **New models are announced.** A refresh that finds models it has not seen before shows a toast, such as "2 new Cursor models (on)", whose Show opens Select Models on that provider. The chat menu's Select Models has a dot, and each new model a "New" tag, until Select Models is opened. - **Agent SDK rows in Settings.** The Claude, Claude API, DeepSeek and Cursor tabs show their SDK's state, the pinned and the installed version, the install log and the next automatic retry, with Install, Cancel, Retry and Reinstall. On a Windows relay, Settings β†’ General β†’ This relay has a "Windows helper" row for koffi, with Retry. - **`oar sdk`, and the agent SDKs in `oar doctor`.** `oar doctor` shows the SDK folder and, per SDK, the pinned version, the installed one and its state; `oar sdk` prints the same lines. `oar sdk install claude|cursor|koffi [--reinstall]` installs one without the relay, for a headless host whose web UI is the problem. ### Changed - **The agent SDKs are downloaded on demand.** The Claude Agent SDK (Claude, Claude API and DeepSeek chats) and the Cursor SDK are no longer in the package, which is smaller by roughly their size: about 50 MB installed on Linux x64 instead of about 650 MB. The relay downloads the one a provider needs from npm, at the version this release pins: when you set the provider up (a Claude login, a provider switched on, a key saved), at the provider's first chat, and at start for the providers in use. The start never waits for it, and neither does a first chat: it opens at once, shows "Installing the … SDK" and delivers the message when the install ends; a failed install shows its cause in the chat and keeps the message queued until a Retry in Settings, or the automatic retry, installs the SDK. The SDKs live in `sdks/` in the state root (`OAR_SDK_DIR` moves it; on Windows it must be a local folder of at most 120 characters): about 360–370 MB for Claude on Windows and Linux x64 and 345–350 MB on macOS arm64, most of it the Claude Code binary, and 40–50 MB for Cursor. The pinned version and the one before are kept, and any folder a running session uses. An install's progress (npm's output, the bytes it downloads) is checked every 15 seconds: 5 minutes without any counts as a network failure. An install takes at most 2 hours, enough for the Claude SDK on links down to about 0.4 Mbit/s. A download that fails for want of a connection, or stalls like that, is tried again after 1, 5 and 15 minutes. OAR needs the npm registry once per SDK version and does not work offline; the host's npm config (registry, proxy, cafile, cache, offline settings) applies. Where a vendor has no build, the row says "Not available on this platform": the Cursor SDK on Windows arm64, both SDKs on Android under Termux (run OAR in a proot Debian there), 32-bit ARM and FreeBSD. See docs/install.md, Agent SDKs. - **How an SDK is installed.** `npm ci` from a lockfile that ships with OAR, at exact versions, into a folder of its own that counts as installed only once its marker, which lists the files and their sizes, checks out. Installs run with scripts disabled, koffi's included. A git checkout runs the same agent SDK versions as devDependencies, as long as they are installed there (not after `npm install --omit=dev`, for example); otherwise, or with `OAR_AGENT_SDK_SOURCE=store`, it downloads them like an install. Below the pinned packages the versions can differ: the Claude SDK's own lockfile has newer releases of eight of them than the package's lockfile (@babel/runtime 7.29.13, encodeurl 2.0.1, eventsource-parser 3.1.1, express-rate-limit 8.7.3, jose 6.2.15, on-finished 2.4.2, range-parser 1.3.0, standardwebhooks 1.1.1). - **The Copilot SDK leaves the package.** No server code used `@github/copilot-sdk`: Copilot workers run on the SDK that comes with the Copilot CLI, and the CLI resolves the extension's `@github/copilot-sdk/extension` import itself. Copilot needs nothing downloaded. - **A plain `npm install -g @oar-sh/oar`.** koffi went with the Copilot SDK, so no shipped dependency has an install script any more: npm 12 needs no `--allow-scripts`, and the **Update** button and `oar update` pass none. On Windows the relay downloads koffi itself (about 3 MB) for its lock check, with scripts disabled like the SDKs: its binary comes prebuilt in `@koromix/koffi-win32-`. - **The Windows lock check without koffi.** Until koffi is there, a relay lock whose owner's start time is unknown counts as stale when it was written before the last boot, or when its process id now runs a program other than Node. A lock left by a crash or a reboot, whose process id Windows has handed on, no longer keeps the relay from starting, which matters most for a relay started at boot after an update reboot. The take-over of an earlier database uses the same rule. The start after koffi arrived has the full check again. - `engine.io` 6.6.11 (was 6.6.9) under socket.io, from a lockfile refresh: a new version of a package already in the tree. - In the Claude SDK's install: `@modelcontextprotocol/sdk` 1.32.2 (was 1.30.0), pinned there as the SDK's peer, with `hono` 4.13.17 (was 4.13.9), `@hono/node-server` 2.1.6 (was 2.1.0), `fast-uri` 3.1.9 (was 3.1.8) and `ip-address` 10.7.6 (was 10.4.0). All are new versions of packages that were already in the tree; no package was added. - **Whether a new model starts on is yours to choose.** Every Select Models tab, Copilot and OpenAI included, starts with "Turn new models on automatically", on by default. Models that are off today stay off; only models found from now on follow the switch. A new effort level of a Copilot model follows the model's other levels. - **A model you turn off stays off.** Each provider now remembers every model it has offered and whether it is on. Unticking everything leaves the default model on instead of turning every model back on; a model that leaves the list and comes back keeps its state; removing a Cursor, DeepSeek, Claude API or OpenAI key and adding one again finds the models as they were. When every model you turned on has left the list, the default model stands in and the provider's tab says so. The pickers show the same models after the upgrade as before. - **OpenAI models can be picked.** The Select Models OpenAI tab lists the key's models with checkboxes, like the other providers; after the upgrade all of them stay on. ### Fixed - **gpt-image replies above about 3 MB were lost, and the turn was billed up to five times.** The relay checked the base64 of a generated image with a regular expression over the whole string. V8 stops optimising regexps once a process has generated more than 1 MB of regexp code and holds more than 16 MB of executable memory, which a relay reaches after some uptime; the unoptimised check then overflows V8's regexp stack at 4,194,304 characters with "Maximum call stack size exceeded", the reply was answered as a provider outage, and the queue retried the paid generation four more times before "Relay timeout after 5 attempts". The base64 and data-URL checks on the relay's image paths (OpenAI replies, generated images, edit attachments) are character loops now; a reply the relay cannot process after the provider answered 2xx fails the message at once, with the reason and a note that the generation was billed, instead of being requeued, and the error is logged with its stack. ### Upgrade notes - **The first Claude or Cursor chat after the update may wait.** The new relay downloads the agent SDKs of the providers in use at its first start, in the background, once: about 360–370 MB for Claude (345–350 MB on macOS arm64) and 40–50 MB for Cursor. A chat that comes first shows "Installing the … SDK" and runs once the download is done. A host that cannot reach the npm registry, or a mirror set in its npm config, cannot run Claude, Claude API, DeepSeek or Cursor chats on this version. - **Sessions still on 0.9.23's worker wait one worker start.** A session whose worker still runs the old code is given a new worker before its next message, so the first message after the update waits for that start instead of failing. - **No migration.** As before, the relay copies its database to the backups folder before the first start of a new version. - **Going back to 0.9.23** needs no database step. Install it with `npm install -g --allow-scripts=koffi @oar-sh/oar@0.9.23` (in a git checkout: `git checkout v0.9.23`, then `npm ci`). 0.9.23 does not use `sdks/` in the state root; delete it once no 0.9.24 relay runs. Model choices made on 0.9.24 do not go back with it: for Claude, Claude API, Cursor, Grok and DeepSeek, 0.9.23 shows the models that were on at the update, and OpenAI offers every model again; Copilot keeps its choices. ## [0.9.23] β€” 2026-11-07 Highlights: a dependency round. The Copilot, Claude and Cursor SDKs, zod, express, socket.io, ws and better-sqlite3 move to newer releases: the tree holds one zod, without CVE-2023-54404, and one express, and prebuild-install, which is no longer maintained, is gone. OAR's package has no install script any more, which package scanners flag. On Termux, 32-bit ARM and FreeBSD, run **`oar build-native`** once after updating to this version: it builds the SQLite binding, and later updates build it themselves. ### Added - **`oar build-native`.** better-sqlite3 13 ships its SQLite binding for Linux, macOS and Windows on x64 and arm64. On Android under Termux, 32-bit ARM and FreeBSD, `oar build-native` compiles it in the installed package (Python 3, make and a C++ compiler; on Termux `pkg install python clang make`). The **Update** button and `oar update` run it there themselves before the relay restarts; a build that fails ends the update in an error and leaves the running relay alone. Elsewhere it has nothing to do, so an installer may run it everywhere. A relay started without the binding says which command to run and exits instead of printing a stack trace. - **What the agent SDKs contact.** docs/providers.md lists the services the Copilot, Claude and Cursor SDKs and their CLIs talk to, and what the Statsig client inside the Cursor SDK does. ### Changed - **No install script.** OAR's package has no `preinstall` any more. From 0.9.14 to 0.9.22 it copied the data a relay before 0.9.14 kept inside the package out before npm deleted it. npm 12 never ran it without `--allow-scripts`, and package scanners flag every package that has one. A copy those versions made is still taken over at the next start. - **`--allow-scripts=koffi`.** better-sqlite3 13 has no install script, so a manual install with npm 12 is now `npm install -g --allow-scripts=koffi @oar-sh/oar`; the installers, the **Update** button and `oar update` pass it themselves. - **No terminal console.** The optional `tty-console` dependency is gone, and neo-blessed with it. A relay started in a terminal prints its log lines, each with its time, as under a service; routine worker lines are no longer held back. - **Express 5.** The file routes take express 5's syntax and serve the same paths. Static JavaScript is served as `text/javascript`. - `better-sqlite3` 13.0.3 (was 12.11.1); `prebuild-install`, which is no longer maintained, and its subtree are gone. New in the tree: `node-addon-api` 8.9.2, the headers of the source build. - `express` 5.3.0 (was 4.22.3), the major the MCP SDK already used: one express instead of two, and array-flatten, mime 1 and the rest of express 4's subtree are gone. - `@anthropic-ai/claude-agent-sdk` 0.3.319 (was 0.3.304; bundled Claude Code 2.1.319). The key chats (DeepSeek, Claude API) also pin the cloud-sessions token and the two new switches that would let a folder's settings widen which certificates Claude Code trusts. - `@cursor/sdk` 1.3.1 (was 1.0.38). Its context-compaction summaries are not shown yet. - `@github/copilot-sdk` 1.0.29 (was 1.0.23; bundled Copilot CLI 1.0.107) and `zod` 4.6.7 (was 4.4.3): one zod in the tree, without CVE-2023-54404 (GHSA-mrhx-c7xc-m3w5). - `socket.io` 4.8.4 (was 4.8.3) and `ws` 8.22.1 (was 8.21.3). `uuid` is gone: Node's `crypto.randomUUID()` makes the same ids. - package.json names its author and its bug tracker. ### Upgrade notes - **From 0.9.13 or older, copy the data out of the package first.** A relay before 0.9.14 could keep its database, uploads and config inside the package folder, which `npm install -g` deletes, and this version no longer copies them out. Copy `data/`, `uploads/` and `config.json` from the installed package's `server` folder into a new folder under `rescued-package-data` in the data folder (for example `~/.oar/data/rescued-package-data/before-update/`); the first start takes them over. From 0.9.14 on there is nothing to do. - **Termux, 32-bit ARM, FreeBSD: the update from 0.9.22 needs `oar build-native` once.** The Update button and `oar update` of 0.9.22 install 0.9.23 but do not build the binding, so the relay stops at its first start on 0.9.23: its log names the command, and the page cannot reach the relay. In Termux run: ```sh pkg install python clang make oar build-native oar start ``` Later updates build the binding themselves, before the relay restarts. - **Linux needs glibc 2.34 or newer,** for example Debian 12, Ubuntu 22.04 or RHEL 9. The SQLite binding better-sqlite3 13 ships for Linux on x64 and arm64 needs it, and a binding built with `oar build-native` would not be used there. On an older system a relay that updates to 0.9.23 stays down at its first start and says to update the operating system; stay on 0.9.22 until the system is updated. - **No migration.** Going back to 0.9.22 needs no database step. ## [0.9.22] β€” 2026-11-05 Highlights: questions that don't block move into the chat header. A **πŸ““** icon with a count opens them, urgent ones first, and the turn that asked keeps one line, "πŸ““ Question for you", in place of the card. Settings β†’ General reads as five headed sections, with three new choices: whether thoughts start open while an agent works, how many tool lines a working agent shows, counted per agent so a busy sub-agent no longer pushes out the main agent's lines, and a stream view that shows a turn's thoughts and tool calls in the order they happened. While a turn runs, the page's poll leaves out what it already holds, and an idle relay and its workers wake far less often. Claude sessions now ask you in a **Safety check** card, which only you can answer, before removals and other asks Claude Code marks as person-only. ### Changed - **πŸ““ Questions in the chat header.** A conversation's questions that don't block sit behind a πŸ““ icon with a count, first among the header icons (in οΈ™ when the header runs out of room). It opens a list under it with the full cards, urgent ones first with their details open, and what was answered in the last day below; answering keeps the list open. On a phone the list spans the width over a dimmed page, with half-width buttons. While an urgent question waits, πŸ““ and the Inbox show the urgent count in red. The Inbox's link to a session opens it with πŸ““ at that question. Each ask now remembers the turn that asked it (migration 0037). - **A question leaves one line in the turn that asked it.** The cards of questions that don't block no longer sit at the end of the conversation; the reply of the turn that asked shows a one-line "πŸ““ Question for you" instead (live while it runs, "Answered: …" once answered), and a tap opens πŸ““ at that question. A question asked for another session, carried over by a compaction or from before this version stands as one line at the end while it waits. - **Settings β†’ General in headed sections.** The tab reads as five groups, Appearance, Reading, Turns and tasks, This relay and Updates, instead of one long column. Each relay-wide row now says it is a setting of this relay; the per-browser rows keep "Saved in this browser." - **Thoughts open or closed by your choice.** Settings β†’ General β†’ Appearance has "Expand thoughts while an agent works" (on by default), with a πŸ’­ Thoughts mirror in the 🧠 modal. It decides whether thoughts start open in the live turn, in live sub-agent bubbles and in the sub-agent viewer, finished thoughts there included. An open live thought keeps a one-line header to fold it by; a thought you fold or open by hand stays that way while more text streams in. A finished reply's πŸ’­ fold stays closed as before. - **The main agent's tool lines stay while sub-agents work.** The live bubble's tool list is capped per agent now, the main agent and each sub-agent apart, so a busy sub-agent no longer pushes the main agent's lines out. Settings β†’ General β†’ Appearance has "Tool lines shown while an agent works": 50 by default, 10 to 500 or no limit. The relay sends the running turn's newest lines per agent as the page's setting asks (50 when it does not say, 500 at most), and for a finished turn the main agent's newest 200 and each sub-agent's newest 50 (it sent the first 48 of the whole turn). A sub-agent bubble that holds as many lines as the setting shows "50+ steps". - **Stream view: a turn in the order it happened.** Settings β†’ General β†’ Appearance has "Stream view" (off by default), with a 🧡 mirror in the 🧠 modal. When it is on, an agent turn's thoughts and tool calls show as one list in the order they happened, live and in the finished message, with the reply last. For Copilot, Cursor and Grok, tools and thoughts stream in arrival order and the reply comes after them. The relay now numbers a turn's thoughts and tool lines in one sequence (migration 0038); turns recorded before that keep the grouped layout. A finished turn whose main agent ran more than 200 tool lines says how many earlier ones it left out. - **The live poll leaves out what the page already has.** While a turn runs, the page asks for its conversation about once a second. It now names the finished replies whose tool lines and thoughts it already holds, and the relay leaves those out unless they changed since (a busy window: about 6 KB per poll instead of over 200 KB). Opening a conversation, paging back and every other load still get everything. - **An idle relay and its workers wake far less often.** The relay no longer polls its queue twice a second: a write that makes a message deliverable wakes delivery at once, through triggers on the queue table. A retry or a usage-limit hold gets a timer for the moment it ends, and a 10 s poll stays as a backstop. A gate lifting (relay resumed, restart cancelled, tunnel connected, a kill's grace over, a worker replacement done) wakes delivery too. The CLI status, the share-viewer prune and the worker prime run only while there is something for them to do. Each worker has one keepalive instead of two 10 s timers, and it beats every 30 s while idle, on a relay that supports it. A silent worker now counts as gone after 90 s instead of 30 s. Workers and relays of older versions keep working with the new ones. - **Claude sessions ask you before removals and other asks Claude Code marks as person-only, instead of allowing them.** Such an ask, for example an `rm -rf` of the working folder, used to be approved by the relay like every other ask. Now a **Safety check** card, in the session and in the Inbox, shows Claude Code's reason and then the whole command, each line behind `β”‚`, with hidden or reordering characters shown as `\u{…}`. It offers **Allow once** and **Deny**, takes no typed reply, and only you can answer it: no agent, of this relay or another, sees it among a session's questions or may answer it. It allows only that one call and adds no rule. A denial, no answer within the question timeout, a stopped turn or a relay that cannot show the card all leave the command unrun, and a command too long to show is refused without a card. Other asks are still allowed as before. ### Upgrade notes - **Two small migrations.** 0037 adds the asking turn to each ask and 0038 the sequence number to a turn's thoughts and tool lines, with an index; neither rewrites the rows already there. As before, the relay copies its database to the backups folder before the first start of a new version. - **Every session worker is replaced once after the update,** as in 0.9.21: once its session is idle and its CLI has ended, usually some 10 minutes after its last turn. A Claude session shows the **Safety check** card once its worker runs 0.9.22. Nothing needs to be done. - **Mixed versions work together.** A 0.9.21 worker on a 0.9.22 relay keeps its 10 s beat, and a 0.9.22 worker on a 0.9.21 relay beats every 10 s. Paired relays of both versions keep working with each other. - **No new dependency.** Going back to 0.9.21 needs no database step: it leaves the new columns and the index alone. ## [0.9.21] β€” 2026-11-01 Highlights: the chat screen is more compact. The mode, model, effort, context window and billing of a chat move behind **βš™** in a slim label row above the message box, such as `πŸ”’ Opus 5.5 Β· high Β· Auto`. The header menu is now **οΈ™**, and the header's icon buttons stay there while there is room and move into it when there is not. Spacing is about halved across the app, with controls and hit areas at 28 px. Back and forward walk through the conversations you opened, with `?conv=` in the address bar, and back closes what is open on top first. A pinch, Ctrl/⌘+wheel or Text scaling zooms the whole app, not only the text. A phone held sideways keeps the phone's layout. A session group folds while one of its chats is open, the filter row folds or unfolds all groups at once, and a group's name takes its sessions' colour. The origin, watched and scheduled pills of a sidebar row become one **β“˜**. After a deploy, the relay replaces each session worker still on the old code once its session is idle, so a restart no longer leaves old worker code running. ### Added - **Chat settings behind βš™ in the composer.** A slim label row above the message box holds **βš™**, a label that says what the next message runs with (model Β· effort Β· mode, such as `πŸ”’ Opus 5.5 Β· high Β· Auto`, with the context window added when it is not the model's default), the billing pill of a Claude chat, the file browser, **πŸ“Ž** and, on a desktop, **πŸ™‚**. **βš™**, or a tap on the label, opens the settings: **Mode**, **Model** with the lock note under it, **Effort**, **Context window**, **Billing** and **🧠 Context usage**. A tap on one part of the label opens them at that field, and the **Subscription** or **API key** pill at **Billing**. On a desktop they open as a popover above the row, on a phone as a sheet from the bottom; **βœ•**, Escape, a tap outside or back closes them. An image chat shows **Quality** and **Image size** in place of Effort and Context window; a Claude Cloud chat has only **Model**. The label reads **Auto** for the autopilot mode (the settings keep the full name) and **Plan πŸ”’** on a plan-only session. - **Back and forward through conversations.** Each conversation you open is one step in the browser's history, with `?conv=` in the address bar, so back and forward walk through them, and a reload or a copied address opens that conversation. The tab's title is the conversation's title. Back first closes what is open on top: the session list on a phone, the Inbox or Status view, the composer's settings, the **οΈ™** menu, a row's menu, dialogs, the git, tmux and annotate views, the watch and schedule panels, the emoji picker and the Change CWD folder list. On a phone, back from the first conversation you opened shows the session list, then the installed app's *Press back again to close the app* as before. - A link to a conversation the relay does not know (deleted, or a wrong id) clears the view with a notice and keeps the last conversation; back and forward step over it from then on, and nothing opens by itself until you pick a conversation. A link to an archived conversation shows its title. - No address is written before the first conversation shows, so a link survives an early tap. A jump from the Inbox to a question replaces the history entry instead of adding one. - A notification tap still opens `?push_conv=`; the address then reads `?conv=`. - **Zoom scales the whole app.** A pinch, Ctrl+wheel (⌘+wheel on a Mac) or **βš™οΈ Settings β†’ General β†’ Text scaling** now zooms text, buttons, spacing, the sidebar, menus and dialogs alike, so the layout keeps its proportions; at 100 % nothing changes. Popovers and sheets are placed anew on a change. The new-chat dialog's selects and inputs follow the zoom too (they stayed at the browser's fixed 13.33 px). - **A phone held sideways keeps the phone's layout.** The header is one line. While the keyboard is up, the composer's label row folds into a floating **βš™** that opens the settings and keeps the keyboard up, and the background-task line into a floating **πŸ€–**; both come back when the keyboard goes down. Picking a conversation closes the session list and does not bring up the keyboard (a long press does not pick). The **οΈ™** menu gains entries for the watch list, the schedules and the origin. - **Fold a group with its chat open, and fold all.** A group folds even while one of its sessions is open; that session stays under the folded head until you open another one, and opening a member of a folded group leaves it folded. A drop on that kept row is a drop on the folded head. While the list has groups, the filter row has a fold-all button: **βž–** folds every group, **βž•** unfolds them all once all are folded. It counts as your own choice for each group and is disabled while the list is filtered. The Archived view keeps its folds apart from the live list. - **A group's name takes its sessions' colour:** red while one waits for you, bright green while one works, dark green while one is idle with its session ready, else the plain title colour. Archived heads stay muted. - **β“˜ on sidebar rows.** The origin, watched and scheduled pills of a row fold into one **β“˜** in its second line. Its menu lists each with its action: *Started by an agent in "…"* (or *on relay "…"*), which opens that conversation or the session on the other relay, *Watched by …* with the watchers' names, and *Scheduled Β· next …* (or *N schedules Β· next …*). A member under the head row of the session that started it leaves the origin out. - **Session workers are recycled after a deploy.** Every worker states the build id (version and commit) it started on; a worker on another build than the relay, or one too old to say, is stopped and relaunched by the relay once it is idle, the way **Change CWD β†’ πŸš€ Set new CWD and (re)launch** does, so a restart no longer leaves old worker code running. An archived conversation's worker is stopped for good. `/api/status` and the About modal show which build each worker runs. - Idle means no running turn, open question card, compaction, background task or message on its way in, and the worker's CLI has ended. A CLI ends by itself about 10 minutes after the last delivery or turn, so a session is usually replaced some 10 minutes after its last turn. A worker too old to say whether its CLI runs is replaced after 10 minutes without a delivery or turn end. At most two are replaced at a time, one start every 3 seconds, and each session once per relay build. - Nothing is lost: the new worker resumes the native session, and a message sent meanwhile waits and goes to the new worker. A Cursor session is the exception for background shells and tasks, which its SDK does not report: only the 10-minute quiet window protects them. - **About OAR** shows *Workers: 14, all on this build* or *Workers: 14, 3 on an older build*. `/api/status` gives the relay's `buildId` and, per worker, `buildId`, `startedAt` and `stale`, and the recycle's `staleCount`, replacements and the sessions it waits on with the reason. relay_tool's session items carry `worker { kind, buildId, stale }`. The relay log has `[worker-recycle]` lines for each replacement and, at most once a minute per session, what it waits for. - A Claude Cloud worker opens its event stream from the stored cursor at start, so turns the cloud session starts by itself are followed after a replacement too. New worker route `GET /api/claude-cloud-session`. - The troubleshooting guide has a section, *Workers after a deploy*. ### Changed - **The header menu is οΈ™, and the icon buttons move into it when the header is narrow.** **πŸ“** pins and **πŸ™ˆ** hidden messages (while there are any), **πŸ”** search, **🧠** context usage, **πŸ“Š** Check Usage, **β˜€οΈ/πŸŒ™** Day/Night and **β›Ά** fullscreen stay in the header while there is room and move into **οΈ™** under their full names otherwise, the last ones first. The menu stays inside the window, is capped in height and scrolls. Fullscreen is disabled where the browser has none. Relay refusals, log lines and help texts that named the `β‹―` menu now name **οΈ™**. - **The open-question banner sits between the title and the header buttons,** so **οΈ™** always stays at the right edge. Where room is short (a phone held sideways, a narrow desktop window) it shrinks to a **❓ N** chip; on a wider header it takes at most 40 % of the row and ends in "…". A long title ends in "…" too. When even the chip, **☰** and **οΈ™** do not fit next to the title, as at 150 % zoom with the sidebar open, the banner takes a row of its own; on a phone held upright it always does. - **About half the spacing, and 28 px controls.** Paddings, margins and gaps in menus, the sidebar, dialogs, Settings, cards, bubbles and panels are about halved, and controls that were 30 to 46 px high are 28 px, also dialog buttons and tabs. Every hit area is at least 28 px at 100 %: the row's **β“˜**, fold, archive and delete, the live reply's **Stop** and **πŸ’­ πŸ“ƒ πŸ› οΈ** follow buttons, the Previews tab's **Open**, **Copy link** and **Close**, the phone's header chips and the question banner, the sidebar head buttons, Settings fields, selects, checkboxes, radios and sliders, the repo browser's toolbar, the group picker, the dialogs' **Γ—** and **↻**, the Relays **Add**, the Sharing row buttons, **Unsubscribe** and **Cancel**. Small labels and links get an invisible 28 px hit area: the repo browser's Focus and crumbs, tree rows, Sharing row titles, DeepSeek **Advanced**, the plan-only label, the waiting pill, the agent-exchange and activity folds and the header chips. - **On a phone the lines under the title are one row** that scrolls sideways, with short labels (`β†— title`, `πŸ‘ 3`, `⏰ 04:15`, the folder's last name), and the open-question banner gets a row of its own. - **Dialogs on a phone keep their title** with a compact **Γ—**; the header buttons no longer stretch across the width. Select Models puts its three buttons under the title. - **A group's head.** One chevron **β€Ί** that turns down while the group is open replaces β–Ύ/β–Έ; focus returns to it only after a keyboard toggle, with a focus ring in the accent colour. A head line without a head row reads " Β· N sessions", and its pill shows only what waits, runs or failed. A long head drops its message count whole, then its date, instead of cutting them. A mouse click on **β“˜** hands focus back without a ring. - **Sidebar rows.** The title and the "1 open" pill share one line and only the title is cut short; the title and meta line keep clear of the archive and delete buttons. Archive and delete show on keyboard focus too. The filter row's archive button reads **πŸ—„οΈ**, and the buttons close up to the right when **Γ—** or **βž•** is hidden. The sidebar head follows the zoom and wraps instead of cutting off **Inbox** or **Install**. - **The Claude usage warning above the composer is one line,** such as `⚠ Claude 94 % weekly Β· resets Tue 04:00`; a tap shows the full sentence. - **A sub-agent's Stop is red and asks first** (**⏹ Stop agent**). - **On a phone a background task's title has its own full-width line.** - **Images in messages have no card around them.** - **Editing a chat's title on a desktop takes the whole header row** while you type; the title lines, the banner and the header buttons come back when you are done. - **Settings takes the focus when it opens, and Escape closes it.** The Settings and Plan usage tab strips fade at an edge while more tabs lie beyond it, and keep the selected tab in view. The Features help wraps long identifiers. - **Pack guard ceilings.** The npm tarball may hold 610 entries and 9.5 MiB unpacked (590 and 9 MiB before); 0.9.20 had used 99.3 % of the bytes. ### Fixed - **The archive button on a sidebar row is visible on touch screens.** It was invisible there but could still be tapped. - **A οΈ™ entry tapped on a phone no longer closes what it opened.** The tap's follow-up click landed on the menu's backdrop or the new dialog and closed it again: About, Kill session, Check Usage, Context, Restart relay and Search, and the watch and schedule panels; Git changes did not open a diff. - **Contrast in the light theme.** Primary buttons read at 5.19:1, red buttons at 4.67:1, the sidebar's "1 open" pill at 6.47:1, Settings help links at 6.85:1, the offline banner at 5.6:1 and the **☰** status tones at 4.9:1 or more. A hovered accent button gets a darker fill (6.36:1) instead of turning paler. - **Status and the Inbox, closed with nothing to go back to,** show their empty state instead of a blank pane. - **The OpenAI, Grok and Cursor fields in Settings look like the others,** and **Use a different token** and **Empty queue** are real buttons. - **The βš™ popover fits its width under zoom,** the full origin title shows, and focus is kept when the list or header is drawn anew. ### Upgrade notes - **Every session worker is replaced once after the update.** Workers from 0.9.20 state no build id, so 30 seconds after the relay first hears of one it counts as on an older build and is replaced once its session is idle and its CLI has ended, usually some 10 minutes after its last turn. About shows *M on an older build* until then. Nothing needs to be done. Each replacement costs one cold start on the session's next message. - **No migration and no new dependency.** Going back to 0.9.20 needs no database step. ## [0.9.20] β€” 2026-10-29 Highlights: every plan card gets an "Approve plan" button that approves the plan without starting the implementation and keeps the session in plan mode, and a session can be locked in plan mode (`planOnly`) so agents cannot take it out; plan cards are in the Inbox. Check Usage shows the tokens of every turn for every provider: input, output, cache writes and cache reads, for today, this month and all time, and by model. The relay backs up its database before a new version's migrations and keeps the last two copies; `oar backup` takes one by hand. A new database is `oar.db`; an existing install keeps running on `copilot.db`, nothing is renamed. Under the systemd service a restart hands the relay to systemd with its new code, waits for running turns and keeps session workers, and `oar service` works in a git checkout. A paired relay's approval cards in the Inbox say what they are about, a card a turn there waits on pushes once to your devices, and the badge is current within about 20 s. An open fold in the Inbox stays open. ### Added - **"Approve plan" on every plan card.** It is the first button of every provider's card (the Copilot CLI's own list gets it too), also on cards that were waiting when the relay was updated. It marks the plan approved and queues exactly "Plan approved by the user. Do not implement. Finish only the plan-only steps you were given, then stop." That one turn runs in agent mode (plan mode blocks writes, and the plan doc may need a commit); the session stays in plan mode, so the next message plans again. Pressed while the plan turn still runs, the follow-up waits for it and runs as its own turn. - **Plan-only sessions (`planOnly`).** An optional lock, set only when asked: relay_tool `create_session` with `planOnly: true` (no mode named: it starts in plan; another mode is refused), `update_session` with `planOnly: true` (with `mode: "plan"` unless the session is in plan already), or the owner's switch "Plan only: agents can't take this session out of plan mode" in the context modal, for every session. Plan mode alone never sets it. - An agent's `send` or `update_session` that names another mode gets HTTP 409, code `plan_only`: "This session is plan-only; agents can't change its mode. Clear planOnly first, or ask the user." Nothing is queued or changed (an update_session dry run refuses before any Allow card). A send without a mode runs in the session's saved mode. An agent's mode change through the preferences route is refused (409), and so is any planOnly change there (403). - `update_session` with `planOnly: false` counts only in the same call that names a mode other than plan (`{planOnly: false, mode: "agent"}`). Alone, or with `mode: "plan"`, it is ignored, and on a plan-only session the answer says "planOnly unchanged: clear it in the same call as the new mode", so a client that sends every property as false cannot drop the lock. The owner's switch clears it any time. - The owner still changes the mode freely; the mode select shows "Plan πŸ”’". On such a session the card recommends "Approve plan" and asks "This session is plan-only.