pi-subagents 0.53.0 → 0.55.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +66 -5
- package/README.md +1 -1
- package/agents/reviewer.md +12 -2
- package/docs/agents.md +4 -4
- package/docs/configuration.md +6 -6
- package/docs/extension-api.md +14 -4
- package/docs/models.md +41 -6
- package/docs/observability.md +14 -3
- package/docs/tool-reference.md +21 -6
- package/docs/workflows.md +7 -1
- package/index.ts +10 -1
- package/package.json +1 -1
- package/prompts/council.md +19 -7
- package/prompts/parallel-review.md +5 -1
- package/prompts/review-loop.md +9 -5
- package/skills/council-mode/SKILL.md +50 -26
- package/skills/pi-subagents/SKILL.md +5 -1
- package/skills/pi-subagents/references/constraints-and-recipes.md +16 -1
- package/skills/pi-subagents/references/execution-controls.md +13 -4
- package/skills/pi-subagents/references/multi-lane-orchestration.md +3 -3
- package/skills/pi-subagents/references/prompting-and-roles.md +14 -7
- package/src/agents/agent-management.ts +115 -14
- package/src/agents/agent-serializer.ts +2 -2
- package/src/agents/agents.ts +195 -47
- package/src/api/external-job-provider.ts +10 -1
- package/src/api/preflight.ts +33 -19
- package/src/api/project-panes.ts +2 -0
- package/src/extension/doctor.ts +10 -0
- package/src/extension/fanout-child.ts +3 -2
- package/src/extension/index.ts +24 -4
- package/src/extension/public-execution.ts +12 -9
- package/src/extension/rpc.ts +77 -3
- package/src/extension/schemas.ts +2 -1
- package/src/extension/tool-description.ts +9 -6
- package/src/extension/tool-result.ts +19 -0
- package/src/inspectors/herdr/client.ts +3 -3
- package/src/inspectors/herdr/focus.ts +55 -0
- package/src/inspectors/herdr/project-panes.ts +228 -44
- package/src/integrations/herdr-status.ts +26 -4
- package/src/runs/background/async-execution.ts +112 -13
- package/src/runs/background/async-job-tracker.ts +33 -14
- package/src/runs/background/async-resume.ts +20 -2
- package/src/runs/background/async-retention.ts +1 -1
- package/src/runs/background/async-status.ts +10 -0
- package/src/runs/background/chain-root-attachment.ts +5 -0
- package/src/runs/background/control-channel.ts +98 -10
- package/src/runs/background/notify.ts +62 -1
- package/src/runs/background/result-watcher.ts +4 -3
- package/src/runs/background/run-status.ts +15 -1
- package/src/runs/background/stale-run-reconciler.ts +3 -0
- package/src/runs/background/subagent-runner.ts +251 -38
- package/src/runs/background/wait-completions.ts +2 -0
- package/src/runs/background/wait-tool.ts +4 -3
- package/src/runs/foreground/async-stop-action.ts +23 -2
- package/src/runs/foreground/execution.ts +110 -8
- package/src/runs/foreground/subagent-executor.ts +361 -45
- package/src/runs/foreground/workflow-detach-reconcile.ts +3 -0
- package/src/runs/shared/acceptance.ts +1 -0
- package/src/runs/shared/child-identity.ts +36 -0
- package/src/runs/shared/completion-guard.ts +50 -1
- package/src/runs/shared/external-job-bridge.ts +53 -37
- package/src/runs/shared/external-job-runner.ts +126 -23
- package/src/runs/shared/model-fallback.ts +46 -15
- package/src/runs/shared/model-scope.ts +106 -39
- package/src/runs/shared/orca-progress-tabs.ts +71 -9
- package/src/runs/shared/parallel-utils.ts +12 -0
- package/src/runs/shared/pi-args.ts +31 -1
- package/src/runs/shared/subagent-prompt-runtime.ts +39 -10
- package/src/runs/shared/tool-availability.ts +1 -3
- package/src/shared/launch-contract.ts +6 -5
- package/src/shared/thinking-ceiling.ts +52 -0
- package/src/shared/types.ts +72 -5
- package/src/tui/fleet-status.ts +66 -13
- package/src/tui/render.ts +5 -4
- package/src/watchdog/permission-arbiter.ts +59 -51
- package/src/workflows/scripted-workflow.ts +107 -10
|
@@ -30,7 +30,7 @@ import { handleWatchdogToolAction, WATCHDOG_TOOL_ACTIONS } from "../../watchdog/
|
|
|
30
30
|
import type { MainWatchdogRuntime } from "../../watchdog/runtime.ts";
|
|
31
31
|
import { buildModelCandidates, inheritsParentModel, normalizeParentModel, resolveEffectiveSubagentModel, resolveModelCandidate, type ParentModel } from "../shared/model-fallback.ts";
|
|
32
32
|
import { formatRetainedChildren, listRetainedChildren } from "../background/retained-children.ts";
|
|
33
|
-
import type
|
|
33
|
+
import { resolveModelScopesForAgent, type ModelScopeConfig } from "../shared/model-scope.ts";
|
|
34
34
|
import { aggregateParallelOutputs } from "../shared/parallel-utils.ts";
|
|
35
35
|
import { recordRun } from "../shared/run-history.ts";
|
|
36
36
|
import {
|
|
@@ -108,7 +108,10 @@ import { resultFilePath, writeAsyncResultFile } from "../background/result-files
|
|
|
108
108
|
import { attachRootChildrenToSteps, createNestedRoute, findNestedControlResult, resolveInheritedNestedRouteFromEnv, resolveNestedAsyncDir, resolveNestedParentAddressFromEnv, snapshotNestedEventFiles, updateForegroundNestedProjection, writeNestedControlRequest, writeNestedEvent, type NestedRunResolutionScope } from "../shared/nested-events.ts";
|
|
109
109
|
import { resolveSubagentRunId, type ResolvedSubagentRunId } from "../background/run-id-resolver.ts";
|
|
110
110
|
import { formatNestedRunStatusLines } from "../shared/nested-render.ts";
|
|
111
|
+
import { isStoppableAsyncStatusStep, resolveAsyncStatusChild } from "../shared/child-identity.ts";
|
|
111
112
|
import { inspectSubagentStatus } from "../background/run-status.ts";
|
|
113
|
+
import { getExternalJobProvider } from "../../api/external-job-provider.ts";
|
|
114
|
+
import { externalJobFollowUpRequestDigest, externalJobFollowUpRequestId, externalJobFollowUpRunId, externalJobPromptDigest, externalJobStableJson } from "../shared/external-job-runner.ts";
|
|
112
115
|
import { applyForceTopLevelAsyncOverride } from "../background/top-level-async.ts";
|
|
113
116
|
import { handleMissionAction, MISSION_ACTIONS } from "../../missions/actions.ts";
|
|
114
117
|
import { attachMissionToLaunchResult, prepareMissionLaunch, writeMissionAsyncBinding, type MissionLaunchBinding } from "../../missions/lifecycle.ts";
|
|
@@ -154,6 +157,7 @@ import {
|
|
|
154
157
|
type ResolvedToolBudget,
|
|
155
158
|
type RunFanoutBudgetDescriptor,
|
|
156
159
|
type SingleResult,
|
|
160
|
+
type SubagentChildStatusEvent,
|
|
157
161
|
type ToolBudgetConfig,
|
|
158
162
|
type TurnBudgetConfig,
|
|
159
163
|
type UsageBudgetConfig,
|
|
@@ -274,6 +278,7 @@ export interface SubagentParamsLike {
|
|
|
274
278
|
dir?: string;
|
|
275
279
|
handoffPath?: string;
|
|
276
280
|
index?: number;
|
|
281
|
+
childId?: string;
|
|
277
282
|
view?: "fleet" | "transcript";
|
|
278
283
|
lines?: number;
|
|
279
284
|
topic?: string;
|
|
@@ -298,12 +303,15 @@ export interface SubagentParamsLike {
|
|
|
298
303
|
/** Set by the scheduler so this run's completion can name the schedule that produced it. */
|
|
299
304
|
scheduleOrigin?: ScheduleOrigin;
|
|
300
305
|
workflowChildAsyncId?: string;
|
|
306
|
+
workflowAwaitAsync?: boolean;
|
|
301
307
|
workflowParentDeadlineAt?: number;
|
|
302
308
|
suppressRoutineResultIntercom?: boolean;
|
|
303
309
|
/** Internal inherited cumulative run-tree budget. */
|
|
304
310
|
runFanoutBudget?: RunFanoutBudgetDescriptor;
|
|
305
311
|
/** Internal workflow host admission proof. */
|
|
306
312
|
runFanoutAdmitted?: boolean;
|
|
313
|
+
/** Internal inherited tool/agent ceiling for delegated child launches. */
|
|
314
|
+
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
307
315
|
/** Internal durable-run compatibility fields. Public callers must use workflowScript. */
|
|
308
316
|
chain?: ChainStep[];
|
|
309
317
|
tasks?: TaskParam[];
|
|
@@ -384,7 +392,7 @@ interface ExecutorDeps {
|
|
|
384
392
|
tempArtifactsDir: string;
|
|
385
393
|
getSubagentSessionRoot: (parentSessionFile: string | null) => string;
|
|
386
394
|
expandTilde: (p: string) => string;
|
|
387
|
-
discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[]; agentDiagnostics?: AgentDiscoveryDiagnostic[]; modelScope?: ModelScopeConfig };
|
|
395
|
+
discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[]; agentDiagnostics?: AgentDiscoveryDiagnostic[]; modelScope?: ModelScopeConfig; maxThinking?: AgentConfig["maxThinking"] };
|
|
388
396
|
allowMutatingManagementActions?: boolean;
|
|
389
397
|
activateSupervisorTransport?: () => void;
|
|
390
398
|
refreshResultDelivery?: () => void;
|
|
@@ -853,6 +861,10 @@ function isAsyncRunNotFound(error: unknown): boolean {
|
|
|
853
861
|
return error instanceof Error && error.message.startsWith("Async run not found.");
|
|
854
862
|
}
|
|
855
863
|
|
|
864
|
+
function isMissingExactAsyncStatusFile(error: unknown, requested: string): boolean {
|
|
865
|
+
return error instanceof Error && error.message === `Status file not found for async run '${requested}'.`;
|
|
866
|
+
}
|
|
867
|
+
|
|
856
868
|
function isResumeAmbiguity(error: unknown): boolean {
|
|
857
869
|
return error instanceof Error && /Ambiguous .*run id prefix/.test(error.message);
|
|
858
870
|
}
|
|
@@ -906,7 +918,7 @@ function resolveResumeTarget(params: SubagentParamsLike, state: SubagentState, o
|
|
|
906
918
|
throw new Error(`Resume id '${requested}' is ambiguous between foreground run '${foregroundTarget.runId}' and async run '${asyncTarget.runId}'. Provide a full run id.`);
|
|
907
919
|
}
|
|
908
920
|
if (foregroundTarget) {
|
|
909
|
-
if (isExactResumeError(asyncError, "async", requested)) throw asyncError;
|
|
921
|
+
if (isExactResumeError(asyncError, "async", requested) && !isMissingExactAsyncStatusFile(asyncError, requested)) throw asyncError;
|
|
910
922
|
if (isResumeAmbiguity(asyncError) && !resumeTargetExact(foregroundTarget, requested)) throw asyncError;
|
|
911
923
|
return foregroundTarget;
|
|
912
924
|
}
|
|
@@ -1456,6 +1468,31 @@ async function steerNestedRun(input: { target: ResolvedSubagentRunId & { kind: "
|
|
|
1456
1468
|
return { content: [{ type: "text", text: `Nested run ${run.id} is not a live async Pi child session with a steering inbox. action='steer' cannot target foreground nested runs.` }], isError: true, details: { mode: "management", results: [] } };
|
|
1457
1469
|
}
|
|
1458
1470
|
|
|
1471
|
+
function externalJobOptionsEqual(left: Record<string, unknown>, right: Record<string, unknown>): boolean {
|
|
1472
|
+
return externalJobStableJson(left) === externalJobStableJson(right);
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
function providerFollowUpSupport(providerName: string): { ok: true } | { ok: false; message: string } {
|
|
1476
|
+
try {
|
|
1477
|
+
const provider = getExternalJobProvider(providerName);
|
|
1478
|
+
if (!provider) return { ok: false, message: `External-job provider '${providerName}' is not registered. Load the provider package, then retry action='resume'.` };
|
|
1479
|
+
if (typeof provider.followUp !== "function") return { ok: false, message: `External-job provider '${providerName}' does not support follow-up. Update or reload the provider package, then retry action='resume'.` };
|
|
1480
|
+
return { ok: true };
|
|
1481
|
+
} catch (error) {
|
|
1482
|
+
return { ok: false, message: `External-job provider registry unavailable: ${error instanceof Error ? error.message : String(error)}` };
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
function externalJobFollowUpStarted(input: { sourceRunId: string; runId: string; asyncDir: string; duplicate?: boolean; interactive: boolean }): AgentToolResult<Details> {
|
|
1487
|
+
const lines = [
|
|
1488
|
+
input.duplicate ? `External-job follow-up already exists for ${input.sourceRunId}.` : `Started external-job follow-up for ${input.sourceRunId}.`,
|
|
1489
|
+
`Follow-up run: ${input.runId}`,
|
|
1490
|
+
`Async dir: ${input.asyncDir}`,
|
|
1491
|
+
`Status if needed: subagent({ action: "status", id: "${input.runId}" })`,
|
|
1492
|
+
];
|
|
1493
|
+
return { content: [{ type: "text", text: formatAsyncStartedMessage(lines.join("\n"), input.interactive) }], details: { mode: "single", results: [], asyncId: input.runId, asyncDir: input.asyncDir } };
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1459
1496
|
function externalRunnerControlError(asyncDir: string, action: "steer" | "resume"): AgentToolResult<Details> | undefined {
|
|
1460
1497
|
const status = readStatus(asyncDir);
|
|
1461
1498
|
if (!status?.steps?.length || !status.steps.every((step) => step.runner?.type === "external-cli" || step.runner?.type === "external-job")) return undefined;
|
|
@@ -1465,6 +1502,116 @@ function externalRunnerControlError(asyncDir: string, action: "steer" | "resume"
|
|
|
1465
1502
|
return { content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } };
|
|
1466
1503
|
}
|
|
1467
1504
|
|
|
1505
|
+
async function resumeExternalJobFollowUp(input: {
|
|
1506
|
+
target: AsyncResumeSourceTarget;
|
|
1507
|
+
followUp: string;
|
|
1508
|
+
baseAgentConfig: AgentConfig;
|
|
1509
|
+
effectiveCwd: string;
|
|
1510
|
+
requestCwd: string;
|
|
1511
|
+
ctx: ExtensionContext;
|
|
1512
|
+
deps: ExecutorDeps;
|
|
1513
|
+
parentModel?: ParentModel;
|
|
1514
|
+
modelScope?: ModelScopeConfig;
|
|
1515
|
+
intercomBridge: IntercomBridgeState;
|
|
1516
|
+
parentSessionFile: string | null;
|
|
1517
|
+
absoluteDeadlineAt?: number;
|
|
1518
|
+
}): Promise<AgentToolResult<Details>> {
|
|
1519
|
+
if (input.target.kind === "live" || input.target.state === "running" || input.target.state === "queued") {
|
|
1520
|
+
return { content: [{ type: "text", text: `External-job run '${input.target.runId}' is still running. Wait for completion, then use subagent({ action: "resume", id: "${input.target.runId}", message: "..." }).` }], isError: true, details: { mode: "management", results: [] } };
|
|
1521
|
+
}
|
|
1522
|
+
const runner = input.target.runner;
|
|
1523
|
+
const externalJob = input.target.externalJob;
|
|
1524
|
+
if (runner?.type !== "external-job") return { content: [{ type: "text", text: "Internal error: external-job follow-up was requested for a non-external-job runner." }], isError: true, details: { mode: "management", results: [] } };
|
|
1525
|
+
if (!externalJob) return { content: [{ type: "text", text: `External-job run '${input.target.runId}' has no persisted provider metadata. Cannot follow up without the parent provider job id.` }], isError: true, details: { mode: "management", results: [] } };
|
|
1526
|
+
if (externalJob.provider !== runner.provider) return { content: [{ type: "text", text: `External-job run '${input.target.runId}' has mismatched provider metadata. Refusing to follow up.` }], isError: true, details: { mode: "management", results: [] } };
|
|
1527
|
+
if (!externalJobOptionsEqual(externalJob.options, runner.options)) return { content: [{ type: "text", text: `External-job run '${input.target.runId}' has mismatched provider options. Refusing to follow up.` }], isError: true, details: { mode: "management", results: [] } };
|
|
1528
|
+
if (!externalJob.providerJobId) return { content: [{ type: "text", text: `External-job run '${input.target.runId}' has no parent provider job id. Cannot follow up without reopening or redispatching, so this fails closed.` }], isError: true, details: { mode: "management", results: [] } };
|
|
1529
|
+
if (externalJob.state !== "completed") return { content: [{ type: "text", text: `External-job run '${input.target.runId}' provider state is ${externalJob.state}. Wait for completion, then use action='resume'.` }], isError: true, details: { mode: "management", results: [] } };
|
|
1530
|
+
const support = providerFollowUpSupport(runner.provider);
|
|
1531
|
+
if (!support.ok) return { content: [{ type: "text", text: support.message }], isError: true, details: { mode: "management", results: [] } };
|
|
1532
|
+
|
|
1533
|
+
const promptDigest = externalJobPromptDigest(input.followUp);
|
|
1534
|
+
const requestDigest = externalJobFollowUpRequestDigest({ provider: runner.provider, parentProviderJobId: externalJob.providerJobId, promptDigest, options: runner.options });
|
|
1535
|
+
const requestId = externalJobFollowUpRequestId(requestDigest);
|
|
1536
|
+
const runId = externalJobFollowUpRunId(requestDigest);
|
|
1537
|
+
const asyncDir = path.join(DIRS.async, runId);
|
|
1538
|
+
const currentSessionId = input.deps.state.currentSessionId;
|
|
1539
|
+
if (!currentSessionId) return { content: [{ type: "text", text: "External-job follow-up requires an active parent session." }], isError: true, details: { mode: "management", results: [] } };
|
|
1540
|
+
if (fs.existsSync(asyncDir) || fs.existsSync(resultFilePath(DIRS.results, runId))) {
|
|
1541
|
+
return externalJobFollowUpStarted({ sourceRunId: input.target.runId, runId, asyncDir, duplicate: true, interactive: input.ctx.hasUI });
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
const depthState = checkSubagentDepth(input.deps.config.maxSubagentDepth);
|
|
1545
|
+
if (depthState.blocked) {
|
|
1546
|
+
return { content: [{ type: "text", text: `Nested subagent resume blocked (depth=${depthState.depth}, max=${depthState.maxDepth}). Complete the follow-up directly instead.` }], isError: true, details: { mode: "management", results: [] } };
|
|
1547
|
+
}
|
|
1548
|
+
const topLevelResume = depthState.depth === 0 && !resolveInheritedNestedRouteFromEnv() && !input.deps.state.workflowControllers?.has(input.target.runId);
|
|
1549
|
+
let activeAsyncCapacity: ActiveAsyncCapacityHandle | undefined;
|
|
1550
|
+
try {
|
|
1551
|
+
activeAsyncCapacity = topLevelResume ? acquireActiveAsyncCapacity({
|
|
1552
|
+
sessionId: currentSessionId,
|
|
1553
|
+
limit: resolveMaxActiveAsyncRunsPerSession(input.deps.config.maxActiveAsyncRunsPerSession),
|
|
1554
|
+
runId,
|
|
1555
|
+
kind: "runner",
|
|
1556
|
+
asyncDir,
|
|
1557
|
+
}, { liveWorkflowRunIds: new Set(input.deps.state.workflowControllers?.keys() ?? []) }) : undefined;
|
|
1558
|
+
} catch (error) {
|
|
1559
|
+
if (error instanceof ActiveAsyncCapacityError) return { content: [{ type: "text", text: error.message }], isError: true, details: { mode: "single", results: [], activeAsyncCapacity: error.snapshot } };
|
|
1560
|
+
return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "single", results: [] } };
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
const artifactConfig: ArtifactConfig = omitUndefinedProperties({ ...DEFAULT_ARTIFACT_CONFIG, enabled: true, dir: input.deps.config.artifactDir ?? DEFAULT_ARTIFACT_CONFIG.dir });
|
|
1564
|
+
const artifactsDir = getArtifactsDir(input.parentSessionFile, input.effectiveCwd, artifactConfig.dir);
|
|
1565
|
+
const parentModel = input.parentModel;
|
|
1566
|
+
const agentConfig: AgentConfig = {
|
|
1567
|
+
...input.baseAgentConfig,
|
|
1568
|
+
runner: { type: "external-job", provider: runner.provider, options: runner.options },
|
|
1569
|
+
};
|
|
1570
|
+
const result = executeAsyncSingle(runId, compactOptional<Parameters<typeof executeAsyncSingle>[1]>({
|
|
1571
|
+
agent: input.target.agent,
|
|
1572
|
+
task: input.followUp,
|
|
1573
|
+
goal: input.followUp,
|
|
1574
|
+
agentConfig,
|
|
1575
|
+
recoveryAgentConfig: agentConfig,
|
|
1576
|
+
ctx: compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
|
|
1577
|
+
pi: input.deps.pi,
|
|
1578
|
+
cwd: input.requestCwd,
|
|
1579
|
+
currentSessionId,
|
|
1580
|
+
parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
|
|
1581
|
+
currentModelProvider: parentModel?.provider,
|
|
1582
|
+
currentModel: parentModel,
|
|
1583
|
+
modelScope: input.modelScope,
|
|
1584
|
+
interactive: input.ctx.hasUI,
|
|
1585
|
+
permissions: input.deps.config.permissions,
|
|
1586
|
+
}),
|
|
1587
|
+
cwd: input.effectiveCwd,
|
|
1588
|
+
artifactsDir,
|
|
1589
|
+
artifactConfig,
|
|
1590
|
+
shareEnabled: false,
|
|
1591
|
+
...(input.parentSessionFile ? { sessionRoot: input.deps.getSubagentSessionRoot(input.parentSessionFile) } : {}),
|
|
1592
|
+
maxSubagentDepth: resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth),
|
|
1593
|
+
waitToolEnabled: input.deps.waitToolEnabled,
|
|
1594
|
+
worktreeSetupHook: input.deps.config.worktreeSetupHook,
|
|
1595
|
+
worktreeSetupHookTimeoutMs: input.deps.config.worktreeSetupHookTimeoutMs,
|
|
1596
|
+
worktreeBaseDir: input.deps.config.worktreeBaseDir,
|
|
1597
|
+
controlConfig: resolveControlConfig(input.deps.config.control, undefined),
|
|
1598
|
+
controlIntercomTarget: input.intercomBridge.active ? input.intercomBridge.orchestratorTarget : undefined,
|
|
1599
|
+
childIntercomTarget: input.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(runId, agent, index) : undefined,
|
|
1600
|
+
availableModels: input.ctx.modelRegistry.getAvailable().map(toModelInfo),
|
|
1601
|
+
outputBaseDir: resolveSingleRunOutputBaseDir(input.deps, artifactsDir, runId),
|
|
1602
|
+
...(input.absoluteDeadlineAt !== undefined ? { absoluteDeadlineAt: input.absoluteDeadlineAt } : {}),
|
|
1603
|
+
capabilityCeiling: resolveCurrentSubagentCapabilityCeiling(currentSessionId),
|
|
1604
|
+
runFanoutBudget: createRunFanoutBudget(runId, resolveMaxSubagentSpawnsPerRun(input.deps.config.maxSubagentSpawnsPerRun)),
|
|
1605
|
+
activeAsyncCapacity,
|
|
1606
|
+
externalJobFollowUp: { sourceRunId: input.target.runId, sourceStepIndex: input.target.index, parentProviderJobId: externalJob.providerJobId, requestId, requestDigest },
|
|
1607
|
+
}));
|
|
1608
|
+
if (result.isError) {
|
|
1609
|
+
activeAsyncCapacity?.rollback();
|
|
1610
|
+
return result;
|
|
1611
|
+
}
|
|
1612
|
+
return externalJobFollowUpStarted({ sourceRunId: input.target.runId, runId: result.details.asyncId ?? runId, asyncDir: result.details.asyncDir ?? asyncDir, interactive: input.ctx.hasUI });
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1468
1615
|
async function resumeAsyncRun(input: {
|
|
1469
1616
|
params: SubagentParamsLike;
|
|
1470
1617
|
requestCwd: string;
|
|
@@ -1529,11 +1676,7 @@ async function resumeAsyncRun(input: {
|
|
|
1529
1676
|
];
|
|
1530
1677
|
target = resolveNestedResumeTarget(resolved, trustedSessionRoots);
|
|
1531
1678
|
} else {
|
|
1532
|
-
|
|
1533
|
-
const unsupported = externalRunnerControlError(resolved.location.asyncDir, "resume");
|
|
1534
|
-
if (unsupported) return unsupported;
|
|
1535
|
-
}
|
|
1536
|
-
target = resolveResumeTarget(input.params, input.deps.state, { asyncRequireSessionFile: !attachChain });
|
|
1679
|
+
target = resolveResumeTarget(input.params, input.deps.state, { asyncRequireSessionFile: false });
|
|
1537
1680
|
}
|
|
1538
1681
|
} catch (error) {
|
|
1539
1682
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1591,6 +1734,7 @@ async function resumeAsyncRun(input: {
|
|
|
1591
1734
|
inheritSkills: recoveryDescriptor.inheritSkills,
|
|
1592
1735
|
source: "project",
|
|
1593
1736
|
filePath: recoveryDescriptor.agentFilePath ?? path.join(getProjectSubagentsDir(recoveryDescriptor.cwd), "recovery-agent"),
|
|
1737
|
+
...(discovered.maxThinking ? { maxThinking: discovered.maxThinking } : {}),
|
|
1594
1738
|
} : undefined);
|
|
1595
1739
|
if (!baseAgentConfig) {
|
|
1596
1740
|
return {
|
|
@@ -1599,6 +1743,26 @@ async function resumeAsyncRun(input: {
|
|
|
1599
1743
|
details: { mode: "management", results: [] },
|
|
1600
1744
|
};
|
|
1601
1745
|
}
|
|
1746
|
+
if (target.source === "async" && target.runner?.type === "external-cli") {
|
|
1747
|
+
return { content: [{ type: "text", text: "External runners do not persist Pi sessions and cannot be resumed." }], isError: true, details: { mode: "management", results: [] } };
|
|
1748
|
+
}
|
|
1749
|
+
if (target.source === "async" && target.runner?.type === "external-job") {
|
|
1750
|
+
if (attachChain) return { content: [{ type: "text", text: "External-job follow-up does not support chain attachment. Use action='resume' with message instead." }], isError: true, details: { mode: "management", results: [] } };
|
|
1751
|
+
return resumeExternalJobFollowUp({
|
|
1752
|
+
target,
|
|
1753
|
+
followUp,
|
|
1754
|
+
baseAgentConfig,
|
|
1755
|
+
effectiveCwd,
|
|
1756
|
+
requestCwd: input.requestCwd,
|
|
1757
|
+
ctx: input.ctx,
|
|
1758
|
+
deps: input.deps,
|
|
1759
|
+
parentModel: input.parentModel,
|
|
1760
|
+
modelScope,
|
|
1761
|
+
intercomBridge,
|
|
1762
|
+
parentSessionFile,
|
|
1763
|
+
absoluteDeadlineAt: input.absoluteDeadlineAt,
|
|
1764
|
+
});
|
|
1765
|
+
}
|
|
1602
1766
|
|
|
1603
1767
|
if (attachChain) {
|
|
1604
1768
|
if (target.source !== "async") {
|
|
@@ -1683,6 +1847,7 @@ async function resumeAsyncRun(input: {
|
|
|
1683
1847
|
globalConcurrencyLimit: input.deps.config.globalConcurrencyLimit,
|
|
1684
1848
|
runFanoutBudget: createRunFanoutBudget(runId, resolveMaxSubagentSpawnsPerRun(input.deps.config.maxSubagentSpawnsPerRun)),
|
|
1685
1849
|
capabilityCeiling: intersectSubagentCapabilityCeilings("capabilityCeiling" in target ? target.capabilityCeiling : undefined, resolveCurrentSubagentCapabilityCeiling(input.deps.state.currentSessionId)),
|
|
1850
|
+
thinkingCeiling: target.thinkingCeiling,
|
|
1686
1851
|
activeAsyncCapacity,
|
|
1687
1852
|
}));
|
|
1688
1853
|
if (result.isError) {
|
|
@@ -1705,7 +1870,7 @@ async function resumeAsyncRun(input: {
|
|
|
1705
1870
|
const effectiveFollowUp = [...queuedBriefs.map(({ request }) => request.message), followUp].filter(Boolean).join("\n\n");
|
|
1706
1871
|
const revivalSessionFile = target.sessionFile;
|
|
1707
1872
|
if (!revivalSessionFile) {
|
|
1708
|
-
return { content: [{ type: "text", text: `Async
|
|
1873
|
+
return { content: [{ type: "text", text: `Async run '${target.runId}' child ${target.index} does not have a persisted session file to resume from.` }], isError: true, details: { mode: "management", results: [] } };
|
|
1709
1874
|
}
|
|
1710
1875
|
if (target.source === "async" && asyncReviveRequiresRecoveryDescriptor(target)) {
|
|
1711
1876
|
return { content: [{ type: "text", text: `Async child '${target.runId}' is missing its required run fan-out recovery identity. Start a new run instead.` }], isError: true, details: { mode: "management", results: [] } };
|
|
@@ -1775,6 +1940,7 @@ async function resumeAsyncRun(input: {
|
|
|
1775
1940
|
modelOverride: recoveryDescriptor?.model ?? target.model,
|
|
1776
1941
|
modelOverrideFromParent: recoveryDescriptor?.modelOverrideFromParent,
|
|
1777
1942
|
thinkingOverride: recoveryDescriptor?.thinking ?? target.thinking,
|
|
1943
|
+
thinkingCeiling: recoveryDescriptor?.thinkingCeiling ?? ("thinkingCeiling" in target ? target.thinkingCeiling : undefined),
|
|
1778
1944
|
outputBaseDir: resolveSingleRunOutputBaseDir(input.deps, artifactsDir, runId),
|
|
1779
1945
|
maxSubagentDepth: recoveryDescriptor?.maxSubagentDepth ?? resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth),
|
|
1780
1946
|
waitToolEnabled: input.deps.waitToolEnabled,
|
|
@@ -1866,6 +2032,7 @@ async function resumeAsyncRun(input: {
|
|
|
1866
2032
|
...(completed.model ? { model: completed.model } : {}),
|
|
1867
2033
|
...(completed.attemptedModels ? { attemptedModels: completed.attemptedModels } : {}),
|
|
1868
2034
|
...(completed.modelAttempts ? { modelAttempts: completed.modelAttempts } : {}),
|
|
2035
|
+
...(completed.contextOverflow ? { contextOverflow: true } : {}),
|
|
1869
2036
|
...(completed.structuredOutput !== undefined ? { structuredOutput: completed.structuredOutput } : {}),
|
|
1870
2037
|
...(completed.structuredOutputPath ? { structuredOutputPath: completed.structuredOutputPath } : {}),
|
|
1871
2038
|
...(completed.structuredOutputSchemaPath ? { structuredOutputSchemaPath: completed.structuredOutputSchemaPath } : {}),
|
|
@@ -2652,6 +2819,7 @@ function resolveStaticLaunchSummary(input: {
|
|
|
2652
2819
|
}): StaticLaunchSummary {
|
|
2653
2820
|
const agentConfig = input.agents.find((agent) => agent.name === input.agent);
|
|
2654
2821
|
const externalRunner = agentConfig?.runner?.type === "external-cli" || agentConfig?.runner?.type === "external-job";
|
|
2822
|
+
const modelScopes = resolveModelScopesForAgent(input.modelScope, input.agent, input.parentModel);
|
|
2655
2823
|
const model = externalRunner
|
|
2656
2824
|
? undefined
|
|
2657
2825
|
: resolveEffectiveSubagentModel(
|
|
@@ -2659,8 +2827,8 @@ function resolveStaticLaunchSummary(input: {
|
|
|
2659
2827
|
agentConfig?.model,
|
|
2660
2828
|
input.parentModel,
|
|
2661
2829
|
input.availableModels,
|
|
2662
|
-
input.currentProvider,
|
|
2663
|
-
|
|
2830
|
+
agentConfig?.modelProvider ?? input.currentProvider,
|
|
2831
|
+
modelScopes.length === 0 ? {} : { scope: modelScopes },
|
|
2664
2832
|
);
|
|
2665
2833
|
const thinkingOverride = externalRunner ? undefined : input.thinkingOverrideForTask(input.agent, input.index, model);
|
|
2666
2834
|
const thinking = externalRunner ? undefined : resolveEffectiveThinking(model, thinkingOverride ?? agentConfig?.thinking);
|
|
@@ -2820,7 +2988,68 @@ function preflightForkSessionsForStaticTasks(
|
|
|
2820
2988
|
}
|
|
2821
2989
|
}
|
|
2822
2990
|
|
|
2823
|
-
function
|
|
2991
|
+
async function waitForWorkflowAsyncSingleResult(
|
|
2992
|
+
params: SubagentParamsLike,
|
|
2993
|
+
launchResult: AgentToolResult<Details>,
|
|
2994
|
+
options: { runId: string; task: string; signal?: AbortSignal; state: SubagentState; kill?: ExecutorDeps["kill"] },
|
|
2995
|
+
): Promise<AgentToolResult<Details>> {
|
|
2996
|
+
if (params.workflowAwaitAsync !== true || !launchResult.details.asyncDir) return launchResult;
|
|
2997
|
+
const asyncDir = launchResult.details.asyncDir;
|
|
2998
|
+
const resultPath = workflowAwaitedAsyncResultPath(asyncDir);
|
|
2999
|
+
const stopOnAbort = () => { stopAsyncRun(options.state, options.runId, options.kill, { asyncDir, resolvedId: options.runId }); };
|
|
3000
|
+
if (options.signal?.aborted) stopOnAbort();
|
|
3001
|
+
else options.signal?.addEventListener("abort", stopOnAbort, { once: true });
|
|
3002
|
+
let completed: Awaited<ReturnType<typeof waitForImportedAsyncRoot>>;
|
|
3003
|
+
try {
|
|
3004
|
+
completed = await waitForImportedAsyncRoot({ runId: options.runId, asyncDir, resultPath, index: 0 }, omitUndefinedProperties({
|
|
3005
|
+
shouldAbort: () => options.signal?.aborted === true,
|
|
3006
|
+
timeoutMessage: "Workflow stopped before async child completed.",
|
|
3007
|
+
}));
|
|
3008
|
+
} finally {
|
|
3009
|
+
options.signal?.removeEventListener("abort", stopOnAbort);
|
|
3010
|
+
}
|
|
3011
|
+
fs.rmSync(resultPath, { force: true });
|
|
3012
|
+
const totalCost = completed.totalCost;
|
|
3013
|
+
const childResult: SingleResult = omitUndefinedProperties({
|
|
3014
|
+
index: 0,
|
|
3015
|
+
agent: completed.agent,
|
|
3016
|
+
task: options.task,
|
|
3017
|
+
exitCode: completed.exitCode,
|
|
3018
|
+
usage: {
|
|
3019
|
+
input: totalCost?.inputTokens ?? 0,
|
|
3020
|
+
output: totalCost?.outputTokens ?? 0,
|
|
3021
|
+
cacheRead: 0,
|
|
3022
|
+
cacheWrite: 0,
|
|
3023
|
+
cost: totalCost?.costUsd ?? 0,
|
|
3024
|
+
turns: 0,
|
|
3025
|
+
},
|
|
3026
|
+
finalOutput: completed.output,
|
|
3027
|
+
outputState: completed.output.trim() ? "present" as const : "absent" as const,
|
|
3028
|
+
...(completed.error ? { error: completed.error } : {}),
|
|
3029
|
+
...(completed.timedOut ? { timedOut: true } : {}),
|
|
3030
|
+
...(completed.stopped ? { stopped: true } : {}),
|
|
3031
|
+
...(completed.sessionFile ? { sessionFile: completed.sessionFile } : {}),
|
|
3032
|
+
...(completed.model ? { model: completed.model } : {}),
|
|
3033
|
+
...(completed.attemptedModels ? { attemptedModels: completed.attemptedModels } : {}),
|
|
3034
|
+
...(completed.modelAttempts ? { modelAttempts: completed.modelAttempts } : {}),
|
|
3035
|
+
...(completed.contextOverflow ? { contextOverflow: true } : {}),
|
|
3036
|
+
...(completed.structuredOutput !== undefined ? { structuredOutput: completed.structuredOutput } : {}),
|
|
3037
|
+
...(completed.structuredOutputPath ? { structuredOutputPath: completed.structuredOutputPath } : {}),
|
|
3038
|
+
...(completed.structuredOutputSchemaPath ? { structuredOutputSchemaPath: completed.structuredOutputSchemaPath } : {}),
|
|
3039
|
+
...(completed.acceptance ? { acceptance: completed.acceptance } : {}),
|
|
3040
|
+
});
|
|
3041
|
+
return {
|
|
3042
|
+
content: [{ type: "text", text: completed.output || completed.error || `Async workflow child ${options.runId} completed without output.` }],
|
|
3043
|
+
...(completed.success ? {} : { isError: true }),
|
|
3044
|
+
details: {
|
|
3045
|
+
...launchResult.details,
|
|
3046
|
+
runId: options.runId,
|
|
3047
|
+
results: [childResult],
|
|
3048
|
+
},
|
|
3049
|
+
};
|
|
3050
|
+
}
|
|
3051
|
+
|
|
3052
|
+
async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Promise<AgentToolResult<Details> | null> {
|
|
2824
3053
|
const {
|
|
2825
3054
|
params,
|
|
2826
3055
|
effectiveCwd,
|
|
@@ -2890,11 +3119,12 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2890
3119
|
const externalRunnerWithoutExplicitModel = (a.runner?.type === "external-cli" || a.runner?.type === "external-job")
|
|
2891
3120
|
&& params.model === undefined
|
|
2892
3121
|
&& (a.model === undefined || (a.modelSource?.type === "subagents.defaultModel" && a.model === a.modelSource.model));
|
|
3122
|
+
const modelScopes = resolveModelScopesForAgent(data.modelScope, a.name, parentModel);
|
|
2893
3123
|
const modelOverride = a.runner?.type === "external-cli" || a.runner?.type === "external-job"
|
|
2894
3124
|
? params.model ?? (externalRunnerWithoutExplicitModel ? undefined : a.model)
|
|
2895
|
-
: resolveEffectiveSubagentModel(params.model as string | undefined, a.model, parentModel, availableModels, currentProvider,
|
|
3125
|
+
: resolveEffectiveSubagentModel(params.model as string | undefined, a.model, parentModel, availableModels, a.modelProvider ?? currentProvider, modelScopes.length === 0 ? {} : { scope: modelScopes });
|
|
2896
3126
|
const modelOverrideFromParent = inheritsParentModel(params.model as string | undefined, a.model, parentModel);
|
|
2897
|
-
|
|
3127
|
+
const asyncResult = executeAsyncSingle(id, compactOptional<Parameters<typeof executeAsyncSingle>[1]>({
|
|
2898
3128
|
agent: params.agent!,
|
|
2899
3129
|
task: shouldForkAgent(contextPolicy, params.agent!) ? wrapForkTask(params.task ?? "") : (params.task ?? ""),
|
|
2900
3130
|
goal: params.task ?? "",
|
|
@@ -2919,6 +3149,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2919
3149
|
modelOverride,
|
|
2920
3150
|
modelOverrideFromParent,
|
|
2921
3151
|
thinkingOverride: externalRunnerWithoutExplicitModel ? undefined : thinkingOverrideForTask(params.agent!, 0, modelOverride, modelOverrideFromParent),
|
|
3152
|
+
thinkingCeiling: a.maxThinking,
|
|
2922
3153
|
maxSubagentDepth,
|
|
2923
3154
|
waitToolEnabled: deps.waitToolEnabled,
|
|
2924
3155
|
...(params.worktree === true ? { worktree: true } : {}),
|
|
@@ -2944,7 +3175,9 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2944
3175
|
runFanoutBudget: data.runFanoutBudget,
|
|
2945
3176
|
parentWorkflowRunId: params.workflowParentRunId,
|
|
2946
3177
|
workflowKey: params.workflowKey,
|
|
3178
|
+
workflowAwaitAsync: params.workflowAwaitAsync,
|
|
2947
3179
|
}));
|
|
3180
|
+
return waitForWorkflowAsyncSingleResult(params, asyncResult, { runId: id, task: params.task ?? "", signal: data.signal, state: deps.state, kill: deps.kill });
|
|
2948
3181
|
}
|
|
2949
3182
|
|
|
2950
3183
|
return null;
|
|
@@ -2998,9 +3231,7 @@ function resolveWorkflowAggregateOutputPath(
|
|
|
2998
3231
|
ctxCwd: string,
|
|
2999
3232
|
workflowCwd: string,
|
|
3000
3233
|
outputBaseDir: string,
|
|
3001
|
-
configuredOutputBaseDir: string | undefined,
|
|
3002
3234
|
): string | undefined {
|
|
3003
|
-
if (typeof output === "string" && output && !path.isAbsolute(output) && !configuredOutputBaseDir) return path.resolve(workflowCwd, output);
|
|
3004
3235
|
return resolveSingleOutputPath(output, ctxCwd, workflowCwd, outputBaseDir);
|
|
3005
3236
|
}
|
|
3006
3237
|
|
|
@@ -3036,6 +3267,7 @@ function resolveWorkflowChildOutputPath(input: {
|
|
|
3036
3267
|
aggregateOutputPath?: string;
|
|
3037
3268
|
configuredOutputBaseDir?: string;
|
|
3038
3269
|
discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[] };
|
|
3270
|
+
agents: AgentConfig[];
|
|
3039
3271
|
workflowAgentScope?: unknown;
|
|
3040
3272
|
state?: SubagentState;
|
|
3041
3273
|
key: string;
|
|
@@ -3054,8 +3286,10 @@ function resolveWorkflowChildOutputPath(input: {
|
|
|
3054
3286
|
const hasExplicitOutput = typeof rawOutput === "string" || typeof rawOutput === "boolean";
|
|
3055
3287
|
const childCwd = typeof input.params.cwd === "string" ? resolveChildCwd(input.workflowCwd, input.params.cwd) : input.workflowCwd;
|
|
3056
3288
|
const agentScope = resolveExecutionAgentScope(input.params.agentScope ?? input.workflowAgentScope);
|
|
3057
|
-
const
|
|
3058
|
-
const agent = typeof input.params.agent === "string"
|
|
3289
|
+
const discoveredAgents = input.discoverAgents(childCwd, agentScope).agents;
|
|
3290
|
+
const agent = typeof input.params.agent === "string"
|
|
3291
|
+
? resolveAgentName(input.params.agent, discoveredAgents).agent ?? resolveAgentName(input.params.agent, input.agents).agent
|
|
3292
|
+
: undefined;
|
|
3059
3293
|
const agentOutput = typeof agent?.output === "string" ? agent.output : undefined;
|
|
3060
3294
|
const output = rawOutput === true || rawOutput === "true"
|
|
3061
3295
|
? agentOutput
|
|
@@ -3065,7 +3299,7 @@ function resolveWorkflowChildOutputPath(input: {
|
|
|
3065
3299
|
? workflowChildDefaultOutput(input.aggregateOutputPath, input.artifactsDir, input.workflowRunId, input.key)
|
|
3066
3300
|
: agentOutput;
|
|
3067
3301
|
return {
|
|
3068
|
-
path: resolveSingleOutputPath(output, input.ctxCwd, childCwd, input.configuredOutputBaseDir),
|
|
3302
|
+
path: resolveSingleOutputPath(output, input.ctxCwd, childCwd, input.configuredOutputBaseDir ?? path.join(input.artifactsDir, "outputs", sanitizeRunPathSegment(input.workflowRunId))),
|
|
3069
3303
|
inherited: !hasExplicitOutput && !input.aggregateOutputPath && agentOutput !== undefined,
|
|
3070
3304
|
};
|
|
3071
3305
|
}
|
|
@@ -3078,6 +3312,7 @@ function workflowChildOutputClaims(input: {
|
|
|
3078
3312
|
aggregateOutputPath?: string;
|
|
3079
3313
|
configuredOutputBaseDir?: string;
|
|
3080
3314
|
discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[] };
|
|
3315
|
+
agents: AgentConfig[];
|
|
3081
3316
|
workflowAgentScope?: unknown;
|
|
3082
3317
|
state: SubagentState;
|
|
3083
3318
|
claimedOutputPaths: Map<string, string>;
|
|
@@ -3126,9 +3361,10 @@ function prepareWorkflowChildLaunchParams(input: {
|
|
|
3126
3361
|
aggregateOutputPath?: string;
|
|
3127
3362
|
configuredOutputBaseDir?: string;
|
|
3128
3363
|
discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[] };
|
|
3364
|
+
agents: AgentConfig[];
|
|
3129
3365
|
workflowAgentScope?: unknown;
|
|
3130
3366
|
outputOverride?: string;
|
|
3131
|
-
options?: { missionDetached?: boolean; suppressRoutineResultIntercom?: boolean; runFanoutBudget?: RunFanoutBudgetDescriptor; parentDeadlineAt?: number };
|
|
3367
|
+
options?: { missionDetached?: boolean; suppressRoutineResultIntercom?: boolean; runFanoutBudget?: RunFanoutBudgetDescriptor; parentDeadlineAt?: number; capabilityCeiling?: ResolvedSubagentCapabilityCeiling };
|
|
3132
3368
|
}): SubagentParamsLike {
|
|
3133
3369
|
let childParams = input.childParams;
|
|
3134
3370
|
if (input.childParams.output === undefined && input.childParams.resume === undefined && input.outputOverride !== undefined) {
|
|
@@ -3136,10 +3372,17 @@ function prepareWorkflowChildLaunchParams(input: {
|
|
|
3136
3372
|
} else if (input.childParams.output === undefined && input.childParams.resume === undefined && input.aggregateOutputPath !== undefined) {
|
|
3137
3373
|
childParams = { ...input.childParams, output: workflowChildDefaultOutput(input.aggregateOutputPath, input.artifactsDir, input.parentWorkflowRunId, input.workflowKey) };
|
|
3138
3374
|
} else if (input.childParams.resume === undefined) {
|
|
3139
|
-
const resolvedOutput = resolveWorkflowChildOutputPath({ ctxCwd: input.ctxCwd, workflowCwd: input.workflowCwd, artifactsDir: input.artifactsDir, workflowRunId: input.parentWorkflowRunId, aggregateOutputPath: input.aggregateOutputPath, configuredOutputBaseDir: input.configuredOutputBaseDir, discoverAgents: input.discoverAgents, workflowAgentScope: input.workflowAgentScope, key: input.workflowKey, params: input.childParams });
|
|
3375
|
+
const resolvedOutput = resolveWorkflowChildOutputPath({ ctxCwd: input.ctxCwd, workflowCwd: input.workflowCwd, artifactsDir: input.artifactsDir, workflowRunId: input.parentWorkflowRunId, aggregateOutputPath: input.aggregateOutputPath, configuredOutputBaseDir: input.configuredOutputBaseDir, discoverAgents: input.discoverAgents, agents: input.agents, workflowAgentScope: input.workflowAgentScope, key: input.workflowKey, params: input.childParams });
|
|
3140
3376
|
if (resolvedOutput.path) childParams = { ...input.childParams, output: resolvedOutput.path };
|
|
3141
3377
|
}
|
|
3142
|
-
|
|
3378
|
+
const childCwd = typeof childParams.cwd === "string" ? resolveChildCwd(input.workflowCwd, childParams.cwd) : input.workflowCwd;
|
|
3379
|
+
const agentScope = resolveExecutionAgentScope(childParams.agentScope ?? input.workflowAgentScope);
|
|
3380
|
+
const discoveredAgents = input.discoverAgents(childCwd, agentScope).agents;
|
|
3381
|
+
const agent = typeof childParams.agent === "string"
|
|
3382
|
+
? resolveAgentName(childParams.agent, discoveredAgents).agent ?? resolveAgentName(childParams.agent, input.agents).agent
|
|
3383
|
+
: undefined;
|
|
3384
|
+
const externalAsyncRequired = agent?.runner?.type === "external-cli" || agent?.runner?.type === "external-job";
|
|
3385
|
+
return prepareWorkflowLaunchParams(input.workflowDefaults, childParams, input.parentWorkflowRunId, input.workflowKey, { ...input.options, externalAsyncRequired });
|
|
3143
3386
|
}
|
|
3144
3387
|
|
|
3145
3388
|
function finalizeSingleWorktreeHandoff(input: {
|
|
@@ -3234,14 +3477,15 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3234
3477
|
const parentModel = data.parentModel;
|
|
3235
3478
|
const currentProvider = parentModel?.provider;
|
|
3236
3479
|
const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
3480
|
+
const modelScopes = resolveModelScopesForAgent(data.modelScope, agentConfig.name, parentModel);
|
|
3237
3481
|
let task = params.task ?? "";
|
|
3238
3482
|
let modelOverride: string | undefined = resolveEffectiveSubagentModel(
|
|
3239
3483
|
params.model as string | undefined,
|
|
3240
3484
|
agentConfig.model,
|
|
3241
3485
|
parentModel,
|
|
3242
3486
|
availableModels,
|
|
3243
|
-
currentProvider,
|
|
3244
|
-
|
|
3487
|
+
agentConfig.modelProvider ?? currentProvider,
|
|
3488
|
+
modelScopes.length === 0 ? {} : { scope: modelScopes },
|
|
3245
3489
|
);
|
|
3246
3490
|
const modelOverrideFromParent = inheritsParentModel(params.model as string | undefined, agentConfig.model, parentModel);
|
|
3247
3491
|
let skillOverride: string[] | false | undefined = normalizeSkillInput(params.skill);
|
|
@@ -3382,9 +3626,10 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3382
3626
|
modelOverride,
|
|
3383
3627
|
modelOverrideFromParent,
|
|
3384
3628
|
thinkingOverride: thinkingOverrideForTask(params.agent!, 0, modelOverride, modelOverrideFromParent),
|
|
3629
|
+
thinkingCeiling: agentConfig.maxThinking,
|
|
3385
3630
|
availableModels,
|
|
3386
3631
|
preferredModelProvider: currentProvider,
|
|
3387
|
-
modelScope:
|
|
3632
|
+
modelScope: modelScopes,
|
|
3388
3633
|
skills: effectiveSkills,
|
|
3389
3634
|
structuredOutput: structuredRuntime,
|
|
3390
3635
|
agentContract: params.agentContract,
|
|
@@ -3649,7 +3894,8 @@ function workflowChildResult(
|
|
|
3649
3894
|
? result.details.results[0].finalOutput
|
|
3650
3895
|
: receiptOutput;
|
|
3651
3896
|
const detached = result.details.results.some((child) => child.detached);
|
|
3652
|
-
const
|
|
3897
|
+
const stopped = result.details.results.some((child) => child.stopped);
|
|
3898
|
+
const ok = result.isError !== true && !detached && !stopped;
|
|
3653
3899
|
const artifactPaths = new Set<string>();
|
|
3654
3900
|
if (result.details.asyncDir) artifactPaths.add(result.details.asyncDir);
|
|
3655
3901
|
if (result.details.parallelHandoff?.path) artifactPaths.add(result.details.parallelHandoff.path);
|
|
@@ -3689,6 +3935,7 @@ function workflowChildResult(
|
|
|
3689
3935
|
output,
|
|
3690
3936
|
...(!ok ? { error: receiptOutput || output || "Child run failed." } : {}),
|
|
3691
3937
|
...(detached ? { detached: true } : {}),
|
|
3938
|
+
...(stopped ? { stopped: true } : {}),
|
|
3692
3939
|
...(structured.length === 1 ? { structuredOutput: structured[0] } : structured.length > 1 ? { structuredOutput: structured } : {}),
|
|
3693
3940
|
...(requestedContext ? { requestedContext } : {}),
|
|
3694
3941
|
...(resolvedContext ? { resolvedContext } : {}),
|
|
@@ -3813,8 +4060,9 @@ export function prepareWorkflowLaunchParams(
|
|
|
3813
4060
|
childParams: Record<string, unknown>,
|
|
3814
4061
|
parentWorkflowRunId: string,
|
|
3815
4062
|
workflowKey: string,
|
|
3816
|
-
options: { missionDetached?: boolean; suppressRoutineResultIntercom?: boolean; runFanoutBudget?: RunFanoutBudgetDescriptor; parentDeadlineAt?: number } = {},
|
|
4063
|
+
options: { missionDetached?: boolean; suppressRoutineResultIntercom?: boolean; runFanoutBudget?: RunFanoutBudgetDescriptor; parentDeadlineAt?: number; externalAsyncRequired?: boolean; capabilityCeiling?: ResolvedSubagentCapabilityCeiling } = {},
|
|
3817
4064
|
): SubagentParamsLike {
|
|
4065
|
+
const capabilityCeiling = intersectSubagentCapabilityCeilings(workflowDefaults.capabilityCeiling, options.capabilityCeiling);
|
|
3818
4066
|
const parentTimeoutMs = options.parentDeadlineAt === undefined
|
|
3819
4067
|
|| childParams.timeoutMs !== undefined
|
|
3820
4068
|
|| childParams.maxRuntimeMs !== undefined
|
|
@@ -3845,18 +4093,21 @@ export function prepareWorkflowLaunchParams(
|
|
|
3845
4093
|
...(turnBudget !== undefined ? { turnBudget: turnBudget as TurnBudgetConfig } : {}),
|
|
3846
4094
|
...(toolBudget !== undefined ? { toolBudget: toolBudget as ToolBudgetConfig } : {}),
|
|
3847
4095
|
...(intercomBridge !== undefined ? { intercomBridge: intercomBridge as IntercomBridgeConfig } : {}),
|
|
4096
|
+
...(capabilityCeiling ? { capabilityCeiling } : {}),
|
|
3848
4097
|
};
|
|
3849
4098
|
}
|
|
3850
4099
|
const launchParams = {
|
|
3851
4100
|
...workflowDefaults,
|
|
3852
|
-
async: false,
|
|
4101
|
+
async: options.externalAsyncRequired === true && childParams.async === undefined && workflowDefaults.async === undefined ? true : false,
|
|
3853
4102
|
...childParams,
|
|
4103
|
+
...(options.externalAsyncRequired === true && childParams.async === undefined && workflowDefaults.async === undefined ? { workflowAwaitAsync: true } : {}),
|
|
3854
4104
|
...(options.missionDetached ? { mission: false } : {}),
|
|
3855
4105
|
workflowParentRunId: parentWorkflowRunId,
|
|
3856
4106
|
workflowKey,
|
|
3857
4107
|
...(parentTimeoutMs !== undefined ? { workflowParentDeadlineAt: options.parentDeadlineAt } : {}),
|
|
3858
4108
|
...(options.runFanoutBudget ? { runFanoutBudget: { ...options.runFanoutBudget, parentPath: `${options.runFanoutBudget.parentPath ? `${options.runFanoutBudget.parentPath}/` : ""}workflow[${workflowKey}]` } } : {}),
|
|
3859
4109
|
...(options.suppressRoutineResultIntercom ? { suppressRoutineResultIntercom: true } : {}),
|
|
4110
|
+
...(capabilityCeiling ? { capabilityCeiling } : {}),
|
|
3860
4111
|
} as SubagentParamsLike;
|
|
3861
4112
|
const normalizedGate = normalizeGateParams(launchParams);
|
|
3862
4113
|
if (!normalizedGate.ok) throw new Error(normalizedGate.error);
|
|
@@ -3914,6 +4165,7 @@ function createScheduledOwnerState(source: SubagentState, ownerSessionId: string
|
|
|
3914
4165
|
parentSessionFile: ctx.sessionManager.getSessionFile() ?? null,
|
|
3915
4166
|
subagentInProgress: false,
|
|
3916
4167
|
...(ownerSpawns ? { subagentSpawns: ownerSpawns } : { subagentSpawns: undefined }),
|
|
4168
|
+
herdrProjectPanes: new Map(),
|
|
3917
4169
|
asyncJobs: new Map(),
|
|
3918
4170
|
fleetJobs: new Map(),
|
|
3919
4171
|
foregroundRuns: new Map(),
|
|
@@ -3927,6 +4179,7 @@ function createScheduledOwnerState(source: SubagentState, ownerSessionId: string
|
|
|
3927
4179
|
watcherRestartTimer: null,
|
|
3928
4180
|
waitSubscriptions: new Map(),
|
|
3929
4181
|
workflowControllers: new Map(),
|
|
4182
|
+
workflowChildStops: new Map(),
|
|
3930
4183
|
};
|
|
3931
4184
|
}
|
|
3932
4185
|
|
|
@@ -3997,6 +4250,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3997
4250
|
const workflowUsageBudget = validateUsageBudgetConfig(requestParams.usageBudget ?? deps.config.usageBudget, requestParams.usageBudget ? "usageBudget" : "config.usageBudget");
|
|
3998
4251
|
if (workflowUsageBudget.error) return buildRequestedModeError(requestParams, workflowUsageBudget.error);
|
|
3999
4252
|
const workflowCwd = resolveRequestedCwd(parentCwd, requestParams.cwd);
|
|
4253
|
+
const workflowAgents = deps.discoverAgents(workflowCwd, resolveExecutionAgentScope(requestParams.agentScope)).agents;
|
|
4000
4254
|
const workflowArtifactConfig: ArtifactConfig = omitUndefinedProperties({
|
|
4001
4255
|
...DEFAULT_ARTIFACT_CONFIG,
|
|
4002
4256
|
enabled: requestParams.artifacts !== false,
|
|
@@ -4093,6 +4347,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4093
4347
|
const eventsPath = path.join(asyncDir, "events.jsonl");
|
|
4094
4348
|
const startedAt = Date.now();
|
|
4095
4349
|
const currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
4350
|
+
const workflowCapabilityCeiling = intersectSubagentCapabilityCeilings(requestParams.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(currentSessionId));
|
|
4096
4351
|
const completionOwnerId = deps.state.completionOwnerId ?? currentCompletionOwnerId();
|
|
4097
4352
|
deps.state.completionOwnerId = completionOwnerId;
|
|
4098
4353
|
try {
|
|
@@ -4105,6 +4360,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4105
4360
|
}
|
|
4106
4361
|
const controller = new AbortController();
|
|
4107
4362
|
deps.state.workflowControllers ??= new Map();
|
|
4363
|
+
deps.state.workflowChildStops ??= new Map();
|
|
4108
4364
|
deps.state.workflowControllers.set(workflowRunId, controller);
|
|
4109
4365
|
workflowCapacity?.markWorkflowStarted();
|
|
4110
4366
|
if (workflowCapacity) deps.state.activeAsyncCapacity = getActiveAsyncCapacitySnapshot(currentSessionId, resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession), { liveWorkflowRunIds: new Set(deps.state.workflowControllers.keys()) });
|
|
@@ -4272,7 +4528,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4272
4528
|
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;
|
|
4273
4529
|
const workflowOutput = typeof workflowChildDefaults.output === "string" || typeof workflowChildDefaults.output === "boolean" ? workflowChildDefaults.output : undefined;
|
|
4274
4530
|
const configuredOutputBaseDir = resolveConfiguredSingleRunOutputBaseDir(deps);
|
|
4275
|
-
const workflowAggregateOutputPath = resolveWorkflowAggregateOutputPath(workflowOutput, ctx.cwd, workflowCwd, resolveSingleRunOutputBaseDir(deps, workflowArtifactsDir, workflowRunId)
|
|
4531
|
+
const workflowAggregateOutputPath = resolveWorkflowAggregateOutputPath(workflowOutput, ctx.cwd, workflowCwd, resolveSingleRunOutputBaseDir(deps, workflowArtifactsDir, workflowRunId));
|
|
4276
4532
|
const claimedOutputPaths = new Map<string, string>();
|
|
4277
4533
|
const childOutputOverrides = new Map<string, string>();
|
|
4278
4534
|
const producedChildOutputPaths = new Set<string>();
|
|
@@ -4333,6 +4589,23 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4333
4589
|
status.steps?.push(step);
|
|
4334
4590
|
workflowSteps.set(entry.key, step);
|
|
4335
4591
|
}
|
|
4592
|
+
const projectedStep = workflowSteps.get(entry.key);
|
|
4593
|
+
if (entry.state === "stopped" && projectedStep) {
|
|
4594
|
+
appendWorkflowEvent({
|
|
4595
|
+
type: "subagent.child-status",
|
|
4596
|
+
version: 1,
|
|
4597
|
+
childId: entry.key,
|
|
4598
|
+
status: "stopped",
|
|
4599
|
+
reason: "user",
|
|
4600
|
+
source: "async",
|
|
4601
|
+
stepIndex: status.steps?.indexOf(projectedStep),
|
|
4602
|
+
agent: projectedStep.agent,
|
|
4603
|
+
...(projectedStep.runId ? { childRunId: projectedStep.runId } : {}),
|
|
4604
|
+
workflowKey: entry.key,
|
|
4605
|
+
...(projectedStep.phase ? { phase: projectedStep.phase } : {}),
|
|
4606
|
+
...(projectedStep.label ? { label: projectedStep.label } : {}),
|
|
4607
|
+
});
|
|
4608
|
+
}
|
|
4336
4609
|
}
|
|
4337
4610
|
projectedTraceLength = trace.length;
|
|
4338
4611
|
projectedTraceTail = trace.at(-1);
|
|
@@ -4345,10 +4618,14 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4345
4618
|
script: workflowScript,
|
|
4346
4619
|
timeoutMs: timeout,
|
|
4347
4620
|
signal: controller.signal,
|
|
4621
|
+
registerStopChild: (stop) => {
|
|
4622
|
+
if (stop) deps.state.workflowChildStops?.set(workflowRunId, stop);
|
|
4623
|
+
else deps.state.workflowChildStops?.delete(workflowRunId);
|
|
4624
|
+
},
|
|
4348
4625
|
...(workflowState ? { state: workflowState } : {}),
|
|
4349
4626
|
onTrace: updateTrace,
|
|
4350
4627
|
admit: (calls) => {
|
|
4351
|
-
const outputClaims = workflowChildOutputClaims({ ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, workflowRunId, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: deps.discoverAgents, workflowAgentScope: workflowChildDefaults.agentScope, state: deps.state, claimedOutputPaths, entries: calls });
|
|
4628
|
+
const outputClaims = workflowChildOutputClaims({ ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, workflowRunId, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: deps.discoverAgents, agents: workflowAgents, workflowAgentScope: workflowChildDefaults.agentScope, state: deps.state, claimedOutputPaths, entries: calls });
|
|
4352
4629
|
if (outputClaims.error) throw new Error(outputClaims.error);
|
|
4353
4630
|
status.runFanoutBudget = claimRunFanoutBatch(workflowFanoutBudget, calls.map(({ key }) => `workflow[${key}]`));
|
|
4354
4631
|
if (outputClaims.claims) applyWorkflowChildOutputClaims(claimedOutputPaths, outputClaims.claims);
|
|
@@ -4376,7 +4653,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4376
4653
|
});
|
|
4377
4654
|
const result = await runMissionWorkflowChild(missionBinding, workflowRunId, key, childPhase, () => {
|
|
4378
4655
|
const childRequest = bindMissionWorkflowChildAsyncLaunch(
|
|
4379
|
-
{ ...prepareWorkflowChildLaunchParams({ workflowDefaults: workflowChildDefaults, childParams, parentWorkflowRunId: workflowRunId, workflowKey: key, ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: deps.discoverAgents, workflowAgentScope: workflowChildDefaults.agentScope, outputOverride: childOutputOverrides.get(key), options: { missionDetached: detachWorkflowChildMissions, runFanoutBudget: workflowFanoutBudget, parentDeadlineAt: workflowDeadlineAt } }), runFanoutAdmitted: admission.admitted },
|
|
4656
|
+
{ ...prepareWorkflowChildLaunchParams({ workflowDefaults: workflowChildDefaults, childParams, parentWorkflowRunId: workflowRunId, workflowKey: key, ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: deps.discoverAgents, agents: workflowAgents, workflowAgentScope: workflowChildDefaults.agentScope, outputOverride: childOutputOverrides.get(key), options: { missionDetached: detachWorkflowChildMissions, runFanoutBudget: workflowFanoutBudget, parentDeadlineAt: workflowDeadlineAt, capabilityCeiling: workflowCapabilityCeiling } }), runFanoutAdmitted: admission.admitted },
|
|
4380
4657
|
missionBinding,
|
|
4381
4658
|
deps.asyncByDefault,
|
|
4382
4659
|
);
|
|
@@ -4463,7 +4740,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4463
4740
|
} catch (receiptError) {
|
|
4464
4741
|
appendWorkflowEvent({ type: "subagent.workflow.receipt_write_failed", error: `Failed to persist async workflow receipt: ${receiptError instanceof Error ? receiptError.message : String(receiptError)}` });
|
|
4465
4742
|
}
|
|
4466
|
-
if (!writeWorkflowResult({ id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: true, state: "complete", summary: resultSummary, output: resultSummary, 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, ...(workflowReceipt ? { workflowReceipt } : {}), asyncDir, cwd: workflowCwd, sessionId: currentSessionId, completionOwnerId, ...(requestParams.scheduleOrigin ? { scheduleOrigin: requestParams.scheduleOrigin } : {}), timestamp: Date.now(), durationMs: Date.now() - startedAt })) return;
|
|
4743
|
+
if (!writeWorkflowResult({ id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: true, state: "complete", summary: resultSummary, output: resultSummary, 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.stopped ? { stopped: true } : {}), ...(child.artifactPaths[0] ? { artifactPaths: { outputPath: child.artifactPaths[0] } } : {}) })), workflow: status.workflow, ...(workflowReceipt ? { workflowReceipt } : {}), asyncDir, cwd: workflowCwd, sessionId: currentSessionId, completionOwnerId, ...(requestParams.scheduleOrigin ? { scheduleOrigin: requestParams.scheduleOrigin } : {}), timestamp: Date.now(), durationMs: Date.now() - startedAt })) return;
|
|
4467
4744
|
persist();
|
|
4468
4745
|
persistClosed = true;
|
|
4469
4746
|
appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, ...(status.error ? { error: status.error } : {}) });
|
|
@@ -4503,13 +4780,14 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4503
4780
|
} catch (receiptError) {
|
|
4504
4781
|
appendWorkflowEvent({ type: "subagent.workflow.receipt_write_failed", error: `Failed to persist async workflow receipt: ${receiptError instanceof Error ? receiptError.message : String(receiptError)}` });
|
|
4505
4782
|
}
|
|
4506
|
-
if (!writeWorkflowResult({ id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: status.state === "complete", state: status.state, summary: resultSummary, error: status.state === "complete" ? undefined : status.error, stopped: status.stopped, activityState: status.activityState, 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.detached && status.state !== "complete" ? { detached: true } : {}), ...(child.artifactPaths[0] ? { artifactPaths: { outputPath: child.artifactPaths[0] } } : {}) })), workflow: status.workflow, ...(workflowReceipt ? { workflowReceipt } : {}), asyncDir, cwd: workflowCwd, sessionId: currentSessionId, completionOwnerId, ...(requestParams.scheduleOrigin ? { scheduleOrigin: requestParams.scheduleOrigin } : {}), timestamp: Date.now(), durationMs: Date.now() - startedAt })) return;
|
|
4783
|
+
if (!writeWorkflowResult({ id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: status.state === "complete", state: status.state, summary: resultSummary, error: status.state === "complete" ? undefined : status.error, stopped: status.stopped, activityState: status.activityState, 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.stopped ? { stopped: true } : {}), ...(child.detached && status.state !== "complete" ? { detached: true } : {}), ...(child.artifactPaths[0] ? { artifactPaths: { outputPath: child.artifactPaths[0] } } : {}) })), workflow: status.workflow, ...(workflowReceipt ? { workflowReceipt } : {}), asyncDir, cwd: workflowCwd, sessionId: currentSessionId, completionOwnerId, ...(requestParams.scheduleOrigin ? { scheduleOrigin: requestParams.scheduleOrigin } : {}), timestamp: Date.now(), durationMs: Date.now() - startedAt })) return;
|
|
4507
4784
|
persist();
|
|
4508
4785
|
persistClosed = true;
|
|
4509
4786
|
appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, ...(status.error ? { error: status.error } : {}), ...(status.activityState ? { activityState: status.activityState } : {}) });
|
|
4510
4787
|
} finally {
|
|
4511
4788
|
persistClosed = true;
|
|
4512
4789
|
deps.state.workflowControllers?.delete(workflowRunId);
|
|
4790
|
+
deps.state.workflowChildStops?.delete(workflowRunId);
|
|
4513
4791
|
deps.state.activeAsyncCapacity = workflowCapacity?.reconcile(new Set(deps.state.workflowControllers?.keys() ?? []))
|
|
4514
4792
|
?? deps.state.activeAsyncCapacity;
|
|
4515
4793
|
}
|
|
@@ -4522,7 +4800,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4522
4800
|
const { workflowScript: _workflowScript, action: _action, agent: _agent, task: _task, resume: _resume, tasks: _tasks, chain: _chain, concurrency: _concurrency, async: _async, foregroundOnly: _foregroundOnly, clarify: _clarify, timeoutMs: _timeoutMs, maxRuntimeMs: _maxRuntimeMs, usageBudget: _usageBudget, chatProgress: _chatProgress, missionId: _missionId, mission: _mission, ...workflowChildDefaults } = requestParams;
|
|
4523
4801
|
const workflowOutput = typeof workflowChildDefaults.output === "string" || typeof workflowChildDefaults.output === "boolean" ? workflowChildDefaults.output : undefined;
|
|
4524
4802
|
const configuredOutputBaseDir = resolveConfiguredSingleRunOutputBaseDir(deps);
|
|
4525
|
-
const workflowAggregateOutputPath = resolveWorkflowAggregateOutputPath(workflowOutput, ctx.cwd, workflowCwd, resolveSingleRunOutputBaseDir(deps, workflowArtifactsDir, _id)
|
|
4803
|
+
const workflowAggregateOutputPath = resolveWorkflowAggregateOutputPath(workflowOutput, ctx.cwd, workflowCwd, resolveSingleRunOutputBaseDir(deps, workflowArtifactsDir, _id));
|
|
4526
4804
|
const claimedOutputPaths = new Map<string, string>();
|
|
4527
4805
|
const childOutputOverrides = new Map<string, string>();
|
|
4528
4806
|
const producedChildOutputPaths = new Set<string>();
|
|
@@ -4530,6 +4808,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4530
4808
|
const workflowChildRunIds = new Map<string, string>();
|
|
4531
4809
|
let liveWorkflow: NonNullable<Details["workflow"]> = { trace: [], emits: [], console: [] };
|
|
4532
4810
|
const workflowDeadlineAt = timeout === undefined ? undefined : Date.now() + timeout;
|
|
4811
|
+
const workflowCapabilityCeiling = intersectSubagentCapabilityCeilings(requestParams.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(resolveCurrentSessionId(ctx.sessionManager)));
|
|
4533
4812
|
const sendWorkflowProgress = () => {
|
|
4534
4813
|
const update = workflowChatProgressUpdate(_id, chatProgress, liveWorkflow);
|
|
4535
4814
|
if (update) onUpdate?.(update);
|
|
@@ -4545,7 +4824,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4545
4824
|
sendWorkflowProgress();
|
|
4546
4825
|
},
|
|
4547
4826
|
admit: (calls) => {
|
|
4548
|
-
const outputClaims = workflowChildOutputClaims({ ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, workflowRunId: _id, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: deps.discoverAgents, workflowAgentScope: workflowChildDefaults.agentScope, state: deps.state, claimedOutputPaths, entries: calls });
|
|
4827
|
+
const outputClaims = workflowChildOutputClaims({ ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, workflowRunId: _id, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: deps.discoverAgents, agents: workflowAgents, workflowAgentScope: workflowChildDefaults.agentScope, state: deps.state, claimedOutputPaths, entries: calls });
|
|
4549
4828
|
if (outputClaims.error) throw new Error(outputClaims.error);
|
|
4550
4829
|
claimRunFanoutBatch(workflowFanoutBudget, calls.map(({ key }) => `workflow[${key}]`));
|
|
4551
4830
|
if (outputClaims.claims) applyWorkflowChildOutputClaims(claimedOutputPaths, outputClaims.claims);
|
|
@@ -4570,7 +4849,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4570
4849
|
});
|
|
4571
4850
|
const result = await runMissionWorkflowChild(missionBinding, _id, key, childPhase, () => {
|
|
4572
4851
|
const childRequest = bindMissionWorkflowChildAsyncLaunch(
|
|
4573
|
-
{ ...prepareWorkflowChildLaunchParams({ workflowDefaults: workflowChildDefaults, childParams, parentWorkflowRunId: _id, workflowKey: key, ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: deps.discoverAgents, workflowAgentScope: workflowChildDefaults.agentScope, outputOverride: childOutputOverrides.get(key), options: { missionDetached: detachWorkflowChildMissions, suppressRoutineResultIntercom: chatProgress.mode === "live-card", runFanoutBudget: workflowFanoutBudget, parentDeadlineAt: workflowDeadlineAt } }), runFanoutAdmitted: admission.admitted },
|
|
4852
|
+
{ ...prepareWorkflowChildLaunchParams({ workflowDefaults: workflowChildDefaults, childParams, parentWorkflowRunId: _id, workflowKey: key, ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: deps.discoverAgents, agents: workflowAgents, workflowAgentScope: workflowChildDefaults.agentScope, outputOverride: childOutputOverrides.get(key), options: { missionDetached: detachWorkflowChildMissions, suppressRoutineResultIntercom: chatProgress.mode === "live-card", runFanoutBudget: workflowFanoutBudget, parentDeadlineAt: workflowDeadlineAt, capabilityCeiling: workflowCapabilityCeiling } }), runFanoutAdmitted: admission.admitted },
|
|
4574
4853
|
missionBinding,
|
|
4575
4854
|
deps.asyncByDefault,
|
|
4576
4855
|
);
|
|
@@ -4693,7 +4972,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4693
4972
|
if (deps.allowMutatingManagementActions === false && MUTATING_MANAGEMENT_ACTIONS.has(action)) {
|
|
4694
4973
|
return { content: [{ type: "text", text: `Action '${action}' is not available from child-safe subagent fanout mode.` }], isError: true, details: { mode: "management", results: [] } };
|
|
4695
4974
|
}
|
|
4696
|
-
|
|
4975
|
+
deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
4976
|
+
return handleHerdrProjectPaneAction(action as (typeof HERDR_PROJECT_PANE_ACTIONS)[number], paramsWithResolvedCwd, { cwd: requestCwd, state: deps.state, signal });
|
|
4697
4977
|
}
|
|
4698
4978
|
if ((HERDR_INSPECTOR_ACTIONS as readonly string[]).includes(action)) {
|
|
4699
4979
|
if (deps.allowMutatingManagementActions === false && MUTATING_MANAGEMENT_ACTIONS.has(action)) {
|
|
@@ -5104,6 +5384,40 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5104
5384
|
const targetRunId = paramsWithResolvedCwd.runId ?? paramsWithResolvedCwd.id;
|
|
5105
5385
|
const workflowController = targetRunId ? deps.state.workflowControllers?.get(targetRunId) : undefined;
|
|
5106
5386
|
if (workflowController) {
|
|
5387
|
+
if (paramsWithResolvedCwd.childId !== undefined) {
|
|
5388
|
+
const workflowRunId = targetRunId!;
|
|
5389
|
+
const asyncJob = deps.state.asyncJobs.get(workflowRunId);
|
|
5390
|
+
if (!asyncJob?.asyncDir) return { content: [{ type: "text", text: `Status file not found for async workflow '${workflowRunId}'.` }], isError: true, details: { mode: "management", results: [] } };
|
|
5391
|
+
const status = readStatus(asyncJob.asyncDir);
|
|
5392
|
+
if (!status) return { content: [{ type: "text", text: `Status file not found for async workflow '${workflowRunId}'.` }], isError: true, details: { mode: "management", results: [] } };
|
|
5393
|
+
const resolution = resolveAsyncStatusChild(status, paramsWithResolvedCwd.childId);
|
|
5394
|
+
if (!resolution.ok) return { content: [{ type: "text", text: resolution.message }], isError: true, details: { mode: "management", results: [] } };
|
|
5395
|
+
if (!isStoppableAsyncStatusStep(resolution.child.step)) return { content: [{ type: "text", text: `Child '${paramsWithResolvedCwd.childId}' in async run '${targetRunId}' is ${resolution.child.step.status}; stop only supports pending or running children.` }], isError: true, details: { mode: "management", results: [] } };
|
|
5396
|
+
const stopChild = deps.state.workflowChildStops?.get(targetRunId!);
|
|
5397
|
+
if (!stopChild) return { content: [{ type: "text", text: `Workflow ${targetRunId} child stop is unavailable in this extension runtime.` }], isError: true, details: { mode: "management", results: [] } };
|
|
5398
|
+
if (!stopChild(resolution.child.id)) return { content: [{ type: "text", text: `Child '${paramsWithResolvedCwd.childId}' in workflow ${workflowRunId} is not available to stop.` }], isError: true, details: { mode: "management", results: [] } };
|
|
5399
|
+
try {
|
|
5400
|
+
fs.appendFileSync(path.join(asyncJob.asyncDir, "events.jsonl"), `${JSON.stringify({
|
|
5401
|
+
type: "subagent.child-status",
|
|
5402
|
+
version: 1,
|
|
5403
|
+
runId: workflowRunId,
|
|
5404
|
+
childId: resolution.child.id,
|
|
5405
|
+
status: "stopping",
|
|
5406
|
+
ts: Date.now(),
|
|
5407
|
+
reason: "user",
|
|
5408
|
+
source: "async",
|
|
5409
|
+
stepIndex: resolution.child.index,
|
|
5410
|
+
agent: resolution.child.step.agent,
|
|
5411
|
+
...(resolution.child.step.runId ? { childRunId: resolution.child.step.runId } : {}),
|
|
5412
|
+
...(resolution.child.step.workflowKey ? { workflowKey: resolution.child.step.workflowKey } : {}),
|
|
5413
|
+
...(resolution.child.step.phase ? { phase: resolution.child.step.phase } : {}),
|
|
5414
|
+
...(resolution.child.step.label ? { label: resolution.child.step.label } : {}),
|
|
5415
|
+
} satisfies SubagentChildStatusEvent)}\n`, "utf-8");
|
|
5416
|
+
} catch (error) {
|
|
5417
|
+
console.error(`Failed to append child status event for workflow ${workflowRunId}:`, error);
|
|
5418
|
+
}
|
|
5419
|
+
return { content: [{ type: "text", text: `Stop requested for child ${resolution.child.id} in async workflow ${workflowRunId}.` }], details: { mode: "management", results: [] } };
|
|
5420
|
+
}
|
|
5107
5421
|
workflowController.abort(new Error("Workflow stopped by user."));
|
|
5108
5422
|
return { content: [{ type: "text", text: `Stop requested for async workflow ${targetRunId}.` }], details: { mode: "management", results: [] } };
|
|
5109
5423
|
}
|
|
@@ -5115,7 +5429,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5115
5429
|
if (existingStatus?.mode === "workflow" && existingStatus.state === "running") {
|
|
5116
5430
|
return { content: [{ type: "text", text: `Workflow ${existingStatus.runId} is not controlled by this extension runtime; reload recovery cannot stop it safely.` }], isError: true, details: { mode: "management", results: [] } };
|
|
5117
5431
|
}
|
|
5118
|
-
const stopResult = stopAsyncRun(deps.state, location.resolvedId ?? targetRunId ?? path.basename(location.asyncDir ?? paramsWithResolvedCwd.dir), deps.kill, location);
|
|
5432
|
+
const stopResult = stopAsyncRun(deps.state, location.resolvedId ?? targetRunId ?? path.basename(location.asyncDir ?? paramsWithResolvedCwd.dir), deps.kill, location, paramsWithResolvedCwd.childId);
|
|
5119
5433
|
return stopResult ?? { content: [{ type: "text", text: `No running or queued async run was found for '${targetRunId ?? paramsWithResolvedCwd.dir}'.` }], isError: true, details: { mode: "management", results: [] } };
|
|
5120
5434
|
} catch (error) {
|
|
5121
5435
|
const text = error instanceof Error ? error.message : String(error);
|
|
@@ -5142,6 +5456,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5142
5456
|
resolved?.kind === "async" ? resolved.id : targetRunId,
|
|
5143
5457
|
deps.kill,
|
|
5144
5458
|
resolved?.kind === "async" ? resolved.location : undefined,
|
|
5459
|
+
paramsWithResolvedCwd.childId,
|
|
5145
5460
|
);
|
|
5146
5461
|
if (stopResult) return stopResult;
|
|
5147
5462
|
return {
|
|
@@ -5333,13 +5648,13 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5333
5648
|
agentConfig?.model,
|
|
5334
5649
|
parentModel,
|
|
5335
5650
|
forkAvailableModels,
|
|
5336
|
-
parentModel?.provider,
|
|
5651
|
+
agentConfig?.modelProvider ?? parentModel?.provider,
|
|
5337
5652
|
);
|
|
5338
5653
|
const candidates = buildModelCandidates(
|
|
5339
5654
|
primaryModel,
|
|
5340
5655
|
agentConfig?.fallbackModels,
|
|
5341
5656
|
forkAvailableModels,
|
|
5342
|
-
parentModel?.provider,
|
|
5657
|
+
agentConfig?.modelProvider ?? parentModel?.provider,
|
|
5343
5658
|
{ primaryModelFromParent: modelOverrideFromParent ?? inheritsParentModel(modelOverride, agentConfig?.model, parentModel) },
|
|
5344
5659
|
);
|
|
5345
5660
|
forkThinkingRequirements.set(
|
|
@@ -5356,9 +5671,6 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5356
5671
|
} catch (error) {
|
|
5357
5672
|
return toExecutionErrorResult(effectiveParams, error, contextPolicy.contextSummary);
|
|
5358
5673
|
}
|
|
5359
|
-
const requestedAsync = effectiveParams.async ?? deps.asyncByDefault;
|
|
5360
|
-
const backgroundRequestedWhileClarifying = (hasChain || hasTasks) && requestedAsync && effectiveParams.clarify === true;
|
|
5361
|
-
const effectiveAsync = requestedAsync && effectiveParams.clarify !== true;
|
|
5362
5674
|
const selectedAgentNames = hasSingle
|
|
5363
5675
|
? [effectiveParams.agent!]
|
|
5364
5676
|
: hasTasks
|
|
@@ -5367,6 +5679,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5367
5679
|
const externalAgent = selectedAgentNames
|
|
5368
5680
|
.map((name) => agents.find((agent) => agent.name === name))
|
|
5369
5681
|
.find((agent) => agent?.runner?.type === "external-cli" || agent?.runner?.type === "external-job");
|
|
5682
|
+
const externalAsyncRequired = Boolean(externalAgent) && effectiveParams.async === undefined && effectiveParams.clarify !== true && effectiveParams.foregroundOnly !== true;
|
|
5683
|
+
const requestedAsync = externalAsyncRequired ? true : effectiveParams.async ?? deps.asyncByDefault;
|
|
5684
|
+
const backgroundRequestedWhileClarifying = (hasChain || hasTasks) && requestedAsync && effectiveParams.clarify === true;
|
|
5685
|
+
const effectiveAsync = requestedAsync && effectiveParams.clarify !== true;
|
|
5370
5686
|
if (externalAgent && (!effectiveAsync || effectiveParams.foregroundOnly === true)) {
|
|
5371
5687
|
return buildRequestedModeError(effectiveParams, `Agent '${externalAgent.name}' uses runner.type='${externalAgent.runner?.type}', which currently supports async/background execution only. Omit async or pass async:true; clarify and foregroundOnly are unsupported.`);
|
|
5372
5688
|
}
|
|
@@ -5571,7 +5887,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5571
5887
|
parentModel: requestParentModel,
|
|
5572
5888
|
parentSessionId: requestSessionId,
|
|
5573
5889
|
parentPiSessionId: requestPiSessionId,
|
|
5574
|
-
capabilityCeiling: resolveCurrentSubagentCapabilityCeiling(requestSessionId),
|
|
5890
|
+
capabilityCeiling: intersectSubagentCapabilityCeilings(effectiveParams.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(requestSessionId)),
|
|
5575
5891
|
runFanoutBudget,
|
|
5576
5892
|
topLevelAsyncCapacityEligible,
|
|
5577
5893
|
activeAsyncCapacity,
|
|
@@ -5755,7 +6071,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5755
6071
|
workflowLaunchObserver(launch);
|
|
5756
6072
|
}
|
|
5757
6073
|
}
|
|
5758
|
-
const asyncResult = runAsyncPath(execData, deps);
|
|
6074
|
+
const asyncResult = await runAsyncPath(execData, deps);
|
|
5759
6075
|
if (asyncResult) {
|
|
5760
6076
|
asyncLaunchFailed = asyncResult.isError === true;
|
|
5761
6077
|
return attachMission(withRunFanoutBudget(withResolvedContext(withForkThinkingNotes(asyncResult, forkThinkingDowngrades), contextPolicy.contextSummary), runFanoutBudget));
|