@oar-sh/oar 0.9.24 → 0.9.26
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.
- package/.github/extensions/web-relay/skills/reasoning-stream.mjs +3 -1
- package/CHANGELOG.md +284 -0
- package/README.md +12 -11
- package/package.json +5 -1
- package/server/claude-worker/claude-remote-relay-tool.mjs +15 -0
- package/server/claude-worker/claude-sdk-adapter.mjs +8 -4
- package/server/claude-worker/claude-session-process.mjs +4 -0
- package/server/claude-worker/claude-turn-publisher.mjs +25 -11
- package/server/claude-worker/sdk-message-normalizer.mjs +29 -1
- package/server/copilot-worker/copilot-prompt-context.mjs +2 -1
- package/server/copilot-worker/copilot-sdk-session-process.mjs +11 -9
- package/server/cursor-worker/cursor-turn-runner.mjs +3 -1
- package/server/cursor-worker/sdk-message-normalizer.mjs +13 -0
- package/server/db-schema.mjs +19 -0
- package/server/grok-worker/grok-turn-runner.mjs +3 -1
- package/server/grok-worker/sdk-message-normalizer.mjs +3 -0
- package/server/migrations/0039-media-index.mjs +167 -0
- package/server/migrations/0040-image-session-defaults.mjs +93 -0
- package/server/public/app/api-client.js +58 -0
- package/server/public/app/ask-user-view.js +2 -1
- package/server/public/app/attachments-view.js +66 -9
- package/server/public/app/bootstrap.js +85 -25
- package/server/public/app/chat-actions-menu-state.mjs +1 -0
- package/server/public/app/context-media-tab.js +143 -0
- package/server/public/app/context-usage-view.mjs +2 -2
- package/server/public/app/conversation-view.js +90 -5
- package/server/public/app/image-composer-ui.js +518 -0
- package/server/public/app/image-turn-placeholder.mjs +233 -0
- package/server/public/app/journal-view.js +12 -2
- package/server/public/app/live-activity-cap.mjs +3 -3
- package/server/public/app/markdown-math.mjs +471 -0
- package/server/public/app/media-gallery-model.mjs +202 -0
- package/server/public/app/media-gallery-ui.js +524 -0
- package/server/public/app/media-view.js +79 -0
- package/server/public/app/openai-image-composer.mjs +492 -0
- package/server/public/app/openai-image-cost-ui.js +426 -0
- package/server/public/app/openai-image-spend.mjs +443 -0
- package/server/public/app/plan-usage-view.mjs +38 -6
- package/server/public/app/pwa-install.js +25 -4
- package/server/public/app/relay-error-ctas.mjs +5 -0
- package/server/public/app/relay-prompt-sanitizer.mjs +3 -0
- package/server/public/app/relay-restart-ui.js +2 -2
- package/server/public/app/remote-relays-settings.js +33 -6
- package/server/public/app/router.js +49 -237
- package/server/public/app/session-history-ui.js +2 -0
- package/server/public/app/settings-modal.js +4 -0
- package/server/public/app/settings-tabs.js +10 -1
- package/server/public/app/socket-handlers.js +26 -0
- package/server/public/app/store.js +2 -0
- package/server/public/app/stream-view-preference.mjs +1 -1
- package/server/public/app/tab-strip-fade.mjs +1 -1
- package/server/public/app/thoughts-preference.mjs +1 -1
- package/server/public/app/tmux-inspector-view.js +29 -16
- package/server/public/app/viewer-gallery.mjs +23 -1
- package/server/public/index.html +517 -123
- package/server/relay-tools.md +1 -0
- package/server/repositories/image-conversation-repository.mjs +69 -0
- package/server/repositories/media-repository.mjs +305 -0
- package/server/routes/media-routes.mjs +361 -0
- package/server/routes/messages-routes.mjs +1410 -360
- package/server/routes/relay-board-routes.mjs +46 -1
- package/server/routes/remote-relay-routes.mjs +1 -1
- package/server/routes/sessions-routes.mjs +361 -14
- package/server/server-runtime.mjs +21 -0
- package/server/services/database-file.mjs +6 -4
- package/server/services/generated-image-cleanup-service.mjs +2 -1
- package/server/services/generated-image-files.mjs +110 -0
- package/server/services/image-input-paths.mjs +389 -0
- package/server/services/image-operation-service.mjs +6 -2
- package/server/services/media-extract.mjs +653 -0
- package/server/services/media-index-service.mjs +221 -0
- package/server/services/openai-image-preview-turns.mjs +330 -0
- package/server/services/openai-image-spend-service.mjs +516 -0
- package/server/services/openai-image-stream.mjs +170 -0
- package/server/services/plan-usage-openai-image.mjs +148 -0
- package/server/services/relay-prompt-sanitizer.mjs +7 -0
- package/server/services/relay-restart-queue.mjs +1 -1
- package/server/services/remote-relay-caller-context.mjs +6 -0
- package/server/services/remote-relay-dispatcher.mjs +409 -23
- package/server/services/remote-relay-registry-service.mjs +10 -2
- package/server/services/session-worker-stop-service.mjs +50 -18
- package/server/services/token-usage-service.mjs +15 -0
- package/shared/image-header.mjs +90 -0
- package/shared/math-instructions.mjs +30 -0
- package/shared/openai-image-options.mjs +299 -0
- package/shared/openai-image-presets.mjs +175 -0
- package/shared/openai-image-previews.mjs +40 -0
- package/shared/openai-image-prices.mjs +291 -0
- package/shared/openai-image-spend.mjs +90 -0
- package/shared/remote-relay-contract.mjs +126 -4
- package/shared/worker-runtime/direct-openai-image.mjs +96 -50
- package/server/public/app/math-dollars.mjs +0 -89
|
@@ -168,7 +168,9 @@ export function createReasoningStreamHandlers({
|
|
|
168
168
|
const reasoningId = reasoningIdFor(event);
|
|
169
169
|
const deltaContent = String(event?.data?.deltaContent ?? event?.data?.delta ?? "");
|
|
170
170
|
if (!deltaContent) return;
|
|
171
|
-
|
|
171
|
+
// Capped before the gate: past the cap the text stops changing, so the
|
|
172
|
+
// same 16 KiB is not posted again and the accumulator stops growing.
|
|
173
|
+
const accumulated = capThought((reasoningAccum.get(reasoningId) || "") + deltaContent);
|
|
172
174
|
reasoningAccum.set(reasoningId, accumulated);
|
|
173
175
|
const lastSent = reasoningLastSent.get(reasoningId) || "";
|
|
174
176
|
if (!shouldEmitDelta(accumulated, lastSent)) return;
|
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,290 @@
|
|
|
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.26] — 2026-11-11
|
|
7
|
+
|
|
8
|
+
Highlights: formulas from their delimiters only, and Settings sorted by
|
|
9
|
+
where they live. Replies render a formula only when it is written as
|
|
10
|
+
`$…$`, `\(…\)`, `$$…$$`, `\[…\]` or a fenced `math` block, so an id in
|
|
11
|
+
parentheses, a Windows path, a price or a shell variable stays text, a
|
|
12
|
+
formula KaTeX cannot read is shown as written instead of in red, and
|
|
13
|
+
the agent is told the notation that renders, except in Claude Cloud
|
|
14
|
+
chats and on the Copilot Extension engine. KaTeX moves to 0.19.1.
|
|
15
|
+
Theme, text scaling and the live-turn settings get an Appearance tab of
|
|
16
|
+
their own, and the relay name and the two restart settings move to
|
|
17
|
+
Relays, where an empty name shows the host name the relay goes by and a
|
|
18
|
+
rename made on another device shows at once. In Claude, Claude API and
|
|
19
|
+
DeepSeek chats a plan card comes only from a plan the agent hands over,
|
|
20
|
+
and a plan card shows up to 200,000 characters of the plan instead of
|
|
21
|
+
12,000. Archive and delete no longer fail on Linux and macOS for a
|
|
22
|
+
worker that has already ended, a session an agent on a paired relay
|
|
23
|
+
started can always report back to it, and the tmux console draws a real
|
|
24
|
+
terminal.
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- **Fenced `math` blocks.** A code block with the language `math` is a
|
|
29
|
+
displayed formula once it is closed.
|
|
30
|
+
- **Agents are told how to write formulas.** Once per session, the agent
|
|
31
|
+
of a Claude, Claude API, DeepSeek, Cursor or Grok chat, and of a
|
|
32
|
+
Copilot or OpenAI chat on the SDK engine, learns that `$…$` in a
|
|
33
|
+
sentence and `$$…$$` on lines of their own render (`\(…\)` and `\[…\]`
|
|
34
|
+
too), that TeX in bare parentheses or brackets does not, and that a
|
|
35
|
+
dollar amount stays text. Claude Cloud chats and the Copilot Extension
|
|
36
|
+
engine do not get the note. The prompt a transcript shows leaves the
|
|
37
|
+
note out.
|
|
38
|
+
|
|
39
|
+
### Changed
|
|
40
|
+
|
|
41
|
+
- **Settings: an Appearance tab, and the relay's own settings on
|
|
42
|
+
Relays.** Theme, Text scaling, Expand thoughts while an agent works,
|
|
43
|
+
Tool lines shown while an agent works, Stream view and Show Suspend
|
|
44
|
+
host action move from General to the new Appearance tab right after
|
|
45
|
+
it, under one line that says they are kept in this browser; Jump to
|
|
46
|
+
the end when I send a message and Messages kept per conversation stay
|
|
47
|
+
in General's Reading block. Relay name moves to the top of Relays,
|
|
48
|
+
above Public address, and After a relay restart, tell interrupted
|
|
49
|
+
sessions to carry on and Ask after a queued relay restart has waited
|
|
50
|
+
follow right below Public address; what stays of General's "This
|
|
51
|
+
relay" block (Default CWD, Autostart, the Windows helper) is headed
|
|
52
|
+
"This computer". Every setting keeps its value and default, and a
|
|
53
|
+
remembered tab or a link to General still opens General.
|
|
54
|
+
- **Formulas are read from their delimiters only.** `$…$` and `\(…\)`
|
|
55
|
+
inside a sentence, `$$…$$` and `\[…\]` as a display, and a fenced
|
|
56
|
+
`math` block; nothing is guessed from TeX in bare parentheses or
|
|
57
|
+
brackets any more. On one line `\[ … \]` needs a space inside both
|
|
58
|
+
ends, so `\[1\]` and `\[x\]` are escaped brackets. A `$` opens a
|
|
59
|
+
formula only where maths is written that way, so prices (`$5 and $9`,
|
|
60
|
+
`$5/$10`), shell variables (`$HOME/$PATH`) and Windows paths stay
|
|
61
|
+
text, and `\$` is always a dollar sign. Code spans and code blocks
|
|
62
|
+
never hold a formula. A formula KaTeX cannot parse is shown as
|
|
63
|
+
written, in the normal text colour, and a percent sign in a one-line
|
|
64
|
+
formula is shown. A message over 100,000 characters renders no
|
|
65
|
+
formulas. The rules are in
|
|
66
|
+
[Formulas in replies](docs/web-ui.md#formulas-in-replies).
|
|
67
|
+
- **KaTeX 0.19.1.** The page loads KaTeX 0.19.1 (was 0.16.22) and its
|
|
68
|
+
chemistry extension from jsDelivr, as before, now with integrity hashes;
|
|
69
|
+
its auto-render script is no longer loaded. KaTeX is not in the npm
|
|
70
|
+
package: a browser that cannot reach jsDelivr shows every formula as
|
|
71
|
+
written.
|
|
72
|
+
- **A plan card comes from a plan hand-over only** in Claude, Claude API
|
|
73
|
+
and DeepSeek chats. A plan-mode reply with two or more list lines no
|
|
74
|
+
longer gets a "Plan ready for review" card, so a session that stays in
|
|
75
|
+
plan mode no longer puts the buttons under every status reply. When the
|
|
76
|
+
plan of a hand-over cannot be read, the reply stands in for it. Cursor,
|
|
77
|
+
Grok, Copilot and OpenAI (BYOK) chats keep the card for such replies.
|
|
78
|
+
|
|
79
|
+
### Fixed
|
|
80
|
+
|
|
81
|
+
- **The whole plan on the plan card.** The card cut every plan at 12,000
|
|
82
|
+
characters, without a mark. It now keeps up to 200,000; a longer plan
|
|
83
|
+
is cut, at a line break where it can, and ends with a line that says
|
|
84
|
+
how much is missing and, where the agent named it, the file that holds
|
|
85
|
+
the whole plan. A cut inside a code block closes the block first, also
|
|
86
|
+
in a plan file with Windows line ends.
|
|
87
|
+
- **Archive and delete no longer fail for a worker that has ended**
|
|
88
|
+
(Linux and macOS). Archiving or deleting an idle conversation could
|
|
89
|
+
answer "Couldn't stop this conversation's session" again and again:
|
|
90
|
+
the relay counted the old process id of a worker that had ended, by
|
|
91
|
+
then another program's, and processes that were ending by themselves.
|
|
92
|
+
Those no longer count, and a process the relay may not signal no longer
|
|
93
|
+
keeps it from stopping the others. A worker that really survives the
|
|
94
|
+
stop still leaves the conversation as it was.
|
|
95
|
+
- **A session an agent on a paired relay started can always report
|
|
96
|
+
back.** Its send to the session that started it was refused with
|
|
97
|
+
`REMOTE_RELAY_HOP_LIMIT` when its turn also acted on a prompt from a
|
|
98
|
+
session on its own relay. Another session on that relay, a third relay
|
|
99
|
+
and `create_session` are still held to the limit.
|
|
100
|
+
- **The relay name in Settings.** An empty Relay name showed "OAR" as its
|
|
101
|
+
placeholder; it now shows the host name, the name paired relays know
|
|
102
|
+
the relay by then. A rename made on another device shows in an open
|
|
103
|
+
Settings without reopening it.
|
|
104
|
+
- **The tmux console draws a terminal.** 🖥️ Inspect tmux console asked
|
|
105
|
+
jsDelivr and unpkg for xterm.js versions that were never published, so
|
|
106
|
+
it always fell back to plain text. It now loads `@xterm/xterm` 5.5.0
|
|
107
|
+
and `@xterm/addon-fit` 0.10.0 from jsDelivr, with integrity hashes,
|
|
108
|
+
and only the two scripts from unpkg when jsDelivr fails; a browser
|
|
109
|
+
that reaches neither still gets the plain text.
|
|
110
|
+
- **A long line in a code block no longer runs under its Copy button**,
|
|
111
|
+
which has a strip of its own above the first line.
|
|
112
|
+
|
|
113
|
+
### Upgrade notes
|
|
114
|
+
|
|
115
|
+
- **No migration.** As before, the relay copies its database to the
|
|
116
|
+
backups folder before the first start of a new version; nothing else
|
|
117
|
+
waits.
|
|
118
|
+
- **Sessions still on 0.9.25's worker wait one worker start.** A session
|
|
119
|
+
whose worker still runs the old code is given a new worker between
|
|
120
|
+
turns, as in 0.9.25: a Claude, Claude API, DeepSeek or Cursor session
|
|
121
|
+
outside a git checkout before its next message, any other once its
|
|
122
|
+
agent has been idle for about 10 minutes. Its agent learns the formula
|
|
123
|
+
notation with the new worker, and Claude, Claude API and DeepSeek
|
|
124
|
+
chats get the new plan-card rule with it.
|
|
125
|
+
- **Your settings stay as they were.** The Appearance settings are kept
|
|
126
|
+
in the browser under the same keys, and the relay's own settings in its
|
|
127
|
+
database; only their place in Settings changes.
|
|
128
|
+
- **Older replies follow the new formula rules** when they are shown
|
|
129
|
+
again: a parenthesis 0.9.25 drew as a formula is text now, TeX
|
|
130
|
+
written in bare parentheses shows as written, and a one-line
|
|
131
|
+
`\[x^2\]` without a space inside its ends is read as escaped brackets.
|
|
132
|
+
- **KaTeX and xterm.js load in the browser,** not from the relay: from
|
|
133
|
+
jsDelivr, and the two xterm.js scripts, not its stylesheet, also from
|
|
134
|
+
unpkg; neither is in the npm package. A browser without access to them
|
|
135
|
+
shows formulas as written and the tmux console as plain text, whatever
|
|
136
|
+
the relay host can reach.
|
|
137
|
+
- **Mixed versions work together.** The report-back rule is checked by
|
|
138
|
+
the relay the started session runs on: that one needs 0.9.26, the relay
|
|
139
|
+
of the session that started it may run 0.9.25.
|
|
140
|
+
- **Going back to 0.9.25** needs no database step. Install it with
|
|
141
|
+
`oar update --to 0.9.25` or `npm install -g @oar-sh/oar@0.9.25` (in a
|
|
142
|
+
git checkout: `git checkout v0.9.25`, then a relay restart). The
|
|
143
|
+
settings go back to their old places with their values; a browser that
|
|
144
|
+
last had Appearance open opens General. Formulas follow 0.9.25's rules
|
|
145
|
+
again, Claude plan-mode replies with list lines get cards again, and
|
|
146
|
+
plan cards made on 0.9.26 keep their whole plan.
|
|
147
|
+
|
|
148
|
+
## [0.9.25] — 2026-11-09
|
|
149
|
+
|
|
150
|
+
Highlights: a media gallery, and agents that make images. Every picture,
|
|
151
|
+
video and file of a session is now in one place, the Media tab of the 🧠
|
|
152
|
+
modal, and 🖼️ Media in the ︙ menu shows those of every session of the
|
|
153
|
+
relay, with filters and a search; the file viewer moves through the whole
|
|
154
|
+
gallery. Agents drive OpenAI Image sessions through `relay_tool`: they send
|
|
155
|
+
pictures to edit and set the size, quality, count, format and background.
|
|
156
|
+
While gpt-image works, the reply shows partial previews; the composer gets
|
|
157
|
+
a custom size, a count, a format, a background and presets, and the
|
|
158
|
+
gpt-image-2.5 and 2.6 models take xhigh and max quality. Every image turn
|
|
159
|
+
shows what it cost, a daily cap of USD 5 per relay unless you change it
|
|
160
|
+
keeps image spend in check, and a failed image turn costs at most two calls
|
|
161
|
+
instead of up to five. The relay's memory no longer grows while several
|
|
162
|
+
agents stream long thinking at once.
|
|
163
|
+
|
|
164
|
+
### Added
|
|
165
|
+
|
|
166
|
+
- **Media gallery.** The 🧠 modal has two tabs, Context (where it opens)
|
|
167
|
+
and Media: the session's pictures and videos as a square grid under month
|
|
168
|
+
headers, three columns on a phone, with a Files tab for attached PDFs,
|
|
169
|
+
audio and documents. **🖼️ Media** in the ︙ menu shows the media of every
|
|
170
|
+
session of the relay, with filters (All, Mine, Agent, Generated, a
|
|
171
|
+
provider, a session, Hide archived) and a search. It collects uploads,
|
|
172
|
+
`@file:` pictures, generated images and the pictures agents put in their
|
|
173
|
+
replies; a picture shown in several messages appears once, at its newest
|
|
174
|
+
message, and a deleted local file is left out. A tap opens the file
|
|
175
|
+
viewer, which now moves through the whole gallery (it loads more at the
|
|
176
|
+
end) and offers **Open in session** and **Copy path / link**. The pictures
|
|
177
|
+
on screen that could not load while the relay was away load again once it
|
|
178
|
+
is back; one scrolled out of view waits for the next reconnect.
|
|
179
|
+
Owner only: not in shared views, and no agent reads it through
|
|
180
|
+
`relay_tool`, on this relay or a paired one.
|
|
181
|
+
- **Media index.** The relay keeps the gallery's index in its database
|
|
182
|
+
(migration 0039) and fills it for older messages in the background after
|
|
183
|
+
the upgrade, in short slices.
|
|
184
|
+
- **Agents drive OpenAI Image sessions.** `relay_tool`'s `send` and
|
|
185
|
+
`create_session` take `images`, up to 4 absolute paths of PNG, JPEG or
|
|
186
|
+
WebP files on the target relay, and `image`, the turn's size (any W×H
|
|
187
|
+
within the model's limits), quality, count (1 to 4), output format and
|
|
188
|
+
background; `update_session` with `image` sets a session's own defaults
|
|
189
|
+
(migration 0040). The target relay takes only files inside the session's
|
|
190
|
+
working directory or images one of its sessions generated, follows links
|
|
191
|
+
to the real file, and checks each file's first bytes. Replies list the
|
|
192
|
+
pictures as `images` with their paths there, and a reply without text
|
|
193
|
+
reads like `1 image · 1536x1024 · gpt-image-2`. A paired relay must run
|
|
194
|
+
this version (`imageInputs` in `relay_info`); an older one is refused
|
|
195
|
+
before anything is sent. A new image session without a model starts on
|
|
196
|
+
gpt-image-2.5-flare (else its dated snapshot, else gpt-image-2, else the
|
|
197
|
+
first gpt-image model listed), and is refused when none is listed.
|
|
198
|
+
|
|
199
|
+
- **Image previews.** While a gpt-image model makes an image, the reply's
|
|
200
|
+
bubble shows a timer with the size and quality, then two partial previews
|
|
201
|
+
as OpenAI streams them, then the final image. Previews reach your open
|
|
202
|
+
clients only, never the transcript, shared views, the media gallery or
|
|
203
|
+
agents, and their files go when the turn ends.
|
|
204
|
+
- **Image composer.** An image chat's composer has a Custom… size (any
|
|
205
|
+
W×H the model takes, checked as you type), a Count (1 to 4), a Format
|
|
206
|
+
and a Background for gpt-image models.
|
|
207
|
+
|
|
208
|
+
- **What image turns cost.** Every try of an OpenAI image turn is priced
|
|
209
|
+
as it ends, from the usage OpenAI reports and its published rates, and
|
|
210
|
+
estimated where OpenAI reports no usage for a try that may have been
|
|
211
|
+
billed; a model the relay has no price for counts at the dearest listed
|
|
212
|
+
price. The reply line ends with the cost, the chat menu shows what a
|
|
213
|
+
chat's images cost, and Check Usage has an OpenAI images card.
|
|
214
|
+
- **A daily cap for OpenAI images.** USD 5 per relay and day unless you
|
|
215
|
+
change it (Settings → Providers → OpenAI; 0 is no cap). A request that
|
|
216
|
+
would pass it is not sent, and once it is reached new image messages are
|
|
217
|
+
refused, from you and from agents alike; estimates count toward it. A
|
|
218
|
+
warning shows from 80 %. Allow for today lifts it until midnight.
|
|
219
|
+
- **Image presets.** Five built-in bundles of size, quality, format and
|
|
220
|
+
background, and your own (Save as preset… in the composer); agents name
|
|
221
|
+
one as image.preset.
|
|
222
|
+
- **xhigh and max image quality.** The gpt-image-2.5 and 2.6 models take
|
|
223
|
+
the two higher qualities, in the composer and for agents; other models
|
|
224
|
+
keep auto, low, medium and high. Sizes for gpt-image-2 and later need at
|
|
225
|
+
least 655,360 pixels (1024x640), as OpenAI requires.
|
|
226
|
+
|
|
227
|
+
### Changed
|
|
228
|
+
|
|
229
|
+
- **OpenAI Image turns.** All attached images go to the edit, in order,
|
|
230
|
+
not only the first, and the format and background reach OpenAI. Quality
|
|
231
|
+
is checked per model family before the call (gpt-image: auto, low,
|
|
232
|
+
medium, high). OpenAI gets 3 minutes for a new image and 5 for
|
|
233
|
+
an edit (`openaiImageGenerateTimeoutMs`, `openaiImageEditTimeoutMs`).
|
|
234
|
+
- **Copilot hides image models.** Copilot's `gpt-image-*` and `dall-e-*`
|
|
235
|
+
ids, which its runtime cannot serve, are left out of its model list; a
|
|
236
|
+
Copilot chat on one is refused with a pointer to OpenAI Image.
|
|
237
|
+
|
|
238
|
+
### Fixed
|
|
239
|
+
|
|
240
|
+
- **An image turn costs at most two calls.** A timeout, a server error or
|
|
241
|
+
a broken connection is tried once more, then the turn ends with a note
|
|
242
|
+
that says whether the request may have been billed; before, the turn went
|
|
243
|
+
back into the queue up to five times and ended as "Relay timeout after 5
|
|
244
|
+
attempts", each attempt a new billed request. When the second try works,
|
|
245
|
+
the reply notes that the first may have been billed as well. A relay
|
|
246
|
+
restart during the call no longer repeats it either.
|
|
247
|
+
- **The relay's own image route is local only.** A paired relay's token can
|
|
248
|
+
no longer call it directly, past the queue.
|
|
249
|
+
- **An agent's prompt header no longer reaches the image model.**
|
|
250
|
+
- **Relay memory no longer grows while several agents stream long thinking
|
|
251
|
+
at the same time.** A few sessions thinking at length at once could make
|
|
252
|
+
the relay hold more and more memory until it ran out. Thinking still shows
|
|
253
|
+
live; a slow connection may skip some of the updates in between, and the
|
|
254
|
+
whole thought arrives when it ends and after a reconnect, as before.
|
|
255
|
+
|
|
256
|
+
### Upgrade notes
|
|
257
|
+
|
|
258
|
+
- **The first start takes a little longer on a big database.** Before it
|
|
259
|
+
listens, the relay copies its database to the backups folder, as before,
|
|
260
|
+
and migration 0039 creates the media index and queues every message for
|
|
261
|
+
it once: about 0.2 s per 200,000 messages on a small server. Migration
|
|
262
|
+
0040 adds two columns. Nothing else waits.
|
|
263
|
+
- **The gallery fills in the background.** After the start the relay
|
|
264
|
+
indexes the older messages in slices of about 20 ms every half second,
|
|
265
|
+
in no particular order: about 3 minutes per 200,000 messages on a small
|
|
266
|
+
server, longer on a phone or with long messages. Until it ends, the
|
|
267
|
+
gallery shows what is indexed and "Indexing older messages… (N left)". A
|
|
268
|
+
restart picks up where it stopped.
|
|
269
|
+
- **The daily cap for OpenAI images is on after the update**: USD 5 per
|
|
270
|
+
relay and day, in Settings → Providers → OpenAI (0 is no cap). Without an
|
|
271
|
+
OpenAI key nothing changes, and Check Usage shows no OpenAI images card
|
|
272
|
+
until something was booked in the month.
|
|
273
|
+
- **Sessions still on 0.9.24's worker wait one worker start.** A session
|
|
274
|
+
whose worker still runs the old code is given a new worker before its
|
|
275
|
+
next message, as in 0.9.24. A Claude session's agent gets `images` and
|
|
276
|
+
`image` in `relay_tool` with its new worker.
|
|
277
|
+
- **Mixed versions work together**, except for images across relays: a
|
|
278
|
+
0.9.25 relay refuses to send `images` or `image` to a paired 0.9.24 relay
|
|
279
|
+
and sends nothing, and a 0.9.24 relay cannot send them.
|
|
280
|
+
- **Going back to 0.9.24** needs no database step. Install it with
|
|
281
|
+
`npm install -g @oar-sh/oar@0.9.24` (in a git checkout:
|
|
282
|
+
`git checkout v0.9.24`, then a relay restart). 0.9.24 ignores the media
|
|
283
|
+
index and the image defaults and has no daily image cap, so image turns
|
|
284
|
+
are not limited there. The index's triggers stay in the database and
|
|
285
|
+
queue the messages written on 0.9.24, which a later 0.9.25 indexes. To
|
|
286
|
+
remove the index, drop its two triggers first, then its three tables, as
|
|
287
|
+
`server/migrations/0039-media-index.mjs` describes; a table dropped
|
|
288
|
+
before its trigger breaks every new message.
|
|
289
|
+
|
|
6
290
|
## [0.9.24] — 2026-11-08
|
|
7
291
|
|
|
8
292
|
Highlights: a much smaller install. The Claude and Cursor agent SDKs leave
|
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.
|
|
48
|
+
<a href="https://oar.sh/assets/shots/cfmail-mobile.png?v=0.9.26"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/cfmail-mobile.light.png?v=0.9.26"><img src="https://oar.sh/assets/shots/cfmail-mobile.png?v=0.9.26" 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.
|
|
52
|
+
<a href="https://oar.sh/assets/shots/cfmail-agents.png?v=0.9.26"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/cfmail-agents.light.png?v=0.9.26"><img src="https://oar.sh/assets/shots/cfmail-agents.png?v=0.9.26" 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.
|
|
58
|
+
<a href="https://oar.sh/assets/shots/cfmail-question.png?v=0.9.26"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/cfmail-question.light.png?v=0.9.26"><img src="https://oar.sh/assets/shots/cfmail-question.png?v=0.9.26" 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.
|
|
64
|
+
<a href="https://oar.sh/assets/shots/pingcf-thoughts.png?v=0.9.26"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/pingcf-thoughts.light.png?v=0.9.26"><img src="https://oar.sh/assets/shots/pingcf-thoughts.png?v=0.9.26" 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.
|
|
70
|
+
<a href="https://oar.sh/assets/shots/pagewright-watching.png?v=0.9.26"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/pagewright-watching.light.png?v=0.9.26"><img src="https://oar.sh/assets/shots/pagewright-watching.png?v=0.9.26" alt="A session watching two other sessions: a finished reply arrives as a compact relay-event row, the watching 2 sessions list is open with Unsubscribe, and a request to also watch a session on a paired relay waits on an Allow / Deny card"></picture></a>
|
|
71
71
|
<br><sub>Agents that watch agents: woken by events, approving nothing.</sub>
|
|
72
72
|
</td>
|
|
73
73
|
</tr>
|
|
@@ -100,7 +100,7 @@ The runtime (the *provider* in the UI) is chosen in **New Chat** and stays fixed
|
|
|
100
100
|
| ------- | ---------- | ------------- |
|
|
101
101
|
| [**GitHub Copilot**](docs/providers.md#github-copilot--engine-choice) (default) | GitHub's coding agent, with every model your Copilot plan offers | The Copilot CLI, signed in on the relay host |
|
|
102
102
|
| [**OpenAI (BYOK)**](docs/providers.md#openai-byok-and-openai-image) | Copilot's agent against OpenAI or a compatible endpoint | Your own API key |
|
|
103
|
-
| [**OpenAI Image**](docs/providers.md#openai-byok-and-openai-image) | A chat whose replies are images | The same key |
|
|
103
|
+
| [**OpenAI Image**](docs/providers.md#openai-byok-and-openai-image) | A chat whose replies are images, with a daily spending cap; agents can use it too | The same key |
|
|
104
104
|
| [**Claude**](docs/providers.md#claude-agent-sdk) | Claude Code on your machine | A Claude login, made from **⚙️ Settings** |
|
|
105
105
|
| [**Claude API**](docs/providers.md#claude-api) | Claude Code on your machine, billed to a Claude Console API key; a Claude chat can switch between the two | A Claude Console API key |
|
|
106
106
|
| [**Claude Cloud**](docs/providers.md#claude-cloud) (experimental) | Claude Code in a sandbox at Anthropic, on a GitHub repository | A claude.ai account with GitHub connected |
|
|
@@ -118,11 +118,11 @@ API keys are entered in **⚙️ Settings** and stay on the relay host. The Clau
|
|
|
118
118
|
<table>
|
|
119
119
|
<tr>
|
|
120
120
|
<td width="62%" valign="top">
|
|
121
|
-
<a href="https://oar.sh/assets/shots/cfmail-steering.png?v=0.9.
|
|
121
|
+
<a href="https://oar.sh/assets/shots/cfmail-steering.png?v=0.9.26"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/cfmail-steering.light.png?v=0.9.26"><img src="https://oar.sh/assets/shots/cfmail-steering.png?v=0.9.26" 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.
|
|
125
|
+
<a href="https://oar.sh/assets/shots/usage-copilot-compact.png?v=0.9.26"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/usage-copilot-compact.light.png?v=0.9.26"><img src="https://oar.sh/assets/shots/usage-copilot-compact.png?v=0.9.26" alt="The GitHub Copilot card in Check Usage: premium requests and plan quota with reset dates, and the cost of the last turn"></picture></a>
|
|
126
126
|
<br><sub>What each plan has left, in one place.</sub>
|
|
127
127
|
</td>
|
|
128
128
|
</tr>
|
|
@@ -134,12 +134,13 @@ API keys are entered in **⚙️ Settings** and stay on the relay host. The Clau
|
|
|
134
134
|
- **Background work**: a task panel lists the commands and sub-agents that outlive a reply, each with its own **Stop** and its full record; a command's console shows its output live, says when it has gone quiet and counts a test run's passes and failures; the panel and its consoles come back after a relay restart, and a queued restart shows what it waits for and asks after a maximum wait ([Background tasks](docs/web-ui.md#background-tasks), [After a relay restart](docs/web-ui.md#after-a-relay-restart)).
|
|
135
135
|
- **Choose how the agent works**: `ask`, `plan`, `agent` or `autopilot`, plus model and reasoning effort, per message, from the composer's **⚙** ([The composer](docs/web-ui.md#the-composer), [Relay modes](docs/web-ui.md#relay-modes)).
|
|
136
136
|
- **Files and git**: browse the workspace, preview files, review git changes in a diff viewer, and publish a dev server as a preview link ([Files, git, and previews](docs/web-ui.md#files-git-and-previews), [preview servers](docs/preview-servers.md)).
|
|
137
|
-
- **
|
|
137
|
+
- **Media**: the pictures and videos of a session, or of every session, in one gallery, with a file viewer that moves through all of them ([Media](docs/web-ui.md#media)).
|
|
138
|
+
- **Usage and context**: plan limits and credits of every provider, the estimated spend of DeepSeek and Claude API keys and of OpenAI images, and what fills the model's context window ([Usage and context](docs/web-ui.md#usage-and-context)).
|
|
138
139
|
- **Your conversations everywhere**: the history lives on the relay, drafts follow you from device to device, and conversations can be searched, archived, grouped and pinned ([The conversation list](docs/web-ui.md#the-conversation-list), [drafts](docs/web-ui.md#drafts-across-devices), [session groups](docs/web-ui.md#session-groups), [pins](docs/web-ui.md#pinned-messages)).
|
|
139
140
|
- **Share** a conversation as a read-only link, hide single messages and the host's paths from it, and see when it was opened ([Sharing](docs/web-ui.md#sharing)).
|
|
140
141
|
- **On your phone**: install OAR as an app and get push notifications; back walks through the conversations you opened, a pinch zooms the whole app, and held sideways it keeps a phone's layout ([Notifications and the app](docs/web-ui.md#notifications-and-the-app), [back and forward](docs/web-ui.md#back-and-forward), [zoom](docs/web-ui.md#zoom), [a phone held sideways](docs/web-ui.md#a-phone-held-sideways)).
|
|
141
142
|
|
|
142
|
-
The full tour is in [Using the web UI](docs/web-ui.md); every setting and config key is in [Settings and configuration](docs/configuration.md). What changed in each release is in the [changelog](CHANGELOG.md), with [0.9.
|
|
143
|
+
The full tour is in [Using the web UI](docs/web-ui.md); every setting and config key is in [Settings and configuration](docs/configuration.md). What changed in each release is in the [changelog](CHANGELOG.md), with [0.9.26](CHANGELOG.md#0926--2026-11-11) as the latest.
|
|
143
144
|
|
|
144
145
|
## Remote access
|
|
145
146
|
|
|
@@ -190,7 +191,7 @@ flowchart LR
|
|
|
190
191
|
|
|
191
192
|
You stay in charge: per paired relay you decide whether agents may only read, also prompt, or do everything, and in the `ask` and `plan` modes every write asks you first. A subscription only notifies; it approves nothing.
|
|
192
193
|
|
|
193
|
-
<p><a href="https://oar.sh/assets/shots/pagewright-cross-relay.png?v=0.9.
|
|
194
|
+
<p><a href="https://oar.sh/assets/shots/pagewright-cross-relay.png?v=0.9.26"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/pagewright-cross-relay.light.png?v=0.9.26"><img src="https://oar.sh/assets/shots/pagewright-cross-relay.png?v=0.9.26" alt="A session watching two sessions on a paired relay: a reply arrives as a relay-event row, and after a short outage the events the other relay kept arrive as one replayed row" width="720"></picture></a></p>
|
|
194
195
|
|
|
195
196
|
How pairing, permissions and limits work is in [Agents working together](docs/agents-together.md).
|
|
196
197
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oar-sh/oar",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.26",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "OAR — Open Agent Relay: drive your local coding agents from any browser",
|
|
6
6
|
"license": "MIT",
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"server/",
|
|
26
26
|
"!server/**/*.test.mjs",
|
|
27
27
|
"!server/**/*-test-harness.mjs",
|
|
28
|
+
"!server/**/*-test-fixtures.mjs",
|
|
28
29
|
"!server/**/fixtures/",
|
|
29
30
|
"!server/README.md",
|
|
30
31
|
"!server/data/",
|
|
@@ -36,6 +37,7 @@
|
|
|
36
37
|
"shared/",
|
|
37
38
|
"!shared/**/*.test.mjs",
|
|
38
39
|
"!shared/**/*-test-harness.mjs",
|
|
40
|
+
"!shared/**/*-test-fixtures.mjs",
|
|
39
41
|
"!shared/**/fixtures/",
|
|
40
42
|
".github/extensions/web-relay/",
|
|
41
43
|
"!.github/extensions/web-relay/**/*.test.mjs",
|
|
@@ -72,7 +74,9 @@
|
|
|
72
74
|
"@modelcontextprotocol/sdk": "1.32.2",
|
|
73
75
|
"@playwright/test": "^1.60.0",
|
|
74
76
|
"jsdom": "^30.0.1",
|
|
77
|
+
"katex": "0.19.1",
|
|
75
78
|
"koffi": "3.3.4",
|
|
79
|
+
"marked": "15.0.12",
|
|
76
80
|
"zod": "4.6.7"
|
|
77
81
|
},
|
|
78
82
|
"overrides": {
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
REMOTE_RELAY_THINKING_DISPLAYS,
|
|
16
16
|
} from '../../shared/remote-relay-contract.mjs';
|
|
17
17
|
import { RELAY_ASK_URGENCIES } from '../../shared/relay-asks.mjs';
|
|
18
|
+
import { OPENAI_IMAGE_BACKGROUNDS, OPENAI_IMAGE_OUTPUT_FORMATS } from '../../shared/openai-image-options.mjs';
|
|
18
19
|
import {
|
|
19
20
|
REMOTE_RELAY_CALL_FAILED,
|
|
20
21
|
REMOTE_RELAY_TOOL_DESCRIPTION,
|
|
@@ -80,6 +81,20 @@ export const REMOTE_RELAY_TOOL_ZOD_SHAPE = {
|
|
|
80
81
|
thinking_enabled: z.boolean().optional().describe(FIELDS.thinking_enabled.description),
|
|
81
82
|
planOnly: z.boolean().optional().describe(FIELDS.planOnly.description),
|
|
82
83
|
thinking_display: choice('thinking_display', REMOTE_RELAY_THINKING_DISPLAYS),
|
|
84
|
+
// A list, or one path as text, which the contract takes as well.
|
|
85
|
+
images: z.union([z.array(z.string()), z.string()]).optional().describe(FIELDS.images.description),
|
|
86
|
+
// Loose like quiet_hours: the contract's validator checks the values per
|
|
87
|
+
// model family and names an unknown key; the two fixed lists are enums as
|
|
88
|
+
// in the JSON schema.
|
|
89
|
+
image: z.looseObject({
|
|
90
|
+
size: z.string().optional().describe(FIELDS.image.properties.size.description),
|
|
91
|
+
quality: z.string().optional().describe(FIELDS.image.properties.quality.description),
|
|
92
|
+
count: z.number().optional().describe(FIELDS.image.properties.count.description),
|
|
93
|
+
output_format: z.enum([...OPENAI_IMAGE_OUTPUT_FORMATS]).optional().describe(FIELDS.image.properties.output_format.description),
|
|
94
|
+
background: z.enum([...OPENAI_IMAGE_BACKGROUNDS]).optional().describe(FIELDS.image.properties.background.description),
|
|
95
|
+
// Free text: the target relay knows its presets and resolves the name.
|
|
96
|
+
preset: z.string().optional().describe(FIELDS.image.properties.preset.description),
|
|
97
|
+
}).optional().describe(FIELDS.image.description),
|
|
83
98
|
question_id: text('question_id'),
|
|
84
99
|
// ask: the contract's validator refuses a question or detail that is too
|
|
85
100
|
// long, or more than eight options, with a message the model can act on.
|
|
@@ -3,6 +3,7 @@ import { loadAgentSdk } from '../agent-sdk-loader.mjs';
|
|
|
3
3
|
import { formatClaudeBackgroundCommandLimitLabel } from '../../shared/claude-background-command-limit.mjs';
|
|
4
4
|
import { parseThinkingDisplay } from '../../shared/claude-thinking.mjs';
|
|
5
5
|
import { PERSON_ONLY_QUESTION_SOURCE } from '../../shared/relay-asks.mjs';
|
|
6
|
+
import { MATH_INSTRUCTION_TEXT } from '../../shared/math-instructions.mjs';
|
|
6
7
|
import { MEDIA_EMBED_INSTRUCTION_TEXT } from '../../shared/media-embed-instructions.mjs';
|
|
7
8
|
import { REMOTE_RELAY_TOOL_CALL_TIMEOUT_MS } from '../../shared/remote-relay-tool-core.mjs';
|
|
8
9
|
import { createPreviewToolDefinition } from './claude-preview-tool.mjs';
|
|
@@ -205,10 +206,12 @@ export function longCommandInstructionText(backgroundCommandLimitMs = null) {
|
|
|
205
206
|
export function systemPromptForRelayMode(relayMode, { backgroundCommandLimitMs = null } = {}) {
|
|
206
207
|
const mode = String(relayMode || 'agent').trim().toLowerCase();
|
|
207
208
|
// The media-embed capability rides every session — unlike the mode nudges it
|
|
208
|
-
// is renderer knowledge the model cannot discover any other way
|
|
209
|
-
//
|
|
209
|
+
// is renderer knowledge the model cannot discover any other way; so is the
|
|
210
|
+
// notation the web app renders formulas from. The long command note rides
|
|
211
|
+
// every session too: the limit is the relay's setting.
|
|
210
212
|
const append = [
|
|
211
213
|
MEDIA_EMBED_INSTRUCTION_TEXT,
|
|
214
|
+
MATH_INSTRUCTION_TEXT,
|
|
212
215
|
longCommandInstructionText(backgroundCommandLimitMs),
|
|
213
216
|
MODE_SYSTEM_PROMPT_APPEND[mode],
|
|
214
217
|
]
|
|
@@ -792,8 +795,9 @@ const EXIT_PLAN_DENY_MESSAGE = 'Plan received — it is shown to the user in the
|
|
|
792
795
|
+ 'End the turn now with a short closing message and do not start implementing; '
|
|
793
796
|
+ 'the user\'s decision will arrive as a new message.';
|
|
794
797
|
// When the handler could not surface a board (no plan text reached it), the
|
|
795
|
-
// model must restate the plan as its final message:
|
|
796
|
-
//
|
|
798
|
+
// model must restate the plan as its final message: after a hand-over without
|
|
799
|
+
// a card, the turn's reply is posted as the plan (publishCompletedTurn), so
|
|
800
|
+
// the user still gets the review UI.
|
|
797
801
|
const EXIT_PLAN_DENY_NO_BOARD_MESSAGE = 'Plan received, but it could not be shown for review. '
|
|
798
802
|
+ 'Restate the complete plan as your final message, then end the turn without implementing; '
|
|
799
803
|
+ 'the user\'s decision will arrive as a new message.';
|
|
@@ -876,6 +876,8 @@ export function createClaudeSessionRunner({
|
|
|
876
876
|
modelUsage: null,
|
|
877
877
|
},
|
|
878
878
|
planBoardPosted: false,
|
|
879
|
+
// The CLI handed a plan over (ExitPlanMode), whether or not a card came of it.
|
|
880
|
+
planBoardRequested: false,
|
|
879
881
|
// The CLI process the turn ran in, as its usage report names it: taken
|
|
880
882
|
// from the process here and again when the context is made active, and
|
|
881
883
|
// read from the context at finalize — a late continuation flush
|
|
@@ -1569,6 +1571,7 @@ export function createClaudeSessionRunner({
|
|
|
1569
1571
|
state: ctx.state,
|
|
1570
1572
|
responseModel,
|
|
1571
1573
|
planBoardPosted: ctx.planBoardPosted,
|
|
1574
|
+
planBoardRequested: ctx.planBoardRequested,
|
|
1572
1575
|
consumedSteerIds,
|
|
1573
1576
|
});
|
|
1574
1577
|
}
|
|
@@ -3789,6 +3792,7 @@ export function createClaudeSessionRunner({
|
|
|
3789
3792
|
onExitPlanMode: async (input) => {
|
|
3790
3793
|
const ctx = processRef.activeCtx || processRef.pendingDelivered[0]?.ctx || null;
|
|
3791
3794
|
if (!ctx?.message?.id) return false;
|
|
3795
|
+
ctx.planBoardRequested = true;
|
|
3792
3796
|
const posted = await publisher.publishPlanBoard(ctx.message, input);
|
|
3793
3797
|
if (posted) ctx.planBoardPosted = true;
|
|
3794
3798
|
return posted;
|
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
classifyClaudeResultFailure,
|
|
7
7
|
claudeFailureGuidance,
|
|
8
8
|
} from './claude-turn-failure.mjs';
|
|
9
|
-
import { countPlanLikeLines } from '../../shared/plan-lines.mjs';
|
|
10
9
|
import { PLAN_BOARD_ACTIONS } from '../../shared/plan-board-actions.mjs';
|
|
11
10
|
import { EMPTY_TURN_COMPLETION_NOTE } from '../../shared/empty-turn-completion.mjs';
|
|
12
11
|
import { buildSteerSettleFailure, steerSettleFailureText } from '../../shared/steer-settle-failure.mjs';
|
|
@@ -34,9 +33,10 @@ export function isStaleAttemptError(error) {
|
|
|
34
33
|
export { buildSteerSettleFailure, steerSettleFailureText };
|
|
35
34
|
export const STEER_SETTLE_FAILED_TEXT = steerSettleFailureText('folded');
|
|
36
35
|
|
|
37
|
-
export function buildClaudePlanReadyBoardPayload({ message, planText = '' } = {}) {
|
|
36
|
+
export function buildClaudePlanReadyBoardPayload({ message, planText = '', planFilePath = '' } = {}) {
|
|
38
37
|
const summary = String(planText || '').trim();
|
|
39
38
|
if (!summary) return null;
|
|
39
|
+
const filePath = String(planFilePath || '').trim();
|
|
40
40
|
return {
|
|
41
41
|
queueId: message?.id,
|
|
42
42
|
messageId: message?.id,
|
|
@@ -52,6 +52,8 @@ export function buildClaudePlanReadyBoardPayload({ message, planText = '' } = {}
|
|
|
52
52
|
queueMessageId: message?.id || null,
|
|
53
53
|
conversationId: message?.conversationId || null,
|
|
54
54
|
relayMode: message?.relayMode || 'agent',
|
|
55
|
+
// Named on the card when the relay has to cut a very long plan.
|
|
56
|
+
...(filePath ? { planFilePath: filePath } : {}),
|
|
55
57
|
},
|
|
56
58
|
};
|
|
57
59
|
}
|
|
@@ -797,13 +799,13 @@ export function createClaudeTurnPublisher({
|
|
|
797
799
|
* Post the plan-ready review board for an ExitPlanMode interception. The CLI
|
|
798
800
|
* sends both `plan` and `planFilePath`; the inline plan is authoritative, the
|
|
799
801
|
* plan file covers CLI versions that stop inlining it. Returns false when no
|
|
800
|
-
* plan text could be recovered so the
|
|
801
|
-
*
|
|
802
|
+
* plan text could be recovered, so the turn's reply can stand in for it
|
|
803
|
+
* (see publishCompletedTurn).
|
|
802
804
|
*/
|
|
803
805
|
async function publishPlanBoard(message, input) {
|
|
804
806
|
let planText = String(input?.plan || input?.summary || '').trim();
|
|
807
|
+
const planFilePath = String(input?.planFilePath || '').trim();
|
|
805
808
|
if (!planText) {
|
|
806
|
-
const planFilePath = String(input?.planFilePath || '').trim();
|
|
807
809
|
if (planFilePath) {
|
|
808
810
|
try {
|
|
809
811
|
planText = String(fs.readFileSync(planFilePath, 'utf8') || '').trim();
|
|
@@ -812,7 +814,7 @@ export function createClaudeTurnPublisher({
|
|
|
812
814
|
}
|
|
813
815
|
}
|
|
814
816
|
}
|
|
815
|
-
const boardPayload = buildClaudePlanReadyBoardPayload({ message, planText });
|
|
817
|
+
const boardPayload = buildClaudePlanReadyBoardPayload({ message, planText, planFilePath });
|
|
816
818
|
if (!boardPayload) return false;
|
|
817
819
|
await api('POST', '/api/relay-board', boardPayload).catch((error) => {
|
|
818
820
|
dbg('plan board publish failed', error?.message || String(error));
|
|
@@ -822,18 +824,30 @@ export function createClaudeTurnPublisher({
|
|
|
822
824
|
|
|
823
825
|
/**
|
|
824
826
|
* Publish a completed (non-error, non-aborted) turn: final stream snapshot,
|
|
825
|
-
* plan-mode fallback board, response row.
|
|
826
|
-
*
|
|
827
|
+
* plan-mode fallback board, response row.
|
|
828
|
+
*
|
|
829
|
+
* A plan card comes only from a plan hand-over (ExitPlanMode). The fallback
|
|
830
|
+
* covers one case: the CLI handed a plan over but its text could not be
|
|
831
|
+
* read, so the reply is the plan. A reply is never taken for a plan because
|
|
832
|
+
* of its list lines: in a session that stays in plan mode, every status
|
|
833
|
+
* reply with two bullets got a card.
|
|
827
834
|
*/
|
|
828
|
-
async function publishCompletedTurn({
|
|
835
|
+
async function publishCompletedTurn({
|
|
836
|
+
message,
|
|
837
|
+
state,
|
|
838
|
+
responseModel,
|
|
839
|
+
planBoardPosted,
|
|
840
|
+
planBoardRequested = false,
|
|
841
|
+
consumedSteerIds = null,
|
|
842
|
+
}) {
|
|
829
843
|
const result = state.result;
|
|
830
844
|
const finalText = String(
|
|
831
845
|
state.resultTexts.join('\n\n') || result?.text || state.lastStreamedText || '',
|
|
832
846
|
).trim();
|
|
833
847
|
if (
|
|
834
|
-
|
|
848
|
+
planBoardRequested
|
|
849
|
+
&& !planBoardPosted
|
|
835
850
|
&& String(message.relayMode || '').trim().toLowerCase() === 'plan'
|
|
836
|
-
&& countPlanLikeLines(finalText) >= 2
|
|
837
851
|
) {
|
|
838
852
|
const boardPayload = buildClaudePlanReadyBoardPayload({ message, planText: finalText });
|
|
839
853
|
if (boardPayload) {
|