pi-subagents 0.14.1 → 0.16.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 +31 -0
- package/README.md +113 -5
- package/agent-management.ts +28 -7
- package/agent-manager-detail.ts +4 -1
- package/agent-manager-edit.ts +46 -6
- package/agent-manager.ts +28 -2
- package/agent-serializer.ts +6 -0
- package/agents/context-builder.md +24 -26
- package/agents/delegate.md +4 -1
- package/agents/planner.md +21 -15
- package/agents/researcher.md +23 -25
- package/agents/reviewer.md +22 -14
- package/agents/scout.md +24 -19
- package/agents/worker.md +20 -8
- package/agents.ts +153 -25
- package/async-execution.ts +18 -12
- package/async-job-tracker.ts +3 -3
- package/async-status.ts +3 -3
- package/chain-execution.ts +5 -5
- package/execution.ts +3 -3
- package/fork-context.ts +7 -2
- package/formatters.ts +18 -14
- package/index.ts +2 -2
- package/package.json +1 -1
- package/parallel-utils.ts +4 -15
- package/pi-args.ts +13 -6
- package/render.ts +73 -49
- package/schemas.ts +2 -1
- package/settings.ts +2 -2
- package/slash-commands.ts +20 -25
- package/subagent-executor.ts +100 -38
- package/subagent-prompt-runtime.ts +67 -0
- package/subagent-runner.ts +3 -8
- package/types.ts +31 -0
- package/utils.ts +29 -2
- package/worktree.ts +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,37 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.16.0] - 2026-04-16
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Top-level parallel `tasks` mode now supports a per-call `concurrency` override, matching the existing chain parallel-step concurrency control. This ships part of issue `#91`. Thanks @Gabrielgvl.
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- Top-level parallel defaults and limits can now be configured through `~/.pi/agent/extensions/subagent/config.json` under `parallel.maxTasks` and `parallel.concurrency`, while keeping the existing defaults of 8 tasks and concurrency 4 when unset. This completes issue `#91`. Thanks @Gabrielgvl.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- `context: "fork"` sync runs now create child sessions from a throwaway session-manager instance opened on the persisted parent session file, instead of mutating the live parent session manager. This keeps the parent session writing to its own file so the matching `toolResult(subagent)` no longer lands in a descendant session by accident. This fixes issue `#87`. Thanks @asmisha.
|
|
15
|
+
- Project agent and chain discovery now reads both `.agents/` and `.pi/agents/`, while preferring `.pi/agents/` when both locations define the same parsed name and keeping manager writes on the `.pi/agents/` path. This fixes issue `#88`. Thanks @desek.
|
|
16
|
+
- Ctrl+O expanded subagent results now actually show expanded content. Previously the `expanded` flag was received but ignored, so task text and tool-call args were identically truncated in both views. Now expanded mode shows the full task and longer (but still bounded) tool-call previews. Additionally, tool calls are no longer lost after foreground compaction: compact display summaries are preserved and shown in expanded view even after `messages` are stripped. This addresses issue `#90`. Thanks @asagajda.
|
|
17
|
+
|
|
18
|
+
## [0.15.0] - 2026-04-16
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
- Added `systemPromptMode` so subagents can replace Pi's base prompt with `--system-prompt` instead of always appending with `--append-system-prompt`, shipping the core of issue `#85` from @isvlasov.
|
|
22
|
+
- Added `inheritProjectContext` and `inheritSkills` so child runs can keep or strip inherited project instruction files (`AGENTS.md`, `CLAUDE.md`, etc.) and Pi's discovered skills block.
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
- Builtin subagents now default to `systemPromptMode: replace`, with builtin `delegate` staying on `append`.
|
|
26
|
+
- Builtin agents now inherit project-level instruction files by default unless the user overrides them.
|
|
27
|
+
- Builtin agent prompts were rewritten for the new prompt-assembly model, and builtin `reviewer` / `context-builder` tool lists now match their documented behaviors. This rounds out the prompt-assembly work merged in PR `#92`, which closed issue `#85`. Thanks @isvlasov.
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
- Cross-platform tests now avoid machine-specific Pi install paths, align homedir-sensitive settings discovery on Windows CI, and use deterministic async config-write failure fixtures.
|
|
31
|
+
- Request-level `cwd` handling is now consistent across management and execution paths. `subagent` requests that target a worktree or nested checkout now resolve project agents, project settings, and builtin agent overrides from the requested `cwd` instead of accidentally inheriting the parent session's repo. This fixes issue `#83`. Thanks @hakin19 for the report.
|
|
32
|
+
- Relative child `cwd` values now resolve from the already-selected request/shared `cwd` across sync runs, async/background runs, chain steps, and top-level parallel tasks. This fixes cases where values like `packages/app` were interpreted from the wrong base directory, which could break skill lookup, output paths, and child process spawning.
|
|
33
|
+
- Worktree parallel-mode validation now compares task-level `cwd` overrides after relative-path resolution, so equivalent paths like `.` no longer trigger false conflict errors against the shared worktree base.
|
|
34
|
+
- Internal TypeScript source imports in the touched runtime paths now consistently use `.ts` local specifiers, matching the repo's direct TypeScript runtime loading conventions and reducing drift between adjacent modules.
|
|
35
|
+
|
|
5
36
|
## [0.14.1] - 2026-04-14
|
|
6
37
|
|
|
7
38
|
### Fixed
|
package/README.md
CHANGED
|
@@ -42,6 +42,8 @@ Agents are markdown files with YAML frontmatter that define specialized subagent
|
|
|
42
42
|
| User | `~/.pi/agent/agents/{name}.md` | Medium |
|
|
43
43
|
| Project | `.pi/agents/{name}.md` (searches up directory tree) | Highest |
|
|
44
44
|
|
|
45
|
+
Project discovery also reads legacy `.agents/{name}.md` files. If both `.agents/` and `.pi/agents/` contain a project agent with the same parsed `name`, `.pi/agents/` wins and the agent manager still writes to `.pi/agents/`.
|
|
46
|
+
|
|
45
47
|
Use `agentScope` parameter to control discovery: `"user"`, `"project"`, or `"both"` (default; project takes priority).
|
|
46
48
|
|
|
47
49
|
**Builtin agents:** The extension ships with ready-to-use agents — `scout`, `planner`, `worker`, `reviewer`, `context-builder`, `researcher`, and `delegate`. They load at lowest priority so any user or project agent with the same name overrides them.
|
|
@@ -51,10 +53,44 @@ You can also override selected builtin fields without copying the whole agent. B
|
|
|
51
53
|
- User scope: `~/.pi/agent/settings.json`
|
|
52
54
|
- Project scope: `.pi/settings.json`
|
|
53
55
|
|
|
54
|
-
Supported builtin override fields are `model`, `fallbackModels`, `thinking`, `skills`, `tools`, and `systemPrompt`. Project overrides beat user overrides.
|
|
56
|
+
Supported builtin override fields are `model`, `fallbackModels`, `thinking`, `systemPromptMode`, `inheritProjectContext`, `inheritSkills`, `skills`, `tools`, and `systemPrompt`. Project overrides beat user overrides.
|
|
57
|
+
|
|
58
|
+
**Overriding builtin defaults:**
|
|
59
|
+
|
|
60
|
+
All builtin agents inherit project instruction files (`AGENTS.md`, `CLAUDE.md`, etc.) by default. To make a builtin agent run without those project instructions:
|
|
61
|
+
|
|
62
|
+
**Via settings file** — add to `~/.pi/agent/settings.json` (user scope) or `.pi/settings.json` (project scope):
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"subagents": {
|
|
66
|
+
"agentOverrides": {
|
|
67
|
+
"reviewer": {
|
|
68
|
+
"inheritProjectContext": false
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**Via `/agents` UI** — press `e` on any builtin agent (like `reviewer`), then toggle `inheritProjectContext` to `false` and save. This creates an override that you can edit or remove later without modifying the builtin definition itself.
|
|
76
|
+
|
|
77
|
+
Overridden builtins show badges like `[builtin+user]` or `[builtin+project]` to indicate they have customizations applied.
|
|
55
78
|
|
|
56
79
|
> **Note:** The `researcher` agent uses `web_search`, `fetch_content`, and `get_search_content` tools which require the [pi-web-access](https://github.com/nicobailon/pi-web-access) extension. Install it with `pi install npm:pi-web-access`.
|
|
57
80
|
|
|
81
|
+
### Prompt Assembly Philosophy
|
|
82
|
+
|
|
83
|
+
Subagents are designed to be **narrow and intentional**. Custom subagents default to seeing only what you explicitly give them — not Pi's entire base prompt, not your repo's `AGENTS.md`, and not the parent's discovered skills.
|
|
84
|
+
|
|
85
|
+
This prevents subtle bugs where a "simple" code review agent starts making decisions based on project architecture rules it shouldn't know about, or a focused research agent gets distracted by tool descriptions meant for the orchestrator.
|
|
86
|
+
|
|
87
|
+
Use inheritance flags to selectively add back context when you actually want it:
|
|
88
|
+
- **`inheritProjectContext: true`** — Keep Pi's inherited project-instructions block from project-level files like `AGENTS.md` and `CLAUDE.md`
|
|
89
|
+
- **`inheritSkills: true`** — Let the child use Pi's discovered skills catalog
|
|
90
|
+
- **`systemPromptMode: append`** — Add the agent's prompt to Pi's base instead of replacing it
|
|
91
|
+
|
|
92
|
+
Builtin agents ship a little differently: they all inherit project instruction files by default so they follow repo-specific rules out of the box. `delegate` is still the exception for `systemPromptMode` — it stays on `append` because its job is orchestration within the parent workflow, not isolated task execution.
|
|
93
|
+
|
|
58
94
|
**Agent frontmatter:**
|
|
59
95
|
|
|
60
96
|
```yaml
|
|
@@ -66,6 +102,9 @@ extensions: # absent=all, empty=none, csv=allowlist
|
|
|
66
102
|
model: claude-haiku-4-5
|
|
67
103
|
fallbackModels: openai/gpt-5-mini, anthropic/claude-sonnet-4 # optional ordered fallbacks
|
|
68
104
|
thinking: high # off, minimal, low, medium, high, xhigh
|
|
105
|
+
systemPromptMode: replace # replace by default, except builtin delegate
|
|
106
|
+
inheritProjectContext: false # custom agents default false; builtins opt into true
|
|
107
|
+
inheritSkills: false # strip Pi's discovered skills section
|
|
69
108
|
skill: safe-bash, chrome-devtools # comma-separated skills to inject
|
|
70
109
|
output: context.md # writes to {chain_dir}/context.md
|
|
71
110
|
defaultReads: context.md # comma-separated files to read
|
|
@@ -81,10 +120,51 @@ The `thinking` field sets a default extended thinking level for the agent. At ru
|
|
|
81
120
|
|
|
82
121
|
`fallbackModels` is an optional ordered list of backup models to try when the primary model fails with a provider/model-style error such as quota, auth, timeout, or provider/model unavailable. In markdown frontmatter, declare it as a comma-separated string. In management `config` objects, you can pass either a comma-separated string or a string array.
|
|
83
122
|
|
|
123
|
+
`systemPromptMode` — How the agent markdown body is passed to Pi:
|
|
124
|
+
- **`replace`** (default) — The agent's markdown body becomes the system prompt. Clean slate, no Pi base prompt baggage.
|
|
125
|
+
- **`append`** — The agent's prompt is appended to Pi's normal base prompt. Use this when you want Pi's full capabilities plus your extra instructions.
|
|
126
|
+
|
|
127
|
+
`inheritProjectContext` — Whether the child keeps Pi's inherited project-instructions block, built from project-level instruction files like `AGENTS.md` and `CLAUDE.md`:
|
|
128
|
+
- **`false`** (default) — Strip those inherited project-level instructions from the child's final system prompt. This does not remove repo access, cwd, tools, or the task itself; it only removes those inherited instructions.
|
|
129
|
+
- **`true`** — Keep those inherited project-level instructions. Use for specialists that should follow project-specific constraints and conventions.
|
|
130
|
+
|
|
131
|
+
`inheritSkills` — Whether the child keeps Pi's discovered skills section:
|
|
132
|
+
- **`false`** (default) — Skills catalog is stripped. Good for focused agents that shouldn't browse unrelated skills.
|
|
133
|
+
- **`true`** — Child sees the full skills list. Use for general-purpose assistants that might need varied tools.
|
|
134
|
+
|
|
135
|
+
The `skills` field still works independently — it injects specific skills directly into the agent prompt regardless of `inheritSkills`.
|
|
136
|
+
|
|
137
|
+
**Common Recipes**
|
|
138
|
+
|
|
139
|
+
| Goal | `systemPromptMode` | `inheritProjectContext` | `inheritSkills` |
|
|
140
|
+
|------|-------------------|------------------------|-----------------|
|
|
141
|
+
| Fully isolated specialist (custom-agent default) | `replace` | `false` | `false` |
|
|
142
|
+
| Specialist that should follow project instruction files | `replace` | `true` | `false` |
|
|
143
|
+
| Pi-plus-extensions | `append` | `true` | `true` |
|
|
144
|
+
|
|
145
|
+
- **Security auditor**: Fully isolated so it objectively checks for vulnerabilities without being biased by project conventions.
|
|
146
|
+
- **Architecture planner**: Repo-aware so it respects `AGENTS.md` constraints when making design decisions.
|
|
147
|
+
- **Generic helper**: Append mode with full inheritance so it behaves like a slightly-customized Pi.
|
|
148
|
+
|
|
84
149
|
Fallback resolution follows the same conservative model lookup as normal execution. Explicit `provider/model` values are used as-is. Bare model IDs first prefer the current session provider when that provider actually exposes the model, then fall back to a unique registry match. If a bare ID is still ambiguous, it stays bare.
|
|
85
150
|
|
|
86
151
|
Fallback is only used for provider/model availability failures. Ordinary task failures such as bad `bash` commands, missing files, or other tool/runtime errors do not trigger a model hop.
|
|
87
152
|
|
|
153
|
+
**Tool selection semantics**
|
|
154
|
+
|
|
155
|
+
The `tools` field controls builtin-tool allowlisting and a couple of related extension behaviors:
|
|
156
|
+
|
|
157
|
+
- `tools` **omitted** → `pi-subagents` does not pass `--tools`, so the child gets Pi's normal default builtin tools.
|
|
158
|
+
- `tools` **present** → listed builtin tool names become an explicit allowlist passed via `--tools`.
|
|
159
|
+
- `mcp:...` entries are split out of `tools` and forwarded as direct MCP tool selections.
|
|
160
|
+
- Path-like entries in `tools` (extension paths or file paths ending in `.ts` / `.js`) are treated as tool-extension paths, not builtin tool names.
|
|
161
|
+
|
|
162
|
+
This means:
|
|
163
|
+
|
|
164
|
+
- `tools` omitted + `extensions` omitted → child gets Pi's normal builtin tools and normal extension set.
|
|
165
|
+
- `tools: mcp:chrome-devtools` → child still gets Pi's default builtin tools, plus direct MCP tools from `chrome-devtools`.
|
|
166
|
+
- `tools: read, bash, mcp:chrome-devtools` → child is restricted to `read` and `bash` for builtin tools, plus direct MCP tools from `chrome-devtools`.
|
|
167
|
+
|
|
88
168
|
**Extension sandboxing**
|
|
89
169
|
|
|
90
170
|
Use `extensions` in frontmatter to control which extensions a subagent can access:
|
|
@@ -177,7 +257,7 @@ Append `[key=value,...]` to any agent name to override its defaults:
|
|
|
177
257
|
```
|
|
178
258
|
/chain scout[output=context.md] "scan code" -> planner[reads=context.md] "analyze auth"
|
|
179
259
|
/run scout[model=anthropic/claude-sonnet-4] summarize this codebase
|
|
180
|
-
/parallel scanner[
|
|
260
|
+
/parallel scanner[model=anthropic/claude-sonnet-4] "find issues" -> reviewer[skills=code-review+security] "check style"
|
|
181
261
|
```
|
|
182
262
|
|
|
183
263
|
| Key | Example | Description |
|
|
@@ -229,7 +309,7 @@ Press **Ctrl+Shift+A** or type `/agents` to open the Agents Manager overlay —
|
|
|
229
309
|
|--------|-------------|
|
|
230
310
|
| List | Browse all agents and chains with search/filter, scope badges, chain badges |
|
|
231
311
|
| Detail | View resolved prompt, frontmatter fields, recent run history, and active builtin override path |
|
|
232
|
-
| Edit | Edit fields with specialized pickers (model, thinking, skills, prompt editor) |
|
|
312
|
+
| Edit | Edit fields with specialized pickers and toggles (model, thinking, prompt mode, inherited context, inherited skills, prompt editor) |
|
|
233
313
|
| Chain Detail | View chain steps with flow visualization and dependency map |
|
|
234
314
|
| Parallel Builder | Build parallel execution slots, add same agent multiple times, per-slot task overrides |
|
|
235
315
|
| Task Input | Enter task and launch with optional skip-clarify toggle |
|
|
@@ -281,6 +361,8 @@ Chains are `.chain.md` files stored alongside agent files. They define reusable
|
|
|
281
361
|
| User | `~/.pi/agent/agents/{name}.chain.md` |
|
|
282
362
|
| Project | `.pi/agents/{name}.chain.md` |
|
|
283
363
|
|
|
364
|
+
Project chain discovery also reads legacy `.agents/{name}.chain.md` files. If both locations define the same parsed chain `name`, `.pi/agents/` wins and manager writes stay in `.pi/agents/`.
|
|
365
|
+
|
|
284
366
|
**Format:**
|
|
285
367
|
|
|
286
368
|
```markdown
|
|
@@ -467,6 +549,9 @@ These are the parameters the **LLM agent** passes when it calls the `subagent` t
|
|
|
467
549
|
// Parallel
|
|
468
550
|
{ tasks: [{ agent: "scout", task: "a" }, { agent: "scout", task: "b" }] }
|
|
469
551
|
|
|
552
|
+
// Parallel with top-level concurrency override
|
|
553
|
+
{ tasks: [{ agent: "scout", task: "a" }, { agent: "reviewer", task: "b" }], concurrency: 2 }
|
|
554
|
+
|
|
470
555
|
// Parallel with count shorthand (run the same task 3 times)
|
|
471
556
|
{ tasks: [{ agent: "scout", task: "audit auth", count: 3 }] }
|
|
472
557
|
|
|
@@ -573,6 +658,9 @@ Agent definitions are not loaded into LLM context by default. Management actions
|
|
|
573
658
|
description: "Scans codebases for patterns and issues",
|
|
574
659
|
scope: "user",
|
|
575
660
|
systemPrompt: "You are a code scout...",
|
|
661
|
+
systemPromptMode: "replace",
|
|
662
|
+
inheritProjectContext: false,
|
|
663
|
+
inheritSkills: false,
|
|
576
664
|
model: "anthropic/claude-sonnet-4",
|
|
577
665
|
fallbackModels: ["openai/gpt-5-mini", "anthropic/claude-haiku-4-5"],
|
|
578
666
|
tools: "read, bash, mcp:github/search_repositories",
|
|
@@ -613,7 +701,7 @@ Agent definitions are not loaded into LLM context by default. Management actions
|
|
|
613
701
|
Notes:
|
|
614
702
|
- `create` uses `config.scope` (`"user"` or `"project"`), not `agentScope`.
|
|
615
703
|
- `update`/`delete` use `agentScope` only for scope disambiguation when the same name exists in both scopes.
|
|
616
|
-
- Agent config mapping: `reads -> defaultReads`, `progress -> defaultProgress`, `extensions` controls extension sandboxing, `maxSubagentDepth`
|
|
704
|
+
- Agent config mapping: `reads -> defaultReads`, `progress -> defaultProgress`, `extensions` controls extension sandboxing, `maxSubagentDepth`, `fallbackModels`, `systemPromptMode`, `inheritProjectContext`, and `inheritSkills` map directly to agent frontmatter, and `tools` supports `mcp:` entries that map to direct MCP tools.
|
|
617
705
|
- To clear any optional field, set it to `false` or `""` (e.g., `{ model: false }` or `{ skills: "" }`). Both work for all string-typed fields.
|
|
618
706
|
|
|
619
707
|
## Parameters
|
|
@@ -624,12 +712,13 @@ Notes:
|
|
|
624
712
|
| `task` | string | - | Task string (single mode) |
|
|
625
713
|
| `action` | string | - | Management action: `list`, `get`, `create`, `update`, `delete` |
|
|
626
714
|
| `chainName` | string | - | Chain name for management get/update/delete |
|
|
627
|
-
| `config` | object | - | Agent or chain config for management create/update. Agent configs also accept `fallbackModels` (comma-separated string or string array). |
|
|
715
|
+
| `config` | object | - | Agent or chain config for management create/update. Agent configs also accept `fallbackModels` (comma-separated string or string array), `systemPromptMode` (`append` or `replace`), `inheritProjectContext` (boolean), and `inheritSkills` (boolean). |
|
|
628
716
|
| `output` | `string \| false` | agent default | Override output file for single agent (absolute path as-is, relative path resolved against cwd) |
|
|
629
717
|
| `skill` | `string \| string[] \| false` | agent default | Override skills (comma-separated string, array, or false to disable) |
|
|
630
718
|
| `model` | string | agent default | Override model for single agent |
|
|
631
719
|
| `fallbackModels` | `string \| string[]` | agent default | Management/config-only field for ordered backup models. Markdown frontmatter uses a comma-separated string. |
|
|
632
720
|
| `tasks` | `{agent, task, cwd?, count?, skill?}[]` | - | Parallel tasks. Foreground runs directly; background requests are converted to an equivalent chain. `count` repeats one task entry N times with the same settings. |
|
|
721
|
+
| `concurrency` | number | `config.parallel.concurrency` or `4` | Top-level `tasks` mode only: max concurrent tasks. |
|
|
633
722
|
| `worktree` | boolean | false | Create isolated git worktrees for each parallel task. Requires clean git state. Per-worktree diffs included in output. |
|
|
634
723
|
| `chain` | ChainItem[] | - | Sequential steps with behavior overrides (see below) |
|
|
635
724
|
| `context` | `"fresh" \| "fork"` | `fresh` | Execution context mode. `fork` uses a real branched session from the parent's current leaf for each child run |
|
|
@@ -764,6 +853,25 @@ This aggregated output becomes `{previous}` for the next step.
|
|
|
764
853
|
|
|
765
854
|
`pi-subagents` reads optional JSON config from `~/.pi/agent/extensions/subagent/config.json`.
|
|
766
855
|
|
|
856
|
+
### `parallel`
|
|
857
|
+
|
|
858
|
+
`parallel` controls top-level `tasks` mode defaults and limits.
|
|
859
|
+
|
|
860
|
+
```json
|
|
861
|
+
{
|
|
862
|
+
"parallel": {
|
|
863
|
+
"maxTasks": 12,
|
|
864
|
+
"concurrency": 6
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
```
|
|
868
|
+
|
|
869
|
+
Fields:
|
|
870
|
+
- `maxTasks` defaults to `8` when unset or invalid
|
|
871
|
+
- `concurrency` defaults to `4` when unset or invalid
|
|
872
|
+
|
|
873
|
+
Per-call `concurrency` on the `subagent` tool takes precedence over `config.parallel.concurrency` for top-level `tasks` runs.
|
|
874
|
+
|
|
767
875
|
### `defaultSessionDir`
|
|
768
876
|
|
|
769
877
|
`defaultSessionDir` sets the fallback directory used for session logs. Eg:
|
package/agent-management.ts
CHANGED
|
@@ -8,6 +8,9 @@ import {
|
|
|
8
8
|
type AgentSource,
|
|
9
9
|
type ChainConfig,
|
|
10
10
|
type ChainStepConfig,
|
|
11
|
+
defaultInheritProjectContext,
|
|
12
|
+
defaultInheritSkills,
|
|
13
|
+
defaultSystemPromptMode,
|
|
11
14
|
discoverAgentsAll,
|
|
12
15
|
} from "./agents.ts";
|
|
13
16
|
import { serializeAgent } from "./agent-serializer.ts";
|
|
@@ -244,6 +247,18 @@ function applyAgentConfig(target: AgentConfig, cfg: Record<string, unknown>): st
|
|
|
244
247
|
else if (typeof cfg.thinking === "string") target.thinking = cfg.thinking.trim() || undefined;
|
|
245
248
|
else return "config.thinking must be a string or false when provided.";
|
|
246
249
|
}
|
|
250
|
+
if (hasKey(cfg, "systemPromptMode")) {
|
|
251
|
+
if (cfg.systemPromptMode === "append" || cfg.systemPromptMode === "replace") target.systemPromptMode = cfg.systemPromptMode;
|
|
252
|
+
else return "config.systemPromptMode must be 'append' or 'replace' when provided.";
|
|
253
|
+
}
|
|
254
|
+
if (hasKey(cfg, "inheritProjectContext")) {
|
|
255
|
+
if (typeof cfg.inheritProjectContext !== "boolean") return "config.inheritProjectContext must be a boolean when provided.";
|
|
256
|
+
target.inheritProjectContext = cfg.inheritProjectContext;
|
|
257
|
+
}
|
|
258
|
+
if (hasKey(cfg, "inheritSkills")) {
|
|
259
|
+
if (typeof cfg.inheritSkills !== "boolean") return "config.inheritSkills must be a boolean when provided.";
|
|
260
|
+
target.inheritSkills = cfg.inheritSkills;
|
|
261
|
+
}
|
|
247
262
|
if (hasKey(cfg, "output")) {
|
|
248
263
|
if (cfg.output === false || cfg.output === "") target.output = undefined;
|
|
249
264
|
else if (typeof cfg.output === "string") target.output = cfg.output;
|
|
@@ -320,6 +335,9 @@ export function formatAgentDetail(agent: AgentConfig): string {
|
|
|
320
335
|
if (agent.fallbackModels?.length) lines.push(`Fallback models: ${agent.fallbackModels.join(", ")}`);
|
|
321
336
|
if (tools.length) lines.push(`Tools: ${tools.join(", ")}`);
|
|
322
337
|
if (agent.skills?.length) lines.push(`Skills: ${agent.skills.join(", ")}`);
|
|
338
|
+
lines.push(`System prompt mode: ${agent.systemPromptMode}`);
|
|
339
|
+
lines.push(`Inherit project context: ${agent.inheritProjectContext ? "true" : "false"}`);
|
|
340
|
+
lines.push(`Inherit skills: ${agent.inheritSkills ? "true" : "false"}`);
|
|
323
341
|
if (agent.extensions !== undefined) lines.push(`Extensions: ${agent.extensions.length ? agent.extensions.join(", ") : "(none)"}`);
|
|
324
342
|
if (agent.thinking) lines.push(`Thinking: ${agent.thinking}`);
|
|
325
343
|
if (agent.output) lines.push(`Output: ${agent.output}`);
|
|
@@ -418,7 +436,16 @@ export function handleCreate(params: ManagementParams, ctx: ManagementContext):
|
|
|
418
436
|
warnings.push(...chainStepWarnings(ctx, chain.steps));
|
|
419
437
|
return result([`Created chain '${name}' at ${targetPath}.`, ...warnings].join("\n"));
|
|
420
438
|
}
|
|
421
|
-
const agent: AgentConfig = {
|
|
439
|
+
const agent: AgentConfig = {
|
|
440
|
+
name,
|
|
441
|
+
description: cfg.description.trim(),
|
|
442
|
+
source: scope,
|
|
443
|
+
filePath: targetPath,
|
|
444
|
+
systemPrompt: "",
|
|
445
|
+
systemPromptMode: defaultSystemPromptMode(name),
|
|
446
|
+
inheritProjectContext: defaultInheritProjectContext(name),
|
|
447
|
+
inheritSkills: defaultInheritSkills(),
|
|
448
|
+
};
|
|
422
449
|
const applyError = applyAgentConfig(agent, cfg);
|
|
423
450
|
if (applyError) return result(applyError, true);
|
|
424
451
|
const mw = modelWarning(ctx, agent.model);
|
|
@@ -446,7 +473,6 @@ export function handleUpdate(params: ManagementParams, ctx: ManagementContext):
|
|
|
446
473
|
const target = targetOrError;
|
|
447
474
|
const updated: AgentConfig = { ...target };
|
|
448
475
|
const oldName = target.name;
|
|
449
|
-
// Validate all fields before any filesystem mutation
|
|
450
476
|
if (hasKey(cfg, "name") && (typeof cfg.name !== "string" || !cfg.name.trim())) return result("config.name must be a non-empty string when provided.", true);
|
|
451
477
|
if (hasKey(cfg, "description") && (typeof cfg.description !== "string" || !cfg.description.trim())) return result("config.description must be a non-empty string when provided.", true);
|
|
452
478
|
let newName: string | undefined;
|
|
@@ -456,7 +482,6 @@ export function handleUpdate(params: ManagementParams, ctx: ManagementContext):
|
|
|
456
482
|
}
|
|
457
483
|
const applyError = applyAgentConfig(updated, cfg);
|
|
458
484
|
if (applyError) return result(applyError, true);
|
|
459
|
-
// Apply name/description (validated above)
|
|
460
485
|
if (newName !== undefined) updated.name = newName;
|
|
461
486
|
if (hasKey(cfg, "description")) updated.description = (cfg.description as string).trim();
|
|
462
487
|
if (hasKey(cfg, "model")) {
|
|
@@ -471,7 +496,6 @@ export function handleUpdate(params: ManagementParams, ctx: ManagementContext):
|
|
|
471
496
|
const sw = skillsWarning(ctx.cwd, updated.skills);
|
|
472
497
|
if (sw) warnings.push(sw);
|
|
473
498
|
}
|
|
474
|
-
// Filesystem mutations last
|
|
475
499
|
if (updated.name !== oldName) {
|
|
476
500
|
const renamed = renamePath("agent", target.filePath, updated.name, target.source, ctx.cwd);
|
|
477
501
|
if (renamed.error) return result(renamed.error, true);
|
|
@@ -493,7 +517,6 @@ export function handleUpdate(params: ManagementParams, ctx: ManagementContext):
|
|
|
493
517
|
const target = targetOrError;
|
|
494
518
|
const updated: ChainConfig = { ...target, steps: [...target.steps] };
|
|
495
519
|
const oldName = target.name;
|
|
496
|
-
// Validate all fields before any filesystem mutation
|
|
497
520
|
if (hasKey(cfg, "name") && (typeof cfg.name !== "string" || !cfg.name.trim())) return result("config.name must be a non-empty string when provided.", true);
|
|
498
521
|
if (hasKey(cfg, "description") && (typeof cfg.description !== "string" || !cfg.description.trim())) return result("config.description must be a non-empty string when provided.", true);
|
|
499
522
|
let newName: string | undefined;
|
|
@@ -507,7 +530,6 @@ export function handleUpdate(params: ManagementParams, ctx: ManagementContext):
|
|
|
507
530
|
if (parsed.error) return result(parsed.error, true);
|
|
508
531
|
parsedSteps = parsed.steps!;
|
|
509
532
|
}
|
|
510
|
-
// Apply validated changes to in-memory object
|
|
511
533
|
if (newName !== undefined) updated.name = newName;
|
|
512
534
|
if (hasKey(cfg, "description")) updated.description = (cfg.description as string).trim();
|
|
513
535
|
if (parsedSteps) {
|
|
@@ -516,7 +538,6 @@ export function handleUpdate(params: ManagementParams, ctx: ManagementContext):
|
|
|
516
538
|
if (missing.length) warnings.push(`Warning: chain steps reference unknown agents: ${missing.join(", ")}.`);
|
|
517
539
|
warnings.push(...chainStepWarnings(ctx, updated.steps));
|
|
518
540
|
}
|
|
519
|
-
// Filesystem mutations last
|
|
520
541
|
if (updated.name !== oldName) {
|
|
521
542
|
const renamed = renamePath("chain", target.filePath, updated.name, target.source, ctx.cwd);
|
|
522
543
|
if (renamed.error) return result(renamed.error, true);
|
package/agent-manager-detail.ts
CHANGED
|
@@ -27,7 +27,7 @@ function renderFieldLine(
|
|
|
27
27
|
width: number,
|
|
28
28
|
theme: Theme,
|
|
29
29
|
): string {
|
|
30
|
-
const labelWidth =
|
|
30
|
+
const labelWidth = 12;
|
|
31
31
|
const labelText = theme.fg("dim", pad(label, labelWidth));
|
|
32
32
|
const available = Math.max(0, width - labelWidth);
|
|
33
33
|
return `${labelText}${truncateToWidth(value, available)}`;
|
|
@@ -61,6 +61,9 @@ function buildDetailLines(
|
|
|
61
61
|
const maxSubagentDepth = agent.maxSubagentDepth !== undefined ? String(agent.maxSubagentDepth) : "(default)";
|
|
62
62
|
|
|
63
63
|
lines.push(renderFieldLine("Model:", agent.model ?? "default", contentWidth, theme));
|
|
64
|
+
lines.push(renderFieldLine("Prompt mode:", agent.systemPromptMode, contentWidth, theme));
|
|
65
|
+
lines.push(renderFieldLine("Project ctx:", agent.inheritProjectContext ? "on" : "off", contentWidth, theme));
|
|
66
|
+
lines.push(renderFieldLine("Skills ctx:", agent.inheritSkills ? "on" : "off", contentWidth, theme));
|
|
64
67
|
if (agent.override) {
|
|
65
68
|
const overrideLabel = `${agent.override.scope} · ${formatPath(agent.override.path)}`;
|
|
66
69
|
lines.push(renderFieldLine("Override:", overrideLabel, contentWidth, theme));
|
package/agent-manager-edit.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Theme } from "@mariozechner/pi-coding-agent";
|
|
2
2
|
import { matchesKey, truncateToWidth } from "@mariozechner/pi-tui";
|
|
3
|
-
import type
|
|
3
|
+
import { defaultSystemPromptMode, type AgentConfig, type BuiltinAgentOverrideBase } from "./agents.ts";
|
|
4
4
|
import { createEditorState, ensureCursorVisible, getCursorDisplayPos, handleEditorInput, renderEditor, wrapText } from "./text-editor.ts";
|
|
5
5
|
import type { TextEditorState } from "./text-editor.ts";
|
|
6
6
|
import { pad, row, renderHeader, renderFooter, formatScrollInfo } from "./render-helpers.ts";
|
|
@@ -26,7 +26,7 @@ export interface CreateEditStateOptions {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh"] as const;
|
|
29
|
-
const FIELD_ORDER = ["name", "description", "model", "fallbackModels", "thinking", "tools", "extensions", "skills", "output", "reads", "progress", "interactive", "prompt"] as const;
|
|
29
|
+
const FIELD_ORDER = ["name", "description", "model", "fallbackModels", "thinking", "systemPromptMode", "inheritProjectContext", "inheritSkills", "tools", "extensions", "skills", "output", "reads", "progress", "interactive", "prompt"] as const;
|
|
30
30
|
type ThinkingLevel = typeof THINKING_LEVELS[number];
|
|
31
31
|
const PROMPT_VIEWPORT_HEIGHT = 16;
|
|
32
32
|
const MODEL_SELECTOR_HEIGHT = 10;
|
|
@@ -45,6 +45,9 @@ function fieldValueMatchesBase(field: EditField, state: EditState): boolean {
|
|
|
45
45
|
case "model": return state.draft.model === base.model;
|
|
46
46
|
case "fallbackModels": return arraysEqual(state.draft.fallbackModels, base.fallbackModels);
|
|
47
47
|
case "thinking": return state.draft.thinking === base.thinking;
|
|
48
|
+
case "systemPromptMode": return state.draft.systemPromptMode === base.systemPromptMode;
|
|
49
|
+
case "inheritProjectContext": return state.draft.inheritProjectContext === base.inheritProjectContext;
|
|
50
|
+
case "inheritSkills": return state.draft.inheritSkills === base.inheritSkills;
|
|
48
51
|
case "tools": return arraysEqual(toolList(state.draft), toolList(base));
|
|
49
52
|
case "skills": return arraysEqual(state.draft.skills, base.skills);
|
|
50
53
|
case "prompt": return state.draft.systemPrompt === base.systemPrompt;
|
|
@@ -59,6 +62,9 @@ function resetFieldToBase(field: EditField, state: EditState): void {
|
|
|
59
62
|
case "model": state.draft.model = base.model; break;
|
|
60
63
|
case "fallbackModels": state.draft.fallbackModels = base.fallbackModels ? [...base.fallbackModels] : undefined; break;
|
|
61
64
|
case "thinking": state.draft.thinking = base.thinking; break;
|
|
65
|
+
case "systemPromptMode": state.draft.systemPromptMode = base.systemPromptMode; break;
|
|
66
|
+
case "inheritProjectContext": state.draft.inheritProjectContext = base.inheritProjectContext; break;
|
|
67
|
+
case "inheritSkills": state.draft.inheritSkills = base.inheritSkills; break;
|
|
62
68
|
case "tools": state.draft.tools = base.tools ? [...base.tools] : undefined; state.draft.mcpDirectTools = base.mcpDirectTools ? [...base.mcpDirectTools] : undefined; break;
|
|
63
69
|
case "skills": state.draft.skills = base.skills ? [...base.skills] : undefined; break;
|
|
64
70
|
case "prompt": state.draft.systemPrompt = base.systemPrompt; state.promptEditor = createEditorState(base.systemPrompt); break;
|
|
@@ -83,6 +89,9 @@ function renderFieldValue(field: EditField, state: EditState): string {
|
|
|
83
89
|
case "model": return draft.model ?? "default";
|
|
84
90
|
case "fallbackModels": return draft.fallbackModels && draft.fallbackModels.length > 0 ? draft.fallbackModels.join(", ") : "";
|
|
85
91
|
case "thinking": return draft.thinking ?? "off";
|
|
92
|
+
case "systemPromptMode": return draft.systemPromptMode ?? defaultSystemPromptMode(draft.name);
|
|
93
|
+
case "inheritProjectContext": return draft.inheritProjectContext ? "on" : "off";
|
|
94
|
+
case "inheritSkills": return draft.inheritSkills ? "on" : "off";
|
|
86
95
|
case "tools": return formatTools(draft);
|
|
87
96
|
case "extensions": return draft.extensions !== undefined ? (draft.extensions.length > 0 ? draft.extensions.join(", ") : "") : "(all)";
|
|
88
97
|
case "skills": return draft.skills && draft.skills.length > 0 ? draft.skills.join(", ") : "";
|
|
@@ -101,12 +110,28 @@ function applyFieldValue(field: EditField, state: EditState, value: string): voi
|
|
|
101
110
|
case "description": draft.description = value.trim(); break;
|
|
102
111
|
case "model": draft.model = value.trim() || undefined; break;
|
|
103
112
|
case "fallbackModels": draft.fallbackModels = parseCommaList(value); break;
|
|
113
|
+
case "systemPromptMode": {
|
|
114
|
+
const trimmed = value.trim();
|
|
115
|
+
if (trimmed === "") {
|
|
116
|
+
draft.systemPromptMode = defaultSystemPromptMode(draft.name);
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
if (trimmed === "append" || trimmed === "replace") {
|
|
120
|
+
draft.systemPromptMode = trimmed;
|
|
121
|
+
}
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
104
124
|
case "tools": { const parsed = parseTools(value); draft.tools = parsed.tools; draft.mcpDirectTools = parsed.mcp; break; }
|
|
105
125
|
case "extensions": { const trimmed = value.trim(); draft.extensions = trimmed === "(all)" ? undefined : parseCommaList(trimmed) ?? []; break; }
|
|
106
126
|
case "skills": draft.skills = parseCommaList(value); break;
|
|
107
127
|
case "output": { const trimmed = value.trim(); draft.output = trimmed.length > 0 ? trimmed : undefined; break; }
|
|
108
128
|
case "reads": draft.defaultReads = parseCommaList(value); break;
|
|
109
|
-
case "
|
|
129
|
+
case "inheritProjectContext":
|
|
130
|
+
case "inheritSkills":
|
|
131
|
+
case "progress":
|
|
132
|
+
case "interactive":
|
|
133
|
+
case "prompt":
|
|
134
|
+
break;
|
|
110
135
|
}
|
|
111
136
|
}
|
|
112
137
|
|
|
@@ -231,13 +256,19 @@ export function handleEditInput(screen: EditScreen, state: EditState, data: stri
|
|
|
231
256
|
if (data === "m") { openModelPicker(state, models); return { nextScreen: "edit-field" }; }
|
|
232
257
|
if (data === "t") { openThinkingPicker(state); return { nextScreen: "edit-field" }; }
|
|
233
258
|
if (data === "s") { openSkillPicker(state, skills); return { nextScreen: "edit-field" }; }
|
|
234
|
-
if (data === " " && (field === "
|
|
259
|
+
if (data === " " && (field === "inheritProjectContext" || field === "inheritSkills" || field === "progress" || field === "interactive")) {
|
|
260
|
+
if (field === "inheritProjectContext") state.draft.inheritProjectContext = !state.draft.inheritProjectContext;
|
|
261
|
+
if (field === "inheritSkills") state.draft.inheritSkills = !state.draft.inheritSkills;
|
|
262
|
+
if (field === "progress") state.draft.defaultProgress = !state.draft.defaultProgress;
|
|
263
|
+
if (field === "interactive") state.draft.interactive = !state.draft.interactive;
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
235
266
|
if (matchesKey(data, "return")) {
|
|
236
267
|
if (field === "model") { openModelPicker(state, models); return { nextScreen: "edit-field" }; }
|
|
237
268
|
if (field === "thinking") { openThinkingPicker(state); return { nextScreen: "edit-field" }; }
|
|
238
269
|
if (field === "skills") { openSkillPicker(state, skills); return { nextScreen: "edit-field" }; }
|
|
239
270
|
if (field === "prompt") { state.promptEditor = createEditorState(state.draft.systemPrompt ?? ""); return { nextScreen: "edit-prompt" }; }
|
|
240
|
-
if (field === "progress" || field === "interactive") return;
|
|
271
|
+
if (field === "inheritProjectContext" || field === "inheritSkills" || field === "progress" || field === "interactive") return;
|
|
241
272
|
state.fieldMode = "text"; state.fieldEditor = createEditorState(renderFieldValue(field, state)); return { nextScreen: "edit-field" };
|
|
242
273
|
}
|
|
243
274
|
return;
|
|
@@ -304,8 +335,17 @@ export function renderEdit(screen: EditScreen, state: EditState, width: number,
|
|
|
304
335
|
for (let i = 0; i < state.fields.length; i++) {
|
|
305
336
|
const field = state.fields[i]!; if (field === "prompt") break;
|
|
306
337
|
const isFocused = i === state.fieldIndex; const prefix = isFocused ? theme.fg("accent", "▸ ") : " ";
|
|
307
|
-
const
|
|
338
|
+
const fieldLabel = field === "systemPromptMode"
|
|
339
|
+
? "Prompt Mode"
|
|
340
|
+
: field === "inheritProjectContext"
|
|
341
|
+
? "Project Ctx"
|
|
342
|
+
: field === "inheritSkills"
|
|
343
|
+
? "Skills Ctx"
|
|
344
|
+
: `${field[0]!.toUpperCase()}${field.slice(1)}`;
|
|
345
|
+
const rawLabel = pad(`${fieldLabel}:`, labelWidth);
|
|
308
346
|
const labelText = state.overrideBase && !fieldValueMatchesBase(field, state) ? theme.fg("accent", rawLabel) : rawLabel; let valueText = renderFieldValue(field, state);
|
|
347
|
+
if (field === "inheritProjectContext") { const toggle = state.draft.inheritProjectContext ? theme.fg("success", "[x]") : "[ ]"; valueText = `${toggle} ${state.draft.inheritProjectContext ? "on" : "off"}`; lines.push(row(` ${prefix}${labelText} ${pad(truncateToWidth(valueText, valueWidth), valueWidth)}`, width, theme)); continue; }
|
|
348
|
+
if (field === "inheritSkills") { const toggle = state.draft.inheritSkills ? theme.fg("success", "[x]") : "[ ]"; valueText = `${toggle} ${state.draft.inheritSkills ? "on" : "off"}`; lines.push(row(` ${prefix}${labelText} ${pad(truncateToWidth(valueText, valueWidth), valueWidth)}`, width, theme)); continue; }
|
|
309
349
|
if (field === "progress") { const toggle = state.draft.defaultProgress ? theme.fg("success", "[x]") : "[ ]"; valueText = `${toggle} ${state.draft.defaultProgress ? "on" : "off"}`; lines.push(row(` ${prefix}${labelText} ${pad(truncateToWidth(valueText, valueWidth), valueWidth)}`, width, theme)); continue; }
|
|
310
350
|
if (field === "interactive") { const toggle = state.draft.interactive ? theme.fg("success", "[x]") : "[ ]"; valueText = `${toggle} ${state.draft.interactive ? "on" : "off"}`; lines.push(row(` ${prefix}${labelText} ${pad(truncateToWidth(valueText, valueWidth), valueWidth)}`, width, theme)); continue; }
|
|
311
351
|
let displayValue = truncateToWidth(valueText, valueWidth);
|
package/agent-manager.ts
CHANGED
|
@@ -5,6 +5,9 @@ import type { Component, TUI } from "@mariozechner/pi-tui";
|
|
|
5
5
|
import { matchesKey, truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
|
|
6
6
|
import {
|
|
7
7
|
buildBuiltinOverrideConfig,
|
|
8
|
+
defaultInheritProjectContext,
|
|
9
|
+
defaultInheritSkills,
|
|
10
|
+
defaultSystemPromptMode,
|
|
8
11
|
discoverAgentsAll,
|
|
9
12
|
removeBuiltinAgentOverride,
|
|
10
13
|
saveBuiltinAgentOverride,
|
|
@@ -40,7 +43,7 @@ interface NameInputState { mode: "new-agent" | "clone-agent" | "clone-chain" | "
|
|
|
40
43
|
interface StatusMessage { text: string; type: "error" | "info"; }
|
|
41
44
|
interface OverrideScopeState { selectedScope: "user" | "project"; allowProject: boolean; }
|
|
42
45
|
|
|
43
|
-
const BUILTIN_OVERRIDE_FIELDS: EditField[] = ["model", "fallbackModels", "thinking", "tools", "skills", "prompt"];
|
|
46
|
+
const BUILTIN_OVERRIDE_FIELDS: EditField[] = ["model", "fallbackModels", "thinking", "systemPromptMode", "inheritProjectContext", "inheritSkills", "tools", "skills", "prompt"];
|
|
44
47
|
|
|
45
48
|
function cloneConfig(config: AgentConfig): AgentConfig {
|
|
46
49
|
return {
|
|
@@ -130,6 +133,9 @@ export class AgentManagerComponent implements Component {
|
|
|
130
133
|
model: entry.config.model,
|
|
131
134
|
fallbackModels: entry.config.fallbackModels ? [...entry.config.fallbackModels] : undefined,
|
|
132
135
|
thinking: entry.config.thinking,
|
|
136
|
+
systemPromptMode: entry.config.systemPromptMode,
|
|
137
|
+
inheritProjectContext: entry.config.inheritProjectContext,
|
|
138
|
+
inheritSkills: entry.config.inheritSkills,
|
|
133
139
|
systemPrompt: entry.config.systemPrompt,
|
|
134
140
|
skills: entry.config.skills ? [...entry.config.skills] : undefined,
|
|
135
141
|
tools: entry.config.tools ? [...entry.config.tools] : undefined,
|
|
@@ -233,6 +239,16 @@ export class AgentManagerComponent implements Component {
|
|
|
233
239
|
filePath = path.join(dir, `${edit.draft.name}.md`);
|
|
234
240
|
if (fs.existsSync(filePath)) { edit.error = "An agent with that name already exists."; return false; }
|
|
235
241
|
fs.mkdirSync(dir, { recursive: true });
|
|
242
|
+
} else if (edit.draft.name !== entry.config.name) {
|
|
243
|
+
const nextPath = path.join(path.dirname(filePath), `${edit.draft.name}.md`);
|
|
244
|
+
if (nextPath !== filePath && fs.existsSync(nextPath)) {
|
|
245
|
+
edit.error = "An agent with that name already exists.";
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
if (nextPath !== filePath) {
|
|
249
|
+
fs.renameSync(filePath, nextPath);
|
|
250
|
+
filePath = nextPath;
|
|
251
|
+
}
|
|
236
252
|
}
|
|
237
253
|
try { const toSave: AgentConfig = { ...edit.draft, filePath }; fs.writeFileSync(filePath, serializeAgent(toSave), "utf-8"); entry.config = cloneConfig(toSave); entry.isNew = false; edit.error = undefined; return true; }
|
|
238
254
|
catch (err) { edit.error = err instanceof Error ? err.message : "Failed to save agent."; return false; }
|
|
@@ -349,7 +365,17 @@ export class AgentManagerComponent implements Component {
|
|
|
349
365
|
baseConfig = cloneConfig(sourceEntry.config);
|
|
350
366
|
} else {
|
|
351
367
|
const templateConfig = state.template?.config ?? {};
|
|
352
|
-
baseConfig = {
|
|
368
|
+
baseConfig = {
|
|
369
|
+
name,
|
|
370
|
+
description: "Describe this agent",
|
|
371
|
+
systemPrompt: "",
|
|
372
|
+
systemPromptMode: defaultSystemPromptMode(name),
|
|
373
|
+
inheritProjectContext: defaultInheritProjectContext(name),
|
|
374
|
+
inheritSkills: defaultInheritSkills(),
|
|
375
|
+
source: state.scope,
|
|
376
|
+
filePath: "",
|
|
377
|
+
...templateConfig,
|
|
378
|
+
};
|
|
353
379
|
}
|
|
354
380
|
const dir = state.scope === "project" ? this.agentData.projectDir : this.agentData.userDir;
|
|
355
381
|
if (!dir) { state.error = "Project agents directory not found."; this.tui.requestRender(); return; }
|
package/agent-serializer.ts
CHANGED
|
@@ -8,6 +8,9 @@ export const KNOWN_FIELDS = new Set([
|
|
|
8
8
|
"model",
|
|
9
9
|
"fallbackModels",
|
|
10
10
|
"thinking",
|
|
11
|
+
"systemPromptMode",
|
|
12
|
+
"inheritProjectContext",
|
|
13
|
+
"inheritSkills",
|
|
11
14
|
"skill",
|
|
12
15
|
"skills",
|
|
13
16
|
"extensions",
|
|
@@ -40,6 +43,9 @@ export function serializeAgent(config: AgentConfig): string {
|
|
|
40
43
|
const fallbackModelsValue = joinComma(config.fallbackModels);
|
|
41
44
|
if (fallbackModelsValue) lines.push(`fallbackModels: ${fallbackModelsValue}`);
|
|
42
45
|
if (config.thinking && config.thinking !== "off") lines.push(`thinking: ${config.thinking}`);
|
|
46
|
+
lines.push(`systemPromptMode: ${config.systemPromptMode}`);
|
|
47
|
+
lines.push(`inheritProjectContext: ${config.inheritProjectContext ? "true" : "false"}`);
|
|
48
|
+
lines.push(`inheritSkills: ${config.inheritSkills ? "true" : "false"}`);
|
|
43
49
|
|
|
44
50
|
const skillsValue = joinComma(config.skills);
|
|
45
51
|
if (skillsValue) lines.push(`skills: ${skillsValue}`);
|