pi-subagents 0.48.0 → 0.50.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 +82 -19
- package/agents/oracle.md +7 -5
- package/agents/researcher.md +1 -1
- package/agents/reviewer.md +2 -2
- package/agents/scout.md +1 -1
- package/agents/worker.md +1 -1
- package/docs/agents.md +3 -0
- package/docs/configuration.md +75 -8
- package/docs/extension-api.md +36 -0
- package/docs/missions.md +5 -3
- package/docs/observability.md +42 -2
- package/docs/tool-reference.md +19 -2
- package/docs/workflows.md +2 -2
- package/package.json +1 -1
- package/skills/pi-subagents/references/constraints-and-recipes.md +3 -2
- package/skills/pi-subagents/references/execution-controls.md +5 -4
- package/skills/pi-subagents/references/management-authoring-rpc.md +2 -2
- package/skills/pi-subagents/references/prompting-and-roles.md +31 -15
- package/src/agents/agent-serializer.ts +2 -0
- package/src/agents/agents.ts +37 -12
- package/src/api/external-runs.ts +174 -84
- package/src/api/preflight.ts +13 -7
- package/src/extension/config.ts +53 -0
- package/src/extension/index.ts +62 -9
- package/src/extension/public-execution.ts +32 -4
- package/src/extension/rpc.ts +5 -1
- package/src/extension/schemas.ts +10 -8
- package/src/extension/tool-description.ts +13 -9
- package/src/inspectors/herdr/actions.ts +13 -8
- package/src/inspectors/herdr/inspector-runner.ts +16 -3
- package/src/inspectors/herdr/project-panes.ts +2 -6
- package/src/inspectors/herdr/shell-command.ts +16 -0
- package/src/intercom/intercom-bridge.ts +5 -4
- package/src/intercom/native-supervisor-channel.ts +19 -42
- package/src/missions/goal-driver.ts +3 -1
- package/src/missions/store.ts +8 -3
- package/src/runs/background/active-async-capacity.ts +82 -25
- package/src/runs/background/active-run-index.ts +71 -1
- package/src/runs/background/async-execution.ts +73 -43
- package/src/runs/background/async-job-tracker.ts +5 -0
- package/src/runs/background/async-resume.ts +14 -6
- package/src/runs/background/async-status-snapshot.ts +277 -0
- package/src/runs/background/async-status.ts +8 -3
- package/src/runs/background/chain-root-attachment.ts +2 -2
- package/src/runs/background/completion-replay.ts +11 -1
- package/src/runs/background/fleet-view.ts +21 -6
- package/src/runs/background/result-files.ts +437 -0
- package/src/runs/background/result-watcher.ts +188 -41
- package/src/runs/background/resume-guidance.ts +27 -7
- package/src/runs/background/retained-children.ts +75 -18
- package/src/runs/background/run-id-resolver.ts +30 -24
- package/src/runs/background/run-status.ts +101 -4
- package/src/runs/background/scheduled-runs.ts +54 -28
- package/src/runs/background/stale-run-reconciler.ts +27 -13
- package/src/runs/background/subagent-runner.ts +298 -33
- package/src/runs/background/subagent-wait.ts +2 -0
- package/src/runs/background/wait-completions.ts +5 -2
- package/src/runs/foreground/async-dismiss-action.ts +2 -1
- package/src/runs/foreground/chain-execution.ts +16 -0
- package/src/runs/foreground/execution.ts +219 -15
- package/src/runs/foreground/prompt-audit.ts +4 -3
- package/src/runs/foreground/subagent-executor.ts +328 -43
- package/src/runs/shared/completion-guard.ts +107 -1
- package/src/runs/shared/external-cli-runner.ts +4 -0
- package/src/runs/shared/llm-intent-arbiter.ts +39 -23
- package/src/runs/shared/model-fallback.ts +16 -2
- package/src/runs/shared/nested-events.ts +66 -62
- package/src/runs/shared/orca-progress-tabs.ts +375 -0
- package/src/runs/shared/parallel-utils.ts +2 -0
- package/src/runs/shared/subagent-control.ts +15 -0
- package/src/runs/shared/subagent-prompt-runtime.ts +1 -9
- package/src/runs/shared/subagent-startup-retry.ts +12 -0
- package/src/runs/shared/tool-timeout.ts +93 -0
- package/src/shared/agent-stream-options.ts +5 -0
- package/src/shared/artifacts.ts +2 -6
- package/src/shared/display-text.ts +50 -0
- package/src/shared/node-executable.ts +21 -0
- package/src/shared/types.ts +51 -5
- package/src/slash/slash-commands.ts +34 -25
- package/src/slash/slash-live-state.ts +3 -0
- package/src/tui/fleet-status.ts +160 -45
- package/src/tui/fleet-transcript.ts +1 -48
- package/src/tui/fleet.ts +128 -16
- package/src/tui/render.ts +122 -44
- package/src/watchdog/permission-arbiter.ts +2 -1
- package/src/watchdog/review.ts +4 -3
- package/src/workflows/chat-progress.ts +10 -2
- package/src/workflows/scripted-workflow.ts +272 -76
|
@@ -101,6 +101,7 @@ import { stopAsyncRun } from "./async-stop-action.ts";
|
|
|
101
101
|
import { dismissRecoveredWorkflow } from "./async-dismiss-action.ts";
|
|
102
102
|
import { reconcileAsyncRun } from "../background/stale-run-reconciler.ts";
|
|
103
103
|
import { resolveAsyncRootResultPath, waitForImportedAsyncRoot } from "../background/chain-root-attachment.ts";
|
|
104
|
+
import { resultFilePath, writeAsyncResultFile } from "../background/result-files.ts";
|
|
104
105
|
import { attachRootChildrenToSteps, createNestedRoute, findNestedControlResult, resolveInheritedNestedRouteFromEnv, resolveNestedAsyncDir, resolveNestedParentAddressFromEnv, snapshotNestedEventFiles, updateForegroundNestedProjection, writeNestedControlRequest, writeNestedEvent, type NestedRunResolutionScope } from "../shared/nested-events.ts";
|
|
105
106
|
import { resolveSubagentRunId, type ResolvedSubagentRunId } from "../background/run-id-resolver.ts";
|
|
106
107
|
import { formatNestedRunStatusLines } from "../shared/nested-render.ts";
|
|
@@ -108,7 +109,7 @@ import { inspectSubagentStatus } from "../background/run-status.ts";
|
|
|
108
109
|
import { applyForceTopLevelAsyncOverride } from "../background/top-level-async.ts";
|
|
109
110
|
import { handleMissionAction, MISSION_ACTIONS } from "../../missions/actions.ts";
|
|
110
111
|
import { attachMissionToLaunchResult, prepareMissionLaunch, writeMissionAsyncBinding, type MissionLaunchBinding } from "../../missions/lifecycle.ts";
|
|
111
|
-
import { updateMission } from "../../missions/store.ts";
|
|
112
|
+
import { MissionNotFoundError, updateMission } from "../../missions/store.ts";
|
|
112
113
|
import type { MissionWorkflowChildUpdate } from "../../missions/types.ts";
|
|
113
114
|
import { createMissionWorkflowState } from "../../missions/workflow-state.ts";
|
|
114
115
|
import { resolveAuthorityDecision } from "../../policy/authority.ts";
|
|
@@ -139,6 +140,7 @@ import {
|
|
|
139
140
|
type Details,
|
|
140
141
|
type ExtensionConfig,
|
|
141
142
|
type ForegroundRunControl,
|
|
143
|
+
type IntercomBridgeConfig,
|
|
142
144
|
type IntercomEventBus,
|
|
143
145
|
type JsonSchemaObject,
|
|
144
146
|
type MaxOutputConfig,
|
|
@@ -300,10 +302,14 @@ export interface SubagentParamsLike {
|
|
|
300
302
|
concurrency?: number;
|
|
301
303
|
worktree?: boolean;
|
|
302
304
|
context?: "fresh" | "fork";
|
|
305
|
+
/** Per-run intercom bridge config. It replaces the global config for this launch only. */
|
|
306
|
+
intercomBridge?: IntercomBridgeConfig;
|
|
303
307
|
async?: boolean;
|
|
304
308
|
foregroundOnly?: boolean;
|
|
305
309
|
timeoutMs?: number;
|
|
306
310
|
maxRuntimeMs?: number;
|
|
311
|
+
/** Optional hard per-tool-call timeout (ms). Known-fast tools also have a default. */
|
|
312
|
+
toolTimeoutMs?: number;
|
|
307
313
|
turnBudget?: TurnBudgetConfig;
|
|
308
314
|
/** Internal-only strict turn-boundary enforcement for versioned foreground delegation. */
|
|
309
315
|
enforceHardTurnLimit?: boolean;
|
|
@@ -387,6 +393,8 @@ interface ExecutionContextData {
|
|
|
387
393
|
signal: AbortSignal;
|
|
388
394
|
onUpdate?: (r: AgentToolResult<Details>) => void;
|
|
389
395
|
agents: AgentConfig[];
|
|
396
|
+
/** Discovered agent definitions before per-run bridge injection. */
|
|
397
|
+
recoveryAgents: AgentConfig[];
|
|
390
398
|
runId: string;
|
|
391
399
|
shareEnabled: boolean;
|
|
392
400
|
sessionRoot: string;
|
|
@@ -404,6 +412,8 @@ interface ExecutionContextData {
|
|
|
404
412
|
nestedRoute?: NestedRouteInfo;
|
|
405
413
|
timeoutMs?: number;
|
|
406
414
|
deadlineAt?: number;
|
|
415
|
+
/** Raw global config.toolTimeoutMs, for per-step resolution in async runners. */
|
|
416
|
+
configToolTimeoutMs?: number;
|
|
407
417
|
turnBudget?: ResolvedTurnBudget;
|
|
408
418
|
toolBudget?: ResolvedToolBudget;
|
|
409
419
|
usageBudget?: UsageBudgetConfig;
|
|
@@ -1511,17 +1521,18 @@ async function resumeAsyncRun(input: {
|
|
|
1511
1521
|
const discoveredAgents = discovered.agents;
|
|
1512
1522
|
const modelScope = discovered.modelScope;
|
|
1513
1523
|
const sessionName = resolveIntercomSessionTarget(input.deps.pi.getSessionName(), input.ctx.sessionManager.getSessionId());
|
|
1524
|
+
const recoveryDescriptor = "recoveryDescriptor" in target ? target.recoveryDescriptor : undefined;
|
|
1514
1525
|
const intercomBridge = resolveIntercomBridge({
|
|
1515
1526
|
config: input.deps.config.intercomBridge,
|
|
1527
|
+
override: input.params.intercomBridge ?? recoveryDescriptor?.intercomBridge,
|
|
1516
1528
|
context: input.params.context,
|
|
1517
1529
|
orchestratorTarget: sessionName,
|
|
1518
1530
|
});
|
|
1519
1531
|
const agents = intercomBridge.active
|
|
1520
1532
|
? discoveredAgents.map((agent) => applyIntercomBridgeToAgent(agent, intercomBridge))
|
|
1521
1533
|
: discoveredAgents;
|
|
1522
|
-
const
|
|
1523
|
-
const
|
|
1524
|
-
const agentConfig: AgentConfig | undefined = discoveredAgentConfig ?? (recoveryDescriptor ? {
|
|
1534
|
+
const discoveredAgentConfig = discoveredAgents.find((agent) => agent.name === target.agent);
|
|
1535
|
+
const baseAgentConfig: AgentConfig | undefined = discoveredAgentConfig ?? (recoveryDescriptor ? {
|
|
1525
1536
|
name: recoveryDescriptor.agent,
|
|
1526
1537
|
description: "Persisted async recovery contract",
|
|
1527
1538
|
systemPrompt: "",
|
|
@@ -1531,7 +1542,7 @@ async function resumeAsyncRun(input: {
|
|
|
1531
1542
|
source: "project",
|
|
1532
1543
|
filePath: recoveryDescriptor.agentFilePath ?? path.join(getProjectSubagentsDir(recoveryDescriptor.cwd), "recovery-agent"),
|
|
1533
1544
|
} : undefined);
|
|
1534
|
-
if (!
|
|
1545
|
+
if (!baseAgentConfig) {
|
|
1535
1546
|
return {
|
|
1536
1547
|
content: [{ type: "text", text: `Unknown agent for resume: ${target.agent}` }],
|
|
1537
1548
|
isError: true,
|
|
@@ -1672,7 +1683,8 @@ async function resumeAsyncRun(input: {
|
|
|
1672
1683
|
if (error instanceof ActiveAsyncCapacityError) return { content: [{ type: "text", text: error.message }], isError: true, details: { mode: "single", results: [], activeAsyncCapacity: error.snapshot } };
|
|
1673
1684
|
return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "single", results: [] } };
|
|
1674
1685
|
}
|
|
1675
|
-
const recoveryAgentConfig = recoveryDescriptor ? applySteeringRecoveryAgentConfig(
|
|
1686
|
+
const recoveryAgentConfig = recoveryDescriptor ? applySteeringRecoveryAgentConfig(baseAgentConfig, recoveryDescriptor) : baseAgentConfig;
|
|
1687
|
+
const agentConfig = intercomBridge.active ? applyIntercomBridgeToAgent(recoveryAgentConfig, intercomBridge) : recoveryAgentConfig;
|
|
1676
1688
|
const artifactConfig: ArtifactConfig = recoveryDescriptor?.artifactConfig ?? omitUndefinedProperties({ ...DEFAULT_ARTIFACT_CONFIG, enabled: input.params.artifacts !== false, dir: input.deps.config.artifactDir ?? DEFAULT_ARTIFACT_CONFIG.dir });
|
|
1677
1689
|
const artifactsDir = recoveryDescriptor?.artifactsDir ?? getArtifactsDir(parentSessionFile, effectiveCwd, artifactConfig.dir);
|
|
1678
1690
|
const availableModels = input.ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
@@ -1682,7 +1694,8 @@ async function resumeAsyncRun(input: {
|
|
|
1682
1694
|
agent: target.agent,
|
|
1683
1695
|
task: buildRevivedAsyncTask(target as Parameters<typeof buildRevivedAsyncTask>[0], effectiveFollowUp),
|
|
1684
1696
|
goal: effectiveFollowUp,
|
|
1685
|
-
agentConfig
|
|
1697
|
+
agentConfig,
|
|
1698
|
+
recoveryAgentConfig,
|
|
1686
1699
|
ctx: compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
|
|
1687
1700
|
pi: input.deps.pi,
|
|
1688
1701
|
cwd: input.requestCwd,
|
|
@@ -1717,6 +1730,7 @@ async function resumeAsyncRun(input: {
|
|
|
1717
1730
|
worktreeSetupHookTimeoutMs: input.deps.config.worktreeSetupHookTimeoutMs,
|
|
1718
1731
|
worktreeBaseDir: input.deps.config.worktreeBaseDir,
|
|
1719
1732
|
controlConfig: recoveryDescriptor?.controlConfig ?? resolveControlConfig(input.deps.config.control, input.params.control),
|
|
1733
|
+
intercomBridge: input.params.intercomBridge ?? recoveryDescriptor?.intercomBridge,
|
|
1720
1734
|
controlIntercomTarget: intercomBridge.active ? intercomBridge.orchestratorTarget : undefined,
|
|
1721
1735
|
childIntercomTarget: intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(runId, agent, index) : undefined,
|
|
1722
1736
|
availableModels,
|
|
@@ -1731,7 +1745,7 @@ async function resumeAsyncRun(input: {
|
|
|
1731
1745
|
...(input.params.turnBudget !== undefined ? { turnBudget: input.params.turnBudget } : {}),
|
|
1732
1746
|
...(input.params.toolBudget !== undefined ? { toolBudget: input.params.toolBudget } : {}),
|
|
1733
1747
|
capabilityCeiling: intersectSubagentCapabilityCeilings("capabilityCeiling" in target ? target.capabilityCeiling : undefined, recoveryDescriptor?.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(input.deps.state.currentSessionId)),
|
|
1734
|
-
runFanoutBudget: recoveryDescriptor?.runFanoutBudget ?? createRunFanoutBudget(runId, resolveMaxSubagentSpawnsPerRun(input.deps.config.maxSubagentSpawnsPerRun)),
|
|
1748
|
+
runFanoutBudget: input.params.runFanoutBudget ?? recoveryDescriptor?.runFanoutBudget ?? createRunFanoutBudget(runId, resolveMaxSubagentSpawnsPerRun(input.deps.config.maxSubagentSpawnsPerRun)),
|
|
1735
1749
|
parentWorkflowRunId: input.params.workflowParentRunId,
|
|
1736
1750
|
workflowKey: input.params.workflowKey,
|
|
1737
1751
|
activeAsyncCapacity,
|
|
@@ -2497,16 +2511,19 @@ function resolveStaticLaunchSummary(input: {
|
|
|
2497
2511
|
thinkingOverrideForTask: ForkThinkingOverrideForTask;
|
|
2498
2512
|
}): StaticLaunchSummary {
|
|
2499
2513
|
const agentConfig = input.agents.find((agent) => agent.name === input.agent);
|
|
2500
|
-
const
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2514
|
+
const externalRunner = agentConfig?.runner?.type === "external-cli";
|
|
2515
|
+
const model = externalRunner
|
|
2516
|
+
? undefined
|
|
2517
|
+
: resolveEffectiveSubagentModel(
|
|
2518
|
+
input.explicitModel,
|
|
2519
|
+
agentConfig?.model,
|
|
2520
|
+
input.parentModel,
|
|
2521
|
+
input.availableModels,
|
|
2522
|
+
input.currentProvider,
|
|
2523
|
+
input.modelScope === undefined ? {} : { scope: input.modelScope },
|
|
2524
|
+
);
|
|
2525
|
+
const thinkingOverride = externalRunner ? undefined : input.thinkingOverrideForTask(input.agent, input.index, model);
|
|
2526
|
+
const thinking = externalRunner ? undefined : resolveEffectiveThinking(model, thinkingOverride ?? agentConfig?.thinking);
|
|
2510
2527
|
return {
|
|
2511
2528
|
agent: input.agent,
|
|
2512
2529
|
...(model ? { model } : {}),
|
|
@@ -2789,6 +2806,8 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2789
2806
|
toolBudget: data.toolBudget,
|
|
2790
2807
|
usageBudget: data.usageBudget,
|
|
2791
2808
|
configToolBudget: data.configToolBudget,
|
|
2809
|
+
callToolTimeoutMs: data.params?.toolTimeoutMs,
|
|
2810
|
+
configToolTimeoutMs: data.configToolTimeoutMs,
|
|
2792
2811
|
capabilityCeiling: data.capabilityCeiling,
|
|
2793
2812
|
runFanoutBudget: data.runFanoutBudget,
|
|
2794
2813
|
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
@@ -2836,6 +2855,8 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2836
2855
|
toolBudget: data.toolBudget,
|
|
2837
2856
|
usageBudget: data.usageBudget,
|
|
2838
2857
|
configToolBudget: data.configToolBudget,
|
|
2858
|
+
callToolTimeoutMs: data.params?.toolTimeoutMs,
|
|
2859
|
+
configToolTimeoutMs: data.configToolTimeoutMs,
|
|
2839
2860
|
capabilityCeiling: data.capabilityCeiling,
|
|
2840
2861
|
runFanoutBudget: data.runFanoutBudget,
|
|
2841
2862
|
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
@@ -2862,14 +2883,15 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2862
2883
|
const externalRunnerWithoutExplicitModel = a.runner?.type === "external-cli"
|
|
2863
2884
|
&& params.model === undefined
|
|
2864
2885
|
&& (a.model === undefined || (a.modelSource?.type === "subagents.defaultModel" && a.model === a.modelSource.model));
|
|
2865
|
-
const modelOverride =
|
|
2866
|
-
? undefined
|
|
2886
|
+
const modelOverride = a.runner?.type === "external-cli"
|
|
2887
|
+
? params.model ?? (externalRunnerWithoutExplicitModel ? undefined : a.model)
|
|
2867
2888
|
: resolveEffectiveSubagentModel(params.model as string | undefined, a.model, parentModel, availableModels, currentProvider, data.modelScope === undefined ? {} : { scope: data.modelScope });
|
|
2868
2889
|
return executeAsyncSingle(id, compactOptional<Parameters<typeof executeAsyncSingle>[1]>({
|
|
2869
2890
|
agent: params.agent!,
|
|
2870
2891
|
task: shouldForkAgent(contextPolicy, params.agent!) ? wrapForkTask(params.task ?? "") : (params.task ?? ""),
|
|
2871
2892
|
goal: params.task ?? "",
|
|
2872
2893
|
agentConfig: a,
|
|
2894
|
+
recoveryAgentConfig: data.recoveryAgents.find((agent) => agent.name === params.agent),
|
|
2873
2895
|
ctx: asyncCtx,
|
|
2874
2896
|
availableModels,
|
|
2875
2897
|
cwd: effectiveCwd,
|
|
@@ -2894,6 +2916,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2894
2916
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
2895
2917
|
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
2896
2918
|
controlConfig,
|
|
2919
|
+
intercomBridge: params.intercomBridge,
|
|
2897
2920
|
controlIntercomTarget,
|
|
2898
2921
|
childIntercomTarget: childIntercomTarget ? (agent, index) => childIntercomTarget(agent, index) : undefined,
|
|
2899
2922
|
nestedRoute,
|
|
@@ -2905,6 +2928,8 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2905
2928
|
toolBudget: data.toolBudget,
|
|
2906
2929
|
usageBudget: data.usageBudget,
|
|
2907
2930
|
configToolBudget: data.configToolBudget,
|
|
2931
|
+
toolTimeoutMs: data.params?.toolTimeoutMs,
|
|
2932
|
+
configToolTimeoutMs: data.configToolTimeoutMs,
|
|
2908
2933
|
capabilityCeiling: data.capabilityCeiling,
|
|
2909
2934
|
runFanoutBudget: data.runFanoutBudget,
|
|
2910
2935
|
parentWorkflowRunId: params.workflowParentRunId,
|
|
@@ -2994,6 +3019,8 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
2994
3019
|
toolBudget: data.toolBudget,
|
|
2995
3020
|
usageBudget: data.usageBudget,
|
|
2996
3021
|
configToolBudget: data.configToolBudget,
|
|
3022
|
+
callToolTimeoutMs: data.params?.toolTimeoutMs,
|
|
3023
|
+
configToolTimeoutMs: data.configToolTimeoutMs,
|
|
2997
3024
|
permissions: deps.config.permissions,
|
|
2998
3025
|
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
2999
3026
|
capabilityCeiling: data.capabilityCeiling,
|
|
@@ -3061,6 +3088,8 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
3061
3088
|
toolBudget: data.toolBudget,
|
|
3062
3089
|
usageBudget: data.usageBudget,
|
|
3063
3090
|
configToolBudget: data.configToolBudget,
|
|
3091
|
+
callToolTimeoutMs: data.params?.toolTimeoutMs,
|
|
3092
|
+
configToolTimeoutMs: data.configToolTimeoutMs,
|
|
3064
3093
|
capabilityCeiling: data.capabilityCeiling,
|
|
3065
3094
|
runFanoutBudget: data.runFanoutBudget,
|
|
3066
3095
|
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
@@ -3148,6 +3177,10 @@ interface ForegroundParallelRunInput {
|
|
|
3148
3177
|
worktreeSetup?: WorktreeSetup;
|
|
3149
3178
|
timeoutMs?: number;
|
|
3150
3179
|
deadlineAt?: number;
|
|
3180
|
+
/** Per-call per-tool timeout, resolved separately for each task's agent. */
|
|
3181
|
+
toolTimeoutMs?: number;
|
|
3182
|
+
/** Raw global config.toolTimeoutMs, used by each task's resolver. */
|
|
3183
|
+
configToolTimeoutMs?: number;
|
|
3151
3184
|
turnBudget?: ResolvedTurnBudget;
|
|
3152
3185
|
usageBudget?: UsageBudgetConfig;
|
|
3153
3186
|
toolBudgets: (ResolvedToolBudget | undefined)[];
|
|
@@ -3239,10 +3272,128 @@ function buildParallelWorktreeTaskCwdError(
|
|
|
3239
3272
|
return formatWorktreeTaskCwdConflict(conflict, sharedCwd);
|
|
3240
3273
|
}
|
|
3241
3274
|
|
|
3242
|
-
function
|
|
3275
|
+
function resolveConfiguredSingleRunOutputBaseDir(deps: ExecutorDeps): string | undefined {
|
|
3243
3276
|
return deps.config.singleRunOutputBaseDir
|
|
3244
3277
|
? path.resolve(deps.expandTilde(deps.config.singleRunOutputBaseDir))
|
|
3245
|
-
:
|
|
3278
|
+
: undefined;
|
|
3279
|
+
}
|
|
3280
|
+
|
|
3281
|
+
function resolveSingleRunOutputBaseDir(deps: ExecutorDeps, artifactsDir: string, runId: string): string {
|
|
3282
|
+
return resolveConfiguredSingleRunOutputBaseDir(deps) ?? path.join(artifactsDir, "outputs", runId);
|
|
3283
|
+
}
|
|
3284
|
+
|
|
3285
|
+
function resolveWorkflowAggregateOutputPath(
|
|
3286
|
+
output: string | boolean | undefined,
|
|
3287
|
+
ctxCwd: string,
|
|
3288
|
+
workflowCwd: string,
|
|
3289
|
+
outputBaseDir: string,
|
|
3290
|
+
configuredOutputBaseDir: string | undefined,
|
|
3291
|
+
): string | undefined {
|
|
3292
|
+
if (typeof output === "string" && output && !path.isAbsolute(output) && !configuredOutputBaseDir) return path.resolve(workflowCwd, output);
|
|
3293
|
+
return resolveSingleOutputPath(output, ctxCwd, workflowCwd, outputBaseDir);
|
|
3294
|
+
}
|
|
3295
|
+
|
|
3296
|
+
function workflowChildDefaultOutput(aggregateOutputPath: string | undefined, artifactsDir: string, workflowRunId: string, workflowKey: string): string {
|
|
3297
|
+
if (aggregateOutputPath) {
|
|
3298
|
+
const parsed = path.parse(aggregateOutputPath);
|
|
3299
|
+
return path.join(parsed.dir, `${parsed.name}.${workflowKey}${parsed.ext || ".md"}`);
|
|
3300
|
+
}
|
|
3301
|
+
return path.join(artifactsDir, "outputs", workflowRunId, `${workflowKey}.md`);
|
|
3302
|
+
}
|
|
3303
|
+
|
|
3304
|
+
function writeWorkflowAggregateOutput(outputPath: string | undefined, text: string): string | undefined {
|
|
3305
|
+
if (!outputPath) return undefined;
|
|
3306
|
+
try {
|
|
3307
|
+
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
3308
|
+
fs.writeFileSync(outputPath, text, "utf-8");
|
|
3309
|
+
return undefined;
|
|
3310
|
+
} catch (error) {
|
|
3311
|
+
return `Output file error: ${outputPath}\n${error instanceof Error ? error.message : String(error)}`;
|
|
3312
|
+
}
|
|
3313
|
+
}
|
|
3314
|
+
|
|
3315
|
+
function appendWorkflowOutputWarning(text: string, warning: string | undefined): string {
|
|
3316
|
+
return warning ? `${text}\n\n${warning}` : text;
|
|
3317
|
+
}
|
|
3318
|
+
|
|
3319
|
+
function resolveWorkflowChildOutputPath(input: {
|
|
3320
|
+
ctxCwd: string;
|
|
3321
|
+
workflowCwd: string;
|
|
3322
|
+
artifactsDir: string;
|
|
3323
|
+
workflowRunId: string;
|
|
3324
|
+
aggregateOutputPath?: string;
|
|
3325
|
+
configuredOutputBaseDir?: string;
|
|
3326
|
+
discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[] };
|
|
3327
|
+
workflowAgentScope?: unknown;
|
|
3328
|
+
key: string;
|
|
3329
|
+
params: Record<string, unknown>;
|
|
3330
|
+
}): string | undefined {
|
|
3331
|
+
const rawOutput = input.params.output;
|
|
3332
|
+
const hasExplicitOutput = typeof rawOutput === "string" || typeof rawOutput === "boolean";
|
|
3333
|
+
const childCwd = typeof input.params.cwd === "string" ? resolveChildCwd(input.workflowCwd, input.params.cwd) : input.workflowCwd;
|
|
3334
|
+
const agentScope = resolveExecutionAgentScope(input.params.agentScope ?? input.workflowAgentScope);
|
|
3335
|
+
const agents = input.discoverAgents(childCwd, agentScope).agents;
|
|
3336
|
+
const agent = typeof input.params.agent === "string" ? resolveAgentName(input.params.agent, agents).agent : undefined;
|
|
3337
|
+
const agentOutput = typeof agent?.output === "string" ? agent.output : undefined;
|
|
3338
|
+
const output = rawOutput === true || rawOutput === "true"
|
|
3339
|
+
? agentOutput
|
|
3340
|
+
: hasExplicitOutput
|
|
3341
|
+
? rawOutput
|
|
3342
|
+
: workflowChildDefaultOutput(input.aggregateOutputPath, input.artifactsDir, input.workflowRunId, input.key);
|
|
3343
|
+
return resolveSingleOutputPath(output, input.ctxCwd, childCwd, input.configuredOutputBaseDir);
|
|
3344
|
+
}
|
|
3345
|
+
|
|
3346
|
+
function workflowChildOutputClaims(input: {
|
|
3347
|
+
ctxCwd: string;
|
|
3348
|
+
workflowCwd: string;
|
|
3349
|
+
artifactsDir: string;
|
|
3350
|
+
workflowRunId: string;
|
|
3351
|
+
aggregateOutputPath?: string;
|
|
3352
|
+
configuredOutputBaseDir?: string;
|
|
3353
|
+
discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[] };
|
|
3354
|
+
workflowAgentScope?: unknown;
|
|
3355
|
+
claimedOutputPaths: Map<string, string>;
|
|
3356
|
+
entries: Array<{ key: string; params: Record<string, unknown> }>;
|
|
3357
|
+
}): { error?: string; claims?: Map<string, string> } {
|
|
3358
|
+
const claims = new Map(input.claimedOutputPaths);
|
|
3359
|
+
for (const { key, params } of input.entries) {
|
|
3360
|
+
const resolved = resolveWorkflowChildOutputPath({ ...input, key, params });
|
|
3361
|
+
if (!resolved) continue;
|
|
3362
|
+
if (input.aggregateOutputPath && resolved === input.aggregateOutputPath) return { error: `Workflow child '${key}' output resolves to the workflow aggregate output path: ${resolved}. Use a distinct child output path.` };
|
|
3363
|
+
const previous = claims.get(resolved);
|
|
3364
|
+
if (previous) return { error: `Workflow children '${previous}' and '${key}' resolve output to the same path: ${resolved}. Use distinct child output paths.` };
|
|
3365
|
+
claims.set(resolved, key);
|
|
3366
|
+
}
|
|
3367
|
+
return { claims };
|
|
3368
|
+
}
|
|
3369
|
+
|
|
3370
|
+
function applyWorkflowChildOutputClaims(target: Map<string, string>, claims: Map<string, string>): void {
|
|
3371
|
+
target.clear();
|
|
3372
|
+
for (const [resolved, key] of claims) target.set(resolved, key);
|
|
3373
|
+
}
|
|
3374
|
+
|
|
3375
|
+
function prepareWorkflowChildLaunchParams(input: {
|
|
3376
|
+
workflowDefaults: SubagentParamsLike;
|
|
3377
|
+
childParams: Record<string, unknown>;
|
|
3378
|
+
parentWorkflowRunId: string;
|
|
3379
|
+
workflowKey: string;
|
|
3380
|
+
ctxCwd: string;
|
|
3381
|
+
workflowCwd: string;
|
|
3382
|
+
artifactsDir: string;
|
|
3383
|
+
aggregateOutputPath?: string;
|
|
3384
|
+
configuredOutputBaseDir?: string;
|
|
3385
|
+
discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[] };
|
|
3386
|
+
workflowAgentScope?: unknown;
|
|
3387
|
+
options?: { missionDetached?: boolean; suppressRoutineResultIntercom?: boolean; runFanoutBudget?: RunFanoutBudgetDescriptor };
|
|
3388
|
+
}): SubagentParamsLike {
|
|
3389
|
+
let childParams = input.childParams;
|
|
3390
|
+
if (input.childParams.output === undefined && input.childParams.resume === undefined) {
|
|
3391
|
+
childParams = { ...input.childParams, output: workflowChildDefaultOutput(input.aggregateOutputPath, input.artifactsDir, input.parentWorkflowRunId, input.workflowKey) };
|
|
3392
|
+
} else if (input.childParams.resume === undefined) {
|
|
3393
|
+
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 });
|
|
3394
|
+
if (resolvedOutput) childParams = { ...input.childParams, output: resolvedOutput };
|
|
3395
|
+
}
|
|
3396
|
+
return prepareWorkflowLaunchParams(input.workflowDefaults, childParams, input.parentWorkflowRunId, input.workflowKey, input.options);
|
|
3246
3397
|
}
|
|
3247
3398
|
|
|
3248
3399
|
function buildChainWorktreeTaskCwdError(chain: ChainStep[], sharedCwd: string): string | undefined {
|
|
@@ -3406,6 +3557,8 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
3406
3557
|
share: input.shareEnabled || undefined,
|
|
3407
3558
|
maxOutput: input.maxOutput,
|
|
3408
3559
|
timeoutMs: input.timeoutMs,
|
|
3560
|
+
toolTimeoutMs: input.toolTimeoutMs,
|
|
3561
|
+
configToolTimeoutMs: input.configToolTimeoutMs,
|
|
3409
3562
|
turnBudget: input.turnBudget,
|
|
3410
3563
|
...(model ? { model } : {}),
|
|
3411
3564
|
...(effectiveSkills !== undefined ? { skill: effectiveSkills } : {}),
|
|
@@ -3485,6 +3638,8 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
3485
3638
|
acceptanceContext: { mode: "parallel" },
|
|
3486
3639
|
onEffectivePrompt: input.foregroundControl ? (prompt) => updateLiveEffectivePrompt(input.foregroundControl!, index, prompt) : undefined,
|
|
3487
3640
|
timeoutMs: input.timeoutMs,
|
|
3641
|
+
toolTimeoutMs: input.toolTimeoutMs,
|
|
3642
|
+
configToolTimeoutMs: input.configToolTimeoutMs,
|
|
3488
3643
|
deadlineAt: input.deadlineAt,
|
|
3489
3644
|
turnBudget: input.turnBudget,
|
|
3490
3645
|
toolBudget: input.toolBudgets[index],
|
|
@@ -3612,7 +3767,9 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
3612
3767
|
...(task.model !== undefined ? { model: task.model } : {}),
|
|
3613
3768
|
}));
|
|
3614
3769
|
const modelOverrides: (string | undefined)[] = tasks.map((_, i) =>
|
|
3615
|
-
|
|
3770
|
+
agentConfigs[i]?.runner?.type === "external-cli"
|
|
3771
|
+
? undefined
|
|
3772
|
+
: resolveEffectiveSubagentModel(behaviorOverrides[i]?.model, agentConfigs[i]?.model, parentModel, availableModels, currentProvider, data.modelScope === undefined ? {} : { scope: data.modelScope }),
|
|
3616
3773
|
);
|
|
3617
3774
|
|
|
3618
3775
|
if (params.clarify === true && ctx.hasUI) {
|
|
@@ -3647,7 +3804,9 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
3647
3804
|
for (let i = 0; i < result.behaviorOverrides.length; i++) {
|
|
3648
3805
|
const override = result.behaviorOverrides[i];
|
|
3649
3806
|
if (override?.model !== undefined) {
|
|
3650
|
-
modelOverrides[i] =
|
|
3807
|
+
modelOverrides[i] = agentConfigs[i]?.runner?.type === "external-cli"
|
|
3808
|
+
? undefined
|
|
3809
|
+
: resolveEffectiveSubagentModel(override.model, agentConfigs[i]?.model, parentModel, availableModels, currentProvider, data.modelScope === undefined ? {} : { scope: data.modelScope });
|
|
3651
3810
|
behaviorOverrides[i]!.model = override.model;
|
|
3652
3811
|
}
|
|
3653
3812
|
if (override?.output !== undefined) behaviorOverrides[i]!.output = override.output;
|
|
@@ -3734,6 +3893,8 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
3734
3893
|
usageBudget: data.usageBudget,
|
|
3735
3894
|
toolBudget: data.toolBudget,
|
|
3736
3895
|
configToolBudget: data.configToolBudget,
|
|
3896
|
+
callToolTimeoutMs: data.params?.toolTimeoutMs,
|
|
3897
|
+
configToolTimeoutMs: data.configToolTimeoutMs,
|
|
3737
3898
|
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
3738
3899
|
runFanoutBudget: data.runFanoutBudget,
|
|
3739
3900
|
}));
|
|
@@ -3854,6 +4015,8 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
3854
4015
|
worktreeSetup,
|
|
3855
4016
|
timeoutMs: data.timeoutMs,
|
|
3856
4017
|
deadlineAt,
|
|
4018
|
+
toolTimeoutMs: data.params?.toolTimeoutMs,
|
|
4019
|
+
configToolTimeoutMs: data.configToolTimeoutMs,
|
|
3857
4020
|
turnBudget: data.turnBudget,
|
|
3858
4021
|
usageBudget: data.usageBudget,
|
|
3859
4022
|
toolBudgets,
|
|
@@ -4073,6 +4236,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
4073
4236
|
task: shouldForkAgent(contextPolicy, params.agent!) ? wrapForkTask(task) : task,
|
|
4074
4237
|
goal: task,
|
|
4075
4238
|
agentConfig,
|
|
4239
|
+
recoveryAgentConfig: data.recoveryAgents.find((agent) => agent.name === params.agent),
|
|
4076
4240
|
ctx: asyncCtx,
|
|
4077
4241
|
availableModels,
|
|
4078
4242
|
cwd: effectiveCwd,
|
|
@@ -4097,6 +4261,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
4097
4261
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
4098
4262
|
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
4099
4263
|
controlConfig,
|
|
4264
|
+
intercomBridge: params.intercomBridge,
|
|
4100
4265
|
controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
4101
4266
|
childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(id, agent, index) : undefined,
|
|
4102
4267
|
nestedRoute: data.nestedRoute,
|
|
@@ -4246,6 +4411,8 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
4246
4411
|
},
|
|
4247
4412
|
timeoutMs: data.timeoutMs,
|
|
4248
4413
|
deadlineAt,
|
|
4414
|
+
toolTimeoutMs: params.toolTimeoutMs,
|
|
4415
|
+
configToolTimeoutMs: data.configToolTimeoutMs,
|
|
4249
4416
|
turnBudget: data.turnBudget,
|
|
4250
4417
|
enforceHardTurnLimit: params.enforceHardTurnLimit,
|
|
4251
4418
|
toolBudget: effectiveToolBudget.toolBudget,
|
|
@@ -4324,8 +4491,15 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
4324
4491
|
}
|
|
4325
4492
|
|
|
4326
4493
|
if (r.detached) {
|
|
4494
|
+
const statusRecovery = `subagent({ action: "status", id: "${runId}" }) to recover the result; do not resume or launch a replacement while it remains detached.`;
|
|
4495
|
+
const blockingRecovery = `subagent_wait({ id: "${runId}" }). Use ${statusRecovery}`;
|
|
4496
|
+
const message = r.detachedReason === "intercom coordination"
|
|
4497
|
+
? `Detached for intercom coordination: ${params.agent}. Reply to the supervisor request first, then wait with ${blockingRecovery}`
|
|
4498
|
+
: r.detachedReason === "user request"
|
|
4499
|
+
? `Detached at user request: ${params.agent}. The child continues independently. Register a completion wake-up with subagent_wait({ id: "${runId}", nonBlocking: true }), or use ${statusRecovery}`
|
|
4500
|
+
: `Detached before task completion: ${params.agent}. Wait with ${blockingRecovery}`;
|
|
4327
4501
|
return {
|
|
4328
|
-
content: [{ type: "text", text:
|
|
4502
|
+
content: [{ type: "text", text: message }],
|
|
4329
4503
|
details,
|
|
4330
4504
|
};
|
|
4331
4505
|
}
|
|
@@ -4369,17 +4543,28 @@ function duplicateSubagentCallResult(params: SubagentParamsLike): AgentToolResul
|
|
|
4369
4543
|
|
|
4370
4544
|
const workflowLaunchObservers = new WeakMap<object, (launch: { agent: string; sessionFile?: string; async: boolean; runId?: string }) => void>();
|
|
4371
4545
|
|
|
4546
|
+
/**
|
|
4547
|
+
* Terminal-mission retention can remove a mission while its children still
|
|
4548
|
+
* report. Remember it to prevent later heartbeats from warning repeatedly.
|
|
4549
|
+
*/
|
|
4550
|
+
const missionsMissingFromStore = new Set<string>();
|
|
4551
|
+
|
|
4372
4552
|
function recordMissionWorkflowChild(
|
|
4373
4553
|
binding: MissionLaunchBinding | undefined,
|
|
4374
4554
|
workflowRunId: string,
|
|
4375
4555
|
key: string,
|
|
4376
4556
|
update: Omit<MissionWorkflowChildUpdate, "workflowRunId" | "key">,
|
|
4377
4557
|
): void {
|
|
4378
|
-
if (!binding) return;
|
|
4558
|
+
if (!binding || missionsMissingFromStore.has(binding.missionId)) return;
|
|
4379
4559
|
const { task: _task, ...durableUpdate } = update;
|
|
4380
4560
|
try {
|
|
4381
4561
|
updateMission(binding.location, binding.missionId, { upsertWorkflowChildren: [{ workflowRunId, key, ...durableUpdate }] });
|
|
4382
4562
|
} catch (error) {
|
|
4563
|
+
if (error instanceof MissionNotFoundError) {
|
|
4564
|
+
missionsMissingFromStore.add(binding.missionId);
|
|
4565
|
+
console.warn(`[pi-subagents] Mission '${binding.missionId}' is no longer in the mission store; stopped recording its workflow children. Terminal-mission retention can prune a mission while its run is still active.`);
|
|
4566
|
+
return;
|
|
4567
|
+
}
|
|
4383
4568
|
console.warn(`[pi-subagents] Failed to record mission workflow child '${key}': ${error instanceof Error ? error.message : String(error)}`);
|
|
4384
4569
|
}
|
|
4385
4570
|
}
|
|
@@ -4449,6 +4634,7 @@ function workflowChildResult(key: string, result: AgentToolResult<Details>): Wor
|
|
|
4449
4634
|
...(result.details.runId || result.details.asyncId ? { runId: result.details.runId ?? result.details.asyncId } : {}),
|
|
4450
4635
|
output,
|
|
4451
4636
|
...(!ok ? { error: receiptOutput || output || "Child run failed." } : {}),
|
|
4637
|
+
...(detached ? { detached: true } : {}),
|
|
4452
4638
|
...(structured.length === 1 ? { structuredOutput: structured[0] } : structured.length > 1 ? { structuredOutput: structured } : {}),
|
|
4453
4639
|
artifactPaths: [...artifactPaths],
|
|
4454
4640
|
results: result.details.results,
|
|
@@ -4469,16 +4655,19 @@ export function prepareWorkflowLaunchParams(
|
|
|
4469
4655
|
const timeoutMs = childParams.timeoutMs ?? childParams.maxRuntimeMs ?? workflowDefaults.timeoutMs ?? workflowDefaults.maxRuntimeMs;
|
|
4470
4656
|
const turnBudget = childParams.turnBudget ?? workflowDefaults.turnBudget;
|
|
4471
4657
|
const toolBudget = childParams.toolBudget ?? workflowDefaults.toolBudget;
|
|
4658
|
+
const intercomBridge = childParams.intercomBridge ?? workflowDefaults.intercomBridge;
|
|
4472
4659
|
return {
|
|
4473
4660
|
action: "resume",
|
|
4474
4661
|
id: childParams.resume.trim(),
|
|
4475
4662
|
message: typeof childParams.task === "string" ? childParams.task.trim() : "",
|
|
4476
4663
|
workflowParentRunId: parentWorkflowRunId,
|
|
4477
4664
|
workflowKey,
|
|
4665
|
+
...(options.runFanoutBudget ? { runFanoutBudget: { ...options.runFanoutBudget, parentPath: `${options.runFanoutBudget.parentPath ? `${options.runFanoutBudget.parentPath}/` : ""}workflow[${workflowKey}]` } } : {}),
|
|
4478
4666
|
...(options.missionDetached ? { mission: false } : {}),
|
|
4479
4667
|
...(timeoutMs !== undefined ? { timeoutMs: timeoutMs as number } : {}),
|
|
4480
4668
|
...(turnBudget !== undefined ? { turnBudget: turnBudget as TurnBudgetConfig } : {}),
|
|
4481
4669
|
...(toolBudget !== undefined ? { toolBudget: toolBudget as ToolBudgetConfig } : {}),
|
|
4670
|
+
...(intercomBridge !== undefined ? { intercomBridge: intercomBridge as IntercomBridgeConfig } : {}),
|
|
4482
4671
|
};
|
|
4483
4672
|
}
|
|
4484
4673
|
const launchParams = {
|
|
@@ -4665,6 +4854,17 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4665
4854
|
const workflowUsageBudget = validateUsageBudgetConfig(requestParams.usageBudget ?? deps.config.usageBudget, requestParams.usageBudget ? "usageBudget" : "config.usageBudget");
|
|
4666
4855
|
if (workflowUsageBudget.error) return buildRequestedModeError(requestParams, workflowUsageBudget.error);
|
|
4667
4856
|
const workflowCwd = resolveRequestedCwd(parentCwd, requestParams.cwd);
|
|
4857
|
+
const workflowArtifactConfig: ArtifactConfig = omitUndefinedProperties({
|
|
4858
|
+
...DEFAULT_ARTIFACT_CONFIG,
|
|
4859
|
+
enabled: requestParams.artifacts !== false,
|
|
4860
|
+
dir: deps.config.artifactDir ?? DEFAULT_ARTIFACT_CONFIG.dir,
|
|
4861
|
+
});
|
|
4862
|
+
const workflowArtifactsDir = getArtifactsDir(ctx.sessionManager.getSessionFile() ?? null, workflowCwd, workflowArtifactConfig.dir);
|
|
4863
|
+
if (workflowArtifactConfig.dir === "project" && !warnedArtifactPackageDirs.has(workflowCwd)) {
|
|
4864
|
+
warnedArtifactPackageDirs.add(workflowCwd);
|
|
4865
|
+
const warning = getProjectArtifactPackagingWarning(workflowCwd);
|
|
4866
|
+
if (warning) console.warn(`[pi-subagents] ${warning}`);
|
|
4867
|
+
}
|
|
4668
4868
|
const workflowPrompts = { render: (ref: string, vars?: unknown) => renderWorkflowPrompt(ref, vars, workflowCwd) };
|
|
4669
4869
|
const chatProgressResult = resolveWorkflowChatProgress({ requested: requestParams.chatProgress, parentCwd, workflowCwd, background: requestParams.async !== false });
|
|
4670
4870
|
if (chatProgressResult.error) return { content: [{ type: "text", text: chatProgressResult.error }], isError: true, details: { mode: "workflow", results: [] } };
|
|
@@ -4743,7 +4943,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4743
4943
|
if (workflowRunId) {
|
|
4744
4944
|
const toolCallId = _id;
|
|
4745
4945
|
const asyncDir = path.join(DIRS.async, workflowRunId);
|
|
4746
|
-
const resultPath =
|
|
4946
|
+
const resultPath = resultFilePath(DIRS.results, workflowRunId);
|
|
4747
4947
|
const statusPath = path.join(asyncDir, "status.json");
|
|
4748
4948
|
const eventsPath = path.join(asyncDir, "events.jsonl");
|
|
4749
4949
|
const startedAt = Date.now();
|
|
@@ -4782,7 +4982,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4782
4982
|
status.lastUpdate = Date.now();
|
|
4783
4983
|
writeAtomicJson(statusPath, status);
|
|
4784
4984
|
if (indexedState !== status.state) {
|
|
4785
|
-
updateActiveRunIndex(asyncDir, status.state);
|
|
4985
|
+
updateActiveRunIndex(asyncDir, status.state, status.toolCallId);
|
|
4786
4986
|
indexedState = status.state;
|
|
4787
4987
|
}
|
|
4788
4988
|
const job = deps.state.asyncJobs.get(workflowRunId);
|
|
@@ -4807,6 +5007,17 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4807
5007
|
job.workflow = status.workflow;
|
|
4808
5008
|
}
|
|
4809
5009
|
};
|
|
5010
|
+
const writeWorkflowResult = (payload: Record<string, unknown>): boolean => {
|
|
5011
|
+
try {
|
|
5012
|
+
writeAsyncResultFile(resultPath, payload);
|
|
5013
|
+
return true;
|
|
5014
|
+
} catch (error) {
|
|
5015
|
+
const message = `Failed to write async workflow result ${resultPath}: ${error instanceof Error ? error.message : String(error)}`;
|
|
5016
|
+
console.error(message, error);
|
|
5017
|
+
appendWorkflowEvent({ type: "subagent.workflow.result_write_failed", error: message });
|
|
5018
|
+
return false;
|
|
5019
|
+
}
|
|
5020
|
+
};
|
|
4810
5021
|
const projectWorkflowActivity = () => {
|
|
4811
5022
|
const steps = status.steps ?? [];
|
|
4812
5023
|
const runningSteps = steps.filter((step) => step.status === "running");
|
|
@@ -4828,7 +5039,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4828
5039
|
status.toolCount = toolCounts.length > 0 ? toolCounts.reduce((total, count) => total + count, 0) : undefined;
|
|
4829
5040
|
status.currentStep = runningSteps.length === 1 ? steps.indexOf(runningSteps[0]!) : undefined;
|
|
4830
5041
|
};
|
|
4831
|
-
const workflowJob: AsyncJobState = { asyncId: workflowRunId, asyncDir, cwd: workflowCwd, status: "running", sessionId: currentSessionId ?? undefined, mode: "workflow", agents: [], steps: [], startedAt, updatedAt: startedAt, ...(timeout !== undefined ? { timeoutMs: timeout, deadlineAt: startedAt + timeout } : {}), workflow: status.workflow };
|
|
5042
|
+
const workflowJob: AsyncJobState = { asyncId: workflowRunId, asyncDir, toolCallId, cwd: workflowCwd, status: "running", sessionId: currentSessionId ?? undefined, mode: "workflow", agents: [], steps: [], startedAt, updatedAt: startedAt, ...(timeout !== undefined ? { timeoutMs: timeout, deadlineAt: startedAt + timeout } : {}), workflow: status.workflow };
|
|
4832
5043
|
deps.state.asyncJobs.set(workflowRunId, workflowJob);
|
|
4833
5044
|
deps.state.fleetJobs ??= new Map();
|
|
4834
5045
|
deps.state.fleetJobs.set(workflowRunId, workflowJob);
|
|
@@ -4838,6 +5049,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4838
5049
|
void Promise.resolve().then(async () => {
|
|
4839
5050
|
const workflowResults: SingleResult[] = [];
|
|
4840
5051
|
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;
|
|
5052
|
+
const workflowOutput = typeof workflowChildDefaults.output === "string" || typeof workflowChildDefaults.output === "boolean" ? workflowChildDefaults.output : undefined;
|
|
5053
|
+
const configuredOutputBaseDir = resolveConfiguredSingleRunOutputBaseDir(deps);
|
|
5054
|
+
const workflowAggregateOutputPath = resolveWorkflowAggregateOutputPath(workflowOutput, ctx.cwd, workflowCwd, resolveSingleRunOutputBaseDir(deps, workflowArtifactsDir, workflowRunId), configuredOutputBaseDir);
|
|
5055
|
+
const claimedOutputPaths = new Map<string, string>();
|
|
4841
5056
|
const workflowSteps = new Map<string, NonNullable<AsyncStatus["steps"]>[number]>();
|
|
4842
5057
|
let projectedTraceLength = 0;
|
|
4843
5058
|
let projectedTraceTail: NonNullable<Details["workflow"]>["trace"][number] | undefined;
|
|
@@ -4857,12 +5072,24 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4857
5072
|
if (entry.operation !== "run") continue;
|
|
4858
5073
|
const existing = workflowSteps.get(entry.key);
|
|
4859
5074
|
if (entry.state === "reused" && existing) continue;
|
|
4860
|
-
const mapped = entry.state === "started" || entry.state === "reused"
|
|
5075
|
+
const mapped = entry.state === "started" || entry.state === "reused"
|
|
5076
|
+
? "running"
|
|
5077
|
+
: entry.state === "completed"
|
|
5078
|
+
? "completed"
|
|
5079
|
+
: entry.state === "stopped"
|
|
5080
|
+
? "stopped"
|
|
5081
|
+
: entry.state === "detached"
|
|
5082
|
+
? "paused"
|
|
5083
|
+
: "failed";
|
|
4861
5084
|
if (existing) {
|
|
4862
5085
|
existing.status = mapped;
|
|
4863
5086
|
if (entry.agent) existing.agent = entry.agent;
|
|
4864
5087
|
if (entry.runId) existing.runId = entry.runId;
|
|
4865
5088
|
if (entry.state === "failed" && !entry.runId && existing.async === undefined) existing.async = false;
|
|
5089
|
+
if (entry.state === "detached") existing.activityState = "needs_attention";
|
|
5090
|
+
else if (existing.status !== "running") delete existing.activityState;
|
|
5091
|
+
if (entry.state === "stopped") existing.stopped = true;
|
|
5092
|
+
else delete existing.stopped;
|
|
4866
5093
|
if (entry.error === undefined) delete existing.error;
|
|
4867
5094
|
else existing.error = entry.error;
|
|
4868
5095
|
if (entry.durationMs === undefined) delete existing.durationMs;
|
|
@@ -4877,6 +5104,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4877
5104
|
startedAt: Date.now(),
|
|
4878
5105
|
...(entry.runId ? { runId: entry.runId } : {}),
|
|
4879
5106
|
...(entry.state === "failed" && !entry.runId ? { async: false } : {}),
|
|
5107
|
+
...(entry.state === "detached" ? { activityState: "needs_attention" as const } : {}),
|
|
5108
|
+
...(entry.state === "stopped" ? { stopped: true } : {}),
|
|
4880
5109
|
};
|
|
4881
5110
|
status.steps?.push(step);
|
|
4882
5111
|
workflowSteps.set(entry.key, step);
|
|
@@ -4897,7 +5126,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4897
5126
|
...(workflowState ? { state: workflowState } : {}),
|
|
4898
5127
|
onTrace: updateTrace,
|
|
4899
5128
|
admit: (calls) => {
|
|
5129
|
+
const outputClaims = workflowChildOutputClaims({ ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, workflowRunId, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: deps.discoverAgents, workflowAgentScope: workflowChildDefaults.agentScope, claimedOutputPaths, entries: calls });
|
|
5130
|
+
if (outputClaims.error) throw new Error(outputClaims.error);
|
|
4900
5131
|
status.runFanoutBudget = claimRunFanoutBatch(workflowFanoutBudget, calls.map(({ key }) => `workflow[${key}]`));
|
|
5132
|
+
if (outputClaims.claims) applyWorkflowChildOutputClaims(claimedOutputPaths, outputClaims.claims);
|
|
4901
5133
|
persist();
|
|
4902
5134
|
},
|
|
4903
5135
|
onEmit: (emits) => {
|
|
@@ -4921,7 +5153,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4921
5153
|
});
|
|
4922
5154
|
const result = await runMissionWorkflowChild(missionBinding, workflowRunId, key, childPhase, () => {
|
|
4923
5155
|
const childRequest = bindMissionWorkflowChildAsyncLaunch(
|
|
4924
|
-
{ ...
|
|
5156
|
+
{ ...prepareWorkflowChildLaunchParams({ workflowDefaults: workflowChildDefaults, childParams, parentWorkflowRunId: workflowRunId, workflowKey: key, ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: deps.discoverAgents, workflowAgentScope: workflowChildDefaults.agentScope, options: { missionDetached: detachWorkflowChildMissions, runFanoutBudget: workflowFanoutBudget } }), runFanoutAdmitted: admission.admitted },
|
|
4925
5157
|
missionBinding,
|
|
4926
5158
|
deps.asyncByDefault,
|
|
4927
5159
|
);
|
|
@@ -4939,7 +5171,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4939
5171
|
return execute(randomUUID(), childRequest, workflowSignal, (update) => {
|
|
4940
5172
|
const progress = update.details.progress?.[0];
|
|
4941
5173
|
const step = status.steps?.find((candidate) => candidate.workflowKey === key);
|
|
4942
|
-
if (!progress || !step) return;
|
|
5174
|
+
if (!progress || !step || step.stopped) return;
|
|
4943
5175
|
step.status = progress.status === "completed" ? "completed" : progress.status === "failed" ? "failed" : "running";
|
|
4944
5176
|
step.activityState = progress.activityState;
|
|
4945
5177
|
step.lastActivityAt = progress.lastActivityAt;
|
|
@@ -4991,18 +5223,32 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4991
5223
|
const returnPreview = formatWorkflowValue(workflow.value).slice(0, 1_000);
|
|
4992
5224
|
const emitPreview = workflow.emits.length > 0 ? ` Emitted: ${workflow.emits.map(formatWorkflowValue).join(", ").slice(0, 1_000)}` : "";
|
|
4993
5225
|
const summary = `Workflow completed with ${workflow.children.length} child run(s). Return: ${returnPreview}${emitPreview} Trace: ${workflow.trace.length} event(s).`;
|
|
5226
|
+
const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, summary);
|
|
5227
|
+
const resultSummary = appendWorkflowOutputWarning(summary, outputWarning);
|
|
4994
5228
|
const workflowUsage = sumResultsUsage(workflowResults);
|
|
4995
5229
|
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) };
|
|
4996
|
-
|
|
5230
|
+
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, asyncDir, cwd: workflowCwd, sessionId: currentSessionId, timestamp: Date.now(), durationMs: Date.now() - startedAt })) return;
|
|
4997
5231
|
persist();
|
|
4998
|
-
appendWorkflowEvent({ type: "subagent.workflow.completed", state:
|
|
5232
|
+
appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, ...(status.error ? { error: status.error } : {}) });
|
|
4999
5233
|
} catch (error) {
|
|
5000
5234
|
const partial = error instanceof WorkflowScriptError ? error.partial : { trace: [], emits: [], console: [], children: [] };
|
|
5001
5235
|
const stopped = controller.signal.aborted;
|
|
5002
|
-
|
|
5003
|
-
|
|
5236
|
+
const detachedChildKeys = new Set(partial.children.filter((child) => child.detached).map((child) => child.key));
|
|
5237
|
+
const hasRealFailedChild = partial.children.some((child) => !child.ok && !child.detached);
|
|
5238
|
+
const pauseForDetached = !stopped && error instanceof WorkflowScriptError && error.errorKind === "detached-child" && detachedChildKeys.size > 0 && !hasRealFailedChild;
|
|
5239
|
+
const state = stopped ? "stopped" : pauseForDetached ? "paused" : "failed";
|
|
5240
|
+
for (const step of status.steps ?? []) {
|
|
5241
|
+
if (step.workflowKey && detachedChildKeys.has(step.workflowKey)) {
|
|
5242
|
+
step.status = "paused";
|
|
5243
|
+
step.activityState = "needs_attention";
|
|
5244
|
+
}
|
|
5245
|
+
}
|
|
5246
|
+
status = compactOptional<AsyncStatus>({ ...status, state, stopped: stopped || undefined, activityState: pauseForDetached ? "needs_attention" : undefined, error: error instanceof Error ? error.message : String(error), endedAt: Date.now(), workflow: { trace: partial.trace, emits: partial.emits, console: partial.console } });
|
|
5247
|
+
const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, status.error ?? (pauseForDetached ? "Workflow paused." : "Workflow failed."));
|
|
5248
|
+
const resultSummary = appendWorkflowOutputWarning(status.error ?? (pauseForDetached ? "Workflow paused." : "Workflow failed."), outputWarning);
|
|
5249
|
+
if (!writeWorkflowResult({ id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: false, state: status.state, summary: resultSummary, error: 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 ? { detached: true } : {}), ...(child.artifactPaths[0] ? { artifactPaths: { outputPath: child.artifactPaths[0] } } : {}) })), workflow: status.workflow, asyncDir, cwd: workflowCwd, sessionId: currentSessionId, timestamp: Date.now(), durationMs: Date.now() - startedAt })) return;
|
|
5004
5250
|
persist();
|
|
5005
|
-
appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, error: status.error });
|
|
5251
|
+
appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, error: status.error, ...(status.activityState ? { activityState: status.activityState } : {}) });
|
|
5006
5252
|
} finally {
|
|
5007
5253
|
deps.state.workflowControllers?.delete(workflowRunId);
|
|
5008
5254
|
deps.state.activeAsyncCapacity = workflowCapacity?.reconcile(new Set(deps.state.workflowControllers?.keys() ?? []))
|
|
@@ -5015,6 +5261,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5015
5261
|
}, workflowFanoutBudget));
|
|
5016
5262
|
}
|
|
5017
5263
|
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;
|
|
5264
|
+
const workflowOutput = typeof workflowChildDefaults.output === "string" || typeof workflowChildDefaults.output === "boolean" ? workflowChildDefaults.output : undefined;
|
|
5265
|
+
const configuredOutputBaseDir = resolveConfiguredSingleRunOutputBaseDir(deps);
|
|
5266
|
+
const workflowAggregateOutputPath = resolveWorkflowAggregateOutputPath(workflowOutput, ctx.cwd, workflowCwd, resolveSingleRunOutputBaseDir(deps, workflowArtifactsDir, _id), configuredOutputBaseDir);
|
|
5267
|
+
const claimedOutputPaths = new Map<string, string>();
|
|
5018
5268
|
const workflowResults: SingleResult[] = [];
|
|
5019
5269
|
let liveWorkflow: NonNullable<Details["workflow"]> = { trace: [], emits: [], console: [] };
|
|
5020
5270
|
const sendWorkflowProgress = () => {
|
|
@@ -5033,7 +5283,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5033
5283
|
sendWorkflowProgress();
|
|
5034
5284
|
},
|
|
5035
5285
|
admit: (calls) => {
|
|
5286
|
+
const outputClaims = workflowChildOutputClaims({ ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, workflowRunId: _id, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: deps.discoverAgents, workflowAgentScope: workflowChildDefaults.agentScope, claimedOutputPaths, entries: calls });
|
|
5287
|
+
if (outputClaims.error) throw new Error(outputClaims.error);
|
|
5036
5288
|
claimRunFanoutBatch(workflowFanoutBudget, calls.map(({ key }) => `workflow[${key}]`));
|
|
5289
|
+
if (outputClaims.claims) applyWorkflowChildOutputClaims(claimedOutputPaths, outputClaims.claims);
|
|
5037
5290
|
},
|
|
5038
5291
|
onEmit: (emits) => {
|
|
5039
5292
|
liveWorkflow = { ...liveWorkflow, emits };
|
|
@@ -5054,7 +5307,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5054
5307
|
});
|
|
5055
5308
|
const result = await runMissionWorkflowChild(missionBinding, _id, key, childPhase, () => {
|
|
5056
5309
|
const childRequest = bindMissionWorkflowChildAsyncLaunch(
|
|
5057
|
-
{ ...
|
|
5310
|
+
{ ...prepareWorkflowChildLaunchParams({ workflowDefaults: workflowChildDefaults, childParams, parentWorkflowRunId: _id, workflowKey: key, ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: deps.discoverAgents, workflowAgentScope: workflowChildDefaults.agentScope, options: { missionDetached: detachWorkflowChildMissions, suppressRoutineResultIntercom: chatProgress.mode === "live-card", runFanoutBudget: workflowFanoutBudget } }), runFanoutAdmitted: admission.admitted },
|
|
5058
5311
|
missionBinding,
|
|
5059
5312
|
deps.asyncByDefault,
|
|
5060
5313
|
);
|
|
@@ -5095,8 +5348,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5095
5348
|
if (workflow.emits.length > 0) sections.push(`Emitted:\n${workflow.emits.map(formatWorkflowValue).join("\n")}`);
|
|
5096
5349
|
if (workflow.console.length > 0) sections.push(`Console:\n${workflow.console.map((entry) => `[${entry.level}] ${entry.text}`).join("\n")}`);
|
|
5097
5350
|
if (traceLines.length > 0) sections.push(`Call trace:\n${traceLines.join("\n")}`);
|
|
5351
|
+
const workflowText = sections.join("\n\n");
|
|
5352
|
+
const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, workflowText);
|
|
5353
|
+
const displayText = appendWorkflowOutputWarning(workflowText, outputWarning);
|
|
5098
5354
|
return attachWorkflowMission(withRunFanoutBudget({
|
|
5099
|
-
content: [{ type: "text", text:
|
|
5355
|
+
content: [{ type: "text", text: displayText }],
|
|
5100
5356
|
details: compactOptional<Details>({ mode: "workflow", runId: _id, results: workflow.children.flatMap((child) => (child.results ?? []) as SingleResult[]), totalChildUsage: sumResultsUsage(workflowResults), totalCost: sumResultsCost(workflowResults), usageBudget: usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)), workflow: { value: workflow.value, trace: workflow.trace, emits: workflow.emits, console: workflow.console }, chatProgress }),
|
|
5101
5357
|
}, workflowFanoutBudget));
|
|
5102
5358
|
} catch (error) {
|
|
@@ -5107,8 +5363,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5107
5363
|
if (partial.emits.length > 0) sections.push(`Emitted:\n${partial.emits.map(formatWorkflowValue).join("\n")}`);
|
|
5108
5364
|
if (partial.console.length > 0) sections.push(`Console:\n${partial.console.map((entry) => `[${entry.level}] ${entry.text}`).join("\n")}`);
|
|
5109
5365
|
if (traceLines.length > 0) sections.push(`Call trace:\n${traceLines.join("\n")}`);
|
|
5366
|
+
const workflowText = sections.join("\n\n");
|
|
5367
|
+
const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, workflowText);
|
|
5368
|
+
const displayText = appendWorkflowOutputWarning(workflowText, outputWarning);
|
|
5110
5369
|
return attachWorkflowMission(withRunFanoutBudget({
|
|
5111
|
-
content: [{ type: "text", text:
|
|
5370
|
+
content: [{ type: "text", text: displayText }],
|
|
5112
5371
|
isError: true,
|
|
5113
5372
|
details: compactOptional<Details>({ mode: "workflow", runId: _id, results: partial.children.flatMap((child) => (child.results ?? []) as SingleResult[]), totalChildUsage: sumResultsUsage(workflowResults), totalCost: sumResultsCost(workflowResults), usageBudget: usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)), workflow: { trace: partial.trace, emits: partial.emits, console: partial.console }, chatProgress }),
|
|
5114
5373
|
}, workflowFanoutBudget));
|
|
@@ -5372,11 +5631,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5372
5631
|
details: { mode: "management", results: [], spawnBudget, activeAsyncCapacity },
|
|
5373
5632
|
};
|
|
5374
5633
|
}
|
|
5375
|
-
if (action === "status") {
|
|
5634
|
+
if (action === "status" || action === "debug.run") {
|
|
5376
5635
|
if (!preserveActiveSession) deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
5377
5636
|
const targetRunId = paramsWithResolvedCwd.id ?? paramsWithResolvedCwd.runId;
|
|
5378
5637
|
const hasDirectoryTarget = Boolean(paramsWithResolvedCwd.dir);
|
|
5379
|
-
const targetLabel = formatStatusTargetLabel(paramsWithResolvedCwd, targetRunId);
|
|
5638
|
+
const targetLabel = action === "debug.run" ? "Debug run" : formatStatusTargetLabel(paramsWithResolvedCwd, targetRunId);
|
|
5380
5639
|
const withBudget = (result: AgentToolResult<Details>) => {
|
|
5381
5640
|
const budgeted = withSpawnBudgetStatus(result, deps.state, deps.config, deps.state.currentSessionId);
|
|
5382
5641
|
return {
|
|
@@ -5388,6 +5647,15 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5388
5647
|
};
|
|
5389
5648
|
const nestedScope = nestedResolutionScopeForExecutor(deps);
|
|
5390
5649
|
const sessionRoots = trustedSessionRootsForStatus(ctx, deps);
|
|
5650
|
+
if (action === "debug.run") {
|
|
5651
|
+
if (!targetRunId && !hasDirectoryTarget) {
|
|
5652
|
+
return withBudget({ content: [{ type: "text", text: "action='debug.run' requires id, runId, or dir." }], isError: true, details: { mode: "management", results: [] } });
|
|
5653
|
+
}
|
|
5654
|
+
if (paramsWithResolvedCwd.view) {
|
|
5655
|
+
return withBudget({ content: [{ type: "text", text: "action='debug.run' does not support status views." }], isError: true, details: { mode: "management", results: [] } });
|
|
5656
|
+
}
|
|
5657
|
+
return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots })));
|
|
5658
|
+
}
|
|
5391
5659
|
if (paramsWithResolvedCwd.view === "fleet") {
|
|
5392
5660
|
return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots })));
|
|
5393
5661
|
}
|
|
@@ -5777,6 +6045,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5777
6045
|
const sessionName = resolveIntercomSessionTarget(deps.pi.getSessionName(), ctx.sessionManager.getSessionId());
|
|
5778
6046
|
const intercomBridge = resolveIntercomBridge({
|
|
5779
6047
|
config: deps.config.intercomBridge,
|
|
6048
|
+
override: effectiveParams.intercomBridge,
|
|
5780
6049
|
context: effectiveParams.context ?? (contextPolicy.usesFork ? "fork" : undefined),
|
|
5781
6050
|
orchestratorTarget: sessionName,
|
|
5782
6051
|
});
|
|
@@ -5825,6 +6094,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5825
6094
|
const parentModel = requestParentModel;
|
|
5826
6095
|
prepareForkThinking = (agentName, index, modelOverride) => {
|
|
5827
6096
|
const agentConfig = agents.find((agent) => agent.name === agentName);
|
|
6097
|
+
if (agentConfig?.runner?.type === "external-cli") {
|
|
6098
|
+
forkThinkingRequirements.set(index, true);
|
|
6099
|
+
return;
|
|
6100
|
+
}
|
|
5828
6101
|
const primaryModel = resolveEffectiveSubagentModel(
|
|
5829
6102
|
modelOverride,
|
|
5830
6103
|
agentConfig?.model,
|
|
@@ -6039,6 +6312,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6039
6312
|
signal,
|
|
6040
6313
|
onUpdate: onUpdateWithContext,
|
|
6041
6314
|
agents,
|
|
6315
|
+
recoveryAgents: discoveredAgents,
|
|
6042
6316
|
runId,
|
|
6043
6317
|
shareEnabled,
|
|
6044
6318
|
sessionRoot,
|
|
@@ -6060,6 +6334,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6060
6334
|
usageBudget: usageBudget.budget,
|
|
6061
6335
|
allowZeroToolBudget,
|
|
6062
6336
|
configToolBudget: configToolBudget.toolBudget,
|
|
6337
|
+
configToolTimeoutMs: deps.config.toolTimeoutMs,
|
|
6063
6338
|
contextPolicy,
|
|
6064
6339
|
modelScope,
|
|
6065
6340
|
parentModel: requestParentModel,
|
|
@@ -6229,7 +6504,12 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6229
6504
|
};
|
|
6230
6505
|
|
|
6231
6506
|
let nestedForegroundStarted = false;
|
|
6507
|
+
let asyncLaunchFailed = false;
|
|
6232
6508
|
try {
|
|
6509
|
+
if (effectiveAsync) {
|
|
6510
|
+
deps.state.liveAsyncSessionRoots ??= new Map();
|
|
6511
|
+
deps.state.liveAsyncSessionRoots.set(asyncRunId, sessionRoot);
|
|
6512
|
+
}
|
|
6233
6513
|
if (workflowLaunchObserver) {
|
|
6234
6514
|
const singleTask = hasTasks && effectiveParams.tasks?.length === 1 ? effectiveParams.tasks[0] : undefined;
|
|
6235
6515
|
const launch = hasSingle
|
|
@@ -6243,7 +6523,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6243
6523
|
}
|
|
6244
6524
|
}
|
|
6245
6525
|
const asyncResult = runAsyncPath(execData, deps);
|
|
6246
|
-
if (asyncResult)
|
|
6526
|
+
if (asyncResult) {
|
|
6527
|
+
asyncLaunchFailed = asyncResult.isError === true;
|
|
6528
|
+
return attachMission(withRunFanoutBudget(withResolvedContext(withForkThinkingNotes(asyncResult, forkThinkingDowngrades), contextPolicy.contextSummary), runFanoutBudget));
|
|
6529
|
+
}
|
|
6247
6530
|
if (foregroundControl) {
|
|
6248
6531
|
writeNestedForegroundEvent("subagent.nested.started");
|
|
6249
6532
|
nestedForegroundStarted = true;
|
|
@@ -6265,10 +6548,12 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6265
6548
|
return attachMission(withRunFanoutBudget(withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary), runFanoutBudget, { annotateContent: runFanoutAnnotateContent }));
|
|
6266
6549
|
}
|
|
6267
6550
|
} catch (error) {
|
|
6551
|
+
asyncLaunchFailed = effectiveAsync;
|
|
6268
6552
|
const errorResult = withForkThinkingNotes(toExecutionErrorResult(effectiveParams, error, contextPolicy.contextSummary), forkThinkingDowngrades);
|
|
6269
6553
|
if (nestedForegroundStarted) writeNestedForegroundEvent("subagent.nested.completed", errorResult);
|
|
6270
6554
|
return attachMission(errorResult);
|
|
6271
6555
|
} finally {
|
|
6556
|
+
if (effectiveAsync && (asyncLaunchFailed || (activeAsyncCapacity && !activeAsyncCapacity.owner.runnerStartedAt))) deps.state.liveAsyncSessionRoots?.delete(asyncRunId);
|
|
6272
6557
|
if (activeAsyncCapacity && !activeAsyncCapacity.owner.runnerStartedAt) activeAsyncCapacity.rollback();
|
|
6273
6558
|
if (foregroundControl) {
|
|
6274
6559
|
settleForegroundSchedulingOwner(foregroundControl);
|