diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json
index 05beae4..bca7ce4 100644
--- a/.claude-plugin/plugin.json
+++ b/.claude-plugin/plugin.json
@@ -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",
diff --git a/AGENTS.md b/AGENTS.md
index b736930..a36c453 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -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 |
@@ -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: : `, 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`.
diff --git a/README.md b/README.md
index 2c1558a..fba3a63 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
-
+
Progress and an ETA for background shells and Monitor watches, read from their real output or past runs, with no model request.
@@ -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.
@@ -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`.
@@ -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.
@@ -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