@oar-sh/oar 0.9.25 → 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/CHANGELOG.md +142 -0
- package/README.md +9 -9
- package/package.json +3 -1
- 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/copilot-worker/copilot-prompt-context.mjs +2 -1
- package/server/cursor-worker/cursor-turn-runner.mjs +3 -1
- package/server/grok-worker/grok-turn-runner.mjs +3 -1
- package/server/public/app/context-usage-view.mjs +2 -2
- package/server/public/app/live-activity-cap.mjs +3 -3
- package/server/public/app/markdown-math.mjs +471 -0
- package/server/public/app/pwa-install.js +25 -4
- 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/settings-tabs.js +10 -1
- package/server/public/app/store.js +2 -0
- package/server/public/app/stream-view-preference.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/index.html +134 -116
- package/server/routes/relay-board-routes.mjs +46 -1
- package/server/routes/remote-relay-routes.mjs +1 -1
- package/server/server-runtime.mjs +2 -0
- package/server/services/relay-prompt-sanitizer.mjs +7 -0
- package/server/services/relay-restart-queue.mjs +1 -1
- package/server/services/remote-relay-dispatcher.mjs +23 -1
- package/server/services/remote-relay-registry-service.mjs +7 -1
- package/server/services/session-worker-stop-service.mjs +50 -18
- package/shared/math-instructions.mjs +30 -0
- package/server/public/app/math-dollars.mjs +0 -89
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,148 @@
|
|
|
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
|
+
|
|
6
148
|
## [0.9.25] — 2026-11-09
|
|
7
149
|
|
|
8
150
|
Highlights: a media gallery, and agents that make images. Every picture,
|
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>
|
|
@@ -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>
|
|
@@ -140,7 +140,7 @@ API keys are entered in **⚙️ Settings** and stay on the relay host. The Clau
|
|
|
140
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)).
|
|
141
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)).
|
|
142
142
|
|
|
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.
|
|
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.
|
|
144
144
|
|
|
145
145
|
## Remote access
|
|
146
146
|
|
|
@@ -191,7 +191,7 @@ flowchart LR
|
|
|
191
191
|
|
|
192
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.
|
|
193
193
|
|
|
194
|
-
<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>
|
|
195
195
|
|
|
196
196
|
How pairing, permissions and limits work is in [Agents working together](docs/agents-together.md).
|
|
197
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",
|
|
@@ -74,7 +74,9 @@
|
|
|
74
74
|
"@modelcontextprotocol/sdk": "1.32.2",
|
|
75
75
|
"@playwright/test": "^1.60.0",
|
|
76
76
|
"jsdom": "^30.0.1",
|
|
77
|
+
"katex": "0.19.1",
|
|
77
78
|
"koffi": "3.3.4",
|
|
79
|
+
"marked": "15.0.12",
|
|
78
80
|
"zod": "4.6.7"
|
|
79
81
|
},
|
|
80
82
|
"overrides": {
|
|
@@ -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) {
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
applyPreviewInstructions,
|
|
26
26
|
createPreviewInstructionsProvider,
|
|
27
27
|
} from '../../shared/preview-instructions.mjs';
|
|
28
|
+
import { applyMathInstructions } from '../../shared/math-instructions.mjs';
|
|
28
29
|
import { applyMediaEmbedInstructions } from '../../shared/media-embed-instructions.mjs';
|
|
29
30
|
import { buildModePrompt } from '../../shared/worker-runtime/prompt-context.mjs';
|
|
30
31
|
import { loadRelayInstructionsFromFile } from '../../shared/worker-runtime/config-loader.mjs';
|
|
@@ -84,7 +85,7 @@ export function createCopilotPromptContextBuilder({
|
|
|
84
85
|
.then(() => getPreviewInstructions({ conversationId: String(message?.conversationId || '') }))
|
|
85
86
|
.catch(() => '')
|
|
86
87
|
: '';
|
|
87
|
-
instructions = applyMediaEmbedInstructions(applyPreviewInstructions(toolInstructions, previewBlock));
|
|
88
|
+
instructions = applyMathInstructions(applyMediaEmbedInstructions(applyPreviewInstructions(toolInstructions, previewBlock)));
|
|
88
89
|
}
|
|
89
90
|
const prefix = buildModePrompt(relayMode, instructions, { includeInstructions });
|
|
90
91
|
return {
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
cursorSubagentRosterFingerprint,
|
|
21
21
|
} from './cursor-subagent-roster.mjs';
|
|
22
22
|
import { createAskUserBridge } from '../../shared/ask-user-bridge.mjs';
|
|
23
|
+
import { renderMathInstructionBlock } from '../../shared/math-instructions.mjs';
|
|
23
24
|
import { renderMediaEmbedInstructionBlock } from '../../shared/media-embed-instructions.mjs';
|
|
24
25
|
import { EMPTY_TURN_COMPLETION_NOTE } from '../../shared/empty-turn-completion.mjs';
|
|
25
26
|
import { resolveFallbackContextLimitTokens } from '../../shared/context-window-fallbacks.mjs';
|
|
@@ -586,7 +587,8 @@ export function createCursorTurnRunner({
|
|
|
586
587
|
await ensureAgentHandle(message, model);
|
|
587
588
|
const userMessage = buildUserMessageImpl(message);
|
|
588
589
|
const modeNudge = cursorModeNudge(message.relayMode, lastNudgedRelayMode);
|
|
589
|
-
|
|
590
|
+
// The formula notation rides with it, for the same reason.
|
|
591
|
+
const mediaGuidance = mediaGuidanceSent ? '' : `${renderMediaEmbedInstructionBlock()}\n\n${renderMathInstructionBlock()}`;
|
|
590
592
|
if (modeNudge || mediaGuidance) {
|
|
591
593
|
userMessage.text = [mediaGuidance, modeNudge, userMessage.text].filter(Boolean).join('\n\n');
|
|
592
594
|
}
|
|
@@ -10,6 +10,7 @@ import { extractGrokUsageFromPromptResult, normalizeGrokTurnUsage } from '../ser
|
|
|
10
10
|
import { OAR_MCP_SERVER_NAME, buildOarMcpServerLaunch, isOarMcpServerDisabled } from '../mcp/oar-mcp-server.mjs';
|
|
11
11
|
import { createPreviewInstructionsProvider } from '../../shared/preview-instructions.mjs';
|
|
12
12
|
import { fetchRemoteRelayInflight } from '../../shared/remote-relay-tool-core.mjs';
|
|
13
|
+
import { renderMathInstructionBlock } from '../../shared/math-instructions.mjs';
|
|
13
14
|
import { renderMediaEmbedInstructionBlock } from '../../shared/media-embed-instructions.mjs';
|
|
14
15
|
import { countPlanLikeLines } from '../../shared/plan-lines.mjs';
|
|
15
16
|
import { PLAN_BOARD_ACTIONS } from '../../shared/plan-board-actions.mjs';
|
|
@@ -476,7 +477,8 @@ export function createGrokTurnRunner({
|
|
|
476
477
|
: await Promise.resolve()
|
|
477
478
|
.then(() => getPreviewInstructions?.({ conversationId: message.conversationId || sdkSessionId || '' }))
|
|
478
479
|
.catch(() => '');
|
|
479
|
-
|
|
480
|
+
// The formula notation rides with the media block, once per worker.
|
|
481
|
+
const mediaBlock = mediaInstructionsSent ? '' : `${renderMediaEmbedInstructionBlock()}\n\n${renderMathInstructionBlock()}`;
|
|
480
482
|
const promptText = [previewBlock, mediaBlock, modeNudge, userText].filter(Boolean).join('\n\n');
|
|
481
483
|
const pendingNudgedRelayMode = String(message.relayMode || 'agent').trim().toLowerCase();
|
|
482
484
|
|
|
@@ -356,7 +356,7 @@ const THOUGHTS_EXPANDED_CHOICES = Object.freeze([
|
|
|
356
356
|
]);
|
|
357
357
|
|
|
358
358
|
/**
|
|
359
|
-
* The mirror of Settings ›
|
|
359
|
+
* The mirror of Settings › Appearance "Expand thoughts while an agent works", for
|
|
360
360
|
* every provider. A setting of this browser, so nothing goes to the relay:
|
|
361
361
|
* the buttons write thoughts-preference.mjs and both places follow it.
|
|
362
362
|
*
|
|
@@ -390,7 +390,7 @@ const STREAM_VIEW_CHOICES = Object.freeze([
|
|
|
390
390
|
]);
|
|
391
391
|
|
|
392
392
|
/**
|
|
393
|
-
* The mirror of Settings ›
|
|
393
|
+
* The mirror of Settings › Appearance "Stream view", for every provider. A
|
|
394
394
|
* setting of this browser (stream-view-preference.mjs); nothing goes to the
|
|
395
395
|
* relay.
|
|
396
396
|
*
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
// "Tool lines shown while an agent works" (Settings ›
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// "Tool lines shown while an agent works" (Settings › Appearance): how many
|
|
2
|
+
// of its newest tool lines each agent of a running turn keeps in the live
|
|
3
|
+
// bubble, the main agent and every sub-agent apart.
|
|
4
4
|
// A setting of this browser, kept in localStorage like the scroll-on-send
|
|
5
5
|
// one. The slider runs from 10 to 500 by 10 and has one more step at the far
|
|
6
6
|
// end, no limit (stored as '0'). The document and the storage are passed in
|