pi-subagents 0.27.0 → 0.29.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 +27 -0
- package/README.md +16 -15
- package/package.json +1 -1
- package/skills/pi-subagents/SKILL.md +3 -6
- package/src/agents/agent-management.ts +10 -6
- package/src/agents/agent-selection.ts +2 -0
- package/src/agents/agents.ts +303 -6
- package/src/agents/chain-serializer.ts +4 -9
- package/src/extension/doctor.ts +4 -3
- package/src/extension/fanout-child.ts +0 -1
- package/src/extension/index.ts +1 -4
- package/src/extension/schemas.ts +31 -28
- package/src/intercom/intercom-bridge.ts +11 -1
- package/src/runs/background/async-execution.ts +20 -7
- package/src/runs/background/run-status.ts +1 -7
- package/src/runs/background/subagent-runner.ts +73 -146
- package/src/runs/foreground/chain-execution.ts +61 -13
- package/src/runs/foreground/execution.ts +28 -172
- package/src/runs/foreground/subagent-executor.ts +25 -40
- package/src/runs/shared/acceptance.ts +605 -22
- package/src/runs/shared/completion-guard.ts +3 -26
- package/src/runs/shared/model-fallback.ts +38 -0
- package/src/runs/shared/parallel-utils.ts +6 -8
- package/src/runs/shared/subagent-prompt-runtime.ts +3 -2
- package/src/shared/atomic-json.ts +68 -11
- package/src/shared/settings.ts +1 -0
- package/src/shared/types.ts +8 -32
- package/src/shared/utils.ts +2 -1
- package/src/tui/render.ts +1 -11
- package/src/runs/shared/acceptance-contract.ts +0 -291
- package/src/runs/shared/acceptance-evaluation.ts +0 -221
- package/src/runs/shared/acceptance-finalization.ts +0 -161
- package/src/runs/shared/acceptance-reports.ts +0 -127
|
@@ -59,12 +59,13 @@ import {
|
|
|
59
59
|
MAX_CONCURRENCY,
|
|
60
60
|
resolveChildMaxSubagentDepth,
|
|
61
61
|
} from "../../shared/types.ts";
|
|
62
|
-
import {
|
|
62
|
+
import { resolveSubagentModelOverride } from "../shared/model-fallback.ts";
|
|
63
63
|
import { validateFileOnlyOutputMode } from "../shared/single-output.ts";
|
|
64
64
|
import { buildWorkflowGraphSnapshot } from "../shared/workflow-graph.ts";
|
|
65
65
|
import { ChainOutputValidationError, outputEntryFromResult, resolveOutputReferences, validateChainOutputBindings } from "../shared/chain-outputs.ts";
|
|
66
66
|
import { createStructuredOutputRuntime } from "../shared/structured-output.ts";
|
|
67
67
|
import { collectDynamicResults, DynamicFanoutError, materializeDynamicParallelStep, validateDynamicCollection, type DynamicCollectedResult } from "../shared/dynamic-fanout.ts";
|
|
68
|
+
import { acceptanceFailureMessage, aggregateAcceptanceReport, evaluateAcceptance, resolveEffectiveAcceptance } from "../shared/acceptance.ts";
|
|
68
69
|
import type { ChainOutputMap } from "../../shared/types.ts";
|
|
69
70
|
|
|
70
71
|
interface ChainExecutionDetailsInput {
|
|
@@ -231,9 +232,12 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
|
|
|
231
232
|
taskStr = prefix + taskStr + suffix;
|
|
232
233
|
|
|
233
234
|
const taskAgentConfig = input.agents.find((agent) => agent.name === task.agent);
|
|
234
|
-
const effectiveModel =
|
|
235
|
-
|
|
236
|
-
|
|
235
|
+
const effectiveModel = resolveSubagentModelOverride(
|
|
236
|
+
task.model ?? taskAgentConfig?.model,
|
|
237
|
+
input.ctx.model,
|
|
238
|
+
input.availableModels,
|
|
239
|
+
input.ctx.model?.provider,
|
|
240
|
+
);
|
|
237
241
|
const maxSubagentDepth = resolveChildMaxSubagentDepth(input.maxSubagentDepth, taskAgentConfig?.maxSubagentDepth);
|
|
238
242
|
|
|
239
243
|
const taskCwd = input.worktreeSetup
|
|
@@ -750,11 +754,6 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
750
754
|
if (worktreeSetup) cleanupWorktrees(worktreeSetup);
|
|
751
755
|
}
|
|
752
756
|
} else if (isDynamicParallelStep(step)) {
|
|
753
|
-
if (Object.hasOwn(step, "acceptance")) {
|
|
754
|
-
const message = `Dynamic fanout step ${stepIndex + 1} does not support group-level acceptance; set acceptance on the child template instead.`;
|
|
755
|
-
dynamicGroupStatuses[stepIndex] = { status: "failed", error: message };
|
|
756
|
-
return buildChainExecutionErrorResult(message, makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex }));
|
|
757
|
-
}
|
|
758
757
|
let materialized: ReturnType<typeof materializeDynamicParallelStep>;
|
|
759
758
|
try {
|
|
760
759
|
materialized = materializeDynamicParallelStep(step, outputs, stepIndex, { maxItems: params.dynamicFanoutMaxItems });
|
|
@@ -788,6 +787,30 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
788
787
|
stepIndex,
|
|
789
788
|
};
|
|
790
789
|
dynamicGroupStatuses[stepIndex] = { status: "completed" };
|
|
790
|
+
if (step.acceptance !== undefined) {
|
|
791
|
+
const effectiveGroupAcceptance = resolveEffectiveAcceptance({
|
|
792
|
+
explicit: step.acceptance,
|
|
793
|
+
agentName: step.parallel.agent,
|
|
794
|
+
task: step.parallel.task ?? originalTask,
|
|
795
|
+
mode: "chain",
|
|
796
|
+
dynamicGroup: true,
|
|
797
|
+
});
|
|
798
|
+
const groupAcceptance = await evaluateAcceptance({
|
|
799
|
+
acceptance: effectiveGroupAcceptance,
|
|
800
|
+
output: "",
|
|
801
|
+
report: aggregateAcceptanceReport({
|
|
802
|
+
results: [],
|
|
803
|
+
notes: "Dynamic fanout produced 0 results.",
|
|
804
|
+
}),
|
|
805
|
+
cwd: cwd ?? ctx.cwd,
|
|
806
|
+
});
|
|
807
|
+
dynamicGroupStatuses[stepIndex].acceptance = groupAcceptance;
|
|
808
|
+
const groupAcceptanceFailure = acceptanceFailureMessage(groupAcceptance);
|
|
809
|
+
if (groupAcceptanceFailure) {
|
|
810
|
+
dynamicGroupStatuses[stepIndex] = { status: "failed", error: groupAcceptanceFailure, acceptance: groupAcceptance };
|
|
811
|
+
return buildChainExecutionErrorResult(groupAcceptanceFailure, makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex }));
|
|
812
|
+
}
|
|
813
|
+
}
|
|
791
814
|
prev = "Dynamic fanout produced 0 results.";
|
|
792
815
|
continue;
|
|
793
816
|
}
|
|
@@ -919,6 +942,28 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
919
942
|
stepIndex,
|
|
920
943
|
};
|
|
921
944
|
dynamicGroupStatuses[stepIndex] = { status: "completed" };
|
|
945
|
+
const effectiveGroupAcceptance = resolveEffectiveAcceptance({
|
|
946
|
+
explicit: step.acceptance,
|
|
947
|
+
agentName: step.parallel.agent,
|
|
948
|
+
task: step.parallel.task ?? originalTask,
|
|
949
|
+
mode: "chain",
|
|
950
|
+
dynamicGroup: true,
|
|
951
|
+
});
|
|
952
|
+
const groupAcceptance = await evaluateAcceptance({
|
|
953
|
+
acceptance: effectiveGroupAcceptance,
|
|
954
|
+
output: "",
|
|
955
|
+
report: aggregateAcceptanceReport({
|
|
956
|
+
results: parallelResults,
|
|
957
|
+
notes: `Dynamic fanout collected ${collected.length} result(s) into ${step.collect.as}.`,
|
|
958
|
+
}),
|
|
959
|
+
cwd: cwd ?? ctx.cwd,
|
|
960
|
+
});
|
|
961
|
+
dynamicGroupStatuses[stepIndex].acceptance = groupAcceptance;
|
|
962
|
+
const groupAcceptanceFailure = acceptanceFailureMessage(groupAcceptance);
|
|
963
|
+
if (groupAcceptanceFailure) {
|
|
964
|
+
dynamicGroupStatuses[stepIndex] = { status: "failed", error: groupAcceptanceFailure, acceptance: groupAcceptance };
|
|
965
|
+
return buildChainExecutionErrorResult(groupAcceptanceFailure, makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex - dynamicParallelStep.parallel.length }));
|
|
966
|
+
}
|
|
922
967
|
const taskResults: ParallelTaskResult[] = parallelResults.map((result, i) => ({
|
|
923
968
|
agent: result.agent,
|
|
924
969
|
taskIndex: i,
|
|
@@ -974,10 +1019,13 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
974
1019
|
const cleanTask = stepTask;
|
|
975
1020
|
stepTask = prefix + stepTask + suffix;
|
|
976
1021
|
|
|
977
|
-
const effectiveModel =
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
1022
|
+
const effectiveModel = tuiOverride?.model
|
|
1023
|
+
?? resolveSubagentModelOverride(
|
|
1024
|
+
seqStep.model ?? agentConfig.model,
|
|
1025
|
+
ctx.model,
|
|
1026
|
+
availableModels,
|
|
1027
|
+
ctx.model?.provider,
|
|
1028
|
+
);
|
|
981
1029
|
|
|
982
1030
|
const outputPath = typeof behavior.output === "string"
|
|
983
1031
|
? (path.isAbsolute(behavior.output) ? behavior.output : path.join(chainDir, behavior.output))
|
|
@@ -3,9 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
6
|
-
import { existsSync,
|
|
7
|
-
import * as os from "node:os";
|
|
8
|
-
import * as path from "node:path";
|
|
6
|
+
import { existsSync, unlinkSync } from "node:fs";
|
|
9
7
|
import type { Message } from "@earendil-works/pi-ai";
|
|
10
8
|
import type { AgentConfig } from "../../agents/agents.ts";
|
|
11
9
|
import {
|
|
@@ -15,13 +13,10 @@ import {
|
|
|
15
13
|
writeMetadata,
|
|
16
14
|
} from "../../shared/artifacts.ts";
|
|
17
15
|
import {
|
|
18
|
-
type AcceptanceFinalizationTurn,
|
|
19
|
-
type AcceptanceLedger,
|
|
20
16
|
type AgentProgress,
|
|
21
17
|
type ArtifactPaths,
|
|
22
18
|
type ControlEvent,
|
|
23
19
|
type ModelAttempt,
|
|
24
|
-
type ResolvedAcceptanceConfig,
|
|
25
20
|
type RunSyncOptions,
|
|
26
21
|
type SingleResult,
|
|
27
22
|
type Usage,
|
|
@@ -46,7 +41,7 @@ import {
|
|
|
46
41
|
extractTextFromContent,
|
|
47
42
|
} from "../../shared/utils.ts";
|
|
48
43
|
import { buildSkillInjection, resolveSkillsWithFallback } from "../../agents/skills.ts";
|
|
49
|
-
import { evaluateCompletionMutationGuard
|
|
44
|
+
import { evaluateCompletionMutationGuard } from "../shared/completion-guard.ts";
|
|
50
45
|
import { getPiSpawnCommand } from "../shared/pi-spawn.ts";
|
|
51
46
|
import { createJsonlWriter } from "../../shared/jsonl-writer.ts";
|
|
52
47
|
import { attachPostExitStdioGuard, trySignalChild } from "../../shared/post-exit-stdio-guard.ts";
|
|
@@ -69,20 +64,7 @@ import {
|
|
|
69
64
|
shouldEscalateMutatingFailures,
|
|
70
65
|
summarizeRecentMutatingFailures,
|
|
71
66
|
} from "../shared/long-running-guard.ts";
|
|
72
|
-
import {
|
|
73
|
-
acceptanceFailureMessage,
|
|
74
|
-
acceptanceSelfReviewConfig,
|
|
75
|
-
attachFinalizationToLedger,
|
|
76
|
-
buildFinalizationProcessFailureLedger,
|
|
77
|
-
createFinalizationProcessFailureTurn,
|
|
78
|
-
createFinalizationTurn,
|
|
79
|
-
evaluateAcceptance,
|
|
80
|
-
formatAcceptanceFinalizationPrompt,
|
|
81
|
-
formatAcceptancePrompt,
|
|
82
|
-
resolveEffectiveAcceptance,
|
|
83
|
-
shouldRunAcceptanceFinalization,
|
|
84
|
-
stripAcceptanceReport,
|
|
85
|
-
} from "../shared/acceptance.ts";
|
|
67
|
+
import { acceptanceFailureMessage, evaluateAcceptance, formatAcceptancePrompt, resolveEffectiveAcceptance, stripAcceptanceReport } from "../shared/acceptance.ts";
|
|
86
68
|
|
|
87
69
|
const artifactOutputByResult = new WeakMap<SingleResult, string>();
|
|
88
70
|
const acceptanceOutputByResult = new WeakMap<SingleResult, string>();
|
|
@@ -166,11 +148,10 @@ async function runSingleAttempt(
|
|
|
166
148
|
attemptNotes: string[];
|
|
167
149
|
outputSnapshot?: SingleOutputSnapshot;
|
|
168
150
|
originalTask?: string;
|
|
169
|
-
completionPolicy: CompletionPolicy;
|
|
170
151
|
},
|
|
171
152
|
): Promise<SingleResult> {
|
|
172
153
|
const modelArg = applyThinkingSuffix(model, agent.thinking);
|
|
173
|
-
|
|
154
|
+
const { args, env: sharedEnv, tempDir } = buildPiArgs({
|
|
174
155
|
baseArgs: ["--mode", "json", "-p"],
|
|
175
156
|
task,
|
|
176
157
|
sessionEnabled: shared.sessionEnabled,
|
|
@@ -194,10 +175,10 @@ async function runSingleAttempt(
|
|
|
194
175
|
childIndex: options.index ?? 0,
|
|
195
176
|
parentEventSink: options.nestedRoute?.eventSink,
|
|
196
177
|
parentControlInbox: options.nestedRoute?.controlInbox,
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
178
|
+
parentRootRunId: options.nestedRoute?.rootRunId,
|
|
179
|
+
parentCapabilityToken: options.nestedRoute?.capabilityToken,
|
|
180
|
+
structuredOutput: options.structuredOutput,
|
|
181
|
+
});
|
|
201
182
|
|
|
202
183
|
const result: SingleResult = {
|
|
203
184
|
agent: agent.name,
|
|
@@ -728,9 +709,9 @@ async function runSingleAttempt(
|
|
|
728
709
|
durationMs: progress.durationMs,
|
|
729
710
|
};
|
|
730
711
|
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
const completionGuard = result.exitCode === 0 && !result.error &&
|
|
712
|
+
const acceptanceOutput = getFinalOutput(result.messages);
|
|
713
|
+
let fullOutput = stripAcceptanceReport(acceptanceOutput);
|
|
714
|
+
const completionGuard = result.exitCode === 0 && !result.error && agent.completionGuard !== false
|
|
734
715
|
? evaluateCompletionMutationGuard({
|
|
735
716
|
agent: agent.name,
|
|
736
717
|
task: shared.originalTask ?? task,
|
|
@@ -739,8 +720,7 @@ async function runSingleAttempt(
|
|
|
739
720
|
mcpDirectTools: agent.mcpDirectTools,
|
|
740
721
|
})
|
|
741
722
|
: undefined;
|
|
742
|
-
|
|
743
|
-
if (completionGuardTriggered) {
|
|
723
|
+
if (completionGuard?.triggered && !observedMutationAttempt) {
|
|
744
724
|
result.exitCode = 1;
|
|
745
725
|
result.error = "Subagent completed without making edits for an implementation task.\nIt appears to have returned planning or scratchpad output instead of applying changes.";
|
|
746
726
|
progress.status = "failed";
|
|
@@ -756,17 +736,17 @@ async function runSingleAttempt(
|
|
|
756
736
|
reason: "completion_guard",
|
|
757
737
|
}));
|
|
758
738
|
}
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
739
|
+
if (options.outputPath && result.exitCode === 0) {
|
|
740
|
+
const resolvedOutput = resolveSingleOutput(options.outputPath, fullOutput, shared.outputSnapshot);
|
|
741
|
+
fullOutput = stripAcceptanceReport(resolvedOutput.fullOutput);
|
|
742
|
+
result.savedOutputPath = resolvedOutput.savedPath;
|
|
743
|
+
result.outputSaveError = resolvedOutput.saveError;
|
|
744
|
+
if (resolvedOutput.savedPath) {
|
|
745
|
+
result.outputReference = formatSavedOutputReference(resolvedOutput.savedPath, fullOutput);
|
|
746
|
+
}
|
|
767
747
|
}
|
|
768
|
-
|
|
769
|
-
|
|
748
|
+
artifactOutputByResult.set(result, fullOutput);
|
|
749
|
+
acceptanceOutputByResult.set(result, acceptanceOutput);
|
|
770
750
|
result.outputMode = options.outputMode ?? "inline";
|
|
771
751
|
result.finalOutput = options.outputMode === "file-only" && result.savedOutputPath && result.outputReference
|
|
772
752
|
? result.outputReference.message
|
|
@@ -789,99 +769,6 @@ async function runSingleAttempt(
|
|
|
789
769
|
return result;
|
|
790
770
|
}
|
|
791
771
|
|
|
792
|
-
async function runAcceptanceFinalizationLoop(input: {
|
|
793
|
-
runtimeCwd: string;
|
|
794
|
-
agent: AgentConfig;
|
|
795
|
-
result: SingleResult;
|
|
796
|
-
initialLedger: AcceptanceLedger;
|
|
797
|
-
initialOutput: string;
|
|
798
|
-
acceptance: ResolvedAcceptanceConfig;
|
|
799
|
-
options: RunSyncOptions;
|
|
800
|
-
systemPrompt: string;
|
|
801
|
-
resolvedSkillNames?: string[];
|
|
802
|
-
skillsWarning?: string;
|
|
803
|
-
}): Promise<AcceptanceLedger> {
|
|
804
|
-
const sessionFile = input.result.sessionFile ?? input.options.sessionFile;
|
|
805
|
-
const maxTurns = input.acceptance.finalization.maxTurns;
|
|
806
|
-
const turns: AcceptanceFinalizationTurn[] = [];
|
|
807
|
-
if (!sessionFile) {
|
|
808
|
-
const message = "Acceptance finalization requires a session file for same-session continuation.";
|
|
809
|
-
turns.push(createFinalizationProcessFailureTurn({ turn: 1, prompt: "", message }));
|
|
810
|
-
return buildFinalizationProcessFailureLedger({ initialLedger: input.initialLedger, turns, maxTurns, message });
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
const selfReviewAcceptance = acceptanceSelfReviewConfig(input.acceptance);
|
|
814
|
-
let previousFailure = acceptanceFailureMessage(input.initialLedger);
|
|
815
|
-
let authoritativeLedger = input.initialLedger;
|
|
816
|
-
for (let turn = 1; turn <= maxTurns; turn++) {
|
|
817
|
-
const prompt = formatAcceptanceFinalizationPrompt({
|
|
818
|
-
acceptance: input.acceptance,
|
|
819
|
-
initialOutput: input.initialOutput,
|
|
820
|
-
initialLedger: input.initialLedger,
|
|
821
|
-
turn,
|
|
822
|
-
maxTurns,
|
|
823
|
-
...(previousFailure ? { previousFailure } : {}),
|
|
824
|
-
});
|
|
825
|
-
const finalizationOptions: RunSyncOptions = { ...input.options, sessionFile, outputMode: "inline" };
|
|
826
|
-
delete finalizationOptions.sessionDir;
|
|
827
|
-
delete finalizationOptions.outputPath;
|
|
828
|
-
delete finalizationOptions.structuredOutput;
|
|
829
|
-
delete finalizationOptions.onUpdate;
|
|
830
|
-
finalizationOptions.allowIntercomDetach = false;
|
|
831
|
-
const finalizationResult = await runSingleAttempt(
|
|
832
|
-
input.runtimeCwd,
|
|
833
|
-
input.agent,
|
|
834
|
-
prompt,
|
|
835
|
-
input.result.model,
|
|
836
|
-
finalizationOptions,
|
|
837
|
-
{
|
|
838
|
-
sessionEnabled: true,
|
|
839
|
-
systemPrompt: input.systemPrompt,
|
|
840
|
-
resolvedSkillNames: input.resolvedSkillNames,
|
|
841
|
-
skillsWarning: input.skillsWarning,
|
|
842
|
-
attemptNotes: [],
|
|
843
|
-
originalTask: prompt,
|
|
844
|
-
completionPolicy: "acceptance-contract",
|
|
845
|
-
},
|
|
846
|
-
);
|
|
847
|
-
sumUsage(input.result.usage, finalizationResult.usage);
|
|
848
|
-
input.result.progressSummary = {
|
|
849
|
-
toolCount: (input.result.progressSummary?.toolCount ?? 0) + (finalizationResult.progressSummary?.toolCount ?? 0),
|
|
850
|
-
tokens: input.result.usage.input + input.result.usage.output,
|
|
851
|
-
durationMs: (input.result.progressSummary?.durationMs ?? 0) + (finalizationResult.progressSummary?.durationMs ?? 0),
|
|
852
|
-
};
|
|
853
|
-
if (finalizationResult.controlEvents?.length) {
|
|
854
|
-
input.result.controlEvents = [...(input.result.controlEvents ?? []), ...finalizationResult.controlEvents];
|
|
855
|
-
}
|
|
856
|
-
const rawOutput = acceptanceOutputByResult.get(finalizationResult) ?? getFinalOutput(finalizationResult.messages) ?? finalizationResult.finalOutput ?? "";
|
|
857
|
-
if (finalizationResult.exitCode !== 0 || finalizationResult.error || finalizationResult.detached || finalizationResult.interrupted) {
|
|
858
|
-
const message = finalizationResult.error ?? "Acceptance finalization turn did not complete successfully.";
|
|
859
|
-
turns.push(createFinalizationProcessFailureTurn({ turn, prompt, rawOutput, message }));
|
|
860
|
-
return buildFinalizationProcessFailureLedger({ initialLedger: input.initialLedger, turns, maxTurns, message });
|
|
861
|
-
}
|
|
862
|
-
const selfReviewLedger = await evaluateAcceptance({
|
|
863
|
-
acceptance: selfReviewAcceptance,
|
|
864
|
-
output: rawOutput,
|
|
865
|
-
cwd: input.options.cwd ?? input.runtimeCwd,
|
|
866
|
-
});
|
|
867
|
-
authoritativeLedger = selfReviewLedger;
|
|
868
|
-
turns.push(createFinalizationTurn({ turn, prompt, rawOutput, ledger: selfReviewLedger }));
|
|
869
|
-
const failure = acceptanceFailureMessage(selfReviewLedger);
|
|
870
|
-
if (!failure) {
|
|
871
|
-
authoritativeLedger = input.acceptance === selfReviewAcceptance
|
|
872
|
-
? selfReviewLedger
|
|
873
|
-
: await evaluateAcceptance({
|
|
874
|
-
acceptance: input.acceptance,
|
|
875
|
-
output: rawOutput,
|
|
876
|
-
cwd: input.options.cwd ?? input.runtimeCwd,
|
|
877
|
-
});
|
|
878
|
-
return attachFinalizationToLedger({ initialLedger: input.initialLedger, authoritativeLedger, turns, status: "completed", maxTurns });
|
|
879
|
-
}
|
|
880
|
-
previousFailure = failure;
|
|
881
|
-
}
|
|
882
|
-
return attachFinalizationToLedger({ initialLedger: input.initialLedger, authoritativeLedger, turns, status: "failed", maxTurns });
|
|
883
|
-
}
|
|
884
|
-
|
|
885
772
|
/**
|
|
886
773
|
* Run a subagent synchronously (blocking until complete)
|
|
887
774
|
*/
|
|
@@ -926,10 +813,6 @@ export async function runSync(
|
|
|
926
813
|
dynamic: options.acceptanceContext?.dynamic,
|
|
927
814
|
dynamicGroup: options.acceptanceContext?.dynamicGroup,
|
|
928
815
|
});
|
|
929
|
-
if (shouldRunAcceptanceFinalization(effectiveAcceptance) && !options.sessionFile) {
|
|
930
|
-
const sessionDir = options.sessionDir ?? mkdtempSync(path.join(os.tmpdir(), "pi-subagent-finalization-"));
|
|
931
|
-
options.sessionFile = path.join(sessionDir, "session.jsonl");
|
|
932
|
-
}
|
|
933
816
|
const acceptancePrompt = formatAcceptancePrompt(effectiveAcceptance);
|
|
934
817
|
const taskWithAcceptance = acceptancePrompt ? `${task}\n${acceptancePrompt}` : task;
|
|
935
818
|
const sessionEnabled = Boolean(options.sessionFile || options.sessionDir) || shareEnabled;
|
|
@@ -994,14 +877,6 @@ export async function runSync(
|
|
|
994
877
|
attemptNotes,
|
|
995
878
|
outputSnapshot,
|
|
996
879
|
originalTask: task,
|
|
997
|
-
completionPolicy: resolveCompletionPolicy({
|
|
998
|
-
agent: agent.name,
|
|
999
|
-
task,
|
|
1000
|
-
completionGuardEnabled: agent.completionGuard !== false,
|
|
1001
|
-
usesAcceptanceContract: effectiveAcceptance.explicit,
|
|
1002
|
-
tools: agent.tools,
|
|
1003
|
-
mcpDirectTools: agent.mcpDirectTools,
|
|
1004
|
-
}),
|
|
1005
880
|
});
|
|
1006
881
|
lastResult = result;
|
|
1007
882
|
sumUsage(aggregateUsage, result.usage);
|
|
@@ -1091,33 +966,14 @@ export async function runSync(
|
|
|
1091
966
|
if (sessionFile) result.sessionFile = sessionFile;
|
|
1092
967
|
}
|
|
1093
968
|
|
|
1094
|
-
|
|
1095
|
-
const acceptanceForInitialReport = shouldRunAcceptanceFinalization(effectiveAcceptance)
|
|
1096
|
-
? acceptanceSelfReviewConfig(effectiveAcceptance)
|
|
1097
|
-
: effectiveAcceptance;
|
|
1098
|
-
const initialAcceptance = await evaluateAcceptance({
|
|
1099
|
-
acceptance: acceptanceForInitialReport,
|
|
1100
|
-
output: initialAcceptanceOutput,
|
|
1101
|
-
cwd: options.cwd ?? runtimeCwd,
|
|
1102
|
-
});
|
|
1103
|
-
result.acceptance = initialAcceptance;
|
|
1104
|
-
if (shouldRunAcceptanceFinalization(effectiveAcceptance) && result.exitCode === 0 && !result.detached && !result.interrupted) {
|
|
1105
|
-
result.acceptance = await runAcceptanceFinalizationLoop({
|
|
1106
|
-
runtimeCwd,
|
|
1107
|
-
agent,
|
|
1108
|
-
result,
|
|
1109
|
-
initialLedger: initialAcceptance,
|
|
1110
|
-
initialOutput: initialAcceptanceOutput,
|
|
969
|
+
result.acceptance = await evaluateAcceptance({
|
|
1111
970
|
acceptance: effectiveAcceptance,
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
resolvedSkillNames: resolvedSkills.length > 0 ? resolvedSkills.map((skill) => skill.name) : undefined,
|
|
1115
|
-
...(missingSkills.length > 0 ? { skillsWarning: `Skills not found: ${missingSkills.join(", ")}` } : {}),
|
|
971
|
+
output: acceptanceOutputByResult.get(result) ?? result.finalOutput ?? "",
|
|
972
|
+
cwd: options.cwd ?? runtimeCwd,
|
|
1116
973
|
});
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
if (acceptanceFailure && result.acceptance.explicit && result.exitCode === 0 && !result.detached && !result.interrupted) {
|
|
974
|
+
const acceptanceFailure = acceptanceFailureMessage(result.acceptance);
|
|
975
|
+
stripAcceptanceReportsFromMessages(result.messages);
|
|
976
|
+
if (acceptanceFailure && result.acceptance.explicit && result.exitCode === 0 && !result.detached && !result.interrupted) {
|
|
1121
977
|
result.exitCode = 1;
|
|
1122
978
|
result.error = result.error ? `${result.error}\n${acceptanceFailure}` : acceptanceFailure;
|
|
1123
979
|
if (result.progress) {
|
|
@@ -13,7 +13,7 @@ import { handleManagementAction } from "../../agents/agent-management.ts";
|
|
|
13
13
|
import { buildDoctorReport } from "../../extension/doctor.ts";
|
|
14
14
|
import { clearPendingForegroundControlNotices } from "../../extension/control-notices.ts";
|
|
15
15
|
import { runSync } from "./execution.ts";
|
|
16
|
-
import { resolveModelCandidate } from "../shared/model-fallback.ts";
|
|
16
|
+
import { resolveModelCandidate, resolveSubagentModelOverride } from "../shared/model-fallback.ts";
|
|
17
17
|
import { aggregateParallelOutputs } from "../shared/parallel-utils.ts";
|
|
18
18
|
import { recordRun } from "../shared/run-history.ts";
|
|
19
19
|
import {
|
|
@@ -53,7 +53,6 @@ import { resolveSubagentRunId, type ResolvedSubagentRunId } from "../background/
|
|
|
53
53
|
import { formatNestedRunStatusLines } from "../shared/nested-render.ts";
|
|
54
54
|
import { inspectSubagentStatus } from "../background/run-status.ts";
|
|
55
55
|
import { applyForceTopLevelAsyncOverride } from "../background/top-level-async.ts";
|
|
56
|
-
import { validateAcceptanceInput } from "../shared/acceptance.ts";
|
|
57
56
|
import {
|
|
58
57
|
cleanupWorktrees,
|
|
59
58
|
createWorktrees,
|
|
@@ -650,6 +649,7 @@ async function resumeAsyncRun(input: {
|
|
|
650
649
|
cwd: input.requestCwd,
|
|
651
650
|
currentSessionId: input.deps.state.currentSessionId,
|
|
652
651
|
currentModelProvider: input.ctx.model?.provider,
|
|
652
|
+
currentModel: input.ctx.model,
|
|
653
653
|
},
|
|
654
654
|
cwd: effectiveCwd,
|
|
655
655
|
maxOutput: input.params.maxOutput,
|
|
@@ -691,6 +691,19 @@ function resultSummaryForIntercom(result: SingleResult): string {
|
|
|
691
691
|
return output || result.error || "(no output)";
|
|
692
692
|
}
|
|
693
693
|
|
|
694
|
+
function formatFailedSingleRunOutput(result: SingleResult, displayOutput: string): string {
|
|
695
|
+
const error = result.error || "Failed";
|
|
696
|
+
const output = displayOutput.trim();
|
|
697
|
+
const lines = [error];
|
|
698
|
+
if (output && output !== error.trim()) {
|
|
699
|
+
lines.push("", "Output:", output);
|
|
700
|
+
}
|
|
701
|
+
if (result.artifactPaths?.outputPath) {
|
|
702
|
+
lines.push("", `Output artifact: ${result.artifactPaths.outputPath}`);
|
|
703
|
+
}
|
|
704
|
+
return lines.join("\n");
|
|
705
|
+
}
|
|
706
|
+
|
|
694
707
|
function createForegroundControlNotifier(data: Pick<ExecutionContextData, "controlConfig" | "intercomBridge">, deps: Pick<ExecutorDeps, "pi">): (event: ControlEvent) => void {
|
|
695
708
|
return (event) => emitControlNotification({
|
|
696
709
|
pi: deps.pi,
|
|
@@ -761,36 +774,6 @@ async function maybeBuildForegroundIntercomReceipt(input: {
|
|
|
761
774
|
};
|
|
762
775
|
}
|
|
763
776
|
|
|
764
|
-
function validationErrorResult(mode: Details["mode"], text: string): AgentToolResult<Details> {
|
|
765
|
-
return { content: [{ type: "text", text }], isError: true, details: { mode, results: [] } };
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
function validateAcceptanceForExecution(params: SubagentParamsLike): AgentToolResult<Details> | null {
|
|
769
|
-
const topLevelErrors = validateAcceptanceInput(params.acceptance);
|
|
770
|
-
if (topLevelErrors.length > 0) return validationErrorResult("single", topLevelErrors.join(" "));
|
|
771
|
-
for (const [index, task] of (params.tasks ?? []).entries()) {
|
|
772
|
-
const errors = validateAcceptanceInput(task.acceptance, `tasks[${index}].acceptance`);
|
|
773
|
-
if (errors.length > 0) return validationErrorResult("parallel", errors.join(" "));
|
|
774
|
-
}
|
|
775
|
-
for (const [stepIndex, step] of (params.chain ?? []).entries()) {
|
|
776
|
-
if (isParallelStep(step)) {
|
|
777
|
-
if (Object.hasOwn(step, "acceptance")) return validationErrorResult("chain", `chain[${stepIndex}].acceptance is not supported on static parallel groups; set acceptance on each parallel task.`);
|
|
778
|
-
for (const [taskIndex, task] of step.parallel.entries()) {
|
|
779
|
-
const errors = validateAcceptanceInput(task.acceptance, `chain[${stepIndex}].parallel[${taskIndex}].acceptance`);
|
|
780
|
-
if (errors.length > 0) return validationErrorResult("chain", errors.join(" "));
|
|
781
|
-
}
|
|
782
|
-
} else if (isDynamicParallelStep(step)) {
|
|
783
|
-
if (Object.hasOwn(step, "acceptance")) return validationErrorResult("chain", `chain[${stepIndex}].acceptance is not supported on dynamic fanout groups; set acceptance on chain[${stepIndex}].parallel.acceptance for each materialized child.`);
|
|
784
|
-
const errors = validateAcceptanceInput(step.parallel.acceptance, `chain[${stepIndex}].parallel.acceptance`);
|
|
785
|
-
if (errors.length > 0) return validationErrorResult("chain", errors.join(" "));
|
|
786
|
-
} else {
|
|
787
|
-
const stepErrors = validateAcceptanceInput(step.acceptance, `chain[${stepIndex}].acceptance`);
|
|
788
|
-
if (stepErrors.length > 0) return validationErrorResult("chain", stepErrors.join(" "));
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
return null;
|
|
792
|
-
}
|
|
793
|
-
|
|
794
777
|
function validateExecutionInput(
|
|
795
778
|
params: SubagentParamsLike,
|
|
796
779
|
agents: AgentConfig[],
|
|
@@ -799,9 +782,6 @@ function validateExecutionInput(
|
|
|
799
782
|
hasSingle: boolean,
|
|
800
783
|
allowClarifyTaskPrompt: boolean,
|
|
801
784
|
): AgentToolResult<Details> | null {
|
|
802
|
-
const acceptanceError = validateAcceptanceForExecution(params);
|
|
803
|
-
if (acceptanceError) return acceptanceError;
|
|
804
|
-
|
|
805
785
|
if (Number(hasChain) + Number(hasTasks) + Number(hasSingle) !== 1) {
|
|
806
786
|
return {
|
|
807
787
|
content: [
|
|
@@ -1116,6 +1096,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1116
1096
|
cwd: ctx.cwd,
|
|
1117
1097
|
currentSessionId: deps.state.currentSessionId!,
|
|
1118
1098
|
currentModelProvider: ctx.model?.provider,
|
|
1099
|
+
currentModel: ctx.model,
|
|
1119
1100
|
};
|
|
1120
1101
|
const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
1121
1102
|
const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
|
|
@@ -1126,7 +1107,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1126
1107
|
if (hasTasks && params.tasks) {
|
|
1127
1108
|
const agentConfigs = params.tasks.map((task) => agents.find((agent) => agent.name === task.agent));
|
|
1128
1109
|
const modelOverrides = params.tasks.map((task, index) =>
|
|
1129
|
-
|
|
1110
|
+
resolveSubagentModelOverride(task.model ?? agentConfigs[index]?.model, ctx.model, availableModels, currentProvider),
|
|
1130
1111
|
);
|
|
1131
1112
|
const skillOverrides = params.tasks.map((task) => normalizeSkillInput(task.skill));
|
|
1132
1113
|
const parallelTasks = params.tasks.map((task, index) => ({
|
|
@@ -1213,7 +1194,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1213
1194
|
const normalizedSkills = normalizeSkillInput(params.skill);
|
|
1214
1195
|
const skills = normalizedSkills === false ? [] : normalizedSkills;
|
|
1215
1196
|
const maxSubagentDepth = resolveChildMaxSubagentDepth(currentMaxSubagentDepth, a.maxSubagentDepth);
|
|
1216
|
-
const modelOverride =
|
|
1197
|
+
const modelOverride = resolveSubagentModelOverride((params.model as string | undefined) ?? a.model, ctx.model, availableModels, currentProvider);
|
|
1217
1198
|
return executeAsyncSingle(id, {
|
|
1218
1199
|
agent: params.agent!,
|
|
1219
1200
|
task: params.context === "fork" ? wrapForkTask(params.task ?? "") : (params.task ?? ""),
|
|
@@ -1314,6 +1295,7 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
1314
1295
|
cwd: ctx.cwd,
|
|
1315
1296
|
currentSessionId: deps.state.currentSessionId!,
|
|
1316
1297
|
currentModelProvider: ctx.model?.provider,
|
|
1298
|
+
currentModel: ctx.model,
|
|
1317
1299
|
};
|
|
1318
1300
|
const asyncChain = wrapChainTasksForFork(chainResult.requestedAsync.chain, params.context);
|
|
1319
1301
|
return executeAsyncChain(id, {
|
|
@@ -1669,7 +1651,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
1669
1651
|
...(task.model ? { model: task.model } : {}),
|
|
1670
1652
|
}));
|
|
1671
1653
|
const modelOverrides: (string | undefined)[] = tasks.map((_, i) =>
|
|
1672
|
-
|
|
1654
|
+
resolveSubagentModelOverride(behaviorOverrides[i]?.model ?? agentConfigs[i]?.model, ctx.model, availableModels, currentProvider),
|
|
1673
1655
|
);
|
|
1674
1656
|
|
|
1675
1657
|
if (params.clarify === true && ctx.hasUI) {
|
|
@@ -1730,6 +1712,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
1730
1712
|
cwd: ctx.cwd,
|
|
1731
1713
|
currentSessionId: deps.state.currentSessionId!,
|
|
1732
1714
|
currentModelProvider: ctx.model?.provider,
|
|
1715
|
+
currentModel: ctx.model,
|
|
1733
1716
|
};
|
|
1734
1717
|
const parallelTasks = tasks.map((t, i) => {
|
|
1735
1718
|
const taskText = params.context === "fork" ? wrapForkTask(taskTexts[i]!) : taskTexts[i]!;
|
|
@@ -1952,8 +1935,9 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
1952
1935
|
const currentProvider = ctx.model?.provider;
|
|
1953
1936
|
const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
1954
1937
|
let task = params.task ?? "";
|
|
1955
|
-
let modelOverride: string | undefined =
|
|
1938
|
+
let modelOverride: string | undefined = resolveSubagentModelOverride(
|
|
1956
1939
|
(params.model as string | undefined) ?? agentConfig.model,
|
|
1940
|
+
ctx.model,
|
|
1957
1941
|
availableModels,
|
|
1958
1942
|
currentProvider,
|
|
1959
1943
|
);
|
|
@@ -2010,6 +1994,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2010
1994
|
cwd: ctx.cwd,
|
|
2011
1995
|
currentSessionId: deps.state.currentSessionId!,
|
|
2012
1996
|
currentModelProvider: ctx.model?.provider,
|
|
1997
|
+
currentModel: ctx.model,
|
|
2013
1998
|
};
|
|
2014
1999
|
return executeAsyncSingle(id, {
|
|
2015
2000
|
agent: params.agent!,
|
|
@@ -2194,7 +2179,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2194
2179
|
|
|
2195
2180
|
if (r.exitCode !== 0)
|
|
2196
2181
|
return {
|
|
2197
|
-
content: [{ type: "text", text: r.
|
|
2182
|
+
content: [{ type: "text", text: formatFailedSingleRunOutput(r, finalizedOutput.displayOutput) }],
|
|
2198
2183
|
details,
|
|
2199
2184
|
isError: true,
|
|
2200
2185
|
};
|