pi-subagents 0.45.2 → 0.47.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 +47 -0
- package/README.md +2 -0
- package/docs/agents.md +342 -0
- package/docs/configuration.md +328 -0
- package/docs/extension-api.md +308 -0
- package/docs/missions.md +119 -0
- package/docs/models.md +192 -0
- package/docs/observability.md +174 -0
- package/docs/tool-reference.md +343 -0
- package/docs/watchdog.md +176 -0
- package/docs/workflows.md +163 -0
- package/package.json +4 -2
- package/skills/pi-subagents/references/execution-controls.md +6 -6
- package/skills/pi-subagents/references/management-authoring-rpc.md +1 -1
- package/src/agents/agents.ts +17 -8
- package/src/agents/frontmatter.ts +7 -3
- package/src/agents/skills.ts +2 -9
- package/src/api/project-panes.ts +30 -0
- package/src/extension/config.ts +18 -1
- package/src/extension/fanout-child.ts +5 -4
- package/src/extension/index.ts +66 -19
- package/src/extension/rpc.ts +3 -6
- package/src/extension/schemas.ts +28 -7
- package/src/extension/subagent-guide.ts +39 -0
- package/src/extension/tool-description.ts +30 -12
- package/src/inspectors/herdr/project-panes.ts +459 -63
- package/src/missions/actions.ts +25 -2
- package/src/missions/lifecycle.ts +21 -2
- package/src/missions/store.ts +79 -2
- package/src/missions/types.ts +33 -0
- package/src/missions/workflow-state.ts +19 -13
- package/src/runs/background/async-execution.ts +17 -6
- package/src/runs/background/async-job-tracker.ts +15 -0
- package/src/runs/background/async-resume.ts +19 -3
- package/src/runs/background/async-status.ts +6 -1
- package/src/runs/background/completion-replay.ts +267 -0
- package/src/runs/background/control-channel.ts +36 -0
- package/src/runs/background/result-watcher.ts +28 -6
- package/src/runs/background/scheduled-runs.ts +2 -1
- package/src/runs/background/stale-run-reconciler.ts +2 -21
- package/src/runs/background/subagent-runner.ts +47 -6
- package/src/runs/background/wait-completions.ts +39 -5
- package/src/runs/background/wait-subscriptions.ts +18 -3
- package/src/runs/foreground/async-steering-action.ts +1 -1
- package/src/runs/foreground/chain-execution.ts +3 -0
- package/src/runs/foreground/execution.ts +7 -0
- package/src/runs/foreground/foreground-history.ts +137 -0
- package/src/runs/foreground/subagent-executor.ts +403 -54
- package/src/runs/foreground/workflow-foreground-steering.ts +187 -0
- package/src/runs/shared/dynamic-fanout.ts +1 -1
- package/src/runs/shared/model-fallback.ts +8 -4
- package/src/runs/shared/model-scope.ts +12 -2
- package/src/runs/shared/parallel-utils.ts +1 -0
- package/src/runs/shared/worktree.ts +3 -2
- package/src/shared/artifacts.ts +14 -14
- package/src/shared/display-text.ts +100 -0
- package/src/shared/fork-context.ts +13 -0
- package/src/shared/formatters.ts +4 -6
- package/src/shared/prompt-resources.ts +51 -0
- package/src/shared/settings.ts +15 -2
- package/src/shared/types.ts +41 -2
- package/src/shared/utf8.ts +11 -0
- package/src/shared/utils.ts +43 -33
- package/src/slash/prompt-workflows.ts +2 -15
- package/src/slash/slash-commands.ts +22 -2
- package/src/tui/fleet-status.ts +22 -12
- package/src/tui/fleet.ts +135 -25
- package/src/tui/render.ts +150 -33
- package/src/watchdog/change-signature.ts +4 -3
- package/src/workflows/scripted-workflow.ts +167 -10
|
@@ -4,7 +4,7 @@ import * as path from "node:path";
|
|
|
4
4
|
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
5
5
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
6
6
|
import { resolveAgentName, type AgentConfig, type AgentScope } from "../../agents/agents.ts";
|
|
7
|
-
import { getArtifactsDir, getChainRunsDir, getProjectArtifactPackagingWarning } from "../../shared/artifacts.ts";
|
|
7
|
+
import { getArtifactsDir, getChainRunsDir, getProjectArtifactPackagingWarning, getProjectSubagentsDir } from "../../shared/artifacts.ts";
|
|
8
8
|
import { writeAtomicJson } from "../../shared/atomic-json.ts";
|
|
9
9
|
import { ChainClarifyComponent, type ChainClarifyResult } from "./chain-clarify.ts";
|
|
10
10
|
import { resolveEffectiveThinking, toModelInfo, type ModelInfo } from "../../shared/model-info.ts";
|
|
@@ -17,10 +17,12 @@ import {
|
|
|
17
17
|
settleForegroundSchedulingOwner,
|
|
18
18
|
updateForegroundChild,
|
|
19
19
|
} from "./foreground-control.ts";
|
|
20
|
+
import { persistForegroundRunHistory, MAX_REMEMBERED_FOREGROUND_RUNS } from "./foreground-history.ts";
|
|
20
21
|
import { resolveExecutionAgentScope } from "../../agents/agent-scope.ts";
|
|
21
22
|
import { handleManagementAction } from "../../agents/agent-management.ts";
|
|
22
23
|
import { handleRefinementAction } from "../../agents/agent-refinements.ts";
|
|
23
24
|
import { buildDoctorReport } from "../../extension/doctor.ts";
|
|
25
|
+
import { readSubagentGuide } from "../../extension/subagent-guide.ts";
|
|
24
26
|
import { normalizePublicSubagentExecution } from "../../extension/public-execution.ts";
|
|
25
27
|
import { runSync } from "./execution.ts";
|
|
26
28
|
import { handleWatchdogToolAction, WATCHDOG_TOOL_ACTIONS } from "../../watchdog/tool-actions.ts";
|
|
@@ -37,6 +39,7 @@ import {
|
|
|
37
39
|
isParallelStep,
|
|
38
40
|
isDynamicParallelStep,
|
|
39
41
|
resolveChainPath,
|
|
42
|
+
resolveExistingReadPaths,
|
|
40
43
|
resolveStepBehavior,
|
|
41
44
|
suppressProgressForReadOnlyTask,
|
|
42
45
|
taskDisallowsFileUpdates,
|
|
@@ -49,8 +52,8 @@ import {
|
|
|
49
52
|
type StepOverrides,
|
|
50
53
|
} from "../../shared/settings.ts";
|
|
51
54
|
import { discoverAvailableSkills, normalizeSkillInput } from "../../agents/skills.ts";
|
|
52
|
-
import { buildAsyncRunnerSteps, executeAsyncChain, executeAsyncSingle, formatAsyncStartedMessage, isAsyncAvailable } from "../background/async-execution.ts";
|
|
53
|
-
import type
|
|
55
|
+
import { buildAsyncRunnerSteps, DEFAULT_ASYNC_TIMEOUT_MS, executeAsyncChain, executeAsyncSingle, formatAsyncStartedMessage, isAsyncAvailable, workflowAwaitedAsyncResultPath } from "../background/async-execution.ts";
|
|
56
|
+
import { isScheduledRunAction, type ScheduledRunAction } from "../background/scheduled-runs.ts";
|
|
54
57
|
import { enqueueChainAppendRequest, readPendingChainAppendRequests, runnerStepOutputNames } from "../background/chain-append.ts";
|
|
55
58
|
import { ChainOutputValidationError, validateChainOutputBindingsWithContext } from "../shared/chain-outputs.ts";
|
|
56
59
|
import { normalizeGateAcceptance, validateExecutionAcceptance } from "../shared/acceptance.ts";
|
|
@@ -82,22 +85,31 @@ import { applySteeringRecoveryAgentConfig, buildRevivedAsyncTask, resolveAsyncRe
|
|
|
82
85
|
import { deliverCheckpointDecisionRequest, deliverInterruptRequest, readRevivalBriefs, requestAsyncSteer, type SteerDeliveryMode } from "../background/control-channel.ts";
|
|
83
86
|
import { updateSteeringTarget, waitForSteeringAction } from "../background/steering.ts";
|
|
84
87
|
import { steerAsyncRun } from "./async-steering-action.ts";
|
|
88
|
+
import {
|
|
89
|
+
removeWorkflowForegroundSteeringRoute,
|
|
90
|
+
resolveWorkflowForegroundSteeringTarget,
|
|
91
|
+
steerWorkflowForegroundTarget,
|
|
92
|
+
workflowForegroundSteeringDir,
|
|
93
|
+
workflowForegroundSteeringLaunchOptions,
|
|
94
|
+
} from "./workflow-foreground-steering.ts";
|
|
85
95
|
import { stopAsyncRun } from "./async-stop-action.ts";
|
|
86
96
|
import { reconcileAsyncRun } from "../background/stale-run-reconciler.ts";
|
|
87
|
-
import { resolveAsyncRootResultPath } from "../background/chain-root-attachment.ts";
|
|
97
|
+
import { resolveAsyncRootResultPath, waitForImportedAsyncRoot } from "../background/chain-root-attachment.ts";
|
|
88
98
|
import { attachRootChildrenToSteps, createNestedRoute, findNestedControlResult, resolveInheritedNestedRouteFromEnv, resolveNestedAsyncDir, resolveNestedParentAddressFromEnv, snapshotNestedEventFiles, updateForegroundNestedProjection, writeNestedControlRequest, writeNestedEvent, type NestedRunResolutionScope } from "../shared/nested-events.ts";
|
|
89
99
|
import { resolveSubagentRunId, type ResolvedSubagentRunId } from "../background/run-id-resolver.ts";
|
|
90
100
|
import { formatNestedRunStatusLines } from "../shared/nested-render.ts";
|
|
91
101
|
import { inspectSubagentStatus } from "../background/run-status.ts";
|
|
92
102
|
import { applyForceTopLevelAsyncOverride } from "../background/top-level-async.ts";
|
|
93
103
|
import { handleMissionAction, MISSION_ACTIONS } from "../../missions/actions.ts";
|
|
94
|
-
import { attachMissionToLaunchResult, prepareMissionLaunch, type MissionLaunchBinding } from "../../missions/lifecycle.ts";
|
|
104
|
+
import { attachMissionToLaunchResult, prepareMissionLaunch, writeMissionAsyncBinding, type MissionLaunchBinding } from "../../missions/lifecycle.ts";
|
|
95
105
|
import { updateMission } from "../../missions/store.ts";
|
|
106
|
+
import type { MissionWorkflowChildUpdate } from "../../missions/types.ts";
|
|
96
107
|
import { createMissionWorkflowState } from "../../missions/workflow-state.ts";
|
|
97
108
|
import { resolveAuthorityDecision } from "../../policy/authority.ts";
|
|
98
109
|
import { handleHerdrInspectorAction, HERDR_INSPECTOR_ACTIONS } from "../../inspectors/herdr/actions.ts";
|
|
99
110
|
import { handleHerdrProjectPaneAction, HERDR_PROJECT_PANE_ACTIONS } from "../../inspectors/herdr/project-panes.ts";
|
|
100
111
|
import { previewSimpleWorkflowRun, runWorkflowScript, WorkflowScriptError, type WorkflowScriptChildResult } from "../../workflows/scripted-workflow.ts";
|
|
112
|
+
import { renderWorkflowPrompt } from "../../shared/prompt-resources.ts";
|
|
101
113
|
import { resolveWorkflowChatProgress, type WorkflowChatProgressProjection } from "../../workflows/chat-progress.ts";
|
|
102
114
|
import {
|
|
103
115
|
cleanupWorktrees,
|
|
@@ -150,7 +162,48 @@ import {
|
|
|
150
162
|
wrapForkTask,
|
|
151
163
|
} from "../../shared/types.ts";
|
|
152
164
|
|
|
153
|
-
const MUTATING_MANAGEMENT_ACTIONS = new Set(["create", "update", "delete", "eject", "disable", "enable", "reset", "grant-spawn-budget", "watchdog.configure", "mission.create", "mission.update", "mission.attach-run", "mission.close", "inspector.open", "inspector.close", "project.open", "project.close", "worktree.discard", "refine", "refine.rollback", "schedule.create", "schedule.pause", "schedule.resume", "schedule.run", "schedule.run-due", "schedule.delete"]);
|
|
165
|
+
const MUTATING_MANAGEMENT_ACTIONS = new Set(["create", "update", "delete", "eject", "disable", "enable", "reset", "grant-spawn-budget", "watchdog.configure", "mission.create", "mission.update", "mission.resolve-decision", "mission.attach-run", "mission.close", "inspector.open", "inspector.close", "project.open", "project.close", "worktree.discard", "refine", "refine.rollback", "schedule.create", "schedule.pause", "schedule.resume", "schedule.run", "schedule.run-due", "schedule.delete"]);
|
|
166
|
+
const DESTRUCTIVE_MANAGEMENT_ACTIONS = new Set(["delete", "eject", "disable", "reset", "mission.close", "worktree.discard", "refine.rollback", "inspector.close", "project.close", "stop", "interrupt", "reject-checkpoint", "schedule.delete"]);
|
|
167
|
+
|
|
168
|
+
function editDistance(left: string, right: string): number {
|
|
169
|
+
const previous = Array.from({ length: right.length + 1 }, (_, index) => index);
|
|
170
|
+
for (let leftIndex = 1; leftIndex <= left.length; leftIndex += 1) {
|
|
171
|
+
let diagonal = previous[0]!;
|
|
172
|
+
previous[0] = leftIndex;
|
|
173
|
+
for (let rightIndex = 1; rightIndex <= right.length; rightIndex += 1) {
|
|
174
|
+
const above = previous[rightIndex]!;
|
|
175
|
+
previous[rightIndex] = left[leftIndex - 1] === right[rightIndex - 1]
|
|
176
|
+
? diagonal
|
|
177
|
+
: Math.min(diagonal, above, previous[rightIndex - 1]!) + 1;
|
|
178
|
+
diagonal = above;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return previous[right.length]!;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function hasSingleAdjacentTransposition(left: string, right: string): boolean {
|
|
185
|
+
if (left.length !== right.length) return false;
|
|
186
|
+
const mismatch = [...left].findIndex((character, index) => character !== right[index]);
|
|
187
|
+
return mismatch >= 0
|
|
188
|
+
&& left[mismatch] === right[mismatch + 1]
|
|
189
|
+
&& left[mismatch + 1] === right[mismatch]
|
|
190
|
+
&& left.slice(mismatch + 2) === right.slice(mismatch + 2);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function unknownSubagentActionMessage(action: string): string {
|
|
194
|
+
const requested = action.toLowerCase();
|
|
195
|
+
const suggestion = SUBAGENT_ACTIONS.find((candidate) => {
|
|
196
|
+
const distance = editDistance(requested, candidate);
|
|
197
|
+
const closeMatch = distance <= Math.max(1, Math.floor(candidate.length / 4)) || hasSingleAdjacentTransposition(requested, candidate);
|
|
198
|
+
if (DESTRUCTIVE_MANAGEMENT_ACTIONS.has(candidate)) return distance === 1 && requested.length >= candidate.length - 1;
|
|
199
|
+
return closeMatch;
|
|
200
|
+
});
|
|
201
|
+
const nextStep = 'Use subagent({ action: "status" }) to inspect runs or subagent({ action: "list" }) to inspect agents.';
|
|
202
|
+
const validActions = `Valid: ${SUBAGENT_ACTIONS.join(", ")}.`;
|
|
203
|
+
return suggestion
|
|
204
|
+
? `Unknown action: ${action}. Did you mean ${suggestion}? ${nextStep} ${validActions}`
|
|
205
|
+
: `Unknown action: ${action}. ${nextStep} ${validActions}`;
|
|
206
|
+
}
|
|
154
207
|
|
|
155
208
|
type UndefinedOmitted<T extends object> = {
|
|
156
209
|
[K in keyof T as undefined extends T[K] ? never : K]: T[K];
|
|
@@ -209,6 +262,7 @@ export interface SubagentParamsLike {
|
|
|
209
262
|
index?: number;
|
|
210
263
|
view?: "fleet" | "transcript";
|
|
211
264
|
lines?: number;
|
|
265
|
+
topic?: string;
|
|
212
266
|
chainName?: string;
|
|
213
267
|
config?: unknown;
|
|
214
268
|
name?: string;
|
|
@@ -226,6 +280,7 @@ export interface SubagentParamsLike {
|
|
|
226
280
|
/** Internal workflow ownership metadata; not part of the public schema. */
|
|
227
281
|
workflowParentRunId?: string;
|
|
228
282
|
workflowKey?: string;
|
|
283
|
+
workflowChildAsyncId?: string;
|
|
229
284
|
suppressRoutineResultIntercom?: boolean;
|
|
230
285
|
/** Internal durable-run compatibility fields. Public callers must use workflowScript. */
|
|
231
286
|
chain?: ChainStep[];
|
|
@@ -356,6 +411,7 @@ function resolveRequestedCwd(runtimeCwd: string, requestedCwd: string | undefine
|
|
|
356
411
|
function removeForegroundControlIfIdle(state: SubagentState, runId: string): boolean {
|
|
357
412
|
const control = state.foregroundControls.get(runId);
|
|
358
413
|
if (control && (!foregroundSchedulingSettled(control) || (control.activeChildren?.size ?? 0) > 0)) return false;
|
|
414
|
+
if (control) removeWorkflowForegroundSteeringRoute(control);
|
|
359
415
|
state.foregroundControls.delete(runId);
|
|
360
416
|
if (state.lastForegroundControlId === runId) state.lastForegroundControlId = null;
|
|
361
417
|
return true;
|
|
@@ -473,7 +529,7 @@ function foregroundStatusResult(control: SubagentState["foregroundControls"] ext
|
|
|
473
529
|
|
|
474
530
|
function trimRememberedForegroundRuns(state: SubagentState): void {
|
|
475
531
|
if (!state.foregroundRuns) return;
|
|
476
|
-
while (state.foregroundRuns.size >
|
|
532
|
+
while (state.foregroundRuns.size > MAX_REMEMBERED_FOREGROUND_RUNS) {
|
|
477
533
|
const oldestTerminal = [...state.foregroundRuns.values()]
|
|
478
534
|
.filter((run) => !run.children.some((child) => child.status === "detached"))
|
|
479
535
|
.sort((left, right) => left.updatedAt - right.updatedAt)[0];
|
|
@@ -482,6 +538,14 @@ function trimRememberedForegroundRuns(state: SubagentState): void {
|
|
|
482
538
|
}
|
|
483
539
|
}
|
|
484
540
|
|
|
541
|
+
function persistRememberedForegroundRuns(state: SubagentState): void {
|
|
542
|
+
try {
|
|
543
|
+
persistForegroundRunHistory(state);
|
|
544
|
+
} catch (error) {
|
|
545
|
+
console.error("Failed to persist foreground run history:", error);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
485
549
|
function foregroundChildActivityFromProgress(progress: SingleResult["progress"] | undefined) {
|
|
486
550
|
return {
|
|
487
551
|
...(progress?.activityState ? { activityState: progress.activityState } : {}),
|
|
@@ -548,6 +612,7 @@ function rememberForegroundRun(state: SubagentState, input: { runId: string; mod
|
|
|
548
612
|
}),
|
|
549
613
|
});
|
|
550
614
|
trimRememberedForegroundRuns(state);
|
|
615
|
+
persistRememberedForegroundRuns(state);
|
|
551
616
|
}
|
|
552
617
|
|
|
553
618
|
function applyControlEventToRememberedForegroundRun(state: SubagentState, event: ControlEvent): void {
|
|
@@ -623,6 +688,7 @@ function updateRememberedForegroundChild(state: SubagentState, input: { runId: s
|
|
|
623
688
|
...(input.result.capabilityAudit ? { capabilityAudit: input.result.capabilityAudit } : {}),
|
|
624
689
|
});
|
|
625
690
|
trimRememberedForegroundRuns(state);
|
|
691
|
+
persistRememberedForegroundRuns(state);
|
|
626
692
|
const output = getSingleResultOutput(input.result).trim();
|
|
627
693
|
const success = terminalStatus === "completed";
|
|
628
694
|
const summary = !success && input.result.error
|
|
@@ -1277,6 +1343,7 @@ async function resumeAsyncRun(input: {
|
|
|
1277
1343
|
deps: ExecutorDeps;
|
|
1278
1344
|
parentModel?: ParentModel;
|
|
1279
1345
|
absoluteDeadlineAt?: number;
|
|
1346
|
+
signal?: AbortSignal;
|
|
1280
1347
|
}): Promise<AgentToolResult<Details>> {
|
|
1281
1348
|
const followUp = (input.params.message ?? input.params.task ?? "").trim();
|
|
1282
1349
|
const attachChain = (input.params.chain?.length ?? 0) > 0 ? input.params.chain as ChainStep[] : undefined;
|
|
@@ -1392,7 +1459,7 @@ async function resumeAsyncRun(input: {
|
|
|
1392
1459
|
inheritProjectContext: recoveryDescriptor.inheritProjectContext,
|
|
1393
1460
|
inheritSkills: recoveryDescriptor.inheritSkills,
|
|
1394
1461
|
source: "project",
|
|
1395
|
-
filePath: recoveryDescriptor.agentFilePath ?? path.join(recoveryDescriptor.cwd, "
|
|
1462
|
+
filePath: recoveryDescriptor.agentFilePath ?? path.join(getProjectSubagentsDir(recoveryDescriptor.cwd), "recovery-agent"),
|
|
1396
1463
|
} : undefined);
|
|
1397
1464
|
if (!agentConfig) {
|
|
1398
1465
|
return {
|
|
@@ -1563,6 +1630,58 @@ async function resumeAsyncRun(input: {
|
|
|
1563
1630
|
}
|
|
1564
1631
|
|
|
1565
1632
|
const revivedId = result.details.asyncId ?? runId;
|
|
1633
|
+
if (input.params.workflowParentRunId !== undefined && result.details.asyncDir) {
|
|
1634
|
+
const asyncDir = result.details.asyncDir;
|
|
1635
|
+
const resultPath = workflowAwaitedAsyncResultPath(asyncDir);
|
|
1636
|
+
const stopOnAbort = () => { stopAsyncRun(input.deps.state, revivedId, input.deps.kill, { asyncDir, resolvedId: revivedId }); };
|
|
1637
|
+
if (input.signal?.aborted) stopOnAbort();
|
|
1638
|
+
else input.signal?.addEventListener("abort", stopOnAbort, { once: true });
|
|
1639
|
+
let completed: Awaited<ReturnType<typeof waitForImportedAsyncRoot>>;
|
|
1640
|
+
try {
|
|
1641
|
+
completed = await waitForImportedAsyncRoot({ runId: revivedId, asyncDir, resultPath, index: 0 });
|
|
1642
|
+
} finally {
|
|
1643
|
+
input.signal?.removeEventListener("abort", stopOnAbort);
|
|
1644
|
+
}
|
|
1645
|
+
fs.rmSync(resultPath, { force: true });
|
|
1646
|
+
const totalCost = completed.totalCost;
|
|
1647
|
+
const childResult: SingleResult = {
|
|
1648
|
+
index: 0,
|
|
1649
|
+
agent: completed.agent,
|
|
1650
|
+
task: effectiveFollowUp,
|
|
1651
|
+
exitCode: completed.exitCode,
|
|
1652
|
+
usage: {
|
|
1653
|
+
input: totalCost?.inputTokens ?? 0,
|
|
1654
|
+
output: totalCost?.outputTokens ?? 0,
|
|
1655
|
+
cacheRead: 0,
|
|
1656
|
+
cacheWrite: 0,
|
|
1657
|
+
cost: totalCost?.costUsd ?? 0,
|
|
1658
|
+
turns: 0,
|
|
1659
|
+
},
|
|
1660
|
+
finalOutput: completed.output,
|
|
1661
|
+
outputState: completed.output.trim() ? "present" : "absent",
|
|
1662
|
+
...(completed.error ? { error: completed.error } : {}),
|
|
1663
|
+
...(completed.timedOut ? { timedOut: true } : {}),
|
|
1664
|
+
...(completed.stopped ? { stopped: true } : {}),
|
|
1665
|
+
...(completed.sessionFile ? { sessionFile: completed.sessionFile } : {}),
|
|
1666
|
+
...(completed.model ? { model: completed.model } : {}),
|
|
1667
|
+
...(completed.attemptedModels ? { attemptedModels: completed.attemptedModels } : {}),
|
|
1668
|
+
...(completed.modelAttempts ? { modelAttempts: completed.modelAttempts } : {}),
|
|
1669
|
+
...(completed.structuredOutput !== undefined ? { structuredOutput: completed.structuredOutput } : {}),
|
|
1670
|
+
...(completed.structuredOutputPath ? { structuredOutputPath: completed.structuredOutputPath } : {}),
|
|
1671
|
+
...(completed.structuredOutputSchemaPath ? { structuredOutputSchemaPath: completed.structuredOutputSchemaPath } : {}),
|
|
1672
|
+
...(completed.acceptance ? { acceptance: completed.acceptance } : {}),
|
|
1673
|
+
};
|
|
1674
|
+
return {
|
|
1675
|
+
content: [{ type: "text", text: completed.output || completed.error || `Revived ${target.source} subagent ${revivedId} completed without output.` }],
|
|
1676
|
+
...(completed.success ? {} : { isError: true }),
|
|
1677
|
+
details: {
|
|
1678
|
+
...result.details,
|
|
1679
|
+
runId: revivedId,
|
|
1680
|
+
results: [childResult],
|
|
1681
|
+
...(target.launchContractDigest ? { sourceLaunchContractDigest: target.launchContractDigest } : {}),
|
|
1682
|
+
},
|
|
1683
|
+
};
|
|
1684
|
+
}
|
|
1566
1685
|
const revivedTarget = intercomBridge.active ? resolveSubagentIntercomTarget(revivedId, target.agent, 0) : undefined;
|
|
1567
1686
|
const sourceLabel = target.source;
|
|
1568
1687
|
const lines = [
|
|
@@ -1986,7 +2105,17 @@ function applySingleAgentLaunchDefaults(params: SubagentParamsLike, agents: Agen
|
|
|
1986
2105
|
|
|
1987
2106
|
export const DEFAULT_FOREGROUND_TIMEOUT_MS = 30 * 60 * 1000;
|
|
1988
2107
|
|
|
1989
|
-
|
|
2108
|
+
// Async single-agent runs also need a wall-clock backstop: a child whose bash
|
|
2109
|
+
// tool blocks forever (e.g. a background process inheriting the terminal with
|
|
2110
|
+
// no bash `timeout` arg) would otherwise hang the parent indefinitely with
|
|
2111
|
+
// zero signal. Same generous default as foreground; explicit timeoutMs/
|
|
2112
|
+
// maxRuntimeMs and agent-level defaultTimeoutMs remain authoritative.
|
|
2113
|
+
//
|
|
2114
|
+
// Deliberately NOT applied at the workflow level: async scripted workflows
|
|
2115
|
+
// stay unbounded as a whole, while each runner child has its own deadline.
|
|
2116
|
+
export { DEFAULT_ASYNC_TIMEOUT_MS };
|
|
2117
|
+
|
|
2118
|
+
export function resolveForegroundTimeout(params: SubagentParamsLike, defaultTimeoutMs?: number): { timeoutMs?: number; error?: string } {
|
|
1990
2119
|
const rawTimeout = params.timeoutMs;
|
|
1991
2120
|
const rawMaxRuntime = params.maxRuntimeMs;
|
|
1992
2121
|
if (rawTimeout === undefined && rawMaxRuntime === undefined) {
|
|
@@ -2005,6 +2134,21 @@ function resolveForegroundTimeout(params: SubagentParamsLike, defaultTimeoutMs?:
|
|
|
2005
2134
|
return timeoutMs === undefined ? {} : { timeoutMs };
|
|
2006
2135
|
}
|
|
2007
2136
|
|
|
2137
|
+
/**
|
|
2138
|
+
* Resolve the effective launch timeout for a single-agent run, applying the
|
|
2139
|
+
* async/foreground default when neither the caller nor the agent set one.
|
|
2140
|
+
*
|
|
2141
|
+
* The async default is deliberately applied only to plain single-agent
|
|
2142
|
+
* launches. Composite launches keep their top-level execution unbounded when
|
|
2143
|
+
* no timeout is set; their runner children resolve separate deadlines.
|
|
2144
|
+
* Exported so the executor wiring is directly testable.
|
|
2145
|
+
*/
|
|
2146
|
+
export function resolveSingleAgentLaunchTimeout(params: SubagentParamsLike, async: boolean): { timeoutMs?: number; error?: string } {
|
|
2147
|
+
const isComposite = (params.chain?.length ?? 0) > 0 || (params.tasks?.length ?? 0) > 0 || params.workflowScript !== undefined;
|
|
2148
|
+
const defaultTimeoutMs = !async ? DEFAULT_FOREGROUND_TIMEOUT_MS : isComposite ? undefined : DEFAULT_ASYNC_TIMEOUT_MS;
|
|
2149
|
+
return resolveForegroundTimeout(params, defaultTimeoutMs);
|
|
2150
|
+
}
|
|
2151
|
+
|
|
2008
2152
|
function resolveToolBudget(
|
|
2009
2153
|
raw: unknown,
|
|
2010
2154
|
label = "toolBudget",
|
|
@@ -2414,7 +2558,8 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2414
2558
|
details: { mode: "single" as const, results: [] },
|
|
2415
2559
|
};
|
|
2416
2560
|
}
|
|
2417
|
-
const
|
|
2561
|
+
const requestedWorkflowChildAsyncId = typeof params.workflowChildAsyncId === "string" ? params.workflowChildAsyncId.trim() : "";
|
|
2562
|
+
const id = requestedWorkflowChildAsyncId && path.basename(requestedWorkflowChildAsyncId) === requestedWorkflowChildAsyncId ? requestedWorkflowChildAsyncId : randomUUID();
|
|
2418
2563
|
const parentModel = data.parentModel;
|
|
2419
2564
|
const asyncCtx = compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
|
|
2420
2565
|
pi: deps.pi,
|
|
@@ -3044,6 +3189,7 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
3044
3189
|
const result = await runSync(input.ctx.cwd, input.agents, task.agent, taskText, compactOptional<Parameters<typeof runSync>[4]>({
|
|
3045
3190
|
permissions: input.permissions,
|
|
3046
3191
|
parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
|
|
3192
|
+
...workflowForegroundSteeringLaunchOptions(input.foregroundControl, index),
|
|
3047
3193
|
context: input.contextPolicy.contextForAgent(task.agent),
|
|
3048
3194
|
cwd: taskCwd,
|
|
3049
3195
|
signal: input.signal,
|
|
@@ -3719,8 +3865,9 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3719
3865
|
// Reads: caller override > agent defaultReads > none. `~`/`~/` expand to home;
|
|
3720
3866
|
// absolute paths pass through; relative paths resolve against the child cwd.
|
|
3721
3867
|
const reads = readsOverride !== undefined ? readsOverride : agentConfig.defaultReads ?? false;
|
|
3722
|
-
const
|
|
3723
|
-
|
|
3868
|
+
const readPaths = Array.isArray(reads) ? resolveExistingReadPaths(reads, effectiveCwd) : [];
|
|
3869
|
+
const readsInstruction = readPaths.length > 0
|
|
3870
|
+
? `[Read from: ${readPaths.join(", ")}]\n\n`
|
|
3724
3871
|
: "";
|
|
3725
3872
|
task = readsInstruction + task;
|
|
3726
3873
|
task = injectSingleOutputInstruction(task, outputPath, agentConfig);
|
|
@@ -3764,6 +3911,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3764
3911
|
r = await runSync(ctx.cwd, agents, params.agent!, task, compactOptional<Parameters<typeof runSync>[4]>({
|
|
3765
3912
|
permissions: deps.config.permissions,
|
|
3766
3913
|
parentSessionId: ctx.sessionManager.getSessionId() ?? undefined,
|
|
3914
|
+
...workflowForegroundSteeringLaunchOptions(foregroundControl, 0),
|
|
3767
3915
|
context: data.contextPolicy.contextForAgent(params.agent!),
|
|
3768
3916
|
cwd: effectiveCwd,
|
|
3769
3917
|
signal,
|
|
@@ -3944,6 +4092,61 @@ function duplicateSubagentCallResult(params: SubagentParamsLike): AgentToolResul
|
|
|
3944
4092
|
|
|
3945
4093
|
const workflowLaunchObservers = new WeakMap<object, (launch: { agent: string; sessionFile?: string }) => void>();
|
|
3946
4094
|
|
|
4095
|
+
function recordMissionWorkflowChild(
|
|
4096
|
+
binding: MissionLaunchBinding | undefined,
|
|
4097
|
+
workflowRunId: string,
|
|
4098
|
+
key: string,
|
|
4099
|
+
update: Omit<MissionWorkflowChildUpdate, "workflowRunId" | "key">,
|
|
4100
|
+
): void {
|
|
4101
|
+
if (!binding) return;
|
|
4102
|
+
try {
|
|
4103
|
+
updateMission(binding.location, binding.missionId, { upsertWorkflowChildren: [{ workflowRunId, key, ...update }] });
|
|
4104
|
+
} catch (error) {
|
|
4105
|
+
console.warn(`[pi-subagents] Failed to record mission workflow child '${key}': ${error instanceof Error ? error.message : String(error)}`);
|
|
4106
|
+
}
|
|
4107
|
+
}
|
|
4108
|
+
|
|
4109
|
+
export function missionWorkflowChildStatus(result: AgentToolResult<Details>): string {
|
|
4110
|
+
const childResults = result.details.results;
|
|
4111
|
+
if (childResults.some((child) => child.detached || child.interrupted)) return "paused";
|
|
4112
|
+
if (result.isError === true || childResults.some((child) => child.exitCode !== 0)) return "failed";
|
|
4113
|
+
if (childResults.length === 0 && (result.details.asyncId || result.details.asyncDir)) return "running";
|
|
4114
|
+
return "completed";
|
|
4115
|
+
}
|
|
4116
|
+
|
|
4117
|
+
export async function runMissionWorkflowChild(
|
|
4118
|
+
binding: MissionLaunchBinding | undefined,
|
|
4119
|
+
workflowRunId: string,
|
|
4120
|
+
key: string,
|
|
4121
|
+
phase: string | undefined,
|
|
4122
|
+
run: () => Promise<AgentToolResult<Details>>,
|
|
4123
|
+
): Promise<AgentToolResult<Details>> {
|
|
4124
|
+
try {
|
|
4125
|
+
return await run();
|
|
4126
|
+
} catch (error) {
|
|
4127
|
+
recordMissionWorkflowChild(binding, workflowRunId, key, {
|
|
4128
|
+
status: "failed",
|
|
4129
|
+
completedAt: new Date().toISOString(),
|
|
4130
|
+
heartbeat: { status: "failed", ...(phase ? { phase } : {}), message: error instanceof Error ? error.message : String(error) },
|
|
4131
|
+
});
|
|
4132
|
+
throw error;
|
|
4133
|
+
}
|
|
4134
|
+
}
|
|
4135
|
+
|
|
4136
|
+
export function bindMissionWorkflowChildAsyncLaunch(
|
|
4137
|
+
params: SubagentParamsLike,
|
|
4138
|
+
binding: MissionLaunchBinding | undefined,
|
|
4139
|
+
asyncByDefault: boolean,
|
|
4140
|
+
asyncId: string = randomUUID(),
|
|
4141
|
+
): SubagentParamsLike {
|
|
4142
|
+
const requestedAsync = params.async ?? asyncByDefault;
|
|
4143
|
+
if (!binding || !requestedAsync || params.clarify === true) return params;
|
|
4144
|
+
const id = asyncId.trim();
|
|
4145
|
+
if (!id || path.basename(id) !== id) throw new Error("workflow child async id must be a single path segment");
|
|
4146
|
+
writeMissionAsyncBinding(path.join(DIRS.async, id), binding);
|
|
4147
|
+
return { ...params, workflowChildAsyncId: id };
|
|
4148
|
+
}
|
|
4149
|
+
|
|
3947
4150
|
function workflowChildResult(key: string, result: AgentToolResult<Details>): WorkflowScriptChildResult {
|
|
3948
4151
|
const receiptOutput = result.content.map((part) => part.type === "text" ? part.text : "").filter(Boolean).join("\n");
|
|
3949
4152
|
const output = result.details.results.length === 1 && result.details.results[0]?.finalOutput !== undefined
|
|
@@ -3960,9 +4163,11 @@ function workflowChildResult(key: string, result: AgentToolResult<Details>): Wor
|
|
|
3960
4163
|
if (child.sessionFile) artifactPaths.add(child.sessionFile);
|
|
3961
4164
|
}
|
|
3962
4165
|
const structured = result.details.results.map((child) => child.structuredOutput).filter((value) => value !== undefined);
|
|
4166
|
+
const resolvedAgents = [...new Set(result.details.results.map((child) => child.agent).filter((agent): agent is string => Boolean(agent)))];
|
|
3963
4167
|
return {
|
|
3964
4168
|
key,
|
|
3965
4169
|
ok,
|
|
4170
|
+
...(resolvedAgents.length === 1 ? { agent: resolvedAgents[0] } : {}),
|
|
3966
4171
|
...(result.details.runId || result.details.asyncId ? { runId: result.details.runId ?? result.details.asyncId } : {}),
|
|
3967
4172
|
output,
|
|
3968
4173
|
...(!ok ? { error: receiptOutput || output || "Child run failed." } : {}),
|
|
@@ -4000,6 +4205,7 @@ export function prepareWorkflowLaunchParams(
|
|
|
4000
4205
|
}
|
|
4001
4206
|
const launchParams = {
|
|
4002
4207
|
...workflowDefaults,
|
|
4208
|
+
async: false,
|
|
4003
4209
|
...childParams,
|
|
4004
4210
|
...(options.missionDetached ? { mission: false } : {}),
|
|
4005
4211
|
workflowParentRunId: parentWorkflowRunId,
|
|
@@ -4178,6 +4384,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4178
4384
|
const workflowUsageBudget = validateUsageBudgetConfig(requestParams.usageBudget ?? deps.config.usageBudget, requestParams.usageBudget ? "usageBudget" : "config.usageBudget");
|
|
4179
4385
|
if (workflowUsageBudget.error) return buildRequestedModeError(requestParams, workflowUsageBudget.error);
|
|
4180
4386
|
const workflowCwd = resolveRequestedCwd(parentCwd, requestParams.cwd);
|
|
4387
|
+
const workflowPrompts = { render: (ref: string, vars?: unknown) => renderWorkflowPrompt(ref, vars, workflowCwd) };
|
|
4181
4388
|
const chatProgressResult = resolveWorkflowChatProgress({ requested: requestParams.chatProgress, parentCwd, workflowCwd, background: requestParams.async !== false });
|
|
4182
4389
|
if (chatProgressResult.error) return { content: [{ type: "text", text: chatProgressResult.error }], isError: true, details: { mode: "workflow", results: [] } };
|
|
4183
4390
|
const chatProgress = chatProgressResult.projection!;
|
|
@@ -4313,22 +4520,41 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4313
4520
|
void Promise.resolve().then(async () => {
|
|
4314
4521
|
const workflowResults: SingleResult[] = [];
|
|
4315
4522
|
const { action: _action, agent: _agent, task: _task, resume: _resume, tasks: _tasks, chain: _chain, concurrency: _concurrency, foregroundOnly: _foregroundOnly, clarify: _clarify, timeoutMs: _timeoutMs, maxRuntimeMs: _maxRuntimeMs, usageBudget: _usageBudget, missionId: _missionId, mission: _mission, ...workflowChildDefaults } = workflowRequest;
|
|
4523
|
+
const workflowSteps = new Map<string, NonNullable<AsyncStatus["steps"]>[number]>();
|
|
4524
|
+
let projectedTraceLength = 0;
|
|
4525
|
+
let projectedTraceTail: NonNullable<Details["workflow"]>["trace"][number] | undefined;
|
|
4316
4526
|
const updateTrace = (trace: NonNullable<Details["workflow"]>["trace"]) => {
|
|
4317
4527
|
status.workflow = { ...(status.workflow ?? { emits: [], console: [] }), trace };
|
|
4318
|
-
|
|
4319
|
-
|
|
4528
|
+
const rebuild = trace.length < projectedTraceLength
|
|
4529
|
+
|| (projectedTraceLength > 0 && trace[projectedTraceLength - 1] !== projectedTraceTail);
|
|
4530
|
+
if (rebuild) {
|
|
4531
|
+
workflowSteps.clear();
|
|
4532
|
+
for (const step of status.steps ?? []) {
|
|
4533
|
+
if (step.workflowKey) workflowSteps.set(step.workflowKey, step);
|
|
4534
|
+
}
|
|
4535
|
+
projectedTraceLength = 0;
|
|
4536
|
+
}
|
|
4537
|
+
for (let index = projectedTraceLength; index < trace.length; index += 1) {
|
|
4538
|
+
const entry = trace[index]!;
|
|
4539
|
+
if (entry.operation !== "run") continue;
|
|
4540
|
+
const existing = workflowSteps.get(entry.key);
|
|
4320
4541
|
if (entry.state === "reused" && existing) continue;
|
|
4321
4542
|
const mapped = entry.state === "started" || entry.state === "reused" ? "running" : entry.state === "completed" ? "completed" : "failed";
|
|
4322
4543
|
if (existing) {
|
|
4323
4544
|
existing.status = mapped;
|
|
4545
|
+
if (entry.agent) existing.agent = entry.agent;
|
|
4324
4546
|
if (entry.error === undefined) delete existing.error;
|
|
4325
4547
|
else existing.error = entry.error;
|
|
4326
4548
|
if (entry.durationMs === undefined) delete existing.durationMs;
|
|
4327
4549
|
else existing.durationMs = entry.durationMs;
|
|
4328
4550
|
} else {
|
|
4329
|
-
|
|
4551
|
+
const step: NonNullable<AsyncStatus["steps"]>[number] = { agent: entry.agent ?? entry.key, label: entry.key, workflowKey: entry.key, parentWorkflowRunId: workflowRunId, status: mapped, startedAt: Date.now() };
|
|
4552
|
+
status.steps?.push(step);
|
|
4553
|
+
workflowSteps.set(entry.key, step);
|
|
4330
4554
|
}
|
|
4331
4555
|
}
|
|
4556
|
+
projectedTraceLength = trace.length;
|
|
4557
|
+
projectedTraceTail = trace.at(-1);
|
|
4332
4558
|
projectWorkflowActivity();
|
|
4333
4559
|
persist();
|
|
4334
4560
|
appendWorkflowEvent({ type: "subagent.workflow.trace", trace });
|
|
@@ -4338,6 +4564,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4338
4564
|
script: workflowScript,
|
|
4339
4565
|
timeoutMs: timeout,
|
|
4340
4566
|
signal: controller.signal,
|
|
4567
|
+
prompts: workflowPrompts,
|
|
4341
4568
|
...(workflowState ? { state: workflowState } : {}),
|
|
4342
4569
|
onTrace: updateTrace,
|
|
4343
4570
|
onEmit: (emits) => {
|
|
@@ -4351,37 +4578,70 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4351
4578
|
const budgetState = usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults));
|
|
4352
4579
|
if (budgetState?.exhausted) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, usageBudgetExceededMessage(budgetState)));
|
|
4353
4580
|
patchMissionObjective(childParams.task);
|
|
4354
|
-
const
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4581
|
+
const childPhase = typeof childParams.phase === "string" && childParams.phase.trim() ? childParams.phase.trim() : undefined;
|
|
4582
|
+
const childLabel = typeof childParams.label === "string" && childParams.label.trim() ? childParams.label.trim() : undefined;
|
|
4583
|
+
recordMissionWorkflowChild(missionBinding, workflowRunId, key, {
|
|
4584
|
+
status: "running",
|
|
4585
|
+
...(typeof childParams.agent === "string" && childParams.agent.trim() ? { agent: childParams.agent.trim() } : {}),
|
|
4586
|
+
...(typeof childParams.task === "string" && childParams.task.trim() ? { task: childParams.task.trim() } : {}),
|
|
4587
|
+
...(childLabel ? { label: childLabel } : {}),
|
|
4588
|
+
...(childPhase ? { phase: childPhase } : {}),
|
|
4589
|
+
heartbeat: { status: "running", ...(childPhase ? { phase: childPhase } : {}) },
|
|
4590
|
+
});
|
|
4591
|
+
const result = await runMissionWorkflowChild(missionBinding, workflowRunId, key, childPhase, () => {
|
|
4592
|
+
const childRequest = bindMissionWorkflowChildAsyncLaunch(
|
|
4593
|
+
prepareWorkflowLaunchParams(workflowChildDefaults, childParams, workflowRunId, key, { missionDetached: detachWorkflowChildMissions }),
|
|
4594
|
+
missionBinding,
|
|
4595
|
+
deps.asyncByDefault,
|
|
4596
|
+
);
|
|
4597
|
+
workflowLaunchObservers.set(childRequest, (launch) => {
|
|
4598
|
+
const step = status.steps?.find((candidate) => candidate.workflowKey === key);
|
|
4599
|
+
if (step) {
|
|
4600
|
+
step.agent = launch.agent;
|
|
4601
|
+
step.sessionFile = launch.sessionFile;
|
|
4602
|
+
persist();
|
|
4603
|
+
}
|
|
4604
|
+
recordMissionWorkflowChild(missionBinding, workflowRunId, key, { status: "running", agent: launch.agent, ...(launch.sessionFile ? { sessionPath: launch.sessionFile } : {}) });
|
|
4605
|
+
});
|
|
4606
|
+
return execute(randomUUID(), childRequest, workflowSignal, (update) => {
|
|
4607
|
+
const progress = update.details.progress?.[0];
|
|
4608
|
+
const step = status.steps?.find((candidate) => candidate.workflowKey === key);
|
|
4609
|
+
if (!progress || !step) return;
|
|
4610
|
+
step.status = progress.status === "completed" ? "completed" : progress.status === "failed" ? "failed" : "running";
|
|
4611
|
+
step.activityState = progress.activityState;
|
|
4612
|
+
step.lastActivityAt = progress.lastActivityAt;
|
|
4613
|
+
step.currentTool = progress.currentTool;
|
|
4614
|
+
step.currentToolArgs = progress.currentToolArgs;
|
|
4615
|
+
step.currentToolStartedAt = progress.currentToolStartedAt;
|
|
4616
|
+
step.currentPath = progress.currentPath;
|
|
4617
|
+
step.recentTools = progress.recentTools.map((tool) => ({ ...tool }));
|
|
4618
|
+
step.recentOutput = [...progress.recentOutput];
|
|
4619
|
+
step.turnCount = progress.turnCount;
|
|
4620
|
+
step.toolCount = progress.toolCount;
|
|
4621
|
+
step.model = progress.model;
|
|
4622
|
+
step.thinking = progress.thinking;
|
|
4623
|
+
step.error = progress.error;
|
|
4624
|
+
projectWorkflowActivity();
|
|
4625
|
+
persist();
|
|
4626
|
+
recordMissionWorkflowChild(missionBinding, workflowRunId, key, {
|
|
4627
|
+
status: step.status,
|
|
4628
|
+
heartbeat: { status: step.status, ...(childPhase ? { phase: childPhase } : {}) },
|
|
4629
|
+
});
|
|
4630
|
+
}, ctx, preserveActiveSession);
|
|
4361
4631
|
});
|
|
4362
|
-
const result = await execute(randomUUID(), childRequest, workflowSignal, (update) => {
|
|
4363
|
-
const progress = update.details.progress?.[0];
|
|
4364
|
-
const step = status.steps?.find((candidate) => candidate.workflowKey === key);
|
|
4365
|
-
if (!progress || !step) return;
|
|
4366
|
-
step.status = progress.status === "completed" ? "completed" : progress.status === "failed" ? "failed" : "running";
|
|
4367
|
-
step.activityState = progress.activityState;
|
|
4368
|
-
step.lastActivityAt = progress.lastActivityAt;
|
|
4369
|
-
step.currentTool = progress.currentTool;
|
|
4370
|
-
step.currentToolArgs = progress.currentToolArgs;
|
|
4371
|
-
step.currentToolStartedAt = progress.currentToolStartedAt;
|
|
4372
|
-
step.currentPath = progress.currentPath;
|
|
4373
|
-
step.recentTools = progress.recentTools.map((tool) => ({ ...tool }));
|
|
4374
|
-
step.recentOutput = [...progress.recentOutput];
|
|
4375
|
-
step.turnCount = progress.turnCount;
|
|
4376
|
-
step.toolCount = progress.toolCount;
|
|
4377
|
-
step.model = progress.model;
|
|
4378
|
-
step.thinking = progress.thinking;
|
|
4379
|
-
step.error = progress.error;
|
|
4380
|
-
projectWorkflowActivity();
|
|
4381
|
-
persist();
|
|
4382
|
-
}, ctx, preserveActiveSession);
|
|
4383
4632
|
workflowResults.push(...result.details.results);
|
|
4633
|
+
if (result.details.asyncDir && missionBinding) writeMissionAsyncBinding(result.details.asyncDir, missionBinding);
|
|
4384
4634
|
const child = workflowChildResult(key, result);
|
|
4635
|
+
const childStatus = missionWorkflowChildStatus(result);
|
|
4636
|
+
recordMissionWorkflowChild(missionBinding, workflowRunId, key, {
|
|
4637
|
+
status: childStatus,
|
|
4638
|
+
...(child.runId ? { runId: child.runId } : {}),
|
|
4639
|
+
...(result.details.results[0]?.agent ? { agent: result.details.results[0].agent } : {}),
|
|
4640
|
+
...(result.details.results[0]?.sessionFile ? { sessionPath: result.details.results[0].sessionFile } : {}),
|
|
4641
|
+
artifactPaths: child.artifactPaths,
|
|
4642
|
+
...(["completed", "failed"].includes(childStatus) ? { completedAt: new Date().toISOString() } : {}),
|
|
4643
|
+
heartbeat: { status: childStatus, ...(childPhase ? { phase: childPhase } : {}) },
|
|
4644
|
+
});
|
|
4385
4645
|
if (result.details.asyncId) {
|
|
4386
4646
|
const childJob = deps.state.asyncJobs.get(result.details.asyncId);
|
|
4387
4647
|
if (childJob) { childJob.parentWorkflowRunId = workflowRunId; childJob.workflowKey = key; }
|
|
@@ -4395,14 +4655,14 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4395
4655
|
const summary = `Workflow completed with ${workflow.children.length} child run(s). Return: ${returnPreview}${emitPreview} Trace: ${workflow.trace.length} event(s).`;
|
|
4396
4656
|
const workflowUsage = sumResultsUsage(workflowResults);
|
|
4397
4657
|
status = { ...status, state: "complete", endedAt: Date.now(), workflow: { value: workflow.value, trace: workflow.trace, emits: workflow.emits, console: workflow.console }, totalTokens: { input: workflowUsage.input, output: workflowUsage.output, total: workflowUsage.input + workflowUsage.output }, totalCost: sumResultsCost(workflowResults) };
|
|
4398
|
-
writeAtomicJson(resultPath, { id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: true, state: "complete", summary, output: summary, results: workflow.children.map((child) => ({
|
|
4658
|
+
writeAtomicJson(resultPath, { id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: true, state: "complete", summary, output: summary, results: workflow.children.map((child) => ({ workflowKey: child.key, ...(child.agent ? { agent: child.agent } : {}), ...(child.runId ? { runId: child.runId } : {}), output: child.output, outputState: child.output.trim() || child.structuredOutput !== undefined ? "present" : "absent", structuredOutput: child.structuredOutput, success: child.ok, ...(child.artifactPaths[0] ? { artifactPaths: { outputPath: child.artifactPaths[0] } } : {}) })), workflow: status.workflow, asyncDir, cwd: workflowCwd, sessionId: currentSessionId, timestamp: Date.now(), durationMs: Date.now() - startedAt });
|
|
4399
4659
|
persist();
|
|
4400
4660
|
appendWorkflowEvent({ type: "subagent.workflow.completed", state: "complete" });
|
|
4401
4661
|
} catch (error) {
|
|
4402
4662
|
const partial = error instanceof WorkflowScriptError ? error.partial : { trace: [], emits: [], console: [], children: [] };
|
|
4403
4663
|
const stopped = controller.signal.aborted;
|
|
4404
4664
|
status = compactOptional<AsyncStatus>({ ...status, state: stopped ? "stopped" : "failed", stopped: stopped || undefined, error: error instanceof Error ? error.message : String(error), endedAt: Date.now(), workflow: { trace: partial.trace, emits: partial.emits, console: partial.console } });
|
|
4405
|
-
writeAtomicJson(resultPath, { id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: false, state: status.state, summary: status.error, error: status.error, stopped: status.stopped, results: partial.children.map((child) => ({
|
|
4665
|
+
writeAtomicJson(resultPath, { id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: false, state: status.state, summary: status.error, error: status.error, stopped: status.stopped, results: partial.children.map((child) => ({ workflowKey: child.key, ...(child.agent ? { agent: child.agent } : {}), ...(child.runId ? { runId: child.runId } : {}), output: child.output, outputState: child.output.trim() || child.structuredOutput !== undefined ? "present" : "absent", structuredOutput: child.structuredOutput, success: child.ok, ...(child.artifactPaths[0] ? { artifactPaths: { outputPath: child.artifactPaths[0] } } : {}) })), workflow: status.workflow, asyncDir, cwd: workflowCwd, sessionId: currentSessionId, timestamp: Date.now(), durationMs: Date.now() - startedAt });
|
|
4406
4666
|
persist();
|
|
4407
4667
|
appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, error: status.error });
|
|
4408
4668
|
} finally {
|
|
@@ -4426,6 +4686,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4426
4686
|
script: requestParams.workflowScript,
|
|
4427
4687
|
timeoutMs: timeout,
|
|
4428
4688
|
signal,
|
|
4689
|
+
prompts: workflowPrompts,
|
|
4429
4690
|
...(workflowState ? { state: workflowState } : {}),
|
|
4430
4691
|
onTrace: (trace) => {
|
|
4431
4692
|
liveWorkflow = { ...liveWorkflow, trace };
|
|
@@ -4440,10 +4701,51 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4440
4701
|
const budgetState = usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults));
|
|
4441
4702
|
if (budgetState?.exhausted) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, usageBudgetExceededMessage(budgetState)));
|
|
4442
4703
|
patchMissionObjective(childParams.task);
|
|
4443
|
-
const
|
|
4444
|
-
const
|
|
4704
|
+
const childPhase = typeof childParams.phase === "string" && childParams.phase.trim() ? childParams.phase.trim() : undefined;
|
|
4705
|
+
const childLabel = typeof childParams.label === "string" && childParams.label.trim() ? childParams.label.trim() : undefined;
|
|
4706
|
+
recordMissionWorkflowChild(missionBinding, _id, key, {
|
|
4707
|
+
status: "running",
|
|
4708
|
+
...(typeof childParams.agent === "string" && childParams.agent.trim() ? { agent: childParams.agent.trim() } : {}),
|
|
4709
|
+
...(typeof childParams.task === "string" && childParams.task.trim() ? { task: childParams.task.trim() } : {}),
|
|
4710
|
+
...(childLabel ? { label: childLabel } : {}),
|
|
4711
|
+
...(childPhase ? { phase: childPhase } : {}),
|
|
4712
|
+
heartbeat: { status: "running", ...(childPhase ? { phase: childPhase } : {}) },
|
|
4713
|
+
});
|
|
4714
|
+
const result = await runMissionWorkflowChild(missionBinding, _id, key, childPhase, () => {
|
|
4715
|
+
const childRequest = bindMissionWorkflowChildAsyncLaunch(
|
|
4716
|
+
prepareWorkflowLaunchParams(workflowChildDefaults, childParams, _id, key, { missionDetached: detachWorkflowChildMissions, suppressRoutineResultIntercom: chatProgress.mode === "live-card" }),
|
|
4717
|
+
missionBinding,
|
|
4718
|
+
deps.asyncByDefault,
|
|
4719
|
+
);
|
|
4720
|
+
workflowLaunchObservers.set(childRequest, (launch) => recordMissionWorkflowChild(missionBinding, _id, key, {
|
|
4721
|
+
status: "running",
|
|
4722
|
+
agent: launch.agent,
|
|
4723
|
+
...(launch.sessionFile ? { sessionPath: launch.sessionFile } : {}),
|
|
4724
|
+
}));
|
|
4725
|
+
return execute(randomUUID(), childRequest, workflowSignal, (update) => {
|
|
4726
|
+
const progress = update.details.progress?.[0];
|
|
4727
|
+
if (!progress) return;
|
|
4728
|
+
const progressStatus = progress.status === "completed" ? "completed" : progress.status === "failed" ? "failed" : "running";
|
|
4729
|
+
recordMissionWorkflowChild(missionBinding, _id, key, {
|
|
4730
|
+
status: progressStatus,
|
|
4731
|
+
heartbeat: { status: progressStatus, ...(childPhase ? { phase: childPhase } : {}) },
|
|
4732
|
+
});
|
|
4733
|
+
}, ctx, preserveActiveSession);
|
|
4734
|
+
});
|
|
4445
4735
|
workflowResults.push(...result.details.results);
|
|
4446
|
-
|
|
4736
|
+
if (result.details.asyncDir && missionBinding) writeMissionAsyncBinding(result.details.asyncDir, missionBinding);
|
|
4737
|
+
const child = workflowChildResult(key, result);
|
|
4738
|
+
const childStatus = missionWorkflowChildStatus(result);
|
|
4739
|
+
recordMissionWorkflowChild(missionBinding, _id, key, {
|
|
4740
|
+
status: childStatus,
|
|
4741
|
+
...(child.runId ? { runId: child.runId } : {}),
|
|
4742
|
+
...(result.details.results[0]?.agent ? { agent: result.details.results[0].agent } : {}),
|
|
4743
|
+
...(result.details.results[0]?.sessionFile ? { sessionPath: result.details.results[0].sessionFile } : {}),
|
|
4744
|
+
artifactPaths: child.artifactPaths,
|
|
4745
|
+
...(["completed", "failed"].includes(childStatus) ? { completedAt: new Date().toISOString() } : {}),
|
|
4746
|
+
heartbeat: { status: childStatus, ...(childPhase ? { phase: childPhase } : {}) },
|
|
4747
|
+
});
|
|
4748
|
+
return child;
|
|
4447
4749
|
},
|
|
4448
4750
|
status: async (keyOrRunId, workflowSignal) => workflowChildResult(keyOrRunId, await execute(randomUUID(), { action: "status", id: keyOrRunId }, workflowSignal, undefined, ctx, preserveActiveSession)),
|
|
4449
4751
|
});
|
|
@@ -4485,7 +4787,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4485
4787
|
? normalizeParentModel(ctx.model)
|
|
4486
4788
|
: rememberParentModel(deps.state, requestSessionId, ctx.model);
|
|
4487
4789
|
} catch (error) {
|
|
4488
|
-
if (action?.toLowerCase() !== "doctor") throw error;
|
|
4790
|
+
if (action?.toLowerCase() !== "doctor" && action?.toLowerCase() !== "guide") throw error;
|
|
4489
4791
|
requestParentModel = normalizeParentModel(ctx.model);
|
|
4490
4792
|
}
|
|
4491
4793
|
if (action) {
|
|
@@ -4667,6 +4969,20 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4667
4969
|
details: { mode: "management", results: [], spawnBudget: granted.snapshot },
|
|
4668
4970
|
};
|
|
4669
4971
|
}
|
|
4972
|
+
if (action === "guide") {
|
|
4973
|
+
try {
|
|
4974
|
+
return {
|
|
4975
|
+
content: [{ type: "text", text: readSubagentGuide(paramsWithResolvedCwd.topic) }],
|
|
4976
|
+
details: { mode: "management", results: [] },
|
|
4977
|
+
};
|
|
4978
|
+
} catch (error) {
|
|
4979
|
+
return {
|
|
4980
|
+
content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }],
|
|
4981
|
+
isError: true,
|
|
4982
|
+
details: { mode: "management", results: [] },
|
|
4983
|
+
};
|
|
4984
|
+
}
|
|
4985
|
+
}
|
|
4670
4986
|
if (action === "children.list") {
|
|
4671
4987
|
deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
4672
4988
|
const children = listRetainedChildren(DIRS.async, deps.state.currentSessionId);
|
|
@@ -4817,7 +5133,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4817
5133
|
}
|
|
4818
5134
|
}
|
|
4819
5135
|
if (action === "resume") {
|
|
4820
|
-
return resumeAsyncRun(omitUndefinedProperties({ params: paramsWithResolvedCwd, requestCwd, ctx, deps, parentModel: requestParentModel }));
|
|
5136
|
+
return resumeAsyncRun(omitUndefinedProperties({ params: paramsWithResolvedCwd, requestCwd, ctx, deps, parentModel: requestParentModel, signal }));
|
|
4821
5137
|
}
|
|
4822
5138
|
if (action === "steer") {
|
|
4823
5139
|
deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
@@ -4828,6 +5144,12 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4828
5144
|
try {
|
|
4829
5145
|
const location = resolveAsyncRunLocation(paramsWithResolvedCwd, DIRS.async, DIRS.results);
|
|
4830
5146
|
const runId = location.resolvedId ?? targetRunId ?? path.basename(location.asyncDir ?? paramsWithResolvedCwd.dir);
|
|
5147
|
+
const directoryStatus = location.asyncDir ? readStatus(location.asyncDir) : null;
|
|
5148
|
+
if (directoryStatus?.mode === "workflow") {
|
|
5149
|
+
const route = resolveWorkflowForegroundSteeringTarget({ state: deps.state, workflowRunId: directoryStatus.runId || runId, asyncDirRoot: DIRS.async });
|
|
5150
|
+
if (!route.ok) return { content: [{ type: "text", text: route.message }], isError: true, details: { mode: "management", results: [] } };
|
|
5151
|
+
return steerWorkflowForegroundTarget({ target: route.target, message, mode: paramsWithResolvedCwd.mode, index: paramsWithResolvedCwd.index, signal });
|
|
5152
|
+
}
|
|
4831
5153
|
if (location.asyncDir) {
|
|
4832
5154
|
const unsupported = externalRunnerControlError(location.asyncDir, "steer");
|
|
4833
5155
|
if (unsupported) return unsupported;
|
|
@@ -4863,8 +5185,18 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4863
5185
|
return { content: [{ type: "text", text }], isError: true, details: { mode: "management", results: [] } };
|
|
4864
5186
|
}
|
|
4865
5187
|
if (resolved?.kind === "nested") return steerNestedRun(omitUndefinedProperties({ target: resolved, message, mode: paramsWithResolvedCwd.mode, index: paramsWithResolvedCwd.index, signal }));
|
|
4866
|
-
if (resolved?.kind === "foreground")
|
|
5188
|
+
if (resolved?.kind === "foreground") {
|
|
5189
|
+
const route = resolveWorkflowForegroundSteeringTarget({ state: deps.state, childRunId: resolved.id, asyncDirRoot: DIRS.async });
|
|
5190
|
+
if (!route.ok) return { content: [{ type: "text", text: route.message }], isError: true, details: { mode: "management", results: [] } };
|
|
5191
|
+
return steerWorkflowForegroundTarget({ target: route.target, message, mode: paramsWithResolvedCwd.mode, index: paramsWithResolvedCwd.index, signal });
|
|
5192
|
+
}
|
|
4867
5193
|
if (resolved?.kind !== "async") return { content: [{ type: "text", text: `No async run found for '${targetRunId}'.` }], isError: true, details: { mode: "management", results: [] } };
|
|
5194
|
+
const resolvedStatus = resolved.location.asyncDir ? readStatus(resolved.location.asyncDir) : null;
|
|
5195
|
+
if (resolvedStatus?.mode === "workflow") {
|
|
5196
|
+
const route = resolveWorkflowForegroundSteeringTarget({ state: deps.state, workflowRunId: resolvedStatus.runId || resolved.id, asyncDirRoot: DIRS.async });
|
|
5197
|
+
if (!route.ok) return { content: [{ type: "text", text: route.message }], isError: true, details: { mode: "management", results: [] } };
|
|
5198
|
+
return steerWorkflowForegroundTarget({ target: route.target, message, mode: paramsWithResolvedCwd.mode, index: paramsWithResolvedCwd.index, signal });
|
|
5199
|
+
}
|
|
4868
5200
|
if (resolved.location.asyncDir) {
|
|
4869
5201
|
const unsupported = externalRunnerControlError(resolved.location.asyncDir, "steer");
|
|
4870
5202
|
if (unsupported) return unsupported;
|
|
@@ -4898,6 +5230,9 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4898
5230
|
return appendStepToAsyncChain(omitUndefinedProperties({ params: paramsWithResolvedCwd, requestCwd, ctx, deps, parentModel: requestParentModel }));
|
|
4899
5231
|
}
|
|
4900
5232
|
if (action.startsWith("schedule.")) {
|
|
5233
|
+
if (!isScheduledRunAction(action)) {
|
|
5234
|
+
return { content: [{ type: "text", text: unknownSubagentActionMessage(action) }], isError: true, details: { mode: "management", results: [] } };
|
|
5235
|
+
}
|
|
4901
5236
|
if (deps.allowMutatingManagementActions === false && MUTATING_MANAGEMENT_ACTIONS.has(action)) {
|
|
4902
5237
|
return {
|
|
4903
5238
|
content: [{ type: "text", text: `Action '${action}' is not available from child-safe subagent fanout mode.` }],
|
|
@@ -5004,7 +5339,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5004
5339
|
}
|
|
5005
5340
|
if (!(SUBAGENT_ACTIONS as readonly string[]).includes(action)) {
|
|
5006
5341
|
return {
|
|
5007
|
-
content: [{ type: "text", text:
|
|
5342
|
+
content: [{ type: "text", text: unknownSubagentActionMessage(action) }],
|
|
5008
5343
|
isError: true,
|
|
5009
5344
|
details: { mode: "management" as const, results: [] },
|
|
5010
5345
|
};
|
|
@@ -5168,9 +5503,9 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5168
5503
|
if (externalAgent && (!effectiveAsync || effectiveParams.foregroundOnly === true)) {
|
|
5169
5504
|
return buildRequestedModeError(effectiveParams, `Agent '${externalAgent.name}' uses runner.type='external-cli', which currently supports async/background execution only. Omit async or pass async:true; clarify and foregroundOnly are unsupported.`);
|
|
5170
5505
|
}
|
|
5171
|
-
const foregroundTimeout =
|
|
5506
|
+
const foregroundTimeout = resolveSingleAgentLaunchTimeout(
|
|
5172
5507
|
effectiveParams,
|
|
5173
|
-
effectiveAsync
|
|
5508
|
+
effectiveAsync,
|
|
5174
5509
|
);
|
|
5175
5510
|
if (foregroundTimeout.error) return buildRequestedModeError(effectiveParams, foregroundTimeout.error);
|
|
5176
5511
|
const controlConfig = resolveControlConfig(deps.config.control, effectiveParams.control);
|
|
@@ -5320,12 +5655,26 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5320
5655
|
const foregroundDescription = effectiveParams.task?.trim()
|
|
5321
5656
|
|| effectiveParams.tasks?.[0]?.task?.trim()
|
|
5322
5657
|
|| (effectiveParams.chain ? firstRawChainTask(effectiveParams.chain)?.trim() : undefined);
|
|
5658
|
+
const parentWorkflowStatus = effectiveParams.workflowParentRunId
|
|
5659
|
+
? readStatus(path.join(DIRS.async, effectiveParams.workflowParentRunId))
|
|
5660
|
+
: null;
|
|
5661
|
+
const workflowSteeringDir = effectiveParams.workflowParentRunId
|
|
5662
|
+
&& requestSessionId
|
|
5663
|
+
&& deps.state.workflowControllers?.has(effectiveParams.workflowParentRunId)
|
|
5664
|
+
&& parentWorkflowStatus?.mode === "workflow"
|
|
5665
|
+
&& (parentWorkflowStatus.state === "running" || parentWorkflowStatus.state === "queued")
|
|
5666
|
+
&& parentWorkflowStatus.sessionId === requestSessionId
|
|
5667
|
+
? workflowForegroundSteeringDir(DIRS.async, effectiveParams.workflowParentRunId, runId)
|
|
5668
|
+
: undefined;
|
|
5323
5669
|
const foregroundControl: ForegroundRunControl | undefined = effectiveAsync
|
|
5324
5670
|
? undefined
|
|
5325
5671
|
: compactOptional<ForegroundRunControl>({
|
|
5326
5672
|
runId,
|
|
5327
5673
|
sessionId: requestSessionId,
|
|
5328
5674
|
mode: foregroundMode,
|
|
5675
|
+
...(effectiveParams.workflowParentRunId ? { parentWorkflowRunId: effectiveParams.workflowParentRunId } : {}),
|
|
5676
|
+
...(effectiveParams.workflowKey ? { workflowKey: effectiveParams.workflowKey } : {}),
|
|
5677
|
+
workflowSteeringDir,
|
|
5329
5678
|
startedAt: Date.now(),
|
|
5330
5679
|
updatedAt: Date.now(),
|
|
5331
5680
|
cwd: effectiveCwd,
|