@oar-sh/oar 0.9.11 → 0.9.12
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 +126 -0
- package/README.md +10 -10
- package/package.json +1 -1
- package/server/README.md +17 -3
- package/server/claude-worker/claude-remote-relay-tool.mjs +2 -0
- package/server/claude-worker/claude-sdk-adapter.mjs +57 -4
- package/server/claude-worker/claude-session-process.mjs +128 -0
- package/server/claude-worker/claude-session-worker.mjs +17 -0
- package/server/claude-worker/task-output-discovery.mjs +126 -0
- package/server/copilot-worker/copilot-sdk-session-process.mjs +159 -3
- package/server/copilot-worker/copilot-sdk-session-worker.mjs +2 -0
- package/server/copilot-worker/copilot-shell-output-mirror.mjs +94 -0
- package/server/copilot-worker/copilot-task-settle.mjs +96 -0
- package/server/db-schema.mjs +9 -0
- package/server/grok-worker/acp-host-services.mjs +42 -3
- package/server/grok-worker/grok-background-tasks.mjs +241 -0
- package/server/grok-worker/grok-sdk-adapter.mjs +3 -1
- package/server/grok-worker/grok-session-worker.mjs +33 -1
- package/server/grok-worker/grok-turn-runner.mjs +11 -0
- package/server/migrations/0017-conversation-history-indexes.mjs +89 -0
- package/server/public/app/api-client.js +13 -0
- package/server/public/app/background-tasks-view.mjs +285 -16
- package/server/public/app/bootstrap.js +95 -5
- package/server/public/app/claude-background-command-limit-options.mjs +21 -0
- package/server/public/app/conversation-payload-cache.mjs +357 -0
- package/server/public/app/conversation-turn-state.mjs +1 -1
- package/server/public/app/conversation-view.js +433 -3
- package/server/public/app/conversation-window-retention.mjs +96 -0
- package/server/public/app/journal-view.js +252 -66
- package/server/public/app/relay-prompt-sanitizer.mjs +11 -4
- package/server/public/app/settings-modal.js +110 -2
- package/server/public/app/socket-handlers.js +59 -7
- package/server/public/app/store.js +25 -0
- package/server/public/app/subagent-viewer.mjs +7 -1
- package/server/public/app/task-console-render.mjs +237 -0
- package/server/public/app/task-console-watch.mjs +42 -0
- package/server/public/app/task-console.mjs +630 -0
- package/server/public/app/task-output-text.mjs +234 -0
- package/server/public/app/transcript-island.mjs +73 -0
- package/server/public/app/transcript-jump-bottom-view.mjs +38 -8
- package/server/public/app/transcript-jump-bottom.mjs +48 -11
- package/server/public/app/transcript-reading-anchor.mjs +256 -0
- package/server/public/app/transcript-reload-freshness.mjs +17 -1
- package/server/public/app/transcript-viewport-anchor.mjs +20 -1
- package/server/public/index.html +239 -6
- package/server/relay-tools.md +1 -0
- package/server/repositories/message-repository.mjs +8 -0
- package/server/repositories/question-repository.mjs +4 -0
- package/server/routes/messages-routes.mjs +249 -3
- package/server/routes/sessions-routes.mjs +133 -63
- package/server/server-runtime.mjs +77 -0
- package/server/services/background-task-store.mjs +240 -3
- package/server/services/cli-process-runner.mjs +4 -21
- package/server/services/conversation-history-page.mjs +199 -0
- package/server/services/relay-prompt-sanitizer.mjs +11 -4
- package/server/services/remote-relay-dispatcher.mjs +130 -2
- package/server/services/session-activity-tracker.mjs +6 -2
- package/server/services/task-output-rooms.mjs +259 -0
- package/server/services/task-output-service.mjs +562 -0
- package/shared/claude-background-command-limit.mjs +83 -0
- package/shared/remote-relay-contract.mjs +32 -2
- package/shared/task-output-text.mjs +737 -0
- package/shared/worker-runtime/task-output-spool.mjs +181 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,132 @@
|
|
|
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.12] — 2026-10-18
|
|
7
|
+
|
|
8
|
+
Highlights: a background command or monitor in the task panel opens a
|
|
9
|
+
console with its output, live, in colour and with secrets blanked, paging
|
|
10
|
+
back through the whole log and with its own **Stop**. Task rows say when a
|
|
11
|
+
task has gone quiet ("no output for 12 min", amber after a time you set in
|
|
12
|
+
Settings) and show a **✓ 412 ✘ 2** chip when the output is a test run;
|
|
13
|
+
sub-agent rows count their steps, and agents read a task's output on a
|
|
14
|
+
paired relay with `remote_relay` `read_task_output`. Long conversations open
|
|
15
|
+
and page back fast, and switching away and back, or reloading, returns to
|
|
16
|
+
the message you were reading, older pages included; new messages wait below
|
|
17
|
+
a place far up the history instead of skipping the ones in between.
|
|
18
|
+
|
|
19
|
+
### Upgrade notes
|
|
20
|
+
|
|
21
|
+
- Migration 0017 (two indexes for the history of long conversations) runs by
|
|
22
|
+
itself on the first start. The Node.js requirement is unchanged (22.13 or
|
|
23
|
+
newer), and no setting or config key needs changing.
|
|
24
|
+
- Two new settings in **Settings › General**: **Mark a background task as
|
|
25
|
+
quiet after** (10 minutes by default, for the relay) and **Messages kept
|
|
26
|
+
per conversation for switching back** (200 by default, per browser).
|
|
27
|
+
- **Claude background commands may run for 2 hours** before Claude Code
|
|
28
|
+
stops them, and up to 8 hours when Claude asks; **Settings › Providers ›
|
|
29
|
+
Claude** sets the limit. OAR now sets `BASH_DEFAULT_TIMEOUT_MS`, `BASH_MAX_TIMEOUT_MS` and
|
|
30
|
+
`CLAUDE_CODE_AUTO_BACKGROUND_TIMEOUT_MS` for the Claude sessions it starts,
|
|
31
|
+
so values of these in the relay's environment no longer apply. Commands
|
|
32
|
+
that start with `sleep`, and sub-agents' commands, are not moved to the
|
|
33
|
+
background after 2 minutes and may block a turn up to the limit.
|
|
34
|
+
|
|
35
|
+
### Added
|
|
36
|
+
|
|
37
|
+
- **Click a background command to watch its output.** A backgrounded shell
|
|
38
|
+
command or monitor in the composer's task panel opens a console: the last
|
|
39
|
+
2,000 lines, following live, with **Load earlier** paging back through the
|
|
40
|
+
whole log (about 5 MB at most). Progress-bar redraws collapse to their last
|
|
41
|
+
state, repeated lines fold into one with ×N, colours are kept and other
|
|
42
|
+
control codes dropped, secrets are blanked, and the console has its own
|
|
43
|
+
**Stop**. A finished command keeps a dimmed ✓/✘ row for an hour so a failed
|
|
44
|
+
run can still be read. Claude reads the file its CLI writes for the command,
|
|
45
|
+
Copilot a detached shell's log or an attached shell's recent output, and
|
|
46
|
+
Grok now lists its terminals as tasks. A session started before this
|
|
47
|
+
release names no output file; its console says so and that a restart of
|
|
48
|
+
the session helps.
|
|
49
|
+
- **Task rows say when a task has gone quiet** ("no output for 12 min", amber
|
|
50
|
+
after **Settings › General › Mark a background task as quiet after**, 10
|
|
51
|
+
minutes by default) and show a **✓ 412 ✘ 2** chip when the output is a
|
|
52
|
+
vitest, Playwright, node:test, jest, pytest or turbo run (also through
|
|
53
|
+
pnpm); the console header's chip jumps to the first failure, and a run
|
|
54
|
+
that ends shows its final counts within a moment.
|
|
55
|
+
- **Sub-agent rows in the task panel show their step count** ("22 steps"), as
|
|
56
|
+
the rows in the reply already did.
|
|
57
|
+
- **Settings › Providers › Claude › Claude background command limit** (30
|
|
58
|
+
minutes to 8 hours, 2 hours by default) sets how long a command Claude runs
|
|
59
|
+
in the background may take before Claude Code stops it, when Claude gives it
|
|
60
|
+
no timeout of its own; Claude may still ask for up to 8 hours for one
|
|
61
|
+
command. A foreground command moves to the background after 2 minutes, so
|
|
62
|
+
the turn goes on while it runs. Claude is also told to write the output of
|
|
63
|
+
long runs to a log file instead of piping it into `tail`, and to start runs
|
|
64
|
+
that must survive a session restart with `nohup`. A change applies to Claude
|
|
65
|
+
sessions started afterwards; Claude Cloud is not affected.
|
|
66
|
+
- **`remote_relay` action `read_task_output`** gives agents the same tail,
|
|
67
|
+
quiet time and counts of a task on a relay they may read; `read_session`
|
|
68
|
+
lists the task ids with that health.
|
|
69
|
+
|
|
70
|
+
### Changed
|
|
71
|
+
|
|
72
|
+
- **Long conversations open and scroll back fast.** A history page used to
|
|
73
|
+
load and prepare every message of the conversation, so a chat with a few
|
|
74
|
+
thousand messages took about two seconds per page and per live refresh. The
|
|
75
|
+
relay now reads only the requested page from the database (migration 0017
|
|
76
|
+
adds the indexes for it), so a page loads in well under a tenth of a second
|
|
77
|
+
however long the conversation is.
|
|
78
|
+
- Switching back to a conversation already viewed in this tab draws it at
|
|
79
|
+
once from what the page kept of it; the relay's fresh answer is merged into
|
|
80
|
+
it when it arrives.
|
|
81
|
+
- **Settings › General › Messages kept per conversation for switching back**
|
|
82
|
+
(200 by default, 100 to 1,000 or no limit, saved in this browser) sets how
|
|
83
|
+
many loaded messages each of the last eight conversations keeps.
|
|
84
|
+
- **The conversation list shows activity by the title's colour.** The three
|
|
85
|
+
animated dots after a working session's title are gone: a ready session
|
|
86
|
+
that runs a turn or background work keeps the bright green title, an idle
|
|
87
|
+
one turns a darker green (a muted green in the light theme), and the chat
|
|
88
|
+
header follows the same rule. Question, error, offline and archived titles
|
|
89
|
+
keep their colours.
|
|
90
|
+
- The npm package guard allows 7.5 MiB unpacked and 510 entries (was 7.25
|
|
91
|
+
MiB and 490); the task console, the transcript's handling of new
|
|
92
|
+
messages below a place far up the history, the Copilot task report and the
|
|
93
|
+
Claude background command limit grew the server and the app to about 7.37
|
|
94
|
+
MiB and 505 entries.
|
|
95
|
+
|
|
96
|
+
### Fixed
|
|
97
|
+
|
|
98
|
+
- **Copilot background tasks report how they ended.** Finished shells and
|
|
99
|
+
sub-agents of Copilot's SDK engine showed the status *unknown* and no exit
|
|
100
|
+
code, in the task data agents read and wherever the end is shown. The
|
|
101
|
+
worker now reports completed, failed or cancelled, a shell's exit code, and
|
|
102
|
+
a sub-agent's result (or its error) as its summary; a passing status line
|
|
103
|
+
such as *Thinking…* is no longer kept as the final summary.
|
|
104
|
+
- **Cancel on a usage-limit pause removes the relay's held follow-up from the
|
|
105
|
+
open conversation at once.** The *Automatic message from OAR* row was
|
|
106
|
+
deleted on the relay but stayed on the page, and in what the page kept for
|
|
107
|
+
switching back, until a reload. A row deleted while you were elsewhere
|
|
108
|
+
leaves too, also when it was the conversation's newest.
|
|
109
|
+
|
|
110
|
+
- **Switching away from a long conversation and back keeps your place.** After
|
|
111
|
+
loading older messages and scrolling back into them, a switch to another
|
|
112
|
+
conversation and back lost the reading position: with more than 100 messages
|
|
113
|
+
loaded the oldest were dropped, and messages written meanwhile moved the view
|
|
114
|
+
by as many rows. The page now remembers the message you were reading and
|
|
115
|
+
keeps the loaded messages, so you come back to the same message at the same
|
|
116
|
+
place, older pages included; after a reload it opens at that message too.
|
|
117
|
+
- **New messages no longer skip the ones in between while you read far up.**
|
|
118
|
+
Opened at a pin, a search result or the place you were reading after a
|
|
119
|
+
reload, a conversation shows the messages around it. A message or reply that
|
|
120
|
+
arrived then was put right under them, and the messages between were never
|
|
121
|
+
loaded when you scrolled down, only the jump-to-bottom button brought them.
|
|
122
|
+
Now new messages wait: the button counts them, and scrolling down loads
|
|
123
|
+
every message in between, in order, before them. A reply still being
|
|
124
|
+
written shows once you are there. A message you send from there takes you
|
|
125
|
+
to the end, where it, its reply and everything in between show.
|
|
126
|
+
- **Images that load late no longer push the message you are reading down.**
|
|
127
|
+
In a browser without scroll anchoring (Safari, for one), pictures above
|
|
128
|
+
your place that arrived after a switch back, a reload or an older page moved
|
|
129
|
+
the text by their height. The transcript now holds your place while they
|
|
130
|
+
load, and lets go as soon as you scroll.
|
|
131
|
+
|
|
6
132
|
## [0.9.11] — 2026-10-17
|
|
7
133
|
|
|
8
134
|
Highlights: sessions an agent starts now sit in a group under the session
|
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.12"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/cfmail-mobile.light.png?v=0.9.12"><img src="https://oar.sh/assets/shots/cfmail-mobile.png?v=0.9.12" 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.12"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/cfmail-agents.light.png?v=0.9.12"><img src="https://oar.sh/assets/shots/cfmail-agents.png?v=0.9.12" 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.12"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/cfmail-question.light.png?v=0.9.12"><img src="https://oar.sh/assets/shots/cfmail-question.png?v=0.9.12" 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.12"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/pingcf-thoughts.light.png?v=0.9.12"><img src="https://oar.sh/assets/shots/pingcf-thoughts.png?v=0.9.12" 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.12"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/pagewright-watching.light.png?v=0.9.12"><img src="https://oar.sh/assets/shots/pagewright-watching.png?v=0.9.12" 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>
|
|
@@ -116,11 +116,11 @@ API keys are entered in **⚙️ Settings** and stay on the relay host. The Clau
|
|
|
116
116
|
<table>
|
|
117
117
|
<tr>
|
|
118
118
|
<td width="62%" valign="top">
|
|
119
|
-
<a href="https://oar.sh/assets/shots/cfmail-steering.png?v=0.9.
|
|
119
|
+
<a href="https://oar.sh/assets/shots/cfmail-steering.png?v=0.9.12"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/cfmail-steering.light.png?v=0.9.12"><img src="https://oar.sh/assets/shots/cfmail-steering.png?v=0.9.12" 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>
|
|
120
120
|
<br><sub>Send a message while the agent works, and it steers the running turn.</sub>
|
|
121
121
|
</td>
|
|
122
122
|
<td width="38%" valign="top">
|
|
123
|
-
<a href="https://oar.sh/assets/shots/usage-copilot-compact.png?v=0.9.
|
|
123
|
+
<a href="https://oar.sh/assets/shots/usage-copilot-compact.png?v=0.9.12"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/usage-copilot-compact.light.png?v=0.9.12"><img src="https://oar.sh/assets/shots/usage-copilot-compact.png?v=0.9.12" 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>
|
|
124
124
|
<br><sub>What each plan has left, in one place.</sub>
|
|
125
125
|
</td>
|
|
126
126
|
</tr>
|
|
@@ -129,7 +129,7 @@ API keys are entered in **⚙️ Settings** and stay on the relay host. The Clau
|
|
|
129
129
|
- **Watch a turn live**: the reply, the agent's thoughts and every tool call appear as they happen, and each sub-agent gets a bubble of its own. **Stop** is always one tap away ([While a turn runs](docs/web-ui.md#while-a-turn-runs)).
|
|
130
130
|
- **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)).
|
|
131
131
|
- **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)).
|
|
132
|
-
- **Background work**: a task panel lists the commands and sub-agents that outlive a reply, each with its own **Stop** and its full record ([Background tasks](docs/web-ui.md#background-tasks)).
|
|
132
|
+
- **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 ([Background tasks](docs/web-ui.md#background-tasks)).
|
|
133
133
|
- **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)).
|
|
134
134
|
- **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)).
|
|
135
135
|
- **Usage and context**: plan limits and credits of every provider, and what fills the model's context window ([Usage and context](docs/web-ui.md#usage-and-context)).
|
|
@@ -137,7 +137,7 @@ API keys are entered in **⚙️ Settings** and stay on the relay host. The Clau
|
|
|
137
137
|
- **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)).
|
|
138
138
|
- **On your phone**: install OAR as an app and get push notifications ([Notifications and the app](docs/web-ui.md#notifications-and-the-app)).
|
|
139
139
|
|
|
140
|
-
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.
|
|
140
|
+
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.12](CHANGELOG.md#0912--2026-10-18) as the latest.
|
|
141
141
|
|
|
142
142
|
## Remote access
|
|
143
143
|
|
|
@@ -188,7 +188,7 @@ flowchart LR
|
|
|
188
188
|
|
|
189
189
|
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.
|
|
190
190
|
|
|
191
|
-
<p><a href="https://oar.sh/assets/shots/pagewright-cross-relay.png?v=0.9.
|
|
191
|
+
<p><a href="https://oar.sh/assets/shots/pagewright-cross-relay.png?v=0.9.12"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/pagewright-cross-relay.light.png?v=0.9.12"><img src="https://oar.sh/assets/shots/pagewright-cross-relay.png?v=0.9.12" 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>
|
|
192
192
|
|
|
193
193
|
How pairing, permissions and limits work is in [Agents working together](docs/agents-together.md).
|
|
194
194
|
|
package/package.json
CHANGED
package/server/README.md
CHANGED
|
@@ -899,7 +899,12 @@ turn processing, or queued and not held by a usage-limit pause; `background_only
|
|
|
899
899
|
`subagent` | `shell`, `description`, `startedAt`, `lastActivityAt`, `status` `running` |
|
|
900
900
|
`completed` | `failed` | `stopped`, `exitCode`, `summary`, `endedAt`) and `lastActivityAt`.
|
|
901
901
|
`remote_relay`'s `list_sessions` and `read_session` pass them on as `state`, `tasks` and
|
|
902
|
-
`lastActivityAt`; `active` stays and is true for background work too.
|
|
902
|
+
`lastActivityAt`; `active` stays and is true for background work too. `read_session` adds to each
|
|
903
|
+
task its `taskId`, `output` (whether it has output to read) and, for a task with an output file,
|
|
904
|
+
`health` (`minutesSinceOutput`, `quiet`, `quietAfterMinutes`, test-run `counts`);
|
|
905
|
+
`read_task_output {session, task_id, lines?}` (read permission; default 200 lines, at most 500 or
|
|
906
|
+
32 KB) reads the end of that output through `GET /api/conversation/:id/background-task/:taskId/output`,
|
|
907
|
+
scrubbed of credentials.
|
|
903
908
|
|
|
904
909
|
### Browser
|
|
905
910
|
|
|
@@ -1301,8 +1306,8 @@ Queue metrics include `parkedCount` for turns deferred behind restart/rebind gat
|
|
|
1301
1306
|
| GET | `/api/usage` | Unified plan usage: one card per configured provider (`providers[]` with `meters`, `details`, `links`) built from the live Copilot quota, the Claude worker's latest stored snapshot, and the derived Cursor cycle totals. A provider that cannot be read yields an unavailable card rather than failing the response, so the modal always renders. While Claude Cloud is on, the Claude card's windows are read live from the account, and a Claude Cloud card is added (credit meters, the relay's cloud spend, and the cost of the conversation named by `?conversationId=` when it is a cloud one); a card may carry `notes[]` (a line of text with an optional https link), and a meter `severity` and `resetKind: 'expiry'`. Legacy top-level Copilot quota fields are spread alongside `providers` for cached clients; `?legacy=1` returns the Copilot-only snapshot on its own |
|
|
1302
1307
|
| GET | `/api/settings/cursor-allowance` | Read the manual Cursor plan allowances (`cursorModelsUsd`, `otherModelsUsd`, `resetDay`) |
|
|
1303
1308
|
| POST | `/api/settings/cursor-allowance` | Set the Cursor pool allowances and billing reset day; `null` clears an allowance, and `resetAccounting: true` re-baselines the derived spend tracking |
|
|
1304
|
-
| GET | `/api/settings/claude` | Read Claude provider settings (`enabled`, `model`, `models`, `availableModels`) |
|
|
1305
|
-
| POST | `/api/settings/claude` | Enable/disable Claude, set the default model
|
|
1309
|
+
| GET | `/api/settings/claude` | Read Claude provider settings (`enabled`, `model`, `models`, `availableModels`, `attributionMode`, `backgroundCommandLimitMinutes` with the slider bounds `backgroundCommandLimitMinMinutes`, `backgroundCommandLimitMaxMinutes`, `backgroundCommandLimitStepMinutes`, `backgroundCommandLimitDefaultMinutes`) |
|
|
1310
|
+
| POST | `/api/settings/claude` | Enable/disable Claude, set the default model, the enabled model subset, the attribution mode or `backgroundCommandLimitMinutes` (clamped to 30…480 and snapped to 30-minute steps; not a number is `400`; read by Claude sessions spawned afterwards); triggers discovery and unstarted-conversation reconciliation |
|
|
1306
1311
|
| GET | `/api/settings/claude-cloud` | Read Claude Cloud settings: `{ enabled, defaultModel, environmentId, environments: [{ id, name }] \| null, environmentsError, account: { loggedIn, email, orgName, subscriptionType } \| null, token: { source, hasToken, expiresAt }, models }`. `token` says where the login is read from (`env`, `file`, `none`) and when it runs out, never the token. `environments` is `null` while the provider is off or the host has no login: nothing is asked of Anthropic then |
|
|
1307
1312
|
| POST | `/api/settings/claude-cloud` | `{ enabled?, defaultModel?, environmentId? }` → `{ ok: true, …the GET body }`. Enabling without a Claude login on the host is `400` with `code: 'claude_cloud_login_missing'`; an empty `environmentId` clears the stored one. Emits `claude_cloud_settings_updated` |
|
|
1308
1313
|
| GET | `/api/settings/cursor` | Read Cursor provider settings — same shape as Claude's: `models` is the enabled subset, `availableModels` the full discovered list, plus per-model `efforts` |
|
|
@@ -1937,6 +1942,15 @@ the watched one `subscriber_relay_id`, `subscriber_relay_name`, `subscriber_watc
|
|
|
1937
1942
|
`subscriber_watcher_title`, `last_pull_at`, `dropped_count`) and `relay_event_links` (per paired
|
|
1938
1943
|
relay the fetch cursor and the link's health). See [Across paired relays](#across-paired-relays).
|
|
1939
1944
|
|
|
1945
|
+
`0017-conversation-history-indexes.mjs` adds two indexes and nothing else:
|
|
1946
|
+
`queue(response_message_id)` for the sub-agent records of each reply on a history page, and
|
|
1947
|
+
`messages(conversation_id, timestamp, id)` for selecting the page itself. `GET
|
|
1948
|
+
/api/conversation/:id` and the shared view select the requested page in SQL in conversation order
|
|
1949
|
+
(timestamp, then id) and hydrate only its replies, so a page costs the same in a conversation of
|
|
1950
|
+
any length. A conversation with a row whose timestamp is not the relay's ISO form
|
|
1951
|
+
(`YYYY-MM-DDTHH:MM:SS.mmmZ`) or whose id is not lowercase letters, digits and dashes is paged the
|
|
1952
|
+
old way: every row loaded, the page cut afterwards.
|
|
1953
|
+
|
|
1940
1954
|
`0007-claude-cloud.mjs` adds the storage of [Claude Cloud](#claude-cloud-provider) conversations.
|
|
1941
1955
|
It is additive and idempotent, runs on every boot, and a failure is logged and retried on the next
|
|
1942
1956
|
one:
|
|
@@ -81,6 +81,8 @@ export const REMOTE_RELAY_TOOL_ZOD_SHAPE = {
|
|
|
81
81
|
subscription_id: text('subscription_id'),
|
|
82
82
|
watcher: text('watcher'),
|
|
83
83
|
from: text('from'),
|
|
84
|
+
task_id: text('task_id'),
|
|
85
|
+
lines: number('lines'),
|
|
84
86
|
};
|
|
85
87
|
|
|
86
88
|
export function createRemoteRelayToolDefinition({ api, getConversationId = () => '', dbg = () => {} } = {}) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createSdkMcpServer, query } from '@anthropic-ai/claude-agent-sdk';
|
|
2
2
|
|
|
3
|
+
import { formatClaudeBackgroundCommandLimitLabel } from '../../shared/claude-background-command-limit.mjs';
|
|
3
4
|
import { parseThinkingDisplay } from '../../shared/claude-thinking.mjs';
|
|
4
5
|
import { MEDIA_EMBED_INSTRUCTION_TEXT } from '../../shared/media-embed-instructions.mjs';
|
|
5
6
|
import { REMOTE_RELAY_TOOL_CALL_TIMEOUT_MS } from '../../shared/remote-relay-tool-core.mjs';
|
|
@@ -136,11 +137,57 @@ export function permissionModeForRelayMode(relayMode) {
|
|
|
136
137
|
return 'default';
|
|
137
138
|
}
|
|
138
139
|
|
|
139
|
-
|
|
140
|
+
// Bash timeouts the CLI reads from its environment (measured against CLI
|
|
141
|
+
// 2.1.292). BASH_DEFAULT_TIMEOUT_MS is the only lever on the background
|
|
142
|
+
// default (`max(30 min, BASH_DEFAULT_TIMEOUT_MS)`), but it also becomes the
|
|
143
|
+
// foreground default; CLAUDE_CODE_AUTO_BACKGROUND_TIMEOUT_MS therefore caps a
|
|
144
|
+
// main-agent foreground command at 2 min and then moves it to the background
|
|
145
|
+
// instead of letting it block the turn. BASH_MAX_TIMEOUT_MS lifts the ceiling
|
|
146
|
+
// an explicit `timeout` may ask for (foreground and background) to 8 h.
|
|
147
|
+
// Commands that start with `sleep` are never moved to the background by the
|
|
148
|
+
// CLI, and subagents get no auto-background; both run in the foreground for
|
|
149
|
+
// up to the default.
|
|
150
|
+
export const CLAUDE_BASH_MAX_TIMEOUT_MS = 8 * 3_600_000;
|
|
151
|
+
export const CLAUDE_AUTO_BACKGROUND_TIMEOUT_MS = 120_000;
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* The environment a Claude CLI spawn gets, or null to inherit the worker's
|
|
155
|
+
* own. `env` REPLACES the subprocess environment in the SDK, so the base is
|
|
156
|
+
* spread in.
|
|
157
|
+
*/
|
|
158
|
+
export function claudeSpawnEnv({ backgroundCommandLimitMs = null, baseEnv = process.env } = {}) {
|
|
159
|
+
const limit = Number(backgroundCommandLimitMs);
|
|
160
|
+
if (backgroundCommandLimitMs === null || !Number.isFinite(limit) || limit <= 0) return null;
|
|
161
|
+
return {
|
|
162
|
+
...baseEnv,
|
|
163
|
+
BASH_DEFAULT_TIMEOUT_MS: String(Math.round(limit)),
|
|
164
|
+
BASH_MAX_TIMEOUT_MS: String(Math.max(CLAUDE_BASH_MAX_TIMEOUT_MS, Math.round(limit))),
|
|
165
|
+
CLAUDE_CODE_AUTO_BACKGROUND_TIMEOUT_MS: String(CLAUDE_AUTO_BACKGROUND_TIMEOUT_MS),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** How to run long commands, with the session's limit when it is known. */
|
|
170
|
+
export function longCommandInstructionText(backgroundCommandLimitMs = null) {
|
|
171
|
+
const limit = Number(backgroundCommandLimitMs);
|
|
172
|
+
const known = backgroundCommandLimitMs !== null && Number.isFinite(limit) && limit > 0;
|
|
173
|
+
const limitClause = known
|
|
174
|
+
? `Background Bash commands are stopped after ${formatClaudeBackgroundCommandLimitLabel(limit / 60_000)} unless you pass a longer timeout (up to 8 h); `
|
|
175
|
+
: 'For long background Bash commands, ';
|
|
176
|
+
return `${limitClause}write the full output to a log file instead of piping it into tail or head, `
|
|
177
|
+
+ 'since the pipe holds the output back and loses it if the command is stopped. '
|
|
178
|
+
+ 'Start runs that must survive a session restart detached with nohup and a log file.';
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function systemPromptForRelayMode(relayMode, { backgroundCommandLimitMs = null } = {}) {
|
|
140
182
|
const mode = String(relayMode || 'agent').trim().toLowerCase();
|
|
141
183
|
// The media-embed capability rides every session — unlike the mode nudges it
|
|
142
|
-
// is renderer knowledge the model cannot discover any other way.
|
|
143
|
-
|
|
184
|
+
// is renderer knowledge the model cannot discover any other way. The long
|
|
185
|
+
// command note rides every session too: the limit is the relay's setting.
|
|
186
|
+
const append = [
|
|
187
|
+
MEDIA_EMBED_INSTRUCTION_TEXT,
|
|
188
|
+
longCommandInstructionText(backgroundCommandLimitMs),
|
|
189
|
+
MODE_SYSTEM_PROMPT_APPEND[mode],
|
|
190
|
+
]
|
|
144
191
|
.filter(Boolean)
|
|
145
192
|
.join('\n\n');
|
|
146
193
|
return {
|
|
@@ -233,6 +280,10 @@ export function startClaudeSession({
|
|
|
233
280
|
autoCompactWindow = null,
|
|
234
281
|
thinkingEnabled = null,
|
|
235
282
|
attribution = null,
|
|
283
|
+
// How long a backgrounded Bash command may run without its own timeout
|
|
284
|
+
// (ms, the relay's setting); null leaves the CLI's defaults and the
|
|
285
|
+
// worker's environment alone.
|
|
286
|
+
backgroundCommandLimitMs = null,
|
|
236
287
|
thinkingDisplay = '',
|
|
237
288
|
abortController,
|
|
238
289
|
canUseTool,
|
|
@@ -259,14 +310,16 @@ export function startClaudeSession({
|
|
|
259
310
|
const relayMcpServer = typeof api === 'function'
|
|
260
311
|
? createRelayMcpServer({ api, getConversationId, dbg, remoteRelayTool: remoteRelayTool === true })
|
|
261
312
|
: null;
|
|
313
|
+
const env = claudeSpawnEnv({ backgroundCommandLimitMs });
|
|
262
314
|
const options = {
|
|
263
315
|
cwd,
|
|
264
316
|
permissionMode: permissionModeForRelayMode(relayMode),
|
|
265
|
-
systemPrompt: systemPromptForRelayMode(relayMode),
|
|
317
|
+
systemPrompt: systemPromptForRelayMode(relayMode, { backgroundCommandLimitMs: env ? backgroundCommandLimitMs : null }),
|
|
266
318
|
includePartialMessages: true,
|
|
267
319
|
forwardSubagentText: true,
|
|
268
320
|
...(effortOption ? { effort: effortOption } : {}),
|
|
269
321
|
...(spawnSettings ? { settings: spawnSettings } : {}),
|
|
322
|
+
...(env ? { env } : {}),
|
|
270
323
|
...(normalizedModel && normalizedModel !== 'auto' ? { model: String(model).trim() } : {}),
|
|
271
324
|
...(String(resume || '').trim() ? { resume: String(resume).trim() } : {}),
|
|
272
325
|
...(abortController ? { abortController } : {}),
|
|
@@ -58,6 +58,13 @@ import {
|
|
|
58
58
|
withSlashCommandGuardContent,
|
|
59
59
|
} from '../../shared/slash-command-guard.mjs';
|
|
60
60
|
import { createUsageLimitTracker } from '../../shared/claude-usage-limit.mjs';
|
|
61
|
+
import {
|
|
62
|
+
discoverTaskOutputPaths,
|
|
63
|
+
exitCodeFromSummary,
|
|
64
|
+
isShellTaskType,
|
|
65
|
+
outputDirOf,
|
|
66
|
+
usableOutputPath,
|
|
67
|
+
} from './task-output-discovery.mjs';
|
|
61
68
|
|
|
62
69
|
/**
|
|
63
70
|
* Which system-prompt append a relay mode gets (claude-sdk-adapter's
|
|
@@ -77,6 +84,8 @@ export { STEER_FOLDED_TEXT, STEER_STOPPED_TEXT };
|
|
|
77
84
|
/** Task types that run a model of their own (vs. bash/monitor/workflow). */
|
|
78
85
|
const AGENT_TASK_TYPES = new Set(['local_agent', 'agent', 'subagent']);
|
|
79
86
|
const MAX_TASK_TOOL_CALL_CHARS = 300;
|
|
87
|
+
/** Output paths remembered per process (one per backgrounded shell). */
|
|
88
|
+
const MAX_TASK_OUTPUT_PATHS = 200;
|
|
80
89
|
|
|
81
90
|
// ---------------------------------------------------------------------------
|
|
82
91
|
// Workflow progress sources (CLI 2.1.226 on-disk formats)
|
|
@@ -460,6 +469,10 @@ export function createClaudeSessionRunner({
|
|
|
460
469
|
// leaves the CLI's own attribution. Piggybacked on deliveries like the
|
|
461
470
|
// window above.
|
|
462
471
|
getAttribution = () => null,
|
|
472
|
+
// How long a backgrounded Bash command may run without its own timeout
|
|
473
|
+
// (ms; the relay's Claude setting). Read at spawn only: the CLI takes it
|
|
474
|
+
// from its environment, so a change applies to the next process.
|
|
475
|
+
getBackgroundCommandLimitMs = () => null,
|
|
463
476
|
// The per-conversation thinking state ({enabled, display}); read per turn
|
|
464
477
|
// like the window, so a settings change reaches a running process on its
|
|
465
478
|
// next delivery.
|
|
@@ -1769,6 +1782,9 @@ export function createClaudeSessionRunner({
|
|
|
1769
1782
|
// The workflow digest (poller below) rides the same row; the relay
|
|
1770
1783
|
// sanitizer whitelists and re-clamps it before storing.
|
|
1771
1784
|
...(task.workflowProgress ? { workflowProgress: task.workflowProgress } : {}),
|
|
1785
|
+
...(agentLike && Number.isInteger(task.steps) ? { steps: task.steps } : {}),
|
|
1786
|
+
// Where the relay reads the task's console from.
|
|
1787
|
+
...(isShellTaskType(task.taskType) ? taskOutputFields(processRef, taskId) : {}),
|
|
1772
1788
|
};
|
|
1773
1789
|
});
|
|
1774
1790
|
return postTaskSet({ tasks, ...(resync ? { resync: true } : {}) }).catch((error) => {
|
|
@@ -1788,6 +1804,89 @@ export function createClaudeSessionRunner({
|
|
|
1788
1804
|
return Promise.resolve();
|
|
1789
1805
|
}
|
|
1790
1806
|
|
|
1807
|
+
// ---------------------------------------------------------------------------
|
|
1808
|
+
// Task output files
|
|
1809
|
+
//
|
|
1810
|
+
// The relay reads a shell task's console straight from the file the CLI
|
|
1811
|
+
// writes (`<tasks dir>/<taskId>.output`); the worker only names it. Bash
|
|
1812
|
+
// results announce the path (task-output-discovery.mjs); a Monitor result
|
|
1813
|
+
// does not, but its file follows the same scheme next to any known one, and
|
|
1814
|
+
// is named only once it exists. Without a path the relay falls back to the
|
|
1815
|
+
// CLI's `getTaskOutput` snapshot (task_output_snapshot control).
|
|
1816
|
+
|
|
1817
|
+
function rememberTaskOutputPath(processRef, taskId, outputPath) {
|
|
1818
|
+
const id = String(taskId || '').trim();
|
|
1819
|
+
const usable = usableOutputPath(outputPath);
|
|
1820
|
+
if (!id || !usable) return false;
|
|
1821
|
+
const dir = outputDirOf(usable);
|
|
1822
|
+
if (dir) processRef.taskOutputDir = dir;
|
|
1823
|
+
// The first path a task names wins, here as on the relay.
|
|
1824
|
+
if (processRef.taskOutputPaths.has(id)) return false;
|
|
1825
|
+
processRef.taskOutputPaths.set(id, usable);
|
|
1826
|
+
while (processRef.taskOutputPaths.size > MAX_TASK_OUTPUT_PATHS) {
|
|
1827
|
+
processRef.taskOutputPaths.delete(processRef.taskOutputPaths.keys().next().value);
|
|
1828
|
+
}
|
|
1829
|
+
return true;
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
function taskOutputFields(processRef, taskId) {
|
|
1833
|
+
const known = processRef.taskOutputPaths.get(taskId);
|
|
1834
|
+
if (known) return { outputPath: known, outputKind: 'file' };
|
|
1835
|
+
if (processRef.taskOutputDir) {
|
|
1836
|
+
const candidate = nodePath.join(processRef.taskOutputDir, `${taskId}.output`);
|
|
1837
|
+
let exists = false;
|
|
1838
|
+
try { exists = nodeFs.existsSync(candidate); } catch {}
|
|
1839
|
+
if (exists && rememberTaskOutputPath(processRef, taskId, candidate)) {
|
|
1840
|
+
return { outputPath: candidate, outputKind: 'file' };
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
return { outputKind: 'snapshot' };
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
/** A backgrounded Bash/Monitor result: note its file, and show it on the live row. */
|
|
1847
|
+
function recordTaskOutputPaths(sdkMessage) {
|
|
1848
|
+
let liveChanged = false;
|
|
1849
|
+
for (const { taskId, outputPath } of discoverTaskOutputPaths(sdkMessage)) {
|
|
1850
|
+
if (rememberTaskOutputPath(proc, taskId, outputPath) && proc.liveTasks.has(taskId)) liveChanged = true;
|
|
1851
|
+
}
|
|
1852
|
+
if (liveChanged) publishBackgroundTasks();
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
/**
|
|
1856
|
+
* The relay's fallback for a shell task with no file: the CLI's last 8 KiB
|
|
1857
|
+
* (`Query.getTaskOutput`, implemented in sdk.mjs though sdk.d.ts does not
|
|
1858
|
+
* declare it), posted for the relay to serve. Asked once per viewer open.
|
|
1859
|
+
*/
|
|
1860
|
+
async function snapshotTaskOutput(taskId) {
|
|
1861
|
+
const id = String(taskId || '').trim();
|
|
1862
|
+
const processRef = proc;
|
|
1863
|
+
if (!id || typeof processRef?.turn?.getTaskOutput !== 'function') {
|
|
1864
|
+
dbg('task output snapshot unavailable', id || '(no task)');
|
|
1865
|
+
return false;
|
|
1866
|
+
}
|
|
1867
|
+
let answer;
|
|
1868
|
+
try {
|
|
1869
|
+
answer = await processRef.turn.getTaskOutput(id);
|
|
1870
|
+
} catch (error) {
|
|
1871
|
+
dbg('getTaskOutput failed', id, error?.message || String(error));
|
|
1872
|
+
return false;
|
|
1873
|
+
}
|
|
1874
|
+
const output = typeof answer?.output === 'string' ? answer.output : '';
|
|
1875
|
+
const totalBytes = Number(answer?.total_bytes);
|
|
1876
|
+
try {
|
|
1877
|
+
await api('POST', `/api/background-tasks/${encodeURIComponent(id)}/output-snapshot`, {
|
|
1878
|
+
conversationId: sdkSessionId,
|
|
1879
|
+
output,
|
|
1880
|
+
totalBytes: Number.isFinite(totalBytes) && totalBytes >= 0 ? totalBytes : Buffer.byteLength(output),
|
|
1881
|
+
truncated: answer?.truncated === true,
|
|
1882
|
+
});
|
|
1883
|
+
return true;
|
|
1884
|
+
} catch (error) {
|
|
1885
|
+
dbg('task output snapshot post failed', id, error?.message || String(error));
|
|
1886
|
+
return false;
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1791
1890
|
// ---------------------------------------------------------------------------
|
|
1792
1891
|
// Workflow progress poller
|
|
1793
1892
|
//
|
|
@@ -2226,6 +2325,11 @@ export function createClaudeSessionRunner({
|
|
|
2226
2325
|
recordSubagentActivity(sdkMessage);
|
|
2227
2326
|
return;
|
|
2228
2327
|
}
|
|
2328
|
+
if (type === 'user') {
|
|
2329
|
+
// Any agent's backgrounded shell, the main thread's or a subagent's.
|
|
2330
|
+
recordTaskOutputPaths(sdkMessage);
|
|
2331
|
+
return;
|
|
2332
|
+
}
|
|
2229
2333
|
if (type !== 'system') return;
|
|
2230
2334
|
const subtype = String(sdkMessage.subtype || '');
|
|
2231
2335
|
if (subtype === 'init') {
|
|
@@ -2524,6 +2628,9 @@ export function createClaudeSessionRunner({
|
|
|
2524
2628
|
known.toolUseId = String(sdkMessage.tool_use_id || '').trim() || known.toolUseId || '';
|
|
2525
2629
|
const totalTokens = Number(sdkMessage.usage?.total_tokens);
|
|
2526
2630
|
if (Number.isFinite(totalTokens)) known.totalTokens = totalTokens;
|
|
2631
|
+
// The sub-agent's tool calls so far: the panel row's step count.
|
|
2632
|
+
const toolUses = Number(sdkMessage.usage?.tool_uses);
|
|
2633
|
+
if (Number.isFinite(toolUses) && toolUses >= 0) known.steps = Math.floor(toolUses);
|
|
2527
2634
|
publishBackgroundTasks({ throttled: true });
|
|
2528
2635
|
}
|
|
2529
2636
|
return;
|
|
@@ -2543,6 +2650,16 @@ export function createClaudeSessionRunner({
|
|
|
2543
2650
|
// tell its watchers the end. A failed post is kept and sent again.
|
|
2544
2651
|
const published = proc.liveTasks.get(taskId) || proc.departedTasks.get(taskId) || null;
|
|
2545
2652
|
proc.departedTasks.delete(taskId);
|
|
2653
|
+
// A shell's file and exit code make the relay's ended row: its
|
|
2654
|
+
// console stays readable after the task is gone.
|
|
2655
|
+
rememberTaskOutputPath(proc, taskId, sdkMessage.output_file);
|
|
2656
|
+
const shellLike = isShellTaskType(published?.taskType);
|
|
2657
|
+
const outputPath = shellLike ? (proc.taskOutputPaths.get(taskId) || '') : '';
|
|
2658
|
+
const exitCode = shellLike ? exitCodeFromSummary(sdkMessage.summary) : null;
|
|
2659
|
+
const toolUses = Number(sdkMessage.usage?.tool_uses);
|
|
2660
|
+
const steps = AGENT_TASK_TYPES.has(String(published?.taskType || '')) && Number.isFinite(toolUses) && toolUses >= 0
|
|
2661
|
+
? Math.floor(toolUses)
|
|
2662
|
+
: null;
|
|
2546
2663
|
void keptTaskSettles.post('/api/background-task-settled', {
|
|
2547
2664
|
conversationId: sdkSessionId,
|
|
2548
2665
|
taskId,
|
|
@@ -2555,6 +2672,9 @@ export function createClaudeSessionRunner({
|
|
|
2555
2672
|
taskType: String(published.taskType || ''),
|
|
2556
2673
|
startedAt: published.startedAt || null,
|
|
2557
2674
|
} : {}),
|
|
2675
|
+
...(outputPath ? { outputPath } : {}),
|
|
2676
|
+
...(exitCode !== null ? { exitCode } : {}),
|
|
2677
|
+
...(steps !== null ? { steps } : {}),
|
|
2558
2678
|
});
|
|
2559
2679
|
}
|
|
2560
2680
|
// A settled session-level task means the CLI is about to dequeue a
|
|
@@ -3365,6 +3485,7 @@ export function createClaudeSessionRunner({
|
|
|
3365
3485
|
const thinkingEnabled = parseThinkingEnabled(thinkingState.enabled);
|
|
3366
3486
|
const thinkingDisplay = parseThinkingDisplay(thinkingState.display);
|
|
3367
3487
|
const attribution = getAttribution() || null;
|
|
3488
|
+
const backgroundCommandLimitMs = getBackgroundCommandLimitMs() ?? null;
|
|
3368
3489
|
const abortController = new AbortController();
|
|
3369
3490
|
const processRef = {
|
|
3370
3491
|
turn: null,
|
|
@@ -3391,6 +3512,11 @@ export function createClaudeSessionRunner({
|
|
|
3391
3512
|
// taskId → the row of a task that left the live set, until its
|
|
3392
3513
|
// notification arrives.
|
|
3393
3514
|
departedTasks: new Map(),
|
|
3515
|
+
// taskId → the file a shell task's output goes to (first named wins),
|
|
3516
|
+
// and the directory of the last one seen: a Monitor task's file sits
|
|
3517
|
+
// next to it.
|
|
3518
|
+
taskOutputPaths: new Map(),
|
|
3519
|
+
taskOutputDir: '',
|
|
3394
3520
|
// taskId → { runId, labels, digestJson } for live local_workflow tasks;
|
|
3395
3521
|
// pruned by syncWorkflowPoller when a task leaves the live set.
|
|
3396
3522
|
workflowStates: new Map(),
|
|
@@ -3538,6 +3664,7 @@ export function createClaudeSessionRunner({
|
|
|
3538
3664
|
thinkingEnabled,
|
|
3539
3665
|
thinkingDisplay,
|
|
3540
3666
|
attribution,
|
|
3667
|
+
backgroundCommandLimitMs,
|
|
3541
3668
|
abortController,
|
|
3542
3669
|
canUseTool,
|
|
3543
3670
|
pathToClaudeCodeExecutable,
|
|
@@ -4121,6 +4248,7 @@ export function createClaudeSessionRunner({
|
|
|
4121
4248
|
isTurnActive,
|
|
4122
4249
|
stopBackgroundTask,
|
|
4123
4250
|
stopBackgroundTaskByToolUseId,
|
|
4251
|
+
snapshotTaskOutput,
|
|
4124
4252
|
backgroundTaskIds,
|
|
4125
4253
|
resyncBackgroundTasks,
|
|
4126
4254
|
flushKeptTaskSettles,
|