@gotgenes/pi-subagents 11.4.0 → 11.6.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 +15 -0
- package/dist/public.d.ts +178 -0
- package/docs/architecture/architecture.md +28 -3
- package/docs/decisions/0003-publish-bundled-type-declarations.md +69 -0
- package/docs/plans/0262-add-workspace-provider-seam.md +262 -0
- package/docs/plans/0270-type-consumable-public-surface.md +202 -0
- package/docs/retro/0262-add-workspace-provider-seam.md +87 -0
- package/docs/retro/0270-type-consumable-public-surface.md +56 -0
- package/package.json +18 -2
- package/src/index.ts +1 -0
- package/src/lifecycle/agent-manager.ts +30 -0
- package/src/lifecycle/agent.ts +44 -7
- package/src/lifecycle/workspace.ts +47 -0
- package/src/service/service-adapter.ts +6 -0
- package/src/service/service.ts +13 -1
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 270
|
|
3
|
+
issue_title: "Make @gotgenes/pi-subagents type-consumable by sibling workspace packages"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Publish a bundled `.d.ts` for the pi-subagents public surface
|
|
7
|
+
|
|
8
|
+
## Problem Statement
|
|
9
|
+
|
|
10
|
+
`@gotgenes/pi-subagents` cannot be imported by another TypeScript package in this workspace.
|
|
11
|
+
A sibling that writes `import { getSubagentsService, type WorkspaceProvider } from "@gotgenes/pi-subagents"` fails its `tsc` run.
|
|
12
|
+
This blocks Issue #263 (extract worktree isolation to `@gotgenes/pi-subagents-worktrees`), the first package that needs to import the subagents service and the `WorkspaceProvider` seam by name.
|
|
13
|
+
|
|
14
|
+
Two compounding causes were confirmed empirically with `tsc --traceResolution`:
|
|
15
|
+
|
|
16
|
+
1. `package.json` `exports["."]` points at `./src/service.ts`, which does not exist — the real module is `./src/service/service.ts`.
|
|
17
|
+
This is a latent bug; nothing in-repo imports the package by name today.
|
|
18
|
+
2. Once the path is corrected, the public entry's internal alias imports cascade.
|
|
19
|
+
`service/service.ts` imports `type LifetimeUsage` and `type WorkspaceProvider` via `#src/*`.
|
|
20
|
+
When a sibling's `tsc` follows the symlink and resolves those specifiers, the consumer's own `paths` (`#src/*` → `./src/*`) intercept first and point into the *consumer's* `src/` (a global-`paths` collision — both packages even define `#src/*`).
|
|
21
|
+
The fallback to the publisher's `package.json` `imports` field then fails too: tsc cannot resolve the extensionless `.ts` target under Node `imports` semantics ("Import specifier '#src/lifecycle/usage' does not exist in package.json scope").
|
|
22
|
+
|
|
23
|
+
The public entry's type closure is also deeply entangled: `WorkspaceProvider` (in `lifecycle/workspace.ts`) pulls in `AgentStatus` from the 510-line `lifecycle/agent.ts`, plus `SubagentType`/`AgentInvocation` from `types.ts` (which itself re-exports the `Agent` class).
|
|
24
|
+
A shallow alias-free entry is therefore not achievable without a substantial source restructure.
|
|
25
|
+
|
|
26
|
+
## Goals
|
|
27
|
+
|
|
28
|
+
- A consumer using the **published/packaged** `@gotgenes/pi-subagents` can `import { getSubagentsService, type WorkspaceProvider } from "@gotgenes/pi-subagents"` and have `tsc` pass.
|
|
29
|
+
- Fix the stale `exports["."]` path so it resolves to a real file.
|
|
30
|
+
- Emit a self-contained (alias-free) `.d.ts` for the public surface via `rollup-plugin-dts`, generated at pack/publish time and shipped in the npm tarball.
|
|
31
|
+
- Add a verification harness that proves external consumability via `pnpm pack` → throwaway consumer → `tsc`, with no publish round-trip.
|
|
32
|
+
- No regression to how Pi loads the extension from source (`pi.extensions: ["./src/index.ts"]` is untouched).
|
|
33
|
+
- `feat:` — adds a publishable capability (a typed public API surface) to `pi-subagents`.
|
|
34
|
+
|
|
35
|
+
## Non-Goals
|
|
36
|
+
|
|
37
|
+
- No source restructuring to make the entry alias-free (the rejected alternative — see Background).
|
|
38
|
+
`src/` modules and `#src/*` internal imports are left exactly as they are.
|
|
39
|
+
- No removal of worktree code from the core (`worktree.ts`, `worktree-isolation.ts`, `IsolationMode`, `isolation` spawn mode) — that is Issue #263.
|
|
40
|
+
- No change to `pi-subagents-worktrees`' dependency wiring: it stays on `workspace:*` and does not yet import `@gotgenes/pi-subagents`.
|
|
41
|
+
Flipping it to registry consumption (drop `workspace:*`, set `link-workspace-packages: false`, point at the fixed version, wire the real import) is deferred to Issue #263, because the registry version carrying this fix does not exist until #270 is published.
|
|
42
|
+
- No registration of `pi-subagents-worktrees` into `release-please-config.json` — that belongs to Issue #263 when it is ready to publish.
|
|
43
|
+
|
|
44
|
+
## Background
|
|
45
|
+
|
|
46
|
+
Relevant modules and facts:
|
|
47
|
+
|
|
48
|
+
- `packages/pi-subagents/src/service/service.ts` — the real public entry.
|
|
49
|
+
Locally declares the public service contract (`SubagentsService`, `SubagentRecord`, `SpawnOptions`, `SubagentStatus`, `SUBAGENT_EVENTS`) and the `Symbol.for()` accessor functions (`publishSubagentsService`, `getSubagentsService`, `unpublishSubagentsService`).
|
|
50
|
+
Its only internal imports are `import type { LifetimeUsage }` and `import type { WorkspaceProvider }` — both type-only, so they erase at runtime.
|
|
51
|
+
- `src/lifecycle/workspace.ts` — defines `WorkspaceProvider`, `Workspace`, and the prepare/dispose context types.
|
|
52
|
+
Imports `AgentStatus` from `#src/lifecycle/agent` and `AgentInvocation`/`SubagentType` from `#src/types`.
|
|
53
|
+
- `src/lifecycle/usage.ts` — defines `LifetimeUsage` plus internal runtime helpers.
|
|
54
|
+
- `src/lifecycle/agent.ts` (510 LOC) — defines `AgentStatus` near the top, alongside the `Agent` class and a wide `#src/*` import graph.
|
|
55
|
+
- `src/index.ts` — the Pi extension entry; imports `publishSubagentsService`/`unpublishSubagentsService` from `#src/service/service` (internal use, unaffected).
|
|
56
|
+
|
|
57
|
+
Constraints from `AGENTS.md` and the package skill:
|
|
58
|
+
|
|
59
|
+
- Ship-source model: every package ships raw `.ts` executed directly by Pi; there is no build step today.
|
|
60
|
+
ADR 0002 frames pi-subagents as a minimal core.
|
|
61
|
+
Introducing the repo's **first build step** is a deliberate decision and warrants an ADR.
|
|
62
|
+
- `eslint`'s `no-parent-relative-imports` rule forbids `../` imports inside `packages/*/src`; same-directory `./` is allowed.
|
|
63
|
+
This (plus the deep type entanglement above) is why the alias-free-entry alternative was rejected.
|
|
64
|
+
- New packages and internal docs subdirectories must be added to `release-please-config.json` `exclude-paths` where appropriate.
|
|
65
|
+
|
|
66
|
+
Rejected alternative (recorded for the ADR): restructure the source so the entry's full type closure is alias-free (leaf types module imported via `./`).
|
|
67
|
+
It is mechanically possible but requires moving `AgentStatus`/`SubagentType`/`AgentInvocation`/`WorkspaceProvider` definitions and reworking the `agent.ts`/`types.ts` entanglement, with care around dependency direction (inner layers must not import the outer service layer).
|
|
68
|
+
Larger blast radius than emitting a `.d.ts`.
|
|
69
|
+
|
|
70
|
+
Release/CI mechanics relevant to sequencing:
|
|
71
|
+
|
|
72
|
+
- CI publishes only when the `release-please` PR is merged (`publish` job gated on `releases_created == true`), not on every push to `main`.
|
|
73
|
+
- `release-please` batches all releasable commits per component.
|
|
74
|
+
The `#263` scaffold commits on `main` touch only the `pi-subagents-worktrees` component (which is **not** registered in `release-please-config.json`), so they neither trigger a release nor batch into `pi-subagents`.
|
|
75
|
+
|
|
76
|
+
## Design Overview
|
|
77
|
+
|
|
78
|
+
### Conditional exports
|
|
79
|
+
|
|
80
|
+
Point the `types` condition at the bundled declaration and the runtime condition at the real source module:
|
|
81
|
+
|
|
82
|
+
```jsonc
|
|
83
|
+
"exports": {
|
|
84
|
+
".": {
|
|
85
|
+
"types": "./dist/public.d.ts",
|
|
86
|
+
"default": "./src/service/service.ts"
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
- `default` → `./src/service/service.ts` fixes the stale path and serves runtime `await import("@gotgenes/pi-subagents")` (the accessor functions; its `import type` lines erase, so no runtime `#src/*` resolution is needed).
|
|
92
|
+
- `types` → `./dist/public.d.ts` gives a consumer's `tsc` a self-contained declaration that never references `#src/*`, sidestepping both the `paths` collision and the `imports`-field resolution failure.
|
|
93
|
+
|
|
94
|
+
### Bundled declaration emit
|
|
95
|
+
|
|
96
|
+
`rollup-plugin-dts` rolls the declaration graph rooted at `src/service/service.ts` into a single `dist/public.d.ts`.
|
|
97
|
+
It tree-shakes to only the types reachable from the entry's exports — `SubagentsService`, `SubagentRecord`, `SpawnOptions`, `SubagentStatus`, `SUBAGENT_EVENTS`, the accessor signatures, plus the seam closure (`WorkspaceProvider`, `Workspace`, the context types, `AgentStatus`, `AgentInvocation`, `SubagentType`) and `LifetimeUsage`, all inlined.
|
|
98
|
+
Imports from `@earendil-works/*` and `@sinclair/typebox` remain **external** in the output (the consumer has them as peers), so they are not inlined.
|
|
99
|
+
|
|
100
|
+
We ship `.ts` source, so we want **only** the `.d.ts` — no JS bundle.
|
|
101
|
+
`rollup-plugin-dts` does exactly that.
|
|
102
|
+
|
|
103
|
+
Resolution note (primary feasibility risk): the roll-up must resolve the publisher's `#src/*` specifiers while parsing the type graph.
|
|
104
|
+
`rollup-plugin-dts` drives the TypeScript compiler, which reads `compilerOptions.paths`; the build config references a tsconfig carrying the existing `#src/*` → `./src/*` paths.
|
|
105
|
+
If `#src/*` does not resolve out of the box, add a tsconfig-paths/alias resolver to the rollup config.
|
|
106
|
+
The first build step (below) is the checkpoint that proves this.
|
|
107
|
+
|
|
108
|
+
### Generation timing and packaging
|
|
109
|
+
|
|
110
|
+
- `prepack` runs `build:types` before both `pnpm pack` and `pnpm publish` (publish packs internally), so the tarball always contains a freshly generated `dist/public.d.ts`.
|
|
111
|
+
- `dist/` is gitignored, so the artifact is **never committed**.
|
|
112
|
+
- A `files` allowlist is added so the gitignored `dist/` is included in the published tarball.
|
|
113
|
+
The allowlist must preserve everything currently published (the whole `src/` tree, `docs/`, `README.md`, `LICENSE`, `CHANGELOG.md`, `AGENTS.md`, `.prettierignore`) plus `dist/`.
|
|
114
|
+
Validate parity with `pnpm pack --dry-run` before/after.
|
|
115
|
+
|
|
116
|
+
### Verification harness (the proof)
|
|
117
|
+
|
|
118
|
+
A script (run locally and in CI) proves external consumability without a publish:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
# pseudocode — scripts/verify-public-types.sh
|
|
122
|
+
pnpm --filter @gotgenes/pi-subagents pack --pack-destination "$TMP" # triggers prepack → build:types
|
|
123
|
+
cd "$TMP/consumer" # minimal package.json + tsconfig
|
|
124
|
+
pnpm add "$TMP/gotgenes-pi-subagents-*.tgz" \
|
|
125
|
+
@earendil-works/pi-ai @earendil-works/pi-coding-agent @earendil-works/pi-tui typescript
|
|
126
|
+
# probe.ts: import { getSubagentsService, type WorkspaceProvider } from "@gotgenes/pi-subagents"
|
|
127
|
+
pnpm exec tsc --noEmit # must pass
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Plus a cheap self-containment guard: assert `dist/public.d.ts` exists, exports the expected symbols, and contains no `#src/` substring (proving it is alias-free).
|
|
131
|
+
|
|
132
|
+
## Module-Level Changes
|
|
133
|
+
|
|
134
|
+
- `packages/pi-subagents/package.json`
|
|
135
|
+
- `exports["."]` → `{ "types": "./dist/public.d.ts", "default": "./src/service/service.ts" }`.
|
|
136
|
+
- Add `devDependencies`: `rollup`, `rollup-plugin-dts` (package-specific, not catalog — only this package builds types).
|
|
137
|
+
- Add scripts: `"build:types"` (runs rollup with the dts config) and `"prepack": "pnpm run build:types"`; add `"verify:public-types"` invoking the harness script.
|
|
138
|
+
- Add a `files` allowlist including `src`, `dist`, `docs`, `README.md`, `LICENSE`, `CHANGELOG.md`, `AGENTS.md`, `.prettierignore` (validated against `pnpm pack --dry-run`).
|
|
139
|
+
- `packages/pi-subagents/rollup.dts.config.mjs` — **new**.
|
|
140
|
+
Entry `src/service/service.ts` → output `dist/public.d.ts`; `rollup-plugin-dts` with the package tsconfig (for `#src/*` paths); externals = peer deps + `@sinclair/typebox`.
|
|
141
|
+
- `packages/pi-subagents/scripts/verify-public-types.sh` (or repo-level `scripts/`) — **new**.
|
|
142
|
+
Pack → throwaway consumer → `tsc`, plus the self-containment grep guard.
|
|
143
|
+
- `.github/workflows/ci.yml`
|
|
144
|
+
- Add a "Verify public types" step in the `check` job (runs on PR and `main`) invoking `pnpm --filter @gotgenes/pi-subagents run verify:public-types`.
|
|
145
|
+
- `packages/pi-subagents/docs/decisions/0003-publish-bundled-type-declarations.md` — **new** ADR.
|
|
146
|
+
Records the first-build-step decision, the rejected alias-free alternative, and the ship-source tradeoff (docs path; `exclude-paths` already covers `docs/decisions`, so it does not trigger a release).
|
|
147
|
+
|
|
148
|
+
No `src/` module is added, renamed, or removed; no exported symbol is removed.
|
|
149
|
+
No `docs/architecture/` layout/complexity tables reference `dist/` or the build config, so no architecture-doc edits are required beyond the ADR (optionally cross-link a one-line "build step" note — defer to build stage).
|
|
150
|
+
|
|
151
|
+
## Test Impact Analysis
|
|
152
|
+
|
|
153
|
+
This is a build/packaging change; `src/` is untouched, so the existing vitest suite (362 tests) is unaffected and stays as-is.
|
|
154
|
+
|
|
155
|
+
1. New verification this enables: a packaged-artifact consumability check (`pnpm pack` → consumer `tsc`) that did not exist and was previously impossible (the package was not consumable at all).
|
|
156
|
+
2. No existing tests become redundant — none currently exercise packaging or the public `exports`.
|
|
157
|
+
3. No existing tests must change; the public service contract types in `service.ts` are unchanged.
|
|
158
|
+
|
|
159
|
+
## Build Order
|
|
160
|
+
|
|
161
|
+
This is a tooling/config change with a verification harness (not red→green→refactor), so it proceeds as build steps that each leave the repo valid.
|
|
162
|
+
|
|
163
|
+
1. **Emit checkpoint.**
|
|
164
|
+
Add `rollup` + `rollup-plugin-dts` devDeps, write `rollup.dts.config.mjs`, add the `build:types` script.
|
|
165
|
+
Run `build:types`; confirm `dist/public.d.ts` is generated, exports the expected symbols, and contains no `#src/` substring (resolve the `#src/*` resolution risk here — add a paths/alias resolver if needed).
|
|
166
|
+
Commit: `build(pi-subagents): bundle public .d.ts with rollup-plugin-dts`.
|
|
167
|
+
2. **Wire exports + packaging.**
|
|
168
|
+
Set the conditional `exports` (`types` + `default`, fixing the stale path), add `prepack`, add the `files` allowlist; validate `pnpm pack --dry-run` parity (no currently-shipped file dropped; `dist/public.d.ts` present).
|
|
169
|
+
Commit: `feat(pi-subagents): publish bundled type declarations and fix stale exports path`.
|
|
170
|
+
3. **Verification harness + CI.**
|
|
171
|
+
Add `scripts/verify-public-types.sh` (pack → throwaway consumer → `tsc`, plus self-containment guard), the `verify:public-types` script, and the CI step.
|
|
172
|
+
Commit: `test(pi-subagents): verify the public surface is type-consumable from the packaged tarball`.
|
|
173
|
+
4. **ADR.**
|
|
174
|
+
Add `docs/decisions/0003-publish-bundled-type-declarations.md`.
|
|
175
|
+
Commit: `docs(pi-subagents): record decision to publish bundled type declarations (ADR 0003)`.
|
|
176
|
+
|
|
177
|
+
## Risks and Mitigations
|
|
178
|
+
|
|
179
|
+
- **`rollup-plugin-dts` cannot resolve `#src/*`** (primary risk).
|
|
180
|
+
Mitigation: drive it with the package tsconfig (which carries `#src/*` paths); if needed, add a tsconfig-paths/alias resolver.
|
|
181
|
+
Step 1 is the explicit checkpoint — if it cannot produce an alias-free `dist/public.d.ts`, stop and reassess before wiring exports.
|
|
182
|
+
- **Deep type graph via `agent.ts`.**
|
|
183
|
+
The seam closure reaches the 510-LOC `agent.ts`.
|
|
184
|
+
Mitigation: `rollup-plugin-dts` tree-shakes to only reachable types; the self-containment guard asserts the output is minimal and alias-free.
|
|
185
|
+
- **`files` allowlist drops currently-published files.**
|
|
186
|
+
Mitigation: diff `pnpm pack --dry-run` before/after; the allowlist must reproduce the existing tarball plus `dist/`.
|
|
187
|
+
- **`types` condition points at a gitignored, build-time artifact.**
|
|
188
|
+
An in-repo workspace-linked consumer that imported the package would need `dist/public.d.ts` present.
|
|
189
|
+
Mitigation: tight scope — `pi-subagents-worktrees` does not import the package yet; #263 consumes the built artifact from the published tarball.
|
|
190
|
+
- **Release batching / ordering with #263.**
|
|
191
|
+
Once #263 resumes and edits `pi-subagents` core, its commits batch into the same `pi-subagents` release.
|
|
192
|
+
Mitigation: publish #270 first (merge its release-please PR → `pi-subagents` publishes), then resume #263 against the published version.
|
|
193
|
+
The current `#263` scaffold commits touch only the unregistered `pi-subagents-worktrees` component, so they do not batch into `pi-subagents` today.
|
|
194
|
+
- **`prepack` must fire under the publish path.**
|
|
195
|
+
`scripts/publish-released.sh` runs `pnpm --filter @gotgenes/pi-subagents publish`, which packs and therefore runs `prepack`.
|
|
196
|
+
Mitigation: the verification harness exercises the same `pnpm pack` path that publish uses.
|
|
197
|
+
|
|
198
|
+
## Open Questions
|
|
199
|
+
|
|
200
|
+
- Whether to add a fast vitest self-containment assertion in addition to the shell harness, or keep the guard inside the script only — defer to the build stage.
|
|
201
|
+
- Whether the ADR should add a short "build process" subsection to `docs/architecture/architecture.md` — defer; the ADR is sufficient.
|
|
202
|
+
- Exact `files` allowlist entries — finalize against `pnpm pack --dry-run` in Step 2.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 262
|
|
3
|
+
issue_title: "Add WorkspaceProvider extension seam"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Retro: #262 — Add WorkspaceProvider extension seam
|
|
7
|
+
|
|
8
|
+
## Stage: Planning (2026-05-29T14:51:15Z)
|
|
9
|
+
|
|
10
|
+
### Session summary
|
|
11
|
+
|
|
12
|
+
Produced a numbered implementation plan for the Phase 16, Step 2 `WorkspaceProvider` seam (ADR 0002).
|
|
13
|
+
The plan adds the seam additively — `WorkspaceProvider` / `Workspace` interfaces, `SubagentsService.registerWorkspaceProvider`, run-start consultation, and `dispose` with a verbatim `resultAddendum` — while leaving the existing `isolation: "worktree"` path untouched for #263 to evict.
|
|
14
|
+
Three TDD steps (two `feat`, one `docs`).
|
|
15
|
+
|
|
16
|
+
### Observations
|
|
17
|
+
|
|
18
|
+
- Two ambiguous choices were surfaced via `ask_user` and resolved: **scope = additive seam only** (Option A — leave the legacy worktree path; #263 evicts it), and **duplicate registration = throw** (loud misconfiguration surface, disposer clears only the active provider).
|
|
19
|
+
- The package's public surface is `./src/service.ts` (per `package.json` `exports`), so the seam types are defined in a new core `src/lifecycle/workspace.ts` and re-exported from `service.ts` — avoiding a `service ↔ lifecycle` import cycle while still exposing them to the worktrees consumer.
|
|
20
|
+
- Diverged from the issue's literal `Disposable` return type: the repo convention for unsubscribe/unregister is a plain `() => void` (matching `SubscribableSession.subscribe` and `pi.events.on`); no `Symbol.dispose` usage exists anywhere in the codebase.
|
|
21
|
+
- Provider-first precedence was chosen so the new seam and the legacy worktree collaborator never silently conflict during the transient dual-path window (#263 collapses the branch).
|
|
22
|
+
- Headline risk is the ADR "no vacant hooks" rule: within #262 the seam is exercised only by test fakes, so it must land **alongside** #263 (`@gotgenes/pi-subagents-worktrees`) and not ship in a release on its own.
|
|
23
|
+
- Step 1 bundles the entire registration surface (types, `SubagentsService` method, adapter impl, `AgentManagerLike`, required `baseCwd`) into one commit because the interface method forces the adapter and the required field forces both construction sites — splitting would not type-check.
|
|
24
|
+
- Verified `test/service/service.test.ts` casts its mock `as unknown as SubagentsService`, so adding an interface method does not break it; flagged the `createManager` and `AgentManagerLike` mock updates for the `baseCwd` and registration additions.
|
|
25
|
+
|
|
26
|
+
## Stage: Implementation — TDD (2026-05-29T15:09:49Z)
|
|
27
|
+
|
|
28
|
+
### Session summary
|
|
29
|
+
|
|
30
|
+
Implemented the `WorkspaceProvider` seam across three TDD cycles (two `feat`, one `docs`): the registration surface (`AgentManager.registerWorkspaceProvider` + service/adapter delegation + `workspace.ts` types), run-start consumption in `Agent.run()` with provider-first precedence and `dispose`/`resultAddendum`, and an architecture-doc update.
|
|
31
|
+
Test count went from 1049 to 1061 (+12 new tests; +6 in `agent.test.ts`, +4 registration in `agent-manager.test.ts`, +1 adapter delegation, plus existing-helper additions).
|
|
32
|
+
All deterministic gates green: `check`, `lint`, `test`, and `fallow dead-code` (run from repo root).
|
|
33
|
+
|
|
34
|
+
### Observations
|
|
35
|
+
|
|
36
|
+
- Deviation from plan (Module-Level Changes): the plan said `service.ts` would re-export "the five seam types and `AgentStatus`", but `fallow dead-code` flagged those five re-exports as unused (no consumer until #263), and AGENTS.md forbids speculative re-exports.
|
|
37
|
+
Resolved by re-exporting only `WorkspaceProvider` — a consumer assigning to it gets `Workspace` and the context types via inference; #263 adds named re-exports when it imports them.
|
|
38
|
+
This is the concrete manifestation of the plan's headline "vacant hook" risk surfacing in the dead-code gate.
|
|
39
|
+
- Lint surprise: `WorkspaceDisposeResult | void` tripped eslint `no-invalid-void-type`.
|
|
40
|
+
Changed the `dispose` return type to `WorkspaceDisposeResult | undefined` (equivalent — a side-effecting `dispose` that falls off the end returns `undefined`); minor divergence from the issue's literal `| void`.
|
|
41
|
+
- Three test mock factories implement `AgentManagerLike` in `service-adapter.test.ts` (`createMockManager`, `defaultManager`, `createTestManager`) — all three needed the new `registerWorkspaceProvider` stub; `tsc` caught the third after the first two were updated.
|
|
42
|
+
- Used `git commit --fixup` + `--autosquash` rebase twice (unpushed history) to fold the fallow trim into the Step 1 `feat` commit and the reviewer's doc-wording fix into the Step 3 `docs` commit, keeping each commit self-consistent.
|
|
43
|
+
- Pre-completion reviewer: WARN — all blocking checks pass; one non-blocking doc finding (architecture.md overstated that `Workspace` is re-exported).
|
|
44
|
+
Addressed before finishing.
|
|
45
|
+
|
|
46
|
+
## Stage: Final Retrospective (2026-05-29T15:40:54Z)
|
|
47
|
+
|
|
48
|
+
### Session summary
|
|
49
|
+
|
|
50
|
+
Shipped #262: pushed, CI green, closed the issue, and merged release-please PR #269 → `pi-subagents-v11.5.0`.
|
|
51
|
+
Mid-session the user asked what `model: claude-sonnet-4-6-20260526` in `.pi/agents/pre-completion-reviewer.md` resolved to; investigation found it had no entry in Pi's model registry and was silently falling back to the parent session model, and the fix (`anthropic/claude-sonnet-4-6`) landed in `1e46c5f4`.
|
|
52
|
+
Across all stages the plan's risk predictions held and TDD verification was incremental.
|
|
53
|
+
|
|
54
|
+
### Observations
|
|
55
|
+
|
|
56
|
+
#### What went well
|
|
57
|
+
|
|
58
|
+
- The planning-stage "vacant hook" risk prediction manifested exactly as predicted at the `fallow dead-code` gate: the plan named the failure (speculative re-exports flagged dead) before it happened, and the fix (re-export only `WorkspaceProvider`) was already reasoned out — a clean closed loop from planning risk to implementation gate.
|
|
59
|
+
- Incremental verification during TDD: `check` / `test` / `lint` ran after each step plus per-file vitest red→green, not just at the end.
|
|
60
|
+
- The model-spec question surfaced and fixed a latent silent bug — the reviewer's configured `model:` had no registry entry and was inheriting the parent model, so the misconfiguration produced no error.
|
|
61
|
+
- Cost discipline: the session model was switched to `opencode-go/deepseek-v4-flash` for the mechanical shipping stage — appropriate model-to-task matching.
|
|
62
|
+
|
|
63
|
+
#### What caused friction (agent side)
|
|
64
|
+
|
|
65
|
+
- `rabbit-hole` — the model-resolution investigation (≈30 tool calls, turns 119–160) grepped the Pi core monorepo (`~/development/pi/pi/packages/coding-agent`) for `.pi/agents/` frontmatter handling that does not live there, before landing on `pi-prompt-template-model/model-selection.ts` (a `pi-packages` dependency) plus the `models.generated.ts` registry.
|
|
66
|
+
Impact: long detour on a user tangent; no rework, but the answer was reachable far sooner.
|
|
67
|
+
- `other` (minor, recurring) — the pre-commit `trim trailing whitespace` hook reformatted files on the first `git commit`, failing it; the immediate retry succeeded (turns 55→56 and 95→96).
|
|
68
|
+
Impact: one wasted commit attempt per occurrence, no rework.
|
|
69
|
+
|
|
70
|
+
#### What caused friction (user side)
|
|
71
|
+
|
|
72
|
+
- The broken model spec was pre-existing config; because the failure mode is a silent fallback, such typos never surface on their own.
|
|
73
|
+
Opportunity (not criticism): a short convention note so future `.pi/agents/*.md` authoring uses the registry-resolvable `provider/id` form.
|
|
74
|
+
|
|
75
|
+
### Diagnostic details
|
|
76
|
+
|
|
77
|
+
- **Model-performance correlation** — the `pre-completion-reviewer` subagent was dispatched (turn 90) while the parent model was `anthropic/claude-opus-4-8`; because its `model:` frontmatter did not resolve, it ran on opus-4-8 (strong reasoning, appropriate for judgment-heavy review) rather than the configured Sonnet.
|
|
78
|
+
No quality mismatch in outcome, but the risk was latent: a weaker parent model would have silently degraded the reviewer.
|
|
79
|
+
The shipping stage ran on `deepseek-v4-flash` (mechanical git/CI/release ops) — appropriate.
|
|
80
|
+
- **Escalation-delay tracking** — the model-resolution `rabbit-hole` ran ≈30 consecutive search calls on the same goal, far past the 5-call threshold; consulting the `prompt-template-authoring` skill or reading `model-selection.ts` first would have shortcut it.
|
|
81
|
+
- **Unused-tool detection** — for that investigation the `prompt-template-authoring` skill (documents template/agent `model:` format) and `code_search` were available but not used; grep over two monorepos was used instead.
|
|
82
|
+
- **Feedback-loop gap analysis** — none; verification ran incrementally after each TDD step, not only at the end.
|
|
83
|
+
|
|
84
|
+
### Changes made
|
|
85
|
+
|
|
86
|
+
1. `AGENTS.md` (Pre-completion reviewer subsection) — added a one-line guardrail: agent `model:` frontmatter must use the `provider/id` alias form the Pi CLI/UI accepts, because an ID absent from the model registry silently falls back to the parent session model.
|
|
87
|
+
2. `packages/pi-subagents/docs/retro/0262-add-workspace-provider-seam.md` — this Final Retrospective stage entry.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 270
|
|
3
|
+
issue_title: "Make @gotgenes/pi-subagents type-consumable by sibling workspace packages"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Retro: #270 — Make @gotgenes/pi-subagents type-consumable by sibling workspace packages
|
|
7
|
+
|
|
8
|
+
## Stage: Planning (2026-05-29T00:00:00Z)
|
|
9
|
+
|
|
10
|
+
### Session summary
|
|
11
|
+
|
|
12
|
+
Diagnosed the consumability failure empirically with `tsc --traceResolution` and planned a `.d.ts`-emit fix.
|
|
13
|
+
The plan adds a `rollup-plugin-dts` build that bundles `src/service/service.ts` into a self-contained `dist/public.d.ts`, wires conditional `exports` (`types` → the bundle, `default` → the real source), generates the artifact at `prepack` time, ships it via a `files` allowlist, and proves external consumability with a `pnpm pack` → throwaway-consumer → `tsc` harness.
|
|
14
|
+
|
|
15
|
+
### Observations
|
|
16
|
+
|
|
17
|
+
- Root cause is two compounding failures: the stale `exports["."]` path (`./src/service.ts` does not exist) and, once fixed, an unresolvable `#src/*` cascade.
|
|
18
|
+
The trace showed the consumer's own `paths` (`#src/*` → `./src/*`) intercept first (both packages define `#src/*`), and the publisher's `imports`-field fallback cannot resolve the extensionless `.ts` target.
|
|
19
|
+
- The public type closure is entangled: `WorkspaceProvider` → `AgentStatus` (in the 510-LOC `agent.ts`) → `types.ts` (which re-exports the `Agent` class).
|
|
20
|
+
This made the alias-free-entry alternative (Option 2) a substantial source restructure, so it was rejected.
|
|
21
|
+
- Decisions taken via `ask_user`:
|
|
22
|
+
1. Approach — emit a bundled `.d.ts` (the repo's first build step), over alias-free restructure or type re-declaration.
|
|
23
|
+
2. Bundler — `rollup-plugin-dts` (purpose-built for flattening declarations; no JS bundle, which suits ship-source), over `tsdown`/`api-extractor`.
|
|
24
|
+
3. Artifact — not committed; generated at `prepack` and shipped in the tarball, consumed via the package interface.
|
|
25
|
+
4. Scope — tight: packaging + a `pnpm pack`-based verification harness in #270; defer the `pi-subagents-worktrees` registry-consumption flip (drop `workspace:*`, `link-workspace-packages: false`, wire the real import) to #263.
|
|
26
|
+
- Scope was deliberately narrowed after a chicken-and-egg surfaced: the registry version carrying the fix does not exist until #270 publishes, so the meaningful consumer flip belongs to #263.
|
|
27
|
+
- Sequencing constraint for #263 (captured in the plan): publish #270 first — merge its release-please PR so `pi-subagents` publishes — *before* resuming #263, otherwise #263's `pi-subagents` core edits batch into the same release.
|
|
28
|
+
The current `#263` scaffold commits on `main` touch only the unregistered `pi-subagents-worktrees` component, so they do not batch into `pi-subagents` and #270 ships cleanly.
|
|
29
|
+
- Primary feasibility risk flagged: whether `rollup-plugin-dts` resolves `#src/*` while rolling up the type graph.
|
|
30
|
+
Build Step 1 is the explicit checkpoint (emit + assert the output is alias-free and exports the expected symbols).
|
|
31
|
+
- `dist/` is gitignored and already excluded by eslint/biome; the new wrinkle is that a `files` allowlist is required so the gitignored `dist/public.d.ts` is included in the npm tarball — validate `pnpm pack --dry-run` parity so no currently-shipped file is dropped.
|
|
32
|
+
|
|
33
|
+
## Stage: Implementation — Build (2026-05-29T00:00:00Z)
|
|
34
|
+
|
|
35
|
+
### Session summary
|
|
36
|
+
|
|
37
|
+
Executed all four build-order steps: added `rollup` + `rollup-plugin-dts` and a `build:types` script that bundles `src/service/service.ts` into a self-contained `dist/public.d.ts`; wired conditional `exports` (`types` + `default`, fixing the stale path) with a `prepack` hook and a `files` allowlist; added a `pnpm pack` → throwaway-consumer → `tsc` verification harness (`scripts/verify-public-types.sh`) plus a CI step; and recorded ADR 0003.
|
|
38
|
+
A fifth commit documented the new build step in the `package-pi-subagents` skill (reviewer WARN).
|
|
39
|
+
Root `pnpm run check`, root `pnpm run lint`, and `verify:public-types` all pass.
|
|
40
|
+
|
|
41
|
+
### Observations
|
|
42
|
+
|
|
43
|
+
- The primary feasibility risk (`rollup-plugin-dts` resolving `#src/*`) resolved cleanly out of the box: driving it with the package `tsconfig` (which carries the `#src/*` paths) produced a 178-line `dist/public.d.ts` with zero `#src/` residue and only `ThinkingLevel` kept external from `@earendil-works/pi-ai`.
|
|
44
|
+
No alias/path resolver plugin was needed.
|
|
45
|
+
- Harness deviation (fixed in the same step): `pnpm add` in the isolated (`--ignore-workspace`) throwaway consumer exited non-zero with `ERR_PNPM_IGNORED_BUILDS` because it does not inherit the workspace `allowBuilds` approvals (`@google/genai`, `protobufjs`).
|
|
46
|
+
Fixed by adding `--ignore-scripts` — a type-check needs no dependency build scripts.
|
|
47
|
+
Worth remembering for any future packaged-consumer harness.
|
|
48
|
+
- A subtle gotcha while debugging: `pnpm ... | tail; echo $?` reports `tail`'s exit, not pnpm's, which masked the real failure.
|
|
49
|
+
Use `set -o pipefail` or check the command directly.
|
|
50
|
+
- `files` allowlist parity was validated with a before/after `pnpm pack --dry-run` diff: nothing dropped, only `dist/public.d.ts` added.
|
|
51
|
+
The allowlist reproduces the current contents (`src`, `docs`, `vitest.config.ts`, `AGENTS.md`, `CHANGELOG.md`, `.prettierignore`) plus `dist`.
|
|
52
|
+
Did not take the opportunity to slim the tarball (docs/test-config still ship) — that would be a separate deliberate change.
|
|
53
|
+
- Runtime `default` → `./src/service/service.ts` is safe because that module's only internal imports are `import type`, which erase; no runtime `#src/*` resolution occurs.
|
|
54
|
+
- No `src/`/`test/` `.ts` files were touched, so the vitest suite and `tsc` were unaffected (confirmed via root check).
|
|
55
|
+
- Pre-completion reviewer: WARN — no findings attributable to this session.
|
|
56
|
+
Reviewer warnings: (1) the `package-pi-subagents` skill lacked a build-step note — addressed in commit `2ff5a375`; (2) `pnpm fallow dead-code` exits non-zero on a pre-existing finding in `packages/pi-subagents-worktrees/package.json` from the #263 scaffold (commit `9a7dcfc5`), out of scope for #270 and left for #263.
|
package/package.json
CHANGED
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gotgenes/pi-subagents",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.6.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
|
-
".":
|
|
6
|
+
".": {
|
|
7
|
+
"types": "./dist/public.d.ts",
|
|
8
|
+
"default": "./src/service/service.ts"
|
|
9
|
+
}
|
|
7
10
|
},
|
|
11
|
+
"files": [
|
|
12
|
+
"src",
|
|
13
|
+
"dist",
|
|
14
|
+
"docs",
|
|
15
|
+
"vitest.config.ts",
|
|
16
|
+
"AGENTS.md",
|
|
17
|
+
"CHANGELOG.md",
|
|
18
|
+
".prettierignore"
|
|
19
|
+
],
|
|
8
20
|
"imports": {
|
|
9
21
|
"#src/*": "./src/*",
|
|
10
22
|
"#test/*": "./test/*"
|
|
@@ -51,6 +63,8 @@
|
|
|
51
63
|
"@earendil-works/pi-coding-agent": "0.75.4",
|
|
52
64
|
"@earendil-works/pi-tui": "0.75.4",
|
|
53
65
|
"@types/node": "^22.15.3",
|
|
66
|
+
"rollup": "^4.60.4",
|
|
67
|
+
"rollup-plugin-dts": "^6.4.1",
|
|
54
68
|
"rumdl": "^0.1.93",
|
|
55
69
|
"typescript": "^6.0.3",
|
|
56
70
|
"vitest": "^4.1.5"
|
|
@@ -64,6 +78,8 @@
|
|
|
64
78
|
},
|
|
65
79
|
"scripts": {
|
|
66
80
|
"check": "tsc --noEmit",
|
|
81
|
+
"build:types": "rollup -c rollup.dts.config.mjs",
|
|
82
|
+
"verify:public-types": "bash scripts/verify-public-types.sh",
|
|
67
83
|
"test": "vitest run",
|
|
68
84
|
"test:watch": "vitest",
|
|
69
85
|
"lint:md": "rumdl check *.md docs/**/*.md",
|
package/src/index.ts
CHANGED
|
@@ -167,6 +167,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
167
167
|
const manager = new AgentManager({
|
|
168
168
|
runner: new ConcreteAgentRunner(runnerDeps),
|
|
169
169
|
worktrees: new GitWorktreeManager(process.cwd()),
|
|
170
|
+
baseCwd: process.cwd(),
|
|
170
171
|
observer,
|
|
171
172
|
queue,
|
|
172
173
|
getRunConfig: () => settings,
|
|
@@ -13,6 +13,7 @@ import { Agent, type AgentLifecycleObserver } from "#src/lifecycle/agent";
|
|
|
13
13
|
import type { AgentRunner } from "#src/lifecycle/agent-runner";
|
|
14
14
|
import type { ConcurrencyQueue } from "#src/lifecycle/concurrency-queue";
|
|
15
15
|
import type { ParentSnapshot } from "#src/lifecycle/parent-snapshot";
|
|
16
|
+
import type { WorkspaceProvider } from "#src/lifecycle/workspace";
|
|
16
17
|
import type { WorktreeManager } from "#src/lifecycle/worktree";
|
|
17
18
|
import { WorktreeIsolation } from "#src/lifecycle/worktree-isolation";
|
|
18
19
|
|
|
@@ -33,6 +34,8 @@ export interface AgentManagerOptions {
|
|
|
33
34
|
worktrees: WorktreeManager;
|
|
34
35
|
/** Concurrency queue — owns scheduling, limit checks, and drain logic. */
|
|
35
36
|
queue: ConcurrencyQueue;
|
|
37
|
+
/** Base working directory handed to a workspace provider (the parent cwd). */
|
|
38
|
+
baseCwd: string;
|
|
36
39
|
getRunConfig?: () => RunConfig;
|
|
37
40
|
observer?: AgentManagerObserver;
|
|
38
41
|
}
|
|
@@ -70,12 +73,20 @@ export class AgentManager {
|
|
|
70
73
|
private readonly runner: AgentRunner;
|
|
71
74
|
private readonly worktrees: WorktreeManager;
|
|
72
75
|
private readonly queue: ConcurrencyQueue;
|
|
76
|
+
private readonly baseCwd: string;
|
|
73
77
|
private getRunConfig?: () => RunConfig;
|
|
78
|
+
private _workspaceProvider?: WorkspaceProvider;
|
|
79
|
+
|
|
80
|
+
/** The registered workspace provider, or undefined when none is registered. */
|
|
81
|
+
get workspaceProvider(): WorkspaceProvider | undefined {
|
|
82
|
+
return this._workspaceProvider;
|
|
83
|
+
}
|
|
74
84
|
|
|
75
85
|
constructor(options: AgentManagerOptions) {
|
|
76
86
|
this.runner = options.runner;
|
|
77
87
|
this.worktrees = options.worktrees;
|
|
78
88
|
this.queue = options.queue;
|
|
89
|
+
this.baseCwd = options.baseCwd;
|
|
79
90
|
this.observer = options.observer;
|
|
80
91
|
this.getRunConfig = options.getRunConfig;
|
|
81
92
|
// Cleanup completed agents after 10 minutes (but keep sessions for resume)
|
|
@@ -83,6 +94,23 @@ export class AgentManager {
|
|
|
83
94
|
this.cleanupInterval.unref();
|
|
84
95
|
}
|
|
85
96
|
|
|
97
|
+
/**
|
|
98
|
+
* Register the single workspace provider. Throws if one is already
|
|
99
|
+
* registered (chaining is out of scope — see ADR 0002). Returns a disposer
|
|
100
|
+
* that clears the slot only if this provider is still the active one.
|
|
101
|
+
*/
|
|
102
|
+
registerWorkspaceProvider(provider: WorkspaceProvider): () => void {
|
|
103
|
+
if (this._workspaceProvider) {
|
|
104
|
+
throw new Error(
|
|
105
|
+
"A WorkspaceProvider is already registered; only one is supported.",
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
this._workspaceProvider = provider;
|
|
109
|
+
return () => {
|
|
110
|
+
if (this._workspaceProvider === provider) this._workspaceProvider = undefined;
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
86
114
|
/** Compose a per-agent lifecycle observer from manager and spawn-config concerns. */
|
|
87
115
|
private buildObserver(options: AgentSpawnConfig): AgentLifecycleObserver {
|
|
88
116
|
return {
|
|
@@ -140,6 +168,8 @@ export class AgentManager {
|
|
|
140
168
|
: undefined,
|
|
141
169
|
observer: this.buildObserver(options),
|
|
142
170
|
getRunConfig: this.getRunConfig,
|
|
171
|
+
baseCwd: this.baseCwd,
|
|
172
|
+
getWorkspaceProvider: () => this._workspaceProvider,
|
|
143
173
|
});
|
|
144
174
|
this.agents.set(id, record);
|
|
145
175
|
|
package/src/lifecycle/agent.ts
CHANGED
|
@@ -26,6 +26,7 @@ import type { ExecutionState } from "#src/lifecycle/execution-state";
|
|
|
26
26
|
import type { ParentSnapshot } from "#src/lifecycle/parent-snapshot";
|
|
27
27
|
import type { LifetimeUsage } from "#src/lifecycle/usage";
|
|
28
28
|
import { addUsage } from "#src/lifecycle/usage";
|
|
29
|
+
import type { Workspace, WorkspaceProvider } from "#src/lifecycle/workspace";
|
|
29
30
|
import type { WorktreeIsolation } from "#src/lifecycle/worktree-isolation";
|
|
30
31
|
import { NotificationState } from "#src/observation/notification-state";
|
|
31
32
|
import { subscribeAgentObserver } from "#src/observation/record-observer";
|
|
@@ -72,6 +73,10 @@ export interface AgentInit {
|
|
|
72
73
|
worktree?: WorktreeIsolation;
|
|
73
74
|
observer?: AgentLifecycleObserver;
|
|
74
75
|
getRunConfig?: () => RunConfig;
|
|
76
|
+
/** Resolves the registered workspace provider (if any) at run-start. */
|
|
77
|
+
getWorkspaceProvider?: () => WorkspaceProvider | undefined;
|
|
78
|
+
/** Parent working directory handed to a workspace provider's prepare(). */
|
|
79
|
+
baseCwd?: string;
|
|
75
80
|
|
|
76
81
|
// Run config (required for run(), optional for tests)
|
|
77
82
|
snapshot?: ParentSnapshot;
|
|
@@ -129,6 +134,10 @@ export class Agent {
|
|
|
129
134
|
readonly worktree?: WorktreeIsolation;
|
|
130
135
|
readonly observer?: AgentLifecycleObserver;
|
|
131
136
|
private readonly _getRunConfig?: () => RunConfig;
|
|
137
|
+
private readonly _getWorkspaceProvider?: () => WorkspaceProvider | undefined;
|
|
138
|
+
private readonly _baseCwd: string;
|
|
139
|
+
/** Workspace prepared at run-start by a provider — undefined when none is registered. */
|
|
140
|
+
private _workspace?: Workspace;
|
|
132
141
|
|
|
133
142
|
// Run config — optional (required for run())
|
|
134
143
|
private readonly _snapshot?: ParentSnapshot;
|
|
@@ -186,6 +195,8 @@ export class Agent {
|
|
|
186
195
|
this.worktree = init.worktree;
|
|
187
196
|
this.observer = init.observer;
|
|
188
197
|
this._getRunConfig = init.getRunConfig;
|
|
198
|
+
this._getWorkspaceProvider = init.getWorkspaceProvider;
|
|
199
|
+
this._baseCwd = init.baseCwd ?? "";
|
|
189
200
|
|
|
190
201
|
// Run config
|
|
191
202
|
this._snapshot = init.snapshot;
|
|
@@ -223,8 +234,23 @@ export class Agent {
|
|
|
223
234
|
this.observer?.onStarted?.(this);
|
|
224
235
|
this.wireSignal(this._signal, () => this.abort());
|
|
225
236
|
|
|
237
|
+
let cwd: string | undefined;
|
|
226
238
|
try {
|
|
227
|
-
|
|
239
|
+
// Provider-first: a registered workspace provider supplies the cwd and
|
|
240
|
+
// owns teardown; otherwise fall back to the legacy worktree collaborator.
|
|
241
|
+
const provider = this._getWorkspaceProvider?.();
|
|
242
|
+
if (provider) {
|
|
243
|
+
this._workspace = await provider.prepare({
|
|
244
|
+
agentId: this.id,
|
|
245
|
+
agentType: this.type,
|
|
246
|
+
baseCwd: this._baseCwd,
|
|
247
|
+
invocation: this.invocation,
|
|
248
|
+
});
|
|
249
|
+
cwd = this._workspace?.cwd;
|
|
250
|
+
} else {
|
|
251
|
+
this.worktree?.setup();
|
|
252
|
+
cwd = this.worktree?.path;
|
|
253
|
+
}
|
|
228
254
|
} catch (err) {
|
|
229
255
|
this.markError(err);
|
|
230
256
|
this.releaseListeners();
|
|
@@ -236,7 +262,7 @@ export class Agent {
|
|
|
236
262
|
try {
|
|
237
263
|
const result = await this._runner.run(this._snapshot, this.type, this._prompt, {
|
|
238
264
|
context: {
|
|
239
|
-
cwd
|
|
265
|
+
cwd,
|
|
240
266
|
parentSession: this._parentSession,
|
|
241
267
|
},
|
|
242
268
|
model: this._model,
|
|
@@ -442,9 +468,19 @@ export class Agent {
|
|
|
442
468
|
this.releaseListeners();
|
|
443
469
|
|
|
444
470
|
let finalResult = result.responseText;
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
471
|
+
if (this._workspace) {
|
|
472
|
+
const finalStatus: AgentStatus = result.aborted
|
|
473
|
+
? "aborted"
|
|
474
|
+
: result.steered
|
|
475
|
+
? "steered"
|
|
476
|
+
: "completed";
|
|
477
|
+
const disposeResult = this._workspace.dispose({ status: finalStatus, description: this.description });
|
|
478
|
+
if (disposeResult?.resultAddendum) finalResult += disposeResult.resultAddendum;
|
|
479
|
+
} else {
|
|
480
|
+
const wtResult = this.worktree?.cleanup(this.description);
|
|
481
|
+
if (wtResult?.hasChanges && wtResult.branch) {
|
|
482
|
+
finalResult += `\n\n---\nChanges saved to branch \`${wtResult.branch}\`. Merge with: \`git merge ${wtResult.branch}\``;
|
|
483
|
+
}
|
|
448
484
|
}
|
|
449
485
|
|
|
450
486
|
if (result.aborted) this.markAborted(finalResult);
|
|
@@ -465,8 +501,9 @@ export class Agent {
|
|
|
465
501
|
this.releaseListeners();
|
|
466
502
|
|
|
467
503
|
try {
|
|
468
|
-
this.
|
|
469
|
-
|
|
504
|
+
if (this._workspace) this._workspace.dispose({ status: "error", description: this.description });
|
|
505
|
+
else this.worktree?.cleanup(this.description);
|
|
506
|
+
} catch (cleanupErr) { debugLog("workspace dispose on agent error", cleanupErr); }
|
|
470
507
|
|
|
471
508
|
this.observer?.onRunFinished?.(this);
|
|
472
509
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* workspace.ts — The single generative extension seam (ADR 0002, Phase 16 Step 2).
|
|
3
|
+
*
|
|
4
|
+
* "Where does a child run, and what brackets the run?" is a strategy (git
|
|
5
|
+
* worktree, container, tmpdir, remote sandbox), not core behavior. The core
|
|
6
|
+
* needs only a working directory plus a disposal hook; the default — the
|
|
7
|
+
* parent's cwd, with no setup/teardown — is always correct.
|
|
8
|
+
*
|
|
9
|
+
* Unlike the observational lifecycle events in child-lifecycle.ts, this is a
|
|
10
|
+
* *generative* seam: a registered provider returns a value the core consumes
|
|
11
|
+
* synchronously at run-start. The core has no knowledge of git or worktrees.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { AgentStatus } from "#src/lifecycle/agent";
|
|
15
|
+
import type { AgentInvocation, SubagentType } from "#src/types";
|
|
16
|
+
|
|
17
|
+
/** Context the core hands a provider when a child run starts. */
|
|
18
|
+
export interface WorkspacePrepareContext {
|
|
19
|
+
agentId: string;
|
|
20
|
+
agentType: SubagentType;
|
|
21
|
+
baseCwd: string;
|
|
22
|
+
invocation?: AgentInvocation;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Outcome the core reports to a workspace when the run ends. */
|
|
26
|
+
export interface WorkspaceDisposeOutcome {
|
|
27
|
+
status: AgentStatus;
|
|
28
|
+
description: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** What dispose may hand back for the core to fold into the child result. */
|
|
32
|
+
export interface WorkspaceDisposeResult {
|
|
33
|
+
/** Appended verbatim to the child's result text — the provider owns the wording. */
|
|
34
|
+
resultAddendum?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** A prepared working directory plus its bracketed teardown. Born complete. */
|
|
38
|
+
export interface Workspace {
|
|
39
|
+
/** The working directory — already exists when the workspace is handed back. */
|
|
40
|
+
readonly cwd: string;
|
|
41
|
+
dispose(outcome: WorkspaceDisposeOutcome): WorkspaceDisposeResult | undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** The single generative seam: supplies a child's workspace. */
|
|
45
|
+
export interface WorkspaceProvider {
|
|
46
|
+
prepare(ctx: WorkspacePrepareContext): Promise<Workspace | undefined>;
|
|
47
|
+
}
|