@gotgenes/pi-subagents 1.0.0 → 1.0.1

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.
Files changed (60) hide show
  1. package/AGENTS.md +4 -82
  2. package/CHANGELOG.md +21 -0
  3. package/README.md +18 -4
  4. package/docs/architecture/architecture.md +391 -0
  5. package/docs/decisions/0001-deferred-patches.md +8 -12
  6. package/package.json +12 -17
  7. package/.markdownlint-cli2.yaml +0 -19
  8. package/.release-please-manifest.json +0 -3
  9. package/dist/agent-manager.d.ts +0 -108
  10. package/dist/agent-manager.js +0 -390
  11. package/dist/agent-runner.d.ts +0 -93
  12. package/dist/agent-runner.js +0 -428
  13. package/dist/agent-types.d.ts +0 -48
  14. package/dist/agent-types.js +0 -136
  15. package/dist/context.d.ts +0 -12
  16. package/dist/context.js +0 -56
  17. package/dist/cross-extension-rpc.d.ts +0 -46
  18. package/dist/cross-extension-rpc.js +0 -54
  19. package/dist/custom-agents.d.ts +0 -14
  20. package/dist/custom-agents.js +0 -127
  21. package/dist/default-agents.d.ts +0 -7
  22. package/dist/default-agents.js +0 -119
  23. package/dist/env.d.ts +0 -6
  24. package/dist/env.js +0 -28
  25. package/dist/group-join.d.ts +0 -32
  26. package/dist/group-join.js +0 -116
  27. package/dist/index.d.ts +0 -13
  28. package/dist/index.js +0 -1731
  29. package/dist/invocation-config.d.ts +0 -22
  30. package/dist/invocation-config.js +0 -15
  31. package/dist/memory.d.ts +0 -49
  32. package/dist/memory.js +0 -151
  33. package/dist/model-resolver.d.ts +0 -19
  34. package/dist/model-resolver.js +0 -62
  35. package/dist/output-file.d.ts +0 -24
  36. package/dist/output-file.js +0 -86
  37. package/dist/prompts.d.ts +0 -29
  38. package/dist/prompts.js +0 -72
  39. package/dist/schedule-store.d.ts +0 -36
  40. package/dist/schedule-store.js +0 -144
  41. package/dist/schedule.d.ts +0 -109
  42. package/dist/schedule.js +0 -338
  43. package/dist/settings.d.ts +0 -66
  44. package/dist/settings.js +0 -130
  45. package/dist/skill-loader.d.ts +0 -24
  46. package/dist/skill-loader.js +0 -93
  47. package/dist/types.d.ts +0 -164
  48. package/dist/types.js +0 -5
  49. package/dist/ui/agent-widget.d.ts +0 -134
  50. package/dist/ui/agent-widget.js +0 -451
  51. package/dist/ui/conversation-viewer.d.ts +0 -35
  52. package/dist/ui/conversation-viewer.js +0 -252
  53. package/dist/ui/schedule-menu.d.ts +0 -16
  54. package/dist/ui/schedule-menu.js +0 -95
  55. package/dist/usage.d.ts +0 -50
  56. package/dist/usage.js +0 -49
  57. package/dist/worktree.d.ts +0 -36
  58. package/dist/worktree.js +0 -139
  59. package/prek.toml +0 -24
  60. package/release-please-config.json +0 -22
package/AGENTS.md CHANGED
@@ -1,85 +1,7 @@
1
1
  # AGENTS.md
2
2
 
3
- ## Project Purpose
3
+ ⚠️ It looks like the agent was started from a package subdirectory.
4
4
 
5
- This repository is a Pi extension that adds Claude Code-style autonomous subagent dispatch to the Pi coding agent.
6
-
7
- This package is a friendly fork of [`tintinweb/pi-subagents`](https://github.com/tintinweb/pi-subagents).
8
- It carries a small number of patches needed for downstream consumers (notably [RepOne](https://github.com/Tiny-IG-Software/repone)) that intend to use it as a normal Pi extension dependency:
9
-
10
- 1. **Peer-dep rename** — peer dependencies point at `@earendil-works/pi-*` (the active scope) rather than the deprecated `@mariozechner/pi-*` scope.
11
- 2. **Patch 2 (post-bind active-tool re-filter)** — `runAgent` re-runs the active-tool filter after `session.bindExtensions(...)` so extension-registered tools land in the child's active tool set. Without this, the `extensions: string[]` allowlist branch is functionally dead for extension tools.
12
- 3. **Patch 3 (active_agent tag)** — `runAgent` prepends `<active_agent name="${agentConfig.name}"/>` to every assembled child system prompt so `@gotgenes/pi-permission-system` can resolve per-agent `permission:` frontmatter inside the child.
13
-
14
- See `docs/decisions/0001-deferred-patches.md` for a fourth patch (mirror parent resource paths) that was scoped out, and the rationale for not opening upstream PRs yet.
15
-
16
- ## Workflow
17
-
18
- - Keep scope tight — this fork stays as close to upstream as possible.
19
- - Prefer small, reversible changes.
20
- - Preserve upstream behavior unless there is a clear reason to diverge.
21
- - When in doubt about whether a change should land here or be proposed upstream, prefer upstream.
22
-
23
- ## Implementation Priorities
24
-
25
- - Maintain compatibility with upstream's public API.
26
- - Keep the patch set minimal and clearly identified in the code (search for `Patch 2 (RepOne` / `Patch 3 (RepOne` comments).
27
- - Mirror sibling Pi-package conventions for tooling (pnpm, biome, vitest, prek, markdownlint-cli2, release-please).
28
- - Track the upstream `tintinweb/pi-subagents` repository for fixes and incorporate them as merges or cherry-picks.
29
-
30
- ## Code Style
31
-
32
- Use TypeScript. This project uses **pnpm** exclusively — never `npm` or `npx`.
33
-
34
- Formatting is handled by Biome (`biome check`, `biome format`). The repo intentionally does not use Prettier — a top-level `.prettierignore` blocks any harness with project-level write-time Prettier formatting from reformatting files here.
35
-
36
- ## Build, Test, Lint Commands
37
-
38
- ```bash
39
- pnpm install # Install dependencies + run prek install
40
- pnpm run build # tsc
41
- pnpm run typecheck # tsc --noEmit
42
- pnpm run lint # biome check src/ test/
43
- pnpm run lint:fix # biome check --fix src/ test/
44
- pnpm run lint:md # markdownlint-cli2 '*.md' 'docs/**/*.md'
45
- pnpm run lint:md:fix # markdownlint-cli2 --fix '*.md' 'docs/**/*.md'
46
- pnpm run lint:all # lint + lint:md
47
- pnpm run format # biome format --write src/ test/
48
- pnpm test # vitest run
49
- pnpm run test:watch # vitest
50
- pnpm run check # build + lint:all + test (full local CI)
51
- ```
52
-
53
- ## Markdown
54
-
55
- This project uses `markdownlint-cli2` with the config in `.markdownlint-cli2.yaml`.
56
- The `CHANGELOG.md` file is machine-generated by release-please and is excluded from linting.
57
-
58
- ## Testing
59
-
60
- The fork preserves upstream's full `vitest` suite (362 tests) plus tests added for Patches 2 and 3.
61
- All tests must pass before publishing.
62
- Use `vi.hoisted(...)` for module-level mocks, matching the existing patterns in `test/agent-runner.test.ts`.
63
-
64
- ## Releases
65
-
66
- This repo uses [release-please](https://github.com/googleapis/release-please) and npm trusted publishing via OIDC.
67
-
68
- - Conventional Commits drive the version bump (`feat:` → minor, `fix:` → patch, `BREAKING CHANGE:` → major).
69
- - Pushing to `main` triggers `release-please-action` which opens a release PR.
70
- - Merging the release PR creates a tag and triggers the publish job (no `NPM_TOKEN` — trusted publishing via OIDC).
71
-
72
- ## Commits
73
-
74
- Use Conventional Commits.
75
- Commit at meaningful checkpoints without waiting for an explicit reminder.
76
- Prefer small, reviewable commits that leave the repository in a valid state.
77
-
78
- ## Notes for Agents
79
-
80
- When working in this repo:
81
-
82
- 1. The two RepOne-specific patches are clearly marked in source — search for `// Patch 2 (RepOne` or `// Patch 3 (RepOne` to find them.
83
- 2. Do not introduce a third or fourth patch without first documenting the rationale in `docs/decisions/`.
84
- 3. Upstream PRs to `tintinweb/pi-subagents` for Patches 2 and 3 are deferred pending production validation in RepOne — see `docs/decisions/0001-deferred-patches.md`.
85
- 4. When syncing with upstream (rare), reapply the peer-dep rename and the two patches; the upstream `vitest` suite is the canary that nothing regressed.
5
+ Advise the user to launch Pi from the **repository root** (`pi-packages/`) instead.
6
+ The root provides `.pi/settings.json`, `.pi/prompts/`, and the full skill set.
7
+ Package-specific context for this package is available via the `package-pi-subagents` skill.
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
+ ## [1.0.1](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v1.0.0...pi-subagents-v1.0.1) (2026-05-17)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * restore per-package lint:md and lint scripts ([0e42617](https://github.com/gotgenes/pi-packages/commit/0e42617c443a7f8695f33855fa17058fc1712f27))
14
+ * use root markdownlint config from all packages ([30192f8](https://github.com/gotgenes/pi-packages/commit/30192f8ccfc5c3c420f9f9b602df174baf263e92))
15
+
16
+
17
+ ### Documentation
18
+
19
+ * add redirect AGENTS.md to each package subdirectory ([cbdcd29](https://github.com/gotgenes/pi-packages/commit/cbdcd297194c814f545ae93eaa7418e9337450d3))
20
+
21
+
22
+ ### Miscellaneous Chores
23
+
24
+ * consolidate configs into monorepo root ([8583eaf](https://github.com/gotgenes/pi-packages/commit/8583eaf0764ac98def1987f20fafcc25e912b134))
25
+ * remove per-package pi-autoformat configs ([b2d405a](https://github.com/gotgenes/pi-packages/commit/b2d405a0a278341e4f6ff1c8b607533eaa4f021a))
26
+ * replace markdownlint-cli2 with rumdl ([d8dc789](https://github.com/gotgenes/pi-packages/commit/d8dc7897d854bf11396b85bc8c365e8e2ed7e66c))
27
+ * update package.json URLs to monorepo ([b92dbfa](https://github.com/gotgenes/pi-packages/commit/b92dbfaeaeb6cf2823272cb6fb6f206fb99a5009))
28
+
8
29
  ## [1.0.0](https://github.com/gotgenes/pi-subagents/compare/v0.7.2...v1.0.0) (2026-05-12)
9
30
 
10
31
 
package/README.md CHANGED
@@ -1,10 +1,14 @@
1
- # @tintinweb/pi-subagents
1
+ # @gotgenes/pi-subagents
2
2
 
3
3
  A [pi](https://pi.dev) extension that brings **Claude Code-style autonomous sub-agents** to pi. Spawn specialized agents that run in isolated sessions — each with its own tools, system prompt, model, and thinking level. Run them in foreground or background, steer them mid-run, resume completed sessions, and define your own custom agent types.
4
4
 
5
+ > **Fork notice:** This package is a friendly fork of [`tintinweb/pi-subagents`](https://github.com/tintinweb/pi-subagents), published to npm as `@gotgenes/pi-subagents`.
6
+ > It carries a small number of patches on top of upstream — peer-dep migration to `@earendil-works/pi-*`, a post-`bindExtensions` active-tool re-filter, and an `<active_agent>` system-prompt tag for permission resolution.
7
+ > See [Deviations from upstream](#deviations-from-upstream) at the bottom of this README for details.
8
+
5
9
  > **Status:** Early release.
6
10
 
7
- <img width="600" alt="pi-subagents screenshot" src="https://github.com/tintinweb/pi-subagents/raw/master/media/screenshot.png" />
11
+ <img width="600" alt="pi-subagents screenshot" src="https://github.com/gotgenes/pi-subagents/raw/main/media/screenshot.png" />
8
12
 
9
13
 
10
14
  https://github.com/user-attachments/assets/8685261b-9338-4fea-8dfe-1c590d5df543
@@ -35,7 +39,7 @@ https://github.com/user-attachments/assets/8685261b-9338-4fea-8dfe-1c590d5df543
35
39
  ## Install
36
40
 
37
41
  ```bash
38
- pi install npm:@tintinweb/pi-subagents
42
+ pi install npm:@gotgenes/pi-subagents
39
43
  ```
40
44
 
41
45
  Or load directly for development:
@@ -523,6 +527,16 @@ src/
523
527
  conversation-viewer.ts # Live conversation overlay for viewing agent sessions
524
528
  ```
525
529
 
530
+ ## Deviations from upstream
531
+
532
+ This fork carries three divergences from [`tintinweb/pi-subagents`](https://github.com/tintinweb/pi-subagents). Each has a corresponding upstream PR:
533
+
534
+ 1. **Peer-dep migration to `@earendil-works/pi-*`** — `peerDependencies` and all imports point at `@earendil-works/pi-ai`, `@earendil-works/pi-coding-agent`, and `@earendil-works/pi-tui` (the active scope on npm) instead of the deprecated `@mariozechner/pi-*` scope. Also fixes a latent bug where `ThinkingLevel` was imported from `pi-agent-core` (an undeclared transitive dep that breaks under pnpm). Upstream PR: [tintinweb/pi-subagents#71](https://github.com/tintinweb/pi-subagents/pull/71).
535
+ 2. **Post-`bindExtensions` active-tool re-filter** (`src/agent-runner.ts`) — `runAgent` re-runs its active-tool filter after `session.bindExtensions(...)` so extension-registered tools join the child's active tool set. Without this, the `extensions: string[]` allowlist branch was functionally dead for extension tools, and `extensions: true` with a `disallowedTools` denylist let denylisted extension tools slip through. Upstream PR: [tintinweb/pi-subagents#72](https://github.com/tintinweb/pi-subagents/pull/72).
536
+ 3. **`<active_agent>` system-prompt tag** (`src/prompts.ts`) — `buildAgentPrompt` prepends `<active_agent name="${config.name}"/>` to every assembled child system prompt (both `replace` and `append` modes). Downstream extensions like [`@gotgenes/pi-permission-system`](https://github.com/gotgenes/pi-permission-system) parse this tag to resolve per-agent `permission:` frontmatter inside the child session. Upstream PR: [tintinweb/pi-subagents#73](https://github.com/tintinweb/pi-subagents/pull/73).
537
+
538
+ The upstream `vitest` suite plus tests added for each patch all pass on every commit.
539
+
526
540
  ## License
527
541
 
528
- MIT — [tintinweb](https://github.com/tintinweb)
542
+ MIT — [tintinweb](https://github.com/tintinweb) (upstream) and [Chris Lasher](https://github.com/gotgenes) (fork)
@@ -0,0 +1,391 @@
1
+ # Architecture
2
+
3
+ This document describes the planned decomposition of the pi-subagents fork
4
+ into a focused, composable core with a stable API boundary that other
5
+ extensions can build on.
6
+
7
+ ## Design principles
8
+
9
+ 1. **Narrow core** — the extension owns agent spawning, execution, and result
10
+ retrieval. Everything else is a consumer.
11
+ 2. **Composable by default** — other extensions can spawn agents, observe
12
+ their lifecycle, and display their state without importing this package
13
+ directly.
14
+ 3. **Typed API boundary** — this package exports a `SubagentsAPI` interface
15
+ and `Symbol.for()` accessors (`publishSubagentsAPI` /
16
+ `getSubagentsAPI`). Consumers declare this package as an optional peer
17
+ dependency and use dynamic import for compile-time types. The runtime
18
+ bridge is `Symbol.for()` on `globalThis` — no separate API package.
19
+ 4. **No scheduling** — in-process scheduling is removed from the core.
20
+ Scheduling is a separate concern that any extension can implement by
21
+ calling `spawn()` on the published API.
22
+ 5. **UI extraction is deferred** — the widget, conversation viewer, and
23
+ `/agents` command menu stay in the core for now. They are the first
24
+ candidate for extraction once the API boundary is proven stable.
25
+
26
+ ## Current state
27
+
28
+ The extension is a 6,300 LOC monolith organized into well-factored internal
29
+ modules but with no public API contract. The subsystems are:
30
+
31
+ ```text
32
+ index.ts (1,894 LOC) — entry point, tool registration, event wiring
33
+ agent-manager.ts — lifecycle, concurrency, queue
34
+ agent-runner.ts — session creation, turn loop, tool filtering
35
+ agent-types.ts — type registry (defaults + custom .md files)
36
+ types.ts — shared type definitions
37
+
38
+ prompts.ts — system prompt assembly
39
+ context.ts — parent conversation extraction
40
+ memory.ts — persistent MEMORY.md per agent
41
+ skill-loader.ts — preload .pi/skills into prompts
42
+ env.ts — git/platform detection
43
+
44
+ worktree.ts — git worktree isolation
45
+ usage.ts — token usage tracking
46
+ model-resolver.ts — fuzzy model name resolution
47
+ invocation-config.ts — merge tool params with agent config
48
+ output-file.ts — JSONL transcript streaming
49
+ settings.ts — persistent operational settings
50
+
51
+ schedule.ts — cron/interval/one-shot job dispatch ← removing
52
+ schedule-store.ts — file-backed schedule persistence ← removing
53
+ cross-extension-rpc.ts — RPC over pi.events ← replacing
54
+ group-join.ts — batch completion notifications
55
+
56
+ ui/agent-widget.ts — above-editor live status widget
57
+ ui/conversation-viewer.ts — scrollable session overlay
58
+ ui/schedule-menu.ts — /agents schedule submenu ← removing
59
+ ```
60
+
61
+ ### Coupling today
62
+
63
+ The widget reads agent state by holding a direct reference to
64
+ `AgentManager` and polling a shared mutable `Map<string, AgentActivity>`
65
+ every 80 ms. The conversation viewer subscribes directly to `AgentSession`
66
+ objects. The scheduler holds a direct `AgentManager` reference and calls
67
+ `manager.spawn()`.
68
+
69
+ Cross-extension consumers use an ad-hoc RPC layer over `pi.events`
70
+ (`subagents:rpc:spawn`, `subagents:rpc:stop`, `subagents:rpc:ping`) with
71
+ per-request reply channels and untyped envelopes.
72
+
73
+ There is also a `Symbol.for("pi-subagents:manager")` export on
74
+ `globalThis` that exposes `{ waitForAll, hasRunning, spawn, getRecord }`,
75
+ but it is undocumented and untyped.
76
+
77
+ ## Target state
78
+
79
+ ```text
80
+ ┌────────────────────────────────────────────────────────┐
81
+ │ @earendil-works/pi-subagents (this package) │
82
+ │ │
83
+ │ Exports: │
84
+ │ SubagentsAPI interface │
85
+ │ publishSubagentsAPI() / getSubagentsAPI() │
86
+ │ SubagentRecord, SubagentStatus, LifetimeUsage types │
87
+ │ Event channel constants │
88
+ │ │
89
+ │ Core: │
90
+ │ Agent + get_subagent_result + steer_subagent tools │
91
+ │ AgentManager, agent-runner, agent-types │
92
+ │ publishSubagentsAPI(impl) ← called at init │
93
+ │ │
94
+ │ Internal UI (widget, viewer, /agents menu) │
95
+ │ ← moves to pi-subagents-ui later │
96
+ └──────────────────────┬─────────────────────────────────┘
97
+ │ Symbol.for("pi:service:subagents")
98
+
99
+ ┌─────────────────┼──────────────────┐
100
+ │ │ │
101
+ ▼ ▼ ▼
102
+ ┌─────────┐ ┌──────────────┐ ┌──────────────┐
103
+ │ pi- │ │ pi-subagents │ │ any future │
104
+ │ schedule│ │ -ui │ │ extension │
105
+ │ (other │ │ (deferred) │ │ │
106
+ │ ext) │ └──────────────┘ └──────────────┘
107
+ └─────────┘
108
+
109
+ │ getSubagentsAPI()?.spawn(...)
110
+ │ (optional peer dep + dynamic import for types)
111
+
112
+ ```
113
+
114
+ ### What the core owns
115
+
116
+ - The three tools: `Agent`, `get_subagent_result`, `steer_subagent`.
117
+ - `AgentManager` — spawn, queue, abort, resume, concurrency control.
118
+ - `agent-runner` — session creation, turn loop, tool filtering, extension
119
+ binding (Patches 2 and 3).
120
+ - Agent type registry — default agents, custom `.md` file loading.
121
+ - Prompt assembly, context extraction, memory, skills, environment.
122
+ - Worktree isolation.
123
+ - Token usage tracking.
124
+ - Settings persistence.
125
+ - Internal UI (widget, conversation viewer, `/agents` menu) — these stay
126
+ until the API boundary is proven, then move to a separate extension.
127
+
128
+ ### What the core drops
129
+
130
+ - **Scheduling** (`schedule.ts`, `schedule-store.ts`,
131
+ `ui/schedule-menu.ts`) — 612 LOC removed. The `schedule` parameter is
132
+ removed from the `Agent` tool schema. Any extension that wants scheduling
133
+ can implement it by calling `getSubagentsAPI()?.spawn(...)` on a timer.
134
+ - **Ad-hoc RPC** (`cross-extension-rpc.ts`) — replaced by the typed
135
+ `SubagentsAPI` published via `Symbol.for()`. The untyped event-bus RPC
136
+ channels are removed.
137
+ - **Group join** (`group-join.ts`) — 141 LOC removed. The grouped
138
+ notification batching adds complexity for a marginal UX improvement.
139
+ Individual completion notifications are sufficient.
140
+ - **Output file** (`output-file.ts`) — 96 LOC removed. JSONL transcript
141
+ streaming is a consumer concern; a separate extension can subscribe to
142
+ lifecycle events and write transcripts.
143
+
144
+ ### Estimated impact
145
+
146
+ | Subsystem removed | LOC removed | LOC removed from index.ts |
147
+ | ----------------- | ----------- | ------------------------- |
148
+ | Scheduling | 612 | ~200 |
149
+ | Ad-hoc RPC | 80 | ~50 |
150
+ | Group join | 141 | ~100 |
151
+ | Output file | 83 | ~50 |
152
+ | **Total** | **~916** | **~400** |
153
+
154
+ After removal and `index.ts` decomposition, the core shrinks from ~6,300
155
+ to ~5,400 LOC, and `index.ts` shrinks from ~1,894 to ~1,300 LOC.
156
+
157
+ ## SubagentsAPI
158
+
159
+ The `SubagentsAPI` interface, accessor functions, and serializable types
160
+ are exported directly from this package (`@earendil-works/pi-subagents`).
161
+ No separate API package is needed.
162
+
163
+ Consumers declare this package as an optional peer dependency:
164
+
165
+ ```json
166
+ {
167
+ "peerDependencies": {
168
+ "@earendil-works/pi-subagents": ">=2.0.0"
169
+ },
170
+ "peerDependenciesMeta": {
171
+ "@earendil-works/pi-subagents": { "optional": true }
172
+ }
173
+ }
174
+ ```
175
+
176
+ At runtime, consumers use dynamic import for type-safe access to the
177
+ accessor functions:
178
+
179
+ ```typescript
180
+ const { getSubagentsAPI } = await import("@earendil-works/pi-subagents");
181
+ const api = getSubagentsAPI();
182
+ if (api) {
183
+ api.spawn("Explore", "Check for stale TODOs");
184
+ }
185
+ ```
186
+
187
+ Pi's extension loader creates a fresh `jiti` instance per extension with
188
+ `moduleCache: false`, so module-scoped singletons don't survive across
189
+ extensions. The accessor functions use `Symbol.for()` on `globalThis`,
190
+ which is process-global by spec, to bridge this gap. The dynamic import
191
+ provides compile-time types; the `Symbol.for()` key is the actual
192
+ runtime channel.
193
+
194
+ ### Interface
195
+
196
+ ```typescript
197
+ /** The public API surface published by pi-subagents. */
198
+ export interface SubagentsAPI {
199
+ /**
200
+ * Spawn an agent. Returns the agent ID immediately.
201
+ * The agent runs in the background unless options.foreground is true.
202
+ */
203
+ spawn(type: string, prompt: string, options?: SpawnOptions): string;
204
+
205
+ /** Get a snapshot of an agent's current state. */
206
+ getRecord(id: string): SubagentRecord | undefined;
207
+
208
+ /** List all tracked agents, most recent first. */
209
+ listAgents(): SubagentRecord[];
210
+
211
+ /** Abort a running or queued agent. Returns false if not found. */
212
+ abort(id: string): boolean;
213
+
214
+ /** Send a steering message to a running agent. */
215
+ steer(id: string, message: string): Promise<boolean>;
216
+
217
+ /** Wait for all running and queued agents to complete. */
218
+ waitForAll(): Promise<void>;
219
+
220
+ /** Whether any agents are running or queued. */
221
+ hasRunning(): boolean;
222
+ }
223
+
224
+ export interface SpawnOptions {
225
+ description?: string;
226
+ model?: string;
227
+ maxTurns?: number;
228
+ thinkingLevel?: string;
229
+ isolated?: boolean;
230
+ inheritContext?: boolean;
231
+ foreground?: boolean;
232
+ /** Skip the concurrency queue — start immediately. */
233
+ bypassQueue?: boolean;
234
+ isolation?: "worktree";
235
+ }
236
+ ```
237
+
238
+ ### Accessor pattern
239
+
240
+ ```typescript
241
+ const KEY = Symbol.for("pi:service:subagents");
242
+
243
+ export function publishSubagentsAPI(api: SubagentsAPI): void {
244
+ (globalThis as any)[KEY] = api;
245
+ }
246
+
247
+ export function getSubagentsAPI(): SubagentsAPI | undefined {
248
+ return (globalThis as any)[KEY];
249
+ }
250
+ ```
251
+
252
+ If Pi gains a native service registry ([earendil-works/pi#4207]), these
253
+ accessors can be updated to delegate to `pi.registerService()` /
254
+ `pi.getService()` internally while keeping the same consumer API.
255
+
256
+ ### Lifecycle events
257
+
258
+ The core emits events on `pi.events` that any extension can observe:
259
+
260
+ | Channel | Payload | When |
261
+ | --------------------- | ------------------------------------------- | -------------------- |
262
+ | `subagents:started` | `{ id, type, description }` | Agent begins running |
263
+ | `subagents:completed` | `{ id, type, status, result?, error? }` | Agent finishes |
264
+ | `subagents:activity` | `{ id, toolName?, textDelta?, turnCount? }` | Streaming progress |
265
+
266
+ These replace the ad-hoc RPC channels. They are fire-and-forget broadcast
267
+ events — no request IDs, no reply channels.
268
+
269
+ ### Consumer example: scheduling extension
270
+
271
+ ```typescript
272
+ // package.json:
273
+ // "peerDependencies": { "@earendil-works/pi-subagents": ">=2.0.0" }
274
+ // "peerDependenciesMeta": { "@earendil-works/pi-subagents": { "optional": true } }
275
+
276
+ export default function (pi) {
277
+ pi.on("session_start", async (event, ctx) => {
278
+ let getSubagentsAPI;
279
+ try {
280
+ ({ getSubagentsAPI } = await import("@earendil-works/pi-subagents"));
281
+ } catch {
282
+ return; // pi-subagents not installed
283
+ }
284
+ const api = getSubagentsAPI();
285
+ if (!api) return;
286
+
287
+ setInterval(() => {
288
+ api.spawn("Explore", "Check for stale TODOs", {
289
+ bypassQueue: true,
290
+ });
291
+ }, 60 * 60 * 1000);
292
+ });
293
+ }
294
+ ```
295
+
296
+ ### Consumer example: transcript extension
297
+
298
+ ```typescript
299
+ export default function (pi) {
300
+ pi.events.on("subagents:completed", async (data) => {
301
+ const { id } = data as { id: string };
302
+ let getSubagentsAPI;
303
+ try {
304
+ ({ getSubagentsAPI } = await import("@earendil-works/pi-subagents"));
305
+ } catch {
306
+ return;
307
+ }
308
+ const record = getSubagentsAPI()?.getRecord(id);
309
+ if (record?.result) {
310
+ fs.appendFileSync("agent-log.jsonl", JSON.stringify(record) + "\n");
311
+ }
312
+ });
313
+ }
314
+ ```
315
+
316
+ ## index.ts decomposition
317
+
318
+ The 1,894-line `index.ts` is decomposed into focused modules:
319
+
320
+ ```text
321
+ src/
322
+ ├── index.ts ← slimmed entry point: init, tool registration
323
+ ├── tools/
324
+ │ ├── agent-tool.ts ← Agent tool definition + execute
325
+ │ ├── result-tool.ts ← get_subagent_result tool
326
+ │ └── steer-tool.ts ← steer_subagent tool
327
+ ├── notifications.ts ← completion nudges, custom renderer
328
+ ├── activity-tracker.ts ← AgentActivity map + callback factory
329
+ ├── agents-command.ts ← /agents slash command menu
330
+ ├── api-adapter.ts ← SubagentsAPI implementation wrapping AgentManager
331
+ └── (existing modules unchanged)
332
+ ```
333
+
334
+ Each extracted module receives narrow constructor-injected dependencies
335
+ rather than closing over module-level state.
336
+
337
+ ## Phase plan
338
+
339
+ ### Phase 1: Export `SubagentsAPI` from this package
340
+
341
+ Add the `SubagentsAPI` interface, serializable types, and `Symbol.for()`
342
+ accessor functions as public exports of this package. No behavioral
343
+ changes to the core yet.
344
+
345
+ ### Phase 2: Remove scheduling
346
+
347
+ Delete `schedule.ts`, `schedule-store.ts`, `ui/schedule-menu.ts`. Remove
348
+ the `schedule` parameter from the `Agent` tool schema. Remove scheduler
349
+ setup and lifecycle hooks from `index.ts`.
350
+
351
+ ### Phase 3: Remove group-join, output-file, ad-hoc RPC
352
+
353
+ Delete `group-join.ts`, `output-file.ts`, `cross-extension-rpc.ts`.
354
+ Simplify `index.ts` to use direct individual notifications. Emit
355
+ lifecycle events on `pi.events` for external consumers.
356
+
357
+ ### Phase 4: Implement and publish `SubagentsAPI`
358
+
359
+ Wire `api-adapter.ts` to wrap `AgentManager` and call
360
+ `publishSubagentsAPI()` at extension init. Resolve model strings inside
361
+ the adapter (fixing upstream [tintinweb/pi-subagents#60]).
362
+
363
+ ### Phase 5: Decompose `index.ts`
364
+
365
+ Extract tools, notifications, activity tracking, and the `/agents` command
366
+ into separate modules per the decomposition above.
367
+
368
+ ### Phase 6 (future): Extract UI to `@earendil-works/pi-subagents-ui`
369
+
370
+ Move `ui/agent-widget.ts`, `ui/conversation-viewer.ts`, the `/agents`
371
+ command, notifications, and activity tracking to a separate extension that
372
+ consumes `SubagentsAPI` + lifecycle events. This phase is deferred until
373
+ the API boundary is proven stable in production.
374
+
375
+ ## Relationship with upstream
376
+
377
+ This fork ([earendil-works/pi-subagents]) is now a **hard fork** of
378
+ [tintinweb/pi-subagents]. The decomposition diverges materially from
379
+ upstream's direction.
380
+
381
+ The three upstream PRs (#71, #72, #73) remain open. If they land, upstream
382
+ gains the peer-dep fix and the two RepOne patches. This fork continues
383
+ independently regardless.
384
+
385
+ Upstream fixes and ideas are cherry-picked when they align with this
386
+ fork's scope. The upstream test suite is run periodically as a regression
387
+ canary for the agent-runner core.
388
+
389
+ [earendil-works/pi#4207]: https://github.com/earendil-works/pi/issues/4207
390
+ [earendil-works/pi-subagents]: https://github.com/earendil-works/pi-subagents
391
+ [tintinweb/pi-subagents]: https://github.com/tintinweb/pi-subagents
@@ -43,17 +43,15 @@ Neither matches the production need. For RepOne (and any consumer that installs
43
43
 
44
44
  We therefore defer Patch 1 rather than carry a speculative patch in the fork's diff against upstream. A follow-up issue on the RepOne board (linked from #443) captures the criterion for revisiting: **a workflow that needs `pi -e <path>` ephemeral extensions to reach children**.
45
45
 
46
- ### Upstream PRs are deferred
46
+ ### Upstream PRs are open
47
47
 
48
- Patches 2 and 3 are both clearly upstream-mergeable bug fixes they finish a mirror the upstream fork already started (carrying the parent's session configuration through to the child). The natural place for them is in `tintinweb/pi-subagents` itself.
48
+ All three divergences now have upstream PRs, opened after production validation in RepOne:
49
49
 
50
- We defer opening the PRs until **Patches 2 and 3 have been validated end-to-end in production** through at least one milestone of RepOne usage. The reasoning:
50
+ 1. **Peer-dep migration** [tintinweb/pi-subagents#71](https://github.com/tintinweb/pi-subagents/pull/71) (`fix(deps)!: migrate from deprecated @mariozechner/pi-* to @earendil-works/pi-*`)
51
+ 2. **Post-bind re-filter** — [tintinweb/pi-subagents#72](https://github.com/tintinweb/pi-subagents/pull/72) (`fix(agent-runner): re-filter active tools after bindExtensions so extension tools land in child`)
52
+ 3. **Active-agent tag** — [tintinweb/pi-subagents#73](https://github.com/tintinweb/pi-subagents/pull/73) (`feat(prompts): inject <active_agent name="..."/> tag for permission resolution`)
51
53
 
52
- 1. Production validation is stronger evidence for the upstream maintainer than the spike findings alone.
53
- 2. The patch shapes (especially Patch 2's helper-extraction refactor and Patch 3's exact prepend point) may need adjustment based on real-world behavior; opening PRs prematurely risks needing to amend them under review.
54
- 3. Carrying the fork is low-cost while we iterate; the publishing infrastructure mirrors the other Pi siblings.
55
-
56
- A follow-up issue on the RepOne board (linked from #443) tracks the upstream-PR work and the criterion for proceeding.
54
+ Once these land upstream, the fork's divergence reduces to package naming and tooling.
57
55
 
58
56
  ## Consequences
59
57
 
@@ -61,15 +59,13 @@ A follow-up issue on the RepOne board (linked from #443) tracks the upstream-PR
61
59
 
62
60
  - The fork's diff against upstream stays minimal — three patches plus tooling alignment.
63
61
  - We avoid landing a speculative Patch 1 that would need rework if upstream's `ExtensionContext` API changes.
64
- - We get production evidence before asking the upstream maintainer to review.
62
+ - Production evidence strengthened the upstream PRs.
65
63
 
66
64
  ### Negative
67
65
 
68
66
  - The `pi -e <path>` ephemeral-extension case in subagents will not work until Patch 1 lands. We accept this because no consumer in scope uses that pattern.
69
- - Patches 2 and 3 stay carried in `@gotgenes/pi-subagents` rather than upstream. Consumers must use this fork (not the upstream package) to get the patches.
70
67
 
71
68
  ### Operational
72
69
 
73
- - A follow-up issue on the RepOne board (linked from this fork's `README.md` "Deviations from upstream" section and from RepOne issue #443) records both deferrals.
74
- - When upstream PRs are eventually opened, they should be opened separately for Patches 2 and 3 to keep review simple.
70
+ - Upstream PRs are open and linked above. Once merged, the fork's behavioral divergence is eliminated.
75
71
  - When Patch 1 is eventually added, it should be a separate ADR in `docs/decisions/` with its own follow-up.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gotgenes/pi-subagents",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "A pi extension that brings Claude Code-style autonomous sub-agents to pi. Friendly fork of @tintinweb/pi-subagents.",
5
5
  "author": {
6
6
  "name": "Chris Lasher"
@@ -8,11 +8,12 @@
8
8
  "license": "MIT",
9
9
  "repository": {
10
10
  "type": "git",
11
- "url": "git+https://github.com/gotgenes/pi-subagents.git"
11
+ "url": "git+https://github.com/gotgenes/pi-packages.git",
12
+ "directory": "packages/pi-subagents"
12
13
  },
13
- "homepage": "https://github.com/gotgenes/pi-subagents#readme",
14
+ "homepage": "https://github.com/gotgenes/pi-packages/tree/main/packages/pi-subagents#readme",
14
15
  "bugs": {
15
- "url": "https://github.com/gotgenes/pi-subagents/issues"
16
+ "url": "https://github.com/gotgenes/pi-packages/issues"
16
17
  },
17
18
  "keywords": [
18
19
  "pi-package",
@@ -40,10 +41,10 @@
40
41
  },
41
42
  "devDependencies": {
42
43
  "@biomejs/biome": "^2.4.14",
43
- "@types/node": "^25.5.0",
44
- "markdownlint-cli2": "^0.22.1",
45
- "typescript": "^6.0.0",
46
- "vitest": "^4.0.18"
44
+ "@types/node": "^25.6.2",
45
+ "typescript": "^6.0.3",
46
+ "vitest": "^4.1.5",
47
+ "rumdl": "^0.1.93"
47
48
  },
48
49
  "pi": {
49
50
  "extensions": [
@@ -53,16 +54,10 @@
53
54
  "image": "https://github.com/gotgenes/pi-subagents/raw/main/media/screenshot.png"
54
55
  },
55
56
  "scripts": {
56
- "build": "tsc",
57
+ "check": "tsc --noEmit",
57
58
  "test": "vitest run",
58
59
  "test:watch": "vitest",
59
- "typecheck": "tsc --noEmit",
60
- "lint": "biome check src/ test/",
61
- "lint:fix": "biome check --fix src/ test/",
62
- "lint:md": "markdownlint-cli2 '*.md' 'docs/**/*.md'",
63
- "lint:md:fix": "markdownlint-cli2 --fix '*.md' 'docs/**/*.md'",
64
- "lint:all": "pnpm run lint && pnpm run lint:md",
65
- "format": "biome format --write src/ test/",
66
- "check": "pnpm run build && pnpm run lint:all && pnpm run test"
60
+ "lint:md": "rumdl check '*.md' 'docs/**/*.md'",
61
+ "lint": "biome check . && pnpm run lint:md"
67
62
  }
68
63
  }