pi-subagents 0.29.0 → 0.31.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 +43 -0
- package/README.md +125 -19
- package/agents/context-builder.md +3 -3
- package/agents/planner.md +1 -1
- package/agents/researcher.md +1 -1
- package/agents/scout.md +1 -1
- package/package.json +7 -7
- package/skills/pi-subagents/SKILL.md +30 -0
- package/src/agents/agent-management.ts +189 -8
- package/src/agents/agent-serializer.ts +35 -12
- package/src/agents/agents.ts +243 -24
- package/src/agents/frontmatter.ts +66 -2
- package/src/agents/proactive-skills.ts +191 -0
- package/src/agents/skills.ts +117 -20
- package/src/extension/doctor.ts +20 -0
- package/src/extension/fanout-child.ts +2 -1
- package/src/extension/index.ts +50 -5
- package/src/extension/schemas.ts +40 -79
- package/src/intercom/intercom-bridge.ts +2 -3
- package/src/runs/background/async-execution.ts +180 -67
- package/src/runs/background/async-job-tracker.ts +56 -11
- package/src/runs/background/async-resume.ts +53 -5
- package/src/runs/background/async-status.ts +4 -1
- package/src/runs/background/chain-append.ts +282 -0
- package/src/runs/background/chain-root-attachment.ts +161 -0
- package/src/runs/background/result-watcher.ts +11 -2
- package/src/runs/background/run-status.ts +1 -0
- package/src/runs/background/stale-run-reconciler.ts +9 -4
- package/src/runs/background/subagent-runner.ts +158 -11
- package/src/runs/foreground/chain-execution.ts +26 -2
- package/src/runs/foreground/execution.ts +114 -8
- package/src/runs/foreground/subagent-executor.ts +611 -87
- package/src/runs/shared/acceptance.ts +285 -34
- package/src/runs/shared/chain-outputs.ts +23 -8
- package/src/runs/shared/completion-guard.ts +1 -1
- package/src/runs/shared/dynamic-fanout.ts +5 -3
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +2 -2
- package/src/runs/shared/parallel-utils.ts +13 -1
- package/src/runs/shared/pi-args.ts +12 -3
- package/src/runs/shared/single-output.ts +15 -1
- package/src/runs/shared/subagent-control.ts +8 -11
- package/src/shared/settings.ts +1 -0
- package/src/shared/types.ts +17 -2
- package/src/shared/utils.ts +19 -1
- package/src/slash/prompt-template-bridge.ts +26 -3
- package/src/slash/slash-bridge.ts +3 -1
- package/src/slash/slash-commands.ts +34 -4
- package/src/tui/render.ts +265 -13
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
type AgentSource,
|
|
9
9
|
type ChainConfig,
|
|
10
10
|
type ChainStepConfig,
|
|
11
|
+
BUILTIN_AGENT_NAMES,
|
|
11
12
|
defaultInheritProjectContext,
|
|
12
13
|
defaultInheritSkills,
|
|
13
14
|
defaultSystemPromptMode,
|
|
@@ -19,11 +20,18 @@ import {
|
|
|
19
20
|
import { serializeAgent } from "./agent-serializer.ts";
|
|
20
21
|
import { serializeChain, serializeJsonChain } from "./chain-serializer.ts";
|
|
21
22
|
import { discoverAvailableSkills } from "./skills.ts";
|
|
22
|
-
import
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
import {
|
|
24
|
+
buildProactiveSkillSubagentRecommendationLines,
|
|
25
|
+
} from "./proactive-skills.ts";
|
|
26
|
+
import { parseFrontmatter } from "./frontmatter.ts";
|
|
27
|
+
import { toModelInfo } from "../shared/model-info.ts";
|
|
28
|
+
import { resolveSubagentModelOverride, type ParentModel } from "../runs/shared/model-fallback.ts";
|
|
29
|
+
import type { Details, ExtensionConfig } from "../shared/types.ts";
|
|
30
|
+
import { getProjectConfigDir } from "../shared/utils.ts";
|
|
31
|
+
|
|
32
|
+
type ManagementAction = "list" | "get" | "models" | "create" | "update" | "delete";
|
|
25
33
|
type ManagementScope = "user" | "project";
|
|
26
|
-
type ManagementContext = Pick<ExtensionContext, "cwd" | "modelRegistry"
|
|
34
|
+
type ManagementContext = Pick<ExtensionContext, "cwd" | "modelRegistry"> & { model?: ExtensionContext["model"]; config?: ExtensionConfig };
|
|
27
35
|
|
|
28
36
|
interface ManagementParams {
|
|
29
37
|
action?: string;
|
|
@@ -163,6 +171,84 @@ function skillsWarning(cwd: string, skills: string[] | undefined): string | unde
|
|
|
163
171
|
return missing.length ? `Warning: skills not found: ${missing.join(", ")}.` : undefined;
|
|
164
172
|
}
|
|
165
173
|
|
|
174
|
+
function editableAgentConfig(agent: AgentConfig): AgentConfig {
|
|
175
|
+
const base = agent.override?.base;
|
|
176
|
+
if (!base) return { ...agent };
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
...agent,
|
|
180
|
+
model: base.model,
|
|
181
|
+
fallbackModels: base.fallbackModels ? [...base.fallbackModels] : undefined,
|
|
182
|
+
thinking: base.thinking,
|
|
183
|
+
systemPromptMode: base.systemPromptMode,
|
|
184
|
+
inheritProjectContext: base.inheritProjectContext,
|
|
185
|
+
inheritSkills: base.inheritSkills,
|
|
186
|
+
defaultContext: base.defaultContext,
|
|
187
|
+
disabled: base.disabled,
|
|
188
|
+
systemPrompt: base.systemPrompt,
|
|
189
|
+
skills: base.skills ? [...base.skills] : undefined,
|
|
190
|
+
tools: base.tools ? [...base.tools] : undefined,
|
|
191
|
+
mcpDirectTools: base.mcpDirectTools ? [...base.mcpDirectTools] : undefined,
|
|
192
|
+
subagentOnlyExtensions: base.subagentOnlyExtensions ? [...base.subagentOnlyExtensions] : undefined,
|
|
193
|
+
completionGuard: base.completionGuard,
|
|
194
|
+
override: undefined,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function readAgentFrontmatterFields(filePath: string): Set<string> {
|
|
199
|
+
try {
|
|
200
|
+
const { frontmatter } = parseFrontmatter(fs.readFileSync(filePath, "utf-8"));
|
|
201
|
+
return new Set(Object.keys(frontmatter));
|
|
202
|
+
} catch {
|
|
203
|
+
return new Set();
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function preservedAgentFrontmatterFields(agent: AgentConfig, cfg: Record<string, unknown>): Set<string> {
|
|
208
|
+
const fields = readAgentFrontmatterFields(agent.filePath);
|
|
209
|
+
const changed = (...names: string[]) => {
|
|
210
|
+
for (const name of names) fields.delete(name);
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
if (hasKey(cfg, "name")) changed("name");
|
|
214
|
+
if (hasKey(cfg, "package")) changed("package");
|
|
215
|
+
if (hasKey(cfg, "description")) changed("description");
|
|
216
|
+
if (hasKey(cfg, "systemPrompt")) changed("systemPrompt");
|
|
217
|
+
if (hasKey(cfg, "model")) changed("model");
|
|
218
|
+
if (hasKey(cfg, "fallbackModels")) changed("fallbackModels");
|
|
219
|
+
if (hasKey(cfg, "tools")) changed("tools");
|
|
220
|
+
if (hasKey(cfg, "skills")) changed("skill", "skills");
|
|
221
|
+
if (hasKey(cfg, "extensions")) changed("extensions");
|
|
222
|
+
if (hasKey(cfg, "subagentOnlyExtensions")) changed("subagentOnlyExtensions");
|
|
223
|
+
if (hasKey(cfg, "thinking")) {
|
|
224
|
+
changed("thinking");
|
|
225
|
+
if (cfg.thinking === "off") fields.add("thinking");
|
|
226
|
+
}
|
|
227
|
+
if (hasKey(cfg, "systemPromptMode")) {
|
|
228
|
+
changed("systemPromptMode");
|
|
229
|
+
fields.add("systemPromptMode");
|
|
230
|
+
}
|
|
231
|
+
if (hasKey(cfg, "inheritProjectContext")) {
|
|
232
|
+
changed("inheritProjectContext");
|
|
233
|
+
fields.add("inheritProjectContext");
|
|
234
|
+
}
|
|
235
|
+
if (hasKey(cfg, "inheritSkills")) {
|
|
236
|
+
changed("inheritSkills");
|
|
237
|
+
fields.add("inheritSkills");
|
|
238
|
+
}
|
|
239
|
+
if (hasKey(cfg, "defaultContext")) changed("defaultContext");
|
|
240
|
+
if (hasKey(cfg, "output")) changed("output");
|
|
241
|
+
if (hasKey(cfg, "reads")) changed("defaultReads");
|
|
242
|
+
if (hasKey(cfg, "progress")) changed("defaultProgress");
|
|
243
|
+
if (hasKey(cfg, "maxSubagentDepth")) changed("maxSubagentDepth");
|
|
244
|
+
if (hasKey(cfg, "completionGuard")) {
|
|
245
|
+
changed("completionGuard");
|
|
246
|
+
if (cfg.completionGuard === true) fields.add("completionGuard");
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return fields;
|
|
250
|
+
}
|
|
251
|
+
|
|
166
252
|
function parseStepList(raw: unknown): { steps?: ChainStepConfig[]; error?: string } {
|
|
167
253
|
if (!Array.isArray(raw)) return { error: "config.steps must be an array." };
|
|
168
254
|
if (raw.length === 0) return { error: "config.steps must include at least one step." };
|
|
@@ -273,6 +359,12 @@ function applyAgentConfig(target: AgentConfig, cfg: Record<string, unknown>): st
|
|
|
273
359
|
else if (typeof cfg.extensions === "string") target.extensions = parseCsv(cfg.extensions);
|
|
274
360
|
else return "config.extensions must be a comma-separated string, empty string, or false when provided.";
|
|
275
361
|
}
|
|
362
|
+
if (hasKey(cfg, "subagentOnlyExtensions")) {
|
|
363
|
+
if (cfg.subagentOnlyExtensions === false) target.subagentOnlyExtensions = undefined;
|
|
364
|
+
else if (cfg.subagentOnlyExtensions === "") target.subagentOnlyExtensions = [];
|
|
365
|
+
else if (typeof cfg.subagentOnlyExtensions === "string") target.subagentOnlyExtensions = parseCsv(cfg.subagentOnlyExtensions);
|
|
366
|
+
else return "config.subagentOnlyExtensions must be a comma-separated string, empty string, or false when provided.";
|
|
367
|
+
}
|
|
276
368
|
if (hasKey(cfg, "thinking")) {
|
|
277
369
|
if (cfg.thinking === false || cfg.thinking === "") target.thinking = undefined;
|
|
278
370
|
else if (typeof cfg.thinking === "string") target.thinking = cfg.thinking.trim() || undefined;
|
|
@@ -385,6 +477,7 @@ function formatAgentDetail(agent: AgentConfig): string {
|
|
|
385
477
|
if (agent.defaultContext) lines.push(`Default context: ${agent.defaultContext}`);
|
|
386
478
|
if (agent.source === "builtin") lines.push(`Disabled: ${agent.disabled ? "true" : "false"}`);
|
|
387
479
|
if (agent.extensions !== undefined) lines.push(`Extensions: ${agent.extensions.length ? agent.extensions.join(", ") : "(none)"}`);
|
|
480
|
+
if (agent.subagentOnlyExtensions !== undefined) lines.push(`Subagent-only extensions: ${agent.subagentOnlyExtensions.length ? agent.subagentOnlyExtensions.join(", ") : "(none)"}`);
|
|
388
481
|
if (agent.thinking) lines.push(`Thinking: ${agent.thinking}`);
|
|
389
482
|
if (agent.output) lines.push(`Output: ${agent.output}`);
|
|
390
483
|
if (agent.defaultReads?.length) lines.push(`Reads: ${agent.defaultReads.join(", ")}`);
|
|
@@ -450,6 +543,12 @@ export function handleList(params: ManagementParams, ctx: ManagementContext): Ag
|
|
|
450
543
|
const agents = scopedAgents.filter((a) => !a.disabled);
|
|
451
544
|
const chains = d.chains.filter((c) => scope === "both" || c.source === "package" || c.source === scope).sort((a, b) => a.name.localeCompare(b.name));
|
|
452
545
|
const diagnostics = d.chainDiagnostics.filter((entry) => scope === "both" || entry.source === scope);
|
|
546
|
+
const proactiveSuggestions = buildProactiveSkillSubagentRecommendationLines({
|
|
547
|
+
agents,
|
|
548
|
+
chains,
|
|
549
|
+
config: ctx.config?.proactiveSkillSubagents,
|
|
550
|
+
discoverAvailableSkills: () => discoverAvailableSkills(ctx.cwd),
|
|
551
|
+
});
|
|
453
552
|
const lines = [
|
|
454
553
|
"Executable agents:",
|
|
455
554
|
...(agents.length
|
|
@@ -458,11 +557,90 @@ export function handleList(params: ManagementParams, ctx: ManagementContext): Ag
|
|
|
458
557
|
"",
|
|
459
558
|
"Chains:",
|
|
460
559
|
...(chains.length ? chains.map((c) => `- ${c.name} (${c.source}): ${c.description}`) : ["- (none)"]),
|
|
560
|
+
...(proactiveSuggestions.length ? ["", ...proactiveSuggestions] : []),
|
|
461
561
|
...(diagnostics.length ? ["", "Chain diagnostics:", ...diagnostics.map((entry) => `- ${entry.filePath}: ${entry.error}`)] : []),
|
|
462
562
|
];
|
|
463
563
|
return result(lines.join("\n"));
|
|
464
564
|
}
|
|
465
565
|
|
|
566
|
+
function formatModelSource(agent: AgentConfig, currentModel: ParentModel | undefined): string {
|
|
567
|
+
if (agent.override && agent.model !== agent.override.base.model) {
|
|
568
|
+
return `${agent.override.scope} override`;
|
|
569
|
+
}
|
|
570
|
+
if (agent.model) return "builtin agent config";
|
|
571
|
+
if (currentModel) return "inherits current session model";
|
|
572
|
+
return "inherit requested, but no current session model is available";
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
function handleModels(params: ManagementParams, ctx: ManagementContext): AgentToolResult<Details> {
|
|
576
|
+
const requestedAgent = params.agent?.trim();
|
|
577
|
+
if (requestedAgent && !(BUILTIN_AGENT_NAMES as readonly string[]).includes(requestedAgent)) {
|
|
578
|
+
return result(`Builtin agent '${requestedAgent}' not found. Available: ${BUILTIN_AGENT_NAMES.join(", ")}.`, true);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
const discovered = discoverAgentsAll(ctx.cwd);
|
|
582
|
+
const builtinByName = new Map(discovered.builtin.map((agent) => [agent.name, agent]));
|
|
583
|
+
const availableModels = ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
584
|
+
const currentModel = ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined;
|
|
585
|
+
const preferredProvider = ctx.model?.provider;
|
|
586
|
+
const names = requestedAgent ? [requestedAgent] : [...BUILTIN_AGENT_NAMES];
|
|
587
|
+
|
|
588
|
+
if (requestedAgent) {
|
|
589
|
+
const agent = builtinByName.get(requestedAgent);
|
|
590
|
+
if (!agent) return result(`Builtin agent '${requestedAgent}' not found.`, true);
|
|
591
|
+
const resolvedModel = resolveSubagentModelOverride(agent.model, currentModel, availableModels, preferredProvider);
|
|
592
|
+
const lines = [
|
|
593
|
+
"Builtin subagent model",
|
|
594
|
+
"",
|
|
595
|
+
`Agent: ${requestedAgent}`,
|
|
596
|
+
"Effective model:",
|
|
597
|
+
` ${resolvedModel ?? "(unresolved)"}`,
|
|
598
|
+
`Source: ${formatModelSource(agent, currentModel)}`,
|
|
599
|
+
];
|
|
600
|
+
if (agent.override) {
|
|
601
|
+
lines.push("Override file:");
|
|
602
|
+
lines.push(` ${agent.override.path}`);
|
|
603
|
+
}
|
|
604
|
+
if (agent.model && resolvedModel && agent.model !== resolvedModel) {
|
|
605
|
+
lines.push("Requested model setting:");
|
|
606
|
+
lines.push(` ${agent.model}`);
|
|
607
|
+
}
|
|
608
|
+
if (agent.disabled) lines.push("Disabled: true");
|
|
609
|
+
lines.push("Current session model:");
|
|
610
|
+
lines.push(` ${currentModel ? `${currentModel.provider}/${currentModel.id}` : "(unavailable)"}`);
|
|
611
|
+
return result(lines.join("\n"));
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
const lines = [
|
|
615
|
+
"Builtin subagent models",
|
|
616
|
+
"",
|
|
617
|
+
"Current session model:",
|
|
618
|
+
` ${currentModel ? `${currentModel.provider}/${currentModel.id}` : "(unavailable)"}`,
|
|
619
|
+
"",
|
|
620
|
+
];
|
|
621
|
+
|
|
622
|
+
for (const name of names) {
|
|
623
|
+
const agent = builtinByName.get(name);
|
|
624
|
+
if (!agent) {
|
|
625
|
+
lines.push(name);
|
|
626
|
+
lines.push(" model:");
|
|
627
|
+
lines.push(" (builtin definition not found)");
|
|
628
|
+
lines.push(" source: missing");
|
|
629
|
+
lines.push("");
|
|
630
|
+
continue;
|
|
631
|
+
}
|
|
632
|
+
const resolvedModel = resolveSubagentModelOverride(agent.model, currentModel, availableModels, preferredProvider);
|
|
633
|
+
const source = `${formatModelSource(agent, currentModel)}${agent.disabled ? "; disabled" : ""}`;
|
|
634
|
+
lines.push(name);
|
|
635
|
+
lines.push(" model:");
|
|
636
|
+
lines.push(` ${resolvedModel ?? "(unresolved)"}`);
|
|
637
|
+
lines.push(` source: ${source}`);
|
|
638
|
+
lines.push("");
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
return result(lines.join("\n"));
|
|
642
|
+
}
|
|
643
|
+
|
|
466
644
|
function handleGet(params: ManagementParams, ctx: ManagementContext): AgentToolResult<Details> {
|
|
467
645
|
if (!params.agent && !params.chainName) return result("Specify 'agent' or 'chainName' for get.", true);
|
|
468
646
|
const hasBoth = Boolean(params.agent && params.chainName);
|
|
@@ -510,9 +688,10 @@ export function handleCreate(params: ManagementParams, ctx: ManagementContext):
|
|
|
510
688
|
const scope = scopeRaw as ManagementScope;
|
|
511
689
|
const isChain = hasKey(cfg, "steps");
|
|
512
690
|
const d = discoverAgentsAll(ctx.cwd);
|
|
691
|
+
const projectConfigDir = getProjectConfigDir(ctx.cwd);
|
|
513
692
|
const targetDir = isChain
|
|
514
|
-
? scope === "user" ? d.userChainDir : d.projectChainDir ?? path.join(
|
|
515
|
-
: scope === "user" ? d.userDir : d.projectDir ?? path.join(
|
|
693
|
+
? scope === "user" ? d.userChainDir : d.projectChainDir ?? path.join(projectConfigDir, "chains")
|
|
694
|
+
: scope === "user" ? d.userDir : d.projectDir ?? path.join(projectConfigDir, "agents");
|
|
516
695
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
517
696
|
if (nameExistsInScope(ctx.cwd, scope, runtimeName)) return result(`Name '${runtimeName}' already exists in ${scope} scope. Use update instead.`, true);
|
|
518
697
|
const targetPath = path.join(targetDir, isChain ? `${runtimeName}.chain.md` : `${runtimeName}.md`);
|
|
@@ -566,7 +745,7 @@ export function handleUpdate(params: ManagementParams, ctx: ManagementContext):
|
|
|
566
745
|
const targetOrError = resolveTarget("agent", params.agent, findAgents(params.agent, ctx.cwd, scopeHint ?? "both"), ctx.cwd, params.agentScope);
|
|
567
746
|
if ("content" in targetOrError) return targetOrError;
|
|
568
747
|
const target = targetOrError;
|
|
569
|
-
const updated
|
|
748
|
+
const updated = editableAgentConfig(target);
|
|
570
749
|
const oldName = target.name;
|
|
571
750
|
if (hasKey(cfg, "name") && (typeof cfg.name !== "string" || !cfg.name.trim())) return result("config.name must be a non-empty string when provided.", true);
|
|
572
751
|
if (hasKey(cfg, "description") && (typeof cfg.description !== "string" || !cfg.description.trim())) return result("config.description must be a non-empty string when provided.", true);
|
|
@@ -583,6 +762,7 @@ export function handleUpdate(params: ManagementParams, ctx: ManagementContext):
|
|
|
583
762
|
}
|
|
584
763
|
const applyError = applyAgentConfig(updated, cfg);
|
|
585
764
|
if (applyError) return result(applyError, true);
|
|
765
|
+
const preserveFrontmatterFields = preservedAgentFrontmatterFields(target, cfg);
|
|
586
766
|
updated.localName = newLocalName;
|
|
587
767
|
updated.packageName = newPackageName;
|
|
588
768
|
updated.name = buildRuntimeName(newLocalName, newPackageName);
|
|
@@ -604,7 +784,7 @@ export function handleUpdate(params: ManagementParams, ctx: ManagementContext):
|
|
|
604
784
|
if (renamed.error) return result(renamed.error, true);
|
|
605
785
|
updated.filePath = renamed.filePath!;
|
|
606
786
|
}
|
|
607
|
-
fs.writeFileSync(updated.filePath, serializeAgent(updated), "utf-8");
|
|
787
|
+
fs.writeFileSync(updated.filePath, serializeAgent(updated, { preserveFrontmatterFields }), "utf-8");
|
|
608
788
|
if (updated.name !== oldName) {
|
|
609
789
|
const refs = discoverAgentsAll(ctx.cwd).chains.filter((c) => c.steps.some((s) => s.agent === oldName)).map((c) => `${c.name} (${c.source})`);
|
|
610
790
|
if (refs.length) warnings.push(`Warning: chains still reference '${oldName}': ${refs.join(", ")}.`);
|
|
@@ -686,6 +866,7 @@ export function handleManagementAction(action: string, params: ManagementParams,
|
|
|
686
866
|
switch (action as ManagementAction) {
|
|
687
867
|
case "list": return handleList(params, ctx);
|
|
688
868
|
case "get": return handleGet(params, ctx);
|
|
869
|
+
case "models": return handleModels(params, ctx);
|
|
689
870
|
case "create": return handleCreate(params, ctx);
|
|
690
871
|
case "update": return handleUpdate(params, ctx);
|
|
691
872
|
case "delete": return handleDelete(params, ctx);
|
|
@@ -16,6 +16,7 @@ export const KNOWN_FIELDS = new Set([
|
|
|
16
16
|
"skill",
|
|
17
17
|
"skills",
|
|
18
18
|
"extensions",
|
|
19
|
+
"subagentOnlyExtensions",
|
|
19
20
|
"output",
|
|
20
21
|
"defaultReads",
|
|
21
22
|
"defaultProgress",
|
|
@@ -29,8 +30,14 @@ function joinComma(values: string[] | undefined): string | undefined {
|
|
|
29
30
|
return values.join(", ");
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
|
|
33
|
+
interface SerializeAgentOptions {
|
|
34
|
+
preserveFrontmatterFields?: ReadonlySet<string>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function serializeAgent(config: AgentConfig, options: SerializeAgentOptions = {}): string {
|
|
33
38
|
const lines: string[] = [];
|
|
39
|
+
const preserve = (...fields: string[]) => fields.some((field) => options.preserveFrontmatterFields?.has(field));
|
|
40
|
+
const preservingExistingFrontmatter = options.preserveFrontmatterFields !== undefined;
|
|
34
41
|
lines.push("---");
|
|
35
42
|
lines.push(`name: ${frontmatterNameForConfig(config)}`);
|
|
36
43
|
if (config.packageName) lines.push(`package: ${config.packageName}`);
|
|
@@ -41,24 +48,30 @@ export function serializeAgent(config: AgentConfig): string {
|
|
|
41
48
|
...(config.mcpDirectTools ?? []).map((tool) => `mcp:${tool}`),
|
|
42
49
|
];
|
|
43
50
|
const toolsValue = joinComma(tools);
|
|
44
|
-
if (toolsValue) lines.push(`tools: ${toolsValue}`);
|
|
51
|
+
if (toolsValue || preserve("tools")) lines.push(`tools: ${toolsValue ?? ""}`);
|
|
45
52
|
|
|
46
|
-
if (config.model) lines.push(`model: ${config.model}`);
|
|
53
|
+
if (config.model || preserve("model")) lines.push(`model: ${config.model ?? ""}`);
|
|
47
54
|
const fallbackModelsValue = joinComma(config.fallbackModels);
|
|
48
|
-
if (fallbackModelsValue) lines.push(`fallbackModels: ${fallbackModelsValue}`);
|
|
49
|
-
if (config.thinking && config.thinking !== "off"
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
lines.push(`
|
|
53
|
-
if (
|
|
55
|
+
if (fallbackModelsValue || preserve("fallbackModels")) lines.push(`fallbackModels: ${fallbackModelsValue ?? ""}`);
|
|
56
|
+
if ((config.thinking && (config.thinking !== "off" || preserve("thinking"))) || (!config.thinking && preserve("thinking"))) {
|
|
57
|
+
lines.push(`thinking: ${config.thinking ?? ""}`);
|
|
58
|
+
}
|
|
59
|
+
if (!preservingExistingFrontmatter || preserve("systemPromptMode")) lines.push(`systemPromptMode: ${config.systemPromptMode}`);
|
|
60
|
+
if (!preservingExistingFrontmatter || preserve("inheritProjectContext")) lines.push(`inheritProjectContext: ${config.inheritProjectContext ? "true" : "false"}`);
|
|
61
|
+
if (!preservingExistingFrontmatter || preserve("inheritSkills")) lines.push(`inheritSkills: ${config.inheritSkills ? "true" : "false"}`);
|
|
62
|
+
if (config.defaultContext || preserve("defaultContext")) lines.push(`defaultContext: ${config.defaultContext ?? ""}`);
|
|
54
63
|
|
|
55
64
|
const skillsValue = joinComma(config.skills);
|
|
56
|
-
if (skillsValue) lines.push(`skills: ${skillsValue}`);
|
|
65
|
+
if (skillsValue || preserve("skill", "skills")) lines.push(`skills: ${skillsValue ?? ""}`);
|
|
57
66
|
|
|
58
67
|
if (config.extensions !== undefined) {
|
|
59
68
|
const extensionsValue = joinComma(config.extensions);
|
|
60
69
|
lines.push(`extensions: ${extensionsValue ?? ""}`);
|
|
61
70
|
}
|
|
71
|
+
if (config.subagentOnlyExtensions !== undefined || preserve("subagentOnlyExtensions")) {
|
|
72
|
+
const subagentOnlyExtensionsValue = joinComma(config.subagentOnlyExtensions);
|
|
73
|
+
lines.push(`subagentOnlyExtensions: ${subagentOnlyExtensionsValue ?? ""}`);
|
|
74
|
+
}
|
|
62
75
|
|
|
63
76
|
if (config.output) lines.push(`output: ${config.output}`);
|
|
64
77
|
|
|
@@ -71,12 +84,22 @@ export function serializeAgent(config: AgentConfig): string {
|
|
|
71
84
|
if (typeof maxSubagentDepth === "number" && Number.isInteger(maxSubagentDepth) && maxSubagentDepth >= 0) {
|
|
72
85
|
lines.push(`maxSubagentDepth: ${maxSubagentDepth}`);
|
|
73
86
|
}
|
|
74
|
-
if (config.completionGuard === false
|
|
87
|
+
if (config.completionGuard === false || preserve("completionGuard")) {
|
|
88
|
+
lines.push(`completionGuard: ${config.completionGuard === undefined ? "" : config.completionGuard ? "true" : "false"}`);
|
|
89
|
+
}
|
|
75
90
|
|
|
76
91
|
if (config.extraFields) {
|
|
77
92
|
for (const [key, value] of Object.entries(config.extraFields)) {
|
|
78
93
|
if (KNOWN_FIELDS.has(key)) continue;
|
|
79
|
-
|
|
94
|
+
if (typeof value === "string" && value.includes("\n")) {
|
|
95
|
+
// Multi-line block value (e.g. permission: nested YAML)
|
|
96
|
+
lines.push(`${key}:`);
|
|
97
|
+
for (const blockLine of value.split("\n")) {
|
|
98
|
+
lines.push(` ${blockLine}`);
|
|
99
|
+
}
|
|
100
|
+
} else {
|
|
101
|
+
lines.push(`${key}: ${value}`);
|
|
102
|
+
}
|
|
80
103
|
}
|
|
81
104
|
}
|
|
82
105
|
|