brl-subagent 2.3.8 → 2.4.0

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/AGENT.md CHANGED
@@ -27,7 +27,7 @@ delegation-heavy work.
27
27
  - Background single returns immediately with an agent id; a completion message wakes you (see below).
28
28
  - Chain, parallel, and graph are batch — one call, many subtasks. Chain and graph are foreground; `tasks` + `background: true` fans out: every task starts as its own background agent, the call returns with one id per task in task order, and you are woken once per agent as each finishes.
29
29
  - Fan-out validates the whole batch before spawning anything: any invalid task (cwd, `outputFile`, or a pre-task check) rejects the entire batch before a single spawn starts, and the error names the task (`Task N ("label")`).
30
- - Fan-out rejects `gitMode: "branch"` for the batch — the git lock is awaited inside the first spawn and held until that agent settles, so same-cwd branch-mode spawns would serialize and block the call (the rule is blanket because the lock is keyed by the cwd path, not the repository root). It also rejects `approvalMode: "always"` up front, and the session cost limit is checked as per-task estimate × N.
30
+ - Fan-out rejects `gitMode: "branch"` for the batch — the git lock is awaited inside the first spawn and held until that agent settles, so branch-mode spawns targeting the same repository would serialize and block the call (the rule is blanket because the lock is keyed by the resolved repository root, so any two units of the same repo contend). It also rejects `approvalMode: "always"` up front, and the session cost limit is checked as per-task estimate × N.
31
31
  - Spawn failure mid-loop stops further spawns and reports the failed task plus the ids already started; abort mid-loop stops further spawns and reports the ids already started. Either way those agents are detached and still wake you.
32
32
  - Chain stops at the first failure; max 10 steps.
33
33
  - Parallel runs every task regardless of the others — no short-circuit.
@@ -35,22 +35,29 @@ delegation-heavy work.
35
35
 
36
36
  ## The completion contract
37
37
 
38
- - A background run wakes you with a structured `subagent-completion` message when it reaches a terminal state (`completed`, `failed`, or `stopped`). The `details` carry `id`, `status`, `errorCategory` (with `errorMessage` when present), cost/tokens/duration, and `label`; the content carries a tail of output.
39
- - The wake triggers a turn even when you are idle. Do not poll for it.
38
+ - A background run wakes you with a structured `subagent-completion` message when it reaches a terminal state (`completed`, `failed`, or `stopped`). The `details` carry `id`, `status`, `errorCategory` (`"unknown"` when unclassified) and `label`; `errorMessage`, `stopReason`, cost/tokens/duration ride along only when the run entry has been finalized — a `stopped` run is notified at stop time, **before** finalize, so those fields are usually absent there. The content carries a tail of output when one has been captured.
39
+ - The wake triggers a turn even when you are idle — whenever the knob's wake condition covers that terminal status (see the matrix below). Do not poll for it.
40
40
  - Delivery mode by status holds at knob `"all"`: `failed`/`stopped` → steering you to act; `completed` → follow-up. The `completionNotify` knob changes both the delivery mode and the wake: under `"failed"` a `completed` run is delivered as a passive `nextTurn` (no wake) while `failed`/`stopped` still steer; under `"off"` everything is a passive `nextTurn` and nothing wakes you. (The knob controls whether an idle conductor is triggered — see below.)
41
- - Knob dependency: polling is correct only when wakes are disabled (`completionNotify: "off"`). The wake is per-terminal-status and knob-scoped: under `"all"` a terminal run always wakes you; under `"failed"` only a `failed` or `stopped` run wakes you — a `completed` run stays a passive `nextTurn` (no wake); under `"off"` nothing wakes you. So polling is wrong unless wakes are disabled. One status check as a stall check is legitimate; repeated polling is not.
41
+ - Knob dependency: polling is correct only when wakes are disabled (`completionNotify: "off"`). The wake is per-terminal-status and knob-scoped: under `"all"` a terminal run always wakes you; under `"failed"` only a `failed` or `stopped` run wakes you — a `completed` run stays a passive `nextTurn` (no wake); under `"off"` nothing wakes you. So repeated polling is wrong; a single status check as a stall check is legitimate, and a status check is needed only when the knob will not wake you for that outcome (`"off"`, or a `completed` run under `"failed"`).
42
42
  - One message per run — the extension deduplicates on the first terminal event for a run id, so don't expect multiple messages for a single run. A fan-out wakes you once per agent — still one message per run (each agent is its own run), never coalesced.
43
- - Honest records: trust the message's `category`/abort source over your own guess. The provider/abort origin is authoritative on why the run stopped.
43
+ - Honest records: trust the message's `errorCategory`/abort source over your own guess. The provider/abort origin is authoritative on why the run stopped.
44
44
  - Steering is not an abort. Being steered to act on a terminal run is not terminating it. A real `stop` is an abort — and a stopped run still wakes you.
45
45
 
46
46
  ## Delegation judgment
47
47
 
48
48
  - Foreground vs background: if your next step needs the result inline, run foreground. If the user can keep working or is AFK, run background and act on the wake. Pre-declared independent work while the user is away → `tasks` + `background: true` (fan-out); act on each wake.
49
49
  - Batch vs sequential: if the work is pre-declared, use chain/parallel/graph. If you're steering incrementally with the user, issue sequential calls.
50
- - Retry taxonomy: `retryRunId` is a retry — it re-runs with the recorded task and params. A re-dispatch (a fresh call with the same task text) is not a retry. Never re-issue an identical spec after a termination without confirming the cause with the user (Rule 18).
50
+ - Retry taxonomy: `retryRunId` is a retry — it re-runs with the recorded task and params. A re-dispatch (a fresh call with the same task text) is not a retry. Never re-issue an identical spec after a termination without confirming the cause with the user.
51
51
  - When to delegate: when the task needs an isolated context, a deep investigation, parallel research, or a long-running analysis.
52
52
  - Approval: `approvalMode: "always"` is rejected in background — there is no dialog. Use `auto` (default) or `writes`; `writes` auto-approves in background.
53
53
 
54
+ ### Claim verification
55
+
56
+ - Ground claims about run state, liveness, or elapsed time in observed results — the subagent result API (`get_subagent_result`), the user-visible monitor, or a command's output.
57
+ - Do not infer liveness from a run record: an agent's output record is finalized when the run completes, so a quiet record is not evidence the agent has stopped.
58
+ - Measure elapsed time rather than estimating it; state the measured value.
59
+ - When a claim is unverified, say so.
60
+
54
61
  ## Canonical shapes
55
62
 
56
63
  - Batch chain with `{previous}`: `chain: [{ task: "…" }, { task: "Use {previous} …" }]` — each step gets the prior step's output.
@@ -59,6 +66,11 @@ delegation-heavy work.
59
66
  - Background fan-out: `tasks: [...], background: true` — one background agent per task, one id per task in task order, one wake per agent.
60
67
  - retryRunId: pass a failed run's id to re-run it with its recorded task and params; explicit parameters override the originals.
61
68
 
69
+ ## Deadlines and retries
70
+
71
+ - `timeout` (ms) is a **deadline, not a ceiling** — an explicit value is honored as given; 30 minutes is only the default for a background run that passes none. Background expiry aborts the session (the run ends `stopped`); foreground expiry kills the process.
72
+ - `retryOnTimeout` applies to **foreground runs only**. A timed-out background run is not auto-retried: re-dispatch it with `retryRunId` if that is the right call.
73
+
62
74
  ## Mechanics pointer
63
75
 
64
76
  Presets and templates are documented in the schema, not here. Judgment only:
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > Multi-agent orchestration for [pi](https://github.com/earendil-works/pi) — chain, parallel, and dependency-graph delegation to isolated subagents with per-step model routing, preset-driven tool scoping, thinking-level control, and background execution with live monitoring, real abort, and per-agent timeouts.
4
4
 
5
- **Version:** 2.3.8 · **Author:** Azmeen Afandi / [Beeroo Labs](https://beeroolabs.com) · **License:** MIT
5
+ **Version:** 2.4.0 · **Author:** Azmeen Afandi / [Beeroo Labs](https://beeroolabs.com) · **License:** MIT
6
6
 
7
7
  ---
8
8
 
@@ -11,10 +11,10 @@
11
11
  `brl-subagent` is **one of the most capable subagent orchestration extensions for pi** — one `delegate_task` tool that spawns fully isolated subagent processes, each with its own model, context window, tool permissions, and thinking level.
12
12
 
13
13
  - **Multi-step delegation, natively** — chain, parallel, and dependency-graph modes with per-step model routing, so a complex task fans out exactly as you design it.
14
- - **True background execution** — live monitor, real abort (`stop_subagent`), per-agent timeouts, and hard caps. Nothing orphans; nothing leaks.
14
+ - **True background execution** — live monitor, real abort (`stop_subagent`), per-agent timeouts, and a 30-minute default deadline for runs that don't set one. Nothing orphans; nothing leaks.
15
15
  - **Preset-driven tool scoping** — every subagent runs with exactly the tools its job needs, restricted by preset or per-call `tools`/`excludeTools`, with auto-route that picks the right preset when you don't.
16
16
  - **Templates with slots** — saved, file-backed task templates with `${param}` placeholders for workflows you run again and again.
17
- - **Safety by default** — task-fence injection protection, sanitized error paths, owner-only persistence, and a 975+ test suite pinning every contract against the real pi SDK.
17
+ - **Safety by default** — task-fence injection protection, sanitized error paths, owner-only persistence, and a 1200+ test suite pinning every contract against the real pi SDK.
18
18
 
19
19
  ---
20
20
 
@@ -59,11 +59,18 @@ pi remove npm:brl-subagent # uninstall
59
59
  | `/brl-subagent history` | Browse past subagent runs |
60
60
  | `/brl-subagent monitor` | Live monitor running subagents |
61
61
  | `/brl-subagent dashboard` | Live observability dashboard |
62
+ | `/brl-subagent compliance` | Compliance reports (file access, secrets exposure, full summary) |
62
63
  | `/brl-subagent retry` | Browse failed runs to retry |
63
64
  | `/brl-subagent reset` | Reset all configuration |
64
65
 
65
66
  All settings persist across sessions.
66
67
 
68
+ ### Environment variables
69
+
70
+ - `BRL_LOG_LEVEL` — minimum level written to the log file. One of `debug`,
71
+ `info` (default), `warn`, `error`. Read once at module load; unrecognised
72
+ values fall back to `info`. Runtime changes go through `setLogLevel()`.
73
+
67
74
  ---
68
75
 
69
76
  ## delegate_task parameters
@@ -82,14 +89,39 @@ All settings persist across sessions.
82
89
  | `inheritSystemPrompt` | boolean | `true` | Whether to inherit the main agent's system prompt. Set `false` to save tokens. |
83
90
  | `thinkingLevel` | string | — | `off` / `minimal` / `low` / `medium` / `high` / `xhigh`. Capped at user's configured max. |
84
91
  | `outputFile` | string | — | Path for the subagent to write full findings. Returns only a summary. |
85
- | `timeout` | number | — | Max milliseconds. Exceeded → SIGTERM (5s grace) → SIGKILL. |
92
+ | `timeout` | number | — | Max milliseconds. Foreground: exceeded → SIGTERM (5s grace) → SIGKILL. Background: the session is aborted and the run ends `stopped` with the timeout reason. A background run with no explicit `timeout` gets the 30-minute default deadline — an explicit timeout is honored as given (a default, not a ceiling). A raw value `≥ 2^31-1` normalizes to no timeout (for a background run, the 30-minute default then applies). |
86
93
  | `cwd` | string | — | Working directory. Defaults to conductor's cwd. |
87
94
  | `background` | boolean | `false` | Spawn as an independent background session; returns an ID immediately. With `tasks` it fans out (one background agent per task, one ID per task), and the conductor is woken once per agent as each finishes. See [Background execution](#background-execution). |
88
95
  | `priority` | string | — | Concurrency priority: `critical` / `high` / `normal` / `low`. Defaults to `normal`; higher-priority delegations queue ahead. `tasks[]` / `graph[]` steps can set `priority` per unit (see below). |
96
+ | `tools` | array | — | Explicit tool allowlist for the subagent. |
97
+ | `excludeTools` | array | — | Tool names to disable for the subagent. |
98
+ | `noBuiltinTools` | boolean | — | Disable all pi built-in tools for the subagent. |
99
+
100
+ ### Safety & control
101
+
102
+ | Parameter | Type | Default | Description |
103
+ |---|---|---|---|
104
+ | `gitMode` | string | — | `'branch'` creates a work branch, captures the diff, and switches back; `'none'` does nothing. Falls back to the configured default. |
105
+ | `approvalMode` | string | — | `auto` / `writes` / `always`. Default is user config (`/brl-subagent approval`). `always` is rejected for background runs. |
106
+ | `force` | boolean | `false` | Dispatch anyway past a capability pre-flight block; the block is downgraded to a warning. |
107
+ | `retryRunId` | string | — | Re-run a previously failed run with its recorded task and params. |
108
+ | `retryOnTimeout` | boolean | `false` | Retry once if a **foreground** subagent times out. The second timeout is a final failure. Background runs are not auto-retried — re-dispatch with `retryRunId`. |
109
+
110
+ **Capability pre-flight:** before spawning, the extension checks that the resolved toolset can actually do the task — a run/execute/test/compile/benchmark task with no `bash`, or an exploration task (search/grep/find/list/locate/glob) with none of `find`/`ls`/`grep`/`bash`. Such a dispatch is **rejected** by default; `force: true` downgrades the rejection to a **warning**. `force` never suppresses an `outputFile`-without-write conflict — that stays a hard error. Warnings are surfaced in the returned result in every mode.
111
+
112
+ **Retries (`retryRunId`):** a retry rebuilds the parameter object from a fixed set of fields, falling back to the original run's recorded values, with explicit values on the retry call winning: `task`, `label`, `model`, `preset`, `systemPrompt`, `inheritSystemPrompt`, `thinkingLevel`, `priority`, `outputFile`, `timeout`, `cwd`, `tools`, `excludeTools`, `noBuiltinTools`, plus the four **execution-shape** fields `background`, `gitMode`, `approvalMode` and `force`.
113
+
114
+ **The execution-shape fields are restored, so a retry keeps the original's shape:** retrying a background run re-runs it in the background (with its completion wake), a `gitMode: 'branch'` retry keeps its work branch, an `approvalMode`-constrained retry keeps that gating, and a retry that was capability-blocked keeps its `force`. Passing any of them explicitly on the retry call overrides the recorded value. One qualification on `gitMode`: when neither the record nor the call carries one, the **configured** default still applies — so a user with a global `gitMode: 'branch'` gets a work branch on every retry, and one with the default `'none'` gets none. Do not assume "no work branch" without checking the effective `gitMode`.
115
+
116
+ **Fan-out origins are the exception.** Parallel/chain/graph units are recorded per unit, and those records deliberately snapshot **no** `background`, so retrying a unit is a **single run in the foreground**. Pass `background: true` explicitly if you want a background retry.
117
+
118
+ **Not restored:** `chain` / `tasks` / `graph` and `params` are discarded, so retrying a multi-step run silently degrades to a single task — re-issue it fresh for those shapes. `retryOnTimeout` is **explicit-only**: it is never restored from the recorded run and takes effect only when you pass it on the retry call (foreground only; it arms a deadline for *this* call).
119
+
120
+ **`template` is asymmetric:** the original run's template is **not** restored, but a `template` you supply explicitly on the retry call **does** take effect (it is resolved before the retry merge, issue #175) — its body wins over the recorded `task`.
89
121
 
90
122
  ## Multi-step modes (chain, tasks, graph)
91
123
 
92
- Beyond a single `task`, `delegate_task` accepts three multi-step shapes: `chain` (sequential steps, `{previous}` references the prior step's output), `tasks` (parallel, independent), and `graph` (dependency-ordered, `{otherId}` references another task's output).
124
+ Beyond a single `task`, `delegate_task` accepts three multi-step shapes: `chain` (sequential steps, `{previous}` references the prior step's output), `tasks` (parallel, independent), and `graph` (dependency-ordered, `{<nodeId>}` references another node's output — the referenced node's own `id`, which must be a word-character id: letters, digits, and underscore. `{step-1}` and `{node.a}` are not matched and are left as literal text).
93
125
 
94
126
  `background: true` fans out the `tasks` shape into one background agent per task (one ID per task, one completion wake per agent); `chain` and `graph` reject it.
95
127
 
@@ -113,7 +145,7 @@ Stops a running background agent — a real abort, not a status flip.
113
145
  |---|---|---|
114
146
  | `agent_id` | string | Agent ID of the running background session to stop |
115
147
 
116
- **What it does:** aborts the live session via `session.abort()` — the pending `prompt()` resolves with `stopReason: "aborted"` rather than hanging — and marks the agent `stopped`. Partial work from a `gitMode: 'branch'` run is captured in the result before the work branch is discarded. Returns an error if the agent is not found or already terminal. Use it to halt a background agent that is no longer needed.
148
+ **What it does:** aborts the live session via `session.abort()` — the pending `prompt()` resolves with `stopReason: "aborted"` rather than hanging — and marks the agent `stopped`. Partial work from a `gitMode: 'branch'` run is captured in the result before the work branch is discarded. Returns an error if the agent is not found; an already-terminal agent is a no-op success (the call is idempotent). Use it to halt a background agent that is no longer needed.
117
149
 
118
150
  ---
119
151
 
@@ -215,10 +247,12 @@ These are deliberately THIN teaching examples — 2-4 line bodies showing `${par
215
247
  ```js
216
248
  delegate_task({
217
249
  template: "code-review",
218
- params: { pr: "https://github.com/org/repo/pull/42" },
250
+ params: { target: "https://github.com/org/repo/pull/42" },
219
251
  });
220
252
  ```
221
253
 
254
+ The built-in `code-review` template's slot is `${target}` (its companion `security-audit` / `refactor` templates use `${target}` too). The `${pr}` slot in the custom-template YAML example above belongs to *that* file, not the built-in one.
255
+
222
256
  - Every `${param}` slot in the template must be provided in `params` — a missing slot fails the call with an error listing the missing names.
223
257
  - Template fields are **defaults**: explicitly-provided `delegate_task` parameters override them.
224
258
  - The template's task **replaces** `params.task` entirely.
@@ -246,7 +280,7 @@ delegate_task({
246
280
 
247
281
  ## Background execution
248
282
 
249
- Set `background: true` to spawn the subagent as an independent session that returns an ID immediately and keeps running. Its progress is tracked via `getAgent` and the live monitor while the main session continues. (Steering via `steerAgent` currently records the message in the transcript and marks the agent as steered — delivery to the live session is pending pi's extension API.)
283
+ Set `background: true` to spawn the subagent as an independent session that returns an ID immediately and keeps running. Its progress is tracked via `getAgent` and the live monitor while the main session continues. Steering via `steerAgent` delivers the message into the agent's live session — it is queued while the agent runs and delivered after the current tool call finishes, before the next model call. Repeat steers work, and a delivery failure surfaces as a real tool error rather than a recorded-but-undelivered steer.
250
284
 
251
285
  **System prompt semantics:** background sessions are spawned with the same built prompt as foreground subagents (base prompt when `inheritSystemPrompt: true`, custom prompt, preset guidance, and subagent instructions). Like pi's own `--append-system-prompt`, supplying this prompt **replaces** any discovered `.pi/APPEND_SYSTEM.md` / global `APPEND_SYSTEM.md` content — this matches foreground subagent behavior exactly. With `inheritSystemPrompt: true` (the default) your conductor session's instructions — including its own appended content — are inherited through the base prompt.
252
286
 
@@ -254,7 +288,7 @@ Set `background: true` to spawn the subagent as an independent session that retu
254
288
 
255
289
  **Background safety controls (issue #28):** background agents honor the same safety controls as foreground runs — no more unattended sessions that bypass approval, git isolation, deadlines, or cost:
256
290
 
257
- - **Per-agent timeout** — the deadline is armed before the prompt starts (preflight time counts toward it). On expiry the session is aborted and the agent ends with status `stopped` and the timeout reason. Timeout values are normalized (`0`/negative/`NaN`/`Infinity`/`≥ 2^31` → no timeout) and a double-fire guard prevents the timer from acting on an already-settled agent.
291
+ - **Per-agent timeout** — the deadline is armed immediately after the prompt is issued, so in-prompt preflight (auth, model resolution) counts toward it. A background run with no explicit `timeout` gets a 30-minute default deadline (the issue #28 orphan protection); an explicit timeout is honored as given — 30 minutes is the default, not a ceiling. On expiry the session is aborted and the agent ends with status `stopped` and the timeout reason. Timeout values are normalized (`0`/negative/`NaN`/`Infinity`/`≥ 2^31` → no timeout, which for a background run means the 30-minute default) and a double-fire guard prevents the timer from acting on an already-settled agent.
258
292
  - **Session cost limit (R5)** — the cost check runs before the background spawn, so a session at its limit cannot bypass it by delegating to background. For a background fan-out the whole batch is checked up front as per-task estimate × N.
259
293
  - **Approval mode** — `approvalMode: 'always'` is rejected for background agents (there is no interactive dialog to approve a diff while running unattended); `'writes'` silently auto-approves with a warning logged. For a fan-out, `'always'` rejects the whole batch before any spawn and `'writes'` warns once for the batch.
260
294
  - **gitMode branch isolation** — with `gitMode: 'branch'` a work branch is created before the run, the agent's changes are committed at teardown so the diff is real, the diff is captured and surfaced via `get_subagent_result`, and the branch is then switched away from and deleted. This requires a clean working tree — a dirty tree is refused loudly rather than risking the base branch. `gitMode: 'branch'` is rejected for background fan-out — see [Background fan-out](#background-fan-out).
@@ -265,10 +299,15 @@ Set `background: true` to spawn the subagent as an independent session that retu
265
299
 
266
300
  Fan-out validates the entire batch before spawning anything. Any invalid task — `cwd`, `outputFile`, or a pre-task check — rejects the whole batch before a single spawn starts, and the error names the task (`Task N ("label")`). Per-task overrides are honoured: `model`, `thinkingLevel`, `tools` / `excludeTools` / `noBuiltinTools`, `systemPrompt` / `inheritSystemPrompt`, `outputFile`, `timeout`, `cwd`, `priority`, `label`.
267
301
 
268
- Three checks reject the fan-out up front, before any spawn: `approvalMode: 'always'`; `gitMode: 'branch'` for the batch (the git lock is awaited inside the first spawn and held until that agent settles, so same-cwd branch-mode spawns would serialize and block the call — the rule is blanket because the lock is keyed by the cwd path, not the repository root); and the session cost limit, checked as per-task estimate × N.
302
+ Three checks reject the fan-out up front, before any spawn: `approvalMode: 'always'`; `gitMode: 'branch'` for the batch (the git lock is awaited inside the first spawn and held until that agent settles, so branch-mode spawns targeting the same repository would serialize and block the call — the rule is blanket because the lock is keyed by the resolved repository root, so any two units of the same repo contend); and the session cost limit, checked as per-task estimate × N.
269
303
 
270
304
  If a spawn fails mid-loop, fan-out stops and reports the failed task plus the IDs already started; an abort mid-loop stops further spawns. Either way, the agents already started stay detached and still wake the conductor. Everything else is unchanged: single background, foreground parallel, the `MAX_PARALLEL_TASKS` cap (8), a live-monitor row per agent, `get_subagent_result` / `steer_subagent` / `stop_subagent` by agent ID, per-agent timeouts, and a single `'writes'` approval warning for the batch.
271
305
 
306
+ ## Contributing
307
+
308
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for setup, the branch model (`main` = releases, `dev` = integration),
309
+ the worktree workflow, and the test gates. Development docs live in [`.development/`](.development/).
310
+
272
311
  ## Changelog
273
312
 
274
313
  See [CHANGELOG.md](CHANGELOG.md) for the release history.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "brl-subagent",
3
- "version": "2.3.8",
4
- "description": "Subagent extension for pi with configurable model and thinking mode via selection UI",
3
+ "version": "2.4.0",
4
+ "description": "Subagent delegation for pi — blocking or background runs, chain/parallel/graph modes, presets and task templates, live monitor, and dispatch capability guards.",
5
5
  "keywords": [
6
6
  "pi-package"
7
7
  ],
@@ -25,7 +25,9 @@
25
25
  ]
26
26
  },
27
27
  "scripts": {
28
- "typecheck": "tsc --noEmit"
28
+ "typecheck": "tsc --noEmit",
29
+ "docs:arch": "node scripts/docs-arch.mjs --write",
30
+ "docs:decisions": "node scripts/docs-decisions.mjs --write"
29
31
  },
30
32
  "peerDependencies": {
31
33
  "@earendil-works/pi-agent-core": "*",
@@ -35,13 +37,13 @@
35
37
  "typebox": "*"
36
38
  },
37
39
  "devDependencies": {
38
- "@earendil-works/pi-agent-core": "^0.87.0",
39
- "@earendil-works/pi-ai": "^0.87.0",
40
- "@earendil-works/pi-coding-agent": "^0.87.0",
41
- "@earendil-works/pi-tui": "^0.87.0",
40
+ "@earendil-works/pi-agent-core": "^1.0.0",
41
+ "@earendil-works/pi-ai": "^1.0.0",
42
+ "@earendil-works/pi-coding-agent": "^1.0.0",
43
+ "@earendil-works/pi-tui": "^1.0.0",
42
44
  "jiti": "^2.7.0",
43
- "typebox": "^1.3.25",
44
- "typescript": "^5.9.3",
45
+ "typebox": "^1.3.34",
46
+ "typescript": "7.0.2",
45
47
  "vitest": "^5.0.0"
46
48
  },
47
49
  "overrides": {
@@ -11,6 +11,7 @@ tools:
11
11
  - ls
12
12
  - write
13
13
  - edit
14
+ - bash
14
15
  ---
15
16
 
16
17
  # Test Engineer
@@ -32,3 +33,4 @@ You are **Test Engineer**, a specialist in test design, coverage analysis, and q
32
33
  - Use descriptive test names that explain the scenario
33
34
  - Prefer explicit assertions over snapshots
34
35
  - Mock external dependencies, not internal logic
36
+ - Run the tests you write — use bash to verify them and report the command and result; when testing a fix, confirm the test fails on the defect first
@@ -1,3 +1,4 @@
1
+ // Purpose: Session-bound concurrency queue: slot acquisition/release, priority insertion, and progress counters.
1
2
  /**
2
3
  * brl-subagent — Concurrency Controller (F8)
3
4
  *
package/src/diff.ts CHANGED
@@ -1,3 +1,4 @@
1
+ // Purpose: Parses git unified diffs into structured per-file summaries and hunks for the result UI.
1
2
  /**
2
3
  * brl-subagent — Git Diff Parser (P5)
3
4
  *
package/src/event-bus.ts CHANGED
@@ -1,3 +1,4 @@
1
+ // Purpose: In-memory lifecycle pub/sub (`subagent:*` events) for extensions and the completion wake.
1
2
  import type { SubagentEvent, SubagentEventType, SubagentEventListener } from './types';
2
3
 
3
4
  // Event listeners by type
package/src/git.ts CHANGED
@@ -1,3 +1,4 @@
1
+ // Purpose: Branch-based git workflow for worktree runs: branch creation, diff capture, switch-back, cleanup.
1
2
  /**
2
3
  * brl-subagent — Git Integration (P3)
3
4
  *
@@ -32,6 +33,21 @@ export function getCurrentBranch(cwd: string): string {
32
33
  return output.trim();
33
34
  }
34
35
 
36
+ /**
37
+ * Resolve the repository root that contains the given directory.
38
+ * Returns undefined when the directory is not inside a repository, or when
39
+ * git is unavailable — callers fall back to the directory path itself.
40
+ */
41
+ export function getRepoRoot(cwd: string): string | undefined {
42
+ try {
43
+ const output = execFileSync("git", ["rev-parse", "--show-toplevel"], gitOpts(cwd));
44
+ const root = output.trim();
45
+ return root.length > 0 ? root : undefined;
46
+ } catch {
47
+ return undefined;
48
+ }
49
+ }
50
+
35
51
  /**
36
52
  * Check if there are uncommitted changes in the working tree.
37
53
  */
package/src/history.ts CHANGED
@@ -1,3 +1,4 @@
1
+ // Purpose: Run-record store: creation, finalization, retry lookup, and history pruning.
1
2
  /**
2
3
  * brl-subagent — Run History
3
4
  *
@@ -125,6 +126,15 @@ export function finalizeRunRecord(
125
126
  /**
126
127
  * Merge retry parameters: explicit params override original,
127
128
  * but task falls back to the original task if not provided.
129
+ *
130
+ * The execution-shape keys (background, gitMode, approvalMode, force) follow
131
+ * the same explicit-wins rule as everything else: an explicitly passed value
132
+ * is honoured, otherwise the recorded value is restored. Fan-out unit records
133
+ * (src/index.ts and unit-run.ts) snapshot their resolved unit params and do
134
+ * NOT set these, so a retried unit stays a single foreground run.
135
+ * `retryOnTimeout` is deliberately explicit-only: it arms a deadline for THIS
136
+ * call, so it is never inherited. `template`, `chain`, `tasks`, `graph` and
137
+ * `params` are not carried — a retried multi-step run degrades to a single task.
128
138
  */
129
139
  export function resolveRetryParams(
130
140
  params: {
@@ -147,6 +157,10 @@ export function resolveRetryParams(
147
157
  noBuiltinTools?: boolean;
148
158
  retryRunId?: string;
149
159
  retryOnTimeout?: boolean;
160
+ background?: boolean;
161
+ gitMode?: string;
162
+ approvalMode?: string;
163
+ force?: boolean;
150
164
  },
151
165
  run: SubagentRun,
152
166
  ): typeof params {
@@ -167,6 +181,10 @@ export function resolveRetryParams(
167
181
  excludeTools: params.excludeTools ?? orig?.excludeTools,
168
182
  noBuiltinTools: params.noBuiltinTools ?? orig?.noBuiltinTools,
169
183
  retryOnTimeout: params.retryOnTimeout,
184
+ background: params.background ?? orig?.background,
185
+ gitMode: params.gitMode ?? orig?.gitMode,
186
+ approvalMode: params.approvalMode ?? orig?.approvalMode,
187
+ force: params.force ?? orig?.force,
170
188
  };
171
189
  }
172
190