pi-subagents 0.65.1 → 0.66.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/README.md +1 -1
- package/agents/researcher.md +23 -13
- package/docs/agents.md +16 -2
- package/docs/configuration.md +18 -0
- package/docs/extension-api.md +91 -0
- package/docs/models.md +58 -1
- package/docs/observability.md +42 -2
- package/docs/tool-reference.md +3 -3
- package/docs/workflows.md +14 -7
- package/package.json +2 -1
- package/skills/pi-subagents/references/execution-controls.md +14 -1
- package/skills/pi-subagents/references/management-authoring-rpc.md +2 -1
- package/src/agents/advertised-agent-prompt.ts +63 -0
- package/src/agents/agent-management.ts +14 -1
- package/src/agents/agent-serializer.ts +2 -0
- package/src/agents/agents.ts +8 -0
- package/src/api/shared-types.ts +1 -1
- package/src/api/workflow-resources.ts +6 -0
- package/src/extension/index.ts +40 -2
- package/src/extension/public-execution.ts +0 -1
- package/src/extension/rpc.ts +4 -21
- package/src/extension/schemas.ts +8 -6
- package/src/extension/tool-description.ts +6 -5
- package/src/intercom/native-supervisor-channel.ts +82 -54
- package/src/runs/background/active-async-capacity.ts +18 -18
- package/src/runs/background/async-job-tracker.ts +35 -3
- package/src/runs/background/async-status-snapshot.ts +10 -12
- package/src/runs/background/async-status.ts +17 -9
- package/src/runs/background/auto-drain.ts +40 -29
- package/src/runs/background/chain-root-attachment.ts +8 -0
- package/src/runs/background/control-channel.ts +78 -44
- package/src/runs/background/notify.ts +86 -12
- package/src/runs/background/owned-process-tree.ts +6 -6
- package/src/runs/background/process-terminal.ts +23 -23
- package/src/runs/background/run-child-session.ts +60 -32
- package/src/runs/background/run-status.ts +75 -5
- package/src/runs/background/runner-aliases.ts +18 -7
- package/src/runs/background/runner-child-launch.ts +86 -0
- package/src/runs/background/stale-run-reconciler.ts +3 -1
- package/src/runs/background/subagent-runner.ts +412 -209
- package/src/runs/background/subagent-wait.ts +3 -0
- package/src/runs/background/wait-completions.ts +4 -0
- package/src/runs/foreground/async-steering-action.ts +19 -0
- package/src/runs/foreground/execution.ts +101 -25
- package/src/runs/foreground/subagent-executor.ts +474 -197
- package/src/runs/foreground/workflow-detach-reconcile.ts +8 -5
- package/src/runs/foreground/workflow-foreground-steering.ts +56 -2
- package/src/runs/shared/acceptance.ts +2 -2
- package/src/runs/shared/agent-contract.ts +1 -1
- package/src/runs/shared/async-status-projection.ts +47 -47
- package/src/runs/shared/child-hooks.ts +151 -2
- package/src/runs/shared/child-launch.ts +18 -13
- package/src/runs/shared/child-session.ts +42 -6
- package/src/runs/shared/child-tool-plan.ts +2 -2
- package/src/runs/shared/completion-evidence.ts +2 -2
- package/src/runs/shared/completion-guard.ts +1 -0
- package/src/runs/shared/host-step-status.ts +11 -11
- package/src/runs/shared/llm-intent-arbiter.ts +10 -9
- package/src/runs/shared/model-fallback.ts +10 -6
- package/src/runs/shared/nested-events.ts +5 -5
- package/src/runs/shared/orca-progress-tabs.ts +6 -0
- package/src/runs/shared/parallel-handoff.ts +57 -12
- package/src/runs/shared/parallel-utils.ts +2 -2
- package/src/runs/shared/readonly-drain-observation.ts +42 -0
- package/src/runs/shared/readonly-model-continuation.ts +69 -0
- package/src/runs/shared/readonly-session-evidence.ts +307 -0
- package/src/runs/shared/run-fanout-budget.ts +8 -8
- package/src/runs/shared/runtime-acknowledged-extensions.ts +3 -3
- package/src/runs/shared/subagent-prompt-runtime.ts +13 -3
- package/src/runs/shared/worktree-setup-command.ts +190 -0
- package/src/runs/shared/worktree.ts +332 -204
- package/src/shared/types.ts +81 -60
- package/src/shared/utils.ts +7 -2
- package/src/shared/workflow-child-permit.ts +18 -13
- package/src/tui/fleet.ts +11 -5
- package/src/tui/render.ts +23 -5
- package/src/workflows/chat-progress.ts +3 -3
- package/src/workflows/scripted-workflow.ts +38 -10
- package/src/workflows/workflow-checklist.ts +11 -15
- package/src/workflows/workflow-child-summary.ts +57 -8
- package/src/workflows/workflow-preflight.ts +19 -19
- package/src/workflows/workflow-receipt.ts +3 -3
- package/src/workflows/workflow-resources.ts +96 -21
- package/src/workflows/workflow-settlement.ts +3 -0
|
@@ -6,12 +6,13 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import * as fs from "node:fs";
|
|
8
8
|
import * as path from "node:path";
|
|
9
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
9
10
|
import type { ChildWatchdogConfig, ChildWatchdogStatusEvent } from "../../watchdog/child-status.ts";
|
|
10
11
|
import type { ThinkingLevel } from "../../shared/model-info.ts";
|
|
11
12
|
import { intersectThinkingCeilings } from "../../shared/thinking-ceiling.ts";
|
|
12
13
|
import {
|
|
13
14
|
resolveChildDepth,
|
|
14
|
-
type
|
|
15
|
+
type LaunchResolvedChildExtensions,
|
|
15
16
|
type ResolvedToolBudget,
|
|
16
17
|
type RunFanoutBudgetDescriptor,
|
|
17
18
|
} from "../../shared/types.ts";
|
|
@@ -20,8 +21,7 @@ import type { McpRuntimeSnapshotHost } from "./mcp-direct-tool-allowlist.ts";
|
|
|
20
21
|
import type { PermissionRules } from "./permissions.ts";
|
|
21
22
|
import type { StructuredOutputRuntime } from "./structured-output.ts";
|
|
22
23
|
import type { ChildToolDiagnostic } from "./tool-availability.ts";
|
|
23
|
-
import type {
|
|
24
|
-
import { projectRuntimeAcknowledgedExtensions } from "./runtime-acknowledged-extensions.ts";
|
|
24
|
+
import type { RuntimeAcknowledgedChildExtensions } from "../../shared/types.ts";
|
|
25
25
|
import { encodeExtensionBindings, PI_SUBAGENT_EXTENSION_BINDINGS_ENV, type ExtensionBindings } from "./extension-bindings.ts";
|
|
26
26
|
import type { ResolvedSubagentCapabilityCeiling, SubagentCapabilityAudit } from "./capability-ceiling.ts";
|
|
27
27
|
import {
|
|
@@ -32,7 +32,8 @@ import {
|
|
|
32
32
|
type PiLaunchToolPlan,
|
|
33
33
|
} from "./child-tool-plan.ts";
|
|
34
34
|
import type { ChildRuntimeConfig } from "./child-runtime-config.ts";
|
|
35
|
-
import {
|
|
35
|
+
import { createCapturedChildHooks, withChildSessionErrorReporting } from "./child-hooks.ts";
|
|
36
|
+
import type { ChildTranscriptWriter } from "../../shared/child-transcript.ts";
|
|
36
37
|
import type { ChildSessionLaunch, ChildSessionStorage } from "./child-session.ts";
|
|
37
38
|
|
|
38
39
|
/** Environment variable pi-mcp-adapter reads for the tools a child may expose. */
|
|
@@ -114,9 +115,10 @@ export interface BuildInProcessChildLaunchInput {
|
|
|
114
115
|
}
|
|
115
116
|
|
|
116
117
|
export interface InProcessChildCapture {
|
|
118
|
+
completionIntentContext?(): Pick<ExtensionContext, "model" | "modelRegistry"> | undefined;
|
|
117
119
|
structuredOutput(): { called: boolean; value?: unknown; acceptanceReport?: unknown; acceptanceReportProvided: boolean };
|
|
118
120
|
toolDiagnostic(): ChildToolDiagnostic | undefined;
|
|
119
|
-
runtimeAcknowledgedExtensions():
|
|
121
|
+
runtimeAcknowledgedExtensions(): RuntimeAcknowledgedChildExtensions | undefined;
|
|
120
122
|
}
|
|
121
123
|
|
|
122
124
|
export interface InProcessChildLaunch {
|
|
@@ -124,11 +126,16 @@ export interface InProcessChildLaunch {
|
|
|
124
126
|
config: ChildRuntimeConfig;
|
|
125
127
|
session: Omit<ChildSessionLaunch, "onExtensionError">;
|
|
126
128
|
capture: InProcessChildCapture;
|
|
127
|
-
launchResolvedExtensions:
|
|
129
|
+
launchResolvedExtensions: LaunchResolvedChildExtensions;
|
|
128
130
|
warnings: string[];
|
|
129
131
|
capabilityAudit?: SubagentCapabilityAudit;
|
|
130
132
|
}
|
|
131
133
|
|
|
134
|
+
/** Actual host create-input boundary. Evidence remains explicitly opt-in and dormant in production. */
|
|
135
|
+
export function createReportedChildSessionInput(launch: InProcessChildLaunch, transcriptWriter?: ChildTranscriptWriter): ChildSessionLaunch {
|
|
136
|
+
return withChildSessionErrorReporting(launch.session, transcriptWriter);
|
|
137
|
+
}
|
|
138
|
+
|
|
132
139
|
/** Escape XML-significant characters in a string for safe attribute interpolation. */
|
|
133
140
|
function escapeXmlAttr(value: string): string {
|
|
134
141
|
return value
|
|
@@ -214,8 +221,6 @@ export function buildInProcessChildLaunch(input: BuildInProcessChildLaunchInput)
|
|
|
214
221
|
let structuredAcceptanceReport: unknown;
|
|
215
222
|
let structuredCalled = false;
|
|
216
223
|
let structuredAcceptanceProvided = false;
|
|
217
|
-
let toolDiagnostic: ChildToolDiagnostic | undefined;
|
|
218
|
-
let acknowledgedIds: string[] | undefined;
|
|
219
224
|
|
|
220
225
|
const config: ChildRuntimeConfig = {
|
|
221
226
|
...(input.runId ? { runId: input.runId } : {}),
|
|
@@ -264,10 +269,9 @@ export function buildInProcessChildLaunch(input: BuildInProcessChildLaunchInput)
|
|
|
264
269
|
: {}),
|
|
265
270
|
...(toolPlan.requiredChildTools.length > 0 ? { requiredTools: toolPlan.requiredChildTools } : {}),
|
|
266
271
|
...(toolPlan.effectiveMcpTools.length > 0 ? { mcpDirectTools: toolPlan.effectiveMcpTools } : {}),
|
|
267
|
-
toolDiagnostic: (diagnostic) => { toolDiagnostic = diagnostic; },
|
|
268
|
-
runtimeAcknowledgements: (ids) => { acknowledgedIds = ids; },
|
|
269
272
|
fast: input.fast === true,
|
|
270
273
|
};
|
|
274
|
+
const capturedHooks = createCapturedChildHooks(config, input.host === "runner");
|
|
271
275
|
|
|
272
276
|
const extensionPaths = toolPlan.extensionArgs.filter((extensionPath) => !isSubagentRuntimeExtensionPath(extensionPath));
|
|
273
277
|
const ambientExtensions = input.host === "runner" && !toolPlan.disableAmbientExtensions;
|
|
@@ -288,7 +292,7 @@ export function buildInProcessChildLaunch(input: BuildInProcessChildLaunchInput)
|
|
|
288
292
|
...(!toolPlan.explicitToolAllowlist && toolPlan.excludeTools.length > 0 ? { excludeTools: toolPlan.excludeTools } : {}),
|
|
289
293
|
extensionPaths,
|
|
290
294
|
ambientExtensions,
|
|
291
|
-
hooks:
|
|
295
|
+
hooks: capturedHooks.hooks,
|
|
292
296
|
...(input.host === "runner" ? { processEnv: childProcessEnv(input, toolPlan) } : {}),
|
|
293
297
|
runtime: config,
|
|
294
298
|
noSkills: !input.inheritSkills,
|
|
@@ -303,9 +307,10 @@ export function buildInProcessChildLaunch(input: BuildInProcessChildLaunchInput)
|
|
|
303
307
|
config,
|
|
304
308
|
session,
|
|
305
309
|
capture: {
|
|
310
|
+
completionIntentContext: capturedHooks.completionIntentContext,
|
|
306
311
|
structuredOutput: () => ({ called: structuredCalled, value: structuredValue, acceptanceReport: structuredAcceptanceReport, acceptanceReportProvided: structuredAcceptanceProvided }),
|
|
307
|
-
toolDiagnostic:
|
|
308
|
-
runtimeAcknowledgedExtensions:
|
|
312
|
+
toolDiagnostic: capturedHooks.toolDiagnostic,
|
|
313
|
+
runtimeAcknowledgedExtensions: capturedHooks.runtimeAcknowledgedExtensions,
|
|
309
314
|
},
|
|
310
315
|
launchResolvedExtensions,
|
|
311
316
|
warnings: toolPlan.warnings,
|
|
@@ -12,6 +12,14 @@ import type { AgentMessage } from "@earendil-works/pi-agent-core";
|
|
|
12
12
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
13
13
|
import { getAgentDir } from "../../shared/utils.ts";
|
|
14
14
|
import type { ChildRuntimeConfig } from "./child-runtime-config.ts";
|
|
15
|
+
import { prepareReadonlySessionEvidence } from "./readonly-session-evidence.ts";
|
|
16
|
+
import { toModelInfo, type ModelInfo } from "../../shared/model-info.ts";
|
|
17
|
+
|
|
18
|
+
// Private runtime authority for host continuation planning; injected factories have none.
|
|
19
|
+
const readonlyModels = new WeakMap<ChildSession, { current: ModelInfo; resolve(reference: string): ModelInfo | undefined; requestBytes: number }>();
|
|
20
|
+
export function getReadonlyChildModels(child: ChildSession) {
|
|
21
|
+
return readonlyModels.get(child);
|
|
22
|
+
}
|
|
15
23
|
|
|
16
24
|
export interface ChildSessionEvent {
|
|
17
25
|
type: string;
|
|
@@ -185,6 +193,7 @@ export function createDefaultChildSessionFactory(options: DefaultChildSessionFac
|
|
|
185
193
|
};
|
|
186
194
|
return {
|
|
187
195
|
async create(launch) {
|
|
196
|
+
const observeReadonly = prepareReadonlySessionEvidence(launch);
|
|
188
197
|
const pi = await loadPiCodingAgent();
|
|
189
198
|
const modelRuntime = await sharedRuntime(pi);
|
|
190
199
|
const agentDir = getAgentDir();
|
|
@@ -208,8 +217,11 @@ export function createDefaultChildSessionFactory(options: DefaultChildSessionFac
|
|
|
208
217
|
const open = async () => {
|
|
209
218
|
applyProcessEnv(launch.processEnv);
|
|
210
219
|
if (!resetExtensionCacheOnReload(loader) && (launch.ambientExtensions || launch.extensionPaths.length)) launch.onExtensionError?.({ extensionPath: "<loader>", event: "load", error: new Error("pi's extension cache reset is unavailable; extensions loaded into this child share module state with other sessions in this process.") });
|
|
211
|
-
|
|
220
|
+
observeReadonly?.loadingHooks(true);
|
|
221
|
+
try { await loader.reload(); } finally { observeReadonly?.loadingHooks(false); }
|
|
212
222
|
await flushQueuedProviderRegistrations(loader, modelRuntime, launch.onExtensionError);
|
|
223
|
+
// No await between receipt validation and the SDK's permissive file open.
|
|
224
|
+
observeReadonly?.beforeOpen();
|
|
213
225
|
const sessionManager = launch.storage.kind === "file"
|
|
214
226
|
? pi.SessionManager.open(launch.storage.sessionFile, undefined, launch.cwd)
|
|
215
227
|
: launch.storage.kind === "dir"
|
|
@@ -217,6 +229,7 @@ export function createDefaultChildSessionFactory(options: DefaultChildSessionFac
|
|
|
217
229
|
: launch.storage.kind === "memory"
|
|
218
230
|
? pi.SessionManager.inMemory(launch.cwd)
|
|
219
231
|
: pi.SessionManager.create(launch.cwd);
|
|
232
|
+
observeReadonly?.opened(sessionManager);
|
|
220
233
|
const resolvedModel = launch.model
|
|
221
234
|
? pi.resolveCliModel({ cliModel: launch.model, modelRuntime })
|
|
222
235
|
: undefined;
|
|
@@ -248,6 +261,9 @@ export function createDefaultChildSessionFactory(options: DefaultChildSessionFac
|
|
|
248
261
|
const opened = loading.catch(() => {}).then(open);
|
|
249
262
|
loading = opened;
|
|
250
263
|
const session = await opened;
|
|
264
|
+
let evidence: ReturnType<NonNullable<typeof observeReadonly>["observe"]>;
|
|
265
|
+
try { evidence = observeReadonly?.observe(pi, modelRuntime, session); }
|
|
266
|
+
catch (error) { session.dispose(); throw error; }
|
|
251
267
|
let pending: Promise<void> | undefined;
|
|
252
268
|
// pi's own hosts emit `session_shutdown` before disposing a session so the
|
|
253
269
|
// extensions loaded into it (ambient extensions included) release their
|
|
@@ -255,19 +271,29 @@ export function createDefaultChildSessionFactory(options: DefaultChildSessionFac
|
|
|
255
271
|
const shutdown = async (): Promise<void> => {
|
|
256
272
|
try {
|
|
257
273
|
const runner = session.extensionRunner;
|
|
258
|
-
if (runner.hasHandlers("session_shutdown"))
|
|
274
|
+
if (runner.hasHandlers("session_shutdown")) {
|
|
275
|
+
evidence?.beforeShutdown();
|
|
276
|
+
const settled = await Promise.race([runner.emit({ type: "session_shutdown", reason: "quit" }).then(() => true), new Promise<false>((resolve) => setTimeout(() => resolve(false), shutdownTimeoutMs).unref?.())]);
|
|
277
|
+
if (!settled) evidence?.invalidate();
|
|
278
|
+
}
|
|
259
279
|
} catch (error) {
|
|
280
|
+
evidence?.invalidate();
|
|
260
281
|
launch.onExtensionError?.({ extensionPath: "<session>", event: "session_shutdown", error });
|
|
261
282
|
} finally {
|
|
262
283
|
session.dispose();
|
|
284
|
+
evidence?.finish(child);
|
|
263
285
|
}
|
|
264
286
|
};
|
|
265
287
|
const child: ChildSession = {
|
|
266
288
|
subscribe: (listener) => session.subscribe((event) => listener(event as unknown as ChildSessionEvent)),
|
|
267
|
-
prompt: (text) =>
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
289
|
+
prompt: (text) => {
|
|
290
|
+
if (!evidence) return session.prompt(text);
|
|
291
|
+
try { evidence.start(); } catch (error) { return Promise.reject(error); }
|
|
292
|
+
return session.prompt(text).then(() => evidence?.settled(), (error) => { evidence?.invalidate(); throw error; });
|
|
293
|
+
},
|
|
294
|
+
steer: (text) => { evidence?.invalidate(); return session.steer(text); },
|
|
295
|
+
followUp: (text) => { evidence?.invalidate(); return session.followUp(text); },
|
|
296
|
+
abort: () => { evidence?.invalidate(); return session.abort(); },
|
|
271
297
|
dispose: () => {
|
|
272
298
|
if (!pending) {
|
|
273
299
|
live.delete(child);
|
|
@@ -283,6 +309,16 @@ export function createDefaultChildSessionFactory(options: DefaultChildSessionFac
|
|
|
283
309
|
get sessionId() { return session.sessionId; },
|
|
284
310
|
get modelId() { return session.model ? `${session.model.provider}/${session.model.id}` : undefined; },
|
|
285
311
|
};
|
|
312
|
+
if (evidence && session.model) readonlyModels.set(child, {
|
|
313
|
+
current: toModelInfo(session.model),
|
|
314
|
+
requestBytes: Buffer.byteLength(session.systemPrompt) + Buffer.byteLength(JSON.stringify(session.agent.state.tools)),
|
|
315
|
+
resolve(reference) {
|
|
316
|
+
try {
|
|
317
|
+
const resolved = pi.resolveCliModel({ cliModel: reference, modelRuntime });
|
|
318
|
+
return !resolved.error && resolved.model ? toModelInfo(resolved.model) : undefined;
|
|
319
|
+
} catch { return undefined; }
|
|
320
|
+
},
|
|
321
|
+
});
|
|
286
322
|
live.add(child);
|
|
287
323
|
return child;
|
|
288
324
|
},
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
import {
|
|
17
17
|
TEMP_ROOT_DIR,
|
|
18
18
|
type JsonSchemaObject,
|
|
19
|
-
type
|
|
19
|
+
type LaunchResolvedChildExtensions,
|
|
20
20
|
} from "../../shared/types.ts";
|
|
21
21
|
import { THINKING_LEVELS } from "../../shared/model-info.ts";
|
|
22
22
|
import { getAgentDir } from "../../shared/utils.ts";
|
|
@@ -214,7 +214,7 @@ export function projectLaunchResolvedChildExtensions(
|
|
|
214
214
|
| "extensionArgs"
|
|
215
215
|
| "disableAmbientExtensions"
|
|
216
216
|
>,
|
|
217
|
-
):
|
|
217
|
+
): LaunchResolvedChildExtensions {
|
|
218
218
|
const runtime = boundedExtensionIdentifiers(toolPlan.runtimeExtensions);
|
|
219
219
|
const configured = boundedExtensionIdentifiers(toolPlan.configuredExtensions);
|
|
220
220
|
const effective = boundedExtensionIdentifiers(toolPlan.extensionArgs);
|
|
@@ -26,7 +26,7 @@ export function planCompletionEvidence(input: {
|
|
|
26
26
|
mutationAttemptObserved: boolean;
|
|
27
27
|
mutationEvidence?: TrackedMutationEvidence;
|
|
28
28
|
arbiterRescued?: boolean;
|
|
29
|
-
|
|
29
|
+
agentContractEnabled: boolean;
|
|
30
30
|
}): CompletionEvidencePlan {
|
|
31
31
|
const guardBlocked = input.guard?.blocked === true;
|
|
32
32
|
const guardTriggered = input.guardTriggered
|
|
@@ -59,7 +59,7 @@ export function planCompletionEvidence(input: {
|
|
|
59
59
|
mutationExpected,
|
|
60
60
|
mutationAttempted,
|
|
61
61
|
fileMutation,
|
|
62
|
-
legacyFailureError: guardTriggered && !input.
|
|
62
|
+
legacyFailureError: guardTriggered && !input.agentContractEnabled
|
|
63
63
|
? MISSING_IMPLEMENTATION_MUTATION_ERROR
|
|
64
64
|
: undefined,
|
|
65
65
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
AsyncStatus,
|
|
3
|
-
|
|
3
|
+
HostStepNode,
|
|
4
4
|
HostStepState,
|
|
5
5
|
HostStepVerdict,
|
|
6
6
|
WorkflowGraphNode,
|
|
@@ -57,7 +57,7 @@ function assertTimestamp(value: unknown, field: string, source: string, required
|
|
|
57
57
|
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) throw new Error(`Invalid host step '${source}': ${field} must be a non-negative safe integer.`);
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
function assertFreshness(value: unknown, source: string): asserts value is
|
|
60
|
+
function assertFreshness(value: unknown, source: string): asserts value is HostStepNode["freshness"] {
|
|
61
61
|
if (!isRecord(value)) throw new Error(`Invalid host step '${source}': freshness must be an object.`);
|
|
62
62
|
const unknownFields = Object.keys(value).filter((field) => !FRESHNESS_FIELDS.has(field));
|
|
63
63
|
if (unknownFields.length > 0) throw new Error(`Invalid host step '${source}': freshness has unsupported fields: ${unknownFields.join(", ")}.`);
|
|
@@ -70,7 +70,7 @@ function assertFreshness(value: unknown, source: string): asserts value is HostS
|
|
|
70
70
|
* Validate the persisted host-step contract. This deliberately rejects unknown
|
|
71
71
|
* fields and unbounded values so status and receipt loaders fail closed.
|
|
72
72
|
*/
|
|
73
|
-
export function assertHostStepNode(value: unknown, source = "status"): asserts value is
|
|
73
|
+
export function assertHostStepNode(value: unknown, source = "status"): asserts value is HostStepNode {
|
|
74
74
|
if (!isRecord(value)) throw new Error(`Invalid host step '${source}': expected an object.`);
|
|
75
75
|
const unknownFields = Object.keys(value).filter((field) => !HOST_STEP_FIELDS.has(field));
|
|
76
76
|
if (unknownFields.length > 0) throw new Error(`Invalid host step '${source}': unsupported fields: ${unknownFields.join(", ")}.`);
|
|
@@ -105,7 +105,7 @@ export function assertHostStepNode(value: unknown, source = "status"): asserts v
|
|
|
105
105
|
assertTimestamp(value.deadlineAt, "deadlineAt", source);
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
export function parseHostStepNode(value: unknown, source = "status"):
|
|
108
|
+
export function parseHostStepNode(value: unknown, source = "status"): HostStepNode {
|
|
109
109
|
assertHostStepNode(value, source);
|
|
110
110
|
return {
|
|
111
111
|
...value,
|
|
@@ -113,7 +113,7 @@ export function parseHostStepNode(value: unknown, source = "status"): HostStepNo
|
|
|
113
113
|
};
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
export function assertUniqueHostStepIds(hostSteps: readonly
|
|
116
|
+
export function assertUniqueHostStepIds(hostSteps: readonly HostStepNode[], source = "status"): void {
|
|
117
117
|
const ids = new Set<string>();
|
|
118
118
|
for (const hostStep of hostSteps) {
|
|
119
119
|
if (ids.has(hostStep.id)) throw new Error(`Invalid host step '${source}': duplicate host step id '${hostStep.id}'.`);
|
|
@@ -122,11 +122,11 @@ export function assertUniqueHostStepIds(hostSteps: readonly HostStepNodeV1[], so
|
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
/** Return only valid host nodes so an untrusted in-memory projection fails closed. */
|
|
125
|
-
export function validHostStepNodes(graph: WorkflowGraphSnapshot | undefined):
|
|
125
|
+
export function validHostStepNodes(graph: WorkflowGraphSnapshot | undefined): HostStepNode[] {
|
|
126
126
|
const nodes = graph?.nodes ?? [];
|
|
127
127
|
const nodeIdCounts = new Map<string, number>();
|
|
128
128
|
for (const node of nodes) nodeIdCounts.set(node.id, (nodeIdCounts.get(node.id) ?? 0) + 1);
|
|
129
|
-
const hostSteps:
|
|
129
|
+
const hostSteps: HostStepNode[] = [];
|
|
130
130
|
for (const [index, node] of nodes.entries()) {
|
|
131
131
|
if (node.kind !== "host-step") continue;
|
|
132
132
|
try {
|
|
@@ -147,7 +147,7 @@ export function assertWorkflowGraphHostSteps(graph: WorkflowGraphSnapshot | unde
|
|
|
147
147
|
if (!Array.isArray(graph.nodes)) throw new Error(`Invalid host step '${source}.workflowGraph': nodes must be an array.`);
|
|
148
148
|
const hostStepCount = graph.nodes.filter((node) => node.kind === "host-step").length;
|
|
149
149
|
if (hostStepCount > HOST_STEP_MAX_COUNT) throw new Error(`Invalid host step '${source}': workflowGraph contains more than ${HOST_STEP_MAX_COUNT} host steps.`);
|
|
150
|
-
const hostSteps:
|
|
150
|
+
const hostSteps: HostStepNode[] = [];
|
|
151
151
|
for (const [index, node] of graph.nodes.entries()) {
|
|
152
152
|
if (node.kind !== "host-step") continue;
|
|
153
153
|
const hostStep = parseHostStepNode(node.hostStep, `${source}.workflowGraph.nodes[${index}].hostStep`);
|
|
@@ -161,7 +161,7 @@ export function assertWorkflowGraphHostSteps(graph: WorkflowGraphSnapshot | unde
|
|
|
161
161
|
}
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
-
export function hostStepWorkflowNode(hostStep:
|
|
164
|
+
export function hostStepWorkflowNode(hostStep: HostStepNode): WorkflowGraphNode {
|
|
165
165
|
assertHostStepNode(hostStep, hostStep.id);
|
|
166
166
|
return {
|
|
167
167
|
id: hostStep.id,
|
|
@@ -172,7 +172,7 @@ export function hostStepWorkflowNode(hostStep: HostStepNodeV1): WorkflowGraphNod
|
|
|
172
172
|
};
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
-
function workflowNodeStatus(hostStep:
|
|
175
|
+
function workflowNodeStatus(hostStep: HostStepNode): WorkflowNodeStatus {
|
|
176
176
|
if (hostStep.state === "pending") return "pending";
|
|
177
177
|
if (hostStep.state === "running") return "running";
|
|
178
178
|
if (hostStep.state === "cancelled") return "stopped";
|
|
@@ -187,7 +187,7 @@ function workflowNodeStatus(hostStep: HostStepNodeV1): WorkflowNodeStatus {
|
|
|
187
187
|
*/
|
|
188
188
|
export function upsertHostStep(input: {
|
|
189
189
|
status: AsyncStatus;
|
|
190
|
-
hostStep:
|
|
190
|
+
hostStep: HostStepNode;
|
|
191
191
|
persist: (status: AsyncStatus) => void;
|
|
192
192
|
}): AsyncStatus {
|
|
193
193
|
const hostStep = parseHostStepNode(input.hostStep, input.hostStep.id);
|
|
@@ -76,9 +76,10 @@ export interface TaskMutationArbiterOptions {
|
|
|
76
76
|
const DEFAULT_ARBITER_TIMEOUT_MS = 10_000;
|
|
77
77
|
|
|
78
78
|
type RegistryModel = ReturnType<NonNullable<ExtensionContext["modelRegistry"]["find"]>>;
|
|
79
|
+
type ArbiterModelContext = Pick<ExtensionContext, "model" | "modelRegistry">;
|
|
79
80
|
|
|
80
81
|
function resolveArbiterModel(
|
|
81
|
-
ctx:
|
|
82
|
+
ctx: ArbiterModelContext,
|
|
82
83
|
options?: TaskMutationArbiterOptions,
|
|
83
84
|
): NonNullable<RegistryModel> | null {
|
|
84
85
|
const registry = ctx.modelRegistry as {
|
|
@@ -98,7 +99,7 @@ function resolveArbiterModel(
|
|
|
98
99
|
}
|
|
99
100
|
|
|
100
101
|
function resolveArbiterRuntime(
|
|
101
|
-
ctx:
|
|
102
|
+
ctx: ArbiterModelContext,
|
|
102
103
|
options?: TaskMutationArbiterOptions,
|
|
103
104
|
): ArbiterRuntime | null {
|
|
104
105
|
const model = resolveArbiterModel(ctx, options);
|
|
@@ -117,9 +118,9 @@ function resolveArbiterRuntime(
|
|
|
117
118
|
}
|
|
118
119
|
|
|
119
120
|
async function resolveArbiterAuth(
|
|
120
|
-
ctx:
|
|
121
|
+
ctx: ArbiterModelContext,
|
|
121
122
|
model: RegistryModel,
|
|
122
|
-
): Promise<ArbiterAuth> {
|
|
123
|
+
): Promise<ArbiterAuth | undefined> {
|
|
123
124
|
const registry = ctx.modelRegistry as {
|
|
124
125
|
getApiKeyAndHeaders?: (m: RegistryModel) => Promise<{
|
|
125
126
|
ok: boolean;
|
|
@@ -135,14 +136,14 @@ async function resolveArbiterAuth(
|
|
|
135
136
|
if (!registry.getApiKeyAndHeaders) return {};
|
|
136
137
|
try {
|
|
137
138
|
const auth = await registry.getApiKeyAndHeaders(model);
|
|
138
|
-
if (auth.ok === false) return
|
|
139
|
+
if (auth.ok === false) return undefined;
|
|
139
140
|
return {
|
|
140
141
|
...(auth.apiKey ? { apiKey: auth.apiKey } : {}),
|
|
141
142
|
...(auth.headers ? { headers: auth.headers } : {}),
|
|
142
143
|
...(auth.env ? { env: auth.env } : {}),
|
|
143
144
|
};
|
|
144
145
|
} catch {
|
|
145
|
-
return
|
|
146
|
+
return undefined;
|
|
146
147
|
}
|
|
147
148
|
}
|
|
148
149
|
|
|
@@ -230,9 +231,9 @@ async function runArbitration(
|
|
|
230
231
|
}
|
|
231
232
|
}
|
|
232
233
|
|
|
233
|
-
/** Create a memoized arbiter bound to the
|
|
234
|
+
/** Create a memoized arbiter bound to the supplied model services, or undefined when disabled/unavailable. */
|
|
234
235
|
export function createTaskMutationArbiter(
|
|
235
|
-
ctx:
|
|
236
|
+
ctx: ArbiterModelContext,
|
|
236
237
|
options?: TaskMutationArbiterOptions,
|
|
237
238
|
): TaskMutationArbiter | undefined {
|
|
238
239
|
if (process.env.PI_SUBAGENTS_LLM_INTENT_ARBITER === "0") return undefined;
|
|
@@ -244,7 +245,7 @@ export function createTaskMutationArbiter(
|
|
|
244
245
|
const cached = cache.get(key);
|
|
245
246
|
if (cached) return cached;
|
|
246
247
|
const auth = await resolveArbiterAuth(ctx, runtime.model);
|
|
247
|
-
const verdict = await runArbitration(runtime, auth, task);
|
|
248
|
+
const verdict = auth ? await runArbitration(runtime, auth, task) : "unavailable";
|
|
248
249
|
if (cache.size > 200) cache.clear();
|
|
249
250
|
cache.set(key, verdict);
|
|
250
251
|
return verdict;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { splitKnownThinkingSuffix, type ModelInfo as AvailableModelInfo } from "../../shared/model-info.ts";
|
|
1
|
+
import { splitKnownThinkingSuffix as splitThinkingSuffix, type ModelInfo as AvailableModelInfo } from "../../shared/model-info.ts";
|
|
2
2
|
import type { Usage } from "../../shared/types.ts";
|
|
3
3
|
import { filterFallbackCandidates, findModelExclusion, parseModelKey, recordModelFailure } from "./model-exclusions.ts";
|
|
4
4
|
import { checkModelScope, type ModelScopeCheckRule, type ModelScopeViolation, type ModelSource } from "./model-scope.ts";
|
|
@@ -14,9 +14,7 @@ interface ModelAttemptSummary {
|
|
|
14
14
|
usage?: Usage;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
export
|
|
18
|
-
return splitKnownThinkingSuffix(model);
|
|
19
|
-
}
|
|
17
|
+
export { splitThinkingSuffix };
|
|
20
18
|
|
|
21
19
|
/** Aliases apply only to the resolved launch candidate (without its thinking suffix) and the exact raw response ID. */
|
|
22
20
|
export function formatSubagentModelVerificationError(
|
|
@@ -38,7 +36,7 @@ export function formatSubagentModelVerificationError(
|
|
|
38
36
|
const expectedFullIdLeaf = expectedEntry.fullId.slice(expectedEntry.fullId.lastIndexOf("/") + 1);
|
|
39
37
|
if (expectedIdLeaf === observedBase || expectedFullIdLeaf === observedBase) return undefined;
|
|
40
38
|
}
|
|
41
|
-
return `model_verification_failed: child reported a different model than the launch candidate. Expected '${expectedModel}' but observed '${observedModel}'.`;
|
|
39
|
+
return `model_verification_failed: native Pi child reported a different model than the launch candidate. Expected '${expectedModel}' but observed '${observedModel}'. If you have independently verified this response ID identifies the requested model, declare the exact mapping in modelResponseAliases in ~/.pi/agent/extensions/subagent/config.json (see docs/configuration.md#modelresponsealiases). Use the resolved provider/model ID without its thinking suffix as the key. This leaves the outgoing request unchanged. Configuration changes affect new independent native runs; resumed native runs retain their launch-time declaration. External CLI adapters do not use this setting.`;
|
|
42
40
|
}
|
|
43
41
|
|
|
44
42
|
/** Sentinel model value requesting that a subagent inherit the parent session's model. */
|
|
@@ -537,6 +535,7 @@ export function buildModelCandidates(
|
|
|
537
535
|
}
|
|
538
536
|
|
|
539
537
|
const RETRYABLE_MODEL_FAILURE_PATTERNS = [
|
|
538
|
+
/^REQUEST_LIMIT_EXCEEDED$/,
|
|
540
539
|
/rate\s*limit/i,
|
|
541
540
|
/usage\s*limit/i,
|
|
542
541
|
/too many requests/i,
|
|
@@ -608,8 +607,13 @@ export function isRetryableModelFailureAttempt(input: { error: string | undefine
|
|
|
608
607
|
return Boolean(error && input.messages?.some((message) => messageError(message)?.trim() === error));
|
|
609
608
|
}
|
|
610
609
|
|
|
610
|
+
// Request-shape failures can match broad fallback signals such as "upstream",
|
|
611
|
+
// but do not establish that the model is unhealthy for subsequent requests.
|
|
612
|
+
const REQUEST_SHAPE_FAILURE_PATTERN = /\b(?:bad[ _]request|invalid[ _]argument|invalid_request_error)\b/i;
|
|
613
|
+
|
|
611
614
|
export function recordRetryableModelFailure(model: string | undefined, error: string | undefined): void {
|
|
612
|
-
if (!model || !isRetryableModelFailure(error)) return;
|
|
615
|
+
if (!model || !error || !isRetryableModelFailure(error) || isContextOverflow(error)) return;
|
|
616
|
+
if (REQUEST_SHAPE_FAILURE_PATTERN.test(error)) return;
|
|
613
617
|
const { provider, modelId } = parseModelKey(model);
|
|
614
618
|
recordModelFailure({ modelId, reason: error, ...(provider ? { provider } : {}) });
|
|
615
619
|
}
|
|
@@ -6,8 +6,8 @@ import {
|
|
|
6
6
|
TEMP_ROOT_DIR,
|
|
7
7
|
type AsyncJobState,
|
|
8
8
|
type AsyncStatus,
|
|
9
|
-
type
|
|
10
|
-
type
|
|
9
|
+
type LaunchResolvedChildExtensions,
|
|
10
|
+
type RuntimeAcknowledgedChildExtensions,
|
|
11
11
|
type NestedRouteInfo,
|
|
12
12
|
type TurnBudgetState,
|
|
13
13
|
type NestedRunSummary,
|
|
@@ -217,7 +217,7 @@ function sanitizeCost(value: unknown): NestedRunSummary["totalCost"] | undefined
|
|
|
217
217
|
: undefined;
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
-
function sanitizeLaunchResolvedExtensions(value: unknown):
|
|
220
|
+
function sanitizeLaunchResolvedExtensions(value: unknown): LaunchResolvedChildExtensions | undefined {
|
|
221
221
|
if (!value || typeof value !== "object") return undefined;
|
|
222
222
|
const raw = value as Record<string, unknown>;
|
|
223
223
|
if (raw.version !== 1 || raw.source !== "launch-resolved" || typeof raw.disableAmbientExtensions !== "boolean") return undefined;
|
|
@@ -241,7 +241,7 @@ function sanitizeLaunchResolvedExtensions(value: unknown): LaunchResolvedChildEx
|
|
|
241
241
|
};
|
|
242
242
|
}
|
|
243
243
|
|
|
244
|
-
function sanitizeRuntimeAcknowledgedExtensions(value: unknown):
|
|
244
|
+
function sanitizeRuntimeAcknowledgedExtensions(value: unknown): RuntimeAcknowledgedChildExtensions | undefined {
|
|
245
245
|
if (!value || typeof value !== "object") return undefined;
|
|
246
246
|
const raw = value as Record<string, unknown>;
|
|
247
247
|
if (raw.version !== 1 || raw.source !== "child-runtime" || !Array.isArray(raw.ids)) return undefined;
|
|
@@ -261,7 +261,7 @@ function sanitizeRuntimeAcknowledgedExtensions(value: unknown): RuntimeAcknowled
|
|
|
261
261
|
};
|
|
262
262
|
}
|
|
263
263
|
|
|
264
|
-
function runtimeAcknowledgedEntry(value: unknown): { runtimeAcknowledgedExtensions:
|
|
264
|
+
function runtimeAcknowledgedEntry(value: unknown): { runtimeAcknowledgedExtensions: RuntimeAcknowledgedChildExtensions } | Record<string, never> {
|
|
265
265
|
const sanitized = sanitizeRuntimeAcknowledgedExtensions(value);
|
|
266
266
|
return sanitized ? { runtimeAcknowledgedExtensions: sanitized } : {};
|
|
267
267
|
}
|
|
@@ -53,6 +53,8 @@ const ORCA_CLEANUP_WATCHDOG_SCRIPT = [
|
|
|
53
53
|
].join("");
|
|
54
54
|
|
|
55
55
|
export interface OrcaProgressTab {
|
|
56
|
+
/** Resolves when the terminal-create watchdog closes, after its final manifest/queue writes (success or failure). Not viewer completion. */
|
|
57
|
+
readonly creationSettled: Promise<void>;
|
|
56
58
|
append(text: string): void;
|
|
57
59
|
section(input: { agent: string; index: number; count: number }): void;
|
|
58
60
|
event(event: { type?: string; message?: Message; toolName?: string; args?: unknown }): void;
|
|
@@ -402,6 +404,8 @@ export function createOrcaProgressTab(input: {
|
|
|
402
404
|
}
|
|
403
405
|
};
|
|
404
406
|
let createSettled = false;
|
|
407
|
+
let resolveCreationSettled!: () => void;
|
|
408
|
+
const creationSettled = new Promise<void>((resolve) => { resolveCreationSettled = resolve; });
|
|
405
409
|
let cleanupPaths: string[] | undefined;
|
|
406
410
|
const scheduleDeferredCleanup = () => {
|
|
407
411
|
if (!createSettled || cleanupPaths === undefined) return;
|
|
@@ -434,6 +438,7 @@ export function createOrcaProgressTab(input: {
|
|
|
434
438
|
createSettled = true;
|
|
435
439
|
if (code !== 0) failObserver();
|
|
436
440
|
scheduleDeferredCleanup();
|
|
441
|
+
resolveCreationSettled();
|
|
437
442
|
});
|
|
438
443
|
watchdog.once("error", () => {
|
|
439
444
|
markCreateReady();
|
|
@@ -450,6 +455,7 @@ export function createOrcaProgressTab(input: {
|
|
|
450
455
|
|
|
451
456
|
let finished = false;
|
|
452
457
|
return {
|
|
458
|
+
creationSettled,
|
|
453
459
|
append(text) {
|
|
454
460
|
if (finished) return;
|
|
455
461
|
writeProgress(text);
|
|
@@ -17,6 +17,7 @@ import type {
|
|
|
17
17
|
WorktreeCleanupReport,
|
|
18
18
|
WorktreeDiff,
|
|
19
19
|
WorktreeSetup,
|
|
20
|
+
WorktreeSetupProgress,
|
|
20
21
|
WorktreeCleanupIntent,
|
|
21
22
|
} from "./worktree.ts";
|
|
22
23
|
import { cleanupWorktrees } from "./worktree.ts";
|
|
@@ -614,18 +615,62 @@ export function parallelHandoffPath(baseDir: string, runId?: string): string {
|
|
|
614
615
|
return runId ? path.join(baseDir, "handoffs", `${runId}.json`) : path.join(baseDir, "handoff.json");
|
|
615
616
|
}
|
|
616
617
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
setup
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
618
|
+
/** Synchronous onProgress projection shared by setup owners; snapshots are cumulative. */
|
|
619
|
+
export function writeWorktreeSetupHandoff(input: Omit<Parameters<typeof writeParallelHandoffGroup>[0], "setup" | "diffs" | "results" | "cleanup" | "now"> & {
|
|
620
|
+
progress: WorktreeSetupProgress;
|
|
621
|
+
}): ParallelHandoffReference | undefined {
|
|
622
|
+
const { progress, ...handoff } = input;
|
|
623
|
+
const { setup, attempts, cleanup } = progress;
|
|
624
|
+
// Preflight has no allocation identity yet. Its original error belongs to the caller.
|
|
625
|
+
if (attempts.length === 0 && setup.worktrees.length === 0) return undefined;
|
|
626
|
+
if (!setup.cwd || !setup.baseCommit) throw new Error("Cannot publish worktree allocation evidence without repository and base commit.");
|
|
627
|
+
const diagnostic = (text: string): string => text.slice(0, 512);
|
|
628
|
+
// Never copy argv, environment, Error objects or captured stdout/stderr into artifacts.
|
|
629
|
+
const commandEvidence = (command: WorktreeSetupProgress["command"]) => command && ({
|
|
630
|
+
pid: command.pid,
|
|
631
|
+
processGroupId: command.processGroupId,
|
|
632
|
+
...(command.result ? {
|
|
633
|
+
status: command.result.status,
|
|
634
|
+
signal: command.result.signal,
|
|
635
|
+
failed: Boolean(command.result.error),
|
|
636
|
+
outputIncomplete: command.result.outputIncomplete,
|
|
637
|
+
processTree: command.result.processTree?.state,
|
|
638
|
+
} : {}),
|
|
639
|
+
});
|
|
640
|
+
const errors = [
|
|
641
|
+
`Worktree setup ${JSON.stringify({ runId: input.runId, stepIndex: input.stepIndex, phase: progress.phase,
|
|
642
|
+
unknown: Boolean(progress.unknown), command: commandEvidence(progress.command) })}`,
|
|
643
|
+
...attempts.map((attempt) => {
|
|
644
|
+
const task = cleanup?.tasks.find((candidate) => candidate.index === attempt.index);
|
|
645
|
+
return `Allocation attempt ${JSON.stringify({ index: attempt.index, branch: attempt.branch,
|
|
646
|
+
path: attempt.path ?? null, validated: attempt.validated,
|
|
647
|
+
command: commandEvidence(attempt.command), hookCommand: commandEvidence(attempt.hookCommand),
|
|
648
|
+
...(task ? { worktreeRemoved: task.worktreeRemoved, branchRemoved: task.branchRemoved,
|
|
649
|
+
preserved: task.preserved, reason: task.reason && diagnostic(task.reason), errors: task.errors?.map(diagnostic) } : {}),
|
|
650
|
+
})}`;
|
|
651
|
+
}),
|
|
652
|
+
...(cleanup?.errors?.map(diagnostic) ?? []),
|
|
653
|
+
...(progress.unknown ? ["Setup settlement unknown; manual reconciliation required."] : []),
|
|
654
|
+
];
|
|
655
|
+
return writeParallelHandoffGroup({
|
|
656
|
+
...handoff, setup, diffs: [], results: [],
|
|
657
|
+
laneBindings: input.laneBindings?.filter((binding) => setup.worktrees.some((worktree) => worktree.index === binding.taskIndex)),
|
|
658
|
+
cleanup: {
|
|
659
|
+
state: cleanup?.state ?? "partial",
|
|
660
|
+
pruned: cleanup?.pruned ?? false,
|
|
661
|
+
errors,
|
|
662
|
+
// An attempted native path is not a validated recovery task, even during rollback.
|
|
663
|
+
tasks: setup.worktrees.map((worktree) => {
|
|
664
|
+
const task = cleanup?.tasks.find((candidate) => candidate.index === worktree.index);
|
|
665
|
+
return task ? { ...task, reason: task.reason && diagnostic(task.reason), errors: task.errors?.map(diagnostic) } : {
|
|
666
|
+
index: worktree.index, path: worktree.path, branch: worktree.branch,
|
|
667
|
+
provider: worktree.provider, naming: worktree.naming,
|
|
668
|
+
worktreeRemoved: false, branchRemoved: false, preserved: true,
|
|
669
|
+
reason: "setup pending durable handoff capture",
|
|
670
|
+
};
|
|
671
|
+
}),
|
|
672
|
+
},
|
|
673
|
+
});
|
|
629
674
|
}
|
|
630
675
|
|
|
631
676
|
export function formatParallelHandoffReference(reference: ParallelHandoffReference): string {
|
|
@@ -75,8 +75,8 @@ export interface RunnerSubagentStep {
|
|
|
75
75
|
launchBindingTask?: string;
|
|
76
76
|
launchContractDigest?: string;
|
|
77
77
|
extensionBindings?: import("./extension-bindings.ts").ExtensionBindings;
|
|
78
|
-
launchResolvedExtensions?: import("../../shared/types.ts").
|
|
79
|
-
runtimeAcknowledgedExtensions?: import("../../shared/types.ts").
|
|
78
|
+
launchResolvedExtensions?: import("../../shared/types.ts").LaunchResolvedChildExtensions;
|
|
79
|
+
runtimeAcknowledgedExtensions?: import("../../shared/types.ts").RuntimeAcknowledgedChildExtensions;
|
|
80
80
|
effectiveAcceptance?: import("../../shared/types.ts").ResolvedAcceptanceConfig;
|
|
81
81
|
acceptanceInput?: import("../../shared/types.ts").AcceptanceInput;
|
|
82
82
|
acceptanceRole?: import("../../shared/types.ts").AcceptanceRole;
|