@gotgenes/pi-subagents 6.15.0 → 6.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/docs/architecture/architecture.md +15 -18
- package/docs/plans/0144-consolidate-observation-model.md +263 -0
- package/docs/plans/0148-split-agent-widget-rendering.md +255 -0
- package/docs/retro/0144-consolidate-observation-model.md +39 -0
- package/docs/retro/0145-decompose-execute-push-ctx-to-boundary.md +56 -0
- package/package.json +1 -1
- package/src/agent-manager.ts +3 -3
- package/src/agent-record.ts +11 -0
- package/src/index.ts +6 -6
- package/src/notification.ts +21 -24
- package/src/service-adapter.ts +1 -1
- package/src/tools/agent-tool.ts +1 -1
- package/src/tools/background-spawner.ts +1 -1
- package/src/tools/foreground-runner.ts +7 -4
- package/src/tools/get-result-tool.ts +3 -3
- package/src/tools/steer-tool.ts +1 -1
- package/src/ui/agent-activity-tracker.ts +3 -27
- package/src/ui/agent-menu.ts +1 -1
- package/src/ui/agent-widget.ts +12 -189
- package/src/ui/conversation-viewer.ts +3 -3
- package/src/ui/display.ts +2 -1
- package/src/ui/ui-observer.ts +1 -12
- package/src/ui/widget-renderer.ts +236 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,34 @@ 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
|
+
## [6.16.1](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v6.16.0...pi-subagents-v6.16.1) (2026-05-23)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* **pi-subagents:** theme parentheses and separator in formatSessionTokens ([33b67f6](https://github.com/gotgenes/pi-packages/commit/33b67f63915563c41605addb885af52b47844e96))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Documentation
|
|
17
|
+
|
|
18
|
+
* plan split AgentWidget rendering from lifecycle ([#148](https://github.com/gotgenes/pi-packages/issues/148)) ([24c11d5](https://github.com/gotgenes/pi-packages/commit/24c11d51f2b6c9d2712815fbe46ede72084ffcbb))
|
|
19
|
+
* **retro:** add retro notes for issue [#144](https://github.com/gotgenes/pi-packages/issues/144) ([f3cdfd4](https://github.com/gotgenes/pi-packages/commit/f3cdfd46fe223d6cecb5dad0d739f053e7468433))
|
|
20
|
+
* update architecture for widget rendering extraction ([#148](https://github.com/gotgenes/pi-packages/issues/148)) ([450707e](https://github.com/gotgenes/pi-packages/commit/450707e1d0f41ae78f1a655ab350fd8f4fd64125))
|
|
21
|
+
|
|
22
|
+
## [6.16.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v6.15.0...pi-subagents-v6.16.0) (2026-05-23)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
### Features
|
|
26
|
+
|
|
27
|
+
* add session and outputFile convenience getters to AgentRecord ([#144](https://github.com/gotgenes/pi-packages/issues/144)) ([b451894](https://github.com/gotgenes/pi-packages/commit/b451894d101b43be9115dcf6d45725a09da81df8))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
### Documentation
|
|
31
|
+
|
|
32
|
+
* mark Step L complete, remove resolved smells from architecture doc ([#144](https://github.com/gotgenes/pi-packages/issues/144)) ([36ab7a5](https://github.com/gotgenes/pi-packages/commit/36ab7a51d0320666b71d6c9e20c3bbf63b7c43c5))
|
|
33
|
+
* plan consolidate observation model ([#144](https://github.com/gotgenes/pi-packages/issues/144)) ([9aa2c85](https://github.com/gotgenes/pi-packages/commit/9aa2c8508079c6ae847662631afd223e8966e12e))
|
|
34
|
+
* **retro:** add retro notes for issue [#145](https://github.com/gotgenes/pi-packages/issues/145) ([2d23081](https://github.com/gotgenes/pi-packages/commit/2d230817d53357a62eb752b56f8b1c8ce4af718c))
|
|
35
|
+
|
|
8
36
|
## [6.15.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v6.14.1...pi-subagents-v6.15.0) (2026-05-23)
|
|
9
37
|
|
|
10
38
|
|
|
@@ -69,7 +69,8 @@ renderer.ts - notification TUI component
|
|
|
69
69
|
record-observer.ts - session-event observer for record statistics
|
|
70
70
|
|
|
71
71
|
ui/display.ts - pure formatters, display helpers, and shared types (Theme, AgentDetails)
|
|
72
|
-
ui/agent-widget.ts - above-editor live status widget
|
|
72
|
+
ui/agent-widget.ts - above-editor live status widget (thin lifecycle wrapper)
|
|
73
|
+
ui/widget-renderer.ts - pure rendering functions for agent widget
|
|
73
74
|
ui/agent-menu.ts - /agents slash command menu
|
|
74
75
|
ui/conversation-viewer.ts - scrollable session overlay
|
|
75
76
|
ui/ui-observer.ts - session-event observer for UI streaming
|
|
@@ -615,15 +616,13 @@ Phase 9 targets the next layer: observation model consolidation, `ExtensionConte
|
|
|
615
616
|
|
|
616
617
|
### Current smells
|
|
617
618
|
|
|
618
|
-
| Smell | Location | Evidence
|
|
619
|
-
| ------------------------------------------------ | --------------------------------------------------------------------- |
|
|
620
|
-
|
|
|
621
|
-
| `
|
|
622
|
-
|
|
|
623
|
-
|
|
|
624
|
-
|
|
|
625
|
-
| Widget mixes rendering, lifecycle, and state | `agent-widget.ts` (370 lines) | `renderWidget` is ~109 lines mixing data collection, formatting, and overflow layout; constructor takes 3 concrete collaborators | Low |
|
|
626
|
-
| `deps.` prefix noise in function bodies | 12 modules across tools, UI, notification, service-adapter | Functions accept a `deps` bag and access every field as `deps.foo`; hides real dependencies and lengthens every call line | Low |
|
|
619
|
+
| Smell | Location | Evidence | Severity |
|
|
620
|
+
| ------------------------------------------------ | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
|
|
621
|
+
| `execute` does config resolution for its callees | `agent-tool.ts` (145-line `execute`) | ~60 lines unpack config, resolve model, compute metadata, repack into 16-field bags for spawners; `ctx` threaded 4 layers deep | Medium |
|
|
622
|
+
| Wide `ctx` in menu handlers | `agent-menu.ts`, `agent-config-editor.ts`, `agent-creation-wizard.ts` | Functions declare `ctx: ExtensionContext` but only call `ctx.ui.select/confirm/input/notify/editor`; 43 `ctx as any` casts across 3 test files | Medium |
|
|
623
|
+
| Direct SDK import in `conversation-viewer.ts` | `conversation-viewer.test.ts` | Hoisted `vi.mock("@earendil-works/pi-tui")` to intercept `wrapTextWithAnsi` | Low |
|
|
624
|
+
| ~~Widget mixes rendering, lifecycle, and state~~ | ~~`agent-widget.ts` (370 lines)~~ | Resolved by #148: rendering extracted to `widget-renderer.ts`; widget is now 198 lines | Done |
|
|
625
|
+
| `deps.` prefix noise in function bodies | remaining modules across tools, UI, service-adapter | Functions accept a `deps` bag and access every field as `deps.foo`; hides real dependencies and lengthens every call line | Low |
|
|
627
626
|
|
|
628
627
|
### Dependency bag convention
|
|
629
628
|
|
|
@@ -634,9 +633,9 @@ Applied incrementally as each step touches a module:
|
|
|
634
633
|
|
|
635
634
|
This eliminates the `deps.` prefix noise across ~124 callsites in 12 modules.
|
|
636
635
|
|
|
637
|
-
### Step L: Consolidate observation model (#144)
|
|
636
|
+
### Step L: Consolidate observation model (#144) ✓
|
|
638
637
|
|
|
639
|
-
|
|
638
|
+
Removed `_toolUses` and `_lifetimeUsage` from `AgentActivityTracker`.
|
|
640
639
|
UI consumers read stats from `AgentRecord` instead of the tracker.
|
|
641
640
|
The UI observer retains event subscriptions for re-render triggers but no longer accumulates stats independently.
|
|
642
641
|
|
|
@@ -690,13 +689,11 @@ Apply the dependency bag convention: `ConversationViewerOptions` is destructured
|
|
|
690
689
|
|
|
691
690
|
Impact: eliminates the hoisted `vi.mock("@earendil-works/pi-tui")` in `conversation-viewer.test.ts`.
|
|
692
691
|
|
|
693
|
-
### Step P: Split AgentWidget rendering (#148)
|
|
692
|
+
### Step P: Split AgentWidget rendering (#148) ✓
|
|
694
693
|
|
|
695
|
-
|
|
696
|
-
The widget
|
|
697
|
-
Rendering functions receive data (agent list, activity map, registry) and return formatted strings
|
|
698
|
-
|
|
699
|
-
Depends on Step L: once the tracker drops stats fields, the renderer reads from `AgentRecord` for tool uses and usage, and from `AgentActivityTracker` only for live UI state (active tools, response text, turn count).
|
|
694
|
+
Extracted pure rendering functions (`renderWidgetLines`, `renderFinishedLine`, `renderRunningLines`) from `AgentWidget` into `ui/widget-renderer.ts`.
|
|
695
|
+
The widget is now a thin lifecycle/polling wrapper (198 lines, down from 374) that delegates to pure render functions.
|
|
696
|
+
Rendering functions receive data (agent list, activity map, registry) and return formatted strings — testable without widget lifecycle. 23 new unit tests cover all status variants, overflow, tree connectors, and empty states.
|
|
700
697
|
|
|
701
698
|
### Step dependencies
|
|
702
699
|
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 144
|
|
3
|
+
issue_title: "Consolidate observation model (Phase 9, Step L)"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Consolidate observation model
|
|
7
|
+
|
|
8
|
+
## Problem Statement
|
|
9
|
+
|
|
10
|
+
`record-observer.ts` and `ui-observer.ts` independently count tool uses and accumulate lifetime usage from the same session events.
|
|
11
|
+
`AgentRecord` owns `_toolUses` and `_lifetimeUsage` (accumulated by the record observer), while `AgentActivityTracker` maintains its own `_toolUses` and `_lifetimeUsage` (accumulated by the UI observer).
|
|
12
|
+
Consumers use `activity?.toolUses ?? record.toolUses` fallbacks to paper over the ambiguity.
|
|
13
|
+
|
|
14
|
+
Separately, 15+ callsites navigate `record.execution?.session` and `record.execution?.outputFile`, leaking the `ExecutionState` structure to every consumer.
|
|
15
|
+
|
|
16
|
+
Finally, `NotificationDeps` (4 fields) is a dependency bag on a class where every method uses every field — plain constructor parameters are simpler.
|
|
17
|
+
|
|
18
|
+
## Goals
|
|
19
|
+
|
|
20
|
+
- Remove `_toolUses` and `_lifetimeUsage` from `AgentActivityTracker` so stats have a single source of truth on `AgentRecord`.
|
|
21
|
+
- Update UI consumers (widget, conversation viewer, notification, foreground runner) to read stats from `AgentRecord` instead of the tracker.
|
|
22
|
+
- Remove the `onToolEnd` counter and `onUsageUpdate` accumulator from `AgentActivityTracker`, keeping only live UI state (active tools, response text, turn count).
|
|
23
|
+
- Stop the UI observer from duplicating `tool_execution_end` counting and `message_end` usage accumulation.
|
|
24
|
+
- Add `session` and `outputFile` convenience getters on `AgentRecord` to hide the `execution?.` traversal.
|
|
25
|
+
- Replace `NotificationDeps` interface with plain parameters on `NotificationManager` constructor.
|
|
26
|
+
|
|
27
|
+
## Non-Goals
|
|
28
|
+
|
|
29
|
+
- Splitting `AgentWidget` rendering into pure functions (Step P / #148 — depends on this work).
|
|
30
|
+
- Narrowing `ExtensionContext` for menu handlers (Step N / #146 — independent track).
|
|
31
|
+
- Injecting text wrapping into `ConversationViewer` (Step O / #147 — independent track).
|
|
32
|
+
|
|
33
|
+
## Background
|
|
34
|
+
|
|
35
|
+
### Dual counting
|
|
36
|
+
|
|
37
|
+
Both observers subscribe to the same session events:
|
|
38
|
+
|
|
39
|
+
| Event | `record-observer.ts` | `ui-observer.ts` |
|
|
40
|
+
| --------------------- | ---------------------------- | ------------------------------------- |
|
|
41
|
+
| `tool_execution_end` | `record.incrementToolUses()` | `tracker.onToolEnd()` → `_toolUses++` |
|
|
42
|
+
| `message_end` (usage) | `record.addUsage(delta)` | `tracker.onUsageUpdate(delta)` |
|
|
43
|
+
|
|
44
|
+
The widget reads `bg?.toolUses ?? a.toolUses` and the conversation viewer reads `this.activity?.toolUses ?? this.record.toolUses`, preferring the tracker when alive and falling back to the record.
|
|
45
|
+
After this change, there is one source: `AgentRecord`.
|
|
46
|
+
|
|
47
|
+
### `execution?.` traversal
|
|
48
|
+
|
|
49
|
+
`ExecutionState` holds `session` and `outputFile`.
|
|
50
|
+
These are set once when the agent session is created (`agent-manager.ts` line 276).
|
|
51
|
+
Consumers reach through `record.execution?.session` and `record.execution?.outputFile` in 12 distinct locations across 7 files.
|
|
52
|
+
Convenience getters on `AgentRecord` eliminate the traversal and hide the `ExecutionState` structure.
|
|
53
|
+
|
|
54
|
+
### `NotificationDeps` bag
|
|
55
|
+
|
|
56
|
+
`NotificationManager` receives 4 fields via `NotificationDeps`: `sendMessage`, `agentActivity`, `markFinished`, `updateWidget`.
|
|
57
|
+
Every method on the class uses every field.
|
|
58
|
+
Per code-design convention (dependency width), a 4-field interface where every consumer uses every field is fine as a bag, but the architecture doc explicitly calls for plain parameters here.
|
|
59
|
+
This is a mechanical change.
|
|
60
|
+
|
|
61
|
+
## Design Overview
|
|
62
|
+
|
|
63
|
+
### AgentActivityTracker changes
|
|
64
|
+
|
|
65
|
+
Remove `_toolUses`, `_lifetimeUsage`, `onToolEnd`, `onUsageUpdate`, `toolUses` getter, and `lifetimeUsage` getter.
|
|
66
|
+
Retain: `_activeTools`, `_toolKeySeq`, `_responseText`, `_session`, `_turnCount`, `_maxTurns`, and their associated transition methods and getters.
|
|
67
|
+
|
|
68
|
+
`onToolStart` stays (tracks active tools for the widget spinner).
|
|
69
|
+
A new `onToolDone(toolName)` method replaces `onToolEnd` — it removes the tool from `_activeTools` without incrementing any counter.
|
|
70
|
+
This rename clarifies that the method only manages the active-tool display set.
|
|
71
|
+
|
|
72
|
+
### UI observer changes
|
|
73
|
+
|
|
74
|
+
The UI observer stops calling counter/accumulator methods:
|
|
75
|
+
|
|
76
|
+
- `tool_execution_end` handler: calls `tracker.onToolDone(name)` (active-tool tracking only).
|
|
77
|
+
- `message_end` handler: removed entirely (no more usage accumulation).
|
|
78
|
+
The `onUpdate?.()` call for usage re-render moves to the record observer path via the existing widget update flow.
|
|
79
|
+
|
|
80
|
+
### AgentRecord convenience getters
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
get session(): AgentSession | undefined {
|
|
84
|
+
return this.execution?.session;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
get outputFile(): string | undefined {
|
|
88
|
+
return this.execution?.outputFile;
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Callers change from `record.execution?.session` to `record.session`.
|
|
93
|
+
The `execution` field remains writable (set by `AgentManager`), but consumers no longer need to know about `ExecutionState`.
|
|
94
|
+
|
|
95
|
+
### Consumer migration
|
|
96
|
+
|
|
97
|
+
| File | Before | After |
|
|
98
|
+
| -------------------------- | -------------------------------------------------- | ------------------------------------------------ |
|
|
99
|
+
| `agent-widget.ts` | `bg?.toolUses ?? a.toolUses` | `a.toolUses` |
|
|
100
|
+
| `agent-widget.ts` | `getLifetimeTotal(bg?.lifetimeUsage)` | `getLifetimeTotal(a.lifetimeUsage)` |
|
|
101
|
+
| `agent-widget.ts` | `getSessionContextPercent(bg?.session)` | `getSessionContextPercent(a.session)` |
|
|
102
|
+
| `conversation-viewer.ts` | `this.activity?.toolUses ?? this.record.toolUses` | `this.record.toolUses` |
|
|
103
|
+
| `conversation-viewer.ts` | `getLifetimeTotal(this.activity?.lifetimeUsage)` | `getLifetimeTotal(this.record.lifetimeUsage)` |
|
|
104
|
+
| `conversation-viewer.ts` | `getSessionContextPercent(this.activity?.session)` | `getSessionContextPercent(this.record.session)` |
|
|
105
|
+
| `notification.ts` | `record.execution?.session` | `record.session` |
|
|
106
|
+
| `notification.ts` | `record.execution?.outputFile` | `record.outputFile` |
|
|
107
|
+
| `tools/get-result-tool.ts` | `record.execution?.session` | `record.session` |
|
|
108
|
+
| `tools/steer-tool.ts` | `record.execution?.session` | `record.session` |
|
|
109
|
+
| `agent-menu.ts` | `record.execution?.session` | `record.session` |
|
|
110
|
+
| `service-adapter.ts` | `record.execution?.session` | `record.session` |
|
|
111
|
+
| `agent-manager.ts` | `record.execution?.session` | `record.session` (in dispose paths) |
|
|
112
|
+
| `foreground-runner.ts` | `fgState.toolUses` | `record.toolUses` (in final result) |
|
|
113
|
+
| `foreground-runner.ts` | `formatLifetimeTokens(fgState)` | `formatLifetimeTokens(record)` (in final result) |
|
|
114
|
+
|
|
115
|
+
### NotificationManager refactoring
|
|
116
|
+
|
|
117
|
+
Replace:
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
export interface NotificationDeps { … }
|
|
121
|
+
constructor(private deps: NotificationDeps) {}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
With:
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
constructor(
|
|
128
|
+
private sendMessage: NotificationDeps["sendMessage"],
|
|
129
|
+
private agentActivity: Map<string, AgentActivityTracker>,
|
|
130
|
+
private markFinished: (id: string) => void,
|
|
131
|
+
private updateWidget: () => void,
|
|
132
|
+
) {}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Internal references change from `this.deps.sendMessage(…)` to `this.sendMessage(…)`.
|
|
136
|
+
The `NotificationDeps` interface is removed.
|
|
137
|
+
|
|
138
|
+
### Foreground runner streaming
|
|
139
|
+
|
|
140
|
+
During foreground execution, `streamUpdate` reads `fgState.toolUses` and `formatLifetimeTokens(fgState)` for live spinner updates.
|
|
141
|
+
After removing these from the tracker, `streamUpdate` must read from the record instead.
|
|
142
|
+
The record is available only after `spawnAndWait` returns the record reference via `onSessionCreated`.
|
|
143
|
+
Before that callback fires, tool uses and usage are both zero — the tracker never had meaningful data at that point either.
|
|
144
|
+
`streamUpdate` will capture a `let recordRef: AgentRecord | undefined` and read `recordRef?.toolUses ?? 0` during the spinner phase.
|
|
145
|
+
After `onSessionCreated`, `recordRef` is set and subsequent spinner ticks read live values from the record.
|
|
146
|
+
|
|
147
|
+
## Module-Level Changes
|
|
148
|
+
|
|
149
|
+
### Modified files
|
|
150
|
+
|
|
151
|
+
1. `src/ui/agent-activity-tracker.ts` — Remove `_toolUses`, `_lifetimeUsage`, `onToolEnd`, `onUsageUpdate`, `toolUses` getter, `lifetimeUsage` getter.
|
|
152
|
+
Rename remaining tool-end logic to `onToolDone`.
|
|
153
|
+
Remove `addUsage` and `UsageDelta` imports.
|
|
154
|
+
2. `src/ui/ui-observer.ts` — Call `tracker.onToolDone` instead of `tracker.onToolEnd`.
|
|
155
|
+
Remove `message_end` usage accumulation block.
|
|
156
|
+
3. `src/agent-record.ts` — Add `get session()` and `get outputFile()` convenience getters.
|
|
157
|
+
Import `AgentSession` type for the return type.
|
|
158
|
+
4. `src/notification.ts` — Remove `NotificationDeps` interface.
|
|
159
|
+
Change `NotificationManager` constructor to plain parameters.
|
|
160
|
+
Replace `this.deps.*` with `this.*`.
|
|
161
|
+
Change `record.execution?.session` → `record.session`, `record.execution?.outputFile` → `record.outputFile`.
|
|
162
|
+
5. `src/ui/agent-widget.ts` — Read `a.toolUses`, `a.lifetimeUsage`, `a.session` instead of `bg?.toolUses ?? a.toolUses` etc.
|
|
163
|
+
6. `src/ui/conversation-viewer.ts` — Read `this.record.toolUses`, `this.record.lifetimeUsage`, `this.record.session` instead of fallback pattern.
|
|
164
|
+
7. `src/tools/get-result-tool.ts` — `record.execution?.session` → `record.session`.
|
|
165
|
+
8. `src/tools/steer-tool.ts` — `record.execution?.session` → `record.session`.
|
|
166
|
+
9. `src/ui/agent-menu.ts` — `record.execution?.session` → `record.session`.
|
|
167
|
+
10. `src/service-adapter.ts` — `record.execution?.session` → `record.session`.
|
|
168
|
+
11. `src/agent-manager.ts` — `record.execution?.session` → `record.session` in dispose paths.
|
|
169
|
+
Keep the `record.execution = { session, outputFile }` assignment unchanged (that's the write path).
|
|
170
|
+
12. `src/tools/foreground-runner.ts` — Read `record.toolUses` and `formatLifetimeTokens(record)` for final result.
|
|
171
|
+
Capture `recordRef` for streaming phase.
|
|
172
|
+
13. `src/tools/helpers.ts` — `buildDetails`: read `record.toolUses` and `record.lifetimeUsage` (already does); remove optional `activity` parameter's usage of `toolUses`/`lifetimeUsage` if present (verify).
|
|
173
|
+
14. `src/index.ts` — Update `NotificationManager` construction from bag to plain parameters.
|
|
174
|
+
15. `src/tools/background-spawner.ts` — `record?.execution?.outputFile` → `record?.outputFile`.
|
|
175
|
+
|
|
176
|
+
### Test files modified
|
|
177
|
+
|
|
178
|
+
1. `test/ui/agent-activity-tracker.test.ts` — Remove tests for `toolUses`, `lifetimeUsage`, `onToolEnd`, `onUsageUpdate`.
|
|
179
|
+
Add tests for `onToolDone` (active-tool removal without counting).
|
|
180
|
+
2. `test/ui/ui-observer.test.ts` — Update `tool_execution_end` expectations (calls `onToolDone` not `onToolEnd`).
|
|
181
|
+
Remove `message_end` usage accumulation tests.
|
|
182
|
+
3. `test/notification.test.ts` — Update `makeDeps()` factory to use plain parameters.
|
|
183
|
+
Or update `NotificationManager` construction call to match new signature.
|
|
184
|
+
4. `test/agent-record.test.ts` — Add tests for `session` and `outputFile` getters.
|
|
185
|
+
5. `test/conversation-viewer.test.ts` — Remove mock activity tracker usage for stats; tests use record stats directly.
|
|
186
|
+
|
|
187
|
+
## Test Impact Analysis
|
|
188
|
+
|
|
189
|
+
### New unit tests enabled
|
|
190
|
+
|
|
191
|
+
1. `AgentRecord.session` and `AgentRecord.outputFile` getters — straightforward getter tests on the record class.
|
|
192
|
+
2. `AgentActivityTracker.onToolDone` — verifies active-tool removal without side effects on a counter.
|
|
193
|
+
|
|
194
|
+
### Existing tests that become simpler
|
|
195
|
+
|
|
196
|
+
1. `agent-activity-tracker.test.ts` — 4 test blocks for `toolUses`, `lifetimeUsage`, `onToolEnd`, `onUsageUpdate` can be removed.
|
|
197
|
+
The `onToolDone` replacement needs fewer assertions (no counter check).
|
|
198
|
+
2. `ui-observer.test.ts` — The `message_end` usage accumulation tests can be removed.
|
|
199
|
+
The `tool_execution_end` test simplifies (verifies active-tool removal only).
|
|
200
|
+
3. `notification.test.ts` — The `makeDeps()` helper changes shape but stays the same size.
|
|
201
|
+
4. `conversation-viewer.test.ts` — Tests that mock `activity.toolUses` and `activity.lifetimeUsage` can simplify to reading from the record.
|
|
202
|
+
|
|
203
|
+
### Existing tests that must stay
|
|
204
|
+
|
|
205
|
+
1. `record-observer.test.ts` — All tests remain as-is.
|
|
206
|
+
The record observer is the sole source of `incrementToolUses` and `addUsage` calls now.
|
|
207
|
+
2. `agent-record.test.ts` — All existing tests for `incrementToolUses`, `addUsage`, `incrementCompactions` remain.
|
|
208
|
+
3. `notification.test.ts` — Pure helper tests (`escapeXml`, `getStatusLabel`, `formatTaskNotification`, `buildNotificationDetails`, `buildEventData`) remain unchanged.
|
|
209
|
+
|
|
210
|
+
## TDD Order
|
|
211
|
+
|
|
212
|
+
1. **Red→Green: `AgentRecord` convenience getters.**
|
|
213
|
+
Add tests for `record.session` and `record.outputFile` (returns `undefined` when no execution, delegates when set).
|
|
214
|
+
Implement the getters.
|
|
215
|
+
Commit: `feat: add session and outputFile convenience getters to AgentRecord (#144)`
|
|
216
|
+
|
|
217
|
+
2. **Green→Green: Migrate callsites from `execution?.` to convenience getters.**
|
|
218
|
+
Update all 12 callsites across 7 files (`notification.ts`, `agent-widget.ts`, `agent-menu.ts`, `get-result-tool.ts`, `steer-tool.ts`, `service-adapter.ts`, `agent-manager.ts`, `background-spawner.ts`).
|
|
219
|
+
No test changes needed — existing tests pass.
|
|
220
|
+
Run `pnpm run check` to verify.
|
|
221
|
+
Commit: `refactor: use AgentRecord.session and .outputFile convenience getters (#144)`
|
|
222
|
+
|
|
223
|
+
3. **Red→Green: Rename `onToolEnd` to `onToolDone` and remove counter.**
|
|
224
|
+
Update `agent-activity-tracker.test.ts`: remove `onToolEnd` counter tests, add `onToolDone` tests (active-tool removal only, no `toolUses` assertion).
|
|
225
|
+
Implement: rename method, remove `_toolUses++`.
|
|
226
|
+
Update `ui-observer.ts` to call `onToolDone`.
|
|
227
|
+
Update `ui-observer.test.ts` expectations.
|
|
228
|
+
Commit: `refactor: rename AgentActivityTracker.onToolEnd to onToolDone (#144)`
|
|
229
|
+
|
|
230
|
+
4. **Red→Green: Remove `_toolUses` and `_lifetimeUsage` from tracker.**
|
|
231
|
+
Update `agent-activity-tracker.test.ts`: remove `toolUses` getter, `lifetimeUsage` getter, and `onUsageUpdate` test blocks.
|
|
232
|
+
Implement: remove `_toolUses`, `_lifetimeUsage`, `toolUses` getter, `lifetimeUsage` getter, `onUsageUpdate` method, `UsageDelta` type, `addUsage` import.
|
|
233
|
+
Update `ui-observer.ts`: remove `message_end` usage accumulation block.
|
|
234
|
+
Update `ui-observer.test.ts`: remove `message_end` usage tests.
|
|
235
|
+
Run `pnpm run check` to catch any remaining references.
|
|
236
|
+
Commit: `refactor: remove duplicate stats from AgentActivityTracker (#144)`
|
|
237
|
+
|
|
238
|
+
5. **Green→Green: Migrate UI consumers to read stats from `AgentRecord`.**
|
|
239
|
+
Update `agent-widget.ts`: replace `bg?.toolUses ?? a.toolUses` → `a.toolUses`, `bg?.lifetimeUsage` → `a.lifetimeUsage`, `bg?.session` → `a.session`.
|
|
240
|
+
Update `conversation-viewer.ts`: replace `activity?.toolUses ?? record.toolUses` → `record.toolUses`, `activity?.lifetimeUsage` → `record.lifetimeUsage`, `activity?.session` → `record.session`.
|
|
241
|
+
Update `foreground-runner.ts`: read stats from record instead of tracker for final result.
|
|
242
|
+
Capture `recordRef` for streaming phase.
|
|
243
|
+
Update `conversation-viewer.test.ts` if any tests mock activity stats.
|
|
244
|
+
Commit: `refactor: read stats from AgentRecord in UI consumers (#144)`
|
|
245
|
+
|
|
246
|
+
6. **Red→Green: Replace `NotificationDeps` with plain parameters.**
|
|
247
|
+
Update `notification.test.ts`: change `makeDeps()` → individual parameters in `NotificationManager` constructor calls.
|
|
248
|
+
Implement: remove `NotificationDeps` interface, change constructor to plain parameters, replace `this.deps.*` with `this.*`.
|
|
249
|
+
Update `index.ts`: change `NotificationManager` construction to pass individual arguments.
|
|
250
|
+
Commit: `refactor: dissolve NotificationDeps into plain constructor parameters (#144)`
|
|
251
|
+
|
|
252
|
+
## Risks and Mitigations
|
|
253
|
+
|
|
254
|
+
| Risk | Mitigation |
|
|
255
|
+
| ------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
256
|
+
| Foreground streaming reads stale `recordRef` before `onSessionCreated` fires | Before `onSessionCreated`, both tracker and record have zero stats — behavior is unchanged. Capture `recordRef` as `undefined` initially and guard with `??`. |
|
|
257
|
+
| Widget reads `a.toolUses` but `a` is the snapshot from `listAgents()` — might be stale | `listAgents()` returns live `AgentRecord` references (not copies), so `a.toolUses` reflects the latest record-observer increment. |
|
|
258
|
+
| Removing `lifetimeUsage` from tracker breaks `formatLifetimeTokens(fgState)` in foreground runner | Step 5 explicitly migrates this callsite to `formatLifetimeTokens(record)`. The TDD order places the removal (step 4) before the migration (step 5), but step 4's `pnpm run check` will catch the type error and both steps can be merged if needed. |
|
|
259
|
+
| `buildDetails` in `helpers.ts` accepts an `activity` parameter for `turnCount`/`maxTurns` | `turnCount` and `maxTurns` remain on the tracker — only `toolUses` and `lifetimeUsage` are removed. `buildDetails` reads `record.toolUses` and `record.lifetimeUsage` already; it reads `activity?.turnCount` and `activity?.maxTurns` which remain valid. |
|
|
260
|
+
|
|
261
|
+
## Open Questions
|
|
262
|
+
|
|
263
|
+
- None — the architecture doc prescribes the exact changes and the design is straightforward.
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 148
|
|
3
|
+
issue_title: "Split AgentWidget rendering from lifecycle (Phase 9, Step P)"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Split AgentWidget rendering from lifecycle
|
|
7
|
+
|
|
8
|
+
## Problem Statement
|
|
9
|
+
|
|
10
|
+
`AgentWidget` (374 lines) mixes rendering, lifecycle management, spinner animation, state filtering, and status bar management in a single class.
|
|
11
|
+
`renderWidget` alone is ~109 lines, and `renderFinishedLine` adds another ~40.
|
|
12
|
+
The constructor takes 3 concrete collaborators (`AgentManager`, `Map<string, AgentActivityTracker>`, `AgentTypeRegistry`) with no interface extraction.
|
|
13
|
+
Rendering logic cannot be unit-tested without instantiating the full widget with its lifecycle machinery.
|
|
14
|
+
|
|
15
|
+
## Goals
|
|
16
|
+
|
|
17
|
+
- Extract pure rendering functions from `AgentWidget` into `ui/widget-renderer.ts`.
|
|
18
|
+
- Make `AgentWidget` a thin lifecycle/polling wrapper that delegates to pure render functions.
|
|
19
|
+
- Enable direct unit testing of rendering logic with plain data — no widget lifecycle, no mocks for `setInterval`/`setWidget`/`requestRender`.
|
|
20
|
+
|
|
21
|
+
## Non-Goals
|
|
22
|
+
|
|
23
|
+
- Changing the visual output of the widget (this is a pure refactor).
|
|
24
|
+
- Extracting the status bar logic into a separate module (could follow up).
|
|
25
|
+
- Narrowing the `AgentManager` dependency to an interface (tracked separately in the architecture doc).
|
|
26
|
+
- Injecting `truncateToWidth` (tracked as #147, Step O — an independent track).
|
|
27
|
+
|
|
28
|
+
## Background
|
|
29
|
+
|
|
30
|
+
### Dependency: #144 (Step L) — Consolidate observation model
|
|
31
|
+
|
|
32
|
+
Issue #144 is **closed/implemented**.
|
|
33
|
+
The renderer now reads stats (`toolUses`, `lifetimeUsage`, `compactionCount`) from `AgentRecord` and live UI state (`activeTools`, `responseText`, `turnCount`, `maxTurns`) from `AgentActivityTracker`.
|
|
34
|
+
No dual-counting fallback exists.
|
|
35
|
+
|
|
36
|
+
### Existing pure helpers
|
|
37
|
+
|
|
38
|
+
`ui/display.ts` already contains stateless formatting functions (`formatMs`, `formatTurns`, `formatSessionTokens`, `describeActivity`, `getDisplayName`, `getPromptModeLabel`, `SPINNER`, `ERROR_STATUSES`, `Theme`).
|
|
39
|
+
The new `widget-renderer.ts` will consume these — it does not duplicate them.
|
|
40
|
+
|
|
41
|
+
### Rendering data flow
|
|
42
|
+
|
|
43
|
+
The widget's `renderWidget` currently:
|
|
44
|
+
|
|
45
|
+
1. Calls `this.manager.listAgents()` to get `AgentRecord[]`.
|
|
46
|
+
2. Categorizes into running/queued/finished.
|
|
47
|
+
3. Filters finished agents via `this.shouldShowFinished()`.
|
|
48
|
+
4. Looks up `this.agentActivity.get(a.id)` for live stats.
|
|
49
|
+
5. Calls `this.registry` for display names.
|
|
50
|
+
6. Reads `this.widgetFrame` for spinner animation.
|
|
51
|
+
7. Assembles tree-style lines with overflow logic.
|
|
52
|
+
|
|
53
|
+
Steps 3–7 are pure given the right inputs.
|
|
54
|
+
Steps 1–2 are also pure categorization.
|
|
55
|
+
|
|
56
|
+
## Design Overview
|
|
57
|
+
|
|
58
|
+
### Separation of concerns
|
|
59
|
+
|
|
60
|
+
The rendering extraction splits the widget into two layers:
|
|
61
|
+
|
|
62
|
+
1. **`widget-renderer.ts`** — Pure functions that accept data and return `string[]`.
|
|
63
|
+
No `this`, no timers, no SDK types, no side effects.
|
|
64
|
+
2. **`agent-widget.ts`** — Thin lifecycle wrapper that owns timers, UICtx, finished-turn aging, and calls the renderer with live data.
|
|
65
|
+
|
|
66
|
+
### Renderer input shape
|
|
67
|
+
|
|
68
|
+
Rather than passing the full `AgentRecord` class (which carries mutation methods and phase collaborators), the renderer receives a plain data slice:
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
/** Minimal agent snapshot for rendering — no class methods, no mutation surface. */
|
|
72
|
+
export interface WidgetAgent {
|
|
73
|
+
readonly id: string;
|
|
74
|
+
readonly type: SubagentType;
|
|
75
|
+
readonly status: string;
|
|
76
|
+
readonly description: string;
|
|
77
|
+
readonly toolUses: number;
|
|
78
|
+
readonly startedAt: number;
|
|
79
|
+
readonly completedAt?: number;
|
|
80
|
+
readonly error?: string;
|
|
81
|
+
readonly lifetimeUsage?: Readonly<LifetimeUsage>;
|
|
82
|
+
readonly compactionCount: number;
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
This is structurally compatible with `AgentRecord` (the class satisfies it), so no mapping code is needed at the call site — `listAgents()` returns `AgentRecord[]` which satisfies `WidgetAgent[]`.
|
|
87
|
+
|
|
88
|
+
### Renderer input for activity
|
|
89
|
+
|
|
90
|
+
Activity state is read from `AgentActivityTracker`.
|
|
91
|
+
The renderer needs a read-only view per agent:
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
/** Read-only activity snapshot for widget rendering. */
|
|
95
|
+
export interface WidgetActivity {
|
|
96
|
+
readonly activeTools: ReadonlyMap<string, string>;
|
|
97
|
+
readonly responseText: string;
|
|
98
|
+
readonly turnCount: number;
|
|
99
|
+
readonly maxTurns?: number;
|
|
100
|
+
readonly session?: SessionLike;
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
`AgentActivityTracker` already satisfies this structurally (it exposes these as getters).
|
|
105
|
+
|
|
106
|
+
### Agent config lookup
|
|
107
|
+
|
|
108
|
+
The renderer needs `getDisplayName` and `getPromptModeLabel`, which take a `SubagentType` and an `AgentConfigLookup`.
|
|
109
|
+
The renderer accepts `AgentConfigLookup` (the existing interface from `agent-types.ts`) — not the concrete `AgentTypeRegistry` class.
|
|
110
|
+
|
|
111
|
+
### Renderer API
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
/** Pure rendering of the widget body. Returns lines to display. */
|
|
115
|
+
export function renderWidgetLines(params: {
|
|
116
|
+
agents: readonly WidgetAgent[];
|
|
117
|
+
activityMap: ReadonlyMap<string, WidgetActivity>;
|
|
118
|
+
registry: AgentConfigLookup;
|
|
119
|
+
spinnerFrame: number;
|
|
120
|
+
terminalWidth: number;
|
|
121
|
+
shouldShowFinished: (agentId: string, status: string) => boolean;
|
|
122
|
+
}): string[];
|
|
123
|
+
|
|
124
|
+
/** Pure rendering of a single finished agent line (no tree connector prefix). */
|
|
125
|
+
export function renderFinishedLine(
|
|
126
|
+
agent: WidgetAgent,
|
|
127
|
+
activity: WidgetActivity | undefined,
|
|
128
|
+
registry: AgentConfigLookup,
|
|
129
|
+
theme: Theme,
|
|
130
|
+
): string;
|
|
131
|
+
|
|
132
|
+
/** Pure rendering of a single running agent (header + activity lines, no tree connector prefix). */
|
|
133
|
+
export function renderRunningLines(
|
|
134
|
+
agent: WidgetAgent,
|
|
135
|
+
activity: WidgetActivity | undefined,
|
|
136
|
+
registry: AgentConfigLookup,
|
|
137
|
+
spinnerFrame: number,
|
|
138
|
+
theme: Theme,
|
|
139
|
+
): [header: string, activity: string];
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
The top-level `renderWidgetLines` encapsulates the full categorization, overflow logic, and tree-connector fixup.
|
|
143
|
+
The per-agent functions are exported for fine-grained testing.
|
|
144
|
+
|
|
145
|
+
The `shouldShowFinished` callback is injected rather than re-implementing the aging logic inside the renderer, keeping the renderer pure and the aging state in the widget.
|
|
146
|
+
|
|
147
|
+
### Call site in AgentWidget
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
// Inside renderWidget(tui, theme):
|
|
151
|
+
const w = tui.terminal.columns;
|
|
152
|
+
return renderWidgetLines({
|
|
153
|
+
agents: this.manager.listAgents(),
|
|
154
|
+
activityMap: this.agentActivity,
|
|
155
|
+
registry: this.registry,
|
|
156
|
+
spinnerFrame: this.widgetFrame,
|
|
157
|
+
terminalWidth: w,
|
|
158
|
+
shouldShowFinished: (id, status) => this.shouldShowFinished(id, status),
|
|
159
|
+
});
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
The widget's `renderWidget` method shrinks to ~5 lines.
|
|
163
|
+
|
|
164
|
+
### Tell-Don't-Ask verification
|
|
165
|
+
|
|
166
|
+
The renderer receives pre-collected data and returns formatted strings.
|
|
167
|
+
It does not reach through collaborators — it reads flat fields from `WidgetAgent` and `WidgetActivity`.
|
|
168
|
+
The widget tells the renderer "render this data"; the renderer returns lines.
|
|
169
|
+
No Law of Demeter violations.
|
|
170
|
+
|
|
171
|
+
## Module-Level Changes
|
|
172
|
+
|
|
173
|
+
### New file: `src/ui/widget-renderer.ts`
|
|
174
|
+
|
|
175
|
+
- `WidgetAgent` interface (structural subset of `AgentRecord`).
|
|
176
|
+
- `WidgetActivity` interface (structural subset of `AgentActivityTracker`).
|
|
177
|
+
- `renderWidgetLines()` — top-level rendering with categorization, overflow, tree connectors.
|
|
178
|
+
- `renderFinishedLine()` — single finished-agent line.
|
|
179
|
+
- `renderRunningLines()` — single running-agent header + activity pair.
|
|
180
|
+
- Imports from `display.ts` (`SPINNER`, `ERROR_STATUSES`, `formatMs`, `formatTurns`, `formatSessionTokens`, `describeActivity`, `getDisplayName`, `getPromptModeLabel`, `Theme`), from `usage.ts` (`getLifetimeTotal`, `getSessionContextPercent`, `LifetimeUsage`, `SessionLike`), and from `@earendil-works/pi-tui` (`truncateToWidth`).
|
|
181
|
+
|
|
182
|
+
### Modified: `src/ui/agent-widget.ts`
|
|
183
|
+
|
|
184
|
+
- Remove `renderWidget()` method body — replace with call to `renderWidgetLines()`.
|
|
185
|
+
- Remove `renderFinishedLine()` method entirely.
|
|
186
|
+
- Remove direct imports of display helpers and usage helpers that are now only consumed by `widget-renderer.ts`.
|
|
187
|
+
- Keep: constructor, `setUICtx`, `onTurnStart`, `ensureTimer`, `shouldShowFinished`, `markFinished`, `update`, `dispose`, `UICtx` type, `MAX_WIDGET_LINES`.
|
|
188
|
+
- The inline type on `renderFinishedLine`'s parameter `a` is replaced by the `WidgetAgent` import.
|
|
189
|
+
|
|
190
|
+
### New file: `test/widget-renderer.test.ts`
|
|
191
|
+
|
|
192
|
+
- Unit tests for `renderWidgetLines`, `renderFinishedLine`, `renderRunningLines`.
|
|
193
|
+
- Uses plain data objects (no mocks for `AgentManager`, `setInterval`, or SDK).
|
|
194
|
+
- Stub `Theme` matching the pattern in `test/renderer.test.ts`.
|
|
195
|
+
|
|
196
|
+
### No changes to
|
|
197
|
+
|
|
198
|
+
- `src/index.ts` — the widget is constructed the same way; renderer is internal to the widget module.
|
|
199
|
+
- `src/ui/display.ts` — unchanged; consumed by the new renderer.
|
|
200
|
+
- `src/usage.ts` — unchanged.
|
|
201
|
+
|
|
202
|
+
## Test Impact Analysis
|
|
203
|
+
|
|
204
|
+
1. The extraction enables direct unit testing of widget rendering that was previously impossible — testing `renderWidget` required constructing a full `AgentWidget` with mocked `AgentManager`, fake timers, and a stubbed UICtx.
|
|
205
|
+
The new tests cover: finished-agent line formatting (all status variants), running-agent header/activity rendering, overflow logic, tree-connector fixup, empty-state handling, and `shouldShowFinished` filtering.
|
|
206
|
+
2. No existing tests become redundant — there are currently **no** unit tests for `AgentWidget` rendering.
|
|
207
|
+
The existing `display.test.ts` tests lower-level formatters and remains as-is.
|
|
208
|
+
3. `renderer.test.ts` tests the notification renderer — unrelated, stays as-is.
|
|
209
|
+
|
|
210
|
+
## TDD Order
|
|
211
|
+
|
|
212
|
+
1. **Red → Green:** Test `renderFinishedLine` for a completed agent (success icon, stats, duration).
|
|
213
|
+
Commit: `test: add renderFinishedLine tests for completed status`
|
|
214
|
+
|
|
215
|
+
2. **Red → Green:** Test `renderFinishedLine` for error/aborted/steered/stopped statuses (icon and status text variations).
|
|
216
|
+
Commit: `test: renderFinishedLine error and terminal status variants`
|
|
217
|
+
|
|
218
|
+
3. **Red → Green:** Test `renderRunningLines` (spinner frame, stats, activity description, token display).
|
|
219
|
+
Commit: `test: add renderRunningLines tests`
|
|
220
|
+
|
|
221
|
+
4. **Red → Green:** Test `renderWidgetLines` — basic case with one running agent (heading, tree connectors).
|
|
222
|
+
Commit: `test: renderWidgetLines single running agent`
|
|
223
|
+
|
|
224
|
+
5. **Red → Green:** Test `renderWidgetLines` — mixed running + finished + queued, verifying categorization and ordering.
|
|
225
|
+
Commit: `test: renderWidgetLines mixed agent states`
|
|
226
|
+
|
|
227
|
+
6. **Red → Green:** Test `renderWidgetLines` — overflow cap with many agents, verifying the priority (running > queued > finished) and overflow summary line.
|
|
228
|
+
Commit: `test: renderWidgetLines overflow behavior`
|
|
229
|
+
|
|
230
|
+
7. **Red → Green:** Test `renderWidgetLines` — empty state returns `[]`; finished-only state uses dim heading.
|
|
231
|
+
Commit: `test: renderWidgetLines empty and finished-only states`
|
|
232
|
+
|
|
233
|
+
8. **Green → Refactor:** Extract `renderFinishedLine`, `renderRunningLines`, and `renderWidgetLines` into `src/ui/widget-renderer.ts`.
|
|
234
|
+
All tests pass against the extracted module.
|
|
235
|
+
Commit: `refactor: extract widget rendering into widget-renderer`
|
|
236
|
+
|
|
237
|
+
9. **Green → Refactor:** Wire `AgentWidget.renderWidget()` to delegate to `renderWidgetLines()`.
|
|
238
|
+
Remove the inlined rendering logic from `agent-widget.ts`.
|
|
239
|
+
Remove unused imports.
|
|
240
|
+
Commit: `refactor: AgentWidget delegates rendering to widget-renderer`
|
|
241
|
+
|
|
242
|
+
10. **Verify:** Run full test suite (`pnpm vitest run`) and type check (`pnpm run check`).
|
|
243
|
+
Commit: none (verification only).
|
|
244
|
+
|
|
245
|
+
## Risks and Mitigations
|
|
246
|
+
|
|
247
|
+
| Risk | Mitigation |
|
|
248
|
+
| -------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
249
|
+
| Structural compatibility between `AgentRecord` and `WidgetAgent` could drift if `AgentRecord` renames a field. | TypeScript's structural checking catches this at the call site in `agent-widget.ts` — `listAgents()` returns `AgentRecord[]` which must satisfy `readonly WidgetAgent[]`. |
|
|
250
|
+
| `truncateToWidth` is an external dependency (`@earendil-works/pi-tui`) in the renderer. | Step O (#147) will inject it; for now, the renderer imports it directly, matching the current widget behavior. |
|
|
251
|
+
| Overflow logic is complex and hand-tested — extraction could introduce subtle line-count bugs. | TDD steps 4–7 exercise overflow edge cases before the extraction step. The extraction is a mechanical move with tests already passing. |
|
|
252
|
+
|
|
253
|
+
## Open Questions
|
|
254
|
+
|
|
255
|
+
- None — the design follows the architecture doc's Step P specification and the dependency (#144) is already implemented.
|