pi-subagents 0.54.0 → 0.55.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 +35 -1
- package/agents/reviewer.md +12 -2
- package/docs/agents.md +4 -4
- package/docs/configuration.md +4 -4
- package/docs/extension-api.md +14 -4
- package/docs/models.md +24 -3
- package/docs/observability.md +14 -3
- package/docs/tool-reference.md +21 -6
- package/docs/workflows.md +7 -1
- package/package.json +1 -1
- package/prompts/council.md +9 -0
- package/prompts/parallel-review.md +5 -1
- package/prompts/review-loop.md +9 -5
- package/skills/council-mode/SKILL.md +33 -10
- package/skills/pi-subagents/references/constraints-and-recipes.md +16 -1
- package/skills/pi-subagents/references/execution-controls.md +11 -4
- package/skills/pi-subagents/references/multi-lane-orchestration.md +3 -3
- package/skills/pi-subagents/references/prompting-and-roles.md +8 -8
- package/src/agents/agent-management.ts +30 -9
- package/src/agents/agent-serializer.ts +2 -2
- package/src/agents/agents.ts +146 -41
- package/src/api/external-job-provider.ts +10 -1
- package/src/api/preflight.ts +26 -17
- package/src/api/project-panes.ts +2 -0
- package/src/extension/index.ts +20 -1
- package/src/extension/public-execution.ts +12 -9
- package/src/extension/rpc.ts +77 -3
- package/src/extension/schemas.ts +2 -1
- package/src/extension/tool-description.ts +9 -6
- package/src/inspectors/herdr/focus.ts +55 -0
- package/src/inspectors/herdr/project-panes.ts +228 -44
- package/src/integrations/herdr-status.ts +26 -4
- package/src/runs/background/async-execution.ts +103 -9
- package/src/runs/background/async-job-tracker.ts +33 -14
- package/src/runs/background/async-resume.ts +20 -2
- package/src/runs/background/async-status.ts +10 -0
- package/src/runs/background/control-channel.ts +98 -10
- package/src/runs/background/notify.ts +62 -1
- package/src/runs/background/run-status.ts +15 -1
- package/src/runs/background/subagent-runner.ts +226 -37
- package/src/runs/foreground/async-stop-action.ts +23 -2
- package/src/runs/foreground/execution.ts +81 -7
- package/src/runs/foreground/subagent-executor.ts +348 -40
- package/src/runs/foreground/workflow-detach-reconcile.ts +3 -0
- package/src/runs/shared/acceptance.ts +1 -0
- package/src/runs/shared/child-identity.ts +36 -0
- package/src/runs/shared/completion-guard.ts +50 -1
- package/src/runs/shared/external-job-bridge.ts +53 -37
- package/src/runs/shared/external-job-runner.ts +126 -23
- package/src/runs/shared/model-fallback.ts +10 -0
- package/src/runs/shared/orca-progress-tabs.ts +71 -9
- package/src/runs/shared/parallel-utils.ts +12 -0
- package/src/runs/shared/pi-args.ts +9 -12
- package/src/runs/shared/subagent-prompt-runtime.ts +3 -1
- package/src/runs/shared/tool-availability.ts +1 -3
- package/src/shared/launch-contract.ts +6 -5
- package/src/shared/thinking-ceiling.ts +52 -0
- package/src/shared/types.ts +59 -2
- package/src/tui/fleet-status.ts +66 -13
- package/src/tui/render.ts +5 -4
- package/src/workflows/scripted-workflow.ts +107 -10
|
@@ -11,7 +11,7 @@ import { createCapacityResilientJsonWriter } from "../../shared/capacity-resilie
|
|
|
11
11
|
import { isStorageCapacityError } from "../../shared/file-system-retry.ts";
|
|
12
12
|
import { updateActiveRunIndex } from "./active-run-index.ts";
|
|
13
13
|
import { createChildTranscriptWriter, type ChildTranscriptWriter } from "../../shared/child-transcript.ts";
|
|
14
|
-
import { closeSteerInbox, consumeInterruptRequest, consumeSteerRequests, deliverInterruptRequest, deliverStopRequest, deliverTimeoutRequest, enqueueStepSteer, steerAcksDir, steerCapabilityPath, stepSteerInboxDir, watchAsyncControlInbox, type SteerAck, type SteerCapability, type SteerRequest } from "./control-channel.ts";
|
|
14
|
+
import { closeSteerInbox, consumeInterruptRequest, consumeSteerRequests, deliverInterruptRequest, deliverStopRequest, deliverTimeoutRequest, enqueueStepSteer, steerAcksDir, steerCapabilityPath, stepSteerInboxDir, watchAsyncControlInbox, type SteerAck, type SteerCapability, type SteerRequest, type StopRequest } from "./control-channel.ts";
|
|
15
15
|
import { appendJsonl as appendRawJsonl, formatOutputArtifactContent, getArtifactPaths, writeArtifact, writeMetadata } from "../../shared/artifacts.ts";
|
|
16
16
|
import { PI_CODING_AGENT_PACKAGE, getPiSpawnCommand, resolveInstalledPiPackageRoot } from "../shared/pi-spawn.ts";
|
|
17
17
|
import { captureSingleOutputSnapshot, extractChildWrittenOutput, finalizeSingleOutput, formatSavedOutputReference, injectOutputPathSystemPrompt, injectSingleOutputInstruction, resolveSingleOutput, type SingleOutputSnapshot } from "../shared/single-output.ts";
|
|
@@ -48,6 +48,7 @@ import {
|
|
|
48
48
|
type SteeringStatus,
|
|
49
49
|
type SteeringTargetState,
|
|
50
50
|
type SteeringTargetStatus,
|
|
51
|
+
type SubagentChildStatusEvent,
|
|
51
52
|
DEFAULT_MAX_OUTPUT,
|
|
52
53
|
type MaxOutputConfig,
|
|
53
54
|
SUBAGENT_LIFECYCLE_ARTIFACT_VERSION,
|
|
@@ -85,7 +86,7 @@ import { readChildToolDiagnosticError } from "../shared/tool-availability.ts";
|
|
|
85
86
|
import { collectDynamicResults, DynamicFanoutError, materializeDynamicParallelStep, validateDynamicCollection } from "../shared/dynamic-fanout.ts";
|
|
86
87
|
import { claimRunFanoutBatch, getRunFanoutBudgetSnapshot } from "../shared/run-fanout-budget.ts";
|
|
87
88
|
import { nestedSummaryFromAsyncStatus, projectNestedEvents, resolveNestedAsyncDir, writeNestedEvent } from "../shared/nested-events.ts";
|
|
88
|
-
import { formatModelAttemptNote, isContextOverflow, isRetryableModelFailure, recordRetryableModelFailure } from "../shared/model-fallback.ts";
|
|
89
|
+
import { formatModelAttemptNote, formatSubagentModelVerificationError, isContextOverflow, isRetryableModelFailure, recordRetryableModelFailure } from "../shared/model-fallback.ts";
|
|
89
90
|
import {
|
|
90
91
|
SUBAGENT_STARTUP_RETRY_DELAYS_MS,
|
|
91
92
|
formatSubagentExtensionConflictError,
|
|
@@ -99,7 +100,7 @@ import { createOwnedProcessTreeController, type OwnedProcessTreeController } fro
|
|
|
99
100
|
import { createSteeringStatus, recordSteeringRequest, steeringStatus, terminalSteeringNoticeState, updateSteeringTarget } from "./steering.ts";
|
|
100
101
|
import { attachPostExitStdioGuard, trySignalChild } from "../../shared/post-exit-stdio-guard.ts";
|
|
101
102
|
import { PROMPT_REDACTED, detectSubagentError, extractTextFromContent, extractToolArgsPreview, getFinalOutput, hasEmptyTerminalAssistantResponse, readStatus } from "../../shared/utils.ts";
|
|
102
|
-
import { evaluateCompletionMutationGuard } from "../shared/completion-guard.ts";
|
|
103
|
+
import { evaluateCompletionMutationGuard, validateImplementationToolContract } from "../shared/completion-guard.ts";
|
|
103
104
|
import {
|
|
104
105
|
createMutatingFailureState,
|
|
105
106
|
didMutatingToolFail,
|
|
@@ -123,6 +124,7 @@ import {
|
|
|
123
124
|
type WorktreeSetup,
|
|
124
125
|
} from "../shared/worktree.ts";
|
|
125
126
|
import { resolveEffectiveThinking } from "../../shared/model-info.ts";
|
|
127
|
+
import { assertThinkingWithinCeiling, decodeThinkingCeiling, SUBAGENT_THINKING_CEILING_ENV } from "../../shared/thinking-ceiling.ts";
|
|
126
128
|
import { launchBindingDigest } from "../../shared/launch-contract.ts";
|
|
127
129
|
import { writeInitialProgressFile } from "../../shared/settings.ts";
|
|
128
130
|
import { resolveSubagentIntercomTarget } from "../../intercom/intercom-bridge.ts";
|
|
@@ -130,6 +132,7 @@ import { acceptanceFailureMessage, aggregateAcceptanceReport, buildSkippedAccept
|
|
|
130
132
|
import { attachContractProjections, isAgentContractV1 } from "../shared/agent-contract.ts";
|
|
131
133
|
import { waitForImportedAsyncRoot } from "./chain-root-attachment.ts";
|
|
132
134
|
import { appendRunnerStepsToStatus, consumeChainAppendRequests, countPendingChainAppendRequests, statusStepDescription } from "./chain-append.ts";
|
|
135
|
+
import { asyncStatusChildIdentity } from "../shared/child-identity.ts";
|
|
133
136
|
import { appendTurnBudgetSystemPrompt, formatTurnBudgetOutput, initialTurnBudgetState, turnBudgetDecision, turnBudgetDeferredNote, turnBudgetDeferredState, turnBudgetExceededMessage, turnBudgetSoftNote, turnBudgetState } from "../shared/turn-budget.ts";
|
|
134
137
|
import { initialToolBudgetState, toolBudgetState } from "../shared/tool-budget.ts";
|
|
135
138
|
import { effectiveToolTimeoutMs, formatToolTimeoutMessage, toolTimeoutCallKey } from "../shared/tool-timeout.ts";
|
|
@@ -560,6 +563,8 @@ function runPiStreaming(
|
|
|
560
563
|
toolTimeoutMs?: number,
|
|
561
564
|
runDeadlineAt?: number,
|
|
562
565
|
orcaProgressTab?: OrcaProgressTab,
|
|
566
|
+
expectedModelForVerification?: string,
|
|
567
|
+
modelVerificationRegistry?: Array<{ provider: string; id: string; fullId: string }>,
|
|
563
568
|
): Promise<RunPiStreamingResult> {
|
|
564
569
|
return new Promise((resolve) => {
|
|
565
570
|
const startedAt = Date.now();
|
|
@@ -718,7 +723,14 @@ function runPiStreaming(
|
|
|
718
723
|
if (text) writeOutputText(text);
|
|
719
724
|
|
|
720
725
|
if (event.type !== "message_end" || event.message.role !== "assistant") return;
|
|
721
|
-
|
|
726
|
+
const hasToolCall = assistantStartsToolCall(event.message);
|
|
727
|
+
if (event.message.model) {
|
|
728
|
+
model = event.message.model;
|
|
729
|
+
if (expectedModelForVerification && !hasToolCall) {
|
|
730
|
+
const modelVerificationError = formatSubagentModelVerificationError(expectedModelForVerification, event.message.model, modelVerificationRegistry);
|
|
731
|
+
if (modelVerificationError && !error) error = modelVerificationError;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
722
734
|
if (event.message.errorMessage) assistantError = event.message.errorMessage;
|
|
723
735
|
const eventUsage = event.message.usage;
|
|
724
736
|
if (eventUsage) {
|
|
@@ -1279,6 +1291,43 @@ async function runSingleStepInner(
|
|
|
1279
1291
|
let task = step.task.replace(placeholderRegex, () => ctx.previousOutput);
|
|
1280
1292
|
if (ctx.outputs) task = resolveOutputReferences(task, ctx.outputs);
|
|
1281
1293
|
const taskForCompletionGuard = task;
|
|
1294
|
+
let resolvedTaskToolPlan: ReturnType<typeof resolvePiLaunchToolPlan> | undefined;
|
|
1295
|
+
if (!step.runner) {
|
|
1296
|
+
resolvedTaskToolPlan = resolvePiLaunchToolPlan(omitUndefinedProperties({
|
|
1297
|
+
tools: step.tools,
|
|
1298
|
+
extensions: step.extensions,
|
|
1299
|
+
subagentOnlyExtensions: step.subagentOnlyExtensions,
|
|
1300
|
+
mcpDirectTools: step.mcpDirectTools,
|
|
1301
|
+
cwd: step.cwd ?? ctx.cwd,
|
|
1302
|
+
requireReadTool: Boolean(step.skills?.length),
|
|
1303
|
+
structuredOutput: Boolean(effectiveStructuredOutput),
|
|
1304
|
+
capabilityCeiling: step.capabilityCeiling ?? ctx.capabilityCeiling,
|
|
1305
|
+
inheritedCapabilityCeiling: decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV]),
|
|
1306
|
+
permissionRules: step.permissionRules,
|
|
1307
|
+
}));
|
|
1308
|
+
const contractTools = resolvedTaskToolPlan.explicitToolAllowlist ? resolvedTaskToolPlan.effectiveToolAllowlist : undefined;
|
|
1309
|
+
const contractError = validateImplementationToolContract({
|
|
1310
|
+
agent: step.agent,
|
|
1311
|
+
task: taskForCompletionGuard,
|
|
1312
|
+
tools: contractTools,
|
|
1313
|
+
mcpDirectTools: resolvedTaskToolPlan.effectiveMcpTools,
|
|
1314
|
+
configuredExtensions: resolvedTaskToolPlan.configuredExtensions,
|
|
1315
|
+
requestedTools: resolvedTaskToolPlan.requestedBuiltinTools,
|
|
1316
|
+
acceptanceRole: step.acceptanceRole,
|
|
1317
|
+
completionGuard: step.completionGuard,
|
|
1318
|
+
});
|
|
1319
|
+
if (contractError) {
|
|
1320
|
+
return omitUndefinedProperties({
|
|
1321
|
+
agent: step.agent,
|
|
1322
|
+
context: step.context,
|
|
1323
|
+
output: contractError,
|
|
1324
|
+
error: contractError,
|
|
1325
|
+
exitCode: 1,
|
|
1326
|
+
capabilityCeiling: resolvedTaskToolPlan.capabilityCeiling,
|
|
1327
|
+
capabilityAudit: resolvedTaskToolPlan.capabilityAudit,
|
|
1328
|
+
});
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1282
1331
|
if (step.effectiveAcceptance) {
|
|
1283
1332
|
const acceptancePrompt = formatAcceptancePrompt(step.effectiveAcceptance, { reportOptional: isAgentContractV1(step.agentContract) });
|
|
1284
1333
|
if (acceptancePrompt) task = `${task}\n${acceptancePrompt}`;
|
|
@@ -1395,6 +1444,7 @@ async function runSingleStepInner(
|
|
|
1395
1444
|
timeoutMessage: ctx.timeoutMessage,
|
|
1396
1445
|
stopMessage: ctx.stopMessage,
|
|
1397
1446
|
onExternalJob: ctx.onExternalJob,
|
|
1447
|
+
followUp: step.externalJobFollowUp,
|
|
1398
1448
|
}));
|
|
1399
1449
|
try { fs.writeFileSync(ctx.outputFile, external.output, "utf-8"); } catch { /* Observability output is best-effort. */ }
|
|
1400
1450
|
const resolvedOutput = step.outputPath && external.exitCode === 0
|
|
@@ -1466,6 +1516,13 @@ async function runSingleStepInner(
|
|
|
1466
1516
|
modelAttemptsLoop: while (modelIndex < candidates.length) {
|
|
1467
1517
|
if (ctx.timeoutSignal?.aborted || ctx.stopSignal?.aborted || ctx.skipAcceptance?.()) break;
|
|
1468
1518
|
const candidate = candidates[modelIndex];
|
|
1519
|
+
const expectedModelForVerification = candidate && !(step.skipPrimaryModelVerification && modelIndex === 0) ? candidate : undefined;
|
|
1520
|
+
try {
|
|
1521
|
+
assertThinkingWithinCeiling({ model: candidate, configThinking: step.thinking, ceiling: step.thinkingCeiling ?? decodeThinkingCeiling(process.env[SUBAGENT_THINKING_CEILING_ENV]), agent: step.agent, runId: ctx.id });
|
|
1522
|
+
} catch (error) {
|
|
1523
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1524
|
+
return omitUndefinedProperties({ agent: step.agent, output: message, error: message, exitCode: 1, context: step.context, thinkingCeiling: step.thinkingCeiling });
|
|
1525
|
+
}
|
|
1469
1526
|
ctx.onAttemptStart?.(omitUndefinedProperties({ model: candidate, thinking: resolveEffectiveThinking(candidate, step.thinking) }));
|
|
1470
1527
|
const outputSnapshot = captureSingleOutputSnapshot(step.outputPath);
|
|
1471
1528
|
if (effectiveStructuredOutput) {
|
|
@@ -1529,13 +1586,14 @@ async function runSingleStepInner(
|
|
|
1529
1586
|
: undefined,
|
|
1530
1587
|
childWatchdog,
|
|
1531
1588
|
waitToolEnabled: step.waitToolEnabled,
|
|
1589
|
+
thinkingCeiling: step.thinkingCeiling,
|
|
1532
1590
|
}));
|
|
1533
1591
|
if (!launchWarningsEmitted && warnings.length > 0) {
|
|
1534
1592
|
for (const warning of warnings) console.warn(`[pi-subagents] ${warning}`);
|
|
1535
1593
|
launchWarningsEmitted = true;
|
|
1536
1594
|
}
|
|
1537
1595
|
if (step.definitionDigest) {
|
|
1538
|
-
const toolPlan = resolvePiLaunchToolPlan(omitUndefinedProperties({
|
|
1596
|
+
const toolPlan = resolvedTaskToolPlan ?? resolvePiLaunchToolPlan(omitUndefinedProperties({
|
|
1539
1597
|
tools: step.tools,
|
|
1540
1598
|
extensions: step.extensions,
|
|
1541
1599
|
subagentOnlyExtensions: step.subagentOnlyExtensions,
|
|
@@ -1554,6 +1612,7 @@ async function runSingleStepInner(
|
|
|
1554
1612
|
...(candidate ? { model: candidate } : {}),
|
|
1555
1613
|
modelCandidates: candidates as string[],
|
|
1556
1614
|
...(resolveEffectiveThinking(candidate, step.thinking) ? { thinking: resolveEffectiveThinking(candidate, step.thinking) } : {}),
|
|
1615
|
+
...(step.thinkingCeiling ? { thinkingCeiling: step.thinkingCeiling } : {}),
|
|
1557
1616
|
systemPrompt: appendTurnBudgetSystemPrompt(step.systemPrompt ?? "", ctx.turnBudget),
|
|
1558
1617
|
systemPromptMode: step.systemPromptMode,
|
|
1559
1618
|
inheritProjectContext: step.inheritProjectContext,
|
|
@@ -1590,6 +1649,8 @@ async function runSingleStepInner(
|
|
|
1590
1649
|
ctx.toolTimeoutMs,
|
|
1591
1650
|
ctx.deadlineAt,
|
|
1592
1651
|
ctx.orcaProgressTab,
|
|
1652
|
+
expectedModelForVerification,
|
|
1653
|
+
step.modelVerificationRegistry,
|
|
1593
1654
|
);
|
|
1594
1655
|
if (run.processCloseObservedAt !== undefined) {
|
|
1595
1656
|
writerProcesses.push({
|
|
@@ -1662,21 +1723,25 @@ async function runSingleStepInner(
|
|
|
1662
1723
|
? "Subagent produced no output (possible model cold-start or empty response)."
|
|
1663
1724
|
: undefined;
|
|
1664
1725
|
const completionGuardEnabled = isAgentContractV1(step.agentContract) ? step.completionGuard === true : step.completionGuard !== false;
|
|
1665
|
-
const
|
|
1726
|
+
const completionToolPlan = resolvedTaskToolPlan;
|
|
1727
|
+
const completionGuard = run.exitCode === 0 && !run.error && !structuredError && !hiddenError?.hasError && !emptyOutputError && completionGuardEnabled
|
|
1666
1728
|
? evaluateCompletionMutationGuard(omitUndefinedProperties({
|
|
1667
1729
|
agent: step.agent,
|
|
1668
1730
|
task: taskForCompletionGuard,
|
|
1669
1731
|
messages: run.messages,
|
|
1670
|
-
tools: step.tools,
|
|
1671
|
-
mcpDirectTools: step.mcpDirectTools,
|
|
1732
|
+
tools: completionToolPlan ? (completionToolPlan.explicitToolAllowlist ? completionToolPlan.effectiveToolAllowlist : undefined) : step.tools,
|
|
1733
|
+
mcpDirectTools: completionToolPlan?.effectiveMcpTools ?? step.mcpDirectTools,
|
|
1734
|
+
toolAvailabilityError,
|
|
1672
1735
|
}))
|
|
1673
1736
|
: undefined;
|
|
1674
1737
|
const completionGuardTriggered = completionGuard?.triggered === true && !run.observedMutationAttempt;
|
|
1738
|
+
const completionGuardBlocked = completionGuard?.blocked === true;
|
|
1675
1739
|
const fileMutationEffect = completionGuard
|
|
1676
1740
|
? {
|
|
1677
|
-
status: completionGuard.expectedMutation ? completionGuardTriggered ? "missing" as const : "observed" as const : "not-applicable" as const,
|
|
1741
|
+
status: completionGuardBlocked ? "blocked" as const : completionGuard.expectedMutation ? completionGuardTriggered ? "missing" as const : "observed" as const : "not-applicable" as const,
|
|
1678
1742
|
expected: completionGuard.expectedMutation,
|
|
1679
|
-
attempted: completionGuard.attemptedMutation || run.observedMutationAttempt === true,
|
|
1743
|
+
attempted: completionGuardBlocked ? false : completionGuard.attemptedMutation || run.observedMutationAttempt === true,
|
|
1744
|
+
...(completionGuardBlocked && completionGuard.message ? { message: completionGuard.message } : {}),
|
|
1680
1745
|
...(completionGuardTriggered ? { message: "Subagent completed without making edits for an implementation task." } : {}),
|
|
1681
1746
|
}
|
|
1682
1747
|
: undefined;
|
|
@@ -1959,21 +2024,8 @@ async function runSingleStep(
|
|
|
1959
2024
|
step: SubagentStep,
|
|
1960
2025
|
ctx: SingleStepContext,
|
|
1961
2026
|
): Promise<StepResult & { completionGuardTriggered?: boolean }> {
|
|
1962
|
-
if (step.importAsyncRoot)
|
|
1963
|
-
|
|
1964
|
-
cwd: step.cwd ?? ctx.cwd,
|
|
1965
|
-
runId: ctx.id,
|
|
1966
|
-
agent: step.agent,
|
|
1967
|
-
index: ctx.flatIndex,
|
|
1968
|
-
});
|
|
1969
|
-
try {
|
|
1970
|
-
const result = await runSingleStepInner(step, { ...ctx, orcaProgressTab });
|
|
1971
|
-
orcaProgressTab?.finish(result.stopped ? "stopped" : result.exitCode === 0 && !result.error ? "completed" : "failed", result.sessionFile);
|
|
1972
|
-
return result;
|
|
1973
|
-
} catch (error) {
|
|
1974
|
-
orcaProgressTab?.finish("failed");
|
|
1975
|
-
throw error;
|
|
1976
|
-
}
|
|
2027
|
+
if (!step.importAsyncRoot) ctx.orcaProgressTab?.section({ agent: step.agent, index: ctx.flatIndex, count: ctx.flatStepCount });
|
|
2028
|
+
return runSingleStepInner(step, ctx);
|
|
1977
2029
|
}
|
|
1978
2030
|
|
|
1979
2031
|
type RunnerStatusStep = NonNullable<AsyncStatus["steps"]>[number] & {
|
|
@@ -2242,6 +2294,7 @@ async function runSubagent(
|
|
|
2242
2294
|
const activeChildInterrupts = new Map<number, () => void>();
|
|
2243
2295
|
const activeChildTimeouts = new Map<number, () => void>();
|
|
2244
2296
|
const activeChildStops = new Map<number, () => void>();
|
|
2297
|
+
const childStopRequests = new Map<number, { childId: string; requestedAt: number }>();
|
|
2245
2298
|
const activeChildTurnBudgetAborts = new Map<number, (message: string, state?: TurnBudgetState) => void>();
|
|
2246
2299
|
const pendingStepSteers: SteerRequest[] = [];
|
|
2247
2300
|
const steeringCapabilities = new Map<number, SteerCapability>();
|
|
@@ -2285,6 +2338,7 @@ async function runSubagent(
|
|
|
2285
2338
|
...(task.launchContractDigest ? { launchContractDigest: task.launchContractDigest } : {}),
|
|
2286
2339
|
...(task.launchResolvedExtensions ? { launchResolvedExtensions: task.launchResolvedExtensions } : {}),
|
|
2287
2340
|
...(task.capabilityCeiling ? { capabilityCeiling: task.capabilityCeiling } : {}),
|
|
2341
|
+
...(task.thinkingCeiling ? { thinkingCeiling: task.thinkingCeiling } : {}),
|
|
2288
2342
|
status: "pending",
|
|
2289
2343
|
...(task.toolBudget ? { toolBudget: initialToolBudgetState(task.toolBudget) } : {}),
|
|
2290
2344
|
...(task.sessionFile ? { sessionFile: task.sessionFile } : {}),
|
|
@@ -2310,6 +2364,7 @@ async function runSubagent(
|
|
|
2310
2364
|
structured: Boolean(step.collect.outputSchema),
|
|
2311
2365
|
...(step.agentContract ? { agentContract: step.agentContract } : {}),
|
|
2312
2366
|
...(step.capabilityCeiling ? { capabilityCeiling: step.capabilityCeiling } : {}),
|
|
2367
|
+
...(step.thinkingCeiling ? { thinkingCeiling: step.thinkingCeiling } : {}),
|
|
2313
2368
|
status: "pending",
|
|
2314
2369
|
...(step.parallel.toolBudget ? { toolBudget: initialToolBudgetState(step.parallel.toolBudget) } : {}),
|
|
2315
2370
|
recentTools: [],
|
|
@@ -2332,6 +2387,7 @@ async function runSubagent(
|
|
|
2332
2387
|
...(step.launchContractDigest ? { launchContractDigest: step.launchContractDigest } : {}),
|
|
2333
2388
|
...(step.launchResolvedExtensions ? { launchResolvedExtensions: step.launchResolvedExtensions } : {}),
|
|
2334
2389
|
...(step.capabilityCeiling ? { capabilityCeiling: step.capabilityCeiling } : {}),
|
|
2390
|
+
...(step.thinkingCeiling ? { thinkingCeiling: step.thinkingCeiling } : {}),
|
|
2335
2391
|
status: "pending",
|
|
2336
2392
|
...(step.toolBudget ? { toolBudget: initialToolBudgetState(step.toolBudget) } : {}),
|
|
2337
2393
|
...(step.sessionFile ? { sessionFile: step.sessionFile } : {}),
|
|
@@ -2354,6 +2410,19 @@ async function runSubagent(
|
|
|
2354
2410
|
step.processTerminal = { version: 1, state: "pending", runId: id, runnerProcessInstanceId: config.runnerProcessInstanceId };
|
|
2355
2411
|
}
|
|
2356
2412
|
}
|
|
2413
|
+
const initialAgentLabel = initialStatusSteps.length === 1
|
|
2414
|
+
? initialStatusSteps[0]!.agent
|
|
2415
|
+
: (config.resultMode ?? (flatSteps.length > 1 ? "chain" : "single")) === "parallel"
|
|
2416
|
+
? `parallel:${initialStatusSteps.map((step) => step.agent).join("+")}`
|
|
2417
|
+
: `chain:${initialStatusSteps.map((step) => step.agent).join("->")}`;
|
|
2418
|
+
const orcaProgressTab = flatSteps.every((step) => step.importAsyncRoot) ? undefined : createOrcaProgressTab({
|
|
2419
|
+
cwd,
|
|
2420
|
+
runId: id,
|
|
2421
|
+
agent: initialAgentLabel,
|
|
2422
|
+
index: 0,
|
|
2423
|
+
stepCount: Math.max(initialStatusSteps.length, 1),
|
|
2424
|
+
});
|
|
2425
|
+
|
|
2357
2426
|
const statusPayload: RunnerStatusPayload = omitUndefinedProperties({
|
|
2358
2427
|
lifecycleArtifactVersion: SUBAGENT_LIFECYCLE_ARTIFACT_VERSION,
|
|
2359
2428
|
runId: id,
|
|
@@ -2572,6 +2641,83 @@ async function runSubagent(
|
|
|
2572
2641
|
statusPayload.lastUpdate = Date.now();
|
|
2573
2642
|
writeStatusPayload();
|
|
2574
2643
|
};
|
|
2644
|
+
const childStopTargetId = (index: number): string => asyncStatusChildIdentity(requiredStatusStep(statusPayload, index), index);
|
|
2645
|
+
const appendChildStatusEvent = (index: number, childId: string, status: "stopping" | "stopped", now = Date.now()): void => {
|
|
2646
|
+
const step = requiredStatusStep(statusPayload, index);
|
|
2647
|
+
appendJsonl(eventsPath, JSON.stringify({
|
|
2648
|
+
type: "subagent.child-status",
|
|
2649
|
+
version: 1,
|
|
2650
|
+
ts: now,
|
|
2651
|
+
runId: id,
|
|
2652
|
+
childId,
|
|
2653
|
+
status,
|
|
2654
|
+
reason: "user",
|
|
2655
|
+
source: "async",
|
|
2656
|
+
stepIndex: index,
|
|
2657
|
+
agent: step.agent,
|
|
2658
|
+
...(step.runId ? { childRunId: step.runId } : {}),
|
|
2659
|
+
...(step.workflowKey ? { workflowKey: step.workflowKey } : {}),
|
|
2660
|
+
...(step.phase ? { phase: step.phase } : {}),
|
|
2661
|
+
...(step.label ? { label: step.label } : {}),
|
|
2662
|
+
} satisfies SubagentChildStatusEvent));
|
|
2663
|
+
};
|
|
2664
|
+
const appendTerminalChildStatusEvent = (index: number, now = Date.now()): void => {
|
|
2665
|
+
const request = childStopRequests.get(index);
|
|
2666
|
+
if (request) appendChildStatusEvent(index, request.childId, "stopped", now);
|
|
2667
|
+
};
|
|
2668
|
+
const markChildStopRequested = (index: number, childId: string, now = Date.now()): boolean => {
|
|
2669
|
+
const step = statusPayload.steps[index];
|
|
2670
|
+
if (!step || (step.status !== "pending" && step.status !== "running")) return false;
|
|
2671
|
+
childStopRequests.set(index, { childId, requestedAt: now });
|
|
2672
|
+
step.stopRequested = true;
|
|
2673
|
+
step.stopRequestedAt = now;
|
|
2674
|
+
delete step.activityState;
|
|
2675
|
+
statusPayload.lastUpdate = now;
|
|
2676
|
+
writeStatusPayload();
|
|
2677
|
+
appendJsonl(eventsPath, JSON.stringify({ type: "subagent.step.stop_requested", ts: now, runId: id, stepIndex: index, childId, agent: step.agent }));
|
|
2678
|
+
appendChildStatusEvent(index, childId, "stopping", now);
|
|
2679
|
+
return true;
|
|
2680
|
+
};
|
|
2681
|
+
const markChildStopped = (index: number, now = Date.now()): void => {
|
|
2682
|
+
const step = requiredStatusStep(statusPayload, index);
|
|
2683
|
+
if (step.status === "stopped") return;
|
|
2684
|
+
step.status = "stopped";
|
|
2685
|
+
step.error = stopMessage;
|
|
2686
|
+
step.exitCode = 1;
|
|
2687
|
+
step.stopped = true;
|
|
2688
|
+
step.stopRequested = true;
|
|
2689
|
+
step.stopRequestedAt = childStopRequests.get(index)?.requestedAt ?? step.stopRequestedAt ?? now;
|
|
2690
|
+
delete step.activityState;
|
|
2691
|
+
step.endedAt = now;
|
|
2692
|
+
step.durationMs = step.startedAt ? now - step.startedAt : 0;
|
|
2693
|
+
step.lastActivityAt = now;
|
|
2694
|
+
statusPayload.lastUpdate = now;
|
|
2695
|
+
writeStatusPayload();
|
|
2696
|
+
const childId = childStopRequests.get(index)?.childId ?? childStopTargetId(index);
|
|
2697
|
+
appendJsonl(eventsPath, JSON.stringify({ type: "subagent.step.stopped", ts: now, runId: id, stepIndex: index, childId, agent: step.agent, exitCode: 1, durationMs: step.durationMs }));
|
|
2698
|
+
appendChildStatusEvent(index, childId, "stopped", now);
|
|
2699
|
+
};
|
|
2700
|
+
const childStopResult = (index: number, agent: string, context?: "fresh" | "fork"): SingleStepResult => {
|
|
2701
|
+
markChildStopped(index);
|
|
2702
|
+
return stoppedStepResult(agent, context);
|
|
2703
|
+
};
|
|
2704
|
+
const stopChildStep = (request: StopRequest): void => {
|
|
2705
|
+
if (request.targetIndex === undefined) {
|
|
2706
|
+
stopRunner();
|
|
2707
|
+
return;
|
|
2708
|
+
}
|
|
2709
|
+
const childId = request.childId ?? childStopTargetId(request.targetIndex);
|
|
2710
|
+
const now = Date.now();
|
|
2711
|
+
if (!markChildStopRequested(request.targetIndex, childId, now)) {
|
|
2712
|
+
appendJsonl(eventsPath, JSON.stringify({ type: "subagent.step.stop_failed", ts: now, runId: id, stepIndex: request.targetIndex, childId, message: "Child is not pending or running." }));
|
|
2713
|
+
return;
|
|
2714
|
+
}
|
|
2715
|
+
const stop = activeChildStops.get(request.targetIndex);
|
|
2716
|
+
if (stop) stop();
|
|
2717
|
+
else if (requiredStatusStep(statusPayload, request.targetIndex).status === "pending") {
|
|
2718
|
+
appendJsonl(eventsPath, JSON.stringify({ type: "subagent.step.stop_queued", ts: now, runId: id, stepIndex: request.targetIndex, childId }));
|
|
2719
|
+
}
|
|
2720
|
+
};
|
|
2575
2721
|
const registerStepInterrupt = (flatIndex: number, interrupt: (() => void) | undefined): void => {
|
|
2576
2722
|
if (!interrupt) {
|
|
2577
2723
|
activeChildInterrupts.delete(flatIndex);
|
|
@@ -2594,7 +2740,7 @@ async function runSubagent(
|
|
|
2594
2740
|
return;
|
|
2595
2741
|
}
|
|
2596
2742
|
activeChildStops.set(flatIndex, stop);
|
|
2597
|
-
if (stopped) stop();
|
|
2743
|
+
if (stopped || childStopRequests.has(flatIndex)) stop();
|
|
2598
2744
|
};
|
|
2599
2745
|
const registerStepTurnBudgetAbort = (flatIndex: number, abort: ((message: string, state?: TurnBudgetState) => void) | undefined): void => {
|
|
2600
2746
|
if (!abort) {
|
|
@@ -3499,7 +3645,7 @@ async function runSubagent(
|
|
|
3499
3645
|
const disposeControlInbox = watchAsyncControlInbox(asyncDir, {
|
|
3500
3646
|
onInterrupt: interruptRunner,
|
|
3501
3647
|
onTimeout: timeoutRunner,
|
|
3502
|
-
onStop:
|
|
3648
|
+
onStop: stopChildStep,
|
|
3503
3649
|
onSteer: (request) => {
|
|
3504
3650
|
const targetStep = request.targetIndex !== undefined ? statusPayload.steps[request.targetIndex] : undefined;
|
|
3505
3651
|
if (targetStep?.status === "pending") {
|
|
@@ -3575,6 +3721,19 @@ async function runSubagent(
|
|
|
3575
3721
|
if (materialized.parallel.length > 1 && step.parallel.outputPath && !step.parallel.namespaceOutputPath) {
|
|
3576
3722
|
throw new DynamicFanoutError(`Dynamic chain step ${stepIndex + 1} materialized ${materialized.parallel.length} items that resolve output to the same path: ${step.parallel.outputPath}. Remove the explicit output path or use an inherited relative agent output so each item can be isolated.`);
|
|
3577
3723
|
}
|
|
3724
|
+
for (const [itemIndex] of materialized.parallel.entries()) {
|
|
3725
|
+
const thinkingOverride = step.thinkingOverrides?.[itemIndex];
|
|
3726
|
+
const model = thinkingOverride ? applyThinkingSuffix(step.parallel.model, thinkingOverride, true) : step.parallel.model;
|
|
3727
|
+
const configThinking = thinkingOverride ? thinkingOverride : step.parallel.thinking;
|
|
3728
|
+
const candidates = step.parallel.modelCandidates !== undefined
|
|
3729
|
+
? step.parallel.modelCandidates.length > 0
|
|
3730
|
+
? step.parallel.modelCandidates.map((candidate) => thinkingOverride ? applyThinkingSuffix(candidate, thinkingOverride, true) ?? candidate : candidate)
|
|
3731
|
+
: [undefined]
|
|
3732
|
+
: model ? [model] : [undefined];
|
|
3733
|
+
for (const candidate of candidates) {
|
|
3734
|
+
assertThinkingWithinCeiling({ model: candidate, configThinking, ceiling: step.parallel.thinkingCeiling ?? decodeThinkingCeiling(process.env[SUBAGENT_THINKING_CEILING_ENV]), agent: step.parallel.agent, runId: id });
|
|
3735
|
+
}
|
|
3736
|
+
}
|
|
3578
3737
|
if (materialized.collectedOnEmpty) await validateDynamicCollection(step.collect.outputSchema, materialized.collectedOnEmpty);
|
|
3579
3738
|
if (!config.runFanoutBudget) throw new Error("Async runner is missing its run fan-out budget identity.");
|
|
3580
3739
|
const runFanoutBudget = claimRunFanoutBatch(config.runFanoutBudget, materialized.parallel.map((_, itemIndex) => `chain[${stepIndex}].expand[${itemIndex}]`));
|
|
@@ -3731,6 +3890,7 @@ async function runSubagent(
|
|
|
3731
3890
|
...(task.skills ? { skills: task.skills } : {}),
|
|
3732
3891
|
...(task.model ? { model: task.model } : {}),
|
|
3733
3892
|
...(task.thinking ? { thinking: task.thinking } : {}),
|
|
3893
|
+
...(task.thinkingCeiling ? { thinkingCeiling: task.thinkingCeiling } : {}),
|
|
3734
3894
|
...(task.modelCandidates && task.modelCandidates.length > 0 ? { attemptedModels: task.modelCandidates } : task.model ? { attemptedModels: [task.model] } : {}),
|
|
3735
3895
|
recentTools: [],
|
|
3736
3896
|
recentOutput: [],
|
|
@@ -3802,6 +3962,7 @@ async function runSubagent(
|
|
|
3802
3962
|
}
|
|
3803
3963
|
if (timedOut) return timedOutStepResult(task.agent, task.context);
|
|
3804
3964
|
if (stopped) return stoppedStepResult(task.agent, task.context);
|
|
3965
|
+
if (childStopRequests.has(fi)) return childStopResult(fi, task.agent, task.context);
|
|
3805
3966
|
if (interrupted) return pausedStepResult(task.agent, task.context);
|
|
3806
3967
|
if (aborted && failFast) {
|
|
3807
3968
|
const skippedAt = Date.now();
|
|
@@ -3861,7 +4022,8 @@ async function runSubagent(
|
|
|
3861
4022
|
onWriterProcess,
|
|
3862
4023
|
onExternalProcess: (process) => updateExternalProcess(fi, process),
|
|
3863
4024
|
onExternalJob: (externalJob) => updateExternalJob(fi, externalJob),
|
|
3864
|
-
skipAcceptance: () => timedOut || stopped,
|
|
4025
|
+
skipAcceptance: () => timedOut || stopped || childStopRequests.has(fi),
|
|
4026
|
+
orcaProgressTab,
|
|
3865
4027
|
}), config.deadlineAt);
|
|
3866
4028
|
const taskEndTime = Date.now();
|
|
3867
4029
|
const childInterrupted = singleResult.interrupted === true;
|
|
@@ -3918,12 +4080,13 @@ async function runSubagent(
|
|
|
3918
4080
|
statusPayload.lastUpdate = taskEndTime;
|
|
3919
4081
|
writeStatusPayload();
|
|
3920
4082
|
appendCapabilityCeilingAppliedEvent(eventsPath, id, fi, task.agent, singleResult);
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
4083
|
+
appendJsonl(eventsPath, JSON.stringify({
|
|
4084
|
+
type: stopped || childStopped ? "subagent.step.stopped" : timedOut ? "subagent.step.failed" : childInterrupted ? "subagent.step.paused" : singleResult.exitCode === 0 ? "subagent.step.completed" : "subagent.step.failed",
|
|
4085
|
+
ts: taskEndTime, runId: id, stepIndex: fi, agent: task.agent,
|
|
4086
|
+
exitCode: stopped || childStopped ? 1 : timedOut ? 1 : childInterrupted ? 0 : singleResult.exitCode, durationMs: taskEndTime - taskStartTime,
|
|
4087
|
+
}));
|
|
4088
|
+
if (stopped || childStopped) appendTerminalChildStatusEvent(fi, taskEndTime);
|
|
4089
|
+
if (singleResult.exitCode !== 0 && failFast && !childStopped) aborted = true;
|
|
3927
4090
|
return stopped || childStopped ? { ...singleResult, output: stopMessage, error: stopMessage, exitCode: 1, interrupted: false, timedOut: false, stopped: true, skipped: false } : timedOut ? { ...singleResult, output: timeoutMessage ?? "Subagent timed out.", error: timeoutMessage ?? "Subagent timed out.", exitCode: 1, interrupted: false, timedOut: true, skipped: false } : { ...singleResult, skipped: false };
|
|
3928
4091
|
}, globalSemaphore);
|
|
3929
4092
|
|
|
@@ -4176,6 +4339,7 @@ async function runSubagent(
|
|
|
4176
4339
|
}
|
|
4177
4340
|
if (timedOut) return timedOutStepResult(task.agent, task.context);
|
|
4178
4341
|
if (stopped) return stoppedStepResult(task.agent, task.context);
|
|
4342
|
+
if (childStopRequests.has(fi)) return childStopResult(fi, task.agent, task.context);
|
|
4179
4343
|
if (interrupted) return pausedStepResult(task.agent, task.context);
|
|
4180
4344
|
if (aborted && failFast) {
|
|
4181
4345
|
const skippedAt = Date.now();
|
|
@@ -4251,7 +4415,8 @@ async function runSubagent(
|
|
|
4251
4415
|
onWriterProcess,
|
|
4252
4416
|
onExternalProcess: (process) => updateExternalProcess(fi, process),
|
|
4253
4417
|
onExternalJob: (externalJob) => updateExternalJob(fi, externalJob),
|
|
4254
|
-
skipAcceptance: () => timedOut || stopped,
|
|
4418
|
+
skipAcceptance: () => timedOut || stopped || childStopRequests.has(fi),
|
|
4419
|
+
orcaProgressTab,
|
|
4255
4420
|
}), config.deadlineAt);
|
|
4256
4421
|
if (task.sessionFile) {
|
|
4257
4422
|
latestSessionFile = task.sessionFile;
|
|
@@ -4319,6 +4484,7 @@ async function runSubagent(
|
|
|
4319
4484
|
ts: taskEndTime, runId: id, stepIndex: fi, agent: task.agent,
|
|
4320
4485
|
exitCode: stopped || childStopped ? 1 : timedOut ? 1 : childInterrupted ? 0 : singleResult.exitCode, durationMs: taskDuration,
|
|
4321
4486
|
}));
|
|
4487
|
+
if (stopped || childStopped) appendTerminalChildStatusEvent(fi, taskEndTime);
|
|
4322
4488
|
if (singleResult.completionGuardTriggered) {
|
|
4323
4489
|
const event = buildControlEvent(omitUndefinedProperties({
|
|
4324
4490
|
from: requiredStatusStep(statusPayload, fi).activityState,
|
|
@@ -4333,7 +4499,7 @@ async function runSubagent(
|
|
|
4333
4499
|
appendControlEvent(event);
|
|
4334
4500
|
}
|
|
4335
4501
|
|
|
4336
|
-
if (singleResult.exitCode !== 0 && failFast) aborted = true;
|
|
4502
|
+
if (singleResult.exitCode !== 0 && failFast && !childStopped) aborted = true;
|
|
4337
4503
|
return stopped || childStopped ? { ...singleResult, output: stopMessage, error: stopMessage, exitCode: 1, interrupted: false, timedOut: false, stopped: true, skipped: false } : timedOut ? { ...singleResult, output: timeoutMessage ?? "Subagent timed out.", error: timeoutMessage ?? "Subagent timed out.", exitCode: 1, interrupted: false, timedOut: true, skipped: false } : { ...singleResult, skipped: false };
|
|
4338
4504
|
},
|
|
4339
4505
|
globalSemaphore,
|
|
@@ -4490,6 +4656,26 @@ async function runSubagent(
|
|
|
4490
4656
|
}
|
|
4491
4657
|
} else {
|
|
4492
4658
|
const seqStep = step as SubagentStep;
|
|
4659
|
+
if (timedOut) {
|
|
4660
|
+
results.push(timedOutStepResult(seqStep.agent, seqStep.context));
|
|
4661
|
+
flatIndex++;
|
|
4662
|
+
continue;
|
|
4663
|
+
}
|
|
4664
|
+
if (stopped) {
|
|
4665
|
+
results.push(stoppedStepResult(seqStep.agent, seqStep.context));
|
|
4666
|
+
flatIndex++;
|
|
4667
|
+
continue;
|
|
4668
|
+
}
|
|
4669
|
+
if (childStopRequests.has(flatIndex)) {
|
|
4670
|
+
results.push(childStopResult(flatIndex, seqStep.agent, seqStep.context));
|
|
4671
|
+
flatIndex++;
|
|
4672
|
+
continue;
|
|
4673
|
+
}
|
|
4674
|
+
if (interrupted) {
|
|
4675
|
+
results.push(pausedStepResult(seqStep.agent, seqStep.context));
|
|
4676
|
+
flatIndex++;
|
|
4677
|
+
continue;
|
|
4678
|
+
}
|
|
4493
4679
|
let singleWorktreeSetup: WorktreeSetup | undefined;
|
|
4494
4680
|
if (seqStep.worktree) {
|
|
4495
4681
|
try {
|
|
@@ -4574,7 +4760,8 @@ async function runSubagent(
|
|
|
4574
4760
|
onWriterProcess,
|
|
4575
4761
|
onExternalProcess: (process) => updateExternalProcess(flatIndex, process),
|
|
4576
4762
|
onExternalJob: (externalJob) => updateExternalJob(flatIndex, externalJob),
|
|
4577
|
-
skipAcceptance: () => timedOut || stopped,
|
|
4763
|
+
skipAcceptance: () => timedOut || stopped || childStopRequests.has(flatIndex),
|
|
4764
|
+
orcaProgressTab,
|
|
4578
4765
|
}), config.deadlineAt);
|
|
4579
4766
|
} catch (error) {
|
|
4580
4767
|
if (singleWorktreeSetup) cleanupWorktrees(singleWorktreeSetup);
|
|
@@ -4721,6 +4908,7 @@ async function runSubagent(
|
|
|
4721
4908
|
durationMs: stepEndTime - stepStartTime,
|
|
4722
4909
|
tokens: stepTokens,
|
|
4723
4910
|
}));
|
|
4911
|
+
if (stopped || childStopped) appendTerminalChildStatusEvent(flatIndex, stepEndTime);
|
|
4724
4912
|
if (singleWorktreeSetup && !singleResult.detached) {
|
|
4725
4913
|
const diffs = diffWorktrees(singleWorktreeSetup, [seqStep.agent], path.join(asyncDir, "worktree-diffs", `step-${stepIndex}`));
|
|
4726
4914
|
const diffSummary = formatWorktreeDiffSummary(diffs);
|
|
@@ -5023,6 +5211,7 @@ async function runSubagent(
|
|
|
5023
5211
|
statusPayload.lastUpdate = Date.now();
|
|
5024
5212
|
}
|
|
5025
5213
|
writeStatusPayload();
|
|
5214
|
+
orcaProgressTab?.finish(statusPayload.state === "complete" ? "completed" : statusPayload.state === "stopped" ? "stopped" : "failed", effectiveSessionFile);
|
|
5026
5215
|
appendJsonl(
|
|
5027
5216
|
eventsPath,
|
|
5028
5217
|
JSON.stringify({
|
|
@@ -3,6 +3,7 @@ import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
|
3
3
|
import type { Details, SubagentState } from "../../shared/types.ts";
|
|
4
4
|
import { deliverStopRequest } from "../background/control-channel.ts";
|
|
5
5
|
import { reconcileAsyncRun } from "../background/stale-run-reconciler.ts";
|
|
6
|
+
import { isStoppableAsyncStatusStep, resolveAsyncStatusChild, type ResolvedAsyncStatusChild } from "../shared/child-identity.ts";
|
|
6
7
|
|
|
7
8
|
function getAsyncStopTarget(
|
|
8
9
|
state: SubagentState,
|
|
@@ -25,6 +26,7 @@ export function stopAsyncRun(
|
|
|
25
26
|
runId: string | undefined,
|
|
26
27
|
kill?: (pid: number, signal?: NodeJS.Signals | 0) => boolean,
|
|
27
28
|
location?: { asyncDir: string | null; resolvedId?: string },
|
|
29
|
+
childId?: string,
|
|
28
30
|
): AgentToolResult<Details> | null {
|
|
29
31
|
const target = getAsyncStopTarget(state, runId, location);
|
|
30
32
|
if (!target) return null;
|
|
@@ -43,15 +45,34 @@ export function stopAsyncRun(
|
|
|
43
45
|
details: { mode: "management", results: [] },
|
|
44
46
|
};
|
|
45
47
|
}
|
|
48
|
+
let child: ResolvedAsyncStatusChild | undefined;
|
|
49
|
+
if (childId !== undefined) {
|
|
50
|
+
const resolution = resolveAsyncStatusChild(status, childId);
|
|
51
|
+
if (!resolution.ok) {
|
|
52
|
+
return {
|
|
53
|
+
content: [{ type: "text", text: resolution.message }],
|
|
54
|
+
isError: true,
|
|
55
|
+
details: { mode: "management", results: [] },
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
child = resolution.child;
|
|
59
|
+
if (!isStoppableAsyncStatusStep(child.step)) {
|
|
60
|
+
return {
|
|
61
|
+
content: [{ type: "text", text: `Child '${childId}' in async run '${status.runId}' is ${child.step.status}; stop only supports pending or running children.` }],
|
|
62
|
+
isError: true,
|
|
63
|
+
details: { mode: "management", results: [] },
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
46
67
|
try {
|
|
47
|
-
deliverStopRequest({ asyncDir: target.asyncDir, pid: typeof status.pid === "number" ? status.pid : undefined, kill, source: "stop-action" });
|
|
68
|
+
deliverStopRequest({ asyncDir: target.asyncDir, pid: typeof status.pid === "number" ? status.pid : undefined, kill, source: "stop-action", targetIndex: child?.index, childId: child?.id ?? childId });
|
|
48
69
|
const tracked = state.asyncJobs.get(target.asyncId);
|
|
49
70
|
if (tracked) {
|
|
50
71
|
tracked.activityState = undefined;
|
|
51
72
|
tracked.updatedAt = Date.now();
|
|
52
73
|
}
|
|
53
74
|
return {
|
|
54
|
-
content: [{ type: "text", text: `Stop requested for async run ${target.asyncId}.` }],
|
|
75
|
+
content: [{ type: "text", text: child ? `Stop requested for child ${child.id} in async run ${target.asyncId}.` : `Stop requested for async run ${target.asyncId}.` }],
|
|
55
76
|
details: { mode: "management", results: [] },
|
|
56
77
|
};
|
|
57
78
|
} catch (error) {
|