pi-subagents 0.50.0 → 0.51.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 +62 -0
- package/agents/gpt-pro.md +17 -0
- package/async-retention-discovery-worker.mjs +180 -0
- package/docs/agents.md +35 -2
- package/docs/configuration.md +30 -12
- package/docs/extension-api.md +42 -1
- package/docs/observability.md +4 -4
- package/docs/tool-reference.md +38 -39
- package/docs/workflows.md +169 -3
- package/package.json +4 -2
- package/skills/pi-subagents/SKILL.md +5 -4
- package/skills/pi-subagents/references/constraints-and-recipes.md +6 -4
- package/skills/pi-subagents/references/execution-controls.md +18 -15
- package/skills/pi-subagents/references/management-authoring-rpc.md +3 -3
- package/skills/pi-subagents/references/prompting-and-roles.md +2 -2
- package/src/agents/agent-management.ts +100 -345
- package/src/agents/agents.ts +125 -25
- package/src/api/external-job-provider.ts +185 -0
- package/src/api/preflight.ts +31 -6
- package/src/api/shared-types.ts +2 -0
- package/src/extension/config.ts +3 -3
- package/src/extension/doctor.ts +3 -6
- package/src/extension/fanout-child.ts +2 -2
- package/src/extension/index.ts +165 -88
- package/src/extension/public-execution.ts +28 -1
- package/src/extension/schemas.ts +12 -35
- package/src/extension/tool-description.ts +35 -24
- package/src/inspectors/herdr/actions.ts +2 -2
- package/src/inspectors/herdr/inspector-runner.ts +2 -1
- package/src/inspectors/herdr/project-panes.ts +2 -2
- package/src/intercom/native-supervisor-channel.ts +30 -9
- package/src/missions/lifecycle.ts +6 -1
- package/src/missions/store.ts +4 -9
- package/src/profiles/profiles.ts +3 -1
- package/src/runs/background/active-run-index.ts +31 -8
- package/src/runs/background/async-execution.ts +49 -31
- package/src/runs/background/async-job-tracker.ts +20 -4
- package/src/runs/background/async-resume.ts +28 -13
- package/src/runs/background/async-retention.ts +888 -0
- package/src/runs/background/async-status.ts +39 -53
- package/src/runs/background/chain-append.ts +3 -33
- package/src/runs/background/control-channel.ts +14 -68
- package/src/runs/background/index-segment.ts +59 -0
- package/src/runs/background/notify.ts +3 -1
- package/src/runs/background/result-files.ts +158 -90
- package/src/runs/background/result-watcher.ts +72 -20
- package/src/runs/background/retained-children.ts +13 -3
- package/src/runs/background/run-id-query.ts +7 -0
- package/src/runs/background/run-id-resolver.ts +11 -9
- package/src/runs/background/run-status.ts +27 -18
- package/src/runs/background/scheduled-runs.ts +22 -4
- package/src/runs/background/stale-run-reconciler.ts +6 -3
- package/src/runs/background/steering.ts +11 -1
- package/src/runs/background/subagent-runner.ts +250 -138
- package/src/runs/background/subagent-wait.ts +7 -7
- package/src/runs/background/terminal-run-index.ts +129 -0
- package/src/runs/background/wait-completions.ts +21 -4
- package/src/runs/background/wait-subscriptions.ts +80 -1
- package/src/runs/foreground/async-steering-action.ts +21 -14
- package/src/runs/foreground/execution.ts +2 -0
- package/src/runs/foreground/subagent-executor.ts +460 -1536
- package/src/runs/foreground/workflow-foreground-steering.ts +6 -5
- package/src/runs/shared/chain-outputs.ts +1 -3
- package/src/runs/shared/external-job-bridge.ts +450 -0
- package/src/runs/shared/external-job-runner.ts +286 -0
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +14 -0
- package/src/runs/shared/model-fallback.ts +22 -5
- package/src/runs/shared/orca-progress-tabs.ts +84 -22
- package/src/runs/shared/parallel-handoff.ts +46 -4
- package/src/runs/shared/parallel-utils.ts +4 -15
- package/src/runs/shared/permissions.ts +5 -1
- package/src/runs/shared/pi-args.ts +8 -1
- package/src/runs/shared/subagent-control.ts +26 -4
- package/src/runs/shared/subagent-prompt-runtime.ts +12 -6
- package/src/runs/shared/workflow-graph.ts +1 -23
- package/src/runs/shared/worktree.ts +12 -1
- package/src/shared/atomic-json.ts +22 -2
- package/src/shared/capacity-resilient-json.ts +102 -0
- package/src/shared/completion-owner.ts +14 -0
- package/src/shared/file-system-retry.ts +49 -1
- package/src/shared/fork-context.ts +42 -0
- package/src/shared/prompt-resources.ts +0 -40
- package/src/shared/settings.ts +3 -27
- package/src/shared/types.ts +60 -26
- package/src/shared/utils.ts +8 -0
- package/src/shared/watch-strategy.ts +10 -0
- package/src/slash/slash-commands.ts +11 -3
- package/src/tui/fleet.ts +63 -15
- package/src/workflows/chat-progress.ts +1 -1
- package/src/workflows/scripted-workflow.ts +207 -65
- package/src/runs/foreground/chain-clarify.ts +0 -1354
- package/src/runs/foreground/chain-execution.ts +0 -1581
package/src/agents/agents.ts
CHANGED
|
@@ -38,6 +38,7 @@ export interface AgentMemoryConfig {
|
|
|
38
38
|
export const BUILTIN_AGENT_NAMES = [
|
|
39
39
|
"advisor",
|
|
40
40
|
"delegate",
|
|
41
|
+
"gpt-pro",
|
|
41
42
|
"oracle",
|
|
42
43
|
"researcher",
|
|
43
44
|
"reviewer",
|
|
@@ -137,6 +138,7 @@ export interface AgentConfig {
|
|
|
137
138
|
systemPrompt: string;
|
|
138
139
|
source: AgentSource;
|
|
139
140
|
filePath: string;
|
|
141
|
+
discoveryPriority?: number;
|
|
140
142
|
skills?: string[];
|
|
141
143
|
skillPath?: string[];
|
|
142
144
|
extensions?: string[];
|
|
@@ -212,8 +214,45 @@ export interface ChainDiscoveryDiagnostic {
|
|
|
212
214
|
error: string;
|
|
213
215
|
}
|
|
214
216
|
|
|
217
|
+
export interface AgentDiscoveryDiagnostic extends ChainDiscoveryDiagnostic {
|
|
218
|
+
name?: string;
|
|
219
|
+
runtimeName?: string;
|
|
220
|
+
packageSpecified?: boolean;
|
|
221
|
+
discoveryPriority?: number;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const AGENT_SOURCE_PRIORITY: Record<AgentSource, number> = {
|
|
225
|
+
builtin: 0,
|
|
226
|
+
package: 1,
|
|
227
|
+
user: 2,
|
|
228
|
+
project: 3,
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
function agentDefinitionPriority(definition: Pick<AgentConfig | AgentDiscoveryDiagnostic, "source" | "discoveryPriority">): number {
|
|
232
|
+
return AGENT_SOURCE_PRIORITY[definition.source] * 1_000_000
|
|
233
|
+
+ (definition.discoveryPriority ?? 0);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export function findBlockingAgentDiagnostic(name: string, agent: AgentConfig | readonly AgentConfig[] | undefined, diagnostics: AgentDiscoveryDiagnostic[] | undefined): AgentDiscoveryDiagnostic | undefined {
|
|
237
|
+
const normalizedName = name.trim();
|
|
238
|
+
const agents = Array.isArray(agent) ? agent : agent ? [agent] : [];
|
|
239
|
+
let match: AgentDiscoveryDiagnostic | undefined;
|
|
240
|
+
for (const diagnostic of diagnostics ?? []) {
|
|
241
|
+
if ((diagnostic.runtimeName === normalizedName
|
|
242
|
+
|| (diagnostic.name === normalizedName && (!diagnostic.packageSpecified
|
|
243
|
+
|| diagnostic.runtimeName === undefined
|
|
244
|
+
|| agents.some((agent) => agent.name === diagnostic.runtimeName && agent.localName === diagnostic.name))))
|
|
245
|
+
&& (!match || agentDefinitionPriority(diagnostic) > agentDefinitionPriority(match))) {
|
|
246
|
+
match = diagnostic;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
const highestPriority = Math.max(...agents.map(agentDefinitionPriority), -Infinity);
|
|
250
|
+
return !agents.length || (match && agentDefinitionPriority(match) > highestPriority) ? match : undefined;
|
|
251
|
+
}
|
|
252
|
+
|
|
215
253
|
interface AgentDiscoveryResult {
|
|
216
254
|
agents: AgentConfig[];
|
|
255
|
+
agentDiagnostics?: AgentDiscoveryDiagnostic[];
|
|
217
256
|
projectAgentsDir: string | null;
|
|
218
257
|
modelScope?: ModelScopeConfig;
|
|
219
258
|
}
|
|
@@ -1448,6 +1487,14 @@ function isLegacyAgentSkillPath(rootDir: string, filePath: string): boolean {
|
|
|
1448
1487
|
return parts.some((part, index) => part === ".agents" && parts[index + 1] === "skills");
|
|
1449
1488
|
}
|
|
1450
1489
|
|
|
1490
|
+
function isJsonSerializable(value: unknown): boolean {
|
|
1491
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return true;
|
|
1492
|
+
if (typeof value === "number") return Number.isFinite(value);
|
|
1493
|
+
if (Array.isArray(value)) return value.every(isJsonSerializable);
|
|
1494
|
+
if (value && typeof value === "object") return Object.values(value).every(isJsonSerializable);
|
|
1495
|
+
return false;
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1451
1498
|
function parseAgentRunnerFrontmatter(raw: string | undefined, agentName: string): AgentRunnerConfig | undefined {
|
|
1452
1499
|
if (raw === undefined || !raw.trim()) return undefined;
|
|
1453
1500
|
let parsed: unknown;
|
|
@@ -1464,8 +1511,24 @@ function parseAgentRunnerFrontmatter(raw: string | undefined, agentName: string)
|
|
|
1464
1511
|
if (Object.keys(runner).some((key) => key !== "type")) throw new Error(`Agent '${agentName}' has invalid Pi runner frontmatter; only 'type' is supported.`);
|
|
1465
1512
|
return { type: "pi" };
|
|
1466
1513
|
}
|
|
1514
|
+
if (runner.type === "external-job") {
|
|
1515
|
+
if (typeof runner.provider !== "string" || !runner.provider.trim() || runner.provider.trim() !== runner.provider) {
|
|
1516
|
+
throw new Error(`Agent '${agentName}' external-job runner requires a non-empty trimmed provider string.`);
|
|
1517
|
+
}
|
|
1518
|
+
if (runner.options !== undefined && (!runner.options || typeof runner.options !== "object" || Array.isArray(runner.options) || !isJsonSerializable(runner.options))) {
|
|
1519
|
+
throw new Error(`Agent '${agentName}' external-job runner options must be a JSON-serializable object.`);
|
|
1520
|
+
}
|
|
1521
|
+
const supported = new Set(["type", "provider", "options"]);
|
|
1522
|
+
const unknown = Object.keys(runner).filter((key) => !supported.has(key));
|
|
1523
|
+
if (unknown.length > 0) throw new Error(`Agent '${agentName}' external-job runner has unsupported fields: ${unknown.join(", ")}.`);
|
|
1524
|
+
return {
|
|
1525
|
+
type: "external-job",
|
|
1526
|
+
provider: runner.provider,
|
|
1527
|
+
...(runner.options ? { options: runner.options as Record<string, unknown> } : {}),
|
|
1528
|
+
};
|
|
1529
|
+
}
|
|
1467
1530
|
if (runner.type !== "external-cli") {
|
|
1468
|
-
throw new Error(`Agent '${agentName}' has invalid runner.type; expected 'pi' or 'external-
|
|
1531
|
+
throw new Error(`Agent '${agentName}' has invalid runner.type; expected 'pi', 'external-cli', or 'external-job'.`);
|
|
1469
1532
|
}
|
|
1470
1533
|
if (typeof runner.command !== "string" || !runner.command.trim()) {
|
|
1471
1534
|
throw new Error(`Agent '${agentName}' external-cli runner requires a non-empty command string.`);
|
|
@@ -1489,11 +1552,11 @@ function parseAgentRunnerFrontmatter(raw: string | undefined, agentName: string)
|
|
|
1489
1552
|
}
|
|
1490
1553
|
|
|
1491
1554
|
function validateExternalRunnerProfile(frontmatter: Record<string, string>, agentName: string, runner: AgentRunnerConfig | undefined): void {
|
|
1492
|
-
if (runner?.type !== "external-cli") return;
|
|
1555
|
+
if (runner?.type !== "external-cli" && runner?.type !== "external-job") return;
|
|
1493
1556
|
const unsupported = ["tools", "model", "fallbackModels", "thinking", "extensions", "subagentOnlyExtensions", "maxSubagentDepth", "completionGuard", "skills", "skill", "skillPath", "toolBudget", "permission", "permissions"]
|
|
1494
1557
|
.filter((field) => frontmatter[field] !== undefined);
|
|
1495
1558
|
if (unsupported.length > 0) {
|
|
1496
|
-
throw new Error(`Agent '${agentName}' uses runner.type='
|
|
1559
|
+
throw new Error(`Agent '${agentName}' uses runner.type='${runner.type}' and declares unsupported Pi-only fields: ${unsupported.join(", ")}.`);
|
|
1497
1560
|
}
|
|
1498
1561
|
}
|
|
1499
1562
|
|
|
@@ -1510,8 +1573,9 @@ function parseAgentAcceptanceFrontmatter(raw: string | undefined, agentName: str
|
|
|
1510
1573
|
return parsed as AcceptanceInput;
|
|
1511
1574
|
}
|
|
1512
1575
|
|
|
1513
|
-
function loadAgentsFromDir(dir: string, source: AgentSource): AgentConfig[] {
|
|
1576
|
+
function loadAgentsFromDir(dir: string, source: AgentSource, discoveryPriority?: number): { agents: AgentConfig[]; diagnostics: AgentDiscoveryDiagnostic[] } {
|
|
1514
1577
|
const agents: AgentConfig[] = [];
|
|
1578
|
+
const diagnostics: AgentDiscoveryDiagnostic[] = [];
|
|
1515
1579
|
|
|
1516
1580
|
for (const filePath of listFilesRecursive(dir, (fileName) => fileName.endsWith(".md") && !fileName.endsWith(".chain.md"))) {
|
|
1517
1581
|
if (isLegacyAgentSkillPath(dir, filePath)) {
|
|
@@ -1525,6 +1589,10 @@ function loadAgentsFromDir(dir: string, source: AgentSource): AgentConfig[] {
|
|
|
1525
1589
|
continue;
|
|
1526
1590
|
}
|
|
1527
1591
|
|
|
1592
|
+
let name: string | undefined;
|
|
1593
|
+
let runtimeName: string | undefined;
|
|
1594
|
+
let packageSpecified = false;
|
|
1595
|
+
try {
|
|
1528
1596
|
const { frontmatter, body } = parseFrontmatter(content);
|
|
1529
1597
|
|
|
1530
1598
|
if (!frontmatter.name || !frontmatter.description) {
|
|
@@ -1532,10 +1600,12 @@ function loadAgentsFromDir(dir: string, source: AgentSource): AgentConfig[] {
|
|
|
1532
1600
|
}
|
|
1533
1601
|
|
|
1534
1602
|
const localName = frontmatter.name;
|
|
1603
|
+
name = localName;
|
|
1535
1604
|
const parsedPackage = parsePackageName(frontmatter.package, `Agent '${localName}' package`);
|
|
1536
|
-
|
|
1605
|
+
packageSpecified = parsedPackage.packageName !== undefined || parsedPackage.error !== undefined;
|
|
1606
|
+
if (parsedPackage.error) throw new Error(parsedPackage.error);
|
|
1537
1607
|
const packageName = parsedPackage.packageName;
|
|
1538
|
-
|
|
1608
|
+
runtimeName = buildRuntimeName(localName, packageName);
|
|
1539
1609
|
|
|
1540
1610
|
const runner = parseAgentRunnerFrontmatter(frontmatter.runner, localName);
|
|
1541
1611
|
validateExternalRunnerProfile(frontmatter, localName, runner);
|
|
@@ -1664,6 +1734,7 @@ function loadAgentsFromDir(dir: string, source: AgentSource): AgentConfig[] {
|
|
|
1664
1734
|
systemPrompt: body,
|
|
1665
1735
|
source,
|
|
1666
1736
|
filePath,
|
|
1737
|
+
...(discoveryPriority !== undefined ? { discoveryPriority } : {}),
|
|
1667
1738
|
...(skills?.length ? { skills } : {}),
|
|
1668
1739
|
...(skillPath?.length ? { skillPath } : {}),
|
|
1669
1740
|
...(extensions !== undefined ? { extensions } : {}),
|
|
@@ -1681,9 +1752,12 @@ function loadAgentsFromDir(dir: string, source: AgentSource): AgentConfig[] {
|
|
|
1681
1752
|
};
|
|
1682
1753
|
agentFrontmatterFields.set(agent, new Set(Object.keys(frontmatter)));
|
|
1683
1754
|
agents.push(agent);
|
|
1755
|
+
} catch (error) {
|
|
1756
|
+
diagnostics.push({ source, filePath, ...(name ? { name } : {}), ...(runtimeName && runtimeName !== name ? { runtimeName } : {}), ...(packageSpecified ? { packageSpecified: true } : {}), ...(discoveryPriority !== undefined ? { discoveryPriority } : {}), error: error instanceof Error ? error.message : String(error) });
|
|
1757
|
+
}
|
|
1684
1758
|
}
|
|
1685
1759
|
|
|
1686
|
-
return agents;
|
|
1760
|
+
return { agents, diagnostics };
|
|
1687
1761
|
}
|
|
1688
1762
|
|
|
1689
1763
|
function loadChainsFromDir(dir: string, source: AgentSource): { chains: ChainConfig[]; diagnostics: ChainDiscoveryDiagnostic[] } {
|
|
@@ -1781,34 +1855,42 @@ export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryRe
|
|
|
1781
1855
|
includeProject: scope !== "user",
|
|
1782
1856
|
});
|
|
1783
1857
|
|
|
1858
|
+
const builtinLoaded = loadAgentsFromDir(BUILTIN_AGENTS_DIR, "builtin");
|
|
1784
1859
|
const builtinAgents = applyBuiltinOverrides(
|
|
1785
|
-
applySubagentDefaults(
|
|
1860
|
+
applySubagentDefaults(builtinLoaded.agents, defaultModel, defaultThinking, defaultExtensions),
|
|
1786
1861
|
userSettings,
|
|
1787
1862
|
projectSettings,
|
|
1788
1863
|
userSettingsPath,
|
|
1789
1864
|
projectSettingsPath,
|
|
1790
1865
|
);
|
|
1791
1866
|
|
|
1792
|
-
const
|
|
1793
|
-
|
|
1794
|
-
const userAgentsNew = scope === "project" ? [] : loadAgentsFromDir(userDirNew, "user");
|
|
1867
|
+
const userLoaded = scope === "project" ? [] : [...extraUserAgentDirs(), userDirOld, userDirNew]
|
|
1868
|
+
.map((dir, discoveryPriority) => loadAgentsFromDir(dir, "user", discoveryPriority));
|
|
1795
1869
|
const userAgents = applyCustomAgentOverrides(
|
|
1796
|
-
applySubagentDefaults(
|
|
1870
|
+
applySubagentDefaults(userLoaded.flatMap((loaded) => loaded.agents), defaultModel, defaultThinking, defaultExtensions),
|
|
1797
1871
|
userSettings,
|
|
1798
1872
|
projectSettings,
|
|
1799
1873
|
userSettingsPath,
|
|
1800
1874
|
projectSettingsPath,
|
|
1801
1875
|
);
|
|
1802
1876
|
|
|
1877
|
+
const projectLoaded = scope === "user" ? [] : projectAgentDirs.map((dir) => loadAgentsFromDir(dir, "project", dir === projectAgentsDir ? 1 : 0));
|
|
1803
1878
|
const projectAgents = applyCustomAgentOverrides(
|
|
1804
|
-
applySubagentDefaults(
|
|
1879
|
+
applySubagentDefaults(projectLoaded.flatMap((loaded) => loaded.agents), defaultModel, defaultThinking, defaultExtensions),
|
|
1805
1880
|
userSettings,
|
|
1806
1881
|
projectSettings,
|
|
1807
1882
|
userSettingsPath,
|
|
1808
1883
|
projectSettingsPath,
|
|
1809
1884
|
);
|
|
1885
|
+
const packageLoaded = packageSubagentPaths.agents.map((dir, index) => loadAgentsFromDir(dir, "package", packageSubagentPaths.agents.length - index));
|
|
1886
|
+
const packageMap = new Map<string, AgentConfig>();
|
|
1887
|
+
for (const loaded of packageLoaded) {
|
|
1888
|
+
for (const agent of loaded.agents) {
|
|
1889
|
+
if (!packageMap.has(agent.name)) packageMap.set(agent.name, agent);
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1810
1892
|
const packageAgents = applyCustomAgentOverrides(
|
|
1811
|
-
applySubagentDefaults(
|
|
1893
|
+
applySubagentDefaults(Array.from(packageMap.values()), defaultModel, defaultThinking, defaultExtensions),
|
|
1812
1894
|
userSettings,
|
|
1813
1895
|
projectSettings,
|
|
1814
1896
|
userSettingsPath,
|
|
@@ -1817,7 +1899,13 @@ export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryRe
|
|
|
1817
1899
|
const agents = mergeAgentsForScope(scope, userAgents, projectAgents, builtinAgents, packageAgents)
|
|
1818
1900
|
.filter((agent) => agent.disabled !== true);
|
|
1819
1901
|
|
|
1820
|
-
|
|
1902
|
+
const agentDiagnostics = [
|
|
1903
|
+
...builtinLoaded.diagnostics,
|
|
1904
|
+
...userLoaded.flatMap((loaded) => loaded.diagnostics),
|
|
1905
|
+
...projectLoaded.flatMap((loaded) => loaded.diagnostics),
|
|
1906
|
+
...packageLoaded.flatMap((loaded) => loaded.diagnostics),
|
|
1907
|
+
];
|
|
1908
|
+
return { agents, agentDiagnostics, projectAgentsDir, ...(modelScope !== undefined ? { modelScope } : {}) };
|
|
1821
1909
|
}
|
|
1822
1910
|
|
|
1823
1911
|
export function discoverAgentsAll(cwd: string): {
|
|
@@ -1825,6 +1913,7 @@ export function discoverAgentsAll(cwd: string): {
|
|
|
1825
1913
|
package: AgentConfig[];
|
|
1826
1914
|
user: AgentConfig[];
|
|
1827
1915
|
project: AgentConfig[];
|
|
1916
|
+
agentDiagnostics?: AgentDiscoveryDiagnostic[];
|
|
1828
1917
|
chains: ChainConfig[];
|
|
1829
1918
|
chainDiagnostics: ChainDiscoveryDiagnostic[];
|
|
1830
1919
|
userDir: string;
|
|
@@ -1848,27 +1937,29 @@ export function discoverAgentsAll(cwd: string): {
|
|
|
1848
1937
|
const defaultExtensions = resolveSubagentDefaultExtensions(userSettings, projectSettings, projectSettingsPath);
|
|
1849
1938
|
const packageSubagentPaths = collectPackageSubagentPaths(cwd);
|
|
1850
1939
|
|
|
1940
|
+
const builtinLoaded = loadAgentsFromDir(BUILTIN_AGENTS_DIR, "builtin");
|
|
1851
1941
|
const builtin = applyBuiltinOverrides(
|
|
1852
|
-
applySubagentDefaults(
|
|
1942
|
+
applySubagentDefaults(builtinLoaded.agents, defaultModel, defaultThinking, defaultExtensions),
|
|
1853
1943
|
userSettings,
|
|
1854
1944
|
projectSettings,
|
|
1855
1945
|
userSettingsPath,
|
|
1856
1946
|
projectSettingsPath,
|
|
1857
1947
|
);
|
|
1948
|
+
const userLoaded = [...extraUserAgentDirs(), userDirOld, userDirNew]
|
|
1949
|
+
.map((dir, discoveryPriority) => loadAgentsFromDir(dir, "user", discoveryPriority));
|
|
1858
1950
|
const user = applyCustomAgentOverrides(
|
|
1859
|
-
applySubagentDefaults(
|
|
1860
|
-
...extraUserAgentDirs().flatMap((dir) => loadAgentsFromDir(dir, "user")),
|
|
1861
|
-
...loadAgentsFromDir(userDirOld, "user"),
|
|
1862
|
-
...loadAgentsFromDir(userDirNew, "user"),
|
|
1863
|
-
], defaultModel, defaultThinking, defaultExtensions),
|
|
1951
|
+
applySubagentDefaults(userLoaded.flatMap((loaded) => loaded.agents), defaultModel, defaultThinking, defaultExtensions),
|
|
1864
1952
|
userSettings,
|
|
1865
1953
|
projectSettings,
|
|
1866
1954
|
userSettingsPath,
|
|
1867
1955
|
projectSettingsPath,
|
|
1868
1956
|
);
|
|
1869
1957
|
const packageMap = new Map<string, AgentConfig>();
|
|
1870
|
-
|
|
1871
|
-
|
|
1958
|
+
const packageAgentDiagnostics: AgentDiscoveryDiagnostic[] = [];
|
|
1959
|
+
for (const [index, dir] of packageSubagentPaths.agents.entries()) {
|
|
1960
|
+
const loaded = loadAgentsFromDir(dir, "package", packageSubagentPaths.agents.length - index);
|
|
1961
|
+
packageAgentDiagnostics.push(...loaded.diagnostics);
|
|
1962
|
+
for (const agent of loaded.agents) {
|
|
1872
1963
|
if (!packageMap.has(agent.name)) packageMap.set(agent.name, agent);
|
|
1873
1964
|
}
|
|
1874
1965
|
}
|
|
@@ -1880,8 +1971,11 @@ export function discoverAgentsAll(cwd: string): {
|
|
|
1880
1971
|
projectSettingsPath,
|
|
1881
1972
|
);
|
|
1882
1973
|
const projectMap = new Map<string, AgentConfig>();
|
|
1974
|
+
const projectAgentDiagnostics: AgentDiscoveryDiagnostic[] = [];
|
|
1883
1975
|
for (const dir of projectDirs) {
|
|
1884
|
-
|
|
1976
|
+
const loaded = loadAgentsFromDir(dir, "project", dir === projectDir ? 1 : 0);
|
|
1977
|
+
projectAgentDiagnostics.push(...loaded.diagnostics);
|
|
1978
|
+
for (const agent of loaded.agents) {
|
|
1885
1979
|
projectMap.set(agent.name, agent);
|
|
1886
1980
|
}
|
|
1887
1981
|
}
|
|
@@ -1922,8 +2016,14 @@ export function discoverAgentsAll(cwd: string): {
|
|
|
1922
2016
|
...userChains.diagnostics,
|
|
1923
2017
|
...projectChainDiagnostics,
|
|
1924
2018
|
];
|
|
2019
|
+
const agentDiagnostics = [
|
|
2020
|
+
...builtinLoaded.diagnostics,
|
|
2021
|
+
...userLoaded.flatMap((loaded) => loaded.diagnostics),
|
|
2022
|
+
...packageAgentDiagnostics,
|
|
2023
|
+
...projectAgentDiagnostics,
|
|
2024
|
+
];
|
|
1925
2025
|
|
|
1926
2026
|
const userDir = process.env.PI_CODING_AGENT_DIR ? userDirOld : fs.existsSync(userDirNew) ? userDirNew : userDirOld;
|
|
1927
2027
|
|
|
1928
|
-
return { builtin, package: packageAgents, user, project, chains, chainDiagnostics, userDir, projectDir, userChainDir, projectChainDir, userSettingsPath, projectSettingsPath };
|
|
2028
|
+
return { builtin, package: packageAgents, user, project, agentDiagnostics, chains, chainDiagnostics, userDir, projectDir, userChainDir, projectChainDir, userSettingsPath, projectSettingsPath };
|
|
1929
2029
|
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
export const EXTERNAL_JOB_PROVIDER_PROTOCOL_VERSION = 1;
|
|
2
|
+
export const EXTERNAL_JOB_PROVIDER_REGISTRY_KEY = "pi-subagents.external-job-providers.v1";
|
|
3
|
+
|
|
4
|
+
const MAX_PROVIDER_NAME_LENGTH = 128;
|
|
5
|
+
const MAX_PROVIDERS = 100;
|
|
6
|
+
const MAX_JOB_ID_LENGTH = 256;
|
|
7
|
+
const MAX_FAILURE_CODE_LENGTH = 128;
|
|
8
|
+
const MAX_FAILURE_MESSAGE_LENGTH = 4_096;
|
|
9
|
+
const MAX_URL_LENGTH = 4_096;
|
|
10
|
+
|
|
11
|
+
export type ExternalJobState = "queued" | "running" | "completed" | "failed" | "stopped" | "blocked";
|
|
12
|
+
export type ExternalJobOperation = "start" | "status" | "result" | "reattach";
|
|
13
|
+
|
|
14
|
+
export type ExternalJobOptions = Record<string, unknown>;
|
|
15
|
+
|
|
16
|
+
export interface ExternalJobStartInput {
|
|
17
|
+
prompt: string;
|
|
18
|
+
promptDigest: string;
|
|
19
|
+
cwd: string;
|
|
20
|
+
runId: string;
|
|
21
|
+
stepIndex: number;
|
|
22
|
+
agent: string;
|
|
23
|
+
options: ExternalJobOptions;
|
|
24
|
+
sessionId?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ExternalJobHandle {
|
|
28
|
+
providerJobId: string;
|
|
29
|
+
state: ExternalJobState;
|
|
30
|
+
handleUrl?: string;
|
|
31
|
+
conversationUrl?: string;
|
|
32
|
+
failureCode?: string;
|
|
33
|
+
failureMessage?: string;
|
|
34
|
+
blockingJobId?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface ExternalJobResult extends ExternalJobHandle {
|
|
38
|
+
output?: string;
|
|
39
|
+
artifactPath?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface ExternalJobProvider {
|
|
43
|
+
name: string;
|
|
44
|
+
start(input: ExternalJobStartInput): Promise<ExternalJobHandle> | ExternalJobHandle;
|
|
45
|
+
status(providerJobId: string): Promise<ExternalJobHandle> | ExternalJobHandle;
|
|
46
|
+
result(providerJobId: string): Promise<ExternalJobResult> | ExternalJobResult;
|
|
47
|
+
reattach(providerJobId: string): Promise<ExternalJobHandle> | ExternalJobHandle;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export class ExternalJobProviderError extends Error {
|
|
51
|
+
readonly code: string;
|
|
52
|
+
readonly blockingJobId?: string;
|
|
53
|
+
|
|
54
|
+
constructor(message: string, options: { code: string; blockingJobId?: string; cause?: unknown }) {
|
|
55
|
+
super(message, options.cause === undefined ? undefined : { cause: options.cause });
|
|
56
|
+
this.name = "ExternalJobProviderError";
|
|
57
|
+
this.code = options.code;
|
|
58
|
+
this.blockingJobId = options.blockingJobId;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
interface ExternalJobProviderRegistry {
|
|
63
|
+
version: typeof EXTERNAL_JOB_PROVIDER_PROTOCOL_VERSION;
|
|
64
|
+
providers: Map<string, ExternalJobProvider>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function registry(): ExternalJobProviderRegistry {
|
|
68
|
+
const key = Symbol.for(EXTERNAL_JOB_PROVIDER_REGISTRY_KEY);
|
|
69
|
+
const globalObject = globalThis as Record<PropertyKey, unknown>;
|
|
70
|
+
const existing = globalObject[key];
|
|
71
|
+
if (existing === undefined) {
|
|
72
|
+
const created: ExternalJobProviderRegistry = {
|
|
73
|
+
version: EXTERNAL_JOB_PROVIDER_PROTOCOL_VERSION,
|
|
74
|
+
providers: new Map(),
|
|
75
|
+
};
|
|
76
|
+
globalObject[key] = created;
|
|
77
|
+
return created;
|
|
78
|
+
}
|
|
79
|
+
if (!existing || typeof existing !== "object" || Array.isArray(existing)) {
|
|
80
|
+
throw new Error(`Malformed external-job provider registry at Symbol.for("${EXTERNAL_JOB_PROVIDER_REGISTRY_KEY}").`);
|
|
81
|
+
}
|
|
82
|
+
const candidate = existing as Partial<ExternalJobProviderRegistry>;
|
|
83
|
+
if (candidate.version !== EXTERNAL_JOB_PROVIDER_PROTOCOL_VERSION || !(candidate.providers instanceof Map)) {
|
|
84
|
+
throw new Error(`Unsupported external-job provider registry at Symbol.for("${EXTERNAL_JOB_PROVIDER_REGISTRY_KEY}").`);
|
|
85
|
+
}
|
|
86
|
+
return candidate as ExternalJobProviderRegistry;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function validateString(value: unknown, field: string, maxLength: number): string {
|
|
90
|
+
if (typeof value !== "string" || value.length === 0 || value.trim() !== value) {
|
|
91
|
+
throw new Error(`${field} must be a non-empty string without leading or trailing whitespace.`);
|
|
92
|
+
}
|
|
93
|
+
if (value.length > maxLength) throw new Error(`${field} must be at most ${maxLength} characters.`);
|
|
94
|
+
if (value.includes("\0")) throw new Error(`${field} must not contain NUL characters.`);
|
|
95
|
+
return value;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function validateOptionalString(value: unknown, field: string, maxLength: number): string | undefined {
|
|
99
|
+
if (value === undefined) return undefined;
|
|
100
|
+
return validateString(value, field, maxLength);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function validateState(value: unknown, field: string): ExternalJobState {
|
|
104
|
+
if (value === "queued" || value === "running" || value === "completed" || value === "failed" || value === "stopped" || value === "blocked") return value;
|
|
105
|
+
throw new Error(`${field} must be queued, running, completed, failed, stopped, or blocked.`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function validateHandle(provider: string, value: unknown, field: string, extraFields: readonly string[] = []): ExternalJobHandle {
|
|
109
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${field} from external-job provider '${provider}' must be an object.`);
|
|
110
|
+
const handle = value as Record<string, unknown>;
|
|
111
|
+
const supported = new Set(["providerJobId", "state", "handleUrl", "conversationUrl", "failureCode", "failureMessage", "blockingJobId", ...extraFields]);
|
|
112
|
+
const unknown = Object.keys(handle).filter((key) => !supported.has(key));
|
|
113
|
+
if (unknown.length > 0) throw new Error(`${field} from external-job provider '${provider}' has unknown fields: ${unknown.join(", ")}.`);
|
|
114
|
+
const handleUrl = validateOptionalString(handle.handleUrl, `${field}.handleUrl`, MAX_URL_LENGTH);
|
|
115
|
+
const conversationUrl = validateOptionalString(handle.conversationUrl, `${field}.conversationUrl`, MAX_URL_LENGTH);
|
|
116
|
+
const failureCode = validateOptionalString(handle.failureCode, `${field}.failureCode`, MAX_FAILURE_CODE_LENGTH);
|
|
117
|
+
const failureMessage = validateOptionalString(handle.failureMessage, `${field}.failureMessage`, MAX_FAILURE_MESSAGE_LENGTH);
|
|
118
|
+
const blockingJobId = validateOptionalString(handle.blockingJobId, `${field}.blockingJobId`, MAX_JOB_ID_LENGTH);
|
|
119
|
+
return {
|
|
120
|
+
providerJobId: validateString(handle.providerJobId, `${field}.providerJobId`, MAX_JOB_ID_LENGTH),
|
|
121
|
+
state: validateState(handle.state, `${field}.state`),
|
|
122
|
+
...(handleUrl ? { handleUrl } : {}),
|
|
123
|
+
...(conversationUrl ? { conversationUrl } : {}),
|
|
124
|
+
...(failureCode ? { failureCode } : {}),
|
|
125
|
+
...(failureMessage ? { failureMessage } : {}),
|
|
126
|
+
...(blockingJobId ? { blockingJobId } : {}),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function validateExternalJobHandle(provider: string, value: unknown, field = "External-job handle"): ExternalJobHandle {
|
|
131
|
+
return validateHandle(provider, value, field);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function validateExternalJobResult(provider: string, value: unknown, field = "External-job result"): ExternalJobResult {
|
|
135
|
+
const result = validateHandle(provider, value, field, ["output", "artifactPath"]) as ExternalJobResult;
|
|
136
|
+
const record = value as Record<string, unknown>;
|
|
137
|
+
const output = validateOptionalString(record.output, `${field}.output`, 1024 * 1024);
|
|
138
|
+
const artifactPath = validateOptionalString(record.artifactPath, `${field}.artifactPath`, MAX_URL_LENGTH);
|
|
139
|
+
return {
|
|
140
|
+
...result,
|
|
141
|
+
...(output !== undefined ? { output } : {}),
|
|
142
|
+
...(artifactPath !== undefined ? { artifactPath } : {}),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function validateProvider(value: unknown): ExternalJobProvider {
|
|
147
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("External-job provider must be an object.");
|
|
148
|
+
const provider = value as Record<string, unknown>;
|
|
149
|
+
const unknownFields = Object.keys(provider).filter((key) => !["name", "start", "status", "result", "reattach"].includes(key));
|
|
150
|
+
if (unknownFields.length > 0) throw new Error(`External-job provider has unknown fields: ${unknownFields.join(", ")}.`);
|
|
151
|
+
const name = validateString(provider.name, "External-job provider name", MAX_PROVIDER_NAME_LENGTH);
|
|
152
|
+
for (const op of ["start", "status", "result", "reattach"] as const) {
|
|
153
|
+
if (typeof provider[op] !== "function") throw new Error(`External-job provider '${name}' must expose ${op}().`);
|
|
154
|
+
}
|
|
155
|
+
return value as ExternalJobProvider;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function registerExternalJobProvider(provider: ExternalJobProvider): () => void {
|
|
159
|
+
const validated = validateProvider(provider);
|
|
160
|
+
const current = registry();
|
|
161
|
+
if (!current.providers.has(validated.name) && current.providers.size >= MAX_PROVIDERS) {
|
|
162
|
+
throw new Error(`External-job provider registry supports at most ${MAX_PROVIDERS} providers.`);
|
|
163
|
+
}
|
|
164
|
+
current.providers.set(validated.name, validated);
|
|
165
|
+
return () => {
|
|
166
|
+
if (current.providers.get(validated.name) === validated) current.providers.delete(validated.name);
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function listExternalJobProviders(): readonly ExternalJobProvider[] {
|
|
171
|
+
const current = registry();
|
|
172
|
+
if (current.providers.size > MAX_PROVIDERS) throw new Error(`External-job provider registry contains more than ${MAX_PROVIDERS} providers.`);
|
|
173
|
+
const providers: ExternalJobProvider[] = [];
|
|
174
|
+
for (const [key, value] of current.providers) {
|
|
175
|
+
const provider = validateProvider(value);
|
|
176
|
+
if (key !== provider.name) throw new Error(`External-job provider registry key '${key}' does not match provider name '${provider.name}'.`);
|
|
177
|
+
providers.push(provider);
|
|
178
|
+
}
|
|
179
|
+
return providers;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function getExternalJobProvider(name: string): ExternalJobProvider | undefined {
|
|
183
|
+
const safeName = validateString(name, "External-job provider name", MAX_PROVIDER_NAME_LENGTH);
|
|
184
|
+
return listExternalJobProviders().find((provider) => provider.name === safeName);
|
|
185
|
+
}
|
package/src/api/preflight.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { discoverAgents, discoverAgentsAll, resolveAgentName, type AgentConfig, type AgentScope, type AgentSource } from "../agents/agents.ts";
|
|
5
|
+
import { discoverAgents, discoverAgentsAll, findBlockingAgentDiagnostic, resolveAgentName, type AgentConfig, type AgentScope, type AgentSource } from "../agents/agents.ts";
|
|
6
6
|
import { resolveExecutionAgentScope } from "../agents/agent-scope.ts";
|
|
7
7
|
import { buildSkillInjection, normalizeSkillInput, resolveSkillsWithFallback } from "../agents/skills.ts";
|
|
8
8
|
import { buildAgentMemoryInjection } from "../agents/agent-memory.ts";
|
|
@@ -17,6 +17,8 @@ import { appendTurnBudgetSystemPrompt } from "../runs/shared/turn-budget.ts";
|
|
|
17
17
|
import type { ResolvedTurnBudget } from "../shared/types.ts";
|
|
18
18
|
import type { ResolvedMcpDirectToolSelection } from "../runs/shared/mcp-direct-tool-allowlist.ts";
|
|
19
19
|
import { resolveStepBehavior } from "../shared/settings.ts";
|
|
20
|
+
import { canPreferForkFromSnapshot, resolveSubagentLaunchContext } from "../shared/fork-context.ts";
|
|
21
|
+
import { loadConfig } from "../extension/config.ts";
|
|
20
22
|
import { agentDefinitionDigest, AGENT_DEFINITION_PROJECTION_VERSION, launchBindingDigest } from "../shared/launch-contract.ts";
|
|
21
23
|
import { DIRS, TEMP_ROOT_DIR } from "../shared/types.ts";
|
|
22
24
|
import { processTerminalCandidatePath, processTerminalPath } from "../runs/background/process-terminal.ts";
|
|
@@ -60,6 +62,8 @@ export interface SubagentLaunchContractInput {
|
|
|
60
62
|
artifacts?: boolean;
|
|
61
63
|
artifactDir?: ArtifactDirPreference;
|
|
62
64
|
parentSessionFile?: string | null;
|
|
65
|
+
/** Current parent leaf required before an implicit `defaultContext: fork` stays `fork`. */
|
|
66
|
+
parentLeafId?: string | null;
|
|
63
67
|
sessionRoot?: string;
|
|
64
68
|
sessionDir?: string;
|
|
65
69
|
runId?: string;
|
|
@@ -190,6 +194,18 @@ function normalizeAvailableModels(models: SubagentLaunchContractInput["available
|
|
|
190
194
|
return (models ?? []).map((model) => ({ ...model, fullId: model.fullId ?? `${model.provider}/${model.id}` }));
|
|
191
195
|
}
|
|
192
196
|
|
|
197
|
+
function resolveLaunchContractContext(input: SubagentLaunchContractInput, agent: AgentConfig): "fresh" | "fork" {
|
|
198
|
+
return resolveSubagentLaunchContext({
|
|
199
|
+
explicitContext: input.context,
|
|
200
|
+
agentDefaultContext: agent.defaultContext,
|
|
201
|
+
defaultSubagentContext: loadConfig().defaultSubagentContext,
|
|
202
|
+
canUseImplicitFork: canPreferForkFromSnapshot({
|
|
203
|
+
parentSessionFile: input.parentSessionFile,
|
|
204
|
+
leafId: input.parentLeafId,
|
|
205
|
+
}),
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
193
209
|
function candidateList(inputAgent: string, selected: AgentConfig | undefined, cwd: string): SubagentLaunchContractAgentCandidate[] {
|
|
194
210
|
const all = discoverAgentsAll(cwd);
|
|
195
211
|
return [...all.builtin, ...all.package, ...all.user, ...all.project]
|
|
@@ -222,12 +238,17 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
|
|
|
222
238
|
if (input.artifactDir !== undefined && input.artifactDir !== "project" && input.artifactDir !== "session" && input.artifactDir !== "temp") {
|
|
223
239
|
return { ok: false, code: "invalid_artifact_dir", message: `Unsupported artifactDir '${String(input.artifactDir)}'; expected 'project', 'session', or 'temp'.`, diagnostics };
|
|
224
240
|
}
|
|
225
|
-
if (input.context === "fork") {
|
|
226
|
-
diagnostics.push({ code: "host_required", severity: "host-required", message: "Exact fork session branching and fork-thinking downgrade checks require Pi host session and model-registry snapshots." });
|
|
227
|
-
}
|
|
228
241
|
const scope = resolveExecutionAgentScope(input.agentScope);
|
|
229
242
|
const discovered = discoverAgents(effectiveCwd, scope);
|
|
230
243
|
const resolvedAgent = resolveAgentName(input.agent, discovered.agents);
|
|
244
|
+
const ambiguousCandidates = resolvedAgent.error
|
|
245
|
+
? discovered.agents.filter((agent) => resolveAgentName(input.agent, [agent]).agent)
|
|
246
|
+
: resolvedAgent.agent;
|
|
247
|
+
const invalidAgent = findBlockingAgentDiagnostic(input.agent, ambiguousCandidates, discovered.agentDiagnostics);
|
|
248
|
+
if (invalidAgent) {
|
|
249
|
+
const message = `Agent '${input.agent}' has invalid configuration: ${invalidAgent.error}`;
|
|
250
|
+
return { ok: false, code: "missing_agent", message, diagnostics: [{ code: "missing_agent", severity: "error", message }] };
|
|
251
|
+
}
|
|
231
252
|
if (resolvedAgent.error) {
|
|
232
253
|
return { ok: false, code: "ambiguous_agent", message: resolvedAgent.error, diagnostics };
|
|
233
254
|
}
|
|
@@ -235,6 +256,10 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
|
|
|
235
256
|
return { ok: false, code: "missing_agent", message: `Unknown agent: ${input.agent}`, diagnostics };
|
|
236
257
|
}
|
|
237
258
|
const agent = resolvedAgent.agent;
|
|
259
|
+
const context = resolveLaunchContractContext(input, agent);
|
|
260
|
+
if (context === "fork") {
|
|
261
|
+
diagnostics.push({ code: "host_required", severity: "host-required", message: "Exact fork session branching and fork-thinking downgrade checks require Pi host session and model-registry snapshots." });
|
|
262
|
+
}
|
|
238
263
|
const effectiveCapabilityCeiling = intersectSubagentCapabilityCeilings(input.capabilityCeiling, input.inheritedCapabilityCeiling);
|
|
239
264
|
const restrictionMessage = capabilityCeilingAgentRestrictionMessage(agent.name, effectiveCapabilityCeiling);
|
|
240
265
|
if (restrictionMessage) return { ok: false, code: "restricted_agent", message: restrictionMessage, diagnostics };
|
|
@@ -260,7 +285,7 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
|
|
|
260
285
|
}
|
|
261
286
|
if (resolvedSkills.missing.length > 0) diagnostics.push({ code: "missing_skill", severity: "error", message: `Missing skills: ${resolvedSkills.missing.join(", ")}` });
|
|
262
287
|
|
|
263
|
-
const externalRunner = agent.runner?.type === "external-cli";
|
|
288
|
+
const externalRunner = agent.runner?.type === "external-cli" || agent.runner?.type === "external-job";
|
|
264
289
|
const availableModels = normalizeAvailableModels(input.availableModels);
|
|
265
290
|
const preferredProvider = input.preferredProvider ?? input.parentModel?.provider;
|
|
266
291
|
const primaryModel = externalRunner
|
|
@@ -335,7 +360,7 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
|
|
|
335
360
|
definitionDigest,
|
|
336
361
|
shadowedCandidates,
|
|
337
362
|
},
|
|
338
|
-
context
|
|
363
|
+
context,
|
|
339
364
|
...(model ? { model } : {}),
|
|
340
365
|
modelCandidates,
|
|
341
366
|
...(resolveEffectiveThinking(model, effectiveThinkingConfig) ? { thinking: resolveEffectiveThinking(model, effectiveThinkingConfig) } : {}),
|
package/src/api/shared-types.ts
CHANGED
package/src/extension/config.ts
CHANGED
|
@@ -94,6 +94,9 @@ function validateMainWindowRendererConfig(value: unknown): void {
|
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
function validateConfig(config: Record<string, unknown>): void {
|
|
97
|
+
if (config.defaultSubagentContext !== undefined && config.defaultSubagentContext !== "fresh" && config.defaultSubagentContext !== "fork") {
|
|
98
|
+
throw new Error('config.defaultSubagentContext must be "fresh" or "fork"');
|
|
99
|
+
}
|
|
97
100
|
if (config.foregroundDetachShortcut !== undefined
|
|
98
101
|
&& (typeof config.foregroundDetachShortcut !== "string" || !isValidKeyId(config.foregroundDetachShortcut))) {
|
|
99
102
|
throw new Error("config.foregroundDetachShortcut must be a valid keybinding string such as \"ctrl+b\"");
|
|
@@ -101,9 +104,6 @@ function validateConfig(config: Record<string, unknown>): void {
|
|
|
101
104
|
if (config.artifactDir !== undefined && !ARTIFACT_DIR_PREFERENCES.has(config.artifactDir as ArtifactDirPreference)) {
|
|
102
105
|
throw new Error(`config.artifactDir must be "project", "session", or "temp"`);
|
|
103
106
|
}
|
|
104
|
-
if (config.legacyChainControls !== undefined && typeof config.legacyChainControls !== "boolean") {
|
|
105
|
-
throw new Error("config.legacyChainControls must be a boolean");
|
|
106
|
-
}
|
|
107
107
|
if (config.maxActiveAsyncRunsPerSession !== undefined
|
|
108
108
|
&& (typeof config.maxActiveAsyncRunsPerSession !== "number"
|
|
109
109
|
|| !Number.isInteger(config.maxActiveAsyncRunsPerSession)
|
package/src/extension/doctor.ts
CHANGED
|
@@ -134,7 +134,7 @@ function formatSessionLines(input: DoctorReportInput): string[] {
|
|
|
134
134
|
|
|
135
135
|
function formatDiscovery(input: DoctorReportInput, deps: DoctorDeps): string[] {
|
|
136
136
|
return [
|
|
137
|
-
lineFromCheck("agents
|
|
137
|
+
lineFromCheck("agents", () => {
|
|
138
138
|
const discovered = deps.discoverAgentsAll(input.cwd);
|
|
139
139
|
const agentCounts = {
|
|
140
140
|
builtin: discovered.builtin.length,
|
|
@@ -142,13 +142,10 @@ function formatDiscovery(input: DoctorReportInput, deps: DoctorDeps): string[] {
|
|
|
142
142
|
user: discovered.user.length,
|
|
143
143
|
project: discovered.project.length,
|
|
144
144
|
};
|
|
145
|
-
const
|
|
146
|
-
counts[chain.source] += 1;
|
|
147
|
-
return counts;
|
|
148
|
-
}, { builtin: 0, package: 0, user: 0, project: 0 });
|
|
145
|
+
const diagnostics = discovered.agentDiagnostics ?? [];
|
|
149
146
|
return [
|
|
150
147
|
`- agents: total ${agentCounts.builtin + agentCounts.package + agentCounts.user + agentCounts.project} (${formatSourceCounts(agentCounts)})`,
|
|
151
|
-
`-
|
|
148
|
+
...diagnostics.map((diagnostic) => `- invalid agent ${diagnostic.name ?? diagnostic.filePath} (${diagnostic.source}): ${diagnostic.error}`),
|
|
152
149
|
].join("\n");
|
|
153
150
|
}),
|
|
154
151
|
lineFromCheck("skills", () => {
|
|
@@ -171,13 +171,13 @@ export default function registerFanoutChildSubagentExtension(pi: ExtensionAPI):
|
|
|
171
171
|
allowMutatingManagementActions: false,
|
|
172
172
|
});
|
|
173
173
|
|
|
174
|
-
const params = createSubagentParamsSchema(
|
|
174
|
+
const params = createSubagentParamsSchema();
|
|
175
175
|
const tool: ToolDefinition<typeof params, Details> = {
|
|
176
176
|
name: "subagent",
|
|
177
177
|
label: "Subagent",
|
|
178
178
|
description: [
|
|
179
179
|
"Delegate to subagents from child-safe fanout mode.",
|
|
180
|
-
|
|
180
|
+
"Allowed management/control actions: list, get, status, interrupt, resume, steer, doctor.",
|
|
181
181
|
"Mutating management actions (create, update, delete, eject, disable, enable, reset, grant-spawn-budget) are blocked in this mode.",
|
|
182
182
|
].join("\n"),
|
|
183
183
|
parameters: params,
|