@gotgenes/pi-subagents 8.0.0 → 9.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/README.md +2 -3
- package/docs/architecture/architecture.md +1 -1
- package/docs/plans/0238-remove-extensions-filtering.md +191 -0
- package/docs/retro/0237-remove-disallowed-tools.md +37 -0
- package/docs/retro/0238-remove-extensions-filtering.md +39 -0
- package/package.json +1 -1
- package/src/config/custom-agents.ts +17 -1
- package/src/lifecycle/agent-runner.ts +8 -12
- package/src/session/session-config.ts +2 -2
- package/src/types.ts +2 -2
- package/src/ui/agent-config-editor.ts +1 -3
- package/src/ui/agent-creation-wizard.ts +1 -1
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
|
+
## [9.0.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v8.0.0...pi-subagents-v9.0.0) (2026-05-27)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### ⚠ BREAKING CHANGES
|
|
12
|
+
|
|
13
|
+
* extensions field in agent frontmatter no longer supports a comma-separated allowlist. Values like `extensions: pi-github-tools, colgrep` are coerced to `true` (inherit all). Users who relied on per-extension filtering should migrate to `permission:` frontmatter in pi-permission-system.
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
* narrow extensions type from union to boolean ([90350ab](https://github.com/gotgenes/pi-packages/commit/90350abef259f5494990c5796a005f2edba2163d))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Documentation
|
|
21
|
+
|
|
22
|
+
* mark Phase 14 Step 2 complete in architecture ([fc8bc57](https://github.com/gotgenes/pi-packages/commit/fc8bc57788a3ade9ec2f74200973e5a811c48bb3))
|
|
23
|
+
* plan remove extensions filtering ([#238](https://github.com/gotgenes/pi-packages/issues/238)) ([1f4046a](https://github.com/gotgenes/pi-packages/commit/1f4046a84a308e80472425b5ca9e83263ecc310a))
|
|
24
|
+
* **retro:** add planning stage notes for issue [#238](https://github.com/gotgenes/pi-packages/issues/238) ([b2ce842](https://github.com/gotgenes/pi-packages/commit/b2ce842dcc999fd48f6aeb67c6d464750d6d87aa))
|
|
25
|
+
* **retro:** add retro notes for issue [#237](https://github.com/gotgenes/pi-packages/issues/237) ([6be215e](https://github.com/gotgenes/pi-packages/commit/6be215e6f4d9307f94b5bb61c024292a8eb77469))
|
|
26
|
+
* **retro:** add TDD stage notes for issue [#238](https://github.com/gotgenes/pi-packages/issues/238) ([87256db](https://github.com/gotgenes/pi-packages/commit/87256dbd97ba4a69267f2091896d959bef9ff9df))
|
|
27
|
+
* simplify extensions field to boolean in README ([b4028d7](https://github.com/gotgenes/pi-packages/commit/b4028d7c203fce792238acb147709ad3c3d4d28e))
|
|
28
|
+
|
|
8
29
|
## [8.0.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v7.8.1...pi-subagents-v8.0.0) (2026-05-27)
|
|
9
30
|
|
|
10
31
|
|
package/README.md
CHANGED
|
@@ -175,7 +175,7 @@ All fields are optional — sensible defaults for everything.
|
|
|
175
175
|
| `description` | filename | Agent description shown in tool listings |
|
|
176
176
|
| `display_name` | — | Display name for UI (e.g. widget, agent list) |
|
|
177
177
|
| `tools` | all 7 | Comma-separated built-in tools: read, bash, edit, write, grep, find, ls. `none` for no tools |
|
|
178
|
-
| `extensions` | `true` |
|
|
178
|
+
| `extensions` | `true` | `true` to inherit all MCP/extension tools, `false` to disable |
|
|
179
179
|
| `skills` | `true` | Inherit skills from parent. Can be a comma-separated list of skill names to preload (see [Skill Preloading](#skill-preloading) for discovery locations) |
|
|
180
180
|
| `memory` | — | Persistent agent memory scope: `project`, `local`, or `user`. Auto-detects read-only agents |
|
|
181
181
|
| `isolation` | — | Set to `worktree` to run in an isolated git worktree |
|
|
@@ -479,8 +479,7 @@ Each has a corresponding upstream PR:
|
|
|
479
479
|
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.
|
|
480
480
|
Also fixes a latent bug where `ThinkingLevel` was imported from `pi-agent-core` (an undeclared transitive dep that breaks under pnpm).
|
|
481
481
|
Upstream PR: [tintinweb/pi-subagents#71](https://github.com/tintinweb/pi-subagents/pull/71).
|
|
482
|
-
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
|
|
483
|
-
Without this, the `extensions: string[]` allowlist branch was functionally dead for extension tools.
|
|
482
|
+
2. **Post-`bindExtensions` active-tool re-filter** (`src/agent-runner.ts`) — `runAgent` re-runs its active-tool filter after `session.bindExtensions(...)` so the `EXCLUDED_TOOL_NAMES` recursion guard applies to extension-registered tools (which join the active set during `bindExtensions`).
|
|
484
483
|
Upstream PR: [tintinweb/pi-subagents#72](https://github.com/tintinweb/pi-subagents/pull/72).
|
|
485
484
|
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).
|
|
486
485
|
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.
|
|
@@ -697,7 +697,7 @@ Remove the `disallowedTools` field from `AgentConfig` and all code that processe
|
|
|
697
697
|
- Smell: A (responsibility overlap with pi-permission-system)
|
|
698
698
|
- Outcome: users migrate to `permission:` frontmatter for tool restrictions; single source of truth for access control
|
|
699
699
|
|
|
700
|
-
### Step 2: Remove `extensions` filtering — [#238]
|
|
700
|
+
### Step 2: Remove `extensions` filtering — [#238] ✅ Complete
|
|
701
701
|
|
|
702
702
|
Remove the `extensions: string[]` allowlist and simplify the field to a boolean.
|
|
703
703
|
The `extensions: false` case (used by `isolated`) is retained in this step and removed in Phase 16.
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 238
|
|
3
|
+
issue_title: "Remove extensions filtering from pi-subagents (Phase 14, Step 2)"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Remove `extensions` filtering from pi-subagents
|
|
7
|
+
|
|
8
|
+
## Problem Statement
|
|
9
|
+
|
|
10
|
+
The `extensions: string[]` allowlist in agent configuration is tool filtering disguised as extension lifecycle control.
|
|
11
|
+
Extensions always initialize via `bindExtensions()` — the allowlist only hides their tools from the active set afterward, which is pi-permission-system's responsibility.
|
|
12
|
+
Simplifying `extensions` from `true | string[] | false` to `boolean` removes this overlap and makes pi-permission-system the sole authority for tool access control.
|
|
13
|
+
|
|
14
|
+
## Goals
|
|
15
|
+
|
|
16
|
+
- Simplify `extensions` from `true | string[] | false` to `boolean` in `AgentConfig`, `ToolFilterConfig`, and all related code.
|
|
17
|
+
- Remove the `Array.isArray(extensions)` branch from `filterActiveTools` in `agent-runner.ts`.
|
|
18
|
+
- Remove extensions array handling from the agent config editor and creation wizard.
|
|
19
|
+
- Update custom agent frontmatter parsing to coerce array values to `true` (with a deprecation warning).
|
|
20
|
+
- Update tests to remove `extensions: string[]` assertions and fixture data.
|
|
21
|
+
- This is a **breaking change** — users with `extensions: <csv-list>` in agent frontmatter lose per-extension filtering.
|
|
22
|
+
|
|
23
|
+
## Non-Goals
|
|
24
|
+
|
|
25
|
+
- Collapsing `filterActiveTools` to a recursion guard (Step 3, #239).
|
|
26
|
+
- Removing `extensions: false` — it is retained here (used by `isolated`) and dissolved in Phase 16.
|
|
27
|
+
- Changing any pi-permission-system code — this issue is purely pi-subagents.
|
|
28
|
+
- Removing Patch 2 (post-bind re-filter) — it still serves the `EXCLUDED_TOOL_NAMES` recursion guard when `extensions === true`.
|
|
29
|
+
Issue #239 collapses the two-pass dance.
|
|
30
|
+
|
|
31
|
+
## Background
|
|
32
|
+
|
|
33
|
+
### Phase 14 context
|
|
34
|
+
|
|
35
|
+
This is Phase 14, Step 2 of the architecture roadmap in `docs/architecture/architecture.md`.
|
|
36
|
+
Steps 1 (#237, completed) and 2 (#238) are independent; Step 3 (#239) depends on both.
|
|
37
|
+
|
|
38
|
+
### Files involved
|
|
39
|
+
|
|
40
|
+
| File | Role |
|
|
41
|
+
| --------------------------------- | --------------------------------------------------------------------------- |
|
|
42
|
+
| `src/types.ts` | `AgentConfig.extensions` field type |
|
|
43
|
+
| `src/config/custom-agents.ts` | Parses `extensions` from YAML frontmatter via `inheritField()` |
|
|
44
|
+
| `src/session/session-config.ts` | `ToolFilterConfig.extensions` type + passthrough in `assembleSessionConfig` |
|
|
45
|
+
| `src/lifecycle/agent-runner.ts` | `filterActiveTools` array branch + Patch 2 comment |
|
|
46
|
+
| `src/ui/agent-config-editor.ts` | Serializes `extensions` array to frontmatter |
|
|
47
|
+
| `src/ui/agent-creation-wizard.ts` | Template text describing `extensions` field options |
|
|
48
|
+
| `README.md` | Frontmatter table row, Patch 2 notes |
|
|
49
|
+
|
|
50
|
+
### `filterActiveTools` after this change
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
function filterActiveTools(
|
|
54
|
+
activeTools: string[],
|
|
55
|
+
config: ToolFilterConfig,
|
|
56
|
+
): string[] {
|
|
57
|
+
const { toolNames, extensions } = config;
|
|
58
|
+
if (extensions === false) {
|
|
59
|
+
return activeTools;
|
|
60
|
+
}
|
|
61
|
+
const builtinToolNameSet = new Set(toolNames);
|
|
62
|
+
return activeTools.filter((t) => {
|
|
63
|
+
if (EXCLUDED_TOOL_NAMES.includes(t)) return false;
|
|
64
|
+
if (builtinToolNameSet.has(t)) return true;
|
|
65
|
+
return true;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The `Array.isArray(extensions)` branch is gone.
|
|
71
|
+
The `builtinToolNameSet.has(t)` check becomes redundant (always returns true for builtins, falls through to `return true` for non-builtins) — but simplifying further is #239's scope.
|
|
72
|
+
|
|
73
|
+
### Custom agent frontmatter migration
|
|
74
|
+
|
|
75
|
+
The `inheritField()` function currently parses CSV values into `string[]`.
|
|
76
|
+
After this change, it must still accept array values gracefully: coerce them to `true` and emit a warning so users know to update their frontmatter.
|
|
77
|
+
|
|
78
|
+
## Design Overview
|
|
79
|
+
|
|
80
|
+
This is primarily a removal — no new types, no new modules.
|
|
81
|
+
The one piece of new behavior is the deprecation warning when `inheritField` encounters an array value for `extensions`.
|
|
82
|
+
|
|
83
|
+
### `inheritField` change
|
|
84
|
+
|
|
85
|
+
Currently `inheritField` is shared between `extensions` and `skills` parsing.
|
|
86
|
+
Since `skills` still supports `string[]`, the function itself cannot change.
|
|
87
|
+
Instead, the call site for `extensions` will post-process: if `inheritField` returns `string[]`, coerce to `true` and log a warning.
|
|
88
|
+
|
|
89
|
+
### Type change scope
|
|
90
|
+
|
|
91
|
+
`AgentConfig.extensions` changes from `true | string[] | false` to `boolean`.
|
|
92
|
+
`ToolFilterConfig.extensions` changes from `boolean | string[]` to `boolean`.
|
|
93
|
+
Both are narrowing changes — existing `true` and `false` values remain valid.
|
|
94
|
+
|
|
95
|
+
## Module-Level Changes
|
|
96
|
+
|
|
97
|
+
1. **`src/types.ts`** — Change `extensions` type from `true | string[] | false` to `boolean`.
|
|
98
|
+
Update JSDoc comment to remove the `string[] = only listed` description.
|
|
99
|
+
2. **`src/config/custom-agents.ts`** — After calling `inheritField(fm.extensions ?? fm.inherit_extensions)`, coerce `string[]` results to `true` and emit a `debugLog` warning.
|
|
100
|
+
The warning informs users that `extensions: <csv-list>` is no longer supported and treated as `extensions: true`.
|
|
101
|
+
3. **`src/session/session-config.ts`** — Change `ToolFilterConfig.extensions` type from `boolean | string[]` to `boolean`.
|
|
102
|
+
Update the JSDoc comment.
|
|
103
|
+
The `assembleSessionConfig` passthrough `const extensions = options.isolated ? false : agentConfig.extensions;` works without changes since the type narrows.
|
|
104
|
+
4. **`src/lifecycle/agent-runner.ts`** — Remove the `Array.isArray(extensions)` branch (lines 47–49) from `filterActiveTools`.
|
|
105
|
+
Update the Patch 2 comment (line 366) that references `extensions: string[]`.
|
|
106
|
+
Update the comment at line 341 ("apply extension allowlist if specified").
|
|
107
|
+
5. **`src/ui/agent-config-editor.ts`** — Remove the `else if (Array.isArray(cfg.extensions))` branch (lines 51–52) from `buildEjectContent`.
|
|
108
|
+
6. **`src/ui/agent-creation-wizard.ts`** — Simplify the `extensions:` line in the template text from `<true (inherit all MCP/extension tools), false (none), or comma-separated names. Default: true>` to `<true (inherit all MCP/extension tools) or false (none). Default: true>`.
|
|
109
|
+
7. **`README.md`** — Simplify the `extensions` row in the frontmatter table to document boolean-only.
|
|
110
|
+
Update Patch 2 notes to remove `extensions: string[]` reference.
|
|
111
|
+
8. **`src/config/default-agents.ts`** — No changes needed; all defaults already use `extensions: true`.
|
|
112
|
+
|
|
113
|
+
### Test files
|
|
114
|
+
|
|
115
|
+
9. **`test/config/custom-agents.test.ts`** — Update the "handles extension allowlist" test: change expectation from `toEqual(["web-search", "mcp-server"])` to `toBe(true)` since CSV values now coerce to `true`.
|
|
116
|
+
Add a test verifying the deprecation warning is emitted.
|
|
117
|
+
10. **`test/session/session-config.test.ts`** — Update the "isolated:true forces extensions to false even for string[] extension list" test: since `AgentConfig.extensions` no longer supports `string[]`, simplify this test to use `extensions: true` and verify `isolated` still forces it to `false`.
|
|
118
|
+
The test name changes to reflect the boolean type.
|
|
119
|
+
11. **`test/lifecycle/agent-runner-extension-tools.test.ts`** — Remove the "post-bind re-filter respects extensions: string[] allowlist" test (line 139).
|
|
120
|
+
Change the `extensions` type annotation in the mutable config mock (line 26) from `boolean | string[]` to `boolean`.
|
|
121
|
+
Update the file-level JSDoc comment to remove `extensions: string[]` references.
|
|
122
|
+
12. **`test/ui/agent-config-editor.test.ts`** — Remove the "emits 'extensions: \<list\>' when extensions is an array" test.
|
|
123
|
+
13. **Test fixtures** — All test helper defaults already use `extensions: false` or `extensions: true`; no fixture changes needed beyond the tests listed above.
|
|
124
|
+
|
|
125
|
+
### Architecture docs
|
|
126
|
+
|
|
127
|
+
14. **`docs/architecture/architecture.md`** — Mark Step 2 as complete.
|
|
128
|
+
|
|
129
|
+
## Test Impact Analysis
|
|
130
|
+
|
|
131
|
+
1. **Tests removed** — 2 tests that directly assert `extensions: string[]` behavior:
|
|
132
|
+
- `agent-runner-extension-tools.test.ts`: "post-bind re-filter respects extensions: string[] allowlist"
|
|
133
|
+
- `agent-config-editor.test.ts`: "emits 'extensions: \<list\>' when extensions is an array"
|
|
134
|
+
|
|
135
|
+
2. **Tests updated** — 3 tests that reference `extensions: string[]` in fixtures or assertions:
|
|
136
|
+
- `custom-agents.test.ts`: "handles extension allowlist" → expectation changes to `true`
|
|
137
|
+
- `session-config.test.ts`: "isolated:true forces extensions to false even for string[] extension list" → uses `true` instead of array
|
|
138
|
+
- `agent-runner-extension-tools.test.ts`: mock config type annotation narrows
|
|
139
|
+
|
|
140
|
+
3. **Tests added** — 1 test:
|
|
141
|
+
- `custom-agents.test.ts`: verifies deprecation warning when array value is provided
|
|
142
|
+
|
|
143
|
+
4. **Tests unchanged** — All other `extensions: true` and `extensions: false` tests remain valid since the boolean cases are preserved.
|
|
144
|
+
|
|
145
|
+
## TDD Order
|
|
146
|
+
|
|
147
|
+
1. **Narrow `extensions` type in `AgentConfig` and `ToolFilterConfig`** — Change `extensions` from `true | string[] | false` to `boolean` in `types.ts`.
|
|
148
|
+
Change `ToolFilterConfig.extensions` from `boolean | string[]` to `boolean` in `session-config.ts`.
|
|
149
|
+
Update JSDoc comments.
|
|
150
|
+
Run `pnpm run check` to surface all downstream type errors.
|
|
151
|
+
Commit: `feat!: narrow extensions type from union to boolean`
|
|
152
|
+
|
|
153
|
+
2. **Remove array branch from `filterActiveTools`** — Remove the `Array.isArray(extensions)` branch.
|
|
154
|
+
Update the Patch 2 comment and the pre-bind filter comment.
|
|
155
|
+
Remove the "post-bind re-filter respects extensions: string[] allowlist" test.
|
|
156
|
+
Narrow the mock config type annotation in `agent-runner-extension-tools.test.ts`.
|
|
157
|
+
Update the file-level JSDoc.
|
|
158
|
+
Run tests: `pnpm vitest run`.
|
|
159
|
+
Commit: `refactor: remove extensions array branch from filterActiveTools`
|
|
160
|
+
|
|
161
|
+
3. **Coerce array values in custom agent frontmatter** — Update the `extensions` assignment in `custom-agents.ts` to coerce `string[]` results from `inheritField` to `true` with a deprecation warning.
|
|
162
|
+
Update the "handles extension allowlist" test expectation from array to `true`.
|
|
163
|
+
Add a test for the deprecation warning.
|
|
164
|
+
Update the "isolated:true forces extensions to false even for string[] extension list" test in `session-config.test.ts` to use `extensions: true`.
|
|
165
|
+
Run tests: `pnpm vitest run`.
|
|
166
|
+
Commit: `refactor: coerce extensions array values to true with deprecation warning`
|
|
167
|
+
|
|
168
|
+
4. **Remove extensions array from UI** — Remove the `Array.isArray(cfg.extensions)` branch from `buildEjectContent`.
|
|
169
|
+
Simplify the `extensions:` line in the creation wizard template.
|
|
170
|
+
Remove the "emits 'extensions: \<list\>'" test from `agent-config-editor.test.ts`.
|
|
171
|
+
Run tests: `pnpm vitest run`.
|
|
172
|
+
Commit: `refactor: remove extensions array from config editor and creation wizard`
|
|
173
|
+
|
|
174
|
+
5. **Update README** — Simplify the `extensions` frontmatter table row to document boolean-only.
|
|
175
|
+
Update Patch 2 notes to remove `extensions: string[]` reference.
|
|
176
|
+
Commit: `docs: simplify extensions field to boolean in README`
|
|
177
|
+
|
|
178
|
+
6. **Mark Phase 14 Step 2 complete** — Update `docs/architecture/architecture.md`.
|
|
179
|
+
Commit: `docs: mark Phase 14 Step 2 complete in architecture`
|
|
180
|
+
|
|
181
|
+
## Risks and Mitigations
|
|
182
|
+
|
|
183
|
+
| Risk | Mitigation |
|
|
184
|
+
| -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
185
|
+
| Users with `extensions: <csv-list>` silently lose per-extension filtering | `feat!:` commit triggers major version bump; frontmatter parser emits deprecation warning and coerces to `true`; README documents the change |
|
|
186
|
+
| `filterActiveTools` behavior regresses for boolean paths | Steps 1–4 each run type checks or tests; `extensions: true` and `extensions: false` tests are unchanged |
|
|
187
|
+
| Type annotation `boolean | string[]` in test mock causes tsc errors after step 1 | Step 2 narrows the annotation in the same commit that removes the array branch |
|
|
188
|
+
|
|
189
|
+
## Open Questions
|
|
190
|
+
|
|
191
|
+
None — the issue's proposed change and the architecture doc's step description are unambiguous.
|
|
@@ -39,3 +39,40 @@ All checks (type check, lint, fallow dead-code gate) pass clean.
|
|
|
39
39
|
- The `filterActiveTools` `extensions === false` branch simplified from "apply denylist to built-in tools" to `return activeTools`, exactly as the plan specified.
|
|
40
40
|
Both guard conditions at the call sites simplified to `cfg.toolFilter.extensions !== false`.
|
|
41
41
|
- No deviations from the plan's module-level changes list; all 7 source files and 4 test files were touched as specified.
|
|
42
|
+
|
|
43
|
+
## Stage: Final Retrospective (2026-05-27T01:12:34Z)
|
|
44
|
+
|
|
45
|
+
### Session summary
|
|
46
|
+
|
|
47
|
+
Three-stage lifecycle (planning → TDD → ship) completed in one session for Phase 14, Step 1.
|
|
48
|
+
All 6 TDD steps landed in 7 commits (6 planned + 1 fixup for dead `csvListOptional`).
|
|
49
|
+
Test count: 983 → 978 (−5).
|
|
50
|
+
Released as `pi-subagents-v8.0.0` (major bump from the `feat!:` breaking change).
|
|
51
|
+
|
|
52
|
+
### Observations
|
|
53
|
+
|
|
54
|
+
#### What went well
|
|
55
|
+
|
|
56
|
+
- The plan's type-dependency-chain ordering (AgentConfig → ToolFilterConfig → `filterActiveTools` → UI → docs) meant each step produced the exact downstream type errors expected for the next step, with zero surprises.
|
|
57
|
+
This ordering pattern is worth preserving for future removal issues.
|
|
58
|
+
- Incremental verification ran after every step: `pnpm run check` after type changes, `pnpm vitest run <file>` after logic changes, full suite + lint + fallow at the end.
|
|
59
|
+
No regressions discovered at the final sweep.
|
|
60
|
+
- The reformulation of one deleted test into "extensions: false skips the filter entirely" added coverage for the simplified code path rather than just deleting it.
|
|
61
|
+
Good judgment call during implementation.
|
|
62
|
+
|
|
63
|
+
#### What caused friction (agent side)
|
|
64
|
+
|
|
65
|
+
- `missing-context` — The plan said "remove `disallowedTools: csvListOptional(fm.disallowed_tools)`" from `custom-agents.ts` but did not check whether `csvListOptional` had other callers.
|
|
66
|
+
It was the sole call site, so the function became dead code.
|
|
67
|
+
Biome caught it at the final lint sweep, requiring a separate `refactor:` commit (`f1ee7c1`).
|
|
68
|
+
Impact: one unplanned commit; no rework but added friction.
|
|
69
|
+
Self-identified at final lint.
|
|
70
|
+
Root cause: the `plan-issue.md` template had a rule for grepping removed *exports* but not for checking private-function orphans.
|
|
71
|
+
|
|
72
|
+
#### What caused friction (user side)
|
|
73
|
+
|
|
74
|
+
- No friction observed — the user's involvement was limited to confirming the target package (pi-subagents vs pi-permission-system) during planning, which was an appropriate clarification given the incorrect `pkg:` label on the issue.
|
|
75
|
+
|
|
76
|
+
### Changes made
|
|
77
|
+
|
|
78
|
+
1. `.pi/prompts/plan-issue.md` — Added rule: when a step removes a call to a private function, grep the file for other callers and list the function for removal if the removed call was the sole call site.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 238
|
|
3
|
+
issue_title: "Remove extensions filtering from pi-subagents (Phase 14, Step 2)"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Retro: #238 — Remove extensions filtering from pi-subagents
|
|
7
|
+
|
|
8
|
+
## Stage: Planning (2026-05-27T01:31:01Z)
|
|
9
|
+
|
|
10
|
+
### Session summary
|
|
11
|
+
|
|
12
|
+
Produced a 6-step TDD plan for narrowing `extensions` from `true | string[] | false` to `boolean` across `AgentConfig`, `ToolFilterConfig`, `filterActiveTools`, custom agent frontmatter parsing, UI serialization, and tests.
|
|
13
|
+
The plan mirrors the structure of the completed #237 plan (Step 1 of Phase 14) and explicitly defers `filterActiveTools` collapse to #239.
|
|
14
|
+
|
|
15
|
+
### Observations
|
|
16
|
+
|
|
17
|
+
- The `pkg:pi-permission-system` label on this issue is intentional context (pi-permission-system becomes the sole tool-policy authority) but all code changes are in pi-subagents.
|
|
18
|
+
- The `inheritField()` helper is shared between `extensions` and `skills` parsing — it cannot be simplified since `skills` still supports `string[]`.
|
|
19
|
+
The plan coerces at the call site instead.
|
|
20
|
+
- After removing the `Array.isArray(extensions)` branch, the `builtinToolNameSet.has(t)` check in `filterActiveTools` becomes logically redundant (both branches return `true`), but simplifying further is #239's scope.
|
|
21
|
+
- Only 2 tests are removed and 3 updated; the boolean paths (`true`/`false`) are well-covered and unchanged.
|
|
22
|
+
|
|
23
|
+
## Stage: Implementation — TDD (2026-05-27T01:39:43Z)
|
|
24
|
+
|
|
25
|
+
### Session summary
|
|
26
|
+
|
|
27
|
+
All 6 TDD steps completed across 6 commits.
|
|
28
|
+
Test count went from 978 to 977 (net −1: 2 removed, 1 added for the deprecation warning).
|
|
29
|
+
Full suite: 62 files, 977 tests, all passing; type check and lint clean.
|
|
30
|
+
|
|
31
|
+
### Observations
|
|
32
|
+
|
|
33
|
+
- **Plan deviation — step consolidation**: The type narrowing in step 1 caused immediate `tsc` errors in test files that the plan assigned to steps 2–4 (the `string[]`-specific tests in `agent-runner-extension-tools.test.ts`, `session-config.test.ts`, and `agent-config-editor.test.ts`).
|
|
34
|
+
All test fixes were folded into the step 1 commit to keep the build green at every commit.
|
|
35
|
+
Steps 2–4 became purely production-code changes.
|
|
36
|
+
- **Plan deviation — deprecation warning**: The plan said to use `debugLog` for the deprecation warning, but `debugLog` is debug-only (requires `PI_SUBAGENTS_DEBUG=1`) and its signature requires an error object as the second argument. `console.warn` was used instead, which is user-visible without special env vars and testable with `vi.spyOn`.
|
|
37
|
+
- **ESLint auto-fix in step 2**: ESLint rewrote `extensions === false` to `!extensions` and `cfg.toolFilter.extensions === false` to `!cfg.toolFilter.extensions` after the type narrowed to `boolean`.
|
|
38
|
+
Both are semantically identical; the changes were staged and included in the same commit.
|
|
39
|
+
- The `resolveBoolExtensions` helper in `custom-agents.ts` is a clean seam — if `skills` is ever also simplified to `boolean`, the same pattern applies.
|
package/package.json
CHANGED
|
@@ -58,7 +58,7 @@ function loadFromDir(dir: string, agents: Map<string, AgentConfig>, source: "pro
|
|
|
58
58
|
displayName: str(fm.display_name),
|
|
59
59
|
description: str(fm.description) ?? name,
|
|
60
60
|
builtinToolNames: csvList(fm.tools, BUILTIN_TOOL_NAMES),
|
|
61
|
-
extensions:
|
|
61
|
+
extensions: resolveBoolExtensions(fm.extensions ?? fm.inherit_extensions),
|
|
62
62
|
skills: inheritField(fm.skills ?? fm.inherit_skills),
|
|
63
63
|
model: str(fm.model),
|
|
64
64
|
thinking: str(fm.thinking) as ThinkingLevel | undefined,
|
|
@@ -109,6 +109,22 @@ function csvList(val: unknown, defaults: string[]): string[] {
|
|
|
109
109
|
return parseCsvField(val) ?? [];
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
/**
|
|
113
|
+
* Resolve the `extensions` field to a boolean.
|
|
114
|
+
* CSV/array values (legacy allowlist syntax) are coerced to `true` with a warning.
|
|
115
|
+
*/
|
|
116
|
+
function resolveBoolExtensions(val: unknown): boolean {
|
|
117
|
+
const result = inheritField(val);
|
|
118
|
+
if (Array.isArray(result)) {
|
|
119
|
+
console.warn(
|
|
120
|
+
"[pi-subagents] extensions allowlist syntax is deprecated — treating as \"true\" (inherit all).\n" +
|
|
121
|
+
"Use \"permission:\" frontmatter in pi-permission-system for per-tool access control.",
|
|
122
|
+
);
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
return result;
|
|
126
|
+
}
|
|
127
|
+
|
|
112
128
|
/**
|
|
113
129
|
* Parse an inherit field (extensions, skills).
|
|
114
130
|
* omitted/true → true (inherit all); false/"none"/empty → false; csv → listed names.
|
|
@@ -37,16 +37,13 @@ function filterActiveTools(
|
|
|
37
37
|
config: ToolFilterConfig,
|
|
38
38
|
): string[] {
|
|
39
39
|
const { toolNames, extensions } = config;
|
|
40
|
-
if (extensions
|
|
40
|
+
if (!extensions) {
|
|
41
41
|
return activeTools;
|
|
42
42
|
}
|
|
43
43
|
const builtinToolNameSet = new Set(toolNames);
|
|
44
44
|
return activeTools.filter((t) => {
|
|
45
45
|
if (EXCLUDED_TOOL_NAMES.includes(t)) return false;
|
|
46
46
|
if (builtinToolNameSet.has(t)) return true;
|
|
47
|
-
if (Array.isArray(extensions)) {
|
|
48
|
-
return extensions.some((ext) => t.startsWith(ext) || t.includes(ext));
|
|
49
|
-
}
|
|
50
47
|
return true;
|
|
51
48
|
});
|
|
52
49
|
}
|
|
@@ -299,7 +296,7 @@ export async function runAgent(
|
|
|
299
296
|
|
|
300
297
|
const agentDir = io.getAgentDir();
|
|
301
298
|
|
|
302
|
-
// Load extensions/skills: true
|
|
299
|
+
// Load extensions/skills: true → load; false → don't.
|
|
303
300
|
// Suppress AGENTS.md/CLAUDE.md and APPEND_SYSTEM.md - upstream's
|
|
304
301
|
// buildSystemPrompt() re-appends both AFTER systemPromptOverride, which
|
|
305
302
|
// would defeat prompt_mode: replace and isolated: true. Parent context, if
|
|
@@ -308,7 +305,7 @@ export async function runAgent(
|
|
|
308
305
|
const loader = io.createResourceLoader({
|
|
309
306
|
cwd: cfg.effectiveCwd,
|
|
310
307
|
agentDir,
|
|
311
|
-
noExtensions: cfg.toolFilter.extensions
|
|
308
|
+
noExtensions: !cfg.toolFilter.extensions,
|
|
312
309
|
noSkills: cfg.noSkills,
|
|
313
310
|
noPromptTemplates: true,
|
|
314
311
|
noThemes: true,
|
|
@@ -337,10 +334,9 @@ export async function runAgent(
|
|
|
337
334
|
thinkingLevel: cfg.thinkingLevel,
|
|
338
335
|
});
|
|
339
336
|
|
|
340
|
-
// Filter active tools: remove our own tools to prevent nesting
|
|
341
|
-
// apply extension allowlist if specified.
|
|
337
|
+
// Filter active tools: remove our own tools to prevent nesting.
|
|
342
338
|
// First pass - over built-in tools, before bindExtensions registers extension tools.
|
|
343
|
-
if (cfg.toolFilter.extensions
|
|
339
|
+
if (cfg.toolFilter.extensions) {
|
|
344
340
|
const filtered = filterActiveTools(session.getActiveToolNames(), cfg.toolFilter);
|
|
345
341
|
session.setActiveToolsByName(filtered);
|
|
346
342
|
}
|
|
@@ -363,9 +359,9 @@ export async function runAgent(
|
|
|
363
359
|
// Patch 2 (RepOne #443): re-filter active tools after bindExtensions.
|
|
364
360
|
// Extension-registered tools (added during bindExtensions) are not in the
|
|
365
361
|
// session's active set when the first filter pass runs above. Without this
|
|
366
|
-
// re-filter,
|
|
367
|
-
//
|
|
368
|
-
if (cfg.toolFilter.extensions
|
|
362
|
+
// re-filter, EXCLUDED_TOOL_NAMES would not be applied to extension-registered
|
|
363
|
+
// tools. Run the same filter against the post-bind active set.
|
|
364
|
+
if (cfg.toolFilter.extensions) {
|
|
369
365
|
const refiltered = filterActiveTools(session.getActiveToolNames(), cfg.toolFilter);
|
|
370
366
|
session.setActiveToolsByName(refiltered);
|
|
371
367
|
}
|
|
@@ -27,8 +27,8 @@ import type { AgentPromptConfig, SubagentType, ThinkingLevel } from "#src/types"
|
|
|
27
27
|
export interface ToolFilterConfig {
|
|
28
28
|
/** Built-in tool name allowlist for this agent type. */
|
|
29
29
|
toolNames: string[];
|
|
30
|
-
/** Resolved extensions setting:
|
|
31
|
-
extensions: boolean
|
|
30
|
+
/** Resolved extensions setting: true = inherit all, false = none. */
|
|
31
|
+
extensions: boolean;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/**
|
package/src/types.ts
CHANGED
|
@@ -42,8 +42,8 @@ export interface AgentPromptConfig {
|
|
|
42
42
|
/** Unified agent configuration — used for both default and user-defined agents. */
|
|
43
43
|
export interface AgentConfig extends AgentIdentity, AgentPromptConfig {
|
|
44
44
|
builtinToolNames?: string[];
|
|
45
|
-
/** true = inherit all
|
|
46
|
-
extensions:
|
|
45
|
+
/** true = inherit all extensions, false = none */
|
|
46
|
+
extensions: boolean;
|
|
47
47
|
/** true = inherit all, string[] = only listed, false = none */
|
|
48
48
|
skills: true | string[] | false;
|
|
49
49
|
model?: string;
|
|
@@ -47,9 +47,7 @@ export function buildEjectContent(cfg: AgentConfig): string {
|
|
|
47
47
|
if (cfg.thinking) fmFields.push(`thinking: ${cfg.thinking}`);
|
|
48
48
|
if (cfg.maxTurns) fmFields.push(`max_turns: ${cfg.maxTurns}`);
|
|
49
49
|
fmFields.push(`prompt_mode: ${cfg.promptMode}`);
|
|
50
|
-
if (cfg.extensions
|
|
51
|
-
else if (Array.isArray(cfg.extensions))
|
|
52
|
-
fmFields.push(`extensions: ${cfg.extensions.join(", ")}`);
|
|
50
|
+
if (!cfg.extensions) fmFields.push("extensions: false");
|
|
53
51
|
if (cfg.skills === false) fmFields.push("skills: false");
|
|
54
52
|
else if (Array.isArray(cfg.skills))
|
|
55
53
|
fmFields.push(`skills: ${cfg.skills.join(", ")}`);
|
|
@@ -104,7 +104,7 @@ model: <optional model as "provider/modelId", e.g. "anthropic/claude-haiku-4-5-2
|
|
|
104
104
|
thinking: <optional thinking level: off, minimal, low, medium, high, xhigh. Omit to inherit>
|
|
105
105
|
max_turns: <optional max agentic turns. 0 or omit for unlimited (default)>
|
|
106
106
|
prompt_mode: <"replace" (body IS the full system prompt) or "append" (body is appended to default prompt). Default: replace>
|
|
107
|
-
extensions: <true (inherit all MCP/extension tools)
|
|
107
|
+
extensions: <true (inherit all MCP/extension tools) or false (none). Default: true>
|
|
108
108
|
skills: <true (inherit all), false (none), or comma-separated skill names to preload into prompt. Default: true>
|
|
109
109
|
inherit_context: <true to fork parent conversation into agent so it sees chat history. Default: false>
|
|
110
110
|
run_in_background: <true to run in background by default. Default: false>
|