@gotgenes/pi-subagents 6.6.0 → 6.8.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 +30 -0
- package/docs/architecture/architecture.md +36 -34
- package/docs/plans/0110-agent-activity-tracker.md +297 -0
- package/docs/plans/0111-split-agent-record-lifecycle.md +582 -0
- package/docs/retro/0110-agent-activity-tracker.md +44 -0
- package/docs/retro/0118-settings-manager-apply-methods.md +40 -0
- package/package.json +1 -1
- package/src/agent-manager.ts +41 -21
- package/src/agent-record.ts +45 -34
- package/src/execution-state.ts +17 -0
- package/src/index.ts +2 -1
- package/src/notification-state.ts +27 -0
- package/src/notification.ts +12 -9
- package/src/record-observer.ts +6 -7
- package/src/runtime.ts +3 -2
- package/src/service-adapter.ts +8 -7
- package/src/tools/agent-tool.ts +13 -24
- package/src/tools/get-result-tool.ts +7 -5
- package/src/tools/steer-tool.ts +8 -6
- package/src/ui/agent-activity-tracker.ts +108 -0
- package/src/ui/agent-menu.ts +4 -4
- package/src/ui/agent-widget.ts +4 -17
- package/src/ui/conversation-viewer.ts +3 -3
- package/src/ui/ui-observer.ts +16 -23
- package/src/worktree-state.ts +35 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,36 @@ 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.8.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v6.7.0...pi-subagents-v6.8.0) (2026-05-21)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* add ExecutionState interface ([#111](https://github.com/gotgenes/pi-packages/issues/111)) ([4f33e09](https://github.com/gotgenes/pi-packages/commit/4f33e09b8578509985308b225111cfdfce22bb06))
|
|
14
|
+
* add NotificationState class ([#111](https://github.com/gotgenes/pi-packages/issues/111)) ([cbee34d](https://github.com/gotgenes/pi-packages/commit/cbee34d1944e411dd8593ac0cfbaa3fa66982d00))
|
|
15
|
+
* add WorktreeState class ([#111](https://github.com/gotgenes/pi-packages/issues/111)) ([eddb0c8](https://github.com/gotgenes/pi-packages/commit/eddb0c84311d420f3b50c2861f7585cfd8d1037f))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Documentation
|
|
19
|
+
|
|
20
|
+
* plan AgentRecord lifecycle state split ([#111](https://github.com/gotgenes/pi-packages/issues/111)) ([c271d89](https://github.com/gotgenes/pi-packages/commit/c271d8931729e620e46f05e82cdca8276dbd0a6d))
|
|
21
|
+
* **retro:** add retro notes for issue [#110](https://github.com/gotgenes/pi-packages/issues/110) ([4a48c65](https://github.com/gotgenes/pi-packages/commit/4a48c655752902f86b864f224e209831f73e241d))
|
|
22
|
+
* update architecture doc for AgentRecord lifecycle split ([#111](https://github.com/gotgenes/pi-packages/issues/111)) ([b0d8967](https://github.com/gotgenes/pi-packages/commit/b0d8967601eb7aca41cf59ce7f40f399ccc51fec))
|
|
23
|
+
|
|
24
|
+
## [6.7.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v6.6.0...pi-subagents-v6.7.0) (2026-05-21)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
### Features
|
|
28
|
+
|
|
29
|
+
* add AgentActivityTracker class ([#110](https://github.com/gotgenes/pi-packages/issues/110)) ([151308a](https://github.com/gotgenes/pi-packages/commit/151308ad3da569d72e40495bece502281dc302a8))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
### Documentation
|
|
33
|
+
|
|
34
|
+
* plan AgentActivityTracker class ([#110](https://github.com/gotgenes/pi-packages/issues/110)) ([8f5e56b](https://github.com/gotgenes/pi-packages/commit/8f5e56ba0a29730e060bac4658bb4953ce36d4a9))
|
|
35
|
+
* **retro:** add retro notes for issue [#118](https://github.com/gotgenes/pi-packages/issues/118) ([1959e52](https://github.com/gotgenes/pi-packages/commit/1959e52d8b150bbc90da72edd93dc6f2ec315e22))
|
|
36
|
+
* update architecture doc — mark A3 done, fix Map type ([#110](https://github.com/gotgenes/pi-packages/issues/110)) ([463e997](https://github.com/gotgenes/pi-packages/commit/463e9974db21df17f4b2578825f3c67bc1e90b29))
|
|
37
|
+
|
|
8
38
|
## [6.6.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v6.5.0...pi-subagents-v6.6.0) (2026-05-21)
|
|
9
39
|
|
|
10
40
|
|
|
@@ -82,7 +82,7 @@ Record statistics (tool uses, token usage, compaction counts) are updated by `re
|
|
|
82
82
|
UI streaming (active tools, response text, turn counts) is handled by `ui/ui-observer.ts`, which subscribes to the same session events independently.
|
|
83
83
|
Neither observer wraps or forwards the other — both subscribe directly to the session.
|
|
84
84
|
|
|
85
|
-
The widget reads agent state by polling a shared `Map<string,
|
|
85
|
+
The widget reads agent state by polling a shared `Map<string, AgentActivityTracker>` on `SubagentRuntime` every 80 ms. The conversation viewer subscribes directly to `AgentSession` objects.
|
|
86
86
|
|
|
87
87
|
Cross-extension consumers use the typed `SubagentsService` API published via `Symbol.for("@gotgenes/pi-subagents:service")` on `globalThis`.
|
|
88
88
|
|
|
@@ -377,17 +377,17 @@ Each step is sequenced so it makes the next step easier.
|
|
|
377
377
|
|
|
378
378
|
### Current smells
|
|
379
379
|
|
|
380
|
-
| Smell
|
|
381
|
-
|
|
|
382
|
-
| ~~Global mutable state~~
|
|
383
|
-
| Closure bag as class
|
|
384
|
-
| Mutable state bag |
|
|
385
|
-
| ~~Settings relay~~
|
|
386
|
-
| Post-construction mutation |
|
|
387
|
-
| Fire-and-forget callbacks
|
|
388
|
-
| Duplicate `SpawnOptions`
|
|
389
|
-
| Type dumping ground
|
|
390
|
-
| Wide dependency bags
|
|
380
|
+
| Smell | Location | Evidence |
|
|
381
|
+
| ------------------------------ | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
382
|
+
| ~~Global mutable state~~ | ~~`agent-types.ts`~~ | **Fixed #108**: `AgentTypeRegistry` class; `reloadCustomAgents` callback removed from `AgentToolDeps` and `AgentMenuDeps` |
|
|
383
|
+
| Closure bag as class | `createNotificationSystem()` | Returns 4 functions sharing closure state (`pendingNudges`, timers) |
|
|
384
|
+
| ~~Mutable state bag~~ | ~~`AgentActivity` (7 fields)~~ | **Fixed #110**: `AgentActivityTracker` class; `ui-observer.ts` calls transition methods; widget, notification, agent-tool use read-only accessors |
|
|
385
|
+
| ~~Settings relay~~ | ~~`AgentMenuDeps` (13 fields)~~ | **Fixed #109**: `SettingsManager` class; 6 callback fields collapsed to `settings: SettingsManager`; `AgentMenuDeps` now 8 fields |
|
|
386
|
+
| ~~Post-construction mutation~~ | ~~`AgentRecord` non-transition state~~ | **Fixed #111**: `ExecutionState`, `WorktreeState`, `NotificationState` collaborators; `pendingSteers` moved to `AgentManager`; stats encapsulated behind mutation methods |
|
|
387
|
+
| Fire-and-forget callbacks | `AgentManagerOptions` | `onStart`, `onComplete`, `onCompact` wired as closures in `index.ts` |
|
|
388
|
+
| Duplicate `SpawnOptions` | `service.ts` + `agent-manager.ts` | Two incompatible shapes (JSON-friendly vs runtime types) with the same name |
|
|
389
|
+
| Type dumping ground | `types.ts` | `NotificationDetails` used only by notification/renderer; ~~`DEFAULT_AGENT_NAMES` moved to `AgentTypeRegistry` (#108)~~; `AgentConfig` (21 fields) consumers use 2–4 each |
|
|
390
|
+
| Wide dependency bags | `AgentToolDeps` (7), `AgentMenuDeps` (8) | Settings narrowed (#109); registry narrowed (#108); more narrowing planned in D steps |
|
|
391
391
|
|
|
392
392
|
### Step A: Extract state into classes (foundation, parallel)
|
|
393
393
|
|
|
@@ -422,23 +422,25 @@ Each owns the full consequence chain: normalize → set in memory → notify cal
|
|
|
422
422
|
|
|
423
423
|
Impact: eliminates LoD / Tell-Don't-Ask violation in `showSettings`; menu no longer coordinates between settings and manager.
|
|
424
424
|
|
|
425
|
-
#### A3. `AgentActivityTracker` class (#110)
|
|
425
|
+
#### ~~A3. `AgentActivityTracker` class (#110)~~ — **Done**
|
|
426
426
|
|
|
427
|
-
|
|
428
|
-
`ui-observer.ts` calls
|
|
429
|
-
The notification system, widget, and agent-tool
|
|
427
|
+
Wrapped the 7-field mutable `AgentActivity` interface in an `AgentActivityTracker` class (`src/ui/agent-activity-tracker.ts`).
|
|
428
|
+
`ui-observer.ts` calls transition methods (`onToolStart`, `onToolEnd`, `onMessageStart`, `onMessageUpdate`, `onTurnEnd`, `onUsageUpdate`, `setSession`).
|
|
429
|
+
The notification system, widget, conversation viewer, and agent-tool use read-only accessors.
|
|
430
|
+
The shared map on `SubagentRuntime` is now `Map<string, AgentActivityTracker>`.
|
|
430
431
|
|
|
431
432
|
Impact: eliminates output-argument writes in `ui-observer.ts`, makes the mutation contract explicit.
|
|
432
433
|
|
|
433
|
-
### Step B: Split `AgentRecord` lifecycle state (#111)
|
|
434
|
+
### ~~Step B: Split `AgentRecord` lifecycle state (#111)~~ — **Done**
|
|
434
435
|
|
|
435
|
-
|
|
436
|
-
The fix is not setter methods — it's splitting along lifecycle boundaries so each object is born complete.
|
|
436
|
+
Split post-construction mutation into phase-specific collaborators, each born complete:
|
|
437
437
|
|
|
438
|
-
- **`
|
|
439
|
-
-
|
|
440
|
-
-
|
|
441
|
-
- **`pendingSteers`**
|
|
438
|
+
- **`ExecutionState`** (`session`, `outputFile`) — constructed in `onSessionCreated`, attached as `record.execution`.
|
|
439
|
+
- **`WorktreeState`** (`path`, `branch`, `cleanupResult`) — constructed at worktree setup, attached as `record.worktreeState`.
|
|
440
|
+
- **`NotificationState`** (`toolCallId`, `resultConsumed`) — constructed by agent-tool after spawn, attached as `record.notification`.
|
|
441
|
+
- **`pendingSteers`** moved to `Map<string, string[]>` on `AgentManager`; steer-tool and service-adapter call `manager.queueSteer()`.
|
|
442
|
+
- Stats (`toolUses`, `lifetimeUsage`, `compactionCount`) encapsulated behind mutation methods (`incrementToolUses`, `addUsage`, `incrementCompactions`) with read-only getters.
|
|
443
|
+
- `AgentRecordInit` trimmed from 19 optional fields to 4 construction-time fields.
|
|
442
444
|
|
|
443
445
|
Each piece is born complete at the moment its information is available.
|
|
444
446
|
The record doesn't accumulate half-baked state — it receives fully constructed collaborators.
|
|
@@ -485,17 +487,17 @@ The 654-line file splits along a natural seam.
|
|
|
485
487
|
|
|
486
488
|
### Expected impact
|
|
487
489
|
|
|
488
|
-
| Metric | Before
|
|
489
|
-
| ------------------------------------------ |
|
|
490
|
-
| Module-scoped mutable state | ~~1 (`agent-types.ts` Map)~~
|
|
491
|
-
| Closure-bag "classes" | ~~2~~ 1 (`createNotificationSystem`; settings free functions **fixed #109**)
|
|
492
|
-
| Externally-mutated state bags | 2 (`
|
|
493
|
-
| `AgentManagerOptions` fields | 8
|
|
494
|
-
| `AgentToolDeps` fields | ~~9~~ **7** (−6 registry #108, −1 settings #109 → +1 settings obj)
|
|
495
|
-
| `AgentMenuDeps` fields | ~~13~~ **8** (−6 settings #109 collapsed to 1; −1 registry #108)
|
|
496
|
-
| `SpawnOptions` callback fields | 1 (`onSessionCreated`)
|
|
497
|
-
| Callbacks threaded through deps | ~~8~~ 0 remaining settings callbacks (**fixed #109**); `emitEvent` ×3 remain
|
|
498
|
-
| Types in `types.ts` without a natural home | 4
|
|
490
|
+
| Metric | Before | After |
|
|
491
|
+
| ------------------------------------------ | -------------------------------------------------------------------------------- | ------- |
|
|
492
|
+
| Module-scoped mutable state | ~~1 (`agent-types.ts` Map)~~ | **0** ✓ |
|
|
493
|
+
| Closure-bag "classes" | ~~2~~ 1 (`createNotificationSystem`; settings free functions **fixed #109**) | 0 |
|
|
494
|
+
| Externally-mutated state bags | ~~2~~ ~~1~~ **0** (`AgentRecord` **fixed #111**; `AgentActivity` **fixed #110**) | 0 ✓ |
|
|
495
|
+
| `AgentManagerOptions` fields | 8 | 5 |
|
|
496
|
+
| `AgentToolDeps` fields | ~~9~~ **7** (−6 registry #108, −1 settings #109 → +1 settings obj) | ~5 |
|
|
497
|
+
| `AgentMenuDeps` fields | ~~13~~ **8** (−6 settings #109 collapsed to 1; −1 registry #108) | ~6 ✓ |
|
|
498
|
+
| `SpawnOptions` callback fields | 1 (`onSessionCreated`) | 0 |
|
|
499
|
+
| Callbacks threaded through deps | ~~8~~ 0 remaining settings callbacks (**fixed #109**); `emitEvent` ×3 remain | 0 |
|
|
500
|
+
| Types in `types.ts` without a natural home | 4 | 0 |
|
|
499
501
|
|
|
500
502
|
### Dependency graph
|
|
501
503
|
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 110
|
|
3
|
+
issue_title: "refactor(pi-subagents): wrap AgentActivity in AgentActivityTracker class"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Wrap AgentActivity in AgentActivityTracker class
|
|
7
|
+
|
|
8
|
+
## Problem Statement
|
|
9
|
+
|
|
10
|
+
`AgentActivity` is a 7-field mutable interface (`activeTools`, `toolUses`, `responseText`, `session`, `turnCount`, `maxTurns`, `lifetimeUsage`) shared across 4 modules.
|
|
11
|
+
`ui-observer.ts` writes raw fields on it (output arguments), the widget and conversation viewer read them, and the agent-tool creates empty instances and stuffs them into a shared `Map`.
|
|
12
|
+
The mutation contract is implicit — callers know which fields to set by convention, not by API.
|
|
13
|
+
|
|
14
|
+
This is Phase 7, Step A3 in the architecture doc.
|
|
15
|
+
|
|
16
|
+
## Goals
|
|
17
|
+
|
|
18
|
+
- Wrap `AgentActivity` in an `AgentActivityTracker` class with explicit transition methods.
|
|
19
|
+
- Replace the output-argument writes in `ui-observer.ts` with tracker method calls.
|
|
20
|
+
- Expose read-only accessors for the state the widget, notification system, conversation viewer, and agent-tool need.
|
|
21
|
+
- Change the shared `Map<string, AgentActivity>` on `SubagentRuntime` to `Map<string, AgentActivityTracker>`.
|
|
22
|
+
- Preserve all existing behavior — this is a pure encapsulation refactor.
|
|
23
|
+
|
|
24
|
+
## Non-Goals
|
|
25
|
+
|
|
26
|
+
- Splitting `AgentRecord` lifecycle state (#111) — deferred to Step B.
|
|
27
|
+
- Replacing `AgentManager` callbacks with an observer (#112) — deferred to Step C.
|
|
28
|
+
- Narrowing `AgentToolDeps` or `AgentMenuDeps` further (#114) — deferred to Step D2.
|
|
29
|
+
- Changing `createNotificationSystem` from closure to class (#116) — deferred to Step E2.
|
|
30
|
+
|
|
31
|
+
## Background
|
|
32
|
+
|
|
33
|
+
### Who writes AgentActivity today
|
|
34
|
+
|
|
35
|
+
`ui-observer.ts` (`subscribeUIObserver`) is the sole writer.
|
|
36
|
+
It subscribes to session events and mutates the state object directly:
|
|
37
|
+
|
|
38
|
+
- `state.activeTools.set(...)` / `state.activeTools.delete(...)` on tool start/end
|
|
39
|
+
- `state.toolUses++` on tool end
|
|
40
|
+
- `state.responseText = ""` on message start, `state.responseText += delta` on message update
|
|
41
|
+
- `state.turnCount++` on turn end
|
|
42
|
+
- `addUsage(state.lifetimeUsage, ...)` on message end with assistant usage
|
|
43
|
+
|
|
44
|
+
The agent-tool also writes `session` after session creation (`fgState.session = session`, `bgState.session = session`).
|
|
45
|
+
|
|
46
|
+
### Who reads AgentActivity today
|
|
47
|
+
|
|
48
|
+
| Consumer | Fields read |
|
|
49
|
+
| ---------------------------------------------- | ---------------------------------------------------------------------------------------------- |
|
|
50
|
+
| `agent-widget.ts` (widget render) | `activeTools`, `responseText`, `toolUses`, `turnCount`, `maxTurns`, `lifetimeUsage`, `session` |
|
|
51
|
+
| `conversation-viewer.ts` | `toolUses`, `lifetimeUsage`, `session`, `activeTools`, `responseText` |
|
|
52
|
+
| `notification.ts` (`buildNotificationDetails`) | `turnCount`, `maxTurns` |
|
|
53
|
+
| `agent-tool.ts` (foreground streaming) | `toolUses`, `turnCount`, `maxTurns`, `activeTools`, `responseText`, `lifetimeUsage` |
|
|
54
|
+
| `agent-menu.ts` (conversation viewer launch) | passes to `ConversationViewer` |
|
|
55
|
+
|
|
56
|
+
### Who creates AgentActivity today
|
|
57
|
+
|
|
58
|
+
`createAgentActivity()` in `agent-tool.ts` — a factory function that returns a plain object with defaults.
|
|
59
|
+
|
|
60
|
+
### AGENTS.md constraints
|
|
61
|
+
|
|
62
|
+
- One concern per file: the tracker gets its own module.
|
|
63
|
+
- Avoid `any`: use typed accessors.
|
|
64
|
+
- Output arguments: this refactor eliminates them from `ui-observer.ts`.
|
|
65
|
+
- Keep modules focused: the tracker owns its mutable state; consumers use read-only accessors.
|
|
66
|
+
|
|
67
|
+
## Design Overview
|
|
68
|
+
|
|
69
|
+
### AgentActivityTracker class
|
|
70
|
+
|
|
71
|
+
New file: `src/ui/agent-activity-tracker.ts`.
|
|
72
|
+
|
|
73
|
+
The class owns the 7 mutable fields and exposes:
|
|
74
|
+
|
|
75
|
+
1. Transition methods (the write surface — called by `ui-observer.ts` and agent-tool):
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
onToolStart(toolName: string): void // adds to activeTools map
|
|
79
|
+
onToolEnd(toolName: string): void // removes from activeTools, increments toolUses
|
|
80
|
+
onMessageStart(): void // resets responseText
|
|
81
|
+
onMessageUpdate(delta: string): void // appends to responseText
|
|
82
|
+
onTurnEnd(): void // increments turnCount
|
|
83
|
+
onUsageUpdate(usage: UsageDelta): void // accumulates into lifetimeUsage
|
|
84
|
+
setSession(session: SessionLike): void // one-time session binding
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
2. Read-only accessors (the read surface — used by widget, notification, conversation viewer, agent-tool streaming):
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
get activeTools(): ReadonlyMap<string, string>
|
|
91
|
+
get toolUses(): number
|
|
92
|
+
get responseText(): string
|
|
93
|
+
get session(): SessionLike | undefined
|
|
94
|
+
get turnCount(): number
|
|
95
|
+
get maxTurns(): number | undefined
|
|
96
|
+
get lifetimeUsage(): Readonly<LifetimeUsage>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The constructor accepts `maxTurns?: number` (set at creation time, immutable).
|
|
100
|
+
|
|
101
|
+
### UsageDelta type
|
|
102
|
+
|
|
103
|
+
A narrow type for the usage values passed to `onUsageUpdate`:
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
interface UsageDelta { input: number; output: number; cacheWrite: number }
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
This matches the shape `addUsage` already expects and avoids coupling to the full `LifetimeUsage` type name for what is logically a delta.
|
|
110
|
+
|
|
111
|
+
### activeTools key strategy
|
|
112
|
+
|
|
113
|
+
The current `activeTools` Map uses `toolName + "_" + Date.now()` as a key to allow multiple concurrent tools with the same name.
|
|
114
|
+
`onToolStart` returns `void` and generates the key internally (same `Date.now()` strategy).
|
|
115
|
+
`onToolEnd(toolName)` finds and removes the first matching entry (same logic as today).
|
|
116
|
+
|
|
117
|
+
### subscribeUIObserver changes
|
|
118
|
+
|
|
119
|
+
`subscribeUIObserver` changes its second parameter from `state: AgentActivity` to `tracker: AgentActivityTracker`.
|
|
120
|
+
Instead of writing fields, it calls tracker methods:
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
// Before:
|
|
124
|
+
state.activeTools.set(event.toolName + "_" + Date.now(), event.toolName);
|
|
125
|
+
// After:
|
|
126
|
+
tracker.onToolStart(event.toolName);
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Consumer interface
|
|
130
|
+
|
|
131
|
+
Readers continue to access the same properties but through getters.
|
|
132
|
+
Since the tracker exposes matching property names, consumer code like `bg.turnCount` and `bg.toolUses` remains syntactically identical — only the type annotation changes from `AgentActivity` to `AgentActivityTracker`.
|
|
133
|
+
|
|
134
|
+
The `AgentActivity` interface is removed entirely.
|
|
135
|
+
All references migrate to `AgentActivityTracker`.
|
|
136
|
+
|
|
137
|
+
### Map type change
|
|
138
|
+
|
|
139
|
+
`SubagentRuntime.agentActivity` changes from `Map<string, AgentActivity>` to `Map<string, AgentActivityTracker>`.
|
|
140
|
+
All dependency bags that pass this map (`AgentToolDeps`, `AgentMenuDeps`, `NotificationDeps`, `AgentWidget` constructor) update their type annotations.
|
|
141
|
+
|
|
142
|
+
### createAgentActivity replacement
|
|
143
|
+
|
|
144
|
+
The factory function in `agent-tool.ts` is replaced by `new AgentActivityTracker(maxTurns)`.
|
|
145
|
+
|
|
146
|
+
## Module-Level Changes
|
|
147
|
+
|
|
148
|
+
### New file
|
|
149
|
+
|
|
150
|
+
| File | What |
|
|
151
|
+
| ---------------------------------- | ---------------------------------------------------------------------------- |
|
|
152
|
+
| `src/ui/agent-activity-tracker.ts` | `AgentActivityTracker` class with transition methods and read-only accessors |
|
|
153
|
+
|
|
154
|
+
### New test file
|
|
155
|
+
|
|
156
|
+
| File | What |
|
|
157
|
+
| ---------------------------------------- | ------------------------------------------------------------- |
|
|
158
|
+
| `test/ui/agent-activity-tracker.test.ts` | Unit tests for all transition methods and read-only accessors |
|
|
159
|
+
|
|
160
|
+
### Modified files
|
|
161
|
+
|
|
162
|
+
| File | What changes |
|
|
163
|
+
| ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
164
|
+
| `src/ui/ui-observer.ts` | Accept `AgentActivityTracker` instead of `AgentActivity`; call transition methods instead of writing fields |
|
|
165
|
+
| `src/ui/agent-widget.ts` | Remove `AgentActivity` interface; import `AgentActivityTracker`; update `Map` type and read sites (property names stay the same) |
|
|
166
|
+
| `src/ui/conversation-viewer.ts` | Import `AgentActivityTracker` instead of `AgentActivity`; update parameter type |
|
|
167
|
+
| `src/ui/agent-menu.ts` | Import `AgentActivityTracker` instead of `AgentActivity`; update `Map` type |
|
|
168
|
+
| `src/tools/agent-tool.ts` | Import `AgentActivityTracker`; replace `createAgentActivity()` with `new AgentActivityTracker()`; replace `fgState.session = session` with `fgState.setSession(session)`; update `Map` type in `AgentToolDeps` |
|
|
169
|
+
| `src/notification.ts` | Import `AgentActivityTracker` instead of `AgentActivity`; update `buildNotificationDetails` parameter and `NotificationDeps.agentActivity` Map type |
|
|
170
|
+
| `src/runtime.ts` | Import `AgentActivityTracker`; change `agentActivity` Map type; update `AgentWidget` import (type only change since `AgentActivity` moves) |
|
|
171
|
+
| `src/index.ts` | No changes (already references `runtime.agentActivity` by reference, the type flows through) |
|
|
172
|
+
|
|
173
|
+
### Modified test files
|
|
174
|
+
|
|
175
|
+
| File | What changes |
|
|
176
|
+
| ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
177
|
+
| `test/ui/ui-observer.test.ts` | Replace `makeActivity()` factory with `new AgentActivityTracker()`; access state through getters; assertions on accessor values instead of raw field reads |
|
|
178
|
+
| `test/notification.test.ts` | Replace `as AgentActivity` casts with `new AgentActivityTracker()`; set up tracker state via transition methods |
|
|
179
|
+
| `test/tools/agent-tool.test.ts` | Update `Map<string, AgentActivity>` type to `Map<string, AgentActivityTracker>` |
|
|
180
|
+
|
|
181
|
+
### Removed exports
|
|
182
|
+
|
|
183
|
+
| Symbol | Was in | Replaced by |
|
|
184
|
+
| ---------------------------------- | ------------------------- | ------------------------------------------------------------------ |
|
|
185
|
+
| `AgentActivity` (interface) | `src/ui/agent-widget.ts` | `AgentActivityTracker` class in `src/ui/agent-activity-tracker.ts` |
|
|
186
|
+
| `createAgentActivity()` (function) | `src/tools/agent-tool.ts` | `new AgentActivityTracker(maxTurns)` |
|
|
187
|
+
|
|
188
|
+
Grep verification: `AgentActivity` is referenced in 7 source files and 3 test files (listed above) — all accounted for in the modified files list.
|
|
189
|
+
|
|
190
|
+
## Test Impact Analysis
|
|
191
|
+
|
|
192
|
+
### New unit tests enabled
|
|
193
|
+
|
|
194
|
+
The `AgentActivityTracker` class enables focused unit tests for each transition method in isolation:
|
|
195
|
+
|
|
196
|
+
- `onToolStart` / `onToolEnd` — concurrent tool tracking, correct toolUses increment
|
|
197
|
+
- `onMessageStart` / `onMessageUpdate` — response text lifecycle
|
|
198
|
+
- `onTurnEnd` — turn counting from initial value
|
|
199
|
+
- `onUsageUpdate` — accumulation semantics
|
|
200
|
+
- `setSession` — one-time binding
|
|
201
|
+
- Read-only accessors — verify consumers cannot mutate internal state
|
|
202
|
+
|
|
203
|
+
These were previously impossible to test without going through `subscribeUIObserver` and a mock session.
|
|
204
|
+
|
|
205
|
+
### Existing tests that simplify
|
|
206
|
+
|
|
207
|
+
`test/ui/ui-observer.test.ts` — currently constructs a plain `AgentActivity` object and asserts on raw field mutations.
|
|
208
|
+
After the change, it constructs an `AgentActivityTracker` and the assertions read the same properties through accessors.
|
|
209
|
+
The `makeActivity()` helper is replaced by `new AgentActivityTracker()`.
|
|
210
|
+
The test logic stays the same (event → tracker state check) but the assertions use getter-backed properties.
|
|
211
|
+
|
|
212
|
+
### Existing tests that stay as-is
|
|
213
|
+
|
|
214
|
+
`test/notification.test.ts` — the pure helper tests (`escapeXml`, `getStatusLabel`, `formatTaskNotification`, `buildNotificationDetails`) only need their `AgentActivity` type references updated to `AgentActivityTracker`.
|
|
215
|
+
The notification system integration tests that cast `{} as AgentActivity` need minimal updates to construct a real tracker instead.
|
|
216
|
+
|
|
217
|
+
`test/tools/agent-tool.test.ts` — only the `Map` type annotation changes.
|
|
218
|
+
|
|
219
|
+
## TDD Order
|
|
220
|
+
|
|
221
|
+
### 1. Red/green: AgentActivityTracker class — transition methods and read-only accessors
|
|
222
|
+
|
|
223
|
+
Test file: `test/ui/agent-activity-tracker.test.ts`
|
|
224
|
+
|
|
225
|
+
Tests:
|
|
226
|
+
|
|
227
|
+
- Constructor sets initial state (`turnCount: 1`, empty `activeTools`, `toolUses: 0`, empty `responseText`, zero `lifetimeUsage`, `maxTurns` from constructor arg, `session` undefined)
|
|
228
|
+
- `onToolStart` adds entry to `activeTools`
|
|
229
|
+
- `onToolEnd` removes entry and increments `toolUses`
|
|
230
|
+
- `onToolEnd` with no matching tool is a no-op (defensive)
|
|
231
|
+
- Multiple concurrent tools with same name tracked independently
|
|
232
|
+
- `onMessageStart` resets `responseText` to empty
|
|
233
|
+
- `onMessageUpdate` appends delta to `responseText`
|
|
234
|
+
- `onTurnEnd` increments `turnCount`
|
|
235
|
+
- `onUsageUpdate` accumulates into `lifetimeUsage`
|
|
236
|
+
- `setSession` stores the session reference
|
|
237
|
+
- Read-only: `activeTools` returns `ReadonlyMap`
|
|
238
|
+
- Read-only: `lifetimeUsage` returns `Readonly<LifetimeUsage>`
|
|
239
|
+
|
|
240
|
+
Commit: `feat: add AgentActivityTracker class (#110)`
|
|
241
|
+
|
|
242
|
+
### 2. Red/green: migrate ui-observer to use AgentActivityTracker
|
|
243
|
+
|
|
244
|
+
Update `src/ui/ui-observer.ts` to accept `AgentActivityTracker` and call transition methods.
|
|
245
|
+
Update `test/ui/ui-observer.test.ts`: replace `makeActivity()` with `new AgentActivityTracker()`, read state through accessors.
|
|
246
|
+
|
|
247
|
+
All existing test scenarios must pass unchanged (same events → same observable state).
|
|
248
|
+
|
|
249
|
+
Commit: `refactor: migrate ui-observer to AgentActivityTracker (#110)`
|
|
250
|
+
|
|
251
|
+
### 3. Migrate agent-widget and conversation-viewer
|
|
252
|
+
|
|
253
|
+
Update `src/ui/agent-widget.ts`: remove `AgentActivity` interface, import `AgentActivityTracker`, update `Map` type and constructor parameter.
|
|
254
|
+
Update `src/ui/conversation-viewer.ts`: import `AgentActivityTracker`, update parameter type.
|
|
255
|
+
|
|
256
|
+
No test changes needed — widget and conversation viewer are not unit-tested (they render UI).
|
|
257
|
+
|
|
258
|
+
Commit: `refactor: migrate widget and conversation-viewer to AgentActivityTracker (#110)`
|
|
259
|
+
|
|
260
|
+
### 4. Migrate agent-tool
|
|
261
|
+
|
|
262
|
+
Update `src/tools/agent-tool.ts`: import `AgentActivityTracker`, replace `createAgentActivity()` with `new AgentActivityTracker()`, replace `fgState.session = session` with `fgState.setSession(session)`, update `AgentToolDeps` Map type.
|
|
263
|
+
Update `test/tools/agent-tool.test.ts`: update Map type.
|
|
264
|
+
|
|
265
|
+
Commit: `refactor: migrate agent-tool to AgentActivityTracker (#110)`
|
|
266
|
+
|
|
267
|
+
### 5. Migrate notification and agent-menu
|
|
268
|
+
|
|
269
|
+
Update `src/notification.ts`: import `AgentActivityTracker`, update `buildNotificationDetails` parameter and `NotificationDeps` Map type.
|
|
270
|
+
Update `src/ui/agent-menu.ts`: import `AgentActivityTracker`, update Map type in `AgentMenuDeps`.
|
|
271
|
+
Update `test/notification.test.ts`: replace `as AgentActivity` casts with real `AgentActivityTracker` instances.
|
|
272
|
+
|
|
273
|
+
Commit: `refactor: migrate notification and agent-menu to AgentActivityTracker (#110)`
|
|
274
|
+
|
|
275
|
+
### 6. Migrate runtime and clean up
|
|
276
|
+
|
|
277
|
+
Update `src/runtime.ts`: import `AgentActivityTracker`, change `agentActivity` Map type.
|
|
278
|
+
Remove any remaining `AgentActivity` references.
|
|
279
|
+
Run `pnpm run check` to verify no type errors remain.
|
|
280
|
+
|
|
281
|
+
Commit: `refactor: complete AgentActivityTracker migration, remove AgentActivity interface (#110)`
|
|
282
|
+
|
|
283
|
+
## Risks and Mitigations
|
|
284
|
+
|
|
285
|
+
| Risk | Mitigation |
|
|
286
|
+
| --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
287
|
+
| Read-only getters change identity semantics for `activeTools` and `lifetimeUsage` | The `ReadonlyMap` and `Readonly<LifetimeUsage>` types restrict writes at compile time. Consumers already only read these values, so no runtime change. Return the internal mutable reference cast to readonly (no copy overhead). |
|
|
288
|
+
| Spreading a class instance loses methods | Grep for `{ ...activity` or `{ ...bg` patterns — none found in current code. The `buildDetails` function spreads `detailBase` (a plain object), not the activity. |
|
|
289
|
+
| `onToolEnd` matching logic fragility | Port the exact same `for...of` + `break` pattern from `ui-observer.ts`. New unit tests cover this independently. |
|
|
290
|
+
| `turnCount` initial value of 1 (not 0) | The current `createAgentActivity` sets `turnCount: 1`. The tracker constructor preserves this. A dedicated test asserts the initial value. |
|
|
291
|
+
| Test files casting `{} as AgentActivity` | Replace with real `AgentActivityTracker` instances. Where tests only need `turnCount` and `maxTurns` (e.g., `buildNotificationDetails`), construct a tracker and call `onTurnEnd` to set up the desired state. |
|
|
292
|
+
|
|
293
|
+
## Open Questions
|
|
294
|
+
|
|
295
|
+
- None — the issue and architecture doc are prescriptive about the approach.
|
|
296
|
+
The only design latitude is whether `onToolStart` returns a key (for symmetric `onToolEnd(key)`) or whether `onToolEnd(toolName)` does a lookup.
|
|
297
|
+
The plan uses `onToolEnd(toolName)` with lookup to match the existing pattern and avoid threading a key through the session event handler.
|