pi-subagents 0.35.0 → 0.36.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/README.md +132 -24
- package/agents/advisor.md +73 -0
- package/package.json +8 -12
- package/skills/pi-subagents/SKILL.md +22 -9
- package/src/agents/agents.ts +22 -5
- package/src/api/delegation.ts +125 -0
- package/src/extension/config.ts +7 -1
- package/src/extension/index.ts +50 -38
- package/src/extension/rpc.ts +27 -2
- package/src/extension/schemas.ts +22 -2
- package/src/extension/tool-description.ts +2 -2
- package/src/intercom/intercom-bridge.ts +1 -1
- package/src/intercom/native-supervisor-channel.ts +45 -6
- package/src/intercom/result-intercom.ts +7 -0
- package/src/runs/background/async-execution.ts +34 -4
- package/src/runs/background/async-job-tracker.ts +4 -0
- package/src/runs/background/async-resume.ts +27 -5
- package/src/runs/background/async-status.ts +76 -3
- package/src/runs/background/chain-append.ts +2 -0
- package/src/runs/background/completion-batcher.ts +6 -4
- package/src/runs/background/completion-dedupe.ts +2 -11
- package/src/runs/background/fleet-view.ts +9 -4
- package/src/runs/background/notify.ts +132 -120
- package/src/runs/background/result-watcher.ts +138 -78
- package/src/runs/background/run-status.ts +3 -1
- package/src/runs/background/subagent-runner.ts +225 -43
- package/src/runs/background/subagent-wait.ts +130 -4
- package/src/runs/background/wait-tool.ts +2 -2
- package/src/runs/foreground/chain-execution.ts +176 -111
- package/src/runs/foreground/execution.ts +90 -36
- package/src/runs/foreground/foreground-control.ts +90 -0
- package/src/runs/foreground/subagent-executor.ts +394 -163
- package/src/runs/shared/acceptance.ts +55 -13
- package/src/runs/shared/agent-contract.ts +38 -0
- package/src/runs/shared/child-protocol.ts +1 -1
- package/src/runs/shared/completion-guard.ts +36 -5
- package/src/runs/shared/context-mode.ts +44 -0
- package/src/runs/shared/dynamic-fanout.ts +4 -4
- package/src/runs/shared/long-running-guard.ts +4 -0
- package/src/runs/shared/nested-events.ts +27 -2
- package/src/runs/shared/parallel-handoff.ts +154 -0
- package/src/runs/shared/parallel-utils.ts +6 -0
- package/src/runs/shared/pi-args.ts +23 -14
- package/src/runs/shared/run-history.ts +90 -5
- package/src/runs/shared/structured-output.ts +112 -7
- package/src/runs/shared/subagent-control.ts +4 -0
- package/src/runs/shared/subagent-prompt-runtime.ts +17 -18
- package/src/runs/shared/task-intent.ts +10 -5
- package/src/runs/shared/tool-availability.ts +3 -1
- package/src/runs/shared/tool-budget.ts +11 -5
- package/src/runs/shared/turn-budget.ts +2 -1
- package/src/runs/shared/worktree.ts +63 -14
- package/src/shared/accessible-dir.ts +25 -0
- package/src/shared/artifacts.ts +37 -7
- package/src/shared/atomic-json.ts +14 -42
- package/src/shared/child-transcript.ts +52 -0
- package/src/shared/file-system-retry.ts +47 -0
- package/src/shared/settings.ts +9 -1
- package/src/shared/types.ts +211 -22
- package/src/slash/delegation-adapters.ts +152 -5
- package/src/slash/delegation-json.ts +108 -0
- package/src/slash/delegation-request.ts +182 -36
- package/src/slash/prompt-template-bridge.ts +222 -37
- package/src/slash/selector.ts +147 -0
- package/src/slash/slash-commands.ts +14 -5
- package/src/slash/slash-live-state.ts +2 -2
- package/src/slash/subagents-admin.ts +42 -42
- package/src/tui/fleet-status.ts +362 -0
- package/src/tui/fleet-transcript.ts +472 -0
- package/src/tui/fleet.ts +318 -59
- package/src/tui/render.ts +25 -15
- package/src/watchdog/change-signature.ts +105 -12
- package/src/watchdog/review.ts +7 -2
- package/src/watchdog/runtime.ts +5 -3
- package/src/slash/subagents-editor.ts +0 -86
|
@@ -9,6 +9,7 @@ import type { Message } from "@earendil-works/pi-ai";
|
|
|
9
9
|
import type { AgentConfig } from "../../agents/agents.ts";
|
|
10
10
|
import {
|
|
11
11
|
ensureArtifactsDir,
|
|
12
|
+
formatOutputArtifactContent,
|
|
12
13
|
getArtifactPaths,
|
|
13
14
|
writeArtifact,
|
|
14
15
|
writeMetadata,
|
|
@@ -51,7 +52,8 @@ import { getPiSpawnCommand } from "../shared/pi-spawn.ts";
|
|
|
51
52
|
import { createJsonlWriter } from "../../shared/jsonl-writer.ts";
|
|
52
53
|
import { attachPostExitStdioGuard, trySignalChild } from "../../shared/post-exit-stdio-guard.ts";
|
|
53
54
|
import { applyThinkingSuffix, buildPiArgs, cleanupTempDir } from "../shared/pi-args.ts";
|
|
54
|
-
import {
|
|
55
|
+
import { resolveEffectiveThinking } from "../../shared/model-info.ts";
|
|
56
|
+
import { MISSING_STRUCTURED_OUTPUT_CALL_ERROR, readStructuredOutput } from "../shared/structured-output.ts";
|
|
55
57
|
import { readChildToolDiagnosticError } from "../shared/tool-availability.ts";
|
|
56
58
|
import { captureSingleOutputSnapshot, extractChildWrittenOutput, formatSavedOutputReference, injectOutputPathSystemPrompt, resolveSingleOutput, validateFileOnlyOutputMode, type SingleOutputSnapshot } from "../shared/single-output.ts";
|
|
57
59
|
import {
|
|
@@ -71,6 +73,7 @@ import {
|
|
|
71
73
|
summarizeRecentMutatingFailures,
|
|
72
74
|
} from "../shared/long-running-guard.ts";
|
|
73
75
|
import { acceptanceFailureMessage, buildSkippedAcceptanceLedger, evaluateAcceptance, formatAcceptancePrompt, resolveEffectiveAcceptance, stripAcceptanceReport } from "../shared/acceptance.ts";
|
|
76
|
+
import { attachContractProjections, isAgentContractV1 } from "../shared/agent-contract.ts";
|
|
74
77
|
import { appendTurnBudgetSystemPrompt, formatTurnBudgetOutput, initialTurnBudgetState, turnBudgetDecision, turnBudgetDeferredNote, turnBudgetDeferredState, turnBudgetExceededMessage, turnBudgetSoftNote, turnBudgetState } from "../shared/turn-budget.ts";
|
|
75
78
|
import { initialToolBudgetState, toolBudgetState } from "../shared/tool-budget.ts";
|
|
76
79
|
import { resolveWatchdogConfig } from "../../watchdog/settings.ts";
|
|
@@ -90,6 +93,12 @@ function emptyUsage(): Usage {
|
|
|
90
93
|
return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 };
|
|
91
94
|
}
|
|
92
95
|
|
|
96
|
+
function withRunContext<T extends SingleResult>(result: T, context: RunSyncOptions["context"]): T {
|
|
97
|
+
if (!context) return result;
|
|
98
|
+
result.context = context;
|
|
99
|
+
return result;
|
|
100
|
+
}
|
|
101
|
+
|
|
93
102
|
function sumUsage(target: Usage, source: Usage): void {
|
|
94
103
|
target.input += source.input;
|
|
95
104
|
target.output += source.output;
|
|
@@ -196,6 +205,7 @@ async function runSingleAttempt(
|
|
|
196
205
|
): Promise<SingleResult> {
|
|
197
206
|
const effectiveThinking = options.thinkingOverride ?? agent.thinking;
|
|
198
207
|
const modelArg = applyThinkingSuffix(model, effectiveThinking, options.thinkingOverride !== undefined);
|
|
208
|
+
const resolvedThinking = resolveEffectiveThinking(modelArg, effectiveThinking);
|
|
199
209
|
const watchdogConfig = resolveWatchdogConfig(options.cwd ?? runtimeCwd);
|
|
200
210
|
const childWatchdog = watchdogConfig.ok
|
|
201
211
|
? resolveChildWatchdogConfig({
|
|
@@ -236,24 +246,27 @@ async function runSingleAttempt(
|
|
|
236
246
|
parentSessionId: options.parentSessionId,
|
|
237
247
|
structuredOutput: options.structuredOutput,
|
|
238
248
|
toolBudget: options.toolBudget,
|
|
249
|
+
allowZeroToolBudget: options.allowZeroToolBudget,
|
|
239
250
|
childWatchdog,
|
|
240
251
|
waitToolEnabled: options.waitToolEnabled,
|
|
241
252
|
});
|
|
242
253
|
|
|
243
|
-
const result: SingleResult = {
|
|
254
|
+
const result: SingleResult = withRunContext({
|
|
244
255
|
agent: agent.name,
|
|
245
256
|
task: shared.originalTask ?? task,
|
|
257
|
+
...(options.agentContract ? { agentContract: options.agentContract } : {}),
|
|
246
258
|
exitCode: 0,
|
|
247
259
|
messages: [],
|
|
248
260
|
usage: emptyUsage(),
|
|
249
261
|
model: modelArg,
|
|
262
|
+
...(resolvedThinking ? { thinking: resolvedThinking } : {}),
|
|
250
263
|
artifactPaths: shared.artifactPaths,
|
|
251
264
|
transcriptPath: shared.transcriptWriter ? shared.artifactPaths?.transcriptPath : undefined,
|
|
252
265
|
skills: shared.resolvedSkillNames,
|
|
253
266
|
skillsWarning: shared.skillsWarning,
|
|
254
267
|
...(options.turnBudget ? { turnBudget: initialTurnBudgetState(options.turnBudget) } : {}),
|
|
255
268
|
...(options.toolBudget ? { toolBudget: initialToolBudgetState(options.toolBudget) } : {}),
|
|
256
|
-
};
|
|
269
|
+
}, options.context);
|
|
257
270
|
const startTime = Date.now();
|
|
258
271
|
if (options.structuredOutput) {
|
|
259
272
|
try {
|
|
@@ -307,6 +320,7 @@ async function runSingleAttempt(
|
|
|
307
320
|
}
|
|
308
321
|
const spawnEnv = { ...process.env, ...sharedEnv, ...getSubagentDepthEnv(options.maxSubagentDepth) };
|
|
309
322
|
let observedMutationAttempt = false;
|
|
323
|
+
let structuredOutputToolInvoked = false;
|
|
310
324
|
|
|
311
325
|
const exitCode = await new Promise<number>((resolve) => {
|
|
312
326
|
const spawnSpec = getPiSpawnCommand(args);
|
|
@@ -592,7 +606,7 @@ async function runSingleAttempt(
|
|
|
592
606
|
result.wrapUpRequested = true;
|
|
593
607
|
appendRecentOutput(progress, [turnBudgetSoftNote(budget, turnCount)]);
|
|
594
608
|
}
|
|
595
|
-
const decision = turnBudgetDecision(budget, turnCount, terminalAssistantStop, toolWorkActiveOrStarting);
|
|
609
|
+
const decision = turnBudgetDecision(budget, turnCount, terminalAssistantStop, toolWorkActiveOrStarting, options.enforceHardTurnLimit);
|
|
596
610
|
if (decision === "defer") {
|
|
597
611
|
result.turnBudget = turnBudgetDeferredState(
|
|
598
612
|
budget,
|
|
@@ -646,7 +660,7 @@ async function runSingleAttempt(
|
|
|
646
660
|
const fireUpdate = () => {
|
|
647
661
|
if (!options.onUpdate || processClosed) return;
|
|
648
662
|
progress.durationMs = Date.now() - startTime;
|
|
649
|
-
const output = (result.timedOut || result.turnBudgetExceeded) && result.finalOutput ? result.finalOutput : getFinalOutput(result.messages);
|
|
663
|
+
const output = (result.timedOut || result.turnBudgetExceeded) && result.finalOutput ? result.finalOutput : getFinalOutput(result.messages ?? []);
|
|
650
664
|
emitUpdateSnapshot(output || "(running...)");
|
|
651
665
|
};
|
|
652
666
|
|
|
@@ -696,6 +710,7 @@ async function runSingleAttempt(
|
|
|
696
710
|
const toolArgs = evt.args && typeof evt.args === "object" && !Array.isArray(evt.args)
|
|
697
711
|
? evt.args as Record<string, unknown>
|
|
698
712
|
: {};
|
|
713
|
+
if (options.structuredOutput && evt.toolName === "structured_output") structuredOutputToolInvoked = true;
|
|
699
714
|
if (options.allowIntercomDetach && (evt.toolName === "intercom" || evt.toolName === "contact_supervisor")) {
|
|
700
715
|
intercomStarted = true;
|
|
701
716
|
}
|
|
@@ -729,15 +744,20 @@ async function runSingleAttempt(
|
|
|
729
744
|
}
|
|
730
745
|
|
|
731
746
|
if (evt.type === "message_end" && evt.message) {
|
|
732
|
-
result.messages
|
|
747
|
+
result.messages!.push(evt.message);
|
|
733
748
|
if (evt.message.role === "assistant") {
|
|
734
749
|
result.usage.turns++;
|
|
735
750
|
progress.turnCount = result.usage.turns;
|
|
736
751
|
const stopReason = (evt.message as { stopReason?: string }).stopReason;
|
|
737
|
-
const
|
|
738
|
-
|
|
752
|
+
const toolCalls = Array.isArray(evt.message.content)
|
|
753
|
+
? evt.message.content.filter((part) => (part as { type?: string }).type === "toolCall")
|
|
754
|
+
: [];
|
|
755
|
+
const hasToolCall = toolCalls.length > 0;
|
|
739
756
|
const terminalAssistantStop = stopReason === "stop" && !hasToolCall;
|
|
740
|
-
|
|
757
|
+
const terminalStructuredOutputCall = Boolean(options.structuredOutput)
|
|
758
|
+
&& toolCalls.length === 1
|
|
759
|
+
&& (toolCalls[0] as { name?: string }).name === "structured_output";
|
|
760
|
+
updateTurnBudget(result.usage.turns, terminalAssistantStop || terminalStructuredOutputCall, hasToolCall || Boolean(progress.currentTool));
|
|
741
761
|
const u = evt.message.usage;
|
|
742
762
|
if (u) {
|
|
743
763
|
result.usage.input += u.input || 0;
|
|
@@ -763,7 +783,7 @@ async function runSingleAttempt(
|
|
|
763
783
|
}
|
|
764
784
|
|
|
765
785
|
if (evt.type === "tool_result_end" && evt.message) {
|
|
766
|
-
result.messages
|
|
786
|
+
result.messages!.push(evt.message);
|
|
767
787
|
const resultText = extractTextFromContent(evt.message.content);
|
|
768
788
|
if (options.toolBudget && pendingToolResult && resultText.includes("Tool budget hard limit reached")) {
|
|
769
789
|
result.toolBudgetBlocked = true;
|
|
@@ -1021,7 +1041,7 @@ async function runSingleAttempt(
|
|
|
1021
1041
|
result.exitCode = 1;
|
|
1022
1042
|
}
|
|
1023
1043
|
if (result.exitCode === 0 && !result.error) {
|
|
1024
|
-
const errInfo = detectSubagentError(result.messages);
|
|
1044
|
+
const errInfo = detectSubagentError(result.messages ?? []);
|
|
1025
1045
|
if (errInfo.hasError) {
|
|
1026
1046
|
result.exitCode = errInfo.exitCode ?? 1;
|
|
1027
1047
|
result.error = errInfo.details
|
|
@@ -1030,7 +1050,7 @@ async function runSingleAttempt(
|
|
|
1030
1050
|
}
|
|
1031
1051
|
}
|
|
1032
1052
|
if (result.exitCode === 0 && !result.error) {
|
|
1033
|
-
const finalText = getFinalOutput(result.messages);
|
|
1053
|
+
const finalText = getFinalOutput(result.messages ?? []);
|
|
1034
1054
|
const missingStructuredOutput = options.structuredOutput
|
|
1035
1055
|
? !existsSync(options.structuredOutput.outputPath)
|
|
1036
1056
|
: false;
|
|
@@ -1040,18 +1060,25 @@ async function runSingleAttempt(
|
|
|
1040
1060
|
}
|
|
1041
1061
|
}
|
|
1042
1062
|
if (options.structuredOutput && result.exitCode === 0 && !result.error) {
|
|
1043
|
-
const structured = readStructuredOutput({
|
|
1044
|
-
schema: options.structuredOutput.schema,
|
|
1045
|
-
schemaPath: options.structuredOutput.schemaPath,
|
|
1046
|
-
outputPath: options.structuredOutput.outputPath,
|
|
1047
|
-
});
|
|
1048
1063
|
result.structuredOutputSchemaPath = options.structuredOutput.schemaPath;
|
|
1049
1064
|
result.structuredOutputPath = options.structuredOutput.outputPath;
|
|
1050
|
-
if (
|
|
1065
|
+
if (!structuredOutputToolInvoked) {
|
|
1051
1066
|
result.exitCode = 1;
|
|
1052
|
-
result.error =
|
|
1067
|
+
result.error = MISSING_STRUCTURED_OUTPUT_CALL_ERROR;
|
|
1068
|
+
result.structuredOutputFailed = true;
|
|
1053
1069
|
} else {
|
|
1054
|
-
|
|
1070
|
+
const structured = await readStructuredOutput({
|
|
1071
|
+
schema: options.structuredOutput.schema,
|
|
1072
|
+
schemaPath: options.structuredOutput.schemaPath,
|
|
1073
|
+
outputPath: options.structuredOutput.outputPath,
|
|
1074
|
+
});
|
|
1075
|
+
if (structured.error) {
|
|
1076
|
+
result.exitCode = 1;
|
|
1077
|
+
result.error = structured.error;
|
|
1078
|
+
result.structuredOutputFailed = true;
|
|
1079
|
+
} else {
|
|
1080
|
+
result.structuredOutput = structured.value;
|
|
1081
|
+
}
|
|
1055
1082
|
}
|
|
1056
1083
|
}
|
|
1057
1084
|
|
|
@@ -1070,7 +1097,7 @@ async function runSingleAttempt(
|
|
|
1070
1097
|
durationMs: progress.durationMs,
|
|
1071
1098
|
};
|
|
1072
1099
|
|
|
1073
|
-
const acceptanceOutput = getFinalOutput(result.messages);
|
|
1100
|
+
const acceptanceOutput = getFinalOutput(result.messages ?? []);
|
|
1074
1101
|
let fullOutput = stripAcceptanceReport(acceptanceOutput);
|
|
1075
1102
|
if (result.timedOut) {
|
|
1076
1103
|
const timeoutMessage = formatTimeoutMessage(options.timeoutMs ?? 0);
|
|
@@ -1086,16 +1113,29 @@ async function runSingleAttempt(
|
|
|
1086
1113
|
const note = turnBudgetSoftNote(result.turnBudget, result.turnBudget.wrapUpRequestedAtTurn ?? result.turnBudget.turnCount);
|
|
1087
1114
|
fullOutput = fullOutput.trim() ? `${note}\n\n${fullOutput}` : note;
|
|
1088
1115
|
}
|
|
1089
|
-
const
|
|
1116
|
+
const completionGuardEnabled = isAgentContractV1(options.agentContract) ? agent.completionGuard === true : agent.completionGuard !== false;
|
|
1117
|
+
const completionGuard = result.exitCode === 0 && !result.error && completionGuardEnabled
|
|
1090
1118
|
? evaluateCompletionMutationGuard({
|
|
1091
1119
|
agent: agent.name,
|
|
1092
1120
|
task: shared.originalTask ?? task,
|
|
1093
|
-
messages: result.messages,
|
|
1121
|
+
messages: result.messages ?? [],
|
|
1094
1122
|
tools: agent.tools,
|
|
1095
1123
|
mcpDirectTools: agent.mcpDirectTools,
|
|
1096
1124
|
})
|
|
1097
1125
|
: undefined;
|
|
1098
|
-
|
|
1126
|
+
const completionGuardTriggered = completionGuard?.triggered === true && !observedMutationAttempt;
|
|
1127
|
+
if (completionGuard) {
|
|
1128
|
+
result.effects = {
|
|
1129
|
+
...(result.effects ?? {}),
|
|
1130
|
+
fileMutation: {
|
|
1131
|
+
status: completionGuard.expectedMutation ? completionGuardTriggered ? "missing" : "observed" : "not-applicable",
|
|
1132
|
+
expected: completionGuard.expectedMutation,
|
|
1133
|
+
attempted: completionGuard.attemptedMutation || observedMutationAttempt,
|
|
1134
|
+
...(completionGuardTriggered ? { message: "Subagent completed without making edits for an implementation task." } : {}),
|
|
1135
|
+
},
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
1138
|
+
if (completionGuardTriggered && !isAgentContractV1(options.agentContract)) {
|
|
1099
1139
|
result.exitCode = 1;
|
|
1100
1140
|
result.error = "Subagent completed without making edits for an implementation task.\nIt appears to have returned planning or scratchpad output instead of applying changes.";
|
|
1101
1141
|
progress.status = "failed";
|
|
@@ -1156,18 +1196,18 @@ export async function runSync(
|
|
|
1156
1196
|
): Promise<SingleResult> {
|
|
1157
1197
|
const agent = agents.find((a) => a.name === agentName);
|
|
1158
1198
|
if (!agent) {
|
|
1159
|
-
return {
|
|
1199
|
+
return withRunContext({
|
|
1160
1200
|
agent: agentName,
|
|
1161
1201
|
task,
|
|
1162
1202
|
exitCode: 1,
|
|
1163
1203
|
messages: [],
|
|
1164
1204
|
usage: emptyUsage(),
|
|
1165
1205
|
error: `Unknown agent: ${agentName}`,
|
|
1166
|
-
};
|
|
1206
|
+
}, options.context);
|
|
1167
1207
|
}
|
|
1168
1208
|
const outputModeValidationError = validateFileOnlyOutputMode(options.outputMode, options.outputPath, `Single run (${agentName})`);
|
|
1169
1209
|
if (outputModeValidationError) {
|
|
1170
|
-
return {
|
|
1210
|
+
return withRunContext({
|
|
1171
1211
|
agent: agentName,
|
|
1172
1212
|
task,
|
|
1173
1213
|
exitCode: 1,
|
|
@@ -1175,7 +1215,7 @@ export async function runSync(
|
|
|
1175
1215
|
usage: emptyUsage(),
|
|
1176
1216
|
outputMode: options.outputMode,
|
|
1177
1217
|
error: outputModeValidationError,
|
|
1178
|
-
};
|
|
1218
|
+
}, options.context);
|
|
1179
1219
|
}
|
|
1180
1220
|
|
|
1181
1221
|
const shareEnabled = options.share === true;
|
|
@@ -1188,8 +1228,9 @@ export async function runSync(
|
|
|
1188
1228
|
async: options.acceptanceContext?.async,
|
|
1189
1229
|
dynamic: options.acceptanceContext?.dynamic,
|
|
1190
1230
|
dynamicGroup: options.acceptanceContext?.dynamicGroup,
|
|
1231
|
+
agentContract: options.agentContract,
|
|
1191
1232
|
});
|
|
1192
|
-
const acceptancePrompt = formatAcceptancePrompt(effectiveAcceptance);
|
|
1233
|
+
const acceptancePrompt = formatAcceptancePrompt(effectiveAcceptance, { reportOptional: isAgentContractV1(options.agentContract) });
|
|
1193
1234
|
const taskWithAcceptance = acceptancePrompt ? `${task}\n${acceptancePrompt}` : task;
|
|
1194
1235
|
const sessionEnabled = Boolean(options.sessionFile || options.sessionDir) || shareEnabled;
|
|
1195
1236
|
const skillNames = options.skills ?? agent.skills ?? [];
|
|
@@ -1202,14 +1243,14 @@ export async function runSync(
|
|
|
1202
1243
|
agent.filePath ? path.dirname(agent.filePath) : skillCwd,
|
|
1203
1244
|
);
|
|
1204
1245
|
if (skillNames.some((skill) => skill.trim() === "pi-subagents") && missingSkills.includes("pi-subagents")) {
|
|
1205
|
-
return {
|
|
1246
|
+
return withRunContext({
|
|
1206
1247
|
agent: agentName,
|
|
1207
1248
|
task,
|
|
1208
1249
|
exitCode: 1,
|
|
1209
1250
|
messages: [],
|
|
1210
1251
|
usage: emptyUsage(),
|
|
1211
1252
|
error: "Skills not found: pi-subagents",
|
|
1212
|
-
};
|
|
1253
|
+
}, options.context);
|
|
1213
1254
|
}
|
|
1214
1255
|
let systemPrompt = agent.systemPrompt?.trim() || "";
|
|
1215
1256
|
if (resolvedSkills.length > 0) {
|
|
@@ -1275,7 +1316,11 @@ export async function runSync(
|
|
|
1275
1316
|
durationMs: target.progressSummary?.durationMs,
|
|
1276
1317
|
toolCount: target.progressSummary?.toolCount,
|
|
1277
1318
|
error: target.error,
|
|
1319
|
+
agentContract: target.agentContract,
|
|
1320
|
+
execution: target.execution,
|
|
1278
1321
|
acceptance: target.acceptance,
|
|
1322
|
+
review: target.review,
|
|
1323
|
+
effects: target.effects,
|
|
1279
1324
|
...(transcriptWriter ? { transcriptPath: artifactPathsResult.transcriptPath } : {}),
|
|
1280
1325
|
transcriptError: target.transcriptError,
|
|
1281
1326
|
skills: target.skills,
|
|
@@ -1299,10 +1344,11 @@ export async function runSync(
|
|
|
1299
1344
|
? { content: childWrittenOutput, path: options.outputPath, authoritative: options.outputMode === "file-only" }
|
|
1300
1345
|
: undefined,
|
|
1301
1346
|
cwd: options.cwd ?? runtimeCwd,
|
|
1347
|
+
reportOptional: isAgentContractV1(options.agentContract),
|
|
1302
1348
|
});
|
|
1303
1349
|
const acceptanceFailure = acceptanceFailureMessage(recoveredResult.acceptance);
|
|
1304
1350
|
stripAcceptanceReportsFromMessages(recoveredResult.messages);
|
|
1305
|
-
if (acceptanceFailure && recoveredResult.acceptance.explicit && recoveredResult.exitCode === 0) {
|
|
1351
|
+
if (acceptanceFailure && recoveredResult.acceptance.explicit && recoveredResult.exitCode === 0 && !isAgentContractV1(options.agentContract)) {
|
|
1306
1352
|
recoveredResult.exitCode = 1;
|
|
1307
1353
|
recoveredResult.error = recoveredResult.error ? `${recoveredResult.error}\n${acceptanceFailure}` : acceptanceFailure;
|
|
1308
1354
|
if (recoveredResult.progress) {
|
|
@@ -1310,6 +1356,7 @@ export async function runSync(
|
|
|
1310
1356
|
recoveredResult.progress.error = recoveredResult.error;
|
|
1311
1357
|
}
|
|
1312
1358
|
}
|
|
1359
|
+
if (isAgentContractV1(options.agentContract)) attachContractProjections(recoveredResult);
|
|
1313
1360
|
persistResultMetadata(recoveredResult);
|
|
1314
1361
|
options.onDetachedExit?.(recoveredResult);
|
|
1315
1362
|
})().catch((error) => {
|
|
@@ -1366,14 +1413,14 @@ export async function runSync(
|
|
|
1366
1413
|
attemptNotes.push(formatModelAttemptNote(attempt, modelsToTry[i + 1]));
|
|
1367
1414
|
}
|
|
1368
1415
|
|
|
1369
|
-
const result = lastResult ?? {
|
|
1416
|
+
const result = withRunContext(lastResult ?? {
|
|
1370
1417
|
agent: agentName,
|
|
1371
1418
|
task,
|
|
1372
1419
|
exitCode: 1,
|
|
1373
1420
|
messages: [],
|
|
1374
1421
|
usage: emptyUsage(),
|
|
1375
1422
|
error: "Subagent did not produce a result.",
|
|
1376
|
-
} satisfies SingleResult;
|
|
1423
|
+
} satisfies SingleResult, options.context);
|
|
1377
1424
|
|
|
1378
1425
|
result.usage = aggregateUsage;
|
|
1379
1426
|
result.attemptedModels = attemptedModels.length > 0 ? attemptedModels : undefined;
|
|
@@ -1396,7 +1443,12 @@ export async function runSync(
|
|
|
1396
1443
|
if (artifactPathsResult && options.artifactConfig?.enabled !== false) {
|
|
1397
1444
|
result.artifactPaths = artifactPathsResult;
|
|
1398
1445
|
if (options.artifactConfig?.includeOutput !== false) {
|
|
1399
|
-
writeArtifact(artifactPathsResult.outputPath,
|
|
1446
|
+
writeArtifact(artifactPathsResult.outputPath, formatOutputArtifactContent({
|
|
1447
|
+
output: artifactOutputByResult.get(result) ?? result.finalOutput ?? "",
|
|
1448
|
+
error: result.error,
|
|
1449
|
+
transcriptPath: result.transcriptPath,
|
|
1450
|
+
metadataPath: options.artifactConfig?.includeMetadata === false ? undefined : artifactPathsResult.metadataPath,
|
|
1451
|
+
}));
|
|
1400
1452
|
}
|
|
1401
1453
|
if (options.maxOutput) {
|
|
1402
1454
|
const config = { ...DEFAULT_MAX_OUTPUT, ...options.maxOutput };
|
|
@@ -1435,11 +1487,12 @@ export async function runSync(
|
|
|
1435
1487
|
? { content: childWrittenOutput, path: options.outputPath, authoritative: options.outputMode === "file-only" }
|
|
1436
1488
|
: undefined,
|
|
1437
1489
|
cwd: options.cwd ?? runtimeCwd,
|
|
1490
|
+
reportOptional: isAgentContractV1(options.agentContract),
|
|
1438
1491
|
});
|
|
1439
1492
|
}
|
|
1440
1493
|
const acceptanceFailure = acceptanceFailureMessage(result.acceptance);
|
|
1441
1494
|
stripAcceptanceReportsFromMessages(result.messages);
|
|
1442
|
-
if (acceptanceFailure && result.acceptance.explicit && result.exitCode === 0 && !result.detached && !result.interrupted && !result.timedOut) {
|
|
1495
|
+
if (acceptanceFailure && result.acceptance.explicit && result.exitCode === 0 && !result.detached && !result.interrupted && !result.timedOut && !isAgentContractV1(options.agentContract)) {
|
|
1443
1496
|
result.exitCode = 1;
|
|
1444
1497
|
result.error = result.error ? `${result.error}\n${acceptanceFailure}` : acceptanceFailure;
|
|
1445
1498
|
if (result.progress) {
|
|
@@ -1447,6 +1500,7 @@ export async function runSync(
|
|
|
1447
1500
|
result.progress.error = result.error;
|
|
1448
1501
|
}
|
|
1449
1502
|
}
|
|
1503
|
+
if (isAgentContractV1(options.agentContract)) attachContractProjections(result);
|
|
1450
1504
|
persistResultMetadata(result);
|
|
1451
1505
|
|
|
1452
1506
|
return result;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { AgentProgress, ForegroundChildControl, ForegroundRunControl } from "../../shared/types.ts";
|
|
2
|
+
|
|
3
|
+
interface BeginForegroundChildInput {
|
|
4
|
+
index: number;
|
|
5
|
+
agent: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
interrupt: () => boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function copyProgress(target: ForegroundChildControl, progress: AgentProgress | undefined): void {
|
|
11
|
+
if (!progress) return;
|
|
12
|
+
target.currentActivityState = progress.activityState;
|
|
13
|
+
target.lastActivityAt = progress.lastActivityAt;
|
|
14
|
+
target.currentTool = progress.currentTool;
|
|
15
|
+
target.currentToolStartedAt = progress.currentToolStartedAt;
|
|
16
|
+
target.currentPath = progress.currentPath;
|
|
17
|
+
target.turnCount = progress.turnCount;
|
|
18
|
+
target.tokens = progress.tokens;
|
|
19
|
+
target.toolCount = progress.toolCount;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function syncCurrentChild(control: ForegroundRunControl, child: ForegroundChildControl): void {
|
|
23
|
+
control.currentAgent = child.agent;
|
|
24
|
+
control.currentIndex = child.index;
|
|
25
|
+
control.description = child.description;
|
|
26
|
+
control.currentActivityState = child.currentActivityState;
|
|
27
|
+
control.lastActivityAt = child.lastActivityAt;
|
|
28
|
+
control.currentTool = child.currentTool;
|
|
29
|
+
control.currentToolStartedAt = child.currentToolStartedAt;
|
|
30
|
+
control.currentPath = child.currentPath;
|
|
31
|
+
control.turnCount = child.turnCount;
|
|
32
|
+
control.tokens = child.tokens;
|
|
33
|
+
control.toolCount = child.toolCount;
|
|
34
|
+
control.interrupt = child.interrupt;
|
|
35
|
+
control.updatedAt = child.updatedAt;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function clearCurrentChild(control: ForegroundRunControl): void {
|
|
39
|
+
control.currentAgent = undefined;
|
|
40
|
+
control.currentIndex = undefined;
|
|
41
|
+
control.currentActivityState = undefined;
|
|
42
|
+
control.lastActivityAt = undefined;
|
|
43
|
+
control.currentTool = undefined;
|
|
44
|
+
control.currentToolStartedAt = undefined;
|
|
45
|
+
control.currentPath = undefined;
|
|
46
|
+
control.turnCount = undefined;
|
|
47
|
+
control.tokens = undefined;
|
|
48
|
+
control.toolCount = undefined;
|
|
49
|
+
control.interrupt = undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function beginForegroundChild(control: ForegroundRunControl, input: BeginForegroundChildInput): void {
|
|
53
|
+
const now = Date.now();
|
|
54
|
+
const child: ForegroundChildControl = {
|
|
55
|
+
index: input.index,
|
|
56
|
+
agent: input.agent,
|
|
57
|
+
...(input.description ? { description: input.description } : {}),
|
|
58
|
+
startedAt: now,
|
|
59
|
+
updatedAt: now,
|
|
60
|
+
};
|
|
61
|
+
child.interrupt = () => {
|
|
62
|
+
if (!input.interrupt()) return false;
|
|
63
|
+
child.currentActivityState = undefined;
|
|
64
|
+
child.updatedAt = Date.now();
|
|
65
|
+
syncCurrentChild(control, child);
|
|
66
|
+
return true;
|
|
67
|
+
};
|
|
68
|
+
control.activeChildren ??= new Map();
|
|
69
|
+
control.activeChildren.set(input.index, child);
|
|
70
|
+
syncCurrentChild(control, child);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function updateForegroundChild(control: ForegroundRunControl, index: number, progress: AgentProgress | undefined): void {
|
|
74
|
+
const child = control.activeChildren?.get(index);
|
|
75
|
+
if (!child) return;
|
|
76
|
+
copyProgress(child, progress);
|
|
77
|
+
child.updatedAt = Date.now();
|
|
78
|
+
syncCurrentChild(control, child);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function finishForegroundChild(control: ForegroundRunControl, index: number): void {
|
|
82
|
+
control.activeChildren?.delete(index);
|
|
83
|
+
if (control.currentIndex === index) {
|
|
84
|
+
const next = [...(control.activeChildren?.values() ?? [])]
|
|
85
|
+
.sort((left, right) => right.updatedAt - left.updatedAt || left.index - right.index)[0];
|
|
86
|
+
if (next) syncCurrentChild(control, next);
|
|
87
|
+
else clearCurrentChild(control);
|
|
88
|
+
}
|
|
89
|
+
control.updatedAt = Date.now();
|
|
90
|
+
}
|