@gotgenes/pi-subagents 17.0.1 → 17.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/CHANGELOG.md CHANGED
@@ -5,6 +5,26 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [17.2.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v17.1.0...pi-subagents-v17.2.0) (2026-06-20)
9
+
10
+
11
+ ### Features
12
+
13
+ * shrink agent widget to background runs only ([#444](https://github.com/gotgenes/pi-packages/issues/444)) ([76463b4](https://github.com/gotgenes/pi-packages/commit/76463b47227961226dbce5efb70a71d596fe092e))
14
+
15
+
16
+ ### Documentation
17
+
18
+ * note background-only widget in README and roadmap ([#444](https://github.com/gotgenes/pi-packages/issues/444)) ([437332f](https://github.com/gotgenes/pi-packages/commit/437332fa4a1fbb2f0a0358495454847bc597ae13))
19
+
20
+ ## [17.1.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v17.0.1...pi-subagents-v17.1.0) (2026-06-20)
21
+
22
+
23
+ ### Features
24
+
25
+ * add SubagentsSettingsHandler for focused settings command ([#447](https://github.com/gotgenes/pi-packages/issues/447)) ([cae804d](https://github.com/gotgenes/pi-packages/commit/cae804d1be7ac437ad7344035b5d99cd1424e0c0))
26
+ * register /subagents-settings command ([#447](https://github.com/gotgenes/pi-packages/issues/447)) ([7735a38](https://github.com/gotgenes/pi-packages/commit/7735a38db2658fd9faca6388ac4bc712fa8e5a86))
27
+
8
28
  ## [17.0.1](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v17.0.0...pi-subagents-v17.0.1) (2026-06-20)
9
29
 
10
30
 
package/README.md CHANGED
@@ -61,7 +61,7 @@ Background agents return an ID immediately and notify you on completion.
61
61
 
62
62
  ## UI
63
63
 
64
- The extension renders a persistent widget above the editor showing all active agents:
64
+ The extension renders a persistent widget above the editor showing active background agents (foreground runs are rendered inline by the `subagent` tool's progress stream):
65
65
 
66
66
  ```text
67
67
  ● Agents
@@ -950,7 +950,7 @@ Outcome: ADR-0004 updated with all four entry-criteria answers; Step 4 unblocked
950
950
 
951
951
  `Release: independent`
952
952
 
953
- ### Step 2 — Extract settings to a focused `/subagents-settings` command ([#447])
953
+ ### Step 2 — Extract settings to a focused `/subagents-settings` command ([#447])
954
954
 
955
955
  Smell: Category E (naming/organization) — settings are buried inside the monolithic `/agents` command per ADR-0004 Decision C. This step is purely additive: it stands up the new surface without touching `agent-menu.ts`.
956
956
  Target files:
@@ -966,7 +966,7 @@ Outcome: new `subagents-settings.ts` (~80 LOC) and focused command registered; `
966
966
 
967
967
  `Release: independent`
968
968
 
969
- ### Step 3 — Shrink widget to background agents only ([#444])
969
+ ### Step 3 — Shrink widget to background agents only ([#444])
970
970
 
971
971
  Smell: Category C (coupling) — the widget shows all agents including foreground ones, duplicating the `subagent` tool's inline `onUpdate` stream for foreground runs.
972
972
  Target files:
@@ -1061,13 +1061,13 @@ Outcome: test clone groups ≤ 10 (from 16); `subagent-manager.test.ts` uses sha
1061
1061
 
1062
1062
  ```mermaid
1063
1063
  flowchart LR
1064
- S1[✅ Step 1 - Spike (#446)]
1065
- S2[Step 2 - Settings command (#447)]
1066
- S3[Step 3 - Background widget (#444)]
1067
- S4[Step 4 - Native session nav (#445)]
1068
- S5[Step 5 - Dissolve /agents + viewer (#442)]
1069
- S6[Step 6 - Remove definition mgmt (#441)]
1070
- S7[Step 7 - Test clones (#443)]
1064
+ S1["✅ Step 1 - Spike (#446)"]
1065
+ S2["✅ Step 2 - Settings command (#447)"]
1066
+ S3["✅ Step 3 - Background widget (#444)"]
1067
+ S4["Step 4 - Native session nav (#445)"]
1068
+ S5["Step 5 - Dissolve /agents + viewer (#442)"]
1069
+ S6["Step 6 - Remove definition mgmt (#441)"]
1070
+ S7["Step 7 - Test clones (#443)"]
1071
1071
 
1072
1072
  S1 --> S4
1073
1073
  S2 --> S5
@@ -0,0 +1,156 @@
1
+ ---
2
+ issue: 444
3
+ issue_title: "pi-subagents: shrink the agent widget to background runs only"
4
+ ---
5
+
6
+ # Shrink the agent widget to background runs only
7
+
8
+ ## Release Recommendation
9
+
10
+ **Release:** ship independently
11
+
12
+ Phase 19 Step 3 ([architecture roadmap][arch]) is tagged `Release: independent` — it is not a member of any release batch.
13
+ The change is self-contained to `agent-widget.ts` and its tests, touches no public surface, and ADR-0004 explicitly notes it is "independent of the spike."
14
+
15
+ ## Problem Statement
16
+
17
+ The above-editor agent widget currently renders every agent — foreground and background alike.
18
+ For foreground runs the `subagent` tool's inline `onUpdate` stream already renders live progress, so the widget duplicates that display (ADR-0004 Decision A).
19
+ The widget should survive only as the **background-agent status surface**: the one place with no inline tool-call display, which must represent N parallel background agents at once.
20
+
21
+ There is also a latent inconsistency to fix.
22
+ `AgentWidget` calls `manager.listAgents()` at two sites — `update()` (which feeds `seedFinishedAgents`, `assembleWidgetState`, and `clearWidget`) and `renderWidget()` (the tree map).
23
+ Filtering at only one site would leave the other rendering foreground agents.
24
+
25
+ ## Goals
26
+
27
+ - The widget shows only background agents (`record.invocation?.runInBackground === true`); foreground agents never appear.
28
+ - Both `listAgents()` call sites are funneled through a single private accessor that applies the background predicate once at the source.
29
+ - The foreground/widget duplication called out by ADR-0004 Decision A is eliminated.
30
+ - This is a UI behavior refinement, not a public-API or config change — it is **non-breaking** (no exported surface, default, or config shape changes on upgrade).
31
+
32
+ ## Non-Goals
33
+
34
+ - Relabeling the widget heading from `Agents` to `Background agents` (see Open Questions) — not requested; keep scope tight.
35
+ - Any change to `widget-renderer.ts` rendering logic — it has no foreground-specific path; the fix lives entirely at the data source in `agent-widget.ts`.
36
+ - Other `listAgents()` consumers (`agent-menu.ts`) — those are removed wholesale by Phase 19 Step 5 ([#442]), out of scope here.
37
+ - Native session navigation ([#445], Step 4) and the `/subagents-settings` extraction ([#447], Step 2).
38
+
39
+ ## Background
40
+
41
+ Relevant modules:
42
+
43
+ - `src/ui/agent-widget.ts` — `AgentWidget implements SubagentManagerObserver`; self-drives an 80 ms render loop from lifecycle events (Step 4 self-drive, [#423]).
44
+ `update()` reads `manager.listAgents()` into a local `allAgents`, then passes it to `seedFinishedAgents()`, `assembleWidgetState()`, and (on the idle path) `clearWidget()`.
45
+ `renderWidget(tui, theme)` independently calls `manager.listAgents().map(r => this.toWidgetAgent(r))` to build the tree.
46
+ - `src/ui/widget-renderer.ts` — pure rendering; `renderWidgetLines()` categorizes agents into running/queued/finished and emits tree lines.
47
+ It has no knowledge of foreground vs. background — it renders whatever list it receives.
48
+ - `Subagent.invocation` (`src/lifecycle/subagent.ts`) — a readonly `AgentInvocation | undefined` set once at construction.
49
+ `AgentInvocation.runInBackground` (`src/types.ts`) is the reliable signal: set by `spawn-config.ts` → `AgentInvocation.runInBackground` → stored on `Subagent.invocation`.
50
+ - `AgentSummary` (in `agent-widget.ts`) — the minimal `{ id, status, completedAt }` shape consumed by `assembleWidgetState`/`clearWidget`.
51
+ `Subagent` is structurally assignable to it, so the accessor can return `Subagent[]` without an interface change.
52
+
53
+ Constraint from AGENTS.md / package skill: pi-subagents is a minimal core; this is a consumer-side UI behavior change with no policy or core impact.
54
+
55
+ ## Design Overview
56
+
57
+ Introduce one private accessor on `AgentWidget` and route both existing call sites through it:
58
+
59
+ ```typescript
60
+ /** Background agents only — the widget's sole audience (ADR-0004 Decision A). */
61
+ private listBackgroundAgents(): Subagent[] {
62
+ return this.manager
63
+ .listAgents()
64
+ .filter(record => record.invocation?.runInBackground === true);
65
+ }
66
+ ```
67
+
68
+ Call-site changes:
69
+
70
+ - `update()` — replace `const allAgents = this.manager.listAgents();` with `const backgroundAgents = this.listBackgroundAgents();` and rename the downstream references (`seedFinishedAgents`, `assembleWidgetState`, `clearWidget`).
71
+ - `renderWidget()` — replace `this.manager.listAgents().map(...)` with `this.listBackgroundAgents().map(...)`.
72
+
73
+ The predicate `record.invocation?.runInBackground === true` is applied exactly once, at the funnel.
74
+ `undefined` invocation (or `runInBackground` absent/false) is treated as foreground and excluded.
75
+
76
+ Edge cases verified:
77
+
78
+ - **`clearWidget` stale-purge:** it deletes `finishedTurnAge` entries for IDs "no longer in the list."
79
+ Because `seedFinishedAgents` only ever seeds from the background-filtered list, no foreground agent is ever tracked, so purging against the background-only list cannot drop a still-relevant entry — no behavior regression.
80
+ - **Queued agents:** the concurrency limiter queues only background agents (foreground bypasses the queue), so they carry `runInBackground === true` and remain visible; the queued-count display stays accurate.
81
+ - **All-foreground case:** `update()` sees an empty background list → `assembleWidgetState` reports no active/finished → `clearWidget` → widget never registers. `renderWidget()` is consequently never invoked.
82
+ - **Mixed case (the latent bug):** one background running + one foreground running → `update()` registers the widget (1 background active); `renderWidget()` must also filter, or it renders both.
83
+ Routing both sites through the accessor closes this.
84
+
85
+ ## Module-Level Changes
86
+
87
+ - `src/ui/agent-widget.ts` — add private `listBackgroundAgents()`; route `update()` and `renderWidget()` through it; rename the `update()` local `allAgents` → `backgroundAgents`.
88
+ - `src/ui/widget-renderer.ts` — **no change.**
89
+ Verified there is no foreground-specific rendering path; filtering at the data source is sufficient.
90
+ Listed only to record the verification.
91
+ - `test/ui/agent-widget.test.ts` — migrate existing widget fixtures to set `invocation: { runInBackground: true }` (otherwise the new filter excludes them); add background-only filtering tests.
92
+ - `packages/pi-subagents/README.md` — line ~64, change "showing all active agents" → "showing active background agents" (stale prose once foreground is excluded).
93
+ - `packages/pi-subagents/docs/architecture/architecture.md` — mark Step 3 ✅ with a `Landed`/`Outcome` note; the `src/ui/agent-widget.ts` tree caption at line ~342 stays accurate (generic "live status widget").
94
+
95
+ Grep sweep performed for stale references to "all agents" / "showing all active agents" across `src/`, `test/`, `.pi/skills/package-pi-subagents/SKILL.md`, and `docs/`:
96
+
97
+ - `README.md:64` — stale, updated above.
98
+ - `.pi/skills/package-pi-subagents/SKILL.md:57` ("widget ─polls─→ Subagent records (listAgents)") — still accurate (it does poll `listAgents`, now filtered); no edit.
99
+ - `docs/comparison-with-upstream.md:29`, `architecture.md:342` — generic "live above-editor widget"; remain accurate.
100
+
101
+ ## Test Impact Analysis
102
+
103
+ 1. **New tests enabled by the change:**
104
+ - `update()` excludes foreground agents — an all-foreground agent list leaves the widget unregistered (`lastContent()` undefined).
105
+ - `renderWidget()` excludes foreground agents — a mixed background+foreground list renders only the background agent's description in the tree (the foreground description is absent).
106
+ This pins the previously-latent two-site inconsistency.
107
+ 2. **Tests that become redundant:** none are removed.
108
+ The existing widget-behavior fixtures (`makeWidget` in the two `describe` blocks, plus the projection test's `createTestSubagent`) are migrated to set `invocation: { runInBackground: true }` so they continue to exercise the same lifecycle/seeding/self-drive behavior under the new filter.
109
+ 3. **Tests that must stay as-is:** the `assembleWidgetState` pure-function suite — it calls the pure function directly with `AgentSummary[]`, not through the accessor, so it is unaffected by the filter and continues to pin the counting logic.
110
+
111
+ ## Invariants at risk
112
+
113
+ This step touches `agent-widget.ts`, which prior phases already refactored.
114
+ The fixture migration must keep these invariants pinned (the tests stay, only their `invocation` field is added):
115
+
116
+ - **Self-drive from lifecycle ([#423]):** `onSubagentStarted`/`onSubagentCreated` start the 80 ms timer and render; `onSubagentCompleted`/`onSubagentCompacted` render.
117
+ Pinned by the "self-drives from lifecycle notifications" describe block — fixtures migrated to background invocation so the manager stub's agents survive the filter.
118
+ - **`seedFinishedAgents` idempotency / linger aging ([#374]):** finished agents seed once, age out after 1 turn (errors after 2), and `update()` never advances the age without a turn.
119
+ Pinned by the "update self-seeds finished agents" describe block — fixtures migrated to background invocation.
120
+
121
+ A later step must not regress these with a green suite; the migration preserves the assertions verbatim apart from the `invocation` field.
122
+
123
+ ## TDD Order
124
+
125
+ 1. **`test:` migrate widget fixtures to background invocation (preparatory / tidy-first).**
126
+ Surface: `test/ui/agent-widget.test.ts`.
127
+ Add `invocation: { runInBackground: true }` to the `makeWidget` agent stubs (both `describe` blocks) and to the projection test's `createTestSubagent` call.
128
+ No production change — the filter does not exist yet, so the field is inert and the suite stays green.
129
+ This makes the next step a clean addition rather than a mass fixture rewrite.
130
+ Commit: `test: set background invocation on agent-widget fixtures (#444)`.
131
+ 2. **`feat:` filter the widget to background agents only.**
132
+ Red: add (a) an all-foreground list leaves the widget unregistered, and (b) a mixed background+foreground list renders only the background agent in `renderWidget()`.
133
+ Green: add the private `listBackgroundAgents()` accessor; route both `update()` and `renderWidget()` through it; rename the `update()` local to `backgroundAgents`.
134
+ Commit: `feat: shrink agent widget to background runs only (#444)`.
135
+ 3. **`docs:` update widget docs for background-only behavior.**
136
+ Update `README.md` ("all active agents" → "active background agents") and mark Phase 19 Step 3 ✅ with a `Landed`/`Outcome` note in `docs/architecture/architecture.md`.
137
+ Commit: `docs: note background-only widget in README and roadmap (#444)`.
138
+
139
+ ## Risks and Mitigations
140
+
141
+ - **Mass test breakage when the filter lands** — every `update()`-driven fixture would be filtered out and assertions would fail.
142
+ Mitigation: TDD step 1 migrates all fixtures first (inert until the filter exists), so step 2 only adds new tests.
143
+ - **A foreground agent the operator expected to see disappears** — intended per ADR-0004 Decision A; the inline `onUpdate` stream is authoritative for foreground runs.
144
+ - **`clearWidget` stale-purge dropping a relevant entry** — analyzed in Design Overview; foreground agents are never seeded, so purging against the background-only list is correct.
145
+
146
+ ## Open Questions
147
+
148
+ - Should the widget heading read `Background agents` instead of `Agents`?
149
+ Deferred — not in the issue scope; revisit if the bare label reads as ambiguous in practice.
150
+
151
+ [arch]: ../architecture/architecture.md
152
+ [#442]: https://github.com/gotgenes/pi-packages/issues/442
153
+ [#445]: https://github.com/gotgenes/pi-packages/issues/445
154
+ [#447]: https://github.com/gotgenes/pi-packages/issues/447
155
+ [#423]: https://github.com/gotgenes/pi-packages/issues/423
156
+ [#374]: https://github.com/gotgenes/pi-packages/issues/374
@@ -0,0 +1,220 @@
1
+ ---
2
+ issue: 447
3
+ issue_title: "pi-subagents: extract subagent settings to a focused /subagents-settings command"
4
+ ---
5
+
6
+ # Extract subagent settings to a focused `/subagents-settings` command
7
+
8
+ ## Release Recommendation
9
+
10
+ **Release:** ship independently
11
+
12
+ Phase 19 Step 2 ([#447]) carries `Release: independent` in the architecture roadmap, and the only batch defined there is "dissolve-agents" (Steps 5–6, [#442]/[#441]).
13
+ This step is purely additive — it stands up the new command without touching `agent-menu.ts` — so it ships on its own with no batch coupling.
14
+
15
+ ## Problem Statement
16
+
17
+ The `/agents` command bundles four unrelated jobs (running-agent visibility, agent-type browsing, the creation wizard, and operational settings).
18
+ ADR-0004 Decision C splits them.
19
+ Settings has standalone value but does not belong buried inside an agent-management menu — a focused, top-level command is discoverable on its own without navigating a multi-purpose menu.
20
+
21
+ This step is deliberately **additive**: it stands up the new command without touching `agent-menu.ts`.
22
+ The old in-menu Settings option keeps working until Phase 19 Step 5 ([#442]) deletes `agent-menu.ts` wholesale.
23
+ Keeping the work additive avoids surgery on the doomed module and lets this step run in parallel with the rest of the Phase 19 replacement track.
24
+
25
+ ## Goals
26
+
27
+ - Add `src/ui/subagents-settings.ts` — a `SubagentsSettingsHandler` lifted verbatim from `AgentsMenuHandler.showSettings`, carrying its own narrow manager interface (the three `apply*` methods and three readonly accessors only) and a narrow UI interface (only `select`, `input`, `notify`).
28
+ - Register the `/subagents-settings` command in `src/index.ts`, passing the existing `settings` (`SettingsManager`) directly.
29
+ - Add `test/ui/subagents-settings.test.ts` covering the extracted handler's behavior.
30
+
31
+ Non-breaking: this is pure addition.
32
+ No existing export, command, or behavior changes.
33
+
34
+ ## Non-Goals
35
+
36
+ - Do **not** remove `showSettings` or `AgentMenuSettings` from `agent-menu.ts` — that file is deleted whole in Phase 19 Step 5 ([#442]).
37
+ - Do **not** modify the `/agents` command, its handler, or any wiring in `index.ts` beyond adding the new registration block.
38
+ - Do **not** add a settings re-show loop or any new settings behavior — the lift preserves the single-selection-then-return semantics of `showSettings` (see Design Overview).
39
+ - Defer the background widget ([#444]), native session navigation ([#445]), and the `/agents` dissolution ([#442]/[#441]) to their own Phase 19 steps.
40
+
41
+ ## Background
42
+
43
+ Relevant existing modules:
44
+
45
+ - `src/ui/agent-menu.ts` — `AgentsMenuHandler.showSettings(ui)` is the source of the lift.
46
+ It depends solely on `this.settings` (typed `AgentMenuSettings`) and the `ui` parameter (`MenuUI`).
47
+ It calls `ui.select` once, then `ui.input` + one `settings.apply*` + `ui.notify` for the chosen field.
48
+ It does not loop: it shows the settings list once, applies one change (or none), and returns.
49
+ The re-show in the live menu comes from `showAgentsMenu` re-invoking *itself* after `showSettings` returns — not from `showSettings` looping.
50
+ - `AgentMenuSettings` (in `agent-menu.ts`) — the narrow settings shape `showSettings` reads: `readonly maxConcurrent`, `readonly defaultMaxTurns: number | undefined`, `readonly graceTurns`, plus `applyMaxConcurrent`/`applyDefaultMaxTurns`/`applyGraceTurns`, each returning `{ message: string; level: "info" | "warning" }`.
51
+ - `MenuUI` (in `agent-menu.ts`) — the wide menu UI interface; `showSettings` uses only `select`, `input`, and `notify` from it.
52
+ - `src/settings.ts` — `SettingsManager` is the concrete settings object constructed in `index.ts`.
53
+ It already exposes all six members of `AgentMenuSettings` (the three getters and three `apply*` methods), so it structurally satisfies the new narrow manager interface and can be passed directly.
54
+ - `src/index.ts` — constructs `settings = new SettingsManager(...)`, then `new AgentsMenuHandler(manager, registry, settings, ...)` and `registerCommand("agents", ...)`.
55
+ Sibling commands register flat, hyphenated names (`agents`, `colgrep-reindex`, `permission-system`) — no `:` namespace.
56
+ - `docs/decisions/0004-reconsider-ui-direction.md` — the addendum (2026-06-20) confirms the command name **`/subagents-settings`** (Criterion 4) and rejects `/subagents:settings` and `/agents-settings`.
57
+
58
+ AGENTS.md / package constraints:
59
+
60
+ - pi-subagents is a minimal core; the surviving UI is an in-core reactive consumer.
61
+ This extraction adds a command surface only — no policy, no new core dependency.
62
+ - Modules marked `← removing` (the `/agents` subtree) must not gain features — this plan adds nothing to `agent-menu.ts`.
63
+
64
+ ## Design Overview
65
+
66
+ `SubagentsSettingsHandler` is a faithful lift of `showSettings` into a standalone, independently-registered command.
67
+ It owns the settings UI interaction and depends on two narrow interfaces it declares itself (ISP — neither carries a field the handler does not use).
68
+
69
+ ### Narrow interfaces
70
+
71
+ ```typescript
72
+ /** Narrow settings interface required by the subagents-settings command. */
73
+ export interface SubagentsSettingsManager {
74
+ readonly maxConcurrent: number;
75
+ readonly defaultMaxTurns: number | undefined;
76
+ readonly graceTurns: number;
77
+ applyMaxConcurrent(n: number): { message: string; level: "info" | "warning" };
78
+ applyDefaultMaxTurns(n: number): { message: string; level: "info" | "warning" };
79
+ applyGraceTurns(n: number): { message: string; level: "info" | "warning" };
80
+ }
81
+
82
+ /** Narrow UI interface — only the ctx.ui methods the settings handler calls. */
83
+ export interface SubagentsSettingsUI {
84
+ select(title: string, options: string[]): Promise<string | undefined>;
85
+ input(title: string, defaultValue?: string): Promise<string | undefined>;
86
+ notify(message: string, level: "info" | "warning" | "error"): void;
87
+ }
88
+ ```
89
+
90
+ `SubagentsSettingsManager` is shape-identical to `AgentMenuSettings` but owned by the new module (zero import from `agent-menu.ts`, which is doomed).
91
+ `SubagentsSettingsUI` is narrower than `MenuUI` — it drops `confirm`, `editor`, and `custom`, which `showSettings` never calls.
92
+
93
+ ### Handler
94
+
95
+ ```typescript
96
+ export class SubagentsSettingsHandler {
97
+ constructor(private readonly settings: SubagentsSettingsManager) {}
98
+
99
+ async handle({ ui }: { ui: SubagentsSettingsUI }): Promise<void> {
100
+ // verbatim lift of showSettings: one select → input → apply* → notify
101
+ }
102
+ }
103
+ ```
104
+
105
+ The body is copied character-for-character from `showSettings` (the same three `if` branches, the same `parseInt` / validation / toast wiring), with `this.settings` resolving against the new narrow interface.
106
+
107
+ ### Registration call site (index.ts)
108
+
109
+ ```typescript
110
+ const subagentsSettings = new SubagentsSettingsHandler(settings);
111
+ pi.registerCommand("subagents-settings", {
112
+ description: "Configure subagent settings (concurrency, turn limits)",
113
+ handler: async (_args, ctx) => {
114
+ await subagentsSettings.handle({ ui: ctx.ui });
115
+ },
116
+ });
117
+ ```
118
+
119
+ `settings` (the `SettingsManager` instance) is passed directly — it structurally satisfies `SubagentsSettingsManager`.
120
+ The command handler needs only `ctx.ui` (no `modelRegistry`, no `parentSnapshot`), so its params are narrower than the `/agents` handler's.
121
+
122
+ ### Design-review findings
123
+
124
+ | Smell | Location | Evidence | Result |
125
+ | --------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- |
126
+ | Wide interface | `SubagentsSettingsManager` (6 members) | `handle` reads all 3 accessors and calls all 3 apply methods | None — 100% usage |
127
+ | Wide interface | `SubagentsSettingsUI` (3 methods) | `handle` calls `select`, `input`, `notify` | None — 100% usage |
128
+ | LoD violation | `subagents-settings.ts` | `this.settings.applyMaxConcurrent(n)` is a direct call; `ui.notify(toast.message, toast.level)` reads a returned value, not a reach-through | None |
129
+ | Output argument | `subagents-settings.ts` | handler reads accessors + calls apply methods; never writes back into `settings` or `ui` | None |
130
+
131
+ The extraction introduces a genuine new collaborator (a focused command handler owning the settings interaction) with its own narrow contracts — it is not procedure-splitting.
132
+
133
+ ### Edge cases (preserved verbatim from `showSettings`)
134
+
135
+ - `select` returns `undefined` (operator cancels) → return early, no change.
136
+ - `input` returns falsy (empty / cancel) → no apply call.
137
+ - `maxConcurrent` / `graceTurns`: reject `n < 1` with `"Must be a positive integer."` warning.
138
+ - `defaultMaxTurns`: accept `n >= 0` (0 = unlimited); reject `n < 0` with `"Must be 0 (unlimited) or a positive integer."` warning.
139
+ - Single selection then return — no settings re-show loop (matches current `showSettings`).
140
+
141
+ ## Module-Level Changes
142
+
143
+ - **New** `src/ui/subagents-settings.ts` — `SubagentsSettingsHandler` class, `SubagentsSettingsManager` interface, `SubagentsSettingsUI` interface.
144
+ ~80 LOC.
145
+ - **Changed** `src/index.ts` — add the `SubagentsSettingsHandler` import, construct it, and register the `subagents-settings` command.
146
+ No other lines change; the `/agents` block stays intact.
147
+ - **New** `test/ui/subagents-settings.test.ts` — unit tests for the handler.
148
+ - **No change** to `src/ui/agent-menu.ts` (and its `AgentMenuSettings` / `showSettings` / `MenuUI` stay as-is — deleted later in [#442]).
149
+
150
+ Doc / skill grep results (no stale references to update in this step):
151
+
152
+ - `architecture.md` Step 2 already names `src/ui/subagents-settings.ts`, `SubagentsSettingsHandler`, `SubagentsSettingsManager`, and the `/subagents-settings` command with `Outcome: new subagents-settings.ts (~80 LOC) and focused command registered; agent-menu.ts untouched` — the file appears as a *planned* addition, so no doc edit is needed now; its status line flips to ✅ at ship time only if the operator wants it folded in.
153
+ - The package skill (`.pi/skills/package-pi-subagents/SKILL.md`) lists the UI domain by count (`ui/` = 10 modules); adding one file makes it 11, but the table is a coarse summary that is not maintained per-file — leave it for a later Phase 19 doc-sync unless the operator asks otherwise.
154
+ - `settings.ts` comments reference `/agents → Settings` as the writer of project settings; those are still accurate (the in-menu path keeps working), so no edit.
155
+ They become stale only when [#442] deletes `agent-menu.ts`; do not pre-edit them here.
156
+
157
+ ## Test Impact Analysis
158
+
159
+ 1. **New tests enabled by the extraction.**
160
+ `test/ui/subagents-settings.test.ts` can unit-test the settings flow in isolation against the standalone handler — no `AgentsMenuHandler` construction (registry, fileOps, two agent dirs) required.
161
+ This is strictly more focused than the current `agent-menu.test.ts` "agent menu — settings" describe block, which has to drive the full top menu (`"Settings"` from the main menu) before reaching the settings list.
162
+ 2. **Existing tests that become redundant.**
163
+ None are removed in this step.
164
+ The "agent menu — settings" tests in `agent-menu.test.ts` still genuinely exercise the live in-menu path, which keeps working until [#442].
165
+ They are superseded only when `agent-menu.ts` is deleted in Step 5 — removing them now would drop coverage of a still-shipping surface.
166
+ 3. **Existing tests that must stay as-is.**
167
+ All of `agent-menu.test.ts` stays unchanged — this step does not touch `agent-menu.ts`.
168
+
169
+ ## Invariants at risk
170
+
171
+ This step adds a new module and one registration; it does not touch the Phase 18 spine that prior steps refactored.
172
+ The ADR-0004 / Phase 18 invariants (runtime holds zero UI state [#422]; widget is a reactive consumer with no inbound core calls [#423]; the `subagent` tool depends only on manager/runtime/settings/registry [#424]; declared event channels equal emitted channels [#425]) are untouched and stay pinned by their existing suites.
173
+ The new handler adds no inbound call into the core and emits no events — it reads/applies through the existing `SettingsManager` surface only, so it preserves these invariants by construction.
174
+
175
+ ## TDD Order
176
+
177
+ 1. **Red→Green: extract `SubagentsSettingsHandler` with its narrow interfaces.**
178
+ Test surface: `test/ui/subagents-settings.test.ts`.
179
+ Write tests against a `makeSettings()`-style manager stub (lift the stub shape from `agent-menu.test.ts`) and a narrow UI stub (lift `makeMenuUI`, or reuse it — it already provides `select`/`input`/`notify`):
180
+ - constructable;
181
+ - cancel at the settings list (`select` → `undefined`) → no apply call;
182
+ - `maxConcurrent`: valid input delegates to `applyMaxConcurrent(n)` and notifies the returned toast; `n < 1` notifies the positive-integer warning and does not apply;
183
+ - `defaultMaxTurns`: `0` delegates `applyDefaultMaxTurns(0)`; a positive value delegates; `n < 0` notifies the warning;
184
+ - `graceTurns`: valid input delegates to `applyGraceTurns(n)`; `n < 1` notifies the warning;
185
+ - empty/cancelled `input` → no apply call.
186
+ Implementation: create `src/ui/subagents-settings.ts` with the class and both interfaces; body lifted verbatim from `showSettings`.
187
+ Commit: `feat: add SubagentsSettingsHandler for focused settings command (#447)`.
188
+ 2. **Green: register the `/subagents-settings` command in `index.ts`.**
189
+ Test surface: none new (command registration in `index.ts` is exercised by the package's existing index-level wiring tests if present; otherwise verify by `pnpm run check` and the suite).
190
+ Implementation: import `SubagentsSettingsHandler`, construct it with `settings`, and `registerCommand("subagents-settings", ...)` passing `{ ui: ctx.ui }`.
191
+ Run `pnpm run check` immediately (the registration is the only call site of the new export — it must compile alongside the new module).
192
+ Commit: `feat: register /subagents-settings command (#447)`.
193
+
194
+ Both steps may be folded into one commit if preferred — the export and its single call site are small — but keeping the handler (with its tests) separate from the wiring keeps each commit self-contained and reviewable.
195
+
196
+ ## Risks and Mitigations
197
+
198
+ - **Risk: the lift diverges from `showSettings`, causing the standalone command to behave differently from the in-menu option while both ship.**
199
+ Mitigation: copy the body verbatim and assert the same validation/toast behavior in the new test file; the two surfaces share the same `SettingsManager`, so applied values are identical.
200
+ - **Risk: passing `SettingsManager` directly fails structural typing against the new narrow interface.**
201
+ Mitigation: `SettingsManager` already declares all six members with matching signatures (verified in `src/settings.ts`); `pnpm run check` in Step 2 confirms it.
202
+ - **Risk: command-name drift from the ADR.**
203
+ Mitigation: the name `subagents-settings` is fixed by the ADR-0004 addendum (Criterion 4) and the roadmap Step 2; use it exactly.
204
+ - **Risk: an unused-import or dead-export flag if the wiring lands in a separate commit from the handler.**
205
+ Mitigation: Step 2 adds the sole call site immediately; run `pnpm fallow dead-code` before pushing.
206
+
207
+ ## Open Questions
208
+
209
+ - Whether to also flip the `architecture.md` Step 2 status line to ✅ in this issue or defer to a Phase 19 doc-sync — decide at ship time (the architecture entry currently lists the file as planned, which is harmless until then).
210
+ - Whether a settings re-show loop (apply one setting → re-show the list) is desirable for the standalone command — deferred; the lift preserves single-selection-then-return to match current behavior, and a loop is a separate UX decision.
211
+
212
+ [#441]: https://github.com/gotgenes/pi-packages/issues/441
213
+ [#442]: https://github.com/gotgenes/pi-packages/issues/442
214
+ [#444]: https://github.com/gotgenes/pi-packages/issues/444
215
+ [#445]: https://github.com/gotgenes/pi-packages/issues/445
216
+ [#447]: https://github.com/gotgenes/pi-packages/issues/447
217
+ [#422]: https://github.com/gotgenes/pi-packages/issues/422
218
+ [#423]: https://github.com/gotgenes/pi-packages/issues/423
219
+ [#424]: https://github.com/gotgenes/pi-packages/issues/424
220
+ [#425]: https://github.com/gotgenes/pi-packages/issues/425
@@ -0,0 +1,43 @@
1
+ ---
2
+ issue: 444
3
+ issue_title: "pi-subagents: shrink the agent widget to background runs only"
4
+ ---
5
+
6
+ # Retro: #444 — pi-subagents: shrink the agent widget to background runs only
7
+
8
+ ## Stage: Planning (2026-06-20T18:47:05Z)
9
+
10
+ ### Session summary
11
+
12
+ Produced a numbered implementation plan for Phase 19 Step 3: shrink `AgentWidget` to background agents only by funneling both `manager.listAgents()` call sites (`update()` and `renderWidget()`) through a single private `listBackgroundAgents()` accessor that applies `record.invocation?.runInBackground === true` once at the source.
13
+ Confirmed the change is single-package (`pi-subagents`), non-breaking, and `Release: independent` per the architecture roadmap.
14
+
15
+ ### Observations
16
+
17
+ - Issue author is the operator (`gotgenes`) and the proposed change is unambiguous, so the `ask-user` gate was skipped.
18
+ The design is a legitimate improvement (removes the two-site duplication and fixes the latent inconsistency), not procedure-splitting.
19
+ - `widget-renderer.ts` needs **no** code change — it has no foreground-specific path; filtering at the data source in `agent-widget.ts` is sufficient.
20
+ Listed in Module-Level Changes only to record the verification.
21
+ - Key risk is mass test breakage: the existing `update()`-driven fixtures (two `makeWidget` helpers + the projection test's `createTestSubagent`) would all be filtered out once the predicate lands.
22
+ Mitigated with a tidy-first TDD step 1 that adds `invocation: { runInBackground: true }` to fixtures while the filter does not yet exist (inert), so step 2 only adds new tests.
23
+ - `assembleWidgetState` pure-function tests are unaffected — they call the function directly with `AgentSummary[]`, bypassing the accessor.
24
+ - Verified `clearWidget`'s stale-purge has no regression: foreground agents are never seeded into `finishedTurnAge`, so purging against the background-only list is correct.
25
+ - Doc grep surfaced one stale prose line (`README.md:64` "showing all active agents"); SKILL.md and `comparison-with-upstream.md` references remain accurate.
26
+ - Deferred (Open Question): relabeling the widget heading `Agents` → `Background agents` — out of scope for this issue.
27
+
28
+ ## Stage: Implementation — TDD (2026-06-20T19:47:34Z)
29
+
30
+ ### Session summary
31
+
32
+ Executed all three planned TDD cycles plus one reviewer-driven cleanup: (1) `test:` migrated the `agent-widget.test.ts` fixtures to a background invocation, (2) `feat:` added the private `listBackgroundAgents()` accessor and routed both `update()` and `renderWidget()` through it, (3) `docs:` updated `README.md` and marked roadmap Step 3 ✅.
33
+ Test count went from 24 to 26 in `agent-widget.test.ts` (two new background-only filtering tests); full pi-subagents suite is 1064 passing.
34
+
35
+ ### Observations
36
+
37
+ - The tidy-first fixture migration worked exactly as planned — adding `invocation: { runInBackground: true }` was inert (suite stayed green) until the filter landed, so step 2 was a pure addition with no fixture churn.
38
+ - New-test fixtures default the shared `makeWidget` helper to background invocation via `{ invocation: { runInBackground: true }, ...a }`, letting per-agent `invocation` override for the mixed/foreground cases in the new `describe` block.
39
+ - `widget-renderer.ts` needed no change, as the plan predicted — the filter at the single `listBackgroundAgents()` funnel is sufficient.
40
+ - Pre-completion reviewer: PASS.
41
+ One non-blocking naming WARN — `clearWidget`'s `allAgents` parameter and JSDoc were stale after the refactor (it now receives only background agents); fixed in a follow-up `refactor:` commit (`backgroundAgents`).
42
+ Landed as a separate commit rather than amending the feat commit because HEAD was already the `docs:` commit and the fix must not land in a `docs:` commit.
43
+ - All gates green: `pnpm run check`, root `pnpm run lint`, full vitest (1064), `pnpm fallow dead-code`.
@@ -0,0 +1,91 @@
1
+ ---
2
+ issue: 447
3
+ issue_title: "pi-subagents: extract subagent settings to a focused /subagents-settings command"
4
+ ---
5
+
6
+ # Retro: #447 — pi-subagents: extract subagent settings to a focused /subagents-settings command
7
+
8
+ ## Stage: Planning (2026-06-20T00:00:00Z)
9
+
10
+ ### Session summary
11
+
12
+ Produced a numbered TDD plan for Phase 19 Step 2: a purely additive extraction of `AgentsMenuHandler.showSettings` into a standalone `SubagentsSettingsHandler` registered as the `/subagents-settings` command.
13
+ Confirmed the command name against the closed spike (#446) and its ADR-0004 addendum (Criterion 4), and verified `SettingsManager` already structurally satisfies the new narrow manager interface so it can be passed directly.
14
+ The plan ships independently (roadmap `Release: independent`).
15
+
16
+ ### Observations
17
+
18
+ - The extraction is a faithful verbatim lift — `showSettings` already had zero coupling beyond `this.settings` and `ui`, so the design-review checklist came back clean (100% field usage on both new narrow interfaces, no LoD/output-arg smells).
19
+ Classified as a genuine collaborator extraction, not procedure-splitting.
20
+ - Declared two narrow interfaces owned by the new module: `SubagentsSettingsManager` (shape-identical to the doomed `AgentMenuSettings` but with no import from `agent-menu.ts`) and `SubagentsSettingsUI` (drops `confirm`/`editor`/`custom` from `MenuUI` — ISP).
21
+ - Strictly additive: `agent-menu.ts` is untouched, and its settings tests stay as-is because the in-menu path keeps shipping until Step 5 (#442) deletes the file.
22
+ Removing them now would drop coverage of a live surface.
23
+ - Preserved the single-selection-then-return semantics of `showSettings` verbatim (no re-show loop) — flagged a settings re-show loop as a deferred UX open question.
24
+ - Two small TDD steps (handler+tests, then `index.ts` registration); noted they may fold into one commit since the export and its sole call site are tiny, with `pnpm run check` required right after the wiring step.
25
+ - No third-party `ask_user` gate needed — issue filed by the operator (`gotgenes`), direction fixed by ADR-0004, design unambiguous.
26
+
27
+ ## Stage: Implementation — TDD (2026-06-20T14:30:00Z)
28
+
29
+ ### Session summary
30
+
31
+ Implemented Phase 19 Step 2 in two TDD cycles plus a doc-sync commit: added `SubagentsSettingsHandler` (with narrow `SubagentsSettingsManager` and `SubagentsSettingsUI` interfaces) lifted verbatim from `AgentsMenuHandler.showSettings`, then registered the `/subagents-settings` command in `index.ts`.
32
+ Test count went 1051 → 1062 (+11, one new file `test/ui/subagents-settings.test.ts`).
33
+ Full suite, `tsc`, root lint, and `fallow dead-code` all green.
34
+
35
+ ### Observations
36
+
37
+ - The lift was clean: `SettingsManager` already exposed all six members of the new narrow manager interface, so the `index.ts` wiring passed `settings` directly with zero adapter — `pnpm run check` confirmed structural satisfaction.
38
+ - `makeMenuUI` from `ui-stubs.ts` was reused as the UI stub without modification — its wider shape (`confirm`/`editor`/`custom`) structurally satisfies the narrower `SubagentsSettingsUI`, so no new helper was needed.
39
+ - Kept the two cycles as separate commits rather than folding them; the export and its sole call site were small enough that either would have been valid.
40
+ - Deviation from plan: flipped `✅` on the architecture Step 2 heading and its Mermaid node now (the plan had flagged this as a ship-time open question).
41
+ Applied per the `/tdd-plan` template's roadmap-completion rule; verified the diagram still renders via `mmdc`.
42
+ The phase status row was left unchanged (only 1 of 7 steps done).
43
+ - `agent-menu.ts` was not touched (verified by the reviewer via `git log`), preserving the additive-only constraint.
44
+ - Pre-completion reviewer: WARN.
45
+ Reviewer warnings: one non-blocking finding — `.pi/skills/package-pi-subagents/SKILL.md` records `ui/` as 10 modules (now 11); the plan intentionally deferred this coarse-summary update to a later Phase 19 doc-sync.
46
+ No FAILs; all deterministic checks PASS, verbatim-lift fidelity and ISP of both narrow interfaces confirmed.
47
+
48
+ ## Stage: Final Retrospective (2026-06-20T18:36:58Z)
49
+
50
+ ### Session summary
51
+
52
+ Shipped Phase 19 Step 2 (#447) end to end across planning, TDD, and ship stages, releasing `pi-subagents` `17.1.0`.
53
+ The one off-script event was a user-reported Mermaid syntax error in `architecture.md`, which I localized by extracting all six diagram blocks and running `mmdc` on each, then fixed by quoting the offending flowchart node labels.
54
+ Execution was otherwise clean: two green TDD cycles (+11 tests), a WARN pre-completion review (one intentionally-deferred doc finding), and a correct `UNSTABLE`/`GITHUB_TOKEN` release-merge fallback.
55
+
56
+ ### Observations
57
+
58
+ #### What went well
59
+
60
+ - Localizing the Mermaid error with the diagram name wrong: the user called it the "State dependency diagram" but the broken block was the "Step dependency diagram" flowchart.
61
+ Rather than guess from the ambiguous name (I had glanced at the `stateDiagram-v2` lifecycle and the `classDiagram` first), I scripted extraction of all six `mermaid` fences and ran `mmdc` on each — block 6 failed with `got 'PS'`, pinpointing the exact line.
62
+ Brute-force validation beat name-matching.
63
+ - The release-merge fallback worked exactly as the ship prompt describes: `release_pr_merge` refused on `merge_state: UNSTABLE`, the status-check rollup was empty (the `GITHUB_TOKEN` no-checks case), and `gh pr merge 450 --rebase` + `git pull --ff-only` landed `17.1.0` linearly.
64
+
65
+ #### What caused friction (agent side)
66
+
67
+ - `missing-context` (prior session) — the broken diagram was authored in `74e2374f docs: mark Phase 19 Step 1 spike complete (#446)`: a flowchart node label `S1[✅ Step 1 - Spike (#446)]` with unquoted parentheses, which Mermaid parses as a nested round-node shape (`Expecting ... got 'PS'`).
68
+ It was committed without an `mmdc` pass and slipped through `pnpm run lint` (rumdl validates markdown, not Mermaid semantics) and CI.
69
+ Impact: surfaced two sessions later as a user-caught defect; one extra `docs:` fix commit (`90ca6e2d`) this session.
70
+ Root cause is a coverage gap: the `mermaid` skill's pitfall list does not mention parentheses/shape-delimiter characters in node labels, so the exact failure mode was undocumented.
71
+
72
+ #### What caused friction (user side)
73
+
74
+ - None material.
75
+ The diagram-name mismatch ("State" vs "Step" dependency diagram) added one or two orienting reads but did not cause rework — the all-blocks `mmdc` sweep absorbed the ambiguity.
76
+
77
+ ### Diagnostic details
78
+
79
+ - **Model-performance correlation** — the only subagent dispatch was the `pre-completion-reviewer` on `anthropic/claude-sonnet-4-6` (judgment-heavy review work); appropriate match, no over/under-powered mismatch.
80
+ - **Escalation-delay tracking** — the Mermaid detour resolved in ~5 progress-making tool calls (extract blocks → `mmdc` each → read error → quote labels → re-validate); not a rabbit-hole, no subagent escalation warranted.
81
+ - **Unused-tool detection** — none; `mmdc` (the correct validator) was used directly.
82
+ - **Feedback-loop gap analysis** — verification ran incrementally throughout TDD (per-file `vitest` in each red/green, `pnpm run check` right after the interface-bearing wiring step, full suite + root lint + `fallow dead-code` at the end), not just terminally.
83
+
84
+ ### Changes made
85
+
86
+ 1. `.pi/skills/mermaid/SKILL.md` — added a "Parentheses and special characters in node labels" pitfall (with the `S1[✅ Step 1 - Spike (#446)]` WRONG/RIGHT example from this session) covering the `(`/`[`/`{`/`:` shape-delimiter trap that produced the `got 'PS'` parse error.
87
+
88
+ ### Follow-up (not implemented — suggest a GitHub issue + `/plan-issue`)
89
+
90
+ 1. **CI Mermaid-validation gate** — run `mmdc` over every `mermaid` fence as part of `pnpm run lint` or a pre-commit hook, so a broken diagram (like `74e2374f`'s unquoted-parens node) fails at author time instead of surviving to a user-caught defect.
91
+ Out of retro scope (infra, touches `package.json`/lint config/hooks); record as its own issue.
@@ -40,3 +40,36 @@ Test count delta: 1047 → 1051 (+4).
40
40
  - The `makeAgentConfig` helper was added to `test/tools/spawn-config.test.ts` (mirroring the pattern in `test/config/agent-types.test.ts`) rather than importing from a shared fixture, since the existing spawn-config test fixture infrastructure didn't need modification.
41
41
  - All three plan-enumerated cross-step invariants held green throughout: `resolveAgentConfig` disabled-config behavior, unknown-type fallback, and `getAllTypes` disabled-agent listing.
42
42
  - Pre-completion reviewer: **PASS** — all deterministic checks, code design, test artifacts, and cross-step invariants clean.
43
+
44
+ ## Stage: Land — worktree (2026-06-20T17:57:54Z)
45
+
46
+ ### Session summary
47
+
48
+ Ran `/land-worktree 448` from the root checkout: fast-forward merged the peer branch `issue-448-enabled-false-does-not-prevent-explicitl` onto linear `main`, pushed, verified CI green, closed issue #448, merged the release-please PR, and tore down the worktree.
49
+ Released `pi-subagents-v17.0.1` (the plan marked `Release: ship independently`).
50
+
51
+ ### Observations
52
+
53
+ #### What went well
54
+
55
+ - The new `/land-worktree` flow (added in `7cbfea46`) ran cleanly end-to-end on a real issue: ff-merge → push → CI → `issue_close` → release → teardown, with no blockers or rework.
56
+ - The release-please PR returned `MERGEABLE` / `UNSTABLE` with an empty `statusCheckRollup` — the documented `GITHUB_TOKEN` no-checks case.
57
+ Falling back from `release_pr_merge` to `gh pr merge 449 --rebase` followed by `git pull --ff-only` worked exactly as the prompt anticipates.
58
+
59
+ #### What caused friction (agent side)
60
+
61
+ - `instruction-violation` (self-identified, in retro) — the prompt's step 6.2 says to check the **full** release-PR body for which packages it bumps **before** merging.
62
+ I requested `body` in `gh pr view 449 --json body,...` but the `--jq` filter only printed `title`/`state`/`mergeStateStatus`/`statusCheckRollup`, so the body was never actually inspected.
63
+ I learned the bumped package (`pi-subagents`) only **after** merging, from the `git pull` output.
64
+ Impact: none this time (a single expected package bumped), but skipping the pre-merge body check means an unexpected sibling-package bump would slip through unnoticed.
65
+
66
+ ### Diagnostic details
67
+
68
+ - **Model-performance correlation** — no subagents dispatched; the land flow ran entirely on the parent session model.
69
+ No mismatch.
70
+ - **Escalation-delay tracking** — no rabbit-holes; no error retried more than once.
71
+ - **Feedback-loop gap analysis** — no code changes in the land session, so CI (run on the pushed SHA, conclusion `success`) was the appropriate and only verification gate.
72
+
73
+ ### Changes made
74
+
75
+ 1. `.pi/prompts/land-worktree.md` — added a one-line nudge to step 6.2 to print the release-PR body explicitly with `gh pr view <N> --json body -q .body`, since a `--jq` that drops `body` skips the package-bump check silently.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gotgenes/pi-subagents",
3
- "version": "17.0.1",
3
+ "version": "17.2.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
package/src/index.ts CHANGED
@@ -47,6 +47,7 @@ import { SteerTool } from "#src/tools/steer-tool";
47
47
  import { FsAgentFileOps } from "#src/ui/agent-file-ops";
48
48
  import { AgentsMenuHandler } from "#src/ui/agent-menu";
49
49
  import { AgentWidget } from "#src/ui/agent-widget";
50
+ import { SubagentsSettingsHandler } from "#src/ui/subagents-settings";
50
51
 
51
52
  export default function (pi: ExtensionAPI) {
52
53
  // ---- Register custom notification renderer ----
@@ -180,4 +181,15 @@ export default function (pi: ExtensionAPI) {
180
181
  });
181
182
  },
182
183
  });
184
+
185
+ // ---- /subagents-settings command ----
186
+
187
+ const subagentsSettings = new SubagentsSettingsHandler(settings);
188
+
189
+ pi.registerCommand("subagents-settings", {
190
+ description: "Configure subagent settings (concurrency, turn limits)",
191
+ handler: async (_args, ctx) => {
192
+ await subagentsSettings.handle({ ui: ctx.ui });
193
+ },
194
+ });
183
195
  }
@@ -148,6 +148,16 @@ export class AgentWidget implements SubagentManagerObserver {
148
148
  return age < maxAge;
149
149
  }
150
150
 
151
+ /**
152
+ * Background agents only — the widget's sole audience (ADR-0004 Decision A).
153
+ * Foreground runs are rendered by the `subagent` tool's inline `onUpdate` stream,
154
+ * so funneling both `listAgents()` call sites through this accessor applies the
155
+ * background predicate exactly once at the source.
156
+ */
157
+ private listBackgroundAgents(): Subagent[] {
158
+ return this.manager.listAgents().filter(record => record.invocation?.runInBackground === true);
159
+ }
160
+
151
161
  /** Project a live Subagent record onto a pure-data WidgetAgent snapshot. */
152
162
  private toWidgetAgent(record: Subagent): WidgetAgent {
153
163
  return {
@@ -172,7 +182,7 @@ export class AgentWidget implements SubagentManagerObserver {
172
182
  /** Delegate rendering to the pure widget-renderer module. */
173
183
  private renderWidget(tui: any, theme: Theme): string[] {
174
184
  return renderWidgetLines({
175
- agents: this.manager.listAgents().map(r => this.toWidgetAgent(r)),
185
+ agents: this.listBackgroundAgents().map(r => this.toWidgetAgent(r)),
176
186
  registry: this.registry,
177
187
  spinnerFrame: this.widgetFrame,
178
188
  terminalWidth: tui.terminal.columns,
@@ -183,10 +193,10 @@ export class AgentWidget implements SubagentManagerObserver {
183
193
 
184
194
  /**
185
195
  * Unregister the widget, clear the status bar, stop the interval timer, and
186
- * purge stale `finishedTurnAge` entries for agents no longer in `allAgents`.
196
+ * purge stale `finishedTurnAge` entries for agents no longer in `backgroundAgents`.
187
197
  * Called only from `update`'s idle path — not from `dispose`.
188
198
  */
189
- private clearWidget(allAgents: readonly AgentSummary[]): void {
199
+ private clearWidget(backgroundAgents: readonly AgentSummary[]): void {
190
200
  if (this.widgetRegistered) {
191
201
  this.uiCtx!.setWidget("agents", undefined);
192
202
  this.widgetRegistered = false;
@@ -198,7 +208,7 @@ export class AgentWidget implements SubagentManagerObserver {
198
208
  }
199
209
  if (this.widgetInterval) { clearInterval(this.widgetInterval); this.widgetInterval = undefined; }
200
210
  for (const [id] of this.finishedTurnAge) {
201
- if (!allAgents.some(a => a.id === id)) this.finishedTurnAge.delete(id);
211
+ if (!backgroundAgents.some(a => a.id === id)) this.finishedTurnAge.delete(id);
202
212
  }
203
213
  }
204
214
 
@@ -240,12 +250,12 @@ export class AgentWidget implements SubagentManagerObserver {
240
250
  update() {
241
251
  if (!this.uiCtx) return;
242
252
 
243
- const allAgents = this.manager.listAgents();
244
- this.seedFinishedAgents(allAgents);
245
- const state = assembleWidgetState(allAgents, (id, status) => this.shouldShowFinished(id, status));
253
+ const backgroundAgents = this.listBackgroundAgents();
254
+ this.seedFinishedAgents(backgroundAgents);
255
+ const state = assembleWidgetState(backgroundAgents, (id, status) => this.shouldShowFinished(id, status));
246
256
 
247
257
  if (!state.hasActive && !state.hasFinished) {
248
- this.clearWidget(allAgents);
258
+ this.clearWidget(backgroundAgents);
249
259
  return;
250
260
  }
251
261
 
@@ -0,0 +1,83 @@
1
+ // ---- Narrow interfaces ----
2
+
3
+ /** Narrow settings interface required by the subagents-settings command. */
4
+ export interface SubagentsSettingsManager {
5
+ readonly maxConcurrent: number;
6
+ readonly defaultMaxTurns: number | undefined;
7
+ readonly graceTurns: number;
8
+ applyMaxConcurrent(n: number): { message: string; level: "info" | "warning" };
9
+ applyDefaultMaxTurns(n: number): { message: string; level: "info" | "warning" };
10
+ applyGraceTurns(n: number): { message: string; level: "info" | "warning" };
11
+ }
12
+
13
+ /** Narrow UI interface — only the ctx.ui methods the settings handler calls. */
14
+ export interface SubagentsSettingsUI {
15
+ select(title: string, options: string[]): Promise<string | undefined>;
16
+ input(title: string, defaultValue?: string): Promise<string | undefined>;
17
+ notify(message: string, level: "info" | "warning" | "error"): void;
18
+ }
19
+
20
+ // ---- Class ----
21
+
22
+ /**
23
+ * Handler for the `/subagents-settings` slash command.
24
+ *
25
+ * Call `handle({ ui })` from the Pi command registration to open the interactive
26
+ * settings list. Lifted from `AgentsMenuHandler.showSettings`.
27
+ */
28
+ export class SubagentsSettingsHandler {
29
+ constructor(private readonly settings: SubagentsSettingsManager) {}
30
+
31
+ async handle({ ui }: { ui: SubagentsSettingsUI }): Promise<void> {
32
+ const choice = await ui.select("Settings", [
33
+ `Max concurrency (current: ${this.settings.maxConcurrent})`,
34
+ `Default max turns (current: ${this.settings.defaultMaxTurns ?? "unlimited"})`,
35
+ `Grace turns (current: ${this.settings.graceTurns})`,
36
+ ]);
37
+ if (!choice) return;
38
+
39
+ if (choice.startsWith("Max concurrency")) {
40
+ const val = await ui.input(
41
+ "Max concurrent background agents",
42
+ String(this.settings.maxConcurrent),
43
+ );
44
+ if (val) {
45
+ const n = parseInt(val, 10);
46
+ if (n >= 1) {
47
+ const toast = this.settings.applyMaxConcurrent(n);
48
+ ui.notify(toast.message, toast.level);
49
+ } else {
50
+ ui.notify("Must be a positive integer.", "warning");
51
+ }
52
+ }
53
+ } else if (choice.startsWith("Default max turns")) {
54
+ const val = await ui.input(
55
+ "Default max turns before wrap-up (0 = unlimited)",
56
+ String(this.settings.defaultMaxTurns ?? 0),
57
+ );
58
+ if (val) {
59
+ const n = parseInt(val, 10);
60
+ if (n >= 0) {
61
+ const toast = this.settings.applyDefaultMaxTurns(n);
62
+ ui.notify(toast.message, toast.level);
63
+ } else {
64
+ ui.notify("Must be 0 (unlimited) or a positive integer.", "warning");
65
+ }
66
+ }
67
+ } else if (choice.startsWith("Grace turns")) {
68
+ const val = await ui.input(
69
+ "Grace turns after wrap-up steer",
70
+ String(this.settings.graceTurns),
71
+ );
72
+ if (val) {
73
+ const n = parseInt(val, 10);
74
+ if (n >= 1) {
75
+ const toast = this.settings.applyGraceTurns(n);
76
+ ui.notify(toast.message, toast.level);
77
+ } else {
78
+ ui.notify("Must be a positive integer.", "warning");
79
+ }
80
+ }
81
+ }
82
+ }
83
+ }