@gotgenes/pi-subagents 12.1.0 → 13.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 +15 -0
- package/dist/public.d.ts +0 -2
- package/docs/architecture/architecture.md +26 -30
- package/docs/plans/0264-remove-extension-lifecycle-control.md +275 -0
- package/docs/retro/0264-remove-extension-lifecycle-control.md +48 -0
- package/package.json +1 -1
- package/src/config/agent-types.ts +0 -2
- package/src/config/custom-agents.ts +0 -30
- package/src/config/default-agents.ts +1 -7
- package/src/config/invocation-config.ts +0 -3
- package/src/index.ts +0 -2
- package/src/lifecycle/agent-manager.ts +0 -2
- package/src/lifecycle/agent-runner.ts +6 -14
- package/src/lifecycle/agent.ts +0 -4
- package/src/service/service-adapter.ts +0 -1
- package/src/service/service.ts +0 -1
- package/src/session/prompts.ts +2 -23
- package/src/session/session-config.ts +2 -37
- package/src/tools/agent-tool.ts +0 -5
- package/src/tools/background-spawner.ts +0 -1
- package/src/tools/foreground-runner.ts +0 -1
- package/src/tools/spawn-config.ts +0 -4
- package/src/types.ts +0 -7
- package/src/ui/agent-config-editor.ts +0 -5
- package/src/ui/agent-creation-wizard.ts +0 -4
- package/src/ui/display.ts +1 -2
- package/src/session/safe-fs.ts +0 -45
- package/src/session/skill-loader.ts +0 -104
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,21 @@ 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
|
+
## [13.0.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v12.1.0...pi-subagents-v13.0.0) (2026-05-30)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### ⚠ BREAKING CHANGES
|
|
12
|
+
|
|
13
|
+
* the `skills:` custom-agent frontmatter key and skill preloading are removed; children always load the parent's skills.
|
|
14
|
+
* the `extensions:` custom-agent frontmatter key is removed; children always load the parent's extensions.
|
|
15
|
+
* `SpawnOptions.isolated` and the `isolated:` custom-agent frontmatter key are removed. Children always load the parent's extensions.
|
|
16
|
+
|
|
17
|
+
### Features
|
|
18
|
+
|
|
19
|
+
* always inherit extensions; make the recursion guard unconditional ([#264](https://github.com/gotgenes/pi-packages/issues/264)) ([3cc682e](https://github.com/gotgenes/pi-packages/commit/3cc682ec401167f922a1f892f6260a10f9fa99f2))
|
|
20
|
+
* always inherit skills; remove noSkills and the skill-preload path ([#264](https://github.com/gotgenes/pi-packages/issues/264)) ([93266ff](https://github.com/gotgenes/pi-packages/commit/93266ff4a204d154b357efac57912330fad240be))
|
|
21
|
+
* remove isolated from the subagent spawn API and lifecycle ([#264](https://github.com/gotgenes/pi-packages/issues/264)) ([d08f340](https://github.com/gotgenes/pi-packages/commit/d08f34066ea472d850423e67349b7a623ca72f42))
|
|
22
|
+
|
|
8
23
|
## [12.1.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v12.0.0...pi-subagents-v12.1.0) (2026-05-29)
|
|
9
24
|
|
|
10
25
|
|
package/dist/public.d.ts
CHANGED
|
@@ -25,7 +25,6 @@ interface AgentInvocation {
|
|
|
25
25
|
modelName?: string;
|
|
26
26
|
thinking?: ThinkingLevel;
|
|
27
27
|
maxTurns?: number;
|
|
28
|
-
isolated?: boolean;
|
|
29
28
|
inheritContext?: boolean;
|
|
30
29
|
runInBackground?: boolean;
|
|
31
30
|
}
|
|
@@ -125,7 +124,6 @@ interface SpawnOptions {
|
|
|
125
124
|
model?: string;
|
|
126
125
|
maxTurns?: number;
|
|
127
126
|
thinkingLevel?: string;
|
|
128
|
-
isolated?: boolean;
|
|
129
127
|
inheritContext?: boolean;
|
|
130
128
|
foreground?: boolean;
|
|
131
129
|
bypassQueue?: boolean;
|
|
@@ -45,8 +45,6 @@ flowchart TB
|
|
|
45
45
|
SessionConfig["assembleSessionConfig<br/>(pure assembler)"]
|
|
46
46
|
Prompts["prompts<br/>(system prompt)"]
|
|
47
47
|
Context["context<br/>(parent history)"]
|
|
48
|
-
SafeFs["safe-fs<br/>(symlink/name guards)"]
|
|
49
|
-
SkillLoader["skill-loader<br/>(preload skills)"]
|
|
50
48
|
Env["env<br/>(git/platform)"]
|
|
51
49
|
ModelResolver["model-resolver<br/>(fuzzy match)"]
|
|
52
50
|
end
|
|
@@ -90,8 +88,7 @@ flowchart TB
|
|
|
90
88
|
AgentManager --> AgentRunner
|
|
91
89
|
AgentRunner --> SessionConfig
|
|
92
90
|
SessionConfig --> AgentTypeRegistry
|
|
93
|
-
SessionConfig --> Prompts &
|
|
94
|
-
SkillLoader --> SafeFs
|
|
91
|
+
SessionConfig --> Prompts & Env
|
|
95
92
|
AgentTypeRegistry --> DefaultAgents & CustomAgents
|
|
96
93
|
RecordObserver -.->|subscribes| AgentRunner
|
|
97
94
|
UIObserver -.->|subscribes| AgentRunner
|
|
@@ -260,8 +257,6 @@ src/
|
|
|
260
257
|
│ ├── prompts.ts system prompt building
|
|
261
258
|
│ ├── content-items.ts shared message content parsing (tool-call names, assistant content)
|
|
262
259
|
│ ├── context.ts parent conversation extraction
|
|
263
|
-
│ ├── safe-fs.ts symlink rejection and safe file reads
|
|
264
|
-
│ ├── skill-loader.ts skill preloading
|
|
265
260
|
│ ├── env.ts git/platform detection
|
|
266
261
|
│ ├── model-resolver.ts fuzzy model name resolution
|
|
267
262
|
│ └── session-dir.ts session directory derivation
|
|
@@ -415,7 +410,7 @@ Key types:
|
|
|
415
410
|
|
|
416
411
|
- `SubagentsService` — `spawn`, `getRecord`, `listAgents`, `abort`, `steer`, `waitForAll`, `hasRunning`.
|
|
417
412
|
- `SubagentRecord` — serializable agent snapshot (no live session objects).
|
|
418
|
-
- `SpawnOptions` — `description`, `model`, `maxTurns`, `thinkingLevel`, `
|
|
413
|
+
- `SpawnOptions` — `description`, `model`, `maxTurns`, `thinkingLevel`, `inheritContext`, `foreground`, `bypassQueue`.
|
|
419
414
|
- `SUBAGENT_EVENTS` — channel constants for `pi.events` subscriptions.
|
|
420
415
|
|
|
421
416
|
### Accessor pattern
|
|
@@ -482,11 +477,11 @@ Latent extensibility is the deliverable; a vacant hook is not.
|
|
|
482
477
|
### Core responsibilities (keep)
|
|
483
478
|
|
|
484
479
|
- **Agent definitions** — name, model, thinking, system prompt, tools list.
|
|
485
|
-
- **Prompt composition** — system prompt assembly
|
|
480
|
+
- **Prompt composition** — system prompt assembly.
|
|
486
481
|
- **Session lifecycle** — create child sessions, bind extensions, run conversation loop, track results.
|
|
487
482
|
- **Concurrency management** — queue, abort, resume, max concurrency.
|
|
488
483
|
- **Recursion guard** — remove pi-subagents' own three tools from child sessions (prevent infinite nesting).
|
|
489
|
-
With `isolated` removed, children always load the parent's resources, so the guard
|
|
484
|
+
With `isolated` removed (#264), children always load the parent's resources, so the guard is unconditional rather than gated on `cfg.extensions`.
|
|
490
485
|
This is the core defending its own invariant, keyed off its own tool names — not policy.
|
|
491
486
|
- **Lifecycle events** — emit awaited, ordered events when child sessions spawn, are created, complete, and are disposed.
|
|
492
487
|
- **Workspace provider seam** — accept a registered `WorkspaceProvider` and consult it for the child's cwd; default to the parent's cwd when none is registered.
|
|
@@ -499,7 +494,8 @@ Latent extensibility is the deliverable; a vacant hook is not.
|
|
|
499
494
|
- **Worktree isolation** (`worktree.ts`, `worktree-isolation.ts`, `GitWorktreeManager`, the `isolation: "worktree"` spawn mode) — environment policy, not core.
|
|
500
495
|
Git worktrees are one *strategy* for choosing the child's working directory; containers, throwaway tmpdirs, and remote sandboxes are others.
|
|
501
496
|
Evicted to `@gotgenes/pi-subagents-worktrees` (#263), the first consumer of the workspace provider seam.
|
|
502
|
-
- **Extension lifecycle control** (`extensions: false`, `isolated`, `noSkills`) —
|
|
497
|
+
- **Extension lifecycle control** (`extensions: false`, `isolated`, `noSkills`) — removed in #264.
|
|
498
|
+
Deny-at-use (the in-child permission layer blocking disallowed tool calls) covers what `isolated` pretended to do for tools.
|
|
503
499
|
Prevent-load (refusing to bind an extension because of load-time side effects, cost, or true sandboxing) is genuinely generative and is left as a *latent* (un-built) provider seam, added only if a real consumer needs it.
|
|
504
500
|
|
|
505
501
|
### Composition model
|
|
@@ -536,20 +532,20 @@ This is achieved across phases: Phase 14 (strip policy), Phase 16 (invert depend
|
|
|
536
532
|
These interfaces carry hidden dependencies that obscure true coupling.
|
|
537
533
|
Bags with 10+ fields are the highest priority for decomposition.
|
|
538
534
|
|
|
539
|
-
| Interface | Fields
|
|
540
|
-
| --------------------------- |
|
|
541
|
-
| `ResolvedSpawnConfig` | 3 nested
|
|
542
|
-
| `AgentSpawnConfig` | 13 → 13 (ParentSessionInfo nested)
|
|
543
|
-
| `RunOptions` | 9 (`RunContext` nested)
|
|
544
|
-
| `SessionConfig` |
|
|
545
|
-
| `NotificationDetails` | 10
|
|
546
|
-
| `ResourceLoaderOptions` | 10
|
|
547
|
-
| `RunnerIO` | split → `EnvironmentIO` (3) + `SessionFactoryIO` (5+1)
|
|
548
|
-
| `CreateSessionOptions` | 9
|
|
549
|
-
| `AgentToolDeps` | 8
|
|
550
|
-
| `AgentMenuDeps` | 8
|
|
551
|
-
| `ConversationViewerOptions` | 8
|
|
552
|
-
| `AgentInit` | 8
|
|
535
|
+
| Interface | Fields | Consumers | Severity |
|
|
536
|
+
| --------------------------- | ----------------------------------------------------------- | ------------------------------------------------- | --------- |
|
|
537
|
+
| `ResolvedSpawnConfig` | 3 nested | foreground-runner, background-spawner, agent-tool | ✓ done |
|
|
538
|
+
| `AgentSpawnConfig` | 13 → 13 (ParentSessionInfo nested) | agent-manager (internal) | ✓ done |
|
|
539
|
+
| `RunOptions` | 9 (`RunContext` nested) | agent-runner | ✓ done |
|
|
540
|
+
| `SessionConfig` | 6 (flat fields; extensions/noSkills/extras removed in #264) | agent-runner (output of assembler) | ✓ done |
|
|
541
|
+
| `NotificationDetails` | 10 | notification | Low (DTO) |
|
|
542
|
+
| `ResourceLoaderOptions` | 10 | agent-runner (SDK bridge) | Low (SDK) |
|
|
543
|
+
| `RunnerIO` | split → `EnvironmentIO` (3) + `SessionFactoryIO` (5+1) | agent-runner | ✓ done |
|
|
544
|
+
| `CreateSessionOptions` | 9 | agent-runner (SDK bridge) | Low (SDK) |
|
|
545
|
+
| `AgentToolDeps` | 8 | agent-tool | ✓ done |
|
|
546
|
+
| `AgentMenuDeps` | 8 | agent-menu | ✓ done |
|
|
547
|
+
| `ConversationViewerOptions` | 8 | conversation-viewer | Low |
|
|
548
|
+
| `AgentInit` | 8 | agent | Low |
|
|
553
549
|
|
|
554
550
|
### Complexity hotspots
|
|
555
551
|
|
|
@@ -604,7 +600,6 @@ interface SpawnExecution {
|
|
|
604
600
|
thinking: ThinkingLevel | undefined;
|
|
605
601
|
inheritContext: boolean;
|
|
606
602
|
runInBackground: boolean;
|
|
607
|
-
isolated: boolean;
|
|
608
603
|
agentInvocation: AgentInvocation;
|
|
609
604
|
}
|
|
610
605
|
|
|
@@ -648,7 +643,7 @@ export interface RunContext {
|
|
|
648
643
|
}
|
|
649
644
|
```
|
|
650
645
|
|
|
651
|
-
The remaining `RunOptions` fields (`model`, `maxTurns`, `signal`, `
|
|
646
|
+
The remaining `RunOptions` fields (`model`, `maxTurns`, `signal`, `thinkingLevel`, `defaultMaxTurns`, `graceTurns`, `onSessionCreated`) are genuine execution parameters.
|
|
652
647
|
`RunOptions` now has 9 fields: 1 nested `context: RunContext` (2 per-call fields) plus 8 flat execution fields.
|
|
653
648
|
|
|
654
649
|
#### SessionConfig (11 fields → extract ToolFilterConfig) — done ([#168][168])
|
|
@@ -781,13 +776,14 @@ Removed `worktree.ts`, `worktree-isolation.ts`, `GitWorktreeManager`, and the `i
|
|
|
781
776
|
From the release carrying #272, all five workspace types are importable by name: `WorkspaceProvider`, `Workspace`, `WorkspacePrepareContext`, `WorkspaceDisposeOutcome`, and `WorkspaceDisposeResult`.
|
|
782
777
|
- Outcome: git leaves the core; worktree users install one package, everyone else pays nothing.
|
|
783
778
|
|
|
784
|
-
#### Step 4: Remove `isolated` / `extensions: false` / `noSkills` — [#264]
|
|
779
|
+
#### Step 4: Remove `isolated` / `extensions: false` / `noSkills` — [#264] ✅ Delivered
|
|
785
780
|
|
|
786
|
-
Children always load the parent's extensions and skills; the recursion guard
|
|
781
|
+
Children always load the parent's extensions and skills; the recursion guard is now unconditional.
|
|
787
782
|
Deny-at-use (the in-child permission layer) covers tool restriction; prevent-load is left as a latent provider seam (not shipped).
|
|
783
|
+
The `skills` curation axis collapsed symmetrically with `extensions`: `AgentConfig.skills`, the skill-preload path (`skill-loader.ts`, `safe-fs.ts`, `preloadSkills`, `PromptExtras`), `SessionConfig.{extensions,noSkills,extras}`, and the `isolated:` / `extensions:` / `skills:` custom-agent frontmatter keys are all gone.
|
|
788
784
|
|
|
789
|
-
-
|
|
790
|
-
- Outcome: the `isolated`/`extensions`/`noSkills` axis is gone;
|
|
785
|
+
- Depended on: Step 1 (deny-at-use over events).
|
|
786
|
+
- Outcome: the `isolated`/`extensions`/`noSkills`/`skills` axis is gone; the guard is unconditional.
|
|
791
787
|
|
|
792
788
|
#### Step 5: Born-complete child execution; dissolve the runner — [#265]
|
|
793
789
|
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 264
|
|
3
|
+
issue_title: "Remove isolated / extensions:false / noSkills from core"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Remove the extension-lifecycle-control axis from the core
|
|
7
|
+
|
|
8
|
+
## Problem Statement
|
|
9
|
+
|
|
10
|
+
The core still carries an extension-lifecycle-control axis — `isolated`, `extensions: false`, and `noSkills` — that lets a spawn blanket-disable a child's extensions and skills.
|
|
11
|
+
Per ADR 0002, this is policy that does not belong in a minimal orchestrator.
|
|
12
|
+
Deny-at-use (the in-child permission layer, shipped in Step 1 / #261) already covers what `isolated` pretended to do for tools.
|
|
13
|
+
Prevent-load (refusing to bind an extension for true sandboxing) is genuinely generative and is deliberately left as a *latent, un-built* provider seam — we do not ship a vacant hook.
|
|
14
|
+
|
|
15
|
+
This is Phase 16, Step 4.
|
|
16
|
+
With the axis gone, children always load the parent's extensions and skills, and the recursion guard — which currently gates on `cfg.extensions` — becomes unconditional.
|
|
17
|
+
|
|
18
|
+
## Goals
|
|
19
|
+
|
|
20
|
+
- Remove `isolated` from the spawn API, `SubagentsService`, the lifecycle plumbing, and the config assembler.
|
|
21
|
+
- Remove the `extensions` boolean from `AgentConfig` and the assembler (children always inherit extensions).
|
|
22
|
+
- Remove `noSkills` from the assembler and the resource-loader options.
|
|
23
|
+
- Collapse the skill-curation axis symmetrically: remove `AgentConfig.skills` and the skill-**preload** path (`skill-loader.ts`, `safe-fs.ts`, `preloadSkills`, `PromptExtras`, `extras.skillBlocks`).
|
|
24
|
+
Children always load Pi's full skill system, exactly as `skills: true` does today.
|
|
25
|
+
- Make the recursion guard unconditional — it always strips `subagent` / `get_subagent_result` / `steer_subagent` from children, keyed off the core's own tool names.
|
|
26
|
+
- This is a **breaking** change: the public `SpawnOptions.isolated` field and the `isolated:` / `extensions:` / `skills:` custom-agent frontmatter keys are removed.
|
|
27
|
+
Suggested commits use `feat!:`.
|
|
28
|
+
|
|
29
|
+
## Non-Goals
|
|
30
|
+
|
|
31
|
+
- Born-complete child execution / dissolving the runner — that is Step 5 (#265) and depends on this step.
|
|
32
|
+
`RunOptions`, `runAgent`, `ConcreteAgentRunner`, and `Agent.run()` survive this issue (with `isolated` removed from them).
|
|
33
|
+
- Shipping a prevent-load provider seam — ADR 0002 leaves it latent until a real consumer needs it.
|
|
34
|
+
- Changing `builtinToolNames` (the `tools:` frontmatter allowlist) — that is a separate, surviving concern.
|
|
35
|
+
- Changing deny-at-use behavior in `@gotgenes/pi-permission-system` — already in place from #261.
|
|
36
|
+
|
|
37
|
+
## Background
|
|
38
|
+
|
|
39
|
+
Relevant modules and how they relate:
|
|
40
|
+
|
|
41
|
+
- `src/types.ts` — declares `AgentConfig` (`extensions`, `skills`, `isolated`) and `AgentInvocation` (`isolated`).
|
|
42
|
+
- `src/config/default-agents.ts` — the three embedded agents set `extensions: true`, `skills: true`.
|
|
43
|
+
- `src/config/custom-agents.ts` — parses `extensions` / `skills` / `isolated` from `.md` frontmatter via `resolveBoolExtensions` and `inheritField`.
|
|
44
|
+
- `src/config/invocation-config.ts` — merges `isolated` from agent config + tool params.
|
|
45
|
+
- `src/config/agent-types.ts` — an absolute-fallback `AgentConfig` literal sets `extensions: true`, `skills: true`.
|
|
46
|
+
- `src/session/session-config.ts` — `assembleSessionConfig` derives `extensions`/`skills` from `options.isolated`, calls `io.preloadSkills` when `skills` is `string[]`, and computes `noSkills`.
|
|
47
|
+
Returns `SessionConfig.{ extensions, noSkills, extras }`.
|
|
48
|
+
- `src/session/prompts.ts` — `buildAgentPrompt` injects `extras.skillBlocks` as `# Preloaded Skill:` sections; `PromptExtras` carries only `skillBlocks` (memory was removed in #185).
|
|
49
|
+
- `src/session/skill-loader.ts` — `preloadSkills` reads named skill files from disk; consumes `safe-fs.ts`.
|
|
50
|
+
- `src/session/safe-fs.ts` — symlink/path-traversal guards; **only consumer is `skill-loader.ts`**.
|
|
51
|
+
- `src/lifecycle/agent-runner.ts` — `RunOptions.isolated` flows into the assembler; `createResourceLoader` is called with `noExtensions: !cfg.extensions` and `noSkills: cfg.noSkills`; the recursion guard runs only `if (cfg.extensions)`.
|
|
52
|
+
`ResourceLoaderOptions` (a local narrow interface, not the SDK type) declares `noExtensions?` / `noSkills?`.
|
|
53
|
+
- `src/lifecycle/agent.ts` — `AgentInit.isolated`, the `_isolated` field, and `isolated:` in the `runner.run(...)` call.
|
|
54
|
+
- `src/lifecycle/agent-manager.ts` — `AgentSpawnConfig.isolated` and its pass-through to `new Agent(...)`.
|
|
55
|
+
- `src/tools/{agent-tool,spawn-config,foreground-runner,background-spawner}.ts` — tool schema `isolated`, `SpawnExecution.isolated`, and pass-through.
|
|
56
|
+
- `src/service/{service,service-adapter}.ts` — public `SpawnOptions.isolated` and its pass-through to the manager.
|
|
57
|
+
- `src/ui/{display,agent-config-editor,agent-creation-wizard}.ts` — `isolated` tag, eject-content emission, and generation-prompt template text.
|
|
58
|
+
- `src/index.ts` — wires `preloadSkills` and `buildAgentPrompt` into `assemblerIO`.
|
|
59
|
+
|
|
60
|
+
AGENTS.md constraints that apply:
|
|
61
|
+
|
|
62
|
+
- Conventional Commits; breaking changes use `feat!:`.
|
|
63
|
+
- Do not edit `CHANGELOG.md` (release-please owns it).
|
|
64
|
+
- When removing an export, grep all `src/` and `test/` for the symbol before finalizing (done below).
|
|
65
|
+
- When adding/removing a module, check `docs/architecture/` for layout listings and complexity tables that reference it (done below — Mermaid + tree + field tables).
|
|
66
|
+
- One-sentence-per-line markdown; sequential numbering restarting per heading.
|
|
67
|
+
|
|
68
|
+
## Design Overview
|
|
69
|
+
|
|
70
|
+
This is a **symmetric collapse**, not a refactor.
|
|
71
|
+
Two parallel axes leave the core together:
|
|
72
|
+
|
|
73
|
+
| Axis | Today | After |
|
|
74
|
+
| ---------- | -------------------------------------------------------------- | --------------------------------------------- |
|
|
75
|
+
| Extensions | `extensions: true \| false`; `isolated` forces `false` | always inherit (field gone) |
|
|
76
|
+
| Skills | `skills: true \| string[] \| false`; `isolated` forces `false` | always inherit full skill system (field gone) |
|
|
77
|
+
|
|
78
|
+
The `skills` field collapses for the same reason `extensions` does: `noSkills` is the single mechanism behind **both** restriction modes (`skills: false` → no skills; `skills: string[]` → only those, preloaded into the prompt with the SDK loader suppressed).
|
|
79
|
+
Removing `noSkills` without removing `AgentConfig.skills` would leave a field that silently stops restricting — a `string[]` agent would get its baked-in skills *plus* the full system.
|
|
80
|
+
ADR 0002 says children always load the parent's skills, which is exactly the `skills: true` path; the other two values are skill curation/policy and leave the core.
|
|
81
|
+
|
|
82
|
+
### Assembler after collapse
|
|
83
|
+
|
|
84
|
+
`assembleSessionConfig` loses the `isolated` branch, the `preloadSkills` block, and the `noSkills`/`extensions`/`extras` outputs:
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
export interface AssemblerOptions {
|
|
88
|
+
cwd?: string;
|
|
89
|
+
model?: unknown;
|
|
90
|
+
thinkingLevel?: ThinkingLevel;
|
|
91
|
+
// isolated removed
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface SessionConfig {
|
|
95
|
+
effectiveCwd: string;
|
|
96
|
+
systemPrompt: string;
|
|
97
|
+
toolNames: string[];
|
|
98
|
+
model: unknown;
|
|
99
|
+
thinkingLevel: ThinkingLevel | undefined;
|
|
100
|
+
agentMaxTurns: number | undefined;
|
|
101
|
+
// extensions, noSkills, extras removed
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`AssemblerIO` drops `preloadSkills`, keeping only `buildAgentPrompt` (now called without `extras`).
|
|
106
|
+
|
|
107
|
+
### Runner after collapse
|
|
108
|
+
|
|
109
|
+
The resource-loader call drops the two suppression flags, and the guard runs unconditionally:
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
const loader = deps.io.createResourceLoader({
|
|
113
|
+
cwd: cfg.effectiveCwd,
|
|
114
|
+
agentDir,
|
|
115
|
+
noPromptTemplates: true,
|
|
116
|
+
noThemes: true,
|
|
117
|
+
noContextFiles: true,
|
|
118
|
+
systemPromptOverride: () => cfg.systemPrompt,
|
|
119
|
+
appendSystemPromptOverride: () => [],
|
|
120
|
+
// noExtensions, noSkills removed
|
|
121
|
+
});
|
|
122
|
+
// ...
|
|
123
|
+
await session.bindExtensions({});
|
|
124
|
+
// Recursion guard — now unconditional (children always load extensions).
|
|
125
|
+
const filtered = filterActiveTools(session.getActiveToolNames());
|
|
126
|
+
session.setActiveToolsByName(filtered);
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
`ResourceLoaderOptions` drops `noExtensions?` / `noSkills?` (a local narrow interface — removing the latent fields keeps us honest per ADR 0002's "no vacant hooks").
|
|
130
|
+
|
|
131
|
+
### Call-site sketch — recursion guard (Tell-Don't-Ask check)
|
|
132
|
+
|
|
133
|
+
The guard already asks the session for its active tools and tells it the filtered set — unchanged except for removing the `if`.
|
|
134
|
+
No new collaborator, no reach-through; the only structural change is that `cfg.extensions` is no longer consulted, so `SessionConfig` no longer needs to expose it.
|
|
135
|
+
This is a narrowing of the assembler's output contract, which is the desired direction.
|
|
136
|
+
|
|
137
|
+
### Edge cases
|
|
138
|
+
|
|
139
|
+
- Append-mode agents: `parentSystemPrompt` still embeds parent context via `systemPromptOverride`; nothing about that path depends on skills/extensions flags.
|
|
140
|
+
- Custom agents with legacy `extensions:` / `skills:` / `isolated:` frontmatter: the keys are silently ignored after this change (no parse, no warning).
|
|
141
|
+
`resolveBoolExtensions` already warned on the deprecated allowlist syntax; that warning path is removed with the parser.
|
|
142
|
+
- The absolute-fallback `AgentConfig` in `agent-types.ts` drops `extensions`/`skills` like every other construction site.
|
|
143
|
+
|
|
144
|
+
## Module-Level Changes
|
|
145
|
+
|
|
146
|
+
### Source — `isolated` axis
|
|
147
|
+
|
|
148
|
+
- `src/types.ts` — remove `AgentConfig.isolated`, `AgentInvocation.isolated`.
|
|
149
|
+
- `src/config/invocation-config.ts` — remove `isolated` from `AgentInvocationParams` and the return object.
|
|
150
|
+
- `src/config/custom-agents.ts` — remove the `isolated:` frontmatter parse.
|
|
151
|
+
- `src/session/session-config.ts` — remove `AssemblerOptions.isolated` and the `options.isolated ? false : ...` derivation (read `agentConfig.extensions`/`agentConfig.skills` directly, for now).
|
|
152
|
+
- `src/lifecycle/agent-runner.ts` — remove `RunOptions.isolated` and the `isolated:` argument passed to `assembleSessionConfig`.
|
|
153
|
+
- `src/lifecycle/agent.ts` — remove `AgentInit.isolated`, the `_isolated` field + constructor assignment, and `isolated:` in the `runner.run(...)` call.
|
|
154
|
+
- `src/lifecycle/agent-manager.ts` — remove `AgentSpawnConfig.isolated` and its pass-through to `new Agent(...)`.
|
|
155
|
+
- `src/tools/agent-tool.ts` — remove the `isolated` schema property.
|
|
156
|
+
- `src/tools/spawn-config.ts` — remove `SpawnExecution.isolated`, the `const isolated = resolvedConfig.isolated`, `isolated` in `agentInvocation`, and `isolated` in the `execution` return.
|
|
157
|
+
- `src/tools/foreground-runner.ts` / `src/tools/background-spawner.ts` — remove `isolated: execution.isolated`.
|
|
158
|
+
- `src/service/service.ts` — remove `SpawnOptions.isolated`.
|
|
159
|
+
- `src/service/service-adapter.ts` — remove `isolated: options?.isolated` from the `manager.spawn(...)` call.
|
|
160
|
+
- `src/ui/display.ts` — remove `if (invocation.isolated) tags.push("isolated")` and the `isolated` mention in the JSDoc example.
|
|
161
|
+
- `src/ui/agent-config-editor.ts` — remove the `isolated: true` line from `buildEjectContent`.
|
|
162
|
+
- `src/ui/agent-creation-wizard.ts` — remove the `isolated:` template line and the "Set isolated: true …" guideline.
|
|
163
|
+
|
|
164
|
+
### Source — `extensions` axis + unconditional guard
|
|
165
|
+
|
|
166
|
+
- `src/types.ts` — remove `AgentConfig.extensions`.
|
|
167
|
+
- `src/config/default-agents.ts` — remove `extensions: true` from all three agents.
|
|
168
|
+
- `src/config/agent-types.ts` — remove `extensions: true` from the absolute-fallback literal.
|
|
169
|
+
- `src/config/custom-agents.ts` — remove the `extensions:` frontmatter parse and delete `resolveBoolExtensions`.
|
|
170
|
+
- `src/session/session-config.ts` — remove `SessionConfig.extensions` and stop assigning it.
|
|
171
|
+
- `src/lifecycle/agent-runner.ts` — remove `ResourceLoaderOptions.noExtensions`, drop `noExtensions` from the `createResourceLoader` call, and make the recursion guard unconditional (delete `if (cfg.extensions)`); update the explanatory comment.
|
|
172
|
+
- `src/ui/agent-config-editor.ts` — remove the `extensions: false` line from `buildEjectContent`.
|
|
173
|
+
- `src/ui/agent-creation-wizard.ts` — remove the `extensions:` template line.
|
|
174
|
+
|
|
175
|
+
### Source — `skills` axis + preload path
|
|
176
|
+
|
|
177
|
+
- `src/types.ts` — remove `AgentConfig.skills`.
|
|
178
|
+
- `src/config/default-agents.ts` — remove `skills: true` from all three agents.
|
|
179
|
+
- `src/config/agent-types.ts` — remove `skills: true` from the absolute-fallback literal.
|
|
180
|
+
- `src/config/custom-agents.ts` — remove the `skills:` frontmatter parse and delete `inheritField` (its only remaining callers are `skills`/`extensions`; `csvList`, `parseCsvField`, `str`, `nonNegativeInt` stay — `csvList` still serves `tools:`).
|
|
181
|
+
- `src/session/session-config.ts` — remove `AssemblerIO.preloadSkills`, `SessionConfig.noSkills`, `SessionConfig.extras`, the `extras`/`preloadSkills` block, and the `extras` argument to `buildAgentPrompt`.
|
|
182
|
+
- `src/session/prompts.ts` — remove `PromptExtras`, the `extras` parameter, and the `extrasSuffix` logic.
|
|
183
|
+
- `src/session/skill-loader.ts` — **delete** (export `preloadSkills`, `PreloadedSkill`).
|
|
184
|
+
- `src/session/safe-fs.ts` — **delete** (sole consumer was `skill-loader.ts`).
|
|
185
|
+
- `src/lifecycle/agent-runner.ts` — remove `ResourceLoaderOptions.noSkills` and drop `noSkills` from the `createResourceLoader` call.
|
|
186
|
+
- `src/index.ts` — remove the `preloadSkills` import and its `assemblerIO.preloadSkills` wiring.
|
|
187
|
+
- `src/ui/agent-config-editor.ts` — remove the `skills: false` / `skills: <list>` lines from `buildEjectContent`.
|
|
188
|
+
- `src/ui/agent-creation-wizard.ts` — remove the `skills:` template line.
|
|
189
|
+
|
|
190
|
+
### Tests
|
|
191
|
+
|
|
192
|
+
- `test/session/skill-loader.test.ts` — **delete**.
|
|
193
|
+
- `test/session/safe-fs.test.ts` — **delete**.
|
|
194
|
+
- `test/session/session-config.test.ts` — remove the `isolated`-mode `describe`, the `noSkills` assertions, and the preload tests; keep model-resolution and prompt-assembly assertions.
|
|
195
|
+
- `test/session/prompts.test.ts` — remove `isolated`/`extensions`/`skills` from `AgentConfig` fixtures and any `skillBlocks`/`extras` cases.
|
|
196
|
+
- `test/config/invocation-config.test.ts` — remove `isolated` cases and fixture fields.
|
|
197
|
+
- `test/config/custom-agents.test.ts` — remove `extensions` / `skills` / `isolated` frontmatter-parsing tests.
|
|
198
|
+
- `test/config/agent-types.test.ts` — remove `extensions` / `skills` / `isolated` from fixtures.
|
|
199
|
+
- `test/lifecycle/agent-runner-extension-tools.test.ts` — remove `extensions`/`skills`/`isolated` from the mock config; delete the "extensions: false skips the filter entirely" test; keep/adjust the post-bind guard tests to assert the guard runs unconditionally.
|
|
200
|
+
- `test/tools/spawn-config.test.ts` — remove the "sets isolated from params" test and `isolated` fixture fields.
|
|
201
|
+
- `test/tools/background-spawner.test.ts` / `test/tools/foreground-runner.test.ts` — remove `isolated` from fixtures and `agentInvocation` assertions.
|
|
202
|
+
- `test/tools/result-renderer.test.ts` — remove the `"isolated"` tag case.
|
|
203
|
+
- `test/ui/agent-config-editor.test.ts` — remove the `isolated` / `extensions: false` eject-emission tests.
|
|
204
|
+
- `test/display.test.ts` — remove `extensions`/`skills` from `AgentConfig` fixtures.
|
|
205
|
+
- `test/helpers/runner-io.ts` — remove `extensions`/`skills`/`isolated` from `DEFAULT_AGENT_CONFIG`.
|
|
206
|
+
- `test/helpers/runner-io.test.ts` — remove the `config.extensions`/`config.skills` assertions and the `extensions: true` override case.
|
|
207
|
+
- `test/helpers/ui-stubs.ts` — remove `extensions: true` / `skills: true` from the stub `AgentConfig`.
|
|
208
|
+
|
|
209
|
+
### Docs
|
|
210
|
+
|
|
211
|
+
- `docs/architecture/architecture.md` —
|
|
212
|
+
remove the `SafeFs` and `SkillLoader` nodes from the session-domain Mermaid subgraph;
|
|
213
|
+
remove `safe-fs.ts` and `skill-loader.ts` from the directory-tree listing;
|
|
214
|
+
drop `isolated` from the `SpawnOptions` field list (≈ line 418) and the `RunOptions` field list (≈ line 651);
|
|
215
|
+
mark Phase 16 Step 4 (#264) done in the roadmap and update the "Children always load the parent's extensions and skills" note;
|
|
216
|
+
reflect the smaller session domain (8 → 6 modules).
|
|
217
|
+
- `.pi/skills/package-pi-subagents/SKILL.md` — update the Session domain row (module count 8 → 6, drop `safe-fs.ts` / `skill-loader.ts` from the file list).
|
|
218
|
+
|
|
219
|
+
## Test Impact Analysis
|
|
220
|
+
|
|
221
|
+
1. New coverage enabled — minimal (this is a removal).
|
|
222
|
+
The one behavioral assertion worth strengthening: the post-bind recursion guard now runs **unconditionally**.
|
|
223
|
+
Update `agent-runner-extension-tools.test.ts` so a case that previously relied on `extensions: true` instead asserts the guard always calls `setActiveToolsByName` and always excludes `EXCLUDED_TOOL_NAMES`, with no config dependence.
|
|
224
|
+
2. Tests that become redundant and are removed —
|
|
225
|
+
`skill-loader.test.ts` and `safe-fs.test.ts` (modules deleted);
|
|
226
|
+
the `isolated`-mode `describe` in `session-config.test.ts`;
|
|
227
|
+
the "extensions: false skips the filter entirely" case;
|
|
228
|
+
`isolated` parameter tests in `spawn-config.test.ts` / `invocation-config.test.ts`;
|
|
229
|
+
`extensions` / `skills` frontmatter-parsing tests in `custom-agents.test.ts`.
|
|
230
|
+
3. Tests that must stay (genuinely exercise surviving layers) —
|
|
231
|
+
prompt assembly (`prompts.test.ts`, minus `extras`);
|
|
232
|
+
model resolution in `session-config.test.ts`;
|
|
233
|
+
the post-bind guard ordering tests (adjusted, not removed);
|
|
234
|
+
custom-agent `tools:` parsing (`csvList` survives).
|
|
235
|
+
|
|
236
|
+
## TDD Order
|
|
237
|
+
|
|
238
|
+
Each cycle ends green (`pnpm run check` + `pnpm -r run test`).
|
|
239
|
+
Because removing a field from `AgentConfig` / `AgentInvocation` breaks every reader and every object-literal construction site at once (TS excess-property + property-access), each cycle folds its test updates into the same commit (per the testing skill's removal rule).
|
|
240
|
+
The three axes are split so each commit stays reviewable and leaves the repo compiling.
|
|
241
|
+
|
|
242
|
+
1. Remove the `isolated` axis end-to-end.
|
|
243
|
+
Surface: `types.ts` (`AgentConfig.isolated`, `AgentInvocation.isolated`), `invocation-config.ts`, `custom-agents.ts` (parse only), `session-config.ts` (`AssemblerOptions.isolated` + derivation), `agent-runner.ts` (`RunOptions.isolated`), `agent.ts`, `agent-manager.ts`, `tools/*`, `service*.ts`, `ui/*` (isolated bits), plus all listed tests.
|
|
244
|
+
After: `extensions`/`skills`/`noSkills` still function; the assembler reads `agentConfig.extensions`/`agentConfig.skills` directly.
|
|
245
|
+
Commit: `feat!: remove isolated from the subagent spawn API and lifecycle (#264)`.
|
|
246
|
+
2. Remove the `extensions` axis; make the recursion guard unconditional.
|
|
247
|
+
Surface: `types.ts` (`AgentConfig.extensions`), `default-agents.ts`, `agent-types.ts` (fallback), `custom-agents.ts` (parse + delete `resolveBoolExtensions`), `session-config.ts` (`SessionConfig.extensions`), `agent-runner.ts` (`ResourceLoaderOptions.noExtensions`, drop `noExtensions` arg, unconditional guard), `ui/agent-config-editor.ts` + `ui/agent-creation-wizard.ts` (extensions bits), plus tests (including the guard-always-runs update and deleting the "skips filter" case).
|
|
248
|
+
Commit: `feat!: always inherit extensions; make the recursion guard unconditional (#264)`.
|
|
249
|
+
3. Remove the `skills` axis, `noSkills`, and the skill-preload path.
|
|
250
|
+
Surface: `types.ts` (`AgentConfig.skills`), `default-agents.ts`, `agent-types.ts` (fallback), `custom-agents.ts` (parse + delete `inheritField`), `session-config.ts` (`AssemblerIO.preloadSkills`, `SessionConfig.noSkills`/`extras`, preload block, `buildAgentPrompt` call), `prompts.ts` (`PromptExtras`/`extras`/`extrasSuffix`), delete `skill-loader.ts` + `safe-fs.ts`, `agent-runner.ts` (`ResourceLoaderOptions.noSkills`, drop arg), `index.ts` (wiring), `ui/*` (skills bits), plus tests (delete `skill-loader.test.ts` + `safe-fs.test.ts`).
|
|
251
|
+
Commit: `feat!: always inherit skills; remove noSkills and the skill-preload path (#264)`.
|
|
252
|
+
4. Update the architecture doc and package skill.
|
|
253
|
+
Surface: `docs/architecture/architecture.md` (Mermaid session subgraph, directory tree, `SpawnOptions`/`RunOptions` field lists, roadmap status), `.pi/skills/package-pi-subagents/SKILL.md` (session domain row).
|
|
254
|
+
Commit: `docs: record removal of the extension-lifecycle-control axis (#264)`.
|
|
255
|
+
|
|
256
|
+
## Risks and Mitigations
|
|
257
|
+
|
|
258
|
+
- Risk: `resolveBoolExtensions` / `inheritField` deletion removes a helper still used elsewhere.
|
|
259
|
+
Mitigation: greps confirm `resolveBoolExtensions` is used only by the removed `extensions` parse, and `inheritField` only by `extensions`/`skills`; `csvList`/`parseCsvField` (used by `tools:`) stay.
|
|
260
|
+
- Risk: deleting `safe-fs.ts` orphans an import.
|
|
261
|
+
Mitigation: grep confirms `skill-loader.ts` is its sole consumer; `safe-fs.test.ts` is deleted in the same cycle.
|
|
262
|
+
- Risk: removing `SpawnOptions.isolated` breaks a published consumer of the service.
|
|
263
|
+
Mitigation: this is an intentional breaking change (ADR 0002); `feat!:` triggers a major bump via release-please.
|
|
264
|
+
The public type surface is verified by `pnpm run verify:public-types` after Step 3.
|
|
265
|
+
- Risk: skill behavior silently changes for agents that relied on `skills: string[]` curation.
|
|
266
|
+
Mitigation: documented in Goals as breaking; children now inherit the full skill system, which is strictly more capable, and deny-at-use governs what they may act on.
|
|
267
|
+
- Risk: a test file is touched in multiple cycles (e.g. `runner-io.ts`).
|
|
268
|
+
Mitigation: each cycle removes only the field it owns; ordering is fixed (isolated → extensions → skills) so no cycle leaves a dangling reference.
|
|
269
|
+
|
|
270
|
+
## Open Questions
|
|
271
|
+
|
|
272
|
+
- Should custom-agent `.md` files with now-defunct `extensions:` / `skills:` / `isolated:` frontmatter emit a one-time deprecation warning, or be silently ignored?
|
|
273
|
+
Deferred: silent-ignore matches the Phase 14 precedent for the removed `disallowed_tools` field; revisit only if users report confusion.
|
|
274
|
+
- Does `verify:public-types` need a new negative assertion that `isolated` is absent from `SpawnOptions`?
|
|
275
|
+
Deferred to Step 3 implementation — the existing consumer type-check will fail if a stale field lingers.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 264
|
|
3
|
+
issue_title: "Remove isolated / extensions:false / noSkills from core"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Retro: #264 — Remove isolated / extensions:false / noSkills from core
|
|
7
|
+
|
|
8
|
+
## Stage: Planning (2026-05-30T00:09:21Z)
|
|
9
|
+
|
|
10
|
+
### Session summary
|
|
11
|
+
|
|
12
|
+
Planned Phase 16, Step 4: removing the extension-lifecycle-control axis (`isolated`, `extensions: false`, `noSkills`) from the pi-subagents core per ADR 0002.
|
|
13
|
+
Confirmed all three prerequisite Phase 16 steps (#261, #262, #263) are closed, so the explicit "deny-at-use" dependency is satisfied.
|
|
14
|
+
Produced a four-cycle TDD plan (`isolated` → `extensions` → `skills`/`noSkills`/preload → docs) and committed it.
|
|
15
|
+
|
|
16
|
+
### Observations
|
|
17
|
+
|
|
18
|
+
- Scope expansion decided with the user: the issue names only `isolated` / `extensions: false` / `noSkills`, but `noSkills` is the single mechanism behind **both** skill-restriction modes (`skills: false` and `skills: string[]` preload).
|
|
19
|
+
Removing `noSkills` without also removing `AgentConfig.skills` would leave a field that silently stops restricting.
|
|
20
|
+
Chose the **collapse-skills-fully** option (symmetric with `extensions`): retire `AgentConfig.skills`, `skill-loader.ts`, `safe-fs.ts` (sole consumer was the skill loader), `preloadSkills`, `PromptExtras`, and `extras.skillBlocks`.
|
|
21
|
+
Children always inherit Pi's full skill system — the `skills: true` path.
|
|
22
|
+
- The recursion guard's `if (cfg.extensions)` gate is removed in the `extensions` cycle (cycle 2), since `SessionConfig.extensions` disappears there.
|
|
23
|
+
A guard-always-runs assertion replaces the deleted "extensions: false skips the filter entirely" test.
|
|
24
|
+
- This is a **breaking** change (`feat!:`): public `SpawnOptions.isolated` and the `isolated:` / `extensions:` / `skills:` custom-agent frontmatter keys are removed.
|
|
25
|
+
Custom agents with legacy frontmatter will silently ignore those keys (matches the Phase 14 precedent for `disallowed_tools`).
|
|
26
|
+
- Sequencing note surfaced to the user: some `isolated`-threading removed here (`RunOptions.isolated`, `Agent.run()` plumbing) is structure that Step 5 (#265, dissolve the runner) will delete anyway — small, unavoidable, and #265 depends on this step, so no reordering benefit.
|
|
27
|
+
- Helper-file churn accepted: `test/helpers/runner-io.ts` is touched in all three removal cycles (one field per cycle); ordering is fixed (`isolated` → `extensions` → `skills`) so no cycle leaves a dangling reference.
|
|
28
|
+
- Doc updates identified: `docs/architecture/architecture.md` (Mermaid session subgraph, directory tree, `SpawnOptions`/`RunOptions` field lists, roadmap status) and the `package-pi-subagents` SKILL.md session-domain row (8 → 6 modules).
|
|
29
|
+
|
|
30
|
+
## Stage: Implementation — TDD (2026-05-30T00:38:00Z)
|
|
31
|
+
|
|
32
|
+
### Session summary
|
|
33
|
+
|
|
34
|
+
Executed all four plan cycles (`isolated` → `extensions` + unconditional guard → `skills`/`noSkills`/preload → docs) as three `feat!:` commits plus one `docs:` commit.
|
|
35
|
+
The extension-lifecycle-control axis and the `skills` curation axis are gone; children always inherit the parent's extensions and full skill system, and the recursion guard is unconditional.
|
|
36
|
+
Test count went 1016 → 951 (−65): deleted `skill-loader.test.ts` and `safe-fs.test.ts`, plus removed isolated/extensions/skills/preload-specific cases; `check`, `lint`, `fallow dead-code`, and `verify:public-types` all green.
|
|
37
|
+
|
|
38
|
+
### Observations
|
|
39
|
+
|
|
40
|
+
- The plan's cycle split held up cleanly.
|
|
41
|
+
The only interleaving friction was `test/config/custom-agents.test.ts`, where `extensions` and `skills` assertions shared the same `it` blocks.
|
|
42
|
+
Handled it by retitling the shared tests to skills-only in cycle 2 (keeping skills compiling/passing), then deleting them in cycle 3 — no dangling references between commits.
|
|
43
|
+
- BSD `sed` (macOS) does not support `\|` alternation in basic regex; the standalone-fixture-line deletions needed `sed -E`.
|
|
44
|
+
Worth remembering for future bulk fixture removals.
|
|
45
|
+
- Two in-scope judgment calls beyond the literal plan: (1) kept the generic tag-rendering test in `result-renderer.test.ts` by swapping the example tag `"isolated"` → `"inherit context"` rather than deleting coverage; (2) removed the now-unused `vi` import from `custom-agents.test.ts` after dropping the extensions-deprecation-warning test.
|
|
46
|
+
- The `spawn-config.test.ts` `agentInvocation` snapshot carried a stale `isolation: undefined` leftover (from the #263 worktree eviction) that `toEqual` had been silently ignoring; removed it alongside `isolated: false` for a clean exact-match assertion.
|
|
47
|
+
- `verify:public-types` confirmed the breaking `SpawnOptions.isolated` removal type-checks against an external consumer; no lockfile changes; `dist/` correctly gitignored after the type-bundle build.
|
|
48
|
+
- Pre-completion reviewer: **PASS** — both acceptance criteria code-verified, all deterministic checks green, 6 Mermaid diagrams render, docs accurate, zero dead code.
|
package/package.json
CHANGED
|
@@ -114,8 +114,6 @@ export class AgentTypeRegistry implements AgentConfigLookup {
|
|
|
114
114
|
displayName: "Agent",
|
|
115
115
|
description: "General-purpose agent for complex, multi-step tasks",
|
|
116
116
|
builtinToolNames: BUILTIN_TOOL_NAMES,
|
|
117
|
-
extensions: true,
|
|
118
|
-
skills: true,
|
|
119
117
|
systemPrompt: "",
|
|
120
118
|
promptMode: "append",
|
|
121
119
|
};
|
|
@@ -58,8 +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
|
-
extensions: resolveBoolExtensions(fm.extensions ?? fm.inherit_extensions),
|
|
62
|
-
skills: inheritField(fm.skills ?? fm.inherit_skills),
|
|
63
61
|
model: str(fm.model),
|
|
64
62
|
thinking: str(fm.thinking) as ThinkingLevel | undefined,
|
|
65
63
|
maxTurns: nonNegativeInt(fm.max_turns),
|
|
@@ -67,7 +65,6 @@ function loadFromDir(dir: string, agents: Map<string, AgentConfig>, source: "pro
|
|
|
67
65
|
promptMode: fm.prompt_mode === "append" ? "append" : "replace",
|
|
68
66
|
inheritContext: fm.inherit_context != null ? fm.inherit_context === true : undefined,
|
|
69
67
|
runInBackground: fm.run_in_background != null ? fm.run_in_background === true : undefined,
|
|
70
|
-
isolated: fm.isolated != null ? fm.isolated === true : undefined,
|
|
71
68
|
enabled: fm.enabled !== false, // default true; explicitly false disables
|
|
72
69
|
source,
|
|
73
70
|
});
|
|
@@ -107,30 +104,3 @@ function csvList(val: unknown, defaults: string[]): string[] {
|
|
|
107
104
|
if (val === undefined || val === null) return defaults;
|
|
108
105
|
return parseCsvField(val) ?? [];
|
|
109
106
|
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Resolve the `extensions` field to a boolean.
|
|
113
|
-
* CSV/array values (legacy allowlist syntax) are coerced to `true` with a warning.
|
|
114
|
-
*/
|
|
115
|
-
function resolveBoolExtensions(val: unknown): boolean {
|
|
116
|
-
const result = inheritField(val);
|
|
117
|
-
if (Array.isArray(result)) {
|
|
118
|
-
console.warn(
|
|
119
|
-
"[pi-subagents] extensions allowlist syntax is deprecated — treating as \"true\" (inherit all).\n" +
|
|
120
|
-
"Use \"permission:\" frontmatter in pi-permission-system for per-tool access control.",
|
|
121
|
-
);
|
|
122
|
-
return true;
|
|
123
|
-
}
|
|
124
|
-
return result;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Parse an inherit field (extensions, skills).
|
|
129
|
-
* omitted/true → true (inherit all); false/"none"/empty → false; csv → listed names.
|
|
130
|
-
*/
|
|
131
|
-
function inheritField(val: unknown): true | string[] | false {
|
|
132
|
-
if (val === undefined || val === null || val === true) return true;
|
|
133
|
-
if (val === false || val === "none") return false;
|
|
134
|
-
const items = csvList(val, []);
|
|
135
|
-
return items.length > 0 ? items : false;
|
|
136
|
-
}
|
|
@@ -16,10 +16,8 @@ export const DEFAULT_AGENTS: Map<string, AgentConfig> = new Map([
|
|
|
16
16
|
displayName: "Agent",
|
|
17
17
|
description: "General-purpose agent for complex, multi-step tasks",
|
|
18
18
|
// builtinToolNames omitted — means "all available tools" (resolved at lookup time)
|
|
19
|
-
// inheritContext / runInBackground
|
|
19
|
+
// inheritContext / runInBackground omitted — strategy fields, callers decide per-call.
|
|
20
20
|
// Setting them to false would lock callsite intent (see resolveAgentInvocationConfig in invocation-config.ts).
|
|
21
|
-
extensions: true,
|
|
22
|
-
skills: true,
|
|
23
21
|
systemPrompt: "",
|
|
24
22
|
promptMode: "append",
|
|
25
23
|
isDefault: true,
|
|
@@ -32,8 +30,6 @@ export const DEFAULT_AGENTS: Map<string, AgentConfig> = new Map([
|
|
|
32
30
|
displayName: "Explore",
|
|
33
31
|
description: "Fast codebase exploration agent (read-only)",
|
|
34
32
|
builtinToolNames: READ_ONLY_TOOLS,
|
|
35
|
-
extensions: true,
|
|
36
|
-
skills: true,
|
|
37
33
|
model: "anthropic/claude-haiku-4-5-20251001",
|
|
38
34
|
systemPrompt: `# CRITICAL: READ-ONLY MODE - NO FILE MODIFICATIONS
|
|
39
35
|
You are a file search specialist. You excel at thoroughly navigating and exploring codebases.
|
|
@@ -74,8 +70,6 @@ Use Bash ONLY for read-only operations: ls, git status, git log, git diff, find,
|
|
|
74
70
|
displayName: "Plan",
|
|
75
71
|
description: "Software architect for implementation planning (read-only)",
|
|
76
72
|
builtinToolNames: READ_ONLY_TOOLS,
|
|
77
|
-
extensions: true,
|
|
78
|
-
skills: true,
|
|
79
73
|
systemPrompt: `# CRITICAL: READ-ONLY MODE - NO FILE MODIFICATIONS
|
|
80
74
|
You are a software architect and planning specialist.
|
|
81
75
|
Your role is EXCLUSIVELY to explore the codebase and design implementation plans.
|
|
@@ -6,7 +6,6 @@ interface AgentInvocationParams {
|
|
|
6
6
|
max_turns?: number;
|
|
7
7
|
run_in_background?: boolean;
|
|
8
8
|
inherit_context?: boolean;
|
|
9
|
-
isolated?: boolean;
|
|
10
9
|
}
|
|
11
10
|
|
|
12
11
|
export function resolveAgentInvocationConfig(
|
|
@@ -19,7 +18,6 @@ export function resolveAgentInvocationConfig(
|
|
|
19
18
|
maxTurns?: number;
|
|
20
19
|
inheritContext: boolean;
|
|
21
20
|
runInBackground: boolean;
|
|
22
|
-
isolated: boolean;
|
|
23
21
|
} {
|
|
24
22
|
return {
|
|
25
23
|
modelInput: agentConfig?.model ?? params.model,
|
|
@@ -28,6 +26,5 @@ export function resolveAgentInvocationConfig(
|
|
|
28
26
|
maxTurns: agentConfig?.maxTurns ?? params.max_turns,
|
|
29
27
|
inheritContext: agentConfig?.inheritContext ?? params.inherit_context ?? false,
|
|
30
28
|
runInBackground: agentConfig?.runInBackground ?? params.run_in_background ?? false,
|
|
31
|
-
isolated: agentConfig?.isolated ?? params.isolated ?? false,
|
|
32
29
|
};
|
|
33
30
|
}
|
package/src/index.ts
CHANGED
|
@@ -38,7 +38,6 @@ import { detectEnv } from "#src/session/env";
|
|
|
38
38
|
import { resolveModel } from "#src/session/model-resolver";
|
|
39
39
|
import { buildAgentPrompt } from "#src/session/prompts";
|
|
40
40
|
import { deriveSubagentSessionDir } from "#src/session/session-dir";
|
|
41
|
-
import { preloadSkills } from "#src/session/skill-loader";
|
|
42
41
|
import { SettingsManager } from "#src/settings";
|
|
43
42
|
import { AgentTool } from "#src/tools/agent-tool";
|
|
44
43
|
import { GetResultTool } from "#src/tools/get-result-tool";
|
|
@@ -143,7 +142,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
143
142
|
createSettingsManager: (cwd, dir) => SdkSettingsManager.create(cwd, dir),
|
|
144
143
|
createSession: (opts) => createAgentSession(opts as any),
|
|
145
144
|
assemblerIO: {
|
|
146
|
-
preloadSkills,
|
|
147
145
|
buildAgentPrompt,
|
|
148
146
|
},
|
|
149
147
|
},
|
|
@@ -41,7 +41,6 @@ export interface AgentSpawnConfig {
|
|
|
41
41
|
description: string;
|
|
42
42
|
model?: Model<any>;
|
|
43
43
|
maxTurns?: number;
|
|
44
|
-
isolated?: boolean;
|
|
45
44
|
inheritContext?: boolean;
|
|
46
45
|
thinkingLevel?: ThinkingLevel;
|
|
47
46
|
isBackground?: boolean;
|
|
@@ -149,7 +148,6 @@ export class AgentManager {
|
|
|
149
148
|
prompt,
|
|
150
149
|
model: options.model,
|
|
151
150
|
maxTurns: options.maxTurns,
|
|
152
|
-
isolated: options.isolated,
|
|
153
151
|
thinkingLevel: options.thinkingLevel,
|
|
154
152
|
parentSession: options.parentSession,
|
|
155
153
|
signal: options.signal,
|
|
@@ -55,8 +55,6 @@ export interface SessionManagerLike {
|
|
|
55
55
|
export interface ResourceLoaderOptions {
|
|
56
56
|
cwd: string;
|
|
57
57
|
agentDir: string;
|
|
58
|
-
noExtensions?: boolean;
|
|
59
|
-
noSkills?: boolean;
|
|
60
58
|
noPromptTemplates?: boolean;
|
|
61
59
|
noThemes?: boolean;
|
|
62
60
|
noContextFiles?: boolean;
|
|
@@ -148,7 +146,6 @@ export interface RunOptions {
|
|
|
148
146
|
model?: Model<any>;
|
|
149
147
|
maxTurns?: number;
|
|
150
148
|
signal?: AbortSignal;
|
|
151
|
-
isolated?: boolean;
|
|
152
149
|
thinkingLevel?: ThinkingLevel;
|
|
153
150
|
/** Called once after session creation - session delivery mechanism. */
|
|
154
151
|
onSessionCreated?: (session: AgentSession) => void;
|
|
@@ -283,7 +280,6 @@ export async function runAgent(
|
|
|
283
280
|
},
|
|
284
281
|
{
|
|
285
282
|
cwd: options.context.cwd,
|
|
286
|
-
isolated: options.isolated,
|
|
287
283
|
model: options.model,
|
|
288
284
|
thinkingLevel: options.thinkingLevel,
|
|
289
285
|
},
|
|
@@ -294,17 +290,15 @@ export async function runAgent(
|
|
|
294
290
|
|
|
295
291
|
const agentDir = deps.io.getAgentDir();
|
|
296
292
|
|
|
297
|
-
//
|
|
293
|
+
// Children always load the parent's extensions and skills.
|
|
298
294
|
// Suppress AGENTS.md/CLAUDE.md and APPEND_SYSTEM.md - upstream's
|
|
299
295
|
// buildSystemPrompt() re-appends both AFTER systemPromptOverride, which
|
|
300
|
-
// would defeat prompt_mode: replace
|
|
296
|
+
// would defeat prompt_mode: replace. Parent context, if
|
|
301
297
|
// wanted, reaches the subagent via prompt_mode: append (parentSystemPrompt
|
|
302
298
|
// is embedded in systemPromptOverride) or inherit_context (conversation).
|
|
303
299
|
const loader = deps.io.createResourceLoader({
|
|
304
300
|
cwd: cfg.effectiveCwd,
|
|
305
301
|
agentDir,
|
|
306
|
-
noExtensions: !cfg.extensions,
|
|
307
|
-
noSkills: cfg.noSkills,
|
|
308
302
|
noPromptTemplates: true,
|
|
309
303
|
noThemes: true,
|
|
310
304
|
noContextFiles: true,
|
|
@@ -348,12 +342,10 @@ export async function runAgent(
|
|
|
348
342
|
|
|
349
343
|
// Apply recursion guard: remove our own tools from the child's active set.
|
|
350
344
|
// Runs after bindExtensions so extension-registered tools are included in the
|
|
351
|
-
// post-bind active set.
|
|
352
|
-
//
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
session.setActiveToolsByName(filtered);
|
|
356
|
-
}
|
|
345
|
+
// post-bind active set. Unconditional: children always load the parent's
|
|
346
|
+
// extensions, so the guard must always strip our dispatch tools.
|
|
347
|
+
const filtered = filterActiveTools(session.getActiveToolNames());
|
|
348
|
+
session.setActiveToolsByName(filtered);
|
|
357
349
|
|
|
358
350
|
options.onSessionCreated?.(session);
|
|
359
351
|
|
package/src/lifecycle/agent.ts
CHANGED
|
@@ -81,7 +81,6 @@ export interface AgentInit {
|
|
|
81
81
|
prompt?: string;
|
|
82
82
|
model?: Model<any>;
|
|
83
83
|
maxTurns?: number;
|
|
84
|
-
isolated?: boolean;
|
|
85
84
|
thinkingLevel?: ThinkingLevel;
|
|
86
85
|
parentSession?: ParentSessionInfo;
|
|
87
86
|
isBackground?: boolean;
|
|
@@ -140,7 +139,6 @@ export class Agent {
|
|
|
140
139
|
private readonly _prompt?: string;
|
|
141
140
|
private readonly _model?: Model<any>;
|
|
142
141
|
private readonly _maxTurns?: number;
|
|
143
|
-
private readonly _isolated?: boolean;
|
|
144
142
|
private readonly _thinkingLevel?: ThinkingLevel;
|
|
145
143
|
private readonly _parentSession?: ParentSessionInfo;
|
|
146
144
|
private readonly _signal?: AbortSignal;
|
|
@@ -198,7 +196,6 @@ export class Agent {
|
|
|
198
196
|
this._prompt = init.prompt;
|
|
199
197
|
this._model = init.model;
|
|
200
198
|
this._maxTurns = init.maxTurns;
|
|
201
|
-
this._isolated = init.isolated;
|
|
202
199
|
this._thinkingLevel = init.thinkingLevel;
|
|
203
200
|
this._parentSession = init.parentSession;
|
|
204
201
|
this._signal = init.signal;
|
|
@@ -261,7 +258,6 @@ export class Agent {
|
|
|
261
258
|
maxTurns: this._maxTurns,
|
|
262
259
|
defaultMaxTurns: runConfig?.defaultMaxTurns,
|
|
263
260
|
graceTurns: runConfig?.graceTurns,
|
|
264
|
-
isolated: this._isolated,
|
|
265
261
|
thinkingLevel: this._thinkingLevel,
|
|
266
262
|
signal: this.abortController.signal,
|
|
267
263
|
onSessionCreated: (session) => {
|
|
@@ -66,7 +66,6 @@ export class SubagentsServiceAdapter implements SubagentsService {
|
|
|
66
66
|
model,
|
|
67
67
|
maxTurns: options?.maxTurns,
|
|
68
68
|
thinkingLevel: options?.thinkingLevel,
|
|
69
|
-
isolated: options?.isolated,
|
|
70
69
|
inheritContext: options?.inheritContext,
|
|
71
70
|
bypassQueue: options?.bypassQueue,
|
|
72
71
|
isBackground,
|
package/src/service/service.ts
CHANGED
package/src/session/prompts.ts
CHANGED
|
@@ -5,12 +5,6 @@
|
|
|
5
5
|
import type { EnvInfo } from "#src/session/env";
|
|
6
6
|
import type { AgentPromptConfig } from "#src/types";
|
|
7
7
|
|
|
8
|
-
/** Extra sections to inject into the system prompt (skills, etc.). */
|
|
9
|
-
export interface PromptExtras {
|
|
10
|
-
/** Preloaded skill contents to inject. */
|
|
11
|
-
skillBlocks?: { name: string; content: string }[];
|
|
12
|
-
}
|
|
13
|
-
|
|
14
8
|
/**
|
|
15
9
|
* Build the system prompt for an agent from its config.
|
|
16
10
|
*
|
|
@@ -25,14 +19,12 @@ export interface PromptExtras {
|
|
|
25
19
|
* inherited content so the stable prefix is cacheable by the LLM's KV cache.
|
|
26
20
|
*
|
|
27
21
|
* @param parentSystemPrompt The parent agent's effective system prompt (for append mode).
|
|
28
|
-
* @param extras Optional extra sections to inject (memory, preloaded skills).
|
|
29
22
|
*/
|
|
30
23
|
export function buildAgentPrompt(
|
|
31
24
|
config: AgentPromptConfig,
|
|
32
25
|
cwd: string,
|
|
33
26
|
env: EnvInfo,
|
|
34
27
|
parentSystemPrompt?: string,
|
|
35
|
-
extras?: PromptExtras,
|
|
36
28
|
): string {
|
|
37
29
|
const activeAgentTag = `<active_agent name="${config.name}"/>\n\n`;
|
|
38
30
|
|
|
@@ -41,18 +33,6 @@ Working directory: ${cwd}
|
|
|
41
33
|
${env.isGitRepo ? `Git repository: yes\nBranch: ${env.branch}` : "Not a git repository"}
|
|
42
34
|
Platform: ${env.platform}`;
|
|
43
35
|
|
|
44
|
-
// Build optional extras suffix
|
|
45
|
-
const extraSections: string[] = [];
|
|
46
|
-
if (extras?.skillBlocks?.length) {
|
|
47
|
-
for (const skill of extras.skillBlocks) {
|
|
48
|
-
extraSections.push(
|
|
49
|
-
`\n# Preloaded Skill: ${skill.name}\n${skill.content}`,
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
const extrasSuffix =
|
|
54
|
-
extraSections.length > 0 ? "\n\n" + extraSections.join("\n") : "";
|
|
55
|
-
|
|
56
36
|
if (config.promptMode === "append") {
|
|
57
37
|
const identity = parentSystemPrompt ?? genericBase;
|
|
58
38
|
|
|
@@ -85,8 +65,7 @@ You are operating as a sub-agent invoked to handle a specific task.
|
|
|
85
65
|
"\n\n" +
|
|
86
66
|
activeAgentTag +
|
|
87
67
|
envBlock +
|
|
88
|
-
customSection
|
|
89
|
-
extrasSuffix
|
|
68
|
+
customSection
|
|
90
69
|
);
|
|
91
70
|
}
|
|
92
71
|
|
|
@@ -97,7 +76,7 @@ You have been invoked to handle a specific task autonomously.
|
|
|
97
76
|
${envBlock}`;
|
|
98
77
|
|
|
99
78
|
return (
|
|
100
|
-
activeAgentTag + replaceHeader + "\n\n" + config.systemPrompt
|
|
79
|
+
activeAgentTag + replaceHeader + "\n\n" + config.systemPrompt
|
|
101
80
|
);
|
|
102
81
|
}
|
|
103
82
|
|
|
@@ -12,8 +12,6 @@
|
|
|
12
12
|
|
|
13
13
|
import type { AgentConfigLookup } from "#src/config/agent-types";
|
|
14
14
|
import type { EnvInfo } from "#src/session/env";
|
|
15
|
-
import type { PromptExtras } from "#src/session/prompts";
|
|
16
|
-
import type { PreloadedSkill } from "#src/session/skill-loader";
|
|
17
15
|
import type { AgentPromptConfig, SubagentType, ThinkingLevel } from "#src/types";
|
|
18
16
|
|
|
19
17
|
// ── Public interfaces ────────────────────────────────────────────────────────
|
|
@@ -21,19 +19,17 @@ import type { AgentPromptConfig, SubagentType, ThinkingLevel } from "#src/types"
|
|
|
21
19
|
/**
|
|
22
20
|
* IO collaborators injected into `assembleSessionConfig`.
|
|
23
21
|
*
|
|
24
|
-
* Bundling the
|
|
22
|
+
* Bundling the IO-touching (or promptly testable) function into a single
|
|
25
23
|
* interface keeps the assembler free of direct module imports and makes it
|
|
26
24
|
* trivially testable without `vi.mock()` — callers inject real implementations
|
|
27
25
|
* at the edge (`agent-runner.ts`) or stubs in tests.
|
|
28
26
|
*/
|
|
29
27
|
export interface AssemblerIO {
|
|
30
|
-
preloadSkills: (skills: string[], cwd: string) => PreloadedSkill[];
|
|
31
28
|
buildAgentPrompt: (
|
|
32
29
|
config: AgentPromptConfig,
|
|
33
30
|
cwd: string,
|
|
34
31
|
env: EnvInfo,
|
|
35
32
|
parentPrompt?: string,
|
|
36
|
-
extras?: PromptExtras,
|
|
37
33
|
) => string;
|
|
38
34
|
}
|
|
39
35
|
|
|
@@ -67,8 +63,6 @@ export interface AssemblerContext {
|
|
|
67
63
|
export interface AssemblerOptions {
|
|
68
64
|
/** Override working directory (e.g. for worktree isolation). */
|
|
69
65
|
cwd?: string;
|
|
70
|
-
/** When true, forces extensions and skills to false. */
|
|
71
|
-
isolated?: boolean;
|
|
72
66
|
/** Explicit model override — wins over agentConfig.model and parent model. */
|
|
73
67
|
model?: unknown;
|
|
74
68
|
/** Explicit thinking level — wins over agentConfig.thinking. */
|
|
@@ -87,8 +81,6 @@ export interface SessionConfig {
|
|
|
87
81
|
systemPrompt: string;
|
|
88
82
|
/** Built-in tool name allowlist for this agent type. */
|
|
89
83
|
toolNames: string[];
|
|
90
|
-
/** Resolved extensions setting: true = inherit all, false = none. */
|
|
91
|
-
extensions: boolean;
|
|
92
84
|
/**
|
|
93
85
|
* Resolved model instance (undefined → use parent model as passed to SDK).
|
|
94
86
|
* Opaque handle — the assembler passes it through without inspection.
|
|
@@ -97,10 +89,6 @@ export interface SessionConfig {
|
|
|
97
89
|
model: unknown;
|
|
98
90
|
/** Resolved thinking level (undefined → inherit from session). */
|
|
99
91
|
thinkingLevel: ThinkingLevel | undefined;
|
|
100
|
-
/** Whether to skip skill loading in the resource loader (`noSkills` flag). */
|
|
101
|
-
noSkills: boolean;
|
|
102
|
-
/** Prompt extras (memory block, preloaded skill blocks) — for transparency. */
|
|
103
|
-
extras: PromptExtras;
|
|
104
92
|
/** Per-agent configured max turns (from agentConfig.maxTurns). */
|
|
105
93
|
agentMaxTurns: number | undefined;
|
|
106
94
|
}
|
|
@@ -150,7 +138,7 @@ function resolveDefaultModel(
|
|
|
150
138
|
*
|
|
151
139
|
* @param type The subagent type name (case-insensitive registry lookup).
|
|
152
140
|
* @param ctx Narrow context from the parent session.
|
|
153
|
-
* @param options Per-call overrides (cwd,
|
|
141
|
+
* @param options Per-call overrides (cwd, model, thinkingLevel).
|
|
154
142
|
* @param env Pre-resolved environment info from `detectEnv()`.
|
|
155
143
|
* @param registry Agent config lookup — provides resolveAgentConfig and getToolNamesForType.
|
|
156
144
|
* @param io IO collaborators (skill loader, memory builder, prompt builder).
|
|
@@ -167,21 +155,6 @@ export function assembleSessionConfig(
|
|
|
167
155
|
|
|
168
156
|
const effectiveCwd = options.cwd ?? ctx.cwd;
|
|
169
157
|
|
|
170
|
-
// Resolve extensions/skills: isolated overrides to false
|
|
171
|
-
const extensions = options.isolated ? false : agentConfig.extensions;
|
|
172
|
-
const skills = options.isolated ? false : agentConfig.skills;
|
|
173
|
-
|
|
174
|
-
// Build prompt extras (memory, preloaded skills)
|
|
175
|
-
const extras: PromptExtras = {};
|
|
176
|
-
|
|
177
|
-
// Skill preloading: when skills is string[], preload their content into the prompt
|
|
178
|
-
if (Array.isArray(skills)) {
|
|
179
|
-
const loaded = io.preloadSkills(skills, effectiveCwd);
|
|
180
|
-
if (loaded.length > 0) {
|
|
181
|
-
extras.skillBlocks = loaded;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
158
|
const toolNames = registry.getToolNamesForType(type);
|
|
186
159
|
|
|
187
160
|
// Build system prompt from the resolved agent config
|
|
@@ -190,13 +163,8 @@ export function assembleSessionConfig(
|
|
|
190
163
|
effectiveCwd,
|
|
191
164
|
env,
|
|
192
165
|
ctx.parentSystemPrompt,
|
|
193
|
-
extras,
|
|
194
166
|
);
|
|
195
167
|
|
|
196
|
-
// noSkills: when we've already preloaded skills into the prompt, or skills = false,
|
|
197
|
-
// tell the resource loader not to load them again.
|
|
198
|
-
const noSkills = skills === false || Array.isArray(skills);
|
|
199
|
-
|
|
200
168
|
// Model resolution: explicit option > config model string > parent model
|
|
201
169
|
const model =
|
|
202
170
|
options.model ??
|
|
@@ -212,11 +180,8 @@ export function assembleSessionConfig(
|
|
|
212
180
|
effectiveCwd,
|
|
213
181
|
systemPrompt,
|
|
214
182
|
toolNames,
|
|
215
|
-
extensions,
|
|
216
183
|
model,
|
|
217
184
|
thinkingLevel,
|
|
218
|
-
noSkills,
|
|
219
|
-
extras,
|
|
220
185
|
agentMaxTurns,
|
|
221
186
|
};
|
|
222
187
|
}
|
package/src/tools/agent-tool.ts
CHANGED
|
@@ -220,11 +220,6 @@ Guidelines:
|
|
|
220
220
|
description: "Optional agent ID to resume from. Continues from previous context.",
|
|
221
221
|
}),
|
|
222
222
|
),
|
|
223
|
-
isolated: Type.Optional(
|
|
224
|
-
Type.Boolean({
|
|
225
|
-
description: "If true, agent gets no extension/MCP tools — only built-in tools.",
|
|
226
|
-
}),
|
|
227
|
-
),
|
|
228
223
|
inherit_context: Type.Optional(
|
|
229
224
|
Type.Boolean({
|
|
230
225
|
description:
|
|
@@ -48,7 +48,6 @@ export function spawnBackground(
|
|
|
48
48
|
description: execution.description,
|
|
49
49
|
model: execution.model,
|
|
50
50
|
maxTurns: execution.effectiveMaxTurns,
|
|
51
|
-
isolated: execution.isolated,
|
|
52
51
|
inheritContext: execution.inheritContext,
|
|
53
52
|
thinkingLevel: execution.thinking,
|
|
54
53
|
isBackground: true,
|
|
@@ -102,7 +102,6 @@ export async function runForeground(
|
|
|
102
102
|
description: execution.description,
|
|
103
103
|
model: execution.model,
|
|
104
104
|
maxTurns: execution.effectiveMaxTurns,
|
|
105
|
-
isolated: execution.isolated,
|
|
106
105
|
inheritContext: execution.inheritContext,
|
|
107
106
|
thinkingLevel: execution.thinking,
|
|
108
107
|
invocation: execution.agentInvocation,
|
|
@@ -43,7 +43,6 @@ export interface SpawnExecution {
|
|
|
43
43
|
thinking: ThinkingLevel | undefined;
|
|
44
44
|
inheritContext: boolean;
|
|
45
45
|
runInBackground: boolean;
|
|
46
|
-
isolated: boolean;
|
|
47
46
|
agentInvocation: AgentInvocation;
|
|
48
47
|
}
|
|
49
48
|
|
|
@@ -102,7 +101,6 @@ export function resolveSpawnConfig(
|
|
|
102
101
|
const thinking = resolvedConfig.thinking;
|
|
103
102
|
const inheritContext = resolvedConfig.inheritContext;
|
|
104
103
|
const runInBackground = resolvedConfig.runInBackground;
|
|
105
|
-
const isolated = resolvedConfig.isolated;
|
|
106
104
|
|
|
107
105
|
// Compute display model name (only shown when different from parent)
|
|
108
106
|
const parentModelId = modelInfo.parentModel?.id;
|
|
@@ -120,7 +118,6 @@ export function resolveSpawnConfig(
|
|
|
120
118
|
modelName,
|
|
121
119
|
thinking,
|
|
122
120
|
maxTurns: normalizeMaxTurns(resolvedConfig.maxTurns),
|
|
123
|
-
isolated,
|
|
124
121
|
inheritContext,
|
|
125
122
|
runInBackground,
|
|
126
123
|
};
|
|
@@ -147,7 +144,6 @@ export function resolveSpawnConfig(
|
|
|
147
144
|
thinking,
|
|
148
145
|
inheritContext,
|
|
149
146
|
runInBackground,
|
|
150
|
-
isolated,
|
|
151
147
|
agentInvocation,
|
|
152
148
|
},
|
|
153
149
|
presentation: { modelName, agentTags, detailBase },
|
package/src/types.ts
CHANGED
|
@@ -39,10 +39,6 @@ export interface AgentPromptConfig {
|
|
|
39
39
|
/** Unified agent configuration — used for both default and user-defined agents. */
|
|
40
40
|
export interface AgentConfig extends AgentIdentity, AgentPromptConfig {
|
|
41
41
|
builtinToolNames?: string[];
|
|
42
|
-
/** true = inherit all extensions, false = none */
|
|
43
|
-
extensions: boolean;
|
|
44
|
-
/** true = inherit all, string[] = only listed, false = none */
|
|
45
|
-
skills: true | string[] | false;
|
|
46
42
|
model?: string;
|
|
47
43
|
thinking?: ThinkingLevel;
|
|
48
44
|
maxTurns?: number;
|
|
@@ -50,8 +46,6 @@ export interface AgentConfig extends AgentIdentity, AgentPromptConfig {
|
|
|
50
46
|
inheritContext?: boolean;
|
|
51
47
|
/** Default for spawn: run in background. undefined = caller decides. */
|
|
52
48
|
runInBackground?: boolean;
|
|
53
|
-
/** Default for spawn: no extension tools. undefined = caller decides. */
|
|
54
|
-
isolated?: boolean;
|
|
55
49
|
/** true = this is an embedded default agent (informational) */
|
|
56
50
|
isDefault?: boolean;
|
|
57
51
|
/** false = agent is hidden from the registry */
|
|
@@ -65,7 +59,6 @@ export interface AgentInvocation {
|
|
|
65
59
|
modelName?: string;
|
|
66
60
|
thinking?: ThinkingLevel;
|
|
67
61
|
maxTurns?: number;
|
|
68
|
-
isolated?: boolean;
|
|
69
62
|
inheritContext?: boolean;
|
|
70
63
|
runInBackground?: boolean;
|
|
71
64
|
}
|
|
@@ -47,13 +47,8 @@ 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) fmFields.push("extensions: false");
|
|
51
|
-
if (cfg.skills === false) fmFields.push("skills: false");
|
|
52
|
-
else if (Array.isArray(cfg.skills))
|
|
53
|
-
fmFields.push(`skills: ${cfg.skills.join(", ")}`);
|
|
54
50
|
if (cfg.inheritContext) fmFields.push("inherit_context: true");
|
|
55
51
|
if (cfg.runInBackground) fmFields.push("run_in_background: true");
|
|
56
|
-
if (cfg.isolated) fmFields.push("isolated: true");
|
|
57
52
|
return `---\n${fmFields.join("\n")}\n---\n\n${cfg.systemPrompt}\n`;
|
|
58
53
|
}
|
|
59
54
|
|
|
@@ -104,11 +104,8 @@ 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) or false (none). Default: true>
|
|
108
|
-
skills: <true (inherit all), false (none), or comma-separated skill names to preload into prompt. Default: true>
|
|
109
107
|
inherit_context: <true to fork parent conversation into agent so it sees chat history. Default: false>
|
|
110
108
|
run_in_background: <true to run in background by default. Default: false>
|
|
111
|
-
isolated: <true for no extension/MCP tools, only built-in tools. Default: false>
|
|
112
109
|
---
|
|
113
110
|
|
|
114
111
|
<system prompt body — instructions for the agent>
|
|
@@ -120,7 +117,6 @@ Guidelines for choosing settings:
|
|
|
120
117
|
- Use prompt_mode: append if the agent should keep the default system prompt and add specialization on top
|
|
121
118
|
- Use prompt_mode: replace for fully custom agents with their own personality/instructions
|
|
122
119
|
- Set inherit_context: true if the agent needs to know what was discussed in the parent conversation
|
|
123
|
-
- Set isolated: true if the agent should NOT have access to MCP servers or other extensions
|
|
124
120
|
- Only include frontmatter fields that differ from defaults — omit fields where the default is fine
|
|
125
121
|
|
|
126
122
|
Write the file using the write tool. Only write the file, nothing else.`;
|
package/src/ui/display.ts
CHANGED
|
@@ -30,7 +30,7 @@ export interface AgentDetails {
|
|
|
30
30
|
spinnerFrame?: number;
|
|
31
31
|
/** Short model name if different from parent (e.g. "haiku", "sonnet"). */
|
|
32
32
|
modelName?: string;
|
|
33
|
-
/** Notable config tags (e.g. ["thinking: high", "
|
|
33
|
+
/** Notable config tags (e.g. ["thinking: high", "inherit context"]). */
|
|
34
34
|
tags?: string[];
|
|
35
35
|
/** Current turn count. */
|
|
36
36
|
turnCount?: number;
|
|
@@ -135,7 +135,6 @@ export function buildInvocationTags(
|
|
|
135
135
|
const tags: string[] = [];
|
|
136
136
|
if (!invocation) return { tags };
|
|
137
137
|
if (invocation.thinking) tags.push(`thinking: ${invocation.thinking}`);
|
|
138
|
-
if (invocation.isolated) tags.push("isolated");
|
|
139
138
|
if (invocation.inheritContext) tags.push("inherit context");
|
|
140
139
|
if (invocation.runInBackground) tags.push("background");
|
|
141
140
|
if (invocation.maxTurns != null) tags.push(`max turns: ${invocation.maxTurns}`);
|
package/src/session/safe-fs.ts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* safe-fs.ts — Filesystem safety utilities for reading untrusted paths.
|
|
3
|
-
*
|
|
4
|
-
* Used by skill-loader.ts to reject symlinks and path-traversal names
|
|
5
|
-
* before reading skill files from disk.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { existsSync, lstatSync, readFileSync } from "node:fs";
|
|
9
|
-
import { debugLog } from "#src/debug";
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Returns true if a name contains characters not allowed in agent/skill names.
|
|
13
|
-
* Uses a whitelist: only alphanumeric, hyphens, underscores, and dots (no leading dot).
|
|
14
|
-
*/
|
|
15
|
-
export function isUnsafeName(name: string): boolean {
|
|
16
|
-
if (!name || name.length > 128) return true;
|
|
17
|
-
return !/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(name);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Returns true if the given path is a symlink (defense against symlink attacks).
|
|
22
|
-
*/
|
|
23
|
-
export function isSymlink(filePath: string): boolean {
|
|
24
|
-
try {
|
|
25
|
-
return lstatSync(filePath).isSymbolicLink();
|
|
26
|
-
} catch (err) {
|
|
27
|
-
debugLog("lstatSync", err);
|
|
28
|
-
return false;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Safely read a file, rejecting symlinks.
|
|
34
|
-
* Returns undefined if the file doesn't exist, is a symlink, or can't be read.
|
|
35
|
-
*/
|
|
36
|
-
export function safeReadFile(filePath: string): string | undefined {
|
|
37
|
-
if (!existsSync(filePath)) return undefined;
|
|
38
|
-
if (isSymlink(filePath)) return undefined;
|
|
39
|
-
try {
|
|
40
|
-
return readFileSync(filePath, "utf-8");
|
|
41
|
-
} catch (err) {
|
|
42
|
-
debugLog("readFileSync", err);
|
|
43
|
-
return undefined;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* skill-loader.ts — Preload named skills.
|
|
3
|
-
*
|
|
4
|
-
* Roots, in precedence order:
|
|
5
|
-
* - <cwd>/.pi/skills (project, Pi's standard)
|
|
6
|
-
* - <cwd>/.agents/skills (project, cross-tool Agent Skills spec — https://agentskills.io)
|
|
7
|
-
* - getAgentDir()/skills (user, default ~/.pi/agent/skills — Pi's standard)
|
|
8
|
-
* - ~/.agents/skills (user, cross-tool Agent Skills spec)
|
|
9
|
-
* - ~/.pi/skills (legacy global, pre-Pi)
|
|
10
|
-
*
|
|
11
|
-
* Layout per root:
|
|
12
|
-
* - <root>/<name>.md (flat file at the top level)
|
|
13
|
-
* - <root>/.../<name>/SKILL.md (directory skill, may be nested — Pi's standard)
|
|
14
|
-
*
|
|
15
|
-
* Recursion skips dotfile entries and node_modules. A directory that itself contains
|
|
16
|
-
* SKILL.md is a skill — we don't descend into it (Pi: skills don't nest).
|
|
17
|
-
*
|
|
18
|
-
* Symlinks are rejected for security (deviation from Pi, which follows them).
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
import type { Dirent } from "node:fs";
|
|
22
|
-
import { existsSync, readdirSync } from "node:fs";
|
|
23
|
-
import { homedir } from "node:os";
|
|
24
|
-
import { join } from "node:path";
|
|
25
|
-
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
26
|
-
import { debugLog } from "#src/debug";
|
|
27
|
-
import { isSymlink, isUnsafeName, safeReadFile } from "#src/session/safe-fs";
|
|
28
|
-
|
|
29
|
-
export interface PreloadedSkill {
|
|
30
|
-
name: string;
|
|
31
|
-
content: string;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function preloadSkills(skillNames: string[], cwd: string): PreloadedSkill[] {
|
|
35
|
-
return skillNames.map((name) => ({ name, content: loadSkillContent(name, cwd) }));
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function loadSkillContent(name: string, cwd: string): string {
|
|
39
|
-
if (isUnsafeName(name)) {
|
|
40
|
-
return `(Skill "${name}" skipped: name contains path traversal characters)`;
|
|
41
|
-
}
|
|
42
|
-
const roots = [
|
|
43
|
-
join(cwd, ".pi", "skills"), // project — Pi standard
|
|
44
|
-
join(cwd, ".agents", "skills"), // project — Agent Skills spec
|
|
45
|
-
join(getAgentDir(), "skills"), // user — Pi standard
|
|
46
|
-
join(homedir(), ".agents", "skills"), // user — Agent Skills spec
|
|
47
|
-
join(homedir(), ".pi", "skills"), // legacy global, pre-Pi
|
|
48
|
-
];
|
|
49
|
-
for (const root of roots) {
|
|
50
|
-
const content = findInRoot(root, name);
|
|
51
|
-
if (content !== undefined) return content;
|
|
52
|
-
}
|
|
53
|
-
return `(Skill "${name}" not found in .pi/skills/, .agents/skills/, or global skill locations)`;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function findInRoot(root: string, name: string): string | undefined {
|
|
57
|
-
if (isSymlink(root)) return undefined; // reject symlinked roots entirely
|
|
58
|
-
const flat = safeReadFile(join(root, `${name}.md`))?.trim();
|
|
59
|
-
if (flat !== undefined) return flat;
|
|
60
|
-
return findSkillDirectory(root, name);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/** BFS under `root` for a directory named `name` containing `SKILL.md`. Pi-conforming filters. */
|
|
64
|
-
function findSkillDirectory(root: string, name: string): string | undefined {
|
|
65
|
-
if (!existsSync(root)) return undefined;
|
|
66
|
-
const queue: string[] = [root];
|
|
67
|
-
|
|
68
|
-
while (queue.length > 0) {
|
|
69
|
-
const current = queue.shift();
|
|
70
|
-
if (current === undefined) continue;
|
|
71
|
-
|
|
72
|
-
let entries: Dirent[];
|
|
73
|
-
try {
|
|
74
|
-
entries = readdirSync(current, { withFileTypes: true });
|
|
75
|
-
} catch (err) {
|
|
76
|
-
debugLog("readdirSync skill root", err);
|
|
77
|
-
continue;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Deterministic byte-order traversal — locale-independent.
|
|
81
|
-
entries.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
82
|
-
|
|
83
|
-
for (const entry of entries) {
|
|
84
|
-
if (!entry.isDirectory()) continue;
|
|
85
|
-
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
86
|
-
|
|
87
|
-
// Symlinked dirs already filtered by entry.isDirectory() — Dirent uses lstat semantics.
|
|
88
|
-
const path = join(current, entry.name);
|
|
89
|
-
const skillMd = join(path, "SKILL.md");
|
|
90
|
-
const isSkillDir = existsSync(skillMd);
|
|
91
|
-
|
|
92
|
-
if (isSkillDir) {
|
|
93
|
-
if (entry.name === name) {
|
|
94
|
-
const content = safeReadFile(skillMd)?.trim();
|
|
95
|
-
if (content !== undefined) return content;
|
|
96
|
-
}
|
|
97
|
-
continue; // Pi rule: skills don't nest — don't descend into a skill dir
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
queue.push(path);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
return undefined;
|
|
104
|
-
}
|