@oar-sh/oar 0.9.20 → 0.9.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/CHANGELOG.md +325 -0
  2. package/README.md +13 -13
  3. package/bin/oar.js +2 -2
  4. package/package.json +1 -1
  5. package/server/claude-cloud-worker/claude-cloud-session-process.mjs +231 -1
  6. package/server/claude-cloud-worker/claude-cloud-session-worker.mjs +31 -2
  7. package/server/claude-cloud-worker/claude-cloud-worker-steering.mjs +34 -0
  8. package/server/claude-worker/claude-sdk-adapter.mjs +149 -4
  9. package/server/claude-worker/claude-session-process.mjs +16 -4
  10. package/server/claude-worker/claude-session-worker-main.mjs +18 -2
  11. package/server/copilot-worker/copilot-sdk-session-process.mjs +75 -1
  12. package/server/copilot-worker/copilot-sdk-session-worker.mjs +18 -2
  13. package/server/cursor-worker/cursor-session-worker.mjs +21 -2
  14. package/server/cursor-worker/cursor-worker-steering.mjs +20 -0
  15. package/server/db-schema.mjs +19 -0
  16. package/server/grok-worker/grok-background-tasks.mjs +10 -0
  17. package/server/grok-worker/grok-session-worker.mjs +22 -2
  18. package/server/grok-worker/grok-turn-runner.mjs +4 -0
  19. package/server/grok-worker/grok-worker-steering.mjs +23 -0
  20. package/server/migrations/0037-relay-asks-queue-id.mjs +66 -0
  21. package/server/migrations/0038-turn-seq.mjs +92 -0
  22. package/server/public/app/about-ui.js +1 -1
  23. package/server/public/app/about-view.mjs +19 -0
  24. package/server/public/app/action-confirmations.js +1 -1
  25. package/server/public/app/activity-replay-state.mjs +160 -42
  26. package/server/public/app/api-client.js +11 -2
  27. package/server/public/app/ask-user-view.js +24 -9
  28. package/server/public/app/attachments-view.js +1 -1
  29. package/server/public/app/back-guard.mjs +31 -5
  30. package/server/public/app/bootstrap.js +618 -72
  31. package/server/public/app/chat-actions-menu-state.mjs +141 -4
  32. package/server/public/app/chat-header-meta.mjs +27 -0
  33. package/server/public/app/claude-billing-toggle-ui.js +2 -1
  34. package/server/public/app/claude-cloud-conversation-ui.js +2 -2
  35. package/server/public/app/composer-settings-label.mjs +90 -0
  36. package/server/public/app/composer-settings-ui.js +442 -0
  37. package/server/public/app/context-menu.mjs +36 -5
  38. package/server/public/app/context-usage-view.mjs +68 -0
  39. package/server/public/app/conversation-deep-link.mjs +15 -11
  40. package/server/public/app/conversation-group-drag.mjs +12 -2
  41. package/server/public/app/conversation-group-order.mjs +9 -2
  42. package/server/public/app/conversation-group-state.mjs +47 -10
  43. package/server/public/app/conversation-origin-ui.js +17 -1
  44. package/server/public/app/conversation-view.js +309 -49
  45. package/server/public/app/cwd-menu-placement.mjs +18 -6
  46. package/server/public/app/cwd-picker.js +36 -6
  47. package/server/public/app/emoji-view.js +1 -1
  48. package/server/public/app/font-scaling.js +26 -0
  49. package/server/public/app/header-priority-actions.mjs +238 -0
  50. package/server/public/app/held-details.mjs +202 -0
  51. package/server/public/app/hidden-messages-view.mjs +4 -0
  52. package/server/public/app/inbox-ui.js +88 -81
  53. package/server/public/app/inbox-view.mjs +35 -19
  54. package/server/public/app/journal-view.js +328 -58
  55. package/server/public/app/live-activity-cap.mjs +103 -0
  56. package/server/public/app/phone-landscape-ui.js +225 -0
  57. package/server/public/app/phone-landscape.mjs +99 -0
  58. package/server/public/app/phone-typing-state.mjs +104 -0
  59. package/server/public/app/pinned-messages-view.mjs +5 -0
  60. package/server/public/app/push-settings.js +2 -2
  61. package/server/public/app/pwa-install.js +24 -3
  62. package/server/public/app/questions-panel-ui.js +634 -0
  63. package/server/public/app/questions-stub-view.mjs +40 -0
  64. package/server/public/app/questions-view.mjs +89 -0
  65. package/server/public/app/relay-board-view.js +12 -2
  66. package/server/public/app/relay-event-subscriptions-ui.js +71 -22
  67. package/server/public/app/relay-event-view.mjs +38 -0
  68. package/server/public/app/relay-restart-ui.js +2 -2
  69. package/server/public/app/relay-schedule-view.mjs +32 -6
  70. package/server/public/app/relay-schedules-ui.js +69 -20
  71. package/server/public/app/relay-thoughts.mjs +15 -1
  72. package/server/public/app/remote-relay-origin-view.mjs +41 -23
  73. package/server/public/app/session-history-ui.js +251 -0
  74. package/server/public/app/session-history.mjs +599 -0
  75. package/server/public/app/settings-modal.js +67 -6
  76. package/server/public/app/settings-tabs.js +12 -0
  77. package/server/public/app/share-state.js +1 -1
  78. package/server/public/app/shared-conversations-settings.js +2 -2
  79. package/server/public/app/socket-handlers.js +47 -13
  80. package/server/public/app/status-view.mjs +18 -2
  81. package/server/public/app/store.js +87 -16
  82. package/server/public/app/stream-view-preference.mjs +60 -0
  83. package/server/public/app/subagent-fold.mjs +6 -3
  84. package/server/public/app/subagent-timeline-view.mjs +12 -5
  85. package/server/public/app/subagent-viewer.mjs +35 -8
  86. package/server/public/app/tab-strip-fade.mjs +101 -0
  87. package/server/public/app/tap-actions.js +66 -0
  88. package/server/public/app/thoughts-preference.mjs +79 -0
  89. package/server/public/app/turn-stream-view.mjs +124 -0
  90. package/server/public/app/usage-limit-ui.js +63 -2
  91. package/server/public/app/viewer-history.mjs +13 -2
  92. package/server/public/index.html +2603 -1767
  93. package/server/relay-tools.md +1 -0
  94. package/server/repositories/message-repository.mjs +33 -0
  95. package/server/repositories/question-repository.mjs +51 -7
  96. package/server/repositories/relay-turn-rows.mjs +56 -0
  97. package/server/repositories/session-repository.mjs +19 -0
  98. package/server/routes/ask-user-routes.mjs +34 -5
  99. package/server/routes/claude-cloud-routes.mjs +19 -0
  100. package/server/routes/messages-routes.mjs +92 -15
  101. package/server/routes/relay-ask-routes.mjs +15 -5
  102. package/server/routes/sessions-routes.mjs +117 -12
  103. package/server/server-runtime.mjs +367 -170
  104. package/server/services/about-service.mjs +22 -53
  105. package/server/services/claude-billing-swap-service.mjs +7 -20
  106. package/server/services/claude-cloud-session-service.mjs +47 -5
  107. package/server/services/cloudflared-tunnel-service.mjs +7 -0
  108. package/server/services/idle-timers.mjs +184 -0
  109. package/server/services/queue-wake-hook.mjs +145 -0
  110. package/server/services/queue-waker.mjs +127 -0
  111. package/server/services/relay-ask-service.mjs +64 -5
  112. package/server/services/relay-event-service.mjs +4 -1
  113. package/server/services/remote-relay-dispatcher.mjs +84 -18
  114. package/server/services/session-activity-tracker.mjs +1 -1
  115. package/server/services/session-history-refresh-service.mjs +4 -0
  116. package/server/services/session-launch-mutex-service.mjs +23 -0
  117. package/server/services/session-worker-registry-service.mjs +36 -0
  118. package/server/services/session-worker-supervisor-service.mjs +36 -0
  119. package/server/services/session-worker-websocket-service.mjs +183 -31
  120. package/server/services/ssh-tunnel-manager-service.mjs +7 -0
  121. package/server/services/worker-recycle-service.mjs +772 -0
  122. package/server/services/worker-replacement.mjs +197 -0
  123. package/shared/ask-user-bridge.mjs +9 -6
  124. package/shared/build-id.mjs +128 -0
  125. package/shared/relay-asks.mjs +10 -0
  126. package/shared/relay-events.mjs +1 -0
  127. package/shared/worker-runtime/heartbeat.mjs +31 -0
  128. package/shared/worker-runtime/simple-steering-state.mjs +38 -0
  129. package/shared/worker-runtime/worker-keepalive.mjs +108 -0
  130. package/shared/worker-runtime/worker-websocket-link.mjs +44 -10
  131. package/shared/worker-runtime/working-directory.mjs +1 -1
package/CHANGELOG.md CHANGED
@@ -3,6 +3,331 @@
3
3
  All notable changes to OAR are documented here. The format follows
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow semver.
5
5
 
6
+ ## [0.9.22] — 2026-11-05
7
+
8
+ Highlights: questions that don't block move into the chat header. A **📓**
9
+ icon with a count opens them, urgent ones first, and the turn that asked
10
+ keeps one line, "📓 Question for you", in place of the card. Settings →
11
+ General reads as five headed sections, with three new choices: whether
12
+ thoughts start open while an agent works, how many tool lines a working
13
+ agent shows, counted per agent so a busy sub-agent no longer pushes out the
14
+ main agent's lines, and a stream view that shows a turn's thoughts and tool
15
+ calls in the order they happened. While a turn runs, the page's poll leaves
16
+ out what it already holds, and an idle relay and its workers wake far less
17
+ often. Claude sessions now ask you in a **Safety check** card, which only
18
+ you can answer, before removals and other asks Claude Code marks as
19
+ person-only.
20
+
21
+ ### Changed
22
+
23
+ - **📓 Questions in the chat header.** A conversation's questions that don't
24
+ block sit behind a 📓 icon with a count, first among the header icons (in
25
+ ︙ when the header runs out of room). It opens a list under it with the
26
+ full cards, urgent ones first with their details open, and what was
27
+ answered in the last day below; answering keeps the list open. On a phone
28
+ the list spans the width over a dimmed page, with half-width buttons. While
29
+ an urgent question waits, 📓 and the Inbox show the urgent count in red.
30
+ The Inbox's link to a session opens it with 📓 at that question. Each ask
31
+ now remembers the turn that asked it (migration 0037).
32
+ - **A question leaves one line in the turn that asked it.** The cards of
33
+ questions that don't block no longer sit at the end of the conversation;
34
+ the reply of the turn that asked shows a one-line "📓 Question for you"
35
+ instead (live while it runs, "Answered: …" once answered), and a tap opens
36
+ 📓 at that question. A question asked for another session, carried over by
37
+ a compaction or from before this version stands as one line at the end
38
+ while it waits.
39
+ - **Settings → General in headed sections.** The tab reads as five groups,
40
+ Appearance, Reading, Turns and tasks, This relay and Updates, instead of
41
+ one long column. Each relay-wide row now says it is a setting of this
42
+ relay; the per-browser rows keep "Saved in this browser."
43
+ - **Thoughts open or closed by your choice.** Settings → General →
44
+ Appearance has "Expand thoughts while an agent works" (on by default), with
45
+ a 💭 Thoughts mirror in the 🧠 modal. It decides whether thoughts start open
46
+ in the live turn, in live sub-agent bubbles and in the sub-agent viewer,
47
+ finished thoughts there included. An open live thought keeps a one-line
48
+ header to fold it by; a thought you fold or open by hand stays that way
49
+ while more text streams in. A finished reply's 💭 fold stays closed as
50
+ before.
51
+ - **The main agent's tool lines stay while sub-agents work.** The live
52
+ bubble's tool list is capped per agent now, the main agent and each
53
+ sub-agent apart, so a busy sub-agent no longer pushes the main agent's
54
+ lines out. Settings → General → Appearance has "Tool lines shown while an
55
+ agent works": 50 by default, 10 to 500 or no limit. The relay sends the
56
+ running turn's newest lines per agent as the page's setting asks (50 when
57
+ it does not say, 500 at most), and for a finished turn the main agent's
58
+ newest 200 and each sub-agent's newest 50 (it sent the first 48 of the
59
+ whole turn). A sub-agent bubble that holds as many lines as the setting
60
+ shows "50+ steps".
61
+ - **Stream view: a turn in the order it happened.** Settings → General →
62
+ Appearance has "Stream view" (off by default), with a 🧵 mirror in the 🧠
63
+ modal. When it is on, an agent turn's thoughts and tool calls show as one
64
+ list in the order they happened, live and in the finished message, with
65
+ the reply last. For Copilot, Cursor and Grok, tools and thoughts stream in
66
+ arrival order and the reply comes after them. The relay now numbers a
67
+ turn's thoughts and tool lines in one sequence (migration 0038); turns
68
+ recorded before that keep the grouped layout. A finished turn whose main
69
+ agent ran more than 200 tool lines says how many earlier ones it left out.
70
+ - **The live poll leaves out what the page already has.** While a turn
71
+ runs, the page asks for its conversation about once a second. It now
72
+ names the finished replies whose tool lines and thoughts it already holds,
73
+ and the relay leaves those out unless they changed since (a busy window:
74
+ about 6 KB per poll instead of over 200 KB). Opening a conversation,
75
+ paging back and every other load still get everything.
76
+ - **An idle relay and its workers wake far less often.** The relay no
77
+ longer polls its queue twice a second: a write that makes a message
78
+ deliverable wakes delivery at once, through triggers on the queue table.
79
+ A retry or a usage-limit hold gets a timer for the moment it ends, and a
80
+ 10 s poll stays as a backstop. A gate lifting (relay resumed, restart
81
+ cancelled, tunnel connected, a kill's grace over, a worker replacement
82
+ done) wakes delivery too. The CLI status, the share-viewer prune and the
83
+ worker prime run only while there is something for them to do. Each
84
+ worker has one keepalive instead of two 10 s timers, and it beats every
85
+ 30 s while idle, on a relay that supports it. A silent worker now counts
86
+ as gone after 90 s instead of 30 s. Workers and relays of older versions
87
+ keep working with the new ones.
88
+ - **Claude sessions ask you before removals and other asks Claude Code
89
+ marks as person-only, instead of allowing them.** Such an ask, for
90
+ example an `rm -rf` of the working folder, used to be approved by the
91
+ relay like every other ask. Now a **Safety check** card, in the session
92
+ and in the Inbox, shows Claude Code's reason
93
+ and then the whole command, each line behind `│`, with hidden or
94
+ reordering characters shown as `\u{…}`. It offers **Allow once** and
95
+ **Deny**, takes no typed reply, and only you can answer it: no agent,
96
+ of this relay or another, sees it among a session's questions or may
97
+ answer it. It allows only that one call and adds no rule. A denial, no
98
+ answer within the question timeout, a stopped turn or a relay that
99
+ cannot show the card all leave the command unrun, and a command too long
100
+ to show is refused without a card. Other asks are still allowed as
101
+ before.
102
+
103
+ ### Upgrade notes
104
+
105
+ - **Two small migrations.** 0037 adds the asking turn to each ask and 0038
106
+ the sequence number to a turn's thoughts and tool lines, with an index;
107
+ neither rewrites the rows already there. As before, the relay copies its
108
+ database to the backups folder before the first start of a new version.
109
+ - **Every session worker is replaced once after the update,** as in
110
+ 0.9.21: once its session is idle and its CLI has ended, usually some 10
111
+ minutes after its last turn. A Claude session shows the **Safety check**
112
+ card once its worker runs 0.9.22. Nothing needs to be done.
113
+ - **Mixed versions work together.** A 0.9.21 worker on a 0.9.22 relay keeps
114
+ its 10 s beat, and a 0.9.22 worker on a 0.9.21 relay beats every 10 s.
115
+ Paired relays of both versions keep working with each other.
116
+ - **No new dependency.** Going back to 0.9.21 needs no database step: it
117
+ leaves the new columns and the index alone.
118
+
119
+ ## [0.9.21] — 2026-11-01
120
+
121
+ Highlights: the chat screen is more compact. The mode, model, effort,
122
+ context window and billing of a chat move behind **⚙** in a slim label row
123
+ above the message box, such as `🔒 Opus 5.5 · high · Auto`. The header menu
124
+ is now **︙**, and the header's icon buttons stay there while there is room
125
+ and move into it when there is not. Spacing is about halved across the app,
126
+ with controls and hit areas at 28 px. Back and forward walk through the
127
+ conversations you opened, with `?conv=<id>` in the address bar, and back
128
+ closes what is open on top first. A pinch, Ctrl/⌘+wheel or Text scaling
129
+ zooms the whole app, not only the text. A phone held sideways keeps the
130
+ phone's layout. A session group folds while one of its chats is open, the
131
+ filter row folds or unfolds all groups at once, and a group's name takes
132
+ its sessions' colour. The origin, watched and scheduled pills of a sidebar
133
+ row become one **ⓘ**. After a deploy, the relay replaces each session worker
134
+ still on the old code once its session is idle, so a restart no longer
135
+ leaves old worker code running.
136
+
137
+ ### Added
138
+
139
+ - **Chat settings behind ⚙ in the composer.** A slim label row above the
140
+ message box holds **⚙**, a label that says what the next message runs
141
+ with (model · effort · mode, such as `🔒 Opus 5.5 · high · Auto`, with the
142
+ context window added when it is not the model's default), the billing pill
143
+ of a Claude chat, the file browser, **📎** and, on a desktop, **🙂**. **⚙**,
144
+ or a tap on the label, opens the settings: **Mode**, **Model** with the
145
+ lock note under it, **Effort**, **Context window**, **Billing** and
146
+ **🧠 Context usage**. A tap on one part of the label opens them at that
147
+ field, and the **Subscription** or **API key** pill at **Billing**. On a
148
+ desktop they open as a popover above the row, on a phone as a sheet from
149
+ the bottom; **✕**, Escape, a tap outside or back closes them. An image
150
+ chat shows **Quality** and **Image size** in place of Effort and Context
151
+ window; a Claude Cloud chat has only **Model**. The label reads **Auto**
152
+ for the autopilot mode (the settings keep the full name) and **Plan 🔒**
153
+ on a plan-only session.
154
+ - **Back and forward through conversations.** Each conversation you open is
155
+ one step in the browser's history, with `?conv=<id>` in the address bar,
156
+ so back and forward walk through them, and a reload or a copied address
157
+ opens that conversation. The tab's title is the conversation's title.
158
+ Back first closes what is open on top: the session list on a phone, the
159
+ Inbox or Status view, the composer's settings, the **︙** menu, a row's
160
+ menu, dialogs, the git, tmux and annotate views, the watch and schedule
161
+ panels, the emoji picker and the Change CWD folder list. On a phone, back
162
+ from the first conversation you opened shows the session list, then the
163
+ installed app's *Press back again to close the app* as before.
164
+ - A link to a conversation the relay does not know (deleted, or a wrong
165
+ id) clears the view with a notice and keeps the last conversation; back
166
+ and forward step over it from then on, and nothing opens by itself until
167
+ you pick a conversation. A link to an archived conversation shows its
168
+ title.
169
+ - No address is written before the first conversation shows, so a link
170
+ survives an early tap. A jump from the Inbox to a question replaces the
171
+ history entry instead of adding one.
172
+ - A notification tap still opens `?push_conv=<id>`; the address then
173
+ reads `?conv=<id>`.
174
+ - **Zoom scales the whole app.** A pinch, Ctrl+wheel (⌘+wheel on a Mac) or
175
+ **⚙️ Settings → General → Text scaling** now zooms text, buttons,
176
+ spacing, the sidebar, menus and dialogs alike, so the layout keeps its
177
+ proportions; at 100 % nothing changes. Popovers and sheets are placed
178
+ anew on a change. The new-chat dialog's selects and inputs follow the
179
+ zoom too (they stayed at the browser's fixed 13.33 px).
180
+ - **A phone held sideways keeps the phone's layout.** The header is one
181
+ line. While the keyboard is up, the composer's label row folds into a
182
+ floating **⚙** that opens the settings and keeps the keyboard up, and the
183
+ background-task line into a floating **🤖**; both come back when the
184
+ keyboard goes down. Picking a conversation closes the session list and
185
+ does not bring up the keyboard (a long press does not pick). The **︙**
186
+ menu gains entries for the watch list, the schedules and the origin.
187
+ - **Fold a group with its chat open, and fold all.** A group folds even
188
+ while one of its sessions is open; that session stays under the folded
189
+ head until you open another one, and opening a member of a folded group
190
+ leaves it folded. A drop on that kept row is a drop on the folded head.
191
+ While the list has groups, the filter row has a fold-all button: **➖**
192
+ folds every group, **➕** unfolds them all once all are folded. It counts
193
+ as your own choice for each group and is disabled while the list is
194
+ filtered. The Archived view keeps its folds apart from the live list.
195
+ - **A group's name takes its sessions' colour:** red while one waits for
196
+ you, bright green while one works, dark green while one is idle with its
197
+ session ready, else the plain title colour. Archived heads stay muted.
198
+ - **ⓘ on sidebar rows.** The origin, watched and scheduled pills of a row
199
+ fold into one **ⓘ** in its second line. Its menu lists each with its
200
+ action: *Started by an agent in "…"* (or *on relay "…"*), which opens
201
+ that conversation or the session on the other relay, *Watched by …* with
202
+ the watchers' names, and *Scheduled · next …* (or *N schedules · next
203
+ …*). A member under the head row of the session that started it leaves
204
+ the origin out.
205
+ - **Session workers are recycled after a deploy.** Every worker states the
206
+ build id (version and commit) it started on; a worker on another build than
207
+ the relay, or one too old to say, is stopped and relaunched by the relay
208
+ once it is idle, the way **Change CWD → 🚀 Set new CWD and (re)launch**
209
+ does, so a restart no longer leaves old worker code running. An archived conversation's worker is
210
+ stopped for good. `/api/status` and the About modal show which build each
211
+ worker runs.
212
+ - Idle means no running turn, open question card, compaction, background
213
+ task or message on its way in, and the worker's CLI has ended. A CLI
214
+ ends by itself about 10 minutes after the last delivery or turn, so a
215
+ session is usually replaced some 10 minutes after its last turn. A
216
+ worker too old to say whether its CLI runs is replaced after 10 minutes
217
+ without a delivery or turn end. At most two are replaced at a time, one
218
+ start every 3 seconds, and each session once per relay build.
219
+ - Nothing is lost: the new worker resumes the native session, and a
220
+ message sent meanwhile waits and goes to the new worker. A Cursor
221
+ session is the exception for background shells and tasks, which its SDK
222
+ does not report: only the 10-minute quiet window protects them.
223
+ - **About OAR** shows *Workers: 14, all on this build* or *Workers: 14,
224
+ 3 on an older build*. `/api/status` gives the relay's `buildId` and,
225
+ per worker, `buildId`, `startedAt` and `stale`, and the recycle's
226
+ `staleCount`, replacements and the sessions it waits on with the reason.
227
+ relay_tool's session items carry `worker { kind, buildId, stale }`.
228
+ The relay log has `[worker-recycle]` lines for each replacement and,
229
+ at most once a minute per session, what it waits for.
230
+ - A Claude Cloud worker opens its event stream from the stored cursor at
231
+ start, so turns the cloud session starts by itself are followed after a
232
+ replacement too. New worker route `GET /api/claude-cloud-session`.
233
+ - The troubleshooting guide has a section, *Workers after a deploy*.
234
+
235
+ ### Changed
236
+
237
+ - **The header menu is ︙, and the icon buttons move into it when the
238
+ header is narrow.** **📍** pins and **🙈** hidden messages (while there
239
+ are any), **🔍** search, **🧠** context usage, **📊** Check Usage,
240
+ **☀️/🌙** Day/Night and **⛶** fullscreen stay in the header while there is
241
+ room and move into **︙** under their full names otherwise, the last ones
242
+ first. The menu stays inside the window, is capped in height and scrolls.
243
+ Fullscreen is disabled where the browser has none. Relay refusals, log
244
+ lines and help texts that named the `⋯` menu now name **︙**.
245
+ - **The open-question banner sits between the title and the header
246
+ buttons,** so **︙** always stays at the right edge. Where room is short
247
+ (a phone held sideways, a narrow desktop window) it shrinks to a **❓ N**
248
+ chip; on a wider header it takes at most 40 % of the row and ends in "…".
249
+ A long title ends in "…" too. When even the chip, **☰** and **︙** do not
250
+ fit next to the title, as at 150 % zoom with the sidebar open, the banner
251
+ takes a row of its own; on a phone held upright it always does.
252
+ - **About half the spacing, and 28 px controls.** Paddings, margins and
253
+ gaps in menus, the sidebar, dialogs, Settings, cards, bubbles and panels
254
+ are about halved, and controls that were 30 to 46 px high are 28 px, also
255
+ dialog buttons and tabs. Every hit area is at least 28 px at 100 %: the
256
+ row's **ⓘ**, fold, archive and delete, the live reply's **Stop** and
257
+ **💭 📃 🛠️** follow buttons, the Previews tab's **Open**, **Copy link**
258
+ and **Close**, the phone's header chips and the
259
+ question banner, the sidebar head buttons, Settings fields, selects,
260
+ checkboxes, radios and sliders, the repo browser's toolbar, the group
261
+ picker, the dialogs' **×** and **↻**, the Relays **Add**, the Sharing
262
+ row buttons, **Unsubscribe** and **Cancel**. Small labels and links get
263
+ an invisible 28 px hit area: the repo browser's Focus and crumbs, tree
264
+ rows, Sharing row titles, DeepSeek **Advanced**, the plan-only label, the
265
+ waiting pill, the agent-exchange and activity folds and the header chips.
266
+ - **On a phone the lines under the title are one row** that scrolls
267
+ sideways, with short labels (`↗ title`, `👁 3`, `⏰ 04:15`, the folder's
268
+ last name), and the open-question banner gets a row of its own.
269
+ - **Dialogs on a phone keep their title** with a compact **×**; the header
270
+ buttons no longer stretch across the width. Select Models puts its three
271
+ buttons under the title.
272
+ - **A group's head.** One chevron **›** that turns down while the group is
273
+ open replaces ▾/▸; focus returns to it only after a keyboard toggle, with
274
+ a focus ring in the accent colour. A head line without a head row reads
275
+ "<last active> · N sessions", and its pill shows only what waits, runs or
276
+ failed. A long head drops its message count whole, then its date, instead
277
+ of cutting them. A mouse click on **ⓘ** hands focus back without a ring.
278
+ - **Sidebar rows.** The title and the "1 open" pill share one line and only
279
+ the title is cut short; the title and meta line keep clear of the archive
280
+ and delete buttons. Archive and delete show on keyboard focus too. The
281
+ filter row's archive button reads **🗄️**, and the buttons close up to the
282
+ right when **×** or **➕** is hidden. The sidebar head follows the zoom and
283
+ wraps instead of cutting off **Inbox** or **Install**.
284
+ - **The Claude usage warning above the composer is one line,** such as
285
+ `⚠ Claude 94 % weekly · resets Tue 04:00`; a tap shows the full sentence.
286
+ - **A sub-agent's Stop is red and asks first** (**⏹ Stop agent**).
287
+ - **On a phone a background task's title has its own full-width line.**
288
+ - **Images in messages have no card around them.**
289
+ - **Editing a chat's title on a desktop takes the whole header row** while
290
+ you type; the title lines, the banner and the header buttons come back
291
+ when you are done.
292
+ - **Settings takes the focus when it opens, and Escape closes it.** The
293
+ Settings and Plan usage tab strips fade at an edge while more tabs lie
294
+ beyond it, and keep the selected tab in view. The Features help wraps
295
+ long identifiers.
296
+ - **Pack guard ceilings.** The npm tarball may hold 610 entries and 9.5 MiB
297
+ unpacked (590 and 9 MiB before); 0.9.20 had used 99.3 % of the bytes.
298
+
299
+ ### Fixed
300
+
301
+ - **The archive button on a sidebar row is visible on touch screens.** It
302
+ was invisible there but could still be tapped.
303
+ - **A ︙ entry tapped on a phone no longer closes what it opened.** The tap's
304
+ follow-up click landed on the menu's backdrop or the new dialog and closed
305
+ it again: About, Kill session, Check Usage, Context, Restart relay and
306
+ Search, and the watch and schedule panels; Git changes did not open a
307
+ diff.
308
+ - **Contrast in the light theme.** Primary buttons read at 5.19:1, red
309
+ buttons at 4.67:1, the sidebar's "1 open" pill at 6.47:1, Settings help
310
+ links at 6.85:1, the offline banner at 5.6:1 and the **☰** status tones
311
+ at 4.9:1 or more. A hovered accent button gets a darker fill (6.36:1)
312
+ instead of turning paler.
313
+ - **Status and the Inbox, closed with nothing to go back to,** show their
314
+ empty state instead of a blank pane.
315
+ - **The OpenAI, Grok and Cursor fields in Settings look like the others,**
316
+ and **Use a different token** and **Empty queue** are real buttons.
317
+ - **The ⚙ popover fits its width under zoom,** the full origin title shows,
318
+ and focus is kept when the list or header is drawn anew.
319
+
320
+ ### Upgrade notes
321
+
322
+ - **Every session worker is replaced once after the update.** Workers from
323
+ 0.9.20 state no build id, so 30 seconds after the relay first hears of
324
+ one it counts as on an older build and is replaced once its session is
325
+ idle and its CLI has ended, usually some 10 minutes after its last turn.
326
+ About shows *M on an older build* until then. Nothing needs to be done.
327
+ Each replacement costs one cold start on the session's next message.
328
+ - **No migration and no new dependency.** Going back to 0.9.20 needs no
329
+ database step.
330
+
6
331
  ## [0.9.20] — 2026-10-29
7
332
 
8
333
  Highlights: every plan card gets an "Approve plan" button that approves the
package/README.md CHANGED
@@ -45,29 +45,29 @@ flowchart LR
45
45
  <table>
46
46
  <tr>
47
47
  <td width="42%" rowspan="4" valign="top">
48
- <a href="https://oar.sh/assets/shots/cfmail-mobile.png?v=0.9.20"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/cfmail-mobile.light.png?v=0.9.20"><img src="https://oar.sh/assets/shots/cfmail-mobile.png?v=0.9.20" alt="OAR on a phone: a finished agent turn in a conversation, with the composer below"></picture></a>
48
+ <a href="https://oar.sh/assets/shots/cfmail-mobile.png?v=0.9.22"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/cfmail-mobile.light.png?v=0.9.22"><img src="https://oar.sh/assets/shots/cfmail-mobile.png?v=0.9.22" alt="OAR on a phone: a finished agent turn in a conversation, with the composer below"></picture></a>
49
49
  <br><sub>On a phone: the same conversations, installable as an app.</sub>
50
50
  </td>
51
51
  <td width="58%" valign="top">
52
- <a href="https://oar.sh/assets/shots/cfmail-agents.png?v=0.9.20"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/cfmail-agents.light.png?v=0.9.20"><img src="https://oar.sh/assets/shots/cfmail-agents.png?v=0.9.20" alt="Background subagents running in parallel in the task panel, each with live token counts, elapsed time, and its own Stop button"></picture></a>
52
+ <a href="https://oar.sh/assets/shots/cfmail-agents.png?v=0.9.22"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/cfmail-agents.light.png?v=0.9.22"><img src="https://oar.sh/assets/shots/cfmail-agents.png?v=0.9.22" alt="Background subagents running in parallel in the task panel, each with live token counts, elapsed time, and its own Stop button"></picture></a>
53
53
  <br><sub>Agents working in parallel, each with live token counts and its own Stop.</sub>
54
54
  </td>
55
55
  </tr>
56
56
  <tr>
57
57
  <td valign="top">
58
- <a href="https://oar.sh/assets/shots/cfmail-question.png?v=0.9.20"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/cfmail-question.light.png?v=0.9.20"><img src="https://oar.sh/assets/shots/cfmail-question.png?v=0.9.20" alt="A question card in the middle of a turn: the agent offers several choices and a free-text reply"></picture></a>
58
+ <a href="https://oar.sh/assets/shots/cfmail-question.png?v=0.9.22"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/cfmail-question.light.png?v=0.9.22"><img src="https://oar.sh/assets/shots/cfmail-question.png?v=0.9.22" alt="A question card in the middle of a turn: the agent offers several choices and a free-text reply"></picture></a>
59
59
  <br><sub>When the agent needs a decision, it asks, and waits for your answer.</sub>
60
60
  </td>
61
61
  </tr>
62
62
  <tr>
63
63
  <td valign="top">
64
- <a href="https://oar.sh/assets/shots/pingcf-thoughts.png?v=0.9.20"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/pingcf-thoughts.light.png?v=0.9.20"><img src="https://oar.sh/assets/shots/pingcf-thoughts.png?v=0.9.20" alt="An expanded Thoughts block showing the agent's reasoning above its reply"></picture></a>
64
+ <a href="https://oar.sh/assets/shots/pingcf-thoughts.png?v=0.9.22"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/pingcf-thoughts.light.png?v=0.9.22"><img src="https://oar.sh/assets/shots/pingcf-thoughts.png?v=0.9.22" alt="An expanded Thoughts block showing the agent's reasoning above its reply"></picture></a>
65
65
  <br><sub>Read the reasoning, not just the answer.</sub>
66
66
  </td>
67
67
  </tr>
68
68
  <tr>
69
69
  <td valign="top">
70
- <a href="https://oar.sh/assets/shots/pagewright-watching.png?v=0.9.20"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/pagewright-watching.light.png?v=0.9.20"><img src="https://oar.sh/assets/shots/pagewright-watching.png?v=0.9.20" alt="A session watching two other sessions: a finished reply arrives as a compact relay-event row, the watching 2 sessions list is open with Unsubscribe, and a request to also watch a session on a paired relay waits on an Allow / Deny card"></picture></a>
70
+ <a href="https://oar.sh/assets/shots/pagewright-watching.png?v=0.9.22"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/pagewright-watching.light.png?v=0.9.22"><img src="https://oar.sh/assets/shots/pagewright-watching.png?v=0.9.22" alt="A session watching two other sessions: a finished reply arrives as a compact relay-event row, the watching 2 sessions list is open with Unsubscribe, and a request to also watch a session on a paired relay waits on an Allow / Deny card"></picture></a>
71
71
  <br><sub>Agents that watch agents: woken by events, approving nothing.</sub>
72
72
  </td>
73
73
  </tr>
@@ -118,11 +118,11 @@ API keys are entered in **⚙️ Settings** and stay on the relay host. The Clau
118
118
  <table>
119
119
  <tr>
120
120
  <td width="62%" valign="top">
121
- <a href="https://oar.sh/assets/shots/cfmail-steering.png?v=0.9.20"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/cfmail-steering.light.png?v=0.9.20"><img src="https://oar.sh/assets/shots/cfmail-steering.png?v=0.9.20" alt="A running Claude turn with a message steered into it mid-turn, the reply streaming with its tool calls, and the Steer button in the composer"></picture></a>
121
+ <a href="https://oar.sh/assets/shots/cfmail-steering.png?v=0.9.22"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/cfmail-steering.light.png?v=0.9.22"><img src="https://oar.sh/assets/shots/cfmail-steering.png?v=0.9.22" alt="A running Claude turn with a message steered into it mid-turn, the reply streaming with its tool calls, and the Steer button in the composer"></picture></a>
122
122
  <br><sub>Send a message while the agent works, and it steers the running turn.</sub>
123
123
  </td>
124
124
  <td width="38%" valign="top">
125
- <a href="https://oar.sh/assets/shots/usage-copilot-compact.png?v=0.9.20"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/usage-copilot-compact.light.png?v=0.9.20"><img src="https://oar.sh/assets/shots/usage-copilot-compact.png?v=0.9.20" alt="The GitHub Copilot card in Check Usage: premium requests and plan quota with reset dates, and the cost of the last turn"></picture></a>
125
+ <a href="https://oar.sh/assets/shots/usage-copilot-compact.png?v=0.9.22"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/usage-copilot-compact.light.png?v=0.9.22"><img src="https://oar.sh/assets/shots/usage-copilot-compact.png?v=0.9.22" alt="The GitHub Copilot card in Check Usage: premium requests and plan quota with reset dates, and the cost of the last turn"></picture></a>
126
126
  <br><sub>What each plan has left, in one place.</sub>
127
127
  </td>
128
128
  </tr>
@@ -132,14 +132,14 @@ API keys are entered in **⚙️ Settings** and stay on the relay host. The Clau
132
132
  - **Steer or queue**: a message sent during a turn goes into it (Claude, Copilot) or waits behind it ([While a turn runs](docs/web-ui.md#while-a-turn-runs)).
133
133
  - **Answer questions**: the agent asks on a card with one-click choices, checkmarks or free text, and waits for you ([Question cards](docs/web-ui.md#question-cards)).
134
134
  - **Background work**: a task panel lists the commands and sub-agents that outlive a reply, each with its own **Stop** and its full record; a command's console shows its output live, says when it has gone quiet and counts a test run's passes and failures; the panel and its consoles come back after a relay restart, and a queued restart shows what it waits for and asks after a maximum wait ([Background tasks](docs/web-ui.md#background-tasks), [After a relay restart](docs/web-ui.md#after-a-relay-restart)).
135
- - **Choose how the agent works**: `ask`, `plan`, `agent` or `autopilot`, plus model and reasoning effort, per message ([Relay modes](docs/web-ui.md#relay-modes)).
135
+ - **Choose how the agent works**: `ask`, `plan`, `agent` or `autopilot`, plus model and reasoning effort, per message, from the composer's **⚙** ([The composer](docs/web-ui.md#the-composer), [Relay modes](docs/web-ui.md#relay-modes)).
136
136
  - **Files and git**: browse the workspace, preview files, review git changes in a diff viewer, and publish a dev server as a preview link ([Files, git, and previews](docs/web-ui.md#files-git-and-previews), [preview servers](docs/preview-servers.md)).
137
137
  - **Usage and context**: plan limits and credits of every provider, the estimated spend of DeepSeek and Claude API keys, and what fills the model's context window ([Usage and context](docs/web-ui.md#usage-and-context)).
138
138
  - **Your conversations everywhere**: the history lives on the relay, drafts follow you from device to device, and conversations can be searched, archived, grouped and pinned ([The conversation list](docs/web-ui.md#the-conversation-list), [drafts](docs/web-ui.md#drafts-across-devices), [session groups](docs/web-ui.md#session-groups), [pins](docs/web-ui.md#pinned-messages)).
139
139
  - **Share** a conversation as a read-only link, hide single messages and the host's paths from it, and see when it was opened ([Sharing](docs/web-ui.md#sharing)).
140
- - **On your phone**: install OAR as an app and get push notifications ([Notifications and the app](docs/web-ui.md#notifications-and-the-app)).
140
+ - **On your phone**: install OAR as an app and get push notifications; back walks through the conversations you opened, a pinch zooms the whole app, and held sideways it keeps a phone's layout ([Notifications and the app](docs/web-ui.md#notifications-and-the-app), [back and forward](docs/web-ui.md#back-and-forward), [zoom](docs/web-ui.md#zoom), [a phone held sideways](docs/web-ui.md#a-phone-held-sideways)).
141
141
 
142
- The full tour is in [Using the web UI](docs/web-ui.md); every setting and config key is in [Settings and configuration](docs/configuration.md). What changed in each release is in the [changelog](CHANGELOG.md), with [0.9.20](CHANGELOG.md#0920--2026-10-29) as the latest.
142
+ The full tour is in [Using the web UI](docs/web-ui.md); every setting and config key is in [Settings and configuration](docs/configuration.md). What changed in each release is in the [changelog](CHANGELOG.md), with [0.9.22](CHANGELOG.md#0922--2026-11-05) as the latest.
143
143
 
144
144
  ## Remote access
145
145
 
@@ -190,7 +190,7 @@ flowchart LR
190
190
 
191
191
  You stay in charge: per paired relay you decide whether agents may only read, also prompt, or do everything, and in the `ask` and `plan` modes every write asks you first. A subscription only notifies; it approves nothing.
192
192
 
193
- <p><a href="https://oar.sh/assets/shots/pagewright-cross-relay.png?v=0.9.20"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/pagewright-cross-relay.light.png?v=0.9.20"><img src="https://oar.sh/assets/shots/pagewright-cross-relay.png?v=0.9.20" alt="A session watching two sessions on a paired relay: a reply arrives as a relay-event row, and after a short outage the events the other relay kept arrive as one replayed row" width="720"></picture></a></p>
193
+ <p><a href="https://oar.sh/assets/shots/pagewright-cross-relay.png?v=0.9.22"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/pagewright-cross-relay.light.png?v=0.9.22"><img src="https://oar.sh/assets/shots/pagewright-cross-relay.png?v=0.9.22" alt="A session watching two sessions on a paired relay: a reply arrives as a relay-event row, and after a short outage the events the other relay kept arrive as one replayed row" width="720"></picture></a></p>
194
194
 
195
195
  How pairing, permissions and limits work is in [Agents working together](docs/agents-together.md).
196
196
 
@@ -211,7 +211,7 @@ How keys, cookies, served files, paired relays and Claude Cloud's use of the Cla
211
211
  | A runtime is missing from **New Chat** | Turn it on in **⚙️ Settings → Providers**; all of them except Copilot are off until you do |
212
212
  | A reply says a CLI is missing or signed out | Press the button on the failed reply (**Install Grok CLI**, **Sign in to Grok**, **Claude settings**) |
213
213
  | Banner says *CLI is offline* | Normal on an idle relay: send a message, and the banner clears once the conversation's worker connects |
214
- | A conversation seems wedged | **☠️ Kill session** in the `⋯` menu, then retry or send a new message |
214
+ | A conversation seems wedged | **☠️ Kill session** in the **︙** menu, then retry or send a new message |
215
215
  | `npm install -g @oar-sh/oar` fails with `EACCES` | Do not use `sudo`; run the installer, which installs into your home directory |
216
216
 
217
217
  `oar doctor` prints the version, paths, port and provider CLIs in use. More symptoms are in [Troubleshooting](docs/troubleshooting.md).
@@ -225,7 +225,7 @@ How keys, cookies, served files, paired relays and Claude Cloud's use of the Cla
225
225
  | ----- | ------------- |
226
226
  | [Installing and running OAR](docs/install.md) | Prerequisites, every install method, starting the relay, the `oar` command, files, updating |
227
227
  | [Providers](docs/providers.md) | Each runtime in detail, installing provider CLIs from the relay, models |
228
- | [Using the web UI](docs/web-ui.md) | Conversations, running turns, question cards, background tasks, files, usage, sharing, the menu |
228
+ | [Using the web UI](docs/web-ui.md) | Conversations, the composer, running turns, question cards, background tasks, files, usage, sharing, zoom and navigation, the menu |
229
229
  | [Settings and configuration](docs/configuration.md) | Settings, `config.json`, environment variables, stuck-turn detection |
230
230
  | [Remote access](docs/remote-access.md) | SSH and Cloudflare tunnels, a reverse proxy on another host, the session-worker path guard |
231
231
  | [Agents working together](docs/agents-together.md) | Paired relays, agent sessions, watching sessions |
package/bin/oar.js CHANGED
@@ -1005,7 +1005,7 @@ async function restartRelay(ctx, paths, { old, viaService, answered = true, now
1005
1005
  if (!restartService(ctx)) return { ok: false, reason: 'service' };
1006
1006
  } else if (!(await requestShutdown(ctx, old, true, { immediate: now }))) {
1007
1007
  if (!viaService) {
1008
- logger.error('[oar] The relay did not accept the restart request. Restart it from the web UI (⋯ → Restart web relay).');
1008
+ logger.error('[oar] The relay did not accept the restart request. Restart it from the web UI (︙ → Restart web relay).');
1009
1009
  return { ok: false, reason: 'restart-refused' };
1010
1010
  }
1011
1011
  if (!restartService(ctx)) return { ok: false, reason: 'service' };
@@ -1428,7 +1428,7 @@ function reportPortTaken(ctx, paths, port) {
1428
1428
  // cannot be asked to restart: only its lock tells that it is there.
1429
1429
  const pid = readRelayLock(paths.lockPath)?.pid;
1430
1430
  if (Number.isInteger(pid) && ctx.isProcessAliveImpl(pid)) {
1431
- ctx.logger.error(`[oar] A relay of this install seems to run (process ${pid}). If its token is an earlier one, restart it from its web UI (⋯ → Restart web relay), or end that process and run: oar start`);
1431
+ ctx.logger.error(`[oar] A relay of this install seems to run (process ${pid}). If its token is an earlier one, restart it from its web UI (︙ → Restart web relay), or end that process and run: oar start`);
1432
1432
  }
1433
1433
  return { code: 1, reason: 'port-taken' };
1434
1434
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oar-sh/oar",
3
- "version": "0.9.20",
3
+ "version": "0.9.22",
4
4
  "type": "module",
5
5
  "description": "OAR — Open Agent Relay: drive your local coding agents from any browser",
6
6
  "license": "MIT",