@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 +20 -0
- package/README.md +1 -1
- package/docs/architecture/architecture.md +9 -9
- package/docs/plans/0444-shrink-widget-to-background-agents.md +156 -0
- package/docs/plans/0447-extract-subagents-settings-command.md +220 -0
- package/docs/retro/0444-shrink-widget-to-background-agents.md +43 -0
- package/docs/retro/0447-extract-subagents-settings-command.md +91 -0
- package/docs/retro/0448-block-disabled-agent-spawn.md +33 -0
- package/package.json +1 -1
- package/src/index.ts +12 -0
- package/src/ui/agent-widget.ts +18 -8
- package/src/ui/subagents-settings.ts +83 -0
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
|
|
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
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
|
}
|
package/src/ui/agent-widget.ts
CHANGED
|
@@ -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.
|
|
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 `
|
|
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(
|
|
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 (!
|
|
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
|
|
244
|
-
this.seedFinishedAgents(
|
|
245
|
-
const state = assembleWidgetState(
|
|
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(
|
|
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
|
+
}
|