@gotgenes/pi-subagents 11.5.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 CHANGED
@@ -5,6 +5,13 @@ 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
+ ## [11.6.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v11.5.0...pi-subagents-v11.6.0) (2026-05-29)
9
+
10
+
11
+ ### Features
12
+
13
+ * **pi-subagents:** publish bundled type declarations and fix stale exports path ([8eda6f6](https://github.com/gotgenes/pi-packages/commit/8eda6f6611a12c60d99a5069f352abd634997e67))
14
+
8
15
  ## [11.5.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v11.4.0...pi-subagents-v11.5.0) (2026-05-29)
9
16
 
10
17
 
@@ -0,0 +1,178 @@
1
+ import { ThinkingLevel } from '@earendil-works/pi-ai';
2
+
3
+ /** usage.ts — Token usage: shapes, accumulator operators, session-stats readers. */
4
+ /**
5
+ * Lifetime usage components, accumulated via `message_end` events. Survives
6
+ * compaction (which replaces session.state.messages and would reset any
7
+ * stats-derived sum). cacheRead is excluded because each turn's cacheRead is
8
+ * the cumulative cached prefix re-read on that one call — summing across
9
+ * turns counts the prefix N times. See issue #38.
10
+ */
11
+ type LifetimeUsage = {
12
+ input: number;
13
+ output: number;
14
+ cacheWrite: number;
15
+ };
16
+
17
+ /**
18
+ * types.ts — Type definitions for the subagent system.
19
+ */
20
+
21
+ /** Agent type: any string name (built-in defaults or user-defined). */
22
+ type SubagentType = string;
23
+ /** Isolation mode for agent execution. */
24
+ type IsolationMode = "worktree";
25
+ interface AgentInvocation {
26
+ /** Short display name, e.g. "haiku" — only set when different from parent. */
27
+ modelName?: string;
28
+ thinking?: ThinkingLevel;
29
+ maxTurns?: number;
30
+ isolated?: boolean;
31
+ inheritContext?: boolean;
32
+ runInBackground?: boolean;
33
+ isolation?: IsolationMode;
34
+ }
35
+
36
+ /**
37
+ * agent.ts — Agent class with encapsulated status-transition logic and per-agent behavior.
38
+ *
39
+ * Status transitions (status, result, error, startedAt, completedAt) are owned
40
+ * by the class and exposed via transition methods. External code reads these
41
+ * fields through public properties but cannot write them directly.
42
+ *
43
+ * Stats (toolUses, lifetimeUsage, compactionCount) are owned by the class and
44
+ * accumulated via mutation methods (incrementToolUses, addUsage, incrementCompactions).
45
+ *
46
+ * Behavior (abort, steer buffering, worktree setup) lives on the agent
47
+ * rather than on AgentManager — each agent manages its own lifecycle concerns.
48
+ *
49
+ * Worktree isolation is delegated to an optional WorktreeIsolation collaborator
50
+ * (set at construction when isolation is requested); its presence IS the mode.
51
+ *
52
+ * Phase-specific collaborators (execution, notification) are attached
53
+ * after construction as lifecycle information becomes available.
54
+ */
55
+
56
+ type AgentStatus = "queued" | "running" | "completed" | "steered" | "aborted" | "stopped" | "error";
57
+
58
+ /**
59
+ * workspace.ts — The single generative extension seam (ADR 0002, Phase 16 Step 2).
60
+ *
61
+ * "Where does a child run, and what brackets the run?" is a strategy (git
62
+ * worktree, container, tmpdir, remote sandbox), not core behavior. The core
63
+ * needs only a working directory plus a disposal hook; the default — the
64
+ * parent's cwd, with no setup/teardown — is always correct.
65
+ *
66
+ * Unlike the observational lifecycle events in child-lifecycle.ts, this is a
67
+ * *generative* seam: a registered provider returns a value the core consumes
68
+ * synchronously at run-start. The core has no knowledge of git or worktrees.
69
+ */
70
+
71
+ /** Context the core hands a provider when a child run starts. */
72
+ interface WorkspacePrepareContext {
73
+ agentId: string;
74
+ agentType: SubagentType;
75
+ baseCwd: string;
76
+ invocation?: AgentInvocation;
77
+ }
78
+ /** Outcome the core reports to a workspace when the run ends. */
79
+ interface WorkspaceDisposeOutcome {
80
+ status: AgentStatus;
81
+ description: string;
82
+ }
83
+ /** What dispose may hand back for the core to fold into the child result. */
84
+ interface WorkspaceDisposeResult {
85
+ /** Appended verbatim to the child's result text — the provider owns the wording. */
86
+ resultAddendum?: string;
87
+ }
88
+ /** A prepared working directory plus its bracketed teardown. Born complete. */
89
+ interface Workspace {
90
+ /** The working directory — already exists when the workspace is handed back. */
91
+ readonly cwd: string;
92
+ dispose(outcome: WorkspaceDisposeOutcome): WorkspaceDisposeResult | undefined;
93
+ }
94
+ /** The single generative seam: supplies a child's workspace. */
95
+ interface WorkspaceProvider {
96
+ prepare(ctx: WorkspacePrepareContext): Promise<Workspace | undefined>;
97
+ }
98
+
99
+ /**
100
+ * service.ts — Public API surface for cross-extension access to subagents.
101
+ *
102
+ * Consumers declare this package as an optional peer dependency and use
103
+ * dynamic import to access the accessor functions:
104
+ *
105
+ * const { getSubagentsService } = await import("@gotgenes/pi-subagents");
106
+ * const svc = getSubagentsService();
107
+ * svc?.spawn("Explore", "Check for stale TODOs");
108
+ */
109
+
110
+ type SubagentStatus = "queued" | "running" | "completed" | "steered" | "aborted" | "stopped" | "error";
111
+ /** Serializable snapshot of an agent's state — no live session objects. */
112
+ interface SubagentRecord {
113
+ id: string;
114
+ type: string;
115
+ description: string;
116
+ status: SubagentStatus;
117
+ result?: string;
118
+ error?: string;
119
+ toolUses: number;
120
+ startedAt: number;
121
+ completedAt?: number;
122
+ lifetimeUsage: LifetimeUsage;
123
+ compactionCount: number;
124
+ worktreeResult?: {
125
+ hasChanges: boolean;
126
+ branch?: string;
127
+ };
128
+ }
129
+ /** Options for spawning an agent via the service. */
130
+ interface SpawnOptions {
131
+ description?: string;
132
+ model?: string;
133
+ maxTurns?: number;
134
+ thinkingLevel?: string;
135
+ isolated?: boolean;
136
+ inheritContext?: boolean;
137
+ foreground?: boolean;
138
+ bypassQueue?: boolean;
139
+ isolation?: "worktree";
140
+ }
141
+ /** The public service contract for cross-extension subagent access. */
142
+ interface SubagentsService {
143
+ /** Spawn an agent. Returns the agent ID immediately. */
144
+ spawn(type: string, prompt: string, options?: SpawnOptions): string;
145
+ /** Get a snapshot of an agent's current state. */
146
+ getRecord(id: string): SubagentRecord | undefined;
147
+ /** List all tracked agents, most recent first. */
148
+ listAgents(): SubagentRecord[];
149
+ /** Abort a running or queued agent. Returns false if not found. */
150
+ abort(id: string): boolean;
151
+ /** Send a steering message to a running agent. */
152
+ steer(id: string, message: string): Promise<boolean>;
153
+ /** Wait for all running and queued agents to complete. */
154
+ waitForAll(): Promise<void>;
155
+ /** Whether any agents are running or queued. */
156
+ hasRunning(): boolean;
157
+ /**
158
+ * Register the single workspace provider that supplies a child's working
159
+ * directory plus bracketed setup/teardown. Throws if one is already
160
+ * registered. Returns a disposer that unregisters the provider.
161
+ */
162
+ registerWorkspaceProvider(provider: WorkspaceProvider): () => void;
163
+ }
164
+ /** Event channel constants for pi.events subscriptions. */
165
+ declare const SUBAGENT_EVENTS: {
166
+ readonly STARTED: "subagents:started";
167
+ readonly COMPLETED: "subagents:completed";
168
+ readonly ACTIVITY: "subagents:activity";
169
+ };
170
+ /** Publish the SubagentsService on globalThis for cross-extension access. */
171
+ declare function publishSubagentsService(service: SubagentsService): void;
172
+ /** Retrieve the published SubagentsService, or undefined if not yet published. */
173
+ declare function getSubagentsService(): SubagentsService | undefined;
174
+ /** Remove the SubagentsService from globalThis (call on shutdown/reload). */
175
+ declare function unpublishSubagentsService(): void;
176
+
177
+ export { SUBAGENT_EVENTS, getSubagentsService, publishSubagentsService, unpublishSubagentsService };
178
+ export type { LifetimeUsage, SpawnOptions, SubagentRecord, SubagentStatus, SubagentsService, WorkspaceProvider };
@@ -0,0 +1,69 @@
1
+ ---
2
+ status: accepted
3
+ date: 2026-05-29
4
+ ---
5
+
6
+ # 0003 — Publish a bundled `.d.ts` for the public surface
7
+
8
+ ## Status
9
+
10
+ Accepted.
11
+ Introduces the repository's first build step, scoped to type declarations only.
12
+
13
+ ## Context
14
+
15
+ `@gotgenes/pi-subagents` could not be imported by another TypeScript package in this workspace.
16
+ Issue #263 (extract worktree isolation to `@gotgenes/pi-subagents-worktrees`) is the first intra-repo consumer: it must `implements WorkspaceProvider` and call `getSubagentsService().registerWorkspaceProvider(...)`, both of which require importing the package by name.
17
+
18
+ A `tsc --traceResolution` of a sibling consuming the package surfaced two compounding failures.
19
+
20
+ 1. `package.json` `exports["."]` pointed at `./src/service.ts`, which does not exist — the real module is `./src/service/service.ts`.
21
+ A latent bug, unnoticed because nothing in-repo imported the package by name.
22
+ 2. Once corrected, the public entry's internal alias imports cascade.
23
+ `service/service.ts` imports `type LifetimeUsage` and `type WorkspaceProvider` via the `#src/*` alias.
24
+ When a sibling's `tsc` follows the symlink, the consumer's own `paths` (`#src/*` → `./src/*`) intercept first and resolve into the *consumer's* `src/` — a global-`paths` collision, since both packages define `#src/*`.
25
+ The fallback to the publisher's `package.json` `imports` field also fails: `tsc` cannot resolve the extensionless `.ts` target under Node `imports` semantics ("Import specifier '#src/lifecycle/usage' does not exist in package.json scope").
26
+
27
+ The public entry's type closure is deeply entangled: `WorkspaceProvider` (in `lifecycle/workspace.ts`) reaches `AgentStatus` in the 510-line `lifecycle/agent.ts`, plus `SubagentType`/`AgentInvocation` from `types.ts` (which itself re-exports the `Agent` class).
28
+ A shallow alias-free entry is therefore not achievable without a substantial source restructure.
29
+
30
+ This collides with the ship-source model (ADR 0002): every package ships raw `.ts` executed directly by Pi, with no build step.
31
+
32
+ ## Decision
33
+
34
+ Emit a single, self-contained `dist/public.d.ts` for the public surface and advertise it through a `types` export condition, while the runtime entry continues to serve `.ts` source.
35
+
36
+ ```jsonc
37
+ "exports": {
38
+ ".": {
39
+ "types": "./dist/public.d.ts",
40
+ "default": "./src/service/service.ts"
41
+ }
42
+ }
43
+ ```
44
+
45
+ - `rollup-plugin-dts` rolls the declaration graph rooted at `src/service/service.ts` into one file, inlining the internal `#src/*` types and keeping peer-dependency types (`@earendil-works/*`, `@sinclair/typebox`) external.
46
+ We ship `.ts` source, so only the declaration bundle is emitted — no JS.
47
+ - The bundle is generated at `prepack` time and shipped via a `files` allowlist; it is gitignored and never committed.
48
+ - `default` → `./src/service/service.ts` fixes the stale path and serves runtime consumers; its `import type` lines erase, so no runtime `#src/*` resolution is needed.
49
+ - A `pnpm pack` → throwaway-consumer → `tsc` harness proves external consumability with no publish round-trip and no workspace privileges.
50
+
51
+ This is the repository's first build step.
52
+ It is deliberately narrow: it produces type declarations only and changes nothing about how Pi loads the extension from source (`pi.extensions: ["./src/index.ts"]` is untouched).
53
+
54
+ ## Alternatives considered
55
+
56
+ - Alias-free public entry (restructure the source so the entry's full type closure resolves via same-directory `./` imports).
57
+ Mechanically possible, but it requires moving the `AgentStatus`/`SubagentType`/`AgentInvocation`/`WorkspaceProvider` definitions and untangling the `agent.ts`/`types.ts` graph, with care that inner layers do not import the outer service layer.
58
+ `eslint`'s `no-parent-relative-imports` rule (which forbids `../`) narrows the options further.
59
+ Larger blast radius than emitting a `.d.ts`, and it churns the domain model to serve a packaging concern.
60
+ - A self-contained entry that re-declares the public types inline, guarded by a conformance test.
61
+ Avoids a build step but duplicates the seam/usage/status type definitions, which drift over time.
62
+
63
+ ## Consequences
64
+
65
+ - The repository now has a build step, but it is type-only and isolated to this package; the ship-source model is otherwise intact.
66
+ - Consumers (including `@gotgenes/pi-subagents-worktrees` in #263) consume the packaged public interface like any external developer — no `workspace:*` privileges.
67
+ - The `types` condition points at a build-time artifact; an in-repo workspace-linked consumer that imported the package would need `dist/public.d.ts` present.
68
+ This is acceptable because no in-repo package imports the surface yet; #263 consumes the built artifact from the published tarball.
69
+ - Sequencing: #270 must be published (its release-please PR merged) before #263 edits `pi-subagents` core, so #263's changes do not batch into the same `pi-subagents` release.
@@ -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.
@@ -42,3 +42,46 @@ All deterministic gates green: `check`, `lint`, `test`, and `fallow dead-code` (
42
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
43
  - Pre-completion reviewer: WARN — all blocking checks pass; one non-blocking doc finding (architecture.md overstated that `Workspace` is re-exported).
44
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.5.0",
3
+ "version": "11.6.0",
4
4
  "type": "module",
5
5
  "exports": {
6
- ".": "./src/service.ts"
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",