pi-subagents 0.40.0 → 0.41.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 +76 -0
- package/README.md +246 -525
- package/agents/oracle.md +1 -0
- package/package.json +12 -4
- package/prompts/parallel-context-build.md +1 -1
- package/prompts/parallel-handoff-plan.md +1 -1
- package/prompts/review-loop.md +1 -1
- package/skills/pi-subagents/SKILL.md +6 -6
- package/skills/pi-subagents/references/constraints-and-recipes.md +19 -26
- package/skills/pi-subagents/references/execution-controls.md +98 -97
- package/skills/pi-subagents/references/management-authoring-rpc.md +2 -2
- package/skills/pi-subagents/references/prompting-and-roles.md +18 -27
- package/src/agents/agent-management.ts +155 -65
- package/src/agents/agent-serializer.ts +19 -0
- package/src/agents/agents.ts +154 -71
- package/src/agents/chain-serializer.ts +10 -7
- package/src/agents/frontmatter.ts +5 -3
- package/src/agents/identity.ts +1 -1
- package/src/agents/proactive-skills.ts +13 -10
- package/src/agents/skills.ts +23 -6
- package/src/api/control-channel.ts +4 -0
- package/src/api/delegation.ts +26 -194
- package/src/api/external-runs.ts +129 -0
- package/src/api/intercom-bridge.ts +3 -0
- package/src/api/pi-args.ts +5 -0
- package/src/api/preflight.ts +3 -3
- package/src/api/shared-types.ts +19 -0
- package/src/extension/config.ts +10 -0
- package/src/extension/control-notices.ts +5 -39
- package/src/extension/doctor.ts +10 -9
- package/src/extension/fanout-child.ts +7 -4
- package/src/extension/index.ts +232 -68
- package/src/extension/rpc.ts +18 -12
- package/src/extension/schemas.ts +48 -37
- package/src/extension/tool-description.ts +36 -86
- package/src/inspectors/herdr/actions.ts +229 -0
- package/src/inspectors/herdr/client.ts +130 -0
- package/src/inspectors/herdr/inspector-runner.ts +141 -0
- package/src/inspectors/herdr/project-panes.ts +154 -0
- package/src/integrations/herdr-status.ts +330 -0
- package/src/intercom/intercom-bridge.ts +3 -2
- package/src/intercom/result-intercom.ts +5 -1
- package/src/missions/actions.ts +372 -0
- package/src/missions/lifecycle.ts +314 -0
- package/src/missions/store.ts +442 -0
- package/src/missions/types.ts +135 -0
- package/src/policy/authority.ts +46 -0
- package/src/profiles/profiles.ts +29 -3
- package/src/runs/background/async-execution.ts +98 -49
- package/src/runs/background/async-job-tracker.ts +10 -2
- package/src/runs/background/async-resume.ts +6 -6
- package/src/runs/background/async-status.ts +29 -1
- package/src/runs/background/auto-drain.ts +3 -3
- package/src/runs/background/chain-append.ts +3 -2
- package/src/runs/background/control-channel.ts +9 -7
- package/src/runs/background/fleet-view.ts +3 -4
- package/src/runs/background/notify.ts +2 -1
- package/src/runs/background/process-terminal.ts +5 -5
- package/src/runs/background/result-watcher.ts +13 -5
- package/src/runs/background/run-id-resolver.ts +3 -3
- package/src/runs/background/run-status.ts +35 -8
- package/src/runs/background/scheduled-runs.ts +602 -375
- package/src/runs/background/stale-run-reconciler.ts +3 -3
- package/src/runs/background/subagent-runner.ts +608 -445
- package/src/runs/background/subagent-wait.ts +50 -9
- package/src/runs/background/wait-subscriptions.ts +253 -0
- package/src/runs/background/wait-tool.ts +12 -4
- package/src/runs/foreground/async-steering-action.ts +3 -3
- package/src/runs/foreground/chain-clarify.ts +8 -4
- package/src/runs/foreground/chain-execution.ts +56 -30
- package/src/runs/foreground/execution.ts +15 -2
- package/src/runs/foreground/subagent-executor.ts +1023 -273
- package/src/runs/shared/acceptance.ts +28 -6
- package/src/runs/shared/child-protocol.ts +302 -22
- package/src/runs/shared/dynamic-fanout.ts +1 -1
- package/src/runs/shared/external-cli-runner.ts +130 -0
- package/src/runs/shared/long-running-guard.ts +42 -1
- package/src/runs/shared/nested-events.ts +59 -5
- package/src/runs/shared/nested-render.ts +9 -4
- package/src/runs/shared/parallel-handoff.ts +86 -2
- package/src/runs/shared/parallel-utils.ts +11 -2
- package/src/runs/shared/permissions.ts +95 -0
- package/src/runs/shared/pi-args.ts +11 -1
- package/src/runs/shared/pi-spawn.ts +11 -1
- package/src/runs/shared/run-history.ts +1 -1
- package/src/runs/shared/subagent-prompt-runtime.ts +36 -5
- package/src/runs/shared/subagent-startup-retry.ts +5 -2
- package/src/runs/shared/turn-budget.ts +6 -6
- package/src/runs/shared/worktree.ts +122 -12
- package/src/shared/accessible-dir.ts +29 -7
- package/src/shared/artifacts.ts +18 -1
- package/src/shared/fork-context.ts +3 -2
- package/src/shared/launch-contract.ts +1 -0
- package/src/shared/settings.ts +10 -0
- package/src/shared/types.ts +156 -14
- package/src/shared/utils.ts +8 -6
- package/src/slash/delegation-adapters.ts +32 -194
- package/src/slash/delegation-request.ts +43 -126
- package/src/slash/prompt-template-bridge.ts +158 -205
- package/src/slash/prompt-workflows.ts +21 -57
- package/src/slash/slash-bridge.ts +14 -0
- package/src/slash/slash-commands.ts +31 -632
- package/src/slash/subagents-admin.ts +18 -14
- package/src/tui/fleet-status.ts +156 -21
- package/src/tui/fleet-transcript.ts +110 -5
- package/src/tui/fleet.ts +56 -24
- package/src/tui/render.ts +291 -109
- package/src/types/pi-runtime-compat.d.ts +14 -0
- package/src/watchdog/lsp-diagnostics.ts +12 -7
- package/src/watchdog/model-selection.ts +2 -2
- package/src/watchdog/permission-arbiter.ts +145 -0
- package/src/watchdog/register-child.ts +1 -1
- package/src/watchdog/register-main.ts +1 -1
- package/src/watchdog/review.ts +4 -1
- package/src/watchdog/runtime.ts +3 -2
- package/src/workflows/chat-progress.ts +140 -0
- package/src/workflows/scripted-workflow.ts +415 -0
- package/agents/advisor.md +0 -73
- package/src/extension/chain-validation.ts +0 -181
package/src/agents/agents.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { parse as parseYaml } from "yaml";
|
|
|
8
8
|
import * as os from "node:os";
|
|
9
9
|
import * as path from "node:path";
|
|
10
10
|
import { fileURLToPath } from "node:url";
|
|
11
|
-
import type { AcceptanceInput, AcceptanceRole, OutputMode, ToolBudgetConfig, TurnBudgetConfig } from "../shared/types.ts";
|
|
11
|
+
import type { AcceptanceInput, AcceptanceRole, AgentRunnerConfig, OutputMode, ToolBudgetConfig, TurnBudgetConfig } from "../shared/types.ts";
|
|
12
12
|
import { getAgentDir, getProjectConfigDir } from "../shared/utils.ts";
|
|
13
13
|
import { KNOWN_FIELDS } from "./agent-serializer.ts";
|
|
14
14
|
import { parseChain, parseJsonChain } from "./chain-serializer.ts";
|
|
@@ -20,6 +20,7 @@ export { buildRuntimeName, frontmatterNameForConfig, parsePackageName } from "./
|
|
|
20
20
|
import { parseMemoryFrontmatter } from "./agent-memory.ts";
|
|
21
21
|
import { resolveTurnBudgetConfig } from "../runs/shared/turn-budget.ts";
|
|
22
22
|
import { validateAcceptanceInput } from "../runs/shared/acceptance.ts";
|
|
23
|
+
import { validatePermissionRules, type PermissionRules } from "../runs/shared/permissions.ts";
|
|
23
24
|
|
|
24
25
|
export type AgentScope = "user" | "project" | "both";
|
|
25
26
|
|
|
@@ -115,6 +116,7 @@ export interface AgentModelSourceInfo {
|
|
|
115
116
|
|
|
116
117
|
export interface AgentConfig {
|
|
117
118
|
name: string;
|
|
119
|
+
runner?: AgentRunnerConfig;
|
|
118
120
|
localName?: string;
|
|
119
121
|
packageName?: string;
|
|
120
122
|
description: string;
|
|
@@ -148,6 +150,7 @@ export interface AgentConfig {
|
|
|
148
150
|
maxSubagentDepth?: number;
|
|
149
151
|
completionGuard?: boolean;
|
|
150
152
|
toolBudget?: ToolBudgetConfig;
|
|
153
|
+
permissions?: PermissionRules;
|
|
151
154
|
memory?: AgentMemoryConfig;
|
|
152
155
|
disabled?: boolean;
|
|
153
156
|
extraFields?: Record<string, string>;
|
|
@@ -327,6 +330,21 @@ function getGlobalNpmRoot(): string | null {
|
|
|
327
330
|
const offline = process.env.PI_OFFLINE?.toLowerCase();
|
|
328
331
|
if (offline === "1" || offline === "true" || offline === "yes") return null;
|
|
329
332
|
if (cachedGlobalNpmRoot !== null) return cachedGlobalNpmRoot;
|
|
333
|
+
|
|
334
|
+
const windowsGlobalRoot = process.platform === "win32" && process.env.APPDATA
|
|
335
|
+
? path.join(process.env.APPDATA, "npm", "node_modules")
|
|
336
|
+
: undefined;
|
|
337
|
+
if (windowsGlobalRoot) {
|
|
338
|
+
try {
|
|
339
|
+
if (fs.statSync(windowsGlobalRoot).isDirectory()) {
|
|
340
|
+
cachedGlobalNpmRoot = fs.realpathSync(windowsGlobalRoot);
|
|
341
|
+
return cachedGlobalNpmRoot;
|
|
342
|
+
}
|
|
343
|
+
} catch {
|
|
344
|
+
// Fall through if the directory disappears while resolving it.
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
330
348
|
try {
|
|
331
349
|
cachedGlobalNpmRoot = fs.realpathSync(execSync("npm root -g", { encoding: "utf-8", timeout: 5000 }).trim());
|
|
332
350
|
return cachedGlobalNpmRoot;
|
|
@@ -486,7 +504,8 @@ function effectiveAgentMatch(matches: AgentConfig[]): { agent?: AgentConfig; err
|
|
|
486
504
|
const distinctNames = [...new Set(matches.map((agent) => agent.name))];
|
|
487
505
|
if (distinctNames.length === 1) {
|
|
488
506
|
const sourceRank = new Map<AgentConfig["source"], number>([["builtin", 0], ["package", 1], ["user", 2], ["project", 3]]);
|
|
489
|
-
|
|
507
|
+
const agent = [...matches].sort((a, b) => (sourceRank.get(b.source) ?? 0) - (sourceRank.get(a.source) ?? 0))[0];
|
|
508
|
+
return agent ? { agent } : {};
|
|
490
509
|
}
|
|
491
510
|
return {};
|
|
492
511
|
}
|
|
@@ -494,7 +513,7 @@ function effectiveAgentMatch(matches: AgentConfig[]): { agent?: AgentConfig; err
|
|
|
494
513
|
export function resolveAgentName(name: string, agents: AgentConfig[]): { agent?: AgentConfig; error?: string } {
|
|
495
514
|
const raw = name.trim();
|
|
496
515
|
const exact = agents.filter((agent) => agent.name === raw || agent.localName === raw);
|
|
497
|
-
if (exact.length === 1) return { agent: exact[0] };
|
|
516
|
+
if (exact.length === 1) return exact[0] ? { agent: exact[0] } : {};
|
|
498
517
|
if (exact.length > 1) {
|
|
499
518
|
const effective = effectiveAgentMatch(exact);
|
|
500
519
|
if (effective.agent) return effective;
|
|
@@ -502,7 +521,7 @@ export function resolveAgentName(name: string, agents: AgentConfig[]): { agent?:
|
|
|
502
521
|
}
|
|
503
522
|
|
|
504
523
|
const aliases = agents.filter((agent) => agent.aliases?.includes(raw));
|
|
505
|
-
if (aliases.length === 1) return { agent: aliases[0] };
|
|
524
|
+
if (aliases.length === 1) return aliases[0] ? { agent: aliases[0] } : {};
|
|
506
525
|
if (aliases.length > 1) {
|
|
507
526
|
const effective = effectiveAgentMatch(aliases);
|
|
508
527
|
if (effective.agent) return effective;
|
|
@@ -548,24 +567,24 @@ function arraysEqual(a: string[] | undefined, b: string[] | undefined): boolean
|
|
|
548
567
|
function cloneOverrideBase(agent: AgentConfig): BuiltinAgentOverrideBase {
|
|
549
568
|
return {
|
|
550
569
|
description: agent.description,
|
|
551
|
-
model: agent.model,
|
|
552
|
-
|
|
553
|
-
thinking: agent.thinking,
|
|
570
|
+
...(agent.model !== undefined ? { model: agent.model } : {}),
|
|
571
|
+
...(agent.fallbackModels ? { fallbackModels: [...agent.fallbackModels] } : {}),
|
|
572
|
+
...(agent.thinking !== undefined ? { thinking: agent.thinking } : {}),
|
|
554
573
|
systemPromptMode: agent.systemPromptMode,
|
|
555
574
|
inheritProjectContext: agent.inheritProjectContext,
|
|
556
575
|
inheritSkills: agent.inheritSkills,
|
|
557
|
-
defaultContext: agent.defaultContext,
|
|
558
|
-
acceptanceRole: agent.acceptanceRole,
|
|
559
|
-
disabled: agent.disabled,
|
|
576
|
+
...(agent.defaultContext !== undefined ? { defaultContext: agent.defaultContext } : {}),
|
|
577
|
+
...(agent.acceptanceRole !== undefined ? { acceptanceRole: agent.acceptanceRole } : {}),
|
|
578
|
+
...(agent.disabled !== undefined ? { disabled: agent.disabled } : {}),
|
|
560
579
|
systemPrompt: agent.systemPrompt,
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
completionGuard: agent.completionGuard,
|
|
568
|
-
toolBudget: agent.toolBudget,
|
|
580
|
+
...(agent.skills ? { skills: [...agent.skills] } : {}),
|
|
581
|
+
...(agent.skillPath ? { skillPath: [...agent.skillPath] } : {}),
|
|
582
|
+
...(agent.tools ? { tools: [...agent.tools] } : {}),
|
|
583
|
+
...(agent.mcpDirectTools ? { mcpDirectTools: [...agent.mcpDirectTools] } : {}),
|
|
584
|
+
...(!agent.extensionsFromDefault && agent.extensions ? { extensions: [...agent.extensions] } : {}),
|
|
585
|
+
...(agent.subagentOnlyExtensions ? { subagentOnlyExtensions: [...agent.subagentOnlyExtensions] } : {}),
|
|
586
|
+
...(agent.completionGuard !== undefined ? { completionGuard: agent.completionGuard } : {}),
|
|
587
|
+
...(agent.toolBudget !== undefined ? { toolBudget: agent.toolBudget } : {}),
|
|
569
588
|
};
|
|
570
589
|
}
|
|
571
590
|
|
|
@@ -882,14 +901,23 @@ function readSubagentSettings(filePath: string | null): SubagentSettings {
|
|
|
882
901
|
|
|
883
902
|
const parsed: Record<string, BuiltinAgentOverrideConfig> = {};
|
|
884
903
|
const agentOverrides = subagentsObject.agentOverrides;
|
|
904
|
+
const parsedSettings: SubagentSettings = {
|
|
905
|
+
overrides: parsed,
|
|
906
|
+
...(defaultModel !== undefined ? { defaultModel } : {}),
|
|
907
|
+
...(defaultThinking !== undefined ? { defaultThinking } : {}),
|
|
908
|
+
...(defaultExtensions !== undefined ? { defaultExtensions } : {}),
|
|
909
|
+
...(disableBuiltins !== undefined ? { disableBuiltins } : {}),
|
|
910
|
+
...(disableThinking !== undefined ? { disableThinking } : {}),
|
|
911
|
+
...(modelScope !== undefined ? { modelScope } : {}),
|
|
912
|
+
};
|
|
885
913
|
if (!agentOverrides || typeof agentOverrides !== "object" || Array.isArray(agentOverrides)) {
|
|
886
|
-
return
|
|
914
|
+
return parsedSettings;
|
|
887
915
|
}
|
|
888
916
|
for (const [name, value] of Object.entries(agentOverrides)) {
|
|
889
917
|
const override = parseBuiltinOverrideEntry(name, value, filePath);
|
|
890
918
|
if (override) parsed[name] = override;
|
|
891
919
|
}
|
|
892
|
-
return
|
|
920
|
+
return parsedSettings;
|
|
893
921
|
}
|
|
894
922
|
|
|
895
923
|
function resolveSubagentDefaultModel(
|
|
@@ -980,41 +1008,34 @@ function applyBuiltinOverride(
|
|
|
980
1008
|
};
|
|
981
1009
|
|
|
982
1010
|
if (override.description !== undefined) next.description = override.description;
|
|
983
|
-
if (override.model !== undefined)
|
|
984
|
-
if (override.fallbackModels !== undefined) {
|
|
985
|
-
|
|
986
|
-
}
|
|
987
|
-
if (override.thinking !== undefined) next.thinking = override.thinking === false ? undefined : override.thinking;
|
|
1011
|
+
if (override.model !== undefined) { if (override.model === false) delete next.model; else next.model = override.model; }
|
|
1012
|
+
if (override.fallbackModels !== undefined) { if (override.fallbackModels === false) delete next.fallbackModels; else next.fallbackModels = [...override.fallbackModels]; }
|
|
1013
|
+
if (override.thinking !== undefined) { if (override.thinking === false) delete next.thinking; else next.thinking = override.thinking; }
|
|
988
1014
|
if (override.systemPromptMode !== undefined) next.systemPromptMode = override.systemPromptMode;
|
|
989
1015
|
if (override.inheritProjectContext !== undefined) next.inheritProjectContext = override.inheritProjectContext;
|
|
990
1016
|
if (override.inheritSkills !== undefined) next.inheritSkills = override.inheritSkills;
|
|
991
|
-
if (override.defaultContext !== undefined)
|
|
992
|
-
if (override.acceptanceRole !== undefined)
|
|
1017
|
+
if (override.defaultContext !== undefined) { if (override.defaultContext === false) delete next.defaultContext; else next.defaultContext = override.defaultContext; }
|
|
1018
|
+
if (override.acceptanceRole !== undefined) { if (override.acceptanceRole === false) delete next.acceptanceRole; else next.acceptanceRole = override.acceptanceRole; }
|
|
993
1019
|
if (override.disabled !== undefined) next.disabled = override.disabled;
|
|
994
1020
|
if (override.systemPrompt !== undefined) next.systemPrompt = override.systemPrompt;
|
|
995
|
-
if (override.skills !== undefined)
|
|
1021
|
+
if (override.skills !== undefined) { if (override.skills === false) delete next.skills; else next.skills = [...override.skills]; }
|
|
996
1022
|
if (override.tools !== undefined) {
|
|
997
1023
|
const { tools, mcpDirectTools } = splitToolList(override.tools === false ? [] : override.tools);
|
|
998
|
-
next.tools = tools;
|
|
999
|
-
next.mcpDirectTools = mcpDirectTools;
|
|
1000
|
-
}
|
|
1001
|
-
if (override.extensions !== undefined) next.extensions = override.extensions === false ? undefined : [...override.extensions];
|
|
1002
|
-
if (override.subagentOnlyExtensions !== undefined) {
|
|
1003
|
-
next.subagentOnlyExtensions = override.subagentOnlyExtensions === false ? undefined : [...override.subagentOnlyExtensions];
|
|
1024
|
+
if (tools === undefined) delete next.tools; else next.tools = tools;
|
|
1025
|
+
if (mcpDirectTools === undefined) delete next.mcpDirectTools; else next.mcpDirectTools = mcpDirectTools;
|
|
1004
1026
|
}
|
|
1027
|
+
if (override.extensions !== undefined) { if (override.extensions === false) delete next.extensions; else next.extensions = [...override.extensions]; }
|
|
1028
|
+
if (override.subagentOnlyExtensions !== undefined) { if (override.subagentOnlyExtensions === false) delete next.subagentOnlyExtensions; else next.subagentOnlyExtensions = [...override.subagentOnlyExtensions]; }
|
|
1005
1029
|
if (override.completionGuard !== undefined) next.completionGuard = override.completionGuard;
|
|
1006
|
-
if (override.toolBudget !== undefined)
|
|
1030
|
+
if (override.toolBudget !== undefined) { if (override.toolBudget === false) delete next.toolBudget; else next.toolBudget = override.toolBudget; }
|
|
1007
1031
|
|
|
1008
1032
|
return next;
|
|
1009
1033
|
}
|
|
1010
1034
|
|
|
1011
1035
|
function clearBuiltinThinking(agent: AgentConfig, meta: { scope: "user" | "project"; path: string }): AgentConfig {
|
|
1012
1036
|
if (agent.thinking === undefined) return agent;
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
thinking: undefined,
|
|
1016
|
-
override: agent.override ?? { ...meta, base: cloneOverrideBase(agent) },
|
|
1017
|
-
};
|
|
1037
|
+
const { thinking: _thinking, ...next } = agent;
|
|
1038
|
+
return { ...next, override: agent.override ?? { ...meta, base: cloneOverrideBase(agent) } };
|
|
1018
1039
|
}
|
|
1019
1040
|
|
|
1020
1041
|
function applyBuiltinOverrides(
|
|
@@ -1096,7 +1117,8 @@ function applyCustomAgentOverride(
|
|
|
1096
1117
|
value: AgentConfig[K],
|
|
1097
1118
|
): void => {
|
|
1098
1119
|
if (agentHasFrontmatterField(agent, ...frontmatterFields)) return;
|
|
1099
|
-
mutable()
|
|
1120
|
+
const target = mutable();
|
|
1121
|
+
if (value === undefined) delete target[field]; else target[field] = value;
|
|
1100
1122
|
anyFilled = true;
|
|
1101
1123
|
};
|
|
1102
1124
|
|
|
@@ -1142,8 +1164,8 @@ function applyCustomAgentOverride(
|
|
|
1142
1164
|
if (override.tools !== undefined && !agentHasFrontmatterField(agent, "tools")) {
|
|
1143
1165
|
const { tools, mcpDirectTools } = splitToolList(override.tools === false ? [] : override.tools);
|
|
1144
1166
|
const target = mutable();
|
|
1145
|
-
target.tools = tools;
|
|
1146
|
-
target.mcpDirectTools = mcpDirectTools;
|
|
1167
|
+
if (tools === undefined) delete target.tools; else target.tools = tools;
|
|
1168
|
+
if (mcpDirectTools === undefined) delete target.mcpDirectTools; else target.mcpDirectTools = mcpDirectTools;
|
|
1147
1169
|
anyFilled = true;
|
|
1148
1170
|
}
|
|
1149
1171
|
if (override.extensions !== undefined) {
|
|
@@ -1397,6 +1419,55 @@ function isLegacyAgentSkillPath(rootDir: string, filePath: string): boolean {
|
|
|
1397
1419
|
return parts.some((part, index) => part === ".agents" && parts[index + 1] === "skills");
|
|
1398
1420
|
}
|
|
1399
1421
|
|
|
1422
|
+
function parseAgentRunnerFrontmatter(raw: string | undefined, agentName: string): AgentRunnerConfig | undefined {
|
|
1423
|
+
if (raw === undefined || !raw.trim()) return undefined;
|
|
1424
|
+
let parsed: unknown;
|
|
1425
|
+
try {
|
|
1426
|
+
parsed = parseYaml(raw);
|
|
1427
|
+
} catch (error) {
|
|
1428
|
+
throw new Error(`Agent '${agentName}' has invalid runner frontmatter: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
1429
|
+
}
|
|
1430
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1431
|
+
throw new Error(`Agent '${agentName}' has invalid runner frontmatter; expected an object.`);
|
|
1432
|
+
}
|
|
1433
|
+
const runner = parsed as Record<string, unknown>;
|
|
1434
|
+
if (runner.type === "pi") {
|
|
1435
|
+
if (Object.keys(runner).some((key) => key !== "type")) throw new Error(`Agent '${agentName}' has invalid Pi runner frontmatter; only 'type' is supported.`);
|
|
1436
|
+
return { type: "pi" };
|
|
1437
|
+
}
|
|
1438
|
+
if (runner.type !== "external-cli") {
|
|
1439
|
+
throw new Error(`Agent '${agentName}' has invalid runner.type; expected 'pi' or 'external-cli'.`);
|
|
1440
|
+
}
|
|
1441
|
+
if (typeof runner.command !== "string" || !runner.command.trim()) {
|
|
1442
|
+
throw new Error(`Agent '${agentName}' external-cli runner requires a non-empty command string.`);
|
|
1443
|
+
}
|
|
1444
|
+
if (runner.args !== undefined && (!Array.isArray(runner.args) || runner.args.some((arg) => typeof arg !== "string"))) {
|
|
1445
|
+
throw new Error(`Agent '${agentName}' external-cli runner args must be an array of strings.`);
|
|
1446
|
+
}
|
|
1447
|
+
if (runner.promptDelivery !== undefined && runner.promptDelivery !== "stdin") {
|
|
1448
|
+
throw new Error(`Agent '${agentName}' external-cli runner promptDelivery must be 'stdin'.`);
|
|
1449
|
+
}
|
|
1450
|
+
const supported = new Set(["type", "command", "args", "promptDelivery"]);
|
|
1451
|
+
const unknown = Object.keys(runner).filter((key) => !supported.has(key));
|
|
1452
|
+
if (unknown.length > 0) throw new Error(`Agent '${agentName}' external-cli runner has unsupported fields: ${unknown.join(", ")}.`);
|
|
1453
|
+
const runnerArgs = Array.isArray(runner.args) ? runner.args.filter((arg): arg is string => typeof arg === "string") : undefined;
|
|
1454
|
+
return {
|
|
1455
|
+
type: "external-cli",
|
|
1456
|
+
command: runner.command.trim(),
|
|
1457
|
+
...(runnerArgs?.length ? { args: runnerArgs } : {}),
|
|
1458
|
+
...(runner.promptDelivery ? { promptDelivery: "stdin" as const } : {}),
|
|
1459
|
+
};
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
function validateExternalRunnerProfile(frontmatter: Record<string, string>, agentName: string, runner: AgentRunnerConfig | undefined): void {
|
|
1463
|
+
if (runner?.type !== "external-cli") return;
|
|
1464
|
+
const unsupported = ["tools", "model", "fallbackModels", "thinking", "extensions", "subagentOnlyExtensions", "maxSubagentDepth", "completionGuard", "skills", "skill", "skillPath", "toolBudget", "permission", "permissions"]
|
|
1465
|
+
.filter((field) => frontmatter[field] !== undefined);
|
|
1466
|
+
if (unsupported.length > 0) {
|
|
1467
|
+
throw new Error(`Agent '${agentName}' uses runner.type='external-cli' and declares unsupported Pi-only fields: ${unsupported.join(", ")}.`);
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1400
1471
|
function parseAgentAcceptanceFrontmatter(raw: string | undefined, agentName: string): AcceptanceInput | undefined {
|
|
1401
1472
|
if (raw === undefined || !raw.trim()) return undefined;
|
|
1402
1473
|
let parsed: unknown;
|
|
@@ -1437,6 +1508,8 @@ function loadAgentsFromDir(dir: string, source: AgentSource): AgentConfig[] {
|
|
|
1437
1508
|
const packageName = parsedPackage.packageName;
|
|
1438
1509
|
const runtimeName = buildRuntimeName(localName, packageName);
|
|
1439
1510
|
|
|
1511
|
+
const runner = parseAgentRunnerFrontmatter(frontmatter.runner, localName);
|
|
1512
|
+
validateExternalRunnerProfile(frontmatter, localName, runner);
|
|
1440
1513
|
const rawTools = parseFrontmatterList(frontmatter.tools);
|
|
1441
1514
|
const parsedTools = splitToolList(rawTools);
|
|
1442
1515
|
const tools = parsedTools.tools ?? [];
|
|
@@ -1504,6 +1577,13 @@ function loadAgentsFromDir(dir: string, source: AgentSource): AgentConfig[] {
|
|
|
1504
1577
|
}
|
|
1505
1578
|
|
|
1506
1579
|
const parsedMaxSubagentDepth = Number(frontmatter.maxSubagentDepth);
|
|
1580
|
+
if (frontmatter.permission !== undefined && frontmatter.permissions !== undefined) {
|
|
1581
|
+
throw new Error(`Agent '${localName}' cannot declare both permission and permissions frontmatter.`);
|
|
1582
|
+
}
|
|
1583
|
+
const permissionSource = frontmatter.permissions ?? frontmatter.permission;
|
|
1584
|
+
const permissions = permissionSource?.trim()
|
|
1585
|
+
? validatePermissionRules(parseYaml(permissionSource), `Agent '${localName}' permissions`)
|
|
1586
|
+
: undefined;
|
|
1507
1587
|
let toolBudget: ToolBudgetConfig | undefined;
|
|
1508
1588
|
if (frontmatter.toolBudget !== undefined && frontmatter.toolBudget.trim()) {
|
|
1509
1589
|
const parsed = JSON.parse(frontmatter.toolBudget) as unknown;
|
|
@@ -1518,45 +1598,48 @@ function loadAgentsFromDir(dir: string, source: AgentSource): AgentConfig[] {
|
|
|
1518
1598
|
? true
|
|
1519
1599
|
: undefined;
|
|
1520
1600
|
|
|
1601
|
+
const maxSubagentDepth = Number.isInteger(parsedMaxSubagentDepth) && parsedMaxSubagentDepth >= 0
|
|
1602
|
+
? parsedMaxSubagentDepth
|
|
1603
|
+
: undefined;
|
|
1604
|
+
const memory = parseMemoryFrontmatter(frontmatter.memory);
|
|
1521
1605
|
const agent: AgentConfig = {
|
|
1522
1606
|
name: runtimeName,
|
|
1607
|
+
...(runner !== undefined ? { runner } : {}),
|
|
1523
1608
|
localName,
|
|
1524
|
-
packageName,
|
|
1609
|
+
...(packageName !== undefined ? { packageName } : {}),
|
|
1525
1610
|
description: frontmatter.description,
|
|
1526
|
-
aliases,
|
|
1527
|
-
|
|
1528
|
-
mcpDirectTools
|
|
1529
|
-
model: frontmatter.model,
|
|
1530
|
-
fallbackModels
|
|
1531
|
-
thinking: frontmatter.thinking === "false" ? false : frontmatter.thinking,
|
|
1611
|
+
...(aliases !== undefined ? { aliases } : {}),
|
|
1612
|
+
...(rawTools !== undefined ? { tools } : {}),
|
|
1613
|
+
...(mcpDirectTools.length > 0 ? { mcpDirectTools } : {}),
|
|
1614
|
+
...(frontmatter.model !== undefined ? { model: frontmatter.model } : {}),
|
|
1615
|
+
...(fallbackModels?.length ? { fallbackModels } : {}),
|
|
1616
|
+
...(frontmatter.thinking !== undefined ? { thinking: frontmatter.thinking === "false" ? false : frontmatter.thinking } : {}),
|
|
1532
1617
|
systemPromptMode,
|
|
1533
1618
|
inheritProjectContext,
|
|
1534
1619
|
inheritSkills,
|
|
1535
|
-
defaultContext,
|
|
1536
|
-
defaultAsync,
|
|
1537
|
-
defaultTimeoutMs,
|
|
1538
|
-
defaultTurnBudget,
|
|
1539
|
-
defaultAcceptance,
|
|
1540
|
-
acceptanceRole,
|
|
1620
|
+
...(defaultContext !== undefined ? { defaultContext } : {}),
|
|
1621
|
+
...(defaultAsync !== undefined ? { defaultAsync } : {}),
|
|
1622
|
+
...(defaultTimeoutMs !== undefined ? { defaultTimeoutMs } : {}),
|
|
1623
|
+
...(defaultTurnBudget !== undefined ? { defaultTurnBudget } : {}),
|
|
1624
|
+
...(defaultAcceptance !== undefined ? { defaultAcceptance } : {}),
|
|
1625
|
+
...(acceptanceRole !== undefined ? { acceptanceRole } : {}),
|
|
1541
1626
|
systemPrompt: body,
|
|
1542
1627
|
source,
|
|
1543
1628
|
filePath,
|
|
1544
|
-
skills
|
|
1545
|
-
skillPath
|
|
1546
|
-
extensions,
|
|
1547
|
-
subagentOnlyExtensions,
|
|
1548
|
-
output: frontmatter.output,
|
|
1549
|
-
defaultReads
|
|
1629
|
+
...(skills?.length ? { skills } : {}),
|
|
1630
|
+
...(skillPath?.length ? { skillPath } : {}),
|
|
1631
|
+
...(extensions !== undefined ? { extensions } : {}),
|
|
1632
|
+
...(subagentOnlyExtensions !== undefined ? { subagentOnlyExtensions } : {}),
|
|
1633
|
+
...(frontmatter.output !== undefined ? { output: frontmatter.output } : {}),
|
|
1634
|
+
...(defaultReads?.length ? { defaultReads } : {}),
|
|
1550
1635
|
defaultProgress: frontmatter.defaultProgress === "true",
|
|
1551
1636
|
interactive: frontmatter.interactive === "true",
|
|
1552
|
-
maxSubagentDepth:
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
memory: parseMemoryFrontmatter(frontmatter.memory),
|
|
1559
|
-
extraFields: Object.keys(extraFields).length > 0 ? extraFields : undefined,
|
|
1637
|
+
...(maxSubagentDepth !== undefined ? { maxSubagentDepth } : {}),
|
|
1638
|
+
...(completionGuard !== undefined ? { completionGuard } : {}),
|
|
1639
|
+
...(toolBudget !== undefined ? { toolBudget } : {}),
|
|
1640
|
+
...(permissions !== undefined ? { permissions } : {}),
|
|
1641
|
+
...(memory !== undefined ? { memory } : {}),
|
|
1642
|
+
...(Object.keys(extraFields).length > 0 ? { extraFields } : {}),
|
|
1560
1643
|
};
|
|
1561
1644
|
agentFrontmatterFields.set(agent, new Set(Object.keys(frontmatter)));
|
|
1562
1645
|
agents.push(agent);
|
|
@@ -1696,7 +1779,7 @@ export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryRe
|
|
|
1696
1779
|
const agents = mergeAgentsForScope(scope, userAgents, projectAgents, builtinAgents, packageAgents)
|
|
1697
1780
|
.filter((agent) => agent.disabled !== true);
|
|
1698
1781
|
|
|
1699
|
-
return { agents, projectAgentsDir, modelScope };
|
|
1782
|
+
return { agents, projectAgentsDir, ...(modelScope !== undefined ? { modelScope } : {}) };
|
|
1700
1783
|
}
|
|
1701
1784
|
|
|
1702
1785
|
export function discoverAgentsAll(cwd: string): {
|
|
@@ -17,8 +17,10 @@ function parseStepBody(agent: string, sectionBody: string): ChainStepConfig {
|
|
|
17
17
|
for (const line of configLines) {
|
|
18
18
|
const match = line.match(/^([\w-]+):\s*(.*)$/);
|
|
19
19
|
if (!match) continue;
|
|
20
|
-
const
|
|
21
|
-
|
|
20
|
+
const [keyValue, rawValueValue] = match.slice(1);
|
|
21
|
+
if (keyValue === undefined || rawValueValue === undefined) continue;
|
|
22
|
+
const key = keyValue.trim().toLowerCase();
|
|
23
|
+
const rawValue = rawValueValue.trim();
|
|
22
24
|
|
|
23
25
|
if (key === "output") {
|
|
24
26
|
if (rawValue === "false") step.output = false;
|
|
@@ -91,7 +93,8 @@ function parseStepBody(agent: string, sectionBody: string): ChainStepConfig {
|
|
|
91
93
|
}
|
|
92
94
|
const validation = validateToolBudgetConfig(parsed, `toolBudget for step '${agent}'`);
|
|
93
95
|
if (validation.error) throw new Error(validation.error);
|
|
94
|
-
|
|
96
|
+
const toolBudget = parsed as ChainStepConfig["toolBudget"];
|
|
97
|
+
if (toolBudget !== undefined) step.toolBudget = toolBudget;
|
|
95
98
|
}
|
|
96
99
|
}
|
|
97
100
|
|
|
@@ -130,12 +133,12 @@ export function parseChain(content: string, source: AgentSource, filePath: strin
|
|
|
130
133
|
return {
|
|
131
134
|
name: buildRuntimeName(localName, packageName),
|
|
132
135
|
localName,
|
|
133
|
-
packageName,
|
|
136
|
+
...(packageName !== undefined ? { packageName } : {}),
|
|
134
137
|
description: frontmatter.description,
|
|
135
138
|
source,
|
|
136
139
|
filePath,
|
|
137
140
|
steps,
|
|
138
|
-
|
|
141
|
+
...(Object.keys(extraFields).length > 0 ? { extraFields } : {}),
|
|
139
142
|
};
|
|
140
143
|
}
|
|
141
144
|
|
|
@@ -213,12 +216,12 @@ export function parseJsonChain(content: string, source: AgentSource, filePath: s
|
|
|
213
216
|
return {
|
|
214
217
|
name: buildRuntimeName(input.name.trim(), parsedPackage.packageName),
|
|
215
218
|
localName: input.name.trim(),
|
|
216
|
-
packageName: parsedPackage.packageName,
|
|
219
|
+
...(parsedPackage.packageName !== undefined ? { packageName: parsedPackage.packageName } : {}),
|
|
217
220
|
description: input.description.trim(),
|
|
218
221
|
source,
|
|
219
222
|
filePath,
|
|
220
223
|
steps: input.chain as ChainStepConfig[],
|
|
221
|
-
|
|
224
|
+
...(Object.keys(extraFields).length > 0 ? { extraFields } : {}),
|
|
222
225
|
};
|
|
223
226
|
}
|
|
224
227
|
|
|
@@ -113,20 +113,22 @@ export function parseFrontmatter(content: string): { frontmatter: Record<string,
|
|
|
113
113
|
|
|
114
114
|
const match = line.match(/^([\w-]+):\s*(.*)$/);
|
|
115
115
|
if (match) {
|
|
116
|
-
const
|
|
116
|
+
const [key, rawValueValue] = match.slice(1);
|
|
117
|
+
if (key === undefined || rawValueValue === undefined) continue;
|
|
118
|
+
const rawValue = rawValueValue.trim();
|
|
117
119
|
const isQuoted = (rawValue.startsWith('"') && rawValue.endsWith('"')) || (rawValue.startsWith("'") && rawValue.endsWith("'"));
|
|
118
120
|
const value = isQuoted ? rawValue.slice(1, -1) : rawValue;
|
|
119
121
|
const isFolded = !isQuoted && (rawValue === ">" || rawValue === ">-");
|
|
120
122
|
|
|
121
123
|
if (value === "" || isFolded) {
|
|
122
124
|
// Key with empty value or folded block indicator — defer storing until we see indent
|
|
123
|
-
currentKey =
|
|
125
|
+
currentKey = key;
|
|
124
126
|
currentBlockLines = [];
|
|
125
127
|
currentIndent = indent;
|
|
126
128
|
currentFolded = isFolded;
|
|
127
129
|
} else {
|
|
128
130
|
// Simple key: value
|
|
129
|
-
frontmatter[
|
|
131
|
+
frontmatter[key] = value;
|
|
130
132
|
}
|
|
131
133
|
}
|
|
132
134
|
// Lines that don't match a key pattern (e.g., comments, empty lines) are ignored
|
package/src/agents/identity.ts
CHANGED
|
@@ -9,7 +9,7 @@ function normalizePackageName(value: string | undefined): string | undefined {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export function parsePackageName(value: unknown, label = "package"): { packageName?: string; error?: string } {
|
|
12
|
-
if (value === undefined || value === false || value === "") return {
|
|
12
|
+
if (value === undefined || value === false || value === "") return {};
|
|
13
13
|
if (typeof value !== "string") return { error: `${label} must be a string or false when provided.` };
|
|
14
14
|
const packageName = normalizePackageName(value);
|
|
15
15
|
if (!packageName || !IDENTIFIER_PATTERN.test(packageName)) return { error: `${label} is invalid after sanitization.` };
|
|
@@ -141,14 +141,17 @@ export function recommendProactiveSkillSubagents(input: {
|
|
|
141
141
|
|
|
142
142
|
return [...counts.entries()]
|
|
143
143
|
.filter(([skill, sources]) => sources.size >= config.minReferences && (!availableByName || availableByName.has(skill)))
|
|
144
|
-
.map(([skill, sources]) =>
|
|
145
|
-
skill
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
144
|
+
.map(([skill, sources]) => {
|
|
145
|
+
const description = availableByName?.get(skill)?.description;
|
|
146
|
+
return {
|
|
147
|
+
skill,
|
|
148
|
+
agent,
|
|
149
|
+
references: sources.size,
|
|
150
|
+
sources: [...sources].sort((a, b) => a.localeCompare(b)),
|
|
151
|
+
...(description !== undefined ? { description } : {}),
|
|
152
|
+
reason: `referenced by ${sources.size} configured agents/chains`,
|
|
153
|
+
};
|
|
154
|
+
})
|
|
152
155
|
.sort((a, b) => b.references - a.references || a.skill.localeCompare(b.skill))
|
|
153
156
|
.slice(0, config.maxRecommendations);
|
|
154
157
|
}
|
|
@@ -184,8 +187,8 @@ export function buildProactiveSkillSubagentRecommendationLines(input: {
|
|
|
184
187
|
}
|
|
185
188
|
return formatProactiveSkillSubagentRecommendations(recommendProactiveSkillSubagents({
|
|
186
189
|
agents: input.agents,
|
|
187
|
-
chains: input.chains,
|
|
190
|
+
...(input.chains !== undefined ? { chains: input.chains } : {}),
|
|
188
191
|
availableSkills,
|
|
189
|
-
config: input.config,
|
|
192
|
+
...(input.config !== undefined ? { config: input.config } : {}),
|
|
190
193
|
}));
|
|
191
194
|
}
|
package/src/agents/skills.ts
CHANGED
|
@@ -125,8 +125,23 @@ function getGlobalNpmRoot(): string | null {
|
|
|
125
125
|
const offline = process.env.PI_OFFLINE?.toLowerCase();
|
|
126
126
|
if (offline === "1" || offline === "true" || offline === "yes") return null;
|
|
127
127
|
if (cachedGlobalNpmRoot !== null) return cachedGlobalNpmRoot;
|
|
128
|
+
|
|
129
|
+
const windowsGlobalRoot = process.platform === "win32" && process.env.APPDATA
|
|
130
|
+
? path.join(process.env.APPDATA, "npm", "node_modules")
|
|
131
|
+
: undefined;
|
|
132
|
+
if (windowsGlobalRoot) {
|
|
133
|
+
try {
|
|
134
|
+
if (fs.statSync(windowsGlobalRoot).isDirectory()) {
|
|
135
|
+
cachedGlobalNpmRoot = fs.realpathSync(windowsGlobalRoot);
|
|
136
|
+
return cachedGlobalNpmRoot;
|
|
137
|
+
}
|
|
138
|
+
} catch {
|
|
139
|
+
// Fall through if the directory disappears while resolving it.
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
128
143
|
try {
|
|
129
|
-
cachedGlobalNpmRoot = execSync("npm root -g", { encoding: "utf-8", timeout: 5000 }).trim();
|
|
144
|
+
cachedGlobalNpmRoot = fs.realpathSync(execSync("npm root -g", { encoding: "utf-8", timeout: 5000 }).trim());
|
|
130
145
|
return cachedGlobalNpmRoot;
|
|
131
146
|
} catch {
|
|
132
147
|
// Global npm root is optional in constrained environments.
|
|
@@ -410,15 +425,17 @@ function collectFilesystemSkills(cwd: string, agentDir: string, skillPaths: Skil
|
|
|
410
425
|
const resolvedFile = path.resolve(filePath);
|
|
411
426
|
if (!fs.existsSync(resolvedFile)) return;
|
|
412
427
|
const source = inferSkillSource(resolvedFile, cwd, agentDir, sourceHint);
|
|
428
|
+
const description = maybeReadSkillDescription(resolvedFile);
|
|
413
429
|
const existingIndex = seen.get(resolvedFile);
|
|
414
430
|
if (existingIndex !== undefined) {
|
|
415
431
|
const existing = entries[existingIndex];
|
|
416
432
|
if (existing && (SOURCE_PRIORITY[source] ?? 0) > (SOURCE_PRIORITY[existing.source] ?? 0)) {
|
|
433
|
+
const { description: _description, ...existingWithoutDescription } = existing;
|
|
417
434
|
entries[existingIndex] = {
|
|
418
|
-
...
|
|
435
|
+
...existingWithoutDescription,
|
|
419
436
|
name,
|
|
420
437
|
source,
|
|
421
|
-
description:
|
|
438
|
+
...(description !== undefined ? { description } : {}),
|
|
422
439
|
};
|
|
423
440
|
}
|
|
424
441
|
return;
|
|
@@ -428,7 +445,7 @@ function collectFilesystemSkills(cwd: string, agentDir: string, skillPaths: Skil
|
|
|
428
445
|
name,
|
|
429
446
|
filePath: resolvedFile,
|
|
430
447
|
source,
|
|
431
|
-
description:
|
|
448
|
+
...(description !== undefined ? { description } : {}),
|
|
432
449
|
order: order++,
|
|
433
450
|
});
|
|
434
451
|
};
|
|
@@ -592,7 +609,7 @@ function readSkill(
|
|
|
592
609
|
name: skillName,
|
|
593
610
|
path: skillPath,
|
|
594
611
|
content,
|
|
595
|
-
description,
|
|
612
|
+
...(description !== undefined ? { description } : {}),
|
|
596
613
|
source,
|
|
597
614
|
};
|
|
598
615
|
|
|
@@ -733,7 +750,7 @@ export function discoverAvailableSkills(cwd: string): Array<{
|
|
|
733
750
|
.map((s) => ({
|
|
734
751
|
name: s.name,
|
|
735
752
|
source: s.source,
|
|
736
|
-
description: s.description,
|
|
753
|
+
...(s.description !== undefined ? { description: s.description } : {}),
|
|
737
754
|
}))
|
|
738
755
|
.sort((a, b) => a.name.localeCompare(b.name));
|
|
739
756
|
}
|