pi-subagents 0.55.0 → 0.57.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 +81 -3
- package/agents/claude-code-writer.md +15 -0
- package/agents/claude-code.md +15 -0
- package/agents/codex-exec-writer.md +15 -0
- package/agents/codex-exec.md +15 -0
- package/agents/cursor-agent-writer.md +14 -0
- package/agents/cursor-agent.md +14 -0
- package/docs/agents.md +111 -15
- package/docs/configuration.md +37 -0
- package/docs/extension-api.md +2 -2
- package/docs/models.md +6 -0
- package/docs/observability.md +8 -7
- package/docs/tool-reference.md +11 -1
- package/docs/workflows.md +20 -0
- package/package.json +1 -1
- package/skills/pi-subagents/references/execution-controls.md +1 -1
- package/src/agents/agent-management.ts +13 -5
- package/src/agents/agent-refinements.ts +4 -4
- package/src/agents/agent-serializer.ts +2 -0
- package/src/agents/agents.ts +242 -49
- package/src/agents/builtin-names.ts +6 -0
- package/src/agents/runtime-agent-registry.ts +8 -2
- package/src/api/preflight.ts +22 -3
- package/src/extension/config.ts +70 -0
- package/src/extension/doctor.ts +3 -3
- package/src/extension/index.ts +18 -4
- package/src/extension/public-execution.ts +29 -13
- package/src/extension/rpc.ts +20 -5
- package/src/extension/schemas.ts +9 -3
- package/src/extension/tool-description.ts +11 -9
- package/src/inspectors/herdr/actions.ts +2 -1
- package/src/inspectors/herdr/inspector-runner.ts +2 -10
- package/src/inspectors/herdr/session-roots-codec.ts +42 -0
- package/src/integrations/herdr-status.ts +46 -3
- package/src/runs/background/active-async-capacity.ts +77 -10
- package/src/runs/background/async-execution.ts +148 -27
- package/src/runs/background/async-job-tracker.ts +5 -0
- package/src/runs/background/async-resume.ts +3 -0
- package/src/runs/background/async-retention.ts +20 -3
- package/src/runs/background/async-status.ts +5 -0
- package/src/runs/background/chain-root-attachment.ts +15 -1
- package/src/runs/background/fleet-view.ts +16 -10
- package/src/runs/background/inspect-rpc.ts +8 -8
- package/src/runs/background/result-files.ts +27 -14
- package/src/runs/background/result-watcher.ts +9 -5
- package/src/runs/background/run-status.ts +33 -6
- package/src/runs/background/scheduled-runs.ts +7 -1
- package/src/runs/background/subagent-runner.ts +254 -49
- package/src/runs/background/wait-completions.ts +4 -0
- package/src/runs/foreground/execution.ts +107 -16
- package/src/runs/foreground/foreground-control.ts +6 -0
- package/src/runs/foreground/foreground-history.ts +25 -3
- package/src/runs/foreground/subagent-executor.ts +324 -84
- package/src/runs/foreground/workflow-detach-reconcile.ts +31 -7
- package/src/runs/shared/acceptance.ts +10 -5
- package/src/runs/shared/agent-contract.ts +1 -1
- package/src/runs/shared/child-protocol.ts +21 -7
- package/src/runs/shared/claude-code-adapter.ts +129 -0
- package/src/runs/shared/codex-exec-adapter.ts +129 -0
- package/src/runs/shared/completion-guard.ts +3 -2
- package/src/runs/shared/cursor-agent-adapter.ts +114 -0
- package/src/runs/shared/dynamic-fanout.ts +1 -1
- package/src/runs/shared/extension-bindings.ts +78 -0
- package/src/runs/shared/external-cli-contract.ts +167 -0
- package/src/runs/shared/external-cli-preflight.ts +122 -0
- package/src/runs/shared/external-cli-runner.ts +349 -54
- package/src/runs/shared/fast-mode-extension.ts +10 -0
- package/src/runs/shared/model-exclusions.ts +59 -8
- package/src/runs/shared/model-fallback.ts +25 -8
- package/src/runs/shared/mutation-evidence.ts +150 -0
- package/src/runs/shared/nested-events.ts +3 -1
- package/src/runs/shared/nested-render.ts +2 -2
- package/src/runs/shared/parallel-utils.ts +3 -0
- package/src/runs/shared/pi-args.ts +47 -0
- package/src/runs/shared/process-signal.ts +13 -0
- package/src/runs/shared/run-history.ts +21 -1
- package/src/runs/shared/structured-output.ts +18 -4
- package/src/runs/shared/subagent-prompt-runtime.ts +9 -5
- package/src/shared/fork-context.ts +21 -0
- package/src/shared/formatters.ts +7 -1
- package/src/shared/launch-contract.ts +6 -0
- package/src/shared/pruned-fork.ts +450 -0
- package/src/shared/session-file-trust.ts +19 -0
- package/src/shared/session-tokens.ts +14 -3
- package/src/shared/settings.ts +16 -3
- package/src/shared/shortcuts.ts +17 -0
- package/src/shared/types.ts +166 -9
- package/src/shared/workflow-child-permit.ts +116 -0
- package/src/slash/delegation-adapters.ts +0 -1
- package/src/slash/slash-commands.ts +8 -6
- package/src/tui/fleet-status.ts +27 -10
- package/src/tui/fleet-transcript.ts +11 -5
- package/src/tui/fleet.ts +28 -13
- package/src/tui/render.ts +47 -21
- package/src/workflows/scripted-workflow.ts +298 -30
- package/src/workflows/workflow-child-summary.ts +117 -0
- package/src/workflows/workflow-receipt.ts +119 -4
|
@@ -3,6 +3,7 @@ import type { ArtifactPaths, SubagentState, WaitCompletion, WaitCompletionChild
|
|
|
3
3
|
import type { AsyncRunSummary } from "./async-status.ts";
|
|
4
4
|
import { readCompletionReplay, writeCompletionReplay } from "./completion-replay.ts";
|
|
5
5
|
import { fallbackResultPayloadPathForSessionRun, resultFilePath, resultPayloadPathForSessionRun } from "./result-files.ts";
|
|
6
|
+
import { parseWorkflowChildSummary } from "../../workflows/workflow-child-summary.ts";
|
|
6
7
|
|
|
7
8
|
function asNonEmptyString(value: unknown): string | undefined {
|
|
8
9
|
return typeof value === "string" && value ? value : undefined;
|
|
@@ -60,6 +61,8 @@ export function toWaitCompletion(data: Record<string, unknown>, runId: string):
|
|
|
60
61
|
const agent = asNonEmptyString(data.agent);
|
|
61
62
|
const mode = asNonEmptyString(data.mode);
|
|
62
63
|
const state = asNonEmptyString(data.state);
|
|
64
|
+
const workflowChildren = parseWorkflowChildSummary(data.workflowChildren);
|
|
65
|
+
if (workflowChildren && workflowChildren.workflowRunId !== runId) throw new Error("workflowChildren.workflowRunId does not match its completion run id.");
|
|
63
66
|
return {
|
|
64
67
|
runId,
|
|
65
68
|
...(agent ? { agent } : {}),
|
|
@@ -67,6 +70,7 @@ export function toWaitCompletion(data: Record<string, unknown>, runId: string):
|
|
|
67
70
|
...(state ? { state } : {}),
|
|
68
71
|
...(typeof data.success === "boolean" ? { success: data.success } : {}),
|
|
69
72
|
...(results && results.length > 0 ? { results } : {}),
|
|
73
|
+
...(workflowChildren ? { workflowChildren } : {}),
|
|
70
74
|
};
|
|
71
75
|
}
|
|
72
76
|
|
|
@@ -6,7 +6,7 @@ import { spawn } from "node:child_process";
|
|
|
6
6
|
import { existsSync, unlinkSync } from "node:fs";
|
|
7
7
|
import * as path from "node:path";
|
|
8
8
|
import type { Message } from "@earendil-works/pi-ai";
|
|
9
|
-
import type
|
|
9
|
+
import { discoverAgents, formatUnknownAgentError, unknownAgentDiagnosticContext, type AgentConfig } from "../../agents/agents.ts";
|
|
10
10
|
import { appendAgentRefinementOverlay } from "../../agents/agent-refinements.ts";
|
|
11
11
|
import { alignForkedSessionCwd } from "../../shared/fork-context.ts";
|
|
12
12
|
import {
|
|
@@ -67,16 +67,17 @@ import { readRuntimeAcknowledgedExtensions } from "../shared/runtime-acknowledge
|
|
|
67
67
|
import { assertAgentAllowedByCapabilityCeiling, decodeSubagentCapabilityCeiling, intersectSubagentCapabilityCeilings, resolveCurrentSubagentCapabilityCeiling, SUBAGENT_CAPABILITY_CEILING_ENV } from "../shared/capability-ceiling.ts";
|
|
68
68
|
import { resolveEffectiveThinking } from "../../shared/model-info.ts";
|
|
69
69
|
import { assertThinkingWithinCeiling, decodeThinkingCeiling, intersectThinkingCeilings, SUBAGENT_THINKING_CEILING_ENV } from "../../shared/thinking-ceiling.ts";
|
|
70
|
-
import { MISSING_STRUCTURED_OUTPUT_CALL_ERROR, readStructuredOutput } from "../shared/structured-output.ts";
|
|
71
|
-
import { formatProcessSignalError, isUnexplainedProcessSignal } from "../shared/process-signal.ts";
|
|
70
|
+
import { MISSING_STRUCTURED_OUTPUT_CALL_ERROR, readStructuredOutput, readStructuredOutputAcceptanceReport } from "../shared/structured-output.ts";
|
|
71
|
+
import { formatMidToolExitError, formatProcessSignalError, isOrdinaryToolForMidToolExit, isUnexplainedProcessSignal } from "../shared/process-signal.ts";
|
|
72
72
|
import { readChildToolDiagnosticError } from "../shared/tool-availability.ts";
|
|
73
|
+
import { buildTimeoutRecoverySummary, collectTrackedMutationEvidence, snapshotTrackedMutations } from "../shared/mutation-evidence.ts";
|
|
73
74
|
import { captureSingleOutputSnapshot, extractChildWrittenOutput, finalizeSingleOutput, formatSavedOutputReference, injectOutputPathSystemPrompt, resolveSingleOutput, validateFileOnlyOutputMode, type SingleOutputSnapshot } from "../shared/single-output.ts";
|
|
74
75
|
import {
|
|
75
76
|
buildModelCandidates,
|
|
76
77
|
formatSubagentModelVerificationError,
|
|
77
78
|
formatModelAttemptNote,
|
|
78
79
|
isContextOverflow,
|
|
79
|
-
|
|
80
|
+
isRetryableModelFailureAttempt,
|
|
80
81
|
recordRetryableModelFailure,
|
|
81
82
|
} from "../shared/model-fallback.ts";
|
|
82
83
|
import {
|
|
@@ -105,7 +106,8 @@ import { appendTurnBudgetSystemPrompt, formatTurnBudgetOutput, initialTurnBudget
|
|
|
105
106
|
import { initialToolBudgetState, toolBudgetState } from "../shared/tool-budget.ts";
|
|
106
107
|
import { resolveWatchdogConfig } from "../../watchdog/settings.ts";
|
|
107
108
|
import { agentDefinitionDigest, launchBindingDigest } from "../../shared/launch-contract.ts";
|
|
108
|
-
import {
|
|
109
|
+
import { consumeWorkflowChildPermit } from "../../shared/workflow-child-permit.ts";
|
|
110
|
+
import { createBoundedByteTail, createBoundedLineReader, formatProtocolOutputLimit, MAX_CHILD_STDERR_BYTES, PI_AGGREGATE_EVENT_PROJECTOR, projectChildLifecycle, type ChildLifecycleAction, type ChildLifecycleState, type ProtocolOutputLimit } from "../shared/child-protocol.ts";
|
|
109
111
|
import {
|
|
110
112
|
acceptChildWatchdogEvent,
|
|
111
113
|
childWatchdogIsActive,
|
|
@@ -368,6 +370,8 @@ async function runSingleAttempt(
|
|
|
368
370
|
steerCapabilityPath: options.steerCapabilityPath,
|
|
369
371
|
steerAckDir: options.steerAckDir,
|
|
370
372
|
structuredOutput: options.structuredOutput,
|
|
373
|
+
fast: options.fast ?? agent.fast,
|
|
374
|
+
modelCandidates: shared.modelCandidates,
|
|
371
375
|
toolBudget: options.toolBudget,
|
|
372
376
|
allowZeroToolBudget: options.allowZeroToolBudget,
|
|
373
377
|
permissionRules,
|
|
@@ -376,6 +380,7 @@ async function runSingleAttempt(
|
|
|
376
380
|
waitToolEnabled: options.waitToolEnabled,
|
|
377
381
|
capabilityCeiling: options.capabilityCeiling,
|
|
378
382
|
thinkingCeiling: options.thinkingCeiling,
|
|
383
|
+
extensionBindings: options.extensionBindings,
|
|
379
384
|
});
|
|
380
385
|
if (!shared.launchWarnings.emitted && warnings.length > 0) {
|
|
381
386
|
for (const warning of warnings) console.warn(`[pi-subagents] ${warning}`);
|
|
@@ -391,6 +396,9 @@ async function runSingleAttempt(
|
|
|
391
396
|
cwd: options.cwd ?? runtimeCwd,
|
|
392
397
|
requireReadTool: Boolean(shared.resolvedSkillNames?.length),
|
|
393
398
|
structuredOutput: Boolean(options.structuredOutput),
|
|
399
|
+
fast: options.fast ?? agent.fast,
|
|
400
|
+
model: modelArg,
|
|
401
|
+
modelCandidates: shared.modelCandidates,
|
|
394
402
|
capabilityCeiling: options.capabilityCeiling,
|
|
395
403
|
inheritedCapabilityCeiling: decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV]),
|
|
396
404
|
agentName: agent.name,
|
|
@@ -432,6 +440,7 @@ async function runSingleAttempt(
|
|
|
432
440
|
task: shared.originalTask ?? task,
|
|
433
441
|
...(modelArg ? { model: modelArg } : {}),
|
|
434
442
|
modelCandidates: shared.modelCandidates,
|
|
443
|
+
...((options.fast ?? agent.fast) !== undefined ? { fast: options.fast ?? agent.fast } : {}),
|
|
435
444
|
...(resolvedThinking ? { thinking: resolvedThinking } : {}),
|
|
436
445
|
...(options.thinkingCeiling ? { thinkingCeiling: options.thinkingCeiling } : {}),
|
|
437
446
|
systemPrompt: effectiveSystemPrompt,
|
|
@@ -445,6 +454,7 @@ async function runSingleAttempt(
|
|
|
445
454
|
...(options.outputPath ? { outputPath: options.outputPath } : {}),
|
|
446
455
|
outputMode: options.outputMode ?? "inline",
|
|
447
456
|
...(options.structuredOutput ? { structuredOutputSchema: options.structuredOutput.schema } : {}),
|
|
457
|
+
...(options.extensionBindings ? { extensionBindings: options.extensionBindings } : {}),
|
|
448
458
|
});
|
|
449
459
|
const result: SingleResult = withRunContext({
|
|
450
460
|
index: options.index ?? 0,
|
|
@@ -524,13 +534,34 @@ async function runSingleAttempt(
|
|
|
524
534
|
return result;
|
|
525
535
|
}
|
|
526
536
|
const spawnEnv = { ...process.env, ...sharedEnv, ...getSubagentDepthEnv(options.maxSubagentDepth) };
|
|
537
|
+
const mutationSnapshot = snapshotTrackedMutations(options.cwd ?? runtimeCwd);
|
|
527
538
|
let observedMutationAttempt = false;
|
|
528
539
|
let structuredOutputToolInvoked = false;
|
|
529
540
|
let structuredOutputMessageStartIndex: number | undefined;
|
|
530
541
|
let toolAvailabilityError: string | undefined;
|
|
542
|
+
let abortedBySignal = options.signal?.aborted === true;
|
|
531
543
|
|
|
544
|
+
const spawnSpec = getPiSpawnCommand(args);
|
|
545
|
+
if (options.workflowChildPermitLaunch) {
|
|
546
|
+
const permitError = consumeWorkflowChildPermit(options.workflowChildPermitLaunch.permit, {
|
|
547
|
+
workflowRunId: options.workflowChildPermitLaunch.workflowRunId,
|
|
548
|
+
childKey: options.workflowChildPermitLaunch.childKey,
|
|
549
|
+
agent: agent.name,
|
|
550
|
+
launchContractDigest,
|
|
551
|
+
context: options.context ?? "fresh",
|
|
552
|
+
runner: "pi",
|
|
553
|
+
});
|
|
554
|
+
if (permitError) {
|
|
555
|
+
cleanupTempDir(tempDir);
|
|
556
|
+
result.exitCode = 1;
|
|
557
|
+
result.error = permitError;
|
|
558
|
+
result.finalOutput = permitError;
|
|
559
|
+
progress.status = "failed";
|
|
560
|
+
progress.error = permitError;
|
|
561
|
+
return result;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
532
564
|
const exitCode = await new Promise<number>((resolve) => {
|
|
533
|
-
const spawnSpec = getPiSpawnCommand(args);
|
|
534
565
|
const proc = spawn(spawnSpec.command, spawnSpec.args, {
|
|
535
566
|
cwd: options.cwd ?? runtimeCwd,
|
|
536
567
|
env: spawnEnv,
|
|
@@ -697,6 +728,7 @@ async function runSingleAttempt(
|
|
|
697
728
|
}
|
|
698
729
|
if (action === "start-drain") startFinalDrain();
|
|
699
730
|
};
|
|
731
|
+
const childLifecycleState: ChildLifecycleState = { compactionRetryActive: false };
|
|
700
732
|
|
|
701
733
|
const unsubscribeIntercomDetach = options.intercomEvents?.on?.(INTERCOM_DETACH_REQUEST_EVENT, (payload) => {
|
|
702
734
|
if (!options.allowIntercomDetach || processClosed) return;
|
|
@@ -984,8 +1016,9 @@ async function runSingleAttempt(
|
|
|
984
1016
|
}
|
|
985
1017
|
shared.transcriptWriter?.writeChildEvent(evt);
|
|
986
1018
|
shared.orcaProgressTab?.event(evt);
|
|
987
|
-
|
|
988
|
-
|
|
1019
|
+
const lifecycleAction = projectChildLifecycle(evt, false, childLifecycleState);
|
|
1020
|
+
if (evt.type === "agent_settled" && lifecycleAction === "start-drain") agentSettledReceived = true;
|
|
1021
|
+
applyChildLifecycle(lifecycleAction);
|
|
989
1022
|
|
|
990
1023
|
if (isChildWatchdogStatusEvent(evt)) {
|
|
991
1024
|
if (!childWatchdog) return;
|
|
@@ -1068,6 +1101,7 @@ async function runSingleAttempt(
|
|
|
1068
1101
|
updateTurnBudget(result.usage.turns, terminalAssistantStop || terminalStructuredOutputCall, hasToolCall || Boolean(progress.currentTool));
|
|
1069
1102
|
const u = evt.message.usage;
|
|
1070
1103
|
if (u) {
|
|
1104
|
+
const window = (u.input || 0) + (u.cacheRead || 0);
|
|
1071
1105
|
result.usage.input += u.input || 0;
|
|
1072
1106
|
result.usage.output += u.output || 0;
|
|
1073
1107
|
result.usage.cacheRead += u.cacheRead || 0;
|
|
@@ -1076,6 +1110,8 @@ async function runSingleAttempt(
|
|
|
1076
1110
|
progress.tokens = result.usage.input + result.usage.output;
|
|
1077
1111
|
progress.inputTokens = result.usage.input;
|
|
1078
1112
|
progress.outputTokens = result.usage.output;
|
|
1113
|
+
progress.window = window;
|
|
1114
|
+
progress.windowPeak = Math.max(progress.windowPeak ?? 0, window);
|
|
1079
1115
|
}
|
|
1080
1116
|
if (evt.message.model) {
|
|
1081
1117
|
progress.model = evt.message.model;
|
|
@@ -1092,7 +1128,11 @@ async function runSingleAttempt(
|
|
|
1092
1128
|
if (terminalAssistantStop) {
|
|
1093
1129
|
if (!evt.message.errorMessage && assistantText.trim()) assistantError = undefined;
|
|
1094
1130
|
cleanTerminalAssistantStopReceived ||= !evt.message.errorMessage;
|
|
1095
|
-
|
|
1131
|
+
clearAllToolTimeouts();
|
|
1132
|
+
activeToolCalls.clear();
|
|
1133
|
+
activeToolKeysByName.clear();
|
|
1134
|
+
refreshCurrentTool();
|
|
1135
|
+
applyChildLifecycle(projectChildLifecycle(evt, true, childLifecycleState));
|
|
1096
1136
|
}
|
|
1097
1137
|
}
|
|
1098
1138
|
updateActivityState(now);
|
|
@@ -1359,6 +1399,7 @@ async function runSingleAttempt(
|
|
|
1359
1399
|
if (options.signal) {
|
|
1360
1400
|
const kill = () => {
|
|
1361
1401
|
if (processClosed || lifecycleFinished) return;
|
|
1402
|
+
abortedBySignal = true;
|
|
1362
1403
|
proc.kill("SIGTERM");
|
|
1363
1404
|
setTimeout(() => !proc.killed && proc.kill("SIGKILL"), 3000);
|
|
1364
1405
|
};
|
|
@@ -1421,6 +1462,22 @@ async function runSingleAttempt(
|
|
|
1421
1462
|
};
|
|
1422
1463
|
return result;
|
|
1423
1464
|
}
|
|
1465
|
+
if (progress.currentTool
|
|
1466
|
+
&& isOrdinaryToolForMidToolExit(progress.currentTool)
|
|
1467
|
+
&& !abortedBySignal
|
|
1468
|
+
&& !result.timedOut
|
|
1469
|
+
&& !result.stopped
|
|
1470
|
+
&& !result.turnBudgetExceeded
|
|
1471
|
+
&& !result.protocolError
|
|
1472
|
+
&& !toolAvailabilityError) {
|
|
1473
|
+
const processExitCode = result.exitCode;
|
|
1474
|
+
result.exitCode = 1;
|
|
1475
|
+
result.error = formatMidToolExitError({
|
|
1476
|
+
toolName: progress.currentTool,
|
|
1477
|
+
exitCode: processExitCode,
|
|
1478
|
+
processSignal: result.processSignal,
|
|
1479
|
+
});
|
|
1480
|
+
}
|
|
1424
1481
|
result.error = formatSubagentExtensionConflictError(result.error, {
|
|
1425
1482
|
agent: agent.name,
|
|
1426
1483
|
ambientExtensionsEnabled: !launchResolvedExtensions.disableAmbientExtensions,
|
|
@@ -1448,6 +1505,9 @@ async function runSingleAttempt(
|
|
|
1448
1505
|
result.structuredOutputFailed = true;
|
|
1449
1506
|
} else {
|
|
1450
1507
|
result.structuredOutput = structured.value;
|
|
1508
|
+
const acceptanceReport = readStructuredOutputAcceptanceReport(options.structuredOutput);
|
|
1509
|
+
(result as SingleResult & { structuredAcceptanceReport?: unknown; structuredAcceptanceReportError?: string }).structuredAcceptanceReport = acceptanceReport.value;
|
|
1510
|
+
(result as SingleResult & { structuredAcceptanceReport?: unknown; structuredAcceptanceReportError?: string }).structuredAcceptanceReportError = acceptanceReport.error;
|
|
1451
1511
|
validatedStructuredOutput = true;
|
|
1452
1512
|
}
|
|
1453
1513
|
}
|
|
@@ -1485,6 +1545,7 @@ async function runSingleAttempt(
|
|
|
1485
1545
|
tokens: progress.tokens,
|
|
1486
1546
|
durationMs: progress.durationMs,
|
|
1487
1547
|
};
|
|
1548
|
+
const mutationEvidence = collectTrackedMutationEvidence(mutationSnapshot, options.cwd ?? runtimeCwd);
|
|
1488
1549
|
|
|
1489
1550
|
const acceptanceOutput = getFinalOutput(result.messages ?? []);
|
|
1490
1551
|
let fullOutput = stripAcceptanceReport(acceptanceOutput);
|
|
@@ -1492,9 +1553,19 @@ async function runSingleAttempt(
|
|
|
1492
1553
|
result.outputState = fullOutput.trim() || result.structuredOutput !== undefined ? "present" : "absent";
|
|
1493
1554
|
if (result.timedOut) {
|
|
1494
1555
|
const timeoutMessage = formatTimeoutMessage(options.timeoutMs ?? 0);
|
|
1556
|
+
result.timeoutRecovery = buildTimeoutRecoverySummary({
|
|
1557
|
+
termination: "timed-out",
|
|
1558
|
+
evidence: mutationEvidence,
|
|
1559
|
+
currentTool: progress.currentTool,
|
|
1560
|
+
currentToolArgs: progress.currentToolArgs,
|
|
1561
|
+
currentPath: progress.currentPath,
|
|
1562
|
+
sessionFile: options.sessionFile,
|
|
1563
|
+
transcriptPath: shared.transcriptWriter ? shared.artifactPaths?.transcriptPath : undefined,
|
|
1564
|
+
artifactPaths: shared.artifactPaths,
|
|
1565
|
+
});
|
|
1495
1566
|
fullOutput = fullOutput.trim()
|
|
1496
|
-
? `${timeoutMessage}\n\nPartial output before timeout:\n${fullOutput}`
|
|
1497
|
-
: timeoutMessage
|
|
1567
|
+
? `${timeoutMessage}\n\n${result.timeoutRecovery.message}\n\nPartial output before timeout:\n${fullOutput}`
|
|
1568
|
+
: `${timeoutMessage}\n\n${result.timeoutRecovery.message}`;
|
|
1498
1569
|
} else if (result.turnBudgetExceeded && result.turnBudget) {
|
|
1499
1570
|
fullOutput = formatTurnBudgetOutput(turnBudgetExceededMessage(result.turnBudget, result.turnBudget.turnCount), fullOutput);
|
|
1500
1571
|
} else if (result.turnBudget?.outcome === "termination-deferred") {
|
|
@@ -1513,9 +1584,11 @@ async function runSingleAttempt(
|
|
|
1513
1584
|
tools: contractTools,
|
|
1514
1585
|
mcpDirectTools: toolPlan.effectiveMcpTools,
|
|
1515
1586
|
toolAvailabilityError,
|
|
1587
|
+
mutationEvidence,
|
|
1516
1588
|
})
|
|
1517
1589
|
: undefined;
|
|
1518
|
-
|
|
1590
|
+
const mutationAttemptObserved = observedMutationAttempt || mutationEvidence.attemptedMutation;
|
|
1591
|
+
let completionGuardTriggered = completionGuard?.triggered === true && !mutationAttemptObserved;
|
|
1519
1592
|
const completionGuardBlocked = completionGuard?.blocked === true;
|
|
1520
1593
|
// The classifier is deliberately narrow, so a read-only review task can
|
|
1521
1594
|
// still be misread as implementation. Arbitrate BEFORE any failure side
|
|
@@ -1547,7 +1620,8 @@ async function runSingleAttempt(
|
|
|
1547
1620
|
: "observed"
|
|
1548
1621
|
: "not-applicable",
|
|
1549
1622
|
expected: completionGuard.expectedMutation,
|
|
1550
|
-
attempted: completionGuardBlocked ? false : completionGuard.attemptedMutation ||
|
|
1623
|
+
attempted: completionGuardBlocked ? false : completionGuard.attemptedMutation || mutationAttemptObserved,
|
|
1624
|
+
evidence: mutationEvidence,
|
|
1551
1625
|
...(completionGuardBlocked && completionGuard.message ? { message: completionGuard.message } : {}),
|
|
1552
1626
|
...(completionGuardTriggered ? { message: "Subagent completed without making edits for an implementation task." } : {}),
|
|
1553
1627
|
...(arbiterRescued ? { resolvedBy: "llm-intent-arbiter" } : {}),
|
|
@@ -1620,6 +1694,8 @@ async function runSyncCompletionInner(
|
|
|
1620
1694
|
};
|
|
1621
1695
|
const agent = agents.find((a) => a.name === agentName);
|
|
1622
1696
|
if (!agent) {
|
|
1697
|
+
const diagnosticContext = options.unknownAgentDiagnosticContext
|
|
1698
|
+
?? unknownAgentDiagnosticContext(discoverAgents(path.resolve(options.cwd ?? runtimeCwd), "both"));
|
|
1623
1699
|
return redactResultPrompt(withRunContext({
|
|
1624
1700
|
index: options.index ?? 0,
|
|
1625
1701
|
agent: agentName,
|
|
@@ -1627,7 +1703,7 @@ async function runSyncCompletionInner(
|
|
|
1627
1703
|
exitCode: 1,
|
|
1628
1704
|
messages: [],
|
|
1629
1705
|
usage: emptyUsage(),
|
|
1630
|
-
error:
|
|
1706
|
+
error: formatUnknownAgentError(agentName, diagnosticContext),
|
|
1631
1707
|
}, options.context));
|
|
1632
1708
|
}
|
|
1633
1709
|
options = {
|
|
@@ -1708,7 +1784,7 @@ async function runSyncCompletionInner(
|
|
|
1708
1784
|
dynamicGroup: options.acceptanceContext?.dynamicGroup,
|
|
1709
1785
|
agentContract: options.agentContract,
|
|
1710
1786
|
});
|
|
1711
|
-
const acceptancePrompt = formatAcceptancePrompt(effectiveAcceptance, { reportOptional: isAgentContractV1(options.agentContract) });
|
|
1787
|
+
const acceptancePrompt = formatAcceptancePrompt(effectiveAcceptance, { reportOptional: isAgentContractV1(options.agentContract), structuredOutput: Boolean(options.structuredOutput?.acceptanceReportPath) });
|
|
1712
1788
|
const taskWithAcceptance = acceptancePrompt ? `${task}\n${acceptancePrompt}` : task;
|
|
1713
1789
|
options.onEffectivePrompt?.(taskWithAcceptance);
|
|
1714
1790
|
const sessionEnabled = Boolean(options.sessionFile || options.sessionDir) || shareEnabled;
|
|
@@ -1754,6 +1830,18 @@ async function runSyncCompletionInner(
|
|
|
1754
1830
|
agent.modelProvider ?? options.preferredModelProvider,
|
|
1755
1831
|
{ scope: options.modelScope, primaryModelFromParent: options.modelOverrideFromParent },
|
|
1756
1832
|
);
|
|
1833
|
+
if (options.workflowChildPermitLaunch && candidates.length > 1) {
|
|
1834
|
+
const error = "Workflow child permit does not support model fallback.";
|
|
1835
|
+
return redactResultPrompt(withRunContext({
|
|
1836
|
+
index: options.index ?? 0,
|
|
1837
|
+
agent: agent.name,
|
|
1838
|
+
task,
|
|
1839
|
+
exitCode: 1,
|
|
1840
|
+
messages: [],
|
|
1841
|
+
usage: emptyUsage(),
|
|
1842
|
+
error,
|
|
1843
|
+
}, options.context));
|
|
1844
|
+
}
|
|
1757
1845
|
try {
|
|
1758
1846
|
for (const candidate of candidates) {
|
|
1759
1847
|
const model = applyThinkingSuffix(candidate, options.thinkingOverride ?? agent.thinking, options.thinkingOverride !== undefined);
|
|
@@ -1887,6 +1975,7 @@ async function runSyncCompletionInner(
|
|
|
1887
1975
|
usage: { ...result.usage },
|
|
1888
1976
|
};
|
|
1889
1977
|
modelAttempts.push(attempt);
|
|
1978
|
+
if (options.workflowChildPermitLaunch && !attemptSucceeded) break modelAttemptsLoop;
|
|
1890
1979
|
// Preserve the legacy intercom handoff contract: once this logical run has
|
|
1891
1980
|
// been handed to a supervisor, terminating that attempt must not launch a
|
|
1892
1981
|
// startup retry or model fallback. Explicit user detach retains fallback.
|
|
@@ -1959,7 +2048,7 @@ async function runSyncCompletionInner(
|
|
|
1959
2048
|
attempt.error = startupError;
|
|
1960
2049
|
break modelAttemptsLoop;
|
|
1961
2050
|
}
|
|
1962
|
-
const retryableModelFailure =
|
|
2051
|
+
const retryableModelFailure = isRetryableModelFailureAttempt({ error: result.error, messages: result.messages, toolCount: result.progressSummary?.toolCount });
|
|
1963
2052
|
if (retryableModelFailure) recordRetryableModelFailure(result.model ?? candidate, result.error);
|
|
1964
2053
|
if (isContextOverflow(result.error)) {
|
|
1965
2054
|
result.contextOverflow = true;
|
|
@@ -2045,6 +2134,8 @@ async function runSyncCompletionInner(
|
|
|
2045
2134
|
result.acceptance = await evaluateAcceptance({
|
|
2046
2135
|
acceptance: effectiveAcceptance,
|
|
2047
2136
|
output: acceptanceOutputByResult.get(result) ?? result.finalOutput ?? "",
|
|
2137
|
+
report: (result as SingleResult & { structuredAcceptanceReport?: import("../../shared/types.ts").AcceptanceReport; structuredAcceptanceReportError?: string }).structuredAcceptanceReport,
|
|
2138
|
+
reportError: (result as SingleResult & { structuredAcceptanceReport?: import("../../shared/types.ts").AcceptanceReport; structuredAcceptanceReportError?: string }).structuredAcceptanceReportError,
|
|
2048
2139
|
fileOutput: childWrittenOutput !== undefined && options.outputPath
|
|
2049
2140
|
? { content: childWrittenOutput, path: options.outputPath, authoritative: options.outputMode === "file-only" }
|
|
2050
2141
|
: undefined,
|
|
@@ -27,6 +27,8 @@ function copyProgress(target: ForegroundChildControl, progress: AgentProgress |
|
|
|
27
27
|
target.tokens = progress.tokens;
|
|
28
28
|
target.inputTokens = progress.inputTokens;
|
|
29
29
|
target.outputTokens = progress.outputTokens;
|
|
30
|
+
target.window = progress.window;
|
|
31
|
+
target.windowPeak = progress.windowPeak;
|
|
30
32
|
target.model = progress.model;
|
|
31
33
|
target.thinking = progress.thinking;
|
|
32
34
|
target.toolCount = progress.toolCount;
|
|
@@ -45,6 +47,8 @@ function syncCurrentChild(control: ForegroundRunControl, child: ForegroundChildC
|
|
|
45
47
|
control.tokens = child.tokens;
|
|
46
48
|
control.inputTokens = child.inputTokens;
|
|
47
49
|
control.outputTokens = child.outputTokens;
|
|
50
|
+
control.window = child.window;
|
|
51
|
+
control.windowPeak = child.windowPeak;
|
|
48
52
|
control.model = child.model;
|
|
49
53
|
control.thinking = child.thinking;
|
|
50
54
|
control.toolCount = child.toolCount;
|
|
@@ -65,6 +69,8 @@ function clearCurrentChild(control: ForegroundRunControl): void {
|
|
|
65
69
|
control.tokens = undefined;
|
|
66
70
|
control.inputTokens = undefined;
|
|
67
71
|
control.outputTokens = undefined;
|
|
72
|
+
control.window = undefined;
|
|
73
|
+
control.windowPeak = undefined;
|
|
68
74
|
control.model = undefined;
|
|
69
75
|
control.thinking = undefined;
|
|
70
76
|
control.toolCount = undefined;
|
|
@@ -2,12 +2,14 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import type { ForegroundResumeChild, ForegroundResumeRun, SubagentState } from "../../shared/types.ts";
|
|
4
4
|
import { DIRS } from "../../shared/types.ts";
|
|
5
|
-
import {
|
|
5
|
+
import { writePrivateAtomicJson } from "../../shared/atomic-json.ts";
|
|
6
6
|
import { utf8Tail } from "../../shared/utf8.ts";
|
|
7
|
+
import { validateAcceptanceInput } from "../shared/acceptance.ts";
|
|
7
8
|
|
|
8
9
|
export const MAX_REMEMBERED_FOREGROUND_RUNS = 50;
|
|
9
10
|
const HISTORY_VERSION = 1;
|
|
10
11
|
const MAX_INLINE_OUTPUT_BYTES = 64 * 1024;
|
|
12
|
+
const MAX_RESUME_CONTRACT_BYTES = 64 * 1024;
|
|
11
13
|
|
|
12
14
|
interface ForegroundHistoryIndex {
|
|
13
15
|
version: 1;
|
|
@@ -51,7 +53,9 @@ function compactChild(child: ForegroundResumeChild): ForegroundResumeChild {
|
|
|
51
53
|
...(child.transcriptPath ? { transcriptPath: child.transcriptPath } : {}),
|
|
52
54
|
...(child.transcriptError ? { transcriptError: child.transcriptError } : {}),
|
|
53
55
|
...(child.acceptance ? { acceptance: child.acceptance } : {}),
|
|
56
|
+
...(child.resumeContract ? { resumeContract: child.resumeContract } : {}),
|
|
54
57
|
...(child.launchContractDigest ? { launchContractDigest: child.launchContractDigest } : {}),
|
|
58
|
+
...(child.extensionBindings ? { extensionBindings: child.extensionBindings } : {}),
|
|
55
59
|
...(child.capabilityCeiling ? { capabilityCeiling: child.capabilityCeiling } : {}),
|
|
56
60
|
...(child.updatedAt !== undefined ? { updatedAt: child.updatedAt } : {}),
|
|
57
61
|
};
|
|
@@ -61,9 +65,26 @@ function isRestorableForegroundStatus(status: unknown): status is ForegroundResu
|
|
|
61
65
|
return status === "completed" || status === "failed" || status === "paused" || status === "stopped";
|
|
62
66
|
}
|
|
63
67
|
|
|
68
|
+
function isRestorableResumeContract(value: unknown): boolean {
|
|
69
|
+
if (value === undefined) return true;
|
|
70
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
71
|
+
try {
|
|
72
|
+
if (Buffer.byteLength(JSON.stringify(value), "utf8") > MAX_RESUME_CONTRACT_BYTES) return false;
|
|
73
|
+
} catch {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
const contract = value as NonNullable<ForegroundResumeChild["resumeContract"]>;
|
|
77
|
+
if (Object.keys(contract).some((key) => !["outputSchema", "agentContract", "acceptance", "output", "outputMode"].includes(key))) return false;
|
|
78
|
+
if (contract.outputSchema !== undefined && (!contract.outputSchema || typeof contract.outputSchema !== "object" || Array.isArray(contract.outputSchema))) return false;
|
|
79
|
+
if (contract.agentContract !== undefined && (!contract.agentContract || typeof contract.agentContract !== "object" || Array.isArray(contract.agentContract) || contract.agentContract.version !== 1)) return false;
|
|
80
|
+
if (validateAcceptanceInput(contract.acceptance).length > 0) return false;
|
|
81
|
+
if (contract.output !== undefined && typeof contract.output !== "string" && typeof contract.output !== "boolean") return false;
|
|
82
|
+
return contract.outputMode === undefined || contract.outputMode === "inline" || contract.outputMode === "file-only";
|
|
83
|
+
}
|
|
84
|
+
|
|
64
85
|
function compactRun(run: ForegroundResumeRun): ForegroundResumeRun | undefined {
|
|
65
86
|
if (!run.sessionId) return undefined;
|
|
66
|
-
if (run.children.length === 0 || !run.children.every((child) => isRestorableForegroundStatus(child.status))) return undefined;
|
|
87
|
+
if (run.children.length === 0 || !run.children.every((child) => isRestorableForegroundStatus(child.status) && isRestorableResumeContract(child.resumeContract))) return undefined;
|
|
67
88
|
return {
|
|
68
89
|
runId: run.runId,
|
|
69
90
|
mode: run.mode,
|
|
@@ -101,6 +122,7 @@ function isRestorableRun(value: unknown): value is ForegroundResumeRun {
|
|
|
101
122
|
&& run.children.every((child) => Boolean(child && typeof child === "object" && !Array.isArray(child)
|
|
102
123
|
&& typeof (child as Partial<ForegroundResumeChild>).agent === "string"
|
|
103
124
|
&& typeof (child as Partial<ForegroundResumeChild>).index === "number"
|
|
125
|
+
&& isRestorableResumeContract((child as Partial<ForegroundResumeChild>).resumeContract)
|
|
104
126
|
&& isRestorableForegroundStatus((child as Partial<ForegroundResumeChild>).status)));
|
|
105
127
|
}
|
|
106
128
|
|
|
@@ -118,7 +140,7 @@ export function persistForegroundRunHistory(state: SubagentState, options: { res
|
|
|
118
140
|
if (compact) merged.set(compact.runId, compact);
|
|
119
141
|
}
|
|
120
142
|
const runs = sortAndBound([...merged.values()], limit);
|
|
121
|
-
|
|
143
|
+
writePrivateAtomicJson(historyPath(resultsDir), { version: HISTORY_VERSION, runs });
|
|
122
144
|
}
|
|
123
145
|
|
|
124
146
|
export function restoreForegroundRunHistory(state: SubagentState, options: { resultsDir?: string; sessionId?: string | null; limit?: number } = {}): number {
|