pi-subagents 0.21.3 → 0.21.5
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 +18 -0
- package/README.md +26 -14
- package/package.json +17 -2
- package/skills/pi-subagents/SKILL.md +18 -12
- package/src/agents/agent-management.ts +58 -16
- package/src/agents/agent-serializer.ts +4 -1
- package/src/agents/agents.ts +39 -30
- package/src/agents/chain-serializer.ts +16 -3
- package/src/agents/identity.ts +30 -0
- package/src/extension/index.ts +9 -6
- package/src/extension/schemas.ts +11 -2
- package/src/intercom/result-intercom.ts +4 -3
- package/src/manager-ui/agent-manager-chain-detail.ts +4 -0
- package/src/manager-ui/agent-manager-detail.ts +4 -0
- package/src/manager-ui/agent-manager-edit.ts +25 -6
- package/src/manager-ui/agent-manager.ts +28 -8
- package/src/runs/background/async-execution.ts +27 -11
- package/src/runs/background/async-job-tracker.ts +12 -19
- package/src/runs/background/async-resume.ts +1 -0
- package/src/runs/background/async-status.ts +35 -49
- package/src/runs/background/parallel-groups.ts +45 -0
- package/src/runs/background/result-watcher.ts +2 -2
- package/src/runs/background/run-status.ts +26 -7
- package/src/runs/background/stale-run-reconciler.ts +15 -2
- package/src/runs/background/subagent-runner.ts +17 -13
- package/src/runs/foreground/chain-clarify.ts +2 -0
- package/src/runs/foreground/chain-execution.ts +36 -0
- package/src/runs/foreground/execution.ts +26 -4
- package/src/runs/foreground/subagent-executor.ts +30 -5
- package/src/runs/shared/parallel-utils.ts +1 -0
- package/src/runs/shared/single-output.ts +47 -3
- package/src/shared/session-identity.ts +10 -0
- package/src/shared/settings.ts +9 -3
- package/src/shared/types.ts +25 -5
- package/src/slash/slash-commands.ts +11 -1
- package/src/tui/render.ts +67 -2
- package/src/tui/subagents-status.ts +129 -15
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.21.5] - 2026-05-02
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- Show top-level async parallel runs as `parallel` instead of `chain`, with foreground-style running/done wording in widgets and status output, and group running async chain detail by chain step.
|
|
9
|
+
- Scoped `/subagents-status` to async runs launched from the current pi session instead of showing prior or unrelated sessions.
|
|
10
|
+
- Declared the Pi TUI package as a direct dev dependency and added a manifest guard so CI installs do not rely on transitive optional peer dependencies for tests.
|
|
11
|
+
- Made prompt-runtime extension path assertions portable on Windows.
|
|
12
|
+
|
|
13
|
+
## [0.21.4] - 2026-05-01
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
- Added explicit frontmatter `package` identifiers for agents and saved chains, registering runtime names like `code-analysis.scout` while preserving separate `name` and `package` fields on save.
|
|
17
|
+
- Added recursive subdirectory discovery for user and project agent and chain definitions.
|
|
18
|
+
- Added `outputMode: "inline" | "file-only"` for saved subagent outputs. `inline` remains the default, while `file-only` returns a concise saved-file reference instead of injecting full saved output back into the parent context.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- Marked Pi runtime peer dependencies as optional so npm package installs do not auto-install duplicate Pi packages or emit unrelated transitive dependency warnings.
|
|
22
|
+
|
|
5
23
|
## [0.21.3] - 2026-04-30
|
|
6
24
|
|
|
7
25
|
### Fixed
|
package/README.md
CHANGED
|
@@ -150,12 +150,14 @@ Use `~/.pi/agent/settings.json` for a user override or `.pi/settings.json` for a
|
|
|
150
150
|
|
|
151
151
|
Foreground runs stream progress in the conversation while they run.
|
|
152
152
|
|
|
153
|
-
Background runs keep working after control returns to you. They show completion notifications and can be inspected with:
|
|
153
|
+
Background runs keep working after control returns to you. They show a compact async widget, send completion notifications, and can be inspected with:
|
|
154
154
|
|
|
155
155
|
```text
|
|
156
156
|
/subagents-status
|
|
157
157
|
```
|
|
158
158
|
|
|
159
|
+
The status view shows active and recent runs for the current Pi session. Parallel background runs are shown as parallel work, with per-agent progress instead of fake chain steps. Chains with parallel groups keep their grouped shape in both progress and results views, so failed or paused agents stay visible next to completed ones.
|
|
160
|
+
|
|
159
161
|
You can also ask naturally:
|
|
160
162
|
|
|
161
163
|
```text
|
|
@@ -298,12 +300,13 @@ Append `[key=value,...]` to an agent name to override defaults for that step:
|
|
|
298
300
|
| Key | Example | Description |
|
|
299
301
|
|-----|---------|-------------|
|
|
300
302
|
| `output` | `output=context.md` | Write results to a file. For `/chain` and `/parallel`, relative paths live under the chain directory; for `/run`, relative paths resolve against cwd. |
|
|
303
|
+
| `outputMode` | `outputMode=file-only` | Return only a concise file reference for saved output instead of the full saved content. Requires `output`; default is `inline`. |
|
|
301
304
|
| `reads` | `reads=a.md+b.md` | Read files before executing. `+` separates multiple paths. |
|
|
302
305
|
| `model` | `model=anthropic/claude-sonnet-4` | Override model for this step. |
|
|
303
306
|
| `skills` | `skills=planning+review` | Override injected skills. `+` separates multiple skills. |
|
|
304
307
|
| `progress` | `progress` | Enable progress tracking. |
|
|
305
308
|
|
|
306
|
-
Set `output=false`, `reads=false`, or `skills=false` to disable that behavior explicitly.
|
|
309
|
+
Set `output=false`, `reads=false`, or `skills=false` to disable that behavior explicitly. Do not use `output=false` for file-only returns; use `outputMode=file-only` with an `output` path.
|
|
307
310
|
|
|
308
311
|
### Background and forked runs
|
|
309
312
|
|
|
@@ -396,10 +399,10 @@ Agent locations, lowest to highest priority:
|
|
|
396
399
|
| Scope | Path |
|
|
397
400
|
|-------|------|
|
|
398
401
|
| Builtin | `~/.pi/agent/extensions/subagent/agents/` |
|
|
399
|
-
| User | `~/.pi/agent/agents
|
|
400
|
-
| Project | `.pi/agents
|
|
402
|
+
| User | `~/.pi/agent/agents/**/*.md` |
|
|
403
|
+
| Project | `.pi/agents/**/*.md` |
|
|
401
404
|
|
|
402
|
-
Project discovery also reads legacy `.agents
|
|
405
|
+
Project discovery also reads legacy `.agents/**/*.md` files. Nested subdirectories are discovered recursively. `.chain.md` files are treated as chains, not agents. If both `.agents/` and `.pi/agents/` define the same parsed runtime agent name, `.pi/agents/` wins. Use `agentScope: "user" | "project" | "both"` to control discovery; `both` is the default and project definitions win runtime-name collisions.
|
|
403
406
|
|
|
404
407
|
Builtin agents load at the lowest priority, so a user or project agent with the same name overrides them. `oracle` is an advisory reviewer that critiques direction and proposes an execution prompt without editing files. `worker` is the implementation agent for normal tasks and approved oracle handoffs.
|
|
405
408
|
|
|
@@ -458,6 +461,8 @@ A typical agent looks like this:
|
|
|
458
461
|
```yaml
|
|
459
462
|
---
|
|
460
463
|
name: scout
|
|
464
|
+
# Optional: registers this as code-analysis.scout while preserving name: scout
|
|
465
|
+
package: code-analysis
|
|
461
466
|
description: Fast codebase recon
|
|
462
467
|
tools: read, grep, find, ls, bash, mcp:chrome-devtools
|
|
463
468
|
extensions:
|
|
@@ -482,6 +487,7 @@ Important fields:
|
|
|
482
487
|
|
|
483
488
|
| Field | Notes |
|
|
484
489
|
|-------|-------|
|
|
490
|
+
| `package` | Optional package identifier. A file with `name: scout` and `package: code-analysis` registers as `code-analysis.scout`; serialization keeps `name` and `package` separate. |
|
|
485
491
|
| `tools` | Builtin tool allowlist. `mcp:` entries select direct MCP tools when `pi-mcp-adapter` is installed. |
|
|
486
492
|
| `extensions` | Omitted means normal extensions; empty means no extensions; comma-separated values allowlist specific extensions. |
|
|
487
493
|
| `model` | Default model. Bare ids prefer the current provider when possible, then unique registry matches. |
|
|
@@ -530,10 +536,10 @@ Chains are reusable `.chain.md` workflows stored next to agent files.
|
|
|
530
536
|
|
|
531
537
|
| Scope | Path |
|
|
532
538
|
|-------|------|
|
|
533
|
-
| User | `~/.pi/agent/agents
|
|
534
|
-
| Project | `.pi/agents
|
|
539
|
+
| User | `~/.pi/agent/agents/**/*.chain.md` |
|
|
540
|
+
| Project | `.pi/agents/**/*.chain.md` |
|
|
535
541
|
|
|
536
|
-
Project discovery also reads legacy `.agents
|
|
542
|
+
Project discovery also reads legacy `.agents/**/*.chain.md` files. Nested subdirectories are discovered recursively. If both locations define the same parsed runtime chain name, `.pi/agents/` wins. Chains support the same optional `package` frontmatter as agents; `name: review-flow` plus `package: code-analysis` runs as `code-analysis.review-flow`.
|
|
537
543
|
|
|
538
544
|
Example:
|
|
539
545
|
|
|
@@ -556,9 +562,9 @@ progress: true
|
|
|
556
562
|
Create an implementation plan based on {previous}
|
|
557
563
|
```
|
|
558
564
|
|
|
559
|
-
Each `## agent-name` section is a step. Config lines such as `output`, `reads`, `model`, `skills`, and `progress` go immediately after the header. A blank line separates config from task text.
|
|
565
|
+
Each `## agent-name` section is a step. Config lines such as `output`, `outputMode`, `reads`, `model`, `skills`, and `progress` go immediately after the header. A blank line separates config from task text.
|
|
560
566
|
|
|
561
|
-
|
|
567
|
+
For `output`, `reads`, `skills`, and `progress`, chain behavior is three-state: omitted inherits from the agent, a value overrides, and `false` disables.
|
|
562
568
|
|
|
563
569
|
Create chains from the Agents Manager template picker, save them from the chain-clarify TUI, or write them by hand. Run them with natural language, `/agents`, or:
|
|
564
570
|
|
|
@@ -645,6 +651,7 @@ These are the parameters the LLM passes when it calls the `subagent` tool. Most
|
|
|
645
651
|
{ agent: "worker", task: "refactor auth" }
|
|
646
652
|
{ agent: "scout", task: "find todos", maxOutput: { lines: 1000 } }
|
|
647
653
|
{ agent: "scout", task: "investigate", output: false }
|
|
654
|
+
{ agent: "scout", task: "write a large report", output: "reports/scout.md", outputMode: "file-only" }
|
|
648
655
|
|
|
649
656
|
// Forked context
|
|
650
657
|
{ agent: "worker", task: "continue this thread", context: "fork" }
|
|
@@ -690,10 +697,12 @@ Agent definitions are not loaded into context by default. Management actions let
|
|
|
690
697
|
{ action: "list" }
|
|
691
698
|
{ action: "list", agentScope: "project" }
|
|
692
699
|
{ action: "get", agent: "scout" }
|
|
700
|
+
{ action: "get", agent: "code-analysis.scout" }
|
|
693
701
|
{ action: "get", chainName: "review-pipeline" }
|
|
694
702
|
|
|
695
703
|
{ action: "create", config: {
|
|
696
704
|
name: "Code Scout",
|
|
705
|
+
package: "code-analysis",
|
|
697
706
|
description: "Scans codebases for patterns and issues",
|
|
698
707
|
scope: "user",
|
|
699
708
|
systemPrompt: "You are a code scout...",
|
|
@@ -721,13 +730,13 @@ Agent definitions are not loaded into context by default. Management actions let
|
|
|
721
730
|
]
|
|
722
731
|
}}
|
|
723
732
|
|
|
724
|
-
{ action: "update", agent: "scout", config: { model: "openai/gpt-4o" } }
|
|
733
|
+
{ action: "update", agent: "code-analysis.scout", config: { model: "openai/gpt-4o" } }
|
|
725
734
|
{ action: "update", chainName: "review-pipeline", config: { steps: [...] } }
|
|
726
735
|
{ action: "delete", agent: "scout" }
|
|
727
736
|
{ action: "delete", chainName: "review-pipeline" }
|
|
728
737
|
```
|
|
729
738
|
|
|
730
|
-
`create` uses `config.scope`, not `agentScope`. `update` and `delete` use `agentScope` only when the same name exists in multiple scopes. To clear optional string fields, set them to `false` or `""`.
|
|
739
|
+
`create` uses `config.scope`, not `agentScope`. `config.name` is the local frontmatter name; optional `config.package` registers the runtime name as `{package}.{name}` and is saved as separate `name` and `package` frontmatter. `update` and `delete` use the runtime name and `agentScope` only when the same runtime name exists in multiple scopes. To clear optional string fields, including `package`, set them to `false` or `""`.
|
|
731
740
|
|
|
732
741
|
### Parameter reference
|
|
733
742
|
|
|
@@ -739,9 +748,10 @@ Agent definitions are not loaded into context by default. Management actions let
|
|
|
739
748
|
| `chainName` | string | - | Chain name for management actions. |
|
|
740
749
|
| `config` | object/string | - | Agent or chain config for create/update. |
|
|
741
750
|
| `output` | `string \| false` | agent default | Override single-agent output file. |
|
|
751
|
+
| `outputMode` | `"inline" \| "file-only"` | `inline` | Return saved output inline or as a concise saved-file reference. `file-only` requires an `output` path. |
|
|
742
752
|
| `skill` | `string \| string[] \| false` | agent default | Override skills or disable all. |
|
|
743
753
|
| `model` | string | agent default | Override model. |
|
|
744
|
-
| `tasks` | array | - | Top-level parallel tasks. Supports `agent`, `task`, `cwd`, `count`, `output`, `reads`, `progress`, `skill`, and `model`. |
|
|
754
|
+
| `tasks` | array | - | Top-level parallel tasks. Supports `agent`, `task`, `cwd`, `count`, `output`, `outputMode`, `reads`, `progress`, `skill`, and `model`. |
|
|
745
755
|
| `concurrency` | number | config or `4` | Top-level parallel concurrency. |
|
|
746
756
|
| `worktree` | boolean | false | Create isolated git worktrees for parallel tasks. |
|
|
747
757
|
| `chain` | array | - | Sequential and parallel chain steps. |
|
|
@@ -759,7 +769,9 @@ Agent definitions are not loaded into context by default. Management actions let
|
|
|
759
769
|
|
|
760
770
|
`context: "fork"` fails fast when the parent session is not persisted, the current leaf is missing, or the branched child session cannot be created. It never silently downgrades to `fresh`. In multi-agent runs, if any requested agent has `defaultContext: fork` and the launch omits `context`, the whole invocation uses forked context; pass `context: "fresh"` when you intentionally want a fresh run.
|
|
761
771
|
|
|
762
|
-
|
|
772
|
+
Use `outputMode: "file-only"` when a saved output may be large and the parent only needs a pointer. The returned text is a compact reference like `Output saved to: /abs/report.md (48.2 KB, 2847 lines). Read this file if needed.` Failed runs and save errors still return normal inline output for debugging. In chains, later `{previous}` steps receive the same compact reference when the prior step used file-only mode.
|
|
773
|
+
|
|
774
|
+
Sequential and parallel chain tasks accept `agent`, `task`, `cwd`, `output`, `outputMode`, `reads`, `progress`, `skill`, and `model`. Parallel tasks also accept `count`. Parallel step groups accept `parallel`, `concurrency`, `failFast`, and `worktree`.
|
|
763
775
|
|
|
764
776
|
Status and control actions:
|
|
765
777
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-subagents",
|
|
3
|
-
"version": "0.21.
|
|
3
|
+
"version": "0.21.5",
|
|
4
4
|
"description": "Pi extension for delegating tasks to subagents with chains, parallel execution, and TUI clarification",
|
|
5
5
|
"author": "Nico Bailon",
|
|
6
6
|
"license": "MIT",
|
|
@@ -57,12 +57,27 @@
|
|
|
57
57
|
"@mariozechner/pi-coding-agent": "*",
|
|
58
58
|
"@mariozechner/pi-tui": "*"
|
|
59
59
|
},
|
|
60
|
+
"peerDependenciesMeta": {
|
|
61
|
+
"@mariozechner/pi-agent-core": {
|
|
62
|
+
"optional": true
|
|
63
|
+
},
|
|
64
|
+
"@mariozechner/pi-ai": {
|
|
65
|
+
"optional": true
|
|
66
|
+
},
|
|
67
|
+
"@mariozechner/pi-coding-agent": {
|
|
68
|
+
"optional": true
|
|
69
|
+
},
|
|
70
|
+
"@mariozechner/pi-tui": {
|
|
71
|
+
"optional": true
|
|
72
|
+
}
|
|
73
|
+
},
|
|
60
74
|
"dependencies": {
|
|
61
75
|
"typebox": "^1.1.24"
|
|
62
76
|
},
|
|
63
77
|
"devDependencies": {
|
|
64
78
|
"@mariozechner/pi-agent-core": "^0.65.0",
|
|
65
79
|
"@mariozechner/pi-ai": "^0.65.0",
|
|
66
|
-
"@mariozechner/pi-coding-agent": "^0.65.0"
|
|
80
|
+
"@mariozechner/pi-coding-agent": "^0.65.0",
|
|
81
|
+
"@mariozechner/pi-tui": "^0.65.2"
|
|
67
82
|
}
|
|
68
83
|
}
|
|
@@ -178,16 +178,18 @@ agent with the same name only when you want a substantially different agent.
|
|
|
178
178
|
## Discovery and Scope Rules
|
|
179
179
|
|
|
180
180
|
Agent files can live in:
|
|
181
|
-
- `~/.pi/agent/agents
|
|
182
|
-
- `.pi/agents
|
|
183
|
-
- legacy `.agents
|
|
181
|
+
- `~/.pi/agent/agents/**/*.md` — user scope
|
|
182
|
+
- `.pi/agents/**/*.md` — canonical project scope
|
|
183
|
+
- legacy `.agents/**/*.md` — still read for compatibility, but `.pi/agents/` wins on conflicts
|
|
184
184
|
|
|
185
185
|
Chains live in:
|
|
186
|
-
- `~/.pi/agent/agents
|
|
187
|
-
- `.pi/agents
|
|
188
|
-
- legacy `.agents
|
|
186
|
+
- `~/.pi/agent/agents/**/*.chain.md`
|
|
187
|
+
- `.pi/agents/**/*.chain.md`
|
|
188
|
+
- legacy `.agents/**/*.chain.md`
|
|
189
189
|
|
|
190
|
-
|
|
190
|
+
Discovery is recursive. `.chain.md` files are chains, not agents. Agents and chains can set optional frontmatter `package: code-analysis`; `name: scout` plus `package: code-analysis` registers as runtime name `code-analysis.scout` while serialization keeps `name` and `package` separate.
|
|
191
|
+
|
|
192
|
+
Precedence is by parsed runtime name:
|
|
191
193
|
1. project scope
|
|
192
194
|
2. user scope
|
|
193
195
|
3. builtin agents
|
|
@@ -241,7 +243,7 @@ subagent({
|
|
|
241
243
|
})
|
|
242
244
|
```
|
|
243
245
|
|
|
244
|
-
Avoid duplicate output paths in parallel tasks. Concurrent children should not write to the same file.
|
|
246
|
+
Avoid duplicate output paths in parallel tasks. Concurrent children should not write to the same file. For large saved outputs, set `outputMode: "file-only"` together with an `output` path. The parent result then contains only a compact reference like `Output saved to: /abs/report.md (48.2 KB, 2847 lines). Read this file if needed.` instead of the full saved content. Do not use `output: false` for this; `output: false` means no file output. Failed runs and save errors still return inline details for debugging.
|
|
245
247
|
|
|
246
248
|
### Chain execution
|
|
247
249
|
|
|
@@ -271,6 +273,8 @@ subagent({
|
|
|
271
273
|
})
|
|
272
274
|
```
|
|
273
275
|
|
|
276
|
+
File-only output mode also works for async single runs, top-level parallel task items, sequential chain steps, and chain parallel task items. In chains, `{previous}` receives the compact saved-file reference when the prior step used file-only mode.
|
|
277
|
+
|
|
274
278
|
For review fanout where the parent continues a local audit:
|
|
275
279
|
|
|
276
280
|
```typescript
|
|
@@ -451,6 +455,7 @@ subagent({
|
|
|
451
455
|
action: "create",
|
|
452
456
|
config: {
|
|
453
457
|
name: "my-agent",
|
|
458
|
+
package: "code-analysis",
|
|
454
459
|
description: "Project-specific implementation helper",
|
|
455
460
|
systemPrompt: "Your system prompt here.",
|
|
456
461
|
systemPromptMode: "replace",
|
|
@@ -465,7 +470,7 @@ subagent({
|
|
|
465
470
|
```typescript
|
|
466
471
|
subagent({
|
|
467
472
|
action: "update",
|
|
468
|
-
agent: "my-agent",
|
|
473
|
+
agent: "code-analysis.my-agent",
|
|
469
474
|
config: {
|
|
470
475
|
thinking: "high"
|
|
471
476
|
}
|
|
@@ -475,13 +480,13 @@ subagent({
|
|
|
475
480
|
### Delete an agent
|
|
476
481
|
|
|
477
482
|
```typescript
|
|
478
|
-
subagent({ action: "delete", agent: "my-agent" })
|
|
483
|
+
subagent({ action: "delete", agent: "code-analysis.my-agent" })
|
|
479
484
|
```
|
|
480
485
|
|
|
481
486
|
Use management actions when the system needs to create or edit subagents on
|
|
482
487
|
demand without dropping into raw file editing.
|
|
483
488
|
|
|
484
|
-
Management actions create or update user/project agent files. For small builtin changes such as a model swap, prefer `/agents` builtin overrides or `subagents.agentOverrides` in settings.
|
|
489
|
+
Management actions create or update user/project agent files. `config.name` is the local frontmatter name; optional `config.package` registers and looks up the runtime name as `{package}.{name}`. Use the dotted runtime name for `get`, `update`, `delete`, slash commands, and chain steps. For small builtin changes such as a model swap, prefer `/agents` builtin overrides or `subagents.agentOverrides` in settings.
|
|
485
490
|
|
|
486
491
|
## Creating and Editing Agents by File
|
|
487
492
|
|
|
@@ -490,6 +495,7 @@ A minimal agent file looks like this:
|
|
|
490
495
|
```markdown
|
|
491
496
|
---
|
|
492
497
|
name: my-agent
|
|
498
|
+
package: code-analysis
|
|
493
499
|
description: What this agent does
|
|
494
500
|
model: openai-codex/gpt-5.4
|
|
495
501
|
thinking: high
|
|
@@ -502,7 +508,7 @@ inheritSkills: false
|
|
|
502
508
|
Your system prompt here.
|
|
503
509
|
```
|
|
504
510
|
|
|
505
|
-
That is only a starting point. Common optional fields include:
|
|
511
|
+
That is only a starting point. Omit `package` for the traditional unqualified runtime name. Common optional fields include:
|
|
506
512
|
- `defaultProgress`
|
|
507
513
|
- `defaultReads`
|
|
508
514
|
- `output`
|
|
@@ -12,6 +12,9 @@ import {
|
|
|
12
12
|
defaultInheritSkills,
|
|
13
13
|
defaultSystemPromptMode,
|
|
14
14
|
discoverAgentsAll,
|
|
15
|
+
buildRuntimeName,
|
|
16
|
+
frontmatterNameForConfig,
|
|
17
|
+
parsePackageName,
|
|
15
18
|
} from "./agents.ts";
|
|
16
19
|
import { serializeAgent } from "./agent-serializer.ts";
|
|
17
20
|
import { serializeChain } from "./chain-serializer.ts";
|
|
@@ -71,6 +74,10 @@ function sanitizeName(name: string): string {
|
|
|
71
74
|
return name.toLowerCase().trim().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
|
|
72
75
|
}
|
|
73
76
|
|
|
77
|
+
function parsePackageConfig(value: unknown): { packageName?: string; error?: string } {
|
|
78
|
+
return parsePackageName(value, "config.package");
|
|
79
|
+
}
|
|
80
|
+
|
|
74
81
|
function allAgents(d: { builtin: AgentConfig[]; user: AgentConfig[]; project: AgentConfig[] }): AgentConfig[] {
|
|
75
82
|
return [...d.builtin, ...d.user, ...d.project];
|
|
76
83
|
}
|
|
@@ -167,6 +174,10 @@ function parseStepList(raw: unknown): { steps?: ChainStepConfig[]; error?: strin
|
|
|
167
174
|
else if (typeof s.output === "string") step.output = s.output;
|
|
168
175
|
else return { error: `config.steps[${i}].output must be a string or false.` };
|
|
169
176
|
}
|
|
177
|
+
if (hasKey(s, "outputMode")) {
|
|
178
|
+
if (s.outputMode === "inline" || s.outputMode === "file-only") step.outputMode = s.outputMode;
|
|
179
|
+
else return { error: `config.steps[${i}].outputMode must be 'inline' or 'file-only'.` };
|
|
180
|
+
}
|
|
170
181
|
if (hasKey(s, "reads")) {
|
|
171
182
|
if (s.reads === false) step.reads = false;
|
|
172
183
|
else if (Array.isArray(s.reads)) step.reads = s.reads.filter((v): v is string => typeof v === "string").map((v) => v.trim()).filter(Boolean);
|
|
@@ -336,6 +347,10 @@ function renamePath(
|
|
|
336
347
|
function formatAgentDetail(agent: AgentConfig): string {
|
|
337
348
|
const tools = [...(agent.tools ?? []), ...(agent.mcpDirectTools ?? []).map((t) => `mcp:${t}`)];
|
|
338
349
|
const lines: string[] = [`Agent: ${agent.name} (${agent.source})`, `Path: ${agent.filePath}`, `Description: ${agent.description}`];
|
|
350
|
+
if (agent.packageName) {
|
|
351
|
+
lines.push(`Local name: ${frontmatterNameForConfig(agent)}`);
|
|
352
|
+
lines.push(`Package: ${agent.packageName}`);
|
|
353
|
+
}
|
|
339
354
|
if (agent.model) lines.push(`Model: ${agent.model}`);
|
|
340
355
|
if (agent.fallbackModels?.length) lines.push(`Fallback models: ${agent.fallbackModels.join(", ")}`);
|
|
341
356
|
if (tools.length) lines.push(`Tools: ${tools.join(", ")}`);
|
|
@@ -356,13 +371,19 @@ function formatAgentDetail(agent: AgentConfig): string {
|
|
|
356
371
|
}
|
|
357
372
|
|
|
358
373
|
function formatChainDetail(chain: ChainConfig): string {
|
|
359
|
-
const lines: string[] = [`Chain: ${chain.name} (${chain.source})`, `Path: ${chain.filePath}`, `Description: ${chain.description}
|
|
374
|
+
const lines: string[] = [`Chain: ${chain.name} (${chain.source})`, `Path: ${chain.filePath}`, `Description: ${chain.description}`];
|
|
375
|
+
if (chain.packageName) {
|
|
376
|
+
lines.push(`Local name: ${frontmatterNameForConfig(chain)}`);
|
|
377
|
+
lines.push(`Package: ${chain.packageName}`);
|
|
378
|
+
}
|
|
379
|
+
lines.push("", "Steps:");
|
|
360
380
|
for (let i = 0; i < chain.steps.length; i++) {
|
|
361
381
|
const s = chain.steps[i]!;
|
|
362
382
|
lines.push(`${i + 1}. ${s.agent}`);
|
|
363
383
|
if (s.task.trim()) lines.push(` Task: ${s.task}`);
|
|
364
384
|
if (s.output === false) lines.push(" Output: false");
|
|
365
385
|
else if (s.output) lines.push(` Output: ${s.output}`);
|
|
386
|
+
if (s.outputMode) lines.push(` Output mode: ${s.outputMode}`);
|
|
366
387
|
if (s.reads === false) lines.push(" Reads: false");
|
|
367
388
|
else if (Array.isArray(s.reads) && s.reads.length > 0) lines.push(` Reads: ${s.reads.join(", ")}`);
|
|
368
389
|
if (s.model) lines.push(` Model: ${s.model}`);
|
|
@@ -430,6 +451,9 @@ export function handleCreate(params: ManagementParams, ctx: ManagementContext):
|
|
|
430
451
|
if (typeof cfg.description !== "string" || !cfg.description.trim()) return result("config.description is required and must be a non-empty string.", true);
|
|
431
452
|
const name = sanitizeName(cfg.name);
|
|
432
453
|
if (!name) return result("config.name is invalid after sanitization. Use letters, numbers, spaces, or hyphens.", true);
|
|
454
|
+
const parsedPackage = parsePackageConfig(cfg.package);
|
|
455
|
+
if (parsedPackage.error) return result(parsedPackage.error, true);
|
|
456
|
+
const runtimeName = buildRuntimeName(name, parsedPackage.packageName);
|
|
433
457
|
const scopeRaw = cfg.scope ?? "user";
|
|
434
458
|
if (scopeRaw !== "user" && scopeRaw !== "project") return result("config.scope must be 'user' or 'project'.", true);
|
|
435
459
|
const scope = scopeRaw as ManagementScope;
|
|
@@ -437,23 +461,25 @@ export function handleCreate(params: ManagementParams, ctx: ManagementContext):
|
|
|
437
461
|
const d = discoverAgentsAll(ctx.cwd);
|
|
438
462
|
const targetDir = scope === "user" ? d.userDir : d.projectDir ?? path.join(ctx.cwd, ".pi", "agents");
|
|
439
463
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
440
|
-
if (nameExistsInScope(ctx.cwd, scope,
|
|
441
|
-
const targetPath = path.join(targetDir, isChain ? `${
|
|
464
|
+
if (nameExistsInScope(ctx.cwd, scope, runtimeName)) return result(`Name '${runtimeName}' already exists in ${scope} scope. Use update instead.`, true);
|
|
465
|
+
const targetPath = path.join(targetDir, isChain ? `${runtimeName}.chain.md` : `${runtimeName}.md`);
|
|
442
466
|
if (fs.existsSync(targetPath)) return result(`File already exists at ${targetPath} but is not a valid ${isChain ? "chain" : "agent"} definition. Remove or rename it first.`, true);
|
|
443
467
|
const warnings: string[] = [];
|
|
444
|
-
if (!isChain && d.builtin.some((a) => a.name ===
|
|
468
|
+
if (!isChain && d.builtin.some((a) => a.name === runtimeName)) warnings.push(`Note: this shadows the builtin agent '${runtimeName}'.`);
|
|
445
469
|
if (isChain) {
|
|
446
470
|
const parsed = parseStepList(cfg.steps);
|
|
447
471
|
if (parsed.error) return result(parsed.error, true);
|
|
448
|
-
const chain: ChainConfig = { name, description: cfg.description.trim(), source: scope, filePath: targetPath, steps: parsed.steps! };
|
|
472
|
+
const chain: ChainConfig = { name: runtimeName, localName: name, packageName: parsedPackage.packageName, description: cfg.description.trim(), source: scope, filePath: targetPath, steps: parsed.steps! };
|
|
449
473
|
fs.writeFileSync(targetPath, serializeChain(chain), "utf-8");
|
|
450
474
|
const missing = unknownChainAgents(ctx.cwd, chain.steps);
|
|
451
475
|
if (missing.length) warnings.push(`Warning: chain steps reference unknown agents: ${missing.join(", ")}.`);
|
|
452
476
|
warnings.push(...chainStepWarnings(ctx, chain.steps));
|
|
453
|
-
return result([`Created chain '${
|
|
477
|
+
return result([`Created chain '${runtimeName}' at ${targetPath}.`, ...warnings].join("\n"));
|
|
454
478
|
}
|
|
455
479
|
const agent: AgentConfig = {
|
|
456
|
-
name,
|
|
480
|
+
name: runtimeName,
|
|
481
|
+
localName: name,
|
|
482
|
+
packageName: parsedPackage.packageName,
|
|
457
483
|
description: cfg.description.trim(),
|
|
458
484
|
source: scope,
|
|
459
485
|
filePath: targetPath,
|
|
@@ -471,7 +497,7 @@ export function handleCreate(params: ManagementParams, ctx: ManagementContext):
|
|
|
471
497
|
const sw = skillsWarning(ctx.cwd, agent.skills);
|
|
472
498
|
if (sw) warnings.push(sw);
|
|
473
499
|
fs.writeFileSync(targetPath, serializeAgent(agent), "utf-8");
|
|
474
|
-
return result([`Created agent '${
|
|
500
|
+
return result([`Created agent '${runtimeName}' at ${targetPath}.`, ...warnings].join("\n"));
|
|
475
501
|
}
|
|
476
502
|
|
|
477
503
|
export function handleUpdate(params: ManagementParams, ctx: ManagementContext): AgentToolResult<Details> {
|
|
@@ -491,14 +517,22 @@ export function handleUpdate(params: ManagementParams, ctx: ManagementContext):
|
|
|
491
517
|
const oldName = target.name;
|
|
492
518
|
if (hasKey(cfg, "name") && (typeof cfg.name !== "string" || !cfg.name.trim())) return result("config.name must be a non-empty string when provided.", true);
|
|
493
519
|
if (hasKey(cfg, "description") && (typeof cfg.description !== "string" || !cfg.description.trim())) return result("config.description must be a non-empty string when provided.", true);
|
|
494
|
-
let
|
|
520
|
+
let newLocalName = target.localName ?? frontmatterNameForConfig(target);
|
|
495
521
|
if (hasKey(cfg, "name")) {
|
|
496
|
-
|
|
497
|
-
if (!
|
|
522
|
+
newLocalName = sanitizeName(cfg.name as string);
|
|
523
|
+
if (!newLocalName) return result("config.name is invalid after sanitization.", true);
|
|
524
|
+
}
|
|
525
|
+
let newPackageName = target.packageName;
|
|
526
|
+
if (hasKey(cfg, "package")) {
|
|
527
|
+
const parsedPackage = parsePackageConfig(cfg.package);
|
|
528
|
+
if (parsedPackage.error) return result(parsedPackage.error, true);
|
|
529
|
+
newPackageName = parsedPackage.packageName;
|
|
498
530
|
}
|
|
499
531
|
const applyError = applyAgentConfig(updated, cfg);
|
|
500
532
|
if (applyError) return result(applyError, true);
|
|
501
|
-
|
|
533
|
+
updated.localName = newLocalName;
|
|
534
|
+
updated.packageName = newPackageName;
|
|
535
|
+
updated.name = buildRuntimeName(newLocalName, newPackageName);
|
|
502
536
|
if (hasKey(cfg, "description")) updated.description = (cfg.description as string).trim();
|
|
503
537
|
if (hasKey(cfg, "model")) {
|
|
504
538
|
const mw = modelWarning(ctx, updated.model);
|
|
@@ -535,10 +569,16 @@ export function handleUpdate(params: ManagementParams, ctx: ManagementContext):
|
|
|
535
569
|
const oldName = target.name;
|
|
536
570
|
if (hasKey(cfg, "name") && (typeof cfg.name !== "string" || !cfg.name.trim())) return result("config.name must be a non-empty string when provided.", true);
|
|
537
571
|
if (hasKey(cfg, "description") && (typeof cfg.description !== "string" || !cfg.description.trim())) return result("config.description must be a non-empty string when provided.", true);
|
|
538
|
-
let
|
|
572
|
+
let newLocalName = target.localName ?? frontmatterNameForConfig(target);
|
|
539
573
|
if (hasKey(cfg, "name")) {
|
|
540
|
-
|
|
541
|
-
if (!
|
|
574
|
+
newLocalName = sanitizeName(cfg.name as string);
|
|
575
|
+
if (!newLocalName) return result("config.name is invalid after sanitization.", true);
|
|
576
|
+
}
|
|
577
|
+
let newPackageName = target.packageName;
|
|
578
|
+
if (hasKey(cfg, "package")) {
|
|
579
|
+
const parsedPackage = parsePackageConfig(cfg.package);
|
|
580
|
+
if (parsedPackage.error) return result(parsedPackage.error, true);
|
|
581
|
+
newPackageName = parsedPackage.packageName;
|
|
542
582
|
}
|
|
543
583
|
let parsedSteps: ChainStepConfig[] | undefined;
|
|
544
584
|
if (hasKey(cfg, "steps")) {
|
|
@@ -546,7 +586,9 @@ export function handleUpdate(params: ManagementParams, ctx: ManagementContext):
|
|
|
546
586
|
if (parsed.error) return result(parsed.error, true);
|
|
547
587
|
parsedSteps = parsed.steps!;
|
|
548
588
|
}
|
|
549
|
-
|
|
589
|
+
updated.localName = newLocalName;
|
|
590
|
+
updated.packageName = newPackageName;
|
|
591
|
+
updated.name = buildRuntimeName(newLocalName, newPackageName);
|
|
550
592
|
if (hasKey(cfg, "description")) updated.description = (cfg.description as string).trim();
|
|
551
593
|
if (parsedSteps) {
|
|
552
594
|
updated.steps = parsedSteps;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import type { AgentConfig } from "./agents.ts";
|
|
3
|
+
import { frontmatterNameForConfig } from "./identity.ts";
|
|
3
4
|
|
|
4
5
|
export const KNOWN_FIELDS = new Set([
|
|
5
6
|
"name",
|
|
7
|
+
"package",
|
|
6
8
|
"description",
|
|
7
9
|
"tools",
|
|
8
10
|
"model",
|
|
@@ -30,7 +32,8 @@ function joinComma(values: string[] | undefined): string | undefined {
|
|
|
30
32
|
export function serializeAgent(config: AgentConfig): string {
|
|
31
33
|
const lines: string[] = [];
|
|
32
34
|
lines.push("---");
|
|
33
|
-
lines.push(`name: ${config
|
|
35
|
+
lines.push(`name: ${frontmatterNameForConfig(config)}`);
|
|
36
|
+
if (config.packageName) lines.push(`package: ${config.packageName}`);
|
|
34
37
|
lines.push(`description: ${config.description}`);
|
|
35
38
|
|
|
36
39
|
const tools = [
|
package/src/agents/agents.ts
CHANGED
|
@@ -6,10 +6,13 @@ import * as fs from "node:fs";
|
|
|
6
6
|
import * as os from "node:os";
|
|
7
7
|
import * as path from "node:path";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
|
+
import type { OutputMode } from "../shared/types.ts";
|
|
9
10
|
import { KNOWN_FIELDS } from "./agent-serializer.ts";
|
|
10
11
|
import { parseChain } from "./chain-serializer.ts";
|
|
11
12
|
import { mergeAgentsForScope } from "./agent-selection.ts";
|
|
12
13
|
import { parseFrontmatter } from "./frontmatter.ts";
|
|
14
|
+
import { buildRuntimeName, parsePackageName } from "./identity.ts";
|
|
15
|
+
export { buildRuntimeName, frontmatterNameForConfig, parsePackageName } from "./identity.ts";
|
|
13
16
|
|
|
14
17
|
export type AgentScope = "user" | "project" | "both";
|
|
15
18
|
|
|
@@ -66,6 +69,8 @@ interface BuiltinAgentOverrideInfo {
|
|
|
66
69
|
|
|
67
70
|
export interface AgentConfig {
|
|
68
71
|
name: string;
|
|
72
|
+
localName?: string;
|
|
73
|
+
packageName?: string;
|
|
69
74
|
description: string;
|
|
70
75
|
tools?: string[];
|
|
71
76
|
mcpDirectTools?: string[];
|
|
@@ -102,6 +107,7 @@ export interface ChainStepConfig {
|
|
|
102
107
|
agent: string;
|
|
103
108
|
task: string;
|
|
104
109
|
output?: string | false;
|
|
110
|
+
outputMode?: OutputMode;
|
|
105
111
|
reads?: string[] | false;
|
|
106
112
|
model?: string;
|
|
107
113
|
skills?: string[] | false;
|
|
@@ -110,6 +116,8 @@ export interface ChainStepConfig {
|
|
|
110
116
|
|
|
111
117
|
export interface ChainConfig {
|
|
112
118
|
name: string;
|
|
119
|
+
localName?: string;
|
|
120
|
+
packageName?: string;
|
|
113
121
|
description: string;
|
|
114
122
|
source: AgentSource;
|
|
115
123
|
filePath: string;
|
|
@@ -505,26 +513,34 @@ export function removeBuiltinAgentOverride(cwd: string, name: string, scope: "us
|
|
|
505
513
|
return filePath;
|
|
506
514
|
}
|
|
507
515
|
|
|
508
|
-
function
|
|
509
|
-
const
|
|
510
|
-
|
|
511
|
-
if (!fs.existsSync(dir)) {
|
|
512
|
-
return agents;
|
|
513
|
-
}
|
|
516
|
+
function listMarkdownFilesRecursive(dir: string, predicate: (fileName: string) => boolean): string[] {
|
|
517
|
+
const files: string[] = [];
|
|
518
|
+
if (!fs.existsSync(dir)) return files;
|
|
514
519
|
|
|
515
520
|
let entries: fs.Dirent[];
|
|
516
521
|
try {
|
|
517
|
-
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
522
|
+
entries = fs.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
|
|
518
523
|
} catch {
|
|
519
|
-
return
|
|
524
|
+
return files;
|
|
520
525
|
}
|
|
521
526
|
|
|
522
527
|
for (const entry of entries) {
|
|
523
|
-
|
|
524
|
-
if (entry.
|
|
528
|
+
const filePath = path.join(dir, entry.name);
|
|
529
|
+
if (entry.isDirectory()) {
|
|
530
|
+
files.push(...listMarkdownFilesRecursive(filePath, predicate));
|
|
531
|
+
continue;
|
|
532
|
+
}
|
|
525
533
|
if (!entry.isFile() && !entry.isSymbolicLink()) continue;
|
|
534
|
+
if (!predicate(entry.name)) continue;
|
|
535
|
+
files.push(filePath);
|
|
536
|
+
}
|
|
537
|
+
return files;
|
|
538
|
+
}
|
|
526
539
|
|
|
527
|
-
|
|
540
|
+
function loadAgentsFromDir(dir: string, source: AgentSource): AgentConfig[] {
|
|
541
|
+
const agents: AgentConfig[] = [];
|
|
542
|
+
|
|
543
|
+
for (const filePath of listMarkdownFilesRecursive(dir, (fileName) => fileName.endsWith(".md") && !fileName.endsWith(".chain.md"))) {
|
|
528
544
|
let content: string;
|
|
529
545
|
try {
|
|
530
546
|
content = fs.readFileSync(filePath, "utf-8");
|
|
@@ -538,6 +554,12 @@ function loadAgentsFromDir(dir: string, source: AgentSource): AgentConfig[] {
|
|
|
538
554
|
continue;
|
|
539
555
|
}
|
|
540
556
|
|
|
557
|
+
const localName = frontmatter.name;
|
|
558
|
+
const parsedPackage = parsePackageName(frontmatter.package, `Agent '${localName}' package`);
|
|
559
|
+
if (parsedPackage.error) continue;
|
|
560
|
+
const packageName = parsedPackage.packageName;
|
|
561
|
+
const runtimeName = buildRuntimeName(localName, packageName);
|
|
562
|
+
|
|
541
563
|
const rawTools = frontmatter.tools
|
|
542
564
|
?.split(",")
|
|
543
565
|
.map((t) => t.trim())
|
|
@@ -573,12 +595,12 @@ function loadAgentsFromDir(dir: string, source: AgentSource): AgentConfig[] {
|
|
|
573
595
|
? "replace"
|
|
574
596
|
: frontmatter.systemPromptMode === "append"
|
|
575
597
|
? "append"
|
|
576
|
-
: defaultSystemPromptMode(
|
|
598
|
+
: defaultSystemPromptMode(localName);
|
|
577
599
|
const inheritProjectContext = frontmatter.inheritProjectContext === "true"
|
|
578
600
|
? true
|
|
579
601
|
: frontmatter.inheritProjectContext === "false"
|
|
580
602
|
? false
|
|
581
|
-
: defaultInheritProjectContext(
|
|
603
|
+
: defaultInheritProjectContext(localName);
|
|
582
604
|
const inheritSkills = frontmatter.inheritSkills === "true"
|
|
583
605
|
? true
|
|
584
606
|
: frontmatter.inheritSkills === "false"
|
|
@@ -606,7 +628,9 @@ function loadAgentsFromDir(dir: string, source: AgentSource): AgentConfig[] {
|
|
|
606
628
|
const parsedMaxSubagentDepth = Number(frontmatter.maxSubagentDepth);
|
|
607
629
|
|
|
608
630
|
agents.push({
|
|
609
|
-
name:
|
|
631
|
+
name: runtimeName,
|
|
632
|
+
localName,
|
|
633
|
+
packageName,
|
|
610
634
|
description: frontmatter.description,
|
|
611
635
|
tools: tools.length > 0 ? tools : undefined,
|
|
612
636
|
mcpDirectTools: mcpDirectTools.length > 0 ? mcpDirectTools : undefined,
|
|
@@ -640,22 +664,7 @@ function loadAgentsFromDir(dir: string, source: AgentSource): AgentConfig[] {
|
|
|
640
664
|
function loadChainsFromDir(dir: string, source: AgentSource): ChainConfig[] {
|
|
641
665
|
const chains: ChainConfig[] = [];
|
|
642
666
|
|
|
643
|
-
|
|
644
|
-
return chains;
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
let entries: fs.Dirent[];
|
|
648
|
-
try {
|
|
649
|
-
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
650
|
-
} catch {
|
|
651
|
-
return chains;
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
for (const entry of entries) {
|
|
655
|
-
if (!entry.name.endsWith(".chain.md")) continue;
|
|
656
|
-
if (!entry.isFile() && !entry.isSymbolicLink()) continue;
|
|
657
|
-
|
|
658
|
-
const filePath = path.join(dir, entry.name);
|
|
667
|
+
for (const filePath of listMarkdownFilesRecursive(dir, (fileName) => fileName.endsWith(".chain.md"))) {
|
|
659
668
|
let content: string;
|
|
660
669
|
try {
|
|
661
670
|
content = fs.readFileSync(filePath, "utf-8");
|