pi-subagents 0.38.0 → 0.40.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 +38 -0
- package/README.md +85 -13
- package/agents/worker.md +1 -0
- package/package.json +1 -1
- package/prompts/review-loop.md +1 -1
- package/skills/pi-subagents/SKILL.md +4 -3
- package/skills/pi-subagents/references/execution-controls.md +22 -3
- package/skills/pi-subagents/references/management-authoring-rpc.md +5 -1
- package/skills/pi-subagents/references/prompting-and-roles.md +17 -3
- package/src/agents/agent-management.ts +71 -30
- package/src/agents/agent-serializer.ts +4 -0
- package/src/agents/agents.ts +60 -1
- package/src/api/preflight.ts +16 -12
- package/src/extension/chain-validation.ts +17 -1
- package/src/extension/rpc.ts +2 -0
- package/src/extension/schemas.ts +16 -3
- package/src/extension/tool-description.ts +6 -5
- package/src/intercom/result-intercom.ts +33 -4
- package/src/runs/background/async-execution.ts +63 -16
- package/src/runs/background/async-job-tracker.ts +5 -1
- package/src/runs/background/async-resume.ts +1 -0
- package/src/runs/background/async-status.ts +18 -2
- package/src/runs/background/chain-append.ts +48 -5
- package/src/runs/background/control-channel.ts +60 -0
- package/src/runs/background/notify.ts +26 -4
- package/src/runs/background/result-watcher.ts +20 -3
- package/src/runs/background/run-status.ts +29 -3
- package/src/runs/background/subagent-runner.ts +302 -18
- package/src/runs/foreground/async-stop-action.ts +65 -0
- package/src/runs/foreground/chain-execution.ts +143 -43
- package/src/runs/foreground/execution.ts +384 -202
- package/src/runs/foreground/foreground-control.ts +30 -0
- package/src/runs/foreground/subagent-executor.ts +314 -77
- package/src/runs/shared/capability-ceiling.ts +51 -19
- package/src/runs/shared/chain-outputs.ts +3 -1
- package/src/runs/shared/dynamic-fanout.ts +1 -1
- package/src/runs/shared/nested-events.ts +97 -20
- package/src/runs/shared/parallel-utils.ts +43 -12
- package/src/runs/shared/pi-args.ts +45 -3
- package/src/runs/shared/process-signal.ts +19 -0
- package/src/runs/shared/run-history.ts +45 -9
- package/src/runs/shared/runtime-acknowledged-extensions.ts +71 -0
- package/src/runs/shared/subagent-prompt-runtime.ts +30 -0
- package/src/runs/shared/usage-budget.ts +65 -0
- package/src/runs/shared/workflow-graph.ts +26 -1
- package/src/shared/settings.ts +17 -1
- package/src/shared/types.ts +124 -8
- package/src/shared/utils.ts +12 -1
- package/src/slash/slash-commands.ts +43 -1
- package/src/slash/slash-live-state.ts +5 -3
- package/src/tui/fleet-status.ts +12 -2
- package/src/tui/fleet.ts +181 -8
- package/src/tui/render.ts +4 -2
- package/src/watchdog/register-main.ts +14 -7
- package/src/watchdog/review.ts +4 -3
- package/src/watchdog/runtime.ts +170 -17
- package/src/watchdog/scope.ts +62 -0
- package/src/watchdog/settings.ts +41 -1
- package/src/watchdog/types.ts +10 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
|
+
import { isUnexplainedProcessSignal } from "../runs/shared/process-signal.ts";
|
|
3
4
|
import {
|
|
4
5
|
type Details,
|
|
5
6
|
type IntercomEventBus,
|
|
@@ -10,6 +11,7 @@ import {
|
|
|
10
11
|
type SubagentResultIntercomChild,
|
|
11
12
|
type SubagentResultIntercomPayload,
|
|
12
13
|
type SubagentResultStatus,
|
|
14
|
+
type SubagentOutputState,
|
|
13
15
|
type SubagentRunMode,
|
|
14
16
|
SUBAGENT_RESULT_INTERCOM_DELIVERY_EVENT,
|
|
15
17
|
SUBAGENT_RESULT_INTERCOM_EVENT,
|
|
@@ -21,11 +23,17 @@ export function resolveSubagentResultStatus(input: {
|
|
|
21
23
|
state?: string;
|
|
22
24
|
interrupted?: boolean;
|
|
23
25
|
detached?: boolean;
|
|
26
|
+
processSignal?: string | null;
|
|
27
|
+
timedOut?: boolean;
|
|
28
|
+
stopped?: boolean;
|
|
29
|
+
turnBudgetExceeded?: boolean;
|
|
24
30
|
}): SubagentResultStatus {
|
|
25
31
|
if (input.detached) return "detached";
|
|
26
|
-
if (input.state === "stopped") return "stopped";
|
|
32
|
+
if (input.stopped || input.state === "stopped") return "stopped";
|
|
27
33
|
if (input.interrupted || input.state === "paused") return "paused";
|
|
28
|
-
if (
|
|
34
|
+
if (input.success === true) return "completed";
|
|
35
|
+
if (isUnexplainedProcessSignal(input) && input.exitCode !== 0) return "stopped";
|
|
36
|
+
if (input.success === false) return "failed";
|
|
29
37
|
if (input.state === "complete") return "completed";
|
|
30
38
|
if (input.state === "failed") return "failed";
|
|
31
39
|
if (typeof input.exitCode === "number") return input.exitCode === 0 ? "completed" : "failed";
|
|
@@ -57,6 +65,21 @@ function formatStatusCounts(counts: Record<SubagentResultStatus, number>): strin
|
|
|
57
65
|
return parts.length ? parts.join(", ") : "0 results";
|
|
58
66
|
}
|
|
59
67
|
|
|
68
|
+
function countOutputStates(children: SubagentResultIntercomChild[]): Record<SubagentOutputState, number> {
|
|
69
|
+
const counts: Record<SubagentOutputState, number> = { present: 0, absent: 0, unknown: 0 };
|
|
70
|
+
for (const child of children) counts[child.outputState ?? "unknown"] += 1;
|
|
71
|
+
return counts;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function formatOutputCounts(counts: Record<SubagentOutputState, number>): string {
|
|
75
|
+
const parts = [
|
|
76
|
+
counts.present ? `${counts.present} present` : undefined,
|
|
77
|
+
counts.absent ? `${counts.absent} absent` : undefined,
|
|
78
|
+
counts.unknown ? `${counts.unknown} unknown` : undefined,
|
|
79
|
+
].filter((part): part is string => Boolean(part));
|
|
80
|
+
return parts.length ? parts.join(", ") : "0 outputs";
|
|
81
|
+
}
|
|
82
|
+
|
|
60
83
|
function resolveGroupedStatus(children: SubagentResultIntercomChild[]): SubagentResultStatus {
|
|
61
84
|
const counts = countStatuses(children);
|
|
62
85
|
if (counts.failed > 0) return "failed";
|
|
@@ -212,14 +235,19 @@ function formatSubagentResultIntercomMessage(input: {
|
|
|
212
235
|
parallelHandoff?: ParallelHandoffReference;
|
|
213
236
|
}): string {
|
|
214
237
|
const counts = countStatuses(input.children);
|
|
238
|
+
const outputCounts = countOutputStates(input.children);
|
|
215
239
|
const lines: string[] = [
|
|
216
240
|
"subagent results",
|
|
217
241
|
"",
|
|
218
242
|
`Run: ${input.runId}`,
|
|
219
243
|
`Mode: ${input.mode}`,
|
|
220
|
-
`
|
|
244
|
+
`Process status: ${input.status}`,
|
|
221
245
|
`Children: ${formatStatusCounts(counts)}`,
|
|
246
|
+
`Outputs: ${formatOutputCounts(outputCounts)} (semantic adequacy unassessed)`,
|
|
222
247
|
];
|
|
248
|
+
if (input.children.some((child) => child.status === "failed" && child.outputState === "present")) {
|
|
249
|
+
lines.push("Recovery: At least one failed process produced output. Inspect that output before retrying; output presence does not establish task completion.");
|
|
250
|
+
}
|
|
223
251
|
if (input.mode === "chain" && typeof input.chainSteps === "number") {
|
|
224
252
|
lines.push(`Chain steps: ${input.chainSteps}`);
|
|
225
253
|
}
|
|
@@ -238,7 +266,7 @@ function formatSubagentResultIntercomMessage(input: {
|
|
|
238
266
|
for (let index = 0; index < input.children.length; index++) {
|
|
239
267
|
const child = input.children[index]!;
|
|
240
268
|
lines.push("");
|
|
241
|
-
lines.push(`${index + 1}. ${child.agent} — ${child.status}`);
|
|
269
|
+
lines.push(`${index + 1}. ${child.agent} — process ${child.status} · output ${child.outputState ?? "unknown"}`);
|
|
242
270
|
if (child.intercomTarget) lines.push(`${input.source === "async" ? "Previous intercom target" : "Run intercom target"}: ${child.intercomTarget}`);
|
|
243
271
|
if (child.artifactPath) lines.push(`Output artifact: ${child.artifactPath}`);
|
|
244
272
|
if (child.sessionPath) lines.push(`Session: ${child.sessionPath}`);
|
|
@@ -253,6 +281,7 @@ function formatSubagentResultIntercomMessage(input: {
|
|
|
253
281
|
export function buildSubagentResultIntercomPayload(input: GroupedResultIntercomMessageInput): SubagentResultIntercomPayload {
|
|
254
282
|
const children = input.children.map((child) => ({
|
|
255
283
|
...child,
|
|
284
|
+
outputState: child.outputState ?? "unknown",
|
|
256
285
|
summary: child.summary.trim() || "(no output)",
|
|
257
286
|
children: compactNestedResultChildren(child.children),
|
|
258
287
|
}));
|
|
@@ -12,9 +12,9 @@ import { createRequire } from "node:module";
|
|
|
12
12
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
13
13
|
import type { AgentConfig } from "../../agents/agents.ts";
|
|
14
14
|
import { writePrivateAtomicJson } from "../../shared/atomic-json.ts";
|
|
15
|
-
import { applyThinkingSuffix, resolvePiLaunchToolPlan } from "../shared/pi-args.ts";
|
|
15
|
+
import { applyThinkingSuffix, projectLaunchResolvedChildExtensions, resolvePiLaunchToolPlan } from "../shared/pi-args.ts";
|
|
16
16
|
import { injectOutputPathSystemPrompt, injectSingleOutputInstruction, normalizeSingleOutputOverride, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
|
|
17
|
-
import { buildChainInstructions, isDynamicParallelStep, isParallelStep, resolveStepBehavior, suppressProgressForReadOnlyTask, writeInitialProgressFile, type ChainStep, type ResolvedStepBehavior, type SequentialStep, type StepOverrides } from "../../shared/settings.ts";
|
|
17
|
+
import { buildChainInstructions, isCheckpointStep, isDynamicParallelStep, isParallelStep, resolveStepBehavior, suppressProgressForReadOnlyTask, writeInitialProgressFile, type ChainStep, type ResolvedStepBehavior, type SequentialStep, type StepOverrides } from "../../shared/settings.ts";
|
|
18
18
|
import type { RunnerStep } from "../shared/parallel-utils.ts";
|
|
19
19
|
import type { ContextMode } from "../shared/context-mode.ts";
|
|
20
20
|
import { resolvePiPackageRoot } from "../shared/pi-spawn.ts";
|
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
type ResolvedToolBudget,
|
|
43
43
|
type SubagentRunMode,
|
|
44
44
|
type SteeringRecoveryDescriptor,
|
|
45
|
+
type UsageBudgetConfig,
|
|
45
46
|
ASYNC_DIR,
|
|
46
47
|
RESULTS_DIR,
|
|
47
48
|
SUBAGENT_ASYNC_STARTED_EVENT,
|
|
@@ -53,11 +54,12 @@ import {
|
|
|
53
54
|
import { nestedResultsPath, nestedSummaryFromAsyncStatus, resolveInheritedNestedRouteFromEnv, resolveNestedParentAddressFromEnv, writeNestedEvent } from "../shared/nested-events.ts";
|
|
54
55
|
import { appendTurnBudgetSystemPrompt, initialTurnBudgetState } from "../shared/turn-budget.ts";
|
|
55
56
|
import { validateToolBudgetConfig } from "../shared/tool-budget.ts";
|
|
57
|
+
import { usageBudgetState } from "../shared/usage-budget.ts";
|
|
56
58
|
import type { ImportedAsyncRoot } from "./chain-root-attachment.ts";
|
|
57
59
|
import type { SessionLeaseRequest } from "../shared/session-lease.ts";
|
|
58
60
|
import { finalizeProcessTerminal, readProcessTerminal } from "./process-terminal.ts";
|
|
59
61
|
import { SUBAGENT_PROCESS_TERMINAL_EVENT } from "../../shared/types.ts";
|
|
60
|
-
import { decodeSubagentCapabilityCeiling, resolveCurrentSubagentCapabilityCeiling, SUBAGENT_CAPABILITY_CEILING_ENV, type ResolvedSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
|
|
62
|
+
import { assertAgentAllowedByCapabilityCeiling, decodeSubagentCapabilityCeiling, intersectSubagentCapabilityCeilings, resolveCurrentSubagentCapabilityCeiling, SUBAGENT_CAPABILITY_CEILING_ENV, type ResolvedSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
|
|
61
63
|
import { agentDefinitionDigest, launchBindingDigest } from "../../shared/launch-contract.ts";
|
|
62
64
|
|
|
63
65
|
const require = createRequire(import.meta.url);
|
|
@@ -159,6 +161,7 @@ interface AsyncChainParams {
|
|
|
159
161
|
timeoutMs?: number;
|
|
160
162
|
turnBudget?: ResolvedTurnBudget;
|
|
161
163
|
toolBudget?: ResolvedToolBudget;
|
|
164
|
+
usageBudget?: UsageBudgetConfig;
|
|
162
165
|
configToolBudget?: ResolvedToolBudget;
|
|
163
166
|
/** Global cap on simultaneously-running subagent tasks within the async run. */
|
|
164
167
|
globalConcurrencyLimit?: number;
|
|
@@ -205,6 +208,7 @@ interface AsyncSingleParams {
|
|
|
205
208
|
absoluteDeadlineAt?: number;
|
|
206
209
|
turnBudget?: ResolvedTurnBudget;
|
|
207
210
|
toolBudget?: ResolvedToolBudget;
|
|
211
|
+
usageBudget?: UsageBudgetConfig;
|
|
208
212
|
configToolBudget?: ResolvedToolBudget;
|
|
209
213
|
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
210
214
|
}
|
|
@@ -592,11 +596,13 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
592
596
|
: chain;
|
|
593
597
|
const firstStep = chain[0];
|
|
594
598
|
const originalTask = params.task ?? (firstStep
|
|
595
|
-
? (
|
|
596
|
-
?
|
|
597
|
-
:
|
|
598
|
-
? firstStep.parallel
|
|
599
|
-
: (firstStep
|
|
599
|
+
? (isCheckpointStep(firstStep)
|
|
600
|
+
? undefined
|
|
601
|
+
: isParallelStep(firstStep)
|
|
602
|
+
? firstStep.parallel[0]?.task
|
|
603
|
+
: isDynamicParallelStep(firstStep)
|
|
604
|
+
? firstStep.parallel.task
|
|
605
|
+
: (firstStep as SequentialStep).task)
|
|
600
606
|
: undefined);
|
|
601
607
|
try {
|
|
602
608
|
if (params.validateOutputBindings !== false) {
|
|
@@ -609,11 +615,13 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
609
615
|
const workflowGraph = buildWorkflowGraphSnapshot({ runId: id, mode: resultMode, steps: graphChain });
|
|
610
616
|
|
|
611
617
|
for (const s of chain) {
|
|
612
|
-
const stepAgents =
|
|
613
|
-
?
|
|
614
|
-
:
|
|
615
|
-
?
|
|
616
|
-
:
|
|
618
|
+
const stepAgents = isCheckpointStep(s)
|
|
619
|
+
? []
|
|
620
|
+
: isParallelStep(s)
|
|
621
|
+
? s.parallel.map((t) => t.agent)
|
|
622
|
+
: isDynamicParallelStep(s)
|
|
623
|
+
? [s.parallel.agent]
|
|
624
|
+
: [(s as SequentialStep).agent];
|
|
617
625
|
for (const agentName of stepAgents) {
|
|
618
626
|
if (!agents.find((x) => x.name === agentName)) {
|
|
619
627
|
return { error: `Unknown agent: ${agentName}` };
|
|
@@ -635,6 +643,11 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
635
643
|
};
|
|
636
644
|
const buildSeqStep = (s: SequentialStep, sessionFile?: string, behaviorCwd?: string, progressPrecreated = false, resolvedBehavior?: ResolvedStepBehavior, flatIndex?: number, parallelOutputNamespace?: { stepIndex: number; taskIndex?: number }) => {
|
|
637
645
|
const a = agents.find((x) => x.name === s.agent)!;
|
|
646
|
+
try {
|
|
647
|
+
assertAgentAllowedByCapabilityCeiling(a.name, intersectSubagentCapabilityCeilings(params.capabilityCeiling, decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV])));
|
|
648
|
+
} catch (error) {
|
|
649
|
+
throw new AsyncStartValidationError(error instanceof Error ? error.message : String(error));
|
|
650
|
+
}
|
|
638
651
|
const toolBudgetInput = s.toolBudget ?? params.toolBudget ?? a.toolBudget ?? params.configToolBudget;
|
|
639
652
|
const resolvedToolBudget = validateToolBudgetConfig(toolBudgetInput, s.toolBudget ? "toolBudget" : a.toolBudget ? "agent.toolBudget" : "config.toolBudget");
|
|
640
653
|
if (resolvedToolBudget.error) throw new AsyncStartValidationError(resolvedToolBudget.error);
|
|
@@ -695,6 +708,19 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
695
708
|
const effectiveThinking = thinkingOverride ?? a.thinking;
|
|
696
709
|
const model = applyThinkingSuffix(primaryModel, effectiveThinking, thinkingOverride !== undefined);
|
|
697
710
|
const agentContract = s.agentContract ?? params.agentContract;
|
|
711
|
+
const toolPlan = resolvePiLaunchToolPlan({
|
|
712
|
+
tools: a.tools,
|
|
713
|
+
extensions: a.extensions,
|
|
714
|
+
subagentOnlyExtensions: a.subagentOnlyExtensions,
|
|
715
|
+
mcpDirectTools: a.mcpDirectTools,
|
|
716
|
+
cwd: stepCwd,
|
|
717
|
+
requireReadTool: Boolean(resolvedSkills.length),
|
|
718
|
+
structuredOutput: Boolean(s.outputSchema),
|
|
719
|
+
capabilityCeiling: params.capabilityCeiling,
|
|
720
|
+
inheritedCapabilityCeiling: decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV]),
|
|
721
|
+
agentName: a.name,
|
|
722
|
+
});
|
|
723
|
+
const launchResolvedExtensions = projectLaunchResolvedChildExtensions(toolPlan);
|
|
698
724
|
return {
|
|
699
725
|
parentSessionId: ctx.parentSessionId ?? ctx.currentSessionId,
|
|
700
726
|
...(params.capabilityCeiling ? { capabilityCeiling: params.capabilityCeiling } : {}),
|
|
@@ -709,6 +735,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
709
735
|
cwd: stepCwd,
|
|
710
736
|
model,
|
|
711
737
|
thinking: resolveEffectiveThinking(model, effectiveThinking),
|
|
738
|
+
launchResolvedExtensions,
|
|
712
739
|
modelCandidates: buildModelCandidates(primaryModel, a.fallbackModels, availableModels, ctx.currentModelProvider, { scope: ctx.modelScope }).map((candidate) =>
|
|
713
740
|
applyThinkingSuffix(candidate, effectiveThinking, thinkingOverride !== undefined),
|
|
714
741
|
),
|
|
@@ -762,6 +789,9 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
762
789
|
|
|
763
790
|
try {
|
|
764
791
|
const builtSteps = chain.map((s, stepIndex) => {
|
|
792
|
+
if (isCheckpointStep(s)) {
|
|
793
|
+
return { checkpoint: s.checkpoint, ...(s.message ? { message: s.message } : {}), phase: s.phase, label: s.label };
|
|
794
|
+
}
|
|
765
795
|
if (isParallelStep(s)) {
|
|
766
796
|
const parallelBehaviors = s.parallel.map((task) => {
|
|
767
797
|
const agent = agents.find((candidate) => candidate.name === task.agent)!;
|
|
@@ -948,6 +978,7 @@ export function executeAsyncChain(
|
|
|
948
978
|
const { steps, runnerCwd, workflowGraph, eventChain } = built;
|
|
949
979
|
const deadlineAt = params.timeoutMs !== undefined ? Date.now() + params.timeoutMs : undefined;
|
|
950
980
|
const initialTurnBudget = params.turnBudget ? initialTurnBudgetState(params.turnBudget) : undefined;
|
|
981
|
+
const initialUsageBudget = usageBudgetState(params.usageBudget, undefined);
|
|
951
982
|
let childTargetIndex = 0;
|
|
952
983
|
const childIntercomTargets = childIntercomTarget ? steps.flatMap((step) => {
|
|
953
984
|
if (!("parallel" in step) && step.importAsyncRoot) {
|
|
@@ -989,6 +1020,7 @@ export function executeAsyncChain(
|
|
|
989
1020
|
controlConfig,
|
|
990
1021
|
turnBudget: params.turnBudget,
|
|
991
1022
|
toolBudget: params.toolBudget,
|
|
1023
|
+
usageBudget: params.usageBudget,
|
|
992
1024
|
controlIntercomTarget,
|
|
993
1025
|
childIntercomTargets,
|
|
994
1026
|
resultMode,
|
|
@@ -1106,6 +1138,7 @@ export function executeAsyncChain(
|
|
|
1106
1138
|
asyncDir,
|
|
1107
1139
|
...(params.timeoutMs !== undefined ? { timeoutMs: params.timeoutMs, deadlineAt } : {}),
|
|
1108
1140
|
...(initialTurnBudget ? { turnBudget: initialTurnBudget } : {}),
|
|
1141
|
+
...(initialUsageBudget ? { usageBudget: initialUsageBudget } : {}),
|
|
1109
1142
|
...(capabilityCeiling ? { capabilityCeiling } : {}),
|
|
1110
1143
|
nestedRoute,
|
|
1111
1144
|
});
|
|
@@ -1119,7 +1152,7 @@ export function executeAsyncChain(
|
|
|
1119
1152
|
|
|
1120
1153
|
return {
|
|
1121
1154
|
content: [{ type: "text", text: formatAsyncStartedMessage(`Async ${resultMode}: ${chainDesc} [${id}]`, ctx.interactive === true) }],
|
|
1122
|
-
details: { mode: resultMode, runId: id, results: [], asyncId: id, asyncDir, workflowGraph, ...(capabilityCeiling ? { capabilityCeiling } : {}), ...(params.timeoutMs !== undefined ? { timeoutMs: params.timeoutMs, deadlineAt } : {}), ...(params.turnBudget ? { turnBudget: params.turnBudget } : {}), ...(params.toolBudget ? { toolBudget: params.toolBudget } : {}) },
|
|
1155
|
+
details: { mode: resultMode, runId: id, results: [], asyncId: id, asyncDir, workflowGraph, ...(capabilityCeiling ? { capabilityCeiling } : {}), ...(params.timeoutMs !== undefined ? { timeoutMs: params.timeoutMs, deadlineAt } : {}), ...(params.turnBudget ? { turnBudget: params.turnBudget } : {}), ...(params.toolBudget ? { toolBudget: params.toolBudget } : {}), ...(initialUsageBudget ? { usageBudget: initialUsageBudget } : {}) },
|
|
1123
1156
|
};
|
|
1124
1157
|
}
|
|
1125
1158
|
|
|
@@ -1151,7 +1184,12 @@ export function executeAsyncSingle(
|
|
|
1151
1184
|
nestedRoute,
|
|
1152
1185
|
} = params;
|
|
1153
1186
|
const task = params.task ?? "";
|
|
1154
|
-
const capabilityCeiling = params.capabilityCeiling ?? resolveCurrentSubagentCapabilityCeiling(ctx.currentSessionId);
|
|
1187
|
+
const capabilityCeiling = intersectSubagentCapabilityCeilings(params.capabilityCeiling ?? resolveCurrentSubagentCapabilityCeiling(ctx.currentSessionId), decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV]));
|
|
1188
|
+
try {
|
|
1189
|
+
assertAgentAllowedByCapabilityCeiling(agentConfig.name, capabilityCeiling);
|
|
1190
|
+
} catch (error) {
|
|
1191
|
+
return formatAsyncStartError("single", error instanceof Error ? error.message : String(error));
|
|
1192
|
+
}
|
|
1155
1193
|
const runnerCwd = resolveChildCwd(ctx.cwd, cwd);
|
|
1156
1194
|
const skillNames = params.skills ?? agentConfig.skills ?? [];
|
|
1157
1195
|
const availableModels = params.availableModels;
|
|
@@ -1213,6 +1251,7 @@ export function executeAsyncSingle(
|
|
|
1213
1251
|
: params.timeoutMs;
|
|
1214
1252
|
if (timeoutMs !== undefined && timeoutMs <= 0) return formatAsyncStartError("single", "The source run's absolute deadline expired before recovery could launch.");
|
|
1215
1253
|
const initialTurnBudget = params.turnBudget ? initialTurnBudgetState(params.turnBudget) : undefined;
|
|
1254
|
+
const initialUsageBudget = usageBudgetState(params.usageBudget, undefined);
|
|
1216
1255
|
const resolvedSessionDir = params.sessionDir ?? (sessionRoot ? path.join(sessionRoot, `async-${id}`) : undefined);
|
|
1217
1256
|
const structuredOutput = params.structuredOutputSchema
|
|
1218
1257
|
? createStructuredOutputRuntime(params.structuredOutputSchema, path.join(asyncDir, "structured-output"))
|
|
@@ -1231,7 +1270,9 @@ export function executeAsyncSingle(
|
|
|
1231
1270
|
structuredOutput: Boolean(params.structuredOutputSchema),
|
|
1232
1271
|
capabilityCeiling,
|
|
1233
1272
|
inheritedCapabilityCeiling: decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV]),
|
|
1273
|
+
agentName: agentConfig.name,
|
|
1234
1274
|
});
|
|
1275
|
+
const launchResolvedExtensions = projectLaunchResolvedChildExtensions(toolPlan);
|
|
1235
1276
|
const launchContractDigest = launchBindingDigest({
|
|
1236
1277
|
definitionDigest: agentDefinitionDigest(agentConfig),
|
|
1237
1278
|
task,
|
|
@@ -1265,6 +1306,7 @@ export function executeAsyncSingle(
|
|
|
1265
1306
|
sourceRunId: id,
|
|
1266
1307
|
...(params.agentContract ? { agentContract: params.agentContract } : {}),
|
|
1267
1308
|
agent,
|
|
1309
|
+
launchResolvedExtensions,
|
|
1268
1310
|
...(sessionFile ? { sessionFile } : {}),
|
|
1269
1311
|
cwd: runnerCwd,
|
|
1270
1312
|
...(model ? { model } : {}),
|
|
@@ -1339,6 +1381,7 @@ export function executeAsyncSingle(
|
|
|
1339
1381
|
definitionDigest: agentDefinitionDigest(agentConfig),
|
|
1340
1382
|
launchBindingTask: task,
|
|
1341
1383
|
launchContractDigest,
|
|
1384
|
+
launchResolvedExtensions,
|
|
1342
1385
|
effectiveAcceptance: resolvedAcceptance,
|
|
1343
1386
|
...(structuredOutput ? { structuredOutput } : {}),
|
|
1344
1387
|
...(params.structuredOutputSchema ? { structuredOutputSchema: params.structuredOutputSchema } : {}),
|
|
@@ -1366,10 +1409,12 @@ export function executeAsyncSingle(
|
|
|
1366
1409
|
deadlineAt,
|
|
1367
1410
|
turnBudget: params.turnBudget,
|
|
1368
1411
|
toolBudget: params.toolBudget,
|
|
1412
|
+
usageBudget: params.usageBudget,
|
|
1369
1413
|
controlIntercomTarget,
|
|
1370
1414
|
childIntercomTargets: childIntercomTarget ? [childIntercomTarget(agent, 0)] : undefined,
|
|
1371
1415
|
resultMode: "single",
|
|
1372
1416
|
launchContractDigest,
|
|
1417
|
+
launchResolvedExtensions,
|
|
1373
1418
|
...(params.revivalLease ? { revivalLease: params.revivalLease } : {}),
|
|
1374
1419
|
nestedRoute: nestedRoute ?? inheritedNestedRoute,
|
|
1375
1420
|
nestedSelf: inheritedNestedRoute && nestedAddress ? {
|
|
@@ -1441,8 +1486,10 @@ export function executeAsyncSingle(
|
|
|
1441
1486
|
cwd: runnerCwd,
|
|
1442
1487
|
asyncDir,
|
|
1443
1488
|
launchContractDigest,
|
|
1489
|
+
launchResolvedExtensions,
|
|
1444
1490
|
...(timeoutMs !== undefined ? { timeoutMs, deadlineAt } : {}),
|
|
1445
1491
|
...(initialTurnBudget ? { turnBudget: initialTurnBudget } : {}),
|
|
1492
|
+
...(initialUsageBudget ? { usageBudget: initialUsageBudget } : {}),
|
|
1446
1493
|
...(capabilityCeiling ? { capabilityCeiling } : {}),
|
|
1447
1494
|
nestedRoute,
|
|
1448
1495
|
});
|
|
@@ -1450,6 +1497,6 @@ export function executeAsyncSingle(
|
|
|
1450
1497
|
|
|
1451
1498
|
return {
|
|
1452
1499
|
content: [{ type: "text", text: formatAsyncStartedMessage(`Async: ${agent} [${id}]`, ctx.interactive === true) }],
|
|
1453
|
-
details: { mode: "single", runId: id, results: [], asyncId: id, asyncDir, launchContractDigest, ...(capabilityCeiling ? { capabilityCeiling } : {}), ...(params.context ? { context: params.context } : {}), ...(timeoutMs !== undefined ? { timeoutMs, deadlineAt } : {}), ...(params.turnBudget ? { turnBudget: params.turnBudget } : {}), ...(params.toolBudget ? { toolBudget: params.toolBudget } : {}) },
|
|
1500
|
+
details: { mode: "single", runId: id, results: [], asyncId: id, asyncDir, launchContractDigest, launchResolvedExtensions, ...(capabilityCeiling ? { capabilityCeiling } : {}), ...(params.context ? { context: params.context } : {}), ...(timeoutMs !== undefined ? { timeoutMs, deadlineAt } : {}), ...(params.turnBudget ? { turnBudget: params.turnBudget } : {}), ...(params.toolBudget ? { toolBudget: params.toolBudget } : {}), ...(initialUsageBudget ? { usageBudget: initialUsageBudget } : {}) },
|
|
1454
1501
|
};
|
|
1455
1502
|
}
|
|
@@ -165,6 +165,10 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
165
165
|
const startedFromTail = savedCursor === undefined && stat.size > CONTROL_EVENT_SCAN_WINDOW_BYTES;
|
|
166
166
|
if (startedFromTail) cursor = stat.size - CONTROL_EVENT_SCAN_WINDOW_BYTES;
|
|
167
167
|
if (stat.size <= cursor) return;
|
|
168
|
+
const previousByte = Buffer.alloc(1);
|
|
169
|
+
const startsMidLine = cursor > 0
|
|
170
|
+
&& fs.readSync(fd, previousByte, 0, 1, cursor - 1) === 1
|
|
171
|
+
&& previousByte[0] !== 0x0a;
|
|
168
172
|
const scanEnd = Math.min(stat.size, cursor + CONTROL_EVENT_SCAN_WINDOW_BYTES);
|
|
169
173
|
const handleLine = (line: string) => {
|
|
170
174
|
if (!line.trim()) return;
|
|
@@ -217,7 +221,7 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
217
221
|
let lastCompleteCursor = cursor;
|
|
218
222
|
let lineParts: Buffer[] = [];
|
|
219
223
|
let lineBytes = 0;
|
|
220
|
-
let skippingOversizedLine = startedFromTail;
|
|
224
|
+
let skippingOversizedLine = startedFromTail || startsMidLine;
|
|
221
225
|
const appendLineSegment = (segment: Buffer) => {
|
|
222
226
|
if (segment.length === 0 || skippingOversizedLine) return;
|
|
223
227
|
if (lineBytes + segment.length > MAX_CONTROL_EVENT_LINE_BYTES) {
|
|
@@ -290,6 +290,7 @@ export function readAsyncRecoveryDescriptor(asyncDir: string | undefined): Steer
|
|
|
290
290
|
"subagentOnlyExtensions", "mcpDirectTools", "systemPrompt", "systemPromptMode", "inheritProjectContext", "inheritSkills", "skills",
|
|
291
291
|
"skillPath", "agentFilePath", "completionGuard", "memory", "outputPath", "outputMode", "structuredOutputSchema", "acceptance", "sessionDir", "artifactConfig",
|
|
292
292
|
"artifactsDir", "maxOutput", "controlConfig", "absoluteDeadlineAt", "initialTurnBudget", "initialToolBudget", "maxSubagentDepth", "share", "capabilityCeiling",
|
|
293
|
+
"launchResolvedExtensions",
|
|
293
294
|
]);
|
|
294
295
|
for (const field of Object.keys(parsed)) {
|
|
295
296
|
if (!allowedFields.has(field)) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': unknown field '${field}'.`);
|
|
@@ -2,7 +2,7 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { formatDuration, formatModelThinking, formatTokens, shortenPath } from "../../shared/formatters.ts";
|
|
4
4
|
import { formatActivityLabel, formatParallelOutcome } from "../../shared/status-format.ts";
|
|
5
|
-
import { type ActivityState, type AsyncJobStep, type AsyncParallelGroupStatus, type AsyncStatus, type CostSummary, type NestedRunSummary, type SteeringStatus, type SubagentRunMode, type TokenUsage, type TurnBudgetState } from "../../shared/types.ts";
|
|
5
|
+
import { type ActivityState, type AsyncJobStep, type AsyncParallelGroupStatus, type AsyncStatus, type CostSummary, type LaunchResolvedChildExtensionsV1, type RuntimeAcknowledgedChildExtensionsV1, type NestedRunSummary, type SteeringStatus, type SubagentRunMode, type TokenUsage, type TurnBudgetState, type UsageBudgetState, type ChainCheckpointState } from "../../shared/types.ts";
|
|
6
6
|
import type { ResolvedSubagentCapabilityCeiling, SubagentCapabilityAudit } from "../shared/capability-ceiling.ts";
|
|
7
7
|
import { readStatus } from "../../shared/utils.ts";
|
|
8
8
|
import { attachRootChildrenToSteps, buildNestedRouteIndex, type NestedRoute, projectNestedEvents } from "../shared/nested-events.ts";
|
|
@@ -17,9 +17,11 @@ interface AsyncRunStepSummary {
|
|
|
17
17
|
agent: string;
|
|
18
18
|
context?: ContextMode;
|
|
19
19
|
label?: string;
|
|
20
|
+
description?: string;
|
|
20
21
|
phase?: string;
|
|
21
22
|
outputName?: string;
|
|
22
23
|
structured?: boolean;
|
|
24
|
+
checkpoint?: ChainCheckpointState;
|
|
23
25
|
status: AsyncJobStep["status"];
|
|
24
26
|
activityState?: ActivityState;
|
|
25
27
|
lastActivityAt?: number;
|
|
@@ -53,6 +55,8 @@ interface AsyncRunStepSummary {
|
|
|
53
55
|
review?: AsyncJobStep["review"];
|
|
54
56
|
effects?: AsyncJobStep["effects"];
|
|
55
57
|
processTerminal?: AsyncJobStep["processTerminal"];
|
|
58
|
+
launchResolvedExtensions?: LaunchResolvedChildExtensionsV1;
|
|
59
|
+
runtimeAcknowledgedExtensions?: RuntimeAcknowledgedChildExtensionsV1;
|
|
56
60
|
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
57
61
|
capabilityAudit?: SubagentCapabilityAudit;
|
|
58
62
|
children?: NestedRunSummary[];
|
|
@@ -62,7 +66,7 @@ export interface AsyncRunSummary {
|
|
|
62
66
|
id: string;
|
|
63
67
|
asyncDir: string;
|
|
64
68
|
sessionId?: string;
|
|
65
|
-
state: "queued" | "running" | "complete" | "failed" | "paused" | "stopped";
|
|
69
|
+
state: "queued" | "running" | "complete" | "failed" | "paused" | "stopped" | "rejected";
|
|
66
70
|
error?: string;
|
|
67
71
|
activityState?: ActivityState;
|
|
68
72
|
lastActivityAt?: number;
|
|
@@ -90,14 +94,18 @@ export interface AsyncRunSummary {
|
|
|
90
94
|
pendingAppends?: number;
|
|
91
95
|
parallelGroups?: AsyncParallelGroupStatus[];
|
|
92
96
|
steps: AsyncRunStepSummary[];
|
|
97
|
+
checkpoint?: ChainCheckpointState;
|
|
93
98
|
sessionDir?: string;
|
|
94
99
|
outputFile?: string;
|
|
95
100
|
totalTokens?: TokenUsage;
|
|
96
101
|
totalCost?: CostSummary;
|
|
102
|
+
usageBudget?: UsageBudgetState;
|
|
97
103
|
sessionFile?: string;
|
|
98
104
|
nestedChildren?: NestedRunSummary[];
|
|
99
105
|
nestedWarnings?: string[];
|
|
100
106
|
processTerminal?: AsyncStatus["processTerminal"];
|
|
107
|
+
launchResolvedExtensions?: LaunchResolvedChildExtensionsV1;
|
|
108
|
+
runtimeAcknowledgedExtensions?: RuntimeAcknowledgedChildExtensionsV1;
|
|
101
109
|
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
102
110
|
capabilityAudit?: SubagentCapabilityAudit;
|
|
103
111
|
}
|
|
@@ -225,9 +233,11 @@ function statusToSummary(asyncDir: string, status: AsyncStatus & { cwd?: string
|
|
|
225
233
|
agent: step.agent,
|
|
226
234
|
...(step.context ? { context: step.context } : {}),
|
|
227
235
|
...(step.label ? { label: step.label } : {}),
|
|
236
|
+
...(step.description ? { description: step.description } : {}),
|
|
228
237
|
...(step.phase ? { phase: step.phase } : {}),
|
|
229
238
|
...(step.outputName ? { outputName: step.outputName } : {}),
|
|
230
239
|
...(step.structured ? { structured: step.structured } : {}),
|
|
240
|
+
...(step.checkpoint ? { checkpoint: step.checkpoint } : {}),
|
|
231
241
|
status: step.status,
|
|
232
242
|
...(stepActivityState ? { activityState: stepActivityState } : {}),
|
|
233
243
|
...(stepLastActivityAt ? { lastActivityAt: stepLastActivityAt } : {}),
|
|
@@ -258,6 +268,8 @@ function statusToSummary(asyncDir: string, status: AsyncStatus & { cwd?: string
|
|
|
258
268
|
...(step.acceptance ? { acceptance: step.acceptance } : {}),
|
|
259
269
|
...(step.agentContract ? { agentContract: step.agentContract } : {}),
|
|
260
270
|
...(step.launchContractDigest ? { launchContractDigest: step.launchContractDigest } : {}),
|
|
271
|
+
...(step.launchResolvedExtensions ? { launchResolvedExtensions: step.launchResolvedExtensions } : {}),
|
|
272
|
+
...(step.runtimeAcknowledgedExtensions ? { runtimeAcknowledgedExtensions: step.runtimeAcknowledgedExtensions } : {}),
|
|
261
273
|
...(step.execution ? { execution: step.execution } : {}),
|
|
262
274
|
...(step.review ? { review: step.review } : {}),
|
|
263
275
|
...(step.effects ? { effects: step.effects } : {}),
|
|
@@ -300,16 +312,20 @@ function statusToSummary(asyncDir: string, status: AsyncStatus & { cwd?: string
|
|
|
300
312
|
...(status.pendingAppends !== undefined ? { pendingAppends: status.pendingAppends } : {}),
|
|
301
313
|
...(parallelGroups.length ? { parallelGroups } : {}),
|
|
302
314
|
steps: summarizedSteps,
|
|
315
|
+
...(status.checkpoint ? { checkpoint: status.checkpoint } : {}),
|
|
303
316
|
...(nestedChildren.length ? { nestedChildren } : {}),
|
|
304
317
|
...(nestedWarnings.length ? { nestedWarnings } : {}),
|
|
305
318
|
...(processTerminal ? { processTerminal } : {}),
|
|
306
319
|
...(status.launchContractDigest ? { launchContractDigest: status.launchContractDigest } : {}),
|
|
320
|
+
...(status.launchResolvedExtensions ? { launchResolvedExtensions: status.launchResolvedExtensions } : {}),
|
|
321
|
+
...(status.runtimeAcknowledgedExtensions ? { runtimeAcknowledgedExtensions: status.runtimeAcknowledgedExtensions } : {}),
|
|
307
322
|
...(status.capabilityCeiling ? { capabilityCeiling: status.capabilityCeiling } : {}),
|
|
308
323
|
...(status.capabilityAudit ? { capabilityAudit: status.capabilityAudit } : {}),
|
|
309
324
|
...(status.sessionDir ? { sessionDir: status.sessionDir } : {}),
|
|
310
325
|
...(status.outputFile ? { outputFile: status.outputFile } : {}),
|
|
311
326
|
...(status.totalTokens ? { totalTokens: status.totalTokens } : {}),
|
|
312
327
|
...(status.totalCost ? { totalCost: status.totalCost } : {}),
|
|
328
|
+
...(status.usageBudget ? { usageBudget: status.usageBudget } : {}),
|
|
313
329
|
...(status.sessionFile ? { sessionFile: status.sessionFile } : {}),
|
|
314
330
|
};
|
|
315
331
|
}
|
|
@@ -5,8 +5,8 @@ import { writeAtomicJson } from "../../shared/atomic-json.ts";
|
|
|
5
5
|
import { appendJsonl } from "../../shared/artifacts.ts";
|
|
6
6
|
import type { AsyncParallelGroupStatus, AsyncStatus, WorkflowGraphNode, WorkflowGraphSnapshot } from "../../shared/types.ts";
|
|
7
7
|
import { readStatus } from "../../shared/utils.ts";
|
|
8
|
-
import type { DynamicRunnerGroup, ParallelStepGroup, RunnerStep, RunnerSubagentStep } from "../shared/parallel-utils.ts";
|
|
9
|
-
import { isDynamicRunnerGroup, isParallelGroup } from "../shared/parallel-utils.ts";
|
|
8
|
+
import type { DynamicRunnerGroup, ParallelStepGroup, RunnerStep, RunnerSubagentStep, RunnerCheckpointStep } from "../shared/parallel-utils.ts";
|
|
9
|
+
import { isCheckpointRunnerStep, isDynamicRunnerGroup, isParallelGroup } from "../shared/parallel-utils.ts";
|
|
10
10
|
|
|
11
11
|
const APPEND_REQUESTS_DIR = "append-requests";
|
|
12
12
|
|
|
@@ -51,7 +51,9 @@ export function countPendingChainAppendRequests(asyncDir: string): number {
|
|
|
51
51
|
export function runnerStepOutputNames(steps: RunnerStep[]): string[] {
|
|
52
52
|
const names: string[] = [];
|
|
53
53
|
for (const step of steps) {
|
|
54
|
-
if (
|
|
54
|
+
if (isCheckpointRunnerStep(step)) {
|
|
55
|
+
continue;
|
|
56
|
+
} else if (isParallelGroup(step)) {
|
|
55
57
|
names.push(...step.parallel.map((task) => task.outputName).filter((name): name is string => Boolean(name)));
|
|
56
58
|
} else if (isDynamicRunnerGroup(step)) {
|
|
57
59
|
if (step.collect.as) names.push(step.collect.as);
|
|
@@ -128,9 +130,22 @@ export function consumeChainAppendRequests(asyncDir: string): ChainAppendRequest
|
|
|
128
130
|
return requests.sort((left, right) => left.createdAt - right.createdAt || left.id.localeCompare(right.id));
|
|
129
131
|
}
|
|
130
132
|
|
|
133
|
+
const MAX_STATUS_STEP_DESCRIPTION_CHARS = 160;
|
|
134
|
+
|
|
135
|
+
/** Bounded one-line per-step task description persisted into status.json for fleet display. */
|
|
136
|
+
export function statusStepDescription(task: string | undefined): string | undefined {
|
|
137
|
+
const description = task?.replace(/\s+/g, " ").trim();
|
|
138
|
+
if (!description) return undefined;
|
|
139
|
+
return description.length > MAX_STATUS_STEP_DESCRIPTION_CHARS
|
|
140
|
+
? `${description.slice(0, MAX_STATUS_STEP_DESCRIPTION_CHARS - 1)}…`
|
|
141
|
+
: description;
|
|
142
|
+
}
|
|
143
|
+
|
|
131
144
|
function statusStepForTask(task: RunnerSubagentStep): StatusStep {
|
|
145
|
+
const description = statusStepDescription(task.task);
|
|
132
146
|
return {
|
|
133
147
|
agent: task.agent,
|
|
148
|
+
...(description ? { description } : {}),
|
|
134
149
|
...(task.context ? { context: task.context } : {}),
|
|
135
150
|
phase: task.phase,
|
|
136
151
|
label: task.label,
|
|
@@ -147,7 +162,20 @@ function statusStepForTask(task: RunnerSubagentStep): StatusStep {
|
|
|
147
162
|
};
|
|
148
163
|
}
|
|
149
164
|
|
|
150
|
-
function
|
|
165
|
+
function statusStepForCheckpoint(step: RunnerCheckpointStep, stepIndex: number): StatusStep {
|
|
166
|
+
return {
|
|
167
|
+
agent: `checkpoint:${step.checkpoint}`,
|
|
168
|
+
phase: step.phase,
|
|
169
|
+
label: step.label ?? step.checkpoint,
|
|
170
|
+
status: "pending",
|
|
171
|
+
checkpoint: { name: step.checkpoint, ...(step.message ? { message: step.message } : {}), status: "pending", stepIndex },
|
|
172
|
+
recentTools: [],
|
|
173
|
+
recentOutput: [],
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function statusStepsForRunnerStep(step: RunnerStep, stepIndex: number): StatusStep[] {
|
|
178
|
+
if (isCheckpointRunnerStep(step)) return [statusStepForCheckpoint(step, stepIndex)];
|
|
151
179
|
if (isParallelGroup(step)) return step.parallel.map(statusStepForTask);
|
|
152
180
|
if (isDynamicRunnerGroup(step)) {
|
|
153
181
|
return [{
|
|
@@ -239,6 +267,21 @@ function graphNodeForDynamic(step: DynamicRunnerGroup, stepIndex: number): Workf
|
|
|
239
267
|
|
|
240
268
|
function appendWorkflowNode(graph: WorkflowGraphSnapshot | undefined, step: RunnerStep, stepIndex: number, flatIndex: number): void {
|
|
241
269
|
if (!graph) return;
|
|
270
|
+
if (isCheckpointRunnerStep(step)) {
|
|
271
|
+
const node: WorkflowGraphNode = {
|
|
272
|
+
id: `step-${stepIndex}`,
|
|
273
|
+
kind: "checkpoint",
|
|
274
|
+
phase: step.phase,
|
|
275
|
+
label: step.label?.trim() || step.checkpoint,
|
|
276
|
+
status: "pending",
|
|
277
|
+
flatIndex,
|
|
278
|
+
stepIndex,
|
|
279
|
+
checkpoint: { name: step.checkpoint, ...(step.message ? { message: step.message } : {}), status: "pending", stepIndex },
|
|
280
|
+
};
|
|
281
|
+
graph.nodes.push(node);
|
|
282
|
+
pushPhase(graph, step.phase, node.id);
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
242
285
|
if (isParallelGroup(step)) {
|
|
243
286
|
graph.nodes.push(graphNodeForParallel(step, stepIndex, flatIndex, graph));
|
|
244
287
|
return;
|
|
@@ -263,7 +306,7 @@ export function appendRunnerStepsToStatus(input: {
|
|
|
263
306
|
for (const step of input.steps) {
|
|
264
307
|
const stepIndex = input.status.chainStepCount ?? input.status.steps?.length ?? 0;
|
|
265
308
|
const flatIndex = input.status.steps?.length ?? 0;
|
|
266
|
-
const statusSteps = statusStepsForRunnerStep(step);
|
|
309
|
+
const statusSteps = statusStepsForRunnerStep(step, stepIndex);
|
|
267
310
|
input.status.steps ??= [];
|
|
268
311
|
input.status.steps.push(...statusSteps);
|
|
269
312
|
if (isParallelGroup(step)) {
|