pi-subagents 0.36.0 → 0.37.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 +17 -0
- package/README.md +93 -8
- package/agents/delegate.md +2 -0
- package/agents/worker.md +2 -0
- package/package.json +4 -2
- package/skills/pi-subagents/SKILL.md +20 -4
- package/src/agents/agent-management.ts +7 -1
- package/src/agents/agents.ts +99 -12
- package/src/api/capability-ceiling.ts +17 -0
- package/src/api/delegation.ts +3 -1
- package/src/api/preflight.ts +399 -0
- package/src/extension/index.ts +2 -0
- package/src/extension/rpc.ts +4 -0
- package/src/extension/schemas.ts +8 -2
- package/src/extension/tool-description.ts +2 -0
- package/src/runs/background/async-execution.ts +125 -14
- package/src/runs/background/async-resume.ts +24 -7
- package/src/runs/background/async-status.ts +18 -0
- package/src/runs/background/process-terminal.ts +280 -0
- package/src/runs/background/run-status.ts +7 -1
- package/src/runs/background/scheduled-runs.ts +6 -1
- package/src/runs/background/stale-run-reconciler.ts +6 -0
- package/src/runs/background/subagent-runner.ts +182 -12
- package/src/runs/foreground/chain-execution.ts +5 -0
- package/src/runs/foreground/execution.ts +59 -13
- package/src/runs/foreground/subagent-executor.ts +32 -2
- package/src/runs/shared/acceptance.ts +41 -30
- package/src/runs/shared/capability-ceiling.ts +177 -0
- package/src/runs/shared/dynamic-fanout.ts +1 -1
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +12 -6
- package/src/runs/shared/nested-events.ts +8 -1
- package/src/runs/shared/parallel-utils.ts +5 -0
- package/src/runs/shared/pi-args.ts +141 -58
- package/src/runs/shared/session-lease.ts +25 -5
- package/src/runs/shared/subagent-prompt-runtime.ts +14 -1
- package/src/runs/shared/tool-availability.ts +18 -2
- package/src/shared/launch-contract.ts +123 -0
- package/src/shared/types.ts +104 -7
- package/src/shared/utils.ts +17 -42
- package/src/slash/delegation-adapters.ts +1 -1
- package/src/slash/slash-commands.ts +1 -1
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export {
|
|
2
|
+
SUBAGENT_CAPABILITY_CEILING_ENV,
|
|
3
|
+
SUBAGENT_CAPABILITY_CEILING_REGISTRY_KEY,
|
|
4
|
+
SUBAGENT_CAPABILITY_CEILING_VERSION,
|
|
5
|
+
decodeSubagentCapabilityCeiling,
|
|
6
|
+
encodeSubagentCapabilityCeiling,
|
|
7
|
+
intersectSubagentCapabilityCeilings,
|
|
8
|
+
parseSubagentCapabilityCeiling,
|
|
9
|
+
registerSubagentCapabilityCeiling,
|
|
10
|
+
resolveCurrentSubagentCapabilityCeiling,
|
|
11
|
+
resolveSubagentCapabilityCeiling,
|
|
12
|
+
type RegisterSubagentCapabilityCeilingOptions,
|
|
13
|
+
type ResolvedSubagentCapabilityCeiling,
|
|
14
|
+
type SubagentCapabilityCeiling,
|
|
15
|
+
type SubagentCapabilityCeilingHandle,
|
|
16
|
+
type SubagentCapabilityAudit,
|
|
17
|
+
} from "../runs/shared/capability-ceiling.ts";
|
package/src/api/delegation.ts
CHANGED
|
@@ -38,7 +38,7 @@ export interface SubagentDelegationExecutionResult {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
export interface SubagentDelegationReviewResult {
|
|
41
|
-
status: "not-requested" | "
|
|
41
|
+
status: "not-requested" | "review-required" | "reviewed" | "blockers";
|
|
42
42
|
findings?: Array<{ severity: "blocker" | "non-blocking"; file?: string; issue: string; rationale: string }>;
|
|
43
43
|
}
|
|
44
44
|
|
|
@@ -162,12 +162,14 @@ export type SubagentDelegationAcceptanceStatus =
|
|
|
162
162
|
| "attested"
|
|
163
163
|
| "checked"
|
|
164
164
|
| "verified"
|
|
165
|
+
| "review-required"
|
|
165
166
|
| "reviewed"
|
|
166
167
|
| "accepted"
|
|
167
168
|
| "rejected";
|
|
168
169
|
|
|
169
170
|
export interface SubagentDelegationAcceptanceResult {
|
|
170
171
|
status: SubagentDelegationAcceptanceStatus;
|
|
172
|
+
evidenceStatus: Exclude<SubagentDelegationAcceptanceStatus, "review-required" | "reviewed" | "accepted">;
|
|
171
173
|
explicit: boolean;
|
|
172
174
|
}
|
|
173
175
|
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { discoverAgents, discoverAgentsAll, type AgentConfig, type AgentScope, type AgentSource } from "../agents/agents.ts";
|
|
6
|
+
import { resolveExecutionAgentScope } from "../agents/agent-scope.ts";
|
|
7
|
+
import { buildSkillInjection, normalizeSkillInput, resolveSkillsWithFallback } from "../agents/skills.ts";
|
|
8
|
+
import { buildAgentMemoryInjection } from "../agents/agent-memory.ts";
|
|
9
|
+
import { buildModelCandidates, resolveEffectiveSubagentModel, type AvailableModelInfo, type ParentModel } from "../runs/shared/model-fallback.ts";
|
|
10
|
+
import { applyThinkingSuffix, resolvePiLaunchToolPlan, type PiLaunchToolPlan } from "../runs/shared/pi-args.ts";
|
|
11
|
+
import { injectOutputPathSystemPrompt, normalizeSingleOutputOverride, resolveSingleOutputPath } from "../runs/shared/single-output.ts";
|
|
12
|
+
import { getArtifactPaths, getArtifactsDir } from "../shared/artifacts.ts";
|
|
13
|
+
import { resolveEffectiveThinking } from "../shared/model-info.ts";
|
|
14
|
+
import { SUBAGENT_LIFECYCLE_ARTIFACT_VERSION, type ArtifactDirPreference, type ArtifactPaths, type JsonSchemaObject, type OutputMode } from "../shared/types.ts";
|
|
15
|
+
import type { ResolvedSubagentCapabilityCeiling, SubagentCapabilityAudit } from "../runs/shared/capability-ceiling.ts";
|
|
16
|
+
import { appendTurnBudgetSystemPrompt } from "../runs/shared/turn-budget.ts";
|
|
17
|
+
import type { ResolvedTurnBudget } from "../shared/types.ts";
|
|
18
|
+
import type { ResolvedMcpDirectToolSelection } from "../runs/shared/mcp-direct-tool-allowlist.ts";
|
|
19
|
+
import { resolveStepBehavior } from "../shared/settings.ts";
|
|
20
|
+
import { agentDefinitionDigest, AGENT_DEFINITION_PROJECTION_VERSION, launchBindingDigest } from "../shared/launch-contract.ts";
|
|
21
|
+
import { ASYNC_DIR, RESULTS_DIR, TEMP_ROOT_DIR } from "../shared/types.ts";
|
|
22
|
+
import { processTerminalCandidatePath, processTerminalPath } from "../runs/background/process-terminal.ts";
|
|
23
|
+
import { nestedResultsPath } from "../runs/shared/nested-events.ts";
|
|
24
|
+
|
|
25
|
+
export const SUBAGENT_LAUNCH_CONTRACT_VERSION = 2 as const;
|
|
26
|
+
|
|
27
|
+
export type SubagentLaunchContractReasonCode =
|
|
28
|
+
| "missing_agent"
|
|
29
|
+
| "ambiguous_agent"
|
|
30
|
+
| "missing_skill"
|
|
31
|
+
| "denied_required_tool"
|
|
32
|
+
| "invalid_artifact_dir"
|
|
33
|
+
| "invalid_cwd"
|
|
34
|
+
| "unsupported_mode";
|
|
35
|
+
|
|
36
|
+
export interface SubagentLaunchContractDiagnostic {
|
|
37
|
+
code: SubagentLaunchContractReasonCode | "host_required" | "snapshot_warning";
|
|
38
|
+
severity: "error" | "warning" | "host-required";
|
|
39
|
+
message: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface SubagentLaunchContractInput {
|
|
43
|
+
agent: string;
|
|
44
|
+
cwd: string;
|
|
45
|
+
task?: string;
|
|
46
|
+
agentScope?: AgentScope;
|
|
47
|
+
context?: "fresh" | "fork";
|
|
48
|
+
model?: string;
|
|
49
|
+
thinking?: string | false;
|
|
50
|
+
parentModel?: ParentModel;
|
|
51
|
+
availableModels?: ReadonlyArray<AvailableModelInfo | { provider: string; id: string; fullId?: string; reasoning?: boolean }>;
|
|
52
|
+
preferredProvider?: string;
|
|
53
|
+
skill?: string | string[] | boolean;
|
|
54
|
+
output?: string | boolean;
|
|
55
|
+
outputMode?: OutputMode;
|
|
56
|
+
outputSchema?: JsonSchemaObject;
|
|
57
|
+
turnBudget?: ResolvedTurnBudget;
|
|
58
|
+
artifacts?: boolean;
|
|
59
|
+
artifactDir?: ArtifactDirPreference;
|
|
60
|
+
parentSessionFile?: string | null;
|
|
61
|
+
sessionRoot?: string;
|
|
62
|
+
sessionDir?: string;
|
|
63
|
+
runId?: string;
|
|
64
|
+
/** Root run id supplied by a host when projecting nested async lifecycle paths. */
|
|
65
|
+
nestedRootRunId?: string;
|
|
66
|
+
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
67
|
+
inheritedCapabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface SubagentLaunchContractAgentCandidate {
|
|
71
|
+
name: string;
|
|
72
|
+
localName?: string;
|
|
73
|
+
packageName?: string;
|
|
74
|
+
source: AgentSource;
|
|
75
|
+
filePath: string;
|
|
76
|
+
disabled?: boolean;
|
|
77
|
+
selected: boolean;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface SubagentLaunchContractAgent {
|
|
81
|
+
name: string;
|
|
82
|
+
localName?: string;
|
|
83
|
+
packageName?: string;
|
|
84
|
+
source: AgentSource;
|
|
85
|
+
filePath: string;
|
|
86
|
+
definitionProjectionVersion: typeof AGENT_DEFINITION_PROJECTION_VERSION;
|
|
87
|
+
definitionDigest: string;
|
|
88
|
+
shadowedCandidates: SubagentLaunchContractAgentCandidate[];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface SubagentLaunchContractSkills {
|
|
92
|
+
requested: string[];
|
|
93
|
+
resolved: Array<{ name: string; path: string; source: string }>;
|
|
94
|
+
missing: string[];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface SubagentLaunchContractTools {
|
|
98
|
+
requestedBuiltin: string[];
|
|
99
|
+
declaredBuiltin: string[];
|
|
100
|
+
effectiveAllowlist: string[];
|
|
101
|
+
explicitAllowlist: boolean;
|
|
102
|
+
requiredChildTools: string[];
|
|
103
|
+
internalTools: string[];
|
|
104
|
+
mcp: ResolvedMcpDirectToolSelection[];
|
|
105
|
+
effectiveMcpTools: string[];
|
|
106
|
+
toolExtensionPaths: string[];
|
|
107
|
+
runtimeExtensions: string[];
|
|
108
|
+
configuredExtensions: string[];
|
|
109
|
+
extensionArgs: string[];
|
|
110
|
+
disableAmbientExtensions: boolean;
|
|
111
|
+
fanoutAuthorized: boolean;
|
|
112
|
+
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
113
|
+
capabilityAudit?: SubagentCapabilityAudit;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export interface SubagentLaunchContractRoots {
|
|
117
|
+
cwd: string;
|
|
118
|
+
sessionRoot?: string;
|
|
119
|
+
sessionDir?: string;
|
|
120
|
+
sessionFile?: string;
|
|
121
|
+
artifactsDir?: string;
|
|
122
|
+
artifactPaths?: ArtifactPaths;
|
|
123
|
+
outputPath?: string;
|
|
124
|
+
lifecycle?: {
|
|
125
|
+
asyncDir: string;
|
|
126
|
+
resultPath: string;
|
|
127
|
+
statusPath: string;
|
|
128
|
+
eventsPath: string;
|
|
129
|
+
processTerminalPath: string;
|
|
130
|
+
processTerminalCandidatePath: string;
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export interface SubagentLaunchContract {
|
|
135
|
+
version: typeof SUBAGENT_LAUNCH_CONTRACT_VERSION;
|
|
136
|
+
runId: string;
|
|
137
|
+
agent: SubagentLaunchContractAgent;
|
|
138
|
+
context: "fresh" | "fork";
|
|
139
|
+
model?: string;
|
|
140
|
+
modelCandidates: string[];
|
|
141
|
+
thinking?: string;
|
|
142
|
+
systemPromptMode: AgentConfig["systemPromptMode"];
|
|
143
|
+
inheritProjectContext: boolean;
|
|
144
|
+
inheritSkills: boolean;
|
|
145
|
+
skills: SubagentLaunchContractSkills;
|
|
146
|
+
tools: SubagentLaunchContractTools;
|
|
147
|
+
roots: SubagentLaunchContractRoots;
|
|
148
|
+
protocol: {
|
|
149
|
+
lifecycleArtifactVersion: number;
|
|
150
|
+
packageVersion: string;
|
|
151
|
+
};
|
|
152
|
+
diagnostics: SubagentLaunchContractDiagnostic[];
|
|
153
|
+
/** Digest of the resolved child inputs, recomputed by execution paths. */
|
|
154
|
+
launchContractDigest: string;
|
|
155
|
+
digest: string;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export type SubagentLaunchContractResult =
|
|
159
|
+
| { ok: true; contract: SubagentLaunchContract }
|
|
160
|
+
| { ok: false; code: SubagentLaunchContractReasonCode; message: string; diagnostics: SubagentLaunchContractDiagnostic[] };
|
|
161
|
+
|
|
162
|
+
function packageVersion(): string {
|
|
163
|
+
const packagePath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "package.json");
|
|
164
|
+
const parsed = JSON.parse(fs.readFileSync(packagePath, "utf-8")) as { version?: unknown };
|
|
165
|
+
if (typeof parsed.version !== "string" || !parsed.version.trim()) {
|
|
166
|
+
throw new Error(`Invalid package version in '${packagePath}'.`);
|
|
167
|
+
}
|
|
168
|
+
return parsed.version;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function stableJson(value: unknown): string {
|
|
172
|
+
if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
|
|
173
|
+
if (value && typeof value === "object") {
|
|
174
|
+
return `{${Object.entries(value as Record<string, unknown>)
|
|
175
|
+
.filter(([, entry]) => entry !== undefined)
|
|
176
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
177
|
+
.map(([key, entry]) => `${JSON.stringify(key)}:${stableJson(entry)}`)
|
|
178
|
+
.join(",")}}`;
|
|
179
|
+
}
|
|
180
|
+
return JSON.stringify(value);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function digestContract(contract: Omit<SubagentLaunchContract, "digest">): string {
|
|
184
|
+
return createHash("sha256").update(stableJson(contract)).digest("hex");
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function normalizeAvailableModels(models: SubagentLaunchContractInput["availableModels"]): AvailableModelInfo[] {
|
|
188
|
+
return (models ?? []).map((model) => ({ ...model, fullId: model.fullId ?? `${model.provider}/${model.id}` }));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function candidateList(inputAgent: string, selected: AgentConfig | undefined, cwd: string): SubagentLaunchContractAgentCandidate[] {
|
|
192
|
+
const all = discoverAgentsAll(cwd);
|
|
193
|
+
return [...all.builtin, ...all.package, ...all.user, ...all.project]
|
|
194
|
+
.filter((agent) => agent.name === inputAgent || agent.localName === inputAgent)
|
|
195
|
+
.map((agent) => ({
|
|
196
|
+
name: agent.name,
|
|
197
|
+
...(agent.localName ? { localName: agent.localName } : {}),
|
|
198
|
+
...(agent.packageName ? { packageName: agent.packageName } : {}),
|
|
199
|
+
source: agent.source,
|
|
200
|
+
filePath: agent.filePath,
|
|
201
|
+
...(agent.disabled === true ? { disabled: true } : {}),
|
|
202
|
+
selected: Boolean(selected && agent.filePath === selected.filePath && agent.name === selected.name),
|
|
203
|
+
}));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export async function resolveSubagentLaunchContract(input: SubagentLaunchContractInput): Promise<SubagentLaunchContractResult> {
|
|
207
|
+
const diagnostics: SubagentLaunchContractDiagnostic[] = [];
|
|
208
|
+
const effectiveCwd = path.resolve(input.cwd);
|
|
209
|
+
try {
|
|
210
|
+
if (!fs.statSync(effectiveCwd).isDirectory()) {
|
|
211
|
+
return { ok: false, code: "invalid_cwd", message: `cwd '${effectiveCwd}' is not a directory.`, diagnostics };
|
|
212
|
+
}
|
|
213
|
+
} catch (error) {
|
|
214
|
+
const detail = error instanceof Error ? ` ${error.message}` : "";
|
|
215
|
+
return { ok: false, code: "invalid_cwd", message: `cwd '${effectiveCwd}' is not a directory.${detail}`, diagnostics };
|
|
216
|
+
}
|
|
217
|
+
if (input.context !== undefined && input.context !== "fresh" && input.context !== "fork") {
|
|
218
|
+
return { ok: false, code: "unsupported_mode", message: `Unsupported context '${String(input.context)}'; expected 'fresh' or 'fork'.`, diagnostics };
|
|
219
|
+
}
|
|
220
|
+
if (input.artifactDir !== undefined && input.artifactDir !== "project" && input.artifactDir !== "session" && input.artifactDir !== "temp") {
|
|
221
|
+
return { ok: false, code: "invalid_artifact_dir", message: `Unsupported artifactDir '${String(input.artifactDir)}'; expected 'project', 'session', or 'temp'.`, diagnostics };
|
|
222
|
+
}
|
|
223
|
+
if (input.context === "fork") {
|
|
224
|
+
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." });
|
|
225
|
+
}
|
|
226
|
+
const scope = resolveExecutionAgentScope(input.agentScope);
|
|
227
|
+
const discovered = discoverAgents(effectiveCwd, scope);
|
|
228
|
+
const matches = discovered.agents.filter((agent) => agent.name === input.agent || agent.localName === input.agent);
|
|
229
|
+
if (matches.length === 0) {
|
|
230
|
+
return { ok: false, code: "missing_agent", message: `Unknown agent: ${input.agent}`, diagnostics };
|
|
231
|
+
}
|
|
232
|
+
if (matches.length > 1) {
|
|
233
|
+
return { ok: false, code: "ambiguous_agent", message: `Ambiguous agent: ${input.agent}`, diagnostics };
|
|
234
|
+
}
|
|
235
|
+
const agent = matches[0]!;
|
|
236
|
+
const runId = input.runId ?? "preflight";
|
|
237
|
+
const skillInput = normalizeSkillInput(input.skill);
|
|
238
|
+
const outputOverride = normalizeSingleOutputOverride(input.output, agent.output);
|
|
239
|
+
const behavior = resolveStepBehavior(agent, {
|
|
240
|
+
...(outputOverride !== undefined ? { output: outputOverride } : {}),
|
|
241
|
+
...(input.outputMode !== undefined ? { outputMode: input.outputMode } : {}),
|
|
242
|
+
...(skillInput !== undefined ? { skills: skillInput } : {}),
|
|
243
|
+
...(input.model !== undefined ? { model: input.model } : {}),
|
|
244
|
+
});
|
|
245
|
+
const requestedSkills = behavior.skills === false ? [] : behavior.skills;
|
|
246
|
+
const resolvedSkills = resolveSkillsWithFallback(
|
|
247
|
+
requestedSkills,
|
|
248
|
+
effectiveCwd,
|
|
249
|
+
effectiveCwd,
|
|
250
|
+
agent.skillPath,
|
|
251
|
+
agent.filePath ? path.dirname(agent.filePath) : effectiveCwd,
|
|
252
|
+
);
|
|
253
|
+
if (resolvedSkills.missing.includes("pi-subagents")) {
|
|
254
|
+
return { ok: false, code: "missing_skill", message: "The pi-subagents orchestration skill is not child-injectable.", diagnostics };
|
|
255
|
+
}
|
|
256
|
+
if (resolvedSkills.missing.length > 0) diagnostics.push({ code: "missing_skill", severity: "error", message: `Missing skills: ${resolvedSkills.missing.join(", ")}` });
|
|
257
|
+
|
|
258
|
+
const availableModels = normalizeAvailableModels(input.availableModels);
|
|
259
|
+
const preferredProvider = input.preferredProvider ?? input.parentModel?.provider;
|
|
260
|
+
const primaryModel = resolveEffectiveSubagentModel(input.model, agent.model, input.parentModel, availableModels, preferredProvider, { scope: discovered.modelScope });
|
|
261
|
+
const effectiveThinkingConfig = input.thinking !== undefined ? input.thinking : agent.thinking;
|
|
262
|
+
const model = applyThinkingSuffix(primaryModel, effectiveThinkingConfig, input.thinking !== undefined);
|
|
263
|
+
const modelCandidates = buildModelCandidates(primaryModel, agent.fallbackModels, availableModels, preferredProvider, { scope: discovered.modelScope })
|
|
264
|
+
.map((candidate) => applyThinkingSuffix(candidate, effectiveThinkingConfig, input.thinking !== undefined) ?? candidate);
|
|
265
|
+
let toolPlan: PiLaunchToolPlan;
|
|
266
|
+
try {
|
|
267
|
+
toolPlan = resolvePiLaunchToolPlan({
|
|
268
|
+
tools: agent.tools,
|
|
269
|
+
extensions: agent.extensions,
|
|
270
|
+
subagentOnlyExtensions: agent.subagentOnlyExtensions,
|
|
271
|
+
mcpDirectTools: agent.mcpDirectTools,
|
|
272
|
+
cwd: effectiveCwd,
|
|
273
|
+
requireReadTool: resolvedSkills.resolved.length > 0,
|
|
274
|
+
structuredOutput: Boolean(input.outputSchema),
|
|
275
|
+
capabilityCeiling: input.capabilityCeiling,
|
|
276
|
+
inheritedCapabilityCeiling: input.inheritedCapabilityCeiling,
|
|
277
|
+
});
|
|
278
|
+
} catch (error) {
|
|
279
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
280
|
+
diagnostics.push({ code: "denied_required_tool", severity: "error", message });
|
|
281
|
+
return { ok: false, code: "denied_required_tool", message, diagnostics };
|
|
282
|
+
}
|
|
283
|
+
const artifactsEnabled = input.artifacts !== false;
|
|
284
|
+
const artifactsDir = artifactsEnabled ? getArtifactsDir(input.parentSessionFile ?? null, effectiveCwd, input.artifactDir ?? "project") : undefined;
|
|
285
|
+
const artifactPaths = artifactsDir ? getArtifactPaths(artifactsDir, runId, agent.name, 0) : undefined;
|
|
286
|
+
const outputPath = resolveSingleOutputPath(behavior.output, effectiveCwd, effectiveCwd, artifactsDir ? path.join(artifactsDir, "outputs", runId) : undefined);
|
|
287
|
+
const sessionRoot = input.sessionDir ? path.resolve(input.sessionDir) : input.sessionRoot ? path.join(path.resolve(input.sessionRoot), runId) : undefined;
|
|
288
|
+
const sessionDir = sessionRoot ? path.join(sessionRoot, "run-0") : undefined;
|
|
289
|
+
const lifecycleAsyncDir = input.nestedRootRunId
|
|
290
|
+
? path.join(TEMP_ROOT_DIR, "nested-subagent-runs", input.nestedRootRunId, runId)
|
|
291
|
+
: path.join(ASYNC_DIR, runId);
|
|
292
|
+
const lifecycleResultPath = input.nestedRootRunId
|
|
293
|
+
? nestedResultsPath(input.nestedRootRunId, runId)
|
|
294
|
+
: path.join(RESULTS_DIR, `${runId}.json`);
|
|
295
|
+
if (!sessionDir) diagnostics.push({ code: "host_required", severity: "host-required", message: "No sessionRoot/sessionDir was supplied; exact child session paths require the Pi host session-root policy." });
|
|
296
|
+
if (input.availableModels === undefined && (input.model || agent.model || input.parentModel)) {
|
|
297
|
+
diagnostics.push({ code: "host_required", severity: "host-required", message: "No availableModels snapshot was supplied; model resolution may differ from the active Pi host registry." });
|
|
298
|
+
}
|
|
299
|
+
if (resolvedSkills.missing.length > 0) {
|
|
300
|
+
return { ok: false, code: "missing_skill", message: `Missing skills: ${resolvedSkills.missing.join(", ")}`, diagnostics };
|
|
301
|
+
}
|
|
302
|
+
let effectiveSystemPrompt = agent.systemPrompt?.trim() ?? "";
|
|
303
|
+
if (resolvedSkills.resolved.length > 0) {
|
|
304
|
+
const skillInjection = buildSkillInjection(resolvedSkills.resolved);
|
|
305
|
+
effectiveSystemPrompt = effectiveSystemPrompt ? `${effectiveSystemPrompt}\n\n${skillInjection}` : skillInjection;
|
|
306
|
+
}
|
|
307
|
+
const memoryInjection = buildAgentMemoryInjection(agent, effectiveCwd);
|
|
308
|
+
if (memoryInjection) effectiveSystemPrompt = effectiveSystemPrompt ? `${effectiveSystemPrompt}\n\n${memoryInjection}` : memoryInjection;
|
|
309
|
+
effectiveSystemPrompt = injectOutputPathSystemPrompt(effectiveSystemPrompt, outputPath, agent);
|
|
310
|
+
const turnBudget = input.turnBudget ?? agent.defaultTurnBudget;
|
|
311
|
+
effectiveSystemPrompt = appendTurnBudgetSystemPrompt(effectiveSystemPrompt, turnBudget);
|
|
312
|
+
const candidates = candidateList(input.agent, agent, effectiveCwd);
|
|
313
|
+
const shadowedCandidates = candidates.filter((candidate) => !candidate.selected);
|
|
314
|
+
const definitionDigest = agentDefinitionDigest(agent);
|
|
315
|
+
const contractBase: Omit<SubagentLaunchContract, "digest"> = {
|
|
316
|
+
version: SUBAGENT_LAUNCH_CONTRACT_VERSION,
|
|
317
|
+
runId,
|
|
318
|
+
agent: {
|
|
319
|
+
name: agent.name,
|
|
320
|
+
...(agent.localName ? { localName: agent.localName } : {}),
|
|
321
|
+
...(agent.packageName ? { packageName: agent.packageName } : {}),
|
|
322
|
+
source: agent.source,
|
|
323
|
+
filePath: agent.filePath,
|
|
324
|
+
definitionProjectionVersion: AGENT_DEFINITION_PROJECTION_VERSION,
|
|
325
|
+
definitionDigest,
|
|
326
|
+
shadowedCandidates,
|
|
327
|
+
},
|
|
328
|
+
context: input.context ?? agent.defaultContext ?? "fresh",
|
|
329
|
+
...(model ? { model } : {}),
|
|
330
|
+
modelCandidates,
|
|
331
|
+
...(resolveEffectiveThinking(model, effectiveThinkingConfig) ? { thinking: resolveEffectiveThinking(model, effectiveThinkingConfig) } : {}),
|
|
332
|
+
systemPromptMode: agent.systemPromptMode,
|
|
333
|
+
inheritProjectContext: agent.inheritProjectContext,
|
|
334
|
+
inheritSkills: agent.inheritSkills,
|
|
335
|
+
skills: {
|
|
336
|
+
requested: requestedSkills,
|
|
337
|
+
resolved: resolvedSkills.resolved.map((skill) => ({ name: skill.name, path: skill.path, source: skill.source })),
|
|
338
|
+
missing: resolvedSkills.missing,
|
|
339
|
+
},
|
|
340
|
+
tools: {
|
|
341
|
+
requestedBuiltin: toolPlan.requestedBuiltinTools,
|
|
342
|
+
declaredBuiltin: toolPlan.declaredBuiltinTools,
|
|
343
|
+
effectiveAllowlist: toolPlan.effectiveToolAllowlist,
|
|
344
|
+
explicitAllowlist: toolPlan.explicitToolAllowlist,
|
|
345
|
+
requiredChildTools: toolPlan.requiredChildTools,
|
|
346
|
+
internalTools: toolPlan.internalTools,
|
|
347
|
+
mcp: toolPlan.effectiveMcpSelections,
|
|
348
|
+
effectiveMcpTools: toolPlan.effectiveMcpTools,
|
|
349
|
+
toolExtensionPaths: toolPlan.toolExtensionPaths,
|
|
350
|
+
runtimeExtensions: toolPlan.runtimeExtensions,
|
|
351
|
+
configuredExtensions: toolPlan.configuredExtensions,
|
|
352
|
+
extensionArgs: toolPlan.extensionArgs,
|
|
353
|
+
disableAmbientExtensions: toolPlan.disableAmbientExtensions,
|
|
354
|
+
fanoutAuthorized: toolPlan.fanoutAuthorized,
|
|
355
|
+
...(toolPlan.capabilityCeiling ? { capabilityCeiling: toolPlan.capabilityCeiling } : {}),
|
|
356
|
+
...(toolPlan.capabilityAudit ? { capabilityAudit: toolPlan.capabilityAudit } : {}),
|
|
357
|
+
},
|
|
358
|
+
roots: {
|
|
359
|
+
cwd: effectiveCwd,
|
|
360
|
+
...(sessionRoot ? { sessionRoot } : {}),
|
|
361
|
+
...(sessionDir ? { sessionDir, sessionFile: path.join(sessionDir, "session.jsonl") } : {}),
|
|
362
|
+
...(artifactsDir ? { artifactsDir } : {}),
|
|
363
|
+
...(artifactPaths ? { artifactPaths } : {}),
|
|
364
|
+
...(outputPath ? { outputPath } : {}),
|
|
365
|
+
lifecycle: {
|
|
366
|
+
asyncDir: lifecycleAsyncDir,
|
|
367
|
+
resultPath: lifecycleResultPath,
|
|
368
|
+
statusPath: path.join(lifecycleAsyncDir, "status.json"),
|
|
369
|
+
eventsPath: path.join(lifecycleAsyncDir, "events.jsonl"),
|
|
370
|
+
processTerminalPath: processTerminalPath(lifecycleAsyncDir),
|
|
371
|
+
processTerminalCandidatePath: processTerminalCandidatePath(lifecycleAsyncDir),
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
protocol: {
|
|
375
|
+
lifecycleArtifactVersion: SUBAGENT_LIFECYCLE_ARTIFACT_VERSION,
|
|
376
|
+
packageVersion: packageVersion(),
|
|
377
|
+
},
|
|
378
|
+
diagnostics,
|
|
379
|
+
launchContractDigest: launchBindingDigest({
|
|
380
|
+
task: input.task ?? "",
|
|
381
|
+
definitionDigest,
|
|
382
|
+
...(model ? { model } : {}),
|
|
383
|
+
modelCandidates,
|
|
384
|
+
...(resolveEffectiveThinking(model, effectiveThinkingConfig) ? { thinking: resolveEffectiveThinking(model, effectiveThinkingConfig) } : {}),
|
|
385
|
+
systemPrompt: effectiveSystemPrompt,
|
|
386
|
+
systemPromptMode: agent.systemPromptMode,
|
|
387
|
+
inheritProjectContext: agent.inheritProjectContext,
|
|
388
|
+
inheritSkills: agent.inheritSkills,
|
|
389
|
+
skills: requestedSkills,
|
|
390
|
+
tools: toolPlan.effectiveToolAllowlist,
|
|
391
|
+
extensions: toolPlan.extensionArgs,
|
|
392
|
+
mcpDirectTools: toolPlan.effectiveMcpTools,
|
|
393
|
+
...(outputPath ? { outputPath } : {}),
|
|
394
|
+
outputMode: input.outputMode ?? "inline",
|
|
395
|
+
...(input.outputSchema ? { structuredOutputSchema: input.outputSchema } : {}),
|
|
396
|
+
}),
|
|
397
|
+
};
|
|
398
|
+
return { ok: true, contract: { ...contractBase, digest: digestContract(contractBase) } };
|
|
399
|
+
}
|
package/src/extension/index.ts
CHANGED
|
@@ -47,6 +47,7 @@ import { drainOutstandingWork } from "../runs/background/auto-drain.ts";
|
|
|
47
47
|
import registerSubagentNotify, { parseSubagentNotifyContent, type SubagentNotifyDetails } from "../runs/background/notify.ts";
|
|
48
48
|
import { formatSteeringNotice, handleSubagentSteeringNotice, SUBAGENT_STEERING_MESSAGE_TYPE, type SubagentSteeringMessageDetails } from "./steering-notices.ts";
|
|
49
49
|
import { SUBAGENT_CHILD_ENV, SUBAGENT_PARENT_SESSION_ENV } from "../runs/shared/pi-args.ts";
|
|
50
|
+
import { resolveCurrentSubagentCapabilityCeiling } from "../runs/shared/capability-ceiling.ts";
|
|
50
51
|
import { formatDuration, shortenPath } from "../shared/formatters.ts";
|
|
51
52
|
import { loadConfig } from "./config.ts";
|
|
52
53
|
import { buildSubagentToolDescription } from "./tool-description.ts";
|
|
@@ -277,6 +278,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
277
278
|
}
|
|
278
279
|
return executorExecute(randomUUID(), params, signal, undefined, ctx);
|
|
279
280
|
},
|
|
281
|
+
resolveCapabilityCeiling: (sessionId) => resolveCurrentSubagentCapabilityCeiling(sessionId),
|
|
280
282
|
});
|
|
281
283
|
const executor = createSubagentExecutor({
|
|
282
284
|
pi,
|
package/src/extension/rpc.ts
CHANGED
|
@@ -11,6 +11,8 @@ import {
|
|
|
11
11
|
ASYNC_DIR,
|
|
12
12
|
RESULTS_DIR,
|
|
13
13
|
SUBAGENT_ASYNC_COMPLETE_EVENT,
|
|
14
|
+
SUBAGENT_PROCESS_TERMINAL_EVENT,
|
|
15
|
+
SUBAGENT_LIFECYCLE_ARTIFACT_VERSION,
|
|
14
16
|
} from "../shared/types.ts";
|
|
15
17
|
import { readStatus } from "../shared/utils.ts";
|
|
16
18
|
import { SubagentParams } from "./schemas.ts";
|
|
@@ -181,12 +183,14 @@ function pingData(ctx: ExtensionContext | null) {
|
|
|
181
183
|
nonRecoveringSteer: true,
|
|
182
184
|
interrupt: true,
|
|
183
185
|
stop: true,
|
|
186
|
+
processTerminalProof: { version: 1, lifecycleArtifactVersion: SUBAGENT_LIFECYCLE_ARTIFACT_VERSION },
|
|
184
187
|
},
|
|
185
188
|
events: {
|
|
186
189
|
ready: SUBAGENT_RPC_READY_EVENT,
|
|
187
190
|
request: SUBAGENT_RPC_REQUEST_EVENT,
|
|
188
191
|
replyPrefix: SUBAGENT_RPC_REPLY_EVENT_PREFIX,
|
|
189
192
|
asyncComplete: SUBAGENT_ASYNC_COMPLETE_EVENT,
|
|
193
|
+
processTerminal: SUBAGENT_PROCESS_TERMINAL_EVENT,
|
|
190
194
|
},
|
|
191
195
|
session: sessionData(ctx),
|
|
192
196
|
};
|
package/src/extension/schemas.ts
CHANGED
|
@@ -67,11 +67,17 @@ const JsonSchemaObject = Type.Unsafe({
|
|
|
67
67
|
|
|
68
68
|
const AcceptanceOverride = Type.Unsafe({
|
|
69
69
|
anyOf: [
|
|
70
|
-
{ type: "string", enum: ["auto", "attested", "checked", "verified"
|
|
70
|
+
{ type: "string", enum: ["auto", "attested", "checked", "verified"] },
|
|
71
|
+
{
|
|
72
|
+
type: "string",
|
|
73
|
+
enum: ["reviewed"],
|
|
74
|
+
deprecated: true,
|
|
75
|
+
description: "Invalid as an explicit policy. Recognized only so preflight can explain that reviewed is an achieved status.",
|
|
76
|
+
},
|
|
71
77
|
{ type: "boolean", enum: [false] },
|
|
72
78
|
{ type: "object", additionalProperties: true },
|
|
73
79
|
],
|
|
74
|
-
description: "Optional acceptance policy. In the current/default contract, omitted means auto-inferred
|
|
80
|
+
description: "Optional acceptance policy. For reviewer/read-only calls, omit acceptance. Evidence levels end at verified; require independent review with acceptance.review.required. The value reviewed is an achieved status and is accepted by the schema only for actionable preflight recovery. In the current/default contract, omitted means auto-inferred. With agentContract.version=1, omitted means not requested and acceptance failures are reported separately from execution.",
|
|
75
81
|
});
|
|
76
82
|
|
|
77
83
|
const AgentContractOverride = Type.Object({
|
|
@@ -24,6 +24,7 @@ EXECUTION (use exactly ONE mode):
|
|
|
24
24
|
• Optional context: { context: "fresh" | "fork" } (explicit value overrides every child; when omitted, each requested agent uses its own defaultContext, otherwise "fresh"; inspect agent defaults via { action: "list" })
|
|
25
25
|
• Fork thinking: model strings accept a thinking suffix (provider/model:off|minimal|low|medium|high|xhigh|max). Forking over a parent transcript that carries signed Anthropic thinking blocks forces thinking off only when a child's effective primary or fallback model resolves to the Anthropic provider or anthropic-messages API; unresolved models are treated conservatively. The result notes affected children, including on failures. Use fresh context when an Anthropic child needs thinking.
|
|
26
26
|
• Optional timeout: { timeoutMs } or { maxRuntimeMs } sets a run-level max runtime for foreground and async/background runs
|
|
27
|
+
• Acceptance: omit acceptance for reviewer/read-only calls. Evidence levels end at verified. Use acceptance.review.required to require independent review of a writer result. Never request acceptance:"reviewed"; reviewed is achieved only after an independent reviewer result.
|
|
27
28
|
• If { action: "list" } shows proactive skill subagent suggestions, consider a small fresh-context fanout for broad tasks where one of those skills would materially help
|
|
28
29
|
|
|
29
30
|
CHAIN TEMPLATE VARIABLES (use in task strings):
|
|
@@ -80,6 +81,7 @@ EXECUTE:
|
|
|
80
81
|
• Before execution, call { action: "list" }; run only executable/non-disabled configured agents/chains.
|
|
81
82
|
• SINGLE {agent, task?}; PARALLEL {tasks:[{agent,task,count?,output?,reads?,progress?}], concurrency?, worktree?}; CHAIN {chain:[{agent,task?},{parallel:[...]}]}.
|
|
82
83
|
• context can be "fresh" or "fork"; omitted uses each agent defaultContext, otherwise fresh. timeoutMs/maxRuntimeMs apply to foreground and async/background runs.
|
|
84
|
+
• Omit acceptance for reviewer/read-only calls. Evidence levels end at verified; use acceptance.review.required for independent writer review. reviewed is an achieved status, never an explicit input.
|
|
83
85
|
• Chain templates may use {task}, {previous}, {chain_dir}, and named outputs. Parallel worktree isolation requires a clean git repo.
|
|
84
86
|
• Chain example: { chain: [{agent:"agent-a", task:"Analyze {task}"}, {parallel: [{agent:"agent-b", task:"Check {previous}", count: 3}]}] }
|
|
85
87
|
• If list shows proactive skill subagent suggestions, use a small fresh-context fanout only when the task is broad enough.
|