@henryqw/pi-subagent 27.7.0 → 27.9.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/README.md CHANGED
@@ -25,13 +25,14 @@ Configure the profiles that your Roles use in `/task-models`. Direct writer term
25
25
 
26
26
  Ask Main: “Have a scout trace sign-in without editing files.” Main calls `delegate_task` and stays available until progress and results arrive.
27
27
 
28
- Commands and UI are for you. Tools and the packaged skill are for Main, except `git_read`, which is only for Role-declared children.
28
+ Commands, prompts, and UI are for you. Tools and the packaged skill are for Main, except `git_read`, which is only for Role-declared children.
29
29
 
30
30
  | Surface | Type | Purpose |
31
31
  | --- | --- | --- |
32
32
  | `/subagent` | command | Browse direct and isolated work, including requests whose parent checkout was removed. Stop direct workers, inspect isolated work, send follow-ups, or delete owned isolated worktrees after confirmation. **Select multiple…** applies one action to many rows. Without interactive UI, only reports. |
33
33
  | `/subagent roles` | command | List Role sources, resources, resolved model and thinking level, route errors, and file errors. Read-only; requires no Herdr pane. |
34
34
  | `/subagent prune` | command | Review remaining worker and integration branches and worktrees. Select the items to remove; merged items are selected first. Some blocked items can also be selected, after a separate confirmation. Requires a running Herdr server; without interactive UI, only reports. |
35
+ | `/review [target] [focus]` | prompt | Tell Main to review a change with parallel `reviewer` tasks and give one ship verdict. See [Review a change](#review-a-change). |
35
36
  | `/subagent recover` | command | Classify interrupted isolated work in the canonical repository. Send Main request IDs, blockers, and next actions. Requires no UI or Herdr pane. |
36
37
  | `delegate_task` | tool | Start authorized shared-checkout work or a durable isolated checked graph. With `dryRun: true`, check the request and show the resolved plan without a launch. |
37
38
  | `subagent_status` | tool | Read saved isolated state, exact identities, failed review verdicts, and allowed continuation. Does not replay work. Supports structured output for `codemode`. |
@@ -90,6 +91,21 @@ The launch returns a task ID and first Herdr tab, not the answer. One follow-up
90
91
 
91
92
  To continue a finished worker, add `"continueFrom": "D7.1"` with the same `role`. This opens a new tab with the saved conversation and a new reference. Continue the newest reference from the original checkout and workspace. For eligibility and stop behavior, see [Continue or stop one direct worker](./docs/orchestration.md#continue-or-stop-one-direct-worker).
92
93
 
94
+ ### Review a change
95
+
96
+ Run `/review` to review uncommitted changes against `HEAD`. To review other work, give one target:
97
+
98
+ | Target | Reviewed change |
99
+ | --- | --- |
100
+ | `A..B` | From `A` to `B` |
101
+ | `feature-branch` | From the merge-base with `main` to the branch |
102
+ | `a1b2c3d` | The commit against its first parent |
103
+ | `#42` | The pull request head against its merge-base with the pull request base |
104
+
105
+ Text after the target adds a review focus, for example `/review #42 cache invalidation`. If the first word is not a target, Main reviews uncommitted changes and uses all text as the focus. Main changes the target to exact local commit IDs. Reviewers can read only local Git. So, for a pull request, Main fetches the head and base from the pull request repository to `refs/pi-review/`. Main groups changed files by location, starts one read-only `reviewer` task for each group, and merges the findings.
106
+
107
+ The verdict is **Ship** only if each reviewer task succeeded and answered exactly `PASS`. Otherwise, the verdict is **Do not ship**, with the findings in priority order. If the checkout changed during a review task, the verdict is also **Do not ship**, and you must run the review again. Main does not fix findings. If another `/review` prompt template loads first, Pi keeps that template and reports a name collision.
108
+
93
109
  ### Isolated workflow
94
110
 
95
111
  ![Isolated workers commit checked candidates; Main stages ready work while siblings run, advances dependents, validates the combined tip, and explicitly promotes](./docs/orchestration-architecture.svg)
@@ -139,6 +155,10 @@ The extension sends follow-ups when a candidate becomes ready, a worker needs at
139
155
 
140
156
  Open `/subagent` to browse direct work on this session branch and isolated requests in the current canonical Git checkout, including other sessions. The menu groups rows under **Direct**, **Isolated**, and **Orphaned** headers. Each isolated row shows the request ID, the status, and a short goal summary. **Refresh** or a header reloads saved status without recovery or mutation. Invalid state IDs stay preserved with a warning. Direct recovery remains available outside Git or without isolated inventory.
141
157
 
158
+ Before the menu opens, and after each **Refresh**, a **Current subagent work** report shows which subagents are working. The extension calculates it from local and saved evidence without a model call. Without interactive UI, `/subagent` sends this report before the request list.
159
+
160
+ A direct workflow is working only when this Pi process owns it and has a running worker. Direct work with a delivered result shows its outcome under **Completed / history** and is not in the report. Other recorded-only direct work shows as unverified. An isolated request is working only when its status is active and a live run holds the repository lease. Without a live lease, an active request shows as interrupted; run `/subagent recover`.
161
+
142
162
  Each row starts with a status symbol: `●` active, `✓` completed, `⚠` needs attention, `✗` failed. In the terminal UI, `/subagent` reports (Roles, details, reviews, and results) show as colored transcript entries. The model does not get these entries. In RPC mode, they show as notifications. Without interactive UI, they show as plain text messages.
143
163
 
144
164
  Requests whose parent checkout was removed show in the **Orphaned · parent checkout removed** group from any checkout of the same repository. Every isolated request, including an orphaned request, has **Close and delete worktrees (discard work)**. Select it to review owned resources and confirm deletion. **This permanently deletes committed, uncommitted, and ignored content in the listed checkouts.** Cancel makes no change.
@@ -196,6 +216,12 @@ Role Markdown files in `~/.pi/agent/config/pi-subagent/` require frontmatter and
196
216
 
197
217
  The package ships `implementer`, `reviewer`, and `scout`; same-name user Roles override built-ins. Roles define capabilities, not access or isolation. Unknown frontmatter keys invalidate that file and report its path and key. Other valid Roles load; duplicate names become unavailable. Delegation to an invalid file's claimed Role fails with that file's error. Inspect `/subagent roles`.
198
218
 
219
+ The built-in reviewer answers exactly `PASS` when it finds no actionable correctness risk. Otherwise, each finding starts with a priority and a confidence (`high`, `medium`, or `low`), for example `[P1] confidence: high`. Then it gives file and line evidence, the trigger, the impact, and the smallest fix. Each finding blocks approval.
220
+
221
+ - `P0`: breaks release or operation on ordinary input.
222
+ - `P1`: wrong behavior under a realistic condition.
223
+ - `P2`: wrong behavior in a narrow edge case with limited impact.
224
+
199
225
  The built-in reviewer and scout include `git_read`; existing user reviewers and scouts must add it to `tools`. No additional extension or Bash access is necessary. See [Reviewer Git inspection](./docs/orchestration.md#reviewer-git-inspection) for operations, parameters, limits, and Git requirements.
200
226
 
201
227
  Children load only declared resources and required adapters. Roles must list their model provider extension. `codemode` requires both `builtin:codemode` in `extensions` and `codemode` in `tools`. Missing Skills, tools, MCP servers, Roles, or routes fail before productive work. Main-only delegation/recovery tools and `ask_question` stay unavailable to children.
package/dist/runner.d.ts CHANGED
@@ -332,6 +332,8 @@ export declare class IsolatedRunner {
332
332
  invalidIds: string[];
333
333
  states: RunState[];
334
334
  }>;
335
+ /** True while a live productive run holds this root's lease; the lease does not name its request. */
336
+ hasProductiveRunLease(root: string): Promise<boolean>;
335
337
  /** Classify orphaned work under the same repository lease as productive execution.
336
338
  * Status remains read-only; Main alone chooses any subsequent action. */
337
339
  recoverRepository(root: string): Promise<{
package/dist/runner.js CHANGED
@@ -639,6 +639,10 @@ export class IsolatedRunner {
639
639
  })),
640
640
  })) };
641
641
  }
642
+ /** True while a live productive run holds this root's lease; the lease does not name its request. */
643
+ async hasProductiveRunLease(root) {
644
+ return await this.store.hasProductiveRunLease(root);
645
+ }
642
646
  /** Classify orphaned work under the same repository lease as productive execution.
643
647
  * Status remains read-only; Main alone chooses any subsequent action. */
644
648
  async recoverRepository(root) {
@@ -269,7 +269,7 @@ After readiness, use `subagent_stage revise` with the exact candidate, generatio
269
269
 
270
270
  ### Inspect and revise active work
271
271
 
272
- To select work by label, open `/subagent` without arguments. The menu shows direct records on this session branch and isolated requests in the current canonical Git checkout. This includes requests launched in other sessions. Select an isolated request. Then select **Inspect status and recovery**.
272
+ To select work by label, open `/subagent` without arguments. First, a **Current subagent work** report shows the work that local and saved evidence proves is running: locally owned direct workflows with a running worker, and active isolated requests while a live run holds the repository lease. The report lists active requests without a live lease as interrupted, and recorded-only direct work as unverified. A live lease does not identify its request, so several active requests in one checkout all show as working. The menu shows direct records on this session branch and isolated requests in the current canonical Git checkout. This includes requests launched in other sessions. Select an isolated request. Then select **Inspect status and recovery**.
273
273
 
274
274
  The menu also shows requests whose saved parent checkout no longer exists, in their own **Orphaned · parent checkout removed** group. You can open this view from any checkout of the same repository. The saved worker or integration path identifies the primary repository. Orphaned requests have the same **Close and delete worktrees (discard work)** action, but no inspection or follow-up actions. The extension reads their original state directories; it does not recreate the removed parent checkout. Unreadable files stay preserved and are reported.
275
275
 
@@ -16,4 +16,10 @@ Review the supplied candidate read-only. Use only supplied requirements and name
16
16
 
17
17
  Report only actionable correctness risks introduced by the change, not style preferences, speculative hypotheticals, or unrelated pre-existing issues. Run no tests or commands outside `git_read`. Never edit, write, commit, push, mutate Git or manage worktrees, or invoke external LLM APIs, SDKs, agent harnesses, or model CLIs.
18
18
 
19
- Output exactly `PASS` when there are no findings. Otherwise output findings only, ordered by severity, with file:line evidence, impact, and the smallest valid fix. Any finding blocks approval; never combine `PASS` with findings. Stop when the supplied evidence is covered.
19
+ Output exactly `PASS` when there are no findings. Otherwise output findings only, ordered by priority, then confidence. Start each finding with its priority and confidence, for example `[P1] confidence: high`, then give file:line evidence, the trigger, the impact, and the smallest valid fix:
20
+
21
+ - `P0`: breaks release or operation on ordinary input, such as data loss, a security bypass, or a crash.
22
+ - `P1`: wrong behavior under a realistic condition, such as a race, a missed error path, or a broken caller.
23
+ - `P2`: wrong behavior in a narrow edge case with limited impact.
24
+
25
+ Confidence is `high`, `medium`, or `low` that the defect is real; name the affected code path even when low. Any finding blocks approval; never combine `PASS` with findings. Stop when the supplied evidence is covered.
@@ -842,7 +842,8 @@ export function registerIsolatedExtension(pi: ExtensionAPI, options: RegisterIso
842
842
  catch (error) { console.error("Pi Subagent workspace widget update failed.", error); }
843
843
  }
844
844
  const orphans = await getComponents().runner.listOrphanRequests(root);
845
- return { root, requests: [...requests, ...orphans.requests], invalidIds: [...new Set([...invalidIds, ...orphans.invalidIds])] };
845
+ const leaseLive = await getComponents().runner.hasProductiveRunLease(root);
846
+ return { root, leaseLive, requests: [...requests, ...orphans.requests], invalidIds: [...new Set([...invalidIds, ...orphans.invalidIds])] };
846
847
  },
847
848
  async recover(cwd) {
848
849
  const root = await lookupRoot(cwd);
@@ -56,6 +56,8 @@ export interface IsolatedRequest {
56
56
  }
57
57
  export interface IsolatedInventory {
58
58
  root: string;
59
+ /** A live productive run holds this root's lease; the lease does not name its request. */
60
+ leaseLive: boolean;
59
61
  requests: readonly IsolatedRequest[];
60
62
  invalidIds: readonly string[];
61
63
  }
@@ -291,6 +293,41 @@ function directReport(task: DirectTask): Report {
291
293
  ] };
292
294
  }
293
295
 
296
+ const WORKING_REPORT_CODE_POINTS = 4_000;
297
+ /** Current work from local and saved evidence only. Recorded-only direct work and isolated work without a live lease never count as working. */
298
+ export function workingReport(direct: readonly DirectTask[], inventory: IsolatedInventory | undefined): Report {
299
+ const working: Row[] = [];
300
+ const attention: Row[] = [];
301
+ const unverified: Row[] = [];
302
+ for (const task of direct) {
303
+ const text = `${directLabel(task)} · ${task.status}`;
304
+ // Read workflows list their running workers; write workflows stay observed locally until their workers are proved stopped.
305
+ if (task.status === "observed locally" && task.stoppable?.length !== 0) working.push({ text: task.stoppable ? `${directLabel(task)} · ${task.stoppable.length} running` : text });
306
+ else if (/retained|attention/.test(task.status)) attention.push({ text });
307
+ else if (completed(task.status)) continue;
308
+ else unverified.push({ text: task.status === "observed locally" ? `${text}; no running worker` : text });
309
+ }
310
+ for (const request of inventory?.requests ?? []) {
311
+ const active = !request.orphaned && ["pending", "running"].includes(request.status);
312
+ if (active && inventory!.leaseLive) working.push({ text: isolatedLabel(request) });
313
+ else if (active) attention.push({ text: `${isolatedLabel(request)} · interrupted; run /subagent recover` });
314
+ else if (!request.orphaned && (request.status === "needs_attention" || /retained/.test(request.status))) attention.push({ text: isolatedLabel(request) });
315
+ }
316
+ const sections: Section[] = [
317
+ working.length ? { heading: `Working (${working.length})`, tone: "info", rows: working } : { rows: [{ text: inventory ? "No subagents are working." : "No direct subagents are working. Isolated status is unavailable." }] },
318
+ { heading: `Needs attention (${attention.length})`, tone: "warning", rows: attention },
319
+ { heading: `Unverified (${unverified.length})`, note: "not proof of running or completion", tone: "warning", rows: unverified },
320
+ ];
321
+ const head: Report = { tone: attention.length ? "warning" : "info", title: "Current subagent work", sections: [] };
322
+ const items = sections.flatMap((section) => section.rows.map((row, index) => ({ section, index, row })));
323
+ const fitted = fitItems(reportMarkdown(head), items, ({ section, index, row }) => index ? sectionMarkdown({ rows: [row] }) : `\n${sectionMarkdown({ ...section, rows: [row] })}`,
324
+ WORKING_REPORT_CODE_POINTS, "open /subagent with interactive UI to see them.");
325
+ return { ...head, sections: [...sections.flatMap((section) => {
326
+ const rows = fitted.shown.filter((item) => item.section === section).map(({ row }) => row);
327
+ return rows.length ? [{ ...section, rows }] : [];
328
+ }), ...moreSection(fitted.more)] };
329
+ }
330
+
294
331
  type Choice<T> = { label: string; value: T };
295
332
  async function choose<T>(ctx: ExtensionContext, title: string, items: readonly Choice<T>[]): Promise<T | undefined> {
296
333
  // Numeric suffixes only tell duplicate labels apart. Never parse display text into an identity.
@@ -462,6 +499,7 @@ export function registerSubagentCommand(pi: ExtensionAPI, adapter: SubagentComma
462
499
  }
463
500
  if (!ctx.hasUI) {
464
501
  const inventory = await adapter.isolated(ctx.cwd, current);
502
+ deliver(ctx, workingReport(adapter.direct(ctx), inventory));
465
503
  const head: Report = { tone: "info", title: "Isolated requests (report only)", sections: [] };
466
504
  const row = (request: IsolatedRequest): Row => ({ text: `${glyph(request.status)} ${request.orphaned ? "orphaned · " : ""}${isolatedLabel(request)}` });
467
505
  const listed = fitItems(`${reportMarkdown(head)}\n`, inventory.requests, (request) => sectionMarkdown({ rows: [row(request)] }), ROLES_REPORT_CODE_POINTS,
@@ -473,6 +511,8 @@ export function registerSubagentCommand(pi: ExtensionAPI, adapter: SubagentComma
473
511
  return;
474
512
  }
475
513
  let history = false;
514
+ // The working report shows on open and on each Refresh, not after other menu actions.
515
+ let report = true;
476
516
  for (;;) {
477
517
  if (!valid()) return;
478
518
  // Direct recovery is independent of Git and isolated config/inventory failures.
@@ -482,6 +522,8 @@ export function registerSubagentCommand(pi: ExtensionAPI, adapter: SubagentComma
482
522
  try { inventory = await adapter.isolated(ctx.cwd, current); }
483
523
  catch (error) { unavailable = errorText(error); }
484
524
  if (!valid()) return;
525
+ if (report) deliver(ctx, workingReport(direct, inventory));
526
+ report = false;
485
527
  if (unavailable) ctx.ui.notify(`Isolated inventory unavailable (${unavailable}). Direct branch recovery remains available; check the canonical Git checkout/configuration.`, "warning");
486
528
  if (inventory?.invalidIds.length) for (const id of inventory.invalidIds) ctx.ui.notify(`Unreadable isolated state ID: ${JSON.stringify(id)}. Preserve this file; inspect the state store before making changes.`, "warning");
487
529
  type Selection = { kind: "direct"; task: DirectTask } | { kind: "isolated"; request: IsolatedRequest; root: string } | { kind: "refresh" } | { kind: "history" } | { kind: "many" } | { kind: "close" };
@@ -509,7 +551,7 @@ export function registerSubagentCommand(pi: ExtensionAPI, adapter: SubagentComma
509
551
  if (!unavailable && !direct.length && !inventory?.requests.length && !inventory?.invalidIds.length) ctx.ui.notify("No direct tasks recorded on this session branch or isolated requests in this checkout. Ask Main to start delegation.", "info");
510
552
  const selected = await choose(ctx, history ? "Subagent · completed / history" : "Subagent · current work", selections);
511
553
  if (!valid() || !selected || selected.kind === "close") return;
512
- if (selected.kind === "refresh") continue;
554
+ if (selected.kind === "refresh") { report = true; continue; }
513
555
  if (selected.kind === "history") { history = !history; continue; }
514
556
  if (selected.kind === "many") {
515
557
  const picked = await chooseMany(ctx, "Subagent · select work", rows.map(({ label, value }) => ({ label: label.trim(), value })), [], valid);
@@ -300,7 +300,10 @@ export default function subagentExtension(pi: ExtensionAPI): void {
300
300
  const local = directTasks.get(record.taskId);
301
301
  let task = grouped.get(record.taskId);
302
302
  if (!task) grouped.set(record.taskId, task = { id: record.taskId, name: record.taskId,
303
- status: local?.recovery ? "admission retained" : local ? "observed locally" : "recorded only; not proof of running or completion",
303
+ status: local?.recovery ? "admission retained" : local ? "observed locally"
304
+ // A recovery result did not prove worker termination, so it stays visible for recovery.
305
+ : results.get(record.taskId)?.recovery ? "needs attention: worker termination unproved"
306
+ : results.get(record.taskId)?.outcome ?? "recorded only; not proof of running or completion",
304
307
  ...(results.get(record.taskId)?.cost === undefined ? {} : { cost: results.get(record.taskId)!.cost }),
305
308
  ...(local?.recovery ? { recovery: local.recovery } : {}), canClose: Boolean(local?.close),
306
309
  ...(local?.stopWorker ? { stoppable: local.stopWorker.running() } : {}), workers: [], tabs: [] });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@henryqw/pi-subagent",
3
- "version": "27.7.0",
3
+ "version": "27.9.0",
4
4
  "description": "Delegate bounded direct work or durable checked isolated task graphs to Pi roles.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -19,6 +19,7 @@
19
19
  "extensions",
20
20
  "docs",
21
21
  "examples",
22
+ "prompts",
22
23
  "example.png",
23
24
  "README.md",
24
25
  "CONTEXT.md",
@@ -63,6 +64,9 @@
63
64
  "skills": [
64
65
  "./skills"
65
66
  ],
67
+ "prompts": [
68
+ "./prompts"
69
+ ],
66
70
  "image": "https://raw.githubusercontent.com/HenryQW/pi-harness/main/extensions/pi-subagent/example.png"
67
71
  },
68
72
  "dependencies": {
@@ -0,0 +1,20 @@
1
+ ---
2
+ description: Review a change with parallel reviewer Subagents and give one ship verdict.
3
+ argument-hint: "[A..B | branch | commit | #PR] [focus]"
4
+ ---
5
+
6
+ Review this target with parallel `reviewer` Subagents: $@
7
+
8
+ Do not edit files, fix findings, commit, or push.
9
+
10
+ 1. Resolve the first argument to exact local commit IDs with Git. Treat the remaining text as extra review focus. If the first argument is not `#N` and does not resolve as a ref or range, use the empty target and treat all text as focus.
11
+ - Empty: uncommitted staged, unstaged, and untracked changes against `HEAD`.
12
+ - `A..B`: base `A`, tip `B`.
13
+ - Branch: base is `git merge-base main <branch>`, tip is the branch.
14
+ - Commit: base is its first parent, tip is the commit.
15
+ - `#N`: run `gh pr view N --json url,baseRefName`. The base repository URL is `url` without `/pull/N`. Run `git fetch <repository URL> +refs/heads/<baseRefName>:refs/pi-review/pr-N-base +refs/pull/N/head:refs/pi-review/pr-N`. Base is the merge-base of the two refs; tip is `refs/pi-review/pr-N`. Reviewers read only local Git, so fetch before delegation.
16
+ If the target is ambiguous or has no changes, stop and report.
17
+ 2. List the changed files, and mark untracked files. Group them by locality: same directory or module, related behavior, and tests with their implementation. Use one group for a small change and at most 5 groups.
18
+ 3. Start one `delegate_task` call with `mode: "direct"` and one `tasks` entry per group: `role: "reviewer"`, a short unique `name` (at most 29 characters), `access: "read"`, and a `task` that gives the base and tip commit IDs, its exact file list, and the criteria. For uncommitted changes, give `HEAD` <ID> instead, and tell the reviewer to combine `git_read` diff in `staged` and `working` modes and to read each marked untracked file whole with `read`. Criteria: report actionable correctness risks that the change introduces in these files, with the reviewer finding format; read other files only as context; output exactly `PASS` when there are no findings. Add the extra focus, if any.
19
+ 4. When the result arrives, merge the findings: remove duplicates that share a root cause and keep the highest priority, then sort by priority (`P0`, `P1`, `P2`) and confidence. Keep file:line evidence and fixes.
20
+ 5. Give one verdict. **Ship** only if every reviewer task succeeded and answered exactly `PASS`. Otherwise **Do not ship**, with the merged findings and any failed or incomplete reviewer task. If any result reports `checkout changed during task`, the verdict is **Do not ship**; rerun the review.