@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.
Files changed (36) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/public.d.ts +4 -1
  3. package/docs/architecture/architecture.md +238 -169
  4. package/docs/architecture/history/phase-18-reconsider-ui.md +166 -0
  5. package/docs/decisions/0004-reconsider-ui-direction.md +220 -0
  6. package/docs/plans/0422-delete-activity-tracker-ui-observer.md +204 -0
  7. package/docs/plans/0423-widget-self-drive-from-lifecycle.md +264 -0
  8. package/docs/plans/0424-drop-widget-dep-from-subagent-tool.md +166 -0
  9. package/docs/plans/0425-reconcile-subagent-events-contract.md +144 -0
  10. package/docs/plans/0426-consolidate-residual-test-clone-families.md +235 -0
  11. package/docs/plans/0427-reconsider-ui-direction.md +212 -0
  12. package/docs/plans/0446-spike-session-navigation-entry-criteria.md +171 -0
  13. package/docs/plans/0448-block-disabled-agent-spawn.md +207 -0
  14. package/docs/retro/0421-migrate-activity-readers-to-record-getters.md +49 -0
  15. package/docs/retro/0422-delete-activity-tracker-ui-observer.md +97 -0
  16. package/docs/retro/0423-widget-self-drive-from-lifecycle.md +112 -0
  17. package/docs/retro/0424-drop-widget-dep-from-subagent-tool.md +89 -0
  18. package/docs/retro/0425-reconcile-subagent-events-contract.md +101 -0
  19. package/docs/retro/0426-consolidate-residual-test-clone-families.md +100 -0
  20. package/docs/retro/0427-reconsider-ui-direction.md +102 -0
  21. package/docs/retro/0446-spike-session-navigation-entry-criteria.md +99 -0
  22. package/docs/retro/0448-block-disabled-agent-spawn.md +42 -0
  23. package/package.json +1 -1
  24. package/src/index.ts +11 -3
  25. package/src/observation/composite-subagent-observer.ts +49 -0
  26. package/src/runtime.ts +0 -6
  27. package/src/service/service.ts +4 -1
  28. package/src/tools/agent-tool.ts +1 -36
  29. package/src/tools/background-spawner.ts +1 -25
  30. package/src/tools/foreground-runner.ts +1 -30
  31. package/src/tools/helpers.ts +3 -2
  32. package/src/tools/spawn-config.ts +6 -0
  33. package/src/types.ts +1 -1
  34. package/src/ui/agent-widget.ts +34 -14
  35. package/src/ui/agent-activity-tracker.ts +0 -84
  36. package/src/ui/ui-observer.ts +0 -61
@@ -0,0 +1,101 @@
1
+ ---
2
+ issue: 425
3
+ issue_title: "pi-subagents: reconcile the public SUBAGENT_EVENTS contract with emitted channels"
4
+ ---
5
+
6
+ # Retro: #425 — pi-subagents: reconcile the public SUBAGENT_EVENTS contract with emitted channels
7
+
8
+ ## Stage: Planning (2026-06-18T00:00:00Z)
9
+
10
+ ### Session summary
11
+
12
+ Planned Phase 18 Step 6: reconcile the public `SUBAGENT_EVENTS` constant map in `src/service/service.ts` with the agent-lifecycle channels the core actually emits.
13
+ The plan removes the vacant `ACTIVITY` constant (breaking) and adds `FAILED`, `COMPACTED`, `CREATED`, and `STEERED`, then updates the architecture doc's lifecycle-events table.
14
+ Two TDD steps: a `feat!:` constant-map reconciliation pinned from both sides (declaration test + existing emission tests), and a `docs:` table update.
15
+
16
+ ### Observations
17
+
18
+ - This is the operator's own issue (author `gotgenes` matches the gh CLI user), so the proposed change was treated as the working hypothesis.
19
+ - Two genuine design choices were surfaced via `ask_user` and resolved: (1) remove `ACTIVITY` rather than emit a real broadcast — the activity tier was already deleted in Phase 18 Steps 1–3, so there is no streaming-progress source; (2) declare all four emitted agent-lifecycle channels including `subagents:steered` (from `steer-tool.ts`), not just the three named in the issue body, so declared == emitted is fully true for the lifecycle bus.
20
+ - Classified as **breaking**: removing a key from the exported `SUBAGENT_EVENTS` `as const` map breaks any consumer referencing `SUBAGENT_EVENTS.ACTIVITY` at the type level.
21
+ Plan uses `feat!:` with a `BREAKING CHANGE:` footer; the footer notes there is no replacement for `ACTIVITY`.
22
+ - Scope boundaries decided: config-domain events (`subagents:settings_loaded`/`settings_changed`) and the child-session seam events (`subagents:child:*`) stay out of `SUBAGENT_EVENTS` — separate domains with their own constant homes. `subagents:record` is an `appendEntry`, not a `pi.events.emit`, so it is not a channel constant.
23
+ - Corrected a stale doc artifact found during planning: the architecture lifecycle-events table listed `subagents:completed` as `{ id, type, status, result?, error? }`, but `buildEventData` emits `{ id, type, description, result, error, status, toolUses, durationMs, tokens? }`.
24
+ The plan fixes this in the same doc step.
25
+ - Public-surface gate: the plan requires running `verify:public-types` in the code step before committing, since `SUBAGENT_EVENTS` is rolled into `dist/public.d.ts`.
26
+ - Value-only reconciliation — no new collaborator, no dependency-wiring change — so the `design-review` checklist surfaces nothing actionable; noted in the plan rather than run as a separate gate.
27
+ - Next step: `/tdd-plan` (the change has a red→green test cycle).
28
+
29
+ ## Stage: Implementation — TDD (2026-06-18T00:00:00Z)
30
+
31
+ ### Session summary
32
+
33
+ Executed both planned TDD steps in two commits: a `feat!:` reconciling the `SUBAGENT_EVENTS` constant map (removed `ACTIVITY`, added `FAILED`/`COMPACTED`/`CREATED`/`STEERED`) pinned by an expanded `service.test.ts` assertion plus an explicit "no vacant `ACTIVITY`" check, then a `docs:` update to the lifecycle-events table and the Phase 18 Step 6 roadmap entry.
34
+ Full suite green at 1038 tests (+1 from the planning baseline of 1037); `check`, root `lint`, `verify:public-types`, and `fallow dead-code` all pass.
35
+
36
+ ### Observations
37
+
38
+ - No deviations from the plan's design.
39
+ The only mid-stream addition was the extra `"ACTIVITY" in SUBAGENT_EVENTS` falsity assertion, which strengthens the breaking-removal coverage beyond what the plan sketched.
40
+ - The breaking change went smoothly: `SUBAGENT_EVENTS.ACTIVITY` had no live consumers, so removal broke only the one service test that asserted it — folded into the same `feat!:` step as planned.
41
+ - Ran `verify:public-types` before committing Step 1 (public-surface gate); the rolled `dist/public.d.ts` regenerated cleanly with the narrowed `as const` literal types.
42
+ - `git diff` since the last tag (`pi-subagents-v16.6.0`) lists files from prior unreleased issues #422/#423/#424; scoped the pre-completion reviewer to #425's two commits to avoid noise.
43
+ - Pre-completion reviewer: WARN (1 non-blocking finding).
44
+ The Phase 18 Mermaid node `S6` was missing the `✅` mark carried by completed nodes S1–S5; fixed by appending `✅` to the node label and amended into the unpushed `docs:` commit.
45
+ All other checklist items PASS or SKIP (no acceptance-criteria section; `service.ts` was not a target of any prior Phase 18 step, so no cross-step invariant at risk).
46
+ - Next step: `/ship-issue`.
47
+
48
+ ## Stage: Final Retrospective (2026-06-18T12:00:00Z)
49
+
50
+ ### Session summary
51
+
52
+ A single continuous session carried issue #425 from planning through ship in four stages: planned the `SUBAGENT_EVENTS` reconciliation, executed two TDD commits (a `feat!:` constant-map change and a `docs:` table update), passed a WARN-then-resolved pre-completion review, and shipped `pi-subagents-v17.0.0` (major bump from the breaking `ACTIVITY` removal).
53
+ The one notable bump was at ship time: the batch-vs-release question led the user to choose batch, then immediately reverse course ("I have regrets.
54
+ Release it.").
55
+
56
+ ### Observations
57
+
58
+ #### What went well
59
+
60
+ - The planning `ask_user` gate caught a real scope gap: the issue body named three missing channels (`failed`/`compacted`/`created`), but exploration of `steer-tool.ts` found a fourth emitted-but-undeclared channel (`subagents:steered`).
61
+ Surfacing it as a design choice let the operator opt into a fully reconciled `declared == emitted` set rather than shipping the issue's literal-but-incomplete list.
62
+ - The invariant was pinned from both sides — `service.test.ts` for declaration, the existing observer/steer-tool tests for emission — so "declared channels equal emitted channels" is enforced by the suite, not just prose.
63
+ - The pre-completion reviewer caught a genuine inconsistency the deterministic gates missed: the Phase 18 Mermaid node `S6` lacked the `✅` mark that completed nodes `S1`–`S5` carried.
64
+ A one-character fix, amended cleanly into the unpushed `docs:` commit.
65
+
66
+ #### What caused friction (agent side)
67
+
68
+ - `missing-context` (self-identified at retro) — at ship step 4b, the batch-vs-release question fired on the plan's "Phase 18 Step 6 / phased roadmap" framing, but both the issue body ("Independent of the disentanglement spine — can land at any time") and the planning retro recorded an explicit independent-releasability signal.
69
+ The `ask_user` call presented batch and release as neutral options without surfacing that counter-signal, so the user chose batch and then reversed ("I have regrets.
70
+ Release it.").
71
+ Impact: one wasted `ask_user` round-trip, a cancelled `ci_watch`, and a mid-flow correction before the normal release path resumed; no commits had to be redone.
72
+ - `wrong-abstraction` (self-identified) — after the user cancelled the `ci_watch`, I read "cancelled by user" as the GitHub run being cancelled and tried `gh run rerun`, which failed ("already running").
73
+ Cancelling the watch tool aborts the poll, not the remote run, which was still in progress.
74
+ Impact: 1 wasted tool call; recovered immediately by re-listing and re-watching.
75
+
76
+ #### What caused friction (user side)
77
+
78
+ - The independent-releasability of this step was knowable from the issue body but only surfaced as a reversal after the batch choice.
79
+ Opportunity, not criticism: when an issue is explicitly carved out as independently shippable, stating "release immediately" up front (or the prompt biasing toward it) avoids the round-trip.
80
+
81
+ ### Diagnostic details
82
+
83
+ - **Model-performance correlation** — the only subagent dispatch was the `pre-completion-reviewer` on `anthropic/claude-sonnet-4-6` (219s, 37 tool uses); a strong model on judgment-heavy review work — appropriate, no mismatch.
84
+ - **Escalation-delay tracking** — no rabbit-hole exceeded 5 consecutive tool calls; the `gh run rerun` misstep was a single call, recovered next turn.
85
+ - **Unused-tool detection** — none; planning exploration (`Read`/`grep`/`colgrep`) and TDD verification covered the needed context without a missed Explore/`colgrep` opportunity.
86
+ - **Feedback-loop gap analysis** — verification ran incrementally, not just at the end: green-baseline `check`/`lint`/test before TDD, `verify:public-types` before committing the public-surface change in step 1, and the full suite + `check` + root `lint` + `fallow` after step 2.
87
+
88
+ ### Follow-up
89
+
90
+ The ship-time batch friction prompted a larger design than a retro should land inline: make release batching plan-driven rather than an ad-hoc step-4b question.
91
+ The architecture doc would annotate coherent **release batches** per phase (guidance for the `improvement-discovery` skill / `plan-improvements.md`), `/plan-issue` would read those annotations and write a prominent Release recommendation into the plan, and `/ship-issue` would read that recommendation and confirm with the user **early** (before pull/CI) instead of inferring from prose mid-flow.
92
+ Filed as [#434] for a later `/plan-issue`.
93
+
94
+ ### Changes made
95
+
96
+ 1. Applied a one-sentence carve-out to `.pi/prompts/ship-issue.md` step 4b (skip the batch question when a step is explicitly independently releasable), then **reverted** it after discussion — the file is unchanged.
97
+ The structured plan-driven approach in [#434] supersedes it.
98
+ 2. Created [#434] (`enhancement`) capturing the plan-driven release-batching design across the architecture-doc authoring guidance, `/plan-issue`, and `/ship-issue`.
99
+ 3. Appended this Final Retrospective entry to the retro file.
100
+
101
+ [#434]: https://github.com/gotgenes/pi-packages/issues/434
@@ -0,0 +1,100 @@
1
+ ---
2
+ issue: 426
3
+ issue_title: "pi-subagents: consolidate residual test clone families"
4
+ ---
5
+
6
+ # Retro: #426 — pi-subagents: consolidate residual test clone families
7
+
8
+ ## Stage: Planning (2026-06-18T17:48:42Z)
9
+
10
+ ### Session summary
11
+
12
+ Produced a numbered plan (`docs/plans/0426-consolidate-residual-test-clone-families.md`) for consolidating the residual test clone families in four named `pi-subagents` test files.
13
+ Ran `fallow dupes` to enumerate the live clone groups: 24 test clone groups package-wide, of which the four target files (`test/settings.test.ts` + `test/layered-settings.test.ts`, `test/lifecycle/create-subagent-session.test.ts`, `test/ui/agent-config-editor.test.ts`) contribute exactly 10 — consolidating them lands at 14, below the issue's `<15` target.
14
+ The plan is a test-only refactor verified by the existing suite staying green plus a falling fallow clone count; the follow-up is `/build-plan` (no red→green behavior cycles).
15
+
16
+ ### Observations
17
+
18
+ - Release: ship independently — the Phase 18 roadmap step 7 carries no `Release: batch` tag and is explicitly "independent of the disentanglement spine."
19
+ - The `testing` skill's rule "do not wrap the system-under-test call in a helper to eliminate a duplication-metric clone" drove the design: consolidate *arrange* only, keep every `loadSettings`/`createSubagentSession`/`showAgentDetail` act written out.
20
+ For `agent-config-editor` menu cases, `it.each` is the right tool — it keeps the act visible in the table body rather than hiding it in a helper.
21
+ - Three independent consolidations: (1) a new shared `test/helpers/tmp-settings-dirs.ts` fixture for the cross-file settings tmp-dir scaffolding (with paired self-test, per the `test/helpers/*.test.ts` convention); (2) file-local `arrangeFactory`/`defaultDeps` for the `create-subagent-session` post-bind-guard block; (3) `it.each` table + hoisted `filePath` for `agent-config-editor`.
22
+ - Scope deliberately excludes: the production clone in `src/ui/agent-config-editor.ts` (test-only issue), the cross-package `vitest.config.ts` clone, the `5d8dbd48` group spanning `subagent-manager.test.ts` ↔ `subagent.test.ts` (neither is a target file), and all other non-target test clone families.
23
+ - Key risk flagged for build: removing inline `writeGlobal`/`writeProject` may orphan fs imports (`mkdtempSync`/`rmSync`/`mkdirSync`/`writeFileSync`/`tmpdir`/`join`); Biome `noUnusedImports` is warning-level, so step 4 runs `pnpm fallow dead-code` as the backstop.
24
+ - Markdown lint gotcha hit during planning: bare `#N` inline mentions are fine, but reference-style `[#N]` link defs trip `MD053` unless a matching bracket reference exists in the body — kept only the `[#427]` cross-link def.
25
+
26
+ ## Stage: Implementation — Build (2026-06-18T18:14:01Z)
27
+
28
+ ### Session summary
29
+
30
+ Executed all four refactor steps plus the architecture doc-flip across four commits.
31
+ Extracted two shared test fixtures (`test/helpers/tmp-settings-dirs.ts`, `test/helpers/capture-warn.ts`, each with a paired self-test) and table-drove the `create-subagent-session` post-bind and `agent-config-editor` menu/confirm-remove cases into `it.each`.
32
+ Dropped pi-subagents test clone groups from 24 to 14 (below the `<15` target); full suite green at 1047 tests (was 1038), type check and lint clean, `fallow dead-code` clean.
33
+
34
+ ### Observations
35
+
36
+ - Pre-completion reviewer: PASS (deterministic checks, assertion-strength preservation, act-visibility, and no-coverage-drop all verified).
37
+ - Deviation 1 — the settings fixture exposes a `dispose()` method instead of the plan's separate `disposeSettingsDirs()` function (Tell-Don't-Ask; the fixture disposes itself).
38
+ - Deviation 2 — added `test/helpers/capture-warn.ts` (`captureWarn`) beyond the plan's tmp-dir-only fixture.
39
+ The plan's Step 1 verify listed clone group `4003c0e7` (the `console.warn` spy try/finally boilerplate) as expected-gone, but the tmp-dir fixture alone did not address it; the warn-capture helper does, and migrating the spy tests in both files cleared it.
40
+ Squarely within the issue's "extract shared fixtures for the clone families" intent.
41
+ - Deviation 3 — Step 2's first pass (arrange helpers only) left a transient arrange+assert clone (`62899223`) between the two adjacent post-bind membership tests; folding the three membership cases into one `it.each` with a strong `toEqual` (replacing the prior `toContain` checks) cleared it.
42
+ - Used destructure-to-locals in `settings.test.ts`/`layered-settings.test.ts` (e.g. `({ globalDir: agentDir, projectDir: cwd, ... } = dirs)`) rather than the plan's `dirs.X` member-access sketch — keeps the existing terse test bodies unchanged and lowers edit risk.
43
+ - Dropped one brittle `captureWarn` self-test ("suppresses real stderr") that was actually exercising `vi.spyOn`'s restore-to-original semantics with nested spies, not the helper's behavior.
44
+ - The risk flagged at planning (orphaned `node:fs`/`node:os` imports after removing inline `writeGlobal`/`writeProject`) materialized only in `layered-settings.test.ts` (`mkdtempSync`/`rmSync`/`tmpdir`/`vi` became unused); removed them, and `fallow dead-code` confirmed clean.
45
+
46
+ ## Stage: Final Retrospective (2026-06-18T18:31:04Z)
47
+
48
+ ### Session summary
49
+
50
+ Planned, built, and shipped #426 in one continuous session: extracted two shared test fixtures (`tmp-settings-dirs.ts`, `capture-warn.ts`) and table-drove four clone families into `it.each`, dropping pi-subagents test clone groups from 24 to 14.
51
+ CI green, issue closed, no release cut (all `test:`/`docs:` commits batch until the next `feat`/`fix`).
52
+ Execution was clean overall; the one recurring friction was the plan over-predicting which clone fingerprints each change would clear.
53
+
54
+ ### Observations
55
+
56
+ #### What went well
57
+
58
+ - **Metric-driven feedback loop.**
59
+ Each build step re-ran `pnpm fallow dupes` and checked the targeted fingerprints (`21d1fb01`, `4003c0e7`, `48ff1484`, …) disappeared before committing.
60
+ This caught the two prediction gaps (below) immediately rather than at pre-completion, and turned a fuzzy "reduce duplication" goal into a precise per-step pass/fail.
61
+ - **`it.each` strengthened assertions while consolidating.**
62
+ Folding the `create-subagent-session` post-bind cases into a table let the merged test assert `toEqual(expected)` on the full post-bind set, replacing the prior weaker `toContain` checks — consolidation improved coverage rather than just moving lines.
63
+ - **Pre-completion reviewer verified the real risk.**
64
+ For a test-refactor the central risk is silent coverage loss; the reviewer explicitly confirmed former-`it`-count equals row-count for every `it.each` and that each act stayed explicit.
65
+ PASS with nothing to fix.
66
+
67
+ #### What caused friction (agent side)
68
+
69
+ - `missing-context` (planning) — the plan's Step 1 *verify* listed clone group `4003c0e7` (the `console.warn` spy try/finally boilerplate) as expected-gone, but the Step 1 *design* only described the tmp-dir fixture, which does not touch that clone.
70
+ The build discovered the gap and added `capture-warn.ts` + migrated ~7 spy tests mid-step.
71
+ Impact: one unplanned helper (with self-test) folded into the same commit; no follow-up commit, but the plan-vs-build mismatch was real.
72
+ - `wrong-abstraction` (build, self-corrected) — Step 2's first pass applied arrange helpers (`arrangeFactory`/`defaultDeps`) alone, which left a *new* transient clone (`62899223`) between the two adjacent post-bind tests.
73
+ Folding the three membership cases into one `it.each` cleared it.
74
+ Impact: one extra edit+verify iteration within the step; caught by the same-step `fallow dupes` run.
75
+ - `other` (build, trivial) — wrote a brittle `captureWarn` self-test ("suppresses real stderr") that asserted `vi.spyOn` nested-restore semantics rather than the helper's behavior; the test failed immediately and was dropped.
76
+ Impact: ~2 tool calls, no rework downstream.
77
+ - `other` (ship, trivial) — `git log | grep -P` failed on macOS BSD grep; retried with `grep -o`.
78
+ Impact: one retry.
79
+
80
+ #### What caused friction (user side)
81
+
82
+ - None.
83
+ The session ran end-to-end (plan → build → ship → retro) without a mid-course correction; the prompts carried enough structure that no strategic intervention was needed.
84
+
85
+ ### Diagnostic details
86
+
87
+ - **Model-performance correlation** — the entire `/ship-issue` stage ran on `opencode-go/deepseek-v4-flash` (a reasoning-weak model), covering judgment-bearing steps: release-coordination decision, stacked-release detection, and close-comment synthesis.
88
+ It executed correctly because `/ship-issue` is heavily proceduralized (deterministic checks, explicit decision tree), so the weak model followed the rails without error.
89
+ Planning/build ran on stronger models (`opus-4-8`/`sonnet-4-6`); retro on `opus-4-8`.
90
+ No quality defect resulted, but ship-issue does carry real judgment — worth noting that its proceduralization is what absorbed the model mismatch.
91
+ - **Escalation-delay tracking** — no `rabbit-hole`; the two prediction-gap fixes resolved in 1–2 tool calls each via the per-step `fallow dupes` check.
92
+ No sequence exceeded the 5-call flag.
93
+ - **Unused-tool detection** — none needed; scope was small and fully understood from the planning-stage file reads. `fallow dupes` was the right and sufficient tool.
94
+ - **Feedback-loop gap analysis** — verification ran *incrementally*: `pnpm run check` + `vitest run <file>` + `pnpm fallow dupes` after every step, full suite + `fallow dead-code` at the end.
95
+ No end-only verification gap.
96
+
97
+ ### Changes made
98
+
99
+ 1. `.pi/prompts/plan-issue.md` — added a cross-section consistency rule under **Module-Level Changes**: when a plan step's verify criterion names a static-analysis finding (clone fingerprint, dead-code symbol, complexity target) as resolved, the design or Module-Level Changes must map the change that clears it.
100
+ Motivated by the Step 1 `4003c0e7` prediction gap (the verify step listed it as expected-gone but the design did not address it, forcing the mid-build `capture-warn.ts` addition).
@@ -0,0 +1,102 @@
1
+ ---
2
+ issue: 427
3
+ issue_title: "pi-subagents: reconsider the UI direction from first principles (ADR)"
4
+ ---
5
+
6
+ # Retro: #427 — pi-subagents: reconsider the UI direction from first principles (ADR)
7
+
8
+ ## Stage: Planning (2026-06-18T00:00:00Z)
9
+
10
+ ### Session summary
11
+
12
+ Planned the Phase 18 Step 8 decision-only ADR for the pi-subagents UI direction.
13
+ Two `ask_user` rounds with the operator (their own issue) settled a per-component decision and surfaced a key SDK finding — Pi's `switchSession(sessionPath)` — that reshapes the conversation-viewer direction.
14
+ The plan writes `docs/decisions/0004-reconsider-ui-direction.md` plus an architecture-doc update; no `src/`/`test/` changes (implementation deferred to a separately-planned Phase 19).
15
+
16
+ ### Observations
17
+
18
+ - **Decision-only ADR → `/build-plan`, not `/tdd-plan`.**
19
+ The operator chose to record decisions and defer all code to Phase 19, so the plan has a docs-only Build Order, no test cycles.
20
+ - **Per-component decisions recorded:**
21
+ (A) foreground widget shrinks to background-agents-only;
22
+ (B) conversation viewer replaced by native session navigation (remove the bespoke `ConversationViewer`);
23
+ (C) `/agents` menu dissolved — **remove** both agent-management surfaces outright (creation wizard and agent-types config editor; managing definitions belongs in an editor/IDE or a Pi agent, not the menu), re-home running-agent visibility onto the widget + session navigation, extract settings to a focused `/subagents:settings` command;
24
+ (D) distribution = keep surviving UI in-core (substitutable, _not_ extracted to `@gotgenes/pi-subagents-ui`).
25
+ - **Key SDK finding — `switchSession`.**
26
+ `@earendil-works/pi-coding-agent@0.79.1` exposes `ExtensionActions.switchSession(sessionPath, { withSession })`.
27
+ It is a _full active-session takeover_ (fires `session_before_switch`/`session_shutdown`, invalidates the current context), and the switched-to session is interactive (`ReplacedSessionContext.sendUserMessage`).
28
+ A read-only alternative exists: `loadEntriesFromFile`/`parseSessionEntries` render a transcript without switching.
29
+ These tensions are recorded as Phase 19 spike gates rather than pretend-resolved — the ADR commits to the _direction_ (native session machinery over a bespoke renderer), not the _mechanism_.
30
+ - **Operator-raised open questions (now Phase 19 entry criteria):** root-continuity during a session switch, view-only vs interactive, parallel-agent navigation gesture, settings command namespace, and confirming the creation-wizard's value is covered by "generate via a Pi agent" before deleting it.
31
+ - **Release:** ship independently — Phase 18 carries no `Release:` batch tag; this issue completes the phase.
32
+ - **Numbering:** plan `0427`, ADR `0004` (next free in `docs/decisions/`).
33
+
34
+ ## Stage: Implementation — Build (2026-06-18T20:05:00Z)
35
+
36
+ ### Session summary
37
+
38
+ Executed the decision-only ADR plan in two docs steps: wrote `docs/decisions/0004-reconsider-ui-direction.md` (per-component decisions A–D plus Phase 19 entry criteria) and updated `docs/architecture/architecture.md` (Step 8 + phase row marked `✅` complete, `S8` Mermaid node `✅`, ADR-0004 Landed line gateway-ing Phase 19).
39
+ No `src/`/`test/`/`.ts` files were touched, so the type-check and suite were correctly skipped; `pnpm run lint` is green.
40
+
41
+ ### Observations
42
+
43
+ - **Decision-only ADR held to scope:** four docs files total (ADR, arch doc, plan, retro); zero runtime change, matching the plan's Non-Goals.
44
+ - **Pre-completion reviewer: PASS.**
45
+ One non-blocking WARN — architecture design-principle #5 still read "UI extraction is deferred … first candidate for extraction," which ADR-0004's Decision D now contradicts.
46
+ - **Reviewer warning addressed in-session:** rewrote principle #5 to "UI is an in-core, substitutable consumer" pointing at ADR-0004 (commit `1c445ed4`), rather than deferring it to Phase 19 — it lived in the same doc and directly conflicted with the just-landed ADR.
47
+ - **Lint gotcha:** the relative ADR link from `docs/architecture/` needs `../decisions/…` (the Step 8 Landed line already had it); an initial `decisions/…` tripped `MD057` and was fixed by amend.
48
+ Also note `pnpm … lint | tail -N` masks the pipeline exit status — check `PIPESTATUS`/run lint unpiped to gate `&&` chains.
49
+ - **Commit count:** 4 build/doc commits for this stage (`17b0546a`, `7b1d9316`, `1c445ed4` for the ADR + arch doc; planning commits `12e7814a`/`e4895548`/`f1e65a14` predate this stage).
50
+
51
+ ## Stage: Final Retrospective (2026-06-18T21:00:00Z)
52
+
53
+ ### Session summary
54
+
55
+ Shipped issue #427 — a decision-only ADR (`docs/decisions/0004-reconsider-ui-direction.md`) completing Phase 18 of the `pi-subagents` roadmap — across planning, build, and ship stages in one session.
56
+ The whole arc held to docs-only scope: four docs files, zero runtime change, all `docs:` commits, so release-please correctly batched (no version cut).
57
+ CI passed; #427 closed with a per-component decision summary; siblings #425/#426/#434 were already closed.
58
+
59
+ ### Observations
60
+
61
+ #### What went well
62
+
63
+ - **Verified the SDK surface before recording it (novel).**
64
+ Rather than build the ADR on an assumed `switchSession` API, I read `@earendil-works/pi-coding-agent@0.79.1` `.d.ts` files directly in `node_modules` and confirmed the real contract — full active-session takeover, interactive `ReplacedSessionContext`, plus the `loadEntriesFromFile` read-only alternative.
65
+ This generalizes the AGENTS.md "verify the remediation exists in the real surface" rule (written for breaking-change migration notes) to an ADR's SDK claims, and it directly shaped Decision B's "direction not mechanism" framing.
66
+ - **Decision-only scope discipline.**
67
+ Held to four docs files matching the plan's Non-Goals; no `src/`/`test/` drift despite the ADR describing future code.
68
+ - **Pre-completion reviewer earned its keep on a docs-only change.**
69
+ It caught a stale design-principle (#5) that directly contradicted the just-landed ADR — a real inconsistency, not a code defect — showing the reviewer adds value beyond test/lint gates.
70
+ - **`ask_user` used well for a decision-heavy issue.**
71
+ Two structured rounds (per-component fates, then the `switchSession`-informed follow-up) let the operator drive the ADR's content; the second round was correctly gated on the first's SDK finding.
72
+
73
+ #### What caused friction (agent side)
74
+
75
+ - `other` (shell footgun) — gated a commit on a lint check piped through `tail`: `pnpm … lint 2>&1 | tail -3 && git add … && git commit`.
76
+ A pipeline's exit status is the last command (`tail`), so the lint failure was masked and a broken-link commit (`89ad57a3`, `MD057`) landed before I noticed.
77
+ Impact: one extra fix + `git commit --amend`; self-caught on the next unpiped lint run.
78
+ No bad push (the amend preceded `/ship-issue`).
79
+ - `other` (doc-link slip) — wrote a relative ADR link as `decisions/0004-…` from `docs/architecture/`, when the same session's earlier Step 8 Landed line already used the correct `../decisions/…`.
80
+ Impact: the `MD057` failure above; had the correct pattern in hand and didn't reuse it.
81
+ - `other` (malformed tool call) — one `Edit` call included a stray `newText_unused` property and was rejected; retried cleanly.
82
+ Impact: one wasted call, self-caught.
83
+
84
+ #### What caused friction (user side)
85
+
86
+ - None blocking.
87
+ The operator firmed the `/agents` decision (remove create+edit surfaces) one turn after the plan was first committed, costing a small follow-up commit (`f1e65a14`).
88
+ Opportunity, not criticism: the `ask_user` `/agents` option labeled "shrink — keep config management" may have under-signaled that outright removal was on the table; a crisper "remove agent-management surfaces" option could have surfaced the firm decision in the first round.
89
+
90
+ ### Diagnostic details
91
+
92
+ - **Model-performance correlation** — one subagent dispatch (`pre-completion-reviewer`) for judgment-heavy review; appropriate (per AGENTS.md its `model:` frontmatter must be a `provider/id` alias to actually run).
93
+ The main session's `model_change` log shows frequent toggling (`glm-5.2`, `opus-4-8`, `sonnet-4-6`, `deepseek-v4-flash`) but turns can't be cleanly attributed and nothing looks mismatched — not actionable.
94
+ - **Feedback-loop gap analysis** — `pnpm run lint` ran incrementally after each build step (good), not just at the end; baseline `check`+`lint` ran before any change.
95
+ The one gap was process, not timing: the `tail`-masked lint let a failure slip a commit (above).
96
+ - **Escalation-delay / unused-tool** — no rabbit-holes; longest same-error streak was 1–2 calls (the `MD057` fix).
97
+ Direct `node_modules` `.d.ts` inspection substituted well for `web_search`/`code_search` on the SDK contract — no missing-context gap.
98
+
99
+ ### Changes made
100
+
101
+ 1. `AGENTS.md` § Commits — added a two-line rule: don't gate a commit (or any `&&` step) on a check piped through `tail`/`head`, since the pipeline's exit status is the filter's and a failed `pnpm run lint`/`check` is masked; run the check unpiped or test `${PIPESTATUS[0]}`.
102
+ 2. `packages/pi-subagents/docs/retro/0427-reconsider-ui-direction.md` — appended this Final Retrospective stage entry.
@@ -0,0 +1,99 @@
1
+ ---
2
+ issue: 446
3
+ issue_title: "pi-subagents: spike — resolve ADR-0004 session-navigation entry criteria"
4
+ ---
5
+
6
+ # Retro: #446 — pi-subagents: spike — resolve ADR-0004 session-navigation entry criteria
7
+
8
+ ## Stage: Planning (2026-06-20T00:00:00Z)
9
+
10
+ ### Session summary
11
+
12
+ Planned the Phase 19 Step 1 spike that answers the four ADR-0004 session-navigation entry criteria and records them as an ADR-0004 addendum.
13
+ Confirmed the release is independent and that the only committed artifact is the addendum.
14
+ The plan lives at `packages/pi-subagents/docs/plans/0446-spike-session-navigation-entry-criteria.md`; next stage is `/build-plan` (docs/spike deliverable, no committed TDD cycles).
15
+
16
+ ### Observations
17
+
18
+ - Operator owns the issue (`gotgenes` == gh user), so the "Proposed change" is the working hypothesis.
19
+ Used `ask_user` once to resolve two method ambiguities: spike method = **automated observed test (vitest)**, committed artifact = **ADR addendum only** (the vitest harness is throwaway, discarded).
20
+ - Gathered the SDK evidence up front so the addendum's expected answers are grounded: `switchSession` is a full active-session takeover that tears down the current runtime via `session_shutdown` (so it threatens the root's in-flight turn); `ReplacedSessionContext` exposes `sendUserMessage` (switch makes the child interactive); `loadEntriesFromFile`/`parseSessionEntries` read entries without switching; `Subagent.outputFile` already exposes the child JSONL path; sibling commands use flat hyphenated names (`agents`, `colgrep-reindex`, `permission-system`).
21
+ - Expected recommendations the spike will confirm: read-only `loadEntriesFromFile` transcript (resolves root-continuity by construction), command-first parallel-agent selection (widget gesture deferred), and `/subagents-settings` (reject the ADR's tentative `/subagents:settings`).
22
+ - `setBeforeSessionInvalidate` is a **host** runtime seam (`agent-session-runtime`/`interactive-mode`), not on the extension command context — noted in Background so Step 4 does not assume the extension can call it.
23
+ - No production code changes and no invariants at risk; the read-only path was chosen partly to keep transcript rendering out of core (preserving the Phase 18 spine invariants from issues #422–#425).
24
+
25
+ ## Stage: Implementation — Build (2026-06-20T10:00:00Z)
26
+
27
+ ### Session summary
28
+
29
+ Executed the spike: ran a throwaway vitest harness against a real 43-entry child session JSONL, confirmed the read-only transcript path, then wrote the ADR-0004 addendum answering all four entry criteria.
30
+ Discarded the harness (operator decision: addendum only) and folded the architecture.md doc-sync into this build rather than deferring it.
31
+ Four `docs:` commits; pre-completion reviewer returned WARN, whose findings were then resolved.
32
+
33
+ ### Observations
34
+
35
+ - **Key divergence from the plan (Finding 0):** the plan's Design Overview assumed `loadEntriesFromFile(path)` would be the read mechanism, but it is **not part of the package's public surface** — it lives in the deep `core/session-manager` module (marked `/** Exported for testing */`) and the public barrel (`src/index.ts` → `dist/index.{d.ts,js}`) re-exports only a curated subset that includes `parseSessionEntries` but not `loadEntriesFromFile`; the `exports` map exposes only `"."`, so the deep import is unsupported too.
36
+ This is **not** a types/runtime mismatch — both barrels agree, and `tsc` rejects the import with `TS2305`.
37
+ My first harness reached a runtime `is not a function` only because Vitest/esbuild strips types without type-checking; `pnpm run check` (`tsc`) would have caught it at compile time.
38
+ My earlier "types/runtime mismatch" framing in the addendum/architecture was wrong and was corrected in a follow-up `docs:` commit.
39
+ Viable path: `parseSessionEntries(readFileSync(outputFile, "utf8"))` (`parseSessionEntries` is public).
40
+ - **Upgrade check (operator question):** verified the omission is **not** version-specific — the latest `0.79.8` barrel omits it identically to the pinned `0.79.1`, so an SDK bump does not surface `loadEntriesFromFile`.
41
+ No upgrade pursued (out of scope for a docs-only spike); noted the routine `0.79.1` → `0.79.8` freshness gap as a separate, unrelated item.
42
+ - **Doc-sync landed now, not deferred:** the reviewer flagged architecture.md line 997 ("Mechanism (confirmed by Step 1): `switchSession` … or `loadEntriesFromFile`") as actively contradicting the spike.
43
+ Since the spike now exists, I marked Step 1 ✅ (heading + Mermaid node `S1`), corrected the Phase 18 summary line, and rewrote the Step 4 mechanism line to `parseSessionEntries(readFileSync(...))` — closing the WARN.
44
+ - **Pre-completion reviewer: WARN** (no FAILs) — three architecture.md staleness findings, all addressed in the final `docs:` commit (`74e2374f`).
45
+ No `src/`/`test/` changes; `pnpm run check` + `pnpm run lint` + `pnpm fallow dead-code` all green at baseline and after.
46
+ - Release recommendation unchanged: **ship independently** (`Release: independent`).
47
+
48
+ ## Stage: Final Retrospective (2026-06-20T18:00:00Z)
49
+
50
+ ### Session summary
51
+
52
+ Planned, built, and shipped the Phase 19 Step 1 spike across three workflow stages, producing the ADR-0004 addendum that answers all four session-navigation entry criteria and unblocks #445.
53
+ The spike did its job — it rejected ADR-0004's literally-named `loadEntriesFromFile` mechanism before Step 4 coded against it — but the build stage committed an incorrect "types/runtime mismatch" characterization that took two user questions and a correction commit to fix.
54
+ Eight `docs:` commits landed; no release (all `docs:`, auto-batched).
55
+
56
+ ### Observations
57
+
58
+ #### What went well
59
+
60
+ - **The spike paid for itself, then over-delivered.**
61
+ It caught that ADR-0004's named candidate (`loadEntriesFromFile`) is not in the SDK's public surface — a finding about the *implementation*, not the harness — before Step 4 started.
62
+ The operator's three follow-up questions (upgrade?
63
+ describe-the-mismatch?
64
+ spike-vs-implementation impact?) then drove the addendum from a thin "use `parseSessionEntries`" note into a complete, link-backed render pipeline (Finding 1: `parseSessionEntries` → `buildSessionContext` → Pi's public entry components / `serializeConversation`).
65
+ Step 4 now starts from a verified, public-API-only design.
66
+ - **Clean, non-defensive self-correction.**
67
+ When the operator asked me to substantiate the claim with links (turn 74), running the actual `tsc` probe surfaced my own error; I corrected all three artifacts (addendum, `architecture.md`, retro) in commit `112c4254` without hedging.
68
+ "Ask the agent to cite/substantiate a claim" proved a high-leverage verification gesture.
69
+
70
+ #### What caused friction (agent side)
71
+
72
+ - `wrong-abstraction` (compounded by `missing-context`) — I diagnosed a question about the SDK's *export surface* from a *runtime* symptom.
73
+ The throwaway vitest harness threw `loadEntriesFromFile is not a function`, and I leapt to the dramatic reading ("declared in `.d.ts` but absent at runtime — a types/runtime mismatch") instead of running `tsc`, which disambiguates instantly (`TS2305: has no exported member`).
74
+ The symbol is simply not in the public barrel — types and runtime agree.
75
+ Impact: an incorrect technical claim shipped into three committed artifacts (addendum `7c505b78`, `architecture.md`, the build retro note) and required a 3-file correction commit (`112c4254`) plus two operator questions to surface and fix.
76
+ - `instruction-violation` (user-caught) — the `testing` skill already states "Vitest uses esbuild and does not typecheck; run `pnpm run check`."
77
+ I never ran `tsc` against the harness or the export claim during the build.
78
+ Two reasons it slipped: (1) the skill was not loaded — `/build-plan` only loads `testing` "if the plan involves test changes or TDD steps," and a docs-only spike using a throwaway harness does not obviously match; (2) the existing rule is framed for "type-only changes," not for *claims/findings about what a module exports*, so it would not clearly have fired even if loaded.
79
+ - `other` (tooling friction, minor) — getting the `tsc` probe to resolve the package took four attempts (turns 77–80: `npx` blocked → `pnpm exec` → files-on-cmdline tsconfig error → probe-inside-package).
80
+ Impact: added friction, no rework; not a conceptual rabbit-hole.
81
+
82
+ #### What caused friction (user side)
83
+
84
+ - None material — the operator's three interventions were strategic probes (dependency-freshness, claim substantiation, spike-vs-implementation scope) that each improved the artifact.
85
+ The only "earlier" opportunity is an agent-side gap: I should have self-applied the `tsc` check so the operator did not need to ask "describe the mismatch" to trigger verification.
86
+
87
+ ### Diagnostic details
88
+
89
+ - **Model-performance correlation** — Planning + Build + the correction work all ran on `anthropic/claude-opus-4-8` (turns 2–104); the mischaracterization therefore occurred on the *strong* model, so the fix is process (run `tsc` on export claims), not model selection.
90
+ The Ship stage (turns 106–122) ran on `opencode-go/deepseek-v4-flash` — appropriate cost-matching for a mechanical git/CI/close procedure, which it executed cleanly.
91
+ A transient `anthropic/claude-sonnet-4-6` `model_change` carried no assistant turn (never ran).
92
+ - **Escalation-delay tracking** — no rabbit-hole exceeded five consecutive tool calls on one error; the runtime `is not a function` was resolved in ~2 calls (the issue was the *interpretation*, not a stuck loop).
93
+ - **Unused-tool detection** — for the mischaracterization, `tsc` / `pnpm run check` was available and routine but applied only at baseline (turn 29) and not again until the operator forced it (turns 77–80); it was never run against the harness or the claim.
94
+ - **Feedback-loop gap analysis** — `pnpm run lint` ran after every doc edit (good incremental hygiene), but the *type-check* loop was skipped for precisely the finding that needed it — the harness ran under vitest (no typecheck) and `tsc` was not run on the export claim before committing it.
95
+
96
+ ### Changes made
97
+
98
+ 1. Extended the `testing` skill's `## Type checking` section to cover verifying claims about module exports with `tsc`, not runtime symptoms — `Refs #446`.
99
+ File: `.pi/skills/testing/SKILL.md`.
@@ -0,0 +1,42 @@
1
+ ---
2
+ issue: 448
3
+ issue_title: "`enabled: false` does not prevent explicitly spawning disabled agents"
4
+ ---
5
+
6
+ # Retro: #448 — `enabled: false` does not prevent explicitly spawning disabled agents
7
+
8
+ ## Stage: Planning (2026-06-20T00:00:00Z)
9
+
10
+ ### Session summary
11
+
12
+ Planned the fix for a third-party bug report: `enabled: false` agent overrides are hidden from the available-types list but still spawnable when named explicitly via `subagent_type`.
13
+ The plan adds a disabled-type gate in `resolveSpawnConfig` (returning an explicit error) and a `enabled` filter in `buildTypeListText`, both localized changes with no new collaborators or interface changes.
14
+
15
+ ### Observations
16
+
17
+ - Issue author (`nickadminroot`) is not the operator (`gotgenes`), so I ran the `ask-user` direction gate.
18
+ Operator confirmed: fix it, **return an explicit error** (`Agent type "<Name>" is disabled`) rather than the lenient fall-back-to-`general-purpose` alternative, and **include both fixes** (spawn path + tool-description list).
19
+ - Root cause is `resolveType` → `resolveKey` ignoring `enabled`; the registry already has `isValidType` (checks `enabled`) but it was unused on the spawn path.
20
+ The gate reuses `isValidType`, leaving `resolveType` / `resolveAgentConfig` untouched so UI consumers that intentionally resolve disabled configs keep working.
21
+ - Rejected changing `resolveType` or `resolveAgentConfig` directly — `agent-config-editor.ts` and `agent-menu.ts` rely on resolving disabled agents to display/edit/re-enable them.
22
+ - For the tool-description fix, chose to filter inside `buildTypeListText` rather than re-define `getDefaultAgentNames` / `getUserAgentNames` semantics; those two methods have `buildTypeListText` as their sole consumer (verified by grep), but keeping their meaning intact is cleaner.
23
+ - Classified as non-breaking `fix:` — the change aligns code with the documented README/registry contract; explicit spawning of a disabled agent was undocumented buggy behavior.
24
+ - Not in any architecture roadmap step (no `#448` reference in `docs/`), so **ship independently**.
25
+
26
+ ## Stage: Implementation — TDD (2026-06-20T12:40:00Z)
27
+
28
+ ### Session summary
29
+
30
+ Completed 2 TDD cycles and all post-step verification gates.
31
+ Step 1 added a 3-line enabled-type gate in `resolveSpawnConfig` (reusing `isValidType`) and 2 new test cases in `test/tools/spawn-config.test.ts`.
32
+ Step 2 added an `isEnabled` predicate filter in `buildTypeListText` and 2 new test cases in `test/tools/helpers.test.ts`.
33
+ Test count delta: 1047 → 1051 (+4).
34
+
35
+ ### Observations
36
+
37
+ - No deviations from the plan.
38
+ Both changes were as small as designed: 3 lines in `spawn-config.ts`, 2 lines in `helpers.ts`.
39
+ - Extended the `makeRegistry` stub's `resolve` type in `test/tools/helpers.test.ts` to include an optional `enabled` field, so the `isEnabled` predicate could be exercised without touching production code.
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
+ - All three plan-enumerated cross-step invariants held green throughout: `resolveAgentConfig` disabled-config behavior, unknown-type fallback, and `getAllTypes` disabled-agent listing.
42
+ - Pre-completion reviewer: **PASS** — all deterministic checks, code design, test artifacts, and cross-step invariants clean.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gotgenes/pi-subagents",
3
- "version": "16.6.0",
3
+ "version": "17.0.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
package/src/index.ts CHANGED
@@ -28,6 +28,7 @@ import { ConcurrencyLimiter } from "#src/lifecycle/concurrency-limiter";
28
28
  import { createSubagentSession, type SubagentSessionDeps } from "#src/lifecycle/create-subagent-session";
29
29
  import { buildParentSnapshot } from "#src/lifecycle/parent-snapshot";
30
30
  import { SubagentManager } from "#src/lifecycle/subagent-manager";
31
+ import { CompositeSubagentObserver } from "#src/observation/composite-subagent-observer";
31
32
  import { type NotificationDetails, NotificationManager } from "#src/observation/notification";
32
33
  import { createNotificationRenderer } from "#src/observation/renderer";
33
34
  import { SubagentEventsObserver } from "#src/observation/subagent-events-observer";
@@ -75,12 +76,18 @@ export default function (pi: ExtensionAPI) {
75
76
  settings.load();
76
77
 
77
78
  // Observer: receives agent lifecycle notifications and dispatches events/notifications.
78
- const observer = new SubagentEventsObserver({
79
+ const eventsObserver = new SubagentEventsObserver({
79
80
  emit: (channel, data) => pi.events.emit(channel, data),
80
81
  appendEntry: (customType, data) => pi.appendEntry(customType, data),
81
82
  notifications,
82
83
  });
83
84
 
85
+ // Fan-out observer: lets the widget subscribe as a second lifecycle consumer
86
+ // while the manager keeps its single-observer contract. The widget is added
87
+ // after construction (it needs the manager); the manager consults the observer
88
+ // only at spawn time, so registering late is safe.
89
+ const observer = new CompositeSubagentObserver([eventsObserver]);
90
+
84
91
  const subagentSessionDeps: SubagentSessionDeps = {
85
92
  io: {
86
93
  detectEnv,
@@ -128,8 +135,9 @@ export default function (pi: ExtensionAPI) {
128
135
  pi.on("session_shutdown", () => lifecycle.handleSessionShutdown());
129
136
 
130
137
  // Live widget: constructed after the manager (it polls listAgents()) and
131
- // injected directly into its consumers no post-construction field write.
138
+ // registered as a lifecycle observer so it self-drives its update timer.
132
139
  const widget = new AgentWidget(manager, registry);
140
+ observer.add(widget);
133
141
 
134
142
  // Grab UI context from first tool execution + clear lingering widget on new turn
135
143
  const toolStart = new ToolStartHandler(widget);
@@ -141,7 +149,7 @@ export default function (pi: ExtensionAPI) {
141
149
 
142
150
  // ---- Agent tool ----
143
151
 
144
- pi.registerTool(new AgentTool(manager, runtime, widget, settings, registry, getAgentDir()).toToolDefinition());
152
+ pi.registerTool(new AgentTool(manager, runtime, settings, registry, getAgentDir()).toToolDefinition());
145
153
 
146
154
  // ---- get_subagent_result tool ----
147
155
 
@@ -0,0 +1,49 @@
1
+ import { debugLog } from "#src/debug";
2
+ import type { SubagentManagerObserver } from "#src/lifecycle/subagent-manager";
3
+ import type { CompactionInfo, Subagent } from "#src/types";
4
+
5
+ /**
6
+ * Fans out SubagentManager lifecycle notifications to multiple observers.
7
+ *
8
+ * Lets the manager keep its single-observer contract while several independent
9
+ * consumers (event/notification dispatch, the reactive widget) subscribe.
10
+ * Each delegate is isolated: a throw in one does not suppress the others.
11
+ */
12
+ export class CompositeSubagentObserver implements SubagentManagerObserver {
13
+ private readonly delegates: SubagentManagerObserver[];
14
+
15
+ constructor(delegates: SubagentManagerObserver[]) {
16
+ this.delegates = [...delegates];
17
+ }
18
+
19
+ /** Register an additional observer (breaks the widget↔manager construction cycle). */
20
+ add(observer: SubagentManagerObserver): void {
21
+ this.delegates.push(observer);
22
+ }
23
+
24
+ onSubagentStarted(record: Subagent): void {
25
+ this.dispatch((o) => o.onSubagentStarted(record), "onSubagentStarted");
26
+ }
27
+
28
+ onSubagentCreated(record: Subagent): void {
29
+ this.dispatch((o) => o.onSubagentCreated(record), "onSubagentCreated");
30
+ }
31
+
32
+ onSubagentCompleted(record: Subagent): void {
33
+ this.dispatch((o) => o.onSubagentCompleted(record), "onSubagentCompleted");
34
+ }
35
+
36
+ onSubagentCompacted(record: Subagent, info: CompactionInfo): void {
37
+ this.dispatch((o) => o.onSubagentCompacted(record, info), "onSubagentCompacted");
38
+ }
39
+
40
+ private dispatch(call: (o: SubagentManagerObserver) => void, label: string): void {
41
+ for (const o of this.delegates) {
42
+ try {
43
+ call(o);
44
+ } catch (err) {
45
+ debugLog(`CompositeSubagentObserver.${label}`, err);
46
+ }
47
+ }
48
+ }
49
+ }
package/src/runtime.ts CHANGED
@@ -9,7 +9,6 @@
9
9
  import { buildParentSnapshot, type ParentSnapshot } from "#src/lifecycle/parent-snapshot";
10
10
  import type { ModelInfo } from "#src/tools/spawn-config";
11
11
  import type { SessionContext } from "#src/types";
12
- import type { AgentActivityTracker } from "#src/ui/agent-activity-tracker";
13
12
 
14
13
  /**
15
14
  * Narrow config subset read by Agent when driving the turn loop (defaultMaxTurns, graceTurns).
@@ -30,11 +29,6 @@ export class SubagentRuntime {
30
29
  // ── Session state (was closure-scoped in index.ts) ───────────────────────
31
30
  /** Active Pi session context — set on session_start, cleared on session_shutdown. */
32
31
  currentCtx: SessionContext | undefined = undefined;
33
- /**
34
- * Per-agent live activity state shared across the notification system,
35
- * widget, and tool handlers. The Map itself is never replaced.
36
- */
37
- readonly agentActivity: Map<string, AgentActivityTracker> = new Map();
38
32
 
39
33
  // ── Session-context methods ──────────────────────────────────────────────
40
34
 
@@ -97,7 +97,10 @@ export interface SubagentsService {
97
97
  export const SUBAGENT_EVENTS = {
98
98
  STARTED: "subagents:started",
99
99
  COMPLETED: "subagents:completed",
100
- ACTIVITY: "subagents:activity",
100
+ FAILED: "subagents:failed",
101
+ COMPACTED: "subagents:compacted",
102
+ CREATED: "subagents:created",
103
+ STEERED: "subagents:steered",
101
104
  } as const;
102
105
 
103
106
  // ---- Accessor functions ----