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
2 changes: 1 addition & 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.1",
"version": "1.1.2",
"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 Down
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ Notes:
| `hooks/register.tsx` | Every hook, every `$` call, the command name and the pane constants, the state atoms, the writers, the ticker, the carry, and the diagnostics |
| `hooks/limits.ts` | Every named limit |
| `hooks/output.ts` | Clipping, display lines, progress, ETA math, durations, the output path rule, and the end of an output |
| `hooks/redirect.ts` | The redirect rule: the one file that a background command's text sends its output to |
| `hooks/notices.ts` | Task-notification parsing |
| `hooks/jobs.ts` | Job builders and pure reducers |
| `hooks/history.ts` | The usual-duration history |
Expand Down Expand Up @@ -80,7 +81,7 @@ Run all of them after each change. All must pass.
- 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.
- Start every diagnostic with `downrange: <event>: `, then the cause. Never log output text, prompt text, or a command line.
- Add no network call, no file write, and no host command other than `tail -c` for outputs over 4 MiB.
- Read only output files whose path matches the output path rule for the job's own id.
- Read only two files of a job: its output file, whose path matches the output path rule for the job's own id, and its redirect file, the one regular file that `hooks/redirect.ts` finds in the job's command.
- Look up words that come from the engine (statuses, task types, tool names) in a `Map`, never in a plain object.
- In tests, stub every tool result, file, and process. A test never runs a real command.
- Name each new limit in `hooks/limits.ts`.
Expand Down
22 changes: 14 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<img src="https://img.shields.io/badge/Claude_Code-2.1.288%2B-D97757" alt="Claude Code 2.1.288+">
<img src="https://img.shields.io/badge/Claude_Code-mod-555555" alt="Claude Code mod">
<img src="https://img.shields.io/badge/license-MIT-78997A" alt="License: MIT">
<img src="https://img.shields.io/badge/version-1.1.1-555555" alt="Version: 1.1.1">
<img src="https://img.shields.io/badge/version-1.1.2-555555" alt="Version: 1.1.2">
</p>

<p align="center"><strong>Progress and an ETA for background shells and Monitor watches, read from their real output or past runs, with no model request.</strong></p>
Expand All @@ -23,7 +23,7 @@ Downrange reads that output on your machine and shows where each job stands.

## Features

- **Progress from real output.** `45%`, `[12/40]`, and `Step 3 of 7` lines, and the progress lines of tools such as Docker, cargo, pytest, and git, become a progress label, and a bar in the pane. Shell output and Monitor events both count. A ratio in another place, such as `loss 12/40`, counts only with the `genericRatios` option on.
- **Progress from real output.** `45%`, `[12/40]`, and `Step 3 of 7` lines, and the progress lines of tools such as Docker, cargo, pytest, and git, become a progress label, and a bar in the pane. Shell output and Monitor events both count. A ratio in another place, such as `loss 12/40`, counts only with the `genericRatios` option on. A shell command that sends its output to a file, such as `make > build.log 2>&1`, shows progress from that file.
- **ETA.** From the progress rate. With no rate yet, from the usual duration of the same command in the same folder, or from a Monitor watch's deadline. In the pane, a usual-duration ETA turns yellow once the run passes 1.5 times its usual duration.
- **The background tasks of your session.** Shell commands (`run_in_background`, ctrl+b, or a command moved at its timeout), Monitor watches, background and remote subagents, teammates, workflow runs, and the pending dynamic `/loop` wakeup. For background sessions, use agent view (`claude agents`).
- **Liveness flags.** `quiet 3m 00s` for a shell command or subagent that stayed silent for 2 minutes or more, and `3 failed calls in a row` for a struggling subagent.
Expand Down Expand Up @@ -58,6 +58,7 @@ flowchart LR

- **Launches.** Hooks on the Bash, Monitor, Agent, and Workflow tools record each background launch with its id and title, and a shell's output file. A ScheduleWakeup result sets the wakeup. A TaskStop result marks its job stopped.
- **Output.** The ticker reads each running shell's output file every 2 seconds, or every 10 seconds above 256 KiB. It keeps the end of the file: the last 16,384 characters, or the last 16,384 bytes through `tail` above 4 MiB. Progress comes from the last 10 display lines of that end.
- **Redirect files.** Some commands send their output to a file, such as `> build.log` or `| tee build.log`. The ticker then also reads the one file that the command's text names, its redirect file, in the same way. At each read, the row shows the file that changed last: Claude Code's output file or the redirect file. On equal times, it shows Claude Code's output file. While the row shows the redirect file, it notes `output: build.log`. The end and the exit code still come from Claude Code's notification. Downrange follows no redirect file on Windows, or in a command with code that may not run, such as an `if` or a function.
- **Ends.** Task notifications end jobs and carry Monitor events. The agent list updates subagent states. Stop and SubagentStop snapshots add tasks that Downrange missed, and end tasks that left the list after a 10-second grace period. A scheduled prompt that fires from 5 seconds before the wakeup's due time clears the wakeup. So does a snapshot with no one-shot schedule, after the same grace period.
- **Usual durations.** A clean end (exit code 0 or none) of a shell that Downrange saw start adds its run time to a history keyed by a hash of folder and command. The usual-duration ETA is the median of the last 5 runs.
- **Views and carry.** The band, the pane, and `/downrange` draw from the same state. The ticker stops when nothing is live. At session end, Downrange keeps the running jobs and the wakeup in memory, and restores them after `/clear`, `/resume`, or `/branch`.
Expand Down Expand Up @@ -187,7 +188,7 @@ Set options in `/config`, or under `pluginConfigs["downrange@downrange"].options
### What it does not catch

- **Progress.** Ratios outside known formats, such as `loss 12/40`, unless `genericRatios` is on. Dates such as `[10/11]` can still count.
- **Jobs.** Fixed-interval loops such as `/loop 10m`, other cron schedules, and jobs after `/background` until the next turn end.
- **Jobs.** Fixed-interval loops such as `/loop 10m`, other cron schedules, jobs after `/background` until the next turn end, and work that goes on after its job ends. `nohup` alone does not end a job at once. A command that ends with `&` does. Downrange follows no file of a command with `&` or `nohup`.
- **Stalls.** Stuck remote subagents, teammates in their own panes, and waiting agents. They show no `quiet` flag.
- **ETAs.** Run time in the foreground before ctrl+b or a timeout move. In output silence, a rate ETA runs down to `<1m` and stays there.

Expand Down Expand Up @@ -216,10 +217,15 @@ Downrange watches events and changes almost none of them. It makes no network re
- **What it sends, and where.** Downrange itself sends nothing off your machine. Its output goes to your screen: the band, the pane, the `/downrange` table, the local status answer, and `downrange:` diagnostic lines in the transcript and the debug log. Two of these go further through Claude Code itself:
- The `/downrange` table enters the conversation, so Claude reads it with its next request, as it reads any command output. The table cuts last output lines to 80 characters and removes control characters. A subagent row in the table shows only the tool it last used; the pane also shows that tool's input in short.
- Where a remote surface draws the band or the pane, Claude Code sends that drawing to the surface.
- **The program it starts.** Only `tail -c 16384 <output file>`, with a 2-second time limit, to read the end of a large output. It runs only when a background shell's output file is over 4 MiB, the largest file Claude Code's file read accepts. It prints the last 16,384 bytes of that file. Downrange passes it as an argument list, with no shell. The output file path is the only part that varies. That path must be the output file of the job's own task id: absolute, in a `tasks` folder, and named `<task id>.output`. An organization policy that refuses mods which run host commands also refuses this mod.
- **Local data it reads.** The output files of background shells, with their size and change time. Its own duration store, a JSON file that Claude Code keeps for the mod in your Claude Code configuration folder. The session's working folder, only to hash it with a command. The list of the session's agents and their states.
- **Conversation data it reads.** The input and result of the tool calls in the hook table below: a command or description, the task id, the output file path, and the wakeup time and reason. One input field of each tool call a tracked subagent makes (see the last item). Task notifications. The `background_tasks` and `session_crons` lists of Stop and SubagentStop hooks. The text of a prompt you type, only when `answerStatusPrompts` is on, to compare it with the status phrases. Downrange reads no transcript file.
- **What it keeps.** Session state, which Claude Code holds for the session and which other mods in the session can read. It holds job titles, last output lines, notes, tool summaries, and the wakeup's reason, each cut to 200 characters or fewer. It also holds task ids, output file paths, progress, exit codes, times, and the last status prompt it answered. The duration store holds run times under a hash of folder and command, with no command text, no path, and no output.
- **The program it starts.** Only `tail -c 16384 <file>`, with a 2-second time limit, to read the end of a large output. It runs only when the file it reads for a background shell is over 4 MiB, the largest file Claude Code's file read accepts. It prints the last 16,384 bytes of that file. Downrange passes it as an argument list, with no shell. The file path is the only part that varies. That path must be the output file of the job's own task id (absolute, in a `tasks` folder, and named `<task id>.output`), or the job's redirect file (see the next item). An organization policy that refuses mods which run host commands also refuses this mod.
- **Local data it reads.** Downrange reads these files, and no other file:
- The output file of each background shell, which Claude Code writes in its `tasks` folder, with its size and change time.
- The redirect file of a background shell, with its size and change time, at each read of its output file. Downrange reads the redirect file's text when that file changed last, or while the output file holds no line of the job. That is the one file that the command's own text sends its output to, such as `build.log` in `make > build.log 2>&1`. Downrange reads it only when it is a regular file, never a path under `/dev`, and never on Windows.
- Its own duration store, a JSON file that Claude Code keeps for the mod in your Claude Code configuration folder.

It also reads the session's working folder, to hash it with a command and to place a relative redirect file, and the list of the session's agents and their states.
- **Conversation data it reads.** The input and result of the tool calls in the hook table below: a command or description, the task id, the output file path, and the wakeup time and reason. Downrange reads a background command's text to find its redirect file. One input field of each tool call a tracked subagent makes (see the last item). Task notifications. The `background_tasks` and `session_crons` lists of Stop and SubagentStop hooks. The text of a prompt you type, only when `answerStatusPrompts` is on, to compare it with the status phrases. Downrange reads no transcript file.
- **What it keeps.** Session state, which Claude Code holds for the session and which other mods in the session can read. It holds job titles, last output lines, notes, tool summaries, and the wakeup's reason, each cut to 200 characters or fewer. It also holds task ids, output file paths, redirect file paths, progress, exit codes, times, and the last status prompt it answered. The duration store holds run times under a hash of folder and command, with no command text, no path, and no output.
- **Permissions.** Downrange never changes a permission decision. No hook denies or approves a call, rewrites a tool's input, or answers a permission prompt. Claude Code and you decide as before. `claude plugin validate` lists most hooks as gating hooks with a `.catch`, because their events can block. Each such `.catch`, such as the one on `classic.SessionStart` in `hooks/register.tsx`, only logs the failure and passes the event on unchanged with `next(e)`. The `/downrange` command and the Jobs pane show the failure instead.
- **The `Reflect.get` call in `hooks/jobs.ts`.** It reads one named field of a tracked subagent's tool input, from a fixed list in that file. The fields are `command` for Bash and Monitor, the file name from `file_path` or `notebook_path`, the `pattern` of Grep and Glob, the host of a WebFetch `url`, the `query` of WebSearch, and the `description` of Agent. That field becomes a summary such as `Edit: view.ts`, cut to 60 characters. Each tool's input has its own shape, so the code reads the field by name. It calls no function and reads no other field. Other tools show only their name.

Expand Down Expand Up @@ -251,7 +257,7 @@ Downrange watches events and changes almost none of them. It makes no network re
| --- | --- | --- |
| `/downrange` is missing | Claude Code rejected command registration | Read the cause after `downrange: session.start: /downrange was not registered:` in the transcript. A built-in name conflict is one possible cause. Use the band's Open button |
| A job shows `no output file` | The tool result named no output file | Run `claude --debug` and search for `downrange: tool.call:` |
| A job shows `output unavailable` or `tail failed` | The output file is gone, or `tail` is not on PATH (common on Windows, for outputs over 4 MiB) | Run `claude --debug` and search for `downrange: clock.every:` |
| A job shows `output unavailable` or `tail failed` | The output file or the redirect file is gone, or `tail` is not on PATH (common on Windows, for outputs over 4 MiB) | Run `claude --debug` and search for `downrange: clock.every:` |
| `/downrange pane` prints `The Jobs pane is not shown here` | Nothing draws in this session, or the surface places no panes | Use `/downrange` |
| Ages and output lines stop moving | Another mod refused a timer period | The next change starts the timer again; `claude --debug` shows `the ticker stopped` |
| Jobs end only when Claude's turn ends | Some Claude Code notices are plain text and carry no structured end status | No action for plain notices. If `claude --debug` shows `a task notification held no complete` or `unknown notification status`, report that line |
Expand Down
Loading