pi-subagents 0.35.1 → 0.37.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 +71 -0
- package/README.md +224 -31
- package/agents/advisor.md +73 -0
- package/agents/delegate.md +2 -0
- package/agents/worker.md +2 -0
- package/package.json +11 -13
- package/skills/pi-subagents/SKILL.md +42 -13
- package/src/agents/agent-management.ts +7 -1
- package/src/agents/agents.ts +121 -17
- package/src/api/capability-ceiling.ts +17 -0
- package/src/api/delegation.ts +127 -0
- package/src/api/preflight.ts +399 -0
- package/src/extension/config.ts +7 -1
- package/src/extension/index.ts +52 -38
- package/src/extension/rpc.ts +31 -2
- package/src/extension/schemas.ts +29 -3
- package/src/extension/tool-description.ts +4 -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 +158 -17
- package/src/runs/background/async-job-tracker.ts +4 -0
- package/src/runs/background/async-resume.ts +47 -8
- package/src/runs/background/async-status.ts +94 -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/process-terminal.ts +280 -0
- package/src/runs/background/result-watcher.ts +138 -78
- package/src/runs/background/run-status.ts +10 -2
- package/src/runs/background/scheduled-runs.ts +6 -1
- package/src/runs/background/stale-run-reconciler.ts +6 -0
- package/src/runs/background/subagent-runner.ts +406 -54
- 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 +181 -111
- package/src/runs/foreground/execution.ts +147 -47
- package/src/runs/foreground/foreground-control.ts +90 -0
- package/src/runs/foreground/subagent-executor.ts +426 -165
- package/src/runs/shared/acceptance.ts +94 -41
- package/src/runs/shared/agent-contract.ts +38 -0
- package/src/runs/shared/capability-ceiling.ts +177 -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 +5 -5
- package/src/runs/shared/long-running-guard.ts +4 -0
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +12 -6
- package/src/runs/shared/nested-events.ts +35 -3
- package/src/runs/shared/parallel-handoff.ts +154 -0
- package/src/runs/shared/parallel-utils.ts +11 -0
- package/src/runs/shared/pi-args.ts +148 -56
- package/src/runs/shared/run-history.ts +90 -5
- package/src/runs/shared/session-lease.ts +25 -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 +31 -19
- package/src/runs/shared/task-intent.ts +10 -5
- package/src/runs/shared/tool-availability.ts +21 -3
- 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/launch-contract.ts +123 -0
- package/src/shared/settings.ts +9 -1
- package/src/shared/types.ts +314 -28
- package/src/shared/utils.ts +17 -42
- package/src/slash/delegation-adapters.ts +153 -6
- 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 +15 -6
- 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 +17 -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,
|
|
@@ -41,6 +42,7 @@ import {
|
|
|
41
42
|
getFinalOutput,
|
|
42
43
|
findLatestSessionFile,
|
|
43
44
|
detectSubagentError,
|
|
45
|
+
hasEmptyTerminalAssistantResponse,
|
|
44
46
|
extractToolArgsPreview,
|
|
45
47
|
extractTextFromContent,
|
|
46
48
|
} from "../../shared/utils.ts";
|
|
@@ -50,8 +52,10 @@ import { evaluateCompletionMutationGuard } from "../shared/completion-guard.ts";
|
|
|
50
52
|
import { getPiSpawnCommand } from "../shared/pi-spawn.ts";
|
|
51
53
|
import { createJsonlWriter } from "../../shared/jsonl-writer.ts";
|
|
52
54
|
import { attachPostExitStdioGuard, trySignalChild } from "../../shared/post-exit-stdio-guard.ts";
|
|
53
|
-
import { applyThinkingSuffix, buildPiArgs, cleanupTempDir } from "../shared/pi-args.ts";
|
|
54
|
-
import {
|
|
55
|
+
import { applyThinkingSuffix, buildPiArgs, cleanupTempDir, resolvePiLaunchToolPlan } from "../shared/pi-args.ts";
|
|
56
|
+
import { decodeSubagentCapabilityCeiling, resolveCurrentSubagentCapabilityCeiling, SUBAGENT_CAPABILITY_CEILING_ENV } from "../shared/capability-ceiling.ts";
|
|
57
|
+
import { resolveEffectiveThinking } from "../../shared/model-info.ts";
|
|
58
|
+
import { MISSING_STRUCTURED_OUTPUT_CALL_ERROR, readStructuredOutput } from "../shared/structured-output.ts";
|
|
55
59
|
import { readChildToolDiagnosticError } from "../shared/tool-availability.ts";
|
|
56
60
|
import { captureSingleOutputSnapshot, extractChildWrittenOutput, formatSavedOutputReference, injectOutputPathSystemPrompt, resolveSingleOutput, validateFileOnlyOutputMode, type SingleOutputSnapshot } from "../shared/single-output.ts";
|
|
57
61
|
import {
|
|
@@ -71,9 +75,11 @@ import {
|
|
|
71
75
|
summarizeRecentMutatingFailures,
|
|
72
76
|
} from "../shared/long-running-guard.ts";
|
|
73
77
|
import { acceptanceFailureMessage, buildSkippedAcceptanceLedger, evaluateAcceptance, formatAcceptancePrompt, resolveEffectiveAcceptance, stripAcceptanceReport } from "../shared/acceptance.ts";
|
|
78
|
+
import { attachContractProjections, isAgentContractV1 } from "../shared/agent-contract.ts";
|
|
74
79
|
import { appendTurnBudgetSystemPrompt, formatTurnBudgetOutput, initialTurnBudgetState, turnBudgetDecision, turnBudgetDeferredNote, turnBudgetDeferredState, turnBudgetExceededMessage, turnBudgetSoftNote, turnBudgetState } from "../shared/turn-budget.ts";
|
|
75
80
|
import { initialToolBudgetState, toolBudgetState } from "../shared/tool-budget.ts";
|
|
76
81
|
import { resolveWatchdogConfig } from "../../watchdog/settings.ts";
|
|
82
|
+
import { agentDefinitionDigest, launchBindingDigest } from "../../shared/launch-contract.ts";
|
|
77
83
|
import { createBoundedByteTail, createBoundedLineReader, formatProtocolOutputLimit, MAX_CHILD_STDERR_BYTES, projectChildLifecycle, type ChildLifecycleAction, type ProtocolOutputLimit } from "../shared/child-protocol.ts";
|
|
78
84
|
import {
|
|
79
85
|
acceptChildWatchdogEvent,
|
|
@@ -90,6 +96,12 @@ function emptyUsage(): Usage {
|
|
|
90
96
|
return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 };
|
|
91
97
|
}
|
|
92
98
|
|
|
99
|
+
function withRunContext<T extends SingleResult>(result: T, context: RunSyncOptions["context"]): T {
|
|
100
|
+
if (!context) return result;
|
|
101
|
+
result.context = context;
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
|
|
93
105
|
function sumUsage(target: Usage, source: Usage): void {
|
|
94
106
|
target.input += source.input;
|
|
95
107
|
target.output += source.output;
|
|
@@ -116,6 +128,7 @@ function resolveAttemptTimeout(options: RunSyncOptions): { timeoutMs: number; re
|
|
|
116
128
|
function buildPendingAcceptanceLedger(acceptance: ResolvedAcceptanceConfig): AcceptanceLedger {
|
|
117
129
|
return {
|
|
118
130
|
status: "pending",
|
|
131
|
+
evidenceStatus: "pending",
|
|
119
132
|
explicit: acceptance.explicit,
|
|
120
133
|
effectiveAcceptance: acceptance,
|
|
121
134
|
inferredReason: acceptance.inferredReason,
|
|
@@ -185,6 +198,7 @@ async function runSingleAttempt(
|
|
|
185
198
|
sessionEnabled: boolean;
|
|
186
199
|
systemPrompt: string;
|
|
187
200
|
resolvedSkillNames?: string[];
|
|
201
|
+
modelCandidates?: string[];
|
|
188
202
|
skillsWarning?: string;
|
|
189
203
|
jsonlPath?: string;
|
|
190
204
|
artifactPaths?: ArtifactPaths;
|
|
@@ -196,6 +210,7 @@ async function runSingleAttempt(
|
|
|
196
210
|
): Promise<SingleResult> {
|
|
197
211
|
const effectiveThinking = options.thinkingOverride ?? agent.thinking;
|
|
198
212
|
const modelArg = applyThinkingSuffix(model, effectiveThinking, options.thinkingOverride !== undefined);
|
|
213
|
+
const resolvedThinking = resolveEffectiveThinking(modelArg, effectiveThinking);
|
|
199
214
|
const watchdogConfig = resolveWatchdogConfig(options.cwd ?? runtimeCwd);
|
|
200
215
|
const childWatchdog = watchdogConfig.ok
|
|
201
216
|
? resolveChildWatchdogConfig({
|
|
@@ -205,7 +220,7 @@ async function runSingleAttempt(
|
|
|
205
220
|
childIndex: options.index ?? 0,
|
|
206
221
|
})
|
|
207
222
|
: undefined;
|
|
208
|
-
const { args, env: sharedEnv, tempDir, toolDiagnosticPath } = buildPiArgs({
|
|
223
|
+
const { args, env: sharedEnv, tempDir, toolDiagnosticPath, capabilityAudit } = buildPiArgs({
|
|
209
224
|
baseArgs: ["--mode", "json", "-p"],
|
|
210
225
|
task,
|
|
211
226
|
sessionEnabled: shared.sessionEnabled,
|
|
@@ -236,24 +251,61 @@ async function runSingleAttempt(
|
|
|
236
251
|
parentSessionId: options.parentSessionId,
|
|
237
252
|
structuredOutput: options.structuredOutput,
|
|
238
253
|
toolBudget: options.toolBudget,
|
|
254
|
+
allowZeroToolBudget: options.allowZeroToolBudget,
|
|
239
255
|
childWatchdog,
|
|
240
256
|
waitToolEnabled: options.waitToolEnabled,
|
|
257
|
+
capabilityCeiling: options.capabilityCeiling,
|
|
241
258
|
});
|
|
242
259
|
|
|
243
|
-
const
|
|
260
|
+
const effectiveSystemPrompt = appendTurnBudgetSystemPrompt(shared.systemPrompt, options.turnBudget);
|
|
261
|
+
const toolPlan = resolvePiLaunchToolPlan({
|
|
262
|
+
tools: agent.tools,
|
|
263
|
+
extensions: agent.extensions,
|
|
264
|
+
subagentOnlyExtensions: agent.subagentOnlyExtensions,
|
|
265
|
+
mcpDirectTools: agent.mcpDirectTools,
|
|
266
|
+
cwd: options.cwd ?? runtimeCwd,
|
|
267
|
+
requireReadTool: Boolean(shared.resolvedSkillNames?.length),
|
|
268
|
+
structuredOutput: Boolean(options.structuredOutput),
|
|
269
|
+
capabilityCeiling: options.capabilityCeiling,
|
|
270
|
+
inheritedCapabilityCeiling: decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV]),
|
|
271
|
+
});
|
|
272
|
+
const launchContractDigest = launchBindingDigest({
|
|
273
|
+
definitionDigest: agentDefinitionDigest(agent),
|
|
274
|
+
task: shared.originalTask ?? task,
|
|
275
|
+
...(modelArg ? { model: modelArg } : {}),
|
|
276
|
+
modelCandidates: shared.modelCandidates,
|
|
277
|
+
...(resolvedThinking ? { thinking: resolvedThinking } : {}),
|
|
278
|
+
systemPrompt: effectiveSystemPrompt,
|
|
279
|
+
systemPromptMode: agent.systemPromptMode,
|
|
280
|
+
inheritProjectContext: agent.inheritProjectContext,
|
|
281
|
+
inheritSkills: agent.inheritSkills,
|
|
282
|
+
skills: shared.resolvedSkillNames ?? [],
|
|
283
|
+
tools: toolPlan.effectiveToolAllowlist,
|
|
284
|
+
extensions: toolPlan.extensionArgs,
|
|
285
|
+
mcpDirectTools: toolPlan.effectiveMcpTools,
|
|
286
|
+
...(options.outputPath ? { outputPath: options.outputPath } : {}),
|
|
287
|
+
outputMode: options.outputMode ?? "inline",
|
|
288
|
+
...(options.structuredOutput ? { structuredOutputSchema: options.structuredOutput.schema } : {}),
|
|
289
|
+
});
|
|
290
|
+
const result: SingleResult = withRunContext({
|
|
244
291
|
agent: agent.name,
|
|
245
292
|
task: shared.originalTask ?? task,
|
|
293
|
+
...(options.agentContract ? { agentContract: options.agentContract } : {}),
|
|
294
|
+
launchContractDigest,
|
|
246
295
|
exitCode: 0,
|
|
247
296
|
messages: [],
|
|
248
297
|
usage: emptyUsage(),
|
|
249
298
|
model: modelArg,
|
|
299
|
+
...(resolvedThinking ? { thinking: resolvedThinking } : {}),
|
|
250
300
|
artifactPaths: shared.artifactPaths,
|
|
251
301
|
transcriptPath: shared.transcriptWriter ? shared.artifactPaths?.transcriptPath : undefined,
|
|
252
302
|
skills: shared.resolvedSkillNames,
|
|
253
303
|
skillsWarning: shared.skillsWarning,
|
|
254
304
|
...(options.turnBudget ? { turnBudget: initialTurnBudgetState(options.turnBudget) } : {}),
|
|
255
305
|
...(options.toolBudget ? { toolBudget: initialToolBudgetState(options.toolBudget) } : {}),
|
|
256
|
-
|
|
306
|
+
...(options.capabilityCeiling ? { capabilityCeiling: options.capabilityCeiling } : {}),
|
|
307
|
+
...(capabilityAudit ? { capabilityAudit } : {}),
|
|
308
|
+
}, options.context);
|
|
257
309
|
const startTime = Date.now();
|
|
258
310
|
if (options.structuredOutput) {
|
|
259
311
|
try {
|
|
@@ -307,6 +359,7 @@ async function runSingleAttempt(
|
|
|
307
359
|
}
|
|
308
360
|
const spawnEnv = { ...process.env, ...sharedEnv, ...getSubagentDepthEnv(options.maxSubagentDepth) };
|
|
309
361
|
let observedMutationAttempt = false;
|
|
362
|
+
let structuredOutputToolInvoked = false;
|
|
310
363
|
|
|
311
364
|
const exitCode = await new Promise<number>((resolve) => {
|
|
312
365
|
const spawnSpec = getPiSpawnCommand(args);
|
|
@@ -592,7 +645,7 @@ async function runSingleAttempt(
|
|
|
592
645
|
result.wrapUpRequested = true;
|
|
593
646
|
appendRecentOutput(progress, [turnBudgetSoftNote(budget, turnCount)]);
|
|
594
647
|
}
|
|
595
|
-
const decision = turnBudgetDecision(budget, turnCount, terminalAssistantStop, toolWorkActiveOrStarting);
|
|
648
|
+
const decision = turnBudgetDecision(budget, turnCount, terminalAssistantStop, toolWorkActiveOrStarting, options.enforceHardTurnLimit);
|
|
596
649
|
if (decision === "defer") {
|
|
597
650
|
result.turnBudget = turnBudgetDeferredState(
|
|
598
651
|
budget,
|
|
@@ -646,7 +699,7 @@ async function runSingleAttempt(
|
|
|
646
699
|
const fireUpdate = () => {
|
|
647
700
|
if (!options.onUpdate || processClosed) return;
|
|
648
701
|
progress.durationMs = Date.now() - startTime;
|
|
649
|
-
const output = (result.timedOut || result.turnBudgetExceeded) && result.finalOutput ? result.finalOutput : getFinalOutput(result.messages);
|
|
702
|
+
const output = (result.timedOut || result.turnBudgetExceeded) && result.finalOutput ? result.finalOutput : getFinalOutput(result.messages ?? []);
|
|
650
703
|
emitUpdateSnapshot(output || "(running...)");
|
|
651
704
|
};
|
|
652
705
|
|
|
@@ -696,6 +749,7 @@ async function runSingleAttempt(
|
|
|
696
749
|
const toolArgs = evt.args && typeof evt.args === "object" && !Array.isArray(evt.args)
|
|
697
750
|
? evt.args as Record<string, unknown>
|
|
698
751
|
: {};
|
|
752
|
+
if (options.structuredOutput && evt.toolName === "structured_output") structuredOutputToolInvoked = true;
|
|
699
753
|
if (options.allowIntercomDetach && (evt.toolName === "intercom" || evt.toolName === "contact_supervisor")) {
|
|
700
754
|
intercomStarted = true;
|
|
701
755
|
}
|
|
@@ -729,15 +783,20 @@ async function runSingleAttempt(
|
|
|
729
783
|
}
|
|
730
784
|
|
|
731
785
|
if (evt.type === "message_end" && evt.message) {
|
|
732
|
-
result.messages
|
|
786
|
+
result.messages!.push(evt.message);
|
|
733
787
|
if (evt.message.role === "assistant") {
|
|
734
788
|
result.usage.turns++;
|
|
735
789
|
progress.turnCount = result.usage.turns;
|
|
736
790
|
const stopReason = (evt.message as { stopReason?: string }).stopReason;
|
|
737
|
-
const
|
|
738
|
-
|
|
791
|
+
const toolCalls = Array.isArray(evt.message.content)
|
|
792
|
+
? evt.message.content.filter((part) => (part as { type?: string }).type === "toolCall")
|
|
793
|
+
: [];
|
|
794
|
+
const hasToolCall = toolCalls.length > 0;
|
|
739
795
|
const terminalAssistantStop = stopReason === "stop" && !hasToolCall;
|
|
740
|
-
|
|
796
|
+
const terminalStructuredOutputCall = Boolean(options.structuredOutput)
|
|
797
|
+
&& toolCalls.length === 1
|
|
798
|
+
&& (toolCalls[0] as { name?: string }).name === "structured_output";
|
|
799
|
+
updateTurnBudget(result.usage.turns, terminalAssistantStop || terminalStructuredOutputCall, hasToolCall || Boolean(progress.currentTool));
|
|
741
800
|
const u = evt.message.usage;
|
|
742
801
|
if (u) {
|
|
743
802
|
result.usage.input += u.input || 0;
|
|
@@ -763,7 +822,7 @@ async function runSingleAttempt(
|
|
|
763
822
|
}
|
|
764
823
|
|
|
765
824
|
if (evt.type === "tool_result_end" && evt.message) {
|
|
766
|
-
result.messages
|
|
825
|
+
result.messages!.push(evt.message);
|
|
767
826
|
const resultText = extractTextFromContent(evt.message.content);
|
|
768
827
|
if (options.toolBudget && pendingToolResult && resultText.includes("Tool budget hard limit reached")) {
|
|
769
828
|
result.toolBudgetBlocked = true;
|
|
@@ -1021,37 +1080,43 @@ async function runSingleAttempt(
|
|
|
1021
1080
|
result.exitCode = 1;
|
|
1022
1081
|
}
|
|
1023
1082
|
if (result.exitCode === 0 && !result.error) {
|
|
1024
|
-
const
|
|
1025
|
-
|
|
1026
|
-
result.exitCode = errInfo.exitCode ?? 1;
|
|
1027
|
-
result.error = errInfo.details
|
|
1028
|
-
? `${errInfo.errorType} failed (exit ${errInfo.exitCode}): ${errInfo.details}`
|
|
1029
|
-
: `${errInfo.errorType} failed with exit code ${errInfo.exitCode}`;
|
|
1030
|
-
}
|
|
1031
|
-
}
|
|
1032
|
-
if (result.exitCode === 0 && !result.error) {
|
|
1033
|
-
const finalText = getFinalOutput(result.messages);
|
|
1083
|
+
const messages = result.messages ?? [];
|
|
1084
|
+
const finalText = getFinalOutput(messages);
|
|
1034
1085
|
const missingStructuredOutput = options.structuredOutput
|
|
1035
1086
|
? !existsSync(options.structuredOutput.outputPath)
|
|
1036
1087
|
: false;
|
|
1037
|
-
|
|
1088
|
+
const errInfo = detectSubagentError(messages);
|
|
1089
|
+
const missingOutput = !finalText?.trim() && (!options.structuredOutput || missingStructuredOutput);
|
|
1090
|
+
if (missingOutput && (!errInfo.hasError || hasEmptyTerminalAssistantResponse(messages))) {
|
|
1038
1091
|
result.exitCode = 1;
|
|
1039
1092
|
result.error = "Subagent produced no output (possible model cold-start or empty response).";
|
|
1093
|
+
} else if (errInfo.hasError) {
|
|
1094
|
+
result.exitCode = errInfo.exitCode ?? 1;
|
|
1095
|
+
result.error = errInfo.details
|
|
1096
|
+
? `${errInfo.errorType} failed (exit ${errInfo.exitCode}): ${errInfo.details}`
|
|
1097
|
+
: `${errInfo.errorType} failed with exit code ${errInfo.exitCode}`;
|
|
1040
1098
|
}
|
|
1041
1099
|
}
|
|
1042
1100
|
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
1101
|
result.structuredOutputSchemaPath = options.structuredOutput.schemaPath;
|
|
1049
1102
|
result.structuredOutputPath = options.structuredOutput.outputPath;
|
|
1050
|
-
if (
|
|
1103
|
+
if (!structuredOutputToolInvoked) {
|
|
1051
1104
|
result.exitCode = 1;
|
|
1052
|
-
result.error =
|
|
1105
|
+
result.error = MISSING_STRUCTURED_OUTPUT_CALL_ERROR;
|
|
1106
|
+
result.structuredOutputFailed = true;
|
|
1053
1107
|
} else {
|
|
1054
|
-
|
|
1108
|
+
const structured = await readStructuredOutput({
|
|
1109
|
+
schema: options.structuredOutput.schema,
|
|
1110
|
+
schemaPath: options.structuredOutput.schemaPath,
|
|
1111
|
+
outputPath: options.structuredOutput.outputPath,
|
|
1112
|
+
});
|
|
1113
|
+
if (structured.error) {
|
|
1114
|
+
result.exitCode = 1;
|
|
1115
|
+
result.error = structured.error;
|
|
1116
|
+
result.structuredOutputFailed = true;
|
|
1117
|
+
} else {
|
|
1118
|
+
result.structuredOutput = structured.value;
|
|
1119
|
+
}
|
|
1055
1120
|
}
|
|
1056
1121
|
}
|
|
1057
1122
|
|
|
@@ -1070,7 +1135,7 @@ async function runSingleAttempt(
|
|
|
1070
1135
|
durationMs: progress.durationMs,
|
|
1071
1136
|
};
|
|
1072
1137
|
|
|
1073
|
-
const acceptanceOutput = getFinalOutput(result.messages);
|
|
1138
|
+
const acceptanceOutput = getFinalOutput(result.messages ?? []);
|
|
1074
1139
|
let fullOutput = stripAcceptanceReport(acceptanceOutput);
|
|
1075
1140
|
if (result.timedOut) {
|
|
1076
1141
|
const timeoutMessage = formatTimeoutMessage(options.timeoutMs ?? 0);
|
|
@@ -1086,16 +1151,29 @@ async function runSingleAttempt(
|
|
|
1086
1151
|
const note = turnBudgetSoftNote(result.turnBudget, result.turnBudget.wrapUpRequestedAtTurn ?? result.turnBudget.turnCount);
|
|
1087
1152
|
fullOutput = fullOutput.trim() ? `${note}\n\n${fullOutput}` : note;
|
|
1088
1153
|
}
|
|
1089
|
-
const
|
|
1154
|
+
const completionGuardEnabled = isAgentContractV1(options.agentContract) ? agent.completionGuard === true : agent.completionGuard !== false;
|
|
1155
|
+
const completionGuard = result.exitCode === 0 && !result.error && completionGuardEnabled
|
|
1090
1156
|
? evaluateCompletionMutationGuard({
|
|
1091
1157
|
agent: agent.name,
|
|
1092
1158
|
task: shared.originalTask ?? task,
|
|
1093
|
-
messages: result.messages,
|
|
1159
|
+
messages: result.messages ?? [],
|
|
1094
1160
|
tools: agent.tools,
|
|
1095
1161
|
mcpDirectTools: agent.mcpDirectTools,
|
|
1096
1162
|
})
|
|
1097
1163
|
: undefined;
|
|
1098
|
-
|
|
1164
|
+
const completionGuardTriggered = completionGuard?.triggered === true && !observedMutationAttempt;
|
|
1165
|
+
if (completionGuard) {
|
|
1166
|
+
result.effects = {
|
|
1167
|
+
...(result.effects ?? {}),
|
|
1168
|
+
fileMutation: {
|
|
1169
|
+
status: completionGuard.expectedMutation ? completionGuardTriggered ? "missing" : "observed" : "not-applicable",
|
|
1170
|
+
expected: completionGuard.expectedMutation,
|
|
1171
|
+
attempted: completionGuard.attemptedMutation || observedMutationAttempt,
|
|
1172
|
+
...(completionGuardTriggered ? { message: "Subagent completed without making edits for an implementation task." } : {}),
|
|
1173
|
+
},
|
|
1174
|
+
};
|
|
1175
|
+
}
|
|
1176
|
+
if (completionGuardTriggered && !isAgentContractV1(options.agentContract)) {
|
|
1099
1177
|
result.exitCode = 1;
|
|
1100
1178
|
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
1179
|
progress.status = "failed";
|
|
@@ -1154,20 +1232,24 @@ export async function runSync(
|
|
|
1154
1232
|
task: string,
|
|
1155
1233
|
options: RunSyncOptions,
|
|
1156
1234
|
): Promise<SingleResult> {
|
|
1235
|
+
options = {
|
|
1236
|
+
...options,
|
|
1237
|
+
capabilityCeiling: options.capabilityCeiling ?? resolveCurrentSubagentCapabilityCeiling(options.parentSessionId),
|
|
1238
|
+
};
|
|
1157
1239
|
const agent = agents.find((a) => a.name === agentName);
|
|
1158
1240
|
if (!agent) {
|
|
1159
|
-
return {
|
|
1241
|
+
return withRunContext({
|
|
1160
1242
|
agent: agentName,
|
|
1161
1243
|
task,
|
|
1162
1244
|
exitCode: 1,
|
|
1163
1245
|
messages: [],
|
|
1164
1246
|
usage: emptyUsage(),
|
|
1165
1247
|
error: `Unknown agent: ${agentName}`,
|
|
1166
|
-
};
|
|
1248
|
+
}, options.context);
|
|
1167
1249
|
}
|
|
1168
1250
|
const outputModeValidationError = validateFileOnlyOutputMode(options.outputMode, options.outputPath, `Single run (${agentName})`);
|
|
1169
1251
|
if (outputModeValidationError) {
|
|
1170
|
-
return {
|
|
1252
|
+
return withRunContext({
|
|
1171
1253
|
agent: agentName,
|
|
1172
1254
|
task,
|
|
1173
1255
|
exitCode: 1,
|
|
@@ -1175,7 +1257,7 @@ export async function runSync(
|
|
|
1175
1257
|
usage: emptyUsage(),
|
|
1176
1258
|
outputMode: options.outputMode,
|
|
1177
1259
|
error: outputModeValidationError,
|
|
1178
|
-
};
|
|
1260
|
+
}, options.context);
|
|
1179
1261
|
}
|
|
1180
1262
|
|
|
1181
1263
|
const shareEnabled = options.share === true;
|
|
@@ -1188,8 +1270,9 @@ export async function runSync(
|
|
|
1188
1270
|
async: options.acceptanceContext?.async,
|
|
1189
1271
|
dynamic: options.acceptanceContext?.dynamic,
|
|
1190
1272
|
dynamicGroup: options.acceptanceContext?.dynamicGroup,
|
|
1273
|
+
agentContract: options.agentContract,
|
|
1191
1274
|
});
|
|
1192
|
-
const acceptancePrompt = formatAcceptancePrompt(effectiveAcceptance);
|
|
1275
|
+
const acceptancePrompt = formatAcceptancePrompt(effectiveAcceptance, { reportOptional: isAgentContractV1(options.agentContract) });
|
|
1193
1276
|
const taskWithAcceptance = acceptancePrompt ? `${task}\n${acceptancePrompt}` : task;
|
|
1194
1277
|
const sessionEnabled = Boolean(options.sessionFile || options.sessionDir) || shareEnabled;
|
|
1195
1278
|
const skillNames = options.skills ?? agent.skills ?? [];
|
|
@@ -1202,14 +1285,14 @@ export async function runSync(
|
|
|
1202
1285
|
agent.filePath ? path.dirname(agent.filePath) : skillCwd,
|
|
1203
1286
|
);
|
|
1204
1287
|
if (skillNames.some((skill) => skill.trim() === "pi-subagents") && missingSkills.includes("pi-subagents")) {
|
|
1205
|
-
return {
|
|
1288
|
+
return withRunContext({
|
|
1206
1289
|
agent: agentName,
|
|
1207
1290
|
task,
|
|
1208
1291
|
exitCode: 1,
|
|
1209
1292
|
messages: [],
|
|
1210
1293
|
usage: emptyUsage(),
|
|
1211
1294
|
error: "Skills not found: pi-subagents",
|
|
1212
|
-
};
|
|
1295
|
+
}, options.context);
|
|
1213
1296
|
}
|
|
1214
1297
|
let systemPrompt = agent.systemPrompt?.trim() || "";
|
|
1215
1298
|
if (resolvedSkills.length > 0) {
|
|
@@ -1275,7 +1358,14 @@ export async function runSync(
|
|
|
1275
1358
|
durationMs: target.progressSummary?.durationMs,
|
|
1276
1359
|
toolCount: target.progressSummary?.toolCount,
|
|
1277
1360
|
error: target.error,
|
|
1361
|
+
agentContract: target.agentContract,
|
|
1362
|
+
launchContractDigest: target.launchContractDigest,
|
|
1363
|
+
execution: target.execution,
|
|
1278
1364
|
acceptance: target.acceptance,
|
|
1365
|
+
capabilityCeiling: target.capabilityCeiling,
|
|
1366
|
+
capabilityAudit: target.capabilityAudit,
|
|
1367
|
+
review: target.review,
|
|
1368
|
+
effects: target.effects,
|
|
1279
1369
|
...(transcriptWriter ? { transcriptPath: artifactPathsResult.transcriptPath } : {}),
|
|
1280
1370
|
transcriptError: target.transcriptError,
|
|
1281
1371
|
skills: target.skills,
|
|
@@ -1299,10 +1389,11 @@ export async function runSync(
|
|
|
1299
1389
|
? { content: childWrittenOutput, path: options.outputPath, authoritative: options.outputMode === "file-only" }
|
|
1300
1390
|
: undefined,
|
|
1301
1391
|
cwd: options.cwd ?? runtimeCwd,
|
|
1392
|
+
reportOptional: isAgentContractV1(options.agentContract),
|
|
1302
1393
|
});
|
|
1303
1394
|
const acceptanceFailure = acceptanceFailureMessage(recoveredResult.acceptance);
|
|
1304
1395
|
stripAcceptanceReportsFromMessages(recoveredResult.messages);
|
|
1305
|
-
if (acceptanceFailure && recoveredResult.acceptance.explicit && recoveredResult.exitCode === 0) {
|
|
1396
|
+
if (acceptanceFailure && recoveredResult.acceptance.explicit && recoveredResult.exitCode === 0 && !isAgentContractV1(options.agentContract)) {
|
|
1306
1397
|
recoveredResult.exitCode = 1;
|
|
1307
1398
|
recoveredResult.error = recoveredResult.error ? `${recoveredResult.error}\n${acceptanceFailure}` : acceptanceFailure;
|
|
1308
1399
|
if (recoveredResult.progress) {
|
|
@@ -1310,6 +1401,7 @@ export async function runSync(
|
|
|
1310
1401
|
recoveredResult.progress.error = recoveredResult.error;
|
|
1311
1402
|
}
|
|
1312
1403
|
}
|
|
1404
|
+
if (isAgentContractV1(options.agentContract)) attachContractProjections(recoveredResult);
|
|
1313
1405
|
persistResultMetadata(recoveredResult);
|
|
1314
1406
|
options.onDetachedExit?.(recoveredResult);
|
|
1315
1407
|
})().catch((error) => {
|
|
@@ -1336,6 +1428,7 @@ export async function runSync(
|
|
|
1336
1428
|
artifactPaths: artifactPathsResult,
|
|
1337
1429
|
transcriptWriter,
|
|
1338
1430
|
attemptNotes,
|
|
1431
|
+
modelCandidates: candidates.map((candidate) => applyThinkingSuffix(candidate, options.thinkingOverride ?? agent.thinking, options.thinkingOverride !== undefined)),
|
|
1339
1432
|
outputSnapshot,
|
|
1340
1433
|
originalTask: task,
|
|
1341
1434
|
});
|
|
@@ -1366,14 +1459,14 @@ export async function runSync(
|
|
|
1366
1459
|
attemptNotes.push(formatModelAttemptNote(attempt, modelsToTry[i + 1]));
|
|
1367
1460
|
}
|
|
1368
1461
|
|
|
1369
|
-
const result = lastResult ?? {
|
|
1462
|
+
const result = withRunContext(lastResult ?? {
|
|
1370
1463
|
agent: agentName,
|
|
1371
1464
|
task,
|
|
1372
1465
|
exitCode: 1,
|
|
1373
1466
|
messages: [],
|
|
1374
1467
|
usage: emptyUsage(),
|
|
1375
1468
|
error: "Subagent did not produce a result.",
|
|
1376
|
-
} satisfies SingleResult;
|
|
1469
|
+
} satisfies SingleResult, options.context);
|
|
1377
1470
|
|
|
1378
1471
|
result.usage = aggregateUsage;
|
|
1379
1472
|
result.attemptedModels = attemptedModels.length > 0 ? attemptedModels : undefined;
|
|
@@ -1396,7 +1489,12 @@ export async function runSync(
|
|
|
1396
1489
|
if (artifactPathsResult && options.artifactConfig?.enabled !== false) {
|
|
1397
1490
|
result.artifactPaths = artifactPathsResult;
|
|
1398
1491
|
if (options.artifactConfig?.includeOutput !== false) {
|
|
1399
|
-
writeArtifact(artifactPathsResult.outputPath,
|
|
1492
|
+
writeArtifact(artifactPathsResult.outputPath, formatOutputArtifactContent({
|
|
1493
|
+
output: artifactOutputByResult.get(result) ?? result.finalOutput ?? "",
|
|
1494
|
+
error: result.error,
|
|
1495
|
+
transcriptPath: result.transcriptPath,
|
|
1496
|
+
metadataPath: options.artifactConfig?.includeMetadata === false ? undefined : artifactPathsResult.metadataPath,
|
|
1497
|
+
}));
|
|
1400
1498
|
}
|
|
1401
1499
|
if (options.maxOutput) {
|
|
1402
1500
|
const config = { ...DEFAULT_MAX_OUTPUT, ...options.maxOutput };
|
|
@@ -1435,11 +1533,12 @@ export async function runSync(
|
|
|
1435
1533
|
? { content: childWrittenOutput, path: options.outputPath, authoritative: options.outputMode === "file-only" }
|
|
1436
1534
|
: undefined,
|
|
1437
1535
|
cwd: options.cwd ?? runtimeCwd,
|
|
1536
|
+
reportOptional: isAgentContractV1(options.agentContract),
|
|
1438
1537
|
});
|
|
1439
1538
|
}
|
|
1440
1539
|
const acceptanceFailure = acceptanceFailureMessage(result.acceptance);
|
|
1441
1540
|
stripAcceptanceReportsFromMessages(result.messages);
|
|
1442
|
-
if (acceptanceFailure && result.acceptance.explicit && result.exitCode === 0 && !result.detached && !result.interrupted && !result.timedOut) {
|
|
1541
|
+
if (acceptanceFailure && result.acceptance.explicit && result.exitCode === 0 && !result.detached && !result.interrupted && !result.timedOut && !isAgentContractV1(options.agentContract)) {
|
|
1443
1542
|
result.exitCode = 1;
|
|
1444
1543
|
result.error = result.error ? `${result.error}\n${acceptanceFailure}` : acceptanceFailure;
|
|
1445
1544
|
if (result.progress) {
|
|
@@ -1447,6 +1546,7 @@ export async function runSync(
|
|
|
1447
1546
|
result.progress.error = result.error;
|
|
1448
1547
|
}
|
|
1449
1548
|
}
|
|
1549
|
+
if (isAgentContractV1(options.agentContract)) attachContractProjections(result);
|
|
1450
1550
|
persistResultMetadata(result);
|
|
1451
1551
|
|
|
1452
1552
|
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
|
+
}
|