pi-subagents 0.16.1 → 0.17.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 +10 -0
- package/README.md +4 -2
- package/agent-management.ts +2 -1
- package/agent-manager-detail.ts +12 -3
- package/agent-manager-edit.ts +10 -2
- package/agent-manager-list.ts +5 -2
- package/agent-manager.ts +41 -10
- package/agents.ts +75 -18
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.17.0] - 2026-04-16
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Builtin agents can now be disabled through `subagents.agentOverrides.<name>.disabled` or the bulk `subagents.disableBuiltins` setting, with `/agents` keeping disabled builtins visible so they can be re-enabled from the manager. This builds on PR `#81`. Thanks @danielcherubini.
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- Builtin disable precedence is now coherent across user and project settings: project overrides beat user overrides, project bulk disable beats user re-enable attempts, and same-scope per-agent overrides can opt an agent out of bulk disable.
|
|
12
|
+
- `/agents` now blocks launching disabled builtins, shows their disabled state in list/detail views and management output, and avoids exposing the builtin-only `disabled` field when editing normal user/project agents.
|
|
13
|
+
- Multi-agent chain launches from `/agents` now collect a task before dispatching instead of emitting an empty task, and settings read failures now surface as read errors instead of being mislabeled as parse failures.
|
|
14
|
+
|
|
5
15
|
## [0.16.1] - 2026-04-16
|
|
6
16
|
|
|
7
17
|
### Changed
|
package/README.md
CHANGED
|
@@ -53,7 +53,7 @@ You can also override selected builtin fields without copying the whole agent. B
|
|
|
53
53
|
- User scope: `~/.pi/agent/settings.json`
|
|
54
54
|
- Project scope: `.pi/settings.json`
|
|
55
55
|
|
|
56
|
-
Supported builtin override fields are `model`, `fallbackModels`, `thinking`, `systemPromptMode`, `inheritProjectContext`, `inheritSkills`, `skills`, `tools`, and `systemPrompt`. Project overrides beat user overrides.
|
|
56
|
+
Supported builtin override fields are `model`, `fallbackModels`, `thinking`, `systemPromptMode`, `inheritProjectContext`, `inheritSkills`, `disabled`, `skills`, `tools`, and `systemPrompt`. Project overrides beat user overrides.
|
|
57
57
|
|
|
58
58
|
**Overriding builtin defaults:**
|
|
59
59
|
|
|
@@ -74,7 +74,9 @@ All builtin agents inherit project instruction files (`AGENTS.md`, `CLAUDE.md`,
|
|
|
74
74
|
|
|
75
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
76
|
|
|
77
|
-
|
|
77
|
+
Set `disabled: true` inside an agent override to hide one builtin from runtime discovery while still keeping it visible in `/agents` so you can re-enable it later. For bulk control, set `subagents.disableBuiltins: true` in `settings.json`; project scope beats user scope, and an explicit same-scope override can opt one builtin back in with `disabled: false` or any other override fields.
|
|
78
|
+
|
|
79
|
+
Overridden builtins show badges like `[builtin+user]` or `[builtin+project]`. Disabled builtins show `off` badges in the manager so they are easy to spot and re-enable.
|
|
78
80
|
|
|
79
81
|
> **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`.
|
|
80
82
|
|
package/agent-management.ts
CHANGED
|
@@ -338,6 +338,7 @@ export function formatAgentDetail(agent: AgentConfig): string {
|
|
|
338
338
|
lines.push(`System prompt mode: ${agent.systemPromptMode}`);
|
|
339
339
|
lines.push(`Inherit project context: ${agent.inheritProjectContext ? "true" : "false"}`);
|
|
340
340
|
lines.push(`Inherit skills: ${agent.inheritSkills ? "true" : "false"}`);
|
|
341
|
+
if (agent.source === "builtin") lines.push(`Disabled: ${agent.disabled ? "true" : "false"}`);
|
|
341
342
|
if (agent.extensions !== undefined) lines.push(`Extensions: ${agent.extensions.length ? agent.extensions.join(", ") : "(none)"}`);
|
|
342
343
|
if (agent.thinking) lines.push(`Thinking: ${agent.thinking}`);
|
|
343
344
|
if (agent.output) lines.push(`Output: ${agent.output}`);
|
|
@@ -371,7 +372,7 @@ export function handleList(params: ManagementParams, ctx: ManagementContext): Ag
|
|
|
371
372
|
const d = discoverAgentsAll(ctx.cwd);
|
|
372
373
|
const agents = allAgents(d).filter((a) => scope === "both" || a.source === "builtin" || a.source === scope).sort((a, b) => a.name.localeCompare(b.name));
|
|
373
374
|
const chains = d.chains.filter((c) => scope === "both" || c.source === scope).sort((a, b) => a.name.localeCompare(b.name));
|
|
374
|
-
const lines = ["Agents:", ...(agents.length ? agents.map((a) => `- ${a.name} (${a.source}): ${a.description}`) : ["- (none)"]), "", "Chains:", ...(chains.length ? chains.map((c) => `- ${c.name} (${c.source}): ${c.description}`) : ["- (none)"])];
|
|
375
|
+
const lines = ["Agents:", ...(agents.length ? agents.map((a) => `- ${a.name} (${a.source}${a.disabled ? ", disabled" : ""}): ${a.description}`) : ["- (none)"]), "", "Chains:", ...(chains.length ? chains.map((c) => `- ${c.name} (${c.source}): ${c.description}`) : ["- (none)"])];
|
|
375
376
|
return result(lines.join("\n"));
|
|
376
377
|
}
|
|
377
378
|
|
package/agent-manager-detail.ts
CHANGED
|
@@ -64,6 +64,9 @@ function buildDetailLines(
|
|
|
64
64
|
lines.push(renderFieldLine("Prompt mode:", agent.systemPromptMode, contentWidth, theme));
|
|
65
65
|
lines.push(renderFieldLine("Project ctx:", agent.inheritProjectContext ? "on" : "off", contentWidth, theme));
|
|
66
66
|
lines.push(renderFieldLine("Skills ctx:", agent.inheritSkills ? "on" : "off", contentWidth, theme));
|
|
67
|
+
if (agent.source === "builtin") {
|
|
68
|
+
lines.push(renderFieldLine("Disabled:", agent.disabled ? "on" : "off", contentWidth, theme));
|
|
69
|
+
}
|
|
67
70
|
if (agent.override) {
|
|
68
71
|
const overrideLabel = `${agent.override.scope} · ${formatPath(agent.override.path)}`;
|
|
69
72
|
lines.push(renderFieldLine("Override:", overrideLabel, contentWidth, theme));
|
|
@@ -139,7 +142,9 @@ export function renderDetail(
|
|
|
139
142
|
): string[] {
|
|
140
143
|
const lines: string[] = [];
|
|
141
144
|
const scopeBadge = agent.source === "builtin"
|
|
142
|
-
? (agent.
|
|
145
|
+
? (agent.disabled
|
|
146
|
+
? (agent.override ? `[builtin off+${agent.override.scope}]` : "[builtin off]")
|
|
147
|
+
: (agent.override ? `[builtin+${agent.override.scope}]` : "[builtin]"))
|
|
143
148
|
: agent.source === "project"
|
|
144
149
|
? "[proj]"
|
|
145
150
|
: "[user]";
|
|
@@ -164,8 +169,12 @@ export function renderDetail(
|
|
|
164
169
|
|
|
165
170
|
const footer = agent.source === "builtin"
|
|
166
171
|
? agent.override
|
|
167
|
-
?
|
|
168
|
-
|
|
172
|
+
? (agent.disabled
|
|
173
|
+
? " [e]dit override [v] raw/resolved [↑↓] scroll [esc] back "
|
|
174
|
+
: " [l]aunch [e]dit override [v] raw/resolved [↑↓] scroll [esc] back ")
|
|
175
|
+
: (agent.disabled
|
|
176
|
+
? " [e]create override [v] raw/resolved [↑↓] scroll [esc] back "
|
|
177
|
+
: " [l]aunch [e]create override [v] raw/resolved [↑↓] scroll [esc] back ")
|
|
169
178
|
: " [l]aunch [e]dit [v] raw/resolved [↑↓] scroll [esc] back ";
|
|
170
179
|
lines.push(renderFooter(footer, width, theme));
|
|
171
180
|
return lines;
|
package/agent-manager-edit.ts
CHANGED
|
@@ -48,6 +48,7 @@ function fieldValueMatchesBase(field: EditField, state: EditState): boolean {
|
|
|
48
48
|
case "systemPromptMode": return state.draft.systemPromptMode === base.systemPromptMode;
|
|
49
49
|
case "inheritProjectContext": return state.draft.inheritProjectContext === base.inheritProjectContext;
|
|
50
50
|
case "inheritSkills": return state.draft.inheritSkills === base.inheritSkills;
|
|
51
|
+
case "disabled": return state.draft.disabled === base.disabled;
|
|
51
52
|
case "tools": return arraysEqual(toolList(state.draft), toolList(base));
|
|
52
53
|
case "skills": return arraysEqual(state.draft.skills, base.skills);
|
|
53
54
|
case "prompt": return state.draft.systemPrompt === base.systemPrompt;
|
|
@@ -65,6 +66,7 @@ function resetFieldToBase(field: EditField, state: EditState): void {
|
|
|
65
66
|
case "systemPromptMode": state.draft.systemPromptMode = base.systemPromptMode; break;
|
|
66
67
|
case "inheritProjectContext": state.draft.inheritProjectContext = base.inheritProjectContext; break;
|
|
67
68
|
case "inheritSkills": state.draft.inheritSkills = base.inheritSkills; break;
|
|
69
|
+
case "disabled": state.draft.disabled = base.disabled; break;
|
|
68
70
|
case "tools": state.draft.tools = base.tools ? [...base.tools] : undefined; state.draft.mcpDirectTools = base.mcpDirectTools ? [...base.mcpDirectTools] : undefined; break;
|
|
69
71
|
case "skills": state.draft.skills = base.skills ? [...base.skills] : undefined; break;
|
|
70
72
|
case "prompt": state.draft.systemPrompt = base.systemPrompt; state.promptEditor = createEditorState(base.systemPrompt); break;
|
|
@@ -92,6 +94,7 @@ function renderFieldValue(field: EditField, state: EditState): string {
|
|
|
92
94
|
case "systemPromptMode": return draft.systemPromptMode ?? defaultSystemPromptMode(draft.name);
|
|
93
95
|
case "inheritProjectContext": return draft.inheritProjectContext ? "on" : "off";
|
|
94
96
|
case "inheritSkills": return draft.inheritSkills ? "on" : "off";
|
|
97
|
+
case "disabled": return draft.disabled ? "on" : "off";
|
|
95
98
|
case "tools": return formatTools(draft);
|
|
96
99
|
case "extensions": return draft.extensions !== undefined ? (draft.extensions.length > 0 ? draft.extensions.join(", ") : "") : "(all)";
|
|
97
100
|
case "skills": return draft.skills && draft.skills.length > 0 ? draft.skills.join(", ") : "";
|
|
@@ -128,6 +131,7 @@ function applyFieldValue(field: EditField, state: EditState, value: string): voi
|
|
|
128
131
|
case "reads": draft.defaultReads = parseCommaList(value); break;
|
|
129
132
|
case "inheritProjectContext":
|
|
130
133
|
case "inheritSkills":
|
|
134
|
+
case "disabled":
|
|
131
135
|
case "progress":
|
|
132
136
|
case "interactive":
|
|
133
137
|
case "prompt":
|
|
@@ -256,9 +260,10 @@ export function handleEditInput(screen: EditScreen, state: EditState, data: stri
|
|
|
256
260
|
if (data === "m") { openModelPicker(state, models); return { nextScreen: "edit-field" }; }
|
|
257
261
|
if (data === "t") { openThinkingPicker(state); return { nextScreen: "edit-field" }; }
|
|
258
262
|
if (data === "s") { openSkillPicker(state, skills); return { nextScreen: "edit-field" }; }
|
|
259
|
-
if (data === " " && (field === "inheritProjectContext" || field === "inheritSkills" || field === "progress" || field === "interactive")) {
|
|
263
|
+
if (data === " " && (field === "inheritProjectContext" || field === "inheritSkills" || field === "disabled" || field === "progress" || field === "interactive")) {
|
|
260
264
|
if (field === "inheritProjectContext") state.draft.inheritProjectContext = !state.draft.inheritProjectContext;
|
|
261
265
|
if (field === "inheritSkills") state.draft.inheritSkills = !state.draft.inheritSkills;
|
|
266
|
+
if (field === "disabled") state.draft.disabled = !state.draft.disabled;
|
|
262
267
|
if (field === "progress") state.draft.defaultProgress = !state.draft.defaultProgress;
|
|
263
268
|
if (field === "interactive") state.draft.interactive = !state.draft.interactive;
|
|
264
269
|
return;
|
|
@@ -268,7 +273,7 @@ export function handleEditInput(screen: EditScreen, state: EditState, data: stri
|
|
|
268
273
|
if (field === "thinking") { openThinkingPicker(state); return { nextScreen: "edit-field" }; }
|
|
269
274
|
if (field === "skills") { openSkillPicker(state, skills); return { nextScreen: "edit-field" }; }
|
|
270
275
|
if (field === "prompt") { state.promptEditor = createEditorState(state.draft.systemPrompt ?? ""); return { nextScreen: "edit-prompt" }; }
|
|
271
|
-
if (field === "inheritProjectContext" || field === "inheritSkills" || field === "progress" || field === "interactive") return;
|
|
276
|
+
if (field === "inheritProjectContext" || field === "inheritSkills" || field === "disabled" || field === "progress" || field === "interactive") return;
|
|
272
277
|
state.fieldMode = "text"; state.fieldEditor = createEditorState(renderFieldValue(field, state)); return { nextScreen: "edit-field" };
|
|
273
278
|
}
|
|
274
279
|
return;
|
|
@@ -341,11 +346,14 @@ export function renderEdit(screen: EditScreen, state: EditState, width: number,
|
|
|
341
346
|
? "Project Ctx"
|
|
342
347
|
: field === "inheritSkills"
|
|
343
348
|
? "Skills Ctx"
|
|
349
|
+
: field === "disabled"
|
|
350
|
+
? "Disabled"
|
|
344
351
|
: `${field[0]!.toUpperCase()}${field.slice(1)}`;
|
|
345
352
|
const rawLabel = pad(`${fieldLabel}:`, labelWidth);
|
|
346
353
|
const labelText = state.overrideBase && !fieldValueMatchesBase(field, state) ? theme.fg("accent", rawLabel) : rawLabel; let valueText = renderFieldValue(field, state);
|
|
347
354
|
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
355
|
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; }
|
|
356
|
+
if (field === "disabled") { const toggle = state.draft.disabled ? theme.fg("warning", "[x]") : "[ ]"; valueText = `${toggle} ${state.draft.disabled ? "on" : "off"}`; lines.push(row(` ${prefix}${labelText} ${pad(truncateToWidth(valueText, valueWidth), valueWidth)}`, width, theme)); continue; }
|
|
349
357
|
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; }
|
|
350
358
|
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; }
|
|
351
359
|
let displayValue = truncateToWidth(valueText, valueWidth);
|
package/agent-manager-list.ts
CHANGED
|
@@ -10,6 +10,7 @@ export interface ListAgent {
|
|
|
10
10
|
model?: string;
|
|
11
11
|
source: AgentSource;
|
|
12
12
|
overrideScope?: "user" | "project";
|
|
13
|
+
disabled?: boolean;
|
|
13
14
|
kind: "agent" | "chain";
|
|
14
15
|
stepCount?: number;
|
|
15
16
|
}
|
|
@@ -191,7 +192,7 @@ export function renderList(
|
|
|
191
192
|
const innerW = width - 2;
|
|
192
193
|
const nameWidth = 16;
|
|
193
194
|
const modelWidth = 12;
|
|
194
|
-
const scopeWidth =
|
|
195
|
+
const scopeWidth = 21;
|
|
195
196
|
|
|
196
197
|
for (let i = 0; i < visible.length; i++) {
|
|
197
198
|
const agent = visible[i]!;
|
|
@@ -212,7 +213,9 @@ export function renderList(
|
|
|
212
213
|
const scopeLabel = agent.kind === "chain"
|
|
213
214
|
? "[chain]"
|
|
214
215
|
: agent.source === "builtin"
|
|
215
|
-
? (agent.
|
|
216
|
+
? (agent.disabled
|
|
217
|
+
? (agent.overrideScope ? `[builtin off+${agent.overrideScope}]` : "[builtin off]")
|
|
218
|
+
: (agent.overrideScope ? `[builtin+${agent.overrideScope}]` : "[builtin]"))
|
|
216
219
|
: agent.source === "project"
|
|
217
220
|
? "[proj]"
|
|
218
221
|
: "[user]";
|
package/agent-manager.ts
CHANGED
|
@@ -43,7 +43,7 @@ interface NameInputState { mode: "new-agent" | "clone-agent" | "clone-chain" | "
|
|
|
43
43
|
interface StatusMessage { text: string; type: "error" | "info"; }
|
|
44
44
|
interface OverrideScopeState { selectedScope: "user" | "project"; allowProject: boolean; }
|
|
45
45
|
|
|
46
|
-
const BUILTIN_OVERRIDE_FIELDS: EditField[] = ["model", "fallbackModels", "thinking", "systemPromptMode", "inheritProjectContext", "inheritSkills", "tools", "skills", "prompt"];
|
|
46
|
+
const BUILTIN_OVERRIDE_FIELDS: EditField[] = ["model", "fallbackModels", "thinking", "systemPromptMode", "inheritProjectContext", "inheritSkills", "disabled", "tools", "skills", "prompt"];
|
|
47
47
|
|
|
48
48
|
function cloneConfig(config: AgentConfig): AgentConfig {
|
|
49
49
|
return {
|
|
@@ -59,6 +59,7 @@ function cloneConfig(config: AgentConfig): AgentConfig {
|
|
|
59
59
|
...config.override,
|
|
60
60
|
base: {
|
|
61
61
|
...config.override.base,
|
|
62
|
+
disabled: config.override.base.disabled,
|
|
62
63
|
fallbackModels: config.override.base.fallbackModels ? [...config.override.base.fallbackModels] : undefined,
|
|
63
64
|
skills: config.override.base.skills ? [...config.override.base.skills] : undefined,
|
|
64
65
|
tools: config.override.base.tools ? [...config.override.base.tools] : undefined,
|
|
@@ -124,8 +125,9 @@ export class AgentManagerComponent implements Component {
|
|
|
124
125
|
|
|
125
126
|
private getAgentEntry(id: string | null): AgentEntry | undefined { if (!id) return undefined; return this.agents.find((entry) => entry.id === id); }
|
|
126
127
|
private getChainEntry(id: string | null): ChainEntry | undefined { if (!id) return undefined; return this.chains.find((entry) => entry.id === id); }
|
|
127
|
-
private listAgents(): ListAgent[] { const a = this.agents.map((entry) => ({ id: entry.id, name: entry.config.name, description: entry.config.description, model: entry.config.model, source: entry.config.source, overrideScope: entry.config.override?.scope, kind: "agent" as const })); const c = this.chains.map((entry) => ({ id: entry.id, name: entry.config.name, description: entry.config.description, source: entry.config.source, kind: "chain" as const, stepCount: entry.config.steps.length })); return [...a, ...c]; }
|
|
128
|
+
private listAgents(): ListAgent[] { const a = this.agents.map((entry) => ({ id: entry.id, name: entry.config.name, description: entry.config.description, model: entry.config.model, source: entry.config.source, overrideScope: entry.config.override?.scope, disabled: entry.config.disabled, kind: "agent" as const })); const c = this.chains.map((entry) => ({ id: entry.id, name: entry.config.name, description: entry.config.description, source: entry.config.source, kind: "chain" as const, stepCount: entry.config.steps.length })); return [...a, ...c]; }
|
|
128
129
|
private clearStatus(): void { this.statusMessage = undefined; }
|
|
130
|
+
private disabledAgentEntries(ids: string[]): AgentEntry[] { return ids.map((id) => this.getAgentEntry(id)).filter((entry): entry is AgentEntry => Boolean(entry?.config.disabled)); }
|
|
129
131
|
|
|
130
132
|
private resolveBuiltinOverrideBase(entry: AgentEntry): BuiltinAgentOverrideBase {
|
|
131
133
|
if (entry.config.override) return entry.config.override.base;
|
|
@@ -136,6 +138,7 @@ export class AgentManagerComponent implements Component {
|
|
|
136
138
|
systemPromptMode: entry.config.systemPromptMode,
|
|
137
139
|
inheritProjectContext: entry.config.inheritProjectContext,
|
|
138
140
|
inheritSkills: entry.config.inheritSkills,
|
|
141
|
+
disabled: entry.config.disabled,
|
|
139
142
|
systemPrompt: entry.config.systemPrompt,
|
|
140
143
|
skills: entry.config.skills ? [...entry.config.skills] : undefined,
|
|
141
144
|
tools: entry.config.tools ? [...entry.config.tools] : undefined,
|
|
@@ -185,11 +188,6 @@ export class AgentManagerComponent implements Component {
|
|
|
185
188
|
this.screen = "parallel-builder";
|
|
186
189
|
}
|
|
187
190
|
private enterTaskInput(ids: string[], backScreen: ManagerScreen = "list"): void {
|
|
188
|
-
if (ids.length > 1) {
|
|
189
|
-
const names = ids.map((id) => { const e = this.getAgentEntry(id); return e ? e.config.name : id; });
|
|
190
|
-
this.done({ action: "chain", agents: names, task: "", skipClarify: false });
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
191
|
this.chainAgentIds = ids; this.chainLaunchId = null; this.parallelMode = false; this.taskBackScreen = backScreen; this.taskEditor = createEditorState(); this.skipClarify = true; this.screen = "task-input";
|
|
194
192
|
}
|
|
195
193
|
private enterSavedChainLaunch(entry: ChainEntry): void { this.chainLaunchId = entry.id; this.chainAgentIds = []; this.parallelMode = false; this.taskBackScreen = "chain-detail"; this.taskEditor = createEditorState(); this.skipClarify = true; this.screen = "task-input"; }
|
|
@@ -507,6 +505,13 @@ export class AgentManagerComponent implements Component {
|
|
|
507
505
|
const tasks = this.parallelState.slots.map((slot) => ({ agent: slot.agentName, task: slot.customTask || sharedTask }));
|
|
508
506
|
this.done({ action: "parallel", tasks, skipClarify: this.skipClarify }); return;
|
|
509
507
|
}
|
|
508
|
+
if (this.chainAgentIds.length > 1) {
|
|
509
|
+
const agents = this.chainAgentIds
|
|
510
|
+
.map((id) => this.getAgentEntry(id)?.config.name)
|
|
511
|
+
.filter((name): name is string => Boolean(name));
|
|
512
|
+
if (agents.length !== this.chainAgentIds.length) { this.screen = "list"; this.tui.requestRender(); return; }
|
|
513
|
+
this.done({ action: "chain", agents, task: this.taskEditor.buffer, skipClarify: this.skipClarify }); return;
|
|
514
|
+
}
|
|
510
515
|
const name = this.getAgentEntry(this.chainAgentIds[0] ?? null)?.config.name;
|
|
511
516
|
if (!name) { this.screen = "list"; this.tui.requestRender(); return; }
|
|
512
517
|
this.done({ action: "launch", agent: name, task: this.taskEditor.buffer, skipClarify: this.skipClarify }); return;
|
|
@@ -562,8 +567,24 @@ export class AgentManagerComponent implements Component {
|
|
|
562
567
|
case "clone": if (this.getAgentEntry(action.id)) this.enterNameInput("clone-agent", action.id); else if (this.getChainEntry(action.id)) this.enterNameInput("clone-chain", action.id); return;
|
|
563
568
|
case "new": this.enterTemplateSelect(); return;
|
|
564
569
|
case "delete": { if (this.isBuiltin(action.id)) { this.statusMessage = { text: "Builtin agents cannot be deleted. Clone to user scope to override.", type: "error" }; return; } this.confirmDeleteId = action.id; this.screen = "confirm-delete"; return; }
|
|
565
|
-
case "run-chain":
|
|
566
|
-
|
|
570
|
+
case "run-chain": {
|
|
571
|
+
const disabled = this.disabledAgentEntries(action.ids);
|
|
572
|
+
if (disabled.length > 0) {
|
|
573
|
+
this.statusMessage = { text: `Disabled builtin agents cannot run: ${disabled.map((entry) => entry.config.name).join(", ")}. Edit the override to re-enable them.`, type: "error" };
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
this.enterTaskInput(action.ids);
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
case "run-parallel": {
|
|
580
|
+
const disabled = this.disabledAgentEntries(action.ids);
|
|
581
|
+
if (disabled.length > 0) {
|
|
582
|
+
this.statusMessage = { text: `Disabled builtin agents cannot run: ${disabled.map((entry) => entry.config.name).join(", ")}. Edit the override to re-enable them.`, type: "error" };
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
this.enterParallelBuilder(action.ids);
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
567
588
|
case "close": this.done(undefined); return;
|
|
568
589
|
}
|
|
569
590
|
}
|
|
@@ -579,7 +600,11 @@ export class AgentManagerComponent implements Component {
|
|
|
579
600
|
this.enterEdit(entry);
|
|
580
601
|
return;
|
|
581
602
|
}
|
|
582
|
-
if (action.type === "launch") {
|
|
603
|
+
if (action.type === "launch") {
|
|
604
|
+
if (entry.config.disabled) return;
|
|
605
|
+
this.enterTaskInput([entry.id], "detail");
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
583
608
|
}
|
|
584
609
|
|
|
585
610
|
private handleChainDetailAction(action: ChainDetailAction, entry: ChainEntry): void {
|
|
@@ -605,6 +630,12 @@ export class AgentManagerComponent implements Component {
|
|
|
605
630
|
case "task-input": {
|
|
606
631
|
if (this.chainLaunchId) { const entry = this.getChainEntry(this.chainLaunchId); const title = entry ? `Chain: ${entry.config.name}` : "Chain"; return renderTaskInput(title, this.taskEditor, this.skipClarify, w, this.theme); }
|
|
607
632
|
if (this.parallelMode && this.parallelState) return renderTaskInput(formatParallelTitle(this.parallelState.slots), this.taskEditor, this.skipClarify, w, this.theme);
|
|
633
|
+
if (this.chainAgentIds.length > 1) {
|
|
634
|
+
const names = this.chainAgentIds
|
|
635
|
+
.map((id) => this.getAgentEntry(id)?.config.name)
|
|
636
|
+
.filter((name): name is string => Boolean(name));
|
|
637
|
+
return renderTaskInput(`Chain: ${names.join(" → ")}`, this.taskEditor, this.skipClarify, w, this.theme);
|
|
638
|
+
}
|
|
608
639
|
const name = this.getAgentEntry(this.chainAgentIds[0] ?? null)?.config.name ?? "Agent";
|
|
609
640
|
return renderTaskInput(`Run: ${name}`, this.taskEditor, this.skipClarify, w, this.theme);
|
|
610
641
|
}
|
package/agents.ts
CHANGED
|
@@ -35,6 +35,7 @@ export interface BuiltinAgentOverrideBase {
|
|
|
35
35
|
systemPromptMode: SystemPromptMode;
|
|
36
36
|
inheritProjectContext: boolean;
|
|
37
37
|
inheritSkills: boolean;
|
|
38
|
+
disabled?: boolean;
|
|
38
39
|
systemPrompt: string;
|
|
39
40
|
skills?: string[];
|
|
40
41
|
tools?: string[];
|
|
@@ -48,6 +49,7 @@ export interface BuiltinAgentOverrideConfig {
|
|
|
48
49
|
systemPromptMode?: SystemPromptMode;
|
|
49
50
|
inheritProjectContext?: boolean;
|
|
50
51
|
inheritSkills?: boolean;
|
|
52
|
+
disabled?: boolean;
|
|
51
53
|
systemPrompt?: string;
|
|
52
54
|
skills?: string[] | false;
|
|
53
55
|
tools?: string[] | false;
|
|
@@ -80,10 +82,18 @@ export interface AgentConfig {
|
|
|
80
82
|
defaultProgress?: boolean;
|
|
81
83
|
interactive?: boolean;
|
|
82
84
|
maxSubagentDepth?: number;
|
|
85
|
+
disabled?: boolean;
|
|
83
86
|
extraFields?: Record<string, string>;
|
|
84
87
|
override?: BuiltinAgentOverrideInfo;
|
|
85
88
|
}
|
|
86
89
|
|
|
90
|
+
interface SubagentSettings {
|
|
91
|
+
overrides: Record<string, BuiltinAgentOverrideConfig>;
|
|
92
|
+
disableBuiltins?: boolean;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const EMPTY_SUBAGENT_SETTINGS: SubagentSettings = { overrides: {} };
|
|
96
|
+
|
|
87
97
|
export interface ChainStepConfig {
|
|
88
98
|
agent: string;
|
|
89
99
|
task: string;
|
|
@@ -150,6 +160,7 @@ function cloneOverrideBase(agent: AgentConfig): BuiltinAgentOverrideBase {
|
|
|
150
160
|
systemPromptMode: agent.systemPromptMode,
|
|
151
161
|
inheritProjectContext: agent.inheritProjectContext,
|
|
152
162
|
inheritSkills: agent.inheritSkills,
|
|
163
|
+
disabled: agent.disabled,
|
|
153
164
|
systemPrompt: agent.systemPrompt,
|
|
154
165
|
skills: agent.skills ? [...agent.skills] : undefined,
|
|
155
166
|
tools: agent.tools ? [...agent.tools] : undefined,
|
|
@@ -167,6 +178,7 @@ function cloneOverrideValue(override: BuiltinAgentOverrideConfig): BuiltinAgentO
|
|
|
167
178
|
...(override.systemPromptMode !== undefined ? { systemPromptMode: override.systemPromptMode } : {}),
|
|
168
179
|
...(override.inheritProjectContext !== undefined ? { inheritProjectContext: override.inheritProjectContext } : {}),
|
|
169
180
|
...(override.inheritSkills !== undefined ? { inheritSkills: override.inheritSkills } : {}),
|
|
181
|
+
...(override.disabled !== undefined ? { disabled: override.disabled } : {}),
|
|
170
182
|
...(override.systemPrompt !== undefined ? { systemPrompt: override.systemPrompt } : {}),
|
|
171
183
|
...(override.skills !== undefined ? { skills: override.skills === false ? false : [...override.skills] } : {}),
|
|
172
184
|
...(override.tools !== undefined ? { tools: override.tools === false ? false : [...override.tools] } : {}),
|
|
@@ -197,9 +209,17 @@ export function getProjectAgentSettingsPath(cwd: string): string | null {
|
|
|
197
209
|
|
|
198
210
|
function readSettingsFileStrict(filePath: string): Record<string, unknown> {
|
|
199
211
|
if (!fs.existsSync(filePath)) return {};
|
|
212
|
+
let raw: string;
|
|
213
|
+
try {
|
|
214
|
+
raw = fs.readFileSync(filePath, "utf-8");
|
|
215
|
+
} catch (error) {
|
|
216
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
217
|
+
throw new Error(`Failed to read settings file '${filePath}': ${message}`, { cause: error });
|
|
218
|
+
}
|
|
219
|
+
|
|
200
220
|
let parsed: unknown;
|
|
201
221
|
try {
|
|
202
|
-
parsed = JSON.parse(
|
|
222
|
+
parsed = JSON.parse(raw);
|
|
203
223
|
} catch (error) {
|
|
204
224
|
const message = error instanceof Error ? error.message : String(error);
|
|
205
225
|
throw new Error(`Failed to parse settings file '${filePath}': ${message}`, { cause: error });
|
|
@@ -282,6 +302,14 @@ function parseBuiltinOverrideEntry(
|
|
|
282
302
|
}
|
|
283
303
|
}
|
|
284
304
|
|
|
305
|
+
if ("disabled" in input) {
|
|
306
|
+
if (typeof input.disabled === "boolean") {
|
|
307
|
+
override.disabled = input.disabled;
|
|
308
|
+
} else {
|
|
309
|
+
throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'disabled'; expected a boolean.`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
285
313
|
if ("systemPrompt" in input) {
|
|
286
314
|
if (typeof input.systemPrompt === "string") override.systemPrompt = input.systemPrompt;
|
|
287
315
|
else throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'systemPrompt'; expected a string.`);
|
|
@@ -299,20 +327,32 @@ function parseBuiltinOverrideEntry(
|
|
|
299
327
|
return Object.keys(override).length > 0 ? override : undefined;
|
|
300
328
|
}
|
|
301
329
|
|
|
302
|
-
function
|
|
303
|
-
if (!filePath
|
|
330
|
+
function readSubagentSettings(filePath: string | null): SubagentSettings {
|
|
331
|
+
if (!filePath) return EMPTY_SUBAGENT_SETTINGS;
|
|
304
332
|
const settings = readSettingsFileStrict(filePath);
|
|
305
333
|
const subagents = settings.subagents;
|
|
306
|
-
if (!subagents || typeof subagents !== "object" || Array.isArray(subagents)) return
|
|
307
|
-
|
|
308
|
-
|
|
334
|
+
if (!subagents || typeof subagents !== "object" || Array.isArray(subagents)) return EMPTY_SUBAGENT_SETTINGS;
|
|
335
|
+
|
|
336
|
+
const subagentsObject = subagents as Record<string, unknown>;
|
|
337
|
+
let disableBuiltins: boolean | undefined;
|
|
338
|
+
if ("disableBuiltins" in subagentsObject) {
|
|
339
|
+
if (typeof subagentsObject.disableBuiltins === "boolean") {
|
|
340
|
+
disableBuiltins = subagentsObject.disableBuiltins;
|
|
341
|
+
} else {
|
|
342
|
+
throw new Error(`Subagent settings in '${filePath}' have invalid 'disableBuiltins'; expected a boolean.`);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
309
345
|
|
|
310
346
|
const parsed: Record<string, BuiltinAgentOverrideConfig> = {};
|
|
347
|
+
const agentOverrides = subagentsObject.agentOverrides;
|
|
348
|
+
if (!agentOverrides || typeof agentOverrides !== "object" || Array.isArray(agentOverrides)) {
|
|
349
|
+
return { overrides: parsed, disableBuiltins };
|
|
350
|
+
}
|
|
311
351
|
for (const [name, value] of Object.entries(agentOverrides)) {
|
|
312
352
|
const override = parseBuiltinOverrideEntry(name, value, filePath);
|
|
313
353
|
if (override) parsed[name] = override;
|
|
314
354
|
}
|
|
315
|
-
return parsed;
|
|
355
|
+
return { overrides: parsed, disableBuiltins };
|
|
316
356
|
}
|
|
317
357
|
|
|
318
358
|
function applyBuiltinOverride(
|
|
@@ -333,6 +373,7 @@ function applyBuiltinOverride(
|
|
|
333
373
|
if (override.systemPromptMode !== undefined) next.systemPromptMode = override.systemPromptMode;
|
|
334
374
|
if (override.inheritProjectContext !== undefined) next.inheritProjectContext = override.inheritProjectContext;
|
|
335
375
|
if (override.inheritSkills !== undefined) next.inheritSkills = override.inheritSkills;
|
|
376
|
+
if (override.disabled !== undefined) next.disabled = override.disabled;
|
|
336
377
|
if (override.systemPrompt !== undefined) next.systemPrompt = override.systemPrompt;
|
|
337
378
|
if (override.skills !== undefined) next.skills = override.skills === false ? undefined : [...override.skills];
|
|
338
379
|
if (override.tools !== undefined) {
|
|
@@ -346,29 +387,40 @@ function applyBuiltinOverride(
|
|
|
346
387
|
|
|
347
388
|
function applyBuiltinOverrides(
|
|
348
389
|
builtinAgents: AgentConfig[],
|
|
349
|
-
|
|
350
|
-
|
|
390
|
+
userSettings: SubagentSettings,
|
|
391
|
+
projectSettings: SubagentSettings,
|
|
351
392
|
userSettingsPath: string,
|
|
352
393
|
projectSettingsPath: string | null,
|
|
353
394
|
): AgentConfig[] {
|
|
395
|
+
const projectBulkDisabled = projectSettings.disableBuiltins === true && projectSettingsPath !== null;
|
|
396
|
+
const userBulkDisabled = projectSettings.disableBuiltins === undefined && userSettings.disableBuiltins === true;
|
|
397
|
+
|
|
354
398
|
return builtinAgents.map((agent) => {
|
|
355
|
-
const projectOverride =
|
|
399
|
+
const projectOverride = projectSettings.overrides[agent.name];
|
|
356
400
|
if (projectOverride && projectSettingsPath) {
|
|
357
401
|
return applyBuiltinOverride(agent, projectOverride, { scope: "project", path: projectSettingsPath });
|
|
358
402
|
}
|
|
359
403
|
|
|
360
|
-
|
|
404
|
+
if (projectBulkDisabled && projectSettingsPath) {
|
|
405
|
+
return applyBuiltinOverride(agent, { disabled: true }, { scope: "project", path: projectSettingsPath });
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const userOverride = userSettings.overrides[agent.name];
|
|
361
409
|
if (userOverride) {
|
|
362
410
|
return applyBuiltinOverride(agent, userOverride, { scope: "user", path: userSettingsPath });
|
|
363
411
|
}
|
|
364
412
|
|
|
413
|
+
if (userBulkDisabled) {
|
|
414
|
+
return applyBuiltinOverride(agent, { disabled: true }, { scope: "user", path: userSettingsPath });
|
|
415
|
+
}
|
|
416
|
+
|
|
365
417
|
return agent;
|
|
366
418
|
});
|
|
367
419
|
}
|
|
368
420
|
|
|
369
421
|
export function buildBuiltinOverrideConfig(
|
|
370
422
|
base: BuiltinAgentOverrideBase,
|
|
371
|
-
draft: Pick<AgentConfig, "model" | "fallbackModels" | "thinking" | "systemPromptMode" | "inheritProjectContext" | "inheritSkills" | "systemPrompt" | "skills" | "tools" | "mcpDirectTools">,
|
|
423
|
+
draft: Pick<AgentConfig, "model" | "fallbackModels" | "thinking" | "systemPromptMode" | "inheritProjectContext" | "inheritSkills" | "disabled" | "systemPrompt" | "skills" | "tools" | "mcpDirectTools">,
|
|
372
424
|
): BuiltinAgentOverrideConfig | undefined {
|
|
373
425
|
const override: BuiltinAgentOverrideConfig = {};
|
|
374
426
|
|
|
@@ -378,6 +430,7 @@ export function buildBuiltinOverrideConfig(
|
|
|
378
430
|
if (draft.systemPromptMode !== base.systemPromptMode) override.systemPromptMode = draft.systemPromptMode;
|
|
379
431
|
if (draft.inheritProjectContext !== base.inheritProjectContext) override.inheritProjectContext = draft.inheritProjectContext;
|
|
380
432
|
if (draft.inheritSkills !== base.inheritSkills) override.inheritSkills = draft.inheritSkills;
|
|
433
|
+
if (draft.disabled !== base.disabled) override.disabled = draft.disabled ?? false;
|
|
381
434
|
if (draft.systemPrompt !== base.systemPrompt) override.systemPrompt = draft.systemPrompt;
|
|
382
435
|
if (!arraysEqual(draft.skills, base.skills)) override.skills = draft.skills ? [...draft.skills] : false;
|
|
383
436
|
|
|
@@ -621,7 +674,6 @@ function resolveNearestProjectAgentDirs(cwd: string): { readDirs: string[]; pref
|
|
|
621
674
|
preferredDir,
|
|
622
675
|
};
|
|
623
676
|
}
|
|
624
|
-
|
|
625
677
|
const BUILTIN_AGENTS_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), "agents");
|
|
626
678
|
|
|
627
679
|
export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryResult {
|
|
@@ -630,11 +682,13 @@ export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryRe
|
|
|
630
682
|
const { readDirs: projectAgentDirs, preferredDir: projectAgentsDir } = resolveNearestProjectAgentDirs(cwd);
|
|
631
683
|
const userSettingsPath = getUserAgentSettingsPath();
|
|
632
684
|
const projectSettingsPath = getProjectAgentSettingsPath(cwd);
|
|
685
|
+
const userSettings = scope === "project" ? EMPTY_SUBAGENT_SETTINGS : readSubagentSettings(userSettingsPath);
|
|
686
|
+
const projectSettings = scope === "user" ? EMPTY_SUBAGENT_SETTINGS : readSubagentSettings(projectSettingsPath);
|
|
633
687
|
|
|
634
688
|
const builtinAgents = applyBuiltinOverrides(
|
|
635
689
|
loadAgentsFromDir(BUILTIN_AGENTS_DIR, "builtin"),
|
|
636
|
-
|
|
637
|
-
|
|
690
|
+
userSettings,
|
|
691
|
+
projectSettings,
|
|
638
692
|
userSettingsPath,
|
|
639
693
|
projectSettingsPath,
|
|
640
694
|
);
|
|
@@ -644,7 +698,8 @@ export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryRe
|
|
|
644
698
|
const userAgents = [...userAgentsOld, ...userAgentsNew];
|
|
645
699
|
|
|
646
700
|
const projectAgents = scope === "user" ? [] : projectAgentDirs.flatMap((dir) => loadAgentsFromDir(dir, "project"));
|
|
647
|
-
const agents = mergeAgentsForScope(scope, userAgents, projectAgents, builtinAgents)
|
|
701
|
+
const agents = mergeAgentsForScope(scope, userAgents, projectAgents, builtinAgents)
|
|
702
|
+
.filter((agent) => agent.disabled !== true);
|
|
648
703
|
|
|
649
704
|
return { agents, projectAgentsDir };
|
|
650
705
|
}
|
|
@@ -664,11 +719,13 @@ export function discoverAgentsAll(cwd: string): {
|
|
|
664
719
|
const { readDirs: projectDirs, preferredDir: projectDir } = resolveNearestProjectAgentDirs(cwd);
|
|
665
720
|
const userSettingsPath = getUserAgentSettingsPath();
|
|
666
721
|
const projectSettingsPath = getProjectAgentSettingsPath(cwd);
|
|
722
|
+
const userSettings = readSubagentSettings(userSettingsPath);
|
|
723
|
+
const projectSettings = readSubagentSettings(projectSettingsPath);
|
|
667
724
|
|
|
668
725
|
const builtin = applyBuiltinOverrides(
|
|
669
726
|
loadAgentsFromDir(BUILTIN_AGENTS_DIR, "builtin"),
|
|
670
|
-
|
|
671
|
-
|
|
727
|
+
userSettings,
|
|
728
|
+
projectSettings,
|
|
672
729
|
userSettingsPath,
|
|
673
730
|
projectSettingsPath,
|
|
674
731
|
);
|
package/package.json
CHANGED