pi-subagents 0.54.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 +59 -0
- package/agents/reviewer.md +12 -2
- package/docs/agents.md +4 -4
- package/docs/configuration.md +4 -4
- package/docs/extension-api.md +14 -4
- package/docs/models.md +30 -3
- package/docs/observability.md +14 -3
- package/docs/tool-reference.md +21 -6
- package/docs/workflows.md +7 -1
- package/package.json +1 -1
- package/prompts/council.md +9 -0
- package/prompts/parallel-review.md +5 -1
- package/prompts/review-loop.md +9 -5
- package/skills/council-mode/SKILL.md +33 -10
- package/skills/pi-subagents/references/constraints-and-recipes.md +16 -1
- package/skills/pi-subagents/references/execution-controls.md +11 -4
- package/skills/pi-subagents/references/multi-lane-orchestration.md +3 -3
- package/skills/pi-subagents/references/prompting-and-roles.md +8 -8
- package/src/agents/agent-management.ts +30 -9
- package/src/agents/agent-serializer.ts +4 -2
- package/src/agents/agents.ts +168 -41
- package/src/api/external-job-provider.ts +10 -1
- package/src/api/preflight.ts +45 -17
- package/src/api/project-panes.ts +2 -0
- package/src/extension/index.ts +20 -1
- package/src/extension/public-execution.ts +12 -9
- package/src/extension/rpc.ts +77 -3
- package/src/extension/schemas.ts +7 -1
- package/src/extension/tool-description.ts +9 -6
- package/src/inspectors/herdr/focus.ts +55 -0
- package/src/inspectors/herdr/project-panes.ts +228 -44
- package/src/integrations/herdr-status.ts +26 -4
- package/src/runs/background/async-execution.ts +137 -13
- package/src/runs/background/async-job-tracker.ts +33 -14
- package/src/runs/background/async-resume.ts +23 -2
- package/src/runs/background/async-status.ts +10 -0
- package/src/runs/background/control-channel.ts +98 -10
- package/src/runs/background/notify.ts +62 -1
- package/src/runs/background/run-status.ts +15 -1
- package/src/runs/background/subagent-runner.ts +308 -47
- package/src/runs/foreground/async-stop-action.ts +23 -2
- package/src/runs/foreground/execution.ts +115 -12
- package/src/runs/foreground/foreground-history.ts +3 -2
- package/src/runs/foreground/subagent-executor.ts +404 -45
- package/src/runs/foreground/workflow-detach-reconcile.ts +18 -6
- package/src/runs/shared/acceptance.ts +11 -5
- package/src/runs/shared/agent-contract.ts +1 -1
- package/src/runs/shared/child-identity.ts +36 -0
- package/src/runs/shared/completion-guard.ts +52 -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/external-job-bridge.ts +53 -37
- package/src/runs/shared/external-job-runner.ts +126 -23
- 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 +12 -7
- package/src/runs/shared/mutation-evidence.ts +145 -0
- package/src/runs/shared/orca-progress-tabs.ts +71 -9
- package/src/runs/shared/parallel-utils.ts +15 -0
- package/src/runs/shared/pi-args.ts +56 -12
- package/src/runs/shared/structured-output.ts +18 -4
- package/src/runs/shared/subagent-prompt-runtime.ts +12 -6
- package/src/runs/shared/tool-availability.ts +1 -3
- package/src/shared/launch-contract.ts +12 -5
- package/src/shared/settings.ts +6 -1
- package/src/shared/thinking-ceiling.ts +52 -0
- package/src/shared/types.ts +107 -2
- package/src/tui/fleet-status.ts +66 -13
- package/src/tui/render.ts +5 -4
- package/src/workflows/scripted-workflow.ts +156 -12
|
@@ -28,12 +28,14 @@ import { buildModelCandidates, inheritsParentModel, resolveEffectiveSubagentMode
|
|
|
28
28
|
import { resolveToolTimeoutMs, toolTimeoutFromEnv } from "../shared/tool-timeout.ts";
|
|
29
29
|
import { resolveModelScopesForAgent, type ModelScopeConfig } from "../shared/model-scope.ts";
|
|
30
30
|
import { resolveEffectiveThinking } from "../../shared/model-info.ts";
|
|
31
|
+
import { assertThinkingWithinCeiling, decodeThinkingCeiling, intersectThinkingCeilings, SUBAGENT_THINKING_CEILING_ENV, type ThinkingLevel } from "../../shared/thinking-ceiling.ts";
|
|
31
32
|
import { resolveExpectedWorktreeAgentCwd } from "../shared/worktree.ts";
|
|
32
33
|
import { buildWorkflowGraphSnapshot } from "../shared/workflow-graph.ts";
|
|
33
34
|
import { ChainOutputValidationError, validateChainOutputBindings } from "../shared/chain-outputs.ts";
|
|
34
35
|
import { createStructuredOutputRuntime } from "../shared/structured-output.ts";
|
|
35
36
|
import { resolveEffectiveAcceptance, validateAcceptanceInput, validateExecutionAcceptance } from "../shared/acceptance.ts";
|
|
36
37
|
import { createRunFanoutBudget, writeRunFanoutBudgetDescriptor } from "../shared/run-fanout-budget.ts";
|
|
38
|
+
import { validateImplementationToolContract } from "../shared/completion-guard.ts";
|
|
37
39
|
import {
|
|
38
40
|
type AcceptanceInput,
|
|
39
41
|
type AgentContract,
|
|
@@ -72,6 +74,7 @@ import { SUBAGENT_PROCESS_TERMINAL_EVENT } from "../../shared/types.ts";
|
|
|
72
74
|
import { assertAgentAllowedByCapabilityCeiling, decodeSubagentCapabilityCeiling, intersectSubagentCapabilityCeilings, resolveCurrentSubagentCapabilityCeiling, SUBAGENT_CAPABILITY_CEILING_ENV, type ResolvedSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
|
|
73
75
|
import { agentDefinitionDigest, launchBindingDigest } from "../../shared/launch-contract.ts";
|
|
74
76
|
import { resolvePermissionRules, type PermissionConfig } from "../shared/permissions.ts";
|
|
77
|
+
import { normalizeExtensionBindings, omitExtensionBindingsEnv, type ExtensionBindings } from "../shared/extension-bindings.ts";
|
|
75
78
|
|
|
76
79
|
const require = createRequire(import.meta.url);
|
|
77
80
|
const piPackageRoot = resolvePiPackageRoot();
|
|
@@ -173,6 +176,7 @@ interface AsyncChainParams {
|
|
|
173
176
|
childIntercomTarget?: (agent: string, index: number) => string | undefined;
|
|
174
177
|
nestedRoute?: NestedRouteInfo;
|
|
175
178
|
acceptance?: AcceptanceInput;
|
|
179
|
+
fast?: boolean;
|
|
176
180
|
timeoutMs?: number;
|
|
177
181
|
turnBudget?: ResolvedTurnBudget;
|
|
178
182
|
toolBudget?: ResolvedToolBudget;
|
|
@@ -187,6 +191,7 @@ interface AsyncChainParams {
|
|
|
187
191
|
/** Global cap on simultaneously-running subagent tasks within the async run. */
|
|
188
192
|
globalConcurrencyLimit?: number;
|
|
189
193
|
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
194
|
+
thinkingCeiling?: ThinkingLevel;
|
|
190
195
|
runFanoutBudget?: RunFanoutBudgetDescriptor;
|
|
191
196
|
parentWorkflowRunId?: string;
|
|
192
197
|
workflowKey?: string;
|
|
@@ -221,6 +226,7 @@ interface AsyncSingleParams {
|
|
|
221
226
|
structuredOutputSchema?: JsonSchemaObject;
|
|
222
227
|
modelOverride?: string;
|
|
223
228
|
modelOverrideFromParent?: boolean;
|
|
229
|
+
fast?: boolean;
|
|
224
230
|
thinkingOverride?: AgentConfig["thinking"];
|
|
225
231
|
availableModels?: AvailableModelInfo[];
|
|
226
232
|
maxSubagentDepth: number;
|
|
@@ -249,10 +255,20 @@ interface AsyncSingleParams {
|
|
|
249
255
|
toolTimeoutMsEnv?: string | undefined;
|
|
250
256
|
allowZeroToolBudget?: boolean;
|
|
251
257
|
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
258
|
+
thinkingCeiling?: ThinkingLevel;
|
|
252
259
|
runFanoutBudget?: RunFanoutBudgetDescriptor;
|
|
253
260
|
parentWorkflowRunId?: string;
|
|
254
261
|
workflowKey?: string;
|
|
262
|
+
workflowAwaitAsync?: boolean;
|
|
255
263
|
activeAsyncCapacity?: ActiveAsyncCapacityHandle;
|
|
264
|
+
externalJobFollowUp?: {
|
|
265
|
+
sourceRunId: string;
|
|
266
|
+
sourceStepIndex: number;
|
|
267
|
+
parentProviderJobId: string;
|
|
268
|
+
requestId: string;
|
|
269
|
+
requestDigest: string;
|
|
270
|
+
};
|
|
271
|
+
extensionBindings?: ExtensionBindings;
|
|
256
272
|
}
|
|
257
273
|
|
|
258
274
|
interface AsyncExecutionResult {
|
|
@@ -283,6 +299,7 @@ export interface AsyncRunnerStepBuildParams {
|
|
|
283
299
|
asyncDir: string;
|
|
284
300
|
outputBaseDir?: string;
|
|
285
301
|
validateOutputBindings?: boolean;
|
|
302
|
+
fast?: boolean;
|
|
286
303
|
toolBudget?: ResolvedToolBudget;
|
|
287
304
|
configToolBudget?: ResolvedToolBudget;
|
|
288
305
|
/** Optional per-call hard toolTimeoutMs override from the subagent invocation. */
|
|
@@ -292,6 +309,7 @@ export interface AsyncRunnerStepBuildParams {
|
|
|
292
309
|
/** PI_SUBAGENT_TOOL_TIMEOUT_MS override (lowest precedence). */
|
|
293
310
|
toolTimeoutMsEnv?: string | undefined;
|
|
294
311
|
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
312
|
+
thinkingCeiling?: ThinkingLevel;
|
|
295
313
|
}
|
|
296
314
|
|
|
297
315
|
export type AsyncRunnerStepBuildResult =
|
|
@@ -493,7 +511,7 @@ function spawnRunner(cfg: object, suffix: string, cwd: string, onProcessTerminal
|
|
|
493
511
|
const cfgPath = getAsyncConfigPath(suffix);
|
|
494
512
|
const runnerProcessInstanceId = randomUUID();
|
|
495
513
|
const launchConfig = { ...cfg, runnerProcessInstanceId };
|
|
496
|
-
|
|
514
|
+
writePrivateAtomicJson(cfgPath, launchConfig);
|
|
497
515
|
const runner = path.join(path.dirname(fileURLToPath(import.meta.url)), "subagent-runner.ts");
|
|
498
516
|
const nodeCommand = resolveNodeExecutable();
|
|
499
517
|
const launchForStartup = launchConfig as typeof launchConfig & { asyncDir?: unknown; id?: unknown; sessionId?: unknown; completionOwnerId?: unknown; revivalLease?: unknown };
|
|
@@ -525,7 +543,7 @@ function spawnRunner(cfg: object, suffix: string, cwd: string, onProcessTerminal
|
|
|
525
543
|
stdio: ["ignore", stdoutFd ?? "ignore", stderrFd ?? "ignore"],
|
|
526
544
|
windowsHide: true,
|
|
527
545
|
env: {
|
|
528
|
-
...process.env,
|
|
546
|
+
...omitExtensionBindingsEnv(process.env),
|
|
529
547
|
...(piPackageRoot ? { [PI_CODING_AGENT_PACKAGE_ROOT_ENV]: piPackageRoot } : {}),
|
|
530
548
|
},
|
|
531
549
|
});
|
|
@@ -714,6 +732,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
714
732
|
...(s.progress !== undefined ? { progress: s.progress } : {}),
|
|
715
733
|
...(stepSkillInput !== undefined ? { skills: stepSkillInput } : {}),
|
|
716
734
|
...(s.model !== undefined ? { model: s.model } : {}),
|
|
735
|
+
...(s.fast !== undefined ? { fast: s.fast } : {}),
|
|
717
736
|
};
|
|
718
737
|
};
|
|
719
738
|
const buildSeqStep = (s: SequentialStep, sessionFile?: string, behaviorCwd?: string, progressPrecreated = false, resolvedBehavior?: ResolvedStepBehavior, flatIndex?: number, parallelOutputNamespace?: { stepIndex: number; taskIndex?: number }, runFanoutPath?: string) => {
|
|
@@ -726,6 +745,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
726
745
|
if (s.outputSchema !== undefined) unsupported.push("structured output");
|
|
727
746
|
if (s.acceptance !== undefined || params.agentContract !== undefined || s.agentContract !== undefined) unsupported.push("acceptance/agent contract");
|
|
728
747
|
if (s.toolBudget !== undefined || params.toolBudget !== undefined || a.toolBudget !== undefined || params.configToolBudget !== undefined) unsupported.push("tool budget");
|
|
748
|
+
if ((s.fast ?? params.fast ?? a.fast) === true) unsupported.push("fast mode");
|
|
729
749
|
if (params.contextForAgent?.(s.agent) === "fork") unsupported.push("fork context");
|
|
730
750
|
if (unsupported.length > 0) throw new AsyncStartValidationError(`Agent '${a.name}' uses runner.type='${externalRunnerType}' and does not support: ${unsupported.join(", ")}.`);
|
|
731
751
|
}
|
|
@@ -792,19 +812,47 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
792
812
|
const task = namespaceOutputPath ? taskText : injectSingleOutputInstruction(taskText, outputPath, a);
|
|
793
813
|
|
|
794
814
|
const modelScopes = resolveModelScopesForAgent(ctx.modelScope, a.name, ctx.currentModel);
|
|
815
|
+
const primaryModelFromParent = inheritsParentModel(s.model, a.model, ctx.currentModel);
|
|
795
816
|
const primaryModel = externalRunner ? undefined : resolveEffectiveSubagentModel(
|
|
796
817
|
s.model,
|
|
797
818
|
a.model,
|
|
798
819
|
ctx.currentModel,
|
|
799
820
|
availableModels,
|
|
800
|
-
ctx.currentModelProvider,
|
|
821
|
+
a.modelProvider ?? ctx.currentModelProvider,
|
|
801
822
|
{ scope: modelScopes },
|
|
802
823
|
);
|
|
803
824
|
const thinkingOverride = flatIndex === undefined ? undefined : thinkingOverridesByFlatIndex?.[flatIndex];
|
|
804
825
|
const effectiveThinking = externalRunner ? undefined : thinkingOverride ?? a.thinking;
|
|
805
826
|
const model = externalRunner ? undefined : applyThinkingSuffix(primaryModel, effectiveThinking, thinkingOverride !== undefined);
|
|
827
|
+
const thinkingCeiling = externalRunner ? undefined : intersectThinkingCeilings(
|
|
828
|
+
params.thinkingCeiling,
|
|
829
|
+
a.maxThinking,
|
|
830
|
+
decodeThinkingCeiling(process.env[SUBAGENT_THINKING_CEILING_ENV]),
|
|
831
|
+
);
|
|
832
|
+
if (!externalRunner) {
|
|
833
|
+
try {
|
|
834
|
+
assertThinkingWithinCeiling({ model, configThinking: effectiveThinking, ceiling: thinkingCeiling, agent: a.name, runId: id });
|
|
835
|
+
} catch (error) {
|
|
836
|
+
throw new AsyncStartValidationError(error instanceof Error ? error.message : String(error));
|
|
837
|
+
}
|
|
838
|
+
}
|
|
806
839
|
const agentContract = s.agentContract ?? params.agentContract;
|
|
807
840
|
const permissionRules = resolvePermissionRules(ctx.permissions, a.permissions);
|
|
841
|
+
const modelCandidates = externalRunner ? [] : buildModelCandidates(primaryModel, a.fallbackModels, availableModels, a.modelProvider ?? ctx.currentModelProvider, {
|
|
842
|
+
scope: modelScopes,
|
|
843
|
+
primaryModelFromParent,
|
|
844
|
+
}).flatMap((candidate) => {
|
|
845
|
+
const resolved = applyThinkingSuffix(candidate, effectiveThinking, thinkingOverride !== undefined);
|
|
846
|
+
return resolved ? [resolved] : [];
|
|
847
|
+
});
|
|
848
|
+
if (!externalRunner) {
|
|
849
|
+
try {
|
|
850
|
+
for (const candidate of modelCandidates) assertThinkingWithinCeiling({ model: candidate, configThinking: effectiveThinking, ceiling: thinkingCeiling, agent: a.name, runId: id });
|
|
851
|
+
} catch (error) {
|
|
852
|
+
throw new AsyncStartValidationError(error instanceof Error ? error.message : String(error));
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
const fast = s.fast ?? params.fast ?? a.fast;
|
|
808
856
|
const toolPlan = resolvePiLaunchToolPlan({
|
|
809
857
|
tools: a.tools,
|
|
810
858
|
extensions: a.extensions,
|
|
@@ -813,6 +861,9 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
813
861
|
cwd: stepCwd,
|
|
814
862
|
requireReadTool: Boolean(resolvedSkills.length),
|
|
815
863
|
structuredOutput: Boolean(s.outputSchema),
|
|
864
|
+
fast,
|
|
865
|
+
model,
|
|
866
|
+
modelCandidates,
|
|
816
867
|
capabilityCeiling: params.capabilityCeiling,
|
|
817
868
|
inheritedCapabilityCeiling: decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV]),
|
|
818
869
|
agentName: a.name,
|
|
@@ -822,6 +873,20 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
822
873
|
if (externalRunner && permissionRules) {
|
|
823
874
|
throw new AsyncStartValidationError(`Agent '${a.name}' uses runner.type='${externalRunnerType}', which cannot enforce native Pi child permission rules.`);
|
|
824
875
|
}
|
|
876
|
+
if (!externalRunner) {
|
|
877
|
+
const contractTools = toolPlan.explicitToolAllowlist ? toolPlan.effectiveToolAllowlist : undefined;
|
|
878
|
+
const contractError = validateImplementationToolContract({
|
|
879
|
+
agent: a.name,
|
|
880
|
+
task,
|
|
881
|
+
tools: contractTools,
|
|
882
|
+
mcpDirectTools: toolPlan.effectiveMcpTools,
|
|
883
|
+
configuredExtensions: toolPlan.configuredExtensions,
|
|
884
|
+
requestedTools: toolPlan.requestedBuiltinTools,
|
|
885
|
+
acceptanceRole: a.acceptanceRole,
|
|
886
|
+
completionGuard: a.completionGuard,
|
|
887
|
+
});
|
|
888
|
+
if (contractError) throw new AsyncStartValidationError(contractError);
|
|
889
|
+
}
|
|
825
890
|
return {
|
|
826
891
|
parentSessionId: ctx.parentSessionId ?? ctx.currentSessionId,
|
|
827
892
|
permissionRules,
|
|
@@ -838,14 +903,13 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
838
903
|
structured: Boolean(s.outputSchema),
|
|
839
904
|
cwd: stepCwd,
|
|
840
905
|
model,
|
|
906
|
+
...(fast !== undefined ? { fast } : {}),
|
|
841
907
|
thinking: resolveEffectiveThinking(model, effectiveThinking),
|
|
908
|
+
...(thinkingCeiling ? { thinkingCeiling } : {}),
|
|
842
909
|
launchResolvedExtensions,
|
|
843
|
-
modelCandidates: externalRunner ? undefined :
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
}).map((candidate) =>
|
|
847
|
-
applyThinkingSuffix(candidate, effectiveThinking, thinkingOverride !== undefined),
|
|
848
|
-
),
|
|
910
|
+
modelCandidates: externalRunner ? undefined : modelCandidates,
|
|
911
|
+
...(primaryModelFromParent ? { skipPrimaryModelVerification: true } : {}),
|
|
912
|
+
...(availableModels && availableModels.length > 0 ? { modelVerificationRegistry: availableModels } : {}),
|
|
849
913
|
tools: a.tools,
|
|
850
914
|
extensions: a.extensions,
|
|
851
915
|
subagentOnlyExtensions: a.subagentOnlyExtensions,
|
|
@@ -878,7 +942,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
878
942
|
acceptanceRole: a.acceptanceRole,
|
|
879
943
|
...(s.gateOn ? { gateOn: s.gateOn } : {}),
|
|
880
944
|
...(s.outputSchema ? { structuredOutputSchema: s.outputSchema } : {}),
|
|
881
|
-
...(s.outputSchema ? { structuredOutput: createStructuredOutputRuntime(s.outputSchema, path.join(asyncDir, "structured-output")) } : {}),
|
|
945
|
+
...(s.outputSchema ? { structuredOutput: createStructuredOutputRuntime(s.outputSchema, path.join(asyncDir, "structured-output"), { captureAcceptanceReport: s.acceptance !== false }) } : {}),
|
|
882
946
|
...(resolvedToolBudget.budget ? { toolBudget: resolvedToolBudget.budget } : {}),
|
|
883
947
|
...(s.worktree ? { worktree: true } : {}),
|
|
884
948
|
};
|
|
@@ -962,6 +1026,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
962
1026
|
acceptanceRole: agent.acceptanceRole,
|
|
963
1027
|
...(s.agentContract ?? params.agentContract ? { agentContract: s.agentContract ?? params.agentContract } : {}),
|
|
964
1028
|
...(s.gateOn ? { gateOn: s.gateOn } : {}),
|
|
1029
|
+
...(parallel.thinkingCeiling ? { thinkingCeiling: parallel.thinkingCeiling } : {}),
|
|
965
1030
|
};
|
|
966
1031
|
}
|
|
967
1032
|
const sequential = s as SequentialStep;
|
|
@@ -1090,12 +1155,14 @@ export function executeAsyncChain(
|
|
|
1090
1155
|
waitToolEnabled: params.waitToolEnabled,
|
|
1091
1156
|
worktreeBaseDir,
|
|
1092
1157
|
asyncDir,
|
|
1158
|
+
fast: params.fast,
|
|
1093
1159
|
toolBudget: params.toolBudget,
|
|
1094
1160
|
configToolBudget: params.configToolBudget,
|
|
1095
1161
|
callToolTimeoutMs: params.callToolTimeoutMs,
|
|
1096
1162
|
configToolTimeoutMs: params.configToolTimeoutMs,
|
|
1097
1163
|
toolTimeoutMsEnv: params.toolTimeoutMsEnv ?? toolTimeoutFromEnv(),
|
|
1098
1164
|
capabilityCeiling,
|
|
1165
|
+
thinkingCeiling: params.thinkingCeiling,
|
|
1099
1166
|
});
|
|
1100
1167
|
if ("error" in built) {
|
|
1101
1168
|
try {
|
|
@@ -1337,6 +1404,12 @@ export function executeAsyncSingle(
|
|
|
1337
1404
|
nestedRoute,
|
|
1338
1405
|
} = params;
|
|
1339
1406
|
const task = params.task ?? "";
|
|
1407
|
+
let extensionBindings: ExtensionBindings | undefined;
|
|
1408
|
+
try {
|
|
1409
|
+
extensionBindings = normalizeExtensionBindings(params.extensionBindings)?.value;
|
|
1410
|
+
} catch (error) {
|
|
1411
|
+
return formatAsyncStartError("single", error instanceof Error ? error.message : String(error));
|
|
1412
|
+
}
|
|
1340
1413
|
const acceptanceErrors = validateAcceptanceInput(params.acceptance);
|
|
1341
1414
|
if (acceptanceErrors.length > 0) return formatAsyncStartError("single", acceptanceErrors.join(" "));
|
|
1342
1415
|
const externalRunner = agentConfig.runner?.type === "external-cli" || agentConfig.runner?.type === "external-job";
|
|
@@ -1345,6 +1418,7 @@ export function executeAsyncSingle(
|
|
|
1345
1418
|
if (externalRunner) {
|
|
1346
1419
|
const unsupported: string[] = [];
|
|
1347
1420
|
if (params.modelOverride !== undefined) unsupported.push("model override");
|
|
1421
|
+
if ((params.fast ?? agentConfig.fast) === true) unsupported.push("fast mode");
|
|
1348
1422
|
if (params.thinkingOverride !== undefined) unsupported.push("thinking override");
|
|
1349
1423
|
if (params.structuredOutputSchema !== undefined) unsupported.push("structured output");
|
|
1350
1424
|
if (params.acceptance !== undefined || params.agentContract !== undefined) unsupported.push("acceptance/agent contract");
|
|
@@ -1352,6 +1426,7 @@ export function executeAsyncSingle(
|
|
|
1352
1426
|
if (params.context === "fork") unsupported.push("fork context");
|
|
1353
1427
|
if ((params.skills?.length ?? 0) > 0) unsupported.push("skills");
|
|
1354
1428
|
if (permissionRules) unsupported.push("native Pi child permissions");
|
|
1429
|
+
if (extensionBindings !== undefined) unsupported.push("extension bindings");
|
|
1355
1430
|
if (unsupported.length > 0) return formatAsyncStartError("single", `Agent '${agentConfig.name}' uses runner.type='${externalRunnerType}' and does not support: ${unsupported.join(", ")}.`);
|
|
1356
1431
|
}
|
|
1357
1432
|
const capabilityCeiling = intersectSubagentCapabilityCeilings(params.capabilityCeiling ?? resolveCurrentSubagentCapabilityCeiling(ctx.currentSessionId), decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV]));
|
|
@@ -1432,6 +1507,18 @@ export function executeAsyncSingle(
|
|
|
1432
1507
|
);
|
|
1433
1508
|
const effectiveThinking = externalRunner ? undefined : params.thinkingOverride ?? agentConfig.thinking;
|
|
1434
1509
|
const model = externalRunner ? undefined : applyThinkingSuffix(primaryModel, effectiveThinking, params.thinkingOverride !== undefined);
|
|
1510
|
+
const thinkingCeiling = externalRunner ? undefined : intersectThinkingCeilings(
|
|
1511
|
+
params.thinkingCeiling,
|
|
1512
|
+
agentConfig.maxThinking,
|
|
1513
|
+
decodeThinkingCeiling(process.env[SUBAGENT_THINKING_CEILING_ENV]),
|
|
1514
|
+
);
|
|
1515
|
+
if (!externalRunner) {
|
|
1516
|
+
try {
|
|
1517
|
+
assertThinkingWithinCeiling({ model, configThinking: effectiveThinking, ceiling: thinkingCeiling, agent: agentConfig.name, runId: id });
|
|
1518
|
+
} catch (error) {
|
|
1519
|
+
return formatAsyncStartError("single", error instanceof Error ? error.message : String(error));
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1435
1522
|
const toolBudgetInput = params.toolBudget ?? agentConfig.toolBudget ?? params.configToolBudget;
|
|
1436
1523
|
const resolvedToolBudget = validateToolBudgetConfig(toolBudgetInput, params.toolBudget ? "toolBudget" : agentConfig.toolBudget ? "agent.toolBudget" : "config.toolBudget");
|
|
1437
1524
|
if (resolvedToolBudget.error) return formatAsyncStartError("single", resolvedToolBudget.error);
|
|
@@ -1452,11 +1539,11 @@ export function executeAsyncSingle(
|
|
|
1452
1539
|
const initialUsageBudget = usageBudgetState(params.usageBudget, undefined);
|
|
1453
1540
|
const resolvedSessionDir = params.sessionDir ?? (sessionRoot ? path.join(sessionRoot, `async-${id}`) : undefined);
|
|
1454
1541
|
const structuredOutput = params.structuredOutputSchema
|
|
1455
|
-
? createStructuredOutputRuntime(params.structuredOutputSchema, path.join(asyncDir, "structured-output"))
|
|
1542
|
+
? createStructuredOutputRuntime(params.structuredOutputSchema, path.join(asyncDir, "structured-output"), { captureAcceptanceReport: params.acceptance !== false })
|
|
1456
1543
|
: undefined;
|
|
1457
1544
|
const modelCandidates = externalRunner
|
|
1458
1545
|
? []
|
|
1459
|
-
: buildModelCandidates(primaryModel, agentConfig.fallbackModels, availableModels, ctx.currentModelProvider, {
|
|
1546
|
+
: buildModelCandidates(primaryModel, agentConfig.fallbackModels, availableModels, agentConfig.modelProvider ?? ctx.currentModelProvider, {
|
|
1460
1547
|
scope: modelScopes,
|
|
1461
1548
|
primaryModelFromParent: params.modelOverrideFromParent,
|
|
1462
1549
|
})
|
|
@@ -1464,6 +1551,13 @@ export function executeAsyncSingle(
|
|
|
1464
1551
|
const resolved = applyThinkingSuffix(candidate, effectiveThinking, params.thinkingOverride !== undefined);
|
|
1465
1552
|
return resolved ? [resolved] : [];
|
|
1466
1553
|
});
|
|
1554
|
+
if (!externalRunner) {
|
|
1555
|
+
try {
|
|
1556
|
+
for (const candidate of modelCandidates) assertThinkingWithinCeiling({ model: candidate, configThinking: effectiveThinking, ceiling: thinkingCeiling, agent: agentConfig.name, runId: id });
|
|
1557
|
+
} catch (error) {
|
|
1558
|
+
return formatAsyncStartError("single", error instanceof Error ? error.message : String(error));
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1467
1561
|
const effectiveSystemPrompt = appendTurnBudgetSystemPrompt(systemPrompt, params.turnBudget);
|
|
1468
1562
|
const toolPlan = resolvePiLaunchToolPlan({
|
|
1469
1563
|
tools: agentConfig.tools,
|
|
@@ -1473,18 +1567,37 @@ export function executeAsyncSingle(
|
|
|
1473
1567
|
cwd: runnerCwd,
|
|
1474
1568
|
requireReadTool: Boolean(resolvedSkills.length),
|
|
1475
1569
|
structuredOutput: Boolean(params.structuredOutputSchema),
|
|
1570
|
+
fast: params.fast ?? agentConfig.fast,
|
|
1571
|
+
model,
|
|
1572
|
+
modelCandidates,
|
|
1476
1573
|
capabilityCeiling,
|
|
1477
1574
|
inheritedCapabilityCeiling: decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV]),
|
|
1478
1575
|
agentName: agentConfig.name,
|
|
1479
1576
|
permissionRules: resolvePermissionRules(ctx.permissions, agentConfig.permissions),
|
|
1480
1577
|
});
|
|
1481
1578
|
const launchResolvedExtensions = externalRunner ? undefined : projectLaunchResolvedChildExtensions(toolPlan);
|
|
1579
|
+
if (!externalRunner) {
|
|
1580
|
+
const contractTools = toolPlan.explicitToolAllowlist ? toolPlan.effectiveToolAllowlist : undefined;
|
|
1581
|
+
const contractError = validateImplementationToolContract({
|
|
1582
|
+
agent: agentConfig.name,
|
|
1583
|
+
task: taskText,
|
|
1584
|
+
tools: contractTools,
|
|
1585
|
+
mcpDirectTools: toolPlan.effectiveMcpTools,
|
|
1586
|
+
configuredExtensions: toolPlan.configuredExtensions,
|
|
1587
|
+
requestedTools: toolPlan.requestedBuiltinTools,
|
|
1588
|
+
acceptanceRole: agentConfig.acceptanceRole,
|
|
1589
|
+
completionGuard: agentConfig.completionGuard,
|
|
1590
|
+
});
|
|
1591
|
+
if (contractError) return formatAsyncStartError("single", contractError);
|
|
1592
|
+
}
|
|
1482
1593
|
const launchContractDigest = launchBindingDigest({
|
|
1483
1594
|
definitionDigest: agentDefinitionDigest(agentConfig),
|
|
1484
1595
|
task,
|
|
1485
1596
|
...(model ? { model } : {}),
|
|
1486
1597
|
modelCandidates,
|
|
1598
|
+
...((params.fast ?? agentConfig.fast) !== undefined ? { fast: params.fast ?? agentConfig.fast } : {}),
|
|
1487
1599
|
...(resolveEffectiveThinking(model, effectiveThinking) ? { thinking: resolveEffectiveThinking(model, effectiveThinking) } : {}),
|
|
1600
|
+
...(thinkingCeiling ? { thinkingCeiling } : {}),
|
|
1488
1601
|
systemPrompt: effectiveSystemPrompt,
|
|
1489
1602
|
systemPromptMode: agentConfig.systemPromptMode,
|
|
1490
1603
|
inheritProjectContext: agentConfig.inheritProjectContext,
|
|
@@ -1496,6 +1609,7 @@ export function executeAsyncSingle(
|
|
|
1496
1609
|
...(outputPath ? { outputPath } : {}),
|
|
1497
1610
|
outputMode,
|
|
1498
1611
|
...(params.structuredOutputSchema ? { structuredOutputSchema: params.structuredOutputSchema } : {}),
|
|
1612
|
+
...(extensionBindings ? { extensionBindings } : {}),
|
|
1499
1613
|
});
|
|
1500
1614
|
const resolvedAcceptance = resolveEffectiveAcceptance({
|
|
1501
1615
|
explicit: params.acceptance,
|
|
@@ -1510,6 +1624,7 @@ export function executeAsyncSingle(
|
|
|
1510
1624
|
const recoveryDescriptor: SteeringRecoveryDescriptor = {
|
|
1511
1625
|
version: 1,
|
|
1512
1626
|
launchContractDigest,
|
|
1627
|
+
...(extensionBindings ? { extensionBindings } : {}),
|
|
1513
1628
|
runFanoutBudget,
|
|
1514
1629
|
sourceRunId: id,
|
|
1515
1630
|
...(params.agentContract ? { agentContract: params.agentContract } : {}),
|
|
@@ -1518,9 +1633,12 @@ export function executeAsyncSingle(
|
|
|
1518
1633
|
...(sessionFile ? { sessionFile } : {}),
|
|
1519
1634
|
cwd: runnerCwd,
|
|
1520
1635
|
...(model ? { model } : {}),
|
|
1636
|
+
...(params.fast ?? recoveryAgentConfig.fast ? { fast: params.fast ?? recoveryAgentConfig.fast } : {}),
|
|
1637
|
+
...(recoveryAgentConfig.modelProvider ? { modelProvider: recoveryAgentConfig.modelProvider } : {}),
|
|
1521
1638
|
...(params.modelOverrideFromParent ? { modelOverrideFromParent: true } : {}),
|
|
1522
1639
|
...(recoveryAgentConfig.fallbackModels ? { fallbackModels: [...recoveryAgentConfig.fallbackModels] } : {}),
|
|
1523
1640
|
...(effectiveThinking ? { thinking: resolveEffectiveThinking(model, effectiveThinking) } : {}),
|
|
1641
|
+
...(thinkingCeiling ? { thinkingCeiling } : {}),
|
|
1524
1642
|
...(recoveryAgentConfig.tools ? { tools: [...recoveryAgentConfig.tools] } : {}),
|
|
1525
1643
|
...(recoveryAgentConfig.extensions ? { extensions: [...recoveryAgentConfig.extensions] } : {}),
|
|
1526
1644
|
...(recoveryAgentConfig.subagentOnlyExtensions ? { subagentOnlyExtensions: [...recoveryAgentConfig.subagentOnlyExtensions] } : {}),
|
|
@@ -1572,11 +1690,16 @@ export function executeAsyncSingle(
|
|
|
1572
1690
|
agent,
|
|
1573
1691
|
task: taskText,
|
|
1574
1692
|
...(agentConfig.runner ? { runner: agentConfig.runner } : {}),
|
|
1693
|
+
...(params.externalJobFollowUp ? { externalJobFollowUp: params.externalJobFollowUp } : {}),
|
|
1575
1694
|
...(params.context ? { context: params.context } : {}),
|
|
1576
1695
|
cwd: runnerCwd,
|
|
1577
1696
|
model,
|
|
1697
|
+
...(params.fast ?? agentConfig.fast ? { fast: params.fast ?? agentConfig.fast } : {}),
|
|
1578
1698
|
thinking: resolveEffectiveThinking(model, effectiveThinking),
|
|
1699
|
+
...(thinkingCeiling ? { thinkingCeiling } : {}),
|
|
1579
1700
|
modelCandidates,
|
|
1701
|
+
...(params.modelOverrideFromParent ? { skipPrimaryModelVerification: true } : {}),
|
|
1702
|
+
...(availableModels && availableModels.length > 0 ? { modelVerificationRegistry: availableModels } : {}),
|
|
1580
1703
|
tools: agentConfig.tools,
|
|
1581
1704
|
extensions: agentConfig.extensions,
|
|
1582
1705
|
subagentOnlyExtensions: agentConfig.subagentOnlyExtensions,
|
|
@@ -1596,6 +1719,7 @@ export function executeAsyncSingle(
|
|
|
1596
1719
|
definitionDigest: agentDefinitionDigest(agentConfig),
|
|
1597
1720
|
launchBindingTask: task,
|
|
1598
1721
|
launchContractDigest,
|
|
1722
|
+
...(extensionBindings ? { extensionBindings } : {}),
|
|
1599
1723
|
launchResolvedExtensions,
|
|
1600
1724
|
effectiveAcceptance: resolvedAcceptance,
|
|
1601
1725
|
...(structuredOutput ? { structuredOutput } : {}),
|
|
@@ -1604,7 +1728,7 @@ export function executeAsyncSingle(
|
|
|
1604
1728
|
...(params.worktree === true ? { worktree: true } : {}),
|
|
1605
1729
|
},
|
|
1606
1730
|
],
|
|
1607
|
-
resultPath: params.parentWorkflowRunId !== undefined && params.revivalLease !== undefined
|
|
1731
|
+
resultPath: params.parentWorkflowRunId !== undefined && (params.revivalLease !== undefined || params.workflowAwaitAsync === true)
|
|
1608
1732
|
? workflowAwaitedAsyncResultPath(asyncDir)
|
|
1609
1733
|
: inheritedNestedRoute ? nestedResultsPath(inheritedNestedRoute.rootRunId, id) : resultFilePath(DIRS.results, id),
|
|
1610
1734
|
cwd: runnerCwd,
|
|
@@ -8,12 +8,14 @@ import {
|
|
|
8
8
|
type AsyncStartedEvent,
|
|
9
9
|
type ControlEvent,
|
|
10
10
|
type SteeringNotice,
|
|
11
|
+
type SubagentChildStatusEvent,
|
|
11
12
|
type SubagentState,
|
|
12
|
-
POLL_INTERVAL_MS,
|
|
13
13
|
DIRS,
|
|
14
|
+
SUBAGENT_CHILD_STATUS_EVENT,
|
|
14
15
|
SUBAGENT_CONTROL_EVENT,
|
|
15
16
|
SUBAGENT_CONTROL_INTERCOM_EVENT,
|
|
16
17
|
SUBAGENT_STEERING_NOTICE_EVENT,
|
|
18
|
+
WIDGET_ANIMATION_INTERVAL_MS,
|
|
17
19
|
} from "../../shared/types.ts";
|
|
18
20
|
import { readStatus, resolveWatchPath } from "../../shared/utils.ts";
|
|
19
21
|
import { normalizeParallelGroups } from "./parallel-groups.ts";
|
|
@@ -71,6 +73,7 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
71
73
|
const runningJobIds = new Set<string>();
|
|
72
74
|
let rootWatcher: fs.FSWatcher | undefined;
|
|
73
75
|
let nextLivenessAt = Date.now() + livenessIntervalMs;
|
|
76
|
+
let nextWidgetAnimationAt = Date.now() + WIDGET_ANIMATION_INTERVAL_MS;
|
|
74
77
|
const watch = options.watch ?? fs.watch;
|
|
75
78
|
const useNativeWatcher = () => shouldUseNativeFsWatch("async-job-tracker", options.platform);
|
|
76
79
|
const terminalStatus = (status: string) => status === "complete" || status === "failed" || status === "paused" || status === "stopped";
|
|
@@ -93,17 +96,8 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
93
96
|
}
|
|
94
97
|
};
|
|
95
98
|
const requestLastWidgetRender = () => {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
try {
|
|
99
|
-
if (ctx.hasUI) (ctx.ui as { requestRender?: () => void }).requestRender?.();
|
|
100
|
-
} catch (error) {
|
|
101
|
-
if (error instanceof Error && error.message.includes("extension ctx is stale")) {
|
|
102
|
-
state.lastUiContext = null;
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
throw error;
|
|
106
|
-
}
|
|
99
|
+
if (options.widgetEnabled === false) return;
|
|
100
|
+
rerenderLastWidget();
|
|
107
101
|
};
|
|
108
102
|
const refreshWidget = (ctx: ExtensionContext) => rerenderWidget(ctx);
|
|
109
103
|
const restoredControlEventCursor = (asyncDir: string) => {
|
|
@@ -227,6 +221,28 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
227
221
|
return;
|
|
228
222
|
}
|
|
229
223
|
if (!parsed || typeof parsed !== "object") return;
|
|
224
|
+
if ((parsed as { type?: unknown }).type === "subagent.child-status") {
|
|
225
|
+
const event = parsed as Partial<SubagentChildStatusEvent>;
|
|
226
|
+
if (event.version !== 1 || typeof event.runId !== "string" || typeof event.childId !== "string" || (event.status !== "stopping" && event.status !== "stopped") || typeof event.ts !== "number") return;
|
|
227
|
+
pi.events.emit(SUBAGENT_CHILD_STATUS_EVENT, {
|
|
228
|
+
type: "subagent.child-status",
|
|
229
|
+
version: 1,
|
|
230
|
+
runId: event.runId,
|
|
231
|
+
childId: event.childId,
|
|
232
|
+
status: event.status,
|
|
233
|
+
ts: event.ts,
|
|
234
|
+
...(typeof event.reason === "string" ? { reason: event.reason } : {}),
|
|
235
|
+
source: event.source === "rpc" ? "rpc" : "async",
|
|
236
|
+
asyncDir: job.asyncDir,
|
|
237
|
+
...(typeof event.stepIndex === "number" ? { stepIndex: event.stepIndex } : {}),
|
|
238
|
+
...(typeof event.agent === "string" ? { agent: event.agent } : {}),
|
|
239
|
+
...(typeof event.childRunId === "string" ? { childRunId: event.childRunId } : {}),
|
|
240
|
+
...(typeof event.workflowKey === "string" ? { workflowKey: event.workflowKey } : {}),
|
|
241
|
+
...(typeof event.phase === "string" ? { phase: event.phase } : {}),
|
|
242
|
+
...(typeof event.label === "string" ? { label: event.label } : {}),
|
|
243
|
+
} satisfies SubagentChildStatusEvent);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
230
246
|
if ((parsed as { type?: unknown }).type === "subagent.steering.notice") {
|
|
231
247
|
const notice = parsed as Partial<SteeringNotice>;
|
|
232
248
|
if (typeof notice.requestId !== "string" || typeof notice.runId !== "string" || (notice.state !== "failed" && notice.state !== "partial" && notice.state !== "recovered") || typeof notice.message !== "string") return;
|
|
@@ -556,8 +572,11 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
556
572
|
}
|
|
557
573
|
if (widgetChanged) rerenderLastWidget();
|
|
558
574
|
}
|
|
559
|
-
if (runningJobIds.size > 0)
|
|
560
|
-
|
|
575
|
+
if (runningJobIds.size > 0 && now >= nextWidgetAnimationAt) {
|
|
576
|
+
nextWidgetAnimationAt = now + WIDGET_ANIMATION_INTERVAL_MS;
|
|
577
|
+
requestLastWidgetRender();
|
|
578
|
+
}
|
|
579
|
+
}, Math.min(WIDGET_ANIMATION_INTERVAL_MS, livenessIntervalMs));
|
|
561
580
|
state.poller.unref?.();
|
|
562
581
|
};
|
|
563
582
|
|
|
@@ -2,6 +2,7 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { DIRS, type AcceptanceInput, type AsyncStatus, type ResolvedTurnBudget, type SteeringRecoveryDescriptor, type SubagentRunMode } from "../../shared/types.ts";
|
|
4
4
|
import type { AgentConfig } from "../../agents/agents.ts";
|
|
5
|
+
import { normalizeExtensionBindings } from "../shared/extension-bindings.ts";
|
|
5
6
|
import { validateAcceptanceInput } from "../shared/acceptance.ts";
|
|
6
7
|
import { validateToolBudgetConfig } from "../shared/tool-budget.ts";
|
|
7
8
|
import { intersectSubagentCapabilityCeilings, parseSubagentCapabilityCeiling, type ResolvedSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
|
|
@@ -11,6 +12,7 @@ import { reconcileAsyncRun } from "./stale-run-reconciler.ts";
|
|
|
11
12
|
import { resultFilePath, resultPayloadPathForIndexedRun } from "./result-files.ts";
|
|
12
13
|
import { canScanAsyncRunPrefix, MIN_SAFE_ASYNC_RUN_PREFIX_LENGTH } from "./run-id-query.ts";
|
|
13
14
|
import { parallelHandoffPath, resolveRetainedWorktreeCwd } from "../shared/parallel-handoff.ts";
|
|
15
|
+
import { intersectThinkingCeilings, parseThinkingLevel, type ThinkingLevel } from "../../shared/thinking-ceiling.ts";
|
|
14
16
|
|
|
15
17
|
export interface AsyncResumeParams {
|
|
16
18
|
id?: string;
|
|
@@ -43,9 +45,12 @@ export type AsyncResumeTarget = {
|
|
|
43
45
|
sessionFile?: string;
|
|
44
46
|
model?: string;
|
|
45
47
|
thinking?: string;
|
|
48
|
+
thinkingCeiling?: ThinkingLevel;
|
|
46
49
|
recoveryDescriptor?: SteeringRecoveryDescriptor;
|
|
47
50
|
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
48
51
|
launchContractDigest?: string;
|
|
52
|
+
runner?: NonNullable<AsyncStatus["steps"]>[number]["runner"];
|
|
53
|
+
externalJob?: NonNullable<AsyncStatus["steps"]>[number]["externalJob"];
|
|
49
54
|
};
|
|
50
55
|
|
|
51
56
|
interface AsyncResultFile {
|
|
@@ -265,6 +270,7 @@ function validateStatusForResume(status: AsyncStatus | null, source: string): vo
|
|
|
265
270
|
if (stepRecord.sessionFile !== undefined && typeof stepRecord.sessionFile !== "string") throw new Error(`Invalid async status '${source}': steps[${index}].sessionFile must be a string.`);
|
|
266
271
|
if (stepRecord.model !== undefined && typeof stepRecord.model !== "string") throw new Error(`Invalid async status '${source}': steps[${index}].model must be a string.`);
|
|
267
272
|
if (stepRecord.thinking !== undefined && typeof stepRecord.thinking !== "string") throw new Error(`Invalid async status '${source}': steps[${index}].thinking must be a string.`);
|
|
273
|
+
if (stepRecord.thinkingCeiling !== undefined) stepRecord.thinkingCeiling = parseThinkingLevel(stepRecord.thinkingCeiling, `async status '${source}' steps[${index}].thinkingCeiling`);
|
|
268
274
|
if (stepRecord.launchContractDigest !== undefined && typeof stepRecord.launchContractDigest !== "string") throw new Error(`Invalid async status '${source}': steps[${index}].launchContractDigest must be a string.`);
|
|
269
275
|
if (stepRecord.capabilityCeiling !== undefined) stepRecord.capabilityCeiling = parseSubagentCapabilityCeiling(stepRecord.capabilityCeiling, `async status '${source}' steps[${index}].capabilityCeiling`);
|
|
270
276
|
});
|
|
@@ -318,11 +324,12 @@ export function readAsyncRecoveryDescriptor(asyncDir: string | undefined): Steer
|
|
|
318
324
|
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': expected an object.`);
|
|
319
325
|
const parsed = value as Record<string, unknown>;
|
|
320
326
|
const allowedFields = new Set([
|
|
321
|
-
"version", "launchContractDigest", "sourceRunId", "agentContract", "agent", "sessionFile", "cwd", "model", "modelOverrideFromParent", "fallbackModels", "thinking", "tools", "extensions",
|
|
327
|
+
"version", "launchContractDigest", "sourceRunId", "agentContract", "agent", "sessionFile", "cwd", "model", "modelProvider", "modelOverrideFromParent", "fallbackModels", "thinking", "thinkingCeiling", "tools", "extensions",
|
|
322
328
|
"subagentOnlyExtensions", "mcpDirectTools", "systemPrompt", "systemPromptMode", "inheritProjectContext", "inheritSkills", "skills",
|
|
323
329
|
"skillPath", "agentFilePath", "completionGuard", "memory", "outputPath", "outputMode", "structuredOutputSchema", "acceptance", "sessionDir", "artifactConfig",
|
|
324
330
|
"artifactsDir", "maxOutput", "controlConfig", "context", "intercomBridge", "absoluteDeadlineAt", "initialTurnBudget", "initialToolBudget", "maxSubagentDepth", "share", "capabilityCeiling",
|
|
325
331
|
"launchResolvedExtensions", "runFanoutBudget",
|
|
332
|
+
"extensionBindings",
|
|
326
333
|
]);
|
|
327
334
|
for (const field of Object.keys(parsed)) {
|
|
328
335
|
if (!allowedFields.has(field)) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': unknown field '${field}'.`);
|
|
@@ -338,6 +345,8 @@ export function readAsyncRecoveryDescriptor(asyncDir: string | undefined): Steer
|
|
|
338
345
|
throw new Error(`Invalid async recovery descriptor '${descriptorPath}': ${error instanceof Error ? error.message : String(error)}`);
|
|
339
346
|
}
|
|
340
347
|
if (parsed.capabilityCeiling !== undefined) parsed.capabilityCeiling = parseSubagentCapabilityCeiling(parsed.capabilityCeiling, `async recovery descriptor '${descriptorPath}' capabilityCeiling`);
|
|
348
|
+
if (parsed.thinkingCeiling !== undefined) parsed.thinkingCeiling = parseThinkingLevel(parsed.thinkingCeiling, `async recovery descriptor '${descriptorPath}' thinkingCeiling`);
|
|
349
|
+
if (parsed.extensionBindings !== undefined) parsed.extensionBindings = normalizeExtensionBindings(parsed.extensionBindings)!.value;
|
|
341
350
|
if (parsed.agentContract !== undefined) {
|
|
342
351
|
if (!parsed.agentContract || typeof parsed.agentContract !== "object" || Array.isArray(parsed.agentContract)) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': agentContract must be an object.`);
|
|
343
352
|
const contract = parsed.agentContract as Record<string, unknown>;
|
|
@@ -356,7 +365,7 @@ export function readAsyncRecoveryDescriptor(asyncDir: string | undefined): Steer
|
|
|
356
365
|
if (item !== undefined && (!Array.isArray(item) || item.some((entry) => typeof entry !== "string" || !entry.trim()))) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': ${field} must contain non-empty strings.`);
|
|
357
366
|
}
|
|
358
367
|
if (parsed.systemPrompt !== undefined && typeof parsed.systemPrompt !== "string") throw new Error(`Invalid async recovery descriptor '${descriptorPath}': systemPrompt must be a string.`);
|
|
359
|
-
for (const field of ["launchContractDigest", "sessionFile", "model", "thinking", "agentFilePath", "outputPath", "sessionDir", "artifactsDir"] as const) {
|
|
368
|
+
for (const field of ["launchContractDigest", "sessionFile", "model", "modelProvider", "thinking", "agentFilePath", "outputPath", "sessionDir", "artifactsDir"] as const) {
|
|
360
369
|
if (parsed[field] !== undefined && (typeof parsed[field] !== "string" || !(parsed[field] as string).trim())) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': ${field} must be a non-empty string.`);
|
|
361
370
|
}
|
|
362
371
|
if (parsed.completionGuard !== undefined && typeof parsed.completionGuard !== "boolean") throw new Error(`Invalid async recovery descriptor '${descriptorPath}': completionGuard must be a boolean.`);
|
|
@@ -487,7 +496,10 @@ export function resolveAsyncResumeTarget(params: AsyncResumeParams, deps: AsyncR
|
|
|
487
496
|
model: selectedStep.model,
|
|
488
497
|
thinking: selectedStep.thinking,
|
|
489
498
|
launchContractDigest: selectedStep.launchContractDigest ?? result?.results?.[requestedIndex]?.launchContractDigest ?? result?.launchContractDigest ?? recoveryDescriptor?.launchContractDigest,
|
|
499
|
+
...(selectedStep.runner ? { runner: selectedStep.runner } : {}),
|
|
500
|
+
...(selectedStep.externalJob ? { externalJob: selectedStep.externalJob } : {}),
|
|
490
501
|
...(capabilityCeiling ? { capabilityCeiling } : {}),
|
|
502
|
+
...(selectedStep.thinkingCeiling ? { thinkingCeiling: selectedStep.thinkingCeiling } : {}),
|
|
491
503
|
...(recoveryDescriptor ? { recoveryDescriptor } : {}),
|
|
492
504
|
};
|
|
493
505
|
}
|
|
@@ -515,7 +527,10 @@ export function resolveAsyncResumeTarget(params: AsyncResumeParams, deps: AsyncR
|
|
|
515
527
|
model: selected.step.model,
|
|
516
528
|
thinking: selected.step.thinking,
|
|
517
529
|
launchContractDigest: selected.step.launchContractDigest ?? result?.results?.[selected.index]?.launchContractDigest ?? result?.launchContractDigest ?? recoveryDescriptor?.launchContractDigest,
|
|
530
|
+
...(selected.step.runner ? { runner: selected.step.runner } : {}),
|
|
531
|
+
...(selected.step.externalJob ? { externalJob: selected.step.externalJob } : {}),
|
|
518
532
|
...(capabilityCeiling ? { capabilityCeiling } : {}),
|
|
533
|
+
...(selected.step.thinkingCeiling ? { thinkingCeiling: selected.step.thinkingCeiling } : {}),
|
|
519
534
|
...(recoveryDescriptor ? { recoveryDescriptor } : {}),
|
|
520
535
|
};
|
|
521
536
|
}
|
|
@@ -537,6 +552,7 @@ export function resolveAsyncResumeTarget(params: AsyncResumeParams, deps: AsyncR
|
|
|
537
552
|
const resolvedSessionFile = sessionFile ? validateResumeSessionFile(runId, sessionFile) : undefined;
|
|
538
553
|
const stepModel = statusSteps[index]?.model ?? resultSteps[index]?.model ?? (stepCount === 1 ? result?.model : undefined);
|
|
539
554
|
const stepThinking = statusSteps[index]?.thinking ?? resultSteps[index]?.thinking ?? (stepCount === 1 ? result?.thinking : undefined);
|
|
555
|
+
const thinkingCeiling = statusSteps[index]?.thinkingCeiling ?? (stepCount === 1 ? recoveryDescriptor?.thinkingCeiling : undefined);
|
|
540
556
|
const capabilityCeiling = intersectSubagentCapabilityCeilings(status?.capabilityCeiling, statusSteps[index]?.capabilityCeiling, result?.capabilityCeiling, resultSteps[index]?.capabilityCeiling);
|
|
541
557
|
const managedWorktreeCwd = location.asyncDir
|
|
542
558
|
? resolveRetainedWorktreeCwd(parallelHandoffPath(location.asyncDir), runId, index)
|
|
@@ -556,7 +572,10 @@ export function resolveAsyncResumeTarget(params: AsyncResumeParams, deps: AsyncR
|
|
|
556
572
|
...(stepModel ? { model: stepModel } : {}),
|
|
557
573
|
...(stepThinking ? { thinking: stepThinking } : {}),
|
|
558
574
|
launchContractDigest: statusSteps[index]?.launchContractDigest ?? resultSteps[index]?.launchContractDigest ?? result?.launchContractDigest ?? recoveryDescriptor?.launchContractDigest,
|
|
575
|
+
...(statusSteps[index]?.runner ? { runner: statusSteps[index]!.runner } : {}),
|
|
576
|
+
...(statusSteps[index]?.externalJob ? { externalJob: statusSteps[index]!.externalJob } : {}),
|
|
559
577
|
...(capabilityCeiling ? { capabilityCeiling } : {}),
|
|
578
|
+
...(thinkingCeiling ? { thinkingCeiling } : {}),
|
|
560
579
|
...(recoveryDescriptor ? { recoveryDescriptor } : {}),
|
|
561
580
|
};
|
|
562
581
|
}
|
|
@@ -565,8 +584,10 @@ export function applySteeringRecoveryAgentConfig(agentConfig: AgentConfig, descr
|
|
|
565
584
|
return {
|
|
566
585
|
...agentConfig,
|
|
567
586
|
model: descriptor.model,
|
|
587
|
+
modelProvider: descriptor.modelProvider,
|
|
568
588
|
fallbackModels: descriptor.fallbackModels ? [...descriptor.fallbackModels] : undefined,
|
|
569
589
|
thinking: descriptor.thinking,
|
|
590
|
+
maxThinking: intersectThinkingCeilings(descriptor.thinkingCeiling, agentConfig.maxThinking),
|
|
570
591
|
tools: descriptor.tools ? [...descriptor.tools] : undefined,
|
|
571
592
|
extensions: descriptor.extensions ? [...descriptor.extensions] : undefined,
|
|
572
593
|
subagentOnlyExtensions: descriptor.subagentOnlyExtensions ? [...descriptor.subagentOnlyExtensions] : undefined,
|