pi-subagents 0.60.0 → 0.62.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 +55 -0
- package/docs/agents.md +7 -3
- package/docs/configuration.md +9 -5
- package/docs/extension-api.md +14 -7
- package/docs/models.md +1 -1
- package/docs/observability.md +1 -1
- package/docs/tool-reference.md +13 -4
- package/docs/workflows.md +14 -13
- package/install.mjs +1 -1
- package/package.json +1 -1
- package/skills/pi-subagents/SKILL.md +6 -4
- package/skills/pi-subagents/references/constraints-and-recipes.md +1 -1
- package/skills/pi-subagents/references/execution-controls.md +41 -11
- package/skills/pi-subagents/references/multi-lane-orchestration.md +1 -1
- package/skills/pi-subagents/references/prompting-and-roles.md +4 -4
- package/skills/pi-subagents/references/review-and-validation.md +1 -1
- package/src/agents/agent-management.ts +121 -63
- package/src/agents/agent-serializer.ts +3 -0
- package/src/agents/agents.ts +543 -223
- package/src/agents/runtime-agent-registry.ts +5 -1
- package/src/api/background-work.ts +7 -2
- package/src/api/external-runs.ts +67 -4
- package/src/api/preflight.ts +17 -8
- package/src/api/shared-types.ts +1 -0
- package/src/extension/index.ts +7 -4
- package/src/extension/public-execution.ts +48 -4
- package/src/extension/rpc.ts +62 -4
- package/src/extension/schemas.ts +17 -9
- package/src/extension/tool-description.ts +15 -19
- package/src/runs/background/active-async-capacity.ts +26 -7
- package/src/runs/background/async-execution.ts +98 -51
- package/src/runs/background/async-job-tracker.ts +62 -3
- package/src/runs/background/async-resume.ts +6 -3
- package/src/runs/background/async-status.ts +59 -9
- package/src/runs/background/auto-drain.ts +1 -1
- package/src/runs/background/fleet-view.ts +1 -1
- package/src/runs/background/process-terminal.ts +16 -0
- package/src/runs/background/result-watcher.ts +1 -1
- package/src/runs/background/resume-guidance.ts +1 -1
- package/src/runs/background/run-status.ts +2 -2
- package/src/runs/background/scheduled-runs.ts +63 -6
- package/src/runs/background/steering.ts +4 -1
- package/src/runs/background/subagent-runner.ts +15 -9
- package/src/runs/background/subagent-wait.ts +20 -21
- package/src/runs/background/wait-completions.ts +1 -1
- package/src/runs/background/wait-tool.ts +18 -18
- package/src/runs/foreground/execution.ts +75 -9
- package/src/runs/foreground/subagent-executor.ts +181 -60
- package/src/runs/shared/acceptance.ts +113 -27
- package/src/runs/shared/capability-ceiling.ts +1 -0
- package/src/runs/shared/dynamic-fanout.ts +1 -1
- package/src/runs/shared/host-step-status.ts +1 -0
- package/src/runs/shared/model-fallback.ts +61 -17
- package/src/runs/shared/parallel-utils.ts +2 -6
- package/src/runs/shared/permissions.ts +1 -1
- package/src/runs/shared/pi-args.ts +24 -12
- package/src/runs/shared/pi-spawn.ts +69 -35
- package/src/runs/shared/structured-output.ts +33 -6
- package/src/runs/shared/subagent-prompt-runtime.ts +20 -3
- package/src/runs/shared/task-intent.ts +17 -6
- package/src/runs/shared/tool-timeout.ts +1 -1
- package/src/runs/shared/workflow-graph.ts +3 -2
- package/src/shared/atomic-json.ts +3 -1
- package/src/shared/fork-context.ts +0 -12
- package/src/shared/fork-session-cwd.ts +27 -0
- package/src/shared/launch-contract.ts +3 -0
- package/src/shared/types.ts +43 -4
- package/src/shared/workflow-child-permit.ts +91 -0
- package/src/slash/prompt-template-bridge.ts +37 -1
- package/src/slash/slash-commands.ts +18 -26
- package/src/slash/subagents-admin.ts +2 -0
- package/src/tui/render.ts +95 -52
- package/src/workflows/scripted-workflow.ts +153 -4
- package/src/workflows/workflow-child-summary.ts +1 -1
- package/src/workflows/workflow-receipt.ts +41 -4
- package/src/workflows/workflow-resources.ts +150 -0
|
@@ -47,7 +47,7 @@ const VALID_EVIDENCE_KINDS: AcceptanceEvidenceKind[] = [
|
|
|
47
47
|
const VALID_EVIDENCE = new Set<AcceptanceEvidenceKind>(VALID_EVIDENCE_KINDS);
|
|
48
48
|
const ACCEPTANCE_EVIDENCE_HELP = `Supported evidence kinds: ${VALID_EVIDENCE_KINDS.join(", ")}. Example: { level: "checked", evidence: ["commands-run", "changed-files"] }.`;
|
|
49
49
|
const ACCEPTANCE_OBJECT_EXAMPLE = "Example: { level: \"checked\", evidence: [\"commands-run\", \"changed-files\"] }.";
|
|
50
|
-
const ACCEPTANCE_CONFIG_KEYS = new Set(["level", "criteria", "evidence", "verify", "review", "stopRules", "reason"]);
|
|
50
|
+
const ACCEPTANCE_CONFIG_KEYS = new Set(["level", "report", "criteria", "evidence", "verify", "review", "stopRules", "reason"]);
|
|
51
51
|
const ACCEPTANCE_GATE_KEYS = new Set(["id", "must", "evidence", "severity"]);
|
|
52
52
|
const ACCEPTANCE_VERIFY_KEYS = new Set(["id", "command", "timeoutMs", "cwd", "env", "allowFailure"]);
|
|
53
53
|
const ACCEPTANCE_REVIEW_KEYS = new Set(["agent", "focus", "required"]);
|
|
@@ -146,11 +146,46 @@ function inferLevel(input: {
|
|
|
146
146
|
};
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
-
|
|
149
|
+
type AcceptanceInputNormalizationResult = { value: unknown; error?: string };
|
|
150
|
+
|
|
151
|
+
function normalizeAcceptanceValue(input: unknown, pathLabel = "acceptance"): AcceptanceInputNormalizationResult {
|
|
152
|
+
if (typeof input !== "string") return { value: input };
|
|
153
|
+
const trimmed = input.trim();
|
|
154
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return { value: input };
|
|
155
|
+
let parsed: unknown;
|
|
156
|
+
try {
|
|
157
|
+
parsed = JSON.parse(trimmed);
|
|
158
|
+
} catch (error) {
|
|
159
|
+
return {
|
|
160
|
+
value: input,
|
|
161
|
+
error: `${pathLabel} JSON string must encode a valid acceptance object: ${error instanceof Error ? error.message : String(error)}`,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
165
|
+
const kind = parsed === null ? "null" : Array.isArray(parsed) ? "an array" : typeof parsed;
|
|
166
|
+
return { value: input, error: `${pathLabel} JSON string must encode an object; got ${kind}.` };
|
|
167
|
+
}
|
|
168
|
+
return { value: parsed };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function normalizeAcceptanceInput(input: unknown): AcceptanceConfig {
|
|
172
|
+
const normalized = normalizeAcceptanceValue(input);
|
|
173
|
+
if (normalized.error) throw new Error(normalized.error);
|
|
174
|
+
input = normalized.value;
|
|
150
175
|
if (input === undefined || input === "auto") return { level: "auto" };
|
|
151
176
|
if (input === false) return { level: "none", reason: "disabled by deprecated false shorthand" };
|
|
152
|
-
if (typeof input === "string") return { level: input };
|
|
153
|
-
return { ...input };
|
|
177
|
+
if (typeof input === "string") return { level: input as AcceptanceConfig["level"] };
|
|
178
|
+
return { ...(input as AcceptanceConfig) };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export type ResolvedAcceptanceReportMode = "off" | "optional" | "required";
|
|
182
|
+
|
|
183
|
+
export function resolveAcceptanceReportMode(input: unknown): ResolvedAcceptanceReportMode {
|
|
184
|
+
const normalized = normalizeAcceptanceValue(input);
|
|
185
|
+
if (normalized.error) throw new Error(normalized.error);
|
|
186
|
+
const value = normalized.value;
|
|
187
|
+
const report = value && typeof value === "object" && !Array.isArray(value) ? (value as AcceptanceConfig).report : undefined;
|
|
188
|
+
return value === false || report === "off" ? "off" : report === "on" ? "required" : "optional";
|
|
154
189
|
}
|
|
155
190
|
|
|
156
191
|
type GateAcceptanceNormalizationResult =
|
|
@@ -158,7 +193,11 @@ type GateAcceptanceNormalizationResult =
|
|
|
158
193
|
| { ok: false; error: string };
|
|
159
194
|
|
|
160
195
|
export function normalizeGateAcceptance(gate: unknown, acceptance: AcceptanceInput | undefined): GateAcceptanceNormalizationResult {
|
|
161
|
-
if (gate === undefined)
|
|
196
|
+
if (gate === undefined) {
|
|
197
|
+
if (acceptance === undefined) return { ok: true };
|
|
198
|
+
const normalized = normalizeAcceptanceValue(acceptance);
|
|
199
|
+
return normalized.error ? { ok: false, error: normalized.error } : { ok: true, acceptance: normalized.value as AcceptanceInput };
|
|
200
|
+
}
|
|
162
201
|
if (typeof gate !== "string" || !gate.trim()) return { ok: false, error: "gate must be a non-empty command string." };
|
|
163
202
|
if (acceptance !== undefined) return { ok: false, error: "gate cannot be combined with acceptance; use one gate command or acceptance.verify." };
|
|
164
203
|
return { ok: true, acceptance: { level: "verified", verify: [{ id: "gate", command: gate.trim() }] } };
|
|
@@ -177,6 +216,9 @@ export function validateAcceptanceInput(input: unknown, pathLabel = "acceptance"
|
|
|
177
216
|
const errors: string[] = [];
|
|
178
217
|
if (input === undefined) return errors;
|
|
179
218
|
if (input === false) return errors;
|
|
219
|
+
const normalized = normalizeAcceptanceValue(input, pathLabel);
|
|
220
|
+
if (normalized.error) return [normalized.error];
|
|
221
|
+
input = normalized.value;
|
|
180
222
|
if (typeof input === "string") {
|
|
181
223
|
if (input === "reviewed") errors.push(`${pathLabel} ${EXPLICIT_REVIEWED_UNAVAILABLE}`);
|
|
182
224
|
else if (!VALID_LEVELS.has(input as AcceptanceLevel)) errors.push(`${pathLabel} has invalid level '${input}'.`);
|
|
@@ -197,6 +239,9 @@ export function validateAcceptanceInput(input: unknown, pathLabel = "acceptance"
|
|
|
197
239
|
} else if (value.level !== undefined && (typeof value.level !== "string" || !VALID_LEVELS.has(value.level as AcceptanceLevel))) {
|
|
198
240
|
errors.push(`${pathLabel}.level must be one of auto, none, attested, checked, verified.`);
|
|
199
241
|
}
|
|
242
|
+
if (value.report !== undefined && value.report !== "on" && value.report !== "off") {
|
|
243
|
+
errors.push(`${pathLabel}.report must be on or off.`);
|
|
244
|
+
}
|
|
200
245
|
if (value.level === "none" && (typeof value.reason !== "string" || !value.reason.trim())) {
|
|
201
246
|
errors.push(`${pathLabel}.reason is required when level is none.`);
|
|
202
247
|
}
|
|
@@ -304,29 +349,45 @@ export function validateAcceptanceInput(input: unknown, pathLabel = "acceptance"
|
|
|
304
349
|
|
|
305
350
|
export function validateExecutionAcceptance(input: {
|
|
306
351
|
acceptance?: unknown;
|
|
307
|
-
|
|
352
|
+
outputSchema?: unknown;
|
|
353
|
+
tasks?: Array<{ acceptance?: unknown; outputSchema?: unknown }>;
|
|
308
354
|
chain?: Array<{
|
|
309
355
|
acceptance?: unknown;
|
|
310
|
-
|
|
356
|
+
outputSchema?: unknown;
|
|
357
|
+
parallel?: Array<{ acceptance?: unknown; outputSchema?: unknown }> | { acceptance?: unknown; outputSchema?: unknown };
|
|
311
358
|
}>;
|
|
312
359
|
}): string[] {
|
|
313
360
|
const errors = validateAcceptanceInput(input.acceptance, "acceptance");
|
|
361
|
+
errors.push(...validateAcceptanceReportMode(input.acceptance, input.outputSchema, "acceptance"));
|
|
314
362
|
for (const [index, task] of (input.tasks ?? []).entries()) {
|
|
315
363
|
errors.push(...validateAcceptanceInput(task.acceptance, `tasks[${index}].acceptance`));
|
|
364
|
+
errors.push(...validateAcceptanceReportMode(task.acceptance, task.outputSchema, `tasks[${index}].acceptance`));
|
|
316
365
|
}
|
|
317
366
|
for (const [stepIndex, step] of (input.chain ?? []).entries()) {
|
|
318
367
|
errors.push(...validateAcceptanceInput(step.acceptance, `chain[${stepIndex}].acceptance`));
|
|
368
|
+
errors.push(...validateAcceptanceReportMode(step.acceptance, step.outputSchema, `chain[${stepIndex}].acceptance`));
|
|
319
369
|
if (Array.isArray(step.parallel)) {
|
|
320
370
|
for (const [taskIndex, task] of step.parallel.entries()) {
|
|
321
371
|
errors.push(...validateAcceptanceInput(task.acceptance, `chain[${stepIndex}].parallel[${taskIndex}].acceptance`));
|
|
372
|
+
errors.push(...validateAcceptanceReportMode(task.acceptance, task.outputSchema, `chain[${stepIndex}].parallel[${taskIndex}].acceptance`));
|
|
322
373
|
}
|
|
323
374
|
} else if (step.parallel) {
|
|
324
375
|
errors.push(...validateAcceptanceInput(step.parallel.acceptance, `chain[${stepIndex}].parallel.acceptance`));
|
|
376
|
+
errors.push(...validateAcceptanceReportMode(step.parallel.acceptance, step.parallel.outputSchema, `chain[${stepIndex}].parallel.acceptance`));
|
|
325
377
|
}
|
|
326
378
|
}
|
|
327
379
|
return errors;
|
|
328
380
|
}
|
|
329
381
|
|
|
382
|
+
function validateAcceptanceReportMode(acceptance: unknown, outputSchema: unknown, pathLabel: string): string[] {
|
|
383
|
+
const normalized = normalizeAcceptanceValue(acceptance, pathLabel);
|
|
384
|
+
if (normalized.error) return [];
|
|
385
|
+
acceptance = normalized.value;
|
|
386
|
+
if (!acceptance || typeof acceptance !== "object" || Array.isArray(acceptance)) return [];
|
|
387
|
+
if (!("report" in acceptance)) return [];
|
|
388
|
+
return outputSchema === undefined ? [`${pathLabel}.report requires outputSchema.`] : [];
|
|
389
|
+
}
|
|
390
|
+
|
|
330
391
|
function normalizeCriteria(criteria: Array<string | { id?: string; must?: string; evidence?: AcceptanceEvidenceKind[]; severity?: "required" | "recommended" }> | undefined, evidence: AcceptanceEvidenceKind[]): ResolvedAcceptanceGate[] {
|
|
331
392
|
return (criteria ?? []).map((criterion, index): ResolvedAcceptanceGate => {
|
|
332
393
|
if (typeof criterion === "string") {
|
|
@@ -678,7 +739,15 @@ function parseAcceptanceReportBody(body: string): { report?: AcceptanceReport; e
|
|
|
678
739
|
return validateAcceptanceReport(parseReportJson(body));
|
|
679
740
|
}
|
|
680
741
|
|
|
681
|
-
|
|
742
|
+
interface AcceptanceReportParseResult {
|
|
743
|
+
report?: AcceptanceReport;
|
|
744
|
+
error?: string;
|
|
745
|
+
/** True when an acceptance-report signal was found but its envelope was invalid. */
|
|
746
|
+
malformed?: boolean;
|
|
747
|
+
sourcePath?: string;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
function parseUnterminatedAcceptanceReportFence(output: string): AcceptanceReportParseResult {
|
|
682
751
|
const opener = /```acceptance[-_]report\b[^\n]*\n/gi.exec(output);
|
|
683
752
|
if (!opener) return {};
|
|
684
753
|
const bodyStart = opener.index + opener[0].length;
|
|
@@ -687,13 +756,13 @@ function parseUnterminatedAcceptanceReportFence(output: string): { report?: Acce
|
|
|
687
756
|
const validation = validateAcceptanceReport(JSON.parse(output.slice(bodyStart).trim()) as unknown);
|
|
688
757
|
return validation.report
|
|
689
758
|
? { report: validation.report }
|
|
690
|
-
: { error: `Failed to parse acceptance-report: Invalid acceptance-report: ${validation.errors.join("; ")}
|
|
759
|
+
: { error: `Failed to parse acceptance-report: Invalid acceptance-report: ${validation.errors.join("; ")}`, malformed: true };
|
|
691
760
|
} catch (error) {
|
|
692
|
-
return { error: `Failed to parse acceptance-report: ${error instanceof Error ? error.message : String(error)}
|
|
761
|
+
return { error: `Failed to parse acceptance-report: ${error instanceof Error ? error.message : String(error)}`, malformed: true };
|
|
693
762
|
}
|
|
694
763
|
}
|
|
695
764
|
|
|
696
|
-
function parseGenericJsonAcceptanceReportBody(body: string):
|
|
765
|
+
function parseGenericJsonAcceptanceReportBody(body: string): AcceptanceReportParseResult {
|
|
697
766
|
const parsed = parseReportJson(body);
|
|
698
767
|
const normalized = normalizeAcceptanceReportValue(parsed);
|
|
699
768
|
const hasCriteriaMarker = normalized.value !== null
|
|
@@ -704,12 +773,12 @@ function parseGenericJsonAcceptanceReportBody(body: string): { report?: Acceptan
|
|
|
704
773
|
const validation = validateAcceptanceReport(parsed);
|
|
705
774
|
return validation.report
|
|
706
775
|
? { report: validation.report }
|
|
707
|
-
: { error: `Invalid acceptance-report: ${validation.errors.join("; ")}
|
|
776
|
+
: { error: `Invalid acceptance-report: ${validation.errors.join("; ")}`, malformed: true };
|
|
708
777
|
}
|
|
709
778
|
|
|
710
779
|
export const ACCEPTANCE_REPORT_NOT_FOUND = "Structured acceptance report not found.";
|
|
711
780
|
|
|
712
|
-
export function parseAcceptanceReport(output: string):
|
|
781
|
+
export function parseAcceptanceReport(output: string): AcceptanceReportParseResult {
|
|
713
782
|
const explicitFencePresent = /```acceptance[-_]report\b/i.test(output);
|
|
714
783
|
const fenced = fencedBlocks(output, "acceptance[-_]report");
|
|
715
784
|
const parseErrors: string[] = [];
|
|
@@ -722,17 +791,17 @@ export function parseAcceptanceReport(output: string): { report?: AcceptanceRepo
|
|
|
722
791
|
parseErrors.push(error instanceof Error ? error.message : String(error));
|
|
723
792
|
}
|
|
724
793
|
}
|
|
725
|
-
if (parseErrors.length > 0) return { error: `Failed to parse acceptance-report: ${parseErrors.join("; ")}
|
|
794
|
+
if (parseErrors.length > 0) return { error: `Failed to parse acceptance-report: ${parseErrors.join("; ")}`, malformed: true };
|
|
726
795
|
if (explicitFencePresent) {
|
|
727
796
|
const recovered = parseUnterminatedAcceptanceReportFence(output);
|
|
728
797
|
if (recovered.report || recovered.error) return recovered;
|
|
729
|
-
return { error: "Failed to parse acceptance-report: Empty or unterminated acceptance-report fence." };
|
|
798
|
+
return { error: "Failed to parse acceptance-report: Empty or unterminated acceptance-report fence.", malformed: true };
|
|
730
799
|
}
|
|
731
800
|
for (const body of fencedBlocks(output, "(?:json|jsonc|json5)")) {
|
|
732
801
|
try {
|
|
733
802
|
const parsed = parseGenericJsonAcceptanceReportBody(body);
|
|
734
803
|
if (parsed.report) return { report: parsed.report };
|
|
735
|
-
if (parsed.error) return { error: `Failed to parse acceptance-report: ${parsed.error}
|
|
804
|
+
if (parsed.error) return { error: `Failed to parse acceptance-report: ${parsed.error}`, malformed: parsed.malformed };
|
|
736
805
|
} catch {
|
|
737
806
|
// Ignore unrelated malformed generic JSON. A recognizable report shape
|
|
738
807
|
// returns exact validation errors above instead of being mistaken for prose.
|
|
@@ -742,20 +811,20 @@ export function parseAcceptanceReport(output: string): { report?: AcceptanceRepo
|
|
|
742
811
|
if (markerIndex !== -1) {
|
|
743
812
|
const jsonStart = output.indexOf("{", markerIndex);
|
|
744
813
|
if (jsonStart === -1) {
|
|
745
|
-
return { error: "Failed to parse acceptance-report: Expected a JSON object after ACCEPTANCE_REPORT:." };
|
|
814
|
+
return { error: "Failed to parse acceptance-report: Expected a JSON object after ACCEPTANCE_REPORT:.", malformed: true };
|
|
746
815
|
}
|
|
747
816
|
const json = extractBalancedJson(output, jsonStart);
|
|
748
817
|
if (!json) {
|
|
749
|
-
return { error: "Failed to parse acceptance-report: Unterminated JSON object after ACCEPTANCE_REPORT:." };
|
|
818
|
+
return { error: "Failed to parse acceptance-report: Unterminated JSON object after ACCEPTANCE_REPORT:.", malformed: true };
|
|
750
819
|
}
|
|
751
820
|
try {
|
|
752
821
|
const parsed = JSON.parse(json) as unknown;
|
|
753
822
|
const validation = validateAcceptanceReport(parsed);
|
|
754
823
|
if (validation.report) return { report: validation.report };
|
|
755
|
-
return { error: `Failed to parse acceptance-report: Invalid acceptance-report: ${validation.errors.join("; ")}
|
|
824
|
+
return { error: `Failed to parse acceptance-report: Invalid acceptance-report: ${validation.errors.join("; ")}`, malformed: true };
|
|
756
825
|
} catch (error) {
|
|
757
826
|
const message = error instanceof Error ? error.message : String(error);
|
|
758
|
-
return { error: `Failed to parse acceptance-report: ${message}
|
|
827
|
+
return { error: `Failed to parse acceptance-report: ${message}`, malformed: true };
|
|
759
828
|
}
|
|
760
829
|
}
|
|
761
830
|
return { error: ACCEPTANCE_REPORT_NOT_FOUND };
|
|
@@ -764,14 +833,18 @@ export function parseAcceptanceReport(output: string): { report?: AcceptanceRepo
|
|
|
764
833
|
function parseAcceptanceReportSources(
|
|
765
834
|
output: string,
|
|
766
835
|
fileOutput: { content: string; path: string; authoritative?: boolean } | undefined,
|
|
767
|
-
):
|
|
836
|
+
): AcceptanceReportParseResult {
|
|
768
837
|
const fromText = () => parseAcceptanceReport(output);
|
|
769
838
|
const fromFile = () => {
|
|
770
839
|
if (!fileOutput) return { error: ACCEPTANCE_REPORT_NOT_FOUND };
|
|
771
840
|
const parsed = parseAcceptanceReport(fileOutput.content);
|
|
772
841
|
return parsed.report || parsed.error === ACCEPTANCE_REPORT_NOT_FOUND
|
|
773
842
|
? parsed
|
|
774
|
-
: {
|
|
843
|
+
: {
|
|
844
|
+
...parsed,
|
|
845
|
+
error: `${parsed.error} (in configured output ${fileOutput.path})`,
|
|
846
|
+
sourcePath: fileOutput.path,
|
|
847
|
+
};
|
|
775
848
|
};
|
|
776
849
|
const [primary, secondary] = fileOutput?.authoritative ? [fromFile, fromText] : [fromText, fromFile];
|
|
777
850
|
const first = primary();
|
|
@@ -839,7 +912,7 @@ function validateStringArrayField(errors: string[], value: unknown, pathLabel: s
|
|
|
839
912
|
}
|
|
840
913
|
}
|
|
841
914
|
|
|
842
|
-
function validateAcceptanceReport(value: unknown, pathLabel = ""): { report?: AcceptanceReport; errors: string[] } {
|
|
915
|
+
export function validateAcceptanceReport(value: unknown, pathLabel = ""): { report?: AcceptanceReport; errors: string[] } {
|
|
843
916
|
const normalized = normalizeAcceptanceReportValue(value, pathLabel);
|
|
844
917
|
value = normalized.value;
|
|
845
918
|
pathLabel = normalized.pathLabel;
|
|
@@ -1272,7 +1345,7 @@ export async function evaluateAcceptance(input: {
|
|
|
1272
1345
|
* be misattributed). Searched for the acceptance report; searched before
|
|
1273
1346
|
* the assistant output when `authoritative` (outputMode "file-only").
|
|
1274
1347
|
*/
|
|
1275
|
-
fileOutput?: { content: string; path: string; authoritative?: boolean };
|
|
1348
|
+
fileOutput?: { content: string; path: string; authoritative?: boolean; durable?: boolean };
|
|
1276
1349
|
report?: AcceptanceReport;
|
|
1277
1350
|
reportError?: string;
|
|
1278
1351
|
reviewResult?: AcceptanceReviewResult;
|
|
@@ -1296,16 +1369,25 @@ export async function evaluateAcceptance(input: {
|
|
|
1296
1369
|
};
|
|
1297
1370
|
if (acceptance.level === "none") return ledger;
|
|
1298
1371
|
|
|
1299
|
-
const parsed = input.reportError
|
|
1372
|
+
const parsed: AcceptanceReportParseResult = input.reportError
|
|
1300
1373
|
? { error: input.reportError }
|
|
1301
|
-
: input.report
|
|
1374
|
+
: input.report !== undefined
|
|
1302
1375
|
? (() => {
|
|
1303
1376
|
const validation = validateAcceptanceReport(input.report);
|
|
1304
1377
|
return validation.report
|
|
1305
1378
|
? { report: validation.report }
|
|
1306
|
-
: { error: `Failed to parse acceptance-report: Invalid acceptance-report: ${validation.errors.join("; ")}
|
|
1379
|
+
: { error: `Failed to parse acceptance-report: Invalid acceptance-report: ${validation.errors.join("; ")}`, malformed: true };
|
|
1307
1380
|
})()
|
|
1308
1381
|
: parseAcceptanceReportSources(input.output, input.fileOutput);
|
|
1382
|
+
const durableFileOutput = input.fileOutput?.authoritative && input.fileOutput.durable ? input.fileOutput : undefined;
|
|
1383
|
+
if (parsed.malformed && parsed.sourcePath && durableFileOutput) {
|
|
1384
|
+
ledger.recovery = {
|
|
1385
|
+
status: "available-for-review",
|
|
1386
|
+
reason: "acceptance-metadata-rejected",
|
|
1387
|
+
reportPath: parsed.sourcePath,
|
|
1388
|
+
reportHash: hash(durableFileOutput.content),
|
|
1389
|
+
};
|
|
1390
|
+
}
|
|
1309
1391
|
const needsReport = acceptanceRequiresChildReport(acceptance);
|
|
1310
1392
|
if (parsed.report) {
|
|
1311
1393
|
ledger.childReport = parsed.report;
|
|
@@ -1314,6 +1396,10 @@ export async function evaluateAcceptance(input: {
|
|
|
1314
1396
|
} else if (!input.reportOptional || needsReport || parsed.error !== ACCEPTANCE_REPORT_NOT_FOUND) {
|
|
1315
1397
|
ledger.childReportParseError = parsed.error;
|
|
1316
1398
|
ledger.runtimeChecks.push({ id: "attestation", status: "failed", message: parsed.error ?? "Structured acceptance report missing." });
|
|
1399
|
+
if (ledger.recovery) {
|
|
1400
|
+
ledger.status = "rejected";
|
|
1401
|
+
ledger.evidenceStatus = "rejected";
|
|
1402
|
+
}
|
|
1317
1403
|
if (!input.reportOptional) {
|
|
1318
1404
|
ledger.status = "rejected";
|
|
1319
1405
|
ledger.evidenceStatus = "rejected";
|
|
@@ -50,7 +50,7 @@ const DYNAMIC_PARALLEL_KEYS = new Set(["agent", "task", "phase", "label", "outpu
|
|
|
50
50
|
const RUNNER_DYNAMIC_PARALLEL_KEYS = new Set([
|
|
51
51
|
...DYNAMIC_PARALLEL_KEYS,
|
|
52
52
|
"outputName", "structured", "inheritProjectContext", "inheritGlobalContext", "inheritSkills", "skills", "outputPath", "namespaceOutputPath", "maxSubagentDepth", "timeoutMs", "waitToolEnabled", "waitToolDefaultTimeoutMs",
|
|
53
|
-
"structuredOutput", "structuredOutputSchema", "tools", "allowNestedSubagents", "extensions", "subagentOnlyExtensions", "mcpDirectTools", "mutationTools", "capabilityCeiling", "completionGuard", "systemPrompt",
|
|
53
|
+
"structuredOutput", "structuredOutputSchema", "tools", "excludeTools", "allowNestedSubagents", "extensions", "subagentOnlyExtensions", "mcpDirectTools", "mutationTools", "capabilityCeiling", "completionGuard", "systemPrompt",
|
|
54
54
|
"systemPromptMode", "thinking", "modelCandidates", "sessionFile", "effectiveAcceptance", "acceptanceInput", "acceptanceRole", "parentSessionId", "launchResolvedExtensions", "requestedCwd",
|
|
55
55
|
]);
|
|
56
56
|
const DYNAMIC_COLLECT_KEYS = new Set(["as", "outputSchema"]);
|
|
@@ -144,6 +144,7 @@ export function validHostStepNodes(graph: WorkflowGraphSnapshot | undefined): Ho
|
|
|
144
144
|
export function assertWorkflowGraphHostSteps(graph: WorkflowGraphSnapshot | undefined, source = "status", expectedRunId?: string): void {
|
|
145
145
|
if (!graph) return;
|
|
146
146
|
if (expectedRunId !== undefined && graph.runId !== expectedRunId) throw new Error(`Invalid host step '${source}': workflowGraph.runId does not match the status run id.`);
|
|
147
|
+
if (!Array.isArray(graph.nodes)) throw new Error(`Invalid host step '${source}.workflowGraph': nodes must be an array.`);
|
|
147
148
|
const hostStepCount = graph.nodes.filter((node) => node.kind === "host-step").length;
|
|
148
149
|
if (hostStepCount > HOST_STEP_MAX_COUNT) throw new Error(`Invalid host step '${source}': workflowGraph contains more than ${HOST_STEP_MAX_COUNT} host steps.`);
|
|
149
150
|
const hostSteps: HostStepNodeV1[] = [];
|
|
@@ -321,15 +321,23 @@ export function resolveSubagentModelOverride(
|
|
|
321
321
|
const explicit = trimmed && trimmed !== INHERIT_MODEL ? trimmed : undefined;
|
|
322
322
|
if (!parentModel) throwForUnresolvedEnforcedInheritScope(options?.scope, explicit === undefined || options?.source === "inherited");
|
|
323
323
|
let resolved: string | undefined;
|
|
324
|
+
let resolvedFromRegistry = explicit === undefined;
|
|
324
325
|
if (explicit === undefined) {
|
|
325
326
|
resolved = parentModel ? `${parentModel.provider}/${parentModel.id}` : undefined;
|
|
326
327
|
} else {
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
328
|
+
const candidate = resolveSubagentModelCandidate(explicit, availableModels, preferredProvider);
|
|
329
|
+
if (options?.source === "explicit") {
|
|
330
|
+
resolved = candidate ?? resolveRequiredSubagentModelCandidate(explicit, availableModels, preferredProvider);
|
|
331
|
+
throwForExplicitModelExclusion(resolved);
|
|
332
|
+
resolvedFromRegistry = true;
|
|
333
|
+
} else if (candidate) {
|
|
334
|
+
resolved = candidate;
|
|
335
|
+
resolvedFromRegistry = true;
|
|
336
|
+
} else {
|
|
337
|
+
resolved = explicit;
|
|
338
|
+
}
|
|
331
339
|
}
|
|
332
|
-
if (resolved && options?.scope) {
|
|
340
|
+
if (resolved && options?.scope && resolvedFromRegistry) {
|
|
333
341
|
const source: ModelSource = explicit === undefined ? "inherited" : (options.source ?? "inherited");
|
|
334
342
|
enforceModelScopes(resolved, options.scope, source, options.onWarn);
|
|
335
343
|
}
|
|
@@ -362,12 +370,33 @@ export function resolveEffectiveSubagentModel(
|
|
|
362
370
|
);
|
|
363
371
|
}
|
|
364
372
|
|
|
373
|
+
export type ModelOrigin = ModelSource | "configured";
|
|
374
|
+
|
|
365
375
|
export interface BuildModelCandidatesOptions {
|
|
366
376
|
/** Fallback models warn by default and throw when strict scope enforcement is enabled. */
|
|
367
377
|
scope?: ModelScopeCheckRule | ModelScopeCheckRule[];
|
|
368
378
|
onWarn?: (violation: ModelScopeViolation) => void;
|
|
369
379
|
/** The primary model came from the running parent session, not configuration. */
|
|
370
380
|
primaryModelFromParent?: boolean;
|
|
381
|
+
/** How the primary model was selected. Explicit stays strict and does not rotate to fallbacks. */
|
|
382
|
+
origin?: ModelOrigin;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const ZERO_USABLE_MODEL_CANDIDATES_ERROR =
|
|
386
|
+
"No usable subagent models remain after registry, scope, and cached-exclusion filtering.";
|
|
387
|
+
|
|
388
|
+
export function resolveModelOrigin(input: {
|
|
389
|
+
explicitModel?: string | boolean;
|
|
390
|
+
agentModel?: string | boolean;
|
|
391
|
+
parentModel?: ParentModel;
|
|
392
|
+
fromParent?: boolean;
|
|
393
|
+
storedOrigin?: ModelOrigin;
|
|
394
|
+
}): ModelOrigin {
|
|
395
|
+
if (input.storedOrigin) return input.storedOrigin;
|
|
396
|
+
if (input.fromParent) return "inherited";
|
|
397
|
+
if (inheritsParentModel(input.explicitModel, input.agentModel, input.parentModel)) return "inherited";
|
|
398
|
+
const trimmed = typeof input.explicitModel === "string" ? input.explicitModel.trim() : "";
|
|
399
|
+
return trimmed && trimmed !== INHERIT_MODEL ? "explicit" : "configured";
|
|
371
400
|
}
|
|
372
401
|
|
|
373
402
|
export function inheritsParentModel(
|
|
@@ -388,36 +417,51 @@ export function buildModelCandidates(
|
|
|
388
417
|
options?: BuildModelCandidatesOptions,
|
|
389
418
|
): string[] {
|
|
390
419
|
if (!primaryModel) throwForUnresolvedEnforcedInheritScope(options?.scope, true);
|
|
420
|
+
const origin = options?.origin ?? (options?.primaryModelFromParent ? "inherited" : "configured");
|
|
421
|
+
const scopes = configuredScopes(options?.scope);
|
|
422
|
+
const warnCachedExclusion = (candidate: string, exclusion: NonNullable<ReturnType<typeof findModelExclusion>>) => {
|
|
423
|
+
const reason = redactSecretValues((exclusion.reason ?? "runtime-failure").replace(/[\u0000-\u001f\u007f]+/g, " ")).slice(0, 240);
|
|
424
|
+
console.warn(`[pi-subagents] Skipping model '${candidate}' due to a cached exclusion (reason: ${reason}; expires: ${new Date(exclusion.expiresAt).toISOString()}).`);
|
|
425
|
+
};
|
|
426
|
+
if (origin === "explicit" && primaryModel) {
|
|
427
|
+
const normalized = resolveRequiredSubagentModelCandidate(primaryModel.trim(), availableModels, preferredProvider);
|
|
428
|
+
throwForExplicitModelExclusion(normalized);
|
|
429
|
+
enforceModelScopes(normalized, scopes, "explicit", options?.onWarn);
|
|
430
|
+
primaryModel = normalized;
|
|
431
|
+
}
|
|
391
432
|
const seen = new Set<string>();
|
|
392
433
|
const candidates: string[] = [];
|
|
393
434
|
const rawCandidates = [primaryModel, ...(fallbackModels ?? [])];
|
|
435
|
+
let skippedPrimary: string | undefined;
|
|
394
436
|
for (let index = 0; index < rawCandidates.length; index++) {
|
|
395
437
|
const raw = rawCandidates[index];
|
|
396
438
|
if (!raw) continue;
|
|
397
439
|
const model = raw.trim();
|
|
398
|
-
const normalized = index === 0
|
|
399
|
-
?
|
|
400
|
-
? model
|
|
401
|
-
: resolveRequiredSubagentModelCandidate(model, availableModels, preferredProvider)
|
|
440
|
+
const normalized = index === 0 && (origin === "inherited" || origin === "explicit" || options?.primaryModelFromParent)
|
|
441
|
+
? model
|
|
402
442
|
: resolveSubagentModelCandidate(model, availableModels, preferredProvider);
|
|
403
443
|
if (!normalized) {
|
|
404
|
-
|
|
444
|
+
if (index === 0) skippedPrimary = model;
|
|
445
|
+
else console.warn(`[pi-subagents] Skipping fallback model '${model}' because it is unavailable in this environment.`);
|
|
405
446
|
continue;
|
|
406
447
|
}
|
|
407
448
|
if (seen.has(normalized)) continue;
|
|
408
|
-
const scopes = configuredScopes(options?.scope);
|
|
409
449
|
if (index > 0 || scopes.some((scope) => scope.enforce === true && scope.strict === true)) {
|
|
410
450
|
enforceModelScopes(normalized, scopes, "inherited", options?.onWarn);
|
|
411
451
|
}
|
|
412
452
|
seen.add(normalized);
|
|
413
453
|
candidates.push(normalized);
|
|
414
454
|
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
}
|
|
455
|
+
const resolved = filterFallbackCandidates(candidates, { onExcluded: warnCachedExclusion });
|
|
456
|
+
if (resolved.length === 0) {
|
|
457
|
+
if (skippedPrimary) resolveRequiredSubagentModelCandidate(skippedPrimary, availableModels, preferredProvider);
|
|
458
|
+
if (candidates.length > 0) throw new Error(ZERO_USABLE_MODEL_CANDIDATES_ERROR);
|
|
459
|
+
return resolved;
|
|
460
|
+
}
|
|
461
|
+
if (skippedPrimary) {
|
|
462
|
+
console.warn(`[pi-subagents] Skipping primary model '${skippedPrimary}' because it is unavailable in this environment.`);
|
|
463
|
+
}
|
|
464
|
+
return resolved;
|
|
421
465
|
}
|
|
422
466
|
|
|
423
467
|
const RETRYABLE_MODEL_FAILURE_PATTERNS = [
|
|
@@ -42,6 +42,7 @@ export interface RunnerSubagentStep {
|
|
|
42
42
|
skipPrimaryModelVerification?: boolean;
|
|
43
43
|
modelVerificationRegistry?: Array<{ provider: string; id: string; fullId: string; contextWindow?: number }>;
|
|
44
44
|
tools?: string[];
|
|
45
|
+
excludeTools?: string[];
|
|
45
46
|
allowNestedSubagents?: boolean;
|
|
46
47
|
extensions?: string[];
|
|
47
48
|
subagentOnlyExtensions?: string[];
|
|
@@ -68,12 +69,7 @@ export interface RunnerSubagentStep {
|
|
|
68
69
|
toolTimeoutMs?: number;
|
|
69
70
|
waitToolEnabled?: boolean;
|
|
70
71
|
waitToolDefaultTimeoutMs?: number;
|
|
71
|
-
structuredOutput?:
|
|
72
|
-
schema: import("../../shared/types.ts").JsonSchemaObject;
|
|
73
|
-
schemaPath: string;
|
|
74
|
-
outputPath: string;
|
|
75
|
-
acceptanceReportPath?: string;
|
|
76
|
-
};
|
|
72
|
+
structuredOutput?: import("./structured-output.ts").StructuredOutputRuntime;
|
|
77
73
|
structuredOutputSchema?: import("../../shared/types.ts").JsonSchemaObject;
|
|
78
74
|
agentContract?: import("../../shared/types.ts").AgentContract;
|
|
79
75
|
definitionDigest?: string;
|
|
@@ -7,7 +7,7 @@ export interface PermissionConfig { rules?: PermissionRules }
|
|
|
7
7
|
|
|
8
8
|
export const PERMISSION_POLICY_ENV = "PI_SUBAGENT_PERMISSION_POLICY";
|
|
9
9
|
export const PERMISSION_AUDIT_PATH_ENV = "PI_SUBAGENT_PERMISSION_AUDIT_PATH";
|
|
10
|
-
const INTERNAL_TOOLS = new Set(["contact_supervisor", "intercom", "
|
|
10
|
+
const INTERNAL_TOOLS = new Set(["contact_supervisor", "intercom", "bg_wait", "structured_output"]);
|
|
11
11
|
const DECISIONS = new Set<PermissionDecision>(["allow", "ask", "deny"]);
|
|
12
12
|
const MAX_POLICY_BYTES = 16 * 1024;
|
|
13
13
|
const MAX_PREVIEW_BYTES = 2048;
|
|
@@ -20,8 +20,10 @@ import { encodeExtensionBindings, PI_SUBAGENT_EXTENSION_BINDINGS_ENV, type Exten
|
|
|
20
20
|
import { RUNTIME_EXTENSION_ACK_PATH_ENV } from "./runtime-acknowledged-extensions.ts";
|
|
21
21
|
import {
|
|
22
22
|
STRUCTURED_OUTPUT_ACCEPTANCE_CAPTURE_ENV,
|
|
23
|
+
STRUCTURED_OUTPUT_ACCEPTANCE_REQUIRED_ENV,
|
|
23
24
|
STRUCTURED_OUTPUT_CAPTURE_ENV,
|
|
24
25
|
STRUCTURED_OUTPUT_SCHEMA_ENV,
|
|
26
|
+
type StructuredOutputRuntime,
|
|
25
27
|
} from "./structured-output.ts";
|
|
26
28
|
import {
|
|
27
29
|
TEMP_ROOT_DIR,
|
|
@@ -171,6 +173,7 @@ export interface BuildPiArgsInput {
|
|
|
171
173
|
inheritSkills: boolean;
|
|
172
174
|
requireReadTool?: boolean;
|
|
173
175
|
tools?: string[];
|
|
176
|
+
excludeTools?: string[];
|
|
174
177
|
extensions?: string[];
|
|
175
178
|
subagentOnlyExtensions?: string[];
|
|
176
179
|
systemPrompt?: string | null;
|
|
@@ -201,12 +204,7 @@ export interface BuildPiArgsInput {
|
|
|
201
204
|
steerInboxDir?: string;
|
|
202
205
|
steerCapabilityPath?: string;
|
|
203
206
|
steerAckDir?: string;
|
|
204
|
-
structuredOutput?:
|
|
205
|
-
schema: JsonSchemaObject;
|
|
206
|
-
schemaPath: string;
|
|
207
|
-
outputPath: string;
|
|
208
|
-
acceptanceReportPath?: string;
|
|
209
|
-
};
|
|
207
|
+
structuredOutput?: StructuredOutputRuntime;
|
|
210
208
|
fast?: boolean;
|
|
211
209
|
modelCandidates?: readonly string[];
|
|
212
210
|
toolBudget?: ResolvedToolBudget;
|
|
@@ -300,6 +298,7 @@ function resolveFastModeExtension(input: Pick<ResolvePiLaunchToolPlanInput, "fas
|
|
|
300
298
|
|
|
301
299
|
export interface ResolvePiLaunchToolPlanInput {
|
|
302
300
|
tools?: string[];
|
|
301
|
+
excludeTools?: string[];
|
|
303
302
|
allowNestedSubagents?: boolean;
|
|
304
303
|
extensions?: string[];
|
|
305
304
|
subagentOnlyExtensions?: string[];
|
|
@@ -329,6 +328,7 @@ export interface PiLaunchToolPlan {
|
|
|
329
328
|
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
330
329
|
requestedBuiltinTools: string[];
|
|
331
330
|
declaredBuiltinTools: string[];
|
|
331
|
+
excludeTools: string[];
|
|
332
332
|
toolExtensionPaths: string[];
|
|
333
333
|
resolvedMcpSelections: ResolvedMcpDirectToolSelection[];
|
|
334
334
|
effectiveMcpSelections: ResolvedMcpDirectToolSelection[];
|
|
@@ -499,8 +499,12 @@ export function resolvePiLaunchToolPlan(
|
|
|
499
499
|
? ["read", ...requestedBuiltinTools]
|
|
500
500
|
: requestedBuiltinTools
|
|
501
501
|
).filter((tool) => !allowedToolSet || allowedToolSet.has(tool));
|
|
502
|
-
const
|
|
502
|
+
const excludeTools = [...new Set((input.excludeTools ?? []).map((tool) => tool.trim()).filter(Boolean))];
|
|
503
|
+
const excludedToolSet = new Set(excludeTools);
|
|
504
|
+
const effectiveDeclaredBuiltinTools = declaredBuiltinTools.filter((tool) => !excludedToolSet.has(tool));
|
|
505
|
+
const fanoutAuthorized = effectiveDeclaredBuiltinTools.includes("subagent") || (
|
|
503
506
|
input.allowNestedSubagents === true &&
|
|
507
|
+
!excludedToolSet.has("subagent") &&
|
|
504
508
|
(!allowedToolSet || allowedToolSet.has("subagent"))
|
|
505
509
|
);
|
|
506
510
|
const toolExtensionPaths: string[] = capabilityCeiling?.denyExtensions
|
|
@@ -524,7 +528,7 @@ export function resolvePiLaunchToolPlan(
|
|
|
524
528
|
!allowedToolSet ||
|
|
525
529
|
allowedToolSet.has(selection.name) ||
|
|
526
530
|
isLegacyUnderscoreMcpToolAllowed(selection, allowedToolSet, resolvedMcpNames, legacyMcpNameCounts),
|
|
527
|
-
);
|
|
531
|
+
).filter((selection) => !excludedToolSet.has(selection.name));
|
|
528
532
|
const effectiveMcpTools = effectiveMcpSelections.map(
|
|
529
533
|
(selection) => selection.name,
|
|
530
534
|
);
|
|
@@ -540,10 +544,10 @@ export function resolvePiLaunchToolPlan(
|
|
|
540
544
|
input.tools !== undefined ||
|
|
541
545
|
(input.mcpDirectTools?.length ?? 0) > 0 ||
|
|
542
546
|
allowedToolSet !== undefined;
|
|
543
|
-
const internalTools = input.structuredOutput ? ["structured_output"] : [];
|
|
547
|
+
const internalTools = (input.structuredOutput ? ["structured_output"] : []).filter((tool) => !excludedToolSet.has(tool));
|
|
544
548
|
const effectiveToolAllowlist = [
|
|
545
549
|
...new Set([
|
|
546
|
-
...
|
|
550
|
+
...effectiveDeclaredBuiltinTools,
|
|
547
551
|
...effectiveMcpTools,
|
|
548
552
|
...internalTools,
|
|
549
553
|
]),
|
|
@@ -554,11 +558,11 @@ export function resolvePiLaunchToolPlan(
|
|
|
554
558
|
// appended intercom alongside contact_supervisor, so that exact pairing is
|
|
555
559
|
// legacy plumbing, not a user demand for an external intercom provider;
|
|
556
560
|
// a lone intercom entry stays strictly required (#1207).
|
|
557
|
-
const legacySupervisorPairing =
|
|
561
|
+
const legacySupervisorPairing = effectiveDeclaredBuiltinTools.includes("contact_supervisor");
|
|
558
562
|
const requiredChildTools = explicitToolAllowlist
|
|
559
563
|
? [
|
|
560
564
|
...new Set([
|
|
561
|
-
...(input.tools !== undefined ?
|
|
565
|
+
...(input.tools !== undefined ? effectiveDeclaredBuiltinTools : []),
|
|
562
566
|
...(input.mcpDirectTools?.length ? effectiveMcpTools : []),
|
|
563
567
|
...internalTools,
|
|
564
568
|
].filter((tool) => tool !== "contact_supervisor" && (!legacySupervisorPairing || tool !== "intercom"))),
|
|
@@ -620,6 +624,7 @@ export function resolvePiLaunchToolPlan(
|
|
|
620
624
|
ceiling: capabilityCeiling,
|
|
621
625
|
...(requestedToolNames ? { requestedTools: requestedToolNames } : {}),
|
|
622
626
|
effectiveTools: effectiveToolAllowlist,
|
|
627
|
+
...(excludeTools.length > 0 ? { excludeTools } : {}),
|
|
623
628
|
removedTools:
|
|
624
629
|
requestedToolNames?.filter(
|
|
625
630
|
(tool) => !effectiveToolAllowlist.includes(tool),
|
|
@@ -657,6 +662,7 @@ export function resolvePiLaunchToolPlan(
|
|
|
657
662
|
...(capabilityCeiling ? { capabilityCeiling } : {}),
|
|
658
663
|
requestedBuiltinTools,
|
|
659
664
|
declaredBuiltinTools,
|
|
665
|
+
excludeTools,
|
|
660
666
|
toolExtensionPaths,
|
|
661
667
|
resolvedMcpSelections,
|
|
662
668
|
effectiveMcpSelections,
|
|
@@ -740,6 +746,7 @@ export function buildPiArgs(input: BuildPiArgsInput): BuildPiArgsResult {
|
|
|
740
746
|
|
|
741
747
|
const toolPlan = resolvePiLaunchToolPlan({
|
|
742
748
|
tools: input.tools,
|
|
749
|
+
excludeTools: input.excludeTools,
|
|
743
750
|
allowNestedSubagents: input.allowNestedSubagents,
|
|
744
751
|
extensions: input.extensions,
|
|
745
752
|
subagentOnlyExtensions: input.subagentOnlyExtensions,
|
|
@@ -766,6 +773,8 @@ export function buildPiArgs(input: BuildPiArgsInput): BuildPiArgsResult {
|
|
|
766
773
|
);
|
|
767
774
|
if (toolPlan.effectiveToolAllowlist.length > 0)
|
|
768
775
|
args.push(toolPlan.effectiveToolAllowlist.join(","));
|
|
776
|
+
} else if (toolPlan.excludeTools.length > 0) {
|
|
777
|
+
args.push("--exclude-tools", toolPlan.excludeTools.join(","));
|
|
769
778
|
}
|
|
770
779
|
if (toolPlan.disableAmbientExtensions) {
|
|
771
780
|
args.push("--no-extensions");
|
|
@@ -999,10 +1008,13 @@ export function buildPiArgs(input: BuildPiArgsInput): BuildPiArgsResult {
|
|
|
999
1008
|
env[SUBAGENT_CAPABILITY_CEILING_ENV] = encodedCapabilityCeiling;
|
|
1000
1009
|
if (encodedPermissionRules)
|
|
1001
1010
|
env[PERMISSION_POLICY_ENV] = encodedPermissionRules;
|
|
1011
|
+
env[STRUCTURED_OUTPUT_ACCEPTANCE_CAPTURE_ENV] = undefined;
|
|
1012
|
+
env[STRUCTURED_OUTPUT_ACCEPTANCE_REQUIRED_ENV] = undefined;
|
|
1002
1013
|
if (input.structuredOutput) {
|
|
1003
1014
|
env[STRUCTURED_OUTPUT_CAPTURE_ENV] = input.structuredOutput.outputPath;
|
|
1004
1015
|
env[STRUCTURED_OUTPUT_SCHEMA_ENV] = input.structuredOutput.schemaPath;
|
|
1005
1016
|
if (input.structuredOutput.acceptanceReportPath) env[STRUCTURED_OUTPUT_ACCEPTANCE_CAPTURE_ENV] = input.structuredOutput.acceptanceReportPath;
|
|
1017
|
+
if (input.structuredOutput.acceptanceReportRequired) env[STRUCTURED_OUTPUT_ACCEPTANCE_REQUIRED_ENV] = "1";
|
|
1006
1018
|
}
|
|
1007
1019
|
if (input.steerInboxDir) {
|
|
1008
1020
|
env[SUBAGENT_STEER_INBOX_ENV] = input.steerInboxDir;
|