@ferris1225/pi-subagents 2.0.3 → 2.2.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
@@ -32,15 +32,23 @@ The common quality loop now runs end to end without waking the main agent betwee
32
32
  steps:
33
33
 
34
34
  ```text
35
- reviewer (find blockers) → worker (fix) → reviewer (verify) → final PASS/FAIL
35
+ reviewer (find issues) → worker (fix every finding) → reviewer (verify) → final PASS/FAIL
36
36
  ```
37
37
 
38
+ Reviews use a single flat findings list — no severity triage. Every reported
39
+ finding is fixed before the change is accepted, and each re-review converges on
40
+ an open-finding set: the worker's explicit rejections are adjudicated once, only
41
+ defects a fix round introduced or exposed are added, and resolved items never
42
+ re-open. `maxFixRounds` stays the hard cap, so a chain always settles and wakes
43
+ the main agent with the full picture.
44
+
38
45
  Cleanup stays a separate lifecycle: explicit cleanup intent can dispatch the
39
46
  evidence-first `cleaner`; any edits still go through the independent `reviewer`
40
47
  gate.
41
48
 
42
- Each chain is delivered as one concise completion group, while full per-run
43
- reports remain available through `subagent_status`. Its parent stays `running`
49
+ Each chain is delivered as one concise completion group whose footer totals the
50
+ aggregate token usage and cost of every included run, while full per-run reports
51
+ remain available through `subagent_status`. Its parent stays `running`
44
52
  until the whole chain settles; completed internal rounds leave active status
45
53
  immediately, so no `done` row keeps accumulating elapsed time. Selected-to-main
46
54
  model handoffs keep the same retained context, and isolated parallel workers use
@@ -63,12 +71,16 @@ index.
63
71
  "go check" step. `subagent_wait` is a **non-blocking** in-turn lookup by default
64
72
  (pass `timeoutMs` to block); `subagent_status` inspects runs; `subagent_stop`
65
73
  cancels one and delivers its partial output.
66
- - **Active-only live widget** — each queued or running sub-agent gets one compact
67
- width-aware primary line with task, effective model/thinking, and elapsed time;
68
- current activity appears only when present on an indented second line:
74
+ - **Active-only live widget, as a tree** — each queued or running sub-agent gets one compact
75
+ width-aware primary line with task, effective model/thinking, and elapsed time; current
76
+ activity appears only when present on an indented second line. Auto-fix rounds nest under
77
+ the triggering reviewer row that owns the chain, so it is always visible who dispatched
78
+ what; no run ids appear here — the tree and the task label identify each row:
69
79
  ```text
70
- #7 cleaner · remove dead code in src/cache.ts · claude-sonnet-4-5/high · 42s
71
- grep cacheKey
80
+ reviewer · review diff of src/foo.ts · claude-sonnet-4-5/high · 42s
81
+ ● worker · fix round 1 · src/foo.ts · claude-sonnet-4-5/high · 10s
82
+ │ grep cacheKey
83
+ └ ○ reviewer · re-review round 1 · claude-sonnet-4-5/high · 3s
72
84
  ```
73
85
  Long tasks and activity paths truncate first (preserving a useful path tail when
74
86
  possible), groups have no blank rows, and settled/parked runs disappear immediately.
@@ -83,7 +95,10 @@ index.
83
95
  - **A quality gate that closes the loop** — when a reviewer returns `REVIEW_FAIL`,
84
96
  the extension dispatches a worker briefed with the concrete findings, then a
85
97
  re-review, up to `maxFixRounds` times — and only then wakes the main agent.
86
- Every round stays in the triggering reviewer's cwd, and chains that target the
98
+ Every reported finding gets fixed (no severity triage), and re-reviews converge
99
+ on an open-finding set instead of ping-ponging: worker rejections are adjudicated
100
+ once, only defects the fix round introduced are added, and resolved items never
101
+ re-open. Every round stays in the triggering reviewer's cwd, and chains that target the
87
102
  same repository are serialized so shared-checkout edits cannot race.
88
103
  - **Direct fallback with real thinking capabilities** — each agent has at most
89
104
  one selected model. An unavailable selection, rate limit, invalid key, quota,
@@ -98,9 +113,9 @@ index.
98
113
  `subagent_control` can steer active work, retarget it after a stable abort,
99
114
  park/resume it under the same run id, or fork a parked/settled checkpoint into
100
115
  a new independent run. Concurrent resume calls are serialized.
101
- - **Concise but honest completions** — a clean process that contained failed tool
102
- calls adds one short warning with its run id; individual errors stay out of the
103
- main context and remain available through `subagent_status`. Actual process,
116
+ - **Concise but honest completions** — group completions end with aggregate token
117
+ and cost totals across every included run; failed-tool diagnostics stay out of the
118
+ delivered message and remain one `subagent_status` call away. Actual process,
104
119
  model, and integration failures still surface as failures.
105
120
  - **Parallel fan-out with filesystem isolation** — independent tasks run up to a
106
121
  configurable limit (default 4). Parallel workers default to detached Git
@@ -123,8 +138,9 @@ an undifferentiated child-agent launcher:
123
138
  independent reviewer gate;
124
139
  - isolated, retained threads that can be steered, parked, resumed, retargeted, or
125
140
  forked under stable run ids;
126
- - the reviewer → worker auto-fix → reviewer loop;
127
- - compact failed-tool warnings with full diagnostics available by run id;
141
+ - the reviewer → worker auto-fix → reviewer loop, fixing every finding under a
142
+ convergence contract with a hard round cap;
143
+ - failed-tool diagnostics available by run id through `subagent_status`;
128
144
  - direct selected→main fallback plus capability-aware Auto thinking;
129
145
  - detached Git worktree isolation for parallel workers and opt-in write-capable
130
146
  cleaner runs.
@@ -226,7 +242,8 @@ because they may need to inspect the same images.
226
242
  ### Controlling and stopping
227
243
 
228
244
  Dispatch confirmations, tool result rows, and completion blocks all show the
229
- stable `#id`, so a thread remains directly controllable after its live UI is gone.
245
+ stable `#id`, so a thread remains directly controllable after its live UI is gone
246
+ (the widget itself identifies rows by tree position and task instead of ids).
230
247
 
231
248
  - `subagent_control` — `steer`, `retarget`, `park`, `resume`, or `fork` a logical
232
249
  thread by stable run id. Resume accepts an optional replacement objective;
@@ -317,7 +334,7 @@ direct-file settings.
317
334
  | `proactiveInjection` | Whether to add the delegation directive to the main system prompt. |
318
335
  | `agentScope` | `user`, `project`, or `both`; controls which user/project agent directories are discovered. |
319
336
  | `maxConcurrency` | Max sub-agent processes running at once (1–16, default 4), and the max tasks one parallel `subagent` call accepts. Extra work waits in the queue. |
320
- | `maxFixRounds` | Auto-fix rounds when a reviewer returns `REVIEW_FAIL` (default 2; `0` disables the loop). |
337
+ | `maxFixRounds` | Auto-fix rounds when a reviewer returns `REVIEW_FAIL` (default 2; `0` disables the loop). Hard cap: the chain always settles, delivers its condensed summary, and wakes the main agent. |
321
338
  | `idleTimeoutSec` | Idle watchdog: a sub-agent whose stdout goes silent for this long is terminated; a selected model then hands to current main. `0` disables it. Default 90. |
322
339
 
323
340
  ### Model routing and thinking
@@ -429,12 +446,13 @@ npm run check
429
446
  npm test
430
447
  ```
431
448
 
432
- The source is modular: `dispatch.ts` (dispatch, controls, isolation, auto-fix),
449
+ The source is modular: `dispatch.ts` (public dispatch contract + auto-fix),
450
+ `thread-lifecycle.ts` (queued generations, resume/fork, and isolation settlement),
433
451
  `rpc-run.ts` / `spawn.ts` (persistent child transport + selected→main handoff),
434
452
  `worktree.ts` / `session-fork.ts` (filesystem/session branching), `tools.ts`
435
453
  (wait/status/control/stop), `widget.ts` (active-only TUI status), `announcements.ts`
436
- (recovery and feature notices), and `runtime.ts` (session-scoped ownership). No runtime dependencies beyond pi peer
437
- dependencies.
454
+ (recovery and feature notices), and `runtime.ts` (session-scoped ownership). No runtime
455
+ dependencies beyond pi peer dependencies.
438
456
 
439
457
  ## License
440
458
 
@@ -42,25 +42,23 @@ Understand the context first, then verify: the fix addresses the root cause, cha
42
42
  - Concurrency: shared mutable state, locks held across await, races.
43
43
  - Encoding/Unicode: assuming `char*`/files/CLI text is UTF-8; wrong `A` vs `W` Win32 APIs; boundary conversions.
44
44
  - Resource leaks; violations of the project's stated conventions.
45
- - Classify severity honestly. Distinguish blockers from nits; do not pad with style preferences.
45
+ - Every finding you report gets fixed by the auto-fix loop — there are no severity tiers and no optional nits. Report genuine defects and risks only, each defensible with file:line evidence; leave out style preferences and anything you would merely "suggest". Order findings most important first.
46
46
 
47
47
  ## Collaboration
48
48
  - Independent of `worker` by design — your verdict is the gate before commit. Fix nothing yourself; report so the caller can dispatch a worker.
49
+ - Re-reviews converge on an open-finding set: rule on each previously reported finding once (adjudicating the worker's explicit rejections — uphold only with a concrete refutation), add only defects the fix round introduced or exposed, and never re-open an item you verified resolved. Rounds are hard-capped, so padding a re-review with restated findings just burns them.
49
50
 
50
51
  ## Output format
51
52
  ## Files Reviewed
52
53
  - `path/to/file.ts`
53
- ## Critical (must fix)
54
+ ## Findings
54
55
  - `file.ts:42` — concrete issue and why it breaks.
55
- ## Warnings (should fix)
56
- - `file.ts:10` — issue and suggested direction.
57
- ## Suggestions (consider)
58
- - Optional improvements.
56
+ (Write "None" when you found nothing; an empty findings list is a valid, honest result.)
59
57
  ## Verdict
60
- One of: APPROVE / APPROVE_WITH_NITS / REQUEST_CHANGES, plus a 2-3 sentence rationale.
61
- End with exactly one machine-readable line: `VERDICT: REVIEW_PASS` for APPROVE or APPROVE_WITH_NITS; `VERDICT: REVIEW_FAIL` for REQUEST_CHANGES.
58
+ One of: APPROVE / REQUEST_CHANGES, plus a 2-3 sentence rationale.
59
+ End with exactly one machine-readable line: `VERDICT: REVIEW_PASS` for APPROVE; `VERDICT: REVIEW_FAIL` for REQUEST_CHANGES.
62
60
 
63
- REQUEST_CHANGES (or review-blocking concern) on this extension automatically starts an auto-fix loop: a worker is briefed with your findings, then you re-review, up to the configured round limit. Choose REVIEW_PASS when nothing objectively blocks the change (style preferences, optional refactors, or items the caller can knowingly accept are not blockers) do not force a verdict that would trigger another loop just to hedge.
61
+ REQUEST_CHANGES whenever even one finding remains. On this extension REQUEST_CHANGES automatically starts an auto-fix loop: a worker is briefed with your findings, fixes every one of them, and you re-review, up to the configured round limit. APPROVE means the findings list is empty or, on a re-review, that every previously reported finding is resolved. Never quietly wave an issue through, and never invent findings to hedge: report each genuine issue once, accurately.
64
62
 
65
63
  ## Quality standards
66
64
  Specific file paths and line numbers. No vague feedback. A clean report means you looked hard, not that you found nothing to say.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ferris1225/pi-subagents",
3
- "version": "2.0.3",
3
+ "version": "2.2.0",
4
4
  "description": "Controllable background sub-agent threads for pi: specialized roles, capability-aware thinking, direct main-model fallback, auto-fix chains, and Git worktree isolation.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,70 +1,70 @@
1
- /** Session-start recovery and one-time feature announcements. */
2
-
3
- import { stat } from "node:fs/promises";
4
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
5
- import { loadConfig, saveConfig } from "./config.ts";
6
- import { announceRecoveryRecords } from "./recovery.ts";
7
- import type { SubagentRuntime } from "./runtime.ts";
8
- import { pruneResultArtifacts } from "./spawn.ts";
9
- import { installActiveRunsWidget } from "./widget.ts";
10
-
11
- const ANNOUNCEMENTS: Array<{
12
- key: string;
13
- condition: (config: Awaited<ReturnType<typeof loadConfig>>) => boolean;
14
- message: string;
15
- }> = [
16
- {
17
- key: "visionModel",
18
- condition: (config) => config.visionModel === undefined,
19
- message:
20
- "pi-subagents: new — a vision-capable model can now handle image tasks (screenshots, mockups, designs). Run /subagents-setup to configure it; until set, vision tasks use the main session's current model.",
21
- },
22
- {
23
- key: "cleanerAgent",
24
- condition: (config) => !config.enabledAgents.includes("cleaner"),
25
- message:
26
- "pi-subagents: new built-in cleaner agent is available for evidence-first code cleanup. Run /subagents-setup to enable it; your existing enabledAgents selection was left unchanged.",
27
- },
28
- ];
29
-
30
- async function announceNewFeatures(
31
- ctx: { ui: { notify: (message: string, kind: "info" | "warning" | "error") => void } },
32
- runtime: SubagentRuntime,
33
- ): Promise<void> {
34
- try {
35
- let configExists = true;
36
- try {
37
- await stat(runtime.configPath);
38
- } catch {
39
- configExists = false;
40
- }
41
- if (!configExists) return;
42
-
43
- const config = await loadConfig(runtime.configPath);
44
- const pending = ANNOUNCEMENTS.filter(
45
- (announcement) =>
46
- announcement.condition(config) && !config.announcedFeatures.includes(announcement.key),
47
- );
48
- if (pending.length === 0) return;
49
- await saveConfig(
50
- {
51
- ...config,
52
- announcedFeatures: [...config.announcedFeatures, ...pending.map((announcement) => announcement.key)],
53
- },
54
- runtime.configPath,
55
- );
56
- for (const announcement of pending) ctx.ui.notify(announcement.message, "info");
57
- } catch {
58
- /* announcement failures are non-fatal */
59
- }
60
- }
61
-
62
- export function registerAnnouncements(pi: ExtensionAPI, runtime: SubagentRuntime): void {
63
- pi.on("session_start", async (_event, ctx) => {
64
- pruneResultArtifacts();
65
- await announceRecoveryRecords(runtime.configPath, ctx);
66
- if (ctx.mode !== "tui") return;
67
- installActiveRunsWidget(ctx);
68
- await announceNewFeatures(ctx, runtime);
69
- });
70
- }
1
+ /** Session-start recovery and one-time feature announcements. */
2
+
3
+ import { stat } from "node:fs/promises";
4
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
5
+ import { loadConfig, saveConfig } from "./config.ts";
6
+ import { announceRecoveryRecords } from "./recovery.ts";
7
+ import type { SubagentRuntime } from "./runtime.ts";
8
+ import { pruneResultArtifacts } from "./spawn.ts";
9
+ import { installActiveRunsWidget } from "./widget.ts";
10
+
11
+ const ANNOUNCEMENTS: Array<{
12
+ key: string;
13
+ condition: (config: Awaited<ReturnType<typeof loadConfig>>) => boolean;
14
+ message: string;
15
+ }> = [
16
+ {
17
+ key: "visionModel",
18
+ condition: (config) => config.visionModel === undefined,
19
+ message:
20
+ "pi-subagents: new — a vision-capable model can now handle image tasks (screenshots, mockups, designs). Run /subagents-setup to configure it; until set, vision tasks use the main session's current model.",
21
+ },
22
+ {
23
+ key: "cleanerAgent",
24
+ condition: (config) => !config.enabledAgents.includes("cleaner"),
25
+ message:
26
+ "pi-subagents: new built-in cleaner agent is available for evidence-first code cleanup. Run /subagents-setup to enable it; your existing enabledAgents selection was left unchanged.",
27
+ },
28
+ ];
29
+
30
+ async function announceNewFeatures(
31
+ ctx: { ui: { notify: (message: string, kind: "info" | "warning" | "error") => void } },
32
+ runtime: SubagentRuntime,
33
+ ): Promise<void> {
34
+ try {
35
+ let configExists = true;
36
+ try {
37
+ await stat(runtime.configPath);
38
+ } catch {
39
+ configExists = false;
40
+ }
41
+ if (!configExists) return;
42
+
43
+ const config = await loadConfig(runtime.configPath);
44
+ const pending = ANNOUNCEMENTS.filter(
45
+ (announcement) =>
46
+ announcement.condition(config) && !config.announcedFeatures.includes(announcement.key),
47
+ );
48
+ if (pending.length === 0) return;
49
+ await saveConfig(
50
+ {
51
+ ...config,
52
+ announcedFeatures: [...config.announcedFeatures, ...pending.map((announcement) => announcement.key)],
53
+ },
54
+ runtime.configPath,
55
+ );
56
+ for (const announcement of pending) ctx.ui.notify(announcement.message, "info");
57
+ } catch {
58
+ /* announcement failures are non-fatal */
59
+ }
60
+ }
61
+
62
+ export function registerAnnouncements(pi: ExtensionAPI, runtime: SubagentRuntime): void {
63
+ pi.on("session_start", async (_event, ctx) => {
64
+ pruneResultArtifacts();
65
+ await announceRecoveryRecords(runtime.configPath, ctx);
66
+ if (ctx.mode !== "tui") return;
67
+ installActiveRunsWidget(ctx);
68
+ await announceNewFeatures(ctx, runtime);
69
+ });
70
+ }
package/src/completion.ts CHANGED
@@ -8,6 +8,8 @@
8
8
  */
9
9
 
10
10
  import { getResultOutput, isFailedResult, reviewVerdict, type SingleResult } from "./spawn.ts";
11
+ import { formatUsageCompact, sumUsage } from "./monitor.ts";
12
+ import type { UsageStats } from "./rpc-run.ts";
11
13
 
12
14
  export interface CompletionBatchTimings {
13
15
  debounceMs: number;
@@ -98,14 +100,20 @@ export interface CompletionMessageItem {
98
100
  agent: string;
99
101
  block: string;
100
102
  triggerTurn: boolean;
103
+ /** Final usage of the underlying run (or chain); aggregated into the group totals. */
104
+ usage?: UsageStats;
101
105
  }
102
106
 
103
- /** Keep the established single-result shape; add a summary only for real groups. */
107
+ /** Keep the established single-result shape; add a group header and an aggregate
108
+ * token/cost footer only for real groups. */
104
109
  export function formatCompletionMessage(items: readonly CompletionMessageItem[]): string {
105
110
  if (items.length === 0) return "";
106
111
  if (items.length === 1) return items[0].block;
107
112
  const agents = items.map((item) => item.agent).join(", ");
108
- return `### Subagents completed (${items.length}): ${agents}\n\n${items.map((item) => item.block).join("\n\n")}`;
113
+ const withUsage = items.filter((item) => item.usage !== undefined);
114
+ const totals = withUsage.length > 0 ? formatUsageCompact(sumUsage(withUsage.map((item) => item.usage!))) : "";
115
+ const footer = totals ? `\n\nTotals: ${items.length} runs · ${totals}` : "";
116
+ return `### Subagents completed (${items.length}): ${agents}\n\n${items.map((item) => item.block).join("\n\n")}${footer}`;
109
117
  }
110
118
 
111
119
  /** A grouped completion wakes the main agent when any member requires a turn. */