@esso0428/pi-subagents 0.17.6 → 0.17.8
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 +14 -0
- package/CONTRIBUTING.md +4 -0
- package/dist/abortable.d.ts +13 -0
- package/dist/abortable.d.ts.map +1 -0
- package/dist/abortable.js +43 -0
- package/dist/abortable.js.map +1 -0
- package/dist/agent-color.d.ts +36 -0
- package/dist/agent-color.d.ts.map +1 -0
- package/dist/agent-color.js +124 -0
- package/dist/agent-color.js.map +1 -0
- package/dist/agent-file-toggle.d.ts +126 -0
- package/dist/agent-file-toggle.d.ts.map +1 -0
- package/dist/agent-file-toggle.js +259 -0
- package/dist/agent-file-toggle.js.map +1 -0
- package/dist/agent-history.d.ts +4 -0
- package/dist/agent-history.d.ts.map +1 -1
- package/dist/agent-history.js +47 -1
- package/dist/agent-history.js.map +1 -1
- package/dist/agent-manager.d.ts +370 -56
- package/dist/agent-manager.d.ts.map +1 -1
- package/dist/agent-manager.js +1123 -409
- package/dist/agent-manager.js.map +1 -1
- package/dist/agent-runner.d.ts +100 -10
- package/dist/agent-runner.d.ts.map +1 -1
- package/dist/agent-runner.js +166 -21
- package/dist/agent-runner.js.map +1 -1
- package/dist/agent-types.d.ts +57 -5
- package/dist/agent-types.d.ts.map +1 -1
- package/dist/agent-types.js +164 -32
- package/dist/agent-types.js.map +1 -1
- package/dist/child-context.d.ts +3 -0
- package/dist/child-context.d.ts.map +1 -0
- package/dist/child-context.js +13 -0
- package/dist/child-context.js.map +1 -0
- package/dist/cross-extension-rpc.d.ts +23 -3
- package/dist/cross-extension-rpc.d.ts.map +1 -1
- package/dist/cross-extension-rpc.js +79 -17
- package/dist/cross-extension-rpc.js.map +1 -1
- package/dist/custom-agents.d.ts +38 -1
- package/dist/custom-agents.d.ts.map +1 -1
- package/dist/custom-agents.js +164 -12
- package/dist/custom-agents.js.map +1 -1
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1912 -492
- package/dist/index.js.map +1 -1
- package/dist/invocation-config.d.ts +87 -2
- package/dist/invocation-config.d.ts.map +1 -1
- package/dist/invocation-config.js +71 -3
- package/dist/invocation-config.js.map +1 -1
- package/dist/mention-clone.d.ts +88 -0
- package/dist/mention-clone.d.ts.map +1 -0
- package/dist/mention-clone.js +154 -0
- package/dist/mention-clone.js.map +1 -0
- package/dist/mention.d.ts +82 -0
- package/dist/mention.d.ts.map +1 -0
- package/dist/mention.js +132 -0
- package/dist/mention.js.map +1 -0
- package/dist/model-resolver.d.ts +17 -0
- package/dist/model-resolver.d.ts.map +1 -1
- package/dist/model-resolver.js +15 -0
- package/dist/model-resolver.js.map +1 -1
- package/dist/model-scope.d.ts +50 -0
- package/dist/model-scope.d.ts.map +1 -0
- package/dist/model-scope.js +49 -0
- package/dist/model-scope.js.map +1 -0
- package/dist/nested-tools.d.ts +57 -0
- package/dist/nested-tools.d.ts.map +1 -0
- package/dist/nested-tools.js +301 -0
- package/dist/nested-tools.js.map +1 -0
- package/dist/output-file.d.ts +22 -3
- package/dist/output-file.d.ts.map +1 -1
- package/dist/output-file.js +58 -7
- package/dist/output-file.js.map +1 -1
- package/dist/prompts.d.ts +23 -0
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js +20 -2
- package/dist/prompts.js.map +1 -1
- package/dist/schedule.d.ts.map +1 -1
- package/dist/schedule.js +36 -15
- package/dist/schedule.js.map +1 -1
- package/dist/settings.d.ts +228 -2
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +94 -0
- package/dist/settings.js.map +1 -1
- package/dist/status-note.d.ts +49 -1
- package/dist/status-note.d.ts.map +1 -1
- package/dist/status-note.js +62 -1
- package/dist/status-note.js.map +1 -1
- package/dist/structured-output.d.ts +62 -0
- package/dist/structured-output.d.ts.map +1 -0
- package/dist/structured-output.js +113 -0
- package/dist/structured-output.js.map +1 -0
- package/dist/types.d.ts +176 -10
- package/dist/types.d.ts.map +1 -1
- package/dist/ui/agent-mention.d.ts +83 -0
- package/dist/ui/agent-mention.d.ts.map +1 -0
- package/dist/ui/agent-mention.js +188 -0
- package/dist/ui/agent-mention.js.map +1 -0
- package/dist/ui/agent-widget.d.ts +97 -75
- package/dist/ui/agent-widget.d.ts.map +1 -1
- package/dist/ui/agent-widget.js +398 -420
- package/dist/ui/agent-widget.js.map +1 -1
- package/dist/ui/conversation-blocks.d.ts.map +1 -1
- package/dist/ui/conversation-blocks.js +6 -0
- package/dist/ui/conversation-blocks.js.map +1 -1
- package/dist/ui/conversation-timeline.d.ts +10 -2
- package/dist/ui/conversation-timeline.d.ts.map +1 -1
- package/dist/ui/conversation-timeline.js +130 -23
- package/dist/ui/conversation-timeline.js.map +1 -1
- package/dist/ui/conversation-viewer.d.ts +15 -5
- package/dist/ui/conversation-viewer.d.ts.map +1 -1
- package/dist/ui/conversation-viewer.js +202 -50
- package/dist/ui/conversation-viewer.js.map +1 -1
- package/dist/ui/fleet-list.d.ts +198 -0
- package/dist/ui/fleet-list.d.ts.map +1 -0
- package/dist/ui/fleet-list.js +487 -0
- package/dist/ui/fleet-list.js.map +1 -0
- package/dist/ui/schedule-menu.d.ts.map +1 -1
- package/dist/ui/schedule-menu.js +6 -7
- package/dist/ui/schedule-menu.js.map +1 -1
- package/dist/ui/select-item.d.ts +28 -0
- package/dist/ui/select-item.d.ts.map +1 -0
- package/dist/ui/select-item.js +35 -0
- package/dist/ui/select-item.js.map +1 -0
- package/dist/ui/workflow-card.d.ts +176 -0
- package/dist/ui/workflow-card.d.ts.map +1 -0
- package/dist/ui/workflow-card.js +333 -0
- package/dist/ui/workflow-card.js.map +1 -0
- package/dist/ui/workflow-dialog.d.ts +306 -0
- package/dist/ui/workflow-dialog.d.ts.map +1 -0
- package/dist/ui/workflow-dialog.js +844 -0
- package/dist/ui/workflow-dialog.js.map +1 -0
- package/dist/ui/workflow-menu.d.ts +61 -0
- package/dist/ui/workflow-menu.d.ts.map +1 -0
- package/dist/ui/workflow-menu.js +148 -0
- package/dist/ui/workflow-menu.js.map +1 -0
- package/dist/usage.d.ts +86 -1
- package/dist/usage.d.ts.map +1 -1
- package/dist/usage.js +72 -1
- package/dist/usage.js.map +1 -1
- package/dist/workflow/collisions.d.ts +96 -0
- package/dist/workflow/collisions.d.ts.map +1 -0
- package/dist/workflow/collisions.js +89 -0
- package/dist/workflow/collisions.js.map +1 -0
- package/dist/workflow/entry.d.ts +33 -0
- package/dist/workflow/entry.d.ts.map +1 -0
- package/dist/workflow/entry.js +30 -0
- package/dist/workflow/entry.js.map +1 -0
- package/dist/workflow/host.d.ts +63 -0
- package/dist/workflow/host.d.ts.map +1 -0
- package/dist/workflow/host.js +363 -0
- package/dist/workflow/host.js.map +1 -0
- package/dist/workflow/journal.d.ts +98 -0
- package/dist/workflow/journal.d.ts.map +1 -0
- package/dist/workflow/journal.js +121 -0
- package/dist/workflow/journal.js.map +1 -0
- package/dist/workflow/json-schema.d.ts +52 -0
- package/dist/workflow/json-schema.d.ts.map +1 -0
- package/dist/workflow/json-schema.js +112 -0
- package/dist/workflow/json-schema.js.map +1 -0
- package/dist/workflow/meta.d.ts +68 -0
- package/dist/workflow/meta.d.ts.map +1 -0
- package/dist/workflow/meta.js +318 -0
- package/dist/workflow/meta.js.map +1 -0
- package/dist/workflow/progress.d.ts +225 -0
- package/dist/workflow/progress.d.ts.map +1 -0
- package/dist/workflow/progress.js +362 -0
- package/dist/workflow/progress.js.map +1 -0
- package/dist/workflow/runtime.d.ts +335 -0
- package/dist/workflow/runtime.d.ts.map +1 -0
- package/dist/workflow/runtime.js +831 -0
- package/dist/workflow/runtime.js.map +1 -0
- package/dist/workflow/saved.d.ts +91 -0
- package/dist/workflow/saved.d.ts.map +1 -0
- package/dist/workflow/saved.js +204 -0
- package/dist/workflow/saved.js.map +1 -0
- package/dist/workflow/task.d.ts +137 -0
- package/dist/workflow/task.d.ts.map +1 -0
- package/dist/workflow/task.js +208 -0
- package/dist/workflow/task.js.map +1 -0
- package/dist/workflow/tool-description.d.ts +39 -0
- package/dist/workflow/tool-description.d.ts.map +1 -0
- package/dist/workflow/tool-description.js +200 -0
- package/dist/workflow/tool-description.js.map +1 -0
- package/dist/workflow/worker-source.d.ts +48 -0
- package/dist/workflow/worker-source.d.ts.map +1 -0
- package/dist/workflow/worker-source.js +779 -0
- package/dist/workflow/worker-source.js.map +1 -0
- package/dist/worktree.d.ts +10 -3
- package/dist/worktree.d.ts.map +1 -1
- package/dist/worktree.js +58 -54
- package/dist/worktree.js.map +1 -1
- package/dist/xml.d.ts +11 -0
- package/dist/xml.d.ts.map +1 -0
- package/dist/xml.js +13 -0
- package/dist/xml.js.map +1 -0
- package/docs/rpc.md +183 -0
- package/docs/superpowers/plans/2026-09-30-upstream-event-workflow-partial-history.md +195 -0
- package/docs/superpowers/specs/2026-09-30-upstream-event-workflow-partial-history-design.md +49 -0
- package/docs/workflows.md +437 -0
- package/examples/agent-tool-description.md +7 -7
- package/examples/workflows/compose.js +51 -0
- package/examples/workflows/fan-out-audit.js +47 -0
- package/examples/workflows/gated-fix.js +60 -0
- package/examples/workflows/lib/count-child.js +27 -0
- package/examples/workflows/review-panel.js +63 -0
- package/examples/workflows/structured-findings.js +78 -0
- package/package.json +1 -1
- package/src/abortable.ts +43 -0
- package/src/agent-color.ts +161 -0
- package/src/agent-file-toggle.ts +269 -0
- package/src/agent-history.ts +54 -2
- package/src/agent-manager.ts +1263 -402
- package/src/agent-runner.ts +251 -27
- package/src/agent-types.ts +188 -32
- package/src/child-context.ts +15 -0
- package/src/cross-extension-rpc.ts +96 -20
- package/src/custom-agents.ts +170 -13
- package/src/index.ts +2029 -536
- package/src/invocation-config.ts +118 -3
- package/src/mention-clone.ts +196 -0
- package/src/mention.ts +141 -0
- package/src/model-resolver.ts +18 -0
- package/src/model-scope.ts +70 -0
- package/src/nested-tools.ts +424 -0
- package/src/output-file.ts +61 -6
- package/src/prompts.ts +45 -2
- package/src/schedule.ts +35 -14
- package/src/settings.ts +312 -2
- package/src/status-note.ts +66 -1
- package/src/structured-output.ts +130 -0
- package/src/types.ts +177 -10
- package/src/ui/agent-mention.ts +216 -0
- package/src/ui/agent-widget.ts +393 -441
- package/src/ui/conversation-blocks.ts +6 -0
- package/src/ui/conversation-timeline.ts +139 -25
- package/src/ui/conversation-viewer.ts +212 -48
- package/src/ui/fleet-list.ts +558 -0
- package/src/ui/schedule-menu.ts +9 -8
- package/src/ui/select-item.ts +45 -0
- package/src/ui/workflow-card.ts +470 -0
- package/src/ui/workflow-dialog.ts +1115 -0
- package/src/ui/workflow-menu.ts +193 -0
- package/src/usage.ts +109 -2
- package/src/workflow/collisions.ts +123 -0
- package/src/workflow/entry.ts +47 -0
- package/src/workflow/host.ts +403 -0
- package/src/workflow/journal.ts +164 -0
- package/src/workflow/json-schema.ts +128 -0
- package/src/workflow/meta.ts +325 -0
- package/src/workflow/progress.ts +550 -0
- package/src/workflow/runtime.ts +1219 -0
- package/src/workflow/saved.ts +217 -0
- package/src/workflow/task.ts +302 -0
- package/src/workflow/tool-description.ts +200 -0
- package/src/workflow/worker-source.ts +781 -0
- package/src/worktree.ts +69 -55
- package/src/xml.ts +13 -0
- package/vitest.config.ts +0 -18
package/src/custom-agents.ts
CHANGED
|
@@ -6,7 +6,22 @@ import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
|
6
6
|
import { basename, join } from "node:path";
|
|
7
7
|
import { getAgentDir, parseFrontmatter } from "@earendil-works/pi-coding-agent";
|
|
8
8
|
import { BUILTIN_TOOL_NAMES } from "./agent-types.js";
|
|
9
|
-
import type { AgentConfig, MemoryScope, ThinkingLevel } from "./types.js";
|
|
9
|
+
import type { AgentConfig, IsolationMode, MemoryScope, ThinkingLevel } from "./types.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The one thing a declared `name:` may not contain, matching Claude Code
|
|
13
|
+
* exactly: it reserves `:` for plugin-scoped identifiers (`my-plugin:reviewer`)
|
|
14
|
+
* and refuses to load a file whose name uses one.
|
|
15
|
+
*
|
|
16
|
+
* Nothing else is rejected. Claude Code's docs describe names as "lowercase
|
|
17
|
+
* letters and hyphens", but that is guidance — the only stated load failure is
|
|
18
|
+
* the colon, so `name: Code Reviewer` must work here too. (The stricter
|
|
19
|
+
* letters/digits/underscore/hyphen regex in Claude Code applies to the Agent
|
|
20
|
+
* tool's spawn-time `name` parameter, which is a different field.) Mixed case
|
|
21
|
+
* has to be allowed regardless: the built-in types `Explore` and `Plan` use it,
|
|
22
|
+
* and a file must be able to override one.
|
|
23
|
+
*/
|
|
24
|
+
const RESERVED_IN_TYPE = ":";
|
|
10
25
|
|
|
11
26
|
/**
|
|
12
27
|
* Scan for custom agent .md files from multiple locations.
|
|
@@ -19,21 +34,30 @@ import type { AgentConfig, MemoryScope, ThinkingLevel } from "./types.js";
|
|
|
19
34
|
* between the two project locations, .pi/agents wins — .pi stays the project
|
|
20
35
|
* authority; .agents/agents is an additional read location.
|
|
21
36
|
* Any name is allowed — names matching defaults (e.g. "Explore") override them.
|
|
37
|
+
*
|
|
38
|
+
* An agent's type comes from its frontmatter `name:`, falling back to the
|
|
39
|
+
* filename — Claude Code's rule, where "the filename doesn't have to match".
|
|
40
|
+
* Because the type is now declared rather than derived from a unique path, two
|
|
41
|
+
* files can claim the same one; the later load wins, as it always has for a
|
|
42
|
+
* filename clash, and `warnSkippedOverride` reports the substitution.
|
|
22
43
|
*/
|
|
23
|
-
export function loadCustomAgents(cwd: string): Map<string, AgentConfig> {
|
|
44
|
+
export function loadCustomAgents(cwd: string, strict = false): Map<string, AgentConfig> {
|
|
24
45
|
const globalDir = join(getAgentDir(), "agents");
|
|
25
46
|
const workspaceProjectDir = join(cwd, ".agents", "agents");
|
|
26
47
|
const projectDir = join(cwd, ".pi", "agents");
|
|
27
48
|
|
|
28
49
|
const agents = new Map<string, AgentConfig>();
|
|
29
|
-
loadFromDir(globalDir, agents, "global"); // lowest priority
|
|
30
|
-
loadFromDir(workspaceProjectDir, agents, "project"); // shared workspace
|
|
31
|
-
loadFromDir(projectDir, agents, "project"); // highest priority (overwrites)
|
|
50
|
+
loadFromDir(globalDir, agents, "global", strict); // lowest priority
|
|
51
|
+
loadFromDir(workspaceProjectDir, agents, "project", strict); // shared workspace
|
|
52
|
+
loadFromDir(projectDir, agents, "project", strict); // highest priority (overwrites)
|
|
53
|
+
|
|
54
|
+
warnedLastLoad = warnedThisLoad;
|
|
55
|
+
warnedThisLoad = new Set();
|
|
32
56
|
return agents;
|
|
33
57
|
}
|
|
34
58
|
|
|
35
59
|
/** Load agent configs from a directory into the map. */
|
|
36
|
-
function loadFromDir(dir: string, agents: Map<string, AgentConfig>, source: "project" | "global"): void {
|
|
60
|
+
function loadFromDir(dir: string, agents: Map<string, AgentConfig>, source: "project" | "global", strict: boolean): void {
|
|
37
61
|
if (!existsSync(dir)) return;
|
|
38
62
|
|
|
39
63
|
let files: string[];
|
|
@@ -44,22 +68,49 @@ function loadFromDir(dir: string, agents: Map<string, AgentConfig>, source: "pro
|
|
|
44
68
|
}
|
|
45
69
|
|
|
46
70
|
for (const file of files) {
|
|
47
|
-
const
|
|
71
|
+
const filenameType = basename(file, ".md");
|
|
48
72
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
73
|
+
const path = join(dir, file);
|
|
74
|
+
|
|
75
|
+
const parsed = readAgentFile(path, strict);
|
|
76
|
+
if (!parsed) {
|
|
77
|
+
warnSkippedOverride(filenameType, agents);
|
|
53
78
|
continue;
|
|
54
79
|
}
|
|
80
|
+
const { frontmatter: fm, body } = parsed;
|
|
55
81
|
|
|
56
|
-
|
|
82
|
+
// Claude Code's rule: `name:` IS the agent type, and the filename need not
|
|
83
|
+
// match. Absent, the filename stands in — Claude Code requires the field,
|
|
84
|
+
// but most files here predate it and must keep loading.
|
|
85
|
+
const declared = str(fm.name)?.trim();
|
|
86
|
+
if (declared?.includes(RESERVED_IN_TYPE)) {
|
|
87
|
+
// Refusing beats silently substituting: the file would otherwise load
|
|
88
|
+
// under its filename, so `Agent({subagent_type})` would succeed against
|
|
89
|
+
// an agent whose declared identity nothing honoured.
|
|
90
|
+
warnIfNew(
|
|
91
|
+
`Agent file ${path} declares name "${declared}", which contains "${RESERVED_IN_TYPE}" — reserved for `
|
|
92
|
+
+ "plugin-scoped identifiers. Rename it, or move the label to `display_name:`. Skipping.",
|
|
93
|
+
);
|
|
94
|
+
// No `warnSkippedOverride`: this file would have registered under its
|
|
95
|
+
// *declared* name, which nothing else can hold (a colon keeps it out of
|
|
96
|
+
// the registry), so it shadowed nothing. Passing the filename instead
|
|
97
|
+
// would report a substitution of an unrelated agent that never happened.
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
// `||`, not `??`: a quoted empty or all-whitespace `name:` would otherwise
|
|
101
|
+
// register the agent under the empty type — unspawnable, and it takes the
|
|
102
|
+
// filename-derived one down with it.
|
|
103
|
+
const name = declared || filenameType;
|
|
57
104
|
|
|
58
105
|
const { builtinToolNames, extSelectors } = parseToolsField(fm.tools);
|
|
59
106
|
|
|
60
107
|
agents.set(name, {
|
|
61
108
|
name,
|
|
109
|
+
// Only `display_name` now: `name` is the type, and `getConfig` already
|
|
110
|
+
// falls back to the type when no label is set — so a Claude Code file
|
|
111
|
+
// with `name: code-reviewer` still badges as "code-reviewer".
|
|
62
112
|
displayName: str(fm.display_name),
|
|
113
|
+
color: str(fm.color),
|
|
63
114
|
description: str(fm.description) ?? name,
|
|
64
115
|
builtinToolNames,
|
|
65
116
|
extSelectors,
|
|
@@ -73,19 +124,91 @@ function loadFromDir(dir: string, agents: Map<string, AgentConfig>, source: "pro
|
|
|
73
124
|
persistSession: fm.persist_session != null ? fm.persist_session === true : undefined,
|
|
74
125
|
outputTranscript: fm.output_transcript != null ? fm.output_transcript !== false : undefined,
|
|
75
126
|
sessionDir: str(fm.session_dir),
|
|
127
|
+
allowedSubagents: parseAllowedSubagents(fm.allowed_subagents),
|
|
76
128
|
systemPrompt: body.trim(),
|
|
77
129
|
promptMode: fm.prompt_mode === "append" ? "append" : "replace",
|
|
78
130
|
inheritContext: fm.inherit_context != null ? fm.inherit_context === true : undefined,
|
|
79
131
|
runInBackground: fm.run_in_background != null ? fm.run_in_background === true : undefined,
|
|
80
132
|
isolated: fm.isolated != null ? fm.isolated === true : undefined,
|
|
81
133
|
memory: parseMemory(fm.memory),
|
|
82
|
-
isolation: fm.isolation
|
|
134
|
+
isolation: parseIsolation(fm.isolation),
|
|
83
135
|
enabled: fm.enabled !== false, // default true; explicitly false disables
|
|
84
136
|
source,
|
|
137
|
+
sourcePath: path,
|
|
85
138
|
});
|
|
86
139
|
}
|
|
87
140
|
}
|
|
88
141
|
|
|
142
|
+
/**
|
|
143
|
+
* Read and parse one agent file, or warn and return undefined for the caller to
|
|
144
|
+
* skip. One bad file must not take the whole extension down with it — an
|
|
145
|
+
* unparseable `.md` used to abort activation, so pi exited before the TUI.
|
|
146
|
+
*
|
|
147
|
+
* The path is as much of the fix as the recovery: a bare YAML error ("line 2,
|
|
148
|
+
* column 14") is unactionable when agents come from three directories at once,
|
|
149
|
+
* and the only other symptom is `Unknown agent type`, which reads like a typo.
|
|
150
|
+
*
|
|
151
|
+
* Under `strict` the same failure rethrows, still naming the path, so callers
|
|
152
|
+
* that opted into failing closed stop rather than run a substituted agent.
|
|
153
|
+
*/
|
|
154
|
+
/**
|
|
155
|
+
* Parse an agent file's frontmatter, tolerating a leading UTF-8 BOM.
|
|
156
|
+
*
|
|
157
|
+
* Editors across the Windows/CJK world write UTF-8 with a BOM by default, and
|
|
158
|
+
* pi's parser did not look past one before 0.84.3: the fence never matched, so
|
|
159
|
+
* the frontmatter came back empty and the *whole file* — YAML and all — became
|
|
160
|
+
* the body. An agent authored that way silently lost every field. `tools: none`
|
|
161
|
+
* going missing is the sharp edge: the agent registers with the default
|
|
162
|
+
* toolset rather than none, which is a wider grant than its author wrote.
|
|
163
|
+
*
|
|
164
|
+
* Stripped here rather than detected per pi version, because this is the only
|
|
165
|
+
* place agent files are read and the BOM is a file-encoding artifact, not
|
|
166
|
+
* content — normalising it at the boundary keeps one behaviour across the whole
|
|
167
|
+
* supported peer range instead of forking on what happens to be installed.
|
|
168
|
+
*/
|
|
169
|
+
export function parseAgentFrontmatter<T extends Record<string, unknown>>(content: string): { frontmatter: T; body: string } {
|
|
170
|
+
return parseFrontmatter<T>(content.startsWith("\uFEFF") ? content.slice(1) : content);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function readAgentFile(path: string, strict: boolean): { frontmatter: Record<string, unknown>; body: string } | undefined {
|
|
174
|
+
try {
|
|
175
|
+
return parseAgentFrontmatter<Record<string, unknown>>(readFileSync(path, "utf-8"));
|
|
176
|
+
} catch (err) {
|
|
177
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
178
|
+
if (strict) throw new Error(`${path}: ${reason}`);
|
|
179
|
+
warnIfNew(`Skipping agent file ${path}: ${reason}`);
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* A skipped file that was overriding an already-loaded agent leaves the name
|
|
186
|
+
* pointing at a *different* file — its own prompt, model and tools. Nothing
|
|
187
|
+
* downstream can flag that: unlike an unknown type, the `Agent` call succeeds.
|
|
188
|
+
*/
|
|
189
|
+
function warnSkippedOverride(name: string, agents: Map<string, AgentConfig>): void {
|
|
190
|
+
const surviving = agents.get(name);
|
|
191
|
+
// Nothing shadowed, or what it shadowed is disabled: dispatch refuses the type
|
|
192
|
+
// either way (see resolveEnabledTypeIn), so there is no substitution to report.
|
|
193
|
+
if (!surviving?.sourcePath || surviving.enabled === false) return;
|
|
194
|
+
warnIfNew(`Agent "${name}" now loads from ${surviving.sourcePath} instead`);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
let warnedLastLoad = new Set<string>();
|
|
198
|
+
let warnedThisLoad = new Set<string>();
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Agents reload on activation and again on every `Agent` call, so an unchanged
|
|
202
|
+
* problem would re-warn all session — over a painted TUI, since pi does not
|
|
203
|
+
* redirect console output. Compare against the previous load rather than every
|
|
204
|
+
* load ever, so a file that is fixed and then broken again still reports.
|
|
205
|
+
*/
|
|
206
|
+
function warnIfNew(message: string): void {
|
|
207
|
+
warnedThisLoad.add(message);
|
|
208
|
+
if (warnedLastLoad.has(message)) return;
|
|
209
|
+
console.warn(`[pi-subagents] ${message}`);
|
|
210
|
+
}
|
|
211
|
+
|
|
89
212
|
// ---- Field parsers ----
|
|
90
213
|
// All follow the same convention: omitted → default, "none"/empty → nothing, value → exact.
|
|
91
214
|
|
|
@@ -110,6 +233,22 @@ function parseCsvField(val: unknown): string[] | undefined {
|
|
|
110
233
|
return items.length > 0 ? items : undefined;
|
|
111
234
|
}
|
|
112
235
|
|
|
236
|
+
/**
|
|
237
|
+
* Parse the nested-delegation allowlist. Single field, default-off:
|
|
238
|
+
* omitted/empty/"none"/`false` → undefined (no nested tools); "all"/"*"/`true`
|
|
239
|
+
* → "all" (any enabled agent); csv → only the listed types.
|
|
240
|
+
*
|
|
241
|
+
* Booleans are accepted because `extensions:`/`skills:` take them and users
|
|
242
|
+
* generalize: without this, YAML's `true` stringifies into an agent type
|
|
243
|
+
* literally named "true", so the tools appear and every spawn is refused.
|
|
244
|
+
*/
|
|
245
|
+
function parseAllowedSubagents(val: unknown): "all" | string[] | undefined {
|
|
246
|
+
if (typeof val === "boolean") return val ? "all" : undefined;
|
|
247
|
+
const items = parseCsvField(val);
|
|
248
|
+
if (!items) return undefined;
|
|
249
|
+
return items.some(i => i === "*" || i.toLowerCase() === "all") ? "all" : items;
|
|
250
|
+
}
|
|
251
|
+
|
|
113
252
|
/**
|
|
114
253
|
* Parse a comma-separated list field with defaults.
|
|
115
254
|
* omitted → defaults; "none"/empty → []; csv → listed items.
|
|
@@ -155,6 +294,24 @@ function parseMemory(val: unknown): MemoryScope | undefined {
|
|
|
155
294
|
return undefined;
|
|
156
295
|
}
|
|
157
296
|
|
|
297
|
+
/**
|
|
298
|
+
* Parse the `isolation` frontmatter field.
|
|
299
|
+
*
|
|
300
|
+
* `off` is kept as a value rather than folded into `undefined` because the two
|
|
301
|
+
* do not mean the same thing here: agent config outranks tool-call params, so
|
|
302
|
+
* `off` vetoes a caller's `worktree` while an absent field lets it through.
|
|
303
|
+
*
|
|
304
|
+
* pi's frontmatter parser is not YAML 1.1 — bare `off` and `no` arrive as
|
|
305
|
+
* strings and only `false` becomes a boolean — so all three spellings are
|
|
306
|
+
* accepted rather than leaving an author's intent silently dropped. Anything
|
|
307
|
+
* else stays `undefined`, as before.
|
|
308
|
+
*/
|
|
309
|
+
function parseIsolation(val: unknown): IsolationMode | undefined {
|
|
310
|
+
if (val === "worktree") return "worktree";
|
|
311
|
+
if (val === "off" || val === "none" || val === "no" || val === false) return "off";
|
|
312
|
+
return undefined;
|
|
313
|
+
}
|
|
314
|
+
|
|
158
315
|
/**
|
|
159
316
|
* Parse an inherit field (extensions, skills).
|
|
160
317
|
* omitted/true → true (inherit all); false/"none"/empty → false; csv → listed names.
|