pi-subagents 0.55.0 → 0.56.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 +28 -3
- package/docs/models.md +6 -0
- package/package.json +1 -1
- package/src/agents/agent-serializer.ts +2 -0
- package/src/agents/agents.ts +23 -1
- package/src/api/preflight.ts +20 -1
- package/src/extension/schemas.ts +5 -0
- package/src/runs/background/async-execution.ts +48 -18
- package/src/runs/background/async-resume.ts +3 -0
- package/src/runs/background/subagent-runner.ts +83 -11
- package/src/runs/foreground/execution.ts +35 -6
- package/src/runs/foreground/foreground-history.ts +3 -2
- package/src/runs/foreground/subagent-executor.ts +59 -8
- package/src/runs/foreground/workflow-detach-reconcile.ts +15 -6
- package/src/runs/shared/acceptance.ts +10 -5
- package/src/runs/shared/agent-contract.ts +1 -1
- package/src/runs/shared/completion-guard.ts +3 -2
- package/src/runs/shared/dynamic-fanout.ts +1 -1
- package/src/runs/shared/extension-bindings.ts +78 -0
- package/src/runs/shared/external-cli-runner.ts +2 -0
- package/src/runs/shared/fast-mode-extension.ts +10 -0
- package/src/runs/shared/model-exclusions.ts +2 -1
- package/src/runs/shared/model-fallback.ts +2 -7
- package/src/runs/shared/mutation-evidence.ts +145 -0
- package/src/runs/shared/parallel-utils.ts +3 -0
- package/src/runs/shared/pi-args.ts +47 -0
- package/src/runs/shared/structured-output.ts +18 -4
- package/src/runs/shared/subagent-prompt-runtime.ts +9 -5
- package/src/shared/launch-contract.ts +6 -0
- package/src/shared/settings.ts +6 -1
- package/src/shared/types.ts +48 -0
- package/src/workflows/scripted-workflow.ts +50 -3
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import * as fs from "node:fs";
|
|
4
|
+
import * as os from "node:os";
|
|
5
|
+
import * as path from "node:path";
|
|
6
|
+
import type { ArtifactPaths, TimeoutRecoverySummary, TrackedMutationEvidence, TrackedMutationFingerprint, TrackedMutationSnapshot } from "../../shared/types.ts";
|
|
7
|
+
|
|
8
|
+
const MAX_TRACKED_PATHS = 500;
|
|
9
|
+
const MAX_HASH_BYTES = 1024 * 1024;
|
|
10
|
+
const MAX_TIMEOUT_FILES = 20;
|
|
11
|
+
|
|
12
|
+
function gitOutput(cwd: string, args: string[], maxBuffer = MAX_HASH_BYTES): string {
|
|
13
|
+
return execFileSync("git", args, { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"], maxBuffer });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function splitNul(output: string): string[] {
|
|
17
|
+
return output.split("\0").filter((part) => part.length > 0);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function hashLargeDiff(cwd: string, relativePath: string): string {
|
|
21
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagents-tracked-diff-"));
|
|
22
|
+
const diffPath = path.join(tempDir, "diff.patch");
|
|
23
|
+
try {
|
|
24
|
+
execFileSync("git", ["diff", "--no-ext-diff", "--binary", `--output=${diffPath}`, "HEAD", "--", relativePath], { cwd, stdio: "ignore" });
|
|
25
|
+
const hash = createHash("sha256");
|
|
26
|
+
const buffer = Buffer.allocUnsafe(64 * 1024);
|
|
27
|
+
const fd = fs.openSync(diffPath, "r");
|
|
28
|
+
try {
|
|
29
|
+
for (;;) {
|
|
30
|
+
const bytesRead = fs.readSync(fd, buffer, 0, buffer.length, null);
|
|
31
|
+
if (bytesRead === 0) break;
|
|
32
|
+
hash.update(buffer.subarray(0, bytesRead));
|
|
33
|
+
}
|
|
34
|
+
} finally {
|
|
35
|
+
fs.closeSync(fd);
|
|
36
|
+
}
|
|
37
|
+
return hash.digest("hex");
|
|
38
|
+
} finally {
|
|
39
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function listChangedTrackedFiles(cwd: string): { paths: string[]; truncated: boolean } {
|
|
44
|
+
const paths = splitNul(gitOutput(cwd, ["diff", "--no-ext-diff", "--name-only", "-z", "HEAD", "--"], MAX_HASH_BYTES));
|
|
45
|
+
return { paths: paths.slice(0, MAX_TRACKED_PATHS), truncated: paths.length > MAX_TRACKED_PATHS };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function fingerprintPath(cwd: string, relativePath: string): TrackedMutationFingerprint {
|
|
49
|
+
try {
|
|
50
|
+
const diff = gitOutput(cwd, ["diff", "--no-ext-diff", "--binary", "HEAD", "--", relativePath], MAX_HASH_BYTES);
|
|
51
|
+
return { kind: "diff", digest: createHash("sha256").update(diff).digest("hex") };
|
|
52
|
+
} catch {
|
|
53
|
+
return { kind: "diff", digest: hashLargeDiff(cwd, relativePath) };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function sameFingerprint(left: TrackedMutationFingerprint | undefined, right: TrackedMutationFingerprint): boolean {
|
|
58
|
+
if (!left || left.kind !== "diff" || right.kind !== "diff") return false;
|
|
59
|
+
return left.digest === right.digest;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function snapshotTrackedMutations(cwd: string): TrackedMutationSnapshot {
|
|
63
|
+
try {
|
|
64
|
+
const changed = listChangedTrackedFiles(cwd);
|
|
65
|
+
const fingerprints: Record<string, TrackedMutationFingerprint> = {};
|
|
66
|
+
for (const file of changed.paths) fingerprints[file] = fingerprintPath(cwd, file);
|
|
67
|
+
return { source: "tracked-files", trackedOnly: true, cwd, dirtyFiles: changed.paths, fingerprints, truncated: changed.truncated };
|
|
68
|
+
} catch (error) {
|
|
69
|
+
return { source: "tracked-files", trackedOnly: true, cwd, dirtyFiles: [], fingerprints: {}, unavailable: error instanceof Error ? error.message : String(error) };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function collectTrackedMutationEvidence(snapshot: TrackedMutationSnapshot, cwd = snapshot.cwd): TrackedMutationEvidence {
|
|
74
|
+
if (snapshot.unavailable) {
|
|
75
|
+
return { source: "tracked-files", trackedOnly: true, changedFiles: [], attemptedMutation: false, unavailable: snapshot.unavailable };
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
const current = listChangedTrackedFiles(cwd);
|
|
79
|
+
const startDirty = new Set(snapshot.dirtyFiles);
|
|
80
|
+
const candidates = new Set([...snapshot.dirtyFiles, ...current.paths]);
|
|
81
|
+
const changedFiles: string[] = [];
|
|
82
|
+
for (const file of candidates) {
|
|
83
|
+
if (!startDirty.has(file)) {
|
|
84
|
+
if (!snapshot.truncated) changedFiles.push(file);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (!sameFingerprint(snapshot.fingerprints[file], fingerprintPath(cwd, file))) changedFiles.push(file);
|
|
88
|
+
}
|
|
89
|
+
changedFiles.sort();
|
|
90
|
+
return {
|
|
91
|
+
source: "tracked-files",
|
|
92
|
+
trackedOnly: true,
|
|
93
|
+
changedFiles,
|
|
94
|
+
attemptedMutation: changedFiles.length > 0,
|
|
95
|
+
truncated: snapshot.truncated || current.truncated || undefined,
|
|
96
|
+
};
|
|
97
|
+
} catch (error) {
|
|
98
|
+
return { source: "tracked-files", trackedOnly: true, changedFiles: [], attemptedMutation: false, unavailable: error instanceof Error ? error.message : String(error) };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function formatPathList(paths: string[]): string {
|
|
103
|
+
if (paths.length === 0) return "none";
|
|
104
|
+
const shown = paths.slice(0, MAX_TIMEOUT_FILES).join(", ");
|
|
105
|
+
return paths.length > MAX_TIMEOUT_FILES ? `${shown}, ... (${paths.length - MAX_TIMEOUT_FILES} more)` : shown;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function buildTimeoutRecoverySummary(input: {
|
|
109
|
+
termination: "timed-out" | "stopped";
|
|
110
|
+
evidence: TrackedMutationEvidence;
|
|
111
|
+
currentTool?: string;
|
|
112
|
+
currentToolArgs?: string;
|
|
113
|
+
currentPath?: string;
|
|
114
|
+
sessionFile?: string;
|
|
115
|
+
transcriptPath?: string;
|
|
116
|
+
artifactPaths?: ArtifactPaths;
|
|
117
|
+
}): TimeoutRecoverySummary {
|
|
118
|
+
const warning = "Inspect partial changes before retrying or resuming the child.";
|
|
119
|
+
const changedFiles = input.evidence.changedFiles.slice(0, MAX_TIMEOUT_FILES);
|
|
120
|
+
const lines = [
|
|
121
|
+
"Recovery summary:",
|
|
122
|
+
`- termination: ${input.termination}`,
|
|
123
|
+
`- changed tracked files: ${input.evidence.unavailable ? `unavailable (${input.evidence.unavailable})` : formatPathList(input.evidence.changedFiles)}`,
|
|
124
|
+
];
|
|
125
|
+
if (input.currentTool) lines.push(`- active tool: ${input.currentTool}${input.currentToolArgs ? ` — ${input.currentToolArgs}` : ""}`);
|
|
126
|
+
if (input.currentPath) lines.push(`- active path: ${input.currentPath}`);
|
|
127
|
+
if (input.sessionFile) lines.push(`- session file: ${input.sessionFile}`);
|
|
128
|
+
if (input.transcriptPath) lines.push(`- transcript: ${input.transcriptPath}`);
|
|
129
|
+
if (input.artifactPaths?.outputPath) lines.push(`- output artifact: ${input.artifactPaths.outputPath}`);
|
|
130
|
+
if (input.artifactPaths?.metadataPath) lines.push(`- metadata artifact: ${input.artifactPaths.metadataPath}`);
|
|
131
|
+
lines.push(`Warning: ${warning}`);
|
|
132
|
+
return {
|
|
133
|
+
termination: input.termination,
|
|
134
|
+
changedFiles,
|
|
135
|
+
truncated: input.evidence.changedFiles.length > changedFiles.length || input.evidence.truncated || undefined,
|
|
136
|
+
currentTool: input.currentTool,
|
|
137
|
+
currentToolArgs: input.currentToolArgs,
|
|
138
|
+
currentPath: input.currentPath,
|
|
139
|
+
sessionFile: input.sessionFile,
|
|
140
|
+
transcriptPath: input.transcriptPath,
|
|
141
|
+
artifactPaths: input.artifactPaths,
|
|
142
|
+
warning,
|
|
143
|
+
message: lines.join("\n"),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
@@ -29,6 +29,7 @@ export interface RunnerSubagentStep {
|
|
|
29
29
|
structured?: boolean;
|
|
30
30
|
cwd?: string;
|
|
31
31
|
model?: string;
|
|
32
|
+
fast?: boolean;
|
|
32
33
|
thinking?: string;
|
|
33
34
|
thinkingCeiling?: import("../../shared/model-info.ts").ThinkingLevel;
|
|
34
35
|
modelCandidates?: string[];
|
|
@@ -59,12 +60,14 @@ export interface RunnerSubagentStep {
|
|
|
59
60
|
schema: import("../../shared/types.ts").JsonSchemaObject;
|
|
60
61
|
schemaPath: string;
|
|
61
62
|
outputPath: string;
|
|
63
|
+
acceptanceReportPath?: string;
|
|
62
64
|
};
|
|
63
65
|
structuredOutputSchema?: import("../../shared/types.ts").JsonSchemaObject;
|
|
64
66
|
agentContract?: import("../../shared/types.ts").AgentContract;
|
|
65
67
|
definitionDigest?: string;
|
|
66
68
|
launchBindingTask?: string;
|
|
67
69
|
launchContractDigest?: string;
|
|
70
|
+
extensionBindings?: import("./extension-bindings.ts").ExtensionBindings;
|
|
68
71
|
launchResolvedExtensions?: import("../../shared/types.ts").LaunchResolvedChildExtensionsV1;
|
|
69
72
|
runtimeAcknowledgedExtensions?: import("../../shared/types.ts").RuntimeAcknowledgedChildExtensionsV1;
|
|
70
73
|
effectiveAcceptance?: import("../../shared/types.ts").ResolvedAcceptanceConfig;
|
|
@@ -13,8 +13,10 @@ import {
|
|
|
13
13
|
type ResolvedMcpDirectToolSelection,
|
|
14
14
|
} from "./mcp-direct-tool-allowlist.ts";
|
|
15
15
|
import { resolvePiPackageRoot } from "./pi-spawn.ts";
|
|
16
|
+
import { encodeExtensionBindings, PI_SUBAGENT_EXTENSION_BINDINGS_ENV, type ExtensionBindings } from "./extension-bindings.ts";
|
|
16
17
|
import { RUNTIME_EXTENSION_ACK_PATH_ENV } from "./runtime-acknowledged-extensions.ts";
|
|
17
18
|
import {
|
|
19
|
+
STRUCTURED_OUTPUT_ACCEPTANCE_CAPTURE_ENV,
|
|
18
20
|
STRUCTURED_OUTPUT_CAPTURE_ENV,
|
|
19
21
|
STRUCTURED_OUTPUT_SCHEMA_ENV,
|
|
20
22
|
} from "./structured-output.ts";
|
|
@@ -104,6 +106,14 @@ const FANOUT_CHILD_EXTENSION_PATH = path.join(
|
|
|
104
106
|
"extension",
|
|
105
107
|
"fanout-child.ts",
|
|
106
108
|
);
|
|
109
|
+
const FAST_MODE_EXTENSION_PATH = path.join(
|
|
110
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
111
|
+
"fast-mode-extension.ts",
|
|
112
|
+
);
|
|
113
|
+
const FAST_MODE_ALLOWED_MODELS = new Set([
|
|
114
|
+
"openai-codex/gpt-5.6-luna",
|
|
115
|
+
"openai-codex/gpt-5.6-sol",
|
|
116
|
+
]);
|
|
107
117
|
export const SUBAGENT_CHILD_ENV = "PI_SUBAGENT_CHILD";
|
|
108
118
|
export const SUBAGENT_ORCHESTRATOR_TARGET_ENV =
|
|
109
119
|
"PI_SUBAGENT_ORCHESTRATOR_TARGET";
|
|
@@ -173,7 +183,10 @@ export interface BuildPiArgsInput {
|
|
|
173
183
|
schema: JsonSchemaObject;
|
|
174
184
|
schemaPath: string;
|
|
175
185
|
outputPath: string;
|
|
186
|
+
acceptanceReportPath?: string;
|
|
176
187
|
};
|
|
188
|
+
fast?: boolean;
|
|
189
|
+
modelCandidates?: readonly string[];
|
|
177
190
|
toolBudget?: ResolvedToolBudget;
|
|
178
191
|
allowZeroToolBudget?: boolean;
|
|
179
192
|
permissionRules?: PermissionRules;
|
|
@@ -188,6 +201,7 @@ export interface BuildPiArgsInput {
|
|
|
188
201
|
waitToolEnabled?: boolean;
|
|
189
202
|
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
190
203
|
thinkingCeiling?: import("../../shared/model-info.ts").ThinkingLevel;
|
|
204
|
+
extensionBindings?: ExtensionBindings;
|
|
191
205
|
}
|
|
192
206
|
|
|
193
207
|
export interface BuildPiArgsResult {
|
|
@@ -237,6 +251,28 @@ export function applyThinkingSuffix(
|
|
|
237
251
|
return `${model}:${thinking}`;
|
|
238
252
|
}
|
|
239
253
|
|
|
254
|
+
function stripThinkingSuffix(model: string): string {
|
|
255
|
+
const colonIdx = model.lastIndexOf(":");
|
|
256
|
+
if (colonIdx === -1) return model;
|
|
257
|
+
return THINKING_LEVELS.some((level) => level === model.substring(colonIdx + 1))
|
|
258
|
+
? model.slice(0, colonIdx)
|
|
259
|
+
: model;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function resolveFastModeExtension(input: Pick<ResolvePiLaunchToolPlanInput, "fast" | "model" | "modelCandidates" | "agentName">): string[] {
|
|
263
|
+
if (!input.fast) return [];
|
|
264
|
+
const candidates = (input.modelCandidates?.length ? input.modelCandidates : input.model ? [input.model] : [])
|
|
265
|
+
.map(stripThinkingSuffix);
|
|
266
|
+
if (candidates.length === 0) {
|
|
267
|
+
throw new Error(`fast mode requires an explicit supported native OpenAI-Codex model${input.agentName ? ` for agent '${input.agentName}'` : ""}.`);
|
|
268
|
+
}
|
|
269
|
+
const unsupported = candidates.filter((model) => !FAST_MODE_ALLOWED_MODELS.has(model));
|
|
270
|
+
if (unsupported.length > 0) {
|
|
271
|
+
throw new Error(`fast mode supports only ${[...FAST_MODE_ALLOWED_MODELS].join(", ")}; unsupported model${unsupported.length === 1 ? "" : "s"}: ${unsupported.join(", ")}.`);
|
|
272
|
+
}
|
|
273
|
+
return [FAST_MODE_EXTENSION_PATH];
|
|
274
|
+
}
|
|
275
|
+
|
|
240
276
|
export interface ResolvePiLaunchToolPlanInput {
|
|
241
277
|
tools?: string[];
|
|
242
278
|
extensions?: string[];
|
|
@@ -251,6 +287,9 @@ export interface ResolvePiLaunchToolPlanInput {
|
|
|
251
287
|
schemaPath: string;
|
|
252
288
|
outputPath: string;
|
|
253
289
|
};
|
|
290
|
+
fast?: boolean;
|
|
291
|
+
model?: string;
|
|
292
|
+
modelCandidates?: readonly string[];
|
|
254
293
|
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
255
294
|
inheritedCapabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
256
295
|
agentName?: string;
|
|
@@ -464,8 +503,11 @@ export function resolvePiLaunchToolPlan(
|
|
|
464
503
|
: hasPermissionRules(input.permissionRules)
|
|
465
504
|
? resolvePermissionSystemExtension()
|
|
466
505
|
: undefined;
|
|
506
|
+
if (input.fast && capabilityCeiling?.denyExtensions) throw new Error("fast mode requires a child runtime extension, but this launch denies extensions.");
|
|
507
|
+
const fastModeExtensions = resolveFastModeExtension({ fast: input.fast, model: input.model, modelCandidates: input.modelCandidates, agentName: input.agentName });
|
|
467
508
|
const runtimeExtensions = [
|
|
468
509
|
PROMPT_RUNTIME_EXTENSION_PATH,
|
|
510
|
+
...fastModeExtensions,
|
|
469
511
|
...(fanoutAuthorized ? [FANOUT_CHILD_EXTENSION_PATH] : []),
|
|
470
512
|
...(permSystemExt ? [permSystemExt] : []),
|
|
471
513
|
];
|
|
@@ -605,6 +647,9 @@ export function buildPiArgs(input: BuildPiArgsInput): BuildPiArgsResult {
|
|
|
605
647
|
cwd: input.cwd,
|
|
606
648
|
requireReadTool: input.requireReadTool,
|
|
607
649
|
structuredOutput: input.structuredOutput,
|
|
650
|
+
fast: input.fast,
|
|
651
|
+
model: modelArg,
|
|
652
|
+
modelCandidates: input.modelCandidates,
|
|
608
653
|
capabilityCeiling: input.capabilityCeiling,
|
|
609
654
|
inheritedCapabilityCeiling: decodeSubagentCapabilityCeiling(
|
|
610
655
|
process.env[SUBAGENT_CAPABILITY_CEILING_ENV],
|
|
@@ -668,6 +713,7 @@ export function buildPiArgs(input: BuildPiArgsInput): BuildPiArgsResult {
|
|
|
668
713
|
}
|
|
669
714
|
|
|
670
715
|
const env: Record<string, string | undefined> = {};
|
|
716
|
+
env[PI_SUBAGENT_EXTENSION_BINDINGS_ENV] = encodeExtensionBindings(input.extensionBindings);
|
|
671
717
|
const piPackageRoot =
|
|
672
718
|
process.env[PI_CODING_AGENT_PACKAGE_ROOT_ENV] ?? resolvePiPackageRoot();
|
|
673
719
|
if (piPackageRoot) env[PI_CODING_AGENT_PACKAGE_ROOT_ENV] = piPackageRoot;
|
|
@@ -838,6 +884,7 @@ export function buildPiArgs(input: BuildPiArgsInput): BuildPiArgsResult {
|
|
|
838
884
|
if (input.structuredOutput) {
|
|
839
885
|
env[STRUCTURED_OUTPUT_CAPTURE_ENV] = input.structuredOutput.outputPath;
|
|
840
886
|
env[STRUCTURED_OUTPUT_SCHEMA_ENV] = input.structuredOutput.schemaPath;
|
|
887
|
+
if (input.structuredOutput.acceptanceReportPath) env[STRUCTURED_OUTPUT_ACCEPTANCE_CAPTURE_ENV] = input.structuredOutput.acceptanceReportPath;
|
|
841
888
|
}
|
|
842
889
|
if (input.steerInboxDir) {
|
|
843
890
|
env[SUBAGENT_STEER_INBOX_ENV] = input.steerInboxDir;
|
|
@@ -8,12 +8,14 @@ import type { JsonSchemaObject } from "../../shared/types.ts";
|
|
|
8
8
|
|
|
9
9
|
export const STRUCTURED_OUTPUT_SCHEMA_ENV = "PI_SUBAGENT_STRUCTURED_OUTPUT_SCHEMA";
|
|
10
10
|
export const STRUCTURED_OUTPUT_CAPTURE_ENV = "PI_SUBAGENT_STRUCTURED_OUTPUT_CAPTURE";
|
|
11
|
+
export const STRUCTURED_OUTPUT_ACCEPTANCE_CAPTURE_ENV = "PI_SUBAGENT_STRUCTURED_OUTPUT_ACCEPTANCE_CAPTURE";
|
|
11
12
|
export const MISSING_STRUCTURED_OUTPUT_CALL_ERROR = "Missing structured_output call; this step has outputSchema and must finish by calling structured_output.";
|
|
12
13
|
|
|
13
14
|
export interface StructuredOutputRuntime {
|
|
14
15
|
schema: JsonSchemaObject;
|
|
15
16
|
schemaPath: string;
|
|
16
17
|
outputPath: string;
|
|
18
|
+
acceptanceReportPath?: string;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
const SCHEMA_MAP_KEYWORDS = ["properties", "patternProperties", "$defs", "definitions", "dependentSchemas"] as const;
|
|
@@ -59,10 +61,13 @@ function rewriteLocalJsonPointerRefs(schema: unknown, pointerPrefix: string, inh
|
|
|
59
61
|
return rewritten;
|
|
60
62
|
}
|
|
61
63
|
|
|
62
|
-
export function createStructuredOutputToolParameters(schema: JsonSchemaObject): JsonSchemaObject {
|
|
64
|
+
export function createStructuredOutputToolParameters(schema: JsonSchemaObject, options: { acceptanceReport?: boolean } = {}): JsonSchemaObject {
|
|
63
65
|
return {
|
|
64
66
|
type: "object",
|
|
65
|
-
properties: {
|
|
67
|
+
properties: {
|
|
68
|
+
value: rewriteLocalJsonPointerRefs(schema, "#/properties/value"),
|
|
69
|
+
...(options.acceptanceReport ? { acceptanceReport: { type: "object" } } : {}),
|
|
70
|
+
},
|
|
66
71
|
required: ["value"],
|
|
67
72
|
additionalProperties: false,
|
|
68
73
|
};
|
|
@@ -124,7 +129,7 @@ export function assertJsonSchemaObject(schema: unknown, label = "outputSchema"):
|
|
|
124
129
|
}
|
|
125
130
|
}
|
|
126
131
|
|
|
127
|
-
export function createStructuredOutputRuntime(schema: JsonSchemaObject, baseDir?: string): StructuredOutputRuntime {
|
|
132
|
+
export function createStructuredOutputRuntime(schema: JsonSchemaObject, baseDir?: string, options: { captureAcceptanceReport?: boolean } = {}): StructuredOutputRuntime {
|
|
128
133
|
assertJsonSchemaObject(schema);
|
|
129
134
|
const rootDir = baseDir ?? os.tmpdir();
|
|
130
135
|
fs.mkdirSync(rootDir, { recursive: true });
|
|
@@ -132,7 +137,7 @@ export function createStructuredOutputRuntime(schema: JsonSchemaObject, baseDir?
|
|
|
132
137
|
const schemaPath = path.join(dir, "schema.json");
|
|
133
138
|
const outputPath = path.join(dir, "output.json");
|
|
134
139
|
fs.writeFileSync(schemaPath, JSON.stringify(schema), { mode: 0o600 });
|
|
135
|
-
return { schema, schemaPath, outputPath };
|
|
140
|
+
return { schema, schemaPath, outputPath, ...(options.captureAcceptanceReport ? { acceptanceReportPath: path.join(dir, "acceptance-report.json") } : {}) };
|
|
136
141
|
}
|
|
137
142
|
|
|
138
143
|
export async function validateStructuredOutputValue(schema: JsonSchemaObject, value: unknown): Promise<{ status: "valid" } | { status: "invalid"; message: string }> {
|
|
@@ -172,6 +177,15 @@ export async function readStructuredOutput(runtime: StructuredOutputRuntime): Pr
|
|
|
172
177
|
return { value };
|
|
173
178
|
}
|
|
174
179
|
|
|
180
|
+
export function readStructuredOutputAcceptanceReport(runtime: StructuredOutputRuntime): { value?: unknown; error?: string } {
|
|
181
|
+
if (!runtime.acceptanceReportPath || !fs.existsSync(runtime.acceptanceReportPath)) return {};
|
|
182
|
+
try {
|
|
183
|
+
return { value: JSON.parse(fs.readFileSync(runtime.acceptanceReportPath, "utf-8")) };
|
|
184
|
+
} catch (error) {
|
|
185
|
+
return { error: `Failed to read structured output acceptance report: ${error instanceof Error ? error.message : String(error)}` };
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
175
189
|
export function cleanupStructuredOutputRuntime(runtime: StructuredOutputRuntime | undefined): void {
|
|
176
190
|
if (!runtime) return;
|
|
177
191
|
try {
|
|
@@ -8,7 +8,7 @@ import { decodePermissionRules, permissionDecision, PERMISSION_AUDIT_PATH_ENV, P
|
|
|
8
8
|
import { consumeSteerRequestsFromDir, MAX_STEER_QUEUE_SIZE, steerAckPathFromDir, writeSteerAckAt, writeSteerCapabilityAt, writeSteerRequestToDir, type SteerDeliveryStatus, type SteerRequest } from "../background/control-channel.ts";
|
|
9
9
|
import { SUBAGENT_CHILD_AGENT_ENV, SUBAGENT_CHILD_INDEX_ENV, SUBAGENT_FANOUT_CHILD_ENV, SUBAGENT_STEER_ACK_DIR_ENV, SUBAGENT_STEER_CAPABILITY_ENV, SUBAGENT_STEER_INBOX_ENV } from "./pi-args.ts";
|
|
10
10
|
import { RUNTIME_EXTENSION_ACK_EVENT, RUNTIME_EXTENSION_ACK_PATH_ENV, isRuntimeAcknowledgedExtensionId, writeRuntimeAcknowledgedExtensions } from "./runtime-acknowledged-extensions.ts";
|
|
11
|
-
import { createStructuredOutputToolParameters, STRUCTURED_OUTPUT_CAPTURE_ENV, STRUCTURED_OUTPUT_SCHEMA_ENV, validateStructuredOutputValue } from "./structured-output.ts";
|
|
11
|
+
import { createStructuredOutputToolParameters, STRUCTURED_OUTPUT_ACCEPTANCE_CAPTURE_ENV, STRUCTURED_OUTPUT_CAPTURE_ENV, STRUCTURED_OUTPUT_SCHEMA_ENV, validateStructuredOutputValue } from "./structured-output.ts";
|
|
12
12
|
import {
|
|
13
13
|
CHILD_TOOL_DIAGNOSTIC_PATH_ENV,
|
|
14
14
|
formatChildToolDiagnostic,
|
|
@@ -634,29 +634,33 @@ export default function registerSubagentPromptRuntime(pi: ExtensionAPI): void {
|
|
|
634
634
|
await drainOutstandingWork({ state: waitState, events: pi.events });
|
|
635
635
|
});
|
|
636
636
|
const structuredOutputPath = process.env[STRUCTURED_OUTPUT_CAPTURE_ENV];
|
|
637
|
+
const structuredAcceptanceReportPath = process.env[STRUCTURED_OUTPUT_ACCEPTANCE_CAPTURE_ENV];
|
|
637
638
|
const structuredSchemaPath = process.env[STRUCTURED_OUTPUT_SCHEMA_ENV];
|
|
638
639
|
if (structuredOutputPath && structuredSchemaPath) {
|
|
639
640
|
const schema = JSON.parse(fs.readFileSync(structuredSchemaPath, "utf-8")) as JsonSchemaObject;
|
|
640
|
-
const parameters = createStructuredOutputToolParameters(schema);
|
|
641
|
+
const parameters = createStructuredOutputToolParameters(schema, { acceptanceReport: Boolean(structuredAcceptanceReportPath) });
|
|
641
642
|
const registerTool = pi.registerTool as unknown as (tool: {
|
|
642
643
|
name: string;
|
|
643
644
|
label: string;
|
|
644
645
|
description: string;
|
|
645
646
|
parameters: unknown;
|
|
646
|
-
execute: (_id: string, params: { value: unknown }) => Promise<unknown>;
|
|
647
|
+
execute: (_id: string, params: { value: unknown; acceptanceReport?: unknown }) => Promise<unknown>;
|
|
647
648
|
}) => void;
|
|
648
649
|
registerTool({
|
|
649
650
|
name: "structured_output",
|
|
650
651
|
label: "Structured Output",
|
|
651
652
|
description: "Submit the required final structured output for this subagent step. This terminates the step.",
|
|
652
|
-
parameters
|
|
653
|
-
async execute(_id: string, params: { value: unknown }) {
|
|
653
|
+
parameters,
|
|
654
|
+
async execute(_id: string, params: { value: unknown; acceptanceReport?: unknown }) {
|
|
654
655
|
const validation = await validateStructuredOutputValue(schema, params.value);
|
|
655
656
|
if (validation.status === "invalid") {
|
|
656
657
|
throw new Error(`Structured output validation failed: ${validation.message}`);
|
|
657
658
|
}
|
|
658
659
|
fs.mkdirSync(path.dirname(structuredOutputPath), { recursive: true });
|
|
659
660
|
fs.writeFileSync(structuredOutputPath, JSON.stringify(params.value), { mode: 0o600 });
|
|
661
|
+
if (structuredAcceptanceReportPath && params.acceptanceReport !== undefined) {
|
|
662
|
+
fs.writeFileSync(structuredAcceptanceReportPath, JSON.stringify(params.acceptanceReport), { mode: 0o600 });
|
|
663
|
+
}
|
|
660
664
|
return {
|
|
661
665
|
content: [{ type: "text", text: "Structured output captured." }],
|
|
662
666
|
details: { path: structuredOutputPath },
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import type { AgentConfig } from "../agents/agents.ts";
|
|
4
|
+
import type { ExtensionBindings } from "../runs/shared/extension-bindings.ts";
|
|
4
5
|
|
|
5
6
|
export const AGENT_DEFINITION_PROJECTION_VERSION = 1 as const;
|
|
6
7
|
export const LAUNCH_BINDING_PROJECTION_VERSION = 1 as const;
|
|
@@ -46,6 +47,7 @@ export function projectAgentDefinition(agent: AgentConfig): Record<string, unkno
|
|
|
46
47
|
model: agent.model,
|
|
47
48
|
modelProvider: agent.modelProvider,
|
|
48
49
|
fallbackModels: agent.fallbackModels,
|
|
50
|
+
fast: agent.fast,
|
|
49
51
|
thinking: agent.thinking,
|
|
50
52
|
tools: agent.tools,
|
|
51
53
|
mcpDirectTools: agent.mcpDirectTools,
|
|
@@ -80,6 +82,7 @@ export interface LaunchBindingInput {
|
|
|
80
82
|
task?: string;
|
|
81
83
|
model?: string;
|
|
82
84
|
modelCandidates?: string[];
|
|
85
|
+
fast?: boolean;
|
|
83
86
|
thinking?: string;
|
|
84
87
|
systemPrompt?: string | null;
|
|
85
88
|
systemPromptMode?: AgentConfig["systemPromptMode"];
|
|
@@ -93,6 +96,7 @@ export interface LaunchBindingInput {
|
|
|
93
96
|
outputPath?: string;
|
|
94
97
|
outputMode?: string;
|
|
95
98
|
structuredOutputSchema?: unknown;
|
|
99
|
+
extensionBindings?: ExtensionBindings;
|
|
96
100
|
}
|
|
97
101
|
|
|
98
102
|
/** Canonical projection of the resolved inputs handed to the child. */
|
|
@@ -104,6 +108,7 @@ export function projectLaunchBinding(input: LaunchBindingInput): Record<string,
|
|
|
104
108
|
// The ordered candidate set already contains each attempted model; keeping only
|
|
105
109
|
// this set makes retries correlate to the same preflight binding.
|
|
106
110
|
modelCandidates: input.modelCandidates,
|
|
111
|
+
fast: input.fast,
|
|
107
112
|
thinking: input.thinking,
|
|
108
113
|
systemPromptDigest: input.systemPrompt === undefined || input.systemPrompt === null ? undefined : stableJsonDigest(input.systemPrompt),
|
|
109
114
|
systemPromptMode: input.systemPromptMode,
|
|
@@ -117,6 +122,7 @@ export function projectLaunchBinding(input: LaunchBindingInput): Record<string,
|
|
|
117
122
|
outputPath: input.outputPath,
|
|
118
123
|
outputMode: input.outputMode,
|
|
119
124
|
structuredOutputSchema: input.structuredOutputSchema,
|
|
125
|
+
extensionBindings: input.extensionBindings,
|
|
120
126
|
};
|
|
121
127
|
}
|
|
122
128
|
|
package/src/shared/settings.ts
CHANGED
|
@@ -22,6 +22,7 @@ export interface ResolvedStepBehavior {
|
|
|
22
22
|
progress: boolean;
|
|
23
23
|
skills: string[] | false;
|
|
24
24
|
model?: string;
|
|
25
|
+
fast?: boolean;
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
export type OutputOverrideInput = string | boolean;
|
|
@@ -33,6 +34,7 @@ export interface StepOverrides {
|
|
|
33
34
|
progress?: boolean;
|
|
34
35
|
skills?: string[] | false;
|
|
35
36
|
model?: string;
|
|
37
|
+
fast?: boolean;
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
function normalizeOutputOverride(output: unknown): string | false | undefined {
|
|
@@ -60,6 +62,7 @@ export interface SequentialStep {
|
|
|
60
62
|
progress?: boolean;
|
|
61
63
|
skill?: string | string[] | false;
|
|
62
64
|
model?: string;
|
|
65
|
+
fast?: boolean;
|
|
63
66
|
toolBudget?: ToolBudgetConfig;
|
|
64
67
|
acceptance?: AcceptanceInput;
|
|
65
68
|
agentContract?: AgentContract;
|
|
@@ -84,6 +87,7 @@ export interface ParallelTaskItem {
|
|
|
84
87
|
progress?: boolean;
|
|
85
88
|
skill?: string | string[] | false;
|
|
86
89
|
model?: string;
|
|
90
|
+
fast?: boolean;
|
|
87
91
|
toolBudget?: ToolBudgetConfig;
|
|
88
92
|
acceptance?: AcceptanceInput;
|
|
89
93
|
agentContract?: AgentContract;
|
|
@@ -284,7 +288,8 @@ export function resolveStepBehavior(
|
|
|
284
288
|
|
|
285
289
|
const outputMode = stepOverrides.outputMode ?? agentConfig.outputMode ?? "inline";
|
|
286
290
|
const model = stepOverrides.model ?? agentConfig.model;
|
|
287
|
-
|
|
291
|
+
const fast = stepOverrides.fast ?? agentConfig.fast;
|
|
292
|
+
return { output, outputMode, reads, progress, skills, model, fast };
|
|
288
293
|
}
|
|
289
294
|
|
|
290
295
|
export function resolveTaskTextForFileUpdatePolicy(task: string | undefined, originalTask?: string): string | undefined {
|
package/src/shared/types.ts
CHANGED
|
@@ -13,6 +13,7 @@ import type { ResolvedSubagentCapabilityCeiling, SubagentCapabilityAudit } from
|
|
|
13
13
|
import type { AuthorityPolicyConfig } from "../policy/authority.ts";
|
|
14
14
|
import type { ThinkingLevel } from "./model-info.ts";
|
|
15
15
|
import type { GlobalMissionIndexRecord, MissionRecord, MissionStoreConfig } from "../missions/types.ts";
|
|
16
|
+
import type { ExtensionBindings } from "../runs/shared/extension-bindings.ts";
|
|
16
17
|
|
|
17
18
|
// ============================================================================
|
|
18
19
|
// Basic Types
|
|
@@ -348,12 +349,49 @@ export interface FileMutationEffect {
|
|
|
348
349
|
attempted: boolean;
|
|
349
350
|
message?: string;
|
|
350
351
|
resolvedBy?: "llm-intent-arbiter";
|
|
352
|
+
evidence?: TrackedMutationEvidence;
|
|
351
353
|
}
|
|
352
354
|
|
|
353
355
|
export interface EffectsProjection {
|
|
354
356
|
fileMutation?: FileMutationEffect;
|
|
355
357
|
}
|
|
356
358
|
|
|
359
|
+
export interface TrackedMutationSnapshot {
|
|
360
|
+
source: "tracked-files";
|
|
361
|
+
trackedOnly: true;
|
|
362
|
+
cwd: string;
|
|
363
|
+
gitRoot?: string;
|
|
364
|
+
dirtyFiles: string[];
|
|
365
|
+
fingerprints: Record<string, TrackedMutationFingerprint>;
|
|
366
|
+
truncated?: boolean;
|
|
367
|
+
unavailable?: string;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
export type TrackedMutationFingerprint = { kind: "diff"; digest: string };
|
|
371
|
+
|
|
372
|
+
export interface TrackedMutationEvidence {
|
|
373
|
+
source: "tracked-files";
|
|
374
|
+
trackedOnly: true;
|
|
375
|
+
changedFiles: string[];
|
|
376
|
+
attemptedMutation: boolean;
|
|
377
|
+
truncated?: boolean;
|
|
378
|
+
unavailable?: string;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
export interface TimeoutRecoverySummary {
|
|
382
|
+
termination: "timed-out" | "stopped";
|
|
383
|
+
changedFiles: string[];
|
|
384
|
+
truncated?: boolean;
|
|
385
|
+
currentTool?: string;
|
|
386
|
+
currentToolArgs?: string;
|
|
387
|
+
currentPath?: string;
|
|
388
|
+
sessionFile?: string;
|
|
389
|
+
transcriptPath?: string;
|
|
390
|
+
artifactPaths?: ArtifactPaths;
|
|
391
|
+
warning: string;
|
|
392
|
+
message: string;
|
|
393
|
+
}
|
|
394
|
+
|
|
357
395
|
export const SUBAGENT_LIFECYCLE_ARTIFACT_VERSION = 3;
|
|
358
396
|
export type SubagentLifecycleArtifactVersion = typeof SUBAGENT_LIFECYCLE_ARTIFACT_VERSION;
|
|
359
397
|
|
|
@@ -526,6 +564,7 @@ export interface RunFanoutRejection extends RunFanoutBudgetSnapshot {
|
|
|
526
564
|
export interface SteeringRecoveryDescriptor {
|
|
527
565
|
version: 1;
|
|
528
566
|
launchContractDigest?: string;
|
|
567
|
+
extensionBindings?: ExtensionBindings;
|
|
529
568
|
runFanoutBudget: RunFanoutBudgetDescriptor;
|
|
530
569
|
sourceRunId: string;
|
|
531
570
|
agentContract?: AgentContract;
|
|
@@ -536,6 +575,7 @@ export interface SteeringRecoveryDescriptor {
|
|
|
536
575
|
modelProvider?: string;
|
|
537
576
|
modelOverrideFromParent?: boolean;
|
|
538
577
|
fallbackModels?: string[];
|
|
578
|
+
fast?: boolean;
|
|
539
579
|
thinking?: string;
|
|
540
580
|
thinkingCeiling?: ThinkingLevel;
|
|
541
581
|
tools?: string[];
|
|
@@ -941,6 +981,7 @@ export interface SingleResult {
|
|
|
941
981
|
context?: "fresh" | "fork";
|
|
942
982
|
exitCode: number;
|
|
943
983
|
processSignal?: string | null;
|
|
984
|
+
timeoutRecovery?: TimeoutRecoverySummary;
|
|
944
985
|
detached?: boolean;
|
|
945
986
|
detachedReason?: string;
|
|
946
987
|
interrupted?: boolean;
|
|
@@ -1505,6 +1546,7 @@ export interface AsyncStatus {
|
|
|
1505
1546
|
durationMs?: number;
|
|
1506
1547
|
exitCode?: number | null;
|
|
1507
1548
|
timedOut?: boolean;
|
|
1549
|
+
timeoutRecovery?: TimeoutRecoverySummary;
|
|
1508
1550
|
stopped?: boolean;
|
|
1509
1551
|
turnBudget?: TurnBudgetState;
|
|
1510
1552
|
turnBudgetExceeded?: boolean;
|
|
@@ -1644,6 +1686,8 @@ export interface ForegroundResumeChild {
|
|
|
1644
1686
|
acceptance?: AcceptanceLedger;
|
|
1645
1687
|
agentContract?: AgentContract;
|
|
1646
1688
|
launchContractDigest?: string;
|
|
1689
|
+
/** Private retained launch authority. Never project into status or result output. */
|
|
1690
|
+
extensionBindings?: ExtensionBindings;
|
|
1647
1691
|
launchResolvedExtensions?: LaunchResolvedChildExtensionsV1;
|
|
1648
1692
|
runtimeAcknowledgedExtensions?: RuntimeAcknowledgedChildExtensionsV1;
|
|
1649
1693
|
execution?: ExecutionProjection;
|
|
@@ -1944,6 +1988,8 @@ export interface RunSyncOptions {
|
|
|
1944
1988
|
nestedRoute?: NestedRouteInfo;
|
|
1945
1989
|
/** Override the agent's default model (format: "provider/id" or just "id") */
|
|
1946
1990
|
modelOverride?: string;
|
|
1991
|
+
/** Opt into priority service tier for supported native OpenAI-Codex launches. */
|
|
1992
|
+
fast?: boolean;
|
|
1947
1993
|
/** The override came from the running parent session, not configuration. */
|
|
1948
1994
|
modelOverrideFromParent?: boolean;
|
|
1949
1995
|
/** LLM intent arbiter for the completion mutation guard (rescues read-only review runs). */
|
|
@@ -1951,6 +1997,7 @@ export interface RunSyncOptions {
|
|
|
1951
1997
|
/** Override the agent's default thinking level for this run */
|
|
1952
1998
|
thinkingOverride?: AgentConfig["thinking"];
|
|
1953
1999
|
thinkingCeiling?: ThinkingLevel;
|
|
2000
|
+
extensionBindings?: ExtensionBindings;
|
|
1954
2001
|
/** Registry models available for heuristic bare-model resolution */
|
|
1955
2002
|
availableModels?: Array<{ provider: string; id: string; fullId: string }>;
|
|
1956
2003
|
/** Current parent-session provider to prefer for ambiguous bare model ids */
|
|
@@ -1963,6 +2010,7 @@ export interface RunSyncOptions {
|
|
|
1963
2010
|
schema: JsonSchemaObject;
|
|
1964
2011
|
schemaPath: string;
|
|
1965
2012
|
outputPath: string;
|
|
2013
|
+
acceptanceReportPath?: string;
|
|
1966
2014
|
};
|
|
1967
2015
|
agentContract?: AgentContract;
|
|
1968
2016
|
acceptance?: AcceptanceInput;
|