Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "downrange",
"version": "1.1.0",
"version": "1.1.1",
"description": "A band, pane, and /downrange table: progress, ETA, and last output for Claude Code background shells and Monitor watches, tool activity for subagents, state for workflows, and the next /loop wakeup.",
"author": { "name": "dot-agi" },
"license": "MIT",
Expand All @@ -24,6 +24,12 @@
"title": "Answer status prompts locally",
"description": "When Claude is idle and background work runs, answer an exact status prompt such as \"status?\" with the /downrange table instead of sending it to Claude. Send the same prompt again within 60 seconds to ask Claude.",
"default": false
},
"genericRatios": {
"type": "boolean",
"title": "Progress from any ratio",
"description": "Also read progress from a ratio in any place in the output, such as \"loss 12/40\" or \"Retry 1 of 3\". Off: only known formats count, such as \"45%\", \"[3/7]\", \"Step 3 of 7\", or a count after a progress bar. On shows progress for more jobs, and more false readings, such as a date in \"Released 10/11\".",
"default": false
}
}
}
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ Run all of them after each change. All must pass.
- Write event names in `on(...)` as string literals. Write each matcher value as a literal or as a constant of `hooks/register.tsx`: `claude plugin validate` shows an imported constant as `?`. Use static imports only. Hooks run with no DOM and no Node.
- Export every type in `types/index.d.ts`. `claude plugin validate` refuses a contract type without `export`.
- Keep the other modules pure: no `$`, no I/O, and no clock reads. Pass `now` in.
- Keep session values in `$.state` through the atoms. The only module variables are `ticker`, `isTicking`, `lastTickAt`, `latest`, `carry`, `historyWrites`, and `reported`.
- Keep session values in `$.state` through the atoms. The only module variables are `ticker`, `isTicking`, `lastTickAt`, `latest`, `carry`, `historyWrites`, `reported`, and `isAnyRatio`, the `genericRatios` option that `register` sets.
- Write `jobs` only through `writeJobs` and `wakeup` only through `writeWakeup`. They keep the carry's copy and the ticker current.
- Render hooks never write state. Buttons write from their press handlers.
- Give every `on(...)` registration a `.catch` that logs the failure under the hook's label, such as `tool.call{tool=Bash}`, and passes the event on with `next(e)`: write it as a function literal that calls `passOn`. `claude plugin validate` refuses a `.catch` that is not a function literal or the name of one. The `/downrange` command and the pane answer with the cause instead.
Expand Down
40 changes: 20 additions & 20 deletions README.md

Large diffs are not rendered by default.

22 changes: 18 additions & 4 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,19 @@ When nothing draws in the session, or the surface cannot show the pane, `/downra

**Age:** `4m 12s` for a running job. `≥4m 12s` when Downrange first saw the job already running. `took 1m 12s` for an ended job seen from its start. `ended` for other ended jobs. A shell command's age starts when it went to the background, so it leaves out time in the foreground before ctrl+b or a timeout move.

**Progress** is a guess from the last 10 display lines, newest first: a percentage (`45%`), a fraction (`12/40`), or `3 of 7`. A shell's lines come from the end of its output: the last 16,384 characters (UTF-16 code units) of a file up to 4 MiB, or the last 16,384 bytes above that. Values over 100% do not count. A fraction joined to a letter, a dot, a slash, or a dash does not count, so full dates such as `2026/10/11` and most paths and version numbers do not count. A fraction from 1 to 12 out of at most 31 reads as a date when a two-digit clock follows it, as in `10/11 14:03`, and does not count. `3 of 7` joined to a path or a number, such as `/backups/3 of 7/` or `version 1.2 of 7.0`, does not count. These filters are heuristic: a short date with no clock after it, such as `10/11`, counts as a fraction, and so does `3 of 7` followed by a space inside a path. A subagent shows its tool count. A remote subagent or a teammate in a terminal pane of its own shows none until one of its tool calls reaches Downrange. A Monitor with no progress shows its event count. An ended job shows its exit code.
**Progress** is a guess from the last 10 display lines, newest first: a percentage (`45%`), a fraction (`12/40`), or `3 of 7`. A shell's lines come from the end of its output: the last 16,384 characters (UTF-16 code units) of a file up to 4 MiB, or the last 16,384 bytes above that. Values over 100% do not count. A percentage counts in any place. By default, a fraction or `3 of 7` counts only in a known progress format:

- In brackets, alone or after one or two labels: `[3/7]`, `[ 5/8]`, `(3/15)`, `(3 of 7)`, or Docker's `[builder 3/7]`.
- At the start of a line: `3/7 compiling` or `3 of 7 done`.
- Right after a step word, with an optional colon: `Step 3/7`, `Epoch 3/10`, `batch: 9/30`, or pip's `Progress 12 of 40`. The step words are step, stage, phase, epoch, iteration, batch, chunk, part, file, item, task, test, and progress, in any letter case.
- Right after a drawn bar: cargo's `[=====> ] 120/300`, tqdm's `|█████ | 450/1000`, or rich's `━━━━━━ 12/40`.
- In Docker Compose's `[+] Running 3/7`.

These formats cover the progress lines of Yarn (`[1/4]`), pip (`--progress-bar raw`, for downloads under 10 MB), cargo and cargo-nextest, pytest (`[ 62%]` or `[ 5/8]`), Docker (`#7 [3/7]`, `Step 3/7`, and Compose), and git (`45% (450/1000)` or `Rebasing (3/7)`). A fraction or `3 of 7` in another place, such as `loss 12/40` or `[12:34:56] 3/15 done`, does not count. With the option `genericRatios` on, a fraction or `3 of 7` counts in any place.

In both modes, a fraction joined to a letter, a dot, a slash, or a dash does not count, so full dates such as `2026/10/11` and most paths and version numbers do not count. A fraction from 1 to 12 out of at most 31 reads as a date when a two-digit clock follows it, as in `10/11 14:03`, and does not count. `3 of 7` joined to a path or a number, such as `/backups/3 of 7/` or `version 1.2 of 7.0`, does not count. These filters are heuristic. A short date with no clock after it counts where a fraction counts: `[10/11]`, or `10/11 deploy` at the start of a line, or with `genericRatios` on, `Released 10/11`. So does `3 of 7` followed by a space inside a path: `Part 1 of 2 (live)/a.flac` after the step word part, or with `genericRatios` on, `Disc 1 of 2 (live)/a.flac`.

A subagent shows its tool count. A remote subagent or a teammate in a terminal pane of its own shows none until one of its tool calls reaches Downrange. A Monitor with no progress shows its event count. An ended job shows its exit code.

**ETA:**

Expand Down Expand Up @@ -153,8 +165,9 @@ Set options in `/config`, or under `pluginConfigs["downrange@downrange"].options
| --- | --- | --- |
| `band` | `true` | Shows the band while work is live |
| `answerStatusPrompts` | `false` | Answers exact status prompts locally, as described above |
| `genericRatios` | `false` | Reads progress from a fraction or `3 of 7` in any place in the output, not only in a known progress format (see [Reading a row](#reading-a-row)). It shows progress for more jobs, and more false readings, such as a date like `Released 10/11` |

A change takes effect at once: Claude Code reloads the mod with the new values.
A change takes effect at once: Claude Code reloads the mod with the new values. After a reload, Downrange reads the output of each running shell again, so its row follows the current `genericRatios` value. A Monitor cannot read its past events again. With `genericRatios` off, a Monitor row drops progress and a rate ETA that came from another ratio. Without progress, it shows its event count until an event in a known format arrives. With `genericRatios` on, another ratio counts from the Monitor's next event.

## Where it runs

Expand All @@ -175,9 +188,10 @@ Background work keeps running after these commands. Downrange carries running jo

- **Only dynamic `/loop` wakeups.** Only wakeups that `ScheduleWakeup` creates appear. Fixed-interval loops such as `/loop 10m` and other cron schedules do not.
- **Other schedules and the wakeup.** Downrange cannot tell which schedule fired a scheduled prompt, or which one-shot schedule in a turn-end snapshot is the wakeup. A prompt of a scheduled task, a routine, or a fixed-interval loop that fires from 5 seconds before the wakeup's due time on clears the wakeup, even before the wakeup's own prompt fires. A cancelled wakeup that only a snapshot can clear stays while any other one-shot schedule remains. It then shows until a scheduled prompt fires near its due time, a new `ScheduleWakeup` result replaces it, or it is 10 minutes past due.
- **Progress read from output.** Progress is a guess from the text. A short date such as `10/11` counts as progress, unless a two-digit clock follows it, as in `10/11 14:03`. `N of M` followed by a space inside a path, such as `Disc 1 of 2 (live)/a.flac`, counts too, and so does another ratio or percentage that is not progress. These change the bar and the rate ETA. Full dates such as `2026/10/11` and `10/11/2026` do not count. Neither does `N of M` joined to a path or a number, such as `/backups/3 of 7/`, `version 1.2 of 7.0`, or `3 of 7.5 GB`. The date rule also drops real progress of 1 to 12 out of at most 31 before a clock, such as `3/15 00:12`.
- **Progress read from output.** Progress is a guess from the text. By default, a ratio counts only in a known progress format: in brackets, at the start of a line, after a step word such as `Step` or `Epoch`, after a drawn bar, or in Docker Compose's `[+] Running 3/7`. A ratio in such a place that is not progress still counts. A short date counts, such as `[10/11]` or `10/11 deploy` at the start of a line, unless a two-digit clock follows it, as in `10/11 14:03`. So does `N of M` after a step word and before a space inside a path, such as `Part 1 of 2 (live)/a.flac`, and a percentage that is not progress, such as `CPU 45%`. Real progress in another place, such as `loss 12/40` or `[12:34:56] 3/15 done`, does not count. With the `genericRatios` option on, a ratio counts in any place, so `Released 10/11`, `Disc 1 of 2 (live)/a.flac`, and other ratios that are not progress count too. These change the bar and the rate ETA. In both modes, full dates such as `2026/10/11` and `10/11/2026` do not count. Neither does `N of M` joined to a path or a number, such as `/backups/3 of 7/`, `version 1.2 of 7.0`, or `3 of 7.5 GB`. The date rule also drops real progress of 1 to 12 out of at most 31 before a clock, such as `3/15 00:12`.
- **Only the end of an output.** A direct read of a file up to 4 MiB keeps the last 16,384 characters (UTF-16 code units), which can be up to 48 KiB of non-ASCII text. A tail read above 4 MiB keeps the last 16,384 bytes. Progress and the last line come from that end. Long lines after a progress line can push it out.
- **ETAs during output silence.** When output stops, the bar keeps its last reading, and the rate ETA keeps counting down from the time of that reading. Once the estimate runs out, the ETA stays at `<1m` while the job runs. This holds after `quiet` shows too, so a row can show `20%`, `<1m`, and `quiet 2m 00s` together. A Monitor whose events stop behaves the same way.
- **Monitor progress after an update.** An earlier version of Downrange saved progress with no mark of its format, and counted any ratio. With `genericRatios` off, the first load of this version drops that progress and its rate ETA. A shell reads its output again at once, and its rate ETA starts again from the new reading. A Monitor shows its event count until its next event in a known format.
- **Monitor deadlines and rate ETAs.** A Monitor watch shows its deadline only when it has no progress-based ETA. Once its progress gives a rate ETA, or reaches 100% and shows `finishing`, the deadline no longer shows. A rate ETA can run past the deadline: a 30-minute watch that goes from 1% to 2% in its first minute shows `~1h 38m`, although its deadline is 29 minutes away.
- **Time in the foreground before a move to the background.** A shell job's clock starts when the Bash call returns its background result. A command that first ran in the foreground, such as one moved at its timeout or with ctrl+b, loses that time. Its age, its `took` time, and the run it adds to the duration history count only the time after the move. For example, a command moved after 2 minutes that ends 1 minute later shows `took 1m 00s` and records 1 minute. Runs of one command in one folder share one history, whatever the launch mode. The next run of that command that starts in the background then shows `~1m usual`, even when it needs 3 minutes, and in the pane it can turn yellow early. A moved run can get a usual-duration ETA that is too long.
- **Duration history after a snapshot end.** A run adds to the duration history only when its completion notice arrives while its row still runs. When a turn-end snapshot already ended the row as `ended without a report`, the later notice marks it completed but adds no duration.
Expand All @@ -187,7 +201,7 @@ Background work keeps running after these commands. Downrange carries running jo
- **Tasks first seen in a snapshot.** Claude Code's turn-end snapshot can list a Monitor as a shell. A launch result is the result of the Bash or Monitor call that started a task. A Monitor first seen in a snapshot shows as a shell until its launch result, its first event, or its end notification arrives. Only its launch result gives it a deadline. A shell first seen in a snapshot shows no last line, progress, ETA, or `quiet` flag until its launch result or a notification names its output file. Only its launch result brings a usual-duration ETA. A task that started before Downrange loaded, such as one in a session moved to another process, gets no launch result. Claude Code's internal tasks also appear, under their engine labels, and a long-lived one can keep the band visible. Use **Hide** to dismiss the band.
- **`awaiting confirmation`.** A turn-end snapshot that leaves out a job younger than 10 seconds does not end it. The job keeps running with `awaiting confirmation` until newer input confirms or ends it, such as a later snapshot or a notification. The label does not expire on a timer.
- **Inputs that reach Downrange late.** Claude Code gives its events no shared sequence number. Downrange stamps each input with the time it receives it, and drops an input older than one it already applied to the same job. Hooks can overlap, so an end notification can land after a newer output read, agent list, or snapshot of its job. A completed, failed, or stopped notification of a shell, Monitor, or workflow still applies then, with its exit code, because such a task never runs again. Downrange drops a late end of an agent or a teammate, a `blocked` end, and the end of a remote task such as a remote workflow, because such a task can run again under its id. That row keeps running until a later agent list ends it, or a snapshot ends it as `ended without a report`, with no exit code or end note. A teammate can leave the agent list as it ends, so its row ends at the next turn-end snapshot. A notification does not add back a job that left the list, such as one you cleared. It still can when it lands during `/clear`, `/resume`, or `/branch`, before Downrange restores what it remembers, or when Downrange forgot the task (see the 2,000 tasks below). The row then comes back as running, with a `≥` age, until a later snapshot ends it.
- **Launch details that arrive late.** The same rule applies to launch results. A newer notification, Monitor event, snapshot, or output read of a task can land while Downrange still handles its launch result. Downrange then drops what the launch result adds. A Monitor then shows no deadline, and its age starts with `≥`. A shell can lose its usual-duration ETA, or its output file and launch note too, and that run adds nothing to the duration history. A notification that later names the shell's output file still brings back its last line.
- **Launch details that arrive late.** The same rule applies to launch results. A newer notification, Monitor event, snapshot, or output read of a task, or a reload of Downrange, can land while Downrange still handles its launch result. Downrange then drops what the launch result adds. A Monitor then shows no deadline, and its age starts with `≥`. A shell can lose its usual-duration ETA, or its output file and launch note too, and that run adds nothing to the duration history. A notification that later names the shell's output file still brings back its last line.
- **Commands that subagents start.** A command with the note `ends with its agent` ends when its subagent answers. It then shows `ended with its agent` and no exit code, unless its own notification arrives later.
- **Agents that wait.** A subagent that the agent list reports as pending, waiting, or idle shows that word, beside its tool count when it shows one. It shows no `quiet` flag and no failed-calls flag, however long it stays in that state.
- **Agents that run elsewhere.** Downrange sees the tool calls only of agents that run in this Claude Code process. A remote subagent shows `remote`, and a teammate in a terminal pane of its own shows `own pane`. Their rows show no tool count and no `quiet` flag, so a stuck or dead one looks the same as a busy one. A row whose launch result Downrange missed, or that an earlier version of Downrange saved, still keeps `0 tools` and shows `quiet` from 2 minutes after launch, even while the agent works. Its notification or a snapshot still ends it.
Expand Down
Loading