@gotgenes/pi-subagents 16.6.0 → 17.0.1
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 +19 -0
- package/dist/public.d.ts +4 -1
- package/docs/architecture/architecture.md +238 -169
- package/docs/architecture/history/phase-18-reconsider-ui.md +166 -0
- package/docs/decisions/0004-reconsider-ui-direction.md +220 -0
- package/docs/plans/0422-delete-activity-tracker-ui-observer.md +204 -0
- package/docs/plans/0423-widget-self-drive-from-lifecycle.md +264 -0
- package/docs/plans/0424-drop-widget-dep-from-subagent-tool.md +166 -0
- package/docs/plans/0425-reconcile-subagent-events-contract.md +144 -0
- package/docs/plans/0426-consolidate-residual-test-clone-families.md +235 -0
- package/docs/plans/0427-reconsider-ui-direction.md +212 -0
- package/docs/plans/0446-spike-session-navigation-entry-criteria.md +171 -0
- package/docs/plans/0448-block-disabled-agent-spawn.md +207 -0
- package/docs/retro/0421-migrate-activity-readers-to-record-getters.md +49 -0
- package/docs/retro/0422-delete-activity-tracker-ui-observer.md +97 -0
- package/docs/retro/0423-widget-self-drive-from-lifecycle.md +112 -0
- package/docs/retro/0424-drop-widget-dep-from-subagent-tool.md +89 -0
- package/docs/retro/0425-reconcile-subagent-events-contract.md +101 -0
- package/docs/retro/0426-consolidate-residual-test-clone-families.md +100 -0
- package/docs/retro/0427-reconsider-ui-direction.md +102 -0
- package/docs/retro/0446-spike-session-navigation-entry-criteria.md +99 -0
- package/docs/retro/0448-block-disabled-agent-spawn.md +42 -0
- package/package.json +1 -1
- package/src/index.ts +11 -3
- package/src/observation/composite-subagent-observer.ts +49 -0
- package/src/runtime.ts +0 -6
- package/src/service/service.ts +4 -1
- package/src/tools/agent-tool.ts +1 -36
- package/src/tools/background-spawner.ts +1 -25
- package/src/tools/foreground-runner.ts +1 -30
- package/src/tools/helpers.ts +3 -2
- package/src/tools/spawn-config.ts +6 -0
- package/src/types.ts +1 -1
- package/src/ui/agent-widget.ts +34 -14
- package/src/ui/agent-activity-tracker.ts +0 -84
- package/src/ui/ui-observer.ts +0 -61
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,25 @@ 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.0.1](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v17.0.0...pi-subagents-v17.0.1) (2026-06-20)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* **pi-subagents:** exclude disabled agents from the subagent tool description ([#448](https://github.com/gotgenes/pi-packages/issues/448)) ([9a43414](https://github.com/gotgenes/pi-packages/commit/9a43414b3e15f0c978db9d468b737b13b801fdb2))
|
|
14
|
+
* **pi-subagents:** return an error when spawning a disabled agent type ([#448](https://github.com/gotgenes/pi-packages/issues/448)) ([0e0225e](https://github.com/gotgenes/pi-packages/commit/0e0225e167596217dc7b1d99dd6269600d65326b))
|
|
15
|
+
|
|
16
|
+
## [17.0.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v16.6.0...pi-subagents-v17.0.0) (2026-06-18)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### ⚠ BREAKING CHANGES
|
|
20
|
+
|
|
21
|
+
* SUBAGENT_EVENTS.ACTIVITY ("subagents:activity") is removed. It was never emitted, so no consumer could act on it, and there is no replacement — the activity tier was removed in Phase 18. Consumers should subscribe to the now-declared FAILED, COMPACTED, CREATED, and STEERED channels instead.
|
|
22
|
+
|
|
23
|
+
### Features
|
|
24
|
+
|
|
25
|
+
* reconcile SUBAGENT_EVENTS with emitted channels ([#425](https://github.com/gotgenes/pi-packages/issues/425)) ([ba0c48b](https://github.com/gotgenes/pi-packages/commit/ba0c48b294123c1498db2040b1037adcd7ad4e4e))
|
|
26
|
+
|
|
8
27
|
## [16.6.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v16.5.0...pi-subagents-v16.6.0) (2026-06-17)
|
|
9
28
|
|
|
10
29
|
|
package/dist/public.d.ts
CHANGED
|
@@ -154,7 +154,10 @@ interface SubagentsService {
|
|
|
154
154
|
declare const SUBAGENT_EVENTS: {
|
|
155
155
|
readonly STARTED: "subagents:started";
|
|
156
156
|
readonly COMPLETED: "subagents:completed";
|
|
157
|
-
readonly
|
|
157
|
+
readonly FAILED: "subagents:failed";
|
|
158
|
+
readonly COMPACTED: "subagents:compacted";
|
|
159
|
+
readonly CREATED: "subagents:created";
|
|
160
|
+
readonly STEERED: "subagents:steered";
|
|
158
161
|
};
|
|
159
162
|
/** Publish the SubagentsService on globalThis for cross-extension access. */
|
|
160
163
|
declare function publishSubagentsService(service: SubagentsService): void;
|
|
@@ -13,8 +13,8 @@ This document describes the architecture of the pi-subagents fork: a focused, co
|
|
|
13
13
|
4. **No time-based scheduling** — cron-style timed dispatch (upstream's `schedule.ts` subsystem) is removed from the core (#52).
|
|
14
14
|
Timed dispatch is a separate concern that any extension can implement by calling `spawn()` on the published API.
|
|
15
15
|
The max-concurrent admission gate is not scheduling in this sense — concurrency management stays in core.
|
|
16
|
-
5. **UI
|
|
17
|
-
|
|
16
|
+
5. **UI is an in-core, substitutable consumer** — [ADR-0004](../decisions/0004-reconsider-ui-direction.md) records the per-component decision: the widget shrinks to background agents only, the bespoke conversation viewer is replaced by native session navigation, the `/agents` command is dissolved into focused surfaces, and the surviving UI stays in the core as a reactive consumer (not extracted to a separate package).
|
|
17
|
+
Extraction remains an available future option because the composition invariant holds — the core is byte-for-byte identical with or without a given UI consumer.
|
|
18
18
|
6. **Snapshot, don't capture** — mutable parent state (ctx, session, model) is read once at spawn time and frozen into a `ParentSnapshot` data object.
|
|
19
19
|
No live references survive past the spawn call.
|
|
20
20
|
7. **Subscribe, don't thread** — observation of agent progress uses direct session-event subscription, not callback parameters threaded through multiple layers.
|
|
@@ -63,9 +63,8 @@ flowchart TB
|
|
|
63
63
|
|
|
64
64
|
subgraph observation["Observation domain"]
|
|
65
65
|
direction TB
|
|
66
|
-
RecordObserver["record-observer<br/>(stats via events)"]
|
|
66
|
+
RecordObserver["record-observer<br/>(stats + live activity via events)"]
|
|
67
67
|
Notification["notification<br/>(completion nudges)"]
|
|
68
|
-
UIObserver["ui-observer<br/>(streaming state)"]
|
|
69
68
|
end
|
|
70
69
|
|
|
71
70
|
subgraph tools["Tools domain"]
|
|
@@ -95,8 +94,8 @@ flowchart TB
|
|
|
95
94
|
SessionConfig --> Prompts & Env
|
|
96
95
|
AgentTypeRegistry --> DefaultAgents & CustomAgents
|
|
97
96
|
RecordObserver -.->|subscribes| SubagentSession
|
|
98
|
-
UIObserver -.->|subscribes| SubagentSession
|
|
99
97
|
Widget -.->|polls| SubagentManager
|
|
98
|
+
SubagentManager -.->|notifies| Widget
|
|
100
99
|
```
|
|
101
100
|
|
|
102
101
|
### Key domain types
|
|
@@ -258,7 +257,7 @@ sequenceDiagram
|
|
|
258
257
|
Asm-->>Factory: SessionConfig
|
|
259
258
|
Factory->>Child: create session + bind extensions
|
|
260
259
|
Factory-->>Ag: SubagentSession (born complete)
|
|
261
|
-
Note over Ag:
|
|
260
|
+
Note over Ag: record-observer subscribes to session events
|
|
262
261
|
Ag->>Sub: runTurnLoop(prompt, opts)
|
|
263
262
|
Sub->>Child: prompt + drive turn loop
|
|
264
263
|
Child-->>Sub: result text
|
|
@@ -322,6 +321,7 @@ src/
|
|
|
322
321
|
│ ├── notification.ts completion nudges
|
|
323
322
|
│ ├── notification-state.ts per-agent notification tracking
|
|
324
323
|
│ ├── renderer.ts notification TUI component
|
|
324
|
+
│ ├── composite-subagent-observer.ts fans manager notifications out to multiple observers
|
|
325
325
|
│ └── subagent-events-observer.ts manager lifecycle observer (event emission + persistence + notification)
|
|
326
326
|
│
|
|
327
327
|
├── service/ cross-extension API boundary
|
|
@@ -346,10 +346,8 @@ src/
|
|
|
346
346
|
│ ├── agent-creation-wizard.ts agent creation (AgentCreationWizard class)
|
|
347
347
|
│ ├── conversation-viewer.ts scrollable session overlay
|
|
348
348
|
│ ├── message-formatters.ts pure per-message-type formatters (extracted from conversation-viewer)
|
|
349
|
-
│ ├── agent-activity-tracker.ts live activity state tracker
|
|
350
349
|
│ ├── agent-file-ops.ts filesystem abstraction
|
|
351
350
|
│ ├── agent-file-writer.ts overwrite-guard + write + reload + notify helper
|
|
352
|
-
│ ├── ui-observer.ts session-event observer for streaming
|
|
353
351
|
│ └── display.ts pure formatters and shared types
|
|
354
352
|
│
|
|
355
353
|
└── handlers/ event handlers
|
|
@@ -361,11 +359,11 @@ src/
|
|
|
361
359
|
|
|
362
360
|
### Observation model
|
|
363
361
|
|
|
364
|
-
Record statistics (tool uses, token usage, compaction counts) are updated by `record-observer.ts`, which subscribes directly to session events.
|
|
365
|
-
|
|
366
|
-
Neither observer wraps or forwards the other — both subscribe directly to the session.
|
|
362
|
+
Record statistics (tool uses, token usage, compaction counts) and live activity (active tools, response text, turn counts) are updated by `record-observer.ts`, which subscribes directly to session events.
|
|
363
|
+
This is the single per-child session subscription — all run state lives on the `Subagent` record.
|
|
367
364
|
|
|
368
|
-
The widget reads agent state by polling
|
|
365
|
+
The widget reads agent state by polling the records exposed via `SubagentManager.listAgents()` every 80 ms; that poll loop is now started by the manager's lifecycle notifications (the widget subscribes as a `SubagentManagerObserver` fanned out through `CompositeSubagentObserver`), not by inbound calls from the spawn tools.
|
|
366
|
+
The conversation viewer subscribes to session events via `Subagent.subscribeToUpdates()` and reads messages via `Subagent.messages` — no direct `AgentSession` reference (#277).
|
|
369
367
|
|
|
370
368
|
## Cross-extension architecture
|
|
371
369
|
|
|
@@ -483,11 +481,14 @@ If Pi gains a native service registry ([earendil-works/pi#4207]), these accessor
|
|
|
483
481
|
|
|
484
482
|
The core emits events on `pi.events` that any extension can observe:
|
|
485
483
|
|
|
486
|
-
| Channel | Payload
|
|
487
|
-
| --------------------- |
|
|
488
|
-
| `subagents:started` | `{ id, type, description }`
|
|
489
|
-
| `subagents:completed` | `{ id, type, status, result?, error? }`
|
|
490
|
-
| `subagents:
|
|
484
|
+
| Channel | Payload | When |
|
|
485
|
+
| --------------------- | ----------------------------------------------------------------------------------- | --------------------------------------------- |
|
|
486
|
+
| `subagents:started` | `{ id, type, description }` | Agent begins running |
|
|
487
|
+
| `subagents:completed` | `{ id, type, description, status, result?, error?, toolUses, durationMs, tokens? }` | Agent finishes successfully |
|
|
488
|
+
| `subagents:failed` | same as `completed` (`buildEventData` shape) | Agent ends in `error`/`stopped`/`aborted` |
|
|
489
|
+
| `subagents:compacted` | `{ id, type, description, reason, tokensBefore, compactionCount }` | Child session compacts |
|
|
490
|
+
| `subagents:created` | `{ id, type, description, isBackground }` | Background agent created (pre-admission) |
|
|
491
|
+
| `subagents:steered` | `{ id, message }` | Steering message delivered to a running agent |
|
|
491
492
|
|
|
492
493
|
These are fire-and-forget broadcast events — no request IDs, no reply channels.
|
|
493
494
|
|
|
@@ -894,173 +895,232 @@ All nine steps are closed: [#381], [#373], [#374], [#375], [#376], [#377], [#378
|
|
|
894
895
|
[#415] migrated `pi-subagents-worktrees` to `loadLayeredSettings` after the Step 9 published release.
|
|
895
896
|
See [phase-17-core-consolidation.md](history/phase-17-core-consolidation.md) for the full findings, step outcomes, dependency diagram, and tracks.
|
|
896
897
|
|
|
897
|
-
## Phase 18
|
|
898
|
-
|
|
899
|
-
After 17 phases the core is in very good shape: health 78/100 (B), zero dead code, zero fallow refactoring targets, average cyclomatic 1.4.
|
|
900
|
-
The one structural debt that remains is not spread thin across the core — it is concentrated in a single place: the UI's live-streaming state (the "activity tier") is woven into the core's runtime, spawn tools, the LLM-facing `subagent` tool, and the notification path.
|
|
901
|
-
This is the entanglement the [first-principles refinement](#first-principles-refinement-and-the-deeper-target) predicted — "the activity/metrics push tier is provisional; its only reactive consumer is the inherited widget" — observed in the code.
|
|
902
|
-
Disentangling it is what lets the core reach a great state, and it is the prerequisite to any "reconsider the UI" decision: today the UI cannot be a clean consumer because the core does its streaming bookkeeping for it.
|
|
903
|
-
|
|
904
|
-
### Findings
|
|
905
|
-
|
|
906
|
-
Six findings tell one story — the activity tier has metastasized out of the UI and into the core.
|
|
907
|
-
|
|
908
|
-
1. **UI state lives on the core runtime.**
|
|
909
|
-
`SubagentRuntime.agentActivity: Map<string, AgentActivityTracker>` puts a UI streaming-state map on the core composition root.
|
|
910
|
-
`SubagentRuntime` owns session-scoped lifecycle state; `AgentActivityTracker` (active tools, response text, turn count) is pure rendering state the core never reads.
|
|
911
|
-
Category C (coupling).
|
|
912
|
-
2. **Core spawn tools wire UI streaming.**
|
|
913
|
-
`foreground-runner.ts` and `background-spawner.ts` construct `AgentActivityTracker`, call `subscribeUIObserver` (a second session subscription parallel to the core's own `record-observer`), call `setSession`, and populate or delete the activity map and drive the widget (`ensureTimer`, `update`, `markFinished`).
|
|
914
|
-
Their own doc comments list "AgentActivityTracker creation, UI observer subscription" as responsibilities.
|
|
915
|
-
Category C (mixed responsibility, parameter relay).
|
|
916
|
-
3. **The LLM-facing tool depends on the widget.**
|
|
917
|
-
`AgentTool` takes a 4-method `widget` dependency plus `runtime.agentActivity` and calls `this.widget.setUICtx(ctx.ui)`.
|
|
918
|
-
Every `AgentTool` unit test must stub the widget and the activity map (`createToolDeps`), although the tool's real concern is dispatch — testability friction marking a domain seam.
|
|
919
|
-
Category C/D.
|
|
920
|
-
4. **Two parallel observers split the run-metric domain.**
|
|
921
|
-
Each child session gets two subscriptions: `record-observer` accumulates `SubagentState` (tool uses, usage, compactions) and `ui-observer` accumulates `AgentActivityTracker` (active tools, response text, turn count).
|
|
922
|
-
`turnCount` is a genuine run metric that lives only in the UI tracker, so `notification.ts` and the foreground result text reach into the tracker to recover it.
|
|
923
|
-
Category C (anemic split, duplication).
|
|
924
|
-
5. **A core observation concern reaches into UI state.**
|
|
925
|
-
`NotificationManager` holds the activity map and reads `turnCount`/`maxTurns` from it to build notification details, and deletes entries from it.
|
|
926
|
-
Category C (Law of Demeter).
|
|
927
|
-
6. **The public event contract is both vacant and incomplete.**
|
|
928
|
-
`SUBAGENT_EVENTS.ACTIVITY = "subagents:activity"` is declared in the service surface and the doc's lifecycle-events table but is never emitted anywhere — a vacant hook the doc's own "no vacant hooks" rule forbids.
|
|
929
|
-
Meanwhile three emitted channels (`subagents:failed`, `subagents:compacted`, `subagents:created`) are absent from the constant map.
|
|
930
|
-
Category A/E.
|
|
898
|
+
## Phase 18 (complete)
|
|
931
899
|
|
|
932
|
-
|
|
900
|
+
Phase 18 disentangled the activity tier from the core and recorded a first-principles decision about the UI's direction.
|
|
901
|
+
Steps 1–5 (the spine) consolidated all run state onto `SubagentState`, deleted `AgentActivityTracker` and `ui-observer` (−145 LOC), and made the widget a pure reactive consumer of lifecycle events with no inbound calls from core spawn tools.
|
|
902
|
+
Step 6 reconciled the public event contract (breaking: removed the vacant `SUBAGENT_EVENTS.ACTIVITY` channel; added `FAILED`, `COMPACTED`, `CREATED`, `STEERED`).
|
|
903
|
+
Step 7 consolidated residual test clone families, dropping from 24 to 14 clone groups (below the <15 target).
|
|
904
|
+
Step 8 captured the per-component UI decisions in [ADR-0004]: the widget shrinks to background agents only; the bespoke `ConversationViewer` is replaced by native session navigation (a read-only transcript via `parseSessionEntries`, per the Step 1 spike — see [ADR-0004]'s addendum); the `/agents` command is dissolved (remove the create wizard and agent-types editor, extract settings to a focused command); the surviving UI stays in-core as a substitutable reactive consumer.
|
|
905
|
+
Source LOC decreased from 7,751 (62 files) to 7,650 (61 files); tests grew from 1,031 to 1,047.
|
|
906
|
+
All eight steps are closed: [#420], [#421], [#422], [#423], [#424], [#425], [#426], [#427].
|
|
907
|
+
See [phase-18-reconsider-ui.md](history/phase-18-reconsider-ui.md) for the full findings, step outcomes, dependency diagram, and tracks.
|
|
908
|
+
|
|
909
|
+
## Phase 19 (proposed)
|
|
910
|
+
|
|
911
|
+
Phase 19 implements the per-component UI decisions recorded in [ADR-0004]: shrink the widget to background-only, replace the bespoke conversation viewer with native session navigation, dissolve the monolithic `/agents` menu, and keep the surviving UI in-core.
|
|
912
|
+
|
|
913
|
+
The sequencing follows Kent Beck's "make the change easy, then make the easy change."
|
|
914
|
+
The end state deletes `agent-menu.ts` — the god-command that bundles four unrelated jobs — and everything reachable only from it.
|
|
915
|
+
Rather than surgically mutate that doomed module (and the #1 churn hotspot `index.ts`) once per option, Phase 19 first stands up the replacement surfaces additively, then removes the now-orphaned subtree in a single terminal cut.
|
|
916
|
+
This keeps every responsibility's old surface live until its replacement exists (ADR-0004's no-interim-regression invariant), turns the three replacement steps into genuinely parallel work (none touch `agent-menu.ts`), and reduces `index.ts` edits from four surgical removals to one deregistration.
|
|
917
|
+
|
|
918
|
+
Seven steps in three phases:
|
|
919
|
+
|
|
920
|
+
- **Phase A — stand up replacements (additive):** spike, settings command, background widget, native session navigation (Steps 1–4).
|
|
921
|
+
- **Phase B — dissolve `/agents` (terminal cut):** delete the orphaned subtree in two deletion commits, one per subtree (Steps 5–6).
|
|
922
|
+
- **Phase C — test health:** consolidate the test clones that survive the cut (Step 7).
|
|
923
|
+
|
|
924
|
+
### Health metrics (Phase 18 → Phase 19 target)
|
|
925
|
+
|
|
926
|
+
| Metric | Phase 18 (current) | Phase 19 target |
|
|
927
|
+
| ---------------------- | ------------------------ | -------------------- |
|
|
928
|
+
| Health score | 78/100 (B) | 83/100 (B+) |
|
|
929
|
+
| Source LOC | 7,650 (61 files) | ~6,780 (~55 files) |
|
|
930
|
+
| Production duplication | 11 lines (1 group) | 0 lines |
|
|
931
|
+
| Test clone groups | 16 | ≤ 10 |
|
|
932
|
+
| Top churn hotspot | `index.ts` (103 commits) | `index.ts` (cooling) |
|
|
933
|
+
|
|
934
|
+
### ✅ Step 1 — Spike: resolve ADR-0004 entry criteria ([#446])
|
|
935
|
+
|
|
936
|
+
Smell: Category C (coupling boundary) — four open decisions block the session-navigation implementation.
|
|
937
|
+
Target: `docs/decisions/0004-reconsider-ui-direction.md` addendum.
|
|
938
|
+
|
|
939
|
+
The four entry criteria from ADR-0004:
|
|
940
|
+
|
|
941
|
+
1. **Root-continuity:** Does the root's in-flight turn survive `ctx.switchSession()` and a return gesture?
|
|
942
|
+
2. **View-only vs interactive:** `switchSession` (full interactive takeover) or `loadEntriesFromFile` (read-only transcript built from JSONL)?
|
|
943
|
+
3. **Parallel-agent navigation:** Operator gesture to select which of N background agents to view (from the widget, a command, or both).
|
|
944
|
+
4. **Settings command name:** `/subagents-settings`, `/agents-settings`, or another form consistent with sibling packages?
|
|
945
|
+
|
|
946
|
+
Produce a minimal spike (observed test or PoC against a real session) that answers each question, then record the answers as an addendum to ADR-0004.
|
|
947
|
+
No production source files change; the spike closes when the ADR addendum is merged.
|
|
948
|
+
|
|
949
|
+
Outcome: ADR-0004 updated with all four entry-criteria answers; Step 4 unblocked.
|
|
950
|
+
|
|
951
|
+
`Release: independent`
|
|
952
|
+
|
|
953
|
+
### Step 2 — Extract settings to a focused `/subagents-settings` command ([#447])
|
|
954
|
+
|
|
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
|
+
Target files:
|
|
957
|
+
|
|
958
|
+
- New `src/ui/subagents-settings.ts` — `SubagentsSettingsHandler` lifted from `AgentsMenuHandler.showSettings`, carrying its own narrow `SubagentsSettingsManager` interface (the three `apply*` methods and three readonly accessors only).
|
|
959
|
+
- `src/index.ts` — register the new command (name confirmed by Step 1); pass `settings` directly.
|
|
960
|
+
- New `test/ui/subagents-settings.test.ts` — unit tests for the extracted handler.
|
|
961
|
+
|
|
962
|
+
`showSettings` depends only on `this.settings` (the self-contained `AgentMenuSettings` shape), so the extraction copies that logic into a new file with zero coupling to the wizard, editor, or viewer.
|
|
963
|
+
The old in-menu Settings option keeps working until the terminal cut deletes `agent-menu.ts` wholesale — there is no surgical removal of `showSettings` or `AgentMenuSettings` from the doomed file.
|
|
964
|
+
|
|
965
|
+
Outcome: new `subagents-settings.ts` (~80 LOC) and focused command registered; `agent-menu.ts` untouched.
|
|
966
|
+
|
|
967
|
+
`Release: independent`
|
|
968
|
+
|
|
969
|
+
### Step 3 — Shrink widget to background agents only ([#444])
|
|
970
|
+
|
|
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
|
+
Target files:
|
|
973
|
+
|
|
974
|
+
- `src/ui/agent-widget.ts` — funnel both `manager.listAgents()` call sites (`update()` and `renderWidget()`) through a single private accessor, then flip that accessor to background-only via `record.invocation?.runInBackground === true`.
|
|
975
|
+
- `src/ui/widget-renderer.ts` — verify no foreground-specific rendering path survives.
|
|
976
|
+
- `test/ui/agent-widget.test.ts` — add background-only filtering tests; update assertions.
|
|
977
|
+
|
|
978
|
+
The widget calls `listAgents()` at two sites today — `update()` (feeding `seedFinishedAgents`, `assembleWidgetState`, and `clearWidget`) and `renderWidget()` (the tree map).
|
|
979
|
+
Filtering at only one site leaves the other rendering foreground agents, so the enabling move is to route both through one accessor and apply the predicate once at the source.
|
|
980
|
+
`Subagent.invocation.runInBackground` is the reliable signal: set by `spawn-config.ts` → `AgentInvocation.runInBackground` → stored on `Subagent.invocation`.
|
|
981
|
+
ADR-0004 Decision A: foreground runs suppress the widget; the inline `onUpdate` stream is authoritative there.
|
|
982
|
+
|
|
983
|
+
Outcome: widget shows only background agents; foreground/widget duplication eliminated; the background predicate lives at a single funnel.
|
|
984
|
+
|
|
985
|
+
`Release: independent`
|
|
933
986
|
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
987
|
+
### Step 4 — Implement native session navigation ([#445])
|
|
988
|
+
|
|
989
|
+
Smell: Category C (coupling) — the bespoke `ConversationViewer` re-implements session-transcript rendering when Pi's own machinery targets the already-persisted child session JSONL.
|
|
990
|
+
This step adds the new surface alongside the existing viewer; it does not touch `agent-menu.ts`.
|
|
991
|
+
Target files:
|
|
992
|
+
|
|
993
|
+
- New `src/ui/session-navigator.ts` (or a widget gesture, per Step 1's answer to parallel-agent navigation) — operator picks one of N background agents and views its persisted session via the spike-chosen mechanism, keyed on `record.outputFile`.
|
|
994
|
+
- `src/index.ts` — register the new navigation surface (command and/or widget gesture, per the spike).
|
|
995
|
+
|
|
996
|
+
ADR-0004 Decision B: "Tell-Don't-Ask — hand Pi the session path; Pi owns the viewer."
|
|
997
|
+
Mechanism (confirmed by the Step 1 spike, [ADR-0004] addendum): a **read-only** transcript rendered from `parseSessionEntries(readFileSync(record.outputFile, "utf8"))`, surfaced through a flat command — **not** `switchSession` (a full takeover that invalidates the root's in-flight turn) and **not** `loadEntriesFromFile` (a test-only export of the SDK's `core/session-manager` module that the package's public barrel does not re-export, in both `0.79.1` and `0.79.8`).
|
|
998
|
+
`Subagent.outputFile` already exposes the persisted child session JSONL path via `subagentSession?.outputFile` — no new SDK dependency.
|
|
999
|
+
Rendering uses Pi's own public APIs (no bespoke renderer): `parseSessionEntries` → drop the `SessionHeader` → `buildSessionContext(...).messages` → Pi's public entry components (`AssistantMessageComponent` / `ToolExecutionComponent` / …) or `serializeConversation` (see the [ADR-0004] addendum, Findings 0 and 1).
|
|
1000
|
+
The new surface stands up while the old `viewAgentConversation`/`ConversationViewer` path still works; the bespoke viewer is removed only by the terminal cut (Step 5).
|
|
1001
|
+
|
|
1002
|
+
Outcome: operator views a child agent's persisted session through Pi's native machinery; the new surface coexists with the old viewer until Step 5.
|
|
1003
|
+
|
|
1004
|
+
`Release: independent` (spike-gated)
|
|
1005
|
+
|
|
1006
|
+
### Step 5 — Dissolve `/agents` and remove the conversation-viewer subtree ([#442])
|
|
1007
|
+
|
|
1008
|
+
Smell: Category A (dead subsystem) plus Category B (oversized) — once Steps 2–4 re-home all four menu responsibilities, the `/agents` command and everything reachable only from `agent-menu.ts` is an unreferenced subtree.
|
|
1009
|
+
This is the first of two deletion commits (split by subtree).
|
|
1010
|
+
The hub `agent-menu.ts` is deleted here, not surgically narrowed, and deleting it is what orphans the leaf subtrees — so it must precede the definition-management deletion (Step 6), because `agent-menu.ts` statically imports the wizard, editor, and file-ops, and dynamically imports the viewer.
|
|
1011
|
+
Target files:
|
|
1012
|
+
|
|
1013
|
+
- `src/index.ts` — remove the `registerCommand("agents", …)` block, the `AgentsMenuHandler` construction and import, and the `FsAgentFileOps` import/construction (its only use is wiring the menu).
|
|
1014
|
+
- Delete `src/ui/agent-menu.ts` (331 LOC) and `test/ui/agent-menu.test.ts` (185 LOC).
|
|
1015
|
+
- Delete `src/ui/conversation-viewer.ts` (241 LOC) and `test/conversation-viewer.test.ts` (239 LOC) — its only consumer is `agent-menu.ts`'s dynamic import, gone with the hub.
|
|
1016
|
+
- Delete `src/ui/message-formatters.ts` (195 LOC) and `test/message-formatters.test.ts` (388 LOC, the largest test function by LOC) — its only consumer is `ConversationViewer`.
|
|
1017
|
+
|
|
1018
|
+
Running-agent visibility is now owned by the background widget (Step 3); session navigation replaces the bespoke overlay (Step 4); settings live in `/subagents-settings` (Step 2).
|
|
1019
|
+
Deleting the hub in one move avoids any surgical edit to the doomed file and leaves the definition-management leaves orphaned for Step 6.
|
|
1020
|
+
|
|
1021
|
+
Outcome: `/agents` dissolved; −767 LOC source (menu hub + viewer + formatters); −812 LOC test; largest test function eliminated; `index.ts` edited once (deregistration), never surgically narrowed.
|
|
1022
|
+
|
|
1023
|
+
`Release: batch "dissolve-agents"`
|
|
1024
|
+
|
|
1025
|
+
### Step 6 — Remove the orphaned agent-definition management subtree ([#441])
|
|
1026
|
+
|
|
1027
|
+
Smell: Category A (dead subsystem) — the creation wizard and config editor are removed per ADR-0004 Decision C; after Step 5 deletes their only importer (`agent-menu.ts`), they and their file-ops helpers are pure orphans.
|
|
1028
|
+
This is the second deletion commit (split by subtree).
|
|
1029
|
+
Target files:
|
|
1030
|
+
|
|
1031
|
+
- Delete `src/ui/agent-creation-wizard.ts` (233 LOC) and `test/ui/agent-creation-wizard.test.ts` (296 LOC).
|
|
1032
|
+
- Delete `src/ui/agent-config-editor.ts` (199 LOC) and `test/ui/agent-config-editor.test.ts` (392 LOC) — eliminates the 11-line internal production clone in `disableAgent`/`ejectAgent`, the package's only remaining production duplication.
|
|
1033
|
+
- Delete `src/ui/agent-file-ops.ts` (59 LOC) and `test/ui/agent-file-ops.test.ts` (112 LOC) — only consumers were wizard + editor.
|
|
1034
|
+
- Delete `src/ui/agent-file-writer.ts` (55 LOC) and `test/ui/agent-file-writer.test.ts` (148 LOC) — only consumers were wizard + editor.
|
|
1035
|
+
- `test/helpers/ui-stubs.ts` — delete `makeFileOps`, `createTestSubagentConfig`, and `spawnAndWait` from `makeMenuManager` if no surviving consumer remains; delete the file outright once all consumers are gone.
|
|
1036
|
+
|
|
1037
|
+
An operator generates a new agent `.md` by asking a Pi session directly (more capable than a fixed wizard) or by writing the file in an editor; viewing and editing definitions is served by opening the `.md` files in an editor or IDE.
|
|
1038
|
+
These files are orphaned by Step 5, so this is a pure `git rm` with no surviving references and no edit to any doomed file.
|
|
1039
|
+
|
|
1040
|
+
Outcome: −546 LOC source (wizard + editor + file-ops + file-writer); −948 LOC test; production duplication → 0 lines; 1 production and 1 test clone group eliminated.
|
|
1041
|
+
|
|
1042
|
+
`Release: batch "dissolve-agents"`
|
|
1043
|
+
|
|
1044
|
+
### Step 7 — Consolidate remaining test clone families ([#443])
|
|
1045
|
+
|
|
1046
|
+
Smell: Category D (testability) — 16 clone groups at Phase 18 end; the terminal cut (Steps 5–6) removes ~4 groups; remaining groups are extraction targets.
|
|
1047
|
+
Run after the cut so no helper is extracted into a file the cut then deletes.
|
|
1048
|
+
Target files:
|
|
1049
|
+
|
|
1050
|
+
- `test/lifecycle/subagent-manager.test.ts` — extract a shared assertion helper for 3 clone families (23 lines across groups at :92/:109, :282/:330, and :323 shared with `subagent.test.ts`).
|
|
1051
|
+
- `test/ui/agent-widget.test.ts` — merge the duplicate `makeWidget` helper defined twice across two `describe` blocks (14-line clone at :225/:284).
|
|
1052
|
+
- `test/session/session-config.test.ts` — extract a shared fixture for the 16-line internal clone (lines 131–146 / 151–166).
|
|
1053
|
+
- `test/lifecycle/concurrency-limiter.test.ts` — extract shared setup for the 10-line clone (lines 21–30 / 148–155).
|
|
1054
|
+
- `test/tools/spawn-config.test.ts` — extract a shared fixture for the 9-line clone (lines 22–30 / 35–43).
|
|
1055
|
+
|
|
1056
|
+
Outcome: test clone groups ≤ 10 (from 16); `subagent-manager.test.ts` uses shared factory helpers.
|
|
1057
|
+
|
|
1058
|
+
`Release: independent`
|
|
1006
1059
|
|
|
1007
1060
|
### Step dependency diagram
|
|
1008
1061
|
|
|
1009
1062
|
```mermaid
|
|
1010
|
-
flowchart
|
|
1011
|
-
S1[
|
|
1012
|
-
S2[
|
|
1013
|
-
S3[
|
|
1014
|
-
S4[
|
|
1015
|
-
S5[
|
|
1016
|
-
S6[
|
|
1017
|
-
S7[
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
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)]
|
|
1071
|
+
|
|
1072
|
+
S1 --> S4
|
|
1073
|
+
S2 --> S5
|
|
1074
|
+
S3 --> S5
|
|
1075
|
+
S4 --> S5
|
|
1076
|
+
S5 --> S6
|
|
1077
|
+
S6 --> S7
|
|
1023
1078
|
```
|
|
1024
1079
|
|
|
1080
|
+
The terminal cut (Step 5) depends on all three replacements — settings (Step 2), widget (Step 3), and session navigation (Step 4) — because each of the four `/agents` options must have its responsibility re-homed before its branch can die.
|
|
1081
|
+
The old `S1 → S6 → S7` chain hid the widget dependency; this diagram makes it explicit.
|
|
1082
|
+
|
|
1025
1083
|
### Parallel tracks
|
|
1026
1084
|
|
|
1027
|
-
- **Track A —
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1085
|
+
- **Track A — Replacements (Steps 1–4):** the spike gates session navigation (Step 1 → Step 4); settings (Step 2) and the background widget (Step 3) are independent of the spike and of each other.
|
|
1086
|
+
None of these steps edits `agent-menu.ts`, so they carry no shared-file collision on the menu — genuinely parallelizable, unlike the prior plan's Steps 2/3/5, which all collided on `agent-menu.ts` and `index.ts`.
|
|
1087
|
+
Steps 2 and 4 each append a command registration to `index.ts` (additive, low-conflict).
|
|
1088
|
+
- **Track B — Dissolution (Steps 5 → 6):** the terminal cut, gated on all of Track A landing.
|
|
1089
|
+
Hub-first ordering is forced: Step 5 deletes `agent-menu.ts` (orphaning the leaves), then Step 6 `git rm`s the now-orphaned definition-management subtree.
|
|
1090
|
+
- **Track C — Test health (Step 7):** clone consolidation, run after the cut so no surviving helper is extracted into a doomed file.
|
|
1091
|
+
|
|
1092
|
+
### Release batches
|
|
1093
|
+
|
|
1094
|
+
- **Batch "dissolve-agents":** Steps 5, 6 (ship together; tail = Step 6).
|
|
1095
|
+
Depends on Steps 2, 3, 4 already merged.
|
|
1096
|
+
- Independently releasable: Steps 1, 2, 3, 4, 7.
|
|
1037
1097
|
|
|
1038
1098
|
## Refactoring history
|
|
1039
1099
|
|
|
1040
|
-
Phases 1–5, 7–
|
|
1041
|
-
Phase 6 (UI extraction to a separate package)
|
|
1100
|
+
Phases 1–5, 7–18 are complete.
|
|
1101
|
+
Phase 6 (UI extraction to a separate package) was deferred to Phase 18; its intent was resolved by [ADR-0004] (Phase 18 Step 8).
|
|
1042
1102
|
Detailed records are preserved in per-phase history files:
|
|
1043
1103
|
|
|
1044
|
-
| Phase | Title | Status
|
|
1045
|
-
| ----- | --------------------------------------------------- |
|
|
1046
|
-
| 1 | Export SubagentsService API boundary | Complete
|
|
1047
|
-
| 2 | Remove scheduling subsystem | Complete
|
|
1048
|
-
| 3 | Remove group-join, RPC; replace output-file | Complete
|
|
1049
|
-
| 4 | Implement and publish SubagentsService | Complete
|
|
1050
|
-
| 5 | Decompose index.ts | Complete
|
|
1051
|
-
| 6 | Extract UI to separate package |
|
|
1052
|
-
| 7 | Encapsulation and dependency narrowing | Complete
|
|
1053
|
-
| 8 | Testability, display extraction, menu decomposition | Complete
|
|
1054
|
-
| 9 | Observation consolidation, ctx elimination | Complete
|
|
1055
|
-
| 10 | Domain organization, bag decomposition, complexity | Complete
|
|
1056
|
-
| 11 | Closure factories to classes | Complete
|
|
1057
|
-
| 12 | Complexity reduction and test fixture extraction | Complete
|
|
1058
|
-
| 13 | Remaining structural smells | Complete
|
|
1059
|
-
| 14 | Strip policy from core | Complete
|
|
1060
|
-
| 15 | Domain model evolution | Complete
|
|
1061
|
-
| 16 | Invert dependencies (extensions on a minimal core) | Complete
|
|
1062
|
-
| 17 | Core consolidation | Complete
|
|
1063
|
-
| 18 | Reconsider UI (first principles) |
|
|
1104
|
+
| Phase | Title | Status | History |
|
|
1105
|
+
| ----- | --------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------ |
|
|
1106
|
+
| 1 | Export SubagentsService API boundary | Complete | [phase-1-api-boundary.md](history/phase-1-api-boundary.md) |
|
|
1107
|
+
| 2 | Remove scheduling subsystem | Complete | [phase-2-remove-scheduling.md](history/phase-2-remove-scheduling.md) |
|
|
1108
|
+
| 3 | Remove group-join, RPC; replace output-file | Complete | [phase-3-remove-rpc-groupjoin.md](history/phase-3-remove-rpc-groupjoin.md) |
|
|
1109
|
+
| 4 | Implement and publish SubagentsService | Complete | [phase-4-implement-service.md](history/phase-4-implement-service.md) |
|
|
1110
|
+
| 5 | Decompose index.ts | Complete | [phase-5-decompose-index.md](history/phase-5-decompose-index.md) |
|
|
1111
|
+
| 6 | Extract UI to separate package | Superseded by ADR-0004 | — |
|
|
1112
|
+
| 7 | Encapsulation and dependency narrowing | Complete | [phase-7-encapsulation.md](history/phase-7-encapsulation.md) |
|
|
1113
|
+
| 8 | Testability, display extraction, menu decomposition | Complete | [phase-8-testability.md](history/phase-8-testability.md) |
|
|
1114
|
+
| 9 | Observation consolidation, ctx elimination | Complete | [phase-9-observation-ctx.md](history/phase-9-observation-ctx.md) |
|
|
1115
|
+
| 10 | Domain organization, bag decomposition, complexity | Complete | [phase-10-structural-decomposition.md](history/phase-10-structural-decomposition.md) |
|
|
1116
|
+
| 11 | Closure factories to classes | Complete | [phase-11-closure-to-class.md](history/phase-11-closure-to-class.md) |
|
|
1117
|
+
| 12 | Complexity reduction and test fixture extraction | Complete | [phase-12-complexity-test-fixtures.md](history/phase-12-complexity-test-fixtures.md) |
|
|
1118
|
+
| 13 | Remaining structural smells | Complete | [phase-13-remaining-smells.md](history/phase-13-remaining-smells.md) |
|
|
1119
|
+
| 14 | Strip policy from core | Complete | [phase-14-strip-policy.md](history/phase-14-strip-policy.md) |
|
|
1120
|
+
| 15 | Domain model evolution | Complete | [phase-15-domain-model-evolution.md](history/phase-15-domain-model-evolution.md) |
|
|
1121
|
+
| 16 | Invert dependencies (extensions on a minimal core) | Complete | [phase-16-invert-dependencies.md](history/phase-16-invert-dependencies.md) |
|
|
1122
|
+
| 17 | Core consolidation | Complete | [phase-17-core-consolidation.md](history/phase-17-core-consolidation.md) |
|
|
1123
|
+
| 18 | Reconsider UI (first principles) | Complete | [phase-18-reconsider-ui.md](history/phase-18-reconsider-ui.md) |
|
|
1064
1124
|
|
|
1065
1125
|
### Structural refactoring issues
|
|
1066
1126
|
|
|
@@ -1084,6 +1144,7 @@ Detailed records are preserved in per-phase history files:
|
|
|
1084
1144
|
| Phase 16 (abandoned) | #256 (superseded), #257 (parked), #258, #259 (not planned) | Agent collaborator architecture — replaced by the inversion approach above ([ADR-0002]) |
|
|
1085
1145
|
| Phase 17 | #381, #373, #374, #375, #376, #377, #378, #379, #380 | ConcurrencyLimiter, SubagentState, run-start encapsulation, run collaborators, events observer, widget decoupling, lifecycle test fixtures, UI/tools test fixtures, settings-loader extraction |
|
|
1086
1146
|
| Phase 17 (follow-on) | #412, #415 | Session-mock builder unification, worktrees settings-helper migration |
|
|
1147
|
+
| Phase 18 | #420, #421, #422, #423, #424, #425, #426, #427 | Fold metrics onto record, migrate readers, delete activity tier, widget self-drives, drop widget from tool, reconcile event contract, consolidate test clones, UI-direction ADR |
|
|
1087
1148
|
|
|
1088
1149
|
The remaining open issue is #22 (parent-session resolution), a cross-extension track that does not gate the structural work.
|
|
1089
1150
|
|
|
@@ -1147,4 +1208,12 @@ The upstream test suite is run periodically as a regression canary for the sessi
|
|
|
1147
1208
|
[#425]: https://github.com/gotgenes/pi-packages/issues/425
|
|
1148
1209
|
[#426]: https://github.com/gotgenes/pi-packages/issues/426
|
|
1149
1210
|
[#427]: https://github.com/gotgenes/pi-packages/issues/427
|
|
1211
|
+
[#441]: https://github.com/gotgenes/pi-packages/issues/441
|
|
1212
|
+
[#442]: https://github.com/gotgenes/pi-packages/issues/442
|
|
1213
|
+
[#443]: https://github.com/gotgenes/pi-packages/issues/443
|
|
1214
|
+
[#444]: https://github.com/gotgenes/pi-packages/issues/444
|
|
1215
|
+
[#445]: https://github.com/gotgenes/pi-packages/issues/445
|
|
1216
|
+
[#446]: https://github.com/gotgenes/pi-packages/issues/446
|
|
1217
|
+
[#447]: https://github.com/gotgenes/pi-packages/issues/447
|
|
1150
1218
|
[ADR-0002]: ../decisions/0002-extensions-on-a-minimal-core.md
|
|
1219
|
+
[ADR-0004]: ../decisions/0004-reconsider-ui-direction.md
|