@gotgenes/pi-subagents 7.8.1 → 9.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +46 -0
- package/README.md +11 -15
- package/docs/architecture/architecture.md +179 -133
- package/docs/architecture/history/phase-13-remaining-smells.md +88 -0
- package/docs/plans/0237-remove-disallowed-tools.md +180 -0
- package/docs/plans/0238-remove-extensions-filtering.md +191 -0
- package/docs/retro/0219-reduce-test-duplication-top-3-clone-families.md +28 -0
- package/docs/retro/0237-remove-disallowed-tools.md +78 -0
- package/docs/retro/0238-remove-extensions-filtering.md +39 -0
- package/package.json +1 -1
- package/src/config/custom-agents.ts +13 -6
- package/src/lifecycle/agent-runner.ts +11 -19
- package/src/session/session-config.ts +5 -12
- package/src/types.ts +2 -4
- package/src/ui/agent-config-editor.ts +1 -5
- package/src/ui/agent-creation-wizard.ts +1 -2
|
@@ -58,8 +58,7 @@ function loadFromDir(dir: string, agents: Map<string, AgentConfig>, source: "pro
|
|
|
58
58
|
displayName: str(fm.display_name),
|
|
59
59
|
description: str(fm.description) ?? name,
|
|
60
60
|
builtinToolNames: csvList(fm.tools, BUILTIN_TOOL_NAMES),
|
|
61
|
-
|
|
62
|
-
extensions: inheritField(fm.extensions ?? fm.inherit_extensions),
|
|
61
|
+
extensions: resolveBoolExtensions(fm.extensions ?? fm.inherit_extensions),
|
|
63
62
|
skills: inheritField(fm.skills ?? fm.inherit_skills),
|
|
64
63
|
model: str(fm.model),
|
|
65
64
|
thinking: str(fm.thinking) as ThinkingLevel | undefined,
|
|
@@ -111,11 +110,19 @@ function csvList(val: unknown, defaults: string[]): string[] {
|
|
|
111
110
|
}
|
|
112
111
|
|
|
113
112
|
/**
|
|
114
|
-
*
|
|
115
|
-
*
|
|
113
|
+
* Resolve the `extensions` field to a boolean.
|
|
114
|
+
* CSV/array values (legacy allowlist syntax) are coerced to `true` with a warning.
|
|
116
115
|
*/
|
|
117
|
-
function
|
|
118
|
-
|
|
116
|
+
function resolveBoolExtensions(val: unknown): boolean {
|
|
117
|
+
const result = inheritField(val);
|
|
118
|
+
if (Array.isArray(result)) {
|
|
119
|
+
console.warn(
|
|
120
|
+
"[pi-subagents] extensions allowlist syntax is deprecated — treating as \"true\" (inherit all).\n" +
|
|
121
|
+
"Use \"permission:\" frontmatter in pi-permission-system for per-tool access control.",
|
|
122
|
+
);
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
return result;
|
|
119
126
|
}
|
|
120
127
|
|
|
121
128
|
/**
|
|
@@ -22,7 +22,7 @@ import type { ShellExec, SubagentType, ThinkingLevel } from "#src/types";
|
|
|
22
22
|
const EXCLUDED_TOOL_NAMES = ["Agent", "get_subagent_result", "steer_subagent"];
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
|
-
* Filter the session's active tool names according to extension
|
|
25
|
+
* Filter the session's active tool names according to extension rules.
|
|
26
26
|
*
|
|
27
27
|
* Run twice - once before `bindExtensions` (filters built-in tools) and once after
|
|
28
28
|
* (filters extension-registered tools, which only join the active set during
|
|
@@ -36,20 +36,14 @@ function filterActiveTools(
|
|
|
36
36
|
activeTools: string[],
|
|
37
37
|
config: ToolFilterConfig,
|
|
38
38
|
): string[] {
|
|
39
|
-
const { toolNames, extensions
|
|
40
|
-
if (extensions
|
|
41
|
-
|
|
42
|
-
if (!disallowedSet) return activeTools;
|
|
43
|
-
return activeTools.filter((t) => !disallowedSet.has(t));
|
|
39
|
+
const { toolNames, extensions } = config;
|
|
40
|
+
if (!extensions) {
|
|
41
|
+
return activeTools;
|
|
44
42
|
}
|
|
45
43
|
const builtinToolNameSet = new Set(toolNames);
|
|
46
44
|
return activeTools.filter((t) => {
|
|
47
45
|
if (EXCLUDED_TOOL_NAMES.includes(t)) return false;
|
|
48
|
-
if (disallowedSet?.has(t)) return false;
|
|
49
46
|
if (builtinToolNameSet.has(t)) return true;
|
|
50
|
-
if (Array.isArray(extensions)) {
|
|
51
|
-
return extensions.some((ext) => t.startsWith(ext) || t.includes(ext));
|
|
52
|
-
}
|
|
53
47
|
return true;
|
|
54
48
|
});
|
|
55
49
|
}
|
|
@@ -302,7 +296,7 @@ export async function runAgent(
|
|
|
302
296
|
|
|
303
297
|
const agentDir = io.getAgentDir();
|
|
304
298
|
|
|
305
|
-
// Load extensions/skills: true
|
|
299
|
+
// Load extensions/skills: true → load; false → don't.
|
|
306
300
|
// Suppress AGENTS.md/CLAUDE.md and APPEND_SYSTEM.md - upstream's
|
|
307
301
|
// buildSystemPrompt() re-appends both AFTER systemPromptOverride, which
|
|
308
302
|
// would defeat prompt_mode: replace and isolated: true. Parent context, if
|
|
@@ -311,7 +305,7 @@ export async function runAgent(
|
|
|
311
305
|
const loader = io.createResourceLoader({
|
|
312
306
|
cwd: cfg.effectiveCwd,
|
|
313
307
|
agentDir,
|
|
314
|
-
noExtensions: cfg.toolFilter.extensions
|
|
308
|
+
noExtensions: !cfg.toolFilter.extensions,
|
|
315
309
|
noSkills: cfg.noSkills,
|
|
316
310
|
noPromptTemplates: true,
|
|
317
311
|
noThemes: true,
|
|
@@ -340,10 +334,9 @@ export async function runAgent(
|
|
|
340
334
|
thinkingLevel: cfg.thinkingLevel,
|
|
341
335
|
});
|
|
342
336
|
|
|
343
|
-
// Filter active tools: remove our own tools to prevent nesting
|
|
344
|
-
// apply extension allowlist if specified, and apply disallowedTools denylist.
|
|
337
|
+
// Filter active tools: remove our own tools to prevent nesting.
|
|
345
338
|
// First pass - over built-in tools, before bindExtensions registers extension tools.
|
|
346
|
-
if (cfg.toolFilter.extensions
|
|
339
|
+
if (cfg.toolFilter.extensions) {
|
|
347
340
|
const filtered = filterActiveTools(session.getActiveToolNames(), cfg.toolFilter);
|
|
348
341
|
session.setActiveToolsByName(filtered);
|
|
349
342
|
}
|
|
@@ -366,10 +359,9 @@ export async function runAgent(
|
|
|
366
359
|
// Patch 2 (RepOne #443): re-filter active tools after bindExtensions.
|
|
367
360
|
// Extension-registered tools (added during bindExtensions) are not in the
|
|
368
361
|
// session's active set when the first filter pass runs above. Without this
|
|
369
|
-
// re-filter,
|
|
370
|
-
//
|
|
371
|
-
|
|
372
|
-
if (cfg.toolFilter.extensions !== false || cfg.toolFilter.disallowedSet) {
|
|
362
|
+
// re-filter, EXCLUDED_TOOL_NAMES would not be applied to extension-registered
|
|
363
|
+
// tools. Run the same filter against the post-bind active set.
|
|
364
|
+
if (cfg.toolFilter.extensions) {
|
|
373
365
|
const refiltered = filterActiveTools(session.getActiveToolNames(), cfg.toolFilter);
|
|
374
366
|
session.setActiveToolsByName(refiltered);
|
|
375
367
|
}
|
|
@@ -21,16 +21,14 @@ import type { AgentPromptConfig, SubagentType, ThinkingLevel } from "#src/types"
|
|
|
21
21
|
/**
|
|
22
22
|
* Tool filtering configuration — consumed by `filterActiveTools` in `agent-runner.ts`.
|
|
23
23
|
*
|
|
24
|
-
* Groups the
|
|
25
|
-
* the built-in tool allowlist
|
|
24
|
+
* Groups the two fields that travel together through the assembly→runner boundary:
|
|
25
|
+
* the built-in tool allowlist and the extensions setting.
|
|
26
26
|
*/
|
|
27
27
|
export interface ToolFilterConfig {
|
|
28
28
|
/** Built-in tool name allowlist for this agent type. */
|
|
29
29
|
toolNames: string[];
|
|
30
|
-
/**
|
|
31
|
-
|
|
32
|
-
/** Resolved extensions setting: false | true | string[] allowlist. */
|
|
33
|
-
extensions: boolean | string[];
|
|
30
|
+
/** Resolved extensions setting: true = inherit all, false = none. */
|
|
31
|
+
extensions: boolean;
|
|
34
32
|
}
|
|
35
33
|
|
|
36
34
|
/**
|
|
@@ -210,11 +208,6 @@ export function assembleSessionConfig(
|
|
|
210
208
|
// tell the resource loader not to load them again.
|
|
211
209
|
const noSkills = skills === false || Array.isArray(skills);
|
|
212
210
|
|
|
213
|
-
// Disallowed tools set (for filterActiveTools in runAgent)
|
|
214
|
-
const disallowedSet = agentConfig.disallowedTools
|
|
215
|
-
? new Set(agentConfig.disallowedTools)
|
|
216
|
-
: undefined;
|
|
217
|
-
|
|
218
211
|
// Model resolution: explicit option > config model string > parent model
|
|
219
212
|
const model =
|
|
220
213
|
options.model ??
|
|
@@ -229,7 +222,7 @@ export function assembleSessionConfig(
|
|
|
229
222
|
return {
|
|
230
223
|
effectiveCwd,
|
|
231
224
|
systemPrompt,
|
|
232
|
-
toolFilter: { toolNames,
|
|
225
|
+
toolFilter: { toolNames, extensions },
|
|
233
226
|
model,
|
|
234
227
|
thinkingLevel,
|
|
235
228
|
noSkills,
|
package/src/types.ts
CHANGED
|
@@ -42,10 +42,8 @@ export interface AgentPromptConfig {
|
|
|
42
42
|
/** Unified agent configuration — used for both default and user-defined agents. */
|
|
43
43
|
export interface AgentConfig extends AgentIdentity, AgentPromptConfig {
|
|
44
44
|
builtinToolNames?: string[];
|
|
45
|
-
/**
|
|
46
|
-
|
|
47
|
-
/** true = inherit all, string[] = only listed, false = none */
|
|
48
|
-
extensions: true | string[] | false;
|
|
45
|
+
/** true = inherit all extensions, false = none */
|
|
46
|
+
extensions: boolean;
|
|
49
47
|
/** true = inherit all, string[] = only listed, false = none */
|
|
50
48
|
skills: true | string[] | false;
|
|
51
49
|
model?: string;
|
|
@@ -47,14 +47,10 @@ export function buildEjectContent(cfg: AgentConfig): string {
|
|
|
47
47
|
if (cfg.thinking) fmFields.push(`thinking: ${cfg.thinking}`);
|
|
48
48
|
if (cfg.maxTurns) fmFields.push(`max_turns: ${cfg.maxTurns}`);
|
|
49
49
|
fmFields.push(`prompt_mode: ${cfg.promptMode}`);
|
|
50
|
-
if (cfg.extensions
|
|
51
|
-
else if (Array.isArray(cfg.extensions))
|
|
52
|
-
fmFields.push(`extensions: ${cfg.extensions.join(", ")}`);
|
|
50
|
+
if (!cfg.extensions) fmFields.push("extensions: false");
|
|
53
51
|
if (cfg.skills === false) fmFields.push("skills: false");
|
|
54
52
|
else if (Array.isArray(cfg.skills))
|
|
55
53
|
fmFields.push(`skills: ${cfg.skills.join(", ")}`);
|
|
56
|
-
if (cfg.disallowedTools?.length)
|
|
57
|
-
fmFields.push(`disallowed_tools: ${cfg.disallowedTools.join(", ")}`);
|
|
58
54
|
if (cfg.inheritContext) fmFields.push("inherit_context: true");
|
|
59
55
|
if (cfg.runInBackground) fmFields.push("run_in_background: true");
|
|
60
56
|
if (cfg.isolated) fmFields.push("isolated: true");
|
|
@@ -104,9 +104,8 @@ model: <optional model as "provider/modelId", e.g. "anthropic/claude-haiku-4-5-2
|
|
|
104
104
|
thinking: <optional thinking level: off, minimal, low, medium, high, xhigh. Omit to inherit>
|
|
105
105
|
max_turns: <optional max agentic turns. 0 or omit for unlimited (default)>
|
|
106
106
|
prompt_mode: <"replace" (body IS the full system prompt) or "append" (body is appended to default prompt). Default: replace>
|
|
107
|
-
extensions: <true (inherit all MCP/extension tools)
|
|
107
|
+
extensions: <true (inherit all MCP/extension tools) or false (none). Default: true>
|
|
108
108
|
skills: <true (inherit all), false (none), or comma-separated skill names to preload into prompt. Default: true>
|
|
109
|
-
disallowed_tools: <comma-separated tool names to block, even if otherwise available. Omit for none>
|
|
110
109
|
inherit_context: <true to fork parent conversation into agent so it sees chat history. Default: false>
|
|
111
110
|
run_in_background: <true to run in background by default. Default: false>
|
|
112
111
|
isolated: <true for no extension/MCP tools, only built-in tools. Default: false>
|