Ctrl K
ComunidadGuías…

CHANGELOG.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# 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 <provider>" 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-<arch>`.
- **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=<id>` 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=<id>` 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=<id>`; the address then
    reads `?conv=<id>`.
- **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
  "<last active> · 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. <label> leaves plan mode. Go on?" before "Implement
    in autopilot", "Implement with autopilot fleet" and "Stop here and prompt
    myself".
  - list_sessions and read_session show `planOnly: true`. A compaction keeps
    the lock; a turn cut by a usage limit or a restart goes on in plan, except
    the "Approve plan" turn itself. A plan-only conversation's own agent counts
    as a plan-mode caller during that turn too.
  - Migration 0035 adds `conversations.plan_only`.
  - A relay on 0.9.19 still sends its caller's mode with every `send`: a send
    from a non-plan 0.9.19 caller to a plan-only session is refused. A
    `create_session` with planOnly to a 0.9.19 relay creates the session
    without the lock and says so (`planOnlyWarning`).
- **Relay event `board_settled`.** A session's subscribers hear when one of
  its plan cards is settled, "Stop here" included, with `boardId`,
  `boardType`, `action`, `actionLabel`, `title` and `queuedMessageId`. The
  line reads:

  ```
  [relay event] session "<title>" (<id>): board_settled "<card title>" → "<label>" (action <action>, board_id <board id>, queued message <message id>)
  ```

  For "Stop here" it ends `(action exit_only, board_id <board id>, nothing
  queued)`. It is one of the default event types, also for subscriptions
  stored with the 0.9.19 default; `match` applies to its action, label and
  card title.
- **Plan cards in the Inbox.** For each session that is neither deleted nor
  archived, its newest pending plan card, with the plan folded and the card's
  own buttons (and the plan-only question); plan cards settled in the last
  day show what was chosen. They count in the badge. A paired relay's Inbox
  shows their title only, with "Open there".
- **Other relays' approval cards are easier to notice.** A paired relay's
  approval card in the Inbox says what it is about: "New session · the
  session waits", then "relay B · New session requested by 'X': title,
  model" (or loop guard, write, session settings, watch, start sessions),
  still with "Open there" only. The agent's prompt, answer or question never
  leaves its relay. Every relay now checks its paired relays about every
  20 s, even with no browser open, so the badge is current within about
  20 s. A card a turn there waits on (an approval, or a question the turn is
  blocked on) pushes once in total: its own relay pushes as before, and when
  that reached none of your devices, the first paired relay with push on
  that checks it pushes instead (`Approval waiting on relay "B"`), with no
  answer buttons; the tap opens the session there. It follows the device's
  "questions" toggle and notification settings: with previews off, an
  approval's body carries no summary, only its kind ("New session · the
  session waits"); the title names the session only with the device's
  include-title setting on.
  - `GET /api/relay-asks/remote/inbox`: approval items gain `variant` and
    `summary`; with `?push=1` (the caller can push), pending approval and
    ask_user items gain `notice` (`here|wait|you|other`), and `you` gains
    `claimed` (`now|before`). A 0.9.19 relay ignores both and never pushes
    another relay's card. New socket event `inbox_remote_changed`. A push's
    data may carry `url`. Claims are in memory; no migration.
- **The relay backs up its database before a new version's migrations.**
  At the first start of each new version (in a git checkout: of each new
  commit) it copies the database into `backups/` in the state root
  (`~/.oar/backups`, `%APPDATA%\oar\backups`; `server/backups/` in a
  checkout) before the migrations run, as one checked file named after the
  version that ran on the data, such as
  `oar-0.9.19-20261103-091500-pre-update.db`. It keeps the last two
  (`dbBackups.keep` in `config.json`; `dbBackups.enabled: false` turns it off,
  `dbBackups.dir` moves the folder) and deletes only files it wrote itself:
  hand-made backups, `MIGRATION.md` and the `backups/<time>/` folders of
  earlier moves stay as they are, and `oar doctor` lists them as not rotated.
  When the disk would keep less than max(1 GiB, 5 %) free, or the folder holds
  a `DO_NOT_TOUCH` file, the copy is skipped with a warning in the log, in
  About, in `oar doctor` and in the in-app update's log, and the start goes on.
  Plain restarts take no copy. While it runs, `oar start` says the relay is
  backing up its database. A start while `oar backup` writes a copy waits
  for it (up to 15 minutes; `oar start` says so and leaves it running), then
  makes its own in any case.
- **`oar backup [--reason <text>]`.** Copies the database while the relay
  runs, into the same folder with the same name and rotation
  (`oar-<version>-<time>-manual.db`, or the reason given).
- **About and `oar doctor` show the database file** by its full path with its
  size, and the backups: the last one, how many are kept, and the folder.

- **Tokens per turn for every provider, in Check Usage.** Every tab gets a
  **Tokens** section (today, this month, all time) and **Tokens by model,
  this month**, with fresh input, output, cache writes and cache reads apart
  and the share of input the cache served. A figure a provider does not report
  reads "—" with the reason in the section's note; 0 means reported zero.
  Copilot turns on your own OpenAI key show as their own group, "Tokens — own
  OpenAI key". **About OAR** shows each provider's tokens of all time,
  Claude, Claude API and DeepSeek included. Each turn's tokens are kept per
  run and model with the chat and go when it is deleted; the daily sums stay.
  The Claude API and DeepSeek days from before the upgrade are copied in once,
  with cache writes inside input. Both sections open expanded. Two model ids
  with one name (a dated and an undated Claude id) are one row, their tokens
  added up; pointing at the row shows the ids.
  - Detail rows in `GET /api/usage` may carry a `title`: on a merged Tokens
    by model row, `Model ids: <id>, <id>`. Those rows are sorted by name.
  - New worker route `POST /api/turn-token-usage` and tables `turn_token_usage`
    and `token_usage_days` (migration 0036). Every worker posts after each
    turn: Claude, Claude API, DeepSeek, Claude cloud, Copilot (plan and own
    key), Cursor and Grok.
  - `OAR_DEBUG_TOKEN_USAGE=1` makes the Copilot worker log each model call's
    raw token counts (no ids).

### Changed

- **No plan summary in a card's follow-up.** "Implement in autopilot",
  "Implement with autopilot fleet" and "Stop here and prompt myself" queue
  only their line; the plan is no longer repeated after it.
- **relay_tool `send` without `mode` leaves the target's mode alone.** It
  used to run the target's turn in the calling session's mode (a plan-mode
  agent put the sessions it prompted into plan mode). Name `mode` when a turn
  should run in a given one. `create_session` without a mode still takes the
  calling session's.
- **Switching between plan mode and a writing mode during a running turn
  queues the message instead of steering it in.** A steer switched the running
  turn to the message's mode, so a plan turn could gain write access halfway
  through, or a writing turn lose it. Such a message now waits and runs as its
  own turn once the current one ends; the composer reads "Queue" for it.
  Messages in a mode on the same side (agent, ask and autopilot with each
  other, or plan with plan) steer in as before.
- **A new database is `oar.db`.** The relay's database file gets the
  product's name: a database created from nothing is `oar.db` (with
  `oar.db-wal` and `oar.db-shm`). An existing install keeps running on
  `copilot.db`; nothing is renamed, and `oar doctor` and About show which file
  a relay runs on. A database brought over from elsewhere (an earlier
  install's package, `oar setup --migrate-from`) keeps its own name.
- **`oar.db` and `copilot.db` in one data folder.** When
  `oar.db` and `copilot.db` are both there, the start reads both. A
  `copilot.db` that is not newer, is empty, or holds only what a start of an
  earlier version writes (no conversation, message or other work; a few
  start-up settings) is set aside as
  `copilot.db.older-<time>`, never deleted, and the relay runs on `oar.db`.
  A `copilot.db` with newer work, or each newer somewhere, stops the start
  (exit code 78) with both files named and nothing changed: move the one not
  to go on with into a folder of your own, then start again. An `oar.db-wal`
  or `oar.db-shm` without `oar.db` stops the start too (a database renamed to
  `oar.db` later would take its pages in, losing the rows written since):
  move it into a folder of your own.
- **Migrations and `npm run deepseek:import` run by hand never create an
  empty database.** Without a path they take the relay's database in
  `server/data` (`oar.db`, else `copilot.db`); a path given must exist.
- **`oar uninstall --purge` says it deletes the database backups** with the
  state root.
- **The DeepSeek card's "Tokens by model" is now "Priced tokens by model"**:
  the cache hits, misses and output its spend estimate prices, beside the
  Tokens sections every tab gets.
- **The Claude API card's "Tokens by model" is replaced** by the Tokens
  sections every tab gets, which show cache writes apart instead of inside
  "other input". Its spend by model and the cap stay.
- **Cache writes are no longer lost** for the Claude family, Copilot, Cursor
  and Grok: the token booking keeps them apart. The older bookings (the Claude
  API spend, the DeepSeek estimate) still count them as misses, unchanged.
- **The Claude API spend no longer counts a turn twice** when Claude Code
  reported one of its results without per-model usage: the next result's
  growth used to include that turn again.
- **The systemd service restarts the relay with new code, waits for running
  turns, and keeps session workers.** A relay under the systemd user service
  hands a restart (**🌄 Restart web relay**, the **Update** button,
  `oar update`, `oar restart`) to systemd once its turns are done, which
  starts a fresh supervisor and runtime from the code on disk; before, only
  the runtime was relaunched and the supervisor kept its old code. `oar stop`
  and `oar restart` under the service now go through the relay too and wait
  for running turns (the unit stays enabled after a stop); `--now` goes
  through `systemctl --user` at once, as both did before, and so does a relay
  that does not answer. Without the service, `--now` asks the relay not to
  wait, as **Restart now** does. The relay starts its session workers in
  scopes of their own (`systemd-run --user --scope`), so a stop or restart of
  the service no longer ends them. Detached helpers the relay runs for a
  while (a provider CLI being installed, a provider login) still end with a
  full restart of the service. A stop that comes in while the restart is
  handed to systemd wins: no restart follows. On a host with cgroup v1 next to
  an unused v2 hierarchy (a bare `0::/` line) the relay still finds its unit.
- **The unit restarts a failing relay with a backoff and never gives up.**
  The relay's supervisor relaunches the first two crashes itself; then
  systemd starts it again after 2 seconds, with longer waits up to 5 minutes
  (systemd before 254: every 10 seconds). The unit has `KillMode=mixed`, so
  no runtime of a dead supervisor is left holding the port.
- **`oar service` works in a git checkout on Linux.**
  `node bin/oar.js service install` writes a unit that runs
  `node server/server.js` in the checkout, on its own config, data and logs;
  `node bin/oar.js start|stop|restart|status` go through it. A hand-made unit
  counts as the checkout's service only when its `ExecStart` runs
  `<checkout>/server/server.js`.
- **Hand-made units are left as they are.** A unit OAR did not write for this
  install (edited or written by hand, or another install's, such as the
  global install beside a checkout) is no longer written over by
  `oar service install` or `oar setup --start`, or deleted by
  `oar service remove`: they name it and change nothing. `--replace` replaces
  or removes it, keeping a copy as `oar.service.bak`. `oar service status`
  says whose the unit is, lists the drop-ins in `oar.service.d/` (your own
  settings, never touched), and says when lingering is off.

### Fixed

- **Archiving or deleting a chat whose session worker is gone no longer
  fails with "Couldn't stop this conversation's session".** The relay still
  tried to stop the worker it last knew of; on Windows that stop could fail
  on its own and answered 409 `worker-stop-failed` until a later try. A
  worker whose process has ended counts as stopped. When a stop does fail,
  the relay log says why.
- **An open fold in the Inbox no longer closes by itself.** Every change of
  a question and the minute's check of paired relays drew the Inbox anew,
  with every **Details** and plan fold closed. An open fold now stays open
  until you close it or its item leaves the list; the same for a question's
  **Details** on its card in the session.

### Upgrade notes

- **Under the systemd service, run `oar service install` after updating**
  (`node bin/oar.js service install` in a checkout) to get the new unit. A
  hand-made unit is reported and kept; `oar service install --replace` puts
  OAR's in its place, keeping a copy as `oar.service.bak` (drop-ins in
  `oar.service.d/` stay and keep applying).
- **A checkout's hand-made unit that only runs in the checkout is not its
  service.** A unit with `WorkingDirectory=<checkout>` whose `ExecStart` does
  not name `<checkout>/server/server.js` (`npm start`, a relative
  `server/server.js`) is kept as hand-made: `node bin/oar.js start` and
  `restart` refuse, and `status` says it is not for this checkout. Run
  `node bin/oar.js service install --replace` to put OAR's unit in its place,
  keeping a copy as `oar.service.bak`.
- **The supervisor of 0.9.19 keeps running until the service restarts once
  by systemd:** at an idle moment, run `oar restart --now`. That restart ends
  running turns once; after it, restarts wait for them.
- **Session workers started before that restart may stop at it, once.** They
  were started inside the service; those started afterwards run in scopes of
  their own.
- **Nothing to do for the new database name.** A relay that ran on
  `copilot.db` goes on running on it. To give it the new name, by hand and
  with the relay stopped: take a backup (`oar backup --reason pre-rename`),
  stop the relay fully (`oar stop`, or the systemd service or autostart, so
  nothing starts it again), check that no program holds the files (Linux:
  `fuser copilot.db*`; Windows: close database browsers and Explorer previews),
  make sure no `oar.db-wal` or `oar.db-shm` is there already (move one that
  is into a folder of your own), rename `copilot.db`, and `copilot.db-wal`
  and `copilot.db-shm` where they are there, to `oar.db`, `oar.db-wal`,
  `oar.db-shm`, and start again.
  `oar doctor` then shows `oar.db`. Scripts of your own that open
  `copilot.db` by path need the new name first.
- **Going back to 0.9.19 on `copilot.db` needs no step.** Migrations 0035 and
  0036 only add the column `conversations.plan_only`, the tables
  `turn_token_usage` and `token_usage_days` and an index; 0.9.19 opens the
  database as it is and ignores them, so a plan-only lock does not hold on
  0.9.19, and the tokens kept so far are there again at the next 0.9.20
  start. Turns run on 0.9.19 book no tokens.
- **Going back to a version before 0.9.20 on `oar.db`** (a new 0.9.20 install,
  or one renamed by hand): stop the relay fully, make sure no
  `copilot.db-wal` or `copilot.db-shm` is there already (move one that is
  into a folder of your own), rename `oar.db`, `oar.db-wal` and `oar.db-shm`
  (those there) to `copilot.db`, `copilot.db-wal`, `copilot.db-shm`, then
  start the earlier version. A relay still on
  `copilot.db` needs no step. Skipped, the earlier version starts on a new,
  empty `copilot.db`; back on 0.9.20 that one is set aside if nothing was done
  on it, and the start stops with both names if something was.
- **The first 0.9.20 start backs up the database.** After `oar update` or a
  `git pull` its name carries the running version and says
  `pre-update-from-unknown`, such as
  `oar-0.9.20-20261103-091500-pre-update-from-unknown.db`, though it holds the
  data of the version before: earlier versions did not record which version
  ran on the data. After an in-app update it carries the version before. The
  time in a backup's name is UTC. It needs
  free space for one more copy of the database; `oar doctor` shows the
  result. Backups made by hand before (`copilot-pre-*`, `main-*`,
  `oar-pre-*`) stay where they are and are never counted or deleted.
- **After going back to an earlier version and up again, run `oar backup`
  before the upgrade:** versions before 0.9.20 do not record that they ran,
  so the second upgrade sees no version change and takes no copy.
- **To restore a backup:** stop the relay, move the live database files
  (`oar.db*` or `copilot.db*`) into a folder of your own, copy the backup into
  the data folder (it is one file, no `-wal` or `-shm`), and start the version
  the data is of. Name it as that version runs on it: `copilot.db` for a
  version before 0.9.20, else the name the relay ran on. A
  `pre-update-from-unknown` backup holds the data of the version before the
  one in its name. Open a backup only read-only
  (`sqlite3 'file:<backup>?immutable=1'`).

## [0.9.19] — 2026-10-27

Highlights: a relay that runs out of memory is restarted instead of staying
down. Its runtime starts with a heap limit, set with `heapLimitMb` in
`config.json`, and writes one heap snapshot when it nears that limit; a
snapshot holds the relay's auth token and API keys, so never share one as it
is. Agent sessions can use the providers a relay update added without a
restart of their own, and Copilot refuses another provider's model id before
it creates anything.

### Fixed

- **A relay that runs out of memory is restarted.** A relay runtime that ran
  out of memory ended the relay for good. Its supervisor took the
  out-of-memory abort for a clean stop and exited 0, which systemd does not
  restart. The runtime is now relaunched like any crash. It starts with a heap
  limit (60 % of RAM, 2048–8192 MB, set with `heapLimitMb` in `config.json`)
  and writes one heap snapshot to `diagnostics` in the data directory when it
  nears that limit. A heap snapshot holds the relay's auth token and API keys:
  never share one as it is. The status log shows its memory every 10 minutes
  and when the heap passes 80 % of the limit.
- **`relay_tool` takes any provider id.** Agent sessions started before a
  relay update refused the providers the update added (such as `deepseek` and
  `claude-api`) in `relay_tool`'s `create_session` and `send`. The tool now
  takes any provider id; the relay checks it against its current list and
  names that list when it refuses one.
- **Copilot refuses another provider's model id before creating anything.** A
  Copilot session given another provider's model id (such as `claude-opus-5-5`
  for Copilot's `claude-opus-5.5`) was created and then refused with
  `CURSOR_MODEL_REQUIRES_NEW_CONVERSATION`. Copilot now refuses such an id
  with `COPILOT_MODEL_UNAVAILABLE` and the close match before anything is
  created, and a first prompt refused after `create_session` made the session
  names that session.

### Upgrade notes

- **Agent sessions started before 0.9.19 keep their provider list:** their
  workers keep running through the update, with the `relay_tool` they
  started with. That list is the one 0.9.19 has, so nothing changes now; it
  lacks the providers a later update adds until the session's worker
  restarts, for example after **☠️ Kill session** in the `⋯` menu.

## [0.9.18] — 2026-10-26

Highlights: Claude chats can run on a Claude Console API key. Claude API is
a provider beside your subscription, with an optional monthly spend cap and
a card in Check Usage with the estimated spend. A Claude chat can switch
between the subscription and the key and keep its Claude Code session, also
when it is paused at the usage limit. The relay's agent tool is now called
`relay_tool`. DeepSeek and Claude API chats start with all hooks off, and a
subscription chat no longer runs on an API key that Claude Code's settings
hand it.

### Added

- **Claude API: Claude chats on a Claude Console API key.** A provider
  beside Claude (your subscription), billed to the Console organisation
  the key belongs to.
  - Settings → Providers → Claude API holds the key, the switch, the
    default model and an optional monthly spend cap in US dollars. The key
    is stored on the relay host and never shown again: the tab only says
    that one is saved. Any `sk-ant-` Console key is taken; Claude login
    and session tokens and Admin keys are refused when you save. A key
    Anthropic itself refuses stays saved, and the tab's status line says
    so until a save or Refresh models goes through. A cap the field cannot
    take is named under the field.
  - The relay lists the key's models from Anthropic when the key is saved,
    when Claude API is switched on again, and on Refresh models. Until
    then it offers the Claude tab's models.
  - Commit attribution, the background command limit, the effort levels
    and the compaction window stay on the Claude tab, shared by both; the
    Claude API tab links there.
  - New Chat offers Claude API, Select Models has a Claude API tab, and
    its chats show a gold "Claude API" pill and "🔒 Session locked to
    Claude API models."
  - A turn that Claude API refuses or could not run ends with buttons:
    Claude API settings (at the cap field when the monthly cap is
    reached), Switch to subscription, Add funds, Console billing or
    Console limits, as fits the reason.
  - When the relay refuses to start a Claude API chat (the monthly cap, no
    key, switched off), New Chat stays open with the reason, Claude API
    settings, and Start on subscription, which starts the same chat on
    the Claude provider; on a phone the dialog scrolls to it.
- **Switch a Claude chat between your subscription and the Claude API
  key.** `POST /api/conversation/:id/billing` moves a chat between
  `claude` (the subscription) and `claude-api` (the saved key). It keeps
  its Claude Code session.
  - In the browser, a Claude chat has a `Subscription | API key` toggle
    under the lock note while Claude API is on with a saved key. It says
    what a pending switch waits for, and why a side cannot be chosen; at
    the monthly cap it says the chat stays on the subscription, and so
    does the usage-limit banner.
  - The switch applies from the next turn: a running turn finishes on the
    billing it started on. Messages already queued run on the new one.
  - The relay restarts the chat's Claude Code between turns. It waits
    while a turn, a question, a compaction or a background task is still
    running.
  - The transcript notes each switch. The first turn on the key has no
    prompt cache and costs more.
  - The switch is refused, with the reason, when the key is missing or
    Claude API is off, when the monthly cap is reached, or when the
    chat's model is not on the target's model list.
- **Switch this chat to API key at the usage limit.** When a subscription
  chat is paused at the Claude usage limit, the banner offers the switch
  if the chat may move to the key. The paused turn then carries on on
  the key at once. Nothing switches by itself.
- **Agents can switch a Claude session's billing.** `relay_tool` `send`
  takes `provider` (`claude` or `claude-api`) for a Claude session. The
  other billing switches the session together with the message:
  - in one call, so a refused switch sends nothing and a refused message
    switches nothing;
  - the approval card says "Switches billing to API key";
  - a relay from before this version refuses it and sends nothing.
- **A Claude API card in Check Usage.** It shows what this relay's turns
  on the Claude API key are estimated to have cost today and this month,
  per model, with the tokens, and the monthly cap as a bar when one is set.
  - The figures are Claude Code's own estimate from its price table, so
    sub-agents, compaction and web search are in them. The Console's
    billing page (linked) has the real figures and the balance.
  - Days and the month follow the relay's time zone, and the cap starts
    over at local midnight on the 1st.
  - Tokens Claude Code has no price for show as "no price known" or
    "estimate incomplete", never as $0.
  - Turns paid from a Max or Team plan's subscriber API credit are counted
    too; OAR cannot see that credit.
  - The card stays while a key is saved, also with Claude API switched
    off, so the month's spend stays in view (DeepSeek's card goes with the
    provider).
- **A monthly cap for Claude API.** Once this month's estimate reaches the
  cap set in Settings → Providers → Claude API:
  - a new turn on the key does not run: the chat gets a note with the code
    `relay.claude-api-cap-reached`. A turn Claude Code starts by itself,
    when a background task reports back, gets the note and that chat's
    background tasks are stopped as soon as the relay hears of the turn,
    since each report would start another such turn; the turn itself is
    stopped when the worker takes the stop. Its first request may already
    be billed, and a short turn can end before the stop arrives. The note
    also mentions tasks no stop reaches, since they can keep starting such
    turns and keep a switch to the subscription waiting. The note comes
    once per chat until the cap clears, a Claude API setting is saved, or a
    turn of that chat runs on the key again. The same stop applies when
    Claude API has no key or is switched off;
  - a switch to the key is refused;
  - an agent's send to a session on the key and a new Claude API chat (New
    Chat or an agent's `create_session`) get 409 `CLAUDE_API_CAP_REACHED`,
    and nothing is queued or created.

  A turn that is already running finishes. A turn's cost is booked when it
  ends, so running turns can take the month past the cap. The cap counts
  only the priced part of the estimate. If the spend cannot be read, the cap counts as
  reached. Console spend limits still apply on top.

### Changed

- **A send does not switch a Claude chat's billing.** A message that
  names the other billing gets 409 `CLAUDE_BILLING_SWITCH_REQUIRED`
  (before, a Claude API chat ignored `provider: "claude"`). A message
  from a page that still showed the other billing gets 409
  `CLAUDE_BILLING_CHANGED`. Its text goes into the chat's draft, so the
  page, and a send replayed from the offline outbox, lose nothing. The
  outbox keeps the text only, not attachments.

- **No hooks or status line run in DeepSeek chats.** In 0.9.17, Claude Code
  in a DeepSeek chat ran the hooks and the `statusLine` command of
  DeepSeek's settings folder, of the chat folder's `.claude/settings.json`
  and `settings.local.json`, and of plugins, as the relay's user with the
  DeepSeek key in their environment; a folder's `SessionStart` hook ran
  before the relay had pinned the endpoint and key. Now those chats start
  with all hooks off, and so do Claude API chats. The setting is on Claude
  Code's command line, so it is in force from the start, and a folder's
  settings can't turn it back on (`scripts/claude-cli-hooks-proof.mjs`
  shows it on the bundled Claude Code). Hooks in managed settings still
  run. Chats on your Claude login keep their hooks and status line. The
  agent's own commands can still read the key; the relay redacts it in
  what it shows.
- **The relay's agent tool is now called `relay_tool`.** It was
  `remote_relay`, and it is listed under the new name on every provider
  (Claude's `mcp__relay__relay_tool`), with the same actions and fields; the
  docs and the agents' instructions use it too. OAR's own MCP server, which
  Grok and the Copilot extension engine use, still runs a call to
  `remote_relay` but no longer lists it. Tool lines written under the old
  name still show as this tool.
  - If your own instructions, permission rules or hooks name `remote_relay`
    or `mcp__relay__remote_relay`, change them to `relay_tool` or
    `mcp__relay__relay_tool`: outside OAR's MCP server the old name is
    refused.
- **A subscription chat never runs on an API key.** Claude Code takes an
  API key over your Claude login when one of its settings hands it one:
  - `env.ANTHROPIC_API_KEY` in `~/.claude/settings.json` or the chat
    folder's `.claude/settings*.json`;
  - an `apiKeyHelper`;
  - a Console login.

  Until now such a chat ran on that key and billed the key's owner. Now
  the turn stops at Claude Code's start, with a note naming the source
  and the setting to remove. At most one model request, sent a moment
  after that start, can go out first.

- **The docs cover Claude API.** The providers guide has a Claude API
  section: the key, the models, the shared `~/.claude`, the billing switch
  and when it applies, what fails closed, the monthly cap and the
  subscriber API credit. The web UI, configuration, security,
  agents-together and troubleshooting pages, the README, the server
  README and DEVELOPING.md (the worker and its live check) cover it too,
  and the feature tracker has a Claude API page. Troubleshooting has two
  known gaps:
  - a key that is not tied to a workspace gets HTTP 400 from Anthropic;
    for now, create the key inside a workspace (naming the workspace in
    OAR is planned);
  - a model Claude Code has no price for (currently Claude Haiku 5.5)
    shows "no price known" on the Claude API card, and its tokens are left
    out of the estimate.
- **Corrections to the 0.9.17 notes:**
  - DeepSeek's budget bar changes colour at 50 % and 75 % of the budget
    and turns red at 90 %; the notes said amber near it and red over it.
  - The composer's context chip shows DeepSeek's 1M window (1,048,576
    tokens) as `1.05M`.
  - The `preview` tool's hint to bind a dev server to 127.0.0.1 reaches
    only sessions that have the tool (Claude, Claude API, DeepSeek, Cursor,
    and Grok and Copilot's Extension engine through OAR's MCP server), not
    every provider. Copilot's SDK engine, and those two when the relay runs
    with `OAR_MCP_SERVER=0`, are taught the preview API without it.

### Upgrade notes

- **Downgrading to 0.9.17:** first switch Claude chats on the Claude API
  key back to Subscription. 0.9.17 does not know Claude API, so a chat
  left on the key runs as a Copilot chat there. That now includes a chat
  that started on the subscription and was switched to the key. 0.9.17
  ignores the new database columns and tables. The two triggers on the
  queue table keep stamping its new column there, which does no harm.
- **Downgrading to 0.9.17 turns hooks back on in DeepSeek chats:** they
  run the hooks and status line of their settings folders again.

## [0.9.17] — 2026-10-25

Highlights: DeepSeek is a provider of its own. Paste a DeepSeek API key and
New Chat offers DeepSeek's models, with all four modes, tools, question
cards, sub-agents and reasoning effort, the 1M context window, a usage card
with the balance and the estimated spend, and an import of earlier usage
from DeepSeek's usage export. A new session in a group now shows
at the top of the group. And the relay's own Anthropic credentials
(`ANTHROPIC_API_KEY`, `ANTHROPIC_AUTH_TOKEN`) no longer reach the agents it
starts.

### Added

- **DeepSeek is a provider of its own.** Paste a DeepSeek API key in
  Settings → Providers → DeepSeek, and New Chat offers DeepSeek with the
  models DeepSeek lists (`deepseek-flash` by default, `deepseek-v4-pro`).
  - A DeepSeek chat runs Claude Code on the relay host against DeepSeek's
    Anthropic-compatible API, so it has all four modes, tools, question
    cards, sub-agents, steering, Stop and background tasks.
  - Reasoning effort is low, high or max (high by default). The model can
    change between turns; a switch to a model without image input is
    refused once the chat holds images.
  - The context panel shows the 1M window, and the chat compacts at 400K
    unless you set another window there.
  - Commits carry `Open Agent Relay (DeepSeek Flash)` or no attribution, as
    set on the DeepSeek tab.
  - The key is stored on the relay host, is never sent to browsers, and
    reaches only DeepSeek's workers. Their Claude Code keeps its state in a
    directory of its own, without your Claude login. The tab's Advanced
    section takes another base URL (https, or http on the same machine).
  - The chat folder's settings can't route the key elsewhere. A
    `.claude/settings.json` or `.claude/settings.local.json` there that sets
    `ANTHROPIC_BASE_URL`, a key of its own or an `apiKeyHelper` took the
    DeepSeek key to that endpoint, or replaced it; the relay now pins
    DeepSeek's endpoint and key over the folder's settings, and with them
    the proxy, TLS trust, provider and credential settings, without putting
    the key on the command line, and sends nothing when Claude Code doesn't
    take them within a minute (`scripts/deepseek-cli-proof.mjs` shows it on
    the bundled Claude Code). This covers settings, not commands: hooks and
    a status line in those files still run as the relay's user, as in any
    Claude Code chat, so use DeepSeek chats only in folders you trust.
  - The agent's own commands can read the key from their environment, as
    with any key a CLI holds. The relay replaces it with `[redacted]` in
    everything a worker posts (replies, thoughts, tool activity, sub-agent
    text, questions, task output), including the first part of a key cut
    short at a text's end or before an ellipsis, the last part of one a
    text starts with, and keys changed since the relay started, before it
    stores or shows it; the DeepSeek worker does the same in its log. Task
    output the relay reads from a task's file is scrubbed by shape only
    (`sk-` keys and other long tokens), not by the saved key's exact text.
    This keeps the key out of what the relay stores and shows; it is no
    barrier against the agent, which can change the text, call the relay's
    local API without the worker header or read the relay's database.
  - The usage view has a DeepSeek card: the account balance, read with the
    saved key, and the estimated spend of DeepSeek turns on this relay, by
    model, from DeepSeek's published prices with peak and off-peak hours
    (the card names the peak hours in your browser's time zone). On Chinese
    public holidays DeepSeek charges off-peak prices all day; the estimate
    doesn't know those dates, so turns at peak hours on a holiday are
    counted at double the real price. Only WebSearch runs on DeepSeek's
    servers: its searches are counted at $0.75 / ¥5 per 1,000 requests from
    2026-10-31 16:00 UTC, on a "Search" row of their own, taken from Claude
    Code's own totals, which count the request its WebSearch tool makes.
    WebFetch reads the page on the relay host and costs only tokens, and
    DeepSeek does not bill count_tokens requests.
  - The card draws the money: a balance bar per currency, measured against
    the balance after its last top-up (kept on the relay; migration 0029
    notes when it rose), or against a starting balance set on the DeepSeek
    tab, such as the first top-up, until the next top-up replaces it (typed
    in again, the same amount too, it counts from then on); the
    daily spend of the last 30 days as columns stacked by model, with the
    day's total on hover or tap; where the money goes per model (cache hits, misses,
    output, searches) with what the cache saved; and a monthly
    budget bar when a budget is set on the DeepSeek tab, in the currency
    chosen with it (USD or CNY, named on the bar; a budget saved without one
    follows the balance's currency), amber near it and red over it, starting
    over on the 1st (UTC). No chart library; every bar has a text equivalent
    and every chart a table, in both themes. The card shows everything at
    once, figures first (the balance and the spend as label-and-value rows,
    the tables by model open, the note on the estimate last), and fits a
    phone's width.
  - A DeepSeek chat's cold start reads "Starting Claude Code for DeepSeek",
    and the `preview` tool tells the agent to bind its dev server to
    127.0.0.1 (every provider).
  - Sub-agents stay on the chat's model. Claude Code's model aliases are
    pinned to it, so a sub-agent the agent asks for as "opus" or "sonnet",
    or one without a model, runs on the chat's model, and one asked for as
    "haiku" on DeepSeek Flash; before, an alias went to DeepSeek as a Claude
    model id, which DeepSeek serves by its own mapping (a Flash chat got a
    V4 Pro sub-agent). A model switch between turns now restarts the chat's
    Claude Code, like an effort change. A sub-agent's badge names the
    DeepSeek model, and usage a Claude Code still reports under a Claude
    name is booked and priced under the DeepSeek model that served it;
    rows booked under a Claude name earlier are folded in at start-up
    (migration 0026). The folded rows are priced at one time each, the
    row's last turn, so a row that spans peak and off-peak turns is
    approximated (the token part by up to 2×).
  - **Import usage export** on the DeepSeek tab takes the two CSV files
    DeepSeek's usage page exports (amount and cost) and shows what was
    imported. The usage card then shows DeepSeek's own figures for the saved
    key on the days the export covers, by the export's days, and the
    estimate on the other days; the card says through when. Only rows in
    the card's currency are used, and the import says when they won't be.
    A re-import replaces the key's days it covers, a day with a period that
    has not ended is left out, and so are export days up to and including
    the first day of the relay's daily history.
    `npm run deepseek:import -- amount.csv cost.csv [db-path]` does the same
    from the shell. The relay's own usage logging is unchanged (migration
    0030 adds a table of its own).

### Changed

- **A new session in a group shows at the top of the group.** In a group
  you ordered by hand, a session an agent creates there, one started with
  the group chosen, or one moved in by its menu used to join at the end;
  it now comes first, right under the group's first session. The order you
  set stays, and the next drag numbers the members again from there.
- **A host's `ANTHROPIC_API_KEY` and `ANTHROPIC_AUTH_TOKEN` no longer reach
  any worker.** A relay started with either in its environment handed it to
  every worker it started (Copilot, OpenAI, Cursor, Grok, Claude, Claude
  Cloud, DeepSeek), on every platform, and Claude Code prefers either to the
  Claude login. Now no worker gets them, and under tmux the worker's shell
  also unsets them, so they cannot come back from a tmux server that was
  already running or from your login profile. Claude chats that ran on such
  a key now run on the host's Claude login.

### Fixed

- **The Inbox opens at the top, or where you left it.** It used to open at
  the scroll height of the transcript it replaced, somewhere in the middle of
  the list. It now opens at the top the first time and afterwards where it
  was scrolled when you last left it.
- **The session header on a phone comes in two rows.** ☰, the title and the
  buttons share the first row, so the title gets the width left after the
  buttons. The "via agent" line, the watching, watched and scheduled chips
  and the folder sit side by side on a second row across the header, and a
  chip no longer breaks inside its text. The desktop header is unchanged.
- **A DeepSeek chat's failures carry DeepSeek's codes, and their buttons
  show.** A refused DeepSeek request printed as `relay.claude-api-error`,
  and a used-up balance or a refused key as `relay.claude-deepseek-balance`
  or `relay.claude-deepseek-api-key`, which the **Top up DeepSeek** and
  **DeepSeek settings** buttons never matched. They now print as
  `relay.deepseek-balance`, `relay.deepseek-api-key`,
  `relay.deepseek-api-error` and so on, without the Claude account advice.
- **A relay's Claude login token no longer reaches workers that are not
  Claude's.** On a relay started with `CLAUDE_CODE_OAUTH_TOKEN` in its
  environment, the Copilot, OpenAI, Cursor and Grok workers got the token too:
  on Windows and without tmux through the environment they inherit, and under
  tmux whenever the launch started the tmux server, which then kept it for
  every later session. Now only Claude Cloud and Claude workers get it, under
  tmux through the owner-only file. Local Claude chats on a host that runs on
  the token alone now get it under tmux as well.
- **A relay started from inside a Claude Code session no longer hands that
  session to its workers.** Its variables (`CLAUDECODE`, the session id, the
  messaging socket and token, entrypoint, `CLAUDE_PID`, `CLAUDE_EFFORT`)
  reached every worker, with and without tmux, and a DeepSeek chat's Claude
  Code took itself for a child of the host's session. The launcher now drops
  them for every provider, and the DeepSeek backend drops them again. The
  values of one launch (worker kind, model, folder) go into that session's
  command only, no longer into the environment a tmux server started by the
  relay kept for every later session. Under tmux that was not enough: a new
  tmux session starts from the tmux server's own environment, which a
  server started from inside a Claude Code session, or by an older relay,
  carried on to every worker. Every worker's command now begins by unsetting
  the host session's variables and the launch variables it sets no value
  for, so a stale model or worker kind from an earlier launch is gone too.
  The host session's variables are matched by family as well as by name
  (every `CLAUDE_CODE_SESSION_*`, `CLAUDE_CODE_*_SESSION_ID` and
  `CLAUDE_CODE_MESSAGING_*`, with the SDK host's version and app markers
  and the Bash cap a relay's Claude worker gives its CLI), so a name a
  newer Claude Code adds is dropped too; your own `CLAUDE_CODE_*` and
  `CLAUDE_AGENT_SDK_*` settings stay. A DeepSeek chat's Claude Code also no
  longer gets the relay's settings for its worker (worker kind, the relay's
  folders and tools file, `COPILOT_ALLOW_ALL`, `SESSION_ID`);
  with them the worker log file a relay started from inside a worker
  session inherited, and, by name, every secret the launch carries no value
  for (other kinds' and its own alike) that a shell may have exported into
  the tmux server's environment; the worker's own keys come from its secret
  file, read after that.
- **The DeepSeek spend no longer undercounts a Claude Code that started
  counting over, and no longer double-counts a report that fell behind.**
  The relay books what a worker's report adds to the session's last totals;
  totals below them were booked as nothing. Now the worker names its Claude
  Code process in each report (`runId`; migration 0027 keeps it with the
  checkpoint): totals below the checkpoint from the same process are a
  stale report (a retried post, or one delivered after a later one) and
  book nothing, while from another process they are a CLI counting from
  zero again on the same session id, booked in full. The web searches and
  fetches are reported as the process's running counts and booked the same
  way, so a retried post no longer counts them twice.
- **The DeepSeek spend no longer books a session's history again after a
  Claude Code died hard.** Claude Code writes its cost state at exit, so a
  resumed CLI that was killed left an older snapshot, and the next resume
  restored totals below what the relay had booked: read as a process
  counting from zero, the whole restored history was booked once more. The
  worker no longer reports what the CLI restored: it reports each process's
  own totals, counted from zero (what the session totals grow by per
  result; for the first result of a resumed process the turn's own usage,
  which leaves out what sub-agents spent in that one turn), and the relay
  books a new process in full and the increase within one. The search and
  fetch counts keep a run id of their own in the checkpoint (migration
  0028), so a report with counts only no longer moves the token totals' run
  id, and a `/clear` in the CLI, which starts a new session id, starts the
  worker's counts over with it instead of booking the old session's under
  the new one. A turn that finishes after its Claude Code died (a background
  task's continuation whose row was still registering) reports under that
  process, not under the one running by then or as a report from no
  process, and a result that arrives behind a newer one never lowers the
  worker's baseline. A late report of an earlier process after a later one
  started is booked in full, and the later process's next report is booked
  in full too: the relay cannot tell either from a new process.
- **OpenAI image models work on the SDK engine again.** A turn on
  `gpt-image-*` or `dall-e-*` went to the Copilot runtime as a chat turn
  and failed ("No available capacity was found for the model", or a request
  rejected as invalid). It now goes to the OpenAI Images API, as on the
  Extension engine, and an attached image is edited. A refused image request
  now fails its turn with `relay.image-provider-rejected` instead of staying
  in progress until the relay ran it again, and an OpenAI chat turn that
  fails on the SDK engine says OpenAI rather than Copilot.

## [0.9.16] — 2026-10-24

Highlights: agents can file the sessions they start into your session
groups, change another session's model, mode or other settings, watch other
sessions with fewer wake-ups (digests, quiet hours, exclusions), and
schedule prompts that survive a relay restart. Agents can ask you a question
without stopping their work, and the new Inbox lists every open question
from this relay and its paired relays in one place. A new switch lets the
agents of a conversation start sessions themselves.

### Added

- **Agents can put a new session into an existing session group.**
  `remote_relay` create_session takes `group` (a group id, or its name in
  any case) on this relay or a paired one, and `relay_info` lists the groups
  there. An unknown group is refused with the groups listed, and nothing is
  created. The approval card names the group, and the result carries it. A
  relay too old for this is refused before anything is created. Renaming
  and rearranging groups stays the owner's. An argument an action does not
  take is no longer dropped without a word: create_session refuses it, and
  the other actions list it in `ignored`.
- **Agents can change another session's settings.** `remote_relay`
  update_session sets a session's model, reasoning effort, relay mode,
  auto-compact window, title, group, thinking or working folder for its next
  turns, on this relay or a paired one, and answers the settings and what
  changed. An unknown model is refused with the close matches; a group name
  that does not exist makes a new group, and `none` takes the session out (a
  group you made is never removed this way: it stays, shown empty in the
  sidebar for you to rename or dissolve); a new folder
  restarts the session's CLI there and is refused while it works. Only a
  session the agent's own conversation started may be changed freely; any
  other, yours or another agent's, needs your Allow once per conversation and
  relay. A failed change still lists what it wrote. An open page shows the
  change at once.
- **"Agents in this conversation may start sessions."** A switch in the 🧠
  settings of a session an agent started lets its agents start sessions
  themselves, past the one-level rule; the limit of four at work and your
  first Allow still apply. Only you can set it: agents and paired relays are
  refused.
- **Fewer wake-ups for watching agents.** A `remote_relay` subscription takes
  `digest_minutes` (its events come as one message every N minutes),
  `quiet_hours` (a daily window in a time zone, crossing midnight and the
  clock changes included, whose events come as one message when it ends) and
  `exclude` (sessions a subscription to all leaves out). Questions and errors
  still come at once, held events survive a restart, and the hourly limit
  stays on top. A turn the watcher prompted itself and whose finished reply it
  waited for no longer wakes it a second time with turn_end.
  `list_subscriptions` shows how many events each subscription holds and when
  they are due, and the watching chip shows a small digest or quiet-hours hint.
- **Scheduled prompts that survive a restart.** `remote_relay` schedule
  queues a prompt at a time (`at`) or every N minutes (`every_minutes`, 5 to
  10080), into the agent's own conversation or, with the user's approval in
  ask and plan mode, another session on this relay. The relay keeps it in its
  database, so a nightly review or a sweep no longer has to be set up again
  after a deploy. A time missed while the relay was down fires once when it is
  back, and a repeat waits while the one before is still queued. The message
  says it is automated and approves nothing. `list_schedules` and `unschedule`
  manage them, archiving a session ends its schedules, and a conversation
  holds at most 20. The sidebar marks a scheduled session, and its header
  lists the schedules with a Cancel button each.
- **Questions that don't block, and the Inbox.** `remote_relay` ask puts a
  question for you into the agent's session (up to eight options, a
  recommendation, a folded detail, and urgency `now` for a push) and returns
  at once, so the agent goes on with what does not depend on it. Your answer
  or dismissal reaches it as a relay event, question_answered, without a
  subscription, and does not count as your message for the agents' loop
  guard. A lead can ask on behalf of another session, on this relay or a
  paired one (kept where that session lives); `list_questions` and `withdraw`
  manage them, and `read_session` lists them with their kind. The new
  **Inbox** in the sidebar, with its count, lists every pending question
  across this relay and the paired relays that answer, grouped by relay,
  with a filter for your sessions or the agents', and what you answered in
  the last day under a divider. An ask is answered there or in its session,
  also one on a paired relay; a paired relay's cards open there, and the
  pairing never carries an answer to a card. Agent text shows as plain text,
  and on a phone the Inbox is one column with full-width buttons.

### Fixed

- **The agents' loop guard no longer counts the relay's own messages as the
  user's.** A relay event, the usage-limit follow-up or the note after a
  restart reset the count of prompts an agent may send to other agents
  before the user is asked, so two agents that watched each other could go
  on without the user. Only a message the user wrote resets it now.

## [0.9.15] — 2026-10-22

Highlights: `oar setup` asks how the relay starts on Windows, at sign-in or
at boot before anyone signs in, and `oar autostart` switches between them
later. `oar uninstall` takes OAR off the machine and keeps your data unless
you ask for `--purge`. A declined administrator prompt changes nothing, boot
tasks work in profile folders with spaces or non-ASCII names, and a
hand-made task, or one only an administrator can read, is never deleted
without a copy. The package is smaller.

### Upgrade notes

- **From 0.9.14 with a hand-made autostart task:** switching to boot mode
  in 0.9.14 could delete an existing task of the same name (`oar-relay` or
  the pre-rebrand name) without a copy. 0.9.15 keeps such a task and asks
  for `--replace`, which saves a copy first.

### Added

- **`oar setup` asks how the relay starts** on Windows: at sign-in, at boot
  (before anyone signs in; one administrator prompt) or not at all, with the
  current mode as the default. `--autostart signin|boot|off` answers it
  without a question.
- **`oar autostart signin|boot|off|status`.** Switching to or from boot mode
  asks for an administrator once, only for the schtasks call; sign-in mode
  needs none. A declined prompt changes nothing. `status` lists each entry,
  whether OAR generated it, and which other OAR install a stale one starts.
  `--replace` replaces hand-made entries with generated ones, keeping a copy
  of each (`<name>.bak`: a launcher's beside it, a task's exported XML and a
  Startup entry's in the OAR folder); nothing else ever does. On Linux it is
  the systemd user service (`oar service`); elsewhere it says it is not
  supported.
- **`oar uninstall [--purge]`** stops the relay, takes out the autostart
  entries OAR generated and the Copilot extension wrapper OAR wrote for this
  install (a changed one, or one of another install, stays and is named),
  and has npm remove the package once `oar` has ended. **The data stays** (config,
  database, uploads, logs in the state root), and the command says where.
  `--purge` deletes the state root after you type `delete`; a data folder
  outside it, one in a git checkout, and `rescued-package-data` are each
  deleted only after a question of their own; a drive root, the home folder
  or a folder above it, a system folder and a folder without OAR's config or
  database in it are refused, whatever is typed. A relay that holds its lock
  without answering (bringing over a database) stops the command before it
  deletes anything. Programs that still run from the package (session
  workers) are named: npm leaves the files they hold in a hidden `.oar-*`
  folder, which the log names. A step
  that fails stops the command, which says what is done and what is left.

### Changed

- The two developer READMEs (`server/README.md` with the HTTP API, and the
  web-relay extension's) are no longer in the npm package; they are on
  [GitHub](https://github.com/oar-sh/oar).

### Fixed

- **A hand-made autostart entry is never deleted or written over.** Leaving
  boot mode deleted a boot task or launcher edited by hand, and *Off* also
  a task OAR did not make; switching to sign-in replaced an edited Startup
  entry. Only entries exactly as an OAR version generated them are changed
  now, in the web UI as in `oar autostart`: a hand-made entry that would stay
  beside the new mode, or be replaced by it, stops the switch with the same
  message. Picking *At system startup* no longer replaces a hand-edited
  launcher or a task someone else made, the pre-rebrand one included. A boot
  task is OAR's own only while it is exactly the one OAR registers: the
  account, the run level, one action, one boot trigger and its settings.
- **Boot mode switches on and off with a space, an apostrophe or non-ASCII
  characters in the profile path.** The elevated step was a script file
  passed unquoted, and read in the wrong encoding; it now travels as an
  encoded command with every path quoted. A failed switch is no longer
  reported as a declined prompt: it puts back the tasks it deleted from their
  copies and keeps the copies, and a declined one leaves no file behind.
  Windows PowerShell can report a declined prompt with an error that does not
  stop it, or with no process at all; that read as confirmed, so leaving boot
  mode deleted the launcher but kept the task, and switching it on removed
  the sign-in entry without registering the task. Both now count as declined.
  Leaving boot mode in the web UI switches the sign-in entry only after the
  prompt was confirmed. Leaving boot mode removes the task XML and the record of
  the sweep too, and the web UI names a Startup entry OAR did not make.
- **A boot task with non-ASCII characters in its paths reads right.**
  schtasks writes its XML in the console's code page, so a profile path like
  `C:\Users\Jürgen` came back garbled and OAR took its own task for a
  hand-made one: switching boot on reported a failure and deleted the new
  launcher, and *Off* refused to remove the task. Such a task is now read
  again through PowerShell in UTF-8. A task that still cannot be read is
  never taken as missing, removed, put back or someone else's, and never
  deleted without a copy: nothing is changed while it cannot be read, and
  after a confirmed prompt OAR says it could not check, and undoes nothing.
- **A boot task only an administrator can read is never deleted.** One that
  stores another account's password answers "Access is denied" to OAR; that
  read as no task, so `oar autostart boot` deleted it without a copy. Any
  failed read of a task that is there now counts as unreadable: status says
  so, and OAR changes nothing, `--replace` included.

## [0.9.14] — 2026-10-21

Highlights: a relay started by Windows autostart, or by a plain
`gh copilot`, now runs on the same data folder as `oar start`, outside the
installed package, so an update no longer takes its conversations with it;
the update copies that data before npm removes the old version, and the
relay takes it over at its next start (read the upgrade note first). A
relay never moves to another database unannounced, a lock an unclean end
left no longer blocks the next start, autostart waits for a late drive, and
Cursor conversations keep their agent across updates. An agent can ask to
be woken when the relay runs again (`relay_restarted`), its preview tool
reaches only its own conversation's previews, approval cards take a choice
instead of typed text, and a conversation whose folder is gone says so.

### Upgrade notes

- **Windows autostart users, and anyone whose relay a plain `gh copilot`
  started: save your data before this update.** Before 0.9.14, those relays
  ran on the database and uploads inside the installed package, and
  `npm i -g` deletes them with the old version. This update copies them
  first, while it installs, to `rescued-package-data/<time>/` in the OAR
  data folder (`%APPDATA%\oar\data`, `~/.oar/data`), and the relay takes
  the copy over at its next start. npm 10 and 11 run that copy (11.21 with
  a warning), and if it fails they put the old version back with its data.
  npm 12 skips it unless `@oar-sh/oar` is allowed: the install then
  succeeds and the old data is gone. With `strict-allow-scripts`, npm 11
  and 12 refuse the install and change nothing; `--ignore-scripts` skips
  the copy everywhere, and the native modules (better-sqlite3, koffi) too,
  so the relay cannot start afterwards: never update OAR with
  `--ignore-scripts`. On npm 12, update with the relay stopped:
  `npm i -g @oar-sh/oar --allow-scripts=@oar-sh/oar,better-sqlite3,koffi`.
  An `allow-scripts` entry in `.npmrc` does not help the **Update** button
  or `oar update` of 0.9.13: their own `--allow-scripts` replaces it. To be
  safe, do this update by hand:
  1. Close every `gh copilot` session (its extension starts the relay
     again), then `oar stop`, and wait until `oar status` says it is not
     running. If 0.9.13's `oar stop` does not stop a relay the
     system-startup launcher started, end the relay before installing:
     end the relay's processes in Task Manager (node.exe running OAR).
  2. Run `npm root -g` and open `<that folder>/@oar-sh/oar/server`
     (usually `%APPDATA%\npm\node_modules\@oar-sh\oar\server`). Copy
     **both** the `data` and the `uploads` folders, with every file in
     them, to a safe place.
  3. `npm i -g @oar-sh/oar --allow-scripts=@oar-sh/oar,better-sqlite3,koffi`.
  4. Before starting the relay, copy both folders back into the same
     `server` folder.
  5. `oar start`. In `relay-console.log` in the OAR `logs` folder, look
     for `[data] Brought over the database …`: your conversations should
     be back. A database that was already in the OAR data folder is kept
     in `backups/<time>/` beside it. The folder brought over keeps its
     database as `copilot.db.adopted-<time>`; the others looked at keep
     theirs as `copilot.db.older-<time>` (if a rescue copy was used, the
     package's own database ends up `.older-`).
  6. If the relay stops with `[data] Not starting: each database has
     changes newer than the other's`, both hold work; the error line names
     both folders, and one may be under
     `rescued-package-data/<time>/data`. Pick the one to keep, and move
     the other one's `copilot.db`, `copilot.db-wal` and `copilot.db-shm`
     into a folder of your own (do not delete them). Then `oar start`
     again.
  7. If you already updated without saving and nothing was copied, the
     database from the package can't be recovered by OAR.
- The npm package guard allows 8 MiB unpacked and 530 entries (was 7.75
  MiB and 520); the take-over of earlier databases, the install-time copy
  and the lock checks grew the package to about 7.75 MiB and 521 entries.

### Added

- **Woken when the relay runs again** (`relay_restarted`, opt-in per
  conversation). A conversation that queued a relay restart, or watched
  sessions, slept after the restart until someone wrote, because the timers
  its CLI had scheduled might not survive it. An agent can now subscribe to
  `relay_restarted` through `remote_relay` (no sessions needed, no approval
  card, this relay only); after each start it gets one relay event with when
  the relay came back and how it stopped (who requested the restart, when
  and why, or that it did not record how it stopped). Never part of the
  default events. A restart requested while a turn it woke still runs does
  not wake that conversation (a one-shot subscription held back so ends),
  archived and deleted conversations are not woken, and each start is told
  once.

### Changed

- **Help texts and CLI messages say what the relay does.** **Accept prompts
  from other relays' agents** lists everything it refuses when off, watching
  sessions included; **Show Suspend host action** says it is a switch of this
  browser and suspend works on Windows relays only; the Cursor session token
  names the real usage bars; the Copilot engine applies whenever a
  conversation's worker starts. `oar` points to **Settings → General →
  Autostart (Windows)**, says the service starts again at boot with
  lingering, sends a failed start or restart through the service to
  `journalctl --user -u oar` instead of `server-err.log`, words a busy port
  one way, and says an already running relay keeps its version and settings
  until `oar restart`.
- **Agent guidance.** The ask_user example in the Copilot engines' relay
  guidance is a real question instead of quiz placeholders, the guidance and
  the Cursor and Copilot ask_user descriptions give the choice counts the
  relay accepts (it keeps the first 8), the remote relay section names every
  subscription action and `usage`, and the restart note says a queued
  restart waits for every running turn and holds new messages.
- **Docs brought in line with the code**: the paired-relay switch, watch
  approvals per relay, no push for agent-started turns, provider rebinding of
  unstarted chats, OpenAI key removal, `oar stop`/`oar restart` under the
  systemd service (they end running turns and workers), Claude turns on the
  bundled Claude Code, the config keys and environment variables, which
  variables reach workers started under tmux, the worker supervisor and the
  database migrations.
- **Uploads follow the config's `dataDir`.** It moved the database but
  left uploads in `server/uploads` unless `COPILOT_WEB_RELAY_DATA_DIR` was
  set; they now go to `<dataDir>/uploads` as well. Uploads earlier versions
  left in `server/uploads`, or that a state migration copied into the state
  root's `uploads/`, are still read from there in place: every attachment
  opens after the update, and nothing in those folders is moved or deleted.
- `@anthropic-ai/claude-agent-sdk` 0.3.304 (was 0.3.292; bundled Claude Code
  2.1.304). Claude now forks sub-agents that inherit the conversation
  (`subagent_type: "fork"`) by default. With thinking turned off, a model that
  cannot run without thinking now uses adaptive thinking. A full disk during a
  login refresh is reported as a failed request instead of a sign-in error.
  Fixes include an approved tool call that could run twice on resume, model
  switches and interrupts waiting behind a context-usage check, and
  `hook_progress` messages piling up in memory.
- `@cursor/sdk` 1.0.38 (was 1.0.36), which uses the relay's own `zod`
  instead of carrying a copy of zod 3.
- `@github/copilot-sdk` 1.0.23 (was 1.0.17; bundled Copilot CLI 1.0.100).
  The Copilot workers and the web-relay extension are unchanged: under the
  Copilot CLI both run the SDK that comes with the installed CLI. **About
  OAR** shows the new version.

### Fixed

- **Windows autostart starts the relay on the same data as `oar start`.**
  The sign-in entry and the system-startup launcher set only the config
  file, so a relay they started kept its database, uploads and log inside
  the installed package: other conversations and settings than with
  `oar start`, and gone with the next update. Both now also set the data
  and log folders `oar start` uses (`%APPDATA%\oar\data` and
  `%APPDATA%\oar\logs` on a global install). Once it runs, the relay brings
  an entry or launcher that an earlier version (0.9.0 on) generated for
  this install and its config up to date, the pre-rebrand
  `copilot-remote-web-relay.cmd` under its own name, so the first start
  after the update (by `oar start`, `oar update` or the autostart itself)
  repairs it. An entry edited by hand, one that points at another config
  file, and one of a relay started on data folders of its own are left
  alone: switch autostart off and on in Settings to replace those. Rewriting
  a launcher while the relay it started still runs is safe: its cmd window
  ends with the relay instead of starting it a second time. The launchers
  now switch cmd to UTF-8, so a user folder with non-ASCII characters in its
  name no longer garbles the paths in them. Conversations a relay started
  by autostart kept inside the package folder are not carried over.
- **A relay never moves to another database unannounced.** Before its own
  database opens, a global install's relay pinned to the OAR data folder
  looks once at each database it may have run on until now (the one inside
  the package, the install-time copies of it): a newer one is copied in
  after a `quick_check` of the copy, with the uploads and Cursor agent
  stores (never over a file there), the older one in place is kept in
  `backups/<time>/`, and one line says so. Each one looked at is renamed
  in place to `copilot.db.adopted-<time>` or `copilot.db.older-<time>`,
  never deleted. When neither is clearly newer, or a relay may still run
  on the other, the relay stops with one line naming both and exit code
  78, which its supervisors, and a systemd unit written by this version's
  `oar service install`, do not retry. The swap survives a kill. A global
  install always runs on the state root's data folder, however it is
  started; a `dataDir` in config.json that an earlier autostart relay used
  is copied from once and left exactly as it is. A git checkout and a
  relay on another state root are left alone. A first start that finds an earlier launcher
  but no database of it says the update may have removed it. A large
  database takes minutes: the relay logs its size at the start, progress
  at least once a minute and each step's time at the end, and `oar start`
  meanwhile says it is still bringing it over. A second `oar start` then
  no longer removes the relay's lock and starts a second relay.
- **A relay lock an unclean end left no longer blocks the next start.** A
  lock from before the last boot, or whose pid now belongs to a process
  started after it, is stale; a lock still held stops the start with one
  line naming it and exit code 78. A lock is never seen half written, and
  one that cannot be read is read again and held unless its time is more
  than 10 s off (an empty one a power loss left). Two starts recovering
  the same stale lock no longer both run.
- **A `TEMP` or `TMP` set with quotes, or naming a missing folder, is fixed
  for the relay** on Windows (quotes off, else the user's temp folder).
- **The relay a plain `gh copilot` starts keeps its data outside the
  package.** After `oar copilot` installed the Copilot CLI extension for
  every folder, a plain `gh copilot` ran its relay on `server/data` and
  `server/config.json` inside the installed package, on every platform. A
  global install's extension now starts it on the OAR state root's
  config, data and logs, as `oar start` does, and copies a config the
  package held there once. The database from the package is saved only
  where the install-time copy ran (see the upgrade note).
- **Windows autostart waits for a late drive.** Both the sign-in entry and
  the system-startup launcher now wait up to 5 minutes in all for the
  `%TEMP%` folder and the drive of the data folder (a RAM disk or a drive a
  startup script mounts later), where the system-startup launcher waited
  2 minutes for `%TEMP%` only and the sign-in entry not at all. A wait that
  runs out writes one line naming what is still missing, then starts the
  relay anyway: `oar-web-relay-launcher.log` beside the system-startup
  launcher, `oar-web-relay.log` in the OAR folder (`%APPDATA%\oar`) for the
  sign-in entry. A data folder on a network share is not waited for. The
  first start after the update brings a generated entry or launcher up to
  date without an admin confirmation; edited ones stay as they are. A relay
  whose data or upload folder cannot be created (its drive still missing, a
  file in its place) now stops with one line naming the folder and exit
  code 78 instead of an error trace. A `TEMP`
  set with quotes (`"D:\My Temp"`) no longer stops the system-startup
  launcher before it starts the relay.
- **Cursor conversations keep their agent across updates.** The Cursor
  worker kept its per-conversation agent stores in
  `server/data/cursor-agents` inside the installed package, so an update
  deleted them and a Cursor conversation went on with a new agent that knew
  nothing of the earlier turns. The stores now live in `cursor-agents` in
  the relay's data folder (`~/.oar/data` on a global install, unchanged
  `server/data` in a checkout); `CURSOR_AGENT_STORE_DIR` still overrides
  it. The copies the state migration made there are read again, as they
  were at the migration. A conversation with a store in both places uses
  the one changed last, and one with a store only at the old place keeps
  using it there; nothing is moved or deleted.
- **`oar setup --migrate-from` works after the installer has run.** The
  state migration wrote its marker even when it found nothing to bring
  over, and every later `--migrate-from <dir>` then did nothing without a
  word. The marker is now written only after a real copy, and a folder
  named with `--migrate-from` is always migrated, with a line saying what
  was copied or why nothing was. It still never overwrites: a `config.json`
  already in the state root is kept; the empty database the installer's
  relay created is moved into `backups/` (the relay has to be stopped
  first, `oar stop`), and a database with conversations makes it refuse
  with what to do instead.
  The same folder again only says it is already migrated, and a refused
  `oar copilot --migrate-from` warns and starts the session anyway. While
  the migration copies a database it holds the relay's lock, so no relay
  starts on it in between. Migrated uploads now land in `data/uploads`,
  where a global install's relay reads them (they went to an `uploads`
  folder nothing read); uploads an earlier migration left in
  `~/.oar/uploads` are merged into `data/uploads` at the next relay start
  (nothing is overwritten or deleted). A migration that fails its check
  removes only what it copied, no longer the whole state folder, and never
  a link or file that was there before; a database it moved aside and
  cannot put back stays in `backups/`. Symlinked folders in the checkout
  are copied as real folders.
- **An agent's preview tool reaches only its own conversation's previews.**
  The tool's `list` showed every live preview of the relay with its token,
  the only credential of a preview, and `close` closed any of them. `list`
  now shows the calling conversation's previews, and `close` of another
  conversation's preview is refused with a message saying so. Copilot and
  Grok sessions that use the HTTP API are taught the calls with their
  conversation written in. The relay enforces this for every preview
  request that names a conversation (the `conversationId` query or the
  worker's conversation header); the web UI's Settings › Previews and
  `/preview list` still show and close every preview. Agents present the
  same relay token as the owner, so an agent that calls the API by hand
  without naming a conversation still sees them all; see
  docs/preview-servers.md.
- **A conversation whose working directory is gone says so.** A Claude turn
  in a folder that had been deleted or renamed failed with "native binary …
  exists but failed to launch", blaming the system's libc; a Grok turn said
  the Grok CLI was not installed, a Copilot (SDK engine) turn showed the
  spawn's `ENOENT`. Claude, Copilot, Cursor and Grok workers now check the
  folder before they start their CLI or agent in it, and the turn fails with
  the folder's path and what to do: restore it, or choose another one with
  **⋯ → Change CWD**, then send the message again. A worker that cannot be
  started at all for that reason (a launch without tmux, as on Windows)
  names the folder in its launch note instead of a missing `node` or
  `cmd.exe`.
- **Answering a question no longer gets a long turn requeued by Max turn
  duration.** Answering one of a turn's questions restarts the max turn
  duration's clock, but only when the relay picked the turns to requeue; the
  requeue itself still counted from the turn's start and took every turn
  past it whenever another turn was due. Such a turn was sent again
  mid-answer, without the retry limit and without the page hearing of it.
  The relay now requeues exactly the turns it picked, by the same rule. The
  settings help and the docs now say where the limit counts from, and that
  Grok also enforces it itself, counted from the start of the prompt and
  without pausing for a remote relay approval card.
- **A resumed sub-agent shows its record and is listed while it runs.** When
  a Claude session sent a finished background agent a new message, the task
  panel showed it running, but opening it said "This agent's record could
  not be loaded", and the session's task list (`read_session`,
  `list_sessions`) did not list it at all: the relay took it for the
  agent's first run, which had ended. The resumed agent now
  opens its own record, which goes on where the first run ended (a note
  marks where it resumed) and reads running again until it ends; it is a
  running task of the session again, with a start and an end of its own.
- **Approval cards take a choice, not typed text, and every one reads it the
  same way.** OAR's own Allow / Deny cards (agent sessions, watching
  sessions, write actions in the ask and plan modes) and Claude Cloud's
  permission cards showed a reply box, and whatever was typed there ("yes",
  "ok", "allow it") counted as a denial; Claude Cloud even denied a typed
  "allow". These cards have no reply box now, the relay refuses typed text
  for any card that does not take it (the card stays open), and a choice
  counts however it is typed. A card created through the API with choices
  and no `allowFreeform` is such a card too (the flag defaults to `false`
  when choices are given). Copilot's permission card keeps its reply box,
  and now says that a typed reply refuses and tells Copilot why. A card's
  header (**Agent sessions**, **Relay events**, **Remote relay**, Claude's
  question headers) is shown above the question, and a question's push
  notification names the provider that asks instead of always saying
  "Copilot needs your input".
- **Reasoning effort reaches every Cursor model that takes one.** Cursor
  models whose effort parameter is named `reasoning_effort` (Claude Haiku 5.5,
  Claude Sonnet 5.5, Grok 4.7, Gemini 4 Argon, Gemini 3.8 Flash, Muse Wonder
  1.0) offered only None in the composer and could not be sent an effort.
  They now list their tiers once the Cursor models are discovered again
  (**Refresh** in **Select Models**, or saving the Cursor key), and the
  chosen effort is sent with the turn.

## [0.9.13] — 2026-10-20

Highlights: a queued relay restart now shows every page what it waits for,
which messages it holds and which background tasks keep running, and after a
time you set it asks instead of waiting forever; it never restarts by itself
while turns run, and can tell the sessions a restart cut off to carry on
(opt-in). The task console survives a relay restart, marks where a `| tail`
holds output back, keeps the task's row in view, and counts a test run over
its whole log. A message
sent without a model keeps the conversation's model, effort and mode,
`remote_relay` refuses a model the provider does not list, watching agents
are no longer woken by every finished shell command, workers reconnect after
a hung restart, and the unit suite passes on Windows again.

### Upgrade notes

- Migration 0018 (the table `background_task_rows`) runs by itself on the
  first start. Rows the runtime before this update held in memory are lost
  one last time at the restart into it. The Node.js requirement is unchanged
  (22.13 or newer).
- New settings in **Settings › General**: **Ask after a queued relay restart
  has waited** (30 minutes by default) and **After a relay restart, tell
  interrupted sessions to carry on** (off by default); a new push event,
  **Queued relay restart waited its maximum time** (Settings › Notifications),
  is on by default.
- **Watching agents:** a subscription that named no events now leaves out
  successful shell commands (sub-agent ends and failures still wake it); one
  that named its events keeps them. Name `task_finished` to get every task end.
- **Scripts that post `/api/message` without a model** to an existing
  conversation now get the conversation's own model, effort and mode, or a
  `409 CONVERSATION_MODEL_UNAVAILABLE` when that model left the catalog,
  instead of the relay's first model.
- The npm package guard allows 7.75 MiB unpacked and 520 entries (was 7.5
  MiB and 510); the restart queue, the stored task rows and the restart note
  grew the server and the app to about 7.57 MiB and 514 entries.

### Added

- **After a relay restart, tell interrupted sessions to carry on** (Settings
  › General, off by default, a setting of the relay). A turn a restart cut
  off for good (the agent had already taken the message, so it cannot run
  again) used to stop without the agent ever hearing of it. With the setting
  on, its session gets one compact **relay** note once the relay is back:
  when the relay restarted, and to check what it was doing and carry on.
  The user's message is never sent again, and nothing goes to idle sessions,
  to turns the relay requeues (they run again by themselves), to archived
  conversations or while a usage-limit pause holds the session; a message
  queued meanwhile goes first.
- **The task console marks where a `| tail` holds the output back.** A
  command piped into `tail` (`npm test 2>&1 | tail -40`) writes nothing
  until it ends, so its console looked stuck. When the task's command line
  shows such a pipe (`tail -f` and `head` pass lines on and are left
  alone), the console marks where the output stops, with a hint to log to a
  file instead, until more output comes or the task ends; the row's quiet
  hint says the same on hover. The Claude, Copilot and Grok workers now report a
  shell task's command line on its row (scrubbed of credentials by the relay
  and cut at 2,000 characters; `read_session` passes it on).
- **Load earlier in the console's header.** It sat only above the first
  line, out of reach while following the end of a long log; the header now
  has it too whenever there is earlier output.

- **A queued relay restart shows what it waits for, and asks instead of
  waiting forever.** **🌄 Restart web relay** first lists the sessions with
  a running turn it would wait for, the messages it would hold, and the
  sessions with background tasks (commands, monitors, sub-agents), each with
  whether it keeps running; **Restart when idle** queues the restart,
  **Restart now** does not wait. While a restart is queued every page shows a
  banner: *Relay restart queued — waiting for N sessions*, the sessions as
  links, how long it has waited, that new messages are held until then, the
  background tasks, and **Restart now** (it asks first and names the
  sessions it does not wait for) and **Cancel restart**. A new setting,
  **Ask after a queued relay restart has waited** (Settings › General,
  30 minutes by default, 5 minutes to 4 hours or no limit), bounds the wait:
  after it the banner asks *restart now, keep waiting or cancel?* and a push
  notification (a new event, on by default) asks the same. It never
  restarts while the turns run, and still restarts as soon as they end;
  **Keep waiting** waits another period.
  `POST /api/relay/shutdown` answers with the same picture (`waitingFor`,
  `held`, `backgroundTasks` with `survives`), so an agent that requests a
  restart can tell the user, and takes `immediate: true`; `GET
  /api/relay/shutdown` and `POST /api/relay/shutdown/keep-waiting` are new.

### Changed

- **The open console leaves the task's row in view.** On a screen wider
  than a phone the console now ends above the task's row in the composer
  panel, so the row's ✓/✘ chip, clock and **Stop** stay visible and live
  beside it; on a phone it stays full-screen, with the same chip in its
  header.
- **One wording for a task without output.** The row's hover title said
  "output not available for this provider" and the console "Output is not
  available for this provider."; both now use the sentence, from the same
  text the relay answers with.
- **A watching agent is no longer woken for every shell command that ended
  well.** A relay event subscription that names no `events` now gets
  `subagent_finished` instead of `task_finished`: a background sub-agent that
  finished still wakes the watcher, and so does any task that failed or
  exited non-zero (`task_failed`), but a shell command that ended well does
  not. A busy worker ends one every minute or so, and each was a turn of the
  watcher on its whole context (35 during one 20-minute wait were seen).
  Name `task_finished` in `events` to get every task end, as before.
  Existing subscriptions that named no events switch to the new default by
  themselves; those that named their events keep them. A paired relay on an
  older OAR applies its own default and refuses `subagent_finished` by name.

### Fixed

- **A message sent without a model keeps the conversation's model and
  effort.** A message to an existing Copilot conversation that named no model
  (sent by a script or an agent, not by the composer) ran on the first model
  of the catalog with effort `none`, which some models refuse, and then saved
  that model and effort as the conversation's own, so the composer showed
  them from then on. Such a message now runs on the conversation's model and
  effort, and saves nothing it did not name. If the conversation's model has
  left the catalog, the send is refused with a message saying so instead of
  switching to another model. OpenAI, Claude, Cursor and Grok conversations
  keep their effort the same way, and so does the follow-up of a plan
  board's action. A message that names no mode runs in the conversation's
  mode instead of agent mode, and no longer switches a plan-mode
  conversation to agent. A message whose turn failed can be sent again at
  once: it no longer counts as a duplicate of itself.
- **Test counts of a long run no longer drop out.** The relay counted a
  test run from the last 64 KB of its output only, so a live run that wrote
  more lost its runner's first line: the chip disappeared or undercounted.
  A run still going is now counted over its whole log, read on as it grows
  (and read again in bounded steps after a relay restart); the closing
  summary still decides once it is there.
- **Credentials in background-task rows are blanked.** A task's
  description, summary and last tool call went to the pages, `read_session`
  and the stored console rows as the worker sent them, and a Grok terminal's
  (or a Copilot shell's) description is its command line. The relay now
  scrubs them like the console output. The scrubber also catches `curl -u
  name:password` (also attached), `--password`/`--token`/`--secret`
  values, `mysql -p…`, `docker login -p`, `sshpass -p`, `redis-cli -a`,
  `aws_secret_access_key` in a file or `aws configure set`, Slack webhook
  paths, `?key=`/`&sig=`/`&token=` query values and an `Authorization`
  header in any case and with any scheme word, and leaves a reference such
  as `$(cat ~/.token)` or `$API_TOKEN` alone.
- **Inverse text in the task console.** vitest's RUN, PASS and FAIL badges
  (SGR 7) showed as plain words; inverse text now swaps its colours, so
  they show filled.
- **The task console survives a relay restart.** Its rows lived in the
  relay's memory: after a restart a running command came back to the panel
  once its worker reported, but the dimmed rows of commands that had
  finished within the hour were gone, and a console open at the time said
  the output was no longer available. The rows are now kept in the database
  (migration 0018): finished rows come back with their ✓ or ✘ for the rest
  of their hour, and a running command's console reads its output at once.
  Such a command is shown as running again only when its worker says so
  (until then its console says it waits for the worker, without **Stop**);
  one whose worker did not survive the restart is marked finished with an
  unknown result (a neutral ? instead of ✓ or ✘) after about a minute and a
  half, its output still readable.
- **A scroll right after older messages load is no longer undone.** The page
  put the reader back where they were when the older page arrived, a frame
  later: scrolling in that frame (up again after the jump-to-bottom button,
  for instance) was reverted, and could leave you at the end, following new
  rows you meant to read later.
- **The jump-to-bottom badge counts a message that arrives just as you scroll
  up.** One that landed before the page had noted the scroll, a frame and on
  a busy page up to a tenth of a second, was taken as already seen.
- **The background-task panel no longer shrinks back for a moment.** While
  the agent works the page re-reads the conversation, and an answer read
  before the panel grew put the smaller set of tasks back until the next
  read. The panel jumped, and a reader following the newest messages could be
  left above them.
- **A worker reconnects after a relay restart even when its connect hangs.**
  A worker's link to the relay was retried only once the socket reported an
  error or a close. A connect that never finished, a socket the relay
  accepted but never greeted, or a close that never completed while the
  relay hung in its shutdown left the worker without a connection and
  without a next attempt, and messages to its session stayed queued; the
  relay's replacement of such a worker does not step in while the session
  is working. The link now gives up a socket that has not completed its
  handshake within 15 seconds, does not wait for a close after the relay
  says it is shutting down, and retries a socket that fails to construct.
  There is always exactly one next attempt, with the usual backoff of up to
  8 seconds, which now starts over only after a completed handshake. Failed
  attempts log one line each in the worker log, at most one a minute after
  the first three.
- **`remote_relay` refuses a model the provider does not list.**
  `create_session` passed any model id on, so a Copilot id written with a
  hyphen ("gpt-5-4" for "gpt-5.4") created a session on another model, which
  every later `send` with that id failed to reach. A model in
  `create_session` or `send` is now checked against the list New Chat offers
  for the provider on the target relay, before any approval card: an unknown
  one is refused with the close matches and the valid ids ("Unknown model
  'gpt-5-4' for Copilot on relay …; did you mean 'gpt-5.4'?"), and nothing is created
  or sent. Claude also takes `opus`, `sonnet` and `haiku` (the newest listed
  model of that name), and a Copilot id with its effort (`gpt-5.4-high`) runs
  as that model with that effort. When the relay has no list for the provider yet, the
  model goes on unchecked and the answer says so. A relay also refuses an
  unknown Copilot model when another relay's agent starts a session on it.
- **A preview's websocket is dropped when the browser hangs up before the dev
  server answers.** A browser that closed its connection with a plain FIN
  while the upgrade was still waiting on the dev server left both sides
  open, even when a reset followed: the request
  to the dev server stayed pending, and the preview lane could not stop
  until the dev server answered. Both sides are now dropped, and stopping
  the lane also drops upgrades still waiting on the dev server.
- **The unit test suite passes and finishes on Windows again.** A preview
  test waited forever for a websocket hang-up (fixed in the product, above),
  and several tests expected bare program names or Unix paths where Windows
  resolves the full path of its own tools; two unlocks recorded in the same
  millisecond are now listed in the order they were recorded.

## [0.9.12] — 2026-10-18

Highlights: a background command or monitor in the task panel opens a
console with its output, live, in colour and with secrets blanked, paging
back through the whole log and with its own **Stop**. Task rows say when a
task has gone quiet ("no output for 12 min", amber after a time you set in
Settings) and show a **✓ 412 ✘ 2** chip when the output is a test run;
sub-agent rows count their steps, and agents read a task's output on a
paired relay with `remote_relay` `read_task_output`. Long conversations open
and page back fast, and switching away and back, or reloading, returns to
the message you were reading, older pages included; new messages wait below
a place far up the history instead of skipping the ones in between.

### Upgrade notes

- Migration 0017 (two indexes for the history of long conversations) runs by
  itself on the first start. The Node.js requirement is unchanged (22.13 or
  newer), and no setting or config key needs changing.
- Two new settings in **Settings › General**: **Mark a background task as
  quiet after** (10 minutes by default, for the relay) and **Messages kept
  per conversation for switching back** (200 by default, per browser).
- **Claude background commands may run for 2 hours** before Claude Code
  stops them, and up to 8 hours when Claude asks; **Settings › Providers ›
  Claude** sets the limit. OAR now sets `BASH_DEFAULT_TIMEOUT_MS`, `BASH_MAX_TIMEOUT_MS` and
  `CLAUDE_CODE_AUTO_BACKGROUND_TIMEOUT_MS` for the Claude sessions it starts,
  so values of these in the relay's environment no longer apply. Commands
  that start with `sleep`, and sub-agents' commands, are not moved to the
  background after 2 minutes and may block a turn up to the limit.

### Added

- **Click a background command to watch its output.** A backgrounded shell
  command or monitor in the composer's task panel opens a console: the last
  2,000 lines, following live, with **Load earlier** paging back through the
  whole log (about 5 MB at most). Progress-bar redraws collapse to their last
  state, repeated lines fold into one with ×N, colours are kept and other
  control codes dropped, secrets are blanked, and the console has its own
  **Stop**. A finished command keeps a dimmed ✓/✘ row for an hour so a failed
  run can still be read. Claude reads the file its CLI writes for the command,
  Copilot a detached shell's log or an attached shell's recent output, and
  Grok now lists its terminals as tasks. A session started before this
  release names no output file; its console says so and that a restart of
  the session helps.
- **Task rows say when a task has gone quiet** ("no output for 12 min", amber
  after **Settings › General › Mark a background task as quiet after**, 10
  minutes by default) and show a **✓ 412 ✘ 2** chip when the output is a
  vitest, Playwright, node:test, jest, pytest or turbo run (also through
  pnpm); the console header's chip jumps to the first failure, and a run
  that ends shows its final counts within a moment.
- **Sub-agent rows in the task panel show their step count** ("22 steps"), as
  the rows in the reply already did.
- **Settings › Providers › Claude › Claude background command limit** (30
  minutes to 8 hours, 2 hours by default) sets how long a command Claude runs
  in the background may take before Claude Code stops it, when Claude gives it
  no timeout of its own; Claude may still ask for up to 8 hours for one
  command. A foreground command moves to the background after 2 minutes, so
  the turn goes on while it runs. Claude is also told to write the output of
  long runs to a log file instead of piping it into `tail`, and to start runs
  that must survive a session restart with `nohup`. A change applies to Claude
  sessions started afterwards; Claude Cloud is not affected.
- **`remote_relay` action `read_task_output`** gives agents the same tail,
  quiet time and counts of a task on a relay they may read; `read_session`
  lists the task ids with that health.

### Changed

- **Long conversations open and scroll back fast.** A history page used to
  load and prepare every message of the conversation, so a chat with a few
  thousand messages took about two seconds per page and per live refresh. The
  relay now reads only the requested page from the database (migration 0017
  adds the indexes for it), so a page loads in well under a tenth of a second
  however long the conversation is.
- Switching back to a conversation already viewed in this tab draws it at
  once from what the page kept of it; the relay's fresh answer is merged into
  it when it arrives.
- **Settings › General › Messages kept per conversation for switching back**
  (200 by default, 100 to 1,000 or no limit, saved in this browser) sets how
  many loaded messages each of the last eight conversations keeps.
- **The conversation list shows activity by the title's colour.** The three
  animated dots after a working session's title are gone: a ready session
  that runs a turn or background work keeps the bright green title, an idle
  one turns a darker green (a muted green in the light theme), and the chat
  header follows the same rule. Question, error, offline and archived titles
  keep their colours.
- The npm package guard allows 7.5 MiB unpacked and 510 entries (was 7.25
  MiB and 490); the task console, the transcript's handling of new
  messages below a place far up the history, the Copilot task report and the
  Claude background command limit grew the server and the app to about 7.37
  MiB and 505 entries.

### Fixed

- **Copilot background tasks report how they ended.** Finished shells and
  sub-agents of Copilot's SDK engine showed the status *unknown* and no exit
  code, in the task data agents read and wherever the end is shown. The
  worker now reports completed, failed or cancelled, a shell's exit code, and
  a sub-agent's result (or its error) as its summary; a passing status line
  such as *Thinking…* is no longer kept as the final summary.
- **Cancel on a usage-limit pause removes the relay's held follow-up from the
  open conversation at once.** The *Automatic message from OAR* row was
  deleted on the relay but stayed on the page, and in what the page kept for
  switching back, until a reload. A row deleted while you were elsewhere
  leaves too, also when it was the conversation's newest.

- **Switching away from a long conversation and back keeps your place.** After
  loading older messages and scrolling back into them, a switch to another
  conversation and back lost the reading position: with more than 100 messages
  loaded the oldest were dropped, and messages written meanwhile moved the view
  by as many rows. The page now remembers the message you were reading and
  keeps the loaded messages, so you come back to the same message at the same
  place, older pages included; after a reload it opens at that message too.
- **New messages no longer skip the ones in between while you read far up.**
  Opened at a pin, a search result or the place you were reading after a
  reload, a conversation shows the messages around it. A message or reply that
  arrived then was put right under them, and the messages between were never
  loaded when you scrolled down, only the jump-to-bottom button brought them.
  Now new messages wait: the button counts them, and scrolling down loads
  every message in between, in order, before them. A reply still being
  written shows once you are there. A message you send from there takes you
  to the end, where it, its reply and everything in between show.
- **Images that load late no longer push the message you are reading down.**
  In a browser without scroll anchoring (Safari, for one), pictures above
  your place that arrived after a switch back, a reload or an older page moved
  the text by their height. The transcript now holds your place while they
  load, and lets go as soon as you scroll.

## [0.9.11] — 2026-10-17

Highlights: sessions an agent starts now sit in a group under the session
that started it, with a pill that sums up what runs and what waits for you,
and rows drag into, out of and within a group. Settings › Sharing lists every
shared link with Copy, Renew and Stop and an access log of each open (the IP
address optional), shared views hide the host's paths in one of three modes,
**Preview as shared** shows what a viewer gets, and a 🙈 list gathers the
messages hidden from viewers. The relay is harder to reach from outside: ten
wrong tokens lock an address out for a minute, the live channel accepts only
the relay's own origin, forwarded headers count only from a trusted proxy, and
`?token=` signs in the page only. Day/Night is back in the ⋯ menu, next to the
new **About OAR**.

### Upgrade notes

- **Behind a reverse proxy on another host or origin, set `trustProxy` and
  `allowedOrigins`.** This is the first release that checks the origin of the
  live Socket.IO channel and that reads the client address, host and scheme
  from `X-Forwarded-*` headers only when the proxy is trusted. A proxy on the
  relay host itself is trusted already (`trustProxy` defaults to
  `"loopback"`). For one on another machine, or one that rewrites the `Host`
  header, add to the config file (`~/.oar/config.json`, or `server/config.json`
  in a git checkout) and restart the relay:

  ```json
  "trustProxy": "192.0.2.10",
  "allowedOrigins": ["https://relay.example.com"]
  ```

  `trustProxy` takes what Express's `trust proxy` takes: the proxy's address,
  a subnet such as `"10.0.0.0/8"`, or a hop count such as `1`.
  `allowedOrigins` lists the public origins exactly as the browser shows them
  (scheme, host, a port other than the default). Without them, the page loads
  but its live connection is refused (the relay logs `Refused a handshake from
  origin …`), and every client shares the proxy's address, so one client's
  ten wrong tokens lock the rest out for a minute.
- **Ten wrong tokens from one address within a minute answer `429`** with
  `Retry-After` until the minute is over. Requests from the relay host itself
  that no proxy forwarded are never counted.
- **`?token=` works on the page address only.** Scripts that fetch API, file
  or asset URLs with `?token=` must send `Authorization: Bearer <token>`
  instead (or the cookie); a Socket.IO client passes it as `auth.token` (or
  the cookie), not in the query.
- **Restart web relay, cancelling a queued restart and Empty queue need only
  the token**, from any signed-in device. The session-worker socket and the
  image-operation run answer only requests from the relay host that no tunnel
  or proxy forwarded.
- Four database migrations (0013 previews, 0014 session groups, 0015 the place
  in a group, 0016 the share access log) run by themselves on the first
  start. The Node.js requirement is unchanged (22.13 or newer).

### Added

- **Session groups in the sidebar.** A session that an agent starts with the
  `remote_relay` tool now sits under the session that started it. The head
  row carries a fold button and a pill that sums up the members ("3 · 1 waiting
  for you, 2 running"); the members are indented under it, folded away once
  everything in the group has finished unless you fold or unfold it yourself.
  A question or approval waiting in a folded member shows on the head row,
  and a tap on it opens that member. A member's activity lifts the whole
  group to the top of the list. The head row's menu archives the whole group
  (members still working are kept and named) and stops everything running in
  it, and dissolves it; any row's menu adds a conversation to a group,
  moves it, or takes it out, and groups can be named by hand. The `remote_relay` tool's
  `list_sessions` and `read_session` show the group a session is in (agents
  only read it). Migration 0014 adds the `conversation_groups` table and the
  `group_id` column; nothing is backfilled.
- **Drag and drop for session groups.** Drag a session into a group, out of
  it, or to another place inside it; the order you set stays, and later
  arrivals join at the end. Drop one loose session onto the middle of another
  and the two make a new group. On a phone a long press lifts the row and a
  move drags it; with the keyboard, a member's menu has "Move up" and "Move
  down". Migration 0015 adds the `group_position` column. Rolling back needs
  no data change: older releases ignore the column, and a stale place they
  leave behind is cleared when this release starts again.
- **A 🙈 list of the messages hidden from shared viewers.** Next to 📍, a 🙈
  button with a count appears while the open conversation hides messages
  from its shared link (never in the shared view itself). It opens a list
  like the pinned one: each row jumps to its message, also one far up in the
  history, and has Unhide; "Unhide all" asks first and leaves only a message
  whose turn is still running hidden, naming it. The count and the list cover
  messages outside the loaded page, and other open tabs and devices follow a
  change without a reload.
- **Manage your shared links in Settings › Sharing.** Every shared
  conversation is listed with when it was shared and last opened, how often
  and by how many viewers. **Copy link** copies it, **Renew link** gives the
  conversation a new link (the old one stops working at once), and **Stop
  sharing** ends it; the `⋯` menu has **Stop sharing** too while the open
  conversation is shared, and asks first. Each row unfolds into an access
  log: one entry per open of the page (not per refresh) with time, IP
  address, country and browser. The log carries over to a renewed link and
  is deleted when you stop sharing or delete the conversation. Switch off
  **Store viewer IP addresses** to log new opens without the address (the
  status log of shared reads follows it); switching it off also offers to
  remove the addresses already stored, from both logs. Distinct viewers are
  still told apart. Migration 0016 adds the share lineage and the
  `conversation_share_opens` table.
- **Preview as shared.** The `⋯` menu's new entry opens the conversation as
  a share link shows it, without creating one: what viewers get instead of a
  path is marked green, whatever still looks like a path amber.
- **Sub-agent records in shared views.** A sub-agent's row in a shared
  conversation now opens its record, read-only and without Stop, filtered
  like the rest of the share.

- **Wrong tokens lock an address out for a minute.** Ten wrong tokens from
  one address within a minute (a Bearer header, the cookie, the sign-in field
  or a Socket.IO handshake) make the relay answer that address `429` with
  `Retry-After` until the minute is over, the right token included, and the
  sign-in page says how long to wait. The same stale value repeated counts
  once, so a tab with an old cookie does not lock you out. Requests from the
  relay host itself that no proxy forwarded are never locked out, so the
  relay's own workers and agent tools keep working.
- **About OAR in the ⋯ menu.** A new entry, "ℹ️ About OAR · <version>",
  opens what this relay is and has done: version, commit (for a git
  checkout) and when the relay started; each provider that is on, with its
  CLI and SDK version and whether it is online; conversations, turns and
  messages since the first conversation, turns per provider, and the usage
  the providers report (Copilot premium requests, Cursor and Grok tokens and
  cost, Claude Cloud cost); uptime, database size and running workers. With
  the opt-in update check on it names the latest version, otherwise it says
  where to turn the check on. Shared viewers do not get the entry. The data
  comes from the new `GET /api/about`, which counts large databases in small
  steps and keeps the totals for a minute.

### Changed

- **A stopped or renewed share link answers a plain 404**, the page included,
  exactly like a link that never existed, and a shared page that is still open
  stops at its next refresh instead of polling on. The address in the access
  log and in the status log of shared reads is now the one the relay trusts
  through its `trustProxy` setting; a forwarded header from an untrusted peer
  no longer sets it.

- **A share link no longer carries the relay host's paths.** The relay
  filters what it serves to shared viewers, by **Paths and file names** in
  Settings › Sharing: show every path, hide the path and keep the file name
  (the default), or hide both. Tool lines, thoughts, replies, attachments and sub-agent records all
  pass through it; before, only the viewer's browser shortened the paths in
  tool lines, and attachments carried their full path on the host. Free text
  is filtered on a best-effort basis: relative paths are left as they are.

- **A long press on a sidebar row opens its menu when the finger lifts**, no
  longer while it is still down: a long press that then moves drags the row
  instead. Where rows cannot move (the Archived view, under a filter, on a
  group's own line) the menu still opens during the press.

- **A reply to another agent's note is no longer folded away.** Only the
  note itself folds into its row now, "note from linux-test · report builder ·
  answered below"; the session's reply to it always shows, like a reply to
  you, because it may hold a line for you. A turn you steered a message into
  folds its note too, since nothing of yours is behind the row any more. The
  ⌄ button's badge counts the note and the reply as two rows.

- **A conversation no longer scrolls down by itself while you read further
  up.** It only moves when you are at its end, or when you have just sent a
  message yourself. Anywhere else it stays exactly where it is, whatever
  lands: a reply, a relay event, another agent's note, a question, an approval
  or a plan board. The ⌄ button then tells you: it turns blue with a count
  (or a dot while a reply is still being written) when something new is
  below, and amber with a "?" when a question or an approval waits for your
  answer; a screen reader hears both. The text you are reading also keeps its
  place when the reply you are in the middle of is finished and when the
  conversation reloads. New in Settings › General: "Jump to the end when I
  send a message", on by default and saved in the browser; switch it off and
  your own messages leave you where you are as well.

- The npm package guard allows 7.25 MiB unpacked and 490 entries (was 6.75
  MiB and 450); session groups, the stored previews, the background-task
  resync, the transcript anchor, share management, path privacy in shared
  views and About OAR grew the server and the app to about 7.05 MiB and 485
  entries.

- **Restart web relay, cancelling a queued restart and Empty queue work from
  every signed-in device.** They were meant for the relay host only, but the
  check let everything through a tunnel or a proxy on the same machine and
  refused only a direct LAN connection; they now need the token and nothing
  else. The other way round, the session-worker socket and the image-operation
  run now answer only requests from the relay host itself: a loopback peer
  with no `cf-ray` or other tunnel marker and no `X-Forwarded-For`,
  `Forwarded` or `X-Real-IP`. A same-host proxy that adds no marker no longer
  reaches them.

- **A `?token=` signs in the page only.** The page takes it out of the address
  and signs in with it as before; API, file and asset URLs now accept the
  token in the `Authorization` header or the cookie only, and a Socket.IO
  handshake no longer reads a `token` query.

- **The live Socket.IO channel accepts the relay's own origin only** (it
  accepted any). The host compared is the one the browser addressed,
  `X-Forwarded-Host` included when a trusted proxy sends it; for a proxy that
  rewrites the `Host` header, list the public origin in the new
  `allowedOrigins` setting.

### Fixed

- **Day/Night is back in the ⋯ menu.** Since 0.9.10 the entry showed only in
  a shared view; everywhere else, on every screen size, it was gone from the
  menu. It is there again whenever you are signed in, with or without a
  conversation open, and stays the one entry of a shared view.

- **A shared conversation no longer marks every reply "ran on" its own
  provider.** In a shared link to a Claude, Cursor, Grok or OpenAI
  conversation, each reply carried the red `ran on claude` (etc.) chip, as if
  the turn had run on the wrong provider. The shared view now knows which
  provider the conversation is bound to, and shows the chip only when a reply
  really ran on another one; with no provider bound it shows none.

- **A wide table in the file reader scrolls sideways.** A markdown table wider
  than the reader made the whole page scroll sideways, and a sideways swipe to
  read the rest of it turned to the next file instead. The table now scrolls
  in its own box, as code blocks do; a swipe that starts on it stays in the
  file.

- **A worker no longer stops reconnecting after a relay restart.** On Node
  22, a worker whose reconnect reached a relay that was not ready yet gave up
  for good: its process kept running without a connection, the relay started
  no new one while that process lived, and every message to the session
  stayed queued until the worker was killed and launched by hand. A failed
  connect now always leads to the next attempt, with the usual backoff of up
  to 8 seconds. As a safety net, the relay replaces a worker that has had no
  connection for 60 seconds while a message waits for it: it stops the old
  process and starts a new one, which then takes the message. It never does
  so while a turn or background work of that session is running, and logs a
  `WORKER DETACHED` line when it does.

- **A relay restart no longer forgets running background tasks.** A session
  with a background shell or sub-agent still running read as idle after the
  relay restarted: its task panel was empty, **Suspend host** saw nothing
  to wait for, and a watching session could be told `all_idle`. The relay
  now asks each worker for its tasks again on the worker's next heartbeat,
  and the tasks come back with their original start time. A session whose
  worker was started before this update shows "background work, details not
  reported since the relay restarted" until it reports. For the first 45
  seconds after a relay start a suspend waits for sessions that are still
  reporting, and a task that ended while the relay was away still reports
  its end.

- **Workers start when no `node` is on the relay's PATH.** A relay started
  by the Windows system-startup task, before anyone signed in, ran fine, but
  none of its workers ever started: they were launched with a bare `node`
  that the task's PATH did not have, Windows reported the failure in a
  dialog no one could see, and the log showed only a heartbeat timeout
  every 90 seconds. Workers now run on the relay's own Node by its full path
  on every platform, tmux included; `COPILOT_WEB_RELAY_NODE` still overrides
  it. That Node's folder is added at the end of a worker's PATH when the PATH
  lacks it, so the CLIs and commands a worker starts find a `node` too; a
  PATH that has one keeps it.

- **A worker that cannot be started says why.** On Windows, a launch whose
  `start` command fails or never returns now fails at once, naming the
  program, and the conversation's launch note shows it; before, the relay
  waited for a heartbeat that never came. The relay log names the command
  and the PATH, SystemRoot and ComSpec it was started with, also when a
  launch saw no worker process yet, and a failed process-list read logs its
  exit code and error output. Windows' own programs (PowerShell, schtasks,
  taskkill and others) are called by their full path. Changing the launch
  folder and the Windows autostart setting show the relay's reason when they
  fail, not a bare "Failed to …", and an autostart read that fails is logged.

- **The Windows system-startup launcher copes with an early boot.** The
  launcher OAR generates adds Node's folder at the end of PATH and waits up
  to two minutes for the `%TEMP%` folder to exist before it starts the relay
  (a temp folder on a drive that a startup script creates later no longer
  makes the relay and its CLIs fail); a wait that runs out is logged next to
  the launcher. Settings now names how a boot task or launcher differs from
  the generated one (a missing boot delay, an edited launcher) instead of
  treating it as a failure; selecting "At system startup" again regenerates
  both and keeps a changed launcher as a `.bak` copy. A boot task that cannot
  be read no longer breaks the setting.

- **Preview links survive a relay restart.** A preview lived only in the
  relay's memory, so an update, a queued restart or a crash turned every
  preview link into a 404, and publishing again gave a new link. Previews are
  now kept in the relay's database and restored when it starts, each checked
  again: a folder preview while its folder is still inside the workspace root,
  a dev-server preview until the machine reboots (on Windows with fast
  startup, until a Restart: Shut down and power-on is not seen as one).
  Publishing the same port or folder again in a conversation returns the same
  link. Deleting a conversation, also through a cache rebuild, closes its
  previews; archiving keeps them. Settings → Previews shows when each preview
  was published and last opened, and the limit of 8 names the oldest ones and
  how to close them. Relay logs no longer carry a preview's token; a publicly
  shared transcript shows a preview card with its label only and removes every
  preview link quoted in its text, also one cut short by a reply still being
  written. A preview the relay cannot check at a start (a busy database, an
  unreadable folder) is not served, shows as such in Settings, and is checked
  again every 15 s for ten minutes. The table comes with migration 0013, which
  runs by itself on the first start; the npm package gains two modules for it.

- **An install gets the dependency versions the release was tested with.**
  The package asked for each of its dependencies "this version or newer", so
  an install or update fetched whatever was newest that day, e.g. a newer
  Copilot SDK than any release was tested with. The packages OAR depends on
  directly (the Claude, Copilot and Cursor SDKs among them) are now pinned to
  exact versions; a newer one arrives with an OAR update. Packages those
  depend on still follow their own version ranges.

- **Restart web relay no longer reports success when the relay refuses it.**
  A refused request said "Relay restart requested"; it now shows the relay's
  reason, and a request that got no answer says so instead of claiming
  success. The restart after a feature-flag change and cancelling a queued
  restart report their errors the same way, and Empty queue names the reason
  it failed.

- **Forwarded headers count only from a trusted proxy everywhere.** The
  shared-view presence limit read `X-Forwarded-For` and `X-Real-IP` and the
  share links read `X-Forwarded-Host` and `X-Forwarded-Proto` straight from the
  request, so a direct client could pick its own rate-limit bucket or the
  host of a new share link. Both now follow the `trustProxy` setting.

- **`tunnelMarkerHeaders: []` in the config no longer switches off
  `COPILOT_TUNNEL_MARKER_HEADERS`.** The two lists are merged, with `cf-ray`
  always included.

## [0.9.10] — 2026-10-12

Highlights: sessions watch other sessions. An agent subscribes to sessions on
its own relay or on a paired one and is woken by what happens there:
questions waiting for an answer, errors, replies, finished background work,
sessions gone quiet. A subscription only notifies; it approves nothing, and
the first one in a conversation asks you on a card. Also new: a sub-agent's
whole record opens in a viewer, a note between agents and its reply fold into
one row, `remote_relay` reports what the provider plans have left, the file
browser creates, renames and deletes folders and files, a ⌄ button jumps back
to the latest message, archiving an active session asks first, and Settings
opens with no conversation open.

### Upgrade notes

- **Running sessions pick up the new tools when their worker restarts.** A
  session worker builds its `remote_relay` tool when it starts, so a worker
  that was already running before the update offers neither `subscribe` nor
  `usage`, and records no sub-agent timeline, until it is restarted: **☠️
  Kill session** on an idle session is enough, the next message starts a new
  worker. The database migrations run by themselves on the first start.

### Added

- **A session can watch other sessions and be woken by what happens there.**
  With `remote_relay`'s new `subscribe` an agent asks to be told about
  questions waiting for an answer, errors (a failed turn, the usage limit, a
  worker that died), replies, background agents and shell tasks that finish
  or fail, sessions that went quiet while working, and the moment nothing
  runs any more, for listed sessions or all of them. OAR writes a short event
  message into the watcher's conversation: a new turn when it is idle, a
  steer into the running turn where the provider steers, the next turn
  otherwise. The message says it is automated and approves nothing, and shows
  as a compact row, never as one of your messages. Events within 30 s come
  together; past 20 wake-ups an hour the rest comes as a digest every ten
  minutes, while questions and errors still come at once. An optional
  heartbeat says the watching works while anything runs. Subscriptions
  survive a restart and end on unsubscribe, when the watcher or the watched
  session is archived, or after firing once. The first subscribe of a
  conversation, and any to all sessions, asks you on an approval card;
  `transfer_subscriptions` hands every subscription of an old watcher to a
  new one after your approval. The watcher's header shows **watching N
  sessions** with an Unsubscribe button per subscription, and a watched
  session shows a **watched** badge.

- **Watch sessions on a paired relay.** `subscribe`, `unsubscribe`,
  `list_subscriptions` and `transfer_subscriptions` also take a paired
  relay, with the same events and options. The relay must be unlocked in
  the conversation and allow at least reading, and the other relay must
  accept other relays' agents; the approval card names that relay, and
  nothing is asked there. This relay fetches the events every few seconds
  over the pairing (a pairing in one direction is enough), and they arrive
  like those of its own sessions, their lines starting with the relay's
  name. If the link drops, the other relay keeps what happens (up to 500
  events per subscription, for 7 days) and delivers it as one row tagged
  **replayed** once it answers again, questions and errors first, between
  "link lost" and "link restored". Subscriptions survive restarts of either
  relay and end on unsubscribe (reaching the other relay later if it was
  offline), when the watcher or the watched session is archived, or when
  the pairing is removed. An older relay answers that it needs an update.

- **list_sessions and read_session say what a session is doing.** `state`
  is `running_turn`, `background_only` or `idle`, and `tasks` lists its
  background agents and shell tasks with when they started, when they last
  did something, how they ended and the exit code where it is known. A
  session with background agents running no longer reads as inactive.

- **Agents can check what the provider plans have left.** `remote_relay`'s
  new `usage` action answers, for the relay an agent runs on or a paired
  one, what each provider's plan has left: Cursor's spend against its
  limit and when it resets, Claude's windows and whether a usage limit has
  paused turns, Copilot and Grok where they are tracked; a provider that is
  not set up says so. It needs only read permission and returns no account
  names, addresses or tokens.

- **Notes between agents fold away.** A prompt another agent sent a
  session, from a paired relay or from this one, and the session's reply to
  it show as one row, "note from linux-test · report builder · replied",
  which opens to both, on a desktop and on a phone. Your own messages and
  the replies to them stay as they are, and a turn you steered a message
  into, or one with a question or card open for you, is never folded. A
  note still being worked on stays open and folds by itself once it is
  answered; one you fold or open yourself stays as you left it.

- **Open a sub-agent's whole record.** A sub-agent's title in the composer's
  background-task panel opens a viewer with everything the agent did, in
  order: its thoughts, every tool call with its full input and its result,
  its reply text and the agents it started itself, live while it runs. The
  finished reply lists the turn's sub-agents as the same rows, with model,
  type, tokens, time and how they ended, so the record stays one click away.
  A child agent opens in place, and back returns to its parent. Stop is in
  the viewer too. Claude records everything; Copilot and Cursor show what
  they report. A tool result is kept to its first 4 KB, and a record to 2000
  entries. Only a page with the viewer open receives the record as it grows.

- **Choose how long sub-agent tool output is kept.** Settings → General →
  **Keep sub-agent tool output** offers 7, 14, 30 or 90 days (the default)
  or always. Once a finished agent's record is older than that, its tool
  inputs and results are removed, once a day; its thoughts, its reply, its
  notes and the one-line summary of each tool call stay, and the viewer says
  what was removed.

- **Folders can be created in the file browser.** A **📁+ New folder**
  button in the toolbar opens a row at the top of the folder pane; the name
  is typed there and Enter creates the folder inside the open one and opens
  it. It works in the repository, on the drives and in the session folder,
  and in the folder picker of **New chat** and **Change CWD**, so a new
  project folder can be made and picked in one go. A taken name or a name
  the system refuses is reported under the input.

- **Files and folders can be renamed and deleted in the file browser.** A
  right-click, or a long press on a phone, on a folder in the tree or a file
  in the folder pane opens a menu with **Rename** and **Delete**; a folder's
  menu also offers **New folder here**. Rename edits the name in place: Enter
  saves, Escape cancels, and an unchanged name is a cancel. Delete asks in a
  dialog that says what goes: a file, an empty folder, or a folder with so
  many files and folders in it; for a folder with content the folder's name
  must be typed before the red button enables. Links are never followed or
  changed; roots (the repository root, `/`, a drive, the session folder)
  cannot be renamed or deleted, and the session folder is read-only apart
  from new folders.

- **Jump back to the latest message.** A round ⌄ button above the composer
  fades in once the transcript is about a screen above its end, and its
  badge counts the rows that arrived since (an agent's folded exchange
  counts once). A click goes back to the end, smoothly up to three screens
  away and at once beyond, and the transcript follows new rows again; from
  a search result or a pin deep in the history it loads the end.

### Changed

- **Archiving a session whose process is still active asks first.** A green
  session, idle or working, now opens "Archive this session? Its process is
  still active." from the row's 🗄, its menu and the chat menu alike; Cancel
  or Escape keeps it. An inactive session archives at once, as before.

- **A message from another device no longer pulls a reader who scrolled up
  down to the end.** Another client's message, an agent's prompt included,
  is followed like any live row: only while the transcript is at its end.

- The npm package guard allows 6.75 MiB unpacked (was 6.5 MiB); the
  sub-agent records and the event subscriptions, now across paired relays,
  grew the server.

### Fixed

- **A dropped websocket can no longer take the relay down.** A browser that
  reset its connection while a preview's websocket, or any websocket to the
  relay, was still being set up ended the whole relay with `read ECONNRESET`.

- **A queued relay restart no longer waits forever on a busy relay.** The
  restart used to wait for the whole queue to empty while new messages kept
  being handed to workers, so each new turn postponed it again. It now waits
  for the running turns only; queued messages are held until the relay is
  back and run then. The banner says which.

- **Waking from sleep no longer restarts the running turns.** After the host
  slept for hours, every worker heartbeat looked hours old the moment the
  relay's timer fired again, so live turns were recovered as stale and their
  messages delivered a second time while the workers' own replies were
  refused, hundreds of warnings in minutes. The relay now notices the clock
  jump and gives live workers a full inactivity window to report before
  anything is recovered; a superseded attempt's refusals are logged once and
  summarized, not line by line.

- **A Cursor turn waiting on a long command is no longer ended as stalled.**
  When the agent started a command in the background and then waited for
  it, the wait counted as a model request, and the turn was ended after five
  minutes of quiet. The wait now gets the 30 minutes a running tool gets.

- **An event's fields cannot add lines to a watcher's event message.** A
  line break in a session title, a task description or a field of a paired
  relay's event could start a line without the `[relay event]` prefix. Every
  field is now written as one line, and what a paired relay sends is stored
  field by field from a fixed list, never as the payload it sent.

- **The relay event tables no longer grow without bound.** Delivered events
  and ended subscriptions were never deleted, and the retention check for
  paired relays scanned the whole event table every 30 seconds. Delivered
  events now go after two hours (the wake-up count reads the last hour),
  ended subscriptions after a week, and the check uses its index.

- **The "watching N sessions" list opens in full.** The header cut it off,
  so on a phone it did not show at all. It now opens under the chip on top
  of the page, and Escape or a tap elsewhere closes it.

- **Sub-agent runs a worker never closed no longer count as running work.**
  A run whose worker went away without reporting its end stayed `running`
  for good, so a subscription to all sessions never saw `all_idle` and
  reported the session stalled over and over, and the suspend watchdog saw
  work. Such runs are now closed shortly after the relay starts and every
  ten minutes (never while their worker is alive), and a run row not touched
  for a day is not counted meanwhile.

- **A background agent's output stays with the turn that started it.** Its
  thoughts and tool calls no longer land in a later turn's reply, and between
  turns they are no longer lost.

- **A background agent no longer shows as finished the moment it starts.**
  Its row now ends when the agent does.

- **A sub-agent stopped by a failed turn or a worker crash settles on screen
  without a reload.** The update used to arrive without its turn, and the
  page ignored it.

- **The transcript keeps following the live reply when a background task
  starts.** The task panel appearing, opening or growing shrank the
  transcript from below and left it that much short of the end, so the next
  live row stopped following. It now stays at the end.

- **The transcript follows the agent's reply as it streams in.** Each piece
  of streamed text was scrolled to before it was shown, so the first new
  line left the reader short of the end and the rest was not followed.

- **Going back to the end while a turn finishes sticks.** The reload at the
  end of a turn put a reader back where they were when it was asked for, so
  scrolling to the end, or pressing ⌄, during it was undone and the next
  rows were not followed. It now keeps where the reader is when it lands.

- **⌄ no longer opens the keyboard on a phone.** The composer is no longer
  focused when the page loads on a phone, and ⌄ keeps it focused only while
  the keyboard is already open, so a tap on it just scrolls.

- **A message that arrives while the conversation reloads stays on screen.**
  A reload asked for just before another client's message or a reply landed
  rebuilt the transcript without it, until the next reload. The reload now
  asks again when rows arrived on the way.

- **A message sent right after switching conversations shows up where it was
  sent.** A quick run through the list, or a click back on the conversation
  still open, could hand the view to an earlier click once the draft of the
  conversation left behind had saved. The message then went to that
  conversation, or its bubble and the live reply stayed hidden until the
  conversation was opened again. A click now opens its conversation at once.

- **The live reply stays on screen right after a send.** A poll answer read
  just before the agent took the message could end the turn on the page, and
  a reply update lost on a busy connection left the bubble empty; both waited
  for the conversation to be opened again.

- **Settings opens with no conversation open.** On a fresh install the ⋯
  menu did nothing until a chat existed, so no provider could be turned on.
  The menu now works on an empty screen, with the entries that need a
  conversation left out, and the empty screen and New Chat point to
  Settings → Providers until a provider is ready instead of starting a
  Copilot chat that cannot run.

## [0.9.9] — 2026-10-07

Highlights: `oar` opens a menu and gains stop, restart, status, url and
service commands, and setup asks for access and port; files open in a
fullscreen viewer with galleries to swipe through, and a PDF opens in the
browser; messages can be pinned; a downloaded file is the file as it is now,
and no cache may keep an API answer; the built-in Cloudflare tunnel uses the
`cloudflared` you installed instead of a bundled copy; the three provider
SDKs are current.

### Upgrade notes

- **The built-in Cloudflare tunnel needs `cloudflared` installed on the relay
  host.** An install that ran the tunnel (`cloudflaredTunnel` in the config)
  on the copy that came with OAR loses the tunnel after this update until
  `cloudflared` is installed: `winget install --id Cloudflare.cloudflared`
  on Windows, `brew install cloudflared` on macOS, Cloudflare's package
  repository on Linux (<https://pkg.cloudflare.com/>). The relay itself
  starts and works locally in the meantime. Installs with
  `cloudflaredTunnel.binary` set, with `cloudflared` on the `PATH`, or
  with a tunnel that runs outside OAR are not affected.

### Added

- **The file viewer fills the screen.** A file opened from the file browser
  or from a chat takes the whole screen: pictures, video and audio on black,
  text in a column that reads well on a wide screen. The title, the toolbar
  and the meta line float over the content; a tap on the content hides them
  and brings them back, and while a video or an audio file plays they fade
  by themselves after three seconds, as players do. Audio files play in the
  viewer. A PDF, which the viewer cannot show, is a card with **Open in new
  tab** (the browser's own viewer) next to **Download**.
- **Swipe through a folder, or through a chat's media.** A file opened from
  the browser's folder pane carries its neighbours along: a sideways swipe
  shows the next or the previous file, as do the arrow keys and the two
  buttons below with the position between them. A picture or an attachment
  tapped in a chat opens with every image, video and audio file of the
  conversation to swipe through, oldest to newest. Neighbours are fetched
  ahead, so a swipe lands on the file and not on "Fetching…".
- **The back gesture closes the viewer, and a swipe down dismisses it.** On
  a phone the system back gesture closed the app; it now closes the viewer
  and nothing else, and the address never changes. A swipe down on a picture,
  a player or a card lets the content follow the finger and then go.
- **The back button never closes the installed app.** In the app installed
  from the browser (Android, iOS, desktop), pressing back with nothing left
  to go back to used to close it. From the first touch on, the app keeps
  one entry beneath its own and steps back onto it: back closes the file
  viewer and nothing more, and a back with nothing left to close shows
  *Press back again to close the app* — a second press without a touch in
  between closes it, as phone apps do. (Before any touch the browser lets
  back leave on purpose; a page cannot change that.) In a browser tab the
  back button is the browser's as before.
- **`oar` opens a menu.** In a terminal, `oar` shows whether the relay runs,
  its version, URL, port, access and service, and offers Start, Stop, Restart,
  Show URL and QR code, Settings, Install or remove the service, Update,
  Doctor and Copilot session (arrow keys or digits, Enter, `q`). Without a
  terminal it prints the status and the usage.
- **`oar stop`, `oar restart`, `oar status`, `oar url`, `oar service`.** Stop
  and restart wait for running turns (the systemd service is stopped and
  restarted by systemd). `oar status` exits 0 when the relay runs and 3 when
  it does not. `oar url` prints the relay URL again. `oar service install`,
  `remove` and `status` manage the start at login: the systemd user service
  on Linux, the sign-in autostart on Windows; on macOS, and on a Linux
  without a systemd user session, the command says so and names `oar start`.
- **`oar setup` asks for access and port, with options for each.** In a
  terminal, setup asks for a new token, the access (this machine only, or
  LAN) and the port, each with its current value; Enter keeps it. `--port`,
  `--lan`, `--local` and `--new-token` answer without a question, and
  `--defaults` keeps everything of an existing config.
- **WSL: a port has to be free on Windows too.** In WSL's default networking
  a port can be free in the distro while a Windows program listens on it, and
  a Windows browser's `localhost` then reaches that program. A new config now
  takes the first port that is free on both sides, setup refuses a port
  Windows holds, and `oar start`, `oar status`, `oar doctor` and `oar setup`
  warn when Windows holds the relay's port. Where Windows cannot be asked,
  nothing changes.
- **Pin messages in a conversation.** Every message has a **Pin** button
  beside **Hide**. A conversation with pins shows a **📍** button with their
  count in its header; it opens the list of pinned messages, each with a short
  preview. A row scrolls to its message, also one far up in the history, and
  **🗑** unpins it. Pins are stored on the relay, appear on every open device
  at once, and are not part of the shared view. At most 100 per conversation.

### Changed

- **The Copilot terminal session is `oar copilot`.** `oar` alone no longer
  starts the relay and `gh copilot`. `oar copilot [-- <args>]` does; the old
  forms with an option and no command (`oar -- <args>`,
  `oar --install-extension`, `oar --no-install-extension`,
  `oar --migrate-from <dir>`) still run it.
- **Setup no longer asks about the tunnel or the service.** The service is
  `oar service install`; the tunnel is set in the config
  ([Remote access](README.md#remote-access)). `oar setup --start` still
  installs the service where a systemd user session exists.
- **A QR code only for an address a phone can reach.** With LAN access the
  URL and the QR code carry the LAN address. With access for this machine
  only, setup prints the localhost URL and one line on how to get phone
  access, and no QR code. In WSL a LAN address is internal to WSL: setup says
  so and names the two ways out, a tunnel or WSL's mirrored networking.
- The npm package guard allows 6.5 MiB unpacked (was 6 MiB); the command line
  grew by the menu and the new commands, and the app by the pinned messages
  and the file viewer.
- **A tunnel without `cloudflared` says so.** When the built-in Cloudflare
  tunnel is switched on and no `cloudflared` is found, the relay starts as
  usual, the tunnel stays off, and the status dot's tooltip, `oar doctor` and
  the server log name the install command for the system. The relay looks
  again every minute and starts the tunnel once `cloudflared` is there. With
  `cloudflaredTunnel.required` the queue stays paused until the tunnel is
  connected, as before.
- **`--allow-scripts` lists two packages.** A manual install on npm 12 is now
  `npm install -g --allow-scripts=better-sqlite3,koffi @oar-sh/oar`;
  `oar update` and the **Update** button pass the same list.
- `@github/copilot-sdk` 1.0.17 (bundled Copilot CLI 1.0.93): typed structured
  outputs for `sendAndWait`, structured JSON-RPC error data, an experimental
  `setTools()` to replace a session's client tools mid-session, sub-agent
  start/stop hooks, and the new session events `tool.shell_output` and
  `human_response.recorded`. The extension entry renames `factories` to
  `workflows`; OAR passes neither. The package now pins its own dependencies
  exactly, so it carries its own `zod` 4.3.6.
- `@anthropic-ai/claude-agent-sdk` 0.3.292 (bundled Claude Code 2.1.292).
  Of the nine releases since 0.3.283 these touch the relay's worker: a
  cut-short or interrupted streaming reply now always ends with its
  `message_stop` (0.3.287, 0.3.290), so the turn-liveness watchdog sees the
  end of the model's output; a follow-up turn woken by a finished background
  agent no longer fails its hooks, `canUseTool` and SDK MCP calls with
  "Stream closed" (0.3.284); `commands_changed` no longer arrives before
  `init` (0.3.287); a user message replayed under the same `uuid` while the
  first copy still waits is no longer replayed twice (0.3.290); subagent
  `assistant`/`user` messages carry `agent_id`, task events carry
  `parent_task_id` and `run_id`, and `background_tasks_changed` for a
  finishing task now arrives after its `task_updated` and `task_notification`
  (0.3.292). An omitted `permissionMode` is now left to Claude Code (0.3.286);
  the worker always passes one.
- `@cursor/sdk` 1.0.36 (additive API changes only): an opt-in
  `subagentInherit` for `Task` child sessions, `sessionId` in the custom-tool
  context, and the SDK's *default* agent-store path now derives from SHA-256
  with a one-time rename of the old MD5-named store. OAR passes its own
  `stateRoot`, so its stores under `data/cursor-agents` keep their layout.
  The package dropped its `@connectrpc/connect-node` dependency.

### Removed

- **The bundled `cloudflared`.** OAR no longer installs the `cloudflared`
  npm package, which downloaded the binary during the install. The tunnel
  runs the `cloudflared` named in `cloudflaredTunnel.binary` (or
  `COPILOT_CLOUDFLARED_BINARY`), or else the one on the `PATH`.

### Fixed

- **A jump into the history stays where it landed.** After opening a search
  result in an older part of a conversation, the page put the end of the
  conversation back as soon as it refreshed itself: within a second while a
  turn was running, and whenever a turn ended or the connection came back.
  The window you jumped to now stays until you scroll down to the newest
  message. The same holds for a jump to a pinned message.
- **On a phone, the Pin button of a message can be tapped.** A tap on a bubble
  reveals its Pin and Hide buttons, but the reveal hung on the hover state the
  tap leaves behind, which the browser drops at the next scroll or layout
  change (seen on Linux), and the share-visibility line then took the tap
  instead of the button. The tap now holds the reveal until a tap lands
  outside the bubble.
- **A PDF opens in the browser's viewer again.** Served files carry a
  `Content-Security-Policy` with `sandbox` so a worker-written file can never
  run as script on the relay's origin; browsers refuse to show a sandboxed
  PDF in their built-in viewer, so "open in a new tab" turned into a
  download. A PDF alone is now served without the `sandbox` directive (PDF
  viewers run no document script) and may be framed by the relay's own
  origin; HTML, SVG and every other type keep the full lock-down.
- **Audio files were an opaque download.** No audio type was known, so an
  `.mp3` or `.wav` went out as `application/octet-stream` with a download
  disposition. Audio types are served as such, previewed as audio, and
  `.bmp` and `.avif` are served as images.
- **An SVG can be previewed.** It was served as plain text so that a
  worker-written SVG could never run as a page on the relay's origin, and
  an `<img>` refuses plain text: the viewer and the file browser's
  thumbnails showed a broken picture. An SVG now keeps its type; the
  `sandbox` in every served file's `Content-Security-Policy` still denies
  it script and an origin if it is opened as a page.
- **Uploaded video can seek.** A video attachment in a chat was streamed
  without range support, so the player could not jump; it now goes through
  the same range-aware file serving as a workspace file, and so does a
  shared conversation's attachment. Their long-lived caching stays.
- **A file with a Japanese, Chinese or emoji name can be served again.** Its
  name went into `Content-Disposition` as is, which the HTTP layer refuses
  for characters outside Latin-1, so such a workspace file, upload or shared
  attachment failed with a server error and left a file handle open each
  time. The header now carries an ASCII fallback and the full name in the
  `filename*` form, so the file opens and is saved under its own name.
- **A changed file is downloaded as it is now.** The **Download** button of
  the file viewer handed back an older copy of a file that had been written
  again under the same name. Three things were wrong:
  - A click on Download did not download: since 0.9.0 every link click was
    turned into "open in a new tab", so the link's own file name was dropped
    and a phone showed or saved the file under its plain name, next to (or
    instead of) the copy it already had. Download links are left to the
    browser again.
  - A file fetched within two seconds of a change arrived cut to its
    previous length, because the announced length came from an older read
    than the bytes. Length, validators and bytes now come from one open file.
  - The address and the saved name never changed. The link now carries the
    file's version (`v=…`), and the file is saved with the time it was last
    changed, `report.pdf` as `report (1005-0719).pdf`.
- **No cache between the relay and you may keep an answer.** Every API
  answer, errors included, now says `Cache-Control: no-store` unless the route
  sets its own value, and every answer carries `CDN-Cache-Control: no-store`
  and `Cloudflare-CDN-Cache-Control: no-store`, which bind a CDN in front of
  a tunnel whatever cache rule its zone has. Served files also carry an
  `ETag` and `Last-Modified`.
- **A file preview could stop the relay.** The preview and file routes used a
  cache they were never given, so dropping a stale entry threw; in the
  preview route that happened outside any handler and ended the process.
- **A changed token, port or access reaches the running relay.** `oar setup`
  wrote the new value while the relay kept the old one, so the relay refused
  the token in its own config. Setup now restarts a running relay after such
  a change (in a terminal it asks first), with the old token on the old port,
  and waits until the relay answers with the new settings. If you decline, or
  the relay is still busy with a turn, `oar status`, `oar stop` and
  `oar restart` still find it.
- **The command may follow its options, and an unknown one starts nothing.**
  `oar --port 3339 setup` and `oar --setup` used to start the relay and
  `gh copilot` instead of running setup. The command is now found wherever it
  stands, `--setup`, `--start`, `--stop` and `--status` name it too, and an
  unknown command or option prints the usage and exits with code 2.
- **`oar copilot` checks the relay on its port, and says when `gh` is
  missing.** A lock file alone no longer counts as a running relay: the relay
  has to answer on the port. Without the GitHub CLI the command says so and
  starts nothing, instead of *spawn gh ENOENT*.
- **Ctrl+C in a prompt** ends the command quietly with exit code 130 instead
  of *Unhandled error*.


## [0.9.8] — 2026-10-04

Highlights: OAR has a logo, an otter, with icons that follow Day and Night
mode and a start screen that continues the phone's launch screen; a
*Compacting context…* line shows while a Claude session compacts, and a
changed auto-compact window now really applies; the one-line installer
finishes the job and `oar start` brings the relay up without a session;
scrollbars are easier to grab with a mouse.

### Added

- **The otter logo, matching Day and Night mode.** OAR has a new logo. The
  start screen and the sign-in box show it on a white tile in Day mode and on
  a dark tile in Night mode, and only the matching file is loaded. The tab
  icon, the toolbar colour and the installed app's icon and launch background
  follow the Theme setting too (Android launchers get a full-bleed icon that
  fills their shape), and the theme is applied before the first
  paint, so Day mode no longer starts with a dark screen. On Android the
  installed app's own launch screen updates when the system refreshes the
  app, which can take a day.
- **The installed app starts in one motion.** The start screen continues the
  phone's launch screen: the otter stands where and as large as the launch
  screen showed it, then slides up and shrinks while *Connecting to OAR…*
  fades in below. Without a stored token it travels on into the sign-in box,
  which fades in around it. Launch screen, start screen and status bar share
  one colour, so no plate shows around the logo. The app waits for the
  animation (under a second); a browser tab starts at once as before, and so
  does an installed app when motion is reduced in the system settings.
- **A compaction shows while it runs.** When a Claude session starts
  compacting its context, a *Compacting context…* line appears above the
  running turn and its bubble says *Compacting the conversation…*. When the
  compaction ends, the same line becomes *Context compacted · 120k → 40k
  tokens*; one that ends without a result leaves no line.
- **`oar start`, and `oar setup --start`.** `oar start` starts the relay of a
  global install in the background, without a Copilot session tied to it
  (through the systemd user service when one is installed), and prints the
  relay URL. `oar setup --start` does the whole job without a question: where
  a systemd user session exists it writes and enables the service and enables
  lingering, elsewhere it starts the relay in the background. `oar setup
  --port <port>` sets the port; a new config otherwise takes the first free
  port from 3333 on.
- **The macOS and Linux installer does everything after one confirmation.**
  `curl -fsSL oar.sh/install | sh` prints what it will do, asks once (`--yes`
  skips the question), then installs OAR and starts the relay. Without
  Node.js, or with one older than 22.13, it downloads Node.js 24 from
  nodejs.org into `~/.oar/runtime/node` for OAR alone. Nothing needs root.

### Changed

- **Scrollbars are easier to grab with a mouse.** A scrollbar doubles its
  width while the pointer is on it and goes back to thin 2.5 seconds after
  the pointer left. The space for the wide scrollbar is always kept, so
  nothing on the page moves when it grows. Touch devices are unchanged.

- **The auto-compact window slider saves when the 🧠 modal closes.** Moving
  it only changes the label and the notes beneath it; the value is saved
  when the modal is closed, and setting it back sends nothing. A notice
  warns when the new window is below what the conversation already uses
  (*The conversation will be compacted on the next message.*), and another
  says when the change takes effect: after the current turn, when
  background work has finished, or on the next message. The modal's
  headline reads `60.9k used · compaction window 100.0k · model limit 1M`.

### Fixed

- **`oar start` no longer mistakes a leftover lock or another program for a
  running relay.** It asks the relay itself: only an answer to OAR's own status
  call with the configured token counts as running. A lock left behind by a
  relay that did not stop cleanly is removed and the relay is started; if
  another program holds the port, `oar start` says so and names
  `oar setup --port`.
- **A new config no longer picks a port that is in use on Windows.** The
  free-port check binds the loopback addresses as well as all interfaces and
  tries a connection, since Windows allows a wildcard bind beside a listener
  on 127.0.0.1 alone.
- **The compaction line survives a busy turn.** The live *Compacting context…*
  / *Context compacted* line used to disappear after two dozen further
  activity lines until the next reload.
- **The auto-compact slider is saved when the page closes.** Closing the tab
  or the app with the context window still open used to drop a moved slider.

- **A "Compacting context…" line no longer stays on a waiting message.** When
  the session compacted while a message waited behind a turn the agent had
  opened itself, the line stayed on the waiting message for its whole turn.

- **A new auto-compact window now reaches a running Claude session.** The
  CLI reads the window only when it starts, so a changed window used to wait
  until the session restarted for some other reason. The session now
  restarts once, before the next message, when nothing is running in it
  (no turn, question, compaction or background work); the reply notes it
  (*Restarted the session to apply the compaction window (Auto → 100k)*).
- **The installer no longer fails with `EACCES` on a Node.js from a
  distribution or NodeSource package.** There npm's global folder needs root,
  and the installer refuses to run as root, so the install could not succeed.
  It now installs the package into `~/.oar/npm` and puts the `oar` command
  into `~/.local/bin`.
- **Updates land in the install that is running.** `oar update` and the
  **Update** button ran whatever `npm` was first on `PATH` into npm's default
  global folder. They now run the npm of the Node.js that runs the relay, into
  the folder the package is installed in, so an install made with `--prefix`,
  a relay service without `npm` on its `PATH`, and a version manager pointing
  at another Node.js all update correctly.
- **Installs and updates work on npm 12.** npm 12 runs no install script it
  was not told to allow, so `better-sqlite3`, `koffi` and `cloudflared` were
  installed without their binaries and the relay did not start. `oar update`,
  the **Update** button and both installers now pass `--allow-scripts` for
  these three; for a manual `npm install -g` the README gives the flag.
- **The systemd user service finds your tools.** The unit `oar setup` writes
  now carries the `PATH` of the shell that ran it, plus `~/.local/bin` and the
  directory of its Node.js. A user service otherwise starts with systemd's
  bare `PATH`, on which the relay's sessions found neither the provider CLIs
  nor, with a version manager, `node` itself.

## [0.9.7] — 2026-10-03

Highlights: Claude Cloud chats (experimental, off by default) run Claude Code
in a sandbox at Anthropic on a GitHub repository; conversations can be
archived and have a context menu in the list; agents can start and use
sessions on the relay they run on; commits of Claude sessions say "Open
Agent Relay"; Check Usage opens at once.

### Added

- **Archive conversations, and a context menu in the list.** Every
  conversation row has an **Archive** button beside Delete (and the chat's
  `⋯` menu an **Archive conversation** entry). An archived chat leaves the
  list and its session worker is stopped; the 🗄 toggle at the top of the
  sidebar shows the archived chats, each with **Unarchive**. A Claude Cloud
  chat's cloud session is archived and unarchived at Anthropic with it. A
  message into an archived chat (an agent's, say) brings it back by itself;
  the composer of an archived chat says so instead of sending. A right-click
  on a row — a long press on a phone — opens a menu with Open, Edit title,
  Stop turn, Kill session, Archive/Unarchive and Delete.
- **Commits made from OAR say so.** Claude sessions now end their commit
  messages with `Co-authored-by: Open Agent Relay (<model>) <no-reply@oar.sh>`
  — the model's name in the parentheses, e.g. `Claude Fable 5.1` — and pull
  request bodies with `🤖 Generated with [Open Agent Relay](https://oar.sh)`,
  instead of Claude Code's own lines. Settings → Providers → Claude has the
  choice: **OAR** (the default), **vanilla** (Claude Code's own attribution)
  or **off** (none); a repo folder can override it from the 🧠 context modal
  of any of its Claude sessions. A change reaches running sessions with their
  next message. Claude Cloud chats follow the same setting: the sandbox gets
  it with the first message and again when it changes, and in OAR mode the
  `Claude-Session` link is left out of the commit as well. Other providers
  are not touched: their tools have no such setting.
- **Claude Cloud (experimental): chats that run in a sandbox at Anthropic.**
  A new provider, off by default. A Claude Cloud chat runs Claude Code in
  Anthropic's cloud, on its own clone of a GitHub repository instead of on
  the relay host. It is billed to the Claude account the host's Claude CLI
  is logged in to, and its results come back as branches the agent pushes.
  Switch it on in Settings → Providers → Claude Cloud and choose the cloud
  environment and a default model there; it needs the Claude CLI logged in
  with a claude.ai account on the relay host, GitHub connected to that
  account, and the Claude GitHub app allowed on the repository.
  - **New Chat** fills **Repository** and **Branch** from the folder you
    pick, offers the repositories the Claude GitHub app can reach (the ones
    used here before first) and the branches of the picked repository, lets
    you type both, and warns about commits that are not pushed, changes
    that are not committed and a repository the app cannot reach.
  - **In the chat** the reply streams in live, the agent's questions and
    permission prompts arrive as cards, **Stop** interrupts the turn, images
    can be attached, and the model can be changed between turns. A line
    above the composer links the session on claude.ai and every branch the
    agent pushed to its comparison on GitHub, and shows the cost so far.
  - **Turns** survive a restart of the worker or the relay without sending
    your message twice. A turn stays open while the agent's background work
    runs, a turn the session starts by itself (after long background work,
    or from a message sent on claude.ai) appears as a reply of its own, and
    a turn refused at the usage limit is paused until the reset like a
    local Claude turn.
  - **Commits** follow the relay's attribution setting (see above), and
    deleting or archiving the chat archives the cloud session.
  - **Not there:** relay modes, a reasoning effort, steering during a turn,
    files other than images and anything else from the relay host, and the
    relay's tools and previews inside the sandbox.
  - **Before you switch it on:** the provider uses the Claude CLI's stored
    login to call the Anthropic API endpoints the CLI itself uses. These
    are not a documented public API and may change. OAR only reads the
    token: it never stores or logs it and sends it nowhere but to
    Anthropic; when it has run out, OAR lets the CLI refresh it. While the
    provider is off the relay fetches nothing with that login, except for
    cloud chats you created before. See "Claude Cloud" in the README.
    `scripts/claude-cloud-live-check.mjs` checks a running relay's Claude
    Cloud end to end.
- **Check Usage: live Claude limits and a Claude Cloud card.** With Claude
  Cloud switched on, the Claude card reads the 5-hour, weekly and per-model
  limits live from the account when you open the modal, instead of showing
  the reading of your last Claude turn, and a limit that is getting close
  carries Anthropic's warning. The card also shows which product the week
  went to (Claude Code, chats and the others) and the organisation's prepaid
  credits. The new Claude Cloud card shows the account's cloud credit (used,
  left, and when it expires) and what the cloud chats on this relay have
  cost as Anthropic reports it, in total and for the chat you opened the
  modal from; a credit that is offered but not yet claimed is mentioned with
  a link. The limit-reset vouchers (full reset, 5-hour reset) stay
  claude.ai-only: no endpoint the CLI's login reaches carries them, so the
  card links to the usage page instead. With Claude Cloud off, the Claude
  card is what it was.
- **Agent sessions: an agent can hand parts of its task to other sessions on
  the same relay.** The `remote_relay` tool now also works on the relay the
  agent runs on: it starts sessions there, waits for them and reads their
  replies, and each one is an ordinary conversation in your list. Off by
  default; switch it on in Settings → Relays → Agent sessions (**Agents may
  start and use sessions on this relay**). No relay has to be paired. The
  first time the agent of a conversation starts a session, a card asks you
  once for that conversation. At most 4 sessions it started may be at work at
  the same time, a session that an agent created cannot start sessions
  itself, and an agent cannot prompt, stop or archive the conversation it
  runs in. A session can use any provider that is enabled on the relay
  (image chats excepted); a Claude Cloud chat is started with the new `repo`
  and `branch` arguments, and an agent on a paired relay can now start a
  cloud chat here the same way. Sessions an agent starts use this relay's
  accounts, show **via agent** with the title of the conversation they came
  from in the sidebar and the header (a click opens it), and are never
  archived automatically. The new
  **Longest wait per tool call** slider in the same place sets how long one
  call may wait for a reply: 10 minutes as before, up to 60; it applies to
  waits on paired relays too. See "Agent sessions" in the README.

### Changed

- **Check Usage opens at once.** The modal shows the reading it showed last
  time immediately and reads only the open tab's provider live (the card says
  "updating…" meanwhile), instead of fetching every provider and showing a
  spinner until the slowest answered. A tab you switch to is read live when
  its reading is older than a minute; **Refresh** reads the open tab only.
  The relay keeps the last live answer of every provider, so the other cards
  stay filled (`GET /api/usage?providers=…`).
- **Windows: the relay no longer stops while it reads the process list.**
  Before every launch, every kill and every check of a worker that went
  quiet, the relay reads the list of all processes through PowerShell. It
  did so synchronously: half a second to a second and a half on an idle
  machine, several seconds under load, during which the relay answered
  nothing — under CI load that was enough for the tunnel to report 502 for
  the whole relay. The list is now read off the relay's thread; callers that
  ask at the same time share one read, and a read is reused for 1.5 s. A
  worker that went quiet is checked by its pid first and by the list last.
  `GET /api/status` → `sessionWorker.processList` shows when the list was
  last read, how long it took and whether it failed; a read slower than two
  seconds is logged. A kill or a workspace relaunch that cannot read the
  list answers 409 with the reason instead of failing with 500 or stopping
  blind.

### Fixed

- **A reply that names an error code is a reply.** A reply a session
  worker published was read as a failed turn when its one paragraph led up
  to "Error code: relay.…" — a short report on a test that had failed with
  that code, for instance. Workers always send the failure as a record, so
  their replies are no longer read for one; the page's fix buttons under a
  failure follow the same rule and no longer appear under a reply that
  mentions a code in a quotation, a code span, a table or a list.
- **Two prices in one paragraph were shown as a formula.** In a reply such
  as `this run cost $4.20, the earlier one $3.80`, everything between the
  two dollar signs was rendered as maths. A dollar sign now starts a formula
  only when it is written the way inline maths is: no space after the
  opening sign, no space before the closing one, and no digit right after
  it. Prices stay text; `$x^2$` is still maths. A wide inline formula also
  no longer makes the whole message scroll sideways: it stays inside the
  bubble and scrolls within itself.

- **Windows: no worker could start once an agent had run a command with an
  arrow or a bullet in it.** Before every launch the relay reads the list of
  all processes through PowerShell. Under the console's OEM code page
  PowerShell wrote characters such as `→` and `•` as control bytes, the list
  stopped being valid JSON, and every launch on the relay failed: each session
  you wrote to turned yellow after six tries and stayed so, for every
  provider, until the relay was restarted. The list is now written as UTF-8, a
  control byte that still gets through is tolerated, a list the relay cannot
  read no longer stops a launch, and a session whose launches were exhausted
  is tried once more every minute instead of waiting for a restart.
- **A session whose worker cannot be started now says so.** Until now such a
  session showed a yellow dot and nothing else. The relay now writes one note
  into the conversation — why the worker could not be started, in one
  sentence plus the cause — and keeps that same note up to date: it tries
  again every minute for ten minutes, then stops and the note gets a
  **Retry** button for once the cause is fixed; when the worker starts, the
  note says so. Your message stays queued the whole time, and a queued
  restart of the relay no longer waits for a message the relay has stopped
  trying to deliver.
- **The Claude usage card showed extra-usage credits 100 times too high.**
  Claude reports these amounts in the currency's minor units (cents), and
  the card showed them as dollars: 12.50 used of a 50.00 limit read as 1250
  of 5000. The percentage was right.

## [0.9.6] — 2026-09-29

Highlights: a Claude turn pauses at the usage limit and carries on after the
reset, a command that prints nothing no longer fails its turn, and agents on
paired relays can work with each other for a whole session.

### Added

- The relay keeps a copy of its console in `relay-console.log` in its log
  folder (`server/logs` unless you moved it), with a time stamp per line. The
  file is rotated at 5 MB and three older copies are kept. The relay's token is
  never written to it. Set `OAR_NO_CONSOLE_LOG=1` to switch it off.
- **Claude usage limit:** a Claude turn that runs into the subscription's
  usage limit is paused instead of failed. Your message closes with a note,
  the relay queues a message of its own ("continue where you left off") and
  sends it a minute after the limit resets. A banner above the composer says
  when, with **Resume now** and **Cancel**. The pause survives a relay
  restart. A reset more than six hours away (a weekly limit) waits for you to
  resume it.
- A banner warns in Claude conversations when the usage passes 90 % of a
  limit, with the time of the reset, and can be hidden until the next one.

### Changed

- **Remote relays:** agents on paired relays can now work with each other for
  a whole session. A relay you mentioned stays in reach whatever prompts arrive
  later, and a relay whose agent wrote to a conversation is open to it, so the
  agent that was asked can answer, ask back or report later. Until now a prompt
  from another relay's agent took the tool away for the rest of the turn, also
  in a turn you had started yourself. A prompt that crossed two relays is still
  not passed on to a third relay you did not unlock; reading and waiting are no
  longer limited. After 30 prompts to other relays' agents without a message
  from you, the next one asks you first.
- Subagents are folded to their header inside the running turn, so a turn with
  many of them stays short to scroll. The header says how many steps the
  subagent has taken and shows the latest one, as far as there is room. A tap
  on the header unfolds a subagent and folds it again; what you chose by hand
  stays. The Stop button of a finished subagent is gone.
- **Copilot, Cursor, Grok:** how long a turn may stay quiet now depends on what
  the agent is doing: 2 minutes with nothing in flight, 5 minutes during a model
  request, 30 minutes while a tool runs. Until now every silence of 2 minutes
  failed the turn, including a command that simply printed nothing. The failure
  note says how long the silence was and what was running. The three windows can
  be set with `OAR_TURN_STALL_IDLE_MS`, `OAR_TURN_STALL_MODEL_MS` and
  `OAR_TURN_STALL_TOOL_MS`.
- **Copilot:** a turn kept past 2 minutes of silence is checked: a runtime that
  no longer answers fails the turn at once. The watchdog also covers the start of
  a session, where a runtime that did not answer used to hold the message without
  any failure, and a Stop during that start now takes effect at once.
- **Cursor:** a stalled run is cancelled, so the next message does not wait for
  it.
- A plan board is part of the reply it belongs to: its content and its buttons
  are shown inside the agent's reply bubble (inside the running turn's bubble
  until the reply is there) instead of on a card of their own at the end of the
  transcript. When the reply already is the plan, the plan is shown once, with
  the buttons below it. After you chose, the plan stays and the bubble says
  what was chosen.
- The choice on a plan board also sets the mode of the session: after
  "Implement in autopilot" the composer says Autopilot, after "Stop here and
  prompt myself" it says Agent, and the next message carries the work on. The
  session used to stay in Plan, so the next message planned again. "Stop here"
  leaves the mode as it is.

### Fixed

- A session worker that was idle and whose process has ended (stopped from
  outside, or crashed between turns) is no longer listed as ready with its old
  process id. The next message started a new worker before as well; the list
  now says what is there, about a minute and a half after the process ended.
- A reply that quotes or describes a failure note (an agent reporting on a
  failed test, for example) is no longer taken for a failure itself. The relay
  cut such a reply off at the quoted error code, put its own advice behind it
  and stored the turn as failed. Where a failure is known from its text alone,
  that text is now kept whole.
- A single `~` in a reply ("about", as in "~35 s … ~7 min") no longer strikes
  out the text up to the next one. Strikethrough needs two tildes on each side:
  `~~text~~`. The page now names the version of its Markdown library (15.0.12,
  the one it was served until now) instead of taking whatever the CDN hands out.
- **Copilot:** a command the agent had started no longer runs on after its
  runtime is gone. When the runtime died or was killed in the middle of a turn,
  the turn failed as it should, but the command kept running with nobody left
  to read its result. The relay now stops what such a runtime leaves behind
  (first gently, then by force), and the failure note says that a command that
  was still running is being stopped, as far as that is known at that moment:
  the note comes at once and does not wait for the list of processes. A command
  the agent started to outlive the session (`detach`) is left alone, and so are
  the commands of a runtime that shuts down in good order.
- **Grok:** a quiet-turn window that is set for the model request or for a tool
  is kept when the shared watchdog is switched off by the environment, and the
  watchdog looks often enough for the shortest window that is set.
- **Windows:** stopping or deleting a session could end the worker of another
  session as well. Windows hands the process id of an ended process out again,
  and a worker whose creator was long gone could so look like a child of the
  session that was being stopped. A process older than its parent is no longer
  taken for its child, and the worker of another session never is.
- **Copilot:** a message sent while a command is running no longer cuts the
  command short. It waits until the running tool call has ended and is steered
  in then; the composer reads "Queue" for as long. Sent at once, the message
  made the runtime push the command to the background: the turn ended early
  with the answer to the new message alone, or failed with "No response was
  returned", and the command's result arrived later in a turn of its own.
- A plan board's action ("Implement in autopilot" and the like) queues its
  follow-up again. It failed with an error and left the board marked as acted
  on, without the message that carries the plan out.
- A finished subagent no longer turns back to "Running" in the running turn
  when another line of activity arrives or the page reloads.
- **Previews:** the first page load after a dev server was restarted no longer
  ends in "The upstream connection failed: ECONNRESET". The preview lane keeps
  its connections to the dev server, and the one it had kept was closed by the
  restart. A request without a body is now sent once more on a connection of
  its own: it reaches the restarted server, or the page says that nothing is
  listening on the port.
- Links in the file viewer and in plan boards have a colour that can be read in
  dark mode; they used the browser's default blue.
- A question or approval card no longer answers itself when the relay is
  briefly unreachable, for example while it restarts. One failed check used to
  end the wait: the agent was told to use its own judgement, or the approval
  was refused, while the card stayed on screen. The wait now lasts through the
  outage, for every provider.
- **Copilot:** a turn that fails is reported at once. The failure used to wait
  for the runtime to finish stopping, which took three minutes on a runtime that
  had stopped answering; stopping it is now bounded, and the next message waits
  for it before a new runtime starts.
- **Copilot:** a failed turn keeps what the agent had written, above the failure
  note. The note says how many tool calls ran and that their changes are still in
  place, and it no longer advises restarting the relay. A message steered into
  the failed turn gets a short marker with Resend instead of a second copy of
  the failure.
- **Copilot:** a slow relay no longer makes a busy runtime look silent.
- **Copilot:** a reply the relay could not take (it was restarting, the
  connection was reset) is offered again for up to five minutes. One failed
  attempt used to put the message back in the queue, which threw the finished
  answer away and ran the prompt a second time. A reply that cannot be saved at
  all ends as a failure that says so, and nothing is run twice.
- **Copilot:** what the agent does by itself after a background task (a
  continuation) is kept while the relay restarts. Its row was given up after
  about a second, and the whole reply with it.
- **Copilot:** a relay that accepts live output and does not answer no longer
  holds the turn: a live update waits 10 seconds at most, and after one that
  ran out, live updates are skipped for 30 seconds. Tool lines skipped then are
  missing from the transcript; the reply is not affected.
- **Copilot:** Stop works on a turn that is stuck. A runtime that does not
  answer the Stop within 10 seconds is ended with the turn, and a turn the user
  stopped ends as stopped even when the runtime died or went silent after it.
- **Copilot:** a runtime that exits, or stops answering, while the agent works
  by itself after a background task is cleared away; the next message used to
  fail on it. A lost connection during a model change is reported as that, not
  as "pick another model".
- **Copilot:** a failed turn tells its failure once. A message sent into the
  turn that the agent had not started on gets the short marker with Resend,
  like one it had taken in.
- **Copilot:** background agents and shells are no longer closed with the
  runtime because its list of them could not be read.
- **Stop button:** Stop keeps working for a message that waits on a question
  card while the agent starts and finishes other work, and for a second message
  stopped while the same turn goes on. It used to do nothing in both cases.
- **Copilot:** a turn the runtime itself ended with an error no longer tells
  you to restart the relay. The worker starts a fresh runtime by itself; a
  restart repairs nothing and costs every other running session.
- The note under a failed turn no longer advises restarting the relay: a restart
  repairs nothing there and interrupts every other running session. It now says
  to send the message again and to include the error code in a report.
- **Claude:** a turn the Claude account refuses (billing, an expired sign-in,
  access the organisation has switched off) says to check the account or to sign
  in again in Settings → Providers → Claude, with a button that opens the panel,
  instead of "Retry the message". Its error code names the failure, for example
  `relay.claude-billing-error`; it used to read `relay.claude-success`.

## [0.9.5] — 2026-09-27

Highlights: a Claude reply on the wrong row no longer blocks the conversation,
workers on Windows write a log, and agents can set the effort of a session on
another relay.

### Added

- **Remote relays**: an agent can set the reasoning effort of a session it starts or
  prompts on another relay (`effort` on `create_session` and `send`), and `relay_info`
  lists the efforts each model there takes.

### Changed

- Session workers on Windows now write the same `worker-<session>.log` as on
  Linux, in the relay's log directory; the console window keeps showing the
  output.

### Fixed

- **Remote relays:** waiting for a remote agent's reply no longer stops at an
  interim answer ("still waiting for …") while the agent goes on in a turn of
  its own without a background task, for example while its subagents work.
- A Claude reply published as a *background continuation* instead of under
  your message no longer blocks the conversation for up to 5 minutes: after
  about 45 seconds of silence your message closes with a small muted note
  naming that reply, and the next message runs normally. The note sends no
  second notification, and an agent on a paired relay receives the reply
  itself, not the note. A stray re-init of the Claude CLI between turns no
  longer sends the reply there in the first place.

## [0.9.4] — 2026-09-27

Highlights: remote relays (pair OAR relays and let an agent on one work on the
other), follow toggles for the running reply, and a Suspend host that waits for
agents, background tasks and CI to finish.

### Added

- **Remote relays**: pair OAR relays in **Settings → Relays** by pasting the other
  relay's web address; the pairing works both ways. Agents of every provider get a
  `remote_relay` tool to list and read the other relay's sessions, prompt one, start
  a new one, wait for its reply and relay its questions to you. A relay unlocks in a
  conversation only once you mention it (`@` picks it from a list). Per relay, agents
  may read, prompt or do everything; in ask and plan mode each write action asks you
  first. The receiving relay marks such messages with a **↗ from …** badge. Tokens
  stay on the relay; agents never see them.
- **OAR MCP server** (`server/mcp/oar-mcp-server.mjs`): Grok and the Copilot
  extension engine now get the `preview` and `remote_relay` tools as real tools
  instead of instruction text.
- **Follow the live reply**: three toggles in the running reply's header keep
  its thoughts, its answer or its tool list pinned just above the composer as
  the reply grows. Scrolling the transcript by hand turns it off; typing in the
  composer does not. The choice is remembered per conversation until the page
  reloads.
- A queued relay restart shows a banner on every client, with a **Cancel**
  button (a new localhost-only cancel endpoint behind it).

### Changed

- **Suspend host waits for the work to finish.** Instead of sleeping the
  machine at once, the relay queues the suspend and fires it after 2 minutes
  with nothing active: no queued, running or parked turn, no background task of
  a live worker, and no open GitHub Actions run in a busy conversation's
  repository (read through `gh`; a repository whose state cannot be read blocks
  for 15 minutes, then is ignored). With nothing running at confirm time it
  fires after a 30-second countdown. The confirmation lists what is still
  active, every client shows a banner with the blockers or the countdown and a
  **Cancel** button, and a push notification reports the suspend, or its drop
  when the relay restarts. Suspend remains Windows-only;
  `OAR_HOST_SUSPEND_DRY_RUN=1` logs instead of suspending.
- **Stop** asks for confirmation before cancelling the running turn.
- The **Install app name** setting is now called **Relay name**. Your value is
  kept. It still labels the installed app, and it is the name paired relays
  know this relay by; when it is empty, paired relays see the machine's
  hostname.
- Dependency updates clear all five `npm audit` findings, one of them rated
  high: express 4.22.3, body-parser 1.20.8, qs 6.16.0, fast-uri 3.1.8 and
  hono 4.13.9. All were inside the declared ranges, so `package.json` is
  unchanged; a git checkout gets them with `npm ci`.

### Fixed

- Switching the Claude model in a conversation that already had replies no
  longer strands the next message. The reply to the message that carried the
  switch landed on a *continuation* row while the message itself stayed
  processing, and every later message waited behind it for up to 5 minutes, or
  until the session was killed.
- **Requests on an idle connection are no longer reset.** The relay closed a
  kept-alive connection after 5 seconds without traffic, so a request sent on
  it a moment later could be reset before the relay read a byte. Behind a
  reverse proxy or tunnel that pools its connections this showed up as an
  occasional 502, and a send was not repeated. Idle connections now stay open
  for 65 seconds.
- After a send, the transcript stays at the newest message when the composer
  grows (a session note or a warning banner appearing under it). It used to
  slip up until the draft was saved.

## [0.9.3] — 2026-09-27

Highlights: mid-turn steering on Claude and Copilot, Copilot background tasks on
par with Claude's, multi-select question cards, drafts that stay safe across
devices, and a sidebar title filter.

### Added

- **Mid-turn steering** for Claude conversations: messages sent while a turn is
  running are pushed into the live turn (any number of them — Claude Code
  parity) instead of queueing behind it. Folded messages settle with a compact
  *merged* marker; a message the CLI answers on its own turn gets its real
  reply as before.
- A **conversation title filter** above the sidebar list. Case-insensitive,
  clears with × or Escape, and automatically loads older pages while active so
  it searches every conversation, not just the loaded ones.
- **Resend** on a steered message that was cut off by **Stop**: the marker
  reads *Stopped with the turn — not answered* and one tap sends the original
  text and attachments again. Each original can be resent once (again only if
  that Resend was cancelled or failed), and the button reads **Resent** on
  every device and after reloads.
- The running reply's **Stop** header stays pinned at the top of the chat
  while a long reply scrolls, so it is always in reach on a phone.
- Background subagents in the task panel show the **model they actually run
  on** (e.g. *Opus 5.5*) beside their kind pill, and the **command they are
  running right now** with its tool emoji (e.g. *🔧 Tool (Bash): npm test*),
  cropped to the row — instead of *using Bash · opus*.
- **Mid-turn steering for Copilot conversations** (SDK engine, hosted and
  BYOK alike, every Copilot model): a message sent while a turn runs is
  pushed into the live turn. The runtime folds it in at the next tool boundary
  (the message settles with the same *merged* marker as on Claude, and the
  reply continues in the running bubble) or, when the model is mid-sentence,
  answers it right after on its own bubble. The composer reads **Steer** for
  these conversations; while a question card, approval, elicitation or a
  compaction is open it reads **Queue** and the message waits in the queue,
  cancellable, then steers in one round-trip after the answer. **Stop** now
  ends only the reply — background agents the turn started keep running under
  their own Stop — and messages pushed just before the Stop settle as *Stopped
  with the turn — not answered* with **Resend**, exactly as on Claude.
- Messages steered into a running turn (Claude and Copilot) carry a short
  hidden note telling the model to handle them in addition to the request it
  is working on. In testing, a model that received a steer before its first
  output answered only the new message and dropped the original request.
- The newest message you steered into a running Copilot turn keeps its
  **Cancel** until the runtime picks it up: cancelling pulls it back out of the
  runtime, so it is never answered. (Copilot's queue can only give back its
  newest waiting message, so older ones lose Cancel once pushed, as on Claude.)
- **Background tasks for Copilot conversations** now match Claude's: background
  agents the model spawns and detached shells appear in the task panel as they
  run, with the agent's kind, the model it runs on, the command it is running
  right now, its token count and a **Stop** that works (the runtime's task
  registry). A reply no longer waits for a background agent to finish; the
  agent's result arrives afterwards as its own *continuation* reply, and the
  relay's *Background task timeout* slider now governs Copilot tasks too
  (expiry stops them instead of silently forgetting them).
- **Multi-select question cards.** A question that allows several answers
  renders checkmarks instead of one-shot buttons, plus one **Reply with
  selection** button; the reply lists every ticked choice and whatever you
  typed in addition. Previously the first click answered the whole card.
  Claude cards follow Claude's own `multiSelect` flag. Copilot models get the
  relay's own `ask_user` tool, which adds a `multi_select` field to the
  runtime's (the built-in has no way to say "pick several"); a question worded
  "select all that apply" counts too, and every Copilot choice card has a
  **Select several** switch for the cases a model forgets to flag.

### Changed

- Copilot replies settle when the main agent goes idle rather than when the
  whole session does, so a background agent the turn spawned no longer keeps
  the reply's bubble spinning for as long as it runs; the agent's own
  follow-up arrives as a *continuation* reply, as on Claude. A background agent
  that asks a question, or in ask mode asks to run something, after the reply
  settled gets its card on a row of its own; approvals follow the
  conversation's current mode, not the one the session started in. A
  follow-up turn that ends with nothing to show (no text, and no activity
  beyond the background agent's own bookkeeping, such as its granted
  permissions) no longer leaves an empty "completed without a text reply"
  row. Messages
  are sent to the Copilot runtime as `immediate` throughout — the only mode
  that never strands a message behind background work.

- The background task panel scrolls when it holds more tasks than fit,
  instead of growing past the window — in phone landscape too, where the
  composer now always stays on screen.
- The **Background task timeout** setting defaults to **4 hours** instead of
  no limit, for Claude and Copilot alike: long-running work still finishes,
  but a forgotten shell or agent no longer keeps its session alive forever.
  A value you already chose (including *No limit*) is kept.
- Rotating a phone to landscape keeps the portrait text size instead of
  jumping to the larger desktop size, and opening the on-screen keyboard
  no longer shrinks it.
- Rotating a phone (or resizing the window, or opening the keyboard) keeps
  your place in the transcript: a reader at the end stays at the end, and
  mid-history the same message stays at the top of the screen. Previously
  the reflow left you screens above where you were, usually further back.
  A question card's reply box you are typing in stays in view when the
  keyboard opens.

- The composer's send button no longer turns into **Stop** while a turn runs.
  It reads **Steer** when a Claude turn is live and text is drafted, and stays
  disabled on an empty composer. While steering is held (open question card or
  plan approval, compaction) it stays enabled and reads **Queue**, with the
  reason in its tooltip: the message waits as an ordinary pending bubble (with
  **Cancel**) and steers into the turn once the card is answered or the
  compaction ends. Stopping lives where the work is: the running reply's bubble
  carries **Stop**, and queued messages carry **Cancel** until they are picked
  up. The label follows the worker within about a second, including right
  after a worker starts or the relay restarts.
- Queued messages are claimed strictly in send order, including messages that
  went through a delivery retry. Previously a retried message could starve
  behind newer ones for minutes in an active conversation, or run after them
  so replies arrived out of order.
- `@anthropic-ai/claude-agent-sdk` 0.3.283 (bundled Claude Code 2.1.283),
  which makes **Claude Opus 5.5** (`claude-opus-5-5`, plus its 1M-context
  variant) discoverable. Model discovery runs the SDK's bundled CLI, so
  `claude update` alone never surfaces a new Claude model.
- `@github/copilot-sdk` 1.0.14: typed message provenance (human vs. system vs.
  agent senders), a `fast` auto-routing tier, a `managedSettings.clearCache`
  RPC, and streaming-throughput fixes.
- `@cursor/sdk` 1.0.32 (additive API changes only).
- The npm package no longer ships test fixtures and test harnesses.
- The README is reorganized around installing and using OAR; relay internals
  and development notes moved to DEVELOPING.md.

### Fixed

- **Deleting a conversation no longer leaves its session running.** Its
  worker used to keep running (about 40 MB each, holding its workspace
  folder) until the relay restarted, and Claude and Copilot SDK
  conversations never had their CLI session removed. Delete now stops the
  worker first, removes the CLI session (Copilot through the SDK, Claude by
  deleting that session's transcript) and the conversation at once. A
  Copilot session the relay only imported from the host, and never ran, is
  hidden instead: its CLI session stays, and no process is stopped for it. A
  conversation that is still working, with a running turn or live background
  tasks, is not deleted: the sidebar says why, so you can stop it first (a task
  without a Stop button: use **Kill session**). A worker that a failed turn
  marked as errored counts as working for as long as its process runs.
- A message starting with "/" (other than the relay's own `/compact` and
  `/preview`) that reached a Claude conversation between turns ran as a Claude
  CLI command instead: Claude never saw the text, and the next reply was
  pinned to that message. It now reaches Claude as the text you typed, as the
  composer's "send again to send as text" promises.
- A Copilot turn that ended in `task_complete` after streaming a fragment
  published the fragment as its final reply; the completion summary now takes
  precedence in the saved reply too, not only in the live stream.
- A background subagent's own events could open a spurious *continuation*
  reply after the Copilot turn that spawned it had settled, and its
  session-level model notice could confuse a pending model switch.
- Switching the model, mode or effort between turns no longer strands the
  next message: the Claude CLI re-initialises after such a change, and the
  worker took that for a turn it opened on its own, so the reply landed on
  a *continuation* row while the message itself stayed processing with an
  empty Stop bubble (until the session was killed). A background task's reply
  that arrives during such a switch still gets its own row instead of
  trading places with the message's reply.
- A steered message could wedge its conversation indefinitely when a
  long-running (or hung) background task was alive: the merge settle waited for
  a full idle that never came, keeping every later message stuck behind it.
  Background tasks no longer block the settle; delivery watchdogs share the
  same rule.
- **Drafts no longer get overwritten across devices.** The draft version check
  never actually ran, so an idle device re-saved its stale (usually empty)
  composer over another device's draft every few seconds. Saves are now
  version-checked and happen only on real edits; a device you are not typing
  on picks up the other's draft, and when two devices edit at once the newest
  keystroke wins and the other text is offered back with **Restore** (and
  **Undo**).
- Other ways to lose a draft are fixed too: text typed or attachments added
  while a send was in flight, switching conversations mid-send or before the
  next draft loaded, reopening the open conversation (which dropped its draft
  attachments), a failed send overwriting what was typed meanwhile, and drafts
  over 20,000 characters re-saving in a loop (the saved part is now cut
  cleanly, and the composer says the rest is not saved).
- A steered message could run twice: Claude had already answered it, but the
  relay's recovery (or a worker crash or restart) delivered it again. Steered
  messages are now handled at most once: if the outcome cannot be recorded, the
  message ends with an error telling you to check the reply above rather than
  being run again.
- A message sent while a question card was open or the conversation was
  compacting could stall for many seconds, or be pushed past the open card.
  Question cards are never bypassed or dropped by steering: such messages wait
  and steer in once the card is answered.
- **Stop now really stops** when steered messages were already pushed into the
  turn. The Claude CLI otherwise opened a follow-up turn for them that re-ran
  the stopped tool call; those messages now show *Stopped with the turn — not
  answered* with **Resend** instead of claiming they were handled. A stopped
  message is also no longer re-run if its worker dies right after the Stop.
- After a reload, a message sent after a folded steer is no longer styled as
  steered with its reply merged into the previous turn, and older replies stay
  anchored under their own messages.
- The live reply bubble no longer carries over into another conversation or
  drops below later steered messages, and the live bubble and **Stop** follow
  the turn that is actually producing output. A finished background task's
  answer is no longer lost when a queued message takes over the turn.
- A screenshot-only send no longer vanishes from the transcript before its
  message lands.
- Sidebar filter: keeps searching older pages while the list is busy loading,
  matches accented titles regardless of how they were typed, no longer zooms
  the page on iOS, and announces the match count once to screen readers
  (saying when only loaded conversations were searched).
- A background agent's failure no longer vanishes: a Copilot follow-up turn
  that carries the failure line, but has nothing else to show, keeps its row.
- The **Background task timeout** slider no longer reads *No limit* while the
  settings load; it starts at the 4-hour default.
- Settings calls the SDK engine the Copilot default (it still read
  *experimental*, with Extension as *current*), and the background task
  timeout help names Copilot's background agents and shells.
- `oar` probed a different port than the one it started the relay on when the
  config's `port` was not 3333 or differed from `--port`, then killed the relay
  it had just started after 20 seconds. It now uses one port: `--port` for that
  run, else the config's. `--help` no longer lists `--token`, which the
  launcher never read.
- Concurrent conversation deletes could start extra Copilot runtimes that were
  never shut down.
- Question cards said *Copilot question* in every conversation; they now name
  the runtime that asks (Claude, Copilot, Cursor, OpenAI).

## [0.9.2] — 2026-09-13

### Added

- **Opt-in self-update**: a settings toggle for automatic update checks (off by
  default; the relay never contacts oar.sh unless you opt in), plus a manual
  check button. `OAR_NO_UPDATE_CHECK=1` disables even manual checks.
- Agents can embed **images, video and audio inline** in replies by bare
  absolute path, on every provider. Clicking an embedded image opens the file
  viewer with zoom, download and copy.
- **Screenshot annotations**: mark up uploaded screenshots with highlighter
  strokes before (or after) sending; the original upload is never modified.
- A **Features** settings tab; feature flags now live in the database (the
  `config.json` `features` key is migrated once and removed; env vars still win).
- Windows **system-startup autostart** mode (a boot-triggered scheduled task,
  one UAC confirmation) alongside the existing at-sign-in mode.
- `config.json` can carry the data directory.

### Changed

- New Copilot conversations default to the **SDK engine** when the relay can
  run it, falling back to the CLI extension otherwise; an explicitly stored
  setting always wins.
- The Copilot model catalog shows each model's real reasoning efforts and
  context window, in a canonical order.
- Model labels are compact enough for phone composers (`Fable 5.1`,
  `Haiku 4.5`), and the composer placeholder names the selected model's family.
- Claude models stay in the picker when Anthropic rotates the advertised
  lineup; `@anthropic-ai/claude-agent-sdk` 0.3.261 (Fable 5.1 model floor).
- Global installs run from the OAR state root, so self-update can replace the
  package directory on Windows.
- Transient relay notices render as an opaque toast instead of bleeding
  through modals.

### Fixed

- A custom PWA app name no longer reverts on Android: the relay serves it in
  the manifest instead of a browser-local override.
- Queue writes are fenced to one processing attempt, so a superseded delivery
  can no longer overwrite a settled message.
- Live-bubble rendering: cross-conversation teardown, muted streams after
  enqueue, a live-poll deadlock and anchor theft.
- A queued send's own text no longer reappears in the composer.
- Copilot turns ending in a bare `task_complete` tool call show the real
  summary instead of a cut-off fragment; detached Copilot shells appear as
  background-task cards.
- Copilot worker and server-side session ownership hardening (continuations,
  questions, shutdown, rekeying, worker kill verification).

## [0.9.1] — 2026-09-02

### Fixed

- The globally installed `oar` command silently did nothing: npm's bin symlink
  names `argv[1]` plain `oar`, which the `oar.js` entrypoint check never
  matched. The guard now resolves real paths (and ships the executable bit).

## [0.9.0] — 2026-09-02

### Changed

- **The project is now OAR — Open Agent Relay** (`@oar-sh/oar`, binary `oar`,
  home at [oar.sh](https://oar.sh)). It began life as `copilot-remote` and has
  long outgrown the name: one relay now drives Copilot (CLI extension or SDK
  engine), Claude (Agent SDK), Cursor, Grok, and OpenAI BYOK.
- Global installs keep their state in `~/.oar` (`%APPDATA%\oar` on Windows) so
  `npm i -g` updates can never touch the database. Existing state from a git
  checkout or the legacy managed config dir is migrated in once, copy-never-move,
  with WAL checkpointing and integrity verification; the old tree is left behind
  as the rollback path. Git checkouts keep their repo-local layout unchanged.
- The published npm package now contains only the runtime (an explicit `files`
  allowlist); tests, docs, and screenshots stay in the repo.

### Added

- `oar --migrate-from <old-checkout>` imports relay state from a pre-rename
  checkout into `~/.oar`.