@gotgenes/pi-subagents 6.9.3 → 6.10.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 +21 -0
- package/docs/architecture/architecture.md +7 -22
- package/docs/plans/0131-consolidate-shared-test-fixtures.md +207 -0
- package/docs/plans/0132-inject-io-into-session-config.md +219 -0
- package/docs/retro/0131-consolidate-shared-test-fixtures.md +46 -0
- package/package.json +1 -1
- package/src/agent-runner.ts +11 -1
- package/src/session-config.ts +41 -10
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,27 @@ 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.10.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v6.9.4...pi-subagents-v6.10.0) (2026-05-22)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* inject IO collaborators into assembleSessionConfig ([#132](https://github.com/gotgenes/pi-packages/issues/132)) ([74d3dbf](https://github.com/gotgenes/pi-packages/commit/74d3dbf5e67cf28f75683e55240719ad2be86490))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Documentation
|
|
17
|
+
|
|
18
|
+
* mark Step G complete in Phase 8 roadmap ([#132](https://github.com/gotgenes/pi-packages/issues/132)) ([95512bd](https://github.com/gotgenes/pi-packages/commit/95512bdec3757d5955d13e22261a90da41cea40e))
|
|
19
|
+
* plan IO collaborator injection into assembleSessionConfig ([#132](https://github.com/gotgenes/pi-packages/issues/132)) ([23c3b62](https://github.com/gotgenes/pi-packages/commit/23c3b624e8c0afb8fda72c1b5fba86cb165f78dd))
|
|
20
|
+
* **retro:** add retro notes for issue [#131](https://github.com/gotgenes/pi-packages/issues/131) ([b91cee9](https://github.com/gotgenes/pi-packages/commit/b91cee9ef69f8b1ab41be986663bad22e77a8c67))
|
|
21
|
+
|
|
22
|
+
## [6.9.4](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v6.9.3...pi-subagents-v6.9.4) (2026-05-22)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
### Documentation
|
|
26
|
+
|
|
27
|
+
* plan consolidate shared test fixtures ([#131](https://github.com/gotgenes/pi-packages/issues/131)) ([2fe1e65](https://github.com/gotgenes/pi-packages/commit/2fe1e65024743384981c057b405f97f9c76f9b05))
|
|
28
|
+
|
|
8
29
|
## [6.9.3](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v6.9.2...pi-subagents-v6.9.3) (2026-05-22)
|
|
9
30
|
|
|
10
31
|
|
|
@@ -506,8 +506,8 @@ Phase 7 eliminated all structural smells (mutable state, closure bags, callback
|
|
|
506
506
|
Phase 8 targets the next layer: testability friction, display module cohesion, and menu decomposition.
|
|
507
507
|
|
|
508
508
|
The test suite (690 tests, 1.4:1 test-to-code ratio) is comprehensive but uneven in quality.
|
|
509
|
-
|
|
510
|
-
This fragility is a symptom of production code that imports IO-touching collaborators directly instead of receiving them through injection.
|
|
509
|
+
`agent-runner.test.ts` accounts for 7 of 8 remaining `vi.mock()` calls and relies heavily on verifying internal call sequences rather than observable outputs.
|
|
510
|
+
This fragility is a symptom of production code that imports IO-touching collaborators directly instead of receiving them through injection. (Step G resolved `session-config.test.ts`, which previously held 4 of the 12 total mocks.)
|
|
511
511
|
|
|
512
512
|
The display and menu improvements were identified during Phase 7 but deferred because they don't gate encapsulation work.
|
|
513
513
|
They are included here because the display extraction unblocks menu decomposition.
|
|
@@ -517,7 +517,7 @@ They are included here because the display extraction unblocks menu decompositio
|
|
|
517
517
|
| Symptom | Location | Root cause |
|
|
518
518
|
| ----------------------------- | ------------------------------------------------------- | ----------------------------------------------------------------- |
|
|
519
519
|
| 7 `vi.mock()` calls | `agent-runner.test.ts` | Runner imports prompts, memory, skills, env, session-dir directly |
|
|
520
|
-
|
|
|
520
|
+
| 7 `vi.mock()` calls | `agent-runner.test.ts` | Runner imports prompts, memory, skills, env, session-dir directly |
|
|
521
521
|
| 52 `as any` casts | Across test suite | SDK session/context interfaces too wide to construct in tests |
|
|
522
522
|
| 3× duplicated `mockSession()` | agent-manager, record-observer, ui-observer tests | No shared test fixture |
|
|
523
523
|
| 3× duplicated `makeDeps()` | agent-tool, background-spawner, foreground-runner tests | No shared tool-deps fixture |
|
|
@@ -535,26 +535,11 @@ Consolidate duplicated mock factories into `test/helpers/`.
|
|
|
535
535
|
|
|
536
536
|
Impact: reduces test boilerplate; single source of truth for mock shapes; changes to dep interfaces propagate automatically.
|
|
537
537
|
|
|
538
|
-
### Step G: Inject IO collaborators into session-config (#132)
|
|
539
|
-
|
|
540
|
-
`assembleSessionConfig` is described as a pure assembler, but it directly imports three IO-touching functions: `preloadSkills` (reads `.pi/skills` files), `buildMemoryBlock` (reads `MEMORY.md`), and `buildReadOnlyMemoryBlock` (reads `MEMORY.md`).
|
|
541
|
-
It also imports `buildAgentPrompt`, which is pure but mocked anyway because tests verify call arguments instead of output properties.
|
|
542
|
-
|
|
543
|
-
Inject these as an `AssemblerIO` parameter:
|
|
544
|
-
|
|
545
|
-
```typescript
|
|
546
|
-
export interface AssemblerIO {
|
|
547
|
-
preloadSkills: (skills: string[], cwd: string) => PreloadedSkill[];
|
|
548
|
-
buildMemoryBlock: (name: string, scope: MemoryScope, cwd: string) => string;
|
|
549
|
-
buildReadOnlyMemoryBlock: (name: string, scope: MemoryScope, cwd: string) => string;
|
|
550
|
-
buildAgentPrompt: (config: AgentPromptConfig, cwd: string, env: EnvInfo, parentPrompt: string, extras: PromptExtras) => string;
|
|
551
|
-
}
|
|
552
|
-
```
|
|
553
|
-
|
|
554
|
-
The production call site in `agent-runner.ts` passes the real implementations.
|
|
555
|
-
Tests pass stubs or let real implementations run against controlled inputs.
|
|
538
|
+
### Step G: Inject IO collaborators into session-config (#132) ✓ done
|
|
556
539
|
|
|
557
|
-
|
|
540
|
+
`assembleSessionConfig` now accepts `io: AssemblerIO` as a required parameter.
|
|
541
|
+
`agent-runner.ts` constructs the real `AssemblerIO` from direct imports and passes it through.
|
|
542
|
+
`session-config.test.ts` injects stubs — all 4 `vi.mock()` calls eliminated, assertions shifted to `SessionConfig` output properties.
|
|
558
543
|
|
|
559
544
|
### Step H: Inject SDK boundary into agent-runner (#133)
|
|
560
545
|
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 131
|
|
3
|
+
issue_title: Consolidate shared test fixtures
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Consolidate shared test fixtures
|
|
7
|
+
|
|
8
|
+
## Problem Statement
|
|
9
|
+
|
|
10
|
+
Three `mockSession()` factories and three `makeDeps()` factories are duplicated across the test suite.
|
|
11
|
+
Each copy drifts independently when production interfaces change, creating maintenance burden and inconsistent mock shapes.
|
|
12
|
+
The architecture doc (Phase 8, Step F) identifies this as the first testability improvement before the IO-injection steps (G and H).
|
|
13
|
+
|
|
14
|
+
## Goals
|
|
15
|
+
|
|
16
|
+
- Extract `createMockSession()` into `test/helpers/mock-session.ts` — single source of truth for the subscribable session mock.
|
|
17
|
+
- Extract `createToolDeps()` into `test/helpers/make-deps.ts` — builds `AgentToolDeps` with sensible defaults and override support.
|
|
18
|
+
- Update all six test files to use the shared factories and remove their local copies.
|
|
19
|
+
- Keep existing test behavior unchanged — this is a pure refactor with no production code changes.
|
|
20
|
+
|
|
21
|
+
## Non-Goals
|
|
22
|
+
|
|
23
|
+
- IO injection into `session-config` (Step G, #132) — deferred.
|
|
24
|
+
- SDK boundary injection into `agent-runner` (Step H, #133) — deferred.
|
|
25
|
+
- Consolidating `makeCtx()` or `makeParams()` helpers — those are specific to each tool's parameter shape and do not share enough structure to justify extraction.
|
|
26
|
+
|
|
27
|
+
## Background
|
|
28
|
+
|
|
29
|
+
### Existing helper
|
|
30
|
+
|
|
31
|
+
`test/helpers/make-record.ts` exports `createTestRecord()`, which builds an `AgentRecord` with sensible defaults and override support.
|
|
32
|
+
It has its own unit test file (`test/helpers/make-record.test.ts`).
|
|
33
|
+
The two new factories follow the same pattern.
|
|
34
|
+
|
|
35
|
+
### `mockSession()` — 3 copies
|
|
36
|
+
|
|
37
|
+
| File | Shape |
|
|
38
|
+
| ------------------------- | ------------------------------------------------------------------------------------------------- |
|
|
39
|
+
| `agent-manager.test.ts` | `subscribe` (vi.fn), `emit`, `dispose` (vi.fn), `steer` (vi.fn), `sessionManager` — cast as `any` |
|
|
40
|
+
| `record-observer.test.ts` | `subscribe` (vi.fn), `emit` |
|
|
41
|
+
| `ui/ui-observer.test.ts` | `subscribe` (plain fn), `emit` |
|
|
42
|
+
|
|
43
|
+
The common core is `subscribe` + `emit` (the subscribable event bus).
|
|
44
|
+
The `agent-manager` copy adds extra properties the other two don't need.
|
|
45
|
+
|
|
46
|
+
### `makeDeps()` — 3 copies
|
|
47
|
+
|
|
48
|
+
| File | Type | Manager methods | Widget methods | Extra fields |
|
|
49
|
+
| ---------------------------------- | ---------------- | -------------------------------------------------------- | ------------------------------------------- | ---------------------------- |
|
|
50
|
+
| `tools/agent-tool.test.ts` | `AgentToolDeps` | spawn, spawnAndWait, resume, getRecord, getMaxConcurrent | setUICtx, ensureTimer, update, markFinished | registry, agentDir, settings |
|
|
51
|
+
| `tools/background-spawner.test.ts` | `BackgroundDeps` | spawn, getRecord, getMaxConcurrent | ensureTimer, update | — |
|
|
52
|
+
| `tools/foreground-runner.test.ts` | `ForegroundDeps` | spawnAndWait | ensureTimer, markFinished | — |
|
|
53
|
+
|
|
54
|
+
`AgentToolDeps` is a structural superset of both `BackgroundDeps` and `ForegroundDeps`.
|
|
55
|
+
TypeScript's structural type system allows an `AgentToolDeps` value to be passed where `BackgroundDeps` or `ForegroundDeps` is expected — the narrower interfaces require a strict subset of the methods present on the wider one.
|
|
56
|
+
|
|
57
|
+
## Design Overview
|
|
58
|
+
|
|
59
|
+
### `createMockSession(overrides?)`
|
|
60
|
+
|
|
61
|
+
Returns the subscribable event bus (core shape) merged with optional overrides.
|
|
62
|
+
The core shape includes:
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
interface MockSession {
|
|
66
|
+
subscribe: Mock<[fn: (event: any) => void], () => void>;
|
|
67
|
+
emit(event: any): void; // test-only helper, not on production Session
|
|
68
|
+
dispose: Mock;
|
|
69
|
+
steer: Mock;
|
|
70
|
+
sessionManager: { getSessionFile: Mock };
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
All fields are present in every call — callers that don't need `dispose` or `steer` simply ignore them.
|
|
75
|
+
This avoids a discriminated "minimal vs. full" shape that would reintroduce the divergence problem.
|
|
76
|
+
The `subscribe` spy is wired to a `Set<fn>` internally so `emit()` broadcasts to all subscribers, matching the existing hand-rolled pattern.
|
|
77
|
+
The return type is `MockSession & Record<string, unknown>` so call sites can pass it as `any`-typed session parameters without explicit casts.
|
|
78
|
+
|
|
79
|
+
Override support lets `agent-manager.test.ts` customize `steer` behavior or add fields:
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
const session = createMockSession({ steer: vi.fn().mockRejectedValue(new Error("fail")) });
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### `createToolDeps(overrides?)`
|
|
86
|
+
|
|
87
|
+
Builds a full `AgentToolDeps` with mock manager, widget, activity map, registry, agent dir, and settings.
|
|
88
|
+
Accepts `Partial<AgentToolDeps>` for overrides, following the same pattern as `createTestRecord()`.
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
function createToolDeps(overrides?: Partial<AgentToolDeps>): AgentToolDeps;
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Consumer call sites:
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
// agent-tool.test.ts — uses the full type directly
|
|
98
|
+
const deps = createToolDeps();
|
|
99
|
+
const tool = createAgentTool(deps);
|
|
100
|
+
|
|
101
|
+
// background-spawner.test.ts — structural typing narrows automatically
|
|
102
|
+
const deps = createToolDeps();
|
|
103
|
+
spawnBackground(deps, makeParams());
|
|
104
|
+
|
|
105
|
+
// foreground-runner.test.ts — same structural narrowing
|
|
106
|
+
const deps = createToolDeps({ manager: { spawnAndWait: vi.fn().mockResolvedValue(customRecord) } });
|
|
107
|
+
await runForeground(deps, makeParams(), undefined, undefined);
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The background and foreground tests gain unused mock methods on `manager` and `widget`, but this is harmless — the production code's ISP compliance ensures only the narrow interface methods are called.
|
|
111
|
+
Tests that assert specific mock interactions (e.g., `expect(deps.manager.spawn).toHaveBeenCalled()`) continue to work because every method is a distinct `vi.fn()`.
|
|
112
|
+
|
|
113
|
+
When a test needs to override a single manager method, it spreads into the nested object:
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
createToolDeps({
|
|
117
|
+
manager: { ...createToolDeps().manager, spawnAndWait: vi.fn().mockRejectedValue(err) },
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
This is slightly more verbose than today's flat override, but it happens rarely and the tradeoff is worthwhile for a single source of truth.
|
|
122
|
+
|
|
123
|
+
Alternatively, `createToolDeps` can accept a `managerOverrides` shorthand if the nested-spread pattern proves too noisy during implementation.
|
|
124
|
+
|
|
125
|
+
## Module-Level Changes
|
|
126
|
+
|
|
127
|
+
### New files
|
|
128
|
+
|
|
129
|
+
1. `test/helpers/mock-session.ts` — exports `createMockSession(overrides?)`.
|
|
130
|
+
2. `test/helpers/mock-session.test.ts` — unit tests for `createMockSession`: verifies event broadcasting, subscribe/unsubscribe, and override merging.
|
|
131
|
+
3. `test/helpers/make-deps.ts` — exports `createToolDeps(overrides?)`.
|
|
132
|
+
4. `test/helpers/make-deps.test.ts` — unit tests for `createToolDeps`: verifies default shape satisfies `AgentToolDeps`, `BackgroundDeps`, and `ForegroundDeps`; verifies override merging.
|
|
133
|
+
|
|
134
|
+
### Modified files
|
|
135
|
+
|
|
136
|
+
1. `test/agent-manager.test.ts` — remove local `mockSession()`, import `createMockSession` from helpers.
|
|
137
|
+
2. `test/record-observer.test.ts` — remove local `mockSession()`, import `createMockSession` from helpers.
|
|
138
|
+
3. `test/ui/ui-observer.test.ts` — remove local `mockSession()`, import `createMockSession` from helpers.
|
|
139
|
+
4. `test/tools/agent-tool.test.ts` — remove local `makeDeps()`, import `createToolDeps` from helpers.
|
|
140
|
+
5. `test/tools/background-spawner.test.ts` — remove local `makeDeps()`, import `createToolDeps` from helpers.
|
|
141
|
+
6. `test/tools/foreground-runner.test.ts` — remove local `makeDeps()`, import `createToolDeps` from helpers.
|
|
142
|
+
|
|
143
|
+
## Test Impact Analysis
|
|
144
|
+
|
|
145
|
+
1. The new factory unit tests (`mock-session.test.ts`, `make-deps.test.ts`) verify the shared fixture behavior that was previously only implicitly tested through the consumer test files.
|
|
146
|
+
This enables targeted debugging when a mock shape drifts from the production interface.
|
|
147
|
+
2. No existing tests become redundant — the consumer tests exercise distinct production behavior that the factory tests do not cover.
|
|
148
|
+
3. All existing tests stay as-is in terms of assertions.
|
|
149
|
+
Only the setup code (local factory → shared import) changes.
|
|
150
|
+
|
|
151
|
+
## TDD Order
|
|
152
|
+
|
|
153
|
+
1. **Red → Green: `createMockSession` factory.**
|
|
154
|
+
Write `test/helpers/mock-session.test.ts` — verify subscribe/emit broadcasting, unsubscribe, dispose/steer are vi.fn stubs, override merging.
|
|
155
|
+
Implement `test/helpers/mock-session.ts`.
|
|
156
|
+
Commit: `test: add createMockSession shared test fixture`
|
|
157
|
+
|
|
158
|
+
2. **Green: migrate `record-observer.test.ts` to `createMockSession`.**
|
|
159
|
+
Replace local `mockSession()` with import from helpers.
|
|
160
|
+
Run test file — all tests pass unchanged.
|
|
161
|
+
Commit: `test: use createMockSession in record-observer tests`
|
|
162
|
+
|
|
163
|
+
3. **Green: migrate `ui/ui-observer.test.ts` to `createMockSession`.**
|
|
164
|
+
Replace local `mockSession()` with import from helpers.
|
|
165
|
+
Run test file — all tests pass unchanged.
|
|
166
|
+
Commit: `test: use createMockSession in ui-observer tests`
|
|
167
|
+
|
|
168
|
+
4. **Green: migrate `agent-manager.test.ts` to `createMockSession`.**
|
|
169
|
+
Replace local `mockSession()` with import from helpers.
|
|
170
|
+
This file uses extra fields (`sessionManager`, `steer`, `dispose`) — verify overrides or defaults cover them.
|
|
171
|
+
Run test file — all tests pass unchanged.
|
|
172
|
+
Commit: `test: use createMockSession in agent-manager tests`
|
|
173
|
+
|
|
174
|
+
5. **Red → Green: `createToolDeps` factory.**
|
|
175
|
+
Write `test/helpers/make-deps.test.ts` — verify default shape, override merging, structural compatibility with `BackgroundDeps` and `ForegroundDeps`.
|
|
176
|
+
Implement `test/helpers/make-deps.ts`.
|
|
177
|
+
Commit: `test: add createToolDeps shared test fixture`
|
|
178
|
+
|
|
179
|
+
6. **Green: migrate `tools/agent-tool.test.ts` to `createToolDeps`.**
|
|
180
|
+
Replace local `makeDeps()` with import from helpers.
|
|
181
|
+
Run test file — all tests pass unchanged.
|
|
182
|
+
Commit: `test: use createToolDeps in agent-tool tests`
|
|
183
|
+
|
|
184
|
+
7. **Green: migrate `tools/background-spawner.test.ts` to `createToolDeps`.**
|
|
185
|
+
Replace local `makeDeps()` with import from helpers.
|
|
186
|
+
Adjust any override patterns for the wider type.
|
|
187
|
+
Run test file — all tests pass unchanged.
|
|
188
|
+
Commit: `test: use createToolDeps in background-spawner tests`
|
|
189
|
+
|
|
190
|
+
8. **Green: migrate `tools/foreground-runner.test.ts` to `createToolDeps`.**
|
|
191
|
+
Replace local `makeDeps()` with import from helpers.
|
|
192
|
+
Adjust any override patterns for the wider type.
|
|
193
|
+
Run test file — all tests pass unchanged.
|
|
194
|
+
Commit: `test: use createToolDeps in foreground-runner tests`
|
|
195
|
+
|
|
196
|
+
## Risks and Mitigations
|
|
197
|
+
|
|
198
|
+
| Risk | Mitigation |
|
|
199
|
+
| ------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
200
|
+
| Wider mock shape causes false-positive tests (tests pass even when production code calls wrong method) | The production interfaces are already ISP-narrow; the mock width only affects tests. Existing assertions on specific mock calls catch regressions. |
|
|
201
|
+
| Override merging doesn't handle nested objects (e.g., overriding a single manager method) | Factory uses shallow merge for top-level fields; document that nested overrides require spreading the default nested object. Evaluate a `managerOverrides` shorthand during implementation if the pattern is too noisy. |
|
|
202
|
+
| `createMockSession` return type is too loose (`any`) and hides type errors in tests | Return a named `MockSession` interface rather than `any`. Consumer sites that pass the mock as `any`-typed SDK parameters are already untyped at that boundary. |
|
|
203
|
+
|
|
204
|
+
## Open Questions
|
|
205
|
+
|
|
206
|
+
- Should `createToolDeps` accept a flat `managerOverrides` shorthand or require the caller to spread the nested object?
|
|
207
|
+
Decide during step 5 based on how verbose the migration turns out in steps 6–8.
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 132
|
|
3
|
+
issue_title: "Inject IO collaborators into `assembleSessionConfig`"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Inject IO collaborators into session-config
|
|
7
|
+
|
|
8
|
+
## Problem Statement
|
|
9
|
+
|
|
10
|
+
`assembleSessionConfig` is described as a pure configuration assembler, but it directly imports three IO-touching functions (`preloadSkills`, `buildMemoryBlock`, `buildReadOnlyMemoryBlock`) and one pure function (`buildAgentPrompt`).
|
|
11
|
+
This forces `session-config.test.ts` to use 4 `vi.mock()` calls, 8 hoisted mock functions, and assertions that verify internal call sequences rather than output properties.
|
|
12
|
+
The result is fragile tests that break on any internal restructuring even when observable behavior is unchanged.
|
|
13
|
+
|
|
14
|
+
## Goals
|
|
15
|
+
|
|
16
|
+
- Define an `AssemblerIO` interface bundling the four collaborators.
|
|
17
|
+
- Add `io: AssemblerIO` as a parameter to `assembleSessionConfig()`.
|
|
18
|
+
- Replace direct imports of the four functions with calls through `io`.
|
|
19
|
+
- Update the single production call site in `agent-runner.ts` to pass real implementations.
|
|
20
|
+
- Eliminate all 4 `vi.mock()` calls in `session-config.test.ts`.
|
|
21
|
+
- Shift test assertions toward output-property verification.
|
|
22
|
+
|
|
23
|
+
## Non-Goals
|
|
24
|
+
|
|
25
|
+
- SDK boundary injection into `agent-runner` (Step H, #133) — depends on this change but is deferred to its own issue.
|
|
26
|
+
- Consolidating shared test fixtures (#131) — independent refactor that can land before or after.
|
|
27
|
+
- Changing the behavior of `assembleSessionConfig` — this is a pure structural refactor.
|
|
28
|
+
- Injecting `getMemoryToolNames` / `getReadOnlyMemoryToolNames` — these are pure utility functions with no IO; they stay as direct imports.
|
|
29
|
+
|
|
30
|
+
## Background
|
|
31
|
+
|
|
32
|
+
### Current state
|
|
33
|
+
|
|
34
|
+
`session-config.ts` imports four functions used during assembly:
|
|
35
|
+
|
|
36
|
+
| Function | Module | IO? | Purpose in assembler |
|
|
37
|
+
| -------------------------- | ----------------- | ------------------------------ | --------------------------------------- |
|
|
38
|
+
| `preloadSkills` | `skill-loader.ts` | Yes (reads `.pi/skills` files) | Loads skill content into prompt extras |
|
|
39
|
+
| `buildMemoryBlock` | `memory.ts` | Yes (reads `MEMORY.md`) | Builds read-write memory prompt section |
|
|
40
|
+
| `buildReadOnlyMemoryBlock` | `memory.ts` | Yes (reads `MEMORY.md`) | Builds read-only memory prompt section |
|
|
41
|
+
| `buildAgentPrompt` | `prompts.ts` | No (pure) | Assembles final system prompt string |
|
|
42
|
+
|
|
43
|
+
The test file mocks all four via `vi.mock()` plus mocks `getMemoryToolNames` and `getReadOnlyMemoryToolNames` from `agent-types.ts` (pure functions that are mocked only for call-argument verification).
|
|
44
|
+
|
|
45
|
+
### Established DI pattern
|
|
46
|
+
|
|
47
|
+
`AgentManager` already injects `AgentRunner` via its constructor options — the same tell-don't-ask pattern used here.
|
|
48
|
+
`assembleSessionConfig` already receives an `AgentConfigLookup` registry by parameter (migrated in #80/#108), demonstrating the incremental injection approach.
|
|
49
|
+
|
|
50
|
+
### Architecture reference
|
|
51
|
+
|
|
52
|
+
Phase 8, Step G in `docs/architecture/architecture.md`.
|
|
53
|
+
|
|
54
|
+
### Constraints from AGENTS.md
|
|
55
|
+
|
|
56
|
+
- Keep scope tight; prefer small, reversible changes.
|
|
57
|
+
- Prefer explicit configuration over hidden behavior.
|
|
58
|
+
- Business logic should be pure functions — keep IO at the edges.
|
|
59
|
+
|
|
60
|
+
## Design Overview
|
|
61
|
+
|
|
62
|
+
### `AssemblerIO` interface
|
|
63
|
+
|
|
64
|
+
Defined in `session-config.ts` alongside the existing assembler types:
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
export interface AssemblerIO {
|
|
68
|
+
preloadSkills: (skills: string[], cwd: string) => PreloadedSkill[];
|
|
69
|
+
buildMemoryBlock: (
|
|
70
|
+
name: string,
|
|
71
|
+
scope: MemoryScope,
|
|
72
|
+
cwd: string,
|
|
73
|
+
) => string;
|
|
74
|
+
buildReadOnlyMemoryBlock: (
|
|
75
|
+
name: string,
|
|
76
|
+
scope: MemoryScope,
|
|
77
|
+
cwd: string,
|
|
78
|
+
) => string;
|
|
79
|
+
buildAgentPrompt: (
|
|
80
|
+
config: AgentPromptConfig,
|
|
81
|
+
cwd: string,
|
|
82
|
+
env: EnvInfo,
|
|
83
|
+
parentPrompt?: string,
|
|
84
|
+
extras?: PromptExtras,
|
|
85
|
+
) => string;
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The interface uses the same parameter types as the real functions.
|
|
90
|
+
The assembler calls `io.preloadSkills(...)` etc. instead of the direct imports.
|
|
91
|
+
|
|
92
|
+
### Call site in `agent-runner.ts`
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
import { preloadSkills } from "./skill-loader.js";
|
|
96
|
+
import { buildMemoryBlock, buildReadOnlyMemoryBlock } from "./memory.js";
|
|
97
|
+
import { buildAgentPrompt } from "./prompts.js";
|
|
98
|
+
|
|
99
|
+
const io: AssemblerIO = {
|
|
100
|
+
preloadSkills,
|
|
101
|
+
buildMemoryBlock,
|
|
102
|
+
buildReadOnlyMemoryBlock,
|
|
103
|
+
buildAgentPrompt,
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const cfg = assembleSessionConfig(type, ctx, options, env, registry, io);
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The runner constructs the real IO object once and passes it through.
|
|
110
|
+
This keeps IO at the edge (runner) and makes the assembler a genuine pure function.
|
|
111
|
+
|
|
112
|
+
### Test-side stubs
|
|
113
|
+
|
|
114
|
+
Tests create a plain object with `vi.fn()` stubs satisfying `AssemblerIO`:
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
const io: AssemblerIO = {
|
|
118
|
+
preloadSkills: vi.fn(() => []),
|
|
119
|
+
buildMemoryBlock: vi.fn(() => "memory block"),
|
|
120
|
+
buildReadOnlyMemoryBlock: vi.fn(() => "read-only memory block"),
|
|
121
|
+
buildAgentPrompt: vi.fn(() => "assembled system prompt"),
|
|
122
|
+
};
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
This replaces all four `vi.mock()` calls and the hoisted mocks for those modules.
|
|
126
|
+
|
|
127
|
+
### Pure utility functions stay as direct imports
|
|
128
|
+
|
|
129
|
+
`getMemoryToolNames` and `getReadOnlyMemoryToolNames` from `agent-types.ts` are pure functions (no IO, no filesystem access).
|
|
130
|
+
After the IO injection, the test's `vi.mock("../src/agent-types.js", ...)` can be removed and real implementations used.
|
|
131
|
+
Tests that previously controlled these mocks to verify call arguments will instead set up input tool names to produce the desired output from the real functions, then assert on the returned `SessionConfig.toolNames`.
|
|
132
|
+
|
|
133
|
+
## Module-Level Changes
|
|
134
|
+
|
|
135
|
+
### Modified files
|
|
136
|
+
|
|
137
|
+
1. `src/session-config.ts`
|
|
138
|
+
- Add `AssemblerIO` interface export.
|
|
139
|
+
- Add `io: AssemblerIO` parameter to `assembleSessionConfig()` (after `registry`).
|
|
140
|
+
- Replace `preloadSkills(...)` with `io.preloadSkills(...)`.
|
|
141
|
+
- Replace `buildMemoryBlock(...)` with `io.buildMemoryBlock(...)`.
|
|
142
|
+
- Replace `buildReadOnlyMemoryBlock(...)` with `io.buildReadOnlyMemoryBlock(...)`.
|
|
143
|
+
- Replace `buildAgentPrompt(...)` with `io.buildAgentPrompt(...)`.
|
|
144
|
+
- Remove imports of `preloadSkills`, `buildMemoryBlock`, `buildReadOnlyMemoryBlock`, `buildAgentPrompt`.
|
|
145
|
+
- Keep imports of `getMemoryToolNames`, `getReadOnlyMemoryToolNames` (pure, no change).
|
|
146
|
+
|
|
147
|
+
2. `src/agent-runner.ts`
|
|
148
|
+
- Add imports for `preloadSkills`, `buildMemoryBlock`, `buildReadOnlyMemoryBlock`, `buildAgentPrompt`.
|
|
149
|
+
- Import `AssemblerIO` type from `session-config.ts`.
|
|
150
|
+
- Construct `AssemblerIO` object from real implementations.
|
|
151
|
+
- Pass `io` to `assembleSessionConfig()`.
|
|
152
|
+
|
|
153
|
+
3. `test/session-config.test.ts`
|
|
154
|
+
- Remove all 4 `vi.mock()` calls and the corresponding hoisted mocks.
|
|
155
|
+
- Create `io` stub object with `vi.fn()` implementations.
|
|
156
|
+
- Pass `io` to every `assembleSessionConfig()` call.
|
|
157
|
+
- Update memory-section tests to use real `getMemoryToolNames` / `getReadOnlyMemoryToolNames`.
|
|
158
|
+
- Migrate mock-call assertions to output-property assertions where the output already captures the information.
|
|
159
|
+
|
|
160
|
+
## Test Impact Analysis
|
|
161
|
+
|
|
162
|
+
1. The IO injection enables testing `assembleSessionConfig` without any module mocking.
|
|
163
|
+
Tests can choose to inject real implementations with controlled inputs (integration-style) or stubs (unit-style).
|
|
164
|
+
Previously this was impossible without `vi.mock()`.
|
|
165
|
+
|
|
166
|
+
2. Several existing tests that only verified mock-call arguments become redundant once we verify the same information through output properties (e.g., "calls buildAgentPrompt with env, cwd, parentSystemPrompt, and extras" is redundant if we verify `result.systemPrompt` reflects those inputs).
|
|
167
|
+
These can be simplified or removed.
|
|
168
|
+
|
|
169
|
+
3. Tests for model resolution, isolated mode, thinking level, and unknown-type fallback stay as-is — they already assert output properties and are unaffected by the IO injection.
|
|
170
|
+
|
|
171
|
+
## TDD Order
|
|
172
|
+
|
|
173
|
+
1. **Define `AssemblerIO` and inject into `assembleSessionConfig`.**
|
|
174
|
+
Add the `AssemblerIO` interface to `session-config.ts`.
|
|
175
|
+
Add `io: AssemblerIO` as a required parameter.
|
|
176
|
+
Replace the 4 direct function calls with `io.*` calls.
|
|
177
|
+
Remove the 4 function imports from `session-config.ts`.
|
|
178
|
+
Add the 4 imports to `agent-runner.ts` and construct the `io` object at the call site.
|
|
179
|
+
Run `pnpm run check` to verify types compile.
|
|
180
|
+
Commit: `feat: inject IO collaborators into assembleSessionConfig (#132)`
|
|
181
|
+
|
|
182
|
+
2. **Migrate test file to use injected IO stubs.**
|
|
183
|
+
Create an `io` stub object with `vi.fn()` stubs matching the existing hoisted mocks' default return values.
|
|
184
|
+
Pass `io` to all `assembleSessionConfig()` calls.
|
|
185
|
+
Remove the 3 `vi.mock()` calls for `prompts.js`, `memory.js`, and `skill-loader.js`.
|
|
186
|
+
Remove the corresponding hoisted mock variables (`mockBuildAgentPrompt`, `mockBuildMemoryBlock`, `mockBuildReadOnlyMemoryBlock`, `mockPreloadSkills`).
|
|
187
|
+
Update `beforeEach` to reset the `io` stubs instead.
|
|
188
|
+
All existing tests pass with the same assertions (io stubs replace module mocks).
|
|
189
|
+
Commit: `test: replace vi.mock with injected IO stubs in session-config tests`
|
|
190
|
+
|
|
191
|
+
3. **Drop the `agent-types.js` mock; use real pure functions.**
|
|
192
|
+
Remove the `vi.mock("../src/agent-types.js", ...)` call and the `importOriginal` pattern.
|
|
193
|
+
Remove hoisted `mockGetMemoryToolNames` and `mockGetReadOnlyMemoryToolNames`.
|
|
194
|
+
Update memory-section tests to set up `mockGetToolNamesForType` return values that produce the desired output from the real `getMemoryToolNames` / `getReadOnlyMemoryToolNames`.
|
|
195
|
+
Assertions shift from "mock was called with Set" to "result.toolNames contains expected names".
|
|
196
|
+
Commit: `test: use real getMemoryToolNames in session-config tests`
|
|
197
|
+
|
|
198
|
+
4. **Shift remaining mock-call assertions to output-property checks.**
|
|
199
|
+
Replace `expect(io.buildAgentPrompt).toHaveBeenCalledWith(...)` with assertions on `result.systemPrompt` (requires io.buildAgentPrompt stub to echo identifying values).
|
|
200
|
+
Replace `expect(io.preloadSkills).toHaveBeenCalledWith(skillList, "/tmp")` with `result.extras.skillBlocks` checks (already partially present).
|
|
201
|
+
Remove test cases that are now fully redundant with output-based tests in the same describe block.
|
|
202
|
+
Clean up any unused imports and variables.
|
|
203
|
+
Commit: `test: verify output properties in session-config tests (#132)`
|
|
204
|
+
|
|
205
|
+
## Risks and Mitigations
|
|
206
|
+
|
|
207
|
+
| Risk | Mitigation |
|
|
208
|
+
| --------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
209
|
+
| Adding a parameter to `assembleSessionConfig` breaks the `agent-runner.ts` call site | Only one production call site exists; updated in the same commit (step 1). `pnpm run check` verifies. |
|
|
210
|
+
| Removing `vi.mock()` causes tests to accidentally call real IO functions | The real functions are no longer imported by `session-config.ts` after step 1. The module simply doesn't reach them. Vitest will error if any unmocked import is called. |
|
|
211
|
+
| Using real `getMemoryToolNames` / `getReadOnlyMemoryToolNames` makes tests depend on their implementation | These are pure, stable utility functions (return tool names from a set). Their behavior is well-defined and unlikely to change. Using real implementations is more robust than mocking. |
|
|
212
|
+
| Step 2 touches 40+ call sites in the test file | All changes are mechanical (add `, io` argument). A find-and-replace handles it. Each call already passes `mockAgentLookup` as the last arg; the new arg follows the same pattern. |
|
|
213
|
+
|
|
214
|
+
## Open Questions
|
|
215
|
+
|
|
216
|
+
- Should `AssemblerIO` be co-located in `session-config.ts` or extracted to a separate `session-config-types.ts`?
|
|
217
|
+
The interface is small (4 methods) and tightly coupled to the assembler.
|
|
218
|
+
Co-location in `session-config.ts` follows the existing pattern (`AssemblerContext`, `AssemblerOptions`, `SessionConfig` are all in the same file).
|
|
219
|
+
Extract only if it grows or gains consumers beyond `agent-runner.ts`.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 131
|
|
3
|
+
issue_title: Consolidate shared test fixtures
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Retro: #131 — Consolidate shared test fixtures
|
|
7
|
+
|
|
8
|
+
## Final Retrospective (2026-05-22T11:30:00Z)
|
|
9
|
+
|
|
10
|
+
### Session summary
|
|
11
|
+
|
|
12
|
+
Planned and implemented the consolidation of six duplicated test factories into two shared helpers (`createMockSession` in `test/helpers/mock-session.ts`, `createToolDeps` in `test/helpers/make-deps.ts`).
|
|
13
|
+
All 715 tests pass, released as `pi-subagents-v6.9.4`.
|
|
14
|
+
The implementation was a pure test refactor with no production code changes.
|
|
15
|
+
|
|
16
|
+
### Observations
|
|
17
|
+
|
|
18
|
+
#### What went well
|
|
19
|
+
|
|
20
|
+
- The lift-and-shift approach worked cleanly: create factory → migrate one consumer at a time → verify green after each step.
|
|
21
|
+
Each migration commit was small and isolated, making failures easy to diagnose.
|
|
22
|
+
- Structural typing as a strategy proved out — `createToolDeps()` returns `AgentToolDeps` (the superset), and `spawnBackground(deps, ...)` and `runForeground(deps, ...)` accept their narrow `BackgroundDeps`/`ForegroundDeps` interfaces without any casting.
|
|
23
|
+
|
|
24
|
+
#### What caused friction (agent side)
|
|
25
|
+
|
|
26
|
+
- `missing-context` — Plan used `registry.resolve("general-purpose", "/dir")` in the `make-deps.test.ts` test, but `AgentTypeRegistry` has no `resolve` method — the correct method is `resolveAgentConfig()`.
|
|
27
|
+
Impact: one test failure during step 5 red→green, fixed immediately with no rework.
|
|
28
|
+
|
|
29
|
+
- `missing-context` — Default values differed between the old narrow factories and the new shared factory: `"bg-1"` vs `"agent-1"` for spawn IDs (`background-spawner.test.ts`), `"Task done."` vs `"All done."` for result text (`foreground-runner.test.ts`).
|
|
30
|
+
Impact: two test failures in step 7, one in step 8, each requiring assertion updates before the migration step could pass.
|
|
31
|
+
|
|
32
|
+
- `missing-context` — `MockSession` interface used `ReturnType<typeof vi.fn>` which expands to `Mock<Procedure | Constructable>` in Vitest v4 — a union type TypeScript cannot call.
|
|
33
|
+
Impact: `pnpm run check` failed after all TDD steps were done, requiring a separate `style:` commit to switch to explicitly parameterized `Mock<() => void>` etc.
|
|
34
|
+
|
|
35
|
+
- `missing-context` — Removed the `AgentToolDeps` import from `agent-tool.test.ts` without checking that the `execute()` helper still referenced it.
|
|
36
|
+
Impact: caught in the same `pnpm run check` pass, fixed in the same `style:` commit.
|
|
37
|
+
|
|
38
|
+
#### What caused friction (user side)
|
|
39
|
+
|
|
40
|
+
- No user-side friction observed.
|
|
41
|
+
The plan was unambiguous, and the session ran autonomously through all 8 TDD steps plus post-checks without intervention.
|
|
42
|
+
|
|
43
|
+
### Changes made
|
|
44
|
+
|
|
45
|
+
1. `.pi/skills/testing/SKILL.md` — added TDD planning rule for diffing default values when consolidating duplicate test factories.
|
|
46
|
+
2. `.pi/skills/testing/SKILL.md` — added Vitest mock pattern rule for typing mock fields with `Mock<specific-signature>` instead of `ReturnType<typeof vi.fn>`.
|
package/package.json
CHANGED
package/src/agent-runner.ts
CHANGED
|
@@ -15,9 +15,12 @@ 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 { buildMemoryBlock, buildReadOnlyMemoryBlock } from "./memory.js";
|
|
18
19
|
import type { ParentSnapshot } from "./parent-snapshot.js";
|
|
19
|
-
import {
|
|
20
|
+
import { buildAgentPrompt } from "./prompts.js";
|
|
21
|
+
import { type AssemblerIO, assembleSessionConfig } from "./session-config.js";
|
|
20
22
|
import { deriveSubagentSessionDir } from "./session-dir.js";
|
|
23
|
+
import { preloadSkills } from "./skill-loader.js";
|
|
21
24
|
import type { ShellExec, SubagentType, ThinkingLevel } from "./types.js";
|
|
22
25
|
|
|
23
26
|
/** Names of tools registered by this extension that subagents must NOT inherit. */
|
|
@@ -178,6 +181,12 @@ export async function runAgent(
|
|
|
178
181
|
const env = await detectEnv(options.exec, effectiveCwd);
|
|
179
182
|
|
|
180
183
|
// Assemble session configuration (synchronous, no SDK objects).
|
|
184
|
+
const io: AssemblerIO = {
|
|
185
|
+
preloadSkills,
|
|
186
|
+
buildMemoryBlock,
|
|
187
|
+
buildReadOnlyMemoryBlock,
|
|
188
|
+
buildAgentPrompt,
|
|
189
|
+
};
|
|
181
190
|
const cfg = assembleSessionConfig(
|
|
182
191
|
type,
|
|
183
192
|
{
|
|
@@ -194,6 +203,7 @@ export async function runAgent(
|
|
|
194
203
|
},
|
|
195
204
|
env,
|
|
196
205
|
options.registry,
|
|
206
|
+
io,
|
|
197
207
|
);
|
|
198
208
|
|
|
199
209
|
const agentDir = getAgentDir();
|
package/src/session-config.ts
CHANGED
|
@@ -16,13 +16,42 @@ import {
|
|
|
16
16
|
getReadOnlyMemoryToolNames,
|
|
17
17
|
} from "./agent-types.js";
|
|
18
18
|
import type { EnvInfo } from "./env.js";
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
|
|
19
|
+
import type { PromptExtras } from "./prompts.js";
|
|
20
|
+
import type { PreloadedSkill } from "./skill-loader.js";
|
|
21
|
+
import type {
|
|
22
|
+
AgentPromptConfig,
|
|
23
|
+
MemoryScope,
|
|
24
|
+
SubagentType,
|
|
25
|
+
ThinkingLevel,
|
|
26
|
+
} from "./types.js";
|
|
23
27
|
|
|
24
28
|
// ── Public interfaces ────────────────────────────────────────────────────────
|
|
25
29
|
|
|
30
|
+
/**
|
|
31
|
+
* IO collaborators injected into `assembleSessionConfig`.
|
|
32
|
+
*
|
|
33
|
+
* Bundling the four IO-touching (or promptly testable) functions into a single
|
|
34
|
+
* interface keeps the assembler free of direct module imports and makes it
|
|
35
|
+
* trivially testable without `vi.mock()` — callers inject real implementations
|
|
36
|
+
* at the edge (`agent-runner.ts`) or stubs in tests.
|
|
37
|
+
*/
|
|
38
|
+
export interface AssemblerIO {
|
|
39
|
+
preloadSkills: (skills: string[], cwd: string) => PreloadedSkill[];
|
|
40
|
+
buildMemoryBlock: (name: string, scope: MemoryScope, cwd: string) => string;
|
|
41
|
+
buildReadOnlyMemoryBlock: (
|
|
42
|
+
name: string,
|
|
43
|
+
scope: MemoryScope,
|
|
44
|
+
cwd: string,
|
|
45
|
+
) => string;
|
|
46
|
+
buildAgentPrompt: (
|
|
47
|
+
config: AgentPromptConfig,
|
|
48
|
+
cwd: string,
|
|
49
|
+
env: EnvInfo,
|
|
50
|
+
parentPrompt?: string,
|
|
51
|
+
extras?: PromptExtras,
|
|
52
|
+
) => string;
|
|
53
|
+
}
|
|
54
|
+
|
|
26
55
|
/**
|
|
27
56
|
* Narrow context the assembler reads from the parent session.
|
|
28
57
|
* Tests construct plain objects satisfying this interface — no SDK mocking needed.
|
|
@@ -132,8 +161,8 @@ function resolveDefaultModel(
|
|
|
132
161
|
/**
|
|
133
162
|
* Assemble all configuration needed to create an agent session.
|
|
134
163
|
*
|
|
135
|
-
* Synchronous and side-effect-free
|
|
136
|
-
*
|
|
164
|
+
* Synchronous and side-effect-free — all IO is delegated through the `io`
|
|
165
|
+
* parameter. The caller is responsible for resolving `EnvInfo` beforehand
|
|
137
166
|
* via `detectEnv()`.
|
|
138
167
|
*
|
|
139
168
|
* @param type The subagent type name (case-insensitive registry lookup).
|
|
@@ -141,6 +170,7 @@ function resolveDefaultModel(
|
|
|
141
170
|
* @param options Per-call overrides (cwd, isolated, model, thinkingLevel).
|
|
142
171
|
* @param env Pre-resolved environment info from `detectEnv()`.
|
|
143
172
|
* @param registry Agent config lookup — provides resolveAgentConfig and getToolNamesForType.
|
|
173
|
+
* @param io IO collaborators (skill loader, memory builder, prompt builder).
|
|
144
174
|
*/
|
|
145
175
|
export function assembleSessionConfig(
|
|
146
176
|
type: SubagentType,
|
|
@@ -148,6 +178,7 @@ export function assembleSessionConfig(
|
|
|
148
178
|
options: AssemblerOptions,
|
|
149
179
|
env: EnvInfo,
|
|
150
180
|
registry: AgentConfigLookup,
|
|
181
|
+
io: AssemblerIO,
|
|
151
182
|
): SessionConfig {
|
|
152
183
|
const agentConfig = registry.resolveAgentConfig(type);
|
|
153
184
|
|
|
@@ -162,7 +193,7 @@ export function assembleSessionConfig(
|
|
|
162
193
|
|
|
163
194
|
// Skill preloading: when skills is string[], preload their content into the prompt
|
|
164
195
|
if (Array.isArray(skills)) {
|
|
165
|
-
const loaded = preloadSkills(skills, effectiveCwd);
|
|
196
|
+
const loaded = io.preloadSkills(skills, effectiveCwd);
|
|
166
197
|
if (loaded.length > 0) {
|
|
167
198
|
extras.skillBlocks = loaded;
|
|
168
199
|
}
|
|
@@ -185,7 +216,7 @@ export function assembleSessionConfig(
|
|
|
185
216
|
if (hasWriteTools) {
|
|
186
217
|
const extraNames = getMemoryToolNames(existingNames);
|
|
187
218
|
if (extraNames.length > 0) toolNames = [...toolNames, ...extraNames];
|
|
188
|
-
extras.memoryBlock = buildMemoryBlock(
|
|
219
|
+
extras.memoryBlock = io.buildMemoryBlock(
|
|
189
220
|
agentConfig.name,
|
|
190
221
|
agentConfig.memory,
|
|
191
222
|
effectiveCwd,
|
|
@@ -193,7 +224,7 @@ export function assembleSessionConfig(
|
|
|
193
224
|
} else {
|
|
194
225
|
const extraNames = getReadOnlyMemoryToolNames(existingNames);
|
|
195
226
|
if (extraNames.length > 0) toolNames = [...toolNames, ...extraNames];
|
|
196
|
-
extras.memoryBlock = buildReadOnlyMemoryBlock(
|
|
227
|
+
extras.memoryBlock = io.buildReadOnlyMemoryBlock(
|
|
197
228
|
agentConfig.name,
|
|
198
229
|
agentConfig.memory,
|
|
199
230
|
effectiveCwd,
|
|
@@ -202,7 +233,7 @@ export function assembleSessionConfig(
|
|
|
202
233
|
}
|
|
203
234
|
|
|
204
235
|
// Build system prompt from the resolved agent config
|
|
205
|
-
const systemPrompt = buildAgentPrompt(
|
|
236
|
+
const systemPrompt = io.buildAgentPrompt(
|
|
206
237
|
agentConfig,
|
|
207
238
|
effectiveCwd,
|
|
208
239
|
env,
|