@gotgenes/pi-subagents 6.14.1 → 6.16.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 +28 -0
- package/docs/architecture/architecture.md +14 -30
- package/docs/plans/0144-consolidate-observation-model.md +263 -0
- package/docs/plans/0145-decompose-execute-push-ctx-to-boundary.md +290 -0
- package/docs/retro/0145-decompose-execute-push-ctx-to-boundary.md +56 -0
- package/package.json +1 -1
- package/src/agent-manager.ts +7 -9
- package/src/agent-record.ts +11 -0
- package/src/index.ts +27 -13
- package/src/notification.ts +21 -24
- package/src/service-adapter.ts +19 -17
- package/src/tools/agent-tool.ts +56 -113
- package/src/tools/background-spawner.ts +34 -52
- package/src/tools/foreground-runner.ts +43 -61
- package/src/tools/get-result-tool.ts +3 -3
- package/src/tools/spawn-config.ts +146 -0
- 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 +3 -4
- package/src/ui/conversation-viewer.ts +3 -3
- package/src/ui/ui-observer.ts +1 -12
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.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v6.15.0...pi-subagents-v6.16.0) (2026-05-23)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* 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))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Documentation
|
|
17
|
+
|
|
18
|
+
* 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))
|
|
19
|
+
* plan consolidate observation model ([#144](https://github.com/gotgenes/pi-packages/issues/144)) ([9aa2c85](https://github.com/gotgenes/pi-packages/commit/9aa2c8508079c6ae847662631afd223e8966e12e))
|
|
20
|
+
* **retro:** add retro notes for issue [#145](https://github.com/gotgenes/pi-packages/issues/145) ([2d23081](https://github.com/gotgenes/pi-packages/commit/2d230817d53357a62eb752b56f8b1c8ce4af718c))
|
|
21
|
+
|
|
22
|
+
## [6.15.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v6.14.1...pi-subagents-v6.15.0) (2026-05-23)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
### Features
|
|
26
|
+
|
|
27
|
+
* extract resolveSpawnConfig pure function ([#145](https://github.com/gotgenes/pi-packages/issues/145)) ([e89724a](https://github.com/gotgenes/pi-packages/commit/e89724a87480713529160d0fa23975becbcfe162))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
### Documentation
|
|
31
|
+
|
|
32
|
+
* plan decompose execute and push ctx to boundary ([#145](https://github.com/gotgenes/pi-packages/issues/145)) ([aae7d7b](https://github.com/gotgenes/pi-packages/commit/aae7d7b4e04ab0dddedd2a0f9f2b806719956ced))
|
|
33
|
+
* update architecture doc for completed Step M ([#145](https://github.com/gotgenes/pi-packages/issues/145)) ([33ec0c7](https://github.com/gotgenes/pi-packages/commit/33ec0c73479076c180381dcc1cb4106ba635f33f))
|
|
34
|
+
* update plan with injected collaborators for ctx elimination ([#145](https://github.com/gotgenes/pi-packages/issues/145)) ([76bb57b](https://github.com/gotgenes/pi-packages/commit/76bb57b4b5190078ded8685907f0878640031e13))
|
|
35
|
+
|
|
8
36
|
## [6.14.1](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v6.14.0...pi-subagents-v6.14.1) (2026-05-23)
|
|
9
37
|
|
|
10
38
|
|
|
@@ -617,13 +617,11 @@ Phase 9 targets the next layer: observation model consolidation, `ExtensionConte
|
|
|
617
617
|
|
|
618
618
|
| Smell | Location | Evidence | Severity |
|
|
619
619
|
| ------------------------------------------------ | --------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
|
|
620
|
-
| Dual observation | `record-observer.ts`, `ui-observer.ts` | Both independently count tool uses and accumulate lifetime usage from the same session events; consumers use `activity?.toolUses ?? record.toolUses` fallbacks | High |
|
|
621
620
|
| `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
621
|
| 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
|
-
| `record.execution?.session` traversal | 15+ callsites across tools, notification, widget, menu | Callers reach through `ExecutionState` to access session and outputFile - Law of Demeter violation | Medium |
|
|
624
622
|
| Direct SDK import in `conversation-viewer.ts` | `conversation-viewer.test.ts` | Hoisted `vi.mock("@earendil-works/pi-tui")` to intercept `wrapTextWithAnsi` | Low |
|
|
625
623
|
| 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 |
|
|
624
|
+
| `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
625
|
|
|
628
626
|
### Dependency bag convention
|
|
629
627
|
|
|
@@ -634,9 +632,9 @@ Applied incrementally as each step touches a module:
|
|
|
634
632
|
|
|
635
633
|
This eliminates the `deps.` prefix noise across ~124 callsites in 12 modules.
|
|
636
634
|
|
|
637
|
-
### Step L: Consolidate observation model (#144)
|
|
635
|
+
### Step L: Consolidate observation model (#144) ✓
|
|
638
636
|
|
|
639
|
-
|
|
637
|
+
Removed `_toolUses` and `_lifetimeUsage` from `AgentActivityTracker`.
|
|
640
638
|
UI consumers read stats from `AgentRecord` instead of the tracker.
|
|
641
639
|
The UI observer retains event subscriptions for re-render triggers but no longer accumulates stats independently.
|
|
642
640
|
|
|
@@ -647,38 +645,24 @@ Apply the dependency bag convention to touched modules: `NotificationDeps` (4 fi
|
|
|
647
645
|
|
|
648
646
|
Impact: eliminates dual counting; removes `??` fallback pattern from widget and conversation viewer; hides `ExecutionState` structure from consumers.
|
|
649
647
|
|
|
650
|
-
### Step M: Decompose execute and push ExtensionContext to the boundary (#145)
|
|
648
|
+
### Step M: Decompose execute and push ExtensionContext to the boundary (#145) ✓
|
|
651
649
|
|
|
652
|
-
|
|
650
|
+
Extracted config resolution into `resolveSpawnConfig` (pure function in `spawn-config.ts`).
|
|
651
|
+
Injected three collaborators (`buildSnapshot`, `getModelInfo`, `getSessionInfo`) into `createAgentTool` so `execute` no longer reads `ctx` beyond `ctx.ui` (already delegated to `widget.setUICtx`).
|
|
652
|
+
`AgentManager.spawn()` and `spawnAndWait()` accept `ParentSnapshot` instead of `ExtensionContext`.
|
|
653
|
+
`service-adapter.ts` calls `buildParentSnapshot(session.ctx)` at its boundary.
|
|
654
|
+
`foreground-runner` and `background-spawner` receive `ResolvedSpawnConfig` + domain values (`snapshot`, `parentSessionFile`, `parentSessionId`) instead of `ctx`.
|
|
653
655
|
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
3. **Dispatch** (~80 lines) - resume / background / foreground, each passing 14-16 field parameter bags.
|
|
657
|
-
|
|
658
|
-
The config resolution section is working for the dependencies: manually unpacking `resolvedConfig` field by field, computing derived values, then repacking everything into massive objects for `spawnBackground` and `runForeground`.
|
|
659
|
-
The 16-field bags are the symptom - they exist because the resolution happened in the wrong place.
|
|
660
|
-
|
|
661
|
-
The fix has two parts:
|
|
662
|
-
|
|
663
|
-
1. **Extract config resolution** into a pure function (e.g. `resolveSpawnConfig`) that accepts the raw tool params, registry, model info, and settings, and returns a single `ResolvedSpawnConfig` object.
|
|
664
|
-
`execute` becomes: extract ctx → resolve config → dispatch.
|
|
665
|
-
`spawnBackground` and `runForeground` receive `ResolvedSpawnConfig` instead of 16 individual fields.
|
|
666
|
-
2. **Push `ctx` to the boundary.**
|
|
667
|
-
`execute` extracts everything from `ctx` in its first few lines.
|
|
668
|
-
`foreground-runner.ts` and `background-spawner.ts` receive domain values (`snapshot`, `parentSessionFile`, `parentSessionId`) instead of `ctx`.
|
|
669
|
-
`AgentManager.spawn()` and `spawnAndWait()` accept `ParentSnapshot` instead of `ExtensionContext`.
|
|
670
|
-
`service-adapter.ts` calls `buildParentSnapshot(session.ctx)` at its boundary.
|
|
656
|
+
Dissolved `ForegroundDeps`, `BackgroundDeps`, and `AdapterDeps` into plain parameters.
|
|
657
|
+
`AgentToolDeps` is destructured in the `createAgentTool` signature.
|
|
671
658
|
|
|
672
659
|
After this step, `ExtensionContext` appears only in:
|
|
673
660
|
|
|
674
|
-
- `
|
|
661
|
+
- `index.ts` closures (wired at extension startup)
|
|
675
662
|
- `service-adapter.ts` (cross-extension boundary)
|
|
676
|
-
- `index.ts` (extension entry point)
|
|
677
663
|
- Menu handlers (addressed by Step N)
|
|
678
664
|
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
Impact: `execute` drops from ~145 to ~30 lines; eliminates 16-field parameter bags; eliminates 1 `vi.mock()` call in `agent-manager.test.ts`; `foreground-runner` and `background-spawner` tests no longer need `ctx` mocks; `AgentManager` operates entirely on domain types.
|
|
665
|
+
Impact: `execute` dropped from ~145 to ~25 lines; eliminated 16-field parameter bags; eliminated `vi.mock("../src/parent-snapshot.js")` in `agent-manager.test.ts`; foreground/background runner tests no longer need `ctx` mocks; `AgentManager` operates entirely on domain types.
|
|
682
666
|
|
|
683
667
|
### Step N: Narrow UI context for menu handlers (#146)
|
|
684
668
|
|
|
@@ -690,7 +674,7 @@ Creation wizard’s `spawnAndWait` call changes: the narrow `AgentMenuManager.sp
|
|
|
690
674
|
|
|
691
675
|
Apply the dependency bag convention to touched modules: `AgentConfigEditorDeps` (4 fields), `SteerToolDeps` (4 fields), and `GetResultDeps` (4 fields) become plain parameters; `AgentMenuDeps` (8 fields) and `AgentCreationWizardDeps` (5 fields) are destructured in the signature.
|
|
692
676
|
|
|
693
|
-
After Steps M and N, `ExtensionContext` appears only at true boundaries: `
|
|
677
|
+
After Steps M and N, `ExtensionContext` appears only at true boundaries: `index.ts` closures, `service-adapter.ts` (cross-extension bridge), and `index.ts` (extension entry point).
|
|
694
678
|
|
|
695
679
|
Impact: eliminates ~43 `ctx as any` casts across menu, editor, and wizard test files; tests construct a plain object satisfying `MenuUI` with no cast.
|
|
696
680
|
|
|
@@ -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.
|