@gotgenes/pi-subagents 6.9.1 → 6.9.2
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 +9 -0
- package/docs/architecture/architecture.md +20 -21
- package/docs/plans/0116-type-housekeeping.md +351 -0
- package/docs/retro/0115-decompose-agent-tool.md +51 -0
- package/package.json +1 -1
- package/src/agent-manager.ts +2 -1
- package/src/agent-runner.ts +2 -1
- package/src/env.ts +7 -1
- package/src/index.ts +2 -3
- package/src/notification.ts +48 -33
- package/src/parent-snapshot.ts +20 -1
- package/src/prompts.ts +3 -2
- package/src/renderer.ts +1 -1
- package/src/session-config.ts +2 -1
- package/src/types.ts +14 -46
- package/src/ui/agent-menu.ts +1 -1
- package/src/ui/conversation-viewer.ts +27 -10
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,15 @@ 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.9.2](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v6.9.1...pi-subagents-v6.9.2) (2026-05-22)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Documentation
|
|
12
|
+
|
|
13
|
+
* mark E2 type housekeeping done in architecture ([#116](https://github.com/gotgenes/pi-packages/issues/116)) ([89f18a8](https://github.com/gotgenes/pi-packages/commit/89f18a82fac39b4a62be8e440355b859afaa6d2f))
|
|
14
|
+
* plan type housekeeping and small structural cleanups ([#116](https://github.com/gotgenes/pi-packages/issues/116)) ([e1cbd26](https://github.com/gotgenes/pi-packages/commit/e1cbd269961a1bff3b11e2e916733a79c39a087d))
|
|
15
|
+
* **retro:** add retro notes for issue [#115](https://github.com/gotgenes/pi-packages/issues/115) ([05b8809](https://github.com/gotgenes/pi-packages/commit/05b88093f7b70ea886b6c7cb5f0cc96161a95df6))
|
|
16
|
+
|
|
8
17
|
## [6.9.1](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v6.9.0...pi-subagents-v6.9.1) (2026-05-22)
|
|
9
18
|
|
|
10
19
|
|
|
@@ -384,13 +384,13 @@ Each step is sequenced so it makes the next step easier.
|
|
|
384
384
|
| Smell | Location | Evidence |
|
|
385
385
|
| ------------------------------ | -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
386
386
|
| ~~Global mutable state~~ | ~~`agent-types.ts`~~ | **Fixed #108**: `AgentTypeRegistry` class; `reloadCustomAgents` callback removed from `AgentToolDeps` and `AgentMenuDeps` |
|
|
387
|
-
| Closure bag as class
|
|
387
|
+
| ~~Closure bag as class~~ | ~~`createNotificationSystem()`~~ | **Fixed #116**: `NotificationManager` class; `pendingNudges` and timer state are private fields |
|
|
388
388
|
| ~~Mutable state bag~~ | ~~`AgentActivity` (7 fields)~~ | **Fixed #110**: `AgentActivityTracker` class; `ui-observer.ts` calls transition methods; widget, notification, agent-tool use read-only accessors |
|
|
389
389
|
| ~~Settings relay~~ | ~~`AgentMenuDeps` (13 fields)~~ | **Fixed #109**: `SettingsManager` class; 6 callback fields collapsed to `settings: SettingsManager`; `AgentMenuDeps` now 8 fields |
|
|
390
390
|
| ~~Post-construction mutation~~ | ~~`AgentRecord` non-transition state~~ | **Fixed #111**: `ExecutionState`, `WorktreeState`, `NotificationState` collaborators; `pendingSteers` moved to `AgentManager`; stats encapsulated behind mutation methods |
|
|
391
391
|
| ~~Fire-and-forget callbacks~~ | ~~`AgentManagerOptions`~~ | **Fixed #112**: `AgentManagerObserver` interface; `observer` replaces 3 callbacks; `index.ts` constructs one observer object instead of 3 closure lambdas |
|
|
392
392
|
| ~~Duplicate `SpawnOptions`~~ | ~~`service.ts` + `agent-manager.ts`~~ | **Fixed #113**: internal type renamed to `AgentSpawnConfig`; public `SpawnOptions` in `service.ts` unchanged |
|
|
393
|
-
| Type dumping ground
|
|
393
|
+
| ~~Type dumping ground~~ | ~~`types.ts`~~ | **Fixed #116**: `NotificationDetails` → `notification.ts`; `ParentSnapshot` → `parent-snapshot.ts`; `EnvInfo` → `env.ts`; `AgentIdentity` and `AgentPromptConfig` narrow subsets |
|
|
394
394
|
| ~~Wide dependency bags~~ | ~~`AgentToolDeps` (9), `AgentMenuDeps` (8)~~ | **Fixed #114**: `AgentToolDeps` 9 → 6; `AgentMenuDeps` 8 → 7; `emitEvent` removed from both; description text derived from registry; `agentActivity` narrowed to typed interfaces |
|
|
395
395
|
|
|
396
396
|
### Step A: Extract state into classes (foundation, parallel)
|
|
@@ -487,29 +487,28 @@ Fixed two upstream API gaps before extracting: `onSessionCreated` now receives `
|
|
|
487
487
|
Extracted `foreground-runner.ts` (~175 lines) and `background-spawner.ts` (~116 lines).
|
|
488
488
|
`agent-tool.ts` reduced from 579 → 411 lines (orchestrator + rendering).
|
|
489
489
|
|
|
490
|
-
#### E2. Type housekeeping (#116)
|
|
490
|
+
#### ~~E2. Type housekeeping (#116)~~ — **Done**
|
|
491
491
|
|
|
492
|
-
-
|
|
493
|
-
-
|
|
494
|
-
-
|
|
495
|
-
-
|
|
496
|
-
-
|
|
497
|
-
-
|
|
498
|
-
- Define narrow `AgentConfig` subset interfaces for consumers that use 2–4 fields of the 21-field type.
|
|
492
|
+
- Moved `NotificationDetails` from `types.ts` to `notification.ts`.
|
|
493
|
+
- Moved `ParentSnapshot` from `types.ts` to `parent-snapshot.ts` (`DEFAULT_AGENT_NAMES` was already moved in #108).
|
|
494
|
+
- Moved `EnvInfo` from `types.ts` to `env.ts`.
|
|
495
|
+
- Converted `createNotificationSystem` closure to `NotificationManager` class.
|
|
496
|
+
- Converted `ConversationViewer` constructor from 7 positional parameters to `ConversationViewerOptions` bag.
|
|
497
|
+
- Defined `AgentIdentity` and `AgentPromptConfig` narrow subsets; `AgentConfig extends` both; `buildAgentPrompt` narrowed to `AgentPromptConfig`.
|
|
499
498
|
|
|
500
499
|
### Expected impact
|
|
501
500
|
|
|
502
|
-
| Metric | Before
|
|
503
|
-
| ------------------------------------------ |
|
|
504
|
-
| Module-scoped mutable state | ~~1 (`agent-types.ts` Map)~~
|
|
505
|
-
| Closure-bag "classes" | ~~2~~ 1 (`createNotificationSystem
|
|
506
|
-
| Externally-mutated state bags | ~~2~~ ~~1~~ **0** (`AgentRecord` **fixed #111**; `AgentActivity` **fixed #110**)
|
|
507
|
-
| `AgentManagerOptions` fields | 8
|
|
508
|
-
| `AgentToolDeps` fields | ~~9~~ **6** (−3 text fields derived; −1 emitEvent → observer; activity narrowed)
|
|
509
|
-
| `AgentMenuDeps` fields | ~~13~~ **7** (−6 settings #109; −1 registry #108; −1 dead emitEvent #114)
|
|
510
|
-
| `SpawnOptions` callback fields | 1 (`onSessionCreated`)
|
|
511
|
-
| Callbacks threaded through deps | ~~8~~ 0 remaining settings callbacks (**fixed #109**); `emitEvent` ×3 remain
|
|
512
|
-
| Types in `types.ts` without a natural home | 4
|
|
501
|
+
| Metric | Before | After |
|
|
502
|
+
| ------------------------------------------ | -------------------------------------------------------------------------------------- | ------- |
|
|
503
|
+
| Module-scoped mutable state | ~~1 (`agent-types.ts` Map)~~ | **0** ✓ |
|
|
504
|
+
| Closure-bag "classes" | ~~2~~ ~~1~~ **0** (`createNotificationSystem` **fixed #116**; settings **fixed #109**) | 0 ✓ |
|
|
505
|
+
| Externally-mutated state bags | ~~2~~ ~~1~~ **0** (`AgentRecord` **fixed #111**; `AgentActivity` **fixed #110**) | 0 ✓ |
|
|
506
|
+
| `AgentManagerOptions` fields | 8 | 5 |
|
|
507
|
+
| `AgentToolDeps` fields | ~~9~~ **6** (−3 text fields derived; −1 emitEvent → observer; activity narrowed) | 6 ✓ |
|
|
508
|
+
| `AgentMenuDeps` fields | ~~13~~ **7** (−6 settings #109; −1 registry #108; −1 dead emitEvent #114) | 7 ✓ |
|
|
509
|
+
| `SpawnOptions` callback fields | 1 (`onSessionCreated`) | 0 |
|
|
510
|
+
| Callbacks threaded through deps | ~~8~~ 0 remaining settings callbacks (**fixed #109**); `emitEvent` ×3 remain | 0 |
|
|
511
|
+
| Types in `types.ts` without a natural home | ~~4~~ **0** ✓ (all moved #116) | 0 ✓ |
|
|
513
512
|
|
|
514
513
|
### Dependency graph
|
|
515
514
|
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 116
|
|
3
|
+
issue_title: "refactor(pi-subagents): type housekeeping and small structural cleanups"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Type housekeeping and small structural cleanups
|
|
7
|
+
|
|
8
|
+
## Problem Statement
|
|
9
|
+
|
|
10
|
+
`types.ts` is a type dumping ground — it collects types that don't have a natural home because the module that should own them didn't exist yet.
|
|
11
|
+
With foundation extractions (#108 registry, `parent-snapshot.ts`, `env.ts`, `notification.ts`) now in place, most of these types have a natural home.
|
|
12
|
+
Several other small housekeeping items share the same "polish" character: a closure-bag factory that is a class in disguise, a positional constructor that creates test friction, and a 22-field `AgentConfig` interface consumed by modules that touch 2–4 of its fields.
|
|
13
|
+
|
|
14
|
+
## Goals
|
|
15
|
+
|
|
16
|
+
- Move `NotificationDetails` to `notification.ts`, `ParentSnapshot` to `parent-snapshot.ts`, and `EnvInfo` to `env.ts`.
|
|
17
|
+
- Convert `createNotificationSystem` closure to a `NotificationManager` class.
|
|
18
|
+
- Convert `ConversationViewer` constructor from 7 positional parameters to an options bag.
|
|
19
|
+
- Define narrow `AgentConfig` subset interfaces for consumers that use only a few fields.
|
|
20
|
+
- Leave `types.ts` containing only genuinely cross-cutting types: `SubagentType`, `MemoryScope`, `IsolationMode`, `AgentConfig`, `AgentInvocation`, `ShellExec`, and re-exports (`AgentRecord`, `ThinkingLevel`).
|
|
21
|
+
|
|
22
|
+
## Non-Goals
|
|
23
|
+
|
|
24
|
+
- Refactoring `AgentConfig` itself (field additions, removals, renames).
|
|
25
|
+
- Changing the `NotificationDeps` interface shape.
|
|
26
|
+
- Modifying `agent-menu.ts` to use narrow subsets — it legitimately reads most `AgentConfig` fields as a config editor/viewer.
|
|
27
|
+
- Touching `invocation-config.ts` — it already receives `AgentConfig | undefined` and only reads invocation-default fields; narrowing its parameter is a separate concern.
|
|
28
|
+
|
|
29
|
+
## Background
|
|
30
|
+
|
|
31
|
+
### Prerequisite status
|
|
32
|
+
|
|
33
|
+
| Issue | Title | Status |
|
|
34
|
+
| ----- | --------------------------------- | ------- |
|
|
35
|
+
| #108 | Extract `AgentTypeRegistry` class | ✅ Done |
|
|
36
|
+
|
|
37
|
+
`DEFAULT_AGENT_NAMES` was already moved to `AgentTypeRegistry.DEFAULT_AGENT_NAMES` as part of #108.
|
|
38
|
+
The remaining work from the issue's checklist is type relocations, two structural conversions, and narrow subset interfaces.
|
|
39
|
+
|
|
40
|
+
### Current `types.ts` exports
|
|
41
|
+
|
|
42
|
+
| Export | Kind | Should stay? |
|
|
43
|
+
| -------------------------------------------------- | --------- | ------------------------------- |
|
|
44
|
+
| `AgentRecord` (re-export) | class | ✅ Cross-cutting |
|
|
45
|
+
| `AgentRecordInit`, `AgentRecordStatus` (re-export) | type | ✅ Cross-cutting |
|
|
46
|
+
| `ThinkingLevel` (re-export) | type | ✅ Cross-cutting |
|
|
47
|
+
| `SubagentType` | type | ✅ Cross-cutting |
|
|
48
|
+
| `MemoryScope` | type | ✅ Cross-cutting |
|
|
49
|
+
| `IsolationMode` | type | ✅ Cross-cutting |
|
|
50
|
+
| `AgentConfig` | interface | ✅ Cross-cutting |
|
|
51
|
+
| `AgentInvocation` | interface | ✅ Cross-cutting |
|
|
52
|
+
| `ShellExec` | type | ✅ Cross-cutting |
|
|
53
|
+
| `NotificationDetails` | interface | ❌ Move to `notification.ts` |
|
|
54
|
+
| `ParentSnapshot` | interface | ❌ Move to `parent-snapshot.ts` |
|
|
55
|
+
| `EnvInfo` | interface | ❌ Move to `env.ts` |
|
|
56
|
+
|
|
57
|
+
### `createNotificationSystem` closure analysis
|
|
58
|
+
|
|
59
|
+
The factory in `notification.ts` shares `pendingNudges` (a `Map`) and timer state across 4 inner functions (`cancelNudge`, `scheduleNudge`, `sendCompletion`, `cleanupCompleted`, `dispose`).
|
|
60
|
+
This is a class in disguise — mutable state + methods that read/write it.
|
|
61
|
+
Converting to a `NotificationManager` class makes the state explicit and lets tests use instance methods directly.
|
|
62
|
+
|
|
63
|
+
### `ConversationViewer` constructor
|
|
64
|
+
|
|
65
|
+
The constructor takes 7 positional parameters:
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
constructor(tui, session, record, activity, theme, done, registry)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Every test must reconstruct all 7 in order.
|
|
72
|
+
An options bag reduces friction and is resilient to new parameters.
|
|
73
|
+
|
|
74
|
+
### `AgentConfig` field usage by consumer
|
|
75
|
+
|
|
76
|
+
| Consumer | Fields used |
|
|
77
|
+
| ---------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
|
|
78
|
+
| `agent-widget.ts` (`getDisplayName`, `getPromptModeLabel`) | `name`, `displayName`, `promptMode` |
|
|
79
|
+
| `tools/helpers.ts` (`formatAgentList`) | `name`, `description`, `model` |
|
|
80
|
+
| `prompts.ts` (`buildAgentPrompt`) | `name`, `promptMode`, `systemPrompt` |
|
|
81
|
+
| `session-config.ts` (`assembleSessionConfig`) | `name`, `model`, `thinking`, `maxTurns`, `extensions`, `skills`, `memory`, `disallowedTools` |
|
|
82
|
+
| `agent-menu.ts` | Nearly all fields (config viewer/editor) |
|
|
83
|
+
|
|
84
|
+
Natural clusters for narrow interfaces:
|
|
85
|
+
|
|
86
|
+
- **`AgentIdentity`**: `name`, `displayName`, `description`, `promptMode` — UI display and agent listing.
|
|
87
|
+
- **`AgentPromptConfig`**: `name`, `promptMode`, `systemPrompt` — prompt assembly.
|
|
88
|
+
|
|
89
|
+
`session-config.ts` uses 8 fields; narrowing it yields limited value vs. complexity.
|
|
90
|
+
`invocation-config.ts` receives `AgentConfig | undefined` and reads invocation-default fields; this is a separate concern.
|
|
91
|
+
|
|
92
|
+
## Design Overview
|
|
93
|
+
|
|
94
|
+
### Type relocations
|
|
95
|
+
|
|
96
|
+
Move each type to its natural home module and re-export from `types.ts` during an interim step.
|
|
97
|
+
After updating all importers to point at the new home, remove the re-export.
|
|
98
|
+
This two-phase approach (move + re-export, then update importers + remove re-export) avoids a big-bang commit.
|
|
99
|
+
|
|
100
|
+
However, since importers are few (2–4 each) and the types are only used internally, a single step per type is simpler: move the type, update all importers in the same commit.
|
|
101
|
+
|
|
102
|
+
### `NotificationManager` class
|
|
103
|
+
|
|
104
|
+
Replace the `createNotificationSystem` factory with a class that owns its state:
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
export class NotificationManager {
|
|
108
|
+
private pendingNudges = new Map<string, ReturnType<typeof setTimeout>>();
|
|
109
|
+
|
|
110
|
+
constructor(private deps: NotificationDeps) {}
|
|
111
|
+
|
|
112
|
+
cancelNudge(key: string): void { /* ... */ }
|
|
113
|
+
sendCompletion(record: AgentRecord): void { /* ... */ }
|
|
114
|
+
cleanupCompleted(id: string): void { /* ... */ }
|
|
115
|
+
dispose(): void { /* ... */ }
|
|
116
|
+
|
|
117
|
+
// private helpers
|
|
118
|
+
private scheduleNudge(key: string, send: () => void, delay?: number): void { /* ... */ }
|
|
119
|
+
private emitIndividualNudge(record: AgentRecord): void { /* ... */ }
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
The `NotificationSystem` interface stays as-is — `NotificationManager implements NotificationSystem`.
|
|
124
|
+
The `NotificationDeps` interface is unchanged.
|
|
125
|
+
Callers in `index.ts` switch from `createNotificationSystem(deps)` to `new NotificationManager(deps)`.
|
|
126
|
+
|
|
127
|
+
Consumer call site (index.ts):
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
const notifications = new NotificationManager({
|
|
131
|
+
sendMessage: (msg, opts) => pi.sendMessage(msg, opts),
|
|
132
|
+
agentActivity: runtime.agentActivity,
|
|
133
|
+
markFinished: (id) => runtime.markFinished(id),
|
|
134
|
+
updateWidget: () => runtime.updateWidget(),
|
|
135
|
+
});
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### `ConversationViewer` options bag
|
|
139
|
+
|
|
140
|
+
Replace positional parameters with a named options interface:
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
export interface ConversationViewerOptions {
|
|
144
|
+
tui: TUI;
|
|
145
|
+
session: AgentSession;
|
|
146
|
+
record: AgentRecord;
|
|
147
|
+
activity: AgentActivityTracker | undefined;
|
|
148
|
+
theme: Theme;
|
|
149
|
+
done: (result: undefined) => void;
|
|
150
|
+
registry: AgentConfigLookup;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export class ConversationViewer {
|
|
154
|
+
constructor(private options: ConversationViewerOptions) {
|
|
155
|
+
// destructure into private fields or use options.field access
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
The existing private fields (`tui`, `session`, `record`, etc.) become reads from `this.options.*` or are destructured in the constructor to named private fields.
|
|
161
|
+
The constructor body (session subscription) is unchanged.
|
|
162
|
+
|
|
163
|
+
### Narrow `AgentConfig` subset interfaces
|
|
164
|
+
|
|
165
|
+
Define two narrow interfaces in `types.ts` and make `AgentConfig` extend them:
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
/** UI display and agent listing — name, display name, description, prompt mode. */
|
|
169
|
+
export interface AgentIdentity {
|
|
170
|
+
name: string;
|
|
171
|
+
displayName?: string;
|
|
172
|
+
description: string;
|
|
173
|
+
promptMode: "replace" | "append";
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Prompt assembly — name, prompt mode, system prompt. */
|
|
177
|
+
export interface AgentPromptConfig {
|
|
178
|
+
name: string;
|
|
179
|
+
promptMode: "replace" | "append";
|
|
180
|
+
systemPrompt: string;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export interface AgentConfig extends AgentIdentity, AgentPromptConfig {
|
|
184
|
+
// remaining fields unchanged
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Then update consumers to accept the narrow interface:
|
|
189
|
+
|
|
190
|
+
- `agent-widget.ts`: `getDisplayName(type, registry)` and `getPromptModeLabel(type, registry)` — these go through `AgentConfigLookup.resolveAgentConfig()`, which returns `AgentConfig`.
|
|
191
|
+
The narrowing applies to the *return type usage*, not the function signature.
|
|
192
|
+
No change needed here — the function already destructures only `displayName`, `name`, `promptMode`.
|
|
193
|
+
- `tools/helpers.ts`: `formatAgentList` reads `description`, `model` — these are not in `AgentIdentity`.
|
|
194
|
+
`model` is session-config territory.
|
|
195
|
+
Leave as-is.
|
|
196
|
+
- `prompts.ts`: `buildAgentPrompt(config: AgentPromptConfig, ...)` — narrow the parameter type.
|
|
197
|
+
- `AgentConfigLookup.resolveAgentConfig()` return type stays `AgentConfig` — the registry always has the full config.
|
|
198
|
+
|
|
199
|
+
Since `AgentConfig extends AgentIdentity, AgentPromptConfig`, any code passing an `AgentConfig` to a function expecting the narrow type works without casts.
|
|
200
|
+
|
|
201
|
+
## Module-Level Changes
|
|
202
|
+
|
|
203
|
+
### Modified files
|
|
204
|
+
|
|
205
|
+
1. **`src/types.ts`**
|
|
206
|
+
- Remove `NotificationDetails`, `ParentSnapshot`, `EnvInfo` interface definitions.
|
|
207
|
+
- Add `AgentIdentity` and `AgentPromptConfig` interfaces.
|
|
208
|
+
- Make `AgentConfig` extend `AgentIdentity` and `AgentPromptConfig`.
|
|
209
|
+
- Remove fields from `AgentConfig` body that are now inherited (`name`, `displayName`, `description`, `promptMode`, `systemPrompt`).
|
|
210
|
+
|
|
211
|
+
2. **`src/notification.ts`**
|
|
212
|
+
- Add `NotificationDetails` interface (moved from `types.ts`).
|
|
213
|
+
- Replace `createNotificationSystem` factory with `NotificationManager` class implementing `NotificationSystem`.
|
|
214
|
+
- Export `NotificationManager` (named export) and keep `NotificationSystem` interface export.
|
|
215
|
+
- Remove `createNotificationSystem` export.
|
|
216
|
+
|
|
217
|
+
3. **`src/parent-snapshot.ts`**
|
|
218
|
+
- Add `ParentSnapshot` interface (moved from `types.ts`).
|
|
219
|
+
|
|
220
|
+
4. **`src/env.ts`**
|
|
221
|
+
- Add `EnvInfo` interface (moved from `types.ts`).
|
|
222
|
+
|
|
223
|
+
5. **`src/prompts.ts`**
|
|
224
|
+
- Change `config` parameter type from `AgentConfig` to `AgentPromptConfig`.
|
|
225
|
+
- Update import to use `AgentPromptConfig` from `./types.js`.
|
|
226
|
+
|
|
227
|
+
6. **`src/index.ts`**
|
|
228
|
+
- Update `NotificationDetails` import from `./types.js` to `./notification.js`.
|
|
229
|
+
- Replace `createNotificationSystem(deps)` with `new NotificationManager(deps)`.
|
|
230
|
+
- Update import: `NotificationManager` from `./notification.js`.
|
|
231
|
+
|
|
232
|
+
7. **`src/renderer.ts`**
|
|
233
|
+
- Update `NotificationDetails` import from `./types.js` to `./notification.js`.
|
|
234
|
+
|
|
235
|
+
8. **`src/agent-runner.ts`**
|
|
236
|
+
- Update `ParentSnapshot` import from `./types.js` to `./parent-snapshot.js`.
|
|
237
|
+
|
|
238
|
+
9. **`src/agent-manager.ts`**
|
|
239
|
+
- Update `ParentSnapshot` import from `./types.js` to `./parent-snapshot.js`.
|
|
240
|
+
|
|
241
|
+
10. **`src/session-config.ts`**
|
|
242
|
+
- Update `EnvInfo` import from `./types.js` to `./env.js`.
|
|
243
|
+
|
|
244
|
+
11. **`src/ui/conversation-viewer.ts`**
|
|
245
|
+
- Define `ConversationViewerOptions` interface.
|
|
246
|
+
- Replace 7 positional constructor parameters with single `options: ConversationViewerOptions` parameter.
|
|
247
|
+
- Assign private fields from `options` in constructor body.
|
|
248
|
+
|
|
249
|
+
### Unchanged files
|
|
250
|
+
|
|
251
|
+
- `src/agent-types.ts` — `DEFAULT_AGENT_NAMES` already moved in #108.
|
|
252
|
+
- `src/invocation-config.ts` — narrowing is a separate concern.
|
|
253
|
+
- `src/ui/agent-menu.ts` — legitimately uses most `AgentConfig` fields.
|
|
254
|
+
|
|
255
|
+
### Test files
|
|
256
|
+
|
|
257
|
+
12. **`test/notification.test.ts`**
|
|
258
|
+
- Replace `createNotificationSystem(deps)` calls with `new NotificationManager(deps)`.
|
|
259
|
+
- Update import.
|
|
260
|
+
- All existing assertions stay — the public API is identical.
|
|
261
|
+
|
|
262
|
+
13. **`test/conversation-viewer.test.ts`**
|
|
263
|
+
- Replace positional `new ConversationViewer(tui, session, record, activity, theme, done, registry)` calls with `new ConversationViewer({ tui, session, record, activity, theme, done, registry })`.
|
|
264
|
+
- All existing assertions stay.
|
|
265
|
+
|
|
266
|
+
14. **`test/prompts.test.ts`**
|
|
267
|
+
- Verify existing test fixtures satisfy `AgentPromptConfig` (they should — tests already pass `name`, `promptMode`, `systemPrompt`).
|
|
268
|
+
- May need to narrow mock type annotations.
|
|
269
|
+
|
|
270
|
+
15. **Other test files importing relocated types** (`test/parent-snapshot.test.ts`, `test/renderer.test.ts`)
|
|
271
|
+
- Update import paths if they import directly from `types.ts` (most import from the source module already).
|
|
272
|
+
|
|
273
|
+
## Test Impact Analysis
|
|
274
|
+
|
|
275
|
+
1. **New unit tests enabled:**
|
|
276
|
+
- `NotificationManager` as a class can be tested with standard `new` + method calls — no factory indirection.
|
|
277
|
+
However, the existing factory tests are already clean and simply switch to `new NotificationManager(deps)`.
|
|
278
|
+
No fundamentally new test capabilities.
|
|
279
|
+
|
|
280
|
+
2. **Existing tests that simplify:**
|
|
281
|
+
- `conversation-viewer.test.ts` — the options bag makes test construction more readable and resilient to parameter additions.
|
|
282
|
+
Each test only needs to specify the fields it cares about (with a helper providing defaults).
|
|
283
|
+
- `prompts.test.ts` — narrower `AgentPromptConfig` parameter means test fixtures can omit 19 irrelevant `AgentConfig` fields.
|
|
284
|
+
|
|
285
|
+
3. **Existing tests that must stay as-is:**
|
|
286
|
+
- `notification.test.ts` — all factory tests stay, just switch constructor syntax.
|
|
287
|
+
- `conversation-viewer.test.ts` — all render and input tests stay, just switch constructor syntax.
|
|
288
|
+
- `parent-snapshot.test.ts`, `env.test.ts`, `renderer.test.ts` — behavior unchanged.
|
|
289
|
+
|
|
290
|
+
## TDD Order
|
|
291
|
+
|
|
292
|
+
1. **Move `NotificationDetails` from `types.ts` to `notification.ts`.**
|
|
293
|
+
Cut the interface from `types.ts`, paste into `notification.ts`.
|
|
294
|
+
Update importers: `index.ts`, `renderer.ts` (change import from `./types.js` to `./notification.js`).
|
|
295
|
+
`notification.ts` already imports from `./types.js` for `AgentRecord` — no circular dependency.
|
|
296
|
+
Run `pnpm run check`.
|
|
297
|
+
Commit: `refactor: move NotificationDetails to notification.ts`
|
|
298
|
+
|
|
299
|
+
2. **Move `ParentSnapshot` from `types.ts` to `parent-snapshot.ts`.**
|
|
300
|
+
Cut the interface from `types.ts`, paste into `parent-snapshot.ts`.
|
|
301
|
+
Update importers: `agent-manager.ts`, `agent-runner.ts` (change import from `./types.js` to `./parent-snapshot.js`).
|
|
302
|
+
Run `pnpm run check`.
|
|
303
|
+
Commit: `refactor: move ParentSnapshot to parent-snapshot.ts`
|
|
304
|
+
|
|
305
|
+
3. **Move `EnvInfo` from `types.ts` to `env.ts`.**
|
|
306
|
+
Cut the interface from `types.ts`, paste into `env.ts`.
|
|
307
|
+
Update importers: `session-config.ts`, `prompts.ts` (change import from `./types.js` to `./env.js`).
|
|
308
|
+
`env.ts` already imports `ShellExec` from `./types.js` — no circular dependency.
|
|
309
|
+
Run `pnpm run check`.
|
|
310
|
+
Commit: `refactor: move EnvInfo to env.ts`
|
|
311
|
+
|
|
312
|
+
4. **Convert `createNotificationSystem` to `NotificationManager` class.**
|
|
313
|
+
Replace the factory with a class implementing `NotificationSystem`.
|
|
314
|
+
Move `pendingNudges` and timer logic to private instance state.
|
|
315
|
+
Convert inner functions to methods.
|
|
316
|
+
Keep `NotificationSystem` interface, `NotificationDeps` interface, and `NUDGE_HOLD_MS` constant unchanged.
|
|
317
|
+
Remove `createNotificationSystem` export, add `NotificationManager` export.
|
|
318
|
+
Update `index.ts`: `new NotificationManager(deps)` instead of `createNotificationSystem(deps)`.
|
|
319
|
+
Update `test/notification.test.ts`: `new NotificationManager(deps)` instead of `createNotificationSystem(deps)`, update import.
|
|
320
|
+
Run `pnpm vitest run test/notification.test.ts`.
|
|
321
|
+
Commit: `refactor: convert createNotificationSystem to NotificationManager class`
|
|
322
|
+
|
|
323
|
+
5. **Convert `ConversationViewer` constructor to options bag.**
|
|
324
|
+
Define `ConversationViewerOptions` interface.
|
|
325
|
+
Replace 7 positional parameters with `options: ConversationViewerOptions`.
|
|
326
|
+
Assign private fields from options in constructor body.
|
|
327
|
+
Update all call sites in `test/conversation-viewer.test.ts` and `src/index.ts` (or wherever `new ConversationViewer(...)` is called).
|
|
328
|
+
Run `pnpm vitest run test/conversation-viewer.test.ts`.
|
|
329
|
+
Commit: `refactor: convert ConversationViewer to options bag constructor`
|
|
330
|
+
|
|
331
|
+
6. **Define narrow `AgentIdentity` and `AgentPromptConfig` interfaces.**
|
|
332
|
+
Add interfaces to `types.ts`.
|
|
333
|
+
Make `AgentConfig` extend both; remove inherited fields from `AgentConfig` body.
|
|
334
|
+
Narrow `prompts.ts` `buildAgentPrompt` parameter from `AgentConfig` to `AgentPromptConfig`.
|
|
335
|
+
Update `test/prompts.test.ts` type annotations if needed.
|
|
336
|
+
Run `pnpm run check` and `pnpm vitest run`.
|
|
337
|
+
Commit: `refactor: define AgentIdentity and AgentPromptConfig subset interfaces`
|
|
338
|
+
|
|
339
|
+
## Risks and Mitigations
|
|
340
|
+
|
|
341
|
+
| Risk | Mitigation |
|
|
342
|
+
| --------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
343
|
+
| Moving types breaks import paths in files not caught by grep | Run `pnpm run check` (full `tsc --noEmit`) after each type relocation step. |
|
|
344
|
+
| `NotificationManager` class changes test mock patterns (e.g., spreading instances) | Tests already use the factory return as an opaque object — switching to `new` is mechanical. No spread patterns in notification tests. |
|
|
345
|
+
| `ConversationViewer` options bag breaks all test call sites at once | All 314 lines of tests use the same 7-arg pattern. A search-and-replace handles it. The options bag is a single-step change. |
|
|
346
|
+
| `AgentConfig extends AgentIdentity, AgentPromptConfig` changes structural compatibility | `extends` is purely additive — existing code passing `AgentConfig` anywhere still works. Narrowed consumers gain type safety, nothing breaks. |
|
|
347
|
+
| `prompts.ts` parameter narrowing breaks callers passing `AgentConfig` | `AgentConfig extends AgentPromptConfig`, so all existing call sites are compatible without changes. |
|
|
348
|
+
|
|
349
|
+
## Open Questions
|
|
350
|
+
|
|
351
|
+
- Whether to define an `AgentSessionConfig` subset for `session-config.ts` (8 fields) — deferred because the narrowing ratio (8 of 22) yields less clarity benefit than `AgentIdentity` (4 of 22) or `AgentPromptConfig` (3 of 22).
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 115
|
|
3
|
+
issue_title: "refactor(pi-subagents): decompose agent-tool.ts into foreground/background modules"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Retro: #115 — decompose agent-tool.ts into foreground/background modules
|
|
7
|
+
|
|
8
|
+
## Final Retrospective (2026-05-21T22:30:00-04:00)
|
|
9
|
+
|
|
10
|
+
### Session summary
|
|
11
|
+
|
|
12
|
+
Decomposed the 579-line `tools/agent-tool.ts` into focused modules: `foreground-runner.ts` (spinner, streaming, cleanup) and `background-spawner.ts` (activity setup, notification wiring), with `agent-tool.ts` remaining as the orchestrator (411 lines).
|
|
13
|
+
Before extracting, fixed two upstream API gaps: widened `onSessionCreated` callback to `(session, record)` to eliminate a `listAgents()` reverse-search, and added `toolCallId` to `AgentSpawnConfig` so the manager wires `NotificationState` at spawn time.
|
|
14
|
+
Test count increased from 641 to 690.
|
|
15
|
+
Released as `pi-subagents-v6.9.1`.
|
|
16
|
+
|
|
17
|
+
### Observations
|
|
18
|
+
|
|
19
|
+
#### What went well
|
|
20
|
+
|
|
21
|
+
- The revised plan (after user feedback) was structurally clean — fixing API gaps first made the extraction trivial; each extracted module received only what it needed without workarounds.
|
|
22
|
+
- TDD execution was smooth: 6 steps, all green after each commit, only minor deviations (type annotation issue, `index.ts` call-site fix).
|
|
23
|
+
|
|
24
|
+
#### What caused friction (agent side)
|
|
25
|
+
|
|
26
|
+
- `wrong-abstraction` (user-caught) — The initial plan was a mechanical code-move that defined 4 new interfaces (`ForegroundRunDeps`, `BackgroundSpawnDeps`, `ForegroundRunParams`, `BackgroundSpawnParams`) to paper over two upstream API gaps: a `listAgents()` reverse-search in the foreground `onSessionCreated` callback, and a post-spawn `record.notification` mutation in the background path.
|
|
27
|
+
The user asked *"What dependencies are still missing for these split tools, that they want, rather than some low level state or collaborators that they have?"*
|
|
28
|
+
— redirecting me to fix the API surface before extracting.
|
|
29
|
+
Impact: entire plan rewritten (~15 minutes), but the revised plan was significantly cleaner and the implementation went smoothly.
|
|
30
|
+
|
|
31
|
+
- `missing-context` (self-identified) — During step 5 (foreground extraction), two tests in `foreground-runner.test.ts` failed because mock sessions were `{}` objects lacking a `subscribe` method required by `subscribeUIObserver`.
|
|
32
|
+
The function's dependency on `SubscribableSession` (requiring `.subscribe()`) wasn't accounted for in the test mock.
|
|
33
|
+
Impact: one test fix cycle (~2 minutes), no rework.
|
|
34
|
+
|
|
35
|
+
- `missing-context` (self-identified) — Annotating `runForeground` with an explicit `Promise<AgentToolResult<any>>` return type widened the content array type from `{ type: "text", text: string }[]` to `(TextContent | ImageContent)[]`, breaking existing `content[0].text` patterns in tests.
|
|
36
|
+
Fixed by removing the explicit annotation and letting TypeScript infer the narrow type.
|
|
37
|
+
Impact: three edit cycles to diagnose and fix (~5 minutes).
|
|
38
|
+
|
|
39
|
+
- `missing-context` (self-identified) — Step 1 removed `listAgents` from `AgentToolManager` but didn't update the construction site in `index.ts`.
|
|
40
|
+
`pnpm run check` caught it in step 4.
|
|
41
|
+
Impact: one-line fix in the same commit, no rework.
|
|
42
|
+
|
|
43
|
+
#### What caused friction (user side)
|
|
44
|
+
|
|
45
|
+
- The initial plan's size and mechanical nature required a user redirect.
|
|
46
|
+
The `/plan-issue` prompt's Design Overview section asks to sketch consumer call sites for *new collaborators*, but the same Tell-Don't-Ask check should apply to *extracted* modules' interactions with their upstream dependencies.
|
|
47
|
+
A prompt tweak could help the agent catch this pattern earlier.
|
|
48
|
+
|
|
49
|
+
### Changes made
|
|
50
|
+
|
|
51
|
+
1. `.pi/prompts/plan-issue.md` — added extraction-specific Tell-Don't-Ask verification step to the Design Overview section: sketch the extracted module's upstream interactions before planning the extraction, fix API gaps first.
|
package/package.json
CHANGED
package/src/agent-manager.ts
CHANGED
|
@@ -14,10 +14,11 @@ import type { AgentRunner } from "./agent-runner.js";
|
|
|
14
14
|
import { AgentTypeRegistry } from "./agent-types.js";
|
|
15
15
|
import { debugLog } from "./debug.js";
|
|
16
16
|
import { NotificationState } from "./notification-state.js";
|
|
17
|
+
import type { ParentSnapshot } from "./parent-snapshot.js";
|
|
17
18
|
import { buildParentSnapshot } from "./parent-snapshot.js";
|
|
18
19
|
import { subscribeRecordObserver } from "./record-observer.js";
|
|
19
20
|
import type { RunConfig } from "./runtime.js";
|
|
20
|
-
import type { AgentInvocation, IsolationMode,
|
|
21
|
+
import type { AgentInvocation, IsolationMode, ShellExec, SubagentType, ThinkingLevel } from "./types.js";
|
|
21
22
|
import type { WorktreeManager } from "./worktree.js";
|
|
22
23
|
import { WorktreeState } from "./worktree-state.js";
|
|
23
24
|
|
package/src/agent-runner.ts
CHANGED
|
@@ -15,9 +15,10 @@ import {
|
|
|
15
15
|
import type { AgentConfigLookup } from "./agent-types.js";
|
|
16
16
|
import { extractText } from "./context.js";
|
|
17
17
|
import { detectEnv } from "./env.js";
|
|
18
|
+
import type { ParentSnapshot } from "./parent-snapshot.js";
|
|
18
19
|
import { assembleSessionConfig } from "./session-config.js";
|
|
19
20
|
import { deriveSubagentSessionDir } from "./session-dir.js";
|
|
20
|
-
import type {
|
|
21
|
+
import type { ShellExec, SubagentType, ThinkingLevel } from "./types.js";
|
|
21
22
|
|
|
22
23
|
/** Names of tools registered by this extension that subagents must NOT inherit. */
|
|
23
24
|
const EXCLUDED_TOOL_NAMES = ["Agent", "get_subagent_result", "steer_subagent"];
|
package/src/env.ts
CHANGED
|
@@ -3,7 +3,13 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { debugLog } from "./debug.js";
|
|
6
|
-
import type {
|
|
6
|
+
import type { ShellExec } from "./types.js";
|
|
7
|
+
|
|
8
|
+
export interface EnvInfo {
|
|
9
|
+
isGitRepo: boolean;
|
|
10
|
+
branch: string;
|
|
11
|
+
platform: string;
|
|
12
|
+
}
|
|
7
13
|
|
|
8
14
|
export async function detectEnv(exec: ShellExec, cwd: string): Promise<EnvInfo> {
|
|
9
15
|
let isGitRepo = false;
|
package/src/index.ts
CHANGED
|
@@ -18,7 +18,7 @@ import { AgentTypeRegistry } from "./agent-types.js";
|
|
|
18
18
|
import { loadCustomAgents } from "./custom-agents.js";
|
|
19
19
|
import { SessionLifecycleHandler, ToolStartHandler } from "./handlers/index.js";
|
|
20
20
|
import { type ModelRegistry, resolveModel } from "./model-resolver.js";
|
|
21
|
-
import { buildEventData,
|
|
21
|
+
import { buildEventData, type NotificationDetails, NotificationManager } from "./notification.js";
|
|
22
22
|
import { createNotificationRenderer } from "./renderer.js";
|
|
23
23
|
import { createSubagentRuntime } from "./runtime.js";
|
|
24
24
|
import { publishSubagentsService, unpublishSubagentsService } from "./service.js";
|
|
@@ -28,7 +28,6 @@ import { createAgentTool } from "./tools/agent-tool.js";
|
|
|
28
28
|
import { createGetResultTool } from "./tools/get-result-tool.js";
|
|
29
29
|
import { getModelLabelFromConfig } from "./tools/helpers.js";
|
|
30
30
|
import { createSteerTool } from "./tools/steer-tool.js";
|
|
31
|
-
import { type NotificationDetails } from "./types.js";
|
|
32
31
|
import { createAgentsMenuHandler } from "./ui/agent-menu.js";
|
|
33
32
|
import {
|
|
34
33
|
AgentWidget,
|
|
@@ -48,7 +47,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
48
47
|
// ---- Notification system ----
|
|
49
48
|
// runtime.widget is assigned after AgentManager construction; arrow closures
|
|
50
49
|
// capture `runtime` by reference so they always read the current value.
|
|
51
|
-
const notifications =
|
|
50
|
+
const notifications = new NotificationManager({
|
|
52
51
|
sendMessage: (msg, opts) => pi.sendMessage(msg, opts),
|
|
53
52
|
agentActivity: runtime.agentActivity,
|
|
54
53
|
markFinished: (id) => runtime.markFinished(id),
|
package/src/notification.ts
CHANGED
|
@@ -1,8 +1,23 @@
|
|
|
1
1
|
import { debugLog } from "./debug.js";
|
|
2
|
-
import type { AgentRecord
|
|
2
|
+
import type { AgentRecord } from "./types.js";
|
|
3
3
|
import type { AgentActivityTracker } from "./ui/agent-activity-tracker.js";
|
|
4
4
|
import { getLifetimeTotal, getSessionContextPercent } from "./usage.js";
|
|
5
5
|
|
|
6
|
+
/** Details attached to custom notification messages for visual rendering. */
|
|
7
|
+
export interface NotificationDetails {
|
|
8
|
+
id: string;
|
|
9
|
+
description: string;
|
|
10
|
+
status: string;
|
|
11
|
+
toolUses: number;
|
|
12
|
+
turnCount: number;
|
|
13
|
+
maxTurns?: number;
|
|
14
|
+
totalTokens: number;
|
|
15
|
+
durationMs: number;
|
|
16
|
+
outputFile?: string;
|
|
17
|
+
error?: string;
|
|
18
|
+
resultPreview: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
6
21
|
// ---- Pure helpers (exported for unit testing) ----
|
|
7
22
|
|
|
8
23
|
/** Escape XML special characters to prevent injection in structured notifications. */
|
|
@@ -129,23 +144,43 @@ export interface NotificationSystem {
|
|
|
129
144
|
|
|
130
145
|
const NUDGE_HOLD_MS = 200;
|
|
131
146
|
|
|
132
|
-
export
|
|
133
|
-
|
|
147
|
+
export class NotificationManager implements NotificationSystem {
|
|
148
|
+
private pendingNudges = new Map<string, ReturnType<typeof setTimeout>>();
|
|
134
149
|
|
|
135
|
-
|
|
136
|
-
|
|
150
|
+
constructor(private deps: NotificationDeps) {}
|
|
151
|
+
|
|
152
|
+
cancelNudge(key: string): void {
|
|
153
|
+
const timer = this.pendingNudges.get(key);
|
|
137
154
|
if (timer != null) {
|
|
138
155
|
clearTimeout(timer);
|
|
139
|
-
pendingNudges.delete(key);
|
|
156
|
+
this.pendingNudges.delete(key);
|
|
140
157
|
}
|
|
141
158
|
}
|
|
142
159
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
160
|
+
sendCompletion(record: AgentRecord): void {
|
|
161
|
+
this.deps.agentActivity.delete(record.id);
|
|
162
|
+
this.deps.markFinished(record.id);
|
|
163
|
+
this.scheduleNudge(record.id, () => this.emitIndividualNudge(record));
|
|
164
|
+
this.deps.updateWidget();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
cleanupCompleted(id: string): void {
|
|
168
|
+
this.deps.agentActivity.delete(id);
|
|
169
|
+
this.deps.markFinished(id);
|
|
170
|
+
this.deps.updateWidget();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
dispose(): void {
|
|
174
|
+
for (const timer of this.pendingNudges.values()) clearTimeout(timer);
|
|
175
|
+
this.pendingNudges.clear();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
private scheduleNudge(key: string, send: () => void, delay = NUDGE_HOLD_MS): void {
|
|
179
|
+
this.cancelNudge(key);
|
|
180
|
+
this.pendingNudges.set(
|
|
146
181
|
key,
|
|
147
182
|
setTimeout(() => {
|
|
148
|
-
pendingNudges.delete(key);
|
|
183
|
+
this.pendingNudges.delete(key);
|
|
149
184
|
try {
|
|
150
185
|
send();
|
|
151
186
|
} catch (err) {
|
|
@@ -155,41 +190,21 @@ export function createNotificationSystem(deps: NotificationDeps): NotificationSy
|
|
|
155
190
|
);
|
|
156
191
|
}
|
|
157
192
|
|
|
158
|
-
|
|
193
|
+
private emitIndividualNudge(record: AgentRecord): void {
|
|
159
194
|
if (record.notification?.resultConsumed) return;
|
|
160
195
|
|
|
161
196
|
const notification = formatTaskNotification(record, 500);
|
|
162
197
|
const outputFile = record.execution?.outputFile;
|
|
163
198
|
const footer = outputFile ? `\nFull transcript available at: ${outputFile}` : "";
|
|
164
199
|
|
|
165
|
-
deps.sendMessage(
|
|
200
|
+
this.deps.sendMessage(
|
|
166
201
|
{
|
|
167
202
|
customType: "subagent-notification",
|
|
168
203
|
content: notification + footer,
|
|
169
204
|
display: true,
|
|
170
|
-
details: buildNotificationDetails(record, 500, deps.agentActivity.get(record.id)),
|
|
205
|
+
details: buildNotificationDetails(record, 500, this.deps.agentActivity.get(record.id)),
|
|
171
206
|
},
|
|
172
207
|
{ deliverAs: "followUp", triggerTurn: true },
|
|
173
208
|
);
|
|
174
209
|
}
|
|
175
|
-
|
|
176
|
-
function sendCompletion(record: AgentRecord) {
|
|
177
|
-
deps.agentActivity.delete(record.id);
|
|
178
|
-
deps.markFinished(record.id);
|
|
179
|
-
scheduleNudge(record.id, () => emitIndividualNudge(record));
|
|
180
|
-
deps.updateWidget();
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
function cleanupCompleted(id: string) {
|
|
184
|
-
deps.agentActivity.delete(id);
|
|
185
|
-
deps.markFinished(id);
|
|
186
|
-
deps.updateWidget();
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function dispose() {
|
|
190
|
-
for (const timer of pendingNudges.values()) clearTimeout(timer);
|
|
191
|
-
pendingNudges.clear();
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
return { cancelNudge, sendCompletion, cleanupCompleted, dispose };
|
|
195
210
|
}
|
package/src/parent-snapshot.ts
CHANGED
|
@@ -4,7 +4,26 @@
|
|
|
4
4
|
|
|
5
5
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
6
6
|
import { buildParentContext } from "./context.js";
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Plain data snapshot of the parent session state captured at spawn time.
|
|
10
|
+
* Replaces live `ExtensionContext` references so queued agents don't read stale state.
|
|
11
|
+
*/
|
|
12
|
+
export interface ParentSnapshot {
|
|
13
|
+
/** Parent working directory. */
|
|
14
|
+
cwd: string;
|
|
15
|
+
/** Parent's effective system prompt (for append-mode agents). */
|
|
16
|
+
systemPrompt: string;
|
|
17
|
+
/** Parent's current model instance (fallback when agent config has no model). */
|
|
18
|
+
model: unknown;
|
|
19
|
+
/** Model registry for resolving config.model strings and creating sessions. */
|
|
20
|
+
modelRegistry: {
|
|
21
|
+
find(provider: string, modelId: string): unknown;
|
|
22
|
+
getAvailable?(): Array<{ provider: string; id: string }>;
|
|
23
|
+
};
|
|
24
|
+
/** Pre-built parent conversation text (when inheritContext was requested). */
|
|
25
|
+
parentContext?: string;
|
|
26
|
+
}
|
|
8
27
|
|
|
9
28
|
/**
|
|
10
29
|
* Build an immutable snapshot of the parent session state.
|
package/src/prompts.ts
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* prompts.ts — System prompt builder for agents.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import type {
|
|
5
|
+
import type { EnvInfo } from "./env.js";
|
|
6
|
+
import type { AgentPromptConfig } from "./types.js";
|
|
6
7
|
|
|
7
8
|
/** Extra sections to inject into the system prompt (memory, skills, etc.). */
|
|
8
9
|
export interface PromptExtras {
|
|
@@ -27,7 +28,7 @@ export interface PromptExtras {
|
|
|
27
28
|
* @param extras Optional extra sections to inject (memory, preloaded skills).
|
|
28
29
|
*/
|
|
29
30
|
export function buildAgentPrompt(
|
|
30
|
-
config:
|
|
31
|
+
config: AgentPromptConfig,
|
|
31
32
|
cwd: string,
|
|
32
33
|
env: EnvInfo,
|
|
33
34
|
parentSystemPrompt?: string,
|
package/src/renderer.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Text } from "@earendil-works/pi-tui";
|
|
2
|
-
import type { NotificationDetails } from "./
|
|
2
|
+
import type { NotificationDetails } from "./notification.js";
|
|
3
3
|
import { formatMs, formatTokens, formatTurns } from "./ui/agent-widget.js";
|
|
4
4
|
|
|
5
5
|
/** Narrow theme interface — only the methods the renderer actually calls. */
|
package/src/session-config.ts
CHANGED
|
@@ -15,10 +15,11 @@ import {
|
|
|
15
15
|
getMemoryToolNames,
|
|
16
16
|
getReadOnlyMemoryToolNames,
|
|
17
17
|
} from "./agent-types.js";
|
|
18
|
+
import type { EnvInfo } from "./env.js";
|
|
18
19
|
import { buildMemoryBlock, buildReadOnlyMemoryBlock } from "./memory.js";
|
|
19
20
|
import { buildAgentPrompt, type PromptExtras } from "./prompts.js";
|
|
20
21
|
import { preloadSkills } from "./skill-loader.js";
|
|
21
|
-
import type {
|
|
22
|
+
import type { SubagentType, ThinkingLevel } from "./types.js";
|
|
22
23
|
|
|
23
24
|
// ── Public interfaces ────────────────────────────────────────────────────────
|
|
24
25
|
|
package/src/types.ts
CHANGED
|
@@ -18,11 +18,23 @@ export type MemoryScope = "user" | "project" | "local";
|
|
|
18
18
|
/** Isolation mode for agent execution. */
|
|
19
19
|
export type IsolationMode = "worktree";
|
|
20
20
|
|
|
21
|
-
/**
|
|
22
|
-
export interface
|
|
21
|
+
/** UI display and agent listing — name, display name, description, prompt mode. */
|
|
22
|
+
export interface AgentIdentity {
|
|
23
23
|
name: string;
|
|
24
24
|
displayName?: string;
|
|
25
25
|
description: string;
|
|
26
|
+
promptMode: "replace" | "append";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Prompt assembly — name, prompt mode, system prompt. */
|
|
30
|
+
export interface AgentPromptConfig {
|
|
31
|
+
name: string;
|
|
32
|
+
promptMode: "replace" | "append";
|
|
33
|
+
systemPrompt: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Unified agent configuration — used for both default and user-defined agents. */
|
|
37
|
+
export interface AgentConfig extends AgentIdentity, AgentPromptConfig {
|
|
26
38
|
builtinToolNames?: string[];
|
|
27
39
|
/** Tool denylist — these tools are removed even if `builtinToolNames` or extensions include them. */
|
|
28
40
|
disallowedTools?: string[];
|
|
@@ -33,8 +45,6 @@ export interface AgentConfig {
|
|
|
33
45
|
model?: string;
|
|
34
46
|
thinking?: ThinkingLevel;
|
|
35
47
|
maxTurns?: number;
|
|
36
|
-
systemPrompt: string;
|
|
37
|
-
promptMode: "replace" | "append";
|
|
38
48
|
/** Default for spawn: fork parent conversation. undefined = caller decides. */
|
|
39
49
|
inheritContext?: boolean;
|
|
40
50
|
/** Default for spawn: run in background. undefined = caller decides. */
|
|
@@ -64,48 +74,6 @@ export interface AgentInvocation {
|
|
|
64
74
|
isolation?: IsolationMode;
|
|
65
75
|
}
|
|
66
76
|
|
|
67
|
-
/** Details attached to custom notification messages for visual rendering. */
|
|
68
|
-
export interface NotificationDetails {
|
|
69
|
-
id: string;
|
|
70
|
-
description: string;
|
|
71
|
-
status: string;
|
|
72
|
-
toolUses: number;
|
|
73
|
-
turnCount: number;
|
|
74
|
-
maxTurns?: number;
|
|
75
|
-
totalTokens: number;
|
|
76
|
-
durationMs: number;
|
|
77
|
-
outputFile?: string;
|
|
78
|
-
error?: string;
|
|
79
|
-
resultPreview: string;
|
|
80
|
-
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Plain data snapshot of the parent session state captured at spawn time.
|
|
85
|
-
* Replaces live `ExtensionContext` references so queued agents don't read stale state.
|
|
86
|
-
*/
|
|
87
|
-
export interface ParentSnapshot {
|
|
88
|
-
/** Parent working directory. */
|
|
89
|
-
cwd: string;
|
|
90
|
-
/** Parent's effective system prompt (for append-mode agents). */
|
|
91
|
-
systemPrompt: string;
|
|
92
|
-
/** Parent's current model instance (fallback when agent config has no model). */
|
|
93
|
-
model: unknown;
|
|
94
|
-
/** Model registry for resolving config.model strings and creating sessions. */
|
|
95
|
-
modelRegistry: {
|
|
96
|
-
find(provider: string, modelId: string): unknown;
|
|
97
|
-
getAvailable?(): Array<{ provider: string; id: string }>;
|
|
98
|
-
};
|
|
99
|
-
/** Pre-built parent conversation text (when inheritContext was requested). */
|
|
100
|
-
parentContext?: string;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export interface EnvInfo {
|
|
104
|
-
isGitRepo: boolean;
|
|
105
|
-
branch: string;
|
|
106
|
-
platform: string;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
77
|
/**
|
|
110
78
|
* Narrow shell-exec callback replacing `ExtensionAPI` in `detectEnv()`.
|
|
111
79
|
* Matches the shape of `pi.exec()` without carrying an SDK dependency.
|
package/src/ui/agent-menu.ts
CHANGED
|
@@ -220,7 +220,7 @@ export function createAgentsMenuHandler(deps: AgentMenuDeps) {
|
|
|
220
220
|
|
|
221
221
|
await ctx.ui.custom<undefined>(
|
|
222
222
|
(tui: any, theme: any, _keybindings: any, done: any) => {
|
|
223
|
-
return new ConversationViewer(tui, session, record, activity, theme, done, deps.registry);
|
|
223
|
+
return new ConversationViewer({ tui, session, record, activity, theme, done, registry: deps.registry });
|
|
224
224
|
},
|
|
225
225
|
{
|
|
226
226
|
overlay: true,
|
|
@@ -20,6 +20,16 @@ const MIN_VIEWPORT = 3;
|
|
|
20
20
|
/** Height ceiling shared by the overlay's `maxHeight` and the viewer's internal viewport cap. */
|
|
21
21
|
export const VIEWPORT_HEIGHT_PCT = 70;
|
|
22
22
|
|
|
23
|
+
export interface ConversationViewerOptions {
|
|
24
|
+
tui: TUI;
|
|
25
|
+
session: AgentSession;
|
|
26
|
+
record: AgentRecord;
|
|
27
|
+
activity: AgentActivityTracker | undefined;
|
|
28
|
+
theme: Theme;
|
|
29
|
+
done: (result: undefined) => void;
|
|
30
|
+
registry: AgentConfigLookup;
|
|
31
|
+
}
|
|
32
|
+
|
|
23
33
|
export class ConversationViewer implements Component {
|
|
24
34
|
private scrollOffset = 0;
|
|
25
35
|
private autoScroll = true;
|
|
@@ -27,16 +37,23 @@ export class ConversationViewer implements Component {
|
|
|
27
37
|
private lastInnerW = 0;
|
|
28
38
|
private closed = false;
|
|
29
39
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
) {
|
|
39
|
-
this.
|
|
40
|
+
private tui: TUI;
|
|
41
|
+
private session: AgentSession;
|
|
42
|
+
private record: AgentRecord;
|
|
43
|
+
private activity: AgentActivityTracker | undefined;
|
|
44
|
+
private theme: Theme;
|
|
45
|
+
private done: (result: undefined) => void;
|
|
46
|
+
private registry: AgentConfigLookup;
|
|
47
|
+
|
|
48
|
+
constructor(options: ConversationViewerOptions) {
|
|
49
|
+
this.tui = options.tui;
|
|
50
|
+
this.session = options.session;
|
|
51
|
+
this.record = options.record;
|
|
52
|
+
this.activity = options.activity;
|
|
53
|
+
this.theme = options.theme;
|
|
54
|
+
this.done = options.done;
|
|
55
|
+
this.registry = options.registry;
|
|
56
|
+
this.unsubscribe = options.session.subscribe(() => {
|
|
40
57
|
if (this.closed) return;
|
|
41
58
|
this.tui.requestRender();
|
|
42
59
|
});
|