@gotgenes/pi-subagents 16.5.0 → 17.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/dist/public.d.ts +4 -1
- package/docs/architecture/architecture.md +39 -31
- package/docs/plans/0421-migrate-activity-readers-to-record-getters.md +309 -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/retro/0420-fold-run-metrics-live-activity-onto-record.md +45 -0
- package/docs/retro/0421-migrate-activity-readers-to-record-getters.md +105 -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 +46 -0
- package/package.json +1 -1
- package/src/index.ts +12 -6
- package/src/observation/composite-subagent-observer.ts +49 -0
- package/src/observation/notification.ts +3 -12
- package/src/observation/subagent-events-observer.ts +1 -4
- 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 +9 -34
- package/src/tools/helpers.ts +5 -4
- package/src/types.ts +1 -1
- package/src/ui/agent-menu.ts +0 -12
- package/src/ui/agent-widget.ts +56 -17
- package/src/ui/conversation-viewer.ts +4 -9
- package/src/ui/widget-renderer.ts +14 -22
- package/src/ui/agent-activity-tracker.ts +0 -84
- package/src/ui/ui-observer.ts +0 -61
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,24 @@ 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.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v16.6.0...pi-subagents-v17.0.0) (2026-06-18)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### ⚠ BREAKING CHANGES
|
|
12
|
+
|
|
13
|
+
* 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.
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
* reconcile SUBAGENT_EVENTS with emitted channels ([#425](https://github.com/gotgenes/pi-packages/issues/425)) ([ba0c48b](https://github.com/gotgenes/pi-packages/commit/ba0c48b294123c1498db2040b1037adcd7ad4e4e))
|
|
18
|
+
|
|
19
|
+
## [16.6.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v16.5.0...pi-subagents-v16.6.0) (2026-06-17)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### Features
|
|
23
|
+
|
|
24
|
+
* read widget activity off subagent records ([#421](https://github.com/gotgenes/pi-packages/issues/421)) ([df09e03](https://github.com/gotgenes/pi-packages/commit/df09e03d122c391212507c4c5d8436cd4a0561ba))
|
|
25
|
+
|
|
8
26
|
## [16.5.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v16.4.0...pi-subagents-v16.5.0) (2026-06-17)
|
|
9
27
|
|
|
10
28
|
|
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;
|
|
@@ -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
|
|
|
@@ -953,35 +954,42 @@ Step 8 is the user-driven UI reconsideration the disentangled core finally makes
|
|
|
953
954
|
The deeper target in the [first-principles refinement](#first-principles-refinement-and-the-deeper-target) — metrics as a pure observer projection rather than mutable fields — is deliberately **not** forced here.
|
|
954
955
|
Folding the live activity onto the record (the single owner of run state, consistent with Phase 17's `SubagentState`) removes the duplication without inventing the asynchronous-observation seam the `improvement-discovery` skill warns is essential, not structural.
|
|
955
956
|
|
|
956
|
-
1.
|
|
957
|
+
1. **✅ Fold run metrics and live activity onto the core record (pure addition) — complete (v16.5.0).**
|
|
957
958
|
([#420]) Target: `lifecycle/subagent-state.ts`, `observation/record-observer.ts`, `lifecycle/subagent.ts`.
|
|
958
959
|
Extend the single owned run-state value object with `turnCount`, active tools, and response text; have the already-subscribed `record-observer` handle `turn_end`, `tool_execution_start`, `message_start`, `message_update`; expose read-only `turnCount`/`maxTurns`/`activeTools`/`responseText` getters on `Subagent`.
|
|
959
960
|
`AgentActivityTracker` still exists; nothing reads the new getters yet (tidy-first).
|
|
960
961
|
Smell: Category C. Outcome: `Subagent` is the single home for all run state; getters available for migration.
|
|
961
|
-
|
|
962
|
+
Landed: `SubagentState` owns `turnCount`/`activeTools`/`responseText` plus their transition methods; `record-observer` populates them on a single subscription; `Subagent` exposes the four read-only getters; +27 tests (1031 → 1058).
|
|
963
|
+
2. **✅ Migrate every activity reader to the record getters — complete.**
|
|
962
964
|
([#421]) Target: `ui/widget-renderer.ts`, `ui/conversation-viewer.ts`, `ui/agent-menu.ts`, `tools/foreground-runner.ts`, `observation/notification.ts`.
|
|
963
965
|
Switch each reader from `AgentActivityTracker` to the record getters added in Step 1 (widget-renderer reads activity off `listAgents()`; viewer/menu drop the `activity` param; notification reads `turnCount`/`maxTurns` off the record).
|
|
964
966
|
Smell: Category C (Law of Demeter).
|
|
965
967
|
Outcome: no consumer references `AgentActivityTracker`.
|
|
966
|
-
|
|
968
|
+
Landed: `WidgetAgent` folds the live-activity fields and a `contextPercent` projection; `AgentWidget` projects records; `ConversationViewer`, `AgentsMenuHandler`, `buildDetails`, and `buildNotificationDetails` read off the record; `NotificationSystem.cleanupCompleted` removed; `SubagentEventsObserver` returns early on consumed results; +8 tests (1058 → 1066).
|
|
969
|
+
3. **✅ Delete `AgentActivityTracker` and `ui-observer`; drop the activity map from the runtime and spawn tools — complete.**
|
|
967
970
|
([#422]) Target: `ui/agent-activity-tracker.ts` (delete), `ui/ui-observer.ts` (delete), `runtime.ts`, `tools/foreground-runner.ts`, `tools/background-spawner.ts`.
|
|
968
971
|
The spawn tools stop constructing trackers, subscribing, and populating maps; `SubagentRuntime.agentActivity` is removed.
|
|
969
972
|
Smell: Category A + C. Outcome: −145 LOC, one session subscription per child, runtime holds zero UI state.
|
|
970
|
-
|
|
971
|
-
|
|
973
|
+
Landed: deleted `agent-activity-tracker.ts` (84) + `ui-observer.ts` (61) and their unit suites; dropped the `agentActivity` parameter from `runForeground`/`spawnBackground`, the `AgentActivityAccess` interface, and the `AgentToolRuntime`/`SubagentRuntime` activity-map fields; the foreground `observer.onSessionCreated` keeps `recordRef`/`fgId` binding and `widget.ensureTimer`; −34 tests (1066 → 1032).
|
|
974
|
+
4. **✅ Make the widget self-drive from lifecycle events — complete.**
|
|
975
|
+
([#423]) Target: `ui/agent-widget.ts`, `observation/composite-subagent-observer.ts` (new), `index.ts`.
|
|
972
976
|
The widget starts/stops its timer in response to started/created/completed notifications instead of tool calls; spawn tools no longer call `ensureTimer`/`update`/`markFinished`.
|
|
973
977
|
Smell: Category C (coupling direction).
|
|
974
978
|
Outcome: the widget is a reactive consumer; no inbound calls from core spawn tools.
|
|
975
|
-
|
|
979
|
+
Landed: `AgentWidget implements SubagentManagerObserver` (starts the 80 ms loop on `onSubagentStarted`/`onSubagentCreated`, re-renders on `onSubagentCompleted`/`onSubagentCompacted`); a new `CompositeSubagentObserver` fans the manager's single observer slot out to `[eventsObserver, widget]` (wired in `index.ts`, keeping `SubagentManager` unchanged); dropped the `widget` parameter and `ForegroundWidgetDeps`/`BackgroundWidgetDeps` interfaces from both spawners, deleted `AgentWidget.markFinished` (redundant with `seedFinishedAgents`), made `ensureTimer` private, and narrowed `AgentToolWidget` to `setUICtx`; +11 tests (1032 → 1043, then −4 with the dropped spawner widget-driving tests → 1039).
|
|
980
|
+
5. **✅ Drop the widget dependency from the `subagent` tool — complete.**
|
|
976
981
|
([#424]) Target: `tools/agent-tool.ts`, `test/helpers/make-deps.ts`.
|
|
977
|
-
`AgentTool` loses its `widget`
|
|
982
|
+
`AgentTool` loses its `widget` constructor param (UICtx capture stays in `ToolStartHandler`); `createToolDeps` sheds the widget stub.
|
|
983
|
+
The activity-map (`agentActivity`) dependency this step originally named was already removed from the tool and runtime in Step 3 ([#422]), so only `widget` remained to drop.
|
|
978
984
|
Smell: Category C/D.
|
|
979
|
-
Outcome: the LLM tool depends only on manager/runtime/settings/registry; fixture drops
|
|
980
|
-
|
|
985
|
+
Outcome: the LLM tool depends only on manager/runtime/settings/registry; fixture drops 1 field.
|
|
986
|
+
Landed: removed the `widget` constructor param and the redundant `this.widget.setUICtx(ctx.ui)` call from `AgentTool.execute`, deleted the `AgentToolWidget` interface and its `UICtx` import, updated the sole `index.ts` call site, and dropped the `widget` field/stub plus two now-obsolete tests (`agent-tool` UICtx capture, `make-deps` widget defaults) — UICtx capture is pinned by `handlers/tool-start.test.ts`; −2 tests (1039 → 1037).
|
|
987
|
+
6. **✅ Reconcile the public event contract — complete.**
|
|
981
988
|
([#425]) Target: `service/service.ts`, this document's lifecycle-events table.
|
|
982
989
|
Remove the vacant `ACTIVITY` channel (or emit a real broadcast for it) and add the emitted `failed`/`compacted`/`created` channels so declared constants match emitted events.
|
|
983
990
|
Smell: Category A/E.
|
|
984
991
|
Outcome: declared channels equal emitted channels; no vacant hook.
|
|
992
|
+
Landed: removed `SUBAGENT_EVENTS.ACTIVITY` (breaking) and added `FAILED`/`COMPACTED`/`CREATED`/`STEERED` — `subagents:steered` (from `steer-tool.ts`) was also emitted-but-undeclared, so all four emitted agent-lifecycle channels are now declared; corrected the stale `subagents:completed` payload in the lifecycle-events table to the real `buildEventData` shape; +1 test (1037 → 1038).
|
|
985
993
|
7. **Consolidate residual test clone families.**
|
|
986
994
|
([#426]) Target: `test/settings.test.ts` + `test/layered-settings.test.ts`, `test/lifecycle/create-subagent-session.test.ts`, `test/ui/agent-config-editor.test.ts`.
|
|
987
995
|
Extract shared fixtures for the clone families fallow reports that the spine does not already rewrite.
|
|
@@ -1006,12 +1014,12 @@ Folding the live activity onto the record (the single owner of run state, consis
|
|
|
1006
1014
|
|
|
1007
1015
|
```mermaid
|
|
1008
1016
|
flowchart TB
|
|
1009
|
-
S1["1 — Fold metrics + activity onto record (#420)"]
|
|
1010
|
-
S2["2 — Migrate readers to record getters (#421)"]
|
|
1011
|
-
S3["3 — Delete tracker + ui-observer, drop activity map (#422)"]
|
|
1012
|
-
S4["4 — Widget self-drives on events (#423)"]
|
|
1013
|
-
S5["5 — Drop widget dep from subagent tool (#424)"]
|
|
1014
|
-
S6["6 — Reconcile public event contract (#425)"]
|
|
1017
|
+
S1["1 — Fold metrics + activity onto record (#420) ✅"]
|
|
1018
|
+
S2["2 — Migrate readers to record getters (#421) ✅"]
|
|
1019
|
+
S3["3 — Delete tracker + ui-observer, drop activity map (#422) ✅"]
|
|
1020
|
+
S4["4 — Widget self-drives on events (#423) ✅"]
|
|
1021
|
+
S5["5 — Drop widget dep from subagent tool (#424) ✅"]
|
|
1022
|
+
S6["6 — Reconcile public event contract (#425) ✅"]
|
|
1015
1023
|
S7["7 — Consolidate test clone families (#426)"]
|
|
1016
1024
|
S8["8 — Reconsider UI direction, ADR (#427)"]
|
|
1017
1025
|
|
|
@@ -1058,7 +1066,7 @@ Detailed records are preserved in per-phase history files:
|
|
|
1058
1066
|
| 15 | Domain model evolution | Complete | [phase-15-domain-model-evolution.md](history/phase-15-domain-model-evolution.md) |
|
|
1059
1067
|
| 16 | Invert dependencies (extensions on a minimal core) | Complete | [phase-16-invert-dependencies.md](history/phase-16-invert-dependencies.md) |
|
|
1060
1068
|
| 17 | Core consolidation | Complete | [phase-17-core-consolidation.md](history/phase-17-core-consolidation.md) |
|
|
1061
|
-
| 18 | Reconsider UI (first principles) |
|
|
1069
|
+
| 18 | Reconsider UI (first principles) | In progress | this document, [Phase 18](#phase-18-reconsider-ui-from-first-principles) |
|
|
1062
1070
|
|
|
1063
1071
|
### Structural refactoring issues
|
|
1064
1072
|
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 421
|
|
3
|
+
issue_title: "pi-subagents: migrate activity-tracker readers to the record getters"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Migrate activity-tracker readers to the record getters
|
|
7
|
+
|
|
8
|
+
## Problem Statement
|
|
9
|
+
|
|
10
|
+
Phase 18 Step 1 ([#420]) folded the live-activity fields (`turnCount`, `activeTools`, `responseText`, `maxTurns`) onto the single owned run-state value object and exposed them as read-only getters on `Subagent`, but nothing reads them yet.
|
|
11
|
+
Five consumers still reach into a separate UI `AgentActivityTracker` for data the record now exposes directly — a Law-of-Demeter coupling that keeps the activity tier entangled with the core.
|
|
12
|
+
This step switches each reader from the tracker to the record getters so that no reader reads activity off `AgentActivityTracker`, unblocking the tracker's deletion in Step 3 ([#422]).
|
|
13
|
+
|
|
14
|
+
This is Phase 18 Step 2 of the architecture roadmap (`docs/architecture/architecture.md`).
|
|
15
|
+
It is a reader migration: the producer plumbing that still constructs trackers, subscribes `ui-observer`, and populates the runtime activity map (`foreground-runner`, `background-spawner`, `runtime.ts`, `ui-observer.ts`) is left in place and removed in Step 3.
|
|
16
|
+
The reader-held map dependencies in `notification`, `agent-widget`, and `agent-menu` — files Step 3 does not touch — are removed here.
|
|
17
|
+
|
|
18
|
+
## Goals
|
|
19
|
+
|
|
20
|
+
- Switch `widget-renderer` to read live activity off the `Subagent` records returned by `listAgents()` rather than a parallel `activityMap`, folding the `WidgetActivity` interface into `WidgetAgent`.
|
|
21
|
+
- Drop the `activity` constructor param from `ConversationViewer`; read `activeTools` / `responseText` off `this.record`.
|
|
22
|
+
- Drop the `AgentActivityReader` dependency from `AgentsMenuHandler`.
|
|
23
|
+
- Migrate `runForeground` and `buildDetails` to read streaming state (`turnCount`, `maxTurns`, `activeTools`, `responseText`) off the `Subagent` they already hold.
|
|
24
|
+
- Migrate `buildNotificationDetails` to read `turnCount` / `maxTurns` off the record, and drop the `agentActivity` map dependency (and the now-vestigial `cleanupCompleted` method) from `NotificationManager`.
|
|
25
|
+
- Project each `Subagent` into a pure-data `WidgetAgent` snapshot inside `AgentWidget` (with a `contextPercent` field), keeping `widget-renderer` a pure function of plain data.
|
|
26
|
+
|
|
27
|
+
This change is **not breaking**.
|
|
28
|
+
The published service surface (`src/service/service.ts`) exposes `SubagentRecord` / `SubagentStatus`, neither of which changes.
|
|
29
|
+
There is one accepted observable behavior change in the widget (below), but no API, config, or default changes on upgrade without a user edit.
|
|
30
|
+
|
|
31
|
+
### Accepted behavior change
|
|
32
|
+
|
|
33
|
+
Today the widget shows **no** turn count on finished-agent lines because the activity tracker is deleted on completion (foreground deletes it after `spawnAndWait`; `NotificationManager.sendCompletion` deletes it for background agents).
|
|
34
|
+
The record's `turnCount` getter persists, so once `widget-renderer` reads off `listAgents()` records, finished lines will show `⟳N`.
|
|
35
|
+
This is **accepted** (confirmed with the operator): the deletion-on-completion behavior was a UI-state artifact, and showing the turn count consistently is the more correct outcome.
|
|
36
|
+
Preserving the old behavior would require the widget to track which finished agents were "cleaned up" — reintroducing exactly the coupling this phase removes.
|
|
37
|
+
|
|
38
|
+
## Non-Goals
|
|
39
|
+
|
|
40
|
+
- Deleting `AgentActivityTracker` / `ui-observer`, or removing the producer plumbing in `foreground-runner` / `background-spawner` / `runtime.ts` that still constructs trackers, subscribes, and populates the activity map — Step 3 ([#422]).
|
|
41
|
+
- Dropping the `widget` and `agentActivity` constructor params from `AgentTool` — Step 5 ([#424]).
|
|
42
|
+
- Making the widget self-drive its timer from lifecycle events instead of spawn-tool calls — Step 4 ([#423]).
|
|
43
|
+
- Reconciling the public event contract (`SUBAGENT_EVENTS.ACTIVITY`) — Step 6 ([#425]).
|
|
44
|
+
- Changing `SubagentState`, `record-observer`, or the getters themselves — landed in Step 1 ([#420]).
|
|
45
|
+
|
|
46
|
+
## Background
|
|
47
|
+
|
|
48
|
+
Relevant modules and how they relate:
|
|
49
|
+
|
|
50
|
+
- `src/lifecycle/subagent.ts` — exposes the four read-only getters added in Step 1: `turnCount` and `activeTools` and `responseText` delegate to the owned `SubagentState`; `maxTurns` returns `this.execution.maxTurns`.
|
|
51
|
+
Also exposes `getContextPercent(): number | null` (delegates to `subagentSession.getContextPercent()`), the live context-window utilization.
|
|
52
|
+
- `src/ui/widget-renderer.ts` — pure rendering functions.
|
|
53
|
+
`renderFinishedLine` / `renderRunningLines` take a `WidgetAgent` plus a `WidgetActivity | undefined`; `renderWidgetLines` takes an `activityMap: ReadonlyMap<string, WidgetActivity>`.
|
|
54
|
+
`renderRunningLines` computes the context percent via `activity?.session ? getSessionContextPercent(activity.session) : null` — a reach into session stats from a module documented as taking no SDK types.
|
|
55
|
+
- `src/ui/agent-widget.ts` — `AgentWidget` holds the `agentActivity` map and passes `manager.listAgents()` (Subagents, structurally accepted as `WidgetAgent`) plus the map to `renderWidgetLines`.
|
|
56
|
+
- `src/ui/conversation-viewer.ts` — `ConversationViewer` takes an `activity: AgentActivityTracker | undefined`; reads `activity.activeTools` / `activity.responseText` in `buildContentLines` for the running streaming indicator.
|
|
57
|
+
- `src/ui/agent-menu.ts` — defines `AgentActivityReader` (`get(id): AgentActivityTracker | undefined`), takes it as a constructor param, and reads `this.agentActivity.get(record.id)` in `viewAgentConversation` to pass to `ConversationViewer`.
|
|
58
|
+
- `src/tools/foreground-runner.ts` — constructs `fgState = new AgentActivityTracker(...)`, subscribes `ui-observer`, populates the activity map, and reads `fgState.turnCount` / `fgState.maxTurns` / `fgState.activeTools` / `fgState.responseText` in `streamUpdate`; passes `fgState` to `buildDetails`.
|
|
59
|
+
- `src/tools/helpers.ts` — `buildDetails(base, record, activity?, overrides?)` reads `activity?.turnCount` and `activity?.maxTurns`.
|
|
60
|
+
Two call sites: `runForeground` (passes `fgState`) and `AgentTool` resume (no activity arg).
|
|
61
|
+
- `src/observation/notification.ts` — `buildNotificationDetails(record, resultMaxLen, activity?)` reads `activity?.turnCount` / `activity?.maxTurns`.
|
|
62
|
+
`NotificationManager` holds the `agentActivity` map and deletes from it in `sendCompletion` and `cleanupCompleted`.
|
|
63
|
+
- `src/observation/subagent-events-observer.ts` — `onSubagentCompleted` calls `notifications.cleanupCompleted(record.id)` when `record.notification?.resultConsumed`, else `notifications.sendCompletion(record)`.
|
|
64
|
+
- `src/index.ts` — constructs `NotificationManager`, `AgentWidget`, and `AgentsMenuHandler`, each currently passed `runtime.agentActivity`.
|
|
65
|
+
|
|
66
|
+
AGENTS.md / code-design constraints that apply:
|
|
67
|
+
|
|
68
|
+
- `widget-renderer.ts` is a pure rendering module ("stateless: they receive data and return formatted strings.
|
|
69
|
+
No timers, no SDK types, no side effects").
|
|
70
|
+
The migration keeps it pure and **narrows** it: the precomputed `contextPercent` field lets it drop the `SessionLike` / `getSessionContextPercent` imports, removing the session reach-through.
|
|
71
|
+
- `AgentWidget` is the adapter between the live manager/records and the pure renderer; the explicit `Subagent` → `WidgetAgent` projection belongs there (DIP, narrow data boundary).
|
|
72
|
+
- `getSessionContextPercent` and `SessionLike` stay in `usage.ts` — still used by `subagent-session.ts` and `getSessionTokens`, so removing the widget-renderer import creates no dead code.
|
|
73
|
+
|
|
74
|
+
## Design Overview
|
|
75
|
+
|
|
76
|
+
### `WidgetAgent` absorbs the activity fields (widget-renderer)
|
|
77
|
+
|
|
78
|
+
`WidgetActivity` is removed; its fields move onto `WidgetAgent` as plain data, plus a precomputed context percent:
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
export interface WidgetAgent {
|
|
82
|
+
readonly id: string;
|
|
83
|
+
readonly type: SubagentType;
|
|
84
|
+
readonly status: string;
|
|
85
|
+
readonly description: string;
|
|
86
|
+
readonly toolUses: number;
|
|
87
|
+
readonly startedAt: number;
|
|
88
|
+
readonly completedAt?: number;
|
|
89
|
+
readonly error?: string;
|
|
90
|
+
readonly lifetimeUsage?: Readonly<LifetimeUsage>;
|
|
91
|
+
readonly compactionCount: number;
|
|
92
|
+
// Folded-in live activity (was WidgetActivity)
|
|
93
|
+
readonly turnCount: number;
|
|
94
|
+
readonly maxTurns?: number;
|
|
95
|
+
readonly activeTools: ReadonlyMap<string, string>;
|
|
96
|
+
readonly responseText: string;
|
|
97
|
+
readonly contextPercent: number | null;
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`renderFinishedLine(agent, registry, theme)` and `renderRunningLines(agent, registry, spinnerFrame, theme)` drop the `WidgetActivity | undefined` parameter and read `agent.turnCount` / `agent.maxTurns` / `agent.activeTools` / `agent.responseText` directly.
|
|
102
|
+
`renderRunningLines` uses `agent.contextPercent` in place of `activity?.session ? getSessionContextPercent(activity.session) : null`.
|
|
103
|
+
`renderWidgetLines` and `buildSections` drop the `activityMap` parameter.
|
|
104
|
+
Because every `WidgetAgent` now carries activity, the old "no activity provided → omit turn count / show `thinking…`" branch is gone — `describeActivity(emptyMap, "")` already returns the `thinking…` string for an idle running agent, so the running-line text is preserved.
|
|
105
|
+
|
|
106
|
+
### `AgentWidget` projects records into snapshots
|
|
107
|
+
|
|
108
|
+
`AgentWidget` drops its `agentActivity` constructor param and field.
|
|
109
|
+
`renderWidget` maps each record through a local projection before handing the array to the pure renderer:
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
private toWidgetAgent(record: Subagent): WidgetAgent {
|
|
113
|
+
return {
|
|
114
|
+
id: record.id,
|
|
115
|
+
type: record.type,
|
|
116
|
+
status: record.status,
|
|
117
|
+
description: record.description,
|
|
118
|
+
toolUses: record.toolUses,
|
|
119
|
+
startedAt: record.startedAt,
|
|
120
|
+
completedAt: record.completedAt,
|
|
121
|
+
error: record.error,
|
|
122
|
+
lifetimeUsage: record.lifetimeUsage,
|
|
123
|
+
compactionCount: record.compactionCount,
|
|
124
|
+
turnCount: record.turnCount,
|
|
125
|
+
maxTurns: record.maxTurns,
|
|
126
|
+
activeTools: record.activeTools,
|
|
127
|
+
responseText: record.responseText,
|
|
128
|
+
contextPercent: record.getContextPercent(),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
`renderWidget` then calls `renderWidgetLines({ agents: this.manager.listAgents().map(r => this.toWidgetAgent(r)), registry, … })` with no `activityMap`.
|
|
134
|
+
`assembleWidgetState` keeps consuming raw records via its `AgentSummary` shape (id / status / completedAt) — unchanged.
|
|
135
|
+
This makes the snapshot boundary explicit: the renderer depends only on plain data; `AgentWidget` owns the projection (Tell-Don't-Ask — it asks each record once and hands forward a value).
|
|
136
|
+
|
|
137
|
+
### `ConversationViewer` reads off the record
|
|
138
|
+
|
|
139
|
+
The `activity` constructor param and field are removed.
|
|
140
|
+
`buildContentLines` reads the streaming indicator inputs off the record it already holds:
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
if (this.record.status === "running") {
|
|
144
|
+
lines.push(...formatStreamingIndicator(
|
|
145
|
+
this.record.activeTools,
|
|
146
|
+
this.record.responseText,
|
|
147
|
+
width,
|
|
148
|
+
th,
|
|
149
|
+
));
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
The `&& this.activity` guard is dropped — a running record always exposes `activeTools` / `responseText` (empty / `""` when idle), and `describeActivity` renders the idle case as `thinking…`, matching the prior no-tracker fallback.
|
|
154
|
+
|
|
155
|
+
### `AgentsMenuHandler` drops the reader
|
|
156
|
+
|
|
157
|
+
The `AgentActivityReader` interface and the `agentActivity` constructor param are removed.
|
|
158
|
+
`viewAgentConversation` no longer looks up a tracker; it constructs `ConversationViewer` without an `activity` field.
|
|
159
|
+
|
|
160
|
+
### `buildDetails` / `runForeground` read off the record
|
|
161
|
+
|
|
162
|
+
`buildDetails` drops its `activity?: AgentActivityTracker` parameter; the structural `record` param gains `turnCount?: number` and `maxTurns?: number`, and the body reads `record.turnCount` / `record.maxTurns`:
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
export function buildDetails(
|
|
166
|
+
base: Pick<AgentDetails, "displayName" | "description" | "subagentType" | "modelName" | "tags">,
|
|
167
|
+
record: {
|
|
168
|
+
toolUses: number; startedAt: number; completedAt?: number; status: string;
|
|
169
|
+
error?: string; id?: string; lifetimeUsage: LifetimeUsage;
|
|
170
|
+
turnCount?: number; maxTurns?: number;
|
|
171
|
+
},
|
|
172
|
+
overrides?: Partial<AgentDetails>,
|
|
173
|
+
): AgentDetails { … turnCount: record.turnCount, maxTurns: record.maxTurns … }
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
`Subagent` satisfies this structurally (the getters supply `turnCount` / `maxTurns`).
|
|
177
|
+
In `runForeground`, `streamUpdate` reads `recordRef?.turnCount ?? 1`, `recordRef?.activeTools ?? new Map()`, `recordRef?.responseText ?? ""`, and `execution.effectiveMaxTurns` for `maxTurns` (the runner already holds this constant; `recordRef` is undefined until `onSessionCreated`).
|
|
178
|
+
The final `buildDetails(presentation.detailBase, record, fgState, { tokens })` becomes `buildDetails(presentation.detailBase, record, { tokens })`.
|
|
179
|
+
The `fgState` tracker, its `subscribeUIObserver` subscription, and the `agentActivity.set` / `delete` calls remain (producer plumbing removed in Step 3) but are no longer read.
|
|
180
|
+
|
|
181
|
+
### `buildNotificationDetails` / `NotificationManager`
|
|
182
|
+
|
|
183
|
+
`buildNotificationDetails(record, resultMaxLen)` drops the `activity?` param and reads `record.turnCount` / `record.maxTurns`.
|
|
184
|
+
`NotificationManager` drops the `agentActivity` constructor param and field; `sendCompletion` keeps only the nudge scheduling.
|
|
185
|
+
`cleanupCompleted` only ever deleted a map entry, so it becomes vestigial and is **removed** from both `NotificationSystem` and `NotificationManager`.
|
|
186
|
+
`SubagentEventsObserver.onSubagentCompleted` updates its `resultConsumed` branch to simply `return` (skip the nudge) instead of calling `cleanupCompleted`.
|
|
187
|
+
|
|
188
|
+
### Consumer call-site check (index.ts)
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
const notifications = new NotificationManager((msg, opts) => pi.sendMessage(msg, opts));
|
|
192
|
+
const widget = new AgentWidget(manager, registry);
|
|
193
|
+
const agentsMenu = new AgentsMenuHandler(manager, registry, settings, new FsAgentFileOps(), …);
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
All three constructions drop the `runtime.agentActivity` argument.
|
|
197
|
+
`runtime.agentActivity` itself stays (still written by the spawn tools; removed in Step 3).
|
|
198
|
+
|
|
199
|
+
## Module-Level Changes
|
|
200
|
+
|
|
201
|
+
- `src/ui/widget-renderer.ts` — remove the `WidgetActivity` interface; fold its fields plus `contextPercent` into `WidgetAgent`; drop the `WidgetActivity | undefined` param from `renderFinishedLine` / `renderRunningLines`; drop `activityMap` from `buildSections` / `renderWidgetLines`; remove the `SessionLike` and `getSessionContextPercent` imports (keep `getLifetimeTotal`).
|
|
202
|
+
- `src/ui/agent-widget.ts` — drop the `agentActivity` constructor param and field; add the `toWidgetAgent` projection; map `listAgents()` through it in `renderWidget`; drop the `AgentActivityTracker` import.
|
|
203
|
+
- `src/ui/conversation-viewer.ts` — drop the `activity` field and `ConversationViewerOptions.activity`; read `activeTools` / `responseText` off `this.record`; drop the `AgentActivityTracker` import.
|
|
204
|
+
- `src/ui/agent-menu.ts` — remove the `AgentActivityReader` interface and the `agentActivity` constructor param; drop the tracker lookup in `viewAgentConversation`; drop the `AgentActivityTracker` import.
|
|
205
|
+
- `src/tools/helpers.ts` — `buildDetails` drops the `activity?` param and reads `record.turnCount` / `record.maxTurns` (param type gains the two optional fields); drop the `AgentActivityTracker` import.
|
|
206
|
+
- `src/tools/foreground-runner.ts` — `streamUpdate` and the final `buildDetails` call read off `recordRef` / `record`; the `fgState` construction, `subscribeUIObserver`, and map writes stay (Step 3).
|
|
207
|
+
- `src/observation/notification.ts` — `buildNotificationDetails` drops the `activity?` param and reads off the record; `NotificationManager` drops the `agentActivity` param/field and the `delete` calls; remove `cleanupCompleted` from `NotificationSystem` and the class; drop the `AgentActivityTracker` import.
|
|
208
|
+
- `src/observation/subagent-events-observer.ts` — `onSubagentCompleted` `resultConsumed` branch returns without calling `cleanupCompleted`.
|
|
209
|
+
- `src/index.ts` — drop `runtime.agentActivity` from the `NotificationManager`, `AgentWidget`, and `AgentsMenuHandler` constructions.
|
|
210
|
+
- `.pi/skills/package-pi-subagents/SKILL.md` — update the module-dependency-flow line `widget ─polls─→ AgentActivityTracker map` to reflect that the widget now polls `listAgents()` records.
|
|
211
|
+
- `docs/architecture/architecture.md` — add a `Landed:` line to the Phase 18 Step 2 entry during implementation (the entry text already describes the work).
|
|
212
|
+
|
|
213
|
+
Test files (see TDD Order for which step each lands in):
|
|
214
|
+
|
|
215
|
+
- `test/helpers/make-subagent.ts` — add `turnCount` / `activeTools` / `responseText` / `maxTurns` shorthands to `createTestSubagent` so reader tests can seed activity on records.
|
|
216
|
+
- `test/widget-renderer.test.ts` — remove `WidgetActivity` / `makeActivity`; fold activity defaults into `makeAgent` (`turnCount`, `maxTurns`, `activeTools`, `responseText`, `contextPercent`); drop the `activity` arg from render calls and `activityMap` from `renderWidgetLines` calls; update the former "omit turn count when no activity" cases to assert the turn count now renders; convert the session-stats context test to a `contextPercent` field.
|
|
217
|
+
- `test/ui/agent-widget.test.ts` — drop the `agentActivity` arg from `new AgentWidget(...)`; add a projection test asserting `renderWidget` reads activity off records.
|
|
218
|
+
- `test/conversation-viewer.test.ts` — drop `activity` from `TestViewerOptions` / `createTestViewer`; seed the running-indicator test's `activeTools` / `responseText` via `createTestSubagent`; drop the `AgentActivityTracker` import.
|
|
219
|
+
- `test/ui/agent-menu.test.ts` — drop the `agentActivity` arg from `new AgentsMenuHandler(...)`.
|
|
220
|
+
- `test/tools/helpers.test.ts` — drop the `activity` arg from `buildDetails` calls; seed `turnCount` / `maxTurns` via `createTestSubagent`; drop the `AgentActivityTracker` import.
|
|
221
|
+
- `test/tools/foreground-runner.test.ts` — adjust any `details.turnCount` assertions to the record's value (turn count `1` default matches the prior tracker default, so most cases are unchanged); the activity-map registration test stays (producer plumbing remains).
|
|
222
|
+
- `test/observation/notification.test.ts` — drop `agentActivity` from `makeArgs` and `new NotificationManager(...)`; drop the `activity` arg from `buildNotificationDetails` calls and seed `turnCount` / `maxTurns` via `createTestSubagent`; remove the two map-cleanup assertions and the `cleanupCompleted` test.
|
|
223
|
+
- `test/observation/subagent-events-observer.test.ts` — update any assertion that `cleanupCompleted` is called on `resultConsumed` to assert no nudge is sent instead.
|
|
224
|
+
|
|
225
|
+
`test/runtime.test.ts` and `test/helpers/make-deps.ts` keep their `AgentActivityTracker` map usage — the runtime map and the `AgentTool` `agentActivity` access survive this step (removed in Steps 3 / 5).
|
|
226
|
+
Completed historical plans under `docs/plans/` are point-in-time records and are not edited.
|
|
227
|
+
|
|
228
|
+
## Test Impact Analysis
|
|
229
|
+
|
|
230
|
+
1. **New unit tests enabled.**
|
|
231
|
+
`agent-widget.test.ts` can now assert the projection directly — that `renderWidget` surfaces a record's `turnCount` / `activeTools` / `contextPercent` without any external map — because activity lives on the record the manager stub returns.
|
|
232
|
+
`widget-renderer.test.ts` tests get simpler: a single `makeAgent` carries everything, removing the two-object (`WidgetAgent` + `WidgetActivity`) arrangement.
|
|
233
|
+
2. **Tests that become redundant / simplified.**
|
|
234
|
+
The `widget-renderer` "omits turn count when no activity provided" cases lose their premise (there is no activity-absent path); they convert to asserting the turn count renders.
|
|
235
|
+
The `notification` "sendCompletion cleans up activity" and "cleanupCompleted removes activity" cases lose their subject (the map is gone) and are removed.
|
|
236
|
+
`ui-observer.test.ts` and `agent-activity-tracker.test.ts` stay — they pin the producer behavior still wired until Step 3.
|
|
237
|
+
3. **Tests that must stay as-is.**
|
|
238
|
+
`foreground-runner.test.ts`'s activity-map registration test (the tracker is still constructed and registered), and `runtime.test.ts`'s `agentActivity` map presence test, genuinely exercise the producer plumbing this step does not remove.
|
|
239
|
+
|
|
240
|
+
## Invariants at risk
|
|
241
|
+
|
|
242
|
+
- **Step 1 ([#420]) outcome — `Subagent` is the single home for run state; getters available for migration.**
|
|
243
|
+
This step is the consumer of that outcome; the getters' parity with the old tracker (turn count starts at `1`, `name_seq` active-tool keying, reset-then-append response text) is what makes the swap behavior-preserving for running agents.
|
|
244
|
+
Pinned by the existing `subagent-state.test.ts` / `record-observer.test.ts` cases from Step 1 plus the reader tests migrated here.
|
|
245
|
+
- **`widget-renderer` purity — pure functions, plain data, no SDK types** (established when the renderer was split out, plan 0148).
|
|
246
|
+
The migration preserves and strengthens it: the `contextPercent` field removes the `getSessionContextPercent(session)` reach-through, so the renderer no longer touches session stats.
|
|
247
|
+
Pinned by `widget-renderer.test.ts` constructing plain `WidgetAgent` objects (no class instances).
|
|
248
|
+
- **`NotificationManager` construction graph stays a cycle-free DAG with no widget dependency** (index.ts comment).
|
|
249
|
+
Dropping the `agentActivity` map narrows its deps further; no new dependency is added.
|
|
250
|
+
|
|
251
|
+
No earlier phase step's documented `Outcome:` is regressed — the producer plumbing Step 1's tidy-first overlap left in place is untouched here.
|
|
252
|
+
|
|
253
|
+
## TDD Order
|
|
254
|
+
|
|
255
|
+
1. **Preparatory: seed activity on test records.**
|
|
256
|
+
Surface: `test/helpers/make-subagent.ts` (+ a focused assertion in `test/helpers/make-deps.test.ts` or `make-subagent` coverage if one exists).
|
|
257
|
+
Add `turnCount` / `activeTools` / `responseText` / `maxTurns` shorthands to `createTestSubagent` (drive `state.incrementTurnCount` / `addActiveTool` / `appendResponseText`; thread `maxTurns` into `makeStubExecution`).
|
|
258
|
+
Pure test-helper addition — no production change; makes the reader-migration steps easy ("make the change that makes the change easy").
|
|
259
|
+
Commit: `test: seed live-activity on createTestSubagent (#421)`.
|
|
260
|
+
2. **Migrate the widget reader.**
|
|
261
|
+
Surface: `test/widget-renderer.test.ts`, `test/ui/agent-widget.test.ts`.
|
|
262
|
+
Red→green: update `makeAgent` to carry activity, drop the `activity` arg / `activityMap`, assert finished lines now render the turn count, assert the `AgentWidget` projection.
|
|
263
|
+
Implement the `WidgetAgent` fold + `contextPercent`, the render-signature changes, and the `AgentWidget.toWidgetAgent` projection; update the `new AgentWidget(...)` call in `index.ts` and the SKILL.md flow line.
|
|
264
|
+
This step lands the accepted behavior change (finished-agent turn count), so the commit is `feat:`, not `refactor:`.
|
|
265
|
+
Run `pnpm run check` after this commit — `renderWidgetLines` is the shared signature changed.
|
|
266
|
+
Commit: `feat: read widget activity off subagent records (#421)`.
|
|
267
|
+
3. **Migrate the conversation viewer and `/agents` menu.**
|
|
268
|
+
Surface: `test/conversation-viewer.test.ts`, `test/ui/agent-menu.test.ts`.
|
|
269
|
+
Red→green: drop `activity` from the viewer factory and seed the running-indicator inputs on the record; drop the `agentActivity` arg from the menu handler construction.
|
|
270
|
+
Implement: drop `ConversationViewer.activity`; read off `this.record`; remove `AgentActivityReader` and the `viewAgentConversation` lookup; update the `new AgentsMenuHandler(...)` call in `index.ts`.
|
|
271
|
+
The viewer constructor signature and the menu constructor signature both change with their sole call sites in the same commit.
|
|
272
|
+
Commit: `refactor: read conversation-viewer activity off the record (#421)`.
|
|
273
|
+
4. **Migrate `buildDetails` and the foreground runner.**
|
|
274
|
+
Surface: `test/tools/helpers.test.ts`, `test/tools/foreground-runner.test.ts`.
|
|
275
|
+
Red→green: drop the `activity` arg from `buildDetails` calls and seed `turnCount` / `maxTurns` on the record; confirm the foreground `details` still report the record's turn count.
|
|
276
|
+
Implement: `buildDetails` param/body change (both call sites — `runForeground` and the `AgentTool` resume path — compile in the same commit), and `runForeground`'s `streamUpdate` / final `buildDetails` reads.
|
|
277
|
+
Commit: `refactor: build agent details from the record, not the tracker (#421)`.
|
|
278
|
+
5. **Migrate notifications and drop the activity map there.**
|
|
279
|
+
Surface: `test/observation/notification.test.ts`, `test/observation/subagent-events-observer.test.ts`.
|
|
280
|
+
Red→green: drop `agentActivity` from the notification fixtures and the `activity` arg from `buildNotificationDetails`; remove the map-cleanup and `cleanupCompleted` cases; assert the observer's `resultConsumed` branch sends no nudge.
|
|
281
|
+
Implement: `buildNotificationDetails` reads off the record; `NotificationManager` drops the map param/field and `cleanupCompleted`; `NotificationSystem` drops `cleanupCompleted`; `SubagentEventsObserver` returns in the `resultConsumed` branch; update the `new NotificationManager(...)` call in `index.ts`.
|
|
282
|
+
Run `pnpm run check` after this commit — `NotificationSystem` is a shared interface and `index.ts` is its construction site.
|
|
283
|
+
Commit: `refactor: build notification details from the record (#421)`.
|
|
284
|
+
|
|
285
|
+
After Step 5, no reader reads activity off `AgentActivityTracker`; the tracker, `ui-observer`, and the runtime map remain only as inert producer plumbing for Step 3 ([#422]).
|
|
286
|
+
|
|
287
|
+
## Risks and Mitigations
|
|
288
|
+
|
|
289
|
+
- **Risk: the running-line idle text changes.**
|
|
290
|
+
Once `activeTools` / `responseText` always come off the record, an idle running agent renders via `describeActivity(new Map(), "")` instead of the old no-tracker `thinking…` fallback.
|
|
291
|
+
Mitigation: `describeActivity` already returns `thinking…` for an empty map and blank text, so the output is preserved; the migrated `widget-renderer` test pins it.
|
|
292
|
+
- **Risk: transient background-tracker map leak between Steps 2 and 3.**
|
|
293
|
+
`NotificationManager` no longer deletes finished agents' trackers from the runtime map, and background agents are no longer cleaned up by anyone until the map is removed.
|
|
294
|
+
Mitigation: after this step the map is write-only and read by nobody, so the leak is inert and short-lived; Step 3 ([#422]) removes the map entirely.
|
|
295
|
+
- **Risk: `streamUpdate` runs before `recordRef` is set (foreground).**
|
|
296
|
+
The initial `streamUpdate()` and the first spinner ticks fire before `onSessionCreated` assigns `recordRef`.
|
|
297
|
+
Mitigation: read with defaults that match the tracker's initial values (`turnCount ?? 1`, empty `activeTools`, `"" `responseText, `execution.effectiveMaxTurns` for `maxTurns`).
|
|
298
|
+
- **Risk: removing `cleanupCompleted` changes the observer's consumed-result path.**
|
|
299
|
+
Mitigation: the method only deleted a map entry; the observer's intent (skip the nudge when the result was already consumed) is preserved by an early `return`, pinned by the updated observer test.
|
|
300
|
+
|
|
301
|
+
## Open Questions
|
|
302
|
+
|
|
303
|
+
- Whether the `NotificationSystem` interface should be narrowed further (e.g. to a two-method completion notifier) now that `cleanupCompleted` is gone is deferred — it is orthogonal to this step and best judged once Steps 3–5 settle the surrounding wiring.
|
|
304
|
+
|
|
305
|
+
[#420]: https://github.com/gotgenes/pi-packages/issues/420
|
|
306
|
+
[#422]: https://github.com/gotgenes/pi-packages/issues/422
|
|
307
|
+
[#423]: https://github.com/gotgenes/pi-packages/issues/423
|
|
308
|
+
[#424]: https://github.com/gotgenes/pi-packages/issues/424
|
|
309
|
+
[#425]: https://github.com/gotgenes/pi-packages/issues/425
|