brl-subagent 2.3.4 → 2.3.5

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 ADDED
@@ -0,0 +1,88 @@
1
+ # AGENT.md — brl-subagent capability reference (v2.3.4+)
2
+
3
+ > The `delegate_task` prompt guidelines are a derived summary of this file; if they disagree, this file wins.
4
+
5
+ This file is the authoritative reference for how the brl-subagent extension behaves
6
+ and how you (the conductor) should judge a delegation. Read it when planning
7
+ delegation-heavy work.
8
+
9
+ ## Execution models
10
+
11
+ | Mode | What blocks | What wakes you | Batchable? |
12
+ | --- | --- | --- | --- |
13
+ | single, foreground | blocks until it returns | — (result comes back inline) | no |
14
+ | single, background | returns immediately with an agent id | yes — a `subagent-completion` message on terminal state | no |
15
+ | chain | blocks until the chain finishes or fails | — | yes (foreground) |
16
+ | parallel (`tasks`) | blocks until every task finishes | — | yes (foreground) |
17
+ | graph | blocks; runs in waves via `dependsOn` | — | yes (foreground) |
18
+
19
+ - Foreground single blocks: you wait for the result inline.
20
+ - Background single returns immediately with an agent id; a completion message wakes you (see below).
21
+ - Chain, parallel, and graph are batch — one call, many subtasks. They are foreground today; background batch is future work.
22
+ - Chain stops at the first failure; max 10 steps.
23
+ - Parallel runs every task regardless of the others — no short-circuit.
24
+ - Graph runs in waves, ordered by `dependsOn` edges.
25
+
26
+ ## The completion contract
27
+
28
+ - 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.
29
+ - The wake triggers a turn even when you are idle. Do not poll for it.
30
+ - 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.)
31
+ - 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.
32
+ - 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.
33
+ - Honest records: trust the message's `category`/abort source over your own guess. The provider/abort origin is authoritative on why the run stopped.
34
+ - 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.
35
+
36
+ ## Delegation judgment
37
+
38
+ - 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.
39
+ - Batch vs sequential: if the work is pre-declared, use chain/parallel/graph. If you're steering incrementally with the user, issue sequential calls.
40
+ - 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).
41
+ - When to delegate: when the task needs an isolated context, a deep investigation, parallel research, or a long-running analysis.
42
+ - Approval: `approvalMode: "always"` is rejected in background — there is no dialog. Use `auto` (default) or `writes`; `writes` auto-approves in background.
43
+
44
+ ## Canonical shapes
45
+
46
+ - Batch chain with `{previous}`: `chain: [{ task: "…" }, { task: "Use {previous} …" }]` — each step gets the prior step's output.
47
+ - Graph with `dependsOn`: subtasks declare edges; the scheduler runs them in waves.
48
+ - Background + act on wake: spawn a background run, then act when the completion message arrives. Never poll between spawn and wake.
49
+ - retryRunId: pass a failed run's id to re-run it with its recorded task and params; explicit parameters override the originals.
50
+
51
+ ## Mechanics pointer
52
+
53
+ Presets and templates are documented in the schema, not here. Judgment only:
54
+
55
+ - Prefer a preset when the delegation matches a standard shape — review/audit/test/docs; override per-call deliberately.
56
+ - Recurring task shapes → a saved template.
57
+
58
+ The schema's `delegate_task` parameters, the `templateSummary` and `presetRestrictionSummary` lines in the prompt guidelines, and `get_subagent_result`'s description are the mechanics reference.
59
+
60
+ ## Local development
61
+
62
+ This repo is the package source. Daily use runs the **published npm package**;
63
+ development runs the **local checkout**. Both are `pi install` entries and cannot
64
+ coexist — two loaded copies register the same tools — so remove one first.
65
+
66
+ ```bash
67
+ # Daily: the published artifact — the same thing users install
68
+ pi remove <local-repo-path>
69
+ pi install npm:brl-subagent
70
+
71
+ # Develop: load the checkout in place — no copying, no syncing
72
+ pi remove npm:brl-subagent
73
+ pi install <local-repo-path> # or a worktree: …/brl-subagent-wt-<n>
74
+ # then /reload pi after edits
75
+
76
+ # Back to the published artifact
77
+ pi remove <local-repo-path>
78
+ pi install npm:brl-subagent
79
+ ```
80
+
81
+ A local-path install loads the directory in place and can point at an **unmerged
82
+ worktree**, so it doubles as live verification for a PR. `pi update --extensions`
83
+ advances the npm install and leaves a local-path install alone.
84
+
85
+ (The former `sync-extension.sh` — rsync into `~/.pi/agent/extensions/brl-subagent`
86
+ — was retired once the package was published: the local-path install supersedes it
87
+ and does not fight `pi update --extensions` by mutating a package-manager-owned
88
+ directory.)
package/README.md CHANGED
@@ -14,7 +14,7 @@
14
14
  - **True background execution** — live monitor, real abort (`stop_subagent`), per-agent timeouts, and hard caps. 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 700+ 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 975+ test suite pinning every contract against the real pi SDK.
18
18
 
19
19
  ---
20
20
 
@@ -22,13 +22,28 @@
22
22
 
23
23
  ```bash
24
24
  # Global (all projects)
25
- cd ~/.pi/agent/extensions && git clone https://github.com/azmeenafandi/brl-subagent.git
25
+ pi install npm:brl-subagent
26
26
 
27
- # Project-local
28
- cd your-project/.pi/extensions && git clone https://github.com/azmeenafandi/brl-subagent.git
27
+ # Project-local (commit .pi/settings.json to share with your team)
28
+ pi install -l npm:brl-subagent
29
29
  ```
30
30
 
31
- Pi auto-discovers extensions in these directories. To update: `git pull` inside the cloned directory.
31
+ Pi installs the package and loads the extension automatically. Updates and removal:
32
+
33
+ ```bash
34
+ pi update --extensions # update installed packages
35
+ pi remove npm:brl-subagent # uninstall
36
+ ```
37
+
38
+ <details>
39
+ <summary>Alternative: install from git</summary>
40
+
41
+ ```bash
42
+ pi install git:github.com/azmeenafandi/brl-subagent@v2.3.4
43
+ ```
44
+
45
+ Git installs are **pinned** to the ref you specify. `pi update --extensions` reconciles to that ref but never advances it — upgrading means re-running `pi install` with a newer `@ref`.
46
+ </details>
32
47
 
33
48
  ---
34
49
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brl-subagent",
3
- "version": "2.3.4",
3
+ "version": "2.3.5",
4
4
  "description": "Subagent extension for pi with configurable model and thinking mode via selection UI",
5
5
  "keywords": [
6
6
  "pi-package"
@@ -16,6 +16,7 @@
16
16
  "templates/",
17
17
  "README.md",
18
18
  "LICENSE",
19
+ "AGENT.md",
19
20
  "!src/__tests__/"
20
21
  ],
21
22
  "pi": {
package/src/index.ts CHANGED
@@ -52,6 +52,7 @@ import {
52
52
  } from "./types";
53
53
  import { validateGraph, topologicalSort } from "./scheduler";
54
54
  import { resolveTemplate, loadAllTemplates, loadBuiltinTemplates, validateTemplatePresetRefs, extractParamNames } from "./templates";
55
+ import { pkgPath } from "./paths";
55
56
 
56
57
  import { sanitizeTask, validateCwd, validateOutputFile, stripAnsi, capOutput, getCurrentDepth, sanitizeErrorMessage, buildCrashResult } from "./sanitize";
57
58
  import {
@@ -200,7 +201,7 @@ export default function (pi: ExtensionAPI) {
200
201
  // Read current version from package.json
201
202
  const currentVersion = (() => {
202
203
  try {
203
- const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, "..", "package.json"), "utf-8"));
204
+ const pkg = JSON.parse(fs.readFileSync(pkgPath("package.json"), "utf-8"));
204
205
  return pkg.version || "0.0.0";
205
206
  } catch {
206
207
  return "0.0.0";
@@ -1954,7 +1955,7 @@ export default function (pi: ExtensionAPI) {
1954
1955
 
1955
1956
  // B1: Load built-in presets once at registration to expose their tool
1956
1957
  // restrictions to the conductor before any delegate_task call happens.
1957
- const registrationPresets = loadBuiltinPresets(path.join(__dirname, "..", "presets"));
1958
+ const registrationPresets = loadBuiltinPresets(pkgPath("presets"));
1958
1959
  const presetRestrictionSummary = buildInventorySummary(registrationPresets, (p) => {
1959
1960
  const restricted = p.excludeTools?.length || p.tools?.length;
1960
1961
  if (!restricted) return p.name;
@@ -1965,9 +1966,14 @@ export default function (pi: ExtensionAPI) {
1965
1966
  // inventory (name + purpose + ${param} slots) to the conductor before any
1966
1967
  // delegate_task call. Custom/user templates can't be enumerated statically,
1967
1968
  // so the summary covers built-ins only and points at /brl-subagent templates.
1968
- const registrationTemplates = loadBuiltinTemplates(path.join(__dirname, "..", "templates"));
1969
+ const registrationTemplates = loadBuiltinTemplates(pkgPath("templates"));
1969
1970
  const templateSummary = buildInventorySummary(registrationTemplates, formatTemplateSummaryItem);
1970
1971
 
1972
+ // Issue #158: resolve the shipped AGENT.md from the extension's own directory
1973
+ // rather than a hardcoded sync-copy destination, so npm-installed packages
1974
+ // (installed via `pi install npm:brl-subagent`) can find it.
1975
+ const agentMdPath = pkgPath("AGENT.md");
1976
+
1971
1977
  pi.registerTool({
1972
1978
  name: "delegate_task",
1973
1979
  label: "Delegate Task",
@@ -1983,7 +1989,7 @@ export default function (pi: ExtensionAPI) {
1983
1989
  "Delegate tasks to a subagent for isolated, parallel or background work",
1984
1990
  promptGuidelines: [
1985
1991
  "Use delegate_task when the user asks you to hand off work to a subagent, or when a task would benefit from an isolated context window (e.g., deep investigation, parallel research, long-running analysis).",
1986
- "The authoritative capability reference for delegation is the extension's AGENT.md — read it at ~/.pi/agent/extensions/brl-subagent/AGENT.md, or ./AGENT.md in the project root when present, when planning delegation-heavy work.",
1992
+ `The authoritative capability reference for delegation is the extension's AGENT.md — read it at ${agentMdPath}, or ./AGENT.md in the project root when present, when planning delegation-heavy work.`,
1987
1993
  "The subagent inherits your system prompt and runs with its own model (configurable via /brl-subagent). It reports what it did when done.",
1988
1994
  "You can customize per-call via inheritSystemPrompt and systemPrompt: set inheritSystemPrompt: false to save context, provide a systemPrompt for custom instructions, or use both to add instructions on top of inheritance.",
1989
1995
  "Set thinkingLevel per call to match task complexity. The level is capped at the user's configured maximum. Map tasks to levels using this heuristic: off = file listing, grep, simple read. minimal = file diff, syntax check, find-and-replace. low = refactoring, test generation, documentation. medium = default — code review, debugging, moderate analysis. high = security audit, architecture review, complex debugging. xhigh = multi-step causal reasoning, research, novel problem solving. Default to 'off' or 'minimal' for trivial tasks — do not waste the user's budget.",
@@ -3691,11 +3697,11 @@ export default function (pi: ExtensionAPI) {
3691
3697
  // Issue #147: capture the session context for the completion-push subscriber.
3692
3698
  sessionCtx = ctx;
3693
3699
  // Load built-in presets
3694
- const presetsDir = path.join(__dirname, "..", "presets");
3700
+ const presetsDir = pkgPath("presets");
3695
3701
  state.builtinPresets = loadBuiltinPresets(presetsDir, log);
3696
3702
  state.customPresets = loadCustomPresets(ctx.cwd, log);
3697
3703
  // Builtin templates + merged full stack (custom overrides builtin)
3698
- const templatesDir = path.join(__dirname, "..", "templates");
3704
+ const templatesDir = pkgPath("templates");
3699
3705
  state.builtinTemplates = loadBuiltinTemplates(templatesDir, log);
3700
3706
  state.config.templates = loadAllTemplates(ctx.cwd, log, templatesDir);
3701
3707
 
package/src/paths.ts ADDED
@@ -0,0 +1,25 @@
1
+ /**
2
+ * brl-subagent — Package-root path resolution
3
+ *
4
+ * The extension is loaded from `<pkg>/src/` (package.json `pi.extensions`
5
+ * points at `./src/index.ts`), so every module in this package lives directly
6
+ * in `src/` and the package root is exactly one level up. `pkgPath()` is the
7
+ * single helper for resolving bundled assets against that root — the same base
8
+ * used for `presets/`, `templates/`, `package.json`, and `AGENT.md`.
9
+ */
10
+
11
+ import * as path from "node:path";
12
+
13
+ /**
14
+ * Resolve `segments` against the package root.
15
+ *
16
+ * Because the extension loads from `<pkg>/src/`, the package root is one level
17
+ * up from this module's directory. A wrong depth here would resolve inside
18
+ * `src/` and fail to find the bundled asset.
19
+ *
20
+ * @param segments - Path segments below the package root (e.g. `"presets"`, `"AGENT.md"`)
21
+ * @returns The absolute path to the bundled asset
22
+ */
23
+ export function pkgPath(...segments: string[]): string {
24
+ return path.join(__dirname, "..", ...segments);
25
+ }
package/src/templates.ts CHANGED
@@ -27,6 +27,7 @@ import type { TaskTemplate, ThinkingLevel, SubagentPreset } from "./types";
27
27
  import { TEMPLATE_PARAM_RE, THINKING_LEVELS } from "./types";
28
28
  import type { Logger } from "./logging";
29
29
  import { parseFrontmatter, sanitizeFileName } from "./presets";
30
+ import { pkgPath } from "./paths";
30
31
 
31
32
  // ---------------------------------------------------------------------------
32
33
  // Param extraction
@@ -373,7 +374,7 @@ export function getAllTemplates(
373
374
  * the extension's presets/ dir.
374
375
  */
375
376
  export function loadAllTemplates(cwd: string, log?: Logger, templatesDir?: string): TaskTemplate[] {
376
- const builtinDir = templatesDir ?? path.join(__dirname, "..", "templates");
377
+ const builtinDir = templatesDir ?? pkgPath("templates");
377
378
  return getAllTemplates(loadBuiltinTemplates(builtinDir, log), loadCustomTemplates(cwd, log));
378
379
  }
379
380