@gotgenes/pi-subagents 7.8.1 → 8.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 +25 -0
- package/README.md +10 -13
- package/docs/architecture/architecture.md +179 -133
- package/docs/architecture/history/phase-13-remaining-smells.md +88 -0
- package/docs/plans/0237-remove-disallowed-tools.md +180 -0
- package/docs/retro/0219-reduce-test-duplication-top-3-clone-families.md +28 -0
- package/docs/retro/0237-remove-disallowed-tools.md +41 -0
- package/package.json +1 -1
- package/src/config/custom-agents.ts +0 -9
- package/src/lifecycle/agent-runner.ts +7 -11
- package/src/session/session-config.ts +3 -10
- package/src/types.ts +0 -2
- package/src/ui/agent-config-editor.ts +0 -2
- package/src/ui/agent-creation-wizard.ts +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,31 @@ 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
|
+
## [8.0.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v7.8.1...pi-subagents-v8.0.0) (2026-05-27)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### ⚠ BREAKING CHANGES
|
|
12
|
+
|
|
13
|
+
* The `disallowed_tools` frontmatter field is no longer supported. Users should migrate to pi-permission-system's `permission:`
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
* remove disallowedTools from AgentConfig ([6044552](https://github.com/gotgenes/pi-packages/commit/6044552db68180bf8a151c0f64c13f264f3dacb9))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Documentation
|
|
21
|
+
|
|
22
|
+
* add Phase 14 issue links, anemic-model and lifecycle-object smells to discovery skill ([9831176](https://github.com/gotgenes/pi-packages/commit/983117652937dce08e61a6cf7624e35ccf8a0dcb))
|
|
23
|
+
* mark Phase 14 Step 1 complete in architecture ([d24e3dd](https://github.com/gotgenes/pi-packages/commit/d24e3dd5c664e63a372f399f9e325f1bdf877022))
|
|
24
|
+
* **pi-subagents:** add Phase 14 issue links ([#237](https://github.com/gotgenes/pi-packages/issues/237), [#238](https://github.com/gotgenes/pi-packages/issues/238), [#239](https://github.com/gotgenes/pi-packages/issues/239)) ([b152a75](https://github.com/gotgenes/pi-packages/commit/b152a75dcd6596468c5c0aeca1b36ac33216dfa8))
|
|
25
|
+
* **pi-subagents:** add target architecture vision and renumber phases 14-17 ([f7e5315](https://github.com/gotgenes/pi-packages/commit/f7e5315bbfb088d7a40637deead1811691dec1bd))
|
|
26
|
+
* **pi-subagents:** archive Phase 13, update metrics for Phase 14 ([9d473db](https://github.com/gotgenes/pi-packages/commit/9d473db1b0e420fa52ce9d09bea7afff75331e58))
|
|
27
|
+
* plan removal of disallowed_tools ([#237](https://github.com/gotgenes/pi-packages/issues/237)) ([9cb600c](https://github.com/gotgenes/pi-packages/commit/9cb600cad9c8cc7b1081b021f039b0f36b9f9a44))
|
|
28
|
+
* remove disallowed_tools from README and add migration note ([9b3905f](https://github.com/gotgenes/pi-packages/commit/9b3905fbaa81db47633c0c8edf21ed3b766d5507))
|
|
29
|
+
* **retro:** add planning stage notes for issue [#237](https://github.com/gotgenes/pi-packages/issues/237) ([85460dd](https://github.com/gotgenes/pi-packages/commit/85460dd72949d7da83eaf9ed2196ff5c41d92f7f))
|
|
30
|
+
* **retro:** add retro notes for issue [#219](https://github.com/gotgenes/pi-packages/issues/219) ([2d92af5](https://github.com/gotgenes/pi-packages/commit/2d92af577c0cfb71c575a5c334df2347840b4a8d))
|
|
31
|
+
* **retro:** add TDD stage notes for issue [#237](https://github.com/gotgenes/pi-packages/issues/237) ([f334538](https://github.com/gotgenes/pi-packages/commit/f3345383a76975f09c0ee689edcb5df286b76fc7))
|
|
32
|
+
|
|
8
33
|
## [7.8.1](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v7.8.0...pi-subagents-v7.8.1) (2026-05-26)
|
|
9
34
|
|
|
10
35
|
|
package/README.md
CHANGED
|
@@ -31,7 +31,6 @@ Run them in foreground or background, steer them mid-run, resume completed sessi
|
|
|
31
31
|
- **Persistent agent memory** — three scopes (project, local, user) with automatic read-only fallback for agents without write tools
|
|
32
32
|
- **Git worktree isolation** — run agents in isolated repo copies; changes auto-committed to branches on completion
|
|
33
33
|
- **Skill preloading** — inject named skills into agent system prompts, discovered from `.pi/skills/`, `.agents/skills/`, and global locations (Pi-standard `<name>/SKILL.md` directory layout supported)
|
|
34
|
-
- **Tool denylist** — block specific tools via `disallowed_tools` frontmatter
|
|
35
34
|
- **Styled completion notifications** — background agent results render as themed, compact notification boxes (icon, stats, result preview) instead of raw XML.
|
|
36
35
|
Expandable to show full output
|
|
37
36
|
- **Event bus** — lifecycle events (`subagents:created`, `started`, `completed`, `failed`, `steered`, `compacted`) emitted via `pi.events`, enabling other extensions to react to sub-agent activity
|
|
@@ -179,7 +178,6 @@ All fields are optional — sensible defaults for everything.
|
|
|
179
178
|
| `extensions` | `true` | Inherit MCP/extension tools. `false` to disable |
|
|
180
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) |
|
|
181
180
|
| `memory` | — | Persistent agent memory scope: `project`, `local`, or `user`. Auto-detects read-only agents |
|
|
182
|
-
| `disallowed_tools` | — | Comma-separated tools to deny even if extensions provide them |
|
|
183
181
|
| `isolation` | — | Set to `worktree` to run in an isolated git worktree |
|
|
184
182
|
| `model` | inherit parent | Model — `provider/modelId` or fuzzy name (`"haiku"`, `"sonnet"`) |
|
|
185
183
|
| `thinking` | inherit | off, minimal, low, medium, high, xhigh |
|
|
@@ -356,8 +354,6 @@ Agents with write tools get full read-write access.
|
|
|
356
354
|
**Read-only agents** (no `write`/`edit` tools) automatically get read-only memory — they can consume memories written by other agents but cannot modify them.
|
|
357
355
|
This prevents unintended tool escalation.
|
|
358
356
|
|
|
359
|
-
The `disallowed_tools` field is respected when determining write capability — an agent with `tools: write` + `disallowed_tools: write` correctly gets read-only memory.
|
|
360
|
-
|
|
361
357
|
## Worktree Isolation
|
|
362
358
|
|
|
363
359
|
Set `isolation: worktree` to run an agent in a temporary git worktree:
|
|
@@ -408,18 +404,19 @@ Traversal is byte-order sorted for deterministic resolution across filesystems.
|
|
|
408
404
|
**Security:** symlinks are rejected at every layer (root, flat file, skill directory, `SKILL.md` inside a skill directory) — intentional deviation from Pi, which follows symlinks.
|
|
409
405
|
Skill names with path-traversal characters (`..`, `/`, `\`, spaces, leading dot, >128 chars) are rejected.
|
|
410
406
|
|
|
411
|
-
##
|
|
407
|
+
## Migrating from `disallowed_tools`
|
|
412
408
|
|
|
413
|
-
|
|
409
|
+
The `disallowed_tools` frontmatter field has been removed.
|
|
410
|
+
Use [`@gotgenes/pi-permission-system`](https://github.com/gotgenes/pi-permission-system)'s `permission:` frontmatter instead — it provides richer semantics (allow/ask/deny vs. binary hide):
|
|
414
411
|
|
|
415
412
|
```yaml
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
disallowed_tools: write, edit
|
|
419
|
-
---
|
|
420
|
-
```
|
|
413
|
+
# Before (no longer supported)
|
|
414
|
+
disallowed_tools: bash
|
|
421
415
|
|
|
422
|
-
|
|
416
|
+
# After
|
|
417
|
+
permission:
|
|
418
|
+
bash: deny
|
|
419
|
+
```
|
|
423
420
|
|
|
424
421
|
## Permission System Integration
|
|
425
422
|
|
|
@@ -483,7 +480,7 @@ Each has a corresponding upstream PR:
|
|
|
483
480
|
Also fixes a latent bug where `ThinkingLevel` was imported from `pi-agent-core` (an undeclared transitive dep that breaks under pnpm).
|
|
484
481
|
Upstream PR: [tintinweb/pi-subagents#71](https://github.com/tintinweb/pi-subagents/pull/71).
|
|
485
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 child's active tool set.
|
|
486
|
-
Without this, the `extensions: string[]` allowlist branch was functionally dead for extension tools
|
|
483
|
+
Without this, the `extensions: string[]` allowlist branch was functionally dead for extension tools.
|
|
487
484
|
Upstream PR: [tintinweb/pi-subagents#72](https://github.com/tintinweb/pi-subagents/pull/72).
|
|
488
485
|
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).
|
|
489
486
|
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.
|
|
@@ -22,6 +22,9 @@ This document describes the architecture of the pi-subagents fork: a focused, co
|
|
|
22
22
|
No post-construction field writes from external code — if an object can't be instantiated ready-to-go, the prep work hasn't been done and the right dependencies haven't been identified.
|
|
23
23
|
9. **State owns its mutations** — mutable state lives in a class whose methods enforce valid transitions and invariants.
|
|
24
24
|
Free functions that mutate module-scoped variables, closure-captured bags-of-functions, and external writes to shared interfaces are replaced by classes that encapsulate the state they manage.
|
|
25
|
+
10. **Open for extension, closed for modification** — pi-subagents is a minimal core that publishes events and a service API.
|
|
26
|
+
Other packages (pi-permission-system, a future UI extension, hypothetical OTel integration) hook into these events to add permissions, rendering, or telemetry.
|
|
27
|
+
Pi-subagents has zero knowledge of its consumers — dependency arrows point inward, never outward.
|
|
25
28
|
|
|
26
29
|
## Domain model
|
|
27
30
|
|
|
@@ -222,7 +225,7 @@ sequenceDiagram
|
|
|
222
225
|
|
|
223
226
|
## Module organization
|
|
224
227
|
|
|
225
|
-
The extension has
|
|
228
|
+
The extension has 56 source files organized into six domains plus entry-point wiring.
|
|
226
229
|
All eight domains have directories: `config/`, `session/`, `lifecycle/`, `observation/`, `service/`, `tools/`, `ui/`, and `handlers/`.
|
|
227
230
|
Issue #164 moved the 26 previously flat root-level files into five new domain directories, reducing the root to 5 files + 8 directories.
|
|
228
231
|
|
|
@@ -260,6 +263,7 @@ src/
|
|
|
260
263
|
│ ├── parent-snapshot.ts immutable spawn-time parent state
|
|
261
264
|
│ ├── execution-state.ts session/output phase state
|
|
262
265
|
│ ├── permission-bridge.ts optional bridge to pi-permission-system registry
|
|
266
|
+
│ ├── run-handle.ts per-run cleanup lifecycle
|
|
263
267
|
│ ├── worktree.ts git worktree isolation
|
|
264
268
|
│ ├── worktree-state.ts worktree phase state
|
|
265
269
|
│ └── usage.ts token usage tracking
|
|
@@ -272,7 +276,7 @@ src/
|
|
|
272
276
|
│
|
|
273
277
|
├── service/ cross-extension API boundary
|
|
274
278
|
│ ├── service.ts SubagentsService interface + Symbol.for() accessors
|
|
275
|
-
│ └── service-adapter.ts
|
|
279
|
+
│ └── service-adapter.ts SubagentsServiceAdapter class wrapping AgentManager
|
|
276
280
|
│
|
|
277
281
|
├── tools/ LLM-facing tool implementations
|
|
278
282
|
│ ├── agent-tool.ts Agent tool definition, validation, dispatch
|
|
@@ -288,8 +292,8 @@ src/
|
|
|
288
292
|
│ ├── agent-widget.ts above-editor live status widget
|
|
289
293
|
│ ├── widget-renderer.ts pure rendering for widget
|
|
290
294
|
│ ├── agent-menu.ts /agents slash command menu
|
|
291
|
-
│ ├── agent-config-editor.ts agent detail/edit view
|
|
292
|
-
│ ├── agent-creation-wizard.ts agent creation (
|
|
295
|
+
│ ├── agent-config-editor.ts agent detail/edit view (AgentConfigEditor class)
|
|
296
|
+
│ ├── agent-creation-wizard.ts agent creation (AgentCreationWizard class)
|
|
293
297
|
│ ├── conversation-viewer.ts scrollable session overlay
|
|
294
298
|
│ ├── message-formatters.ts pure per-message-type formatters (extracted from conversation-viewer)
|
|
295
299
|
│ ├── agent-activity-tracker.ts live activity state tracker
|
|
@@ -335,8 +339,9 @@ They declare this package as an optional peer dependency and use dynamic import
|
|
|
335
339
|
|
|
336
340
|
- The three tools: `Agent`, `get_subagent_result`, `steer_subagent`.
|
|
337
341
|
- `AgentManager` — spawn, queue, abort, resume, concurrency control.
|
|
338
|
-
- `agent-runner` — session creation, turn loop,
|
|
342
|
+
- `agent-runner` — session creation, turn loop, extension binding.
|
|
339
343
|
- `permission-bridge` — optional cross-extension bridge to `@gotgenes/pi-permission-system`; registers each child session with `SubagentSessionRegistry` before `bindExtensions()` so the permission system detects in-process children deterministically.
|
|
344
|
+
Scheduled for removal in Phase 16 — replaced by lifecycle events that consumers listen for.
|
|
340
345
|
- `session-config` — pure configuration assembler (extracted from `agent-runner`).
|
|
341
346
|
- `SubagentRuntime` — session-scoped state bag with methods.
|
|
342
347
|
- `ParentSnapshot` — immutable snapshot of parent session state, captured once at spawn time.
|
|
@@ -430,22 +435,56 @@ The core emits events on `pi.events` that any extension can observe:
|
|
|
430
435
|
|
|
431
436
|
These are fire-and-forget broadcast events — no request IDs, no reply channels.
|
|
432
437
|
|
|
438
|
+
## Target architecture
|
|
439
|
+
|
|
440
|
+
The long-term architectural direction is to make pi-subagents a **minimal core** with inverted dependencies.
|
|
441
|
+
Today, pi-subagents reaches outward to pi-permission-system via a bridge module and owns tool/extension filtering logic that duplicates permission-system responsibilities.
|
|
442
|
+
The target state eliminates this overlap and flips the dependency direction.
|
|
443
|
+
|
|
444
|
+
### Core responsibilities (keep)
|
|
445
|
+
|
|
446
|
+
- **Agent definitions** — name, model, thinking, system prompt, tools list.
|
|
447
|
+
- **Prompt composition** — system prompt assembly, skill preloading into prompt.
|
|
448
|
+
- **Session lifecycle** — create child sessions, bind extensions, run conversation loop, track results.
|
|
449
|
+
- **Concurrency management** — queue, abort, resume, max concurrency.
|
|
450
|
+
- **Recursion guard** — remove pi-subagents' own three tools from child sessions (prevent infinite nesting).
|
|
451
|
+
- **Lifecycle events** — emit events on `pi.events` when child sessions are created, completed, etc.
|
|
452
|
+
- **Service API** — publish `SubagentsService` via `Symbol.for()` for cross-extension access.
|
|
453
|
+
|
|
454
|
+
### Responsibilities to remove
|
|
455
|
+
|
|
456
|
+
- **Tool policy** (`disallowed_tools`, `ToolFilterConfig.disallowedSet`) — access control belongs in pi-permission-system's `permission:` frontmatter.
|
|
457
|
+
- **Extension filtering** (`extensions: string[]` allowlist) — tool visibility is pi-permission-system's job.
|
|
458
|
+
- **Permission bridge** (`permission-bridge.ts`) — outbound coupling to pi-permission-system.
|
|
459
|
+
Replaced by lifecycle events that pi-permission-system listens for.
|
|
460
|
+
- **Extension lifecycle control** (`extensions: false`, `isolated`) — extensions provide behavioral layers (permissions, formatting, context management) that benefit all agents.
|
|
461
|
+
Blanket-disabling them is a blunt instrument with no clear use case; tool restrictions belong in the permission system.
|
|
462
|
+
|
|
463
|
+
### Composition model
|
|
464
|
+
|
|
465
|
+
In the target state, pi-subagents publishes events and other packages hook in:
|
|
466
|
+
|
|
467
|
+
- **pi-permission-system** listens for child session lifecycle events, applies per-agent policy (allow/ask/deny), gates tool calls at runtime.
|
|
468
|
+
- **pi-subagents-ui** (future) subscribes to the service API, renders the widget, conversation viewer, and `/agents` menu.
|
|
469
|
+
- **Any future extension** (OTel, auditing, cost tracking) hooks into the same events without pi-subagents knowing.
|
|
470
|
+
|
|
471
|
+
This is achieved across three phases: Phase 14 (strip policy), Phase 16 (invert dependencies), and Phase 17 (extract UI).
|
|
472
|
+
|
|
433
473
|
## Current structural analysis
|
|
434
474
|
|
|
435
475
|
### Health metrics
|
|
436
476
|
|
|
437
|
-
| Metric | Value
|
|
438
|
-
| -------------------------- |
|
|
439
|
-
| Health score | 78/100 (B)
|
|
440
|
-
| Total LOC | 8,
|
|
441
|
-
|
|
|
442
|
-
|
|
|
443
|
-
|
|
|
444
|
-
|
|
|
445
|
-
|
|
|
446
|
-
|
|
|
447
|
-
|
|
|
448
|
-
| Fallow refactoring targets | 1 (buildParentContext, cognitive 30) |
|
|
477
|
+
| Metric | Value |
|
|
478
|
+
| -------------------------- | --------------------------------- |
|
|
479
|
+
| Health score | 78/100 (B) |
|
|
480
|
+
| Total LOC | 8,382 (56 files) |
|
|
481
|
+
| Dead code | 0 files, 0 exports |
|
|
482
|
+
| Maintainability index | 90.8 (good) |
|
|
483
|
+
| Avg cyclomatic complexity | 1.4 |
|
|
484
|
+
| P90 cyclomatic complexity | 2 |
|
|
485
|
+
| Production duplication | 11 lines (1 internal clone group) |
|
|
486
|
+
| Test duplication | 38 clone groups, 634 lines |
|
|
487
|
+
| Fallow refactoring targets | 0 |
|
|
449
488
|
|
|
450
489
|
### Dependency bag inventory
|
|
451
490
|
|
|
@@ -471,28 +510,29 @@ Bags with 10+ fields are the highest priority for decomposition.
|
|
|
471
510
|
|
|
472
511
|
Functions with cyclomatic complexity ≥ 21 (critical threshold):
|
|
473
512
|
|
|
474
|
-
No functions remain above the critical threshold — all hotspots resolved in Phase 12.
|
|
513
|
+
No functions remain above the critical threshold — all hotspots resolved in Phase 12. 6 functions remain at HIGH severity (CRAP ≥ 65); 13 at moderate.
|
|
475
514
|
|
|
476
515
|
### Churn hotspots
|
|
477
516
|
|
|
478
|
-
Files with highest commit frequency × complexity
|
|
517
|
+
Files with highest commit frequency × complexity:
|
|
479
518
|
|
|
480
519
|
| Score | File | Commits | Trend |
|
|
481
520
|
| ----- | --------------------------- | ------- | -------------- |
|
|
482
|
-
|
|
|
483
|
-
|
|
|
484
|
-
|
|
|
485
|
-
|
|
|
486
|
-
|
|
|
487
|
-
|
|
|
521
|
+
| 65.0 | `index.ts` | 128 | ▲ accelerating |
|
|
522
|
+
| 9.1 | `ui/agent-widget.ts` | 13 | ▼ cooling |
|
|
523
|
+
| 8.4 | `ui/conversation-viewer.ts` | 11 | ─ stable |
|
|
524
|
+
| 6.4 | `runtime.ts` | 12 | ─ stable |
|
|
525
|
+
| 3.3 | `settings.ts` | 4 | ─ stable |
|
|
526
|
+
| 2.9 | `handlers/lifecycle.ts` | 11 | ─ stable |
|
|
488
527
|
|
|
489
|
-
|
|
490
|
-
|
|
528
|
+
Most files have cooled to stable after 13 phases of structural work.
|
|
529
|
+
`index.ts` remains the sole accelerating hotspot — expected as the wiring entry point for each refactoring phase.
|
|
491
530
|
|
|
492
531
|
### Production duplication
|
|
493
532
|
|
|
494
533
|
The prior clone group between `agent-runner.ts` and `message-formatters.ts` was resolved in #172.
|
|
495
|
-
The 20-line clone group between `agent-config-editor.ts` and `agent-creation-wizard.ts` was resolved in #217 — extracted into `ui/agent-file-writer.ts` (`writeAgentFile`).
|
|
534
|
+
The 20-line clone group between `agent-config-editor.ts` and `agent-creation-wizard.ts` was resolved in #217 — extracted into `ui/agent-file-writer.ts` (`writeAgentFile`).
|
|
535
|
+
One 11-line internal clone group remains within `agent-config-editor.ts` (lines 135–145 / 173–183).
|
|
496
536
|
|
|
497
537
|
### Proposed bag decompositions
|
|
498
538
|
|
|
@@ -619,123 +659,97 @@ See [phase-11-closure-to-class.md](history/phase-11-closure-to-class.md) for det
|
|
|
619
659
|
Phase 12 decomposed the three remaining high-complexity UI functions and extracted shared test fixtures.
|
|
620
660
|
All four steps are closed: [#205], [#206], [#207], [#208].
|
|
621
661
|
|
|
622
|
-
##
|
|
623
|
-
|
|
624
|
-
Phase 13 addresses the remaining fallow refactoring target, `agent-manager.ts` oversized method, production duplication, SDK boundary coupling, and the heaviest test clone families.
|
|
625
|
-
Health score target: 80+ (A).
|
|
626
|
-
|
|
627
|
-
### Findings summary
|
|
628
|
-
|
|
629
|
-
| Finding | Category | Impact | Risk | Priority |
|
|
630
|
-
| ---------------------------------------------------------------- | -------------- | ------ | ---- | -------- |
|
|
631
|
-
| 3 remaining closure factories (Phase 11 survivors) | C: Coupling | 4 | 2 | 16 |
|
|
632
|
-
| `buildParentContext` cognitive 30 (only fallow target) | B: Oversized | 3 | 1 | 15 |
|
|
633
|
-
| `startAgent` in agent-manager.ts (~130 LOC method) | B: Oversized | 4 | 3 | 12 |
|
|
634
|
-
| Test duplication: 59 clone groups, 1,046 lines | D: Testability | 3 | 2 | 12 |
|
|
635
|
-
| Overwrite guard duplicated across UI modules (20 lines) | A: Redundant | 2 | 1 | 10 |
|
|
636
|
-
| `settings.ts` calls SDK function `getAgentDir()` at module level | C: Coupling | 2 | 1 | 10 |
|
|
637
|
-
|
|
638
|
-
### Step 1: Convert remaining closure factories to classes — [#214] ✓
|
|
662
|
+
## Phase 13 (complete)
|
|
639
663
|
|
|
640
|
-
|
|
664
|
+
Phase 13 addressed remaining closure factories, the last fallow refactoring target, oversized methods, production duplication, SDK boundary coupling, and test clone families.
|
|
665
|
+
All six steps are closed: [#214], [#215], [#216], [#217], [#218], [#219].
|
|
666
|
+
See [phase-13-remaining-smells.md](history/phase-13-remaining-smells.md) for details.
|
|
641
667
|
|
|
642
|
-
|
|
643
|
-
| ------------------------------------------------------ | ----------------------------- | ---------------------------------------- |
|
|
644
|
-
| `createAgentConfigEditor()` → `AgentConfigEditor` | `ui/agent-config-editor.ts` | `fileOps`, `registry`, 2 dirs |
|
|
645
|
-
| `createAgentCreationWizard()` → `AgentCreationWizard` | `ui/agent-creation-wizard.ts` | `fileOps`, `manager`, `registry`, 2 dirs |
|
|
646
|
-
| `createSubagentsService()` → `SubagentsServiceAdapter` | `service/service-adapter.ts` | `manager`, `resolveModel`, `runtime` |
|
|
668
|
+
## Improvement roadmap (Phase 14 — strip policy from core)
|
|
647
669
|
|
|
648
|
-
|
|
649
|
-
|
|
670
|
+
Phase 14 removes tool and extension policy enforcement from pi-subagents.
|
|
671
|
+
This code duplicates what pi-permission-system already provides with richer semantics (allow/ask/deny vs. binary hide).
|
|
672
|
+
Removing it simplifies `runAgent`, shrinks `AgentConfig` and `SessionConfig`, and makes the Phase 15 domain-model work operate on a cleaner codebase.
|
|
650
673
|
|
|
651
|
-
###
|
|
652
|
-
|
|
653
|
-
`buildParentContext` in `session/context.ts` is the only remaining fallow refactoring target.
|
|
654
|
-
The function loops over branch entries with 3 type-check branches, each with sub-branches for role or summary.
|
|
655
|
-
Extract per-entry-type formatters: `formatMessageEntry(entry)` and `formatCompactionEntry(entry)`.
|
|
656
|
-
|
|
657
|
-
- Target: `src/session/context.ts`
|
|
658
|
-
- Smell: B (oversized function)
|
|
659
|
-
- Outcome: cognitive complexity < 10, function < 15 LOC
|
|
660
|
-
|
|
661
|
-
### Step 3: Decompose `startAgent` in `agent-manager.ts` — [#216] ✓
|
|
662
|
-
|
|
663
|
-
`startAgent` had two mutable closure variables (`unsubRecordObserver`, `detachParentSignal`) shared across three callbacks with duplicated finalization logic in `.then()`/`.catch()`.
|
|
664
|
-
The fix introduced a `RunHandle` lifecycle object (private to `agent-manager.ts`) that owns the per-run cleanup state and exposes `complete()`/`fail()` as Tell-Don't-Ask methods.
|
|
665
|
-
`WorktreeState` gained `performCleanup(worktrees, description)` to eliminate the ask-tell dance at cleanup sites.
|
|
674
|
+
### Findings summary
|
|
666
675
|
|
|
667
|
-
|
|
676
|
+
| Finding | Category | Impact | Risk | Priority |
|
|
677
|
+
| ----------------------------------------------------------------------------------------- | ------------- | ------ | ---- | -------- |
|
|
678
|
+
| `disallowed_tools` duplicates pi-permission-system's `permission:` frontmatter | A: Overlap | 4 | 2 | 12 |
|
|
679
|
+
| `extensions: string[]` allowlist is tool filtering disguised as lifecycle control | A: Overlap | 3 | 2 | 9 |
|
|
680
|
+
| `filterActiveTools` runs twice (pre-bind + post-bind) to catch extension-registered tools | B: Complexity | 3 | 1 | 6 |
|
|
681
|
+
| `ToolFilterConfig` exists solely to carry filtering state through the runner | C: Accidental | 2 | 1 | 4 |
|
|
668
682
|
|
|
669
|
-
|
|
670
|
-
2. `finalizeBackgroundRun(record)` — shared `runningBackground--`, crash-safe observer notification, `drainQueue()`.
|
|
671
|
-
3. `setupWorktree(id, record, isolation)` — worktree creation with strict failure.
|
|
672
|
-
4. `flushPendingSteers(id, session)` — drain buffered steers on session creation.
|
|
673
|
-
5. `WorktreeState.performCleanup(worktrees, description)` — self-cleanup eliminating ask-tell.
|
|
683
|
+
### Step 1: Remove `disallowed_tools` — [#237] ✅ Complete
|
|
674
684
|
|
|
675
|
-
|
|
676
|
-
- Smell: B (oversized method) + A (duplicated finalization logic in then/catch)
|
|
677
|
-
- Outcome: `startAgent` reduced to ~40 LOC coordinator with zero mutable `let` bindings; `.then()`/`.catch()` are one-liners
|
|
685
|
+
Remove the `disallowedTools` field from `AgentConfig` and all code that processes it.
|
|
678
686
|
|
|
679
|
-
|
|
687
|
+
1. Remove `disallowedTools` from the `AgentConfig` interface in `types.ts`.
|
|
688
|
+
2. Remove `disallowed_tools` parsing from custom agent frontmatter in `custom-agents.ts`.
|
|
689
|
+
3. Remove `disallowedSet` from `ToolFilterConfig` in `session-config.ts`.
|
|
690
|
+
4. Remove the `disallowedSet` construction in `assembleSessionConfig`.
|
|
691
|
+
5. Remove the `disallowedSet` branch from `filterActiveTools` in `agent-runner.ts`.
|
|
692
|
+
6. Remove `disallowed_tools` from the agent config editor UI.
|
|
693
|
+
7. Remove `disallowed_tools` from the agent creation wizard.
|
|
694
|
+
8. Update tests.
|
|
680
695
|
|
|
681
|
-
|
|
682
|
-
|
|
696
|
+
- Target: `types.ts`, `custom-agents.ts`, `session-config.ts`, `agent-runner.ts`, `ui/agent-config-editor.ts`, `ui/agent-creation-wizard.ts`
|
|
697
|
+
- Smell: A (responsibility overlap with pi-permission-system)
|
|
698
|
+
- Outcome: users migrate to `permission:` frontmatter for tool restrictions; single source of truth for access control
|
|
683
699
|
|
|
684
|
-
|
|
685
|
-
- Smell: A (production duplication)
|
|
686
|
-
- Outcome: 0 production clone groups
|
|
700
|
+
### Step 2: Remove `extensions` filtering — [#238]
|
|
687
701
|
|
|
688
|
-
|
|
702
|
+
Remove the `extensions: string[]` allowlist and simplify the field to a boolean.
|
|
703
|
+
The `extensions: false` case (used by `isolated`) is retained in this step and removed in Phase 16.
|
|
689
704
|
|
|
690
|
-
`
|
|
691
|
-
|
|
692
|
-
|
|
705
|
+
1. Change `extensions` type from `true | string[] | false` to `boolean` in `AgentConfig`.
|
|
706
|
+
2. Remove the `extensions` array branch from `filterActiveTools` in `agent-runner.ts`.
|
|
707
|
+
3. Remove `extensions` from the agent config editor and creation wizard.
|
|
708
|
+
4. Update custom agent frontmatter parsing to treat array values as `true` (with a warning).
|
|
709
|
+
5. Update tests.
|
|
693
710
|
|
|
694
|
-
- Target: `
|
|
695
|
-
- Smell:
|
|
696
|
-
- Outcome: `
|
|
711
|
+
- Target: `types.ts`, `agent-runner.ts`, `session-config.ts`, `ui/agent-config-editor.ts`, `ui/agent-creation-wizard.ts`
|
|
712
|
+
- Smell: A (tool filtering disguised as extension lifecycle control)
|
|
713
|
+
- Outcome: `filterActiveTools` reduces to two concerns: recursion guard and `extensions: false` passthrough
|
|
697
714
|
|
|
698
|
-
### Step
|
|
715
|
+
### Step 3: Collapse `filterActiveTools` to recursion guard — [#239]
|
|
699
716
|
|
|
700
|
-
|
|
717
|
+
With Steps 1–2 complete, `filterActiveTools` has only two remaining branches: the `EXCLUDED_TOOL_NAMES` recursion guard and the `extensions === false` passthrough.
|
|
718
|
+
Inline the `extensions === false` passthrough into the callsite and reduce the function to its essential purpose.
|
|
701
719
|
|
|
702
|
-
1. `
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
720
|
+
1. Simplify `filterActiveTools` to filter only `EXCLUDED_TOOL_NAMES`.
|
|
721
|
+
2. Remove `ToolFilterConfig` — the function no longer needs a config bag.
|
|
722
|
+
3. Remove the pre-bind filter call — extension tools aren't in the active set yet, so filtering built-in tools pre-bind is only needed for denylist/allowlist logic that no longer exists.
|
|
723
|
+
4. Keep a single post-bind filter call for the recursion guard.
|
|
724
|
+
5. Simplify `SessionConfig` — remove the `toolFilter` field, keep `toolNames` as a flat field.
|
|
725
|
+
6. Update tests.
|
|
708
726
|
|
|
709
|
-
- Target: `
|
|
710
|
-
- Smell:
|
|
711
|
-
- Outcome:
|
|
727
|
+
- Target: `agent-runner.ts`, `session-config.ts`
|
|
728
|
+
- Smell: B (accidental complexity), C (two-pass filter dance)
|
|
729
|
+
- Outcome: `filterActiveTools` is a one-liner; `SessionConfig` loses one nested type; the pre-bind/post-bind dance is gone
|
|
712
730
|
|
|
713
731
|
### Step dependency diagram
|
|
714
732
|
|
|
715
733
|
```mermaid
|
|
716
734
|
flowchart LR
|
|
717
|
-
S1["Step 1\
|
|
718
|
-
S2["Step 2\
|
|
719
|
-
S3["Step 3\
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
S1 --> S4
|
|
725
|
-
S1 --> S6
|
|
726
|
-
S3 --> S6
|
|
727
|
-
S2 ~~~ S5
|
|
735
|
+
S1["Step 1\nRemove disallowed_tools"]
|
|
736
|
+
S2["Step 2\nRemove extensions filtering"]
|
|
737
|
+
S3["Step 3\nCollapse filterActiveTools"]
|
|
738
|
+
|
|
739
|
+
S1 --> S3
|
|
740
|
+
S2 --> S3
|
|
728
741
|
```
|
|
729
742
|
|
|
730
|
-
|
|
743
|
+
Steps 1 and 2 are independent and can proceed in parallel.
|
|
744
|
+
Step 3 depends on both.
|
|
731
745
|
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
746
|
+
[#237]: https://github.com/gotgenes/pi-packages/issues/237
|
|
747
|
+
[#238]: https://github.com/gotgenes/pi-packages/issues/238
|
|
748
|
+
[#239]: https://github.com/gotgenes/pi-packages/issues/239
|
|
735
749
|
|
|
736
|
-
## Improvement roadmap (Phase
|
|
750
|
+
## Improvement roadmap (Phase 15 — domain model evolution)
|
|
737
751
|
|
|
738
|
-
Phase
|
|
752
|
+
Phase 15 addresses the anemic domain model in the lifecycle layer.
|
|
739
753
|
`AgentRecord` is a data bag — identity, status transitions, and stats — but no behavior.
|
|
740
754
|
`AgentManager` reaches into records 37 times, doing work that belongs on the agent.
|
|
741
755
|
Per-agent state (pending steers, abort logic, run lifecycle) is scattered across the manager, `RunHandle`, and a manager-level Map.
|
|
@@ -839,26 +853,51 @@ flowchart LR
|
|
|
839
853
|
Sequential — each depends on the previous.
|
|
840
854
|
2. **Track B — Decoupling** (Steps 3, 4, 5): independent, can proceed in parallel with Track A.
|
|
841
855
|
|
|
856
|
+
## Improvement roadmap (Phase 16 — invert dependencies)
|
|
857
|
+
|
|
858
|
+
Phase 16 completes the architectural inversion by removing the outbound permission bridge and the `extensions: false` / `isolated` concepts.
|
|
859
|
+
It depends on Phase 15's observer pattern (#229) as the replacement mechanism.
|
|
860
|
+
|
|
861
|
+
Phase 16 is scoped but not yet broken into steps.
|
|
862
|
+
Key changes:
|
|
863
|
+
|
|
864
|
+
1. Remove `permission-bridge.ts` — the outbound coupling to pi-permission-system.
|
|
865
|
+
2. Emit child session lifecycle events via the observer — pi-permission-system and other consumers listen for these events instead of being called.
|
|
866
|
+
3. Remove `extensions: false` — all child sessions load all extensions.
|
|
867
|
+
4. Dissolve or redefine `isolated` — without extension control and tool filtering, the concept either disappears or becomes purely about prompt composition (no skill preloading, no parent context inheritance).
|
|
868
|
+
5. Update pi-permission-system to listen for child session events instead of being registered by the bridge.
|
|
869
|
+
|
|
870
|
+
## Improvement roadmap (Phase 17 — extract UI)
|
|
871
|
+
|
|
872
|
+
Phase 17 is the long-deferred UI extraction (originally Phase 6).
|
|
873
|
+
The widget, conversation viewer, and `/agents` command menu move to a separate `pi-subagents-ui` extension that consumes the `SubagentsService` API.
|
|
874
|
+
By this point the core is minimal and stable — the API boundary has been proven across Phases 14–16.
|
|
875
|
+
|
|
842
876
|
## Refactoring history
|
|
843
877
|
|
|
844
878
|
Phases 1–5 and 7–12 are complete.
|
|
845
879
|
Phase 6 (UI extraction to a separate package) is deferred.
|
|
846
880
|
Detailed records are preserved in per-phase history files:
|
|
847
881
|
|
|
848
|
-
| Phase
|
|
849
|
-
|
|
|
850
|
-
| 1
|
|
851
|
-
| 2
|
|
852
|
-
| 3
|
|
853
|
-
| 4
|
|
854
|
-
| 5
|
|
855
|
-
| 6
|
|
856
|
-
| 7
|
|
857
|
-
| 8
|
|
858
|
-
| 9
|
|
859
|
-
| 10
|
|
860
|
-
| 11
|
|
861
|
-
| 12
|
|
882
|
+
| Phase | Title | Status | History |
|
|
883
|
+
| -------- | --------------------------------------------------- | -------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
|
|
884
|
+
| 1 | Export SubagentsService API boundary | Complete | [phase-1-api-boundary.md](history/phase-1-api-boundary.md) |
|
|
885
|
+
| 2 | Remove scheduling subsystem | Complete | [phase-2-remove-scheduling.md](history/phase-2-remove-scheduling.md) |
|
|
886
|
+
| 3 | Remove group-join, RPC; replace output-file | Complete | [phase-3-remove-rpc-groupjoin.md](history/phase-3-remove-rpc-groupjoin.md) |
|
|
887
|
+
| 4 | Implement and publish SubagentsService | Complete | [phase-4-implement-service.md](history/phase-4-implement-service.md) |
|
|
888
|
+
| 5 | Decompose index.ts | Complete | [phase-5-decompose-index.md](history/phase-5-decompose-index.md) |
|
|
889
|
+
| 6 | Extract UI to separate package | Deferred → Phase 17 | — |
|
|
890
|
+
| 7 | Encapsulation and dependency narrowing | Complete | [phase-7-encapsulation.md](history/phase-7-encapsulation.md) |
|
|
891
|
+
| 8 | Testability, display extraction, menu decomposition | Complete | [phase-8-testability.md](history/phase-8-testability.md) |
|
|
892
|
+
| 9 | Observation consolidation, ctx elimination | Complete | [phase-9-observation-ctx.md](history/phase-9-observation-ctx.md) |
|
|
893
|
+
| 10 | Domain organization, bag decomposition, complexity | Complete | [phase-10-structural-decomposition.md](history/phase-10-structural-decomposition.md) |
|
|
894
|
+
| 11 | Closure factories to classes | Complete | [phase-11-closure-to-class.md](history/phase-11-closure-to-class.md) |
|
|
895
|
+
| 12 | Complexity reduction and test fixture extraction | Complete | [phase-12-complexity-test-fixtures.md](history/phase-12-complexity-test-fixtures.md) |
|
|
896
|
+
| 13 | Remaining structural smells | Complete | [phase-13-remaining-smells.md](history/phase-13-remaining-smells.md) |
|
|
897
|
+
| 14 | Strip policy from core | Planned | — |
|
|
898
|
+
| 15 | Domain model evolution | Planned | — |
|
|
899
|
+
| 16 | Invert dependencies | Planned | — |
|
|
900
|
+
| 17 | Extract UI to separate package | Planned | — |
|
|
862
901
|
|
|
863
902
|
### Structural refactoring issues
|
|
864
903
|
|
|
@@ -876,7 +915,8 @@ Detailed records are preserved in per-phase history files:
|
|
|
876
915
|
| Phase 11 | #192, #193, #194, #195, #196 | SessionContext, runtime queries, interface alignment, tool classes, runner/menu classes, index.ts simplification |
|
|
877
916
|
| Phase 12 | #205, #206, #207, #208 | renderWidgetLines, showAgentDetail, widget update, shared test fixtures |
|
|
878
917
|
| Phase 13 | #214, #215, #216, #217, #218, #219 | Closure-to-class, buildParentContext, startAgent decomp, overwrite guard, settings SDK, test duplication |
|
|
879
|
-
| Phase 14 | #
|
|
918
|
+
| Phase 14 | #237, #238, #239 | Remove disallowed_tools, remove extensions filtering, collapse filterActiveTools |
|
|
919
|
+
| Phase 15 | #227, #228, #229, #230, #231, #232 | Agent domain model, async startAgent, onSessionCreated observer, ConcurrencyQueue, relay deps, resume unification |
|
|
880
920
|
|
|
881
921
|
The remaining open issue is #22 (parent-session resolution), a cross-extension track that does not gate the structural work.
|
|
882
922
|
|
|
@@ -909,3 +949,9 @@ The upstream test suite is run periodically as a regression canary for the agent
|
|
|
909
949
|
[#217]: https://github.com/gotgenes/pi-packages/issues/217
|
|
910
950
|
[#218]: https://github.com/gotgenes/pi-packages/issues/218
|
|
911
951
|
[#219]: https://github.com/gotgenes/pi-packages/issues/219
|
|
952
|
+
[#227]: https://github.com/gotgenes/pi-packages/issues/227
|
|
953
|
+
[#228]: https://github.com/gotgenes/pi-packages/issues/228
|
|
954
|
+
[#229]: https://github.com/gotgenes/pi-packages/issues/229
|
|
955
|
+
[#230]: https://github.com/gotgenes/pi-packages/issues/230
|
|
956
|
+
[#231]: https://github.com/gotgenes/pi-packages/issues/231
|
|
957
|
+
[#232]: https://github.com/gotgenes/pi-packages/issues/232
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Phase 13: Remaining structural smells
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
Phase 13 addressed the remaining fallow refactoring target, oversized methods, production duplication, SDK boundary coupling, and test clone families.
|
|
6
|
+
All six steps are closed: [#214], [#215], [#216], [#217], [#218], [#219].
|
|
7
|
+
|
|
8
|
+
## Steps
|
|
9
|
+
|
|
10
|
+
### Step 1: Convert remaining closure factories to classes — [#214]
|
|
11
|
+
|
|
12
|
+
Three closure factories converted to classes, making their dependencies explicit as constructor parameters.
|
|
13
|
+
|
|
14
|
+
| Factory → Class | File |
|
|
15
|
+
| ------------------------------------------------------ | ----------------------------- |
|
|
16
|
+
| `createAgentConfigEditor()` → `AgentConfigEditor` | `ui/agent-config-editor.ts` |
|
|
17
|
+
| `createAgentCreationWizard()` → `AgentCreationWizard` | `ui/agent-creation-wizard.ts` |
|
|
18
|
+
| `createSubagentsService()` → `SubagentsServiceAdapter` | `service/service-adapter.ts` |
|
|
19
|
+
|
|
20
|
+
- Outcome: 0 remaining closure factories (excluding pure-function factories)
|
|
21
|
+
|
|
22
|
+
### Step 2: Decompose `buildParentContext` (cognitive 30) — [#215]
|
|
23
|
+
|
|
24
|
+
`buildParentContext` in `session/context.ts` was the only remaining fallow refactoring target.
|
|
25
|
+
Extracted per-entry-type formatters: `formatMessageEntry()` and `formatCompactionEntry()`.
|
|
26
|
+
|
|
27
|
+
- Target: `src/session/context.ts`
|
|
28
|
+
- Outcome: cognitive complexity < 10, function < 15 LOC, 0 fallow refactoring targets
|
|
29
|
+
|
|
30
|
+
### Step 3: Decompose `startAgent` in `agent-manager.ts` — [#216]
|
|
31
|
+
|
|
32
|
+
`startAgent` had two mutable closure variables and duplicated finalization logic in `.then()`/`.catch()`.
|
|
33
|
+
Introduced `RunHandle` lifecycle object (private to `agent-manager.ts`) that owns per-run cleanup state.
|
|
34
|
+
`WorktreeState` gained `performCleanup()` to eliminate ask-tell at cleanup sites.
|
|
35
|
+
|
|
36
|
+
Extracted:
|
|
37
|
+
|
|
38
|
+
1. `RunHandle` class — owns `unsub`/`detachFn`, `complete()`, `fail()`, idempotent `fireOnFinished()`.
|
|
39
|
+
2. `finalizeBackgroundRun()` — shared background finalization.
|
|
40
|
+
3. `setupWorktree()` — worktree creation with strict failure.
|
|
41
|
+
4. `flushPendingSteers()` — drain buffered steers on session creation.
|
|
42
|
+
5. `WorktreeState.performCleanup()` — self-cleanup eliminating ask-tell.
|
|
43
|
+
|
|
44
|
+
- Target: `src/lifecycle/agent-manager.ts`, `src/lifecycle/worktree-state.ts`
|
|
45
|
+
- Outcome: `startAgent` reduced to ~40 LOC; zero mutable `let` bindings in `.then()`/`.catch()`
|
|
46
|
+
|
|
47
|
+
### Step 4: Extract overwrite guard from UI — [#217]
|
|
48
|
+
|
|
49
|
+
Extracted the 20-line pattern duplicated between `agent-config-editor.ts` and `agent-creation-wizard.ts` into `writeAgentFile()` in `src/ui/agent-file-writer.ts`.
|
|
50
|
+
|
|
51
|
+
- Target: new `src/ui/agent-file-writer.ts`
|
|
52
|
+
- Outcome: 0 production clone groups (at the time; one internal group re-emerged later in `agent-config-editor.ts`)
|
|
53
|
+
|
|
54
|
+
### Step 5: Push SDK boundary in `settings.ts` — [#218]
|
|
55
|
+
|
|
56
|
+
Injected `agentDir: string` as a constructor parameter to `SettingsManager`, replacing the module-level `getAgentDir()` SDK call.
|
|
57
|
+
|
|
58
|
+
- Target: `src/settings.ts`, `src/index.ts`
|
|
59
|
+
- Outcome: `settings.ts` has 0 Pi SDK imports; `loadSettings`/`saveSettings` fully testable without SDK stubs
|
|
60
|
+
|
|
61
|
+
### Step 6: Reduce test duplication — top 3 clone families — [#219]
|
|
62
|
+
|
|
63
|
+
Extracted shared setup/assertion helpers for the three heaviest test clone families.
|
|
64
|
+
|
|
65
|
+
- Target: `test/lifecycle/agent-manager.test.ts`, `test/conversation-viewer.test.ts`, `test/ui/agent-config-editor.test.ts`
|
|
66
|
+
- Outcome: test duplication significantly reduced
|
|
67
|
+
|
|
68
|
+
## Metrics change
|
|
69
|
+
|
|
70
|
+
| Metric | Before | After |
|
|
71
|
+
| -------------------------- | ---------------------- | --------------------------------- |
|
|
72
|
+
| Health score | 78/100 (B) | 78/100 (B) |
|
|
73
|
+
| Source files | 53 | 56 |
|
|
74
|
+
| Total LOC | 8,180 | 8,382 |
|
|
75
|
+
| Dead code | 0 files, 0 exports | 0 files, 0 exports |
|
|
76
|
+
| Maintainability index | 90.7 | 90.8 |
|
|
77
|
+
| Avg cyclomatic complexity | 1.5 | 1.4 |
|
|
78
|
+
| Fallow refactoring targets | 1 | 0 |
|
|
79
|
+
| Production duplication | 0 clone groups | 1 internal clone group (11 lines) |
|
|
80
|
+
| Test duplication | 59 groups, 1,046 lines | 38 groups, 645 lines (overall) |
|
|
81
|
+
| Churn hotspots | 6 accelerating | 1 accelerating (`index.ts`) |
|
|
82
|
+
|
|
83
|
+
[#214]: https://github.com/gotgenes/pi-packages/issues/214
|
|
84
|
+
[#215]: https://github.com/gotgenes/pi-packages/issues/215
|
|
85
|
+
[#216]: https://github.com/gotgenes/pi-packages/issues/216
|
|
86
|
+
[#217]: https://github.com/gotgenes/pi-packages/issues/217
|
|
87
|
+
[#218]: https://github.com/gotgenes/pi-packages/issues/218
|
|
88
|
+
[#219]: https://github.com/gotgenes/pi-packages/issues/219
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 237
|
|
3
|
+
issue_title: "Remove disallowed_tools from pi-subagents (Phase 14, Step 1)"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Remove `disallowed_tools` from pi-subagents
|
|
7
|
+
|
|
8
|
+
## Problem Statement
|
|
9
|
+
|
|
10
|
+
The `disallowed_tools` frontmatter field and its runtime counterpart `disallowedSet` duplicate what pi-permission-system already provides via the `permission:` frontmatter with richer semantics (allow/ask/deny vs. binary hide).
|
|
11
|
+
Removing it establishes a single source of truth for tool access control in pi-permission-system and simplifies `filterActiveTools`, `ToolFilterConfig`, and `AgentConfig`.
|
|
12
|
+
|
|
13
|
+
## Goals
|
|
14
|
+
|
|
15
|
+
- Remove the `disallowedTools` field from `AgentConfig` and all code that reads, parses, or serializes it.
|
|
16
|
+
- Remove the `disallowedSet` field from `ToolFilterConfig` and the construction logic in `assembleSessionConfig`.
|
|
17
|
+
- Remove the `disallowedSet` branch from `filterActiveTools` in `agent-runner.ts`.
|
|
18
|
+
- Remove `disallowed_tools` from the agent config editor UI and the agent creation wizard.
|
|
19
|
+
- Update README.md to remove all `disallowed_tools` references and add a migration note.
|
|
20
|
+
- Update tests to remove `disallowedTools`/`disallowedSet` assertions and fixture data.
|
|
21
|
+
- This is a **breaking change** — users with `disallowed_tools` in agent frontmatter must migrate to `permission:` frontmatter.
|
|
22
|
+
|
|
23
|
+
## Non-Goals
|
|
24
|
+
|
|
25
|
+
- Removing `extensions` filtering (Step 2, #238).
|
|
26
|
+
- Collapsing `filterActiveTools` to a recursion guard (Step 3, #239).
|
|
27
|
+
- Changing any pi-permission-system code — this issue is purely pi-subagents.
|
|
28
|
+
- Adding deprecation warnings or runtime migration helpers — the architecture doc calls for clean removal.
|
|
29
|
+
|
|
30
|
+
## Background
|
|
31
|
+
|
|
32
|
+
### Phase 14 context
|
|
33
|
+
|
|
34
|
+
This is Phase 14, Step 1 of the architecture roadmap in `docs/architecture/architecture.md`.
|
|
35
|
+
Steps 1 and 2 (#237, #238) are independent; Step 3 (#239) depends on both.
|
|
36
|
+
|
|
37
|
+
### Files involved
|
|
38
|
+
|
|
39
|
+
| File | Role |
|
|
40
|
+
| --------------------------------- | -------------------------------------------------------------------------- |
|
|
41
|
+
| `src/types.ts` | `AgentConfig.disallowedTools` field definition |
|
|
42
|
+
| `src/config/custom-agents.ts` | Parses `disallowed_tools` from YAML frontmatter |
|
|
43
|
+
| `src/session/session-config.ts` | `ToolFilterConfig.disallowedSet` + construction in `assembleSessionConfig` |
|
|
44
|
+
| `src/lifecycle/agent-runner.ts` | `filterActiveTools` denylist branch + guard conditions at two call sites |
|
|
45
|
+
| `src/ui/agent-config-editor.ts` | Serializes `disallowedTools` to `disallowed_tools` frontmatter |
|
|
46
|
+
| `src/ui/agent-creation-wizard.ts` | Template text showing `disallowed_tools` as an available field |
|
|
47
|
+
| `README.md` | Feature list, frontmatter table, memory detection note, patch notes |
|
|
48
|
+
|
|
49
|
+
### `filterActiveTools` after this change
|
|
50
|
+
|
|
51
|
+
With `disallowedSet` removed, the function simplifies:
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
function filterActiveTools(
|
|
55
|
+
activeTools: string[],
|
|
56
|
+
config: ToolFilterConfig,
|
|
57
|
+
): string[] {
|
|
58
|
+
const { toolNames, extensions } = config;
|
|
59
|
+
if (extensions === false) {
|
|
60
|
+
return activeTools;
|
|
61
|
+
}
|
|
62
|
+
const builtinToolNameSet = new Set(toolNames);
|
|
63
|
+
return activeTools.filter((t) => {
|
|
64
|
+
if (EXCLUDED_TOOL_NAMES.includes(t)) return false;
|
|
65
|
+
if (builtinToolNameSet.has(t)) return true;
|
|
66
|
+
if (Array.isArray(extensions)) {
|
|
67
|
+
return extensions.some((ext) => t.startsWith(ext) || t.includes(ext));
|
|
68
|
+
}
|
|
69
|
+
return true;
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The `extensions === false` branch becomes a trivial passthrough — no filtering at all.
|
|
75
|
+
The guard condition at both call sites simplifies from `cfg.toolFilter.extensions !== false || cfg.toolFilter.disallowedSet` to `cfg.toolFilter.extensions !== false`.
|
|
76
|
+
|
|
77
|
+
## Design Overview
|
|
78
|
+
|
|
79
|
+
This is a pure removal — no new types, no new modules, no new behavior.
|
|
80
|
+
Every change deletes or simplifies existing code.
|
|
81
|
+
|
|
82
|
+
### Migration path
|
|
83
|
+
|
|
84
|
+
Users currently using `disallowed_tools` in agent frontmatter migrate to pi-permission-system's `permission:` frontmatter:
|
|
85
|
+
|
|
86
|
+
```yaml
|
|
87
|
+
# Before
|
|
88
|
+
disallowed_tools: bash
|
|
89
|
+
|
|
90
|
+
# After
|
|
91
|
+
permission:
|
|
92
|
+
bash: deny
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Module-Level Changes
|
|
96
|
+
|
|
97
|
+
1. **`src/types.ts`** — Remove the `disallowedTools` field and its JSDoc comment from `AgentConfig`.
|
|
98
|
+
2. **`src/config/custom-agents.ts`** — Remove the `disallowedTools: csvListOptional(fm.disallowed_tools)` line from the agent config construction.
|
|
99
|
+
3. **`src/session/session-config.ts`** — Remove `disallowedSet` from the `ToolFilterConfig` interface.
|
|
100
|
+
Remove the `disallowedSet` construction block and its comment in `assembleSessionConfig`.
|
|
101
|
+
Remove `disallowedSet` from the `toolFilter` object literal.
|
|
102
|
+
4. **`src/lifecycle/agent-runner.ts`** — Remove the `disallowedSet` destructure and denylist branches from `filterActiveTools`.
|
|
103
|
+
Simplify the `extensions === false` branch to `return activeTools`.
|
|
104
|
+
Simplify both guard conditions from `cfg.toolFilter.extensions !== false || cfg.toolFilter.disallowedSet` to `cfg.toolFilter.extensions !== false`.
|
|
105
|
+
Update comments that mention `disallowedTools denylist`.
|
|
106
|
+
5. **`src/ui/agent-config-editor.ts`** — Remove the two lines that serialize `disallowedTools` to `disallowed_tools` frontmatter.
|
|
107
|
+
6. **`src/ui/agent-creation-wizard.ts`** — Remove the `disallowed_tools:` line from the template text.
|
|
108
|
+
7. **`README.md`** — Remove the "Tool denylist" feature bullet, the `disallowed_tools` row from the frontmatter table, the memory write-capability paragraph (memory was removed in #185; this reference is already stale), the `disallowed_tools` example in the memory section, and the `disallowedTools` mention in the Patch 2 notes.
|
|
109
|
+
Add a migration note under a "Migration" heading or in the breaking changes section.
|
|
110
|
+
|
|
111
|
+
### Test files
|
|
112
|
+
|
|
113
|
+
8. **`test/config/custom-agents.test.ts`** — Remove the two `disallowed_tools` tests ("parses disallowed_tools as csv list" and "disallowed_tools defaults to undefined when omitted").
|
|
114
|
+
9. **`test/session/session-config.test.ts`** — Remove the `disallowedSet` assertion from the default config test and remove the entire "builds disallowedSet from agentConfig.disallowedTools" test.
|
|
115
|
+
10. **`test/lifecycle/agent-runner-extension-tools.test.ts`** — Remove `disallowedTools` from both mock agent config objects.
|
|
116
|
+
Remove the three `disallowedTools`-specific tests ("post-bind re-filter respects disallowedTools denylist", "extensions: false still applies disallowedTools", "extensions: false with no disallowedTools skips the filter").
|
|
117
|
+
Update the file-level JSDoc comment.
|
|
118
|
+
11. **`test/ui/agent-config-editor.test.ts`** — Remove `disallowedTools` from the test fixture and remove the `disallowed_tools` assertion.
|
|
119
|
+
|
|
120
|
+
### Architecture docs
|
|
121
|
+
|
|
122
|
+
12. **`docs/architecture/architecture.md`** — Mark Step 1 as complete (update the step heading or add a completion note).
|
|
123
|
+
|
|
124
|
+
## Test Impact Analysis
|
|
125
|
+
|
|
126
|
+
1. **Tests removed** — 7 tests that directly assert `disallowedTools`/`disallowedSet` behavior become meaningless and are deleted:
|
|
127
|
+
- `custom-agents.test.ts`: 2 tests (csv parsing, undefined default)
|
|
128
|
+
- `session-config.test.ts`: 1 test (`disallowedSet` construction) + 1 assertion in default config test
|
|
129
|
+
- `agent-runner-extension-tools.test.ts`: 3 tests (denylist for extension tools, denylist for built-in tools with `extensions: false`, filter skip with `extensions: false` + no denylist)
|
|
130
|
+
- `agent-config-editor.test.ts`: 1 assertion in the serialization test
|
|
131
|
+
|
|
132
|
+
2. **Tests updated** — Several tests have `disallowedTools` in their mock fixtures but don't test it directly; those fixtures lose the field:
|
|
133
|
+
- `agent-runner-extension-tools.test.ts`: both mock config objects drop `disallowedTools`
|
|
134
|
+
|
|
135
|
+
3. **Tests unchanged** — The remaining `agent-runner-extension-tools.test.ts` tests (`extensions: true`, `extensions: string[]` allowlist, `extensions: false` passthrough) remain valid — they test extension filtering, not the denylist.
|
|
136
|
+
|
|
137
|
+
## TDD Order
|
|
138
|
+
|
|
139
|
+
1. **Remove `disallowedTools` from `AgentConfig`** — Delete the field and JSDoc from `types.ts`.
|
|
140
|
+
Remove `disallowedTools` parsing from `custom-agents.ts`.
|
|
141
|
+
Remove the two `disallowed_tools` tests from `custom-agents.test.ts`.
|
|
142
|
+
Run `pnpm run check` to surface all downstream type errors.
|
|
143
|
+
Commit: `feat!: remove disallowedTools from AgentConfig`
|
|
144
|
+
|
|
145
|
+
2. **Remove `disallowedSet` from `ToolFilterConfig` and `assembleSessionConfig`** — Delete the field from `ToolFilterConfig`, the construction block in `assembleSessionConfig`, and the `disallowedSet` key from the return literal.
|
|
146
|
+
Remove the `disallowedSet`-specific test and assertion from `session-config.test.ts`.
|
|
147
|
+
Run `pnpm run check`.
|
|
148
|
+
Commit: `refactor: remove disallowedSet from ToolFilterConfig`
|
|
149
|
+
|
|
150
|
+
3. **Simplify `filterActiveTools` and guard conditions** — Remove the `disallowedSet` destructure and denylist branches from `filterActiveTools`.
|
|
151
|
+
Simplify the `extensions === false` branch to `return activeTools`.
|
|
152
|
+
Simplify both guard conditions to `cfg.toolFilter.extensions !== false`.
|
|
153
|
+
Update comments.
|
|
154
|
+
Remove the three `disallowedTools`-specific tests and update mock fixtures in `agent-runner-extension-tools.test.ts`.
|
|
155
|
+
Run tests: `pnpm vitest run`.
|
|
156
|
+
Commit: `refactor: remove disallowedSet branch from filterActiveTools`
|
|
157
|
+
|
|
158
|
+
4. **Remove `disallowed_tools` from UI** — Remove serialization from `agent-config-editor.ts` and the template line from `agent-creation-wizard.ts`.
|
|
159
|
+
Remove `disallowedTools` from the test fixture and assertion in `agent-config-editor.test.ts`.
|
|
160
|
+
Run tests: `pnpm vitest run`.
|
|
161
|
+
Commit: `refactor: remove disallowed_tools from config editor and creation wizard`
|
|
162
|
+
|
|
163
|
+
5. **Update README.md** — Remove all `disallowed_tools` references (feature bullet, frontmatter table row, stale memory paragraph, example, patch notes mention).
|
|
164
|
+
Add a migration note directing users to `permission:` frontmatter.
|
|
165
|
+
Commit: `docs: remove disallowed_tools from README and add migration note`
|
|
166
|
+
|
|
167
|
+
6. **Mark Phase 14 Step 1 complete in architecture doc** — Update `docs/architecture/architecture.md` to reflect completion.
|
|
168
|
+
Commit: `docs: mark Phase 14 Step 1 complete in architecture`
|
|
169
|
+
|
|
170
|
+
## Risks and Mitigations
|
|
171
|
+
|
|
172
|
+
| Risk | Mitigation |
|
|
173
|
+
| ---------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
174
|
+
| Users with existing `disallowed_tools` frontmatter silently lose tool restrictions | Breaking change is intentional and documented; `feat!:` commit triggers a major version bump via release-please; README migration note directs to `permission:` frontmatter |
|
|
175
|
+
| `filterActiveTools` behavior regresses for non-denylist paths | Steps 1–3 each run `pnpm run check` or `pnpm vitest run`; existing extension-filtering tests remain unchanged |
|
|
176
|
+
| Stale references to `disallowed_tools` in docs or plans | Grep sweep in step 5 covers README; architecture doc updated in step 6; historical plan/retro files are left as-is (they document past state) |
|
|
177
|
+
|
|
178
|
+
## Open Questions
|
|
179
|
+
|
|
180
|
+
None — the issue's proposed change is unambiguous and the architecture doc provides detailed step-by-step guidance.
|
|
@@ -34,3 +34,31 @@ All 4 commits landed cleanly; full suite green at every step.
|
|
|
34
34
|
- The `createSessionRunner` + `createRunResult` chain required careful identity-check verification: `createRunResult(sess)` calls `toAgentSession(sess)` which casts without creating a new object, so `toBe(session)` assertions in the execution-state tests still pass. ✓
|
|
35
35
|
- ESLint auto-fixed two cosmetic issues on commit (`activity = undefined` → `activity` destructuring, `session as unknown` cast removal) — caught by pre-commit hooks, not a problem in practice.
|
|
36
36
|
- The `assertRenderFitsWidths` helper in `conversation-viewer.test.ts` reduced the 10 render-safety tests from ~8 lines each to 1–4 lines each; the `setupDetail` helper in `agent-config-editor.test.ts` eliminated 3 repeated setup lines per test across 18 `showAgentDetail` tests.
|
|
37
|
+
|
|
38
|
+
## Stage: Final Retrospective (2026-05-26T17:50:31Z)
|
|
39
|
+
|
|
40
|
+
### Session summary
|
|
41
|
+
|
|
42
|
+
All three stages (planning, TDD, ship) completed in a single session with zero user corrections.
|
|
43
|
+
Four TDD commits landed cleanly, CI passed on first push, issue #219 closed, and `pi-subagents-v7.8.1` released.
|
|
44
|
+
|
|
45
|
+
### Observations
|
|
46
|
+
|
|
47
|
+
#### What went well
|
|
48
|
+
|
|
49
|
+
- Clean incremental verification: `pnpm vitest run <file>` ran after every code change, full suite at the end.
|
|
50
|
+
No regressions at any step.
|
|
51
|
+
- The plan's decision to keep gated runners inline proved correct — tests with `Promise.withResolvers` flow control stayed readable without abstraction.
|
|
52
|
+
- The `write` tool was the right choice for `conversation-viewer.test.ts` and `agent-config-editor.test.ts` (pervasive changes), while `edit` with 16 targeted replacements worked cleanly for `agent-manager.test.ts` (scattered but formulaic substitutions).
|
|
53
|
+
- Ship stage used `deepseek-v4-flash` for purely mechanical CI/release steps — appropriate model-task alignment.
|
|
54
|
+
|
|
55
|
+
#### What caused friction (agent side)
|
|
56
|
+
|
|
57
|
+
- `other` — ESLint pre-commit hooks auto-fixed two cosmetic patterns (`activity = undefined` → bare destructuring; `session as unknown` cast removal), requiring re-stage + re-commit.
|
|
58
|
+
Impact: ~10 seconds per occurrence, no rework.
|
|
59
|
+
This is routine and inherent to the workflow.
|
|
60
|
+
|
|
61
|
+
#### What caused friction (user side)
|
|
62
|
+
|
|
63
|
+
- None observed.
|
|
64
|
+
The user's three-stage prompt chain (`/plan-issue` → `/tdd-plan` → `/ship-issue`) provided sufficient context at each stage with no manual intervention needed.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 237
|
|
3
|
+
issue_title: "Remove disallowed_tools from pi-subagents (Phase 14, Step 1)"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Retro: #237 — Remove disallowed_tools from pi-subagents
|
|
7
|
+
|
|
8
|
+
## Stage: Planning (2026-05-27T00:52:26Z)
|
|
9
|
+
|
|
10
|
+
### Session summary
|
|
11
|
+
|
|
12
|
+
Produced a 6-step TDD plan to remove `disallowedTools` from `AgentConfig`, `disallowedSet` from `ToolFilterConfig`, and all parsing/serialization/UI/test code that references them.
|
|
13
|
+
The plan covers 7 source files, 4 test files, README, and the architecture doc.
|
|
14
|
+
|
|
15
|
+
### Observations
|
|
16
|
+
|
|
17
|
+
- The issue label `pkg:pi-permission-system` was incorrect — all target files live in `packages/pi-subagents`.
|
|
18
|
+
Confirmed with the user that the plan targets pi-subagents.
|
|
19
|
+
- The README still references `disallowed_tools` in the context of memory write-capability detection, but memory was already removed in #185.
|
|
20
|
+
The plan treats this as a stale reference to clean up.
|
|
21
|
+
- After removing `disallowedSet`, the `filterActiveTools` `extensions === false` branch simplifies to a trivial passthrough (`return activeTools`), and both guard conditions at the call sites drop the `|| cfg.toolFilter.disallowedSet` arm.
|
|
22
|
+
This leaves the function in the exact shape that Step 3 (#239) expects.
|
|
23
|
+
- The plan orders steps to follow the type dependency chain: `AgentConfig` first (surfaces all downstream errors), then `ToolFilterConfig`, then `filterActiveTools`, then UI, then docs.
|
|
24
|
+
|
|
25
|
+
## Stage: Implementation — TDD (2026-05-27T00:59:19Z)
|
|
26
|
+
|
|
27
|
+
### Session summary
|
|
28
|
+
|
|
29
|
+
All 6 TDD steps completed in one session, producing 7 commits (6 planned + 1 fixup for dead code).
|
|
30
|
+
Test count dropped from 983 to 978 (5 tests removed: 2 from `custom-agents.test.ts`, 1 from `session-config.test.ts`, 2 from `agent-runner-extension-tools.test.ts` after renaming one deleted test into a retained form).
|
|
31
|
+
All checks (type check, lint, fallow dead-code gate) pass clean.
|
|
32
|
+
|
|
33
|
+
### Observations
|
|
34
|
+
|
|
35
|
+
- `csvListOptional` in `custom-agents.ts` was left dead after removing the `disallowedTools` parsing call; Biome flagged it as unused.
|
|
36
|
+
Removed as a separate `refactor:` commit since it couldn't be amended into the `feat!:` commit (later commits already on top of it).
|
|
37
|
+
- One of the three deleted `agent-runner-extension-tools.test.ts` denylist tests ("extensions: false with no disallowedTools skips the filter") was reformulated into a new test ("extensions: false skips the filter entirely") rather than simply deleted, because it covers genuinely different behavior after the simplification: `extensions: false` now always skips the filter, not just when no denylist is present.
|
|
38
|
+
This adds coverage for the simplified code path.
|
|
39
|
+
- The `filterActiveTools` `extensions === false` branch simplified from "apply denylist to built-in tools" to `return activeTools`, exactly as the plan specified.
|
|
40
|
+
Both guard conditions at the call sites simplified to `cfg.toolFilter.extensions !== false`.
|
|
41
|
+
- No deviations from the plan's module-level changes list; all 7 source files and 4 test files were touched as specified.
|
package/package.json
CHANGED
|
@@ -58,7 +58,6 @@ 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
|
-
disallowedTools: csvListOptional(fm.disallowed_tools),
|
|
62
61
|
extensions: inheritField(fm.extensions ?? fm.inherit_extensions),
|
|
63
62
|
skills: inheritField(fm.skills ?? fm.inherit_skills),
|
|
64
63
|
model: str(fm.model),
|
|
@@ -110,14 +109,6 @@ function csvList(val: unknown, defaults: string[]): string[] {
|
|
|
110
109
|
return parseCsvField(val) ?? [];
|
|
111
110
|
}
|
|
112
111
|
|
|
113
|
-
/**
|
|
114
|
-
* Parse an optional comma-separated list field.
|
|
115
|
-
* omitted → undefined; "none"/empty → undefined; csv → listed items.
|
|
116
|
-
*/
|
|
117
|
-
function csvListOptional(val: unknown): string[] | undefined {
|
|
118
|
-
return parseCsvField(val);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
112
|
/**
|
|
122
113
|
* Parse an inherit field (extensions, skills).
|
|
123
114
|
* omitted/true → true (inherit all); false/"none"/empty → false; csv → listed names.
|
|
@@ -22,7 +22,7 @@ import type { ShellExec, SubagentType, ThinkingLevel } from "#src/types";
|
|
|
22
22
|
const EXCLUDED_TOOL_NAMES = ["Agent", "get_subagent_result", "steer_subagent"];
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
|
-
* Filter the session's active tool names according to extension
|
|
25
|
+
* Filter the session's active tool names according to extension rules.
|
|
26
26
|
*
|
|
27
27
|
* Run twice - once before `bindExtensions` (filters built-in tools) and once after
|
|
28
28
|
* (filters extension-registered tools, which only join the active set during
|
|
@@ -36,16 +36,13 @@ function filterActiveTools(
|
|
|
36
36
|
activeTools: string[],
|
|
37
37
|
config: ToolFilterConfig,
|
|
38
38
|
): string[] {
|
|
39
|
-
const { toolNames, extensions
|
|
39
|
+
const { toolNames, extensions } = config;
|
|
40
40
|
if (extensions === false) {
|
|
41
|
-
|
|
42
|
-
if (!disallowedSet) return activeTools;
|
|
43
|
-
return activeTools.filter((t) => !disallowedSet.has(t));
|
|
41
|
+
return activeTools;
|
|
44
42
|
}
|
|
45
43
|
const builtinToolNameSet = new Set(toolNames);
|
|
46
44
|
return activeTools.filter((t) => {
|
|
47
45
|
if (EXCLUDED_TOOL_NAMES.includes(t)) return false;
|
|
48
|
-
if (disallowedSet?.has(t)) return false;
|
|
49
46
|
if (builtinToolNameSet.has(t)) return true;
|
|
50
47
|
if (Array.isArray(extensions)) {
|
|
51
48
|
return extensions.some((ext) => t.startsWith(ext) || t.includes(ext));
|
|
@@ -341,9 +338,9 @@ export async function runAgent(
|
|
|
341
338
|
});
|
|
342
339
|
|
|
343
340
|
// Filter active tools: remove our own tools to prevent nesting,
|
|
344
|
-
// apply extension allowlist if specified
|
|
341
|
+
// apply extension allowlist if specified.
|
|
345
342
|
// First pass - over built-in tools, before bindExtensions registers extension tools.
|
|
346
|
-
if (cfg.toolFilter.extensions !== false
|
|
343
|
+
if (cfg.toolFilter.extensions !== false) {
|
|
347
344
|
const filtered = filterActiveTools(session.getActiveToolNames(), cfg.toolFilter);
|
|
348
345
|
session.setActiveToolsByName(filtered);
|
|
349
346
|
}
|
|
@@ -367,9 +364,8 @@ export async function runAgent(
|
|
|
367
364
|
// Extension-registered tools (added during bindExtensions) are not in the
|
|
368
365
|
// session's active set when the first filter pass runs above. Without this
|
|
369
366
|
// re-filter, the `extensions: string[]` allowlist branch never matches any
|
|
370
|
-
// extension tools
|
|
371
|
-
|
|
372
|
-
if (cfg.toolFilter.extensions !== false || cfg.toolFilter.disallowedSet) {
|
|
367
|
+
// extension tools. Run the same filter against the post-bind active set.
|
|
368
|
+
if (cfg.toolFilter.extensions !== false) {
|
|
373
369
|
const refiltered = filterActiveTools(session.getActiveToolNames(), cfg.toolFilter);
|
|
374
370
|
session.setActiveToolsByName(refiltered);
|
|
375
371
|
}
|
|
@@ -21,14 +21,12 @@ import type { AgentPromptConfig, SubagentType, ThinkingLevel } from "#src/types"
|
|
|
21
21
|
/**
|
|
22
22
|
* Tool filtering configuration — consumed by `filterActiveTools` in `agent-runner.ts`.
|
|
23
23
|
*
|
|
24
|
-
* Groups the
|
|
25
|
-
* the built-in tool allowlist
|
|
24
|
+
* Groups the two fields that travel together through the assembly→runner boundary:
|
|
25
|
+
* the built-in tool allowlist and the extensions setting.
|
|
26
26
|
*/
|
|
27
27
|
export interface ToolFilterConfig {
|
|
28
28
|
/** Built-in tool name allowlist for this agent type. */
|
|
29
29
|
toolNames: string[];
|
|
30
|
-
/** Disallowed tool set from agentConfig. undefined when empty. */
|
|
31
|
-
disallowedSet: Set<string> | undefined;
|
|
32
30
|
/** Resolved extensions setting: false | true | string[] allowlist. */
|
|
33
31
|
extensions: boolean | string[];
|
|
34
32
|
}
|
|
@@ -210,11 +208,6 @@ export function assembleSessionConfig(
|
|
|
210
208
|
// tell the resource loader not to load them again.
|
|
211
209
|
const noSkills = skills === false || Array.isArray(skills);
|
|
212
210
|
|
|
213
|
-
// Disallowed tools set (for filterActiveTools in runAgent)
|
|
214
|
-
const disallowedSet = agentConfig.disallowedTools
|
|
215
|
-
? new Set(agentConfig.disallowedTools)
|
|
216
|
-
: undefined;
|
|
217
|
-
|
|
218
211
|
// Model resolution: explicit option > config model string > parent model
|
|
219
212
|
const model =
|
|
220
213
|
options.model ??
|
|
@@ -229,7 +222,7 @@ export function assembleSessionConfig(
|
|
|
229
222
|
return {
|
|
230
223
|
effectiveCwd,
|
|
231
224
|
systemPrompt,
|
|
232
|
-
toolFilter: { toolNames,
|
|
225
|
+
toolFilter: { toolNames, extensions },
|
|
233
226
|
model,
|
|
234
227
|
thinkingLevel,
|
|
235
228
|
noSkills,
|
package/src/types.ts
CHANGED
|
@@ -42,8 +42,6 @@ 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
|
-
/** Tool denylist — these tools are removed even if `builtinToolNames` or extensions include them. */
|
|
46
|
-
disallowedTools?: string[];
|
|
47
45
|
/** true = inherit all, string[] = only listed, false = none */
|
|
48
46
|
extensions: true | string[] | false;
|
|
49
47
|
/** true = inherit all, string[] = only listed, false = none */
|
|
@@ -53,8 +53,6 @@ export function buildEjectContent(cfg: AgentConfig): string {
|
|
|
53
53
|
if (cfg.skills === false) fmFields.push("skills: false");
|
|
54
54
|
else if (Array.isArray(cfg.skills))
|
|
55
55
|
fmFields.push(`skills: ${cfg.skills.join(", ")}`);
|
|
56
|
-
if (cfg.disallowedTools?.length)
|
|
57
|
-
fmFields.push(`disallowed_tools: ${cfg.disallowedTools.join(", ")}`);
|
|
58
56
|
if (cfg.inheritContext) fmFields.push("inherit_context: true");
|
|
59
57
|
if (cfg.runInBackground) fmFields.push("run_in_background: true");
|
|
60
58
|
if (cfg.isolated) fmFields.push("isolated: true");
|
|
@@ -106,7 +106,6 @@ 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
107
|
extensions: <true (inherit all MCP/extension tools), false (none), or comma-separated names. Default: true>
|
|
108
108
|
skills: <true (inherit all), false (none), or comma-separated skill names to preload into prompt. Default: true>
|
|
109
|
-
disallowed_tools: <comma-separated tool names to block, even if otherwise available. Omit for none>
|
|
110
109
|
inherit_context: <true to fork parent conversation into agent so it sees chat history. Default: false>
|
|
111
110
|
run_in_background: <true to run in background by default. Default: false>
|
|
112
111
|
isolated: <true for no extension/MCP tools, only built-in tools. Default: false>
|