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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { PI_CODING_AGENT_PACKAGE_ROOT_ENV } from "../../shared/utils.ts";
|
|
4
5
|
|
|
5
6
|
export const PI_CODING_AGENT_PACKAGE = "@earendil-works/pi-coding-agent";
|
|
6
7
|
export const PI_SUBAGENT_PI_BINARY_ENV = "PI_SUBAGENT_PI_BINARY";
|
|
@@ -23,9 +24,13 @@ export function findPiPackageRootFromEntry(
|
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
export function resolveInstalledPiPackageRoot(): string | undefined {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
try {
|
|
28
|
+
return findPiPackageRootFromEntry(
|
|
29
|
+
fileURLToPath(import.meta.resolve(PI_CODING_AGENT_PACKAGE)),
|
|
30
|
+
);
|
|
31
|
+
} catch {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
29
34
|
}
|
|
30
35
|
|
|
31
36
|
export function resolvePiPackageRoot(): string | undefined {
|
|
@@ -58,12 +63,21 @@ interface PiSpawnCommand {
|
|
|
58
63
|
args: string[];
|
|
59
64
|
}
|
|
60
65
|
|
|
66
|
+
interface PiPackageJson {
|
|
67
|
+
name?: unknown;
|
|
68
|
+
bin?: string | Record<string, string>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function isNodeScriptPath(filePath: string): boolean {
|
|
72
|
+
return /\.(?:mjs|cjs|js)$/i.test(filePath);
|
|
73
|
+
}
|
|
74
|
+
|
|
61
75
|
function isRunnableNodeScript(
|
|
62
76
|
filePath: string,
|
|
63
77
|
existsSync: (filePath: string) => boolean,
|
|
64
78
|
): boolean {
|
|
65
79
|
if (!existsSync(filePath)) return false;
|
|
66
|
-
return
|
|
80
|
+
return isNodeScriptPath(filePath);
|
|
67
81
|
}
|
|
68
82
|
|
|
69
83
|
function normalizePath(filePath: string): string {
|
|
@@ -75,6 +89,23 @@ function isStandalonePiExecutable(execPath: string): boolean {
|
|
|
75
89
|
return /^pi(?:\.exe)?$/i.test(executableName ?? "");
|
|
76
90
|
}
|
|
77
91
|
|
|
92
|
+
function resolvePiCliScriptFromPackageJson(
|
|
93
|
+
packageJsonPath: string,
|
|
94
|
+
readFileSync: (filePath: string, encoding: "utf-8") => string,
|
|
95
|
+
existsSync: (filePath: string) => boolean,
|
|
96
|
+
): string | undefined {
|
|
97
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8")) as PiPackageJson;
|
|
98
|
+
if (packageJson.name !== PI_CODING_AGENT_PACKAGE) return undefined;
|
|
99
|
+
const binField = packageJson.bin;
|
|
100
|
+
const binPath =
|
|
101
|
+
typeof binField === "string"
|
|
102
|
+
? binField
|
|
103
|
+
: (binField?.pi ?? Object.values(binField ?? {})[0]);
|
|
104
|
+
if (!binPath) return undefined;
|
|
105
|
+
const candidate = path.resolve(path.dirname(packageJsonPath), binPath);
|
|
106
|
+
return isRunnableNodeScript(candidate, existsSync) ? candidate : undefined;
|
|
107
|
+
}
|
|
108
|
+
|
|
78
109
|
export function resolvePiCliScript(
|
|
79
110
|
deps: PiSpawnDeps = {},
|
|
80
111
|
): string | undefined {
|
|
@@ -84,6 +115,7 @@ export function resolvePiCliScript(
|
|
|
84
115
|
deps.readFileSync ??
|
|
85
116
|
((filePath, encoding) => fs.readFileSync(filePath, encoding));
|
|
86
117
|
const argv1 = deps.argv1 ?? process.argv[1];
|
|
118
|
+
const env = deps.env ?? process.env;
|
|
87
119
|
|
|
88
120
|
if (argv1) {
|
|
89
121
|
const argvPath = normalizePath(argv1);
|
|
@@ -99,38 +131,28 @@ export function resolvePiCliScript(
|
|
|
99
131
|
}
|
|
100
132
|
}
|
|
101
133
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
typeof binField === "string"
|
|
124
|
-
? binField
|
|
125
|
-
: (binField?.pi ?? Object.values(binField ?? {})[0]);
|
|
126
|
-
if (!binPath) return undefined;
|
|
127
|
-
const candidate = path.resolve(path.dirname(packageJsonPath), binPath);
|
|
128
|
-
if (isRunnableNodeScript(candidate, existsSync)) {
|
|
129
|
-
return candidate;
|
|
134
|
+
const packageJsonCandidates: Array<() => string | undefined> = [];
|
|
135
|
+
if (deps.resolvePackageJson) packageJsonCandidates.push(deps.resolvePackageJson);
|
|
136
|
+
for (const root of [deps.piPackageRoot, env[PI_CODING_AGENT_PACKAGE_ROOT_ENV], resolvePiPackageRoot()]) {
|
|
137
|
+
const trimmed = root?.trim();
|
|
138
|
+
if (trimmed) packageJsonCandidates.push(() => path.join(trimmed, "package.json"));
|
|
139
|
+
}
|
|
140
|
+
packageJsonCandidates.push(() => {
|
|
141
|
+
const packageRoot = deps.resolvePackageEntry
|
|
142
|
+
? findPiPackageRootFromEntry(deps.resolvePackageEntry())
|
|
143
|
+
: resolveInstalledPiPackageRoot();
|
|
144
|
+
return packageRoot ? path.join(packageRoot, "package.json") : undefined;
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
for (const candidatePackageJson of packageJsonCandidates) {
|
|
148
|
+
try {
|
|
149
|
+
const packageJsonPath = candidatePackageJson();
|
|
150
|
+
if (!packageJsonPath) continue;
|
|
151
|
+
const candidate = resolvePiCliScriptFromPackageJson(packageJsonPath, readFileSync, existsSync);
|
|
152
|
+
if (candidate) return candidate;
|
|
153
|
+
} catch {
|
|
154
|
+
// Keep resolving; callers decide whether a PATH fallback is safe.
|
|
130
155
|
}
|
|
131
|
-
} catch {
|
|
132
|
-
// Verified CLI resolution is optional; falling back to `pi` lets PATH handle execution.
|
|
133
|
-
return undefined;
|
|
134
156
|
}
|
|
135
157
|
|
|
136
158
|
return undefined;
|
|
@@ -140,9 +162,16 @@ export function getPiSpawnCommand(
|
|
|
140
162
|
args: string[],
|
|
141
163
|
deps: PiSpawnDeps = {},
|
|
142
164
|
): PiSpawnCommand {
|
|
165
|
+
const platform = deps.platform ?? process.platform;
|
|
143
166
|
const env = deps.env ?? process.env;
|
|
144
167
|
const piBinary = env[PI_SUBAGENT_PI_BINARY_ENV]?.trim();
|
|
145
168
|
if (piBinary) {
|
|
169
|
+
if (platform === "win32" && isNodeScriptPath(piBinary)) {
|
|
170
|
+
return {
|
|
171
|
+
command: deps.execPath ?? process.execPath,
|
|
172
|
+
args: [piBinary, ...args],
|
|
173
|
+
};
|
|
174
|
+
}
|
|
146
175
|
return { command: piBinary, args };
|
|
147
176
|
}
|
|
148
177
|
|
|
@@ -158,6 +187,11 @@ export function getPiSpawnCommand(
|
|
|
158
187
|
args: [piCliPath, ...args],
|
|
159
188
|
};
|
|
160
189
|
}
|
|
190
|
+
if (platform === "win32") {
|
|
191
|
+
throw new Error(
|
|
192
|
+
`Could not resolve the Pi CLI on Windows. Set ${PI_SUBAGENT_PI_BINARY_ENV} or ensure ${PI_CODING_AGENT_PACKAGE} is installed.`,
|
|
193
|
+
);
|
|
194
|
+
}
|
|
161
195
|
|
|
162
196
|
return { command: "pi", args };
|
|
163
197
|
}
|
|
@@ -5,17 +5,21 @@ import * as path from "node:path";
|
|
|
5
5
|
import { pathToFileURL } from "node:url";
|
|
6
6
|
import { PI_CODING_AGENT_PACKAGE_ROOT_ENV } from "../../shared/utils.ts";
|
|
7
7
|
import type { JsonSchemaObject } from "../../shared/types.ts";
|
|
8
|
+
import type { ResolvedAcceptanceReportMode } from "./acceptance.ts";
|
|
8
9
|
|
|
9
10
|
export const STRUCTURED_OUTPUT_SCHEMA_ENV = "PI_SUBAGENT_STRUCTURED_OUTPUT_SCHEMA";
|
|
10
11
|
export const STRUCTURED_OUTPUT_CAPTURE_ENV = "PI_SUBAGENT_STRUCTURED_OUTPUT_CAPTURE";
|
|
11
12
|
export const STRUCTURED_OUTPUT_ACCEPTANCE_CAPTURE_ENV = "PI_SUBAGENT_STRUCTURED_OUTPUT_ACCEPTANCE_CAPTURE";
|
|
13
|
+
export const STRUCTURED_OUTPUT_ACCEPTANCE_REQUIRED_ENV = "PI_SUBAGENT_STRUCTURED_OUTPUT_ACCEPTANCE_REQUIRED";
|
|
12
14
|
export const MISSING_STRUCTURED_OUTPUT_CALL_ERROR = "Missing structured_output call; this step has outputSchema and must finish by calling structured_output.";
|
|
15
|
+
export const MISSING_STRUCTURED_ACCEPTANCE_REPORT_ERROR = "Missing acceptanceReport in structured_output call; acceptance.report is \"on\".";
|
|
13
16
|
|
|
14
17
|
export interface StructuredOutputRuntime {
|
|
15
18
|
schema: JsonSchemaObject;
|
|
16
19
|
schemaPath: string;
|
|
17
20
|
outputPath: string;
|
|
18
21
|
acceptanceReportPath?: string;
|
|
22
|
+
acceptanceReportRequired?: boolean;
|
|
19
23
|
}
|
|
20
24
|
|
|
21
25
|
const SCHEMA_MAP_KEYWORDS = ["properties", "patternProperties", "$defs", "definitions", "dependentSchemas"] as const;
|
|
@@ -61,14 +65,14 @@ function rewriteLocalJsonPointerRefs(schema: unknown, pointerPrefix: string, inh
|
|
|
61
65
|
return rewritten;
|
|
62
66
|
}
|
|
63
67
|
|
|
64
|
-
export function createStructuredOutputToolParameters(schema: JsonSchemaObject, options: { acceptanceReport?:
|
|
68
|
+
export function createStructuredOutputToolParameters(schema: JsonSchemaObject, options: { acceptanceReport?: "optional" | "required" } = {}): JsonSchemaObject {
|
|
65
69
|
return {
|
|
66
70
|
type: "object",
|
|
67
71
|
properties: {
|
|
68
72
|
value: rewriteLocalJsonPointerRefs(schema, "#/properties/value"),
|
|
69
73
|
...(options.acceptanceReport ? { acceptanceReport: { type: "object" } } : {}),
|
|
70
74
|
},
|
|
71
|
-
required: ["value"],
|
|
75
|
+
required: ["value", ...(options.acceptanceReport === "required" ? ["acceptanceReport"] : [])],
|
|
72
76
|
additionalProperties: false,
|
|
73
77
|
};
|
|
74
78
|
}
|
|
@@ -129,7 +133,7 @@ export function assertJsonSchemaObject(schema: unknown, label = "outputSchema"):
|
|
|
129
133
|
}
|
|
130
134
|
}
|
|
131
135
|
|
|
132
|
-
export function createStructuredOutputRuntime(schema: JsonSchemaObject, baseDir?: string, options: {
|
|
136
|
+
export function createStructuredOutputRuntime(schema: JsonSchemaObject, baseDir?: string, options: { acceptanceReport?: ResolvedAcceptanceReportMode } = {}): StructuredOutputRuntime {
|
|
133
137
|
assertJsonSchemaObject(schema);
|
|
134
138
|
const rootDir = baseDir ?? os.tmpdir();
|
|
135
139
|
fs.mkdirSync(rootDir, { recursive: true });
|
|
@@ -137,7 +141,14 @@ export function createStructuredOutputRuntime(schema: JsonSchemaObject, baseDir?
|
|
|
137
141
|
const schemaPath = path.join(dir, "schema.json");
|
|
138
142
|
const outputPath = path.join(dir, "output.json");
|
|
139
143
|
fs.writeFileSync(schemaPath, JSON.stringify(schema), { mode: 0o600 });
|
|
140
|
-
return {
|
|
144
|
+
return {
|
|
145
|
+
schema,
|
|
146
|
+
schemaPath,
|
|
147
|
+
outputPath,
|
|
148
|
+
...(options.acceptanceReport && options.acceptanceReport !== "off"
|
|
149
|
+
? { acceptanceReportPath: path.join(dir, "acceptance-report.json"), acceptanceReportRequired: options.acceptanceReport === "required" }
|
|
150
|
+
: {}),
|
|
151
|
+
};
|
|
141
152
|
}
|
|
142
153
|
|
|
143
154
|
export async function validateStructuredOutputValue(schema: JsonSchemaObject, value: unknown): Promise<{ status: "valid" } | { status: "invalid"; message: string }> {
|
|
@@ -178,14 +189,30 @@ export async function readStructuredOutput(runtime: StructuredOutputRuntime): Pr
|
|
|
178
189
|
}
|
|
179
190
|
|
|
180
191
|
export function readStructuredOutputAcceptanceReport(runtime: StructuredOutputRuntime): { value?: unknown; error?: string } {
|
|
181
|
-
if (!runtime.acceptanceReportPath
|
|
192
|
+
if (!runtime.acceptanceReportPath) return {};
|
|
193
|
+
if (!fs.existsSync(runtime.acceptanceReportPath)) {
|
|
194
|
+
return runtime.acceptanceReportRequired ? { error: MISSING_STRUCTURED_ACCEPTANCE_REPORT_ERROR } : {};
|
|
195
|
+
}
|
|
182
196
|
try {
|
|
183
|
-
return { value: JSON.parse(fs.readFileSync(runtime.acceptanceReportPath, "utf-8")) };
|
|
197
|
+
return { value: JSON.parse(fs.readFileSync(runtime.acceptanceReportPath, "utf-8")) as unknown };
|
|
184
198
|
} catch (error) {
|
|
185
199
|
return { error: `Failed to read structured output acceptance report: ${error instanceof Error ? error.message : String(error)}` };
|
|
186
200
|
}
|
|
187
201
|
}
|
|
188
202
|
|
|
203
|
+
export function clearStructuredOutputCaptures(runtime: StructuredOutputRuntime): string | undefined {
|
|
204
|
+
let cleanupError: string | undefined;
|
|
205
|
+
for (const filePath of [runtime.outputPath, runtime.acceptanceReportPath]) {
|
|
206
|
+
if (!filePath) continue;
|
|
207
|
+
try {
|
|
208
|
+
if (fs.existsSync(filePath)) fs.unlinkSync(filePath);
|
|
209
|
+
} catch (error) {
|
|
210
|
+
cleanupError ??= `Failed to clear stale structured output capture ${filePath}: ${error instanceof Error ? error.message : String(error)}`;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return cleanupError;
|
|
214
|
+
}
|
|
215
|
+
|
|
189
216
|
export function cleanupStructuredOutputRuntime(runtime: StructuredOutputRuntime | undefined): void {
|
|
190
217
|
if (!runtime) return;
|
|
191
218
|
try {
|
|
@@ -8,7 +8,8 @@ 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_FORK_CACHE_KEY_ENV, SUBAGENT_INHERIT_GLOBAL_CONTEXT_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_ACCEPTANCE_CAPTURE_ENV, STRUCTURED_OUTPUT_CAPTURE_ENV, STRUCTURED_OUTPUT_SCHEMA_ENV, validateStructuredOutputValue } from "./structured-output.ts";
|
|
11
|
+
import { createStructuredOutputToolParameters, MISSING_STRUCTURED_ACCEPTANCE_REPORT_ERROR, STRUCTURED_OUTPUT_ACCEPTANCE_CAPTURE_ENV, STRUCTURED_OUTPUT_ACCEPTANCE_REQUIRED_ENV, STRUCTURED_OUTPUT_CAPTURE_ENV, STRUCTURED_OUTPUT_SCHEMA_ENV, validateStructuredOutputValue } from "./structured-output.ts";
|
|
12
|
+
import { validateAcceptanceReport } from "./acceptance.ts";
|
|
12
13
|
import {
|
|
13
14
|
CHILD_TOOL_DIAGNOSTIC_PATH_ENV,
|
|
14
15
|
formatChildToolDiagnostic,
|
|
@@ -729,10 +730,14 @@ export default function registerSubagentPromptRuntime(pi: ExtensionAPI): void {
|
|
|
729
730
|
});
|
|
730
731
|
const structuredOutputPath = process.env[STRUCTURED_OUTPUT_CAPTURE_ENV];
|
|
731
732
|
const structuredAcceptanceReportPath = process.env[STRUCTURED_OUTPUT_ACCEPTANCE_CAPTURE_ENV];
|
|
733
|
+
const structuredAcceptanceReportRequired = process.env[STRUCTURED_OUTPUT_ACCEPTANCE_REQUIRED_ENV] === "1";
|
|
732
734
|
const structuredSchemaPath = process.env[STRUCTURED_OUTPUT_SCHEMA_ENV];
|
|
733
735
|
if (structuredOutputPath && structuredSchemaPath) {
|
|
734
736
|
const schema = JSON.parse(fs.readFileSync(structuredSchemaPath, "utf-8")) as JsonSchemaObject;
|
|
735
|
-
const
|
|
737
|
+
const acceptanceReportMode = structuredAcceptanceReportPath
|
|
738
|
+
? structuredAcceptanceReportRequired ? "required" as const : "optional" as const
|
|
739
|
+
: undefined;
|
|
740
|
+
const parameters = createStructuredOutputToolParameters(schema, { acceptanceReport: acceptanceReportMode });
|
|
736
741
|
const registerTool = pi.registerTool as unknown as (tool: {
|
|
737
742
|
name: string;
|
|
738
743
|
label: string;
|
|
@@ -750,11 +755,23 @@ export default function registerSubagentPromptRuntime(pi: ExtensionAPI): void {
|
|
|
750
755
|
if (validation.status === "invalid") {
|
|
751
756
|
throw new Error(`Structured output validation failed: ${validation.message}`);
|
|
752
757
|
}
|
|
758
|
+
if (structuredAcceptanceReportRequired && params.acceptanceReport === undefined) {
|
|
759
|
+
throw new Error(MISSING_STRUCTURED_ACCEPTANCE_REPORT_ERROR);
|
|
760
|
+
}
|
|
761
|
+
if (structuredAcceptanceReportRequired && params.acceptanceReport !== undefined) {
|
|
762
|
+
const acceptanceValidation = validateAcceptanceReport(params.acceptanceReport, "acceptanceReport");
|
|
763
|
+
if (!acceptanceValidation.report) {
|
|
764
|
+
throw new Error(`Invalid structured output acceptance report: ${acceptanceValidation.errors.join("; ")}`);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
753
767
|
fs.mkdirSync(path.dirname(structuredOutputPath), { recursive: true });
|
|
754
|
-
fs.writeFileSync(structuredOutputPath, JSON.stringify(params.value), { mode: 0o600 });
|
|
755
768
|
if (structuredAcceptanceReportPath && params.acceptanceReport !== undefined) {
|
|
769
|
+
fs.mkdirSync(path.dirname(structuredAcceptanceReportPath), { recursive: true });
|
|
756
770
|
fs.writeFileSync(structuredAcceptanceReportPath, JSON.stringify(params.acceptanceReport), { mode: 0o600 });
|
|
771
|
+
} else if (structuredAcceptanceReportPath && fs.existsSync(structuredAcceptanceReportPath)) {
|
|
772
|
+
fs.unlinkSync(structuredAcceptanceReportPath);
|
|
757
773
|
}
|
|
774
|
+
fs.writeFileSync(structuredOutputPath, JSON.stringify(params.value), { mode: 0o600 });
|
|
758
775
|
return {
|
|
759
776
|
content: [{ type: "text", text: "Structured output captured." }],
|
|
760
777
|
details: { path: structuredOutputPath },
|
|
@@ -34,13 +34,22 @@ const REVIEWER_REQUIRED_EDIT_PATTERNS = [
|
|
|
34
34
|
/\bmake\s+(?:the\s+)?code\s+changes\b/i,
|
|
35
35
|
];
|
|
36
36
|
|
|
37
|
-
// The prohibition's object ends at punctuation
|
|
38
|
-
// (but/and/then), so a follow-on clause like "but
|
|
39
|
-
// the text for write-intent testing instead of
|
|
40
|
-
|
|
37
|
+
// The prohibition's object ends at punctuation, a serialized line separator,
|
|
38
|
+
// or a coordinating word (but/and/then), so a follow-on clause like "but
|
|
39
|
+
// implement the fix" stays in the text for write-intent testing instead of
|
|
40
|
+
// being swallowed as the object.
|
|
41
|
+
// Accept serialized line separators too: workflow prompts can carry literal
|
|
42
|
+
// `\\n`/`\\r\\n` between clauses instead of decoded newlines.
|
|
43
|
+
const NO_EDIT_PROHIBITION_PATTERN = /(?:\b|\\(?:r\\n|n))(?:do not|don't|must not)\s+(?:edit|modify|write(?:\s+to)?|touch|change)\b((?:(?!\b(?:but|and|then)\b|\\(?:r\\n|n))[^.;,:!?\n–—-])*)/gi;
|
|
41
44
|
|
|
42
45
|
/** Objects of a no-edit prohibition that mean "the codebase in general" rather than a named scope. */
|
|
43
46
|
const GENERIC_PROHIBITION_OBJECT = /^\s*(?:(?:any|all|the|these|those|your|our|existing|project|product|source|sources|config|configs|repo|repository)[\s/,-]*)*(?:files?|code|codebase|sources?|anything|repo(?:sitory)?)?\s*$/i;
|
|
47
|
+
const SCOPED_PROHIBITION_CONTINUATION = /^(?:\\(?:r\\n|n)|\r?\n)\s*(?:in|inside|under|within|outside|except|other\s+than|besides)\b/i;
|
|
48
|
+
const OUTPUT_INSTRUCTION_CONTINUATION = /^(?:\\(?:r\\n|n)|\r?\n)\s*(?:in|inside|within)\s+(?:(?:(?:your|the)\s+)(?:final\s+)?|final\s+)(?:output|response|report|answer|reply|message|summary|findings?|analysis|recommendations?)(?:\s*\/\s*(?:output|response|report|answer|reply|message|summary|findings?|analysis|recommendations?))*\b(?![\\/])/i;
|
|
49
|
+
|
|
50
|
+
function hasScopedProhibitionContinuation(text: string): boolean {
|
|
51
|
+
return SCOPED_PROHIBITION_CONTINUATION.test(text) && !OUTPUT_INSTRUCTION_CONTINUATION.test(text);
|
|
52
|
+
}
|
|
44
53
|
|
|
45
54
|
const SCOPED_NO_EDIT_CONSTRAINT_PATTERNS = [
|
|
46
55
|
/\bdo not edit files?\s+outside\b/i,
|
|
@@ -135,11 +144,13 @@ function analyzeNoEditProhibitions(taskText: string): NoEditProhibitionAnalysis
|
|
|
135
144
|
|| NO_TOOL_INTENT_PATTERNS.some((pattern) => pattern.test(taskText));
|
|
136
145
|
let blanket = present;
|
|
137
146
|
let strippedText = stripPatterns(taskText, [...REVIEW_ONLY_PATTERNS, ...NO_TOOL_INTENT_PATTERNS]);
|
|
138
|
-
strippedText = strippedText.replace(new RegExp(NO_EDIT_PROHIBITION_PATTERN.source, NO_EDIT_PROHIBITION_PATTERN.flags), (
|
|
147
|
+
strippedText = strippedText.replace(new RegExp(NO_EDIT_PROHIBITION_PATTERN.source, NO_EDIT_PROHIBITION_PATTERN.flags), (match, object: string, offset: number, source: string) => {
|
|
139
148
|
present = true;
|
|
140
|
-
if (GENERIC_PROHIBITION_OBJECT.test(object)) blanket = true;
|
|
149
|
+
if (GENERIC_PROHIBITION_OBJECT.test(object) && !hasScopedProhibitionContinuation(source.slice(offset + match.length))) blanket = true;
|
|
141
150
|
return " ";
|
|
142
151
|
});
|
|
152
|
+
// Restore boundaries after stripping a prohibition from serialized prompts.
|
|
153
|
+
strippedText = strippedText.replace(/\\(?:r\\n|n)/g, "\n");
|
|
143
154
|
return { present, blanket, strippedText };
|
|
144
155
|
}
|
|
145
156
|
|
|
@@ -16,7 +16,7 @@ export const DEFAULT_FAST_TOOL_TIMEOUT_TOOLS = new Set([
|
|
|
16
16
|
]);
|
|
17
17
|
|
|
18
18
|
/** Tools whose normal job can be to wait for a person or another run. */
|
|
19
|
-
export const TOOL_TIMEOUT_EXEMPT_TOOLS = new Set(["contact_supervisor", "intercom", "
|
|
19
|
+
export const TOOL_TIMEOUT_EXEMPT_TOOLS = new Set(["contact_supervisor", "intercom", "bg_wait"]);
|
|
20
20
|
|
|
21
21
|
// Backward-compatible export name for existing callers/tests.
|
|
22
22
|
export const TOOL_TIMEOUT_ALLOWLIST = TOOL_TIMEOUT_EXEMPT_TOOLS;
|
|
@@ -15,7 +15,8 @@ export interface WorkflowGraphBuildInput {
|
|
|
15
15
|
|
|
16
16
|
/** Return displayable workflow stages while hiding structural parallel groups and host monitors. */
|
|
17
17
|
export function workflowGraphStageNodes(graph: WorkflowGraphSnapshot | undefined): WorkflowGraphNode[] {
|
|
18
|
-
|
|
18
|
+
const nodes = graph?.nodes;
|
|
19
|
+
if (!nodes?.length) return [];
|
|
19
20
|
const stages: WorkflowGraphNode[] = [];
|
|
20
21
|
const visit = (node: WorkflowGraphNode): void => {
|
|
21
22
|
if (node.kind === "parallel-group" || node.kind === "dynamic-parallel-group") {
|
|
@@ -24,7 +25,7 @@ export function workflowGraphStageNodes(graph: WorkflowGraphSnapshot | undefined
|
|
|
24
25
|
}
|
|
25
26
|
if (node.kind !== "host-step") stages.push(node);
|
|
26
27
|
};
|
|
27
|
-
for (const node of
|
|
28
|
+
for (const node of nodes) visit(node);
|
|
28
29
|
return stages;
|
|
29
30
|
}
|
|
30
31
|
|
|
@@ -15,6 +15,7 @@ type AtomicJsonWriterOptions = {
|
|
|
15
15
|
mode?: number;
|
|
16
16
|
retryRenameErrors?: boolean;
|
|
17
17
|
retryDirectoryErrors?: boolean;
|
|
18
|
+
ignoreCleanupErrorAfterSuccess?: boolean;
|
|
18
19
|
retryDelaysMs?: readonly number[];
|
|
19
20
|
wait?: (delayMs: number) => void;
|
|
20
21
|
};
|
|
@@ -46,6 +47,7 @@ export function createAtomicJsonWriter(options: AtomicJsonWriterOptions = {}): (
|
|
|
46
47
|
const mode = options.mode;
|
|
47
48
|
const retryRenameErrors = options.retryRenameErrors ?? process.platform === "win32";
|
|
48
49
|
const retryDirectoryErrors = options.retryDirectoryErrors ?? retryRenameErrors;
|
|
50
|
+
const ignoreCleanupErrorAfterSuccess = options.ignoreCleanupErrorAfterSuccess ?? false;
|
|
49
51
|
const retryDelaysMs = options.retryDelaysMs ?? DEFAULT_FILE_SYSTEM_RETRY_DELAYS_MS;
|
|
50
52
|
const renameRetryDelaysMs = retryRenameErrors ? retryDelaysMs : [];
|
|
51
53
|
const directoryRetryDelaysMs = retryDirectoryErrors ? retryDelaysMs : [];
|
|
@@ -71,7 +73,7 @@ export function createAtomicJsonWriter(options: AtomicJsonWriterOptions = {}): (
|
|
|
71
73
|
} catch (cleanupError) {
|
|
72
74
|
// Preserve the write/rename failure: cleanup is best effort and must
|
|
73
75
|
// not hide the error callers need to classify or report.
|
|
74
|
-
if (writeError === undefined) throw cleanupError;
|
|
76
|
+
if (writeError === undefined && !ignoreCleanupErrorAfterSuccess) throw cleanupError;
|
|
75
77
|
}
|
|
76
78
|
}
|
|
77
79
|
};
|
|
@@ -174,18 +174,6 @@ function readSessionEntries(sessionFile: string): BranchSessionEntry[] {
|
|
|
174
174
|
});
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
-
/** Keep Pi from restoring a forked session into the parent's cwd instead of the child launch cwd. */
|
|
178
|
-
export function alignForkedSessionCwd(sessionFile: string, cwd: string): void {
|
|
179
|
-
const entries = readSessionEntries(sessionFile);
|
|
180
|
-
const header = entries[0];
|
|
181
|
-
if (header?.type !== "session") throw new Error(`Forked session ${sessionFile} does not start with a session header.`);
|
|
182
|
-
const resolvedCwd = path.resolve(cwd);
|
|
183
|
-
const effectiveCwd = fs.realpathSync.native(resolvedCwd);
|
|
184
|
-
if (header.cwd === effectiveCwd) return;
|
|
185
|
-
header.cwd = effectiveCwd;
|
|
186
|
-
fs.writeFileSync(sessionFile, `${entries.map((entry) => JSON.stringify(entry)).join("\n")}\n`, "utf-8");
|
|
187
|
-
}
|
|
188
|
-
|
|
189
177
|
export function createForkContextResolver(
|
|
190
178
|
sessionManager: ForkableSessionManager,
|
|
191
179
|
requestedContext: unknown,
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
|
|
4
|
+
interface SessionEntry {
|
|
5
|
+
type?: string;
|
|
6
|
+
cwd?: string;
|
|
7
|
+
[key: string]: unknown;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Keep Pi from restoring a forked session into the parent's cwd instead of the child launch cwd. */
|
|
11
|
+
export function alignForkedSessionCwd(sessionFile: string, cwd: string): void {
|
|
12
|
+
const lines = fs.readFileSync(sessionFile, "utf-8").split("\n").filter((line) => line.trim().length > 0);
|
|
13
|
+
const entries = lines.map((line, index) => {
|
|
14
|
+
try {
|
|
15
|
+
return JSON.parse(line) as SessionEntry;
|
|
16
|
+
} catch (error) {
|
|
17
|
+
const cause = error instanceof Error ? error : new Error(String(error));
|
|
18
|
+
throw new Error(`Unable to inspect forked session ${sessionFile}: invalid JSONL on line ${index + 1}: ${cause.message}`, { cause });
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
const header = entries[0];
|
|
22
|
+
if (header?.type !== "session") throw new Error(`Forked session ${sessionFile} does not start with a session header.`);
|
|
23
|
+
const effectiveCwd = fs.realpathSync.native(path.resolve(cwd));
|
|
24
|
+
if (header.cwd === effectiveCwd) return;
|
|
25
|
+
header.cwd = effectiveCwd;
|
|
26
|
+
fs.writeFileSync(sessionFile, `${entries.map((entry) => JSON.stringify(entry)).join("\n")}\n`, "utf-8");
|
|
27
|
+
}
|
|
@@ -51,6 +51,7 @@ export function projectAgentDefinition(agent: AgentConfig): Record<string, unkno
|
|
|
51
51
|
fast: agent.fast,
|
|
52
52
|
thinking: agent.thinking,
|
|
53
53
|
tools: agent.tools,
|
|
54
|
+
excludeTools: agent.excludeTools,
|
|
54
55
|
allowNestedSubagents: agent.allowNestedSubagents,
|
|
55
56
|
mcpDirectTools: agent.mcpDirectTools,
|
|
56
57
|
extensions: agent.extensions,
|
|
@@ -93,6 +94,7 @@ export interface LaunchBindingInput {
|
|
|
93
94
|
inheritSkills: boolean;
|
|
94
95
|
skills?: string[];
|
|
95
96
|
tools?: string[];
|
|
97
|
+
excludeTools?: string[];
|
|
96
98
|
extensions?: string[];
|
|
97
99
|
subagentOnlyExtensions?: string[];
|
|
98
100
|
mcpDirectTools?: string[];
|
|
@@ -120,6 +122,7 @@ export function projectLaunchBinding(input: LaunchBindingInput): Record<string,
|
|
|
120
122
|
inheritSkills: input.inheritSkills,
|
|
121
123
|
skills: input.skills,
|
|
122
124
|
tools: input.tools,
|
|
125
|
+
excludeTools: input.excludeTools,
|
|
123
126
|
extensions: input.extensions,
|
|
124
127
|
subagentOnlyExtensions: input.subagentOnlyExtensions,
|
|
125
128
|
mcpDirectTools: input.mcpDirectTools,
|
package/src/shared/types.ts
CHANGED
|
@@ -143,6 +143,20 @@ export interface WorkflowRecoveryAction {
|
|
|
143
143
|
taskRequired: true;
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
+
/**
|
|
147
|
+
* Bounded, host-generated identity for a resolved pi-subagents workflow resource.
|
|
148
|
+
* Permission/policy extensions can use it to distinguish resolved content from
|
|
149
|
+
* raw workflow scripts. This audit projection never grants execution authority.
|
|
150
|
+
*/
|
|
151
|
+
export interface WorkflowResourceProvenanceV1 {
|
|
152
|
+
kind: "workflow";
|
|
153
|
+
name: string;
|
|
154
|
+
version: number;
|
|
155
|
+
invocation: "named";
|
|
156
|
+
expansion: "resolved";
|
|
157
|
+
id: string;
|
|
158
|
+
}
|
|
159
|
+
|
|
146
160
|
/**
|
|
147
161
|
* Bounded, launch-declared workflow lane metadata. This is display and
|
|
148
162
|
* triage information only; capability ceilings, authorization, and cleanup
|
|
@@ -189,6 +203,7 @@ export type WorkflowReceiptEntry = WorkflowReceiptEntryResumability & {
|
|
|
189
203
|
requestedContext?: "fresh" | "fork";
|
|
190
204
|
resolvedContext?: "fresh" | "fork" | "mixed";
|
|
191
205
|
outputReference?: string;
|
|
206
|
+
acceptanceRecovery?: AcceptanceRecoveryMetadata;
|
|
192
207
|
externalAdapter?: ExternalCliReceiptMetadata;
|
|
193
208
|
continuation: { runIds: string[] };
|
|
194
209
|
};
|
|
@@ -199,6 +214,7 @@ export interface WorkflowReceipt {
|
|
|
199
214
|
state: WorkflowReceiptState;
|
|
200
215
|
createdAt: number;
|
|
201
216
|
entries: Record<string, WorkflowReceiptEntry>;
|
|
217
|
+
resource?: WorkflowResourceProvenanceV1;
|
|
202
218
|
hostSteps?: HostStepNodeV1[];
|
|
203
219
|
workflowChildren?: WorkflowChildSummaryV1;
|
|
204
220
|
workflowResolution?: WorkflowTerminalResolution;
|
|
@@ -330,7 +346,7 @@ export interface CompletionBatchConfig {
|
|
|
330
346
|
|
|
331
347
|
export interface WaitToolConfigObject {
|
|
332
348
|
enabled?: boolean;
|
|
333
|
-
/** Default blocking window for
|
|
349
|
+
/** Default blocking window for bg_wait calls that omit timeoutMs. */
|
|
334
350
|
defaultTimeoutMs?: number;
|
|
335
351
|
}
|
|
336
352
|
|
|
@@ -758,11 +774,13 @@ export interface SteeringRecoveryDescriptor {
|
|
|
758
774
|
model?: string;
|
|
759
775
|
modelProvider?: string;
|
|
760
776
|
modelOverrideFromParent?: boolean;
|
|
777
|
+
modelOrigin?: "explicit" | "inherited" | "configured";
|
|
761
778
|
fallbackModels?: string[];
|
|
762
779
|
fast?: boolean;
|
|
763
780
|
thinking?: string;
|
|
764
781
|
thinkingCeiling?: ThinkingLevel;
|
|
765
782
|
tools?: string[];
|
|
783
|
+
excludeTools?: string[];
|
|
766
784
|
allowNestedSubagents?: boolean;
|
|
767
785
|
extensions?: string[];
|
|
768
786
|
subagentOnlyExtensions?: string[];
|
|
@@ -977,6 +995,7 @@ export interface AcceptanceReviewGate {
|
|
|
977
995
|
|
|
978
996
|
export interface AcceptanceConfig {
|
|
979
997
|
level?: AcceptanceLevel;
|
|
998
|
+
report?: "on" | "off";
|
|
980
999
|
criteria?: Array<string | AcceptanceGate>;
|
|
981
1000
|
evidence?: AcceptanceEvidenceKind[];
|
|
982
1001
|
verify?: AcceptanceVerifyCommand[];
|
|
@@ -1086,6 +1105,18 @@ export type AcceptanceLedgerStatus =
|
|
|
1086
1105
|
| "reviewed"
|
|
1087
1106
|
| "accepted";
|
|
1088
1107
|
|
|
1108
|
+
/**
|
|
1109
|
+
* Durable child evidence that can be handed to a read-only review after the
|
|
1110
|
+
* acceptance envelope itself was rejected. This never upgrades acceptance;
|
|
1111
|
+
* the enclosing ledger remains rejected and the child remains unsuccessful.
|
|
1112
|
+
*/
|
|
1113
|
+
export interface AcceptanceRecoveryMetadata {
|
|
1114
|
+
status: "available-for-review";
|
|
1115
|
+
reason: "acceptance-metadata-rejected";
|
|
1116
|
+
reportPath: string;
|
|
1117
|
+
reportHash: string;
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1089
1120
|
export interface AcceptanceLedger {
|
|
1090
1121
|
status: AcceptanceLedgerStatus;
|
|
1091
1122
|
evidenceStatus: AcceptanceEvidenceStatus;
|
|
@@ -1095,6 +1126,7 @@ export interface AcceptanceLedger {
|
|
|
1095
1126
|
criteria: ResolvedAcceptanceGate[];
|
|
1096
1127
|
childReport?: AcceptanceReport;
|
|
1097
1128
|
childReportParseError?: string;
|
|
1129
|
+
recovery?: AcceptanceRecoveryMetadata;
|
|
1098
1130
|
runtimeChecks: AcceptanceRuntimeCheck[];
|
|
1099
1131
|
verifyRuns: AcceptanceVerifyResult[];
|
|
1100
1132
|
reviewResult?: AcceptanceReviewResult;
|
|
@@ -1283,7 +1315,7 @@ export interface WaitCompletionChild {
|
|
|
1283
1315
|
}
|
|
1284
1316
|
|
|
1285
1317
|
/**
|
|
1286
|
-
* Terminal completion observed for a run a
|
|
1318
|
+
* Terminal completion observed for a run a bg_wait call covered. Carries run
|
|
1287
1319
|
* identity and the artifact trail; output text stays in the tool result content.
|
|
1288
1320
|
*/
|
|
1289
1321
|
export interface WaitCompletion {
|
|
@@ -1312,7 +1344,7 @@ export interface AgentCapabilityRow {
|
|
|
1312
1344
|
restrictionSources?: string[];
|
|
1313
1345
|
aliases?: string[];
|
|
1314
1346
|
runner: { type: "pi" } | { type: "external-cli"; adapter?: string; capabilities: ExternalCliCapabilities } | { type: "external-job"; provider: string; available?: boolean; capabilities: ExternalJobRunnerStatus["capabilities"] };
|
|
1315
|
-
tools: { ambient: boolean; names: string[]; mcpDirectTools: string[]; mutationTools?: string[] };
|
|
1347
|
+
tools: { ambient: boolean; names: string[]; excludeTools?: string[]; mcpDirectTools: string[]; mutationTools?: string[] };
|
|
1316
1348
|
model?: { value?: string; fallbackModels?: string[]; thinking?: string | false };
|
|
1317
1349
|
execution?: { defaultAsync?: boolean; timeoutMs?: number };
|
|
1318
1350
|
output?: { path?: string; mode?: OutputMode };
|
|
@@ -1329,7 +1361,7 @@ export interface Details {
|
|
|
1329
1361
|
results: SingleResult[];
|
|
1330
1362
|
workflowChildren?: WorkflowChildSummaryV1;
|
|
1331
1363
|
/**
|
|
1332
|
-
* Terminal completion payloads for runs this
|
|
1364
|
+
* Terminal completion payloads for runs this bg_wait call observed
|
|
1333
1365
|
* finishing. Async completions travel as result files that are consumed and
|
|
1334
1366
|
* deleted after text delivery, so without this field their run and artifact
|
|
1335
1367
|
* identity never reaches tool_result details.
|
|
@@ -1401,6 +1433,7 @@ export interface Details {
|
|
|
1401
1433
|
mission?: MissionRecord;
|
|
1402
1434
|
workflow?: {
|
|
1403
1435
|
value?: unknown;
|
|
1436
|
+
resource?: WorkflowResourceProvenanceV1;
|
|
1404
1437
|
preflightWarnings?: string[];
|
|
1405
1438
|
trace: Array<{
|
|
1406
1439
|
operation: "run" | "status" | "steer" | "host";
|
|
@@ -2117,6 +2150,8 @@ export interface ActiveAsyncCapacitySnapshot {
|
|
|
2117
2150
|
export interface SubagentState {
|
|
2118
2151
|
baseCwd: string;
|
|
2119
2152
|
currentSessionId: string | null;
|
|
2153
|
+
/** Session for which active status projections were restored successfully. */
|
|
2154
|
+
statusProjectionSessionId?: string | null;
|
|
2120
2155
|
/** Reload-stable identity for this parent Pi process/window. */
|
|
2121
2156
|
completionOwnerId?: string;
|
|
2122
2157
|
/** Runtime-owned artifact resolution inputs used by Fleet transcript targeting. */
|
|
@@ -2283,6 +2318,8 @@ export interface RunSyncOptions {
|
|
|
2283
2318
|
allowIntercomDetach?: boolean;
|
|
2284
2319
|
intercomEvents?: IntercomEventBus;
|
|
2285
2320
|
onUpdate?: (r: import("@earendil-works/pi-agent-core").AgentToolResult<Details>) => void;
|
|
2321
|
+
/** Internal structured-delegation transport optimization: skip unchanged live snapshots. */
|
|
2322
|
+
suppressUnchangedDelegationUpdates?: boolean;
|
|
2286
2323
|
onControlEvent?: (event: ControlEvent) => void;
|
|
2287
2324
|
/** Exposes a non-terminating detach callback while the child is active. */
|
|
2288
2325
|
onDetachReady?: (detach: (reason?: string) => boolean) => void;
|
|
@@ -2318,6 +2355,8 @@ export interface RunSyncOptions {
|
|
|
2318
2355
|
fast?: boolean;
|
|
2319
2356
|
/** The override came from the running parent session, not configuration. */
|
|
2320
2357
|
modelOverrideFromParent?: boolean;
|
|
2358
|
+
/** How the launch model was selected: explicit per-call, configured agent primary, or inherited parent. */
|
|
2359
|
+
modelOrigin?: "explicit" | "inherited" | "configured";
|
|
2321
2360
|
/** LLM intent arbiter for the completion mutation guard (rescues read-only review runs). */
|
|
2322
2361
|
llmIntentArbiter?: import("../runs/shared/llm-intent-arbiter.ts").TaskMutationArbiter;
|
|
2323
2362
|
/** Override the agent's default thinking level for this run */
|